From f561120295d392678a19d5a8a578da272a082bc0 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Sat, 7 Jan 2023 23:40:02 +0100 Subject: [PATCH 0001/4092] Remove info labels, since they are no longer present in this component in thew new design --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 356 ++++++++++++++++++ 1 file changed, 356 insertions(+) create mode 100644 osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs new file mode 100644 index 0000000000..4d1a2133fd --- /dev/null +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -0,0 +1,356 @@ +// 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.Collections.Generic; +using System.Threading; +using osuTK; +using osuTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Effects; +using osu.Framework.Localisation; +using osu.Game.Configuration; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Screens.Select +{ + public partial class BeatmapInfoWedgeV2 : VisibilityContainer + { + public const float BORDER_THICKNESS = 2.5f; + private const float shear_width = 36.75f; + + private const float transition_duration = 250; + + private static readonly Vector2 wedged_container_shear = new Vector2(shear_width / SongSelect.WEDGE_HEIGHT, 0); + + [Resolved] + private IBindable ruleset { get; set; } + + protected Container DisplayedContent { get; private set; } + + protected WedgeInfoText Info { get; private set; } + + public BeatmapInfoWedgeV2() + { + Shear = wedged_container_shear; + Masking = true; + BorderColour = new Color4(221, 255, 255, 255); + BorderThickness = BORDER_THICKNESS; + Alpha = 0; + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = new Color4(130, 204, 255, 150), + Radius = 20, + Roundness = 15, + }; + } + + [BackgroundDependencyLoader] + private void load() + { + ruleset.BindValueChanged(_ => updateDisplay()); + } + + private const double animation_duration = 800; + + protected override void PopIn() + { + this.MoveToX(0, animation_duration, Easing.OutQuint); + this.RotateTo(0, animation_duration, Easing.OutQuint); + this.FadeIn(transition_duration); + } + + protected override void PopOut() + { + this.MoveToX(-100, animation_duration, Easing.In); + this.RotateTo(10, animation_duration, Easing.In); + this.FadeOut(transition_duration * 2, Easing.In); + } + + private WorkingBeatmap beatmap; + + public WorkingBeatmap Beatmap + { + get => beatmap; + set + { + if (beatmap == value) return; + + beatmap = value; + + updateDisplay(); + } + } + + public override bool IsPresent => base.IsPresent || DisplayedContent == null; // Visibility is updated in the LoadComponentAsync callback + + private Container loadingInfo; + + private void updateDisplay() + { + Scheduler.AddOnce(perform); + + void perform() + { + void removeOldInfo() + { + State.Value = beatmap == null ? Visibility.Hidden : Visibility.Visible; + + DisplayedContent?.FadeOut(transition_duration); + DisplayedContent?.Expire(); + DisplayedContent = null; + } + + if (beatmap == null) + { + removeOldInfo(); + return; + } + + LoadComponentAsync(loadingInfo = new Container + { + RelativeSizeAxes = Axes.Both, + Shear = -Shear, + Depth = DisplayedContent?.Depth + 1 ?? 0, + Children = new Drawable[] + { + new BeatmapInfoWedgeBackground(beatmap), + Info = new WedgeInfoText(beatmap), + } + }, loaded => + { + // ensure we are the most recent loaded wedge. + if (loaded != loadingInfo) return; + + removeOldInfo(); + Add(DisplayedContent = loaded); + }); + } + } + + public partial class WedgeInfoText : Container + { + public OsuSpriteText VersionLabel { get; private set; } + public OsuSpriteText TitleLabel { get; private set; } + public OsuSpriteText ArtistLabel { get; private set; } + public FillFlowContainer MapperContainer { get; private set; } + + private Container difficultyColourBar; + private StarRatingDisplay starRatingDisplay; + + private ILocalisedBindableString titleBinding; + private ILocalisedBindableString artistBinding; + + private readonly WorkingBeatmap working; + + [Resolved] + private IBindable> mods { get; set; } + + [Resolved] + private BeatmapDifficultyCache difficultyCache { get; set; } + + [Resolved] + private OsuColour colours { get; set; } + + private ModSettingChangeTracker settingChangeTracker; + + public WedgeInfoText(WorkingBeatmap working) + { + this.working = working; + } + + private CancellationTokenSource cancellationSource; + private IBindable starDifficulty; + + [BackgroundDependencyLoader] + private void load(LocalisationManager localisation) + { + var beatmapInfo = working.BeatmapInfo; + var metadata = beatmapInfo.Metadata; + + RelativeSizeAxes = Axes.Both; + + titleBinding = localisation.GetLocalisedBindableString(new RomanisableString(metadata.TitleUnicode, metadata.Title)); + artistBinding = localisation.GetLocalisedBindableString(new RomanisableString(metadata.ArtistUnicode, metadata.Artist)); + + const float top_height = 0.7f; + + Children = new Drawable[] + { + difficultyColourBar = new Container + { + RelativeSizeAxes = Axes.Y, + Width = 20f, + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Width = top_height, + }, + new Box + { + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + Alpha = 0.5f, + X = top_height, + Width = 1 - top_height, + } + } + }, + new FillFlowContainer + { + Name = "Topleft-aligned metadata", + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Top = 10, Left = 25, Right = shear_width * 2.5f }, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Children = new Drawable[] + { + VersionLabel = new OsuSpriteText + { + Text = beatmapInfo.DifficultyName, + Font = OsuFont.GetFont(size: 24, italics: true), + RelativeSizeAxes = Axes.X, + Truncate = true, + }, + } + }, + new FillFlowContainer + { + Name = "Topright-aligned metadata", + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Top = 14, Right = shear_width / 2 }, + AutoSizeAxes = Axes.Both, + Shear = wedged_container_shear, + Spacing = new Vector2(0f, 5f), + Children = new Drawable[] + { + starRatingDisplay = new StarRatingDisplay(default, animated: true) + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Shear = -wedged_container_shear, + Alpha = 0f, + }, + new BeatmapSetOnlineStatusPill + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Shear = -wedged_container_shear, + TextSize = 11, + TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, + Status = beatmapInfo.Status, + Alpha = string.IsNullOrEmpty(beatmapInfo.DifficultyName) ? 0 : 1 + } + } + }, + new FillFlowContainer + { + Name = "Centre-aligned metadata", + Anchor = Anchor.CentreLeft, + Origin = Anchor.TopLeft, + Y = -7, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Left = 25, Right = shear_width }, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Children = new Drawable[] + { + TitleLabel = new OsuSpriteText + { + Current = { BindTarget = titleBinding }, + Font = OsuFont.GetFont(size: 28, italics: true), + RelativeSizeAxes = Axes.X, + Truncate = true, + }, + ArtistLabel = new OsuSpriteText + { + Current = { BindTarget = artistBinding }, + Font = OsuFont.GetFont(size: 17, italics: true), + RelativeSizeAxes = Axes.X, + Truncate = true, + }, + MapperContainer = new FillFlowContainer + { + Margin = new MarginPadding { Top = 10 }, + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Child = getMapper(metadata), + } + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + starRatingDisplay.DisplayedStars.BindValueChanged(s => + { + difficultyColourBar.Colour = colours.ForStarDifficulty(s.NewValue); + }, true); + + starDifficulty = difficultyCache.GetBindableDifficulty(working.BeatmapInfo, (cancellationSource = new CancellationTokenSource()).Token); + starDifficulty.BindValueChanged(s => + { + starRatingDisplay.Current.Value = s.NewValue ?? default; + + // Don't roll the counter on initial display (but still allow it to roll on applying mods etc.) + if (!starRatingDisplay.IsPresent) + starRatingDisplay.FinishTransforms(true); + + starRatingDisplay.FadeIn(transition_duration); + }); + + mods.BindValueChanged(m => + { + settingChangeTracker?.Dispose(); + + settingChangeTracker = new ModSettingChangeTracker(m.NewValue); + }, true); + } + + private Drawable getMapper(BeatmapMetadata metadata) + { + if (string.IsNullOrEmpty(metadata.Author.Username)) + return Empty(); + + return new LinkFlowContainer(s => + { + s.Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 15); + }).With(d => + { + d.AutoSizeAxes = Axes.Both; + d.AddText("mapped by "); + d.AddUserLink(metadata.Author); + }); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + settingChangeTracker?.Dispose(); + cancellationSource?.Cancel(); + } + } + } +} From c646f8479b35cc7d66b1075ad5c5ba8ab46434ad Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Sat, 7 Jan 2023 23:53:19 +0100 Subject: [PATCH 0002/4092] Move stardifficulty logic to main BeatmapInfoWedge class instead of text subclass ( for use by background star rating colour bar ) --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index 4d1a2133fd..104fa8787b 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -37,10 +37,16 @@ namespace osu.Game.Screens.Select [Resolved] private IBindable ruleset { get; set; } + [Resolved] + private BeatmapDifficultyCache difficultyCache { get; set; } + protected Container DisplayedContent { get; private set; } protected WedgeInfoText Info { get; private set; } + private IBindable starDifficulty; + private CancellationTokenSource cancellationSource; + public BeatmapInfoWedgeV2() { Shear = wedged_container_shear; @@ -98,6 +104,19 @@ namespace osu.Game.Screens.Select private Container loadingInfo; + protected override void LoadComplete() + { + base.LoadComplete(); + starDifficulty = difficultyCache.GetBindableDifficulty(beatmap.BeatmapInfo, (cancellationSource = new CancellationTokenSource()).Token); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + cancellationSource?.Cancel(); + } + private void updateDisplay() { Scheduler.AddOnce(perform); @@ -127,7 +146,7 @@ namespace osu.Game.Screens.Select Children = new Drawable[] { new BeatmapInfoWedgeBackground(beatmap), - Info = new WedgeInfoText(beatmap), + Info = new WedgeInfoText(beatmap, starDifficulty), } }, loaded => { @@ -154,26 +173,22 @@ namespace osu.Game.Screens.Select private ILocalisedBindableString artistBinding; private readonly WorkingBeatmap working; + private readonly IBindable starDifficulty; [Resolved] private IBindable> mods { get; set; } - [Resolved] - private BeatmapDifficultyCache difficultyCache { get; set; } - [Resolved] private OsuColour colours { get; set; } private ModSettingChangeTracker settingChangeTracker; - public WedgeInfoText(WorkingBeatmap working) + public WedgeInfoText(WorkingBeatmap working, IBindable starDifficulty) { this.working = working; + this.starDifficulty = starDifficulty; } - private CancellationTokenSource cancellationSource; - private IBindable starDifficulty; - [BackgroundDependencyLoader] private void load(LocalisationManager localisation) { @@ -309,7 +324,6 @@ namespace osu.Game.Screens.Select difficultyColourBar.Colour = colours.ForStarDifficulty(s.NewValue); }, true); - starDifficulty = difficultyCache.GetBindableDifficulty(working.BeatmapInfo, (cancellationSource = new CancellationTokenSource()).Token); starDifficulty.BindValueChanged(s => { starRatingDisplay.Current.Value = s.NewValue ?? default; @@ -349,7 +363,6 @@ namespace osu.Game.Screens.Select { base.Dispose(isDisposing); settingChangeTracker?.Dispose(); - cancellationSource?.Cancel(); } } } From 0199c19f74705c2d5c0dd10f3c61c0c8325ef224 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Sun, 8 Jan 2023 01:24:47 +0100 Subject: [PATCH 0003/4092] Add a test scene and move colour bar to back and adjust positioning of it --- .../SongSelect/TestSceneBeatmapInfoWedgeV2.cs | 187 ++++++++++++++++++ osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 131 +++--------- 2 files changed, 217 insertions(+), 101 deletions(-) create mode 100644 osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs new file mode 100644 index 0000000000..98e9d803ca --- /dev/null +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs @@ -0,0 +1,187 @@ +// 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.Collections.Generic; +using JetBrains.Annotations; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Testing; +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.SongSelect +{ + [TestFixture] + public partial class TestSceneBeatmapInfoWedgeV2 : OsuTestScene + { + private RulesetStore rulesets; + private TestBeatmapInfoWedgeV2 infoWedge; + private readonly List beatmaps = new List(); + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + this.rulesets = rulesets; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Add(infoWedge = new TestBeatmapInfoWedgeV2 + { + Size = new Vector2(0.6f, 120), + RelativeSizeAxes = Axes.X, + Margin = new MarginPadding { Top = 20 } + }); + + AddStep("show", () => infoWedge.Show()); + + selectBeatmap(Beatmap.Value.Beatmap); + + AddWaitStep("wait for select", 3); + + AddStep("hide", () => { infoWedge.Hide(); }); + + AddWaitStep("wait for hide", 3); + + AddStep("show", () => { infoWedge.Show(); }); + + AddSliderStep("change star difficulty", 0, 11.9, 5.55, v => + { + foreach (var hasCurrentValue in infoWedge.Info.ChildrenOfType>()) + hasCurrentValue.Current.Value = new StarDifficulty(v, 0); + }); + + foreach (var rulesetInfo in rulesets.AvailableRulesets) + { + var instance = rulesetInfo.CreateInstance(); + var testBeatmap = createTestBeatmap(rulesetInfo); + + beatmaps.Add(testBeatmap); + + setRuleset(rulesetInfo); + + selectBeatmap(testBeatmap); + + testBeatmapLabels(instance); + } + } + + private void testBeatmapLabels(Ruleset ruleset) + { + AddAssert("check title", () => infoWedge.Info.TitleLabel.Current.Value == $"{ruleset.ShortName}Title"); + AddAssert("check artist", () => infoWedge.Info.ArtistLabel.Current.Value == $"{ruleset.ShortName}Artist"); + } + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("reset mods", () => SelectedMods.SetDefault()); + } + + [Test] + public void TestTruncation() + { + selectBeatmap(createLongMetadata()); + } + + private void setRuleset(RulesetInfo rulesetInfo) + { + Container containerBefore = null; + + AddStep("set ruleset", () => + { + // wedge content is only refreshed if the ruleset changes, so only wait for load in that case. + if (!rulesetInfo.Equals(Ruleset.Value)) + containerBefore = infoWedge.DisplayedContent; + + Ruleset.Value = rulesetInfo; + }); + + AddUntilStep("wait for async load", () => infoWedge.DisplayedContent != containerBefore); + } + + private void selectBeatmap([CanBeNull] IBeatmap b) + { + Container containerBefore = null; + + AddStep($"select {b?.Metadata.Title ?? "null"} beatmap", () => + { + containerBefore = infoWedge.DisplayedContent; + infoWedge.Beatmap = Beatmap.Value = b == null ? Beatmap.Default : CreateWorkingBeatmap(b); + }); + + AddUntilStep("wait for async load", () => infoWedge.DisplayedContent != containerBefore); + } + + private IBeatmap createTestBeatmap(RulesetInfo ruleset) + { + List objects = new List(); + for (double i = 0; i < 50000; i += 1000) + objects.Add(new TestHitObject { StartTime = i }); + + return new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + Metadata = new BeatmapMetadata + { + Author = { Username = $"{ruleset.ShortName}Author" }, + Artist = $"{ruleset.ShortName}Artist", + Source = $"{ruleset.ShortName}Source", + Title = $"{ruleset.ShortName}Title" + }, + Ruleset = ruleset, + StarRating = 6, + DifficultyName = $"{ruleset.ShortName}Version", + Difficulty = new BeatmapDifficulty() + }, + HitObjects = objects + }; + } + + private IBeatmap createLongMetadata() + { + return new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + Metadata = new BeatmapMetadata + { + Author = { Username = "WWWWWWWWWWWWWWW" }, + Artist = "Verrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrry long Artist", + Source = "Verrrrry long Source", + Title = "Verrrrry long Title" + }, + DifficultyName = "Verrrrrrrrrrrrrrrrrrrrrrrrrrrrry long Version", + Status = BeatmapOnlineStatus.Graveyard, + }, + }; + } + + private partial class TestBeatmapInfoWedgeV2 : BeatmapInfoWedgeV2 + { + public new Container DisplayedContent => base.DisplayedContent; + + 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; + } + } +} diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index 104fa8787b..5583ad11f7 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Threading; using osuTK; -using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -16,18 +15,16 @@ using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Effects; using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Graphics.Containers; namespace osu.Game.Screens.Select { + [Cached] public partial class BeatmapInfoWedgeV2 : VisibilityContainer { - public const float BORDER_THICKNESS = 2.5f; private const float shear_width = 36.75f; private const float transition_duration = 250; @@ -44,22 +41,25 @@ namespace osu.Game.Screens.Select protected WedgeInfoText Info { get; private set; } - private IBindable starDifficulty; + private IBindable starDifficulty = new Bindable(); private CancellationTokenSource cancellationSource; + private readonly Container difficultyColourBar; + public BeatmapInfoWedgeV2() { + CornerRadius = 10; Shear = wedged_container_shear; Masking = true; - BorderColour = new Color4(221, 255, 255, 255); - BorderThickness = BORDER_THICKNESS; Alpha = 0; - EdgeEffect = new EdgeEffectParameters + Child = difficultyColourBar = new Container { - Type = EdgeEffectType.Glow, - Colour = new Color4(130, 204, 255, 150), - Radius = 20, - Roundness = 15, + Depth = float.MaxValue, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.Y, + Width = 40, + Child = new Box { RelativeSizeAxes = Axes.Both } }; } @@ -95,6 +95,7 @@ namespace osu.Game.Screens.Select if (beatmap == value) return; beatmap = value; + starDifficulty = difficultyCache.GetBindableDifficulty(value.BeatmapInfo, (cancellationSource = new CancellationTokenSource()).Token); updateDisplay(); } @@ -104,12 +105,6 @@ namespace osu.Game.Screens.Select private Container loadingInfo; - protected override void LoadComplete() - { - base.LoadComplete(); - starDifficulty = difficultyCache.GetBindableDifficulty(beatmap.BeatmapInfo, (cancellationSource = new CancellationTokenSource()).Token); - } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -140,13 +135,15 @@ namespace osu.Game.Screens.Select LoadComponentAsync(loadingInfo = new Container { + Masking = true, + X = -30, + CornerRadius = 10, RelativeSizeAxes = Axes.Both, - Shear = -Shear, Depth = DisplayedContent?.Depth + 1 ?? 0, Children = new Drawable[] { - new BeatmapInfoWedgeBackground(beatmap), - Info = new WedgeInfoText(beatmap, starDifficulty), + new BeatmapInfoWedgeBackground(beatmap) { Shear = -Shear }, + Info = new WedgeInfoText(beatmap, starDifficulty) { Shear = -Shear } } }, loaded => { @@ -161,12 +158,9 @@ namespace osu.Game.Screens.Select public partial class WedgeInfoText : Container { - public OsuSpriteText VersionLabel { get; private set; } public OsuSpriteText TitleLabel { get; private set; } public OsuSpriteText ArtistLabel { get; private set; } - public FillFlowContainer MapperContainer { get; private set; } - private Container difficultyColourBar; private StarRatingDisplay starRatingDisplay; private ILocalisedBindableString titleBinding; @@ -178,6 +172,9 @@ namespace osu.Game.Screens.Select [Resolved] private IBindable> mods { get; set; } + [Resolved] + private BeatmapInfoWedgeV2 wedge { get; set; } + [Resolved] private OsuColour colours { get; set; } @@ -200,51 +197,8 @@ namespace osu.Game.Screens.Select titleBinding = localisation.GetLocalisedBindableString(new RomanisableString(metadata.TitleUnicode, metadata.Title)); artistBinding = localisation.GetLocalisedBindableString(new RomanisableString(metadata.ArtistUnicode, metadata.Artist)); - const float top_height = 0.7f; - Children = new Drawable[] { - difficultyColourBar = new Container - { - RelativeSizeAxes = Axes.Y, - Width = 20f, - Children = new[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Width = top_height, - }, - new Box - { - RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, - Alpha = 0.5f, - X = top_height, - Width = 1 - top_height, - } - } - }, - new FillFlowContainer - { - Name = "Topleft-aligned metadata", - Anchor = Anchor.TopLeft, - Origin = Anchor.TopLeft, - Direction = FillDirection.Vertical, - Padding = new MarginPadding { Top = 10, Left = 25, Right = shear_width * 2.5f }, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Children = new Drawable[] - { - VersionLabel = new OsuSpriteText - { - Text = beatmapInfo.DifficultyName, - Font = OsuFont.GetFont(size: 24, italics: true), - RelativeSizeAxes = Axes.X, - Truncate = true, - }, - } - }, new FillFlowContainer { Name = "Topright-aligned metadata", @@ -279,12 +233,10 @@ namespace osu.Game.Screens.Select }, new FillFlowContainer { - Name = "Centre-aligned metadata", - Anchor = Anchor.CentreLeft, - Origin = Anchor.TopLeft, - Y = -7, + Name = "Top-left aligned metadata", Direction = FillDirection.Vertical, - Padding = new MarginPadding { Left = 25, Right = shear_width }, + Position = new Vector2(50, 12), + Width = .8f, AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, Children = new Drawable[] @@ -292,23 +244,17 @@ namespace osu.Game.Screens.Select TitleLabel = new OsuSpriteText { Current = { BindTarget = titleBinding }, - Font = OsuFont.GetFont(size: 28, italics: true), + Font = OsuFont.TorusAlternate.With(size: 40, weight: FontWeight.SemiBold), RelativeSizeAxes = Axes.X, - Truncate = true, + Truncate = true }, ArtistLabel = new OsuSpriteText { Current = { BindTarget = artistBinding }, - Font = OsuFont.GetFont(size: 17, italics: true), + //Not sure if this should be semi bold or medium + Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold), RelativeSizeAxes = Axes.X, - Truncate = true, - }, - MapperContainer = new FillFlowContainer - { - Margin = new MarginPadding { Top = 10 }, - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - Child = getMapper(metadata), + Truncate = true } } } @@ -321,9 +267,8 @@ namespace osu.Game.Screens.Select starRatingDisplay.DisplayedStars.BindValueChanged(s => { - difficultyColourBar.Colour = colours.ForStarDifficulty(s.NewValue); + wedge.difficultyColourBar.FadeColour(colours.ForStarDifficulty(s.NewValue)); }, true); - starDifficulty.BindValueChanged(s => { starRatingDisplay.Current.Value = s.NewValue ?? default; @@ -343,22 +288,6 @@ namespace osu.Game.Screens.Select }, true); } - private Drawable getMapper(BeatmapMetadata metadata) - { - if (string.IsNullOrEmpty(metadata.Author.Username)) - return Empty(); - - return new LinkFlowContainer(s => - { - s.Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 15); - }).With(d => - { - d.AutoSizeAxes = Axes.Both; - d.AddText("mapped by "); - d.AddUserLink(metadata.Author); - }); - } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From 61caabaa8ee4002c7d96859f0937b3fb8b997c82 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Sun, 8 Jan 2023 01:47:22 +0100 Subject: [PATCH 0004/4092] Add coloured star counter --- .../Graphics/UserInterface/StarCounter.cs | 6 ++- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 51 +++++++++++++++---- 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/StarCounter.cs b/osu.Game/Graphics/UserInterface/StarCounter.cs index d7d088d798..7adb482188 100644 --- a/osu.Game/Graphics/UserInterface/StarCounter.cs +++ b/osu.Game/Graphics/UserInterface/StarCounter.cs @@ -32,6 +32,11 @@ namespace osu.Game.Graphics.UserInterface private const float star_spacing = 4; + public virtual FillDirection Direction + { + set => stars.Direction = value; + } + private float current; /// @@ -66,7 +71,6 @@ namespace osu.Game.Graphics.UserInterface stars = new FillFlowContainer { AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, Spacing = new Vector2(star_spacing), ChildrenEnumerable = Enumerable.Range(0, StarCount).Select(_ => CreateStar()) } diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index 5583ad11f7..07fcb42fff 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -3,7 +3,9 @@ #nullable disable +using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using osuTK; using osu.Framework.Allocation; @@ -17,6 +19,7 @@ using osu.Game.Graphics.Sprites; using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; using osu.Game.Configuration; +using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -25,11 +28,12 @@ namespace osu.Game.Screens.Select [Cached] public partial class BeatmapInfoWedgeV2 : VisibilityContainer { - private const float shear_width = 36.75f; + private const float shear_width = 21; + private const int wedge_height = 120; private const float transition_duration = 250; - private static readonly Vector2 wedged_container_shear = new Vector2(shear_width / SongSelect.WEDGE_HEIGHT, 0); + private static readonly Vector2 wedged_container_shear = new Vector2(shear_width / wedge_height, 0); [Resolved] private IBindable ruleset { get; set; } @@ -45,6 +49,7 @@ namespace osu.Game.Screens.Select private CancellationTokenSource cancellationSource; private readonly Container difficultyColourBar; + private readonly StarCounter starCounter; public BeatmapInfoWedgeV2() { @@ -52,14 +57,27 @@ namespace osu.Game.Screens.Select Shear = wedged_container_shear; Masking = true; Alpha = 0; - Child = difficultyColourBar = new Container + + Children = new Drawable[] { - Depth = float.MaxValue, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.Y, - Width = 40, - Child = new Box { RelativeSizeAxes = Axes.Both } + difficultyColourBar = new Container + { + Depth = float.MaxValue, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.Y, + Width = 40, + Child = new Box { RelativeSizeAxes = Axes.Both } + }, + starCounter = new StarCounter + { + Anchor = Anchor.CentreRight, + Origin = Anchor.Centre, + Scale = new Vector2(0.4f), + Shear = -wedged_container_shear, + X = -15, + Direction = FillDirection.Vertical + } }; } @@ -67,6 +85,15 @@ namespace osu.Game.Screens.Select private void load() { ruleset.BindValueChanged(_ => updateDisplay()); + + float starAngle = (float)(Math.Atan(shear_width / wedge_height) * (180 / Math.PI)); + + //Applying the rotation directly to the StarCounter distorts the stars, hence it is applied to the child container + starCounter.Children.First().Rotation = starAngle; + + //Makes sure the stars center themselves properly in the colour bar + starCounter.Children.First().Anchor = Anchor.Centre; + starCounter.Children.First().Origin = Anchor.Centre; } private const double animation_duration = 800; @@ -267,8 +294,12 @@ namespace osu.Game.Screens.Select starRatingDisplay.DisplayedStars.BindValueChanged(s => { - wedge.difficultyColourBar.FadeColour(colours.ForStarDifficulty(s.NewValue)); + wedge.starCounter.Colour = s.NewValue >= 6.5 ? colours.Orange1 : Colour4.Black.Opacity(0.75f); + wedge.starCounter.Current = (float)s.NewValue; + + wedge.difficultyColourBar.FadeColour(colours.ForStarDifficulty(s.NewValue), 750, Easing.OutQuint); }, true); + starDifficulty.BindValueChanged(s => { starRatingDisplay.Current.Value = s.NewValue ?? default; From 1698272eb88b3937228783d3df8d04db993d5da5 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Sun, 8 Jan 2023 12:03:02 +0100 Subject: [PATCH 0005/4092] Simplify passing data from BeatmapInfoWedgeV2.cs to subclass wedgeinfotext --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index 07fcb42fff..1056d9478b 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -60,17 +60,22 @@ namespace osu.Game.Screens.Select Children = new Drawable[] { + //These elements can't be grouped with the rest of the content, due to being present either outside or under the backgrounds area difficultyColourBar = new Container { + Colour = Colour4.Transparent, Depth = float.MaxValue, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, RelativeSizeAxes = Axes.Y, + + //By limiting the width we avoid this box showing up as an outline around the drawables that are on top of it. Width = 40, Child = new Box { RelativeSizeAxes = Axes.Both } }, starCounter = new StarCounter { + Colour = Colour4.Transparent, Anchor = Anchor.CentreRight, Origin = Anchor.Centre, Scale = new Vector2(0.4f), @@ -170,7 +175,7 @@ namespace osu.Game.Screens.Select Children = new Drawable[] { new BeatmapInfoWedgeBackground(beatmap) { Shear = -Shear }, - Info = new WedgeInfoText(beatmap, starDifficulty) { Shear = -Shear } + Info = new WedgeInfoText { Shear = -Shear } } }, loaded => { @@ -193,9 +198,6 @@ namespace osu.Game.Screens.Select private ILocalisedBindableString titleBinding; private ILocalisedBindableString artistBinding; - private readonly WorkingBeatmap working; - private readonly IBindable starDifficulty; - [Resolved] private IBindable> mods { get; set; } @@ -207,17 +209,11 @@ namespace osu.Game.Screens.Select private ModSettingChangeTracker settingChangeTracker; - public WedgeInfoText(WorkingBeatmap working, IBindable starDifficulty) - { - this.working = working; - this.starDifficulty = starDifficulty; - } - [BackgroundDependencyLoader] private void load(LocalisationManager localisation) { - var beatmapInfo = working.BeatmapInfo; - var metadata = beatmapInfo.Metadata; + var beatmapInfo = wedge.Beatmap.BeatmapInfo; + var metadata = wedge.beatmap.Metadata; RelativeSizeAxes = Axes.Both; @@ -262,7 +258,7 @@ namespace osu.Game.Screens.Select { Name = "Top-left aligned metadata", Direction = FillDirection.Vertical, - Position = new Vector2(50, 12), + Position = new Vector2(80, 12), Width = .8f, AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, @@ -270,6 +266,7 @@ namespace osu.Game.Screens.Select { TitleLabel = new OsuSpriteText { + Shadow = true, Current = { BindTarget = titleBinding }, Font = OsuFont.TorusAlternate.With(size: 40, weight: FontWeight.SemiBold), RelativeSizeAxes = Axes.X, @@ -277,6 +274,7 @@ namespace osu.Game.Screens.Select }, ArtistLabel = new OsuSpriteText { + Shadow = true, Current = { BindTarget = artistBinding }, //Not sure if this should be semi bold or medium Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold), @@ -297,10 +295,10 @@ namespace osu.Game.Screens.Select wedge.starCounter.Colour = s.NewValue >= 6.5 ? colours.Orange1 : Colour4.Black.Opacity(0.75f); wedge.starCounter.Current = (float)s.NewValue; - wedge.difficultyColourBar.FadeColour(colours.ForStarDifficulty(s.NewValue), 750, Easing.OutQuint); + wedge.difficultyColourBar.FadeColour(colours.ForStarDifficulty(s.NewValue)); }, true); - starDifficulty.BindValueChanged(s => + wedge.starDifficulty.BindValueChanged(s => { starRatingDisplay.Current.Value = s.NewValue ?? default; From 65c30d2c2e0a322d0d75003905fd44b4e5969d32 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Sun, 8 Jan 2023 12:56:32 +0100 Subject: [PATCH 0006/4092] Remove nullability disabling --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 62 +++++++++---------- 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index 1056d9478b..da7cfc6613 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -36,17 +34,11 @@ namespace osu.Game.Screens.Select private static readonly Vector2 wedged_container_shear = new Vector2(shear_width / wedge_height, 0); [Resolved] - private IBindable ruleset { get; set; } + private IBindable ruleset { get; set; } = null!; - [Resolved] - private BeatmapDifficultyCache difficultyCache { get; set; } + protected Container? DisplayedContent { get; private set; } - protected Container DisplayedContent { get; private set; } - - protected WedgeInfoText Info { get; private set; } - - private IBindable starDifficulty = new Bindable(); - private CancellationTokenSource cancellationSource; + protected WedgeInfoText? Info { get; private set; } private readonly Container difficultyColourBar; private readonly StarCounter starCounter; @@ -117,9 +109,9 @@ namespace osu.Game.Screens.Select this.FadeOut(transition_duration * 2, Easing.In); } - private WorkingBeatmap beatmap; + private WorkingBeatmap? beatmap; - public WorkingBeatmap Beatmap + public WorkingBeatmap? Beatmap { get => beatmap; set @@ -127,7 +119,6 @@ namespace osu.Game.Screens.Select if (beatmap == value) return; beatmap = value; - starDifficulty = difficultyCache.GetBindableDifficulty(value.BeatmapInfo, (cancellationSource = new CancellationTokenSource()).Token); updateDisplay(); } @@ -135,14 +126,7 @@ namespace osu.Game.Screens.Select public override bool IsPresent => base.IsPresent || DisplayedContent == null; // Visibility is updated in the LoadComponentAsync callback - private Container loadingInfo; - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - cancellationSource?.Cancel(); - } + private Container? loadingInfo; private void updateDisplay() { @@ -190,30 +174,36 @@ namespace osu.Game.Screens.Select public partial class WedgeInfoText : Container { - public OsuSpriteText TitleLabel { get; private set; } - public OsuSpriteText ArtistLabel { get; private set; } + public OsuSpriteText TitleLabel { get; private set; } = null!; + public OsuSpriteText ArtistLabel { get; private set; } = null!; - private StarRatingDisplay starRatingDisplay; + private StarRatingDisplay starRatingDisplay = null!; - private ILocalisedBindableString titleBinding; - private ILocalisedBindableString artistBinding; + private ILocalisedBindableString titleBinding = null!; + private ILocalisedBindableString artistBinding = null!; [Resolved] - private IBindable> mods { get; set; } + private IBindable> mods { get; set; } = null!; [Resolved] - private BeatmapInfoWedgeV2 wedge { get; set; } + private BeatmapInfoWedgeV2 wedge { get; set; } = null!; [Resolved] - private OsuColour colours { get; set; } + private OsuColour colours { get; set; } = null!; - private ModSettingChangeTracker settingChangeTracker; + [Resolved] + private BeatmapDifficultyCache difficultyCache { get; set; } = null!; + + private ModSettingChangeTracker? settingChangeTracker; + + private IBindable? starDifficulty; + private CancellationTokenSource? cancellationSource; [BackgroundDependencyLoader] private void load(LocalisationManager localisation) { - var beatmapInfo = wedge.Beatmap.BeatmapInfo; - var metadata = wedge.beatmap.Metadata; + var beatmapInfo = wedge.Beatmap!.BeatmapInfo; + var metadata = wedge.beatmap!.Metadata; RelativeSizeAxes = Axes.Both; @@ -274,6 +264,7 @@ namespace osu.Game.Screens.Select }, ArtistLabel = new OsuSpriteText { + //figma design has a diffused shadow, instead of the solid one present here. Shadow = true, Current = { BindTarget = artistBinding }, //Not sure if this should be semi bold or medium @@ -298,7 +289,8 @@ namespace osu.Game.Screens.Select wedge.difficultyColourBar.FadeColour(colours.ForStarDifficulty(s.NewValue)); }, true); - wedge.starDifficulty.BindValueChanged(s => + starDifficulty = difficultyCache.GetBindableDifficulty(wedge.beatmap!.BeatmapInfo, (cancellationSource = new CancellationTokenSource()).Token); + starDifficulty.BindValueChanged(s => { starRatingDisplay.Current.Value = s.NewValue ?? default; @@ -320,6 +312,8 @@ namespace osu.Game.Screens.Select protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); + + cancellationSource?.Cancel(); settingChangeTracker?.Dispose(); } } From 9afdfd7f067c786f236748c84e7028e0c294ff76 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Sun, 8 Jan 2023 15:42:42 +0100 Subject: [PATCH 0007/4092] small tweaks, container edge - effect addition.. --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 50 ++++++++++++------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index da7cfc6613..ced6931e1f 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -10,6 +10,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; @@ -27,9 +28,9 @@ namespace osu.Game.Screens.Select public partial class BeatmapInfoWedgeV2 : VisibilityContainer { private const float shear_width = 21; - private const int wedge_height = 120; - + private const float wedge_height = 120; private const float transition_duration = 250; + private const float corner_radius = 10; private static readonly Vector2 wedged_container_shear = new Vector2(shear_width / wedge_height, 0); @@ -45,14 +46,20 @@ namespace osu.Game.Screens.Select public BeatmapInfoWedgeV2() { - CornerRadius = 10; Shear = wedged_container_shear; Masking = true; - Alpha = 0; + EdgeEffect = new EdgeEffectParameters + { + Colour = Colour4.Black.Opacity(.25f), + Type = EdgeEffectType.Shadow, + Radius = corner_radius, + Roundness = corner_radius + }; + CornerRadius = corner_radius; Children = new Drawable[] { - //These elements can't be grouped with the rest of the content, due to being present either outside or under the backgrounds area + // These elements can't be grouped with the rest of the content, due to being present either outside or under the backgrounds area difficultyColourBar = new Container { Colour = Colour4.Transparent, @@ -61,7 +68,7 @@ namespace osu.Game.Screens.Select Origin = Anchor.TopRight, RelativeSizeAxes = Axes.Y, - //By limiting the width we avoid this box showing up as an outline around the drawables that are on top of it. + // By limiting the width we avoid this box showing up as an outline around the drawables that are on top of it. Width = 40, Child = new Box { RelativeSizeAxes = Axes.Both } }, @@ -70,7 +77,7 @@ namespace osu.Game.Screens.Select Colour = Colour4.Transparent, Anchor = Anchor.CentreRight, Origin = Anchor.Centre, - Scale = new Vector2(0.4f), + Scale = new Vector2(0.35f), Shear = -wedged_container_shear, X = -15, Direction = FillDirection.Vertical @@ -85,10 +92,10 @@ namespace osu.Game.Screens.Select float starAngle = (float)(Math.Atan(shear_width / wedge_height) * (180 / Math.PI)); - //Applying the rotation directly to the StarCounter distorts the stars, hence it is applied to the child container + // Applying the rotation directly to the StarCounter distorts the stars, hence it is applied to the child container starCounter.Children.First().Rotation = starAngle; - //Makes sure the stars center themselves properly in the colour bar + // Makes sure the stars center themselves properly in the colour bar starCounter.Children.First().Anchor = Anchor.Centre; starCounter.Children.First().Origin = Anchor.Centre; } @@ -153,13 +160,13 @@ namespace osu.Game.Screens.Select { Masking = true, X = -30, - CornerRadius = 10, + CornerRadius = corner_radius, RelativeSizeAxes = Axes.Both, Depth = DisplayedContent?.Depth + 1 ?? 0, Children = new Drawable[] { new BeatmapInfoWedgeBackground(beatmap) { Shear = -Shear }, - Info = new WedgeInfoText { Shear = -Shear } + Info = new WedgeInfoText(beatmap) { Shear = -Shear } } }, loaded => { @@ -182,11 +189,10 @@ namespace osu.Game.Screens.Select private ILocalisedBindableString titleBinding = null!; private ILocalisedBindableString artistBinding = null!; - [Resolved] - private IBindable> mods { get; set; } = null!; + private readonly WorkingBeatmap working; [Resolved] - private BeatmapInfoWedgeV2 wedge { get; set; } = null!; + private IBindable> mods { get; set; } = null!; [Resolved] private OsuColour colours { get; set; } = null!; @@ -194,16 +200,24 @@ namespace osu.Game.Screens.Select [Resolved] private BeatmapDifficultyCache difficultyCache { get; set; } = null!; + [Resolved] + private BeatmapInfoWedgeV2 wedge { get; set; } = null!; + private ModSettingChangeTracker? settingChangeTracker; private IBindable? starDifficulty; private CancellationTokenSource? cancellationSource; + public WedgeInfoText(WorkingBeatmap working) + { + this.working = working; + } + [BackgroundDependencyLoader] private void load(LocalisationManager localisation) { - var beatmapInfo = wedge.Beatmap!.BeatmapInfo; - var metadata = wedge.beatmap!.Metadata; + var beatmapInfo = working.BeatmapInfo; + var metadata = working.Metadata; RelativeSizeAxes = Axes.Both; @@ -249,7 +263,7 @@ namespace osu.Game.Screens.Select Name = "Top-left aligned metadata", Direction = FillDirection.Vertical, Position = new Vector2(80, 12), - Width = .8f, + Width = .7f, AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, Children = new Drawable[] @@ -289,7 +303,7 @@ namespace osu.Game.Screens.Select wedge.difficultyColourBar.FadeColour(colours.ForStarDifficulty(s.NewValue)); }, true); - starDifficulty = difficultyCache.GetBindableDifficulty(wedge.beatmap!.BeatmapInfo, (cancellationSource = new CancellationTokenSource()).Token); + starDifficulty = difficultyCache.GetBindableDifficulty(working.BeatmapInfo, (cancellationSource = new CancellationTokenSource()).Token); starDifficulty.BindValueChanged(s => { starRatingDisplay.Current.Value = s.NewValue ?? default; From 2a82f618ed9a69dd28613dfc8f04189ceecd702f Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Tue, 10 Jan 2023 17:34:47 +0100 Subject: [PATCH 0008/4092] Add TODO for text margin const, added pertinent comments to known "issues" --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index ced6931e1f..ce07a59a0c 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -32,6 +32,9 @@ namespace osu.Game.Screens.Select private const float transition_duration = 250; private const float corner_radius = 10; + /// Todo: move this const out to song select when more new design elements are implemented for the beatmap details area, since it applies to text alignment of various elements + private const float text_margin = 62; + private static readonly Vector2 wedged_container_shear = new Vector2(shear_width / wedge_height, 0); [Resolved] @@ -159,18 +162,21 @@ namespace osu.Game.Screens.Select LoadComponentAsync(loadingInfo = new Container { Masking = true, + // We offset this by the portion of the colour bar underneath we wish to show X = -30, CornerRadius = corner_radius, RelativeSizeAxes = Axes.Both, Depth = DisplayedContent?.Depth + 1 ?? 0, Children = new Drawable[] { + // TODO: New wedge design uses a coloured horizontal gradient for its background, however this lacks implementation information in the figma draft. + // pending https://www.figma.com/file/DXKwqZhD5yyb1igc3mKo1P?node-id=2980:3361#340801912 being answered. new BeatmapInfoWedgeBackground(beatmap) { Shear = -Shear }, Info = new WedgeInfoText(beatmap) { Shear = -Shear } } }, loaded => { - // ensure we are the most recent loaded wedge. + // Ensure we are the most recent loaded wedge. if (loaded != loadingInfo) return; removeOldInfo(); @@ -262,7 +268,7 @@ namespace osu.Game.Screens.Select { Name = "Top-left aligned metadata", Direction = FillDirection.Vertical, - Position = new Vector2(80, 12), + Position = new Vector2(text_margin + shear_width, 12), Width = .7f, AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, @@ -278,10 +284,10 @@ namespace osu.Game.Screens.Select }, ArtistLabel = new OsuSpriteText { - //figma design has a diffused shadow, instead of the solid one present here. + // TODO : figma design has a diffused shadow, instead of the solid one present here, not possible currently as far as i'm aware. Shadow = true, Current = { BindTarget = artistBinding }, - //Not sure if this should be semi bold or medium + // Not sure if this should be semi bold or medium Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold), RelativeSizeAxes = Axes.X, Truncate = true @@ -297,8 +303,8 @@ namespace osu.Game.Screens.Select starRatingDisplay.DisplayedStars.BindValueChanged(s => { - wedge.starCounter.Colour = s.NewValue >= 6.5 ? colours.Orange1 : Colour4.Black.Opacity(0.75f); wedge.starCounter.Current = (float)s.NewValue; + wedge.starCounter.Colour = s.NewValue >= 6.5 ? colours.Orange1 : Colour4.Black.Opacity(0.75f); wedge.difficultyColourBar.FadeColour(colours.ForStarDifficulty(s.NewValue)); }, true); From 8bfe24ced0c9163c1e1017b76d1f1669d57a160c Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Tue, 10 Jan 2023 17:49:33 +0100 Subject: [PATCH 0009/4092] Remove nullable disable in test. --- .../SongSelect/TestSceneBeatmapInfoWedgeV2.cs | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs index 98e9d803ca..193acc8a7b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs @@ -1,10 +1,7 @@ // 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.Collections.Generic; -using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -24,8 +21,8 @@ namespace osu.Game.Tests.Visual.SongSelect [TestFixture] public partial class TestSceneBeatmapInfoWedgeV2 : OsuTestScene { - private RulesetStore rulesets; - private TestBeatmapInfoWedgeV2 infoWedge; + private RulesetStore rulesets = null!; + private TestBeatmapInfoWedgeV2 infoWedge = null!; private readonly List beatmaps = new List(); [BackgroundDependencyLoader] @@ -80,8 +77,8 @@ namespace osu.Game.Tests.Visual.SongSelect private void testBeatmapLabels(Ruleset ruleset) { - AddAssert("check title", () => infoWedge.Info.TitleLabel.Current.Value == $"{ruleset.ShortName}Title"); - AddAssert("check artist", () => infoWedge.Info.ArtistLabel.Current.Value == $"{ruleset.ShortName}Artist"); + AddAssert("check title", () => infoWedge.Info!.TitleLabel.Current.Value == $"{ruleset.ShortName}Title"); + AddAssert("check artist", () => infoWedge.Info!.ArtistLabel.Current.Value == $"{ruleset.ShortName}Artist"); } [SetUpSteps] @@ -98,7 +95,7 @@ namespace osu.Game.Tests.Visual.SongSelect private void setRuleset(RulesetInfo rulesetInfo) { - Container containerBefore = null; + Container? containerBefore = null; AddStep("set ruleset", () => { @@ -112,9 +109,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", () => { @@ -172,9 +169,9 @@ namespace osu.Game.Tests.Visual.SongSelect private partial class TestBeatmapInfoWedgeV2 : BeatmapInfoWedgeV2 { - public new Container DisplayedContent => base.DisplayedContent; + public new Container? DisplayedContent => base.DisplayedContent; - public new WedgeInfoText Info => base.Info; + public new WedgeInfoText? Info => base.Info; } private class TestHitObject : ConvertHitObject, IHasPosition From 880428046a17606b200750c6f8bdec254fd7e4e1 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Tue, 10 Jan 2023 18:03:28 +0100 Subject: [PATCH 0010/4092] Fix margins on top right aligned elements. --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index ce07a59a0c..1497bed121 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -238,7 +238,7 @@ namespace osu.Game.Screens.Select Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Direction = FillDirection.Vertical, - Padding = new MarginPadding { Top = 14, Right = shear_width / 2 }, + Padding = new MarginPadding { Top = 3, Right = 8 }, AutoSizeAxes = Axes.Both, Shear = wedged_container_shear, Spacing = new Vector2(0f, 5f), From 7510201804aab33e90d805b8a34b9883f9ff390f Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Mon, 16 Jan 2023 22:24:21 +0100 Subject: [PATCH 0011/4092] Add back null beatmap test --- .../Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs index 193acc8a7b..a2935fb218 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs @@ -2,6 +2,7 @@ // 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.Allocation; using osu.Framework.Graphics; @@ -93,6 +94,15 @@ namespace osu.Game.Tests.Visual.SongSelect selectBeatmap(createLongMetadata()); } + [Test] + public void TestNullBeatmap() + { + selectBeatmap(null); + AddAssert("check default title", () => infoWedge.Info!.TitleLabel.Current.Value == Beatmap.Default.BeatmapInfo.Metadata.Title); + AddAssert("check default artist", () => infoWedge.Info!.ArtistLabel.Current.Value == Beatmap.Default.BeatmapInfo.Metadata.Artist); + AddAssert("check no info labels", () => !infoWedge.Info.ChildrenOfType().Any()); + } + private void setRuleset(RulesetInfo rulesetInfo) { Container? containerBefore = null; From 74b72e4ac0d6c34df51bf8d27c24d4625a9f8039 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Mon, 16 Jan 2023 22:46:18 +0100 Subject: [PATCH 0012/4092] Address issues that joehuu brought up --- .../Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs | 2 +- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs index a2935fb218..4904e2a723 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.SongSelect Add(infoWedge = new TestBeatmapInfoWedgeV2 { - Size = new Vector2(0.6f, 120), + Width = 0.6f, RelativeSizeAxes = Axes.X, Margin = new MarginPadding { Top = 20 } }); diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index 1497bed121..02d640fc0d 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -31,6 +31,7 @@ namespace osu.Game.Screens.Select private const float wedge_height = 120; private const float transition_duration = 250; private const float corner_radius = 10; + private const float colour_bar_width = 30; /// Todo: move this const out to song select when more new design elements are implemented for the beatmap details area, since it applies to text alignment of various elements private const float text_margin = 62; @@ -49,6 +50,7 @@ namespace osu.Game.Screens.Select public BeatmapInfoWedgeV2() { + Height = wedge_height; Shear = wedged_container_shear; Masking = true; EdgeEffect = new EdgeEffectParameters @@ -72,7 +74,7 @@ namespace osu.Game.Screens.Select RelativeSizeAxes = Axes.Y, // By limiting the width we avoid this box showing up as an outline around the drawables that are on top of it. - Width = 40, + Width = colour_bar_width + corner_radius, Child = new Box { RelativeSizeAxes = Axes.Both } }, starCounter = new StarCounter @@ -82,7 +84,7 @@ namespace osu.Game.Screens.Select Origin = Anchor.Centre, Scale = new Vector2(0.35f), Shear = -wedged_container_shear, - X = -15, + X = -colour_bar_width / 2, Direction = FillDirection.Vertical } }; @@ -163,7 +165,7 @@ namespace osu.Game.Screens.Select { Masking = true, // We offset this by the portion of the colour bar underneath we wish to show - X = -30, + X = -colour_bar_width, CornerRadius = corner_radius, RelativeSizeAxes = Axes.Both, Depth = DisplayedContent?.Depth + 1 ?? 0, @@ -268,8 +270,7 @@ namespace osu.Game.Screens.Select { Name = "Top-left aligned metadata", Direction = FillDirection.Vertical, - Position = new Vector2(text_margin + shear_width, 12), - Width = .7f, + Padding = new MarginPadding { Horizontal = text_margin + shear_width, Top = 12 }, AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, Children = new Drawable[] From 0ac7cd7409e1c61cc0f8df03c1fa0b780adb43da Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Wed, 18 Jan 2023 13:55:52 +0100 Subject: [PATCH 0013/4092] Expose star difficulty to wedge to allow updating starcounter and background colour internally. --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index 02d640fc0d..d90c002953 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -24,7 +24,6 @@ using osu.Game.Rulesets.Mods; namespace osu.Game.Screens.Select { - [Cached] public partial class BeatmapInfoWedgeV2 : VisibilityContainer { private const float shear_width = 21; @@ -41,6 +40,9 @@ namespace osu.Game.Screens.Select [Resolved] private IBindable ruleset { get; set; } = null!; + [Resolved] + private OsuColour colours { get; set; } = null!; + protected Container? DisplayedContent { get; private set; } protected WedgeInfoText? Info { get; private set; } @@ -183,6 +185,14 @@ namespace osu.Game.Screens.Select removeOldInfo(); Add(DisplayedContent = loaded); + + Info.StarRatingDisplay.DisplayedStars.BindValueChanged(s => + { + starCounter.Current = (float)s.NewValue; + starCounter.Colour = s.NewValue >= 6.5 ? colours.Orange1 : Colour4.Black.Opacity(0.75f); + + difficultyColourBar.FadeColour(colours.ForStarDifficulty(s.NewValue)); + }, true); }); } } @@ -192,7 +202,7 @@ namespace osu.Game.Screens.Select public OsuSpriteText TitleLabel { get; private set; } = null!; public OsuSpriteText ArtistLabel { get; private set; } = null!; - private StarRatingDisplay starRatingDisplay = null!; + public StarRatingDisplay StarRatingDisplay = null!; private ILocalisedBindableString titleBinding = null!; private ILocalisedBindableString artistBinding = null!; @@ -202,15 +212,9 @@ namespace osu.Game.Screens.Select [Resolved] private IBindable> mods { get; set; } = null!; - [Resolved] - private OsuColour colours { get; set; } = null!; - [Resolved] private BeatmapDifficultyCache difficultyCache { get; set; } = null!; - [Resolved] - private BeatmapInfoWedgeV2 wedge { get; set; } = null!; - private ModSettingChangeTracker? settingChangeTracker; private IBindable? starDifficulty; @@ -246,7 +250,7 @@ namespace osu.Game.Screens.Select Spacing = new Vector2(0f, 5f), Children = new Drawable[] { - starRatingDisplay = new StarRatingDisplay(default, animated: true) + StarRatingDisplay = new StarRatingDisplay(default, animated: true) { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, @@ -302,24 +306,16 @@ namespace osu.Game.Screens.Select { base.LoadComplete(); - starRatingDisplay.DisplayedStars.BindValueChanged(s => - { - wedge.starCounter.Current = (float)s.NewValue; - wedge.starCounter.Colour = s.NewValue >= 6.5 ? colours.Orange1 : Colour4.Black.Opacity(0.75f); - - wedge.difficultyColourBar.FadeColour(colours.ForStarDifficulty(s.NewValue)); - }, true); - starDifficulty = difficultyCache.GetBindableDifficulty(working.BeatmapInfo, (cancellationSource = new CancellationTokenSource()).Token); starDifficulty.BindValueChanged(s => { - starRatingDisplay.Current.Value = s.NewValue ?? default; + StarRatingDisplay.Current.Value = s.NewValue ?? default; // Don't roll the counter on initial display (but still allow it to roll on applying mods etc.) - if (!starRatingDisplay.IsPresent) - starRatingDisplay.FinishTransforms(true); + if (!StarRatingDisplay.IsPresent) + StarRatingDisplay.FinishTransforms(true); - starRatingDisplay.FadeIn(transition_duration); + StarRatingDisplay.FadeIn(transition_duration); }); mods.BindValueChanged(m => From 655242371b2858371c82970d9fe316eb7d26cd6d Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Mon, 23 Jan 2023 17:00:46 +0100 Subject: [PATCH 0014/4092] Buffer wedge content to avoid opacity issues when showing / hiding --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 66 +++++++++++-------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index d90c002953..63e414d6ad 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -49,6 +49,7 @@ namespace osu.Game.Screens.Select private readonly Container difficultyColourBar; private readonly StarCounter starCounter; + private readonly BufferedContainer bufferedContent; public BeatmapInfoWedgeV2() { @@ -64,30 +65,35 @@ namespace osu.Game.Screens.Select }; CornerRadius = corner_radius; - Children = new Drawable[] + // We want to buffer the wedge to avoid weird transparency overlaps between the colour bar and the background. + Child = bufferedContent = new BufferedContainer { - // These elements can't be grouped with the rest of the content, due to being present either outside or under the backgrounds area - difficultyColourBar = new Container + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - Colour = Colour4.Transparent, - Depth = float.MaxValue, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.Y, + // These elements can't be grouped with the rest of the content, due to being present either outside or under the backgrounds area + difficultyColourBar = new Container + { + Colour = Colour4.Transparent, + Depth = float.MaxValue, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.Y, - // By limiting the width we avoid this box showing up as an outline around the drawables that are on top of it. - Width = colour_bar_width + corner_radius, - Child = new Box { RelativeSizeAxes = Axes.Both } - }, - starCounter = new StarCounter - { - Colour = Colour4.Transparent, - Anchor = Anchor.CentreRight, - Origin = Anchor.Centre, - Scale = new Vector2(0.35f), - Shear = -wedged_container_shear, - X = -colour_bar_width / 2, - Direction = FillDirection.Vertical + // By limiting the width we avoid this box showing up as an outline around the drawables that are on top of it. + Width = colour_bar_width + corner_radius, + Child = new Box { RelativeSizeAxes = Axes.Both } + }, + starCounter = new StarCounter + { + Colour = Colour4.Transparent, + Anchor = Anchor.CentreRight, + Origin = Anchor.Centre, + Scale = new Vector2(0.35f), + Shear = -wedged_container_shear, + X = -colour_bar_width / 2, + Direction = FillDirection.Vertical + } } }; } @@ -184,9 +190,9 @@ namespace osu.Game.Screens.Select if (loaded != loadingInfo) return; removeOldInfo(); - Add(DisplayedContent = loaded); + bufferedContent.Add(DisplayedContent = loaded); - Info.StarRatingDisplay.DisplayedStars.BindValueChanged(s => + Info.DisplayedStars.BindValueChanged(s => { starCounter.Current = (float)s.NewValue; starCounter.Colour = s.NewValue >= 6.5 ? colours.Orange1 : Colour4.Black.Opacity(0.75f); @@ -202,13 +208,15 @@ namespace osu.Game.Screens.Select public OsuSpriteText TitleLabel { get; private set; } = null!; public OsuSpriteText ArtistLabel { get; private set; } = null!; - public StarRatingDisplay StarRatingDisplay = null!; + private StarRatingDisplay starRatingDisplay = null!; private ILocalisedBindableString titleBinding = null!; private ILocalisedBindableString artistBinding = null!; private readonly WorkingBeatmap working; + public IBindable DisplayedStars => starRatingDisplay.DisplayedStars; + [Resolved] private IBindable> mods { get; set; } = null!; @@ -250,7 +258,7 @@ namespace osu.Game.Screens.Select Spacing = new Vector2(0f, 5f), Children = new Drawable[] { - StarRatingDisplay = new StarRatingDisplay(default, animated: true) + starRatingDisplay = new StarRatingDisplay(default, animated: true) { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, @@ -309,13 +317,13 @@ namespace osu.Game.Screens.Select starDifficulty = difficultyCache.GetBindableDifficulty(working.BeatmapInfo, (cancellationSource = new CancellationTokenSource()).Token); starDifficulty.BindValueChanged(s => { - StarRatingDisplay.Current.Value = s.NewValue ?? default; + starRatingDisplay.Current.Value = s.NewValue ?? default; // Don't roll the counter on initial display (but still allow it to roll on applying mods etc.) - if (!StarRatingDisplay.IsPresent) - StarRatingDisplay.FinishTransforms(true); + if (!starRatingDisplay.IsPresent) + starRatingDisplay.FinishTransforms(true); - StarRatingDisplay.FadeIn(transition_duration); + starRatingDisplay.FadeIn(transition_duration); }); mods.BindValueChanged(m => From c7d49bdc82c4ab48ab60040cdd0e161a481fdf94 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Mon, 30 Jan 2023 13:13:57 +0100 Subject: [PATCH 0015/4092] Update ```BeatmapInfoWedgeV2.cs``` animation to be similar to exit transition in ```SongSelect.cs`` --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index 63e414d6ad..6a1662fd6a 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -113,20 +113,18 @@ namespace osu.Game.Screens.Select starCounter.Children.First().Origin = Anchor.Centre; } - private const double animation_duration = 800; + private const double animation_duration = 600; protected override void PopIn() { this.MoveToX(0, animation_duration, Easing.OutQuint); - this.RotateTo(0, animation_duration, Easing.OutQuint); - this.FadeIn(transition_duration); + this.FadeIn(200, Easing.In); } protected override void PopOut() { - this.MoveToX(-100, animation_duration, Easing.In); - this.RotateTo(10, animation_duration, Easing.In); - this.FadeOut(transition_duration * 2, Easing.In); + this.MoveToX(-150, animation_duration, Easing.OutQuint); + this.FadeOut(200, Easing.OutQuint); } private WorkingBeatmap? beatmap; From 92690afa5fecd750583924c5917fc3b877b73041 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Mon, 30 Jan 2023 16:12:26 +0100 Subject: [PATCH 0016/4092] de-nest ```removeOldInfo()``` --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index 6a1662fd6a..41fe9d8d50 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -152,15 +152,6 @@ namespace osu.Game.Screens.Select void perform() { - void removeOldInfo() - { - State.Value = beatmap == null ? Visibility.Hidden : Visibility.Visible; - - DisplayedContent?.FadeOut(transition_duration); - DisplayedContent?.Expire(); - DisplayedContent = null; - } - if (beatmap == null) { removeOldInfo(); @@ -199,6 +190,15 @@ namespace osu.Game.Screens.Select }, true); }); } + + void removeOldInfo() + { + State.Value = beatmap == null ? Visibility.Hidden : Visibility.Visible; + + DisplayedContent?.FadeOut(transition_duration); + DisplayedContent?.Expire(); + DisplayedContent = null; + } } public partial class WedgeInfoText : Container From 27c52a45fc079174f29067ae6b7e97529f02cceb Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Mon, 30 Jan 2023 16:13:55 +0100 Subject: [PATCH 0017/4092] Use inline lambda for scheduling --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index 41fe9d8d50..0cc60e4bba 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -148,9 +148,7 @@ namespace osu.Game.Screens.Select private void updateDisplay() { - Scheduler.AddOnce(perform); - - void perform() + Scheduler.AddOnce(() => { if (beatmap == null) { @@ -189,7 +187,7 @@ namespace osu.Game.Screens.Select difficultyColourBar.FadeColour(colours.ForStarDifficulty(s.NewValue)); }, true); }); - } + }); void removeOldInfo() { From f8a5ce0cd2df5e5247ed0affe00ec4d6e8ffc1a7 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 2 Feb 2023 22:32:33 +0900 Subject: [PATCH 0018/4092] Use stable sort for catch hyperdash generation --- osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index ab61b14ac4..c51d3d5c70 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -209,7 +209,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps } } - palpableObjects.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime)); + palpableObjects = palpableObjects.OrderBy(h => h.StartTime).ToList(); double halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.Difficulty) / 2; From 491ba13b6f1a9dc15c80474fd60970cb66926c3f Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 3 Feb 2023 14:07:21 +0900 Subject: [PATCH 0019/4092] Factor out palpable obejct enumeration logic --- .../Beatmaps/CatchBeatmap.cs | 21 ++++++++++++++++ .../Beatmaps/CatchBeatmapProcessor.cs | 24 ++++--------------- .../Difficulty/CatchDifficultyCalculator.cs | 8 +++---- 3 files changed, 28 insertions(+), 25 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs index f009c10a9c..1f05d66b86 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Catch.Beatmaps { @@ -38,5 +39,25 @@ namespace osu.Game.Rulesets.Catch.Beatmaps } }; } + + /// + /// Enumerate all s, sorted by their start times. + /// + /// + /// If multiple objects have the same start time, the ordering is preserved (it is a stable sorting). + /// + public static IEnumerable GetPalpableObjects(IEnumerable hitObjects) + { + return hitObjects.SelectMany(selectPalpableObjects).OrderBy(h => h.StartTime); + + IEnumerable selectPalpableObjects(HitObject h) + { + if (h is PalpableCatchHitObject palpable) + yield return palpable; + + foreach (var nested in h.NestedHitObjects.OfType()) + yield return nested; + } + } } } diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index c51d3d5c70..32134912f1 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; @@ -192,24 +191,9 @@ namespace osu.Game.Rulesets.Catch.Beatmaps private static void initialiseHyperDash(IBeatmap beatmap) { - List palpableObjects = new List(); - - foreach (var currentObject in beatmap.HitObjects) - { - if (currentObject is Fruit fruitObject) - palpableObjects.Add(fruitObject); - - if (currentObject is JuiceStream) - { - foreach (var juice in currentObject.NestedHitObjects) - { - if (juice is PalpableCatchHitObject palpableObject && !(juice is TinyDroplet)) - palpableObjects.Add(palpableObject); - } - } - } - - palpableObjects = palpableObjects.OrderBy(h => h.StartTime).ToList(); + var palpableObjects = CatchBeatmap.GetPalpableObjects(beatmap.HitObjects) + .Where(h => h is Fruit || (h is Droplet && h is not TinyDroplet)) + .ToArray(); double halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.Difficulty) / 2; @@ -221,7 +205,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps int lastDirection = 0; double lastExcess = halfCatcherWidth; - for (int i = 0; i < palpableObjects.Count - 1; i++) + for (int i = 0; i < palpableObjects.Length - 1; i++) { var currentObject = palpableObjects[i]; var nextObject = palpableObjects[i + 1]; diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index 42cfde268e..959f4830dd 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Catch.Difficulty.Preprocessing; using osu.Game.Rulesets.Catch.Difficulty.Skills; using osu.Game.Rulesets.Catch.Mods; @@ -54,13 +55,10 @@ namespace osu.Game.Rulesets.Catch.Difficulty List objects = new List(); // In 2B beatmaps, it is possible that a normal Fruit is placed in the middle of a JuiceStream. - foreach (var hitObject in beatmap.HitObjects - .SelectMany(obj => obj is JuiceStream stream ? stream.NestedHitObjects.AsEnumerable() : new[] { obj }) - .Cast() - .OrderBy(x => x.StartTime)) + foreach (var hitObject in CatchBeatmap.GetPalpableObjects(beatmap.HitObjects)) { // We want to only consider fruits that contribute to the combo. - if (hitObject is BananaShower || hitObject is TinyDroplet) + if (hitObject is Banana || hitObject is TinyDroplet) continue; if (lastObject != null) From 5fc8f1d1bef944c43f1e9b1ac0456ded7015db72 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Fri, 3 Feb 2023 19:52:01 +0100 Subject: [PATCH 0020/4092] Fix ```BeatmapInfoWedgeV2.cs``` starCounter needing janky rotation application --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index 0cc60e4bba..48a16d5449 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading; using osuTK; using osu.Framework.Allocation; @@ -84,15 +83,25 @@ namespace osu.Game.Screens.Select Width = colour_bar_width + corner_radius, Child = new Box { RelativeSizeAxes = Axes.Both } }, - starCounter = new StarCounter + new Container { - Colour = Colour4.Transparent, - Anchor = Anchor.CentreRight, - Origin = Anchor.Centre, - Scale = new Vector2(0.35f), + // Applying the shear to this container and nesting the starCounter inside avoids + // the deformation that occurs if the shear is applied to the starCounter whilst rotated Shear = -wedged_container_shear, X = -colour_bar_width / 2, - Direction = FillDirection.Vertical + Anchor = Anchor.CentreRight, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Width = colour_bar_width, + Child = starCounter = new StarCounter + { + Rotation = (float)(Math.Atan(shear_width / wedge_height) * (180 / Math.PI)), + Colour = Colour4.Transparent, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(0.35f), + Direction = FillDirection.Vertical + } } } }; @@ -102,15 +111,6 @@ namespace osu.Game.Screens.Select private void load() { ruleset.BindValueChanged(_ => updateDisplay()); - - float starAngle = (float)(Math.Atan(shear_width / wedge_height) * (180 / Math.PI)); - - // Applying the rotation directly to the StarCounter distorts the stars, hence it is applied to the child container - starCounter.Children.First().Rotation = starAngle; - - // Makes sure the stars center themselves properly in the colour bar - starCounter.Children.First().Anchor = Anchor.Centre; - starCounter.Children.First().Origin = Anchor.Centre; } private const double animation_duration = 600; From de37a0a000bfb99ad0f56eaadbe9929eb93229ab Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Fri, 3 Feb 2023 19:53:04 +0100 Subject: [PATCH 0021/4092] enable pixelSnapping for the ```BufferedContainer``` in BeatmapInfoWedgeV2.cs --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index 48a16d5449..a18d4086f7 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -65,7 +65,7 @@ namespace osu.Game.Screens.Select CornerRadius = corner_radius; // We want to buffer the wedge to avoid weird transparency overlaps between the colour bar and the background. - Child = bufferedContent = new BufferedContainer + Child = bufferedContent = new BufferedContainer(pixelSnapping: true) { RelativeSizeAxes = Axes.Both, Children = new Drawable[] From 38cc47d64ec35313d7cb928ae78a018b5641e2d5 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Sat, 4 Feb 2023 16:52:30 +0100 Subject: [PATCH 0022/4092] Remove ```IsPresent``` usages --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index a18d4086f7..fda20dde4d 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -142,8 +142,6 @@ namespace osu.Game.Screens.Select } } - public override bool IsPresent => base.IsPresent || DisplayedContent == null; // Visibility is updated in the LoadComponentAsync callback - private Container? loadingInfo; private void updateDisplay() @@ -316,7 +314,7 @@ namespace osu.Game.Screens.Select starRatingDisplay.Current.Value = s.NewValue ?? default; // Don't roll the counter on initial display (but still allow it to roll on applying mods etc.) - if (!starRatingDisplay.IsPresent) + if (starRatingDisplay.Alpha > 0) starRatingDisplay.FinishTransforms(true); starRatingDisplay.FadeIn(transition_duration); From cb679ccc2b95858b0ff85e86bffcd6ee6a27d9dd Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Sat, 11 Feb 2023 18:00:17 +0100 Subject: [PATCH 0023/4092] Separate wedge visibility test into its own method --- .../SongSelect/TestSceneBeatmapInfoWedgeV2.cs | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs index 4904e2a723..f99950dfb0 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.UserInterface; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -49,12 +50,6 @@ namespace osu.Game.Tests.Visual.SongSelect AddWaitStep("wait for select", 3); - AddStep("hide", () => { infoWedge.Hide(); }); - - AddWaitStep("wait for hide", 3); - - AddStep("show", () => { infoWedge.Show(); }); - AddSliderStep("change star difficulty", 0, 11.9, 5.55, v => { foreach (var hasCurrentValue in infoWedge.Info.ChildrenOfType>()) @@ -76,6 +71,26 @@ namespace osu.Game.Tests.Visual.SongSelect } } + [Test] + public void TestWedgeVisibility() + { + AddStep("Make shadow red for test visibility", () => + { + infoWedge.EdgeEffect = new EdgeEffectParameters + { + Colour = Colour4.Red, + Type = EdgeEffectType.Shadow, + Radius = 5, + }; + }); + AddStep("hide", () => { infoWedge.Hide(); }); + AddWaitStep("wait for hide", 3); + AddAssert("check visibility", () => infoWedge.Alpha == 0); + AddStep("show", () => { infoWedge.Show(); }); + AddWaitStep("wait for show", 1); + AddAssert("check visibility", () => infoWedge.Alpha > 0); + } + private void testBeatmapLabels(Ruleset ruleset) { AddAssert("check title", () => infoWedge.Info!.TitleLabel.Current.Value == $"{ruleset.ShortName}Title"); From 468419896a5364252afa23263d9e43d26c4b7edf Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Sat, 11 Feb 2023 18:08:50 +0100 Subject: [PATCH 0024/4092] Separate ruleset changing tests into their own method. Add small clarification for edge colouring in visibility test --- .../SongSelect/TestSceneBeatmapInfoWedgeV2.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs index f99950dfb0..ebd8c008b3 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs @@ -39,22 +39,25 @@ namespace osu.Game.Tests.Visual.SongSelect Add(infoWedge = new TestBeatmapInfoWedgeV2 { + State = { Value = Visibility.Visible }, Width = 0.6f, RelativeSizeAxes = Axes.X, Margin = new MarginPadding { Top = 20 } }); - AddStep("show", () => infoWedge.Show()); - - selectBeatmap(Beatmap.Value.Beatmap); - - AddWaitStep("wait for select", 3); - AddSliderStep("change star difficulty", 0, 11.9, 5.55, v => { foreach (var hasCurrentValue in infoWedge.Info.ChildrenOfType>()) hasCurrentValue.Current.Value = new StarDifficulty(v, 0); }); + } + + [Test] + public void TestRulesetChange() + { + selectBeatmap(Beatmap.Value.Beatmap); + + AddWaitStep("wait for select", 3); foreach (var rulesetInfo in rulesets.AvailableRulesets) { @@ -74,6 +77,8 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestWedgeVisibility() { + // Mostly just in case someone runs this test before others, + // leading to the shadow being very hard to see if it is black AddStep("Make shadow red for test visibility", () => { infoWedge.EdgeEffect = new EdgeEffectParameters From 09cb6ca3a797e516c3097b48883468be0f85f237 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Sat, 11 Feb 2023 18:15:21 +0100 Subject: [PATCH 0025/4092] Clean up formatting and wedge placement in testscene a tad, --- .../Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs index ebd8c008b3..3f3c7441f4 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.SongSelect State = { Value = Visibility.Visible }, Width = 0.6f, RelativeSizeAxes = Axes.X, - Margin = new MarginPadding { Top = 20 } + Margin = new MarginPadding { Top = 20, Left = -10 } }); AddSliderStep("change star difficulty", 0, 11.9, 5.55, v => @@ -79,7 +79,7 @@ namespace osu.Game.Tests.Visual.SongSelect { // Mostly just in case someone runs this test before others, // leading to the shadow being very hard to see if it is black - AddStep("Make shadow red for test visibility", () => + AddStep("make shadow red for test visibility", () => { infoWedge.EdgeEffect = new EdgeEffectParameters { From 299023fce036d6995ead30d00d1de6364bbcc137 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Tue, 21 Feb 2023 16:07:26 +0100 Subject: [PATCH 0026/4092] Improve visibility of wedge shading in test scene and fix an issue with excessive roundness on said shadow. --- .../SongSelect/TestSceneBeatmapInfoWedgeV2.cs | 25 ++++++++++++++----- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 9 +++---- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs index 3f3c7441f4..09b93119cc 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -37,12 +38,25 @@ namespace osu.Game.Tests.Visual.SongSelect { base.LoadComplete(); - Add(infoWedge = new TestBeatmapInfoWedgeV2 + AddRange(new Drawable[] { - State = { Value = Visibility.Visible }, - Width = 0.6f, - RelativeSizeAxes = Axes.X, - Margin = new MarginPadding { Top = 20, Left = -10 } + // This exists only to make the wedge more visible in the test scene + new Box + { + Y = -20, + Colour = Colour4.Cornsilk.Darken(0.2f), + Height = BeatmapInfoWedgeV2.WEDGE_HEIGHT + 40, + Width = 0.65f, + RelativeSizeAxes = Axes.X, + Margin = new MarginPadding { Top = 20, Left = -10 } + }, + infoWedge = new TestBeatmapInfoWedgeV2 + { + State = { Value = Visibility.Visible }, + Width = 0.6f, + RelativeSizeAxes = Axes.X, + Margin = new MarginPadding { Top = 20, Left = -10 } + }, }); AddSliderStep("change star difficulty", 0, 11.9, 5.55, v => @@ -200,7 +214,6 @@ namespace osu.Game.Tests.Visual.SongSelect private partial class TestBeatmapInfoWedgeV2 : BeatmapInfoWedgeV2 { public new Container? DisplayedContent => base.DisplayedContent; - public new WedgeInfoText? Info => base.Info; } diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index fda20dde4d..0a35e68c7e 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -25,8 +25,8 @@ namespace osu.Game.Screens.Select { public partial class BeatmapInfoWedgeV2 : VisibilityContainer { + public const float WEDGE_HEIGHT = 120; private const float shear_width = 21; - private const float wedge_height = 120; private const float transition_duration = 250; private const float corner_radius = 10; private const float colour_bar_width = 30; @@ -34,7 +34,7 @@ namespace osu.Game.Screens.Select /// Todo: move this const out to song select when more new design elements are implemented for the beatmap details area, since it applies to text alignment of various elements private const float text_margin = 62; - private static readonly Vector2 wedged_container_shear = new Vector2(shear_width / wedge_height, 0); + private static readonly Vector2 wedged_container_shear = new Vector2(shear_width / WEDGE_HEIGHT, 0); [Resolved] private IBindable ruleset { get; set; } = null!; @@ -52,7 +52,7 @@ namespace osu.Game.Screens.Select public BeatmapInfoWedgeV2() { - Height = wedge_height; + Height = WEDGE_HEIGHT; Shear = wedged_container_shear; Masking = true; EdgeEffect = new EdgeEffectParameters @@ -60,7 +60,6 @@ namespace osu.Game.Screens.Select Colour = Colour4.Black.Opacity(.25f), Type = EdgeEffectType.Shadow, Radius = corner_radius, - Roundness = corner_radius }; CornerRadius = corner_radius; @@ -95,7 +94,7 @@ namespace osu.Game.Screens.Select Width = colour_bar_width, Child = starCounter = new StarCounter { - Rotation = (float)(Math.Atan(shear_width / wedge_height) * (180 / Math.PI)), + Rotation = (float)(Math.Atan(shear_width / WEDGE_HEIGHT) * (180 / Math.PI)), Colour = Colour4.Transparent, Anchor = Anchor.Centre, Origin = Anchor.Centre, From f21238f517fa8e22a694affd4406bf3b858a8bb4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Mar 2023 16:51:57 +0900 Subject: [PATCH 0027/4092] Adjust shadow to look better --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index 0a35e68c7e..b7b60cffab 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -57,9 +57,9 @@ namespace osu.Game.Screens.Select Masking = true; EdgeEffect = new EdgeEffectParameters { - Colour = Colour4.Black.Opacity(.25f), + Colour = Colour4.Black.Opacity(0.2f), Type = EdgeEffectType.Shadow, - Radius = corner_radius, + Radius = 3, }; CornerRadius = corner_radius; From 102576cd8c5baa9077e5b1b864b52c83c23c71ae Mon Sep 17 00:00:00 2001 From: Elvendir Date: Sat, 18 Mar 2023 17:53:41 +0100 Subject: [PATCH 0028/4092] adddede LastPlayed as filter option in beatmap carousel --- .../Select/Carousel/CarouselBeatmap.cs | 1 + osu.Game/Screens/Select/FilterCriteria.cs | 1 + osu.Game/Screens/Select/FilterQueryParser.cs | 53 ++++++++++++++++++- 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 7e48bc5cdd..5088dfdc02 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -48,6 +48,7 @@ namespace osu.Game.Screens.Select.Carousel match &= !criteria.CircleSize.HasFilter || criteria.CircleSize.IsInRange(BeatmapInfo.Difficulty.CircleSize); match &= !criteria.OverallDifficulty.HasFilter || criteria.OverallDifficulty.IsInRange(BeatmapInfo.Difficulty.OverallDifficulty); match &= !criteria.Length.HasFilter || criteria.Length.IsInRange(BeatmapInfo.Length); + match &= !criteria.LastPlayed.HasFilter || criteria.LastPlayed.IsInRange(BeatmapInfo.LastPlayed); match &= !criteria.BPM.HasFilter || criteria.BPM.IsInRange(BeatmapInfo.BPM); match &= !criteria.BeatDivisor.HasFilter || criteria.BeatDivisor.IsInRange(BeatmapInfo.BeatDivisor); diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 320bfb1b45..a2da98368d 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -31,6 +31,7 @@ namespace osu.Game.Screens.Select public OptionalRange BPM; public OptionalRange BeatDivisor; public OptionalRange OnlineStatus; + public OptionalRange LastPlayed; public OptionalTextFilter Creator; public OptionalTextFilter Artist; diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index c86554ddbc..6b6b2f04a9 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -61,6 +61,10 @@ namespace osu.Game.Screens.Select case "length": return tryUpdateLengthRange(criteria, op, value); + case "played": + case "lastplayed": + return tryUpdateLastPlayedRange(criteria, op, value); + case "divisor": return TryUpdateCriteriaRange(ref criteria.BeatDivisor, op, value, tryParseInt); @@ -109,7 +113,8 @@ namespace osu.Game.Screens.Select value.EndsWith("ms", StringComparison.Ordinal) ? 1 : value.EndsWith('s') ? 1000 : value.EndsWith('m') ? 60000 : - value.EndsWith('h') ? 3600000 : 1000; + value.EndsWith('h') ? 3600000 : + value.EndsWith('d') ? 86400000 : 1000; private static bool tryParseFloatWithPoint(string value, out float result) => float.TryParse(value, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out result); @@ -368,5 +373,51 @@ namespace osu.Game.Screens.Select return tryUpdateCriteriaRange(ref criteria.Length, op, totalLength, minScale / 2.0); } + + private static bool tryUpdateLastPlayedRange(FilterCriteria criteria, Operator op, string val) + { + List parts = new List(); + + GroupCollection? match = null; + + match ??= tryMatchRegex(val, @"^((?\d+):)?(?\d+):(?\d+)$"); + match ??= tryMatchRegex(val, @"^((?\d+(\.\d+)?)h)?((?\d+(\.\d+)?)m)?((?\d+(\.\d+)?)s)?$"); + match ??= tryMatchRegex(val, @"^(?\d+(\.\d+)?)$"); + + if (match == null) + return false; + + if (match["seconds"].Success) + parts.Add(match["seconds"].Value + "s"); + if (match["minutes"].Success) + parts.Add(match["minutes"].Value + "m"); + if (match["hours"].Success) + parts.Add(match["hours"].Value + "h"); + if (match["days"].Success) + parts.Add(match["days"].Value + "d"); + + + double totalLength = 0; + int minScale = 86400000; + + for (int i = 0; i < parts.Count; i++) + { + string part = parts[i]; + string partNoUnit = part.TrimEnd('m', 's', 'h', 'd') ; + if (!tryParseDoubleWithPoint(partNoUnit, out double length)) + return false; + + if (i != parts.Count - 1 && length >= 60) + return false; + if (i != 0 && partNoUnit.Contains('.')) + return false; + + int scale = getLengthScale(part); + totalLength += length * scale; + minScale = Math.Min(minScale, scale); + } + + return tryUpdateCriteriaRange(ref criteria.LastPlayed, op, totalLength, minScale / 2.0); + } } } From 216a88e18d71f74ddccea1b70aff77305ec88920 Mon Sep 17 00:00:00 2001 From: Elvendir Date: Sat, 18 Mar 2023 21:35:10 +0100 Subject: [PATCH 0029/4092] limited max Date comparison --- .../Screens/Select/Carousel/CarouselBeatmap.cs | 2 +- osu.Game/Screens/Select/FilterQueryParser.cs | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 5088dfdc02..069d4f36d6 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Select.Carousel match &= !criteria.CircleSize.HasFilter || criteria.CircleSize.IsInRange(BeatmapInfo.Difficulty.CircleSize); match &= !criteria.OverallDifficulty.HasFilter || criteria.OverallDifficulty.IsInRange(BeatmapInfo.Difficulty.OverallDifficulty); match &= !criteria.Length.HasFilter || criteria.Length.IsInRange(BeatmapInfo.Length); - match &= !criteria.LastPlayed.HasFilter || criteria.LastPlayed.IsInRange(BeatmapInfo.LastPlayed); + match &= !criteria.LastPlayed.HasFilter || criteria.LastPlayed.IsInRange(BeatmapInfo.LastPlayed ?? DateTimeOffset.MinValue); match &= !criteria.BPM.HasFilter || criteria.BPM.IsInRange(BeatmapInfo.BPM); match &= !criteria.BeatDivisor.HasFilter || criteria.BeatDivisor.IsInRange(BeatmapInfo.BeatDivisor); diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 6b6b2f04a9..38040c2f79 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -381,7 +381,7 @@ namespace osu.Game.Screens.Select GroupCollection? match = null; match ??= tryMatchRegex(val, @"^((?\d+):)?(?\d+):(?\d+)$"); - match ??= tryMatchRegex(val, @"^((?\d+(\.\d+)?)h)?((?\d+(\.\d+)?)m)?((?\d+(\.\d+)?)s)?$"); + match ??= tryMatchRegex(val, @"^((?\d+(\.\d+)?)d)?((?\d+(\.\d+)?)h)?((?\d+(\.\d+)?)m)?((?\d+(\.\d+)?)s)?$"); match ??= tryMatchRegex(val, @"^(?\d+(\.\d+)?)$"); if (match == null) @@ -403,7 +403,7 @@ namespace osu.Game.Screens.Select for (int i = 0; i < parts.Count; i++) { string part = parts[i]; - string partNoUnit = part.TrimEnd('m', 's', 'h', 'd') ; + string partNoUnit = part.TrimEnd('m', 's', 'h', 'd'); if (!tryParseDoubleWithPoint(partNoUnit, out double length)) return false; @@ -417,7 +417,16 @@ namespace osu.Game.Screens.Select minScale = Math.Min(minScale, scale); } - return tryUpdateCriteriaRange(ref criteria.LastPlayed, op, totalLength, minScale / 2.0); + totalLength += minScale / 2; + + // Limits the date to ~2000 years compared to now + // Might want to do it differently before 4000 A.C. + double limit = 86400000; + limit *= 365 * 2000; + totalLength = Math.Min(totalLength, limit); + + DateTimeOffset dateTimeOffset = DateTimeOffset.Now; + return tryUpdateCriteriaRange(ref criteria.LastPlayed, op, dateTimeOffset.AddMilliseconds(-totalLength)); } } } From 6dead81d211a760e185ff0248a080454e6729197 Mon Sep 17 00:00:00 2001 From: Elvendir Date: Sun, 19 Mar 2023 18:43:17 +0100 Subject: [PATCH 0030/4092] Generalized tryUpdateLastPlayedRange to tryUpdateDateRange and Added tests --- .../Filtering/FilterQueryParserTest.cs | 82 ++++++++++++++ osu.Game/Screens/Select/FilterQueryParser.cs | 107 ++++++++++++------ 2 files changed, 152 insertions(+), 37 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index da32edb8fb..9460228644 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -316,5 +316,87 @@ namespace osu.Game.Tests.NonVisual.Filtering return false; } } + + //Date criteria testing + + private static readonly object[] correct_date_query_examples = + { + new object[] { "600" }, + new object[] { "120:120" }, + new object[] { "48:0:0" }, + new object[] { "0.5s" }, + new object[] { "120m" }, + new object[] { "48h120s" }, + new object[] { "10y24M" }, + new object[] { "10y60d120s" }, + new object[] { "0.1y0.1M2d" }, + new object[] { "0.99y0.99M2d" } + }; + + [Test] + [TestCaseSource(nameof(correct_date_query_examples))] + public void TestValidDateQueries(string dateQuery) + { + string query = $"played={dateQuery} time"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual(true, filterCriteria.LastPlayed.HasFilter); + } + + private static readonly object[] incorrect_date_query_examples = + { + new object[] { "7m27" }, + new object[] { "7m7m7m" }, + new object[] { "5s6m" }, + new object[] { "7d7y" }, + new object[] { ":0" }, + new object[] { "0:3:" }, + new object[] { "\"three days\"" } + }; + + [Test] + [TestCaseSource(nameof(incorrect_date_query_examples))] + public void TestInvalidDateQueries(string dateQuery) + { + string query = $"played={dateQuery} time"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual(false, filterCriteria.LastPlayed.HasFilter); + } + + private static readonly object[] list_operators = + { + new object[] { "=", false, false, true, true }, + new object[] { ":", false, false, true, true }, + new object[] { "<", false, true, false, false }, + new object[] { "<=", false, true, true, false }, + new object[] { "<:", false, true, true, false }, + new object[] { ">", true, false, false, false }, + new object[] { ">=", true, false, false, true }, + new object[] { ">:", true, false, false, true } + }; + + [Test] + [TestCaseSource(nameof(list_operators))] + public void TestComparisonDateQueries(string ope, bool minIsNull, bool maxIsNull, bool isLowerInclusive, bool isUpperInclusive) + { + string query = $"played{ope}50"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual(isLowerInclusive, filterCriteria.LastPlayed.IsLowerInclusive); + Assert.AreEqual(isUpperInclusive, filterCriteria.LastPlayed.IsUpperInclusive); + Assert.AreEqual(maxIsNull, filterCriteria.LastPlayed.Max == null); + Assert.AreEqual(minIsNull, filterCriteria.LastPlayed.Min == null); + } + + [Test] + public void TestOutofrangeDateQuery() + { + const string query = "played=10000y"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual(true, filterCriteria.LastPlayed.HasFilter); + Assert.AreEqual(DateTimeOffset.MinValue.AddMilliseconds(1), filterCriteria.LastPlayed.Min); + } } } diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 38040c2f79..f66f1bcd1d 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -63,7 +63,7 @@ namespace osu.Game.Screens.Select case "played": case "lastplayed": - return tryUpdateLastPlayedRange(criteria, op, value); + return tryUpdateDateRange(ref criteria.LastPlayed, op, value); case "divisor": return TryUpdateCriteriaRange(ref criteria.BeatDivisor, op, value, tryParseInt); @@ -374,59 +374,92 @@ namespace osu.Game.Screens.Select return tryUpdateCriteriaRange(ref criteria.Length, op, totalLength, minScale / 2.0); } - private static bool tryUpdateLastPlayedRange(FilterCriteria criteria, Operator op, string val) + private static bool tryUpdateDateRange(ref FilterCriteria.OptionalRange dateRange, Operator op, string val) { - List parts = new List(); - GroupCollection? match = null; match ??= tryMatchRegex(val, @"^((?\d+):)?(?\d+):(?\d+)$"); - match ??= tryMatchRegex(val, @"^((?\d+(\.\d+)?)d)?((?\d+(\.\d+)?)h)?((?\d+(\.\d+)?)m)?((?\d+(\.\d+)?)s)?$"); + match ??= tryMatchRegex(val, @"^((?\d+(\.\d+)?)y)?((?\d+(\.\d+)?)M)?((?\d+(\.\d+)?)d)?((?\d+(\.\d+)?)h)?((?\d+(\.\d+)?)m)?((?\d+(\.\d+)?)s)?$"); match ??= tryMatchRegex(val, @"^(?\d+(\.\d+)?)$"); if (match == null) return false; - if (match["seconds"].Success) - parts.Add(match["seconds"].Value + "s"); - if (match["minutes"].Success) - parts.Add(match["minutes"].Value + "m"); - if (match["hours"].Success) - parts.Add(match["hours"].Value + "h"); - if (match["days"].Success) - parts.Add(match["days"].Value + "d"); + DateTimeOffset dateTimeOffset = DateTimeOffset.Now; - - double totalLength = 0; - int minScale = 86400000; - - for (int i = 0; i < parts.Count; i++) + try { - string part = parts[i]; - string partNoUnit = part.TrimEnd('m', 's', 'h', 'd'); - if (!tryParseDoubleWithPoint(partNoUnit, out double length)) - return false; + List keys = new List { "seconds", "minutes", "hours", "days", "months", "years" }; - if (i != parts.Count - 1 && length >= 60) - return false; - if (i != 0 && partNoUnit.Contains('.')) - return false; + foreach (string key in keys) + { + if (match[key].Success) + { + if (!tryParseDoubleWithPoint(match[key].Value, out double length)) + return false; - int scale = getLengthScale(part); - totalLength += length * scale; - minScale = Math.Min(minScale, scale); + switch (key) + { + case "seconds": + dateTimeOffset = dateTimeOffset.AddSeconds(-length); + break; + + case "minutes": + dateTimeOffset = dateTimeOffset.AddMinutes(-length); + break; + + case "hours": + dateTimeOffset = dateTimeOffset.AddHours(-length); + break; + + case "days": + dateTimeOffset = dateTimeOffset.AddDays(-length); + break; + + case "months": + dateTimeOffset = dateTimeOffset.AddMonths(-(int)Math.Round(length)); + break; + + case "years": + dateTimeOffset = dateTimeOffset.AddYears(-(int)Math.Round(length)); + break; + } + } + } + } + // If DateTime to compare is out-scope put it to Min + catch (Exception) + { + dateTimeOffset = DateTimeOffset.MinValue; + dateTimeOffset = dateTimeOffset.AddMilliseconds(1); } - totalLength += minScale / 2; + return tryUpdateCriteriaRange(ref dateRange, invert(op), dateTimeOffset); + } - // Limits the date to ~2000 years compared to now - // Might want to do it differently before 4000 A.C. - double limit = 86400000; - limit *= 365 * 2000; - totalLength = Math.Min(totalLength, limit); + // Function to reverse an Operator + private static Operator invert(Operator ope) + { + switch (ope) + { + default: + return Operator.Equal; - DateTimeOffset dateTimeOffset = DateTimeOffset.Now; - return tryUpdateCriteriaRange(ref criteria.LastPlayed, op, dateTimeOffset.AddMilliseconds(-totalLength)); + case Operator.Equal: + return Operator.Equal; + + case Operator.Greater: + return Operator.Less; + + case Operator.GreaterOrEqual: + return Operator.LessOrEqual; + + case Operator.Less: + return Operator.Greater; + + case Operator.LessOrEqual: + return Operator.GreaterOrEqual; + } } } } From 4b053b47852ea46ad104156d81a84e3bdaaf3b04 Mon Sep 17 00:00:00 2001 From: Elvendir Date: Sat, 1 Apr 2023 22:58:25 +0200 Subject: [PATCH 0031/4092] changed regex match to be inline with standard --- .../NonVisual/Filtering/FilterQueryParserTest.cs | 3 +-- osu.Game/Screens/Select/FilterQueryParser.cs | 9 +++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 9460228644..b0cc9146d2 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -322,8 +322,6 @@ namespace osu.Game.Tests.NonVisual.Filtering private static readonly object[] correct_date_query_examples = { new object[] { "600" }, - new object[] { "120:120" }, - new object[] { "48:0:0" }, new object[] { "0.5s" }, new object[] { "120m" }, new object[] { "48h120s" }, @@ -350,6 +348,7 @@ namespace osu.Game.Tests.NonVisual.Filtering new object[] { "5s6m" }, new object[] { "7d7y" }, new object[] { ":0" }, + new object[] { "0:3:6" }, new object[] { "0:3:" }, new object[] { "\"three days\"" } }; diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index f66f1bcd1d..f66b8fd377 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -378,9 +378,8 @@ namespace osu.Game.Screens.Select { GroupCollection? match = null; - match ??= tryMatchRegex(val, @"^((?\d+):)?(?\d+):(?\d+)$"); match ??= tryMatchRegex(val, @"^((?\d+(\.\d+)?)y)?((?\d+(\.\d+)?)M)?((?\d+(\.\d+)?)d)?((?\d+(\.\d+)?)h)?((?\d+(\.\d+)?)m)?((?\d+(\.\d+)?)s)?$"); - match ??= tryMatchRegex(val, @"^(?\d+(\.\d+)?)$"); + match ??= tryMatchRegex(val, @"^(?\d+(\.\d+)?)$"); if (match == null) return false; @@ -417,11 +416,13 @@ namespace osu.Game.Screens.Select break; case "months": - dateTimeOffset = dateTimeOffset.AddMonths(-(int)Math.Round(length)); + dateTimeOffset = dateTimeOffset.AddMonths(-(int)Math.Floor(length)); + dateTimeOffset = dateTimeOffset.AddDays(-30 * (length - Math.Floor(length))); break; case "years": - dateTimeOffset = dateTimeOffset.AddYears(-(int)Math.Round(length)); + dateTimeOffset = dateTimeOffset.AddYears(-(int)Math.Floor(length)); + dateTimeOffset = dateTimeOffset.AddDays(-365 * (length - Math.Floor(length))); break; } } From 52adb99fe5fe4f7f7ea756f0723354f17904203e Mon Sep 17 00:00:00 2001 From: Elvendir <39671719+Elvendir@users.noreply.github.com> Date: Tue, 4 Apr 2023 19:29:37 +0200 Subject: [PATCH 0032/4092] Update osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index b0cc9146d2..627b44dcd7 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -317,7 +317,6 @@ namespace osu.Game.Tests.NonVisual.Filtering } } - //Date criteria testing private static readonly object[] correct_date_query_examples = { From d6c6507578a0fd3a7a8a502c15febd91b0123069 Mon Sep 17 00:00:00 2001 From: Elvendir <39671719+Elvendir@users.noreply.github.com> Date: Tue, 4 Apr 2023 19:30:13 +0200 Subject: [PATCH 0033/4092] Update osu.Game/Screens/Select/FilterQueryParser.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Screens/Select/FilterQueryParser.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index f66b8fd377..9582694248 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -428,8 +428,7 @@ namespace osu.Game.Screens.Select } } } - // If DateTime to compare is out-scope put it to Min - catch (Exception) + catch (ArgumentOutOfRangeException) { dateTimeOffset = DateTimeOffset.MinValue; dateTimeOffset = dateTimeOffset.AddMilliseconds(1); From 0c1d6eb89465066285668b619bc73a54c9fa964c Mon Sep 17 00:00:00 2001 From: Elvendir Date: Wed, 5 Apr 2023 11:42:39 +0200 Subject: [PATCH 0034/4092] - rewrote upper and lower bound tests - removed equality in tests --- .../Filtering/FilterQueryParserTest.cs | 42 ++++++++----------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 627b44dcd7..c1678f14a6 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -317,7 +317,6 @@ namespace osu.Game.Tests.NonVisual.Filtering } } - private static readonly object[] correct_date_query_examples = { new object[] { "600" }, @@ -334,7 +333,7 @@ namespace osu.Game.Tests.NonVisual.Filtering [TestCaseSource(nameof(correct_date_query_examples))] public void TestValidDateQueries(string dateQuery) { - string query = $"played={dateQuery} time"; + string query = $"played<{dateQuery} time"; var filterCriteria = new FilterCriteria(); FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.AreEqual(true, filterCriteria.LastPlayed.HasFilter); @@ -342,11 +341,11 @@ namespace osu.Game.Tests.NonVisual.Filtering private static readonly object[] incorrect_date_query_examples = { + new object[] { ".5s" }, new object[] { "7m27" }, new object[] { "7m7m7m" }, new object[] { "5s6m" }, new object[] { "7d7y" }, - new object[] { ":0" }, new object[] { "0:3:6" }, new object[] { "0:3:" }, new object[] { "\"three days\"" } @@ -356,41 +355,36 @@ namespace osu.Game.Tests.NonVisual.Filtering [TestCaseSource(nameof(incorrect_date_query_examples))] public void TestInvalidDateQueries(string dateQuery) { - string query = $"played={dateQuery} time"; + string query = $"played<{dateQuery} time"; var filterCriteria = new FilterCriteria(); FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.AreEqual(false, filterCriteria.LastPlayed.HasFilter); } - private static readonly object[] list_operators = - { - new object[] { "=", false, false, true, true }, - new object[] { ":", false, false, true, true }, - new object[] { "<", false, true, false, false }, - new object[] { "<=", false, true, true, false }, - new object[] { "<:", false, true, true, false }, - new object[] { ">", true, false, false, false }, - new object[] { ">=", true, false, false, true }, - new object[] { ">:", true, false, false, true } - }; - [Test] - [TestCaseSource(nameof(list_operators))] - public void TestComparisonDateQueries(string ope, bool minIsNull, bool maxIsNull, bool isLowerInclusive, bool isUpperInclusive) + public void TestGreaterDateQuery() { - string query = $"played{ope}50"; + const string query = "played>50"; var filterCriteria = new FilterCriteria(); FilterQueryParser.ApplyQueries(filterCriteria, query); - Assert.AreEqual(isLowerInclusive, filterCriteria.LastPlayed.IsLowerInclusive); - Assert.AreEqual(isUpperInclusive, filterCriteria.LastPlayed.IsUpperInclusive); - Assert.AreEqual(maxIsNull, filterCriteria.LastPlayed.Max == null); - Assert.AreEqual(minIsNull, filterCriteria.LastPlayed.Min == null); + Assert.AreEqual(false, filterCriteria.LastPlayed.Max == null); + Assert.AreEqual(true, filterCriteria.LastPlayed.Min == null); + } + + [Test] + public void TestLowerDateQuery() + { + const string query = "played<50"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual(true, filterCriteria.LastPlayed.Max == null); + Assert.AreEqual(false, filterCriteria.LastPlayed.Min == null); } [Test] public void TestOutofrangeDateQuery() { - const string query = "played=10000y"; + const string query = "played<10000y"; var filterCriteria = new FilterCriteria(); FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.AreEqual(true, filterCriteria.LastPlayed.HasFilter); From df170517a85ea1949d20b6df1043143ac028ffec Mon Sep 17 00:00:00 2001 From: Elvendir Date: Wed, 5 Apr 2023 11:59:31 +0200 Subject: [PATCH 0035/4092] -renamed function inverse() to reverseInequalityOperator() for clarity -changed default case of reverseInequalityOperator() to out of range exception --- osu.Game/Screens/Select/FilterQueryParser.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 9582694248..bd4c29e457 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -434,16 +434,16 @@ namespace osu.Game.Screens.Select dateTimeOffset = dateTimeOffset.AddMilliseconds(1); } - return tryUpdateCriteriaRange(ref dateRange, invert(op), dateTimeOffset); + return tryUpdateCriteriaRange(ref dateRange, reverseInequalityOperator(op), dateTimeOffset); } // Function to reverse an Operator - private static Operator invert(Operator ope) + private static Operator reverseInequalityOperator(Operator ope) { switch (ope) { default: - return Operator.Equal; + throw new ArgumentOutOfRangeException(nameof(ope), $"Unsupported operator {ope}"); case Operator.Equal: return Operator.Equal; From c2f225f0253030ea03e710f1647da26f6b106377 Mon Sep 17 00:00:00 2001 From: Elvendir Date: Wed, 5 Apr 2023 21:25:58 +0200 Subject: [PATCH 0036/4092] Made Operator.Equal not parse for date filter and added corresponding test --- .../NonVisual/Filtering/FilterQueryParserTest.cs | 9 +++++++++ osu.Game/Screens/Select/FilterQueryParser.cs | 3 +++ 2 files changed, 12 insertions(+) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index c1678f14a6..450e6bdde7 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -381,6 +381,15 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.AreEqual(false, filterCriteria.LastPlayed.Min == null); } + [Test] + public void TestEqualDateQuery() + { + const string query = "played=50"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual(false, filterCriteria.LastPlayed.HasFilter); + } + [Test] public void TestOutofrangeDateQuery() { diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index bd4c29e457..70893c188d 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -376,6 +376,9 @@ namespace osu.Game.Screens.Select private static bool tryUpdateDateRange(ref FilterCriteria.OptionalRange dateRange, Operator op, string val) { + if (op == Operator.Equal) + return false; + GroupCollection? match = null; match ??= tryMatchRegex(val, @"^((?\d+(\.\d+)?)y)?((?\d+(\.\d+)?)M)?((?\d+(\.\d+)?)d)?((?\d+(\.\d+)?)h)?((?\d+(\.\d+)?)m)?((?\d+(\.\d+)?)s)?$"); From 928145cdeb3b523833ed6b5f3f85c830d91f4dc8 Mon Sep 17 00:00:00 2001 From: Elvendir Date: Wed, 5 Apr 2023 22:12:15 +0200 Subject: [PATCH 0037/4092] Enforce integer value before y and M Change impacted Tests --- .../NonVisual/Filtering/FilterQueryParserTest.cs | 8 +++++--- osu.Game/Screens/Select/FilterQueryParser.cs | 12 ++++++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 450e6bdde7..78b428e7c0 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -325,8 +325,8 @@ namespace osu.Game.Tests.NonVisual.Filtering new object[] { "48h120s" }, new object[] { "10y24M" }, new object[] { "10y60d120s" }, - new object[] { "0.1y0.1M2d" }, - new object[] { "0.99y0.99M2d" } + new object[] { "0y0M2d" }, + new object[] { "1y1M2d" } }; [Test] @@ -348,7 +348,9 @@ namespace osu.Game.Tests.NonVisual.Filtering new object[] { "7d7y" }, new object[] { "0:3:6" }, new object[] { "0:3:" }, - new object[] { "\"three days\"" } + new object[] { "\"three days\"" }, + new object[] { "0.1y0.1M2d" }, + new object[] { "0.99y0.99M2d" } }; [Test] diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 70893c188d..b49f0ba057 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -419,13 +419,17 @@ namespace osu.Game.Screens.Select break; case "months": - dateTimeOffset = dateTimeOffset.AddMonths(-(int)Math.Floor(length)); - dateTimeOffset = dateTimeOffset.AddDays(-30 * (length - Math.Floor(length))); + if (match[key].Value.Contains('.')) + return false; + + dateTimeOffset = dateTimeOffset.AddMonths(-(int)length); break; case "years": - dateTimeOffset = dateTimeOffset.AddYears(-(int)Math.Floor(length)); - dateTimeOffset = dateTimeOffset.AddDays(-365 * (length - Math.Floor(length))); + if (match[key].Value.Contains('.')) + return false; + + dateTimeOffset = dateTimeOffset.AddYears(-(int)length); break; } } From 8e156fdb5102af8cd71aaac32d3c36b3c018d382 Mon Sep 17 00:00:00 2001 From: Elvendir Date: Fri, 7 Apr 2023 00:29:46 +0200 Subject: [PATCH 0038/4092] Enforce integer through regex match instead --- osu.Game/Screens/Select/FilterQueryParser.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index b49f0ba057..348f663b8e 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -381,7 +381,7 @@ namespace osu.Game.Screens.Select GroupCollection? match = null; - match ??= tryMatchRegex(val, @"^((?\d+(\.\d+)?)y)?((?\d+(\.\d+)?)M)?((?\d+(\.\d+)?)d)?((?\d+(\.\d+)?)h)?((?\d+(\.\d+)?)m)?((?\d+(\.\d+)?)s)?$"); + match ??= tryMatchRegex(val, @"^((?\d+)y)?((?\d+)M)?((?\d+(\.\d+)?)d)?((?\d+(\.\d+)?)h)?((?\d+(\.\d+)?)m)?((?\d+(\.\d+)?)s)?$"); match ??= tryMatchRegex(val, @"^(?\d+(\.\d+)?)$"); if (match == null) @@ -419,16 +419,10 @@ namespace osu.Game.Screens.Select break; case "months": - if (match[key].Value.Contains('.')) - return false; - dateTimeOffset = dateTimeOffset.AddMonths(-(int)length); break; case "years": - if (match[key].Value.Contains('.')) - return false; - dateTimeOffset = dateTimeOffset.AddYears(-(int)length); break; } From 7c8c6790d017216cd65ad9fd3f3bc35e6231c6cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 7 May 2023 13:10:59 +0200 Subject: [PATCH 0039/4092] Refactor metadata lookup to streamline online metadata application logic --- osu.Game/Beatmaps/APIBeatmapMetadataSource.cs | 78 ++++++ .../Beatmaps/BeatmapUpdaterMetadataLookup.cs | 251 +++--------------- .../Beatmaps/IOnlineBeatmapMetadataSource.cs | 26 ++ .../LocalCachedBeatmapMetadataSource.cs | 176 ++++++++++++ osu.Game/Beatmaps/OnlineBeatmapMetadata.cs | 61 +++++ 5 files changed, 382 insertions(+), 210 deletions(-) create mode 100644 osu.Game/Beatmaps/APIBeatmapMetadataSource.cs create mode 100644 osu.Game/Beatmaps/IOnlineBeatmapMetadataSource.cs create mode 100644 osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs create mode 100644 osu.Game/Beatmaps/OnlineBeatmapMetadata.cs diff --git a/osu.Game/Beatmaps/APIBeatmapMetadataSource.cs b/osu.Game/Beatmaps/APIBeatmapMetadataSource.cs new file mode 100644 index 0000000000..e1b01aaac5 --- /dev/null +++ b/osu.Game/Beatmaps/APIBeatmapMetadataSource.cs @@ -0,0 +1,78 @@ +// 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.Game.Database; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; + +namespace osu.Game.Beatmaps +{ + public class APIBeatmapMetadataSource : IOnlineBeatmapMetadataSource + { + private readonly IAPIProvider api; + + public APIBeatmapMetadataSource(IAPIProvider api) + { + this.api = api; + } + + public bool Available => api.State.Value == APIState.Online; + + public OnlineBeatmapMetadata? Lookup(BeatmapInfo beatmapInfo) + { + if (!Available) + return null; + + Debug.Assert(beatmapInfo.BeatmapSet != null); + + var req = new GetBeatmapRequest(beatmapInfo); + + try + { + // intentionally blocking to limit web request concurrency + api.Perform(req); + + if (req.CompletionState == APIRequestCompletionState.Failed) + { + logForModel(beatmapInfo.BeatmapSet, $@"Online retrieval failed for {beatmapInfo}"); + return null; + } + + var res = req.Response; + + if (res != null) + { + logForModel(beatmapInfo.BeatmapSet, $@"Online retrieval mapped {beatmapInfo} to {res.OnlineBeatmapSetID} / {res.OnlineID}."); + + return new OnlineBeatmapMetadata + { + BeatmapID = res.OnlineID, + BeatmapSetID = res.OnlineBeatmapSetID, + AuthorID = res.AuthorID, + BeatmapStatus = res.Status, + BeatmapSetStatus = res.BeatmapSet?.Status, + DateRanked = res.BeatmapSet?.Ranked, + DateSubmitted = res.BeatmapSet?.Submitted, + MD5Hash = res.MD5Hash, + LastUpdated = res.LastUpdated + }; + } + } + catch (Exception e) + { + logForModel(beatmapInfo.BeatmapSet, $@"Online retrieval failed for {beatmapInfo} ({e.Message})"); + } + + return null; + } + + private void logForModel(BeatmapSetInfo set, string message) => + RealmArchiveModelImporter.LogForModel(set, $@"[{nameof(APIBeatmapMetadataSource)}] {message}"); + + public void Dispose() + { + } + } +} diff --git a/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs index fac91c23f5..b326e7ad1c 100644 --- a/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs +++ b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs @@ -1,62 +1,31 @@ // 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.Diagnostics; -using System.IO; using System.Linq; -using System.Threading.Tasks; -using Microsoft.Data.Sqlite; -using osu.Framework.Development; -using osu.Framework.IO.Network; -using osu.Framework.Logging; using osu.Framework.Platform; -using osu.Game.Database; using osu.Game.Online.API; -using osu.Game.Online.API.Requests; -using SharpCompress.Compressors; -using SharpCompress.Compressors.BZip2; -using SQLitePCL; namespace osu.Game.Beatmaps { /// /// A component which handles population of online IDs for beatmaps using a two part lookup procedure. /// - /// - /// On creating the component, a copy of a database containing metadata for a large subset of beatmaps (stored to ) will be downloaded if not already present locally. - /// This will always be checked before doing a second online query to get required metadata. - /// public class BeatmapUpdaterMetadataLookup : IDisposable { - private readonly IAPIProvider api; - private readonly Storage storage; - - private FileWebRequest cacheDownloadRequest; - - private const string cache_database_name = "online.db"; + private readonly IOnlineBeatmapMetadataSource apiMetadataSource; + private readonly IOnlineBeatmapMetadataSource localCachedMetadataSource; public BeatmapUpdaterMetadataLookup(IAPIProvider api, Storage storage) + : this(new APIBeatmapMetadataSource(api), new LocalCachedBeatmapMetadataSource(storage)) { - try - { - // required to initialise native SQLite libraries on some platforms. - Batteries_V2.Init(); - raw.sqlite3_config(2 /*SQLITE_CONFIG_MULTITHREAD*/); - } - catch - { - // may fail if platform not supported. - } + } - this.api = api; - this.storage = storage; - - // avoid downloading / using cache for unit tests. - if (!DebugUtils.IsNUnitRunning && !storage.Exists(cache_database_name)) - prepareLocalCache(); + internal BeatmapUpdaterMetadataLookup(IOnlineBeatmapMetadataSource apiMetadataSource, IOnlineBeatmapMetadataSource localCachedMetadataSource) + { + this.apiMetadataSource = apiMetadataSource; + this.localCachedMetadataSource = localCachedMetadataSource; } /// @@ -69,196 +38,57 @@ namespace osu.Game.Beatmaps /// Whether metadata from an online source should be preferred. If true, the local cache will be skipped to ensure the freshest data state possible. public void Update(BeatmapSetInfo beatmapSet, bool preferOnlineFetch) { - foreach (var b in beatmapSet.Beatmaps) - lookup(beatmapSet, b, preferOnlineFetch); - } - - private void lookup(BeatmapSetInfo set, BeatmapInfo beatmapInfo, bool preferOnlineFetch) - { - bool apiAvailable = api?.State.Value == APIState.Online; - - bool useLocalCache = !apiAvailable || !preferOnlineFetch; - - if (useLocalCache && checkLocalCache(set, beatmapInfo)) - return; - - if (!apiAvailable) - return; - - var req = new GetBeatmapRequest(beatmapInfo); - - try + foreach (var beatmapInfo in beatmapSet.Beatmaps) { - // intentionally blocking to limit web request concurrency - api.Perform(req); + var res = lookup(beatmapSet, beatmapInfo, preferOnlineFetch); - if (req.CompletionState == APIRequestCompletionState.Failed) + if (res == null) { - logForModel(set, $"Online retrieval failed for {beatmapInfo}"); beatmapInfo.ResetOnlineInfo(); - return; + continue; } - var res = req.Response; + beatmapInfo.OnlineID = res.BeatmapID; + beatmapInfo.OnlineMD5Hash = res.MD5Hash; + beatmapInfo.LastOnlineUpdate = res.LastUpdated; - if (res != null) + Debug.Assert(beatmapInfo.BeatmapSet != null); + beatmapInfo.BeatmapSet.OnlineID = res.BeatmapSetID; + + // Some metadata should only be applied if there's no local changes. + if (shouldSaveOnlineMetadata(beatmapInfo)) { - beatmapInfo.OnlineID = res.OnlineID; - beatmapInfo.OnlineMD5Hash = res.MD5Hash; - beatmapInfo.LastOnlineUpdate = res.LastUpdated; - - Debug.Assert(beatmapInfo.BeatmapSet != null); - beatmapInfo.BeatmapSet.OnlineID = res.OnlineBeatmapSetID; - - // Some metadata should only be applied if there's no local changes. - if (shouldSaveOnlineMetadata(beatmapInfo)) - { - beatmapInfo.Status = res.Status; - beatmapInfo.Metadata.Author.OnlineID = res.AuthorID; - } - - if (beatmapInfo.BeatmapSet.Beatmaps.All(shouldSaveOnlineMetadata)) - { - beatmapInfo.BeatmapSet.Status = res.BeatmapSet?.Status ?? BeatmapOnlineStatus.None; - beatmapInfo.BeatmapSet.DateRanked = res.BeatmapSet?.Ranked; - beatmapInfo.BeatmapSet.DateSubmitted = res.BeatmapSet?.Submitted; - } - - logForModel(set, $"Online retrieval mapped {beatmapInfo} to {res.OnlineBeatmapSetID} / {res.OnlineID}."); + beatmapInfo.Status = res.BeatmapStatus; + beatmapInfo.Metadata.Author.OnlineID = res.AuthorID; + } + + if (beatmapInfo.BeatmapSet.Beatmaps.All(shouldSaveOnlineMetadata)) + { + beatmapInfo.BeatmapSet.Status = res.BeatmapSetStatus ?? BeatmapOnlineStatus.None; + beatmapInfo.BeatmapSet.DateRanked = res.DateRanked; + beatmapInfo.BeatmapSet.DateSubmitted = res.DateSubmitted; } - } - catch (Exception e) - { - logForModel(set, $"Online retrieval failed for {beatmapInfo} ({e.Message})"); - beatmapInfo.ResetOnlineInfo(); } } - private void prepareLocalCache() + private OnlineBeatmapMetadata? lookup(BeatmapSetInfo set, BeatmapInfo beatmapInfo, bool preferOnlineFetch) { - string cacheFilePath = storage.GetFullPath(cache_database_name); - string compressedCacheFilePath = $"{cacheFilePath}.bz2"; + OnlineBeatmapMetadata? result = null; - cacheDownloadRequest = new FileWebRequest(compressedCacheFilePath, $"https://assets.ppy.sh/client-resources/{cache_database_name}.bz2?{DateTimeOffset.UtcNow:yyyyMMdd}"); + bool useLocalCache = !apiMetadataSource.Available || !preferOnlineFetch; - cacheDownloadRequest.Failed += ex => - { - File.Delete(compressedCacheFilePath); - File.Delete(cacheFilePath); + if (useLocalCache) + result = localCachedMetadataSource.Lookup(beatmapInfo); - Logger.Log($"{nameof(BeatmapUpdaterMetadataLookup)}'s online cache download failed: {ex}", LoggingTarget.Database); - }; + if (result != null) + return result; - cacheDownloadRequest.Finished += () => - { - try - { - using (var stream = File.OpenRead(cacheDownloadRequest.Filename)) - using (var outStream = File.OpenWrite(cacheFilePath)) - using (var bz2 = new BZip2Stream(stream, CompressionMode.Decompress, false)) - bz2.CopyTo(outStream); + if (apiMetadataSource.Available) + result = apiMetadataSource.Lookup(beatmapInfo); - // set to null on completion to allow lookups to begin using the new source - cacheDownloadRequest = null; - } - catch (Exception ex) - { - Logger.Log($"{nameof(BeatmapUpdaterMetadataLookup)}'s online cache extraction failed: {ex}", LoggingTarget.Database); - File.Delete(cacheFilePath); - } - finally - { - File.Delete(compressedCacheFilePath); - } - }; - - Task.Run(async () => - { - try - { - await cacheDownloadRequest.PerformAsync().ConfigureAwait(false); - } - catch - { - // Prevent throwing unobserved exceptions, as they will be logged from the network request to the log file anyway. - } - }); + return result; } - private bool checkLocalCache(BeatmapSetInfo set, BeatmapInfo beatmapInfo) - { - // download is in progress (or was, and failed). - if (cacheDownloadRequest != null) - return false; - - // database is unavailable. - if (!storage.Exists(cache_database_name)) - return false; - - if (string.IsNullOrEmpty(beatmapInfo.MD5Hash) - && string.IsNullOrEmpty(beatmapInfo.Path) - && beatmapInfo.OnlineID <= 0) - return false; - - try - { - using (var db = new SqliteConnection(string.Concat("Data Source=", storage.GetFullPath($@"{"online.db"}", true)))) - { - db.Open(); - - using (var cmd = db.CreateCommand()) - { - cmd.CommandText = - "SELECT beatmapset_id, beatmap_id, approved, user_id, checksum, last_update FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineID OR filename = @Path"; - - cmd.Parameters.Add(new SqliteParameter("@MD5Hash", beatmapInfo.MD5Hash)); - cmd.Parameters.Add(new SqliteParameter("@OnlineID", beatmapInfo.OnlineID)); - cmd.Parameters.Add(new SqliteParameter("@Path", beatmapInfo.Path)); - - using (var reader = cmd.ExecuteReader()) - { - if (reader.Read()) - { - var status = (BeatmapOnlineStatus)reader.GetByte(2); - - // Some metadata should only be applied if there's no local changes. - if (shouldSaveOnlineMetadata(beatmapInfo)) - { - beatmapInfo.Status = status; - beatmapInfo.Metadata.Author.OnlineID = reader.GetInt32(3); - } - - // TODO: DateSubmitted and DateRanked are not provided by local cache. - beatmapInfo.OnlineID = reader.GetInt32(1); - beatmapInfo.OnlineMD5Hash = reader.GetString(4); - beatmapInfo.LastOnlineUpdate = reader.GetDateTimeOffset(5); - - Debug.Assert(beatmapInfo.BeatmapSet != null); - beatmapInfo.BeatmapSet.OnlineID = reader.GetInt32(0); - - if (beatmapInfo.BeatmapSet.Beatmaps.All(shouldSaveOnlineMetadata)) - { - beatmapInfo.BeatmapSet.Status = status; - } - - logForModel(set, $"Cached local retrieval for {beatmapInfo}."); - return true; - } - } - } - } - } - catch (Exception ex) - { - logForModel(set, $"Cached local retrieval for {beatmapInfo} failed with {ex}."); - } - - return false; - } - - private void logForModel(BeatmapSetInfo set, string message) => - RealmArchiveModelImporter.LogForModel(set, $"[{nameof(BeatmapUpdaterMetadataLookup)}] {message}"); - /// /// Check whether the provided beatmap is in a state where online "ranked" status metadata should be saved against it. /// Handles the case where a user may have locally modified a beatmap in the editor and expects the local status to stick. @@ -267,7 +97,8 @@ namespace osu.Game.Beatmaps public void Dispose() { - cacheDownloadRequest?.Dispose(); + apiMetadataSource.Dispose(); + localCachedMetadataSource.Dispose(); } } } diff --git a/osu.Game/Beatmaps/IOnlineBeatmapMetadataSource.cs b/osu.Game/Beatmaps/IOnlineBeatmapMetadataSource.cs new file mode 100644 index 0000000000..753462d493 --- /dev/null +++ b/osu.Game/Beatmaps/IOnlineBeatmapMetadataSource.cs @@ -0,0 +1,26 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; + +namespace osu.Game.Beatmaps +{ + /// + /// Unifying interface for sources of online beatmap metadata. + /// + public interface IOnlineBeatmapMetadataSource : IDisposable + { + /// + /// Whether this source can currently service lookups. + /// + bool Available { get; } + + /// + /// Looks up the online metadata for the supplied . + /// + /// + /// An instance if the lookup is successful, or if the lookup failed. + /// + OnlineBeatmapMetadata? Lookup(BeatmapInfo beatmapInfo); + } +} diff --git a/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs b/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs new file mode 100644 index 0000000000..435242aeab --- /dev/null +++ b/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs @@ -0,0 +1,176 @@ +// 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 System.IO; +using System.Threading.Tasks; +using Microsoft.Data.Sqlite; +using osu.Framework.Development; +using osu.Framework.IO.Network; +using osu.Framework.Logging; +using osu.Framework.Platform; +using osu.Game.Database; +using SharpCompress.Compressors; +using SharpCompress.Compressors.BZip2; +using SQLitePCL; + +namespace osu.Game.Beatmaps +{ + /// + /// + /// + /// + /// On creating the component, a copy of a database containing metadata for a large subset of beatmaps (stored to ) will be downloaded if not already present locally. + /// + public class LocalCachedBeatmapMetadataSource : IOnlineBeatmapMetadataSource + { + private readonly Storage storage; + + private FileWebRequest? cacheDownloadRequest; + + private const string cache_database_name = @"online.db"; + + public LocalCachedBeatmapMetadataSource(Storage storage) + { + try + { + // required to initialise native SQLite libraries on some platforms. + Batteries_V2.Init(); + raw.sqlite3_config(2 /*SQLITE_CONFIG_MULTITHREAD*/); + } + catch + { + // may fail if platform not supported. + } + + this.storage = storage; + + // avoid downloading / using cache for unit tests. + if (!DebugUtils.IsNUnitRunning && !storage.Exists(cache_database_name)) + prepareLocalCache(); + } + + private void prepareLocalCache() + { + string cacheFilePath = storage.GetFullPath(cache_database_name); + string compressedCacheFilePath = $@"{cacheFilePath}.bz2"; + + cacheDownloadRequest = new FileWebRequest(compressedCacheFilePath, $@"https://assets.ppy.sh/client-resources/{cache_database_name}.bz2?{DateTimeOffset.UtcNow:yyyyMMdd}"); + + cacheDownloadRequest.Failed += ex => + { + File.Delete(compressedCacheFilePath); + File.Delete(cacheFilePath); + + Logger.Log($@"{nameof(BeatmapUpdaterMetadataLookup)}'s online cache download failed: {ex}", LoggingTarget.Database); + }; + + cacheDownloadRequest.Finished += () => + { + try + { + using (var stream = File.OpenRead(cacheDownloadRequest.Filename)) + using (var outStream = File.OpenWrite(cacheFilePath)) + using (var bz2 = new BZip2Stream(stream, CompressionMode.Decompress, false)) + bz2.CopyTo(outStream); + + // set to null on completion to allow lookups to begin using the new source + cacheDownloadRequest = null; + } + catch (Exception ex) + { + Logger.Log($@"{nameof(LocalCachedBeatmapMetadataSource)}'s online cache extraction failed: {ex}", LoggingTarget.Database); + File.Delete(cacheFilePath); + } + finally + { + File.Delete(compressedCacheFilePath); + } + }; + + Task.Run(async () => + { + try + { + await cacheDownloadRequest.PerformAsync().ConfigureAwait(false); + } + catch + { + // Prevent throwing unobserved exceptions, as they will be logged from the network request to the log file anyway. + } + }); + } + + public bool Available => + // no download in progress. + cacheDownloadRequest == null + // cached database exists on disk. + && storage.Exists(cache_database_name); + + public OnlineBeatmapMetadata? Lookup(BeatmapInfo beatmapInfo) + { + if (!Available) + return null; + + if (string.IsNullOrEmpty(beatmapInfo.MD5Hash) + && string.IsNullOrEmpty(beatmapInfo.Path) + && beatmapInfo.OnlineID <= 0) + return null; + + Debug.Assert(beatmapInfo.BeatmapSet != null); + + try + { + using (var db = new SqliteConnection(string.Concat(@"Data Source=", storage.GetFullPath(@"online.db", true)))) + { + db.Open(); + + using (var cmd = db.CreateCommand()) + { + cmd.CommandText = + @"SELECT beatmapset_id, beatmap_id, approved, user_id, checksum, last_update FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineID OR filename = @Path"; + + cmd.Parameters.Add(new SqliteParameter(@"@MD5Hash", beatmapInfo.MD5Hash)); + cmd.Parameters.Add(new SqliteParameter(@"@OnlineID", beatmapInfo.OnlineID)); + cmd.Parameters.Add(new SqliteParameter(@"@Path", beatmapInfo.Path)); + + using (var reader = cmd.ExecuteReader()) + { + if (reader.Read()) + { + logForModel(beatmapInfo.BeatmapSet, $@"Cached local retrieval for {beatmapInfo}."); + + return new OnlineBeatmapMetadata + { + BeatmapSetID = reader.GetInt32(0), + BeatmapID = reader.GetInt32(1), + BeatmapStatus = (BeatmapOnlineStatus)reader.GetByte(2), + BeatmapSetStatus = (BeatmapOnlineStatus)reader.GetByte(2), + AuthorID = reader.GetInt32(3), + MD5Hash = reader.GetString(4), + LastUpdated = reader.GetDateTimeOffset(5), + // TODO: DateSubmitted and DateRanked are not provided by local cache. + }; + } + } + } + } + } + catch (Exception ex) + { + logForModel(beatmapInfo.BeatmapSet, $@"Cached local retrieval for {beatmapInfo} failed with {ex}."); + } + + return null; + } + + private void logForModel(BeatmapSetInfo set, string message) => + RealmArchiveModelImporter.LogForModel(set, $@"[{nameof(LocalCachedBeatmapMetadataSource)}] {message}"); + + public void Dispose() + { + cacheDownloadRequest?.Dispose(); + } + } +} diff --git a/osu.Game/Beatmaps/OnlineBeatmapMetadata.cs b/osu.Game/Beatmaps/OnlineBeatmapMetadata.cs new file mode 100644 index 0000000000..8640883ca1 --- /dev/null +++ b/osu.Game/Beatmaps/OnlineBeatmapMetadata.cs @@ -0,0 +1,61 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; + +namespace osu.Game.Beatmaps +{ + /// + /// This structure contains parts of beatmap metadata which are involved with the online parts + /// of the game, and therefore must be treated with particular care. + /// This data is retrieved from trusted sources (such as osu-web API, or a locally downloaded sqlite snapshot + /// of osu-web metadata). + /// + public class OnlineBeatmapMetadata + { + /// + /// The online ID of the beatmap. + /// + public int BeatmapID { get; init; } + + /// + /// The online ID of the beatmap set. + /// + public int BeatmapSetID { get; init; } + + /// + /// The online ID of the author. + /// + public int AuthorID { get; init; } + + /// + /// The online status of the beatmap. + /// + public BeatmapOnlineStatus BeatmapStatus { get; init; } + + /// + /// The online status of the associated beatmap set. + /// + public BeatmapOnlineStatus? BeatmapSetStatus { get; init; } + + /// + /// The rank date of the beatmap, if applicable and available. + /// + public DateTimeOffset? DateRanked { get; init; } + + /// + /// The submission date of the beatmap, if available. + /// + public DateTimeOffset? DateSubmitted { get; init; } + + /// + /// The MD5 hash of the beatmap. Used to verify integrity. + /// + public string MD5Hash { get; init; } = string.Empty; + + /// + /// The date when this metadata was last updated. + /// + public DateTimeOffset LastUpdated { get; init; } + } +} From b384a3258d971419b9be5c1ed35c20070c26e1da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 7 May 2023 13:52:06 +0200 Subject: [PATCH 0040/4092] Remove unused argument --- osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs index b326e7ad1c..98b921e3c7 100644 --- a/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs +++ b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs @@ -40,7 +40,7 @@ namespace osu.Game.Beatmaps { foreach (var beatmapInfo in beatmapSet.Beatmaps) { - var res = lookup(beatmapSet, beatmapInfo, preferOnlineFetch); + var res = lookup(beatmapInfo, preferOnlineFetch); if (res == null) { @@ -71,7 +71,7 @@ namespace osu.Game.Beatmaps } } - private OnlineBeatmapMetadata? lookup(BeatmapSetInfo set, BeatmapInfo beatmapInfo, bool preferOnlineFetch) + private OnlineBeatmapMetadata? lookup(BeatmapInfo beatmapInfo, bool preferOnlineFetch) { OnlineBeatmapMetadata? result = null; From f0ec264bbc2727d8c84ed6e5f8ac59dd89aeac97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 7 May 2023 14:37:34 +0200 Subject: [PATCH 0041/4092] Add test coverage for metadata lookup process --- .../BeatmapUpdaterMetadataLookupTest.cs | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs diff --git a/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs b/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs new file mode 100644 index 0000000000..f1eab065c0 --- /dev/null +++ b/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs @@ -0,0 +1,125 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Moq; +using NUnit.Framework; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Beatmaps; + +namespace osu.Game.Tests.Beatmaps +{ + [TestFixture] + public class BeatmapUpdaterMetadataLookupTest + { + private Mock apiMetadataSourceMock = null!; + private Mock localCachedMetadataSourceMock = null!; + + private BeatmapUpdaterMetadataLookup metadataLookup = null!; + + [SetUp] + public void SetUp() + { + apiMetadataSourceMock = new Mock(); + localCachedMetadataSourceMock = new Mock(); + + metadataLookup = new BeatmapUpdaterMetadataLookup(apiMetadataSourceMock.Object, localCachedMetadataSourceMock.Object); + } + + [Test] + public void TestLocalCacheQueriedFirst() + { + localCachedMetadataSourceMock.Setup(src => src.Lookup(It.IsAny())) + .Returns(new OnlineBeatmapMetadata { BeatmapID = 123456, BeatmapStatus = BeatmapOnlineStatus.Ranked }); + apiMetadataSourceMock.Setup(src => src.Available).Returns(true); + + var beatmap = new BeatmapInfo { OnlineID = 123456 }; + var beatmapSet = new BeatmapSetInfo(beatmap.Yield()); + beatmap.BeatmapSet = beatmapSet; + + metadataLookup.Update(beatmapSet, preferOnlineFetch: false); + + Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.Ranked)); + localCachedMetadataSourceMock.Verify(src => src.Lookup(beatmap), Times.Once); + apiMetadataSourceMock.Verify(src => src.Lookup(It.IsAny()), Times.Never); + } + + [Test] + public void TestAPIQueriedSecond() + { + localCachedMetadataSourceMock.Setup(src => src.Lookup(It.IsAny())) + .Returns((OnlineBeatmapMetadata?)null); + apiMetadataSourceMock.Setup(src => src.Available).Returns(true); + apiMetadataSourceMock.Setup(src => src.Lookup(It.IsAny())) + .Returns(new OnlineBeatmapMetadata { BeatmapID = 123456, BeatmapStatus = BeatmapOnlineStatus.Ranked }); + + var beatmap = new BeatmapInfo { OnlineID = 123456 }; + var beatmapSet = new BeatmapSetInfo(beatmap.Yield()); + beatmap.BeatmapSet = beatmapSet; + + metadataLookup.Update(beatmapSet, preferOnlineFetch: false); + + Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.Ranked)); + localCachedMetadataSourceMock.Verify(src => src.Lookup(beatmap), Times.Once); + apiMetadataSourceMock.Verify(src => src.Lookup(beatmap), Times.Once); + } + + [Test] + public void TestPreferOnlineFetch() + { + localCachedMetadataSourceMock.Setup(src => src.Lookup(It.IsAny())) + .Returns(new OnlineBeatmapMetadata { BeatmapID = 123456, BeatmapStatus = BeatmapOnlineStatus.Ranked }); + apiMetadataSourceMock.Setup(src => src.Available).Returns(true); + apiMetadataSourceMock.Setup(src => src.Lookup(It.IsAny())) + .Returns(new OnlineBeatmapMetadata { BeatmapID = 123456, BeatmapStatus = BeatmapOnlineStatus.Graveyard }); + + var beatmap = new BeatmapInfo { OnlineID = 123456 }; + var beatmapSet = new BeatmapSetInfo(beatmap.Yield()); + beatmap.BeatmapSet = beatmapSet; + + metadataLookup.Update(beatmapSet, preferOnlineFetch: true); + + Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.Graveyard)); + localCachedMetadataSourceMock.Verify(src => src.Lookup(beatmap), Times.Never); + apiMetadataSourceMock.Verify(src => src.Lookup(beatmap), Times.Once); + } + + [Test] + public void TestPreferOnlineFetchFallsBackToLocalCacheIfOnlineSourceUnavailable() + { + localCachedMetadataSourceMock.Setup(src => src.Lookup(It.IsAny())) + .Returns(new OnlineBeatmapMetadata { BeatmapID = 123456, BeatmapStatus = BeatmapOnlineStatus.Ranked }); + apiMetadataSourceMock.Setup(src => src.Available).Returns(false); + + var beatmap = new BeatmapInfo { OnlineID = 123456 }; + var beatmapSet = new BeatmapSetInfo(beatmap.Yield()); + beatmap.BeatmapSet = beatmapSet; + + metadataLookup.Update(beatmapSet, preferOnlineFetch: true); + + Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.Ranked)); + localCachedMetadataSourceMock.Verify(src => src.Lookup(beatmap), Times.Once); + apiMetadataSourceMock.Verify(src => src.Lookup(beatmap), Times.Never); + } + + [Test] + public void TestMetadataLookupFailed() + { + localCachedMetadataSourceMock.Setup(src => src.Lookup(It.IsAny())) + .Returns((OnlineBeatmapMetadata?)null); + apiMetadataSourceMock.Setup(src => src.Available).Returns(true); + apiMetadataSourceMock.Setup(src => src.Lookup(It.IsAny())) + .Returns((OnlineBeatmapMetadata?)null); + + var beatmap = new BeatmapInfo { OnlineID = 123456 }; + var beatmapSet = new BeatmapSetInfo(beatmap.Yield()); + beatmap.BeatmapSet = beatmapSet; + + metadataLookup.Update(beatmapSet, preferOnlineFetch: false); + + Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.None)); + Assert.That(beatmap.OnlineID, Is.EqualTo(-1)); + localCachedMetadataSourceMock.Verify(src => src.Lookup(beatmap), Times.Once); + apiMetadataSourceMock.Verify(src => src.Lookup(beatmap), Times.Once); + } + } +} From af579be0e4d92776f0b6392c878a65e310331e52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 7 May 2023 15:20:38 +0200 Subject: [PATCH 0042/4092] Add test coverage for edge cases --- .../BeatmapUpdaterMetadataLookupTest.cs | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs b/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs index f1eab065c0..64f44c9922 100644 --- a/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs +++ b/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs @@ -28,6 +28,7 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestLocalCacheQueriedFirst() { + localCachedMetadataSourceMock.Setup(src => src.Available).Returns(true); localCachedMetadataSourceMock.Setup(src => src.Lookup(It.IsAny())) .Returns(new OnlineBeatmapMetadata { BeatmapID = 123456, BeatmapStatus = BeatmapOnlineStatus.Ranked }); apiMetadataSourceMock.Setup(src => src.Available).Returns(true); @@ -46,6 +47,7 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestAPIQueriedSecond() { + localCachedMetadataSourceMock.Setup(src => src.Available).Returns(true); localCachedMetadataSourceMock.Setup(src => src.Lookup(It.IsAny())) .Returns((OnlineBeatmapMetadata?)null); apiMetadataSourceMock.Setup(src => src.Available).Returns(true); @@ -66,6 +68,7 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestPreferOnlineFetch() { + localCachedMetadataSourceMock.Setup(src => src.Available).Returns(true); localCachedMetadataSourceMock.Setup(src => src.Lookup(It.IsAny())) .Returns(new OnlineBeatmapMetadata { BeatmapID = 123456, BeatmapStatus = BeatmapOnlineStatus.Ranked }); apiMetadataSourceMock.Setup(src => src.Available).Returns(true); @@ -86,6 +89,7 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestPreferOnlineFetchFallsBackToLocalCacheIfOnlineSourceUnavailable() { + localCachedMetadataSourceMock.Setup(src => src.Available).Returns(true); localCachedMetadataSourceMock.Setup(src => src.Lookup(It.IsAny())) .Returns(new OnlineBeatmapMetadata { BeatmapID = 123456, BeatmapStatus = BeatmapOnlineStatus.Ranked }); apiMetadataSourceMock.Setup(src => src.Available).Returns(false); @@ -104,6 +108,7 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestMetadataLookupFailed() { + localCachedMetadataSourceMock.Setup(src => src.Available).Returns(true); localCachedMetadataSourceMock.Setup(src => src.Lookup(It.IsAny())) .Returns((OnlineBeatmapMetadata?)null); apiMetadataSourceMock.Setup(src => src.Available).Returns(true); @@ -121,5 +126,52 @@ namespace osu.Game.Tests.Beatmaps localCachedMetadataSourceMock.Verify(src => src.Lookup(beatmap), Times.Once); apiMetadataSourceMock.Verify(src => src.Lookup(beatmap), Times.Once); } + + /// + /// For the time being, if we fail to find a match in the local cache but online retrieval is not available, we trust the incoming beatmap verbatim wrt online ID. + /// While this is suboptimal as it implicitly trusts the contents of the beatmap, + /// throwing away the online data would be anti-user as it would make all beatmaps imported offline stop working in online. + /// TODO: revisit if/when we have a better flow of queueing metadata retrieval. + /// + [Test] + public void TestLocalMetadataLookupFailedAndOnlineLookupIsUnavailable([Values] bool preferOnlineFetch) + { + localCachedMetadataSourceMock.Setup(src => src.Available).Returns(true); + localCachedMetadataSourceMock.Setup(src => src.Lookup(It.IsAny())) + .Returns((OnlineBeatmapMetadata?)null); + apiMetadataSourceMock.Setup(src => src.Available).Returns(false); + + var beatmap = new BeatmapInfo { OnlineID = 123456 }; + var beatmapSet = new BeatmapSetInfo(beatmap.Yield()); + beatmap.BeatmapSet = beatmapSet; + + metadataLookup.Update(beatmapSet, preferOnlineFetch); + + Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.None)); + Assert.That(beatmap.OnlineID, Is.EqualTo(123456)); + } + + /// + /// For the time being, if there are no available metadata lookup sources, we trust the incoming beatmap verbatim wrt online ID. + /// While this is suboptimal as it implicitly trusts the contents of the beatmap, + /// throwing away the online data would be anti-user as it would make all beatmaps imported offline stop working in online. + /// TODO: revisit if/when we have a better flow of queueing metadata retrieval. + /// + [Test] + public void TestNoAvailableSources() + { + localCachedMetadataSourceMock.Setup(src => src.Available).Returns(false); + apiMetadataSourceMock.Setup(src => src.Available).Returns(false); + + var beatmap = new BeatmapInfo { OnlineID = 123456 }; + var beatmapSet = new BeatmapSetInfo(beatmap.Yield()); + beatmap.BeatmapSet = beatmapSet; + + metadataLookup.Update(beatmapSet, preferOnlineFetch: false); + + Assert.That(beatmap.OnlineID, Is.EqualTo(123456)); + localCachedMetadataSourceMock.Verify(src => src.Lookup(beatmap), Times.Never); + apiMetadataSourceMock.Verify(src => src.Lookup(beatmap), Times.Never); + } } } From b895d4a42f8c36ef75efba39f72f6bdc409fe634 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 7 May 2023 15:37:50 +0200 Subject: [PATCH 0043/4092] Adjust logic to preserve existing desired behaviour --- .../Beatmaps/BeatmapUpdaterMetadataLookup.cs | 43 ++++++++++++++----- .../Beatmaps/IOnlineBeatmapMetadataSource.cs | 2 +- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs index 98b921e3c7..3e06907c32 100644 --- a/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs +++ b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs @@ -40,7 +40,8 @@ namespace osu.Game.Beatmaps { foreach (var beatmapInfo in beatmapSet.Beatmaps) { - var res = lookup(beatmapInfo, preferOnlineFetch); + if (!tryLookup(beatmapInfo, preferOnlineFetch, out var res)) + continue; if (res == null) { @@ -71,22 +72,42 @@ namespace osu.Game.Beatmaps } } - private OnlineBeatmapMetadata? lookup(BeatmapInfo beatmapInfo, bool preferOnlineFetch) + /// + /// Attempts to retrieve the for the given . + /// + /// The beatmap to perform the online lookup for. + /// Whether online sources should be preferred for the lookup. + /// The result of the lookup. Can be if no matching beatmap was found (or the lookup failed). + /// + /// if any of the metadata sources were available and returned a valid . + /// if none of the metadata sources were available, or if there was insufficient data to return a valid . + /// + /// + /// There are two cases wherein this method will return : + /// + /// If neither the local cache or the API are available to query. + /// If the API is not available to query, and a positive match was not made in the local cache. + /// + /// In either case, the online ID read from the .osu file will be preserved, which may not necessarily be what we want. + /// TODO: reconsider this if/when a better flow for queueing online retrieval is implemented. + /// + private bool tryLookup(BeatmapInfo beatmapInfo, bool preferOnlineFetch, out OnlineBeatmapMetadata? result) { - OnlineBeatmapMetadata? result = null; - - bool useLocalCache = !apiMetadataSource.Available || !preferOnlineFetch; - - if (useLocalCache) + if (localCachedMetadataSource.Available && (!apiMetadataSource.Available || !preferOnlineFetch)) + { result = localCachedMetadataSource.Lookup(beatmapInfo); - - if (result != null) - return result; + if (result != null) + return true; + } if (apiMetadataSource.Available) + { result = apiMetadataSource.Lookup(beatmapInfo); + return true; + } - return result; + result = null; + return false; } /// diff --git a/osu.Game/Beatmaps/IOnlineBeatmapMetadataSource.cs b/osu.Game/Beatmaps/IOnlineBeatmapMetadataSource.cs index 753462d493..a068e92f95 100644 --- a/osu.Game/Beatmaps/IOnlineBeatmapMetadataSource.cs +++ b/osu.Game/Beatmaps/IOnlineBeatmapMetadataSource.cs @@ -19,7 +19,7 @@ namespace osu.Game.Beatmaps /// Looks up the online metadata for the supplied . /// /// - /// An instance if the lookup is successful, or if the lookup failed. + /// An instance if the lookup is successful, or if the lookup did not return a matching beatmap. /// OnlineBeatmapMetadata? Lookup(BeatmapInfo beatmapInfo); } From 29ce27098d1dda1e9a5c62d1676a41bdb42b78a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 7 May 2023 17:49:31 +0200 Subject: [PATCH 0044/4092] Refactor again to fix test failures --- .../BeatmapUpdaterMetadataLookupTest.cs | 86 ++++++++++++------- osu.Game/Beatmaps/APIBeatmapMetadataSource.cs | 18 ++-- .../Beatmaps/BeatmapUpdaterMetadataLookup.cs | 16 ++-- .../Beatmaps/IOnlineBeatmapMetadataSource.cs | 10 ++- .../LocalCachedBeatmapMetadataSource.cs | 20 +++-- 5 files changed, 94 insertions(+), 56 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs b/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs index 64f44c9922..84195f1e7c 100644 --- a/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs +++ b/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs @@ -28,9 +28,11 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestLocalCacheQueriedFirst() { + var localLookupResult = new OnlineBeatmapMetadata { BeatmapID = 123456, BeatmapStatus = BeatmapOnlineStatus.Ranked }; localCachedMetadataSourceMock.Setup(src => src.Available).Returns(true); - localCachedMetadataSourceMock.Setup(src => src.Lookup(It.IsAny())) - .Returns(new OnlineBeatmapMetadata { BeatmapID = 123456, BeatmapStatus = BeatmapOnlineStatus.Ranked }); + localCachedMetadataSourceMock.Setup(src => src.TryLookup(It.IsAny(), out localLookupResult)) + .Returns(true); + apiMetadataSourceMock.Setup(src => src.Available).Returns(true); var beatmap = new BeatmapInfo { OnlineID = 123456 }; @@ -40,19 +42,22 @@ namespace osu.Game.Tests.Beatmaps metadataLookup.Update(beatmapSet, preferOnlineFetch: false); Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.Ranked)); - localCachedMetadataSourceMock.Verify(src => src.Lookup(beatmap), Times.Once); - apiMetadataSourceMock.Verify(src => src.Lookup(It.IsAny()), Times.Never); + localCachedMetadataSourceMock.Verify(src => src.TryLookup(beatmap, out It.Ref.IsAny!), Times.Once); + apiMetadataSourceMock.Verify(src => src.TryLookup(It.IsAny(), out It.Ref.IsAny!), Times.Never); } [Test] public void TestAPIQueriedSecond() { + OnlineBeatmapMetadata? localLookupResult = null; localCachedMetadataSourceMock.Setup(src => src.Available).Returns(true); - localCachedMetadataSourceMock.Setup(src => src.Lookup(It.IsAny())) - .Returns((OnlineBeatmapMetadata?)null); + localCachedMetadataSourceMock.Setup(src => src.TryLookup(It.IsAny(), out localLookupResult)) + .Returns(false); + + var onlineLookupResult = new OnlineBeatmapMetadata { BeatmapID = 123456, BeatmapStatus = BeatmapOnlineStatus.Ranked }; apiMetadataSourceMock.Setup(src => src.Available).Returns(true); - apiMetadataSourceMock.Setup(src => src.Lookup(It.IsAny())) - .Returns(new OnlineBeatmapMetadata { BeatmapID = 123456, BeatmapStatus = BeatmapOnlineStatus.Ranked }); + apiMetadataSourceMock.Setup(src => src.TryLookup(It.IsAny(), out onlineLookupResult)) + .Returns(true); var beatmap = new BeatmapInfo { OnlineID = 123456 }; var beatmapSet = new BeatmapSetInfo(beatmap.Yield()); @@ -61,19 +66,22 @@ namespace osu.Game.Tests.Beatmaps metadataLookup.Update(beatmapSet, preferOnlineFetch: false); Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.Ranked)); - localCachedMetadataSourceMock.Verify(src => src.Lookup(beatmap), Times.Once); - apiMetadataSourceMock.Verify(src => src.Lookup(beatmap), Times.Once); + localCachedMetadataSourceMock.Verify(src => src.TryLookup(beatmap, out It.Ref.IsAny!), Times.Once); + apiMetadataSourceMock.Verify(src => src.TryLookup(beatmap, out It.Ref.IsAny!), Times.Once); } [Test] public void TestPreferOnlineFetch() { + var localLookupResult = new OnlineBeatmapMetadata { BeatmapID = 123456, BeatmapStatus = BeatmapOnlineStatus.Ranked }; localCachedMetadataSourceMock.Setup(src => src.Available).Returns(true); - localCachedMetadataSourceMock.Setup(src => src.Lookup(It.IsAny())) - .Returns(new OnlineBeatmapMetadata { BeatmapID = 123456, BeatmapStatus = BeatmapOnlineStatus.Ranked }); + localCachedMetadataSourceMock.Setup(src => src.TryLookup(It.IsAny(), out localLookupResult)) + .Returns(true); + + var onlineLookupResult = new OnlineBeatmapMetadata { BeatmapID = 123456, BeatmapStatus = BeatmapOnlineStatus.Graveyard }; apiMetadataSourceMock.Setup(src => src.Available).Returns(true); - apiMetadataSourceMock.Setup(src => src.Lookup(It.IsAny())) - .Returns(new OnlineBeatmapMetadata { BeatmapID = 123456, BeatmapStatus = BeatmapOnlineStatus.Graveyard }); + apiMetadataSourceMock.Setup(src => src.TryLookup(It.IsAny(), out onlineLookupResult)) + .Returns(true); var beatmap = new BeatmapInfo { OnlineID = 123456 }; var beatmapSet = new BeatmapSetInfo(beatmap.Yield()); @@ -82,16 +90,18 @@ namespace osu.Game.Tests.Beatmaps metadataLookup.Update(beatmapSet, preferOnlineFetch: true); Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.Graveyard)); - localCachedMetadataSourceMock.Verify(src => src.Lookup(beatmap), Times.Never); - apiMetadataSourceMock.Verify(src => src.Lookup(beatmap), Times.Once); + localCachedMetadataSourceMock.Verify(src => src.TryLookup(beatmap, out It.Ref.IsAny!), Times.Never); + apiMetadataSourceMock.Verify(src => src.TryLookup(beatmap, out It.Ref.IsAny!), Times.Once); } [Test] public void TestPreferOnlineFetchFallsBackToLocalCacheIfOnlineSourceUnavailable() { + var localLookupResult = new OnlineBeatmapMetadata { BeatmapID = 123456, BeatmapStatus = BeatmapOnlineStatus.Ranked }; localCachedMetadataSourceMock.Setup(src => src.Available).Returns(true); - localCachedMetadataSourceMock.Setup(src => src.Lookup(It.IsAny())) - .Returns(new OnlineBeatmapMetadata { BeatmapID = 123456, BeatmapStatus = BeatmapOnlineStatus.Ranked }); + localCachedMetadataSourceMock.Setup(src => src.TryLookup(It.IsAny(), out localLookupResult)) + .Returns(true); + apiMetadataSourceMock.Setup(src => src.Available).Returns(false); var beatmap = new BeatmapInfo { OnlineID = 123456 }; @@ -101,19 +111,22 @@ namespace osu.Game.Tests.Beatmaps metadataLookup.Update(beatmapSet, preferOnlineFetch: true); Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.Ranked)); - localCachedMetadataSourceMock.Verify(src => src.Lookup(beatmap), Times.Once); - apiMetadataSourceMock.Verify(src => src.Lookup(beatmap), Times.Never); + localCachedMetadataSourceMock.Verify(src => src.TryLookup(beatmap, out It.Ref.IsAny!), Times.Once); + apiMetadataSourceMock.Verify(src => src.TryLookup(beatmap, out It.Ref.IsAny!), Times.Never); } [Test] public void TestMetadataLookupFailed() { + OnlineBeatmapMetadata? lookupResult = null; + localCachedMetadataSourceMock.Setup(src => src.Available).Returns(true); - localCachedMetadataSourceMock.Setup(src => src.Lookup(It.IsAny())) - .Returns((OnlineBeatmapMetadata?)null); + localCachedMetadataSourceMock.Setup(src => src.TryLookup(It.IsAny(), out lookupResult)) + .Returns(false); + apiMetadataSourceMock.Setup(src => src.Available).Returns(true); - apiMetadataSourceMock.Setup(src => src.Lookup(It.IsAny())) - .Returns((OnlineBeatmapMetadata?)null); + apiMetadataSourceMock.Setup(src => src.TryLookup(It.IsAny(), out lookupResult)) + .Returns(true); var beatmap = new BeatmapInfo { OnlineID = 123456 }; var beatmapSet = new BeatmapSetInfo(beatmap.Yield()); @@ -123,8 +136,8 @@ namespace osu.Game.Tests.Beatmaps Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.None)); Assert.That(beatmap.OnlineID, Is.EqualTo(-1)); - localCachedMetadataSourceMock.Verify(src => src.Lookup(beatmap), Times.Once); - apiMetadataSourceMock.Verify(src => src.Lookup(beatmap), Times.Once); + localCachedMetadataSourceMock.Verify(src => src.TryLookup(beatmap, out It.Ref.IsAny!), Times.Once); + apiMetadataSourceMock.Verify(src => src.TryLookup(beatmap, out It.Ref.IsAny!), Times.Once); } /// @@ -134,11 +147,13 @@ namespace osu.Game.Tests.Beatmaps /// TODO: revisit if/when we have a better flow of queueing metadata retrieval. /// [Test] - public void TestLocalMetadataLookupFailedAndOnlineLookupIsUnavailable([Values] bool preferOnlineFetch) + public void TestLocalMetadataLookupReturnedNoMatchAndOnlineLookupIsUnavailable([Values] bool preferOnlineFetch) { + OnlineBeatmapMetadata? localLookupResult = null; localCachedMetadataSourceMock.Setup(src => src.Available).Returns(true); - localCachedMetadataSourceMock.Setup(src => src.Lookup(It.IsAny())) - .Returns((OnlineBeatmapMetadata?)null); + localCachedMetadataSourceMock.Setup(src => src.TryLookup(It.IsAny(), out localLookupResult)) + .Returns(false); + apiMetadataSourceMock.Setup(src => src.Available).Returns(false); var beatmap = new BeatmapInfo { OnlineID = 123456 }; @@ -158,20 +173,25 @@ namespace osu.Game.Tests.Beatmaps /// TODO: revisit if/when we have a better flow of queueing metadata retrieval. /// [Test] - public void TestNoAvailableSources() + public void TestNoAvailableSources([Values] bool preferOnlineFetch) { + OnlineBeatmapMetadata? lookupResult = null; + localCachedMetadataSourceMock.Setup(src => src.Available).Returns(false); + localCachedMetadataSourceMock.Setup(src => src.TryLookup(It.IsAny(), out lookupResult)) + .Returns(false); + apiMetadataSourceMock.Setup(src => src.Available).Returns(false); + apiMetadataSourceMock.Setup(src => src.TryLookup(It.IsAny(), out lookupResult)) + .Returns(false); var beatmap = new BeatmapInfo { OnlineID = 123456 }; var beatmapSet = new BeatmapSetInfo(beatmap.Yield()); beatmap.BeatmapSet = beatmapSet; - metadataLookup.Update(beatmapSet, preferOnlineFetch: false); + metadataLookup.Update(beatmapSet, preferOnlineFetch); Assert.That(beatmap.OnlineID, Is.EqualTo(123456)); - localCachedMetadataSourceMock.Verify(src => src.Lookup(beatmap), Times.Never); - apiMetadataSourceMock.Verify(src => src.Lookup(beatmap), Times.Never); } } } diff --git a/osu.Game/Beatmaps/APIBeatmapMetadataSource.cs b/osu.Game/Beatmaps/APIBeatmapMetadataSource.cs index e1b01aaac5..9f76aaf02c 100644 --- a/osu.Game/Beatmaps/APIBeatmapMetadataSource.cs +++ b/osu.Game/Beatmaps/APIBeatmapMetadataSource.cs @@ -20,10 +20,13 @@ namespace osu.Game.Beatmaps public bool Available => api.State.Value == APIState.Online; - public OnlineBeatmapMetadata? Lookup(BeatmapInfo beatmapInfo) + public bool TryLookup(BeatmapInfo beatmapInfo, out OnlineBeatmapMetadata? onlineMetadata) { if (!Available) - return null; + { + onlineMetadata = null; + return false; + } Debug.Assert(beatmapInfo.BeatmapSet != null); @@ -37,7 +40,8 @@ namespace osu.Game.Beatmaps if (req.CompletionState == APIRequestCompletionState.Failed) { logForModel(beatmapInfo.BeatmapSet, $@"Online retrieval failed for {beatmapInfo}"); - return null; + onlineMetadata = null; + return true; } var res = req.Response; @@ -46,7 +50,7 @@ namespace osu.Game.Beatmaps { logForModel(beatmapInfo.BeatmapSet, $@"Online retrieval mapped {beatmapInfo} to {res.OnlineBeatmapSetID} / {res.OnlineID}."); - return new OnlineBeatmapMetadata + onlineMetadata = new OnlineBeatmapMetadata { BeatmapID = res.OnlineID, BeatmapSetID = res.OnlineBeatmapSetID, @@ -58,14 +62,18 @@ namespace osu.Game.Beatmaps MD5Hash = res.MD5Hash, LastUpdated = res.LastUpdated }; + return true; } } catch (Exception e) { logForModel(beatmapInfo.BeatmapSet, $@"Online retrieval failed for {beatmapInfo} ({e.Message})"); + onlineMetadata = null; + return false; } - return null; + onlineMetadata = null; + return false; } private void logForModel(BeatmapSetInfo set, string message) => diff --git a/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs index 3e06907c32..b32310990c 100644 --- a/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs +++ b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs @@ -93,18 +93,12 @@ namespace osu.Game.Beatmaps /// private bool tryLookup(BeatmapInfo beatmapInfo, bool preferOnlineFetch, out OnlineBeatmapMetadata? result) { - if (localCachedMetadataSource.Available && (!apiMetadataSource.Available || !preferOnlineFetch)) - { - result = localCachedMetadataSource.Lookup(beatmapInfo); - if (result != null) - return true; - } - - if (apiMetadataSource.Available) - { - result = apiMetadataSource.Lookup(beatmapInfo); + bool useLocalCache = !apiMetadataSource.Available || !preferOnlineFetch; + if (useLocalCache && localCachedMetadataSource.TryLookup(beatmapInfo, out result)) + return true; + + if (apiMetadataSource.TryLookup(beatmapInfo, out result)) return true; - } result = null; return false; diff --git a/osu.Game/Beatmaps/IOnlineBeatmapMetadataSource.cs b/osu.Game/Beatmaps/IOnlineBeatmapMetadataSource.cs index a068e92f95..8230ef40ac 100644 --- a/osu.Game/Beatmaps/IOnlineBeatmapMetadataSource.cs +++ b/osu.Game/Beatmaps/IOnlineBeatmapMetadataSource.cs @@ -18,9 +18,15 @@ namespace osu.Game.Beatmaps /// /// Looks up the online metadata for the supplied . /// + /// The to look up. + /// + /// An instance if the lookup is successful. + /// if a mismatch between the local instance and the looked-up data was detected. + /// The returned value is only valid if the return value of the method is . + /// /// - /// An instance if the lookup is successful, or if the lookup did not return a matching beatmap. + /// Whether the lookup was performed. /// - OnlineBeatmapMetadata? Lookup(BeatmapInfo beatmapInfo); + bool TryLookup(BeatmapInfo beatmapInfo, out OnlineBeatmapMetadata? onlineMetadata); } } diff --git a/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs b/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs index 435242aeab..7a179b281f 100644 --- a/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs +++ b/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs @@ -108,15 +108,21 @@ namespace osu.Game.Beatmaps // cached database exists on disk. && storage.Exists(cache_database_name); - public OnlineBeatmapMetadata? Lookup(BeatmapInfo beatmapInfo) + public bool TryLookup(BeatmapInfo beatmapInfo, out OnlineBeatmapMetadata? onlineMetadata) { if (!Available) - return null; + { + onlineMetadata = null; + return false; + } if (string.IsNullOrEmpty(beatmapInfo.MD5Hash) && string.IsNullOrEmpty(beatmapInfo.Path) && beatmapInfo.OnlineID <= 0) - return null; + { + onlineMetadata = null; + return false; + } Debug.Assert(beatmapInfo.BeatmapSet != null); @@ -141,7 +147,7 @@ namespace osu.Game.Beatmaps { logForModel(beatmapInfo.BeatmapSet, $@"Cached local retrieval for {beatmapInfo}."); - return new OnlineBeatmapMetadata + onlineMetadata = new OnlineBeatmapMetadata { BeatmapSetID = reader.GetInt32(0), BeatmapID = reader.GetInt32(1), @@ -152,6 +158,7 @@ namespace osu.Game.Beatmaps LastUpdated = reader.GetDateTimeOffset(5), // TODO: DateSubmitted and DateRanked are not provided by local cache. }; + return true; } } } @@ -160,9 +167,12 @@ namespace osu.Game.Beatmaps catch (Exception ex) { logForModel(beatmapInfo.BeatmapSet, $@"Cached local retrieval for {beatmapInfo} failed with {ex}."); + onlineMetadata = null; + return false; } - return null; + onlineMetadata = null; + return false; } private void logForModel(BeatmapSetInfo set, string message) => From c930ec97d6681fbe34afe37ba01a7dd08127c01a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 7 May 2023 19:18:59 +0200 Subject: [PATCH 0045/4092] Polish xmldocs --- osu.Game/Beatmaps/APIBeatmapMetadataSource.cs | 3 +++ osu.Game/Beatmaps/IOnlineBeatmapMetadataSource.cs | 2 +- osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs | 6 ++---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/APIBeatmapMetadataSource.cs b/osu.Game/Beatmaps/APIBeatmapMetadataSource.cs index 9f76aaf02c..a2eebe6161 100644 --- a/osu.Game/Beatmaps/APIBeatmapMetadataSource.cs +++ b/osu.Game/Beatmaps/APIBeatmapMetadataSource.cs @@ -9,6 +9,9 @@ using osu.Game.Online.API.Requests; namespace osu.Game.Beatmaps { + /// + /// Performs online metadata lookups using the osu-web API. + /// public class APIBeatmapMetadataSource : IOnlineBeatmapMetadataSource { private readonly IAPIProvider api; diff --git a/osu.Game/Beatmaps/IOnlineBeatmapMetadataSource.cs b/osu.Game/Beatmaps/IOnlineBeatmapMetadataSource.cs index 8230ef40ac..5bf5381f2a 100644 --- a/osu.Game/Beatmaps/IOnlineBeatmapMetadataSource.cs +++ b/osu.Game/Beatmaps/IOnlineBeatmapMetadataSource.cs @@ -6,7 +6,7 @@ using System; namespace osu.Game.Beatmaps { /// - /// Unifying interface for sources of online beatmap metadata. + /// Unifying interface for sources of . /// public interface IOnlineBeatmapMetadataSource : IDisposable { diff --git a/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs b/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs index 7a179b281f..ff88fecd86 100644 --- a/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs +++ b/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs @@ -18,11 +18,9 @@ using SQLitePCL; namespace osu.Game.Beatmaps { /// - /// + /// Performs online metadata lookups using a copy of a database containing metadata for a large subset of beatmaps (stored to ). + /// The database will be asynchronously downloaded - if not already present locally - when this component is constructed. /// - /// - /// On creating the component, a copy of a database containing metadata for a large subset of beatmaps (stored to ) will be downloaded if not already present locally. - /// public class LocalCachedBeatmapMetadataSource : IOnlineBeatmapMetadataSource { private readonly Storage storage; From c1743dbe1df7994f425d118c19c44ee8ea8c126c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Jun 2023 16:53:29 +0900 Subject: [PATCH 0046/4092] Select all text when popover is initially shown Depends on https://github.com/ppy/osu-framework/pull/5823. --- osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 5a1fbbee1e..62925ff708 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -328,7 +328,7 @@ namespace osu.Game.Screens.Edit.Compose.Components BeatDivisor.BindValueChanged(_ => updateState(), true); divisorTextBox.OnCommit += (_, _) => setPresets(); - Schedule(() => GetContainingInputManager().ChangeFocus(divisorTextBox)); + divisorTextBox.SelectAll(); } private void setPresets() From 5d5e6a5ab750f380393a9c6ffd9cd3028e486f1f Mon Sep 17 00:00:00 2001 From: Hydria Date: Mon, 3 Jul 2023 17:45:30 +0100 Subject: [PATCH 0047/4092] Finalised LN Adjustment Values Spent a couple days discussing this on the pp rework server about values that were the most acceptable, these seemed to be the best from the community standpoint of top players. Note: This is more to fix issues with the current system, not to be a final solution. Related Google Sheets Page: https://docs.google.com/spreadsheets/d/1P0AxfdKvMHwWBQder4ZkFGO1fC9eADSGCryA5-UGriU/edit?usp=sharing --- osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs index 06c825e37d..0a4fec3a70 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills { private const double individual_decay_base = 0.125; private const double overall_decay_base = 0.30; - private const double release_threshold = 24; + private const double release_threshold = 30; protected override double SkillMultiplier => 1; protected override double StrainDecayBase => 1; @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills // 0.0 +--------+-+---------------> Release Difference / ms // release_threshold if (isOverlapping) - holdAddition = 1 / (1 + Math.Exp(0.5 * (release_threshold - closestEndTime))); + holdAddition = 1 / (1 + Math.Exp(0.27 * (release_threshold - closestEndTime))); // Decay and increase individualStrains in own column individualStrains[column] = applyDecay(individualStrains[column], startTime - startTimes[column], individual_decay_base); From 00250972c34caba4c5f1ab8dbdd29fa0ba80c697 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Thu, 6 Jul 2023 02:31:12 -0400 Subject: [PATCH 0048/4092] skip frames after a negative frame until the negative time is "paid back" --- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index c6461840aa..f91c96efb6 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -269,6 +269,13 @@ namespace osu.Game.Scoring.Legacy float lastTime = beatmapOffset; ReplayFrame currentFrame = null; + // the negative time amount that must be "paid back" by positive frames before we start including frames again. + // When a negative frame occurs in a replay, all future frames are skipped until the sum total of their times + // is equal to or greater than the time of that negative frame. + // This value will be negative if we are in a time deficit, ie we have a negative frame that must be paid back. + // Otherwise it will be 0. + float timeDeficit = 0; + string[] frames = reader.ReadToEnd().Split(','); for (int i = 0; i < frames.Length; i++) @@ -296,9 +303,13 @@ namespace osu.Game.Scoring.Legacy // ignore these frames as they serve no real purpose (and can even mislead ruleset-specific handlers - see mania) continue; + timeDeficit += diff; + timeDeficit = Math.Min(0, timeDeficit); + + // still paying back the deficit from a negative frame. Skip this frame. // Todo: At some point we probably want to rewind and play back the negative-time frames // but for now we'll achieve equal playback to stable by skipping negative frames - if (diff < 0) + if (timeDeficit < 0) continue; currentFrame = convertFrame(new LegacyReplayFrame(lastTime, From cc6646c82b13e021514a0465b118383d8e96ba7f Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Thu, 6 Jul 2023 17:13:33 -0400 Subject: [PATCH 0049/4092] properly handle negative frame before a break this was causing replay data before the skip to be...skipped. --- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index f91c96efb6..63465652e8 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -267,6 +267,7 @@ namespace osu.Game.Scoring.Legacy private void readLegacyReplay(Replay replay, StreamReader reader) { float lastTime = beatmapOffset; + bool skipFramesPresent = false; ReplayFrame currentFrame = null; // the negative time amount that must be "paid back" by positive frames before we start including frames again. @@ -298,18 +299,31 @@ namespace osu.Game.Scoring.Legacy lastTime += diff; if (i < 2 && mouseX == 256 && mouseY == -500) + { // at the start of the replay, stable places two replay frames, at time 0 and SkipBoundary - 1, respectively. // both frames use a position of (256, -500). // ignore these frames as they serve no real purpose (and can even mislead ruleset-specific handlers - see mania) + skipFramesPresent = true; continue; + } - timeDeficit += diff; - timeDeficit = Math.Min(0, timeDeficit); + // if the skip frames inserted by stable are present, the third frame will have a large negative time + // roughly equal to SkipBoundary. We don't want this to count towards the deficit: doing so would cause + // the replay data before the skip to be, well, skipped. + // In other words, this frame, if present, is a different kind of negative frame. It sets the "offset" + // for the beginning of the replay. This is the only negative frame to be handled in such a way. + bool isNegativeBreakFrame = i == 2 && skipFramesPresent && diff < 0; + + if (!isNegativeBreakFrame) + { + timeDeficit += diff; + timeDeficit = Math.Min(0, timeDeficit); + } // still paying back the deficit from a negative frame. Skip this frame. // Todo: At some point we probably want to rewind and play back the negative-time frames // but for now we'll achieve equal playback to stable by skipping negative frames - if (timeDeficit < 0) + if (timeDeficit < 0 || isNegativeBreakFrame) continue; currentFrame = convertFrame(new LegacyReplayFrame(lastTime, From 3c76cb2fb0cc67763e50770670a0c890c1755cd9 Mon Sep 17 00:00:00 2001 From: Zyf Date: Sat, 8 Jul 2023 17:54:23 +0200 Subject: [PATCH 0050/4092] Scoring : Use square-root of combo --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 35a7dfe369..0760a89b90 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -293,7 +293,7 @@ namespace osu.Game.Rulesets.Scoring protected virtual double GetBonusScoreChange(JudgementResult result) => Judgement.ToNumericResult(result.Type); - protected virtual double GetComboScoreChange(JudgementResult result) => Judgement.ToNumericResult(result.Type) * (1 + result.ComboAfterJudgement / 10d); + protected virtual double GetComboScoreChange(JudgementResult result) => Judgement.ToNumericResult(result.Type) * Math.Pow(result.ComboAfterJudgement, 0.5); protected virtual void ApplyScoreChange(JudgementResult result) { From 175bd3b5782e5858a4445f0de44a412b86788e8d Mon Sep 17 00:00:00 2001 From: Zyf Date: Sat, 8 Jul 2023 19:05:56 +0200 Subject: [PATCH 0051/4092] Scoring : Move some of the accuracy weight to ComboScore (edited) --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 0760a89b90..ea1140ef71 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -317,8 +317,8 @@ namespace osu.Game.Rulesets.Scoring protected virtual double ComputeTotalScore(double comboProgress, double accuracyProgress, double bonusPortion) { - return 700000 * comboProgress + - 300000 * Math.Pow(Accuracy.Value, 10) * accuracyProgress + + return 700000 * Accuracy.Value * comboProgress + + 300000 * Math.Pow(Accuracy.Value, 8) * accuracyProgress + bonusPortion; } From ecb633f8fbd616b30fbeae5724f86659034ca2eb Mon Sep 17 00:00:00 2001 From: Zyf Date: Sat, 15 Jul 2023 14:55:43 +0200 Subject: [PATCH 0052/4092] Scoring : Remove ComputeTotalScore overide in OsuScoreProcessor --- osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index f97be0d7ff..dfdf3c58fd 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -17,12 +17,5 @@ namespace osu.Game.Rulesets.Osu.Scoring protected override HitEvent CreateHitEvent(JudgementResult result) => base.CreateHitEvent(result).With((result as OsuHitCircleJudgementResult)?.CursorPositionAtHit); - - protected override double ComputeTotalScore(double comboProgress, double accuracyProgress, double bonusPortion) - { - return 700000 * comboProgress - + 300000 * Math.Pow(Accuracy.Value, 10) * accuracyProgress - + bonusPortion; - } } } From b672b49e02b6d650f9b207ab908edfd3f4886dc3 Mon Sep 17 00:00:00 2001 From: Zyf Date: Sat, 15 Jul 2023 23:20:49 +0200 Subject: [PATCH 0053/4092] Scoring : Implement v1 to v3 conversion. --- .../Difficulty/OsuLegacyScoreSimulator.cs | 10 ++- .../StandardisedScoreMigrationTools.cs | 69 +++++++++++++++++-- .../Difficulty/DifficultyAttributes.cs | 10 +++ .../Rulesets/Scoring/ILegacyScoreSimulator.cs | 12 +++- 4 files changed, 92 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuLegacyScoreSimulator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuLegacyScoreSimulator.cs index 980d86e4ad..78245f903a 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuLegacyScoreSimulator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuLegacyScoreSimulator.cs @@ -20,9 +20,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty public int ComboScore { get; private set; } - public double BonusScoreRatio => legacyBonusScore == 0 ? 0 : (double)modernBonusScore / legacyBonusScore; + public int LegacyBonusScore { get; private set; } + + public int MaxCombo { get; private set; } + + public double BonusScoreRatio => LegacyBonusScore == 0 ? 0 : (double)modernBonusScore / LegacyBonusScore; - private int legacyBonusScore; private int modernBonusScore; private int combo; @@ -77,6 +80,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty foreach (var obj in playableBeatmap.HitObjects) simulateHit(obj); + MaxCombo = combo; } private void simulateHit(HitObject hitObject) @@ -164,7 +168,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (isBonus) { - legacyBonusScore += scoreIncrease; + LegacyBonusScore += scoreIncrease; modernBonusScore += Judgement.ToNumericResult(bonusResult); } else diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index 60530c31cb..61f0d1f195 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -218,7 +218,9 @@ namespace osu.Game.Database { LegacyAccuracyScore = sv1Simulator.AccuracyScore, LegacyComboScore = sv1Simulator.ComboScore, - LegacyBonusScoreRatio = sv1Simulator.BonusScoreRatio + LegacyBonusScoreRatio = sv1Simulator.BonusScoreRatio, + LegacyBonusScore = sv1Simulator.LegacyBonusScore, + LegacyMaxCombo = sv1Simulator.MaxCombo, }); } @@ -240,13 +242,16 @@ namespace osu.Game.Database int maximumLegacyAccuracyScore = attributes.LegacyAccuracyScore; int maximumLegacyComboScore = attributes.LegacyComboScore; double maximumLegacyBonusRatio = attributes.LegacyBonusScoreRatio; + int maximumLegacyBonusScore = attributes.LegacyBonusScore; + int maximumLegacyCombo = attributes.LegacyMaxCombo; double modMultiplier = score.Mods.Select(m => m.ScoreMultiplier).Aggregate(1.0, (c, n) => c * n); // The part of total score that doesn't include bonus. int maximumLegacyBaseScore = maximumLegacyAccuracyScore + maximumLegacyComboScore; + int maximumLegacyTotalScore = maximumLegacyBaseScore + maximumLegacyBonusScore; - // The combo proportion is calculated as a proportion of maximumLegacyBaseScore. - double comboProportion = Math.Min(1, (double)score.LegacyTotalScore / maximumLegacyBaseScore); + // The combo proportion is calculated as a proportion of maximumLegacyTotalScore. + double comboProportion = (double)score.LegacyTotalScore / maximumLegacyTotalScore; // The bonus proportion makes up the rest of the score that exceeds maximumLegacyBaseScore. double bonusProportion = Math.Max(0, ((long)score.LegacyTotalScore - maximumLegacyBaseScore) * maximumLegacyBonusRatio); @@ -254,9 +259,63 @@ namespace osu.Game.Database switch (score.Ruleset.OnlineID) { case 0: + if(score.MaxCombo == 0 || score.Accuracy == 0) + return (long)Math.Round(( + 0 + + 300000 * Math.Pow(score.Accuracy, 8) + + bonusProportion) * modMultiplier); + + double v3exp = 0.5; // Scorev3 combo exponent + + // Assumption : + // - sliders and slider-ticks are uniformly spread arround the beatmap + // thus we can ignore them without losing much precision (consider a map of hit-circles only !) + // - the Ok/Meh hit results are uniformly spread in the score + // thus we can simplify and consider each hit result to be score.Accuracy without losing much precision + // What is strippedV1/strippedV3 : + // This is the ComboScore of v1/v3 were we remove all (map-)constant multipliers and accuracy multipliers (including hit results), + // based on the previous assumptions. For Scorev1, this is basically the sum of squared combos (because without sliders: object_count == combo). + double maxStrippedV1 = Math.Pow(maximumLegacyCombo, 2); + double maxStrippedV3 = Math.Pow(maximumLegacyCombo, 1 + v3exp); + + double strippedV1 = maxStrippedV1 * comboProportion / score.Accuracy; + + double strippedV1FromMaxCombo = Math.Pow(score.MaxCombo, 2); + double strippedV3FromMaxCombo = Math.Pow(score.MaxCombo, 1 + v3exp); + + // Compute approximate lower estimate scorev3 for that play + // That is, a play were we made biggest amount of big combos (Repeat MaxCombo + 1 remaining big combo) + // And didn't combo anything in the reminder of the map + double possibleMaxComboRepeat = Math.Floor(strippedV1 / strippedV1FromMaxCombo); + double strippedV1FromMaxComboRepeat = possibleMaxComboRepeat * strippedV1FromMaxCombo; + double remainingStrippedV1 = strippedV1 - strippedV1FromMaxComboRepeat; + double remainingCombo = Math.Sqrt(remainingStrippedV1); + double remainingStrippedV3 = Math.Pow(remainingCombo, 1 + v3exp); + + double newLowerStrippedV3 = (possibleMaxComboRepeat * strippedV3FromMaxCombo) + remainingStrippedV3; + + // Compute approximate upper estimate scorev3 for that play + // That is, a play were all combos were equal (except MaxCombo) + remainingStrippedV1 = strippedV1 - strippedV1FromMaxCombo; + double remainingComboObjects = maximumLegacyCombo - score.MaxCombo - score.Statistics[HitResult.Miss]; + double remainingAverageCombo = remainingComboObjects > 0 ? remainingStrippedV1 / remainingComboObjects : 0; + remainingStrippedV3 = remainingComboObjects * Math.Pow(remainingAverageCombo, v3exp); + + double newUpperStrippedV3 = strippedV3FromMaxCombo + remainingStrippedV3; + + // Approximate by combining lower and upper estimates + // As the lower-estimate is very pessimistic, we use a 30/70 ratio + // And cap it with 1.2 times the middle-point to avoid overstimates + double strippedV3 = Math.Min( + 0.3 * newLowerStrippedV3 + 0.7 * newUpperStrippedV3, + 1.2 * (newLowerStrippedV3 + newUpperStrippedV3) / 2 + ); + + double newComboScoreProportion = (strippedV3 / maxStrippedV3) * score.Accuracy; + return (long)Math.Round(( - 700000 * comboProportion - + 300000 * Math.Pow(score.Accuracy, 10) + 700000 * newComboScoreProportion * score.Accuracy + + 300000 * Math.Pow(score.Accuracy, 8) + bonusProportion) * modMultiplier); case 1: diff --git a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs index 5a01faa417..69345872c6 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs @@ -64,6 +64,16 @@ namespace osu.Game.Rulesets.Difficulty /// public double LegacyBonusScoreRatio { get; set; } + /// + /// The bonus portion of the legacy (ScoreV1) total score. + /// + public int LegacyBonusScore { get; set; } + + /// + /// The maximum combo of the legacy (ScoreV1) total score. + /// + public int LegacyMaxCombo { get; set; } + /// /// Creates new . /// diff --git a/osu.Game/Rulesets/Scoring/ILegacyScoreSimulator.cs b/osu.Game/Rulesets/Scoring/ILegacyScoreSimulator.cs index 7240f0d73e..68ace15b20 100644 --- a/osu.Game/Rulesets/Scoring/ILegacyScoreSimulator.cs +++ b/osu.Game/Rulesets/Scoring/ILegacyScoreSimulator.cs @@ -23,9 +23,19 @@ namespace osu.Game.Rulesets.Scoring int ComboScore { get; } /// - /// A ratio of new_bonus_score / old_bonus_score for converting the bonus score of legacy scores to the new scoring. + /// The bonus portion of the legacy (ScoreV1) total score. /// This is made up of all judgements that would be or . /// + int LegacyBonusScore { get; } + + /// + /// The maximum combo of the legacy (ScoreV1) total score. + /// + int MaxCombo { get; } + + /// + /// A ratio of new_bonus_score / old_bonus_score for converting the bonus score of legacy scores to the new scoring. + /// double BonusScoreRatio { get; } /// From a97915180f37a87ac5716acdb0bfcafffe9e6452 Mon Sep 17 00:00:00 2001 From: Zyf Date: Sat, 15 Jul 2023 23:30:59 +0200 Subject: [PATCH 0054/4092] Scoring : Adds fields to Catch/Mania/Taiko Simulators too --- .../Difficulty/CatchLegacyScoreSimulator.cs | 10 +++++++--- .../Difficulty/ManiaLegacyScoreSimulator.cs | 3 +++ .../Difficulty/TaikoLegacyScoreSimulator.cs | 10 +++++++--- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyScoreSimulator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyScoreSimulator.cs index c79fd36d96..284fd23aea 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyScoreSimulator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyScoreSimulator.cs @@ -20,9 +20,12 @@ namespace osu.Game.Rulesets.Catch.Difficulty public int ComboScore { get; private set; } - public double BonusScoreRatio => legacyBonusScore == 0 ? 0 : (double)modernBonusScore / legacyBonusScore; + public int LegacyBonusScore { get; private set; } + + public int MaxCombo { get; private set; } + + public double BonusScoreRatio => LegacyBonusScore == 0 ? 0 : (double)modernBonusScore / LegacyBonusScore; - private int legacyBonusScore; private int modernBonusScore; private int combo; @@ -74,6 +77,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty foreach (var obj in playableBeatmap.HitObjects) simulateHit(obj); + MaxCombo = combo; } private void simulateHit(HitObject hitObject) @@ -129,7 +133,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty if (isBonus) { - legacyBonusScore += scoreIncrease; + LegacyBonusScore += scoreIncrease; modernBonusScore += Judgement.ToNumericResult(bonusResult); } else diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaLegacyScoreSimulator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaLegacyScoreSimulator.cs index e544428979..f09c911b98 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaLegacyScoreSimulator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaLegacyScoreSimulator.cs @@ -14,6 +14,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty { public int AccuracyScore => 0; public int ComboScore { get; private set; } + public int LegacyBonusScore => 0; + public int MaxCombo { get; private set; } public double BonusScoreRatio => 0; public void Simulate(IWorkingBeatmap workingBeatmap, IBeatmap playableBeatmap, IReadOnlyList mods) @@ -23,6 +25,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty .Aggregate(1.0, (c, n) => c * n); ComboScore = (int)(1000000 * multiplier); + MaxCombo = playableBeatmap.GetMaxCombo(); } } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs index e77327d622..239ec5765c 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs @@ -20,9 +20,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty public int ComboScore { get; private set; } - public double BonusScoreRatio => legacyBonusScore == 0 ? 0 : (double)modernBonusScore / legacyBonusScore; + public int LegacyBonusScore { get; private set; } + + public int MaxCombo { get; private set; } + + public double BonusScoreRatio => LegacyBonusScore == 0 ? 0 : (double)modernBonusScore / LegacyBonusScore; - private int legacyBonusScore; private int modernBonusScore; private int combo; @@ -80,6 +83,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty foreach (var obj in playableBeatmap.HitObjects) simulateHit(obj); + MaxCombo = combo; } private void simulateHit(HitObject hitObject) @@ -189,7 +193,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (isBonus) { - legacyBonusScore += scoreIncrease; + LegacyBonusScore += scoreIncrease; modernBonusScore += Judgement.ToNumericResult(bonusResult); } else From 768d7b5e1c30329280447dc452fde063b6e440b3 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Mon, 17 Jul 2023 23:31:21 -0400 Subject: [PATCH 0055/4092] correct implementation of stable notelock --- .../TestSceneHitCircle.cs | 2 +- .../Objects/Drawables/DrawableHitCircle.cs | 7 ++- .../Objects/Drawables/DrawableOsuHitObject.cs | 10 ++-- .../Objects/Drawables/DrawableSliderHead.cs | 3 +- osu.Game.Rulesets.Osu/UI/AnyOrderHitPolicy.cs | 2 +- osu.Game.Rulesets.Osu/UI/ClickAction.cs | 18 +++++++ osu.Game.Rulesets.Osu/UI/IHitPolicy.cs | 2 +- .../UI/ObjectOrderedHitPolicy.cs | 53 ++++++++++--------- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 2 +- .../UI/StartTimeOrderedHitPolicy.cs | 8 +-- osu.Game/Rulesets/UI/HitObjectContainer.cs | 2 +- osu.Game/Rulesets/UI/IHitObjectContainer.cs | 2 +- 12 files changed, 72 insertions(+), 39 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/UI/ClickAction.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs index 0314afc1ac..c818a361df 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs @@ -131,7 +131,7 @@ namespace osu.Game.Rulesets.Osu.Tests protected override void CheckForResult(bool userTriggered, double timeOffset) { - if (auto && !userTriggered && timeOffset > hitOffset && CheckHittable?.Invoke(this, Time.Current) != false) + if (auto && !userTriggered && timeOffset > hitOffset && CheckHittable?.Invoke(this, Time.Current) == ClickAction.Hit) { // force success ApplyResult(r => r.Type = HitResult.Great); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 3458069dd1..09d818def8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -18,6 +18,7 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Osu.Skinning.Default; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; using osuTK; @@ -154,13 +155,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } var result = ResultFor(timeOffset); + var clickAction = CheckHittable?.Invoke(this, Time.Current); - if (result == HitResult.None || CheckHittable?.Invoke(this, Time.Current) == false) + if (clickAction == ClickAction.Shake || (result == HitResult.None && clickAction != ClickAction.Ignore)) { Shake(); return; } + if (result == HitResult.None) + return; + ApplyResult(r => { var circleResult = (OsuHitCircleJudgementResult)r; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index df0ba344d8..a8ce2118c8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Scoring; +using osu.Game.Rulesets.Osu.UI; using osuTK; using osuTK.Graphics; @@ -30,10 +31,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override float SamplePlaybackPosition => CalculateDrawableRelativePosition(this); /// - /// Whether this can be hit, given a time value. - /// If non-null, judgements will be ignored (resulting in a shake) whilst the function returns false. + /// What action this should take in response to a + /// click at the given time value. + /// If non-null, judgements will be ignored for return values of + /// and , and this hit object will be shaken for return values of + /// . /// - public Func CheckHittable; + public Func CheckHittable; protected DrawableOsuHitObject(OsuHitObject hitObject) : base(hitObject) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index b8a1efabe0..a4cf69ee31 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -8,6 +8,7 @@ using System.Diagnostics; using JetBrains.Annotations; using osu.Framework.Bindables; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects.Drawables @@ -60,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables pathVersion.BindTo(DrawableSlider.PathVersion); - CheckHittable = (d, t) => DrawableSlider.CheckHittable?.Invoke(d, t) ?? true; + CheckHittable = (d, t) => DrawableSlider.CheckHittable?.Invoke(d, t) ?? ClickAction.Hit; } protected override void Update() diff --git a/osu.Game.Rulesets.Osu/UI/AnyOrderHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/AnyOrderHitPolicy.cs index afa54c2dfb..7503c43e0b 100644 --- a/osu.Game.Rulesets.Osu/UI/AnyOrderHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/AnyOrderHitPolicy.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.UI { public IHitObjectContainer HitObjectContainer { get; set; } - public bool IsHittable(DrawableHitObject hitObject, double time) => true; + public ClickAction CheckHittable(DrawableHitObject hitObject, double time) => ClickAction.Hit; public void HandleHit(DrawableHitObject hitObject) { diff --git a/osu.Game.Rulesets.Osu/UI/ClickAction.cs b/osu.Game.Rulesets.Osu/UI/ClickAction.cs new file mode 100644 index 0000000000..2b00f5acce --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/ClickAction.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.Rulesets.Osu.Objects.Drawables; + +namespace osu.Game.Rulesets.Osu.UI +{ + /// + /// An action that an recommends be taken in response to a click + /// on a . + /// + public enum ClickAction + { + Ignore, + Shake, + Hit + } +} diff --git a/osu.Game.Rulesets.Osu/UI/IHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/IHitPolicy.cs index b509796742..9820b8c188 100644 --- a/osu.Game.Rulesets.Osu/UI/IHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/IHitPolicy.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.UI /// The to check. /// The time to check. /// Whether can be hit at the given . - bool IsHittable(DrawableHitObject hitObject, double time); + ClickAction CheckHittable(DrawableHitObject hitObject, double time); /// /// Handles a being hit. diff --git a/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs index 6330208d37..07942954e1 100644 --- a/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs @@ -3,8 +3,7 @@ #nullable disable -using System.Collections.Generic; -using System.Linq; +using System; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -22,35 +21,41 @@ namespace osu.Game.Rulesets.Osu.UI { public IHitObjectContainer HitObjectContainer { get; set; } - public bool IsHittable(DrawableHitObject hitObject, double time) => enumerateHitObjectsUpTo(hitObject.HitObject.StartTime).All(obj => obj.AllJudged); - public void HandleHit(DrawableHitObject hitObject) { } - private IEnumerable enumerateHitObjectsUpTo(double targetTime) + public ClickAction CheckHittable(DrawableHitObject hitObject, double time) { - foreach (var obj in HitObjectContainer.AliveObjects) + int index = HitObjectContainer.AliveObjects.IndexOf(hitObject); + + if (index > 0) { - if (obj.HitObject.StartTime >= targetTime) - yield break; - - switch (obj) - { - case DrawableSpinner: - continue; - - case DrawableSlider slider: - yield return slider.HeadCircle; - - break; - - default: - yield return obj; - - break; - } + var previousHitObject = (DrawableOsuHitObject)HitObjectContainer.AliveObjects[index - 1]; + if (previousHitObject.HitObject.StackHeight > 0 && !previousHitObject.AllJudged) + return ClickAction.Ignore; } + + foreach (DrawableHitObject testObject in HitObjectContainer.AliveObjects) + { + if (testObject.AllJudged) + continue; + + // if we found the object being checked, we can move on to the final timing test. + if (testObject == hitObject) + break; + + // for all other objects, we check for validity and block the hit if any are still valid. + // 3ms of extra leniency to account for slightly unsnapped objects. + if (testObject.HitObject.GetEndTime() + 3 < hitObject.HitObject.StartTime) + return ClickAction.Shake; + } + + // stable has `const HitObjectManager.HITTABLE_RANGE = 400;`, which is only used for notelock code. + // probably not a coincidence that this is equivalent to lazer's OsuHitWindows.MISS_WINDOW. + + // TODO stable compares to 200 when autopilot is enabled, instead of 400. + return Math.Abs(hitObject.HitObject.StartTime - time) < 400 ? ClickAction.Hit : ClickAction.Shake; } } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index ed02284a4b..15ca0a90de 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -89,7 +89,7 @@ namespace osu.Game.Rulesets.Osu.UI protected override void OnNewDrawableHitObject(DrawableHitObject drawable) { - ((DrawableOsuHitObject)drawable).CheckHittable = hitPolicy.IsHittable; + ((DrawableOsuHitObject)drawable).CheckHittable = hitPolicy.CheckHittable; Debug.Assert(!drawable.IsLoaded, $"Already loaded {nameof(DrawableHitObject)} is added to {nameof(OsuPlayfield)}"); drawable.OnLoadComplete += onDrawableHitObjectLoaded; diff --git a/osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs index edc3ba0818..f33ca58aef 100644 --- a/osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.UI { public IHitObjectContainer HitObjectContainer { get; set; } - public bool IsHittable(DrawableHitObject hitObject, double time) + public ClickAction CheckHittable(DrawableHitObject hitObject, double time) { DrawableHitObject blockingObject = null; @@ -36,13 +36,13 @@ namespace osu.Game.Rulesets.Osu.UI // If there is no previous hitobject, allow the hit. if (blockingObject == null) - return true; + return ClickAction.Hit; // A hit is allowed if: // 1. The last blocking hitobject has been judged. // 2. The current time is after the last hitobject's start time. // Hits at exactly the same time as the blocking hitobject are allowed for maps that contain simultaneous hitobjects (e.g. /b/372245). - return blockingObject.Judged || time >= blockingObject.HitObject.StartTime; + return (blockingObject.Judged || time >= blockingObject.HitObject.StartTime) ? ClickAction.Hit : ClickAction.Shake; } public void HandleHit(DrawableHitObject hitObject) @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.UI if (!hitObjectCanBlockFutureHits(hitObject)) return; - if (!IsHittable(hitObject, hitObject.HitObject.StartTime + hitObject.Result.TimeOffset)) + if (CheckHittable(hitObject, hitObject.HitObject.StartTime + hitObject.Result.TimeOffset) != ClickAction.Hit) throw new InvalidOperationException($"A {hitObject} was hit before it became hittable!"); // Miss all hitobjects prior to the hit one. diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 099be486b3..454a83bcda 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.UI { public IEnumerable Objects => InternalChildren.Cast().OrderBy(h => h.HitObject.StartTime); - public IEnumerable AliveObjects => AliveEntries.Select(pair => pair.Drawable).OrderBy(h => h.HitObject.StartTime); + public IList AliveObjects => AliveEntries.Select(pair => pair.Drawable).OrderBy(h => h.HitObject.StartTime).ToList(); /// /// Invoked when a is judged. diff --git a/osu.Game/Rulesets/UI/IHitObjectContainer.cs b/osu.Game/Rulesets/UI/IHitObjectContainer.cs index 6dcb0944be..bb4806206d 100644 --- a/osu.Game/Rulesets/UI/IHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/IHitObjectContainer.cs @@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.UI /// /// If this uses pooled objects, this is equivalent to . /// - IEnumerable AliveObjects { get; } + IList AliveObjects { get; } } } From 15af85226ce9c15c6bdb18d1c727cd257683d4e1 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Wed, 19 Jul 2023 02:06:29 -0400 Subject: [PATCH 0056/4092] adjust test for correct stable notelock stable actually allows for hitobjs to be hit in the middle of sliders, as long as it doesn't interfere with the end time of the slider. --- .../TestSceneObjectOrderedHitPolicy.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs index ee70441688..be2affa50f 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs @@ -213,10 +213,10 @@ namespace osu.Game.Rulesets.Osu.Tests } /// - /// Tests clicking a future circle after a slider's start time, but hitting all slider ticks. + /// Tests clicking a future circle after a slider's start time, but hitting the slider head and all slider ticks. /// [Test] - public void TestMissSliderHeadAndHitAllSliderTicks() + public void TestHitCircleBeforeSliderHead() { const double time_slider = 1500; const double time_circle = 1510; @@ -248,7 +248,7 @@ namespace osu.Game.Rulesets.Osu.Tests new OsuReplayFrame { Time = time_slider + 10, Position = positionSlider, Actions = { OsuAction.RightButton } } }); - addJudgementAssert(hitObjects[0], HitResult.Miss); + addJudgementAssert(hitObjects[0], HitResult.Great); addJudgementAssert(hitObjects[1], HitResult.Great); addJudgementAssert("slider head", () => ((Slider)hitObjects[1]).HeadCircle, HitResult.LargeTickHit); addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.LargeTickHit); From 2c97ac74107c975f8f37e6a840caff06bef08fc8 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Wed, 19 Jul 2023 14:28:04 -0400 Subject: [PATCH 0057/4092] convert AliveObjects to list in hit policy instead of globally --- osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs | 8 +++++--- osu.Game/Rulesets/UI/HitObjectContainer.cs | 2 +- osu.Game/Rulesets/UI/IHitObjectContainer.cs | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs index 07942954e1..172e5a39d8 100644 --- a/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using System.Linq; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -27,16 +28,17 @@ namespace osu.Game.Rulesets.Osu.UI public ClickAction CheckHittable(DrawableHitObject hitObject, double time) { - int index = HitObjectContainer.AliveObjects.IndexOf(hitObject); + var aliveObjects = HitObjectContainer.AliveObjects.ToList(); + int index = aliveObjects.IndexOf(hitObject); if (index > 0) { - var previousHitObject = (DrawableOsuHitObject)HitObjectContainer.AliveObjects[index - 1]; + var previousHitObject = (DrawableOsuHitObject)aliveObjects[index - 1]; if (previousHitObject.HitObject.StackHeight > 0 && !previousHitObject.AllJudged) return ClickAction.Ignore; } - foreach (DrawableHitObject testObject in HitObjectContainer.AliveObjects) + foreach (DrawableHitObject testObject in aliveObjects) { if (testObject.AllJudged) continue; diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 454a83bcda..099be486b3 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.UI { public IEnumerable Objects => InternalChildren.Cast().OrderBy(h => h.HitObject.StartTime); - public IList AliveObjects => AliveEntries.Select(pair => pair.Drawable).OrderBy(h => h.HitObject.StartTime).ToList(); + public IEnumerable AliveObjects => AliveEntries.Select(pair => pair.Drawable).OrderBy(h => h.HitObject.StartTime); /// /// Invoked when a is judged. diff --git a/osu.Game/Rulesets/UI/IHitObjectContainer.cs b/osu.Game/Rulesets/UI/IHitObjectContainer.cs index bb4806206d..6dcb0944be 100644 --- a/osu.Game/Rulesets/UI/IHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/IHitObjectContainer.cs @@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.UI /// /// If this uses pooled objects, this is equivalent to . /// - IList AliveObjects { get; } + IEnumerable AliveObjects { get; } } } From 6a8123029854e79f9124ea90f88c41e0245fda16 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Wed, 19 Jul 2023 14:44:28 -0400 Subject: [PATCH 0058/4092] rename ObjectOrderedHitPolicy to LegacyHitPolicy --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 2 +- .../UI/{ObjectOrderedHitPolicy.cs => LegacyHitPolicy.cs} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename osu.Game.Rulesets.Osu/UI/{ObjectOrderedHitPolicy.cs => LegacyHitPolicy.cs} (97%) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index 250d97c537..229f80c2bd 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Mods var osuRuleset = (DrawableOsuRuleset)drawableRuleset; if (ClassicNoteLock.Value) - osuRuleset.Playfield.HitPolicy = new ObjectOrderedHitPolicy(); + osuRuleset.Playfield.HitPolicy = new LegacyHitPolicy(); usingHiddenFading = drawableRuleset.Mods.OfType().SingleOrDefault()?.OnlyFadeApproachCircles.Value == false; } diff --git a/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/LegacyHitPolicy.cs similarity index 97% rename from osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs rename to osu.Game.Rulesets.Osu/UI/LegacyHitPolicy.cs index 172e5a39d8..c35d4a1b56 100644 --- a/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/LegacyHitPolicy.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.UI /// Hits will be blocked until the previous s have been judged. /// /// - public class ObjectOrderedHitPolicy : IHitPolicy + public class LegacyHitPolicy : IHitPolicy { public IHitObjectContainer HitObjectContainer { get; set; } From ef2134a92a32f0607d0f69bc04c8dbfd2a477acd Mon Sep 17 00:00:00 2001 From: Hydria Date: Sat, 22 Jul 2023 10:45:48 +0100 Subject: [PATCH 0059/4092] Fix issue with processing LN orders Related Issue: https://github.com/ppy/osu/issues/22756 The trigger in question happens when (1) in a chord: a longer LN, then a shorter LN is processed respectively. (2) in a chord: a long LN, then a note is processed respectively. however, given the opposite processing step, it will fail to trigger. We observe that both situations have the same pattern, however has undeterministic results, which only depends on the order the mapper placed each note. --- osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs index 0a4fec3a70..7d8d010da0 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs @@ -53,7 +53,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills isOverlapping |= Precision.DefinitelyBigger(endTimes[i], startTime, 1) && Precision.DefinitelyBigger(endTime, endTimes[i], 1); // We give a slight bonus to everything if something is held meanwhile - if (Precision.DefinitelyBigger(endTimes[i], endTime, 1)) + if (Precision.DefinitelyBigger(endTimes[i], endTime, 1) && + Precision.DefinitelyBigger(startTime, startTimes[i], 1)) holdFactor = 1.25; closestEndTime = Math.Min(closestEndTime, Math.Abs(endTime - endTimes[i])); From 1e19def1538d718421e4d7c6802045011616d21e Mon Sep 17 00:00:00 2001 From: Hydria Date: Sat, 22 Jul 2023 16:44:01 +0100 Subject: [PATCH 0060/4092] 2nd fix to cover all scenarios --- osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs index 7d8d010da0..a24fcaad8d 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs @@ -50,7 +50,9 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills for (int i = 0; i < endTimes.Length; ++i) { // The current note is overlapped if a previous note or end is overlapping the current note body - isOverlapping |= Precision.DefinitelyBigger(endTimes[i], startTime, 1) && Precision.DefinitelyBigger(endTime, endTimes[i], 1); + isOverlapping |= Precision.DefinitelyBigger(endTimes[i], startTime, 1) && + Precision.DefinitelyBigger(endTime, endTimes[i], 1) && + Precision.DefinitelyBigger(startTime, startTimes[i], 1); // We give a slight bonus to everything if something is held meanwhile if (Precision.DefinitelyBigger(endTimes[i], endTime, 1) && From 0bd34a4f13eb79a0dba578f5a17c20e872976a09 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 25 Jul 2023 21:14:21 -0700 Subject: [PATCH 0061/4092] Always show supporter-only playlist durations --- osu.Game/Localisation/OnlinePlayStrings.cs | 19 +++++++ .../Match/Components/RoomSettingsOverlay.cs | 5 +- .../Playlists/PlaylistsRoomSettingsOverlay.cs | 55 +++++++++++++------ 3 files changed, 59 insertions(+), 20 deletions(-) create mode 100644 osu.Game/Localisation/OnlinePlayStrings.cs diff --git a/osu.Game/Localisation/OnlinePlayStrings.cs b/osu.Game/Localisation/OnlinePlayStrings.cs new file mode 100644 index 0000000000..e9f17cddc0 --- /dev/null +++ b/osu.Game/Localisation/OnlinePlayStrings.cs @@ -0,0 +1,19 @@ +// 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.Localisation; + +namespace osu.Game.Localisation +{ + public static class OnlinePlayStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.OnlinePlay"; + + /// + /// "This duration is only available for osu!supporters." + /// + public static LocalisableString SupporterOnlyDurationNotice => new TranslatableString(getKey(@"supporter_only_duration_notice"), @"This duration is only available for osu!supporters."); + + private static string getKey(string key) => $@"{prefix}:{key}"; + } +} diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs index 05232fe0e2..916b799d50 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs @@ -113,7 +113,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components protected partial class Section : Container { - private readonly Container content; + private readonly ReverseChildIDFillFlowContainer content; protected override Container Content => content; @@ -135,10 +135,11 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 12), Text = title.ToUpperInvariant(), }, - content = new Container + content = new ReverseChildIDFillFlowContainer { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical }, }, }; diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index e93f56c2e2..c69ccb7b1c 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -23,6 +23,7 @@ using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Screens.OnlinePlay.Match.Components; using osuTK; +using osu.Game.Localisation; namespace osu.Game.Screens.OnlinePlay.Playlists { @@ -80,6 +81,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private IBindable localUser = null!; private readonly Room room; + private OsuSpriteText durationNoticeText = null!; public MatchSettings(Room room) { @@ -141,14 +143,22 @@ namespace osu.Game.Screens.OnlinePlay.Playlists }, new Section("Duration") { - Child = new Container + Children = new Drawable[] { - RelativeSizeAxes = Axes.X, - Height = 40, - Child = DurationField = new DurationDropdown + new Container { - RelativeSizeAxes = Axes.X - } + RelativeSizeAxes = Axes.X, + Height = 40, + Child = DurationField = new DurationDropdown + { + RelativeSizeAxes = Axes.X + }, + }, + durationNoticeText = new OsuSpriteText + { + Alpha = 0, + Colour = colours.Yellow, + }, } }, new Section("Allowed attempts (across all playlist items)") @@ -305,6 +315,17 @@ namespace osu.Game.Screens.OnlinePlay.Playlists MaxAttempts.BindValueChanged(count => MaxAttemptsField.Text = count.NewValue?.ToString(), true); Duration.BindValueChanged(duration => DurationField.Current.Value = duration.NewValue ?? TimeSpan.FromMinutes(30), true); + DurationField.Current.BindValueChanged(duration => + { + if (selectedAvailableDuration) + durationNoticeText.Hide(); + else + { + durationNoticeText.Show(); + durationNoticeText.Text = OnlinePlayStrings.SupporterOnlyDurationNotice; + } + }); + localUser = api.LocalUser.GetBoundCopy(); localUser.BindValueChanged(populateDurations, true); @@ -314,6 +335,10 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private void populateDurations(ValueChangedEvent user) { + // roughly correct (see https://github.com/Humanizr/Humanizer/blob/18167e56c082449cc4fe805b8429e3127a7b7f93/readme.md?plain=1#L427) + // if we want this to be more accurate we might consider sending an actual end time, not a time span. probably not required though. + const int days_in_month = 31; + DurationField.Items = new[] { TimeSpan.FromMinutes(30), @@ -326,18 +351,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists TimeSpan.FromDays(3), TimeSpan.FromDays(7), TimeSpan.FromDays(14), + TimeSpan.FromDays(days_in_month), + TimeSpan.FromDays(days_in_month * 3), }; - - // TODO: show these in the interface at all times. - if (user.NewValue.IsSupporter) - { - // roughly correct (see https://github.com/Humanizr/Humanizer/blob/18167e56c082449cc4fe805b8429e3127a7b7f93/readme.md?plain=1#L427) - // if we want this to be more accurate we might consider sending an actual end time, not a time span. probably not required though. - const int days_in_month = 31; - - DurationField.AddDropdownItem(TimeSpan.FromDays(days_in_month)); - DurationField.AddDropdownItem(TimeSpan.FromDays(days_in_month * 3)); - } } protected override void Update() @@ -352,7 +368,10 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private void onPlaylistChanged(object? sender, NotifyCollectionChangedEventArgs e) => playlistLength.Text = $"Length: {Playlist.GetTotalDuration()}"; - private bool hasValidSettings => RoomID.Value == null && NameField.Text.Length > 0 && Playlist.Count > 0; + private bool hasValidSettings => RoomID.Value == null && NameField.Text.Length > 0 && Playlist.Count > 0 + && selectedAvailableDuration; + + private bool selectedAvailableDuration => DurationField.Current.Value < TimeSpan.FromDays(31) || localUser.Value.IsSupporter; private void apply() { From 30e0e00c520d8072eb9cfe8fca2c0e2a14fd06e9 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 25 Jul 2023 22:57:01 -0700 Subject: [PATCH 0062/4092] Rename `selectedAvailableDuration` to `hasValidDuration` --- .../OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index c69ccb7b1c..f7bd7fc530 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -317,7 +317,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists DurationField.Current.BindValueChanged(duration => { - if (selectedAvailableDuration) + if (hasValidDuration) durationNoticeText.Hide(); else { @@ -369,9 +369,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists playlistLength.Text = $"Length: {Playlist.GetTotalDuration()}"; private bool hasValidSettings => RoomID.Value == null && NameField.Text.Length > 0 && Playlist.Count > 0 - && selectedAvailableDuration; + && hasValidDuration; - private bool selectedAvailableDuration => DurationField.Current.Value < TimeSpan.FromDays(31) || localUser.Value.IsSupporter; + private bool hasValidDuration => DurationField.Current.Value < TimeSpan.FromDays(31) || localUser.Value.IsSupporter; private void apply() { From c1ba8fe175c9bff9e018f2409fbc3cfa041e4902 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jul 2023 16:07:45 +0900 Subject: [PATCH 0063/4092] Rename `BackgroundBeatmapProcessor` to `BackgroundDataStoreProcessor` --- ...orTests.cs => BackgroundDataStoreProcessorTests.cs} | 8 ++++---- ...mapProcessor.cs => BackgroundDataStoreProcessor.cs} | 10 +++++++--- osu.Game/OsuGame.cs | 2 +- osu.Game/Scoring/ScoreInfo.cs | 2 +- 4 files changed, 13 insertions(+), 9 deletions(-) rename osu.Game.Tests/Database/{BackgroundBeatmapProcessorTests.cs => BackgroundDataStoreProcessorTests.cs} (92%) rename osu.Game/{BackgroundBeatmapProcessor.cs => BackgroundDataStoreProcessor.cs} (97%) diff --git a/osu.Game.Tests/Database/BackgroundBeatmapProcessorTests.cs b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs similarity index 92% rename from osu.Game.Tests/Database/BackgroundBeatmapProcessorTests.cs rename to osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs index c876316be4..15bb0fc09c 100644 --- a/osu.Game.Tests/Database/BackgroundBeatmapProcessorTests.cs +++ b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs @@ -15,7 +15,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Tests.Database { [HeadlessTest] - public partial class BackgroundBeatmapProcessorTests : OsuTestScene, ILocalUserPlayInfo + public partial class BackgroundDataStoreProcessorTests : OsuTestScene, ILocalUserPlayInfo { public IBindable IsPlaying => isPlaying; @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Database AddStep("Run background processor", () => { - Add(new TestBackgroundBeatmapProcessor()); + Add(new TestBackgroundDataStoreProcessor()); }); AddUntilStep("wait for difficulties repopulated", () => @@ -98,7 +98,7 @@ namespace osu.Game.Tests.Database AddStep("Run background processor", () => { - Add(new TestBackgroundBeatmapProcessor()); + Add(new TestBackgroundDataStoreProcessor()); }); AddWaitStep("wait some", 500); @@ -124,7 +124,7 @@ namespace osu.Game.Tests.Database }); } - public partial class TestBackgroundBeatmapProcessor : BackgroundBeatmapProcessor + public partial class TestBackgroundDataStoreProcessor : BackgroundDataStoreProcessor { protected override int TimeToSleepDuringGameplay => 10; } diff --git a/osu.Game/BackgroundBeatmapProcessor.cs b/osu.Game/BackgroundDataStoreProcessor.cs similarity index 97% rename from osu.Game/BackgroundBeatmapProcessor.cs rename to osu.Game/BackgroundDataStoreProcessor.cs index b553fee503..c475a07c79 100644 --- a/osu.Game/BackgroundBeatmapProcessor.cs +++ b/osu.Game/BackgroundDataStoreProcessor.cs @@ -24,7 +24,10 @@ using osu.Game.Screens.Play; namespace osu.Game { - public partial class BackgroundBeatmapProcessor : Component + /// + /// Performs background updating of data stores at startup. + /// + public partial class BackgroundDataStoreProcessor : Component { [Resolved] private RulesetStore rulesetStore { get; set; } = null!; @@ -61,7 +64,8 @@ namespace osu.Game Task.Factory.StartNew(() => { - Logger.Log("Beginning background beatmap processing.."); + Logger.Log("Beginning background data store processing.."); + checkForOutdatedStarRatings(); processBeatmapSetsWithMissingMetrics(); processScoresWithMissingStatistics(); @@ -74,7 +78,7 @@ namespace osu.Game return; } - Logger.Log("Finished background beatmap processing!"); + Logger.Log("Finished background data store processing!"); }); } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 1a40bb8e3d..c60bff9e4c 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1025,7 +1025,7 @@ namespace osu.Game loadComponentSingleFile(CreateHighPerformanceSession(), Add); - loadComponentSingleFile(new BackgroundBeatmapProcessor(), Add); + loadComponentSingleFile(new BackgroundDataStoreProcessor(), Add); Add(difficultyRecommender); Add(externalLinkOpener = new ExternalLinkOpener()); diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index c6f4433824..a3bbb2c09e 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -66,7 +66,7 @@ namespace osu.Game.Scoring /// If this does not match , /// the total score has not yet been updated to reflect the current scoring values. /// - /// See 's conversion logic. + /// See 's conversion logic. /// /// /// This may not match the version stored in the replay files. From 30baac0f3db82d4df246c75bb458113be90c210e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jul 2023 16:08:02 +0900 Subject: [PATCH 0064/4092] Avoid reprocessing scores which already failed an upgrade previously Closes https://github.com/ppy/osu/issues/24301. --- osu.Game/BackgroundDataStoreProcessor.cs | 6 ++++-- osu.Game/Database/RealmAccess.cs | 3 ++- osu.Game/Scoring/ScoreInfo.cs | 9 +++++++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/osu.Game/BackgroundDataStoreProcessor.cs b/osu.Game/BackgroundDataStoreProcessor.cs index c475a07c79..ae9e9527de 100644 --- a/osu.Game/BackgroundDataStoreProcessor.cs +++ b/osu.Game/BackgroundDataStoreProcessor.cs @@ -186,7 +186,7 @@ namespace osu.Game realmAccess.Run(r => { - foreach (var score in r.All()) + foreach (var score in r.All().Where(s => !s.TotalScoreUpgradeFailed)) { if (score.BeatmapInfo != null && score.Statistics.Sum(kvp => kvp.Value) > 0 @@ -225,6 +225,7 @@ namespace osu.Game catch (Exception e) { Logger.Log(@$"Failed to populate maximum statistics for {id}: {e}"); + realmAccess.Write(r => r.Find(id)!.TotalScoreUpgradeFailed = true); } } } @@ -234,7 +235,7 @@ namespace osu.Game Logger.Log("Querying for scores that need total score conversion..."); HashSet scoreIds = realmAccess.Run(r => new HashSet(r.All() - .Where(s => s.BeatmapInfo != null && s.TotalScoreVersion == 30000002) + .Where(s => !s.TotalScoreUpgradeFailed && s.BeatmapInfo != null && s.TotalScoreVersion == 30000002) .AsEnumerable().Select(s => s.ID))); Logger.Log($"Found {scoreIds.Count} scores which require total score conversion."); @@ -283,6 +284,7 @@ namespace osu.Game catch (Exception e) { Logger.Log($"Failed to convert total score for {id}: {e}"); + realmAccess.Write(r => r.Find(id)!.TotalScoreUpgradeFailed = true); ++failedCount; } } diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index f32b161bb6..04a4b28fa4 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -82,8 +82,9 @@ namespace osu.Game.Database /// 30 2023-06-16 Run migration of old lazer scores again. This time with more correct rounding considerations. /// 31 2023-06-26 Add Version and LegacyTotalScore to ScoreInfo, set Version to 30000002 and copy TotalScore into LegacyTotalScore for legacy scores. /// 32 2023-07-09 Populate legacy scores with the ScoreV2 mod (and restore TotalScore to the legacy total for such scores) using replay files. + /// 33 2023-07-26 Add TotalScoreUpgradeFailed flag to ScoreInfo to track upgrade failures. /// - private const int schema_version = 32; + private const int schema_version = 33; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index a3bbb2c09e..c3a45332e4 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -81,6 +81,15 @@ namespace osu.Game.Scoring /// public long? LegacyTotalScore { get; set; } + /// + /// If an reprocess of total score failed to update this score to the latest version, this flag will become true. + /// Should be used to ensure we don't repeatedly attempt to update the same scores each startup even though we already know they will fail. + /// + /// + /// See https://github.com/ppy/osu/issues/24301 for one example of how this can occur(missing beatmap file on disk). + /// + public bool TotalScoreUpgradeFailed { get; set; } + public int MaxCombo { get; set; } public double Accuracy { get; set; } From a2f1ced5a224e6ff1c239c430be62793b3b24fe0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jul 2023 16:22:21 +0900 Subject: [PATCH 0065/4092] Add test coverage of failed-to-upgrade flag --- .../BackgroundDataStoreProcessorTests.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs index 15bb0fc09c..d7ec572026 100644 --- a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs +++ b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs @@ -8,6 +8,9 @@ using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Scoring; +using osu.Game.Scoring.Legacy; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps.IO; using osu.Game.Tests.Visual; @@ -124,6 +127,32 @@ namespace osu.Game.Tests.Database }); } + [Test] + public void TestScoreUpgradeFailed() + { + ScoreInfo scoreInfo = null!; + + AddStep("Add score which requires upgrade (but has no beatmap)", () => + { + Realm.Write(r => + { + r.Add(scoreInfo = new ScoreInfo(ruleset: r.All().First(), beatmap: new BeatmapInfo + { + BeatmapSet = new BeatmapSetInfo(), + Ruleset = r.All().First(), + }) + { + TotalScoreVersion = 30000002, + IsLegacyScore = true, + }); + }); + }); + + AddStep("Run background processor", () => Add(new TestBackgroundDataStoreProcessor())); + + AddUntilStep("Score marked as failed", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreUpgradeFailed)); + } + public partial class TestBackgroundDataStoreProcessor : BackgroundDataStoreProcessor { protected override int TimeToSleepDuringGameplay => 10; From 235cb2d73fcc64ffb2c85d1dafb2a03486557276 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jul 2023 16:28:38 +0900 Subject: [PATCH 0066/4092] Add test coverage of upgrade success for good measure --- .../BackgroundDataStoreProcessorTests.cs | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs index d7ec572026..23b88b7395 100644 --- a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs +++ b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs @@ -127,6 +127,30 @@ namespace osu.Game.Tests.Database }); } + [Test] + public void TestScoreUpgradeSuccess() + { + ScoreInfo scoreInfo = null!; + + AddStep("Add score which requires upgrade (but has no beatmap)", () => + { + Realm.Write(r => + { + r.Add(scoreInfo = new ScoreInfo(ruleset: r.All().First(), beatmap: r.All().First()) + { + TotalScoreVersion = 30000002, + LegacyTotalScore = 123456, + IsLegacyScore = true, + }); + }); + }); + + AddStep("Run background processor", () => Add(new TestBackgroundDataStoreProcessor())); + + AddUntilStep("Score version upgraded", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(LegacyScoreEncoder.LATEST_VERSION)); + AddAssert("Score not marked as failed", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreUpgradeFailed), () => Is.False); + } + [Test] public void TestScoreUpgradeFailed() { @@ -150,7 +174,8 @@ namespace osu.Game.Tests.Database AddStep("Run background processor", () => Add(new TestBackgroundDataStoreProcessor())); - AddUntilStep("Score marked as failed", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreUpgradeFailed)); + AddUntilStep("Score marked as failed", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreUpgradeFailed), () => Is.True); + AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(30000002)); } public partial class TestBackgroundDataStoreProcessor : BackgroundDataStoreProcessor From 9d87ff2986f3430b4c44b03d895720f5c8b57bd3 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 26 Jul 2023 01:37:55 -0700 Subject: [PATCH 0067/4092] Improve supporter-only duration notice and fix max duration of non-supporter See https://github.com/ppy/osu-web/blob/3c542292569b336c4dff38e2b52a823149ec1a81/.env.example#L230. Might have more duration options in the future. --- osu.Game/Localisation/OnlinePlayStrings.cs | 4 ++-- .../OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Localisation/OnlinePlayStrings.cs b/osu.Game/Localisation/OnlinePlayStrings.cs index e9f17cddc0..1853cb753a 100644 --- a/osu.Game/Localisation/OnlinePlayStrings.cs +++ b/osu.Game/Localisation/OnlinePlayStrings.cs @@ -10,9 +10,9 @@ namespace osu.Game.Localisation private const string prefix = @"osu.Game.Resources.Localisation.OnlinePlay"; /// - /// "This duration is only available for osu!supporters." + /// "Playlist durations longer than 2 weeks require an active osu!supporter tag." /// - public static LocalisableString SupporterOnlyDurationNotice => new TranslatableString(getKey(@"supporter_only_duration_notice"), @"This duration is only available for osu!supporters."); + public static LocalisableString SupporterOnlyDurationNotice => new TranslatableString(getKey(@"supporter_only_duration_notice"), @"Playlist durations longer than 2 weeks require an active osu!supporter tag."); private static string getKey(string key) => $@"{prefix}:{key}"; } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index f7bd7fc530..84e419d67a 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -371,7 +371,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private bool hasValidSettings => RoomID.Value == null && NameField.Text.Length > 0 && Playlist.Count > 0 && hasValidDuration; - private bool hasValidDuration => DurationField.Current.Value < TimeSpan.FromDays(31) || localUser.Value.IsSupporter; + private bool hasValidDuration => DurationField.Current.Value <= TimeSpan.FromDays(14) || localUser.Value.IsSupporter; private void apply() { From 217b07810fb497f571fadfeb4d015394a43075d0 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Thu, 27 Jul 2023 02:12:21 -0400 Subject: [PATCH 0068/4092] don't skip the negative break frame investigation reveals this frame is played back by stable --- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index 63465652e8..8b1b24ce95 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable @@ -323,7 +323,7 @@ namespace osu.Game.Scoring.Legacy // still paying back the deficit from a negative frame. Skip this frame. // Todo: At some point we probably want to rewind and play back the negative-time frames // but for now we'll achieve equal playback to stable by skipping negative frames - if (timeDeficit < 0 || isNegativeBreakFrame) + if (timeDeficit < 0) continue; currentFrame = convertFrame(new LegacyReplayFrame(lastTime, From a93561cab05807be032e963b37092ff349f12fc1 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Thu, 27 Jul 2023 02:12:43 -0400 Subject: [PATCH 0069/4092] remove resolved comment --- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index 8b1b24ce95..79224b7d4f 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -321,8 +321,6 @@ namespace osu.Game.Scoring.Legacy } // still paying back the deficit from a negative frame. Skip this frame. - // Todo: At some point we probably want to rewind and play back the negative-time frames - // but for now we'll achieve equal playback to stable by skipping negative frames if (timeDeficit < 0) continue; From 61760f614a9900e5cd71a298a8979ca69d9b8eb0 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Thu, 27 Jul 2023 16:34:18 -0400 Subject: [PATCH 0070/4092] fix legacy score decode tests for negative frame --- .../Beatmaps/Formats/LegacyScoreDecoderTest.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index 93cda34ef7..89b6d76e54 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -92,17 +92,20 @@ namespace osu.Game.Tests.Beatmaps.Formats [TestCase(LegacyBeatmapDecoder.LATEST_VERSION, false)] public void TestLegacyBeatmapReplayOffsetsDecode(int beatmapVersion, bool offsetApplied) { - const double first_frame_time = 48; - const double second_frame_time = 65; + const double first_frame_time = 31; + const double second_frame_time = 48; + const double third_frame_time = 65; var decoder = new TestLegacyScoreDecoder(beatmapVersion); using (var resourceStream = TestResources.OpenResource("Replays/mania-replay.osr")) { var score = decoder.Parse(resourceStream); + int offset = offsetApplied ? LegacyBeatmapDecoder.EARLY_VERSION_TIMING_OFFSET : 0; - Assert.That(score.Replay.Frames[0].Time, Is.EqualTo(first_frame_time + (offsetApplied ? LegacyBeatmapDecoder.EARLY_VERSION_TIMING_OFFSET : 0))); - Assert.That(score.Replay.Frames[1].Time, Is.EqualTo(second_frame_time + (offsetApplied ? LegacyBeatmapDecoder.EARLY_VERSION_TIMING_OFFSET : 0))); + Assert.That(score.Replay.Frames[0].Time, Is.EqualTo(first_frame_time + offset)); + Assert.That(score.Replay.Frames[1].Time, Is.EqualTo(second_frame_time + offset)); + Assert.That(score.Replay.Frames[2].Time, Is.EqualTo(third_frame_time + offset)); } } From 7d174dd8bb4998ac264463067b798fae14541f0c Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Thu, 27 Jul 2023 17:20:54 -0400 Subject: [PATCH 0071/4092] dont count any of first three frames towards time deficit --- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index 79224b7d4f..eceaada399 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -267,7 +267,6 @@ namespace osu.Game.Scoring.Legacy private void readLegacyReplay(Replay replay, StreamReader reader) { float lastTime = beatmapOffset; - bool skipFramesPresent = false; ReplayFrame currentFrame = null; // the negative time amount that must be "paid back" by positive frames before we start including frames again. @@ -299,22 +298,20 @@ namespace osu.Game.Scoring.Legacy lastTime += diff; if (i < 2 && mouseX == 256 && mouseY == -500) - { // at the start of the replay, stable places two replay frames, at time 0 and SkipBoundary - 1, respectively. // both frames use a position of (256, -500). // ignore these frames as they serve no real purpose (and can even mislead ruleset-specific handlers - see mania) - skipFramesPresent = true; continue; - } - // if the skip frames inserted by stable are present, the third frame will have a large negative time - // roughly equal to SkipBoundary. We don't want this to count towards the deficit: doing so would cause - // the replay data before the skip to be, well, skipped. - // In other words, this frame, if present, is a different kind of negative frame. It sets the "offset" - // for the beginning of the replay. This is the only negative frame to be handled in such a way. - bool isNegativeBreakFrame = i == 2 && skipFramesPresent && diff < 0; - - if (!isNegativeBreakFrame) + // negative frames are only counted towards the deficit after the very beginning of the replay. + // When the two skip frames are present (see directly above), the third frame will have a large + // negative time roughly equal to SkipBoundary. This shouldn't be counted towards the deficit, otherwise + // any replay data before the skip would be, well, skipped. + // + // On testing against stable it appears that stable ignores the negative time of *any* of the first + // three frames, regardless of if the skip frames are present. Hence the condition here. + // But this may be incorrect and need to be revisited later. + if (i > 2) { timeDeficit += diff; timeDeficit = Math.Min(0, timeDeficit); From 04ef04b9026dfe84c323e3d08204f5a722fb5d74 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Thu, 27 Jul 2023 21:12:08 -0400 Subject: [PATCH 0072/4092] only ignore the first negative frame among the first 3 replay frames --- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index eceaada399..fdeda24c75 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -267,6 +267,7 @@ namespace osu.Game.Scoring.Legacy private void readLegacyReplay(Replay replay, StreamReader reader) { float lastTime = beatmapOffset; + bool negativeFrameEncounted = false; ReplayFrame currentFrame = null; // the negative time amount that must be "paid back" by positive frames before we start including frames again. @@ -308,15 +309,19 @@ namespace osu.Game.Scoring.Legacy // negative time roughly equal to SkipBoundary. This shouldn't be counted towards the deficit, otherwise // any replay data before the skip would be, well, skipped. // - // On testing against stable it appears that stable ignores the negative time of *any* of the first - // three frames, regardless of if the skip frames are present. Hence the condition here. - // But this may be incorrect and need to be revisited later. - if (i > 2) + // On testing against stable, it appears that stable ignores the negative time of only the first + // negative frame of the first three replay frames, regardless of if the skip frames are present. + // Hence the condition here. + // But there is a possibility this is incorrect and may need to be revisited later. + if (i > 2 || negativeFrameEncounted) { timeDeficit += diff; timeDeficit = Math.Min(0, timeDeficit); } + if (diff < 0) + negativeFrameEncounted = true; + // still paying back the deficit from a negative frame. Skip this frame. if (timeDeficit < 0) continue; From 26c128a09323cb0ee00cdb3c93d0463367b2f451 Mon Sep 17 00:00:00 2001 From: QuantumSno Date: Fri, 28 Jul 2023 14:39:30 -0400 Subject: [PATCH 0073/4092] added keybind and localization string --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 6 +++++- osu.Game/Localisation/GlobalActionKeyBindingStrings.cs | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 01c454e3f9..3d24afbb16 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -116,9 +116,10 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.F3 }, GlobalAction.DecreaseScrollSpeed), new KeyBinding(new[] { InputKey.F4 }, GlobalAction.IncreaseScrollSpeed), new KeyBinding(new[] { InputKey.Shift, InputKey.Tab }, GlobalAction.ToggleInGameInterface), + new KeyBinding(InputKey.Tab, GlobalAction.ToggleInGameLeaderboard), new KeyBinding(InputKey.MouseMiddle, GlobalAction.PauseGameplay), new KeyBinding(InputKey.Control, GlobalAction.HoldForHUD), - new KeyBinding(InputKey.Tab, GlobalAction.ToggleChatFocus), + new KeyBinding(InputKey.Enter, GlobalAction.ToggleChatFocus), new KeyBinding(InputKey.F1, GlobalAction.SaveReplay), new KeyBinding(InputKey.F2, GlobalAction.ExportReplay), }; @@ -285,6 +286,9 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleInGameInterface))] ToggleInGameInterface, + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleInGameLeaderboard))] + ToggleInGameLeaderboard, + // Song select keybindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleModSelection))] ToggleModSelection, diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index f93d86225c..ceefc27968 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -219,6 +219,11 @@ namespace osu.Game.Localisation /// public static LocalisableString ToggleInGameInterface => new TranslatableString(getKey(@"toggle_in_game_interface"), @"Toggle in-game interface"); + /// + /// "Toggle in-game leaderboard" + /// + public static LocalisableString ToggleInGameLeaderboard => new TranslatableString(getKey(@"toggle_in_game_leaderboard"), @"Toggle in-game leaderboard"); + /// /// "Toggle mod select" /// From a4065486c198fd49fd538ba779f7e6cfb08fe79e Mon Sep 17 00:00:00 2001 From: QuantumSno Date: Fri, 28 Jul 2023 14:39:41 -0400 Subject: [PATCH 0074/4092] bound bind during gameplay --- osu.Game/Screens/Play/HUDOverlay.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index d11171e3fe..9c001b3db3 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -78,6 +78,7 @@ namespace osu.Game.Screens.Play public Bindable ShowHud { get; } = new BindableBool(); private Bindable configVisibilityMode; + private Bindable configLeaderboardVisibility; private Bindable configSettingsOverlay; private readonly BindableBool replayLoaded = new BindableBool(); @@ -179,6 +180,7 @@ namespace osu.Game.Screens.Play ModDisplay.Current.Value = mods; configVisibilityMode = config.GetBindable(OsuSetting.HUDVisibilityMode); + configLeaderboardVisibility = config.GetBindable(OsuSetting.GameplayLeaderboard); configSettingsOverlay = config.GetBindable(OsuSetting.ReplaySettingsOverlay); if (configVisibilityMode.Value == HUDVisibilityMode.Never && !hasShownNotificationOnce) @@ -381,6 +383,10 @@ namespace osu.Game.Screens.Play } return true; + + case GlobalAction.ToggleInGameLeaderboard: + configLeaderboardVisibility.Value = !configLeaderboardVisibility.Value; + return true; } return false; From a3301dc7ffb0a6e4958065f8b8210a3b98c1fdc4 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 28 Jul 2023 23:22:28 -0700 Subject: [PATCH 0075/4092] Fix accuracy break info decimal separator being incorrect in certain languages --- osu.Game/Screens/Play/Break/BreakInfoLine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Break/BreakInfoLine.cs b/osu.Game/Screens/Play/Break/BreakInfoLine.cs index 7261155c94..b8696352e8 100644 --- a/osu.Game/Screens/Play/Break/BreakInfoLine.cs +++ b/osu.Game/Screens/Play/Break/BreakInfoLine.cs @@ -58,7 +58,7 @@ namespace osu.Game.Screens.Play.Break private void currentValueChanged(ValueChangedEvent e) { - string newText = prefix + Format(e.NewValue); + LocalisableString newText = LocalisableString.Interpolate($"{prefix}{Format(e.NewValue)}"); if (valueText.Text == newText) return; From 8a06914438fd3d1d8bdcc462c5fe196891bee2c7 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Tue, 25 Jul 2023 20:50:55 +0900 Subject: [PATCH 0076/4092] remove #nullable disable in tournament --- .../NonVisual/LadderInfoSerialisationTest.cs | 5 +- .../Screens/TestSceneLadderEditorScreen.cs | 2 +- .../Screens/TestSceneSeedingEditorScreen.cs | 3 +- .../Screens/TestSceneTeamIntroScreen.cs | 2 +- .../Screens/TestSceneTeamWinScreen.cs | 2 +- .../TournamentTestScene.cs | 9 +-- .../Components/DrawableTeamFlag.cs | 10 ++- .../Components/DrawableTeamTitle.cs | 8 +-- .../Components/DrawableTournamentTeam.cs | 11 ++-- osu.Game.Tournament/Components/SongBar.cs | 8 +-- .../Components/TournamentBeatmapPanel.cs | 18 +++-- .../Components/TournamentMatchChatDisplay.cs | 4 +- .../Components/TourneyVideo.cs | 6 +- osu.Game.Tournament/IPC/FileBasedIPC.cs | 65 +++++++++---------- osu.Game.Tournament/JsonPointConverter.cs | 9 +-- osu.Game.Tournament/Models/LadderInfo.cs | 6 +- osu.Game.Tournament/Models/RoundBeatmap.cs | 1 - osu.Game.Tournament/Models/StableInfo.cs | 6 +- osu.Game.Tournament/Models/TournamentMatch.cs | 22 +++---- osu.Game.Tournament/Models/TournamentTeam.cs | 9 ++- .../Screens/Drawings/Components/Group.cs | 4 +- .../Components/ScrollingTeamContainer.cs | 27 ++++---- .../Components/StorageBackedTeamList.cs | 6 +- .../Components/VisualiserContainer.cs | 4 +- .../Screens/Drawings/DrawingsScreen.cs | 28 ++++---- .../Screens/Editors/LadderEditorScreen.cs | 64 ++++++++---------- .../Gameplay/Components/MatchHeader.cs | 8 +-- .../Gameplay/Components/TeamDisplay.cs | 2 +- .../Gameplay/Components/TeamScoreDisplay.cs | 24 ++++--- .../Components/TournamentMatchScoreDisplay.cs | 4 +- .../Screens/Gameplay/GameplayScreen.cs | 36 +++++----- .../Ladder/Components/DrawableMatchTeam.cs | 29 ++++----- .../Components/DrawableTournamentMatch.cs | 28 ++++---- .../Components/DrawableTournamentRound.cs | 4 +- .../Ladder/Components/LadderEditorSettings.cs | 22 +++---- .../Ladder/Components/SettingsTeamDropdown.cs | 2 +- .../Screens/Ladder/LadderScreen.cs | 12 ++-- .../Screens/MapPool/MapPoolScreen.cs | 38 ++++++----- .../Screens/Schedule/ScheduleScreen.cs | 10 ++- .../Screens/Setup/ResolutionSelector.cs | 6 +- .../Screens/Setup/SetupScreen.cs | 22 +++---- .../Screens/Setup/StablePathSelectScreen.cs | 12 ++-- .../Screens/Setup/TournamentSwitcher.cs | 10 ++- .../Screens/Showcase/ShowcaseScreen.cs | 2 +- .../Screens/TeamIntro/SeedingScreen.cs | 22 +++---- .../Screens/TeamIntro/TeamIntroScreen.cs | 6 +- .../Screens/TeamWin/TeamWinScreen.cs | 12 ++-- .../Screens/TournamentMatchScreen.cs | 8 +-- osu.Game.Tournament/TournamentGame.cs | 8 +-- osu.Game.Tournament/TournamentGameBase.cs | 29 +++++---- osu.Game.Tournament/TournamentSceneManager.cs | 20 +++--- 51 files changed, 333 insertions(+), 382 deletions(-) diff --git a/osu.Game.Tournament.Tests/NonVisual/LadderInfoSerialisationTest.cs b/osu.Game.Tournament.Tests/NonVisual/LadderInfoSerialisationTest.cs index 962a6fbd6c..d18651b8bb 100644 --- a/osu.Game.Tournament.Tests/NonVisual/LadderInfoSerialisationTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/LadderInfoSerialisationTest.cs @@ -3,6 +3,7 @@ using Newtonsoft.Json; using NUnit.Framework; +using osu.Framework.Extensions.ObjectExtensions; using osu.Game.Tournament.Models; namespace osu.Game.Tournament.Tests.NonVisual @@ -35,8 +36,8 @@ namespace osu.Game.Tournament.Tests.NonVisual PlayersPerTeam = { Value = 4 }, Teams = { - match.Team1.Value, - match.Team2.Value, + match.Team1.Value.AsNonNull(), + match.Team2.Value.AsNonNull(), }, Rounds = { diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneLadderEditorScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneLadderEditorScreen.cs index 29ecdaf873..b9b150d264 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneLadderEditorScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneLadderEditorScreen.cs @@ -94,7 +94,7 @@ namespace osu.Game.Tournament.Tests.Screens AddStep("release mouse button", () => InputManager.ReleaseButton(MouseButton.Left)); - AddAssert("assert ladder teams reset", () => Ladder.CurrentMatch.Value.Team1.Value == null && Ladder.CurrentMatch.Value.Team2.Value == null); + AddAssert("assert ladder teams reset", () => Ladder.CurrentMatch.Value?.Team1.Value == null && Ladder.CurrentMatch.Value?.Team2.Value == null); } } } diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs index 15d4fb1f3f..a7ef4d7a29 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Game.Tournament.Models; using osu.Game.Tournament.Screens.Editors; @@ -17,7 +18,7 @@ namespace osu.Game.Tournament.Tests.Screens { var match = CreateSampleMatch(); - Add(new SeedingEditorScreen(match.Team1.Value, new TeamEditorScreen()) + Add(new SeedingEditorScreen(match.Team1.Value.AsNonNull(), new TeamEditorScreen()) { Width = 0.85f // create room for control panel }); diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs index b76e0d7521..d6941848b7 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tournament.Tests.Screens { Team1 = { Value = Ladder.Teams.FirstOrDefault(t => t.Acronym.Value == "USA") }, Team2 = { Value = Ladder.Teams.FirstOrDefault(t => t.Acronym.Value == "JPN") }, - Round = { Value = Ladder.Rounds.FirstOrDefault(g => g.Name.Value == "Finals") } + Round = { Value = Ladder.Rounds.First(g => g.Name.Value == "Quarterfinals") } }; Add(new TeamIntroScreen diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs index 05c21fa0b1..1e3ff72d43 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs @@ -17,7 +17,7 @@ namespace osu.Game.Tournament.Tests.Screens { var match = Ladder.CurrentMatch.Value!; - match.Round.Value = Ladder.Rounds.FirstOrDefault(g => g.Name.Value == "Finals"); + match.Round.Value = Ladder.Rounds.First(g => g.Name.Value == "Quarterfinals"); match.Completed.Value = true; }); diff --git a/osu.Game.Tournament.Tests/TournamentTestScene.cs b/osu.Game.Tournament.Tests/TournamentTestScene.cs index 84e5da62f4..0cf1e22211 100644 --- a/osu.Game.Tournament.Tests/TournamentTestScene.cs +++ b/osu.Game.Tournament.Tests/TournamentTestScene.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading; using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Framework.Utils; @@ -40,10 +41,10 @@ namespace osu.Game.Tournament.Tests match = CreateSampleMatch(); - Ladder.Rounds.Add(match.Round.Value); + Ladder.Rounds.Add(match.Round.Value.AsNonNull()); Ladder.Matches.Add(match); - Ladder.Teams.Add(match.Team1.Value); - Ladder.Teams.Add(match.Team2.Value); + Ladder.Teams.Add(match.Team1.Value.AsNonNull()); + Ladder.Teams.Add(match.Team2.Value.AsNonNull()); Ruleset.BindTo(Ladder.Ruleset); Dependencies.CacheAs(new StableInfo(storage)); @@ -152,7 +153,7 @@ namespace osu.Game.Tournament.Tests }, Round = { - Value = new TournamentRound { Name = { Value = "Quarterfinals" } } + Value = new TournamentRound { Name = { Value = "Quarterfinals" } }, } }; diff --git a/osu.Game.Tournament/Components/DrawableTeamFlag.cs b/osu.Game.Tournament/Components/DrawableTeamFlag.cs index 317d685ee7..aef854bb8d 100644 --- a/osu.Game.Tournament/Components/DrawableTeamFlag.cs +++ b/osu.Game.Tournament/Components/DrawableTeamFlag.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 JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -17,14 +15,14 @@ namespace osu.Game.Tournament.Components { public partial class DrawableTeamFlag : Container { - private readonly TournamentTeam team; + private readonly TournamentTeam? team; [UsedImplicitly] - private Bindable flag; + private Bindable? flag; - private Sprite flagSprite; + private Sprite? flagSprite; - public DrawableTeamFlag(TournamentTeam team) + public DrawableTeamFlag(TournamentTeam? team) { this.team = team; } diff --git a/osu.Game.Tournament/Components/DrawableTeamTitle.cs b/osu.Game.Tournament/Components/DrawableTeamTitle.cs index 68cc46be19..85b3e5419c 100644 --- a/osu.Game.Tournament/Components/DrawableTeamTitle.cs +++ b/osu.Game.Tournament/Components/DrawableTeamTitle.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 JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -12,12 +10,12 @@ namespace osu.Game.Tournament.Components { public partial class DrawableTeamTitle : TournamentSpriteTextWithBackground { - private readonly TournamentTeam team; + private readonly TournamentTeam? team; [UsedImplicitly] - private Bindable acronym; + private Bindable? acronym; - public DrawableTeamTitle(TournamentTeam team) + public DrawableTeamTitle(TournamentTeam? team) { this.team = team; } diff --git a/osu.Game.Tournament/Components/DrawableTournamentTeam.cs b/osu.Game.Tournament/Components/DrawableTournamentTeam.cs index 0036f5f115..9583682e8b 100644 --- a/osu.Game.Tournament/Components/DrawableTournamentTeam.cs +++ b/osu.Game.Tournament/Components/DrawableTournamentTeam.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 JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -14,15 +12,15 @@ namespace osu.Game.Tournament.Components { public abstract partial class DrawableTournamentTeam : CompositeDrawable { - public readonly TournamentTeam Team; + public readonly TournamentTeam? Team; protected readonly Container Flag; protected readonly TournamentSpriteText AcronymText; [UsedImplicitly] - private Bindable acronym; + private Bindable? acronym; - protected DrawableTournamentTeam(TournamentTeam team) + protected DrawableTournamentTeam(TournamentTeam? team) { Team = team; @@ -36,7 +34,8 @@ namespace osu.Game.Tournament.Components [BackgroundDependencyLoader] private void load() { - if (Team == null) return; + if (Team == null) + return; (acronym = Team.Acronym.GetBoundCopy()).BindValueChanged(_ => AcronymText.Text = Team?.Acronym.Value?.ToUpperInvariant() ?? string.Empty, true); } diff --git a/osu.Game.Tournament/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs index 4f4a02ccf1..19d63286d8 100644 --- a/osu.Game.Tournament/Components/SongBar.cs +++ b/osu.Game.Tournament/Components/SongBar.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.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -24,12 +22,12 @@ namespace osu.Game.Tournament.Components { public partial class SongBar : CompositeDrawable { - private TournamentBeatmap beatmap; + private TournamentBeatmap? beatmap; public const float HEIGHT = 145 / 2f; [Resolved] - private IBindable ruleset { get; set; } + private IBindable ruleset { get; set; } = null!; public TournamentBeatmap Beatmap { @@ -55,7 +53,7 @@ namespace osu.Game.Tournament.Components } } - private FillFlowContainer flow; + private FillFlowContainer flow = null!; private bool expanded; diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs index 49478a2174..2d5844d02b 100644 --- a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs +++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs @@ -22,13 +22,13 @@ namespace osu.Game.Tournament.Components { public readonly TournamentBeatmap? Beatmap; - private readonly string mod; + private readonly string? mod; public const float HEIGHT = 50; private readonly Bindable currentMatch = new Bindable(); - private Box flash = null!; + private Box? flash; public TournamentBeatmapPanel(TournamentBeatmap? beatmap, string mod = "") { @@ -135,25 +135,29 @@ namespace osu.Game.Tournament.Components match.OldValue.PicksBans.CollectionChanged -= picksBansOnCollectionChanged; if (match.NewValue != null) match.NewValue.PicksBans.CollectionChanged += picksBansOnCollectionChanged; - - Scheduler.AddOnce(updateState); + updateState(); } private void picksBansOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) - => Scheduler.AddOnce(updateState); + => updateState(); private BeatmapChoice? choice; private void updateState() { - var newChoice = currentMatch.Value?.PicksBans.FirstOrDefault(p => p.BeatmapID == Beatmap?.OnlineID); + if (currentMatch.Value == null) + { + return; + } + + var newChoice = currentMatch.Value.PicksBans.FirstOrDefault(p => p.BeatmapID == Beatmap?.OnlineID); bool shouldFlash = newChoice != choice; if (newChoice != null) { if (shouldFlash) - flash.FadeOutFromOne(500).Loop(0, 10); + flash?.FadeOutFromOne(500).Loop(0, 10); BorderThickness = 6; diff --git a/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs b/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs index e943cb8b8c..0998e606e9 100644 --- a/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs +++ b/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs @@ -92,9 +92,9 @@ namespace osu.Game.Tournament.Components { if (info.CurrentMatch.Value is TournamentMatch match) { - if (match.Team1.Value.Players.Any(u => u.OnlineID == Message.Sender.OnlineID)) + if (match.Team1.Value?.Players.Any(u => u.OnlineID == Message.Sender.OnlineID) == true) UsernameColour = TournamentGame.COLOUR_RED; - else if (match.Team2.Value.Players.Any(u => u.OnlineID == Message.Sender.OnlineID)) + else if (match.Team2.Value?.Players.Any(u => u.OnlineID == Message.Sender.OnlineID) == true) UsernameColour = TournamentGame.COLOUR_BLUE; } } diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs index b9ce84b735..6e45c7556b 100644 --- a/osu.Game.Tournament/Components/TourneyVideo.cs +++ b/osu.Game.Tournament/Components/TourneyVideo.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.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -19,8 +17,8 @@ namespace osu.Game.Tournament.Components { private readonly string filename; private readonly bool drawFallbackGradient; - private Video video; - private ManualClock manualClock; + private Video? video; + private ManualClock? manualClock; public bool VideoAvailable => video != null; diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index 7babb3ea5a..333dd0fd73 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.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.IO; using System.Linq; -using JetBrains.Annotations; using Microsoft.Win32; using osu.Framework.Allocation; using osu.Framework.Extensions.ObjectExtensions; @@ -24,36 +21,35 @@ namespace osu.Game.Tournament.IPC { public partial class FileBasedIPC : MatchIPCInfo { - public Storage IPCStorage { get; private set; } + public Storage? IPCStorage { get; private set; } [Resolved] - protected IAPIProvider API { get; private set; } + protected IAPIProvider API { get; private set; } = null!; [Resolved] - protected IRulesetStore Rulesets { get; private set; } + protected IRulesetStore Rulesets { get; private set; } = null!; [Resolved] - private GameHost host { get; set; } + private GameHost host { get; set; } = null!; [Resolved] - private LadderInfo ladder { get; set; } + private LadderInfo ladder { get; set; } = null!; [Resolved] - private StableInfo stableInfo { get; set; } + private StableInfo stableInfo { get; set; } = null!; private int lastBeatmapId; - private ScheduledDelegate scheduled; - private GetBeatmapRequest beatmapLookupRequest; + private ScheduledDelegate? scheduled; + private GetBeatmapRequest? beatmapLookupRequest; [BackgroundDependencyLoader] private void load() { - string stablePath = stableInfo.StablePath ?? findStablePath(); + string? stablePath = stableInfo.StablePath ?? findStablePath(); initialiseIPCStorage(stablePath); } - [CanBeNull] - private Storage initialiseIPCStorage(string path) + private Storage? initialiseIPCStorage(string? path) { scheduled?.Cancel(); @@ -89,9 +85,9 @@ namespace osu.Game.Tournament.IPC lastBeatmapId = beatmapId; - var existing = ladder.CurrentMatch.Value?.Round.Value?.Beatmaps.FirstOrDefault(b => b.ID == beatmapId && b.Beatmap != null); + var existing = ladder.CurrentMatch.Value?.Round.Value?.Beatmaps.FirstOrDefault(b => b.ID == beatmapId); - if (existing != null) + if (existing?.Beatmap != null) Beatmap.Value = existing.Beatmap; else { @@ -114,7 +110,7 @@ namespace osu.Game.Tournament.IPC using (var stream = IPCStorage.GetStream(file_ipc_channel_filename)) using (var sr = new StreamReader(stream)) { - ChatChannel.Value = sr.ReadLine(); + ChatChannel.Value = sr.ReadLine().AsNonNull(); } } catch (Exception) @@ -140,8 +136,8 @@ namespace osu.Game.Tournament.IPC using (var stream = IPCStorage.GetStream(file_ipc_scores_filename)) using (var sr = new StreamReader(stream)) { - Score1.Value = int.Parse(sr.ReadLine()); - Score2.Value = int.Parse(sr.ReadLine()); + Score1.Value = int.Parse(sr.ReadLine().AsNonNull()); + Score2.Value = int.Parse(sr.ReadLine().AsNonNull()); } } catch (Exception) @@ -164,7 +160,7 @@ namespace osu.Game.Tournament.IPC /// /// Path to the IPC directory /// Whether the supplied path was a valid IPC directory. - public bool SetIPCLocation(string path) + public bool SetIPCLocation(string? path) { if (path == null || !ipcFileExistsInDirectory(path)) return false; @@ -184,29 +180,28 @@ namespace osu.Game.Tournament.IPC /// Whether an IPC directory was successfully auto-detected. public bool AutoDetectIPCLocation() => SetIPCLocation(findStablePath()); - private static bool ipcFileExistsInDirectory(string p) => p != null && File.Exists(Path.Combine(p, "ipc.txt")); + private static bool ipcFileExistsInDirectory(string? p) => p != null && File.Exists(Path.Combine(p, "ipc.txt")); - [CanBeNull] - private string findStablePath() + private string? findStablePath() { - string stableInstallPath = findFromEnvVar() ?? - findFromRegistry() ?? - findFromLocalAppData() ?? - findFromDotFolder(); + string? stableInstallPath = findFromEnvVar() ?? + findFromRegistry() ?? + findFromLocalAppData() ?? + findFromDotFolder(); Logger.Log($"Stable path for tourney usage: {stableInstallPath}"); return stableInstallPath; } - private string findFromEnvVar() + private string? findFromEnvVar() { try { Logger.Log("Trying to find stable with environment variables"); - string stableInstallPath = Environment.GetEnvironmentVariable("OSU_STABLE_PATH"); + string? stableInstallPath = Environment.GetEnvironmentVariable("OSU_STABLE_PATH"); if (ipcFileExistsInDirectory(stableInstallPath)) - return stableInstallPath; + return stableInstallPath!; } catch { @@ -215,7 +210,7 @@ namespace osu.Game.Tournament.IPC return null; } - private string findFromLocalAppData() + private string? findFromLocalAppData() { Logger.Log("Trying to find stable in %LOCALAPPDATA%"); string stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!"); @@ -226,7 +221,7 @@ namespace osu.Game.Tournament.IPC return null; } - private string findFromDotFolder() + private string? findFromDotFolder() { Logger.Log("Trying to find stable in dotfolders"); string stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".osu"); @@ -237,16 +232,16 @@ namespace osu.Game.Tournament.IPC return null; } - private string findFromRegistry() + private string? findFromRegistry() { Logger.Log("Trying to find stable in registry"); try { - string stableInstallPath; + string? stableInstallPath; #pragma warning disable CA1416 - using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu")) + using (RegistryKey? key = Registry.ClassesRoot.OpenSubKey("osu")) stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty)?.ToString()?.Split('"')[1].Replace("osu!.exe", ""); #pragma warning restore CA1416 diff --git a/osu.Game.Tournament/JsonPointConverter.cs b/osu.Game.Tournament/JsonPointConverter.cs index d3b40a3526..a58ec47612 100644 --- a/osu.Game.Tournament/JsonPointConverter.cs +++ b/osu.Game.Tournament/JsonPointConverter.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Diagnostics; using System.Drawing; @@ -28,7 +26,7 @@ namespace osu.Game.Tournament if (reader.TokenType != JsonToken.StartObject) { // if there's no object present then this is using string representation (System.Drawing.Point serializes to "x,y") - string str = (string)reader.Value; + string? str = (string?)reader.Value; Debug.Assert(str != null); @@ -45,9 +43,12 @@ namespace osu.Game.Tournament if (reader.TokenType == JsonToken.PropertyName) { - string name = reader.Value?.ToString(); + string? name = reader.Value?.ToString(); int? val = reader.ReadAsInt32(); + if (name == null) + continue; + if (val == null) continue; diff --git a/osu.Game.Tournament/Models/LadderInfo.cs b/osu.Game.Tournament/Models/LadderInfo.cs index 229837c94e..219a2a7bfb 100644 --- a/osu.Game.Tournament/Models/LadderInfo.cs +++ b/osu.Game.Tournament/Models/LadderInfo.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using Newtonsoft.Json; @@ -17,7 +15,7 @@ namespace osu.Game.Tournament.Models [Serializable] public class LadderInfo { - public Bindable Ruleset = new Bindable(); + public Bindable Ruleset = new Bindable(); public BindableList Matches = new BindableList(); public BindableList Rounds = new BindableList(); @@ -27,7 +25,7 @@ namespace osu.Game.Tournament.Models public List Progressions = new List(); [JsonIgnore] // updated manually in TournamentGameBase - public Bindable CurrentMatch = new Bindable(); + public Bindable CurrentMatch = new Bindable(); public Bindable ChromaKeyWidth = new BindableInt(1024) { diff --git a/osu.Game.Tournament/Models/RoundBeatmap.cs b/osu.Game.Tournament/Models/RoundBeatmap.cs index f2ec261246..b03b28b3b8 100644 --- a/osu.Game.Tournament/Models/RoundBeatmap.cs +++ b/osu.Game.Tournament/Models/RoundBeatmap.cs @@ -8,7 +8,6 @@ namespace osu.Game.Tournament.Models public class RoundBeatmap { public int ID; - public string Mods = string.Empty; [JsonProperty("BeatmapInfo")] diff --git a/osu.Game.Tournament/Models/StableInfo.cs b/osu.Game.Tournament/Models/StableInfo.cs index 1ae80d4596..7ee0b4a361 100644 --- a/osu.Game.Tournament/Models/StableInfo.cs +++ b/osu.Game.Tournament/Models/StableInfo.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.IO; using Newtonsoft.Json; @@ -20,12 +18,12 @@ namespace osu.Game.Tournament.Models /// /// Path to the IPC directory used by the stable (cutting-edge) install. /// - public string StablePath { get; set; } + public string? StablePath { get; set; } /// /// Fired whenever stable info is successfully saved to file. /// - public event Action OnStableInfoSaved; + public event Action? OnStableInfoSaved; private const string config_path = "stable.json"; diff --git a/osu.Game.Tournament/Models/TournamentMatch.cs b/osu.Game.Tournament/Models/TournamentMatch.cs index 97c2060f2c..0a700eb4d6 100644 --- a/osu.Game.Tournament/Models/TournamentMatch.cs +++ b/osu.Game.Tournament/Models/TournamentMatch.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -33,16 +31,16 @@ namespace osu.Game.Tournament.Models } [JsonIgnore] - public readonly Bindable Team1 = new Bindable(); + public readonly Bindable Team1 = new Bindable(); - public string Team1Acronym; + public string? Team1Acronym; public readonly Bindable Team1Score = new Bindable(); [JsonIgnore] - public readonly Bindable Team2 = new Bindable(); + public readonly Bindable Team2 = new Bindable(); - public string Team2Acronym; + public string? Team2Acronym; public readonly Bindable Team2Score = new Bindable(); @@ -53,13 +51,13 @@ namespace osu.Game.Tournament.Models public readonly ObservableCollection PicksBans = new ObservableCollection(); [JsonIgnore] - public readonly Bindable Round = new Bindable(); + public readonly Bindable Round = new Bindable(); [JsonIgnore] - public readonly Bindable Progression = new Bindable(); + public readonly Bindable Progression = new Bindable(); [JsonIgnore] - public readonly Bindable LosersProgression = new Bindable(); + public readonly Bindable LosersProgression = new Bindable(); /// /// Should not be set directly. Use LadderInfo.CurrentMatch.Value = this instead. @@ -79,7 +77,7 @@ namespace osu.Game.Tournament.Models Team2.BindValueChanged(t => Team2Acronym = t.NewValue?.Acronym.Value, true); } - public TournamentMatch(TournamentTeam team1 = null, TournamentTeam team2 = null) + public TournamentMatch(TournamentTeam? team1 = null, TournamentTeam? team2 = null) : this() { Team1.Value = team1; @@ -87,10 +85,10 @@ namespace osu.Game.Tournament.Models } [JsonIgnore] - public TournamentTeam Winner => !Completed.Value ? null : Team1Score.Value > Team2Score.Value ? Team1.Value : Team2.Value; + public TournamentTeam? Winner => !Completed.Value ? null : Team1Score.Value > Team2Score.Value ? Team1.Value : Team2.Value; [JsonIgnore] - public TournamentTeam Loser => !Completed.Value ? null : Team1Score.Value > Team2Score.Value ? Team2.Value : Team1.Value; + public TournamentTeam? Loser => !Completed.Value ? null : Team1Score.Value > Team2Score.Value ? Team2.Value : Team1.Value; public TeamColour WinnerColour => Winner == Team1.Value ? TeamColour.Red : TeamColour.Blue; diff --git a/osu.Game.Tournament/Models/TournamentTeam.cs b/osu.Game.Tournament/Models/TournamentTeam.cs index 1beea517d5..3587aa937e 100644 --- a/osu.Game.Tournament/Models/TournamentTeam.cs +++ b/osu.Game.Tournament/Models/TournamentTeam.cs @@ -1,12 +1,11 @@ // 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.Linq; using Newtonsoft.Json; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; namespace osu.Game.Tournament.Models { @@ -39,7 +38,7 @@ namespace osu.Game.Tournament.Models { int[] ranks = Players.Select(p => p.Rank) .Where(i => i.HasValue) - .Select(i => i.Value) + .Select(i => i.AsNonNull().Value) .ToArray(); if (ranks.Length == 0) @@ -66,14 +65,14 @@ namespace osu.Game.Tournament.Models { // use a sane default flag name based on acronym. if (val.OldValue.StartsWith(FlagName.Value, StringComparison.InvariantCultureIgnoreCase)) - FlagName.Value = val.NewValue.Length >= 2 ? val.NewValue?.Substring(0, 2).ToUpperInvariant() : string.Empty; + FlagName.Value = val.NewValue?.Length >= 2 ? val.NewValue.Substring(0, 2).ToUpperInvariant() : string.Empty; }; FullName.ValueChanged += val => { // use a sane acronym based on full name. if (val.OldValue.StartsWith(Acronym.Value, StringComparison.InvariantCultureIgnoreCase)) - Acronym.Value = val.NewValue.Length >= 3 ? val.NewValue?.Substring(0, 3).ToUpperInvariant() : string.Empty; + Acronym.Value = val.NewValue?.Length >= 3 ? val.NewValue.Substring(0, 3).ToUpperInvariant() : string.Empty; }; } diff --git a/osu.Game.Tournament/Screens/Drawings/Components/Group.cs b/osu.Game.Tournament/Screens/Drawings/Components/Group.cs index a79e2253a4..9d4474a58c 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/Group.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/Group.cs @@ -84,7 +84,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components public bool ContainsTeam(string fullName) { - return allTeams.Any(t => t.Team.FullName.Value == fullName); + return allTeams.Any(t => t.Team?.FullName.Value == fullName); } public bool RemoveTeam(TournamentTeam team) @@ -112,7 +112,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components { StringBuilder sb = new StringBuilder(); foreach (GroupTeam gt in allTeams) - sb.AppendLine(gt.Team.FullName.Value); + sb.AppendLine(gt.Team?.FullName.Value); return sb.ToString(); } } diff --git a/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs index c2b15dd3e9..4a47de8714 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs @@ -1,12 +1,11 @@ // 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.Diagnostics; using System.Linq; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -22,8 +21,8 @@ namespace osu.Game.Tournament.Screens.Drawings.Components { public partial class ScrollingTeamContainer : Container { - public event Action OnScrollStarted; - public event Action OnSelected; + public event Action? OnScrollStarted; + public event Action? OnSelected; private readonly List availableTeams = new List(); @@ -42,7 +41,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components private double lastTime; - private ScheduledDelegate delayedStateChangeDelegate; + private ScheduledDelegate? delayedStateChangeDelegate; public ScrollingTeamContainer() { @@ -117,7 +116,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components if (!Children.Any()) break; - ScrollingTeam closest = null; + ScrollingTeam? closest = null; foreach (var c in Children) { @@ -139,15 +138,14 @@ namespace osu.Game.Tournament.Screens.Drawings.Components Trace.Assert(closest != null, "closest != null"); - // ReSharper disable once PossibleNullReferenceException - offset += DrawWidth / 2f - (closest.Position.X + closest.DrawWidth / 2f); + offset += DrawWidth / 2f - (closest.AsNonNull().Position.X + closest.AsNonNull().DrawWidth / 2f); - ScrollingTeam st = closest; + ScrollingTeam st = closest.AsNonNull(); availableTeams.RemoveAll(at => at == st.Team); st.Selected = true; - OnSelected?.Invoke(st.Team); + OnSelected?.Invoke(st.Team.AsNonNull()); delayedStateChangeDelegate = Scheduler.AddDelayed(() => setScrollState(ScrollState.Idle), 10000); break; @@ -174,7 +172,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components setScrollState(ScrollState.Idle); } - public void AddTeams(IEnumerable teams) + public void AddTeams(IEnumerable? teams) { if (teams == null) return; @@ -190,8 +188,11 @@ namespace osu.Game.Tournament.Screens.Drawings.Components setScrollState(ScrollState.Idle); } - public void RemoveTeam(TournamentTeam team) + public void RemoveTeam(TournamentTeam? team) { + if (team == null) + return; + availableTeams.Remove(team); foreach (var c in Children) @@ -311,6 +312,8 @@ namespace osu.Game.Tournament.Screens.Drawings.Components public partial class ScrollingTeam : DrawableTournamentTeam { + public new TournamentTeam Team => base.Team.AsNonNull(); + public const float WIDTH = 58; public const float HEIGHT = 44; diff --git a/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs b/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs index 74afb42c1a..e13462b9bd 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.IO; @@ -39,7 +37,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components { while (sr.Peek() != -1) { - string line = sr.ReadLine()?.Trim(); + string? line = sr.ReadLine()?.Trim(); if (string.IsNullOrEmpty(line)) continue; @@ -56,7 +54,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components teams.Add(new TournamentTeam { FullName = { Value = split[1], }, - Acronym = { Value = split.Length >= 3 ? split[2] : null, }, + Acronym = { Value = split.Length >= 3 ? split[2] : string.Empty, }, FlagName = { Value = split[0] } }); } diff --git a/osu.Game.Tournament/Screens/Drawings/Components/VisualiserContainer.cs b/osu.Game.Tournament/Screens/Drawings/Components/VisualiserContainer.cs index 676eec14cd..d5e39e3f44 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/VisualiserContainer.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/VisualiserContainer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -72,7 +70,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components private float leftPos => -(float)((Time.Current + Offset) / CycleTime) + expiredCount; - private Texture texture; + private Texture texture = null!; private int expiredCount; diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs index 23d0edf26e..b2dd4e8c36 100644 --- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -1,14 +1,13 @@ // 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.IO; using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -28,27 +27,27 @@ namespace osu.Game.Tournament.Screens.Drawings { private const string results_filename = "drawings_results.txt"; - private ScrollingTeamContainer teamsContainer; - private GroupContainer groupsContainer; - private TournamentSpriteText fullTeamNameText; + private ScrollingTeamContainer teamsContainer = null!; + private GroupContainer groupsContainer = null!; + private TournamentSpriteText fullTeamNameText = null!; private readonly List allTeams = new List(); - private DrawingsConfigManager drawingsConfig; + private DrawingsConfigManager drawingsConfig = null!; - private Task writeOp; + private Task? writeOp; - private Storage storage; + private Storage storage = null!; - public ITeamList TeamList; + public ITeamList? TeamList; [BackgroundDependencyLoader] private void load(Storage storage) { - RelativeSizeAxes = Axes.Both; - this.storage = storage; + RelativeSizeAxes = Axes.Both; + TeamList ??= new StorageBackedTeamList(storage); if (!TeamList.Teams.Any()) @@ -224,7 +223,7 @@ namespace osu.Game.Tournament.Screens.Drawings teamsContainer.ClearTeams(); allTeams.Clear(); - foreach (TournamentTeam t in TeamList.Teams) + foreach (TournamentTeam t in TeamList.AsNonNull().Teams) { if (groupsContainer.ContainsTeam(t.FullName.Value)) continue; @@ -251,7 +250,7 @@ namespace osu.Game.Tournament.Screens.Drawings using (Stream stream = storage.GetStream(results_filename, FileAccess.Read, FileMode.Open)) using (StreamReader sr = new StreamReader(stream)) { - string line; + string? line; while ((line = sr.ReadLine()?.Trim()) != null) { @@ -261,8 +260,7 @@ namespace osu.Game.Tournament.Screens.Drawings if (line.ToUpperInvariant().StartsWith("GROUP", StringComparison.Ordinal)) continue; - // ReSharper disable once AccessToModifiedClosure - TournamentTeam teamToAdd = allTeams.FirstOrDefault(t => t.FullName.Value == line); + TournamentTeam? teamToAdd = allTeams.FirstOrDefault(t => t.FullName.Value == line); if (teamToAdd == null) continue; diff --git a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs index 9411892dc5..a82ac57ee9 100644 --- a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.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.Drawing; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -35,13 +32,12 @@ namespace osu.Game.Tournament.Screens.Editors [Cached] private LadderEditorInfo editorInfo = new LadderEditorInfo(); - private WarningBox rightClickMessage; + private WarningBox rightClickMessage = null!; - private RectangularPositionSnapGrid grid; + private RectangularPositionSnapGrid grid = null!; [Resolved(canBeNull: true)] - [CanBeNull] - private IDialogOverlay dialogOverlay { get; set; } + private IDialogOverlay? dialogOverlay { get; set; } protected override bool DrawLoserPaths => true; @@ -94,36 +90,28 @@ namespace osu.Game.Tournament.Screens.Editors ScrollContent.Add(new JoinVisualiser(MatchesContainer, match, losers, UpdateLayout)); } - public MenuItem[] ContextMenuItems - { - get + public MenuItem[] ContextMenuItems => + new MenuItem[] { - if (editorInfo == null) - return Array.Empty(); - - return new MenuItem[] + new OsuMenuItem("Create new match", MenuItemType.Highlighted, () => { - new OsuMenuItem("Create new match", MenuItemType.Highlighted, () => + Vector2 pos = MatchesContainer.Count == 0 ? Vector2.Zero : lastMatchesContainerMouseDownPosition; + + TournamentMatch newMatch = new TournamentMatch { Position = { Value = new Point((int)pos.X, (int)pos.Y) } }; + + LadderInfo.Matches.Add(newMatch); + + editorInfo.Selected.Value = newMatch; + }), + new OsuMenuItem("Reset teams", MenuItemType.Destructive, () => + { + dialogOverlay?.Push(new LadderResetTeamsDialog(() => { - Vector2 pos = MatchesContainer.Count == 0 ? Vector2.Zero : lastMatchesContainerMouseDownPosition; - - TournamentMatch newMatch = new TournamentMatch { Position = { Value = new Point((int)pos.X, (int)pos.Y) } }; - - LadderInfo.Matches.Add(newMatch); - - editorInfo.Selected.Value = newMatch; - }), - new OsuMenuItem("Reset teams", MenuItemType.Destructive, () => - { - dialogOverlay?.Push(new LadderResetTeamsDialog(() => - { - foreach (var p in MatchesContainer) - p.Match.Reset(); - })); - }) - }; - } - } + foreach (var p in MatchesContainer) + p.Match.Reset(); + })); + }) + }; public void Remove(TournamentMatch match) { @@ -135,11 +123,11 @@ namespace osu.Game.Tournament.Screens.Editors private readonly Container matchesContainer; public readonly TournamentMatch Source; private readonly bool losers; - private readonly Action complete; + private readonly Action? complete; - private ProgressionPath path; + private ProgressionPath? path; - public JoinVisualiser(Container matchesContainer, TournamentMatch source, bool losers, Action complete) + public JoinVisualiser(Container matchesContainer, TournamentMatch source, bool losers, Action? complete) { this.matchesContainer = matchesContainer; RelativeSizeAxes = Axes.Both; @@ -153,7 +141,7 @@ namespace osu.Game.Tournament.Screens.Editors Source.Progression.Value = null; } - private DrawableTournamentMatch findTarget(InputState state) + private DrawableTournamentMatch? findTarget(InputState state) { return matchesContainer.FirstOrDefault(d => d.ReceivePositionalInputAt(state.Mouse.Position)); } diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs b/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs index 8f7484980d..69f150c8ac 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.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.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -14,9 +12,9 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components { public partial class MatchHeader : Container { - private TeamScoreDisplay teamDisplay1; - private TeamScoreDisplay teamDisplay2; - private DrawableTournamentHeaderLogo logo; + private TeamScoreDisplay teamDisplay1 = null!; + private TeamScoreDisplay teamDisplay2 = null!; + private DrawableTournamentHeaderLogo logo = null!; private bool showScores = true; diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs index c23327a43f..3fdbbb5973 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components } } - public TeamDisplay(TournamentTeam team, TeamColour colour, Bindable currentTeamScore, int pointsToWin) + public TeamDisplay(TournamentTeam? team, TeamColour colour, Bindable currentTeamScore, int pointsToWin) : base(team) { AutoSizeAxes = Axes.Both; diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs index 57fe1c7312..c7fcfae602 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.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.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -17,16 +15,22 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components { private readonly TeamColour teamColour; - private readonly Bindable currentMatch = new Bindable(); - private readonly Bindable currentTeam = new Bindable(); + private readonly Bindable currentMatch = new Bindable(); + private readonly Bindable currentTeam = new Bindable(); private readonly Bindable currentTeamScore = new Bindable(); - private TeamDisplay teamDisplay; + private TeamDisplay? teamDisplay; public bool ShowScore { - get => teamDisplay.ShowScore; - set => teamDisplay.ShowScore = value; + get => teamDisplay?.ShowScore ?? false; + set + { + if (teamDisplay != null) + { + teamDisplay.ShowScore = value; + } + } } public TeamScoreDisplay(TeamColour teamColour) @@ -48,7 +52,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components updateMatch(); } - private void matchChanged(ValueChangedEvent match) + private void matchChanged(ValueChangedEvent match) { currentTeamScore.UnbindBindings(); currentTeam.UnbindBindings(); @@ -78,7 +82,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components switch (e.Button) { case MouseButton.Left: - if (currentTeamScore.Value < currentMatch.Value.PointsToWin) + if (currentTeamScore.Value < currentMatch.Value?.PointsToWin) currentTeamScore.Value++; return true; @@ -91,7 +95,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components return base.OnMouseDown(e); } - private void teamChanged(ValueChangedEvent team) + private void teamChanged(ValueChangedEvent team) { bool wasShowingScores = teamDisplay?.ShowScore ?? false; diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/TournamentMatchScoreDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/TournamentMatchScoreDisplay.cs index 838e5fa071..7ae20acc77 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/TournamentMatchScoreDisplay.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/TournamentMatchScoreDisplay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -146,7 +144,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components private partial class MatchScoreCounter : CommaSeparatedScoreCounter { - private OsuSpriteText displayedSpriteText; + private OsuSpriteText displayedSpriteText = null!; public MatchScoreCounter() { diff --git a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs index 4258522cff..79c50e60ba 100644 --- a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs +++ b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.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.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -26,16 +24,16 @@ namespace osu.Game.Tournament.Screens.Gameplay private readonly BindableBool warmup = new BindableBool(); public readonly Bindable State = new Bindable(); - private OsuButton warmupButton; - private MatchIPCInfo ipc; + private OsuButton warmupButton = null!; + private MatchIPCInfo ipc = null!; [Resolved(canBeNull: true)] - private TournamentSceneManager sceneManager { get; set; } + private TournamentSceneManager? sceneManager { get; set; } [Resolved] - private TournamentMatchChatDisplay chat { get; set; } + private TournamentMatchChatDisplay chat { get; set; } = null!; - private Drawable chroma; + private Drawable chroma = null!; [BackgroundDependencyLoader] private void load(LadderInfo ladder, MatchIPCInfo ipc) @@ -142,7 +140,7 @@ namespace osu.Game.Tournament.Screens.Gameplay State.BindValueChanged(_ => updateState(), true); } - protected override void CurrentMatchChanged(ValueChangedEvent match) + protected override void CurrentMatchChanged(ValueChangedEvent match) { base.CurrentMatchChanged(match); @@ -153,29 +151,35 @@ namespace osu.Game.Tournament.Screens.Gameplay scheduledScreenChange?.Cancel(); } - private ScheduledDelegate scheduledScreenChange; - private ScheduledDelegate scheduledContract; + private ScheduledDelegate? scheduledScreenChange; + private ScheduledDelegate? scheduledContract; - private TournamentMatchScoreDisplay scoreDisplay; + private TournamentMatchScoreDisplay scoreDisplay = null!; private TourneyState lastState; - private MatchHeader header; + private MatchHeader header = null!; private void contract() { + if (!IsLoaded) + return; + scheduledContract?.Cancel(); SongBar.Expanded = false; scoreDisplay.FadeOut(100); - using (chat?.BeginDelayedSequence(500)) - chat?.Expand(); + using (chat.BeginDelayedSequence(500)) + chat.Expand(); } private void expand() { + if (!IsLoaded) + return; + scheduledContract?.Cancel(); - chat?.Contract(); + chat.Contract(); using (BeginDelayedSequence(300)) { @@ -252,7 +256,7 @@ namespace osu.Game.Tournament.Screens.Gameplay private partial class ChromaArea : CompositeDrawable { [Resolved] - private LadderInfo ladder { get; set; } + private LadderInfo ladder { get; set; } = null!; [BackgroundDependencyLoader] private void load() diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs index 2b66df1a31..a380ad9b49 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -28,20 +26,20 @@ namespace osu.Game.Tournament.Screens.Ladder.Components { private readonly TournamentMatch match; private readonly bool losers; - private TournamentSpriteText scoreText; - private Box background; - private Box backgroundRight; + private TournamentSpriteText scoreText = null!; + private Box background = null!; + private Box backgroundRight = null!; private readonly Bindable score = new Bindable(); private readonly BindableBool completed = new BindableBool(); private Color4 colourWinner; - private readonly Func isWinner; - private LadderEditorScreen ladderEditor; + private readonly Func? isWinner; + private LadderEditorScreen ladderEditor = null!; [Resolved(canBeNull: true)] - private LadderInfo ladderInfo { get; set; } + private LadderInfo? ladderInfo { get; set; } private void setCurrent() { @@ -56,9 +54,9 @@ namespace osu.Game.Tournament.Screens.Ladder.Components } [Resolved(CanBeNull = true)] - private LadderEditorInfo editorInfo { get; set; } + private LadderEditorInfo? editorInfo { get; set; } - public DrawableMatchTeam(TournamentTeam team, TournamentMatch match, bool losers) + public DrawableMatchTeam(TournamentTeam? team, TournamentMatch match, bool losers) : base(team) { this.match = match; @@ -72,14 +70,11 @@ namespace osu.Game.Tournament.Screens.Ladder.Components AcronymText.Padding = new MarginPadding { Left = 50 }; AcronymText.Font = OsuFont.Torus.With(size: 22, weight: FontWeight.Bold); - if (match != null) - { - isWinner = () => match.Winner == Team; + isWinner = () => match.Winner == Team; - completed.BindTo(match.Completed); - if (team != null) - score.BindTo(team == match.Team1.Value ? match.Team1Score : match.Team2Score); - } + completed.BindTo(match.Completed); + if (team != null) + score.BindTo(team == match.Team1.Value ? match.Team1Score : match.Team2Score); } [BackgroundDependencyLoader(true)] diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs index c394877ae9..7b8f2e373b 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs @@ -1,10 +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.Diagnostics; using System.Drawing; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -27,13 +26,13 @@ namespace osu.Game.Tournament.Screens.Ladder.Components protected readonly FillFlowContainer Flow; private readonly Drawable selectionBox; private readonly Drawable currentMatchSelectionBox; - private Bindable globalSelection; + private Bindable? globalSelection; [Resolved(CanBeNull = true)] - private LadderEditorInfo editorInfo { get; set; } + private LadderEditorInfo? editorInfo { get; set; } [Resolved(CanBeNull = true)] - private LadderInfo ladderInfo { get; set; } + private LadderInfo? ladderInfo { get; set; } public DrawableTournamentMatch(TournamentMatch match, bool editor = false) { @@ -129,7 +128,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components /// /// Fired when something changed that requires a ladder redraw. /// - public Action Changed; + public Action? Changed; private readonly List refBindables = new List(); @@ -201,20 +200,22 @@ namespace osu.Game.Tournament.Screens.Ladder.Components } else { - transferProgression(Match.Progression?.Value, Match.Winner); - transferProgression(Match.LosersProgression?.Value, Match.Loser); + Debug.Assert(Match.Winner != null); + transferProgression(Match.Progression.Value, Match.Winner); + Debug.Assert(Match.Loser != null); + transferProgression(Match.LosersProgression.Value, Match.Loser); } Changed?.Invoke(); } - private void transferProgression(TournamentMatch destination, TournamentTeam team) + private void transferProgression(TournamentMatch? destination, TournamentTeam team) { if (destination == null) return; bool progressionAbove = destination.ID < Match.ID; - Bindable destinationTeam; + Bindable destinationTeam; // check for the case where we have already transferred out value if (destination.Team1.Value == team) @@ -268,8 +269,8 @@ namespace osu.Game.Tournament.Screens.Ladder.Components { foreach (var conditional in Match.ConditionalMatches) { - bool team1Match = conditional.Acronyms.Contains(Match.Team1Acronym); - bool team2Match = conditional.Acronyms.Contains(Match.Team2Acronym); + bool team1Match = Match.Team1Acronym != null && conditional.Acronyms.Contains(Match.Team1Acronym); + bool team2Match = Match.Team2Acronym != null && conditional.Acronyms.Contains(Match.Team2Acronym); if (team1Match && team2Match) Match.Date.Value = conditional.Date.Value; @@ -344,6 +345,9 @@ namespace osu.Game.Tournament.Screens.Ladder.Components Match.Progression.Value = null; Match.LosersProgression.Value = null; + if (ladderInfo == null) + return; + ladderInfo.Matches.Remove(Match); foreach (var m in ladderInfo.Matches) diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs index 4b2a29247b..216e0a5a3e 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.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 JetBrains.Annotations; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -52,7 +50,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components name.BindValueChanged(_ => textName.Text = ((losers ? "Losers " : "") + round.Name).ToUpperInvariant(), true); description = round.Description.GetBoundCopy(); - description.BindValueChanged(_ => textDescription.Text = round.Description.Value?.ToUpperInvariant(), true); + description.BindValueChanged(_ => textDescription.Text = round.Description.Value?.ToUpperInvariant() ?? string.Empty, true); } } } diff --git a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs index 5c9c14cc30..9f0fa19915 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; @@ -23,17 +21,17 @@ namespace osu.Game.Tournament.Screens.Ladder.Components { public partial class LadderEditorSettings : CompositeDrawable { - private SettingsDropdown roundDropdown; - private PlayerCheckbox losersCheckbox; - private DateTextBox dateTimeBox; - private SettingsTeamDropdown team1Dropdown; - private SettingsTeamDropdown team2Dropdown; + private SettingsDropdown roundDropdown = null!; + private PlayerCheckbox losersCheckbox = null!; + private DateTextBox dateTimeBox = null!; + private SettingsTeamDropdown team1Dropdown = null!; + private SettingsTeamDropdown team2Dropdown = null!; [Resolved] - private LadderEditorInfo editorInfo { get; set; } + private LadderEditorInfo editorInfo { get; set; } = null!; [Resolved] - private LadderInfo ladderInfo { get; set; } + private LadderInfo ladderInfo { get; set; } = null!; [BackgroundDependencyLoader] private void load() @@ -77,7 +75,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components }; } - private void roundDropdownChanged(ValueChangedEvent round) + private void roundDropdownChanged(ValueChangedEvent round) { if (editorInfo.Selected.Value?.Date.Value < round.NewValue?.StartDate.Value) { @@ -101,11 +99,11 @@ namespace osu.Game.Tournament.Screens.Ladder.Components { } - private partial class SettingsRoundDropdown : SettingsDropdown + private partial class SettingsRoundDropdown : SettingsDropdown { public SettingsRoundDropdown(BindableList rounds) { - Current = new Bindable(); + Current = new Bindable(); foreach (var r in rounds.Prepend(new TournamentRound())) add(r); diff --git a/osu.Game.Tournament/Screens/Ladder/Components/SettingsTeamDropdown.cs b/osu.Game.Tournament/Screens/Ladder/Components/SettingsTeamDropdown.cs index 00e5353edd..7e35190e2e 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/SettingsTeamDropdown.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/SettingsTeamDropdown.cs @@ -12,7 +12,7 @@ using osu.Game.Tournament.Models; namespace osu.Game.Tournament.Screens.Ladder.Components { - public partial class SettingsTeamDropdown : SettingsDropdown + public partial class SettingsTeamDropdown : SettingsDropdown { public SettingsTeamDropdown(BindableList teams) { diff --git a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs index 2d5281b893..4f56a2fcc9 100644 --- a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs +++ b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Specialized; using System.Diagnostics; using System.Linq; @@ -22,13 +20,13 @@ namespace osu.Game.Tournament.Screens.Ladder { public partial class LadderScreen : TournamentScreen { - protected Container MatchesContainer; - private Container paths; - private Container headings; + protected Container MatchesContainer = null!; + private Container paths = null!; + private Container headings = null!; - protected LadderDragContainer ScrollContent; + protected LadderDragContainer ScrollContent = null!; - protected Container Content; + protected Container Content = null!; [BackgroundDependencyLoader] private void load() diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs index f45da861cb..3091f4293c 100644 --- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs +++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -24,20 +22,20 @@ namespace osu.Game.Tournament.Screens.MapPool { public partial class MapPoolScreen : TournamentMatchScreen { - private FillFlowContainer> mapFlows; + private FillFlowContainer> mapFlows = null!; [Resolved(canBeNull: true)] - private TournamentSceneManager sceneManager { get; set; } + private TournamentSceneManager? sceneManager { get; set; } private TeamColour pickColour; private ChoiceType pickType; - private OsuButton buttonRedBan; - private OsuButton buttonBlueBan; - private OsuButton buttonRedPick; - private OsuButton buttonBluePick; + private OsuButton buttonRedBan = null!; + private OsuButton buttonBlueBan = null!; + private OsuButton buttonRedPick = null!; + private OsuButton buttonBluePick = null!; - private ScheduledDelegate scheduledScreenChange; + private ScheduledDelegate? scheduledScreenChange; [BackgroundDependencyLoader] private void load(MatchIPCInfo ipc) @@ -113,7 +111,7 @@ namespace osu.Game.Tournament.Screens.MapPool ipc.Beatmap.BindValueChanged(beatmapChanged); } - private Bindable splitMapPoolByMods; + private Bindable? splitMapPoolByMods; protected override void LoadComplete() { @@ -148,6 +146,9 @@ namespace osu.Game.Tournament.Screens.MapPool private void setNextMode() { + if (CurrentMatch.Value == null) + return; + const TeamColour roll_winner = TeamColour.Red; //todo: draw from match var nextColour = (CurrentMatch.Value.PicksBans.LastOrDefault()?.Team ?? roll_winner) == TeamColour.Red ? TeamColour.Blue : TeamColour.Red; @@ -169,11 +170,11 @@ namespace osu.Game.Tournament.Screens.MapPool addForBeatmap(map.Beatmap.OnlineID); else { - var existing = CurrentMatch.Value.PicksBans.FirstOrDefault(p => p.BeatmapID == map.Beatmap?.OnlineID); + var existing = CurrentMatch.Value?.PicksBans.FirstOrDefault(p => p.BeatmapID == map.Beatmap?.OnlineID); if (existing != null) { - CurrentMatch.Value.PicksBans.Remove(existing); + CurrentMatch.Value?.PicksBans.Remove(existing); setNextMode(); } } @@ -186,13 +187,13 @@ namespace osu.Game.Tournament.Screens.MapPool private void reset() { - CurrentMatch.Value.PicksBans.Clear(); + CurrentMatch.Value?.PicksBans.Clear(); setNextMode(); } private void addForBeatmap(int beatmapId) { - if (CurrentMatch.Value == null) + if (CurrentMatch.Value?.Round.Value == null) return; if (CurrentMatch.Value.Round.Value.Beatmaps.All(b => b.Beatmap?.OnlineID != beatmapId)) @@ -228,7 +229,7 @@ namespace osu.Game.Tournament.Screens.MapPool base.Hide(); } - protected override void CurrentMatchChanged(ValueChangedEvent match) + protected override void CurrentMatchChanged(ValueChangedEvent match) { base.CurrentMatchChanged(match); updateDisplay(); @@ -245,12 +246,15 @@ namespace osu.Game.Tournament.Screens.MapPool if (CurrentMatch.Value.Round.Value != null) { - FillFlowContainer currentFlow = null; - string currentMods = null; + FillFlowContainer? currentFlow = null; + string? currentMods = null; int flowCount = 0; foreach (var b in CurrentMatch.Value.Round.Value.Beatmaps) { + if (b.Beatmap == null) + continue; + if (currentFlow == null || (LadderInfo.SplitMapPoolByMods.Value && currentMods != b.Mods)) { mapFlows.Add(currentFlow = new FillFlowContainer diff --git a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs index 9232b4c689..063c231add 100644 --- a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs +++ b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Framework.Allocation; @@ -21,9 +19,9 @@ namespace osu.Game.Tournament.Screens.Schedule { public partial class ScheduleScreen : TournamentScreen { - private readonly Bindable currentMatch = new Bindable(); - private Container mainContainer; - private LadderInfo ladder; + private readonly Bindable currentMatch = new Bindable(); + private Container mainContainer = null!; + private LadderInfo ladder = null!; [BackgroundDependencyLoader] private void load(LadderInfo ladder) @@ -107,7 +105,7 @@ namespace osu.Game.Tournament.Screens.Schedule currentMatch.BindValueChanged(matchChanged, true); } - private void matchChanged(ValueChangedEvent match) + private void matchChanged(ValueChangedEvent match) { var upcoming = ladder.Matches.Where(p => !p.Completed.Value && p.Team1.Value != null && p.Team2.Value != null && Math.Abs(p.Date.Value.DayOfYear - DateTimeOffset.UtcNow.DayOfYear) < 4); var conditionals = ladder diff --git a/osu.Game.Tournament/Screens/Setup/ResolutionSelector.cs b/osu.Game.Tournament/Screens/Setup/ResolutionSelector.cs index e6ab6f143a..c700e3bfdd 100644 --- a/osu.Game.Tournament/Screens/Setup/ResolutionSelector.cs +++ b/osu.Game.Tournament/Screens/Setup/ResolutionSelector.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface; @@ -14,9 +12,9 @@ namespace osu.Game.Tournament.Screens.Setup private const int minimum_window_height = 480; private const int maximum_window_height = 2160; - public new Action Action; + public new Action? Action; - private OsuNumberBox numberBox; + private OsuNumberBox? numberBox; protected override Drawable CreateComponent() { diff --git a/osu.Game.Tournament/Screens/Setup/SetupScreen.cs b/osu.Game.Tournament/Screens/Setup/SetupScreen.cs index 5c7bbed69c..1152759c2a 100644 --- a/osu.Game.Tournament/Screens/Setup/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/Setup/SetupScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Drawing; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -24,27 +22,27 @@ namespace osu.Game.Tournament.Screens.Setup { public partial class SetupScreen : TournamentScreen { - private FillFlowContainer fillFlow; + private FillFlowContainer fillFlow = null!; - private LoginOverlay loginOverlay; - private ResolutionSelector resolution; + private LoginOverlay? loginOverlay; + private ResolutionSelector resolution = null!; [Resolved] - private MatchIPCInfo ipc { get; set; } + private MatchIPCInfo ipc { get; set; } = null!; [Resolved] - private StableInfo stableInfo { get; set; } + private StableInfo stableInfo { get; set; } = null!; [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; [Resolved] - private RulesetStore rulesets { get; set; } + private RulesetStore rulesets { get; set; } = null!; [Resolved(canBeNull: true)] - private TournamentSceneManager sceneManager { get; set; } + private TournamentSceneManager? sceneManager { get; set; } - private Bindable windowSize; + private Bindable windowSize = null!; [BackgroundDependencyLoader] private void load(FrameworkConfigManager frameworkConfig) @@ -115,7 +113,7 @@ namespace osu.Game.Tournament.Screens.Setup Failing = api.IsLoggedIn != true, Description = "In order to access the API and display metadata, signing in is required." }, - new LabelledDropdown + new LabelledDropdown { Label = "Ruleset", Description = "Decides what stats are displayed and which ranks are retrieved for players. This requires a restart to reload data for an existing bracket.", diff --git a/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs index 463b012b77..c3d072b6a0 100644 --- a/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs +++ b/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.IO; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -24,19 +22,19 @@ namespace osu.Game.Tournament.Screens.Setup public partial class StablePathSelectScreen : TournamentScreen { [Resolved(canBeNull: true)] - private TournamentSceneManager sceneManager { get; set; } + private TournamentSceneManager? sceneManager { get; set; } [Resolved] - private MatchIPCInfo ipc { get; set; } + private MatchIPCInfo ipc { get; set; } = null!; - private OsuDirectorySelector directorySelector; - private DialogOverlay overlay; + private OsuDirectorySelector directorySelector = null!; + private DialogOverlay? overlay; [BackgroundDependencyLoader(true)] private void load(Storage storage, OsuColour colours) { var initialStorage = (ipc as FileBasedIPC)?.IPCStorage ?? storage; - string initialPath = new DirectoryInfo(initialStorage.GetFullPath(string.Empty)).Parent?.FullName; + string? initialPath = new DirectoryInfo(initialStorage.GetFullPath(string.Empty)).Parent?.FullName; AddRangeInternal(new Drawable[] { diff --git a/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs b/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs index ae49ccb63b..e55cbc2dbb 100644 --- a/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs +++ b/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.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.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface; @@ -13,12 +11,12 @@ namespace osu.Game.Tournament.Screens.Setup { internal partial class TournamentSwitcher : ActionableInfo { - private OsuDropdown dropdown; - private OsuButton folderButton; - private OsuButton reloadTournamentsButton; + private OsuDropdown dropdown = null!; + private OsuButton folderButton = null!; + private OsuButton reloadTournamentsButton = null!; [Resolved] - private TournamentGameBase game { get; set; } + private TournamentGameBase game { get; set; } = null!; [BackgroundDependencyLoader] private void load(TournamentStorage storage) diff --git a/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs b/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs index db4d6198e6..ae2ec0c291 100644 --- a/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs +++ b/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tournament.Screens.Showcase }); } - protected override void CurrentMatchChanged(ValueChangedEvent match) + protected override void CurrentMatchChanged(ValueChangedEvent match) { // showcase screen doesn't care about a match being selected. // base call intentionally omitted to not show match warning. diff --git a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs index b07a0a65dd..120a76c127 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -22,12 +20,12 @@ namespace osu.Game.Tournament.Screens.TeamIntro { public partial class SeedingScreen : TournamentMatchScreen { - private Container mainContainer; + private Container mainContainer = null!; - private readonly Bindable currentTeam = new Bindable(); + private readonly Bindable currentTeam = new Bindable(); - private TourneyButton showFirstTeamButton; - private TourneyButton showSecondTeamButton; + private TourneyButton showFirstTeamButton = null!; + private TourneyButton showSecondTeamButton = null!; [BackgroundDependencyLoader] private void load() @@ -53,13 +51,13 @@ namespace osu.Game.Tournament.Screens.TeamIntro { RelativeSizeAxes = Axes.X, Text = "Show first team", - Action = () => currentTeam.Value = CurrentMatch.Value.Team1.Value, + Action = () => currentTeam.Value = CurrentMatch.Value?.Team1.Value, }, showSecondTeamButton = new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Show second team", - Action = () => currentTeam.Value = CurrentMatch.Value.Team2.Value, + Action = () => currentTeam.Value = CurrentMatch.Value?.Team2.Value, }, new SettingsTeamDropdown(LadderInfo.Teams) { @@ -73,7 +71,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro currentTeam.BindValueChanged(teamChanged, true); } - private void teamChanged(ValueChangedEvent team) => updateTeamDisplay(); + private void teamChanged(ValueChangedEvent team) => updateTeamDisplay(); public override void Show() { @@ -84,7 +82,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro updateTeamDisplay(); } - protected override void CurrentMatchChanged(ValueChangedEvent match) + protected override void CurrentMatchChanged(ValueChangedEvent match) { base.CurrentMatchChanged(match); @@ -256,7 +254,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro private partial class LeftInfo : CompositeDrawable { - public LeftInfo(TournamentTeam team) + public LeftInfo(TournamentTeam? team) { FillFlowContainer fill; @@ -315,7 +313,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro private partial class TeamDisplay : DrawableTournamentTeam { - public TeamDisplay(TournamentTeam team) + public TeamDisplay(TournamentTeam? team) : base(team) { AutoSizeAxes = Axes.Both; diff --git a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs index 950a63808c..2280f21d47 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.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.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -15,7 +13,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro { public partial class TeamIntroScreen : TournamentMatchScreen { - private Container mainContainer; + private Container mainContainer = null!; [BackgroundDependencyLoader] private void load() @@ -36,7 +34,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro }; } - protected override void CurrentMatchChanged(ValueChangedEvent match) + protected override void CurrentMatchChanged(ValueChangedEvent match) { base.CurrentMatchChanged(match); diff --git a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs index 9206de1dc2..af21613541 100644 --- a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs +++ b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.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.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -16,12 +14,12 @@ namespace osu.Game.Tournament.Screens.TeamWin { public partial class TeamWinScreen : TournamentMatchScreen { - private Container mainContainer; + private Container mainContainer = null!; private readonly Bindable currentCompleted = new Bindable(); - private TourneyVideo blueWinVideo; - private TourneyVideo redWinVideo; + private TourneyVideo blueWinVideo = null!; + private TourneyVideo redWinVideo = null!; [BackgroundDependencyLoader] private void load() @@ -51,7 +49,7 @@ namespace osu.Game.Tournament.Screens.TeamWin currentCompleted.BindValueChanged(_ => update()); } - protected override void CurrentMatchChanged(ValueChangedEvent match) + protected override void CurrentMatchChanged(ValueChangedEvent match) { base.CurrentMatchChanged(match); @@ -70,7 +68,7 @@ namespace osu.Game.Tournament.Screens.TeamWin { var match = CurrentMatch.Value; - if (match.Winner == null) + if (match?.Winner == null) { mainContainer.Clear(); return; diff --git a/osu.Game.Tournament/Screens/TournamentMatchScreen.cs b/osu.Game.Tournament/Screens/TournamentMatchScreen.cs index 58444d0c1b..5a9b9d05ed 100644 --- a/osu.Game.Tournament/Screens/TournamentMatchScreen.cs +++ b/osu.Game.Tournament/Screens/TournamentMatchScreen.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.Framework.Bindables; using osu.Game.Tournament.Models; @@ -10,8 +8,8 @@ namespace osu.Game.Tournament.Screens { public abstract partial class TournamentMatchScreen : TournamentScreen { - protected readonly Bindable CurrentMatch = new Bindable(); - private WarningBox noMatchWarning; + protected readonly Bindable CurrentMatch = new Bindable(); + private WarningBox? noMatchWarning; protected override void LoadComplete() { @@ -21,7 +19,7 @@ namespace osu.Game.Tournament.Screens CurrentMatch.BindValueChanged(CurrentMatchChanged, true); } - protected virtual void CurrentMatchChanged(ValueChangedEvent match) + protected virtual void CurrentMatchChanged(ValueChangedEvent match) { if (match.NewValue == null) { diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs index c79cc9cd57..ba3b17b513 100644 --- a/osu.Game.Tournament/TournamentGame.cs +++ b/osu.Game.Tournament/TournamentGame.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Drawing; using System.Linq; using osu.Framework.Allocation; @@ -35,12 +33,12 @@ namespace osu.Game.Tournament public static readonly Color4 ELEMENT_FOREGROUND_COLOUR = Color4Extensions.FromHex("#000"); public static readonly Color4 TEXT_COLOUR = Color4Extensions.FromHex("#fff"); - private Drawable heightWarning; + private Drawable heightWarning = null!; - private Bindable windowMode; + private Bindable windowMode = null!; private readonly BindableSize windowSize = new BindableSize(); - private LoadingSpinner loadingSpinner; + private LoadingSpinner loadingSpinner = null!; [Cached(typeof(IDialogOverlay))] private readonly DialogOverlay dialogOverlay = new DialogOverlay(); diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 509c8bb940..ee03b4c35d 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -1,14 +1,13 @@ // 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.IO; using System.Linq; using System.Threading.Tasks; using Newtonsoft.Json; using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Framework.Input; @@ -31,11 +30,11 @@ namespace osu.Game.Tournament public partial class TournamentGameBase : OsuGameBase { public const string BRACKET_FILENAME = @"bracket.json"; - private LadderInfo ladder; - private TournamentStorage storage; - private DependencyContainer dependencies; - private FileBasedIPC ipc; - private BeatmapLookupCache beatmapCache; + private LadderInfo ladder = null!; + private TournamentStorage storage = null!; + private DependencyContainer dependencies = null!; + private FileBasedIPC ipc = null!; + private BeatmapLookupCache beatmapCache = null!; protected Task BracketLoadTask => bracketLoadTaskCompletionSource.Task; @@ -54,7 +53,7 @@ namespace osu.Game.Tournament return new ProductionEndpointConfiguration(); } - private TournamentSpriteText initialisationText; + private TournamentSpriteText initialisationText = null!; [BackgroundDependencyLoader] private void load(Storage baseStorage) @@ -78,6 +77,8 @@ namespace osu.Game.Tournament dependencies.CacheAs(new StableInfo(storage)); beatmapCache = dependencies.Get(); + + ladder = new LadderInfo(); } protected override void LoadComplete() @@ -100,11 +101,11 @@ namespace osu.Game.Tournament { using (Stream stream = storage.GetStream(BRACKET_FILENAME, FileAccess.Read, FileMode.Open)) using (var sr = new StreamReader(stream)) - ladder = JsonConvert.DeserializeObject(await sr.ReadToEndAsync().ConfigureAwait(false), new JsonPointConverter()); + { + ladder = JsonConvert.DeserializeObject(await sr.ReadToEndAsync().ConfigureAwait(false), new JsonPointConverter()) ?? ladder; + } } - ladder ??= new LadderInfo(); - var resolvedRuleset = ladder.Ruleset.Value != null ? RulesetStore.GetRuleset(ladder.Ruleset.Value.ShortName) : RulesetStore.AvailableRulesets.First(); @@ -283,7 +284,7 @@ namespace osu.Game.Tournament private void updateLoadProgressMessage(string s) => Schedule(() => initialisationText.Text = s); - public void PopulatePlayer(TournamentUser user, Action success = null, Action failure = null, bool immediate = false) + public void PopulatePlayer(TournamentUser user, Action? success = null, Action? failure = null, bool immediate = false) { var req = new GetUserRequest(user.OnlineID, ladder.Ruleset.Value); @@ -348,8 +349,8 @@ namespace osu.Game.Tournament foreach (var r in ladder.Rounds) r.Matches = ladder.Matches.Where(p => p.Round.Value == r).Select(p => p.ID).ToList(); - ladder.Progressions = ladder.Matches.Where(p => p.Progression.Value != null).Select(p => new TournamentProgression(p.ID, p.Progression.Value.ID)).Concat( - ladder.Matches.Where(p => p.LosersProgression.Value != null).Select(p => new TournamentProgression(p.ID, p.LosersProgression.Value.ID, true))) + ladder.Progressions = ladder.Matches.Where(p => p.Progression.Value != null).Select(p => new TournamentProgression(p.ID, p.Progression.Value.AsNonNull().ID)).Concat( + ladder.Matches.Where(p => p.LosersProgression.Value != null).Select(p => new TournamentProgression(p.ID, p.LosersProgression.Value.AsNonNull().ID, true))) .ToList(); return JsonConvert.SerializeObject(ladder, diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs index 9ac5a9de12..c69b76ae29 100644 --- a/osu.Game.Tournament/TournamentSceneManager.cs +++ b/osu.Game.Tournament/TournamentSceneManager.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Framework.Allocation; @@ -35,8 +33,8 @@ namespace osu.Game.Tournament [Cached] public partial class TournamentSceneManager : CompositeDrawable { - private Container screens; - private TourneyVideo video; + private Container screens = null!; + private TourneyVideo video = null!; public const int CONTROL_AREA_WIDTH = 200; @@ -50,8 +48,8 @@ namespace osu.Game.Tournament [Cached] private TournamentMatchChatDisplay chat = new TournamentMatchChatDisplay(); - private Container chatContainer; - private FillFlowContainer buttons; + private Container chatContainer = null!; + private FillFlowContainer buttons = null!; public TournamentSceneManager() { @@ -166,10 +164,10 @@ namespace osu.Game.Tournament private float depth; - private Drawable currentScreen; - private ScheduledDelegate scheduledHide; + private Drawable? currentScreen; + private ScheduledDelegate? scheduledHide; - private Drawable temporaryScreen; + private Drawable? temporaryScreen; public void SetScreen(Drawable screen) { @@ -284,7 +282,7 @@ namespace osu.Game.Tournament Y = -2, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = shortcutKey.ToString(), + Text = shortcutKey.Value.ToString(), } } }); @@ -304,7 +302,7 @@ namespace osu.Game.Tournament private bool isSelected; - public Action RequestSelection; + public Action? RequestSelection; public bool IsSelected { From 4c33013674a9f2ddd761d966e1c6e08e88de66a6 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sat, 29 Jul 2023 22:41:26 +0900 Subject: [PATCH 0077/4092] null check in test --- .../Screens/TestSceneMapPoolScreen.cs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs index 2c17cd0d52..254d5c6996 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs @@ -1,11 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Tournament.Components; @@ -16,7 +15,7 @@ namespace osu.Game.Tournament.Tests.Screens { public partial class TestSceneMapPoolScreen : TournamentScreenTestScene { - private MapPoolScreen screen; + private MapPoolScreen screen = null!; [BackgroundDependencyLoader] private void load() @@ -32,7 +31,7 @@ namespace osu.Game.Tournament.Tests.Screens { AddStep("load few maps", () => { - Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Clear(); + Ladder.CurrentMatch.Value.AsNonNull().Round.Value.AsNonNull().Beatmaps.Clear(); for (int i = 0; i < 8; i++) addBeatmap(); @@ -52,7 +51,7 @@ namespace osu.Game.Tournament.Tests.Screens { AddStep("load just enough maps", () => { - Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Clear(); + Ladder.CurrentMatch.Value.AsNonNull().Round.Value.AsNonNull().Beatmaps.Clear(); for (int i = 0; i < 18; i++) addBeatmap(); @@ -72,7 +71,7 @@ namespace osu.Game.Tournament.Tests.Screens { AddStep("load many maps", () => { - Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Clear(); + Ladder.CurrentMatch.Value.AsNonNull().Round.Value.AsNonNull().Beatmaps.Clear(); for (int i = 0; i < 19; i++) addBeatmap(); @@ -92,7 +91,7 @@ namespace osu.Game.Tournament.Tests.Screens { AddStep("load many maps", () => { - Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Clear(); + Ladder.CurrentMatch.Value.AsNonNull().Round.Value.AsNonNull().Beatmaps.Clear(); for (int i = 0; i < 11; i++) addBeatmap(i > 4 ? Ruleset.Value.CreateInstance().AllMods.ElementAt(i).Acronym : "NM"); @@ -118,7 +117,7 @@ namespace osu.Game.Tournament.Tests.Screens { AddStep("load many maps", () => { - Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Clear(); + Ladder.CurrentMatch.Value.AsNonNull().Round.Value.AsNonNull().Beatmaps.Clear(); for (int i = 0; i < 12; i++) addBeatmap(i > 4 ? Ruleset.Value.CreateInstance().AllMods.ElementAt(i).Acronym : "NM"); @@ -138,7 +137,7 @@ namespace osu.Game.Tournament.Tests.Screens { AddStep("load many maps", () => { - Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Clear(); + Ladder.CurrentMatch.Value.AsNonNull().Round.Value.AsNonNull().Beatmaps.Clear(); for (int i = 0; i < 12; i++) addBeatmap(i > 4 ? Ruleset.Value.CreateInstance().AllMods.ElementAt(i).Acronym : "NM"); @@ -155,7 +154,7 @@ namespace osu.Game.Tournament.Tests.Screens private void addBeatmap(string mods = "NM") { - Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Add(new RoundBeatmap + Ladder.CurrentMatch.Value.AsNonNull().Round.Value.AsNonNull().Beatmaps.Add(new RoundBeatmap { Beatmap = CreateSampleBeatmap(), Mods = mods From 033c9091c04377869a13185230fb4e95bf62c665 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 30 Jul 2023 01:39:31 +0900 Subject: [PATCH 0078/4092] use cast instead AsNonNull --- osu.Game.Tournament/Models/TournamentTeam.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Models/TournamentTeam.cs b/osu.Game.Tournament/Models/TournamentTeam.cs index 3587aa937e..b3b2f213ce 100644 --- a/osu.Game.Tournament/Models/TournamentTeam.cs +++ b/osu.Game.Tournament/Models/TournamentTeam.cs @@ -38,7 +38,7 @@ namespace osu.Game.Tournament.Models { int[] ranks = Players.Select(p => p.Rank) .Where(i => i.HasValue) - .Select(i => i.AsNonNull().Value) + .Cast() .ToArray(); if (ranks.Length == 0) From 625ed729eed3a76703fe0c1713fa9617baf5e33b Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 30 Jul 2023 01:39:56 +0900 Subject: [PATCH 0079/4092] debug assert `closest != null` --- .../Screens/Drawings/Components/ScrollingTeamContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs index 4a47de8714..7f19e8a497 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs @@ -136,11 +136,11 @@ namespace osu.Game.Tournament.Screens.Drawings.Components closest = stc; } - Trace.Assert(closest != null, "closest != null"); + Debug.Assert(closest != null, "closest != null"); - offset += DrawWidth / 2f - (closest.AsNonNull().Position.X + closest.AsNonNull().DrawWidth / 2f); + offset += DrawWidth / 2f - (closest.Position.X + closest.DrawWidth / 2f); - ScrollingTeam st = closest.AsNonNull(); + ScrollingTeam st = closest; availableTeams.RemoveAll(at => at == st.Team); From cb4adf115cae1dbef79f1b8af64d0d71c768b537 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 30 Jul 2023 01:40:13 +0900 Subject: [PATCH 0080/4092] RemoveTeam shouldn't have nullable arg --- .../Screens/Drawings/Components/ScrollingTeamContainer.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs index 7f19e8a497..d4e0f29852 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs @@ -188,11 +188,8 @@ namespace osu.Game.Tournament.Screens.Drawings.Components setScrollState(ScrollState.Idle); } - public void RemoveTeam(TournamentTeam? team) + public void RemoveTeam(TournamentTeam team) { - if (team == null) - return; - availableTeams.Remove(team); foreach (var c in Children) From 9482f7445614907f001aee2097939d25ad6c0b92 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 30 Jul 2023 01:49:57 +0900 Subject: [PATCH 0081/4092] fix nullable for TeamList --- osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs index b2dd4e8c36..fc59b486fe 100644 --- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -39,7 +39,7 @@ namespace osu.Game.Tournament.Screens.Drawings private Storage storage = null!; - public ITeamList? TeamList; + public ITeamList TeamList = null!; [BackgroundDependencyLoader] private void load(Storage storage) @@ -48,7 +48,8 @@ namespace osu.Game.Tournament.Screens.Drawings RelativeSizeAxes = Axes.Both; - TeamList ??= new StorageBackedTeamList(storage); + if (TeamList.IsNull()) + TeamList = new StorageBackedTeamList(storage); if (!TeamList.Teams.Any()) { @@ -223,7 +224,7 @@ namespace osu.Game.Tournament.Screens.Drawings teamsContainer.ClearTeams(); allTeams.Clear(); - foreach (TournamentTeam t in TeamList.AsNonNull().Teams) + foreach (TournamentTeam t in TeamList.Teams) { if (groupsContainer.ContainsTeam(t.FullName.Value)) continue; From 88a1cf40052a92f70f8055fd1da33b44d575f022 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 30 Jul 2023 01:57:44 +0900 Subject: [PATCH 0082/4092] remove all `canBeNull` from attribute --- osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs | 2 +- osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs | 2 +- .../Screens/Ladder/Components/DrawableMatchTeam.cs | 4 ++-- .../Screens/Ladder/Components/DrawableTournamentMatch.cs | 4 ++-- osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs | 2 +- osu.Game.Tournament/Screens/Setup/SetupScreen.cs | 2 +- osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs index a82ac57ee9..4074e681f9 100644 --- a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs @@ -36,7 +36,7 @@ namespace osu.Game.Tournament.Screens.Editors private RectangularPositionSnapGrid grid = null!; - [Resolved(canBeNull: true)] + [Resolved] private IDialogOverlay? dialogOverlay { get; set; } protected override bool DrawLoserPaths => true; diff --git a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs index 79c50e60ba..20188cc5dc 100644 --- a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs +++ b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tournament.Screens.Gameplay private OsuButton warmupButton = null!; private MatchIPCInfo ipc = null!; - [Resolved(canBeNull: true)] + [Resolved] private TournamentSceneManager? sceneManager { get; set; } [Resolved] diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs index a380ad9b49..637591c6f6 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs @@ -38,7 +38,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components private readonly Func? isWinner; private LadderEditorScreen ladderEditor = null!; - [Resolved(canBeNull: true)] + [Resolved] private LadderInfo? ladderInfo { get; set; } private void setCurrent() @@ -53,7 +53,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components ladderInfo.CurrentMatch.Value.Current.Value = true; } - [Resolved(CanBeNull = true)] + [Resolved] private LadderEditorInfo? editorInfo { get; set; } public DrawableMatchTeam(TournamentTeam? team, TournamentMatch match, bool losers) diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs index 7b8f2e373b..4de47d7c7f 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs @@ -28,10 +28,10 @@ namespace osu.Game.Tournament.Screens.Ladder.Components private readonly Drawable currentMatchSelectionBox; private Bindable? globalSelection; - [Resolved(CanBeNull = true)] + [Resolved] private LadderEditorInfo? editorInfo { get; set; } - [Resolved(CanBeNull = true)] + [Resolved] private LadderInfo? ladderInfo { get; set; } public DrawableTournamentMatch(TournamentMatch match, bool editor = false) diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs index 3091f4293c..cfce2694e9 100644 --- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs +++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tournament.Screens.MapPool { private FillFlowContainer> mapFlows = null!; - [Resolved(canBeNull: true)] + [Resolved] private TournamentSceneManager? sceneManager { get; set; } private TeamColour pickColour; diff --git a/osu.Game.Tournament/Screens/Setup/SetupScreen.cs b/osu.Game.Tournament/Screens/Setup/SetupScreen.cs index 1152759c2a..df1ce69c33 100644 --- a/osu.Game.Tournament/Screens/Setup/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/Setup/SetupScreen.cs @@ -39,7 +39,7 @@ namespace osu.Game.Tournament.Screens.Setup [Resolved] private RulesetStore rulesets { get; set; } = null!; - [Resolved(canBeNull: true)] + [Resolved] private TournamentSceneManager? sceneManager { get; set; } private Bindable windowSize = null!; diff --git a/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs index c3d072b6a0..74404e06f8 100644 --- a/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs +++ b/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tournament.Screens.Setup { public partial class StablePathSelectScreen : TournamentScreen { - [Resolved(canBeNull: true)] + [Resolved] private TournamentSceneManager? sceneManager { get; set; } [Resolved] From 65b4ae506ee563b9a2b940c4a72b9c9d34ac5dc2 Mon Sep 17 00:00:00 2001 From: QuantumSno Date: Sat, 29 Jul 2023 13:18:47 -0400 Subject: [PATCH 0083/4092] Moved enum to bottom of enumeration table --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 3d24afbb16..79f098f90e 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -286,9 +286,6 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleInGameInterface))] ToggleInGameInterface, - [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleInGameLeaderboard))] - ToggleInGameLeaderboard, - // Song select keybindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleModSelection))] ToggleModSelection, @@ -382,5 +379,9 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleReplaySettings))] ToggleReplaySettings, + + // Editor (cont) + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleInGameLeaderboard))] + ToggleInGameLeaderboard, } } From 9d928c0225d3b86257f1cf8cd71a606c46038892 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 29 Jul 2023 10:39:50 -0700 Subject: [PATCH 0084/4092] Apply NRT to `BreakInfoLine` --- osu.Game/Screens/Play/Break/BreakInfoLine.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/Break/BreakInfoLine.cs b/osu.Game/Screens/Play/Break/BreakInfoLine.cs index b8696352e8..c6a0ca0ef6 100644 --- a/osu.Game/Screens/Play/Break/BreakInfoLine.cs +++ b/osu.Game/Screens/Play/Break/BreakInfoLine.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -71,7 +69,7 @@ namespace osu.Game.Screens.Play.Break if (count is Enum countEnum) return countEnum.GetDescription(); - return count.ToString(); + return count.ToString() ?? string.Empty; } [BackgroundDependencyLoader] From 740898dffb910d5d6e8306c746e9578b383b47d0 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 29 Jul 2023 10:40:18 -0700 Subject: [PATCH 0085/4092] Remove unnecessary prefix parameter --- osu.Game/Screens/Play/Break/BreakInfoLine.cs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/Break/BreakInfoLine.cs b/osu.Game/Screens/Play/Break/BreakInfoLine.cs index c6a0ca0ef6..cfdf6cc651 100644 --- a/osu.Game/Screens/Play/Break/BreakInfoLine.cs +++ b/osu.Game/Screens/Play/Break/BreakInfoLine.cs @@ -24,12 +24,8 @@ namespace osu.Game.Screens.Play.Break private readonly OsuSpriteText text; private readonly OsuSpriteText valueText; - private readonly string prefix; - - public BreakInfoLine(LocalisableString name, string prefix = @"") + public BreakInfoLine(LocalisableString name) { - this.prefix = prefix; - AutoSizeAxes = Axes.Y; Children = new Drawable[] { @@ -45,7 +41,7 @@ namespace osu.Game.Screens.Play.Break { Anchor = Anchor.Centre, Origin = Anchor.CentreLeft, - Text = prefix + @"-", + Text = @"-", Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 17), Margin = new MarginPadding { Left = margin } } @@ -56,7 +52,7 @@ namespace osu.Game.Screens.Play.Break private void currentValueChanged(ValueChangedEvent e) { - LocalisableString newText = LocalisableString.Interpolate($"{prefix}{Format(e.NewValue)}"); + LocalisableString newText = Format(e.NewValue); if (valueText.Text == newText) return; @@ -82,8 +78,8 @@ namespace osu.Game.Screens.Play.Break public partial class PercentageBreakInfoLine : BreakInfoLine { - public PercentageBreakInfoLine(LocalisableString name, string prefix = "") - : base(name, prefix) + public PercentageBreakInfoLine(LocalisableString name) + : base(name) { } From 6ad8339c6674300806378693d79cc9f12285e71a Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 30 Jul 2023 02:30:11 +0900 Subject: [PATCH 0086/4092] use no null when true --- osu.Game.Tournament/IPC/FileBasedIPC.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index 333dd0fd73..daf1417f6f 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using Microsoft.Win32; @@ -180,7 +181,7 @@ namespace osu.Game.Tournament.IPC /// Whether an IPC directory was successfully auto-detected. public bool AutoDetectIPCLocation() => SetIPCLocation(findStablePath()); - private static bool ipcFileExistsInDirectory(string? p) => p != null && File.Exists(Path.Combine(p, "ipc.txt")); + private static bool ipcFileExistsInDirectory([NotNullWhen(true)] string? p) => p != null && File.Exists(Path.Combine(p, "ipc.txt")); private string? findStablePath() { @@ -201,7 +202,7 @@ namespace osu.Game.Tournament.IPC string? stableInstallPath = Environment.GetEnvironmentVariable("OSU_STABLE_PATH"); if (ipcFileExistsInDirectory(stableInstallPath)) - return stableInstallPath!; + return stableInstallPath; } catch { From a1f0e5978448040c646e0252ffc7fdf4077beb17 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 30 Jul 2023 02:30:51 +0900 Subject: [PATCH 0087/4092] remove null check for b.Beatmap --- osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs index cfce2694e9..9caa87d75e 100644 --- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs +++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs @@ -252,9 +252,6 @@ namespace osu.Game.Tournament.Screens.MapPool foreach (var b in CurrentMatch.Value.Round.Value.Beatmaps) { - if (b.Beatmap == null) - continue; - if (currentFlow == null || (LadderInfo.SplitMapPoolByMods.Value && currentMods != b.Mods)) { mapFlows.Add(currentFlow = new FillFlowContainer From bc2ca11bb0497b81c0a891a34aac645089828e02 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 30 Jul 2023 02:31:05 +0900 Subject: [PATCH 0088/4092] move to initialiser --- osu.Game.Tournament/TournamentGameBase.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index ee03b4c35d..eecd097a97 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tournament public partial class TournamentGameBase : OsuGameBase { public const string BRACKET_FILENAME = @"bracket.json"; - private LadderInfo ladder = null!; + private LadderInfo ladder = new LadderInfo(); private TournamentStorage storage = null!; private DependencyContainer dependencies = null!; private FileBasedIPC ipc = null!; @@ -77,8 +77,6 @@ namespace osu.Game.Tournament dependencies.CacheAs(new StableInfo(storage)); beatmapCache = dependencies.Get(); - - ladder = new LadderInfo(); } protected override void LoadComplete() From 059012130924902e36d3e8311d23e4381b7d07eb Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 30 Jul 2023 02:38:19 +0900 Subject: [PATCH 0089/4092] string mod should not null Already assigned in the constructor --- osu.Game.Tournament/Components/TournamentBeatmapPanel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs index 2d5844d02b..0f916d931d 100644 --- a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs +++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs @@ -22,7 +22,7 @@ namespace osu.Game.Tournament.Components { public readonly TournamentBeatmap? Beatmap; - private readonly string? mod; + private readonly string mod; public const float HEIGHT = 50; From ba80d1e2d5e766a5791b047fcad736adb4f54340 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 30 Jul 2023 02:49:13 +0900 Subject: [PATCH 0090/4092] remove nullable for Box --- osu.Game.Tournament/Components/TournamentBeatmapPanel.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs index 0f916d931d..ba922c7c7b 100644 --- a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs +++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs @@ -28,7 +28,7 @@ namespace osu.Game.Tournament.Components private readonly Bindable currentMatch = new Bindable(); - private Box? flash; + private Box flash = null!; public TournamentBeatmapPanel(TournamentBeatmap? beatmap, string mod = "") { @@ -135,11 +135,12 @@ namespace osu.Game.Tournament.Components match.OldValue.PicksBans.CollectionChanged -= picksBansOnCollectionChanged; if (match.NewValue != null) match.NewValue.PicksBans.CollectionChanged += picksBansOnCollectionChanged; - updateState(); + + Scheduler.AddOnce(updateState); } private void picksBansOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) - => updateState(); + => Scheduler.AddOnce(updateState); private BeatmapChoice? choice; @@ -157,7 +158,7 @@ namespace osu.Game.Tournament.Components if (newChoice != null) { if (shouldFlash) - flash?.FadeOutFromOne(500).Loop(0, 10); + flash.FadeOutFromOne(500).Loop(0, 10); BorderThickness = 6; From 5d09eca1041e10119738707c608340a3d8ddf861 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 30 Jul 2023 02:49:30 +0900 Subject: [PATCH 0091/4092] revert test change --- osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs index d6941848b7..b76e0d7521 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tournament.Tests.Screens { Team1 = { Value = Ladder.Teams.FirstOrDefault(t => t.Acronym.Value == "USA") }, Team2 = { Value = Ladder.Teams.FirstOrDefault(t => t.Acronym.Value == "JPN") }, - Round = { Value = Ladder.Rounds.First(g => g.Name.Value == "Quarterfinals") } + Round = { Value = Ladder.Rounds.FirstOrDefault(g => g.Name.Value == "Finals") } }; Add(new TeamIntroScreen From 99dd156d5322ab17a542b855796323fadd981e36 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 30 Jul 2023 02:50:16 +0900 Subject: [PATCH 0092/4092] remove useless using --- osu.Game.Tournament/Models/TournamentTeam.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tournament/Models/TournamentTeam.cs b/osu.Game.Tournament/Models/TournamentTeam.cs index b3b2f213ce..3cd527aaaa 100644 --- a/osu.Game.Tournament/Models/TournamentTeam.cs +++ b/osu.Game.Tournament/Models/TournamentTeam.cs @@ -5,7 +5,6 @@ using System; using System.Linq; using Newtonsoft.Json; using osu.Framework.Bindables; -using osu.Framework.Extensions.ObjectExtensions; namespace osu.Game.Tournament.Models { From 404a927caf55f05be24d1eadf0f3bda06227d92e Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 30 Jul 2023 02:56:52 +0900 Subject: [PATCH 0093/4092] fix Possible NullReferenceException in test --- .../Components/TestSceneTournamentModDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs index cea4306ff8..0cb95c2e43 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tournament.Tests.Components private void success(APIBeatmap beatmap) { - var ruleset = rulesets.GetRuleset(Ladder.Ruleset.Value.OnlineID); + var ruleset = rulesets.GetRuleset(Ladder.Ruleset.Value?.OnlineID ?? -1); if (ruleset == null) return; From 6ebfafa9c3b1f24cc89ec72a577e70a799b052d1 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 29 Jul 2023 11:02:04 -0700 Subject: [PATCH 0094/4092] Remove unnecessary text comparison --- osu.Game/Screens/Play/Break/BreakInfoLine.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/osu.Game/Screens/Play/Break/BreakInfoLine.cs b/osu.Game/Screens/Play/Break/BreakInfoLine.cs index cfdf6cc651..3357963fac 100644 --- a/osu.Game/Screens/Play/Break/BreakInfoLine.cs +++ b/osu.Game/Screens/Play/Break/BreakInfoLine.cs @@ -47,17 +47,7 @@ namespace osu.Game.Screens.Play.Break } }; - Current.ValueChanged += currentValueChanged; - } - - private void currentValueChanged(ValueChangedEvent e) - { - LocalisableString newText = Format(e.NewValue); - - if (valueText.Text == newText) - return; - - valueText.Text = newText; + Current.ValueChanged += text => valueText.Text = Format(text.NewValue); } protected virtual LocalisableString Format(T count) From 4ddf05602f1f117f8ab0248c937a2487e76f59a3 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 29 Jul 2023 11:05:15 -0700 Subject: [PATCH 0095/4092] Update `BreakInfoLine` to formatting standards --- osu.Game/Screens/Play/Break/BreakInfoLine.cs | 34 ++++++++++++-------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Play/Break/BreakInfoLine.cs b/osu.Game/Screens/Play/Break/BreakInfoLine.cs index 3357963fac..147650ca07 100644 --- a/osu.Game/Screens/Play/Break/BreakInfoLine.cs +++ b/osu.Game/Screens/Play/Break/BreakInfoLine.cs @@ -21,21 +21,30 @@ namespace osu.Game.Screens.Play.Break public Bindable Current = new Bindable(); - private readonly OsuSpriteText text; - private readonly OsuSpriteText valueText; + private readonly LocalisableString name; + + private OsuSpriteText valueText = null!; public BreakInfoLine(LocalisableString name) { + this.name = name; + AutoSizeAxes = Axes.Y; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { Children = new Drawable[] { - text = new OsuSpriteText + new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.CentreRight, Text = name, Font = OsuFont.GetFont(size: 17), - Margin = new MarginPadding { Right = margin } + Margin = new MarginPadding { Right = margin }, + Colour = colours.Yellow, }, valueText = new OsuSpriteText { @@ -43,11 +52,17 @@ namespace osu.Game.Screens.Play.Break Origin = Anchor.CentreLeft, Text = @"-", Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 17), - Margin = new MarginPadding { Left = margin } + Margin = new MarginPadding { Left = margin }, + Colour = colours.YellowLight, } }; + } - Current.ValueChanged += text => valueText.Text = Format(text.NewValue); + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(text => valueText.Text = Format(text.NewValue), true); } protected virtual LocalisableString Format(T count) @@ -57,13 +72,6 @@ namespace osu.Game.Screens.Play.Break return count.ToString() ?? string.Empty; } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - text.Colour = colours.Yellow; - valueText.Colour = colours.YellowLight; - } } public partial class PercentageBreakInfoLine : BreakInfoLine From 2e2b0c4e4109905f46e17f478ab1c0e936abfb23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Aug 2023 00:22:08 +0200 Subject: [PATCH 0096/4092] Move `SliderWithTextBoxInput` to more general namespace --- .../UserInterfaceV2}/SliderWithTextBoxInput.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) rename osu.Game/{Screens/Edit/Timing => Graphics/UserInterfaceV2}/SliderWithTextBoxInput.cs (97%) diff --git a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs b/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs similarity index 97% rename from osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs rename to osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs index 1bf0e5299d..3f4e133d45 100644 --- a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs +++ b/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs @@ -8,12 +8,11 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; -using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays.Settings; using osu.Game.Utils; using osuTK; -namespace osu.Game.Screens.Edit.Timing +namespace osu.Game.Graphics.UserInterfaceV2 { public partial class SliderWithTextBoxInput : CompositeDrawable, IHasCurrentValue where T : struct, IEquatable, IComparable, IConvertible From ca81f233563aca7d39822a6d551fc8824c1d3d48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Aug 2023 00:44:05 +0200 Subject: [PATCH 0097/4092] Add test covering non-instantaneous behaviour --- .../TestSceneSliderWithTextBoxInput.cs | 63 +++++++++++++++++++ .../UserInterfaceV2/SliderWithTextBoxInput.cs | 2 - 2 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneSliderWithTextBoxInput.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSliderWithTextBoxInput.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSliderWithTextBoxInput.cs new file mode 100644 index 0000000000..dc70e2b9cb --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSliderWithTextBoxInput.cs @@ -0,0 +1,63 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public partial class TestSceneSliderWithTextBoxInput : OsuManualInputManagerTestScene + { + private SliderWithTextBoxInput sliderWithTextBoxInput = null!; + + private OsuSliderBar slider => sliderWithTextBoxInput.ChildrenOfType>().Single(); + private Nub nub => sliderWithTextBoxInput.ChildrenOfType().Single(); + private OsuTextBox textBox => sliderWithTextBoxInput.ChildrenOfType().Single(); + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("create slider", () => Child = sliderWithTextBoxInput = new SliderWithTextBoxInput("Test Slider") + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 0.5f, + Current = new BindableFloat + { + MinValue = -5, + MaxValue = 5, + Precision = 0.2f + } + }); + } + + [Test] + public void TestNonInstantaneousMode() + { + AddStep("focus textbox", () => InputManager.ChangeFocus(textBox)); + AddStep("change text", () => textBox.Text = "3"); + AddAssert("slider not moved", () => slider.Current.Value, () => Is.Zero); + AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.Zero); + + AddStep("commit text", () => InputManager.Key(Key.Enter)); + AddAssert("slider moved", () => slider.Current.Value, () => Is.EqualTo(3)); + AddAssert("current changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(3)); + + AddStep("move mouse to nub", () => InputManager.MoveMouseTo(nub)); + AddStep("hold left mouse", () => InputManager.PressButton(MouseButton.Left)); + AddStep("move mouse to minimum", () => InputManager.MoveMouseTo(sliderWithTextBoxInput.ScreenSpaceDrawQuad.BottomLeft)); + AddAssert("textbox not changed", () => textBox.Current.Value, () => Is.EqualTo("3")); + AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(3)); + + AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left)); + AddAssert("textbox changed", () => textBox.Current.Value, () => Is.EqualTo("-5")); + AddAssert("current changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5)); + } + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs b/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs index 3f4e133d45..38de0ea94a 100644 --- a/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs +++ b/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs @@ -51,8 +51,6 @@ namespace osu.Game.Graphics.UserInterfaceV2 textBox.OnCommit += (t, isNew) => { - if (!isNew) return; - try { switch (slider.Current) From 0b019f1d4526dbad742fb8d36d3d7923b575c091 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Aug 2023 00:45:57 +0200 Subject: [PATCH 0098/4092] Add test covering desired instantaneous behaviour --- .../TestSceneSliderWithTextBoxInput.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSliderWithTextBoxInput.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSliderWithTextBoxInput.cs index dc70e2b9cb..37f18cd3dd 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneSliderWithTextBoxInput.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSliderWithTextBoxInput.cs @@ -59,5 +59,28 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("textbox changed", () => textBox.Current.Value, () => Is.EqualTo("-5")); AddAssert("current changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5)); } + + [Test] + public void TestInstantaneousMode() + { + AddStep("focus textbox", () => InputManager.ChangeFocus(textBox)); + AddStep("change text", () => textBox.Text = "3"); + AddAssert("slider moved", () => slider.Current.Value, () => Is.EqualTo(3)); + AddAssert("current changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(3)); + + AddStep("commit text", () => InputManager.Key(Key.Enter)); + AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(3)); + AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(3)); + + AddStep("move mouse to nub", () => InputManager.MoveMouseTo(nub)); + AddStep("hold left mouse", () => InputManager.PressButton(MouseButton.Left)); + AddStep("move mouse to minimum", () => InputManager.MoveMouseTo(sliderWithTextBoxInput.ScreenSpaceDrawQuad.BottomLeft)); + AddAssert("textbox changed", () => textBox.Current.Value, () => Is.EqualTo("-5")); + AddAssert("current changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5)); + + AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left)); + AddAssert("textbox not changed", () => textBox.Current.Value, () => Is.EqualTo("-5")); + AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5)); + } } } From 4fb0ff8800281dc7c4fd97c99359791e1edd6390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Aug 2023 00:51:29 +0200 Subject: [PATCH 0099/4092] Implement instantaneous `SliderWithTextBoxInput` mode --- .../TestSceneSliderWithTextBoxInput.cs | 4 + .../UserInterfaceV2/SliderWithTextBoxInput.cs | 128 ++++++++++++------ 2 files changed, 88 insertions(+), 44 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSliderWithTextBoxInput.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSliderWithTextBoxInput.cs index 37f18cd3dd..797afccc77 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneSliderWithTextBoxInput.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSliderWithTextBoxInput.cs @@ -40,6 +40,8 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestNonInstantaneousMode() { + AddStep("set instantaneous to false", () => sliderWithTextBoxInput.Instantaneous = false); + AddStep("focus textbox", () => InputManager.ChangeFocus(textBox)); AddStep("change text", () => textBox.Text = "3"); AddAssert("slider not moved", () => slider.Current.Value, () => Is.Zero); @@ -63,6 +65,8 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestInstantaneousMode() { + AddStep("set instantaneous to true", () => sliderWithTextBoxInput.Instantaneous = true); + AddStep("focus textbox", () => InputManager.ChangeFocus(textBox)); AddStep("change text", () => textBox.Text = "3"); AddAssert("slider moved", () => slider.Current.Value, () => Is.EqualTo(3)); diff --git a/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs b/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs index 38de0ea94a..fc0e4d2083 100644 --- a/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs +++ b/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs @@ -17,12 +17,42 @@ namespace osu.Game.Graphics.UserInterfaceV2 public partial class SliderWithTextBoxInput : CompositeDrawable, IHasCurrentValue where T : struct, IEquatable, IComparable, IConvertible { + /// + /// A custom step value for each key press which actuates a change on this control. + /// + public float KeyboardStep + { + get => slider.KeyboardStep; + set => slider.KeyboardStep = value; + } + + public Bindable Current + { + get => slider.Current; + set => slider.Current = value; + } + + private bool instantaneous; + + /// + /// Whether changes to the slider should instantaneously transfer to the text box (and vice versa). + /// If , the transfer will happen on text box commit (explicit, or implicit via focus loss), or on slider drag end. + /// + public bool Instantaneous + { + get => instantaneous; + set + { + instantaneous = value; + slider.TransferValueOnCommit = !instantaneous; + } + } + private readonly SettingsSlider slider; + private readonly LabelledTextBox textBox; public SliderWithTextBoxInput(LocalisableString labelText) { - LabelledTextBox textBox; - RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -49,55 +79,65 @@ namespace osu.Game.Graphics.UserInterfaceV2 }, }; - textBox.OnCommit += (t, isNew) => - { - try - { - switch (slider.Current) - { - case Bindable bindableInt: - bindableInt.Value = int.Parse(t.Text); - break; + textBox.OnCommit += textCommitted; + textBox.Current.BindValueChanged(textChanged); - case Bindable bindableDouble: - bindableDouble.Value = double.Parse(t.Text); - break; - - default: - slider.Current.Parse(t.Text); - break; - } - } - catch - { - // TriggerChange below will restore the previous text value on failure. - } - - // This is run regardless of parsing success as the parsed number may not actually trigger a change - // due to bindable clamping. Even in such a case we want to update the textbox to a sane visual state. - Current.TriggerChange(); - }; - - Current.BindValueChanged(_ => - { - decimal decimalValue = slider.Current.Value.ToDecimal(NumberFormatInfo.InvariantInfo); - textBox.Text = decimalValue.ToString($@"N{FormatUtils.FindPrecision(decimalValue)}"); - }, true); + Current.BindValueChanged(updateTextBoxFromSlider, true); } - /// - /// A custom step value for each key press which actuates a change on this control. - /// - public float KeyboardStep + private bool updatingFromTextBox; + + private void textChanged(ValueChangedEvent change) { - get => slider.KeyboardStep; - set => slider.KeyboardStep = value; + if (!instantaneous) return; + + tryUpdateSliderFromTextBox(); } - public Bindable Current + private void textCommitted(TextBox t, bool isNew) { - get => slider.Current; - set => slider.Current = value; + tryUpdateSliderFromTextBox(); + + // If the attempted update above failed, restore text box to match the slider. + Current.TriggerChange(); + } + + private void tryUpdateSliderFromTextBox() + { + updatingFromTextBox = true; + + try + { + switch (slider.Current) + { + case Bindable bindableInt: + bindableInt.Value = int.Parse(textBox.Current.Value); + break; + + case Bindable bindableDouble: + bindableDouble.Value = double.Parse(textBox.Current.Value); + break; + + default: + slider.Current.Parse(textBox.Current.Value); + break; + } + } + catch + { + // ignore parsing failures. + // sane state will eventually be restored by a commit (either explicit, or implicit via focus loss). + } + + updatingFromTextBox = false; + } + + private void updateTextBoxFromSlider(ValueChangedEvent _) + { + if (updatingFromTextBox) return; + + decimal decimalValue = slider.Current.Value.ToDecimal(NumberFormatInfo.InvariantInfo); + textBox.Text = decimalValue.ToString($@"N{FormatUtils.FindPrecision(decimalValue)}"); } } } From 049215f2dc1cbdd2ae17c10d28a0430b0dd28ee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Aug 2023 00:54:40 +0200 Subject: [PATCH 0100/4092] Add test coverage for invalid input scenarios --- .../TestSceneSliderWithTextBoxInput.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSliderWithTextBoxInput.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSliderWithTextBoxInput.cs index 797afccc77..d23fcebae3 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneSliderWithTextBoxInput.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSliderWithTextBoxInput.cs @@ -60,6 +60,26 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left)); AddAssert("textbox changed", () => textBox.Current.Value, () => Is.EqualTo("-5")); AddAssert("current changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5)); + + AddStep("focus textbox", () => InputManager.ChangeFocus(textBox)); + AddStep("set text to invalid", () => textBox.Text = "garbage"); + AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5)); + AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5)); + + AddStep("commit text", () => InputManager.Key(Key.Enter)); + AddAssert("text restored", () => textBox.Text, () => Is.EqualTo("-5")); + AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5)); + AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5)); + + AddStep("focus textbox", () => InputManager.ChangeFocus(textBox)); + AddStep("set text to invalid", () => textBox.Text = "garbage"); + AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5)); + AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5)); + + AddStep("lose focus", () => InputManager.ChangeFocus(null)); + AddAssert("text restored", () => textBox.Text, () => Is.EqualTo("-5")); + AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5)); + AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5)); } [Test] @@ -85,6 +105,26 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left)); AddAssert("textbox not changed", () => textBox.Current.Value, () => Is.EqualTo("-5")); AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5)); + + AddStep("focus textbox", () => InputManager.ChangeFocus(textBox)); + AddStep("set text to invalid", () => textBox.Text = "garbage"); + AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5)); + AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5)); + + AddStep("commit text", () => InputManager.Key(Key.Enter)); + AddAssert("text restored", () => textBox.Text, () => Is.EqualTo("-5")); + AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5)); + AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5)); + + AddStep("focus textbox", () => InputManager.ChangeFocus(textBox)); + AddStep("set text to invalid", () => textBox.Text = "garbage"); + AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5)); + AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5)); + + AddStep("lose focus", () => InputManager.ChangeFocus(null)); + AddAssert("text restored", () => textBox.Text, () => Is.EqualTo("-5")); + AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5)); + AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5)); } } } From 426bc52fd544a7512f27284a28ef6cc50225d286 Mon Sep 17 00:00:00 2001 From: QuantumSno Date: Tue, 1 Aug 2023 14:27:37 -0400 Subject: [PATCH 0101/4092] Updated schema_version number and added braces around some migration cases to help with re-using variable names --- osu.Game/Database/RealmAccess.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index f32b161bb6..c949ccbe43 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -82,8 +82,9 @@ namespace osu.Game.Database /// 30 2023-06-16 Run migration of old lazer scores again. This time with more correct rounding considerations. /// 31 2023-06-26 Add Version and LegacyTotalScore to ScoreInfo, set Version to 30000002 and copy TotalScore into LegacyTotalScore for legacy scores. /// 32 2023-07-09 Populate legacy scores with the ScoreV2 mod (and restore TotalScore to the legacy total for such scores) using replay files. + /// 32 2023-08-01 Added a new keybind that toggles the in-game leaderboard on and off /// - private const int schema_version = 32; + private const int schema_version = 33; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. @@ -771,6 +772,7 @@ namespace osu.Game.Database break; case 8: + { // Ctrl -/+ now adjusts UI scale so let's clear any bindings which overlap these combinations. // New defaults will be populated by the key store afterwards. var keyBindings = migration.NewRealm.All(); @@ -784,6 +786,7 @@ namespace osu.Game.Database migration.NewRealm.Remove(decreaseSpeedBinding); break; + } case 9: // Pretty pointless to do this as beatmaps aren't really loaded via realm yet, but oh well. @@ -838,6 +841,7 @@ namespace osu.Game.Database break; case 11: + { string keyBindingClassName = getMappedOrOriginalName(typeof(RealmKeyBinding)); if (!migration.OldRealm.Schema.TryFindObjectSchema(keyBindingClassName, out _)) @@ -864,6 +868,7 @@ namespace osu.Game.Database } break; + } case 14: foreach (var beatmap in migration.NewRealm.All()) @@ -1012,6 +1017,14 @@ namespace osu.Game.Database break; } + + case 33: + { + var oldKeyBinds = migration.NewRealm.All(); + var newKeyBinds = migration.NewRealm.All(); + + break; + } } Logger.Log($"Migration completed in {stopwatch.ElapsedMilliseconds}ms"); From 5443f89dca0823fac233d54d1dd850b793b4145c Mon Sep 17 00:00:00 2001 From: QuantumSno Date: Tue, 1 Aug 2023 16:58:52 -0400 Subject: [PATCH 0102/4092] Assuming its still set to tab, removes binding for toggle chat so itll be set to the new default --- osu.Game/Database/RealmAccess.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index c949ccbe43..d751cce834 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -1020,8 +1020,14 @@ namespace osu.Game.Database case 33: { - var oldKeyBinds = migration.NewRealm.All(); - var newKeyBinds = migration.NewRealm.All(); + // Get all current keybinds, and find the toggle chat bind + // (by default it used to overlap the new toggle leaderboard) + var newKeyBindings = migration.NewRealm.All().ToList(); + var toggleChatBind = newKeyBindings.FirstOrDefault(bind => bind.ActionInt == (int)GlobalAction.ToggleChatFocus); + + // If we have a bind for it, and that bind is tab, remove it + if (toggleChatBind != default && toggleChatBind.KeyCombination.Keys.SequenceEqual(new[] { InputKey.Tab })) + migration.NewRealm.Remove(toggleChatBind); break; } From eef63b41dae7bc1d7916236b0bb2f0f6800cca4c Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Wed, 2 Aug 2023 15:01:58 +0900 Subject: [PATCH 0103/4092] fetch missing beatmap when import score with missing beatmap --- .../Online/TestSceneReplayMissingBeatmap.cs | 96 ++++++++ osu.Game/OsuGame.cs | 2 + osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 15 +- osu.Game/Scoring/ScoreImporter.cs | 21 ++ osu.Game/Scoring/ScoreManager.cs | 7 + .../Import/ReplayMissingBeatmapScreen.cs | 210 ++++++++++++++++++ 6 files changed, 349 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneReplayMissingBeatmap.cs create mode 100644 osu.Game/Screens/Import/ReplayMissingBeatmapScreen.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneReplayMissingBeatmap.cs b/osu.Game.Tests/Visual/Online/TestSceneReplayMissingBeatmap.cs new file mode 100644 index 0000000000..a260cc8593 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneReplayMissingBeatmap.cs @@ -0,0 +1,96 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Net; +using NUnit.Framework; +using osu.Game.Database; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Screens.Import; +using osu.Game.Tests.Resources; + +namespace osu.Game.Tests.Visual.Online +{ + public partial class TestSceneReplayMissingBeatmap : OsuGameTestScene + { + private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; + + [Test] + public void TestSceneMissingBeatmapWithOnlineAvailable() + { + var beatmap = new APIBeatmap + { + OnlineBeatmapSetID = 173612 + }; + + var beatmapset = new APIBeatmapSet + { + OnlineID = 173612, + }; + + setupBeatmapResponse(beatmap, beatmapset); + + AddStep("import score", () => + { + using (var resourceStream = TestResources.OpenResource("Replays/mania-replay.osr")) + { + var importTask = new ImportTask(resourceStream, "replay.osr"); + + Game.ScoreManager.Import(new[] { importTask }); + } + }); + + AddUntilStep("Replay missing screen show", () => Game.ScreenStack.CurrentScreen.GetType() == typeof(ReplayMissingBeatmapScreen)); + } + + [Test] + public void TestSceneMissingBeatmapWithOnlineUnavailable() + { + setupFailedResponse(); + + AddStep("import score", () => + { + using (var resourceStream = TestResources.OpenResource("Replays/mania-replay.osr")) + { + var importTask = new ImportTask(resourceStream, "replay.osr"); + + Game.ScoreManager.Import(new[] { importTask }); + } + }); + + AddUntilStep("Replay missing screen not show", () => Game.ScreenStack.CurrentScreen.GetType() != typeof(ReplayMissingBeatmapScreen)); + } + + private void setupBeatmapResponse(APIBeatmap b, APIBeatmapSet s) + => AddStep("setup response", () => + { + dummyAPI.HandleRequest = request => + { + if (request is GetBeatmapRequest getBeatmapRequest) + { + getBeatmapRequest.TriggerSuccess(b); + return true; + } + + if (request is GetBeatmapSetRequest getBeatmapSetRequest) + { + getBeatmapSetRequest.TriggerSuccess(s); + return true; + } + + return false; + }; + }); + + private void setupFailedResponse() + => AddStep("setup failed response", () => + { + dummyAPI.HandleRequest = request => + { + request.TriggerFailure(new WebException()); + return true; + }; + }); + } +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 1a40bb8e3d..50b4e4f125 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -854,6 +854,8 @@ namespace osu.Game MultiplayerClient.PostNotification = n => Notifications.Post(n); + ScoreManager.Performer = this; + // make config aware of how to lookup skins for on-screen display purposes. // if this becomes a more common thing, tracked settings should be reconsidered to allow local DI. LocalConfig.LookupSkinName = id => SkinManager.Query(s => s.ID == id)?.ToString() ?? "Unknown"; diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index 8c00110909..a191220b37 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -7,6 +7,7 @@ using System; using System.Diagnostics; using System.IO; using System.Linq; +using JetBrains.Annotations; using Newtonsoft.Json; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; @@ -61,7 +62,7 @@ namespace osu.Game.Scoring.Legacy workingBeatmap = GetBeatmap(beatmapHash); if (workingBeatmap is DummyWorkingBeatmap) - throw new BeatmapNotFoundException(beatmapHash); + throw new BeatmapNotFoundException(beatmapHash, stream); scoreInfo.User = new APIUser { Username = sr.ReadString() }; @@ -349,9 +350,19 @@ namespace osu.Game.Scoring.Legacy { public string Hash { get; } - public BeatmapNotFoundException(string hash) + [CanBeNull] + public MemoryStream ScoreStream { get; } + + public BeatmapNotFoundException(string hash, [CanBeNull] Stream scoreStream) { Hash = hash; + + if (scoreStream != null) + { + ScoreStream = new MemoryStream(); + scoreStream.Position = 0; + scoreStream.CopyTo(ScoreStream); + } } } } diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index 81b9f57bbc..408c83a5b8 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -9,6 +9,7 @@ using System.Threading; using Newtonsoft.Json; using osu.Framework.Logging; using osu.Framework.Platform; +using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.IO.Archives; @@ -19,6 +20,8 @@ using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; +using osu.Game.Screens; +using osu.Game.Screens.Import; using Realms; namespace osu.Game.Scoring @@ -27,6 +30,8 @@ namespace osu.Game.Scoring { public override IEnumerable HandledExtensions => new[] { ".osr" }; + public IPerformFromScreenRunner? Performer { get; set; } + protected override string[] HashableFileTypes => new[] { ".osr" }; private readonly RulesetStore rulesets; @@ -54,12 +59,28 @@ namespace osu.Game.Scoring } catch (LegacyScoreDecoder.BeatmapNotFoundException e) { + onMissingBeatmap(e); Logger.Log($@"Score '{name}' failed to import: no corresponding beatmap with the hash '{e.Hash}' could be found.", LoggingTarget.Database); return null; } } } + private void onMissingBeatmap(LegacyScoreDecoder.BeatmapNotFoundException e) + { + var req = new GetBeatmapRequest(new BeatmapInfo + { + MD5Hash = e.Hash + }); + + req.Success += res => + { + Performer?.PerformFromScreen(screen => screen.Push(new ReplayMissingBeatmapScreen(res, e.ScoreStream))); + }; + + api.Queue(req); + } + public Score GetScore(ScoreInfo score) => new LegacyDatabasedScore(score, rulesets, beatmaps(), Files.Store); protected override void Populate(ScoreInfo model, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 31b5bd8365..9331168ab0 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -21,6 +21,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Scoring; using osu.Game.Online.API; using osu.Game.Scoring.Legacy; +using osu.Game.Screens; namespace osu.Game.Scoring { @@ -30,6 +31,12 @@ namespace osu.Game.Scoring private readonly ScoreImporter scoreImporter; private readonly LegacyScoreExporter scoreExporter; + [CanBeNull] + public IPerformFromScreenRunner Performer + { + set => scoreImporter.Performer = value; + } + public override bool PauseImports { get => base.PauseImports; diff --git a/osu.Game/Screens/Import/ReplayMissingBeatmapScreen.cs b/osu.Game/Screens/Import/ReplayMissingBeatmapScreen.cs new file mode 100644 index 0000000000..d7decc0e4e --- /dev/null +++ b/osu.Game/Screens/Import/ReplayMissingBeatmapScreen.cs @@ -0,0 +1,210 @@ +// 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.Collections.Generic; +using System.IO; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables.Cards; +using osu.Game.Configuration; +using osu.Game.Database; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; +using osu.Game.Overlays.Settings; +using osu.Game.Scoring; +using osu.Game.Screens.Ranking; +using osuTK; +using Realms; + +namespace osu.Game.Screens.Import +{ + [Cached(typeof(IPreviewTrackOwner))] + public partial class ReplayMissingBeatmapScreen : OsuScreen, IPreviewTrackOwner + { + [Resolved] + private BeatmapModelDownloader beatmapDownloader { get; set; } = null!; + + [Resolved] + private IAPIProvider api { get; set; } = null!; + + [Resolved] + private ScoreManager scoreManager { get; set; } = null!; + + [Resolved] + private RealmAccess realm { get; set; } = null!; + + private IDisposable? realmSubscription; + + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); + + [Resolved] + private INotificationOverlay? notificationOverlay { get; set; } + + private Container beatmapPanelContainer = null!; + private ReplayDownloadButton replayDownloadButton = null!; + private SettingsCheckbox automaticDownload = null!; + + private readonly MemoryStream? scoreStream; + private readonly APIBeatmap beatmap; + + private APIBeatmapSet? beatmapSetInfo; + + public ReplayMissingBeatmapScreen(APIBeatmap beatmap, MemoryStream? scoreStream = null) + { + this.beatmap = beatmap; + this.scoreStream = scoreStream; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, OsuConfigManager config) + { + InternalChildren = new Drawable[] + { + new Container + { + Masking = true, + CornerRadius = 20, + AutoSizeAxes = Axes.Both, + AutoSizeDuration = 500, + AutoSizeEasing = Easing.OutQuint, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + new Box + { + Colour = colours.Gray5, + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + Margin = new MarginPadding(20), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Spacing = new Vector2(15), + Children = new Drawable[] + { + new OsuSpriteText + { + Text = "Beatmap info", + Font = OsuFont.Default.With(size: 30), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Spacing = new Vector2(15), + Children = new Drawable[] + { + beatmapPanelContainer = new Container + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + } + }, + automaticDownload = new SettingsCheckbox + { + LabelText = "Automatically download beatmaps", + Current = config.GetBindable(OsuSetting.AutomaticallyDownloadWhenSpectating), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + replayDownloadButton = new ReplayDownloadButton(new ScoreInfo()) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + } + } + } + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + var onlineBeatmapRequest = new GetBeatmapSetRequest(beatmap.OnlineBeatmapSetID); + + onlineBeatmapRequest.Success += res => + { + beatmapSetInfo = res; + beatmapPanelContainer.Child = new BeatmapCardNormal(res, allowExpansion: false); + checkForAutomaticDownload(res); + }; + + api.Queue(onlineBeatmapRequest); + + realmSubscription = realm.RegisterForNotifications( + realm => realm.All().Where(s => !s.DeletePending), beatmapsChanged); + } + + private void checkForAutomaticDownload(APIBeatmapSet beatmap) + { + if (!automaticDownload.Current.Value) + return; + + beatmapDownloader.Download(beatmap); + } + + private void beatmapsChanged(IRealmCollection sender, ChangeSet? changes) + { + if (changes?.InsertedIndices == null) return; + + if (beatmapSetInfo == null) return; + + if (scoreStream == null) return; + + if (sender.Any(b => b.OnlineID == beatmapSetInfo.OnlineID)) + { + var progressNotification = new ImportProgressNotification(); + var importTask = new ImportTask(scoreStream, "score.osr"); + + scoreManager.Import(progressNotification, new[] { importTask }) + .ContinueWith(s => + { + s.GetResultSafely>>().FirstOrDefault()?.PerformRead(score => + { + Guid scoreid = score.ID; + Scheduler.Add(() => + { + replayDownloadButton.Score.Value = realm.Realm.Find(scoreid) ?? new ScoreInfo(); + }); + }); + }); + + notificationOverlay?.Post(progressNotification); + + realmSubscription?.Dispose(); + } + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + realmSubscription?.Dispose(); + } + } +} From c07a1ec91ec0e087c6c7a437d5d0b684cea1b4e8 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Wed, 2 Aug 2023 16:09:37 +0900 Subject: [PATCH 0104/4092] retrun DefaultBeatmap when beatmapset already delete pending reimplement https://github.com/ppy/osu/pull/22741 --- osu.Game/Beatmaps/WorkingBeatmapCache.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index 78eed626f2..c06f4da4ae 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -88,7 +88,7 @@ namespace osu.Game.Beatmaps public virtual WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo) { - if (beatmapInfo?.BeatmapSet == null) + if (beatmapInfo?.BeatmapSet == null || beatmapInfo.BeatmapSet?.DeletePending == true) return DefaultBeatmap; lock (workingCache) From 4c43c9232970c1ac9c4ce2ba4ad4dc54f910e530 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Wed, 2 Aug 2023 17:48:24 +0900 Subject: [PATCH 0105/4092] ensure dispose stream --- osu.Game/Scoring/ScoreImporter.cs | 2 ++ osu.Game/Screens/Import/ReplayMissingBeatmapScreen.cs | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index 408c83a5b8..ea3bd8f5ae 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -78,6 +78,8 @@ namespace osu.Game.Scoring Performer?.PerformFromScreen(screen => screen.Push(new ReplayMissingBeatmapScreen(res, e.ScoreStream))); }; + req.Failure += _ => e.ScoreStream?.Dispose(); + api.Queue(req); } diff --git a/osu.Game/Screens/Import/ReplayMissingBeatmapScreen.cs b/osu.Game/Screens/Import/ReplayMissingBeatmapScreen.cs index d7decc0e4e..a6215165fb 100644 --- a/osu.Game/Screens/Import/ReplayMissingBeatmapScreen.cs +++ b/osu.Game/Screens/Import/ReplayMissingBeatmapScreen.cs @@ -174,16 +174,17 @@ namespace osu.Game.Screens.Import if (beatmapSetInfo == null) return; - if (scoreStream == null) return; + if (scoreStream == null || !scoreStream.CanRead) return; if (sender.Any(b => b.OnlineID == beatmapSetInfo.OnlineID)) { var progressNotification = new ImportProgressNotification(); var importTask = new ImportTask(scoreStream, "score.osr"); - scoreManager.Import(progressNotification, new[] { importTask }) .ContinueWith(s => { + scoreStream.Dispose(); + s.GetResultSafely>>().FirstOrDefault()?.PerformRead(score => { Guid scoreid = score.ID; @@ -205,6 +206,7 @@ namespace osu.Game.Screens.Import base.Dispose(isDisposing); realmSubscription?.Dispose(); + scoreStream?.Dispose(); } } } From 1bdd054bd60a6af5c0816cbbacf6ca629c771b8a Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Wed, 2 Aug 2023 18:15:58 +0900 Subject: [PATCH 0106/4092] extract method --- .../Import/ReplayMissingBeatmapScreen.cs | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Import/ReplayMissingBeatmapScreen.cs b/osu.Game/Screens/Import/ReplayMissingBeatmapScreen.cs index a6215165fb..5707721bde 100644 --- a/osu.Game/Screens/Import/ReplayMissingBeatmapScreen.cs +++ b/osu.Game/Screens/Import/ReplayMissingBeatmapScreen.cs @@ -64,6 +64,8 @@ namespace osu.Game.Screens.Import public ReplayMissingBeatmapScreen(APIBeatmap beatmap, MemoryStream? scoreStream = null) { this.beatmap = beatmap; + beatmapSetInfo = beatmap.BeatmapSet; + this.scoreStream = scoreStream; } @@ -145,21 +147,32 @@ namespace osu.Game.Screens.Import { base.LoadComplete(); - var onlineBeatmapRequest = new GetBeatmapSetRequest(beatmap.OnlineBeatmapSetID); - - onlineBeatmapRequest.Success += res => + if (beatmapSetInfo == null) { - beatmapSetInfo = res; - beatmapPanelContainer.Child = new BeatmapCardNormal(res, allowExpansion: false); - checkForAutomaticDownload(res); - }; + var onlineBeatmapRequest = new GetBeatmapSetRequest(beatmap.OnlineBeatmapSetID); - api.Queue(onlineBeatmapRequest); + onlineBeatmapRequest.Success += res => + { + beatmapSetInfo = res; + updateStatus(); + }; + api.Queue(onlineBeatmapRequest); + } + updateStatus(); realmSubscription = realm.RegisterForNotifications( realm => realm.All().Where(s => !s.DeletePending), beatmapsChanged); } + private void updateStatus() + { + if (beatmapSetInfo == null) return; + + beatmapPanelContainer.Clear(); + beatmapPanelContainer.Child = new BeatmapCardNormal(beatmapSetInfo, allowExpansion: false); + checkForAutomaticDownload(beatmapSetInfo); + } + private void checkForAutomaticDownload(APIBeatmapSet beatmap) { if (!automaticDownload.Current.Value) From 6637a5e7bc39db1083e73453025ce29b1f69ea7e Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Wed, 2 Aug 2023 18:53:27 +0900 Subject: [PATCH 0107/4092] ensure Performer not null --- osu.Game/Scoring/ScoreImporter.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index ea3bd8f5ae..db4c0aff89 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -68,6 +68,12 @@ namespace osu.Game.Scoring private void onMissingBeatmap(LegacyScoreDecoder.BeatmapNotFoundException e) { + if (Performer == null) + { + e.ScoreStream?.Dispose(); + return; + } + var req = new GetBeatmapRequest(new BeatmapInfo { MD5Hash = e.Hash @@ -75,7 +81,7 @@ namespace osu.Game.Scoring req.Success += res => { - Performer?.PerformFromScreen(screen => screen.Push(new ReplayMissingBeatmapScreen(res, e.ScoreStream))); + Performer.PerformFromScreen(screen => screen.Push(new ReplayMissingBeatmapScreen(res, e.ScoreStream))); }; req.Failure += _ => e.ScoreStream?.Dispose(); From 87fee001c786b29db34063ef3350e9a9f024d3ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Aug 2023 18:26:36 +0900 Subject: [PATCH 0108/4092] Fix multiplayer spectator potentially taking too long to start When watching from the middle of gameplay, due to a series of failures, `SpectatorClock` would not get seeked to the current time, causing all clients to look like they were out of sync. This is a hotfix for the issue. A better fix will require framework changes or considerable restructuring. I'd recommend testing this works in practice and agreeing that while it is a hack, it's likely not going to cause issues and is something we want to see fixed sooner rather than later. --- .../Multiplayer/Spectate/SpectatorPlayerClock.cs | 2 ++ osu.Game/Screens/Play/GameplayClockContainer.cs | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/SpectatorPlayerClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/SpectatorPlayerClock.cs index 45615d4e19..2ce78818a0 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/SpectatorPlayerClock.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/SpectatorPlayerClock.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Logging; using osu.Framework.Timing; using osu.Game.Screens.Play; @@ -59,6 +60,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public bool Seek(double position) { + Logger.Log($"{nameof(SpectatorPlayerClock)} seeked to {position}"); CurrentTime = position; return true; } diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 22e6884526..226108209a 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -160,6 +160,13 @@ namespace osu.Game.Screens.Play Seek(StartTime); + // This is a workaround for the fact that DecoupleableInterpolatingFramedClock doesn't seek the source + // if the source is not IsRunning. (see https://github.com/ppy/osu-framework/blob/2102638056dfcf85d21b4d85266d53b5dd018767/osu.Framework/Timing/DecoupleableInterpolatingFramedClock.cs#L209-L210) + // + // This breaks in multiplayer spectator. + // I hope to remove this once we knock some sense into clocks in general. + (SourceClock as IAdjustableClock)?.Seek(StartTime); + if (!wasPaused || startClock) Start(); } From 0e7e36f114006c02c46b18b5cc79703e68293a4d Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Wed, 2 Aug 2023 21:53:14 +0900 Subject: [PATCH 0109/4092] don't passing stream by exception --- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 15 ++------------- osu.Game/Scoring/ScoreImporter.cs | 18 ++++++++++++------ 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index a191220b37..8c00110909 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -7,7 +7,6 @@ using System; using System.Diagnostics; using System.IO; using System.Linq; -using JetBrains.Annotations; using Newtonsoft.Json; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; @@ -62,7 +61,7 @@ namespace osu.Game.Scoring.Legacy workingBeatmap = GetBeatmap(beatmapHash); if (workingBeatmap is DummyWorkingBeatmap) - throw new BeatmapNotFoundException(beatmapHash, stream); + throw new BeatmapNotFoundException(beatmapHash); scoreInfo.User = new APIUser { Username = sr.ReadString() }; @@ -350,19 +349,9 @@ namespace osu.Game.Scoring.Legacy { public string Hash { get; } - [CanBeNull] - public MemoryStream ScoreStream { get; } - - public BeatmapNotFoundException(string hash, [CanBeNull] Stream scoreStream) + public BeatmapNotFoundException(string hash) { Hash = hash; - - if (scoreStream != null) - { - ScoreStream = new MemoryStream(); - scoreStream.Position = 0; - scoreStream.CopyTo(ScoreStream); - } } } } diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index db4c0aff89..64394800cd 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; using System.Threading; using Newtonsoft.Json; @@ -59,19 +60,24 @@ namespace osu.Game.Scoring } catch (LegacyScoreDecoder.BeatmapNotFoundException e) { - onMissingBeatmap(e); + onMissingBeatmap(e, archive, name); Logger.Log($@"Score '{name}' failed to import: no corresponding beatmap with the hash '{e.Hash}' could be found.", LoggingTarget.Database); return null; } } } - private void onMissingBeatmap(LegacyScoreDecoder.BeatmapNotFoundException e) + private void onMissingBeatmap(LegacyScoreDecoder.BeatmapNotFoundException e, ArchiveReader archive, string name) { if (Performer == null) - { - e.ScoreStream?.Dispose(); return; + + var stream = new MemoryStream(); + + // stream will close after exception throw, so fetch the stream again. + using (var scoreStream = archive.GetStream(name)) + { + scoreStream.CopyTo(stream); } var req = new GetBeatmapRequest(new BeatmapInfo @@ -81,10 +87,10 @@ namespace osu.Game.Scoring req.Success += res => { - Performer.PerformFromScreen(screen => screen.Push(new ReplayMissingBeatmapScreen(res, e.ScoreStream))); + Performer.PerformFromScreen(screen => screen.Push(new ReplayMissingBeatmapScreen(res, stream))); }; - req.Failure += _ => e.ScoreStream?.Dispose(); + req.Failure += _ => stream.Dispose(); api.Queue(req); } From aab462fd95534417aa5b3b65918d021a9907885c Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 2 Aug 2023 09:40:22 -0700 Subject: [PATCH 0110/4092] Fix `valueText` being replaced even if current is not binding to anything --- osu.Game/Screens/Play/Break/BreakInfoLine.cs | 7 +------ osu.Game/Screens/Play/BreakOverlay.cs | 17 +++++++++-------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Play/Break/BreakInfoLine.cs b/osu.Game/Screens/Play/Break/BreakInfoLine.cs index 147650ca07..df71767f82 100644 --- a/osu.Game/Screens/Play/Break/BreakInfoLine.cs +++ b/osu.Game/Screens/Play/Break/BreakInfoLine.cs @@ -56,13 +56,8 @@ namespace osu.Game.Screens.Play.Break Colour = colours.YellowLight, } }; - } - protected override void LoadComplete() - { - base.LoadComplete(); - - Current.BindValueChanged(text => valueText.Text = Format(text.NewValue), true); + Current.BindValueChanged(text => valueText.Text = Format(text.NewValue)); } protected virtual LocalisableString Format(T count) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 4927800059..3ca82ec00b 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -46,13 +46,14 @@ namespace osu.Game.Screens.Play private readonly Container remainingTimeBox; private readonly RemainingTimeCounter remainingTimeCounter; private readonly BreakArrows breakArrows; + private readonly ScoreProcessor scoreProcessor; + private readonly BreakInfo info; public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor) { + this.scoreProcessor = scoreProcessor; RelativeSizeAxes = Axes.Both; - BreakInfo info; - Child = fadeContainer = new Container { Alpha = 0, @@ -102,18 +103,18 @@ namespace osu.Game.Screens.Play } } }; - - if (scoreProcessor != null) - { - info.AccuracyDisplay.Current.BindTo(scoreProcessor.Accuracy); - info.GradeDisplay.Current.BindTo(scoreProcessor.Rank); - } } protected override void LoadComplete() { base.LoadComplete(); initializeBreaks(); + + if (scoreProcessor != null) + { + info.AccuracyDisplay.Current.BindTo(scoreProcessor.Accuracy); + info.GradeDisplay.Current.BindTo(scoreProcessor.Rank); + } } private void initializeBreaks() From 79892865288507e43b12c7b376ec8b7bfddb3ed4 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 4 Aug 2023 19:57:25 +0900 Subject: [PATCH 0111/4092] scoreStream never been null --- osu.Game/Screens/Import/ReplayMissingBeatmapScreen.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Import/ReplayMissingBeatmapScreen.cs b/osu.Game/Screens/Import/ReplayMissingBeatmapScreen.cs index 5707721bde..6fabbd00c8 100644 --- a/osu.Game/Screens/Import/ReplayMissingBeatmapScreen.cs +++ b/osu.Game/Screens/Import/ReplayMissingBeatmapScreen.cs @@ -56,12 +56,12 @@ namespace osu.Game.Screens.Import private ReplayDownloadButton replayDownloadButton = null!; private SettingsCheckbox automaticDownload = null!; - private readonly MemoryStream? scoreStream; + private readonly MemoryStream scoreStream; private readonly APIBeatmap beatmap; private APIBeatmapSet? beatmapSetInfo; - public ReplayMissingBeatmapScreen(APIBeatmap beatmap, MemoryStream? scoreStream = null) + public ReplayMissingBeatmapScreen(APIBeatmap beatmap, MemoryStream scoreStream) { this.beatmap = beatmap; beatmapSetInfo = beatmap.BeatmapSet; @@ -187,7 +187,7 @@ namespace osu.Game.Screens.Import if (beatmapSetInfo == null) return; - if (scoreStream == null || !scoreStream.CanRead) return; + if (!scoreStream.CanRead) return; if (sender.Any(b => b.OnlineID == beatmapSetInfo.OnlineID)) { From 09047538c70629861a2121af5dbe9db04224886d Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 4 Aug 2023 20:02:22 +0900 Subject: [PATCH 0112/4092] remove all memory stream dispose --- osu.Game/Scoring/ScoreImporter.cs | 2 -- osu.Game/Screens/Import/ReplayMissingBeatmapScreen.cs | 3 --- 2 files changed, 5 deletions(-) diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index 64394800cd..5c354ac3d1 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -90,8 +90,6 @@ namespace osu.Game.Scoring Performer.PerformFromScreen(screen => screen.Push(new ReplayMissingBeatmapScreen(res, stream))); }; - req.Failure += _ => stream.Dispose(); - api.Queue(req); } diff --git a/osu.Game/Screens/Import/ReplayMissingBeatmapScreen.cs b/osu.Game/Screens/Import/ReplayMissingBeatmapScreen.cs index 6fabbd00c8..2e0e7a8e3f 100644 --- a/osu.Game/Screens/Import/ReplayMissingBeatmapScreen.cs +++ b/osu.Game/Screens/Import/ReplayMissingBeatmapScreen.cs @@ -196,8 +196,6 @@ namespace osu.Game.Screens.Import scoreManager.Import(progressNotification, new[] { importTask }) .ContinueWith(s => { - scoreStream.Dispose(); - s.GetResultSafely>>().FirstOrDefault()?.PerformRead(score => { Guid scoreid = score.ID; @@ -219,7 +217,6 @@ namespace osu.Game.Screens.Import base.Dispose(isDisposing); realmSubscription?.Dispose(); - scoreStream?.Dispose(); } } } From 49f5d6813ec82dec79ef02e8b380fde4cc7a44d8 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 4 Aug 2023 22:05:59 -0700 Subject: [PATCH 0113/4092] Block beatmap editor from testing/exiting/exporting when saving fails --- osu.Game/Screens/Edit/Editor.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index b885eee46f..35d8bb4ab7 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -425,7 +425,8 @@ namespace osu.Game.Screens.Edit { dialogOverlay.Push(new SaveBeforeGameplayTestDialog(() => { - Save(); + if (!Save()) return; + pushEditorPlayer(); })); } @@ -764,7 +765,7 @@ namespace osu.Game.Screens.Edit private void confirmExitWithSave() { - Save(); + if (!Save()) return; ExitConfirmed = true; this.Exit(); @@ -1021,13 +1022,15 @@ namespace osu.Game.Screens.Edit private void exportBeatmap() { - Save(); + if (!Save()) return; + beatmapManager.Export(Beatmap.Value.BeatmapSetInfo); } private void exportLegacyBeatmap() { - Save(); + if (!Save()) return; + beatmapManager.ExportLegacy(Beatmap.Value.BeatmapSetInfo); } From d91798271d80fe735e2f0c9ff72c6faf30fe0525 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 8 Aug 2023 22:06:30 +0200 Subject: [PATCH 0114/4092] Add test case covering exit block Co-authored-by: Joseph Madamba --- .../Editing/TestSceneEditorBeatmapCreation.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 5aa2dd2ebf..37cb43a43a 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -18,6 +18,7 @@ using osu.Game.Database; using osu.Game.Overlays.Dialog; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; @@ -453,6 +454,51 @@ namespace osu.Game.Tests.Visual.Editing }); } + [Test] + public void TestExitBlockedWhenSavingBeatmapWithSameNamedDifficulties() + { + Guid setId = Guid.Empty; + const string duplicate_difficulty_name = "duplicate"; + + AddStep("retrieve set ID", () => setId = EditorBeatmap.BeatmapInfo.BeatmapSet!.ID); + AddStep("set difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = duplicate_difficulty_name); + AddStep("save beatmap", () => Editor.Save()); + AddAssert("new beatmap persisted", () => + { + var set = beatmapManager.QueryBeatmapSet(s => s.ID == setId); + return set != null && set.PerformRead(s => s.Beatmaps.Count == 1 && s.Files.Count == 1); + }); + + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new CatchRuleset().RulesetInfo)); + + AddUntilStep("wait for created", () => + { + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName != duplicate_difficulty_name; + }); + AddUntilStep("wait for editor load", () => Editor.IsLoaded && DialogOverlay.IsLoaded); + + AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[] + { + new Fruit + { + StartTime = 0 + }, + new Fruit + { + StartTime = 1000 + } + })); + + AddStep("set difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = duplicate_difficulty_name); + AddUntilStep("wait for has unsaved changes", () => Editor.HasUnsavedChanges); + + AddStep("exit", () => Editor.Exit()); + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is PromptForSaveDialog); + AddStep("attempt to save", () => DialogOverlay.CurrentDialog.PerformOkAction()); + AddAssert("editor is still current", () => Editor.IsCurrentScreen()); + } + [Test] public void TestCreateNewDifficultyForInconvertibleRuleset() { From c88045dfc7251dde653bd666f3ebf1b7be4fb809 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Aug 2023 18:21:57 +0900 Subject: [PATCH 0115/4092] Fix weird usage of `Cast` --- osu.Game.Tournament/Models/TournamentTeam.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Models/TournamentTeam.cs b/osu.Game.Tournament/Models/TournamentTeam.cs index 3cd527aaaa..5df8c2a620 100644 --- a/osu.Game.Tournament/Models/TournamentTeam.cs +++ b/osu.Game.Tournament/Models/TournamentTeam.cs @@ -37,7 +37,7 @@ namespace osu.Game.Tournament.Models { int[] ranks = Players.Select(p => p.Rank) .Where(i => i.HasValue) - .Cast() + .Select(i => i!.Value) .ToArray(); if (ranks.Length == 0) From 5898a9986b2ee8dc12628f7e2ff880a367457d46 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Wed, 9 Aug 2023 19:11:36 +0900 Subject: [PATCH 0116/4092] Revert "use no null when true" This reverts commit 6ad8339c6674300806378693d79cc9f12285e71a. --- osu.Game.Tournament/IPC/FileBasedIPC.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index daf1417f6f..333dd0fd73 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using Microsoft.Win32; @@ -181,7 +180,7 @@ namespace osu.Game.Tournament.IPC /// Whether an IPC directory was successfully auto-detected. public bool AutoDetectIPCLocation() => SetIPCLocation(findStablePath()); - private static bool ipcFileExistsInDirectory([NotNullWhen(true)] string? p) => p != null && File.Exists(Path.Combine(p, "ipc.txt")); + private static bool ipcFileExistsInDirectory(string? p) => p != null && File.Exists(Path.Combine(p, "ipc.txt")); private string? findStablePath() { @@ -202,7 +201,7 @@ namespace osu.Game.Tournament.IPC string? stableInstallPath = Environment.GetEnvironmentVariable("OSU_STABLE_PATH"); if (ipcFileExistsInDirectory(stableInstallPath)) - return stableInstallPath; + return stableInstallPath!; } catch { From 5ad80879b771015b757823b9fd322ebe7b1c87f1 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Wed, 9 Aug 2023 19:11:51 +0900 Subject: [PATCH 0117/4092] let MatchIPCInfo .Beatmap be nullable --- osu.Game.Tournament/Components/SongBar.cs | 2 +- osu.Game.Tournament/IPC/FileBasedIPC.cs | 3 ++- osu.Game.Tournament/IPC/MatchIPCInfo.cs | 2 +- osu.Game.Tournament/Screens/BeatmapInfoScreen.cs | 2 +- osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs | 4 ++-- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tournament/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs index 19d63286d8..fa0cbda16b 100644 --- a/osu.Game.Tournament/Components/SongBar.cs +++ b/osu.Game.Tournament/Components/SongBar.cs @@ -29,7 +29,7 @@ namespace osu.Game.Tournament.Components [Resolved] private IBindable ruleset { get; set; } = null!; - public TournamentBeatmap Beatmap + public TournamentBeatmap? Beatmap { set { diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index 333dd0fd73..bf835122cc 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -87,12 +87,13 @@ namespace osu.Game.Tournament.IPC var existing = ladder.CurrentMatch.Value?.Round.Value?.Beatmaps.FirstOrDefault(b => b.ID == beatmapId); - if (existing?.Beatmap != null) + if (existing != null) Beatmap.Value = existing.Beatmap; else { beatmapLookupRequest = new GetBeatmapRequest(new APIBeatmap { OnlineID = beatmapId }); beatmapLookupRequest.Success += b => Beatmap.Value = new TournamentBeatmap(b); + beatmapLookupRequest.Failure += _ => Beatmap.Value = null; API.Queue(beatmapLookupRequest); } } diff --git a/osu.Game.Tournament/IPC/MatchIPCInfo.cs b/osu.Game.Tournament/IPC/MatchIPCInfo.cs index ff557717d5..f57518971f 100644 --- a/osu.Game.Tournament/IPC/MatchIPCInfo.cs +++ b/osu.Game.Tournament/IPC/MatchIPCInfo.cs @@ -10,7 +10,7 @@ namespace osu.Game.Tournament.IPC { public partial class MatchIPCInfo : Component { - public Bindable Beatmap { get; } = new Bindable(); + public Bindable Beatmap { get; } = new Bindable(); public Bindable Mods { get; } = new Bindable(); public Bindable State { get; } = new Bindable(); public Bindable ChatChannel { get; } = new Bindable(); diff --git a/osu.Game.Tournament/Screens/BeatmapInfoScreen.cs b/osu.Game.Tournament/Screens/BeatmapInfoScreen.cs index e977012803..32a50d2222 100644 --- a/osu.Game.Tournament/Screens/BeatmapInfoScreen.cs +++ b/osu.Game.Tournament/Screens/BeatmapInfoScreen.cs @@ -37,7 +37,7 @@ namespace osu.Game.Tournament.Screens SongBar.Mods = mods.NewValue; } - private void beatmapChanged(ValueChangedEvent beatmap) + private void beatmapChanged(ValueChangedEvent beatmap) { SongBar.FadeInFromZero(300, Easing.OutQuint); SongBar.Beatmap = beatmap.NewValue; diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs index 9caa87d75e..07dc2bea54 100644 --- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs +++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs @@ -121,13 +121,13 @@ namespace osu.Game.Tournament.Screens.MapPool splitMapPoolByMods.BindValueChanged(_ => updateDisplay()); } - private void beatmapChanged(ValueChangedEvent beatmap) + private void beatmapChanged(ValueChangedEvent beatmap) { if (CurrentMatch.Value == null || CurrentMatch.Value.PicksBans.Count(p => p.Type == ChoiceType.Ban) < 2) return; // if bans have already been placed, beatmap changes result in a selection being made autoamtically - if (beatmap.NewValue.OnlineID > 0) + if (beatmap.NewValue?.OnlineID > 0) addForBeatmap(beatmap.NewValue.OnlineID); } From 6549ca530480828076c7bd372cb8fec764189260 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Aug 2023 00:20:37 +0900 Subject: [PATCH 0118/4092] Revert some usages of `AsNonNull()` in tests We generally prefer `!` when it is unambiguous usage (ie. direct access). --- .../NonVisual/LadderInfoSerialisationTest.cs | 5 ++--- .../Screens/TestSceneMapPoolScreen.cs | 15 +++++++-------- osu.Game.Tournament.Tests/TournamentTestScene.cs | 7 +++---- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tournament.Tests/NonVisual/LadderInfoSerialisationTest.cs b/osu.Game.Tournament.Tests/NonVisual/LadderInfoSerialisationTest.cs index d18651b8bb..acb4a87ffc 100644 --- a/osu.Game.Tournament.Tests/NonVisual/LadderInfoSerialisationTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/LadderInfoSerialisationTest.cs @@ -3,7 +3,6 @@ using Newtonsoft.Json; using NUnit.Framework; -using osu.Framework.Extensions.ObjectExtensions; using osu.Game.Tournament.Models; namespace osu.Game.Tournament.Tests.NonVisual @@ -36,8 +35,8 @@ namespace osu.Game.Tournament.Tests.NonVisual PlayersPerTeam = { Value = 4 }, Teams = { - match.Team1.Value.AsNonNull(), - match.Team2.Value.AsNonNull(), + match.Team1.Value!, + match.Team2.Value!, }, Rounds = { diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs index 254d5c6996..7b2c1ba336 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs @@ -4,7 +4,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Tournament.Components; @@ -31,7 +30,7 @@ namespace osu.Game.Tournament.Tests.Screens { AddStep("load few maps", () => { - Ladder.CurrentMatch.Value.AsNonNull().Round.Value.AsNonNull().Beatmaps.Clear(); + Ladder.CurrentMatch.Value!.Round.Value!.Beatmaps.Clear(); for (int i = 0; i < 8; i++) addBeatmap(); @@ -51,7 +50,7 @@ namespace osu.Game.Tournament.Tests.Screens { AddStep("load just enough maps", () => { - Ladder.CurrentMatch.Value.AsNonNull().Round.Value.AsNonNull().Beatmaps.Clear(); + Ladder.CurrentMatch.Value!.Round.Value!.Beatmaps.Clear(); for (int i = 0; i < 18; i++) addBeatmap(); @@ -71,7 +70,7 @@ namespace osu.Game.Tournament.Tests.Screens { AddStep("load many maps", () => { - Ladder.CurrentMatch.Value.AsNonNull().Round.Value.AsNonNull().Beatmaps.Clear(); + Ladder.CurrentMatch.Value!.Round.Value!.Beatmaps.Clear(); for (int i = 0; i < 19; i++) addBeatmap(); @@ -91,7 +90,7 @@ namespace osu.Game.Tournament.Tests.Screens { AddStep("load many maps", () => { - Ladder.CurrentMatch.Value.AsNonNull().Round.Value.AsNonNull().Beatmaps.Clear(); + Ladder.CurrentMatch.Value!.Round.Value!.Beatmaps.Clear(); for (int i = 0; i < 11; i++) addBeatmap(i > 4 ? Ruleset.Value.CreateInstance().AllMods.ElementAt(i).Acronym : "NM"); @@ -117,7 +116,7 @@ namespace osu.Game.Tournament.Tests.Screens { AddStep("load many maps", () => { - Ladder.CurrentMatch.Value.AsNonNull().Round.Value.AsNonNull().Beatmaps.Clear(); + Ladder.CurrentMatch.Value!.Round.Value!.Beatmaps.Clear(); for (int i = 0; i < 12; i++) addBeatmap(i > 4 ? Ruleset.Value.CreateInstance().AllMods.ElementAt(i).Acronym : "NM"); @@ -137,7 +136,7 @@ namespace osu.Game.Tournament.Tests.Screens { AddStep("load many maps", () => { - Ladder.CurrentMatch.Value.AsNonNull().Round.Value.AsNonNull().Beatmaps.Clear(); + Ladder.CurrentMatch.Value!.Round.Value!.Beatmaps.Clear(); for (int i = 0; i < 12; i++) addBeatmap(i > 4 ? Ruleset.Value.CreateInstance().AllMods.ElementAt(i).Acronym : "NM"); @@ -154,7 +153,7 @@ namespace osu.Game.Tournament.Tests.Screens private void addBeatmap(string mods = "NM") { - Ladder.CurrentMatch.Value.AsNonNull().Round.Value.AsNonNull().Beatmaps.Add(new RoundBeatmap + Ladder.CurrentMatch.Value!.Round.Value!.Beatmaps.Add(new RoundBeatmap { Beatmap = CreateSampleBeatmap(), Mods = mods diff --git a/osu.Game.Tournament.Tests/TournamentTestScene.cs b/osu.Game.Tournament.Tests/TournamentTestScene.cs index 0cf1e22211..f24fc61d85 100644 --- a/osu.Game.Tournament.Tests/TournamentTestScene.cs +++ b/osu.Game.Tournament.Tests/TournamentTestScene.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Threading; using osu.Framework.Allocation; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Framework.Utils; @@ -41,10 +40,10 @@ namespace osu.Game.Tournament.Tests match = CreateSampleMatch(); - Ladder.Rounds.Add(match.Round.Value.AsNonNull()); + Ladder.Rounds.Add(match.Round.Value!); Ladder.Matches.Add(match); - Ladder.Teams.Add(match.Team1.Value.AsNonNull()); - Ladder.Teams.Add(match.Team2.Value.AsNonNull()); + Ladder.Teams.Add(match.Team1.Value!); + Ladder.Teams.Add(match.Team2.Value!); Ruleset.BindTo(Ladder.Ruleset); Dependencies.CacheAs(new StableInfo(storage)); From 4e83245acfc61c0460fc4882935d5b7e64b04e09 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Aug 2023 00:24:57 +0900 Subject: [PATCH 0119/4092] Apply nullability to all remaining tests where it wasn't applied --- .../Components/TestSceneDateTextBox.cs | 2 -- osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs | 4 +--- .../Components/TestSceneTournamentModDisplay.cs | 2 -- osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs | 2 -- osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs | 6 ++---- osu.Game.Tournament.Tests/NonVisual/TournamentHostTest.cs | 4 +--- .../Screens/TestSceneGameplayScreen.cs | 2 -- .../Screens/TestSceneScheduleScreen.cs | 2 -- 8 files changed, 4 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tournament.Tests/Components/TestSceneDateTextBox.cs b/osu.Game.Tournament.Tests/Components/TestSceneDateTextBox.cs index f547acd635..c1651b8ce3 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneDateTextBox.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneDateTextBox.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 NUnit.Framework; using osu.Game.Tests.Visual; using osu.Game.Tournament.Components; diff --git a/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs b/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs index f793c33878..762cfa2519 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneSongBar.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 NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -22,7 +20,7 @@ namespace osu.Game.Tournament.Tests.Components [Test] public void TestSongBar() { - SongBar songBar = null; + SongBar songBar = null!; AddStep("create bar", () => Child = songBar = new SongBar { diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs index 0cb95c2e43..99a448c494 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.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.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs b/osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs index 256a984a7c..014c96b075 100644 --- a/osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.IO; using System.Threading.Tasks; diff --git a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs index ca6354cb48..6ee7808099 100644 --- a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.IO; using System.Linq; using NUnit.Framework; @@ -36,11 +34,11 @@ namespace osu.Game.Tournament.Tests.NonVisual { var osu = LoadTournament(host); TournamentStorage storage = (TournamentStorage)osu.Dependencies.Get(); - FileBasedIPC ipc = null; + FileBasedIPC? ipc = null; WaitForOrAssert(() => (ipc = osu.Dependencies.Get() as FileBasedIPC)?.IsLoaded == true, @"ipc could not be populated in a reasonable amount of time"); - Assert.True(ipc.SetIPCLocation(testStableInstallDirectory)); + Assert.True(ipc!.SetIPCLocation(testStableInstallDirectory)); Assert.True(storage.AllTournaments.Exists("stable.json")); } finally diff --git a/osu.Game.Tournament.Tests/NonVisual/TournamentHostTest.cs b/osu.Game.Tournament.Tests/NonVisual/TournamentHostTest.cs index 8dc0946432..e4a35913cc 100644 --- a/osu.Game.Tournament.Tests/NonVisual/TournamentHostTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/TournamentHostTest.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Threading; using System.Threading.Tasks; @@ -13,7 +11,7 @@ namespace osu.Game.Tournament.Tests.NonVisual { public abstract class TournamentHostTest { - public static TournamentGameBase LoadTournament(GameHost host, TournamentGameBase tournament = null) + public static TournamentGameBase LoadTournament(GameHost host, TournamentGameBase? tournament = null) { tournament ??= new TournamentGameBase(); Task.Factory.StartNew(() => host.Run(tournament), TaskCreationOptions.LongRunning) diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneGameplayScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneGameplayScreen.cs index abf0905647..31583bf8b7 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneGameplayScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneGameplayScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneScheduleScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneScheduleScreen.cs index 5071fbbfe3..f3c3fdec97 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneScheduleScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneScheduleScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using NUnit.Framework; using osu.Framework.Allocation; From 3501b7c22b9820292cc9b28ca0caadef6255c82e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 9 Aug 2023 21:32:46 +0200 Subject: [PATCH 0120/4092] Fix some more nullability inspections --- .../Components/TestSceneDateTextBox.cs | 2 +- .../Components/TestSceneTournamentModDisplay.cs | 6 +++--- osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tournament.Tests/Components/TestSceneDateTextBox.cs b/osu.Game.Tournament.Tests/Components/TestSceneDateTextBox.cs index c1651b8ce3..d018a7ed5c 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneDateTextBox.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneDateTextBox.cs @@ -11,7 +11,7 @@ namespace osu.Game.Tournament.Tests.Components { public partial class TestSceneDateTextBox : OsuManualInputManagerTestScene { - private DateTextBox textBox; + private DateTextBox textBox = null!; [SetUp] public void Setup() => Schedule(() => diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs index 99a448c494..0b69da78a4 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs @@ -17,12 +17,12 @@ namespace osu.Game.Tournament.Tests.Components public partial class TestSceneTournamentModDisplay : TournamentTestScene { [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; [Resolved] - private IRulesetStore rulesets { get; set; } + private IRulesetStore rulesets { get; set; } = null!; - private FillFlowContainer fillFlow; + private FillFlowContainer fillFlow = null!; [BackgroundDependencyLoader] private void load() diff --git a/osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs b/osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs index 014c96b075..e8c9f65608 100644 --- a/osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs @@ -79,11 +79,11 @@ namespace osu.Game.Tournament.Tests.NonVisual public partial class TestTournament : TournamentGameBase { private readonly bool resetRuleset; - private readonly Action runOnLoadComplete; + private readonly Action? runOnLoadComplete; public new Task BracketLoadTask => base.BracketLoadTask; - public TestTournament(bool resetRuleset = false, [InstantHandle] Action runOnLoadComplete = null) + public TestTournament(bool resetRuleset = false, [InstantHandle] Action? runOnLoadComplete = null) { this.resetRuleset = resetRuleset; this.runOnLoadComplete = runOnLoadComplete; From 71c42167e7e8fce7e4671f67e672786e5584fe21 Mon Sep 17 00:00:00 2001 From: QuantumSno Date: Thu, 10 Aug 2023 10:17:12 -0400 Subject: [PATCH 0121/4092] Updated wording on realm comments --- osu.Game/Database/RealmAccess.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index d751cce834..6eb1e3bf9b 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -1021,11 +1021,11 @@ namespace osu.Game.Database case 33: { // Get all current keybinds, and find the toggle chat bind - // (by default it used to overlap the new toggle leaderboard) + // (by default it would to overlap the new toggle leaderboard) var newKeyBindings = migration.NewRealm.All().ToList(); var toggleChatBind = newKeyBindings.FirstOrDefault(bind => bind.ActionInt == (int)GlobalAction.ToggleChatFocus); - // If we have a bind for it, and that bind is tab, remove it + // If we have a bind for it, and that bind is still tab, remove it if (toggleChatBind != default && toggleChatBind.KeyCombination.Keys.SequenceEqual(new[] { InputKey.Tab })) migration.NewRealm.Remove(toggleChatBind); From 5d6ebb4df9f1fffd6bdadfe454dff9d1249dd589 Mon Sep 17 00:00:00 2001 From: QuantumSno Date: Thu, 10 Aug 2023 10:18:15 -0400 Subject: [PATCH 0122/4092] Removed comments organizing GlobalActionContainer.cs, since they were incorrectly grouping actions --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 79f098f90e..1090eeb462 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -205,7 +205,6 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleMute))] ToggleMute, - // In-Game Keybindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.SkipCutscene))] SkipCutscene, @@ -233,7 +232,6 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.QuickExit))] QuickExit, - // Game-wide beatmap music controller keybindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.MusicNext))] MusicNext, @@ -261,7 +259,6 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.PauseGameplay))] PauseGameplay, - // Editor [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorSetupMode))] EditorSetupMode, @@ -286,7 +283,6 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleInGameInterface))] ToggleInGameInterface, - // Song select keybindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleModSelection))] ToggleModSelection, @@ -380,7 +376,6 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleReplaySettings))] ToggleReplaySettings, - // Editor (cont) [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleInGameLeaderboard))] ToggleInGameLeaderboard, } From 2b738edb93dbc34d1f850db710221656871c3e40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 10 Aug 2023 22:55:02 +0200 Subject: [PATCH 0123/4092] Reword / fix comments --- osu.Game/Database/RealmAccess.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 6eb1e3bf9b..90e39da201 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -82,7 +82,7 @@ namespace osu.Game.Database /// 30 2023-06-16 Run migration of old lazer scores again. This time with more correct rounding considerations. /// 31 2023-06-26 Add Version and LegacyTotalScore to ScoreInfo, set Version to 30000002 and copy TotalScore into LegacyTotalScore for legacy scores. /// 32 2023-07-09 Populate legacy scores with the ScoreV2 mod (and restore TotalScore to the legacy total for such scores) using replay files. - /// 32 2023-08-01 Added a new keybind that toggles the in-game leaderboard on and off + /// 33 2023-08-01 Reset default chat toggle keybind to avoid conflict with leaderboard toggle keybind. /// private const int schema_version = 33; @@ -1020,12 +1020,11 @@ namespace osu.Game.Database case 33: { - // Get all current keybinds, and find the toggle chat bind - // (by default it would to overlap the new toggle leaderboard) + // Clear default bindings for the chat focus toggle, + // as they would conflict with the newly-added leaderboard toggle. var newKeyBindings = migration.NewRealm.All().ToList(); var toggleChatBind = newKeyBindings.FirstOrDefault(bind => bind.ActionInt == (int)GlobalAction.ToggleChatFocus); - // If we have a bind for it, and that bind is still tab, remove it if (toggleChatBind != default && toggleChatBind.KeyCombination.Keys.SequenceEqual(new[] { InputKey.Tab })) migration.NewRealm.Remove(toggleChatBind); From 896cbb0ba074474858e222253a34b449a7869bea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Aug 2023 21:34:59 +0900 Subject: [PATCH 0124/4092] 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 651e5b1fe6..1ac85532b8 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania.Tests.Android/AndroidManifest.xml b/osu.Game.Rulesets.Mania.Tests.Android/AndroidManifest.xml index 4a1545a423..f5a49210ea 100644 --- a/osu.Game.Rulesets.Mania.Tests.Android/AndroidManifest.xml +++ b/osu.Game.Rulesets.Mania.Tests.Android/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu.Tests.Android/AndroidManifest.xml b/osu.Game.Rulesets.Osu.Tests.Android/AndroidManifest.xml index 45d27dda70..ed4725dd94 100644 --- a/osu.Game.Rulesets.Osu.Tests.Android/AndroidManifest.xml +++ b/osu.Game.Rulesets.Osu.Tests.Android/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Taiko.Tests.Android/AndroidManifest.xml b/osu.Game.Rulesets.Taiko.Tests.Android/AndroidManifest.xml index 452b9683ec..cc88d3080a 100644 --- a/osu.Game.Rulesets.Taiko.Tests.Android/AndroidManifest.xml +++ b/osu.Game.Rulesets.Taiko.Tests.Android/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/osu.Game.Tests.Android/AndroidManifest.xml b/osu.Game.Tests.Android/AndroidManifest.xml index f25b2e5328..6f91fb928c 100644 --- a/osu.Game.Tests.Android/AndroidManifest.xml +++ b/osu.Game.Tests.Android/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file From 912f31dabc7160fbd32ccb0a47fd733e427274e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Aug 2023 23:37:11 +0200 Subject: [PATCH 0221/4092] Declare media permissions in game project for editor usage --- osu.Android/AndroidManifest.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Android/AndroidManifest.xml b/osu.Android/AndroidManifest.xml index fb54c8e151..af102a1e4e 100644 --- a/osu.Android/AndroidManifest.xml +++ b/osu.Android/AndroidManifest.xml @@ -2,4 +2,7 @@ + + + \ No newline at end of file From a942b6ff745193ff9b3b0f8c624d5905a4495225 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 21 Aug 2023 07:27:02 +0200 Subject: [PATCH 0222/4092] Replace inline comment with actual explanation of what's happening --- osu.Game/Database/LegacyBeatmapExporter.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/LegacyBeatmapExporter.cs b/osu.Game/Database/LegacyBeatmapExporter.cs index c00977d072..ece705f685 100644 --- a/osu.Game/Database/LegacyBeatmapExporter.cs +++ b/osu.Game/Database/LegacyBeatmapExporter.cs @@ -72,7 +72,16 @@ namespace osu.Game.Database if (hitObject is not IHasPath hasPath) continue; - // Make sure the last control point is inherit type + // stable's hit object parsing expects the entire slider to use only one type of curve, + // and happens to use the last non-empty curve type read for the entire slider. + // this clear of the last control point type handles an edge case + // wherein the last control point of an otherwise-single-segment slider path has a different type than previous, + // which would lead to sliders being mangled when exported back to stable. + // normally, that would be handled by the `BezierConverter.ConvertToModernBezier()` call below, + // which outputs a slider path containing only Bezier control points, + // but a non-inherited last control point is (rightly) not considered to be starting a new segment, + // therefore it would fail to clear the `CountSegments() <= 1` check. + // by clearing explicitly we both fix the issue and avoid unnecessary conversions to Bezier. if (hasPath.Path.ControlPoints.Count > 1) hasPath.Path.ControlPoints[^1].Type = null; From dd1ac461db2c779c5f9a24bfedbdb3437f503c6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 21 Aug 2023 08:23:58 +0200 Subject: [PATCH 0223/4092] Reformat xmldoc --- osu.Game/Rulesets/Objects/SliderPath.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 1c02b18a0f..34113285a4 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -201,13 +201,20 @@ namespace osu.Game.Rulesets.Objects /// Returns the progress values at which (control point) segments of the path end. /// Ranges from 0 (beginning of the path) to 1 (end of the path) to infinity (beyond the end of the path). /// - /// In case is less than , + /// + /// truncates the progression values to [0,1], + /// so you can't use this method in conjunction with that one to retrieve the positions of segment ends beyond the end of the path. + /// + /// + /// + /// In case is less than , /// the last segment ends after the end of the path, hence it returns a value greater than 1. - /// + /// + /// /// In case is greater than , - /// the last segment ends before the end of the path, hence it returns a value less than 1. - /// truncates the progression values to [0,1], - /// so you can't use this method to retrieve the positions of segment ends beyond the end of the path. + /// the last segment ends before the end of the path, hence it returns a value less than 1. + /// + /// public IEnumerable GetSegmentEnds() { ensureValid(); From 479c463751e0159049bb63fe27937072a9b836c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 21 Aug 2023 08:27:08 +0200 Subject: [PATCH 0224/4092] Explain why segment end positions are not recovered in test --- osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs index 16e4ae13d9..635d9f9604 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs @@ -207,6 +207,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("shorten last segment", () => path.ExpectedDistance.Value = 150); AddAssert("segment ends are correct", () => path.GetSegmentEnds(), () => Is.EqualTo(distances.Select(d => d / 150))); + // see remarks in `GetSegmentEnds()` xmldoc (`SliderPath.PositionAt()` clamps progress to [0,1]). AddAssert("segment end positions not recovered", () => path.GetSegmentEnds().Select(p => path.PositionAt(p)), () => Is.EqualTo(new[] { positions[1], From 5009fd379421f928bca35028b00e6b88374bdb5e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 20 Aug 2023 17:57:45 +0900 Subject: [PATCH 0225/4092] Add test coverage of song bar crash --- .../Components/TestSceneSongBar.cs | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs b/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs index 0f31192a9c..d52b453185 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs @@ -2,27 +2,34 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Beatmaps.Legacy; -using osu.Game.Tests.Visual; using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; namespace osu.Game.Tournament.Tests.Components { [TestFixture] - public partial class TestSceneSongBar : OsuTestScene + public partial class TestSceneSongBar : TournamentTestScene { - [Cached] - private readonly LadderInfo ladder = new LadderInfo(); - private SongBar songBar = null!; [SetUpSteps] - public void SetUpSteps() + public override void SetUpSteps() { + base.SetUpSteps(); + + AddStep("setup picks bans", () => + { + Ladder.CurrentMatch.Value!.PicksBans.Add(new BeatmapChoice + { + BeatmapID = CreateSampleBeatmap().OnlineID, + Team = TeamColour.Red, + Type = ChoiceType.Pick, + }); + }); + AddStep("create bar", () => Child = songBar = new SongBar { RelativeSizeAxes = Axes.X, @@ -38,12 +45,14 @@ namespace osu.Game.Tournament.Tests.Components AddStep("set beatmap", () => { var beatmap = CreateAPIBeatmap(Ruleset.Value); + beatmap.CircleSize = 3.4f; beatmap.ApproachRate = 6.8f; beatmap.OverallDifficulty = 5.5f; beatmap.StarRating = 4.56f; beatmap.Length = 123456; beatmap.BPM = 133; + beatmap.OnlineID = CreateSampleBeatmap().OnlineID; songBar.Beatmap = new TournamentBeatmap(beatmap); }); From 1067769b24e924bb34d4cc8975e94e162c7198c7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Aug 2023 15:33:58 +0900 Subject: [PATCH 0226/4092] Remove masking on song bar Turns out this breaks when a border style is applied for picks/bans, and it wasn't doing much for visuals anyway. --- osu.Game.Tournament/Components/SongBar.cs | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/osu.Game.Tournament/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs index 3d060600f7..cde826628e 100644 --- a/osu.Game.Tournament/Components/SongBar.cs +++ b/osu.Game.Tournament/Components/SongBar.cs @@ -234,7 +234,7 @@ namespace osu.Game.Tournament.Components } } }, - new UnmaskedTournamentBeatmapPanel(beatmap) + new TournamentBeatmapPanel(beatmap) { RelativeSizeAxes = Axes.X, Width = 0.5f, @@ -277,18 +277,4 @@ namespace osu.Game.Tournament.Components } } } - - internal partial class UnmaskedTournamentBeatmapPanel : TournamentBeatmapPanel - { - public UnmaskedTournamentBeatmapPanel(IBeatmapInfo? beatmap, string mod = "") - : base(beatmap, mod) - { - } - - [BackgroundDependencyLoader] - private void load() - { - Masking = false; - } - } } From f03c64462e5bd6a21626e7030cfaaf13a50c2e78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 21 Aug 2023 08:58:49 +0200 Subject: [PATCH 0227/4092] Better convey meaning of zero last year placement via tooltip --- osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index 241692d515..250d5acaae 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -10,7 +10,9 @@ using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Overlays.Settings; using osu.Game.Tournament.Models; @@ -128,7 +130,7 @@ namespace osu.Game.Tournament.Screens.Editors Width = 0.2f, Current = Model.Seed }, - new SettingsSlider + new SettingsSlider { LabelText = "Last Year Placement", Width = 0.33f, @@ -175,6 +177,11 @@ namespace osu.Game.Tournament.Screens.Editors }; } + private partial class LastYearPlacementSlider : RoundedSliderBar + { + public override LocalisableString TooltipText => Current.Value == 0 ? "N/A" : base.TooltipText; + } + public partial class PlayerEditor : CompositeDrawable { private readonly TournamentTeam team; From 827d48adcc5fb3d334c5175879898b604871b4d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Aug 2023 16:10:48 +0900 Subject: [PATCH 0228/4092] Fix test coverage not actually covering crash --- osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs b/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs index d52b453185..e0444b6126 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs @@ -14,6 +14,7 @@ namespace osu.Game.Tournament.Tests.Components public partial class TestSceneSongBar : TournamentTestScene { private SongBar songBar = null!; + private TournamentBeatmap ladderBeatmap = null!; [SetUpSteps] public override void SetUpSteps() @@ -22,9 +23,10 @@ namespace osu.Game.Tournament.Tests.Components AddStep("setup picks bans", () => { + ladderBeatmap = CreateSampleBeatmap(); Ladder.CurrentMatch.Value!.PicksBans.Add(new BeatmapChoice { - BeatmapID = CreateSampleBeatmap().OnlineID, + BeatmapID = ladderBeatmap.OnlineID, Team = TeamColour.Red, Type = ChoiceType.Pick, }); @@ -52,7 +54,7 @@ namespace osu.Game.Tournament.Tests.Components beatmap.StarRating = 4.56f; beatmap.Length = 123456; beatmap.BPM = 133; - beatmap.OnlineID = CreateSampleBeatmap().OnlineID; + beatmap.OnlineID = ladderBeatmap.OnlineID; songBar.Beatmap = new TournamentBeatmap(beatmap); }); From e7d61e00022a4a09e8164eb809e2a1dc6b34dfa6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Aug 2023 17:59:24 +0900 Subject: [PATCH 0229/4092] Fix star fountain directions not matching stable --- .../Visual/Menus/TestSceneStarFountain.cs | 2 +- osu.Game/Screens/Menu/KiaiMenuFountains.cs | 30 +++++++++++++++---- osu.Game/Screens/Menu/StarFountain.cs | 6 ++-- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs b/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs index b12f3e7946..bb327e5962 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Menus foreach (var fountain in Children.OfType()) { if (RNG.NextSingle() > 0.8f) - fountain.Shoot(); + fountain.Shoot(RNG.Next(-1, 2)); } }, 150); } diff --git a/osu.Game/Screens/Menu/KiaiMenuFountains.cs b/osu.Game/Screens/Menu/KiaiMenuFountains.cs index a4d58e398a..07c06dcdb9 100644 --- a/osu.Game/Screens/Menu/KiaiMenuFountains.cs +++ b/osu.Game/Screens/Menu/KiaiMenuFountains.cs @@ -2,10 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Graphics; +using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Containers; @@ -13,6 +13,9 @@ namespace osu.Game.Screens.Menu { public partial class KiaiMenuFountains : BeatSyncedContainer { + private StarFountain leftFountain = null!; + private StarFountain rightFountain = null!; + [BackgroundDependencyLoader] private void load() { @@ -20,13 +23,13 @@ namespace osu.Game.Screens.Menu Children = new[] { - new StarFountain + leftFountain = new StarFountain { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, X = 250, }, - new StarFountain + rightFountain = new StarFountain { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, @@ -58,8 +61,25 @@ namespace osu.Game.Screens.Menu if (lastTrigger != null && Clock.CurrentTime - lastTrigger < 500) return; - foreach (var fountain in Children.OfType()) - fountain.Shoot(); + int direction = RNG.Next(-1, 2); + + switch (direction) + { + case -1: + leftFountain.Shoot(1); + rightFountain.Shoot(-1); + break; + + case 0: + leftFountain.Shoot(0); + rightFountain.Shoot(0); + break; + + case 1: + leftFountain.Shoot(-1); + rightFountain.Shoot(1); + break; + } lastTrigger = Clock.CurrentTime; } diff --git a/osu.Game/Screens/Menu/StarFountain.cs b/osu.Game/Screens/Menu/StarFountain.cs index 0d35f6e0e0..fd59ec3573 100644 --- a/osu.Game/Screens/Menu/StarFountain.cs +++ b/osu.Game/Screens/Menu/StarFountain.cs @@ -23,7 +23,7 @@ namespace osu.Game.Screens.Menu InternalChild = spewer = new StarFountainSpewer(); } - public void Shoot() => spewer.Shoot(); + public void Shoot(int direction) => spewer.Shoot(direction); protected override void SkinChanged(ISkinSource skin) { @@ -81,10 +81,10 @@ namespace osu.Game.Screens.Menu return lastShootDirection * x_velocity_from_direction * (float)(1 - 2 * (Clock.CurrentTime - lastShootTime!.Value) / shoot_duration) + getRandomVariance(x_velocity_random_variance); } - public void Shoot() + public void Shoot(int direction) { lastShootTime = Clock.CurrentTime; - lastShootDirection = RNG.Next(-1, 2); + lastShootDirection = direction; } private static float getRandomVariance(float variance) => RNG.NextSingle(-variance, variance); From 5f040a991b3f1492c97d247799a3245b2b825ee1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Aug 2023 19:05:20 +0900 Subject: [PATCH 0230/4092] Fix potential crash when loading menu items due to cross-thread ops --- osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs index eb046932e6..2f2cb7e5f8 100644 --- a/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs @@ -40,8 +40,14 @@ namespace osu.Game.Graphics.UserInterface AddInternal(hoverClickSounds = new HoverClickSounds()); updateTextColour(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); Item.Action.BindDisabledChanged(_ => updateState(), true); + FinishTransforms(); } private void updateTextColour() From 662073c47220d513ed3ef510d70ab0ccb850e334 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Aug 2023 19:35:04 +0900 Subject: [PATCH 0231/4092] Fix some incorrect comments / test step descriptions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs | 2 +- osu.Game/Scoring/ScoreInfo.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs index 23b88b7395..f766faec9a 100644 --- a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs +++ b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Database { ScoreInfo scoreInfo = null!; - AddStep("Add score which requires upgrade (but has no beatmap)", () => + AddStep("Add score which requires upgrade (and has beatmap)", () => { Realm.Write(r => { diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index c3a45332e4..526c4217ef 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -86,7 +86,7 @@ namespace osu.Game.Scoring /// Should be used to ensure we don't repeatedly attempt to update the same scores each startup even though we already know they will fail. /// /// - /// See https://github.com/ppy/osu/issues/24301 for one example of how this can occur(missing beatmap file on disk). + /// See https://github.com/ppy/osu/issues/24301 for one example of how this can occur (missing beatmap file on disk). /// public bool TotalScoreUpgradeFailed { get; set; } From b3e7416972f097242bf8c9fe407dec3567d489d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Aug 2023 19:36:22 +0900 Subject: [PATCH 0232/4092] Rename new flag and update xmldoc to match --- .../Database/BackgroundDataStoreProcessorTests.cs | 4 ++-- osu.Game/BackgroundDataStoreProcessor.cs | 8 ++++---- osu.Game/Scoring/ScoreInfo.cs | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs index f766faec9a..da46392e4b 100644 --- a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs +++ b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs @@ -148,7 +148,7 @@ namespace osu.Game.Tests.Database AddStep("Run background processor", () => Add(new TestBackgroundDataStoreProcessor())); AddUntilStep("Score version upgraded", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(LegacyScoreEncoder.LATEST_VERSION)); - AddAssert("Score not marked as failed", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreUpgradeFailed), () => Is.False); + AddAssert("Score not marked as failed", () => Realm.Run(r => r.Find(scoreInfo.ID)!.BackgroundReprocessingFailed), () => Is.False); } [Test] @@ -174,7 +174,7 @@ namespace osu.Game.Tests.Database AddStep("Run background processor", () => Add(new TestBackgroundDataStoreProcessor())); - AddUntilStep("Score marked as failed", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreUpgradeFailed), () => Is.True); + AddUntilStep("Score marked as failed", () => Realm.Run(r => r.Find(scoreInfo.ID)!.BackgroundReprocessingFailed), () => Is.True); AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(30000002)); } diff --git a/osu.Game/BackgroundDataStoreProcessor.cs b/osu.Game/BackgroundDataStoreProcessor.cs index ae9e9527de..f29b100ee8 100644 --- a/osu.Game/BackgroundDataStoreProcessor.cs +++ b/osu.Game/BackgroundDataStoreProcessor.cs @@ -186,7 +186,7 @@ namespace osu.Game realmAccess.Run(r => { - foreach (var score in r.All().Where(s => !s.TotalScoreUpgradeFailed)) + foreach (var score in r.All().Where(s => !s.BackgroundReprocessingFailed)) { if (score.BeatmapInfo != null && score.Statistics.Sum(kvp => kvp.Value) > 0 @@ -225,7 +225,7 @@ namespace osu.Game catch (Exception e) { Logger.Log(@$"Failed to populate maximum statistics for {id}: {e}"); - realmAccess.Write(r => r.Find(id)!.TotalScoreUpgradeFailed = true); + realmAccess.Write(r => r.Find(id)!.BackgroundReprocessingFailed = true); } } } @@ -235,7 +235,7 @@ namespace osu.Game Logger.Log("Querying for scores that need total score conversion..."); HashSet scoreIds = realmAccess.Run(r => new HashSet(r.All() - .Where(s => !s.TotalScoreUpgradeFailed && s.BeatmapInfo != null && s.TotalScoreVersion == 30000002) + .Where(s => !s.BackgroundReprocessingFailed && s.BeatmapInfo != null && s.TotalScoreVersion == 30000002) .AsEnumerable().Select(s => s.ID))); Logger.Log($"Found {scoreIds.Count} scores which require total score conversion."); @@ -284,7 +284,7 @@ namespace osu.Game catch (Exception e) { Logger.Log($"Failed to convert total score for {id}: {e}"); - realmAccess.Write(r => r.Find(id)!.TotalScoreUpgradeFailed = true); + realmAccess.Write(r => r.Find(id)!.BackgroundReprocessingFailed = true); ++failedCount; } } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 526c4217ef..2efea2105c 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -82,13 +82,13 @@ namespace osu.Game.Scoring public long? LegacyTotalScore { get; set; } /// - /// If an reprocess of total score failed to update this score to the latest version, this flag will become true. - /// Should be used to ensure we don't repeatedly attempt to update the same scores each startup even though we already know they will fail. + /// If background processing of this beatmap failed in some way, this flag will become true. + /// Should be used to ensure we don't repeatedly attempt to reprocess the same scores each startup even though we already know they will fail. /// /// /// See https://github.com/ppy/osu/issues/24301 for one example of how this can occur (missing beatmap file on disk). /// - public bool TotalScoreUpgradeFailed { get; set; } + public bool BackgroundReprocessingFailed { get; set; } public int MaxCombo { get; set; } From 82de7385d1c07dc0bd3e21a46ee6220e82a59244 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 21 Aug 2023 12:59:58 +0200 Subject: [PATCH 0233/4092] Revert "Fix TestSceneFruitRandomness" This reverts commit b9d0a8a9f69fadacf1d1578fccd9101798583d47. --- osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs index 8e7f77285c..de3d9d6530 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs @@ -26,8 +26,6 @@ namespace osu.Game.Rulesets.Catch.Tests AddSliderStep("start time", 500, 600, 0, x => { drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = x; - drawableFruit.RefreshStateTransforms(); - drawableBanana.RefreshStateTransforms(); }); } @@ -46,8 +44,6 @@ namespace osu.Game.Rulesets.Catch.Tests AddStep("Initialize start time", () => { drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = initial_start_time; - drawableFruit.RefreshStateTransforms(); - drawableBanana.RefreshStateTransforms(); fruitRotation = drawableFruit.DisplayRotation; bananaRotation = drawableBanana.DisplayRotation; @@ -58,8 +54,6 @@ namespace osu.Game.Rulesets.Catch.Tests AddStep("change start time", () => { drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = another_start_time; - drawableFruit.RefreshStateTransforms(); - drawableBanana.RefreshStateTransforms(); }); AddAssert("fruit rotation is changed", () => drawableFruit.DisplayRotation != fruitRotation); @@ -70,8 +64,6 @@ namespace osu.Game.Rulesets.Catch.Tests AddStep("reset start time", () => { drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = initial_start_time; - drawableFruit.RefreshStateTransforms(); - drawableBanana.RefreshStateTransforms(); }); AddAssert("rotation and size restored", () => From c7b1c75379983860a2dec8bd2de92d4331a0fc75 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 21 Aug 2023 13:00:01 +0200 Subject: [PATCH 0234/4092] Revert "Fix typo" This reverts commit 90f2acaf0a9d1bfa5ac8d4cc653798604a3fdf21. --- osu.Game/Rulesets/UI/HitObjectContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 76cfc049e3..e919e4d088 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -189,7 +189,7 @@ namespace osu.Game.Rulesets.UI private void unbindUpdated(DrawableHitObject hitObject) { - hitObject.DefaultsApplied -= onDefaultsApplied; + hitObject.DefaultsApplied += onDefaultsApplied; } private void unbindAllUpdated() From 5bc11ed35806ac8fac87c82118ff904905df3127 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 21 Aug 2023 13:02:23 +0200 Subject: [PATCH 0235/4092] Revert "Ensure invariant of monotone time" This reverts commit 5d1ccc2601a739e2d8ed61b8d9b64a0efa580f7f. --- osu.Game/Rulesets/UI/HitObjectContainer.cs | 30 +------------------ osu.Game/Rulesets/UI/Playfield.cs | 35 ++++------------------ 2 files changed, 7 insertions(+), 58 deletions(-) diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index e919e4d088..099be486b3 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -51,11 +51,6 @@ namespace osu.Game.Rulesets.UI [Resolved(CanBeNull = true)] private IPooledHitObjectProvider pooledObjectProvider { get; set; } - /// - /// Invoked when a is updated. - /// - public event Action HitObjectUpdated; - public HitObjectContainer() { RelativeSizeAxes = Axes.Both; @@ -113,7 +108,6 @@ namespace osu.Game.Rulesets.UI drawable.OnNewResult += onNewResult; bindStartTime(drawable); - bindUpdated(drawable); AddInternal(drawable); } @@ -122,7 +116,7 @@ namespace osu.Game.Rulesets.UI drawable.OnNewResult -= onNewResult; unbindStartTime(drawable); - unbindUpdated(drawable); + RemoveInternal(drawable, false); } @@ -182,27 +176,6 @@ namespace osu.Game.Rulesets.UI startTimeMap.Clear(); } - private void bindUpdated(DrawableHitObject hitObject) - { - hitObject.DefaultsApplied += onDefaultsApplied; - } - - private void unbindUpdated(DrawableHitObject hitObject) - { - hitObject.DefaultsApplied += onDefaultsApplied; - } - - private void unbindAllUpdated() - { - foreach (var h in AliveObjects) - unbindUpdated(h); - } - - private void onDefaultsApplied(DrawableHitObject obj) - { - HitObjectUpdated?.Invoke(obj.HitObject); - } - protected override int Compare(Drawable x, Drawable y) { if (!(x is DrawableHitObject xObj) || !(y is DrawableHitObject yObj)) @@ -219,7 +192,6 @@ namespace osu.Game.Rulesets.UI { base.Dispose(isDisposing); unbindAllStartTimes(); - unbindAllUpdated(); } } } diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 9a4d082a06..e9c35555c8 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.UI private readonly HitObjectEntryManager entryManager = new HitObjectEntryManager(); - private readonly LinkedList judgedEntries; + private readonly Stack judgedEntries; /// /// Creates a new . @@ -125,13 +125,12 @@ namespace osu.Game.Rulesets.UI h.NewResult += onNewResult; h.HitObjectUsageBegan += o => HitObjectUsageBegan?.Invoke(o); h.HitObjectUsageFinished += o => HitObjectUsageFinished?.Invoke(o); - h.HitObjectUpdated += onHitObjectUpdated; })); entryManager.OnEntryAdded += onEntryAdded; entryManager.OnEntryRemoved += onEntryRemoved; - judgedEntries = new LinkedList(); + judgedEntries = new Stack(); } [BackgroundDependencyLoader] @@ -271,16 +270,15 @@ namespace osu.Game.Rulesets.UI } // When rewinding, revert future judgements in the reverse order. - while (judgedEntries.Last is not null) + while (judgedEntries.Count > 0) { - var result = judgedEntries.Last.Value.Result; + var result = judgedEntries.Peek().Result; Debug.Assert(result?.RawTime != null); if (Time.Current >= result.RawTime.Value) break; - revertResult(judgedEntries.Last.Value); - judgedEntries.RemoveLast(); + revertResult(judgedEntries.Pop()); } } @@ -473,31 +471,10 @@ namespace osu.Game.Rulesets.UI #endregion - private void onHitObjectUpdated(HitObject _) - { - // The time of judged entries may have changed, so we need to re-sort the list to preserve the invariant of monotone time. - // Insertion sort on linked-list is O(n) for nearly-sorted lists, which is the case here. - var current = judgedEntries.First; - - while (current?.Next is not null) - { - var next = current.Next; - - if (current.Value.Result?.RawTime > next.Value.Result?.RawTime) - { - judgedEntries.Remove(next); - judgedEntries.AddBefore(current, next); - current = next.Previous; - } - else - current = next; - } - } - private void onNewResult(DrawableHitObject drawable, JudgementResult result) { Debug.Assert(result != null && drawable.Entry?.Result == result && result.RawTime != null); - judgedEntries.AddLast(drawable.Entry.AsNonNull()); + judgedEntries.Push(drawable.Entry.AsNonNull()); NewResult?.Invoke(drawable, result); } From c82e997644529adb475bec516ead328d260c1fda Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 21 Aug 2023 13:02:41 +0200 Subject: [PATCH 0236/4092] Revert "Revert "Fix TestSceneFruitRandomness"" This reverts commit 82de7385d1c07dc0bd3e21a46ee6220e82a59244. --- osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs index de3d9d6530..8e7f77285c 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs @@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Catch.Tests AddSliderStep("start time", 500, 600, 0, x => { drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = x; + drawableFruit.RefreshStateTransforms(); + drawableBanana.RefreshStateTransforms(); }); } @@ -44,6 +46,8 @@ namespace osu.Game.Rulesets.Catch.Tests AddStep("Initialize start time", () => { drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = initial_start_time; + drawableFruit.RefreshStateTransforms(); + drawableBanana.RefreshStateTransforms(); fruitRotation = drawableFruit.DisplayRotation; bananaRotation = drawableBanana.DisplayRotation; @@ -54,6 +58,8 @@ namespace osu.Game.Rulesets.Catch.Tests AddStep("change start time", () => { drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = another_start_time; + drawableFruit.RefreshStateTransforms(); + drawableBanana.RefreshStateTransforms(); }); AddAssert("fruit rotation is changed", () => drawableFruit.DisplayRotation != fruitRotation); @@ -64,6 +70,8 @@ namespace osu.Game.Rulesets.Catch.Tests AddStep("reset start time", () => { drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = initial_start_time; + drawableFruit.RefreshStateTransforms(); + drawableBanana.RefreshStateTransforms(); }); AddAssert("rotation and size restored", () => From e283aa2843104bf58bf027486cee0d595c65fa3a Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 21 Aug 2023 13:09:31 +0200 Subject: [PATCH 0237/4092] Update inline comments --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index f141263e27..c442fac0b8 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -330,7 +330,7 @@ namespace osu.Game.Rulesets.Objects.Drawables ClearNestedHitObjects(); // Changes in state trigger defaults applied trigger state updates. - // When a new hitobject is applied, OnApply() automatically performs a state update anyway. + // When a new hitobject is applied, OnApply() automatically performs a state update. HitObject.DefaultsApplied -= onDefaultsApplied; entry.RevertResult -= onRevertResult; @@ -391,6 +391,8 @@ namespace osu.Game.Rulesets.Objects.Drawables Apply(Entry); // Applied defaults indicate a change in hit object state. + // We need to update the judgement result time to the new end time + // and update state to ensure the hit object fades out at the correct time. if (Result is not null) { Result.TimeOffset = 0; From bdac05263164d51af00fa23d4fe84f1e1fcf905c Mon Sep 17 00:00:00 2001 From: tsrk Date: Mon, 21 Aug 2023 15:29:41 +0200 Subject: [PATCH 0238/4092] refactor(MessageNotifier): apply changes required by framework --- osu.Game/Online/Chat/MessageNotifier.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index b91cf06847..de38d3ef26 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -127,7 +127,7 @@ namespace osu.Game.Online.Chat if (!notifyOnPrivateMessage.Value || channel.Type != ChannelType.PM) return false; - (host as DesktopGameHost)?.FlashWindow(); + host.Window?.Flash(); notifications.Post(new PrivateMessageNotification(message, channel)); return true; @@ -137,7 +137,7 @@ namespace osu.Game.Online.Chat { if (!notifyOnUsername.Value || !CheckContainsUsername(message.Content, localUser.Value.Username)) return; - (host as DesktopGameHost)?.FlashWindow(); + host.Window?.Flash(); notifications.Post(new MentionNotification(message, channel)); } From 8533cba0bf6a02b05b8e9bfe99aaee2822dfe5a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 21 Aug 2023 17:27:05 +0200 Subject: [PATCH 0239/4092] Fix mismatching schema version in comment --- osu.Game/Database/RealmAccess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 36781b0454..db4f0d9864 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -83,7 +83,7 @@ namespace osu.Game.Database /// 31 2023-06-26 Add Version and LegacyTotalScore to ScoreInfo, set Version to 30000002 and copy TotalScore into LegacyTotalScore for legacy scores. /// 32 2023-07-09 Populate legacy scores with the ScoreV2 mod (and restore TotalScore to the legacy total for such scores) using replay files. /// 33 2023-08-16 Reset default chat toggle key binding to avoid conflict with newly added leaderboard toggle key binding. - /// 35 2023-08-21 Add TotalScoreUpgradeFailed flag to ScoreInfo to track upgrade failures. + /// 34 2023-08-21 Add TotalScoreUpgradeFailed flag to ScoreInfo to track upgrade failures. /// private const int schema_version = 34; From 273dcf9150f441c475c524455b12376a96d3930f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 21 Aug 2023 17:44:35 +0200 Subject: [PATCH 0240/4092] Also update the reference to added flag in schema change breakdown --- osu.Game/Database/RealmAccess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index db4f0d9864..cd97bb6430 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -83,7 +83,7 @@ namespace osu.Game.Database /// 31 2023-06-26 Add Version and LegacyTotalScore to ScoreInfo, set Version to 30000002 and copy TotalScore into LegacyTotalScore for legacy scores. /// 32 2023-07-09 Populate legacy scores with the ScoreV2 mod (and restore TotalScore to the legacy total for such scores) using replay files. /// 33 2023-08-16 Reset default chat toggle key binding to avoid conflict with newly added leaderboard toggle key binding. - /// 34 2023-08-21 Add TotalScoreUpgradeFailed flag to ScoreInfo to track upgrade failures. + /// 34 2023-08-21 Add BackgroundReprocessingFailed flag to ScoreInfo to track upgrade failures. /// private const int schema_version = 34; From 5454d1caa1927428fe339e3d5b47ddfe45f9dfb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 6 Aug 2023 17:40:55 +0200 Subject: [PATCH 0241/4092] Remove global action container input queue workaround As described in #24248, the workaround employed by `GlobalActionContainer`, wherein it tried to handle actions with priority before its children by being placed in front of the children and not _actually containing_ said children, is blocking the resolution of some rather major input handling issues that allow key releases to be received by deparented drawables. To resolve, migrate `GlobalActionContainer` to use `Prioritised`, which can be done without regressing certain mouse button flows after ppy/osu-framework#5966. --- .../Input/Bindings/GlobalActionContainer.cs | 34 +++++-------------- osu.Game/OsuGameBase.cs | 19 ++++++----- .../Visual/OsuManualInputManagerTestScene.cs | 8 ++++- 3 files changed, 25 insertions(+), 36 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 9a0a2d5c15..296232d9ea 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -3,33 +3,26 @@ using System.Collections.Generic; using System.Linq; -using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Localisation; namespace osu.Game.Input.Bindings { - public partial class GlobalActionContainer : DatabasedKeyBindingContainer, IHandleGlobalKeyboardInput + public partial class GlobalActionContainer : DatabasedKeyBindingContainer, IHandleGlobalKeyboardInput, IKeyBindingHandler { - private readonly Drawable? handler; - - private InputManager? parentInputManager; + private readonly IKeyBindingHandler? handler; public GlobalActionContainer(OsuGameBase? game) : base(matchingMode: KeyCombinationMatchingMode.Modifiers) { - if (game is IKeyBindingHandler) - handler = game; + if (game is IKeyBindingHandler h) + handler = h; } - protected override void LoadComplete() - { - base.LoadComplete(); - - parentInputManager = GetContainingInputManager(); - } + protected override bool Prioritised => true; // IMPORTANT: Take care when changing order of the items in the enumerable. // It is used to decide the order of precedence, with the earlier items having higher precedence. @@ -161,20 +154,9 @@ namespace osu.Game.Input.Bindings new KeyBinding(InputKey.F3, GlobalAction.MusicPlay) }; - protected override IEnumerable KeyBindingInputQueue - { - get - { - // To ensure the global actions are handled with priority, this GlobalActionContainer is actually placed after game content. - // It does not contain children as expected, so we need to forward the NonPositionalInputQueue from the parent input manager to correctly - // allow the whole game to handle these actions. + public bool OnPressed(KeyBindingPressEvent e) => handler?.OnPressed(e) == true; - // An eventual solution to this hack is to create localised action containers for individual components like SongSelect, but this will take some rearranging. - var inputQueue = parentInputManager?.NonPositionalInputQueue ?? base.KeyBindingInputQueue; - - return handler != null ? inputQueue.Prepend(handler) : inputQueue; - } - } + public void OnReleased(KeyBindingReleaseEvent e) => handler?.OnReleased(e); } public enum GlobalAction diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 6737caa5f9..75b46a0a4d 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -392,17 +392,18 @@ namespace osu.Game { SafeAreaOverrideEdges = SafeAreaOverrideEdges, RelativeSizeAxes = Axes.Both, - Child = CreateScalingContainer().WithChildren(new Drawable[] + Child = CreateScalingContainer().WithChild(globalBindings = new GlobalActionContainer(this) { - (GlobalCursorDisplay = new GlobalCursorDisplay + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both - }).WithChild(content = new OsuTooltipContainer(GlobalCursorDisplay.MenuCursor) - { - RelativeSizeAxes = Axes.Both - }), - // to avoid positional input being blocked by children, ensure the GlobalActionContainer is above everything. - globalBindings = new GlobalActionContainer(this) + (GlobalCursorDisplay = new GlobalCursorDisplay + { + RelativeSizeAxes = Axes.Both + }).WithChild(content = new OsuTooltipContainer(GlobalCursorDisplay.MenuCursor) + { + RelativeSizeAxes = Axes.Both + }), + } }) }); diff --git a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs index 37260b3b13..ffe40243ab 100644 --- a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs +++ b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs @@ -57,7 +57,13 @@ namespace osu.Game.Tests.Visual } if (CreateNestedActionContainer) - mainContent.Add(new GlobalActionContainer(null)); + { + var globalActionContainer = new GlobalActionContainer(null) + { + Child = mainContent + }; + mainContent = globalActionContainer; + } base.Content.AddRange(new Drawable[] { From 9f4f81c150895ddc08bb4680bbcbd981a03f9d0d Mon Sep 17 00:00:00 2001 From: Wleter Date: Mon, 21 Aug 2023 19:36:11 +0200 Subject: [PATCH 0242/4092] accumulating negative scaling --- .../SkinEditor/SkinSelectionHandler.cs | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index a952cf3035..c90a1d8edf 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -31,6 +31,8 @@ namespace osu.Game.Overlays.SkinEditor UpdatePosition = updateDrawablePosition }; + private float accumulatedNegativeScaling; + public override bool HandleScale(Vector2 scale, Anchor anchor) { // convert scale to screen space @@ -71,8 +73,25 @@ namespace osu.Game.Overlays.SkinEditor scale.Y = scale.X / selectionRect.Width * selectionRect.Height; } - // If scaling reverses the selection, don't scale. - if (adjustedRect.Width + scale.X < 0 || adjustedRect.Height + scale.Y < 0) return true; + // If scaling reverses the selection, don't scale and accumulate the amount of scaling. + if (adjustedRect.Width + scale.X < 0 || adjustedRect.Height + scale.Y < 0) + { + accumulatedNegativeScaling += scale.Length; // - new Vector2(selectionRect.Width, selectionRect.Height).Length; + + return true; + } + + // Compensate for accumulated negative scaling. + if (Precision.AlmostBigger(accumulatedNegativeScaling, 0) && !Precision.AlmostEquals(accumulatedNegativeScaling, 0)) + { + float length = scale.Length; + accumulatedNegativeScaling -= length; + + // If the accumulated negative scaling is still positive, don't scale. + if (Precision.AlmostBigger(accumulatedNegativeScaling, 0)) return true; + scale *= Math.Abs(accumulatedNegativeScaling) / length; + accumulatedNegativeScaling = 0; + } if (anchor.HasFlagFast(Anchor.x0)) adjustedRect.X -= scale.X; if (anchor.HasFlagFast(Anchor.y0)) adjustedRect.Y -= scale.Y; @@ -150,6 +169,12 @@ namespace osu.Game.Overlays.SkinEditor public static void ApplyClosestAnchor(Drawable drawable) => applyAnchor(drawable, getClosestAnchor(drawable)); + protected override void OnOperationEnded() + { + base.OnOperationEnded(); + accumulatedNegativeScaling = 0; + } + protected override void OnSelectionChanged() { base.OnSelectionChanged(); From 96c58c86ea85c1e2e85cb578a730be649993351d Mon Sep 17 00:00:00 2001 From: tsrk Date: Mon, 21 Aug 2023 23:36:54 +0200 Subject: [PATCH 0243/4092] refactor: make flashing available in `Notifications` This will be used in `NotificationOverlay` when a `Notification` is posted. --- osu.Game/Online/Chat/MessageNotifier.cs | 4 ---- osu.Game/Overlays/NotificationOverlay.cs | 9 +++++++++ osu.Game/Overlays/Notifications/Notification.cs | 5 +++++ osu.Game/Screens/Play/PlayerLoader.cs | 2 ++ 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index de38d3ef26..65aac723da 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -127,8 +127,6 @@ namespace osu.Game.Online.Chat if (!notifyOnPrivateMessage.Value || channel.Type != ChannelType.PM) return false; - host.Window?.Flash(); - notifications.Post(new PrivateMessageNotification(message, channel)); return true; } @@ -137,8 +135,6 @@ namespace osu.Game.Online.Chat { if (!notifyOnUsername.Value || !CheckContainsUsername(message.Content, localUser.Value.Username)) return; - host.Window?.Flash(); - notifications.Post(new MentionNotification(message, channel)); } diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index c9d09848f8..08c567af82 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -43,6 +43,9 @@ namespace osu.Game.Overlays [Resolved] private AudioManager audio { get; set; } = null!; + [Resolved] + private OsuGame game { get; set; } = null!; + [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); @@ -176,6 +179,12 @@ namespace osu.Game.Overlays playDebouncedSample(notification.PopInSampleName); + if (notification.FlashTaskbar) + { + game.Window?.Flash(notification.IsImportant); + notification.Closed += () => game.Window?.CancelFlash(); + } + if (State.Value == Visibility.Hidden) { notification.IsInToastTray = true; diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 8cdc373417..53fc152c96 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -38,6 +38,11 @@ namespace osu.Game.Overlays.Notifications /// public virtual bool IsImportant => true; + /// + /// Whether this notification should trigger a taskbar flash if the window is un-focused when posted. + /// + public bool FlashTaskbar { get; init; } = true; + /// /// Run on user activating the notification. Return true to close. /// diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 872425e3fd..eccfc4dc7b 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -568,6 +568,7 @@ namespace osu.Game.Screens.Play public MutedNotification() { Text = NotificationsStrings.GameVolumeTooLow; + FlashTaskbar = false; } [BackgroundDependencyLoader] @@ -623,6 +624,7 @@ namespace osu.Game.Screens.Play public BatteryWarningNotification() { Text = NotificationsStrings.BatteryLow; + FlashTaskbar = false; } [BackgroundDependencyLoader] From e321303ef665a623436a9bf0e04cf91aebd3323b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Aug 2023 12:32:42 +0900 Subject: [PATCH 0244/4092] Add application category type to enable game mode on new macOS versions --- osu.iOS/Info.plist | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.iOS/Info.plist b/osu.iOS/Info.plist index 0ce1d952d0..cf51fe995b 100644 --- a/osu.iOS/Info.plist +++ b/osu.iOS/Info.plist @@ -34,9 +34,9 @@ CADisableMinimumFrameDurationOnPhone NSCameraUsageDescription - We don't really use the camera. + We don't really use the camera. NSMicrophoneUsageDescription - We don't really use the microphone. + We don't really use the microphone. UISupportedInterfaceOrientations UIInterfaceOrientationLandscapeRight @@ -130,5 +130,7 @@ Editor + LSApplicationCategoryType + public.app-category.music-games From e8337c592a69e1905e75d459bf5ff6aad6c0d47f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 22 Aug 2023 12:50:13 +0900 Subject: [PATCH 0245/4092] Update framework and apply changes to support masking SSBO --- .../UI/Cursor/CursorTrail.cs | 20 ++++++++++++++----- .../Resources/Shaders/sh_TestVertex.vs | 2 +- osu.Game/Screens/Loader.cs | 5 +---- osu.Game/osu.Game.csproj | 2 +- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index a29faac5a0..0774d34488 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -286,7 +286,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor if (time - part.Time >= 1) continue; - vertexBatch.Add(new TexturedTrailVertex + vertexBatch.Add(new TexturedTrailVertex(renderer) { Position = new Vector2(part.Position.X - size.X * originPosition.X, part.Position.Y + size.Y * (1 - originPosition.Y)), TexturePosition = textureRect.BottomLeft, @@ -295,7 +295,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor Time = part.Time }); - vertexBatch.Add(new TexturedTrailVertex + vertexBatch.Add(new TexturedTrailVertex(renderer) { Position = new Vector2(part.Position.X + size.X * (1 - originPosition.X), part.Position.Y + size.Y * (1 - originPosition.Y)), TexturePosition = textureRect.BottomRight, @@ -304,7 +304,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor Time = part.Time }); - vertexBatch.Add(new TexturedTrailVertex + vertexBatch.Add(new TexturedTrailVertex(renderer) { Position = new Vector2(part.Position.X + size.X * (1 - originPosition.X), part.Position.Y - size.Y * originPosition.Y), TexturePosition = textureRect.TopRight, @@ -313,7 +313,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor Time = part.Time }); - vertexBatch.Add(new TexturedTrailVertex + vertexBatch.Add(new TexturedTrailVertex(renderer) { Position = new Vector2(part.Position.X - size.X * originPosition.X, part.Position.Y - size.Y * originPosition.Y), TexturePosition = textureRect.TopLeft, @@ -362,12 +362,22 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor [VertexMember(1, VertexAttribPointerType.Float)] public float Time; + [VertexMember(1, VertexAttribPointerType.Int)] + private readonly int maskingIndex; + + public TexturedTrailVertex(IRenderer renderer) + { + this = default; + maskingIndex = renderer.CurrentMaskingIndex; + } + public bool Equals(TexturedTrailVertex other) { return Position.Equals(other.Position) && TexturePosition.Equals(other.TexturePosition) && Colour.Equals(other.Colour) - && Time.Equals(other.Time); + && Time.Equals(other.Time) + && maskingIndex == other.maskingIndex; } } } diff --git a/osu.Game.Tests/Resources/Shaders/sh_TestVertex.vs b/osu.Game.Tests/Resources/Shaders/sh_TestVertex.vs index 505554bb33..80ed686ba5 100644 --- a/osu.Game.Tests/Resources/Shaders/sh_TestVertex.vs +++ b/osu.Game.Tests/Resources/Shaders/sh_TestVertex.vs @@ -13,7 +13,7 @@ layout(location = 4) out mediump vec2 v_BlendRange; void main(void) { // Transform from screen space to masking space. - highp vec3 maskingPos = g_ToMaskingSpace * vec3(m_Position, 1.0); + highp vec3 maskingPos = g_MaskingInfo.ToMaskingSpace * vec3(m_Position, 1.0); v_MaskingPosition = maskingPos.xy / maskingPos.z; v_Colour = m_Colour; diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index 372cfe748e..962c7d9d14 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -126,12 +126,9 @@ namespace osu.Game.Screens private void load(ShaderManager manager) { loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE)); - loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.BLUR)); - + loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2_NO_MASKING, FragmentShaderDescriptor.BLUR)); loadTargets.Add(manager.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE)); - loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, "TriangleBorder")); - loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE)); } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 7d4a721c91..08107c2fad 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + From f09b81841810df3e63a9bc6bfa11a369803f0d89 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Aug 2023 13:17:12 +0900 Subject: [PATCH 0246/4092] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 08107c2fad..4c3205178a 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + From be1a712f33bfa755a049d9b2763a22f45f8a1498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Aug 2023 08:54:41 +0200 Subject: [PATCH 0247/4092] Make `OsuGame` dependency nullable --- osu.Game/Overlays/NotificationOverlay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 08c567af82..6dd344ca99 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -44,7 +44,7 @@ namespace osu.Game.Overlays private AudioManager audio { get; set; } = null!; [Resolved] - private OsuGame game { get; set; } = null!; + private OsuGame? game { get; set; } [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); @@ -181,8 +181,8 @@ namespace osu.Game.Overlays if (notification.FlashTaskbar) { - game.Window?.Flash(notification.IsImportant); - notification.Closed += () => game.Window?.CancelFlash(); + game?.Window?.Flash(notification.IsImportant); + notification.Closed += () => game?.Window?.CancelFlash(); } if (State.Value == Visibility.Hidden) From aa29e00578a01768d1ee7bf537e5b2a335c46b30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Aug 2023 08:58:10 +0200 Subject: [PATCH 0248/4092] Remove `FlashTaskbar` and use `IsImportant` directly instead --- osu.Game/Overlays/NotificationOverlay.cs | 4 ++-- osu.Game/Overlays/Notifications/Notification.cs | 5 ----- osu.Game/Screens/Play/PlayerLoader.cs | 2 -- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 6dd344ca99..b93d5f1e12 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -179,9 +179,9 @@ namespace osu.Game.Overlays playDebouncedSample(notification.PopInSampleName); - if (notification.FlashTaskbar) + if (notification.IsImportant) { - game?.Window?.Flash(notification.IsImportant); + game?.Window?.Flash(); notification.Closed += () => game?.Window?.CancelFlash(); } diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 53fc152c96..8cdc373417 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -38,11 +38,6 @@ namespace osu.Game.Overlays.Notifications /// public virtual bool IsImportant => true; - /// - /// Whether this notification should trigger a taskbar flash if the window is un-focused when posted. - /// - public bool FlashTaskbar { get; init; } = true; - /// /// Run on user activating the notification. Return true to close. /// diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index eccfc4dc7b..872425e3fd 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -568,7 +568,6 @@ namespace osu.Game.Screens.Play public MutedNotification() { Text = NotificationsStrings.GameVolumeTooLow; - FlashTaskbar = false; } [BackgroundDependencyLoader] @@ -624,7 +623,6 @@ namespace osu.Game.Screens.Play public BatteryWarningNotification() { Text = NotificationsStrings.BatteryLow; - FlashTaskbar = false; } [BackgroundDependencyLoader] From 142abe1fd0a87a2144ee4f61c40cff1c96ce4605 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Aug 2023 09:00:59 +0200 Subject: [PATCH 0249/4092] Make highlight messages important in order to trigger window flash --- osu.Game/Online/Chat/MessageNotifier.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 65aac723da..56f490cb21 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -182,8 +182,6 @@ namespace osu.Game.Online.Chat private readonly Message message; private readonly Channel channel; - public override bool IsImportant => false; - [BackgroundDependencyLoader] private void load(OsuColour colours, ChatOverlay chatOverlay, INotificationOverlay notificationOverlay) { From bbe4635a5080befefc78cccdf61d196d0941bc5d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Aug 2023 16:07:48 +0900 Subject: [PATCH 0250/4092] Remove unused usings --- osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index dfdf3c58fd..97980c6d18 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.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.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Scoring; From 5dee43815c2051cd2bae237082f0307f68e1a283 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Aug 2023 16:13:01 +0900 Subject: [PATCH 0251/4092] Bump version to allow migration to re-run --- osu.Game/Database/StandardisedScoreMigrationTools.cs | 2 +- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index fbc53b9d70..7a77434f23 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -26,7 +26,7 @@ namespace osu.Game.Database if (score.IsLegacyScore) return false; - if (score.TotalScoreVersion > 30000002) + if (score.TotalScoreVersion > 30000003) return false; // Recalculate the old-style standardised score to see if this was an old lazer score. diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 6868c89d26..a1f984484e 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -30,9 +30,10 @@ namespace osu.Game.Scoring.Legacy /// 30000001: Appends to the end of scores. /// 30000002: Score stored to replay calculated using the Score V2 algorithm. Legacy scores on this version are candidate to Score V1 -> V2 conversion. /// 30000003: First version after converting legacy total score to standardised. + /// 30000004: First version after converting legacy total score to standardised (with combo exponent adjustment). /// /// - public const int LATEST_VERSION = 30000003; + public const int LATEST_VERSION = 30000004; /// /// The first stable-compatible YYYYMMDD format version given to lazer usage of replays. From 5be533578499d25c54e18ea76ec2aae2a32a9b24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Aug 2023 09:37:54 +0200 Subject: [PATCH 0252/4092] Reword comment to be better --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index c442fac0b8..e31656e0ff 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -329,8 +329,8 @@ namespace osu.Game.Rulesets.Objects.Drawables Entry.NestedEntries.RemoveAll(nestedEntry => nestedEntry is SyntheticHitObjectEntry); ClearNestedHitObjects(); - // Changes in state trigger defaults applied trigger state updates. - // When a new hitobject is applied, OnApply() automatically performs a state update. + // Changes to `HitObject` properties trigger default application, which triggers `State` updates. + // When a new hitobject is applied, `OnApply()` automatically performs a state update. HitObject.DefaultsApplied -= onDefaultsApplied; entry.RevertResult -= onRevertResult; From 290d18ad690accc2a54c3290e3403f6c2534fb45 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Aug 2023 17:31:19 +0900 Subject: [PATCH 0253/4092] Split out difficulties in beatmap carousel in a bit of a hacky way Seems like the simplest path forward for now, without a full rewrite. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 82 ++++++++++++++++------ osu.Game/Screens/Select/FilterCriteria.cs | 5 ++ 2 files changed, 65 insertions(+), 22 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 9af9a0ce72..d1a9b4176b 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -78,6 +78,8 @@ namespace osu.Game.Screens.Select private CarouselBeatmapSet? selectedBeatmapSet; + private IEnumerable originalBeatmapSetsDetached = Enumerable.Empty(); + /// /// Raised when the is changed. /// @@ -127,13 +129,29 @@ namespace osu.Game.Screens.Select private void loadBeatmapSets(IEnumerable beatmapSets) { + originalBeatmapSetsDetached = beatmapSets.Detach(); + CarouselRoot newRoot = new CarouselRoot(this); - newRoot.AddItems(beatmapSets.Select(s => createCarouselSet(s.Detach())).OfType()); + if (beatmapsSplitOut) + { + var carouselBeatmapSets = originalBeatmapSetsDetached.SelectMany(s => s.Beatmaps).Select(b => + { + var set = new BeatmapSetInfo(new[] { b }); + return createCarouselSet(set); + }).OfType(); + + newRoot.AddItems(carouselBeatmapSets); + } + else + { + var carouselBeatmapSets = originalBeatmapSetsDetached.Select(createCarouselSet).OfType(); + newRoot.AddItems(carouselBeatmapSets); + } root = newRoot; - if (selectedBeatmapSet != null && !beatmapSets.Contains(selectedBeatmapSet.BeatmapSet)) + if (selectedBeatmapSet != null && !originalBeatmapSetsDetached.Contains(selectedBeatmapSet.BeatmapSet)) selectedBeatmapSet = null; Scroll.Clear(false); @@ -330,8 +348,8 @@ namespace osu.Game.Screens.Select // Only require to action here if the beatmap is missing. // This avoids processing these events unnecessarily when new beatmaps are imported, for example. - if (root.BeatmapSetsByID.TryGetValue(beatmapSet.ID, out var existingSet) - && existingSet.BeatmapSet.Beatmaps.All(b => b.ID != beatmapInfo.ID)) + if (root.BeatmapSetsByID.TryGetValue(beatmapSet.ID, out var existingSets) + && existingSets.SelectMany(s => s.Beatmaps).All(b => b.BeatmapInfo.ID != beatmapInfo.ID)) { UpdateBeatmapSet(beatmapSet.Detach()); } @@ -345,15 +363,18 @@ namespace osu.Game.Screens.Select private void removeBeatmapSet(Guid beatmapSetID) => Schedule(() => { - if (!root.BeatmapSetsByID.TryGetValue(beatmapSetID, out var existingSet)) + if (!root.BeatmapSetsByID.TryGetValue(beatmapSetID, out var existingSets)) return; - foreach (var beatmap in existingSet.Beatmaps) - randomSelectedBeatmaps.Remove(beatmap); + foreach (var set in existingSets) + { + foreach (var beatmap in set.Beatmaps) + randomSelectedBeatmaps.Remove(beatmap); + previouslyVisitedRandomSets.Remove(set); - previouslyVisitedRandomSets.Remove(existingSet); + root.RemoveItem(set); + } - root.RemoveItem(existingSet); itemsCache.Invalidate(); if (!Scroll.UserScrolling) @@ -371,13 +392,16 @@ namespace osu.Game.Screens.Select previouslySelectedID = selectedBeatmap?.BeatmapInfo.ID; var newSet = createCarouselSet(beatmapSet); - var removedSet = root.RemoveChild(beatmapSet.ID); + var removedSets = root.RemoveChild(beatmapSet.ID); - // If we don't remove this here, it may remain in a hidden state until scrolled off screen. - // Doesn't really affect anything during actual user interaction, but makes testing annoying. - var removedDrawable = Scroll.FirstOrDefault(c => c.Item == removedSet); - if (removedDrawable != null) - expirePanelImmediately(removedDrawable); + foreach (var removedSet in removedSets) + { + // If we don't remove this here, it may remain in a hidden state until scrolled off screen. + // Doesn't really affect anything during actual user interaction, but makes testing annoying. + var removedDrawable = Scroll.FirstOrDefault(c => c.Item == removedSet); + if (removedDrawable != null) + expirePanelImmediately(removedDrawable); + } if (newSet != null) { @@ -632,6 +656,8 @@ namespace osu.Game.Screens.Select applyActiveCriteria(debounce); } + private bool beatmapsSplitOut; + private void applyActiveCriteria(bool debounce, bool alwaysResetScrollPosition = true) { PendingFilter?.Cancel(); @@ -652,6 +678,13 @@ namespace osu.Game.Screens.Select { PendingFilter = null; + if (activeCriteria.SplitOutDifficulties != beatmapsSplitOut) + { + beatmapsSplitOut = activeCriteria.SplitOutDifficulties; + loadBeatmapSets(originalBeatmapSetsDetached); + return; + } + root.Filter(activeCriteria); itemsCache.Invalidate(); @@ -1055,7 +1088,7 @@ namespace osu.Game.Screens.Select // May only be null during construction (State.Value set causes PerformSelection to be triggered). private readonly BeatmapCarousel? carousel; - public readonly Dictionary BeatmapSetsByID = new Dictionary(); + public readonly Dictionary> BeatmapSetsByID = new Dictionary>(); public CarouselRoot(BeatmapCarousel carousel) { @@ -1069,20 +1102,25 @@ namespace osu.Game.Screens.Select public override void AddItem(CarouselItem i) { CarouselBeatmapSet set = (CarouselBeatmapSet)i; - BeatmapSetsByID.Add(set.BeatmapSet.ID, set); + if (BeatmapSetsByID.TryGetValue(set.BeatmapSet.ID, out var sets)) + sets.Add(set); + else + BeatmapSetsByID.Add(set.BeatmapSet.ID, new List { set }); base.AddItem(i); } - public CarouselBeatmapSet? RemoveChild(Guid beatmapSetID) + public IEnumerable RemoveChild(Guid beatmapSetID) { - if (BeatmapSetsByID.TryGetValue(beatmapSetID, out var carouselBeatmapSet)) + if (BeatmapSetsByID.TryGetValue(beatmapSetID, out var carouselBeatmapSets)) { - RemoveItem(carouselBeatmapSet); - return carouselBeatmapSet; + foreach (var set in carouselBeatmapSets) + RemoveItem(set); + + return carouselBeatmapSets; } - return null; + return Enumerable.Empty(); } public override void RemoveItem(CarouselItem i) diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index ab4f85fc92..a2ae114126 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -19,6 +19,11 @@ namespace osu.Game.Screens.Select public GroupMode Group; public SortMode Sort; + /// + /// Whether the display of beatmap sets should be split apart per-difficulty for the current criteria. + /// + public bool SplitOutDifficulties => Sort == SortMode.Difficulty; + public BeatmapSetInfo? SelectedBeatmapSet; public OptionalRange StarDifficulty; From 2b1c6ae612cb4e6cecbd6736748da942fa724e39 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Aug 2023 18:38:23 +0900 Subject: [PATCH 0254/4092] Ensure ID is maintained in temporary `BeatmapSetInfo`s --- osu.Game/Screens/Select/BeatmapCarousel.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index d1a9b4176b..5157e37a31 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -137,8 +137,10 @@ namespace osu.Game.Screens.Select { var carouselBeatmapSets = originalBeatmapSetsDetached.SelectMany(s => s.Beatmaps).Select(b => { - var set = new BeatmapSetInfo(new[] { b }); - return createCarouselSet(set); + return createCarouselSet(new BeatmapSetInfo(new[] { b }) + { + ID = b.BeatmapSet!.ID, + }); }).OfType(); newRoot.AddItems(carouselBeatmapSets); From ecbf0f138e2c7b3cf5716020a1aad4f8a834dfd7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Aug 2023 18:38:43 +0900 Subject: [PATCH 0255/4092] Fix incorrect handling when new beatmaps arrive --- osu.Game/Screens/Select/BeatmapCarousel.cs | 35 ++++++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 5157e37a31..2227eb801a 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -393,7 +393,6 @@ namespace osu.Game.Screens.Select if (selectedBeatmapSet?.BeatmapSet.ID == beatmapSet.ID) previouslySelectedID = selectedBeatmap?.BeatmapInfo.ID; - var newSet = createCarouselSet(beatmapSet); var removedSets = root.RemoveChild(beatmapSet.ID); foreach (var removedSet in removedSets) @@ -405,13 +404,37 @@ namespace osu.Game.Screens.Select expirePanelImmediately(removedDrawable); } - if (newSet != null) + if (beatmapsSplitOut) { - root.AddItem(newSet); + foreach (var beatmap in beatmapSet.Beatmaps) + { + var newSet = createCarouselSet(new BeatmapSetInfo(new[] { beatmap }) + { + ID = beatmapSet.ID + }); - // check if we can/need to maintain our current selection. - if (previouslySelectedID != null) - select((CarouselItem?)newSet.Beatmaps.FirstOrDefault(b => b.BeatmapInfo.ID == previouslySelectedID) ?? newSet); + if (newSet != null) + { + root.AddItem(newSet); + + // check if we can/need to maintain our current selection. + if (previouslySelectedID != null) + select((CarouselItem?)newSet.Beatmaps.FirstOrDefault(b => b.BeatmapInfo.ID == previouslySelectedID) ?? newSet); + } + } + } + else + { + var newSet = createCarouselSet(beatmapSet); + + if (newSet != null) + { + root.AddItem(newSet); + + // check if we can/need to maintain our current selection. + if (previouslySelectedID != null) + select((CarouselItem?)newSet.Beatmaps.FirstOrDefault(b => b.BeatmapInfo.ID == previouslySelectedID) ?? newSet); + } } itemsCache.Invalidate(); From 018be4c20f408ee98bb00a3897e2658b4f016596 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Aug 2023 18:40:34 +0900 Subject: [PATCH 0256/4092] Fix selection not being retained when switching between split mode --- osu.Game/Screens/Select/BeatmapCarousel.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 2227eb801a..c5e46a00b6 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -131,6 +131,12 @@ namespace osu.Game.Screens.Select { originalBeatmapSetsDetached = beatmapSets.Detach(); + if (selectedBeatmapSet != null && !originalBeatmapSetsDetached.Contains(selectedBeatmapSet.BeatmapSet)) + selectedBeatmapSet = null; + + var selectedSetBefore = selectedBeatmapSet; + var selectedBetmapBefore = selectedBeatmap; + CarouselRoot newRoot = new CarouselRoot(this); if (beatmapsSplitOut) @@ -148,14 +154,12 @@ namespace osu.Game.Screens.Select else { var carouselBeatmapSets = originalBeatmapSetsDetached.Select(createCarouselSet).OfType(); + newRoot.AddItems(carouselBeatmapSets); } root = newRoot; - if (selectedBeatmapSet != null && !originalBeatmapSetsDetached.Contains(selectedBeatmapSet.BeatmapSet)) - selectedBeatmapSet = null; - Scroll.Clear(false); itemsCache.Invalidate(); ScrollToSelected(); @@ -164,6 +168,15 @@ namespace osu.Game.Screens.Select if (loadedTestBeatmaps) signalBeatmapsLoaded(); + + // Restore selection + if (selectedBetmapBefore != null && selectedSetBefore != null && newRoot.BeatmapSetsByID.TryGetValue(selectedSetBefore.BeatmapSet.ID, out var newSelectionCandidates)) + { + CarouselBeatmap? found = newSelectionCandidates.SelectMany(s => s.Beatmaps).SingleOrDefault(b => b.BeatmapInfo.ID == selectedBetmapBefore.BeatmapInfo.ID); + + if (found != null) + found.State.Value = CarouselItemState.Selected; + } } private readonly List visibleItems = new List(); From c9f611a713916ea36f9a98eb93de4c02c11cbfc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Aug 2023 14:30:55 +0200 Subject: [PATCH 0257/4092] Enable NRT in `TestSceneObjectOrderedHitPolicy` --- .../TestSceneObjectOrderedHitPolicy.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs index ee70441688..8c2d8ea84e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -380,7 +378,7 @@ namespace osu.Game.Rulesets.Osu.Tests () => judgementResults.Single(r => r.HitObject == hitObject).Type, () => Is.EqualTo(result)); } - private void addJudgementAssert(string name, Func hitObject, HitResult result) + private void addJudgementAssert(string name, Func hitObject, HitResult result) { AddAssert($"{name} judgement is {result}", () => judgementResults.Single(r => r.HitObject == hitObject()).Type == result); @@ -392,8 +390,8 @@ namespace osu.Game.Rulesets.Osu.Tests () => Precision.AlmostEquals(judgementResults.Single(r => r.HitObject == hitObject).TimeOffset, offset, 100)); } - private ScoreAccessibleReplayPlayer currentPlayer; - private List judgementResults; + private ScoreAccessibleReplayPlayer currentPlayer = null!; + private List judgementResults = null!; private void performTest(List hitObjects, List frames) { From ab4d47b594dd45358110db8b68a76705e1089b2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Aug 2023 14:37:58 +0200 Subject: [PATCH 0258/4092] Rewrite assertions to use nunit constraints --- .../TestSceneObjectOrderedHitPolicy.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs index 8c2d8ea84e..9ee55d95a5 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs @@ -381,13 +381,13 @@ namespace osu.Game.Rulesets.Osu.Tests private void addJudgementAssert(string name, Func hitObject, HitResult result) { AddAssert($"{name} judgement is {result}", - () => judgementResults.Single(r => r.HitObject == hitObject()).Type == result); + () => judgementResults.Single(r => r.HitObject == hitObject()).Type, () => Is.EqualTo(result)); } private void addJudgementOffsetAssert(OsuHitObject hitObject, double offset) { AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judged at {offset}", - () => Precision.AlmostEquals(judgementResults.Single(r => r.HitObject == hitObject).TimeOffset, offset, 100)); + () => judgementResults.Single(r => r.HitObject == hitObject).TimeOffset, () => Is.EqualTo(offset).Within(100)); } private ScoreAccessibleReplayPlayer currentPlayer = null!; From 9fd59b807f23c27f298d146c25f737305d73ecfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Aug 2023 15:06:06 +0200 Subject: [PATCH 0259/4092] Rewrite `TestSceneObjectOrderedHitPolicy` to not rely on custom hitwindows --- .../TestSceneObjectOrderedHitPolicy.cs | 117 +++++++----------- 1 file changed, 47 insertions(+), 70 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs index 9ee55d95a5..1dd9116da2 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs @@ -7,7 +7,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Screens; -using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Replays; @@ -17,6 +16,7 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -28,8 +28,13 @@ namespace osu.Game.Rulesets.Osu.Tests { public partial class TestSceneObjectOrderedHitPolicy : RateAdjustedBeatmapTestScene { - private const double early_miss_window = 1000; // time after -1000 to -500 is considered a miss - private const double late_miss_window = 500; // time after +500 is considered a miss + private readonly OsuHitWindows referenceHitWindows; + + public TestSceneObjectOrderedHitPolicy() + { + referenceHitWindows = new OsuHitWindows(); + referenceHitWindows.SetDifficulty(0); + } /// /// Tests clicking a future circle before the first circle's start time, while the first circle HAS NOT been judged. @@ -44,12 +49,12 @@ namespace osu.Game.Rulesets.Osu.Tests var hitObjects = new List { - new TestHitCircle + new HitCircle { StartTime = time_first_circle, Position = positionFirstCircle }, - new TestHitCircle + new HitCircle { StartTime = time_second_circle, Position = positionSecondCircle @@ -63,7 +68,8 @@ namespace osu.Game.Rulesets.Osu.Tests addJudgementAssert(hitObjects[0], HitResult.Miss); addJudgementAssert(hitObjects[1], HitResult.Miss); - addJudgementOffsetAssert(hitObjects[0], late_miss_window); + // note lock prevented the object from being hit, so the judgement offset should be very late. + addJudgementOffsetAssert(hitObjects[0], referenceHitWindows.WindowFor(HitResult.Meh)); } /// @@ -79,12 +85,12 @@ namespace osu.Game.Rulesets.Osu.Tests var hitObjects = new List { - new TestHitCircle + new HitCircle { StartTime = time_first_circle, Position = positionFirstCircle }, - new TestHitCircle + new HitCircle { StartTime = time_second_circle, Position = positionSecondCircle @@ -98,7 +104,8 @@ namespace osu.Game.Rulesets.Osu.Tests addJudgementAssert(hitObjects[0], HitResult.Miss); addJudgementAssert(hitObjects[1], HitResult.Miss); - addJudgementOffsetAssert(hitObjects[0], late_miss_window); + // note lock prevented the object from being hit, so the judgement offset should be very late. + addJudgementOffsetAssert(hitObjects[0], referenceHitWindows.WindowFor(HitResult.Meh)); } /// @@ -114,12 +121,12 @@ namespace osu.Game.Rulesets.Osu.Tests var hitObjects = new List { - new TestHitCircle + new HitCircle { StartTime = time_first_circle, Position = positionFirstCircle }, - new TestHitCircle + new HitCircle { StartTime = time_second_circle, Position = positionSecondCircle @@ -133,7 +140,8 @@ namespace osu.Game.Rulesets.Osu.Tests addJudgementAssert(hitObjects[0], HitResult.Miss); addJudgementAssert(hitObjects[1], HitResult.Miss); - addJudgementOffsetAssert(hitObjects[0], late_miss_window); + // note lock prevented the object from being hit, so the judgement offset should be very late. + addJudgementOffsetAssert(hitObjects[0], referenceHitWindows.WindowFor(HitResult.Meh)); } /// @@ -149,12 +157,12 @@ namespace osu.Game.Rulesets.Osu.Tests var hitObjects = new List { - new TestHitCircle + new HitCircle { StartTime = time_first_circle, Position = positionFirstCircle }, - new TestHitCircle + new HitCircle { StartTime = time_second_circle, Position = positionSecondCircle @@ -167,8 +175,8 @@ namespace osu.Game.Rulesets.Osu.Tests new OsuReplayFrame { Time = time_first_circle - 100, Position = positionSecondCircle, Actions = { OsuAction.RightButton } } }); - addJudgementAssert(hitObjects[0], HitResult.Great); - addJudgementAssert(hitObjects[1], HitResult.Great); + addJudgementAssert(hitObjects[0], HitResult.Meh); + addJudgementAssert(hitObjects[1], HitResult.Meh); addJudgementOffsetAssert(hitObjects[0], -200); // time_first_circle - 200 addJudgementOffsetAssert(hitObjects[0], -200); // time_second_circle - first_circle_time - 100 } @@ -186,12 +194,12 @@ namespace osu.Game.Rulesets.Osu.Tests var hitObjects = new List { - new TestHitCircle + new HitCircle { StartTime = time_first_circle, Position = positionFirstCircle }, - new TestHitCircle + new HitCircle { StartTime = time_second_circle, Position = positionSecondCircle @@ -204,8 +212,8 @@ namespace osu.Game.Rulesets.Osu.Tests new OsuReplayFrame { Time = time_first_circle, Position = positionSecondCircle, Actions = { OsuAction.RightButton } } }); - addJudgementAssert(hitObjects[0], HitResult.Great); - addJudgementAssert(hitObjects[1], HitResult.Great); + addJudgementAssert(hitObjects[0], HitResult.Meh); + addJudgementAssert(hitObjects[1], HitResult.Ok); addJudgementOffsetAssert(hitObjects[0], -200); // time_first_circle - 200 addJudgementOffsetAssert(hitObjects[1], -100); // time_second_circle - first_circle_time } @@ -223,19 +231,19 @@ namespace osu.Game.Rulesets.Osu.Tests var hitObjects = new List { - new TestHitCircle + new HitCircle { StartTime = time_circle, Position = positionCircle }, - new TestSlider + new Slider { StartTime = time_slider, Position = positionSlider, Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, - new Vector2(25, 0), + new Vector2(50, 0), }) } }; @@ -265,19 +273,19 @@ namespace osu.Game.Rulesets.Osu.Tests var hitObjects = new List { - new TestHitCircle + new HitCircle { StartTime = time_circle, Position = positionCircle }, - new TestSlider + new Slider { StartTime = time_slider, Position = positionSlider, Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, - new Vector2(25, 0), + new Vector2(50, 0), }) } }; @@ -285,11 +293,11 @@ namespace osu.Game.Rulesets.Osu.Tests performTest(hitObjects, new List { new OsuReplayFrame { Time = time_slider, Position = positionSlider, Actions = { OsuAction.LeftButton } }, - new OsuReplayFrame { Time = time_circle + late_miss_window - 100, Position = positionCircle, Actions = { OsuAction.RightButton } }, - new OsuReplayFrame { Time = time_circle + late_miss_window - 90, Position = positionSlider, Actions = { OsuAction.LeftButton } }, + new OsuReplayFrame { Time = time_circle + referenceHitWindows.WindowFor(HitResult.Meh) - 100, Position = positionCircle, Actions = { OsuAction.RightButton } }, + new OsuReplayFrame { Time = time_circle + referenceHitWindows.WindowFor(HitResult.Meh) - 90, Position = positionSlider, Actions = { OsuAction.LeftButton } }, }); - addJudgementAssert(hitObjects[0], HitResult.Great); + addJudgementAssert(hitObjects[0], HitResult.Ok); addJudgementAssert(hitObjects[1], HitResult.Great); addJudgementAssert("slider head", () => ((Slider)hitObjects[1]).HeadCircle, HitResult.LargeTickHit); addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.LargeTickHit); @@ -302,7 +310,7 @@ namespace osu.Game.Rulesets.Osu.Tests public void TestHitCircleBeforeSpinner() { const double time_spinner = 1500; - const double time_circle = 1800; + const double time_circle = 1600; Vector2 positionCircle = Vector2.Zero; var hitObjects = new List @@ -313,7 +321,7 @@ namespace osu.Game.Rulesets.Osu.Tests Position = new Vector2(256, 192), EndTime = time_spinner + 1000, }, - new TestHitCircle + new HitCircle { StartTime = time_circle, Position = positionCircle @@ -331,7 +339,7 @@ namespace osu.Game.Rulesets.Osu.Tests }); addJudgementAssert(hitObjects[0], HitResult.Great); - addJudgementAssert(hitObjects[1], HitResult.Great); + addJudgementAssert(hitObjects[1], HitResult.Meh); } [Test] @@ -344,12 +352,12 @@ namespace osu.Game.Rulesets.Osu.Tests var hitObjects = new List { - new TestHitCircle + new HitCircle { StartTime = time_circle, Position = positionCircle }, - new TestSlider + new Slider { StartTime = time_slider, Position = positionSlider, @@ -400,7 +408,11 @@ namespace osu.Game.Rulesets.Osu.Tests Beatmap.Value = CreateWorkingBeatmap(new Beatmap { HitObjects = hitObjects, - Difficulty = new BeatmapDifficulty { SliderTickRate = 3 }, + Difficulty = new BeatmapDifficulty + { + OverallDifficulty = 0, + SliderTickRate = 3 + }, BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo @@ -428,28 +440,6 @@ namespace osu.Game.Rulesets.Osu.Tests AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value); } - private class TestHitCircle : HitCircle - { - protected override HitWindows CreateHitWindows() => new TestHitWindows(); - } - - private class TestSlider : Slider - { - public TestSlider() - { - SliderVelocity = 0.1f; - - DefaultsApplied += _ => - { - HeadCircle.HitWindows = new TestHitWindows(); - TailCircle.HitWindows = new TestHitWindows(); - - HeadCircle.HitWindows.SetDifficulty(0); - TailCircle.HitWindows.SetDifficulty(0); - }; - } - } - private class TestSpinner : Spinner { protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) @@ -459,19 +449,6 @@ namespace osu.Game.Rulesets.Osu.Tests } } - private class TestHitWindows : HitWindows - { - private static readonly DifficultyRange[] ranges = - { - new DifficultyRange(HitResult.Great, 500, 500, 500), - new DifficultyRange(HitResult.Miss, early_miss_window, early_miss_window, early_miss_window), - }; - - public override bool IsHitResultAllowed(HitResult result) => result == HitResult.Great || result == HitResult.Miss; - - protected override DifficultyRange[] GetRanges() => ranges; - } - private partial class ScoreAccessibleReplayPlayer : ReplayPlayer { public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; From a1b4a5621592234f4787d26a88d1b03830661c8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Aug 2023 18:04:20 +0200 Subject: [PATCH 0260/4092] Add capability to export ordered object policy test cases for stable crosscheck --- .../TestSceneObjectOrderedHitPolicy.cs | 86 ++++++++++++++++++- 1 file changed, 83 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs index 1dd9116da2..5205400a7e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs @@ -3,12 +3,17 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; using NUnit.Framework; +using osu.Framework.Extensions; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Formats; using osu.Game.Replays; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; @@ -20,6 +25,7 @@ using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using osu.Game.Scoring.Legacy; using osu.Game.Screens.Play; using osu.Game.Tests.Visual; using osuTK; @@ -30,6 +36,13 @@ namespace osu.Game.Rulesets.Osu.Tests { private readonly OsuHitWindows referenceHitWindows; + /// + /// This is provided as a convenience for testing note lock behaviour against osu!stable. + /// Setting this field to a non-null path will cause beatmap files and replays used in all test cases + /// to be exported to disk so that they can be cross-checked against stable. + /// + private readonly string? exportLocation = null; + public TestSceneObjectOrderedHitPolicy() { referenceHitWindows = new OsuHitWindows(); @@ -401,12 +414,21 @@ namespace osu.Game.Rulesets.Osu.Tests private ScoreAccessibleReplayPlayer currentPlayer = null!; private List judgementResults = null!; - private void performTest(List hitObjects, List frames) + private void performTest(List hitObjects, List frames, [CallerMemberName] string testCaseName = "") { - AddStep("load player", () => + IBeatmap playableBeatmap = null!; + Score score = null!; + + AddStep("create beatmap", () => { + var cpi = new ControlPointInfo(); + cpi.Add(0, new TimingControlPoint { BeatLength = 1000 }); Beatmap.Value = CreateWorkingBeatmap(new Beatmap { + Metadata = + { + Title = testCaseName + }, HitObjects = hitObjects, Difficulty = new BeatmapDifficulty { @@ -417,11 +439,69 @@ namespace osu.Game.Rulesets.Osu.Tests { Ruleset = new OsuRuleset().RulesetInfo }, + ControlPointInfo = cpi + }); + playableBeatmap = Beatmap.Value.GetPlayableBeatmap(new OsuRuleset().RulesetInfo); + }); + + AddStep("create score", () => + { + score = new Score + { + Replay = new Replay + { + Frames = new List + { + // required for correct playback in stable + new OsuReplayFrame(0, new Vector2(256, -500)), + new OsuReplayFrame(0, new Vector2(256, -500)) + }.Concat(frames).ToList() + }, + ScoreInfo = + { + Ruleset = new OsuRuleset().RulesetInfo, + BeatmapInfo = playableBeatmap.BeatmapInfo + } + }; + }); + + if (exportLocation != null) + { + AddStep("create beatmap", () => + { + var beatmapEncoder = new LegacyBeatmapEncoder(playableBeatmap, null); + + using (var stream = File.Open(Path.Combine(exportLocation, $"{testCaseName}.osu"), FileMode.Create)) + { + var memoryStream = new MemoryStream(); + using (var writer = new StreamWriter(memoryStream, Encoding.UTF8, leaveOpen: true)) + beatmapEncoder.Encode(writer); + + memoryStream.Seek(0, SeekOrigin.Begin); + memoryStream.CopyTo(stream); + memoryStream.Seek(0, SeekOrigin.Begin); + playableBeatmap.BeatmapInfo.MD5Hash = memoryStream.ComputeMD5Hash(); + } }); + AddStep("export score", () => + { + var scoreToEncode = score.DeepClone(); + scoreToEncode.Replay.Frames = scoreToEncode.Replay.Frames.Cast() + .Select(frame => new OsuReplayFrame(frame.Time + LegacyBeatmapDecoder.EARLY_VERSION_TIMING_OFFSET, frame.Position, frame.Actions.ToArray())) + .ToList(); + + using var stream = File.Open(Path.Combine(exportLocation, $"{testCaseName}.osr"), FileMode.Create); + var encoder = new LegacyScoreEncoder(scoreToEncode, playableBeatmap); + encoder.Encode(stream); + }); + } + + AddStep("load player", () => + { SelectedMods.Value = new[] { new OsuModClassic() }; - var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } }); + var p = new ScoreAccessibleReplayPlayer(score); p.OnLoadComplete += _ => { From 64786aaee8d99de8757648635cf1daa3d7ec75d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 23 Aug 2023 10:43:48 +0200 Subject: [PATCH 0261/4092] Adjust test cases slightly to avoid running into hitwindow edge issue Some note lock test cases do not play out correctly when exported out to stable due to a completely separate issue, namely #11311. Adjust the test cases for now to isolate failure vectors. --- .../TestSceneObjectOrderedHitPolicy.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs index 5205400a7e..fd8973c375 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs @@ -184,14 +184,14 @@ namespace osu.Game.Rulesets.Osu.Tests performTest(hitObjects, new List { - new OsuReplayFrame { Time = time_first_circle - 200, Position = positionFirstCircle, Actions = { OsuAction.LeftButton } }, - new OsuReplayFrame { Time = time_first_circle - 100, Position = positionSecondCircle, Actions = { OsuAction.RightButton } } + new OsuReplayFrame { Time = time_first_circle - 190, Position = positionFirstCircle, Actions = { OsuAction.LeftButton } }, + new OsuReplayFrame { Time = time_first_circle - 90, Position = positionSecondCircle, Actions = { OsuAction.RightButton } } }); addJudgementAssert(hitObjects[0], HitResult.Meh); addJudgementAssert(hitObjects[1], HitResult.Meh); - addJudgementOffsetAssert(hitObjects[0], -200); // time_first_circle - 200 - addJudgementOffsetAssert(hitObjects[0], -200); // time_second_circle - first_circle_time - 100 + addJudgementOffsetAssert(hitObjects[0], -190); // time_first_circle - 190 + addJudgementOffsetAssert(hitObjects[0], -90); // time_second_circle - first_circle_time - 90 } /// @@ -221,13 +221,13 @@ namespace osu.Game.Rulesets.Osu.Tests performTest(hitObjects, new List { - new OsuReplayFrame { Time = time_first_circle - 200, Position = positionFirstCircle, Actions = { OsuAction.LeftButton } }, + new OsuReplayFrame { Time = time_first_circle - 190, Position = positionFirstCircle, Actions = { OsuAction.LeftButton } }, new OsuReplayFrame { Time = time_first_circle, Position = positionSecondCircle, Actions = { OsuAction.RightButton } } }); addJudgementAssert(hitObjects[0], HitResult.Meh); addJudgementAssert(hitObjects[1], HitResult.Ok); - addJudgementOffsetAssert(hitObjects[0], -200); // time_first_circle - 200 + addJudgementOffsetAssert(hitObjects[0], -190); // time_first_circle - 190 addJudgementOffsetAssert(hitObjects[1], -100); // time_second_circle - first_circle_time } @@ -343,7 +343,7 @@ namespace osu.Game.Rulesets.Osu.Tests performTest(hitObjects, new List { - new OsuReplayFrame { Time = time_spinner - 100, Position = positionCircle, Actions = { OsuAction.LeftButton } }, + new OsuReplayFrame { Time = time_spinner - 90, Position = positionCircle, Actions = { OsuAction.LeftButton } }, new OsuReplayFrame { Time = time_spinner + 10, Position = new Vector2(236, 192), Actions = { OsuAction.RightButton } }, new OsuReplayFrame { Time = time_spinner + 20, Position = new Vector2(256, 172), Actions = { OsuAction.RightButton } }, new OsuReplayFrame { Time = time_spinner + 30, Position = new Vector2(276, 192), Actions = { OsuAction.RightButton } }, From 91c2cadb4737839e36d087c02658b4872cd2456a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Aug 2023 19:13:32 +0900 Subject: [PATCH 0262/4092] Add missing colon in mod settings tooltip --- osu.Game/Rulesets/Mods/Mod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index f9812d6c00..6d40826afe 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Mods var bindable = (IBindable)property.GetValue(this)!; if (!bindable.IsDefault) - tooltipTexts.Add($"{attr.Label} {bindable}"); + tooltipTexts.Add($"{attr.Label}: {bindable}"); } return string.Join(", ", tooltipTexts.Where(s => !string.IsNullOrEmpty(s))); From 5555f73e97f22f9c6f0425bc4c9459bef88f3be5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Aug 2023 19:38:18 +0900 Subject: [PATCH 0263/4092] Update test to match new behaviour --- .../SongSelect/TestSceneBeatmapCarousel.cs | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 61f95dc628..b0aff8b4db 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -758,7 +758,7 @@ namespace osu.Game.Tests.Visual.SongSelect } [Test] - public void TestSortingWithFiltered() + public void TestSortingWithDifficultyFiltered() { List sets = new List(); @@ -777,13 +777,32 @@ namespace osu.Game.Tests.Visual.SongSelect loadBeatmaps(sets); + AddStep("Sort by difficulty", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty }, false)); + + checkVisibleItemCount(false, 9); + checkVisibleItemCount(true, 1); + AddStep("Filter to normal", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty, SearchText = "Normal" }, false)); - AddAssert("Check first set at end", () => carousel.BeatmapSets.First().Equals(sets.Last())); - AddAssert("Check last set at start", () => carousel.BeatmapSets.Last().Equals(sets.First())); + checkVisibleItemCount(false, 3); + checkVisibleItemCount(true, 1); + + AddUntilStep("Check all visible sets have one normal", () => + { + return carousel.Items.OfType() + .Where(p => p.IsPresent) + .Count(p => ((CarouselBeatmapSet)p.Item)!.Beatmaps.Single().BeatmapInfo.DifficultyName.StartsWith("Normal", StringComparison.Ordinal)) == 3; + }); AddStep("Filter to insane", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty, SearchText = "Insane" }, false)); - AddAssert("Check first set at start", () => carousel.BeatmapSets.First().Equals(sets.First())); - AddAssert("Check last set at end", () => carousel.BeatmapSets.Last().Equals(sets.Last())); + checkVisibleItemCount(false, 3); + checkVisibleItemCount(true, 1); + + AddUntilStep("Check all visible sets have one insane", () => + { + return carousel.Items.OfType() + .Where(p => p.IsPresent) + .Count(p => ((CarouselBeatmapSet)p.Item)!.Beatmaps.Single().BeatmapInfo.DifficultyName.StartsWith("Insane", StringComparison.Ordinal)) == 3; + }); } [Test] From a64381f8553dd49b9c5c5142aa2934c333b101a4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Aug 2023 19:43:08 +0900 Subject: [PATCH 0264/4092] Add test coverage of add/remove when difficulties are split out --- .../SongSelect/TestSceneBeatmapCarousel.cs | 47 +++++++++++++++---- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index b0aff8b4db..5af6d862b2 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -39,6 +39,7 @@ namespace osu.Game.Tests.Visual.SongSelect private BeatmapInfo currentSelection => carousel.SelectedBeatmapInfo; private const int set_count = 5; + private const int diff_count = 3; [BackgroundDependencyLoader] private void load(RulesetStore rulesets) @@ -501,6 +502,36 @@ namespace osu.Game.Tests.Visual.SongSelect waitForSelection(set_count); } + [Test] + public void TestAddRemoveDifficultySort() + { + loadBeatmaps(); + + AddStep("Sort by difficulty", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty }, false)); + + checkVisibleItemCount(false, set_count * diff_count); + + var firstAdded = TestResources.CreateTestBeatmapSetInfo(diff_count); + var secondAdded = TestResources.CreateTestBeatmapSetInfo(diff_count); + + AddStep("Add new set", () => carousel.UpdateBeatmapSet(firstAdded)); + AddStep("Add new set", () => carousel.UpdateBeatmapSet(secondAdded)); + + checkVisibleItemCount(false, (set_count + 2) * diff_count); + + AddStep("Remove set", () => carousel.RemoveBeatmapSet(firstAdded)); + + checkVisibleItemCount(false, (set_count + 1) * diff_count); + + setSelected(set_count + 1, 1); + + AddStep("Remove set", () => carousel.RemoveBeatmapSet(secondAdded)); + + checkVisibleItemCount(false, (set_count) * diff_count); + + waitForSelection(set_count); + } + [Test] public void TestSelectionEnteringFromEmptyRuleset() { @@ -662,7 +693,7 @@ namespace osu.Game.Tests.Visual.SongSelect for (int i = 0; i < 3; i++) { - var set = TestResources.CreateTestBeatmapSetInfo(3); + var set = TestResources.CreateTestBeatmapSetInfo(diff_count); // only need to set the first as they are a shared reference. var beatmap = set.Beatmaps.First(); @@ -709,7 +740,7 @@ namespace osu.Game.Tests.Visual.SongSelect for (int i = 0; i < 3; i++) { - var set = TestResources.CreateTestBeatmapSetInfo(3); + var set = TestResources.CreateTestBeatmapSetInfo(diff_count); // only need to set the first as they are a shared reference. var beatmap = set.Beatmaps.First(); @@ -768,7 +799,7 @@ namespace osu.Game.Tests.Visual.SongSelect for (int i = 0; i < 3; i++) { - var set = TestResources.CreateTestBeatmapSetInfo(3); + var set = TestResources.CreateTestBeatmapSetInfo(diff_count); set.Beatmaps[0].StarRating = 3 - i; set.Beatmaps[2].StarRating = 6 + i; sets.Add(set); @@ -857,7 +888,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("create hidden set", () => { - hidingSet = TestResources.CreateTestBeatmapSetInfo(3); + hidingSet = TestResources.CreateTestBeatmapSetInfo(diff_count); hidingSet.Beatmaps[1].Hidden = true; hiddenList.Clear(); @@ -904,7 +935,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("add mixed ruleset beatmapset", () => { - testMixed = TestResources.CreateTestBeatmapSetInfo(3); + testMixed = TestResources.CreateTestBeatmapSetInfo(diff_count); for (int i = 0; i <= 2; i++) { @@ -926,7 +957,7 @@ namespace osu.Game.Tests.Visual.SongSelect BeatmapSetInfo testSingle = null; AddStep("add single ruleset beatmapset", () => { - testSingle = TestResources.CreateTestBeatmapSetInfo(3); + testSingle = TestResources.CreateTestBeatmapSetInfo(diff_count); testSingle.Beatmaps.ForEach(b => { b.Ruleset = rulesets.AvailableRulesets.ElementAt(1); @@ -949,7 +980,7 @@ namespace osu.Game.Tests.Visual.SongSelect manySets.Clear(); for (int i = 1; i <= 50; i++) - manySets.Add(TestResources.CreateTestBeatmapSetInfo(3)); + manySets.Add(TestResources.CreateTestBeatmapSetInfo(diff_count)); }); loadBeatmaps(manySets); @@ -1113,7 +1144,7 @@ namespace osu.Game.Tests.Visual.SongSelect { beatmapSets.Add(randomDifficulties ? TestResources.CreateTestBeatmapSetInfo() - : TestResources.CreateTestBeatmapSetInfo(3)); + : TestResources.CreateTestBeatmapSetInfo(diff_count)); } } From 5eac604f8b2cf59a5346956d99a7fea0fdda0cd9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Aug 2023 19:44:39 +0900 Subject: [PATCH 0265/4092] Add coverage of selection retention when difficulties are split out --- .../SongSelect/TestSceneBeatmapCarousel.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 5af6d862b2..daa8c9c4c2 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -1005,6 +1005,43 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("Selection was remembered", () => eagerSelectedIDs.Count == 1); } + [Test] + public void TestCarouselRemembersSelectionDifficultySort() + { + List manySets = new List(); + + AddStep("Populuate beatmap sets", () => + { + manySets.Clear(); + + for (int i = 1; i <= 50; i++) + manySets.Add(TestResources.CreateTestBeatmapSetInfo(diff_count)); + }); + + loadBeatmaps(manySets); + + AddStep("Sort by difficulty", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty }, false)); + + advanceSelection(direction: 1, diff: false); + + for (int i = 0; i < 5; i++) + { + AddStep("Toggle non-matching filter", () => + { + carousel.Filter(new FilterCriteria { SearchText = Guid.NewGuid().ToString() }, false); + }); + + AddStep("Restore no filter", () => + { + carousel.Filter(new FilterCriteria(), false); + eagerSelectedIDs.Add(carousel.SelectedBeatmapSet!.ID); + }); + } + + // always returns to same selection as long as it's available. + AddAssert("Selection was remembered", () => eagerSelectedIDs.Count == 1); + } + [Test] public void TestFilteringByUserStarDifficulty() { From d6aded3ac31b232884f0642bedc793f41b377b2e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Aug 2023 20:11:55 +0900 Subject: [PATCH 0266/4092] 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 8ebfde8047..2d15bce85a 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + OgN&7dK}sTL(*04__BcQcE9OD*(V}qc+dhi>N&n^lpyj43`ykWGYzhJwJ8L zqAI1|WMdHEghVN2v?_IkikBjrzW)9qGWL2R^6tu=X=6su)t<-HXBIc(-CObSymBpL zeemb$cB+RY_;>H~>*77%4WHn<+Wm_L$&R;((dgG%owF!}mFKsyr)LrU0O6P4<@ViCKg7X1_ ziH}HN$4*Vr)A7kK<7H>>fPrcT%{;;SP4k-qd)G(D-2CHSgQNV+gR*T`wy~sum-go? zl)Ms6m)TVngGT0SONR`yxibouA&RSRX{B%dBz^C%3PDIdtP_*+DUrZ2_n4F|5?R!` z5(pN>fS|>ZPrr--7`Ll2BOM_yH#rCsmez5PBYcQ@uBz5&!jd94Aqo>L*k|UjcsrW) z?Z+~)T`a#Q?j467!8im{#F3$PlD&BuMkQa!u&9SR1Tivj8?u8QNXx);pu5I1+otyn6AoAT;?0P7Pbzj!%P)(Z1 z@u%F=H1E=M^EChFkE!~G{-jSjdY-K-?VF&*M1jlhsk*MqAlO!n=@^>)fXT}8{I|2# zZ?%1|Z;{`&F1Uh)%5XAN>TkcR3p4LQ(#Q!90kz9AAWWAyVB_kJZddt=d2(5%$Tm` zNc!^hZq!w8G;)7IUKx!b8|!#_ zeg>1f1#ZSPU*LD;r~2jYx>B9ys5Gu;i5DbDxoX7l;@n;+zdwJg zEF13qVVrxkuie+p;JLo7|3IO`sPoEMm%gVR!O_uemq0HrxxPAhlddr4HP7`DX$w7d z&e$xI1i{WQK2XPDH3@zAXen7hD*-}kmsUQc-eyNw*q;4~`=eA0@e0&DCJV$281&Z8 z;-%iyo8Gql*;ewar^P4++-x(d2$n3TO^7=`=WcM2*A}xoxwFx?!D^t>Q{`F@jVpap zyVFrPvh#lYu^_8PQ#@+75_jyyCRd|jMRG$Z;*TFePTJN6b^gi?oL(Z*Lz}Mil~OU| zp6o==r|tu?0cjVS4PulKwz-X1WW;eHncZ1N#O1MZNUkYHkX;*F7w-?eG5^}{52E5T zA%6(s>5?mhrrlvPFYl=PP&(VLdGzTSKrf%JwF>O<6VT^$AhF`f%P^kzh(_RAoBWx2 z1L`e*CN*5Q3)ejuUL43j@^?S_Q}urRw&4Pv-ITO=w~-91UBHk6-zLT00pGnFfwol| zrnTNgJh;e!+|PW1!wSJi!p0+*f2``3oiVVM>+)LtT$I!Wi1z<{NrZ4;nko{Mw4xDa z&+LGs<7~^@-!qpqy#kod zPyWpBiOzEGVW=Cs_fY94DJo*1nx;^axSEkxNIeilmky3#mfJyPR^gQn+HZ>w2SaAa z^GOf@7~-G`?bqOayF3f9Kx3Rhm+0hHjot8TMtvnOcKIFMk1U3P@l4n@kzzU)!CZjH z%1w+ML>CQqK$dk>KAG1~91<(!Hd@h4A|Qr=h*4yitjB@9ZiHVy)&xXfj=kQ4;A&G* z*Fto@#X)cU&17NIU?P%R&?n}1w51OhSSp0D3z^bi;mG$Uj*(MXMAnI_ib;*tp1Rf2 zfJO!}pad2Zx=Eg0sg3~&ZYv9q8(vs>1Usr8%Dv9rQL`mR>6wM_=I~2iO)(*0jR_vY z^c_K2c4BAhIsrP~oREsXpY3knFGxEJ$&eBXylgHM4ao|Z*VL^{eZ0D<@#sq_TDqkJxaIAO1w;s?{CJpZihi&rOkIU6I7n zUROH48g$4PY?KwRrb8xTQ8eVx_nrNs`Y9%2(^UrZ?4=jBeKi%V$zn*CV%9>Y7=pZ(!UMyReA`Qbj_%R@XiAsi<2m*i#Rf!%Kx=pqf+h zI6xG=Fr*zWJML;KeBQn&EQ>}ii2RM}&1LsT4-s!olQ&mY_$C#ov*g zqdy#2@%G@Md$rN|pH)h>@rV84XNtpF7=7E2MG%E;AL%sKgmwhsDs zW+x}WD1HV{@sJG z3qJ*VZ)vcG>V@-2`b>th`NgGh%E1UDIk#BURHj%`y_?OMvnwMJ4{TP%C7w*a9q67_Pjw4iLD*S^=I@a#4Kl zMVW%IrI1PQ=A^@OjW{m1d5n&s%TGnu(+w(!z(RbvL0|di;tOXBZ=KKKXrN-0NB|49 zn%}9(xfVt#m@u;1lk853rx}UB{X~g}KQRZ*H`1NJ8BxG|1@J_7Wq%qT6M7t6V?}Dz z4NGy})DYj!pPHS1QLGc|DEz`J@5g+ZTh?iS`a;N>3J9~DgI^$qUOlAr;n33gxX%aAm=CKHz>*B$NDf}X@uIrBE>{|Cb>6EhO?{Dq7N$9q@(~O z7&VpJoJ@ChuuP3+V@5ur;E4kHyqlKlFh6TfX$n*8$*VnBWA~&D1~KQe?+y_e;Z(Hc zJpgF5VpHhbE^_KH6@=N#Foq@UaBhg;yY_;Riq(B(-sG=0u%C`j`rPwZ-FiCC#h8*@Zpt*2iwo%jv;$uLDJ?v)MU@##VyKN<8dHn5k^;_TVe1sXcY6mL;pqS1cs zyX8ZxgUysqWV#TuC|*JgwNZx$`NT<@-D01Hh4d4TBPTaiuGHKAr!X zgAAA^ml+t*o=-_t4pe)p5RZcmTNmq+nX=UOl&lLMrkt1VM_NHi-}75Ukp7c*U=6qM z?Br|@99Yst3esjeY=v6GC}gHv6x{Pnj;AEC;<{-vV$Q;bko-0~&rF!=NvlxhU#^=< zlYSu1rX|8ap|ja9C{9QY8Cq(LO9l74M@h+k*0U=z0}JI`bWf#~_$y3zKh`9@(jVpb zlt>#5UEMB61YT;r0|HfrS}K&lrv95MC2CSh({N!gAd4-od{hf~LbBXEv>k|v!@_}# z)rl!Vx>Kf7Jh5v#{Am_VFP;X4 z+vNmzzhMeX8Heg3-!Duf_HlC@oX^9n#%PvcVrs&Dljc>c1%vSsnS#m%bRsW%ZLyp@ zg=9p|zBpiPS;%^SH#XDGl_3l!YLub%qHb))hV-saqdjMB!`cw-E)J)@Df6oKcp}U% zhFfx}fJ?3aQS}{$9(`*mj$Zxf2tK|)VZbn6p~}vi!fmGwuc~Tm+q^QVqVUR(DGT=J zBRG=(#y*^Y%*-Gc!X`d%svjK|ZoPUZW;zH?NU%f@0e}K`@XEZ)9S56n^2a$%_Id~L zjBTZc0pbr{l8y<^0vL=VB&n***#Mp?B0l9 zQ;Rkrd6tO*kh^M^72eedEOwi!5%hj!S~^YYMgbg$c6ty}Hjqa8>t&hl zmEW*k#Y)y!w0IfEDfl|oi3lT}zDmgakqaRY2q*mSk}~<8vEUZOKLAFM*+Yrn(QCfg zEkX0+A=GRRCGM4aNvr0rkgoKr?DngUp;7^zFNK_IzYG0}SYAfn)i7p;v)e$?^sQ_+ zuO(3ZWdK!gq5}c)4Iop1(~2?TerHZh-s<1&lbDk$6P2s7d$v zWEuhsvrwn$F@mBqyP@G%kg^bE{h(=zx@@)2`!N~j_hVpA@$+7R@0&;B5q2h-lqI=;;<56&#tdZ zP8uDdoeZ-2mB}H8#r0liZP)|U-%LPikK~xVYrX6)NyhiIpo!Bw)TVj{fM)24&y&@T z`T~kEM8?sS%IfB?DLoNX*>u3v(G6^r4s2Gy$82bK_A(G@cc#{toJDV!a=6P8o`4~k zcLy;+%8;ReFxEeJ9l|s?sIMxN!f60Sl%Vvt(smD?DuB0BU?u*zwq}X#Ol_JehN(9 zkQXrUNyp^j#+0ETT=0zspx!KZX+T7a=kAJ+zmz`QfY#jEa**;OIcYp+Dfe;VvDpdHdw2cd&v51Q)`jCgu4Y+vpv#| z_qy1a%v+|(mBuu+Fz7Oy?qZ{c7JbYjzr=1D%07Y!h&=>qkQB**`zeP!Z0Xz)O zXV2`7(A!Aq^J-c~Rt~G&msuGo!S5>+QXB~S@6;(X-W=~2bLkF0sDs9lQm4E|0+2$* z9d2A}B(~6O(%BNY6ww*WWuM9S={`%vv_%bv?j`0ghvHO{jHAb}h6vhs!8@6SMsr-48IfM56ViT+JEHXoIhAM} z)*?dZp0jqxXR8xJeV=CpLU6`uQMNX!$tZMRGhW)>uhJh0BRF-29sBzw?WaX_sH32B zLnf*8A>(8P=P?(O7hXF_6eO2>iB=5sekuwO#h3qLe?J#L?p9jNrcfz|gqz?`>%L%X zXCRIbqi{BE7KXs9tF^L#LD9ze1baA#Bf(W;-6bJ5mJ=>(2-07q#!9STmQ^hJDQwr` z=b5oFn;sciRLdC~-E=KY6im;>$%^vMk~;kQhJw0_iy4>qBhkK^3VUp)2U`~yEgYau z#F$0ym3-?|xm5!y<7p=&gN>xsRFOV47gt(RTXN8-esWN%(?w0)-=oP>Ux~)GiGzh_ zFy@2kqCkLS>wCo3W*ND+s5Q#jGU0Bx1j23_4QCTg!J3^?nMZ;^dSa7X9n^9u~YdUfm$_Q|yuulFkRL-CL8aSqfQ&PM{jaMm$JmD9552kZjL zkIP0#wtmT76I~3yY;H?w3eNLUUkn}9Z*&Pa5*X%Hkwv)mS)y`a@&sYQ9LU*mBfI0i#;2YsZFAo71#iU0?#!vD@jFP36)1;QO(+$&)An zr~D8p&W2rHSkx~$@&IKu*qRQYmnoZ(mzSK0|KO717kt62TLJOe*mX@vbq3YjFj zJT)T_T6%_90}_pvD;1g1P~REEHo17rIJRnSA?q+F_AV><=jU@&sRF%L<&&?XCkHya zs(3jotr7@-l($yTK%hsAOO6)Vb9zCSLle*McDMS?y3ql)9 zZLikm)ExSXK>L~#yuY~Zx4S_=v!~mblxzod2WKLPXhFGQptIB#c!lk1)yg9v`zEjs z`*IwIsO*I1tF+z_V6}eJRv#H{2yai$l=fJ|`B-ZB{CkjiJ3W zbj~`=79|LRKL>nI%9&V`#ow2C2~<`cJEzsqy0keJo9uGmkyYClhj47j{I2{B_f`3{ zIOdI4>*WHHg&p!|!n71sOYDgEUpztYa0F=i9L z=t*0^l}m9Zb9N3SCd3JIYY~Kx2ppM`sLh7H z(o|mbuFvN$Woi)YUQ`=PtzBvUAzVeEKX(p7&Ip@;=c3(P5BzkNhL#e(e8kJT7F+JVDHPQJ_iC=cf{k1 z_Tm=*V5%cKqWrFsz?S@^#)3@lf~l21S-8iw`Q$-a>2L-LP~UI)LiY0_syG9h(u7KR z#w-B=FCW9D*ESi=a_u(3 zKb?E>`=X?SU02x-(ul3~61Q*u$rs|0unjCJ+H^k&b2m{Y#-ktc3XL^i10f62l&fvD zEv}%8FTY<#K(*Dep*Bei*mxs?;X)|ET96Kx6Z?f3o z13EZ@V$)4`*oOo!vSmpP3(LX}&{-M;%Ye>6cPzWf0EM{XgI|YpD(z^a@vFGSiJOe+ zn$l{uYLh;CbHaR%7$zl3>F&1zc{Wu{^2ku=uY?+7Eit!;Bdn8`wmA-6% zb}ZkjRn#LiCcM%<#$1oVZ{)t#KDPPPx9_ttIbAX!-0~?Ky%cw zZ>^$iZ^Biv@a(jH(TxSg?7Zu4o;^FP&K?>Ils_SyJ~j8nF#4uvUKF7R0p3uFYvi6Y$RN?*Rx z0-jZ(JP1(9^7rV-S}w zdQaS=Et650Op(iOyT-zctqk8mImZlZuT0DFA}AHvJ9um&z=BFTa&Vn7`SY-=YBb1# zIsC`a7SW{4<0MI_*7{>1G=43A*_qlUz;K6h$gR5$4(xv9RY6n8l~;~qVlq|<$q7Qi z^6K_R=H9js+QV*YTlFR*J^k*gFmRc*LoxXm9|g@Tkurz3MjONOD2fmr2r}Xhb`P!TBBQ91G#JpjER84pw+1Z)LYtCej0l2JZqLc8 zIK5_kC1!hX<8=mB6!i9-5GhQ=hPV8X8UC`KRA|#ruZ%NX_lo^?qiGxN<@8m35)F@Q zxuac)%@8RHjy4-`(a~Bht;`jALR!Bo#-X)-X@?O%ZBNW((|$W!a>jp-X>PIy{lopk$Gi6QyS&>aOdNSANmJqkVEj=K(Z3GsLL8~C6v(%Jqf#$`XJ zb*D*`vtJTZL~@3`wjhMb52gr0ayeJfNanShSYwT}61XDwTGuDLDpq_U9a#-ev!PkX zILb}$1(t0j`}hgbb8wbh0wVDVe8E1;cJJR?SD3b(=y%~6)UrQMt%nVf z$j2tK%K`fR4s(b7vTMkRO>tbK4$n$5@@~fvZG(C6G1W3?|HH zH6c{vvq<8fxmud7uYnL%N`-6--v@aT85OpEx9wE4ZS0)iELsJvB<{BWxa;IFF9 zG6vI~)Jmye%?KZBoz%*f5Bh$&b|{yBk465bB;H#QM|}8jKfQ>R(5h7DS>8Dg=`bAI zLR9iFf1?ww$M?iXa3YG|O;XV8ifhcF^CCoW@+c=YM8!{FEvj`M+QtK@IB_9l!_%Pg z2{|iKR>4G&R)t*Sd6usA!f#%Q3E+~G{Eniku(fPb7M(a?_Y|SuXI5@@VT-Mt@CjFI zFdAWa{_9X`gROn+xEe42vB;`B|EzL;swB?V(8pgX%lEL?dmYk@7d21ryt8*{{DeA@ z6LwEJ=h3qTMtwA4pDo98pL)IzA!B@o6{oqB)Ci$d;b+-yuY-KN#)3=XN^<=Ys9d|# z*&g5SGga3Xb{u`k#hCadr`C1sstrGy(Su9er@F^V82Q_PN(VbxsZz36Qa}n0ro=TT zgFw>;G%E`M7vY-cDb}pbO4Bwz2DkB_#qCGJP*zCspWs^Y$HYbR}oK6ZxZWaXXfonJ1~-92H?Y6+yNe|q)f zspn!i5VGNoxQDv2LR*`rzEb<~u=RHzX;PwZ05L*=+)15wKw`v2$L_M%<@E9et$b5q z@MDO|wKx7H>Fj)pft#h0ds4P-MvxE_yCb6147$+R^tcz5oquBX$~}a)O;6_ak4IWG(!29Dmu1X( z!gJ!LWF>T*@6@>N1;#}~JK8GsqReYf@@xEYuBFcvvE!nY4VZ9%MozoZVg!m6wjg#m z$!vaSYQCiuLca4#{2Tp)BxN8ePBk~DR`e3l%B=F3;kb4S?E2I>SrqDEA{87d)%b!r z+~hqWTKuIO(sj7x>Vm4^`GK&kN8FwSnNnCGtm&KeX^d*@3cQIT1ns~=G( z(4j4FN!!`bWSjZIJ;;9q9;|zF^77{-jQYDRuQhZhITWgCRFA49x{2|>{JE<03h!kH z&4FQzYs^F}r(C(pE2$Nn08!>vKHCQxp7qT7fAWVbIWZH|Q9FA(;5ROr#4Z8LRS2Bw z%Jlb^B(bjIDx`S94k^vZsuSzEP?XHnEPlA4^Y>AwDJxf=-kT^&*-<)A+W;Rw{S&$+ zpk%j&haJwjNchW}WZb1dv^h*#LqsN0N+2{!DTr!0n3dy4#y}KV#fDP2|)# z$zpy{&kPIkmlwAcuh~}Qz~U)ZS7weX4wY@U9p9|F7c7fEWJ}4E>!A2lJ~8xq{kSov z*JImy;;RbjX)k7(G!~8k>A|ekr`;=Y-Y*qjt)-l{60eDf8+9hZ2#5F zLQeX(h=;upxvr8bsf4qeB`GH}Co?OPl#i_!JGn4Csi2#M6~DTq^gkg!_CB8XczC$* zv#@x3doz1;Fgv?hv#{~;@v*S7v#_%>eMm65`#O05eVClwDgJ`^2Zp4jySbaKi-)bV z6X{==Kr?4g4x?|FFvT zZzg5rlvMv)<1Y%VZ5>_y*7_j(-y}V3t^S9sfAj6Hp1;HSS4Te7{~Pz;r2i}SzlA@f zl$7`-oy|S}a!*cDi2N`6{1(pUwif(M;S&dJ>a=wxpB7t{whv+V~C7rTWyy9K8OlbM+%HxsA11s@Zixw$11 zJ1>u=B^R3|(8AK{Um#T6Y(KIR=#u4aGL{px!FI^{)VzJ=a+VNa|C`2r>!H<+LFb^$@=e(zX<0SQ0g?E=QpX~KSo8? z*8M}m_pg-yXVPm}y8dJDAIE@$?cZIbq<`lvKhXRiM%;m3mKJ{-`q2AFm$?nl$=dSc z0RLx3{a3r~|7EnyI63*u%z>;-Kn}K#0k<;aWa8ud_~9`#=Q8K!V`F8rx_=kZv{#U~P$*%v<^}k}^eqS^*T7Jbd3j{F2$~PG}TeAjf#gZ^W*Ik_*9&30E)MZh-d=) z_!Mo0TnOUTE;9IXX|kFzG~NnKM|d2};WnvnWhp+77QKPfp>&hq1Ui+v*^YbP0G|xy zT+xwos@yXp7&IeMYOa6nFOy4SgMHl;QSmj~5fzxA4V#ir+5tW;d&8j^Fx>jA=7V8K z9IE6pDeP46hvUiA60w+UUeTz)T*tPUWKsq!sKv$&Hidh}Uzx9HMenU}4BUikjpjr2 zHN3a`Ls2j&PJ$gSd;OtG&$a_$a2Qq*ndnx9@o*mthxQL%*ZnV_yuL=GQ7Fg8kEh`H zx*d+ElF58-@)ASuJ4{k)u2xU8?}CC6=7&Wu4)=O(*&m3>$JTE!ReR$yAB~6OppIA| z1&V`1T0)YdrqgK@E5@Kp#bVMXmy3pi^DR4alwJgB{Psco?07%a|!E@DD9z-dZS~{*-5;~0R7zyZZTFt$gAAy5DJ%JPp z=y2H|jv=}rFgE+lXuDV{hek2YuE5wdt%06SK!Q{nY2ia9-FPq(olcOKd^VCQj zn-FY7pF%T9AII1<3=cG;j7}@lUB8mXfUOMW>Vx9RM|6(``q_>}5#TV_AXRII3DT)k z#x%~*y9-(+BBiPqsilb8iO%o|I=B>v72~8z9J*>R;W4IM;{;*zbkiU;ni=IprC#``SeP^?m~t2*>!@wy>2Mql z%0s4=XbvUYKBZg=rBn_gpW7j=YO!qSqA~gyNDt^1m&)k2I~+lP<75eitZ%$XY9WV8 zGYfq$Xcj3osJ_7p_F~(Mf#Mjuox~CR9FbIC=jQA_wo?!VI zg)9THAxr5amD}*TSsbf@0|qdPm#*m$%L)e*f|MY%$qZ1H)}#54BlFUBIKcVtFwa*KjNQxi8|aS* z5{)XwJQkH$ggnstpYy&mrcq2FtLPnqwP#yu$oE)M?_vm-(6{e|#f20<La^BO;l9Gr7;$lMZ&QFhQM!Djen=FBVIdC|-_btv7A(p|A+kY*XYH{v#QNR6X=dluNImKB58dC%9nqu+0qaTdFARsi{4-Cc(*+zvdJ}38Fe~0cE`F*qi{ec#Uch zU&UvdkwLOl(fp$r6@5_5lqGyUyns7^_cL${Qi{8Cj;Yr=1IcVtZWN$yNbje#~|hw+vy7zb01WhByKocuTX9OjK+h0~F#I!BS zDp-LBaP|%j1E^%+Evr)C0{JZqasU{|=~yi<$%l$pu(N1Rl4%dN2`XlGanv~u2lE(4 z`>hjZlm|``gOnoa`g;4xb#yPm7WBdbwOKahIgTp)FmSlR={q3}#ryRAg#eu?G>1q^ zMxQgYbnlK~+yU!=dPcUk^<6mc&N1^gT6ysJwN`O5c6Dio#(s1HhsF^(e%rBf zjzDy6@Tc2@f$%h#nmAr5GGv3(GKzUv24m%W3T8HOp5CuQf=wKpLqu1sbJzmID-E*7 z$0pBoGZOC;eapY0xVu{x22fA<=GHj!cLdp1vn^v;d?jCR?jfRXt}qmJdb{yx#3DO~ zqv98gznw9pnq?{-XCEU!u9Mb(dp)KO1tm8uPVk;&n?|G7&YilBH_afvo5u1Dmac4z z$H0Bbg#$~6Hz7$27={dN0am~GvSD}5c?()-O`Pj6U26m_4adKy?<|vt8j77BA!g!P z15CarOf?R{F9i%ZY9qJ1`Sf4E8U*P)4{E>ubWim5DH!+)^_iTmP1h`PGhmLqa?~-O zf4-(D4vd+70vDp?h2dJk| z-ftaZqv(;zSAF%pQ+eITWqVV3EpA(1i6{`BbqQraT;YNW!^{;tgyzY%x^IXzf_X{| zK0f)xaz;5$dwq|5a^d$!3f-Y`*r?7uX=T(lnOB_hW`0Vq&MgtcW4Dq45_sj3=A1>Q zsBgJm@a$Vc9#Z@V`?qhBcjq{%qi4?>(Q(HNmzB??a%)Mm(hHl_EB9q9vKxU>{5^9T z;Pl19W$MYV%E+45G9FAHkkf}Zab&JG{=m0z*y7jfwRbJ z&W>@Jdod-IWU5lJg)FLdG;)4r3;JLp5rb4sO$?-EXM$_2LP8|qW1<`!g%vp#8a=Bu z-<&+uJ>=hUO_FMxpzS2z7RM-b>lYJ%$U9=cW>+24V@@?o@HafT7Nn;6gyL3@3;@eG z)oI;{PXY_-k5WWm{Phd&PErq;Q{OR@idp?e?u_wb&mJjWT4sr6o=GW%o>%x$uxl|O zgMh3*Zfd1AOgll_AQ@aj8nVcHwu>loAS8WOv-CZUYBKO`7V{n?r}g9QBw%_|rr_SS zRb-cZ95wTa?ZA^icG6;f7i{MShd7n5BJ$fe=@0@#wbmVR0tv&tD8=PxaH&+mBQ~CA zn55`$qc6@BFGtQsM<~NmkwmGorF>F2SNPjmE{h377;@9oaYV7~@LRi+xzYej^{6ol2w8IvqBPu4U! zV+b&MC=}qA1Yq5r#OzWP!pq$c*11Q+6MlsiEaC7!D{mpF>SB^HY2wLL=7T5uR%(9d`xhlsVF%JY{lnFIkd5QCV@#|>M(e^Q+ZCc4ND zdqX^X$p@Krcx7boO%ZDIzy#A}(^4kEjTxAJ#)99#6=?0hX-E2GK3>=bXD|`pAbo2+ zQ~WS*p`iwVORc{8Q_x*v3V<=-Vu*0er zK^-KXKd&K=S^f1(Z=AV`_f$ODpS-GYY#aKB|9s`AA6Fm{7ansctC^@Ar!_5-Oea?$ zj|{7>Uu3y~2WM5%DlkD2IfjtFXy_Z(=smXYH4g=mGe<2iy987$DpHURFiPDG`xbyH zO{~QhsNJnSDOj?v|6CvqQLVvJ=Fi@x$2ncj{)u_9wXe`?*~ooh-SF-^*%}2dWh*m| zq4XpDtd5U|P6iU)ckplx^$gxRksdzzbeEpQB}3V0qkNeE{g9#8IwpNr3+K`61X@%y zRti!IJ)l1s8tMm#SAh`2r17Al6Ct%90Y}UEq{gWGn z6dg4-Ef}$)$I6dZI2s{Ca+P#KwUl5ispjt2K~eD*wgG=b3)uGK6n27uk<5TOc3X)T z6adsBqxq;}Bnew#4D1$Sig_mc(qg0!)Q8em7-WbZFWxSUAy786ZW`4pr~WyE>O%(> zN?u)@5sbP!3vRz+&xApOa-xx8u~hQRQsYO4&U_F}VBUez?8+H%{g~f`-E!>>;t?u? zbxAz&@pQ?-mf(c$CcV_b%|v-mk>z2HW_)Qm1@wtDt$PShJZHXMG%L#RfEqzPzPMC~ z#(bWkI0RCPZj_v-b5x>jIeo)e7Wa@-CySD{M)vNsSp^vzOjx6JCoB^vydNmezkTJB zWUt6ZGxjI)frJ&F(kCndfIW-8JT4dgh~|JnL5(#`Jchgmhq-&inVDrFi}8#j*VM1% zbW@u8i(@UgA8jBp>jYZcP?zfuI5V_dlWb}fxf`r<;h;K)Q9ysx&olZrIqa-`1t&DF zmUmP&(n+TNIISfXQeasU3fk~m+0XqKXWtl<_N4*ZOx*I;QE$}4wO#)@j-g1yTaK3Z zT{QruWFnFkENwfb7D+3|!Xz);adacq9F7kfSwr`5R6E;%03ybGfADA8q$iqhuq00& z!r?!E;~wLqU;1$6HK3VhF<4}$u9|4V6<|QUYX5pu8kA$n?2v4?qPEJWee9gzR47H- z41S^&&7u0zIT2XQb>Kwx!^1sA;U_E&H6Q-d-gdV6aq`ALTq8=hPe-j#HqWyQ~7zwp@n16SNg`r>>ctx zV9aswlW=g!<_W9ZB|f@1eKeEPK~a+S%!C;Vg=EJR_9nT`-k}0Th(_ysZQc<%#Mi_f zNOwi|`qalcJVJi($x7N}iEKMQSqhWBZ6> zZ}rs^8-HL@*aLsGg1RE(9g$N27cMD~Vwke4Caz*Dnx)5$xIawr=%nV$jREBZ`nRV< zM~eGL>Qt~;_uiTIH9XP^Iiz;C45O5!fu1qE@2G_vzg!FaATS$tQH* zKCKV0SWu5snf;LMda;CJdJ&D6<2?n2x$4^+vkbj6T{Tj`QCz=As)>RA+Q6h^na)3N9en@`3o-34dv;?K>Wb&1uy? z0;kL-Y18+j!1%(8ODv6Bt^&anMs&4;%=)^vJ4#TPRC$hPxW^r|aidN56^|q^zFF*i z{RCR^Lpq3=HOA!rM35k03Nz(TK0r5^dXBf9XBuEgwP(Caxcg7K{K?Kh@%^)+*MV0j)Kk|JVCUNobDR}IG5 zH|DAm$%1kYIWIr#nxw^`dF8y7IG&rKOi|Q7)TD7{b7Kj?R-CwTz6eE$Y*d-K$a;eN82}0VmMz7oox&*08&d_WQ z1W#E_ zm;ckkd55$4y?;E37(uPrGezv$BSwi)1TjjL))tDYhSH)+klK6HR#ls}R$Ejn_TE(Z z(8k_1+K-kZzkI&e@89#BbDitD&-0vf?)Urk+?&EUHu94RqnDlt$X-C|Q{P&yBHkIS zjZ48N4ISjhCZtHGk^?8eLAA)JsAu_zCm{X7kHLAJ?n|{QAsBI4`#13|gce?i=dPZi zX8V`A1pTbmc1-bY^<}nkQzv6;{P4qLTK74=xXTi$hlTwk^;UTuZ!M))lWH%3#b=USAFil)G+eu@tF*bAkad}p&h+R&?*7<3HmE);{^X->y|rL8B44FI0y zt7aflgN*~p3yr#0(plq<(~NplLW;C$AUp5MEx{yr4ywJtDI@W3`5se!$n{kbO0q{$5a>GAYj615wXl0rV4W`R?J@BmUdhS0t zzM3{7f_c8eaTzl5Mg~vldrSI6byMgb{c^31Kflh{l*9~OH}NLgxQlCnx67@q5ytZU zGM{b^)|1gIv{^(C+c{Z3aRWJl;F^n1K0xdQiWyj#&qM$>jntfw8QgRDaxd=;(vB$B z#)C&%yed=Q`cSKN%FS}AZj~+4W#ysG(x3ahl$Ls}AO zuU|r2)||tI83;nNT&+AVNO$tjz533!u~+g{4U$IqAXVZdV^)CD0Km^aPbZVQWQUOe zxX7oOP~Me+rYj!9_XwP;aP!T*{MSktU)p3k?fs$8ot5XqE#oMxhHxAzffhhc?Ly;5ECL6P%y*F_%lnw z8+|&CegbD#-k*;V3{^hS=~pcVuRFnxz3j4B5X2p0oY1V>~RULJs08ffM)?y>S z7m;}eq+Y4}X(Iw)vIiL40EnLQBR^Kq!bYWPcYdV`xZf^_JJEPPUALpBD}Ft2wXTMm zj=@ilqrb!ukj3ay&`^sucv{ru{nc)k4p+pa;m?pL1rXy8hHSlkX4X`ZDHxrfs94wV zcKKB{C9%Z1WKa_ln01_zFLR6=J1hFaxo3Pq^@B7hbYhhNbGZF78kfu@aLy1QN)-0N zgnPhFYF2U63<18)P=c&0n+v?87UVhaX}S-(ENCb|8%NZ_2#*DThfso)JSxC} zSTP7(BdWIHQqZFHqaR{&Y!|$GV=!!w3xl!VDU9oJ+#swk7&&grAlr^;DDdh{v{s^Z z_(45$lF2huX!A1X8sc!urz@5DQw3`~G9LkPXYXk!0YO&ER2G%p`;7<&u@$Ti#nfq{ z<->xM5uyX>!qo*l6H%#;p}=-tphSkK`BwyF=DhjIO$k$nIpTCw4lZ7Gv@B!*Dq`OG z-;-a@^BE*q#4C=dWLmscCKK+)0w8ScNtwt|3!!R+B}`W@79e{IAj4aEwfZTsWTQeD zlEJH1PY;yQ5Q2Fd0QzOvjCaBSHvnO6qLz`Ol2pJH9*}5|>t|tr;FABLu%td9r3g@F zd_UNyFR~vGr6fWom_UWsx})fSDNQNec<3ijJBN4~drHSV0A|oT*rzL|NgK2l$jmph z2P)aD=HWAEFn5HPn2mAM1&ve*T8t7U?6I!3?jS}mBAhO5HV>d_SMNGYhn$xO_RoN` zR6r9T&O7qE;1XmfrDX)X6(Z#d%uKm}NmwbGboMh*4e=J6gP*wlWLp8WN{? z16rB_FsAvNS_IhAJg4@+?@?(v-(mE5tT;FR+fD*D8_!Ic@4+E3fn8=T5aiNVBh5+) z#o4<}?>vw_)VomwSijqbrwjm3{A@N$NqVllq4C-6F(rUJAmWbl<(lzbxCyPh;9z@=2x zIvB-J8B+;Y6gBG!>?hp8@`12>U_x@`7%a^Z`YFgFxh=^3HKUKGPp9K=_FzY#S7&lR z66YQRDAtHZo`6eAo)YyF9j>0$HiV$foB^;B>QwV;hBBf`!)twjw03**Kz8&?-cQ7y zU#x%(Eta9mJepb<(m>7hTNIz%V^`a<{*ns#IQ+y(^`pcp z-BA-ZRfm?K5FAM11KJ>dJLSp;QcqQg3FV7)0GBv+n}$RM zGr4BKEDWApFAS?pT53_O2=P7`;?@*sla359h)>Irzv?X6=t#DeTph~YDE}<&e&R=0 z%Zhs-j9E8*9pKwgu8)K1asU~9d}_o2$Jh1LIe;H_9HhVM(mqy)y2R-!Wn_ReurnNh zbt!e|@kQeuN+4r}gqTElW@bc}dlH?Wh#MpW)^=bx{1rI?L)nsT5rB?BaG?Ft=TBht z0GELQ%FBR|lxVKUyCq*WzZVf_zL+nHeF3(5JU9%p6w@z^`(X&-y8dfL`Ef)1Ad2}q zui!>J>s7;;Mn#yOREB6>BDnC^RJ8SDu@A|kzQPN(OUQcy@G&;e%rYDB4FDKpw<+me z^y}A3R2PlWq zR9(GUX#hX5`8P;64nv9o`QGX3EqX-?Uw~zySXweRd#7nfCa!QNj7aTa^~0biCrv zdGjvoq?$6d&mdkktvB3c8q7>C*YC}Y(T~-4^0MB{7xR}BNg}D3)2nD-w7ZdGc{=a@ z>}QkwU|)^Ld4Wp|qORG_&&*J-tKI*v4;t)Jn#~$>G;^*nEOy^_*7K>^Y_W)k+lYdZ z9v6fWD*s;}ZWj?ky)4N15O41m0EW%#0z~7KhGqPV^e4L^dOoS7%04kQ*s~fpToH@- z)~xyu`iJ-5+p*$OXSu|4-BVgM)xMLtZKRLbr{M#&7= z2uM0VsU&B*u=C*zHD9sJAN6i;G&s%kIdzw)vJs$`8aYVE8Fy&BNw=_#!9fFEGU5Bo zeYR>W7rMQa!@1A}XO4y-I{8!MjGz0^T8aQ5|Dqqajz4>R{~IAG)4UH+zCrc>mtm3!q-dOu(|pfJvOYcxuQ+6d{X$SSRq+b-DAgY02rHTS%VgC+*m z`)0zB9Swi4Ad6~t&bU|kixA2VOUq18;=exJsg4C0r;!wxi7-bc)S{W@rn*IJJ$E=V zdY;UwkxSCrSNY+br_85CQiwl8UmtJd7VFlquoJEGX8ox!7SiU=Gu^`{bM|Di8+=dY zM8_x>Uc7bZ@;4L!DG`O+Z33I}!*J0o-G#5dZ$W)w`?TU0Ndb+1=Q`c_StT|d<>NOG z(C}0A>%~3`q0W4ryAc081VmC+{tWzGrke6bIjpf=70`LTxbnF7(o`-9P$MMYT~i?o zTi%r6+JXv>fOO&Sd?&1?HV5X&Zk&Q=xtPKayi7T9O+zD1`{ZzK9KVj=G2Tcfv*z@3 zqHZ4o&CGOqO-Hrmh-S+@yP7E!G0Gj?;v;C>Fydak#T!<94^(pulx!FiSU6g#W)os~ z!-$O(xGDU+iuoFiz?X|0%f^U%tpr@(1(dSV%DXpxbgUoD{~o>C#qMN2^(Le-TDr{s zYMr_%X2MDnXxr90#G#=aA<4RNw<1pKpGzHPU>XaGlTN`%TtMXlE*leveMz?~{P{aA zcVOHCCFADa70p>L003qR>#A#$^Vh7Y<{h~M2C{i(tc!0&OBXe}y;*t0peu%|mvs}p0TB)8 z+fElL;a8pC$$wq|lvP5-R-EPBi@v)kCc{z8RdXN$@Gg|N0RBEyFsbz1XpVJd8)+IY zEriMwBiJ$4qfLq7V~VftlVw@l(+^r}zmli_GKMbngAup*2O0<6e{3~Iah@~!=Ru-M zsZnE)^KYbyt5XNFL}xZdkh5o;YBH~Q@Q8q5ldf&e3PoE%CX3g1s_gwyc@q<$Z0V9I zu%ftlzCfC~I`!_>{zsBWoY3*xvA=TA4UIWsSnM+31aJ-_ z`L=OFprR{whs8^8HXM)7nRalC0xr?D$Tuyn06)oX6zUiO!fS&$*65Ume~);T0-=x1 z58bs(xf0L(r)fSiMOH{NzMmL2I?vT!%0B!wBH!!YwPwIU>pTxWH$OGDdNJjeEV=mS zXeO)K{tmC%-Aj=CTGl)i{rz(>U?%L4X&S)|LmHtc zClg*;hsBYkUnI4YUR463+J|Ecr;0Adn?_|Qj>|pr-gi@af|ITMu(hD#^PHdf8jMAk zITzO;E8NItjgcLpy#JBSC9Bq922GZ3>SnaoT9YilXo`>%qON*)Yemx8tAn!dwY&D#TJa6#CpPOx5KY1Ilb@+#q;k20*Row%mT)aHNx zX18rr;Z>9L42ew2Ovv}&dtnFIY$dSD-TZs8MvElXf}2iD2jfiNzdCu(` zTzj3GvPUFmvP9Pz%rZ)HlxRRo_6X}jO{ ze63g)%425N6IIBS=B(nHm2mP3QX$>mOvfDUu<}fHyV?22S!=gSg?(t=$af$e0X!{* z%(mM+z%;ncEy;UTBnmC}#yq$8Hsa3i;wtf;oix-7=H3tp?*bQT?2kj&f#gxOI;)%4 z$h8tV>$9BIcY@MM6q_!WTin1jnMH$#U?!+^%`HVB{k9d!+I_-7;Ou(GTd$5=%?cwP zZv)b`lXt}>ST?ksA4T`CNnOs?O3X>;Z&>^>bF7P}cs8lzUQuoIGkQheBizH5&gPve z?r(%gMLO{;P={rTEjIDz_VMS4kEb1B-CCb^7RN1|5dS`cnUn5uR7oW|3c7Tq#w!JS zSrT-GY|WZoR)DVQh7GpftA;~TCaZPY+kBX;-z{D0dc4nA*@-JxzhjGNF=fZYo|jCr z^rZ1~2PbTv48yq^gixaogy?MgPaEQPWvsIBfanXAGF1{igkh%+1_z@AhmhwIJ zJnx|$%+%h-e`SE0w8_CP5ij6L*;&U}W(>f#dc=BH{jS0MxYvQTd>bWV8#xE3yX#s) z*aq`ni!|{O{V&UWp2jG65IOvk_vK}Nrp`kwb-78dTZ*D)mi+3Rqx!xl9qs!i=uuIzLrJe+Lc)7O%t>>(mh+~Jj}ki$ zuO;r)T-PzBm9N|f#OVBQ) zjeAUk820$)ft=gARxqdX?rxg!h`t{-(_6J>dW$wB=sU1> zspIFvY&oCT$>C%E&wumu-05%QKsEHTDJ^b1j{+1&nHXYTsRjI!!Rm;3# zN4oEa&4HKhaZFtnMQDN_t{r}<%h47)O+$qS#iDE$JktM~9pO|y5Z?&;eBI0Lwm3yU zco$P-tFh&pWty}r_ViY6$FS7L)2M&yY(g)X#s#ih)_D8KuZs4dWPbwPn|ImxrI_{_ z2cdpSqg1Nv14s?Od^uNmj|9{#AcN&iP>@iguTr;8o?8lfYEOM2cG8SeG#rV(J(w+l zf>5kJQTPhu^qWL_LKHJ4DVsFJF>w;vG<$Bmy<<*Zu*vy)DNepwd#buzp3Fp}-XGGp z-E;GH*U!DiF@Ec?L6H@TClhq9000aeWP&g_wIRAS9o*XeY+$CBvj6xh_B;OgnR~4@ zKtiQs@Y2Gu^Y2^ zzL2cQwYB&oc*&}{O{dlq^HR{uR0C(`Hs_EBPg9qTPh0rRj(owY<*xlaU4{5l7Pdbz5P2Woh~5dn)iw9qN^ z02~7+yA@d*OdZMwXKUtS0vv}5%CBhL-~WuiQhB%{f)uLc@yPPyA79+%|KwXgAn^7G zhxKUdXZ6~$s+yHM73o+bSM9xDex1oG8xYwPB(_*P z!*rDhie`Q1=sMM(^@G;V>wZVqn|xVB#GjmAJiHu`J6(PC&MuUS5A3O-K1?mfBNq4Y zC4(23T6jdMUnWrtt{26QYE*f`4ahY63B7y_{=O`Jo(%E3AMt>~rf~_L2PzThx*6F@ z*7J-GP0QjMdig)L^j=r3*}t?(4_q60hk-;Fx+Iw(^wF zI&pXD$%?o^>hW~E5Czsm><0d_$qY;1ec&0~rSU(=?ax{^UwZ*P&_{LpMx6D30A3LV zz`HCAf=u}~b@JBMHFa_-C}IXP;6^E0oNi%&*z@Tu+*8&(NMv_*l_rp!gnt6@KC znndSaqi+ae3;KOcs?{F!eJg`hfx{p725Ag(Z>ppIK1#ikMFAT1^OaF-1`w#&2q&xX zUK*lhF%qel+pzqKqBK$7aM3s+X1_v&KPfonG(#n#Kzz|kS;r)*F7(=FfhktS*sNUH zIPg&d#C*~y%~q?;bmHYYdNWQMR6nZ9`Zz9A*fbLKA0K=3X82GR>rlt+Uze`3^ZR{x z9tO@kSrK>ZSG!F4fD7KY!Gy$p>bV@@L0MPZ;1T6i`-g=zxrXw zhDJb^gaoMMM^qj~jgs^~U*EHgY{u zD5YhI@L4+5j!sr&ro+)hH62oJpK>yV`lbErnNuW=P5>(i*C`ImJn|hdTIPK;nd}9U zBEKgQqG^S6&$jNR0n6Wy%;28di}f?r$}|$#r12bbqSi}$KDoHttaMPpHbf_k>epl> zGnZ^3qM@VALpxY14UW#w%9wX{O{ablViFaw`ZCyOVitZl;~6l5V6Pm}$CgVE?Fe>*&||19PvpCyHRw>;$q4UjTFbHD$_t^*s0lq@<45 zY!{6EAE8I?YOiGfuHaVVMB%CvAa9YZUwddJHu zLz7d+#6_GoM69ev=u}Gf-^x000^G!;tqNHTqNZOn&a?!(m=0?74H)-&`Z+u(&AyEm zdo33A(KZhtty&m-G?hSM#TQ$!lJlYS#?2C4E}cmb3G31-`OnA{e)|B7aXL&l38CQD zgG#3m;_b&~FJw;heWM!7jT|SRnzEAes$43w0RrIv;pTvf-(Q*rarJL(xY;lTcmLU)B-7I4ekp3z2FOu(d;sc(xxrgKm#F^(ouB0A literal 4677 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 Hu0mjfgVw2; diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-apple.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-apple.png index 17f3be9c262953cac52da794d601bb853d8dc276..6a862572faca191c265bd1bdd76283c08fdab790 100755 GIT binary patch literal 26685 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 B13Lf! literal 4677 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 Hu0mjf9r&r4 diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-bananas-overlay.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-bananas-overlay.png index 2c94ea78bfdba901614472a3215401d7b0a2dd71..9d1d6d1177b9e36bcef557251ba3914fa5acbae2 100755 GIT binary patch literal 23898 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 literal 4949 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 Hu0mjfi%6-N diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-drop.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-drop.png index f259684055ad3ce25b2c3cdb5c3a53062b60a8e9..b3d7e39dcdec42cc39c96f004690d7f6a7337be3 100755 GIT binary patch literal 26684 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 literal 4677 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 Hu0mjfQGBVH diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-grapes-overlay.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-grapes-overlay.png index 17f3be9c262953cac52da794d601bb853d8dc276..2529b5cd1928637c69a512b292d5533fee42a48c 100755 GIT binary patch literal 26684 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 Hu0mjf9r&r4 diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-grapes.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-grapes.png index 3dc60464cf31e3ba07d5585542d75d616f63da03..f2bbf26ad759fb2ca151a839d4e61a983ac45f2a 100755 GIT binary patch literal 26684 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 P00000NkvXXu0mjfY&D(f diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-orange-overlay.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-orange-overlay.png index 3dc60464cf31e3ba07d5585542d75d616f63da03..254a6f32bca09ab3009cb6d25ca4d57c048ba97a 100755 GIT binary patch literal 26685 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 B;9~#) literal 4633 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 P00000NkvXXu0mjfY&D(f diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-orange.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-orange.png index 3dc60464cf31e3ba07d5585542d75d616f63da03..998f5fb3cb6f04dd8541efdc151bde4014aa69db 100755 GIT binary patch literal 26685 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 P00000NkvXXu0mjfY&D(f diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-pear-overlay.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-pear-overlay.png index 3dc60464cf31e3ba07d5585542d75d616f63da03..0d8d037a5f0357f90e13ff9cb05544cbd6d7d931 100755 GIT binary patch literal 26685 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 literal 4633 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 P00000NkvXXu0mjfY&D(f diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-pear.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-pear.png index 3dc60464cf31e3ba07d5585542d75d616f63da03..c8acaaa58349b47893c0c3ab3bbd4a2d472c8721 100755 GIT binary patch literal 26686 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 B?Uw)m literal 4633 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 P00000NkvXXu0mjfY&D(f diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircle@2x.png index 919d8f405ca7f40c921e71b91e7a81655db1f94b..207ce15dfabab74df8b5c13932506d94ef19dcfe 100755 GIT binary patch delta 95844 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�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 K?H<|qr~Dr=#f2~c delta 42344 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 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay-0@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay-0@2x.png index 8e50cd033596fb3daacd173247d6779168cc8f99..ccd1494cdf1a9d19a9c860b88d240392ef1919ea 100755 GIT binary patch literal 87032 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 CAgN>k literal 26595 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

yftJ!Y0GsdcN%;MEM+5u>2KjHnOqx_AeVsl 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 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay-1@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay-1@2x.png index 3b5e886933d2f7a8803a44304c5439058ef86fa2..e611add3ba9861baf68614ec51d63d3f94c8592a 100755 GIT binary patch literal 119975 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

wSIutbvWi6D`m2u|!^@bLCI5us_1F3$vl-qM^r3 zFXE^6`o}%;%}{-yQTXmtp-76__!$>z)bQ%l+t}9$Z?@2$M$h9A$S%J!A1VOx(}<7I z(*Ow2c-2|OEq5OFGcn*8FN|8d7N$`Z+i0l-@b>~*oP)Rbzn&~GkO+N`vt(N6PYGn$ z^fUD~5y+~MH$39aWoN}}+n0{wKtjpT_jVX81M*DKsXqv z+Yi!2E>g`~kf9_d2fPPJ%A76y{O)tjIr+IM&sYxQS1-H%zS?o%5APXIWR3;$v_gi4 zXBf}fdqEz?aXON$V#vk~plL97yZ#RIw#zk^@&(W)A(%tXBm=|QRMaDqI0)}yS;*?& zs3nTSg&pvHj(h!>qTRM2O3H-+H+tX6R3b$#PoG3Q{_AOk@$+%cBf7jxSh5vcP0@Nx zPHtdM=25xYK1dwBmpcA#D?A&EDW|VALH4ER;A<}Ow|eSSEg#HVq*RJ3rGvX~36NC7 zR!Qm8VUWxWA62FPM-2E)@?nLH-UqHd>ZBPAG|{4JypcCAuvy;rF*Z$pwunrvAU8T< zK(_xL9kifSPk4(Thw2)_hUy_>(*zo=dD#Y!I65dcojaB?NpW~1@6Kj+!1Db;>qO-< zBd-N2aN*ynsd7Y!^3;C*{HGEqu~B^HU6&I$vMS3Ck&2Bk<{!y`cK#SkB>_mb0%q^= z_RvABGKr2TV9og2_f}^X;a{+C&ozxZPP^SF&;{hYpjyPVQ@GZ;3c8+T=_-Th3DlnO z>I+Dg)KspubVz6pICCooHl;?`EH&$!u5W@`zGZW5RUTU44ir_&hiw8IufWcSN+6v! zDds1)*CHq`ggjuT%x8_@uJ|hSqIN$1eA0Dh0LjLI10v*+W?cXK0s0(yj-lRxJa4{b zjhQC6(_(lvp9zB4Z=}YzM^vw*hbT!X`H>nO=E>Qb10EZMd&X$#Ion=_=OR0GsT8_# zBBt#Gk#cmh(~SG_h{3BNzq3!HcjH40C<+rFu@7~V{s`Kr*wxaVFD04QJaKqWk@&gg zirXmes`wSl+7Qlc&RTLW!IBd5ExfMkO=Xj`W&NY4^KXeCMmwB8xr#RJ2kWy#(Lm}J zTFo0r^{)W!%NP2_5F=m_TO;fxMi;<&083q^eZhe`dJL%24usQdZa~c+%zvxi(&p!o zY(Y_5KWjBKNTaqPm<+^zS!|~?;6)|Q+~5FG>KkF)B!3(|Rc>G0X4y!VMX|)`zgCgC z@$wZu^AhRBdCJI6x>R%=B*%nmH`Ko*(Kg2~dD9mug2h=RU#B`pw}`{z2Cq+CuR$b# zCWu=_>q?nT9FMF*ettGd?lsl1rT7)}P<8_Cb}LyQSWHB2e~B~&h5M8IJB*S)mS`jt zcZT<0q16~r<#?{cMSV55zobu0iHvuRe<6hZOhfm`=^9h;v&QS(k<86|K)x<`842svdZ3-jhoq>yZhC#(LvZj6|7nvmaXe1Gk;>fiG~)w+KB zxFn!3V*J4PP1`dcsP^-^aKd~+oTDmPs1|y2A2jvJp+Q6c?%qGj!X*flqlTsfbP-2CR$hhaA?J3ueHkq4R+-HLrKnydba8wwQkSgyj#$-x$eml{BT6;e z0-0?ok*7q@EwUN&VZ8t6da~7u`|YWK{tS@0JOy@H%VXK-R4jXw8b6$pnajFJ$Mb%@ zM9(sK{k_4un=!i~n4d$}m9^W7kL$?geZ*73-d7K8LP7OuHctPl0^dG}pzt>oX=)=F zYU97@yx-)xoVnwrW>Kp$f7j`3+Td14lu$Nkn(SQ>nQf(Vmt19nzw{AJ3r8Uzdhuew zdo4!1J6dmUwE0%=6-n4zQH>)p$6Jot=l;urN}zQMcNc!&96qEI;xxO+MksUz1txtM zL9SV?iSepRZMl3fXaXd9^;nZVmFE0_FHJV;H3Q7|!7*VT?Pz#2a85y}i+s>Qy4*iT z>gN1_E3;b7uXKBi+r_qdW%cr~P4zGSBRZrzxFOzHaT~9(K??ARl2Qk3PBi4uvDRf; zQ;1;lBm$<{rPqY-lsAb4J^w&eGIo}~O8EmcPf>9Rp-@_W+6$+);2Igx87fqlBMvk? z&|`-L+zMJ0s-aTwmj0nPU!>0M2={rn%2EmSD^X=hxTisA-3k-964Tj#E~pt-^?v%V z$m6#n@YP{?G4Z)ks0k*+P~7E&aJ4zZ|}%A4GLZ zEn4A5kq~+@KE2xjt4E{(<<_{p&Yl(NPZn zrdE^CD@VPxWRI#9dX4Emib#iOVA z(jdrTk(E6f3x*4K`XRGLb}fCvZV61n#1et>dKB5 zC9@7S-^#`RIsZKnVbfR>Vd3L%Qk+>dyx@K^3~w193Jh@fzFTS>wEH_n8u~|d`a%M=i@%|h=m^ovaTwGMOMG5l=uluh80_HNK$3i>_zHx9ZVjKW z2V{X>>!%wJNvSz_!g@+PFX~kF48Wed8B;}q_9BF)Tr_Obr z3HGsJlJmVTs#``+bM3!D@R~`k!T0 z#}zk`vH3!W2bKyBvgb=v?ziv%g0>tZt65MiAKz^p8tEKZTf+h>E~cJ7WBw7I7({;< z>5ntN&C`&rnr5hu3KltHb1Jt$S5 z=;rV$cFOQ%wmfK$+1GeGwJbSPf}j+S5qX5^)=eJ5Z*bouwY_!c;M1RleVi)?G~YrZ zHVM}DRIH_^&HX%IX>$Rr^Q>s9b{0_D2>1-Pegb)c-&)fEIin8XLU8D%1O|l3g{d(C zs3ruaj)cG@$k0RaB8#+k78r7dan4pNy)3<)<|Kj`ZuPY5{Ai(b++&awX`X5ww@OV; z<%$fKUGZ~gvXaFxF(T?(0=&(FAcKuSd@9eMLn1?IVn?Wn!c-J=6vUQV56tJ#WklX& ztJ}C(KO?q&cD-{PojO&f>O+wX{rdxIfPfi9U?8`kyHpX+z`7|?Xl&pKm66bVysv(U zv+xD;&xUOeL`#<}e%*LxduCgJrf!C(_I6c@j*UK2R06r?3RQ@U9ed$tHqnRc7biI6 z6PkO`O%lQGP@Xi1u0Nc7r5kZX5_Ju-xfRkvq5A^j1;enMvcPC_0A;?Gk4E$5?4sRo za6x$dmxY>|3?U%?J3(6(5~|7h*rK0(EaPbxaHjkg(oZFCd|Gt*yR}43?KnM&BJnG8 zPZNXIw)sR5vS=g7jy6f-p}bE6yOJ49>B&Gx!JmJQm>XQinM_Fbm_o&Wy(}Q(nvsiY z=#Zv{GOx>LieCtxCYQ-;;W_4gd1vZY%jg%)Kjld>*`aajVb*5|vJLev64^~~I=jN@ zrF&bO$MG&0MmW0$wxiTY&V=+2JB>VoVkOp?Nin-TEor2f8#E|Nz?W}VO}Ft52g<4l za(ol0AwC}r?jz>%X}q@MbBRz?zldWEkgm4JduU%#v%8g|zw>$p+ju|0*tH#hird@=U>Zuai^^qcG$P7uVtmM$Wq!L*2KbbrB#Kf5f3C}R&!b>l~ z7+hL9e6n9^Kp%z}+<-CnAZ)Cta!WNUHE91yWt`E;FpcA@W+d}Bc}MYc2?;VPl(<67 z3vjVL-P`VLDR!fgF|y1Uz{xo!W8MVI5;ADTQpMEEV6M!V8`0vBx%f__I(i_fvx*gH6e78DF&TRQqQ%nC^ zy)>9&J486e(mm?>TnI)su4h0ojNs>qmCjeR6J$IQ@;)UH09Q!wDL3A2=!o>q7*c_3 zH>oxCuLmkl^*fY$n1QeC5Y2?cz%u(2_eUa+C1v)Q2E4qeHtWbp^-n5tcQJ8=S$-w@ zrK9GVPT>jexB0DN!tPTg{u>z`ASXAxX1sA3G^O8Vj+ru{6(s8*VXBp;*8siEOyLjU z&+y`9G+yjs7#sB%23`5S7=Z?kdv$r@3a@JsX|d*m==OGlKCqK}cHm=i2b*T|0L2fF%E02ZxV=ziy~Xiffwt zX5^$?L~|TBN|Ur3B<5(0^XMY8UdyrRKr74b3mwwSl5I#?S$0LCRWCiNph)ORyz>2y za607QA!Um{xu1!>dUWNqZvGBuACF(P=^){^K77&X(Ui1mK6gJ!@Y^#D1@lsOv_2st z5@H<-P)gRO%I#ztX0;`$AV!AsRc}6>A2&58b|~4M0_4G<6?IX(kqh864t_%S-j354v%UUp|yqJsPmcK(7=SGW?)cyAr`{`V0mB;Z3G(TH}$_lRC?sFd}yE zlp)n`P|xas5}E_px+9h)A`Cm+e`BcNp%q5Yy#CiyjM+F*)B%{D8mzHqq%#hi$Tm*6JKb!ff zWo0@AJRue;KN1UF?uUzX9@(EJK<5*+kke9W*asOPfAGIPcuNq2uVbwA3VD*;f!YWL z8!Loys~}$V$o28;%%f#H@cd+TG7uNIkb6eB@E4spgDodK>DenIgR)B-SCC;^S&(F; zTT@R)afTux{GnX)8!7AxoH0wrdU?%14HZ{4_`>Prb13ta&t9|q94EN;bN$kUpM8u` zdg-~?JU^DzIm$40_Jy3k7DdWPj(8jXRkE-lY`9{m*Oq}Qf@j4@{5<--4V!PJ?OPnL z-)|*rAgot?kUor(wqn&`=t#XpCnMWoey4dq;PjumEP#EzyT7(mgZ7O3ySj!?Fx&5Z z6nQE<8tEIfZ8%IE&o`KuG}Y~5cXe39xyFD`@;($2M(uy!V+R}%>VC2X@o>krwU+jG zpFCk+58zJ-E@beDH*B>^#7BmY;*@%u*~#iz!qrJ|(0uM$N2I+&3uT^b+4wzPs5Ej@ zXlp^A3NSMSLN%|PT2Zhs@?9M-NhVLuonk}wz0DIsn3;@29q*-+FAk}P^M0F9Z27e3 z84SDDx>2^$&%yiiIiExO`Nn^zDQfXQOG|4>MEpMtnieH?K0})iYu=_Qb)?py?H)bB zkB(fgwareY$gH~3n%Y?Wcm9hpcTm% zR3r9N){{5pDSsrB(-jgckgzuztV*9VYUF?PVK1@- zp5@5fn#;zY_?5aPGvr!TYxjxzv#?Qx;>3qHlKx#YHowjL!`DGYWZf?}1Q+9fBA^D5 z7d3iX`rn?^f!f34L&F+}du61;Hx*9nB(BE(o{w;E1QH!s@Nv~${KO4hf{hJZ#i*Z&-V4IjdNZc_Y@ z%FdZuzs($neDN%$WpV6Tq_6`?zcSTpmp6? zt}`m>>X>jM9YTU8&t+$40>FrnP~XG$Er59pD9++at0~T&EwAbk;uZKiVlXA14Zh&S zAH4;sTm0I=0P>H7a&q-Qz@`fXUg=ADs7ee^C{_rONk|6Z?&J#pB2E0<2+`N$nUd-9 zo8z2w3Q)_M`@Zahzc@T1934#&Go0Fuo${)XMiz_+mV1vj*)eKNECNua_BhwwTQw(* zok2`WE9fiD`1T+kj8B!2v?ZKLQ}909cSK{d?A3ceE|Sie*PvNnn{n@dI4V`9i13UN zONYE<@(d{!ejf0x?7z}O4Z_f=!@zB)0)zT zGekzbvFzUa7mAGqGkd*@-6zWn>RO{cojGTB`ouTX;|OzaS~y2eF1$Pw_UfMQG`%yW zT~Yrnd*)gM#+{E=dZ?=$qX(}XH2nW|Ht6!k08{7CbAQlNzjj{eV%c1eY#1s%-NiMw zrtRC8v7~vkp4}SLygIj$u!vp=#$6tHe_p)oKl*V-Jnhp(B*bAJl(du{eFRD#BAt7IIJO&LN30WeVCpY7w)f5_vp)OI$vyZrP{L@ZztV|+1P0Juf^_=Z z==(TUn=vhNwOc1r#}>-OHZEiOwnsV?t7_f(oFgy%E_KTX7Gy0iBCl%ha++)I`{7Uf z;0M*}ZHR;&{LyRl@7nvS74|f$LO}RfKm%Y2#r%`RnJFhiT@E2J49H)k#8;aHa)5U% z^4v+}b&kEVV*9>09eSG?(-^mOB;3#CPH~=&Nk4LkrN}F~GBF3okJTw<6T9e(Jg0RZ zAJj+V>|^!Qc9CHjgUHyrZ`fjE~X0d)NX+(yZ=L zh+x0^9;~+Eh~&d~t~IW$9qj={_R;BA>Ilh`Kd&Cg&(}!@@G$)!7l3#w zs+}z;1Uo9uo=rA5OlCf^nJWPXbO`Qyp}A&2PYFcN6dI(DxyAUZ@i}mt>Y&6Lcqjt| zJcgW0GZOJ~{eO6Q>hmQq2c5l-m|9HuUvfCpX}(Cu1hLdx(x;NP9~|r$s>%UhS&nz} zV<$+$#?(n>O8m!U6p-%wDh6yQ z*o`5Gy>L?wR?GVM{m|5bxW?(&74_9CaH1Xk=^P9rRw?f8s|lS*J!WI(fZ6#nmdyaw zB8kILTv{)b^I#St_#d6f$9 zDzWl`s4hk^xLzPk(DBF4M2&C^TKH49Z^!Qjb+U=20l4CG^%40hYCpXXS7&DgfsY>} zxIQNZ-s+eju03Ge@afcZ0|ANETzmj1Ra(5K1qKz7)TSNf7Tf^p9%JO{ig^9MW>MQx zpCi(HFs$>W(*lCic2C+Od&21j5qYWz$-TL)z00H3(YetCb;MbJA^Hn9j0)4vQZj7= zz)7RtNQ@<3pEi`1CWPDXFg`}-F%EvLAzNFJFe}(=~53-rckLK3Arzs&J1ft&6QvbZVBZ`DSKOusrb;yd6ha_FWu#=(T2(rh1VcqZhu;TZ)(DlSVq z5}26^dRxuIQXs0iUqznKWLr7hTvwAF#{9ZseR6V%-jTR3t`^dZA?|e+{c6fC6CYXAHMGv#Y{W@9e7|jVCdrxPM z&Q8|vHE)Q|M)O5eQ_z)Fps8wK1%Q)yXNEo56!*lw9nGL4myn02&#v>_e4e!qN)y2Jz6WjCFLWg_a$e=oph-v3s%L05yNd6JRjr)Fv!L1(e@ORt z2%O{JdxXD~N{{^A&Qnb&#eEQ^9FTxTlaEE>%88RotV_P64XTjcnQtHz^Gy0JL||8e zLHdZKtSGr^68b;1+x!10a3tdf;l_VLmuIj50L0(r7`N_thh(w{0Qvm3_*H!8ItdzpWFh&xZaD$w^ z0;C^!@vh@pj`{@4p@B}A|5mXLMt}4r3sweEA6h7NE5ZWrk__g)d?@{RC*3i4b=-zp^B>88W3%bCWB%nMQxIBvwz1DgimS?$aJmb#dmg^i zCpf8(Hta6`E20U@xiVCwa%OXERGdQERw@M8DX3pMA|mf`~0=Ns`g5q^Xv zTV9sqRWK->fu^`Lu9oY-H_Ws^>h&^Ywjx22yXuh$RWkvxnl4nNf$82A5$c07pOt%I zU`Rcixx2}}%LosLU|Uac*{xh$#mUTu;5rez0629X`{J&D%6(@jNvxS8XF2>p`KV6kGq5nUICVu|CFJ0^Xd8L0ra@?rmvQx0kkG&wdHR zvW4Dw;m$omB+EG3$(;Bh<`^C=hbFy8tX}84aVPYpSk8;9W4d#zqfKbah#Q)Chls&b z@Dc3L{{aFJ`X{I24j2)xH!k*zIC7J6F0iI!bkd(7kE+?_CQ3USZD|*{GfC%8zB-m` zVyp&O@Ar3&Z9ql2y+?5_2L*e=O%o$wtw-`BpPT@P*N#jasCWt~&PVkI?YZ8-C_884 zzex9?1N(le@tBg6?52jh2?mfI$or7LEKY`6-}bLdGrts_XdRp!k?5}IboF) z*7m5uFGVh1cn~3RkubuTBQKRFWz!%eU}|b-U7CmR z+JPm@(vv0$(0OHIv7AO*Q%3BONj+)6k(wBe`Abb&;H zM5Kfi|JW(z2$o3YwMv9RTaDg^ty;T?n#sZ_uB52FgMTe|YG@Lo*tJ@&))6HF?Y^lV z#{7)qXswj4PARTsk3ufejyTz8j z=g`nRC_Y`to=N1qWe6g90rGLut8PMW5O(>|Udhq#@P=lnoFOI3nH(}g>L((r&;M8W z(}KdaPXL*MYj$LI@m}HL`En6i4sEI00MrdaQ7B2Ktr*BQoA46QL~+iE)}&W|S!Nv- z7gTiMFvAp{K(4kT4i~^0Mv&i5Vgx@q79zz0*{eHad{G2Wx#L1b?{4bkXI&*vA_epP z<#?|;ikw<)y7SGnT*w^ul_v8S|8J`CiQz^awA$RbkSKJk`Q>&loUc>0pFFh{TODv+ zaKmR?HSi#4%}KCl)tLRw4l!&xPwA%og(~!aLo)T%w-0m>Pwjfr*ekl*#4@<7ILqvv zrII;)lkv)RE>MynaR6(8N)X{SK9Uo>cQokHw!g%E;4yOOJg>X@t=1uTL6`pXP~}4p zST+9mAG3q;ha#WRourW^b_1<(G1b9pdnooG{DGspu4s-1>*@9A4jI(8e$G#kq132% za+uEL6a8p+jU$98LQVL|o2K%|H^j^)l>1(#Kf*?g86;rOxSA}d6?@$~w}?jmb7aEr z9pSSI-Bq5>P8SvU%b6-^dC;>W&2GW9Sk0yX8?FWsP=vc>xM$!9bj{H)`s}B14}L|u zCHnU>78)O78iOI=e(CMU;u5H(BA``bM3T-JIKp8w_8a2}Yzbtu`0uLKZ?Vtv5Yn_| zzgRu}irB-={^bBGcnHDq$RS7GoS#=X69Qe8mD5YSZB`a=My_0Hq_*<5xs^@≪}_ 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 E0Oy=DQvd(} literal 40858 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�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 U^GtX>4gf!RCwC#opET>M-<0j)>w_GlupaE zQdA~lAy}x?Kb);qief>Tio%>y85B1MMFn+;e-wn4iCWkYhE~K;Tv@diJE>EKHPvca zHDYSh5db zjM@LdW5GVBo-aEjj#cOe{8^>_9`zyW-9figw}yN!BrqBam}foI8>u%0|NK)VUQ17MwEp_u)0SWA_zYSk0`vKNWN>hh+`oUH)YsRO?c29Ulj?)i_fk)eOCKgtn&0myLqkIW z_}bc9vSrJbXh!!!VSvLU|LoZ_a_7z+Qc+PsX3m@$3HVRcrwIkD813_csCRU9kmJXX z6SLW@7y57N1;PP`8XfgXXJ;olapDA-KYzYn@G|O2LINv>5pQpACr6GPAx5K75444P zy0E}XA9uN2q^YTi6c-okf$pcCCoHgHUayxp91e2w=(y;pc(#hyQZ9_ZIAR;C&Y<=sIbDl{);&l`C3{ zkDby0E2H=M^XIin&>U%i(fa!O$gyL`w02LDr2)qJ8y6kBckkvw?~?`?ZDeGGSglsF zeED)-YJV&Zuu_;{3Cf9by;Q(Te?D~R5D)rOsep0!1ecZh`T4v7EQtjy+zEiBq$J45 z$bgcP64qC+A8(ZE$iu{m6Co!jhX=h+x-%YAr%r{98<~O3Wc6iXfya#-2ejmb($dn1 zqAc_&p2?FZLt!EF@f0o}mq`VT1K6B7b724e{j9HSmI@ec%9JU*DC54iNh)BB^4z&| zp}4qMt$9-T;3dULJdE~SF&`#6bcqDT&UKB`_>;zdC7y* zkL3i$s*7P6QUaSyCT49SB`_`~ScAB<7q!f0^VkArEEVMio<4oL>eM7HFz$k=jc3vV zvj#3{f$?h6m;!dY-D)2XOAFlF+pG5Rh_t}n-Q8**JGnR0L|-+jSf!K^W(`;vVJU$< z9uKRjM@nF))5&VGO9_lOVaH5_aSX=0&uVip^?OFyfW2NXoI7_;t*ISPF-jLd@8I^`A_qm>)_PVY}U~28-4p z-SQCCC>k3ZSe(1O}uxL-S@US3|hq84=j z@BTwh=%PZ%D*)krm$6Vl`;B^w+Hy)rL&XTZm&l3kvhZc2&=G$6^eLP=b&A#85{xiN z5n+_LI(_;yFS>|!> zv6w*7k%I>h>PcaodPYsI^}Pb4a1iQO*45SV#DhW|pK6cln*`nqup-Nf>)2hpcIlN| z{u$oEn*-h{umXy5Q03+2#9%P!0rkW(?ZgFGkxs|ax2&v;m`o-9RCLv5(>+ z-D0tjJ$v>5_HeJ99AZ~o4Byv$vQ7rFn!Knt-MUo*7rT`o9)C-<(qUi|^hXXo0 zJAneiwQJW#<2@~C_)@BJ>OTh0Tg2Vo#hZAr{;iKk>T0I+D1+j5Dax2lg9Nj;DIMeQ z!6UhNhOsetc;sH9Z|X-1jIR)C4S{S-lztfvF#f|spJV(5Z{CR44sVX$;?GXe55fE+ ZzyR4Xd;=*Q?iv69002ovPDHLkV1gL3>M{TT literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-roll-middle.png b/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-roll-middle.png new file mode 100644 index 0000000000000000000000000000000000000000..03ebd1904875ab54fa71cbe948304fbb6266d69a GIT binary patch literal 140 zcmeAS@N?(olHy`uVBq!ia0vp^j6ht&!3HE(th_Z1NJ*BsMwA5Sr)zI%U~#c*aSiV>bgcqrenRo!(7sQ*l5b=5lS zHz>XqA5MduI3AJ3)o!d^br=$OWl&|sX@~VZ$$kjMf^*+5^|JIp^;7XGD@8l4m+Fq5 zbR@7C?o*Zte`1aIhsJvoa%oiq#8D}Ki%3xN-Ksnged2uiUDl?~c^n4)<4I1OL@J{s zVbM5~>zhOgK)+qf;w)+W21PgsudT$_W{dvh%J^~W25rSR8(jc< z_fL>Vf#GBu5+M8?a)ADqw~O-tr|7*i>Is87{CTFawH0p@a7Nez!b2*^x0zygxz-zJ zkY>w@5g3#`3)hxc7oS!>lk?q4A2}BKF4OE+l=$q;=o6RE{<#@Y^c6ReN2_0|6weK| z2uIAnbU>U^b5ZVub3{6?%JM{p_E&w1R}>YSKGQiTyB-Xk=tfFQe;f9 zT7frr1ow~=e}5}V$xMU*Y4I@*Evnes())7`go78n7;RKpIx`kD`E%XMaa-?OQZ-28 zi5A$#5ZvHTSuXzTXl?s@$h+&#_xArktA)i(04d2$ziJv9de^lR6uwRAw+mf3DxL5i zZ%jZ&vDSxmHFDjcMMePT4CJ&RQMrwF1c;wW1GqPo!cSZ^A(b-S3e=k6n)X>l7c;buJdiW4TJFtS@WP;S0XgZL zRZr~^B;3JzAm@^5`N8^|J9S$Eo}Uy8Bag4E`!ifO*#yn~@NB|Sx?P45|HI~V?QC?g z4jFo_Dw>(fI(BuPb+*GvzfZv!*mM-wnSs#~9FzwephKn{u!ofyos z#4hRx06epEi_y1?Qq$3HdB0QwjO&@~ShGCE8=pRb&BawoU$ZzWCoUoOIx7L1t28)d z`yS1UG#((0hLy3j40^L2$5CnL#>$kX73~K-M7th}=wN8AyWKeS(#q%ETnn=#%<90t ze7vB1Kb>x9o>VALdA9*Ak}r2xz8Rw57nC4M3H<_h<7Ki1Fz`+PZbuO%l5Br0a(oSA zg;^8KgM`6aeM%%kx-bcr>ZGgP7Qr&44d-K|2JBpyh%9M!COO<|4q{>(ULo9=0@1|` z{mdIm##t*?42H{S5)ek>iXW($sYURj-j(3}Ao(XED3+A5XEmxS;3@V8-Lioga8`Zm zKGNssuY$f>>S4+Fubmb-0Ub}(`9&iCk5&1fTkn{v_16n9O~0IyKJrn1yh6BoSU@*>AiDNP`V(J;9{Ld+x^0JNiTBTS>lAaos86NG z4tj6a1y0egJwb*di9UsPQkh zVUV*XtU<4yq=oM3s=AeRG6WkrM2iyo(WeWwm?<>=Qy7 zPz!0(bK?q3h@tu4v->WnEttwXV40o7A14u+VB^TJY0coQo|V0F9X(&cElEqk8XNmG iXXL~5fUGV&%!C*_ol6Q};|lnZLeiKIqOXgS)&B#53)X=E literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taiko-drum-outer.png b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taiko-drum-outer.png new file mode 100644 index 0000000000000000000000000000000000000000..87ead80a08146adfa553d242f315f764a467506e GIT binary patch literal 3749 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 DZJ4!X literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taiko-roll-end.png b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taiko-roll-end.png new file mode 100644 index 0000000000000000000000000000000000000000..778c231f20ca9253e64ccffb64a0aa39a3b97a76 GIT binary patch literal 14570 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 PK2D*pzfZY$bjE)HM$sIi literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taikobigcircle@2x.png b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taikobigcircle.png similarity index 100% rename from osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taikobigcircle@2x.png rename to osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taikobigcircle.png diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taikobigcircleoverlay.png b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taikobigcircleoverlay.png new file mode 100644 index 0000000000000000000000000000000000000000..6dca33317131cd69ef449c0a6c0122f0f39cd84c GIT binary patch literal 66280 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 e__0p9W$exLq4HUNEC{AIAxiS~PfR=%CLeS{yi$ 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 Qd;A||`ImC#G8P~IA7%MrKL7v# literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taikohitcircleoverlay.png b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taikohitcircleoverlay.png new file mode 100644 index 0000000000000000000000000000000000000000..2626c394dcdccdb7d74da5460ec3f6d6570ad9a5 GIT binary patch literal 66281 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 z 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�kcUrvy?Ky7PM-eKS~7g% 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 literal 0 HcmV?d00001 From 6674c702800ad0d876808e06ade20e43f2b694e8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 3 Sep 2023 01:20:01 +0300 Subject: [PATCH 0369/4092] Add support for limiting skin texture/animation dimensions --- osu.Game/Skinning/IAnimationTimeReference.cs | 3 +- osu.Game/Skinning/LegacySkinExtensions.cs | 35 ++++++++++++++----- .../Drawables/DrawableStoryboardAnimation.cs | 2 +- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/osu.Game/Skinning/IAnimationTimeReference.cs b/osu.Game/Skinning/IAnimationTimeReference.cs index b6a944ddf8..91f1171a72 100644 --- a/osu.Game/Skinning/IAnimationTimeReference.cs +++ b/osu.Game/Skinning/IAnimationTimeReference.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Textures; using osu.Framework.Timing; +using osuTK; namespace osu.Game.Skinning { @@ -13,7 +14,7 @@ namespace osu.Game.Skinning ///

KVsm2B>cb8^}lrej~MtL3IA_&{r^T6&VRN% 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

/// /// This should not be used to start an animation immediately at the current time. - /// To do so, use with startAtCurrentTime = true instead. + /// To do so, use with startAtCurrentTime = true instead. /// [Cached] public interface IAnimationTimeReference diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs index 0d2461567f..ad3b10edd3 100644 --- a/osu.Game/Skinning/LegacySkinExtensions.cs +++ b/osu.Game/Skinning/LegacySkinExtensions.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osuTK; using static osu.Game.Skinning.SkinConfiguration; namespace osu.Game.Skinning @@ -18,16 +19,16 @@ namespace osu.Game.Skinning public static partial class LegacySkinExtensions { public static Drawable? GetAnimation(this ISkin? source, string componentName, bool animatable, bool looping, bool applyConfigFrameRate = false, string animationSeparator = "-", - bool startAtCurrentTime = true, double? frameLength = null) - => source.GetAnimation(componentName, default, default, animatable, looping, applyConfigFrameRate, animationSeparator, startAtCurrentTime, frameLength); + bool startAtCurrentTime = true, double? frameLength = null, Vector2? maxSize = null) + => source.GetAnimation(componentName, default, default, animatable, looping, applyConfigFrameRate, animationSeparator, startAtCurrentTime, frameLength, maxSize); public static Drawable? GetAnimation(this ISkin? source, string componentName, WrapMode wrapModeS, WrapMode wrapModeT, bool animatable, bool looping, bool applyConfigFrameRate = false, - string animationSeparator = "-", bool startAtCurrentTime = true, double? frameLength = null) + string animationSeparator = "-", bool startAtCurrentTime = true, double? frameLength = null, Vector2? maxSize = null) { if (source == null) return null; - var textures = GetTextures(source, componentName, wrapModeS, wrapModeT, animatable, animationSeparator, out var retrievalSource); + var textures = GetTextures(source, componentName, wrapModeS, wrapModeT, animatable, animationSeparator, maxSize, out var retrievalSource); switch (textures.Length) { @@ -53,7 +54,7 @@ namespace osu.Game.Skinning } } - public static Texture[] GetTextures(this ISkin? source, string componentName, WrapMode wrapModeS, WrapMode wrapModeT, bool animatable, string animationSeparator, out ISkin? retrievalSource) + public static Texture[] GetTextures(this ISkin? source, string componentName, WrapMode wrapModeS, WrapMode wrapModeT, bool animatable, string animationSeparator, Vector2? maxSize, out ISkin? retrievalSource) { retrievalSource = null; @@ -78,7 +79,9 @@ namespace osu.Game.Skinning } // if an animation was not allowed or not found, fall back to a sprite retrieval. - var singleTexture = retrievalSource.GetTexture(componentName, wrapModeS, wrapModeT); + var singleTexture = maxSize != null + ? retrievalSource.GetTextureWithMaxSize(componentName, maxSize.Value, wrapModeS, wrapModeT) + : retrievalSource.GetTexture(componentName, wrapModeS, wrapModeT); return singleTexture != null ? new[] { singleTexture } @@ -88,9 +91,11 @@ namespace osu.Game.Skinning { for (int i = 0; true; i++) { - Texture? texture; + var texture = maxSize != null + ? skin.GetTextureWithMaxSize(getFrameName(i), maxSize.Value, wrapModeS, wrapModeT) + : skin.GetTexture(getFrameName(i), wrapModeS, wrapModeT); - if ((texture = skin.GetTexture(getFrameName(i), wrapModeS, wrapModeT)) == null) + if (texture == null) break; yield return texture; @@ -100,6 +105,20 @@ namespace osu.Game.Skinning string getFrameName(int frameIndex) => $"{componentName}{animationSeparator}{frameIndex}"; } + public static Texture? GetTextureWithMaxSize(this ISkin source, string componentName, Vector2 maxSize, WrapMode wrapModeS = WrapMode.None, WrapMode wrapModeT = WrapMode.None) + { + var texture = source.GetTexture(componentName, wrapModeS, wrapModeT); + if (texture == null) + return texture; + + if (texture.DisplayWidth <= maxSize.X && texture.DisplayHeight <= maxSize.Y) + return texture; + + // use scale adjust property for downscaling the texture in order to meet the specified maximum dimensions. + texture.ScaleAdjust *= Math.Max(texture.DisplayWidth / maxSize.X, texture.DisplayHeight / maxSize.Y); + return texture; + } + public static bool HasFont(this ISkin source, LegacyFont font) { return source.GetTexture($"{source.GetFontPrefix(font)}-0") != null; diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index 82c01ea6a1..309ca63896 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -137,7 +137,7 @@ namespace osu.Game.Storyboards.Drawables // When reading from a skin, we match stables weird behaviour where `FrameCount` is ignored // and resources are retrieved until the end of the animation. - foreach (var texture in skin.GetTextures(Path.GetFileNameWithoutExtension(Animation.Path)!, default, default, true, string.Empty, out _)) + foreach (var texture in skin.GetTextures(Path.GetFileNameWithoutExtension(Animation.Path)!, default, default, true, string.Empty, null, out _)) AddFrame(texture, Animation.FrameDelay); } From 351081eb278ff0f48fff992ba9f0e0de2cf98814 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 3 Sep 2023 01:20:40 +0300 Subject: [PATCH 0370/4092] Add limit to osu! hit circle elements --- .../Skinning/Legacy/LegacyMainCirclePiece.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs index cadac4d319..45a18152c2 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs @@ -21,6 +21,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { public partial class LegacyMainCirclePiece : CompositeDrawable { + private static readonly Vector2 circle_piece_size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); + public override bool RemoveCompletedTransforms => false; /// @@ -51,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy this.priorityLookupPrefix = priorityLookupPrefix; this.hasNumber = hasNumber; - Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); + Size = circle_piece_size; } [BackgroundDependencyLoader] @@ -68,7 +70,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy // expected behaviour in this scenario is not showing the overlay, rather than using hitcircleoverlay.png. InternalChildren = new[] { - CircleSprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = skin.GetTexture(circleName) }) + CircleSprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = skin.GetTextureWithMaxSize(circleName, circle_piece_size) }) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -77,7 +79,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Child = OverlaySprite = new LegacyKiaiFlashingDrawable(() => skin.GetAnimation(@$"{circleName}overlay", true, true, frameLength: 1000 / 2d)) + Child = OverlaySprite = new LegacyKiaiFlashingDrawable(() => skin.GetAnimation(@$"{circleName}overlay", true, true, frameLength: 1000 / 2d, maxSize: circle_piece_size)) { Anchor = Anchor.Centre, Origin = Anchor.Centre, From d286816ba8b1beefe81457dd2533ecc9c937b449 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 3 Sep 2023 01:21:02 +0300 Subject: [PATCH 0371/4092] Add limit to taiko hit elements --- .../Skinning/Legacy/LegacyCirclePiece.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyCirclePiece.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyCirclePiece.cs index 5516e025cd..c94016d2b1 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyCirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyCirclePiece.cs @@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { public partial class LegacyCirclePiece : CompositeDrawable, IHasAccentColour { + private static readonly Vector2 circle_piece_size = new Vector2(128); + private Drawable backgroundLayer = null!; private Drawable? foregroundLayer; @@ -52,9 +54,9 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy string prefix = ((drawableHitObject.HitObject as TaikoStrongableHitObject)?.IsStrong ?? false) ? big_hit : normal_hit; - return skin.GetAnimation($"{prefix}{lookup}", true, false) ?? + return skin.GetAnimation($"{prefix}{lookup}", true, false, maxSize: circle_piece_size) ?? // fallback to regular size if "big" version doesn't exist. - skin.GetAnimation($"{normal_hit}{lookup}", true, false); + skin.GetAnimation($"{normal_hit}{lookup}", true, false, maxSize: circle_piece_size); } // backgroundLayer is guaranteed to exist due to the pre-check in TaikoLegacySkinTransformer. @@ -96,7 +98,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy // Not all skins (including the default osu-stable) have similar sizes for "hitcircle" and "hitcircleoverlay". // This ensures they are scaled relative to each other but also match the expected DrawableHit size. foreach (var c in InternalChildren) - c.Scale = new Vector2(DrawHeight / 128); + c.Scale = new Vector2(DrawHeight / circle_piece_size.Y); if (foregroundLayer is IFramedAnimation animatableForegroundLayer) animateForegroundLayer(animatableForegroundLayer); From f182f571cbaa019ed3ad3272ff208dea98ac51a0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 3 Sep 2023 02:22:41 +0300 Subject: [PATCH 0372/4092] Add limit to catch palpable object elements --- .../Skinning/Legacy/LegacyBananaPiece.cs | 8 ++++++-- .../Skinning/Legacy/LegacyDropletPiece.cs | 7 +++++-- .../Skinning/Legacy/LegacyFruitPiece.cs | 12 ++++++++---- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyBananaPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyBananaPiece.cs index 26832b7271..9f99e3a586 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyBananaPiece.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyBananaPiece.cs @@ -2,17 +2,21 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics.Textures; +using osu.Game.Skinning; +using osuTK; namespace osu.Game.Rulesets.Catch.Skinning.Legacy { public partial class LegacyBananaPiece : LegacyCatchHitObjectPiece { + private static readonly Vector2 banana_max_size = new Vector2(128); + protected override void LoadComplete() { base.LoadComplete(); - Texture? texture = Skin.GetTexture("fruit-bananas"); - Texture? overlayTexture = Skin.GetTexture("fruit-bananas-overlay"); + Texture? texture = Skin.GetTextureWithMaxSize("fruit-bananas", banana_max_size); + Texture? overlayTexture = Skin.GetTextureWithMaxSize("fruit-bananas-overlay", banana_max_size); SetTexture(texture, overlayTexture); } diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyDropletPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyDropletPiece.cs index 7ffd682698..63be1bcf91 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyDropletPiece.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyDropletPiece.cs @@ -2,12 +2,15 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics.Textures; +using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Catch.Skinning.Legacy { public partial class LegacyDropletPiece : LegacyCatchHitObjectPiece { + private static readonly Vector2 droplet_max_size = new Vector2(100); + public LegacyDropletPiece() { Scale = new Vector2(0.8f); @@ -17,8 +20,8 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy { base.LoadComplete(); - Texture? texture = Skin.GetTexture("fruit-drop"); - Texture? overlayTexture = Skin.GetTexture("fruit-drop-overlay"); + Texture? texture = Skin.GetTextureWithMaxSize("fruit-drop", droplet_max_size); + Texture? overlayTexture = Skin.GetTextureWithMaxSize("fruit-drop-overlay", droplet_max_size); SetTexture(texture, overlayTexture); } diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyFruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyFruitPiece.cs index 85b60561dd..e4d25e036b 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyFruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyFruitPiece.cs @@ -2,11 +2,15 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Skinning; +using osuTK; namespace osu.Game.Rulesets.Catch.Skinning.Legacy { internal partial class LegacyFruitPiece : LegacyCatchHitObjectPiece { + private static readonly Vector2 fruit_max_size = new Vector2(128); + protected override void LoadComplete() { base.LoadComplete(); @@ -22,19 +26,19 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy switch (visualRepresentation) { case FruitVisualRepresentation.Pear: - SetTexture(Skin.GetTexture("fruit-pear"), Skin.GetTexture("fruit-pear-overlay")); + SetTexture(Skin.GetTextureWithMaxSize("fruit-pear", fruit_max_size), Skin.GetTextureWithMaxSize("fruit-pear-overlay", fruit_max_size)); break; case FruitVisualRepresentation.Grape: - SetTexture(Skin.GetTexture("fruit-grapes"), Skin.GetTexture("fruit-grapes-overlay")); + SetTexture(Skin.GetTextureWithMaxSize("fruit-grapes", fruit_max_size), Skin.GetTextureWithMaxSize("fruit-grapes-overlay", fruit_max_size)); break; case FruitVisualRepresentation.Pineapple: - SetTexture(Skin.GetTexture("fruit-apple"), Skin.GetTexture("fruit-apple-overlay")); + SetTexture(Skin.GetTextureWithMaxSize("fruit-apple", fruit_max_size), Skin.GetTextureWithMaxSize("fruit-apple-overlay", fruit_max_size)); break; case FruitVisualRepresentation.Raspberry: - SetTexture(Skin.GetTexture("fruit-orange"), Skin.GetTexture("fruit-orange-overlay")); + SetTexture(Skin.GetTextureWithMaxSize("fruit-orange", fruit_max_size), Skin.GetTextureWithMaxSize("fruit-orange-overlay", fruit_max_size)); break; } } From d674856e29d230fb6efd6b5a9c0873f6db628050 Mon Sep 17 00:00:00 2001 From: Magnus-Cosmos Date: Sat, 2 Sep 2023 22:49:29 -0400 Subject: [PATCH 0373/4092] Use existing localisations in `BeatmapInfoWedge` --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 81759f6787..8bbf569566 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -30,6 +30,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using osu.Game.Graphics.Containers; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Screens.Select { @@ -371,7 +372,7 @@ namespace osu.Game.Screens.Select { new InfoLabel(new BeatmapStatistic { - Name = $"Length (Drain: {playableBeatmap.CalculateDrainLength().ToFormattedDuration().ToString()})", + Name = BeatmapsetsStrings.ShowStatsTotalLength(playableBeatmap.CalculateDrainLength().ToFormattedDuration()), CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Length), Content = working.BeatmapInfo.Length.ToFormattedDuration().ToString(), }), @@ -415,7 +416,7 @@ namespace osu.Game.Screens.Select bpmLabelContainer.Child = new InfoLabel(new BeatmapStatistic { - Name = "BPM", + Name = BeatmapsetsStrings.ShowStatsBpm, CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Bpm), Content = labelText }); From 40dbf098d2d31730ddecf38032da48a8d2461910 Mon Sep 17 00:00:00 2001 From: Magnus-Cosmos Date: Sat, 2 Sep 2023 22:51:08 -0400 Subject: [PATCH 0374/4092] Use existing localisation for "view profile" --- osu.Game/Graphics/Containers/LinkFlowContainer.cs | 3 ++- osu.Game/Users/Drawables/ClickableAvatar.cs | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 2d27ce906b..40e883f8ac 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -15,6 +15,7 @@ using osu.Framework.Localisation; using osu.Framework.Platform; using osu.Game.Online; using osu.Game.Users; +using osu.Game.Localisation; namespace osu.Game.Graphics.Containers { @@ -74,7 +75,7 @@ namespace osu.Game.Graphics.Containers } public void AddUserLink(IUser user, Action creationParameters = null) - => createLink(CreateChunkFor(user.Username, true, CreateSpriteText, creationParameters), new LinkDetails(LinkAction.OpenUserProfile, user), "view profile"); + => createLink(CreateChunkFor(user.Username, true, CreateSpriteText, creationParameters), new LinkDetails(LinkAction.OpenUserProfile, user), ContextMenuStrings.ViewProfile); private void createLink(ITextPart textPart, LinkDetails link, LocalisableString tooltipText, Action action = null) { diff --git a/osu.Game/Users/Drawables/ClickableAvatar.cs b/osu.Game/Users/Drawables/ClickableAvatar.cs index e74ffc9d54..677a8fff36 100644 --- a/osu.Game/Users/Drawables/ClickableAvatar.cs +++ b/osu.Game/Users/Drawables/ClickableAvatar.cs @@ -6,14 +6,13 @@ using osu.Framework.Allocation; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Graphics.Containers; +using osu.Game.Localisation; using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Users.Drawables { public partial class ClickableAvatar : OsuClickableContainer { - private const string default_tooltip_text = "view profile"; - public override LocalisableString TooltipText { get @@ -21,7 +20,7 @@ namespace osu.Game.Users.Drawables if (!Enabled.Value) return string.Empty; - return ShowUsernameTooltip ? (user?.Username ?? string.Empty) : default_tooltip_text; + return ShowUsernameTooltip ? (user?.Username ?? string.Empty) : ContextMenuStrings.ViewProfile; } set => throw new NotSupportedException(); } From ae9c901b94201de270a3c2fd6ccdbed59107da05 Mon Sep 17 00:00:00 2001 From: Magnus-Cosmos Date: Sun, 3 Sep 2023 01:45:22 -0400 Subject: [PATCH 0375/4092] Fix `BeatmapInfoWedge` tests failing due to BPM --- osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index a470ed47d4..7cd4f06bce 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -15,6 +15,7 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Sprites; +using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mania; @@ -188,7 +189,7 @@ namespace osu.Game.Tests.Visual.SongSelect { AddUntilStep($"displayed bpm is {target}", () => { - var label = infoWedge.DisplayedContent.ChildrenOfType().Single(l => l.Statistic.Name == "BPM"); + var label = infoWedge.DisplayedContent.ChildrenOfType().Single(l => l.Statistic.Name == BeatmapsetsStrings.ShowStatsBpm); return label.Statistic.Content == target; }); } From 079792644886a6c57fb03eee397d9594419e5847 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 3 Sep 2023 12:19:03 +0300 Subject: [PATCH 0376/4092] Update VerticalAttributeDisplay.cs --- osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs b/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs index 2ad420657c..95d979ebd2 100644 --- a/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs +++ b/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs @@ -12,7 +12,6 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osuTK; - namespace osu.Game.Overlays.Mods { public partial class VerticalAttributeDisplay : Container, IHasCurrentValue From 8281ed5173af43556f8b2e5ba937a2e66eff7f3a Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 3 Sep 2023 14:51:53 +0300 Subject: [PATCH 0377/4092] Fixed "no animations" issue --- osu.Game/Overlays/Mods/ModMapInfoContainer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Mods/ModMapInfoContainer.cs b/osu.Game/Overlays/Mods/ModMapInfoContainer.cs index 378e6f6057..281fe8abe5 100644 --- a/osu.Game/Overlays/Mods/ModMapInfoContainer.cs +++ b/osu.Game/Overlays/Mods/ModMapInfoContainer.cs @@ -50,6 +50,7 @@ namespace osu.Game.Overlays.Mods const float corner_radius = 7; const float border_thickness = 2; + AutoSizeAxes = Axes.Both; InternalChild = content = new InputBlockingContainer { Origin = Anchor.BottomRight, From b17a55d6a84aba14c6387a03d64053f5b82d4751 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93=20Nguy=C3=AAn=20Minh?= Date: Mon, 4 Sep 2023 10:43:05 +0700 Subject: [PATCH 0378/4092] Add length check for slider velocity --- .../Edit/Compose/Components/Timeline/DifficultyPointPiece.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs index 173a665d5c..366518eb58 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs @@ -177,6 +177,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline AddHeader("Final velocity"); AddValue($"{beatmapVelocity * current.Value:#,0.00}x"); + if (sliderVelocities.Length == 0) return; if (sliderVelocities.First() != sliderVelocities.Last()) { AddHeader("Beatmap velocity range"); From d5a89c4c45eddff8eb2d88cb7739c08e3260fecb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93=20Nguy=C3=AAn=20Minh?= Date: Mon, 4 Sep 2023 13:32:42 +0700 Subject: [PATCH 0379/4092] Fix formatting --- .../Compose/Components/Timeline/DifficultyPointPiece.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs index 366518eb58..99fb2ab874 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs @@ -177,7 +177,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline AddHeader("Final velocity"); AddValue($"{beatmapVelocity * current.Value:#,0.00}x"); - if (sliderVelocities.Length == 0) return; + if (sliderVelocities.Length == 0) + { + return; + } + if (sliderVelocities.First() != sliderVelocities.Last()) { AddHeader("Beatmap velocity range"); From 0a1ba2ebe08877717f4617cb4d3070ea33d3b3b9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 4 Sep 2023 15:56:32 +0900 Subject: [PATCH 0380/4092] Remove ModNoMod usage --- .../Mods/TestSceneManiaModDoubleTime.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs index 08e83b04b5..00b79529a9 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs @@ -7,7 +7,6 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Replays; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Replays; using osu.Game.Tests.Visual; @@ -23,7 +22,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods [Test] public void TestHitWindowWithoutDoubleTime() => CreateModTest(new ModTestData { - Mod = new ModNoMod(), PassCondition = () => Player.ScoreProcessor.JudgedHits > 0 && Player.ScoreProcessor.Accuracy.Value != 1, Autoplay = false, Beatmap = new Beatmap From 58844092d6984f96e5b7f1fd263d675979c8b879 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Mon, 4 Sep 2023 16:17:21 +0900 Subject: [PATCH 0381/4092] post a notification instead a screen --- .../Online/TestSceneReplayMissingBeatmap.cs | 14 +- .../Database/MissingBeatmapNotification.cs | 157 ++++++++++++++ osu.Game/OsuGame.cs | 2 - osu.Game/Scoring/ScoreImporter.cs | 10 +- osu.Game/Scoring/ScoreManager.cs | 7 - .../Import/ReplayMissingBeatmapScreen.cs | 199 ------------------ 6 files changed, 169 insertions(+), 220 deletions(-) create mode 100644 osu.Game/Database/MissingBeatmapNotification.cs delete mode 100644 osu.Game/Screens/Import/ReplayMissingBeatmapScreen.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneReplayMissingBeatmap.cs b/osu.Game.Tests/Visual/Online/TestSceneReplayMissingBeatmap.cs index eb84d80051..60197e0eb7 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneReplayMissingBeatmap.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneReplayMissingBeatmap.cs @@ -1,13 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using System.Net; using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Screens.Import; using osu.Game.Tests.Resources; namespace osu.Game.Tests.Visual.Online @@ -24,6 +26,12 @@ namespace osu.Game.Tests.Visual.Online OnlineBeatmapSetID = 173612, BeatmapSet = new APIBeatmapSet { + Title = "FREEDOM Dive", + Artist = "xi", + Covers = new BeatmapSetOnlineCovers + { + Card = "https://assets.ppy.sh/beatmaps/173612/covers/card@2x.jpg" + }, OnlineID = 173612 } }; @@ -40,7 +48,7 @@ namespace osu.Game.Tests.Visual.Online } }); - AddUntilStep("Replay missing screen show", () => Game.ScreenStack.CurrentScreen.GetType() == typeof(ReplayMissingBeatmapScreen)); + AddUntilStep("Replay missing notification show", () => Game.Notifications.ChildrenOfType().Any()); } [Test] @@ -58,7 +66,7 @@ namespace osu.Game.Tests.Visual.Online } }); - AddUntilStep("Replay missing screen not show", () => Game.ScreenStack.CurrentScreen.GetType() != typeof(ReplayMissingBeatmapScreen)); + AddUntilStep("Replay missing notification not show", () => !Game.Notifications.ChildrenOfType().Any()); } private void setupBeatmapResponse(APIBeatmap b) diff --git a/osu.Game/Database/MissingBeatmapNotification.cs b/osu.Game/Database/MissingBeatmapNotification.cs new file mode 100644 index 0000000000..2587160a57 --- /dev/null +++ b/osu.Game/Database/MissingBeatmapNotification.cs @@ -0,0 +1,157 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.IO; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; +using osu.Game.Scoring; +using osuTK.Graphics; + +namespace osu.Game.Database +{ + public partial class MissingBeatmapNotification : ProgressNotification + { + [Resolved] + private BeatmapModelDownloader beatmapDownloader { get; set; } = null!; + + [Resolved] + private ScoreManager scoreManager { get; set; } = null!; + + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); + + [Resolved] + private BeatmapSetOverlay? beatmapSetOverlay { get; set; } + + private Container beatmapPanelContainer = null!; + + private readonly MemoryStream scoreStream; + + private readonly APIBeatmapSet beatmapSetInfo; + + private BeatmapDownloadTracker? downloadTracker; + + private Bindable autodownloadConfig = null!; + + public MissingBeatmapNotification(APIBeatmap beatmap, MemoryStream scoreStream) + { + beatmapSetInfo = beatmap.BeatmapSet!; + + this.scoreStream = scoreStream; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, OsuConfigManager config) + { + autodownloadConfig = config.GetBindable(OsuSetting.AutomaticallyDownloadWhenSpectating); + + Text = "You do not have the required beatmap for this replay"; + + Content.Add(beatmapPanelContainer = new ClickableContainer + { + RelativeSizeAxes = Axes.X, + Height = 70, + Anchor = Anchor.CentreLeft, + Origin = Anchor.TopLeft, + Action = () => beatmapSetOverlay?.FetchAndShowBeatmapSet(beatmapSetInfo.OnlineID) + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + downloadTracker = new BeatmapDownloadTracker(beatmapSetInfo); + downloadTracker.State.BindValueChanged(downloadStatusChanged, true); + + beatmapPanelContainer.Clear(); + beatmapPanelContainer.Child = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 4, + Children = new Drawable[] + { + downloadTracker, + new DelayedLoadWrapper(() => new UpdateableOnlineBeatmapSetCover(BeatmapSetCoverType.Card) + { + OnlineInfo = beatmapSetInfo, + RelativeSizeAxes = Axes.Both + }) + { + RelativeSizeAxes = Axes.Both + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.4f + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Padding = new MarginPadding + { + Left = 10f, + Top = 5f + }, + Children = new Drawable[] + { + new TruncatingSpriteText + { + Text = beatmapSetInfo.Title, + Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 17, italics: true), + RelativeSizeAxes = Axes.X, + }, + new TruncatingSpriteText + { + Text = beatmapSetInfo.Artist, + Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 12, italics: true), + RelativeSizeAxes = Axes.X, + } + } + }, + new DownloadButton + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Width = 50, + Height = 30, + Margin = new MarginPadding + { + Bottom = 1f + }, + Action = () => beatmapDownloader.Download(beatmapSetInfo), + State = { BindTarget = downloadTracker.State } + } + } + }; + + if (autodownloadConfig.Value) + beatmapDownloader.Download(beatmapSetInfo); + } + + private void downloadStatusChanged(ValueChangedEvent status) + { + if (status.NewValue != DownloadState.LocallyAvailable) + return; + + var importTask = new ImportTask(scoreStream, "score.osr"); + scoreManager.Import(this, new[] { importTask }); + } + } +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 5d130af6d4..c60bff9e4c 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -854,8 +854,6 @@ namespace osu.Game MultiplayerClient.PostNotification = n => Notifications.Post(n); - ScoreManager.Performer = this; - // make config aware of how to lookup skins for on-screen display purposes. // if this becomes a more common thing, tracked settings should be reconsidered to allow local DI. LocalConfig.LookupSkinName = id => SkinManager.Query(s => s.ID == id)?.ToString() ?? "Unknown"; diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index 5c354ac3d1..e3fce4a82a 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -10,7 +10,6 @@ using System.Threading; using Newtonsoft.Json; using osu.Framework.Logging; using osu.Framework.Platform; -using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.IO.Archives; @@ -21,8 +20,6 @@ using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; -using osu.Game.Screens; -using osu.Game.Screens.Import; using Realms; namespace osu.Game.Scoring @@ -31,8 +28,6 @@ namespace osu.Game.Scoring { public override IEnumerable HandledExtensions => new[] { ".osr" }; - public IPerformFromScreenRunner? Performer { get; set; } - protected override string[] HashableFileTypes => new[] { ".osr" }; private readonly RulesetStore rulesets; @@ -69,9 +64,6 @@ namespace osu.Game.Scoring private void onMissingBeatmap(LegacyScoreDecoder.BeatmapNotFoundException e, ArchiveReader archive, string name) { - if (Performer == null) - return; - var stream = new MemoryStream(); // stream will close after exception throw, so fetch the stream again. @@ -87,7 +79,7 @@ namespace osu.Game.Scoring req.Success += res => { - Performer.PerformFromScreen(screen => screen.Push(new ReplayMissingBeatmapScreen(res, stream))); + PostNotification?.Invoke(new MissingBeatmapNotification(res, stream)); }; api.Queue(req); diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 9331168ab0..31b5bd8365 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -21,7 +21,6 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Scoring; using osu.Game.Online.API; using osu.Game.Scoring.Legacy; -using osu.Game.Screens; namespace osu.Game.Scoring { @@ -31,12 +30,6 @@ namespace osu.Game.Scoring private readonly ScoreImporter scoreImporter; private readonly LegacyScoreExporter scoreExporter; - [CanBeNull] - public IPerformFromScreenRunner Performer - { - set => scoreImporter.Performer = value; - } - public override bool PauseImports { get => base.PauseImports; diff --git a/osu.Game/Screens/Import/ReplayMissingBeatmapScreen.cs b/osu.Game/Screens/Import/ReplayMissingBeatmapScreen.cs deleted file mode 100644 index 614d652f47..0000000000 --- a/osu.Game/Screens/Import/ReplayMissingBeatmapScreen.cs +++ /dev/null @@ -1,199 +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 System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Audio; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Drawables.Cards; -using osu.Game.Configuration; -using osu.Game.Database; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Online.API.Requests.Responses; -using osu.Game.Overlays; -using osu.Game.Overlays.Settings; -using osu.Game.Scoring; -using osu.Game.Screens.Ranking; -using osuTK; -using Realms; - -namespace osu.Game.Screens.Import -{ - [Cached(typeof(IPreviewTrackOwner))] - public partial class ReplayMissingBeatmapScreen : OsuScreen, IPreviewTrackOwner - { - [Resolved] - private BeatmapModelDownloader beatmapDownloader { get; set; } = null!; - - [Resolved] - private ScoreManager scoreManager { get; set; } = null!; - - [Resolved] - private RealmAccess realm { get; set; } = null!; - - private IDisposable? realmSubscription; - - [Cached] - private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); - - [Resolved] - private INotificationOverlay? notificationOverlay { get; set; } - - private Container beatmapPanelContainer = null!; - private ReplayDownloadButton replayDownloadButton = null!; - private SettingsCheckbox automaticDownload = null!; - - private readonly MemoryStream scoreStream; - - private readonly APIBeatmapSet beatmapSetInfo; - - public ReplayMissingBeatmapScreen(APIBeatmap beatmap, MemoryStream scoreStream) - { - beatmapSetInfo = beatmap.BeatmapSet!; - - this.scoreStream = scoreStream; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours, OsuConfigManager config) - { - InternalChildren = new Drawable[] - { - new Container - { - Masking = true, - CornerRadius = 20, - AutoSizeAxes = Axes.Both, - AutoSizeDuration = 500, - AutoSizeEasing = Easing.OutQuint, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Children = new Drawable[] - { - new Box - { - Colour = colours.Gray5, - RelativeSizeAxes = Axes.Both, - }, - new FillFlowContainer - { - Margin = new MarginPadding(20), - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Spacing = new Vector2(15), - Children = new Drawable[] - { - new OsuSpriteText - { - Text = "Beatmap info", - Font = OsuFont.Default.With(size: 30), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Spacing = new Vector2(15), - Children = new Drawable[] - { - beatmapPanelContainer = new Container - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, - } - }, - automaticDownload = new SettingsCheckbox - { - LabelText = "Automatically download beatmaps", - Current = config.GetBindable(OsuSetting.AutomaticallyDownloadWhenSpectating), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - replayDownloadButton = new ReplayDownloadButton(new ScoreInfo()) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - } - } - } - }, - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - updateStatus(); - realmSubscription = realm.RegisterForNotifications( - realm => realm.All().Where(s => !s.DeletePending), beatmapsChanged); - } - - private void updateStatus() - { - beatmapPanelContainer.Clear(); - beatmapPanelContainer.Child = new BeatmapCardNormal(beatmapSetInfo, allowExpansion: false); - checkForAutomaticDownload(beatmapSetInfo); - } - - private void checkForAutomaticDownload(APIBeatmapSet beatmap) - { - if (!automaticDownload.Current.Value) - return; - - beatmapDownloader.Download(beatmap); - } - - private void beatmapsChanged(IRealmCollection sender, ChangeSet? changes) - { - if (changes?.InsertedIndices == null) return; - - if (!scoreStream.CanRead) return; - - if (sender.Any(b => b.OnlineID == beatmapSetInfo.OnlineID)) - { - var progressNotification = new ImportProgressNotification(); - var importTask = new ImportTask(scoreStream, "score.osr"); - scoreManager.Import(progressNotification, new[] { importTask }) - .ContinueWith(s => - { - s.GetResultSafely>>().FirstOrDefault()?.PerformRead(score => - { - Guid scoreid = score.ID; - Scheduler.Add(() => - { - replayDownloadButton.Score.Value = realm.Realm.Find(scoreid) ?? new ScoreInfo(); - }); - }); - }); - - notificationOverlay?.Post(progressNotification); - - realmSubscription?.Dispose(); - } - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - realmSubscription?.Dispose(); - } - } -} From 5abf271b56e24adeecffbcb60ca3edd8c62e667c Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 3 Sep 2023 21:49:29 -0700 Subject: [PATCH 0382/4092] Implement beatmap options popover --- .../SongSelect/TestSceneSongSelectFooterV2.cs | 7 +- .../Select/FooterV2/BeatmapOptionsPopover.cs | 148 ++++++++++++++++++ .../Select/FooterV2/FooterButtonOptionsV2.cs | 37 ++++- osu.Game/Screens/Select/FooterV2/FooterV2.cs | 10 +- osu.Game/Screens/Select/SongSelect.cs | 18 +-- 5 files changed, 205 insertions(+), 15 deletions(-) create mode 100644 osu.Game/Screens/Select/FooterV2/BeatmapOptionsPopover.cs diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectFooterV2.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectFooterV2.cs index 72adbfc104..ed2ae67ae5 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectFooterV2.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectFooterV2.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Testing; using osu.Game.Overlays; using osu.Game.Overlays.Mods; @@ -37,10 +38,10 @@ namespace osu.Game.Tests.Visual.SongSelect Children = new Drawable[] { - footer = new FooterV2 + new PopoverContainer { - Anchor = Anchor.Centre, - Origin = Anchor.Centre + RelativeSizeAxes = Axes.Both, + Child = footer = new FooterV2(), }, overlay = new DummyOverlay() }; diff --git a/osu.Game/Screens/Select/FooterV2/BeatmapOptionsPopover.cs b/osu.Game/Screens/Select/FooterV2/BeatmapOptionsPopover.cs new file mode 100644 index 0000000000..ec35c6ff38 --- /dev/null +++ b/osu.Game/Screens/Select/FooterV2/BeatmapOptionsPopover.cs @@ -0,0 +1,148 @@ +// 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.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Framework.Localisation; +using osu.Game.Collections; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays; +using osuTK; +using osuTK.Graphics; +using osuTK.Input; + +namespace osu.Game.Screens.Select.FooterV2 +{ + public partial class BeatmapOptionsPopover : OsuPopover + { + private FillFlowContainer buttonFlow = null!; + private readonly FooterButtonOptionsV2 footerButton; + + public BeatmapOptionsPopover(FooterButtonOptionsV2 footerButton) + { + this.footerButton = footerButton; + } + + [BackgroundDependencyLoader] + private void load(ManageCollectionsDialog? manageCollectionsDialog, SongSelect? songSelect, OsuColour colours) + { + Content.Padding = new MarginPadding(5); + + Child = buttonFlow = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(3), + }; + + addButton(@"Manage collections", FontAwesome.Solid.Book, () => manageCollectionsDialog?.Show()); + addButton(@"Delete all difficulties", FontAwesome.Solid.Trash, () => songSelect?.DeleteBeatmap(), colours.Red); + addButton(@"Remove from unplayed", FontAwesome.Regular.TimesCircle, null); + addButton(@"Clear local scores", FontAwesome.Solid.Eraser, () => songSelect?.ClearScores()); + + if (songSelect != null && songSelect.AllowEditing) + addButton(@"Edit beatmap", FontAwesome.Solid.PencilAlt, () => songSelect.Edit()); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(this)); + } + + private void addButton(LocalisableString text, IconUsage icon, Action? action, Color4? colour = null) + { + var button = new OptionButton + { + Text = text, + Icon = icon, + TextColour = colour, + Action = () => + { + Hide(); + action?.Invoke(); + }, + }; + + buttonFlow.Add(button); + } + + private partial class OptionButton : OsuButton + { + public IconUsage Icon { get; init; } + public Color4? TextColour { get; init; } + + public OptionButton() + { + Size = new Vector2(265, 50); + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + BackgroundColour = colourProvider.Background3; + + SpriteText.Colour = TextColour ?? Color4.White; + Content.CornerRadius = 10; + + Add(new SpriteIcon + { + Blending = BlendingParameters.Additive, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Size = new Vector2(17), + X = 15, + Icon = Icon, + Colour = TextColour ?? Color4.White, + }); + } + + protected override SpriteText CreateText() => new OsuSpriteText + { + Depth = -1, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + X = 40 + }; + } + + protected override bool OnKeyDown(KeyDownEvent e) + { + // don't absorb control as ToolbarRulesetSelector uses control + number to navigate + if (e.ControlPressed) return false; + + if (!e.Repeat && e.Key >= Key.Number1 && e.Key <= Key.Number9) + { + int requested = e.Key - Key.Number1; + + OptionButton? found = buttonFlow.Children.ElementAtOrDefault(requested); + + if (found != null) + { + found.TriggerClick(); + return true; + } + } + + return base.OnKeyDown(e); + } + + protected override void UpdateState(ValueChangedEvent state) + { + base.UpdateState(state); + + if (state.NewValue == Visibility.Hidden) + footerButton.IsActive.Value = false; + } + } +} diff --git a/osu.Game/Screens/Select/FooterV2/FooterButtonOptionsV2.cs b/osu.Game/Screens/Select/FooterV2/FooterButtonOptionsV2.cs index 87cca0042a..a1559d32dc 100644 --- a/osu.Game/Screens/Select/FooterV2/FooterButtonOptionsV2.cs +++ b/osu.Game/Screens/Select/FooterV2/FooterButtonOptionsV2.cs @@ -2,14 +2,21 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osu.Game.Input.Bindings; namespace osu.Game.Screens.Select.FooterV2 { - public partial class FooterButtonOptionsV2 : FooterButtonV2 + public partial class FooterButtonOptionsV2 : FooterButtonV2, IHasPopover { + public readonly BindableBool IsActive = new BindableBool(); + [BackgroundDependencyLoader] private void load(OsuColour colour) { @@ -17,6 +24,34 @@ namespace osu.Game.Screens.Select.FooterV2 Icon = FontAwesome.Solid.Cog; AccentColour = colour.Purple1; Hotkey = GlobalAction.ToggleBeatmapOptions; + + Action = () => IsActive.Toggle(); } + + protected override void LoadComplete() + { + base.LoadComplete(); + + IsActive.BindValueChanged(active => + { + OverlayState.Value = active.NewValue ? Visibility.Visible : Visibility.Hidden; + }); + + OverlayState.BindValueChanged(state => + { + switch (state.NewValue) + { + case Visibility.Hidden: + this.HidePopover(); + break; + + case Visibility.Visible: + this.ShowPopover(); + break; + } + }); + } + + public Popover GetPopover() => new BeatmapOptionsPopover(this); } } diff --git a/osu.Game/Screens/Select/FooterV2/FooterV2.cs b/osu.Game/Screens/Select/FooterV2/FooterV2.cs index cd95f3eb6c..0529f0d082 100644 --- a/osu.Game/Screens/Select/FooterV2/FooterV2.cs +++ b/osu.Game/Screens/Select/FooterV2/FooterV2.cs @@ -48,11 +48,17 @@ namespace osu.Game.Screens.Select.FooterV2 private FillFlowContainer buttons = null!; - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + public FooterV2() { RelativeSizeAxes = Axes.X; Height = height; + Anchor = Anchor.BottomLeft; + Origin = Anchor.BottomLeft; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { InternalChildren = new Drawable[] { new Box diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 58755878d0..4ce7a6167e 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -311,9 +311,9 @@ namespace osu.Game.Screens.Select Footer.AddButton(button, overlay); BeatmapOptions.AddButton(@"Manage", @"collections", FontAwesome.Solid.Book, colours.Green, () => manageCollectionsDialog?.Show()); - BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo)); + BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, colours.Pink, DeleteBeatmap); BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.Regular.TimesCircle, colours.Purple, null); - BeatmapOptions.AddButton(@"Clear", @"local scores", FontAwesome.Solid.Eraser, colours.Purple, () => clearScores(Beatmap.Value.BeatmapInfo)); + BeatmapOptions.AddButton(@"Clear", @"local scores", FontAwesome.Solid.Eraser, colours.Purple, ClearScores); } sampleChangeDifficulty = audio.Samples.Get(@"SongSelect/select-difficulty"); @@ -916,18 +916,18 @@ namespace osu.Game.Screens.Select return true; } - private void delete(BeatmapSetInfo? beatmap) + public void DeleteBeatmap() { - if (beatmap == null) return; + if (Beatmap.Value.BeatmapSetInfo == null) return; - dialogOverlay?.Push(new BeatmapDeleteDialog(beatmap)); + dialogOverlay?.Push(new BeatmapDeleteDialog(Beatmap.Value.BeatmapSetInfo)); } - private void clearScores(BeatmapInfo? beatmapInfo) + public void ClearScores() { - if (beatmapInfo == null) return; + if (Beatmap.Value.BeatmapInfo == null) return; - dialogOverlay?.Push(new BeatmapClearScoresDialog(beatmapInfo, () => + dialogOverlay?.Push(new BeatmapClearScoresDialog(Beatmap.Value.BeatmapInfo, () => // schedule done here rather than inside the dialog as the dialog may fade out and never callback. Schedule(() => BeatmapDetails.Refresh()))); } @@ -963,7 +963,7 @@ namespace osu.Game.Screens.Select if (e.ShiftPressed) { if (!Beatmap.IsDefault) - delete(Beatmap.Value.BeatmapSetInfo); + DeleteBeatmap(); return true; } From 6c0bd13308589a8efcc2ba83738bc6ed0c2c6ca9 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 4 Sep 2023 00:21:48 -0700 Subject: [PATCH 0383/4092] Add xmldoc to newly exposed methods --- osu.Game/Screens/Select/SongSelect.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 4ce7a6167e..f6fc55b2a5 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -916,6 +916,9 @@ namespace osu.Game.Screens.Select return true; } + /// + /// Request to delete the current beatmap. + /// public void DeleteBeatmap() { if (Beatmap.Value.BeatmapSetInfo == null) return; @@ -923,6 +926,9 @@ namespace osu.Game.Screens.Select dialogOverlay?.Push(new BeatmapDeleteDialog(Beatmap.Value.BeatmapSetInfo)); } + /// + /// Request to clear the scores of the current beatmap. + /// public void ClearScores() { if (Beatmap.Value.BeatmapInfo == null) return; From 3decadaf519c26a6c4b941d065d41ce441589821 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Mon, 4 Sep 2023 16:18:14 +0900 Subject: [PATCH 0384/4092] use realm query --- osu.Game/Beatmaps/BeatmapManager.cs | 3 ++- osu.Game/Beatmaps/WorkingBeatmapCache.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index d71d7b7f67..1f551f1218 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -26,6 +26,7 @@ using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; using osu.Game.Skinning; using osu.Game.Utils; +using Realms; namespace osu.Game.Beatmaps { @@ -284,7 +285,7 @@ namespace osu.Game.Beatmaps /// /// The query. /// The first result for the provided query, or null if no results were found. - public BeatmapInfo? QueryBeatmap(Expression> query) => Realm.Run(r => r.All().FirstOrDefault(query)?.Detach()); + public BeatmapInfo? QueryBeatmap(Expression> query) => Realm.Run(r => r.All().Filter($"{nameof(BeatmapInfo.BeatmapSet)}.{nameof(BeatmapSetInfo.DeletePending)} == false").FirstOrDefault(query)?.Detach()); /// /// A default representation of a WorkingBeatmap to use when no beatmap is available. diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index c06f4da4ae..78eed626f2 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -88,7 +88,7 @@ namespace osu.Game.Beatmaps public virtual WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo) { - if (beatmapInfo?.BeatmapSet == null || beatmapInfo.BeatmapSet?.DeletePending == true) + if (beatmapInfo?.BeatmapSet == null) return DefaultBeatmap; lock (workingCache) From 164f61f59034f2d713cdb4676f7327a4f41b512f Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Mon, 4 Sep 2023 17:14:04 +0900 Subject: [PATCH 0385/4092] clean up --- .../Database/MissingBeatmapNotification.cs | 53 +++++++------------ 1 file changed, 19 insertions(+), 34 deletions(-) diff --git a/osu.Game/Database/MissingBeatmapNotification.cs b/osu.Game/Database/MissingBeatmapNotification.cs index 2587160a57..7a39c6307b 100644 --- a/osu.Game/Database/MissingBeatmapNotification.cs +++ b/osu.Game/Database/MissingBeatmapNotification.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Configuration; @@ -30,21 +31,12 @@ namespace osu.Game.Database [Resolved] private ScoreManager scoreManager { get; set; } = null!; - [Cached] - private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); - - [Resolved] - private BeatmapSetOverlay? beatmapSetOverlay { get; set; } - - private Container beatmapPanelContainer = null!; - private readonly MemoryStream scoreStream; private readonly APIBeatmapSet beatmapSetInfo; - private BeatmapDownloadTracker? downloadTracker; - private Bindable autodownloadConfig = null!; + private Bindable noVideoSetting = null!; public MissingBeatmapNotification(APIBeatmap beatmap, MemoryStream scoreStream) { @@ -54,35 +46,25 @@ namespace osu.Game.Database } [BackgroundDependencyLoader] - private void load(OsuColour colours, OsuConfigManager config) + private void load(OsuConfigManager config, BeatmapSetOverlay? beatmapSetOverlay) { + BeatmapDownloadTracker downloadTracker = new BeatmapDownloadTracker(beatmapSetInfo); + downloadTracker.State.BindValueChanged(downloadStatusChanged); + autodownloadConfig = config.GetBindable(OsuSetting.AutomaticallyDownloadWhenSpectating); + noVideoSetting = config.GetBindable(OsuSetting.PreferNoVideo); Text = "You do not have the required beatmap for this replay"; - Content.Add(beatmapPanelContainer = new ClickableContainer + Content.Add(new ClickableContainer { RelativeSizeAxes = Axes.X, Height = 70, Anchor = Anchor.CentreLeft, Origin = Anchor.TopLeft, - Action = () => beatmapSetOverlay?.FetchAndShowBeatmapSet(beatmapSetInfo.OnlineID) - }); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - downloadTracker = new BeatmapDownloadTracker(beatmapSetInfo); - downloadTracker.State.BindValueChanged(downloadStatusChanged, true); - - beatmapPanelContainer.Clear(); - beatmapPanelContainer.Child = new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, CornerRadius = 4, + Masking = true, + Action = () => beatmapSetOverlay?.FetchAndShowBeatmapSet(beatmapSetInfo.OnlineID), Children = new Drawable[] { downloadTracker, @@ -125,7 +107,7 @@ namespace osu.Game.Database } } }, - new DownloadButton + new BeatmapDownloadButton(beatmapSetInfo) { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, @@ -134,15 +116,18 @@ namespace osu.Game.Database Margin = new MarginPadding { Bottom = 1f - }, - Action = () => beatmapDownloader.Download(beatmapSetInfo), - State = { BindTarget = downloadTracker.State } + } } } - }; + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); if (autodownloadConfig.Value) - beatmapDownloader.Download(beatmapSetInfo); + beatmapDownloader.Download(beatmapSetInfo, noVideoSetting.Value); } private void downloadStatusChanged(ValueChangedEvent status) From f68a12003a6df0dc32d9eefc391ec5228d1e9ddf Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Mon, 4 Sep 2023 17:37:31 +0900 Subject: [PATCH 0386/4092] check beatmap hash before try to import --- .../Database/MissingBeatmapNotification.cs | 33 ++++++++++++++----- osu.Game/Scoring/ScoreImporter.cs | 2 +- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/osu.Game/Database/MissingBeatmapNotification.cs b/osu.Game/Database/MissingBeatmapNotification.cs index 7a39c6307b..92b33e20be 100644 --- a/osu.Game/Database/MissingBeatmapNotification.cs +++ b/osu.Game/Database/MissingBeatmapNotification.cs @@ -2,18 +2,17 @@ // See the LICENCE file in the repository root for full licence text. using System.IO; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.UserInterface; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; @@ -31,31 +30,43 @@ namespace osu.Game.Database [Resolved] private ScoreManager scoreManager { get; set; } = null!; - private readonly MemoryStream scoreStream; + [Resolved] + private RealmAccess realm { get; set; } = null!; + private readonly MemoryStream scoreStream; private readonly APIBeatmapSet beatmapSetInfo; + private readonly string beatmapHash; private Bindable autodownloadConfig = null!; private Bindable noVideoSetting = null!; - public MissingBeatmapNotification(APIBeatmap beatmap, MemoryStream scoreStream) + public MissingBeatmapNotification(APIBeatmap beatmap, MemoryStream scoreStream, string beatmapHash) { beatmapSetInfo = beatmap.BeatmapSet!; + this.beatmapHash = beatmapHash; this.scoreStream = scoreStream; } [BackgroundDependencyLoader] private void load(OsuConfigManager config, BeatmapSetOverlay? beatmapSetOverlay) { + Text = "You do not have the required beatmap for this replay"; + + realm.Run(r => + { + if (r.All().Any(s => s.OnlineID == beatmapSetInfo.OnlineID)) + { + Text = "You have the corresponding beatmapset but no beatmap, you may need to update the beatmap."; + } + }); + BeatmapDownloadTracker downloadTracker = new BeatmapDownloadTracker(beatmapSetInfo); downloadTracker.State.BindValueChanged(downloadStatusChanged); autodownloadConfig = config.GetBindable(OsuSetting.AutomaticallyDownloadWhenSpectating); noVideoSetting = config.GetBindable(OsuSetting.PreferNoVideo); - Text = "You do not have the required beatmap for this replay"; - Content.Add(new ClickableContainer { RelativeSizeAxes = Axes.X, @@ -135,8 +146,14 @@ namespace osu.Game.Database if (status.NewValue != DownloadState.LocallyAvailable) return; - var importTask = new ImportTask(scoreStream, "score.osr"); - scoreManager.Import(this, new[] { importTask }); + realm.Run(r => + { + if (r.All().Any(s => s.MD5Hash == beatmapHash)) + { + var importTask = new ImportTask(scoreStream, "score.osr"); + scoreManager.Import(this, new[] { importTask }); + } + }); } } } diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index e3fce4a82a..650e25a512 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -79,7 +79,7 @@ namespace osu.Game.Scoring req.Success += res => { - PostNotification?.Invoke(new MissingBeatmapNotification(res, stream)); + PostNotification?.Invoke(new MissingBeatmapNotification(res, stream, e.Hash)); }; api.Queue(req); From 87aa191c121214c18e0a4270e7f20f856da40ab2 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Mon, 4 Sep 2023 17:53:12 +0900 Subject: [PATCH 0387/4092] use realm Subscription instead of Beatmap Download Tracker --- .../Database/MissingBeatmapNotification.cs | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/osu.Game/Database/MissingBeatmapNotification.cs b/osu.Game/Database/MissingBeatmapNotification.cs index 92b33e20be..86522d0864 100644 --- a/osu.Game/Database/MissingBeatmapNotification.cs +++ b/osu.Game/Database/MissingBeatmapNotification.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 System; using System.IO; using System.Linq; using osu.Framework.Allocation; @@ -13,12 +14,12 @@ using osu.Game.Beatmaps.Drawables; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Online; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osu.Game.Scoring; using osuTK.Graphics; +using Realms; namespace osu.Game.Database { @@ -40,6 +41,8 @@ namespace osu.Game.Database private Bindable autodownloadConfig = null!; private Bindable noVideoSetting = null!; + private IDisposable? realmSubscription; + public MissingBeatmapNotification(APIBeatmap beatmap, MemoryStream scoreStream, string beatmapHash) { beatmapSetInfo = beatmap.BeatmapSet!; @@ -53,17 +56,17 @@ namespace osu.Game.Database { Text = "You do not have the required beatmap for this replay"; + realmSubscription = realm.RegisterForNotifications( + realm => realm.All().Where(s => !s.DeletePending), beatmapsChanged); + realm.Run(r => { - if (r.All().Any(s => s.OnlineID == beatmapSetInfo.OnlineID)) + if (r.All().Any(s => !s.DeletePending && s.OnlineID == beatmapSetInfo.OnlineID)) { - Text = "You have the corresponding beatmapset but no beatmap, you may need to update the beatmap."; + Text = "You have the corresponding beatmapset but no beatmap, you may need to update the beatmapset."; } }); - BeatmapDownloadTracker downloadTracker = new BeatmapDownloadTracker(beatmapSetInfo); - downloadTracker.State.BindValueChanged(downloadStatusChanged); - autodownloadConfig = config.GetBindable(OsuSetting.AutomaticallyDownloadWhenSpectating); noVideoSetting = config.GetBindable(OsuSetting.PreferNoVideo); @@ -78,7 +81,6 @@ namespace osu.Game.Database Action = () => beatmapSetOverlay?.FetchAndShowBeatmapSet(beatmapSetInfo.OnlineID), Children = new Drawable[] { - downloadTracker, new DelayedLoadWrapper(() => new UpdateableOnlineBeatmapSetCover(BeatmapSetCoverType.Card) { OnlineInfo = beatmapSetInfo, @@ -141,19 +143,16 @@ namespace osu.Game.Database beatmapDownloader.Download(beatmapSetInfo, noVideoSetting.Value); } - private void downloadStatusChanged(ValueChangedEvent status) + private void beatmapsChanged(IRealmCollection sender, ChangeSet? changes) { - if (status.NewValue != DownloadState.LocallyAvailable) - return; + if (changes?.InsertedIndices == null) return; - realm.Run(r => + if (sender.Any(s => s.Beatmaps.Any(b => b.MD5Hash == beatmapHash))) { - if (r.All().Any(s => s.MD5Hash == beatmapHash)) - { - var importTask = new ImportTask(scoreStream, "score.osr"); - scoreManager.Import(this, new[] { importTask }); - } - }); + var importTask = new ImportTask(scoreStream, "score.osr"); + scoreManager.Import(this, new[] { importTask }); + realmSubscription?.Dispose(); + } } } } From fd1fce486a18c6b12859b3fa197c707bac583751 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Tue, 5 Sep 2023 00:21:08 +0900 Subject: [PATCH 0388/4092] ensure dispose realmSubscription --- osu.Game/Database/MissingBeatmapNotification.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Database/MissingBeatmapNotification.cs b/osu.Game/Database/MissingBeatmapNotification.cs index 86522d0864..d6674b9434 100644 --- a/osu.Game/Database/MissingBeatmapNotification.cs +++ b/osu.Game/Database/MissingBeatmapNotification.cs @@ -154,5 +154,11 @@ namespace osu.Game.Database realmSubscription?.Dispose(); } } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + realmSubscription?.Dispose(); + } } } From f616648730ac9fd438cc2d15aeb7eb065f875cae Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 4 Sep 2023 09:40:35 -0700 Subject: [PATCH 0389/4092] Remove icon blending --- osu.Game/Screens/Select/FooterV2/BeatmapOptionsPopover.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Select/FooterV2/BeatmapOptionsPopover.cs b/osu.Game/Screens/Select/FooterV2/BeatmapOptionsPopover.cs index ec35c6ff38..4e1334fd11 100644 --- a/osu.Game/Screens/Select/FooterV2/BeatmapOptionsPopover.cs +++ b/osu.Game/Screens/Select/FooterV2/BeatmapOptionsPopover.cs @@ -97,7 +97,6 @@ namespace osu.Game.Screens.Select.FooterV2 Add(new SpriteIcon { - Blending = BlendingParameters.Additive, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Size = new Vector2(17), From bf71099e5743efb966482d5b145a551b0208729f Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 4 Sep 2023 11:34:21 -0700 Subject: [PATCH 0390/4092] Fix truncating sprite text usage --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index b7b60cffab..7821aa5be0 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -280,15 +280,14 @@ namespace osu.Game.Screens.Select RelativeSizeAxes = Axes.X, Children = new Drawable[] { - TitleLabel = new OsuSpriteText + TitleLabel = new TruncatingSpriteText { Shadow = true, Current = { BindTarget = titleBinding }, Font = OsuFont.TorusAlternate.With(size: 40, weight: FontWeight.SemiBold), RelativeSizeAxes = Axes.X, - Truncate = true }, - ArtistLabel = new OsuSpriteText + ArtistLabel = new TruncatingSpriteText { // TODO : figma design has a diffused shadow, instead of the solid one present here, not possible currently as far as i'm aware. Shadow = true, @@ -296,7 +295,6 @@ namespace osu.Game.Screens.Select // Not sure if this should be semi bold or medium Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold), RelativeSizeAxes = Axes.X, - Truncate = true } } } From e8a793425bf28e19ba3667c8c712c4db96fc09fa Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 4 Sep 2023 12:38:40 -0700 Subject: [PATCH 0391/4092] Use right padding instead of negative x offset --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index 7821aa5be0..284f14cd9e 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -155,18 +155,21 @@ namespace osu.Game.Screens.Select LoadComponentAsync(loadingInfo = new Container { - Masking = true, - // We offset this by the portion of the colour bar underneath we wish to show - X = -colour_bar_width, - CornerRadius = corner_radius, + Padding = new MarginPadding { Right = colour_bar_width }, RelativeSizeAxes = Axes.Both, Depth = DisplayedContent?.Depth + 1 ?? 0, - Children = new Drawable[] + Child = new Container { - // TODO: New wedge design uses a coloured horizontal gradient for its background, however this lacks implementation information in the figma draft. - // pending https://www.figma.com/file/DXKwqZhD5yyb1igc3mKo1P?node-id=2980:3361#340801912 being answered. - new BeatmapInfoWedgeBackground(beatmap) { Shear = -Shear }, - Info = new WedgeInfoText(beatmap) { Shear = -Shear } + Masking = true, + CornerRadius = corner_radius, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + // TODO: New wedge design uses a coloured horizontal gradient for its background, however this lacks implementation information in the figma draft. + // pending https://www.figma.com/file/DXKwqZhD5yyb1igc3mKo1P?node-id=2980:3361#340801912 being answered. + new BeatmapInfoWedgeBackground(beatmap) { Shear = -Shear }, + Info = new WedgeInfoText(beatmap) { Shear = -Shear } + } } }, loaded => { From 2df20027355ddbf24bd43401ef5feabf5a7e1823 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 4 Sep 2023 12:42:01 -0700 Subject: [PATCH 0392/4092] Move negative corner radius margin to constructor --- .../SongSelect/TestSceneBeatmapInfoWedgeV2.cs | 16 ++++++++++------ osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 1 + 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs index 09b93119cc..ae5b739c4d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs @@ -50,13 +50,17 @@ namespace osu.Game.Tests.Visual.SongSelect RelativeSizeAxes = Axes.X, Margin = new MarginPadding { Top = 20, Left = -10 } }, - infoWedge = new TestBeatmapInfoWedgeV2 + new Container { - State = { Value = Visibility.Visible }, - Width = 0.6f, - RelativeSizeAxes = Axes.X, - Margin = new MarginPadding { Top = 20, Left = -10 } - }, + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 20 }, + Child = infoWedge = new TestBeatmapInfoWedgeV2 + { + State = { Value = Visibility.Visible }, + Width = 0.6f, + RelativeSizeAxes = Axes.X, + }, + } }); AddSliderStep("change star difficulty", 0, 11.9, 5.55, v => diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index 284f14cd9e..742d9011b9 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -55,6 +55,7 @@ namespace osu.Game.Screens.Select Height = WEDGE_HEIGHT; Shear = wedged_container_shear; Masking = true; + Margin = new MarginPadding { Left = -corner_radius }; EdgeEffect = new EdgeEffectParameters { Colour = Colour4.Black.Opacity(0.2f), From e70510ef191ef345eee35b490a12118fbb29a755 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 4 Sep 2023 12:56:20 -0700 Subject: [PATCH 0393/4092] Move drawable and binding logic to standard places --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index 742d9011b9..de3e634819 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -46,9 +46,9 @@ namespace osu.Game.Screens.Select protected WedgeInfoText? Info { get; private set; } - private readonly Container difficultyColourBar; - private readonly StarCounter starCounter; - private readonly BufferedContainer bufferedContent; + private Container difficultyColourBar = null!; + private StarCounter starCounter = null!; + private BufferedContainer bufferedContent = null!; public BeatmapInfoWedgeV2() { @@ -63,7 +63,11 @@ namespace osu.Game.Screens.Select Radius = 3, }; CornerRadius = corner_radius; + } + [BackgroundDependencyLoader] + private void load() + { // We want to buffer the wedge to avoid weird transparency overlaps between the colour bar and the background. Child = bufferedContent = new BufferedContainer(pixelSnapping: true) { @@ -107,9 +111,10 @@ namespace osu.Game.Screens.Select }; } - [BackgroundDependencyLoader] - private void load() + protected override void LoadComplete() { + base.LoadComplete(); + ruleset.BindValueChanged(_ => updateDisplay()); } @@ -228,6 +233,8 @@ namespace osu.Game.Screens.Select public WedgeInfoText(WorkingBeatmap working) { this.working = working; + + RelativeSizeAxes = Axes.Both; } [BackgroundDependencyLoader] @@ -236,8 +243,6 @@ namespace osu.Game.Screens.Select var beatmapInfo = working.BeatmapInfo; var metadata = working.Metadata; - RelativeSizeAxes = Axes.Both; - titleBinding = localisation.GetLocalisedBindableString(new RomanisableString(metadata.TitleUnicode, metadata.Title)); artistBinding = localisation.GetLocalisedBindableString(new RomanisableString(metadata.ArtistUnicode, metadata.Artist)); From 82fb9dc2ef5eb03f2db25426f11a19f836e62f68 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 4 Sep 2023 12:59:21 -0700 Subject: [PATCH 0394/4092] Simplify text initialization --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index de3e634819..fd655c50ca 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -212,9 +212,6 @@ namespace osu.Game.Screens.Select private StarRatingDisplay starRatingDisplay = null!; - private ILocalisedBindableString titleBinding = null!; - private ILocalisedBindableString artistBinding = null!; - private readonly WorkingBeatmap working; public IBindable DisplayedStars => starRatingDisplay.DisplayedStars; @@ -238,14 +235,11 @@ namespace osu.Game.Screens.Select } [BackgroundDependencyLoader] - private void load(LocalisationManager localisation) + private void load() { var beatmapInfo = working.BeatmapInfo; var metadata = working.Metadata; - titleBinding = localisation.GetLocalisedBindableString(new RomanisableString(metadata.TitleUnicode, metadata.Title)); - artistBinding = localisation.GetLocalisedBindableString(new RomanisableString(metadata.ArtistUnicode, metadata.Artist)); - Children = new Drawable[] { new FillFlowContainer @@ -292,7 +286,7 @@ namespace osu.Game.Screens.Select TitleLabel = new TruncatingSpriteText { Shadow = true, - Current = { BindTarget = titleBinding }, + Text = new RomanisableString(metadata.TitleUnicode, metadata.Title), Font = OsuFont.TorusAlternate.With(size: 40, weight: FontWeight.SemiBold), RelativeSizeAxes = Axes.X, }, @@ -300,7 +294,7 @@ namespace osu.Game.Screens.Select { // TODO : figma design has a diffused shadow, instead of the solid one present here, not possible currently as far as i'm aware. Shadow = true, - Current = { BindTarget = artistBinding }, + Text = new RomanisableString(metadata.ArtistUnicode, metadata.Artist), // Not sure if this should be semi bold or medium Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold), RelativeSizeAxes = Axes.X, From e0a9c7e9a9680004b98a8086354fb758b65daa51 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 4 Sep 2023 14:54:58 -0700 Subject: [PATCH 0395/4092] Fix wedge showing abruptly in test --- osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs index ae5b739c4d..3236841dc3 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs @@ -56,7 +56,6 @@ namespace osu.Game.Tests.Visual.SongSelect Padding = new MarginPadding { Top = 20 }, Child = infoWedge = new TestBeatmapInfoWedgeV2 { - State = { Value = Visibility.Visible }, Width = 0.6f, RelativeSizeAxes = Axes.X, }, @@ -165,6 +164,7 @@ namespace osu.Game.Tests.Visual.SongSelect { containerBefore = infoWedge.DisplayedContent; infoWedge.Beatmap = Beatmap.Value = b == null ? Beatmap.Default : CreateWorkingBeatmap(b); + infoWedge.Show(); }); AddUntilStep("wait for async load", () => infoWedge.DisplayedContent != containerBefore); From 854bb323cc09e0a296ebd2ca948e4a6074950044 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 4 Sep 2023 15:02:00 -0700 Subject: [PATCH 0396/4092] Remove weird red edge effect visibility --- .../Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs index 3236841dc3..827d23c0fc 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs @@ -7,7 +7,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Testing; @@ -94,17 +93,6 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestWedgeVisibility() { - // Mostly just in case someone runs this test before others, - // leading to the shadow being very hard to see if it is black - AddStep("make shadow red for test visibility", () => - { - infoWedge.EdgeEffect = new EdgeEffectParameters - { - Colour = Colour4.Red, - Type = EdgeEffectType.Shadow, - Radius = 5, - }; - }); AddStep("hide", () => { infoWedge.Hide(); }); AddWaitStep("wait for hide", 3); AddAssert("check visibility", () => infoWedge.Alpha == 0); From 9accd0ded262e451792a10013762e442b5523441 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 4 Sep 2023 15:02:38 -0700 Subject: [PATCH 0397/4092] Fix star rating rolling counter regression --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index fd655c50ca..5316b4620b 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -314,7 +314,7 @@ namespace osu.Game.Screens.Select starRatingDisplay.Current.Value = s.NewValue ?? default; // Don't roll the counter on initial display (but still allow it to roll on applying mods etc.) - if (starRatingDisplay.Alpha > 0) + if (!starRatingDisplay.IsPresent) starRatingDisplay.FinishTransforms(true); starRatingDisplay.FadeIn(transition_duration); From 98d027d207143532178e4588c279368abb391d8b Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 4 Sep 2023 21:34:53 -0700 Subject: [PATCH 0398/4092] Fix star counter animating weird and delayed --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index 5316b4620b..de930ff837 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -187,11 +187,16 @@ namespace osu.Game.Screens.Select Info.DisplayedStars.BindValueChanged(s => { - starCounter.Current = (float)s.NewValue; starCounter.Colour = s.NewValue >= 6.5 ? colours.Orange1 : Colour4.Black.Opacity(0.75f); difficultyColourBar.FadeColour(colours.ForStarDifficulty(s.NewValue)); }, true); + + Info.ActualStars.BindValueChanged(s => + { + // use actual stars as star counter has its own animation + starCounter.Current = (float)s.NewValue; + }); }); }); @@ -215,6 +220,7 @@ namespace osu.Game.Screens.Select private readonly WorkingBeatmap working; public IBindable DisplayedStars => starRatingDisplay.DisplayedStars; + public Bindable ActualStars = new Bindable(); [Resolved] private IBindable> mods { get; set; } = null!; @@ -312,6 +318,7 @@ namespace osu.Game.Screens.Select starDifficulty.BindValueChanged(s => { starRatingDisplay.Current.Value = s.NewValue ?? default; + ActualStars.Value = s.NewValue?.Stars ?? 0; // Don't roll the counter on initial display (but still allow it to roll on applying mods etc.) if (!starRatingDisplay.IsPresent) From 94516133912a4c32048dddc52c53fb9696bb0477 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 4 Sep 2023 21:39:26 -0700 Subject: [PATCH 0399/4092] Rename null beatmap test to indicate there's a background --- osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs index 827d23c0fc..a8484caf1c 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs @@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.SongSelect } [Test] - public void TestNullBeatmap() + public void TestNullBeatmapWithBackground() { selectBeatmap(null); AddAssert("check default title", () => infoWedge.Info!.TitleLabel.Current.Value == Beatmap.Default.BeatmapInfo.Metadata.Title); From 1215ad7ace400cb404121d9a8ead9ac81f2ad2a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Sep 2023 15:40:00 +0900 Subject: [PATCH 0400/4092] 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 2d15bce85a..2bfdce5ab8 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + 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 diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-apple.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-apple.png index 6a862572faca191c265bd1bdd76283c08fdab790..17f3be9c262953cac52da794d601bb853d8dc276 100755 GIT binary patch literal 4677 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 Hu0mjf9r&r4 literal 26685 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 B13Lf! diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-bananas-overlay.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-bananas-overlay.png index 9d1d6d1177b9e36bcef557251ba3914fa5acbae2..2c94ea78bfdba901614472a3215401d7b0a2dd71 100755 GIT binary patch literal 4949 zcmV-b6RPZqP)KLZ*U+5Lu!Sk^o_Z5E4Meg@_7P6crJiNL9pw)e1;Xm069{HJUZAPk55R%$-RIA z6-eL&AQ0xu!e<4=008gy@A0LT~suv4>S3ILP<0Bm`DLLvaF4FK%)Nj?Pt*r}7;7Xa9z9H|HZjR63e zC`Tj$K)V27Re@400>HumpsYY5E(E}?0f1SyGDiY{y#)Yvj#!WnKwtoXnL;eg03bL5 z07D)V%>y7z1E4U{zu>7~aD})?0RX_umCct+(lZpemCzb@^6=o|A>zVpu|i=NDG+7} zl4`aK{0#b-!z=TL9Wt0BGO&T{GJWpjryhdijfaIQ&2!o}p04JRKYg3k&Tf zVxhe-O!X z{f;To;xw^bEES6JSc$k$B2CA6xl)ltA<32E66t?3@gJ7`36pmX0IY^jz)rRYwaaY4 ze(nJRiw;=Qb^t(r^DT@T3y}a2XEZW-_W%Hszxj_qD**t_m!#tW0KDiJT&R>6OvVTR z07RgHDzHHZ48atvzz&?j9lXF70$~P3Knx_nJP<+#`N z#-MZ2bTkiLfR>_b(HgWKJ%F~Nr_oF3b#wrIijHG|(J>BYjM-sajE6;FiC7vY#};Gd zST$CUHDeuEH+B^pz@B062qXfFfD`NpUW5?BY=V%GM_5c)L#QR}BeW8_2v-S%gfYS= zB9o|3v?Y2H`NVi)In3rTB8+ej^> zQ=~r95NVuDChL%G$=>7$vVg20myx%S50Foi`^m%Pw-h?Xh~i8Mq9jtJloCocWk2Nv zrJpiFnV_ms&8eQ$2&#xWpIS+6pmtC%Q-`S&GF4Q#^mhymh7E(qNMa}%YZ-ePrx>>xFPTiH1=E+A$W$=bG8>s^ zm=Bn5Rah$aDtr}@$`X}2l~$F0mFKEdRdZE8)p@E5RI61Ft6o-prbbn>P~)iy)E2AN zsU20jsWz_8Qg>31P|s0cqrPALg8E|(vWA65poU1JRAaZs8I2(p#xiB`SVGovRs-uS zYnV-9TeA7=Om+qP8+I>yOjAR1s%ETak!GFdam@h^# z)@rS0t$wXH+Irf)+G6c;?H29p+V6F6oj{!|o%K3xI`?%6x;DB|x`n#ibhIR?(H}Q3Gzd138Ei2)WAMz7W9Vy`X}HnwgyEn!VS)>mv$8&{hQn>w4zwy3R}t;BYlZQm5)6pty=DfLrs+A-|>>;~;Q z_F?uV_HFjh9n2gO9o9Q^JA86v({H5aB!kjoO6 zc9$1ZZKsN-Zl8L~mE{`ly3)1N^`o1+o7}D0ZPeY&J;i;i`%NyJ8_8Y6J?}yE@b_5a zam?eLr<8@mESk|3$_SkmS{wQ>%qC18))9_|&j{ZT zes8AvOzF(F2#DZEY>2oYX&IRp`F#{ADl)1r>QS^)ba8a|EY_^#S^HO&t^Rgqwv=MZThqqEWH8 zxJo>d=ABlR_Bh=;eM9Tw|Ih34~oTE|= zX_mAr*D$vzw@+p(E0Yc6dFE}(8oqt`+R{gE3x4zjX+Sb3_cYE^= zgB=w+-tUy`ytONMS8KgRef4hA?t0j zufM;t32jm~jUGrkaOInTZ`zyfns>EuS}G30LFK_G-==(f<51|K&cocp&EJ`SxAh3? zNO>#LI=^+SEu(FqJ)ynt=!~PC9bO$rzPJB=?=j6w@a-(u02P7 zaQ)#(uUl{HW%tYNS3ItC^iAtK(eKlL`f9+{bJzISE?u8_z3;~C8@FyI-5j_jy7l;W z_U#vU3hqqYU3!mrul&B+{ptt$59)uk{;_4iZQ%G|z+lhASr6|H35TBkl>gI*;nGLU zN7W-nBaM%pA0HbH8olyl&XeJ%vZoWz%6?Y=dFykl=imL}`%BMQ{Mhgd`HRoLu6e2R za__6DuR6yg#~-}Tc|Gx_{H@O0eebyMy5GmWADJlpK>kqk(fVV@r_fLLKIeS?{4e)} z^ZO;zpECde2(C#)K~#7F?cD{897oo`@i*&u))Z!1GJM~Wz$Bef3Yys#hE{mY7c@Nsvg*(?s<}aEvDVSG!3t+L_XFyYQkvz5>H_< zKEf94K^jM)UptC4_FxM>!XiA3+c6jyL1KqX)Ln(icov_b4O$`x@G+jnWL#wdlwVie zfG4m8T47u81a3fA3!pT*U=&_L8d{~&cnPD>#RBNOMx?MC8Zo<(LZbzczae-X9nb>p z#RqsEGtr8P7={7phnBDP!vGA!M6_Zip2r8+11(SoUdIp%AiGki;CEgl2IZ|HME@L=40~aU7b(Nz6gARstA;ZO|NKACcA8`;Gq63ekK5hZ@#X4w+lSrW#B&K>H zg_F<_>(DnY0gT6CXo$sVfYgWvEQW?SjPX$pAb}JzP>(M$7E+_e;wz{}1}P+>7C=1~ zLPNZO^C30ve7pb+u?XFw62Qgy4C;}_GzjoDGYyBK9-rajY7gKFY=U}hL<__?wO}LE zV-v2Z)&Q=>PN>H`^nipiJun~Yu@l!;TL71b{qys_kueFRvP5uswFGbxHbWiS(F!Su zX+=BKVKXkSbO1?w0(Cfp8IVpfGjJy4#xPl_01}uN^3R_DDGD$)L{V< z5K~eKEPy(!N3xUwJPvi(Sk{+3rUy1c9iAvv07E=CA?b>|-Zd>qdv3yq6gz-=Y==5b zgH(i>4t3a$`eFt!JLHqDRFruU>M*-l0W{;3XPD!BNJW|R@g+3i6q<_>!0S+lv5=}@ z#zGxl?`!}gpbiTmRmAuo4M%h)fMw8tW4IPlRZIg;Km(Q)8o;ekhgpy+Wc)YlqY4RN zg=dSDo{*|!dg1^yU`4(G{2c1=M@UsOe}Fm+%_o4jJR2@_hg3Dw9ebexZ{-?5qi6lz z1CU5C4|vucHsumP%5&#`DI^liB{=3E9PtNmo@c4jKOvD~{^8lU>b$T3ZiYGxghYxN z=zk>i2k;-yw|(4= z4Ok6{G_wjC@RTcntTuQf1DN93ezgt~X{OF|b3gU_ z0Xz*2cn4x);T>qeGx$0H_m(bqLrg5(<5^k!bpY~pYv4z_`iS{?|okuVzxYl1pl0x|I)3bnED+8hf%f`ly$sSS7g z7lzovZ_k)mZ0W{U@ff06KgdG`UN5%j{p$>n5M3NmEHLP}zjk9CpRzmU(V3Zvk z89B3{4x{bp*!a8Nb9C%-NTC6YggPvMM1mb2J+klsfH$BHV<1(*jDb453F&MA%{T=O z_yXrcD$1OXFQEaa(A@a|fH_cy=OGnkUVu8JAjJxx9^0V~(;yXLra>KcpuTtk07KC3 zIa#CyV#=z;Gr!S}p^)MR06YqH*oYnwQx-k25$fl1oha6W{7cW#zv^eW?TuW&H#W*ung*vwhu^7(~yRGEW;&`>J9+thDFd2 z&*OYZjXNJNKtn7>H%K%B01|i*8K}n>7z?RUWAO#lBZCJ^dIMdiml$o(5R1?NsSyoW z1PyTn6CglT0|5GB9W=yA%t9|nO!dMnoP>s0hrW<_1ORl!qv(K!IDkLI?2)lM{(u9} z5FL0FT_G_G0KgFJfM&4|_eFeF@{T zpjn*393&yJ4Zx!h-hk$41O9~Tt8`_~pRgX9qc_k8QZZbr!ALBF<|u=en2ABCgOphv z24N;vA_L9QGK_=(7QokZ6jnj=bO`U@Zrp%NOSI+s4Y(Wc;1D!Vt1t=zSO7l5@isc3 z1=@`d@H`$sD<)zX2B0rmzSb85FborM7aqX#_yD`11?s??7!L8U06dzI!ft59>_G}m z5WoT`R2PiGD>w|TQit&hMxhG?umFlFi5u_~c0wy`C!WF$NJ1ioOVnM3DR>s2;Rv)u z4&Y-viz&DY5@|6WtQdn|<0&l0N7#zJNaFU{rhswnpCUhC>!*Sfm*?$tno&VzeI`1JSy01&CeRrCM=guVm; zxY+0)X5J+Z0D$`<*ucnF&pH6&}C(~4f3*w*ateJ8w01uvy9W1rHNHhb%_?> z)|b7Dw8Juiy?YoM{%=L9J_vu?S~s@V&EzI;!}7AOy1m#FM=dUE_;R+J>1p*G;)xkd z)^eX-j`>hC3|t&My}I_Emlb3xbUdDnn%S+nbGB+v$D8u;+#$bfZN!rK*;NGZ9(BFM z;oYapHgg2yT|NPFNH5nfWtHEaC|w+!T<{&d`Pkh5i-E=VE#1r2Y*UMd)!#RbPbbUI zQYv#qp7>CgUC_pEc(!>wXAPh{xYHm~OK=v78hEfNCBOH&hJ-!LW6HKsg1qi@ zW~vch-B^0Mvz}somSfMHJzNkIO*L9NrkvqppEV_vtWeI8+9%{=o5wW|cp<{yp`iBmr}EmCUt!m9jKGKW9{AbU*(0uaX1W^T!RS z3gmYYs8+w3PQ}kK{^7#wmQ01z)4rr+Lw0x$4((Qn)szfta-f-2#yPjTo4-JkRd-|* zeSr+KSr-O*;cMK@$TPfo%6Fzm8sgq-^&4eZD{Qi4p@($yr23M)(YRsW+*T#3%*x3^ z9gLcvDmikM5_w)e%i#Q)8A>d*_RL41ZtYPB9!I8uH_zM5N9HFhjYj(YU&c&KZ3jDl zq`WW{N|BlMn^#VgJDJfeZzNg}`5w=fBKxDHP)p*5U93;^h_7nks~zYq)gv=!`>G!w zKDt+Yy&;y_tv(X?wtIR!y)#bICDFiHGdXkcY@k=W{ISHkOkPCNu{cNG93*r z!hcJ|lpXV3j(+aX4=?$pqm#E@^K1#p(q_`q)a3R7OzcybskJn#n4AhP=toYa*s_&ZIw});BmU(4Zlts7XP}wY8Tga{dINo zSA&cFkPA6yQx(ruKe%d}_qR>aG=zYc&vIz|d9i9Gmc?O*waxKxq~B>E6M;~7y8VU> z=U1=QC#O#7oSiT!7qxv6)A?=U z{@#ntn)f0_O1EX~K@x6E&H}F+nCZ-={6xF2?6wTBnv;Vv8C?J^whckr)~W1A1;0gS z_^{r#Dxc<0jQ4*2ye8CRrn;>dG`(+MO^LOvXMB}j|6=&WW9BhvZv^>0jkRi1ZHQb6 zbNeBUzs01+c$wHeW_$X?;=IW6nJHe&ublPx{$5SJoecbbuOEHPOSfthwT*f^`)Ib- zEpPA63Y)m}gA>PJRYyL_Q{D#IwFY^!zrluO)GT(xa@}x?;P*CTnUVY-74SvkRE9MZ zeWrx3_~J83r64^+n&yv*=2=}*`Nw=djlSXG4<)X#Oy#LMEt6mmNq6$5UQZC@qa}3~ zGo9#8F-ana2CCaUv_co_Pkzh*_nyQB>>k$k`XIy0zaMpb*@dnyNnszf>Dw zo$Loc02(4^9Oj?sFSJehUNL-W@xzLwoa&o^>P<80#NW@u%ChG0$4L=J_JYiqJ%sme zj%N=>g8Y1Xz>x_Q=}%wb!3)}1MOdjTlJ4;>>^!7S`69*jNhe=?bjO*^MDf*k(bjlu zsknG&nkc(_v?fO%3usJfNL{MK&VnF2$7#8N^n! zz=xLN3LwTO&Cnm8K3_EmP_u2mh+KOKt=5XiSufj4+e%6NxK%q{Qv9n-hpHy1Y<0Uk zVP+-a#z3qsh|myJe|eoQ#RGbjLI3NPGJW!VJ4SYBi1UGS;OLT_dbXlHSQMvmFxpk# zVFBfNeI`&1hZ-l1zP~KoDn0hkypTBzsUHGh3Q%~9Q)iS`JmO=RP~s@nxRMelnX8Vw z2>p!pYEx8i#~zhNFNCDzDQoour#|v8;`)1w3}bz;m$wy-7cz-AMkTRttyP{%k$)n( z=37N2Xc-mzUP|<;9LVtIr`I`_y&{k$c^q&5W68mHi}ZsuUbae&m=wqC@QRM&bTsnfdcOT=<_}!7yfB6o>SV zo+mqoi6|0`e7@+8Gz%tbyvN6F+;@+!|M}>vnT(DE%1upbmf8J=sbX^72C{@Ltq#J{ z;zjv>7bH4e|L2Np<(0n}p9T)LTONq6b0>i?|qqBhENuWzee5d3i zxc>Xll%nVdni3G|AFHN6M0Ip`;4f^?nexKp7UOJ_;bPy^H{Tx~7b*4}m3r!Ome-DE zKVf2$M=pz7Z!$-l1a}+%a1W2jdQ3a8cU)MtN?jhk+wIz(ug^n8?*9m* zuuLZPKE!W!@b(#jPmW4I4*1Bx8l0h4G!IM!gdN)QgNcm zEt_ul`NrHmp2ihUIp>-cK`CB#HM8A!7$d&c_uAgt3bj%_3N6%=nAM`3-xZ-j>+%r~N3T1OB{*a%+3h98IZb0dG34Uv?x;d?D}c&@v`$_&?XiWGTM@>451nswas zI^iZx+bCc4SXig$N+n&$195;meMVr-N8iqk-!`3FIFf%k2v~skSniJq8ublQ^pbQ8 z^qB~dv%ia{<22KcP0650S{xkIT2JcegAz-muq_Eqy`(c{T=WJU2Ct1NgWTypv&u|K zN=ZW9`Q4ki$HL98U#Fk>XIeyu@6xF-pSPVFhP@;LDvIsD{Je~Fccuc&w?R7JZsPi_ z1cAiQO@73okMGonSm?Dc@j3E-oS8Ax1m(K&2yX0M<`Rc1TZRnkHO=m=S_d9wNT&M= zDO*T*I0P4~w093Id0h~$<;I;e?W@^NVRWMLgC^rC8W` z?yGZ>w%ekgb(sm%=VU{fM&P$B^suly0QX%o2;LJr-l4rvg3fyw7yU`YFSD|*1|x4n z4Qub;c2l#5r`q0A5w0cI9r*p~+5{{L0aRfaTQG(V42l-auPtP?0LcOdSdlv(^RboC zLL?;;qynL#M10AkeHK}2#Z`jsmq#WVCGJJ*8)3U3Dv2Wy(QAb{1JZ*M=OB3Y@%vz; z(J|emRC;l~5an*h&oM?wh7}AfWilPcXSf{7N^me0Y&%b{gz-b{R=&fA=?1qieLX;= zT$88kM1DyXjKc=SD{eKZAald2veK5sO^v2$?b#QWWEP05X?gC25kk5ft=;xwfy@YB zA>thwf|N|UAPGuF{asDzX&^fu8Oui`c@fwdHjTVf#9WsMfBLT5loDH2RY>RlGlCz? zR542hnfZ_M#S1^^;^<_T$d5f7<95M6A=6TGE$AN}*Z_R_r0KGg1k?=kUJo&CRBezQ zOI}i8N9Efl3#HPA-)H2cssdNhRWXdFO2yE1CR-O*8hw(UVBAh5>hP|xaTEHA|Ah1j z`4cRe)X*rlC10-A1;`9ad{bfQPk2kkk66sC6ja=)xV?#d#vw zS=?E6xUyMiSsNi4J!*C&-m=E`=af7+b6MuOnsxK;8r{X@-lu%TJI3vM*GxCCINngy z;MlLzkuaXWI`8bp84w%4y4^BYs!GTxFTQWh!38q#xqTkYu-Rn0?{n3%0mt&yXV z^5ZcT4^uwFFviB_vGAq!rQfB@L;i=_51Y0)$Hm8aKa`KJl?0U>jBAWdkE@N%y%!k^ z9UC31c^~k8eiY`M zj{1}OsCtp$&S2f(nBbk@vtztVz8m%%rpv}leQarL7Hll+QtTR{gIK;c$~N=Zo7nP{ z=FKSce2S}6%TiQzu^XvWJnH~UnR5&kHC0{(A&xyE!x9j5M8TQ!$X?w;8m zvreI1u{IP=`c9*^M;}XH@=t#4Je@W>G@Gycxb8m~>fz|oRrAX>U^K^t*`~R*Y4SEI zsxYcMzu$UfxJ78Va^e6xsT1GU-61nU|xb>3#%=5IX+YgB3)J@f+115%$vU15-flH}aW=!$;#-toh_;-I6&gavCk zarva1EHV>WR$6DraqE?Pc9ld8wja5L`%ANKx>*(6TGA>sc4vw$#S3 z(;{&+o}%5a%XYbN`SsE}noGW!IxpE)`FzR6OV(x8pd&9PuU5Wq*MD3-9QyF~W3b_NxqyjWQ>SNt#FywV&aUCy)dGBiA$0-{=Gspm z4ZkX=JPF%2O`=Lv?T`?!bu0N5Kg~TQS?j!8{6ULZGmwKuvs9B;W8QPkqv`y4zu|Ms z(CPGQ`-;ANx4}n!j}|Akf6de|aT^K@3Nrt?^SRLn$M#-N>I<0{zZ-O{4RvdhDl|W9 zn$Vg3G`H9J@?|U3PvZohVyN|`)M2N1dh%P)xANZyQ=^aGJv1~q^C-94>kn?e9KbG& zO$^NnmAbkkt|oCB-hWwNr(XEJdb2HTF>Es;9wW3rwZEVL6~BF{*T*8iL#ywtz4L+L zqqCiEA3fjIyto67V~@KN{`!39q)jpINm@l}b*SUHxjW96!Xk<-vpK2p=OY|DsoQtt z=>w~4y57E9e$u;l#yl$b$j59o@F(8)gaV4(2Wz=+2BJnK;I{dO;cU zUAi2Jkfp?{=c9&=>>rub(b%>rQ&k!E0F>d%j(4Ji07hukrC1oH6#H39VVl z8EH(sdUeJSCVEq8z6Ft!-;$Hww`z@&v7M>pQm{uIhc#@lTv&HR52zfm{kqr18OkN7 zU~wLJtv-?Q@?D1lFUstyZ{=q|bKtcwluAbI*6jFtwEVSYN%ItHWY>Prck1}YkgnXp z;zfgQxX%Lrwma%uRse-a-CpG$uR>PX#s%&1!S1_c?c6Yov*@F%l9RJgbOnsj8C~fz z(z-8g>*+3FZRcrYFA(VNg|37FfUH8Gm$j{{y)VSZ-qG1Zj&<)_Gb_Z|PL9=3Obe>z zrEKry3=j6U*AISRU>odeD`m&3AdfE_D2)bixA(P%1iHI<_(%uJvHpcCjlTYKTaXp< zmx-^d9IKI*4n*10+a4k+ASwXmR}FOb7iN{mhsb){IY{fNsQnWH{Yj41$=BCQT2L?` zAV45MM8MPAQBX)qN=gtaEGR6@kGA0V3G(o@4&?XnVfzE|4-6H1A6su{FJEU*56B-( zYa35LUpZD*bUWl<<8$}Y()u^NhtEG*K=UCOXze8^Bmfn3cNhFm4BKH2lRjR z@G(Fa3g=1$cXzyn4jyCl{_bT*XUEWjI z()qW?9||0u-M#+uLX-VpmcGso|3lV)jqOj%U*Y`cK+x|0#{Dnrf5rZnG1^K?OIpR# z*6+{o)K%nI|MV|y=V|L~C;itYRLB+z6|=GB7n8EF=NGkwit$@pL+$yYVv-_aP$5wX zaY^z2fKvDH@wN7_wf_T!1{ZKf<46dJ*o)gri1XXp+eq+>iiz6sOG*ei@Y~x;*xQQ< zi-}8#N&E+dwzo4{mDXzpb>};jgJiXnm(dl${w|2A_^zv~0tKkpf(uz9ja;(Av(Equk<7VyafOe2$ z)v~sQXzTw^i-EJdy}qyYA3BA^r9{M~M4=*3Az^grLjPm*$llurEyX{eLQnybe*pi< zi!?eKG_%%!go+0E%L5&Zw6eFowXdhQfv2aN9P6JkLH>CDrEiGrKe7UM_CZ?&{gM1X zMXzuF_#dDCF$dh7|7wCj{?e_qwe3GT@v-)|xBIIjwBJ9PY@Mt<9PQB){GW>Y*L~;z zL$++irG$j+Y#sQ8t)B#}Wb5DPKAsM~0oLC3ijL?= z(b1q4^j9 z{}r;V;Qxyh*}n|_VHK z`kSu*6$Ae(k5Wom5$WL%Le3Y~Ak&_1T7M)h)|%!$@&Hh{uxkDK`8fJtGmRe=>)$7e-* zs7L{SffAt$yt@d=DsEESkYUPgD(xwMV}ecR=v-_~-@*$Bf|8FeWB^`jSy51l_+kdT zSNU=;35KNlQkr*mp5XkWd-5S)Dz&V$aQ0(YnBS2$6hobUThV2Di-IJr4r@lL6~GIO z2!aqv1_mz6?lyYd)s$KsXluzH!S+w zO;DN7wF%8#hU(Ebjo-fFXBKbIQ0Baz9$k+)i+p`&Y~4en5=-&Nu$FTD`yLGwDO1WQ z9&!}Vq-&UaDr+S^#L+`ZG~FN1CO>=ah8xif$YXkCQp3uZv8$N@knn&F5)>i6l3i@@{=SY27Zws*a=aOs;-je0-w(4Pkp1cfVf>yG9i?rL%~ zURc2Otbc$ZFin67d(jCCzVIwnb5ll+`0BqYwQus!tOn=k0cIbqKqih>)Dgyj&@h(z zXf}>lw#==%3CQH-}hSg?dea1-sTo5DbfFG~|6HGlVmkLLX_4zbq4g;?3~ zXAmk87kl-fnDKaxQvv)|mU@jiGD<0;$0s(9g5Sd=D(}D-{1Cb0qF4(;h;Nk9c=!>m zdY2rx*8 zj21T3tCL0TF4z|Oa`EKJ15jYgIz>b3^rvSMASmG6oaAsD1W16)3Yx>8$rwund}^2P z$zAlgCDuDWjFnhrpP4?N{|2f%lex`WpFIC^%$NlEl60|mD$kfdm4l$X$Mkl5sBZ9y z$e`<2jowYBWiScoS}Dm2-*)a59_%Q+>G|50Rck7WEK0NO8r7H{8fE8ya~yhcX}p*|t6X+e-@pR|1FIT+?+If&r$B?(({td11&Y{Sf}^lYHi3ND@chPgli7(zI{H9iSheY4zE zF6nUVnvTN4f-#tVvU*z!bzL5FPDk-6@cNO=7~{T|yXEYspTY5ZO&#%wR=}#S_az`Y z*FUawba?mX`gQ92d2OHOv6AZI-8Hy3{{y;!K1~U&7gg!w-Gl1ysHnvCnF>3KRSg}_ zPa2CTYc{F*q{~_b&6>ekZke%c<*lBar3>4I%|W1+uH&Qfeb*lHa^)P(D{{{~smp`r z;ZI|X$Bdl~ezv~G@?FH~jTMK7l94a*LVdY39{&n~Z}}!pv<&*<N8Ro>^3WRXx-O2da!72o-6%r$$!XpO<&+<7fDc^mU3Y8)v#lDj$vM zDLAaP!M)ftk=$VoNI0=rENzDq^w5j?s#@Ec0KzIqqyg~+qGt;01CL8o=5+%?W_R8> z?J5)jvWr^jRq`hoHNRuY3>+;S8myw1hMIAbAYFqOJP1$HBEos7I8qE$LSl*HRu50& zL4^Fch&7P#j_TW&~0KV!C zk9p*;YWz7d6T&Xnyw{2hDyfH@=g+`iFINOtJ^NXmO-slqkH!Fy)Zj6js2U9Tf-M-e1a%Q%Sud4c|&-pbQ3j zDCT2|4juAN&b6L#eyF;F#N?4!#|yOjmGyGVZG3A@v5K5%aR*Q~Gmdp+Ti6ZKs1{QQ z7JD1AWS<|fJZO_Qi(GQeymCToUzKs zW?=@-xndURL}5l=mzhZ8ibIWu)CCNlgGOYGuCh_b`a>HTKxjdH0GS=ShZmu7{{C@y%SpvP$H z?xhIwZX?b$EWCjBlW~Vdn-S*pnGil6{(RAMNe!C-Hj6x+*U1*Zxx_N~ED+TRX%f54vW3le(+P&N#ub(>fVPNN%qnzj}_0$ zDFhA=oqV6x>RpMf!HYdfMSCLd=GB`MA_OXeGG*LXME7&7LKZdrl>=o9GvwmtwZTlu z&k*US_u>?0K>K1^s28+HnZE}lhUkZM-OG`uU)p@A6dN`uwQedBRMh7_mg066l#2*J zQ>&W|K7IlB&~t|4XjL{pUTGS%&VdVTi3y~g=gTmL;#>}-X`HWY*BHdwLu2!Bb7 z-Q^xK-zDmp>l&klt?1^J?3k!=)QYy=<3S}oQOm=~&I>@a@ho+1@(lTdpMSQxp^4?L zctvt`Gcg`vx%cECESNBrRWi#?Vk3yWi!}L}>06F{M(6PSiO8rPdiHP{cJeC;_=i2^ zyUuTd!}@nnm6xSlafWS;SNX*(Pucqua3Sf5wp8SUk5|G=ytCymF|97daz@Rpgak|;Te9WF5qy; zaJo)jLV6z-*)5H+Bm&2~gtzlnAKq!|4nKoA+_$5p=zb00UR-S+g+HiL*d54?Svg|{ zpOXaK)bsU-j#ZP|Ax|w2!$wD69wA$fNfWz@t9c)1rO8Wtl(hvXjZ;V15g`?@)K6}R zMtYe8)(4dnC7YsF%)=3cyIB&440mma;x)Z+S~vS#h~B6KmLgp7+?2ryD53?Hk+6r` zm>=9hzRJ>U?bwbcYSF3!{F1a93lAnZ>c^xS zXj=KT(sF6=EpUzD9R9^bvDNN*_dH$#at@(#t+3Qw7#sLWu~X!xFB^peEI<0`C~2i> zMbH8s3A@uu_IoEpHg?HTqy8r-jTNG-IdW?!R*#$U5YpV$(nB+IQO*i~p|W#LfxNz* zMEcHa8rkRO2M?+1No#t?oE`tf)r9e4myZO)fpdCi)VN|-c{$eLy}!^i*a1 zbUT7nA>&#B2(-Vsilwi>%tTY}6$NG=e}OO{qW)I?#b#g9F7@qc>536s#c&oF(m^2s z0X3(+e>O{|kP$a#AFfq}UiuiyPxcsGu;}J8_|3MF$rIe)t1xC)5O?6|ahPAZ7&L*% z&Bj7xd~>RG;@$UV@6vp6$oH>6BmIst&H0&pplreYk)MlCc(B`$onLLPN9nW1&y+zf`(k9n6Uc%3wT5sk^5PmQB;yTtO4X=Uyz*K!h;c)JZm2Y_)IkqH| z*viWYbNKifj2`8q4+~n|gbGXxbpDFsEL8ew5LNkkj6mz*Fy-ua%uXJ#I;Qz2>*u8= zZ2K&w%YMMmvlDD8LPAawm{l+fk-RO^%$f2+@o?LYN-ny!FHE`73QO<#2lEJZczd|y zilguJNiM`V#(0BN|0n6T9&lV+W?Bslu*y!B-&_mUZinM6NPOHE8O!IWKvbjNoOv-6 ziRTe59%0r)h#pI4**$->Z+3$R^U~O}gfxzEk;y;8BfdBx{F=+MNfwoVM`-Ygkwlw# z^dQAh`eMdPvi1v`N=Ebj-Z&6p*LShmxPK&_88y_ z%}?ib_E__}lEd4cTnX)+RM;@5FujSw#PU>%Oc3(`VMfV4@LF$yDr)|<}v+f9jyECgoYi9&t_~e_igA!qMi(D03 zGia%9Hlm{Y_Ny*JW7XQI_jW|h=j2~qTCWoyKUL;Mvebwm#m`Sns1Wj(H^J>AI?=D6 zDne^e;%E^$Tr$uPQo*mJovIng34cdmUMD_pt9h!t-u)h{+n|7+5_q}s1aoBNNHsoa z*#;Y}GJ4^m(c&|DdYabi#r1}DQ|GO?lQ#D5w}SB70Qgna>MOoCd=wY@$in9X${O^8 z`m^8r@~JH1nOV3_r%1JxZP~KKW!s)u@FT*ab2Vm^AM`86&JXBddS8WVuKoZYi>8a{^?mzsfRd4;tBiH7wHC(;R1{h>VL7;)^x;$= z>zYh_xN9in0O5`)hoLZnwLwq`_DvtcmHLWn6yDX0J(<_3!;l5rZ+>Q1CZgjDzNvL0h~9DZw~2_QXw%(pVNqR%alZ>)uS{7O0vLZvG{I%q{|*PU>0 zr8^1NFfZxNHyCj$B>00r9X|ph7!=bQU*T;jN3vrGUqOB8KNg|&Hv&oP?~6$Z!(8)f z;vdc`4P>+9aPiP`cz$^D1;B>H1+{PLO)oH9gY(Owp!7fq@xj zGx~gaq6DxIlmPt22#1Q{btyV)$!jT8*R+I-Wi6WCt8{jG52iNTToRfw_Ay49Y9}}d zNqEM=8|)W@O2j9OYJtOpFV- zN)>DU6$2qrqL%l%MY0j1cThh*wf{LEU~&|mBRH3^|DoCV9`I>t(29E&f9Ue%)qt7_ zU+mh`47L@g-*+Nv!FlFzlS5O&Ur2-4RHZPm6l%r_`YhGndjll(T=4*rcX+Hpn3D+s zM)nkNr54v^i=^Mvehw*IMhd)p>3yGHi1yVOez&49xV-zly7wpAU`Tc(Gnke=;EV+r zah*wlOa`VGt7X~9#Zn6QwH05{6JB*t*B#?Zi`GaUQ|^kaxUDoGxCvlaY1)UngR=8P zL6>|a7Y;A_&Ln7mkP{fOmfuOY?a+(lU)jKArwzm;i&4}C=W$(q*ktxAIy1ZfR+yIX zpt9WvafgyV^WhQWT#mAewGJbwmbC3`aQJ?ZX1M*`?}kEXqkKP5d3U9hw?J~uy6wKW z3Q&ZEJ!k*0e^2`)^z^p>`N27BRO<||(p~DfGVRi>eJn{0vozAxDZsrjJ!fr0R`nHx z;4vNDP_`6^p#^476X6aQ8q!xuBb$tnZh=JDsJ^p9?c zViT>UI0sE2?pJXvpzFd8@qHWckot)v(Mwr9zT6FEmuHSFK|dxdbS<%!-{v_wHxJ5o;MNUfGJD{_PT5Yib4fVfJk70M@9R}Jf@mY zO_hTPTlu1bJR`t_E->-UgJ=Q-vYCPWoFRgc{=O=13%afvwFs+J8<0y|FlVrXbwj0Y zzPZ8jS*wp;r$G@&rGqU6=2mNFK`nqV7DJh3FR^YmxWq|NN-jB4>P`%J2t>yh_$Cvk ziF+~0G9JDMVN2#ecZ4z$daumLcta^z!Fb*mwEpTioc;%NsXl(H4H3rhpe07cLxyu_ zC?jd)W~&dgEcOz3eecPpARj_eMfHyDV>SkywC`1|Rn1J^_S)2bP6xm7;*H z>>l4z-`4xv0uyGAV2Pf*b+o-jtJTY^8$C1Q%yYQ#ZTMq&pjK49D9~{?nunhBD{_*xe^zl|UPD)x@!@s4qAzoG4ntFBq$O%dp)k;$G z+4tT@m=Bw8@Gdb!3Gt&-Zs(#`7+?pkvtLJw5i`qRFrMSAv-lt(fc_Lo}6F zo<}i;EVJQKA3;!B7tGckMb{N4>_z+5(v|B)Om@~O1{T0HWYHclmbvJ%Dv=_ainRkW zd&Wy~K^qSV7D$VTB`5hOD3llT=2WhHW;W>C5?Y#g<)K>XyWdTWn?XcJLz` zBfvZcj7$z(9H$j@gVKJ$nJqD76W=oDN7yCP!o~>DD%vsuJ_^|YGMITJ!&tD(@2(CY zT0${*(5FtX@c>*HNkJNFLa3b+KxcxWg`S;=wsVvkE~KL!xuT7pBjQEtl6RY((9m(U zvg0;+#8e1>N(CMxOUYjQKh%{0b}Xuj*0Ne&qkDWE;O>q<>!?;8b5MNSC^Z$L$QBfh z2b&98Y%q6>%I73|@$N40Yx+)B;wCr?3+>&6MY{Tdt9Fx3{jf$A7dB^xmS&k5A|x89 z{s2^uu*Z`lv;=%Wgt60ppe6L{+u752#V{B#!_?YmajU5Ouce3(Ol`TL-9PYMNs~UPfaAjkMqjJRZSnTC`+PLWoB|82REw zcoLeC6lYK%%y)m#3MvXsHy{xFPPFgDQ>{{qkN%6k;P~FrGsPQrJO3_9Sk<&jV5 zyAJuX9w`g&JC6_XFe4g`f|RS!%S3Dia0q-QA5d7rBuc*i!zp01e=N5?OAC{F)`89@ zjmb}jk7ZoymMzK+4f!2tm(F;Q8))jjl5|`FW*(Lv^qKG<)dZiVwdwQI{@{chwS5-C z%wrnPh$6&R_TxuL%8sAjp0H!(VI4Tce|Zi3mCYhj&lYWB7$+Vb+e~7hNP8a`O1q^D z6gY%9fIdB60{i~OcLPw(cAGqyj1POmQ@|824{Jw{$PifCH{eH{V6M>SmqcOu20D$A z+5CxGC&yoH-++YFSRFP%o+f?xk;*$W>(6BS1s)=j+ASAbxjH6n?Z8B+wkv2=%Phs9 zm)nz}t$k7B2OuZEXB~X#Z2I#fa%dS&%D@$L%Q1!b3I2$KAdjn4tx>%uTnJ>v5{os2 zoks#I|ARXU9?~rP+VKTg1NgcsfclH29r#ryz9_8=-uE7VZ_&Hh73u?n96%pT7-S6| z0;}`;Dr-|bxSF7LL(jwuIrxd?i?-oRT#ykM`Wk=^hO01fPyL|TP0EL2M=dUdqAa{c z`?MzSws3p_sGW)JSU&+NSd3sOtONZN9gjOa)QR3B=_VW^lxogu$$GI$^N7@(^cfi1 z{g>3Smkzy?7$>*p-M;I_sKV8(*p_IR#Q)?<4-Zd3BRRmzBUOU6h4RI<`Fp0Pe&2OMzJ)fqWq<3IRMXPT!ic0PkHAdK)(X9 zPH3$4CM;?(56QZonjV$Sopal(Z1ZGmx*Yu~LD_690t$=A^~I3vA9v49YW0F}ub2xR zULmb+g%Rq%_13HOfzAf^-D@ApNU3N?jC#-;EU#G3gjTB(+4}CfDl&%huc!wZy}T=d zx&$z-SbOw=TSHV|Y=uBth?RDcz5gFi7K|%%vzPjgKOPzlo;8rXzf=#3kkb>diLn z09c@|`&f2u!UL`#PM~kH-?t)KWNUi@db7UuWw6JpBx^5R0DJIj&rjn{Ane600SyAe z09Dqs!e3;H+-2CVL9aNmpRZ7Oz|G2(R5Z?P)yi*E@AH6R6#A`J2J;0VyKI7Y5%Y>j z@=TuCsudQMZ?Z}(C$*L}Oj^R#sMDL= zNKupu*01G5@x#D!s@>an=SpGlq5S8bzgdgYy_gn>RI#I2?gWEEa2Lp>s3$_!Wx%BN zb@xmFdcqLZTpPNMs0ci`#y+`WZXF0)*|-lC7H1fz6!=Xm^}*As4BgjWwLtd*f(p*F zNB~z6qELP>pyd5*g2f5?n?&7vleLbD`eyP+GtJ8HM44+_L23ZH=B}g{k!G7kxq~do z)2$~`hF?9RwS)e4Khsvk4$EPEmkN>nS}bs73;3)EoN@t?s=!$twQ^HAmfj~X%oclq z#>x+b-4+&tB3KzA^Zv*Y`HrNPdigpMy%FJv(cZMQ(`nF>#FHxgeHXzC5F7KgSWRW@ zg~v12YdoOxc^~TS{faR(g7ta$6DkCw`WI+sBQRqhk zDDBeD?W;2dWnw0-ci;Jr*N<=BLDx!Ss8GK$RRp;5Y^v;f$OcL^ZJ7!sHm=SWPMG%SqHUml4(J`b(b{^esDTH20{W~CtG|&vIW4ch_xk#O4 z^9!^wXvnCA3}9}Q*b6v&D2w0m5SurLZD$bNO}VWrPW!6wcoJNn0eC2FJ0?oZZMLLI z%-m?6-ki~6=xnq^%tPMC6|lwOGTDaE=?yYIpMcW7!h0o-LNX@MS2e2*IBUqeSZTlH z2YQ6cgs8n}$lw2txzBTuc%2glmpi&wjX8llo%DZg-~tsL-YusJH|$B#jmEhR!Q^NT z0Pb7$nf}@?*ZzI9GYG0BTDS)n75-7g4Bn?h3~q{78^5f`@9CLW69`fCAXL2`6R$hP zsFSG4t*MxAXgI}9h7~-$caBq}WEN2sGJ19)8L>}o8EaFQ2U)Ob%g8roHpU=jk*r&O_A73z1y26;3Xf(YI8#dT zP4V*am7xUyw!fs(y9h{N^}_AY<{uVX;ze@Csv1Qg^Mi&um{50%mIPh;&N%Iq&Lk1J z5HVEJY9QMx8A3=fsk%HnGJpk(NV=tBYL6*z@CYvk5`{i-ATd4vA&e1=-L4QyBA=d$ zb&V(G#l6I3$mk&lg4S7%HqTHa+A&6M*%6))+a&aw*+Po@8T=Y)wgwL2JDSHoAhmp&8SL^?PJtnQPdQJPHaKrb(+IrmG%<{ZgLiT$f^ox($X9Mb|}2M6*(4tZOn> z7L}C~J3C8UaPs5q=%U^>aclnbI;~NK4_9A*fFV?obVWBcA)~XtSiwrSQ<%-`_>q;J zdiLD<@r1F#^EWaq9rBZ)kRMM%A<@@CRF@evkh&jx&P07`wP5!K z2n`UTI`1@P22zg7x@~KC*SsVrk65A;7Q1om$=>UE8>7+C_yZUHNRMGuzmDHJL4?C9 zHx=)gM-(Fcy9M(KkvnMDoAhR)bn@o*CzrN!%RAh2n=iNw$Bx4>7nr0Tk~L#ekw>@} z-2?!)Sk4p^pI~=a(kAs|eU8IJuoXCWJcP$supzBYg6g*=`k`MI*3rjWSo>l)#`J_27};)az1wODnbqzJjQFk*^NUup4i1_ESEN)6`r!7FL&tUp z%G*5UIW7bbEr@^Zi`Ul4Ved253C9R|7LIscrNbT;yzXbT(kPCdzC?Nw2X; z^<#z>7$h&i!RZ5&!vC-_eoqn)77=(5?bx`113o)fvq4bgAuD6Y%ffQqiorV|#x zHm4Kg4um@veo!g566rh$n_~$j;KzT>W>xZaF#OdDioDgVxdOwhdPJMop_zrNm^fH% zt(AFPYz9bW{80!}Bkg#S5=6H<$3xznB0nlbnaf^8bIr#e<0sEaM^PaSYE#_3mP0zB_%E+( zY<^$w+U0(m!Q30#D^H{{ZETZ%tLGJopXE1$08ujE{gBzB3xvm38y24j$i|H0(Gp`z z4{zxMsE+<^cye}ll>DtS5RS9Y4A0OWH=AFg>msl1PGWGtbIOJ>gcm{4hB>qktyW z>2POdHKMDTlMLj-@wyo^8|@0+!^N6k11K=;D|NS}3>dyYz%fb~E zyZBYx8~)u7gZv(S!CN{J<8-0)kz+rK!|{9QMV?T&&6dn(qK-v8*@t#vVM}Sd~6(=NYuSQO~Za8fy8e< z2;S6GpXiU{Y;DEst{yXn$67rP3w!gPH;>nAE`eYsO2sKWCa1>33ENj`&&HfGYyy$U!b1b9_#T3~l!@1p`eY|ztcK>Ph zyZ3XsiYCH?5N?9hNNf)W$jbLQ?P77JrQ4{``W;BgS-9*2vWmcCA^~E zUk@NLsN0Zg)hQg%^j>oT!awjf1ZS`RnJRw{(!>#!B`8$-Ey%={`;e(E_h|CXFzB;P z3&c>Hu3;~Qe2Aic4N}l^1yV_xEaAS_DE|Zre;ZOz_L%oU-Ort96amn#tzJl4=OTkU zYVHk}__t>H!Ov3VpHT?zLxwoa0N65%pyodRAxO>s6%zj(cl^T$O2p&BM=-$SHYCs| z2X*R+Jq_^rdkuIq=*X}e6o6Ya>xUsJp4Jln?rp{ww8HCGnt`t%<;jb@8L}n@RUv&l zw7OGbIH&0TT}4na+JWd%T`uW2A?e_0NG0EGt~;pkL7gT5 z4&*7tUFVpZMAmr~Pp+`t=cOBvXbvvsQ;X`4g zkU@~v`-MCHZxrOYX12q=X%h&FN(Krng6n*Kcpnx)p((0nf5x#A)I6Pb)(m&3Io`-CsO5SC3E-u8 z9rb*ML6UvgSp$wAujaqH1N(8k+#P?GyZqgP9e!QV?TULwpZ< z$m&jFs~O+s1njyBeDoe(i{1jCeK!}N9i^3H$1(R;=uSQ3ZvPY#)t_>9e5=VY3t(R{ zf?>$yn9Goi!6=16Z~PwK+$|M+C!3B4EEN%eAgi!b0Ja^gVS5)Vz!iX2=Xt7t$i)Pp z&mn(n1+T$21#ZO=!bwQB!8QH8UCjR$#|>yLcx>ZiB?WMc0yv4F=;|4Q9KM!gtD9sn z3!owvfgT-oq(A^p(BIYR!(G^a3aeS+rc&3q<1dlqZ%BR>fJw**ZY!`d0XQ%bNYW?n zaZKQ0NU%_gSVpmPL|MWvTr6m|WPxNKr&<3J65N|Eh=Z)#5LXj`Y72rMU~-hMfeOG$ zNE%H04b&>=L=X^bW^B@2&p5liTc`ip5r9{a66+O9;h6x`q6la--wl%WK+<7a^m3e5 z!3jqgQcZfrat_hoaJ@xP9Hq@vJA<6oP-Oy8CxW1Xv~IcwM;-DXhXf6^D5fw~=BD53 zF<6_21noAQl`U9W{4|B2GV%>~`Dv0qZ*vt)02;wE&@$LgXKDU?0713f2%qbi@^>$C|#!wZ835;cIRYY37}Pyx`&!xVx% zy<4!O%T3j}DLhTcVvdX*nhM zG9UH179`~y^WS|PT^kn6GXXF}i-ewVGw8Snhxj!ehgDd6;5ON$3ao@y>@3>Z#?CTf v-Qkt+yG7_15Hu>yVrSoiHrv?FUgP-x*v))Ip}s$O00000NkvXXu0mjfr)*@w diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-bananas.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-bananas.png index 88bc33b592a04625c97fafdea8040090e64d5cfc..2c94ea78bfdba901614472a3215401d7b0a2dd71 100755 GIT binary patch literal 4949 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 Hu0mjfi%6-N literal 26684 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 Hu0mjfQGBVH literal 26684 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 diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-grapes-overlay.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-grapes-overlay.png index 2529b5cd1928637c69a512b292d5533fee42a48c..17f3be9c262953cac52da794d601bb853d8dc276 100755 GIT binary patch literal 4677 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 Hu0mjf9r&r4 literal 26684 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 P00000NkvXXu0mjfY&D(f literal 26684 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 P00000NkvXXu0mjfY&D(f literal 26685 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 B;9~#) diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-orange.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-orange.png index 998f5fb3cb6f04dd8541efdc151bde4014aa69db..3dc60464cf31e3ba07d5585542d75d616f63da03 100755 GIT binary patch literal 4633 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 P00000NkvXXu0mjfY&D(f literal 26685 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 P00000NkvXXu0mjfY&D(f literal 26685 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 diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-pear.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-pear.png index c8acaaa58349b47893c0c3ab3bbd4a2d472c8721..3dc60464cf31e3ba07d5585542d75d616f63da03 100755 GIT binary patch literal 4633 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 P00000NkvXXu0mjfY&D(f literal 26686 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 B?Uw)m diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircle@2x.png index 207ce15dfabab74df8b5c13932506d94ef19dcfe..919d8f405ca7f40c921e71b91e7a81655db1f94b 100755 GIT binary patch delta 42344 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 delta 95844 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�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 K?H<|qr~Dr=#f2~c diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay-0@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay-0@2x.png index ccd1494cdf1a9d19a9c860b88d240392ef1919ea..8e50cd033596fb3daacd173247d6779168cc8f99 100755 GIT binary patch literal 26595 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

KVsm2B>cb8^}lrej~MtL3IA_&{r^T6&VRN% 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

wSIutbvWi6D`m2u|!^@bLCI5us_1F3$vl-qM^r3 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≪}_ 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 E0Oy=DQvd(} diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-roll-end.png b/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-roll-end.png deleted file mode 100644 index 60e2d3328f89df794d7b894fd2f6e2dfbb44ba82..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2095 zcmV+~2+;S5P)RCwC#opET>M-<0j)>w_GlupaE zQdA~lAy}x?Kb);qief>Tio%>y85B1MMFn+;e-wn4iCWkYhE~K;Tv@diJE>EKHPvca zHDYSh5db zjM@LdW5GVBo-aEjj#cOe{8^>_9`zyW-9figw}yN!BrqBam}foI8>u%0|NK)VUQ17MwEp_u)0SWA_zYSk0`vKNWN>hh+`oUH)YsRO?c29Ulj?)i_fk)eOCKgtn&0myLqkIW z_}bc9vSrJbXh!!!VSvLU|LoZ_a_7z+Qc+PsX3m@$3HVRcrwIkD813_csCRU9kmJXX z6SLW@7y57N1;PP`8XfgXXJ;olapDA-KYzYn@G|O2LINv>5pQpACr6GPAx5K75444P zy0E}XA9uN2q^YTi6c-okf$pcCCoHgHUayxp91e2w=(y;pc(#hyQZ9_ZIAR;C&Y<=sIbDl{);&l`C3{ zkDby0E2H=M^XIin&>U%i(fa!O$gyL`w02LDr2)qJ8y6kBckkvw?~?`?ZDeGGSglsF zeED)-YJV&Zuu_;{3Cf9by;Q(Te?D~R5D)rOsep0!1ecZh`T4v7EQtjy+zEiBq$J45 z$bgcP64qC+A8(ZE$iu{m6Co!jhX=h+x-%YAr%r{98<~O3Wc6iXfya#-2ejmb($dn1 zqAc_&p2?FZLt!EF@f0o}mq`VT1K6B7b724e{j9HSmI@ec%9JU*DC54iNh)BB^4z&| zp}4qMt$9-T;3dULJdE~SF&`#6bcqDT&UKB`_>;zdC7y* zkL3i$s*7P6QUaSyCT49SB`_`~ScAB<7q!f0^VkArEEVMio<4oL>eM7HFz$k=jc3vV zvj#3{f$?h6m;!dY-D)2XOAFlF+pG5Rh_t}n-Q8**JGnR0L|-+jSf!K^W(`;vVJU$< z9uKRjM@nF))5&VGO9_lOVaH5_aSX=0&uVip^?OFyfW2NXoI7_;t*ISPF-jLd@8I^`A_qm>)_PVY}U~28-4p z-SQCCC>k3ZSe(1O}uxL-S@US3|hq84=j z@BTwh=%PZ%D*)krm$6Vl`;B^w+Hy)rL&XTZm&l3kvhZc2&=G$6^eLP=b&A#85{xiN z5n+_LI(_;yFS>|!> zv6w*7k%I>h>PcaodPYsI^}Pb4a1iQO*45SV#DhW|pK6cln*`nqup-Nf>)2hpcIlN| z{u$oEn*-h{umXy5Q03+2#9%P!0rkW(?ZgFGkxs|ax2&v;m`o-9RCLv5(>+ z-D0tjJ$v>5_HeJ99AZ~o4Byv$vQ7rFn!Knt-MUo*7rT`o9)C-<(qUi|^hXXo0 zJAneiwQJW#<2@~C_)@BJ>OTh0Tg2Vo#hZAr{;iKk>T0I+D1+j5Dax2lg9Nj;DIMeQ z!6UhNhOsetc;sH9Z|X-1jIR)C4S{S-lztfvF#f|spJV(5Z{CR44sVX$;?GXe55fE+ ZzyR4Xd;=*Q?iv69002ovPDHLkV1gL3>M{TT diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-roll-middle.png b/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-roll-middle.png deleted file mode 100644 index 03ebd1904875ab54fa71cbe948304fbb6266d69a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 140 zcmeAS@N?(olHy`uVBq!ia0vp^j6ht&!3HE(th_Z1NJ*BsMwA5Sr)zI%U~#c*aSiV>bgcqrenRo!(7sQ*l5b=5lS zHz>XqA5MduI3AJ3)o!d^br=$OWl&|sX@~VZ$$kjMf^*+5^|JIp^;7XGD@8l4m+Fq5 zbR@7C?o*Zte`1aIhsJvoa%oiq#8D}Ki%3xN-Ksnged2uiUDl?~c^n4)<4I1OL@J{s zVbM5~>zhOgK)+qf;w)+W21PgsudT$_W{dvh%J^~W25rSR8(jc< z_fL>Vf#GBu5+M8?a)ADqw~O-tr|7*i>Is87{CTFawH0p@a7Nez!b2*^x0zygxz-zJ zkY>w@5g3#`3)hxc7oS!>lk?q4A2}BKF4OE+l=$q;=o6RE{<#@Y^c6ReN2_0|6weK| z2uIAnbU>U^b5ZVub3{6?%JM{p_E&w1R}>YSKGQiTyB-Xk=tfFQe;f9 zT7frr1ow~=e}5}V$xMU*Y4I@*Evnes())7`go78n7;RKpIx`kD`E%XMaa-?OQZ-28 zi5A$#5ZvHTSuXzTXl?s@$h+&#_xArktA)i(04d2$ziJv9de^lR6uwRAw+mf3DxL5i zZ%jZ&vDSxmHFDjcMMePT4CJ&RQMrwF1c;wW1GqPo!cSZ^A(b-S3e=k6n)X>l7c;buJdiW4TJFtS@WP;S0XgZL zRZr~^B;3JzAm@^5`N8^|J9S$Eo}Uy8Bag4E`!ifO*#yn~@NB|Sx?P45|HI~V?QC?g z4jFo_Dw>(fI(BuPb+*GvzfZv!*mM-wnSs#~9FzwephKn{u!ofyos z#4hRx06epEi_y1?Qq$3HdB0QwjO&@~ShGCE8=pRb&BawoU$ZzWCoUoOIx7L1t28)d z`yS1UG#((0hLy3j40^L2$5CnL#>$kX73~K-M7th}=wN8AyWKeS(#q%ETnn=#%<90t ze7vB1Kb>x9o>VALdA9*Ak}r2xz8Rw57nC4M3H<_h<7Ki1Fz`+PZbuO%l5Br0a(oSA zg;^8KgM`6aeM%%kx-bcr>ZGgP7Qr&44d-K|2JBpyh%9M!COO<|4q{>(ULo9=0@1|` z{mdIm##t*?42H{S5)ek>iXW($sYURj-j(3}Ao(XED3+A5XEmxS;3@V8-Lioga8`Zm zKGNssuY$f>>S4+Fubmb-0Ub}(`9&iCk5&1fTkn{v_16n9O~0IyKJrn1yh6BoSU@*>AiDNP`V(J;9{Ld+x^0JNiTBTS>lAaos86NG z4tj6a1y0egJwb*di9UsPQkh zVUV*XtU<4yq=oM3s=AeRG6WkrM2iyo(WeWwm?<>=Qy7 zPz!0(bK?q3h@tu4v->WnEttwXV40o7A14u+VB^TJY0coQo|V0F9X(&cElEqk8XNmG iXXL~5fUGV&%!C*_ol6Q};|lnZLeiKIqOXgS)&B#53)X=E diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taiko-drum-outer.png b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taiko-drum-outer.png deleted file mode 100644 index 87ead80a08146adfa553d242f315f764a467506e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3749 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 DZJ4!X diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taiko-roll-end.png b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taiko-roll-end.png deleted file mode 100644 index 778c231f20ca9253e64ccffb64a0aa39a3b97a76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14570 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 PK2D*pzfZY$bjE)HM$sIi diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taikobigcircle.png b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taikobigcircle@2x.png similarity index 100% rename from osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taikobigcircle.png rename to osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taikobigcircle@2x.png diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taikobigcircleoverlay.png b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taikobigcircleoverlay.png deleted file mode 100644 index 6dca33317131cd69ef449c0a6c0122f0f39cd84c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 66280 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 e__0p9W$exLq4HUNEC{AIAxiS~PfR=%CLeS{yi$ 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 Qd;A||`ImC#G8P~IA7%MrKL7v# diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taikohitcircleoverlay.png b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taikohitcircleoverlay.png deleted file mode 100644 index 2626c394dcdccdb7d74da5460ec3f6d6570ad9a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 66281 zcmV(=K-s^EP) zaB^>EX>4U6ba`-PAZ2)IW&i+q+NHf)lI1v(W&6)j)DVa_FdPCPMeYoG_`Md)JTfD) zTBJ&@dxo34**S+Kpt^N$eIT#*|NMX7^Um^B*U%9kiN3A!%^2j4T{H5PNzsC79 z-1+?Z=U4ds^Kak3{`za=YvODA`9XVr-}UGF;cp*vhljtFfBT?67Z3CAKj^Av`qQ~p`l&%4O~xgP(M1;76GgMTb|%)j4H|5&H` z*Vn(kfBpLpLiz6(_N?B&-BEo0x^Vp49lbf-fBW@s3;TV$f9IKbiYHfAvHm{PpC>tg zyLjPbVbXb9<#*%1!r$BZJ^0=4=U!~czSlkZy}}5Qo%liyJ3QeFZ`c=qSz&RHIli&+ zHO3Xw`Cd;gj=0XwWPgV*Hg;^M&i<{lqdCQ&Te-3eInM8OuGYEo4!kr5J}mH-|IzR3 zfBM4z?)UdCmniV?;g7FzUr}r2HrzS?z}Z<75Xy zDRFa?F{f178-R#Q^Okmn^V(SA&-cM2cA})38he9Ju~|76?5E|94Lv25TuP~>m0m`9 zYO1-GTCduw!1lD%ax1O2)_NQ5>8a;ldhMb+pmP7|+8sA3piv%}>7` zzBA)YGtV;XY_rcXpN09XyvnMpt-i*3cHC(L4ZH5P`yTsw!;2}s`7LjK+x7N$yw8WN zed)_z`Rdoc{*CYXYuA2v^*?_9FLo{b?pi!MYVz zAYjnZJ-d6zIl6Q1**!$I61mCZ-t6Fxv4i;yS1e!Poj-f`ubul}@7q=D|8w8s|7Pbd zx9YI`O?klT9s+I;@p zYk9s|+c!i0^YdqBqMqL`-#1fXh2jPX$0rJ{a?LOC(tN*@g?nTExL*Hq;byR|*56&r z2)n&JVXFRgmdsVYFrU5h%g#+8C0zSsfkWVvF-m+k@8`UHwJ$%b0kcmY>1E~b^sscR zy@OEpj2Ua1-0flqpZr~Y6y6j@s}n$~Ev(r?S#7@|y{$|vwAJ)}G3F(B->myLzxY5t zmdhG*#e~?k!q(5K=M=tivvTpo($(K@^S9@#?hq;lW50L~!%rbxCG6p5 zCs?Mvzp~eQ0XaYYVS!TSfoo#f@2~gbEv=8|UFBOW+x6za7DVnIyO?)L2@+77(FTV! zO0H7dd8+;5b-T=%hNi+pr=LA8Z9EsI+ym?I)OBO(Yp<{E2OgJGWyv0l8<$yg@O*dM zd9dEQpEcMm=kLWX8h>6fwUxe6*u|S|j^G`exn{sgF+@C+sQvQ(jR(GWVOth~cSLYv z_pWOJ2G4i3@TI)GMZA)C5tn11=L6mKRr)c)ju+kSW4Y_har1&%)=j)6qGIX2U!chM zwzOHvuj50<7(+_mTjMjzZtLE=K7n_$m@T|d?fV397ySXmuo0Z{8U;t4t?bW_;B~BK zWm_Ib?d3;z?TlKr*7#rR>fMhJSzue(or7gUkPivEhtHRrudp@UYV(N=VCXSxM))x9HYdnh!2K+e&T{Z4L|z19OoZYg2{!}8+~5&W25XIPzIZEcj0=XbDn{;) z!;3u+1PV9B77cd&<9>HutOD>e2?+I@G4Mg{sSQYB`*G5wvVn}`fTQ6N&z$ktQ;#gA zmUAb_A`X2}Ap>vEs_(mpJB3xX=e=63=?l+F6J|B;nhVG{+0gF+v2VF%#l`>%@f8$@ z*mV4Dw%_k_8E!9K6EsPoj9u!xCbrr!{E69@wM!^@@>uUY5ln^ioKLwM?hT~J#xgzx zfq7qH!1%bKFOdR{_P210tK|5I?ki&s?rF#qEs_azy@$Tu z*24?VBy0{Z%S6acNCi7*ESqSZhx`mz1t0kzxA}gh&xaVd>b@}hUK`-@1-A)S!V`vf zgY3i$LMVoU)3!I>bB6E`7~yB&9wEJb=>Y^Yw)kD%_mk&<9c;KeajM_=bs%6ip$ivl zVq58*2dOz8&Oc;UG1^Ie|?AiUnZm?c91Hf1*Jmu{uI()EIZUn^7_k%3p zk+90JKE8tiLWk`$MtVMDYM+bK7Xto#?Q7BMKFBqssd^l257C(0Q|~?HJQMI!w8{$Jy;cF zCSVMpHxJfU;oigZyu<>DR%PYihHLe-z6~nkNEg2GCOi#}c9%VPh%jU0vsuXPrQt^p zFX)g}tPt=Vxxes@9p_?GaIkyKlCF&v?#i07$^{qwGJr;)?l-I^fo&Lm zTMI)VR!vAkBosbTNvshUCA`%-;Og0hD5EX( z$IA607LG>O0^Yfb09D0ZPtIOpa;u==Juoq1fObVGg{6xr1|{7a<$+^-l!hrw2m{44CU> z2*l!p-jP=}+YpFp0SBzRC!Qf<$rC%m%7{ICKv)~A3UOiG(C|F;J0Yf-hmofc-aeog zyM<@Hhpe)!(30n2$1Zl5zz}oUh=XqIGB`dkQ6dgp0FMQ$UK}VADq9)rU4+?pjBm~t zaNvgT@^#mUIQ7BmF?pk%PaIjbgW7Oio)c4fS?aGq6!5KkLh&Br@u=TGVT^Tu@FPR+ z%MJ7@pJi`%;o31G6kbsc+r}n9$SNe%@P=n&J+L~_3(^F$19~sWGT4Bu!3v+B%m&Di zr!r>88!7wy!v zA`pTVu$HbsP>@IloYj=_&^5Ck%iR?hM{tsT@&RVQk@9& zz4OJku%iK#0issNPVWlWMAc((d14C$zT!tM6&b4`dH{V7I0O5_qJR#f2$3%j0(*%! zcn|SMvoy>B=S;u>c7d0ENECG?m_X#&H;S+x0E3?p68o3l3vcas2aJRFceefoFdp;> zY<;j~(5n8S(Fh$=%UF;T6M2D+AP@usA=(vngfi?<)E!%Xkv%7lxNthY4P`C|+9Gd!4^#wVDB+gSnO>kO)Mi}g9zmDL182hPlY&kQxFQVWykH`{vWXLMLnEdu z_@;S7L~#hH8hQ%mvO6e{AV5`3R85{Owja1!M*?2p7>XQ3ejy%*fj7`Js4Q5zfUJYH z@e7R?8=*c`6P+i+o4F#o96ES!oVGzT37Lcz;=55ZLq>ta7qSR?@EEKr@Xa&ve83<# z02-y2_&eC;2w}}JUPMX|V_+(Qje&rmdGpV{VP8aqCR*(%fgJ-|LUgmez!(aCB?3Hg z7QDK+1psqHE~^r+YpL8p{}`4 z$nCukk|T#YbQP-@#gD`SpNqS0H?G%_OjzFz---x;&uDqW#6E}%@&C_XFdWDX08kQf z3L!}(KL0>LVuB;N!Bg|Y5DBkbhWgQP6)y`5K$k(&u6K`{ym7#K=dtt^tN4V2KJL7UDc4b~2_LO`U z9x(44p$_AR$!7p}G(r;uV^@qu<%M#A29*n^fcHa8AY*8@TTC#u)Ft3N(5nX^=DU%D zNE62mM2^GRq0#s&gyFf1tQKy@gW`<@p^cK6GACjO+$=%NpjhcUQN(l^FMyBS_{Ce1 ze<#xS?i!sO9|GM!Uo#~@9ZUk)Wqm+~3H>Np$pY*EERaKVqLNgC8~GW`?d?a(tt!*L?9J=CkAFMXGl3`<)M_1o_vp8srak0wS?q*g8>_kHzT+`Zxhy z2|R+mG@gCBVGUR(G!^cBe?J%tYb&sFo&f#&`x|1wqYkKw(V77F?RF{2A8x(%iRzPH z&218aP0Q=9M6Rq=V3fpg4WIu7SPA-DsNNYliZzrAbA^Sh{4ah<=rxs=yPGVZ0SIu$ z4R`)U?qaw%*9cf_{6CxZ>$n}<4{#(%cQo%>Tg9E!;%|c4mZ#@(xD#q_P9F4Obr<1F z|6W9v#wT3iiSS1v6HELMC*dnVy3ZcPbV4N-Mil)jz|}LZ4UCujc@d;jPl)RU&_Kvt z3A9MYX`TyqfE5PFE0+CCm>ixo!AJoe48wXrazZiCf^lLE$k5sM^IoW!2(MWIZIa>C z)AaPcO(BB(&o?lZNc=&b5-`}{%7(xpl4(dpc8xd@eEZ_H3S}M^-U5&W^)Y%T5C$<= zyfeoXffIvYQM_Yc5Bg|p~u80Tw*|Q2d0F;v5pEm zgJ?lLd1&bLjt}-FCwZ)q5j6q03OPz}Ox9ak#>3VZ#_|NSWiSdP2hW_L!q*rLc}xr= zw1NG7VOS8&8;Q`Zh;e(ju=5=W`x&UdaQpRzp^p0}BiY-93ln)}YvVJ!0lp$GqYDY* zeVIGNa}ZX~e%JdENd-q_>oAQ_@M->;R|^;Ti`F#X#BLELVnVurG0>H_zHooE3vrW& zBg7FvP=|a>g#L$IRc6Q?%;3HDN|l&pLLX_;G4?8wKpxQNTiTD;R7tWnQ48%ZHLBipHt7ZB!x*L{0dLa zR)UOAEDgK|z_?tKv=GQ6NPtW^{kzBh0;whbUUu}Z`c zyPEh862THn$aWJQw`5mJV%;Q~b_7Q)e$;(iF@q}*qIuAm{c$n1@e{Ad z&U{aX%6;q$wx$0e^q-nj30cs1_5l2RB#l25$|$JXgb$YAS1&&&Hh=@h^F@uBZDNr+do zldxbcI*;&eSl@<#vb;WbGP_VMQ3TxhBlz1B0VDwptM|sr5-bQs5E1?}RnEtJc(7$R zc57qWm{?OO;@Y8om@gC(4hg+Dm81DZaDGjuT=f2^L6KXffL7nW)9dv0h^(ocSGZu zhiK8t$YY8i-jBBN{9f6{BCm~)a`h5;wEVOf363l#JOGs7UhuhA#AxIl z?a#G_uApK9EE>%OYaECg9@+L5B^r0WEl0uq5P<_SGHWopKLM78p5?vfDhaUvysyg} z`;Q%KL4aiM@t#j~#jrnMb_PD=8>WgfN*LJTfR5n#IgAmJYS$Hmfuaj95J z_z$0h)vhPchw#h=LKQG2K35gMUkSsRcD|OzD?99j-YZb(`+RT-IW`CYHTtaWQ zZ~!;WL_?$fz-C!At{;&t8^eO~67S6~5>kRWt8KSOFLV6{jth2^i4-q!BAfX(SrAm> zvo0&;4-v0E!SZuv-A6_*Ke(DX(X_&Qk98`P7cUN)1|k~*6~KQOMqfmW*=?slyw!pL zD<|fmJW(6s{C$=i-#@zu&xQ+z0QU~E27r{!n&{v+qJ4AX?P$UY!?5lunm>=x)|kR` zy)kI8m`=W)b-y|Wnq|QsVGzqoqS;@J*ZqG8seRtPu%BpHWVu;842j5xV8Rw)E#HMd z5^*9#ECYCY#$$#N0@5~t545UU8JmRuQ}Uo{Y)v^2R4u36oi%NjPy@3p1m6e!ya1KB zfWcZ2xM|obpeZKSk*%tTEV;~6h#`=?FU)B^b&cyg$spZ?*oAb`*JQ!bLJhivpBn%h z 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�kcUrvy?Ky7PM-eKS~7g% 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 From a9b45c6fdcba03437cca43f9613f85c60f98c0c2 Mon Sep 17 00:00:00 2001 From: Magnus-Cosmos Date: Tue, 19 Sep 2023 01:31:26 -0400 Subject: [PATCH 0589/4092] Fix slider path calculations for edge cases --- osu.Game/Rulesets/Objects/SliderPath.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 05960ec416..cf6d0d212b 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -261,10 +261,14 @@ namespace osu.Game.Rulesets.Objects // The current vertex ends the segment var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1); var segmentType = ControlPoints[start].Type ?? PathType.Linear; - - foreach (Vector2 t in calculateSubPath(segmentVertices, segmentType)) + // No need to calculate path when there is only 1 vertex + if (segmentVertices.Length == 1) + calculatedPath.Add(segmentVertices[0]); + else if (segmentVertices.Length > 1) { - if (calculatedPath.Count == 0 || calculatedPath.Last() != t) + // Skip the first vertex if it is the same as the last vertex from the previous segment + int skipFirst = calculatedPath.Last() == segmentVertices[0] ? 1 : 0; + foreach (Vector2 t in calculateSubPath(segmentVertices, segmentType).Skip(skipFirst)) calculatedPath.Add(t); } From 0360646e9b8858ef52ab4ad5599a32dbdc3ddbb4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Sep 2023 14:38:53 +0900 Subject: [PATCH 0590/4092] Avoid fast fade out if slider head was not hit --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 09d98654c3..1a6a0a9ecc 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -317,7 +317,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables switch (state) { case ArmedState.Hit: - if (SliderBody?.SnakingOut.Value == true) + if (HeadCircle.IsHit && SliderBody?.SnakingOut.Value == true) Body.FadeOut(40); // short fade to allow for any body colour to smoothly disappear. break; } From 4504c9fc43f6783d07041275a40d3fdff29d8f02 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Sep 2023 14:42:07 +0900 Subject: [PATCH 0591/4092] Update tests in line with new slider snaking behaviour --- .../TestSceneSliderSnaking.cs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 630049f408..aef7dcaa59 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -135,9 +135,9 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestRepeatArrowDoesNotMoveWhenHit() + public void TestRepeatArrowDoesNotMove([Values] bool useAutoplay) { - AddStep("enable autoplay", () => autoplay = true); + AddStep($"set autoplay to {useAutoplay}", () => autoplay = useAutoplay); setSnaking(true); CreateTest(); // repeat might have a chance to update its position depending on where in the frame its hit, @@ -145,15 +145,6 @@ namespace osu.Game.Rulesets.Osu.Tests addCheckPositionChangeSteps(() => 16600, getSliderRepeat, positionAlmostSame); } - [Test] - public void TestRepeatArrowMovesWhenNotHit() - { - AddStep("disable autoplay", () => autoplay = false); - setSnaking(true); - CreateTest(); - addCheckPositionChangeSteps(() => 16600, getSliderRepeat, positionDecreased); - } - private void retrieveSlider(int index) { AddStep("retrieve slider at index", () => slider = (Slider)beatmap.HitObjects[index]); From 046e96afcd9ce4c32fcf2a7c1bcaafe9ba811782 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Sep 2023 14:51:03 +0900 Subject: [PATCH 0592/4092] Apply NRT to slider snaking tests --- .../TestSceneSliderSnaking.cs | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index aef7dcaa59..13166c2b6b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -33,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Tests public partial class TestSceneSliderSnaking : TestSceneOsuPlayer { [Resolved] - private AudioManager audioManager { get; set; } + private AudioManager audioManager { get; set; } = null!; protected override bool Autoplay => autoplay; private bool autoplay; @@ -41,12 +39,12 @@ namespace osu.Game.Rulesets.Osu.Tests private readonly BindableBool snakingIn = new BindableBool(); private readonly BindableBool snakingOut = new BindableBool(); - private IBeatmap beatmap; + private IBeatmap beatmap = null!; private const double duration_of_span = 3605; private const double fade_in_modifier = -1200; - protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null) => new ClockBackedTestWorkingBeatmap(this.beatmap = beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); [BackgroundDependencyLoader] @@ -57,15 +55,8 @@ namespace osu.Game.Rulesets.Osu.Tests config.BindWith(OsuRulesetSetting.SnakingOutSliders, snakingOut); } - private Slider slider; - private DrawableSlider drawableSlider; - - [SetUp] - public void Setup() => Schedule(() => - { - slider = null; - drawableSlider = null; - }); + private Slider slider = null!; + private DrawableSlider? drawableSlider; protected override bool HasCustomSteps => true; @@ -150,7 +141,7 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("retrieve slider at index", () => slider = (Slider)beatmap.HitObjects[index]); addSeekStep(() => slider.StartTime); AddUntilStep("retrieve drawable slider", () => - (drawableSlider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == slider)) != null); + (drawableSlider = (DrawableSlider?)Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == slider)) != null); } private void addEnsureSnakingInSteps(Func startTime) => addCheckPositionChangeSteps(startTime, getSliderEnd, positionIncreased); @@ -170,7 +161,7 @@ namespace osu.Game.Rulesets.Osu.Tests private Func timeAtRepeat(Func startTime, int repeatIndex) => () => startTime() + 100 + duration_of_span * repeatIndex; private Func positionAtRepeat(int repeatIndex) => repeatIndex % 2 == 0 ? getSliderStart : getSliderEnd; - private List getSliderCurve() => ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve; + private List getSliderCurve() => ((PlaySliderBody)drawableSlider!.Body.Drawable).CurrentCurve; private Vector2 getSliderStart() => getSliderCurve().First(); private Vector2 getSliderEnd() => getSliderCurve().Last(); From c0f603eb0e4007cf8a35e8af4e25d9e01441b1ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Sep 2023 15:27:55 +0900 Subject: [PATCH 0593/4092] Fix typo in comment --- osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs index 07dc2bea54..f80f43bb77 100644 --- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs +++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs @@ -126,7 +126,7 @@ namespace osu.Game.Tournament.Screens.MapPool if (CurrentMatch.Value == null || CurrentMatch.Value.PicksBans.Count(p => p.Type == ChoiceType.Ban) < 2) return; - // if bans have already been placed, beatmap changes result in a selection being made autoamtically + // if bans have already been placed, beatmap changes result in a selection being made automatically if (beatmap.NewValue?.OnlineID > 0) addForBeatmap(beatmap.NewValue.OnlineID); } From 73db68a49a8226b57c2c41be024fbab946094fd6 Mon Sep 17 00:00:00 2001 From: Magnus-Cosmos Date: Tue, 19 Sep 2023 02:28:28 -0400 Subject: [PATCH 0594/4092] Check if path lists are empty --- osu.Game/Rulesets/Objects/SliderPath.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index cf6d0d212b..0ac057578b 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -261,14 +261,17 @@ namespace osu.Game.Rulesets.Objects // The current vertex ends the segment var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1); var segmentType = ControlPoints[start].Type ?? PathType.Linear; + // No need to calculate path when there is only 1 vertex if (segmentVertices.Length == 1) calculatedPath.Add(segmentVertices[0]); else if (segmentVertices.Length > 1) { + List subPath = calculateSubPath(segmentVertices, segmentType); // Skip the first vertex if it is the same as the last vertex from the previous segment - int skipFirst = calculatedPath.Last() == segmentVertices[0] ? 1 : 0; - foreach (Vector2 t in calculateSubPath(segmentVertices, segmentType).Skip(skipFirst)) + int skipFirst = calculatedPath.Count > 0 && subPath.Count > 0 && calculatedPath.Last() == subPath[0] ? 1 : 0; + + foreach (Vector2 t in subPath.Skip(skipFirst)) calculatedPath.Add(t); } From 8e199de78ac57a7d9ed959378eb17df40c7354e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 19 Sep 2023 08:30:17 +0200 Subject: [PATCH 0595/4092] Tweak nano beatmap card UX further to meet expectations --- .../Drawables/Cards/BeatmapCardNano.cs | 2 - .../Cards/CollapsibleButtonContainerSlim.cs | 237 +++++++++++------- 2 files changed, 142 insertions(+), 97 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNano.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNano.cs index 2f46bc51d6..29f9d7ed2c 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNano.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNano.cs @@ -149,8 +149,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards }; c.Expanded.BindTarget = Expanded; }); - - Action = () => buttonContainer.TriggerClick(); } private LocalisableString createArtistText() diff --git a/osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainerSlim.cs b/osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainerSlim.cs index d17ff0d759..151c91f4c1 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainerSlim.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainerSlim.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -17,10 +18,11 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Resources.Localisation.Web; using osuTK; +using osuTK.Graphics; namespace osu.Game.Beatmaps.Drawables.Cards { - public partial class CollapsibleButtonContainerSlim : OsuClickableContainer + public partial class CollapsibleButtonContainerSlim : Container { public Bindable ShowDetails = new Bindable(); public Bindable FavouriteState = new Bindable(); @@ -56,30 +58,15 @@ namespace osu.Game.Beatmaps.Drawables.Cards protected override Container Content => mainContent; - private readonly APIBeatmapSet beatmapSet; - private readonly Container background; - private readonly Container buttonArea; + private readonly OsuClickableContainer buttonArea; private readonly Container mainArea; private readonly Container mainContent; - private readonly Container icons; - private readonly SpriteIcon downloadIcon; - private readonly LoadingSpinner spinner; - private readonly SpriteIcon goToBeatmapIcon; - private const int icon_size = 12; - private Bindable preferNoVideo = null!; - - [Resolved] - private BeatmapModelDownloader beatmaps { get; set; } = null!; - - [Resolved] - private OsuGame? game { get; set; } - [Resolved] private OsuColour colours { get; set; } = null!; @@ -88,15 +75,13 @@ namespace osu.Game.Beatmaps.Drawables.Cards public CollapsibleButtonContainerSlim(APIBeatmapSet beatmapSet) { - this.beatmapSet = beatmapSet; - downloadTracker = new BeatmapDownloadTracker(beatmapSet); RelativeSizeAxes = Axes.Y; Masking = true; CornerRadius = BeatmapCard.CORNER_RADIUS; - base.Content.AddRange(new Drawable[] + InternalChildren = new Drawable[] { downloadTracker, background = new Container @@ -110,39 +95,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards Colour = Colour4.White }, }, - buttonArea = new Container + buttonArea = new ButtonArea(beatmapSet) { Name = @"Right (button) area", - RelativeSizeAxes = Axes.Y, - Origin = Anchor.TopRight, - Anchor = Anchor.TopRight, - Child = icons = new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - downloadIcon = new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(icon_size), - Icon = FontAwesome.Solid.Download - }, - spinner = new LoadingSpinner - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(icon_size) - }, - goToBeatmapIcon = new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(icon_size), - Icon = FontAwesome.Solid.AngleDoubleRight - }, - } - } + State = { BindTarget = downloadTracker.State } }, mainArea = new Container { @@ -168,23 +124,13 @@ namespace osu.Game.Beatmaps.Drawables.Cards } } } - }); - } - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - preferNoVideo = config.GetBindable(OsuSetting.PreferNoVideo); - - downloadIcon.Colour = spinner.Colour = colourProvider.Content1; - goToBeatmapIcon.Colour = colourProvider.Foreground1; + }; } protected override void LoadComplete() { base.LoadComplete(); - preferNoVideo.BindValueChanged(_ => updateState()); downloadTracker.State.BindValueChanged(_ => updateState()); ShowDetails.BindValueChanged(_ => updateState(), true); FinishTransforms(true); @@ -195,51 +141,152 @@ namespace osu.Game.Beatmaps.Drawables.Cards float targetWidth = Width - (ShowDetails.Value ? ButtonsExpandedWidth : ButtonsCollapsedWidth); mainArea.ResizeWidthTo(targetWidth, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + background.FadeColour(downloadTracker.State.Value == DownloadState.LocallyAvailable ? colours.Lime0 : colourProvider.Background3, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + buttonArea.FadeTo(ShowDetails.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + } - var backgroundColour = downloadTracker.State.Value == DownloadState.LocallyAvailable ? colours.Lime0 : colourProvider.Background3; - if (ShowDetails.Value) - backgroundColour = backgroundColour.Lighten(0.2f); + private partial class ButtonArea : OsuClickableContainer + { + public Bindable State { get; } = new Bindable(); - background.FadeColour(backgroundColour, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - icons.FadeTo(ShowDetails.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + private readonly APIBeatmapSet beatmapSet; - if (beatmapSet.Availability.DownloadDisabled) + private Box hoverLayer = null!; + private SpriteIcon downloadIcon = null!; + private LoadingSpinner spinner = null!; + private SpriteIcon goToBeatmapIcon = null!; + + private Bindable preferNoVideo = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + [Resolved] + private BeatmapModelDownloader beatmaps { get; set; } = null!; + + [Resolved] + private OsuGame? game { get; set; } + + public ButtonArea(APIBeatmapSet beatmapSet) { - Enabled.Value = false; - TooltipText = BeatmapsetsStrings.AvailabilityDisabled; - return; + this.beatmapSet = beatmapSet; } - switch (downloadTracker.State.Value) + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) { - case DownloadState.NotDownloaded: - Action = () => beatmaps.Download(beatmapSet, preferNoVideo.Value); - break; + RelativeSizeAxes = Axes.Y; + Origin = Anchor.TopRight; + Anchor = Anchor.TopRight; + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = -BeatmapCard.CORNER_RADIUS }, + Child = hoverLayer = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.White.Opacity(0.1f), + Blending = BlendingParameters.Additive + } + }, + downloadIcon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(icon_size), + Icon = FontAwesome.Solid.Download + }, + spinner = new LoadingSpinner + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(icon_size) + }, + goToBeatmapIcon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(icon_size), + Icon = FontAwesome.Solid.AngleDoubleRight + }, + } + }; - case DownloadState.LocallyAvailable: - Action = () => game?.PresentBeatmap(beatmapSet); - break; - - default: - Action = null; - break; + preferNoVideo = config.GetBindable(OsuSetting.PreferNoVideo); } - downloadIcon.FadeTo(downloadTracker.State.Value == DownloadState.NotDownloaded ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - spinner.FadeTo(downloadTracker.State.Value == DownloadState.Downloading || downloadTracker.State.Value == DownloadState.Importing ? 1 : 0, - BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - goToBeatmapIcon.FadeTo(downloadTracker.State.Value == DownloadState.LocallyAvailable ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - - if (downloadTracker.State.Value == DownloadState.NotDownloaded) + protected override void LoadComplete() { - if (!beatmapSet.HasVideo) - TooltipText = BeatmapsetsStrings.PanelDownloadAll; + base.LoadComplete(); + + State.BindValueChanged(_ => updateState(), true); + FinishTransforms(true); + } + + protected override bool OnHover(HoverEvent e) + { + updateState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateState(); + base.OnHoverLost(e); + } + + private void updateState() + { + hoverLayer.FadeTo(IsHovered ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + + downloadIcon.FadeTo(State.Value == DownloadState.NotDownloaded ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + downloadIcon.FadeColour(IsHovered ? colourProvider.Content1 : colourProvider.Light1, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + + spinner.FadeTo(State.Value == DownloadState.Downloading || State.Value == DownloadState.Importing ? 1 : 0, + BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + spinner.FadeColour(IsHovered ? colourProvider.Content1 : colourProvider.Light1, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + + goToBeatmapIcon.FadeTo(State.Value == DownloadState.LocallyAvailable ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + goToBeatmapIcon.FadeColour(IsHovered ? colourProvider.Foreground1 : colourProvider.Background3, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + + switch (State.Value) + { + case DownloadState.NotDownloaded: + Action = () => beatmaps.Download(beatmapSet, preferNoVideo.Value); + break; + + case DownloadState.LocallyAvailable: + Action = () => game?.PresentBeatmap(beatmapSet); + break; + + default: + Action = null; + break; + } + + if (beatmapSet.Availability.DownloadDisabled) + { + Enabled.Value = false; + TooltipText = BeatmapsetsStrings.AvailabilityDisabled; + return; + } + + if (State.Value == DownloadState.NotDownloaded) + { + if (!beatmapSet.HasVideo) + TooltipText = BeatmapsetsStrings.PanelDownloadAll; + else + TooltipText = preferNoVideo.Value ? BeatmapsetsStrings.PanelDownloadNoVideo : BeatmapsetsStrings.PanelDownloadVideo; + } else - TooltipText = preferNoVideo.Value ? BeatmapsetsStrings.PanelDownloadNoVideo : BeatmapsetsStrings.PanelDownloadVideo; - } - else - { - TooltipText = default; + { + TooltipText = default; + } } } } From 0555d22eb8cc5edd3eaf1ab20e63d474bc75194c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Sep 2023 16:35:22 +0900 Subject: [PATCH 0596/4092] Add comment mentioning why hover is disabled on the notification type --- osu.Game/Database/MissingBeatmapNotification.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Database/MissingBeatmapNotification.cs b/osu.Game/Database/MissingBeatmapNotification.cs index d98c07ce1f..f2f7315e8b 100644 --- a/osu.Game/Database/MissingBeatmapNotification.cs +++ b/osu.Game/Database/MissingBeatmapNotification.cs @@ -83,6 +83,7 @@ namespace osu.Game.Database card.Width = Content.DrawWidth; } + // Disable hover so we don't have silly colour conflicts with the nested beatmap card. protected override bool OnHover(HoverEvent e) => false; protected override void OnHoverLost(HoverLostEvent e) { } From 7f30354e61d6d9fa8bc931b894f5040ff6d25c4c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Sep 2023 17:20:58 +0900 Subject: [PATCH 0597/4092] Adjust sizing slightly to remove need for `CollapsibleButtonContainerSlim` --- .../Drawables/Cards/BeatmapCardNano.cs | 6 +- .../Cards/CollapsibleButtonContainerSlim.cs | 293 ------------------ .../Database/MissingBeatmapNotification.cs | 4 - 3 files changed, 3 insertions(+), 300 deletions(-) delete mode 100644 osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainerSlim.cs diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNano.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNano.cs index 29f9d7ed2c..4ab2b0c973 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNano.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNano.cs @@ -38,7 +38,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards [Cached] private readonly BeatmapCardContent content; - private CollapsibleButtonContainerSlim buttonContainer = null!; + private CollapsibleButtonContainer buttonContainer = null!; private FillFlowContainer idleBottomContent = null!; private BeatmapCardDownloadProgressBar downloadProgressBar = null!; @@ -66,12 +66,12 @@ namespace osu.Game.Beatmaps.Drawables.Cards Height = height, Children = new Drawable[] { - buttonContainer = new CollapsibleButtonContainerSlim(BeatmapSet) + buttonContainer = new CollapsibleButtonContainer(BeatmapSet) { Width = Width, FavouriteState = { BindTarget = FavouriteState }, ButtonsCollapsedWidth = 5, - ButtonsExpandedWidth = 20, + ButtonsExpandedWidth = 30, Children = new Drawable[] { new FillFlowContainer diff --git a/osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainerSlim.cs b/osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainerSlim.cs deleted file mode 100644 index 151c91f4c1..0000000000 --- a/osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainerSlim.cs +++ /dev/null @@ -1,293 +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.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; -using osu.Game.Configuration; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online; -using osu.Game.Online.API.Requests.Responses; -using osu.Game.Overlays; -using osu.Game.Resources.Localisation.Web; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Beatmaps.Drawables.Cards -{ - public partial class CollapsibleButtonContainerSlim : Container - { - public Bindable ShowDetails = new Bindable(); - public Bindable FavouriteState = new Bindable(); - - private readonly BeatmapDownloadTracker downloadTracker; - - private float buttonsExpandedWidth; - - public float ButtonsExpandedWidth - { - get => buttonsExpandedWidth; - set - { - buttonsExpandedWidth = value; - buttonArea.Width = value; - if (IsLoaded) - updateState(); - } - } - - private float buttonsCollapsedWidth; - - public float ButtonsCollapsedWidth - { - get => buttonsCollapsedWidth; - set - { - buttonsCollapsedWidth = value; - if (IsLoaded) - updateState(); - } - } - - protected override Container Content => mainContent; - - private readonly Container background; - - private readonly OsuClickableContainer buttonArea; - - private readonly Container mainArea; - private readonly Container mainContent; - - private const int icon_size = 12; - - [Resolved] - private OsuColour colours { get; set; } = null!; - - [Resolved] - private OverlayColourProvider colourProvider { get; set; } = null!; - - public CollapsibleButtonContainerSlim(APIBeatmapSet beatmapSet) - { - downloadTracker = new BeatmapDownloadTracker(beatmapSet); - - RelativeSizeAxes = Axes.Y; - Masking = true; - CornerRadius = BeatmapCard.CORNER_RADIUS; - - InternalChildren = new Drawable[] - { - downloadTracker, - background = new Container - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Colour4.White - }, - }, - buttonArea = new ButtonArea(beatmapSet) - { - Name = @"Right (button) area", - State = { BindTarget = downloadTracker.State } - }, - mainArea = new Container - { - Name = @"Main content", - RelativeSizeAxes = Axes.Y, - CornerRadius = BeatmapCard.CORNER_RADIUS, - Masking = true, - Children = new Drawable[] - { - new BeatmapCardContentBackground(beatmapSet) - { - RelativeSizeAxes = Axes.Both, - Dimmed = { BindTarget = ShowDetails } - }, - mainContent = new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding - { - Horizontal = 10, - Vertical = 4 - }, - } - } - } - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - downloadTracker.State.BindValueChanged(_ => updateState()); - ShowDetails.BindValueChanged(_ => updateState(), true); - FinishTransforms(true); - } - - private void updateState() - { - float targetWidth = Width - (ShowDetails.Value ? ButtonsExpandedWidth : ButtonsCollapsedWidth); - - mainArea.ResizeWidthTo(targetWidth, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - background.FadeColour(downloadTracker.State.Value == DownloadState.LocallyAvailable ? colours.Lime0 : colourProvider.Background3, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - buttonArea.FadeTo(ShowDetails.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - } - - private partial class ButtonArea : OsuClickableContainer - { - public Bindable State { get; } = new Bindable(); - - private readonly APIBeatmapSet beatmapSet; - - private Box hoverLayer = null!; - private SpriteIcon downloadIcon = null!; - private LoadingSpinner spinner = null!; - private SpriteIcon goToBeatmapIcon = null!; - - private Bindable preferNoVideo = null!; - - [Resolved] - private OverlayColourProvider colourProvider { get; set; } = null!; - - [Resolved] - private BeatmapModelDownloader beatmaps { get; set; } = null!; - - [Resolved] - private OsuGame? game { get; set; } - - public ButtonArea(APIBeatmapSet beatmapSet) - { - this.beatmapSet = beatmapSet; - } - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - RelativeSizeAxes = Axes.Y; - Origin = Anchor.TopRight; - Anchor = Anchor.TopRight; - Child = new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = -BeatmapCard.CORNER_RADIUS }, - Child = hoverLayer = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Colour4.White.Opacity(0.1f), - Blending = BlendingParameters.Additive - } - }, - downloadIcon = new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(icon_size), - Icon = FontAwesome.Solid.Download - }, - spinner = new LoadingSpinner - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(icon_size) - }, - goToBeatmapIcon = new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(icon_size), - Icon = FontAwesome.Solid.AngleDoubleRight - }, - } - }; - - preferNoVideo = config.GetBindable(OsuSetting.PreferNoVideo); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - State.BindValueChanged(_ => updateState(), true); - FinishTransforms(true); - } - - protected override bool OnHover(HoverEvent e) - { - updateState(); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - updateState(); - base.OnHoverLost(e); - } - - private void updateState() - { - hoverLayer.FadeTo(IsHovered ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - - downloadIcon.FadeTo(State.Value == DownloadState.NotDownloaded ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - downloadIcon.FadeColour(IsHovered ? colourProvider.Content1 : colourProvider.Light1, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - - spinner.FadeTo(State.Value == DownloadState.Downloading || State.Value == DownloadState.Importing ? 1 : 0, - BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - spinner.FadeColour(IsHovered ? colourProvider.Content1 : colourProvider.Light1, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - - goToBeatmapIcon.FadeTo(State.Value == DownloadState.LocallyAvailable ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - goToBeatmapIcon.FadeColour(IsHovered ? colourProvider.Foreground1 : colourProvider.Background3, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - - switch (State.Value) - { - case DownloadState.NotDownloaded: - Action = () => beatmaps.Download(beatmapSet, preferNoVideo.Value); - break; - - case DownloadState.LocallyAvailable: - Action = () => game?.PresentBeatmap(beatmapSet); - break; - - default: - Action = null; - break; - } - - if (beatmapSet.Availability.DownloadDisabled) - { - Enabled.Value = false; - TooltipText = BeatmapsetsStrings.AvailabilityDisabled; - return; - } - - if (State.Value == DownloadState.NotDownloaded) - { - if (!beatmapSet.HasVideo) - TooltipText = BeatmapsetsStrings.PanelDownloadAll; - else - TooltipText = preferNoVideo.Value ? BeatmapsetsStrings.PanelDownloadNoVideo : BeatmapsetsStrings.PanelDownloadVideo; - } - else - { - TooltipText = default; - } - } - } - } -} diff --git a/osu.Game/Database/MissingBeatmapNotification.cs b/osu.Game/Database/MissingBeatmapNotification.cs index f2f7315e8b..bc96625ead 100644 --- a/osu.Game/Database/MissingBeatmapNotification.cs +++ b/osu.Game/Database/MissingBeatmapNotification.cs @@ -83,10 +83,6 @@ namespace osu.Game.Database card.Width = Content.DrawWidth; } - // Disable hover so we don't have silly colour conflicts with the nested beatmap card. - protected override bool OnHover(HoverEvent e) => false; - protected override void OnHoverLost(HoverLostEvent e) { } - private void beatmapsChanged(IRealmCollection sender, ChangeSet? changes) { if (changes?.InsertedIndices == null) return; From 62f97a8d83a5baa59da76cdcea179b6be500360a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 19 Sep 2023 10:27:24 +0200 Subject: [PATCH 0598/4092] Adjust beatmap card thumbnail dim state to match web better --- .../Drawables/Cards/BeatmapCardThumbnail.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs index ad91615031..5a26a988fb 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs @@ -3,15 +3,15 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps.Drawables.Cards.Buttons; -using osu.Game.Graphics; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Framework.Graphics.UserInterface; using osuTK; -using osuTK.Graphics; namespace osu.Game.Beatmaps.Drawables.Cards { @@ -25,7 +25,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards set => foreground.Padding = value; } - private readonly UpdateableOnlineBeatmapSetCover cover; + private readonly Box background; private readonly Container foreground; private readonly PlayButton playButton; private readonly CircularProgress progress; @@ -33,15 +33,22 @@ namespace osu.Game.Beatmaps.Drawables.Cards protected override Container Content => content; + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + public BeatmapCardThumbnail(APIBeatmapSet beatmapSetInfo) { InternalChildren = new Drawable[] { - cover = new UpdateableOnlineBeatmapSetCover(BeatmapSetCoverType.List) + new UpdateableOnlineBeatmapSetCover(BeatmapSetCoverType.List) { RelativeSizeAxes = Axes.Both, OnlineInfo = beatmapSetInfo }, + background = new Box + { + RelativeSizeAxes = Axes.Both + }, foreground = new Container { RelativeSizeAxes = Axes.Both, @@ -68,7 +75,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private void load() { progress.Colour = colourProvider.Highlight1; } @@ -89,7 +96,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards bool shouldDim = Dimmed.Value || playButton.Playing.Value; playButton.FadeTo(shouldDim ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - cover.FadeColour(shouldDim ? OsuColour.Gray(0.2f) : Color4.White, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + background.FadeColour(colourProvider.Background6.Opacity(shouldDim ? 0.8f : 0f), BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); } } } From 0593c76c57436757e7da3ada6556c683396fdbfa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Sep 2023 17:34:24 +0900 Subject: [PATCH 0599/4092] Fix log output using incorrect name --- osu.Game/Scoring/ScoreImporter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index 2875035e1b..26594fb815 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -56,7 +56,7 @@ namespace osu.Game.Scoring catch (LegacyScoreDecoder.BeatmapNotFoundException e) { onMissingBeatmap(e, archive, name); - Logger.Log($@"Score '{name}' failed to import: no corresponding beatmap with the hash '{e.Hash}' could be found.", LoggingTarget.Database); + Logger.Log($@"Score '{archive.Name}' failed to import: no corresponding beatmap with the hash '{e.Hash}' could be found.", LoggingTarget.Database); return null; } } From f726c38215b6ff35305969b7c7e79f89a25818f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Sep 2023 17:41:00 +0900 Subject: [PATCH 0600/4092] Pass `ArchiveReader` instead of `Stream` to simplify resolution code --- .../TestSceneMissingBeatmapNotification.cs | 4 +-- .../Database/MissingBeatmapNotification.cs | 14 ++++----- osu.Game/Scoring/ScoreImporter.cs | 30 ++++--------------- 3 files changed, 14 insertions(+), 34 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneMissingBeatmapNotification.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneMissingBeatmapNotification.cs index 23b9c5f76a..f5506edf3b 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneMissingBeatmapNotification.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneMissingBeatmapNotification.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.IO; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -9,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Database; using osu.Game.Overlays; +using osu.Game.Tests.Scores.IO; namespace osu.Game.Tests.Visual.UserInterface { @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.UserInterface AutoSizeAxes = Axes.Y, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Child = new MissingBeatmapNotification(CreateAPIBeatmapSet(Ruleset.Value).Beatmaps.First(), new MemoryStream(), "deadbeef") + Child = new MissingBeatmapNotification(CreateAPIBeatmapSet(Ruleset.Value).Beatmaps.First(), new ImportScoreTest.TestArchiveReader(), "deadbeef") }; } } diff --git a/osu.Game/Database/MissingBeatmapNotification.cs b/osu.Game/Database/MissingBeatmapNotification.cs index bc96625ead..261de2a938 100644 --- a/osu.Game/Database/MissingBeatmapNotification.cs +++ b/osu.Game/Database/MissingBeatmapNotification.cs @@ -2,19 +2,18 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.IO; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables.Cards; using osu.Game.Configuration; +using osu.Game.IO.Archives; +using osu.Game.Localisation; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Notifications; using osu.Game.Scoring; using Realms; -using osu.Game.Localisation; namespace osu.Game.Database { @@ -29,7 +28,7 @@ namespace osu.Game.Database [Resolved] private RealmAccess realm { get; set; } = null!; - private readonly MemoryStream scoreStream; + private readonly ArchiveReader scoreArchive; private readonly APIBeatmapSet beatmapSetInfo; private readonly string beatmapHash; @@ -39,12 +38,12 @@ namespace osu.Game.Database private IDisposable? realmSubscription; - public MissingBeatmapNotification(APIBeatmap beatmap, MemoryStream scoreStream, string beatmapHash) + public MissingBeatmapNotification(APIBeatmap beatmap, ArchiveReader scoreArchive, string beatmapHash) { beatmapSetInfo = beatmap.BeatmapSet!; this.beatmapHash = beatmapHash; - this.scoreStream = scoreStream; + this.scoreArchive = scoreArchive; } [BackgroundDependencyLoader] @@ -89,7 +88,8 @@ namespace osu.Game.Database if (sender.Any(s => s.Beatmaps.Any(b => b.MD5Hash == beatmapHash))) { - var importTask = new ImportTask(scoreStream, "score.osr"); + string name = scoreArchive.Filenames.First(f => f.EndsWith(".osr", StringComparison.OrdinalIgnoreCase)); + var importTask = new ImportTask(scoreArchive.GetStream(name), name); scoreManager.Import(new[] { importTask }); realmSubscription?.Dispose(); Close(false); diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index 26594fb815..b85b6a066e 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.IO; using System.Linq; using System.Threading; using Newtonsoft.Json; @@ -55,36 +54,17 @@ namespace osu.Game.Scoring } catch (LegacyScoreDecoder.BeatmapNotFoundException e) { - onMissingBeatmap(e, archive, name); Logger.Log($@"Score '{archive.Name}' failed to import: no corresponding beatmap with the hash '{e.Hash}' could be found.", LoggingTarget.Database); + + // In the case of a missing beatmap, let's attempt to resolve it and show a prompt to the user to download the required beatmap. + var req = new GetBeatmapRequest(new BeatmapInfo { MD5Hash = e.Hash }); + req.Success += res => PostNotification?.Invoke(new MissingBeatmapNotification(res, archive, e.Hash)); + api.Queue(req); return null; } } } - private void onMissingBeatmap(LegacyScoreDecoder.BeatmapNotFoundException e, ArchiveReader archive, string name) - { - var stream = new MemoryStream(); - - // stream will be closed after the exception was thrown, so fetch the stream again. - using (var scoreStream = archive.GetStream(name)) - { - scoreStream.CopyTo(stream); - } - - var req = new GetBeatmapRequest(new BeatmapInfo - { - MD5Hash = e.Hash - }); - - req.Success += res => - { - PostNotification?.Invoke(new MissingBeatmapNotification(res, stream, e.Hash)); - }; - - api.Queue(req); - } - public Score GetScore(ScoreInfo score) => new LegacyDatabasedScore(score, rulesets, beatmaps(), Files.Store); protected override void Populate(ScoreInfo model, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default) From cdb5fea513f06bf18eefc62a8b0d4997d4c8a91d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Sep 2023 17:53:00 +0900 Subject: [PATCH 0601/4092] Remove unused translations --- .../Localisation/OnlineSettingsStrings.cs | 5 ----- osu.Game/Localisation/WebSettingsStrings.cs | 19 ------------------- 2 files changed, 24 deletions(-) delete mode 100644 osu.Game/Localisation/WebSettingsStrings.cs diff --git a/osu.Game/Localisation/OnlineSettingsStrings.cs b/osu.Game/Localisation/OnlineSettingsStrings.cs index 5ea53a13bf..0660bac172 100644 --- a/osu.Game/Localisation/OnlineSettingsStrings.cs +++ b/osu.Game/Localisation/OnlineSettingsStrings.cs @@ -54,11 +54,6 @@ namespace osu.Game.Localisation ///

yftJ!Y0GsdcN%;MEM+5u>2KjHnOqx_AeVsl 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 literal 87032 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 CAgN>k diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay-1@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay-1@2x.png index e611add3ba9861baf68614ec51d63d3f94c8592a..3b5e886933d2f7a8803a44304c5439058ef86fa2 100755 GIT binary patch literal 40858 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�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 U^GtX>4gf!XyL%uw1Pc(DKu92j;1Zk!4M7q# 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

public static LocalisableString PreferNoVideo => new TranslatableString(getKey(@"prefer_no_video"), @"Prefer downloads without video"); - /// - /// "Automatically download beatmaps when spectating" - /// - public static LocalisableString AutomaticallyDownloadWhenSpectating => new TranslatableString(getKey(@"automatically_download_when_spectating"), @"Automatically download beatmaps when spectating"); - /// /// "Automatically download missing beatmaps" /// diff --git a/osu.Game/Localisation/WebSettingsStrings.cs b/osu.Game/Localisation/WebSettingsStrings.cs deleted file mode 100644 index b3033524dc..0000000000 --- a/osu.Game/Localisation/WebSettingsStrings.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.Framework.Localisation; - -namespace osu.Game.Localisation -{ - public static class WebSettingsStrings - { - private const string prefix = @"osu.Game.Resources.Localisation.WebSettings"; - - /// - /// "Automatically download missing beatmaps" - /// - public static LocalisableString AutomaticallyDownloadMissingBeatmaps => new TranslatableString(getKey(@"automatically_download_missing_beatmaps"), @"Automatically download missing beatmaps"); - - private static string getKey(string key) => $@"{prefix}:{key}"; - } -} \ No newline at end of file From 05e05f8160a73dd9f270147874ba0632897d2670 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Sep 2023 18:02:08 +0900 Subject: [PATCH 0602/4092] Increase transition speed slightly --- osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index a16f6d5689..25e42bcbf7 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -19,7 +19,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards { public abstract partial class BeatmapCard : OsuClickableContainer, IHasContextMenu { - public const float TRANSITION_DURATION = 400; + public const float TRANSITION_DURATION = 340; public const float CORNER_RADIUS = 10; protected const float WIDTH = 430; From ed9039f60f31415089be387e9075e6439099ee58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 19 Sep 2023 11:09:23 +0200 Subject: [PATCH 0603/4092] Fix notification text sets overwriting each other --- .../Database/MissingBeatmapNotification.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/osu.Game/Database/MissingBeatmapNotification.cs b/osu.Game/Database/MissingBeatmapNotification.cs index 261de2a938..584b2675f3 100644 --- a/osu.Game/Database/MissingBeatmapNotification.cs +++ b/osu.Game/Database/MissingBeatmapNotification.cs @@ -52,14 +52,6 @@ namespace osu.Game.Database realmSubscription = realm.RegisterForNotifications( realm => realm.All().Where(s => !s.DeletePending), beatmapsChanged); - realm.Run(r => - { - if (r.All().Any(s => !s.DeletePending && s.OnlineID == beatmapSetInfo.OnlineID)) - { - Text = NotificationsStrings.MismatchingBeatmapForReplay; - } - }); - autoDownloadConfig = config.GetBindable(OsuSetting.AutomaticallyDownloadMissingBeatmaps); noVideoSetting = config.GetBindable(OsuSetting.PreferNoVideo); @@ -71,9 +63,15 @@ namespace osu.Game.Database base.LoadComplete(); if (autoDownloadConfig.Value) + { + Text = NotificationsStrings.DownloadingBeatmapForReplay; beatmapDownloader.Download(beatmapSetInfo, noVideoSetting.Value); - - Text = autoDownloadConfig.Value ? NotificationsStrings.DownloadingBeatmapForReplay : NotificationsStrings.MissingBeatmapForReplay; + } + else + { + bool missingSetMatchesExistingOnlineId = realm.Run(r => r.All().Any(s => !s.DeletePending && s.OnlineID == beatmapSetInfo.OnlineID)); + Text = missingSetMatchesExistingOnlineId ? NotificationsStrings.MismatchingBeatmapForReplay : NotificationsStrings.MissingBeatmapForReplay; + } } protected override void Update() From 773ec469898c9d616c2211723ebb3a9742dc76c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Sep 2023 23:59:44 +0900 Subject: [PATCH 0604/4092] Expose some storyboard pieces to allow better testability --- osu.Game/Storyboards/Drawables/DrawableStoryboard.cs | 4 +++- osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs | 5 ++++- osu.Game/Storyboards/Storyboard.cs | 2 +- osu.Game/Storyboards/StoryboardVideo.cs | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index 6931cea81e..a11251ed22 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -23,7 +23,9 @@ namespace osu.Game.Storyboards.Drawables { public partial class DrawableStoryboard : Container { - [Cached] + public Vector2 AppliedScale { get; private set; } + + [Cached(typeof(Storyboard))] public Storyboard Storyboard { get; } /// diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs index 38e7ff1c70..40842fe7ed 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.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 System.Collections.Generic; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -30,10 +31,12 @@ namespace osu.Game.Storyboards.Drawables InternalChild = ElementContainer = new LayerElementContainer(layer); } - protected partial class LayerElementContainer : LifetimeManagementContainer + public partial class LayerElementContainer : LifetimeManagementContainer { private readonly StoryboardLayer storyboardLayer; + public IEnumerable Elements => InternalChildren; + public LayerElementContainer(StoryboardLayer layer) { storyboardLayer = layer; diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 1892855d3d..03e30d6272 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -86,7 +86,7 @@ namespace osu.Game.Storyboards } } - public DrawableStoryboard CreateDrawable(IReadOnlyList? mods = null) => + public virtual DrawableStoryboard CreateDrawable(IReadOnlyList? mods = null) => new DrawableStoryboard(this, mods); private static readonly string[] image_extensions = { @".png", @".jpg" }; diff --git a/osu.Game/Storyboards/StoryboardVideo.cs b/osu.Game/Storyboards/StoryboardVideo.cs index 4652e45852..8c11e19a06 100644 --- a/osu.Game/Storyboards/StoryboardVideo.cs +++ b/osu.Game/Storyboards/StoryboardVideo.cs @@ -14,7 +14,7 @@ namespace osu.Game.Storyboards public double StartTime { get; } - public StoryboardVideo(string path, int offset) + public StoryboardVideo(string path, double offset) { Path = path; StartTime = offset; From 2f020f8682aece5fcf4b54f879c4492149485ff6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Sep 2023 19:44:41 +0900 Subject: [PATCH 0605/4092] Add test coverage of storyboard preferring skin when specified --- .../TestSceneDrawableStoryboardSprite.cs | 176 +++++++++++++++--- 1 file changed, 150 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs index ec4bb1a86b..d20c7c2f7a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs @@ -2,17 +2,23 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Framework.IO.Stores; using osu.Framework.Testing; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Storyboards; using osu.Game.Storyboards.Drawables; +using osu.Game.Tests.Resources; using osuTK; namespace osu.Game.Tests.Visual.Gameplay @@ -21,17 +27,21 @@ namespace osu.Game.Tests.Visual.Gameplay { protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); - [Cached] - private Storyboard storyboard { get; set; } = new Storyboard(); + [Cached(typeof(Storyboard))] + private TestStoryboard storyboard { get; set; } = new TestStoryboard(); private IEnumerable sprites => this.ChildrenOfType(); + private const string lookup_name = "hitcircleoverlay"; + [Test] public void TestSkinSpriteDisallowedByDefault() { - const string lookup_name = "hitcircleoverlay"; - - AddStep("allow skin lookup", () => storyboard.UseSkinSprites = false); + AddStep("disallow all lookups", () => + { + storyboard.UseSkinSprites = false; + storyboard.AlwaysProvideTexture = false; + }); AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); @@ -40,11 +50,13 @@ namespace osu.Game.Tests.Visual.Gameplay } [Test] - public void TestAllowLookupFromSkin() + public void TestLookupFromStoryboard() { - const string lookup_name = "hitcircleoverlay"; - - AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true); + AddStep("allow storyboard lookup", () => + { + storyboard.UseSkinSprites = false; + storyboard.AlwaysProvideTexture = true; + }); AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); @@ -52,16 +64,54 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("sprite found texture", () => sprites.Any(sprite => sprite.ChildrenOfType().All(s => s.Texture != null))); - AddAssert("skinnable sprite has correct size", () => - sprites.Any(sprite => sprite.ChildrenOfType().All(s => s.Size == new Vector2(128)))); + assertStoryboardSourced(); + } + + [Test] + public void TestSkinLookupPreferredOverStoryboard() + { + AddStep("allow all lookups", () => + { + storyboard.UseSkinSprites = true; + storyboard.AlwaysProvideTexture = true; + }); + + AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); + + // Only checking for at least one sprite that succeeded, as not all skins in this test provide the hitcircleoverlay texture. + AddAssert("sprite found texture", () => + sprites.Any(sprite => sprite.ChildrenOfType().All(s => s.Texture != null))); + + assertSkinSourced(); + } + + [Test] + public void TestAllowLookupFromSkin() + { + AddStep("allow skin lookup", () => + { + storyboard.UseSkinSprites = true; + storyboard.AlwaysProvideTexture = false; + }); + + AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); + + // Only checking for at least one sprite that succeeded, as not all skins in this test provide the hitcircleoverlay texture. + AddAssert("sprite found texture", () => + sprites.Any(sprite => sprite.ChildrenOfType().All(s => s.Texture != null))); + + assertSkinSourced(); } [Test] public void TestFlippedSprite() { - const string lookup_name = "hitcircleoverlay"; + AddStep("allow all lookups", () => + { + storyboard.UseSkinSprites = true; + storyboard.AlwaysProvideTexture = true; + }); - AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true); AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); AddStep("flip sprites", () => sprites.ForEach(s => { @@ -74,9 +124,12 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestZeroScale() { - const string lookup_name = "hitcircleoverlay"; + AddStep("allow all lookups", () => + { + storyboard.UseSkinSprites = true; + storyboard.AlwaysProvideTexture = true; + }); - AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true); AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); AddAssert("sprites present", () => sprites.All(s => s.IsPresent)); AddStep("scale sprite", () => sprites.ForEach(s => s.VectorScale = new Vector2(0, 1))); @@ -86,9 +139,12 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestNegativeScale() { - const string lookup_name = "hitcircleoverlay"; + AddStep("allow all lookups", () => + { + storyboard.UseSkinSprites = true; + storyboard.AlwaysProvideTexture = true; + }); - AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true); AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); AddStep("scale sprite", () => sprites.ForEach(s => s.VectorScale = new Vector2(-1))); AddAssert("origin flipped", () => sprites.All(s => s.Origin == Anchor.BottomRight)); @@ -97,9 +153,12 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestNegativeScaleWithFlippedSprite() { - const string lookup_name = "hitcircleoverlay"; + AddStep("allow all lookups", () => + { + storyboard.UseSkinSprites = true; + storyboard.AlwaysProvideTexture = true; + }); - AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true); AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); AddStep("scale sprite", () => sprites.ForEach(s => s.VectorScale = new Vector2(-1))); AddAssert("origin flipped", () => sprites.All(s => s.Origin == Anchor.BottomRight)); @@ -111,13 +170,78 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("origin back", () => sprites.All(s => s.Origin == Anchor.TopLeft)); } - private DrawableStoryboardSprite createSprite(string lookupName, Anchor origin, Vector2 initialPosition) - => new DrawableStoryboardSprite( - new StoryboardSprite(lookupName, origin, initialPosition) - ).With(s => + private DrawableStoryboard createSprite(string lookupName, Anchor origin, Vector2 initialPosition) + { + var layer = storyboard.GetLayer("Background"); + + var sprite = new StoryboardSprite(lookupName, origin, initialPosition); + sprite.AddLoop(Time.Current, 100).Alpha.Add(Easing.None, 0, 10000, 1, 1); + + layer.Elements.Clear(); + layer.Add(sprite); + + return storyboard.CreateDrawable(); + } + + private void assertStoryboardSourced() + { + AddAssert("sprite came from storyboard", () => + sprites.Any(sprite => sprite.ChildrenOfType().All(s => s.Size == new Vector2(200)))); + } + + private void assertSkinSourced() + { + AddAssert("sprite came from skin", () => + sprites.Any(sprite => sprite.ChildrenOfType().All(s => s.Size == new Vector2(128)))); + } + + private partial class TestStoryboard : Storyboard + { + public override DrawableStoryboard CreateDrawable(IReadOnlyList? mods = null) { - s.LifetimeStart = double.MinValue; - s.LifetimeEnd = double.MaxValue; - }); + return new TestDrawableStoryboard(this, mods); + } + + public bool AlwaysProvideTexture { get; set; } + + public override string GetStoragePathFromStoryboardPath(string path) => AlwaysProvideTexture ? path : string.Empty; + + private partial class TestDrawableStoryboard : DrawableStoryboard + { + private readonly bool alwaysProvideTexture; + + public TestDrawableStoryboard(TestStoryboard storyboard, IReadOnlyList? mods) + : base(storyboard, mods) + { + alwaysProvideTexture = storyboard.AlwaysProvideTexture; + } + + protected override IResourceStore CreateResourceLookupStore() => alwaysProvideTexture + ? new AlwaysReturnsTextureStore() + : new ResourceStore(); + + internal class AlwaysReturnsTextureStore : IResourceStore + { + private const string test_image = "Resources/Textures/test-image.png"; + + private readonly DllResourceStore store; + + public AlwaysReturnsTextureStore() + { + store = TestResources.GetStore(); + } + + public void Dispose() => store.Dispose(); + + public byte[] Get(string name) => store.Get(test_image); + + public Task GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) => store.GetAsync(test_image, cancellationToken); + + public Stream GetStream(string name) => store.GetStream(test_image); + + public IEnumerable GetAvailableResources() => store.GetAvailableResources(); + } + } + } } } From 4a7dc4d7927aaba2b1f527e6b2939fa179189535 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Sep 2023 19:13:58 +0900 Subject: [PATCH 0606/4092] Fix storyboard `UseSkinSprites` being implemented incorrectly This was implemented as a "fallback", but it's actually intended to be an "override". As in it allows storyboarders to *prefer* a skin sprite before falling back to a local version contained within the storyboard. Can be tested with https://osu.ppy.sh/beatmapsets/832364#osu/1743837. Closes https://github.com/ppy/osu/issues/24813. --- .../Drawables/DrawableStoryboardSprite.cs | 23 ++++++++++++------- osu.Game/Storyboards/Storyboard.cs | 2 +- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index 379de1a497..14132654d1 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.ComponentModel; using osu.Framework.Allocation; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; @@ -74,6 +75,15 @@ namespace osu.Game.Storyboards.Drawables public override bool IsPresent => !float.IsNaN(DrawPosition.X) && !float.IsNaN(DrawPosition.Y) && base.IsPresent; + [Resolved] + private ISkinSource skin { get; set; } = null!; + + [Resolved] + private Storyboard storyboard { get; set; } = null!; + + [Resolved] + private TextureStore textureStore { get; set; } = null!; + public DrawableStoryboardSprite(StoryboardSprite sprite) { Sprite = sprite; @@ -84,24 +94,21 @@ namespace osu.Game.Storyboards.Drawables LifetimeEnd = sprite.EndTimeForDisplay; } - [Resolved] - private ISkinSource skin { get; set; } = null!; - [BackgroundDependencyLoader] - private void load(TextureStore textureStore, Storyboard storyboard) + private void load() { - Texture = textureStore.Get(Sprite.Path); - - if (Texture == null && storyboard.UseSkinSprites) + if (storyboard.UseSkinSprites) { skin.SourceChanged += skinSourceChanged; skinSourceChanged(); } + else + Texture = textureStore.Get(Sprite.Path); Sprite.ApplyTransforms(this); } - private void skinSourceChanged() => Texture = skin.GetTexture(Sprite.Path); + private void skinSourceChanged() => Texture = skin.GetTexture(Sprite.Path) ?? textureStore.Get(Sprite.Path); protected override void Dispose(bool isDisposing) { diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 03e30d6272..21342831b0 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -18,7 +18,7 @@ namespace osu.Game.Storyboards public BeatmapInfo BeatmapInfo = new BeatmapInfo(); /// - /// Whether the storyboard can fall back to skin sprites in case no matching storyboard sprites are found. + /// Whether the storyboard should prefer textures from the current skin before using local storyboard textures. /// public bool UseSkinSprites { get; set; } From 320a9fc17169c81b082c42d16fb750b9fc7e6026 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 19 Sep 2023 13:47:46 +0200 Subject: [PATCH 0607/4092] Replace test with better test --- .../Formats/LegacyBeatmapDecoderTest.cs | 29 +++++++++++-------- osu.Game.Tests/Resources/invalid-bank.osu | 18 ++++++++---- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 6fe9c902bb..1ba63f4037 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -622,7 +622,7 @@ namespace osu.Game.Tests.Beatmaps.Formats } [Test] - public void TestInvalidBankDefaultsToNone() + public void TestInvalidBankDefaultsToNormal() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; @@ -631,20 +631,25 @@ namespace osu.Game.Tests.Beatmaps.Formats { var hitObjects = decoder.Decode(stream).HitObjects; - Assert.AreEqual(HitSampleInfo.BANK_NORMAL, hitObjects[0].Samples[0].Bank); - Assert.AreEqual(HitSampleInfo.BANK_NORMAL, hitObjects[0].Samples[1].Bank); + assertObjectHasBanks(hitObjects[0], HitSampleInfo.BANK_DRUM); + assertObjectHasBanks(hitObjects[1], HitSampleInfo.BANK_NORMAL); + assertObjectHasBanks(hitObjects[2], HitSampleInfo.BANK_SOFT); + assertObjectHasBanks(hitObjects[3], HitSampleInfo.BANK_DRUM); + assertObjectHasBanks(hitObjects[4], HitSampleInfo.BANK_NORMAL); - Assert.AreEqual(HitSampleInfo.BANK_NORMAL, hitObjects[1].Samples[0].Bank); - Assert.AreEqual(HitSampleInfo.BANK_SOFT, hitObjects[1].Samples[1].Bank); + assertObjectHasBanks(hitObjects[5], HitSampleInfo.BANK_DRUM, HitSampleInfo.BANK_DRUM); + assertObjectHasBanks(hitObjects[6], HitSampleInfo.BANK_DRUM, HitSampleInfo.BANK_NORMAL); + assertObjectHasBanks(hitObjects[7], HitSampleInfo.BANK_DRUM, HitSampleInfo.BANK_SOFT); + assertObjectHasBanks(hitObjects[8], HitSampleInfo.BANK_DRUM, HitSampleInfo.BANK_DRUM); + assertObjectHasBanks(hitObjects[9], HitSampleInfo.BANK_DRUM, HitSampleInfo.BANK_NORMAL); + } - Assert.AreEqual(HitSampleInfo.BANK_SOFT, hitObjects[2].Samples[0].Bank); - Assert.AreEqual(HitSampleInfo.BANK_SOFT, hitObjects[2].Samples[1].Bank); + void assertObjectHasBanks(HitObject hitObject, string normalBank, string? additionsBank = null) + { + Assert.AreEqual(normalBank, hitObject.Samples[0].Bank); - Assert.AreEqual(HitSampleInfo.BANK_NORMAL, hitObjects[3].Samples[0].Bank); - Assert.AreEqual(HitSampleInfo.BANK_SOFT, hitObjects[3].Samples[1].Bank); - - Assert.AreEqual(HitSampleInfo.BANK_NORMAL, hitObjects[4].Samples[0].Bank); - Assert.AreEqual(HitSampleInfo.BANK_NORMAL, hitObjects[4].Samples[1].Bank); + if (additionsBank != null) + Assert.AreEqual(additionsBank, hitObject.Samples[1].Bank); } } diff --git a/osu.Game.Tests/Resources/invalid-bank.osu b/osu.Game.Tests/Resources/invalid-bank.osu index fb54a61fd3..8c554cc17f 100644 --- a/osu.Game.Tests/Resources/invalid-bank.osu +++ b/osu.Game.Tests/Resources/invalid-bank.osu @@ -3,9 +3,17 @@ osu file format v14 [General] SampleSet: Normal +[TimingPoints] +0,500,4,3,0,100,1,0 + [HitObjects] -256,192,1000,1,8,0:0:0:0: -256,192,2000,1,8,1:2:0:0: -256,192,3000,1,8,2:62:0:0: -256,192,4000,1,8,41:2:0:0: -256,192,5000,1,8,41:62:0:0: +256,192,1000,5,0,0:0:0:0: +256,192,2000,1,0,1:0:0:0: +256,192,3000,1,0,2:0:0:0: +256,192,4000,1,0,3:0:0:0: +256,192,5000,1,0,42:0:0:0: +256,192,6000,5,4,0:0:0:0: +256,192,7000,1,4,0:1:0:0: +256,192,8000,1,4,0:2:0:0: +256,192,9000,1,4,0:3:0:0: +256,192,10000,1,4,0:42:0:0: From c4a0ca326ed5e02a9219dee579cb9a01c554960b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 19 Sep 2023 13:53:49 +0200 Subject: [PATCH 0608/4092] Replace sample bank fix with more correct fix stable does not treat unknown enum members as `None` / `Auto`, it treats them as `Normal`: switch (sampleSet) { case SampleSet.Normal: default: sample = 0; break; case SampleSet.None: case SampleSet.Soft: sample = 1; break; case SampleSet.Drum: sample = 2; break; } (from https://github.com/peppy/osu-stable-reference/blob/1531237b63392e82c003c712faa028406073aa8f/osu!/Audio/AudioEngine.cs#L1158-L1171). --- .../Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 339e9bb5bc..d20f2d31bb 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -190,13 +190,18 @@ namespace osu.Game.Rulesets.Objects.Legacy string[] split = str.Split(':'); var bank = (LegacySampleBank)Parsing.ParseInt(split[0]); + if (!Enum.IsDefined(bank)) + bank = LegacySampleBank.Normal; + var addBank = (LegacySampleBank)Parsing.ParseInt(split[1]); + if (!Enum.IsDefined(addBank)) + addBank = LegacySampleBank.Normal; string stringBank = bank.ToString().ToLowerInvariant(); - if (stringBank == @"none" || !Enum.IsDefined(bank)) + if (stringBank == @"none") stringBank = null; string stringAddBank = addBank.ToString().ToLowerInvariant(); - if (stringAddBank == @"none" || !Enum.IsDefined(addBank)) + if (stringAddBank == @"none") stringAddBank = null; bankInfo.BankForNormal = stringBank; From ba518e1da8442d8aaf17a235d227a394f077689e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 19 Sep 2023 20:11:16 +0200 Subject: [PATCH 0609/4092] Fix `StoryboardResourceLookupStore` dying on failure to unmap path Before the introduction of `StoryboardResourceLookupStore`, missing files would softly fail by use of null fallbacks. After the aforementioned class was added, however, the fallbacks would not work anymore if for whatever reason `GetStoragePathFromStoryboardPath()` failed to unmap the storyboard asset name to a storage path. --- .../Drawables/DrawableStoryboard.cs | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index 6931cea81e..c2a58d46ef 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -142,14 +142,32 @@ namespace osu.Game.Storyboards.Drawables public void Dispose() => realmFileStore.Dispose(); - public byte[] Get(string name) => - realmFileStore.Get(storyboard.GetStoragePathFromStoryboardPath(name)); + public byte[] Get(string name) + { + string? storagePath = storyboard.GetStoragePathFromStoryboardPath(name); - public Task GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) => - realmFileStore.GetAsync(storyboard.GetStoragePathFromStoryboardPath(name), cancellationToken); + return string.IsNullOrEmpty(storagePath) + ? null! + : realmFileStore.Get(storagePath); + } - public Stream GetStream(string name) => - realmFileStore.GetStream(storyboard.GetStoragePathFromStoryboardPath(name)); + public Task GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) + { + string? storagePath = storyboard.GetStoragePathFromStoryboardPath(name); + + return string.IsNullOrEmpty(storagePath) + ? Task.FromResult(null!) + : realmFileStore.GetAsync(storagePath, cancellationToken); + } + + public Stream? GetStream(string name) + { + string? storagePath = storyboard.GetStoragePathFromStoryboardPath(name); + + return string.IsNullOrEmpty(storagePath) + ? null + : realmFileStore.GetStream(storagePath); + } public IEnumerable GetAvailableResources() => realmFileStore.GetAvailableResources(); From 641e651bf282aeeb11050756e63e3a98cc136bf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 19 Sep 2023 20:18:33 +0200 Subject: [PATCH 0610/4092] Fix `DrawableStoryboardVideo` attempting to unmap path once too much The `StoryboardResourceLookupStore` cached at storyboard level is supposed to already be handling that; no need for local logic anymore. --- osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs index eec2cd6a60..9a5db4bb39 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs @@ -29,12 +29,7 @@ namespace osu.Game.Storyboards.Drawables [BackgroundDependencyLoader(true)] private void load(IBindable beatmap, TextureStore textureStore) { - string? path = beatmap.Value.BeatmapSetInfo?.GetPathForFile(Video.Path); - - if (path == null) - return; - - var stream = textureStore.GetStream(path); + var stream = textureStore.GetStream(Video.Path); if (stream == null) return; From 333b839e0d7b859aa677a6a27163cb894c7d5563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 19 Sep 2023 21:37:44 +0200 Subject: [PATCH 0611/4092] Fix broken automatic beatmap download setting migration --- osu.Game/Configuration/OsuConfigManager.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index b5253d3500..db71ff4e84 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -64,6 +64,12 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.Username, string.Empty); SetDefault(OsuSetting.Token, string.Empty); +#pragma warning disable CS0618 // Type or member is obsolete + // this default set MUST remain despite the setting being deprecated, because `SetDefault()` calls are implicitly used to declare the type returned for the lookup. + // if this is removed, the setting will be interpreted as a string, and `Migrate()` will fail due to cast failure. + // can be removed 20240618 + SetDefault(OsuSetting.AutomaticallyDownloadWhenSpectating, false); +#pragma warning restore CS0618 // Type or member is obsolete SetDefault(OsuSetting.AutomaticallyDownloadMissingBeatmaps, false); SetDefault(OsuSetting.SavePassword, false).ValueChanged += enabled => @@ -218,7 +224,7 @@ namespace osu.Game.Configuration if (combined < 20230918) { #pragma warning disable CS0618 // Type or member is obsolete - SetValue(OsuSetting.AutomaticallyDownloadMissingBeatmaps, Get(OsuSetting.AutomaticallyDownloadWhenSpectating)); // can be removed 20240618 + SetValue(OsuSetting.AutomaticallyDownloadMissingBeatmaps, Get(OsuSetting.AutomaticallyDownloadWhenSpectating)); // can be removed 20240618 #pragma warning restore CS0618 // Type or member is obsolete } } From 8e16b1d50784e62123d4ab308985b186e3c4ecb3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Sep 2023 12:48:15 +0900 Subject: [PATCH 0612/4092] Simplify some maximum size specs --- .../Skinning/Legacy/LegacyFruitPiece.cs | 13 +++++++++---- .../HitCircles/Components/HitCircleOverlapMarker.cs | 2 +- .../HitCircles/Components/HitCirclePiece.cs | 2 +- .../Objects/Drawables/DrawableHitCircle.cs | 2 +- .../Objects/Drawables/DrawableSliderBall.cs | 2 +- .../Objects/Drawables/DrawableSliderRepeat.cs | 2 +- .../Objects/Drawables/DrawableSliderTail.cs | 2 +- .../Objects/Drawables/DrawableSliderTick.cs | 2 +- osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 5 +++++ .../Skinning/Argon/ArgonMainCirclePiece.cs | 2 +- .../Skinning/Argon/ArgonReverseArrow.cs | 2 +- .../Skinning/Default/CirclePiece.cs | 3 +-- .../Skinning/Default/ExplodePiece.cs | 3 +-- .../Skinning/Default/FlashPiece.cs | 3 +-- .../Skinning/Default/MainCirclePiece.cs | 3 +-- .../Skinning/Default/ReverseArrowPiece.cs | 2 +- osu.Game.Rulesets.Osu/Skinning/Default/RingPiece.cs | 3 +-- .../Skinning/Legacy/LegacyApproachCircle.cs | 2 +- .../Skinning/Legacy/LegacyMainCirclePiece.cs | 9 +++------ .../Skinning/Legacy/LegacyReverseArrow.cs | 3 +-- .../Skinning/Legacy/LegacySliderBall.cs | 5 ++--- .../Skinning/Legacy/OsuLegacySkinTransformer.cs | 4 ++-- .../UI/Cursor/CursorRippleVisualiser.cs | 2 +- 23 files changed, 39 insertions(+), 39 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyFruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyFruitPiece.cs index eacda1dc64..62097d79bd 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyFruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyFruitPiece.cs @@ -26,21 +26,26 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy switch (visualRepresentation) { case FruitVisualRepresentation.Pear: - SetTexture(Skin.GetTexture("fruit-pear")?.WithMaximumSize(fruit_max_size), Skin.GetTexture("fruit-pear-overlay")?.WithMaximumSize(fruit_max_size)); + setTextures("pear"); break; case FruitVisualRepresentation.Grape: - SetTexture(Skin.GetTexture("fruit-grapes")?.WithMaximumSize(fruit_max_size), Skin.GetTexture("fruit-grapes-overlay")?.WithMaximumSize(fruit_max_size)); + setTextures("grapes"); break; case FruitVisualRepresentation.Pineapple: - SetTexture(Skin.GetTexture("fruit-apple")?.WithMaximumSize(fruit_max_size), Skin.GetTexture("fruit-apple-overlay")?.WithMaximumSize(fruit_max_size)); + setTextures("apple"); break; case FruitVisualRepresentation.Raspberry: - SetTexture(Skin.GetTexture("fruit-orange")?.WithMaximumSize(fruit_max_size), Skin.GetTexture("fruit-orange-overlay")?.WithMaximumSize(fruit_max_size)); + setTextures("orange"); break; } + + void setTextures(string fruitName) => SetTexture( + Skin.GetTexture($"fruit-{fruitName}")?.WithMaximumSize(fruit_max_size), + Skin.GetTexture($"fruit-{fruitName}-overlay")?.WithMaximumSize(fruit_max_size) + ); } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCircleOverlapMarker.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCircleOverlapMarker.cs index e5cc8595d1..3cba0610a1 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCircleOverlapMarker.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCircleOverlapMarker.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components { Origin = Anchor.Centre; - Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); + Size = OsuHitObject.OBJECT_DIMENSIONS; InternalChild = content = new Container { diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs index 670e98ca50..c585f09b00 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components { Origin = Anchor.Centre; - Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); + Size = OsuHitObject.OBJECT_DIMENSIONS; CornerRadius = Size.X / 2; CornerExponent = 2; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 3458069dd1..999979c491 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -242,7 +242,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public HitReceptor() { - Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); + Size = OsuHitObject.OBJECT_DIMENSIONS; Anchor = Anchor.Centre; Origin = Anchor.Centre; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs index d06fb5b4de..47214f1e53 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Origin = Anchor.Centre; - Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); + Size = OsuHitObject.OBJECT_DIMENSIONS; Children = new[] { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index fc4863f164..5721328057 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private void load() { Origin = Anchor.Centre; - Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); + Size = OsuHitObject.OBJECT_DIMENSIONS; AddInternal(scaleContainer = new Container { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index d9501f7d58..9fbc97c484 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private void load() { Origin = Anchor.Centre; - Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); + Size = OsuHitObject.OBJECT_DIMENSIONS; AddRangeInternal(new Drawable[] { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index 6d0ae93e62..a947580d2f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables [BackgroundDependencyLoader] private void load() { - Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); + Size = OsuHitObject.OBJECT_DIMENSIONS; Origin = Anchor.Centre; AddInternal(scaleContainer = new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.SliderScorePoint), _ => new CircularContainer diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index fd5741698a..0bdbfaa760 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -21,6 +21,11 @@ namespace osu.Game.Rulesets.Osu.Objects /// public const float OBJECT_RADIUS = 64; + /// + /// The width and height any element participating in display of a hitcircle (or similarly sized object) should be. + /// + public static readonly Vector2 OBJECT_DIMENSIONS = new Vector2(OBJECT_RADIUS * 2); + /// /// Scoring distance with a speed-adjusted beat length of 1 second (ie. the speed slider balls move through their track). /// diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs index 3427031dc8..7508a689d2 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon private Bindable configHitLighting = null!; - private static readonly Vector2 circle_size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); + private static readonly Vector2 circle_size = OsuHitObject.OBJECT_DIMENSIONS; [Resolved] private DrawableHitObject drawableObject { get; set; } = null!; diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs index f93e26b2ca..67fc1b2304 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon Anchor = Anchor.Centre; Origin = Anchor.Centre; - Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); + Size = OsuHitObject.OBJECT_DIMENSIONS; InternalChildren = new Drawable[] { diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/CirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/CirclePiece.cs index f4761e0ea8..65a7b1328b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/CirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/CirclePiece.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; -using osuTK; namespace osu.Game.Rulesets.Osu.Skinning.Default { @@ -22,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default public CirclePiece() { - Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); + Size = OsuHitObject.OBJECT_DIMENSIONS; Masking = true; CornerRadius = Size.X / 2; diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/ExplodePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/ExplodePiece.cs index 91bf75617a..7beb16f7d7 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/ExplodePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/ExplodePiece.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; -using osuTK; namespace osu.Game.Rulesets.Osu.Skinning.Default { @@ -20,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default public ExplodePiece() { - Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); + Size = OsuHitObject.OBJECT_DIMENSIONS; Anchor = Anchor.Centre; Origin = Anchor.Centre; diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/FlashPiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/FlashPiece.cs index 789137117e..86087ac50d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/FlashPiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/FlashPiece.cs @@ -5,7 +5,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Osu.Objects; -using osuTK; namespace osu.Game.Rulesets.Osu.Skinning.Default { @@ -13,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { public FlashPiece() { - Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); + Size = OsuHitObject.OBJECT_DIMENSIONS; Anchor = Anchor.Centre; Origin = Anchor.Centre; diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs index 20fa4e5342..bcea33f63c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Default @@ -25,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default public MainCirclePiece() { - Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); + Size = OsuHitObject.OBJECT_DIMENSIONS; Anchor = Anchor.Centre; Origin = Anchor.Centre; diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/ReverseArrowPiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/ReverseArrowPiece.cs index 3fe7872ff7..27868db2f6 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/ReverseArrowPiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/ReverseArrowPiece.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default Anchor = Anchor.Centre; Origin = Anchor.Centre; - Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); + Size = OsuHitObject.OBJECT_DIMENSIONS; Child = new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.ReverseArrow), _ => new SpriteIcon { diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/RingPiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/RingPiece.cs index 46d48f62e7..c3bbd89ab6 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/RingPiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/RingPiece.cs @@ -5,7 +5,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Osu.Objects; -using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Default @@ -14,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { public RingPiece(float thickness = 9) { - Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); + Size = OsuHitObject.OBJECT_DIMENSIONS; Anchor = Anchor.Centre; Origin = Anchor.Centre; diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyApproachCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyApproachCircle.cs index cdc61ebd9b..403a14214e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyApproachCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyApproachCircle.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private DrawableHitObject drawableObject { get; set; } = null!; public LegacyApproachCircle() - : base("Gameplay/osu/approachcircle", new Vector2(OsuHitObject.OBJECT_RADIUS * 2)) + : base("Gameplay/osu/approachcircle", OsuHitObject.OBJECT_DIMENSIONS) { } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs index 18010cdb2c..8990204931 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs @@ -14,15 +14,12 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Skinning; -using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { public partial class LegacyMainCirclePiece : CompositeDrawable { - private static readonly Vector2 circle_piece_size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); - public override bool RemoveCompletedTransforms => false; /// @@ -53,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy this.priorityLookupPrefix = priorityLookupPrefix; this.hasNumber = hasNumber; - Size = circle_piece_size; + Size = OsuHitObject.OBJECT_DIMENSIONS; } [BackgroundDependencyLoader] @@ -70,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy // expected behaviour in this scenario is not showing the overlay, rather than using hitcircleoverlay.png. InternalChildren = new[] { - CircleSprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = skin.GetTexture(circleName)?.WithMaximumSize(circle_piece_size) }) + CircleSprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = skin.GetTexture(circleName)?.WithMaximumSize(OsuHitObject.OBJECT_DIMENSIONS) }) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -79,7 +76,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Child = OverlaySprite = new LegacyKiaiFlashingDrawable(() => skin.GetAnimation(@$"{circleName}overlay", true, true, frameLength: 1000 / 2d, maxSize: circle_piece_size)) + Child = OverlaySprite = new LegacyKiaiFlashingDrawable(() => skin.GetAnimation(@$"{circleName}overlay", true, true, frameLength: 1000 / 2d, maxSize: OsuHitObject.OBJECT_DIMENSIONS)) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs index 293df6b3a0..3a80607522 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs @@ -10,7 +10,6 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Skinning; -using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Legacy @@ -37,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy var skin = skinSource.FindProvider(s => s.GetTexture(lookupName) != null); - InternalChild = arrow = (skin?.GetAnimation(lookupName, true, true, maxSize: new Vector2(OsuHitObject.OBJECT_RADIUS * 2)) ?? Empty()); + InternalChild = arrow = (skin?.GetAnimation(lookupName, true, true, maxSize: OsuHitObject.OBJECT_DIMENSIONS) ?? Empty()); textureIsDefaultSkin = skin is ISkinTransformer transformer && transformer.Skin is DefaultLegacySkin; } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs index 145a8a50af..c3beb5bc35 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs @@ -10,7 +10,6 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Skinning; -using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Legacy @@ -48,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Texture = skin.GetTexture("sliderb-nd")?.WithMaximumSize(new Vector2(OsuHitObject.OBJECT_RADIUS * 2)), + Texture = skin.GetTexture("sliderb-nd")?.WithMaximumSize(OsuHitObject.OBJECT_DIMENSIONS), Colour = new Color4(5, 5, 5, 255), }, LegacyColourCompatibility.ApplyWithDoubledAlpha(animationContent.With(d => @@ -60,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Texture = skin.GetTexture("sliderb-spec")?.WithMaximumSize(new Vector2(OsuHitObject.OBJECT_RADIUS * 2)), + Texture = skin.GetTexture("sliderb-spec")?.WithMaximumSize(OsuHitObject.OBJECT_DIMENSIONS), Blending = BlendingParameters.Additive, }, }; diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 2564dbf335..ea6f6fe6ce 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return null; case OsuSkinComponents.SliderBall: - var sliderBallContent = this.GetAnimation("sliderb", true, true, animationSeparator: "", maxSize: new Vector2(OsuHitObject.OBJECT_RADIUS * 2)); + var sliderBallContent = this.GetAnimation("sliderb", true, true, animationSeparator: "", maxSize: OsuHitObject.OBJECT_DIMENSIONS); // todo: slider ball has a custom frame delay based on velocity // Math.Max((150 / Velocity) * GameBase.SIXTY_FRAME_TIME, GameBase.SIXTY_FRAME_TIME); @@ -139,7 +139,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy if (!this.HasFont(LegacyFont.HitCircle)) return null; - return new LegacySpriteText(LegacyFont.HitCircle, new Vector2(OsuHitObject.OBJECT_RADIUS * 2)) + return new LegacySpriteText(LegacyFont.HitCircle, OsuHitObject.OBJECT_DIMENSIONS) { // stable applies a blanket 0.8x scale to hitcircle fonts Scale = new Vector2(0.8f), diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs index 076d97d06a..52486b701a 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs @@ -95,7 +95,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { new RingPiece(3) { - Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2), + Size = OsuHitObject.OBJECT_DIMENSIONS, Alpha = 0.1f, } }; From 50adb5f7a7991dcfd84eba98bb28155b65e32175 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Sep 2023 12:54:28 +0900 Subject: [PATCH 0613/4092] Remove incorrectly merge conflict resolved --- osu.Game/Storyboards/Drawables/DrawableStoryboard.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index a11251ed22..352246c533 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -23,8 +23,6 @@ namespace osu.Game.Storyboards.Drawables { public partial class DrawableStoryboard : Container { - public Vector2 AppliedScale { get; private set; } - [Cached(typeof(Storyboard))] public Storyboard Storyboard { get; } From b5e64d933c9d09196c0553d548e5dde0bbec8ed2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Sep 2023 12:54:36 +0900 Subject: [PATCH 0614/4092] Apply same fix to `DrawableStoryboardAnimation` --- .../Drawables/DrawableStoryboardAnimation.cs | 43 ++++++++++++------- .../Drawables/DrawableStoryboardSprite.cs | 6 +-- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index 054a50456b..33f7a3c6f2 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -94,25 +94,19 @@ namespace osu.Game.Storyboards.Drawables [Resolved] private IBeatSyncProvider beatSyncProvider { get; set; } + [Resolved] + private TextureStore textureStore { get; set; } + [BackgroundDependencyLoader] - private void load(TextureStore textureStore, Storyboard storyboard) + private void load(Storyboard storyboard) { - int frameIndex = 0; - - Texture frameTexture = textureStore.Get(getFramePath(frameIndex)); - - if (frameTexture != null) + if (storyboard.UseSkinSprites) { - // sourcing from storyboard. - for (frameIndex = 0; frameIndex < Animation.FrameCount; frameIndex++) - AddFrame(textureStore.Get(getFramePath(frameIndex)), Animation.FrameDelay); - } - else if (storyboard.UseSkinSprites) - { - // fallback to skin if required. skin.SourceChanged += skinSourceChanged; skinSourceChanged(); } + else + addFramesFromStoryboardSource(); Animation.ApplyTransforms(this); } @@ -135,11 +129,28 @@ namespace osu.Game.Storyboards.Drawables // When reading from a skin, we match stables weird behaviour where `FrameCount` is ignored // and resources are retrieved until the end of the animation. - foreach (var texture in skin.GetTextures(Path.GetFileNameWithoutExtension(Animation.Path)!, default, default, true, string.Empty, out _)) - AddFrame(texture, Animation.FrameDelay); + var skinTextures = skin.GetTextures(Path.GetFileNameWithoutExtension(Animation.Path)!, default, default, true, string.Empty, out _); + + if (skinTextures.Length > 0) + { + foreach (var texture in skinTextures) + AddFrame(texture, Animation.FrameDelay); + } + else + { + addFramesFromStoryboardSource(); + } } - private string getFramePath(int i) => Animation.Path.Replace(".", $"{i}."); + private void addFramesFromStoryboardSource() + { + int frameIndex; + // sourcing from storyboard. + for (frameIndex = 0; frameIndex < Animation.FrameCount; frameIndex++) + AddFrame(textureStore.Get(getFramePath(frameIndex)), Animation.FrameDelay); + + string getFramePath(int i) => Animation.Path.Replace(".", $"{i}."); + } protected override void Dispose(bool isDisposing) { diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index 14132654d1..ad344b6bd4 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.ComponentModel; using osu.Framework.Allocation; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; @@ -78,9 +77,6 @@ namespace osu.Game.Storyboards.Drawables [Resolved] private ISkinSource skin { get; set; } = null!; - [Resolved] - private Storyboard storyboard { get; set; } = null!; - [Resolved] private TextureStore textureStore { get; set; } = null!; @@ -95,7 +91,7 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader] - private void load() + private void load(Storyboard storyboard) { if (storyboard.UseSkinSprites) { From bd66285bd47859569618a26eebbf2563d65d9f93 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Sep 2023 12:59:40 +0900 Subject: [PATCH 0615/4092] Rename parameter on `LegacySpriteText` to better imply the maximum size is per glyph --- osu.Game/Skinning/LegacySpriteText.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/LegacySpriteText.cs b/osu.Game/Skinning/LegacySpriteText.cs index f021a99102..7eb92126fa 100644 --- a/osu.Game/Skinning/LegacySpriteText.cs +++ b/osu.Game/Skinning/LegacySpriteText.cs @@ -13,7 +13,7 @@ namespace osu.Game.Skinning public sealed partial class LegacySpriteText : OsuSpriteText { private readonly LegacyFont font; - private readonly Vector2? maxSize; + private readonly Vector2? maxSizePerGlyph; private LegacyGlyphStore glyphStore = null!; @@ -21,10 +21,10 @@ namespace osu.Game.Skinning protected override char[] FixedWidthExcludeCharacters => new[] { ',', '.', '%', 'x' }; - public LegacySpriteText(LegacyFont font, Vector2? maxSize = null) + public LegacySpriteText(LegacyFont font, Vector2? maxSizePerGlyph = null) { this.font = font; - this.maxSize = maxSize; + this.maxSizePerGlyph = maxSizePerGlyph; Shadow = false; UseFullGlyphHeight = false; @@ -36,7 +36,7 @@ namespace osu.Game.Skinning Font = new FontUsage(skin.GetFontPrefix(font), 1, fixedWidth: true); Spacing = new Vector2(-skin.GetFontOverlap(font), 0); - glyphStore = new LegacyGlyphStore(skin, maxSize); + glyphStore = new LegacyGlyphStore(skin, maxSizePerGlyph); } protected override TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) => base.CreateTextBuilder(glyphStore); From 1316403180f1f4f095e71e205221eead5a481912 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Sep 2023 13:02:40 +0900 Subject: [PATCH 0616/4092] Fix inspection in new test scene --- .../Visual/Gameplay/TestSceneGameplayElementDimensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayElementDimensions.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayElementDimensions.cs index 20e6e5658c..ff7cf2a124 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayElementDimensions.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayElementDimensions.cs @@ -66,7 +66,7 @@ namespace osu.Game.Tests.Visual.Gameplay return texture; } - public ISkin? FindProvider(Func lookupFunction) => this; + public ISkin FindProvider(Func lookupFunction) => this; public IEnumerable AllSources => new[] { this }; } } From 71ac5cfc792a2aec35720e08f6d02d87e69995be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Sep 2023 14:14:37 +0900 Subject: [PATCH 0617/4092] Don't bother binding to friends changes for score display purposes --- osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs index 502303e80c..7471955493 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs @@ -13,7 +13,6 @@ using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.API; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Scoring; using osu.Game.Users; using osu.Game.Users.Drawables; @@ -110,7 +109,7 @@ namespace osu.Game.Screens.Play.HUD private IBindable scoreDisplayMode = null!; - private readonly IBindableList apiFriends = new BindableList(); + private bool isFriend; /// /// Creates a new . @@ -317,8 +316,7 @@ namespace osu.Game.Screens.Play.HUD HasQuit.BindValueChanged(_ => updateState()); - apiFriends.BindTo(api.Friends); - apiFriends.BindCollectionChanged((_, _) => updateState()); + isFriend = User != null && api.Friends.Any(u => User.OnlineID == u.Id); } protected override void LoadComplete() @@ -397,7 +395,7 @@ namespace osu.Game.Screens.Play.HUD panelColour = BackgroundColour ?? Color4Extensions.FromHex("ffd966"); textColour = TextColour ?? Color4Extensions.FromHex("2e576b"); } - else if (apiFriends.Any(f => User?.Equals(f) == true)) + else if (isFriend) { panelColour = BackgroundColour ?? Color4Extensions.FromHex("ff549a"); textColour = TextColour ?? Color4.White; From c6cc858967ccc3068aa0b19a7e308451b6e16d97 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Sep 2023 15:27:30 +0900 Subject: [PATCH 0618/4092] Change implementation of "show speed changes" to require explicit ruleset support --- .../Edit/ScrollingHitObjectComposer.cs | 37 +++++++------------ .../ISupportConstantAlgorithmToggle.cs | 15 ++++++++ 2 files changed, 28 insertions(+), 24 deletions(-) create mode 100644 osu.Game/Rulesets/UI/Scrolling/ISupportConstantAlgorithmToggle.cs diff --git a/osu.Game/Rulesets/Edit/ScrollingHitObjectComposer.cs b/osu.Game/Rulesets/Edit/ScrollingHitObjectComposer.cs index 0340354016..75305a0c20 100644 --- a/osu.Game/Rulesets/Edit/ScrollingHitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/ScrollingHitObjectComposer.cs @@ -6,7 +6,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI.Scrolling; @@ -28,34 +27,24 @@ namespace osu.Game.Rulesets.Edit [BackgroundDependencyLoader] private void load() { - if (DrawableRuleset is DrawableScrollingRuleset drawableScrollingRuleset) + if (DrawableRuleset is ISupportConstantAlgorithmToggle toggleRuleset) { - var originalVisualisationMethod = drawableScrollingRuleset.VisualisationMethod; - - if (originalVisualisationMethod != ScrollVisualisationMethod.Constant) + LeftToolbox.Add(new EditorToolboxGroup("playfield") { - LeftToolbox.Add(new EditorToolboxGroup("playfield") + Child = new FillFlowContainer { - Child = new FillFlowContainer + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + Children = new[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5), - Children = new[] - { - new DrawableTernaryButton(new TernaryButton(showSpeedChanges, "Show speed changes", () => new SpriteIcon { Icon = FontAwesome.Solid.TachometerAlt })) - } - }, - }); + new DrawableTernaryButton(new TernaryButton(showSpeedChanges, "Show speed changes", () => new SpriteIcon { Icon = FontAwesome.Solid.TachometerAlt })) + } + }, + }); - showSpeedChanges.BindValueChanged(state => - { - drawableScrollingRuleset.VisualisationMethod = state.NewValue == TernaryState.True - ? originalVisualisationMethod - : ScrollVisualisationMethod.Constant; - }, true); - } + showSpeedChanges.BindValueChanged(state => toggleRuleset.ShowSpeedChanges.Value = state.NewValue == TernaryState.True, true); } } } diff --git a/osu.Game/Rulesets/UI/Scrolling/ISupportConstantAlgorithmToggle.cs b/osu.Game/Rulesets/UI/Scrolling/ISupportConstantAlgorithmToggle.cs new file mode 100644 index 0000000000..aaa635350e --- /dev/null +++ b/osu.Game/Rulesets/UI/Scrolling/ISupportConstantAlgorithmToggle.cs @@ -0,0 +1,15 @@ +// 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; + +namespace osu.Game.Rulesets.UI.Scrolling +{ + /// + /// Denotes a which supports toggling constant algorithm for better display in the editor. + /// + public interface ISupportConstantAlgorithmToggle : IDrawableScrollingRuleset + { + public BindableBool ShowSpeedChanges { get; } + } +} From 41a8239e49d87fec622135874bddcf2660ae0000 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Sep 2023 15:27:50 +0900 Subject: [PATCH 0619/4092] Remvoe null default for mods which can't be null --- osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs | 2 +- osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs | 2 +- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 2 +- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs index 136a78b343..d74e6194fb 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs @@ -141,7 +141,7 @@ namespace osu.Game.Rulesets.Catch.Edit return base.OnPressed(e); } - protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList? mods = null) => + protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods) => new DrawableCatchEditorRuleset(ruleset, beatmap, mods) { TimeRangeMultiplier = { BindTarget = timeRangeMultiplier, } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 9bde9485b2..8e61baca81 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Mania.Edit protected override Playfield PlayfieldAtScreenSpacePosition(Vector2 screenSpacePosition) => Playfield.GetColumnByPosition(screenSpacePosition); - protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) + protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods) { drawableRuleset = new DrawableManiaEditorRuleset(ruleset, beatmap, mods); diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index cff2171cbd..fdc11be42c 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Edit { } - protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) + protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods) => new DrawableOsuEditorRuleset(ruleset, beatmap, mods); protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 295a016c7b..f9a6b5083e 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -307,7 +307,7 @@ namespace osu.Game.Rulesets.Edit /// The loaded beatmap. /// The mods to be applied. /// An editor-relevant . - protected virtual DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) + protected virtual DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods) => (DrawableRuleset)ruleset.CreateDrawableRulesetWith(beatmap, mods); #region Tool selection logic From cb0226f84356ae0fe991cae3664590b3c6dcc708 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Sep 2023 15:28:13 +0900 Subject: [PATCH 0620/4092] Implement new interface-based speed change visualisation support on mania/taiko --- .../Edit/DrawableManiaEditorRuleset.cs | 13 ++++++- .../Edit/DrawableTaikoEditorRuleset.cs | 37 +++++++++++++++++++ .../Edit/TaikoHitObjectComposer.cs | 6 +++ .../UI/DrawableTaikoRuleset.cs | 7 +++- 4 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/Edit/DrawableTaikoEditorRuleset.cs diff --git a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditorRuleset.cs b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditorRuleset.cs index 1741dad5d6..7b019a2bdf 100644 --- a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditorRuleset.cs +++ b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditorRuleset.cs @@ -2,8 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; @@ -12,8 +14,10 @@ using osuTK; namespace osu.Game.Rulesets.Mania.Edit { - public partial class DrawableManiaEditorRuleset : DrawableManiaRuleset + public partial class DrawableManiaEditorRuleset : DrawableManiaRuleset, ISupportConstantAlgorithmToggle { + public BindableBool ShowSpeedChanges { get; set; } = new BindableBool(); + public new IScrollingInfo ScrollingInfo => base.ScrollingInfo; public DrawableManiaEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList? mods) @@ -21,6 +25,13 @@ namespace osu.Game.Rulesets.Mania.Edit { } + protected override void LoadComplete() + { + base.LoadComplete(); + + ShowSpeedChanges.BindValueChanged(showChanges => VisualisationMethod = showChanges.NewValue ? ScrollVisualisationMethod.Sequential : ScrollVisualisationMethod.Constant, true); + } + protected override Playfield CreatePlayfield() => new ManiaEditorPlayfield(Beatmap.Stages) { Anchor = Anchor.Centre, diff --git a/osu.Game.Rulesets.Taiko/Edit/DrawableTaikoEditorRuleset.cs b/osu.Game.Rulesets.Taiko/Edit/DrawableTaikoEditorRuleset.cs new file mode 100644 index 0000000000..963ddec0b3 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Edit/DrawableTaikoEditorRuleset.cs @@ -0,0 +1,37 @@ +// 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 osu.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Taiko.UI; +using osu.Game.Rulesets.UI.Scrolling; + +namespace osu.Game.Rulesets.Taiko.Edit +{ + public partial class DrawableTaikoEditorRuleset : DrawableTaikoRuleset, ISupportConstantAlgorithmToggle + { + public BindableBool ShowSpeedChanges { get; set; } = new BindableBool(); + + public DrawableTaikoEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods) + : base(ruleset, beatmap, mods) + { + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ShowSpeedChanges.BindValueChanged(showChanges => VisualisationMethod = showChanges.NewValue ? ScrollVisualisationMethod.Overlapping : ScrollVisualisationMethod.Constant, true); + } + + protected override double ComputeTimeRange() + { + // Adjust when we're using constant algorithm to not be sluggish. + double multiplier = ShowSpeedChanges.Value ? 1 : 4; + return base.ComputeTimeRange() / multiplier; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs index fbad8c7fad..5ae4757b8f 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs @@ -2,9 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Compose.Components; namespace osu.Game.Rulesets.Taiko.Edit @@ -25,6 +28,9 @@ namespace osu.Game.Rulesets.Taiko.Edit new SwellCompositionTool() }; + protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods) => + new DrawableTaikoEditorRuleset(ruleset, beatmap, mods); + protected override ComposeBlueprintContainer CreateBlueprintContainer() => new TaikoBlueprintContainer(this); } diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 979e03f201..2af4c0c2e8 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -64,6 +64,11 @@ namespace osu.Game.Rulesets.Taiko.UI { base.Update(); + TimeRange.Value = ComputeTimeRange(); + } + + protected virtual double ComputeTimeRange() + { // Taiko scrolls at a constant 100px per 1000ms. More notes become visible as the playfield is lengthened. const float scroll_rate = 10; @@ -72,7 +77,7 @@ namespace osu.Game.Rulesets.Taiko.UI // We clamp the ratio to the maximum aspect ratio to keep scroll speed consistent on widths lower than the default. float ratio = Math.Max(DrawSize.X / 768f, TaikoPlayfieldAdjustmentContainer.MAXIMUM_ASPECT); - TimeRange.Value = (Playfield.HitObjectContainer.DrawWidth / ratio) * scroll_rate; + return (Playfield.HitObjectContainer.DrawWidth / ratio) * scroll_rate; } protected override void UpdateAfterChildren() From d7129da8ea708f5485e023a18012940b1d5d3dfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 20 Sep 2023 12:05:23 +0200 Subject: [PATCH 0621/4092] Fix `TestSceneDrawableStoryboardSprite` not displaying anything --- .../Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs index d20c7c2f7a..32693c2bb2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs @@ -180,7 +180,7 @@ namespace osu.Game.Tests.Visual.Gameplay layer.Elements.Clear(); layer.Add(sprite); - return storyboard.CreateDrawable(); + return storyboard.CreateDrawable().With(s => s.RelativeSizeAxes = Axes.Both); } private void assertStoryboardSourced() From f2791d4f3e1c3067d8a2b9fcbab013edcd5eeefb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 20 Sep 2023 12:22:05 +0200 Subject: [PATCH 0622/4092] Move comment a bit to fix formatting Would otherwise trigger IDE0055, but that isn't resolveable without an inspection cycle with resharper, so just move in a more sane place. --- osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs index d74e6194fb..dc3a4416a5 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs @@ -25,8 +25,8 @@ using osuTK; namespace osu.Game.Rulesets.Catch.Edit { + // we're also a ScrollingHitObjectComposer candidate, but can't be everything can we? public partial class CatchHitObjectComposer : DistancedHitObjectComposer - // we're also a ScrollingHitObjectComposer candidate, but can't be everything can we? { private const float distance_snap_radius = 50; From bf984388b364ba4a3a35139df08b09fa0aac93c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Sep 2023 19:12:55 +0900 Subject: [PATCH 0623/4092] Update clocks in line with framework changes --- osu.Game/Beatmaps/FramedBeatmapClock.cs | 2 -- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 2 -- osu.Game/Screens/Edit/EditorClock.cs | 2 -- osu.Game/Screens/Play/GameplayClockContainer.cs | 2 -- 4 files changed, 8 deletions(-) diff --git a/osu.Game/Beatmaps/FramedBeatmapClock.cs b/osu.Game/Beatmaps/FramedBeatmapClock.cs index 9577d1e38b..62484fa12b 100644 --- a/osu.Game/Beatmaps/FramedBeatmapClock.cs +++ b/osu.Game/Beatmaps/FramedBeatmapClock.cs @@ -216,8 +216,6 @@ namespace osu.Game.Beatmaps public double FramesPerSecond => finalClockSource.FramesPerSecond; - public FrameTimeInfo TimeInfo => finalClockSource.TimeInfo; - #endregion protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 90cffab714..2af9916a6b 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -264,8 +264,6 @@ namespace osu.Game.Rulesets.UI public double FramesPerSecond => framedClock.FramesPerSecond; - public FrameTimeInfo TimeInfo => framedClock.TimeInfo; - public double StartTime => parentGameplayClock?.StartTime ?? 0; private readonly AudioAdjustments gameplayAdjustments = new AudioAdjustments(); diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index e5e88a04d9..a05a873101 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -231,8 +231,6 @@ namespace osu.Game.Screens.Edit public double FramesPerSecond => underlyingClock.FramesPerSecond; - public FrameTimeInfo TimeInfo => underlyingClock.TimeInfo; - public void ChangeSource(IClock source) { track.Value = source as Track; diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 20bf6c3829..2478af1dd4 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -234,7 +234,5 @@ namespace osu.Game.Screens.Play public double ElapsedFrameTime => GameplayClock.ElapsedFrameTime; public double FramesPerSecond => GameplayClock.FramesPerSecond; - - public FrameTimeInfo TimeInfo => GameplayClock.TimeInfo; } } From 8a3d412ffc3374c4ad99cd4b26b00edf77e4cf35 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Sep 2023 11:38:16 +0900 Subject: [PATCH 0624/4092] Remove mention of no-heated-gameplay-mechanics discussions We're kinda at the point we're allowing this now. --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 792e2d646a..ce9fe4d053 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,6 @@ The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Curre This project is under constant development, but we aim to keep things in a stable state. Users are encouraged to try it out and keep it installed alongside the stable *osu!* client. It will continue to evolve to the point of eventually replacing the existing stable client as an update. -**IMPORTANT:** Gameplay mechanics (and other features which you may have come to know and love) are in a constant state of flux. Game balance and final quality-of-life passes come at the end of development, preceded by experimentation and changes which may potentially **reduce playability or usability**. This is done in order to allow us to move forward as developers and designers more efficiently. If this offends you, please consider sticking to a [stable release](https://osu.ppy.sh/home/download) of osu!. We are not yet open to heated discussion over game mechanics and will not be using github as a forum for such discussions just yet. - We are accepting bug reports (please report with as much detail as possible and follow the existing issue templates). Feature requests are also welcome, but understand that our focus is on completing the game to feature parity before adding new features. A few resources are available as starting points to getting involved and understanding the project: - Detailed release changelogs are available on the [official osu! site](https://osu.ppy.sh/home/changelog/lazer). From 2954ad78349e126482c02687f98fcf00055314ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Sep 2023 11:56:23 +0900 Subject: [PATCH 0625/4092] Update language across whole readme to read better --- README.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index ce9fe4d053..a9dac3d6cf 100644 --- a/README.md +++ b/README.md @@ -12,33 +12,35 @@ A free-to-win rhythm game. Rhythm is just a *click* away! -The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Currently known by and released under the release codename "*lazer*". As in sharper than cutting-edge. +This is the future – and final – iteration of the [osu!](https://osu.ppy.sh) game client and marks the beginning of an open era! Currently known by and released under the release codename "*lazer*". As in sharper than cutting-edge. ## Status -This project is under constant development, but we aim to keep things in a stable state. Users are encouraged to try it out and keep it installed alongside the stable *osu!* client. It will continue to evolve to the point of eventually replacing the existing stable client as an update. +This project is under constant development, but we do our best to keep things in a stable state. Players are encouraged to install from a release alongside their stable *osu!* client. This project will continue to evolve until we eventually reach the point where most users prefer it over the previous "osu!stable" release. -We are accepting bug reports (please report with as much detail as possible and follow the existing issue templates). Feature requests are also welcome, but understand that our focus is on completing the game to feature parity before adding new features. A few resources are available as starting points to getting involved and understanding the project: +A few resources are available as starting points to getting involved and understanding the project: - Detailed release changelogs are available on the [official osu! site](https://osu.ppy.sh/home/changelog/lazer). - You can learn more about our approach to [project management](https://github.com/ppy/osu/wiki/Project-management). ## Running osu! -If you are looking to install or test osu! without setting up a development environment, you can consume our [releases](https://github.com/ppy/osu/releases). You can also generally download a version for your current device from the [osu! site](https://osu.ppy.sh/home/download). Failing that, you may use the links below to download the latest version for your operating system of choice: +If you are just looking to give the game a whirl, you can grab the latest release for your platform: -**Latest release:** +### Latest release: | [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | macOS 10.15+ ([Intel](https://github.com/ppy/osu/releases/latest/download/osu.app.Intel.zip), [Apple Silicon](https://github.com/ppy/osu/releases/latest/download/osu.app.Apple.Silicon.zip)) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 13.4+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) | | ------------- | ------------- | ------------- | ------------- | ------------- | -- The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets. +You can also generally download a version for your current device from the [osu! site](https://osu.ppy.sh/home/download) If your platform is not listed above, there is still a chance you can manually build it by following the instructions below. +**For iOS/iPadOS users**: The iOS testflight link fills up very fast (Apple has a hard limit of 10,000 users). We reset it occasionally. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements. Our goal is to get the game on mobile app stores in early 2024. + ## Developing a custom ruleset -osu! is designed to have extensible modular gameplay modes, called "rulesets". Building one of these allows a developer to harness the power of osu! for their own game style. To get started working on a ruleset, we have some templates available [here](https://github.com/ppy/osu/tree/master/Templates). +osu! is designed to allow user-created gameplay variations, called "rulesets". Building one of these allows a developer to harness the power of the osu! beatmap library, game engine, and general UX for a new style of gameplay. To get started working on a ruleset, we have some templates available [here](https://github.com/ppy/osu/tree/master/Templates). You can see some examples of custom rulesets by visiting the [custom ruleset directory](https://github.com/ppy/osu/discussions/13096). From c76853c32c6267110657ef7c31c81f58e47d7191 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Sep 2023 11:56:32 +0900 Subject: [PATCH 0626/4092] Add mention of new project --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a9dac3d6cf..2cf3b4bf6b 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ A few resources are available as starting points to getting involved and underst - Detailed release changelogs are available on the [official osu! site](https://osu.ppy.sh/home/changelog/lazer). - You can learn more about our approach to [project management](https://github.com/ppy/osu/wiki/Project-management). +- Track our current efforts [towards full "ranked play" support](https://github.com/orgs/ppy/projects/13?query=is%3Aopen+sort%3Aupdated-desc) ## Running osu! From fc6abae968011aecada1d7f81484b78f2a2b0d64 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Sep 2023 11:56:49 +0900 Subject: [PATCH 0627/4092] Remove note about `dotnet` CLI tools not working (less relevant post-EF) --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 2cf3b4bf6b..3966a9258a 100644 --- a/README.md +++ b/README.md @@ -84,8 +84,6 @@ If you are not interested in debugging *osu!*, you can add `-c Release` to gain If the build fails, try to restore NuGet packages with `dotnet restore`. -_Due to a historical feature gap between .NET Core and Xamarin, running `dotnet` CLI from the root directory will not work for most commands. This can be resolved by specifying a target `.csproj` or the helper project at `build/Desktop.proj`. Configurations have been provided to work around this issue for all supported IDEs mentioned above._ - ### Testing with resource/framework modifications Sometimes it may be necessary to cross-test changes in [osu-resources](https://github.com/ppy/osu-resources) or [osu-framework](https://github.com/ppy/osu-framework). This can be quickly achieved using included commands: From 9629f49afbd26dcf65674b6cf0a2672e67018920 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Sep 2023 11:57:10 +0900 Subject: [PATCH 0628/4092] Update build instructions to be more clear about `slnf` files and mention `workload`s --- README.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3966a9258a..6009ff4d59 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,8 @@ You can see some examples of custom rulesets by visiting the [custom ruleset dir ## Developing osu! +### Prerequisites + Please make sure you have the following prerequisites: - A desktop platform with the [.NET 6.0 SDK](https://dotnet.microsoft.com/download) installed. @@ -70,9 +72,19 @@ git pull ### Building -Build configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `VisualTests` project/configuration. More information on this is provided [below](#contributing). +#### From an IDE -- Visual Studio / Rider users should load the project via one of the platform-specific `.slnf` files, rather than the main `.sln`. This will allow access to template run configurations. +You should load the solution via one of the platform-specific `.slnf` files, rather than the main `.sln`. This will reduce dependencies and hide platforms that you don't care about. Valid `.slnf` files are: + +- `osu.Desktop.slnf` (most common) +- `osu.Android.slnf` +- `osu.iOS.slnf` + +Run configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `VisualTests` project/configuration. More information on this is provided [below](#contributing). + +To build for mobile platforms, you will likely need to run `sudo dotnet workload restore` if you haven't done so previously. This will install android/iOS tooling required to complete the build. + +#### From CLI You can also build and run *osu!* from the command-line with a single command: From 262916787ecfde13d2499aa733611a845fa13e13 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Sep 2023 13:27:00 +0900 Subject: [PATCH 0629/4092] Apply punctuation and terminology fixes Co-authored-by: Joseph Madamba --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6009ff4d59..946a6b03d9 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ A few resources are available as starting points to getting involved and underst - Detailed release changelogs are available on the [official osu! site](https://osu.ppy.sh/home/changelog/lazer). - You can learn more about our approach to [project management](https://github.com/ppy/osu/wiki/Project-management). -- Track our current efforts [towards full "ranked play" support](https://github.com/orgs/ppy/projects/13?query=is%3Aopen+sort%3Aupdated-desc) +- Track our current efforts [towards full "ranked play" support](https://github.com/orgs/ppy/projects/13?query=is%3Aopen+sort%3Aupdated-desc). ## Running osu! @@ -33,7 +33,7 @@ If you are just looking to give the game a whirl, you can grab the latest releas | [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | macOS 10.15+ ([Intel](https://github.com/ppy/osu/releases/latest/download/osu.app.Intel.zip), [Apple Silicon](https://github.com/ppy/osu/releases/latest/download/osu.app.Apple.Silicon.zip)) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 13.4+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) | | ------------- | ------------- | ------------- | ------------- | ------------- | -You can also generally download a version for your current device from the [osu! site](https://osu.ppy.sh/home/download) +You can also generally download a version for your current device from the [osu! site](https://osu.ppy.sh/home/download). If your platform is not listed above, there is still a chance you can manually build it by following the instructions below. @@ -80,9 +80,9 @@ You should load the solution via one of the platform-specific `.slnf` files, rat - `osu.Android.slnf` - `osu.iOS.slnf` -Run configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `VisualTests` project/configuration. More information on this is provided [below](#contributing). +Run configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `osu! (Tests)` project/configuration. More information on this is provided [below](#contributing). -To build for mobile platforms, you will likely need to run `sudo dotnet workload restore` if you haven't done so previously. This will install android/iOS tooling required to complete the build. +To build for mobile platforms, you will likely need to run `sudo dotnet workload restore` if you haven't done so previously. This will install Android/iOS tooling required to complete the build. #### From CLI From 0eab4c5364d23e3bb996894ae3fd58b4842f0914 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Sep 2023 14:47:55 +0900 Subject: [PATCH 0630/4092] Reword sentence with multiple `and`s MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 946a6b03d9..f7a4936e50 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ A free-to-win rhythm game. Rhythm is just a *click* away! -This is the future – and final – iteration of the [osu!](https://osu.ppy.sh) game client and marks the beginning of an open era! Currently known by and released under the release codename "*lazer*". As in sharper than cutting-edge. +This is the future – and final – iteration of the [osu!](https://osu.ppy.sh) game client which marks the beginning of an open era! Currently known by and released under the release codename "*lazer*". As in sharper than cutting-edge. ## Status From 8ef0ef09db1381df7fc0bf0f65701d4ca2ca8a1c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Sep 2023 14:59:37 +0900 Subject: [PATCH 0631/4092] Reword release build disclaimer --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f7a4936e50..d5dc0723af 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ You can also build and run *osu!* from the command-line with a single command: dotnet run --project osu.Desktop ``` -If you are not interested in debugging *osu!*, you can add `-c Release` to gain performance. In this case, you must replace `Debug` with `Release` in any commands mentioned in this document. +When running locally to do any kind of performance testing, make sure to add `-c Release` to the build command, as the overhead of running with the default `Debug` configuration can be large (especially when testing with local framework modifications as below). If the build fails, try to restore NuGet packages with `dotnet restore`. From c4fc4199d190b6bff29d10c65341b9c7a011fca9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 21 Sep 2023 19:02:31 +0300 Subject: [PATCH 0632/4092] Use correct maximum size for droplets --- osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyDropletPiece.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyDropletPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyDropletPiece.cs index 581259a9c4..c6c0839fba 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyDropletPiece.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyDropletPiece.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy { public partial class LegacyDropletPiece : LegacyCatchHitObjectPiece { - private static readonly Vector2 droplet_max_size = new Vector2(100); + private static readonly Vector2 droplet_max_size = new Vector2(82, 103); public LegacyDropletPiece() { From ad86bf2d56cb85e5bd4b84e74a92d1ed63aaf578 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 21 Sep 2023 19:03:08 +0300 Subject: [PATCH 0633/4092] Revert redundant size limitations Already handled by the sprites themselves being resized. --- osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyDrumRoll.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyDrumRoll.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyDrumRoll.cs index 83f05fe6ec..5543a31ec9 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyDrumRoll.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Graphics; using osu.Game.Skinning; -using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Taiko.Skinning.Legacy @@ -48,13 +47,13 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy Anchor = Anchor.CentreRight, Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.Both, - Texture = skin.GetTexture("taiko-roll-end", WrapMode.ClampToEdge, WrapMode.ClampToEdge)?.WithMaximumSize(new Vector2(128, 256)), + Texture = skin.GetTexture("taiko-roll-end", WrapMode.ClampToEdge, WrapMode.ClampToEdge), FillMode = FillMode.Fit, }, body = new Sprite { RelativeSizeAxes = Axes.Both, - Texture = skin.GetTexture("taiko-roll-middle", WrapMode.ClampToEdge, WrapMode.ClampToEdge)?.WithMaximumSize(new Vector2(2, 256)), + Texture = skin.GetTexture("taiko-roll-middle", WrapMode.ClampToEdge, WrapMode.ClampToEdge), }, headCircle = new LegacyCirclePiece { From 9af4e75dfc5dd5cdfd5e70c227d1a48e2747205c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Sep 2023 01:24:24 +0900 Subject: [PATCH 0634/4092] Disable clipboard export for song select textbox In combination with https://github.com/ppy/osu-framework/pull/5997, closes https://github.com/ppy/osu/issues/24867 --- osu.Game/Screens/Select/FilterControl.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 38520a85b7..614c9bd7ec 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -254,6 +254,8 @@ namespace osu.Game.Screens.Select public OsuSpriteText FilterText { get; private set; } + protected override bool AllowClipboardExport => false; + public FilterControlTextBox() { Height += filter_text_size; From f1258a396367d4ccec91977c34f40ca13cb4dd6a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Sep 2023 01:26:38 +0900 Subject: [PATCH 0635/4092] 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 10cee77b09..20b0f220a3 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + System.Net.Sockets.SocketException (11001): No such host is known. 2023-10-06 03:24:17 [verbose]: at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken) ``` Closes https://github.com/ppy/osu/issues/24890 (again). --- osu.Game/Online/API/OAuth.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/OAuth.cs b/osu.Game/Online/API/OAuth.cs index 1f26ab5458..485274f349 100644 --- a/osu.Game/Online/API/OAuth.cs +++ b/osu.Game/Online/API/OAuth.cs @@ -6,6 +6,7 @@ using System; using System.Diagnostics; using System.Net.Http; +using System.Net.Sockets; using Newtonsoft.Json; using osu.Framework.Bindables; @@ -99,6 +100,11 @@ namespace osu.Game.Online.API return true; } } + catch (SocketException) + { + // Network failure. + return false; + } catch (HttpRequestException) { // Network failure. @@ -106,7 +112,7 @@ namespace osu.Game.Online.API } catch { - // Force a full re-reauthentication. + // Force a full re-authentication. Token.Value = null; return false; } From db5178e45306ce9df560b047a563b3e075a75ff6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Oct 2023 16:52:00 +0900 Subject: [PATCH 0824/4092] Change `ArgonHealthDisplay` to be relative sized for now --- .../Gameplay/TestSceneArgonHealthDisplay.cs | 2 - .../Screens/Play/HUD/ArgonHealthDisplay.cs | 68 ++++++++++++++----- 2 files changed, 50 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs index 06a7763711..8261a1729e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs @@ -12,7 +12,6 @@ using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; -using osuTK; using osuTK.Graphics; namespace osu.Game.Tests.Visual.Gameplay @@ -41,7 +40,6 @@ namespace osu.Game.Tests.Visual.Gameplay { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Scale = new Vector2(2f), }, }; }); diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 62a4b958c2..ad4b407692 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -5,14 +5,17 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Lines; using osu.Framework.Graphics.Shapes; +using osu.Framework.Layout; using osu.Framework.Threading; using osu.Framework.Utils; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; @@ -27,6 +30,23 @@ namespace osu.Game.Screens.Play.HUD { public bool UsesFixedAnchor { get; set; } + [SettingSource("Bar height")] + public BindableFloat BarHeight { get; } = new BindableFloat + { + Default = 32, + MinValue = 0, + MaxValue = 64, + Precision = 1 + }; + + [SettingSource("Bar length")] + public BindableFloat BarLength { get; } = new BindableFloat(1) + { + MinValue = 0.2f, + MaxValue = 1, + Precision = 0.01f, + }; + private BarPath mainBar = null!; /// @@ -76,10 +96,13 @@ namespace osu.Game.Screens.Play.HUD } } + private const float left_line_width = 50f; + [BackgroundDependencyLoader] private void load() { - AutoSizeAxes = Axes.Both; + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; InternalChild = new FillFlowContainer { @@ -91,7 +114,7 @@ namespace osu.Game.Screens.Play.HUD new Circle { Margin = new MarginPadding { Top = 8.5f, Left = -2 }, - Size = new Vector2(50f, 3f), + Size = new Vector2(left_line_width, 3f), }, new Container { @@ -127,8 +150,6 @@ namespace osu.Game.Screens.Play.HUD } }, }; - - updatePath(); } protected override void LoadComplete() @@ -144,6 +165,18 @@ namespace osu.Game.Screens.Play.HUD if (resetMissBarDelegate == null) this.TransformTo(nameof(GlowBarValue), v.NewValue, 300, Easing.OutQuint); }, true); + + BarLength.BindValueChanged(l => Width = l.NewValue, true); + BarHeight.BindValueChanged(_ => updatePath()); + updatePath(); + } + + protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source) + { + if ((invalidation & Invalidation.DrawSize) > 0) + updatePath(); + + return base.OnInvalidate(invalidation, source); } protected override void Update() @@ -214,25 +247,24 @@ namespace osu.Game.Screens.Play.HUD private void updatePath() { - const float curve_start = 280; - const float curve_end = 310; + float barLength = DrawWidth - left_line_width - 24; + float curveStart = barLength - 70; + float curveEnd = barLength - 40; + const float curve_smoothness = 10; - const float bar_length = 350; - const float bar_verticality = 32.5f; - - Vector2 diagonalDir = (new Vector2(curve_end, bar_verticality) - new Vector2(curve_start, 0)).Normalized(); + Vector2 diagonalDir = (new Vector2(curveEnd, BarHeight.Value) - new Vector2(curveStart, 0)).Normalized(); barPath = new SliderPath(new[] { new PathControlPoint(new Vector2(0, 0), PathType.Linear), - new PathControlPoint(new Vector2(curve_start - curve_smoothness, 0), PathType.Bezier), - new PathControlPoint(new Vector2(curve_start, 0)), - new PathControlPoint(new Vector2(curve_start, 0) + diagonalDir * curve_smoothness, PathType.Linear), - new PathControlPoint(new Vector2(curve_end, bar_verticality) - diagonalDir * curve_smoothness, PathType.Bezier), - new PathControlPoint(new Vector2(curve_end, bar_verticality)), - new PathControlPoint(new Vector2(curve_end + curve_smoothness, bar_verticality), PathType.Linear), - new PathControlPoint(new Vector2(bar_length, bar_verticality)), + new PathControlPoint(new Vector2(curveStart - curve_smoothness, 0), PathType.Bezier), + new PathControlPoint(new Vector2(curveStart, 0)), + new PathControlPoint(new Vector2(curveStart, 0) + diagonalDir * curve_smoothness, PathType.Linear), + new PathControlPoint(new Vector2(curveEnd, BarHeight.Value) - diagonalDir * curve_smoothness, PathType.Bezier), + new PathControlPoint(new Vector2(curveEnd, BarHeight.Value)), + new PathControlPoint(new Vector2(curveEnd + curve_smoothness, BarHeight.Value), PathType.Linear), + new PathControlPoint(new Vector2(barLength, BarHeight.Value)), }); List vertices = new List(); @@ -267,7 +299,7 @@ namespace osu.Game.Screens.Play.HUD { protected override Color4 ColourAt(float position) { - if (position <= 0.128f) + if (position <= 0.16f) return Color4.White.Opacity(0.8f); return Interpolation.ValueAt(position, From f40e910c51da3f4ad5779b854941ffb5b8e53a23 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Oct 2023 18:56:31 +0900 Subject: [PATCH 0825/4092] Remove left line from health display --- .../Screens/Play/HUD/ArgonHealthDisplay.cs | 67 +++++++------------ 1 file changed, 26 insertions(+), 41 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index ad4b407692..67f21a1c83 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Lines; -using osu.Framework.Graphics.Shapes; using osu.Framework.Layout; using osu.Framework.Threading; using osu.Framework.Utils; @@ -96,7 +95,7 @@ namespace osu.Game.Screens.Play.HUD } } - private const float left_line_width = 50f; + private const float main_path_radius = 10f; [BackgroundDependencyLoader] private void load() @@ -104,51 +103,37 @@ namespace osu.Game.Screens.Play.HUD RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - InternalChild = new FillFlowContainer + InternalChild = new Container { AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(4f, 0f), Children = new Drawable[] { - new Circle + background = new BackgroundPath { - Margin = new MarginPadding { Top = 8.5f, Left = -2 }, - Size = new Vector2(left_line_width, 3f), + PathRadius = main_path_radius, }, - new Container + glowBar = new BarPath { - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - background = new BackgroundPath - { - PathRadius = 10f, - }, - glowBar = new BarPath - { - BarColour = Color4.White, - GlowColour = OsuColour.Gray(0.5f), - Blending = BlendingParameters.Additive, - Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0.8f), Color4.White), - PathRadius = 40f, - // Kinda hacky, but results in correct positioning with increased path radius. - Margin = new MarginPadding(-30f), - GlowPortion = 0.9f, - }, - mainBar = new BarPath - { - AutoSizeAxes = Axes.None, - RelativeSizeAxes = Axes.Both, - Blending = BlendingParameters.Additive, - BarColour = main_bar_colour, - GlowColour = main_bar_glow_colour, - PathRadius = 10f, - GlowPortion = 0.6f, - }, - } - } - }, + BarColour = Color4.White, + GlowColour = OsuColour.Gray(0.5f), + Blending = BlendingParameters.Additive, + Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0.8f), Color4.White), + PathRadius = 40f, + // Kinda hacky, but results in correct positioning with increased path radius. + Margin = new MarginPadding(-30f), + GlowPortion = 0.9f, + }, + mainBar = new BarPath + { + AutoSizeAxes = Axes.None, + RelativeSizeAxes = Axes.Both, + Blending = BlendingParameters.Additive, + BarColour = main_bar_colour, + GlowColour = main_bar_glow_colour, + PathRadius = main_path_radius, + GlowPortion = 0.6f, + }, + } }; } @@ -247,7 +232,7 @@ namespace osu.Game.Screens.Play.HUD private void updatePath() { - float barLength = DrawWidth - left_line_width - 24; + float barLength = DrawWidth - main_path_radius * 2; float curveStart = barLength - 70; float curveEnd = barLength - 40; From 71be3c8f8b783716d4bcb619d36e253b1ad79315 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Oct 2023 18:56:16 +0900 Subject: [PATCH 0826/4092] Add ability to adjust health bar settings in test scene --- .../Gameplay/TestSceneArgonHealthDisplay.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs index 8261a1729e..7bad623d7f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; @@ -21,6 +22,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached(typeof(HealthProcessor))] private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); + private ArgonHealthDisplay healthDisplay = null!; + [SetUpSteps] public void SetUpSteps() { @@ -36,13 +39,25 @@ namespace osu.Game.Tests.Visual.Gameplay RelativeSizeAxes = Axes.Both, Colour = Color4.Gray, }, - new ArgonHealthDisplay + healthDisplay = new ArgonHealthDisplay { Anchor = Anchor.Centre, Origin = Anchor.Centre, }, }; }); + + AddSliderStep("Width", 0, 1f, 1f, val => + { + if (healthDisplay.IsNotNull()) + healthDisplay.BarLength.Value = val; + }); + + AddSliderStep("Height", 0, 64, 0, val => + { + if (healthDisplay.IsNotNull()) + healthDisplay.BarHeight.Value = val; + }); } [Test] From 3f2a00d90d2a967147e33e273dab24779fb03747 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Oct 2023 18:46:50 +0900 Subject: [PATCH 0827/4092] Add argon health display to default skin layout --- osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs | 5 ++--- osu.Game/Skinning/ArgonSkin.cs | 10 +++++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 67f21a1c83..755eaeaf33 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -30,16 +30,15 @@ namespace osu.Game.Screens.Play.HUD public bool UsesFixedAnchor { get; set; } [SettingSource("Bar height")] - public BindableFloat BarHeight { get; } = new BindableFloat + public BindableFloat BarHeight { get; } = new BindableFloat(20) { - Default = 32, MinValue = 0, MaxValue = 64, Precision = 1 }; [SettingSource("Bar length")] - public BindableFloat BarLength { get; } = new BindableFloat(1) + public BindableFloat BarLength { get; } = new BindableFloat(0.98f) { MinValue = 0.2f, MaxValue = 1, diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 6e17458082..d530efbfdd 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -109,6 +109,7 @@ namespace osu.Game.Skinning case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: var skinnableTargetWrapper = new DefaultSkinComponentsContainer(container => { + var health = container.OfType().FirstOrDefault(); var score = container.OfType().FirstOrDefault(); var accuracy = container.OfType().FirstOrDefault(); var combo = container.OfType().FirstOrDefault(); @@ -128,6 +129,13 @@ namespace osu.Game.Skinning score.Position = new Vector2(0, vertical_offset); + if (health != null) + { + health.Origin = Anchor.TopCentre; + health.Anchor = Anchor.TopCentre; + health.Y = 5; + } + if (ppCounter != null) { ppCounter.Y = score.Position.Y + ppCounter.ScreenSpaceDeltaToParentSpace(score.ScreenSpaceDrawQuad.Size).Y - 4; @@ -191,7 +199,7 @@ namespace osu.Game.Skinning new DefaultComboCounter(), new DefaultScoreCounter(), new DefaultAccuracyCounter(), - new DefaultHealthDisplay(), + new ArgonHealthDisplay(), new ArgonSongProgress(), new ArgonKeyCounterDisplay(), new BarHitErrorMeter(), From d87ab9c82dad1081c6a060ea8b6e401bbd29cdee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Oct 2023 19:34:38 +0900 Subject: [PATCH 0828/4092] Adjust transition time based on miss/hit --- osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 755eaeaf33..7af7fd9487 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -145,9 +145,11 @@ namespace osu.Game.Screens.Play.HUD if (v.NewValue >= GlowBarValue) finishMissDisplay(); - this.TransformTo(nameof(HealthBarValue), v.NewValue, 300, Easing.OutQuint); + double time = v.NewValue > GlowBarValue ? 500 : 250; + + this.TransformTo(nameof(HealthBarValue), v.NewValue, time, Easing.OutQuint); if (resetMissBarDelegate == null) - this.TransformTo(nameof(GlowBarValue), v.NewValue, 300, Easing.OutQuint); + this.TransformTo(nameof(GlowBarValue), v.NewValue, time, Easing.OutQuint); }, true); BarLength.BindValueChanged(l => Width = l.NewValue, true); From 8e5b2e78e58842721478fbf5dd7fdd7c7a1f66ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Oct 2023 21:01:23 +0900 Subject: [PATCH 0829/4092] Fix variable clash --- osu.Game/Screens/OnlinePlay/FooterButtonFreeMods.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/FooterButtonFreeMods.cs b/osu.Game/Screens/OnlinePlay/FooterButtonFreeMods.cs index 54cd36d05b..dd6536cf26 100644 --- a/osu.Game/Screens/OnlinePlay/FooterButtonFreeMods.cs +++ b/osu.Game/Screens/OnlinePlay/FooterButtonFreeMods.cs @@ -117,17 +117,17 @@ namespace osu.Game.Screens.OnlinePlay private void updateModDisplay() { - int current = Current.Value.Count; + int currentCount = Current.Value.Count; - if (current == allAvailableAndValidMods.Count()) + if (currentCount == allAvailableAndValidMods.Count()) { count.Text = "all"; count.FadeColour(colours.Gray2, 200, Easing.OutQuint); circle.FadeColour(colours.Yellow, 200, Easing.OutQuint); } - else if (current > 0) + else if (currentCount > 0) { - count.Text = $"{current} mods"; + count.Text = $"{currentCount} mods"; count.FadeColour(colours.Gray2, 200, Easing.OutQuint); circle.FadeColour(colours.YellowDark, 200, Easing.OutQuint); } From 10ce5705ce67acb920f2f4f0d6b30e91178df5d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 6 Oct 2023 14:11:41 +0200 Subject: [PATCH 0830/4092] 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 bc95e96a7b..b3feccbbc0 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + + From 910d74fdda85433e2cf6b1f62daa3edc4a75f816 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 26 Oct 2023 11:59:38 +0200 Subject: [PATCH 1153/4092] Add failing test coverage for double-click on disabled sliders --- .../UserInterface/TestSceneRoundedSliderBar.cs | 17 +++++++++++++++++ .../UserInterface/TestSceneShearedSliderBar.cs | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneRoundedSliderBar.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneRoundedSliderBar.cs index 419e88137c..ae52c26fd2 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneRoundedSliderBar.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneRoundedSliderBar.cs @@ -55,5 +55,22 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("slider is default", () => slider.Current.IsDefault); } + + [Test] + public void TestNubDoubleClickOnDisabledSliderDoesNothing() + { + AddStep("set slider to 1", () => slider.Current.Value = 1); + AddStep("disable slider", () => slider.Current.Disabled = true); + + AddStep("move mouse to nub", () => InputManager.MoveMouseTo(slider.ChildrenOfType().Single())); + + AddStep("double click nub", () => + { + InputManager.Click(MouseButton.Left); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("slider is still at 1", () => slider.Current.Value, () => Is.EqualTo(1)); + } } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneShearedSliderBar.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneShearedSliderBar.cs index d459a2c701..334fc4563a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneShearedSliderBar.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneShearedSliderBar.cs @@ -55,5 +55,22 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("slider is default", () => slider.Current.IsDefault); } + + [Test] + public void TestNubDoubleClickOnDisabledSliderDoesNothing() + { + AddStep("set slider to 1", () => slider.Current.Value = 1); + AddStep("disable slider", () => slider.Current.Disabled = true); + + AddStep("move mouse to nub", () => InputManager.MoveMouseTo(slider.ChildrenOfType().Single())); + + AddStep("double click nub", () => + { + InputManager.Click(MouseButton.Left); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("slider is still at 1", () => slider.Current.Value, () => Is.EqualTo(1)); + } } } From 96437c4518a394c2354853b95c5f07853c6340d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 26 Oct 2023 12:03:35 +0200 Subject: [PATCH 1154/4092] Fix tests specifying float precision for double bindable Would cause assignments to `.Value` to become imprecise even if the underlying bindable could represent the value 100% accurately. --- .../Visual/UserInterface/TestSceneRoundedSliderBar.cs | 2 +- .../Visual/UserInterface/TestSceneShearedSliderBar.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneRoundedSliderBar.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneRoundedSliderBar.cs index ae52c26fd2..311034d595 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneRoundedSliderBar.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneRoundedSliderBar.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.UserInterface private readonly BindableDouble current = new BindableDouble(5) { - Precision = 0.1f, + Precision = 0.1, MinValue = 0, MaxValue = 15 }; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneShearedSliderBar.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneShearedSliderBar.cs index 334fc4563a..a5072b4e60 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneShearedSliderBar.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneShearedSliderBar.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.UserInterface private readonly BindableDouble current = new BindableDouble(5) { - Precision = 0.1f, + Precision = 0.1, MinValue = 0, MaxValue = 15 }; From 89fec95b016364d2a97a30a1d6cbbbf6e46e19a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 26 Oct 2023 12:11:25 +0200 Subject: [PATCH 1155/4092] Fix cross-test data dependency by recreating slider every time --- .../TestSceneRoundedSliderBar.cs | 22 +++++++++---------- .../TestSceneShearedSliderBar.cs | 22 +++++++++---------- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneRoundedSliderBar.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneRoundedSliderBar.cs index 311034d595..66d54c8562 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneRoundedSliderBar.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneRoundedSliderBar.cs @@ -18,26 +18,24 @@ namespace osu.Game.Tests.Visual.UserInterface [Cached] private OverlayColourProvider colourProvider { get; set; } = new OverlayColourProvider(OverlayColourScheme.Purple); - private readonly BindableDouble current = new BindableDouble(5) - { - Precision = 0.1, - MinValue = 0, - MaxValue = 15 - }; - private RoundedSliderBar slider = null!; - [BackgroundDependencyLoader] - private void load() + [SetUpSteps] + public void SetUpSteps() { - Child = slider = new RoundedSliderBar + AddStep("create slider", () => Child = slider = new RoundedSliderBar { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Current = current, + Current = new BindableDouble(5) + { + Precision = 0.1, + MinValue = 0, + MaxValue = 15 + }, RelativeSizeAxes = Axes.X, Width = 0.4f - }; + }); } [Test] diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneShearedSliderBar.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneShearedSliderBar.cs index a5072b4e60..c3038ddb3d 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneShearedSliderBar.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneShearedSliderBar.cs @@ -18,26 +18,24 @@ namespace osu.Game.Tests.Visual.UserInterface [Cached] private OverlayColourProvider colourProvider { get; set; } = new OverlayColourProvider(OverlayColourScheme.Purple); - private readonly BindableDouble current = new BindableDouble(5) - { - Precision = 0.1, - MinValue = 0, - MaxValue = 15 - }; - private ShearedSliderBar slider = null!; - [BackgroundDependencyLoader] - private void load() + [SetUpSteps] + public void SetUpSteps() { - Child = slider = new ShearedSliderBar + AddStep("create slider", () => Child = slider = new ShearedSliderBar { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Current = current, + Current = new BindableDouble(5) + { + Precision = 0.1, + MinValue = 0, + MaxValue = 15 + }, RelativeSizeAxes = Axes.X, Width = 0.4f - }; + }); } [Test] From 3b9c4c9d530eeee26f29a920299b0b363cd7348c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 26 Oct 2023 12:04:27 +0200 Subject: [PATCH 1156/4092] Do not revert to default value when double-clicking disabled slider Closes https://github.com/ppy/osu/issues/25228. --- osu.Game/Graphics/UserInterface/RoundedSliderBar.cs | 6 +++++- osu.Game/Graphics/UserInterface/ShearedSliderBar.cs | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/RoundedSliderBar.cs b/osu.Game/Graphics/UserInterface/RoundedSliderBar.cs index e5976fe893..0981881ead 100644 --- a/osu.Game/Graphics/UserInterface/RoundedSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/RoundedSliderBar.cs @@ -98,7 +98,11 @@ namespace osu.Game.Graphics.UserInterface Origin = Anchor.TopCentre, RelativePositionAxes = Axes.X, Current = { Value = true }, - OnDoubleClicked = () => Current.SetDefault(), + OnDoubleClicked = () => + { + if (!Current.Disabled) + Current.SetDefault(); + }, }, }, hoverClickSounds = new HoverClickSounds() diff --git a/osu.Game/Graphics/UserInterface/ShearedSliderBar.cs b/osu.Game/Graphics/UserInterface/ShearedSliderBar.cs index 9ef5f3073a..60a6670492 100644 --- a/osu.Game/Graphics/UserInterface/ShearedSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/ShearedSliderBar.cs @@ -101,7 +101,11 @@ namespace osu.Game.Graphics.UserInterface Origin = Anchor.TopCentre, RelativePositionAxes = Axes.X, Current = { Value = true }, - OnDoubleClicked = () => Current.SetDefault(), + OnDoubleClicked = () => + { + if (!Current.Disabled) + Current.SetDefault(); + }, }, }, hoverClickSounds = new HoverClickSounds() From dbb69419e6d49e6661d31eb22f2445e499e5e742 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 26 Oct 2023 12:39:07 +0200 Subject: [PATCH 1157/4092] Add test coverage for parsing new online ID --- .../Beatmaps/Formats/LegacyScoreDecoderTest.cs | 14 ++++++++++++++ .../Replays/taiko-replay-with-new-online-id.osr | Bin 0 -> 1531 bytes 2 files changed, 14 insertions(+) create mode 100644 osu.Game.Tests/Resources/Replays/taiko-replay-with-new-online-id.osr diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index c7fd3ba098..ab88be1511 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -101,6 +101,20 @@ namespace osu.Game.Tests.Beatmaps.Formats } } + [Test] + public void TestDecodeNewOnlineID() + { + var decoder = new TestLegacyScoreDecoder(); + + using (var resourceStream = TestResources.OpenResource("Replays/taiko-replay-with-new-online-id.osr")) + { + var score = decoder.Parse(resourceStream); + + Assert.That(score.ScoreInfo.OnlineID, Is.EqualTo(258)); + Assert.That(score.ScoreInfo.LegacyOnlineID, Is.EqualTo(-1)); + } + } + [TestCase(3, true)] [TestCase(6, false)] [TestCase(LegacyBeatmapDecoder.LATEST_VERSION, false)] diff --git a/osu.Game.Tests/Resources/Replays/taiko-replay-with-new-online-id.osr b/osu.Game.Tests/Resources/Replays/taiko-replay-with-new-online-id.osr new file mode 100644 index 0000000000000000000000000000000000000000..63e05f5fcdbda51e8f7c3322def87f4fc74f8da4 GIT binary patch literal 1531 zcmVvTrHZVD6F=Go4b8ul} zWo=<@Utx4?VRJGIAU8K+FfnCiFg9f~Ic8-uIb%6xWnyJxW-~W5FgY}2Hc0>o00001 z0000000e#v08sz|000003jk|tWgGt0+X(9f003P803ZOTO#lD@000007K}rXlJKRA z7M`&HOhZfu53FrP?1?N(Ce<_OIe53onYYKWQKjB!yy__GNTqRxRs4t|7d^=w?^a2; z=;p=RN*NtHSfdZ7KM{`CJ^MjsRn>7amAkGdpDK+x=PDo#tk4aBL?k%f)$Ebh0t%Nn zphxu;)QK_xDZ_`29#Slz$-BsTX6oS*tEpL9Ev7g9I5ScQ++9#K`PS+Bg7YLtZ zZhi6@^BMY%^-@>8#b8diGT>rK8W$eYWM@f+i3@MI*x2jwMoDv7i9dcH+Blva=xLAQ zQ+tf$l}n5c>1hLZYL!1qq`!()(I1Bi(teRO5k6sX_aTq*mVFs>aNFfP7QeOX;b~w1IAj)Te-(%eMdm^#T`gS4`h= z`(YRBu5x52OkrvAE;$$5Ka1rR+V@w)I!3+SveB0rxD~l$s=FEk0DPJXI01( zX<*T#vp20$0MeFEDmFU3$xU;pcASo(q2) zN#UlWQX+|aTArDrD&&we-Q#@5I>P@U#aZ7E_WVu}QZ z{u?U)bPE60zy8;&*@>H#nKF^riT4w%Ra_eUgCD(pFv%H+A(z{%SB~Z zD*JNyOplwu9x#_rabI#%4~0{9EZmm)IZ%cF!fh>HiAX@d}Izf*u8yw}?8+ zNnZ9YLWQcYNC*QlO6tVSziS_G!(qvFc@44`+vV{%9koFdUqvujoNp;)_jO1RN`{^0 zg=z?MuejP`10NKa5$Cxp-Se8FTJyl!tovh4wVKlspX4~-i8~TINGZ^#);*P#i)SE8 zzW1JsdM+A;?~>4&l@303`MAkXiv{_WIWc4$HYYGgMxnMFNGc{X2NX~R?2b@ZRs9cq z$!to>PZGzdmXRl&AW$UR-r`TGqkjmloZEFshw0Jkawz&1Ok4MmYz+~Jhh=obE&;f{ z+ZS(WELeZ_C2m3BkbxZ&DAUVh89w}bvS8+#qHjecQMK`PKw!@~opfX|DAuPffrNhq zDizxF1oWO4uo1ML<2%wPOFapwVrJdCB~~#Tt?D*NdN!;|NsC0|Ahbm z09^n8AOPI}000000000zf`AtsFAk^B)WLFNAyG7WV$N8-!j+hPu8$we_e;|#*+iNK z$kRN=q&Sl0(d`|*oEU1|*~5eG@dH3Miy0i*F=j=X+_a<6T3%2U@VpmXv8`-JhNX1! hacf^hACxf}U(1HP^l0fR;XoK|ePzq5Xz7uby&!FJ#7qDH literal 0 HcmV?d00001 From 8b9b085ef5422976b6ec9ef8cd11406c235209fc Mon Sep 17 00:00:00 2001 From: Termincc <112787855+Termincc@users.noreply.github.com> Date: Thu, 26 Oct 2023 22:15:10 +1000 Subject: [PATCH 1158/4092] Address mod incompatibilities Makes FreezeFrame and Transform mods incompatible. --- osu.Game.Rulesets.Osu/Mods/OsuModFreezeFrame.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFreezeFrame.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFreezeFrame.cs index 0a1aab9ef1..89a00d6c32 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFreezeFrame.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFreezeFrame.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override LocalisableString Description => "Burn the notes into your memory."; //Alters the transforms of the approach circles, breaking the effects of these mods. - public override Type[] IncompatibleMods => new[] { typeof(OsuModApproachDifferent) }; + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModApproachDifferent), typeof(OsuModTransform) }).ToArray(); public override ModType Type => ModType.Fun; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs index 2354cd50ae..92a499e735 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; @@ -21,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Fun; public override LocalisableString Description => "Everything rotates. EVERYTHING."; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModWiggle), typeof(OsuModMagnetised), typeof(OsuModRepel) }; + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModWiggle), typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModFreezeFrame) }).ToArray(); private float theta; From 238e8175ae3b2ddb8e8b86dc969ddba2225e693f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Oct 2023 21:26:26 +0900 Subject: [PATCH 1159/4092] Add ability to quick retry using Ctrl-R Matches osu!stable --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 20220d88cd..947cd5f54f 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -149,6 +149,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(InputKey.Space, GlobalAction.SkipCutscene), new KeyBinding(InputKey.ExtraMouseButton2, GlobalAction.SkipCutscene), new KeyBinding(InputKey.Tilde, GlobalAction.QuickRetry), + new KeyBinding(new[] { InputKey.Control, InputKey.R }, GlobalAction.QuickRetry), new KeyBinding(new[] { InputKey.Control, InputKey.Tilde }, GlobalAction.QuickExit), new KeyBinding(new[] { InputKey.F3 }, GlobalAction.DecreaseScrollSpeed), new KeyBinding(new[] { InputKey.F4 }, GlobalAction.IncreaseScrollSpeed), From 526ee6e14070a2ba0d09ad544353be52796e698c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 26 Oct 2023 14:46:24 +0200 Subject: [PATCH 1160/4092] Remove `IScoreInfo : IHasNamedFiles` inheritance --- osu.Game/Database/IHasRealmFiles.cs | 6 ++++-- osu.Game/Scoring/IScoreInfo.cs | 2 +- osu.Game/Scoring/ScoreInfo.cs | 1 - 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/IHasRealmFiles.cs b/osu.Game/Database/IHasRealmFiles.cs index 79ea719583..b301bb04de 100644 --- a/osu.Game/Database/IHasRealmFiles.cs +++ b/osu.Game/Database/IHasRealmFiles.cs @@ -10,13 +10,15 @@ namespace osu.Game.Database /// /// A model that contains a list of files it is responsible for. /// - public interface IHasRealmFiles + public interface IHasRealmFiles : IHasNamedFiles { /// /// Available files in this model, with locally filenames. /// When performing lookups, consider using or to do case-insensitive lookups. /// - IList Files { get; } + new IList Files { get; } + + IEnumerable IHasNamedFiles.Files => Files; /// /// A combined hash representing the model, based on the files it contains. diff --git a/osu.Game/Scoring/IScoreInfo.cs b/osu.Game/Scoring/IScoreInfo.cs index 4083d57fa0..cde48c3be3 100644 --- a/osu.Game/Scoring/IScoreInfo.cs +++ b/osu.Game/Scoring/IScoreInfo.cs @@ -9,7 +9,7 @@ using osu.Game.Users; namespace osu.Game.Scoring { - public interface IScoreInfo : IHasOnlineID, IHasNamedFiles + public interface IScoreInfo : IHasOnlineID { IUser User { get; } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 6b03e876c4..722d83cac8 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -187,7 +187,6 @@ namespace osu.Game.Scoring IRulesetInfo IScoreInfo.Ruleset => Ruleset; IBeatmapInfo? IScoreInfo.Beatmap => BeatmapInfo; IUser IScoreInfo.User => User; - IEnumerable IHasNamedFiles.Files => Files; #region Properties required to make things work with existing usages From 900530080ff7f4c33a19b6107214c80ff4f0f997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 26 Oct 2023 14:56:22 +0200 Subject: [PATCH 1161/4092] Make `SoloScoreInfo` implement `IScoreInfo` --- .../API/Requests/Responses/SoloScoreInfo.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index 783522220b..0e31f11dc1 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -7,16 +7,16 @@ using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using osu.Game.Beatmaps; -using osu.Game.Database; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using osu.Game.Users; namespace osu.Game.Online.API.Requests.Responses { [Serializable] - public class SoloScoreInfo : IHasOnlineID + public class SoloScoreInfo : IScoreInfo { [JsonProperty("beatmap_id")] public int BeatmapID { get; set; } @@ -138,6 +138,18 @@ namespace osu.Game.Online.API.Requests.Responses #endregion + #region IScoreInfo + + public long OnlineID => (long?)ID ?? -1; + + IUser IScoreInfo.User => User!; + DateTimeOffset IScoreInfo.Date => EndedAt; + long IScoreInfo.LegacyOnlineID => (long?)LegacyScoreId ?? -1; + IBeatmapInfo IScoreInfo.Beatmap => Beatmap!; + IRulesetInfo IScoreInfo.Ruleset => Beatmap!.Ruleset; + + #endregion + public override string ToString() => $"score_id: {ID} user_id: {UserID}"; /// @@ -223,7 +235,5 @@ namespace osu.Game.Online.API.Requests.Responses Statistics = score.Statistics.Where(kvp => kvp.Value != 0).ToDictionary(kvp => kvp.Key, kvp => kvp.Value), MaximumStatistics = score.MaximumStatistics.Where(kvp => kvp.Value != 0).ToDictionary(kvp => kvp.Key, kvp => kvp.Value), }; - - public long OnlineID => (long?)ID ?? -1; } } From c3e9f5184f504f119867c3745af730c6fafe02de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 26 Oct 2023 15:09:34 +0200 Subject: [PATCH 1162/4092] Fix `SoloScoreInfo` not copying over legacy score ID when converting to `ScoreInfo` --- osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index 0e31f11dc1..ac2d8152b1 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -190,6 +190,7 @@ namespace osu.Game.Online.API.Requests.Responses var score = new ScoreInfo { OnlineID = OnlineID, + LegacyOnlineID = (long?)LegacyScoreId ?? -1, User = User ?? new APIUser { Id = UserID }, BeatmapInfo = new BeatmapInfo { OnlineID = BeatmapID }, Ruleset = new RulesetInfo { OnlineID = RulesetID }, From cbb2a0dd70ddce70e2e156be7bac632bbc9b6480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 26 Oct 2023 15:09:59 +0200 Subject: [PATCH 1163/4092] Use both score ID types to deduplicate score on solo results screen --- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index f187b8a302..da08a26a58 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Beatmaps; +using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.Solo; @@ -67,7 +68,7 @@ namespace osu.Game.Screens.Ranking return null; getScoreRequest = new GetScoresRequest(Score.BeatmapInfo, Score.Ruleset); - getScoreRequest.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineID != Score.OnlineID).Select(s => s.ToScoreInfo(rulesets, Beatmap.Value.BeatmapInfo))); + getScoreRequest.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => !s.MatchesOnlineID(Score)).Select(s => s.ToScoreInfo(rulesets, Beatmap.Value.BeatmapInfo))); return getScoreRequest; } From 359ae3120494d0b9d3b61599d6f50f5abe0330ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 26 Oct 2023 15:40:46 +0200 Subject: [PATCH 1164/4092] Fix catch distance snap grid not moving Regressed in https://github.com/ppy/osu/pull/25154. Specifically, in 013b5fa916d819ec7a8a93b1692d4aa027934a67 and 74b86349d58e5169e52bbdc70cef3dec81579a74. A simple case of too-much-code-deleted-itis. --- .../Edit/CatchHitObjectComposer.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs index 6f0ee260ab..4172720ada 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; @@ -179,5 +180,33 @@ namespace osu.Game.Rulesets.Catch.Edit return null; } } + + protected override void Update() + { + base.Update(); + + updateDistanceSnapGrid(); + } + + private void updateDistanceSnapGrid() + { + if (DistanceSnapProvider.DistanceSnapToggle.Value != TernaryState.True) + { + distanceSnapGrid.Hide(); + return; + } + + var sourceHitObject = getDistanceSnapGridSourceHitObject(); + + if (sourceHitObject == null) + { + distanceSnapGrid.Hide(); + return; + } + + distanceSnapGrid.Show(); + distanceSnapGrid.StartTime = sourceHitObject.GetEndTime(); + distanceSnapGrid.StartX = sourceHitObject.EffectiveX; + } } } From 79910df9593b8478419b4eb2f4be12b0cfd1dbf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 26 Oct 2023 15:46:32 +0200 Subject: [PATCH 1165/4092] Fix catch distance snap provider not hiding slider properly Regressed in https://github.com/ppy/osu/pull/25171. The old code was kinda dependent on correct order of setting `Disabled`. `CatchHitObjectComposer` would disable distance spacing in its BDL, and then via the base `DistancedHitObjectComposer.LoadComplete()`, the slider would be faded out. The switch to composition broke that ordering. To fix, stop relying on ordering and just respond to changes as they come. That's what bindables are for. --- osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs index 0b1809e7d9..ddf539771d 100644 --- a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs @@ -98,12 +98,6 @@ namespace osu.Game.Rulesets.Edit } }); - if (DistanceSpacingMultiplier.Disabled) - { - distanceSpacingSlider.Hide(); - return; - } - DistanceSpacingMultiplier.Value = editorBeatmap.BeatmapInfo.DistanceSpacing; DistanceSpacingMultiplier.BindValueChanged(multiplier => { @@ -116,6 +110,8 @@ namespace osu.Game.Rulesets.Edit editorBeatmap.BeatmapInfo.DistanceSpacing = multiplier.NewValue; }, true); + DistanceSpacingMultiplier.BindDisabledChanged(disabled => distanceSpacingSlider.Alpha = disabled ? 0 : 1, true); + // Manual binding to handle enabling distance spacing when the slider is interacted with. distanceSpacingSlider.Current.BindValueChanged(spacing => { From 5d6a58d443cf6467bcc72b8db4d36875acbab7bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 26 Oct 2023 16:19:53 +0200 Subject: [PATCH 1166/4092] Add failing test scene for scroll handling in song select --- .../Navigation/TestSceneScreenNavigation.cs | 38 +++++++++++++++++++ osu.Game/Screens/Select/SongSelect.cs | 2 +- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index fa1ebf5c56..a6d4fb0b52 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -7,6 +7,7 @@ using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Configuration; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -16,6 +17,7 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Collections; using osu.Game.Configuration; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.Leaderboards; @@ -34,6 +36,7 @@ using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; using osu.Game.Screens.Select; +using osu.Game.Screens.Select.Carousel; using osu.Game.Screens.Select.Leaderboards; using osu.Game.Screens.Select.Options; using osu.Game.Tests.Beatmaps.IO; @@ -165,6 +168,41 @@ namespace osu.Game.Tests.Visual.Navigation ConfirmAtMainMenu(); } + [Test] + public void TestSongSelectScrollHandling() + { + TestPlaySongSelect songSelect = null; + double scrollPosition = 0; + + AddStep("set game volume to max", () => Game.Dependencies.Get().SetValue(FrameworkSetting.VolumeUniversal, 1d)); + AddUntilStep("wait for volume overlay to hide", () => Game.ChildrenOfType().Single().State.Value, () => Is.EqualTo(Visibility.Hidden)); + PushAndConfirm(() => songSelect = new TestPlaySongSelect()); + AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); + AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); + AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + + AddStep("store scroll position", () => scrollPosition = getCarouselScrollPosition()); + + AddStep("move to left side", () => InputManager.MoveMouseTo( + songSelect.ChildrenOfType().Single().ScreenSpaceDrawQuad.TopLeft + new Vector2(1))); + AddStep("scroll down", () => InputManager.ScrollVerticalBy(-1)); + AddAssert("carousel didn't move", getCarouselScrollPosition, () => Is.EqualTo(scrollPosition)); + + AddRepeatStep("alt-scroll down", () => + { + InputManager.PressKey(Key.AltLeft); + InputManager.ScrollVerticalBy(-1); + InputManager.ReleaseKey(Key.AltLeft); + }, 5); + AddAssert("game volume decreased", () => Game.Dependencies.Get().Get(FrameworkSetting.VolumeUniversal), () => Is.LessThan(1)); + + AddStep("move to carousel", () => InputManager.MoveMouseTo(songSelect.ChildrenOfType().Single())); + AddStep("scroll down", () => InputManager.ScrollVerticalBy(-1)); + AddAssert("carousel moved", getCarouselScrollPosition, () => Is.Not.EqualTo(scrollPosition)); + + double getCarouselScrollPosition() => Game.ChildrenOfType>().Single().Current; + } + /// /// This tests that the F1 key will open the mod select overlay, and not be handled / blocked by the music controller (which has the same default binding /// but should be handled *after* song select). diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index d5ec94ad71..827884f971 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -1019,7 +1019,7 @@ namespace osu.Game.Screens.Select /// /// Handles mouse interactions required when moving away from the carousel. /// - private partial class LeftSideInteractionContainer : Container + internal partial class LeftSideInteractionContainer : Container { private readonly Action? resetCarouselPosition; From 2fa221738184c9ded7f091cea16bf40f4e02765d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 26 Oct 2023 16:26:31 +0200 Subject: [PATCH 1167/4092] Fix left side of carousel blocking volume adjust hotkeys Closes https://github.com/ppy/osu/issues/25234. A little ad-hoc, but probably fine...? --- osu.Game/Screens/Select/SongSelect.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 827884f971..dfea4e3794 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -1028,7 +1028,10 @@ namespace osu.Game.Screens.Select this.resetCarouselPosition = resetCarouselPosition; } - protected override bool OnScroll(ScrollEvent e) => true; + // we want to block plain scrolls on the left side so that they don't scroll the carousel, + // but also we *don't* want to handle scrolls when they're combined with keyboard modifiers + // as those will usually correspond to other interactions like adjusting volume. + protected override bool OnScroll(ScrollEvent e) => !e.ControlPressed && !e.AltPressed && !e.ShiftPressed && !e.SuperPressed; protected override bool OnMouseDown(MouseDownEvent e) => true; From 0482c05d7c031132a17e5e0fa3e4b605051cd2b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 26 Oct 2023 19:27:05 +0200 Subject: [PATCH 1168/4092] Add failing test case --- .../TestSceneMasterGameplayClockContainer.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs index 393217f371..1368b42a3c 100644 --- a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs @@ -108,6 +108,28 @@ namespace osu.Game.Tests.Gameplay AddAssert("gameplay clock time = 10000", () => gameplayClockContainer.CurrentTime, () => Is.EqualTo(10000).Within(10f)); } + [Test] + public void TestStopUsingBeatmapClock() + { + ClockBackedTestWorkingBeatmap working = null; + MasterGameplayClockContainer gameplayClockContainer = null; + BindableDouble frequencyAdjustment = new BindableDouble(2); + + AddStep("create container", () => + { + working = new ClockBackedTestWorkingBeatmap(new OsuRuleset().RulesetInfo, new FramedClock(new ManualClock()), Audio); + Child = gameplayClockContainer = new MasterGameplayClockContainer(working, 0); + + gameplayClockContainer.Reset(startClock: true); + }); + + AddStep("apply frequency adjustment", () => gameplayClockContainer.AdjustmentsFromMods.AddAdjustment(AdjustableProperty.Frequency, frequencyAdjustment)); + AddAssert("track frequency changed", () => working.Track.AggregateFrequency.Value, () => Is.EqualTo(2)); + + AddStep("stop using beatmap clock", () => gameplayClockContainer.StopUsingBeatmapClock()); + AddAssert("frequency adjustment unapplied", () => working.Track.AggregateFrequency.Value, () => Is.EqualTo(1)); + } + protected override void Dispose(bool isDisposing) { localConfig?.Dispose(); From 565ae99e0dfddbecef6bc24828ba6c39c837f5cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 26 Oct 2023 19:27:50 +0200 Subject: [PATCH 1169/4092] Fix `StopUsingBeatmapClock()` applying adjustments to track it was supposed to stop using - Closes https://github.com/ppy/osu/issues/25248 - Possibly also closes https://github.com/ppy/osu/issues/20475 Regressed in e33486a766044c17c2f254f5e8df6d72b29c341e. `StopUsingBeatmapClock()` intends to, as the name says, stop operating on the working beatmap clock to yield its usage to other components on exit. As part of that it tries to unapply audio adjustments so that other screens can apply theirs freely instead. However, the aforementioned commit introduced a bug in this. Previously to it, `track` was an alias for the `SourceClock`, which could be mutated in an indirect way via `ChangeSource()` calls. The aforementioned commit made `track` a `readonly` field, initialised in constructor, which would _never_ change value. In particular, it would _always_ be the beatmap track, which meant that `StopUsingBeatmapClock()` would remove the adjustments from the beatmap track, but then at the end of the method, _apply them onto that same track again_. This was only saved by the fact that clock adjustments are removed again on disposal of the `MasterGameplayClockContainer()`. This - due to async disposal pressure - could explain infrequently reported cases wherein the track would just continue to speed up ad infinitum. To fix, fully substitute the beatmap track for a virtual track at the point of calling `StopUsingBeatmapClock()`. --- osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 70d9ecd3e7..6e07b01b5f 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Play private readonly WorkingBeatmap beatmap; - private readonly Track track; + private Track track; private readonly double skipTargetTime; @@ -188,11 +188,11 @@ namespace osu.Game.Screens.Play { removeSourceClockAdjustments(); - var virtualTrack = new TrackVirtual(beatmap.Track.Length); - virtualTrack.Seek(CurrentTime); + track = new TrackVirtual(beatmap.Track.Length); + track.Seek(CurrentTime); if (IsRunning) - virtualTrack.Start(); - ChangeSource(virtualTrack); + track.Start(); + ChangeSource(track); addSourceClockAdjustments(); } From fdb81bfa4ca2d9819bb0c252059b7b84919af583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 26 Oct 2023 19:38:41 +0200 Subject: [PATCH 1170/4092] Rename methods to not mention "source clock" anymore --- .../Screens/Play/MasterGameplayClockContainer.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 6e07b01b5f..1c860e9d4b 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -145,7 +145,7 @@ namespace osu.Game.Screens.Play protected override void StartGameplayClock() { - addSourceClockAdjustments(); + addAdjustmentsToTrack(); base.StartGameplayClock(); @@ -186,7 +186,7 @@ namespace osu.Game.Screens.Play /// public void StopUsingBeatmapClock() { - removeSourceClockAdjustments(); + removeAdjustmentsFromTrack(); track = new TrackVirtual(beatmap.Track.Length); track.Seek(CurrentTime); @@ -194,12 +194,12 @@ namespace osu.Game.Screens.Play track.Start(); ChangeSource(track); - addSourceClockAdjustments(); + addAdjustmentsToTrack(); } private bool speedAdjustmentsApplied; - private void addSourceClockAdjustments() + private void addAdjustmentsToTrack() { if (speedAdjustmentsApplied) return; @@ -213,7 +213,7 @@ namespace osu.Game.Screens.Play speedAdjustmentsApplied = true; } - private void removeSourceClockAdjustments() + private void removeAdjustmentsFromTrack() { if (!speedAdjustmentsApplied) return; @@ -228,7 +228,7 @@ namespace osu.Game.Screens.Play protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - removeSourceClockAdjustments(); + removeAdjustmentsFromTrack(); } ControlPointInfo IBeatSyncProvider.ControlPoints => beatmap.Beatmap.ControlPointInfo; From 24b1d1e9558badb2f1a6cb5dc30a5aa6fc79f41d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Oct 2023 18:18:07 +0900 Subject: [PATCH 1171/4092] Fix code quality fail --- osu.Game.Rulesets.Osu/Mods/OsuModFreezeFrame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFreezeFrame.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFreezeFrame.cs index 89a00d6c32..f1197ce0cd 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFreezeFrame.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFreezeFrame.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override LocalisableString Description => "Burn the notes into your memory."; //Alters the transforms of the approach circles, breaking the effects of these mods. - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModApproachDifferent), typeof(OsuModTransform) }).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModApproachDifferent), typeof(OsuModTransform) }).ToArray(); public override ModType Type => ModType.Fun; From f931f4c324f96063a3964fc660daf06d99e657d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Oct 2023 18:19:44 +0900 Subject: [PATCH 1172/4092] Remove reundant interface specification --- osu.Game/Skinning/SkinInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index c2b80b7ead..9763d3b57e 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -14,7 +14,7 @@ namespace osu.Game.Skinning { [MapTo("Skin")] [JsonObject(MemberSerialization.OptIn)] - public class SkinInfo : RealmObject, IHasRealmFiles, IEquatable, IHasGuidPrimaryKey, ISoftDelete, IHasNamedFiles + public class SkinInfo : RealmObject, IHasRealmFiles, IEquatable, IHasGuidPrimaryKey, ISoftDelete { internal static readonly Guid TRIANGLES_SKIN = new Guid("2991CFD8-2140-469A-BCB9-2EC23FBCE4AD"); internal static readonly Guid ARGON_SKIN = new Guid("CFFA69DE-B3E3-4DEE-8563-3C4F425C05D0"); From 7140eee870bab0d86ab25c15f3d56dce1b68d627 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 27 Oct 2023 11:38:10 +0200 Subject: [PATCH 1173/4092] Add failing test coverage for quick retry after completion not changing rank --- osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index a6d4fb0b52..2f378917e6 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -259,6 +259,7 @@ namespace osu.Game.Tests.Visual.Navigation var getOriginalPlayer = playToCompletion(); AddStep("attempt to retry", () => getOriginalPlayer().ChildrenOfType().First().Action()); + AddAssert("original play isn't failed", () => getOriginalPlayer().Score.ScoreInfo.Rank, () => Is.Not.EqualTo(ScoreRank.F)); AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen != getOriginalPlayer() && Game.ScreenStack.CurrentScreen is Player); } From 86a8ab6db6548d211e5f0e5ff1a4feb4b7a506c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 27 Oct 2023 11:39:43 +0200 Subject: [PATCH 1174/4092] Fix quick retry immediately after completion marking score as failed Closes https://github.com/ppy/osu/issues/25247. The scenario involved here is as follows: 1. User completes beatmap by hitting all notes. 2. `checkScoreCompleted()` determines completion, and calls `progressToResults(withDelay: true)`. 3. `progressToResults()` schedules `resultsDisplayDelegate`, which includes a call to `prepareAndImportScoreAsync()`, a second in the future. 4. User presses quick retry hotkey. This calls `Player.Restart(quickRestart: true)`, which invokes `Player.RestartRequested`, which in turn calls `PlayerLoader.restartRequested(true)`, which in turn causes `PlayerLoader` to make itself current, which means that `Player.OnExiting()` will get called. 5. `Player.OnExiting()` sees that `prepareScoreForDisplayTask` is null (because `prepareAndImportScoreAsync()` - which sets it - is scheduled to happen in the future), and as such assumes that the score did not complete. Thus, it marks the score as failed. 6. `Player.Restart()` after invoking `RestartRequested` calls `PerformExit(false)`, which then will unconditionally call `prepareAndImportScoreAsync()`. But the score has already been marked as failed. The flow above can be described as "convoluted", but I'm not sure I have it in me right now to try and refactor it again. Therefore, to fix, switch the `prepareScoreForDisplayTask` null check in `Player.OnExiting()` to check `GameplayState.HasPassed` instead, as it is not susceptible to the same out-of-order read issue. --- osu.Game/Screens/Play/Player.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 97bfa35d49..18d0a65d7a 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1115,8 +1115,7 @@ namespace osu.Game.Screens.Play if (!GameplayState.HasPassed && !GameplayState.HasFailed) GameplayState.HasQuit = true; - // if arriving here and the results screen preparation task hasn't run, it's safe to say the user has not completed the beatmap. - if (prepareScoreForDisplayTask == null && DrawableRuleset.ReplayScore == null) + if (!GameplayState.HasPassed && DrawableRuleset.ReplayScore == null) ScoreProcessor.FailScore(Score.ScoreInfo); } From 3944b045ed4582b233066dc714be6075b47482f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 27 Oct 2023 11:58:02 +0200 Subject: [PATCH 1175/4092] Add extra test coverage for marking score as failed --- osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 2f378917e6..7fa4f8c836 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -247,6 +247,7 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("end spectator before retry", () => Game.SpectatorClient.EndPlaying(player.GameplayState)); AddStep("attempt to retry", () => player.ChildrenOfType().First().Action()); + AddAssert("old player score marked failed", () => player.Score.ScoreInfo.Rank, () => Is.EqualTo(ScoreRank.F)); AddUntilStep("wait for old player gone", () => Game.ScreenStack.CurrentScreen != player); AddUntilStep("get new player", () => (player = Game.ScreenStack.CurrentScreen as Player) != null); From 96d784e06bd6068f09620eaaa45c4766d0da900d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 27 Oct 2023 12:39:54 +0200 Subject: [PATCH 1176/4092] Delete `ScoreInfo.HasReplay` as no longer needed --- osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs | 2 +- osu.Game/Scoring/IScoreInfo.cs | 2 -- osu.Game/Scoring/ScoreInfo.cs | 2 -- osu.Game/Screens/Ranking/ReplayDownloadButton.cs | 2 +- 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index 6ccf73d8ff..5b32f380b9 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -213,7 +213,7 @@ namespace osu.Game.Tests.Visual.Gameplay OnlineID = hasOnlineId ? online_score_id : 0, Ruleset = new OsuRuleset().RulesetInfo, BeatmapInfo = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(), - Hash = replayAvailable ? "online" : string.Empty, + HasOnlineReplay = replayAvailable, User = new APIUser { Id = 39828, diff --git a/osu.Game/Scoring/IScoreInfo.cs b/osu.Game/Scoring/IScoreInfo.cs index cde48c3be3..a1d076b8c2 100644 --- a/osu.Game/Scoring/IScoreInfo.cs +++ b/osu.Game/Scoring/IScoreInfo.cs @@ -22,8 +22,6 @@ namespace osu.Game.Scoring double Accuracy { get; } - bool HasReplay { get; } - long LegacyOnlineID { get; } DateTimeOffset Date { get; } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 722d83cac8..d712702331 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -94,8 +94,6 @@ namespace osu.Game.Scoring public double Accuracy { get; set; } - public bool HasReplay => !string.IsNullOrEmpty(Hash) || HasOnlineReplay; - [Ignored] public bool HasOnlineReplay { get; set; } diff --git a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs index b6166e97f6..df5f9c7a8a 100644 --- a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs +++ b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs @@ -37,7 +37,7 @@ namespace osu.Game.Screens.Ranking if (State.Value == DownloadState.LocallyAvailable) return ReplayAvailability.Local; - if (Score.Value?.HasReplay == true) + if (Score.Value?.HasOnlineReplay == true) return ReplayAvailability.Online; return ReplayAvailability.NotAvailable; From 32fc19ea0d2515aeab4e84248f6cf67ec7d7de98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 27 Oct 2023 13:22:17 +0200 Subject: [PATCH 1177/4092] Fix results screen test failure --- osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 146482e6fb..ab2e867255 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -362,7 +362,7 @@ namespace osu.Game.Tests.Visual.Ranking { var score = TestResources.CreateTestScoreInfo(); score.TotalScore += 10 - i; - score.Hash = $"test{i}"; + score.HasOnlineReplay = true; scores.Add(score); } From 2d5b1711f6ffc233ca308153a665d18dcbbccea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 27 Oct 2023 13:27:48 +0200 Subject: [PATCH 1178/4092] Share `!HasPassed` condition --- osu.Game/Screens/Play/Player.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 18d0a65d7a..a1ec0b3167 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1110,12 +1110,12 @@ namespace osu.Game.Screens.Play failAnimationContainer?.Stop(); PauseOverlay?.StopAllSamples(); - if (LoadedBeatmapSuccessfully) + if (LoadedBeatmapSuccessfully && !GameplayState.HasPassed) { - if (!GameplayState.HasPassed && !GameplayState.HasFailed) + if (!GameplayState.HasFailed) GameplayState.HasQuit = true; - if (!GameplayState.HasPassed && DrawableRuleset.ReplayScore == null) + if (DrawableRuleset.ReplayScore == null) ScoreProcessor.FailScore(Score.ScoreInfo); } From dc7f5cd6edc94275a56b3b5cdb5766c76c84181c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 27 Oct 2023 13:30:51 +0200 Subject: [PATCH 1179/4092] Add preventive assertions concerning submission flow state --- osu.Game/Screens/Play/Player.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a1ec0b3167..58c8c3389a 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1112,6 +1112,8 @@ namespace osu.Game.Screens.Play if (LoadedBeatmapSuccessfully && !GameplayState.HasPassed) { + Debug.Assert(resultsDisplayDelegate == null && prepareScoreForDisplayTask == null); + if (!GameplayState.HasFailed) GameplayState.HasQuit = true; From 6789a522d6d79d6fd1d4f8c15b9c83b119ab61b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 27 Oct 2023 14:15:30 +0200 Subject: [PATCH 1180/4092] Rename test to distinguish it from test-to-come --- .../Visual/Navigation/TestSceneSkinEditorNavigation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs index 88904bf85b..d08d53f747 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Navigation private SkinEditor skinEditor => Game.ChildrenOfType().FirstOrDefault(); [Test] - public void TestEditComponentDuringGameplay() + public void TestEditComponentFromGameplayScene() { advanceToSongSelect(); openSkinEditor(); From b5cb5380045b0d8be6031cbdffb4a799ac402bd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 27 Oct 2023 14:23:41 +0200 Subject: [PATCH 1181/4092] Add failing test case for skin editor freeze --- .../TestSceneSkinEditorNavigation.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs index d08d53f747..c17a9ddf5f 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs @@ -5,6 +5,7 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; @@ -18,6 +19,7 @@ using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD.HitErrorMeters; +using osu.Game.Skinning; using osu.Game.Tests.Beatmaps.IO; using osuTK; using osuTK.Input; @@ -69,6 +71,28 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert("value is less than default", () => hitErrorMeter.JudgementLineThickness.Value < hitErrorMeter.JudgementLineThickness.Default); } + [Test] + public void TestMutateProtectedSkinDuringGameplay() + { + advanceToSongSelect(); + AddStep("set default skin", () => Game.Dependencies.Get().CurrentSkinInfo.SetDefault()); + + AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); + AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + + AddStep("enable NF", () => Game.SelectedMods.Value = new[] { new OsuModNoFail() }); + AddStep("enter gameplay", () => InputManager.Key(Key.Enter)); + + AddUntilStep("wait for player", () => + { + DismissAnyNotifications(); + return Game.ScreenStack.CurrentScreen is Player; + }); + + openSkinEditor(); + AddUntilStep("current skin is mutable", () => !Game.Dependencies.Get().CurrentSkin.Value.SkinInfo.Value.Protected); + } + [Test] public void TestComponentsDeselectedOnSkinEditorHide() { From 5ad962070c0448b81c8457b8816818b6e6041502 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 27 Oct 2023 14:34:30 +0200 Subject: [PATCH 1182/4092] Fix skin editor freezing game if opened during active gameplay --- osu.Game/Database/ImportParameters.cs | 6 ++++++ osu.Game/Database/RealmArchiveModelImporter.cs | 6 +++--- osu.Game/Skinning/SkinManager.cs | 5 ++++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/ImportParameters.cs b/osu.Game/Database/ImportParameters.cs index 83ca0ac694..8d37597afc 100644 --- a/osu.Game/Database/ImportParameters.cs +++ b/osu.Game/Database/ImportParameters.cs @@ -21,5 +21,11 @@ namespace osu.Game.Database /// Whether this import should use hard links rather than file copy operations if available. /// public bool PreferHardLinks { get; set; } + + /// + /// If set to , this import will not respect . + /// This is useful for cases where an import must complete even if gameplay is in progress. + /// + public bool ImportImmediately { get; set; } } } diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index 730465e1b0..5383040eb4 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -261,7 +261,7 @@ namespace osu.Game.Database /// An optional cancellation token. public virtual Live? ImportModel(TModel item, ArchiveReader? archive = null, ImportParameters parameters = default, CancellationToken cancellationToken = default) => Realm.Run(realm => { - pauseIfNecessary(cancellationToken); + pauseIfNecessary(parameters, cancellationToken); TModel? existing; @@ -560,9 +560,9 @@ namespace osu.Game.Database /// Whether to perform deletion. protected virtual bool ShouldDeleteArchive(string path) => false; - private void pauseIfNecessary(CancellationToken cancellationToken) + private void pauseIfNecessary(ImportParameters importParameters, CancellationToken cancellationToken) { - if (!PauseImports) + if (!PauseImports || importParameters.ImportImmediately) return; Logger.Log($@"{GetType().Name} is being paused."); diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index ca46d3af0c..59c2a8bca0 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -182,7 +182,10 @@ namespace osu.Game.Skinning Name = NamingUtils.GetNextBestName(existingSkinNames, $@"{s.Name} (modified)") }; - var result = skinImporter.ImportModel(skinInfo); + var result = skinImporter.ImportModel(skinInfo, parameters: new ImportParameters + { + ImportImmediately = true // to avoid possible deadlocks when editing skin during gameplay. + }); if (result != null) { From 35f30d6135b29b73b68d861bb68564cc951f1a27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 27 Oct 2023 14:39:58 +0200 Subject: [PATCH 1183/4092] Scale back debug assertion The import preparation task can actually be non-null when exiting even if the player hasn't passed: - fail beatmap - click import button to import the failed replay --- 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 58c8c3389a..2392fd9f76 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1112,7 +1112,7 @@ namespace osu.Game.Screens.Play if (LoadedBeatmapSuccessfully && !GameplayState.HasPassed) { - Debug.Assert(resultsDisplayDelegate == null && prepareScoreForDisplayTask == null); + Debug.Assert(resultsDisplayDelegate == null); if (!GameplayState.HasFailed) GameplayState.HasQuit = true; From 7a5f3b856f41d3288b2d7940cb9009dabf8d8770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 27 Oct 2023 19:43:49 +0200 Subject: [PATCH 1184/4092] Add visual test coverage for displaying hitcircles with high combo index --- .../Resources/special-skin/display-0@2x.png | Bin 0 -> 5085 bytes .../Resources/special-skin/display-1@2x.png | Bin 0 -> 1262 bytes .../Resources/special-skin/display-2@2x.png | Bin 0 -> 3534 bytes .../Resources/special-skin/display-3@2x.png | Bin 0 -> 3456 bytes .../Resources/special-skin/display-4@2x.png | Bin 0 -> 3213 bytes .../Resources/special-skin/display-5@2x.png | Bin 0 -> 3647 bytes .../Resources/special-skin/display-6@2x.png | Bin 0 -> 4954 bytes .../Resources/special-skin/display-7@2x.png | Bin 0 -> 2503 bytes .../Resources/special-skin/display-8@2x.png | Bin 0 -> 5710 bytes .../Resources/special-skin/display-9@2x.png | Bin 0 -> 5195 bytes .../Resources/special-skin/skin.ini | 1 + .../TestSceneHitCircle.cs | 10 ++++++---- 12 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/display-0@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/display-1@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/display-2@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/display-3@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/display-4@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/display-5@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/display-6@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/display-7@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/display-8@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/display-9@2x.png diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/display-0@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/display-0@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..67d2e2cf04a71c59957a79a69ef6de8e02a6bbb2 GIT binary patch literal 5085 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/display-1@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/display-1@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..2df10655efabdb9eefbad32abd4be42deef4fb02 GIT binary patch literal 1262 zcmeAS@N?(olHy`uVBq!ia0vp^IzXJo!3HFcw{9)~QjEnx?oJHr&dIz4a#+$GeH|GX zHuiJ>Nn{1`ISV`@iy0XBc7rhE@gG;rfNGjOT^vIy;@-}&_Lp`PXb?Yi*oMiei6eZX zfU1z7(2@mSQC`1P_AA)8sp&2}q+9ADa#Dn?(LsUB;Y2`!gFxIQQK=JW?)7fIcf0!C z+nap9Jtu!%`@YQnSn+i03$r45E#T>A1DjarutZbLY;Te{PxcT!RyJ zZO@dCM0Z@jaos^q!A~K~@t5^`#;ikmY5SiP?ezEKPxkls-&0#x$Cs07uar}t)w!Uu zs=9jmgQrTVyJY_t9yB^|rf8PqHP*QodX>47&wet>5R2UIy8PBp6|Ul0KG&Kf4p$ti zef#RwEFu0^FJ2@#J3HqjBs6?)y?E`}&JQe??!9~R=G(VvckkTU@cQ*@tDF1w?bFQ= zdvWA5M|oM9-$OH56CyP%1t=SibYQ|{=?Z{M`((6s5(Zyr0wcHgbw9}rk2`tx>gYPtU; z^W-Xl=i(nZl(jMsh?(vgRw^U5fQT9IZw=nhS-=x$TCxXIM zJj4RIy4it_X}L2$WLlr|pA^CVf(!l`ULYyXvj!7e*YtSV1+^XR=gyp>BV>8FMKkha z#jNns&f2Hja!N|R$eO%QTyB$h+41h(yF7W8H}Bq+-5+jiVXG2z>dZZ7&-#Yxh(#_8c(DHb z`SU;j?dsU1#gsi`0{g_QQBNo6Yb>nm|RUf(A7WbUDqjcdbJzxnuaV*YETetl)G zcqa?d_d>4I3<{?T99Pde6fpnC>II=Ylyn^bG~H;@X6cX7JHB}N^5r~%NB@R4FLj)t z6BK^<-Q<@_N6b4|UH>%fFj4hp@vQcTa4~-2&K=+=#)~E3KRos__o(YBFc2m zR-H)}hnM}Y{-!)}gV(mytR1RGYbI@#{HC~-C;jN@vm4$S$A)Y%o?{v%DVF(bHOoaI zQy-H>I&pct>8)kF>Aj+6Bq855_J1#&y5qI<&~zbTV`Jl-va)IV>V917w#7zACj&D` zUca;5{BV8YzNYyfZrqTNmXo^{CD!=brQqk$j~_SQew^TPLuJ#4`ua;BmkOUdedfT= zf;hjJ#PgRrdBDs!_5!<0=N!F0iKYIjhm3<&w|-q<-OBExIz4|`-`mdZZe_Mq1eQ?@ Mp00i_>zopr0AYwh{{R30 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/display-2@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/display-2@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..eeb8ec0edf1953c6a8b036e6e33207bb99f84bf6 GIT binary patch literal 3534 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 IM6N<$f*~28E&u=k literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/display-3@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/display-3@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4ee73f503aa11548b1dc44347629007b288ceefb GIT binary patch literal 3456 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 A#sB~S literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/display-9@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/display-9@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..53b687fdea4d84005a44887e130fff5044936aa1 GIT binary patch literal 5195 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 FV1j9g503x< literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini index 49ac2cf80d..9d16267d73 100644 --- a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini +++ b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini @@ -1,3 +1,4 @@ [General] Version: latest HitCircleOverlayAboveNumber: 0 +HitCirclePrefix: display \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs index af02087d1a..30b0451a3b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs @@ -34,6 +34,7 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("Hit Big Stream", () => SetContents(_ => testStream(2, true))); AddStep("Hit Medium Stream", () => SetContents(_ => testStream(5, true))); AddStep("Hit Small Stream", () => SetContents(_ => testStream(7, true))); + AddStep("High combo index", () => SetContents(_ => testSingle(2, true, comboIndex: 15))); } [Test] @@ -66,12 +67,12 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("Hit Big Single", () => SetContents(_ => testSingle(2, true))); } - private Drawable testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null) + private Drawable testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null, int comboIndex = 0) { var playfield = new TestOsuPlayfield(); for (double t = timeOffset; t < timeOffset + 60000; t += 2000) - playfield.Add(createSingle(circleSize, auto, t, positionOffset)); + playfield.Add(createSingle(circleSize, auto, t, positionOffset, comboIndex: comboIndex)); return playfield; } @@ -84,14 +85,14 @@ namespace osu.Game.Rulesets.Osu.Tests for (int i = 0; i <= 1000; i += 100) { - playfield.Add(createSingle(circleSize, auto, i, pos, hitOffset)); + playfield.Add(createSingle(circleSize, auto, i, pos, hitOffset, i / 100 - 1)); pos.X += 50; } return playfield; } - private TestDrawableHitCircle createSingle(float circleSize, bool auto, double timeOffset, Vector2? positionOffset, double hitOffset = 0) + private TestDrawableHitCircle createSingle(float circleSize, bool auto, double timeOffset, Vector2? positionOffset, double hitOffset = 0, int comboIndex = 0) { positionOffset ??= Vector2.Zero; @@ -99,6 +100,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = Time.Current + 1000 + timeOffset, Position = OsuPlayfield.BASE_SIZE / 4 + positionOffset.Value, + IndexInCurrentCombo = comboIndex, }; circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize }); From c9cb0561f7a2ac5117d9add6792e02b53014c1a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 27 Oct 2023 19:48:10 +0200 Subject: [PATCH 1185/4092] Move `maxSizePerGlyph` optional ctor param to init-only property I'm doing this as I'm about to add more similar properties to `LegacySpriteText` and I don't want to create a twenty-argument constructor monstrosity. --- .../Skinning/Legacy/OsuLegacySkinTransformer.cs | 3 ++- osu.Game/Skinning/LegacySpriteText.cs | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 1de8fefaae..c01d28c8e1 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -145,10 +145,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return null; const float hitcircle_text_scale = 0.8f; - return new LegacySpriteText(LegacyFont.HitCircle, OsuHitObject.OBJECT_DIMENSIONS * 2 / hitcircle_text_scale) + return new LegacySpriteText(LegacyFont.HitCircle) { // stable applies a blanket 0.8x scale to hitcircle fonts Scale = new Vector2(hitcircle_text_scale), + MaxSizePerGlyph = OsuHitObject.OBJECT_DIMENSIONS * 2 / hitcircle_text_scale, }; case OsuSkinComponents.SpinnerBody: diff --git a/osu.Game/Skinning/LegacySpriteText.cs b/osu.Game/Skinning/LegacySpriteText.cs index 7eb92126fa..a803ef8747 100644 --- a/osu.Game/Skinning/LegacySpriteText.cs +++ b/osu.Game/Skinning/LegacySpriteText.cs @@ -12,8 +12,9 @@ namespace osu.Game.Skinning { public sealed partial class LegacySpriteText : OsuSpriteText { + public Vector2? MaxSizePerGlyph { get; init; } + private readonly LegacyFont font; - private readonly Vector2? maxSizePerGlyph; private LegacyGlyphStore glyphStore = null!; @@ -21,10 +22,9 @@ namespace osu.Game.Skinning protected override char[] FixedWidthExcludeCharacters => new[] { ',', '.', '%', 'x' }; - public LegacySpriteText(LegacyFont font, Vector2? maxSizePerGlyph = null) + public LegacySpriteText(LegacyFont font) { this.font = font; - this.maxSizePerGlyph = maxSizePerGlyph; Shadow = false; UseFullGlyphHeight = false; @@ -36,7 +36,7 @@ namespace osu.Game.Skinning Font = new FontUsage(skin.GetFontPrefix(font), 1, fixedWidth: true); Spacing = new Vector2(-skin.GetFontOverlap(font), 0); - glyphStore = new LegacyGlyphStore(skin, maxSizePerGlyph); + glyphStore = new LegacyGlyphStore(skin, MaxSizePerGlyph); } protected override TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) => base.CreateTextBuilder(glyphStore); From 99e590c8dd645c58b2fa6197e2f9bd7b003b9e27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 27 Oct 2023 20:10:36 +0200 Subject: [PATCH 1186/4092] Fix legacy sprite texts not matching stable with respect to fixed width stable's `pSpriteText` has a `TextConstantSpacing` flag, that is selectively enabled for some usages. In particular, these are: - mania combo counter (not yet implemented) - taiko combo counter (not yet implemented) - score counter - accuracy counter - scoreboard entries (not yet implemented) Everything else uses non-fixed-width fonts. Hilariously, `LegacySpinner` _tried_ to account for this by changing `Font` to have `fixedWidth: false` specified, only to fail to notice that `LegacySpriteText` changes `Font` in its BDL, making the property set do precisely nothing. For this reason, attempting to set `Font` on a `LegacySpriteText` will now throw. --- .../Skinning/Legacy/LegacySpinner.cs | 4 ++-- osu.Game/Skinning/LegacyAccuracyCounter.cs | 1 + osu.Game/Skinning/LegacyScoreCounter.cs | 1 + osu.Game/Skinning/LegacySpriteText.cs | 13 ++++++++++++- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs index 28acb4a996..5a95eac0f1 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Origin = Anchor.Centre, Scale = new Vector2(SPRITE_SCALE), Y = SPINNER_TOP_OFFSET + 299, - }.With(s => s.Font = s.Font.With(fixedWidth: false)), + }, spmBackground = new Sprite { Anchor = Anchor.TopCentre, @@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Origin = Anchor.TopRight, Scale = new Vector2(SPRITE_SCALE * 0.9f), Position = new Vector2(80, 448 + spm_hide_offset), - }.With(s => s.Font = s.Font.With(fixedWidth: false)), + }, } }); } diff --git a/osu.Game/Skinning/LegacyAccuracyCounter.cs b/osu.Game/Skinning/LegacyAccuracyCounter.cs index 326257c25f..ed12292eb3 100644 --- a/osu.Game/Skinning/LegacyAccuracyCounter.cs +++ b/osu.Game/Skinning/LegacyAccuracyCounter.cs @@ -25,6 +25,7 @@ namespace osu.Game.Skinning { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, + FixedWidth = true, }; } } diff --git a/osu.Game/Skinning/LegacyScoreCounter.cs b/osu.Game/Skinning/LegacyScoreCounter.cs index d238369be1..a86f122836 100644 --- a/osu.Game/Skinning/LegacyScoreCounter.cs +++ b/osu.Game/Skinning/LegacyScoreCounter.cs @@ -28,6 +28,7 @@ namespace osu.Game.Skinning { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, + FixedWidth = true, }; } } diff --git a/osu.Game/Skinning/LegacySpriteText.cs b/osu.Game/Skinning/LegacySpriteText.cs index a803ef8747..041a32e8de 100644 --- a/osu.Game/Skinning/LegacySpriteText.cs +++ b/osu.Game/Skinning/LegacySpriteText.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 System; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; @@ -13,6 +14,7 @@ namespace osu.Game.Skinning public sealed partial class LegacySpriteText : OsuSpriteText { public Vector2? MaxSizePerGlyph { get; init; } + public bool FixedWidth { get; init; } private readonly LegacyFont font; @@ -22,6 +24,15 @@ namespace osu.Game.Skinning protected override char[] FixedWidthExcludeCharacters => new[] { ',', '.', '%', 'x' }; + // ReSharper disable once UnusedMember.Global + // being unused is the point here + public new FontUsage Font + { + get => base.Font; + set => throw new InvalidOperationException(@"Attempting to use this setter will not work correctly. " + + $@"Use specific init-only properties exposed by {nameof(LegacySpriteText)} instead."); + } + public LegacySpriteText(LegacyFont font) { this.font = font; @@ -33,7 +44,7 @@ namespace osu.Game.Skinning [BackgroundDependencyLoader] private void load(ISkinSource skin) { - Font = new FontUsage(skin.GetFontPrefix(font), 1, fixedWidth: true); + base.Font = new FontUsage(skin.GetFontPrefix(font), 1, fixedWidth: FixedWidth); Spacing = new Vector2(-skin.GetFontOverlap(font), 0); glyphStore = new LegacyGlyphStore(skin, MaxSizePerGlyph); From 2f9b50172e0b0f261a94585d2c525405a77eaa48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 27 Oct 2023 22:09:33 +0200 Subject: [PATCH 1187/4092] Add failing test coverage for video events affecting storyboard time bounds --- .../Formats/LegacyStoryboardDecoderTest.cs | 21 +++++++++++++++++++ .../video-background-events-ignored.osb | 5 +++++ 2 files changed, 26 insertions(+) create mode 100644 osu.Game.Tests/Resources/video-background-events-ignored.osb diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs index 34ff8bfd84..647c0aed75 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs @@ -287,5 +287,26 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(manyTimes.EndTime, Is.EqualTo(9000 + loop_duration)); } } + + [Test] + public void TestVideoAndBackgroundEventsDoNotAffectStoryboardBounds() + { + var decoder = new LegacyStoryboardDecoder(); + + using var resStream = TestResources.OpenResource("video-background-events-ignored.osb"); + using var stream = new LineBufferedReader(resStream); + + var storyboard = decoder.Decode(stream); + + Assert.Multiple(() => + { + Assert.That(storyboard.GetLayer(@"Video").Elements, Has.Count.EqualTo(1)); + Assert.That(storyboard.GetLayer(@"Video").Elements.Single(), Is.InstanceOf()); + Assert.That(storyboard.GetLayer(@"Video").Elements.Single().StartTime, Is.EqualTo(-5678)); + + Assert.That(storyboard.EarliestEventTime, Is.Null); + Assert.That(storyboard.LatestEventTime, Is.Null); + }); + } } } diff --git a/osu.Game.Tests/Resources/video-background-events-ignored.osb b/osu.Game.Tests/Resources/video-background-events-ignored.osb new file mode 100644 index 0000000000..7525b1fee9 --- /dev/null +++ b/osu.Game.Tests/Resources/video-background-events-ignored.osb @@ -0,0 +1,5 @@ +osu file format v14 + +[Events] +0,-1234,"BG.jpg",0,0 +Video,-5678,"Video.avi",0,0 \ No newline at end of file From 9ce2c1f49c37e63d3d6c18b8fb854ee3b4766adf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 27 Oct 2023 22:10:03 +0200 Subject: [PATCH 1188/4092] Exclude video events from being accounted for when calculating storyboard time bounds Closes https://github.com/ppy/osu/issues/25263. In some circumstances, stable allows skipping twice if a particularly long storyboarded intro is being displayed: https://github.com/peppy/osu-stable-reference/blob/master/osu!/GameModes/Play/Player.cs#L1728-L1736 `AllowDoubleSkip` is calculated thus: https://github.com/peppy/osu-stable-reference/blob/3ea48705eb67172c430371dcfc8a16a002ed0d3d/osu!/GameModes/Play/Player.cs#L1761-L1770 and `leadInTime` is calculated thus: https://github.com/peppy/osu-stable-reference/blob/3ea48705eb67172c430371dcfc8a16a002ed0d3d/osu!/GameModes/Play/Player.cs#L1342-L1351 The key to watch out for here is `{first,last}EventTime`. `EventManager` will calculate it on-the-fly as it adds storyboard elements: https://github.com/peppy/osu-stable-reference/blob/3ea48705eb67172c430371dcfc8a16a002ed0d3d/osu!/GameplayElements/Events/EventManager.cs#L253-L256 However, this pathway is only used for sprite, animation, sample, and break events. Video and background events use the following pathway: https://github.com/peppy/osu-stable-reference/blob/master/osu!/GameplayElements/Events/EventManager.cs#L368 Note that this particular overload does not mutate either bound. Which means that for the purposes of determining where a storyboard starts and ends temporally, a video event's start time is essentially ignored. To reflect that, add a clause that excludes video events from calculations of `{Earliest,Latest}EventTime`. --- osu.Game/Storyboards/Storyboard.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 21342831b0..a3137fe1b1 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -30,8 +30,11 @@ namespace osu.Game.Storyboards /// /// /// This iterates all elements and as such should be used sparingly or stored locally. + /// Video and background events are not included to match stable. /// - public double? EarliestEventTime => Layers.SelectMany(l => l.Elements).MinBy(e => e.StartTime)?.StartTime; + public double? EarliestEventTime => Layers.SelectMany(l => l.Elements) + .Where(e => e is not StoryboardVideo) + .MinBy(e => e.StartTime)?.StartTime; /// /// Across all layers, find the latest point in time that a storyboard element ends at. @@ -39,9 +42,12 @@ namespace osu.Game.Storyboards /// /// /// This iterates all elements and as such should be used sparingly or stored locally. - /// Videos and samples return StartTime as their EndTIme. + /// Samples return StartTime as their EndTIme. + /// Video and background events are not included to match stable. /// - public double? LatestEventTime => Layers.SelectMany(l => l.Elements).MaxBy(e => e.GetEndTime())?.GetEndTime(); + public double? LatestEventTime => Layers.SelectMany(l => l.Elements) + .Where(e => e is not StoryboardVideo) + .MaxBy(e => e.GetEndTime())?.GetEndTime(); /// /// Depth of the currently front-most storyboard layer, excluding the overlay layer. From bac306879a4c2965b780c8588f97e95c24e354d0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 28 Oct 2023 03:18:13 +0300 Subject: [PATCH 1189/4092] Minor reword on documentation --- osu.Game/Storyboards/Storyboard.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index a3137fe1b1..8c43b99702 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -30,6 +30,7 @@ namespace osu.Game.Storyboards /// /// /// This iterates all elements and as such should be used sparingly or stored locally. + /// Sample events use their start time as "end time" during this calculation. /// Video and background events are not included to match stable. /// public double? EarliestEventTime => Layers.SelectMany(l => l.Elements) @@ -42,7 +43,7 @@ namespace osu.Game.Storyboards /// /// /// This iterates all elements and as such should be used sparingly or stored locally. - /// Samples return StartTime as their EndTIme. + /// Sample events use their start time as "end time" during this calculation. /// Video and background events are not included to match stable. /// public double? LatestEventTime => Layers.SelectMany(l => l.Elements) From 51b7c97cabd8944cc7dfd840ae4a247d42efeca7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 28 Oct 2023 06:07:49 +0300 Subject: [PATCH 1190/4092] Fix `TestScenePlayerMaxDimensions` bottlenecking CI --- .../Gameplay/TestScenePlayerMaxDimensions.cs | 68 +++---------------- 1 file changed, 10 insertions(+), 58 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerMaxDimensions.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerMaxDimensions.cs index 68443b234b..741fc7d789 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerMaxDimensions.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerMaxDimensions.cs @@ -72,66 +72,18 @@ namespace osu.Game.Tests.Visual.Gameplay remove { } } + public override Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) + { + var texture = base.GetTexture(componentName, wrapModeS, wrapModeT); + + if (texture != null) + texture.ScaleAdjust /= scale_factor; + + return texture; + } + public ISkin FindProvider(Func lookupFunction) => this; public IEnumerable AllSources => new[] { this }; - - protected override IResourceStore CreateTextureLoaderStore(IStorageResourceProvider resources, IResourceStore storage) - => new UpscaledTextureLoaderStore(base.CreateTextureLoaderStore(resources, storage)); - - private class UpscaledTextureLoaderStore : IResourceStore - { - private readonly IResourceStore? textureStore; - - public UpscaledTextureLoaderStore(IResourceStore? textureStore) - { - this.textureStore = textureStore; - } - - public void Dispose() - { - textureStore?.Dispose(); - } - - public TextureUpload Get(string name) - { - var textureUpload = textureStore?.Get(name); - - // NRT not enabled on framework side classes (IResourceStore / TextureLoaderStore), welp. - if (textureUpload == null) - return null!; - - return upscale(textureUpload); - } - - public async Task GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) - { - // NRT not enabled on framework side classes (IResourceStore / TextureLoaderStore), welp. - if (textureStore == null) - return null!; - - var textureUpload = await textureStore.GetAsync(name, cancellationToken).ConfigureAwait(false); - - if (textureUpload == null) - return null!; - - return await Task.Run(() => upscale(textureUpload), cancellationToken).ConfigureAwait(false); - } - - private TextureUpload upscale(TextureUpload textureUpload) - { - var image = Image.LoadPixelData(textureUpload.Data.ToArray(), textureUpload.Width, textureUpload.Height); - - // The original texture upload will no longer be returned or used. - textureUpload.Dispose(); - - image.Mutate(i => i.Resize(new Size(textureUpload.Width, textureUpload.Height) * scale_factor)); - return new TextureUpload(image); - } - - public Stream? GetStream(string name) => textureStore?.GetStream(name); - - public IEnumerable GetAvailableResources() => textureStore?.GetAvailableResources() ?? Array.Empty(); - } } } } From a53c0adae077847112830952a025df3de4cdfe68 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 28 Oct 2023 06:56:12 +0300 Subject: [PATCH 1191/4092] Remove unused using directive --- .../Visual/Gameplay/TestScenePlayerMaxDimensions.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerMaxDimensions.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerMaxDimensions.cs index 741fc7d789..6665295e99 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerMaxDimensions.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerMaxDimensions.cs @@ -3,20 +3,14 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Threading; -using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics.Textures; -using osu.Framework.IO.Stores; using osu.Framework.Testing; using osu.Game.IO; using osu.Game.Rulesets; using osu.Game.Screens.Play; using osu.Game.Skinning; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Processing; namespace osu.Game.Tests.Visual.Gameplay { From 28e331deed1c27de3f6177b3fe779bf673cc4b97 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 28 Oct 2023 08:29:46 +0300 Subject: [PATCH 1192/4092] Support displaying team seed in `TeamDisplay` --- .../TestSceneDrawableTournamentTeam.cs | 5 +-- .../TournamentTestScene.cs | 3 +- .../Gameplay/Components/TeamDisplay.cs | 34 ++++++++++++++++--- .../Gameplay/Components/TeamScoreDisplay.cs | 5 ++- 4 files changed, 38 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tournament.Tests/Components/TestSceneDrawableTournamentTeam.cs b/osu.Game.Tournament.Tests/Components/TestSceneDrawableTournamentTeam.cs index a809d0747a..8a50cbbe13 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneDrawableTournamentTeam.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneDrawableTournamentTeam.cs @@ -21,6 +21,7 @@ namespace osu.Game.Tournament.Tests.Components { FlagName = { Value = "AU" }, FullName = { Value = "Australia" }, + Seed = { Value = "#5" }, Players = { new TournamentUser { Username = "ASecretBox" }, @@ -30,7 +31,7 @@ namespace osu.Game.Tournament.Tests.Components new TournamentUser { Username = "Parkes" }, new TournamentUser { Username = "Shiroha" }, new TournamentUser { Username = "Jordan The Bear" }, - } + }, }; var match = new TournamentMatch { Team1 = { Value = team } }; @@ -100,7 +101,7 @@ namespace osu.Game.Tournament.Tests.Components Cell(i).AddRange(new Drawable[] { new TournamentSpriteText { Text = "TeamDisplay" }, - new TeamDisplay(team, TeamColour.Red, new Bindable(2), 6) + new TeamDisplay(team, TeamColour.Red, new Bindable(2), 6, true) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Tournament.Tests/TournamentTestScene.cs b/osu.Game.Tournament.Tests/TournamentTestScene.cs index f24fc61d85..4106556ee1 100644 --- a/osu.Game.Tournament.Tests/TournamentTestScene.cs +++ b/osu.Game.Tournament.Tests/TournamentTestScene.cs @@ -67,7 +67,7 @@ namespace osu.Game.Tournament.Tests FlagName = { Value = "JP" }, FullName = { Value = "Japan" }, LastYearPlacing = { Value = 10 }, - Seed = { Value = "Low" }, + Seed = { Value = "#12" }, SeedingResults = { new SeedingResult @@ -140,6 +140,7 @@ namespace osu.Game.Tournament.Tests Acronym = { Value = "USA" }, FlagName = { Value = "US" }, FullName = { Value = "United States" }, + Seed = { Value = "#3" }, Players = { new TournamentUser { Username = "Hello" }, diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs index 3fdbbb5973..7e63b5b8db 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs @@ -14,9 +14,11 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components { private readonly TeamScore score; - private readonly TournamentSpriteTextWithBackground teamText; + private readonly TournamentSpriteTextWithBackground teamNameText; + private readonly TournamentSpriteTextWithBackground teamSeedText; private readonly Bindable teamName = new Bindable("???"); + private readonly Bindable teamSeed = new Bindable(); private bool showScore; @@ -35,7 +37,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components } } - public TeamDisplay(TournamentTeam? team, TeamColour colour, Bindable currentTeamScore, int pointsToWin) + public TeamDisplay(TournamentTeam? team, TeamColour colour, Bindable currentTeamScore, int pointsToWin, bool displaySeed) : base(team) { AutoSizeAxes = Axes.Both; @@ -95,11 +97,29 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components } } }, - teamText = new TournamentSpriteTextWithBackground + new FillFlowContainer { - Scale = new Vector2(0.5f), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5), Origin = anchor, Anchor = anchor, + Children = new Drawable[] + { + teamNameText = new TournamentSpriteTextWithBackground + { + Scale = new Vector2(0.5f), + Origin = anchor, + Anchor = anchor, + }, + teamSeedText = new TournamentSpriteTextWithBackground + { + Scale = new Vector2(0.5f), + Origin = anchor, + Anchor = anchor, + Alpha = displaySeed ? 1 : 0, + } + } }, } }, @@ -117,9 +137,13 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components FinishTransforms(true); if (Team != null) + { teamName.BindTo(Team.FullName); + teamSeed.BindTo(Team.Seed); + } - teamName.BindValueChanged(name => teamText.Text.Text = name.NewValue, true); + teamName.BindValueChanged(name => teamNameText.Text.Text = name.NewValue, true); + teamSeed.BindValueChanged(seed => teamSeedText.Text.Text = seed.NewValue, true); } private void updateDisplay() diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs index c7fcfae602..9c2922d030 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs @@ -21,6 +21,8 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components private TeamDisplay? teamDisplay; + public readonly BindableBool DisplaySeed = new BindableBool(); + public bool ShowScore { get => teamDisplay?.ShowScore ?? false; @@ -48,6 +50,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components currentMatch.BindValueChanged(matchChanged); currentTeam.BindValueChanged(teamChanged); + DisplaySeed.BindValueChanged(_ => currentTeam.TriggerChange()); updateMatch(); } @@ -101,7 +104,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components InternalChildren = new Drawable[] { - teamDisplay = new TeamDisplay(team.NewValue, teamColour, currentTeamScore, currentMatch.Value?.PointsToWin ?? 0), + teamDisplay = new TeamDisplay(team.NewValue, teamColour, currentTeamScore, currentMatch.Value?.PointsToWin ?? 0, DisplaySeed.Value), }; teamDisplay.ShowScore = wasShowingScores; From e2788a22b153ac5763c9d47a3534f719b4fde497 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 28 Oct 2023 08:30:33 +0300 Subject: [PATCH 1193/4092] Add setting to configure team seed display --- osu.Game.Tournament/Models/LadderInfo.cs | 2 ++ .../Screens/Gameplay/Components/MatchHeader.cs | 5 +++++ .../Screens/Gameplay/GameplayScreen.cs | 12 +++++++++--- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tournament/Models/LadderInfo.cs b/osu.Game.Tournament/Models/LadderInfo.cs index 219a2a7bfb..f96dae8044 100644 --- a/osu.Game.Tournament/Models/LadderInfo.cs +++ b/osu.Game.Tournament/Models/LadderInfo.cs @@ -42,5 +42,7 @@ namespace osu.Game.Tournament.Models public Bindable AutoProgressScreens = new BindableBool(true); public Bindable SplitMapPoolByMods = new BindableBool(true); + + public Bindable DisplayTeamSeeds = new BindableBool(); } } diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs b/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs index 69f150c8ac..edf1d810ed 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.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.Game.Tournament.Components; @@ -16,6 +17,8 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components private TeamScoreDisplay teamDisplay2 = null!; private DrawableTournamentHeaderLogo logo = null!; + public readonly BindableBool DisplaySeeds = new BindableBool(); + private bool showScores = true; public bool ShowScores @@ -88,11 +91,13 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components { Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, + DisplaySeed = { BindTarget = DisplaySeeds }, }, teamDisplay2 = new TeamScoreDisplay(TeamColour.Blue) { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, + DisplaySeed = { BindTarget = DisplaySeeds }, }, }; } diff --git a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs index 20188cc5dc..76d7f27421 100644 --- a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs +++ b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs @@ -36,7 +36,7 @@ namespace osu.Game.Tournament.Screens.Gameplay private Drawable chroma = null!; [BackgroundDependencyLoader] - private void load(LadderInfo ladder, MatchIPCInfo ipc) + private void load(MatchIPCInfo ipc) { this.ipc = ipc; @@ -118,12 +118,18 @@ namespace osu.Game.Tournament.Screens.Gameplay LabelText = "Players per team", Current = LadderInfo.PlayersPerTeam, KeyboardStep = 1, - } + }, + new SettingsCheckbox + { + LabelText = "Display team seeds", + Current = LadderInfo.DisplayTeamSeeds, + }, } } }); - ladder.ChromaKeyWidth.BindValueChanged(width => chroma.Width = width.NewValue, true); + LadderInfo.ChromaKeyWidth.BindValueChanged(width => chroma.Width = width.NewValue, true); + LadderInfo.DisplayTeamSeeds.BindValueChanged(v => header.DisplaySeeds.Value = v.NewValue, true); warmup.BindValueChanged(w => { From 832e30c31a3e68891c23a5f22ad156a3a6224fc4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 28 Oct 2023 08:30:59 +0300 Subject: [PATCH 1194/4092] Adjust horizontal padding in tournament sprite text --- .../Components/TournamentSpriteTextWithBackground.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/Components/TournamentSpriteTextWithBackground.cs b/osu.Game.Tournament/Components/TournamentSpriteTextWithBackground.cs index 97cb610021..ce118727cd 100644 --- a/osu.Game.Tournament/Components/TournamentSpriteTextWithBackground.cs +++ b/osu.Game.Tournament/Components/TournamentSpriteTextWithBackground.cs @@ -29,8 +29,8 @@ namespace osu.Game.Tournament.Components { Colour = TournamentGame.ELEMENT_FOREGROUND_COLOUR, Font = OsuFont.Torus.With(weight: FontWeight.SemiBold, size: 50), - Padding = new MarginPadding { Left = 10, Right = 20 }, - Text = text + Padding = new MarginPadding { Horizontal = 10 }, + Text = text, } }; } From 4371a1ab57a5bfb9549d948121bb15f5b1237ecd Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 28 Oct 2023 08:42:29 +0300 Subject: [PATCH 1195/4092] Move team seed setting from gameplay screen --- osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs | 9 ++------- osu.Game.Tournament/Screens/Setup/SetupScreen.cs | 6 ++++++ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs index 76d7f27421..3e5ba90c52 100644 --- a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs +++ b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs @@ -49,7 +49,8 @@ namespace osu.Game.Tournament.Screens.Gameplay }, header = new MatchHeader { - ShowLogo = false + ShowLogo = false, + DisplaySeeds = { BindTarget = LadderInfo.DisplayTeamSeeds }, }, new Container { @@ -119,17 +120,11 @@ namespace osu.Game.Tournament.Screens.Gameplay Current = LadderInfo.PlayersPerTeam, KeyboardStep = 1, }, - new SettingsCheckbox - { - LabelText = "Display team seeds", - Current = LadderInfo.DisplayTeamSeeds, - }, } } }); LadderInfo.ChromaKeyWidth.BindValueChanged(width => chroma.Width = width.NewValue, true); - LadderInfo.DisplayTeamSeeds.BindValueChanged(v => header.DisplaySeeds.Value = v.NewValue, true); warmup.BindValueChanged(w => { diff --git a/osu.Game.Tournament/Screens/Setup/SetupScreen.cs b/osu.Game.Tournament/Screens/Setup/SetupScreen.cs index df1ce69c33..fed9d625ee 100644 --- a/osu.Game.Tournament/Screens/Setup/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/Setup/SetupScreen.cs @@ -140,6 +140,12 @@ namespace osu.Game.Tournament.Screens.Setup Description = "Screens will progress automatically from gameplay -> results -> map pool", Current = LadderInfo.AutoProgressScreens, }, + new LabelledSwitchButton + { + Label = "Display team seeds", + Description = "Team seeds will display alongside each team at the top in gameplay/map pool screens.", + Current = LadderInfo.DisplayTeamSeeds, + }, }; } From 81c1634d4453ad31acceaacd48c5d59a868b73a7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 28 Oct 2023 08:42:40 +0300 Subject: [PATCH 1196/4092] Display team seeds in map pool screen as well --- osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs index f80f43bb77..15c6fdb2ae 100644 --- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs +++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs @@ -50,6 +50,7 @@ namespace osu.Game.Tournament.Screens.MapPool new MatchHeader { ShowScores = true, + DisplaySeeds = { BindTarget = LadderInfo.DisplayTeamSeeds }, }, mapFlows = new FillFlowContainer> { From 7083c04c5923a9f4494bcdaadfc70de656a33c4d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 28 Oct 2023 09:25:55 +0300 Subject: [PATCH 1197/4092] Refactor logic slightly to display team seed everywhere This change makes the team seed display in "team intro" screen as well. --- .../TestSceneDrawableTournamentTeam.cs | 2 +- .../Components/DrawableTeamSeed.cs | 39 +++++++++++++++++++ .../Components/DrawableTeamTitleWithHeader.cs | 12 +++++- .../Gameplay/Components/MatchHeader.cs | 5 --- .../Gameplay/Components/TeamDisplay.cs | 13 ++----- .../Gameplay/Components/TeamScoreDisplay.cs | 5 +-- .../Screens/Gameplay/GameplayScreen.cs | 1 - .../Screens/MapPool/MapPoolScreen.cs | 1 - 8 files changed, 55 insertions(+), 23 deletions(-) create mode 100644 osu.Game.Tournament/Components/DrawableTeamSeed.cs diff --git a/osu.Game.Tournament.Tests/Components/TestSceneDrawableTournamentTeam.cs b/osu.Game.Tournament.Tests/Components/TestSceneDrawableTournamentTeam.cs index 8a50cbbe13..a6eb482d02 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneDrawableTournamentTeam.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneDrawableTournamentTeam.cs @@ -101,7 +101,7 @@ namespace osu.Game.Tournament.Tests.Components Cell(i).AddRange(new Drawable[] { new TournamentSpriteText { Text = "TeamDisplay" }, - new TeamDisplay(team, TeamColour.Red, new Bindable(2), 6, true) + new TeamDisplay(team, TeamColour.Red, new Bindable(2), 6) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Tournament/Components/DrawableTeamSeed.cs b/osu.Game.Tournament/Components/DrawableTeamSeed.cs new file mode 100644 index 0000000000..a79c63e979 --- /dev/null +++ b/osu.Game.Tournament/Components/DrawableTeamSeed.cs @@ -0,0 +1,39 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Tournament.Models; + +namespace osu.Game.Tournament.Components +{ + public partial class DrawableTeamSeed : TournamentSpriteTextWithBackground + { + private readonly TournamentTeam? team; + + private IBindable seed = null!; + private Bindable displaySeed = null!; + + public DrawableTeamSeed(TournamentTeam? team) + { + this.team = team; + } + + [Resolved] + private LadderInfo ladder { get; set; } = null!; + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (team == null) + return; + + seed = team.Seed.GetBoundCopy(); + seed.BindValueChanged(s => Text.Text = s.NewValue, true); + + displaySeed = ladder.DisplayTeamSeeds.GetBoundCopy(); + displaySeed.BindValueChanged(v => Alpha = v.NewValue ? 1 : 0, true); + } + } +} diff --git a/osu.Game.Tournament/Components/DrawableTeamTitleWithHeader.cs b/osu.Game.Tournament/Components/DrawableTeamTitleWithHeader.cs index 89f45fc1d3..fc4037d4e1 100644 --- a/osu.Game.Tournament/Components/DrawableTeamTitleWithHeader.cs +++ b/osu.Game.Tournament/Components/DrawableTeamTitleWithHeader.cs @@ -22,7 +22,17 @@ namespace osu.Game.Tournament.Components Children = new Drawable[] { new DrawableTeamHeader(colour), - new DrawableTeamTitle(team), + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5), + Children = new Drawable[] + { + new DrawableTeamTitle(team), + new DrawableTeamSeed(team), + } + } } }; } diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs b/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs index edf1d810ed..69f150c8ac 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs @@ -2,7 +2,6 @@ // 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.Game.Tournament.Components; @@ -17,8 +16,6 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components private TeamScoreDisplay teamDisplay2 = null!; private DrawableTournamentHeaderLogo logo = null!; - public readonly BindableBool DisplaySeeds = new BindableBool(); - private bool showScores = true; public bool ShowScores @@ -91,13 +88,11 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components { Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, - DisplaySeed = { BindTarget = DisplaySeeds }, }, teamDisplay2 = new TeamScoreDisplay(TeamColour.Blue) { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - DisplaySeed = { BindTarget = DisplaySeeds }, }, }; } diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs index 7e63b5b8db..49fbc64397 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs @@ -15,10 +15,8 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components private readonly TeamScore score; private readonly TournamentSpriteTextWithBackground teamNameText; - private readonly TournamentSpriteTextWithBackground teamSeedText; private readonly Bindable teamName = new Bindable("???"); - private readonly Bindable teamSeed = new Bindable(); private bool showScore; @@ -37,7 +35,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components } } - public TeamDisplay(TournamentTeam? team, TeamColour colour, Bindable currentTeamScore, int pointsToWin, bool displaySeed) + public TeamDisplay(TournamentTeam? team, TeamColour colour, Bindable currentTeamScore, int pointsToWin) : base(team) { AutoSizeAxes = Axes.Both; @@ -112,13 +110,12 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components Origin = anchor, Anchor = anchor, }, - teamSeedText = new TournamentSpriteTextWithBackground + new DrawableTeamSeed(Team) { Scale = new Vector2(0.5f), Origin = anchor, Anchor = anchor, - Alpha = displaySeed ? 1 : 0, - } + }, } }, } @@ -137,13 +134,9 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components FinishTransforms(true); if (Team != null) - { teamName.BindTo(Team.FullName); - teamSeed.BindTo(Team.Seed); - } teamName.BindValueChanged(name => teamNameText.Text.Text = name.NewValue, true); - teamSeed.BindValueChanged(seed => teamSeedText.Text.Text = seed.NewValue, true); } private void updateDisplay() diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs index 9c2922d030..c7fcfae602 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs @@ -21,8 +21,6 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components private TeamDisplay? teamDisplay; - public readonly BindableBool DisplaySeed = new BindableBool(); - public bool ShowScore { get => teamDisplay?.ShowScore ?? false; @@ -50,7 +48,6 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components currentMatch.BindValueChanged(matchChanged); currentTeam.BindValueChanged(teamChanged); - DisplaySeed.BindValueChanged(_ => currentTeam.TriggerChange()); updateMatch(); } @@ -104,7 +101,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components InternalChildren = new Drawable[] { - teamDisplay = new TeamDisplay(team.NewValue, teamColour, currentTeamScore, currentMatch.Value?.PointsToWin ?? 0, DisplaySeed.Value), + teamDisplay = new TeamDisplay(team.NewValue, teamColour, currentTeamScore, currentMatch.Value?.PointsToWin ?? 0), }; teamDisplay.ShowScore = wasShowingScores; diff --git a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs index 3e5ba90c52..b2152eaf3d 100644 --- a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs +++ b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs @@ -50,7 +50,6 @@ namespace osu.Game.Tournament.Screens.Gameplay header = new MatchHeader { ShowLogo = false, - DisplaySeeds = { BindTarget = LadderInfo.DisplayTeamSeeds }, }, new Container { diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs index 15c6fdb2ae..f80f43bb77 100644 --- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs +++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs @@ -50,7 +50,6 @@ namespace osu.Game.Tournament.Screens.MapPool new MatchHeader { ShowScores = true, - DisplaySeeds = { BindTarget = LadderInfo.DisplayTeamSeeds }, }, mapFlows = new FillFlowContainer> { From e76a5f9419276d8eaa8bea22c899fd1aa651c80b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 28 Oct 2023 10:18:15 +0300 Subject: [PATCH 1198/4092] Fix failing tests --- .../Components/TestSceneDrawableTournamentTeam.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Tournament.Tests/Components/TestSceneDrawableTournamentTeam.cs b/osu.Game.Tournament.Tests/Components/TestSceneDrawableTournamentTeam.cs index a6eb482d02..43adcc61bf 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneDrawableTournamentTeam.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneDrawableTournamentTeam.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.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Tests.Visual; @@ -14,9 +15,14 @@ namespace osu.Game.Tournament.Tests.Components { public partial class TestSceneDrawableTournamentTeam : OsuGridTestScene { + [Cached] + protected LadderInfo Ladder { get; private set; } = new LadderInfo(); + public TestSceneDrawableTournamentTeam() : base(4, 3) { + AddToggleStep("toggle seed view", v => Ladder.DisplayTeamSeeds.Value = v); + var team = new TournamentTeam { FlagName = { Value = "AU" }, From cfc0520481df18553b7252f1f63149ee89e4c9e9 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Sat, 28 Oct 2023 12:13:13 +0200 Subject: [PATCH 1199/4092] Add failing test --- .../SongSelect/TestScenePlaySongSelect.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 6737ec9739..7313bde8fe 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -13,6 +13,7 @@ using osu.Framework.Extensions; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics.Containers; +using osu.Framework.Input; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; @@ -1111,6 +1112,23 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("0 matching shown", () => songSelect.ChildrenOfType().Single().InformationalText == "0 matches"); } + [Test] + public void TestCutInFilterTextBox() + { + createSongSelect(); + + AddStep("set filter text", () => songSelect!.FilterControl.ChildrenOfType().First().Text = "nonono"); + AddStep("select all", () => InputManager.Keys(PlatformAction.SelectAll)); + AddStep("press ctrl-x", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.X); + InputManager.ReleaseKey(Key.ControlLeft); + }); + + AddAssert("filter text cleared", () => songSelect!.FilterControl.ChildrenOfType().First().Text, () => Is.Empty); + } + private void waitForInitialSelection() { AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault); From 366e41f11182c1220b2c8e33bc74172fa7543986 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Sat, 28 Oct 2023 12:23:23 +0200 Subject: [PATCH 1200/4092] Use local workaround instead of disabling clipboard entirely --- osu.Game/Screens/Select/FilterControl.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index b7dc18e46a..c15bd76ef8 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -9,6 +9,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Collections; @@ -23,6 +24,7 @@ using osu.Game.Rulesets; using osu.Game.Screens.Select.Filter; using osuTK; using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Screens.Select { @@ -254,9 +256,6 @@ namespace osu.Game.Screens.Select public OsuSpriteText FilterText { get; private set; } - // clipboard is disabled because one of the "cut" platform key bindings (shift-delete) conflicts with the beatmap deletion action. - protected override bool AllowClipboardExport => false; - public FilterControlTextBox() { Height += filter_text_size; @@ -277,6 +276,15 @@ namespace osu.Game.Screens.Select Colour = colours.Yellow }); } + + public override bool OnPressed(KeyBindingPressEvent e) + { + // the "cut" platform key binding (shift-delete) conflicts with the beatmap deletion action. + if (e.Action == PlatformAction.Cut && e.ShiftPressed && e.CurrentState.Keyboard.Keys.IsPressed(Key.Delete)) + return false; + + return base.OnPressed(e); + } } } } From c38c8e933ab0aba67d7a36ccff1413cde465dd8e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 28 Oct 2023 16:52:33 +0300 Subject: [PATCH 1201/4092] Change tournament date text box parsing to use invariant culture info --- osu.Game.Tournament/Components/DateTextBox.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/Components/DateTextBox.cs b/osu.Game.Tournament/Components/DateTextBox.cs index ab643a5cb5..dd70d5856d 100644 --- a/osu.Game.Tournament/Components/DateTextBox.cs +++ b/osu.Game.Tournament/Components/DateTextBox.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Globalization; using osu.Framework.Bindables; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; @@ -23,13 +24,13 @@ namespace osu.Game.Tournament.Components base.Current = new Bindable(string.Empty); current.BindValueChanged(dto => - base.Current.Value = dto.NewValue.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"), true); + base.Current.Value = dto.NewValue.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ", DateTimeFormatInfo.InvariantInfo), true); ((OsuTextBox)Control).OnCommit += (sender, _) => { try { - current.Value = DateTimeOffset.Parse(sender.Text); + current.Value = DateTimeOffset.Parse(sender.Text, DateTimeFormatInfo.InvariantInfo); } catch { From d877536dc0ec8d1f8f1889be73740a58ef29f869 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 29 Oct 2023 01:03:38 +0300 Subject: [PATCH 1202/4092] Select all text content in `SearchTextBox` on focus --- osu.Game/Graphics/UserInterface/SearchTextBox.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/SearchTextBox.cs b/osu.Game/Graphics/UserInterface/SearchTextBox.cs index a2e0ab6482..b554c2bbd8 100644 --- a/osu.Game/Graphics/UserInterface/SearchTextBox.cs +++ b/osu.Game/Graphics/UserInterface/SearchTextBox.cs @@ -18,6 +18,12 @@ namespace osu.Game.Graphics.UserInterface PlaceholderText = HomeStrings.SearchPlaceholder; } + protected override void OnFocus(FocusEvent e) + { + base.OnFocus(e); + SelectAll(); + } + public override bool OnPressed(KeyBindingPressEvent e) { switch (e.Action) From 31c6973bb646272b3ddcb2b3405d1802c8bf770c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 29 Oct 2023 01:03:45 +0300 Subject: [PATCH 1203/4092] Add test coverage --- .../UserInterface/TestSceneSearchTextBox.cs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneSearchTextBox.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSearchTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSearchTextBox.cs new file mode 100644 index 0000000000..153525d24a --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSearchTextBox.cs @@ -0,0 +1,38 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; +using osuTK; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public partial class TestSceneSearchTextBox : OsuTestScene + { + private SearchTextBox textBox = null!; + + [SetUp] + public void SetUp() => Schedule(() => + { + Child = textBox = new SearchTextBox + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 400, + Scale = new Vector2(2f), + HoldFocus = true, + }; + }); + + [Test] + public void TestSelectionOnFocus() + { + AddStep("set text", () => textBox.Text = "some text"); + AddAssert("no text selected", () => textBox.SelectedText == string.Empty); + AddStep("hide text box", () => textBox.Hide()); + AddStep("show text box", () => textBox.Show()); + AddAssert("search text selected", () => textBox.SelectedText == textBox.Text); + } + } +} From ec9ae12bbd880f2f9a1c31adca3d09e516d0dbb4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 29 Oct 2023 01:43:49 +0300 Subject: [PATCH 1204/4092] Update API response model to accept array of tournament banners --- .../Online/TestSceneUserProfileOverlay.cs | 27 +++++++++++++++---- .../Online/API/Requests/Responses/APIUser.cs | 5 ++-- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index b57b0b7312..a321a194a9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -121,12 +121,29 @@ namespace osu.Game.Tests.Visual.Online Data = Enumerable.Range(2345, 45).Concat(Enumerable.Range(2109, 40)).ToArray() }, }, - TournamentBanner = new TournamentBanner + TournamentBanners = new[] { - Id = 13926, - TournamentId = 35, - ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2022/profile/winner_US.jpg", - Image = "https://assets.ppy.sh/tournament-banners/official/owc2022/profile/winner_US@2x.jpg", + new TournamentBanner + { + Id = 15588, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_CN.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_CN@2x.jpg" + }, + new TournamentBanner + { + Id = 15589, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_PH.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_PH@2x.jpg" + }, + new TournamentBanner + { + Id = 15590, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_CL.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_CL@2x.jpg" + } }, Badges = new[] { diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index d9208d0662..7c4093006d 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -234,9 +234,8 @@ namespace osu.Game.Online.API.Requests.Responses set => Statistics.RankHistory = value; } - [JsonProperty(@"active_tournament_banner")] - [CanBeNull] - public TournamentBanner TournamentBanner; + [JsonProperty(@"active_tournament_banners")] + public TournamentBanner[] TournamentBanners; [JsonProperty("badges")] public Badge[] Badges; From 922ad80cfc9faf6fd63a0a5f01a266247924c4f8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 29 Oct 2023 01:44:21 +0300 Subject: [PATCH 1205/4092] Update user profile overlay to show more than one tournament banner --- .../Profile/Header/BannerHeaderContainer.cs | 15 ++++++++------- .../Header/Components/DrawableTournamentBanner.cs | 9 ++++++++- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/BannerHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BannerHeaderContainer.cs index 8e6648dc4b..7ed58200ec 100644 --- a/osu.Game/Overlays/Profile/Header/BannerHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BannerHeaderContainer.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 System.Linq; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -11,7 +12,7 @@ using osu.Game.Overlays.Profile.Header.Components; namespace osu.Game.Overlays.Profile.Header { - public partial class BannerHeaderContainer : CompositeDrawable + public partial class BannerHeaderContainer : FillFlowContainer { public readonly Bindable User = new Bindable(); @@ -19,9 +20,9 @@ namespace osu.Game.Overlays.Profile.Header private void load() { Alpha = 0; - RelativeSizeAxes = Axes.Both; - FillMode = FillMode.Fit; - FillAspectRatio = 1000 / 60f; + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Direction = FillDirection.Vertical; } protected override void LoadComplete() @@ -40,13 +41,13 @@ namespace osu.Game.Overlays.Profile.Header ClearInternal(); - var banner = user?.TournamentBanner; + var banners = user?.TournamentBanners; - if (banner != null) + if (banners?.Length > 0) { Show(); - LoadComponentAsync(new DrawableTournamentBanner(banner), AddInternal, cancellationTokenSource.Token); + LoadComponentsAsync(banners.Select(b => new DrawableTournamentBanner(b)), AddRangeInternal, cancellationTokenSource.Token); } else { diff --git a/osu.Game/Overlays/Profile/Header/Components/DrawableTournamentBanner.cs b/osu.Game/Overlays/Profile/Header/Components/DrawableTournamentBanner.cs index 26d333ff95..c099009ca4 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DrawableTournamentBanner.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DrawableTournamentBanner.cs @@ -15,12 +15,13 @@ namespace osu.Game.Overlays.Profile.Header.Components [LongRunningLoad] public partial class DrawableTournamentBanner : OsuClickableContainer { + private const float banner_aspect_ratio = 60 / 1000f; private readonly TournamentBanner banner; public DrawableTournamentBanner(TournamentBanner banner) { this.banner = banner; - RelativeSizeAxes = Axes.Both; + RelativeSizeAxes = Axes.X; } [BackgroundDependencyLoader] @@ -41,6 +42,12 @@ namespace osu.Game.Overlays.Profile.Header.Components this.FadeInFromZero(200); } + protected override void Update() + { + base.Update(); + Height = DrawWidth * banner_aspect_ratio; + } + public override LocalisableString TooltipText => "view in browser"; } } From 204ebfade7ecaf5a426bfc21a6b89cfa6393fed8 Mon Sep 17 00:00:00 2001 From: Rowe Wilson Frederisk Holme Date: Sun, 29 Oct 2023 21:25:15 +0800 Subject: [PATCH 1206/4092] Small update to README of Templates --- Templates/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Templates/README.md b/Templates/README.md index cf25a89273..28aaee3290 100644 --- a/Templates/README.md +++ b/Templates/README.md @@ -7,7 +7,7 @@ Templates for use when creating osu! dependent projects. Create a fully-testable ```bash # install (or update) templates package. # this only needs to be done once -dotnet new -i ppy.osu.Game.Templates +dotnet new install ppy.osu.Game.Templates # create an empty freeform ruleset dotnet new ruleset -n MyCoolRuleset From af10dbb76c4b2ab6217655f7b0eeee124acc3635 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 30 Oct 2023 06:19:52 +0300 Subject: [PATCH 1207/4092] Add test case with many tournament banners --- .../Online/TestSceneUserProfileHeader.cs | 266 +++++++++++++++++- 1 file changed, 265 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs index 4f28baa849..c9e5a3315c 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs @@ -5,8 +5,10 @@ using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Configuration; +using osu.Game.Graphics.Containers; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.Profile; @@ -28,7 +30,14 @@ namespace osu.Game.Tests.Visual.Online [SetUpSteps] public void SetUpSteps() { - AddStep("create header", () => Child = header = new ProfileHeader()); + AddStep("create header", () => + { + Child = new OsuScrollContainer(Direction.Vertical) + { + RelativeSizeAxes = Axes.Both, + Child = header = new ProfileHeader() + }; + }); } [Test] @@ -136,5 +145,260 @@ namespace osu.Game.Tests.Visual.Online PreviousUsernames = new[] { "tsrk.", "quoicoubeh", "apagnan", "epita" } }, new OsuRuleset().RulesetInfo)); } + + [Test] + public void TestManyTournamentBanners() + { + AddStep("Show user w/ many tournament banners", () => header.User.Value = new UserProfileData(new APIUser + { + Id = 728, + Username = "Certain Guy", + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg", + Statistics = new UserStatistics + { + IsRanked = false, + // web will sometimes return non-empty rank history even for unranked users. + RankHistory = new APIRankHistory + { + Mode = @"osu", + Data = Enumerable.Range(2345, 85).ToArray() + }, + }, + TournamentBanners = new[] + { + new TournamentBanner + { + Id = 15329, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_HK.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_HK@2x.jpg" + }, + new TournamentBanner + { + Id = 15588, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_CN.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_CN@2x.jpg" + }, + new TournamentBanner + { + Id = 15589, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_PH.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_PH@2x.jpg" + }, + new TournamentBanner + { + Id = 15590, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_CL.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_CL@2x.jpg" + }, + new TournamentBanner + { + Id = 15591, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_JP.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_JP@2x.jpg" + }, + new TournamentBanner + { + Id = 15592, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_RU.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_RU@2x.jpg" + }, + new TournamentBanner + { + Id = 15593, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_KR.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_KR@2x.jpg" + }, + new TournamentBanner + { + Id = 15594, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_NZ.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_NZ@2x.jpg" + }, + new TournamentBanner + { + Id = 15595, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_TH.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_TH@2x.jpg" + }, + new TournamentBanner + { + Id = 15596, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_TW.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_TW@2x.jpg" + }, + new TournamentBanner + { + Id = 15603, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_ID.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_ID@2x.jpg" + }, + new TournamentBanner + { + Id = 15604, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_KZ.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_KZ@2x.jpg" + }, + new TournamentBanner + { + Id = 15605, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_AR.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_AR@2x.jpg" + }, + new TournamentBanner + { + Id = 15606, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_BR.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_BR@2x.jpg" + }, + new TournamentBanner + { + Id = 15607, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_PL.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_PL@2x.jpg" + }, + new TournamentBanner + { + Id = 15639, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_MX.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_MX@2x.jpg" + }, + new TournamentBanner + { + Id = 15640, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_AU.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_AU@2x.jpg" + }, + new TournamentBanner + { + Id = 15641, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_IT.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_IT@2x.jpg" + }, + new TournamentBanner + { + Id = 15642, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_UA.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_UA@2x.jpg" + }, + new TournamentBanner + { + Id = 15643, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_NL.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_NL@2x.jpg" + }, + new TournamentBanner + { + Id = 15644, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_FI.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_FI@2x.jpg" + }, + new TournamentBanner + { + Id = 15645, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_RO.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_RO@2x.jpg" + }, + new TournamentBanner + { + Id = 15646, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_SG.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_SG@2x.jpg" + }, + new TournamentBanner + { + Id = 15647, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_DE.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_DE@2x.jpg" + }, + new TournamentBanner + { + Id = 15648, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_ES.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_ES@2x.jpg" + }, + new TournamentBanner + { + Id = 15649, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_SE.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_SE@2x.jpg" + }, + new TournamentBanner + { + Id = 15650, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_CA.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_CA@2x.jpg" + }, + new TournamentBanner + { + Id = 15651, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_NO.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_NO@2x.jpg" + }, + new TournamentBanner + { + Id = 15652, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_GB.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_GB@2x.jpg" + }, + new TournamentBanner + { + Id = 15653, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_US.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_US@2x.jpg" + }, + new TournamentBanner + { + Id = 15654, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_PL.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_PL@2x.jpg" + }, + new TournamentBanner + { + Id = 15655, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_FR.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_FR@2x.jpg" + }, + new TournamentBanner + { + Id = 15686, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_HK.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_HK@2x.jpg" + } + } + }, new OsuRuleset().RulesetInfo)); + } } } From 984c30ded662bc6091c980dc6d6fcf1da880479e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 30 Oct 2023 06:20:13 +0300 Subject: [PATCH 1208/4092] Load each tournament banner as soon as it is loaded --- .../Overlays/Profile/Header/BannerHeaderContainer.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/BannerHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BannerHeaderContainer.cs index 7ed58200ec..424ab4a529 100644 --- a/osu.Game/Overlays/Profile/Header/BannerHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BannerHeaderContainer.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.Linq; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -47,7 +46,15 @@ namespace osu.Game.Overlays.Profile.Header { Show(); - LoadComponentsAsync(banners.Select(b => new DrawableTournamentBanner(b)), AddRangeInternal, cancellationTokenSource.Token); + for (int index = 0; index < banners.Length; index++) + { + int displayIndex = index; + LoadComponentAsync(new DrawableTournamentBanner(banners[index]), asyncBanner => + { + // load in stable order regardless of async load order. + Insert(displayIndex, asyncBanner); + }, cancellationTokenSource.Token); + } } else { From c7bc8e686543e4fe6e823d06b5d79be144d8ad6e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 30 Oct 2023 06:41:01 +0300 Subject: [PATCH 1209/4092] Move behaviour to settings search text box only --- .../Visual/Settings/TestSceneSettingsPanel.cs | 11 +++++++++++ .../Graphics/UserInterface/SearchTextBox.cs | 6 ------ osu.Game/Overlays/SettingsPanel.cs | 2 +- osu.Game/Overlays/SettingsSearchTextBox.cs | 18 ++++++++++++++++++ 4 files changed, 30 insertions(+), 7 deletions(-) create mode 100644 osu.Game/Overlays/SettingsSearchTextBox.cs diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs index 24c2eee783..69e489b247 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs @@ -140,6 +140,17 @@ namespace osu.Game.Tests.Visual.Settings AddUntilStep("top-level textbox focused", () => settings.SectionsContainer.ChildrenOfType().FirstOrDefault()?.HasFocus == true); } + [Test] + public void TestSearchTextBoxSelectedOnShow() + { + SearchTextBox searchTextBox = null!; + + AddStep("set text", () => (searchTextBox = settings.SectionsContainer.ChildrenOfType().First()).Current.Value = "some text"); + AddAssert("no text selected", () => searchTextBox.SelectedText == string.Empty); + AddRepeatStep("toggle visibility", () => settings.ToggleVisibility(), 2); + AddAssert("search text selected", () => searchTextBox.SelectedText == searchTextBox.Current.Value); + } + [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game/Graphics/UserInterface/SearchTextBox.cs b/osu.Game/Graphics/UserInterface/SearchTextBox.cs index b554c2bbd8..a2e0ab6482 100644 --- a/osu.Game/Graphics/UserInterface/SearchTextBox.cs +++ b/osu.Game/Graphics/UserInterface/SearchTextBox.cs @@ -18,12 +18,6 @@ namespace osu.Game.Graphics.UserInterface PlaceholderText = HomeStrings.SearchPlaceholder; } - protected override void OnFocus(FocusEvent e) - { - base.OnFocus(e); - SelectAll(); - } - public override bool OnPressed(KeyBindingPressEvent e) { switch (e.Action) diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index 2517a58491..3bac6c400f 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -135,7 +135,7 @@ namespace osu.Game.Overlays }, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Child = searchTextBox = new SeekLimitedSearchTextBox + Child = searchTextBox = new SettingsSearchTextBox { RelativeSizeAxes = Axes.X, Origin = Anchor.TopCentre, diff --git a/osu.Game/Overlays/SettingsSearchTextBox.cs b/osu.Game/Overlays/SettingsSearchTextBox.cs new file mode 100644 index 0000000000..bafa6e26eb --- /dev/null +++ b/osu.Game/Overlays/SettingsSearchTextBox.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. + +#nullable disable +using osu.Framework.Input.Events; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays +{ + public partial class SettingsSearchTextBox : SeekLimitedSearchTextBox + { + protected override void OnFocus(FocusEvent e) + { + base.OnFocus(e); + SelectAll(); + } + } +} From d90f29a5ff4a4618955b7ac7c4e743d39b4aa03a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Oct 2023 15:07:26 +0900 Subject: [PATCH 1210/4092] Improve log output surrounding score submission --- osu.Game/Screens/Play/SubmittingPlayer.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 5fa6508a31..a75546f835 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -188,7 +188,10 @@ namespace osu.Game.Screens.Play { // token may be null if the request failed but gameplay was still allowed (see HandleTokenRetrievalFailure). if (token == null) + { + Logger.Log("No token, skipping score submission"); return Task.CompletedTask; + } if (scoreSubmissionSource != null) return scoreSubmissionSource.Task; @@ -197,6 +200,8 @@ namespace osu.Game.Screens.Play if (!score.ScoreInfo.Statistics.Any(s => s.Key.IsHit() && s.Value > 0)) return Task.CompletedTask; + Logger.Log($"Beginning score submission (token:{token.Value})..."); + scoreSubmissionSource = new TaskCompletionSource(); var request = CreateSubmissionRequest(score, token.Value); @@ -206,11 +211,12 @@ namespace osu.Game.Screens.Play score.ScoreInfo.Position = s.Position; scoreSubmissionSource.SetResult(true); + Logger.Log($"Score submission completed! (token:{token.Value} id:{s.ID})"); }; request.Failure += e => { - Logger.Error(e, $"Failed to submit score ({e.Message})"); + Logger.Error(e, $"Failed to submit score (token:{token.Value}): {e.Message}"); scoreSubmissionSource.SetResult(false); }; From a8c3f598457ee9079ba97d2f381f75f7774890dd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Oct 2023 15:07:35 +0900 Subject: [PATCH 1211/4092] Clean up type display for web requests in logs --- osu.Game/Online/API/APIRequest.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index cd6e8df754..6b6b222043 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -7,6 +7,7 @@ using System; using System.Globalization; using JetBrains.Annotations; using Newtonsoft.Json; +using osu.Framework.Extensions.TypeExtensions; using osu.Framework.IO.Network; using osu.Framework.Logging; using osu.Game.Extensions; @@ -46,7 +47,7 @@ namespace osu.Game.Online.API if (WebRequest != null) { Response = ((OsuJsonWebRequest)WebRequest).ResponseObject; - Logger.Log($"{GetType()} finished with response size of {WebRequest.ResponseStream.Length:#,0} bytes", LoggingTarget.Network); + Logger.Log($"{GetType().ReadableName()} finished with response size of {WebRequest.ResponseStream.Length:#,0} bytes", LoggingTarget.Network); } } From a91b704d21df8adeca8068dd448d2ac9424010c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Oct 2023 15:10:10 +0900 Subject: [PATCH 1212/4092] Fix some new nullable inspections --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 8 ++------ osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 2 ++ osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs | 2 ++ osu.Game/Screens/Play/SquareGraph.cs | 2 ++ 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index c62659d67a..3cb9b96090 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -49,13 +49,9 @@ namespace osu.Game.Rulesets.Osu.Objects set { path.ControlPoints.Clear(); - path.ExpectedDistance.Value = null; + path.ControlPoints.AddRange(value.ControlPoints.Select(c => new PathControlPoint(c.Position, c.Type))); - if (value != null) - { - path.ControlPoints.AddRange(value.ControlPoints.Select(c => new PathControlPoint(c.Position, c.Type))); - path.ExpectedDistance.Value = value.ExpectedDistance.Value; - } + path.ExpectedDistance.Value = value.ExpectedDistance.Value; } } diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 158c3c102c..3ed7558bcb 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -5,6 +5,7 @@ using System; using System.Linq; +using JetBrains.Annotations; using osu.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -51,6 +52,7 @@ namespace osu.Game.Rulesets.Edit private SelectionState state; + [CanBeNull] public event Action StateChanged; public SelectionState State diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 1506b884b4..85ea881006 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -166,6 +166,8 @@ namespace osu.Game.Screens.Backgrounds public override void Add(Drawable drawable) { + ArgumentNullException.ThrowIfNull(drawable); + if (drawable is Background) throw new InvalidOperationException($"Use {nameof(Background)} to set a background."); diff --git a/osu.Game/Screens/Play/SquareGraph.cs b/osu.Game/Screens/Play/SquareGraph.cs index b53e86a41b..0c7b485755 100644 --- a/osu.Game/Screens/Play/SquareGraph.cs +++ b/osu.Game/Screens/Play/SquareGraph.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading; +using JetBrains.Annotations; using osu.Framework; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -190,6 +191,7 @@ namespace osu.Game.Screens.Play private const float padding = 2; public const float WIDTH = cube_size + padding; + [CanBeNull] public event Action StateChanged; private readonly List drawableRows = new List(); From 96dd7b3333014cb5e8e404d129b163de1f72f6e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Oct 2023 15:44:16 +0900 Subject: [PATCH 1213/4092] Update the last played date of a beatmap when importing a replay by the local user --- osu.Game.Tests/ImportTest.cs | 4 ++ osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 59 +++++++++++++++++++++ osu.Game/Online/API/DummyAPIAccess.cs | 2 +- osu.Game/Scoring/ScoreImporter.cs | 3 ++ 4 files changed, 67 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/ImportTest.cs b/osu.Game.Tests/ImportTest.cs index 3f0f8a4f14..27b8d3f21e 100644 --- a/osu.Game.Tests/ImportTest.cs +++ b/osu.Game.Tests/ImportTest.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Game.Database; +using osu.Game.Online.API; using osu.Game.Tests.Resources; namespace osu.Game.Tests @@ -46,12 +47,15 @@ namespace osu.Game.Tests public partial class TestOsuGameBase : OsuGameBase { public RealmAccess Realm => Dependencies.Get(); + public new IAPIProvider API => base.API; private readonly bool withBeatmap; public TestOsuGameBase(bool withBeatmap) { this.withBeatmap = withBeatmap; + + base.API = new DummyAPIAccess(); } [BackgroundDependencyLoader] diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index 892ceea185..d1bacaaf69 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -12,6 +12,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Game.IO.Archives; +using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; @@ -67,6 +68,64 @@ namespace osu.Game.Tests.Scores.IO } } + [TestCase(false)] + [TestCase(true)] + public void TestLastPlayedUpdate(bool isLocalUser) + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + { + try + { + var osu = LoadOsuIntoHost(host, true); + + if (!isLocalUser) + osu.API.Logout(); + + var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely(); + var beatmapInfo = beatmap.Beatmaps.First(); + + DateTimeOffset replayDate = DateTimeOffset.Now; + + var toImport = new ScoreInfo + { + Rank = ScoreRank.B, + TotalScore = 987654, + Accuracy = 0.8, + MaxCombo = 500, + Combo = 250, + User = new APIUser + { + Username = "Test user", + Id = DummyAPIAccess.DUMMY_USER_ID, + }, + Date = replayDate, + OnlineID = 12345, + Ruleset = new OsuRuleset().RulesetInfo, + BeatmapInfo = beatmapInfo + }; + + var imported = LoadScoreIntoOsu(osu, toImport); + + Assert.AreEqual(toImport.Rank, imported.Rank); + Assert.AreEqual(toImport.TotalScore, imported.TotalScore); + Assert.AreEqual(toImport.Accuracy, imported.Accuracy); + Assert.AreEqual(toImport.MaxCombo, imported.MaxCombo); + Assert.AreEqual(toImport.User.Username, imported.User.Username); + Assert.AreEqual(toImport.Date, imported.Date); + Assert.AreEqual(toImport.OnlineID, imported.OnlineID); + + if (isLocalUser) + Assert.That(imported.BeatmapInfo!.LastPlayed, Is.EqualTo(replayDate)); + else + Assert.That(imported.BeatmapInfo!.LastPlayed, Is.Null); + } + finally + { + host.Exit(); + } + } + } + [Test] public void TestImportMods() { diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 2764247f5c..d585124db6 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -112,7 +112,7 @@ namespace osu.Game.Online.API LocalUser.Value = new APIUser { Username = username, - Id = 1001, + Id = DUMMY_USER_ID, }; state.Value = APIState.Online; diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index 7473d887c3..32a528f218 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -87,6 +87,9 @@ namespace osu.Game.Scoring if (!model.Ruleset.IsManaged) model.Ruleset = realm.Find(model.Ruleset.ShortName)!; + if (api.IsLoggedIn && api.LocalUser.Value.OnlineID == model.UserID && (model.BeatmapInfo.LastPlayed == null || model.Date > model.BeatmapInfo.LastPlayed)) + model.BeatmapInfo.LastPlayed = model.Date; + // These properties are known to be non-null, but these final checks ensure a null hasn't come from somewhere (or the refetch has failed). // Under no circumstance do we want these to be written to realm as null. ArgumentNullException.ThrowIfNull(model.BeatmapInfo); From c1c8e2968f1f620851c4e608043051f07b944d8a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Oct 2023 15:46:09 +0900 Subject: [PATCH 1214/4092] Move operation to after user population --- osu.Game/Scoring/ScoreImporter.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index 32a528f218..b216c0897e 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -87,9 +87,6 @@ namespace osu.Game.Scoring if (!model.Ruleset.IsManaged) model.Ruleset = realm.Find(model.Ruleset.ShortName)!; - if (api.IsLoggedIn && api.LocalUser.Value.OnlineID == model.UserID && (model.BeatmapInfo.LastPlayed == null || model.Date > model.BeatmapInfo.LastPlayed)) - model.BeatmapInfo.LastPlayed = model.Date; - // These properties are known to be non-null, but these final checks ensure a null hasn't come from somewhere (or the refetch has failed). // Under no circumstance do we want these to be written to realm as null. ArgumentNullException.ThrowIfNull(model.BeatmapInfo); @@ -185,6 +182,12 @@ namespace osu.Game.Scoring base.PostImport(model, realm, parameters); populateUserDetails(model); + + Debug.Assert(model.BeatmapInfo != null); + + // This needs to be run after user detail population to ensure we have a valid user id. + if (api.IsLoggedIn && api.LocalUser.Value.OnlineID == model.UserID && (model.BeatmapInfo.LastPlayed == null || model.Date > model.BeatmapInfo.LastPlayed)) + model.BeatmapInfo.LastPlayed = model.Date; } /// From 4ae9b40a7806d6637840c25349282f07ef6d66e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Oct 2023 16:35:14 +0900 Subject: [PATCH 1215/4092] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index bf0a1fcbad..4f03f398d5 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + From 0dfb41b7966de7823982f3f18b2a5adfa38ac1c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Oct 2023 09:28:37 +0100 Subject: [PATCH 1216/4092] Add test coverage for not updating `LastPlayed` due to newer plays --- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 54 +++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index d1bacaaf69..dd724d268e 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -11,6 +11,8 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Platform; +using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.IO.Archives; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; @@ -126,6 +128,58 @@ namespace osu.Game.Tests.Scores.IO } } + [Test] + public void TestLastPlayedNotUpdatedDueToNewerPlays() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + { + try + { + var osu = LoadOsuIntoHost(host, true); + + var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely(); + var beatmapInfo = beatmap.Beatmaps.First(); + + var realmAccess = osu.Dependencies.Get(); + realmAccess.Write(r => r.Find(beatmapInfo.ID)!.LastPlayed = new DateTimeOffset(2023, 10, 30, 0, 0, 0, TimeSpan.Zero)); + + var toImport = new ScoreInfo + { + Rank = ScoreRank.B, + TotalScore = 987654, + Accuracy = 0.8, + MaxCombo = 500, + Combo = 250, + User = new APIUser + { + Username = "Test user", + Id = DummyAPIAccess.DUMMY_USER_ID, + }, + Date = new DateTimeOffset(2023, 10, 27, 0, 0, 0, TimeSpan.Zero), + OnlineID = 12345, + Ruleset = new OsuRuleset().RulesetInfo, + BeatmapInfo = beatmapInfo + }; + + var imported = LoadScoreIntoOsu(osu, toImport); + + Assert.AreEqual(toImport.Rank, imported.Rank); + Assert.AreEqual(toImport.TotalScore, imported.TotalScore); + Assert.AreEqual(toImport.Accuracy, imported.Accuracy); + Assert.AreEqual(toImport.MaxCombo, imported.MaxCombo); + Assert.AreEqual(toImport.User.Username, imported.User.Username); + Assert.AreEqual(toImport.Date, imported.Date); + Assert.AreEqual(toImport.OnlineID, imported.OnlineID); + + Assert.That(imported.BeatmapInfo!.LastPlayed, Is.EqualTo(new DateTimeOffset(2023, 10, 30, 0, 0, 0, TimeSpan.Zero))); + } + finally + { + host.Exit(); + } + } + } + [Test] public void TestImportMods() { From 39abb8e4085d17bc8ff64a80f19e268a3d4a5b7f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 30 Oct 2023 11:54:19 +0300 Subject: [PATCH 1217/4092] Only run "select all on focus" behaviour on desktop platforms --- osu.Game/Overlays/SettingsSearchTextBox.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SettingsSearchTextBox.cs b/osu.Game/Overlays/SettingsSearchTextBox.cs index bafa6e26eb..84cff1b508 100644 --- a/osu.Game/Overlays/SettingsSearchTextBox.cs +++ b/osu.Game/Overlays/SettingsSearchTextBox.cs @@ -12,7 +12,11 @@ namespace osu.Game.Overlays protected override void OnFocus(FocusEvent e) { base.OnFocus(e); - SelectAll(); + + // on mobile platforms, focus is not held by the search text box, and the select all feature + // will not make sense on it, and might annoy the user when they try to focus manually. + if (HoldFocus) + SelectAll(); } } } From 6be02966b9e79dfbb94ad372b932decc78ee65f5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Oct 2023 18:06:09 +0900 Subject: [PATCH 1218/4092] Add test coverage of failing context menu display --- .../Editing/TestSceneTimelineSelection.cs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs index 50eeb9a54b..0051488029 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs @@ -7,8 +7,10 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; @@ -44,6 +46,47 @@ namespace osu.Game.Tests.Visual.Editing }); } + [Test] + public void TestContextMenuWithObjectBehind() + { + TimelineHitObjectBlueprint blueprint; + + AddStep("add object", () => + { + EditorBeatmap.Add(new HitCircle { StartTime = 3000 }); + }); + + AddStep("enter slider placement", () => + { + InputManager.Key(Key.Number3); + InputManager.MoveMouseTo(ScreenSpaceDrawQuad.Centre); + }); + + AddStep("start conflicting slider", () => + { + InputManager.Click(MouseButton.Left); + + blueprint = this.ChildrenOfType().First(); + InputManager.MoveMouseTo(blueprint.ScreenSpaceDrawQuad.TopLeft - new Vector2(10, 0)); + }); + + AddStep("end conflicting slider", () => + { + InputManager.Click(MouseButton.Right); + }); + + AddStep("click object", () => + { + InputManager.Key(Key.Number1); + blueprint = this.ChildrenOfType().First(); + InputManager.MoveMouseTo(blueprint); + InputManager.Click(MouseButton.Left); + }); + + AddStep("right click", () => InputManager.Click(MouseButton.Right)); + AddAssert("context menu open", () => this.ChildrenOfType().SingleOrDefault()?.State == MenuState.Open); + } + [Test] public void TestNudgeSelection() { From 57d88a0ac459398a14aafd8923238ce1bc012e7f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Oct 2023 17:46:04 +0900 Subject: [PATCH 1219/4092] Fix right clicks on timeline objects potentially getting eaten by playfield area `SelectionHandler` is receiving input from anywhere out of necessity: https://github.com/ppy/osu/blob/19f892687a0607afbe4e0d010366dc2a66236073/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs#L119-L125 Also important is that `BlueprintContainer` will selectively not block right clicks to make sure they fall through to the `ContextMenuContainer`: https://github.com/ppy/osu/blob/19f892687a0607afbe4e0d010366dc2a66236073/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs#L122-L126 But because the whole editor is sharing a `ContextMenuContainer` and it's at a higher level than both components, we observe here the playfield's `SelectionHandler` intercepting the right click before it can reach the `ContextMenuContainer`. The fix here is similar to what we're already doing in `TimelineBlueprintContaienr`. --- .../Compose/Components/ComposeBlueprintContainer.cs | 5 ++++- osu.Game/Screens/Edit/EditorScreenWithTimeline.cs | 13 +++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index c8cfac454a..ba570a9251 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -40,11 +40,14 @@ namespace osu.Game.Screens.Edit.Compose.Components public PlacementBlueprint CurrentPlacement { get; private set; } + [Resolved] + private EditorScreenWithTimeline editorScreen { get; set; } + /// /// Positional input must be received outside the container's bounds, /// in order to handle composer blueprints which are partially offscreen. /// - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => editorScreen.MainContent.ReceivePositionalInputAt(screenSpacePos); public ComposeBlueprintContainer(HitObjectComposer composer) : base(composer) diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index ea2790b50a..e1ec1ad4ac 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -11,13 +11,14 @@ using osu.Game.Screens.Edit.Compose.Components.Timeline; namespace osu.Game.Screens.Edit { + [Cached] public abstract partial class EditorScreenWithTimeline : EditorScreen { public const float PADDING = 10; - private Container timelineContainer = null!; + public Container TimelineContent = null!; - private Container mainContent = null!; + public Container MainContent = null!; private LoadingSpinner spinner = null!; @@ -70,7 +71,7 @@ namespace osu.Game.Screens.Edit { new Drawable[] { - timelineContainer = new Container + TimelineContent = new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -93,7 +94,7 @@ namespace osu.Game.Screens.Edit }, new Drawable[] { - mainContent = new Container + MainContent = new Container { Name = "Main content", RelativeSizeAxes = Axes.Both, @@ -116,10 +117,10 @@ namespace osu.Game.Screens.Edit { spinner.State.Value = Visibility.Hidden; - mainContent.Add(content); + MainContent.Add(content); content.FadeInFromZero(300, Easing.OutQuint); - LoadComponentAsync(new TimelineArea(CreateTimelineContent()), timelineContainer.Add); + LoadComponentAsync(new TimelineArea(CreateTimelineContent()), TimelineContent.Add); }); } From 63e6eaf53880e326d43a75efec66134bb36bfe30 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Oct 2023 17:55:21 +0900 Subject: [PATCH 1220/4092] Fix failing tests --- osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs | 2 +- .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs index 0051488029..d8219ff36e 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs @@ -182,7 +182,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("click away", () => { - InputManager.MoveMouseTo(Editor.ChildrenOfType().Single().ScreenSpaceDrawQuad.TopLeft + Vector2.One); + InputManager.MoveMouseTo(Editor.ChildrenOfType().First().ScreenSpaceDrawQuad.TopLeft + new Vector2(5)); InputManager.Click(MouseButton.Left); }); diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index ba570a9251..c7c7c4aa83 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -40,14 +40,14 @@ namespace osu.Game.Screens.Edit.Compose.Components public PlacementBlueprint CurrentPlacement { get; private set; } - [Resolved] + [Resolved(canBeNull: true)] private EditorScreenWithTimeline editorScreen { get; set; } /// /// Positional input must be received outside the container's bounds, /// in order to handle composer blueprints which are partially offscreen. /// - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => editorScreen.MainContent.ReceivePositionalInputAt(screenSpacePos); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => editorScreen?.MainContent.ReceivePositionalInputAt(screenSpacePos) ?? base.ReceivePositionalInputAt(screenSpacePos); public ComposeBlueprintContainer(HitObjectComposer composer) : base(composer) From 0ed5f274f6e45ea22401a50e905e2a1d3406f170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Oct 2023 10:48:31 +0100 Subject: [PATCH 1221/4092] Enable NRT in `TestSceneSliderVelocityAdjust` --- .../Editor/TestSceneSliderVelocityAdjust.cs | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs index bb8c52bdfc..d92c6ebb60 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Diagnostics; using System.Linq; using NUnit.Framework; @@ -24,15 +22,15 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { public partial class TestSceneSliderVelocityAdjust : OsuGameTestScene { - private Screens.Edit.Editor editor => Game.ScreenStack.CurrentScreen as Screens.Edit.Editor; + private Screens.Edit.Editor? editor => Game.ScreenStack.CurrentScreen as Screens.Edit.Editor; - private EditorBeatmap editorBeatmap => editor.ChildrenOfType().FirstOrDefault(); + private EditorBeatmap editorBeatmap => editor.ChildrenOfType().FirstOrDefault()!; - private EditorClock editorClock => editor.ChildrenOfType().FirstOrDefault(); + private EditorClock editorClock => editor.ChildrenOfType().FirstOrDefault()!; - private Slider slider => editorBeatmap.HitObjects.OfType().FirstOrDefault(); + private Slider? slider => editorBeatmap.HitObjects.OfType().FirstOrDefault(); - private TimelineHitObjectBlueprint blueprint => editor.ChildrenOfType().FirstOrDefault(); + private TimelineHitObjectBlueprint blueprint => editor.ChildrenOfType().FirstOrDefault()!; private DifficultyPointPiece difficultyPointPiece => blueprint.ChildrenOfType().First(); @@ -66,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("ensure one slider placed", () => slider != null); - AddStep("store velocity", () => velocity = slider.Velocity); + AddStep("store velocity", () => velocity = slider!.Velocity); if (adjustVelocity) { @@ -76,10 +74,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("velocity adjusted", () => { Debug.Assert(velocity != null); - return Precision.AlmostEquals(velocity.Value * 2, slider.Velocity); + return Precision.AlmostEquals(velocity.Value * 2, slider!.Velocity); }); - AddStep("store velocity", () => velocity = slider.Velocity); + AddStep("store velocity", () => velocity = slider!.Velocity); } AddStep("save", () => InputManager.Keys(PlatformAction.Save)); @@ -88,8 +86,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("enter editor (again)", () => Game.ScreenStack.Push(new EditorLoader())); AddUntilStep("wait for editor load", () => editor?.ReadyForUse == true); - AddStep("seek to slider", () => editorClock.Seek(slider.StartTime)); - AddAssert("slider has correct velocity", () => slider.Velocity == velocity); + AddStep("seek to slider", () => editorClock.Seek(slider!.StartTime)); + AddAssert("slider has correct velocity", () => slider!.Velocity == velocity); } } } From e1ff0d12c66db4c2f16bc5d88fdb568dd5341bf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Oct 2023 10:55:26 +0100 Subject: [PATCH 1222/4092] Update tests to NUnit-style assertions --- .../Editor/TestSceneSliderVelocityAdjust.cs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs index d92c6ebb60..979801bc41 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.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.Diagnostics; using System.Linq; using NUnit.Framework; using osu.Framework.Input; @@ -45,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor double? velocity = null; AddStep("enter editor", () => Game.ScreenStack.Push(new EditorLoader())); - AddUntilStep("wait for editor load", () => editor?.ReadyForUse == true); + AddUntilStep("wait for editor load", () => editor?.ReadyForUse, () => Is.True); AddStep("seek to first control point", () => editorClock.Seek(editorBeatmap.ControlPointInfo.TimingPoints.First().Time)); AddStep("enter slider placement mode", () => InputManager.Key(Key.Number3)); @@ -58,11 +57,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("exit placement mode", () => InputManager.Key(Key.Number1)); - AddAssert("slider placed", () => slider != null); + AddAssert("slider placed", () => slider, () => Is.Not.Null); AddStep("select slider", () => editorBeatmap.SelectedHitObjects.Add(slider)); - AddAssert("ensure one slider placed", () => slider != null); + AddAssert("ensure one slider placed", () => slider, () => Is.Not.Null); AddStep("store velocity", () => velocity = slider!.Velocity); @@ -71,11 +70,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("open velocity adjust panel", () => difficultyPointPiece.TriggerClick()); AddStep("change velocity", () => velocityTextBox.Current.Value = 2); - AddAssert("velocity adjusted", () => - { - Debug.Assert(velocity != null); - return Precision.AlmostEquals(velocity.Value * 2, slider!.Velocity); - }); + AddAssert("velocity adjusted", () => slider!.Velocity, + () => Is.EqualTo(velocity!.Value * 2).Within(Precision.DOUBLE_EPSILON)); AddStep("store velocity", () => velocity = slider!.Velocity); } @@ -84,10 +80,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("exit", () => InputManager.Key(Key.Escape)); AddStep("enter editor (again)", () => Game.ScreenStack.Push(new EditorLoader())); - AddUntilStep("wait for editor load", () => editor?.ReadyForUse == true); + AddUntilStep("wait for editor load", () => editor?.ReadyForUse, () => Is.True); AddStep("seek to slider", () => editorClock.Seek(slider!.StartTime)); - AddAssert("slider has correct velocity", () => slider!.Velocity == velocity); + AddAssert("slider has correct velocity", () => slider!.Velocity, () => Is.EqualTo(velocity)); } } } From b3369dbb7b188d99f226894924a70339478c21b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Oct 2023 10:57:48 +0100 Subject: [PATCH 1223/4092] Add failing test for slider velocity --- .../Editor/TestSceneSliderVelocityAdjust.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs index 979801bc41..175cbeca6e 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs @@ -85,5 +85,50 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("seek to slider", () => editorClock.Seek(slider!.StartTime)); AddAssert("slider has correct velocity", () => slider!.Velocity, () => Is.EqualTo(velocity)); } + + [Test] + public void TestVelocityUndo() + { + double? velocityBefore = null; + double? durationBefore = null; + + AddStep("enter editor", () => Game.ScreenStack.Push(new EditorLoader())); + AddUntilStep("wait for editor load", () => editor?.ReadyForUse == true); + + AddStep("seek to first control point", () => editorClock.Seek(editorBeatmap.ControlPointInfo.TimingPoints.First().Time)); + AddStep("enter slider placement mode", () => InputManager.Key(Key.Number3)); + + AddStep("move mouse to centre", () => InputManager.MoveMouseTo(editor.ChildrenOfType().First().ScreenSpaceDrawQuad.Centre)); + AddStep("start placement", () => InputManager.Click(MouseButton.Left)); + + AddStep("move mouse to bottom right", () => InputManager.MoveMouseTo(editor.ChildrenOfType().First().ScreenSpaceDrawQuad.BottomRight - new Vector2(10))); + AddStep("end placement", () => InputManager.Click(MouseButton.Right)); + + AddStep("exit placement mode", () => InputManager.Key(Key.Number1)); + + AddAssert("slider placed", () => slider, () => Is.Not.Null); + AddStep("select slider", () => editorBeatmap.SelectedHitObjects.Add(slider)); + + AddStep("store velocity", () => + { + velocityBefore = slider!.Velocity; + durationBefore = slider.Duration; + }); + + AddStep("open velocity adjust panel", () => difficultyPointPiece.TriggerClick()); + AddStep("change velocity", () => velocityTextBox.Current.Value = 2); + + AddAssert("velocity adjusted", () => slider!.Velocity, () => Is.EqualTo(velocityBefore!.Value * 2).Within(Precision.DOUBLE_EPSILON)); + + AddStep("undo", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.Z); + InputManager.ReleaseKey(Key.ControlLeft); + }); + + AddAssert("slider has correct velocity", () => slider!.Velocity, () => Is.EqualTo(velocityBefore)); + AddAssert("slider has correct duration", () => slider!.Duration, () => Is.EqualTo(durationBefore)); + } } } From de89b7e53c305c0bf048b3ee42c028775d2da2cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Oct 2023 10:59:02 +0100 Subject: [PATCH 1224/4092] Fix slider velocity changes not being undone correctly Closes https://github.com/ppy/osu/issues/25239. `LegacyEditorBeatmapPatcher.processHitObjectLocalData()` was already supposed to be handling changes to hitobjects that will show up neither when comparing the hitobjects themselves or the timing point with "legacy" info stripped - so, in other words, changes to slider velocity and samples. However, a change to slider velocity requires default application to take effect, so just resetting the value would visually fix the timeline marker but not change the actual object. Calling `EditorBeatmap.Update()` fixes this by way of triggering default re-application. This could probably be smarter (by only invoking the update when strictly necessary, etc.) - but I'm not sure it's worth the hassle. This is intended to be a quick fix, rather than a complete solution - the complete solution would indeed likely entail a wholesale restructuring of the editor's change handling. --- osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs index fe0d4a7822..bb9f702cb5 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs @@ -123,6 +123,8 @@ namespace osu.Game.Screens.Edit oldWithRepeats.NodeSamples.Clear(); oldWithRepeats.NodeSamples.AddRange(newWithRepeats.NodeSamples); } + + editorBeatmap.Update(oldObject); } } From cea24298cb99c901077c6e8a23bdfa34da3ceafd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Oct 2023 12:42:34 +0100 Subject: [PATCH 1225/4092] Privatise setters --- osu.Game/Screens/Edit/EditorScreenWithTimeline.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index e1ec1ad4ac..575a66d421 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -16,9 +16,9 @@ namespace osu.Game.Screens.Edit { public const float PADDING = 10; - public Container TimelineContent = null!; + public Container TimelineContent { get; private set; } = null!; - public Container MainContent = null!; + public Container MainContent { get; private set; } = null!; private LoadingSpinner spinner = null!; From 88e10dd051d33ca0e63a7cd5f82dac3d9b4e039c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Oct 2023 20:03:44 +0100 Subject: [PATCH 1226/4092] 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 0575817460..2870696c03 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 9b06b4a6a7..f1159f58b9 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From 06508d08fe1f289404bb20756f43403c209e0d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Oct 2023 20:22:41 +0100 Subject: [PATCH 1227/4092] Delete outdated test --- .../UserInterface/TestSceneSearchTextBox.cs | 38 ------------------- 1 file changed, 38 deletions(-) delete mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneSearchTextBox.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSearchTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSearchTextBox.cs deleted file mode 100644 index 153525d24a..0000000000 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneSearchTextBox.cs +++ /dev/null @@ -1,38 +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 NUnit.Framework; -using osu.Framework.Graphics; -using osu.Game.Graphics.UserInterface; -using osuTK; - -namespace osu.Game.Tests.Visual.UserInterface -{ - public partial class TestSceneSearchTextBox : OsuTestScene - { - private SearchTextBox textBox = null!; - - [SetUp] - public void SetUp() => Schedule(() => - { - Child = textBox = new SearchTextBox - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Width = 400, - Scale = new Vector2(2f), - HoldFocus = true, - }; - }); - - [Test] - public void TestSelectionOnFocus() - { - AddStep("set text", () => textBox.Text = "some text"); - AddAssert("no text selected", () => textBox.SelectedText == string.Empty); - AddStep("hide text box", () => textBox.Hide()); - AddStep("show text box", () => textBox.Show()); - AddAssert("search text selected", () => textBox.SelectedText == textBox.Text); - } - } -} From f2c0bc821802067e8cb9c7e5bbf64215e6e4fc31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Oct 2023 21:15:04 +0100 Subject: [PATCH 1228/4092] Add failing test case --- .../TestSceneSpinnerInput.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerInput.cs index 5a473409a4..a6c15d5a67 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerInput.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 System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -10,6 +11,8 @@ using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Replays; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Replays; @@ -47,6 +50,7 @@ namespace osu.Game.Rulesets.Osu.Tests public void Setup() => Schedule(() => { manualClock = null; + SelectedMods.Value = Array.Empty(); }); /// @@ -102,6 +106,34 @@ namespace osu.Game.Rulesets.Osu.Tests assertSpinnerHit(false); } + [Test] + public void TestVibrateWithoutSpinningOnCentreWithDoubleTime() + { + List frames = new List(); + + const int rate = 2; + // the track clock is going to be playing twice as fast, + // so the vibration time in clock time needs to be twice as long + // to keep constant speed in real time. + const int vibrate_time = 50 * rate; + + int direction = -1; + + for (double i = time_spinner_start; i <= time_spinner_end; i += vibrate_time) + { + frames.Add(new OsuReplayFrame(i, new Vector2(centre_x + direction * 50, centre_y), OsuAction.LeftButton)); + frames.Add(new OsuReplayFrame(i + vibrate_time, new Vector2(centre_x - direction * 50, centre_y), OsuAction.LeftButton)); + + direction *= -1; + } + + AddStep("set DT", () => SelectedMods.Value = new[] { new OsuModDoubleTime { SpeedChange = { Value = rate } } }); + performTest(frames); + + assertTicksHit(0); + assertSpinnerHit(false); + } + /// /// Spins in a single direction. /// From e5b51f769ce72e7394131df3b993a7a840115984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Oct 2023 21:15:40 +0100 Subject: [PATCH 1229/4092] Fix incorrect assertion placement in spinner rotation tracker Checking the delta after the application of rate is not correct. The delta is in screen-space *before* the rate from rate-changing mods were applied; the point of the application of the rate is to compensate for the fact that the spinner is still judged in "track time" - but the goal is to keep the spinner's difficulty *independent* of rate, which means that with DT active the user's spin is "twice as effective" to compensate for the fact that the spinner is twice as short in real time. In another formulation, with DT active, the user gets to record replay frames "half as often" as in normal gameplay. --- .../Skinning/Default/SpinnerRotationTracker.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerRotationTracker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerRotationTracker.cs index 374f3f461b..1d75663fd9 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerRotationTracker.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerRotationTracker.cs @@ -101,11 +101,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default rotationTransferred = true; } + Debug.Assert(Math.Abs(delta) <= 180); + double rate = gameplayClock?.GetTrueGameplayRate() ?? Clock.Rate; delta = (float)(delta * Math.Abs(rate)); - Debug.Assert(Math.Abs(delta) <= 180); - currentRotation += delta; drawableSpinner.Result.History.ReportDelta(Time.Current, delta); } From 12ef93ac3b42a86c031c37a9f89e430bf90912b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Oct 2023 21:31:34 +0100 Subject: [PATCH 1230/4092] Remove no-longer-valid assertion --- osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerInput.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerInput.cs index a6c15d5a67..75bcd809c8 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerInput.cs @@ -130,7 +130,6 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("set DT", () => SelectedMods.Value = new[] { new OsuModDoubleTime { SpeedChange = { Value = rate } } }); performTest(frames); - assertTicksHit(0); assertSpinnerHit(false); } From 87c9df937f85d3f7e9b220c2f21c13caae74b25b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Oct 2023 12:40:21 +0900 Subject: [PATCH 1231/4092] Move team seed to below team name --- .../Components/DrawableTeamSeed.cs | 6 +++++ .../TournamentSpriteTextWithBackground.cs | 2 +- .../Gameplay/Components/TeamDisplay.cs | 27 ++++++------------- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tournament/Components/DrawableTeamSeed.cs b/osu.Game.Tournament/Components/DrawableTeamSeed.cs index a79c63e979..077185f5c0 100644 --- a/osu.Game.Tournament/Components/DrawableTeamSeed.cs +++ b/osu.Game.Tournament/Components/DrawableTeamSeed.cs @@ -22,6 +22,12 @@ namespace osu.Game.Tournament.Components [Resolved] private LadderInfo ladder { get; set; } = null!; + [BackgroundDependencyLoader] + private void load() + { + Text.Font = Text.Font.With(size: 36); + } + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game.Tournament/Components/TournamentSpriteTextWithBackground.cs b/osu.Game.Tournament/Components/TournamentSpriteTextWithBackground.cs index ce118727cd..21439482e3 100644 --- a/osu.Game.Tournament/Components/TournamentSpriteTextWithBackground.cs +++ b/osu.Game.Tournament/Components/TournamentSpriteTextWithBackground.cs @@ -29,7 +29,7 @@ namespace osu.Game.Tournament.Components { Colour = TournamentGame.ELEMENT_FOREGROUND_COLOUR, Font = OsuFont.Torus.With(weight: FontWeight.SemiBold, size: 50), - Padding = new MarginPadding { Horizontal = 10 }, + Padding = new MarginPadding { Left = 10, Right = 20 }, Text = text, } }; diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs index 49fbc64397..3eec67c639 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs @@ -95,28 +95,17 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components } } }, - new FillFlowContainer + teamNameText = new TournamentSpriteTextWithBackground { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5), + Scale = new Vector2(0.5f), + Origin = anchor, + Anchor = anchor, + }, + new DrawableTeamSeed(Team) + { + Scale = new Vector2(0.5f), Origin = anchor, Anchor = anchor, - Children = new Drawable[] - { - teamNameText = new TournamentSpriteTextWithBackground - { - Scale = new Vector2(0.5f), - Origin = anchor, - Anchor = anchor, - }, - new DrawableTeamSeed(Team) - { - Scale = new Vector2(0.5f), - Origin = anchor, - Anchor = anchor, - }, - } }, } }, From feeb95e4c389b85a1e72b1f00d2a79131ecb2652 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Oct 2023 12:44:43 +0900 Subject: [PATCH 1232/4092] Adjust `DrawableTeamTitleWithHeader` to match new layout --- .../Components/DrawableTeamTitleWithHeader.cs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tournament/Components/DrawableTeamTitleWithHeader.cs b/osu.Game.Tournament/Components/DrawableTeamTitleWithHeader.cs index fc4037d4e1..7d8fc847d4 100644 --- a/osu.Game.Tournament/Components/DrawableTeamTitleWithHeader.cs +++ b/osu.Game.Tournament/Components/DrawableTeamTitleWithHeader.cs @@ -18,21 +18,12 @@ namespace osu.Game.Tournament.Components { AutoSizeAxes = Axes.Both, Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10), + Spacing = new Vector2(0, 5), Children = new Drawable[] { new DrawableTeamHeader(colour), - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5), - Children = new Drawable[] - { - new DrawableTeamTitle(team), - new DrawableTeamSeed(team), - } - } + new DrawableTeamTitle(team), + new DrawableTeamSeed(team), } }; } From a3dc9a73b19120c0c4ef4ba7199463fb63ad280c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Oct 2023 13:35:15 +0900 Subject: [PATCH 1233/4092] Revert behaviour changes of `MaxDimensions` test and ignore instead --- .../Gameplay/TestScenePlayerMaxDimensions.cs | 76 ++++++++++++++++--- 1 file changed, 66 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerMaxDimensions.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerMaxDimensions.cs index 6665295e99..53a4abdd07 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerMaxDimensions.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerMaxDimensions.cs @@ -3,14 +3,21 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics.Textures; +using osu.Framework.IO.Stores; using osu.Framework.Testing; using osu.Game.IO; using osu.Game.Rulesets; using osu.Game.Screens.Play; using osu.Game.Skinning; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Processing; namespace osu.Game.Tests.Visual.Gameplay { @@ -21,6 +28,7 @@ namespace osu.Game.Tests.Visual.Gameplay /// /// The HUD is hidden as it does't really affect game balance if HUD elements are larger than they should be. /// + [Ignore("This test is for visual testing, and has no value in being run in standard CI runs.")] public partial class TestScenePlayerMaxDimensions : TestSceneAllRulesetPlayers { // scale textures to 4 times their size. @@ -66,18 +74,66 @@ namespace osu.Game.Tests.Visual.Gameplay remove { } } - public override Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) - { - var texture = base.GetTexture(componentName, wrapModeS, wrapModeT); - - if (texture != null) - texture.ScaleAdjust /= scale_factor; - - return texture; - } - public ISkin FindProvider(Func lookupFunction) => this; public IEnumerable AllSources => new[] { this }; + + protected override IResourceStore CreateTextureLoaderStore(IStorageResourceProvider resources, IResourceStore storage) + => new UpscaledTextureLoaderStore(base.CreateTextureLoaderStore(resources, storage)); + + private class UpscaledTextureLoaderStore : IResourceStore + { + private readonly IResourceStore? textureStore; + + public UpscaledTextureLoaderStore(IResourceStore? textureStore) + { + this.textureStore = textureStore; + } + + public void Dispose() + { + textureStore?.Dispose(); + } + + public TextureUpload Get(string name) + { + var textureUpload = textureStore?.Get(name); + + // NRT not enabled on framework side classes (IResourceStore / TextureLoaderStore), welp. + if (textureUpload == null) + return null!; + + return upscale(textureUpload); + } + + public async Task GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) + { + // NRT not enabled on framework side classes (IResourceStore / TextureLoaderStore), welp. + if (textureStore == null) + return null!; + + var textureUpload = await textureStore.GetAsync(name, cancellationToken).ConfigureAwait(false); + + if (textureUpload == null) + return null!; + + return await Task.Run(() => upscale(textureUpload), cancellationToken).ConfigureAwait(false); + } + + private TextureUpload upscale(TextureUpload textureUpload) + { + var image = Image.LoadPixelData(textureUpload.Data.ToArray(), textureUpload.Width, textureUpload.Height); + + // The original texture upload will no longer be returned or used. + textureUpload.Dispose(); + + image.Mutate(i => i.Resize(new Size(textureUpload.Width, textureUpload.Height) * scale_factor)); + return new TextureUpload(image); + } + + public Stream? GetStream(string name) => textureStore?.GetStream(name); + + public IEnumerable GetAvailableResources() => textureStore?.GetAvailableResources() ?? Array.Empty(); + } } } } From 37ec10d4f592ebab514c9a9afc5f9dcb8dce7c2f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Oct 2023 13:49:39 +0900 Subject: [PATCH 1234/4092] Fix `TestSongSelectScrollHandling` not waiting for `VolumeOverlay` to load See https://github.com/ppy/osu/actions/runs/6701786492/job/18210372721. --- osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 7fa4f8c836..9e743ef336 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -175,7 +175,7 @@ namespace osu.Game.Tests.Visual.Navigation double scrollPosition = 0; AddStep("set game volume to max", () => Game.Dependencies.Get().SetValue(FrameworkSetting.VolumeUniversal, 1d)); - AddUntilStep("wait for volume overlay to hide", () => Game.ChildrenOfType().Single().State.Value, () => Is.EqualTo(Visibility.Hidden)); + AddUntilStep("wait for volume overlay to hide", () => Game.ChildrenOfType().SingleOrDefault()?.State.Value, () => Is.EqualTo(Visibility.Hidden)); PushAndConfirm(() => songSelect = new TestPlaySongSelect()); AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); From 89444d5544aad8c89be4eff311cd6beeedc1c421 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Oct 2023 14:00:49 +0900 Subject: [PATCH 1235/4092] Fix export test still occasionally failing due to file write in progress https://github.com/ppy/osu/actions/runs/6701591401/job/18209826074 Basically, `File.Move` may not be an atomic operation. --- .../Gameplay/TestScenePlayerLocalScoreImport.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index 1254aa0639..0dd544bb30 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -214,10 +214,18 @@ namespace osu.Game.Tests.Visual.Gameplay // Files starting with _ are temporary, created by CreateFileSafely call. AddUntilStep("wait for export file", () => filePath = LocalStorage.GetFiles("exports").SingleOrDefault(f => !Path.GetFileName(f).StartsWith("_", StringComparison.Ordinal)), () => Is.Not.Null); - AddAssert("filesize is non-zero", () => + AddUntilStep("filesize is non-zero", () => { - using (var stream = LocalStorage.GetStream(filePath)) - return stream.Length; + try + { + using (var stream = LocalStorage.GetStream(filePath)) + return stream.Length; + } + catch (IOException) + { + // file move may still be in progress. + return 0; + } }, () => Is.Not.Zero); } From 66b84d02cb1631302631d5b16afd31d0d420a13a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Oct 2023 14:20:11 +0900 Subject: [PATCH 1236/4092] Add note about `TestGameplayExitFlow` failure, and ignore for now See: https://github.com/ppy/osu/actions/runs/6695995685/job/18194110641 https://github.com/ppy/osu/actions/runs/6700910613/job/18208272419 --- osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 09624f63b7..16030d568b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -693,7 +693,9 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] // See above + [Ignore("Failing too often, needs revisiting in some future.")] + // This test is failing even after 10 retries (see https://github.com/ppy/osu/actions/runs/6700910613/job/18208272419) + // Something is stopping the ready button from changing states, over multiple runs. public void TestGameplayExitFlow() { Bindable? holdDelay = null; From bdd3f2847b8a27d1e34657297fdc290deeaf1e88 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Oct 2023 14:26:00 +0900 Subject: [PATCH 1237/4092] Add an extra storyboard sample to avoid intermittent failures in `TestStoryboardSamplesStopOnSkip` Probably CI running slow timing balls. The point of failure is `waitUntilStoryboardSamplesPlay()` after already testing the important part of the test (that the samples stop on skip) so let's give it another possible point to recover. See https://github.com/ppy/osu/actions/runs/6698399814/job/18201753701. --- .../Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs index a9d4508f70..11dc0f9c30 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs @@ -41,6 +41,7 @@ namespace osu.Game.Tests.Visual.Gameplay backgroundLayer.Add(new StoryboardSampleInfo("Intro/welcome.mp3", time: -7000, volume: 20)); backgroundLayer.Add(new StoryboardSampleInfo("Intro/welcome.mp3", time: -5000, volume: 20)); backgroundLayer.Add(new StoryboardSampleInfo("Intro/welcome.mp3", time: 0, volume: 20)); + backgroundLayer.Add(new StoryboardSampleInfo("Intro/welcome.mp3", time: 2000, volume: 20)); } [SetUp] From d379e553da920cfd8cbccca2f5a03786c8627dc6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Oct 2023 14:31:26 +0900 Subject: [PATCH 1238/4092] Fix back-to-front logging --- osu.Game.Tests/Visual/Gameplay/TestScenePause.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index b072ce191e..29c9381ee4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -95,7 +95,7 @@ namespace osu.Game.Tests.Visual.Gameplay alwaysGoingForward &= goingForward; if (!goingForward) - Logger.Log($"Backwards time occurred ({currentTime:N1} -> {lastTime:N1})"); + Logger.Log($"Backwards time occurred ({lastTime:N1} -> {currentTime:N1})"); lastTime = currentTime; }; From 7ceced70122d51ffc030074d1f3738645367be59 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Oct 2023 14:47:04 +0900 Subject: [PATCH 1239/4092] Scope `TestPauseWithLargeOffset` to focus on what matters See https://github.com/ppy/osu/actions/runs/6693917410/job/18186111009 This test is to make sure we don't seek before the original pause time, so I've exposed that value precisely to avoid CI woes. --- osu.Game.Tests/Visual/Gameplay/TestScenePause.cs | 16 ++++++++++------ .../Screens/Play/MasterGameplayClockContainer.cs | 12 ++++++------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 29c9381ee4..ec3b3e0822 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestPauseWithLargeOffset() { - double lastTime; + double lastStopTime; bool alwaysGoingForward = true; AddStep("force large offset", () => @@ -84,20 +84,24 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("add time forward check hook", () => { - lastTime = double.MinValue; + lastStopTime = double.MinValue; alwaysGoingForward = true; Player.OnUpdate += _ => { - double currentTime = Player.GameplayClockContainer.CurrentTime; - bool goingForward = currentTime >= lastTime - 500; + var masterClock = (MasterGameplayClockContainer)Player.GameplayClockContainer; + + double currentTime = masterClock.CurrentTime; + + bool goingForward = currentTime >= (masterClock.LastStopTime ?? lastStopTime); alwaysGoingForward &= goingForward; if (!goingForward) - Logger.Log($"Backwards time occurred ({lastTime:N1} -> {currentTime:N1})"); + Logger.Log($"Went too far backwards (last stop: {lastStopTime:N1} current: {currentTime:N1})"); - lastTime = currentTime; + if (masterClock.LastStopTime != null) + lastStopTime = masterClock.LastStopTime.Value; }; }); diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 1c860e9d4b..54ed7ba626 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.Play /// /// In the future I want to change this. /// - private double? actualStopTime; + internal double? LastStopTime; [Resolved] private MusicController musicController { get; set; } = null!; @@ -100,7 +100,7 @@ namespace osu.Game.Screens.Play protected override void StopGameplayClock() { - actualStopTime = GameplayClock.CurrentTime; + LastStopTime = GameplayClock.CurrentTime; if (IsLoaded) { @@ -127,17 +127,17 @@ namespace osu.Game.Screens.Play public override void Seek(double time) { // Safety in case the clock is seeked while stopped. - actualStopTime = null; + LastStopTime = null; base.Seek(time); } protected override void PrepareStart() { - if (actualStopTime != null) + if (LastStopTime != null) { - Seek(actualStopTime.Value); - actualStopTime = null; + Seek(LastStopTime.Value); + LastStopTime = null; } else base.PrepareStart(); From 8c067dc584066527badb6a9029f5ef31a932bba8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Oct 2023 14:53:07 +0900 Subject: [PATCH 1240/4092] Fix mod tests not waiting for presets to finish loading See https://github.com/ppy/osu/actions/runs/6692350567/job/18181352850. --- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 3728fb3f21..f0822ce2a8 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -799,8 +799,11 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("difficulty multiplier display shows correct value", () => modSelectOverlay.ChildrenOfType().Single().Current.Value, () => Is.EqualTo(0.7)); } - private void waitForColumnLoad() => AddUntilStep("all column content loaded", - () => modSelectOverlay.ChildrenOfType().Any() && modSelectOverlay.ChildrenOfType().All(column => column.IsLoaded && column.ItemsLoaded)); + private void waitForColumnLoad() => AddUntilStep("all column content loaded", () => + modSelectOverlay.ChildrenOfType().Any() + && modSelectOverlay.ChildrenOfType().All(column => column.IsLoaded && column.ItemsLoaded) + && modSelectOverlay.ChildrenOfType().Any() + && modSelectOverlay.ChildrenOfType().All(column => column.IsLoaded)); private void changeRuleset(int id) { From 64efc3d251ef1c4ff39f2d3b2c25df4dc020ae6e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Oct 2023 15:33:40 +0900 Subject: [PATCH 1241/4092] Decouple metronome tick playback from pendulum movement Not super happy about doing this, but it seems like it's in the best interest of editor usability. --- .../Screens/Edit/Timing/MetronomeDisplay.cs | 74 +++++++++++++------ 1 file changed, 50 insertions(+), 24 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index 9f03281d0c..29e730c865 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -34,16 +34,18 @@ namespace osu.Game.Screens.Edit.Timing private IAdjustableClock metronomeClock = null!; - private Sample? sampleTick; - private Sample? sampleTickDownbeat; private Sample? sampleLatch; - private ScheduledDelegate? tickPlaybackDelegate; + private readonly MetronomeTick metronomeTick = new MetronomeTick(); [Resolved] private OverlayColourProvider overlayColourProvider { get; set; } = null!; - public bool EnableClicking { get; set; } = true; + public bool EnableClicking + { + get => metronomeTick.EnableClicking; + set => metronomeTick.EnableClicking = value; + } public MetronomeDisplay() { @@ -53,8 +55,6 @@ namespace osu.Game.Screens.Edit.Timing [BackgroundDependencyLoader] private void load(AudioManager audio) { - sampleTick = audio.Samples.Get(@"UI/metronome-tick"); - sampleTickDownbeat = audio.Samples.Get(@"UI/metronome-tick-downbeat"); sampleLatch = audio.Samples.Get(@"UI/metronome-latch"); const float taper = 25; @@ -67,8 +67,11 @@ namespace osu.Game.Screens.Edit.Timing AutoSizeAxes = Axes.Both; + metronomeTick.Ticked = onTickPlayed; + InternalChildren = new Drawable[] { + metronomeTick, new Container { Name = @"Taper adjust", @@ -265,9 +268,6 @@ namespace osu.Game.Screens.Edit.Timing isSwinging = false; - tickPlaybackDelegate?.Cancel(); - tickPlaybackDelegate = null; - // instantly latch if pendulum arm is close enough to center (to prevent awkward delayed playback of latch sound) if (Precision.AlmostEquals(swing.Rotation, 0, 1)) { @@ -306,27 +306,53 @@ namespace osu.Game.Screens.Edit.Timing float targetAngle = currentAngle > 0 ? -angle : angle; swing.RotateTo(targetAngle, beatLength, Easing.InOutQuad); + } - if (currentAngle != 0 && Math.Abs(currentAngle - targetAngle) > angle * 1.8f && isSwinging) + private void onTickPlayed() + { + // Originally, this flash only occurred when the pendulum correctly passess the centre. + // Mappers weren't happy with the metronome tick not playing immediately after starting playback + // so now this matches the actual tick sample. + stick.FlashColour(overlayColourProvider.Content1, beatLength, Easing.OutQuint); + } + + private partial class MetronomeTick : BeatSyncedContainer + { + public bool EnableClicking; + + private Sample? sampleTick; + private Sample? sampleTickDownbeat; + + public Action? Ticked; + + public MetronomeTick() { - using (BeginDelayedSequence(beatLength / 2)) - { - stick.FlashColour(overlayColourProvider.Content1, beatLength, Easing.OutQuint); + AllowMistimedEventFiring = false; + } - tickPlaybackDelegate = Schedule(() => - { - if (!EnableClicking) - return; + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + sampleTick = audio.Samples.Get(@"UI/metronome-tick"); + sampleTickDownbeat = audio.Samples.Get(@"UI/metronome-tick-downbeat"); + } - var channel = beatIndex % timingPoint.TimeSignature.Numerator == 0 ? sampleTickDownbeat?.GetChannel() : sampleTick?.GetChannel(); + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) + { + base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); - if (channel == null) - return; + if (!IsBeatSyncedWithTrack || !EnableClicking) + return; - channel.Frequency.Value = RNG.NextDouble(0.98f, 1.02f); - channel.Play(); - }); - } + var channel = beatIndex % timingPoint.TimeSignature.Numerator == 0 ? sampleTickDownbeat?.GetChannel() : sampleTick?.GetChannel(); + + if (channel == null) + return; + + channel.Frequency.Value = RNG.NextDouble(0.98f, 1.02f); + channel.Play(); + + Ticked?.Invoke(); } } } From 144006fbe83e60b6ba5a0cab7856eb3c4416da28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 31 Oct 2023 08:38:22 +0100 Subject: [PATCH 1242/4092] Update autoselect implementation to work correctly with framework changes --- .../Edit/Compose/Components/BeatDivisorControl.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 7eba1fe1cd..b33edb9edb 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -321,7 +321,7 @@ namespace osu.Game.Screens.Edit.Compose.Components Spacing = new Vector2(10), Children = new Drawable[] { - divisorTextBox = new OsuNumberBox + divisorTextBox = new AutoSelectTextBox { RelativeSizeAxes = Axes.X, PlaceholderText = "Beat divisor" @@ -341,8 +341,6 @@ namespace osu.Game.Screens.Edit.Compose.Components base.LoadComplete(); BeatDivisor.BindValueChanged(_ => updateState(), true); divisorTextBox.OnCommit += (_, _) => setPresetsFromTextBoxEntry(); - - divisorTextBox.SelectAll(); } private void setPresetsFromTextBoxEntry() @@ -590,5 +588,16 @@ namespace osu.Game.Screens.Edit.Compose.Components } } } + + private partial class AutoSelectTextBox : OsuNumberBox + { + protected override void LoadComplete() + { + base.LoadComplete(); + + GetContainingInputManager().ChangeFocus(this); + SelectAll(); + } + } } } From 0d44b5af90d606657e63c9c386574dcd5e18c89f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Oct 2023 17:36:23 +0900 Subject: [PATCH 1243/4092] Fix potential texture corruption when cropping gameplay textures of weird aspet ratios Closes https://github.com/ppy/osu/issues/25273. --- osu.Game/Skinning/LegacySkinExtensions.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs index 62197fa8a7..a8ec67d98b 100644 --- a/osu.Game/Skinning/LegacySkinExtensions.cs +++ b/osu.Game/Skinning/LegacySkinExtensions.cs @@ -115,7 +115,18 @@ namespace osu.Game.Skinning maxSize *= texture.ScaleAdjust; - var croppedTexture = texture.Crop(new RectangleF(texture.Width / 2f - maxSize.X / 2f, texture.Height / 2f - maxSize.Y / 2f, maxSize.X, maxSize.Y)); + // Importantly, check per-axis for the minimum dimension to avoid accidentally inflating + // textures with weird aspect ratios. + float newWidth = Math.Min(texture.Width, maxSize.X); + float newHeight = Math.Min(texture.Height, maxSize.Y); + + var croppedTexture = texture.Crop(new RectangleF( + texture.Width / 2f - newWidth / 2f, + texture.Height / 2f - newHeight / 2f, + newWidth, + newHeight + )); + croppedTexture.ScaleAdjust = texture.ScaleAdjust; return croppedTexture; } From 9f5a280bc22b1f88d20b7b885d4aa252f7feee7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 31 Oct 2023 12:25:08 +0100 Subject: [PATCH 1244/4092] Fix key binding row fire-and-forgetting writes Intends to fix test failures as seen in https://github.com/ppy/osu/actions/runs/6692350567/job/18181352642#step:5:129 This is what happens if you carelessly fire and forget. The working theory (that I'm not sure I have the tools to conclusively confirm) is that the async write from the key binding changing could fire _after_ the section is reset. I briefly considered having the test wait for the change, but given that the entirety of the surrounding flow is using sync operations, this just looks like a bug to me. And there's no real sane way to inject async into that flow due to dependence on `OsuButton.Action`. --- osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs index c85fe4727a..e82cebe9f4 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs @@ -498,7 +498,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input if (existingBinding == null) { - realm.WriteAsync(r => r.Find(keyBinding.ID)!.KeyCombinationString = keyBinding.KeyCombination.ToString()); + realm.Write(r => r.Find(keyBinding.ID)!.KeyCombinationString = keyBinding.KeyCombination.ToString()); BindingUpdated?.Invoke(this, new KeyBindingUpdatedEventArgs(bindingConflictResolved: false, advanceToNextBinding)); return; } From 7ea298a1b6a4f17a8ae3b505c599f04ac498e4ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 31 Oct 2023 16:13:44 +0100 Subject: [PATCH 1245/4092] Add xmldoc to `Mod` playability flags --- osu.Game/Rulesets/Mods/Mod.cs | 40 +++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index a0bdc9ff51..49c2fdd394 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -107,12 +107,52 @@ namespace osu.Game.Rulesets.Mods [JsonIgnore] public virtual bool HasImplementation => this is IApplicableMod; + /// + /// Whether this mod can be played by a real human user. + /// Non-user-playable mods are not viable for single-player score submission. + /// + /// + /// + /// is user-playable. + /// is not user-playable. + /// + /// [JsonIgnore] public virtual bool UserPlayable => true; + /// + /// Whether this mod can be specified as a "required" mod in a multiplayer context. + /// + /// + /// + /// is valid for multiplayer. + /// + /// is valid for multiplayer as long as it is a required mod, + /// as that ensures the same duration of gameplay for all users in the room. + /// + /// + /// is not valid for multiplayer, as it leads to varying + /// gameplay duration depending on how the users in the room play. + /// + /// is not valid for multiplayer. + /// + /// [JsonIgnore] public virtual bool ValidForMultiplayer => true; + /// + /// Whether this mod can be specified as a "free" or "allowed" mod in a multiplayer context. + /// + /// + /// + /// is valid for multiplayer as a free mod. + /// + /// is not valid for multiplayer as a free mod, + /// as it could to varying gameplay duration between users in the room depending on whether they picked it. + /// + /// is not valid for multiplayer as a free mod. + /// + /// [JsonIgnore] public virtual bool ValidForMultiplayerAsFreeMod => true; From 3a2645efb17dbe9bee7251b4a8ecc0868cf8799d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 31 Oct 2023 16:15:10 +0100 Subject: [PATCH 1246/4092] Seal `ModAutoplay` playability flags Can't do much more than that due to the unfortunate fact of Cinema inheriting from Autoplay. --- osu.Game/Rulesets/Mods/ModAutoplay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index a3a4adc53d..973fcffba8 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -20,9 +20,9 @@ namespace osu.Game.Rulesets.Mods public override LocalisableString Description => "Watch a perfect automated play through the song."; public override double ScoreMultiplier => 1; - public override bool UserPlayable => false; - public override bool ValidForMultiplayer => false; - public override bool ValidForMultiplayerAsFreeMod => false; + public sealed override bool UserPlayable => false; + public sealed override bool ValidForMultiplayer => false; + public sealed override bool ValidForMultiplayerAsFreeMod => false; public override Type[] IncompatibleMods => new[] { typeof(ModCinema), typeof(ModRelax), typeof(ModAdaptiveSpeed) }; From 456f4ebba2208d4a764df157d02dcbbbe6dda544 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 31 Oct 2023 16:16:14 +0100 Subject: [PATCH 1247/4092] Seal `ModScoreV2` Nobody should ever need to extend it. --- osu.Game/Rulesets/Mods/ModScoreV2.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModScoreV2.cs b/osu.Game/Rulesets/Mods/ModScoreV2.cs index df83d96769..52dcd71666 100644 --- a/osu.Game/Rulesets/Mods/ModScoreV2.cs +++ b/osu.Game/Rulesets/Mods/ModScoreV2.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Mods /// This mod is used strictly to mark osu!stable scores set with the "Score V2" mod active. /// It should not be used in any real capacity going forward. /// - public class ModScoreV2 : Mod + public sealed class ModScoreV2 : Mod { public override string Name => "Score V2"; public override string Acronym => @"SV2"; From a644c75957247c8f16674ea47e1eec4a239f1159 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 31 Oct 2023 16:16:57 +0100 Subject: [PATCH 1248/4092] Mark `ModScoreV2` as invalid for multiplayer Doesn't do much for the client; mostly a safety for osu-web's sake, as without the change it could theoretically fail to validate the mod properly in multiplayer contexts. --- osu.Game/Rulesets/Mods/ModScoreV2.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Rulesets/Mods/ModScoreV2.cs b/osu.Game/Rulesets/Mods/ModScoreV2.cs index 52dcd71666..6a77cafa30 100644 --- a/osu.Game/Rulesets/Mods/ModScoreV2.cs +++ b/osu.Game/Rulesets/Mods/ModScoreV2.cs @@ -17,5 +17,7 @@ namespace osu.Game.Rulesets.Mods public override LocalisableString Description => "Score set on earlier osu! versions with the V2 scoring algorithm active."; public override double ScoreMultiplier => 1; public override bool UserPlayable => false; + public override bool ValidForMultiplayer => false; + public override bool ValidForMultiplayerAsFreeMod => false; } } From 955e2ed05166aeaf6073938661a9bc7197d9b816 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 31 Oct 2023 16:18:09 +0100 Subject: [PATCH 1249/4092] Seal `UnknownMod` Not a class for extension. --- osu.Game/Rulesets/Mods/UnknownMod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/UnknownMod.cs b/osu.Game/Rulesets/Mods/UnknownMod.cs index abe05996ff..31fc09b0a6 100644 --- a/osu.Game/Rulesets/Mods/UnknownMod.cs +++ b/osu.Game/Rulesets/Mods/UnknownMod.cs @@ -5,7 +5,7 @@ using osu.Framework.Localisation; namespace osu.Game.Rulesets.Mods { - public class UnknownMod : Mod + public sealed class UnknownMod : Mod { /// /// The acronym of the mod which could not be resolved. From a90f8dd4f63191209d593201a2bb1a00b7828a37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 31 Oct 2023 16:20:33 +0100 Subject: [PATCH 1250/4092] Seal a few more multiplayer playability flags of rate-changing mods Not really changing anything, just tightening things down to curb possible funny business. --- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 4 ++-- osu.Game/Rulesets/Mods/ModRateAdjust.cs | 2 +- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index 607e6b8399..77aa5cdc15 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -31,8 +31,8 @@ namespace osu.Game.Rulesets.Mods public override double ScoreMultiplier => 0.5; - public override bool ValidForMultiplayer => false; - public override bool ValidForMultiplayerAsFreeMod => false; + public sealed override bool ValidForMultiplayer => false; + public sealed override bool ValidForMultiplayerAsFreeMod => false; public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust), typeof(ModTimeRamp), typeof(ModAutoplay) }; diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index fa1c143585..e5af758b4f 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Mods { public abstract class ModRateAdjust : Mod, IApplicableToRate { - public override bool ValidForMultiplayerAsFreeMod => false; + public sealed override bool ValidForMultiplayerAsFreeMod => false; public abstract BindableNumber SpeedChange { get; } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index d2772417db..36e4522771 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")] public abstract BindableBool AdjustPitch { get; } - public override bool ValidForMultiplayerAsFreeMod => false; + public sealed override bool ValidForMultiplayerAsFreeMod => false; public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust), typeof(ModAdaptiveSpeed) }; From c2de03aa44c85fccb29e5542c63c01d46dc7145d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Nov 2023 18:28:05 +0900 Subject: [PATCH 1251/4092] Fix all spinner ticks being alive and causing performance degradation Regressed in https://github.com/ppy/osu/pull/25216. The new logic will ensure at least one tick is ready for judgement. There shouldn't be a case where more than one is needed in a single frame. --- .../Objects/Drawables/DrawableSpinner.cs | 13 +++++++++++++ .../Objects/Drawables/DrawableSpinnerTick.cs | 8 ++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index c092b4dd4b..2799ba4a25 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -275,6 +275,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (spinningSample != null && spinnerFrequencyModulate) spinningSample.Frequency.Value = spinning_sample_modulated_base_frequency + Progress; + + // Ticks can theoretically be judged at any point in the spinner's duration. + // For performance reasons, we only want to keep the next tick alive. + var next = NestedHitObjects.FirstOrDefault(h => !h.Judged); + + // See default `LifetimeStart` as set in `DrawableSpinnerTick`. + if (next?.LifetimeStart == double.MaxValue) + { + // the tick can be theoretically judged at any point in the spinner's duration, + // so it must be alive throughout the spinner's entire lifetime. + // this mostly matters for correct sample playback. + next.LifetimeStart = HitObject.StartTime; + } } protected override void UpdateAfterChildren() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs index a5785dd1f6..5b55533edd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs @@ -11,8 +11,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public override bool DisplayResult => false; - protected DrawableSpinner DrawableSpinner => (DrawableSpinner)ParentHitObject; - public DrawableSpinnerTick() : this(null) { @@ -29,10 +27,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.OnApply(); - // the tick can be theoretically judged at any point in the spinner's duration, - // so it must be alive throughout the spinner's entire lifetime. - // this mostly matters for correct sample playback. - LifetimeStart = DrawableSpinner.HitObject.StartTime; + // Lifetime will be managed by `DrawableSpinner`. + LifetimeStart = double.MaxValue; } /// From 48bdeaeff14f456abb371878307c91d79156832e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Nov 2023 02:42:36 +0900 Subject: [PATCH 1252/4092] Fix another potential crash in bubbles mod MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Storing `DrawableHitObject` for later use is not safe – especially when accessing `HitObject` – as it's a pooled class. In the case here, it's important to note that `PrepareForUse` can be called a frame or more later in execution, which made this unsafe. Closes https://github.com/ppy/osu/issues/24444. --- osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs | 77 ++++++++++----------- 1 file changed, 36 insertions(+), 41 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs index 9c0e43e96f..b34cc29741 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs @@ -2,11 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -22,6 +20,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Mods { @@ -90,21 +89,18 @@ namespace osu.Game.Rulesets.Osu.Mods break; default: - addBubble(); + BubbleDrawable bubble = bubblePool.Get(); + + bubble.WasHit = drawable.IsHit; + bubble.Position = getPosition(drawableOsuHitObject); + bubble.AccentColour = drawable.AccentColour.Value; + bubble.InitialSize = new Vector2(bubbleSize); + bubble.FadeTime = bubbleFade; + bubble.MaxSize = maxSize; + + bubbleContainer.Add(bubble); break; } - - void addBubble() - { - BubbleDrawable bubble = bubblePool.Get(); - - bubble.DrawableOsuHitObject = drawableOsuHitObject; - bubble.InitialSize = new Vector2(bubbleSize); - bubble.FadeTime = bubbleFade; - bubble.MaxSize = maxSize; - - bubbleContainer.Add(bubble); - } }; drawableObject.OnRevertResult += (drawable, _) => @@ -118,18 +114,38 @@ namespace osu.Game.Rulesets.Osu.Mods }; } + private Vector2 getPosition(DrawableOsuHitObject drawableObject) + { + switch (drawableObject) + { + // SliderHeads are derived from HitCircles, + // so we must handle them before to avoid them using the wrong positioning logic + case DrawableSliderHead: + return drawableObject.HitObject.Position; + + // Using hitobject position will cause issues with HitCircle placement due to stack leniency. + case DrawableHitCircle: + return drawableObject.Position; + + default: + return drawableObject.HitObject.Position; + } + } + #region Pooled Bubble drawable private partial class BubbleDrawable : PoolableDrawable { - public DrawableOsuHitObject? DrawableOsuHitObject { get; set; } - public Vector2 InitialSize { get; set; } public float MaxSize { get; set; } public double FadeTime { get; set; } + public bool WasHit { get; set; } + + public Color4 AccentColour { get; set; } + private readonly Box colourBox; private readonly CircularContainer content; @@ -157,15 +173,12 @@ namespace osu.Game.Rulesets.Osu.Mods protected override void PrepareForUse() { - Debug.Assert(DrawableOsuHitObject.IsNotNull()); - - Colour = DrawableOsuHitObject.IsHit ? Colour4.White : Colour4.Black; + Colour = WasHit ? Colour4.White : Colour4.Black; Scale = new Vector2(1); - Position = getPosition(DrawableOsuHitObject); Size = InitialSize; //We want to fade to a darker colour to avoid colours such as white hiding the "ripple" effect. - ColourInfo colourDarker = DrawableOsuHitObject.AccentColour.Value.Darken(0.1f); + ColourInfo colourDarker = AccentColour.Darken(0.1f); // The absolute length of the bubble's animation, can be used in fractions for animations of partial length double duration = 1700 + Math.Pow(FadeTime, 1.07f); @@ -178,7 +191,7 @@ namespace osu.Game.Rulesets.Osu.Mods .ScaleTo(MaxSize * 1.5f, duration * 0.2f, Easing.OutQuint) .FadeOut(duration * 0.2f, Easing.OutCirc).Expire(); - if (!DrawableOsuHitObject.IsHit) return; + if (!WasHit) return; content.BorderThickness = InitialSize.X / 3.5f; content.BorderColour = Colour4.White; @@ -192,24 +205,6 @@ namespace osu.Game.Rulesets.Osu.Mods // Avoids transparency overlap issues during the bubble "pop" .TransformTo(nameof(BorderThickness), 0f); } - - private Vector2 getPosition(DrawableOsuHitObject drawableObject) - { - switch (drawableObject) - { - // SliderHeads are derived from HitCircles, - // so we must handle them before to avoid them using the wrong positioning logic - case DrawableSliderHead: - return drawableObject.HitObject.Position; - - // Using hitobject position will cause issues with HitCircle placement due to stack leniency. - case DrawableHitCircle: - return drawableObject.Position; - - default: - return drawableObject.HitObject.Position; - } - } } #endregion From 6dab5ee4cfbad32710777b649d5fd5e6cc1d2c3e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Nov 2023 15:13:43 +0900 Subject: [PATCH 1253/4092] Add support for "argon" default skin to expand columns when on mobile device Should ease those looking to play the game on mobile until we (potentially) have a better solution in the future. If this works out well, we can consider rolling it out to other skins. Closes https://github.com/ppy/osu/issues/23377. --- .../Skinning/Argon/ManiaArgonSkinTransformer.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index ddd6365c25..f74b7f1d02 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps; @@ -99,9 +100,14 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon return SkinUtils.As(new Bindable(30)); case LegacyManiaSkinConfigurationLookups.ColumnWidth: - return SkinUtils.As(new Bindable( - stage.IsSpecialColumn(columnIndex) ? 120 : 60 - )); + + float width = 60; + + // Best effort until we have better mobile support. + if (RuntimeInfo.IsMobile) + width = 180 * Math.Min(1, 7f / beatmap.TotalColumns); + + return SkinUtils.As(new Bindable(stage.IsSpecialColumn(columnIndex) ? width * 2 : width)); case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour: From c83589cb742db95d7fb21df6aa2c00836a01be89 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Nov 2023 15:49:10 +0900 Subject: [PATCH 1254/4092] Change osu!mania conversion mod ordering to be more appeasing --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 0055e10ada..0c317e0f8a 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -255,16 +255,6 @@ namespace osu.Game.Rulesets.Mania case ModType.Conversion: return new Mod[] { - new MultiMod(new ManiaModKey4(), - new ManiaModKey5(), - new ManiaModKey6(), - new ManiaModKey7(), - new ManiaModKey8(), - new ManiaModKey9(), - new ManiaModKey10(), - new ManiaModKey1(), - new ManiaModKey2(), - new ManiaModKey3()), new ManiaModRandom(), new ManiaModDualStages(), new ManiaModMirror(), @@ -272,7 +262,19 @@ namespace osu.Game.Rulesets.Mania new ManiaModClassic(), new ManiaModInvert(), new ManiaModConstantSpeed(), - new ManiaModHoldOff() + new ManiaModHoldOff(), + new MultiMod( + new ManiaModKey1(), + new ManiaModKey2(), + new ManiaModKey3(), + new ManiaModKey4(), + new ManiaModKey5(), + new ManiaModKey6(), + new ManiaModKey7(), + new ManiaModKey8(), + new ManiaModKey9(), + new ManiaModKey10() + ), }; case ModType.Automation: From ad82ada030715427397d6ef5b0a08b475dbfef31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 2 Nov 2023 08:18:37 +0100 Subject: [PATCH 1255/4092] Trim redundant comments --- .../Objects/Drawables/DrawableSpinner.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 2799ba4a25..2e9a4d92ec 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -277,17 +277,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables spinningSample.Frequency.Value = spinning_sample_modulated_base_frequency + Progress; // Ticks can theoretically be judged at any point in the spinner's duration. - // For performance reasons, we only want to keep the next tick alive. + // A tick must be alive to correctly play back samples, + // but for performance reasons, we only want to keep the next tick alive. var next = NestedHitObjects.FirstOrDefault(h => !h.Judged); // See default `LifetimeStart` as set in `DrawableSpinnerTick`. if (next?.LifetimeStart == double.MaxValue) - { - // the tick can be theoretically judged at any point in the spinner's duration, - // so it must be alive throughout the spinner's entire lifetime. - // this mostly matters for correct sample playback. next.LifetimeStart = HitObject.StartTime; - } } protected override void UpdateAfterChildren() From 9c1f4b552e651a8d163cd7adf17a0c6ff3e49f08 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Nov 2023 17:04:49 +0900 Subject: [PATCH 1256/4092] Rename and invert flags for slider classic behaviours --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 4 +- .../Objects/Drawables/DrawableSlider.cs | 47 ++++++++++--------- .../Objects/Drawables/DrawableSliderHead.cs | 13 ++++- osu.Game.Rulesets.Osu/Objects/Slider.cs | 8 +++- .../Objects/SliderHeadCircle.cs | 4 +- 5 files changed, 45 insertions(+), 31 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index 8930b4ad70..c668119db7 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -41,10 +41,10 @@ namespace osu.Game.Rulesets.Osu.Mods switch (hitObject) { case Slider slider: - slider.OnlyJudgeNestedObjects = !NoSliderHeadAccuracy.Value; + slider.ClassicSliderBehaviour = NoSliderHeadAccuracy.Value; foreach (var head in slider.NestedHitObjects.OfType()) - head.JudgeAsNormalHitCircle = !NoSliderHeadAccuracy.Value; + head.ClassicSliderBehaviour = NoSliderHeadAccuracy.Value; break; } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index cdfe888c99..a053c99a53 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables /// public Container OverlayElementContainer { get; private set; } - public override bool DisplayResult => !HitObject.OnlyJudgeNestedObjects; + public override bool DisplayResult => HitObject.ClassicSliderBehaviour; [CanBeNull] public PlaySliderBody SliderBody => Body.Drawable as PlaySliderBody; @@ -272,30 +272,31 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (userTriggered || !TailCircle.Judged || Time.Current < HitObject.EndTime) return; - // If only the nested hitobjects are judged, then the slider's own judgement is ignored for scoring purposes. - // But the slider needs to still be judged with a reasonable hit/miss result for visual purposes (hit/miss transforms, etc). - if (HitObject.OnlyJudgeNestedObjects) + if (HitObject.ClassicSliderBehaviour) { - ApplyResult(r => r.Type = NestedHitObjects.Any(h => h.Result.IsHit) ? r.Judgement.MaxResult : r.Judgement.MinResult); - return; - } - - // Otherwise, if this slider also needs to be judged, apply judgement proportionally to the number of nested hitobjects hit. This is the classic osu!stable scoring. - ApplyResult(r => - { - int totalTicks = NestedHitObjects.Count; - int hitTicks = NestedHitObjects.Count(h => h.IsHit); - - if (hitTicks == totalTicks) - r.Type = HitResult.Great; - else if (hitTicks == 0) - r.Type = HitResult.Miss; - else + // Classic behaviour means a slider is judged proportionally to the number of nested hitobjects hit. This is the classic osu!stable scoring. + ApplyResult(r => { - double hitFraction = (double)hitTicks / totalTicks; - r.Type = hitFraction >= 0.5 ? HitResult.Ok : HitResult.Meh; - } - }); + int totalTicks = NestedHitObjects.Count; + int hitTicks = NestedHitObjects.Count(h => h.IsHit); + + if (hitTicks == totalTicks) + r.Type = HitResult.Great; + else if (hitTicks == 0) + r.Type = HitResult.Miss; + else + { + double hitFraction = (double)hitTicks / totalTicks; + r.Type = hitFraction >= 0.5 ? HitResult.Ok : HitResult.Meh; + } + }); + } + else + { + // If only the nested hitobjects are judged, then the slider's own judgement is ignored for scoring purposes. + // But the slider needs to still be judged with a reasonable hit/miss result for visual purposes (hit/miss transforms, etc). + ApplyResult(r => r.Type = NestedHitObjects.Any(h => h.Result.IsHit) ? r.Judgement.MaxResult : r.Judgement.MinResult); + } } public override void PlaySamples() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index be6c322668..d99ea70fbd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -16,7 +16,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject; - public override bool DisplayResult => HitObject?.JudgeAsNormalHitCircle ?? base.DisplayResult; + public override bool DisplayResult + { + get + { + if (HitObject?.ClassicSliderBehaviour == true) + return false; + + return base.DisplayResult; + } + } private readonly IBindable pathVersion = new Bindable(); @@ -56,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { Debug.Assert(HitObject != null); - if (HitObject.JudgeAsNormalHitCircle) + if (!HitObject.ClassicSliderBehaviour) return base.ResultFor(timeOffset); // If not judged as a normal hitcircle, judge as a slider tick instead. This is the classic osu!stable scoring. diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 3cb9b96090..bcbed8b17f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -127,7 +127,7 @@ namespace osu.Game.Rulesets.Osu.Objects /// Whether this 's judgement is fully handled by its nested s. /// If false, this will be judged proportionally to the number of nested s hit. /// - public bool OnlyJudgeNestedObjects = true; + public bool ClassicSliderBehaviour = false; public BindableNumber SliderVelocityMultiplierBindable { get; } = new BindableDouble(1) { @@ -262,7 +262,11 @@ namespace osu.Game.Rulesets.Osu.Objects TailSamples = this.GetNodeSamples(repeatCount + 1); } - public override Judgement CreateJudgement() => OnlyJudgeNestedObjects ? new OsuIgnoreJudgement() : new OsuJudgement(); + public override Judgement CreateJudgement() => ClassicSliderBehaviour + // See logic in `DrawableSlider.CheckForResult()` + ? new OsuJudgement() + // Of note, this creates a combo discrepancy for non-classic-mod sliders (there is no combo increase for tail or slider judgement). + : new OsuIgnoreJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs index 73c222653e..4712d61dfe 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs @@ -12,8 +12,8 @@ namespace osu.Game.Rulesets.Osu.Objects /// Whether to treat this as a normal for judgement purposes. /// If false, this will be judged as a instead. /// - public bool JudgeAsNormalHitCircle = true; + public bool ClassicSliderBehaviour; - public override Judgement CreateJudgement() => JudgeAsNormalHitCircle ? base.CreateJudgement() : new SliderTickJudgement(); + public override Judgement CreateJudgement() => ClassicSliderBehaviour ? new SliderTickJudgement() : base.CreateJudgement(); } } From 9af2a5930cf3696ffecea558e94bcec01b642ace Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Nov 2023 17:39:41 +0900 Subject: [PATCH 1257/4092] Remove redundant passing of `Scale` to nested objects --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index bcbed8b17f..0859a6fe17 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -187,7 +187,6 @@ namespace osu.Game.Rulesets.Osu.Objects StartTime = e.Time, Position = Position + Path.PositionAt(e.PathProgress), StackHeight = StackHeight, - Scale = Scale, }); break; @@ -206,7 +205,7 @@ namespace osu.Game.Rulesets.Osu.Objects RepeatIndex = e.SpanIndex, StartTime = e.Time, Position = EndPosition, - StackHeight = StackHeight + StackHeight = StackHeight, }); break; @@ -217,7 +216,6 @@ namespace osu.Game.Rulesets.Osu.Objects StartTime = StartTime + (e.SpanIndex + 1) * SpanDuration, Position = Position + Path.PositionAt(e.PathProgress), StackHeight = StackHeight, - Scale = Scale, }); break; } From a7705284e7b56bcedaee756ec8f37432f029d62c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Nov 2023 18:02:47 +0900 Subject: [PATCH 1258/4092] Add better commenting around `DrawableSliderHead` logic --- .../Objects/Drawables/DrawableSliderHead.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index d99ea70fbd..8463c78319 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -65,12 +65,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { Debug.Assert(HitObject != null); - if (!HitObject.ClassicSliderBehaviour) - return base.ResultFor(timeOffset); - - // If not judged as a normal hitcircle, judge as a slider tick instead. This is the classic osu!stable scoring. - var result = base.ResultFor(timeOffset); - return result.IsHit() ? HitResult.LargeTickHit : HitResult.LargeTickMiss; + return HitObject.ClassicSliderBehaviour + // In classic slider behaviour, heads are considered fully hit if in the largest hit window. + // We can't award a full Great because the true Great judgement is awarded on the Slider itself, + // reduced based on number of ticks hit. + // + // so we use the most suitable LargeTick judgement here instead. + ? base.ResultFor(timeOffset).IsHit() ? HitResult.LargeTickHit : HitResult.LargeTickMiss + : base.ResultFor(timeOffset); } public override void Shake() From bf9f20705f06e3094ae7e772f4ac59fa45472856 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Nov 2023 17:39:23 +0900 Subject: [PATCH 1259/4092] Simplify classic behaviour flag to only need to be specified on the slider itself --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 4 ---- osu.Game.Rulesets.Osu/Objects/Slider.cs | 14 +++++++++++++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index c668119db7..f20f95b384 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -42,10 +42,6 @@ namespace osu.Game.Rulesets.Osu.Mods { case Slider slider: slider.ClassicSliderBehaviour = NoSliderHeadAccuracy.Value; - - foreach (var head in slider.NestedHitObjects.OfType()) - head.ClassicSliderBehaviour = NoSliderHeadAccuracy.Value; - break; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 0859a6fe17..50791183f5 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -127,7 +127,18 @@ namespace osu.Game.Rulesets.Osu.Objects /// Whether this 's judgement is fully handled by its nested s. /// If false, this will be judged proportionally to the number of nested s hit. /// - public bool ClassicSliderBehaviour = false; + public bool ClassicSliderBehaviour + { + get => classicSliderBehaviour; + set + { + classicSliderBehaviour = value; + if (HeadCircle != null) + HeadCircle.ClassicSliderBehaviour = value; + } + } + + private bool classicSliderBehaviour; public BindableNumber SliderVelocityMultiplierBindable { get; } = new BindableDouble(1) { @@ -196,6 +207,7 @@ namespace osu.Game.Rulesets.Osu.Objects StartTime = e.Time, Position = Position, StackHeight = StackHeight, + ClassicSliderBehaviour = ClassicSliderBehaviour, }); break; From 818432fab4279b6691286483b6be88eb663e84c3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Nov 2023 19:27:55 +0900 Subject: [PATCH 1260/4092] Fix non-classic osu! combo not matching expectations --- osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs | 9 +++++++++ osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs | 11 ----------- .../Objects/SliderTailCircle.cs | 16 ++++++++++++++-- osu.Game/Rulesets/Scoring/HitResult.cs | 3 +++ 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs index ddbbb300ca..88a34fcb8f 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs @@ -3,6 +3,8 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects @@ -43,5 +45,12 @@ namespace osu.Game.Rulesets.Osu.Objects } protected override HitWindows CreateHitWindows() => HitWindows.Empty; + + public override Judgement CreateJudgement() => new SliderEndJudgement(); + + public class SliderEndJudgement : OsuJudgement + { + public override HitResult MaxResult => HitResult.LargeTickHit; + } } } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs index cca86361c2..e95cfd369d 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs @@ -1,10 +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 osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Osu.Judgements; -using osu.Game.Rulesets.Scoring; - namespace osu.Game.Rulesets.Osu.Objects { public class SliderRepeat : SliderEndCircle @@ -13,12 +9,5 @@ namespace osu.Game.Rulesets.Osu.Objects : base(slider) { } - - public override Judgement CreateJudgement() => new SliderRepeatJudgement(); - - public class SliderRepeatJudgement : OsuJudgement - { - public override HitResult MaxResult => HitResult.LargeTickHit; - } } } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index 54d2afb444..357476ed30 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -9,16 +9,28 @@ namespace osu.Game.Rulesets.Osu.Objects { public class SliderTailCircle : SliderEndCircle { + /// + /// Whether to treat this as a normal for judgement purposes. + /// If false, this will be judged as a instead. + /// + public bool ClassicSliderBehaviour; + public SliderTailCircle(Slider slider) : base(slider) { } - public override Judgement CreateJudgement() => new SliderTailJudgement(); + public override Judgement CreateJudgement() => ClassicSliderBehaviour ? new LegacyTailJudgement() : new TailJudgement(); - public class SliderTailJudgement : OsuJudgement + public class LegacyTailJudgement : OsuJudgement { public override HitResult MaxResult => HitResult.SmallTickHit; } + + public class TailJudgement : SliderEndJudgement + { + public override HitResult MaxResult => HitResult.LargeTickHit; + public override HitResult MinResult => HitResult.IgnoreMiss; + } } } diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index fed338b012..6380b73558 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -350,6 +350,9 @@ namespace osu.Game.Rulesets.Scoring if (maxResult.IsBonus() && minResult != HitResult.IgnoreMiss) throw new ArgumentOutOfRangeException(nameof(minResult), $"{HitResult.IgnoreMiss} is the only valid minimum result for a {maxResult} judgement."); + if (minResult == HitResult.IgnoreMiss) + return; + if (maxResult == HitResult.LargeTickHit && minResult != HitResult.LargeTickMiss) throw new ArgumentOutOfRangeException(nameof(minResult), $"{HitResult.LargeTickMiss} is the only valid minimum result for a {maxResult} judgement."); From 704f5a6de3a4dba381efc0dc62a77ce1cb166f7a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Nov 2023 19:40:20 +0900 Subject: [PATCH 1261/4092] Adjust sizing slightly to ensure we stay within 1366 limits --- .../Skinning/Argon/ManiaArgonSkinTransformer.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index f74b7f1d02..ca7f84cb4d 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -101,13 +101,17 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon case LegacyManiaSkinConfigurationLookups.ColumnWidth: - float width = 60; + float width; + + bool isSpecialColumn = stage.IsSpecialColumn(columnIndex); // Best effort until we have better mobile support. if (RuntimeInfo.IsMobile) - width = 180 * Math.Min(1, 7f / beatmap.TotalColumns); + width = 170 * Math.Min(1, 7f / beatmap.TotalColumns) * (isSpecialColumn ? 1.8f : 1); + else + width = 60 * (isSpecialColumn ? 2 : 1); - return SkinUtils.As(new Bindable(stage.IsSpecialColumn(columnIndex) ? width * 2 : width)); + return SkinUtils.As(new Bindable(width)); case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour: From f0f595ca40ab02cd0637a50ce0246d05423fc0c6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Nov 2023 19:52:49 +0900 Subject: [PATCH 1262/4092] Continue to play spinner bonus sounds when MAX display occurs --- .../Objects/Drawables/DrawableSpinner.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 2e9a4d92ec..aa43532f65 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -303,6 +303,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private static readonly int score_per_tick = new SpinnerBonusTick.OsuSpinnerBonusTickJudgement().MaxNumericResult; + private int lastMaxSamplePlayback; + private void updateBonusScore() { if (ticks.Count == 0) @@ -322,7 +324,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables var tick = ticks.FirstOrDefault(t => !t.Result.HasResult); // tick may be null if we've hit the spin limit. - tick?.TriggerResult(true); + if (tick == null) + { + // we still want to play a sound. this will probably be a new sound in the future, but for now let's continue playing the bonus sound. + // round robin to avoid hitting playback concurrency. + tick = ticks.OfType().Skip(lastMaxSamplePlayback++ % HitObject.MaximumBonusSpins).First(); + tick.PlaySamples(); + } + else + tick.TriggerResult(true); completedFullSpins.Value++; } From ac6fb386d137a13eae242fa65cf417e3d6ec4abb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Nov 2023 23:42:52 +0900 Subject: [PATCH 1263/4092] Remove nested ternary --- .../Objects/Drawables/DrawableSliderHead.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index 8463c78319..83882481a8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -65,14 +65,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { Debug.Assert(HitObject != null); - return HitObject.ClassicSliderBehaviour - // In classic slider behaviour, heads are considered fully hit if in the largest hit window. + if (HitObject.ClassicSliderBehaviour) + { + // With classic slider behaviour, heads are considered fully hit if in the largest hit window. // We can't award a full Great because the true Great judgement is awarded on the Slider itself, // reduced based on number of ticks hit. // // so we use the most suitable LargeTick judgement here instead. - ? base.ResultFor(timeOffset).IsHit() ? HitResult.LargeTickHit : HitResult.LargeTickMiss - : base.ResultFor(timeOffset); + return base.ResultFor(timeOffset).IsHit() ? HitResult.LargeTickHit : HitResult.LargeTickMiss; + } + + return base.ResultFor(timeOffset); } public override void Shake() From 86ede717cbeb705a7036276b49e5c53aeb6ecc53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 2 Nov 2023 18:52:47 +0100 Subject: [PATCH 1264/4092] Clean up comment --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index 83882481a8..ff690417a8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -69,8 +69,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { // With classic slider behaviour, heads are considered fully hit if in the largest hit window. // We can't award a full Great because the true Great judgement is awarded on the Slider itself, - // reduced based on number of ticks hit. - // + // reduced based on number of ticks hit, // so we use the most suitable LargeTick judgement here instead. return base.ResultFor(timeOffset).IsHit() ? HitResult.LargeTickHit : HitResult.LargeTickMiss; } From a0757ce13f3156c526d51b7d3be6afcd47436230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 2 Nov 2023 18:58:14 +0100 Subject: [PATCH 1265/4092] Update xmldoc to match flipped flag semantics --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 4 ++-- osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 50791183f5..7f2d9592af 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -124,8 +124,8 @@ namespace osu.Game.Rulesets.Osu.Objects public double TickDistanceMultiplier = 1; /// - /// Whether this 's judgement is fully handled by its nested s. - /// If false, this will be judged proportionally to the number of nested s hit. + /// If , 's judgement is fully handled by its nested s. + /// If , this will be judged proportionally to the number of nested s hit. /// public bool ClassicSliderBehaviour { diff --git a/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs index 4712d61dfe..8305481788 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs @@ -9,8 +9,8 @@ namespace osu.Game.Rulesets.Osu.Objects public class SliderHeadCircle : HitCircle { /// - /// Whether to treat this as a normal for judgement purposes. - /// If false, this will be judged as a instead. + /// If , treat this as a normal for judgement purposes. + /// If , this will be judged as a instead. /// public bool ClassicSliderBehaviour; From 9f11a04cc731159c6d2c0e4f7084238f6bd88360 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Oct 2023 13:24:01 +0100 Subject: [PATCH 1266/4092] Generalise notion of 'touch device' mod --- osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs | 9 +-------- osu.Game/Rulesets/Mods/ModTouchDevice.cs | 16 ++++++++++++++++ osu.Game/Rulesets/Ruleset.cs | 2 ++ 3 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 osu.Game/Rulesets/Mods/ModTouchDevice.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs index fd5c46a226..abb67c519b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs @@ -1,18 +1,11 @@ // 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.Localisation; using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModTouchDevice : Mod + public class OsuModTouchDevice : ModTouchDevice { - public override string Name => "Touch Device"; - public override string Acronym => "TD"; - public override LocalisableString Description => "Automatically applied to plays on devices with a touchscreen."; - public override double ScoreMultiplier => 1; - - public override ModType Type => ModType.System; } } diff --git a/osu.Game/Rulesets/Mods/ModTouchDevice.cs b/osu.Game/Rulesets/Mods/ModTouchDevice.cs new file mode 100644 index 0000000000..2daea8ea5d --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModTouchDevice.cs @@ -0,0 +1,16 @@ +// 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.Localisation; + +namespace osu.Game.Rulesets.Mods +{ + public class ModTouchDevice : Mod + { + public sealed override string Name => "Touch Device"; + public sealed override string Acronym => "TD"; + public sealed override LocalisableString Description => "Automatically applied to plays on devices with a touchscreen."; + public sealed override double ScoreMultiplier => 1; + public sealed override ModType Type => ModType.System; + } +} diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index be0d757e06..f8aa6c9f57 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -204,6 +204,8 @@ namespace osu.Game.Rulesets public ModAutoplay? GetAutoplayMod() => CreateMod(); + public ModTouchDevice? GetTouchDeviceMod() => CreateMod(); + /// /// Create a transformer which adds lookups specific to a ruleset to skin sources. /// From 68efb3c110aa3617fb801cb8afe6b7fb15afcba1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 2 Nov 2023 19:25:57 +0100 Subject: [PATCH 1267/4092] Mark `ModTouchDevice` as always valid for submission --- osu.Game/Rulesets/Mods/IMod.cs | 7 +++++++ osu.Game/Rulesets/Mods/Mod.cs | 4 ++++ osu.Game/Rulesets/Mods/ModTouchDevice.cs | 1 + 3 files changed, 12 insertions(+) diff --git a/osu.Game/Rulesets/Mods/IMod.cs b/osu.Game/Rulesets/Mods/IMod.cs index ce2d123884..744d02a4fa 100644 --- a/osu.Game/Rulesets/Mods/IMod.cs +++ b/osu.Game/Rulesets/Mods/IMod.cs @@ -59,6 +59,13 @@ namespace osu.Game.Rulesets.Mods /// bool ValidForMultiplayerAsFreeMod { get; } + /// + /// Indicates that this mod is always permitted in scenarios wherein a user is submitting a score regardless of other circumstances. + /// Intended for mods that are informational in nature and do not really affect gameplay by themselves, + /// but are more of a gauge of increased/decreased difficulty due to the user's configuration (e.g. ). + /// + bool AlwaysValidForSubmission { get; } + /// /// Create a fresh instance based on this mod. /// diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 49c2fdd394..775f6a0ed4 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -156,6 +156,10 @@ namespace osu.Game.Rulesets.Mods [JsonIgnore] public virtual bool ValidForMultiplayerAsFreeMod => true; + /// + [JsonIgnore] + public virtual bool AlwaysValidForSubmission => false; + /// /// Whether this mod requires configuration to apply changes to the game. /// diff --git a/osu.Game/Rulesets/Mods/ModTouchDevice.cs b/osu.Game/Rulesets/Mods/ModTouchDevice.cs index 2daea8ea5d..0865603c8c 100644 --- a/osu.Game/Rulesets/Mods/ModTouchDevice.cs +++ b/osu.Game/Rulesets/Mods/ModTouchDevice.cs @@ -12,5 +12,6 @@ namespace osu.Game.Rulesets.Mods public sealed override LocalisableString Description => "Automatically applied to plays on devices with a touchscreen."; public sealed override double ScoreMultiplier => 1; public sealed override ModType Type => ModType.System; + public sealed override bool AlwaysValidForSubmission => true; } } From 980c900f43aa958e25d519e687a4a2e224d2399c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Oct 2023 14:08:56 +0100 Subject: [PATCH 1268/4092] Add component for game-wide touch detection --- .../Navigation/TestSceneScreenNavigation.cs | 22 ++++++++++ osu.Game/Configuration/SessionStatics.cs | 10 ++++- osu.Game/Input/TouchInputInterceptor.cs | 41 +++++++++++++++++++ osu.Game/OsuGameBase.cs | 2 + 4 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Input/TouchInputInterceptor.cs diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 9e743ef336..f4318da403 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -12,6 +12,7 @@ using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -835,6 +836,27 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert("exit dialog is shown", () => Game.Dependencies.Get().CurrentDialog is ConfirmExitDialog); } + [Test] + public void TestTouchScreenDetection() + { + AddStep("touch logo", () => + { + var button = Game.ChildrenOfType().Single(); + var touch = new Touch(TouchSource.Touch1, button.ScreenSpaceDrawQuad.Centre); + InputManager.BeginTouch(touch); + InputManager.EndTouch(touch); + }); + AddAssert("touch screen detected active", () => Game.Dependencies.Get().Get(Static.TouchInputActive), () => Is.True); + + AddStep("click settings button", () => + { + var button = Game.ChildrenOfType().Last(); + InputManager.MoveMouseTo(button); + InputManager.Click(MouseButton.Left); + }); + AddAssert("touch screen detected inactive", () => Game.Dependencies.Get().Get(Static.TouchInputActive), () => Is.False); + } + private Func playToResults() { var player = playToCompletion(); diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs index 5e2f0c2128..0fc2076a7e 100644 --- a/osu.Game/Configuration/SessionStatics.cs +++ b/osu.Game/Configuration/SessionStatics.cs @@ -4,6 +4,7 @@ #nullable disable using osu.Game.Graphics.UserInterface; +using osu.Game.Input; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.Mods; @@ -24,6 +25,7 @@ namespace osu.Game.Configuration SetDefault(Static.LastHoverSoundPlaybackTime, (double?)null); SetDefault(Static.LastModSelectPanelSamplePlaybackTime, (double?)null); SetDefault(Static.SeasonalBackgrounds, null); + SetDefault(Static.TouchInputActive, false); } /// @@ -63,6 +65,12 @@ namespace osu.Game.Configuration /// The last playback time in milliseconds of an on/off sample (from ). /// Used to debounce on/off sounds game-wide to avoid volume saturation, especially in activating mod presets with many mods. /// - LastModSelectPanelSamplePlaybackTime + LastModSelectPanelSamplePlaybackTime, + + /// + /// Whether the last positional input received was a touch input. + /// Used in touchscreen detection scenarios (). + /// + TouchInputActive, } } diff --git a/osu.Game/Input/TouchInputInterceptor.cs b/osu.Game/Input/TouchInputInterceptor.cs new file mode 100644 index 0000000000..377d3c6d9f --- /dev/null +++ b/osu.Game/Input/TouchInputInterceptor.cs @@ -0,0 +1,41 @@ +// 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.Input.Events; +using osu.Framework.Input.StateChanges; +using osu.Game.Configuration; +using osuTK; + +namespace osu.Game.Input +{ + /// + /// Intercepts all positional input events and sets the appropriate value + /// for consumption by particular game screens. + /// + public partial class TouchInputInterceptor : Component + { + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + + [Resolved] + private SessionStatics statics { get; set; } = null!; + + protected override bool Handle(UIEvent e) + { + switch (e) + { + case MouseEvent: + if (e.CurrentState.Mouse.LastSource is not ISourcedFromTouch) + statics.SetValue(Static.TouchInputActive, false); + break; + + case TouchEvent: + statics.SetValue(Static.TouchInputActive, true); + break; + } + + return false; + } + } +} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 1f46eb0c0d..e4376d1c02 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -407,6 +407,8 @@ namespace osu.Game }) }); + base.Content.Add(new TouchInputInterceptor()); + KeyBindingStore = new RealmKeyBindingStore(realm, keyCombinationProvider); KeyBindingStore.Register(globalBindings, RulesetStore.AvailableRulesets); From 2f6ff893b550a18ec1f4791d45c96c5dcb3e99cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Oct 2023 14:38:20 +0100 Subject: [PATCH 1269/4092] Automatically activate and deactivate touch device mod in song select --- .../Navigation/TestSceneScreenNavigation.cs | 42 ++++++++++++++ osu.Game/Rulesets/Mods/ModTouchDevice.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 1 + .../Select/SongSelectTouchInputHandler.cs | 56 +++++++++++++++++++ osu.Game/Utils/ModUtils.cs | 2 +- 5 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Screens/Select/SongSelectTouchInputHandler.cs diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index f4318da403..0ba8f7136d 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -855,6 +855,48 @@ namespace osu.Game.Tests.Visual.Navigation InputManager.Click(MouseButton.Left); }); AddAssert("touch screen detected inactive", () => Game.Dependencies.Get().Get(Static.TouchInputActive), () => Is.False); + + AddStep("close settings sidebar", () => InputManager.Key(Key.Escape)); + + PushAndConfirm(() => new TestPlaySongSelect()); + AddStep("switch to osu! ruleset", () => + { + InputManager.PressKey(Key.LControl); + InputManager.Key(Key.Number1); + InputManager.ReleaseKey(Key.LControl); + }); + AddStep("touch beatmap wedge", () => + { + var wedge = Game.ChildrenOfType().Single(); + var touch = new Touch(TouchSource.Touch2, wedge.ScreenSpaceDrawQuad.Centre); + InputManager.BeginTouch(touch); + InputManager.EndTouch(touch); + }); + AddUntilStep("touch device mod activated", () => Game.SelectedMods.Value, () => Has.One.InstanceOf()); + + AddStep("switch to mania ruleset", () => + { + InputManager.PressKey(Key.LControl); + InputManager.Key(Key.Number4); + InputManager.ReleaseKey(Key.LControl); + }); + AddUntilStep("touch device mod not activated", () => Game.SelectedMods.Value, () => Has.None.InstanceOf()); + AddStep("touch beatmap wedge", () => + { + var wedge = Game.ChildrenOfType().Single(); + var touch = new Touch(TouchSource.Touch2, wedge.ScreenSpaceDrawQuad.Centre); + InputManager.BeginTouch(touch); + InputManager.EndTouch(touch); + }); + AddUntilStep("touch device mod not activated", () => Game.SelectedMods.Value, () => Has.None.InstanceOf()); + + AddStep("switch to osu! ruleset", () => + { + InputManager.PressKey(Key.LControl); + InputManager.Key(Key.Number1); + InputManager.ReleaseKey(Key.LControl); + }); + AddUntilStep("touch device mod activated", () => Game.SelectedMods.Value, () => Has.One.InstanceOf()); } private Func playToResults() diff --git a/osu.Game/Rulesets/Mods/ModTouchDevice.cs b/osu.Game/Rulesets/Mods/ModTouchDevice.cs index 0865603c8c..f532e7b19d 100644 --- a/osu.Game/Rulesets/Mods/ModTouchDevice.cs +++ b/osu.Game/Rulesets/Mods/ModTouchDevice.cs @@ -5,7 +5,7 @@ using osu.Framework.Localisation; namespace osu.Game.Rulesets.Mods { - public class ModTouchDevice : Mod + public class ModTouchDevice : Mod, IApplicableMod { public sealed override string Name => "Touch Device"; public sealed override string Acronym => "TD"; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index dfea4e3794..74454713d1 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -279,6 +279,7 @@ namespace osu.Game.Screens.Select { RelativeSizeAxes = Axes.Both, }, + new SongSelectTouchInputHandler() }); if (ShowFooter) diff --git a/osu.Game/Screens/Select/SongSelectTouchInputHandler.cs b/osu.Game/Screens/Select/SongSelectTouchInputHandler.cs new file mode 100644 index 0000000000..bf4761e871 --- /dev/null +++ b/osu.Game/Screens/Select/SongSelectTouchInputHandler.cs @@ -0,0 +1,56 @@ +// 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 osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Configuration; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Screens.Select +{ + public partial class SongSelectTouchInputHandler : Component + { + [Resolved] + private Bindable ruleset { get; set; } = null!; + + [Resolved] + private Bindable> mods { get; set; } = null!; + + private IBindable touchActive = null!; + + [BackgroundDependencyLoader] + private void load(SessionStatics statics) + { + touchActive = statics.GetBindable(Static.TouchInputActive); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ruleset.BindValueChanged(_ => updateState()); + mods.BindValueChanged(_ => updateState()); + touchActive.BindValueChanged(_ => updateState()); + updateState(); + } + + private void updateState() + { + var touchDeviceMod = ruleset.Value.CreateInstance().GetTouchDeviceMod(); + + if (touchDeviceMod == null) + return; + + bool touchDeviceModEnabled = mods.Value.Any(mod => mod is ModTouchDevice); + + if (touchActive.Value && !touchDeviceModEnabled) + mods.Value = mods.Value.Append(touchDeviceMod).ToArray(); + if (!touchActive.Value && touchDeviceModEnabled) + mods.Value = mods.Value.Where(mod => mod is not ModTouchDevice).ToArray(); + } + } +} diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index edf9cc80da..1bd60fcdde 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -121,7 +121,7 @@ namespace osu.Game.Utils if (!CheckCompatibleSet(mods, out invalidMods)) return false; - return checkValid(mods, m => m.Type != ModType.System && m.HasImplementation, out invalidMods); + return checkValid(mods, m => m.HasImplementation, out invalidMods); } /// From 29d4d81eaab886e0f99bd2c579a1425a41b4b17f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 25 Oct 2023 19:56:51 +0200 Subject: [PATCH 1270/4092] Add test scene for basic touchscreen detection scenarios --- .../Mods/TestSceneOsuModTouchDevice.cs | 134 ++++++++++++++++++ .../Navigation/TestSceneScreenNavigation.cs | 5 +- 2 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs new file mode 100644 index 0000000000..9c84bcc241 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs @@ -0,0 +1,134 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Input; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Timing; +using osu.Game.Overlays; +using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Tests.Visual; +using osuTK.Input; + +namespace osu.Game.Rulesets.Osu.Tests.Mods +{ + public partial class TestSceneOsuModTouchDevice : PlayerTestScene + { + private TestOnScreenDisplay testOnScreenDisplay = null!; + + protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => + new OsuBeatmap + { + HitObjects = + { + new HitCircle + { + Position = OsuPlayfield.BASE_SIZE / 2, + StartTime = 0, + }, + new HitCircle + { + Position = OsuPlayfield.BASE_SIZE / 2, + StartTime = 5000, + }, + }, + Breaks = + { + new BreakPeriod(2000, 3000) + } + }; + + [BackgroundDependencyLoader] + private void load() + { + Add(testOnScreenDisplay = new TestOnScreenDisplay()); + Dependencies.CacheAs(testOnScreenDisplay); + } + + public override void SetUpSteps() + { + base.SetUpSteps(); + AddStep("reset OSD toast count", () => testOnScreenDisplay.ToastCount = 0); + } + + [Test] + public void TestFirstObjectTouched() + { + AddUntilStep("wait until 0 near", () => Player.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(0).Within(500)); + AddStep("slow down", () => Player.GameplayClockContainer.AdjustmentsFromMods.Frequency.Value = 0.2); + AddUntilStep("wait until 0", () => Player.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(0)); + AddStep("touch circle", () => + { + var touch = new Touch(TouchSource.Touch1, Player.DrawableRuleset.Playfield.ScreenSpaceDrawQuad.Centre); + InputManager.BeginTouch(touch); + InputManager.EndTouch(touch); + }); + AddAssert("touch device mod activated", () => Player.Score.ScoreInfo.Mods, () => Has.One.InstanceOf()); + AddAssert("no toasts displayed", () => testOnScreenDisplay.ToastCount, () => Is.Zero); + } + + [Test] + public void TestTouchDuringBreak() + { + AddUntilStep("wait until 2000 near", () => Player.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(2000).Within(500)); + AddStep("slow down", () => Player.GameplayClockContainer.AdjustmentsFromMods.Frequency.Value = 0.2); + AddUntilStep("wait until 2000", () => Player.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(2000)); + AddStep("touch playfield", () => + { + var touch = new Touch(TouchSource.Touch1, Player.DrawableRuleset.Playfield.ScreenSpaceDrawQuad.Centre); + InputManager.BeginTouch(touch); + InputManager.EndTouch(touch); + }); + AddAssert("touch device mod not activated", () => Player.Score.ScoreInfo.Mods, () => Has.None.InstanceOf()); + AddAssert("no toasts displayed", () => testOnScreenDisplay.ToastCount, () => Is.Zero); + } + + [Test] + public void TestSecondObjectTouched() + { + // ensure mouse is active (and that it's not suppressed due to touches in previous tests) + AddStep("click mouse", () => InputManager.Click(MouseButton.Left)); + + AddUntilStep("wait until 0 near", () => Player.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(0).Within(500)); + AddStep("slow down", () => Player.GameplayClockContainer.AdjustmentsFromMods.Frequency.Value = 0.2); + AddUntilStep("wait until 0", () => Player.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(0)); + AddStep("click circle", () => + { + InputManager.MoveMouseTo(Player.DrawableRuleset.Playfield.ScreenSpaceDrawQuad.Centre); + InputManager.Click(MouseButton.Left); + }); + AddAssert("touch device mod not activated", () => Player.Mods.Value, () => Has.No.InstanceOf()); + + AddStep("speed back up", () => Player.GameplayClockContainer.AdjustmentsFromMods.Frequency.Value = 1); + AddUntilStep("wait until 5000 near", () => Player.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(5000).Within(500)); + AddStep("slow down", () => Player.GameplayClockContainer.AdjustmentsFromMods.Frequency.Value = 0.2); + AddUntilStep("wait until 5000", () => Player.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(5000)); + AddStep("touch playfield", () => + { + var touch = new Touch(TouchSource.Touch1, Player.DrawableRuleset.Playfield.ScreenSpaceDrawQuad.Centre); + InputManager.BeginTouch(touch); + InputManager.EndTouch(touch); + }); + AddAssert("touch device mod activated", () => Player.Score.ScoreInfo.Mods, () => Has.One.InstanceOf()); + AddAssert("toast displayed", () => testOnScreenDisplay.ToastCount, () => Is.EqualTo(1)); + } + + private partial class TestOnScreenDisplay : OnScreenDisplay + { + public int ToastCount { get; set; } + + protected override void DisplayTemporarily(Drawable toDisplay) + { + base.DisplayTemporarily(toDisplay); + ToastCount++; + } + } + } +} diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 0ba8f7136d..10c1104376 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -858,7 +858,10 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("close settings sidebar", () => InputManager.Key(Key.Escape)); - PushAndConfirm(() => new TestPlaySongSelect()); + Screens.Select.SongSelect songSelect = null; + PushAndConfirm(() => songSelect = new TestPlaySongSelect()); + AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); + AddStep("switch to osu! ruleset", () => { InputManager.PressKey(Key.LControl); From f2df02b60f7e78a4e1fad2c591c24dfe14271c1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 25 Oct 2023 20:27:07 +0200 Subject: [PATCH 1271/4092] Automatically activate touch device mod in player --- .../Mods/TestSceneOsuModTouchDevice.cs | 18 +++++- .../Overlays/OSD/TouchDeviceDetectedToast.cs | 13 ++++ osu.Game/Screens/Play/Player.cs | 1 + .../Screens/Play/PlayerTouchInputHandler.cs | 61 +++++++++++++++++++ 4 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Overlays/OSD/TouchDeviceDetectedToast.cs create mode 100644 osu.Game/Screens/Play/PlayerTouchInputHandler.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs index 9c84bcc241..5134265741 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs @@ -1,12 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Timing; +using osu.Game.Configuration; +using osu.Game.Input; using osu.Game.Overlays; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Mods; @@ -19,6 +22,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { public partial class TestSceneOsuModTouchDevice : PlayerTestScene { + [Resolved] + private SessionStatics statics { get; set; } = null!; + private TestOnScreenDisplay testOnScreenDisplay = null!; protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); @@ -49,18 +55,26 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods private void load() { Add(testOnScreenDisplay = new TestOnScreenDisplay()); + Add(new TouchInputInterceptor()); Dependencies.CacheAs(testOnScreenDisplay); } public override void SetUpSteps() { - base.SetUpSteps(); AddStep("reset OSD toast count", () => testOnScreenDisplay.ToastCount = 0); + AddStep("reset static", () => statics.SetValue(Static.TouchInputActive, false)); + base.SetUpSteps(); } [Test] - public void TestFirstObjectTouched() + public void TestUserAlreadyHasTouchDeviceActive() { + // it is presumed that a previous screen (i.e. song select) will set this up + AddStep("set up touchscreen user", () => + { + Player.Score.ScoreInfo.Mods = Player.Score.ScoreInfo.Mods.Append(new OsuModTouchDevice()).ToArray(); + statics.SetValue(Static.TouchInputActive, true); + }); AddUntilStep("wait until 0 near", () => Player.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(0).Within(500)); AddStep("slow down", () => Player.GameplayClockContainer.AdjustmentsFromMods.Frequency.Value = 0.2); AddUntilStep("wait until 0", () => Player.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(0)); diff --git a/osu.Game/Overlays/OSD/TouchDeviceDetectedToast.cs b/osu.Game/Overlays/OSD/TouchDeviceDetectedToast.cs new file mode 100644 index 0000000000..0b5e3fffbe --- /dev/null +++ b/osu.Game/Overlays/OSD/TouchDeviceDetectedToast.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.Overlays.OSD +{ + public partial class TouchDeviceDetectedToast : Toast + { + public TouchDeviceDetectedToast() + : base("osu!", "Touch device detected", "Touch Device mod applied to score") + { + } + } +} diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 8c7fc551ba..a67e16912d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -285,6 +285,7 @@ namespace osu.Game.Screens.Play fadeOut(true); }, }, + new PlayerTouchInputHandler() }); if (cancellationToken.IsCancellationRequested) diff --git a/osu.Game/Screens/Play/PlayerTouchInputHandler.cs b/osu.Game/Screens/Play/PlayerTouchInputHandler.cs new file mode 100644 index 0000000000..0252ee9503 --- /dev/null +++ b/osu.Game/Screens/Play/PlayerTouchInputHandler.cs @@ -0,0 +1,61 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Configuration; +using osu.Game.Overlays; +using osu.Game.Overlays.OSD; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Screens.Play +{ + public partial class PlayerTouchInputHandler : Component + { + [Resolved] + private Player player { get; set; } = null!; + + [Resolved] + private GameplayState gameplayState { get; set; } = null!; + + [Resolved] + private OnScreenDisplay? onScreenDisplay { get; set; } + + private IBindable touchActive = new BindableBool(); + + [BackgroundDependencyLoader] + private void load(SessionStatics statics) + { + touchActive = statics.GetBindable(Static.TouchInputActive); + touchActive.BindValueChanged(_ => updateState()); + } + + private void updateState() + { + if (!touchActive.Value) + return; + + if (gameplayState.HasPassed || gameplayState.HasFailed || gameplayState.HasQuit) + return; + + if (gameplayState.Score.ScoreInfo.Mods.OfType().Any()) + return; + + if (player.IsBreakTime.Value) + return; + + var touchDeviceMod = gameplayState.Ruleset.GetTouchDeviceMod(); + if (touchDeviceMod == null) + return; + + onScreenDisplay?.Display(new TouchDeviceDetectedToast()); + + // TODO: this is kinda crude. `Player` (probably rightly so) assumes immutability of mods. + // this probably should be shown immediately on screen in the HUD, + // which means that immutability will probably need to be revisited. + player.Score.ScoreInfo.Mods = player.Score.ScoreInfo.Mods.Append(touchDeviceMod).ToArray(); + } + } +} From 21a4da463b11d557a815581cdb09121a9ea0a94d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 31 Oct 2023 13:47:50 +0100 Subject: [PATCH 1272/4092] Add failing test covering crash scenario --- .../Navigation/TestSceneScreenNavigation.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 10c1104376..a1fd8768b6 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -900,6 +900,38 @@ namespace osu.Game.Tests.Visual.Navigation InputManager.ReleaseKey(Key.LControl); }); AddUntilStep("touch device mod activated", () => Game.SelectedMods.Value, () => Has.One.InstanceOf()); + + AddStep("click beatmap wedge", () => + { + InputManager.MoveMouseTo(Game.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("touch device mod not activated", () => Game.SelectedMods.Value, () => Has.None.InstanceOf()); + + AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); + AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + AddStep("press enter", () => InputManager.Key(Key.Enter)); + + Player player = null; + + AddUntilStep("wait for player", () => + { + DismissAnyNotifications(); + return (player = Game.ScreenStack.CurrentScreen as Player) != null; + }); + + AddUntilStep("wait for track playing", () => Game.Beatmap.Value.Track.IsRunning); + + AddStep("touch", () => + { + var touch = new Touch(TouchSource.Touch2, Game.ScreenSpaceDrawQuad.Centre); + InputManager.BeginTouch(touch); + InputManager.EndTouch(touch); + }); + AddUntilStep("touch device mod added to score", () => player.Score.ScoreInfo.Mods, () => Has.One.InstanceOf()); + + AddStep("exit player", () => player.Exit()); + AddUntilStep("touch device mod still active", () => Game.SelectedMods.Value, () => Has.One.InstanceOf()); } private Func playToResults() From ef555ed0cf442b98cb139de435e0e0815468c05e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Oct 2023 15:48:00 +0100 Subject: [PATCH 1273/4092] Fix test failures --- osu.Game.Tests/Mods/ModUtilsTest.cs | 6 +++--- .../Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs | 1 + osu.Game/Overlays/Mods/SelectAllModsButton.cs | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Mods/ModUtilsTest.cs b/osu.Game.Tests/Mods/ModUtilsTest.cs index aa41fd830b..9107ddd1ae 100644 --- a/osu.Game.Tests/Mods/ModUtilsTest.cs +++ b/osu.Game.Tests/Mods/ModUtilsTest.cs @@ -147,11 +147,11 @@ namespace osu.Game.Tests.Mods new Mod[] { new OsuModDeflate(), new OsuModApproachDifferent() }, new[] { typeof(OsuModDeflate), typeof(OsuModApproachDifferent) } }, - // system mod. + // system mod not applicable in lazer. new object[] { - new Mod[] { new OsuModHidden(), new OsuModTouchDevice() }, - new[] { typeof(OsuModTouchDevice) } + new Mod[] { new OsuModHidden(), new ModScoreV2() }, + new[] { typeof(ModScoreV2) } }, // multi mod. new object[] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs index 66ba908879..f1674401cd 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs @@ -133,6 +133,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private bool assertAllAvailableModsSelected() { var allAvailableMods = availableMods.Value + .Where(pair => pair.Key != ModType.System) .SelectMany(pair => pair.Value) .Where(mod => mod.UserPlayable && mod.HasImplementation) .ToList(); diff --git a/osu.Game/Overlays/Mods/SelectAllModsButton.cs b/osu.Game/Overlays/Mods/SelectAllModsButton.cs index bb61cdc35d..b6b3051a0d 100644 --- a/osu.Game/Overlays/Mods/SelectAllModsButton.cs +++ b/osu.Game/Overlays/Mods/SelectAllModsButton.cs @@ -41,6 +41,7 @@ namespace osu.Game.Overlays.Mods private void updateEnabledState() { Enabled.Value = availableMods.Value + .Where(pair => pair.Key != ModType.System) .SelectMany(pair => pair.Value) .Any(modState => !modState.Active.Value && modState.Visible); } From c588f434e5f51bee3683c6114bc951fda40fb50c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 31 Oct 2023 13:54:18 +0100 Subject: [PATCH 1274/4092] Fix song select touch handler causing crashes when song select is suspended --- osu.Game/Screens/Select/SongSelectTouchInputHandler.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelectTouchInputHandler.cs b/osu.Game/Screens/Select/SongSelectTouchInputHandler.cs index bf4761e871..5c27e59c5b 100644 --- a/osu.Game/Screens/Select/SongSelectTouchInputHandler.cs +++ b/osu.Game/Screens/Select/SongSelectTouchInputHandler.cs @@ -32,14 +32,18 @@ namespace osu.Game.Screens.Select { base.LoadComplete(); - ruleset.BindValueChanged(_ => updateState()); - mods.BindValueChanged(_ => updateState()); - touchActive.BindValueChanged(_ => updateState()); + ruleset.BindValueChanged(_ => Scheduler.AddOnce(updateState)); + mods.BindValueChanged(_ => Scheduler.AddOnce(updateState)); + mods.BindDisabledChanged(_ => Scheduler.AddOnce(updateState)); + touchActive.BindValueChanged(_ => Scheduler.AddOnce(updateState)); updateState(); } private void updateState() { + if (mods.Disabled) + return; + var touchDeviceMod = ruleset.Value.CreateInstance().GetTouchDeviceMod(); if (touchDeviceMod == null) From 4532d0ecdf12abd32b3d64cbab3b9e975f34c63d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 2 Nov 2023 19:48:34 +0100 Subject: [PATCH 1275/4092] Add logging & debug facility for touch input interceptor --- osu.Game/Input/TouchInputInterceptor.cs | 27 +++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/osu.Game/Input/TouchInputInterceptor.cs b/osu.Game/Input/TouchInputInterceptor.cs index 377d3c6d9f..4e6177c70c 100644 --- a/osu.Game/Input/TouchInputInterceptor.cs +++ b/osu.Game/Input/TouchInputInterceptor.cs @@ -1,12 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Diagnostics; using osu.Framework.Allocation; +using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; using osu.Framework.Input.Events; using osu.Framework.Input.StateChanges; +using osu.Framework.Logging; using osu.Game.Configuration; using osuTK; +using osuTK.Input; namespace osu.Game.Input { @@ -23,19 +27,42 @@ namespace osu.Game.Input protected override bool Handle(UIEvent e) { + bool touchInputWasActive = statics.Get(Static.TouchInputActive); + switch (e) { case MouseEvent: if (e.CurrentState.Mouse.LastSource is not ISourcedFromTouch) + { + if (touchInputWasActive) + Logger.Log($@"Touch input deactivated due to received {e.GetType().ReadableName()}", LoggingTarget.Input); statics.SetValue(Static.TouchInputActive, false); + } + break; case TouchEvent: + if (!touchInputWasActive) + Logger.Log($@"Touch input activated due to received {e.GetType().ReadableName()}", LoggingTarget.Input); statics.SetValue(Static.TouchInputActive, true); break; + + case KeyDownEvent keyDown: + if (keyDown.Key == Key.T && keyDown.ControlPressed && keyDown.ShiftPressed) + debugToggleTouchInputActive(); + break; } return false; } + + [Conditional("TOUCH_INPUT_DEBUG")] + private void debugToggleTouchInputActive() + { + bool oldValue = statics.Get(Static.TouchInputActive); + bool newValue = !oldValue; + Logger.Log($@"Debug-toggling touch input to {(newValue ? @"active" : @"inactive")}", LoggingTarget.Input, LogLevel.Debug); + statics.SetValue(Static.TouchInputActive, newValue); + } } } From 3a6d65d395c1bf3c0e30429fec90be5e4fedb7c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 2 Nov 2023 19:51:21 +0100 Subject: [PATCH 1276/4092] Touch up `PlayerTouchInputHandler` --- osu.Game/Screens/Play/PlayerTouchInputHandler.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerTouchInputHandler.cs b/osu.Game/Screens/Play/PlayerTouchInputHandler.cs index 0252ee9503..728b3c846b 100644 --- a/osu.Game/Screens/Play/PlayerTouchInputHandler.cs +++ b/osu.Game/Screens/Play/PlayerTouchInputHandler.cs @@ -9,6 +9,7 @@ using osu.Game.Configuration; using osu.Game.Overlays; using osu.Game.Overlays.OSD; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Screens.Play { @@ -50,11 +51,15 @@ namespace osu.Game.Screens.Play if (touchDeviceMod == null) return; - onScreenDisplay?.Display(new TouchDeviceDetectedToast()); + // do not show the toast if the user hasn't hit anything yet. + // we're kind of assuming that the user just switches to touch for gameplay + // and we don't want to spam them with obvious toasts. + if (gameplayState.ScoreProcessor.HitEvents.Any(ev => ev.Result.IsHit())) + onScreenDisplay?.Display(new TouchDeviceDetectedToast()); - // TODO: this is kinda crude. `Player` (probably rightly so) assumes immutability of mods. - // this probably should be shown immediately on screen in the HUD, - // which means that immutability will probably need to be revisited. + // `Player` (probably rightly so) assumes immutability of mods, + // so this will not be shown immediately on the mod display in the top right. + // if this is to change, the mod immutability should be revisited. player.Score.ScoreInfo.Mods = player.Score.ScoreInfo.Mods.Append(touchDeviceMod).ToArray(); } } From d25b54c06d660455484762a011c162c6cb68dfd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 2 Nov 2023 22:14:38 +0100 Subject: [PATCH 1277/4092] Split test into two They would fail on CI when written as one, and I don't care why. --- .../Visual/Navigation/TestSceneScreenNavigation.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index a1fd8768b6..c6a668a714 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -837,7 +837,7 @@ namespace osu.Game.Tests.Visual.Navigation } [Test] - public void TestTouchScreenDetection() + public void TestTouchScreenDetectionAtSongSelect() { AddStep("touch logo", () => { @@ -859,8 +859,9 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("close settings sidebar", () => InputManager.Key(Key.Escape)); Screens.Select.SongSelect songSelect = null; - PushAndConfirm(() => songSelect = new TestPlaySongSelect()); - AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); + AddRepeatStep("go to solo", () => InputManager.Key(Key.P), 3); + AddUntilStep("wait for song select", () => (songSelect = Game.ScreenStack.CurrentScreen as Screens.Select.SongSelect) != null); + AddUntilStep("wait for beatmap sets loaded", () => songSelect.BeatmapSetsLoaded); AddStep("switch to osu! ruleset", () => { @@ -907,10 +908,15 @@ namespace osu.Game.Tests.Visual.Navigation InputManager.Click(MouseButton.Left); }); AddUntilStep("touch device mod not activated", () => Game.SelectedMods.Value, () => Has.None.InstanceOf()); + } + [Test] + public void TestTouchScreenDetectionInGame() + { + PushAndConfirm(() => new TestPlaySongSelect()); AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); - AddStep("press enter", () => InputManager.Key(Key.Enter)); + AddStep("select", () => InputManager.Key(Key.Enter)); Player player = null; From 8784dd42c0c4c1aba37cb0cc4f5cda8055811a69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 2 Nov 2023 22:20:17 +0100 Subject: [PATCH 1278/4092] Do not attempt to turn on Touch Device in song select with autoplay active --- osu.Game/Rulesets/Mods/ModAutoplay.cs | 2 +- osu.Game/Rulesets/Mods/ModTouchDevice.cs | 2 ++ .../Screens/Select/SongSelectTouchInputHandler.cs | 11 ++++++++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index 973fcffba8..302cdf69c0 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Mods public sealed override bool ValidForMultiplayer => false; public sealed override bool ValidForMultiplayerAsFreeMod => false; - public override Type[] IncompatibleMods => new[] { typeof(ModCinema), typeof(ModRelax), typeof(ModAdaptiveSpeed) }; + public override Type[] IncompatibleMods => new[] { typeof(ModCinema), typeof(ModRelax), typeof(ModAdaptiveSpeed), typeof(ModTouchDevice) }; public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0; diff --git a/osu.Game/Rulesets/Mods/ModTouchDevice.cs b/osu.Game/Rulesets/Mods/ModTouchDevice.cs index f532e7b19d..c29ff28f1a 100644 --- a/osu.Game/Rulesets/Mods/ModTouchDevice.cs +++ b/osu.Game/Rulesets/Mods/ModTouchDevice.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 System; using osu.Framework.Localisation; namespace osu.Game.Rulesets.Mods @@ -13,5 +14,6 @@ namespace osu.Game.Rulesets.Mods public sealed override double ScoreMultiplier => 1; public sealed override ModType Type => ModType.System; public sealed override bool AlwaysValidForSubmission => true; + public sealed override Type[] IncompatibleMods => new[] { typeof(ICreateReplayData) }; } } diff --git a/osu.Game/Screens/Select/SongSelectTouchInputHandler.cs b/osu.Game/Screens/Select/SongSelectTouchInputHandler.cs index 5c27e59c5b..973dc12e12 100644 --- a/osu.Game/Screens/Select/SongSelectTouchInputHandler.cs +++ b/osu.Game/Screens/Select/SongSelectTouchInputHandler.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Utils; namespace osu.Game.Screens.Select { @@ -52,7 +53,15 @@ namespace osu.Game.Screens.Select bool touchDeviceModEnabled = mods.Value.Any(mod => mod is ModTouchDevice); if (touchActive.Value && !touchDeviceModEnabled) - mods.Value = mods.Value.Append(touchDeviceMod).ToArray(); + { + var candidateMods = mods.Value.Append(touchDeviceMod).ToArray(); + + if (!ModUtils.CheckCompatibleSet(candidateMods, out _)) + return; + + mods.Value = candidateMods; + } + if (!touchActive.Value && touchDeviceModEnabled) mods.Value = mods.Value.Where(mod => mod is not ModTouchDevice).ToArray(); } From 0dd0a84312e224bb2cb5396cdc479e2b04688000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 2 Nov 2023 22:26:52 +0100 Subject: [PATCH 1279/4092] Move player touch handler to `SubmittingPlayer` Shouldn't be there in `ReplayPlayer`. --- osu.Game/Screens/Play/Player.cs | 1 - osu.Game/Screens/Play/SubmittingPlayer.cs | 6 ++++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a67e16912d..8c7fc551ba 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -285,7 +285,6 @@ namespace osu.Game.Screens.Play fadeOut(true); }, }, - new PlayerTouchInputHandler() }); if (cancellationToken.IsCancellationRequested) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index a75546f835..c34902aeb8 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -44,6 +44,12 @@ namespace osu.Game.Screens.Play { } + [BackgroundDependencyLoader] + private void load() + { + AddInternal(new PlayerTouchInputHandler()); + } + protected override void LoadAsyncComplete() { base.LoadAsyncComplete(); From 8e9006b5d5c263cf79fed1e74532e7de5dfca122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 2 Nov 2023 22:27:16 +0100 Subject: [PATCH 1280/4092] Declare Touch Device incompatible with Autopilot With Autopilot active, Touch Device no longer matters. --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 3 ++- osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs | 3 +++ osu.Game/Rulesets/Mods/ModTouchDevice.cs | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 3841c9c716..56bf0e08e9 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -33,7 +33,8 @@ namespace osu.Game.Rulesets.Osu.Mods typeof(ModNoFail), typeof(ModAutoplay), typeof(OsuModMagnetised), - typeof(OsuModRepel) + typeof(OsuModRepel), + typeof(ModTouchDevice) }; public bool PerformFail() => false; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs index abb67c519b..f1468d414e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs @@ -1,11 +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 System.Linq; using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Osu.Mods { public class OsuModTouchDevice : ModTouchDevice { + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot) }).ToArray(); } } diff --git a/osu.Game/Rulesets/Mods/ModTouchDevice.cs b/osu.Game/Rulesets/Mods/ModTouchDevice.cs index c29ff28f1a..a5dfe5448c 100644 --- a/osu.Game/Rulesets/Mods/ModTouchDevice.cs +++ b/osu.Game/Rulesets/Mods/ModTouchDevice.cs @@ -14,6 +14,6 @@ namespace osu.Game.Rulesets.Mods public sealed override double ScoreMultiplier => 1; public sealed override ModType Type => ModType.System; public sealed override bool AlwaysValidForSubmission => true; - public sealed override Type[] IncompatibleMods => new[] { typeof(ICreateReplayData) }; + public override Type[] IncompatibleMods => new[] { typeof(ICreateReplayData) }; } } From a613292802ce0fe5f53b31de761e6dcdf3135113 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 2 Nov 2023 23:44:40 +0100 Subject: [PATCH 1281/4092] Fix unknown mod test failure --- osu.Game/Screens/Play/SubmittingPlayer.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index c34902aeb8..e018b8dab3 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -47,6 +47,12 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load() { + if (DrawableRuleset == null) + { + // base load must have failed (e.g. due to an unknown mod); bail. + return; + } + AddInternal(new PlayerTouchInputHandler()); } From a78fab0e7d74d41b58ecefbe74854370ffee08c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 3 Nov 2023 00:17:29 +0100 Subject: [PATCH 1282/4092] Do not hardcode ruleset name in touch device detection toast --- osu.Game/Overlays/OSD/TouchDeviceDetectedToast.cs | 6 ++++-- osu.Game/Screens/Play/PlayerTouchInputHandler.cs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/OSD/TouchDeviceDetectedToast.cs b/osu.Game/Overlays/OSD/TouchDeviceDetectedToast.cs index 0b5e3fffbe..266e10ab1f 100644 --- a/osu.Game/Overlays/OSD/TouchDeviceDetectedToast.cs +++ b/osu.Game/Overlays/OSD/TouchDeviceDetectedToast.cs @@ -1,12 +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 osu.Game.Rulesets; + namespace osu.Game.Overlays.OSD { public partial class TouchDeviceDetectedToast : Toast { - public TouchDeviceDetectedToast() - : base("osu!", "Touch device detected", "Touch Device mod applied to score") + public TouchDeviceDetectedToast(RulesetInfo ruleset) + : base(ruleset.Name, "Touch device detected", "Touch Device mod applied to score") { } } diff --git a/osu.Game/Screens/Play/PlayerTouchInputHandler.cs b/osu.Game/Screens/Play/PlayerTouchInputHandler.cs index 728b3c846b..a7d41de105 100644 --- a/osu.Game/Screens/Play/PlayerTouchInputHandler.cs +++ b/osu.Game/Screens/Play/PlayerTouchInputHandler.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Play // we're kind of assuming that the user just switches to touch for gameplay // and we don't want to spam them with obvious toasts. if (gameplayState.ScoreProcessor.HitEvents.Any(ev => ev.Result.IsHit())) - onScreenDisplay?.Display(new TouchDeviceDetectedToast()); + onScreenDisplay?.Display(new TouchDeviceDetectedToast(gameplayState.Ruleset.RulesetInfo)); // `Player` (probably rightly so) assumes immutability of mods, // so this will not be shown immediately on the mod display in the top right. From 090601b485bdec297ef3d723f7254b54ca335d3b Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 2 Nov 2023 18:12:39 -0700 Subject: [PATCH 1283/4092] Apply peppy's upright key counter attempt diff Co-Authored-By: Dean Herbert --- .../Visual/Gameplay/TestSceneKeyCounter.cs | 20 ++++++++- osu.Game/Screens/Play/ArgonKeyCounter.cs | 45 ++++++++++++------- 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs index 5a66a5c7a6..7e66106264 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs @@ -43,7 +43,25 @@ namespace osu.Game.Tests.Visual.Gameplay { Origin = Anchor.Centre, Anchor = Anchor.Centre, - } + }, + new ArgonKeyCounterDisplay + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Rotation = -90, + }, + new ArgonKeyCounterDisplay + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Rotation = 90, + }, + new ArgonKeyCounterDisplay + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Scale = new Vector2(1, -1) + }, } } }; diff --git a/osu.Game/Screens/Play/ArgonKeyCounter.cs b/osu.Game/Screens/Play/ArgonKeyCounter.cs index 2d725898d8..ebf53abb30 100644 --- a/osu.Game/Screens/Play/ArgonKeyCounter.cs +++ b/osu.Game/Screens/Play/ArgonKeyCounter.cs @@ -3,8 +3,10 @@ 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.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Screens.Play.HUD; using osuTK; @@ -40,26 +42,39 @@ namespace osu.Game.Screens.Play { inputIndicator = new Circle { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.X, Height = line_height * scale_factor, Alpha = 0.5f }, - keyNameText = new OsuSpriteText + new Container { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Position = new Vector2(0, -13) * scale_factor, - Font = OsuFont.Torus.With(size: name_font_size * scale_factor, weight: FontWeight.Bold), - Colour = colours.Blue0, - Text = Trigger.Name - }, - countText = new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Font = OsuFont.Torus.With(size: count_font_size * scale_factor, weight: FontWeight.Bold), + RelativeSizeAxes = Axes.X, + Height = 40, + Children = new Drawable[] + { + new UprightAspectMaintainingContainer + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + keyNameText = new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Font = OsuFont.Torus.With(size: name_font_size * scale_factor, weight: FontWeight.Bold), + Colour = colours.Blue0, + Text = Trigger.Name + }, + countText = new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Position = new Vector2(0, 13) * scale_factor, + Font = OsuFont.Torus.With(size: count_font_size * scale_factor, weight: FontWeight.Bold), + }, + } + } + } }, }; From b3dfe19472a2a6a7f7d2b2d34570036df8f6b49c Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 2 Nov 2023 18:17:25 -0700 Subject: [PATCH 1284/4092] Fix `UprightAspectMaintainingContainer` not being centred --- osu.Game/Screens/Play/ArgonKeyCounter.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Play/ArgonKeyCounter.cs b/osu.Game/Screens/Play/ArgonKeyCounter.cs index ebf53abb30..6ff60c68b7 100644 --- a/osu.Game/Screens/Play/ArgonKeyCounter.cs +++ b/osu.Game/Screens/Play/ArgonKeyCounter.cs @@ -55,6 +55,8 @@ namespace osu.Game.Screens.Play new UprightAspectMaintainingContainer { RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Children = new Drawable[] { keyNameText = new OsuSpriteText From 16731ff85ff90b8c9e20b40b0358bc2c128782c2 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 2 Nov 2023 18:20:15 -0700 Subject: [PATCH 1285/4092] Fix text positioning --- osu.Game/Screens/Play/ArgonKeyCounter.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Play/ArgonKeyCounter.cs b/osu.Game/Screens/Play/ArgonKeyCounter.cs index 6ff60c68b7..bb5fe0daf2 100644 --- a/osu.Game/Screens/Play/ArgonKeyCounter.cs +++ b/osu.Game/Screens/Play/ArgonKeyCounter.cs @@ -9,7 +9,6 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Screens.Play.HUD; -using osuTK; namespace osu.Game.Screens.Play { @@ -27,6 +26,8 @@ namespace osu.Game.Screens.Play // Make things look bigger without using Scale private const float scale_factor = 1.5f; + private const float indicator_press_offset = 4; + [Resolved] private OsuColour colours { get; set; } = null!; @@ -48,8 +49,8 @@ namespace osu.Game.Screens.Play }, new Container { - RelativeSizeAxes = Axes.X, - Height = 40, + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = line_height * scale_factor + indicator_press_offset }, Children = new Drawable[] { new UprightAspectMaintainingContainer @@ -69,9 +70,8 @@ namespace osu.Game.Screens.Play }, countText = new OsuSpriteText { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Position = new Vector2(0, 13) * scale_factor, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, Font = OsuFont.Torus.With(size: count_font_size * scale_factor, weight: FontWeight.Bold), }, } @@ -104,7 +104,7 @@ namespace osu.Game.Screens.Play .FadeIn(10, Easing.OutQuint) .MoveToY(0) .Then() - .MoveToY(4, 60, Easing.OutQuint); + .MoveToY(indicator_press_offset, 60, Easing.OutQuint); } protected override void Deactivate(bool forwardPlayback = true) From e3b3ce6c84102e34778c4fd25bb39e366dc0977f Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 2 Nov 2023 18:44:56 -0700 Subject: [PATCH 1286/4092] Fix test overflowing on widescreen + add default (triangles) key counter testing --- .../Visual/Gameplay/TestSceneKeyCounter.cs | 49 +++++++++++++++---- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs index 7e66106264..03302bae6a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs @@ -31,30 +31,24 @@ namespace osu.Game.Tests.Visual.Gameplay Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, Direction = FillDirection.Vertical, - Spacing = new Vector2(72.7f), - Children = new KeyCounterDisplay[] + Spacing = new Vector2(20), + Children = new Drawable[] { new DefaultKeyCounterDisplay { Origin = Anchor.Centre, Anchor = Anchor.Centre, }, - new ArgonKeyCounterDisplay + new DefaultKeyCounterDisplay { Origin = Anchor.Centre, Anchor = Anchor.Centre, + Scale = new Vector2(1, -1) }, new ArgonKeyCounterDisplay { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Rotation = -90, - }, - new ArgonKeyCounterDisplay - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Rotation = 90, }, new ArgonKeyCounterDisplay { @@ -62,6 +56,41 @@ namespace osu.Game.Tests.Visual.Gameplay Anchor = Anchor.Centre, Scale = new Vector2(1, -1) }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Spacing = new Vector2(20), + Children = new Drawable[] + { + new DefaultKeyCounterDisplay + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Rotation = -90, + }, + new DefaultKeyCounterDisplay + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Rotation = 90, + }, + new ArgonKeyCounterDisplay + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Rotation = -90, + }, + new ArgonKeyCounterDisplay + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Rotation = 90, + }, + } + }, } } }; From 3f8baf913b46b884d9cbb5530721d58913403b5a Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 2 Nov 2023 19:46:09 -0700 Subject: [PATCH 1287/4092] Add 100 and 1000 key press step to test overflow --- osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs index 03302bae6a..2d2b6c3bed 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs @@ -124,8 +124,15 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Disable counting", () => controller.IsCounting.Value = false); addPressKeyStep(); AddAssert($"Check {testKey} count has not changed", () => testTrigger.ActivationCount.Value == 2); + AddStep("Enable counting", () => controller.IsCounting.Value = true); + addPressKeyStep(100); + addPressKeyStep(1000); - void addPressKeyStep() => AddStep($"Press {testKey} key", () => InputManager.Key(testKey)); + void addPressKeyStep(int repeat = 1) => AddStep($"Press {testKey} key {repeat} times", () => + { + for (int i = 0; i < repeat; i++) + InputManager.Key(testKey); + }); } } } From 43ab7f49426626b3d9536af481221c98e7dca86a Mon Sep 17 00:00:00 2001 From: ratinfx Date: Sat, 4 Nov 2023 02:01:18 +0100 Subject: [PATCH 1288/4092] Added OpenEditorTimestamp base implementation --- osu.Game/Localisation/EditorStrings.cs | 15 +++ osu.Game/OsuGame.cs | 48 +++++++++ osu.Game/Screens/Edit/Editor.cs | 34 ++++++ .../Screens/Edit/EditorTimestampParser.cs | 101 ++++++++++++++++++ 4 files changed, 198 insertions(+) create mode 100644 osu.Game/Screens/Edit/EditorTimestampParser.cs diff --git a/osu.Game/Localisation/EditorStrings.cs b/osu.Game/Localisation/EditorStrings.cs index 93e52746c5..227dbc5e0c 100644 --- a/osu.Game/Localisation/EditorStrings.cs +++ b/osu.Game/Localisation/EditorStrings.cs @@ -119,6 +119,21 @@ namespace osu.Game.Localisation /// public static LocalisableString LimitedDistanceSnap => new TranslatableString(getKey(@"limited_distance_snap_grid"), @"Limit distance snap placement to current time"); + /// + /// "Must be in edit mode to handle editor links" + /// + public static LocalisableString MustBeInEdit => new TranslatableString(getKey(@"must_be_in_edit"), @"Must be in edit mode to handle editor links"); + + /// + /// "Failed to process timestamp" + /// + public static LocalisableString FailedToProcessTimestamp => new TranslatableString(getKey(@"failed_to_process_timestamp"), @"Failed to process timestamp"); + + /// + /// "The timestamp was too long to process" + /// + public static LocalisableString TooLongTimestamp => new TranslatableString(getKey(@"too_long_timestamp"), @"The timestamp was too long to process"); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 2f11964f6a..439e112bd3 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -58,6 +58,7 @@ using osu.Game.Performance; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens; +using osu.Game.Screens.Edit; using osu.Game.Screens.Menu; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.Play; @@ -433,6 +434,9 @@ namespace osu.Game break; case LinkAction.OpenEditorTimestamp: + SeekToTimestamp(argString); + break; + case LinkAction.JoinMultiplayerMatch: case LinkAction.Spectate: waitForReady(() => Notifications, _ => Notifications.Post(new SimpleNotification @@ -550,6 +554,50 @@ namespace osu.Game /// The build version of the update stream public void ShowChangelogBuild(string updateStream, string version) => waitForReady(() => changelogOverlay, _ => changelogOverlay.ShowBuild(updateStream, version)); + /// + /// Seek to a given timestamp in the Editor and select relevant HitObjects if needed + /// + /// The timestamp and the selected objects + public void SeekToTimestamp(string timestamp) + { + if (ScreenStack.CurrentScreen is not Editor editor) + { + waitForReady(() => Notifications, _ => Notifications.Post(new SimpleErrorNotification + { + Text = EditorStrings.MustBeInEdit, + })); + return; + } + + string[] groups = EditorTimestampParser.GetRegexGroups(timestamp); + + if (groups.Length != 2 || string.IsNullOrEmpty(groups[0])) + { + waitForReady(() => Notifications, _ => Notifications.Post(new SimpleErrorNotification + { + Text = EditorStrings.FailedToProcessTimestamp + })); + return; + } + + string timeGroup = groups[0]; + string objectsGroup = groups[1]; + string timeMinutes = timeGroup.Split(':').FirstOrDefault() ?? string.Empty; + + // Currently, lazer chat highlights infinite-long editor links like `10000000000:00:000 (1)` + // Limit timestamp link length at 30000 min (50 hr) to avoid parsing issues + if (timeMinutes.Length > 5 || double.Parse(timeMinutes) > 30_000) + { + waitForReady(() => Notifications, _ => Notifications.Post(new SimpleErrorNotification + { + Text = EditorStrings.TooLongTimestamp + })); + return; + } + + editor.SeekToTimestamp(timeGroup, objectsGroup); + } + /// /// Present a skin select immediately. /// diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 91c3c98f01..b2fad55fed 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -39,6 +39,7 @@ using osu.Game.Overlays.Notifications; using osu.Game.Overlays.OSD; using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Compose.Components.Timeline; @@ -1137,6 +1138,39 @@ namespace osu.Game.Screens.Edit loader?.CancelPendingDifficultySwitch(); } + public void SeekToTimestamp(string timeGroup, string objectsGroup) + { + double position = EditorTimestampParser.GetTotalMilliseconds(timeGroup); + editorBeatmap.SelectedHitObjects.Clear(); + + if (string.IsNullOrEmpty(objectsGroup)) + { + if (clock.IsRunning) + clock.Stop(); + + clock.Seek(position); + return; + } + + if (Mode.Value != EditorScreenMode.Compose) + Mode.Value = EditorScreenMode.Compose; + + // Seek to the next closest HitObject's position + HitObject nextObject = editorBeatmap.HitObjects.FirstOrDefault(x => x.StartTime >= position); + if (nextObject != null && nextObject.StartTime > 0) + position = nextObject.StartTime; + + List selected = EditorTimestampParser.GetSelectedHitObjects(editorBeatmap.HitObjects.ToList(), objectsGroup, position); + + if (selected.Any()) + editorBeatmap.SelectedHitObjects.AddRange(selected); + + if (clock.IsRunning) + clock.Stop(); + + clock.Seek(position); + } + public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); public double GetBeatLengthAtTime(double referenceTime) => editorBeatmap.GetBeatLengthAtTime(referenceTime); diff --git a/osu.Game/Screens/Edit/EditorTimestampParser.cs b/osu.Game/Screens/Edit/EditorTimestampParser.cs new file mode 100644 index 0000000000..44d614ca70 --- /dev/null +++ b/osu.Game/Screens/Edit/EditorTimestampParser.cs @@ -0,0 +1,101 @@ +// 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.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text.RegularExpressions; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Screens.Edit +{ + public static class EditorTimestampParser + { + private static readonly Regex timestamp_regex = new Regex(@"^(\d+:\d+:\d+)(?: \((\d+(?:[|,]\d+)*)\))?$", RegexOptions.Compiled); + + public static string[] GetRegexGroups(string timestamp) + { + Match match = timestamp_regex.Match(timestamp); + return match.Success + ? match.Groups.Values.Where(x => x is not Match).Select(x => x.Value).ToArray() + : Array.Empty(); + } + + public static double GetTotalMilliseconds(string timeGroup) + { + int[] times = timeGroup.Split(':').Select(int.Parse).ToArray(); + + Debug.Assert(times.Length == 3); + + return (times[0] * 60 + times[1]) * 1_000 + times[2]; + } + + public static List GetSelectedHitObjects(IEnumerable editorHitObjects, string objectsGroup, double position) + { + List hitObjects = editorHitObjects.Where(x => x.StartTime >= position).ToList(); + List selectedObjects = new List(); + + string[] objectsToSelect = objectsGroup.Split(',').ToArray(); + + foreach (string objectInfo in objectsToSelect) + { + HitObject? current = hitObjects.FirstOrDefault(x => shouldHitObjectBeSelected(x, objectInfo)); + + if (current == null) + continue; + + selectedObjects.Add(current); + hitObjects = hitObjects.Where(x => x != current && x.StartTime >= current.StartTime).ToList(); + } + + // Stable behavior + // - always selects next closest object when `objectsGroup` only has one, non-Column item + if (objectsToSelect.Length != 1 || objectsGroup.Contains('|')) + return selectedObjects; + + HitObject? nextClosest = editorHitObjects.FirstOrDefault(x => x.StartTime >= position); + if (nextClosest == null) + return selectedObjects; + + if (nextClosest.StartTime <= (selectedObjects.FirstOrDefault()?.StartTime ?? position)) + { + selectedObjects.Clear(); + selectedObjects.Add(nextClosest); + } + + return selectedObjects; + } + + private static bool shouldHitObjectBeSelected(HitObject hitObject, string objectInfo) + { + switch (hitObject) + { + // (combo) + case IHasComboInformation comboInfo: + { + if (!double.TryParse(objectInfo, out double comboValue) || comboValue < 1) + return false; + + return comboInfo.IndexInCurrentCombo + 1 == comboValue; + } + + // (time|column) + case IHasColumn column: + { + double[] split = objectInfo.Split('|').Select(double.Parse).ToArray(); + if (split.Length != 2) + return false; + + double timeValue = split[0]; + double columnValue = split[1]; + return hitObject.StartTime == timeValue && column.Column == columnValue; + } + + default: + return false; + } + } + } +} From f854e78bb03f6117b1cfb8b0579e867d1aae093f Mon Sep 17 00:00:00 2001 From: ratinfx Date: Sat, 4 Nov 2023 03:29:05 +0100 Subject: [PATCH 1289/4092] Added ExclamationTriangle Icon to notifications --- osu.Game/OsuGame.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 439e112bd3..5acd958568 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -562,8 +562,9 @@ namespace osu.Game { if (ScreenStack.CurrentScreen is not Editor editor) { - waitForReady(() => Notifications, _ => Notifications.Post(new SimpleErrorNotification + waitForReady(() => Notifications, _ => Notifications.Post(new SimpleNotification { + Icon = FontAwesome.Solid.ExclamationTriangle, Text = EditorStrings.MustBeInEdit, })); return; @@ -573,8 +574,9 @@ namespace osu.Game if (groups.Length != 2 || string.IsNullOrEmpty(groups[0])) { - waitForReady(() => Notifications, _ => Notifications.Post(new SimpleErrorNotification + waitForReady(() => Notifications, _ => Notifications.Post(new SimpleNotification { + Icon = FontAwesome.Solid.ExclamationTriangle, Text = EditorStrings.FailedToProcessTimestamp })); return; @@ -588,8 +590,9 @@ namespace osu.Game // Limit timestamp link length at 30000 min (50 hr) to avoid parsing issues if (timeMinutes.Length > 5 || double.Parse(timeMinutes) > 30_000) { - waitForReady(() => Notifications, _ => Notifications.Post(new SimpleErrorNotification + waitForReady(() => Notifications, _ => Notifications.Post(new SimpleNotification { + Icon = FontAwesome.Solid.ExclamationTriangle, Text = EditorStrings.TooLongTimestamp })); return; From 60f62faec3712e1086af4d0a7462c60d0efbc257 Mon Sep 17 00:00:00 2001 From: ratinfx Date: Sat, 4 Nov 2023 03:30:38 +0100 Subject: [PATCH 1290/4092] Renamed Editor method --- osu.Game/OsuGame.cs | 2 +- osu.Game/Screens/Edit/Editor.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 5acd958568..a6bb6cc120 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -598,7 +598,7 @@ namespace osu.Game return; } - editor.SeekToTimestamp(timeGroup, objectsGroup); + editor.SeekAndSelectHitObjects(timeGroup, objectsGroup); } /// diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index b2fad55fed..80e01d4eb7 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1138,7 +1138,7 @@ namespace osu.Game.Screens.Edit loader?.CancelPendingDifficultySwitch(); } - public void SeekToTimestamp(string timeGroup, string objectsGroup) + public void SeekAndSelectHitObjects(string timeGroup, string objectsGroup) { double position = EditorTimestampParser.GetTotalMilliseconds(timeGroup); editorBeatmap.SelectedHitObjects.Clear(); From f867cff8c79c55f75708ea41c699c6d7a557485c Mon Sep 17 00:00:00 2001 From: ratinfx Date: Sat, 4 Nov 2023 03:42:08 +0100 Subject: [PATCH 1291/4092] Added OpenEditorTimestamp Tests --- .../Editing/TestSceneOpenEditorTimestamp.cs | 377 ++++++++++++++++++ 1 file changed, 377 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs new file mode 100644 index 0000000000..5ae0a20fd2 --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs @@ -0,0 +1,377 @@ +// 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.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Extensions; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Localisation; +using osu.Game.Online.Chat; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Menu; +using osu.Game.Screens.Select; +using osu.Game.Tests.Resources; + +namespace osu.Game.Tests.Visual.Editing +{ + public partial class TestSceneOpenEditorTimestamp : OsuGameTestScene + { + protected Editor Editor => (Editor)Game.ScreenStack.CurrentScreen; + protected EditorBeatmap EditorBeatmap => Editor.ChildrenOfType().Single(); + protected EditorClock EditorClock => Editor.ChildrenOfType().Single(); + + protected void AddStepClickLink(string timestamp, string step = "") + { + AddStep(step + timestamp, () => + Game.HandleLink(new LinkDetails(LinkAction.OpenEditorTimestamp, timestamp)) + ); + } + + protected void AddStepScreenModeTo(EditorScreenMode screenMode) + { + AddStep("change screen to " + screenMode, () => Editor.Mode.Value = screenMode); + } + + protected void AssertOnScreenAt(EditorScreenMode screen, double time, string text = "stays in") + { + AddAssert($"{text} {screen} at {time}", () => + Editor.Mode.Value == screen + && EditorClock.CurrentTime == time + ); + } + + protected bool HasCombosInOrder(IEnumerable selected, params int[] comboNumbers) + { + List hitObjects = selected.ToList(); + if (hitObjects.Count != comboNumbers.Length) + return false; + + return !hitObjects.Select(x => (IHasComboInformation)x) + .Where((combo, i) => combo.IndexInCurrentCombo + 1 != comboNumbers[i]) + .Any(); + } + + protected bool IsNoteAt(HitObject hitObject, double time, int column) + { + return hitObject is IHasColumn columnInfo + && hitObject.StartTime == time + && columnInfo.Column == column; + } + + public void SetUpEditor(RulesetInfo ruleset) + { + BeatmapSetInfo beatmapSet = null!; + + AddStep("Import test beatmap", () => + Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely() + ); + AddStep("Retrieve beatmap", () => + beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach() + ); + AddStep("Present beatmap", () => Game.PresentBeatmap(beatmapSet)); + AddUntilStep("Wait for song select", () => + Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) + && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect + && songSelect.IsLoaded + ); + AddStep("Switch ruleset", () => Game.Ruleset.Value = ruleset); + AddStep("Open editor for ruleset", () => + ((PlaySongSelect)Game.ScreenStack.CurrentScreen) + .Edit(beatmapSet.Beatmaps.Last(beatmap => beatmap.Ruleset.Name == ruleset.Name)) + ); + AddUntilStep("Wait for editor open", () => Editor.ReadyForUse); + } + + [Test] + public void TestErrorNotifications() + { + RulesetInfo rulesetInfo = new OsuRuleset().RulesetInfo; + + AddStepClickLink("00:00:000"); + AddAssert("recieved 'must be in edit'", () => + Game.Notifications.UnreadCount.Value == 1 + && Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEdit) == 1 + ); + + AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo.Invoke()); + AddAssert("entered song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect); + + AddStepClickLink("00:00:000 (1)"); + AddAssert("recieved 'must be in edit'", () => + Game.Notifications.UnreadCount.Value == 2 + && Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEdit) == 2 + ); + + SetUpEditor(rulesetInfo); + AddAssert("is editor Osu", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); + + AddStepClickLink("00:000", "invalid link "); + AddAssert("recieved 'failed to process'", () => + Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp) == 1 + ); + + AddStepClickLink("00:00:00:000", "invalid link "); + AddAssert("recieved 'failed to process'", () => + Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp) == 2 + ); + + AddStepClickLink("00:00:000 ()", "invalid link "); + AddAssert("recieved 'failed to process'", () => + Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp) == 3 + ); + + AddStepClickLink("00:00:000 (-1)", "invalid link "); + AddAssert("recieved 'failed to process'", () => + Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp) == 4 + ); + + AddStepClickLink("50000:00:000", "too long link "); + AddAssert("recieved 'too long'", () => + EditorClock.CurrentTime == 0 + && Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.TooLongTimestamp) == 1 + ); + } + + [Test] + public void TestHandleCurrentScreenChanges() + { + const long long_link_value = 1_000 * 60 * 1_000; + RulesetInfo rulesetInfo = new OsuRuleset().RulesetInfo; + + SetUpEditor(rulesetInfo); + AddAssert("is editor Osu", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); + + AddStepClickLink("1000:00:000", "long link "); + AddAssert("moved to end of track", () => + EditorClock.CurrentTime == long_link_value + || (EditorClock.TrackLength < long_link_value && EditorClock.CurrentTime == EditorClock.TrackLength) + ); + + AddStepScreenModeTo(EditorScreenMode.SongSetup); + AddStepClickLink("00:00:000"); + AssertOnScreenAt(EditorScreenMode.SongSetup, 0); + + AddStepClickLink("00:05:000 (0|0)"); + AddAssert("seek and change screen", () => + Editor.Mode.Value == EditorScreenMode.Compose + && EditorClock.CurrentTime == EditorBeatmap.HitObjects.First(x => x.StartTime >= 5_000).StartTime + ); + + AddStepScreenModeTo(EditorScreenMode.Design); + AddStepClickLink("00:10:000"); + AssertOnScreenAt(EditorScreenMode.Design, 10_000); + + AddStepClickLink("00:15:000 (1)"); + AddAssert("seek and change screen", () => + Editor.Mode.Value == EditorScreenMode.Compose + && EditorClock.CurrentTime == EditorBeatmap.HitObjects.First(x => x.StartTime >= 15_000).StartTime + ); + + AddStepScreenModeTo(EditorScreenMode.Timing); + AddStepClickLink("00:20:000"); + AssertOnScreenAt(EditorScreenMode.Timing, 20_000); + + AddStepClickLink("00:25:000 (0,1)"); + AddAssert("seek and change screen", () => + Editor.Mode.Value == EditorScreenMode.Compose + && EditorClock.CurrentTime == EditorBeatmap.HitObjects.First(x => x.StartTime >= 25_000).StartTime + ); + + AddStepScreenModeTo(EditorScreenMode.Verify); + AddStepClickLink("00:30:000"); + AssertOnScreenAt(EditorScreenMode.Verify, 30_000); + + AddStepClickLink("00:35:000 (0,1)"); + AddAssert("seek and change screen", () => + Editor.Mode.Value == EditorScreenMode.Compose + && EditorClock.CurrentTime == EditorBeatmap.HitObjects.First(x => x.StartTime >= 35_000).StartTime + ); + + AddStepClickLink("00:00:000"); + AssertOnScreenAt(EditorScreenMode.Compose, 0); + } + + [Test] + public void TestSelectionForOsu() + { + HitObject firstObject = null!; + RulesetInfo rulesetInfo = new OsuRuleset().RulesetInfo; + + SetUpEditor(rulesetInfo); + AddAssert("is editor Osu", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); + + AddStepClickLink("00:00:956 (1,2,3)"); + AddAssert("snap and select 1-2-3", () => + { + firstObject = EditorBeatmap.HitObjects.First(); + return EditorClock.CurrentTime == firstObject.StartTime + && EditorBeatmap.SelectedHitObjects.Count == 3 + && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 1, 2, 3); + }); + + AddStepClickLink("00:01:450 (2,3,4,1,2)"); + AddAssert("snap and select 2-3-4-1-2", () => + EditorClock.CurrentTime == 1_450 + && EditorBeatmap.SelectedHitObjects.Count == 5 + && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 2, 3, 4, 1, 2) + ); + + AddStepClickLink("00:00:956 (1,1,1)"); + AddAssert("snap and select 1-1-1", () => + EditorClock.CurrentTime == firstObject.StartTime + && EditorBeatmap.SelectedHitObjects.Count == 3 + && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 1, 1, 1) + ); + } + + [Test] + public void TestUnusualSelectionForOsu() + { + HitObject firstObject = null!; + RulesetInfo rulesetInfo = new OsuRuleset().RulesetInfo; + + SetUpEditor(rulesetInfo); + AddAssert("is editor Osu", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); + + AddStepClickLink("00:00:000 (1,2,3)", "invalid offset "); + AddAssert("snap to next, select 1-2-3", () => + { + firstObject = EditorBeatmap.HitObjects.First(); + return EditorClock.CurrentTime == firstObject.StartTime + && EditorBeatmap.SelectedHitObjects.Count == 3 + && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 1, 2, 3); + }); + + AddStepClickLink("00:00:956 (2,3,4)", "invalid offset "); + AddAssert("snap to next, select 2-3-4", () => + EditorClock.CurrentTime == EditorBeatmap.HitObjects.First(x => x.StartTime >= 956).StartTime + && EditorBeatmap.SelectedHitObjects.Count == 3 + && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 2, 3, 4) + ); + + AddStepClickLink("00:00:000 (0)", "invalid combo "); + AddAssert("snap to 1, select 1", () => + EditorClock.CurrentTime == firstObject.StartTime + && EditorBeatmap.SelectedHitObjects.Count == 1 + && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 1) + ); + + AddStepClickLink("00:00:000 (1)", "invalid offset "); + AddAssert("snap and select 1", () => + EditorClock.CurrentTime == firstObject.StartTime + && EditorBeatmap.SelectedHitObjects.Count == 1 + && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 1) + ); + + AddStepClickLink("00:00:000 (2)", "invalid offset "); + AddAssert("snap and select 1", () => + EditorClock.CurrentTime == firstObject.StartTime + && EditorBeatmap.SelectedHitObjects.Count == 1 + && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 1) + ); + + AddStepClickLink("00:00:000 (2,3)", "invalid offset "); + AddAssert("snap to 1, select 2-3", () => + EditorClock.CurrentTime == firstObject.StartTime + && EditorBeatmap.SelectedHitObjects.Count == 2 + && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 2, 3) + ); + + AddStepClickLink("00:00:956 (956|1,956|2)", "mania link "); + AddAssert("snap to next, select none", () => + EditorClock.CurrentTime == firstObject?.StartTime + && !EditorBeatmap.SelectedHitObjects.Any() + ); + + AddStepClickLink("00:00:000 (0|1)", "mania link "); + AddAssert("snap to 1, select none", () => + EditorClock.CurrentTime == firstObject.StartTime + && !EditorBeatmap.SelectedHitObjects.Any() + ); + } + + [Test] + public void TestSelectionForMania() + { + RulesetInfo rulesetInfo = new ManiaRuleset().RulesetInfo; + + SetUpEditor(rulesetInfo); + AddAssert("is editor Mania", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); + + AddStepClickLink("00:11:010 (11010|1,11175|5,11258|3,11340|5,11505|1)"); + AddAssert("selected group", () => + EditorClock.CurrentTime == 11010 + && EditorBeatmap.SelectedHitObjects.Count == 5 + && EditorBeatmap.SelectedHitObjects.All(x => + IsNoteAt(x, 11010, 1) || IsNoteAt(x, 11175, 5) || + IsNoteAt(x, 11258, 3) || IsNoteAt(x, 11340, 5) || + IsNoteAt(x, 11505, 1)) + ); + + AddStepClickLink("00:00:956 (956|1,956|6,1285|3,1780|4)"); + AddAssert("selected ungrouped", () => + EditorClock.CurrentTime == 956 + && EditorBeatmap.SelectedHitObjects.Count == 4 + && EditorBeatmap.SelectedHitObjects.All(x => + IsNoteAt(x, 956, 1) || IsNoteAt(x, 956, 6) || + IsNoteAt(x, 1285, 3) || IsNoteAt(x, 1780, 4)) + ); + + AddStepClickLink("02:36:560 (156560|1,156560|4,156560|6)"); + AddAssert("selected in row", () => + EditorClock.CurrentTime == 156560 + && EditorBeatmap.SelectedHitObjects.Count == 3 + && EditorBeatmap.SelectedHitObjects.All(x => + IsNoteAt(x, 156560, 1) || IsNoteAt(x, 156560, 4) || + IsNoteAt(x, 156560, 6)) + ); + + AddStepClickLink("00:35:736 (35736|3,36395|3,36725|3,37384|3)"); + AddAssert("selected in column", () => + EditorClock.CurrentTime == 35736 + && EditorBeatmap.SelectedHitObjects.Count == 4 + && EditorBeatmap.SelectedHitObjects.All(x => + IsNoteAt(x, 35736, 3) || IsNoteAt(x, 36395, 3) || + IsNoteAt(x, 36725, 3) || IsNoteAt(x, 37384, 3)) + ); + } + + [Test] + public void TestUnusualSelectionForMania() + { + RulesetInfo rulesetInfo = new ManiaRuleset().RulesetInfo; + + SetUpEditor(rulesetInfo); + AddAssert("is editor Mania", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); + + AddStepClickLink("00:00:000 (0|1)", "invalid link "); + AddAssert("snap to 1, select none", () => + EditorClock.CurrentTime == 956 + && !EditorBeatmap.SelectedHitObjects.Any() + ); + + AddStepClickLink("00:00:000 (0)", "std link "); + AddAssert("snap and select 1", () => + EditorClock.CurrentTime == 956 + && EditorBeatmap.SelectedHitObjects.All(x => IsNoteAt(x, 956, 1)) + ); + + AddStepClickLink("00:00:000 (1,2)", "std link "); + AddAssert("snap to 1, select none", () => + EditorClock.CurrentTime == 956 + && !EditorBeatmap.SelectedHitObjects.Any() + ); + } + } +} From 19cdf99df8f101b5c676528826a39cb905c1efe5 Mon Sep 17 00:00:00 2001 From: ratinfx Date: Sat, 4 Nov 2023 12:04:58 +0100 Subject: [PATCH 1292/4092] Fixed up tests --- .../Editing/TestSceneOpenEditorTimestamp.cs | 216 +++++++----------- 1 file changed, 83 insertions(+), 133 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs index 5ae0a20fd2..fea9334ff8 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.Editing protected void AddStepClickLink(string timestamp, string step = "") { - AddStep(step + timestamp, () => + AddStep($"{step} {timestamp}", () => Game.HandleLink(new LinkDetails(LinkAction.OpenEditorTimestamp, timestamp)) ); } @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("change screen to " + screenMode, () => Editor.Mode.Value = screenMode); } - protected void AssertOnScreenAt(EditorScreenMode screen, double time, string text = "stays in") + protected void AssertOnScreenAt(EditorScreenMode screen, double time, string text = "stayed in") { AddAssert($"{text} {screen} at {time}", () => Editor.Mode.Value == screen @@ -51,7 +51,34 @@ namespace osu.Game.Tests.Visual.Editing ); } - protected bool HasCombosInOrder(IEnumerable selected, params int[] comboNumbers) + protected void AssertMovedScreenTo(EditorScreenMode screen, string text = "moved to") + { + AddAssert($"{text} {screen}", () => Editor.Mode.Value == screen); + } + + private bool checkSnapAndSelectCombo(double startTime, params int[] comboNumbers) + { + bool checkCombos = comboNumbers.Any() + ? hasCombosInOrder(EditorBeatmap.SelectedHitObjects, comboNumbers) + : !EditorBeatmap.SelectedHitObjects.Any(); + + return EditorClock.CurrentTime == startTime + && EditorBeatmap.SelectedHitObjects.Count == comboNumbers.Length + && checkCombos; + } + + private bool checkSnapAndSelectColumn(double startTime, List<(int, int)> columnPairs = null) + { + bool checkColumns = columnPairs != null + ? EditorBeatmap.SelectedHitObjects.All(x => columnPairs.Any(col => isNoteAt(x, col.Item1, col.Item2))) + : !EditorBeatmap.SelectedHitObjects.Any(); + + return EditorClock.CurrentTime == startTime + && EditorBeatmap.SelectedHitObjects.Count == (columnPairs?.Count ?? 0) + && checkColumns; + } + + private bool hasCombosInOrder(IEnumerable selected, params int[] comboNumbers) { List hitObjects = selected.ToList(); if (hitObjects.Count != comboNumbers.Length) @@ -62,7 +89,7 @@ namespace osu.Game.Tests.Visual.Editing .Any(); } - protected bool IsNoteAt(HitObject hitObject, double time, int column) + private bool isNoteAt(HitObject hitObject, double time, int column) { return hitObject is IHasColumn columnInfo && hitObject.StartTime == time @@ -100,8 +127,7 @@ namespace osu.Game.Tests.Visual.Editing AddStepClickLink("00:00:000"); AddAssert("recieved 'must be in edit'", () => - Game.Notifications.UnreadCount.Value == 1 - && Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEdit) == 1 + Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEdit) == 1 ); AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo.Invoke()); @@ -109,37 +135,35 @@ namespace osu.Game.Tests.Visual.Editing AddStepClickLink("00:00:000 (1)"); AddAssert("recieved 'must be in edit'", () => - Game.Notifications.UnreadCount.Value == 2 - && Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEdit) == 2 + Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEdit) == 2 ); SetUpEditor(rulesetInfo); AddAssert("is editor Osu", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); - AddStepClickLink("00:000", "invalid link "); + AddStepClickLink("00:000", "invalid link"); AddAssert("recieved 'failed to process'", () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp) == 1 ); - AddStepClickLink("00:00:00:000", "invalid link "); + AddStepClickLink("00:00:00:000", "invalid link"); AddAssert("recieved 'failed to process'", () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp) == 2 ); - AddStepClickLink("00:00:000 ()", "invalid link "); + AddStepClickLink("00:00:000 ()", "invalid link"); AddAssert("recieved 'failed to process'", () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp) == 3 ); - AddStepClickLink("00:00:000 (-1)", "invalid link "); + AddStepClickLink("00:00:000 (-1)", "invalid link"); AddAssert("recieved 'failed to process'", () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp) == 4 ); - AddStepClickLink("50000:00:000", "too long link "); + AddStepClickLink("50000:00:000", "too long link"); AddAssert("recieved 'too long'", () => - EditorClock.CurrentTime == 0 - && Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.TooLongTimestamp) == 1 + Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.TooLongTimestamp) == 1 ); } @@ -152,7 +176,7 @@ namespace osu.Game.Tests.Visual.Editing SetUpEditor(rulesetInfo); AddAssert("is editor Osu", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); - AddStepClickLink("1000:00:000", "long link "); + AddStepClickLink("1000:00:000", "long link"); AddAssert("moved to end of track", () => EditorClock.CurrentTime == long_link_value || (EditorClock.TrackLength < long_link_value && EditorClock.CurrentTime == EditorClock.TrackLength) @@ -163,40 +187,28 @@ namespace osu.Game.Tests.Visual.Editing AssertOnScreenAt(EditorScreenMode.SongSetup, 0); AddStepClickLink("00:05:000 (0|0)"); - AddAssert("seek and change screen", () => - Editor.Mode.Value == EditorScreenMode.Compose - && EditorClock.CurrentTime == EditorBeatmap.HitObjects.First(x => x.StartTime >= 5_000).StartTime - ); + AssertMovedScreenTo(EditorScreenMode.Compose); AddStepScreenModeTo(EditorScreenMode.Design); AddStepClickLink("00:10:000"); AssertOnScreenAt(EditorScreenMode.Design, 10_000); AddStepClickLink("00:15:000 (1)"); - AddAssert("seek and change screen", () => - Editor.Mode.Value == EditorScreenMode.Compose - && EditorClock.CurrentTime == EditorBeatmap.HitObjects.First(x => x.StartTime >= 15_000).StartTime - ); + AssertMovedScreenTo(EditorScreenMode.Compose); AddStepScreenModeTo(EditorScreenMode.Timing); AddStepClickLink("00:20:000"); AssertOnScreenAt(EditorScreenMode.Timing, 20_000); AddStepClickLink("00:25:000 (0,1)"); - AddAssert("seek and change screen", () => - Editor.Mode.Value == EditorScreenMode.Compose - && EditorClock.CurrentTime == EditorBeatmap.HitObjects.First(x => x.StartTime >= 25_000).StartTime - ); + AssertMovedScreenTo(EditorScreenMode.Compose); AddStepScreenModeTo(EditorScreenMode.Verify); AddStepClickLink("00:30:000"); AssertOnScreenAt(EditorScreenMode.Verify, 30_000); AddStepClickLink("00:35:000 (0,1)"); - AddAssert("seek and change screen", () => - Editor.Mode.Value == EditorScreenMode.Compose - && EditorClock.CurrentTime == EditorBeatmap.HitObjects.First(x => x.StartTime >= 35_000).StartTime - ); + AssertMovedScreenTo(EditorScreenMode.Compose); AddStepClickLink("00:00:000"); AssertOnScreenAt(EditorScreenMode.Compose, 0); @@ -215,24 +227,14 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("snap and select 1-2-3", () => { firstObject = EditorBeatmap.HitObjects.First(); - return EditorClock.CurrentTime == firstObject.StartTime - && EditorBeatmap.SelectedHitObjects.Count == 3 - && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 1, 2, 3); + return checkSnapAndSelectCombo(firstObject.StartTime, 1, 2, 3); }); AddStepClickLink("00:01:450 (2,3,4,1,2)"); - AddAssert("snap and select 2-3-4-1-2", () => - EditorClock.CurrentTime == 1_450 - && EditorBeatmap.SelectedHitObjects.Count == 5 - && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 2, 3, 4, 1, 2) - ); + AddAssert("snap and select 2-3-4-1-2", () => checkSnapAndSelectCombo(1_450, 2, 3, 4, 1, 2)); AddStepClickLink("00:00:956 (1,1,1)"); - AddAssert("snap and select 1-1-1", () => - EditorClock.CurrentTime == firstObject.StartTime - && EditorBeatmap.SelectedHitObjects.Count == 3 - && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 1, 1, 1) - ); + AddAssert("snap and select 1-1-1", () => checkSnapAndSelectCombo(firstObject.StartTime, 1, 1, 1)); } [Test] @@ -244,61 +246,33 @@ namespace osu.Game.Tests.Visual.Editing SetUpEditor(rulesetInfo); AddAssert("is editor Osu", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); - AddStepClickLink("00:00:000 (1,2,3)", "invalid offset "); + AddStepClickLink("00:00:000 (1,2,3)", "invalid offset"); AddAssert("snap to next, select 1-2-3", () => { firstObject = EditorBeatmap.HitObjects.First(); - return EditorClock.CurrentTime == firstObject.StartTime - && EditorBeatmap.SelectedHitObjects.Count == 3 - && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 1, 2, 3); + return checkSnapAndSelectCombo(firstObject.StartTime, 1, 2, 3); }); - AddStepClickLink("00:00:956 (2,3,4)", "invalid offset "); - AddAssert("snap to next, select 2-3-4", () => - EditorClock.CurrentTime == EditorBeatmap.HitObjects.First(x => x.StartTime >= 956).StartTime - && EditorBeatmap.SelectedHitObjects.Count == 3 - && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 2, 3, 4) - ); + AddStepClickLink("00:00:956 (2,3,4)", "invalid offset"); + AddAssert("snap to next, select 2-3-4", () => checkSnapAndSelectCombo(firstObject.StartTime, 2, 3, 4)); - AddStepClickLink("00:00:000 (0)", "invalid combo "); - AddAssert("snap to 1, select 1", () => - EditorClock.CurrentTime == firstObject.StartTime - && EditorBeatmap.SelectedHitObjects.Count == 1 - && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 1) - ); + AddStepClickLink("00:00:000 (0)", "invalid offset"); + AddAssert("snap and select 1", () => checkSnapAndSelectCombo(firstObject.StartTime, 1)); - AddStepClickLink("00:00:000 (1)", "invalid offset "); - AddAssert("snap and select 1", () => - EditorClock.CurrentTime == firstObject.StartTime - && EditorBeatmap.SelectedHitObjects.Count == 1 - && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 1) - ); + AddStepClickLink("00:00:000 (1)", "invalid offset"); + AddAssert("snap and select 1", () => checkSnapAndSelectCombo(firstObject.StartTime, 1)); - AddStepClickLink("00:00:000 (2)", "invalid offset "); - AddAssert("snap and select 1", () => - EditorClock.CurrentTime == firstObject.StartTime - && EditorBeatmap.SelectedHitObjects.Count == 1 - && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 1) - ); + AddStepClickLink("00:00:000 (2)", "invalid offset"); + AddAssert("snap and select 1", () => checkSnapAndSelectCombo(firstObject.StartTime, 1)); - AddStepClickLink("00:00:000 (2,3)", "invalid offset "); - AddAssert("snap to 1, select 2-3", () => - EditorClock.CurrentTime == firstObject.StartTime - && EditorBeatmap.SelectedHitObjects.Count == 2 - && HasCombosInOrder(EditorBeatmap.SelectedHitObjects, 2, 3) - ); + AddStepClickLink("00:00:000 (2,3)", "invalid offset"); + AddAssert("snap to 1, select 2-3", () => checkSnapAndSelectCombo(firstObject.StartTime, 2, 3)); - AddStepClickLink("00:00:956 (956|1,956|2)", "mania link "); - AddAssert("snap to next, select none", () => - EditorClock.CurrentTime == firstObject?.StartTime - && !EditorBeatmap.SelectedHitObjects.Any() - ); + AddStepClickLink("00:00:956 (956|1,956|2)", "mania link"); + AddAssert("snap to next, select none", () => checkSnapAndSelectCombo(firstObject.StartTime)); - AddStepClickLink("00:00:000 (0|1)", "mania link "); - AddAssert("snap to 1, select none", () => - EditorClock.CurrentTime == firstObject.StartTime - && !EditorBeatmap.SelectedHitObjects.Any() - ); + AddStepClickLink("00:00:000 (0|1)", "mania link"); + AddAssert("snap to 1, select none", () => checkSnapAndSelectCombo(firstObject.StartTime)); } [Test] @@ -310,41 +284,24 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("is editor Mania", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); AddStepClickLink("00:11:010 (11010|1,11175|5,11258|3,11340|5,11505|1)"); - AddAssert("selected group", () => - EditorClock.CurrentTime == 11010 - && EditorBeatmap.SelectedHitObjects.Count == 5 - && EditorBeatmap.SelectedHitObjects.All(x => - IsNoteAt(x, 11010, 1) || IsNoteAt(x, 11175, 5) || - IsNoteAt(x, 11258, 3) || IsNoteAt(x, 11340, 5) || - IsNoteAt(x, 11505, 1)) - ); + AddAssert("selected group", () => checkSnapAndSelectColumn(11010, new List<(int, int)> + { (11010, 1), (11175, 5), (11258, 3), (11340, 5), (11505, 1) } + )); AddStepClickLink("00:00:956 (956|1,956|6,1285|3,1780|4)"); - AddAssert("selected ungrouped", () => - EditorClock.CurrentTime == 956 - && EditorBeatmap.SelectedHitObjects.Count == 4 - && EditorBeatmap.SelectedHitObjects.All(x => - IsNoteAt(x, 956, 1) || IsNoteAt(x, 956, 6) || - IsNoteAt(x, 1285, 3) || IsNoteAt(x, 1780, 4)) - ); + AddAssert("selected ungrouped", () => checkSnapAndSelectColumn(956, new List<(int, int)> + { (956, 1), (956, 6), (1285, 3), (1780, 4) } + )); AddStepClickLink("02:36:560 (156560|1,156560|4,156560|6)"); - AddAssert("selected in row", () => - EditorClock.CurrentTime == 156560 - && EditorBeatmap.SelectedHitObjects.Count == 3 - && EditorBeatmap.SelectedHitObjects.All(x => - IsNoteAt(x, 156560, 1) || IsNoteAt(x, 156560, 4) || - IsNoteAt(x, 156560, 6)) - ); + AddAssert("selected in row", () => checkSnapAndSelectColumn(156560, new List<(int, int)> + { (156560, 1), (156560, 4), (156560, 6) } + )); AddStepClickLink("00:35:736 (35736|3,36395|3,36725|3,37384|3)"); - AddAssert("selected in column", () => - EditorClock.CurrentTime == 35736 - && EditorBeatmap.SelectedHitObjects.Count == 4 - && EditorBeatmap.SelectedHitObjects.All(x => - IsNoteAt(x, 35736, 3) || IsNoteAt(x, 36395, 3) || - IsNoteAt(x, 36725, 3) || IsNoteAt(x, 37384, 3)) - ); + AddAssert("selected in column", () => checkSnapAndSelectColumn(35736, new List<(int, int)> + { (35736, 3), (36395, 3), (36725, 3), (37384, 3) } + )); } [Test] @@ -355,23 +312,16 @@ namespace osu.Game.Tests.Visual.Editing SetUpEditor(rulesetInfo); AddAssert("is editor Mania", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); - AddStepClickLink("00:00:000 (0|1)", "invalid link "); - AddAssert("snap to 1, select none", () => - EditorClock.CurrentTime == 956 - && !EditorBeatmap.SelectedHitObjects.Any() + AddStepClickLink("00:00:000 (0|1)", "invalid link"); + AddAssert("snap to 1, select none", () => checkSnapAndSelectColumn(956)); + + AddStepClickLink("00:00:000 (0)", "std link"); + AddAssert("snap and select 1", () => checkSnapAndSelectColumn(956, new List<(int, int)> + { (956, 1) }) ); - AddStepClickLink("00:00:000 (0)", "std link "); - AddAssert("snap and select 1", () => - EditorClock.CurrentTime == 956 - && EditorBeatmap.SelectedHitObjects.All(x => IsNoteAt(x, 956, 1)) - ); - - AddStepClickLink("00:00:000 (1,2)", "std link "); - AddAssert("snap to 1, select none", () => - EditorClock.CurrentTime == 956 - && !EditorBeatmap.SelectedHitObjects.Any() - ); + AddStepClickLink("00:00:000 (1,2)", "std link"); + AddAssert("snap to 1, select none", () => checkSnapAndSelectColumn(956)); } } } From 57170501cdadfa786d8d59bd67efa4f45a9ce81a Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sat, 4 Nov 2023 17:25:09 +0200 Subject: [PATCH 1293/4092] Improve code quality --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 31 +++------------- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 30 +++------------- osu.Game.Rulesets.Osu/OsuRuleset.cs | 33 +++-------------- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 30 +++------------- .../Overlays/Mods/BeatmapAttributesDisplay.cs | 20 ++++++----- .../Overlays/Mods/VerticalAttributeDisplay.cs | 33 ++++++++++++----- osu.Game/Rulesets/Ruleset.cs | 17 +-------- .../Screens/Select/Details/AdvancedStats.cs | 35 ++++++++++++------- 8 files changed, 76 insertions(+), 153 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index f0b20c68c4..2d0e34431d 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -238,37 +238,14 @@ namespace osu.Game.Rulesets.Catch public float ArFromPreempt(double preempt) => (float)(preempt > 1200 ? ((1800 - preempt) / 120) : ((1200 - preempt) / 150)) + 5; public float ChangeArFromRate(float AR, double rate) => ArFromPreempt(PreemptFromAr(AR) / rate); - public override BeatmapDifficulty GetEffectiveDifficulty(IBeatmapDifficultyInfo baseDifficulty, IReadOnlyList mods, ref (RateAdjustType AR, RateAdjustType OD) rateAdjustedInfo) + public override BeatmapDifficulty GetRateAdjustedDifficulty(IBeatmapDifficultyInfo baseDifficulty, double rate) { - BeatmapDifficulty? adjustedDifficulty = null; - rateAdjustedInfo = (RateAdjustType.NotChanged, RateAdjustType.NotChanged); + BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(baseDifficulty); - if (mods.Any(m => m is IApplicableToDifficulty)) - { - adjustedDifficulty = new BeatmapDifficulty(baseDifficulty); - - foreach (var mod in mods.OfType()) - mod.ApplyToDifficulty(adjustedDifficulty); - } - - if (mods.Any(m => m is ModRateAdjust)) - { - adjustedDifficulty ??= new BeatmapDifficulty(baseDifficulty); - - foreach (var mod in mods.OfType()) - { - double speedChange = (float)mod.SpeedChange.Value; - - float ar = adjustedDifficulty.ApproachRate; - float od = adjustedDifficulty.OverallDifficulty; - - adjustedDifficulty.ApproachRate = ChangeArFromRate(ar, speedChange); - - rateAdjustedInfo.AR = GetRateAdjustType(ar, adjustedDifficulty.ApproachRate); - } - } + adjustedDifficulty.ApproachRate = ChangeArFromRate(adjustedDifficulty.ApproachRate, rate); return adjustedDifficulty ?? (BeatmapDifficulty)baseDifficulty; } + } } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index a8a54ebf05..cfdfcbadee 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -431,34 +431,12 @@ namespace osu.Game.Rulesets.Mania public double HitwindowFromOd(float OD) => 64.0 - 3 * OD; public float OdFromHitwindow(double hitwindow300) => (float)(64.0 - hitwindow300) / 3; public float ChangeOdFromRate(float OD, double rate) => OdFromHitwindow(HitwindowFromOd(OD) / rate); - public override BeatmapDifficulty GetEffectiveDifficulty(IBeatmapDifficultyInfo baseDifficulty, IReadOnlyList mods, ref (RateAdjustType AR, RateAdjustType OD) rateAdjustedInfo) + + public override BeatmapDifficulty GetRateAdjustedDifficulty(IBeatmapDifficultyInfo baseDifficulty, double rate) { - BeatmapDifficulty? adjustedDifficulty = null; - rateAdjustedInfo = (RateAdjustType.NotChanged, RateAdjustType.NotChanged); + BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(baseDifficulty); - if (mods.Any(m => m is IApplicableToDifficulty)) - { - adjustedDifficulty = new BeatmapDifficulty(baseDifficulty); - - foreach (var mod in mods.OfType()) - mod.ApplyToDifficulty(adjustedDifficulty); - } - - if (mods.Any(m => m is ModRateAdjust)) - { - adjustedDifficulty ??= new BeatmapDifficulty(baseDifficulty); - - foreach (var mod in mods.OfType()) - { - double speedChange = (float)mod.SpeedChange.Value; - - float od = adjustedDifficulty.OverallDifficulty; - - adjustedDifficulty.OverallDifficulty = ChangeOdFromRate(od, speedChange); - - rateAdjustedInfo.OD = GetRateAdjustType(od, adjustedDifficulty.OverallDifficulty); - } - } + adjustedDifficulty.OverallDifficulty = ChangeOdFromRate(adjustedDifficulty.OverallDifficulty, rate); return adjustedDifficulty ?? (BeatmapDifficulty)baseDifficulty; } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 4810be5db2..3a8f589c9f 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -335,37 +335,12 @@ namespace osu.Game.Rulesets.Osu public float OdFromHitwindow(double hitwindow300) => (float)(80.0 - hitwindow300) / 6; public float ChangeArFromRate(float AR, double rate) => ArFromPreempt(PreemptFromAr(AR) / rate); public float ChangeOdFromRate(float OD, double rate) => OdFromHitwindow(HitwindowFromOd(OD) / rate); - public override BeatmapDifficulty GetEffectiveDifficulty(IBeatmapDifficultyInfo baseDifficulty, IReadOnlyList mods, ref (RateAdjustType AR, RateAdjustType OD) rateAdjustedInfo) + public override BeatmapDifficulty GetRateAdjustedDifficulty(IBeatmapDifficultyInfo baseDifficulty, double rate) { - BeatmapDifficulty? adjustedDifficulty = null; - rateAdjustedInfo = (RateAdjustType.NotChanged, RateAdjustType.NotChanged); + BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(baseDifficulty); - if (mods.Any(m => m is IApplicableToDifficulty)) - { - adjustedDifficulty = new BeatmapDifficulty(baseDifficulty); - - foreach (var mod in mods.OfType()) - mod.ApplyToDifficulty(adjustedDifficulty); - } - - if (mods.Any(m => m is ModRateAdjust)) - { - adjustedDifficulty ??= new BeatmapDifficulty(baseDifficulty); - - foreach (var mod in mods.OfType()) - { - double speedChange = (float)mod.SpeedChange.Value; - - float ar = adjustedDifficulty.ApproachRate; - float od = adjustedDifficulty.OverallDifficulty; - - adjustedDifficulty.ApproachRate = ChangeArFromRate(ar, speedChange); - adjustedDifficulty.OverallDifficulty = ChangeOdFromRate(od, speedChange); - - rateAdjustedInfo.AR = GetRateAdjustType(ar, adjustedDifficulty.ApproachRate); - rateAdjustedInfo.OD = GetRateAdjustType(od, adjustedDifficulty.OverallDifficulty); - } - } + adjustedDifficulty.ApproachRate = ChangeArFromRate(adjustedDifficulty.ApproachRate, rate); + adjustedDifficulty.OverallDifficulty = ChangeOdFromRate(adjustedDifficulty.OverallDifficulty, rate); return adjustedDifficulty ?? (BeatmapDifficulty)baseDifficulty; } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index a9abbea251..026ac4f42e 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -266,34 +266,12 @@ namespace osu.Game.Rulesets.Taiko public double HitwindowFromOd(float OD) => 35.0 - 15.0 * (OD - 5) / 5; public float OdFromHitwindow(double hitwindow300) => (float)(5 * (35 - hitwindow300) / 15 + 5); public float ChangeOdFromRate(float OD, double rate) => OdFromHitwindow(HitwindowFromOd(OD) / rate); - public override BeatmapDifficulty GetEffectiveDifficulty(IBeatmapDifficultyInfo baseDifficulty, IReadOnlyList mods, ref (RateAdjustType AR, RateAdjustType OD) rateAdjustedInfo) + + public override BeatmapDifficulty GetRateAdjustedDifficulty(IBeatmapDifficultyInfo baseDifficulty, double rate) { - BeatmapDifficulty? adjustedDifficulty = null; - rateAdjustedInfo = (RateAdjustType.NotChanged, RateAdjustType.NotChanged); + BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(baseDifficulty); - if (mods.Any(m => m is IApplicableToDifficulty)) - { - adjustedDifficulty = new BeatmapDifficulty(baseDifficulty); - - foreach (var mod in mods.OfType()) - mod.ApplyToDifficulty(adjustedDifficulty); - } - - if (mods.Any(m => m is ModRateAdjust)) - { - adjustedDifficulty ??= new BeatmapDifficulty(baseDifficulty); - - foreach (var mod in mods.OfType()) - { - double speedChange = (float)mod.SpeedChange.Value; - - float od = adjustedDifficulty.OverallDifficulty; - - adjustedDifficulty.OverallDifficulty = ChangeOdFromRate(od, speedChange); - - rateAdjustedInfo.OD = GetRateAdjustType(od, adjustedDifficulty.OverallDifficulty); - } - } + adjustedDifficulty.OverallDifficulty = ChangeOdFromRate(adjustedDifficulty.OverallDifficulty, rate); return adjustedDifficulty ?? (BeatmapDifficulty)baseDifficulty; } diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index d0ee9750b2..fd9ce8f5d9 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -251,17 +251,21 @@ namespace osu.Game.Overlays.Mods bpmDisplay.Current.Value = BeatmapInfo.Value.BPM * rate; - (Ruleset.RateAdjustType AR, Ruleset.RateAdjustType OD) rateAdjustType = (Ruleset.RateAdjustType.NotChanged, Ruleset.RateAdjustType.NotChanged); + var moddedDifficulty = new BeatmapDifficulty(BeatmapInfo.Value.Difficulty); + + foreach (var mod in mods.Value.OfType()) + mod.ApplyToDifficulty(moddedDifficulty); Ruleset ruleset = gameRuleset.Value.CreateInstance(); - BeatmapDifficulty adjustedDifficulty = ruleset.GetEffectiveDifficulty(BeatmapInfo.Value.Difficulty, mods.Value, ref rateAdjustType); - approachRateDisplay.RateChangeType.Value = rateAdjustType.AR; - overallDifficultyDisplay.RateChangeType.Value = rateAdjustType.OD; + var rateAdjustedDifficulty = ruleset.GetRateAdjustedDifficulty(moddedDifficulty, rate); - circleSizeDisplay.Current.Value = adjustedDifficulty.CircleSize; - drainRateDisplay.Current.Value = adjustedDifficulty.DrainRate; - approachRateDisplay.Current.Value = adjustedDifficulty.ApproachRate; - overallDifficultyDisplay.Current.Value = adjustedDifficulty.OverallDifficulty; + approachRateDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(moddedDifficulty.ApproachRate, rateAdjustedDifficulty.ApproachRate); + overallDifficultyDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(moddedDifficulty.OverallDifficulty, rateAdjustedDifficulty.OverallDifficulty); + + circleSizeDisplay.Current.Value = rateAdjustedDifficulty.CircleSize; + drainRateDisplay.Current.Value = rateAdjustedDifficulty.DrainRate; + approachRateDisplay.Current.Value = rateAdjustedDifficulty.ApproachRate; + overallDifficultyDisplay.Current.Value = rateAdjustedDifficulty.OverallDifficulty; }); private void updateCollapsedState() diff --git a/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs b/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs index 16d75cd448..0b0c49bc1b 100644 --- a/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs +++ b/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs @@ -12,7 +12,6 @@ using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osuTK.Graphics; @@ -28,7 +27,7 @@ namespace osu.Game.Overlays.Mods private readonly BindableWithCurrent current = new BindableWithCurrent(); - public Bindable RateChangeType = new Bindable(Ruleset.RateAdjustType.NotChanged); + public Bindable AdjustType = new Bindable(); /// /// Text to display in the top area of the display. @@ -43,22 +42,22 @@ namespace osu.Game.Overlays.Mods private void updateTextColor() { Color4 newColor; - switch (RateChangeType.Value) + switch (AdjustType.Value) { - case Ruleset.RateAdjustType.NotChanged: + case ModEffect.NotChanged: newColor = Color4.White; break; - case Ruleset.RateAdjustType.DifficultyReduction: + case ModEffect.DifficultyReduction: newColor = colours.ForModType(ModType.DifficultyReduction); break; - case Ruleset.RateAdjustType.DifficultyIncrease: + case ModEffect.DifficultyIncrease: newColor = colours.ForModType(ModType.DifficultyIncrease); break; default: - throw new ArgumentOutOfRangeException(nameof(RateChangeType.Value)); + throw new ArgumentOutOfRangeException(nameof(AdjustType.Value)); } text.Colour = newColor; @@ -74,7 +73,7 @@ namespace osu.Game.Overlays.Mods Origin = Anchor.CentreLeft; Anchor = Anchor.CentreLeft; - RateChangeType.BindValueChanged(_ => updateTextColor()); + AdjustType.BindValueChanged(_ => updateTextColor()); InternalChild = new FillFlowContainer { @@ -101,6 +100,24 @@ namespace osu.Game.Overlays.Mods } }; } + + public static ModEffect CalculateEffect(double oldValue, double newValue) + { + if (newValue == oldValue) + return ModEffect.NotChanged; + if (newValue < oldValue) + return ModEffect.DifficultyReduction; + + return ModEffect.DifficultyIncrease; + } + + public enum ModEffect + { + NotChanged, + DifficultyReduction, + DifficultyIncrease, + } + private partial class EffectCounter : RollingCounter { protected override double RollingDuration => 500; diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 7a09bff4aa..fbeec5ff4b 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -389,21 +389,6 @@ namespace osu.Game.Rulesets /// Can be overridden to alter the difficulty section to the editor beatmap setup screen. /// public virtual DifficultySection? CreateEditorDifficultySection() => null; - public virtual BeatmapDifficulty GetEffectiveDifficulty(IBeatmapDifficultyInfo baseDifficulty, IReadOnlyList mods, ref (RateAdjustType AR, RateAdjustType OD) rateAdjustedInfo) => (BeatmapDifficulty)baseDifficulty; - public enum RateAdjustType - { - NotChanged, - DifficultyReduction, - DifficultyIncrease - } - - protected RateAdjustType GetRateAdjustType(float baseValue, float adjustedValue) - { - if (adjustedValue > baseValue) - return RateAdjustType.DifficultyIncrease; - if (adjustedValue < baseValue) - return RateAdjustType.DifficultyReduction; - return RateAdjustType.NotChanged; - } + public virtual BeatmapDifficulty GetRateAdjustedDifficulty(IBeatmapDifficultyInfo baseDifficulty, double rate) => new BeatmapDifficulty(baseDifficulty); } } diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 867d86a69b..d143e12667 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -24,6 +24,7 @@ using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; +using System.Linq; namespace osu.Game.Screens.Select.Details { @@ -116,12 +117,21 @@ namespace osu.Game.Screens.Select.Details { IBeatmapDifficultyInfo baseDifficulty = BeatmapInfo?.Difficulty; BeatmapDifficulty adjustedDifficulty = null; - (Ruleset.RateAdjustType AR, Ruleset.RateAdjustType OD) rateAdjustInfo = (Ruleset.RateAdjustType.NotChanged, Ruleset.RateAdjustType.NotChanged); if (baseDifficulty != null) { - Ruleset ruleset = gameRuleset.Value.CreateInstance(); - adjustedDifficulty = ruleset.GetEffectiveDifficulty(baseDifficulty, mods.Value, ref rateAdjustInfo); + adjustedDifficulty = new BeatmapDifficulty(baseDifficulty); + + foreach (var mod in mods.Value.OfType()) + mod.ApplyToDifficulty(adjustedDifficulty); + + // For now we not using rate adjusted difficulty here + + //Ruleset ruleset = gameRuleset.Value.CreateInstance(); + //double rate = 1; + //foreach (var mod in mods.Value.OfType()) + // rate = mod.ApplyToRate(0, rate); + //adjustedDifficulty = ruleset.GetRateAdjustedDifficulty(adjustedDifficulty, rate); } switch (BeatmapInfo?.Ruleset.OnlineID) @@ -130,18 +140,18 @@ namespace osu.Game.Screens.Select.Details // Account for mania differences locally for now // Eventually this should be handled in a more modular way, allowing rulesets to return arbitrary difficulty attributes FirstValue.Title = BeatmapsetsStrings.ShowStatsCsMania; - FirstValue.Value = (baseDifficulty?.CircleSize ?? 0, null, false); + FirstValue.Value = (baseDifficulty?.CircleSize ?? 0, null); break; default: FirstValue.Title = BeatmapsetsStrings.ShowStatsCs; - FirstValue.Value = (baseDifficulty?.CircleSize ?? 0, adjustedDifficulty?.CircleSize, false); + FirstValue.Value = (baseDifficulty?.CircleSize ?? 0, adjustedDifficulty?.CircleSize); break; } - HpDrain.Value = (baseDifficulty?.DrainRate ?? 0, adjustedDifficulty?.DrainRate, false); - Accuracy.Value = (baseDifficulty?.OverallDifficulty ?? 0, adjustedDifficulty?.OverallDifficulty, rateAdjustInfo.OD != Ruleset.RateAdjustType.NotChanged); - ApproachRate.Value = (baseDifficulty?.ApproachRate ?? 0, adjustedDifficulty?.ApproachRate, rateAdjustInfo.OD != Ruleset.RateAdjustType.NotChanged); + HpDrain.Value = (baseDifficulty?.DrainRate ?? 0, adjustedDifficulty?.DrainRate); + Accuracy.Value = (baseDifficulty?.OverallDifficulty ?? 0, adjustedDifficulty?.OverallDifficulty); + ApproachRate.Value = (baseDifficulty?.ApproachRate ?? 0, adjustedDifficulty?.ApproachRate); updateStarDifficulty(); } @@ -175,7 +185,7 @@ namespace osu.Game.Screens.Select.Details if (normalDifficulty == null || moddedDifficulty == null) return; - starDifficulty.Value = ((float)normalDifficulty.Value.Stars, (float)moddedDifficulty.Value.Stars, false); + starDifficulty.Value = ((float)normalDifficulty.Value.Stars, (float)moddedDifficulty.Value.Stars); }), starDifficultyCancellationSource.Token, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current); }); @@ -206,11 +216,11 @@ namespace osu.Game.Screens.Select.Details set => name.Text = value; } - private (float baseValue, float? adjustedValue, bool isRateAdjusted)? value; + private (float baseValue, float? adjustedValue)? value; - public (float baseValue, float? adjustedValue, bool isRateAdjusted) Value + public (float baseValue, float? adjustedValue) Value { - get => value ?? (0, null, false); + get => value ?? (0, null); set { if (value == this.value) @@ -221,7 +231,6 @@ namespace osu.Game.Screens.Select.Details bar.Length = value.baseValue / maxValue; valueText.Text = (value.adjustedValue ?? value.baseValue).ToString(forceDecimalPlaces ? "0.00" : "0.##"); - if (value.isRateAdjusted) valueText.Text += "*"; ModBar.Length = (value.adjustedValue ?? 0) / maxValue; if (Precision.AlmostEquals(value.baseValue, value.adjustedValue ?? value.baseValue, 0.05f)) From 440d57fb48ec4fc029452ceda9f54aad381fd32a Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sat, 4 Nov 2023 17:47:02 +0200 Subject: [PATCH 1294/4092] Basic rate-adjust tooltip --- .../Overlays/Mods/BeatmapAttributesDisplay.cs | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index fd9ce8f5d9..fc0ef989e6 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -9,6 +9,7 @@ using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; using osu.Game.Beatmaps; @@ -23,6 +24,7 @@ using osuTK.Graphics; using System.Threading; using osu.Framework.Input.Events; using osu.Game.Configuration; +using osu.Game.Rulesets.Difficulty; namespace osu.Game.Overlays.Mods { @@ -30,7 +32,7 @@ namespace osu.Game.Overlays.Mods /// On the mod select overlay, this provides a local updating view of BPM, star rating and other /// difficulty attributes so the user can have a better insight into what mods are changing. /// - public partial class BeatmapAttributesDisplay : CompositeDrawable + public partial class BeatmapAttributesDisplay : CompositeDrawable, IHasTooltip { private Container content = null!; private Container innerContent = null!; @@ -71,6 +73,10 @@ namespace osu.Game.Overlays.Mods private CancellationTokenSource? cancellationSource; private IBindable starDifficulty = null!; + private BeatmapDifficulty? baseDifficultyAttributes = null; + + private bool haveRateChangedValues = false; + [BackgroundDependencyLoader] private void load() { @@ -256,9 +262,13 @@ namespace osu.Game.Overlays.Mods foreach (var mod in mods.Value.OfType()) mod.ApplyToDifficulty(moddedDifficulty); + baseDifficultyAttributes = moddedDifficulty; + Ruleset ruleset = gameRuleset.Value.CreateInstance(); var rateAdjustedDifficulty = ruleset.GetRateAdjustedDifficulty(moddedDifficulty, rate); + haveRateChangedValues = !haveEqualDifficulties(rateAdjustedDifficulty, moddedDifficulty); + approachRateDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(moddedDifficulty.ApproachRate, rateAdjustedDifficulty.ApproachRate); overallDifficultyDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(moddedDifficulty.OverallDifficulty, rateAdjustedDifficulty.OverallDifficulty); @@ -268,6 +278,19 @@ namespace osu.Game.Overlays.Mods overallDifficultyDisplay.Current.Value = rateAdjustedDifficulty.OverallDifficulty; }); + private bool haveEqualDifficulties(BeatmapDifficulty? a, BeatmapDifficulty? b) + { + if (a == null && b == null) return true; + if (a == null || b == null) return false; + + if (a.ApproachRate != b.ApproachRate) return false; + if (a.OverallDifficulty != b.OverallDifficulty) return false; + if (a.DrainRate != b.DrainRate) return false; + if (a.CircleSize != b.CircleSize) return false; + + return true; + } + private void updateCollapsedState() { outerContent.FadeTo(Collapsed.Value && !IsHovered ? 0 : 1, transition_duration, Easing.OutQuint); @@ -285,5 +308,17 @@ namespace osu.Game.Overlays.Mods UseFullGlyphHeight = false, }; } + + public LocalisableString TooltipText + { + get + { + if (haveRateChangedValues) + { + return "Some of the values are Rate-Adjusted."; + } + return ""; + } + } } } From 5597e819be1e400fb481f316b1b6fc47c76b94d7 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sat, 4 Nov 2023 18:01:10 +0200 Subject: [PATCH 1295/4092] fixed bug in AR formula --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 2d0e34431d..da84d12f59 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -235,7 +235,7 @@ namespace osu.Game.Rulesets.Catch } public double PreemptFromAr(float AR) => AR < 5 ? (1200.0 + 600.0 * (5 - AR) / 5) : (1200.0 - 750.0 * (AR - 5) / 5); - public float ArFromPreempt(double preempt) => (float)(preempt > 1200 ? ((1800 - preempt) / 120) : ((1200 - preempt) / 150)) + 5; + public float ArFromPreempt(double preempt) => (float)(preempt > 1200 ? ((1800 - preempt) / 120) : ((1200 - preempt) / 150 + 5)); public float ChangeArFromRate(float AR, double rate) => ArFromPreempt(PreemptFromAr(AR) / rate); public override BeatmapDifficulty GetRateAdjustedDifficulty(IBeatmapDifficultyInfo baseDifficulty, double rate) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 3a8f589c9f..bdbedf1409 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -330,7 +330,7 @@ namespace osu.Game.Rulesets.Osu public override RulesetSetupSection CreateEditorSetupSection() => new OsuSetupSection(); public double PreemptFromAr(float AR) => AR < 5 ? (1200.0 + 600.0 * (5 - AR) / 5) : (1200.0 - 750.0 * (AR - 5) / 5); - public float ArFromPreempt(double preempt) => (float)(preempt > 1200 ? ((1800 - preempt) / 120) : ((1200 - preempt) / 150)) + 5; + public float ArFromPreempt(double preempt) => (float)(preempt > 1200 ? ((1800 - preempt) / 120) : ((1200 - preempt) / 150 + 5)); public double HitwindowFromOd(float OD) => 80.0 - 6 * OD; public float OdFromHitwindow(double hitwindow300) => (float)(80.0 - hitwindow300) / 6; public float ChangeArFromRate(float AR, double rate) => ArFromPreempt(PreemptFromAr(AR) / rate); From a70bfca501312bc587c230064a6269b20a714c82 Mon Sep 17 00:00:00 2001 From: Joshua Hegedus Date: Thu, 2 Nov 2023 09:16:25 +0100 Subject: [PATCH 1296/4092] added usergrid for tooltip --- osu.Game/Users/Drawables/ClickableAvatar.cs | 33 ++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/osu.Game/Users/Drawables/ClickableAvatar.cs b/osu.Game/Users/Drawables/ClickableAvatar.cs index 677a8fff36..0ed9f56cc7 100644 --- a/osu.Game/Users/Drawables/ClickableAvatar.cs +++ b/osu.Game/Users/Drawables/ClickableAvatar.cs @@ -3,16 +3,27 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Graphics.Containers; using osu.Game.Localisation; using osu.Game.Online.API.Requests.Responses; +using osuTK; namespace osu.Game.Users.Drawables { - public partial class ClickableAvatar : OsuClickableContainer + public partial class ClickableAvatar : OsuClickableContainer, IHasCustomTooltip { + public ITooltip GetCustomTooltip() => new UserGridPanelTooltip(); + + public UserGridPanel TooltipContent => new UserGridPanel(user!) + { + Width = 300 + }; + public override LocalisableString TooltipText { get @@ -67,5 +78,25 @@ namespace osu.Game.Users.Drawables return base.OnClick(e); } + + private partial class UserGridPanelTooltip : VisibilityContainer, ITooltip + { + private UserGridPanel? displayedUser; + + protected override void PopIn() + { + Child = displayedUser; + this.FadeIn(20, Easing.OutQuint); + } + + protected override void PopOut() => this.FadeOut(80, Easing.OutQuint); + + public void Move(Vector2 pos) => Position = pos; + + public void SetContent(UserGridPanel userGridPanel) + { + displayedUser = userGridPanel; + } + } } } From ec290ae953c9dffec44bb71e1fcde4e0785ef843 Mon Sep 17 00:00:00 2001 From: Joshua Hegedus Date: Sat, 4 Nov 2023 19:03:23 +0100 Subject: [PATCH 1297/4092] added tests --- .../Online/TestSceneUserClickableAvatar.cs | 125 ++++++++++++++++++ osu.Game/Users/Drawables/ClickableAvatar.cs | 21 --- osu.Game/Users/Drawables/UpdateableAvatar.cs | 1 - 3 files changed, 125 insertions(+), 22 deletions(-) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs b/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs new file mode 100644 index 0000000000..13f559ac09 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs @@ -0,0 +1,125 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Testing; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Users; +using osu.Game.Users.Drawables; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Online +{ + public partial class TestSceneUserClickableAvatar : OsuManualInputManagerTestScene + { + [SetUp] + public void SetUp() => Schedule(() => + { + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Spacing = new Vector2(10f), + Children = new Drawable[] + { + new ClickableAvatar(new APIUser + { + Username = @"flyte", Id = 3103765, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" + }) + { + Width = 50, + Height = 50, + CornerRadius = 10, + Masking = true, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, Radius = 1, Colour = Color4.Black.Opacity(0.2f), + }, + }, + new ClickableAvatar(new APIUser + { + Username = @"peppy", Id = 2, Colour = "99EB47", CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + }) + { + Width = 50, + Height = 50, + CornerRadius = 10, + Masking = true, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, Radius = 1, Colour = Color4.Black.Opacity(0.2f), + }, + }, + new ClickableAvatar(new APIUser + { + Username = @"flyte", + Id = 3103765, + CountryCode = CountryCode.JP, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg", + Status = + { + Value = new UserStatusOnline() + } + }) + { + Width = 50, + Height = 50, + CornerRadius = 10, + Masking = true, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, Radius = 1, Colour = Color4.Black.Opacity(0.2f), + }, + }, + }, + }; + }); + + [Test] + public void TestClickableAvatarHover() + { + AddStep($"click {1}. {nameof(ClickableAvatar)}", () => + { + var targets = this.ChildrenOfType().ToList(); + if (targets.Count < 1) + return; + + InputManager.MoveMouseTo(targets[0]); + }); + AddWaitStep("wait for tooltip to show", 5); + AddStep("Hover out", () => InputManager.MoveMouseTo(new Vector2(0))); + AddWaitStep("wait for tooltip to hide", 3); + + AddStep($"click {2}. {nameof(ClickableAvatar)}", () => + { + var targets = this.ChildrenOfType().ToList(); + if (targets.Count < 2) + return; + + InputManager.MoveMouseTo(targets[1]); + }); + AddWaitStep("wait for tooltip to show", 5); + AddStep("Hover out", () => InputManager.MoveMouseTo(new Vector2(0))); + AddWaitStep("wait for tooltip to hide", 3); + + AddStep($"click {3}. {nameof(ClickableAvatar)}", () => + { + var targets = this.ChildrenOfType().ToList(); + if (targets.Count < 3) + return; + + InputManager.MoveMouseTo(targets[2]); + }); + AddWaitStep("wait for tooltip to show", 5); + AddStep("Hover out", () => InputManager.MoveMouseTo(new Vector2(0))); + AddWaitStep("wait for tooltip to hide", 3); + } + } +} diff --git a/osu.Game/Users/Drawables/ClickableAvatar.cs b/osu.Game/Users/Drawables/ClickableAvatar.cs index 0ed9f56cc7..d6c6afba0b 100644 --- a/osu.Game/Users/Drawables/ClickableAvatar.cs +++ b/osu.Game/Users/Drawables/ClickableAvatar.cs @@ -1,15 +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 System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Input.Events; -using osu.Framework.Localisation; using osu.Game.Graphics.Containers; -using osu.Game.Localisation; using osu.Game.Online.API.Requests.Responses; using osuTK; @@ -24,24 +21,6 @@ namespace osu.Game.Users.Drawables Width = 300 }; - public override LocalisableString TooltipText - { - get - { - if (!Enabled.Value) - return string.Empty; - - return ShowUsernameTooltip ? (user?.Username ?? string.Empty) : ContextMenuStrings.ViewProfile; - } - set => throw new NotSupportedException(); - } - - /// - /// By default, the tooltip will show "view profile" as avatars are usually displayed next to a username. - /// Setting this to true exposes the username via tooltip for special cases where this is not true. - /// - public bool ShowUsernameTooltip { get; set; } - private readonly APIUser? user; [Resolved] diff --git a/osu.Game/Users/Drawables/UpdateableAvatar.cs b/osu.Game/Users/Drawables/UpdateableAvatar.cs index c659685807..58b3646995 100644 --- a/osu.Game/Users/Drawables/UpdateableAvatar.cs +++ b/osu.Game/Users/Drawables/UpdateableAvatar.cs @@ -74,7 +74,6 @@ namespace osu.Game.Users.Drawables { return new ClickableAvatar(user) { - ShowUsernameTooltip = showUsernameTooltip, RelativeSizeAxes = Axes.Both, }; } From 820519c37dcfa271235e75cdc2f9ad10d2f6f91e Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sat, 4 Nov 2023 21:55:46 +0200 Subject: [PATCH 1298/4092] improved tooltip --- .../Overlays/Mods/BeatmapAttributesDisplay.cs | 44 +++++++++---------- .../Overlays/Mods/VerticalAttributeDisplay.cs | 3 +- .../Screens/Select/Details/AdvancedStats.cs | 44 ++++++++++++++++--- 3 files changed, 60 insertions(+), 31 deletions(-) diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index fc0ef989e6..c921e3e075 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Threading; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -11,9 +12,12 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; using osu.Framework.Localisation; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -21,10 +25,6 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osuTK; using osuTK.Graphics; -using System.Threading; -using osu.Framework.Input.Events; -using osu.Game.Configuration; -using osu.Game.Rulesets.Difficulty; namespace osu.Game.Overlays.Mods { @@ -73,8 +73,7 @@ namespace osu.Game.Overlays.Mods private CancellationTokenSource? cancellationSource; private IBindable starDifficulty = null!; - private BeatmapDifficulty? baseDifficultyAttributes = null; - + private BeatmapDifficulty? originalDifficulty = null; private bool haveRateChangedValues = false; [BackgroundDependencyLoader] @@ -257,36 +256,34 @@ namespace osu.Game.Overlays.Mods bpmDisplay.Current.Value = BeatmapInfo.Value.BPM * rate; - var moddedDifficulty = new BeatmapDifficulty(BeatmapInfo.Value.Difficulty); + var adjustedDifficulty = new BeatmapDifficulty(BeatmapInfo.Value.Difficulty); foreach (var mod in mods.Value.OfType()) - mod.ApplyToDifficulty(moddedDifficulty); + mod.ApplyToDifficulty(adjustedDifficulty); - baseDifficultyAttributes = moddedDifficulty; + originalDifficulty = adjustedDifficulty; Ruleset ruleset = gameRuleset.Value.CreateInstance(); - var rateAdjustedDifficulty = ruleset.GetRateAdjustedDifficulty(moddedDifficulty, rate); + adjustedDifficulty = ruleset.GetRateAdjustedDifficulty(adjustedDifficulty, rate); - haveRateChangedValues = !haveEqualDifficulties(rateAdjustedDifficulty, moddedDifficulty); + haveRateChangedValues = !isDifferentArOd(originalDifficulty, adjustedDifficulty); - approachRateDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(moddedDifficulty.ApproachRate, rateAdjustedDifficulty.ApproachRate); - overallDifficultyDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(moddedDifficulty.OverallDifficulty, rateAdjustedDifficulty.OverallDifficulty); + approachRateDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(originalDifficulty.ApproachRate, adjustedDifficulty.ApproachRate); + overallDifficultyDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(originalDifficulty.OverallDifficulty, adjustedDifficulty.OverallDifficulty); - circleSizeDisplay.Current.Value = rateAdjustedDifficulty.CircleSize; - drainRateDisplay.Current.Value = rateAdjustedDifficulty.DrainRate; - approachRateDisplay.Current.Value = rateAdjustedDifficulty.ApproachRate; - overallDifficultyDisplay.Current.Value = rateAdjustedDifficulty.OverallDifficulty; + circleSizeDisplay.Current.Value = adjustedDifficulty.CircleSize; + drainRateDisplay.Current.Value = adjustedDifficulty.DrainRate; + approachRateDisplay.Current.Value = adjustedDifficulty.ApproachRate; + overallDifficultyDisplay.Current.Value = adjustedDifficulty.OverallDifficulty; }); - private bool haveEqualDifficulties(BeatmapDifficulty? a, BeatmapDifficulty? b) + private bool isDifferentArOd(BeatmapDifficulty? a, BeatmapDifficulty? b) { if (a == null && b == null) return true; if (a == null || b == null) return false; - if (a.ApproachRate != b.ApproachRate) return false; - if (a.OverallDifficulty != b.OverallDifficulty) return false; - if (a.DrainRate != b.DrainRate) return false; - if (a.CircleSize != b.CircleSize) return false; + if (!Precision.AlmostEquals(a.ApproachRate, b.ApproachRate, 0.01)) return false; + if (!Precision.AlmostEquals(a.OverallDifficulty, b.OverallDifficulty, 0.01)) return false; return true; } @@ -315,7 +312,8 @@ namespace osu.Game.Overlays.Mods { if (haveRateChangedValues) { - return "Some of the values are Rate-Adjusted."; + return LocalisableString.Format("Values are changed by mods that change speed.\n" + + "Original values: AR = {0}, OD = {1}", originalDifficulty?.ApproachRate ?? 0, originalDifficulty?.OverallDifficulty ?? 0); } return ""; } diff --git a/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs b/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs index 0b0c49bc1b..6ac7ebf159 100644 --- a/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs +++ b/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; +using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -103,7 +104,7 @@ namespace osu.Game.Overlays.Mods public static ModEffect CalculateEffect(double oldValue, double newValue) { - if (newValue == oldValue) + if (Precision.AlmostEquals(newValue, oldValue, 0.01)) return ModEffect.NotChanged; if (newValue < oldValue) return ModEffect.DifficultyReduction; diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index d143e12667..f9c6e95154 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -28,7 +29,7 @@ using System.Linq; namespace osu.Game.Screens.Select.Details { - public partial class AdvancedStats : Container + public partial class AdvancedStats : Container, IHasTooltip { [Resolved] private BeatmapDifficultyCache difficultyCache { get; set; } @@ -44,6 +45,9 @@ namespace osu.Game.Screens.Select.Details protected readonly StatisticRow FirstValue, HpDrain, Accuracy, ApproachRate; private readonly StatisticRow starDifficulty; + private BeatmapDifficulty originalDifficulty = null; + private bool haveRateChangedValues = false; + private IBeatmapInfo beatmapInfo; public IBeatmapInfo BeatmapInfo @@ -125,13 +129,15 @@ namespace osu.Game.Screens.Select.Details foreach (var mod in mods.Value.OfType()) mod.ApplyToDifficulty(adjustedDifficulty); - // For now we not using rate adjusted difficulty here + originalDifficulty = adjustedDifficulty; - //Ruleset ruleset = gameRuleset.Value.CreateInstance(); - //double rate = 1; - //foreach (var mod in mods.Value.OfType()) - // rate = mod.ApplyToRate(0, rate); - //adjustedDifficulty = ruleset.GetRateAdjustedDifficulty(adjustedDifficulty, rate); + Ruleset ruleset = gameRuleset.Value.CreateInstance(); + double rate = 1; + foreach (var mod in mods.Value.OfType()) + rate = mod.ApplyToRate(0, rate); + adjustedDifficulty = ruleset.GetRateAdjustedDifficulty(adjustedDifficulty, rate); + + haveRateChangedValues = !isDifferentArOd(originalDifficulty, adjustedDifficulty); } switch (BeatmapInfo?.Ruleset.OnlineID) @@ -196,6 +202,30 @@ namespace osu.Game.Screens.Select.Details starDifficultyCancellationSource?.Cancel(); } + private bool isDifferentArOd(BeatmapDifficulty a, BeatmapDifficulty b) + { + if (a == null && b == null) return true; + if (a == null || b == null) return false; + + if (!Precision.AlmostEquals(a.ApproachRate, b.ApproachRate, 0.01)) return false; + if (!Precision.AlmostEquals(a.OverallDifficulty, b.OverallDifficulty, 0.01)) return false; + + return true; + } + + public LocalisableString TooltipText + { + get + { + if (haveRateChangedValues) + { + return LocalisableString.Format("Values are changed by mods that change speed.\n" + + "Original values: AR = {0}, OD = {1}", originalDifficulty?.ApproachRate ?? 0, originalDifficulty?.OverallDifficulty ?? 0); + } + return ""; + } + } + public partial class StatisticRow : Container, IHasAccentColour { private const float value_width = 25; From 97caf18036efce177232e4ecfa3e36a5dba69fa0 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sat, 4 Nov 2023 21:57:17 +0200 Subject: [PATCH 1299/4092] Update CatchRuleset.cs --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index da84d12f59..816638ca87 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; From d0b2b4f7b9eaf2bf93cc92532ec683e095707561 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sat, 4 Nov 2023 21:57:42 +0200 Subject: [PATCH 1300/4092] Update AdvancedStats.cs --- osu.Game/Screens/Select/Details/AdvancedStats.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index f9c6e95154..54b76c48a4 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -16,6 +16,7 @@ using osu.Game.Beatmaps; using osu.Framework.Bindables; using System.Collections.Generic; using osu.Game.Rulesets.Mods; +using System.Linq; using System.Threading; using System.Threading.Tasks; using osu.Framework.Extensions; @@ -25,7 +26,6 @@ using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; -using System.Linq; namespace osu.Game.Screens.Select.Details { From 7492d953aee303732df79ba0267f34dbacfbb3e9 Mon Sep 17 00:00:00 2001 From: ratinfx Date: Sat, 4 Nov 2023 21:17:58 +0100 Subject: [PATCH 1301/4092] Moved error checks into Editor - Invoke Action on error to Notify user - added some comments --- .../Editing/TestSceneOpenEditorTimestamp.cs | 25 ++++++------ osu.Game/OsuGame.cs | 39 ++++--------------- osu.Game/Screens/Edit/Editor.cs | 27 ++++++++++++- .../Screens/Edit/EditorTimestampParser.cs | 4 +- 4 files changed, 47 insertions(+), 48 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs index fea9334ff8..f65aff922e 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs @@ -67,17 +67,6 @@ namespace osu.Game.Tests.Visual.Editing && checkCombos; } - private bool checkSnapAndSelectColumn(double startTime, List<(int, int)> columnPairs = null) - { - bool checkColumns = columnPairs != null - ? EditorBeatmap.SelectedHitObjects.All(x => columnPairs.Any(col => isNoteAt(x, col.Item1, col.Item2))) - : !EditorBeatmap.SelectedHitObjects.Any(); - - return EditorClock.CurrentTime == startTime - && EditorBeatmap.SelectedHitObjects.Count == (columnPairs?.Count ?? 0) - && checkColumns; - } - private bool hasCombosInOrder(IEnumerable selected, params int[] comboNumbers) { List hitObjects = selected.ToList(); @@ -89,6 +78,17 @@ namespace osu.Game.Tests.Visual.Editing .Any(); } + private bool checkSnapAndSelectColumn(double startTime, IReadOnlyCollection<(int, int)> columnPairs = null) + { + bool checkColumns = columnPairs != null + ? EditorBeatmap.SelectedHitObjects.All(x => columnPairs.Any(col => isNoteAt(x, col.Item1, col.Item2))) + : !EditorBeatmap.SelectedHitObjects.Any(); + + return EditorClock.CurrentTime == startTime + && EditorBeatmap.SelectedHitObjects.Count == (columnPairs?.Count ?? 0) + && checkColumns; + } + private bool isNoteAt(HitObject hitObject, double time, int column) { return hitObject is IHasColumn columnInfo @@ -96,7 +96,7 @@ namespace osu.Game.Tests.Visual.Editing && columnInfo.Column == column; } - public void SetUpEditor(RulesetInfo ruleset) + protected void SetUpEditor(RulesetInfo ruleset) { BeatmapSetInfo beatmapSet = null!; @@ -320,6 +320,7 @@ namespace osu.Game.Tests.Visual.Editing { (956, 1) }) ); + // TODO: discuss - this selects the first 2 objects on Stable, do we want that or is this fine? AddStepClickLink("00:00:000 (1,2)", "std link"); AddAssert("snap to 1, select none", () => checkSnapAndSelectColumn(956)); } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index a6bb6cc120..a9d4927e33 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -562,43 +562,18 @@ namespace osu.Game { if (ScreenStack.CurrentScreen is not Editor editor) { - waitForReady(() => Notifications, _ => Notifications.Post(new SimpleNotification - { - Icon = FontAwesome.Solid.ExclamationTriangle, - Text = EditorStrings.MustBeInEdit, - })); + postNotification(EditorStrings.MustBeInEdit); return; } - string[] groups = EditorTimestampParser.GetRegexGroups(timestamp); + editor.SeekAndSelectHitObjects(timestamp, onError: postNotification); + return; - if (groups.Length != 2 || string.IsNullOrEmpty(groups[0])) + void postNotification(LocalisableString message) => Schedule(() => Notifications.Post(new SimpleNotification { - waitForReady(() => Notifications, _ => Notifications.Post(new SimpleNotification - { - Icon = FontAwesome.Solid.ExclamationTriangle, - Text = EditorStrings.FailedToProcessTimestamp - })); - return; - } - - string timeGroup = groups[0]; - string objectsGroup = groups[1]; - string timeMinutes = timeGroup.Split(':').FirstOrDefault() ?? string.Empty; - - // Currently, lazer chat highlights infinite-long editor links like `10000000000:00:000 (1)` - // Limit timestamp link length at 30000 min (50 hr) to avoid parsing issues - if (timeMinutes.Length > 5 || double.Parse(timeMinutes) > 30_000) - { - waitForReady(() => Notifications, _ => Notifications.Post(new SimpleNotification - { - Icon = FontAwesome.Solid.ExclamationTriangle, - Text = EditorStrings.TooLongTimestamp - })); - return; - } - - editor.SeekAndSelectHitObjects(timeGroup, objectsGroup); + Icon = FontAwesome.Solid.ExclamationTriangle, + Text = message + })); } /// diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 80e01d4eb7..60d26d9ec0 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1138,11 +1138,33 @@ namespace osu.Game.Screens.Edit loader?.CancelPendingDifficultySwitch(); } - public void SeekAndSelectHitObjects(string timeGroup, string objectsGroup) + public void SeekAndSelectHitObjects(string timestamp, Action onError) { + string[] groups = EditorTimestampParser.GetRegexGroups(timestamp); + + if (groups.Length != 2 || string.IsNullOrEmpty(groups[0])) + { + onError.Invoke(EditorStrings.FailedToProcessTimestamp); + return; + } + + string timeGroup = groups[0]; + string objectsGroup = groups[1]; + string timeMinutes = timeGroup.Split(':').FirstOrDefault() ?? string.Empty; + + // Currently, lazer chat highlights infinite-long editor links like `10000000000:00:000 (1)` + // Limit timestamp link length at 30000 min (50 hr) to avoid parsing issues + if (timeMinutes.Length > 5 || double.Parse(timeMinutes) > 30_000) + { + onError.Invoke(EditorStrings.TooLongTimestamp); + return; + } + double position = EditorTimestampParser.GetTotalMilliseconds(timeGroup); + editorBeatmap.SelectedHitObjects.Clear(); + // Only seeking is necessary if (string.IsNullOrEmpty(objectsGroup)) { if (clock.IsRunning) @@ -1155,8 +1177,9 @@ namespace osu.Game.Screens.Edit if (Mode.Value != EditorScreenMode.Compose) Mode.Value = EditorScreenMode.Compose; - // Seek to the next closest HitObject's position + // Seek to the next closest HitObject HitObject nextObject = editorBeatmap.HitObjects.FirstOrDefault(x => x.StartTime >= position); + if (nextObject != null && nextObject.StartTime > 0) position = nextObject.StartTime; diff --git a/osu.Game/Screens/Edit/EditorTimestampParser.cs b/osu.Game/Screens/Edit/EditorTimestampParser.cs index 44d614ca70..2d8f8a8f4c 100644 --- a/osu.Game/Screens/Edit/EditorTimestampParser.cs +++ b/osu.Game/Screens/Edit/EditorTimestampParser.cs @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Edit return (times[0] * 60 + times[1]) * 1_000 + times[2]; } - public static List GetSelectedHitObjects(IEnumerable editorHitObjects, string objectsGroup, double position) + public static List GetSelectedHitObjects(IReadOnlyList editorHitObjects, string objectsGroup, double position) { List hitObjects = editorHitObjects.Where(x => x.StartTime >= position).ToList(); List selectedObjects = new List(); @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Edit } // Stable behavior - // - always selects next closest object when `objectsGroup` only has one, non-Column item + // - always selects the next closest object when `objectsGroup` only has one (combo) item if (objectsToSelect.Length != 1 || objectsGroup.Contains('|')) return selectedObjects; From 91cf237fc12fcbecc6d1345310717407f559a32e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 4 Nov 2023 02:46:28 +0300 Subject: [PATCH 1302/4092] Revert health display settings changes --- osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index cc69acf8d6..1a213ddc6f 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Play.HUD public bool UsesFixedAnchor { get; set; } [SettingSource("Bar height")] - public BindableFloat BarHeight { get; } = new BindableFloat(30) + public BindableFloat BarHeight { get; } = new BindableFloat(20) { MinValue = 0, MaxValue = 64, @@ -37,7 +37,7 @@ namespace osu.Game.Screens.Play.HUD }; [SettingSource("Bar length")] - public BindableFloat BarLength { get; } = new BindableFloat(0.35f) + public BindableFloat BarLength { get; } = new BindableFloat(0.98f) { MinValue = 0.2f, MaxValue = 1, From 1c844a155e97059e23cdae6470401d217fa88db3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 5 Nov 2023 01:52:12 +0300 Subject: [PATCH 1303/4092] Remove health line detail --- .../Screens/Play/HUD/ArgonHealthDisplay.cs | 64 ++++++++----------- osu.Game/Skinning/ArgonSkin.cs | 4 +- 2 files changed, 29 insertions(+), 39 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 1a213ddc6f..793d43f7ef 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Lines; -using osu.Framework.Graphics.Shapes; using osu.Framework.Layout; using osu.Framework.Threading; using osu.Framework.Utils; @@ -101,45 +100,36 @@ namespace osu.Game.Screens.Play.HUD RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - InternalChildren = new[] + InternalChild = new Container { - new Circle + AutoSizeAxes = Axes.Both, + Children = new Drawable[] { - Position = new Vector2(-4f, main_path_radius - 1.5f), - Size = new Vector2(50f, 3f), - }, - new Container - { - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Left = 50f }, - Children = new Drawable[] + background = new BackgroundPath { - background = new BackgroundPath - { - PathRadius = main_path_radius, - }, - glowBar = new BarPath - { - BarColour = Color4.White, - GlowColour = main_bar_glow_colour, - Blending = BlendingParameters.Additive, - Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0.8f), Color4.White), - PathRadius = 40f, - // Kinda hacky, but results in correct positioning with increased path radius. - Margin = new MarginPadding(-30f), - GlowPortion = 0.9f, - }, - mainBar = new BarPath - { - AutoSizeAxes = Axes.None, - RelativeSizeAxes = Axes.Both, - Blending = BlendingParameters.Additive, - BarColour = main_bar_colour, - GlowColour = main_bar_glow_colour, - PathRadius = main_path_radius, - GlowPortion = 0.6f, - }, - } + PathRadius = main_path_radius, + }, + glowBar = new BarPath + { + BarColour = Color4.White, + GlowColour = main_bar_glow_colour, + Blending = BlendingParameters.Additive, + Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0.8f), Color4.White), + PathRadius = 40f, + // Kinda hacky, but results in correct positioning with increased path radius. + Margin = new MarginPadding(-30f), + GlowPortion = 0.9f, + }, + mainBar = new BarPath + { + AutoSizeAxes = Axes.None, + RelativeSizeAxes = Axes.Both, + Blending = BlendingParameters.Additive, + BarColour = main_bar_colour, + GlowColour = main_bar_glow_colour, + PathRadius = main_path_radius, + GlowPortion = 0.6f, + }, } }; } diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 07d9afffac..f850ae9cbd 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -130,8 +130,8 @@ namespace osu.Game.Skinning // elements default to beneath the health bar const float components_x_offset = 50; - health.Anchor = Anchor.TopLeft; - health.Origin = Anchor.TopLeft; + health.Anchor = Anchor.TopCentre; + health.Origin = Anchor.TopCentre; health.Y = 15; if (scoreWedge != null) From 58a830fc5b2bd749ebccd7cf1b5e0f12b6f4c117 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 5 Nov 2023 01:55:12 +0300 Subject: [PATCH 1304/4092] Revert "Add "Argon" performance points counter" This reverts commit 56eeb117ce2011ad4b80cf32c3b015eb482d7c9b. --- .../TestScenePerformancePointsCounter.cs | 5 +- .../Play/ArgonPerformancePointsCounter.cs | 62 ------------------- 2 files changed, 3 insertions(+), 64 deletions(-) delete mode 100644 osu.Game/Screens/Play/ArgonPerformancePointsCounter.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs index 9b8346cf12..9622caabf5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs @@ -16,6 +16,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD; using osuTK; namespace osu.Game.Tests.Visual.Gameplay @@ -29,7 +30,7 @@ namespace osu.Game.Tests.Visual.Gameplay private int iteration; private Bindable lastJudgementResult = new Bindable(); - private ArgonPerformancePointsCounter counter; + private PerformancePointsCounter counter; [SetUpSteps] public void SetUpSteps() => AddStep("create components", () => @@ -65,7 +66,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void createCounter() => AddStep("Create counter", () => { - dependencyContainer.Child = counter = new ArgonPerformancePointsCounter + dependencyContainer.Child = counter = new PerformancePointsCounter { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/Play/ArgonPerformancePointsCounter.cs b/osu.Game/Screens/Play/ArgonPerformancePointsCounter.cs deleted file mode 100644 index f0e0e1f0a4..0000000000 --- a/osu.Game/Screens/Play/ArgonPerformancePointsCounter.cs +++ /dev/null @@ -1,62 +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.Framework.Allocation; -using osu.Framework.Extensions.LocalisationExtensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Localisation; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Resources.Localisation.Web; -using osu.Game.Screens.Play.HUD; -using osu.Game.Skinning; - -namespace osu.Game.Screens.Play -{ - public partial class ArgonPerformancePointsCounter : GameplayPerformancePointsCounter, ISerialisableDrawable - { - public bool UsesFixedAnchor { get; set; } - - [BackgroundDependencyLoader] - private void load() - { - } - - protected override IHasText CreateText() => new TextComponent(); - - private partial class TextComponent : CompositeDrawable, IHasText - { - private readonly OsuSpriteText text; - - public LocalisableString Text - { - get => text.Text; - set => text.Text = value; - } - - public TextComponent() - { - AutoSizeAxes = Axes.Both; - - InternalChild = new FillFlowContainer - { - Direction = FillDirection.Vertical, - Children = new[] - { - new OsuSpriteText - { - Text = BeatmapsetsStrings.ShowScoreboardHeaderspp.ToUpper(), - Font = OsuFont.Torus.With(size: 12, weight: FontWeight.Bold), - }, - text = new OsuSpriteText - { - Font = OsuFont.Torus.With(size: 16.8f, weight: FontWeight.Regular), - } - } - }; - } - } - } -} From a23dfbeab55bb3e195407bcf436444f150d5a355 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 5 Nov 2023 01:55:13 +0300 Subject: [PATCH 1305/4092] Revert "Abstractify PP counter logic from the "Triangles" implementation" This reverts commit daf4a03fd092ac84955a8f63fa3c45200a28cf04. --- .../HUD/GameplayPerformancePointsCounter.cs | 186 ------------------ .../Play/HUD/PerformancePointsCounter.cs | 181 ++++++++++++++++- 2 files changed, 175 insertions(+), 192 deletions(-) delete mode 100644 osu.Game/Screens/Play/HUD/GameplayPerformancePointsCounter.cs diff --git a/osu.Game/Screens/Play/HUD/GameplayPerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/GameplayPerformancePointsCounter.cs deleted file mode 100644 index a812a3e043..0000000000 --- a/osu.Game/Screens/Play/HUD/GameplayPerformancePointsCounter.cs +++ /dev/null @@ -1,186 +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 System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using JetBrains.Annotations; -using osu.Framework.Allocation; -using osu.Framework.Audio.Track; -using osu.Framework.Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Textures; -using osu.Framework.Localisation; -using osu.Game.Beatmaps; -using osu.Game.Graphics.UserInterface; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Scoring; -using osu.Game.Scoring; -using osu.Game.Skinning; - -namespace osu.Game.Screens.Play.HUD -{ - public abstract partial class GameplayPerformancePointsCounter : RollingCounter - { - protected override bool IsRollingProportional => true; - - protected override double RollingDuration => 1000; - - private const float alpha_when_invalid = 0.3f; - - [Resolved] - private ScoreProcessor scoreProcessor { get; set; } - - [Resolved] - private GameplayState gameplayState { get; set; } - - [CanBeNull] - private List timedAttributes; - - private readonly CancellationTokenSource loadCancellationSource = new CancellationTokenSource(); - - private JudgementResult lastJudgement; - private PerformanceCalculator performanceCalculator; - private ScoreInfo scoreInfo; - - protected GameplayPerformancePointsCounter() - { - Current.Value = DisplayedCount = 0; - } - - private Mod[] clonedMods; - - [BackgroundDependencyLoader] - private void load(BeatmapDifficultyCache difficultyCache) - { - DrawableCount.Alpha = alpha_when_invalid; - - if (gameplayState != null) - { - performanceCalculator = gameplayState.Ruleset.CreatePerformanceCalculator(); - clonedMods = gameplayState.Mods.Select(m => m.DeepClone()).ToArray(); - - scoreInfo = new ScoreInfo(gameplayState.Score.ScoreInfo.BeatmapInfo, gameplayState.Score.ScoreInfo.Ruleset) { Mods = clonedMods }; - - var gameplayWorkingBeatmap = new GameplayWorkingBeatmap(gameplayState.Beatmap); - difficultyCache.GetTimedDifficultyAttributesAsync(gameplayWorkingBeatmap, gameplayState.Ruleset, clonedMods, loadCancellationSource.Token) - .ContinueWith(task => Schedule(() => - { - timedAttributes = task.GetResultSafely(); - - IsValid = true; - - if (lastJudgement != null) - onJudgementChanged(lastJudgement); - }), TaskContinuationOptions.OnlyOnRanToCompletion); - } - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - if (scoreProcessor != null) - { - scoreProcessor.NewJudgement += onJudgementChanged; - scoreProcessor.JudgementReverted += onJudgementChanged; - } - - if (gameplayState?.LastJudgementResult.Value != null) - onJudgementChanged(gameplayState.LastJudgementResult.Value); - } - - private bool isValid; - - protected bool IsValid - { - set - { - if (value == isValid) - return; - - isValid = value; - DrawableCount.FadeTo(isValid ? 1 : alpha_when_invalid, 1000, Easing.OutQuint); - } - } - - private void onJudgementChanged(JudgementResult judgement) - { - lastJudgement = judgement; - - var attrib = getAttributeAtTime(judgement); - - if (gameplayState == null || attrib == null || scoreProcessor == null) - { - IsValid = false; - return; - } - - scoreProcessor.PopulateScore(scoreInfo); - Current.Value = (int)Math.Round(performanceCalculator?.Calculate(scoreInfo, attrib).Total ?? 0, MidpointRounding.AwayFromZero); - IsValid = true; - } - - [CanBeNull] - private DifficultyAttributes getAttributeAtTime(JudgementResult judgement) - { - if (timedAttributes == null || timedAttributes.Count == 0) - return null; - - int attribIndex = timedAttributes.BinarySearch(new TimedDifficultyAttributes(judgement.HitObject.GetEndTime(), null)); - if (attribIndex < 0) - attribIndex = ~attribIndex - 1; - - return timedAttributes[Math.Clamp(attribIndex, 0, timedAttributes.Count - 1)].Attributes; - } - - protected override LocalisableString FormatCount(int count) => count.ToString(@"D"); - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (scoreProcessor != null) - { - scoreProcessor.NewJudgement -= onJudgementChanged; - scoreProcessor.JudgementReverted -= onJudgementChanged; - } - - loadCancellationSource?.Cancel(); - } - - // TODO: This class shouldn't exist, but requires breaking changes to allow DifficultyCalculator to receive an IBeatmap. - private class GameplayWorkingBeatmap : WorkingBeatmap - { - private readonly IBeatmap gameplayBeatmap; - - public GameplayWorkingBeatmap(IBeatmap gameplayBeatmap) - : base(gameplayBeatmap.BeatmapInfo, null) - { - this.gameplayBeatmap = gameplayBeatmap; - } - - public override IBeatmap GetPlayableBeatmap(IRulesetInfo ruleset, IReadOnlyList mods, CancellationToken cancellationToken) - => gameplayBeatmap; - - protected override IBeatmap GetBeatmap() => gameplayBeatmap; - - public override Texture GetBackground() => throw new NotImplementedException(); - - protected override Track GetBeatmapTrack() => throw new NotImplementedException(); - - protected internal override ISkin GetSkin() => throw new NotImplementedException(); - - public override Stream GetStream(string storagePath) => throw new NotImplementedException(); - } - } -} diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index 76feae88af..82f116b4ae 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -1,31 +1,175 @@ // 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.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Audio.Track; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; using osu.Framework.Localisation; +using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Resources.Localisation.Web; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osu.Game.Skinning; using osuTK; namespace osu.Game.Screens.Play.HUD { - // todo: this should be renamed to DefaultPerformancePointsCounter, or probably even TrianglesPerformancePointsCounter once all other triangles components are renamed accordingly. - public partial class PerformancePointsCounter : GameplayPerformancePointsCounter, ISerialisableDrawable + public partial class PerformancePointsCounter : RollingCounter, ISerialisableDrawable { public bool UsesFixedAnchor { get; set; } - [BackgroundDependencyLoader] - private void load(OsuColour colours) + protected override bool IsRollingProportional => true; + + protected override double RollingDuration => 1000; + + private const float alpha_when_invalid = 0.3f; + + [Resolved] + private ScoreProcessor scoreProcessor { get; set; } + + [Resolved] + private GameplayState gameplayState { get; set; } + + [CanBeNull] + private List timedAttributes; + + private readonly CancellationTokenSource loadCancellationSource = new CancellationTokenSource(); + + private JudgementResult lastJudgement; + private PerformanceCalculator performanceCalculator; + private ScoreInfo scoreInfo; + + public PerformancePointsCounter() { - Colour = colours.BlueLighter; + Current.Value = DisplayedCount = 0; } - protected override IHasText CreateText() => new TextComponent(); + private Mod[] clonedMods; + + [BackgroundDependencyLoader] + private void load(OsuColour colours, BeatmapDifficultyCache difficultyCache) + { + Colour = colours.BlueLighter; + + if (gameplayState != null) + { + performanceCalculator = gameplayState.Ruleset.CreatePerformanceCalculator(); + clonedMods = gameplayState.Mods.Select(m => m.DeepClone()).ToArray(); + + scoreInfo = new ScoreInfo(gameplayState.Score.ScoreInfo.BeatmapInfo, gameplayState.Score.ScoreInfo.Ruleset) { Mods = clonedMods }; + + var gameplayWorkingBeatmap = new GameplayWorkingBeatmap(gameplayState.Beatmap); + difficultyCache.GetTimedDifficultyAttributesAsync(gameplayWorkingBeatmap, gameplayState.Ruleset, clonedMods, loadCancellationSource.Token) + .ContinueWith(task => Schedule(() => + { + timedAttributes = task.GetResultSafely(); + + IsValid = true; + + if (lastJudgement != null) + onJudgementChanged(lastJudgement); + }), TaskContinuationOptions.OnlyOnRanToCompletion); + } + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (scoreProcessor != null) + { + scoreProcessor.NewJudgement += onJudgementChanged; + scoreProcessor.JudgementReverted += onJudgementChanged; + } + + if (gameplayState?.LastJudgementResult.Value != null) + onJudgementChanged(gameplayState.LastJudgementResult.Value); + } + + private bool isValid; + + protected bool IsValid + { + set + { + if (value == isValid) + return; + + isValid = value; + DrawableCount.FadeTo(isValid ? 1 : alpha_when_invalid, 1000, Easing.OutQuint); + } + } + + private void onJudgementChanged(JudgementResult judgement) + { + lastJudgement = judgement; + + var attrib = getAttributeAtTime(judgement); + + if (gameplayState == null || attrib == null || scoreProcessor == null) + { + IsValid = false; + return; + } + + scoreProcessor.PopulateScore(scoreInfo); + Current.Value = (int)Math.Round(performanceCalculator?.Calculate(scoreInfo, attrib).Total ?? 0, MidpointRounding.AwayFromZero); + IsValid = true; + } + + [CanBeNull] + private DifficultyAttributes getAttributeAtTime(JudgementResult judgement) + { + if (timedAttributes == null || timedAttributes.Count == 0) + return null; + + int attribIndex = timedAttributes.BinarySearch(new TimedDifficultyAttributes(judgement.HitObject.GetEndTime(), null)); + if (attribIndex < 0) + attribIndex = ~attribIndex - 1; + + return timedAttributes[Math.Clamp(attribIndex, 0, timedAttributes.Count - 1)].Attributes; + } + + protected override LocalisableString FormatCount(int count) => count.ToString(@"D"); + + protected override IHasText CreateText() => new TextComponent + { + Alpha = alpha_when_invalid + }; + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (scoreProcessor != null) + { + scoreProcessor.NewJudgement -= onJudgementChanged; + scoreProcessor.JudgementReverted -= onJudgementChanged; + } + + loadCancellationSource?.Cancel(); + } private partial class TextComponent : CompositeDrawable, IHasText { @@ -65,5 +209,30 @@ namespace osu.Game.Screens.Play.HUD }; } } + + // TODO: This class shouldn't exist, but requires breaking changes to allow DifficultyCalculator to receive an IBeatmap. + private class GameplayWorkingBeatmap : WorkingBeatmap + { + private readonly IBeatmap gameplayBeatmap; + + public GameplayWorkingBeatmap(IBeatmap gameplayBeatmap) + : base(gameplayBeatmap.BeatmapInfo, null) + { + this.gameplayBeatmap = gameplayBeatmap; + } + + public override IBeatmap GetPlayableBeatmap(IRulesetInfo ruleset, IReadOnlyList mods, CancellationToken cancellationToken) + => gameplayBeatmap; + + protected override IBeatmap GetBeatmap() => gameplayBeatmap; + + public override Texture GetBackground() => throw new NotImplementedException(); + + protected override Track GetBeatmapTrack() => throw new NotImplementedException(); + + protected internal override ISkin GetSkin() => throw new NotImplementedException(); + + public override Stream GetStream(string storagePath) => throw new NotImplementedException(); + } } } From 6c3169a0ed6e9f0f9e1e7d668993e04861f63540 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 5 Nov 2023 01:56:45 +0300 Subject: [PATCH 1306/4092] Remove PP wedge and logic for gameplay layout --- osu.Game/Screens/Play/HUD/ArgonRightWedge.cs | 29 -------------------- osu.Game/Skinning/ArgonSkin.cs | 18 ------------ 2 files changed, 47 deletions(-) delete mode 100644 osu.Game/Screens/Play/HUD/ArgonRightWedge.cs diff --git a/osu.Game/Screens/Play/HUD/ArgonRightWedge.cs b/osu.Game/Screens/Play/HUD/ArgonRightWedge.cs deleted file mode 100644 index 6d01fecf13..0000000000 --- a/osu.Game/Screens/Play/HUD/ArgonRightWedge.cs +++ /dev/null @@ -1,29 +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.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Skinning; - -namespace osu.Game.Screens.Play.HUD -{ - public partial class ArgonRightWedge : CompositeDrawable, ISerialisableDrawable - { - public bool UsesFixedAnchor { get; set; } - - [BackgroundDependencyLoader] - private void load() - { - AutoSizeAxes = Axes.Both; - - InternalChild = new ArgonWedgePiece - { - WedgeWidth = { Value = 274 }, - WedgeHeight = { Value = 40 }, - InvertShear = { Value = true }, - EndOpacity = 0.5f, - }; - } - } -} diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index f850ae9cbd..3262812d24 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -120,8 +120,6 @@ namespace osu.Game.Skinning var accuracy = container.OfType().FirstOrDefault(); var comboWedge = container.OfType().FirstOrDefault(); var combo = container.OfType().FirstOrDefault(); - var rightWedge = container.OfType().FirstOrDefault(); - var ppCounter = container.OfType().FirstOrDefault(); var songProgress = container.OfType().FirstOrDefault(); var keyCounter = container.OfType().FirstOrDefault(); @@ -162,20 +160,6 @@ namespace osu.Game.Skinning } } - if (rightWedge != null) - { - rightWedge.Anchor = Anchor.TopRight; - rightWedge.Origin = Anchor.TopRight; - rightWedge.Position = new Vector2(180, 20); - - if (ppCounter != null) - { - ppCounter.Anchor = Anchor.TopRight; - ppCounter.Origin = Anchor.TopRight; - ppCounter.Position = new Vector2(rightWedge.X - 240, rightWedge.Y + 8); - } - } - var hitError = container.OfType().FirstOrDefault(); if (hitError != null) @@ -222,8 +206,6 @@ namespace osu.Game.Skinning new ArgonAccuracyCounter(), new ArgonComboWedge(), new ArgonComboCounter(), - new ArgonRightWedge(), - new ArgonPerformancePointsCounter(), new BarHitErrorMeter(), new BarHitErrorMeter(), new ArgonSongProgress(), From 77f5a4cdf53fca660a71140b84c9897439ddbf2e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 5 Nov 2023 02:01:26 +0300 Subject: [PATCH 1307/4092] Update skin deserialisation archive --- .../Archives/modified-argon-pro-20231026.osk | Bin 2066 -> 0 bytes .../Archives/modified-argon-pro-20231105.osk | Bin 0 -> 1899 bytes osu.Game.Tests/Skins/SkinDeserialisationTest.cs | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 osu.Game.Tests/Resources/Archives/modified-argon-pro-20231026.osk create mode 100644 osu.Game.Tests/Resources/Archives/modified-argon-pro-20231105.osk diff --git a/osu.Game.Tests/Resources/Archives/modified-argon-pro-20231026.osk b/osu.Game.Tests/Resources/Archives/modified-argon-pro-20231026.osk deleted file mode 100644 index e349c14d7403a13b92ae0b5fed2ee449273a199b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2066 zcmZ`)2T&7e77m0Enlyuiw!j8OTtozAAuN$1H30&F&;*VWFo7g=G@x`?nj)@LF(5@o ziUv%GA}CGiaIm3BmEKvJQe?fX=ba;W`~UOipZCvvZ@%}aF``j#>Tx_9IEfXkDYGelFnDBX>BKD|+L0&LpEeSmEZuGOG>$7d&!V{S7tZ7%*`+j4*zLTXPoRVl)r{c&!KkoM3Y^cf*rR zY%d#-iGE}fjuaI5h2&}ul02d*9J#(H7T=>GzocizKa+N9)r)qTaN&k@l79YdV)HR` zZiv|6>CK7Ebmu`KhDo$hfb$60KpkSZnJXdqcAwI>ymRhCE{zr$F+*Sa%sL?8usQq} zy&7F22$f;T>xE%IzXFvlmR0FTR4e6|p@6NTsa&~*9UsLPQ1fOkoX1O;ld~0KLX-(d zA&RVS=9M;#(8US8^_QY0eST*O)N&H_$Tq2A=GPfJ$Pod<2ksyhGJ zYgD{Dcf96(wzmTopV8%{C<8|;Bp`Fn4r=8oYrLYIBL1Oc*H0s8ZMsj;cN@-cBjSf$ zVqI3=8ebKP`M|JXz6-t}U7NKhbKoG%%p#i9#t#6bqUy1fc290tOR>_(2d2FL)bh~u zO<*{^$2nlj+Nanx#t{2Z97Q&P>o|q1L=FXtjHV%UZtp#*5+U*R6-6UW%p0nucyVJS z?ZS@V(Dy8sf%7uw_Okd%OT19$2BW4bbal|OeqQW5hyoV7}Z^JrM!F{CTcA$^BeUCNBWXW6a9r(;SZ^?rv-JWKgevj`9I?= zfC)O}9>^tHLBU+>n@x#h+@`vRha=qQZ>YW65$Rk4S$c=}y)+b~j1*igaIK4t3zhV{ z4t`gJ}2RdBRg>vakF9U(|Hg=;)PthU1jv7&X;C=Q;G?hnLXJ0TVwBKM2t`i#TO zS&VPiEiVgXj1>R*iT&Cp^Dtu;D#!JE6{YXFQ%i&Yg+>?y5696=lgA)Sy`+dd^`|lI=*Ylt4 zb`*7VLwv;m08ukvQUBc+N1;at_%~=IyFmW`I6oTWXgPh0p#l1z;yMaGs^T|rIQLOa T(MS;c4*&qMuL_%9_ZRP9@fu;D diff --git a/osu.Game.Tests/Resources/Archives/modified-argon-pro-20231105.osk b/osu.Game.Tests/Resources/Archives/modified-argon-pro-20231105.osk new file mode 100644 index 0000000000000000000000000000000000000000..16f89502eff0bc46df9922fec370ee1d804170f6 GIT binary patch literal 1899 zcmWIWW@Zs#U|`^2U=>IWU(2a$c^JquWCDpWFcfEJ=ILeTWuEQdo5y4*&{A!Gw1sPl z%y(f;r;?D8l}@c1Vtd%dzP&s7{PN3ry-NL4!aF>tXnFaYU#gL+{apE5!+TG>#M-L$ zizgfVX?3b5E|D`);hl1)bI*K-YnzT1HhnuR{6^sY4(_?fzVs-6-5KKe+J>9etx|rg zjG)EDr7qr^?{6wlsW-@<8S$Uztj4_1v=@t#?uGbnK47z=bZzo(gV~C)ChIm%Uj0Jj zn9UA@Z~Hg?IM(GODp|EM?)BA&2jBf!92Fiq*LK&-{&(U5C>}LoaOf%ndaxUad7&Q7 z%uCDH%PP*#Gwm_tI^-bW`g=~&Osm~FN$KihlhimKF^X;GbUrcPAW~59{krBG`~Lr{ z4m}q*f0|FVdpD0wkzCCs&j;b6_m7obKXPNo>^1(Y&e~=9xnAK5KQn>7ZqMN`A+F_z zn)nvaP`qtBXW!3)nUV=U4-?*Q?rOaCw|vz-c~l36?qoWA5f}^S6c`x9fDZIc%*^u$ zb#czmEy&MH%_}JeyY%hM`}wyF1diYT9qwY|?9y<_sI$E<@3o1qbM|iAkkBVHvL9W& z@#2lc;#L2D&vDQU@RyFAtT5|C!>7I7^|j~Ht#{fN%(m=?xLsCSyCL!Zy#JO{IqVZfoDx4sx10T08KL%XUT@v|qaQxInN8Sup`q1r zv(f8mHmUJ5Z)W{9ZYyX`i>b9#4B4#m+RJ+OjJ=PQ(w=cHH58g9;b^v#aV$GrthTgy>^{vX^<~pp_~0Hd#~SvR7WADnJ(QoPE5=_ zcC{e#jaOIQ~!qITd~rQOiRVTX)KldmQ%dB_Rh`6=7me;zH9BN zd~kQYbJfu|_A}p}Q}6wIc-yU}yZ@F2F1@wq=k)(wds_O#^O=kL&KSiwvu#P5_V|hq z`_1_q1E zzyL``1_onbM&Ji#g5dnT^x)K-)Z`Ly>d&3%pMBVX=ji*tBFCO5gqdwKdXbn?FJZlA z&0W1zcJ)x<;83n@hoijq{eCkcHIvhB#`nozzfABKR(!Jfs=bwS#ZukV)rDL-7nm$B zOMKaNb From 634795e45f12676c277be583bad61c29cc3ec22a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 5 Nov 2023 02:50:14 +0300 Subject: [PATCH 1308/4092] Adjust failing test scenes --- osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index 4e5db5d46e..ec7d92faac 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -96,7 +96,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Begin drag top left", () => { - InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.TopLeft - new Vector2(box1.ScreenSpaceDrawQuad.Width / 4)); + InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.TopLeft - new Vector2(box1.ScreenSpaceDrawQuad.Width / 4, box1.ScreenSpaceDrawQuad.Height / 8)); InputManager.PressButton(MouseButton.Left); }); @@ -146,8 +146,7 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("Add big black box", () => { - InputManager.MoveMouseTo(skinEditor.ChildrenOfType().First()); - InputManager.Click(MouseButton.Left); + skinEditor.ChildrenOfType().First(b => b.ChildrenOfType().FirstOrDefault() != null).TriggerClick(); }); AddStep("store box", () => From 48a75f6152705c5377b116f0dc2b6cf598036bb3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 5 Nov 2023 06:28:10 +0300 Subject: [PATCH 1309/4092] Fix resume cursor following gameplay cursor scale setting --- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index 555610a3b6..bcfefe856d 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -5,7 +5,6 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; @@ -14,7 +13,6 @@ using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Screens.Play; -using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.UI @@ -25,7 +23,6 @@ namespace osu.Game.Rulesets.Osu.UI private OsuClickToResumeCursor clickToResumeCursor; private OsuCursorContainer localCursorContainer; - private IBindable localCursorScale; public override CursorContainer LocalCursor => State.Value == Visibility.Visible ? localCursorContainer : null; @@ -49,13 +46,7 @@ namespace osu.Game.Rulesets.Osu.UI clickToResumeCursor.Appear(); if (localCursorContainer == null) - { Add(localCursorContainer = new OsuCursorContainer()); - - localCursorScale = new BindableFloat(); - localCursorScale.BindTo(localCursorContainer.CursorScale); - localCursorScale.BindValueChanged(scale => cursorScaleContainer.Scale = new Vector2(scale.NewValue), true); - } } protected override void PopOut() From 99405a2bbd38e999672d0fe89c83e56b845a0bdf Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 5 Nov 2023 06:28:34 +0300 Subject: [PATCH 1310/4092] Tidy up resume overlay test scene --- .../TestSceneResumeOverlay.cs | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs index 0bb27cff0f..81dc64cda9 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.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 NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; @@ -12,14 +13,15 @@ namespace osu.Game.Rulesets.Osu.Tests { public partial class TestSceneResumeOverlay : OsuManualInputManagerTestScene { - public TestSceneResumeOverlay() + private ManualOsuInputManager osuInputManager = null!; + private CursorContainer cursor = null!; + private ResumeOverlay resume = null!; + + private bool resumeFired; + + [SetUp] + public void SetUp() => Schedule(() => { - ManualOsuInputManager osuInputManager; - CursorContainer cursor; - ResumeOverlay resume; - - bool resumeFired = false; - Child = osuInputManager = new ManualOsuInputManager(new OsuRuleset().RulesetInfo) { Children = new Drawable[] @@ -32,8 +34,13 @@ namespace osu.Game.Rulesets.Osu.Tests } }; + resumeFired = false; resume.ResumeAction = () => resumeFired = true; + }); + [Test] + public void TestResume() + { AddStep("move mouse to center", () => InputManager.MoveMouseTo(ScreenSpaceDrawQuad.Centre)); AddStep("show", () => resume.Show()); From 9cb331641c39b9a1b54b192be3420e0db8b7ba68 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 5 Nov 2023 06:34:09 +0300 Subject: [PATCH 1311/4092] Rename container --- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index bcfefe856d..12506c83b9 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.UI { public partial class OsuResumeOverlay : ResumeOverlay { - private Container cursorScaleContainer; + private Container resumeCursorContainer; private OsuClickToResumeCursor clickToResumeCursor; private OsuCursorContainer localCursorContainer; @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.UI [BackgroundDependencyLoader] private void load() { - Add(cursorScaleContainer = new Container + Add(resumeCursorContainer = new Container { Child = clickToResumeCursor = new OsuClickToResumeCursor { ResumeRequested = Resume } }); @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.UI base.PopIn(); GameplayCursor.ActiveCursor.Hide(); - cursorScaleContainer.Position = ToLocalSpace(GameplayCursor.ActiveCursor.ScreenSpaceDrawQuad.Centre); + resumeCursorContainer.Position = ToLocalSpace(GameplayCursor.ActiveCursor.ScreenSpaceDrawQuad.Centre); clickToResumeCursor.Appear(); if (localCursorContainer == null) From 86fb33cb90b500dba57b2c199b381e7e8473b558 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Sun, 5 Nov 2023 12:40:06 +0100 Subject: [PATCH 1312/4092] Add disable taps checkbox to touch input settings --- osu.Android/OsuGameAndroid.cs | 4 +++ osu.Game/Configuration/OsuConfigManager.cs | 4 +++ osu.Game/Localisation/TouchSettingsStrings.cs | 24 ++++++++++++++ .../Settings/Sections/Input/TouchSettings.cs | 32 ++++++++++++------- 4 files changed, 53 insertions(+), 11 deletions(-) create mode 100644 osu.Game/Localisation/TouchSettingsStrings.cs diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index dea70e6b27..e4b934a387 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -11,6 +11,7 @@ using osu.Framework.Input.Handlers; using osu.Framework.Platform; using osu.Game; using osu.Game.Overlays.Settings; +using osu.Game.Overlays.Settings.Sections.Input; using osu.Game.Updater; using osu.Game.Utils; @@ -97,6 +98,9 @@ namespace osu.Android case AndroidJoystickHandler jh: return new AndroidJoystickSettings(jh); + case AndroidTouchHandler: + return new TouchSettings(handler); + default: return base.CreateSettingsSubsectionFor(handler); } diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 5d2d782063..339817985e 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -330,6 +330,10 @@ namespace osu.Game.Configuration ShowHealthDisplayWhenCantFail, FadePlayfieldWhenHealthLow, + + /// + /// Disables mouse buttons clicks and touchscreen taps during gameplay. + /// MouseDisableButtons, MouseDisableWheel, ConfineMouseMode, diff --git a/osu.Game/Localisation/TouchSettingsStrings.cs b/osu.Game/Localisation/TouchSettingsStrings.cs new file mode 100644 index 0000000000..785b333100 --- /dev/null +++ b/osu.Game/Localisation/TouchSettingsStrings.cs @@ -0,0 +1,24 @@ +// 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.Localisation; + +namespace osu.Game.Localisation +{ + public static class TouchSettingsStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.TouchSettings"; + + /// + /// "Touch" + /// + public static LocalisableString Touch => new TranslatableString(getKey(@"touch"), @"Touch"); + + /// + /// "Disable taps during gameplay" + /// + public static LocalisableString DisableTapsDuringGameplay => new TranslatableString(getKey(@"disable_taps_during_gameplay"), @"Disable taps during gameplay"); + + private static string getKey(string key) => $@"{prefix}:{key}"; + } +} \ No newline at end of file diff --git a/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs index 8d1b12d5b2..b1b1b59429 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs @@ -3,38 +3,48 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework; using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Input.Handlers.Touch; +using osu.Framework.Input.Handlers; using osu.Framework.Localisation; +using osu.Game.Configuration; using osu.Game.Localisation; namespace osu.Game.Overlays.Settings.Sections.Input { + /// + /// Touch input settings subsection common to all touch handlers (even on different platforms). + /// public partial class TouchSettings : SettingsSubsection { - private readonly TouchHandler handler; + private readonly InputHandler handler; - public TouchSettings(TouchHandler handler) + protected override LocalisableString Header => TouchSettingsStrings.Touch; + + public TouchSettings(InputHandler handler) { this.handler = handler; } [BackgroundDependencyLoader] - private void load() + private void load(OsuConfigManager osuConfig) { - Children = new Drawable[] + if (!RuntimeInfo.IsMobile) // don't allow disabling the only input method (touch) on mobile. { - new SettingsCheckbox + Add(new SettingsCheckbox { LabelText = CommonStrings.Enabled, Current = handler.Enabled - }, - }; + }); + } + + Add(new SettingsCheckbox + { + LabelText = TouchSettingsStrings.DisableTapsDuringGameplay, + Current = osuConfig.GetBindable(OsuSetting.MouseDisableButtons) + }); } public override IEnumerable FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { @"touchscreen" }); - - protected override LocalisableString Header => handler.Description; } } From fa1d1df594d3f5307e01207952c4eb6be7ee3494 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Sun, 5 Nov 2023 12:43:14 +0100 Subject: [PATCH 1313/4092] Rename mouse button string to `Disable clicks during gameplay` --- osu.Android/AndroidMouseSettings.cs | 2 +- osu.Game/Localisation/MouseSettingsStrings.cs | 6 +++--- osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs | 2 +- osu.Game/Screens/Play/PlayerSettings/InputSettings.cs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Android/AndroidMouseSettings.cs b/osu.Android/AndroidMouseSettings.cs index d6d7750448..fd01b11164 100644 --- a/osu.Android/AndroidMouseSettings.cs +++ b/osu.Android/AndroidMouseSettings.cs @@ -70,7 +70,7 @@ namespace osu.Android }, new SettingsCheckbox { - LabelText = MouseSettingsStrings.DisableMouseButtons, + LabelText = MouseSettingsStrings.DisableClicksDuringGameplay, Current = osuConfig.GetBindable(OsuSetting.MouseDisableButtons), }, }); diff --git a/osu.Game/Localisation/MouseSettingsStrings.cs b/osu.Game/Localisation/MouseSettingsStrings.cs index 1772f03b29..e61af07364 100644 --- a/osu.Game/Localisation/MouseSettingsStrings.cs +++ b/osu.Game/Localisation/MouseSettingsStrings.cs @@ -40,14 +40,14 @@ namespace osu.Game.Localisation public static LocalisableString DisableMouseWheelVolumeAdjust => new TranslatableString(getKey(@"disable_mouse_wheel_volume_adjust"), @"Disable mouse wheel adjusting volume during gameplay"); /// - /// "Volume can still be adjusted using the mouse wheel by holding "Alt"" + /// "Volume can still be adjusted using the mouse wheel by holding "Alt"" /// public static LocalisableString DisableMouseWheelVolumeAdjustTooltip => new TranslatableString(getKey(@"disable_mouse_wheel_volume_adjust_tooltip"), @"Volume can still be adjusted using the mouse wheel by holding ""Alt"""); /// - /// "Disable mouse buttons during gameplay" + /// "Disable clicks during gameplay" /// - public static LocalisableString DisableMouseButtons => new TranslatableString(getKey(@"disable_mouse_buttons"), @"Disable mouse buttons during gameplay"); + public static LocalisableString DisableClicksDuringGameplay => new TranslatableString(getKey(@"disable_clicks"), @"Disable clicks during gameplay"); /// /// "Enable high precision mouse to adjust sensitivity" diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index dfaeafbf5d..6bf06f4f98 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -75,7 +75,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input }, new SettingsCheckbox { - LabelText = MouseSettingsStrings.DisableMouseButtons, + LabelText = MouseSettingsStrings.DisableClicksDuringGameplay, Current = osuConfig.GetBindable(OsuSetting.MouseDisableButtons) }, }; diff --git a/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs b/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs index cf261ba49b..4076782ee1 100644 --- a/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs @@ -19,7 +19,7 @@ namespace osu.Game.Screens.Play.PlayerSettings { mouseButtonsCheckbox = new PlayerCheckbox { - LabelText = MouseSettingsStrings.DisableMouseButtons + LabelText = MouseSettingsStrings.DisableClicksDuringGameplay } }; } From 0d8bfedf5d3693809e76471b9feb47ebf140e40b Mon Sep 17 00:00:00 2001 From: Susko3 Date: Sun, 5 Nov 2023 12:44:22 +0100 Subject: [PATCH 1314/4092] Rename popup/binding string to `Toggle gameplay clicks/taps` --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- osu.Game/Input/Bindings/GlobalActionContainer.cs | 2 +- osu.Game/Localisation/GlobalActionKeyBindingStrings.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 339817985e..e3f950ce2c 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -246,7 +246,7 @@ namespace osu.Game.Configuration ), new TrackedSetting(OsuSetting.MouseDisableButtons, disabledState => new SettingDescription( rawValue: !disabledState, - name: GlobalActionKeyBindingStrings.ToggleGameplayMouseButtons, + name: GlobalActionKeyBindingStrings.ToggleGameplayClicksTaps, value: disabledState ? CommonStrings.Disabled.ToLower() : CommonStrings.Enabled.ToLower(), shortcut: LookupKeyBindings(GlobalAction.ToggleGameplayMouseButtons)) ), diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 947cd5f54f..b8163cc3b1 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -237,7 +237,7 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.TakeScreenshot))] TakeScreenshot, - [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleGameplayMouseButtons))] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleGameplayClicksTaps))] ToggleGameplayMouseButtons, [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.Back))] diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index 8356c480dd..1bbbbdc3bc 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -70,9 +70,9 @@ namespace osu.Game.Localisation public static LocalisableString TakeScreenshot => new TranslatableString(getKey(@"take_screenshot"), @"Take screenshot"); /// - /// "Toggle gameplay mouse buttons" + /// "Toggle gameplay clicks/taps" /// - public static LocalisableString ToggleGameplayMouseButtons => new TranslatableString(getKey(@"toggle_gameplay_mouse_buttons"), @"Toggle gameplay mouse buttons"); + public static LocalisableString ToggleGameplayClicksTaps => new TranslatableString(getKey(@"toggle_gameplay_clicks_taps"), @"Toggle gameplay clicks/taps"); /// /// "Back" From 9947897c5f83c7298c381015a95673902486b52d Mon Sep 17 00:00:00 2001 From: Susko3 Date: Sun, 5 Nov 2023 12:53:40 +0100 Subject: [PATCH 1315/4092] Use appropriate clicks/taps text in player loader input settings --- osu.Game/Screens/Play/PlayerSettings/InputSettings.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs b/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs index 4076782ee1..f6b0cddcf1 100644 --- a/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/InputSettings.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.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Configuration; @@ -19,7 +20,8 @@ namespace osu.Game.Screens.Play.PlayerSettings { mouseButtonsCheckbox = new PlayerCheckbox { - LabelText = MouseSettingsStrings.DisableClicksDuringGameplay + // TODO: change to touchscreen detection once https://github.com/ppy/osu/pull/25348 makes it in + LabelText = RuntimeInfo.IsDesktop ? MouseSettingsStrings.DisableClicksDuringGameplay : TouchSettingsStrings.DisableTapsDuringGameplay } }; } From 277cf7dc127cfacaf62f51bc88e0a0a3c1474382 Mon Sep 17 00:00:00 2001 From: ratinfx Date: Sun, 5 Nov 2023 18:26:51 +0100 Subject: [PATCH 1316/4092] Ensure every SelectedItem is alive and has Blueprint --- .../Components/EditorBlueprintContainer.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs index ad0e8b124b..60959ca27a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -51,6 +52,10 @@ namespace osu.Game.Screens.Edit.Compose.Components Beatmap.HitObjectAdded += AddBlueprintFor; Beatmap.HitObjectRemoved += RemoveBlueprintFor; + // This makes sure HitObjects will have active Blueprints ready to display + // after clicking on an Editor Timestamp/Link + Beatmap.SelectedHitObjects.CollectionChanged += SetHitObjectsAlive; + if (Composer != null) { foreach (var obj in Composer.HitObjects) @@ -144,6 +149,15 @@ namespace osu.Game.Screens.Edit.Compose.Components SelectedItems.AddRange(Beatmap.HitObjects.Except(SelectedItems).ToArray()); } + protected void SetHitObjectsAlive(object sender, NotifyCollectionChangedEventArgs e) + { + if (e == null || e.Action != NotifyCollectionChangedAction.Add || e.NewItems == null) + return; + + foreach (HitObject item in e.NewItems) + Composer.Playfield.SetKeepAlive(item, true); + } + protected override void OnBlueprintSelected(SelectionBlueprint blueprint) { base.OnBlueprintSelected(blueprint); @@ -166,6 +180,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { Beatmap.HitObjectAdded -= AddBlueprintFor; Beatmap.HitObjectRemoved -= RemoveBlueprintFor; + Beatmap.SelectedHitObjects.CollectionChanged -= SetHitObjectsAlive; } usageEventBuffer?.Dispose(); From e2b07628fb5719540ea3d1405de25a83b469b8da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Nov 2023 15:24:12 +0900 Subject: [PATCH 1317/4092] Add player name skin component 3 minute implementation. Addresses https://github.com/ppy/osu/discussions/25340. --- osu.Game/Skinning/Components/PlayerName.cs | 40 ++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 osu.Game/Skinning/Components/PlayerName.cs diff --git a/osu.Game/Skinning/Components/PlayerName.cs b/osu.Game/Skinning/Components/PlayerName.cs new file mode 100644 index 0000000000..34ace53d47 --- /dev/null +++ b/osu.Game/Skinning/Components/PlayerName.cs @@ -0,0 +1,40 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.Sprites; +using osu.Game.Screens.Play; + +namespace osu.Game.Skinning.Components +{ + [UsedImplicitly] + public partial class PlayerName : FontAdjustableSkinComponent + { + private readonly OsuSpriteText text; + + public PlayerName() + { + AutoSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + text = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + } + }; + } + + [BackgroundDependencyLoader] + private void load(GameplayState gameplayState) + { + text.Text = gameplayState.Score.ScoreInfo.User.Username; + } + + protected override void SetFont(FontUsage font) => text.Font = font.With(size: 40); + } +} From 11bd801795f5a838cfc478e486586476fed2eb30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 6 Nov 2023 07:44:25 +0100 Subject: [PATCH 1318/4092] Use more intelligent default for `TouchInputActive` --- osu.Game/Configuration/SessionStatics.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs index 0fc2076a7e..8f0a60b23d 100644 --- a/osu.Game/Configuration/SessionStatics.cs +++ b/osu.Game/Configuration/SessionStatics.cs @@ -3,6 +3,7 @@ #nullable disable +using osu.Framework; using osu.Game.Graphics.UserInterface; using osu.Game.Input; using osu.Game.Online.API.Requests.Responses; @@ -25,7 +26,7 @@ namespace osu.Game.Configuration SetDefault(Static.LastHoverSoundPlaybackTime, (double?)null); SetDefault(Static.LastModSelectPanelSamplePlaybackTime, (double?)null); SetDefault(Static.SeasonalBackgrounds, null); - SetDefault(Static.TouchInputActive, false); + SetDefault(Static.TouchInputActive, RuntimeInfo.IsMobile); } /// From 3c72c5bccd21b4127ceaae9346ec04e6b059db9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 6 Nov 2023 07:48:09 +0100 Subject: [PATCH 1319/4092] Steer touch input flag via bindable rather than config manager --- osu.Game/Input/TouchInputInterceptor.cs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/osu.Game/Input/TouchInputInterceptor.cs b/osu.Game/Input/TouchInputInterceptor.cs index 4e6177c70c..b566113a2a 100644 --- a/osu.Game/Input/TouchInputInterceptor.cs +++ b/osu.Game/Input/TouchInputInterceptor.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; using osu.Framework.Input.Events; @@ -22,12 +23,17 @@ namespace osu.Game.Input { public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; - [Resolved] - private SessionStatics statics { get; set; } = null!; + private readonly BindableBool touchInputActive = new BindableBool(); + + [BackgroundDependencyLoader] + private void load(SessionStatics statics) + { + statics.BindWith(Static.TouchInputActive, touchInputActive); + } protected override bool Handle(UIEvent e) { - bool touchInputWasActive = statics.Get(Static.TouchInputActive); + bool touchInputWasActive = touchInputActive.Value; switch (e) { @@ -36,7 +42,7 @@ namespace osu.Game.Input { if (touchInputWasActive) Logger.Log($@"Touch input deactivated due to received {e.GetType().ReadableName()}", LoggingTarget.Input); - statics.SetValue(Static.TouchInputActive, false); + touchInputActive.Value = false; } break; @@ -44,7 +50,7 @@ namespace osu.Game.Input case TouchEvent: if (!touchInputWasActive) Logger.Log($@"Touch input activated due to received {e.GetType().ReadableName()}", LoggingTarget.Input); - statics.SetValue(Static.TouchInputActive, true); + touchInputActive.Value = true; break; case KeyDownEvent keyDown: @@ -59,10 +65,8 @@ namespace osu.Game.Input [Conditional("TOUCH_INPUT_DEBUG")] private void debugToggleTouchInputActive() { - bool oldValue = statics.Get(Static.TouchInputActive); - bool newValue = !oldValue; - Logger.Log($@"Debug-toggling touch input to {(newValue ? @"active" : @"inactive")}", LoggingTarget.Input, LogLevel.Debug); - statics.SetValue(Static.TouchInputActive, newValue); + Logger.Log($@"Debug-toggling touch input to {(touchInputActive.Value ? @"inactive" : @"active")}", LoggingTarget.Input, LogLevel.Debug); + touchInputActive.Toggle(); } } } From adb9ca5a13cb920c6554457392a9f715b85038ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 6 Nov 2023 07:56:09 +0100 Subject: [PATCH 1320/4092] Add failing test coverage for incorrect treatment of TD in mod presets --- .../UserInterface/TestSceneModPresetColumn.cs | 13 +++++++++++-- .../Visual/UserInterface/TestSceneModPresetPanel.cs | 8 ++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs index 1779b240cc..b7c1428397 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs @@ -167,7 +167,7 @@ namespace osu.Game.Tests.Visual.UserInterface } [Test] - public void TestAddingFlow() + public void TestAddingFlow([Values] bool withSystemModActive) { ModPresetColumn modPresetColumn = null!; @@ -181,7 +181,13 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("items loaded", () => modPresetColumn.IsLoaded && modPresetColumn.ItemsLoaded); AddAssert("add preset button disabled", () => !this.ChildrenOfType().Single().Enabled.Value); - AddStep("set mods", () => SelectedMods.Value = new Mod[] { new OsuModDaycore(), new OsuModClassic() }); + AddStep("set mods", () => + { + var newMods = new Mod[] { new OsuModDaycore(), new OsuModClassic() }; + if (withSystemModActive) + newMods = newMods.Append(new OsuModTouchDevice()).ToArray(); + SelectedMods.Value = newMods; + }); AddAssert("add preset button enabled", () => this.ChildrenOfType().Single().Enabled.Value); AddStep("click add preset button", () => @@ -209,6 +215,9 @@ namespace osu.Game.Tests.Visual.UserInterface }); AddUntilStep("popover closed", () => !this.ChildrenOfType().Any()); AddUntilStep("preset creation occurred", () => this.ChildrenOfType().Count() == 4); + AddAssert("preset has correct mods", + () => this.ChildrenOfType().Single(panel => panel.Preset.Value.Name == "new preset").Preset.Value.Mods, + () => Has.Count.EqualTo(2)); AddStep("click add preset button", () => { diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs index 35e352534b..c79cbd3691 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs @@ -86,6 +86,10 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("set mods to HD+HR+DT", () => SelectedMods.Value = new Mod[] { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime() }); AddAssert("panel is not active", () => !panel.AsNonNull().Active.Value); + + // system mods are not included in presets. + AddStep("set mods to HR+DT+TD", () => SelectedMods.Value = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime(), new OsuModTouchDevice() }); + AddAssert("panel is active", () => panel.AsNonNull().Active.Value); } [Test] @@ -113,6 +117,10 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("set customised mod", () => SelectedMods.Value = new[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } }); AddStep("activate panel", () => panel.AsNonNull().TriggerClick()); assertSelectedModsEquivalentTo(new Mod[] { new OsuModHardRock(), new OsuModDoubleTime { SpeedChange = { Value = 1.5 } } }); + + AddStep("set system mod", () => SelectedMods.Value = new[] { new OsuModTouchDevice() }); + AddStep("activate panel", () => panel.AsNonNull().TriggerClick()); + assertSelectedModsEquivalentTo(new Mod[] { new OsuModTouchDevice(), new OsuModHardRock(), new OsuModDoubleTime { SpeedChange = { Value = 1.5 } } }); } private void assertSelectedModsEquivalentTo(IEnumerable mods) From 7ba07ab5305dc0be39bddc06967d826e36bfeea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 6 Nov 2023 08:05:42 +0100 Subject: [PATCH 1321/4092] Add protections against handling system mods in mod presets --- osu.Game/Overlays/Mods/AddPresetPopover.cs | 2 +- osu.Game/Overlays/Mods/EditPresetPopover.cs | 4 ++-- osu.Game/Overlays/Mods/ModPresetPanel.cs | 14 +++++--------- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/osu.Game/Overlays/Mods/AddPresetPopover.cs b/osu.Game/Overlays/Mods/AddPresetPopover.cs index 638592a9b5..b782b5d6ba 100644 --- a/osu.Game/Overlays/Mods/AddPresetPopover.cs +++ b/osu.Game/Overlays/Mods/AddPresetPopover.cs @@ -115,7 +115,7 @@ namespace osu.Game.Overlays.Mods { Name = nameTextBox.Current.Value, Description = descriptionTextBox.Current.Value, - Mods = selectedMods.Value.ToArray(), + Mods = selectedMods.Value.Where(mod => mod.Type != ModType.System).ToArray(), Ruleset = r.Find(ruleset.Value.ShortName)! })); diff --git a/osu.Game/Overlays/Mods/EditPresetPopover.cs b/osu.Game/Overlays/Mods/EditPresetPopover.cs index 571021b0f8..8bce57c96a 100644 --- a/osu.Game/Overlays/Mods/EditPresetPopover.cs +++ b/osu.Game/Overlays/Mods/EditPresetPopover.cs @@ -153,7 +153,7 @@ namespace osu.Game.Overlays.Mods private void useCurrentMods() { - saveableMods = selectedMods.Value.ToHashSet(); + saveableMods = selectedMods.Value.Where(mod => mod.Type != ModType.System).ToHashSet(); updateState(); } @@ -168,7 +168,7 @@ namespace osu.Game.Overlays.Mods if (!selectedMods.Value.Any()) return false; - return !saveableMods.SetEquals(selectedMods.Value); + return !saveableMods.SetEquals(selectedMods.Value.Where(mod => mod.Type != ModType.System)); } private void save() diff --git a/osu.Game/Overlays/Mods/ModPresetPanel.cs b/osu.Game/Overlays/Mods/ModPresetPanel.cs index 00f6e36972..3982abeba7 100644 --- a/osu.Game/Overlays/Mods/ModPresetPanel.cs +++ b/osu.Game/Overlays/Mods/ModPresetPanel.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.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -56,17 +55,14 @@ namespace osu.Game.Overlays.Mods protected override void Select() { - // if the preset is not active at the point of the user click, then set the mods using the preset directly, discarding any previous selections, - // which will also have the side effect of activating the preset (see `updateActiveState()`). - selectedMods.Value = Preset.Value.Mods.ToArray(); + var selectedSystemMods = selectedMods.Value.Where(mod => mod.Type == ModType.System); + // will also have the side effect of activating the preset (see `updateActiveState()`). + selectedMods.Value = Preset.Value.Mods.Concat(selectedSystemMods).ToArray(); } protected override void Deselect() { - // if the preset is active when the user has clicked it, then it means that the set of active mods is exactly equal to the set of mods in the preset - // (there are no other active mods than what the preset specifies, and the mod settings match exactly). - // therefore it's safe to just clear selected mods, since it will have the effect of toggling the preset off. - selectedMods.Value = Array.Empty(); + selectedMods.Value = selectedMods.Value.Except(Preset.Value.Mods).ToArray(); } private void selectedModsChanged() @@ -79,7 +75,7 @@ namespace osu.Game.Overlays.Mods private void updateActiveState() { - Active.Value = new HashSet(Preset.Value.Mods).SetEquals(selectedMods.Value); + Active.Value = new HashSet(Preset.Value.Mods).SetEquals(selectedMods.Value.Where(mod => mod.Type != ModType.System)); } #region Filtering support From 40d081ee2de0940600f94ff126259b019622483b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Nov 2023 16:05:50 +0900 Subject: [PATCH 1322/4092] Add note about `Width` requirement in `UserGridPanel` --- osu.Game/Users/UserGridPanel.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Users/UserGridPanel.cs b/osu.Game/Users/UserGridPanel.cs index f4ec1475b1..aac2315b2f 100644 --- a/osu.Game/Users/UserGridPanel.cs +++ b/osu.Game/Users/UserGridPanel.cs @@ -10,6 +10,10 @@ using osuTK; namespace osu.Game.Users { + /// + /// A user "card", commonly used in a grid layout or in popovers. + /// Comes with a preset height, but width must be specified. + /// public partial class UserGridPanel : ExtendedUserPanel { private const int margin = 10; From 1f0b914251bc61d82bf9e3db207e75399a600806 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Nov 2023 16:18:33 +0900 Subject: [PATCH 1323/4092] Add skin editor dropdown items to reset rotation and scale --- .../Overlays/SkinEditor/SkinSelectionHandler.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index c4e2c4c6bd..df73b15101 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -198,12 +198,26 @@ namespace osu.Game.Overlays.SkinEditor Items = createAnchorItems((d, o) => ((Drawable)d).Origin == o, applyOrigins).ToArray() }; + yield return new EditorMenuItemSpacer(); + yield return new OsuMenuItem("Reset position", MenuItemType.Standard, () => { foreach (var blueprint in SelectedBlueprints) ((Drawable)blueprint.Item).Position = Vector2.Zero; }); + yield return new OsuMenuItem("Reset rotation", MenuItemType.Standard, () => + { + foreach (var blueprint in SelectedBlueprints) + ((Drawable)blueprint.Item).Rotation = 0; + }); + + yield return new OsuMenuItem("Reset scale", MenuItemType.Standard, () => + { + foreach (var blueprint in SelectedBlueprints) + ((Drawable)blueprint.Item).Scale = Vector2.One; + }); + yield return new EditorMenuItemSpacer(); yield return new OsuMenuItem("Bring to front", MenuItemType.Standard, () => skinEditor.BringSelectionToFront()); From 0915ac8891f1a5036a325d95b870d29c0daeb733 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Nov 2023 16:32:12 +0900 Subject: [PATCH 1324/4092] Use left aligned text for non-rotate key counter --- osu.Game/Screens/Play/ArgonKeyCounter.cs | 28 +++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/ArgonKeyCounter.cs b/osu.Game/Screens/Play/ArgonKeyCounter.cs index bb5fe0daf2..874fcde329 100644 --- a/osu.Game/Screens/Play/ArgonKeyCounter.cs +++ b/osu.Game/Screens/Play/ArgonKeyCounter.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 System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -18,6 +19,8 @@ namespace osu.Game.Screens.Play private OsuSpriteText keyNameText = null!; private OsuSpriteText countText = null!; + private UprightAspectMaintainingContainer uprightContainer = null!; + // These values were taken from Figma private const float line_height = 3; private const float name_font_size = 10; @@ -53,7 +56,7 @@ namespace osu.Game.Screens.Play Padding = new MarginPadding { Top = line_height * scale_factor + indicator_press_offset }, Children = new Drawable[] { - new UprightAspectMaintainingContainer + uprightContainer = new UprightAspectMaintainingContainer { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, @@ -62,16 +65,16 @@ namespace osu.Game.Screens.Play { keyNameText = new OsuSpriteText { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, Font = OsuFont.Torus.With(size: name_font_size * scale_factor, weight: FontWeight.Bold), Colour = colours.Blue0, Text = Trigger.Name }, countText = new OsuSpriteText { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, Font = OsuFont.Torus.With(size: count_font_size * scale_factor, weight: FontWeight.Bold), }, } @@ -93,6 +96,21 @@ namespace osu.Game.Screens.Play CountPresses.BindValueChanged(e => countText.Text = e.NewValue.ToString(@"#,0"), true); } + protected override void Update() + { + base.Update(); + + const float allowance = 6; + float absRotation = Math.Abs(uprightContainer.Rotation) % 180; + bool isRotated = absRotation > allowance && absRotation < (180 - allowance); + + keyNameText.Anchor = + keyNameText.Origin = isRotated ? Anchor.TopCentre : Anchor.TopLeft; + + countText.Anchor = + countText.Origin = isRotated ? Anchor.BottomCentre : Anchor.BottomLeft; + } + protected override void Activate(bool forwardPlayback = true) { base.Activate(forwardPlayback); From b45d8c785cd662796c6098f72aadf589b99161e4 Mon Sep 17 00:00:00 2001 From: Joshua Hegedus Date: Mon, 6 Nov 2023 08:38:34 +0100 Subject: [PATCH 1325/4092] fixed review findings --- .../Online/TestSceneUserClickableAvatar.cs | 26 +++++++++++++++++++ osu.Game/Users/Drawables/ClickableAvatar.cs | 21 +++++++++++++++ osu.Game/Users/Drawables/UpdateableAvatar.cs | 1 + 3 files changed, 48 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs b/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs index 13f559ac09..a24581f7ed 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs @@ -78,6 +78,8 @@ namespace osu.Game.Tests.Visual.Online Type = EdgeEffectType.Shadow, Radius = 1, Colour = Color4.Black.Opacity(0.2f), }, }, + new ClickableAvatar(), + new ClickableAvatar(), }, }; }); @@ -120,6 +122,30 @@ namespace osu.Game.Tests.Visual.Online AddWaitStep("wait for tooltip to show", 5); AddStep("Hover out", () => InputManager.MoveMouseTo(new Vector2(0))); AddWaitStep("wait for tooltip to hide", 3); + + AddStep($"click null user {4}. {nameof(ClickableAvatar)}", () => + { + var targets = this.ChildrenOfType().ToList(); + if (targets.Count < 4) + return; + + InputManager.MoveMouseTo(targets[3]); + }); + AddWaitStep("wait for tooltip to show", 5); + AddStep("Hover out", () => InputManager.MoveMouseTo(new Vector2(0))); + AddWaitStep("wait for tooltip to hide", 3); + + AddStep($"click null user {5}. {nameof(ClickableAvatar)}", () => + { + var targets = this.ChildrenOfType().ToList(); + if (targets.Count < 5) + return; + + InputManager.MoveMouseTo(targets[4]); + }); + AddWaitStep("wait for tooltip to show", 5); + AddStep("Hover out", () => InputManager.MoveMouseTo(new Vector2(0))); + AddWaitStep("wait for tooltip to hide", 3); } } } diff --git a/osu.Game/Users/Drawables/ClickableAvatar.cs b/osu.Game/Users/Drawables/ClickableAvatar.cs index d6c6afba0b..0ed9f56cc7 100644 --- a/osu.Game/Users/Drawables/ClickableAvatar.cs +++ b/osu.Game/Users/Drawables/ClickableAvatar.cs @@ -1,12 +1,15 @@ // 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.Cursor; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics.Containers; +using osu.Game.Localisation; using osu.Game.Online.API.Requests.Responses; using osuTK; @@ -21,6 +24,24 @@ namespace osu.Game.Users.Drawables Width = 300 }; + public override LocalisableString TooltipText + { + get + { + if (!Enabled.Value) + return string.Empty; + + return ShowUsernameTooltip ? (user?.Username ?? string.Empty) : ContextMenuStrings.ViewProfile; + } + set => throw new NotSupportedException(); + } + + /// + /// By default, the tooltip will show "view profile" as avatars are usually displayed next to a username. + /// Setting this to true exposes the username via tooltip for special cases where this is not true. + /// + public bool ShowUsernameTooltip { get; set; } + private readonly APIUser? user; [Resolved] diff --git a/osu.Game/Users/Drawables/UpdateableAvatar.cs b/osu.Game/Users/Drawables/UpdateableAvatar.cs index 58b3646995..711e7ab799 100644 --- a/osu.Game/Users/Drawables/UpdateableAvatar.cs +++ b/osu.Game/Users/Drawables/UpdateableAvatar.cs @@ -75,6 +75,7 @@ namespace osu.Game.Users.Drawables return new ClickableAvatar(user) { RelativeSizeAxes = Axes.Both, + ShowUsernameTooltip = showUsernameTooltip, }; } else From 69d6feb5a8b9137aaf74956945b897d7741fe3a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 6 Nov 2023 08:51:31 +0100 Subject: [PATCH 1326/4092] Add test coverage for player name skin component --- .../Archives/modified-argon-20231106.osk | Bin 0 -> 1397 bytes osu.Game.Tests/Skins/SkinDeserialisationTest.cs | 4 +++- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Resources/Archives/modified-argon-20231106.osk diff --git a/osu.Game.Tests/Resources/Archives/modified-argon-20231106.osk b/osu.Game.Tests/Resources/Archives/modified-argon-20231106.osk new file mode 100644 index 0000000000000000000000000000000000000000..70c4ff64d74b2efe0cd630812a41186f11f0467d GIT binary patch literal 1397 zcmWIWW@Zs#U|`^2`0JV$epO>m++rZ_AXtQfp*TA;PcJhsQ?+L!*C7W1*5ALomQQ8$ zRN?Tl(ze##D%fqh-IMQq+0!D$AGu<6llCVZ$xCSax#ym1WAgEgE7h`dx2y;i@;>BY zo8cj)v2)g*`ldCh-M58r@f+Vz{8uC{Cv{oK{FVC3rel?2Ts@D>x5@}wOkC=+bLX2# zi%I)u9S#Zke{oLIYNO+eVy33HxdyzJI$n0@t(Ea^#ZoElIZyPgx-5&|%=KzDx<5ld z0L3F)-r3fd0zJ_S#JoTZ@z} ze0N~b1p^}kqe~$dPMq~W>2uo0m(fVq$7E`WL)4t^AYt!M-445)K5zQ`>GWrXNAoI= zs&-bY&g@j3`Ll9fW#-R-;;(la%U-air$2j|`fTZI%ceC9A4^0MCV9ndWk7YG!T++N zdL{;jQ^E`kVnFx#CT8Y>|EA=N9DWrRJ3sgB|*Ix^Mn12Z6Ttf4I7hBN|o;ho4ex z-L`VowiPOST_$cdYu=l>w>j;)O4yP5_qQxMJ?}baG`(!MR5;IL{J8d;?B?ydr?dJy zV>de-V7EN}E&KJg8$G5%5(bIy^t-E9ZxP@3IfwJuWw)uf-bXThGf`n!`KB{rW^I|_ zw&MorD=#SfFv%opnFbu+#r;0weuId6<)zKt{>PDj^TEUY6Xrf< zdeZ6Bno%xiHDke}TPZuh)p_#HM-6-)cAcKz?=l;;-K z%xlUHocj7LJw}(e`%h%n?Mk~#%S9I2Fsqf=+!M(Sna}R|-TX=9TZM!t#}6!ykUsz5 z4MKBujhA8uxmWH z{Ed0fHl5y6TVDPT*sAo_zTad2^35DV6K;veG_6mT(_1pLTP^R$ikkwg%bq-Mu0ATt zzvF?G_R_tg5ghghr>=aySyZ5Vfu4>@DCeJ+i%ciNt+cPL*)OUb!SLwd_e1&Z^~u6z z<+GI!|KD9;zq^3R#NBHKPhwL{Rw)Pj$z}1$>{SzD3N(G)s#{J9ExqX_-RCf$+sAj_ zpZ@lG$&Fd;#Z!J|2I=fuZx(2I!20>Ft^XKNldR0tq$)LFQq=@vJ|GUrNvurEOwCC_ z57ZD2Ur>zzPW&%bg5eD4l4bWH!XarHP z(g$4^dgg}eVPI%H3DpJ9>gZb0^BqEKCL^v~h;9aYKp@PJVL=a;0B=?{kUR?zegx8a IKotxO0R77}LjV8( literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs index 98008a003d..67f9d20fce 100644 --- a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs +++ b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs @@ -54,7 +54,9 @@ namespace osu.Game.Tests.Skins // Covers key counters "Archives/modified-argon-pro-20230618.osk", // Covers "Argon" health display - "Archives/modified-argon-pro-20231001.osk" + "Archives/modified-argon-pro-20231001.osk", + // Covers player name text component. + "Archives/modified-argon-20231106.osk", }; /// From b62811633f07820848ec960e228989f02e30f68c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Nov 2023 17:17:19 +0900 Subject: [PATCH 1327/4092] Add test coverage of touching and missing not enabled touch mod --- .../Mods/TestSceneOsuModTouchDevice.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs index 5134265741..bcfa407684 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs @@ -104,6 +104,24 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods AddAssert("no toasts displayed", () => testOnScreenDisplay.ToastCount, () => Is.Zero); } + [Test] + public void TestTouchMiss() + { + // ensure mouse is active (and that it's not suppressed due to touches in previous tests) + AddStep("click mouse", () => InputManager.Click(MouseButton.Left)); + + AddUntilStep("wait until 200 near", () => Player.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(200).Within(500)); + AddStep("slow down", () => Player.GameplayClockContainer.AdjustmentsFromMods.Frequency.Value = 0.2); + AddUntilStep("wait until 200", () => Player.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(200)); + AddStep("touch playfield", () => + { + var touch = new Touch(TouchSource.Touch1, Player.DrawableRuleset.Playfield.ScreenSpaceDrawQuad.Centre); + InputManager.BeginTouch(touch); + InputManager.EndTouch(touch); + }); + AddAssert("touch device mod not activated", () => Player.Mods.Value, () => Has.No.InstanceOf()); + } + [Test] public void TestSecondObjectTouched() { From 4a70f2435c574b82829a5d2508ae29f7d2e3a2fb Mon Sep 17 00:00:00 2001 From: Joshua Hegedus Date: Mon, 6 Nov 2023 09:22:50 +0100 Subject: [PATCH 1328/4092] fixed showUsernameTooltip --- .../OnlinePlay/Components/ParticipantsList.cs | 2 +- .../DrawableRoomParticipantsList.cs | 2 +- osu.Game/Users/Drawables/ClickableAvatar.cs | 21 ------------------- osu.Game/Users/Drawables/UpdateableAvatar.cs | 6 +----- 4 files changed, 3 insertions(+), 28 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs b/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs index 00f0889cc8..cb1a846d6c 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs @@ -115,7 +115,7 @@ namespace osu.Game.Screens.OnlinePlay.Components RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex(@"27252d"), }, - avatar = new UpdateableAvatar(showUsernameTooltip: true) { RelativeSizeAxes = Axes.Both }, + avatar = new UpdateableAvatar { RelativeSizeAxes = Axes.Both }, }; } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs index 06f9f35479..1814f5359f 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs @@ -289,7 +289,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components set => avatar.User = value; } - private readonly UpdateableAvatar avatar = new UpdateableAvatar(showUsernameTooltip: true) { RelativeSizeAxes = Axes.Both }; + private readonly UpdateableAvatar avatar = new UpdateableAvatar { RelativeSizeAxes = Axes.Both }; [BackgroundDependencyLoader] private void load(OverlayColourProvider colours) diff --git a/osu.Game/Users/Drawables/ClickableAvatar.cs b/osu.Game/Users/Drawables/ClickableAvatar.cs index 0ed9f56cc7..d6c6afba0b 100644 --- a/osu.Game/Users/Drawables/ClickableAvatar.cs +++ b/osu.Game/Users/Drawables/ClickableAvatar.cs @@ -1,15 +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 System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Input.Events; -using osu.Framework.Localisation; using osu.Game.Graphics.Containers; -using osu.Game.Localisation; using osu.Game.Online.API.Requests.Responses; using osuTK; @@ -24,24 +21,6 @@ namespace osu.Game.Users.Drawables Width = 300 }; - public override LocalisableString TooltipText - { - get - { - if (!Enabled.Value) - return string.Empty; - - return ShowUsernameTooltip ? (user?.Username ?? string.Empty) : ContextMenuStrings.ViewProfile; - } - set => throw new NotSupportedException(); - } - - /// - /// By default, the tooltip will show "view profile" as avatars are usually displayed next to a username. - /// Setting this to true exposes the username via tooltip for special cases where this is not true. - /// - public bool ShowUsernameTooltip { get; set; } - private readonly APIUser? user; [Resolved] diff --git a/osu.Game/Users/Drawables/UpdateableAvatar.cs b/osu.Game/Users/Drawables/UpdateableAvatar.cs index 711e7ab799..3c72d7f7e0 100644 --- a/osu.Game/Users/Drawables/UpdateableAvatar.cs +++ b/osu.Game/Users/Drawables/UpdateableAvatar.cs @@ -46,7 +46,6 @@ namespace osu.Game.Users.Drawables protected override double LoadDelay => 200; private readonly bool isInteractive; - private readonly bool showUsernameTooltip; private readonly bool showGuestOnNull; /// @@ -54,12 +53,10 @@ namespace osu.Game.Users.Drawables /// /// The initial user to display. /// If set to true, hover/click sounds will play and clicking the avatar will open the user's profile. - /// Whether to show the username rather than "view profile" on the tooltip. (note: this only applies if is also true) /// Whether to show a default guest representation on null user (as opposed to nothing). - public UpdateableAvatar(APIUser? user = null, bool isInteractive = true, bool showUsernameTooltip = false, bool showGuestOnNull = true) + public UpdateableAvatar(APIUser? user = null, bool isInteractive = true, bool showGuestOnNull = true) { this.isInteractive = isInteractive; - this.showUsernameTooltip = showUsernameTooltip; this.showGuestOnNull = showGuestOnNull; User = user; @@ -75,7 +72,6 @@ namespace osu.Game.Users.Drawables return new ClickableAvatar(user) { RelativeSizeAxes = Axes.Both, - ShowUsernameTooltip = showUsernameTooltip, }; } else From e2928cc6b96f816320f238e5f653bd75c5ed9ca3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Nov 2023 17:32:39 +0900 Subject: [PATCH 1329/4092] Fix incorrect assertion check targets (and flip assertion for miss case) --- .../Mods/TestSceneOsuModTouchDevice.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs index bcfa407684..b77cc038c9 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs @@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods InputManager.BeginTouch(touch); InputManager.EndTouch(touch); }); - AddAssert("touch device mod not activated", () => Player.Mods.Value, () => Has.No.InstanceOf()); + AddAssert("touch device mod activated", () => Player.Score.ScoreInfo.Mods, () => Has.One.InstanceOf()); } [Test] @@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods InputManager.MoveMouseTo(Player.DrawableRuleset.Playfield.ScreenSpaceDrawQuad.Centre); InputManager.Click(MouseButton.Left); }); - AddAssert("touch device mod not activated", () => Player.Mods.Value, () => Has.No.InstanceOf()); + AddAssert("touch device mod not activated", () => Player.Score.ScoreInfo.Mods, () => Has.None.InstanceOf()); AddStep("speed back up", () => Player.GameplayClockContainer.AdjustmentsFromMods.Frequency.Value = 1); AddUntilStep("wait until 5000 near", () => Player.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(5000).Within(500)); From 97fee6143c7b81d169452396a490b8293b521527 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 6 Nov 2023 10:08:19 +0100 Subject: [PATCH 1330/4092] Rename touch "input handlers" to detectors --- .../{PlayerTouchInputHandler.cs => PlayerTouchInputDetector.cs} | 2 +- osu.Game/Screens/Play/SubmittingPlayer.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 2 +- ...lectTouchInputHandler.cs => SongSelectTouchInputDetector.cs} | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game/Screens/Play/{PlayerTouchInputHandler.cs => PlayerTouchInputDetector.cs} (97%) rename osu.Game/Screens/Select/{SongSelectTouchInputHandler.cs => SongSelectTouchInputDetector.cs} (96%) diff --git a/osu.Game/Screens/Play/PlayerTouchInputHandler.cs b/osu.Game/Screens/Play/PlayerTouchInputDetector.cs similarity index 97% rename from osu.Game/Screens/Play/PlayerTouchInputHandler.cs rename to osu.Game/Screens/Play/PlayerTouchInputDetector.cs index a7d41de105..8bef24b66c 100644 --- a/osu.Game/Screens/Play/PlayerTouchInputHandler.cs +++ b/osu.Game/Screens/Play/PlayerTouchInputDetector.cs @@ -13,7 +13,7 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Screens.Play { - public partial class PlayerTouchInputHandler : Component + public partial class PlayerTouchInputDetector : Component { [Resolved] private Player player { get; set; } = null!; diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index e018b8dab3..30fecbe149 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Play return; } - AddInternal(new PlayerTouchInputHandler()); + AddInternal(new PlayerTouchInputDetector()); } protected override void LoadAsyncComplete() diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 74454713d1..03083672d5 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -279,7 +279,7 @@ namespace osu.Game.Screens.Select { RelativeSizeAxes = Axes.Both, }, - new SongSelectTouchInputHandler() + new SongSelectTouchInputDetector() }); if (ShowFooter) diff --git a/osu.Game/Screens/Select/SongSelectTouchInputHandler.cs b/osu.Game/Screens/Select/SongSelectTouchInputDetector.cs similarity index 96% rename from osu.Game/Screens/Select/SongSelectTouchInputHandler.cs rename to osu.Game/Screens/Select/SongSelectTouchInputDetector.cs index 973dc12e12..b726acb45f 100644 --- a/osu.Game/Screens/Select/SongSelectTouchInputHandler.cs +++ b/osu.Game/Screens/Select/SongSelectTouchInputDetector.cs @@ -13,7 +13,7 @@ using osu.Game.Utils; namespace osu.Game.Screens.Select { - public partial class SongSelectTouchInputHandler : Component + public partial class SongSelectTouchInputDetector : Component { [Resolved] private Bindable ruleset { get; set; } = null!; From 204cd541e2b8876089c619abd94e97baafb97b8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 6 Nov 2023 10:14:56 +0100 Subject: [PATCH 1331/4092] Use placeholder mod icon for touch device --- osu.Game/Rulesets/Mods/ModTouchDevice.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Rulesets/Mods/ModTouchDevice.cs b/osu.Game/Rulesets/Mods/ModTouchDevice.cs index a5dfe5448c..b80b042f11 100644 --- a/osu.Game/Rulesets/Mods/ModTouchDevice.cs +++ b/osu.Game/Rulesets/Mods/ModTouchDevice.cs @@ -2,7 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods { @@ -10,6 +12,7 @@ namespace osu.Game.Rulesets.Mods { public sealed override string Name => "Touch Device"; public sealed override string Acronym => "TD"; + public sealed override IconUsage? Icon => OsuIcon.PlayStyleTouch; public sealed override LocalisableString Description => "Automatically applied to plays on devices with a touchscreen."; public sealed override double ScoreMultiplier => 1; public sealed override ModType Type => ModType.System; From aa6f14b0247f2fe0768379b9a1ae03f75827ba86 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Nov 2023 18:16:04 +0900 Subject: [PATCH 1332/4092] Fix spinner test hitting assertion when spinning too fast --- osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs index ea57a6a1b5..9980e1a55f 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -153,7 +154,7 @@ namespace osu.Game.Rulesets.Osu.Tests { base.Update(); if (auto) - RotationTracker.AddRotation((float)(Clock.ElapsedFrameTime * spinRate.Value)); + RotationTracker.AddRotation((float)Math.Min(180, Clock.ElapsedFrameTime * spinRate.Value)); } } } From 86cf0a36cfb842cf97f074988a28c9958e9b0b81 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Nov 2023 18:16:31 +0900 Subject: [PATCH 1333/4092] Add test coverage of spinner with no bonus ticks --- .../TestSceneSpinner.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs index 9980e1a55f..77b16dd0c5 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs @@ -37,6 +37,12 @@ namespace osu.Game.Rulesets.Osu.Tests AddSliderStep("Spin rate", 0.5, 5, 1, val => spinRate.Value = val); } + [SetUpSteps] + public void SetUpSteps() + { + AddStep("Reset rate", () => spinRate.Value = 1); + } + [TestCase(true)] [TestCase(false)] public void TestVariousSpinners(bool autoplay) @@ -47,6 +53,36 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep($"{term} Small", () => SetContents(_ => testSingle(7, autoplay))); } + [Test] + public void TestSpinnerNoBonus() + { + AddStep("Set high spin rate", () => spinRate.Value = 5); + + Spinner spinner; + + AddStep("add spinner", () => SetContents(_ => + { + spinner = new Spinner + { + StartTime = Time.Current, + EndTime = Time.Current + 750, + Samples = new List + { + new HitSampleInfo(HitSampleInfo.HIT_NORMAL) + } + }; + + spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { OverallDifficulty = 0 }); + + return drawableSpinner = new TestDrawableSpinner(spinner, true, spinRate) + { + Anchor = Anchor.Centre, + Depth = depthIndex++, + Scale = new Vector2(0.75f) + }; + })); + } + [Test] public void TestSpinningSamplePitchShift() { From b219a371a93d82c3638df3b47f51e85d20f1fb94 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Nov 2023 18:29:51 +0900 Subject: [PATCH 1334/4092] Move sample playback logic local to avoid edge case with no bonus ticks Can't see a better way of doing this. --- .../Objects/Drawables/DrawableSpinner.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index aa43532f65..e159d06a02 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -45,6 +45,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private const float spinning_sample_initial_frequency = 1.0f; private const float spinning_sample_modulated_base_frequency = 0.5f; + private SkinnableSound maxBonusSample; + /// /// The amount of bonus score gained from spinning after the required number of spins, for display purposes. /// @@ -109,6 +111,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables MinimumSampleVolume = MINIMUM_SAMPLE_VOLUME, Looping = true, Frequency = { Value = spinning_sample_initial_frequency } + }, + maxBonusSample = new SkinnableSound + { + MinimumSampleVolume = MINIMUM_SAMPLE_VOLUME, } }); @@ -128,6 +134,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.OnFree(); spinningSample.ClearSamples(); + maxBonusSample.ClearSamples(); } protected override void LoadSamples() @@ -136,6 +143,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables spinningSample.Samples = HitObject.CreateSpinningSamples().Cast().ToArray(); spinningSample.Frequency.Value = spinning_sample_initial_frequency; + + maxBonusSample.Samples = new ISampleInfo[] { HitObject.CreateHitSampleInfo("spinnerbonus") }; } private void updateSpinningSample(ValueChangedEvent tracking) @@ -157,6 +166,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.StopAllSamples(); spinningSample?.Stop(); + maxBonusSample?.Stop(); } protected override void AddNestedHitObject(DrawableHitObject hitObject) @@ -303,8 +313,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private static readonly int score_per_tick = new SpinnerBonusTick.OsuSpinnerBonusTickJudgement().MaxNumericResult; - private int lastMaxSamplePlayback; - private void updateBonusScore() { if (ticks.Count == 0) @@ -327,9 +335,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (tick == null) { // we still want to play a sound. this will probably be a new sound in the future, but for now let's continue playing the bonus sound. - // round robin to avoid hitting playback concurrency. - tick = ticks.OfType().Skip(lastMaxSamplePlayback++ % HitObject.MaximumBonusSpins).First(); - tick.PlaySamples(); + // TODO: this doesn't concurrency. i can't figure out how to make it concurrency. samples are bad and need a refactor. + maxBonusSample.Play(); } else tick.TriggerResult(true); From 92e4a8666def6e4bcc66d008ca0d206ca6c6cbd8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Nov 2023 18:43:47 +0900 Subject: [PATCH 1335/4092] Add `spinnerbonus-max` support and fallback to `spinnerbonus` --- .../Objects/Drawables/DrawableSpinner.cs | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index e159d06a02..c0c135d145 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -144,7 +145,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables spinningSample.Samples = HitObject.CreateSpinningSamples().Cast().ToArray(); spinningSample.Frequency.Value = spinning_sample_initial_frequency; - maxBonusSample.Samples = new ISampleInfo[] { HitObject.CreateHitSampleInfo("spinnerbonus") }; + maxBonusSample.Samples = new ISampleInfo[] { new SpinnerBonusMaxSampleInfo(HitObject.CreateHitSampleInfo()) }; } private void updateSpinningSample(ValueChangedEvent tracking) @@ -344,5 +345,26 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables completedFullSpins.Value++; } } + + public class SpinnerBonusMaxSampleInfo : HitSampleInfo + { + public override IEnumerable LookupNames + { + get + { + foreach (string name in base.LookupNames) + yield return name; + + foreach (string name in base.LookupNames) + yield return name.Replace("-max", string.Empty); + } + } + + public SpinnerBonusMaxSampleInfo(HitSampleInfo sampleInfo) + : base("spinnerbonus-max", sampleInfo.Bank, sampleInfo.Suffix, sampleInfo.Volume) + + { + } + } } } From 682668ccf0f73bdbb98b800afcaff983399d391d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 6 Nov 2023 10:54:32 +0100 Subject: [PATCH 1336/4092] Remove touch device toasts entirely --- .../Mods/TestSceneOsuModTouchDevice.cs | 21 ------------------- .../Overlays/OSD/TouchDeviceDetectedToast.cs | 15 ------------- .../Screens/Play/PlayerTouchInputDetector.cs | 12 ----------- 3 files changed, 48 deletions(-) delete mode 100644 osu.Game/Overlays/OSD/TouchDeviceDetectedToast.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs index b77cc038c9..e41cc8cfbc 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs @@ -4,13 +4,11 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Graphics; using osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Timing; using osu.Game.Configuration; using osu.Game.Input; -using osu.Game.Overlays; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; @@ -25,8 +23,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods [Resolved] private SessionStatics statics { get; set; } = null!; - private TestOnScreenDisplay testOnScreenDisplay = null!; - protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => @@ -54,14 +50,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods [BackgroundDependencyLoader] private void load() { - Add(testOnScreenDisplay = new TestOnScreenDisplay()); Add(new TouchInputInterceptor()); - Dependencies.CacheAs(testOnScreenDisplay); } public override void SetUpSteps() { - AddStep("reset OSD toast count", () => testOnScreenDisplay.ToastCount = 0); AddStep("reset static", () => statics.SetValue(Static.TouchInputActive, false)); base.SetUpSteps(); } @@ -85,7 +78,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods InputManager.EndTouch(touch); }); AddAssert("touch device mod activated", () => Player.Score.ScoreInfo.Mods, () => Has.One.InstanceOf()); - AddAssert("no toasts displayed", () => testOnScreenDisplay.ToastCount, () => Is.Zero); } [Test] @@ -101,7 +93,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods InputManager.EndTouch(touch); }); AddAssert("touch device mod not activated", () => Player.Score.ScoreInfo.Mods, () => Has.None.InstanceOf()); - AddAssert("no toasts displayed", () => testOnScreenDisplay.ToastCount, () => Is.Zero); } [Test] @@ -149,18 +140,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods InputManager.EndTouch(touch); }); AddAssert("touch device mod activated", () => Player.Score.ScoreInfo.Mods, () => Has.One.InstanceOf()); - AddAssert("toast displayed", () => testOnScreenDisplay.ToastCount, () => Is.EqualTo(1)); - } - - private partial class TestOnScreenDisplay : OnScreenDisplay - { - public int ToastCount { get; set; } - - protected override void DisplayTemporarily(Drawable toDisplay) - { - base.DisplayTemporarily(toDisplay); - ToastCount++; - } } } } diff --git a/osu.Game/Overlays/OSD/TouchDeviceDetectedToast.cs b/osu.Game/Overlays/OSD/TouchDeviceDetectedToast.cs deleted file mode 100644 index 266e10ab1f..0000000000 --- a/osu.Game/Overlays/OSD/TouchDeviceDetectedToast.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; - -namespace osu.Game.Overlays.OSD -{ - public partial class TouchDeviceDetectedToast : Toast - { - public TouchDeviceDetectedToast(RulesetInfo ruleset) - : base(ruleset.Name, "Touch device detected", "Touch Device mod applied to score") - { - } - } -} diff --git a/osu.Game/Screens/Play/PlayerTouchInputDetector.cs b/osu.Game/Screens/Play/PlayerTouchInputDetector.cs index 8bef24b66c..a5055dfb00 100644 --- a/osu.Game/Screens/Play/PlayerTouchInputDetector.cs +++ b/osu.Game/Screens/Play/PlayerTouchInputDetector.cs @@ -6,10 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Configuration; -using osu.Game.Overlays; -using osu.Game.Overlays.OSD; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Screens.Play { @@ -21,9 +18,6 @@ namespace osu.Game.Screens.Play [Resolved] private GameplayState gameplayState { get; set; } = null!; - [Resolved] - private OnScreenDisplay? onScreenDisplay { get; set; } - private IBindable touchActive = new BindableBool(); [BackgroundDependencyLoader] @@ -51,12 +45,6 @@ namespace osu.Game.Screens.Play if (touchDeviceMod == null) return; - // do not show the toast if the user hasn't hit anything yet. - // we're kind of assuming that the user just switches to touch for gameplay - // and we don't want to spam them with obvious toasts. - if (gameplayState.ScoreProcessor.HitEvents.Any(ev => ev.Result.IsHit())) - onScreenDisplay?.Display(new TouchDeviceDetectedToast(gameplayState.Ruleset.RulesetInfo)); - // `Player` (probably rightly so) assumes immutability of mods, // so this will not be shown immediately on the mod display in the top right. // if this is to change, the mod immutability should be revisited. From 718492a0b7d9c38b32102095c84393fbe5a792d9 Mon Sep 17 00:00:00 2001 From: Joshua Hegedus Date: Mon, 6 Nov 2023 11:29:15 +0100 Subject: [PATCH 1337/4092] fixed DRY --- .../Online/TestSceneUserClickableAvatar.cs | 83 +++++++------------ 1 file changed, 32 insertions(+), 51 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs b/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs index a24581f7ed..72870a5647 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs @@ -27,59 +27,14 @@ namespace osu.Game.Tests.Visual.Online Anchor = Anchor.Centre, Origin = Anchor.Centre, Spacing = new Vector2(10f), - Children = new Drawable[] + Children = new[] { - new ClickableAvatar(new APIUser - { - Username = @"flyte", Id = 3103765, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" - }) - { - Width = 50, - Height = 50, - CornerRadius = 10, - Masking = true, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, Radius = 1, Colour = Color4.Black.Opacity(0.2f), - }, - }, - new ClickableAvatar(new APIUser - { - Username = @"peppy", Id = 2, Colour = "99EB47", CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", - }) - { - Width = 50, - Height = 50, - CornerRadius = 10, - Masking = true, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, Radius = 1, Colour = Color4.Black.Opacity(0.2f), - }, - }, - new ClickableAvatar(new APIUser - { - Username = @"flyte", - Id = 3103765, - CountryCode = CountryCode.JP, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg", - Status = - { - Value = new UserStatusOnline() - } - }) - { - Width = 50, - Height = 50, - CornerRadius = 10, - Masking = true, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, Radius = 1, Colour = Color4.Black.Opacity(0.2f), - }, - }, - new ClickableAvatar(), + generateUser(@"peppy", 2, CountryCode.AU, @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", "99EB47"), + generateUser(@"flyte", 3103765, CountryCode.JP, @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg"), + generateUser(@"flyte", 3103765, CountryCode.JP, @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg"), new ClickableAvatar(), + new UpdateableAvatar(), + new UpdateableAvatar(), }, }; }); @@ -147,5 +102,31 @@ namespace osu.Game.Tests.Visual.Online AddStep("Hover out", () => InputManager.MoveMouseTo(new Vector2(0))); AddWaitStep("wait for tooltip to hide", 3); } + + private Drawable generateUser(string username, int id, CountryCode countryCode, string cover, string? color = null) + { + return new ClickableAvatar(new APIUser + { + Username = username, + Id = id, + CountryCode = countryCode, + CoverUrl = cover, + Colour = color ?? "000000", + Status = + { + Value = new UserStatusOnline() + } + }) + { + Width = 50, + Height = 50, + CornerRadius = 10, + Masking = true, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, Radius = 1, Colour = Color4.Black.Opacity(0.2f), + }, + }; + } } } From 51c891e2e4ff17432a01bf4f9ee75ad8c1498883 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Nov 2023 19:34:34 +0900 Subject: [PATCH 1338/4092] Automatically refresh the verify screen's issue list on re-entering it Addresses https://github.com/ppy/osu/discussions/25365. --- osu.Game/Screens/Edit/Verify/IssueList.cs | 10 +++++----- osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 6 ++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Verify/IssueList.cs b/osu.Game/Screens/Edit/Verify/IssueList.cs index 907949aee8..d07190fca0 100644 --- a/osu.Game/Screens/Edit/Verify/IssueList.cs +++ b/osu.Game/Screens/Edit/Verify/IssueList.cs @@ -72,7 +72,7 @@ namespace osu.Game.Screens.Edit.Verify new RoundedButton { Text = "Refresh", - Action = refresh, + Action = Refresh, Size = new Vector2(120, 40), Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, @@ -86,13 +86,13 @@ namespace osu.Game.Screens.Edit.Verify { base.LoadComplete(); - verify.InterpretedDifficulty.BindValueChanged(_ => refresh()); - verify.HiddenIssueTypes.BindCollectionChanged((_, _) => refresh()); + verify.InterpretedDifficulty.BindValueChanged(_ => Refresh()); + verify.HiddenIssueTypes.BindCollectionChanged((_, _) => Refresh()); - refresh(); + Refresh(); } - private void refresh() + public void Refresh() { var issues = generalVerifier.Run(context); diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index b17cf3379e..b6e0450e23 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs @@ -56,5 +56,11 @@ namespace osu.Game.Screens.Edit.Verify } }; } + + protected override void PopIn() + { + base.PopIn(); + IssueList.Refresh(); + } } } From 6deac9a5a45319d1536a9dc223a4efb07324a63c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 6 Nov 2023 11:24:56 +0100 Subject: [PATCH 1339/4092] Use better colours for system mods --- osu.Game/Graphics/OsuColour.cs | 2 +- osu.Game/Rulesets/UI/ModIcon.cs | 6 ++++-- osu.Game/Rulesets/UI/ModSwitchSmall.cs | 8 ++++++-- osu.Game/Rulesets/UI/ModSwitchTiny.cs | 6 +++++- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 75d313d98c..2e19eac572 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -162,7 +162,7 @@ namespace osu.Game.Graphics return Pink1; case ModType.System: - return Gray7; + return Gray5; default: throw new ArgumentOutOfRangeException(nameof(modType), modType, "Unknown mod type"); diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 5fd1507039..d09db37f2a 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -138,7 +138,6 @@ namespace osu.Game.Rulesets.UI { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Colour = OsuColour.Gray(84), Alpha = 0, Font = OsuFont.Numeric.With(null, 22f), UseFullGlyphHeight = false, @@ -148,7 +147,6 @@ namespace osu.Game.Rulesets.UI { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Colour = OsuColour.Gray(84), Size = new Vector2(45), Icon = FontAwesome.Solid.Question }, @@ -206,6 +204,10 @@ namespace osu.Game.Rulesets.UI private void updateColour() { + modAcronym.Colour = modIcon.Colour = mod.Type != ModType.System + ? OsuColour.Gray(84) + : colours.Yellow; + extendedText.Colour = background.Colour = Selected.Value ? backgroundColour.Lighten(0.2f) : backgroundColour; extendedBackground.Colour = Selected.Value ? backgroundColour.Darken(2.4f) : backgroundColour.Darken(2.8f); } diff --git a/osu.Game/Rulesets/UI/ModSwitchSmall.cs b/osu.Game/Rulesets/UI/ModSwitchSmall.cs index 927379c684..452a5599ba 100644 --- a/osu.Game/Rulesets/UI/ModSwitchSmall.cs +++ b/osu.Game/Rulesets/UI/ModSwitchSmall.cs @@ -85,11 +85,15 @@ namespace osu.Game.Rulesets.UI tinySwitch.Scale = new Vector2(0.3f); } + var modTypeColour = colours.ForModType(mod.Type); + inactiveForegroundColour = colourProvider?.Background5 ?? colours.Gray3; - activeForegroundColour = colours.ForModType(mod.Type); + activeForegroundColour = mod.Type != ModType.System ? modTypeColour : colours.Yellow; inactiveBackgroundColour = colourProvider?.Background2 ?? colours.Gray5; - activeBackgroundColour = Interpolation.ValueAt(0.1f, Colour4.Black, activeForegroundColour, 0, 1); + activeBackgroundColour = mod.Type != ModType.System + ? Interpolation.ValueAt(0.1f, Colour4.Black, modTypeColour, 0, 1) + : modTypeColour; } protected override void LoadComplete() diff --git a/osu.Game/Rulesets/UI/ModSwitchTiny.cs b/osu.Game/Rulesets/UI/ModSwitchTiny.cs index a3e325ace8..5bf501ead3 100644 --- a/osu.Game/Rulesets/UI/ModSwitchTiny.cs +++ b/osu.Game/Rulesets/UI/ModSwitchTiny.cs @@ -106,11 +106,15 @@ namespace osu.Game.Rulesets.UI [BackgroundDependencyLoader(true)] private void load(OsuColour colours, OverlayColourProvider? colourProvider) { + var modTypeColour = colours.ForModType(Mod.Type); + inactiveBackgroundColour = colourProvider?.Background5 ?? colours.Gray3; activeBackgroundColour = colours.ForModType(Mod.Type); inactiveForegroundColour = colourProvider?.Background2 ?? colours.Gray5; - activeForegroundColour = Interpolation.ValueAt(0.1f, Colour4.Black, activeForegroundColour, 0, 1); + activeForegroundColour = Mod.Type != ModType.System + ? Interpolation.ValueAt(0.1f, Colour4.Black, activeForegroundColour, 0, 1) + : colours.Yellow; } protected override void LoadComplete() From 39ad91feea58e0a91e95b3a2be0889ff98be4ce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 6 Nov 2023 11:50:04 +0100 Subject: [PATCH 1340/4092] Make debug input toggle post notifications --- osu.Game/Input/TouchInputInterceptor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Input/TouchInputInterceptor.cs b/osu.Game/Input/TouchInputInterceptor.cs index b566113a2a..368d8469ae 100644 --- a/osu.Game/Input/TouchInputInterceptor.cs +++ b/osu.Game/Input/TouchInputInterceptor.cs @@ -65,7 +65,7 @@ namespace osu.Game.Input [Conditional("TOUCH_INPUT_DEBUG")] private void debugToggleTouchInputActive() { - Logger.Log($@"Debug-toggling touch input to {(touchInputActive.Value ? @"inactive" : @"active")}", LoggingTarget.Input, LogLevel.Debug); + Logger.Log($@"Debug-toggling touch input to {(touchInputActive.Value ? @"inactive" : @"active")}", LoggingTarget.Information, LogLevel.Important); touchInputActive.Toggle(); } } From 034f53da4b1d79220d8af95055a203076ab88c87 Mon Sep 17 00:00:00 2001 From: Joshua Hegedus Date: Mon, 6 Nov 2023 11:54:57 +0100 Subject: [PATCH 1341/4092] added isEnabled to tooltip --- osu.Game/Users/Drawables/ClickableAvatar.cs | 16 +++++++++++++++- osu.Game/Users/Drawables/UpdateableAvatar.cs | 5 ++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/osu.Game/Users/Drawables/ClickableAvatar.cs b/osu.Game/Users/Drawables/ClickableAvatar.cs index d6c6afba0b..0520f62665 100644 --- a/osu.Game/Users/Drawables/ClickableAvatar.cs +++ b/osu.Game/Users/Drawables/ClickableAvatar.cs @@ -14,13 +14,15 @@ namespace osu.Game.Users.Drawables { public partial class ClickableAvatar : OsuClickableContainer, IHasCustomTooltip { - public ITooltip GetCustomTooltip() => new UserGridPanelTooltip(); + public ITooltip GetCustomTooltip() => new UserGridPanelTooltip(IsTooltipEnabled); public UserGridPanel TooltipContent => new UserGridPanel(user!) { Width = 300 }; + public bool IsTooltipEnabled; + private readonly APIUser? user; [Resolved] @@ -33,6 +35,7 @@ namespace osu.Game.Users.Drawables public ClickableAvatar(APIUser? user = null) { this.user = user; + IsTooltipEnabled = true; if (user?.Id != APIUser.SYSTEM_USER_ID) Action = openProfile; @@ -60,10 +63,21 @@ namespace osu.Game.Users.Drawables private partial class UserGridPanelTooltip : VisibilityContainer, ITooltip { + private readonly bool isEnabled; private UserGridPanel? displayedUser; + public UserGridPanelTooltip(bool isEnabled = true) + { + this.isEnabled = isEnabled; + } + protected override void PopIn() { + if (displayedUser is null || !isEnabled) + { + return; + } + Child = displayedUser; this.FadeIn(20, Easing.OutQuint); } diff --git a/osu.Game/Users/Drawables/UpdateableAvatar.cs b/osu.Game/Users/Drawables/UpdateableAvatar.cs index 3c72d7f7e0..a970997056 100644 --- a/osu.Game/Users/Drawables/UpdateableAvatar.cs +++ b/osu.Game/Users/Drawables/UpdateableAvatar.cs @@ -47,17 +47,20 @@ namespace osu.Game.Users.Drawables private readonly bool isInteractive; private readonly bool showGuestOnNull; + private readonly bool showUserPanel; /// /// Construct a new UpdateableAvatar. /// /// The initial user to display. /// If set to true, hover/click sounds will play and clicking the avatar will open the user's profile. + /// If set to true, the user status panel will be displayed in the tooltip. /// Whether to show a default guest representation on null user (as opposed to nothing). - public UpdateableAvatar(APIUser? user = null, bool isInteractive = true, bool showGuestOnNull = true) + public UpdateableAvatar(APIUser? user = null, bool isInteractive = true, bool showUserPanel = true, bool showGuestOnNull = true) { this.isInteractive = isInteractive; this.showGuestOnNull = showGuestOnNull; + this.showUserPanel = showUserPanel; User = user; } From 4bc36a6c90dfd051cd6202be7e4fec48c1b05080 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 6 Nov 2023 12:18:02 +0100 Subject: [PATCH 1342/4092] Fix unused variable --- osu.Game/Rulesets/UI/ModSwitchTiny.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/UI/ModSwitchTiny.cs b/osu.Game/Rulesets/UI/ModSwitchTiny.cs index 5bf501ead3..bb121c085c 100644 --- a/osu.Game/Rulesets/UI/ModSwitchTiny.cs +++ b/osu.Game/Rulesets/UI/ModSwitchTiny.cs @@ -109,11 +109,11 @@ namespace osu.Game.Rulesets.UI var modTypeColour = colours.ForModType(Mod.Type); inactiveBackgroundColour = colourProvider?.Background5 ?? colours.Gray3; - activeBackgroundColour = colours.ForModType(Mod.Type); + activeBackgroundColour = modTypeColour; inactiveForegroundColour = colourProvider?.Background2 ?? colours.Gray5; activeForegroundColour = Mod.Type != ModType.System - ? Interpolation.ValueAt(0.1f, Colour4.Black, activeForegroundColour, 0, 1) + ? Interpolation.ValueAt(0.1f, Colour4.Black, modTypeColour, 0, 1) : colours.Yellow; } From a01f6187f4738f85376cb820315d95d8c4545e4c Mon Sep 17 00:00:00 2001 From: Joshua Hegedus Date: Mon, 6 Nov 2023 14:52:06 +0100 Subject: [PATCH 1343/4092] testing the tooltip --- .../Online/TestSceneUserClickableAvatar.cs | 134 +++++++++--------- osu.Game/Users/Drawables/ClickableAvatar.cs | 43 +++++- osu.Game/Users/Drawables/UpdateableAvatar.cs | 1 + 3 files changed, 102 insertions(+), 76 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs b/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs index 72870a5647..678767f15e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs @@ -1,13 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; using NUnit.Framework; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; -using osu.Framework.Testing; using osu.Game.Online.API.Requests.Responses; using osu.Game.Users; using osu.Game.Users.Drawables; @@ -29,12 +27,9 @@ namespace osu.Game.Tests.Visual.Online Spacing = new Vector2(10f), Children = new[] { - generateUser(@"peppy", 2, CountryCode.AU, @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", "99EB47"), - generateUser(@"flyte", 3103765, CountryCode.JP, @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg"), - generateUser(@"flyte", 3103765, CountryCode.JP, @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg"), - new ClickableAvatar(), - new UpdateableAvatar(), - new UpdateableAvatar(), + generateUser(@"peppy", 2, CountryCode.AU, @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", false, "99EB47"), + generateUser(@"flyte", 3103765, CountryCode.JP, @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg", false), + generateUser(@"joshika39", 17032217, CountryCode.RS, @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", true), }, }; }); @@ -42,68 +37,68 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestClickableAvatarHover() { - AddStep($"click {1}. {nameof(ClickableAvatar)}", () => - { - var targets = this.ChildrenOfType().ToList(); - if (targets.Count < 1) - return; - - InputManager.MoveMouseTo(targets[0]); - }); - AddWaitStep("wait for tooltip to show", 5); - AddStep("Hover out", () => InputManager.MoveMouseTo(new Vector2(0))); - AddWaitStep("wait for tooltip to hide", 3); - - AddStep($"click {2}. {nameof(ClickableAvatar)}", () => - { - var targets = this.ChildrenOfType().ToList(); - if (targets.Count < 2) - return; - - InputManager.MoveMouseTo(targets[1]); - }); - AddWaitStep("wait for tooltip to show", 5); - AddStep("Hover out", () => InputManager.MoveMouseTo(new Vector2(0))); - AddWaitStep("wait for tooltip to hide", 3); - - AddStep($"click {3}. {nameof(ClickableAvatar)}", () => - { - var targets = this.ChildrenOfType().ToList(); - if (targets.Count < 3) - return; - - InputManager.MoveMouseTo(targets[2]); - }); - AddWaitStep("wait for tooltip to show", 5); - AddStep("Hover out", () => InputManager.MoveMouseTo(new Vector2(0))); - AddWaitStep("wait for tooltip to hide", 3); - - AddStep($"click null user {4}. {nameof(ClickableAvatar)}", () => - { - var targets = this.ChildrenOfType().ToList(); - if (targets.Count < 4) - return; - - InputManager.MoveMouseTo(targets[3]); - }); - AddWaitStep("wait for tooltip to show", 5); - AddStep("Hover out", () => InputManager.MoveMouseTo(new Vector2(0))); - AddWaitStep("wait for tooltip to hide", 3); - - AddStep($"click null user {5}. {nameof(ClickableAvatar)}", () => - { - var targets = this.ChildrenOfType().ToList(); - if (targets.Count < 5) - return; - - InputManager.MoveMouseTo(targets[4]); - }); - AddWaitStep("wait for tooltip to show", 5); - AddStep("Hover out", () => InputManager.MoveMouseTo(new Vector2(0))); - AddWaitStep("wait for tooltip to hide", 3); + // AddStep($"click {1}. {nameof(ClickableAvatar)}", () => + // { + // var targets = this.ChildrenOfType().ToList(); + // if (targets.Count < 1) + // return; + // + // InputManager.MoveMouseTo(targets[0]); + // }); + // AddWaitStep("wait for tooltip to show", 5); + // AddStep("Hover out", () => InputManager.MoveMouseTo(new Vector2(0))); + // AddWaitStep("wait for tooltip to hide", 3); + // + // AddStep($"click {2}. {nameof(ClickableAvatar)}", () => + // { + // var targets = this.ChildrenOfType().ToList(); + // if (targets.Count < 2) + // return; + // + // InputManager.MoveMouseTo(targets[1]); + // }); + // AddWaitStep("wait for tooltip to show", 5); + // AddStep("Hover out", () => InputManager.MoveMouseTo(new Vector2(0))); + // AddWaitStep("wait for tooltip to hide", 3); + // + // AddStep($"click {3}. {nameof(ClickableAvatar)}", () => + // { + // var targets = this.ChildrenOfType().ToList(); + // if (targets.Count < 3) + // return; + // + // InputManager.MoveMouseTo(targets[2]); + // }); + // AddWaitStep("wait for tooltip to show", 5); + // AddStep("Hover out", () => InputManager.MoveMouseTo(new Vector2(0))); + // AddWaitStep("wait for tooltip to hide", 3); + // + // AddStep($"click null user {4}. {nameof(ClickableAvatar)}", () => + // { + // var targets = this.ChildrenOfType().ToList(); + // if (targets.Count < 4) + // return; + // + // InputManager.MoveMouseTo(targets[3]); + // }); + // AddWaitStep("wait for tooltip to show", 5); + // AddStep("Hover out", () => InputManager.MoveMouseTo(new Vector2(0))); + // AddWaitStep("wait for tooltip to hide", 3); + // + // AddStep($"click null user {5}. {nameof(ClickableAvatar)}", () => + // { + // var targets = this.ChildrenOfType().ToList(); + // if (targets.Count < 5) + // return; + // + // InputManager.MoveMouseTo(targets[4]); + // }); + // AddWaitStep("wait for tooltip to show", 5); + // AddStep("Hover out", () => InputManager.MoveMouseTo(new Vector2(0))); + // AddWaitStep("wait for tooltip to hide", 3); } - private Drawable generateUser(string username, int id, CountryCode countryCode, string cover, string? color = null) + private Drawable generateUser(string username, int id, CountryCode countryCode, string cover, bool isTooltipEnabled, string? color = null) { return new ClickableAvatar(new APIUser { @@ -115,7 +110,7 @@ namespace osu.Game.Tests.Visual.Online Status = { Value = new UserStatusOnline() - } + }, }) { Width = 50, @@ -126,6 +121,7 @@ namespace osu.Game.Tests.Visual.Online { Type = EdgeEffectType.Shadow, Radius = 1, Colour = Color4.Black.Opacity(0.2f), }, + IsTooltipEnabled = isTooltipEnabled, }; } } diff --git a/osu.Game/Users/Drawables/ClickableAvatar.cs b/osu.Game/Users/Drawables/ClickableAvatar.cs index 0520f62665..c11ad7f720 100644 --- a/osu.Game/Users/Drawables/ClickableAvatar.cs +++ b/osu.Game/Users/Drawables/ClickableAvatar.cs @@ -1,12 +1,15 @@ // 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.Cursor; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics.Containers; +using osu.Game.Localisation; using osu.Game.Online.API.Requests.Responses; using osuTK; @@ -14,14 +17,32 @@ namespace osu.Game.Users.Drawables { public partial class ClickableAvatar : OsuClickableContainer, IHasCustomTooltip { - public ITooltip GetCustomTooltip() => new UserGridPanelTooltip(IsTooltipEnabled); + public ITooltip GetCustomTooltip() => new UserGridPanelTooltip(this); public UserGridPanel TooltipContent => new UserGridPanel(user!) { Width = 300 }; - public bool IsTooltipEnabled; + public override LocalisableString TooltipText + { + get + { + if (!Enabled.Value) + return string.Empty; + + return !IsTooltipEnabled ? (user?.Username ?? string.Empty) : ContextMenuStrings.ViewProfile; + } + set => throw new NotSupportedException(); + } + + /// + /// By default, the tooltip will show "view profile" as avatars are usually displayed next to a username. + /// Setting this to true exposes the username via tooltip for special cases where this is not true. + /// + // public bool ShowUsernameTooltip { get; set; } + + public bool IsTooltipEnabled { get; set; } private readonly APIUser? user; @@ -35,12 +56,16 @@ namespace osu.Game.Users.Drawables public ClickableAvatar(APIUser? user = null) { this.user = user; - IsTooltipEnabled = true; if (user?.Id != APIUser.SYSTEM_USER_ID) Action = openProfile; } + public void SetValue(out bool value) + { + value = IsTooltipEnabled; + } + [BackgroundDependencyLoader] private void load() { @@ -61,18 +86,22 @@ namespace osu.Game.Users.Drawables return base.OnClick(e); } - private partial class UserGridPanelTooltip : VisibilityContainer, ITooltip + public partial class UserGridPanelTooltip : VisibilityContainer, ITooltip { - private readonly bool isEnabled; + private readonly ClickableAvatar parent; private UserGridPanel? displayedUser; + private bool isEnabled; - public UserGridPanelTooltip(bool isEnabled = true) + public UserGridPanelTooltip(ClickableAvatar parent) { - this.isEnabled = isEnabled; + this.parent = parent ?? throw new ArgumentNullException(nameof(parent)); + isEnabled = this.parent.IsTooltipEnabled; } protected override void PopIn() { + parent.SetValue(out isEnabled); + if (displayedUser is null || !isEnabled) { return; diff --git a/osu.Game/Users/Drawables/UpdateableAvatar.cs b/osu.Game/Users/Drawables/UpdateableAvatar.cs index a970997056..64d64c56ce 100644 --- a/osu.Game/Users/Drawables/UpdateableAvatar.cs +++ b/osu.Game/Users/Drawables/UpdateableAvatar.cs @@ -75,6 +75,7 @@ namespace osu.Game.Users.Drawables return new ClickableAvatar(user) { RelativeSizeAxes = Axes.Both, + IsTooltipEnabled = showUserPanel }; } else From f897c21b3f5b57b5892f25cef3644aabf82d7c43 Mon Sep 17 00:00:00 2001 From: Joshua Hegedus Date: Mon, 6 Nov 2023 15:25:12 +0100 Subject: [PATCH 1344/4092] partial change --- osu.Game/Users/Drawables/ClickableAvatar.cs | 39 ++++++++------------- 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/osu.Game/Users/Drawables/ClickableAvatar.cs b/osu.Game/Users/Drawables/ClickableAvatar.cs index c11ad7f720..376ce0b821 100644 --- a/osu.Game/Users/Drawables/ClickableAvatar.cs +++ b/osu.Game/Users/Drawables/ClickableAvatar.cs @@ -15,14 +15,14 @@ using osuTK; namespace osu.Game.Users.Drawables { - public partial class ClickableAvatar : OsuClickableContainer, IHasCustomTooltip + public partial class ClickableAvatar : OsuClickableContainer, IHasCustomTooltip { - public ITooltip GetCustomTooltip() => new UserGridPanelTooltip(this); - - public UserGridPanel TooltipContent => new UserGridPanel(user!) + public ITooltip GetCustomTooltip() { - Width = 300 - }; + return new APIUserTooltip(user); + } + + public APIUser? TooltipContent => user; public override LocalisableString TooltipText { @@ -36,12 +36,6 @@ namespace osu.Game.Users.Drawables set => throw new NotSupportedException(); } - /// - /// By default, the tooltip will show "view profile" as avatars are usually displayed next to a username. - /// Setting this to true exposes the username via tooltip for special cases where this is not true. - /// - // public bool ShowUsernameTooltip { get; set; } - public bool IsTooltipEnabled { get; set; } private readonly APIUser? user; @@ -86,28 +80,23 @@ namespace osu.Game.Users.Drawables return base.OnClick(e); } - public partial class UserGridPanelTooltip : VisibilityContainer, ITooltip + public partial class APIUserTooltip : VisibilityContainer, ITooltip { - private readonly ClickableAvatar parent; - private UserGridPanel? displayedUser; - private bool isEnabled; + private APIUser? user; - public UserGridPanelTooltip(ClickableAvatar parent) + public APIUserTooltip(APIUser? user) { - this.parent = parent ?? throw new ArgumentNullException(nameof(parent)); - isEnabled = this.parent.IsTooltipEnabled; + this.user = user; } protected override void PopIn() { - parent.SetValue(out isEnabled); - - if (displayedUser is null || !isEnabled) + if (user is null) { return; } - Child = displayedUser; + Child = new UserGridPanel(user); this.FadeIn(20, Easing.OutQuint); } @@ -115,9 +104,9 @@ namespace osu.Game.Users.Drawables public void Move(Vector2 pos) => Position = pos; - public void SetContent(UserGridPanel userGridPanel) + public void SetContent(APIUser user) { - displayedUser = userGridPanel; + this.user = user; } } } From 915feeffb05389da36e9eb4e1c75f962202b3703 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 6 Nov 2023 17:37:25 +0300 Subject: [PATCH 1345/4092] Revert gameplay cursor scale changes --- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index 12506c83b9..555610a3b6 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; @@ -13,16 +14,18 @@ using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Screens.Play; +using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.UI { public partial class OsuResumeOverlay : ResumeOverlay { - private Container resumeCursorContainer; + private Container cursorScaleContainer; private OsuClickToResumeCursor clickToResumeCursor; private OsuCursorContainer localCursorContainer; + private IBindable localCursorScale; public override CursorContainer LocalCursor => State.Value == Visibility.Visible ? localCursorContainer : null; @@ -31,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.UI [BackgroundDependencyLoader] private void load() { - Add(resumeCursorContainer = new Container + Add(cursorScaleContainer = new Container { Child = clickToResumeCursor = new OsuClickToResumeCursor { ResumeRequested = Resume } }); @@ -42,11 +45,17 @@ namespace osu.Game.Rulesets.Osu.UI base.PopIn(); GameplayCursor.ActiveCursor.Hide(); - resumeCursorContainer.Position = ToLocalSpace(GameplayCursor.ActiveCursor.ScreenSpaceDrawQuad.Centre); + cursorScaleContainer.Position = ToLocalSpace(GameplayCursor.ActiveCursor.ScreenSpaceDrawQuad.Centre); clickToResumeCursor.Appear(); if (localCursorContainer == null) + { Add(localCursorContainer = new OsuCursorContainer()); + + localCursorScale = new BindableFloat(); + localCursorScale.BindTo(localCursorContainer.CursorScale); + localCursorScale.BindValueChanged(scale => cursorScaleContainer.Scale = new Vector2(scale.NewValue), true); + } } protected override void PopOut() From 75fbbb35ad2f88524670a19312dcb43569db8190 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 6 Nov 2023 18:28:51 +0300 Subject: [PATCH 1346/4092] Move cursor scale application within `OsuCursor` Doing so takes down two birds with one stone. 1. `ResumeOverlay` having to manually apply cursor scale to its "resume cursor". 2. Resume cursor input handling scaling up with the gameplay setting. Now, only the sprite itself gets scaled. --- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 67 ++++++++++++++++--- .../UI/Cursor/OsuCursorContainer.cs | 61 ++--------------- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 12 +--- 3 files changed, 68 insertions(+), 72 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index 66c86ee09d..ab1bb0cf5a 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -4,12 +4,16 @@ #nullable disable using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Rulesets.Osu.Skinning; +using osu.Game.Screens.Play; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -20,12 +24,29 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { private const float size = 28; + private const float pressed_scale = 1.2f; + private const float released_scale = 1f; + private bool cursorExpand; private SkinnableDrawable cursorSprite; + private Container cursorScaleContainer = null!; private Drawable expandTarget => (cursorSprite.Drawable as OsuCursorSprite)?.ExpandTarget ?? cursorSprite; + public IBindable CursorScale => cursorScale; + + private readonly Bindable cursorScale = new BindableFloat(1); + + private Bindable userCursorScale = null!; + private Bindable autoCursorScale = null!; + + [Resolved(canBeNull: true)] + private GameplayState state { get; set; } + + [Resolved] + private OsuConfigManager config { get; set; } + public OsuCursor() { Origin = Anchor.Centre; @@ -33,15 +54,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor Size = new Vector2(size); } - protected override void SkinChanged(ISkinSource skin) - { - cursorExpand = skin.GetConfig(OsuSkinConfiguration.CursorExpand)?.Value ?? true; - } - [BackgroundDependencyLoader] private void load() { - InternalChild = new Container + InternalChild = cursorScaleContainer = new Container { RelativeSizeAxes = Axes.Both, Origin = Anchor.Centre, @@ -52,10 +68,45 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor Anchor = Anchor.Centre, } }; + + userCursorScale = config.GetBindable(OsuSetting.GameplayCursorSize); + userCursorScale.ValueChanged += _ => calculateCursorScale(); + + autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize); + autoCursorScale.ValueChanged += _ => calculateCursorScale(); + + cursorScale.BindValueChanged(e => cursorScaleContainer.Scale = new Vector2(e.NewValue), true); } - private const float pressed_scale = 1.2f; - private const float released_scale = 1f; + protected override void LoadComplete() + { + base.LoadComplete(); + calculateCursorScale(); + } + + /// + /// Get the scale applicable to the ActiveCursor based on a beatmap's circle size. + /// + public static float GetScaleForCircleSize(float circleSize) => + 1f - 0.7f * (1f + circleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY; + + private void calculateCursorScale() + { + float scale = userCursorScale.Value; + + if (autoCursorScale.Value && state != null) + { + // if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier. + scale *= GetScaleForCircleSize(state.Beatmap.Difficulty.CircleSize); + } + + cursorScale.Value = scale; + } + + protected override void SkinChanged(ISkinSource skin) + { + cursorExpand = skin.GetConfig(OsuSkinConfiguration.CursorExpand)?.Value ?? true; + } public void Expand() { diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs index bf1ff872dd..ba8a634ff7 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs @@ -11,11 +11,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Game.Beatmaps; -using osu.Game.Configuration; using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.UI; -using osu.Game.Screens.Play; using osu.Game.Skinning; using osuTK; @@ -23,6 +20,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { public partial class OsuCursorContainer : GameplayCursorContainer, IKeyBindingHandler { + public new OsuCursor ActiveCursor => (OsuCursor)base.ActiveCursor; + protected override Drawable CreateCursor() => new OsuCursor(); protected override Container Content => fadeContainer; @@ -33,13 +32,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private readonly Drawable cursorTrail; - public IBindable CursorScale => cursorScale; - - private readonly Bindable cursorScale = new BindableFloat(1); - - private Bindable userCursorScale; - private Bindable autoCursorScale; - private readonly CursorRippleVisualiser rippleVisualiser; public OsuCursorContainer() @@ -56,12 +48,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor }; } - [Resolved(canBeNull: true)] - private GameplayState state { get; set; } - - [Resolved] - private OsuConfigManager config { get; set; } - [BackgroundDependencyLoader(true)] private void load(OsuRulesetConfigManager rulesetConfig) { @@ -74,46 +60,13 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor showTrail.BindValueChanged(v => cursorTrail.FadeTo(v.NewValue ? 1 : 0, 200), true); - userCursorScale = config.GetBindable(OsuSetting.GameplayCursorSize); - userCursorScale.ValueChanged += _ => calculateScale(); - - autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize); - autoCursorScale.ValueChanged += _ => calculateScale(); - - CursorScale.BindValueChanged(e => + ActiveCursor.CursorScale.BindValueChanged(e => { var newScale = new Vector2(e.NewValue); - ActiveCursor.Scale = newScale; rippleVisualiser.CursorScale = newScale; cursorTrail.Scale = newScale; }, true); - - calculateScale(); - } - - /// - /// Get the scale applicable to the ActiveCursor based on a beatmap's circle size. - /// - public static float GetScaleForCircleSize(float circleSize) => - 1f - 0.7f * (1f + circleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY; - - private void calculateScale() - { - float scale = userCursorScale.Value; - - if (autoCursorScale.Value && state != null) - { - // if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier. - scale *= GetScaleForCircleSize(state.Beatmap.Difficulty.CircleSize); - } - - cursorScale.Value = scale; - - var newScale = new Vector2(scale); - - ActiveCursor.ScaleTo(newScale, 400, Easing.OutQuint); - cursorTrail.Scale = newScale; } private int downCount; @@ -121,9 +74,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private void updateExpandedState() { if (downCount > 0) - (ActiveCursor as OsuCursor)?.Expand(); + ActiveCursor.Expand(); else - (ActiveCursor as OsuCursor)?.Contract(); + ActiveCursor.Contract(); } public bool OnPressed(KeyBindingPressEvent e) @@ -160,13 +113,13 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor protected override void PopIn() { fadeContainer.FadeTo(1, 300, Easing.OutQuint); - ActiveCursor.ScaleTo(CursorScale.Value, 400, Easing.OutQuint); + ActiveCursor.ScaleTo(1f, 400, Easing.OutQuint); } protected override void PopOut() { fadeContainer.FadeTo(0.05f, 450, Easing.OutQuint); - ActiveCursor.ScaleTo(CursorScale.Value * 0.8f, 450, Easing.OutQuint); + ActiveCursor.ScaleTo(0.8f, 450, Easing.OutQuint); } private partial class DefaultCursorTrail : CursorTrail diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index 555610a3b6..ea49836772 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -5,7 +5,6 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; @@ -14,7 +13,6 @@ using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Screens.Play; -using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.UI @@ -25,7 +23,6 @@ namespace osu.Game.Rulesets.Osu.UI private OsuClickToResumeCursor clickToResumeCursor; private OsuCursorContainer localCursorContainer; - private IBindable localCursorScale; public override CursorContainer LocalCursor => State.Value == Visibility.Visible ? localCursorContainer : null; @@ -49,13 +46,7 @@ namespace osu.Game.Rulesets.Osu.UI clickToResumeCursor.Appear(); if (localCursorContainer == null) - { Add(localCursorContainer = new OsuCursorContainer()); - - localCursorScale = new BindableFloat(); - localCursorScale.BindTo(localCursorContainer.CursorScale); - localCursorScale.BindValueChanged(scale => cursorScaleContainer.Scale = new Vector2(scale.NewValue), true); - } } protected override void PopOut() @@ -98,7 +89,8 @@ namespace osu.Game.Rulesets.Osu.UI { case OsuAction.LeftButton: case OsuAction.RightButton: - if (!IsHovered) return false; + if (!IsHovered) + return false; this.ScaleTo(2, TRANSITION_TIME, Easing.OutQuint); From e12ee29a942279becf7ca4fd3816af1787b3fce8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 6 Nov 2023 18:35:30 +0300 Subject: [PATCH 1347/4092] Update existing test coverage --- osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs index c84a6ab70f..e6696032ae 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs @@ -94,16 +94,16 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("load content", loadContent); - AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == OsuCursorContainer.GetScaleForCircleSize(circleSize) * userScale); + AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.CursorScale.Value == OsuCursor.GetScaleForCircleSize(circleSize) * userScale); AddStep("set user scale to 1", () => config.SetValue(OsuSetting.GameplayCursorSize, 1f)); - AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == OsuCursorContainer.GetScaleForCircleSize(circleSize)); + AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.CursorScale.Value == OsuCursor.GetScaleForCircleSize(circleSize)); AddStep("turn off autosizing", () => config.SetValue(OsuSetting.AutoCursorSize, false)); - AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == 1); + AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.CursorScale.Value == 1); AddStep($"set user scale to {userScale}", () => config.SetValue(OsuSetting.GameplayCursorSize, userScale)); - AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == userScale); + AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.CursorScale.Value == userScale); } [Test] From 073249dafb3605a36bb8482f73f2dac1b7733b9f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 6 Nov 2023 18:35:46 +0300 Subject: [PATCH 1348/4092] Allow tinkering with cursor-related settings in resume overlay test scene --- .../TestSceneResumeOverlay.cs | 58 +++++++++++++------ 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs index 81dc64cda9..49a8254f53 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs @@ -2,11 +2,14 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Game.Configuration; using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Play; +using osu.Game.Tests.Gameplay; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests @@ -19,24 +22,37 @@ namespace osu.Game.Rulesets.Osu.Tests private bool resumeFired; - [SetUp] - public void SetUp() => Schedule(() => - { - Child = osuInputManager = new ManualOsuInputManager(new OsuRuleset().RulesetInfo) - { - Children = new Drawable[] - { - cursor = new CursorContainer(), - resume = new OsuResumeOverlay - { - GameplayCursor = cursor - }, - } - }; + private OsuConfigManager localConfig = null!; - resumeFired = false; - resume.ResumeAction = () => resumeFired = true; - }); + [Cached] + private GameplayState gameplayState; + + public TestSceneResumeOverlay() + { + gameplayState = TestGameplayState.Create(new OsuRuleset()); + } + + [BackgroundDependencyLoader] + private void load() + { + Dependencies.Cache(localConfig = new OsuConfigManager(LocalStorage)); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + AddSliderStep("cursor size", 0.1f, 2f, 1f, v => localConfig.SetValue(OsuSetting.GameplayCursorSize, v)); + AddSliderStep("circle size", 0f, 10f, 0f, val => + { + gameplayState.Beatmap.Difficulty.CircleSize = val; + SetUp(); + }); + + AddToggleStep("auto size", v => localConfig.SetValue(OsuSetting.AutoCursorSize, v)); + } + + [SetUp] + public void SetUp() => Schedule(loadContent); [Test] public void TestResume() @@ -53,6 +69,14 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert("dismissed", () => resumeFired && resume.State.Value == Visibility.Hidden); } + private void loadContent() + { + Child = osuInputManager = new ManualOsuInputManager(new OsuRuleset().RulesetInfo) { Children = new Drawable[] { cursor = new CursorContainer(), resume = new OsuResumeOverlay { GameplayCursor = cursor }, } }; + + resumeFired = false; + resume.ResumeAction = () => resumeFired = true; + } + private partial class ManualOsuInputManager : OsuInputManager { public ManualOsuInputManager(RulesetInfo ruleset) From a136f272cf78ec3572bc13bdcf0de47b74789a73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 6 Nov 2023 16:40:57 +0100 Subject: [PATCH 1349/4092] Just use yellow for system mods --- osu.Game/Graphics/OsuColour.cs | 2 +- osu.Game/Rulesets/UI/ModIcon.cs | 4 +--- osu.Game/Rulesets/UI/ModSwitchSmall.cs | 6 ++---- osu.Game/Rulesets/UI/ModSwitchTiny.cs | 4 +--- 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 2e19eac572..a417164e27 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -162,7 +162,7 @@ namespace osu.Game.Graphics return Pink1; case ModType.System: - return Gray5; + return Yellow; default: throw new ArgumentOutOfRangeException(nameof(modType), modType, "Unknown mod type"); diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index d09db37f2a..d1776c5c0b 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -204,9 +204,7 @@ namespace osu.Game.Rulesets.UI private void updateColour() { - modAcronym.Colour = modIcon.Colour = mod.Type != ModType.System - ? OsuColour.Gray(84) - : colours.Yellow; + modAcronym.Colour = modIcon.Colour = OsuColour.Gray(84); extendedText.Colour = background.Colour = Selected.Value ? backgroundColour.Lighten(0.2f) : backgroundColour; extendedBackground.Colour = Selected.Value ? backgroundColour.Darken(2.4f) : backgroundColour.Darken(2.8f); diff --git a/osu.Game/Rulesets/UI/ModSwitchSmall.cs b/osu.Game/Rulesets/UI/ModSwitchSmall.cs index 452a5599ba..6e96cc8e6f 100644 --- a/osu.Game/Rulesets/UI/ModSwitchSmall.cs +++ b/osu.Game/Rulesets/UI/ModSwitchSmall.cs @@ -88,12 +88,10 @@ namespace osu.Game.Rulesets.UI var modTypeColour = colours.ForModType(mod.Type); inactiveForegroundColour = colourProvider?.Background5 ?? colours.Gray3; - activeForegroundColour = mod.Type != ModType.System ? modTypeColour : colours.Yellow; + activeForegroundColour = modTypeColour; inactiveBackgroundColour = colourProvider?.Background2 ?? colours.Gray5; - activeBackgroundColour = mod.Type != ModType.System - ? Interpolation.ValueAt(0.1f, Colour4.Black, modTypeColour, 0, 1) - : modTypeColour; + activeBackgroundColour = Interpolation.ValueAt(0.1f, Colour4.Black, modTypeColour, 0, 1); } protected override void LoadComplete() diff --git a/osu.Game/Rulesets/UI/ModSwitchTiny.cs b/osu.Game/Rulesets/UI/ModSwitchTiny.cs index bb121c085c..4d50e702af 100644 --- a/osu.Game/Rulesets/UI/ModSwitchTiny.cs +++ b/osu.Game/Rulesets/UI/ModSwitchTiny.cs @@ -112,9 +112,7 @@ namespace osu.Game.Rulesets.UI activeBackgroundColour = modTypeColour; inactiveForegroundColour = colourProvider?.Background2 ?? colours.Gray5; - activeForegroundColour = Mod.Type != ModType.System - ? Interpolation.ValueAt(0.1f, Colour4.Black, modTypeColour, 0, 1) - : colours.Yellow; + activeForegroundColour = Interpolation.ValueAt(0.1f, Colour4.Black, modTypeColour, 0, 1); } protected override void LoadComplete() From 9d10d93085cd3cde44d63836911bc607d5618901 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 6 Nov 2023 20:45:52 +0300 Subject: [PATCH 1350/4092] Adjust test scene to see graph flickering --- .../Visual/Online/TestSceneGraph.cs | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneGraph.cs b/osu.Game.Tests/Visual/Online/TestSceneGraph.cs index 4f19003638..eee29e0aeb 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneGraph.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneGraph.cs @@ -13,22 +13,20 @@ namespace osu.Game.Tests.Visual.Online [TestFixture] public partial class TestSceneGraph : OsuTestScene { + private readonly BarGraph graph; + public TestSceneGraph() { - BarGraph graph; - - Children = new[] + Child = graph = new BarGraph { - graph = new BarGraph - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(0.5f), - }, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(0.5f), }; AddStep("values from 1-10", () => graph.Values = Enumerable.Range(1, 10).Select(i => (float)i)); + AddStep("small values", () => graph.Values = Enumerable.Range(1, 10).Select(i => i * 0.01f).Concat(new[] { 100f })); AddStep("values from 1-100", () => graph.Values = Enumerable.Range(1, 100).Select(i => (float)i)); AddStep("reversed values from 1-10", () => graph.Values = Enumerable.Range(1, 10).Reverse().Select(i => (float)i)); AddStep("empty values", () => graph.Values = Array.Empty()); @@ -37,5 +35,12 @@ namespace osu.Game.Tests.Visual.Online AddStep("Left to right", () => graph.Direction = BarDirection.LeftToRight); AddStep("Right to left", () => graph.Direction = BarDirection.RightToLeft); } + + protected override void LoadComplete() + { + base.LoadComplete(); + + graph.MoveToY(-10, 1000).Then().MoveToY(10, 1000).Loop(); + } } } From 944fee56f8d8e61895882a5c55c9d6ff0e6c637e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 6 Nov 2023 21:48:47 +0300 Subject: [PATCH 1351/4092] Add failing test case --- .../TestSceneBeatmapEditorNavigation.cs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs index d0fa5fc737..d9757d8584 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs @@ -6,11 +6,15 @@ using NUnit.Framework; using osu.Framework.Extensions; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Database; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; +using osu.Game.Overlays.Dialog; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; @@ -232,6 +236,35 @@ namespace osu.Game.Tests.Visual.Navigation () => Is.EqualTo(beatmapSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0).ID)); } + [Test] + public void TestCreateNewDifficultyOnNonExistentBeatmap() + { + AddUntilStep("wait for dialog overlay", () => Game.ChildrenOfType().SingleOrDefault() != null); + + AddStep("open editor", () => Game.ChildrenOfType().Single().OnEdit.Invoke()); + AddUntilStep("wait for editor", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.IsLoaded); + AddStep("click on file", () => + { + var item = getEditor().ChildrenOfType().Single(i => i.Item.Text.Value.ToString() == "File"); + item.TriggerClick(); + }); + AddStep("click on create new difficulty", () => + { + var item = getEditor().ChildrenOfType().Single(i => i.Item.Text.Value.ToString() == "Create new difficulty"); + item.TriggerClick(); + }); + AddStep("click on catch", () => + { + var item = getEditor().ChildrenOfType().Single(i => i.Item.Text.Value.ToString() == "osu!catch"); + item.TriggerClick(); + }); + AddAssert("save dialog displayed", () => Game.ChildrenOfType().Single().CurrentDialog is PromptForSaveDialog); + + AddStep("press forget all changes", () => Game.ChildrenOfType().Single().CurrentDialog!.PerformAction()); + AddWaitStep("wait", 5); + AddAssert("editor beatmap uses catch ruleset", () => getEditorBeatmap().BeatmapInfo.Ruleset.ShortName == "fruits"); + } + private EditorBeatmap getEditorBeatmap() => getEditor().ChildrenOfType().Single(); private Editor getEditor() => (Editor)Game.ScreenStack.CurrentScreen; From b2749943e27ae31fb392b1bbad918cbd9432412e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 6 Nov 2023 21:52:46 +0300 Subject: [PATCH 1352/4092] Display "required save" popup when creating another difficulty on a new beatmap --- osu.Game/Screens/Edit/Editor.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 91c3c98f01..3136faf855 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1095,6 +1095,19 @@ namespace osu.Game.Screens.Edit protected void CreateNewDifficulty(RulesetInfo rulesetInfo) { + if (isNewBeatmap) + { + dialogOverlay.Push(new SaveRequiredPopupDialog("This beatmap will be saved in order to create another difficulty.", () => + { + if (!Save()) + return; + + CreateNewDifficulty(rulesetInfo); + })); + + return; + } + if (!rulesetInfo.Equals(editorBeatmap.BeatmapInfo.Ruleset)) { switchToNewDifficulty(rulesetInfo, false); From 38d16f620cdef1ab457ce2b82c5c9d1d91113761 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 6 Nov 2023 21:55:00 +0300 Subject: [PATCH 1353/4092] Alter test case to comply with new behaviour --- .../Visual/Navigation/TestSceneBeatmapEditorNavigation.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs index d9757d8584..b79b61202b 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs @@ -12,9 +12,7 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Database; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; -using osu.Game.Overlays.Dialog; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; @@ -258,10 +256,10 @@ namespace osu.Game.Tests.Visual.Navigation var item = getEditor().ChildrenOfType().Single(i => i.Item.Text.Value.ToString() == "osu!catch"); item.TriggerClick(); }); - AddAssert("save dialog displayed", () => Game.ChildrenOfType().Single().CurrentDialog is PromptForSaveDialog); + AddAssert("save dialog displayed", () => Game.ChildrenOfType().Single().CurrentDialog is SaveRequiredPopupDialog); - AddStep("press forget all changes", () => Game.ChildrenOfType().Single().CurrentDialog!.PerformAction()); - AddWaitStep("wait", 5); + AddStep("press save", () => Game.ChildrenOfType().Single().CurrentDialog!.PerformOkAction()); + AddUntilStep("wait for editor", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.IsLoaded); AddAssert("editor beatmap uses catch ruleset", () => getEditorBeatmap().BeatmapInfo.Ruleset.ShortName == "fruits"); } From d6e7145e1c676ff45eb820f42375500cba43c3a1 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Mon, 6 Nov 2023 20:42:40 +0100 Subject: [PATCH 1354/4092] Add new setting for GameplayDisableTaps --- osu.Game/Configuration/OsuConfigManager.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index e3f950ce2c..21079fc092 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -108,6 +108,8 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.MouseDisableWheel, false); SetDefault(OsuSetting.ConfineMouseMode, OsuConfineMouseMode.DuringGameplay); + SetDefault(OsuSetting.GameplayDisableTaps, false); + // Graphics SetDefault(OsuSetting.ShowFpsDisplay, false); @@ -332,7 +334,7 @@ namespace osu.Game.Configuration FadePlayfieldWhenHealthLow, /// - /// Disables mouse buttons clicks and touchscreen taps during gameplay. + /// Disables mouse buttons clicks during gameplay. /// MouseDisableButtons, MouseDisableWheel, @@ -412,6 +414,7 @@ namespace osu.Game.Configuration EditorLimitedDistanceSnap, ReplaySettingsOverlay, AutomaticallyDownloadMissingBeatmaps, - EditorShowSpeedChanges + EditorShowSpeedChanges, + GameplayDisableTaps, } } From c1967a5cbb640cf296832efec00b392922c398c9 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Mon, 6 Nov 2023 20:43:24 +0100 Subject: [PATCH 1355/4092] Make tests fail --- osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs index 2e62689e2c..19340aac15 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs @@ -133,8 +133,11 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestSimpleInput() + public void TestSimpleInput([Values] bool disableMouseButtons) { + // OsuSetting.MouseDisableButtons should not affect touch taps + AddStep($"{(disableMouseButtons ? "disable" : "enable")} mouse buttons", () => config.SetValue(OsuSetting.MouseDisableButtons, disableMouseButtons)); + beginTouch(TouchSource.Touch1); assertKeyCounter(1, 0); @@ -468,7 +471,7 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestInputWhileMouseButtonsDisabled() { - AddStep("Disable mouse buttons", () => config.SetValue(OsuSetting.MouseDisableButtons, true)); + AddStep("Disable gameplay taps", () => config.SetValue(OsuSetting.GameplayDisableTaps, true)); beginTouch(TouchSource.Touch1); @@ -620,6 +623,7 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("Release all touches", () => { config.SetValue(OsuSetting.MouseDisableButtons, false); + config.SetValue(OsuSetting.GameplayDisableTaps, false); foreach (TouchSource source in InputManager.CurrentState.Touch.ActiveSources) InputManager.EndTouch(new Touch(source, osuInputManager.ScreenSpaceDrawQuad.Centre)); }); From ea357bafddd9970fb2c7f06686e1604b6caca76f Mon Sep 17 00:00:00 2001 From: Susko3 Date: Mon, 6 Nov 2023 20:53:22 +0100 Subject: [PATCH 1356/4092] Fix tests by using the correct setting for touch input --- osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs | 8 +++----- osu.Game/Rulesets/UI/RulesetInputManager.cs | 6 ++++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs b/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs index 5277a1f7d6..994ec024b1 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.UI private readonly OsuInputManager osuInputManager; - private Bindable mouseDisabled = null!; + private Bindable tapsDisabled = null!; public OsuTouchInputMapper(OsuInputManager inputManager) { @@ -43,9 +43,7 @@ namespace osu.Game.Rulesets.Osu.UI [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - // The mouse button disable setting affects touch. It's a bit weird. - // This is mostly just doing the same as what is done in RulesetInputManager to match behaviour. - mouseDisabled = config.GetBindable(OsuSetting.MouseDisableButtons); + tapsDisabled = config.GetBindable(OsuSetting.GameplayDisableTaps); } // Required to handle touches outside of the playfield when screen scaling is enabled. @@ -64,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.UI : OsuAction.LeftButton; // Ignore any taps which trigger an action which is already handled. But track them for potential positional input in the future. - bool shouldResultInAction = osuInputManager.AllowGameplayInputs && !mouseDisabled.Value && trackedTouches.All(t => t.Action != action); + bool shouldResultInAction = osuInputManager.AllowGameplayInputs && !tapsDisabled.Value && trackedTouches.All(t => t.Action != action); // If we can actually accept as an action, check whether this tap was on a circle's receptor. // This case gets special handling to allow for empty-space stream tapping. diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 39b83ecca1..eb19368fc8 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -72,6 +72,7 @@ namespace osu.Game.Rulesets.UI private void load(OsuConfigManager config) { mouseDisabled = config.GetBindable(OsuSetting.MouseDisableButtons); + tapsDisabled = config.GetBindable(OsuSetting.GameplayDisableTaps); } #region Action mapping (for replays) @@ -124,6 +125,7 @@ namespace osu.Game.Rulesets.UI #region Setting application (disables etc.) private Bindable mouseDisabled; + private Bindable tapsDisabled; protected override bool Handle(UIEvent e) { @@ -147,9 +149,9 @@ namespace osu.Game.Rulesets.UI protected override bool HandleMouseTouchStateChange(TouchStateChangeEvent e) { - if (mouseDisabled.Value) + if (tapsDisabled.Value) { - // Only propagate positional data when mouse buttons are disabled. + // Only propagate positional data when taps are disabled. e = new TouchStateChangeEvent(e.State, e.Input, e.Touch, false, e.LastPosition); } From f8b5ecc92a2e303a87a23c218a24c103881cb8c5 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Mon, 6 Nov 2023 21:07:15 +0100 Subject: [PATCH 1357/4092] Update UI to use the new setting --- osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs | 2 +- osu.Game/Screens/Play/PlayerSettings/InputSettings.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs index b1b1b59429..793b707bfc 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs @@ -41,7 +41,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input Add(new SettingsCheckbox { LabelText = TouchSettingsStrings.DisableTapsDuringGameplay, - Current = osuConfig.GetBindable(OsuSetting.MouseDisableButtons) + Current = osuConfig.GetBindable(OsuSetting.GameplayDisableTaps) }); } diff --git a/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs b/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs index f6b0cddcf1..96b543d176 100644 --- a/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs @@ -27,6 +27,6 @@ namespace osu.Game.Screens.Play.PlayerSettings } [BackgroundDependencyLoader] - private void load(OsuConfigManager config) => mouseButtonsCheckbox.Current = config.GetBindable(OsuSetting.MouseDisableButtons); + private void load(OsuConfigManager config) => mouseButtonsCheckbox.Current = config.GetBindable(RuntimeInfo.IsDesktop ? OsuSetting.MouseDisableButtons : OsuSetting.GameplayDisableTaps); } } From a4ac50cf86f8cbd148eedf75b765c65ad34301b6 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Mon, 6 Nov 2023 21:10:04 +0100 Subject: [PATCH 1358/4092] Revert "Rename popup/binding string to `Toggle gameplay clicks/taps`" This reverts commit 0d8bfedf5d3693809e76471b9feb47ebf140e40b. --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- osu.Game/Input/Bindings/GlobalActionContainer.cs | 2 +- osu.Game/Localisation/GlobalActionKeyBindingStrings.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 21079fc092..c44a089c49 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -248,7 +248,7 @@ namespace osu.Game.Configuration ), new TrackedSetting(OsuSetting.MouseDisableButtons, disabledState => new SettingDescription( rawValue: !disabledState, - name: GlobalActionKeyBindingStrings.ToggleGameplayClicksTaps, + name: GlobalActionKeyBindingStrings.ToggleGameplayMouseButtons, value: disabledState ? CommonStrings.Disabled.ToLower() : CommonStrings.Enabled.ToLower(), shortcut: LookupKeyBindings(GlobalAction.ToggleGameplayMouseButtons)) ), diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index b8163cc3b1..947cd5f54f 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -237,7 +237,7 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.TakeScreenshot))] TakeScreenshot, - [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleGameplayClicksTaps))] + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleGameplayMouseButtons))] ToggleGameplayMouseButtons, [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.Back))] diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index 1bbbbdc3bc..8356c480dd 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -70,9 +70,9 @@ namespace osu.Game.Localisation public static LocalisableString TakeScreenshot => new TranslatableString(getKey(@"take_screenshot"), @"Take screenshot"); /// - /// "Toggle gameplay clicks/taps" + /// "Toggle gameplay mouse buttons" /// - public static LocalisableString ToggleGameplayClicksTaps => new TranslatableString(getKey(@"toggle_gameplay_clicks_taps"), @"Toggle gameplay clicks/taps"); + public static LocalisableString ToggleGameplayMouseButtons => new TranslatableString(getKey(@"toggle_gameplay_mouse_buttons"), @"Toggle gameplay mouse buttons"); /// /// "Back" From 01e59d134a9b8436404a1c6de4bc5cb07332dfe8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 7 Nov 2023 00:48:51 +0300 Subject: [PATCH 1359/4092] Adjust health bar settings on default components initialiser to match new layout --- osu.Game/Skinning/ArgonSkin.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 3262812d24..6c4074fd92 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -128,9 +128,11 @@ namespace osu.Game.Skinning // elements default to beneath the health bar const float components_x_offset = 50; - health.Anchor = Anchor.TopCentre; - health.Origin = Anchor.TopCentre; - health.Y = 15; + health.Anchor = Anchor.TopLeft; + health.Origin = Anchor.TopLeft; + health.BarLength.Value = 0.22f; + health.BarHeight.Value = 30f; + health.Position = new Vector2(components_x_offset, 20f); if (scoreWedge != null) { From 754e05213c452f1f1ddc1c3485ae9e4270b23979 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 7 Nov 2023 00:53:20 +0300 Subject: [PATCH 1360/4092] Update argon score wedge design --- osu.Game/Screens/Play/HUD/ArgonScoreWedge.cs | 43 -------------------- osu.Game/Skinning/ArgonSkin.cs | 2 +- 2 files changed, 1 insertion(+), 44 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonScoreWedge.cs b/osu.Game/Screens/Play/HUD/ArgonScoreWedge.cs index dc0130fb8e..fe495b421f 100644 --- a/osu.Game/Screens/Play/HUD/ArgonScoreWedge.cs +++ b/osu.Game/Screens/Play/HUD/ArgonScoreWedge.cs @@ -5,22 +5,13 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Lines; -using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Skinning; using osuTK; -using osuTK.Graphics; namespace osu.Game.Screens.Play.HUD { public partial class ArgonScoreWedge : CompositeDrawable, ISerialisableDrawable { - private SliderPath barPath = null!; - - private const float main_path_radius = 1f; - public bool UsesFixedAnchor { get; set; } [BackgroundDependencyLoader] @@ -28,30 +19,7 @@ namespace osu.Game.Screens.Play.HUD { AutoSizeAxes = Axes.Both; - const float bar_length = 430 - main_path_radius * 2; - const float bar_top = 0; - const float bar_bottom = 80; - const float curve_start = bar_length - 105; - const float curve_end = bar_length - 35; - - const float curve_smoothness = 10; - - Vector2 diagonalDir = (new Vector2(curve_end, bar_top) - new Vector2(curve_start, bar_bottom)).Normalized(); - - barPath = new SliderPath(new[] - { - new PathControlPoint(new Vector2(0, bar_bottom), PathType.Linear), - new PathControlPoint(new Vector2(curve_start - curve_smoothness, bar_bottom), PathType.Bezier), - new PathControlPoint(new Vector2(curve_start, bar_bottom)), - new PathControlPoint(new Vector2(curve_start, bar_bottom) + diagonalDir * curve_smoothness, PathType.Linear), - new PathControlPoint(new Vector2(curve_end, bar_top) - diagonalDir * curve_smoothness, PathType.Bezier), - new PathControlPoint(new Vector2(curve_end, bar_top)), - new PathControlPoint(new Vector2(curve_end + curve_smoothness, bar_top), PathType.Linear), - new PathControlPoint(new Vector2(bar_length, bar_top)), - }); - var vertices = new List(); - barPath.GetPathToProgress(vertices, 0, 1); InternalChildren = new Drawable[] { @@ -66,17 +34,6 @@ namespace osu.Game.Screens.Play.HUD WedgeHeight = { Value = 72 }, Position = new Vector2(4, 5) }, - new SmoothPath - { - Colour = Color4.White, - PathRadius = 1f, - Vertices = vertices, - }, - new Circle - { - Y = bar_bottom - 1.5f + main_path_radius, - Size = new Vector2(300f, 3f), - } }; } } diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 6c4074fd92..42ab93d3c5 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -136,7 +136,7 @@ namespace osu.Game.Skinning if (scoreWedge != null) { - scoreWedge.Position = new Vector2(-50, 50); + scoreWedge.Position = new Vector2(-50, 15); if (score != null) score.Position = new Vector2(components_x_offset, scoreWedge.Y + 15); From 4c7db4c2625483d557f1e4b6964579adcb7a0060 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 7 Nov 2023 00:49:22 +0300 Subject: [PATCH 1361/4092] Make score counter right-aligned --- osu.Game/Skinning/ArgonSkin.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 42ab93d3c5..e00973f710 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -139,7 +139,10 @@ namespace osu.Game.Skinning scoreWedge.Position = new Vector2(-50, 15); if (score != null) - score.Position = new Vector2(components_x_offset, scoreWedge.Y + 15); + { + score.Origin = Anchor.TopRight; + score.Position = new Vector2(components_x_offset + 200, scoreWedge.Y + 30); + } if (accuracy != null) { From ce36884ef05dbe1664e93ef9285abb43b0849067 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 7 Nov 2023 00:54:34 +0300 Subject: [PATCH 1362/4092] Make score wireframes display up to required digits count --- .../Screens/Play/HUD/ArgonScoreCounter.cs | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs b/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs index 03635f2914..5d40dd81a3 100644 --- a/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs @@ -1,9 +1,11 @@ // 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.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -19,7 +21,7 @@ namespace osu.Game.Screens.Play.HUD public partial class ArgonScoreCounter : GameplayScoreCounter, ISerialisableDrawable { [SettingSource("Wireframe opacity", "Controls the opacity of the wire frames behind the digits.")] - public BindableFloat WireframeOpactiy { get; } = new BindableFloat(0.4f) + public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.4f) { Precision = 0.01f, MinValue = 0, @@ -28,9 +30,12 @@ namespace osu.Game.Screens.Play.HUD public bool UsesFixedAnchor { get; set; } + protected override LocalisableString FormatCount(long count) => count.ToLocalisableString(); + protected override IHasText CreateText() => new ArgonScoreTextComponent { - WireframeOpactiy = { BindTarget = WireframeOpactiy }, + RequiredDisplayDigits = { BindTarget = RequiredDisplayDigits }, + WireframeOpacity = { BindTarget = WireframeOpacity }, }; private partial class ArgonScoreTextComponent : CompositeDrawable, IHasText @@ -38,14 +43,15 @@ namespace osu.Game.Screens.Play.HUD private readonly ArgonScoreSpriteText wireframesPart; private readonly ArgonScoreSpriteText textPart; - public IBindable WireframeOpactiy { get; } = new BindableFloat(); + public IBindable RequiredDisplayDigits { get; } = new BindableInt(); + public IBindable WireframeOpacity { get; } = new BindableFloat(); public LocalisableString Text { get => textPart.Text; set { - wireframesPart.Text = value; + wireframesPart.Text = new string('#', Math.Max(value.ToString().Length, RequiredDisplayDigits.Value)); textPart.Text = value; } } @@ -56,15 +62,23 @@ namespace osu.Game.Screens.Play.HUD InternalChildren = new[] { - wireframesPart = new ArgonScoreSpriteText(@"wireframes"), - textPart = new ArgonScoreSpriteText(), + wireframesPart = new ArgonScoreSpriteText(@"wireframes") + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, + textPart = new ArgonScoreSpriteText + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, }; } protected override void LoadComplete() { base.LoadComplete(); - WireframeOpactiy.BindValueChanged(v => wireframesPart.Alpha = v.NewValue, true); + WireframeOpacity.BindValueChanged(v => wireframesPart.Alpha = v.NewValue, true); } private partial class ArgonScoreSpriteText : OsuSpriteText From 7c1c62ba8aef898bddaa6c07124a1053251bdf8a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 7 Nov 2023 01:58:26 +0300 Subject: [PATCH 1363/4092] Remove argon combo wedge and update combo counter position --- osu.Game/Screens/Play/HUD/ArgonComboWedge.cs | 27 -------------------- osu.Game/Skinning/ArgonSkin.cs | 21 +++++---------- 2 files changed, 7 insertions(+), 41 deletions(-) delete mode 100644 osu.Game/Screens/Play/HUD/ArgonComboWedge.cs diff --git a/osu.Game/Screens/Play/HUD/ArgonComboWedge.cs b/osu.Game/Screens/Play/HUD/ArgonComboWedge.cs deleted file mode 100644 index 6da3727505..0000000000 --- a/osu.Game/Screens/Play/HUD/ArgonComboWedge.cs +++ /dev/null @@ -1,27 +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.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Skinning; - -namespace osu.Game.Screens.Play.HUD -{ - public partial class ArgonComboWedge : CompositeDrawable, ISerialisableDrawable - { - public bool UsesFixedAnchor { get; set; } - - [BackgroundDependencyLoader] - private void load() - { - AutoSizeAxes = Axes.Both; - - InternalChild = new ArgonWedgePiece - { - WedgeWidth = { Value = 186 }, - WedgeHeight = { Value = 33 }, - }; - } - } -} diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index e00973f710..82c150ced7 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -118,7 +118,6 @@ namespace osu.Game.Skinning var scoreWedge = container.OfType().FirstOrDefault(); var score = container.OfType().FirstOrDefault(); var accuracy = container.OfType().FirstOrDefault(); - var comboWedge = container.OfType().FirstOrDefault(); var combo = container.OfType().FirstOrDefault(); var songProgress = container.OfType().FirstOrDefault(); var keyCounter = container.OfType().FirstOrDefault(); @@ -153,18 +152,6 @@ namespace osu.Game.Skinning } } - if (comboWedge != null) - { - comboWedge.Position = new Vector2(-12, 130); - - if (combo != null) - { - combo.Anchor = Anchor.TopLeft; - combo.Origin = Anchor.TopLeft; - combo.Position = new Vector2(components_x_offset, comboWedge.Y - 2); - } - } - var hitError = container.OfType().FirstOrDefault(); if (hitError != null) @@ -199,6 +186,13 @@ namespace osu.Game.Skinning keyCounter.Origin = Anchor.BottomRight; keyCounter.Position = new Vector2(-(hitError.Width + padding), -(padding * 2 + song_progress_offset_height)); } + + if (combo != null && hitError != null) + { + combo.Anchor = Anchor.BottomLeft; + combo.Origin = Anchor.BottomLeft; + combo.Position = new Vector2(hitError.Width + padding, -50); + } } } }) @@ -209,7 +203,6 @@ namespace osu.Game.Skinning new ArgonScoreWedge(), new ArgonScoreCounter(), new ArgonAccuracyCounter(), - new ArgonComboWedge(), new ArgonComboCounter(), new BarHitErrorMeter(), new BarHitErrorMeter(), From 0dbba13686388559a25558ebbd4a4fd17c858e1d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 7 Nov 2023 01:59:00 +0300 Subject: [PATCH 1364/4092] Split argon score sprite text and update combo counter design --- .../Screens/Play/HUD/ArgonComboCounter.cs | 20 ++- .../Play/HUD/ArgonCounterTextComponent.cs | 152 ++++++++++++++++++ .../Screens/Play/HUD/ArgonScoreCounter.cs | 106 +----------- 3 files changed, 167 insertions(+), 111 deletions(-) create mode 100644 osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs diff --git a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs index 28c97c53aa..6a7d5ff665 100644 --- a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs @@ -2,28 +2,36 @@ // 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.Sprites; using osu.Framework.Localisation; -using osu.Game.Graphics.Sprites; +using osu.Game.Configuration; using osu.Game.Rulesets.Scoring; namespace osu.Game.Screens.Play.HUD { public partial class ArgonComboCounter : ComboCounter { + [SettingSource("Wireframe opacity", "Controls the opacity of the wire frames behind the digits.")] + public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.4f) + { + Precision = 0.01f, + MinValue = 0, + MaxValue = 1, + }; + [BackgroundDependencyLoader] private void load(ScoreProcessor scoreProcessor) { Current.BindTo(scoreProcessor.Combo); } - protected override OsuSpriteText CreateSpriteText() - => base.CreateSpriteText().With(s => s.Font = FontUsage.Default.With(size: 36f)); + protected override LocalisableString FormatCount(int count) => $@"{count}x"; - protected override LocalisableString FormatCount(int count) + protected override IHasText CreateText() => new ArgonCounterTextComponent(Anchor.TopLeft, "COMBO") { - return $@"{count}x"; - } + WireframeOpacity = { BindTarget = WireframeOpacity }, + }; } } diff --git a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs new file mode 100644 index 0000000000..9545168a46 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs @@ -0,0 +1,152 @@ +// 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.Linq; +using System.Threading.Tasks; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Framework.Text; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Screens.Play.HUD +{ + public partial class ArgonCounterTextComponent : CompositeDrawable, IHasText + { + private readonly LocalisableString? label; + + private readonly ArgonCounterSpriteText wireframesPart; + private readonly ArgonCounterSpriteText textPart; + + public IBindable RequiredDisplayDigits { get; } = new BindableInt(); + public IBindable WireframeOpacity { get; } = new BindableFloat(); + + public LocalisableString Text + { + get => textPart.Text; + set + { + wireframesPart.Text = new string('#', Math.Max(value.ToString().Count(char.IsDigit), RequiredDisplayDigits.Value)); + textPart.Text = value; + } + } + + public ArgonCounterTextComponent(Anchor anchor, LocalisableString? label = null) + { + Anchor = anchor; + Origin = anchor; + + this.label = label; + + wireframesPart = new ArgonCounterSpriteText(@"wireframes") + { + Anchor = anchor, + Origin = anchor, + }; + textPart = new ArgonCounterSpriteText + { + Anchor = anchor, + Origin = anchor, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AutoSizeAxes = Axes.Both; + + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new OsuSpriteText + { + Alpha = label != null ? 1 : 0, + Text = label.GetValueOrDefault(), + Font = OsuFont.Torus.With(size: 12, weight: FontWeight.Bold), + Colour = colours.Blue0, + Margin = new MarginPadding { Left = 2.5f }, + }, + new Container + { + AutoSizeAxes = Axes.Both, + Children = new[] + { + wireframesPart, + textPart, + } + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + WireframeOpacity.BindValueChanged(v => wireframesPart.Alpha = v.NewValue, true); + } + + private partial class ArgonCounterSpriteText : OsuSpriteText + { + private readonly string? glyphLookupOverride; + + private GlyphStore glyphStore = null!; + + protected override char FixedWidthReferenceCharacter => '5'; + + public ArgonCounterSpriteText(string? glyphLookupOverride = null) + { + this.glyphLookupOverride = glyphLookupOverride; + + Shadow = false; + UseFullGlyphHeight = false; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + // todo: rename font + Font = new FontUsage(@"argon-score", 1, fixedWidth: true); + Spacing = new Vector2(-2, 0); + + glyphStore = new GlyphStore(skin, glyphLookupOverride); + } + + protected override TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) => base.CreateTextBuilder(glyphStore); + + private class GlyphStore : ITexturedGlyphLookupStore + { + private readonly ISkin skin; + private readonly string? glyphLookupOverride; + + public GlyphStore(ISkin skin, string? glyphLookupOverride) + { + this.skin = skin; + this.glyphLookupOverride = glyphLookupOverride; + } + + public ITexturedCharacterGlyph? Get(string fontName, char character) + { + string lookup = glyphLookupOverride ?? character.ToString(); + var texture = skin.GetTexture($"{fontName}-{lookup}"); + + if (texture == null) + return null; + + return new TexturedCharacterGlyph(new CharacterGlyph(character, 0, 0, texture.Width, texture.Height, null), texture, 0.125f); + } + + public Task GetAsync(string fontName, char character) => Task.Run(() => Get(fontName, character)); + } + } + } +} diff --git a/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs b/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs index 5d40dd81a3..636565f181 100644 --- a/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs @@ -1,20 +1,13 @@ // 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.Threading.Tasks; -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; -using osu.Framework.Text; using osu.Game.Configuration; -using osu.Game.Graphics.Sprites; using osu.Game.Skinning; -using osuTK; namespace osu.Game.Screens.Play.HUD { @@ -32,107 +25,10 @@ namespace osu.Game.Screens.Play.HUD protected override LocalisableString FormatCount(long count) => count.ToLocalisableString(); - protected override IHasText CreateText() => new ArgonScoreTextComponent + protected override IHasText CreateText() => new ArgonCounterTextComponent(Anchor.TopRight) { RequiredDisplayDigits = { BindTarget = RequiredDisplayDigits }, WireframeOpacity = { BindTarget = WireframeOpacity }, }; - - private partial class ArgonScoreTextComponent : CompositeDrawable, IHasText - { - private readonly ArgonScoreSpriteText wireframesPart; - private readonly ArgonScoreSpriteText textPart; - - public IBindable RequiredDisplayDigits { get; } = new BindableInt(); - public IBindable WireframeOpacity { get; } = new BindableFloat(); - - public LocalisableString Text - { - get => textPart.Text; - set - { - wireframesPart.Text = new string('#', Math.Max(value.ToString().Length, RequiredDisplayDigits.Value)); - textPart.Text = value; - } - } - - public ArgonScoreTextComponent() - { - AutoSizeAxes = Axes.Both; - - InternalChildren = new[] - { - wireframesPart = new ArgonScoreSpriteText(@"wireframes") - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - }, - textPart = new ArgonScoreSpriteText - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - }, - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - WireframeOpacity.BindValueChanged(v => wireframesPart.Alpha = v.NewValue, true); - } - - private partial class ArgonScoreSpriteText : OsuSpriteText - { - private readonly string? glyphLookupOverride; - - private GlyphStore glyphStore = null!; - - protected override char FixedWidthReferenceCharacter => '5'; - - public ArgonScoreSpriteText(string? glyphLookupOverride = null) - { - this.glyphLookupOverride = glyphLookupOverride; - - Shadow = false; - UseFullGlyphHeight = false; - } - - [BackgroundDependencyLoader] - private void load(ISkinSource skin) - { - Font = new FontUsage(@"argon-score", 1, fixedWidth: true); - Spacing = new Vector2(-2, 0); - - glyphStore = new GlyphStore(skin, glyphLookupOverride); - } - - protected override TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) => base.CreateTextBuilder(glyphStore); - - private class GlyphStore : ITexturedGlyphLookupStore - { - private readonly ISkin skin; - private readonly string? glyphLookupOverride; - - public GlyphStore(ISkin skin, string? glyphLookupOverride) - { - this.skin = skin; - this.glyphLookupOverride = glyphLookupOverride; - } - - public ITexturedCharacterGlyph? Get(string fontName, char character) - { - string lookup = glyphLookupOverride ?? character.ToString(); - var texture = skin.GetTexture($"{fontName}-{lookup}"); - - if (texture == null) - return null; - - return new TexturedCharacterGlyph(new CharacterGlyph(character, 0, 0, texture.Width, texture.Height, null), texture, 0.125f); - } - - public Task GetAsync(string fontName, char character) => Task.Run(() => Get(fontName, character)); - } - } - } } } From 05d941871860fc94db9d92d722d44eb4f190766a Mon Sep 17 00:00:00 2001 From: Susko3 Date: Tue, 7 Nov 2023 00:13:46 +0100 Subject: [PATCH 1365/4092] Rename setting to `TouchDisableGameplayTaps` for better visibility when searching --- osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs | 4 ++-- osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs | 2 +- osu.Game/Configuration/OsuConfigManager.cs | 4 ++-- osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs | 2 +- osu.Game/Rulesets/UI/RulesetInputManager.cs | 2 +- osu.Game/Screens/Play/PlayerSettings/InputSettings.cs | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs index 19340aac15..25fe8170b1 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs @@ -471,7 +471,7 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestInputWhileMouseButtonsDisabled() { - AddStep("Disable gameplay taps", () => config.SetValue(OsuSetting.GameplayDisableTaps, true)); + AddStep("Disable gameplay taps", () => config.SetValue(OsuSetting.TouchDisableGameplayTaps, true)); beginTouch(TouchSource.Touch1); @@ -623,7 +623,7 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("Release all touches", () => { config.SetValue(OsuSetting.MouseDisableButtons, false); - config.SetValue(OsuSetting.GameplayDisableTaps, false); + config.SetValue(OsuSetting.TouchDisableGameplayTaps, false); foreach (TouchSource source in InputManager.CurrentState.Touch.ActiveSources) InputManager.EndTouch(new Touch(source, osuInputManager.ScreenSpaceDrawQuad.Centre)); }); diff --git a/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs b/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs index 994ec024b1..e815d7873e 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.UI [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - tapsDisabled = config.GetBindable(OsuSetting.GameplayDisableTaps); + tapsDisabled = config.GetBindable(OsuSetting.TouchDisableGameplayTaps); } // Required to handle touches outside of the playfield when screen scaling is enabled. diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index c44a089c49..6ef55ab919 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -108,7 +108,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.MouseDisableWheel, false); SetDefault(OsuSetting.ConfineMouseMode, OsuConfineMouseMode.DuringGameplay); - SetDefault(OsuSetting.GameplayDisableTaps, false); + SetDefault(OsuSetting.TouchDisableGameplayTaps, false); // Graphics SetDefault(OsuSetting.ShowFpsDisplay, false); @@ -415,6 +415,6 @@ namespace osu.Game.Configuration ReplaySettingsOverlay, AutomaticallyDownloadMissingBeatmaps, EditorShowSpeedChanges, - GameplayDisableTaps, + TouchDisableGameplayTaps, } } diff --git a/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs index 793b707bfc..0056de6674 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs @@ -41,7 +41,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input Add(new SettingsCheckbox { LabelText = TouchSettingsStrings.DisableTapsDuringGameplay, - Current = osuConfig.GetBindable(OsuSetting.GameplayDisableTaps) + Current = osuConfig.GetBindable(OsuSetting.TouchDisableGameplayTaps) }); } diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index eb19368fc8..35d05b87c0 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.UI private void load(OsuConfigManager config) { mouseDisabled = config.GetBindable(OsuSetting.MouseDisableButtons); - tapsDisabled = config.GetBindable(OsuSetting.GameplayDisableTaps); + tapsDisabled = config.GetBindable(OsuSetting.TouchDisableGameplayTaps); } #region Action mapping (for replays) diff --git a/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs b/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs index 96b543d176..8a6e2759e3 100644 --- a/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs @@ -27,6 +27,6 @@ namespace osu.Game.Screens.Play.PlayerSettings } [BackgroundDependencyLoader] - private void load(OsuConfigManager config) => mouseButtonsCheckbox.Current = config.GetBindable(RuntimeInfo.IsDesktop ? OsuSetting.MouseDisableButtons : OsuSetting.GameplayDisableTaps); + private void load(OsuConfigManager config) => mouseButtonsCheckbox.Current = config.GetBindable(RuntimeInfo.IsDesktop ? OsuSetting.MouseDisableButtons : OsuSetting.DisableTapsDuringGameplay); } } From 7385c3c97bb48b507127dd8709d658788adbaf78 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Tue, 7 Nov 2023 00:17:15 +0100 Subject: [PATCH 1366/4092] Move `InputSettings` children creation code to BDL - Avoids now obsolete variable name - Makes changing to touch detection easier (access to session statics in BDL) --- .../Play/PlayerSettings/InputSettings.cs | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs b/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs index 8a6e2759e3..1387e01305 100644 --- a/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs @@ -11,22 +11,23 @@ namespace osu.Game.Screens.Play.PlayerSettings { public partial class InputSettings : PlayerSettingsGroup { - private readonly PlayerCheckbox mouseButtonsCheckbox; - public InputSettings() : base("Input Settings") { - Children = new Drawable[] - { - mouseButtonsCheckbox = new PlayerCheckbox - { - // TODO: change to touchscreen detection once https://github.com/ppy/osu/pull/25348 makes it in - LabelText = RuntimeInfo.IsDesktop ? MouseSettingsStrings.DisableClicksDuringGameplay : TouchSettingsStrings.DisableTapsDuringGameplay - } - }; } [BackgroundDependencyLoader] - private void load(OsuConfigManager config) => mouseButtonsCheckbox.Current = config.GetBindable(RuntimeInfo.IsDesktop ? OsuSetting.MouseDisableButtons : OsuSetting.DisableTapsDuringGameplay); + private void load(OsuConfigManager config) + { + Children = new Drawable[] + { + new PlayerCheckbox + { + // TODO: change to touchscreen detection once https://github.com/ppy/osu/pull/25348 makes it in + LabelText = RuntimeInfo.IsDesktop ? MouseSettingsStrings.DisableClicksDuringGameplay : TouchSettingsStrings.DisableTapsDuringGameplay, + Current = config.GetBindable(RuntimeInfo.IsDesktop ? OsuSetting.MouseDisableButtons : OsuSetting.TouchDisableGameplayTaps) + } + }; + } } } From 8e8a88cfaf4852d4e80611677cb5948806096704 Mon Sep 17 00:00:00 2001 From: ratinfx Date: Tue, 7 Nov 2023 00:54:15 +0100 Subject: [PATCH 1367/4092] Removed nullable line from Test --- osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs index f65aff922e..77dcbf069b 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using NUnit.Framework; From 0834b79cc7fcb78873e381754f709deba0be4693 Mon Sep 17 00:00:00 2001 From: ratinfx Date: Tue, 7 Nov 2023 00:56:24 +0100 Subject: [PATCH 1368/4092] Renamed method and moved Notifications inside --- osu.Game/OsuGame.cs | 15 ++++++--------- osu.Game/Screens/Edit/Editor.cs | 16 ++++++++++++---- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index a9d4927e33..be1776a330 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -562,18 +562,15 @@ namespace osu.Game { if (ScreenStack.CurrentScreen is not Editor editor) { - postNotification(EditorStrings.MustBeInEdit); + Schedule(() => Notifications.Post(new SimpleNotification + { + Icon = FontAwesome.Solid.ExclamationTriangle, + Text = EditorStrings.MustBeInEdit + })); return; } - editor.SeekAndSelectHitObjects(timestamp, onError: postNotification); - return; - - void postNotification(LocalisableString message) => Schedule(() => Notifications.Post(new SimpleNotification - { - Icon = FontAwesome.Solid.ExclamationTriangle, - Text = message - })); + editor.HandleTimestamp(timestamp); } /// diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 60d26d9ec0..9e0671e91d 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable @@ -1138,13 +1138,17 @@ namespace osu.Game.Screens.Edit loader?.CancelPendingDifficultySwitch(); } - public void SeekAndSelectHitObjects(string timestamp, Action onError) + public void HandleTimestamp(string timestamp) { string[] groups = EditorTimestampParser.GetRegexGroups(timestamp); if (groups.Length != 2 || string.IsNullOrEmpty(groups[0])) { - onError.Invoke(EditorStrings.FailedToProcessTimestamp); + Schedule(() => notifications.Post(new SimpleNotification + { + Icon = FontAwesome.Solid.ExclamationTriangle, + Text = EditorStrings.FailedToProcessTimestamp + })); return; } @@ -1156,7 +1160,11 @@ namespace osu.Game.Screens.Edit // Limit timestamp link length at 30000 min (50 hr) to avoid parsing issues if (timeMinutes.Length > 5 || double.Parse(timeMinutes) > 30_000) { - onError.Invoke(EditorStrings.TooLongTimestamp); + Schedule(() => notifications.Post(new SimpleNotification + { + Icon = FontAwesome.Solid.ExclamationTriangle, + Text = EditorStrings.TooLongTimestamp + })); return; } From 44f127c8a86c9fa381118571c5af5e26f8cf141d Mon Sep 17 00:00:00 2001 From: ratinfx Date: Tue, 7 Nov 2023 01:02:45 +0100 Subject: [PATCH 1369/4092] Renamed method and made private --- .../Edit/Compose/Components/EditorBlueprintContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs index 60959ca27a..b68a690097 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.Edit.Compose.Components // This makes sure HitObjects will have active Blueprints ready to display // after clicking on an Editor Timestamp/Link - Beatmap.SelectedHitObjects.CollectionChanged += SetHitObjectsAlive; + Beatmap.SelectedHitObjects.CollectionChanged += keepHitObjectsAlive; if (Composer != null) { @@ -149,7 +149,7 @@ namespace osu.Game.Screens.Edit.Compose.Components SelectedItems.AddRange(Beatmap.HitObjects.Except(SelectedItems).ToArray()); } - protected void SetHitObjectsAlive(object sender, NotifyCollectionChangedEventArgs e) + private void keepHitObjectsAlive(object sender, NotifyCollectionChangedEventArgs e) { if (e == null || e.Action != NotifyCollectionChangedAction.Add || e.NewItems == null) return; @@ -180,7 +180,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { Beatmap.HitObjectAdded -= AddBlueprintFor; Beatmap.HitObjectRemoved -= RemoveBlueprintFor; - Beatmap.SelectedHitObjects.CollectionChanged -= SetHitObjectsAlive; + Beatmap.SelectedHitObjects.CollectionChanged -= keepHitObjectsAlive; } usageEventBuffer?.Dispose(); From aa87e0a44d469bbdfb9abe9bca13ea0323eda774 Mon Sep 17 00:00:00 2001 From: ratinfx Date: Tue, 7 Nov 2023 01:36:58 +0100 Subject: [PATCH 1370/4092] HitObject Selection logic and separation for gamemodes + moved time_regex into EditorTimestampParser --- .../Edit/ManiaHitObjectComposer.cs | 19 ++++++- .../Edit/OsuHitObjectComposer.cs | 13 ++++- .../Editing/TestSceneOpenEditorTimestamp.cs | 30 ++++------- osu.Game/Online/Chat/MessageFormatter.cs | 7 +-- osu.Game/OsuGame.cs | 6 +-- .../Edit/EditorTimestampParser.cs | 54 +++++-------------- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 13 +++++ osu.Game/Screens/Edit/Editor.cs | 50 ++++++++--------- 8 files changed, 95 insertions(+), 97 deletions(-) rename osu.Game/{Screens => Rulesets}/Edit/EditorTimestampParser.cs (50%) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index b9db4168f4..d217f04651 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; @@ -11,6 +12,7 @@ using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit.Compose.Components; @@ -49,6 +51,21 @@ namespace osu.Game.Rulesets.Mania.Edit }; public override string ConvertSelectionToString() - => string.Join(',', EditorBeatmap.SelectedHitObjects.Cast().OrderBy(h => h.StartTime).Select(h => $"{h.StartTime}|{h.Column}")); + => string.Join(ObjectSeparator, EditorBeatmap.SelectedHitObjects.Cast().OrderBy(h => h.StartTime).Select(h => $"{h.StartTime}|{h.Column}")); + + public override bool HandleHitObjectSelection(HitObject hitObject, string objectInfo) + { + if (hitObject is not ManiaHitObject maniaHitObject) + return false; + + double[] split = objectInfo.Split('|').Select(double.Parse).ToArray(); + if (split.Length != 2) + return false; + + double timeValue = split[0]; + double columnValue = split[1]; + return Math.Abs(maniaHitObject.StartTime - timeValue) < 0.5 + && Math.Abs(maniaHitObject.Column - columnValue) < 0.5; + } } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 0f8c960b65..0c63cf71d8 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -104,7 +104,18 @@ namespace osu.Game.Rulesets.Osu.Edit => new OsuBlueprintContainer(this); public override string ConvertSelectionToString() - => string.Join(',', selectedHitObjects.Cast().OrderBy(h => h.StartTime).Select(h => (h.IndexInCurrentCombo + 1).ToString())); + => string.Join(ObjectSeparator, selectedHitObjects.Cast().OrderBy(h => h.StartTime).Select(h => (h.IndexInCurrentCombo + 1).ToString())); + + public override bool HandleHitObjectSelection(HitObject hitObject, string objectInfo) + { + if (hitObject is not OsuHitObject osuHitObject) + return false; + + if (!int.TryParse(objectInfo, out int comboValue) || comboValue < 1) + return false; + + return osuHitObject.IndexInCurrentCombo + 1 == comboValue; + } private DistanceSnapGrid distanceSnapGrid; private Container distanceSnapGridContainer; diff --git a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs index 77dcbf069b..f7b976702a 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs @@ -29,11 +29,14 @@ namespace osu.Game.Tests.Visual.Editing protected EditorBeatmap EditorBeatmap => Editor.ChildrenOfType().Single(); protected EditorClock EditorClock => Editor.ChildrenOfType().Single(); - protected void AddStepClickLink(string timestamp, string step = "") + protected void AddStepClickLink(string timestamp, string step = "", bool waitForSeek = true) { AddStep($"{step} {timestamp}", () => Game.HandleLink(new LinkDetails(LinkAction.OpenEditorTimestamp, timestamp)) ); + + if (waitForSeek) + AddUntilStep("wait for seek", () => EditorClock.SeekingOrStopped.Value); } protected void AddStepScreenModeTo(EditorScreenMode screenMode) @@ -76,7 +79,7 @@ namespace osu.Game.Tests.Visual.Editing .Any(); } - private bool checkSnapAndSelectColumn(double startTime, IReadOnlyCollection<(int, int)> columnPairs = null) + private bool checkSnapAndSelectColumn(double startTime, IReadOnlyCollection<(int, int)>? columnPairs = null) { bool checkColumns = columnPairs != null ? EditorBeatmap.SelectedHitObjects.All(x => columnPairs.Any(col => isNoteAt(x, col.Item1, col.Item2))) @@ -123,7 +126,7 @@ namespace osu.Game.Tests.Visual.Editing { RulesetInfo rulesetInfo = new OsuRuleset().RulesetInfo; - AddStepClickLink("00:00:000"); + AddStepClickLink("00:00:000", waitForSeek: false); AddAssert("recieved 'must be in edit'", () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEdit) == 1 ); @@ -131,7 +134,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo.Invoke()); AddAssert("entered song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect); - AddStepClickLink("00:00:000 (1)"); + AddStepClickLink("00:00:000 (1)", waitForSeek: false); AddAssert("recieved 'must be in edit'", () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEdit) == 2 ); @@ -139,27 +142,12 @@ namespace osu.Game.Tests.Visual.Editing SetUpEditor(rulesetInfo); AddAssert("is editor Osu", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); - AddStepClickLink("00:000", "invalid link"); + AddStepClickLink("00:000", "invalid link", waitForSeek: false); AddAssert("recieved 'failed to process'", () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp) == 1 ); - AddStepClickLink("00:00:00:000", "invalid link"); - AddAssert("recieved 'failed to process'", () => - Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp) == 2 - ); - - AddStepClickLink("00:00:000 ()", "invalid link"); - AddAssert("recieved 'failed to process'", () => - Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp) == 3 - ); - - AddStepClickLink("00:00:000 (-1)", "invalid link"); - AddAssert("recieved 'failed to process'", () => - Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp) == 4 - ); - - AddStepClickLink("50000:00:000", "too long link"); + AddStepClickLink("50000:00:000", "too long link", waitForSeek: false); AddAssert("recieved 'too long'", () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.TooLongTimestamp) == 1 ); diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 667175117f..9a194dba47 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Rulesets.Edit; namespace osu.Game.Online.Chat { @@ -41,10 +42,6 @@ namespace osu.Game.Online.Chat @"(?:#(?:[a-z0-9$_\+!\*\',;:\(\)@&=\/~-]|%[0-9a-f]{2})*)?)?)", RegexOptions.IgnoreCase); - // 00:00:000 (1,2,3) - test - // regex from https://github.com/ppy/osu-web/blob/651a9bac2b60d031edd7e33b8073a469bf11edaa/resources/assets/coffee/_classes/beatmap-discussion-helper.coffee#L10 - private static readonly Regex time_regex = new Regex(@"\b(((\d{2,}):([0-5]\d)[:.](\d{3}))(\s\((?:\d+[,|])*\d+\))?)"); - // #osu private static readonly Regex channel_regex = new Regex(@"(#[a-zA-Z]+[a-zA-Z0-9]+)"); @@ -274,7 +271,7 @@ namespace osu.Game.Online.Chat handleAdvanced(advanced_link_regex, result, startIndex); // handle editor times - handleMatches(time_regex, "{0}", $@"{OsuGameBase.OSU_PROTOCOL}edit/{{0}}", result, startIndex, LinkAction.OpenEditorTimestamp); + handleMatches(EditorTimestampParser.TIME_REGEX, "{0}", $@"{OsuGameBase.OSU_PROTOCOL}edit/{{0}}", result, startIndex, LinkAction.OpenEditorTimestamp); // handle channels handleMatches(channel_regex, "{0}", $@"{OsuGameBase.OSU_PROTOCOL}chan/{{0}}", result, startIndex, LinkAction.OpenChannel); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index be1776a330..cde8ee1457 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -563,10 +563,10 @@ namespace osu.Game if (ScreenStack.CurrentScreen is not Editor editor) { Schedule(() => Notifications.Post(new SimpleNotification - { - Icon = FontAwesome.Solid.ExclamationTriangle, + { + Icon = FontAwesome.Solid.ExclamationTriangle, Text = EditorStrings.MustBeInEdit - })); + })); return; } diff --git a/osu.Game/Screens/Edit/EditorTimestampParser.cs b/osu.Game/Rulesets/Edit/EditorTimestampParser.cs similarity index 50% rename from osu.Game/Screens/Edit/EditorTimestampParser.cs rename to osu.Game/Rulesets/Edit/EditorTimestampParser.cs index 2d8f8a8f4c..4e5a696102 100644 --- a/osu.Game/Screens/Edit/EditorTimestampParser.cs +++ b/osu.Game/Rulesets/Edit/EditorTimestampParser.cs @@ -7,41 +7,43 @@ using System.Diagnostics; using System.Linq; using System.Text.RegularExpressions; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; -namespace osu.Game.Screens.Edit +namespace osu.Game.Rulesets.Edit { public static class EditorTimestampParser { - private static readonly Regex timestamp_regex = new Regex(@"^(\d+:\d+:\d+)(?: \((\d+(?:[|,]\d+)*)\))?$", RegexOptions.Compiled); + // 00:00:000 (1,2,3) - test + // regex from https://github.com/ppy/osu-web/blob/651a9bac2b60d031edd7e33b8073a469bf11edaa/resources/assets/coffee/_classes/beatmap-discussion-helper.coffee#L10 + public static readonly Regex TIME_REGEX = new Regex(@"\b(((\d{2,}):([0-5]\d)[:.](\d{3}))(\s\((?:\d+[,|])*\d+\))?)"); public static string[] GetRegexGroups(string timestamp) { - Match match = timestamp_regex.Match(timestamp); - return match.Success - ? match.Groups.Values.Where(x => x is not Match).Select(x => x.Value).ToArray() + Match match = TIME_REGEX.Match(timestamp); + string[] result = match.Success + ? match.Groups.Values.Where(x => x is not Match && !x.Value.Contains(':')).Select(x => x.Value).ToArray() : Array.Empty(); + return result; } - public static double GetTotalMilliseconds(string timeGroup) + public static double GetTotalMilliseconds(params string[] timesGroup) { - int[] times = timeGroup.Split(':').Select(int.Parse).ToArray(); + int[] times = timesGroup.Select(int.Parse).ToArray(); Debug.Assert(times.Length == 3); return (times[0] * 60 + times[1]) * 1_000 + times[2]; } - public static List GetSelectedHitObjects(IReadOnlyList editorHitObjects, string objectsGroup, double position) + public static List GetSelectedHitObjects(HitObjectComposer composer, IReadOnlyList editorHitObjects, string objectsGroup, double position) { List hitObjects = editorHitObjects.Where(x => x.StartTime >= position).ToList(); List selectedObjects = new List(); - string[] objectsToSelect = objectsGroup.Split(',').ToArray(); + string[] objectsToSelect = objectsGroup.Split(composer.ObjectSeparator).ToArray(); foreach (string objectInfo in objectsToSelect) { - HitObject? current = hitObjects.FirstOrDefault(x => shouldHitObjectBeSelected(x, objectInfo)); + HitObject? current = hitObjects.FirstOrDefault(x => composer.HandleHitObjectSelection(x, objectInfo)); if (current == null) continue; @@ -67,35 +69,5 @@ namespace osu.Game.Screens.Edit return selectedObjects; } - - private static bool shouldHitObjectBeSelected(HitObject hitObject, string objectInfo) - { - switch (hitObject) - { - // (combo) - case IHasComboInformation comboInfo: - { - if (!double.TryParse(objectInfo, out double comboValue) || comboValue < 1) - return false; - - return comboInfo.IndexInCurrentCombo + 1 == comboValue; - } - - // (time|column) - case IHasColumn column: - { - double[] split = objectInfo.Split('|').Select(double.Parse).ToArray(); - if (split.Length != 2) - return false; - - double timeValue = split[0]; - double columnValue = split[1]; - return hitObject.StartTime == timeValue && column.Column == columnValue; - } - - default: - return false; - } - } } } diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 07e5869e28..f6cddcc0d2 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -528,6 +528,19 @@ namespace osu.Game.Rulesets.Edit public virtual string ConvertSelectionToString() => string.Empty; + /// + /// The custom logic that decides whether a HitObject should be selected when clicking an editor timestamp link + /// + /// The hitObject being checked + /// A single hitObject's information created with + /// Whether a HitObject should be selected or not + public virtual bool HandleHitObjectSelection(HitObject hitObject, string objectInfo) => false; + + /// + /// A character that separates the selection in + /// + public virtual char ObjectSeparator => ','; + #region IPositionSnapProvider public abstract SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 9e0671e91d..592e6625cc 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable @@ -14,6 +14,7 @@ using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Framework.Input.Bindings; @@ -1142,7 +1143,7 @@ namespace osu.Game.Screens.Edit { string[] groups = EditorTimestampParser.GetRegexGroups(timestamp); - if (groups.Length != 2 || string.IsNullOrEmpty(groups[0])) + if (groups.Length != 4 || string.IsNullOrEmpty(groups[0])) { Schedule(() => notifications.Post(new SimpleNotification { @@ -1152,13 +1153,14 @@ namespace osu.Game.Screens.Edit return; } - string timeGroup = groups[0]; - string objectsGroup = groups[1]; - string timeMinutes = timeGroup.Split(':').FirstOrDefault() ?? string.Empty; + string timeMin = groups[0]; + string timeSec = groups[1]; + string timeMss = groups[2]; + string objectsGroup = groups[3].Replace("(", "").Replace(")", "").Trim(); // Currently, lazer chat highlights infinite-long editor links like `10000000000:00:000 (1)` // Limit timestamp link length at 30000 min (50 hr) to avoid parsing issues - if (timeMinutes.Length > 5 || double.Parse(timeMinutes) > 30_000) + if (string.IsNullOrEmpty(timeMin) || timeMin.Length > 5 || double.Parse(timeMin) > 30_000) { Schedule(() => notifications.Post(new SimpleNotification { @@ -1168,38 +1170,36 @@ namespace osu.Game.Screens.Edit return; } - double position = EditorTimestampParser.GetTotalMilliseconds(timeGroup); - editorBeatmap.SelectedHitObjects.Clear(); - // Only seeking is necessary + double position = EditorTimestampParser.GetTotalMilliseconds(timeMin, timeSec, timeMss); + if (string.IsNullOrEmpty(objectsGroup)) { - if (clock.IsRunning) - clock.Stop(); - - clock.Seek(position); + clock.SeekSmoothlyTo(position); return; } + // Seek to the next closest HitObject instead + HitObject nextObject = editorBeatmap.HitObjects.FirstOrDefault(x => x.StartTime >= position); + + if (nextObject != null) + position = nextObject.StartTime; + + clock.SeekSmoothlyTo(position); + if (Mode.Value != EditorScreenMode.Compose) Mode.Value = EditorScreenMode.Compose; - // Seek to the next closest HitObject - HitObject nextObject = editorBeatmap.HitObjects.FirstOrDefault(x => x.StartTime >= position); - - if (nextObject != null && nextObject.StartTime > 0) - position = nextObject.StartTime; - - List selected = EditorTimestampParser.GetSelectedHitObjects(editorBeatmap.HitObjects.ToList(), objectsGroup, position); + List selected = EditorTimestampParser.GetSelectedHitObjects( + currentScreen.Dependencies.Get(), + editorBeatmap.HitObjects.ToList(), + objectsGroup, + position + ); if (selected.Any()) editorBeatmap.SelectedHitObjects.AddRange(selected); - - if (clock.IsRunning) - clock.Stop(); - - clock.Seek(position); } public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); From bdbeb2bce4286122584e0c7f9dff61bdc5d57ca1 Mon Sep 17 00:00:00 2001 From: ratinfx Date: Tue, 7 Nov 2023 11:11:32 +0100 Subject: [PATCH 1371/4092] Renamed CollectionChanged event handler --- .../Edit/Compose/Components/EditorBlueprintContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs index b68a690097..a311054ffc 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.Edit.Compose.Components // This makes sure HitObjects will have active Blueprints ready to display // after clicking on an Editor Timestamp/Link - Beatmap.SelectedHitObjects.CollectionChanged += keepHitObjectsAlive; + Beatmap.SelectedHitObjects.CollectionChanged += selectionChanged; if (Composer != null) { @@ -149,7 +149,7 @@ namespace osu.Game.Screens.Edit.Compose.Components SelectedItems.AddRange(Beatmap.HitObjects.Except(SelectedItems).ToArray()); } - private void keepHitObjectsAlive(object sender, NotifyCollectionChangedEventArgs e) + private void selectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e == null || e.Action != NotifyCollectionChangedAction.Add || e.NewItems == null) return; @@ -180,7 +180,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { Beatmap.HitObjectAdded -= AddBlueprintFor; Beatmap.HitObjectRemoved -= RemoveBlueprintFor; - Beatmap.SelectedHitObjects.CollectionChanged -= keepHitObjectsAlive; + Beatmap.SelectedHitObjects.CollectionChanged -= selectionChanged; } usageEventBuffer?.Dispose(); From 38c9a98e67e421a965d5979425ace752517e63e2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Nov 2023 10:52:48 +0900 Subject: [PATCH 1372/4092] Add failing test coverage --- .../TestSceneResumeOverlay.cs | 34 +++++++++++++++++-- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 8 ++--- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs index 49a8254f53..25d0b0a3d3 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs @@ -1,16 +1,20 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Screens.Play; using osu.Game.Tests.Gameplay; using osu.Game.Tests.Visual; +using osuTK; namespace osu.Game.Rulesets.Osu.Tests { @@ -54,9 +58,13 @@ namespace osu.Game.Rulesets.Osu.Tests [SetUp] public void SetUp() => Schedule(loadContent); - [Test] - public void TestResume() + [TestCase(1)] + [TestCase(0.5f)] + [TestCase(2)] + public void TestResume(float cursorSize) { + AddStep($"set cursor size to {cursorSize}", () => localConfig.SetValue(OsuSetting.GameplayCursorSize, cursorSize)); + AddStep("move mouse to center", () => InputManager.MoveMouseTo(ScreenSpaceDrawQuad.Centre)); AddStep("show", () => resume.Show()); @@ -64,7 +72,27 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("click", () => osuInputManager.GameClick()); AddAssert("not dismissed", () => !resumeFired && resume.State.Value == Visibility.Visible); - AddStep("move mouse back", () => InputManager.MoveMouseTo(ScreenSpaceDrawQuad.Centre)); + AddStep("move mouse just out of range", () => + { + var resumeOverlay = this.ChildrenOfType().Single(); + var resumeOverlayCursor = resumeOverlay.ChildrenOfType().Single(); + + Vector2 offset = resumeOverlay.ToScreenSpace(new Vector2(OsuCursor.SIZE / 2)) - resumeOverlay.ToScreenSpace(Vector2.Zero); + InputManager.MoveMouseTo(resumeOverlayCursor.ScreenSpaceDrawQuad.Centre - offset - new Vector2(1)); + }); + + AddStep("click", () => osuInputManager.GameClick()); + AddAssert("not dismissed", () => !resumeFired && resume.State.Value == Visibility.Visible); + + AddStep("move mouse just within range", () => + { + var resumeOverlay = this.ChildrenOfType().Single(); + var resumeOverlayCursor = resumeOverlay.ChildrenOfType().Single(); + + Vector2 offset = resumeOverlay.ToScreenSpace(new Vector2(OsuCursor.SIZE / 2)) - resumeOverlay.ToScreenSpace(Vector2.Zero); + InputManager.MoveMouseTo(resumeOverlayCursor.ScreenSpaceDrawQuad.Centre - offset + new Vector2(1)); + }); + AddStep("click", () => osuInputManager.GameClick()); AddAssert("dismissed", () => resumeFired && resume.State.Value == Visibility.Hidden); } diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index ab1bb0cf5a..8215201d43 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { public partial class OsuCursor : SkinReloadableDrawable { - private const float size = 28; + public const float SIZE = 28; private const float pressed_scale = 1.2f; private const float released_scale = 1f; @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { Origin = Anchor.Centre; - Size = new Vector2(size); + Size = new Vector2(SIZE); } [BackgroundDependencyLoader] @@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, Masking = true, - BorderThickness = size / 6, + BorderThickness = SIZE / 6, BorderColour = Color4.White, EdgeEffect = new EdgeEffectParameters { @@ -156,7 +156,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor Anchor = Anchor.Centre, RelativeSizeAxes = Axes.Both, Masking = true, - BorderThickness = size / 3, + BorderThickness = SIZE / 3, BorderColour = Color4.White.Opacity(0.5f), Children = new Drawable[] { From 544d5d1d86416a179de9c6badd7833a8e53ca518 Mon Sep 17 00:00:00 2001 From: ratinfx Date: Tue, 7 Nov 2023 12:23:22 +0100 Subject: [PATCH 1373/4092] Forgot a clock.Stop call --- osu.Game/Screens/Edit/Editor.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 592e6625cc..58c3ae809c 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1145,7 +1145,7 @@ namespace osu.Game.Screens.Edit if (groups.Length != 4 || string.IsNullOrEmpty(groups[0])) { - Schedule(() => notifications.Post(new SimpleNotification + Schedule(() => notifications?.Post(new SimpleNotification { Icon = FontAwesome.Solid.ExclamationTriangle, Text = EditorStrings.FailedToProcessTimestamp @@ -1162,7 +1162,7 @@ namespace osu.Game.Screens.Edit // Limit timestamp link length at 30000 min (50 hr) to avoid parsing issues if (string.IsNullOrEmpty(timeMin) || timeMin.Length > 5 || double.Parse(timeMin) > 30_000) { - Schedule(() => notifications.Post(new SimpleNotification + Schedule(() => notifications?.Post(new SimpleNotification { Icon = FontAwesome.Solid.ExclamationTriangle, Text = EditorStrings.TooLongTimestamp @@ -1172,6 +1172,9 @@ namespace osu.Game.Screens.Edit editorBeatmap.SelectedHitObjects.Clear(); + if (clock.IsRunning) + clock.Stop(); + double position = EditorTimestampParser.GetTotalMilliseconds(timeMin, timeSec, timeMss); if (string.IsNullOrEmpty(objectsGroup)) From 81caa854e6a7ba3251658f1cd174bf6bad62b1ea Mon Sep 17 00:00:00 2001 From: ratinfx Date: Tue, 7 Nov 2023 13:02:46 +0100 Subject: [PATCH 1374/4092] Separate Test cases by relevant rulesets --- .../TestSceneOpenEditorTimestampInMania.cs | 102 +++++++ .../TestSceneOpenEditorTimestampInOsu.cs | 110 ++++++++ .../Editing/TestSceneOpenEditorTimestamp.cs | 249 ++++-------------- 3 files changed, 259 insertions(+), 202 deletions(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/Editor/TestSceneOpenEditorTimestampInMania.cs create mode 100644 osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOpenEditorTimestampInOsu.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneOpenEditorTimestampInMania.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneOpenEditorTimestampInMania.cs new file mode 100644 index 0000000000..6ec5dcee4c --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneOpenEditorTimestampInMania.cs @@ -0,0 +1,102 @@ +// 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.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Objects; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Mania.Tests.Editor +{ + public partial class TestSceneOpenEditorTimestampInMania : EditorTestScene + { + protected override Ruleset CreateEditorRuleset() => new ManiaRuleset(); + + private void addStepClickLink(string timestamp, string step = "", bool displayTimestamp = true) + { + AddStep(displayTimestamp ? $"{step} {timestamp}" : step, () => Editor.HandleTimestamp(timestamp)); + AddUntilStep("wait for seek", () => EditorClock.SeekingOrStopped.Value); + } + + private void addReset() + { + addStepClickLink("00:00:000", "reset", false); + } + + private bool checkSnapAndSelectColumn(double startTime, IReadOnlyCollection<(int, int)>? columnPairs = null) + { + bool checkColumns = columnPairs != null + ? EditorBeatmap.SelectedHitObjects.All(x => columnPairs.Any(col => isNoteAt(x, col.Item1, col.Item2))) + : !EditorBeatmap.SelectedHitObjects.Any(); + + return EditorClock.CurrentTime == startTime + && EditorBeatmap.SelectedHitObjects.Count == (columnPairs?.Count ?? 0) + && checkColumns; + } + + private bool isNoteAt(HitObject hitObject, double time, int column) + { + return hitObject is ManiaHitObject maniaHitObject + && maniaHitObject.StartTime == time + && maniaHitObject.Column == column; + } + + [Test] + public void TestNormalSelection() + { + addStepClickLink("00:05:920 (5920|3,6623|3,6857|2,7326|1)"); + AddAssert("selected group", () => checkSnapAndSelectColumn(5_920, new List<(int, int)> + { (5_920, 3), (6_623, 3), (6_857, 2), (7_326, 1) } + )); + + addReset(); + addStepClickLink("00:42:716 (42716|3,43420|2,44123|0,44357|1,45295|1)"); + AddAssert("selected ungrouped", () => checkSnapAndSelectColumn(42_716, new List<(int, int)> + { (42_716, 3), (43_420, 2), (44_123, 0), (44_357, 1), (45_295, 1) } + )); + + addReset(); + AddStep("add notes to row", () => + { + if (EditorBeatmap.HitObjects.Any(x => x is ManiaHitObject m && m.StartTime == 11_545 && m.Column is 1 or 2 or 3)) + return; + + ManiaHitObject first = (ManiaHitObject)EditorBeatmap.HitObjects.First(x => x is ManiaHitObject m && m.StartTime == 11_545 && m.Column == 0); + ManiaHitObject second = new Note { Column = 1, StartTime = first.StartTime }; + ManiaHitObject third = new Note { Column = 2, StartTime = first.StartTime }; + ManiaHitObject forth = new Note { Column = 3, StartTime = first.StartTime }; + EditorBeatmap.AddRange(new[] { second, third, forth }); + }); + addStepClickLink("00:11:545 (11545|0,11545|1,11545|2,11545|3)"); + AddAssert("selected in row", () => checkSnapAndSelectColumn(11_545, new List<(int, int)> + { (11_545, 0), (11_545, 1), (11_545, 2), (11_545, 3) } + )); + + addReset(); + addStepClickLink("01:36:623 (96623|1,97560|1,97677|1,97795|1,98966|1)"); + AddAssert("selected in column", () => checkSnapAndSelectColumn(96_623, new List<(int, int)> + { (96_623, 1), (97_560, 1), (97_677, 1), (97_795, 1), (98_966, 1) } + )); + } + + [Test] + public void TestUnusualSelection() + { + addStepClickLink("00:00:000 (0|1)", "invalid link"); + AddAssert("snap to 1, select none", () => checkSnapAndSelectColumn(2_170)); + + addReset(); + addStepClickLink("00:00:000 (0)", "std link"); + AddAssert("snap and select 1", () => checkSnapAndSelectColumn(2_170, new List<(int, int)> + { (2_170, 2) }) + ); + + addReset(); + // TODO: discuss - this selects the first 2 objects on Stable, do we want that or is this fine? + addStepClickLink("00:00:000 (1,2)", "std link"); + AddAssert("snap to 1, select none", () => checkSnapAndSelectColumn(2_170)); + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOpenEditorTimestampInOsu.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOpenEditorTimestampInOsu.cs new file mode 100644 index 0000000000..d69f482d29 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOpenEditorTimestampInOsu.cs @@ -0,0 +1,110 @@ +// 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.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Osu.Tests.Editor +{ + public partial class TestSceneOpenEditorTimestampInOsu : EditorTestScene + { + protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + + private void addStepClickLink(string timestamp, string step = "", bool displayTimestamp = true) + { + AddStep(displayTimestamp ? $"{step} {timestamp}" : step, () => Editor.HandleTimestamp(timestamp)); + AddUntilStep("wait for seek", () => EditorClock.SeekingOrStopped.Value); + } + + private void addReset() + { + addStepClickLink("00:00:000", "reset", false); + } + + private bool checkSnapAndSelectCombo(double startTime, params int[] comboNumbers) + { + bool checkCombos = comboNumbers.Any() + ? hasCombosInOrder(EditorBeatmap.SelectedHitObjects, comboNumbers) + : !EditorBeatmap.SelectedHitObjects.Any(); + + return EditorClock.CurrentTime == startTime + && EditorBeatmap.SelectedHitObjects.Count == comboNumbers.Length + && checkCombos; + } + + private bool hasCombosInOrder(IEnumerable selected, params int[] comboNumbers) + { + List hitObjects = selected.ToList(); + if (hitObjects.Count != comboNumbers.Length) + return false; + + return !hitObjects.Select(x => (OsuHitObject)x) + .Where((x, i) => x.IndexInCurrentCombo + 1 != comboNumbers[i]) + .Any(); + } + + [Test] + public void TestNormalSelection() + { + addStepClickLink("00:02:170 (1,2,3)"); + AddAssert("snap and select 1-2-3", () => checkSnapAndSelectCombo(2_170, 1, 2, 3)); + + addReset(); + addStepClickLink("00:04:748 (2,3,4,1,2)"); + AddAssert("snap and select 2-3-4-1-2", () => checkSnapAndSelectCombo(4_748, 2, 3, 4, 1, 2)); + + addReset(); + addStepClickLink("00:02:170 (1,1,1)"); + AddAssert("snap and select 1-1-1", () => checkSnapAndSelectCombo(2_170, 1, 1, 1)); + + addReset(); + addStepClickLink("00:02:873 (2,2,2,2)"); + AddAssert("snap and select 2-2-2-2", () => checkSnapAndSelectCombo(2_873, 2, 2, 2, 2)); + } + + [Test] + public void TestUnusualSelection() + { + HitObject firstObject = null!; + + addStepClickLink("00:00:000 (1,2,3)", "invalid offset"); + AddAssert("snap to next, select 1-2-3", () => + { + firstObject = EditorBeatmap.HitObjects.First(); + return checkSnapAndSelectCombo(firstObject.StartTime, 1, 2, 3); + }); + + addReset(); + addStepClickLink("00:00:956 (2,3,4)", "invalid offset"); + AddAssert("snap to next, select 2-3-4", () => checkSnapAndSelectCombo(firstObject.StartTime, 2, 3, 4)); + + addReset(); + addStepClickLink("00:00:000 (0)", "invalid offset"); + AddAssert("snap and select 1", () => checkSnapAndSelectCombo(firstObject.StartTime, 1)); + + addReset(); + addStepClickLink("00:00:000 (1)", "invalid offset"); + AddAssert("snap and select 1", () => checkSnapAndSelectCombo(firstObject.StartTime, 1)); + + addReset(); + addStepClickLink("00:00:000 (2)", "invalid offset"); + AddAssert("snap and select 1", () => checkSnapAndSelectCombo(firstObject.StartTime, 1)); + + addReset(); + addStepClickLink("00:00:000 (2,3)", "invalid offset"); + AddAssert("snap to 1, select 2-3", () => checkSnapAndSelectCombo(firstObject.StartTime, 2, 3)); + + addReset(); + addStepClickLink("00:00:956 (956|1,956|2)", "mania link"); + AddAssert("snap to next, select none", () => checkSnapAndSelectCombo(firstObject.StartTime)); + + addReset(); + addStepClickLink("00:00:000 (0|1)", "mania link"); + AddAssert("snap to 1, select none", () => checkSnapAndSelectCombo(firstObject.StartTime)); + } + } +} diff --git a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs index f7b976702a..bc31924e2c 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.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.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Extensions; @@ -12,9 +11,6 @@ using osu.Game.Database; using osu.Game.Localisation; using osu.Game.Online.Chat; using osu.Game.Rulesets; -using osu.Game.Rulesets.Mania; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; using osu.Game.Screens.Menu; @@ -25,79 +21,39 @@ namespace osu.Game.Tests.Visual.Editing { public partial class TestSceneOpenEditorTimestamp : OsuGameTestScene { - protected Editor Editor => (Editor)Game.ScreenStack.CurrentScreen; - protected EditorBeatmap EditorBeatmap => Editor.ChildrenOfType().Single(); - protected EditorClock EditorClock => Editor.ChildrenOfType().Single(); + private Editor editor => (Editor)Game.ScreenStack.CurrentScreen; + private EditorBeatmap editorBeatmap => editor.ChildrenOfType().Single(); + private EditorClock editorClock => editor.ChildrenOfType().Single(); - protected void AddStepClickLink(string timestamp, string step = "", bool waitForSeek = true) + private void addStepClickLink(string timestamp, string step = "", bool waitForSeek = true) { AddStep($"{step} {timestamp}", () => Game.HandleLink(new LinkDetails(LinkAction.OpenEditorTimestamp, timestamp)) ); if (waitForSeek) - AddUntilStep("wait for seek", () => EditorClock.SeekingOrStopped.Value); + AddUntilStep("wait for seek", () => editorClock.SeekingOrStopped.Value); } - protected void AddStepScreenModeTo(EditorScreenMode screenMode) + private void addStepScreenModeTo(EditorScreenMode screenMode) { - AddStep("change screen to " + screenMode, () => Editor.Mode.Value = screenMode); + AddStep("change screen to " + screenMode, () => editor.Mode.Value = screenMode); } - protected void AssertOnScreenAt(EditorScreenMode screen, double time, string text = "stayed in") + private void assertOnScreenAt(EditorScreenMode screen, double time, string text = "stayed in") { AddAssert($"{text} {screen} at {time}", () => - Editor.Mode.Value == screen - && EditorClock.CurrentTime == time + editor.Mode.Value == screen + && editorClock.CurrentTime == time ); } - protected void AssertMovedScreenTo(EditorScreenMode screen, string text = "moved to") + private void assertMovedScreenTo(EditorScreenMode screen, string text = "moved to") { - AddAssert($"{text} {screen}", () => Editor.Mode.Value == screen); + AddAssert($"{text} {screen}", () => editor.Mode.Value == screen); } - private bool checkSnapAndSelectCombo(double startTime, params int[] comboNumbers) - { - bool checkCombos = comboNumbers.Any() - ? hasCombosInOrder(EditorBeatmap.SelectedHitObjects, comboNumbers) - : !EditorBeatmap.SelectedHitObjects.Any(); - - return EditorClock.CurrentTime == startTime - && EditorBeatmap.SelectedHitObjects.Count == comboNumbers.Length - && checkCombos; - } - - private bool hasCombosInOrder(IEnumerable selected, params int[] comboNumbers) - { - List hitObjects = selected.ToList(); - if (hitObjects.Count != comboNumbers.Length) - return false; - - return !hitObjects.Select(x => (IHasComboInformation)x) - .Where((combo, i) => combo.IndexInCurrentCombo + 1 != comboNumbers[i]) - .Any(); - } - - private bool checkSnapAndSelectColumn(double startTime, IReadOnlyCollection<(int, int)>? columnPairs = null) - { - bool checkColumns = columnPairs != null - ? EditorBeatmap.SelectedHitObjects.All(x => columnPairs.Any(col => isNoteAt(x, col.Item1, col.Item2))) - : !EditorBeatmap.SelectedHitObjects.Any(); - - return EditorClock.CurrentTime == startTime - && EditorBeatmap.SelectedHitObjects.Count == (columnPairs?.Count ?? 0) - && checkColumns; - } - - private bool isNoteAt(HitObject hitObject, double time, int column) - { - return hitObject is IHasColumn columnInfo - && hitObject.StartTime == time - && columnInfo.Column == column; - } - - protected void SetUpEditor(RulesetInfo ruleset) + private void setUpEditor(RulesetInfo ruleset) { BeatmapSetInfo beatmapSet = null!; @@ -118,7 +74,7 @@ namespace osu.Game.Tests.Visual.Editing ((PlaySongSelect)Game.ScreenStack.CurrentScreen) .Edit(beatmapSet.Beatmaps.Last(beatmap => beatmap.Ruleset.Name == ruleset.Name)) ); - AddUntilStep("Wait for editor open", () => Editor.ReadyForUse); + AddUntilStep("Wait for editor open", () => editor.ReadyForUse); } [Test] @@ -126,7 +82,7 @@ namespace osu.Game.Tests.Visual.Editing { RulesetInfo rulesetInfo = new OsuRuleset().RulesetInfo; - AddStepClickLink("00:00:000", waitForSeek: false); + addStepClickLink("00:00:000", waitForSeek: false); AddAssert("recieved 'must be in edit'", () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEdit) == 1 ); @@ -134,20 +90,20 @@ namespace osu.Game.Tests.Visual.Editing AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo.Invoke()); AddAssert("entered song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect); - AddStepClickLink("00:00:000 (1)", waitForSeek: false); + addStepClickLink("00:00:000 (1)", waitForSeek: false); AddAssert("recieved 'must be in edit'", () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEdit) == 2 ); - SetUpEditor(rulesetInfo); - AddAssert("is editor Osu", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); + setUpEditor(rulesetInfo); + AddAssert("is editor Osu", () => editorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); - AddStepClickLink("00:000", "invalid link", waitForSeek: false); + addStepClickLink("00:000", "invalid link", waitForSeek: false); AddAssert("recieved 'failed to process'", () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp) == 1 ); - AddStepClickLink("50000:00:000", "too long link", waitForSeek: false); + addStepClickLink("50000:00:000", "too long link", waitForSeek: false); AddAssert("recieved 'too long'", () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.TooLongTimestamp) == 1 ); @@ -159,156 +115,45 @@ namespace osu.Game.Tests.Visual.Editing const long long_link_value = 1_000 * 60 * 1_000; RulesetInfo rulesetInfo = new OsuRuleset().RulesetInfo; - SetUpEditor(rulesetInfo); - AddAssert("is editor Osu", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); + setUpEditor(rulesetInfo); + AddAssert("is editor Osu", () => editorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); - AddStepClickLink("1000:00:000", "long link"); + addStepClickLink("1000:00:000", "long link"); AddAssert("moved to end of track", () => - EditorClock.CurrentTime == long_link_value - || (EditorClock.TrackLength < long_link_value && EditorClock.CurrentTime == EditorClock.TrackLength) + editorClock.CurrentTime == long_link_value + || (editorClock.TrackLength < long_link_value && editorClock.CurrentTime == editorClock.TrackLength) ); - AddStepScreenModeTo(EditorScreenMode.SongSetup); - AddStepClickLink("00:00:000"); - AssertOnScreenAt(EditorScreenMode.SongSetup, 0); + addStepScreenModeTo(EditorScreenMode.SongSetup); + addStepClickLink("00:00:000"); + assertOnScreenAt(EditorScreenMode.SongSetup, 0); - AddStepClickLink("00:05:000 (0|0)"); - AssertMovedScreenTo(EditorScreenMode.Compose); + addStepClickLink("00:05:000 (0|0)"); + assertMovedScreenTo(EditorScreenMode.Compose); - AddStepScreenModeTo(EditorScreenMode.Design); - AddStepClickLink("00:10:000"); - AssertOnScreenAt(EditorScreenMode.Design, 10_000); + addStepScreenModeTo(EditorScreenMode.Design); + addStepClickLink("00:10:000"); + assertOnScreenAt(EditorScreenMode.Design, 10_000); - AddStepClickLink("00:15:000 (1)"); - AssertMovedScreenTo(EditorScreenMode.Compose); + addStepClickLink("00:15:000 (1)"); + assertMovedScreenTo(EditorScreenMode.Compose); - AddStepScreenModeTo(EditorScreenMode.Timing); - AddStepClickLink("00:20:000"); - AssertOnScreenAt(EditorScreenMode.Timing, 20_000); + addStepScreenModeTo(EditorScreenMode.Timing); + addStepClickLink("00:20:000"); + assertOnScreenAt(EditorScreenMode.Timing, 20_000); - AddStepClickLink("00:25:000 (0,1)"); - AssertMovedScreenTo(EditorScreenMode.Compose); + addStepClickLink("00:25:000 (0,1)"); + assertMovedScreenTo(EditorScreenMode.Compose); - AddStepScreenModeTo(EditorScreenMode.Verify); - AddStepClickLink("00:30:000"); - AssertOnScreenAt(EditorScreenMode.Verify, 30_000); + addStepScreenModeTo(EditorScreenMode.Verify); + addStepClickLink("00:30:000"); + assertOnScreenAt(EditorScreenMode.Verify, 30_000); - AddStepClickLink("00:35:000 (0,1)"); - AssertMovedScreenTo(EditorScreenMode.Compose); + addStepClickLink("00:35:000 (0,1)"); + assertMovedScreenTo(EditorScreenMode.Compose); - AddStepClickLink("00:00:000"); - AssertOnScreenAt(EditorScreenMode.Compose, 0); - } - - [Test] - public void TestSelectionForOsu() - { - HitObject firstObject = null!; - RulesetInfo rulesetInfo = new OsuRuleset().RulesetInfo; - - SetUpEditor(rulesetInfo); - AddAssert("is editor Osu", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); - - AddStepClickLink("00:00:956 (1,2,3)"); - AddAssert("snap and select 1-2-3", () => - { - firstObject = EditorBeatmap.HitObjects.First(); - return checkSnapAndSelectCombo(firstObject.StartTime, 1, 2, 3); - }); - - AddStepClickLink("00:01:450 (2,3,4,1,2)"); - AddAssert("snap and select 2-3-4-1-2", () => checkSnapAndSelectCombo(1_450, 2, 3, 4, 1, 2)); - - AddStepClickLink("00:00:956 (1,1,1)"); - AddAssert("snap and select 1-1-1", () => checkSnapAndSelectCombo(firstObject.StartTime, 1, 1, 1)); - } - - [Test] - public void TestUnusualSelectionForOsu() - { - HitObject firstObject = null!; - RulesetInfo rulesetInfo = new OsuRuleset().RulesetInfo; - - SetUpEditor(rulesetInfo); - AddAssert("is editor Osu", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); - - AddStepClickLink("00:00:000 (1,2,3)", "invalid offset"); - AddAssert("snap to next, select 1-2-3", () => - { - firstObject = EditorBeatmap.HitObjects.First(); - return checkSnapAndSelectCombo(firstObject.StartTime, 1, 2, 3); - }); - - AddStepClickLink("00:00:956 (2,3,4)", "invalid offset"); - AddAssert("snap to next, select 2-3-4", () => checkSnapAndSelectCombo(firstObject.StartTime, 2, 3, 4)); - - AddStepClickLink("00:00:000 (0)", "invalid offset"); - AddAssert("snap and select 1", () => checkSnapAndSelectCombo(firstObject.StartTime, 1)); - - AddStepClickLink("00:00:000 (1)", "invalid offset"); - AddAssert("snap and select 1", () => checkSnapAndSelectCombo(firstObject.StartTime, 1)); - - AddStepClickLink("00:00:000 (2)", "invalid offset"); - AddAssert("snap and select 1", () => checkSnapAndSelectCombo(firstObject.StartTime, 1)); - - AddStepClickLink("00:00:000 (2,3)", "invalid offset"); - AddAssert("snap to 1, select 2-3", () => checkSnapAndSelectCombo(firstObject.StartTime, 2, 3)); - - AddStepClickLink("00:00:956 (956|1,956|2)", "mania link"); - AddAssert("snap to next, select none", () => checkSnapAndSelectCombo(firstObject.StartTime)); - - AddStepClickLink("00:00:000 (0|1)", "mania link"); - AddAssert("snap to 1, select none", () => checkSnapAndSelectCombo(firstObject.StartTime)); - } - - [Test] - public void TestSelectionForMania() - { - RulesetInfo rulesetInfo = new ManiaRuleset().RulesetInfo; - - SetUpEditor(rulesetInfo); - AddAssert("is editor Mania", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); - - AddStepClickLink("00:11:010 (11010|1,11175|5,11258|3,11340|5,11505|1)"); - AddAssert("selected group", () => checkSnapAndSelectColumn(11010, new List<(int, int)> - { (11010, 1), (11175, 5), (11258, 3), (11340, 5), (11505, 1) } - )); - - AddStepClickLink("00:00:956 (956|1,956|6,1285|3,1780|4)"); - AddAssert("selected ungrouped", () => checkSnapAndSelectColumn(956, new List<(int, int)> - { (956, 1), (956, 6), (1285, 3), (1780, 4) } - )); - - AddStepClickLink("02:36:560 (156560|1,156560|4,156560|6)"); - AddAssert("selected in row", () => checkSnapAndSelectColumn(156560, new List<(int, int)> - { (156560, 1), (156560, 4), (156560, 6) } - )); - - AddStepClickLink("00:35:736 (35736|3,36395|3,36725|3,37384|3)"); - AddAssert("selected in column", () => checkSnapAndSelectColumn(35736, new List<(int, int)> - { (35736, 3), (36395, 3), (36725, 3), (37384, 3) } - )); - } - - [Test] - public void TestUnusualSelectionForMania() - { - RulesetInfo rulesetInfo = new ManiaRuleset().RulesetInfo; - - SetUpEditor(rulesetInfo); - AddAssert("is editor Mania", () => EditorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); - - AddStepClickLink("00:00:000 (0|1)", "invalid link"); - AddAssert("snap to 1, select none", () => checkSnapAndSelectColumn(956)); - - AddStepClickLink("00:00:000 (0)", "std link"); - AddAssert("snap and select 1", () => checkSnapAndSelectColumn(956, new List<(int, int)> - { (956, 1) }) - ); - - // TODO: discuss - this selects the first 2 objects on Stable, do we want that or is this fine? - AddStepClickLink("00:00:000 (1,2)", "std link"); - AddAssert("snap to 1, select none", () => checkSnapAndSelectColumn(956)); + addStepClickLink("00:00:000"); + assertOnScreenAt(EditorScreenMode.Compose, 0); } } } From fcd73e62d2f98472e66524913c5f58fa2499d4f2 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Tue, 7 Nov 2023 13:06:14 +0100 Subject: [PATCH 1375/4092] Remove mobile specific changes Will be added back in a separate PR --- osu.Android/OsuGameAndroid.cs | 3 --- .../Overlays/Settings/Sections/Input/TouchSettings.cs | 11 ++++------- osu.Game/Screens/Play/PlayerSettings/InputSettings.cs | 5 ++--- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index e4b934a387..97a9848a12 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -98,9 +98,6 @@ namespace osu.Android case AndroidJoystickHandler jh: return new AndroidJoystickSettings(jh); - case AndroidTouchHandler: - return new TouchSettings(handler); - default: return base.CreateSettingsSubsectionFor(handler); } diff --git a/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs index 0056de6674..30a0b1b785 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs @@ -29,14 +29,11 @@ namespace osu.Game.Overlays.Settings.Sections.Input [BackgroundDependencyLoader] private void load(OsuConfigManager osuConfig) { - if (!RuntimeInfo.IsMobile) // don't allow disabling the only input method (touch) on mobile. + Add(new SettingsCheckbox { - Add(new SettingsCheckbox - { - LabelText = CommonStrings.Enabled, - Current = handler.Enabled - }); - } + LabelText = CommonStrings.Enabled, + Current = handler.Enabled + }); Add(new SettingsCheckbox { diff --git a/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs b/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs index 1387e01305..47af4e0b53 100644 --- a/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs @@ -23,9 +23,8 @@ namespace osu.Game.Screens.Play.PlayerSettings { new PlayerCheckbox { - // TODO: change to touchscreen detection once https://github.com/ppy/osu/pull/25348 makes it in - LabelText = RuntimeInfo.IsDesktop ? MouseSettingsStrings.DisableClicksDuringGameplay : TouchSettingsStrings.DisableTapsDuringGameplay, - Current = config.GetBindable(RuntimeInfo.IsDesktop ? OsuSetting.MouseDisableButtons : OsuSetting.TouchDisableGameplayTaps) + LabelText = MouseSettingsStrings.DisableClicksDuringGameplay, + Current = config.GetBindable(OsuSetting.MouseDisableButtons) } }; } From 7bedf7cf165e1a9a125eb74af636dca957e8fc0d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Nov 2023 21:08:49 +0900 Subject: [PATCH 1376/4092] Move static method to end of file --- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index 8215201d43..ba9fda25e4 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -84,12 +84,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor calculateCursorScale(); } - /// - /// Get the scale applicable to the ActiveCursor based on a beatmap's circle size. - /// - public static float GetScaleForCircleSize(float circleSize) => - 1f - 0.7f * (1f + circleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY; - private void calculateCursorScale() { float scale = userCursorScale.Value; @@ -117,6 +111,12 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor public void Contract() => expandTarget.ScaleTo(released_scale, 400, Easing.OutQuad); + /// + /// Get the scale applicable to the ActiveCursor based on a beatmap's circle size. + /// + public static float GetScaleForCircleSize(float circleSize) => + 1f - 0.7f * (1f + circleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY; + private partial class DefaultCursor : OsuCursorSprite { public DefaultCursor() From 00268d0ccce3691e682f7ae2ff08cb6a9a1f88f8 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Tue, 7 Nov 2023 13:09:30 +0100 Subject: [PATCH 1377/4092] Remove unused using --- osu.Android/OsuGameAndroid.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index 97a9848a12..dea70e6b27 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -11,7 +11,6 @@ using osu.Framework.Input.Handlers; using osu.Framework.Platform; using osu.Game; using osu.Game.Overlays.Settings; -using osu.Game.Overlays.Settings.Sections.Input; using osu.Game.Updater; using osu.Game.Utils; From 02d9f005d09df3bd755a337a7d63c3ef4353d8b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Nov 2023 21:27:02 +0900 Subject: [PATCH 1378/4092] Update squirrel to latest release Quite a few fixes since our last update. See https://github.com/clowd/Clowd.Squirrel/releases. Of special note is [a fix for incomplete updates](https://github.com/clowd/Clowd.Squirrel/issues/182) which I believe we've seen cause issues in the wild before. --- 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 1d43e118a3..f37cfdc5f1 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -23,7 +23,7 @@ - + From 3e257f1e6c02795c6bbb6bf08f542707e8e6e827 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Nov 2023 23:21:51 +0900 Subject: [PATCH 1379/4092] Remove unused using statements --- osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs | 1 - osu.Game/Screens/Play/PlayerSettings/InputSettings.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs index 30a0b1b785..175fcc4709 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; -using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Input.Handlers; using osu.Framework.Localisation; diff --git a/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs b/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs index 47af4e0b53..852fbd8dcc 100644 --- a/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/InputSettings.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 osu.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Configuration; From b092b0093affc97453d4f14be7b7a68005d5ae31 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 6 Nov 2023 21:13:36 +0300 Subject: [PATCH 1380/4092] Make sure bar draw quad is thick enough --- osu.Game/Graphics/UserInterface/BarGraph.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs index 27a41eb7e3..d3eebd71f0 100644 --- a/osu.Game/Graphics/UserInterface/BarGraph.cs +++ b/osu.Game/Graphics/UserInterface/BarGraph.cs @@ -145,6 +145,13 @@ namespace osu.Game.Graphics.UserInterface float barHeight = drawSize.Y * ((direction == BarDirection.TopToBottom || direction == BarDirection.BottomToTop) ? lengths[i] : barBreadth); float barWidth = drawSize.X * ((direction == BarDirection.LeftToRight || direction == BarDirection.RightToLeft) ? lengths[i] : barBreadth); + if (barHeight == 0 || barWidth == 0) + continue; + + // Make sure draw quad is thick enough + barHeight = Math.Max(barHeight, 1.5f); + barWidth = Math.Max(barWidth, 1.5f); + Vector2 topLeft; switch (direction) From cbea2db4bef9322b8dcbe9619d95cb918dcc3b55 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 7 Nov 2023 02:03:16 +0300 Subject: [PATCH 1381/4092] Support absolute-sized health bar and use it for default layout --- .../Screens/Play/HUD/ArgonHealthDisplay.cs | 30 +++++++++++++------ osu.Game/Skinning/ArgonSkin.cs | 3 +- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 793d43f7ef..2ae6bdcb15 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -36,12 +36,10 @@ namespace osu.Game.Screens.Play.HUD }; [SettingSource("Bar length")] - public BindableFloat BarLength { get; } = new BindableFloat(0.98f) - { - MinValue = 0.2f, - MaxValue = 1, - Precision = 0.01f, - }; + public BindableFloat BarLength { get; } = new BindableFloat(0.98f); + + [SettingSource("Use relative size")] + public BindableBool UseRelativeSize { get; } = new BindableBool(true); private BarPath mainBar = null!; @@ -140,9 +138,23 @@ namespace osu.Game.Screens.Play.HUD Current.BindValueChanged(_ => Scheduler.AddOnce(updateCurrent), true); - BarLength.BindValueChanged(l => Width = l.NewValue, true); - BarHeight.BindValueChanged(_ => updatePath()); - updatePath(); + // update relative axes first before reading width from bar length. + RelativeSizeAxes = UseRelativeSize.Value ? Axes.X : Axes.None; + Width = BarLength.Value; + + UseRelativeSize.BindValueChanged(v => + { + RelativeSizeAxes = v.NewValue ? Axes.X : Axes.None; + float newWidth = Width; + + BarLength.MinValue = v.NewValue ? 0.2f : 200f; + BarLength.MaxValue = v.NewValue ? 1f : 1000f; + BarLength.Precision = v.NewValue ? 0.01f : 1f; + BarLength.Value = newWidth; + }, true); + + BarLength.ValueChanged += l => Width = l.NewValue; + BarHeight.BindValueChanged(_ => updatePath(), true); } protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source) diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 82c150ced7..95e1820059 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -129,7 +129,8 @@ namespace osu.Game.Skinning health.Anchor = Anchor.TopLeft; health.Origin = Anchor.TopLeft; - health.BarLength.Value = 0.22f; + health.UseRelativeSize.Value = false; + health.BarLength.Value = 300; health.BarHeight.Value = 30f; health.Position = new Vector2(components_x_offset, 20f); From e6d3085353886da1a196f6d0354eae86ba9469fa Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 8 Nov 2023 01:48:21 +0300 Subject: [PATCH 1382/4092] Update accuracy counter design --- .../Screens/Play/HUD/ArgonAccuracyCounter.cs | 20 +++++++++++++++---- .../Play/HUD/ArgonCounterTextComponent.cs | 8 ++++---- osu.Game/Skinning/ArgonSkin.cs | 14 ++++++------- 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs index 33223a526b..0414cbaea4 100644 --- a/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs @@ -1,18 +1,30 @@ // 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.Graphics; -using osu.Game.Graphics.Sprites; +using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; using osu.Game.Skinning; +using osuTK; namespace osu.Game.Screens.Play.HUD { public partial class ArgonAccuracyCounter : GameplayAccuracyCounter, ISerialisableDrawable { + [SettingSource("Wireframe opacity", "Controls the opacity of the wire frames behind the digits.")] + public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.4f) + { + Precision = 0.01f, + MinValue = 0, + MaxValue = 1, + }; + public bool UsesFixedAnchor { get; set; } - protected override OsuSpriteText CreateSpriteText() - => base.CreateSpriteText().With(s => s.Font = OsuFont.Default.With(size: 19.2f)); + protected override IHasText CreateText() => new ArgonCounterTextComponent(Anchor.TopLeft, "ACCURACY", new Vector2(-4, 0)) + { + WireframeOpacity = { BindTarget = WireframeOpacity }, + }; } } diff --git a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs index 9545168a46..fcad0f12d8 100644 --- a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs +++ b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Play.HUD } } - public ArgonCounterTextComponent(Anchor anchor, LocalisableString? label = null) + public ArgonCounterTextComponent(Anchor anchor, LocalisableString? label = null, Vector2? spacing = null) { Anchor = anchor; Origin = anchor; @@ -49,11 +49,13 @@ namespace osu.Game.Screens.Play.HUD { Anchor = anchor, Origin = anchor, + Spacing = spacing ?? new Vector2(-2, 0), }; textPart = new ArgonCounterSpriteText { Anchor = anchor, Origin = anchor, + Spacing = spacing ?? new Vector2(-2, 0), }; } @@ -115,9 +117,7 @@ namespace osu.Game.Screens.Play.HUD private void load(ISkinSource skin) { // todo: rename font - Font = new FontUsage(@"argon-score", 1, fixedWidth: true); - Spacing = new Vector2(-2, 0); - + Font = new FontUsage(@"argon-score", 1); glyphStore = new GlyphStore(skin, glyphLookupOverride); } diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 95e1820059..2af3a29804 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -143,14 +143,14 @@ namespace osu.Game.Skinning score.Origin = Anchor.TopRight; score.Position = new Vector2(components_x_offset + 200, scoreWedge.Y + 30); } + } - if (accuracy != null) - { - // +4 to vertically align the accuracy counter with the score counter. - accuracy.Position = new Vector2(components_x_offset + 4, scoreWedge.Y + 45); - accuracy.Anchor = Anchor.TopLeft; - accuracy.Origin = Anchor.TopLeft; - } + if (accuracy != null) + { + // +4 to vertically align the accuracy counter with the score counter. + accuracy.Position = new Vector2(-20, 20); + accuracy.Anchor = Anchor.TopRight; + accuracy.Origin = Anchor.TopRight; } var hitError = container.OfType().FirstOrDefault(); From d30bac3f49af02f2473e9c1a71aacbdb8f42dd4e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 8 Nov 2023 01:49:13 +0300 Subject: [PATCH 1383/4092] Move "required display digits" feature to reside in argon score counter --- .../Screens/Play/HUD/ArgonCounterTextComponent.cs | 7 +++---- osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs | 15 ++++++++++++++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs index fcad0f12d8..437a627cbc 100644 --- a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs +++ b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.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.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -25,7 +23,6 @@ namespace osu.Game.Screens.Play.HUD private readonly ArgonCounterSpriteText wireframesPart; private readonly ArgonCounterSpriteText textPart; - public IBindable RequiredDisplayDigits { get; } = new BindableInt(); public IBindable WireframeOpacity { get; } = new BindableFloat(); public LocalisableString Text @@ -33,7 +30,7 @@ namespace osu.Game.Screens.Play.HUD get => textPart.Text; set { - wireframesPart.Text = new string('#', Math.Max(value.ToString().Count(char.IsDigit), RequiredDisplayDigits.Value)); + wireframesPart.Text = FormatWireframes(value); textPart.Text = value; } } @@ -91,6 +88,8 @@ namespace osu.Game.Screens.Play.HUD }; } + protected virtual LocalisableString FormatWireframes(LocalisableString text) => text; + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs b/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs index 636565f181..fef4199d31 100644 --- a/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonScoreCounter.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 System; using osu.Framework.Bindables; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; @@ -25,10 +26,22 @@ namespace osu.Game.Screens.Play.HUD protected override LocalisableString FormatCount(long count) => count.ToLocalisableString(); - protected override IHasText CreateText() => new ArgonCounterTextComponent(Anchor.TopRight) + protected override IHasText CreateText() => new ArgonScoreTextComponent(Anchor.TopRight) { RequiredDisplayDigits = { BindTarget = RequiredDisplayDigits }, WireframeOpacity = { BindTarget = WireframeOpacity }, }; + + private partial class ArgonScoreTextComponent : ArgonCounterTextComponent + { + public IBindable RequiredDisplayDigits { get; } = new BindableInt(); + + public ArgonScoreTextComponent(Anchor anchor, LocalisableString? label = null) + : base(anchor, label) + { + } + + protected override LocalisableString FormatWireframes(LocalisableString text) => new string('#', Math.Max(text.ToString().Length, RequiredDisplayDigits.Value)); + } } } From fdc714a248d1a9283649d12a11a14e7763bfa1dc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 8 Nov 2023 02:05:19 +0300 Subject: [PATCH 1384/4092] Support percentages and ignore dot characters in wireframes part --- .../Play/HUD/ArgonCounterTextComponent.cs | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs index 437a627cbc..3d8546e0e3 100644 --- a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs +++ b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.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 System; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -42,13 +43,28 @@ namespace osu.Game.Screens.Play.HUD this.label = label; - wireframesPart = new ArgonCounterSpriteText(@"wireframes") + wireframesPart = new ArgonCounterSpriteText(c => + { + if (c == '.') + return @"dot"; + + return @"wireframes"; + }) { Anchor = anchor, Origin = anchor, Spacing = spacing ?? new Vector2(-2, 0), }; - textPart = new ArgonCounterSpriteText + textPart = new ArgonCounterSpriteText(c => + { + if (c == '.') + return @"dot"; + + if (c == '%') + return @"percentage"; + + return c.ToString(); + }) { Anchor = anchor, Origin = anchor, @@ -98,15 +114,15 @@ namespace osu.Game.Screens.Play.HUD private partial class ArgonCounterSpriteText : OsuSpriteText { - private readonly string? glyphLookupOverride; + private readonly Func getLookup; private GlyphStore glyphStore = null!; protected override char FixedWidthReferenceCharacter => '5'; - public ArgonCounterSpriteText(string? glyphLookupOverride = null) + public ArgonCounterSpriteText(Func getLookup) { - this.glyphLookupOverride = glyphLookupOverride; + this.getLookup = getLookup; Shadow = false; UseFullGlyphHeight = false; @@ -117,7 +133,7 @@ namespace osu.Game.Screens.Play.HUD { // todo: rename font Font = new FontUsage(@"argon-score", 1); - glyphStore = new GlyphStore(skin, glyphLookupOverride); + glyphStore = new GlyphStore(skin, getLookup); } protected override TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) => base.CreateTextBuilder(glyphStore); @@ -125,17 +141,17 @@ namespace osu.Game.Screens.Play.HUD private class GlyphStore : ITexturedGlyphLookupStore { private readonly ISkin skin; - private readonly string? glyphLookupOverride; + private readonly Func getLookup; - public GlyphStore(ISkin skin, string? glyphLookupOverride) + public GlyphStore(ISkin skin, Func getLookup) { this.skin = skin; - this.glyphLookupOverride = glyphLookupOverride; + this.getLookup = getLookup; } public ITexturedCharacterGlyph? Get(string fontName, char character) { - string lookup = glyphLookupOverride ?? character.ToString(); + string lookup = getLookup(character); var texture = skin.GetTexture($"{fontName}-{lookup}"); if (texture == null) From 4de5454538b0dff1b8b4a020a7f12fcb294ccc06 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 8 Nov 2023 02:06:51 +0300 Subject: [PATCH 1385/4092] Bring back left-side line next to health display Makes the score counter not look weird when it reaches 8 digits --- .../Screens/Play/HUD/ArgonHealthDisplay.cs | 9 +++--- .../Screens/Play/HUD/ArgonHealthRightLine.cs | 30 +++++++++++++++++++ osu.Game/Skinning/ArgonSkin.cs | 4 +++ 3 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 osu.Game/Screens/Play/HUD/ArgonHealthRightLine.cs diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 2ae6bdcb15..b2b3181d08 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -90,12 +90,11 @@ namespace osu.Game.Screens.Play.HUD } } - private const float main_path_radius = 10f; + public const float MAIN_PATH_RADIUS = 10f; [BackgroundDependencyLoader] private void load() { - RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; InternalChild = new Container @@ -105,7 +104,7 @@ namespace osu.Game.Screens.Play.HUD { background = new BackgroundPath { - PathRadius = main_path_radius, + PathRadius = MAIN_PATH_RADIUS, }, glowBar = new BarPath { @@ -125,7 +124,7 @@ namespace osu.Game.Screens.Play.HUD Blending = BlendingParameters.Additive, BarColour = main_bar_colour, GlowColour = main_bar_glow_colour, - PathRadius = main_path_radius, + PathRadius = MAIN_PATH_RADIUS, GlowPortion = 0.6f, }, } @@ -248,7 +247,7 @@ namespace osu.Game.Screens.Play.HUD private void updatePath() { - float barLength = DrawWidth - main_path_radius * 2; + float barLength = DrawWidth - MAIN_PATH_RADIUS * 2; float curveStart = barLength - 70; float curveEnd = barLength - 40; diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthRightLine.cs b/osu.Game/Screens/Play/HUD/ArgonHealthRightLine.cs new file mode 100644 index 0000000000..25918f679c --- /dev/null +++ b/osu.Game/Screens/Play/HUD/ArgonHealthRightLine.cs @@ -0,0 +1,30 @@ +// 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.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Screens.Play.HUD +{ + public partial class ArgonHealthRightLine : CompositeDrawable, ISerialisableDrawable + { + public bool UsesFixedAnchor { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + Size = new Vector2(50f, ArgonHealthDisplay.MAIN_PATH_RADIUS * 2); + InternalChild = new Circle + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Width = 45f, + Height = 3f, + }; + } + } +} diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 2af3a29804..e9953b57a7 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -115,6 +115,7 @@ namespace osu.Game.Skinning var skinnableTargetWrapper = new DefaultSkinComponentsContainer(container => { var health = container.OfType().FirstOrDefault(); + var healthLine = container.OfType().FirstOrDefault(); var scoreWedge = container.OfType().FirstOrDefault(); var score = container.OfType().FirstOrDefault(); var accuracy = container.OfType().FirstOrDefault(); @@ -134,6 +135,9 @@ namespace osu.Game.Skinning health.BarHeight.Value = 30f; health.Position = new Vector2(components_x_offset, 20f); + if (healthLine != null) + healthLine.Y = health.Y; + if (scoreWedge != null) { scoreWedge.Position = new Vector2(-50, 15); From 07b7e13633862e85522f0c1778c8dc06ee62724d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 8 Nov 2023 02:07:24 +0300 Subject: [PATCH 1386/4092] Place health display in front of the score wedge --- osu.Game/Skinning/ArgonSkin.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index e9953b57a7..d598c04891 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -204,9 +204,10 @@ namespace osu.Game.Skinning { Children = new Drawable[] { - new ArgonHealthDisplay(), new ArgonScoreWedge(), new ArgonScoreCounter(), + new ArgonHealthDisplay(), + new ArgonHealthRightLine(), new ArgonAccuracyCounter(), new ArgonComboCounter(), new BarHitErrorMeter(), From d0fea381b18b5f19450f4d93951e0aed152a85c8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 8 Nov 2023 02:13:16 +0300 Subject: [PATCH 1387/4092] Update skin deserialisation archives --- .../Archives/modified-argon-20231108.osk | Bin 0 -> 1494 bytes .../Archives/modified-argon-pro-20231105.osk | Bin 1899 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 osu.Game.Tests/Resources/Archives/modified-argon-20231108.osk delete mode 100644 osu.Game.Tests/Resources/Archives/modified-argon-pro-20231105.osk diff --git a/osu.Game.Tests/Resources/Archives/modified-argon-20231108.osk b/osu.Game.Tests/Resources/Archives/modified-argon-20231108.osk new file mode 100644 index 0000000000000000000000000000000000000000..d07c5171007777784216e3ee8d4bc631a602bfb9 GIT binary patch literal 1494 zcmWIWW@Zs#U|`^2Xcf!|uh^e_WIvFX&Il4=U?|Sc%+t%v%RJg|n0LrQ#O1f`(IcWB z8+rX$ChiU?2~qTQ`Ixx>ZD`s5{%hHhb97nVPl+aV1Qjyunwa+G%d_XdW2RNSmguco zzj(5k`_vD>CNlYplEjTyH)@5XT{4unf(89(awmw8vmIBP&~Qzl#P2f&~x=b%nS8oW?ovp zURH5_-s)#T0Rf-9&-jLJ3JN%N*8il>X&>K^jABXeXnw#J9p;v=`-Hf zi&-Q&AHCE#pfYtw79*;wbc&?>&M`4CREjY$01<<4VrHI4sEc!cZb5!tYF4{*mY&!tVq5qJ#(U44BM31-%tO!8+D@iZlx95 z;!KaJH{@S%yxA^7zUQ(M1oD5&e1swB0b=fasoewEjYwjZ}S{?2x8 z#oJS#uHWPPSTxDSGK`AeX9?qUOa!LwkY@Svh^=Z%I-Wb)B1hq+%XCJ$EVJ$ zXJ+R)^s8?Q`@FZ`YqsyU+ZcQMZIx{P^0%Jbxpq(bqF;3>XVb|kyq~4n>fg2{u8WTg z*pbcVGvm_w&aOti$Nzpxo&Uf7S(v20+?36GZ~CL=0GX*tRcgSLuL;C_Kpc>hSecfY znv;Uam_?diI{rSVJingtJ{`U(Xa!prqwD8x@5VVVA{5q7TP?;A;LXS+!hpL#0~!kf zjUWnEyrJttFCn0M7#JF#LUqB*3v{jM*%+a<8d#=c&(!ET7HO literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Resources/Archives/modified-argon-pro-20231105.osk b/osu.Game.Tests/Resources/Archives/modified-argon-pro-20231105.osk deleted file mode 100644 index 16f89502eff0bc46df9922fec370ee1d804170f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1899 zcmWIWW@Zs#U|`^2U=>IWU(2a$c^JquWCDpWFcfEJ=ILeTWuEQdo5y4*&{A!Gw1sPl z%y(f;r;?D8l}@c1Vtd%dzP&s7{PN3ry-NL4!aF>tXnFaYU#gL+{apE5!+TG>#M-L$ zizgfVX?3b5E|D`);hl1)bI*K-YnzT1HhnuR{6^sY4(_?fzVs-6-5KKe+J>9etx|rg zjG)EDr7qr^?{6wlsW-@<8S$Uztj4_1v=@t#?uGbnK47z=bZzo(gV~C)ChIm%Uj0Jj zn9UA@Z~Hg?IM(GODp|EM?)BA&2jBf!92Fiq*LK&-{&(U5C>}LoaOf%ndaxUad7&Q7 z%uCDH%PP*#Gwm_tI^-bW`g=~&Osm~FN$KihlhimKF^X;GbUrcPAW~59{krBG`~Lr{ z4m}q*f0|FVdpD0wkzCCs&j;b6_m7obKXPNo>^1(Y&e~=9xnAK5KQn>7ZqMN`A+F_z zn)nvaP`qtBXW!3)nUV=U4-?*Q?rOaCw|vz-c~l36?qoWA5f}^S6c`x9fDZIc%*^u$ zb#czmEy&MH%_}JeyY%hM`}wyF1diYT9qwY|?9y<_sI$E<@3o1qbM|iAkkBVHvL9W& z@#2lc;#L2D&vDQU@RyFAtT5|C!>7I7^|j~Ht#{fN%(m=?xLsCSyCL!Zy#JO{IqVZfoDx4sx10T08KL%XUT@v|qaQxInN8Sup`q1r zv(f8mHmUJ5Z)W{9ZYyX`i>b9#4B4#m+RJ+OjJ=PQ(w=cHH58g9;b^v#aV$GrthTgy>^{vX^<~pp_~0Hd#~SvR7WADnJ(QoPE5=_ zcC{e#jaOIQ~!qITd~rQOiRVTX)KldmQ%dB_Rh`6=7me;zH9BN zd~kQYbJfu|_A}p}Q}6wIc-yU}yZ@F2F1@wq=k)(wds_O#^O=kL&KSiwvu#P5_V|hq z`_1_q1E zzyL``1_onbM&Ji#g5dnT^x)K-)Z`Ly>d&3%pMBVX=ji*tBFCO5gqdwKdXbn?FJZlA z&0W1zcJ)x<;83n@hoijq{eCkcHIvhB#`nozzfABKR(!Jfs=bwS#ZukV)rDL-7nm$B zOMKaNb Date: Wed, 8 Nov 2023 02:13:35 +0300 Subject: [PATCH 1388/4092] Remove unused local --- osu.Game/Screens/Play/HUD/ArgonScoreWedge.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonScoreWedge.cs b/osu.Game/Screens/Play/HUD/ArgonScoreWedge.cs index fe495b421f..e8ade9e5c6 100644 --- a/osu.Game/Screens/Play/HUD/ArgonScoreWedge.cs +++ b/osu.Game/Screens/Play/HUD/ArgonScoreWedge.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.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -19,8 +18,6 @@ namespace osu.Game.Screens.Play.HUD { AutoSizeAxes = Axes.Both; - var vertices = new List(); - InternalChildren = new Drawable[] { new ArgonWedgePiece From 387de7ec244f1e5c7381693b6dad96a64630ce1f Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 7 Nov 2023 15:53:41 -0800 Subject: [PATCH 1389/4092] Add ability to view kudosu rankings --- .../API/Requests/GetKudosuRankingsRequest.cs | 28 ++++++ .../API/Requests/GetKudosuRankingsResponse.cs | 15 +++ osu.Game/Overlays/KudosuTable.cs | 95 +++++++++++++++++++ osu.Game/Overlays/Rankings/RankingsScope.cs | 5 +- osu.Game/Overlays/RankingsOverlay.cs | 9 ++ 5 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Online/API/Requests/GetKudosuRankingsRequest.cs create mode 100644 osu.Game/Online/API/Requests/GetKudosuRankingsResponse.cs create mode 100644 osu.Game/Overlays/KudosuTable.cs diff --git a/osu.Game/Online/API/Requests/GetKudosuRankingsRequest.cs b/osu.Game/Online/API/Requests/GetKudosuRankingsRequest.cs new file mode 100644 index 0000000000..cd361bf7b8 --- /dev/null +++ b/osu.Game/Online/API/Requests/GetKudosuRankingsRequest.cs @@ -0,0 +1,28 @@ +// 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.IO.Network; + +namespace osu.Game.Online.API.Requests +{ + public class GetKudosuRankingsRequest : APIRequest + { + private readonly int page; + + public GetKudosuRankingsRequest(int page = 1) + { + this.page = page; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + + req.AddParameter(@"page", page.ToString()); + + return req; + } + + protected override string Target => @"rankings/kudosu"; + } +} diff --git a/osu.Game/Online/API/Requests/GetKudosuRankingsResponse.cs b/osu.Game/Online/API/Requests/GetKudosuRankingsResponse.cs new file mode 100644 index 0000000000..4e3ade3795 --- /dev/null +++ b/osu.Game/Online/API/Requests/GetKudosuRankingsResponse.cs @@ -0,0 +1,15 @@ +// 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 Newtonsoft.Json; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Online.API.Requests +{ + public class GetKudosuRankingsResponse + { + [JsonProperty("ranking")] + public List Users = null!; + } +} diff --git a/osu.Game/Overlays/KudosuTable.cs b/osu.Game/Overlays/KudosuTable.cs new file mode 100644 index 0000000000..93884435a4 --- /dev/null +++ b/osu.Game/Overlays/KudosuTable.cs @@ -0,0 +1,95 @@ +// 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 osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays.Rankings.Tables; +using osu.Game.Resources.Localisation.Web; +using osu.Game.Users; + +namespace osu.Game.Overlays +{ + public partial class KudosuTable : RankingsTable + { + public KudosuTable(int page, List users) + : base(page, users) + { + } + + protected override Drawable CreateRowBackground(APIUser item) + { + var background = base.CreateRowBackground(item); + + // see: https://github.com/ppy/osu-web/blob/9de00a0b874c56893d98261d558d78d76259d81b/resources/views/multiplayer/rooms/_rankings_table.blade.php#L23 + if (!item.Active) + background.Alpha = 0.5f; + + return background; + } + + protected override Drawable[] CreateRowContent(int index, APIUser item) + { + var content = base.CreateRowContent(index, item); + + // see: https://github.com/ppy/osu-web/blob/9de00a0b874c56893d98261d558d78d76259d81b/resources/views/multiplayer/rooms/_rankings_table.blade.php#L23 + if (!item.Active) + { + foreach (var d in content) + d.Alpha = 0.5f; + } + + return content; + } + + protected override RankingsTableColumn[] CreateAdditionalHeaders() + { + const int min_width = 120; + return new[] + { + new RankingsTableColumn(RankingsStrings.KudosuTotal, Anchor.Centre, new Dimension(GridSizeMode.AutoSize, minSize: min_width), true), + new RankingsTableColumn(RankingsStrings.KudosuAvailable, Anchor.Centre, new Dimension(GridSizeMode.AutoSize, minSize: min_width)), + new RankingsTableColumn(RankingsStrings.KudosuUsed, Anchor.Centre, new Dimension(GridSizeMode.AutoSize, minSize: min_width)), + }; + } + + protected override Drawable[] CreateAdditionalContent(APIUser item) + { + int kudosuTotal = item.Kudosu.Total; + int kudosuAvailable = item.Kudosu.Available; + return new Drawable[] + { + new RowText + { + Text = kudosuTotal.ToLocalisableString(@"N0") + }, + new ColouredRowText + { + Text = kudosuAvailable.ToLocalisableString(@"N0") + }, + new ColouredRowText + { + Text = (kudosuTotal - kudosuAvailable).ToLocalisableString(@"N0") + }, + }; + } + + protected override CountryCode GetCountryCode(APIUser item) => item.CountryCode; + + protected override Drawable CreateFlagContent(APIUser item) + { + var username = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: TEXT_SIZE, italics: true)) + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + TextAnchor = Anchor.CentreLeft + }; + username.AddUserLink(item); + return username; + } + } +} diff --git a/osu.Game/Overlays/Rankings/RankingsScope.cs b/osu.Game/Overlays/Rankings/RankingsScope.cs index 3392db9360..356a861764 100644 --- a/osu.Game/Overlays/Rankings/RankingsScope.cs +++ b/osu.Game/Overlays/Rankings/RankingsScope.cs @@ -18,6 +18,9 @@ namespace osu.Game.Overlays.Rankings Score, [LocalisableDescription(typeof(RankingsStrings), nameof(RankingsStrings.TypeCountry))] - Country + Country, + + [LocalisableDescription(typeof(RankingsStrings), nameof(RankingsStrings.TypeKudosu))] + Kudosu, } } diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index f25bf80b6a..6a32515cbc 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -135,6 +135,9 @@ namespace osu.Game.Overlays case RankingsScope.Score: return new GetUserRankingsRequest(ruleset.Value, UserRankingsType.Score); + + case RankingsScope.Kudosu: + return new GetKudosuRankingsRequest(); } return null; @@ -166,6 +169,12 @@ namespace osu.Game.Overlays return new CountriesTable(1, countryRequest.Response.Countries); } + + case GetKudosuRankingsRequest kudosuRequest: + if (kudosuRequest.Response == null) + return null; + + return new KudosuTable(1, kudosuRequest.Response.Users); } return null; From c8d276281ada839a37ab6eb484c36b9b7772a0d1 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 7 Nov 2023 15:49:11 -0800 Subject: [PATCH 1390/4092] Fix flags not showing on kudosu rankings The `country` attribute is optional and not included in the kudosu rankings response so use `country_code` instead. --- osu.Game/Online/API/Requests/Responses/APIUser.cs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index 7c4093006d..2ee66453cf 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -34,20 +34,15 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"previous_usernames")] public string[] PreviousUsernames; - private CountryCode? countryCode; + [JsonProperty(@"country_code")] + private string countryCodeString; public CountryCode CountryCode { - get => countryCode ??= (Enum.TryParse(country?.Code, out CountryCode result) ? result : default); - set => countryCode = value; + get => Enum.TryParse(countryCodeString, out CountryCode result) ? result : CountryCode.Unknown; + set => countryCodeString = value.ToString(); } -#pragma warning disable 649 - [CanBeNull] - [JsonProperty(@"country")] - private Country country; -#pragma warning restore 649 - public readonly Bindable Status = new Bindable(); public readonly Bindable Activity = new Bindable(); From 6c6baab1156f64388e5fcea3ad712cc7e24bb463 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Nov 2023 16:41:30 +0900 Subject: [PATCH 1391/4092] Reword comment to explain why --- osu.Game/Graphics/UserInterface/BarGraph.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs index d3eebd71f0..0ac987e85b 100644 --- a/osu.Game/Graphics/UserInterface/BarGraph.cs +++ b/osu.Game/Graphics/UserInterface/BarGraph.cs @@ -148,7 +148,7 @@ namespace osu.Game.Graphics.UserInterface if (barHeight == 0 || barWidth == 0) continue; - // Make sure draw quad is thick enough + // Apply minimum sizing to hide the fact that we don't have fractional anti-aliasing. barHeight = Math.Max(barHeight, 1.5f); barWidth = Math.Max(barWidth, 1.5f); From 38847c3ac5383c9fb69b7bf0b38aa8ea36b23b39 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Nov 2023 17:23:01 +0900 Subject: [PATCH 1392/4092] Change test to move only on a toggle step --- osu.Game.Tests/Visual/Online/TestSceneGraph.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneGraph.cs b/osu.Game.Tests/Visual/Online/TestSceneGraph.cs index eee29e0aeb..f4bde159e5 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneGraph.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneGraph.cs @@ -13,10 +13,10 @@ namespace osu.Game.Tests.Visual.Online [TestFixture] public partial class TestSceneGraph : OsuTestScene { - private readonly BarGraph graph; - public TestSceneGraph() { + BarGraph graph; + Child = graph = new BarGraph { RelativeSizeAxes = Axes.Both, @@ -34,13 +34,14 @@ namespace osu.Game.Tests.Visual.Online AddStep("Top to bottom", () => graph.Direction = BarDirection.TopToBottom); AddStep("Left to right", () => graph.Direction = BarDirection.LeftToRight); AddStep("Right to left", () => graph.Direction = BarDirection.RightToLeft); - } - protected override void LoadComplete() - { - base.LoadComplete(); - - graph.MoveToY(-10, 1000).Then().MoveToY(10, 1000).Loop(); + AddToggleStep("Toggle movement", enabled => + { + if (enabled) + graph.MoveToY(-10, 1000).Then().MoveToY(10, 1000).Loop(); + else + graph.ClearTransforms(); + }); } } } From fc1a0cf645b345c1b2b0727313a4f11b207a3268 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 19 Oct 2023 16:20:10 +0900 Subject: [PATCH 1393/4092] Update `ButtonSystem` to use new sample names --- osu.Game/Screens/Menu/ButtonSystem.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index bf2eba43c0..d26709151c 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -127,14 +127,14 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader(true)] private void load(AudioManager audio, IdleTracker idleTracker, GameHost host) { - buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Solo, @"button-solo-select", FontAwesome.Solid.User, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P)); - buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Multi, @"button-generic-select", FontAwesome.Solid.Users, new Color4(94, 63, 186, 255), onMultiplayer, 0, Key.M)); - buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Playlists, @"button-generic-select", OsuIcon.Charts, new Color4(94, 63, 186, 255), onPlaylists, 0, Key.L)); + buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Solo, @"button-default-select", FontAwesome.Solid.User, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P)); + buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Multi, @"button-default-select", FontAwesome.Solid.Users, new Color4(94, 63, 186, 255), onMultiplayer, 0, Key.M)); + buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Playlists, @"button-default-select", OsuIcon.Charts, new Color4(94, 63, 186, 255), onPlaylists, 0, Key.L)); buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play); buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Play, @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P)); - buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Edit, @"button-edit-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E)); - buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Browse, @"button-direct-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.B, Key.D)); + buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Edit, @"button-default-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E)); + buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Browse, @"button-default-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.B, Key.D)); if (host.CanExit) buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Exit, string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), () => OnExit?.Invoke(), 0, Key.Q)); From 17aa079cb1cd1ee7ae22942aa582507c37037c4d Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 8 Nov 2023 22:08:05 +0900 Subject: [PATCH 1394/4092] Use new tiered 'back' samples --- osu.Game/Screens/Menu/ButtonSystem.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index d26709151c..8ebe4de8de 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -85,7 +85,7 @@ namespace osu.Game.Screens.Menu private readonly List buttonsTopLevel = new List(); private readonly List buttonsPlay = new List(); - private Sample sampleBack; + private Sample sampleBackToLogo; private readonly LogoTrackingContainer logoTrackingContainer; @@ -104,7 +104,7 @@ namespace osu.Game.Screens.Menu buttonArea.AddRange(new Drawable[] { new MainMenuButton(ButtonSystemStrings.Settings, string.Empty, FontAwesome.Solid.Cog, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O), - backButton = new MainMenuButton(ButtonSystemStrings.Back, @"button-back-select", OsuIcon.LeftCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, + backButton = new MainMenuButton(ButtonSystemStrings.Back, @"back-to-top", OsuIcon.LeftCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, -WEDGE_WIDTH) { VisibleState = ButtonSystemState.Play, @@ -155,7 +155,7 @@ namespace osu.Game.Screens.Menu if (idleTracker != null) isIdle.BindTo(idleTracker.IsIdle); - sampleBack = audio.Samples.Get(@"Menu/button-back-select"); + sampleBackToLogo = audio.Samples.Get(@"Menu/back-to-logo"); } private void onMultiplayer() @@ -260,7 +260,9 @@ namespace osu.Game.Screens.Menu { case ButtonSystemState.TopLevel: State = ButtonSystemState.Initial; - sampleBack?.Play(); + + // Samples are explicitly played here in response to user interaction and not when transitioning due to idle. + sampleBackToLogo?.Play(); return true; case ButtonSystemState.Play: From f0a1df06aced3051a93684588a6f21a0bf0fafd0 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 8 Nov 2023 22:13:35 +0900 Subject: [PATCH 1395/4092] Add 'swoosh' samples to accentuate `MainMenu` animations --- osu.Game/Screens/Menu/ButtonSystem.cs | 4 ++++ osu.Game/Screens/Menu/MainMenu.cs | 10 +++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 8ebe4de8de..20af7e4d4a 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -86,6 +86,7 @@ namespace osu.Game.Screens.Menu private readonly List buttonsPlay = new List(); private Sample sampleBackToLogo; + private Sample sampleLogoSwoosh; private readonly LogoTrackingContainer logoTrackingContainer; @@ -156,6 +157,7 @@ namespace osu.Game.Screens.Menu if (idleTracker != null) isIdle.BindTo(idleTracker.IsIdle); sampleBackToLogo = audio.Samples.Get(@"Menu/back-to-logo"); + sampleLogoSwoosh = audio.Samples.Get(@"Menu/osu-logo-swoosh"); } private void onMultiplayer() @@ -263,6 +265,8 @@ namespace osu.Game.Screens.Menu // Samples are explicitly played here in response to user interaction and not when transitioning due to idle. sampleBackToLogo?.Play(); + sampleLogoSwoosh?.Play(); + return true; case ButtonSystemState.Play: diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 22040b4f0b..36e336e960 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -7,6 +7,8 @@ using System; using System.Diagnostics; using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -89,8 +91,10 @@ namespace osu.Game.Screens.Menu private SongTicker songTicker; private Container logoTarget; + private Sample reappearSampleSwoosh; + [BackgroundDependencyLoader(true)] - private void load(BeatmapListingOverlay beatmapListing, SettingsOverlay settings, OsuConfigManager config, SessionStatics statics) + private void load(BeatmapListingOverlay beatmapListing, SettingsOverlay settings, OsuConfigManager config, SessionStatics statics, AudioManager audio) { holdDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); loginDisplayed = statics.GetBindable(Static.LoginOverlayDisplayed); @@ -162,6 +166,8 @@ namespace osu.Game.Screens.Menu Buttons.OnSettings = () => settings?.ToggleVisibility(); Buttons.OnBeatmapListing = () => beatmapListing?.ToggleVisibility(); + reappearSampleSwoosh = audio.Samples.Get(@"Menu/reappear-swoosh"); + preloadSongSelect(); } @@ -291,6 +297,8 @@ namespace osu.Game.Screens.Menu { base.OnResuming(e); + reappearSampleSwoosh?.Play(); + ApplyToBackground(b => (b as BackgroundScreenDefault)?.Next()); // we may have consumed our preloaded instance, so let's make another. From f69c2ea39b46fff223c8372e6dd1d2a6b2da5bce Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 8 Nov 2023 22:18:33 +0900 Subject: [PATCH 1396/4092] Implement sample choking/muting for `ButtonSystem` samples --- osu.Game/Screens/Menu/ButtonSystem.cs | 10 ++++++++++ osu.Game/Screens/Menu/MainMenu.cs | 2 ++ osu.Game/Screens/Menu/MainMenuButton.cs | 6 +++++- osu.Game/Screens/Menu/OsuLogo.cs | 10 +++++++++- 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 20af7e4d4a..13464d4927 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -199,6 +199,7 @@ namespace osu.Game.Screens.Menu { if (State == ButtonSystemState.Initial) { + StopSamplePlayback(); logo?.TriggerClick(); return true; } @@ -264,12 +265,14 @@ namespace osu.Game.Screens.Menu State = ButtonSystemState.Initial; // Samples are explicitly played here in response to user interaction and not when transitioning due to idle. + StopSamplePlayback(); sampleBackToLogo?.Play(); sampleLogoSwoosh?.Play(); return true; case ButtonSystemState.Play: + StopSamplePlayback(); backButton.TriggerClick(); return true; @@ -278,6 +281,13 @@ namespace osu.Game.Screens.Menu } } + public void StopSamplePlayback() + { + buttonsPlay.ForEach(button => button.StopSamplePlayback()); + buttonsTopLevel.ForEach(button => button.StopSamplePlayback()); + logo?.StopSamplePlayback(); + } + private bool onOsuLogo() { switch (state) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 36e336e960..0f73707544 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -297,6 +297,8 @@ namespace osu.Game.Screens.Menu { base.OnResuming(e); + // Ensures any playing `ButtonSystem` samples are stopped when returning to MainMenu (as to not overlap with the 'back' sample) + Buttons.StopSamplePlayback(); reappearSampleSwoosh?.Play(); ApplyToBackground(b => (b as BackgroundScreenDefault)?.Next()); diff --git a/osu.Game/Screens/Menu/MainMenuButton.cs b/osu.Game/Screens/Menu/MainMenuButton.cs index c3a96e36a1..63fc34b4fb 100644 --- a/osu.Game/Screens/Menu/MainMenuButton.cs +++ b/osu.Game/Screens/Menu/MainMenuButton.cs @@ -51,6 +51,7 @@ namespace osu.Game.Screens.Menu private readonly Action clickAction; private Sample sampleClick; private Sample sampleHover; + private SampleChannel sampleChannel; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos); @@ -225,7 +226,8 @@ namespace osu.Game.Screens.Menu private void trigger() { - sampleClick?.Play(); + sampleChannel = sampleClick?.GetChannel(); + sampleChannel?.Play(); clickAction?.Invoke(); @@ -237,6 +239,8 @@ namespace osu.Game.Screens.Menu public override bool HandleNonPositionalInput => state == ButtonState.Expanded; public override bool HandlePositionalInput => state != ButtonState.Exploded && box.Scale.X >= 0.8f; + public void StopSamplePlayback() => sampleChannel?.Stop(); + protected override void Update() { iconText.Alpha = Math.Clamp((box.Scale.X - 0.5f) / 0.3f, 0, 1); diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 8867ecfb2a..75ef8be02e 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -52,6 +52,8 @@ namespace osu.Game.Screens.Menu private readonly IntroSequence intro; private Sample sampleClick; + private SampleChannel sampleClickChannel; + private Sample sampleBeat; private Sample sampleDownbeat; @@ -391,7 +393,11 @@ namespace osu.Game.Screens.Menu flashLayer.FadeOut(1500, Easing.OutExpo); if (Action?.Invoke() == true) - sampleClick.Play(); + { + StopSamplePlayback(); + sampleClickChannel = sampleClick.GetChannel(); + sampleClickChannel.Play(); + } return true; } @@ -440,6 +446,8 @@ namespace osu.Game.Screens.Menu private Container currentProxyTarget; private Drawable proxy; + public void StopSamplePlayback() => sampleClickChannel?.Stop(); + public Drawable ProxyToContainer(Container c) { if (currentProxyTarget != null) From 12a148d1084b1a6f11525ff16c223ecb6ddd6927 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Nov 2023 12:26:41 +0900 Subject: [PATCH 1397/4092] Use new initialisation structure for github sourced update manager --- osu.Desktop/Updater/SquirrelUpdateManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index 941ab335e8..dba157a6e9 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -10,8 +10,8 @@ using osu.Game; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osu.Game.Screens.Play; -using Squirrel; using Squirrel.SimpleSplat; +using Squirrel.Sources; using LogLevel = Squirrel.SimpleSplat.LogLevel; using UpdateManager = osu.Game.Updater.UpdateManager; @@ -63,7 +63,7 @@ namespace osu.Desktop.Updater if (localUserInfo?.IsPlaying.Value == true) return false; - updateManager ??= new GithubUpdateManager(@"https://github.com/ppy/osu", false, github_token, @"osulazer"); + updateManager ??= new Squirrel.UpdateManager(new GithubSource(@"https://github.com/ppy/osu", github_token, false), @"osulazer"); var info = await updateManager.CheckForUpdate(!useDeltaPatching).ConfigureAwait(false); From 8a47f05b1615e126c9841b52dd63576a5b1b521a Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 9 Nov 2023 14:01:33 +0900 Subject: [PATCH 1398/4092] Always play 'swoosh' sample when transitioning back to logo --- osu.Game/Screens/Menu/ButtonSystem.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 13464d4927..a0cf9f5322 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -267,7 +267,6 @@ namespace osu.Game.Screens.Menu // Samples are explicitly played here in response to user interaction and not when transitioning due to idle. StopSamplePlayback(); sampleBackToLogo?.Play(); - sampleLogoSwoosh?.Play(); return true; @@ -362,6 +361,9 @@ namespace osu.Game.Screens.Menu logo?.MoveTo(new Vector2(0.5f), 800, Easing.OutExpo); logo?.ScaleTo(1, 800, Easing.OutExpo); }, buttonArea.Alpha * 150); + + if (lastState == ButtonSystemState.TopLevel) + sampleLogoSwoosh?.Play(); break; case ButtonSystemState.TopLevel: From 5d5c803cf6fc97c044551a8bd7d3d11f0db8f41c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 9 Nov 2023 17:48:43 +0900 Subject: [PATCH 1399/4092] Add failing test case for touch device application during gameplay with incompatible mods active --- .../Mods/TestSceneOsuModTouchDevice.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs index e41cc8cfbc..abeb56209e 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs @@ -9,6 +9,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Timing; using osu.Game.Configuration; using osu.Game.Input; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; @@ -113,6 +114,25 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods AddAssert("touch device mod activated", () => Player.Score.ScoreInfo.Mods, () => Has.One.InstanceOf()); } + [Test] + public void TestIncompatibleModActive() + { + // this is only a veneer of enabling autopilot as having it actually active from the start is annoying to make happen + // given the tests' structure. + AddStep("enable autopilot", () => Player.Score.ScoreInfo.Mods = new Mod[] { new OsuModAutopilot() }); + + AddUntilStep("wait until 0 near", () => Player.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(0).Within(500)); + AddStep("slow down", () => Player.GameplayClockContainer.AdjustmentsFromMods.Frequency.Value = 0.2); + AddUntilStep("wait until 0", () => Player.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(0)); + AddStep("touch playfield", () => + { + var touch = new Touch(TouchSource.Touch1, Player.DrawableRuleset.Playfield.ScreenSpaceDrawQuad.Centre); + InputManager.BeginTouch(touch); + InputManager.EndTouch(touch); + }); + AddAssert("touch device mod not activated", () => Player.Score.ScoreInfo.Mods, () => Has.None.InstanceOf()); + } + [Test] public void TestSecondObjectTouched() { From 63a0ea54109c93984501b66435febf676663cab4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 9 Nov 2023 17:50:57 +0900 Subject: [PATCH 1400/4092] Fix `PlayerTouchInputDetector` applying touch device mod when other inactive mods present --- osu.Game/Screens/Play/PlayerTouchInputDetector.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerTouchInputDetector.cs b/osu.Game/Screens/Play/PlayerTouchInputDetector.cs index a5055dfb00..69c3cd0ded 100644 --- a/osu.Game/Screens/Play/PlayerTouchInputDetector.cs +++ b/osu.Game/Screens/Play/PlayerTouchInputDetector.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; +using osu.Game.Utils; namespace osu.Game.Screens.Play { @@ -45,10 +46,15 @@ namespace osu.Game.Screens.Play if (touchDeviceMod == null) return; + var candidateMods = player.Score.ScoreInfo.Mods.Append(touchDeviceMod).ToArray(); + + if (!ModUtils.CheckCompatibleSet(candidateMods, out _)) + return; + // `Player` (probably rightly so) assumes immutability of mods, // so this will not be shown immediately on the mod display in the top right. // if this is to change, the mod immutability should be revisited. - player.Score.ScoreInfo.Mods = player.Score.ScoreInfo.Mods.Append(touchDeviceMod).ToArray(); + player.Score.ScoreInfo.Mods = candidateMods; } } } From e3e752b912ba0b14f9d2d2a96bc1695e65eb94cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Nov 2023 17:56:58 +0900 Subject: [PATCH 1401/4092] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 60c71a736d..73cd239854 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + From ccb9ff826a87dbafbbeb0a0a572a404bc88c7e88 Mon Sep 17 00:00:00 2001 From: Joshua Hegedus Date: Thu, 9 Nov 2023 13:09:59 +0100 Subject: [PATCH 1402/4092] fixed tests --- .../Online/TestSceneUserClickableAvatar.cs | 130 +++++++++--------- .../OnlinePlay/Components/ParticipantsList.cs | 2 +- .../DrawableRoomParticipantsList.cs | 2 +- osu.Game/Users/Drawables/ClickableAvatar.cs | 115 +++++++++++----- osu.Game/Users/Drawables/UpdateableAvatar.cs | 9 +- 5 files changed, 152 insertions(+), 106 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs b/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs index 678767f15e..9217104aa8 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs @@ -1,11 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; +using osu.Framework.Testing; using osu.Game.Online.API.Requests.Responses; using osu.Game.Users; using osu.Game.Users.Drawables; @@ -28,8 +30,10 @@ namespace osu.Game.Tests.Visual.Online Children = new[] { generateUser(@"peppy", 2, CountryCode.AU, @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", false, "99EB47"), - generateUser(@"flyte", 3103765, CountryCode.JP, @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg", false), - generateUser(@"joshika39", 17032217, CountryCode.RS, @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", true), + generateUser(@"flyte", 3103765, CountryCode.JP, @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg", true), + generateUser(@"joshika39", 17032217, CountryCode.RS, @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", false), + new UpdateableAvatar(), + new UpdateableAvatar() }, }; }); @@ -37,68 +41,68 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestClickableAvatarHover() { - // AddStep($"click {1}. {nameof(ClickableAvatar)}", () => - // { - // var targets = this.ChildrenOfType().ToList(); - // if (targets.Count < 1) - // return; - // - // InputManager.MoveMouseTo(targets[0]); - // }); - // AddWaitStep("wait for tooltip to show", 5); - // AddStep("Hover out", () => InputManager.MoveMouseTo(new Vector2(0))); - // AddWaitStep("wait for tooltip to hide", 3); - // - // AddStep($"click {2}. {nameof(ClickableAvatar)}", () => - // { - // var targets = this.ChildrenOfType().ToList(); - // if (targets.Count < 2) - // return; - // - // InputManager.MoveMouseTo(targets[1]); - // }); - // AddWaitStep("wait for tooltip to show", 5); - // AddStep("Hover out", () => InputManager.MoveMouseTo(new Vector2(0))); - // AddWaitStep("wait for tooltip to hide", 3); - // - // AddStep($"click {3}. {nameof(ClickableAvatar)}", () => - // { - // var targets = this.ChildrenOfType().ToList(); - // if (targets.Count < 3) - // return; - // - // InputManager.MoveMouseTo(targets[2]); - // }); - // AddWaitStep("wait for tooltip to show", 5); - // AddStep("Hover out", () => InputManager.MoveMouseTo(new Vector2(0))); - // AddWaitStep("wait for tooltip to hide", 3); - // - // AddStep($"click null user {4}. {nameof(ClickableAvatar)}", () => - // { - // var targets = this.ChildrenOfType().ToList(); - // if (targets.Count < 4) - // return; - // - // InputManager.MoveMouseTo(targets[3]); - // }); - // AddWaitStep("wait for tooltip to show", 5); - // AddStep("Hover out", () => InputManager.MoveMouseTo(new Vector2(0))); - // AddWaitStep("wait for tooltip to hide", 3); - // - // AddStep($"click null user {5}. {nameof(ClickableAvatar)}", () => - // { - // var targets = this.ChildrenOfType().ToList(); - // if (targets.Count < 5) - // return; - // - // InputManager.MoveMouseTo(targets[4]); - // }); - // AddWaitStep("wait for tooltip to show", 5); - // AddStep("Hover out", () => InputManager.MoveMouseTo(new Vector2(0))); - // AddWaitStep("wait for tooltip to hide", 3); + AddStep($"click user {1} with UserGridPanel {nameof(ClickableAvatar)}", () => + { + var targets = this.ChildrenOfType().ToList(); + if (targets.Count < 1) + return; + + InputManager.MoveMouseTo(targets[0]); + }); + AddWaitStep("wait for tooltip to show", 5); + AddStep("Hover out", () => InputManager.MoveMouseTo(new Vector2(0))); + AddWaitStep("wait for tooltip to hide", 3); + + AddStep($"click user {2} with username only. {nameof(ClickableAvatar)}", () => + { + var targets = this.ChildrenOfType().ToList(); + if (targets.Count < 2) + return; + + InputManager.MoveMouseTo(targets[1]); + }); + AddWaitStep("wait for tooltip to show", 5); + AddStep("Hover out", () => InputManager.MoveMouseTo(new Vector2(0))); + AddWaitStep("wait for tooltip to hide", 3); + + AddStep($"click user {3} with UserGridPanel {nameof(ClickableAvatar)}", () => + { + var targets = this.ChildrenOfType().ToList(); + if (targets.Count < 3) + return; + + InputManager.MoveMouseTo(targets[2]); + }); + AddWaitStep("wait for tooltip to show", 5); + AddStep("Hover out", () => InputManager.MoveMouseTo(new Vector2(0))); + AddWaitStep("wait for tooltip to hide", 3); + + AddStep($"click null user {4}. {nameof(ClickableAvatar)}", () => + { + var targets = this.ChildrenOfType().ToList(); + if (targets.Count < 4) + return; + + InputManager.MoveMouseTo(targets[3]); + }); + AddWaitStep("wait for tooltip to show", 5); + AddStep("Hover out", () => InputManager.MoveMouseTo(new Vector2(0))); + AddWaitStep("wait for tooltip to hide", 3); + + AddStep($"click null user {5}. {nameof(ClickableAvatar)}", () => + { + var targets = this.ChildrenOfType().ToList(); + if (targets.Count < 5) + return; + + InputManager.MoveMouseTo(targets[4]); + }); + AddWaitStep("wait for tooltip to show", 5); + AddStep("Hover out", () => InputManager.MoveMouseTo(new Vector2(0))); + AddWaitStep("wait for tooltip to hide", 3); } - private Drawable generateUser(string username, int id, CountryCode countryCode, string cover, bool isTooltipEnabled, string? color = null) + private Drawable generateUser(string username, int id, CountryCode countryCode, string cover, bool onlyUsername, string? color = null) { return new ClickableAvatar(new APIUser { @@ -121,7 +125,7 @@ namespace osu.Game.Tests.Visual.Online { Type = EdgeEffectType.Shadow, Radius = 1, Colour = Color4.Black.Opacity(0.2f), }, - IsTooltipEnabled = isTooltipEnabled, + ShowUsernameOnly = onlyUsername, }; } } diff --git a/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs b/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs index cb1a846d6c..8cde7859b2 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs @@ -115,7 +115,7 @@ namespace osu.Game.Screens.OnlinePlay.Components RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex(@"27252d"), }, - avatar = new UpdateableAvatar { RelativeSizeAxes = Axes.Both }, + avatar = new UpdateableAvatar(showUsernameOnly: true) { RelativeSizeAxes = Axes.Both }, }; } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs index 1814f5359f..65f0555612 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs @@ -289,7 +289,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components set => avatar.User = value; } - private readonly UpdateableAvatar avatar = new UpdateableAvatar { RelativeSizeAxes = Axes.Both }; + private readonly UpdateableAvatar avatar = new UpdateableAvatar(showUsernameOnly: true) { RelativeSizeAxes = Axes.Both }; [BackgroundDependencyLoader] private void load(OverlayColourProvider colours) diff --git a/osu.Game/Users/Drawables/ClickableAvatar.cs b/osu.Game/Users/Drawables/ClickableAvatar.cs index 376ce0b821..e7934016bc 100644 --- a/osu.Game/Users/Drawables/ClickableAvatar.cs +++ b/osu.Game/Users/Drawables/ClickableAvatar.cs @@ -1,44 +1,43 @@ // 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.Cursor; +using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Graphics.Containers; -using osu.Game.Localisation; +using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; using osuTK; +using osuTK.Graphics; namespace osu.Game.Users.Drawables { - public partial class ClickableAvatar : OsuClickableContainer, IHasCustomTooltip + public partial class ClickableAvatar : OsuClickableContainer, IHasCustomTooltip { - public ITooltip GetCustomTooltip() - { - return new APIUserTooltip(user); - } + // public ITooltip GetCustomTooltip() => new APIUserTooltip(user!) { ShowTooltip = TooltipEnabled }; + public ITooltip GetCustomTooltip() => new APIUserTooltip(new APIUserTooltipContent(user!)); - public APIUser? TooltipContent => user; - - public override LocalisableString TooltipText - { - get - { - if (!Enabled.Value) - return string.Empty; - - return !IsTooltipEnabled ? (user?.Username ?? string.Empty) : ContextMenuStrings.ViewProfile; - } - set => throw new NotSupportedException(); - } - - public bool IsTooltipEnabled { get; set; } + public APIUserTooltipContent TooltipContent => content; + private readonly APIUserTooltipContent content; private readonly APIUser? user; + private bool tooltipEnabled; + + public override LocalisableString TooltipText => user!.Username; + + public bool ShowUsernameOnly + { + get => tooltipEnabled; + set + { + tooltipEnabled = value; + content.ShowUsernameOnly = ShowUsernameOnly; + } + } [Resolved] private OsuGame? game { get; set; } @@ -53,11 +52,8 @@ namespace osu.Game.Users.Drawables if (user?.Id != APIUser.SYSTEM_USER_ID) Action = openProfile; - } - public void SetValue(out bool value) - { - value = IsTooltipEnabled; + content = new APIUserTooltipContent(user!, ShowUsernameOnly); } [BackgroundDependencyLoader] @@ -80,23 +76,57 @@ namespace osu.Game.Users.Drawables return base.OnClick(e); } - public partial class APIUserTooltip : VisibilityContainer, ITooltip + public partial class APIUserTooltip : VisibilityContainer, ITooltip { - private APIUser? user; - - public APIUserTooltip(APIUser? user) + private OsuSpriteText text; + private APIUserTooltipContent content; + public APIUserTooltip(APIUserTooltipContent content) { - this.user = user; + this.content = content; + AutoSizeAxes = Axes.Both; + Masking = true; + CornerRadius = 5; + + Child = new UserGridPanel(content.User) + { + Width = 300 + }; + text = new OsuSpriteText() + { + Text = this.content.User.Username + }; } protected override void PopIn() { - if (user is null) + if (content.ShowUsernameOnly) { - return; + Child = new UserGridPanel(content.User) + { + Width = 300 + }; + } + else + { + Alpha = 0; + AutoSizeAxes = Axes.Both; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Gray, + }, + text = new OsuSpriteText() + { + Font = FrameworkFont.Regular.With(size: 16), + Padding = new MarginPadding(5), + Text = content.User.Username + } + }; } - Child = new UserGridPanel(user); this.FadeIn(20, Easing.OutQuint); } @@ -104,9 +134,22 @@ namespace osu.Game.Users.Drawables public void Move(Vector2 pos) => Position = pos; - public void SetContent(APIUser user) + public void SetContent(APIUserTooltipContent content) { - this.user = user; + this.content = content; + text.Text = this.content.User.Username; + } + } + + public class APIUserTooltipContent + { + public APIUser User { get; } + public bool ShowUsernameOnly { get; set; } + + public APIUserTooltipContent(APIUser user, bool showUsernameOnly = false) + { + User = user; + ShowUsernameOnly = showUsernameOnly; } } } diff --git a/osu.Game/Users/Drawables/UpdateableAvatar.cs b/osu.Game/Users/Drawables/UpdateableAvatar.cs index 64d64c56ce..f6363f61e6 100644 --- a/osu.Game/Users/Drawables/UpdateableAvatar.cs +++ b/osu.Game/Users/Drawables/UpdateableAvatar.cs @@ -47,20 +47,20 @@ namespace osu.Game.Users.Drawables private readonly bool isInteractive; private readonly bool showGuestOnNull; - private readonly bool showUserPanel; + private readonly bool showUsernameOnly; /// /// Construct a new UpdateableAvatar. /// /// The initial user to display. /// If set to true, hover/click sounds will play and clicking the avatar will open the user's profile. - /// If set to true, the user status panel will be displayed in the tooltip. + /// If set to true, the user status panel will be displayed in the tooltip. /// Whether to show a default guest representation on null user (as opposed to nothing). - public UpdateableAvatar(APIUser? user = null, bool isInteractive = true, bool showUserPanel = true, bool showGuestOnNull = true) + public UpdateableAvatar(APIUser? user = null, bool isInteractive = true, bool showUsernameOnly = false, bool showGuestOnNull = true) { this.isInteractive = isInteractive; this.showGuestOnNull = showGuestOnNull; - this.showUserPanel = showUserPanel; + this.showUsernameOnly = showUsernameOnly; User = user; } @@ -75,7 +75,6 @@ namespace osu.Game.Users.Drawables return new ClickableAvatar(user) { RelativeSizeAxes = Axes.Both, - IsTooltipEnabled = showUserPanel }; } else From 4900a91c60fda24f76aeba4d2d2bf42cc1929e2d Mon Sep 17 00:00:00 2001 From: Joshua Hegedus Date: Thu, 9 Nov 2023 13:27:09 +0100 Subject: [PATCH 1403/4092] fixed static analysis problems and finished the implementation --- .../Online/TestSceneUserClickableAvatar.cs | 40 +++++++++---------- osu.Game/Users/Drawables/ClickableAvatar.cs | 12 +++--- osu.Game/Users/Drawables/UpdateableAvatar.cs | 11 +++-- 3 files changed, 31 insertions(+), 32 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs b/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs index 9217104aa8..50e5653ad5 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs @@ -105,28 +105,28 @@ namespace osu.Game.Tests.Visual.Online private Drawable generateUser(string username, int id, CountryCode countryCode, string cover, bool onlyUsername, string? color = null) { return new ClickableAvatar(new APIUser + { + Username = username, + Id = id, + CountryCode = countryCode, + CoverUrl = cover, + Colour = color ?? "000000", + Status = { - Username = username, - Id = id, - CountryCode = countryCode, - CoverUrl = cover, - Colour = color ?? "000000", - Status = - { - Value = new UserStatusOnline() - }, - }) + Value = new UserStatusOnline() + }, + }) + { + Width = 50, + Height = 50, + CornerRadius = 10, + Masking = true, + EdgeEffect = new EdgeEffectParameters { - Width = 50, - Height = 50, - CornerRadius = 10, - Masking = true, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, Radius = 1, Colour = Color4.Black.Opacity(0.2f), - }, - ShowUsernameOnly = onlyUsername, - }; + Type = EdgeEffectType.Shadow, Radius = 1, Colour = Color4.Black.Opacity(0.2f), + }, + ShowUsernameOnly = onlyUsername, + }; } } } diff --git a/osu.Game/Users/Drawables/ClickableAvatar.cs b/osu.Game/Users/Drawables/ClickableAvatar.cs index e7934016bc..de0bcad497 100644 --- a/osu.Game/Users/Drawables/ClickableAvatar.cs +++ b/osu.Game/Users/Drawables/ClickableAvatar.cs @@ -21,9 +21,8 @@ namespace osu.Game.Users.Drawables // public ITooltip GetCustomTooltip() => new APIUserTooltip(user!) { ShowTooltip = TooltipEnabled }; public ITooltip GetCustomTooltip() => new APIUserTooltip(new APIUserTooltipContent(user!)); - public APIUserTooltipContent TooltipContent => content; + public APIUserTooltipContent TooltipContent { get; } - private readonly APIUserTooltipContent content; private readonly APIUser? user; private bool tooltipEnabled; @@ -35,7 +34,7 @@ namespace osu.Game.Users.Drawables set { tooltipEnabled = value; - content.ShowUsernameOnly = ShowUsernameOnly; + TooltipContent.ShowUsernameOnly = ShowUsernameOnly; } } @@ -53,7 +52,7 @@ namespace osu.Game.Users.Drawables if (user?.Id != APIUser.SYSTEM_USER_ID) Action = openProfile; - content = new APIUserTooltipContent(user!, ShowUsernameOnly); + TooltipContent = new APIUserTooltipContent(user!, ShowUsernameOnly); } [BackgroundDependencyLoader] @@ -80,6 +79,7 @@ namespace osu.Game.Users.Drawables { private OsuSpriteText text; private APIUserTooltipContent content; + public APIUserTooltip(APIUserTooltipContent content) { this.content = content; @@ -91,7 +91,7 @@ namespace osu.Game.Users.Drawables { Width = 300 }; - text = new OsuSpriteText() + text = new OsuSpriteText { Text = this.content.User.Username }; @@ -118,7 +118,7 @@ namespace osu.Game.Users.Drawables RelativeSizeAxes = Axes.Both, Colour = Color4.Gray, }, - text = new OsuSpriteText() + text = new OsuSpriteText { Font = FrameworkFont.Regular.With(size: 16), Padding = new MarginPadding(5), diff --git a/osu.Game/Users/Drawables/UpdateableAvatar.cs b/osu.Game/Users/Drawables/UpdateableAvatar.cs index f6363f61e6..f220ee5a25 100644 --- a/osu.Game/Users/Drawables/UpdateableAvatar.cs +++ b/osu.Game/Users/Drawables/UpdateableAvatar.cs @@ -75,15 +75,14 @@ namespace osu.Game.Users.Drawables return new ClickableAvatar(user) { RelativeSizeAxes = Axes.Both, + ShowUsernameOnly = showUsernameOnly }; } - else + + return new DrawableAvatar(user) { - return new DrawableAvatar(user) - { - RelativeSizeAxes = Axes.Both, - }; - } + RelativeSizeAxes = Axes.Both, + }; } } } From 4fa158e0d832f44cddefac1b0dd7b94f879e3993 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Nov 2023 21:35:37 +0900 Subject: [PATCH 1404/4092] Split tournament player lists more equally --- .../Components/DrawableTeamWithPlayers.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/Components/DrawableTeamWithPlayers.cs b/osu.Game.Tournament/Components/DrawableTeamWithPlayers.cs index 4f0c7d6b72..fd7a51140b 100644 --- a/osu.Game.Tournament/Components/DrawableTeamWithPlayers.cs +++ b/osu.Game.Tournament/Components/DrawableTeamWithPlayers.cs @@ -1,7 +1,9 @@ // 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.Linq; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; @@ -17,6 +19,11 @@ namespace osu.Game.Tournament.Components { AutoSizeAxes = Axes.Both; + var players = team?.Players ?? new BindableList(); + + // split the players into two even columns, favouring the first column if odd. + int split = (int)Math.Ceiling(players.Count / 2f); + InternalChildren = new Drawable[] { new FillFlowContainer @@ -39,13 +46,13 @@ namespace osu.Game.Tournament.Components { Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Both, - ChildrenEnumerable = team?.Players.Select(createPlayerText).Take(5) ?? Enumerable.Empty() + ChildrenEnumerable = players.Take(split).Select(createPlayerText), }, new FillFlowContainer { Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Both, - ChildrenEnumerable = team?.Players.Select(createPlayerText).Skip(5) ?? Enumerable.Empty() + ChildrenEnumerable = players.Skip(split).Select(createPlayerText), }, } }, From 1ae3265f925911c3a1f7f640805765b68dc5d335 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Nov 2023 21:24:30 +0900 Subject: [PATCH 1405/4092] Update tests in line with new behaviour --- .../OsuDifficultyCalculatorTest.cs | 20 +++++------ .../TestSceneSliderInput.cs | 36 ++++++++++++------- .../Rulesets/Scoring/HitResultTest.cs | 2 +- 3 files changed, 34 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index 7b7deb9c67..1d76c24620 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs @@ -15,22 +15,22 @@ namespace osu.Game.Rulesets.Osu.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; - [TestCase(6.710442985146793d, 206, "diffcalc-test")] - [TestCase(1.4386882251130073d, 45, "zero-length-sliders")] - [TestCase(0.42506480230838789d, 2, "very-fast-slider")] - [TestCase(0.14102693012101306d, 1, "nan-slider")] + [TestCase(6.710442985146793d, 239, "diffcalc-test")] + [TestCase(1.4386882251130073d, 54, "zero-length-sliders")] + [TestCase(0.42506480230838789d, 4, "very-fast-slider")] + [TestCase(0.14102693012101306d, 2, "nan-slider")] public void Test(double expectedStarRating, int expectedMaxCombo, string name) => base.Test(expectedStarRating, expectedMaxCombo, name); - [TestCase(8.9742952703071666d, 206, "diffcalc-test")] - [TestCase(0.55071082800473514d, 2, "very-fast-slider")] - [TestCase(1.743180218215227d, 45, "zero-length-sliders")] + [TestCase(8.9742952703071666d, 239, "diffcalc-test")] + [TestCase(0.55071082800473514d, 4, "very-fast-slider")] + [TestCase(1.743180218215227d, 54, "zero-length-sliders")] public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name) => Test(expectedStarRating, expectedMaxCombo, name, new OsuModDoubleTime()); - [TestCase(6.710442985146793d, 239, "diffcalc-test")] - [TestCase(0.42506480230838789d, 4, "very-fast-slider")] - [TestCase(1.4386882251130073d, 54, "zero-length-sliders")] + [TestCase(6.710442985146793d, 272, "diffcalc-test")] + [TestCase(0.42506480230838789d, 6, "very-fast-slider")] + [TestCase(1.4386882251130073d, 63, "zero-length-sliders")] public void TestClassicMod(double expectedStarRating, int expectedMaxCombo, string name) => Test(expectedStarRating, expectedMaxCombo, name, new OsuModClassic()); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index f718a5069f..1e295aae70 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -157,7 +157,7 @@ namespace osu.Game.Rulesets.Osu.Tests if (hit) assertAllMaxJudgements(); else - AddAssert("Tracking dropped", assertMidSliderJudgementFail); + assertMidSliderJudgementFail(); AddAssert("Head judgement is first", () => judgementResults.First().HitObject is SliderHeadCircle); @@ -197,7 +197,7 @@ namespace osu.Game.Rulesets.Osu.Tests new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_1 }, }); - AddAssert("Tracking lost", assertMidSliderJudgementFail); + assertMidSliderJudgementFail(); } /// @@ -278,7 +278,7 @@ namespace osu.Game.Rulesets.Osu.Tests new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_before_slider }, }); - AddAssert("Tracking retained, sliderhead miss", assertHeadMissTailTracked); + assertHeadMissTailTracked(); } /// @@ -302,7 +302,7 @@ namespace osu.Game.Rulesets.Osu.Tests new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_4 }, }); - AddAssert("Tracking re-acquired", assertMidSliderJudgements); + assertMidSliderJudgements(); } /// @@ -328,7 +328,7 @@ namespace osu.Game.Rulesets.Osu.Tests new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_4 }, }); - AddAssert("Tracking lost", assertMidSliderJudgementFail); + assertMidSliderJudgementFail(); } /// @@ -350,7 +350,7 @@ namespace osu.Game.Rulesets.Osu.Tests new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_4 }, }); - AddAssert("Tracking acquired", assertMidSliderJudgements); + assertMidSliderJudgements(); } /// @@ -373,7 +373,7 @@ namespace osu.Game.Rulesets.Osu.Tests new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_2 }, }); - AddAssert("Tracking acquired", assertMidSliderJudgements); + assertMidSliderJudgements(); } [Test] @@ -387,7 +387,7 @@ namespace osu.Game.Rulesets.Osu.Tests new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.RightButton }, Time = time_during_slide_2 }, }); - AddAssert("Tracking acquired", assertMidSliderJudgements); + assertMidSliderJudgements(); } /// @@ -412,7 +412,7 @@ namespace osu.Game.Rulesets.Osu.Tests new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_4 }, }); - AddAssert("Tracking acquired", assertMidSliderJudgements); + assertMidSliderJudgements(); } /// @@ -454,7 +454,7 @@ namespace osu.Game.Rulesets.Osu.Tests new OsuReplayFrame { Position = new Vector2(slider_path_length, OsuHitObject.OBJECT_RADIUS * 1.201f), Actions = { OsuAction.LeftButton }, Time = time_slider_end }, }); - AddAssert("Tracking dropped", assertMidSliderJudgementFail); + assertMidSliderJudgementFail(); } private void assertAllMaxJudgements() @@ -465,11 +465,21 @@ namespace osu.Game.Rulesets.Osu.Tests }, () => Is.EqualTo(judgementResults.Select(j => (j.HitObject, j.Judgement.MaxResult)))); } - private bool assertHeadMissTailTracked() => judgementResults[^2].Type == HitResult.SmallTickHit && !judgementResults.First().IsHit; + private void assertHeadMissTailTracked() + { + AddAssert("Tracking retained", () => judgementResults[^2].Type, () => Is.EqualTo(HitResult.LargeTickHit)); + AddAssert("Slider head misseed", () => judgementResults.First().IsHit, () => Is.False); + } - private bool assertMidSliderJudgements() => judgementResults[^2].Type == HitResult.SmallTickHit; + private void assertMidSliderJudgements() + { + AddAssert("Tracking acquired", () => judgementResults[^2].Type, () => Is.EqualTo(HitResult.LargeTickHit)); + } - private bool assertMidSliderJudgementFail() => judgementResults[^2].Type == HitResult.SmallTickMiss; + private void assertMidSliderJudgementFail() + { + AddAssert("Tracking lost", () => judgementResults[^2].Type, () => Is.EqualTo(HitResult.IgnoreMiss)); + } private void performTest(List frames, Slider? slider = null, double? bpm = null, int? tickRate = null) { diff --git a/osu.Game.Tests/Rulesets/Scoring/HitResultTest.cs b/osu.Game.Tests/Rulesets/Scoring/HitResultTest.cs index 68d7335055..72acd18c5b 100644 --- a/osu.Game.Tests/Rulesets/Scoring/HitResultTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/HitResultTest.cs @@ -18,7 +18,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(new[] { HitResult.IgnoreHit }, new[] { HitResult.IgnoreMiss, HitResult.ComboBreak })] public void TestValidResultPairs(HitResult[] maxResults, HitResult[] minResults) { - HitResult[] unsupportedResults = HitResultExtensions.ALL_TYPES.Where(t => !minResults.Contains(t)).ToArray(); + HitResult[] unsupportedResults = HitResultExtensions.ALL_TYPES.Where(t => t != HitResult.IgnoreMiss && !minResults.Contains(t)).ToArray(); Assert.Multiple(() => { From edef31f426f8568afc3abf53b7611e525308c1d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Nov 2023 21:48:41 +0900 Subject: [PATCH 1406/4092] Correctly propagate classic behaviour flag to tail --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 7f2d9592af..cb6827b428 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -135,6 +135,8 @@ namespace osu.Game.Rulesets.Osu.Objects classicSliderBehaviour = value; if (HeadCircle != null) HeadCircle.ClassicSliderBehaviour = value; + if (TailCircle != null) + TailCircle.ClassicSliderBehaviour = value; } } @@ -218,6 +220,7 @@ namespace osu.Game.Rulesets.Osu.Objects StartTime = e.Time, Position = EndPosition, StackHeight = StackHeight, + ClassicSliderBehaviour = ClassicSliderBehaviour, }); break; From 615d8384abcc3c412a831a70a73ed0f6c6173467 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Nov 2023 22:27:29 +0900 Subject: [PATCH 1407/4092] Refactor everythign back to sanity --- .../Cards/Statistics/BeatmapCardStatistic.cs | 2 +- osu.Game/Users/Drawables/ClickableAvatar.cs | 93 +++---------------- 2 files changed, 13 insertions(+), 82 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/Statistics/BeatmapCardStatistic.cs b/osu.Game/Beatmaps/Drawables/Cards/Statistics/BeatmapCardStatistic.cs index 10de2b9128..6fd7142c05 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Statistics/BeatmapCardStatistic.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Statistics/BeatmapCardStatistic.cs @@ -74,7 +74,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Statistics #region Tooltip implementation - public virtual ITooltip GetCustomTooltip() => null; + public virtual ITooltip GetCustomTooltip() => null!; public virtual object TooltipContent => null; #endregion diff --git a/osu.Game/Users/Drawables/ClickableAvatar.cs b/osu.Game/Users/Drawables/ClickableAvatar.cs index de0bcad497..48a12acb5e 100644 --- a/osu.Game/Users/Drawables/ClickableAvatar.cs +++ b/osu.Game/Users/Drawables/ClickableAvatar.cs @@ -5,38 +5,25 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; -using osu.Framework.Localisation; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; +using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osuTK; -using osuTK.Graphics; namespace osu.Game.Users.Drawables { - public partial class ClickableAvatar : OsuClickableContainer, IHasCustomTooltip + public partial class ClickableAvatar : OsuClickableContainer, IHasCustomTooltip { // public ITooltip GetCustomTooltip() => new APIUserTooltip(user!) { ShowTooltip = TooltipEnabled }; - public ITooltip GetCustomTooltip() => new APIUserTooltip(new APIUserTooltipContent(user!)); + public ITooltip GetCustomTooltip() => new UserCardTooltip(); - public APIUserTooltipContent TooltipContent { get; } + public APIUser? TooltipContent { get; } private readonly APIUser? user; - private bool tooltipEnabled; - public override LocalisableString TooltipText => user!.Username; - - public bool ShowUsernameOnly - { - get => tooltipEnabled; - set - { - tooltipEnabled = value; - TooltipContent.ShowUsernameOnly = ShowUsernameOnly; - } - } + // TODO: reimplement. + public bool ShowUsernameOnly { get; set; } [Resolved] private OsuGame? game { get; set; } @@ -47,12 +34,10 @@ namespace osu.Game.Users.Drawables /// The user. A null value will get a placeholder avatar. public ClickableAvatar(APIUser? user = null) { - this.user = user; - if (user?.Id != APIUser.SYSTEM_USER_ID) Action = openProfile; - TooltipContent = new APIUserTooltipContent(user!, ShowUsernameOnly); + TooltipContent = this.user = user; } [BackgroundDependencyLoader] @@ -75,58 +60,17 @@ namespace osu.Game.Users.Drawables return base.OnClick(e); } - public partial class APIUserTooltip : VisibilityContainer, ITooltip + public partial class UserCardTooltip : VisibilityContainer, ITooltip { - private OsuSpriteText text; - private APIUserTooltipContent content; - - public APIUserTooltip(APIUserTooltipContent content) + public UserCardTooltip() { - this.content = content; AutoSizeAxes = Axes.Both; Masking = true; CornerRadius = 5; - - Child = new UserGridPanel(content.User) - { - Width = 300 - }; - text = new OsuSpriteText - { - Text = this.content.User.Username - }; } protected override void PopIn() { - if (content.ShowUsernameOnly) - { - Child = new UserGridPanel(content.User) - { - Width = 300 - }; - } - else - { - Alpha = 0; - AutoSizeAxes = Axes.Both; - - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Gray, - }, - text = new OsuSpriteText - { - Font = FrameworkFont.Regular.With(size: 16), - Padding = new MarginPadding(5), - Text = content.User.Username - } - }; - } - this.FadeIn(20, Easing.OutQuint); } @@ -134,23 +78,10 @@ namespace osu.Game.Users.Drawables public void Move(Vector2 pos) => Position = pos; - public void SetContent(APIUserTooltipContent content) + public void SetContent(APIUser? content) => LoadComponentAsync(new UserGridPanel(content ?? new GuestUser()) { - this.content = content; - text.Text = this.content.User.Username; - } - } - - public class APIUserTooltipContent - { - public APIUser User { get; } - public bool ShowUsernameOnly { get; set; } - - public APIUserTooltipContent(APIUser user, bool showUsernameOnly = false) - { - User = user; - ShowUsernameOnly = showUsernameOnly; - } + Width = 300, + }, panel => Child = panel); } } } From 51cf85a9ab3806fe9294d33b24c52165a08aed6b Mon Sep 17 00:00:00 2001 From: Susko3 Date: Thu, 9 Nov 2023 13:22:49 +0100 Subject: [PATCH 1408/4092] Add touch input settings to android Also updates touch settings so the touch handler can't be disabled on mobile. --- osu.Android/OsuGameAndroid.cs | 4 ++++ .../Settings/Sections/Input/TouchSettings.cs | 12 ++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index dea70e6b27..52cfb67f42 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -11,6 +11,7 @@ using osu.Framework.Input.Handlers; using osu.Framework.Platform; using osu.Game; using osu.Game.Overlays.Settings; +using osu.Game.Overlays.Settings.Sections.Input; using osu.Game.Updater; using osu.Game.Utils; @@ -97,6 +98,9 @@ namespace osu.Android case AndroidJoystickHandler jh: return new AndroidJoystickSettings(jh); + case AndroidTouchHandler th: + return new TouchSettings(th); + default: return base.CreateSettingsSubsectionFor(handler); } diff --git a/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs index 175fcc4709..0056de6674 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Input.Handlers; using osu.Framework.Localisation; @@ -28,11 +29,14 @@ namespace osu.Game.Overlays.Settings.Sections.Input [BackgroundDependencyLoader] private void load(OsuConfigManager osuConfig) { - Add(new SettingsCheckbox + if (!RuntimeInfo.IsMobile) // don't allow disabling the only input method (touch) on mobile. { - LabelText = CommonStrings.Enabled, - Current = handler.Enabled - }); + Add(new SettingsCheckbox + { + LabelText = CommonStrings.Enabled, + Current = handler.Enabled + }); + } Add(new SettingsCheckbox { From 0c4c9aa4b515840611b3b39017ff73d04a3ecf1b Mon Sep 17 00:00:00 2001 From: Susko3 Date: Thu, 9 Nov 2023 14:37:10 +0100 Subject: [PATCH 1409/4092] Show touch taps setting in player loader on mobile platforms --- osu.Game/Screens/Play/PlayerSettings/InputSettings.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs b/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs index 852fbd8dcc..1387e01305 100644 --- a/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/InputSettings.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.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Configuration; @@ -22,8 +23,9 @@ namespace osu.Game.Screens.Play.PlayerSettings { new PlayerCheckbox { - LabelText = MouseSettingsStrings.DisableClicksDuringGameplay, - Current = config.GetBindable(OsuSetting.MouseDisableButtons) + // TODO: change to touchscreen detection once https://github.com/ppy/osu/pull/25348 makes it in + LabelText = RuntimeInfo.IsDesktop ? MouseSettingsStrings.DisableClicksDuringGameplay : TouchSettingsStrings.DisableTapsDuringGameplay, + Current = config.GetBindable(RuntimeInfo.IsDesktop ? OsuSetting.MouseDisableButtons : OsuSetting.TouchDisableGameplayTaps) } }; } From 1b08f317fbd1fac55276dc05c29b0339667f2871 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Thu, 9 Nov 2023 15:12:24 +0100 Subject: [PATCH 1410/4092] Show touch input settings on iOS This does not cover android since `TouchHandler` is SDL-based. --- osu.Game/OsuGameBase.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 1f46eb0c0d..f44dac5f5a 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -575,14 +575,14 @@ namespace osu.Game case JoystickHandler jh: return new JoystickSettings(jh); - - case TouchHandler th: - return new TouchSettings(th); } } switch (handler) { + case TouchHandler th: + return new TouchSettings(th); + case MidiHandler: return new InputSection.HandlerSection(handler); From d4722a398892a18f14e868ff471e9132004da8fd Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 9 Nov 2023 17:20:05 +0300 Subject: [PATCH 1411/4092] Add failing test case --- .../TestSceneBackgroundScreenDefault.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs index 1523ae7027..e902303505 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs @@ -181,6 +181,30 @@ namespace osu.Game.Tests.Visual.Background AddStep("restore default beatmap", () => Beatmap.SetDefault()); } + [Test] + public void TestBeatmapBackgroundWithStoryboardUnloadedOnSuspension() + { + BackgroundScreenBeatmap nestedScreen = null; + + setSupporter(true); + setSourceMode(BackgroundSource.BeatmapWithStoryboard); + + AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithStoryboard()); + AddAssert("background changed", () => screen.CheckLastLoadChange() == true); + AddUntilStep("wait for beatmap background to be loaded", () => getCurrentBackground()?.GetType() == typeof(BeatmapBackgroundWithStoryboard)); + + AddUntilStep("storyboard present", () => screen.ChildrenOfType().SingleOrDefault()?.IsLoaded == true); + + AddStep("push new background to stack", () => stack.Push(nestedScreen = new BackgroundScreenBeatmap(Beatmap.Value))); + AddUntilStep("wait for screen to load", () => nestedScreen.IsLoaded && nestedScreen.IsCurrentScreen()); + + AddUntilStep("storyboard unloaded", () => !screen.ChildrenOfType().Any()); + + AddStep("go back", () => screen.MakeCurrent()); + + AddUntilStep("storyboard reloaded", () => screen.ChildrenOfType().SingleOrDefault()?.IsLoaded == true); + } + [Test] public void TestBackgroundTypeSwitch() { From bd8409219f079bcdd922ae2169d676608e512364 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 8 Nov 2023 06:37:29 +0300 Subject: [PATCH 1412/4092] Unload beatmap storyboard background when no longer present --- .../BeatmapBackgroundWithStoryboard.cs | 50 +++++++++++++++++-- osu.Game/Screens/BackgroundScreen.cs | 3 +- osu.Game/Screens/BackgroundScreenStack.cs | 3 ++ .../Backgrounds/BackgroundScreenDefault.cs | 22 ++++++++ 4 files changed, 72 insertions(+), 6 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs index 9c0d109ce4..e78a93396e 100644 --- a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs @@ -1,15 +1,21 @@ // 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.Collections.Generic; +using System.Diagnostics; +using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Threading; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Overlays; using osu.Game.Rulesets.Mods; +using osu.Game.Screens; using osu.Game.Storyboards.Drawables; namespace osu.Game.Graphics.Backgrounds @@ -18,6 +24,10 @@ namespace osu.Game.Graphics.Backgrounds { private readonly InterpolatingFramedClock storyboardClock; + private AudioContainer storyboardContainer = null!; + private DrawableStoryboard? drawableStoryboard; + private CancellationTokenSource? loadCancellationSource = new CancellationTokenSource(); + [Resolved(CanBeNull = true)] private MusicController? musicController { get; set; } @@ -33,18 +43,48 @@ namespace osu.Game.Graphics.Backgrounds [BackgroundDependencyLoader] private void load() { + AddInternal(storyboardContainer = new AudioContainer + { + RelativeSizeAxes = Axes.Both, + Volume = { Value = 0 }, + }); + + LoadStoryboard(); + } + + public void LoadStoryboard() + { + Debug.Assert(drawableStoryboard == null); + if (!Beatmap.Storyboard.HasDrawable) return; if (Beatmap.Storyboard.ReplacesBackground) Sprite.Alpha = 0; - LoadComponentAsync(new AudioContainer + LoadComponentAsync(drawableStoryboard = new DrawableStoryboard(Beatmap.Storyboard, mods.Value) { - RelativeSizeAxes = Axes.Both, - Volume = { Value = 0 }, - Child = new DrawableStoryboard(Beatmap.Storyboard, mods.Value) { Clock = storyboardClock } - }, AddInternal); + Clock = storyboardClock + }, s => + { + storyboardContainer.FadeInFromZero(BackgroundScreen.TRANSITION_LENGTH, Easing.OutQuint); + storyboardContainer.Add(s); + }, (loadCancellationSource = new CancellationTokenSource()).Token); + } + + public void UnloadStoryboard(Action scheduleStoryboardRemoval) + { + Debug.Assert(drawableStoryboard != null); + + loadCancellationSource.AsNonNull().Cancel(); + loadCancellationSource = null; + + DrawableStoryboard s = drawableStoryboard; + + storyboardContainer.FadeOut(BackgroundScreen.TRANSITION_LENGTH, Easing.OutQuint); + scheduleStoryboardRemoval(s); + + drawableStoryboard = null; } protected override void LoadComplete() diff --git a/osu.Game/Screens/BackgroundScreen.cs b/osu.Game/Screens/BackgroundScreen.cs index a7502f22d5..73af9b1bf2 100644 --- a/osu.Game/Screens/BackgroundScreen.cs +++ b/osu.Game/Screens/BackgroundScreen.cs @@ -13,7 +13,8 @@ namespace osu.Game.Screens { public abstract partial class BackgroundScreen : Screen, IEquatable { - protected const float TRANSITION_LENGTH = 500; + public const float TRANSITION_LENGTH = 500; + private const float x_movement_amount = 50; private readonly bool animateOnEnter; diff --git a/osu.Game/Screens/BackgroundScreenStack.cs b/osu.Game/Screens/BackgroundScreenStack.cs index 99ca383b9f..3ec1835669 100644 --- a/osu.Game/Screens/BackgroundScreenStack.cs +++ b/osu.Game/Screens/BackgroundScreenStack.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.Screens; +using osu.Game.Storyboards.Drawables; namespace osu.Game.Screens { @@ -33,5 +34,7 @@ namespace osu.Game.Screens base.Push(screen); return true; } + + public void ScheduleStoryboardDisposal(DrawableStoryboard storyboard) => Scheduler.AddDelayed(storyboard.RemoveAndDisposeImmediately, BackgroundScreen.TRANSITION_LENGTH); } } diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index d9554c10e2..66835363b4 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -3,11 +3,14 @@ #nullable disable +using System.Diagnostics; using System.Threading; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Logging; +using osu.Framework.Screens; using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Beatmaps; @@ -71,6 +74,25 @@ namespace osu.Game.Screens.Backgrounds void next() => Next(); } + public override void OnSuspending(ScreenTransitionEvent e) + { + var backgroundScreenStack = Parent as BackgroundScreenStack; + Debug.Assert(backgroundScreenStack != null); + + if (background is BeatmapBackgroundWithStoryboard storyboardBackground) + storyboardBackground.UnloadStoryboard(backgroundScreenStack.ScheduleStoryboardDisposal); + + base.OnSuspending(e); + } + + public override void OnResuming(ScreenTransitionEvent e) + { + if (background is BeatmapBackgroundWithStoryboard storyboardBackground) + storyboardBackground.LoadStoryboard(); + + base.OnResuming(e); + } + private ScheduledDelegate nextTask; private CancellationTokenSource cancellationTokenSource; From e451b2197c19503d357fc021984844d75d908c1f Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 9 Nov 2023 18:23:53 +0200 Subject: [PATCH 1413/4092] Delete util functions from rulesets --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 10 +++++----- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 18 +++++++++--------- osu.Game.Rulesets.Osu/OsuRuleset.cs | 17 +++++++++-------- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 7 +++---- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 816638ca87..421714e3b2 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -233,15 +233,15 @@ namespace osu.Game.Rulesets.Catch }; } - public double PreemptFromAr(float AR) => AR < 5 ? (1200.0 + 600.0 * (5 - AR) / 5) : (1200.0 - 750.0 * (AR - 5) / 5); - public float ArFromPreempt(double preempt) => (float)(preempt > 1200 ? ((1800 - preempt) / 120) : ((1200 - preempt) / 150 + 5)); - public float ChangeArFromRate(float AR, double rate) => ArFromPreempt(PreemptFromAr(AR) / rate); - public override BeatmapDifficulty GetRateAdjustedDifficulty(IBeatmapDifficultyInfo baseDifficulty, double rate) { BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(baseDifficulty); - adjustedDifficulty.ApproachRate = ChangeArFromRate(adjustedDifficulty.ApproachRate, rate); + double preempt = adjustedDifficulty.ApproachRate < 5 ? + (1200.0 + 600.0 * (5 - adjustedDifficulty.ApproachRate) / 5) : + (1200.0 - 750.0 * (adjustedDifficulty.ApproachRate - 5) / 5); + preempt /= rate; + adjustedDifficulty.ApproachRate = (float)(preempt > 1200 ? ((1800 - preempt) / 120) : ((1200 - preempt) / 150 + 5)); return adjustedDifficulty ?? (BeatmapDifficulty)baseDifficulty; } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index cfdfcbadee..b8324b3f2f 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -428,18 +428,18 @@ namespace osu.Game.Rulesets.Mania public override DifficultySection CreateEditorDifficultySection() => new ManiaDifficultySection(); - public double HitwindowFromOd(float OD) => 64.0 - 3 * OD; - public float OdFromHitwindow(double hitwindow300) => (float)(64.0 - hitwindow300) / 3; - public float ChangeOdFromRate(float OD, double rate) => OdFromHitwindow(HitwindowFromOd(OD) / rate); + // Mania doesn't have rate-adjusted attributes anymore? - public override BeatmapDifficulty GetRateAdjustedDifficulty(IBeatmapDifficultyInfo baseDifficulty, double rate) - { - BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(baseDifficulty); + //public override BeatmapDifficulty GetRateAdjustedDifficulty(IBeatmapDifficultyInfo baseDifficulty, double rate) + //{ + // BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(baseDifficulty); - adjustedDifficulty.OverallDifficulty = ChangeOdFromRate(adjustedDifficulty.OverallDifficulty, rate); + // double hitwindow = 64.0 - 3 * adjustedDifficulty.OverallDifficulty; + // hitwindow /= rate; + // adjustedDifficulty.OverallDifficulty = (float)(64.0 - hitwindow) / 3; - return adjustedDifficulty ?? (BeatmapDifficulty)baseDifficulty; - } + // return adjustedDifficulty ?? (BeatmapDifficulty)baseDifficulty; + //} } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index bdbedf1409..26a51a0c48 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -329,18 +329,19 @@ namespace osu.Game.Rulesets.Osu public override RulesetSetupSection CreateEditorSetupSection() => new OsuSetupSection(); - public double PreemptFromAr(float AR) => AR < 5 ? (1200.0 + 600.0 * (5 - AR) / 5) : (1200.0 - 750.0 * (AR - 5) / 5); - public float ArFromPreempt(double preempt) => (float)(preempt > 1200 ? ((1800 - preempt) / 120) : ((1200 - preempt) / 150 + 5)); - public double HitwindowFromOd(float OD) => 80.0 - 6 * OD; - public float OdFromHitwindow(double hitwindow300) => (float)(80.0 - hitwindow300) / 6; - public float ChangeArFromRate(float AR, double rate) => ArFromPreempt(PreemptFromAr(AR) / rate); - public float ChangeOdFromRate(float OD, double rate) => OdFromHitwindow(HitwindowFromOd(OD) / rate); public override BeatmapDifficulty GetRateAdjustedDifficulty(IBeatmapDifficultyInfo baseDifficulty, double rate) { BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(baseDifficulty); - adjustedDifficulty.ApproachRate = ChangeArFromRate(adjustedDifficulty.ApproachRate, rate); - adjustedDifficulty.OverallDifficulty = ChangeOdFromRate(adjustedDifficulty.OverallDifficulty, rate); + double preempt = adjustedDifficulty.ApproachRate < 5 ? + (1200.0 + 600.0 * (5 - adjustedDifficulty.ApproachRate) / 5) : + (1200.0 - 750.0 * (adjustedDifficulty.ApproachRate - 5) / 5); + preempt /= rate; + adjustedDifficulty.ApproachRate = (float)(preempt > 1200 ? ((1800 - preempt) / 120) : ((1200 - preempt) / 150 + 5)); + + double hitwindow = 80.0 - 6 * adjustedDifficulty.OverallDifficulty; + hitwindow /= rate; + adjustedDifficulty.OverallDifficulty = (float)(80.0 - hitwindow) / 6; return adjustedDifficulty ?? (BeatmapDifficulty)baseDifficulty; } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 026ac4f42e..3f9f939245 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -263,15 +263,14 @@ namespace osu.Game.Rulesets.Taiko }), true) }; } - public double HitwindowFromOd(float OD) => 35.0 - 15.0 * (OD - 5) / 5; - public float OdFromHitwindow(double hitwindow300) => (float)(5 * (35 - hitwindow300) / 15 + 5); - public float ChangeOdFromRate(float OD, double rate) => OdFromHitwindow(HitwindowFromOd(OD) / rate); public override BeatmapDifficulty GetRateAdjustedDifficulty(IBeatmapDifficultyInfo baseDifficulty, double rate) { BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(baseDifficulty); - adjustedDifficulty.OverallDifficulty = ChangeOdFromRate(adjustedDifficulty.OverallDifficulty, rate); + double hitwindow = 35.0 - 15.0 * (adjustedDifficulty.OverallDifficulty - 5) / 5; + hitwindow /= rate; + adjustedDifficulty.OverallDifficulty = (float)(5 * (35 - hitwindow) / 15 + 5); return adjustedDifficulty ?? (BeatmapDifficulty)baseDifficulty; } From 768a31b2f5ec05907f2e1cf18d4bb3f63ae83da1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 9 Nov 2023 22:56:48 +0300 Subject: [PATCH 1414/4092] Fix background crash on a beatmap with no storyboard --- .../Backgrounds/BeatmapBackgroundWithStoryboard.cs | 8 +++----- osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs | 1 - 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs index e78a93396e..75cebb275f 100644 --- a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs @@ -10,7 +10,6 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Threading; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Overlays; @@ -74,15 +73,14 @@ namespace osu.Game.Graphics.Backgrounds public void UnloadStoryboard(Action scheduleStoryboardRemoval) { - Debug.Assert(drawableStoryboard != null); + if (drawableStoryboard == null) + return; loadCancellationSource.AsNonNull().Cancel(); loadCancellationSource = null; - DrawableStoryboard s = drawableStoryboard; - storyboardContainer.FadeOut(BackgroundScreen.TRANSITION_LENGTH, Easing.OutQuint); - scheduleStoryboardRemoval(s); + scheduleStoryboardRemoval(drawableStoryboard); drawableStoryboard = null; } diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 66835363b4..e22f61d806 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -5,7 +5,6 @@ using System.Diagnostics; using System.Threading; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; From cc9aeb53079f6f00af307fb0544e0335d9eebcc8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 9 Nov 2023 22:57:12 +0300 Subject: [PATCH 1415/4092] Add test coverage --- .../TestSceneBackgroundScreenDefault.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs index e902303505..37f2ee0b3f 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs @@ -205,6 +205,30 @@ namespace osu.Game.Tests.Visual.Background AddUntilStep("storyboard reloaded", () => screen.ChildrenOfType().SingleOrDefault()?.IsLoaded == true); } + [Test] + public void TestBeatmapBackgroundWithStoryboardButBeatmapHasNone() + { + BackgroundScreenBeatmap nestedScreen = null; + + setSupporter(true); + setSourceMode(BackgroundSource.BeatmapWithStoryboard); + + AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground()); + AddAssert("background changed", () => screen.CheckLastLoadChange() == true); + AddUntilStep("wait for beatmap background to be loaded", () => getCurrentBackground()?.GetType() == typeof(BeatmapBackgroundWithStoryboard)); + + AddUntilStep("no storyboard loaded", () => !screen.ChildrenOfType().Any()); + + AddStep("push new background to stack", () => stack.Push(nestedScreen = new BackgroundScreenBeatmap(Beatmap.Value))); + AddUntilStep("wait for screen to load", () => nestedScreen.IsLoaded && nestedScreen.IsCurrentScreen()); + + AddUntilStep("still no storyboard", () => !screen.ChildrenOfType().Any()); + + AddStep("go back", () => screen.MakeCurrent()); + + AddUntilStep("still no storyboard", () => !screen.ChildrenOfType().Any()); + } + [Test] public void TestBackgroundTypeSwitch() { From e9471589697083d8ea2214b299aca226085e7de9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 9 Nov 2023 23:03:01 +0300 Subject: [PATCH 1416/4092] Remove fade out transition Unnecessary addition from this PR, makes the background fade to ugly black during transition between screens. --- osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs index 75cebb275f..9a3d64549b 100644 --- a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs @@ -79,7 +79,6 @@ namespace osu.Game.Graphics.Backgrounds loadCancellationSource.AsNonNull().Cancel(); loadCancellationSource = null; - storyboardContainer.FadeOut(BackgroundScreen.TRANSITION_LENGTH, Easing.OutQuint); scheduleStoryboardRemoval(drawableStoryboard); drawableStoryboard = null; From 59998b507acf084cc8d4ffbe85e16d1f3fab9229 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 9 Nov 2023 23:23:49 +0300 Subject: [PATCH 1417/4092] Hide background sprite when storyboard finishes loading --- .../BeatmapBackgroundWithStoryboard.cs | 16 +++++++++++----- osu.Game/Screens/BackgroundScreenStack.cs | 4 ++-- .../Backgrounds/BackgroundScreenDefault.cs | 2 +- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs index 9a3d64549b..1e702967b6 100644 --- a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs @@ -58,20 +58,20 @@ namespace osu.Game.Graphics.Backgrounds if (!Beatmap.Storyboard.HasDrawable) return; - if (Beatmap.Storyboard.ReplacesBackground) - Sprite.Alpha = 0; - LoadComponentAsync(drawableStoryboard = new DrawableStoryboard(Beatmap.Storyboard, mods.Value) { Clock = storyboardClock }, s => { + if (Beatmap.Storyboard.ReplacesBackground) + Sprite.FadeOut(BackgroundScreen.TRANSITION_LENGTH, Easing.InQuint); + storyboardContainer.FadeInFromZero(BackgroundScreen.TRANSITION_LENGTH, Easing.OutQuint); storyboardContainer.Add(s); }, (loadCancellationSource = new CancellationTokenSource()).Token); } - public void UnloadStoryboard(Action scheduleStoryboardRemoval) + public void UnloadStoryboard(Action scheduleStoryboardRemoval) { if (drawableStoryboard == null) return; @@ -79,7 +79,13 @@ namespace osu.Game.Graphics.Backgrounds loadCancellationSource.AsNonNull().Cancel(); loadCancellationSource = null; - scheduleStoryboardRemoval(drawableStoryboard); + DrawableStoryboard s = drawableStoryboard; + + scheduleStoryboardRemoval(() => + { + s.RemoveAndDisposeImmediately(); + Sprite.Alpha = 1f; + }); drawableStoryboard = null; } diff --git a/osu.Game/Screens/BackgroundScreenStack.cs b/osu.Game/Screens/BackgroundScreenStack.cs index 3ec1835669..9af6601aa4 100644 --- a/osu.Game/Screens/BackgroundScreenStack.cs +++ b/osu.Game/Screens/BackgroundScreenStack.cs @@ -1,10 +1,10 @@ // 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.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.Screens; -using osu.Game.Storyboards.Drawables; namespace osu.Game.Screens { @@ -35,6 +35,6 @@ namespace osu.Game.Screens return true; } - public void ScheduleStoryboardDisposal(DrawableStoryboard storyboard) => Scheduler.AddDelayed(storyboard.RemoveAndDisposeImmediately, BackgroundScreen.TRANSITION_LENGTH); + internal void ScheduleToTransitionEnd(Action action) => Scheduler.AddDelayed(action, BackgroundScreen.TRANSITION_LENGTH); } } diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index e22f61d806..07b1cc6df4 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -79,7 +79,7 @@ namespace osu.Game.Screens.Backgrounds Debug.Assert(backgroundScreenStack != null); if (background is BeatmapBackgroundWithStoryboard storyboardBackground) - storyboardBackground.UnloadStoryboard(backgroundScreenStack.ScheduleStoryboardDisposal); + storyboardBackground.UnloadStoryboard(backgroundScreenStack.ScheduleToTransitionEnd); base.OnSuspending(e); } From 6f5d905ce766da9b2e3753585a41298118e9c6b9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 10 Nov 2023 00:34:29 +0300 Subject: [PATCH 1418/4092] Update argon accuracy counter design --- .../Screens/Play/HUD/ArgonAccuracyCounter.cs | 59 +++++++++++- .../Play/HUD/ArgonCounterTextComponent.cs | 94 ++++++++++--------- .../Screens/Play/HUD/ArgonScoreCounter.cs | 5 - 3 files changed, 107 insertions(+), 51 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs index 0414cbaea4..160841b40f 100644 --- a/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs @@ -3,7 +3,9 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Skinning; using osuTK; @@ -22,9 +24,64 @@ namespace osu.Game.Screens.Play.HUD public bool UsesFixedAnchor { get; set; } - protected override IHasText CreateText() => new ArgonCounterTextComponent(Anchor.TopLeft, "ACCURACY", new Vector2(-4, 0)) + protected override IHasText CreateText() => new ArgonAccuracyTextComponent { WireframeOpacity = { BindTarget = WireframeOpacity }, }; + + private partial class ArgonAccuracyTextComponent : CompositeDrawable, IHasText + { + private readonly ArgonCounterTextComponent wholePart; + private readonly ArgonCounterTextComponent fractionPart; + + public IBindable WireframeOpacity { get; } = new BindableFloat(); + + public LocalisableString Text + { + get => wholePart.Text; + set + { + string[] split = value.ToString().Replace("%", string.Empty).Split("."); + + wholePart.Text = split[0]; + fractionPart.Text = "." + split[1]; + } + } + + public ArgonAccuracyTextComponent() + { + AutoSizeAxes = Axes.Both; + + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new Container + { + AutoSizeAxes = Axes.Both, + Child = wholePart = new ArgonCounterTextComponent(Anchor.TopRight, "ACCURACY") + { + RequiredDisplayDigits = { Value = 3 }, + WireframeOpacity = { BindTarget = WireframeOpacity } + } + }, + fractionPart = new ArgonCounterTextComponent(Anchor.TopLeft) + { + Margin = new MarginPadding { Top = 12f * 2f + 4f }, // +4 to account for the extra spaces above the digits. + WireframeOpacity = { BindTarget = WireframeOpacity }, + Scale = new Vector2(0.5f), + }, + new ArgonCounterTextComponent(Anchor.TopLeft) + { + Text = @"%", + Margin = new MarginPadding { Top = 12f }, + WireframeOpacity = { BindTarget = WireframeOpacity } + }, + } + }; + } + } } } diff --git a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs index 3d8546e0e3..759a5dbfea 100644 --- a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs +++ b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -19,62 +20,30 @@ namespace osu.Game.Screens.Play.HUD { public partial class ArgonCounterTextComponent : CompositeDrawable, IHasText { - private readonly LocalisableString? label; - private readonly ArgonCounterSpriteText wireframesPart; private readonly ArgonCounterSpriteText textPart; + private readonly OsuSpriteText labelText; public IBindable WireframeOpacity { get; } = new BindableFloat(); + public Bindable RequiredDisplayDigits { get; } = new BindableInt(); public LocalisableString Text { get => textPart.Text; set { - wireframesPart.Text = FormatWireframes(value); + int remainingCount = RequiredDisplayDigits.Value - value.ToString().Count(char.IsDigit); + string remainingText = remainingCount > 0 ? new string('#', remainingCount) : string.Empty; + + wireframesPart.Text = remainingText + value; textPart.Text = value; } } - public ArgonCounterTextComponent(Anchor anchor, LocalisableString? label = null, Vector2? spacing = null) + public ArgonCounterTextComponent(Anchor anchor, LocalisableString? label = null) { Anchor = anchor; Origin = anchor; - - this.label = label; - - wireframesPart = new ArgonCounterSpriteText(c => - { - if (c == '.') - return @"dot"; - - return @"wireframes"; - }) - { - Anchor = anchor, - Origin = anchor, - Spacing = spacing ?? new Vector2(-2, 0), - }; - textPart = new ArgonCounterSpriteText(c => - { - if (c == '.') - return @"dot"; - - if (c == '%') - return @"percentage"; - - return c.ToString(); - }) - { - Anchor = anchor, - Origin = anchor, - Spacing = spacing ?? new Vector2(-2, 0), - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { AutoSizeAxes = Axes.Both; InternalChild = new FillFlowContainer @@ -83,12 +52,11 @@ namespace osu.Game.Screens.Play.HUD Direction = FillDirection.Vertical, Children = new Drawable[] { - new OsuSpriteText + labelText = new OsuSpriteText { Alpha = label != null ? 1 : 0, Text = label.GetValueOrDefault(), Font = OsuFont.Torus.With(size: 12, weight: FontWeight.Bold), - Colour = colours.Blue0, Margin = new MarginPadding { Left = 2.5f }, }, new Container @@ -96,14 +64,50 @@ namespace osu.Game.Screens.Play.HUD AutoSizeAxes = Axes.Both, Children = new[] { - wireframesPart, - textPart, + wireframesPart = new ArgonCounterSpriteText(wireframesLookup) + { + Anchor = anchor, + Origin = anchor, + }, + textPart = new ArgonCounterSpriteText(textLookup) + { + Anchor = anchor, + Origin = anchor, + }, } } } }; } + private string textLookup(char c) + { + switch (c) + { + case '.': + return @"dot"; + + case '%': + return @"percentage"; + + default: + return c.ToString(); + } + } + + private string wireframesLookup(char c) + { + if (c == '.') return @"dot"; + + return @"wireframes"; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + labelText.Colour = colours.Blue0; + } + protected virtual LocalisableString FormatWireframes(LocalisableString text) => text; protected override void LoadComplete() @@ -131,8 +135,8 @@ namespace osu.Game.Screens.Play.HUD [BackgroundDependencyLoader] private void load(ISkinSource skin) { - // todo: rename font - Font = new FontUsage(@"argon-score", 1); + Spacing = new Vector2(-2f, 0f); + Font = new FontUsage(@"argon-counter", 1); glyphStore = new GlyphStore(skin, getLookup); } diff --git a/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs b/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs index fef4199d31..b15c21801f 100644 --- a/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonScoreCounter.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.Bindables; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; @@ -34,14 +33,10 @@ namespace osu.Game.Screens.Play.HUD private partial class ArgonScoreTextComponent : ArgonCounterTextComponent { - public IBindable RequiredDisplayDigits { get; } = new BindableInt(); - public ArgonScoreTextComponent(Anchor anchor, LocalisableString? label = null) : base(anchor, label) { } - - protected override LocalisableString FormatWireframes(LocalisableString text) => new string('#', Math.Max(text.ToString().Length, RequiredDisplayDigits.Value)); } } } From 7fc2050f7227424b7e734260126146c48e99358e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Nov 2023 11:52:34 +0900 Subject: [PATCH 1419/4092] Rename variables and mak seconary tooltip type work again --- .../Online/TestSceneUserClickableAvatar.cs | 9 ++- .../OnlinePlay/Components/ParticipantsList.cs | 2 +- .../DrawableRoomParticipantsList.cs | 2 +- osu.Game/Users/Drawables/ClickableAvatar.cs | 74 ++++++++++++++----- osu.Game/Users/Drawables/UpdateableAvatar.cs | 11 ++- 5 files changed, 69 insertions(+), 29 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs b/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs index 50e5653ad5..93e2583eb7 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs @@ -102,9 +102,9 @@ namespace osu.Game.Tests.Visual.Online AddWaitStep("wait for tooltip to hide", 3); } - private Drawable generateUser(string username, int id, CountryCode countryCode, string cover, bool onlyUsername, string? color = null) + private Drawable generateUser(string username, int id, CountryCode countryCode, string cover, bool showPanel, string? color = null) { - return new ClickableAvatar(new APIUser + var user = new APIUser { Username = username, Id = id, @@ -115,7 +115,9 @@ namespace osu.Game.Tests.Visual.Online { Value = new UserStatusOnline() }, - }) + }; + + return new ClickableAvatar(user, showPanel) { Width = 50, Height = 50, @@ -125,7 +127,6 @@ namespace osu.Game.Tests.Visual.Online { Type = EdgeEffectType.Shadow, Radius = 1, Colour = Color4.Black.Opacity(0.2f), }, - ShowUsernameOnly = onlyUsername, }; } } diff --git a/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs b/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs index 8cde7859b2..c4aefe4f99 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs @@ -115,7 +115,7 @@ namespace osu.Game.Screens.OnlinePlay.Components RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex(@"27252d"), }, - avatar = new UpdateableAvatar(showUsernameOnly: true) { RelativeSizeAxes = Axes.Both }, + avatar = new UpdateableAvatar(showUserPanelOnHover: true) { RelativeSizeAxes = Axes.Both }, }; } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs index 65f0555612..60e05285d9 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs @@ -289,7 +289,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components set => avatar.User = value; } - private readonly UpdateableAvatar avatar = new UpdateableAvatar(showUsernameOnly: true) { RelativeSizeAxes = Axes.Both }; + private readonly UpdateableAvatar avatar = new UpdateableAvatar(showUserPanelOnHover: true) { RelativeSizeAxes = Axes.Both }; [BackgroundDependencyLoader] private void load(OverlayColourProvider colours) diff --git a/osu.Game/Users/Drawables/ClickableAvatar.cs b/osu.Game/Users/Drawables/ClickableAvatar.cs index 48a12acb5e..6390acc608 100644 --- a/osu.Game/Users/Drawables/ClickableAvatar.cs +++ b/osu.Game/Users/Drawables/ClickableAvatar.cs @@ -1,12 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Cursor; +using osu.Game.Localisation; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osuTK; @@ -15,15 +18,13 @@ namespace osu.Game.Users.Drawables { public partial class ClickableAvatar : OsuClickableContainer, IHasCustomTooltip { - // public ITooltip GetCustomTooltip() => new APIUserTooltip(user!) { ShowTooltip = TooltipEnabled }; - public ITooltip GetCustomTooltip() => new UserCardTooltip(); + public ITooltip GetCustomTooltip() => showCardOnHover ? new UserCardTooltip() : new NoCardTooltip(); public APIUser? TooltipContent { get; } private readonly APIUser? user; - // TODO: reimplement. - public bool ShowUsernameOnly { get; set; } + private readonly bool showCardOnHover; [Resolved] private OsuGame? game { get; set; } @@ -32,12 +33,15 @@ namespace osu.Game.Users.Drawables /// A clickable avatar for the specified user, with UI sounds included. /// /// The user. A null value will get a placeholder avatar. - public ClickableAvatar(APIUser? user = null) + /// + public ClickableAvatar(APIUser? user = null, bool showCardOnHover = false) { if (user?.Id != APIUser.SYSTEM_USER_ID) Action = openProfile; - TooltipContent = this.user = user; + this.showCardOnHover = showCardOnHover; + + TooltipContent = this.user = user ?? new GuestUser(); } [BackgroundDependencyLoader] @@ -65,23 +69,59 @@ namespace osu.Game.Users.Drawables public UserCardTooltip() { AutoSizeAxes = Axes.Both; - Masking = true; - CornerRadius = 5; } - protected override void PopIn() - { - this.FadeIn(20, Easing.OutQuint); - } - - protected override void PopOut() => this.FadeOut(80, Easing.OutQuint); + protected override void PopIn() => this.FadeIn(150, Easing.OutQuint); + protected override void PopOut() => this.Delay(150).FadeOut(500, Easing.OutQuint); public void Move(Vector2 pos) => Position = pos; - public void SetContent(APIUser? content) => LoadComponentAsync(new UserGridPanel(content ?? new GuestUser()) + private APIUser? user; + + public void SetContent(APIUser? content) { - Width = 300, - }, panel => Child = panel); + if (content == user && Children.Any()) + return; + + user = content; + + if (user != null) + { + LoadComponentAsync(new UserGridPanel(user) + { + Width = 300, + }, panel => Child = panel); + } + else + { + var tooltip = new OsuTooltipContainer.OsuTooltip(); + tooltip.SetContent(ContextMenuStrings.ViewProfile); + tooltip.Show(); + + Child = tooltip; + } + } + } + + public partial class NoCardTooltip : VisibilityContainer, ITooltip + { + public NoCardTooltip() + { + var tooltip = new OsuTooltipContainer.OsuTooltip(); + tooltip.SetContent(ContextMenuStrings.ViewProfile); + tooltip.Show(); + + Child = tooltip; + } + + protected override void PopIn() => this.FadeIn(150, Easing.OutQuint); + protected override void PopOut() => this.Delay(150).FadeOut(500, Easing.OutQuint); + + public void Move(Vector2 pos) => Position = pos; + + public void SetContent(APIUser? content) + { + } } } } diff --git a/osu.Game/Users/Drawables/UpdateableAvatar.cs b/osu.Game/Users/Drawables/UpdateableAvatar.cs index f220ee5a25..b020e7fa63 100644 --- a/osu.Game/Users/Drawables/UpdateableAvatar.cs +++ b/osu.Game/Users/Drawables/UpdateableAvatar.cs @@ -47,20 +47,20 @@ namespace osu.Game.Users.Drawables private readonly bool isInteractive; private readonly bool showGuestOnNull; - private readonly bool showUsernameOnly; + private readonly bool showUserPanelOnHover; /// /// Construct a new UpdateableAvatar. /// /// The initial user to display. /// If set to true, hover/click sounds will play and clicking the avatar will open the user's profile. - /// If set to true, the user status panel will be displayed in the tooltip. + /// If set to true, the user status panel will be displayed in the tooltip. /// Whether to show a default guest representation on null user (as opposed to nothing). - public UpdateableAvatar(APIUser? user = null, bool isInteractive = true, bool showUsernameOnly = false, bool showGuestOnNull = true) + public UpdateableAvatar(APIUser? user = null, bool isInteractive = true, bool showUserPanelOnHover = false, bool showGuestOnNull = true) { this.isInteractive = isInteractive; this.showGuestOnNull = showGuestOnNull; - this.showUsernameOnly = showUsernameOnly; + this.showUserPanelOnHover = showUserPanelOnHover; User = user; } @@ -72,10 +72,9 @@ namespace osu.Game.Users.Drawables if (isInteractive) { - return new ClickableAvatar(user) + return new ClickableAvatar(user, showUserPanelOnHover) { RelativeSizeAxes = Axes.Both, - ShowUsernameOnly = showUsernameOnly }; } From e0a5ec5352d0edf5d73513e203cdeda51624a302 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Nov 2023 12:09:16 +0900 Subject: [PATCH 1420/4092] Revert incorrect classic mod test assertions --- .../OsuDifficultyCalculatorTest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index 1d76c24620..fa7454b435 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs @@ -23,14 +23,14 @@ namespace osu.Game.Rulesets.Osu.Tests => base.Test(expectedStarRating, expectedMaxCombo, name); [TestCase(8.9742952703071666d, 239, "diffcalc-test")] - [TestCase(0.55071082800473514d, 4, "very-fast-slider")] [TestCase(1.743180218215227d, 54, "zero-length-sliders")] + [TestCase(0.55071082800473514d, 4, "very-fast-slider")] public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name) => Test(expectedStarRating, expectedMaxCombo, name, new OsuModDoubleTime()); - [TestCase(6.710442985146793d, 272, "diffcalc-test")] - [TestCase(0.42506480230838789d, 6, "very-fast-slider")] - [TestCase(1.4386882251130073d, 63, "zero-length-sliders")] + [TestCase(6.710442985146793d, 239, "diffcalc-test")] + [TestCase(1.4386882251130073d, 54, "zero-length-sliders")] + [TestCase(0.42506480230838789d, 4, "very-fast-slider")] public void TestClassicMod(double expectedStarRating, int expectedMaxCombo, string name) => Test(expectedStarRating, expectedMaxCombo, name, new OsuModClassic()); From fb02c317507b7c58770bdd52984eb6a0f91f9b53 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Nov 2023 12:09:48 +0900 Subject: [PATCH 1421/4092] Fix typo in assert step MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index 1e295aae70..c9d721d1c4 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -468,7 +468,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void assertHeadMissTailTracked() { AddAssert("Tracking retained", () => judgementResults[^2].Type, () => Is.EqualTo(HitResult.LargeTickHit)); - AddAssert("Slider head misseed", () => judgementResults.First().IsHit, () => Is.False); + AddAssert("Slider head missed", () => judgementResults.First().IsHit, () => Is.False); } private void assertMidSliderJudgements() From f13648418b82f7158a733588e8caf4f094efc1ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Nov 2023 12:10:52 +0900 Subject: [PATCH 1422/4092] Update `HitResultTest` to be more conformant to original expectations --- osu.Game.Tests/Rulesets/Scoring/HitResultTest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/HitResultTest.cs b/osu.Game.Tests/Rulesets/Scoring/HitResultTest.cs index 72acd18c5b..e003c9c534 100644 --- a/osu.Game.Tests/Rulesets/Scoring/HitResultTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/HitResultTest.cs @@ -11,14 +11,14 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestFixture] public class HitResultTest { - [TestCase(new[] { HitResult.Perfect, HitResult.Great, HitResult.Good, HitResult.Ok, HitResult.Meh }, new[] { HitResult.Miss })] - [TestCase(new[] { HitResult.LargeTickHit }, new[] { HitResult.LargeTickMiss })] - [TestCase(new[] { HitResult.SmallTickHit }, new[] { HitResult.SmallTickMiss })] + [TestCase(new[] { HitResult.Perfect, HitResult.Great, HitResult.Good, HitResult.Ok, HitResult.Meh }, new[] { HitResult.Miss, HitResult.IgnoreMiss })] + [TestCase(new[] { HitResult.LargeTickHit }, new[] { HitResult.LargeTickMiss, HitResult.IgnoreMiss })] + [TestCase(new[] { HitResult.SmallTickHit }, new[] { HitResult.SmallTickMiss, HitResult.IgnoreMiss })] [TestCase(new[] { HitResult.LargeBonus, HitResult.SmallBonus }, new[] { HitResult.IgnoreMiss })] [TestCase(new[] { HitResult.IgnoreHit }, new[] { HitResult.IgnoreMiss, HitResult.ComboBreak })] public void TestValidResultPairs(HitResult[] maxResults, HitResult[] minResults) { - HitResult[] unsupportedResults = HitResultExtensions.ALL_TYPES.Where(t => t != HitResult.IgnoreMiss && !minResults.Contains(t)).ToArray(); + HitResult[] unsupportedResults = HitResultExtensions.ALL_TYPES.Where(t => !minResults.Contains(t)).ToArray(); Assert.Multiple(() => { From 44c0442f4fc7f29f94ea1acad609ab8a676788d0 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 10 Nov 2023 14:00:34 +0900 Subject: [PATCH 1423/4092] Adjust comment on Slider's judgement --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index cb6827b428..506145568e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -276,9 +276,9 @@ namespace osu.Game.Rulesets.Osu.Objects } public override Judgement CreateJudgement() => ClassicSliderBehaviour - // See logic in `DrawableSlider.CheckForResult()` + // Final combo is provided by the slider itself - see logic in `DrawableSlider.CheckForResult()` ? new OsuJudgement() - // Of note, this creates a combo discrepancy for non-classic-mod sliders (there is no combo increase for tail or slider judgement). + // Final combo is provided by the tail circle - see `SliderTailCircle` : new OsuIgnoreJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; From b0c5b3cb1097861506b11862b996b91afe7a384a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Nov 2023 14:15:42 +0900 Subject: [PATCH 1424/4092] Add `Size` to serialised components of a `SerialisedDrawableInfo` --- osu.Game/Skinning/SerialisableDrawableExtensions.cs | 3 +++ osu.Game/Skinning/SerialisedDrawableInfo.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/osu.Game/Skinning/SerialisableDrawableExtensions.cs b/osu.Game/Skinning/SerialisableDrawableExtensions.cs index 51b57a000d..0b44db9bdc 100644 --- a/osu.Game/Skinning/SerialisableDrawableExtensions.cs +++ b/osu.Game/Skinning/SerialisableDrawableExtensions.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Configuration; using osu.Game.Extensions; +using osuTK; namespace osu.Game.Skinning { @@ -18,6 +19,8 @@ namespace osu.Game.Skinning // todo: can probably make this better via deserialisation directly using a common interface. component.Position = drawableInfo.Position; component.Rotation = drawableInfo.Rotation; + if (drawableInfo.Size != Vector2.Zero && (component as CompositeDrawable)?.AutoSizeAxes == Axes.None) + component.Size = drawableInfo.Size; component.Scale = drawableInfo.Scale; component.Anchor = drawableInfo.Anchor; component.Origin = drawableInfo.Origin; diff --git a/osu.Game/Skinning/SerialisedDrawableInfo.cs b/osu.Game/Skinning/SerialisedDrawableInfo.cs index c515f228f7..0705e91d6d 100644 --- a/osu.Game/Skinning/SerialisedDrawableInfo.cs +++ b/osu.Game/Skinning/SerialisedDrawableInfo.cs @@ -35,6 +35,8 @@ namespace osu.Game.Skinning public Vector2 Scale { get; set; } + public Vector2 Size { get; set; } + public Anchor Anchor { get; set; } public Anchor Origin { get; set; } @@ -62,6 +64,7 @@ namespace osu.Game.Skinning Position = component.Position; Rotation = component.Rotation; Scale = component.Scale; + Size = component.Size; Anchor = component.Anchor; Origin = component.Origin; From ec3b6e47fb7c6b302e633132362bfe54873a58a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Nov 2023 14:17:11 +0900 Subject: [PATCH 1425/4092] Change selection handling to adjust `Size` instead of `Scale` for edge nodes --- .../Edit/OsuSelectionHandler.cs | 1 + .../SkinEditor/SkinSelectionHandler.cs | 40 +++++++++++++++++-- .../Edit/Compose/Components/SelectionBox.cs | 19 ++++++++- 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index e81941d254..4da640a971 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -42,6 +42,7 @@ namespace osu.Game.Rulesets.Osu.Edit SelectionBox.CanFlipX = SelectionBox.CanScaleX = quad.Width > 0; SelectionBox.CanFlipY = SelectionBox.CanScaleY = quad.Height > 0; + SelectionBox.CanScaleProportionally = SelectionBox.CanScaleX && SelectionBox.CanScaleY; SelectionBox.CanReverse = EditorBeatmap.SelectedHitObjects.Count > 1 || EditorBeatmap.SelectedHitObjects.Any(s => s is Slider); } diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index df73b15101..bf03906e56 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; using osu.Framework.Utils; @@ -31,8 +32,38 @@ namespace osu.Game.Overlays.SkinEditor UpdatePosition = updateDrawablePosition }; + private bool allSelectedSupportManualSizing => SelectedItems.All(b => (b as CompositeDrawable)?.AutoSizeAxes == Axes.None); + public override bool HandleScale(Vector2 scale, Anchor anchor) { + bool adjustSize; + + switch (anchor) + { + // for corners, adjust scale. + case Anchor.TopLeft: + case Anchor.TopRight: + case Anchor.BottomLeft: + case Anchor.BottomRight: + adjustSize = false; + break; + + // for edges, adjust size. + case Anchor.TopCentre: + case Anchor.CentreLeft: + case Anchor.CentreRight: + case Anchor.BottomCentre: + // autosize elements can't be easily handled so just disable sizing for now. + if (!allSelectedSupportManualSizing) + return false; + + adjustSize = true; + break; + + default: + throw new ArgumentOutOfRangeException(nameof(anchor), anchor, null); + } + // convert scale to screen space scale = ToScreenSpace(scale) - ToScreenSpace(Vector2.Zero); @@ -120,7 +151,10 @@ namespace osu.Game.Overlays.SkinEditor if (Precision.AlmostEquals(MathF.Abs(drawableItem.Rotation) % 180, 90)) currentScaledDelta = new Vector2(scaledDelta.Y, scaledDelta.X); - drawableItem.Scale *= currentScaledDelta; + if (adjustSize) + drawableItem.Size *= currentScaledDelta; + else + drawableItem.Scale *= currentScaledDelta; } return true; @@ -169,8 +203,8 @@ namespace osu.Game.Overlays.SkinEditor { base.OnSelectionChanged(); - SelectionBox.CanScaleX = true; - SelectionBox.CanScaleY = true; + SelectionBox.CanScaleX = SelectionBox.CanScaleY = allSelectedSupportManualSizing; + SelectionBox.CanScaleProportionally = true; SelectionBox.CanFlipX = true; SelectionBox.CanFlipY = true; SelectionBox.CanReverse = false; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index 72d96213ee..0917867a61 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -91,6 +91,23 @@ namespace osu.Game.Screens.Edit.Compose.Components } } + private bool canScaleProportionally; + + /// + /// Whether vertical scaling support should be enabled. + /// + public bool CanScaleProportionally + { + get => canScaleProportionally; + set + { + if (canScaleProportionally == value) return; + + canScaleProportionally = value; + recreate(); + } + } + private bool canFlipX; /// @@ -245,7 +262,7 @@ namespace osu.Game.Screens.Edit.Compose.Components }; if (CanScaleX) addXScaleComponents(); - if (CanScaleX && CanScaleY) addFullScaleComponents(); + if (CanScaleProportionally) addFullScaleComponents(); if (CanScaleY) addYScaleComponents(); if (CanFlipX) addXFlipComponents(); if (CanFlipY) addYFlipComponents(); From fb361a4e0a8152bb35fe16cdc98bf06993f64a67 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Nov 2023 14:25:55 +0900 Subject: [PATCH 1426/4092] Reset size along with scale for relative items --- osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index bf03906e56..dab8c1c558 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -249,7 +249,12 @@ namespace osu.Game.Overlays.SkinEditor yield return new OsuMenuItem("Reset scale", MenuItemType.Standard, () => { foreach (var blueprint in SelectedBlueprints) - ((Drawable)blueprint.Item).Scale = Vector2.One; + { + var blueprintItem = ((Drawable)blueprint.Item); + blueprintItem.Scale = Vector2.One; + if (RelativeSizeAxes == Axes.Both) + blueprintItem.Size = Vector2.One; + } }); yield return new EditorMenuItemSpacer(); From 0add035c674d59bd36ef129d474a76550e3ced92 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Nov 2023 14:31:02 +0900 Subject: [PATCH 1427/4092] Disable resizing of `LegacySongProgress` Because it looks bad. --- osu.Game/Skinning/LegacySongProgress.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/LegacySongProgress.cs b/osu.Game/Skinning/LegacySongProgress.cs index 22aea99291..26004ff111 100644 --- a/osu.Game/Skinning/LegacySongProgress.cs +++ b/osu.Game/Skinning/LegacySongProgress.cs @@ -19,11 +19,14 @@ namespace osu.Game.Skinning public override bool HandleNonPositionalInput => false; public override bool HandlePositionalInput => false; + public LegacySongProgress() + { + AutoSizeAxes = Axes.Both; + } + [BackgroundDependencyLoader] private void load() { - Size = new Vector2(33); - InternalChildren = new Drawable[] { new Container @@ -39,7 +42,7 @@ namespace osu.Game.Skinning }, new CircularContainer { - RelativeSizeAxes = Axes.Both, + Size = new Vector2(33), Masking = true, BorderColour = Colour4.White, BorderThickness = 2, From 175dae49c623bfa8632608e204cac168d0b9723a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Nov 2023 14:43:51 +0900 Subject: [PATCH 1428/4092] Reset scale per axis --- osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index dab8c1c558..77e522a925 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -252,8 +252,11 @@ namespace osu.Game.Overlays.SkinEditor { var blueprintItem = ((Drawable)blueprint.Item); blueprintItem.Scale = Vector2.One; - if (RelativeSizeAxes == Axes.Both) - blueprintItem.Size = Vector2.One; + + if (blueprintItem.RelativeSizeAxes.HasFlagFast(Axes.X)) + blueprintItem.Width = 1; + if (blueprintItem.RelativeSizeAxes.HasFlagFast(Axes.Y)) + blueprintItem.Height = 1; } }); From 93ff82bc80c8bc06bc78149fd49584396ca982bd Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 10 Nov 2023 14:52:03 +0900 Subject: [PATCH 1429/4092] Attempt to support quotes in handling of GH comment body --- .github/workflows/diffcalc.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/diffcalc.yml b/.github/workflows/diffcalc.yml index e7c628e365..d4150208d3 100644 --- a/.github/workflows/diffcalc.yml +++ b/.github/workflows/diffcalc.yml @@ -185,9 +185,11 @@ jobs: - name: Add comment environment if: ${{ github.event_name == 'issue_comment' }} + env: + COMMENT_BODY: ${{ github.event.comment.body }} run: | # Add comment environment - echo '${{ github.event.comment.body }}' | sed -r 's/\r$//' | grep -E '^\w+=' | while read -r line; do + echo $COMMENT_BODY | sed -r 's/\r$//' | grep -E '^\w+=' | while read -r line; do opt=$(echo ${line} | cut -d '=' -f1) sed -i "s;^${opt}=.*$;${line};" "${{ needs.directory.outputs.GENERATOR_ENV }}" done From f31c1c9c7941db573a9516710e42e53946947e25 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Nov 2023 12:58:07 +0900 Subject: [PATCH 1430/4092] Rename and move skinnable line component to a more commomn place --- osu.Game/Skinning/ArgonSkin.cs | 5 +++-- .../Components/RoundedLine.cs} | 9 ++++----- 2 files changed, 7 insertions(+), 7 deletions(-) rename osu.Game/{Screens/Play/HUD/ArgonHealthRightLine.cs => Skinning/Components/RoundedLine.cs} (73%) diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index d598c04891..b3eff41495 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -16,6 +16,7 @@ using osu.Game.IO; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD.HitErrorMeters; +using osu.Game.Skinning.Components; using osuTK; using osuTK.Graphics; @@ -115,7 +116,7 @@ namespace osu.Game.Skinning var skinnableTargetWrapper = new DefaultSkinComponentsContainer(container => { var health = container.OfType().FirstOrDefault(); - var healthLine = container.OfType().FirstOrDefault(); + var healthLine = container.OfType().FirstOrDefault(); var scoreWedge = container.OfType().FirstOrDefault(); var score = container.OfType().FirstOrDefault(); var accuracy = container.OfType().FirstOrDefault(); @@ -207,7 +208,7 @@ namespace osu.Game.Skinning new ArgonScoreWedge(), new ArgonScoreCounter(), new ArgonHealthDisplay(), - new ArgonHealthRightLine(), + new RoundedLine(), new ArgonAccuracyCounter(), new ArgonComboCounter(), new BarHitErrorMeter(), diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthRightLine.cs b/osu.Game/Skinning/Components/RoundedLine.cs similarity index 73% rename from osu.Game/Screens/Play/HUD/ArgonHealthRightLine.cs rename to osu.Game/Skinning/Components/RoundedLine.cs index 25918f679c..d7b12c3f4c 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthRightLine.cs +++ b/osu.Game/Skinning/Components/RoundedLine.cs @@ -5,19 +5,18 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Skinning; -using osuTK; -namespace osu.Game.Screens.Play.HUD +namespace osu.Game.Skinning.Components { - public partial class ArgonHealthRightLine : CompositeDrawable, ISerialisableDrawable + public partial class RoundedLine : CompositeDrawable, ISerialisableDrawable { public bool UsesFixedAnchor { get; set; } [BackgroundDependencyLoader] private void load() { - Size = new Vector2(50f, ArgonHealthDisplay.MAIN_PATH_RADIUS * 2); + AutoSizeAxes = Axes.Both; + InternalChild = new Circle { Anchor = Anchor.CentreLeft, From 99d9db5b76ba5292398201039e75fcb0a9bcc1d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Nov 2023 14:03:24 +0900 Subject: [PATCH 1431/4092] Use a better default size for line --- osu.Game/Skinning/ArgonSkin.cs | 7 ++++++- osu.Game/Skinning/Components/RoundedLine.cs | 19 ++++--------------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index b3eff41495..03b089582d 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -137,7 +137,12 @@ namespace osu.Game.Skinning health.Position = new Vector2(components_x_offset, 20f); if (healthLine != null) - healthLine.Y = health.Y; + { + healthLine.Anchor = Anchor.TopLeft; + healthLine.Origin = Anchor.CentreLeft; + healthLine.Y = health.Y + ArgonHealthDisplay.MAIN_PATH_RADIUS; + healthLine.Size = new Vector2(45, 3); + } if (scoreWedge != null) { diff --git a/osu.Game/Skinning/Components/RoundedLine.cs b/osu.Game/Skinning/Components/RoundedLine.cs index d7b12c3f4c..491f87d31e 100644 --- a/osu.Game/Skinning/Components/RoundedLine.cs +++ b/osu.Game/Skinning/Components/RoundedLine.cs @@ -1,29 +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.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osuTK; namespace osu.Game.Skinning.Components { - public partial class RoundedLine : CompositeDrawable, ISerialisableDrawable + public partial class RoundedLine : Circle, ISerialisableDrawable { public bool UsesFixedAnchor { get; set; } - [BackgroundDependencyLoader] - private void load() + public RoundedLine() { - AutoSizeAxes = Axes.Both; - - InternalChild = new Circle - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Width = 45f, - Height = 3f, - }; + Size = new Vector2(200, 8); } } } From 6c1d48dfaf0e4f06489b22c32b437a2d18d5250a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Nov 2023 14:51:14 +0900 Subject: [PATCH 1432/4092] Remove unused function --- osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs index 759a5dbfea..dbeafe5b59 100644 --- a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs +++ b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs @@ -108,8 +108,6 @@ namespace osu.Game.Screens.Play.HUD labelText.Colour = colours.Blue0; } - protected virtual LocalisableString FormatWireframes(LocalisableString text) => text; - protected override void LoadComplete() { base.LoadComplete(); From 7c3a626f4b2a1706cfc46295770b8673b374aee4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Nov 2023 15:06:07 +0900 Subject: [PATCH 1433/4092] Add basic animation for combo counter --- osu.Game/Screens/Play/HUD/ArgonComboCounter.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs index 6a7d5ff665..36194d787b 100644 --- a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs @@ -8,11 +8,15 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Rulesets.Scoring; +using osuTK; namespace osu.Game.Screens.Play.HUD { public partial class ArgonComboCounter : ComboCounter { + protected override double RollingDuration => 500; + protected override Easing RollingEasing => Easing.OutQuint; + [SettingSource("Wireframe opacity", "Controls the opacity of the wire frames behind the digits.")] public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.4f) { @@ -25,6 +29,12 @@ namespace osu.Game.Screens.Play.HUD private void load(ScoreProcessor scoreProcessor) { Current.BindTo(scoreProcessor.Combo); + Current.BindValueChanged(combo => + { + bool wasIncrease = combo.NewValue > combo.OldValue; + DrawableCount.ScaleTo(new Vector2(1, wasIncrease ? 1.2f : 0.8f)) + .ScaleTo(Vector2.One, 600, Easing.OutQuint); + }); } protected override LocalisableString FormatCount(int count) => $@"{count}x"; From e861681cd4a081b3cc557df725cb0d2737096ebc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Nov 2023 15:06:19 +0900 Subject: [PATCH 1434/4092] Adjust argon rolling easings --- osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs | 3 +++ osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs index 160841b40f..ee5e16180e 100644 --- a/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs @@ -14,6 +14,9 @@ namespace osu.Game.Screens.Play.HUD { public partial class ArgonAccuracyCounter : GameplayAccuracyCounter, ISerialisableDrawable { + protected override double RollingDuration => 500; + protected override Easing RollingEasing => Easing.OutQuint; + [SettingSource("Wireframe opacity", "Controls the opacity of the wire frames behind the digits.")] public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.4f) { diff --git a/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs b/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs index b15c21801f..5ec359f5bb 100644 --- a/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs @@ -13,6 +13,9 @@ namespace osu.Game.Screens.Play.HUD { public partial class ArgonScoreCounter : GameplayScoreCounter, ISerialisableDrawable { + protected override double RollingDuration => 500; + protected override Easing RollingEasing => Easing.OutQuint; + [SettingSource("Wireframe opacity", "Controls the opacity of the wire frames behind the digits.")] public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.4f) { From 7e0b41219cd916931bd18d32f87705624b4e723b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Nov 2023 15:26:37 +0900 Subject: [PATCH 1435/4092] Change animation to only affect number portion, add miss animation --- .../Screens/Play/HUD/ArgonComboCounter.cs | 20 ++++++++++++++++--- .../Play/HUD/ArgonCounterTextComponent.cs | 4 +++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs index 36194d787b..b75d4268fc 100644 --- a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonComboCounter.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 System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -9,11 +10,14 @@ using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Rulesets.Scoring; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Play.HUD { public partial class ArgonComboCounter : ComboCounter { + private ArgonCounterTextComponent text = null!; + protected override double RollingDuration => 500; protected override Easing RollingEasing => Easing.OutQuint; @@ -32,14 +36,24 @@ namespace osu.Game.Screens.Play.HUD Current.BindValueChanged(combo => { bool wasIncrease = combo.NewValue > combo.OldValue; - DrawableCount.ScaleTo(new Vector2(1, wasIncrease ? 1.2f : 0.8f)) - .ScaleTo(Vector2.One, 600, Easing.OutQuint); + bool wasMiss = combo.OldValue > 1 && combo.NewValue == 0; + + float newScale = Math.Clamp(text.NumberContainer.Scale.X * (wasIncrease ? 1.1f : 0.8f), 0.6f, 1.4f); + + float duration = wasMiss ? 2000 : 300; + + text.NumberContainer + .ScaleTo(new Vector2(newScale)) + .ScaleTo(Vector2.One, duration, Easing.OutQuint); + + if (wasMiss) + text.FlashColour(Color4.Red, duration, Easing.OutQuint); }); } protected override LocalisableString FormatCount(int count) => $@"{count}x"; - protected override IHasText CreateText() => new ArgonCounterTextComponent(Anchor.TopLeft, "COMBO") + protected override IHasText CreateText() => text = new ArgonCounterTextComponent(Anchor.TopLeft, "COMBO") { WireframeOpacity = { BindTarget = WireframeOpacity }, }; diff --git a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs index dbeafe5b59..56f60deae1 100644 --- a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs +++ b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs @@ -27,6 +27,8 @@ namespace osu.Game.Screens.Play.HUD public IBindable WireframeOpacity { get; } = new BindableFloat(); public Bindable RequiredDisplayDigits { get; } = new BindableInt(); + public Container NumberContainer { get; private set; } + public LocalisableString Text { get => textPart.Text; @@ -59,7 +61,7 @@ namespace osu.Game.Screens.Play.HUD Font = OsuFont.Torus.With(size: 12, weight: FontWeight.Bold), Margin = new MarginPadding { Left = 2.5f }, }, - new Container + NumberContainer = new Container { AutoSizeAxes = Axes.Both, Children = new[] From 4f90ac15fad7df54047abd01b6f88b6e28310277 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Nov 2023 15:39:17 +0900 Subject: [PATCH 1436/4092] Reduce the default wireframe opacity a bit --- osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs | 2 +- osu.Game/Screens/Play/HUD/ArgonComboCounter.cs | 2 +- osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs index ee5e16180e..5f9441a5c4 100644 --- a/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Play.HUD protected override Easing RollingEasing => Easing.OutQuint; [SettingSource("Wireframe opacity", "Controls the opacity of the wire frames behind the digits.")] - public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.4f) + public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.25f) { Precision = 0.01f, MinValue = 0, diff --git a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs index b75d4268fc..63a17529f5 100644 --- a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs @@ -22,7 +22,7 @@ namespace osu.Game.Screens.Play.HUD protected override Easing RollingEasing => Easing.OutQuint; [SettingSource("Wireframe opacity", "Controls the opacity of the wire frames behind the digits.")] - public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.4f) + public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.25f) { Precision = 0.01f, MinValue = 0, diff --git a/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs b/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs index 5ec359f5bb..0192fa3c02 100644 --- a/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.Play.HUD protected override Easing RollingEasing => Easing.OutQuint; [SettingSource("Wireframe opacity", "Controls the opacity of the wire frames behind the digits.")] - public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.4f) + public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.25f) { Precision = 0.01f, MinValue = 0, From 1818a84034c57f76335e06371b6a68296b0126f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 10 Nov 2023 15:52:02 +0900 Subject: [PATCH 1437/4092] Rewrite tests to not rely on realtime clock Definitely faster, hopefully more reliable too. --- .../Mods/TestSceneOsuModTouchDevice.cs | 150 +++++++++++------- 1 file changed, 94 insertions(+), 56 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs index abeb56209e..f528795125 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs @@ -5,6 +5,8 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Input; +using osu.Framework.Screens; +using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Timing; using osu.Game.Configuration; @@ -14,39 +16,24 @@ using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; +using osu.Game.Storyboards; using osu.Game.Tests.Visual; using osuTK.Input; namespace osu.Game.Rulesets.Osu.Tests.Mods { - public partial class TestSceneOsuModTouchDevice : PlayerTestScene + public partial class TestSceneOsuModTouchDevice : RateAdjustedBeatmapTestScene { [Resolved] private SessionStatics statics { get; set; } = null!; - protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); + private ScoreAccessibleSoloPlayer currentPlayer = null!; + private readonly ManualClock manualClock = new ManualClock { Rate = 0 }; - protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => - new OsuBeatmap - { - HitObjects = - { - new HitCircle - { - Position = OsuPlayfield.BASE_SIZE / 2, - StartTime = 0, - }, - new HitCircle - { - Position = OsuPlayfield.BASE_SIZE / 2, - StartTime = 5000, - }, - }, - Breaks = - { - new BreakPeriod(2000, 3000) - } - }; + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null) + => new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(manualClock), Audio); [BackgroundDependencyLoader] private void load() @@ -63,103 +50,154 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods [Test] public void TestUserAlreadyHasTouchDeviceActive() { + loadPlayer(); // it is presumed that a previous screen (i.e. song select) will set this up AddStep("set up touchscreen user", () => { - Player.Score.ScoreInfo.Mods = Player.Score.ScoreInfo.Mods.Append(new OsuModTouchDevice()).ToArray(); + currentPlayer.Score.ScoreInfo.Mods = currentPlayer.Score.ScoreInfo.Mods.Append(new OsuModTouchDevice()).ToArray(); statics.SetValue(Static.TouchInputActive, true); }); - AddUntilStep("wait until 0 near", () => Player.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(0).Within(500)); - AddStep("slow down", () => Player.GameplayClockContainer.AdjustmentsFromMods.Frequency.Value = 0.2); - AddUntilStep("wait until 0", () => Player.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(0)); + + AddStep("seek to 0", () => currentPlayer.GameplayClockContainer.Seek(0)); + AddUntilStep("wait until 0", () => currentPlayer.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(0)); AddStep("touch circle", () => { - var touch = new Touch(TouchSource.Touch1, Player.DrawableRuleset.Playfield.ScreenSpaceDrawQuad.Centre); + var touch = new Touch(TouchSource.Touch1, currentPlayer.DrawableRuleset.Playfield.ScreenSpaceDrawQuad.Centre); InputManager.BeginTouch(touch); InputManager.EndTouch(touch); }); - AddAssert("touch device mod activated", () => Player.Score.ScoreInfo.Mods, () => Has.One.InstanceOf()); + AddAssert("touch device mod activated", () => currentPlayer.Score.ScoreInfo.Mods, () => Has.One.InstanceOf()); } [Test] public void TestTouchDuringBreak() { - AddUntilStep("wait until 2000 near", () => Player.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(2000).Within(500)); - AddStep("slow down", () => Player.GameplayClockContainer.AdjustmentsFromMods.Frequency.Value = 0.2); - AddUntilStep("wait until 2000", () => Player.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(2000)); + loadPlayer(); + AddStep("seek to 2000", () => currentPlayer.GameplayClockContainer.Seek(2000)); + AddUntilStep("wait until 2000", () => currentPlayer.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(2000)); AddStep("touch playfield", () => { - var touch = new Touch(TouchSource.Touch1, Player.DrawableRuleset.Playfield.ScreenSpaceDrawQuad.Centre); + var touch = new Touch(TouchSource.Touch1, currentPlayer.DrawableRuleset.Playfield.ScreenSpaceDrawQuad.Centre); InputManager.BeginTouch(touch); InputManager.EndTouch(touch); }); - AddAssert("touch device mod not activated", () => Player.Score.ScoreInfo.Mods, () => Has.None.InstanceOf()); + AddAssert("touch device mod not activated", () => currentPlayer.Score.ScoreInfo.Mods, () => Has.None.InstanceOf()); } [Test] public void TestTouchMiss() { + loadPlayer(); // ensure mouse is active (and that it's not suppressed due to touches in previous tests) AddStep("click mouse", () => InputManager.Click(MouseButton.Left)); - AddUntilStep("wait until 200 near", () => Player.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(200).Within(500)); - AddStep("slow down", () => Player.GameplayClockContainer.AdjustmentsFromMods.Frequency.Value = 0.2); - AddUntilStep("wait until 200", () => Player.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(200)); + AddStep("seek to 200", () => currentPlayer.GameplayClockContainer.Seek(200)); + AddUntilStep("wait until 200", () => currentPlayer.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(200)); AddStep("touch playfield", () => { - var touch = new Touch(TouchSource.Touch1, Player.DrawableRuleset.Playfield.ScreenSpaceDrawQuad.Centre); + var touch = new Touch(TouchSource.Touch1, currentPlayer.DrawableRuleset.Playfield.ScreenSpaceDrawQuad.Centre); InputManager.BeginTouch(touch); InputManager.EndTouch(touch); }); - AddAssert("touch device mod activated", () => Player.Score.ScoreInfo.Mods, () => Has.One.InstanceOf()); + AddAssert("touch device mod activated", () => currentPlayer.Score.ScoreInfo.Mods, () => Has.One.InstanceOf()); } [Test] public void TestIncompatibleModActive() { + loadPlayer(); // this is only a veneer of enabling autopilot as having it actually active from the start is annoying to make happen // given the tests' structure. - AddStep("enable autopilot", () => Player.Score.ScoreInfo.Mods = new Mod[] { new OsuModAutopilot() }); + AddStep("enable autopilot", () => currentPlayer.Score.ScoreInfo.Mods = new Mod[] { new OsuModAutopilot() }); - AddUntilStep("wait until 0 near", () => Player.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(0).Within(500)); - AddStep("slow down", () => Player.GameplayClockContainer.AdjustmentsFromMods.Frequency.Value = 0.2); - AddUntilStep("wait until 0", () => Player.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(0)); + AddStep("seek to 0", () => currentPlayer.GameplayClockContainer.Seek(0)); + AddUntilStep("wait until 0", () => currentPlayer.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(0)); AddStep("touch playfield", () => { - var touch = new Touch(TouchSource.Touch1, Player.DrawableRuleset.Playfield.ScreenSpaceDrawQuad.Centre); + var touch = new Touch(TouchSource.Touch1, currentPlayer.DrawableRuleset.Playfield.ScreenSpaceDrawQuad.Centre); InputManager.BeginTouch(touch); InputManager.EndTouch(touch); }); - AddAssert("touch device mod not activated", () => Player.Score.ScoreInfo.Mods, () => Has.None.InstanceOf()); + AddAssert("touch device mod not activated", () => currentPlayer.Score.ScoreInfo.Mods, () => Has.None.InstanceOf()); } [Test] public void TestSecondObjectTouched() { + loadPlayer(); // ensure mouse is active (and that it's not suppressed due to touches in previous tests) AddStep("click mouse", () => InputManager.Click(MouseButton.Left)); - AddUntilStep("wait until 0 near", () => Player.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(0).Within(500)); - AddStep("slow down", () => Player.GameplayClockContainer.AdjustmentsFromMods.Frequency.Value = 0.2); - AddUntilStep("wait until 0", () => Player.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(0)); + AddStep("seek to 0", () => currentPlayer.GameplayClockContainer.Seek(0)); + AddUntilStep("wait until 0", () => currentPlayer.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(0)); AddStep("click circle", () => { - InputManager.MoveMouseTo(Player.DrawableRuleset.Playfield.ScreenSpaceDrawQuad.Centre); + InputManager.MoveMouseTo(currentPlayer.DrawableRuleset.Playfield.ScreenSpaceDrawQuad.Centre); InputManager.Click(MouseButton.Left); }); - AddAssert("touch device mod not activated", () => Player.Score.ScoreInfo.Mods, () => Has.None.InstanceOf()); + AddAssert("touch device mod not activated", () => currentPlayer.Score.ScoreInfo.Mods, () => Has.None.InstanceOf()); - AddStep("speed back up", () => Player.GameplayClockContainer.AdjustmentsFromMods.Frequency.Value = 1); - AddUntilStep("wait until 5000 near", () => Player.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(5000).Within(500)); - AddStep("slow down", () => Player.GameplayClockContainer.AdjustmentsFromMods.Frequency.Value = 0.2); - AddUntilStep("wait until 5000", () => Player.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(5000)); + AddStep("seek to 5000", () => currentPlayer.GameplayClockContainer.Seek(5000)); + AddUntilStep("wait until 5000", () => currentPlayer.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(5000)); AddStep("touch playfield", () => { - var touch = new Touch(TouchSource.Touch1, Player.DrawableRuleset.Playfield.ScreenSpaceDrawQuad.Centre); + var touch = new Touch(TouchSource.Touch1, currentPlayer.DrawableRuleset.Playfield.ScreenSpaceDrawQuad.Centre); InputManager.BeginTouch(touch); InputManager.EndTouch(touch); }); - AddAssert("touch device mod activated", () => Player.Score.ScoreInfo.Mods, () => Has.One.InstanceOf()); + AddAssert("touch device mod activated", () => currentPlayer.Score.ScoreInfo.Mods, () => Has.One.InstanceOf()); + } + + private void loadPlayer() + { + AddStep("load player", () => + { + Beatmap.Value = CreateWorkingBeatmap(new OsuBeatmap + { + HitObjects = + { + new HitCircle + { + Position = OsuPlayfield.BASE_SIZE / 2, + StartTime = 0, + }, + new HitCircle + { + Position = OsuPlayfield.BASE_SIZE / 2, + StartTime = 5000, + }, + }, + Breaks = + { + new BreakPeriod(2000, 3000) + } + }); + + var p = new ScoreAccessibleSoloPlayer(); + + LoadScreen(currentPlayer = p); + }); + + AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0); + AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); + } + + private partial class ScoreAccessibleSoloPlayer : SoloPlayer + { + public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; + + public new DrawableRuleset DrawableRuleset => base.DrawableRuleset; + + protected override bool PauseOnFocusLost => false; + + public ScoreAccessibleSoloPlayer() + : base(new PlayerConfiguration + { + AllowPause = false, + ShowResults = false, + }) + { + } } } } From 8690b0876928570e5df1d25a1568db6ec703ee05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 10 Nov 2023 16:08:45 +0900 Subject: [PATCH 1438/4092] Fix compose select box test failures --- osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs index 80c69aacf6..9e8d75efea 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs @@ -47,6 +47,7 @@ namespace osu.Game.Tests.Visual.Editing CanScaleX = true, CanScaleY = true, + CanScaleProportionally = true, CanFlipX = true, CanFlipY = true, From 60df2722ab7d72e040b176d0d756493c6c2daf4e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Nov 2023 16:04:28 +0900 Subject: [PATCH 1439/4092] Rename `RoundedLine` to `BoxElement` and make more generically useful --- osu.Game/Skinning/ArgonSkin.cs | 4 +- osu.Game/Skinning/Components/BoxElement.cs | 50 +++++++++++++++++++++ osu.Game/Skinning/Components/RoundedLine.cs | 18 -------- 3 files changed, 52 insertions(+), 20 deletions(-) create mode 100644 osu.Game/Skinning/Components/BoxElement.cs delete mode 100644 osu.Game/Skinning/Components/RoundedLine.cs diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 03b089582d..b78abdffd7 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -116,7 +116,7 @@ namespace osu.Game.Skinning var skinnableTargetWrapper = new DefaultSkinComponentsContainer(container => { var health = container.OfType().FirstOrDefault(); - var healthLine = container.OfType().FirstOrDefault(); + var healthLine = container.OfType().FirstOrDefault(); var scoreWedge = container.OfType().FirstOrDefault(); var score = container.OfType().FirstOrDefault(); var accuracy = container.OfType().FirstOrDefault(); @@ -213,7 +213,7 @@ namespace osu.Game.Skinning new ArgonScoreWedge(), new ArgonScoreCounter(), new ArgonHealthDisplay(), - new RoundedLine(), + new BoxElement(), new ArgonAccuracyCounter(), new ArgonComboCounter(), new BarHitErrorMeter(), diff --git a/osu.Game/Skinning/Components/BoxElement.cs b/osu.Game/Skinning/Components/BoxElement.cs new file mode 100644 index 0000000000..235f97ceef --- /dev/null +++ b/osu.Game/Skinning/Components/BoxElement.cs @@ -0,0 +1,50 @@ +// 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.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Configuration; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Skinning.Components +{ + public partial class BoxElement : CompositeDrawable, ISerialisableDrawable + { + public bool UsesFixedAnchor { get; set; } + + [SettingSource("Corner rounding", "How round the corners of the box should be.")] + public BindableFloat CornerRounding { get; } = new BindableFloat(1) + { + Precision = 0.01f, + MinValue = 0, + MaxValue = 1, + }; + + public BoxElement() + { + Size = new Vector2(400, 80); + + InternalChildren = new Drawable[] + { + new Box + { + Colour = Color4.White, + RelativeSizeAxes = Axes.Both, + }, + }; + + Masking = true; + } + + protected override void Update() + { + base.Update(); + + CornerRadius = CornerRounding.Value * Math.Min(DrawWidth, DrawHeight) * 0.5f; + } + } +} diff --git a/osu.Game/Skinning/Components/RoundedLine.cs b/osu.Game/Skinning/Components/RoundedLine.cs deleted file mode 100644 index 491f87d31e..0000000000 --- a/osu.Game/Skinning/Components/RoundedLine.cs +++ /dev/null @@ -1,18 +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.Framework.Graphics.Shapes; -using osuTK; - -namespace osu.Game.Skinning.Components -{ - public partial class RoundedLine : Circle, ISerialisableDrawable - { - public bool UsesFixedAnchor { get; set; } - - public RoundedLine() - { - Size = new Vector2(200, 8); - } - } -} From 7db14baed74277383e4ac567577bb08b3b2aeea5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Nov 2023 16:13:45 +0900 Subject: [PATCH 1440/4092] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 73cd239854..b47e2f1ca8 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + From b7938e78a0dc05f6847323058ef58f235e67324f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 10 Nov 2023 16:30:21 +0900 Subject: [PATCH 1441/4092] Make sure test is in a break ...because apparently it may take a while to update the flag. --- osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs index f528795125..cd51ccd751 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs @@ -75,6 +75,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods loadPlayer(); AddStep("seek to 2000", () => currentPlayer.GameplayClockContainer.Seek(2000)); AddUntilStep("wait until 2000", () => currentPlayer.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(2000)); + AddUntilStep("wait until break entered", () => currentPlayer.IsBreakTime.Value); AddStep("touch playfield", () => { var touch = new Touch(TouchSource.Touch1, currentPlayer.DrawableRuleset.Playfield.ScreenSpaceDrawQuad.Centre); From fa5921862f99e6ed4a9757938ad77a8bd9d066ed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Nov 2023 16:19:42 +0900 Subject: [PATCH 1442/4092] Update deserialising test --- .../Archives/modified-argon-20231108.osk | Bin 1494 -> 1473 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/osu.Game.Tests/Resources/Archives/modified-argon-20231108.osk b/osu.Game.Tests/Resources/Archives/modified-argon-20231108.osk index d07c5171007777784216e3ee8d4bc631a602bfb9..d56c4d4dcd8cfbfbdb536cd889dc72bdb5ff76f2 100644 GIT binary patch delta 1141 zcmcb{eUQ68z?+#xgn@y9gJEV{R`|Z@{*%Ge4FSv07GIlh~F@+%Q~oV{YfQ?8rI2O75pb4LXAg8B&@X=l+b z=Qalgdu>*@_;|N3u^G~KWK-~H()ze-kAzcx#5T%JAg zhc(lNfXSwe0YV`EU%Y-baWl~GSz-(fVhodWSS9M;h6m=~G7zYHzrLfQjl;?D^%A?1 z3u`Z%@UDIPEg*DSa^I1|H(tECuq^cdZ@tE#1(D0c6ib31GJd-Iv*zRXyEpgU{k$hi z{--FvXw5a{;Oh5xh1@ch_?!^6jem0f9@F{U^p_$hf{e{R+JEHyv7Cu<%UAZU<^N7r z-nTz=;>CP}o`iY@M~Az+%hy)hGpsfFwc+LS`@61tPw^JuGgaZ+xvIKZB_yb(@ynSW zNBLJSE2I7?!3FUkv?boS~)gGN(#>Dk(5 zhQ_n{3wMcs;OM%;a<=P$n#}Pndn2Yl%f1%%InajXYeKc~!^PX{_i;$;->&<1KO~}; z>$!OJ;>C=!I5I`cR_%6MZS&OhZ}W3*eT^K~dPmDcr(J~<=O5$F&6#+6PWYqL!bvNC zx!taK{YfAe&`{_{A=`0vMd@7;YLC9zNLVhQ9!&ES(?vS`SI zguwX#nAht;`5uVjpz-x&16CQZ8q|CNSHZy0*f=?xRTZp8W@=KE8c<3Ti20C|Ff?XN NUeBt?wu=QM2ml}T`)L3G delta 1146 zcmX@eeT}<5z?+#xgn@y9gP~P0BfMgN@{#>OUOFR4gn^+rJ2Ou&GcWUKzhT}X2N9Rw zwnvYMdTiwNW0|-+q$EVq*X3j4{&n05!$_KRIWI3VioSNzawdi2AtyJm(a(3BKBk7haZ<3wWnjo?buyeER+OkztP|UQS_? zvFpmdS&@8Kdges87`7?1zn}hdH|j+3-AXIA#hD&cZ^*ykc(Yl6v1JqcoMqom9^F{m zGEpRbp-cS_zQY`|t~F;~{>8RphDdwg-2bz}>>pY=T8K=@Sh6RRcT&UV55h~He_}hb z(8D6%M1C>C!ABHw-e^!(;id$#d> z;S5;!ICj->u=ZyIdqNv9I{;Dyu*TfgLlO zI0cTrd!SfQ`^VyJ(El?o^|h>j>=jNJFDesh(9P#P=~pnv3q~dpqO#f%wDP@6VQ(Z9i^v{GILGinpgeUBAcov1pQu$MKI%(&syJ z`&J)Jy?FjgZBg#uW$Ry-l-+q=ruF;IxnmOX_K#1US+COI#Np7qBCn&1c4?^_^XfdXNA8lsf-^ z{j)GheYq){_ulkJ$q186SpxY`v;E{NEE@74A*4LQzzfVPaM1X4vNWrV0!n5PV_*Ox k1Zb>goIICNVsbt!4>M5V Date: Fri, 10 Nov 2023 16:37:07 +0900 Subject: [PATCH 1443/4092] Remove width/height bindables from `ArgonWedgePiece` --- osu.Game/Screens/Play/HUD/ArgonWedgePiece.cs | 24 +++----------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonWedgePiece.cs b/osu.Game/Screens/Play/HUD/ArgonWedgePiece.cs index 085e4c738f..3c2e3e05ea 100644 --- a/osu.Game/Screens/Play/HUD/ArgonWedgePiece.cs +++ b/osu.Game/Screens/Play/HUD/ArgonWedgePiece.cs @@ -18,30 +18,14 @@ namespace osu.Game.Screens.Play.HUD { public bool UsesFixedAnchor { get; set; } - public float EndOpacity { get; init; } = 0.25f; - - [SettingSource("Wedge width")] - public BindableFloat WedgeWidth { get; } = new BindableFloat(400f) - { - MinValue = 0f, - MaxValue = 500f, - Precision = 1f, - }; - - [SettingSource("Wedge height")] - public BindableFloat WedgeHeight { get; } = new BindableFloat(100f) - { - MinValue = 0f, - MaxValue = 500f, - Precision = 1f, - }; - [SettingSource("Inverted shear")] public BindableBool InvertShear { get; } = new BindableBool(); public ArgonWedgePiece() { CornerRadius = 10f; + + Size = new Vector2(400, 100); } [BackgroundDependencyLoader] @@ -53,7 +37,7 @@ namespace osu.Game.Screens.Play.HUD InternalChild = new Box { RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex("#66CCFF").Opacity(0.0f), Color4Extensions.FromHex("#66CCFF").Opacity(EndOpacity)), + Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex("#66CCFF").Opacity(0.0f), Color4Extensions.FromHex("#66CCFF").Opacity(0.25f)), }; } @@ -61,8 +45,6 @@ namespace osu.Game.Screens.Play.HUD { base.LoadComplete(); - WedgeWidth.BindValueChanged(v => Width = v.NewValue, true); - WedgeHeight.BindValueChanged(v => Height = v.NewValue, true); InvertShear.BindValueChanged(v => Shear = new Vector2(0.8f, 0f) * (v.NewValue ? -1 : 1), true); } } From 67312a2db922287fef12c53f00be99b2aa79df8d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Nov 2023 16:43:47 +0900 Subject: [PATCH 1444/4092] Remove `ArgonScoreWedge` and use `ArgonWedgePiece` directly --- osu.Game/Screens/Play/HUD/ArgonScoreWedge.cs | 37 -------------------- osu.Game/Skinning/ArgonSkin.cs | 26 ++++++++------ 2 files changed, 16 insertions(+), 47 deletions(-) delete mode 100644 osu.Game/Screens/Play/HUD/ArgonScoreWedge.cs diff --git a/osu.Game/Screens/Play/HUD/ArgonScoreWedge.cs b/osu.Game/Screens/Play/HUD/ArgonScoreWedge.cs deleted file mode 100644 index e8ade9e5c6..0000000000 --- a/osu.Game/Screens/Play/HUD/ArgonScoreWedge.cs +++ /dev/null @@ -1,37 +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.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Skinning; -using osuTK; - -namespace osu.Game.Screens.Play.HUD -{ - public partial class ArgonScoreWedge : CompositeDrawable, ISerialisableDrawable - { - public bool UsesFixedAnchor { get; set; } - - [BackgroundDependencyLoader] - private void load() - { - AutoSizeAxes = Axes.Both; - - InternalChildren = new Drawable[] - { - new ArgonWedgePiece - { - WedgeWidth = { Value = 380 }, - WedgeHeight = { Value = 72 }, - }, - new ArgonWedgePiece - { - WedgeWidth = { Value = 380 }, - WedgeHeight = { Value = 72 }, - Position = new Vector2(4, 5) - }, - }; - } - } -} diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index b78abdffd7..ddb375778c 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -117,7 +117,7 @@ namespace osu.Game.Skinning { var health = container.OfType().FirstOrDefault(); var healthLine = container.OfType().FirstOrDefault(); - var scoreWedge = container.OfType().FirstOrDefault(); + var wedgePieces = container.OfType().ToArray(); var score = container.OfType().FirstOrDefault(); var accuracy = container.OfType().FirstOrDefault(); var combo = container.OfType().FirstOrDefault(); @@ -144,15 +144,13 @@ namespace osu.Game.Skinning healthLine.Size = new Vector2(45, 3); } - if (scoreWedge != null) - { - scoreWedge.Position = new Vector2(-50, 15); + foreach (var wedgePiece in wedgePieces) + wedgePiece.Position += new Vector2(-50, 15); - if (score != null) - { - score.Origin = Anchor.TopRight; - score.Position = new Vector2(components_x_offset + 200, scoreWedge.Y + 30); - } + if (score != null) + { + score.Origin = Anchor.TopRight; + score.Position = new Vector2(components_x_offset + 200, wedgePieces.Last().Y + 30); } if (accuracy != null) @@ -210,7 +208,15 @@ namespace osu.Game.Skinning { Children = new Drawable[] { - new ArgonScoreWedge(), + new ArgonWedgePiece + { + Size = new Vector2(380, 72), + }, + new ArgonWedgePiece + { + Size = new Vector2(380, 72), + Position = new Vector2(4, 5) + }, new ArgonScoreCounter(), new ArgonHealthDisplay(), new BoxElement(), From a02aeed50af48f580a1c5d7fbb8a792f2a652f1a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Nov 2023 16:50:41 +0900 Subject: [PATCH 1445/4092] Adjust combo animation slightly --- osu.Game/Screens/Play/HUD/ArgonComboCounter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs index 63a17529f5..ac710294ef 100644 --- a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs @@ -40,7 +40,7 @@ namespace osu.Game.Screens.Play.HUD float newScale = Math.Clamp(text.NumberContainer.Scale.X * (wasIncrease ? 1.1f : 0.8f), 0.6f, 1.4f); - float duration = wasMiss ? 2000 : 300; + float duration = wasMiss ? 2000 : 500; text.NumberContainer .ScaleTo(new Vector2(newScale)) From 46a219e01010e32827f3772aae77fece7de8bccf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Nov 2023 17:53:02 +0900 Subject: [PATCH 1446/4092] Add comment explaining `AutoSize` change in `LegacySongProgress` --- osu.Game/Skinning/LegacySongProgress.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Skinning/LegacySongProgress.cs b/osu.Game/Skinning/LegacySongProgress.cs index 26004ff111..fa6ee38fee 100644 --- a/osu.Game/Skinning/LegacySongProgress.cs +++ b/osu.Game/Skinning/LegacySongProgress.cs @@ -21,6 +21,8 @@ namespace osu.Game.Skinning public LegacySongProgress() { + // User shouldn't be able to adjust width/height of this as `CircularProgress` doesn't + // handle stretched cases ell. AutoSizeAxes = Axes.Both; } From f25489cc7be71426891f50046b70aefdc24578de Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Nov 2023 17:53:56 +0900 Subject: [PATCH 1447/4092] Check X/Y sizing available separately to fix weird edge cases --- .../SkinEditor/SkinSelectionHandler.cs | 43 +++++++++++++------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index 77e522a925..a4d530a4d9 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -32,11 +32,11 @@ namespace osu.Game.Overlays.SkinEditor UpdatePosition = updateDrawablePosition }; - private bool allSelectedSupportManualSizing => SelectedItems.All(b => (b as CompositeDrawable)?.AutoSizeAxes == Axes.None); + private bool allSelectedSupportManualSizing(Axes axis) => SelectedItems.All(b => (b as CompositeDrawable)?.AutoSizeAxes.HasFlagFast(axis) == false); public override bool HandleScale(Vector2 scale, Anchor anchor) { - bool adjustSize; + Axes adjustAxis; switch (anchor) { @@ -45,19 +45,25 @@ namespace osu.Game.Overlays.SkinEditor case Anchor.TopRight: case Anchor.BottomLeft: case Anchor.BottomRight: - adjustSize = false; + adjustAxis = Axes.Both; break; // for edges, adjust size. + // autosize elements can't be easily handled so just disable sizing for now. case Anchor.TopCentre: - case Anchor.CentreLeft: - case Anchor.CentreRight: case Anchor.BottomCentre: - // autosize elements can't be easily handled so just disable sizing for now. - if (!allSelectedSupportManualSizing) + if (!allSelectedSupportManualSizing(Axes.Y)) return false; - adjustSize = true; + adjustAxis = Axes.Y; + break; + + case Anchor.CentreLeft: + case Anchor.CentreRight: + if (!allSelectedSupportManualSizing(Axes.X)) + return false; + + adjustAxis = Axes.X; break; default: @@ -151,10 +157,20 @@ namespace osu.Game.Overlays.SkinEditor if (Precision.AlmostEquals(MathF.Abs(drawableItem.Rotation) % 180, 90)) currentScaledDelta = new Vector2(scaledDelta.Y, scaledDelta.X); - if (adjustSize) - drawableItem.Size *= currentScaledDelta; - else - drawableItem.Scale *= currentScaledDelta; + switch (adjustAxis) + { + case Axes.X: + drawableItem.Width *= currentScaledDelta.X; + break; + + case Axes.Y: + drawableItem.Height *= currentScaledDelta.Y; + break; + + case Axes.Both: + drawableItem.Scale *= currentScaledDelta; + break; + } } return true; @@ -203,7 +219,8 @@ namespace osu.Game.Overlays.SkinEditor { base.OnSelectionChanged(); - SelectionBox.CanScaleX = SelectionBox.CanScaleY = allSelectedSupportManualSizing; + SelectionBox.CanScaleX = allSelectedSupportManualSizing(Axes.X); + SelectionBox.CanScaleY = allSelectedSupportManualSizing(Axes.Y); SelectionBox.CanScaleProportionally = true; SelectionBox.CanFlipX = true; SelectionBox.CanFlipY = true; From d0f1326a63da2187c80c7429cf386bfb3856c8c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 10 Nov 2023 17:54:48 +0900 Subject: [PATCH 1448/4092] Fix formatting --- osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs b/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs index 93e2583eb7..b38fb9153a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs @@ -125,7 +125,9 @@ namespace osu.Game.Tests.Visual.Online Masking = true, EdgeEffect = new EdgeEffectParameters { - Type = EdgeEffectType.Shadow, Radius = 1, Colour = Color4.Black.Opacity(0.2f), + Type = EdgeEffectType.Shadow, + Radius = 1, + Colour = Color4.Black.Opacity(0.2f), }, }; } From 35e11c7c63b83122a45f5f9820aa6909ffb892bd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Nov 2023 17:51:30 +0900 Subject: [PATCH 1449/4092] Rename diagonal scale variable and update xmldoc --- .../Edit/OsuSelectionHandler.cs | 2 +- .../Editing/TestSceneComposeSelectBox.cs | 2 +- .../SkinEditor/SkinSelectionHandler.cs | 2 +- .../Edit/Compose/Components/SelectionBox.cs | 22 +++++++++++-------- osu.sln.DotSettings | 1 + 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 4da640a971..4765f615ce 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.Edit SelectionBox.CanFlipX = SelectionBox.CanScaleX = quad.Width > 0; SelectionBox.CanFlipY = SelectionBox.CanScaleY = quad.Height > 0; - SelectionBox.CanScaleProportionally = SelectionBox.CanScaleX && SelectionBox.CanScaleY; + SelectionBox.CanScaleDiagonally = SelectionBox.CanScaleX && SelectionBox.CanScaleY; SelectionBox.CanReverse = EditorBeatmap.SelectedHitObjects.Count > 1 || EditorBeatmap.SelectedHitObjects.Any(s => s is Slider); } diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs index 9e8d75efea..f6637d0e80 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.Editing CanScaleX = true, CanScaleY = true, - CanScaleProportionally = true, + CanScaleDiagonally = true, CanFlipX = true, CanFlipY = true, diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index a4d530a4d9..52c012a15a 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -221,7 +221,7 @@ namespace osu.Game.Overlays.SkinEditor SelectionBox.CanScaleX = allSelectedSupportManualSizing(Axes.X); SelectionBox.CanScaleY = allSelectedSupportManualSizing(Axes.Y); - SelectionBox.CanScaleProportionally = true; + SelectionBox.CanScaleDiagonally = true; SelectionBox.CanFlipX = true; SelectionBox.CanFlipY = true; SelectionBox.CanReverse = false; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index 0917867a61..0b16941bc4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -60,7 +60,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private bool canScaleX; /// - /// Whether horizontal scaling support should be enabled. + /// Whether horizontal scaling (from the left or right edge) support should be enabled. /// public bool CanScaleX { @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private bool canScaleY; /// - /// Whether vertical scaling support should be enabled. + /// Whether vertical scaling (from the top or bottom edge) support should be enabled. /// public bool CanScaleY { @@ -91,19 +91,23 @@ namespace osu.Game.Screens.Edit.Compose.Components } } - private bool canScaleProportionally; + private bool canScaleDiagonally; /// - /// Whether vertical scaling support should be enabled. + /// Whether diagonal scaling (from a corner) support should be enabled. /// - public bool CanScaleProportionally + /// + /// There are some cases where we only want to allow proportional resizing, and not allow + /// one or both explicit directions of scale. + /// + public bool CanScaleDiagonally { - get => canScaleProportionally; + get => canScaleDiagonally; set { - if (canScaleProportionally == value) return; + if (canScaleDiagonally == value) return; - canScaleProportionally = value; + canScaleDiagonally = value; recreate(); } } @@ -262,7 +266,7 @@ namespace osu.Game.Screens.Edit.Compose.Components }; if (CanScaleX) addXScaleComponents(); - if (CanScaleProportionally) addFullScaleComponents(); + if (CanScaleDiagonally) addFullScaleComponents(); if (CanScaleY) addYScaleComponents(); if (CanFlipX) addXFlipComponents(); if (CanFlipY) addYFlipComponents(); diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index c2778ca5b1..342bc8aa79 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -823,6 +823,7 @@ See the LICENCE file in the repository root for full licence text. True True True + True True True True From 36d0bae42dfe95481b3e20751d8f7032b925e9df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 10 Nov 2023 17:57:16 +0900 Subject: [PATCH 1450/4092] Restore mention of dependency on another ctor param --- osu.Game/Users/Drawables/UpdateableAvatar.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Users/Drawables/UpdateableAvatar.cs b/osu.Game/Users/Drawables/UpdateableAvatar.cs index b020e7fa63..21153ecfc3 100644 --- a/osu.Game/Users/Drawables/UpdateableAvatar.cs +++ b/osu.Game/Users/Drawables/UpdateableAvatar.cs @@ -54,7 +54,10 @@ namespace osu.Game.Users.Drawables /// /// The initial user to display. /// If set to true, hover/click sounds will play and clicking the avatar will open the user's profile. - /// If set to true, the user status panel will be displayed in the tooltip. + /// + /// If set to true, the user status panel will be displayed in the tooltip. + /// Only has an effect if is true. + /// /// Whether to show a default guest representation on null user (as opposed to nothing). public UpdateableAvatar(APIUser? user = null, bool isInteractive = true, bool showUserPanelOnHover = false, bool showGuestOnNull = true) { From 2c1f304f3b4faaa645edb3c138b915ad351c9fa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 10 Nov 2023 18:13:36 +0900 Subject: [PATCH 1451/4092] Fix test failures due to fluctuations in needlessly-serialised automatic sizings --- osu.Game/Skinning/SerialisableDrawableExtensions.cs | 7 +++++-- osu.Game/Skinning/SerialisedDrawableInfo.cs | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/SerialisableDrawableExtensions.cs b/osu.Game/Skinning/SerialisableDrawableExtensions.cs index 0b44db9bdc..609dd9c8ab 100644 --- a/osu.Game/Skinning/SerialisableDrawableExtensions.cs +++ b/osu.Game/Skinning/SerialisableDrawableExtensions.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Configuration; @@ -19,8 +20,10 @@ namespace osu.Game.Skinning // todo: can probably make this better via deserialisation directly using a common interface. component.Position = drawableInfo.Position; component.Rotation = drawableInfo.Rotation; - if (drawableInfo.Size != Vector2.Zero && (component as CompositeDrawable)?.AutoSizeAxes == Axes.None) - component.Size = drawableInfo.Size; + if (drawableInfo.Width is float width && width != 0 && (component as CompositeDrawable)?.AutoSizeAxes.HasFlagFast(Axes.X) == false) + component.Width = width; + if (drawableInfo.Height is float height && height != 0 && (component as CompositeDrawable)?.AutoSizeAxes.HasFlagFast(Axes.Y) == false) + component.Height = height; component.Scale = drawableInfo.Scale; component.Anchor = drawableInfo.Anchor; component.Origin = drawableInfo.Origin; diff --git a/osu.Game/Skinning/SerialisedDrawableInfo.cs b/osu.Game/Skinning/SerialisedDrawableInfo.cs index 0705e91d6d..b2237acc5a 100644 --- a/osu.Game/Skinning/SerialisedDrawableInfo.cs +++ b/osu.Game/Skinning/SerialisedDrawableInfo.cs @@ -35,7 +35,9 @@ namespace osu.Game.Skinning public Vector2 Scale { get; set; } - public Vector2 Size { get; set; } + public float? Width { get; set; } + + public float? Height { get; set; } public Anchor Anchor { get; set; } @@ -64,7 +66,8 @@ namespace osu.Game.Skinning Position = component.Position; Rotation = component.Rotation; Scale = component.Scale; - Size = component.Size; + Height = component.Height; + Width = component.Width; Anchor = component.Anchor; Origin = component.Origin; From 374e13b496dc3780e03f2c9f76a850ab0a8050e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 10 Nov 2023 18:18:45 +0900 Subject: [PATCH 1452/4092] Remove argon health display bar length setting It is no longer needed. Intentionally not doing backwards migration to simplify things; users can fix their skins. --- osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 793d43f7ef..b4002343d3 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -35,14 +35,6 @@ namespace osu.Game.Screens.Play.HUD Precision = 1 }; - [SettingSource("Bar length")] - public BindableFloat BarLength { get; } = new BindableFloat(0.98f) - { - MinValue = 0.2f, - MaxValue = 1, - Precision = 0.01f, - }; - private BarPath mainBar = null!; /// @@ -140,7 +132,6 @@ namespace osu.Game.Screens.Play.HUD Current.BindValueChanged(_ => Scheduler.AddOnce(updateCurrent), true); - BarLength.BindValueChanged(l => Width = l.NewValue, true); BarHeight.BindValueChanged(_ => updatePath()); updatePath(); } From 43a4b34295471a2a4c0103aa9383ef75cbb8dc56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 10 Nov 2023 18:20:14 +0900 Subject: [PATCH 1453/4092] Fix typo --- osu.Game/Skinning/LegacySongProgress.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySongProgress.cs b/osu.Game/Skinning/LegacySongProgress.cs index fa6ee38fee..4295060a3a 100644 --- a/osu.Game/Skinning/LegacySongProgress.cs +++ b/osu.Game/Skinning/LegacySongProgress.cs @@ -22,7 +22,7 @@ namespace osu.Game.Skinning public LegacySongProgress() { // User shouldn't be able to adjust width/height of this as `CircularProgress` doesn't - // handle stretched cases ell. + // handle stretched cases well. AutoSizeAxes = Axes.Both; } From 26eae0bdeeed6670fc3a47b81abdc75afceda434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 10 Nov 2023 18:25:39 +0900 Subject: [PATCH 1454/4092] Remove unused using directive --- osu.Game/Skinning/SerialisableDrawableExtensions.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Skinning/SerialisableDrawableExtensions.cs b/osu.Game/Skinning/SerialisableDrawableExtensions.cs index 609dd9c8ab..6938ed4091 100644 --- a/osu.Game/Skinning/SerialisableDrawableExtensions.cs +++ b/osu.Game/Skinning/SerialisableDrawableExtensions.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Configuration; using osu.Game.Extensions; -using osuTK; namespace osu.Game.Skinning { From ec2200d54fff1cf823355571c2a3c827c3a19ca8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 10 Nov 2023 18:26:08 +0900 Subject: [PATCH 1455/4092] Remove another reference to bar length --- .../Visual/Gameplay/TestSceneArgonHealthDisplay.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs index 7bad623d7f..f51577bc84 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs @@ -47,12 +47,6 @@ namespace osu.Game.Tests.Visual.Gameplay }; }); - AddSliderStep("Width", 0, 1f, 1f, val => - { - if (healthDisplay.IsNotNull()) - healthDisplay.BarLength.Value = val; - }); - AddSliderStep("Height", 0, 64, 0, val => { if (healthDisplay.IsNotNull()) From fbf94214a536a5b5aa7246d2ee34ee5ecacf8fb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 10 Nov 2023 18:36:09 +0900 Subject: [PATCH 1456/4092] Fully delegate tooltip show/hide logic --- osu.Game/Users/Drawables/ClickableAvatar.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Users/Drawables/ClickableAvatar.cs b/osu.Game/Users/Drawables/ClickableAvatar.cs index 6390acc608..ef451df95d 100644 --- a/osu.Game/Users/Drawables/ClickableAvatar.cs +++ b/osu.Game/Users/Drawables/ClickableAvatar.cs @@ -105,17 +105,17 @@ namespace osu.Game.Users.Drawables public partial class NoCardTooltip : VisibilityContainer, ITooltip { + private readonly OsuTooltipContainer.OsuTooltip tooltip; + public NoCardTooltip() { - var tooltip = new OsuTooltipContainer.OsuTooltip(); + tooltip = new OsuTooltipContainer.OsuTooltip(); tooltip.SetContent(ContextMenuStrings.ViewProfile); - tooltip.Show(); - Child = tooltip; } - protected override void PopIn() => this.FadeIn(150, Easing.OutQuint); - protected override void PopOut() => this.Delay(150).FadeOut(500, Easing.OutQuint); + protected override void PopIn() => tooltip.Show(); + protected override void PopOut() => tooltip.Hide(); public void Move(Vector2 pos) => Position = pos; From fecc6f580bf309357682d1ee725f0a3796d9261a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 23 Oct 2023 16:37:26 +0900 Subject: [PATCH 1457/4092] Add legacy reference health processor --- .../Scoring/LegacyOsuHealthProcessor.cs | 197 ++++++++++++++++++ .../Scoring/DrainingHealthProcessor.cs | 1 - .../Scoring/LegacyDrainingHealthProcessor.cs | 90 ++++++++ 3 files changed, 287 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Osu/Scoring/LegacyOsuHealthProcessor.cs create mode 100644 osu.Game/Rulesets/Scoring/LegacyDrainingHealthProcessor.cs diff --git a/osu.Game.Rulesets.Osu/Scoring/LegacyOsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/LegacyOsuHealthProcessor.cs new file mode 100644 index 0000000000..103569ffc3 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Scoring/LegacyOsuHealthProcessor.cs @@ -0,0 +1,197 @@ +// 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.Linq; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Osu.Scoring +{ + // Reference implementation for osu!stable's HP drain. + public partial class LegacyOsuHealthProcessor : LegacyDrainingHealthProcessor + { + private const double hp_bar_maximum = 200; + private const double hp_combo_geki = 14; + private const double hp_hit_300 = 6; + private const double hp_slider_repeat = 4; + private const double hp_slider_tick = 3; + + private double lowestHpEver; + private double lowestHpEnd; + private double lowestHpComboEnd; + private double hpRecoveryAvailable; + private double hpMultiplierNormal; + private double hpMultiplierComboEnd; + + public LegacyOsuHealthProcessor(double drainStartTime) + : base(drainStartTime) + { + } + + public override void ApplyBeatmap(IBeatmap beatmap) + { + lowestHpEver = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 195, 160, 60); + lowestHpComboEnd = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 198, 170, 80); + lowestHpEnd = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 198, 180, 80); + hpRecoveryAvailable = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 8, 4, 0); + + base.ApplyBeatmap(beatmap); + } + + protected override void Reset(bool storeResults) + { + hpMultiplierNormal = 1; + hpMultiplierComboEnd = 1; + + base.Reset(storeResults); + } + + protected override double ComputeDrainRate() + { + double testDrop = 0.05; + double currentHp; + double currentHpUncapped; + + do + { + currentHp = hp_bar_maximum; + currentHpUncapped = hp_bar_maximum; + + double lowestHp = currentHp; + double lastTime = DrainStartTime; + int currentBreak = 0; + bool fail = false; + int comboTooLowCount = 0; + string failReason = string.Empty; + + for (int i = 0; i < Beatmap.HitObjects.Count; i++) + { + HitObject h = Beatmap.HitObjects[i]; + + // Find active break (between current and lastTime) + double localLastTime = lastTime; + double breakTime = 0; + + // Subtract any break time from the duration since the last object + if (Beatmap.Breaks.Count > 0 && currentBreak < Beatmap.Breaks.Count) + { + BreakPeriod e = Beatmap.Breaks[currentBreak]; + + if (e.StartTime >= localLastTime && e.EndTime <= h.StartTime) + { + // consider break start equal to object end time for version 8+ since drain stops during this time + breakTime = (Beatmap.BeatmapInfo.BeatmapVersion < 8) ? (e.EndTime - e.StartTime) : e.EndTime - localLastTime; + currentBreak++; + } + } + + reduceHp(testDrop * (h.StartTime - lastTime - breakTime)); + + lastTime = h.GetEndTime(); + + if (currentHp < lowestHp) + lowestHp = currentHp; + + if (currentHp <= lowestHpEver) + { + fail = true; + testDrop *= 0.96; + failReason = $"hp too low ({currentHp / hp_bar_maximum} < {lowestHpEver / hp_bar_maximum})"; + break; + } + + double hpReduction = testDrop * (h.GetEndTime() - h.StartTime); + double hpOverkill = Math.Max(0, hpReduction - currentHp); + reduceHp(hpReduction); + + if (h is Slider slider) + { + for (int j = 0; j < slider.RepeatCount + 2; j++) + increaseHp(hpMultiplierNormal * hp_slider_repeat); + foreach (var _ in slider.NestedHitObjects.OfType()) + increaseHp(hpMultiplierNormal * hp_slider_tick); + } + else if (h is Spinner spinner) + { + foreach (var _ in spinner.NestedHitObjects.Where(t => t is not SpinnerBonusTick)) + increaseHp(hpMultiplierNormal * 1.7); + } + + if (hpOverkill > 0 && currentHp - hpOverkill <= lowestHpEver) + { + fail = true; + testDrop *= 0.96; + failReason = $"overkill ({currentHp / hp_bar_maximum} - {hpOverkill / hp_bar_maximum} <= {lowestHpEver / hp_bar_maximum})"; + break; + } + + if (i == Beatmap.HitObjects.Count - 1 || ((OsuHitObject)Beatmap.HitObjects[i + 1]).NewCombo) + { + increaseHp(hpMultiplierComboEnd * hp_combo_geki + hpMultiplierNormal * hp_hit_300); + + if (currentHp < lowestHpComboEnd) + { + if (++comboTooLowCount > 2) + { + hpMultiplierComboEnd *= 1.07; + hpMultiplierNormal *= 1.03; + fail = true; + failReason = $"combo end hp too low ({currentHp / hp_bar_maximum} < {lowestHpComboEnd / hp_bar_maximum})"; + break; + } + } + } + else + increaseHp(hpMultiplierNormal * hp_hit_300); + } + + if (!fail && currentHp < lowestHpEnd) + { + fail = true; + testDrop *= 0.94; + hpMultiplierComboEnd *= 1.01; + hpMultiplierNormal *= 1.01; + failReason = $"end hp too low ({currentHp / hp_bar_maximum} < {lowestHpEnd / hp_bar_maximum})"; + } + + double recovery = (currentHpUncapped - hp_bar_maximum) / Beatmap.HitObjects.Count; + + if (!fail && recovery < hpRecoveryAvailable) + { + fail = true; + testDrop *= 0.96; + hpMultiplierComboEnd *= 1.02; + hpMultiplierNormal *= 1.01; + failReason = $"recovery too low ({recovery / hp_bar_maximum} < {hpRecoveryAvailable / hp_bar_maximum})"; + } + + if (fail) + { + if (Log) + Console.WriteLine($"FAILED drop {testDrop / hp_bar_maximum}: {failReason}"); + continue; + } + + if (Log) + Console.WriteLine($"PASSED drop {testDrop / hp_bar_maximum}"); + return testDrop / hp_bar_maximum; + } while (true); + + void reduceHp(double amount) + { + currentHpUncapped = Math.Max(0, currentHpUncapped - amount); + currentHp = Math.Max(0, currentHp - amount); + } + + void increaseHp(double amount) + { + currentHpUncapped += amount; + currentHp = Math.Max(0, Math.Min(hp_bar_maximum, currentHp + amount)); + } + } + } +} diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs index 592dcbfeb8..2f81aa735e 100644 --- a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs @@ -42,7 +42,6 @@ namespace osu.Game.Rulesets.Scoring private const double max_health_target = 0.4; private IBeatmap beatmap; - private double gameplayEndTime; private readonly double drainStartTime; diff --git a/osu.Game/Rulesets/Scoring/LegacyDrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/LegacyDrainingHealthProcessor.cs new file mode 100644 index 0000000000..5d2426e4b7 --- /dev/null +++ b/osu.Game/Rulesets/Scoring/LegacyDrainingHealthProcessor.cs @@ -0,0 +1,90 @@ +// 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.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; +using osu.Game.Utils; + +namespace osu.Game.Rulesets.Scoring +{ + /// + /// A which continuously drains health.
+ /// At HP=0, the minimum health reached for a perfect play is 95%.
+ /// At HP=5, the minimum health reached for a perfect play is 70%.
+ /// At HP=10, the minimum health reached for a perfect play is 30%. + ///
+ public abstract partial class LegacyDrainingHealthProcessor : HealthProcessor + { + protected double DrainStartTime { get; } + protected double GameplayEndTime { get; private set; } + + protected IBeatmap Beatmap { get; private set; } + protected PeriodTracker NoDrainPeriodTracker { get; private set; } + + public bool Log { get; set; } + + public double DrainRate { get; private set; } + + /// + /// Creates a new . + /// + /// The time after which draining should begin. + protected LegacyDrainingHealthProcessor(double drainStartTime) + { + DrainStartTime = drainStartTime; + } + + protected override void Update() + { + base.Update(); + + if (NoDrainPeriodTracker?.IsInAny(Time.Current) == true) + return; + + // When jumping in and out of gameplay time within a single frame, health should only be drained for the period within the gameplay time + double lastGameplayTime = Math.Clamp(Time.Current - Time.Elapsed, DrainStartTime, GameplayEndTime); + double currentGameplayTime = Math.Clamp(Time.Current, DrainStartTime, GameplayEndTime); + + Health.Value -= DrainRate * (currentGameplayTime - lastGameplayTime); + } + + public override void ApplyBeatmap(IBeatmap beatmap) + { + Beatmap = beatmap; + + if (beatmap.HitObjects.Count > 0) + GameplayEndTime = beatmap.HitObjects[^1].GetEndTime(); + + NoDrainPeriodTracker = new PeriodTracker(beatmap.Breaks.Select(breakPeriod => new Period( + beatmap.HitObjects + .Select(hitObject => hitObject.GetEndTime()) + .Where(endTime => endTime <= breakPeriod.StartTime) + .DefaultIfEmpty(double.MinValue) + .Last(), + beatmap.HitObjects + .Select(hitObject => hitObject.StartTime) + .Where(startTime => startTime >= breakPeriod.EndTime) + .DefaultIfEmpty(double.MaxValue) + .First() + ))); + + base.ApplyBeatmap(beatmap); + } + + protected override void Reset(bool storeResults) + { + base.Reset(storeResults); + + DrainRate = 1; + + if (storeResults) + DrainRate = ComputeDrainRate(); + } + + protected abstract double ComputeDrainRate(); + } +} From 12e5766d5073374b463ad6752a98fd8ed937292c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 10 Nov 2023 16:35:03 +0900 Subject: [PATCH 1458/4092] Implement OsuHealthProcessor following osu-stable --- .../Scoring/OsuHealthProcessor.cs | 229 ++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs new file mode 100644 index 0000000000..489b8408ea --- /dev/null +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs @@ -0,0 +1,229 @@ +// 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.Linq; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Osu.Scoring +{ + public partial class OsuHealthProcessor : LegacyDrainingHealthProcessor + { + private double lowestHpEver; + private double lowestHpEnd; + private double hpRecoveryAvailable; + private double hpMultiplierNormal; + + public OsuHealthProcessor(double drainStartTime) + : base(drainStartTime) + { + } + + public override void ApplyBeatmap(IBeatmap beatmap) + { + lowestHpEver = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 0.975, 0.8, 0.3); + lowestHpEnd = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 0.99, 0.9, 0.4); + hpRecoveryAvailable = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 0.04, 0.02, 0); + + base.ApplyBeatmap(beatmap); + } + + protected override void Reset(bool storeResults) + { + hpMultiplierNormal = 1; + base.Reset(storeResults); + } + + protected override double ComputeDrainRate() + { + double testDrop = 0.00025; + double currentHp; + double currentHpUncapped; + + do + { + currentHp = 1; + currentHpUncapped = 1; + + double lowestHp = currentHp; + double lastTime = DrainStartTime; + int currentBreak = 0; + bool fail = false; + string failReason = string.Empty; + + for (int i = 0; i < Beatmap.HitObjects.Count; i++) + { + HitObject h = Beatmap.HitObjects[i]; + + // Find active break (between current and lastTime) + double localLastTime = lastTime; + double breakTime = 0; + + // Subtract any break time from the duration since the last object + if (Beatmap.Breaks.Count > 0 && currentBreak < Beatmap.Breaks.Count) + { + BreakPeriod e = Beatmap.Breaks[currentBreak]; + + if (e.StartTime >= localLastTime && e.EndTime <= h.StartTime) + { + // consider break start equal to object end time for version 8+ since drain stops during this time + breakTime = (Beatmap.BeatmapInfo.BeatmapVersion < 8) ? (e.EndTime - e.StartTime) : e.EndTime - localLastTime; + currentBreak++; + } + } + + reduceHp(testDrop * (h.StartTime - lastTime - breakTime)); + + lastTime = h.GetEndTime(); + + if (currentHp < lowestHp) + lowestHp = currentHp; + + if (currentHp <= lowestHpEver) + { + fail = true; + testDrop *= 0.96; + failReason = $"hp too low ({currentHp} < {lowestHpEver})"; + break; + } + + double hpReduction = testDrop * (h.GetEndTime() - h.StartTime); + double hpOverkill = Math.Max(0, hpReduction - currentHp); + reduceHp(hpReduction); + + if (h is Slider slider) + { + foreach (var nested in slider.NestedHitObjects) + increaseHp(nested); + } + else if (h is Spinner spinner) + { + foreach (var nested in spinner.NestedHitObjects.Where(t => t is not SpinnerBonusTick)) + increaseHp(nested); + } + + if (hpOverkill > 0 && currentHp - hpOverkill <= lowestHpEver) + { + fail = true; + testDrop *= 0.96; + failReason = $"overkill ({currentHp} - {hpOverkill} <= {lowestHpEver})"; + break; + } + + increaseHp(h); + } + + if (!fail && currentHp < lowestHpEnd) + { + fail = true; + testDrop *= 0.94; + hpMultiplierNormal *= 1.01; + failReason = $"end hp too low ({currentHp} < {lowestHpEnd})"; + } + + double recovery = (currentHpUncapped - 1) / Beatmap.HitObjects.Count; + + if (!fail && recovery < hpRecoveryAvailable) + { + fail = true; + testDrop *= 0.96; + hpMultiplierNormal *= 1.01; + failReason = $"recovery too low ({recovery} < {hpRecoveryAvailable})"; + } + + if (fail) + { + if (Log) + Console.WriteLine($"FAILED drop {testDrop}: {failReason}"); + continue; + } + + if (Log) + Console.WriteLine($"PASSED drop {testDrop}"); + return testDrop; + } while (true); + + void reduceHp(double amount) + { + currentHpUncapped = Math.Max(0, currentHpUncapped - amount); + currentHp = Math.Max(0, currentHp - amount); + } + + void increaseHp(HitObject hitObject) + { + double amount = healthIncreaseFor(hitObject, hitObject.CreateJudgement().MaxResult); + currentHpUncapped += amount; + currentHp = Math.Max(0, Math.Min(1, currentHp + amount)); + } + } + + protected override double GetHealthIncreaseFor(JudgementResult result) => healthIncreaseFor(result.HitObject, result.Type); + + private double healthIncreaseFor(HitObject hitObject, HitResult result) + { + double increase; + + switch (result) + { + case HitResult.SmallTickMiss: + return IBeatmapDifficultyInfo.DifficultyRange(Beatmap.Difficulty.DrainRate, -0.02, -0.075, -0.14); + + case HitResult.LargeTickMiss: + return IBeatmapDifficultyInfo.DifficultyRange(Beatmap.Difficulty.DrainRate, -0.02, -0.075, -0.14); + + case HitResult.Miss: + return IBeatmapDifficultyInfo.DifficultyRange(Beatmap.Difficulty.DrainRate, -0.03, -0.125, -0.2); + + case HitResult.SmallTickHit: + // This result is always as a result of the slider tail. + increase = 0.02; + break; + + case HitResult.LargeTickHit: + // This result is either a result of a slider tick or a repeat. + increase = hitObject is SliderTick ? 0.015 : 0.02; + break; + + case HitResult.Meh: + increase = 0.002; + break; + + case HitResult.Ok: + increase = 0.011; + break; + + case HitResult.Good: + increase = 0.024; + break; + + case HitResult.Great: + increase = 0.03; + break; + + case HitResult.Perfect: + // 1.1 * Great. Unused. + increase = 0.033; + break; + + case HitResult.SmallBonus: + increase = 0.0085; + break; + + case HitResult.LargeBonus: + increase = 0.01; + break; + + default: + increase = 0; + break; + } + + return hpMultiplierNormal * increase; + } + } +} From b6dcd7d55f0d421d85e2e97d00ae5704d1fb5487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 10 Nov 2023 18:48:45 +0900 Subject: [PATCH 1459/4092] Fix tests so that they actually assert something --- .../Online/TestSceneUserClickableAvatar.cs | 67 +++---------------- 1 file changed, 9 insertions(+), 58 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs b/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs index b38fb9153a..9edaa841b2 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Testing; +using osu.Game.Graphics.Cursor; using osu.Game.Online.API.Requests.Responses; using osu.Game.Users; using osu.Game.Users.Drawables; @@ -41,65 +42,15 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestClickableAvatarHover() { - AddStep($"click user {1} with UserGridPanel {nameof(ClickableAvatar)}", () => - { - var targets = this.ChildrenOfType().ToList(); - if (targets.Count < 1) - return; + AddStep("hover avatar with user panel", () => InputManager.MoveMouseTo(this.ChildrenOfType().ElementAt(1))); + AddUntilStep("wait for tooltip to show", () => this.ChildrenOfType().FirstOrDefault()?.State.Value == Visibility.Visible); + AddStep("hover out", () => InputManager.MoveMouseTo(new Vector2(0))); + AddUntilStep("wait for tooltip to hide", () => this.ChildrenOfType().FirstOrDefault()?.State.Value == Visibility.Hidden); - InputManager.MoveMouseTo(targets[0]); - }); - AddWaitStep("wait for tooltip to show", 5); - AddStep("Hover out", () => InputManager.MoveMouseTo(new Vector2(0))); - AddWaitStep("wait for tooltip to hide", 3); - - AddStep($"click user {2} with username only. {nameof(ClickableAvatar)}", () => - { - var targets = this.ChildrenOfType().ToList(); - if (targets.Count < 2) - return; - - InputManager.MoveMouseTo(targets[1]); - }); - AddWaitStep("wait for tooltip to show", 5); - AddStep("Hover out", () => InputManager.MoveMouseTo(new Vector2(0))); - AddWaitStep("wait for tooltip to hide", 3); - - AddStep($"click user {3} with UserGridPanel {nameof(ClickableAvatar)}", () => - { - var targets = this.ChildrenOfType().ToList(); - if (targets.Count < 3) - return; - - InputManager.MoveMouseTo(targets[2]); - }); - AddWaitStep("wait for tooltip to show", 5); - AddStep("Hover out", () => InputManager.MoveMouseTo(new Vector2(0))); - AddWaitStep("wait for tooltip to hide", 3); - - AddStep($"click null user {4}. {nameof(ClickableAvatar)}", () => - { - var targets = this.ChildrenOfType().ToList(); - if (targets.Count < 4) - return; - - InputManager.MoveMouseTo(targets[3]); - }); - AddWaitStep("wait for tooltip to show", 5); - AddStep("Hover out", () => InputManager.MoveMouseTo(new Vector2(0))); - AddWaitStep("wait for tooltip to hide", 3); - - AddStep($"click null user {5}. {nameof(ClickableAvatar)}", () => - { - var targets = this.ChildrenOfType().ToList(); - if (targets.Count < 5) - return; - - InputManager.MoveMouseTo(targets[4]); - }); - AddWaitStep("wait for tooltip to show", 5); - AddStep("Hover out", () => InputManager.MoveMouseTo(new Vector2(0))); - AddWaitStep("wait for tooltip to hide", 3); + AddStep("hover avatar without user panel", () => InputManager.MoveMouseTo(this.ChildrenOfType().ElementAt(0))); + AddUntilStep("wait for tooltip to show", () => this.ChildrenOfType().FirstOrDefault()?.State.Value == Visibility.Visible); + AddStep("hover out", () => InputManager.MoveMouseTo(new Vector2(0))); + AddUntilStep("wait for tooltip to hide", () => this.ChildrenOfType().FirstOrDefault()?.State.Value == Visibility.Hidden); } private Drawable generateUser(string username, int id, CountryCode countryCode, string cover, bool showPanel, string? color = null) From 793d90e396bb3268af6117f1fff2a5b474e80e2d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 10 Nov 2023 19:09:09 +0900 Subject: [PATCH 1460/4092] Add some notes --- osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs index 489b8408ea..2266cf9d33 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs @@ -64,6 +64,7 @@ namespace osu.Game.Rulesets.Osu.Scoring double localLastTime = lastTime; double breakTime = 0; + // TODO: This doesn't handle overlapping/sequential breaks correctly (/b/614). // Subtract any break time from the duration since the last object if (Beatmap.Breaks.Count > 0 && currentBreak < Beatmap.Breaks.Count) { @@ -107,6 +108,8 @@ namespace osu.Game.Rulesets.Osu.Scoring increaseHp(nested); } + // Note: Because HP is capped during the above increases, long sliders (with many ticks) or spinners + // will appear to overkill at lower drain levels than they should. However, it is also not correct to simply use the uncapped version. if (hpOverkill > 0 && currentHp - hpOverkill <= lowestHpEver) { fail = true; From b7acbde719744a3aac166c676b792e9d87699612 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 10 Nov 2023 19:12:26 +0900 Subject: [PATCH 1461/4092] Only store width/height of serialised drawable if it isn't automatically computed --- osu.Game/Skinning/SerialisedDrawableInfo.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/SerialisedDrawableInfo.cs b/osu.Game/Skinning/SerialisedDrawableInfo.cs index b2237acc5a..2d6113ff70 100644 --- a/osu.Game/Skinning/SerialisedDrawableInfo.cs +++ b/osu.Game/Skinning/SerialisedDrawableInfo.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; using osu.Framework.Bindables; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; @@ -66,8 +67,13 @@ namespace osu.Game.Skinning Position = component.Position; Rotation = component.Rotation; Scale = component.Scale; - Height = component.Height; - Width = component.Width; + + if ((component as CompositeDrawable)?.AutoSizeAxes.HasFlagFast(Axes.X) != true) + Width = component.Width; + + if ((component as CompositeDrawable)?.AutoSizeAxes.HasFlagFast(Axes.Y) != true) + Height = component.Height; + Anchor = component.Anchor; Origin = component.Origin; From 57cd5194ce06b16e03dc0dea32a0e81da80b1029 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Nov 2023 20:00:20 +0900 Subject: [PATCH 1462/4092] Flip comparison to allow non-composite drawables to still get resized --- osu.Game/Skinning/SerialisableDrawableExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/SerialisableDrawableExtensions.cs b/osu.Game/Skinning/SerialisableDrawableExtensions.cs index 6938ed4091..97c4cc8f73 100644 --- a/osu.Game/Skinning/SerialisableDrawableExtensions.cs +++ b/osu.Game/Skinning/SerialisableDrawableExtensions.cs @@ -19,9 +19,9 @@ namespace osu.Game.Skinning // todo: can probably make this better via deserialisation directly using a common interface. component.Position = drawableInfo.Position; component.Rotation = drawableInfo.Rotation; - if (drawableInfo.Width is float width && width != 0 && (component as CompositeDrawable)?.AutoSizeAxes.HasFlagFast(Axes.X) == false) + if (drawableInfo.Width is float width && width != 0 && (component as CompositeDrawable)?.AutoSizeAxes.HasFlagFast(Axes.X) != true) component.Width = width; - if (drawableInfo.Height is float height && height != 0 && (component as CompositeDrawable)?.AutoSizeAxes.HasFlagFast(Axes.Y) == false) + if (drawableInfo.Height is float height && height != 0 && (component as CompositeDrawable)?.AutoSizeAxes.HasFlagFast(Axes.Y) != true) component.Height = height; component.Scale = drawableInfo.Scale; component.Anchor = drawableInfo.Anchor; From 2e48569982039c9c1e6cb722190a6669ea09aee7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Nov 2023 20:00:51 +0900 Subject: [PATCH 1463/4092] Improve test comparison logic Doesn't really help that much with nunit output, but at least you can breakpoint and see the json kinda. --- .../Visual/Gameplay/TestSceneSkinEditor.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index 4e5db5d46e..bd56a95809 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -4,6 +4,9 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; +using System.Text.Unicode; +using Newtonsoft.Json; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; @@ -214,7 +217,11 @@ namespace osu.Game.Tests.Visual.Gameplay }); AddStep("Press undo", () => InputManager.Keys(PlatformAction.Undo)); - AddAssert("Nothing changed", () => defaultState.SequenceEqual(changeHandler.GetCurrentState())); + + AddAssert("Nothing changed", + () => JsonConvert.DeserializeObject>(Encoding.UTF8.GetString(defaultState)), + () => Is.EqualTo(JsonConvert.DeserializeObject>(Encoding.UTF8.GetString(changeHandler.GetCurrentState()))) + ); AddStep("Add components", () => { @@ -243,7 +250,11 @@ namespace osu.Game.Tests.Visual.Gameplay void revertAndCheckUnchanged() { AddStep("Revert changes", () => changeHandler.RestoreState(int.MinValue)); - AddAssert("Current state is same as default", () => defaultState.SequenceEqual(changeHandler.GetCurrentState())); + + AddAssert("Current state is same as default", + () => JsonConvert.DeserializeObject>(Encoding.UTF8.GetString(defaultState)), + () => Is.EqualTo(JsonConvert.DeserializeObject>(Encoding.UTF8.GetString(changeHandler.GetCurrentState()))) + ); } } From 9ef34fa51a97e41f47dc8be0fc40c1495442c5fe Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Fri, 10 Nov 2023 15:02:15 +0200 Subject: [PATCH 1464/4092] FIxed stated problems --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 13 ------------- osu.Game/Rulesets/Ruleset.cs | 8 ++++++++ 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index b8324b3f2f..7fcf400409 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -428,19 +428,6 @@ namespace osu.Game.Rulesets.Mania public override DifficultySection CreateEditorDifficultySection() => new ManiaDifficultySection(); - // Mania doesn't have rate-adjusted attributes anymore? - - //public override BeatmapDifficulty GetRateAdjustedDifficulty(IBeatmapDifficultyInfo baseDifficulty, double rate) - //{ - // BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(baseDifficulty); - - // double hitwindow = 64.0 - 3 * adjustedDifficulty.OverallDifficulty; - // hitwindow /= rate; - // adjustedDifficulty.OverallDifficulty = (float)(64.0 - hitwindow) / 3; - - // return adjustedDifficulty ?? (BeatmapDifficulty)baseDifficulty; - //} - } public enum PlayfieldType diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index fbeec5ff4b..8896517a1a 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -389,6 +389,14 @@ namespace osu.Game.Rulesets /// Can be overridden to alter the difficulty section to the editor beatmap setup screen. ///
public virtual DifficultySection? CreateEditorDifficultySection() => null; + + /// + /// Changes difficulty after they're adjusted according to rate. + /// Doesn't change any attributes by default. + /// + /// Difficulty attributes that will be changed + /// Rate of the gameplay. For example 1.5 for DT. + /// Copy of difficulty info with values changed according to rate and ruleset-specific behaviour. public virtual BeatmapDifficulty GetRateAdjustedDifficulty(IBeatmapDifficultyInfo baseDifficulty, double rate) => new BeatmapDifficulty(baseDifficulty); } } From d0d334a3715d98405c9bcd58cf5d21d25895fd98 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Fri, 10 Nov 2023 15:05:26 +0200 Subject: [PATCH 1465/4092] Update Ruleset.cs --- osu.Game/Rulesets/Ruleset.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 8896517a1a..e0e0c1295b 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -391,10 +391,10 @@ namespace osu.Game.Rulesets public virtual DifficultySection? CreateEditorDifficultySection() => null; /// - /// Changes difficulty after they're adjusted according to rate. + /// Changes after they're adjusted according to rate. /// Doesn't change any attributes by default. /// - /// Difficulty attributes that will be changed + /// >The that will be adjusted. /// Rate of the gameplay. For example 1.5 for DT. /// Copy of difficulty info with values changed according to rate and ruleset-specific behaviour. public virtual BeatmapDifficulty GetRateAdjustedDifficulty(IBeatmapDifficultyInfo baseDifficulty, double rate) => new BeatmapDifficulty(baseDifficulty); From c5feefc2ad186371431c08f593d3ad8b032168a6 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Fri, 10 Nov 2023 15:06:32 +0200 Subject: [PATCH 1466/4092] Update ManiaRuleset.cs --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 7fcf400409..4507015169 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -427,7 +427,6 @@ namespace osu.Game.Rulesets.Mania public override RulesetSetupSection CreateEditorSetupSection() => new ManiaSetupSection(); public override DifficultySection CreateEditorDifficultySection() => new ManiaDifficultySection(); - } public enum PlayfieldType From 60c3e7250bbc406f7ea82c446b9c2b3e2511e384 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Fri, 10 Nov 2023 15:13:40 +0200 Subject: [PATCH 1467/4092] fixed naming incconvinence --- osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs | 12 ++++++------ osu.Game/Screens/Select/Details/AdvancedStats.cs | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index c921e3e075..74bf5e5bd3 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -266,7 +266,7 @@ namespace osu.Game.Overlays.Mods Ruleset ruleset = gameRuleset.Value.CreateInstance(); adjustedDifficulty = ruleset.GetRateAdjustedDifficulty(adjustedDifficulty, rate); - haveRateChangedValues = !isDifferentArOd(originalDifficulty, adjustedDifficulty); + haveRateChangedValues = isDifferentArOd(originalDifficulty, adjustedDifficulty); approachRateDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(originalDifficulty.ApproachRate, adjustedDifficulty.ApproachRate); overallDifficultyDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(originalDifficulty.OverallDifficulty, adjustedDifficulty.OverallDifficulty); @@ -279,13 +279,13 @@ namespace osu.Game.Overlays.Mods private bool isDifferentArOd(BeatmapDifficulty? a, BeatmapDifficulty? b) { - if (a == null && b == null) return true; - if (a == null || b == null) return false; + if (a == null && b == null) return false; + if (a == null || b == null) return true; - if (!Precision.AlmostEquals(a.ApproachRate, b.ApproachRate, 0.01)) return false; - if (!Precision.AlmostEquals(a.OverallDifficulty, b.OverallDifficulty, 0.01)) return false; + if (!Precision.AlmostEquals(a.ApproachRate, b.ApproachRate, 0.01)) return true; + if (!Precision.AlmostEquals(a.OverallDifficulty, b.OverallDifficulty, 0.01)) return true; - return true; + return false; } private void updateCollapsedState() diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 54b76c48a4..802c1901eb 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -137,7 +137,7 @@ namespace osu.Game.Screens.Select.Details rate = mod.ApplyToRate(0, rate); adjustedDifficulty = ruleset.GetRateAdjustedDifficulty(adjustedDifficulty, rate); - haveRateChangedValues = !isDifferentArOd(originalDifficulty, adjustedDifficulty); + haveRateChangedValues = isDifferentArOd(originalDifficulty, adjustedDifficulty); } switch (BeatmapInfo?.Ruleset.OnlineID) @@ -204,13 +204,13 @@ namespace osu.Game.Screens.Select.Details private bool isDifferentArOd(BeatmapDifficulty a, BeatmapDifficulty b) { - if (a == null && b == null) return true; - if (a == null || b == null) return false; + if (a == null && b == null) return false; + if (a == null || b == null) return true; - if (!Precision.AlmostEquals(a.ApproachRate, b.ApproachRate, 0.01)) return false; - if (!Precision.AlmostEquals(a.OverallDifficulty, b.OverallDifficulty, 0.01)) return false; + if (!Precision.AlmostEquals(a.ApproachRate, b.ApproachRate, 0.01)) return true; + if (!Precision.AlmostEquals(a.OverallDifficulty, b.OverallDifficulty, 0.01)) return true; - return true; + return false; } public LocalisableString TooltipText From 080f13e34d0f5f60381d813edfd590aa2b466194 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 11 Nov 2023 02:56:16 +0300 Subject: [PATCH 1468/4092] Schedule outside of `UnloadStoryboard` and fix disposal happening on update thread --- .../Backgrounds/BeatmapBackgroundWithStoryboard.cs | 13 +++++-------- osu.Game/Screens/BackgroundScreenStack.cs | 2 +- .../Screens/Backgrounds/BackgroundScreenDefault.cs | 13 +++++++++++-- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs index 1e702967b6..2bde71a6a1 100644 --- a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs @@ -71,7 +71,7 @@ namespace osu.Game.Graphics.Backgrounds }, (loadCancellationSource = new CancellationTokenSource()).Token); } - public void UnloadStoryboard(Action scheduleStoryboardRemoval) + public void UnloadStoryboard() { if (drawableStoryboard == null) return; @@ -79,15 +79,12 @@ namespace osu.Game.Graphics.Backgrounds loadCancellationSource.AsNonNull().Cancel(); loadCancellationSource = null; - DrawableStoryboard s = drawableStoryboard; - - scheduleStoryboardRemoval(() => - { - s.RemoveAndDisposeImmediately(); - Sprite.Alpha = 1f; - }); + // clear is intentionally used here for the storyboard to be disposed asynchronously. + storyboardContainer.Clear(); drawableStoryboard = null; + + Sprite.Alpha = 1f; } protected override void LoadComplete() diff --git a/osu.Game/Screens/BackgroundScreenStack.cs b/osu.Game/Screens/BackgroundScreenStack.cs index 9af6601aa4..2c7b219791 100644 --- a/osu.Game/Screens/BackgroundScreenStack.cs +++ b/osu.Game/Screens/BackgroundScreenStack.cs @@ -35,6 +35,6 @@ namespace osu.Game.Screens return true; } - internal void ScheduleToTransitionEnd(Action action) => Scheduler.AddDelayed(action, BackgroundScreen.TRANSITION_LENGTH); + internal ScheduledDelegate ScheduleUntilTransitionEnd(Action action) => Scheduler.AddDelayed(action, BackgroundScreen.TRANSITION_LENGTH); } } diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 07b1cc6df4..4583b3e4d6 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -73,13 +73,15 @@ namespace osu.Game.Screens.Backgrounds void next() => Next(); } + private ScheduledDelegate storyboardUnloadDelegate; + public override void OnSuspending(ScreenTransitionEvent e) { var backgroundScreenStack = Parent as BackgroundScreenStack; Debug.Assert(backgroundScreenStack != null); if (background is BeatmapBackgroundWithStoryboard storyboardBackground) - storyboardBackground.UnloadStoryboard(backgroundScreenStack.ScheduleToTransitionEnd); + storyboardUnloadDelegate = backgroundScreenStack.ScheduleUntilTransitionEnd(storyboardBackground.UnloadStoryboard); base.OnSuspending(e); } @@ -87,7 +89,14 @@ namespace osu.Game.Screens.Backgrounds public override void OnResuming(ScreenTransitionEvent e) { if (background is BeatmapBackgroundWithStoryboard storyboardBackground) - storyboardBackground.LoadStoryboard(); + { + if (storyboardUnloadDelegate?.Completed == false) + storyboardUnloadDelegate.Cancel(); + else + storyboardBackground.LoadStoryboard(); + + storyboardUnloadDelegate = null; + } base.OnResuming(e); } From bb912bc6161a8785f105e3da29f7e95f83261249 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 11 Nov 2023 02:57:17 +0300 Subject: [PATCH 1469/4092] Avoid spinning another load thread on initial storyboard load --- .../BeatmapBackgroundWithStoryboard.cs | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs index 2bde71a6a1..784c8e4b44 100644 --- a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs @@ -1,13 +1,11 @@ // 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.Collections.Generic; using System.Diagnostics; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; @@ -48,27 +46,37 @@ namespace osu.Game.Graphics.Backgrounds Volume = { Value = 0 }, }); - LoadStoryboard(); + LoadStoryboard(false); } - public void LoadStoryboard() + public void LoadStoryboard(bool async = true) { Debug.Assert(drawableStoryboard == null); if (!Beatmap.Storyboard.HasDrawable) return; - LoadComponentAsync(drawableStoryboard = new DrawableStoryboard(Beatmap.Storyboard, mods.Value) + drawableStoryboard = new DrawableStoryboard(Beatmap.Storyboard, mods.Value) { Clock = storyboardClock - }, s => + }; + + if (async) + LoadComponentAsync(drawableStoryboard, finishLoad, (loadCancellationSource = new CancellationTokenSource()).Token); + else + { + LoadComponent(drawableStoryboard); + finishLoad(drawableStoryboard); + } + + void finishLoad(DrawableStoryboard s) { if (Beatmap.Storyboard.ReplacesBackground) Sprite.FadeOut(BackgroundScreen.TRANSITION_LENGTH, Easing.InQuint); storyboardContainer.FadeInFromZero(BackgroundScreen.TRANSITION_LENGTH, Easing.OutQuint); storyboardContainer.Add(s); - }, (loadCancellationSource = new CancellationTokenSource()).Token); + } } public void UnloadStoryboard() @@ -76,7 +84,7 @@ namespace osu.Game.Graphics.Backgrounds if (drawableStoryboard == null) return; - loadCancellationSource.AsNonNull().Cancel(); + loadCancellationSource?.Cancel(); loadCancellationSource = null; // clear is intentionally used here for the storyboard to be disposed asynchronously. From 96da7a07bbd79bc67cbc58196bb38cd6c835c83a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 11 Nov 2023 02:57:29 +0300 Subject: [PATCH 1470/4092] Add detailed explaination on existence of `ScheduleUntilTransitionEnd` --- osu.Game/Screens/BackgroundScreenStack.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game/Screens/BackgroundScreenStack.cs b/osu.Game/Screens/BackgroundScreenStack.cs index 2c7b219791..562b212561 100644 --- a/osu.Game/Screens/BackgroundScreenStack.cs +++ b/osu.Game/Screens/BackgroundScreenStack.cs @@ -5,6 +5,8 @@ using System; using System.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.Screens; +using osu.Framework.Threading; +using osu.Game.Screens.Backgrounds; namespace osu.Game.Screens { @@ -35,6 +37,19 @@ namespace osu.Game.Screens return true; } + /// + /// Schedules a delegate to run after 500ms, the time length of a background screen transition. + /// This is used in to dispose of the storyboard once the background screen is completely off-screen. + /// + /// + /// Late storyboard disposals cannot be achieved with any local scheduler from or any component inside it, + /// due to the screen becoming dead at the moment the transition finishes. And, on the frame that it is dead on, it will not receive an , + /// therefore not guaranteeing to dispose the storyboard at any period of time close to the end of the transition. + /// This might require reconsideration framework-side, possibly exposing a "death" event in or all s in general. + /// + /// The delegate + /// + /// internal ScheduledDelegate ScheduleUntilTransitionEnd(Action action) => Scheduler.AddDelayed(action, BackgroundScreen.TRANSITION_LENGTH); } } From 064857c40b7cd18343672fdf103083ba3cb0fdba Mon Sep 17 00:00:00 2001 From: Poyo Date: Fri, 10 Nov 2023 19:57:44 -0800 Subject: [PATCH 1471/4092] Calculate unstable rate using rate-adjusted offsets --- osu.Game/Rulesets/Judgements/JudgementResult.cs | 5 +++++ .../Rulesets/Objects/Drawables/DrawableHitObject.cs | 1 + osu.Game/Rulesets/Scoring/HitEvent.cs | 11 +++++++++-- osu.Game/Rulesets/Scoring/HitEventExtensions.cs | 3 ++- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 +- osu.Game/Rulesets/UI/Playfield.cs | 2 +- osu.Game/Screens/Utility/CircleGameplay.cs | 2 +- osu.Game/Screens/Utility/ScrollingGameplay.cs | 2 +- 8 files changed, 21 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/JudgementResult.cs b/osu.Game/Rulesets/Judgements/JudgementResult.cs index c67f8b9fd5..603d470954 100644 --- a/osu.Game/Rulesets/Judgements/JudgementResult.cs +++ b/osu.Game/Rulesets/Judgements/JudgementResult.cs @@ -54,6 +54,11 @@ namespace osu.Game.Rulesets.Judgements /// public double TimeAbsolute => RawTime != null ? Math.Min(RawTime.Value, HitObject.GetEndTime() + HitObject.MaximumJudgementOffset) : HitObject.GetEndTime(); + /// + /// The gameplay rate at the time this occurred. + /// + public double GameplayRate { get; internal set; } + /// /// The combo prior to this occurring. /// diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index ce6475d3ce..0843fd5bdc 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -704,6 +704,7 @@ namespace osu.Game.Rulesets.Objects.Drawables } Result.RawTime = Time.Current; + Result.GameplayRate = Clock.Rate; if (Result.HasResult) updateState(Result.IsHit ? ArmedState.Hit : ArmedState.Miss); diff --git a/osu.Game/Rulesets/Scoring/HitEvent.cs b/osu.Game/Rulesets/Scoring/HitEvent.cs index cabbf40a7d..afa654318b 100644 --- a/osu.Game/Rulesets/Scoring/HitEvent.cs +++ b/osu.Game/Rulesets/Scoring/HitEvent.cs @@ -19,6 +19,11 @@ namespace osu.Game.Rulesets.Scoring ///
public readonly double TimeOffset; + /// + /// The true gameplay rate at the time of the event. + /// + public readonly double GameplayRate; + /// /// The hit result. /// @@ -46,12 +51,14 @@ namespace osu.Game.Rulesets.Scoring ///
/// The time offset from the end of at which the event occurs. /// The . + /// The true gameplay rate at the time of the event. /// The that triggered the event. /// The previous . /// A position corresponding to the event. - public HitEvent(double timeOffset, HitResult result, HitObject hitObject, [CanBeNull] HitObject lastHitObject, [CanBeNull] Vector2? position) + public HitEvent(double timeOffset, double gameplayRate, HitResult result, HitObject hitObject, [CanBeNull] HitObject lastHitObject, [CanBeNull] Vector2? position) { TimeOffset = timeOffset; + GameplayRate = gameplayRate; Result = result; HitObject = hitObject; LastHitObject = lastHitObject; @@ -63,6 +70,6 @@ namespace osu.Game.Rulesets.Scoring ///
/// The positional offset. /// The new . - public HitEvent With(Vector2? positionOffset) => new HitEvent(TimeOffset, Result, HitObject, LastHitObject, positionOffset); + public HitEvent With(Vector2? positionOffset) => new HitEvent(TimeOffset, GameplayRate, Result, HitObject, LastHitObject, positionOffset); } } diff --git a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs index b4bdd8a1ea..a93385ef43 100644 --- a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs +++ b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs @@ -18,7 +18,8 @@ namespace osu.Game.Rulesets.Scoring /// public static double? CalculateUnstableRate(this IEnumerable hitEvents) { - double[] timeOffsets = hitEvents.Where(affectsUnstableRate).Select(ev => ev.TimeOffset).ToArray(); + // Division by gameplay rate is to account for TimeOffset scaling with gameplay rate. + double[] timeOffsets = hitEvents.Where(affectsUnstableRate).Select(ev => ev.TimeOffset / ev.GameplayRate).ToArray(); return 10 * standardDeviation(timeOffsets); } diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 35a7dfe369..4e899479bd 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -252,7 +252,7 @@ namespace osu.Game.Rulesets.Scoring /// The to describe. /// The . protected virtual HitEvent CreateHitEvent(JudgementResult result) - => new HitEvent(result.TimeOffset, result.Type, result.HitObject, lastHitObject, null); + => new HitEvent(result.TimeOffset, result.GameplayRate, result.Type, result.HitObject, lastHitObject, null); protected sealed override void RevertResultInternal(JudgementResult result) { diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index e9c35555c8..17baf8838c 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -473,7 +473,7 @@ namespace osu.Game.Rulesets.UI private void onNewResult(DrawableHitObject drawable, JudgementResult result) { - Debug.Assert(result != null && drawable.Entry?.Result == result && result.RawTime != null); + Debug.Assert(result != null && drawable.Entry?.Result == result && result.RawTime != null && result.GameplayRate != 0.0); judgedEntries.Push(drawable.Entry.AsNonNull()); NewResult?.Invoke(drawable, result); diff --git a/osu.Game/Screens/Utility/CircleGameplay.cs b/osu.Game/Screens/Utility/CircleGameplay.cs index d97812acb4..1f970c5121 100644 --- a/osu.Game/Screens/Utility/CircleGameplay.cs +++ b/osu.Game/Screens/Utility/CircleGameplay.cs @@ -224,7 +224,7 @@ namespace osu.Game.Screens.Utility .FadeOut(duration) .ScaleTo(1.5f, duration); - HitEvent = new HitEvent(Clock.CurrentTime - HitTime, HitResult.Good, new HitObject + HitEvent = new HitEvent(Clock.CurrentTime - HitTime, 1.0, HitResult.Good, new HitObject { HitWindows = new HitWindows(), }, null, null); diff --git a/osu.Game/Screens/Utility/ScrollingGameplay.cs b/osu.Game/Screens/Utility/ScrollingGameplay.cs index f1331d8fb2..5038c53b4a 100644 --- a/osu.Game/Screens/Utility/ScrollingGameplay.cs +++ b/osu.Game/Screens/Utility/ScrollingGameplay.cs @@ -186,7 +186,7 @@ namespace osu.Game.Screens.Utility .FadeOut(duration / 2) .ScaleTo(1.5f, duration / 2); - HitEvent = new HitEvent(Clock.CurrentTime - HitTime, HitResult.Good, new HitObject + HitEvent = new HitEvent(Clock.CurrentTime - HitTime, 1.0, HitResult.Good, new HitObject { HitWindows = new HitWindows(), }, null, null); From 21e1d68b15960c448d9876baaec51776febde22c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 11 Nov 2023 15:52:24 +0900 Subject: [PATCH 1472/4092] Remove unused using directive --- osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index bd56a95809..08019c90e1 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; -using System.Text.Unicode; using Newtonsoft.Json; using NUnit.Framework; using osu.Framework.Allocation; From 2428a97d449b5e5f51ea2f9c8805c32521301a07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 11 Nov 2023 17:02:21 +0900 Subject: [PATCH 1473/4092] Fix editor not clearing undo history on skin change --- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 5affaedf1d..78c1ea2f0b 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -406,7 +406,14 @@ namespace osu.Game.Overlays.SkinEditor cp.Colour = colours.Yellow; }); + changeHandler?.Dispose(); + skins.EnsureMutableSkin(); + + var targetContainer = getTarget(selectedTarget.Value); + + if (targetContainer != null) + changeHandler = new SkinEditorChangeHandler(targetContainer); hasBegunMutating = true; } From b247b94ecbdb5405de16ba489ef7a62b36cd7656 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 11 Nov 2023 19:15:20 +0900 Subject: [PATCH 1474/4092] Revert test changes They were broken because `SerialisedDrawableInfo` is a reference type and as such equality checks will use reference equality rather than member equality. --- .../Visual/Gameplay/TestSceneSkinEditor.cs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index 08019c90e1..4e5db5d46e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -4,8 +4,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; -using Newtonsoft.Json; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; @@ -216,11 +214,7 @@ namespace osu.Game.Tests.Visual.Gameplay }); AddStep("Press undo", () => InputManager.Keys(PlatformAction.Undo)); - - AddAssert("Nothing changed", - () => JsonConvert.DeserializeObject>(Encoding.UTF8.GetString(defaultState)), - () => Is.EqualTo(JsonConvert.DeserializeObject>(Encoding.UTF8.GetString(changeHandler.GetCurrentState()))) - ); + AddAssert("Nothing changed", () => defaultState.SequenceEqual(changeHandler.GetCurrentState())); AddStep("Add components", () => { @@ -249,11 +243,7 @@ namespace osu.Game.Tests.Visual.Gameplay void revertAndCheckUnchanged() { AddStep("Revert changes", () => changeHandler.RestoreState(int.MinValue)); - - AddAssert("Current state is same as default", - () => JsonConvert.DeserializeObject>(Encoding.UTF8.GetString(defaultState)), - () => Is.EqualTo(JsonConvert.DeserializeObject>(Encoding.UTF8.GetString(changeHandler.GetCurrentState()))) - ); + AddAssert("Current state is same as default", () => defaultState.SequenceEqual(changeHandler.GetCurrentState())); } } From 61d336521da4adc3c8dcc6bacc20a7f23a306d34 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 11 Nov 2023 19:48:56 +0900 Subject: [PATCH 1475/4092] 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 2870696c03..15553510cb 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 f1159f58b9..ef54dd06b4 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From b0aa4a4257a5f590a1b65613cbf988b7034d7a9e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 11 Nov 2023 20:34:35 +0900 Subject: [PATCH 1476/4092] Add "export" item to skin editor menu --- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 78c1ea2f0b..38eed55241 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -8,6 +8,7 @@ using System.IO; using System.Linq; using System.Threading.Tasks; using Newtonsoft.Json; +using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -153,6 +154,8 @@ namespace osu.Game.Overlays.SkinEditor Items = new[] { new EditorMenuItem(Web.CommonStrings.ButtonsSave, MenuItemType.Standard, () => Save()), + new EditorMenuItem(CommonStrings.Export, MenuItemType.Standard, () => skins.ExportCurrentSkin()) { Action = { Disabled = !RuntimeInfo.IsDesktop } }, + new EditorMenuItemSpacer(), new EditorMenuItem(CommonStrings.RevertToDefault, MenuItemType.Destructive, () => dialogOverlay?.Push(new RevertConfirmDialog(revert))), new EditorMenuItemSpacer(), new EditorMenuItem(CommonStrings.Exit, MenuItemType.Standard, () => skinEditorOverlay?.Hide()), From 870e4ce27e751d2a7129d5fe396f2f96e74865bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 11 Nov 2023 20:42:45 +0900 Subject: [PATCH 1477/4092] Fix argon health display not handling invalidation correctly --- .../Screens/Play/HUD/ArgonHealthDisplay.cs | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index b56920c99a..f4ce7d1633 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -89,6 +89,13 @@ namespace osu.Game.Screens.Play.HUD public const float MAIN_PATH_RADIUS = 10f; + private readonly LayoutValue drawSizeLayout = new LayoutValue(Invalidation.DrawSize); + + public ArgonHealthDisplay() + { + AddLayout(drawSizeLayout); + } + [BackgroundDependencyLoader] private void load() { @@ -134,22 +141,11 @@ namespace osu.Game.Screens.Play.HUD Current.BindValueChanged(_ => Scheduler.AddOnce(updateCurrent), true); - UseRelativeSize.BindValueChanged(v => - { - RelativeSizeAxes = v.NewValue ? Axes.X : Axes.None; - }, true); + UseRelativeSize.BindValueChanged(v => RelativeSizeAxes = v.NewValue ? Axes.X : Axes.None, true); BarHeight.BindValueChanged(_ => updatePath(), true); } - protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source) - { - if ((invalidation & Invalidation.DrawSize) > 0) - updatePath(); - - return base.OnInvalidate(invalidation, source); - } - private void updateCurrent() { if (Current.Value >= GlowBarValue) finishMissDisplay(); @@ -165,6 +161,12 @@ namespace osu.Game.Screens.Play.HUD { base.Update(); + if (!drawSizeLayout.IsValid) + { + updatePath(); + drawSizeLayout.Validate(); + } + mainBar.Alpha = (float)Interpolation.DampContinuously(mainBar.Alpha, Current.Value > 0 ? 1 : 0, 40, Time.Elapsed); glowBar.Alpha = (float)Interpolation.DampContinuously(glowBar.Alpha, GlowBarValue > 0 ? 1 : 0, 40, Time.Elapsed); } From ee56b4d205f916ea9cf45543c72cce34f53b8a7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 11 Nov 2023 20:55:47 +0900 Subject: [PATCH 1478/4092] Use barely better assertion fail message in test --- osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index 4e5db5d46e..92f28288ca 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; @@ -243,7 +244,9 @@ namespace osu.Game.Tests.Visual.Gameplay void revertAndCheckUnchanged() { AddStep("Revert changes", () => changeHandler.RestoreState(int.MinValue)); - AddAssert("Current state is same as default", () => defaultState.SequenceEqual(changeHandler.GetCurrentState())); + AddAssert("Current state is same as default", + () => Encoding.UTF8.GetString(defaultState), + () => Is.EqualTo(Encoding.UTF8.GetString(changeHandler.GetCurrentState()))); } } From 50789d2e18a43dd3b2a2e99ea9487851972f35f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 11 Nov 2023 21:28:04 +0900 Subject: [PATCH 1479/4092] Fix song progress bars not sizing vertically properly --- .../Screens/Play/HUD/ArgonSongProgress.cs | 66 +++++++++++-------- .../Screens/Play/HUD/DefaultSongProgress.cs | 49 ++++++++------ 2 files changed, 65 insertions(+), 50 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs index be2ce3b272..cb38854bca 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs @@ -19,6 +19,7 @@ namespace osu.Game.Screens.Play.HUD private readonly ArgonSongProgressGraph graph; private readonly ArgonSongProgressBar bar; private readonly Container graphContainer; + private readonly Container content; private const float bar_height = 10; @@ -30,43 +31,50 @@ namespace osu.Game.Screens.Play.HUD public ArgonSongProgress() { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Anchor = Anchor.BottomCentre; Origin = Anchor.BottomCentre; Masking = true; CornerRadius = 5; - Children = new Drawable[] + + Child = content = new Container { - info = new SongProgressInfo + RelativeSizeAxes = Axes.X, + Children = new Drawable[] { - Origin = Anchor.TopLeft, - Name = "Info", - Anchor = Anchor.TopLeft, - RelativeSizeAxes = Axes.X, - ShowProgress = false - }, - bar = new ArgonSongProgressBar(bar_height) - { - Name = "Seek bar", - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - OnSeek = time => player?.Seek(time), - }, - graphContainer = new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Masking = true, - CornerRadius = 5, - Child = graph = new ArgonSongProgressGraph + info = new SongProgressInfo { - Name = "Difficulty graph", - RelativeSizeAxes = Axes.Both, - Blending = BlendingParameters.Additive + Origin = Anchor.TopLeft, + Name = "Info", + Anchor = Anchor.TopLeft, + RelativeSizeAxes = Axes.X, + ShowProgress = false }, - RelativeSizeAxes = Axes.X, - }, + bar = new ArgonSongProgressBar(bar_height) + { + Name = "Seek bar", + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + OnSeek = time => player?.Seek(time), + }, + graphContainer = new Container + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Masking = true, + CornerRadius = 5, + Child = graph = new ArgonSongProgressGraph + { + Name = "Difficulty graph", + RelativeSizeAxes = Axes.Both, + Blending = BlendingParameters.Additive + }, + RelativeSizeAxes = Axes.X, + }, + } }; - RelativeSizeAxes = Axes.X; } [BackgroundDependencyLoader] @@ -100,7 +108,7 @@ namespace osu.Game.Screens.Play.HUD protected override void Update() { base.Update(); - Height = bar.Height + bar_height + info.Height; + content.Height = bar.Height + bar_height + info.Height; graphContainer.Height = bar.Height; } diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs index 202ead2d66..48809796f3 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Graphics; @@ -27,6 +28,7 @@ namespace osu.Game.Screens.Play.HUD private readonly DefaultSongProgressBar bar; private readonly DefaultSongProgressGraph graph; private readonly SongProgressInfo info; + private readonly Container content; [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowGraph), nameof(SongProgressStrings.ShowGraphDescription))] public Bindable ShowGraph { get; } = new BindableBool(true); @@ -37,31 +39,36 @@ namespace osu.Game.Screens.Play.HUD public DefaultSongProgress() { RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; Anchor = Anchor.BottomRight; Origin = Anchor.BottomRight; - Children = new Drawable[] + Child = content = new Container { - info = new SongProgressInfo + RelativeSizeAxes = Axes.X, + Children = new Drawable[] { - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - }, - graph = new DefaultSongProgressGraph - { - RelativeSizeAxes = Axes.X, - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - Height = graph_height, - Margin = new MarginPadding { Bottom = bottom_bar_height }, - }, - bar = new DefaultSongProgressBar(bottom_bar_height, graph_height, handle_size) - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - OnSeek = time => player?.Seek(time), - }, + info = new SongProgressInfo + { + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + }, + graph = new DefaultSongProgressGraph + { + RelativeSizeAxes = Axes.X, + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Height = graph_height, + Margin = new MarginPadding { Bottom = bottom_bar_height }, + }, + bar = new DefaultSongProgressBar(bottom_bar_height, graph_height, handle_size) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + OnSeek = time => player?.Seek(time), + }, + } }; } @@ -107,7 +114,7 @@ namespace osu.Game.Screens.Play.HUD float newHeight = bottom_bar_height + graph_height + handle_size.Y + info.Height - graph.Y; if (!Precision.AlmostEquals(Height, newHeight, 5f)) - Height = newHeight; + content.Height = newHeight; } private void updateBarVisibility() From 926636cc035a17543d987ba9a9ba9c47fa5e7f7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Mu=CC=88ller-Ho=CC=88hne?= Date: Wed, 8 Nov 2023 19:43:54 +0900 Subject: [PATCH 1480/4092] Generalize Bezier curves to BSplines of Nth degree --- .../TestSceneJuiceStreamSelectionBlueprint.cs | 8 +- .../JuiceStreamPathTest.cs | 4 +- .../Mods/TestSceneCatchModPerfect.cs | 2 +- .../Mods/TestSceneCatchModRelax.cs | 2 +- .../TestSceneAutoJuiceStream.cs | 2 +- .../TestSceneCatchModHidden.cs | 2 +- .../TestSceneDrawableHitObjects.cs | 2 +- .../TestSceneJuiceStream.cs | 2 +- .../Blueprints/Components/EditablePath.cs | 2 +- .../Objects/JuiceStreamPath.cs | 2 +- .../Checks/CheckOffscreenObjectsTest.cs | 12 +-- .../Editor/TestSceneObjectMerging.cs | 10 +-- .../TestSceneOsuEditorSelectInvalidPath.cs | 2 +- .../TestScenePathControlPointVisualiser.cs | 36 ++++----- .../TestSceneSliderControlPointPiece.cs | 28 +++---- .../Editor/TestSceneSliderLengthValidity.cs | 8 +- .../TestSceneSliderPlacementBlueprint.cs | 42 +++++------ .../Editor/TestSceneSliderReversal.cs | 4 +- .../TestSceneSliderSelectionBlueprint.cs | 2 +- .../Editor/TestSceneSliderSnapping.cs | 10 +-- .../Editor/TestSceneSliderSplitting.cs | 28 +++---- .../Editor/TestSliderScaling.cs | 2 +- .../Mods/TestSceneOsuModHidden.cs | 10 +-- .../Mods/TestSceneOsuModPerfect.cs | 2 +- .../OsuHitObjectGenerationUtilsTest.cs | 6 +- .../TestSceneHitCircleLateFade.cs | 2 +- .../TestSceneLegacyHitPolicy.cs | 18 ++--- .../TestSceneSlider.cs | 14 ++-- .../TestSceneSliderApplication.cs | 6 +- .../TestSceneSliderFollowCircleInput.cs | 2 +- .../TestSceneSliderInput.cs | 8 +- .../TestSceneSliderSnaking.cs | 6 +- .../TestSceneStartTimeOrderedHitPolicy.cs | 10 +-- .../Components/PathControlPointPiece.cs | 20 +++-- .../Components/PathControlPointVisualiser.cs | 26 +++---- .../Sliders/SliderPlacementBlueprint.cs | 14 ++-- .../Edit/OsuSelectionHandler.cs | 4 +- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 2 +- .../Formats/LegacyBeatmapDecoderTest.cs | 36 ++++----- .../Formats/LegacyBeatmapEncoderTest.cs | 6 +- .../Editing/LegacyEditorBeatmapPatcherTest.cs | 4 +- .../Editing/TestSceneEditorClipboard.cs | 2 +- .../Editing/TestSceneHitObjectComposer.cs | 2 +- .../Gameplay/TestSceneBezierConverter.cs | 40 +++++----- .../TestSceneGameplaySampleTriggerSource.cs | 2 +- .../Visual/Gameplay/TestSceneSliderPath.cs | 73 +++++++++++-------- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 15 ++-- osu.Game/Database/LegacyBeatmapExporter.cs | 4 +- osu.Game/Rulesets/Objects/BezierConverter.cs | 32 ++++---- .../Objects/Legacy/ConvertHitObjectParser.cs | 20 ++--- osu.Game/Rulesets/Objects/SliderPath.cs | 13 ++-- .../Rulesets/Objects/SliderPathExtensions.cs | 6 +- osu.Game/Rulesets/Objects/Types/PathType.cs | 50 ++++++++++++- .../Screens/Play/HUD/ArgonHealthDisplay.cs | 10 +-- 54 files changed, 372 insertions(+), 305 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs index 05d7a38a95..16b51d414a 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs @@ -140,7 +140,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor AddStep("update hit object path", () => { - hitObject.Path = new SliderPath(PathType.PerfectCurve, new[] + hitObject.Path = new SliderPath(PathType.PERFECTCURVE, new[] { Vector2.Zero, new Vector2(100, 100), @@ -190,16 +190,16 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor [Test] public void TestVertexResampling() { - addBlueprintStep(100, 100, new SliderPath(PathType.PerfectCurve, new[] + addBlueprintStep(100, 100, new SliderPath(PathType.PERFECTCURVE, new[] { Vector2.Zero, new Vector2(100, 100), new Vector2(50, 200), }), 0.5); AddAssert("1 vertex per 1 nested HO", () => getVertices().Count == hitObject.NestedHitObjects.Count); - AddAssert("slider path not yet changed", () => hitObject.Path.ControlPoints[0].Type == PathType.PerfectCurve); + AddAssert("slider path not yet changed", () => hitObject.Path.ControlPoints[0].Type == PathType.PERFECTCURVE); addAddVertexSteps(150, 150); - AddAssert("slider path change to linear", () => hitObject.Path.ControlPoints[0].Type == PathType.Linear); + AddAssert("slider path change to linear", () => hitObject.Path.ControlPoints[0].Type == PathType.LINEAR); } private void addBlueprintStep(double time, float x, SliderPath sliderPath, double velocity) => AddStep("add selection blueprint", () => diff --git a/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs b/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs index 95b4fdc07e..82f24633b5 100644 --- a/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs @@ -154,7 +154,7 @@ namespace osu.Game.Rulesets.Catch.Tests } while (rng.Next(2) != 0); int length = sliderPath.ControlPoints.Count - start + 1; - sliderPath.ControlPoints[start].Type = length <= 2 ? PathType.Linear : length == 3 ? PathType.PerfectCurve : PathType.Bezier; + sliderPath.ControlPoints[start].Type = length <= 2 ? PathType.LINEAR : length == 3 ? PathType.PERFECTCURVE : PathType.BEZIER; } while (rng.Next(3) != 0); if (rng.Next(5) == 0) @@ -215,7 +215,7 @@ namespace osu.Game.Rulesets.Catch.Tests foreach (var point in sliderPath.ControlPoints) { - Assert.That(point.Type, Is.EqualTo(PathType.Linear).Or.Null); + Assert.That(point.Type, Is.EqualTo(PathType.LINEAR).Or.Null); Assert.That(sliderStartY + point.Position.Y, Is.InRange(0, JuiceStreamPath.OSU_PLAYFIELD_HEIGHT)); } diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs index 71df523951..45e7d7aa28 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods var stream = new JuiceStream { StartTime = 1000, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(100, 0), diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs index 5835ccaf78..a161615579 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods { X = CatchPlayfield.CENTER_X, StartTime = 3000, - Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, Vector2.UnitY * 200 }) + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, Vector2.UnitY * 200 }) } } } diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs index 3261fb656e..202f010680 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Catch.Tests beatmap.HitObjects.Add(new JuiceStream { X = CatchPlayfield.CENTER_X - width / 2, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(width, 0) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs index a44575a46e..419a846ec3 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Catch.Tests new JuiceStream { StartTime = 1000, - Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(0, -192) }), + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(0, -192) }), X = CatchPlayfield.WIDTH / 2 } } diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs index 11d6419507..9c5cd68201 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs @@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Catch.Tests { X = xCoords, StartTime = playfieldTime + 1000, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(0, 200) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs index c31a7ca99f..9a923adaab 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Catch.Tests new JuiceStream { X = CatchPlayfield.CENTER_X, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(0, 100) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs index df76bf0a8c..86f92d16ca 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs @@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components path.ConvertFromSliderPath(sliderPath, hitObject.Velocity); // If the original slider path has non-linear type segments, resample the vertices at nested hit object times to reduce the number of vertices. - if (sliderPath.ControlPoints.Any(p => p.Type != null && p.Type != PathType.Linear)) + if (sliderPath.ControlPoints.Any(p => p.Type != null && p.Type != PathType.LINEAR)) { path.ResampleVertices(hitObject.NestedHitObjects .Skip(1).TakeWhile(h => !(h is Fruit)) // Only droplets in the first span are used. diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStreamPath.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStreamPath.cs index 0633151ddd..57acf7cee2 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStreamPath.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStreamPath.cs @@ -236,7 +236,7 @@ namespace osu.Game.Rulesets.Catch.Objects for (int i = 1; i < vertices.Count; i++) { - sliderPath.ControlPoints[^1].Type = PathType.Linear; + sliderPath.ControlPoints[^1].Type = PathType.LINEAR; float deltaX = vertices[i].X - lastPosition.X; double length = (vertices[i].Time - currentTime) * velocity; diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs index a72aaa966c..8612a8eb57 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs @@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks Position = new Vector2(420, 240), Path = new SliderPath(new[] { - new PathControlPoint(new Vector2(0, 0), PathType.Linear), + new PathControlPoint(new Vector2(0, 0), PathType.LINEAR), new PathControlPoint(new Vector2(-100, 0)) }), } @@ -128,7 +128,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks Position = playfield_centre, Path = new SliderPath(new[] { - new PathControlPoint(new Vector2(0, 0), PathType.Linear), + new PathControlPoint(new Vector2(0, 0), PathType.LINEAR), new PathControlPoint(new Vector2(0, -playfield_centre.Y + 5)) }), } @@ -149,7 +149,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks Position = playfield_centre, Path = new SliderPath(new[] { - new PathControlPoint(new Vector2(0, 0), PathType.Linear), + new PathControlPoint(new Vector2(0, 0), PathType.LINEAR), new PathControlPoint(new Vector2(0, -playfield_centre.Y + 5)) }), StackHeight = 5 @@ -171,7 +171,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks Position = new Vector2(0, 0), Path = new SliderPath(new[] { - new PathControlPoint(new Vector2(0, 0), PathType.Linear), + new PathControlPoint(new Vector2(0, 0), PathType.LINEAR), new PathControlPoint(playfield_centre) }), } @@ -192,7 +192,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks Position = playfield_centre, Path = new SliderPath(new[] { - new PathControlPoint(new Vector2(0, 0), PathType.Linear), + new PathControlPoint(new Vector2(0, 0), PathType.LINEAR), new PathControlPoint(-playfield_centre) }), } @@ -214,7 +214,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks Path = new SliderPath(new[] { // Circular arc shoots over the top of the screen. - new PathControlPoint(new Vector2(0, 0), PathType.PerfectCurve), + new PathControlPoint(new Vector2(0, 0), PathType.PERFECTCURVE), new PathControlPoint(new Vector2(-100, -200)), new PathControlPoint(new Vector2(100, -200)) }), diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs index 8d8386cae1..3d35ab79f7 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor mergeSelection(); AddAssert("slider created", () => circle1 is not null && circle2 is not null && sliderCreatedFor( - (pos: circle1.Position, pathType: PathType.Linear), + (pos: circle1.Position, pathType: PathType.LINEAR), (pos: circle2.Position, pathType: null))); AddStep("undo", () => Editor.Undo()); @@ -73,11 +73,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor var controlPoints = slider.Path.ControlPoints; (Vector2, PathType?)[] args = new (Vector2, PathType?)[controlPoints.Count + 2]; - args[0] = (circle1.Position, PathType.Linear); + args[0] = (circle1.Position, PathType.LINEAR); for (int i = 0; i < controlPoints.Count; i++) { - args[i + 1] = (controlPoints[i].Position + slider.Position, i == controlPoints.Count - 1 ? PathType.Linear : controlPoints[i].Type); + args[i + 1] = (controlPoints[i].Position + slider.Position, i == controlPoints.Count - 1 ? PathType.LINEAR : controlPoints[i].Type); } args[^1] = (circle2.Position, null); @@ -172,7 +172,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor mergeSelection(); AddAssert("slider created", () => circle1 is not null && circle2 is not null && sliderCreatedFor( - (pos: circle1.Position, pathType: PathType.Linear), + (pos: circle1.Position, pathType: PathType.LINEAR), (pos: circle2.Position, pathType: null))); AddAssert("samples exist", sliderSampleExist); @@ -227,7 +227,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor mergeSelection(); AddAssert("slider created", () => circle1 is not null && circle2 is not null && sliderCreatedFor( - (pos: circle1.Position, pathType: PathType.Linear), + (pos: circle1.Position, pathType: PathType.LINEAR), (pos: circle2.Position, pathType: null))); } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorSelectInvalidPath.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorSelectInvalidPath.cs index 37a109de18..7ea4d40b90 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorSelectInvalidPath.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorSelectInvalidPath.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor PathControlPoint[] points = { - new PathControlPoint(new Vector2(0), PathType.PerfectCurve), + new PathControlPoint(new Vector2(0), PathType.PERFECTCURVE), new PathControlPoint(new Vector2(-100, 0)), new PathControlPoint(new Vector2(100, 20)) }; diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs index c267cd1f63..16800997f4 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { createVisualiser(true); - addControlPointStep(new Vector2(200), PathType.Bezier); + addControlPointStep(new Vector2(200), PathType.BEZIER); addControlPointStep(new Vector2(300)); addControlPointStep(new Vector2(500, 300)); addControlPointStep(new Vector2(700, 200)); @@ -63,9 +63,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("select control point", () => visualiser.Pieces[1].IsSelected.Value = true); addContextMenuItemStep("Perfect curve"); - assertControlPointPathType(0, PathType.Bezier); - assertControlPointPathType(1, PathType.PerfectCurve); - assertControlPointPathType(3, PathType.Bezier); + assertControlPointPathType(0, PathType.BEZIER); + assertControlPointPathType(1, PathType.PERFECTCURVE); + assertControlPointPathType(3, PathType.BEZIER); } [Test] @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { createVisualiser(true); - addControlPointStep(new Vector2(200), PathType.Bezier); + addControlPointStep(new Vector2(200), PathType.BEZIER); addControlPointStep(new Vector2(300)); addControlPointStep(new Vector2(500, 300)); addControlPointStep(new Vector2(700, 200)); @@ -83,8 +83,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("select control point", () => visualiser.Pieces[2].IsSelected.Value = true); addContextMenuItemStep("Perfect curve"); - assertControlPointPathType(0, PathType.Bezier); - assertControlPointPathType(2, PathType.PerfectCurve); + assertControlPointPathType(0, PathType.BEZIER); + assertControlPointPathType(2, PathType.PERFECTCURVE); assertControlPointPathType(4, null); } @@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { createVisualiser(true); - addControlPointStep(new Vector2(200), PathType.Bezier); + addControlPointStep(new Vector2(200), PathType.BEZIER); addControlPointStep(new Vector2(300)); addControlPointStep(new Vector2(500, 300)); addControlPointStep(new Vector2(700, 200)); @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("select control point", () => visualiser.Pieces[3].IsSelected.Value = true); addContextMenuItemStep("Perfect curve"); - assertControlPointPathType(0, PathType.Bezier); + assertControlPointPathType(0, PathType.BEZIER); AddAssert("point 3 is not inherited", () => slider.Path.ControlPoints[3].Type != null); } @@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { createVisualiser(true); - addControlPointStep(new Vector2(200), PathType.Linear); + addControlPointStep(new Vector2(200), PathType.LINEAR); addControlPointStep(new Vector2(300)); addControlPointStep(new Vector2(500, 300)); addControlPointStep(new Vector2(700, 200)); @@ -123,9 +123,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("select control point", () => visualiser.Pieces[1].IsSelected.Value = true); addContextMenuItemStep("Perfect curve"); - assertControlPointPathType(0, PathType.Linear); - assertControlPointPathType(1, PathType.PerfectCurve); - assertControlPointPathType(3, PathType.Linear); + assertControlPointPathType(0, PathType.LINEAR); + assertControlPointPathType(1, PathType.PERFECTCURVE); + assertControlPointPathType(3, PathType.LINEAR); } [Test] @@ -133,18 +133,18 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { createVisualiser(true); - addControlPointStep(new Vector2(200), PathType.Bezier); - addControlPointStep(new Vector2(300), PathType.PerfectCurve); + addControlPointStep(new Vector2(200), PathType.BEZIER); + addControlPointStep(new Vector2(300), PathType.PERFECTCURVE); addControlPointStep(new Vector2(500, 300)); - addControlPointStep(new Vector2(700, 200), PathType.Bezier); + addControlPointStep(new Vector2(700, 200), PathType.BEZIER); addControlPointStep(new Vector2(500, 100)); moveMouseToControlPoint(3); AddStep("select control point", () => visualiser.Pieces[3].IsSelected.Value = true); addContextMenuItemStep("Inherit"); - assertControlPointPathType(0, PathType.Bezier); - assertControlPointPathType(1, PathType.Bezier); + assertControlPointPathType(0, PathType.BEZIER); + assertControlPointPathType(1, PathType.BEZIER); assertControlPointPathType(3, null); } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs index 408205d6b2..1d8d2cf01a 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs @@ -38,9 +38,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor Position = new Vector2(256, 192), Path = new SliderPath(new[] { - new PathControlPoint(Vector2.Zero, PathType.PerfectCurve), + new PathControlPoint(Vector2.Zero, PathType.PERFECTCURVE), new PathControlPoint(new Vector2(150, 150)), - new PathControlPoint(new Vector2(300, 0), PathType.PerfectCurve), + new PathControlPoint(new Vector2(300, 0), PathType.PERFECTCURVE), new PathControlPoint(new Vector2(400, 0)), new PathControlPoint(new Vector2(400, 150)) }) @@ -182,7 +182,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); assertControlPointPosition(1, new Vector2(150, 50)); - assertControlPointType(0, PathType.PerfectCurve); + assertControlPointType(0, PathType.PERFECTCURVE); } [Test] @@ -210,7 +210,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("three control point pieces selected", () => this.ChildrenOfType>().Count(piece => piece.IsSelected.Value) == 3); assertControlPointPosition(2, new Vector2(450, 50)); - assertControlPointType(2, PathType.PerfectCurve); + assertControlPointType(2, PathType.PERFECTCURVE); assertControlPointPosition(3, new Vector2(550, 50)); @@ -249,7 +249,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("slider moved", () => Precision.AlmostEquals(slider.Position, new Vector2(256, 192) + new Vector2(150, 50))); assertControlPointPosition(0, Vector2.Zero); - assertControlPointType(0, PathType.PerfectCurve); + assertControlPointType(0, PathType.PERFECTCURVE); assertControlPointPosition(1, new Vector2(0, 100)); @@ -272,7 +272,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); assertControlPointPosition(1, new Vector2(400, 0.01f)); - assertControlPointType(0, PathType.Bezier); + assertControlPointType(0, PathType.BEZIER); } [Test] @@ -282,13 +282,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("hold", () => InputManager.PressButton(MouseButton.Left)); addMovementStep(new Vector2(400, 0.01f)); - assertControlPointType(0, PathType.Bezier); + assertControlPointType(0, PathType.BEZIER); addMovementStep(new Vector2(150, 50)); AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); assertControlPointPosition(1, new Vector2(150, 50)); - assertControlPointType(0, PathType.PerfectCurve); + assertControlPointType(0, PathType.PERFECTCURVE); } [Test] @@ -298,32 +298,32 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("hold", () => InputManager.PressButton(MouseButton.Left)); addMovementStep(new Vector2(350, 0.01f)); - assertControlPointType(2, PathType.Bezier); + assertControlPointType(2, PathType.BEZIER); addMovementStep(new Vector2(150, 150)); AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); assertControlPointPosition(4, new Vector2(150, 150)); - assertControlPointType(2, PathType.PerfectCurve); + assertControlPointType(2, PathType.PERFECTCURVE); } [Test] public void TestDragControlPointPathAfterChangingType() { - AddStep("change type to bezier", () => slider.Path.ControlPoints[2].Type = PathType.Bezier); + AddStep("change type to bezier", () => slider.Path.ControlPoints[2].Type = PathType.BEZIER); AddStep("add point", () => slider.Path.ControlPoints.Add(new PathControlPoint(new Vector2(500, 10)))); - AddStep("change type to perfect", () => slider.Path.ControlPoints[3].Type = PathType.PerfectCurve); + AddStep("change type to perfect", () => slider.Path.ControlPoints[3].Type = PathType.PERFECTCURVE); moveMouseToControlPoint(4); AddStep("hold", () => InputManager.PressButton(MouseButton.Left)); - assertControlPointType(3, PathType.PerfectCurve); + assertControlPointType(3, PathType.PERFECTCURVE); addMovementStep(new Vector2(350, 0.01f)); AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); assertControlPointPosition(4, new Vector2(350, 0.01f)); - assertControlPointType(3, PathType.Bezier); + assertControlPointType(3, PathType.BEZIER); } private void addMovementStep(Vector2 relativePosition) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderLengthValidity.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderLengthValidity.cs index 77e828e80a..38ebeb7e8f 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderLengthValidity.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderLengthValidity.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor PathControlPoint[] points = { - new PathControlPoint(new Vector2(0), PathType.Linear), + new PathControlPoint(new Vector2(0), PathType.LINEAR), new PathControlPoint(new Vector2(100, 0)), }; @@ -82,7 +82,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor PathControlPoint[] points = { - new PathControlPoint(new Vector2(0), PathType.Linear), + new PathControlPoint(new Vector2(0), PathType.LINEAR), new PathControlPoint(new Vector2(100, 0)), }; @@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor PathControlPoint[] points = { - new PathControlPoint(new Vector2(0), PathType.PerfectCurve), + new PathControlPoint(new Vector2(0), PathType.PERFECTCURVE), new PathControlPoint(new Vector2(100, 0)), new PathControlPoint(new Vector2(0, 10)) }; @@ -165,7 +165,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor PathControlPoint[] points = { - new PathControlPoint(new Vector2(0), PathType.Linear), + new PathControlPoint(new Vector2(0), PathType.LINEAR), new PathControlPoint(new Vector2(0, 50)), new PathControlPoint(new Vector2(0, 100)) }; diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index 7d29670daa..4b120c1a3f 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertLength(200); assertControlPointCount(2); - assertControlPointType(0, PathType.Linear); + assertControlPointType(0, PathType.LINEAR); } [Test] @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(2); - assertControlPointType(0, PathType.Linear); + assertControlPointType(0, PathType.LINEAR); } [Test] @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(3); assertControlPointPosition(1, new Vector2(100, 0)); - assertControlPointType(0, PathType.PerfectCurve); + assertControlPointType(0, PathType.PERFECTCURVE); } [Test] @@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertControlPointCount(4); assertControlPointPosition(1, new Vector2(100, 0)); assertControlPointPosition(2, new Vector2(100, 100)); - assertControlPointType(0, PathType.Bezier); + assertControlPointType(0, PathType.BEZIER); } [Test] @@ -131,8 +131,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(3); assertControlPointPosition(1, new Vector2(100, 0)); - assertControlPointType(0, PathType.Linear); - assertControlPointType(1, PathType.Linear); + assertControlPointType(0, PathType.LINEAR); + assertControlPointType(1, PathType.LINEAR); } [Test] @@ -150,7 +150,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(2); - assertControlPointType(0, PathType.Linear); + assertControlPointType(0, PathType.LINEAR); assertLength(100); } @@ -172,7 +172,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(3); - assertControlPointType(0, PathType.PerfectCurve); + assertControlPointType(0, PathType.PERFECTCURVE); } [Test] @@ -196,7 +196,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(4); - assertControlPointType(0, PathType.Bezier); + assertControlPointType(0, PathType.BEZIER); } [Test] @@ -216,8 +216,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertControlPointCount(3); assertControlPointPosition(1, new Vector2(100, 0)); assertControlPointPosition(2, new Vector2(100)); - assertControlPointType(0, PathType.Linear); - assertControlPointType(1, PathType.Linear); + assertControlPointType(0, PathType.LINEAR); + assertControlPointType(1, PathType.LINEAR); } [Test] @@ -240,8 +240,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertControlPointCount(4); assertControlPointPosition(1, new Vector2(100, 0)); assertControlPointPosition(2, new Vector2(100)); - assertControlPointType(0, PathType.Linear); - assertControlPointType(1, PathType.PerfectCurve); + assertControlPointType(0, PathType.LINEAR); + assertControlPointType(1, PathType.PERFECTCURVE); } [Test] @@ -269,8 +269,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertControlPointPosition(2, new Vector2(100)); assertControlPointPosition(3, new Vector2(200, 100)); assertControlPointPosition(4, new Vector2(200)); - assertControlPointType(0, PathType.PerfectCurve); - assertControlPointType(2, PathType.PerfectCurve); + assertControlPointType(0, PathType.PERFECTCURVE); + assertControlPointType(2, PathType.PERFECTCURVE); } [Test] @@ -287,7 +287,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertLength(200); assertControlPointCount(2); - assertControlPointType(0, PathType.Linear); + assertControlPointType(0, PathType.LINEAR); } [Test] @@ -306,7 +306,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(3); - assertControlPointType(0, PathType.Bezier); + assertControlPointType(0, PathType.BEZIER); } [Test] @@ -326,7 +326,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(3); - assertControlPointType(0, PathType.PerfectCurve); + assertControlPointType(0, PathType.PERFECTCURVE); } [Test] @@ -347,7 +347,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(3); - assertControlPointType(0, PathType.PerfectCurve); + assertControlPointType(0, PathType.PERFECTCURVE); } [Test] @@ -368,7 +368,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(3); - assertControlPointType(0, PathType.Bezier); + assertControlPointType(0, PathType.BEZIER); } [Test] @@ -385,7 +385,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(3); - assertControlPointType(0, PathType.PerfectCurve); + assertControlPointType(0, PathType.PERFECTCURVE); } private void addMovementStep(Vector2 position) => AddStep($"move mouse to {position}", () => InputManager.MoveMouseTo(InputManager.ToScreenSpace(position))); diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderReversal.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderReversal.cs index 9c5eb83e3c..0ddfc40946 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderReversal.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderReversal.cs @@ -22,12 +22,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private readonly PathControlPoint[][] paths = { createPathSegment( - PathType.PerfectCurve, + PathType.PERFECTCURVE, new Vector2(200, -50), new Vector2(250, 0) ), createPathSegment( - PathType.Linear, + PathType.LINEAR, new Vector2(100, 0), new Vector2(100, 100) ) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs index 413a3c3dfd..d4d99e1019 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor slider = new Slider { Position = new Vector2(256, 192), - Path = new SliderPath(PathType.Bezier, new[] + Path = new SliderPath(PathType.BEZIER, new[] { Vector2.Zero, new Vector2(150, 150), diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs index 0ae14bdde8..c984d9168e 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { ControlPoints = { - new PathControlPoint(Vector2.Zero, PathType.PerfectCurve), + new PathControlPoint(Vector2.Zero, PathType.PERFECTCURVE), new PathControlPoint(new Vector2(136, 205)), new PathControlPoint(new Vector2(-4, 226)) } @@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { OsuSelectionHandler selectionHandler; - AddAssert("first control point perfect", () => slider.Path.ControlPoints[0].Type == PathType.PerfectCurve); + AddAssert("first control point perfect", () => slider.Path.ControlPoints[0].Type == PathType.PERFECTCURVE); AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider)); AddStep("rotate 90 degrees ccw", () => @@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor selectionHandler.HandleRotation(-90); }); - AddAssert("first control point still perfect", () => slider.Path.ControlPoints[0].Type == PathType.PerfectCurve); + AddAssert("first control point still perfect", () => slider.Path.ControlPoints[0].Type == PathType.PERFECTCURVE); } [Test] @@ -223,7 +223,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { OsuSelectionHandler selectionHandler; - AddAssert("first control point perfect", () => slider.Path.ControlPoints[0].Type == PathType.PerfectCurve); + AddAssert("first control point perfect", () => slider.Path.ControlPoints[0].Type == PathType.PERFECTCURVE); AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider)); AddStep("flip slider horizontally", () => @@ -232,7 +232,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor selectionHandler.OnPressed(new KeyBindingPressEvent(InputManager.CurrentState, GlobalAction.EditorFlipVertically)); }); - AddAssert("first control point still perfect", () => slider.Path.ControlPoints[0].Type == PathType.PerfectCurve); + AddAssert("first control point still perfect", () => slider.Path.ControlPoints[0].Type == PathType.PERFECTCURVE); } [Test] diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs index ad37258c9b..cded9165f4 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs @@ -45,9 +45,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor Position = new Vector2(0, 50), Path = new SliderPath(new[] { - new PathControlPoint(Vector2.Zero, PathType.PerfectCurve), + new PathControlPoint(Vector2.Zero, PathType.PERFECTCURVE), new PathControlPoint(new Vector2(150, 150)), - new PathControlPoint(new Vector2(300, 0), PathType.PerfectCurve), + new PathControlPoint(new Vector2(300, 0), PathType.PERFECTCURVE), new PathControlPoint(new Vector2(400, 0)), new PathControlPoint(new Vector2(400, 150)) }) @@ -73,20 +73,20 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("slider split", () => slider is not null && EditorBeatmap.HitObjects.Count == 2 && sliderCreatedFor((Slider)EditorBeatmap.HitObjects[0], 0, EditorBeatmap.HitObjects[1].StartTime - split_gap, - (new Vector2(0, 50), PathType.PerfectCurve), + (new Vector2(0, 50), PathType.PERFECTCURVE), (new Vector2(150, 200), null), (new Vector2(300, 50), null) ) && sliderCreatedFor((Slider)EditorBeatmap.HitObjects[1], slider.StartTime, endTime + split_gap, - (new Vector2(300, 50), PathType.PerfectCurve), + (new Vector2(300, 50), PathType.PERFECTCURVE), (new Vector2(400, 50), null), (new Vector2(400, 200), null) )); AddStep("undo", () => Editor.Undo()); AddAssert("original slider restored", () => EditorBeatmap.HitObjects.Count == 1 && sliderCreatedFor((Slider)EditorBeatmap.HitObjects[0], 0, endTime, - (new Vector2(0, 50), PathType.PerfectCurve), + (new Vector2(0, 50), PathType.PERFECTCURVE), (new Vector2(150, 200), null), - (new Vector2(300, 50), PathType.PerfectCurve), + (new Vector2(300, 50), PathType.PERFECTCURVE), (new Vector2(400, 50), null), (new Vector2(400, 200), null) )); @@ -104,11 +104,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor Position = new Vector2(0, 50), Path = new SliderPath(new[] { - new PathControlPoint(Vector2.Zero, PathType.PerfectCurve), + new PathControlPoint(Vector2.Zero, PathType.PERFECTCURVE), new PathControlPoint(new Vector2(150, 150)), - new PathControlPoint(new Vector2(300, 0), PathType.Bezier), + new PathControlPoint(new Vector2(300, 0), PathType.BEZIER), new PathControlPoint(new Vector2(400, 0)), - new PathControlPoint(new Vector2(400, 150), PathType.Catmull), + new PathControlPoint(new Vector2(400, 150), PathType.CATMULL), new PathControlPoint(new Vector2(300, 200)), new PathControlPoint(new Vector2(400, 250)) }) @@ -139,15 +139,15 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("slider split", () => slider is not null && EditorBeatmap.HitObjects.Count == 3 && sliderCreatedFor((Slider)EditorBeatmap.HitObjects[0], 0, EditorBeatmap.HitObjects[1].StartTime - split_gap, - (new Vector2(0, 50), PathType.PerfectCurve), + (new Vector2(0, 50), PathType.PERFECTCURVE), (new Vector2(150, 200), null), (new Vector2(300, 50), null) ) && sliderCreatedFor((Slider)EditorBeatmap.HitObjects[1], EditorBeatmap.HitObjects[0].GetEndTime() + split_gap, slider.StartTime - split_gap, - (new Vector2(300, 50), PathType.Bezier), + (new Vector2(300, 50), PathType.BEZIER), (new Vector2(400, 50), null), (new Vector2(400, 200), null) ) && sliderCreatedFor((Slider)EditorBeatmap.HitObjects[2], EditorBeatmap.HitObjects[1].GetEndTime() + split_gap, endTime + split_gap * 2, - (new Vector2(400, 200), PathType.Catmull), + (new Vector2(400, 200), PathType.CATMULL), (new Vector2(300, 250), null), (new Vector2(400, 300), null) )); @@ -165,9 +165,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor Position = new Vector2(0, 50), Path = new SliderPath(new[] { - new PathControlPoint(Vector2.Zero, PathType.PerfectCurve), + new PathControlPoint(Vector2.Zero, PathType.PERFECTCURVE), new PathControlPoint(new Vector2(150, 150)), - new PathControlPoint(new Vector2(300, 0), PathType.PerfectCurve), + new PathControlPoint(new Vector2(300, 0), PathType.PERFECTCURVE), new PathControlPoint(new Vector2(400, 0)), new PathControlPoint(new Vector2(400, 150)) }) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs index 64d23090d0..021fdba225 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor PathControlPoint[] points = { - new PathControlPoint(new Vector2(0), PathType.Linear), + new PathControlPoint(new Vector2(0), PathType.LINEAR), new PathControlPoint(new Vector2(100, 0)), }; diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs index 3f84ac6935..58bdd805c1 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs @@ -81,12 +81,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods new Slider { StartTime = 3200, - Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(100, 0), }) + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(100, 0), }) }, new Slider { StartTime = 5200, - Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(100, 0), }) + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(100, 0), }) } } }, @@ -105,12 +105,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods new Slider { StartTime = 1000, - Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(100, 0), }) + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(100, 0), }) }, new Slider { StartTime = 4000, - Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(100, 0), }) + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(100, 0), }) }, } }, @@ -140,7 +140,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { StartTime = 3000, Position = new Vector2(156, 242), - Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(200, 0), }) + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(200, 0), }) }, new Spinner { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs index f0496efc19..26c4133bc4 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods var slider = new Slider { StartTime = 1000, - Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(100, 0), }) + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(100, 0), }) }; CreateHitObjectTest(new HitObjectTestData(slider), shouldMiss); diff --git a/osu.Game.Rulesets.Osu.Tests/OsuHitObjectGenerationUtilsTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuHitObjectGenerationUtilsTest.cs index daa914cac2..d78c32aa6a 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuHitObjectGenerationUtilsTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuHitObjectGenerationUtilsTest.cs @@ -26,9 +26,9 @@ namespace osu.Game.Rulesets.Osu.Tests { ControlPoints = { - new PathControlPoint(new Vector2(), PathType.Linear), - new PathControlPoint(new Vector2(-64, -128), PathType.Linear), // absolute position: (64, 0) - new PathControlPoint(new Vector2(-128, 0), PathType.Linear) // absolute position: (0, 128) + new PathControlPoint(new Vector2(), PathType.LINEAR), + new PathControlPoint(new Vector2(-64, -128), PathType.LINEAR), // absolute position: (64, 0) + new PathControlPoint(new Vector2(-128, 0), PathType.LINEAR) // absolute position: (0, 128) } }, RepeatCount = 1 diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs index 483155e646..7824f26251 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs @@ -167,7 +167,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = Time.Current + 500, Position = new Vector2(250), - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(0, 100), diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyHitPolicy.cs index fa6aa580a3..e460da9bd5 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyHitPolicy.cs @@ -264,7 +264,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = time_slider, Position = positionSlider, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(50, 0), @@ -308,7 +308,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = time_slider, Position = positionSlider, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(50, 0), @@ -391,7 +391,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = time_slider, Position = positionSlider, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(25, 0), @@ -428,7 +428,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = time_first_slider, Position = positionFirstSlider, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(25, 0), @@ -438,7 +438,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = time_second_slider, Position = positionSecondSlider, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(25, 0), @@ -521,7 +521,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = time_first_slider, Position = positionFirstSlider, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(25, 0), @@ -531,7 +531,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = time_second_slider, Position = positionSecondSlider, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(25, 0), @@ -571,7 +571,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = time_first_slider, Position = positionFirstSlider, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(25, 0), @@ -581,7 +581,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = time_second_slider, Position = positionSecondSlider, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(25, 0), diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index b805e7ed63..60003e7950 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -219,7 +219,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = Time.Current + time_offset, Position = new Vector2(239, 176), - Path = new SliderPath(PathType.PerfectCurve, new[] + Path = new SliderPath(PathType.PERFECTCURVE, new[] { Vector2.Zero, new Vector2(154, 28), @@ -255,7 +255,7 @@ namespace osu.Game.Rulesets.Osu.Tests SliderVelocityMultiplier = speedMultiplier, StartTime = Time.Current + time_offset, Position = new Vector2(0, -(distance / 2)), - Path = new SliderPath(PathType.PerfectCurve, new[] + Path = new SliderPath(PathType.PERFECTCURVE, new[] { Vector2.Zero, new Vector2(0, distance), @@ -273,7 +273,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = Time.Current + time_offset, Position = new Vector2(-max_length / 2, 0), - Path = new SliderPath(PathType.PerfectCurve, new[] + Path = new SliderPath(PathType.PERFECTCURVE, new[] { Vector2.Zero, new Vector2(max_length / 2, max_length / 2), @@ -293,7 +293,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = Time.Current + time_offset, Position = new Vector2(-max_length / 2, 0), - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(max_length * 0.375f, max_length * 0.18f), @@ -316,7 +316,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = Time.Current + time_offset, Position = new Vector2(-max_length / 2, 0), - Path = new SliderPath(PathType.Bezier, new[] + Path = new SliderPath(PathType.BEZIER, new[] { Vector2.Zero, new Vector2(max_length * 0.375f, max_length * 0.18f), @@ -338,7 +338,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = Time.Current + time_offset, Position = new Vector2(0, 0), - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(-max_length / 2, 0), @@ -365,7 +365,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = Time.Current + time_offset, Position = new Vector2(-max_length / 4, 0), - Path = new SliderPath(PathType.Catmull, new[] + Path = new SliderPath(PathType.CATMULL, new[] { Vector2.Zero, new Vector2(max_length * 0.125f, max_length * 0.125f), diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs index 88b70a8836..f41dd913ab 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Tests Position = new Vector2(256, 192), IndexInCurrentCombo = 0, StartTime = Time.Current, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(150, 100), @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Osu.Tests Position = new Vector2(256, 192), ComboIndex = 1, StartTime = dho.HitObject.StartTime, - Path = new SliderPath(PathType.Bezier, new[] + Path = new SliderPath(PathType.BEZIER, new[] { Vector2.Zero, new Vector2(150, 100), @@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.Osu.Tests Position = new Vector2(256, 192), IndexInCurrentCombo = 0, StartTime = Time.Current, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(150, 100), diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderFollowCircleInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderFollowCircleInput.cs index d4bb789a12..fc9bb16cb7 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderFollowCircleInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderFollowCircleInput.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Tests StartTime = time_slider_start, Position = new Vector2(0, 0), SliderVelocityMultiplier = velocity, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(followCircleRadius, 0), diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index f718a5069f..08836ef819 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.Tests Position = new Vector2(0, 0), SliderVelocityMultiplier = 10f, RepeatCount = repeatCount, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(sliderLength, 0), @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Tests Position = new Vector2(0, 0), SliderVelocityMultiplier = 10f, RepeatCount = repeatCount, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(sliderLength, 0), @@ -145,7 +145,7 @@ namespace osu.Game.Rulesets.Osu.Tests StartTime = time_slider_start, Position = new Vector2(0, 0), SliderVelocityMultiplier = 10f, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(slider_path_length * 10, 0), @@ -478,7 +478,7 @@ namespace osu.Game.Rulesets.Osu.Tests StartTime = time_slider_start, Position = new Vector2(0, 0), SliderVelocityMultiplier = 0.1f, - Path = new SliderPath(PathType.PerfectCurve, new[] + Path = new SliderPath(PathType.PERFECTCURVE, new[] { Vector2.Zero, new Vector2(slider_path_length, 0), diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 13166c2b6b..ebc5143aed 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -217,7 +217,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = 3000, Position = new Vector2(100, 100), - Path = new SliderPath(PathType.PerfectCurve, new[] + Path = new SliderPath(PathType.PERFECTCURVE, new[] { Vector2.Zero, new Vector2(300, 200) @@ -227,7 +227,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = 13000, Position = new Vector2(100, 100), - Path = new SliderPath(PathType.PerfectCurve, new[] + Path = new SliderPath(PathType.PERFECTCURVE, new[] { Vector2.Zero, new Vector2(300, 200) @@ -238,7 +238,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = 23000, Position = new Vector2(100, 100), - Path = new SliderPath(PathType.PerfectCurve, new[] + Path = new SliderPath(PathType.PERFECTCURVE, new[] { Vector2.Zero, new Vector2(300, 200) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs index 3475680c71..895e9bbdee 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs @@ -196,7 +196,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = time_slider, Position = positionSlider, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(25, 0), @@ -238,7 +238,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = time_slider, Position = positionSlider, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(25, 0), @@ -318,7 +318,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = time_slider, Position = positionSlider, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(25, 0), @@ -352,7 +352,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = time_first_slider, Position = positionFirstSlider, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(25, 0), @@ -362,7 +362,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = time_second_slider, Position = positionSecondSlider, - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(25, 0), diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 12e5ca0236..9658e5f6c3 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -221,11 +221,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components ///
private void updatePathType() { - if (ControlPoint.Type != PathType.PerfectCurve) + if (ControlPoint.Type != PathType.PERFECTCURVE) return; if (PointsInSegment.Count > 3) - ControlPoint.Type = PathType.Bezier; + ControlPoint.Type = PathType.BEZIER; if (PointsInSegment.Count != 3) return; @@ -233,7 +233,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components ReadOnlySpan points = PointsInSegment.Select(p => p.Position).ToArray(); RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(points); if (boundingBox.Width >= 640 || boundingBox.Height >= 480) - ControlPoint.Type = PathType.Bezier; + ControlPoint.Type = PathType.BEZIER; } /// @@ -256,18 +256,22 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private Color4 getColourFromNodeType() { - if (!(ControlPoint.Type is PathType pathType)) + if (ControlPoint.Type is not PathType pathType) return colours.Yellow; switch (pathType) { - case PathType.Catmull: + case { SplineType: SplineType.Catmull }: return colours.SeaFoam; - case PathType.Bezier: - return colours.Pink; + case { SplineType: SplineType.BSpline, Degree: null }: + return colours.PinkLighter; - case PathType.PerfectCurve: + case { SplineType: SplineType.BSpline, Degree: >= 1 }: + int idx = Math.Clamp(pathType.Degree.Value, 0, 3); + return new[] { colours.PinkDarker, colours.PinkDark, colours.Pink, colours.PinkLight }[idx]; + + case { SplineType: SplineType.PerfectCurve }: return colours.PurpleDark; default: diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index f891d23bbd..b5c9016538 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -242,18 +242,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { int indexInSegment = piece.PointsInSegment.IndexOf(piece.ControlPoint); - switch (type) + if (type.HasValue && type.Value.SplineType == SplineType.PerfectCurve) { - case PathType.PerfectCurve: - // Can't always create a circular arc out of 4 or more points, - // so we split the segment into one 3-point circular arc segment - // and one segment of the previous type. - int thirdPointIndex = indexInSegment + 2; + // Can't always create a circular arc out of 4 or more points, + // so we split the segment into one 3-point circular arc segment + // and one segment of the previous type. + int thirdPointIndex = indexInSegment + 2; - if (piece.PointsInSegment.Count > thirdPointIndex + 1) - piece.PointsInSegment[thirdPointIndex].Type = piece.PointsInSegment[0].Type; - - break; + if (piece.PointsInSegment.Count > thirdPointIndex + 1) + piece.PointsInSegment[thirdPointIndex].Type = piece.PointsInSegment[0].Type; } hitObject.Path.ExpectedDistance.Value = null; @@ -370,10 +367,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components curveTypeItems.Add(createMenuItemForPathType(null)); // todo: hide/disable items which aren't valid for selected points - curveTypeItems.Add(createMenuItemForPathType(PathType.Linear)); - curveTypeItems.Add(createMenuItemForPathType(PathType.PerfectCurve)); - curveTypeItems.Add(createMenuItemForPathType(PathType.Bezier)); - curveTypeItems.Add(createMenuItemForPathType(PathType.Catmull)); + curveTypeItems.Add(createMenuItemForPathType(PathType.LINEAR)); + curveTypeItems.Add(createMenuItemForPathType(PathType.PERFECTCURVE)); + curveTypeItems.Add(createMenuItemForPathType(PathType.BEZIER)); + curveTypeItems.Add(createMenuItemForPathType(PathType.BSpline(3))); + curveTypeItems.Add(createMenuItemForPathType(PathType.CATMULL)); var menuItems = new List { diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 9b6adc04cf..8f0a2ee781 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { RelativeSizeAxes = Axes.Both; - HitObject.Path.ControlPoints.Add(segmentStart = new PathControlPoint(Vector2.Zero, PathType.Linear)); + HitObject.Path.ControlPoints.Add(segmentStart = new PathControlPoint(Vector2.Zero, PathType.LINEAR)); currentSegmentLength = 1; } @@ -128,7 +128,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders Debug.Assert(lastPoint != null); segmentStart = lastPoint; - segmentStart.Type = PathType.Linear; + segmentStart.Type = PathType.LINEAR; currentSegmentLength = 1; } @@ -173,15 +173,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { case 1: case 2: - segmentStart.Type = PathType.Linear; + segmentStart.Type = PathType.LINEAR; break; case 3: - segmentStart.Type = PathType.PerfectCurve; + segmentStart.Type = PathType.PERFECTCURVE; break; default: - segmentStart.Type = PathType.Bezier; + segmentStart.Type = PathType.BEZIER; break; } } @@ -195,7 +195,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { HitObject.Path.ControlPoints.Add(cursor = new PathControlPoint { Position = Vector2.Zero }); - // The path type should be adjusted in the progression of updatePathType() (Linear -> PC -> Bezier). + // The path type should be adjusted in the progression of updatePathType() (LINEAR -> PC -> BEZIER). currentSegmentLength++; updatePathType(); } @@ -210,7 +210,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders HitObject.Path.ControlPoints.Remove(cursor); cursor = null; - // The path type should be adjusted in the reverse progression of updatePathType() (Bezier -> PC -> Linear). + // The path type should be adjusted in the reverse progression of updatePathType() (BEZIER -> PC -> LINEAR). currentSegmentLength--; updatePathType(); } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index e81941d254..b972f09136 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -320,7 +320,7 @@ namespace osu.Game.Rulesets.Osu.Edit if (mergedHitObject.Path.ControlPoints.Count == 0) { - mergedHitObject.Path.ControlPoints.Add(new PathControlPoint(Vector2.Zero, PathType.Linear)); + mergedHitObject.Path.ControlPoints.Add(new PathControlPoint(Vector2.Zero, PathType.LINEAR)); } // Merge all the selected hit objects into one slider path. @@ -350,7 +350,7 @@ namespace osu.Game.Rulesets.Osu.Edit // Turn the last control point into a linear type if this is the first merging circle in a sequence, so the subsequent control points can be inherited path type. if (!lastCircle) { - mergedHitObject.Path.ControlPoints.Last().Type = PathType.Linear; + mergedHitObject.Path.ControlPoints.Last().Type = PathType.LINEAR; } mergedHitObject.Path.ControlPoints.Add(new PathControlPoint(selectedMergeableObject.Position - mergedHitObject.Position)); diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 5f47d486e6..2a76782a08 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Taiko.Objects double IHasDistance.Distance => Duration * Velocity; SliderPath IHasPath.Path - => new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(1) }, ((IHasDistance)this).Distance / LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER); + => new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(1) }, ((IHasDistance)this).Distance / LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER); #endregion } diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 66151a51e6..18c21046fb 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -663,7 +663,7 @@ namespace osu.Game.Tests.Beatmaps.Formats assertObjectHasBanks(hitObjects[9], HitSampleInfo.BANK_DRUM, HitSampleInfo.BANK_NORMAL); } - void assertObjectHasBanks(HitObject hitObject, string normalBank, string? additionsBank = null) + static void assertObjectHasBanks(HitObject hitObject, string normalBank, string? additionsBank = null) { Assert.AreEqual(normalBank, hitObject.Samples[0].Bank); @@ -808,14 +808,14 @@ namespace osu.Game.Tests.Beatmaps.Formats var first = ((IHasPath)decoded.HitObjects[0]).Path; Assert.That(first.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero)); - Assert.That(first.ControlPoints[0].Type, Is.EqualTo(PathType.PerfectCurve)); + Assert.That(first.ControlPoints[0].Type, Is.EqualTo(PathType.PERFECTCURVE)); Assert.That(first.ControlPoints[1].Position, Is.EqualTo(new Vector2(161, -244))); Assert.That(first.ControlPoints[1].Type, Is.EqualTo(null)); // ReSharper disable once HeuristicUnreachableCode // weird one, see https://youtrack.jetbrains.com/issue/RIDER-70159. Assert.That(first.ControlPoints[2].Position, Is.EqualTo(new Vector2(376, -3))); - Assert.That(first.ControlPoints[2].Type, Is.EqualTo(PathType.Bezier)); + Assert.That(first.ControlPoints[2].Type, Is.EqualTo(PathType.BEZIER)); Assert.That(first.ControlPoints[3].Position, Is.EqualTo(new Vector2(68, 15))); Assert.That(first.ControlPoints[3].Type, Is.EqualTo(null)); Assert.That(first.ControlPoints[4].Position, Is.EqualTo(new Vector2(259, -132))); @@ -827,7 +827,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var second = ((IHasPath)decoded.HitObjects[1]).Path; Assert.That(second.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero)); - Assert.That(second.ControlPoints[0].Type, Is.EqualTo(PathType.PerfectCurve)); + Assert.That(second.ControlPoints[0].Type, Is.EqualTo(PathType.PERFECTCURVE)); Assert.That(second.ControlPoints[1].Position, Is.EqualTo(new Vector2(161, -244))); Assert.That(second.ControlPoints[1].Type, Is.EqualTo(null)); Assert.That(second.ControlPoints[2].Position, Is.EqualTo(new Vector2(376, -3))); @@ -837,14 +837,14 @@ namespace osu.Game.Tests.Beatmaps.Formats var third = ((IHasPath)decoded.HitObjects[2]).Path; Assert.That(third.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero)); - Assert.That(third.ControlPoints[0].Type, Is.EqualTo(PathType.Bezier)); + Assert.That(third.ControlPoints[0].Type, Is.EqualTo(PathType.BEZIER)); Assert.That(third.ControlPoints[1].Position, Is.EqualTo(new Vector2(0, 192))); Assert.That(third.ControlPoints[1].Type, Is.EqualTo(null)); Assert.That(third.ControlPoints[2].Position, Is.EqualTo(new Vector2(224, 192))); Assert.That(third.ControlPoints[2].Type, Is.EqualTo(null)); Assert.That(third.ControlPoints[3].Position, Is.EqualTo(new Vector2(224, 0))); - Assert.That(third.ControlPoints[3].Type, Is.EqualTo(PathType.Bezier)); + Assert.That(third.ControlPoints[3].Type, Is.EqualTo(PathType.BEZIER)); Assert.That(third.ControlPoints[4].Position, Is.EqualTo(new Vector2(224, -192))); Assert.That(third.ControlPoints[4].Type, Is.EqualTo(null)); Assert.That(third.ControlPoints[5].Position, Is.EqualTo(new Vector2(480, -192))); @@ -856,7 +856,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var fourth = ((IHasPath)decoded.HitObjects[3]).Path; Assert.That(fourth.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero)); - Assert.That(fourth.ControlPoints[0].Type, Is.EqualTo(PathType.Bezier)); + Assert.That(fourth.ControlPoints[0].Type, Is.EqualTo(PathType.BEZIER)); Assert.That(fourth.ControlPoints[1].Position, Is.EqualTo(new Vector2(1, 1))); Assert.That(fourth.ControlPoints[1].Type, Is.EqualTo(null)); Assert.That(fourth.ControlPoints[2].Position, Is.EqualTo(new Vector2(2, 2))); @@ -870,7 +870,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var fifth = ((IHasPath)decoded.HitObjects[4]).Path; Assert.That(fifth.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero)); - Assert.That(fifth.ControlPoints[0].Type, Is.EqualTo(PathType.Bezier)); + Assert.That(fifth.ControlPoints[0].Type, Is.EqualTo(PathType.BEZIER)); Assert.That(fifth.ControlPoints[1].Position, Is.EqualTo(new Vector2(1, 1))); Assert.That(fifth.ControlPoints[1].Type, Is.EqualTo(null)); Assert.That(fifth.ControlPoints[2].Position, Is.EqualTo(new Vector2(2, 2))); @@ -881,7 +881,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(fifth.ControlPoints[4].Type, Is.EqualTo(null)); Assert.That(fifth.ControlPoints[5].Position, Is.EqualTo(new Vector2(4, 4))); - Assert.That(fifth.ControlPoints[5].Type, Is.EqualTo(PathType.Bezier)); + Assert.That(fifth.ControlPoints[5].Type, Is.EqualTo(PathType.BEZIER)); Assert.That(fifth.ControlPoints[6].Position, Is.EqualTo(new Vector2(5, 5))); Assert.That(fifth.ControlPoints[6].Type, Is.EqualTo(null)); @@ -889,12 +889,12 @@ namespace osu.Game.Tests.Beatmaps.Formats var sixth = ((IHasPath)decoded.HitObjects[5]).Path; Assert.That(sixth.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero)); - Assert.That(sixth.ControlPoints[0].Type == PathType.Bezier); + Assert.That(sixth.ControlPoints[0].Type == PathType.BEZIER); Assert.That(sixth.ControlPoints[1].Position, Is.EqualTo(new Vector2(75, 145))); Assert.That(sixth.ControlPoints[1].Type == null); Assert.That(sixth.ControlPoints[2].Position, Is.EqualTo(new Vector2(170, 75))); - Assert.That(sixth.ControlPoints[2].Type == PathType.Bezier); + Assert.That(sixth.ControlPoints[2].Type == PathType.BEZIER); Assert.That(sixth.ControlPoints[3].Position, Is.EqualTo(new Vector2(300, 145))); Assert.That(sixth.ControlPoints[3].Type == null); Assert.That(sixth.ControlPoints[4].Position, Is.EqualTo(new Vector2(410, 20))); @@ -904,12 +904,12 @@ namespace osu.Game.Tests.Beatmaps.Formats var seventh = ((IHasPath)decoded.HitObjects[6]).Path; Assert.That(seventh.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero)); - Assert.That(seventh.ControlPoints[0].Type == PathType.PerfectCurve); + Assert.That(seventh.ControlPoints[0].Type == PathType.PERFECTCURVE); Assert.That(seventh.ControlPoints[1].Position, Is.EqualTo(new Vector2(75, 145))); Assert.That(seventh.ControlPoints[1].Type == null); Assert.That(seventh.ControlPoints[2].Position, Is.EqualTo(new Vector2(170, 75))); - Assert.That(seventh.ControlPoints[2].Type == PathType.PerfectCurve); + Assert.That(seventh.ControlPoints[2].Type == PathType.PERFECTCURVE); Assert.That(seventh.ControlPoints[3].Position, Is.EqualTo(new Vector2(300, 145))); Assert.That(seventh.ControlPoints[3].Type == null); Assert.That(seventh.ControlPoints[4].Position, Is.EqualTo(new Vector2(410, 20))); @@ -1016,7 +1016,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var controlPoints = ((IHasPath)decoded.HitObjects[0]).Path.ControlPoints; Assert.That(controlPoints.Count, Is.EqualTo(6)); - Assert.That(controlPoints.Single(c => c.Type != null).Type, Is.EqualTo(PathType.Catmull)); + Assert.That(controlPoints.Single(c => c.Type != null).Type, Is.EqualTo(PathType.CATMULL)); } } @@ -1032,9 +1032,9 @@ namespace osu.Game.Tests.Beatmaps.Formats var controlPoints = ((IHasPath)decoded.HitObjects[0]).Path.ControlPoints; Assert.That(controlPoints.Count, Is.EqualTo(4)); - Assert.That(controlPoints[0].Type, Is.EqualTo(PathType.Catmull)); - Assert.That(controlPoints[1].Type, Is.EqualTo(PathType.Catmull)); - Assert.That(controlPoints[2].Type, Is.EqualTo(PathType.Catmull)); + Assert.That(controlPoints[0].Type, Is.EqualTo(PathType.CATMULL)); + Assert.That(controlPoints[1].Type, Is.EqualTo(PathType.CATMULL)); + Assert.That(controlPoints[2].Type, Is.EqualTo(PathType.CATMULL)); Assert.That(controlPoints[3].Type, Is.Null); } } @@ -1051,7 +1051,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var controlPoints = ((IHasPath)decoded.HitObjects[0]).Path.ControlPoints; Assert.That(controlPoints.Count, Is.EqualTo(4)); - Assert.That(controlPoints[0].Type, Is.EqualTo(PathType.Catmull)); + Assert.That(controlPoints[0].Type, Is.EqualTo(PathType.CATMULL)); Assert.That(controlPoints[0].Position, Is.EqualTo(Vector2.Zero)); Assert.That(controlPoints[1].Type, Is.Null); Assert.That(controlPoints[1].Position, Is.Not.EqualTo(Vector2.Zero)); diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index 5d9049ead7..db50273f27 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Beatmaps.Formats compareBeatmaps(decoded, decodedAfterEncode); - ControlPointInfo removeLegacyControlPointTypes(ControlPointInfo controlPointInfo) + static ControlPointInfo removeLegacyControlPointTypes(ControlPointInfo controlPointInfo) { // emulate non-legacy control points by cloning the non-legacy portion. // the assertion is that the encoder can recreate this losslessly from hitobject data. @@ -125,10 +125,10 @@ namespace osu.Game.Tests.Beatmaps.Formats Position = new Vector2(0.6f), Path = new SliderPath(new[] { - new PathControlPoint(Vector2.Zero, PathType.Bezier), + new PathControlPoint(Vector2.Zero, PathType.BEZIER), new PathControlPoint(new Vector2(0.5f)), new PathControlPoint(new Vector2(0.51f)), // This is actually on the same position as the previous one in legacy beatmaps (truncated to int). - new PathControlPoint(new Vector2(1f), PathType.Bezier), + new PathControlPoint(new Vector2(1f), PathType.BEZIER), new PathControlPoint(new Vector2(2f)) }) }, diff --git a/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs b/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs index 5af0366e6e..21d8a165ff 100644 --- a/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs +++ b/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs @@ -162,7 +162,7 @@ namespace osu.Game.Tests.Editing { new PathControlPoint(Vector2.Zero), new PathControlPoint(Vector2.One), - new PathControlPoint(new Vector2(2), PathType.Bezier), + new PathControlPoint(new Vector2(2), PathType.BEZIER), new PathControlPoint(new Vector2(3)), }, 50) }, @@ -179,7 +179,7 @@ namespace osu.Game.Tests.Editing StartTime = 2000, Path = new SliderPath(new[] { - new PathControlPoint(Vector2.Zero, PathType.Bezier), + new PathControlPoint(Vector2.Zero, PathType.BEZIER), new PathControlPoint(new Vector2(4)), new PathControlPoint(new Vector2(5)), }, 100) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs index c4c05278b5..a766b253aa 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs @@ -72,7 +72,7 @@ namespace osu.Game.Tests.Visual.Editing ControlPoints = { new PathControlPoint(), - new PathControlPoint(new Vector2(100, 0), PathType.Bezier) + new PathControlPoint(new Vector2(100, 0), PathType.BEZIER) } } }; diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs index ed3bffe5c2..f392841ac7 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Editing new Slider { Position = new Vector2(128, 256), - Path = new SliderPath(PathType.Linear, new[] + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(216, 0), diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBezierConverter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBezierConverter.cs index a40eab5948..5eb82ccbdc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBezierConverter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBezierConverter.cs @@ -114,23 +114,25 @@ namespace osu.Game.Tests.Visual.Gameplay { } - [TestCase(PathType.Linear)] - [TestCase(PathType.Bezier)] - [TestCase(PathType.Catmull)] - [TestCase(PathType.PerfectCurve)] - public void TestSingleSegment(PathType type) - => AddStep("create path", () => path.ControlPoints.AddRange(createSegment(type, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + [TestCase(SplineType.Linear, null)] + [TestCase(SplineType.BSpline, null)] + [TestCase(SplineType.BSpline, 3)] + [TestCase(SplineType.Catmull, null)] + [TestCase(SplineType.PerfectCurve, null)] + public void TestSingleSegment(SplineType splineType, int? degree) + => AddStep("create path", () => path.ControlPoints.AddRange(createSegment(new PathType { SplineType = splineType, Degree = degree }, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); - [TestCase(PathType.Linear)] - [TestCase(PathType.Bezier)] - [TestCase(PathType.Catmull)] - [TestCase(PathType.PerfectCurve)] - public void TestMultipleSegment(PathType type) + [TestCase(SplineType.Linear, null)] + [TestCase(SplineType.BSpline, null)] + [TestCase(SplineType.BSpline, 3)] + [TestCase(SplineType.Catmull, null)] + [TestCase(SplineType.PerfectCurve, null)] + public void TestMultipleSegment(SplineType splineType, int? degree) { AddStep("create path", () => { - path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero)); - path.ControlPoints.AddRange(createSegment(type, new Vector2(0, 100), new Vector2(100), Vector2.Zero)); + path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero)); + path.ControlPoints.AddRange(createSegment(new PathType { SplineType = splineType, Degree = degree }, new Vector2(0, 100), new Vector2(100), Vector2.Zero)); }); } @@ -139,9 +141,9 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("create path", () => { - path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(100, 0))); - path.ControlPoints.AddRange(createSegment(PathType.Bezier, new Vector2(100, 0), new Vector2(150, 30), new Vector2(100, 100))); - path.ControlPoints.AddRange(createSegment(PathType.PerfectCurve, new Vector2(100, 100), new Vector2(25, 50), Vector2.Zero)); + path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(100, 0))); + path.ControlPoints.AddRange(createSegment(PathType.BEZIER, new Vector2(100, 0), new Vector2(150, 30), new Vector2(100, 100))); + path.ControlPoints.AddRange(createSegment(PathType.PERFECTCURVE, new Vector2(100, 100), new Vector2(25, 50), Vector2.Zero)); }); } @@ -157,7 +159,7 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("create path", () => { - path.ControlPoints.AddRange(createSegment(PathType.PerfectCurve, Vector2.Zero, new Vector2(width / 2, height), new Vector2(width, 0))); + path.ControlPoints.AddRange(createSegment(PathType.PERFECTCURVE, Vector2.Zero, new Vector2(width / 2, height), new Vector2(width, 0))); }); } @@ -170,11 +172,11 @@ namespace osu.Game.Tests.Visual.Gameplay switch (points) { case 2: - path.ControlPoints.AddRange(createSegment(PathType.PerfectCurve, Vector2.Zero, new Vector2(0, 100))); + path.ControlPoints.AddRange(createSegment(PathType.PERFECTCURVE, Vector2.Zero, new Vector2(0, 100))); break; case 4: - path.ControlPoints.AddRange(createSegment(PathType.PerfectCurve, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); + path.ControlPoints.AddRange(createSegment(PathType.PERFECTCURVE, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); break; } }); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs index 0f16d3f394..3cbd5eefac 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs @@ -88,7 +88,7 @@ namespace osu.Game.Tests.Visual.Gameplay { HitWindows = new HitWindows(), StartTime = t += spacing, - Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, Vector2.UnitY * 200 }), + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, Vector2.UnitY * 200 }), Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, HitSampleInfo.BANK_SOFT) }, }, }); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs index 635d9f9604..e4d99f6741 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs @@ -52,59 +52,68 @@ namespace osu.Game.Tests.Visual.Gameplay { } - [TestCase(PathType.Linear)] - [TestCase(PathType.Bezier)] - [TestCase(PathType.Catmull)] - [TestCase(PathType.PerfectCurve)] - public void TestSingleSegment(PathType type) - => AddStep("create path", () => path.ControlPoints.AddRange(createSegment(type, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + [TestCase(SplineType.Linear, null)] + [TestCase(SplineType.BSpline, null)] + [TestCase(SplineType.BSpline, 3)] + [TestCase(SplineType.Catmull, null)] + [TestCase(SplineType.PerfectCurve, null)] + public void TestSingleSegment(SplineType splineType, int? degree) + => AddStep("create path", () => path.ControlPoints.AddRange(createSegment( + new PathType { SplineType = splineType, Degree = degree }, + Vector2.Zero, + new Vector2(0, 100), + new Vector2(100), + new Vector2(0, 200), + new Vector2(200) + ))); - [TestCase(PathType.Linear)] - [TestCase(PathType.Bezier)] - [TestCase(PathType.Catmull)] - [TestCase(PathType.PerfectCurve)] - public void TestMultipleSegment(PathType type) + [TestCase(SplineType.Linear, null)] + [TestCase(SplineType.BSpline, null)] + [TestCase(SplineType.BSpline, 3)] + [TestCase(SplineType.Catmull, null)] + [TestCase(SplineType.PerfectCurve, null)] + public void TestMultipleSegment(SplineType splineType, int? degree) { AddStep("create path", () => { - path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero)); - path.ControlPoints.AddRange(createSegment(type, new Vector2(0, 100), new Vector2(100), Vector2.Zero)); + path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero)); + path.ControlPoints.AddRange(createSegment(new PathType { SplineType = splineType, Degree = degree }, new Vector2(0, 100), new Vector2(100), Vector2.Zero)); }); } [Test] public void TestAddControlPoint() { - AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100)))); + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(0, 100)))); AddStep("add point", () => path.ControlPoints.Add(new PathControlPoint { Position = new Vector2(100) })); } [Test] public void TestInsertControlPoint() { - AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(100)))); + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(100)))); AddStep("insert point", () => path.ControlPoints.Insert(1, new PathControlPoint { Position = new Vector2(0, 100) })); } [Test] public void TestRemoveControlPoint() { - AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); AddStep("remove second point", () => path.ControlPoints.RemoveAt(1)); } [Test] public void TestChangePathType() { - AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); - AddStep("change type to bezier", () => path.ControlPoints[0].Type = PathType.Bezier); + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + AddStep("change type to bezier", () => path.ControlPoints[0].Type = PathType.BEZIER); } [Test] public void TestAddSegmentByChangingType() { - AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0)))); - AddStep("change second point type to bezier", () => path.ControlPoints[1].Type = PathType.Bezier); + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0)))); + AddStep("change second point type to bezier", () => path.ControlPoints[1].Type = PathType.BEZIER); } [Test] @@ -112,8 +121,8 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("create path", () => { - path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); - path.ControlPoints[1].Type = PathType.Bezier; + path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); + path.ControlPoints[1].Type = PathType.BEZIER; }); AddStep("change second point type to null", () => path.ControlPoints[1].Type = null); @@ -124,8 +133,8 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("create path", () => { - path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); - path.ControlPoints[1].Type = PathType.Bezier; + path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); + path.ControlPoints[1].Type = PathType.BEZIER; }); AddStep("remove second point", () => path.ControlPoints.RemoveAt(1)); @@ -140,11 +149,11 @@ namespace osu.Game.Tests.Visual.Gameplay switch (points) { case 2: - path.ControlPoints.AddRange(createSegment(PathType.PerfectCurve, Vector2.Zero, new Vector2(0, 100))); + path.ControlPoints.AddRange(createSegment(PathType.PERFECTCURVE, Vector2.Zero, new Vector2(0, 100))); break; case 4: - path.ControlPoints.AddRange(createSegment(PathType.PerfectCurve, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); + path.ControlPoints.AddRange(createSegment(PathType.PERFECTCURVE, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); break; } }); @@ -153,35 +162,35 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestLengthenLastSegment() { - AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); AddStep("lengthen last segment", () => path.ExpectedDistance.Value = 300); } [Test] public void TestShortenLastSegment() { - AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); AddStep("shorten last segment", () => path.ExpectedDistance.Value = 150); } [Test] public void TestShortenFirstSegment() { - AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); AddStep("shorten first segment", () => path.ExpectedDistance.Value = 50); } [Test] public void TestShortenToZeroLength() { - AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); AddStep("shorten to 0 length", () => path.ExpectedDistance.Value = 0); } [Test] public void TestShortenToNegativeLength() { - AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); AddStep("shorten to -10 length", () => path.ExpectedDistance.Value = -10); } @@ -197,7 +206,7 @@ namespace osu.Game.Tests.Visual.Gameplay }; double[] distances = { 100d, 200d, 300d }; - AddStep("create path", () => path.ControlPoints.AddRange(positions.Select(p => new PathControlPoint(p, PathType.Linear)))); + AddStep("create path", () => path.ControlPoints.AddRange(positions.Select(p => new PathControlPoint(p, PathType.LINEAR)))); AddAssert("segment ends are correct", () => path.GetSegmentEnds(), () => Is.EqualTo(distances.Select(d => d / 300))); AddAssert("segment end positions recovered", () => path.GetSegmentEnds().Select(p => path.PositionAt(p)), () => Is.EqualTo(positions.Skip(1))); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 4f8e935ee4..7029f61459 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; @@ -437,7 +438,7 @@ namespace osu.Game.Beatmaps.Formats // Explicit segments have a new format in which the type is injected into the middle of the control point string. // To preserve compatibility with osu-stable as much as possible, explicit segments with the same type are converted to use implicit segments by duplicating the control point. // One exception are consecutive perfect curves, which aren't supported in osu!stable and can lead to decoding issues if encoded as implicit segments - bool needsExplicitSegment = point.Type != lastType || point.Type == PathType.PerfectCurve; + bool needsExplicitSegment = point.Type != lastType || point.Type == PathType.PERFECTCURVE; // Another exception to this is when the last two control points of the last segment were duplicated. This is not a scenario supported by osu!stable. // Lazer does not add implicit segments for the last two control points of _any_ explicit segment, so an explicit segment is forced in order to maintain consistency with the decoder. @@ -455,19 +456,23 @@ namespace osu.Game.Beatmaps.Formats { switch (point.Type) { - case PathType.Bezier: + case { SplineType: SplineType.BSpline, Degree: > 0 }: + writer.Write($"B{point.Type.Value.Degree}|"); + break; + + case { SplineType: SplineType.BSpline, Degree: <= 0 }: writer.Write("B|"); break; - case PathType.Catmull: + case { SplineType: SplineType.Catmull }: writer.Write("C|"); break; - case PathType.PerfectCurve: + case { SplineType: SplineType.PerfectCurve }: writer.Write("P|"); break; - case PathType.Linear: + case { SplineType: SplineType.Linear }: writer.Write("L|"); break; } diff --git a/osu.Game/Database/LegacyBeatmapExporter.cs b/osu.Game/Database/LegacyBeatmapExporter.cs index ece705f685..9ca12a79dd 100644 --- a/osu.Game/Database/LegacyBeatmapExporter.cs +++ b/osu.Game/Database/LegacyBeatmapExporter.cs @@ -78,10 +78,10 @@ namespace osu.Game.Database // wherein the last control point of an otherwise-single-segment slider path has a different type than previous, // which would lead to sliders being mangled when exported back to stable. // normally, that would be handled by the `BezierConverter.ConvertToModernBezier()` call below, - // which outputs a slider path containing only Bezier control points, + // which outputs a slider path containing only BEZIER control points, // but a non-inherited last control point is (rightly) not considered to be starting a new segment, // therefore it would fail to clear the `CountSegments() <= 1` check. - // by clearing explicitly we both fix the issue and avoid unnecessary conversions to Bezier. + // by clearing explicitly we both fix the issue and avoid unnecessary conversions to BEZIER. if (hasPath.Path.ControlPoints.Count > 1) hasPath.Path.ControlPoints[^1].Type = null; diff --git a/osu.Game/Rulesets/Objects/BezierConverter.cs b/osu.Game/Rulesets/Objects/BezierConverter.cs index 0c878fa1fd..74fbe7d8f9 100644 --- a/osu.Game/Rulesets/Objects/BezierConverter.cs +++ b/osu.Game/Rulesets/Objects/BezierConverter.cs @@ -68,26 +68,26 @@ namespace osu.Game.Rulesets.Objects // The current vertex ends the segment var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1); - var segmentType = controlPoints[start].Type ?? PathType.Linear; + var segmentType = controlPoints[start].Type ?? PathType.LINEAR; switch (segmentType) { - case PathType.Catmull: + case { SplineType: SplineType.Catmull }: result.AddRange(from segment in ConvertCatmullToBezierAnchors(segmentVertices) from v in segment select v + position); - break; - case PathType.Linear: + case { SplineType: SplineType.Linear }: result.AddRange(from segment in ConvertLinearToBezierAnchors(segmentVertices) from v in segment select v + position); - break; - case PathType.PerfectCurve: + case { SplineType: SplineType.PerfectCurve }: result.AddRange(ConvertCircleToBezierAnchors(segmentVertices).Select(v => v + position)); - break; default: + if (segmentType.Degree != null) + throw new NotImplementedException("BSpline conversion of arbitrary degree is not implemented."); + foreach (Vector2 v in segmentVertices) { result.Add(v + position); @@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Objects } /// - /// Converts a path of control points to an identical path using only Bezier type control points. + /// Converts a path of control points to an identical path using only BEZIER type control points. /// /// The control points of the path. /// The list of bezier control points. @@ -124,38 +124,38 @@ namespace osu.Game.Rulesets.Objects // The current vertex ends the segment var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1); - var segmentType = controlPoints[start].Type ?? PathType.Linear; + var segmentType = controlPoints[start].Type ?? PathType.LINEAR; switch (segmentType) { - case PathType.Catmull: + case { SplineType: SplineType.Catmull }: foreach (var segment in ConvertCatmullToBezierAnchors(segmentVertices)) { for (int j = 0; j < segment.Length - 1; j++) { - result.Add(new PathControlPoint(segment[j], j == 0 ? PathType.Bezier : null)); + result.Add(new PathControlPoint(segment[j], j == 0 ? PathType.BEZIER : null)); } } break; - case PathType.Linear: + case { SplineType: SplineType.Linear }: foreach (var segment in ConvertLinearToBezierAnchors(segmentVertices)) { for (int j = 0; j < segment.Length - 1; j++) { - result.Add(new PathControlPoint(segment[j], j == 0 ? PathType.Bezier : null)); + result.Add(new PathControlPoint(segment[j], j == 0 ? PathType.BEZIER : null)); } } break; - case PathType.PerfectCurve: + case { SplineType: SplineType.PerfectCurve }: var circleResult = ConvertCircleToBezierAnchors(segmentVertices); for (int j = 0; j < circleResult.Length - 1; j++) { - result.Add(new PathControlPoint(circleResult[j], j == 0 ? PathType.Bezier : null)); + result.Add(new PathControlPoint(circleResult[j], j == 0 ? PathType.BEZIER : null)); } break; @@ -163,7 +163,7 @@ namespace osu.Game.Rulesets.Objects default: for (int j = 0; j < segmentVertices.Length - 1; j++) { - result.Add(new PathControlPoint(segmentVertices[j], j == 0 ? PathType.Bezier : null)); + result.Add(new PathControlPoint(segmentVertices[j], j == 0 ? segmentType : null)); } break; diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index d20f2d31bb..30f4c092d9 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -224,16 +224,18 @@ namespace osu.Game.Rulesets.Objects.Legacy { default: case 'C': - return PathType.Catmull; + return new PathType(SplineType.Catmull); case 'B': - return PathType.Bezier; + if (input.Length > 1 && int.TryParse(input.Substring(1), out int degree) && degree > 0) + return new PathType { SplineType = SplineType.BSpline, Degree = degree }; + return new PathType(SplineType.BSpline); case 'L': - return PathType.Linear; + return new PathType(SplineType.Linear); case 'P': - return PathType.PerfectCurve; + return new PathType(SplineType.PerfectCurve); } } @@ -320,14 +322,14 @@ namespace osu.Game.Rulesets.Objects.Legacy readPoint(endPoint, offset, out vertices[^1]); // Edge-case rules (to match stable). - if (type == PathType.PerfectCurve) + if (type == PathType.PERFECTCURVE) { if (vertices.Length != 3) - type = PathType.Bezier; + type = PathType.BEZIER; else if (isLinear(vertices)) { // osu-stable special-cased colinear perfect curves to a linear path - type = PathType.Linear; + type = PathType.LINEAR; } } @@ -349,10 +351,10 @@ namespace osu.Game.Rulesets.Objects.Legacy if (vertices[endIndex].Position != vertices[endIndex - 1].Position) continue; - // Legacy Catmull sliders don't support multiple segments, so adjacent Catmull segments should be treated as a single one. + // 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. diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 0ac057578b..4c24c111be 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; using System.Linq; +using Microsoft.Toolkit.HighPerformance; using Newtonsoft.Json; using osu.Framework.Bindables; using osu.Framework.Caching; @@ -260,7 +261,7 @@ namespace osu.Game.Rulesets.Objects // The current vertex ends the segment var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1); - var segmentType = ControlPoints[start].Type ?? PathType.Linear; + var segmentType = ControlPoints[start].Type ?? PathType.LINEAR; // No need to calculate path when there is only 1 vertex if (segmentVertices.Length == 1) @@ -288,12 +289,12 @@ namespace osu.Game.Rulesets.Objects private List calculateSubPath(ReadOnlySpan subControlPoints, PathType type) { - switch (type) + switch (type.SplineType) { - case PathType.Linear: + case SplineType.Linear: return PathApproximator.ApproximateLinear(subControlPoints); - case PathType.PerfectCurve: + case SplineType.PerfectCurve: if (subControlPoints.Length != 3) break; @@ -305,11 +306,11 @@ namespace osu.Game.Rulesets.Objects return subPath; - case PathType.Catmull: + case SplineType.Catmull: return PathApproximator.ApproximateCatmull(subControlPoints); } - return PathApproximator.ApproximateBezier(subControlPoints); + return PathApproximator.ApproximateBSpline(subControlPoints, type.Degree ?? subControlPoints.Length); } private void calculateLength() diff --git a/osu.Game/Rulesets/Objects/SliderPathExtensions.cs b/osu.Game/Rulesets/Objects/SliderPathExtensions.cs index 6c88f01249..d7e5e4574d 100644 --- a/osu.Game/Rulesets/Objects/SliderPathExtensions.cs +++ b/osu.Game/Rulesets/Objects/SliderPathExtensions.cs @@ -29,11 +29,11 @@ namespace osu.Game.Rulesets.Objects { var controlPoints = sliderPath.ControlPoints; - var inheritedLinearPoints = controlPoints.Where(p => sliderPath.PointsInSegment(p)[0].Type == PathType.Linear && p.Type is null).ToList(); + var inheritedLinearPoints = controlPoints.Where(p => sliderPath.PointsInSegment(p)[0].Type == PathType.LINEAR && p.Type is null).ToList(); // Inherited points after a linear point, as well as the first control point if it inherited, // should be treated as linear points, so their types are temporarily changed to linear. - inheritedLinearPoints.ForEach(p => p.Type = PathType.Linear); + inheritedLinearPoints.ForEach(p => p.Type = PathType.LINEAR); double[] segmentEnds = sliderPath.GetSegmentEnds().ToArray(); @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Objects inheritedLinearPoints.ForEach(p => p.Type = null); // Recalculate middle perfect curve control points at the end of the slider path. - if (controlPoints.Count >= 3 && controlPoints[^3].Type == PathType.PerfectCurve && controlPoints[^2].Type is null && segmentEnds.Any()) + if (controlPoints.Count >= 3 && controlPoints[^3].Type == PathType.PERFECTCURVE && controlPoints[^2].Type is null && segmentEnds.Any()) { double lastSegmentStart = segmentEnds.Length > 1 ? segmentEnds[^2] : 0; double lastSegmentEnd = segmentEnds[^1]; diff --git a/osu.Game/Rulesets/Objects/Types/PathType.cs b/osu.Game/Rulesets/Objects/Types/PathType.cs index 923ce9eba4..41472fd8b5 100644 --- a/osu.Game/Rulesets/Objects/Types/PathType.cs +++ b/osu.Game/Rulesets/Objects/Types/PathType.cs @@ -1,13 +1,59 @@ // 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; + namespace osu.Game.Rulesets.Objects.Types { - public enum PathType + public enum SplineType { Catmull, - Bezier, + BSpline, Linear, PerfectCurve } + + public struct PathType + { + public static readonly PathType CATMULL = new PathType(SplineType.Catmull); + public static readonly PathType BEZIER = new PathType(SplineType.BSpline); + public static readonly PathType LINEAR = new PathType(SplineType.Linear); + public static readonly PathType PERFECTCURVE = new PathType(SplineType.PerfectCurve); + + /// + /// The type of the spline that should be used to interpret the control points of the path. + /// + public SplineType SplineType { get; init; } + + /// + /// The degree of a BSpline. Unused if is not . + /// Null means the degree is equal to the number of control points, 1 means linear, 2 means quadratic, etc. + /// + public int? Degree { get; init; } + + public PathType(SplineType splineType) + { + SplineType = splineType; + Degree = null; + } + + public override int GetHashCode() + => HashCode.Combine(SplineType, Degree); + + public override bool Equals(object? obj) + => obj is PathType pathType && this == pathType; + + public static bool operator ==(PathType a, PathType b) + => a.SplineType == b.SplineType && a.Degree == b.Degree; + + public static bool operator !=(PathType a, PathType b) + => a.SplineType != b.SplineType || a.Degree != b.Degree; + + public static PathType BSpline(int degree) + { + Debug.Assert(degree > 0); + return new PathType { SplineType = SplineType.BSpline, Degree = degree }; + } + } } diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 793d43f7ef..a114529bf9 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -246,13 +246,13 @@ namespace osu.Game.Screens.Play.HUD barPath = new SliderPath(new[] { - new PathControlPoint(new Vector2(0, 0), PathType.Linear), - new PathControlPoint(new Vector2(curveStart - curve_smoothness, 0), PathType.Bezier), + new PathControlPoint(new Vector2(0, 0), PathType.LINEAR), + new PathControlPoint(new Vector2(curveStart - curve_smoothness, 0), PathType.BEZIER), new PathControlPoint(new Vector2(curveStart, 0)), - new PathControlPoint(new Vector2(curveStart, 0) + diagonalDir * curve_smoothness, PathType.Linear), - new PathControlPoint(new Vector2(curveEnd, BarHeight.Value) - diagonalDir * curve_smoothness, PathType.Bezier), + new PathControlPoint(new Vector2(curveStart, 0) + diagonalDir * curve_smoothness, PathType.LINEAR), + new PathControlPoint(new Vector2(curveEnd, BarHeight.Value) - diagonalDir * curve_smoothness, PathType.BEZIER), new PathControlPoint(new Vector2(curveEnd, BarHeight.Value)), - new PathControlPoint(new Vector2(curveEnd + curve_smoothness, BarHeight.Value), PathType.Linear), + new PathControlPoint(new Vector2(curveEnd + curve_smoothness, BarHeight.Value), PathType.LINEAR), new PathControlPoint(new Vector2(barLength, BarHeight.Value)), }); From 3f85aa79c5b4402b714da14295b6b796ffa8d6e6 Mon Sep 17 00:00:00 2001 From: cs Date: Sat, 11 Nov 2023 10:45:22 +0100 Subject: [PATCH 1481/4092] Add free-hand drawing of sliders to the editor --- .../Sliders/SliderPlacementBlueprint.cs | 86 +++++++++++++++++-- .../Edit/ISliderDrawingSettingsProvider.cs | 12 +++ .../Edit/OsuHitObjectComposer.cs | 8 +- .../Edit/OsuSliderDrawingSettingsProvider.cs | 68 +++++++++++++++ osu.Game/Rulesets/Objects/SliderPath.cs | 8 +- 5 files changed, 171 insertions(+), 11 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Edit/ISliderDrawingSettingsProvider.cs create mode 100644 osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 8f0a2ee781..a5c6ae9465 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -10,6 +10,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -44,6 +45,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders [Resolved(CanBeNull = true)] private IDistanceSnapProvider distanceSnapProvider { get; set; } + [Resolved(CanBeNull = true)] + private ISliderDrawingSettingsProvider drawingSettingsProvider { get; set; } + + private readonly IncrementalBSplineBuilder bSplineBuilder = new IncrementalBSplineBuilder(); + protected override bool IsValidForPlacement => HitObject.Path.HasValidLength; public SliderPlacementBlueprint() @@ -73,6 +79,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { base.LoadComplete(); inputManager = GetContainingInputManager(); + + drawingSettingsProvider.Tolerance.BindValueChanged(e => + { + if (bSplineBuilder.Tolerance != e.NewValue) + bSplineBuilder.Tolerance = e.NewValue; + updateSliderPathFromBSplineBuilder(); + }, true); } [Resolved] @@ -98,7 +111,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders ApplyDefaultsToHitObject(); break; - case SliderPlacementState.Body: + case SliderPlacementState.ControlPoints: updateCursor(); break; } @@ -115,7 +128,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders beginCurve(); break; - case SliderPlacementState.Body: + case SliderPlacementState.ControlPoints: if (canPlaceNewControlPoint(out var lastPoint)) { // Place a new point by detatching the current cursor. @@ -139,9 +152,62 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders return true; } + protected override bool OnDragStart(DragStartEvent e) + { + if (e.Button == MouseButton.Left) + { + switch (state) + { + case SliderPlacementState.Initial: + return true; + + case SliderPlacementState.ControlPoints: + if (HitObject.Path.ControlPoints.Count < 3) + { + var lastCp = HitObject.Path.ControlPoints.LastOrDefault(); + if (lastCp != cursor) + return false; + + bSplineBuilder.Clear(); + bSplineBuilder.AddLinearPoint(ToLocalSpace(e.ScreenSpaceMousePosition) - HitObject.Position); + setState(SliderPlacementState.Drawing); + return true; + } + return false; + } + } + return base.OnDragStart(e); + } + + protected override void OnDrag(DragEvent e) + { + base.OnDrag(e); + + bSplineBuilder.AddLinearPoint(ToLocalSpace(e.ScreenSpaceMousePosition) - HitObject.Position); + updateSliderPathFromBSplineBuilder(); + } + + private void updateSliderPathFromBSplineBuilder() + { + Scheduler.AddOnce(static self => + { + var cps = self.bSplineBuilder.GetControlPoints(); + self.HitObject.Path.ControlPoints.RemoveRange(1, self.HitObject.Path.ControlPoints.Count - 1); + self.HitObject.Path.ControlPoints.AddRange(cps.Select(v => new PathControlPoint(v))); + }, this); + } + + protected override void OnDragEnd(DragEndEvent e) + { + base.OnDragEnd(e); + + if (state == SliderPlacementState.Drawing) + endCurve(); + } + protected override void OnMouseUp(MouseUpEvent e) { - if (state == SliderPlacementState.Body && e.Button == MouseButton.Right) + if (state == SliderPlacementState.ControlPoints && e.Button == MouseButton.Right) endCurve(); base.OnMouseUp(e); } @@ -149,7 +215,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void beginCurve() { BeginPlacement(commitStart: true); - setState(SliderPlacementState.Body); + setState(SliderPlacementState.ControlPoints); } private void endCurve() @@ -169,6 +235,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void updatePathType() { + if (state == SliderPlacementState.Drawing) + { + segmentStart.Type = PathType.BSpline(3); + return; + } + switch (currentSegmentLength) { case 1: @@ -201,7 +273,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders } // Update the cursor position. - var result = positionSnapProvider?.FindSnappedPositionAndTime(inputManager.CurrentState.Mouse.Position, state == SliderPlacementState.Body ? SnapType.GlobalGrids : SnapType.All); + var result = positionSnapProvider?.FindSnappedPositionAndTime(inputManager.CurrentState.Mouse.Position, state == SliderPlacementState.ControlPoints ? SnapType.GlobalGrids : SnapType.All); cursor.Position = ToLocalSpace(result?.ScreenSpacePosition ?? inputManager.CurrentState.Mouse.Position) - HitObject.Position; } else if (cursor != null) @@ -248,7 +320,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private enum SliderPlacementState { Initial, - Body, + ControlPoints, + Drawing, + DrawingFinalization } } } diff --git a/osu.Game.Rulesets.Osu/Edit/ISliderDrawingSettingsProvider.cs b/osu.Game.Rulesets.Osu/Edit/ISliderDrawingSettingsProvider.cs new file mode 100644 index 0000000000..1138588259 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/ISliderDrawingSettingsProvider.cs @@ -0,0 +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; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public interface ISliderDrawingSettingsProvider + { + BindableFloat Tolerance { get; } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 0f8c960b65..d958b558cf 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -63,6 +63,9 @@ namespace osu.Game.Rulesets.Osu.Edit [Cached(typeof(IDistanceSnapProvider))] protected readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider(); + [Cached(typeof(ISliderDrawingSettingsProvider))] + protected readonly OsuSliderDrawingSettingsProvider SliderDrawingSettingsProvider = new OsuSliderDrawingSettingsProvider(); + [BackgroundDependencyLoader] private void load() { @@ -96,8 +99,11 @@ namespace osu.Game.Rulesets.Osu.Edit RightToolbox.Add(new TransformToolboxGroup { - RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler + RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, }); + + AddInternal(SliderDrawingSettingsProvider); + SliderDrawingSettingsProvider.AttachToToolbox(RightToolbox); } protected override ComposeBlueprintContainer CreateBlueprintContainer() diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs b/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs new file mode 100644 index 0000000000..ba2c39e1b5 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs @@ -0,0 +1,68 @@ +// 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.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Utils; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Edit; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public partial class OsuSliderDrawingSettingsProvider : Drawable, ISliderDrawingSettingsProvider + { + public BindableFloat Tolerance { get; } = new BindableFloat(0.1f) + { + MinValue = 0.05f, + MaxValue = 1f, + Precision = 0.01f + }; + + private BindableInt sliderTolerance = new BindableInt(10) + { + MinValue = 5, + MaxValue = 100 + }; + + private ExpandableSlider toleranceSlider = null!; + + private EditorToolboxGroup? toolboxGroup; + + public OsuSliderDrawingSettingsProvider() + { + sliderTolerance.BindValueChanged(v => + { + float newValue = v.NewValue / 100f; + if (!Precision.AlmostEquals(newValue, Tolerance.Value, 1e-7f)) + Tolerance.Value = newValue; + }); + Tolerance.BindValueChanged(v => + { + int newValue = (int)Math.Round(v.NewValue * 100f); + if (sliderTolerance.Value != newValue) + sliderTolerance.Value = newValue; + }); + } + + public void AttachToToolbox(ExpandingToolboxContainer toolboxContainer) + { + toolboxContainer.Add(toolboxGroup = new EditorToolboxGroup("drawing") + { + Children = new Drawable[] + { + toleranceSlider = new ExpandableSlider + { + Current = sliderTolerance + } + } + }); + + sliderTolerance.BindValueChanged(e => + { + toleranceSlider.ContractedLabelText = $"Tolerance: {e.NewValue:N0}"; + toleranceSlider.ExpandedLabelText = $"Tolerance: {e.NewValue:N0}"; + }, true); + } + } +} diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 4c24c111be..75f1ab868d 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -292,13 +292,13 @@ namespace osu.Game.Rulesets.Objects switch (type.SplineType) { case SplineType.Linear: - return PathApproximator.ApproximateLinear(subControlPoints); + return PathApproximator.LinearToPiecewiseLinear(subControlPoints); case SplineType.PerfectCurve: if (subControlPoints.Length != 3) break; - List subPath = PathApproximator.ApproximateCircularArc(subControlPoints); + List subPath = PathApproximator.CircularArcToPiecewiseLinear(subControlPoints); // If for some reason a circular arc could not be fit to the 3 given points, fall back to a numerically stable bezier approximation. if (subPath.Count == 0) @@ -307,10 +307,10 @@ namespace osu.Game.Rulesets.Objects return subPath; case SplineType.Catmull: - return PathApproximator.ApproximateCatmull(subControlPoints); + return PathApproximator.CatmullToPiecewiseLinear(subControlPoints); } - return PathApproximator.ApproximateBSpline(subControlPoints, type.Degree ?? subControlPoints.Length); + return PathApproximator.BSplineToPiecewiseLinear(subControlPoints, type.Degree ?? subControlPoints.Length); } private void calculateLength() From 4e1e19728cb8ff9e11f18ab9c0e8635c2cc2ba9a Mon Sep 17 00:00:00 2001 From: ratinfx Date: Sat, 11 Nov 2023 14:02:42 +0100 Subject: [PATCH 1482/4092] Refactor HitObject selection in Composer --- .../Edit/ManiaHitObjectComposer.cs | 43 ++++++++++++++----- .../Edit/OsuHitObjectComposer.cs | 36 +++++++++++++--- .../Rulesets/Edit/EditorTimestampParser.cs | 40 +---------------- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 14 ++---- osu.Game/Screens/Edit/Editor.cs | 11 +---- 5 files changed, 68 insertions(+), 76 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index d217f04651..b04d3f895d 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -3,16 +3,15 @@ #nullable disable -using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit.Compose.Components; @@ -50,22 +49,44 @@ namespace osu.Game.Rulesets.Mania.Edit new HoldNoteCompositionTool() }; + private static readonly Regex selection_regex = new Regex(@"^\d+\|\d+(,\d+\|\d+)*$"); + public override string ConvertSelectionToString() - => string.Join(ObjectSeparator, EditorBeatmap.SelectedHitObjects.Cast().OrderBy(h => h.StartTime).Select(h => $"{h.StartTime}|{h.Column}")); + => string.Join(',', EditorBeatmap.SelectedHitObjects.Cast().OrderBy(h => h.StartTime).Select(h => $"{h.StartTime}|{h.Column}")); - public override bool HandleHitObjectSelection(HitObject hitObject, string objectInfo) + public override void SelectHitObjects(double timestamp, string objectDescription) { - if (hitObject is not ManiaHitObject maniaHitObject) - return false; + if (!selection_regex.IsMatch(objectDescription)) + return; - double[] split = objectInfo.Split('|').Select(double.Parse).ToArray(); + List remainingHitObjects = EditorBeatmap.HitObjects.Cast().Where(h => h.StartTime >= timestamp).ToList(); + string[] split = objectDescription.Split(',').ToArray(); + + for (int i = 0; i < split.Length; i++) + { + ManiaHitObject current = remainingHitObjects.FirstOrDefault(h => shouldBeSelected(h, split[i])); + + if (current == null) + continue; + + EditorBeatmap.SelectedHitObjects.Add(current); + + if (i < split.Length - 1) + remainingHitObjects = remainingHitObjects.Where(h => h != current && h.StartTime >= current.StartTime).ToList(); + } + } + + private bool shouldBeSelected(ManiaHitObject hitObject, string objectInfo) + { + string[] split = objectInfo.Split('|').ToArray(); if (split.Length != 2) return false; - double timeValue = split[0]; - double columnValue = split[1]; - return Math.Abs(maniaHitObject.StartTime - timeValue) < 0.5 - && Math.Abs(maniaHitObject.Column - columnValue) < 0.5; + if (!double.TryParse(split[0], out double time) || !int.TryParse(split[1], out int column)) + return false; + + return hitObject.StartTime == time + && hitObject.Column == column; } } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 0c63cf71d8..e9c222b0e7 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Caching; @@ -103,18 +104,39 @@ namespace osu.Game.Rulesets.Osu.Edit protected override ComposeBlueprintContainer CreateBlueprintContainer() => new OsuBlueprintContainer(this); + private static readonly Regex selection_regex = new Regex(@"^\d+(,\d+)*$"); + public override string ConvertSelectionToString() - => string.Join(ObjectSeparator, selectedHitObjects.Cast().OrderBy(h => h.StartTime).Select(h => (h.IndexInCurrentCombo + 1).ToString())); + => string.Join(',', selectedHitObjects.Cast().OrderBy(h => h.StartTime).Select(h => (h.IndexInCurrentCombo + 1).ToString())); - public override bool HandleHitObjectSelection(HitObject hitObject, string objectInfo) + public override void SelectHitObjects(double timestamp, string objectDescription) { - if (hitObject is not OsuHitObject osuHitObject) + if (!selection_regex.IsMatch(objectDescription)) + return; + + List remainingHitObjects = EditorBeatmap.HitObjects.Cast().Where(h => h.StartTime >= timestamp).ToList(); + string[] split = objectDescription.Split(',').ToArray(); + + for (int i = 0; i < split.Length; i++) + { + OsuHitObject current = remainingHitObjects.FirstOrDefault(h => shouldBeSelected(h, split[i])); + + if (current == null) + continue; + + EditorBeatmap.SelectedHitObjects.Add(current); + + if (i < split.Length - 1) + remainingHitObjects = remainingHitObjects.Where(h => h != current && h.StartTime >= current.StartTime).ToList(); + } + } + + private bool shouldBeSelected(OsuHitObject hitObject, string objectInfo) + { + if (!int.TryParse(objectInfo, out int combo) || combo < 1) return false; - if (!int.TryParse(objectInfo, out int comboValue) || comboValue < 1) - return false; - - return osuHitObject.IndexInCurrentCombo + 1 == comboValue; + return hitObject.IndexInCurrentCombo + 1 == combo; } private DistanceSnapGrid distanceSnapGrid; diff --git a/osu.Game/Rulesets/Edit/EditorTimestampParser.cs b/osu.Game/Rulesets/Edit/EditorTimestampParser.cs index 4e5a696102..7d4247b269 100644 --- a/osu.Game/Rulesets/Edit/EditorTimestampParser.cs +++ b/osu.Game/Rulesets/Edit/EditorTimestampParser.cs @@ -2,11 +2,9 @@ // 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 System.Text.RegularExpressions; -using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Edit { @@ -31,43 +29,7 @@ namespace osu.Game.Rulesets.Edit Debug.Assert(times.Length == 3); - return (times[0] * 60 + times[1]) * 1_000 + times[2]; - } - - public static List GetSelectedHitObjects(HitObjectComposer composer, IReadOnlyList editorHitObjects, string objectsGroup, double position) - { - List hitObjects = editorHitObjects.Where(x => x.StartTime >= position).ToList(); - List selectedObjects = new List(); - - string[] objectsToSelect = objectsGroup.Split(composer.ObjectSeparator).ToArray(); - - foreach (string objectInfo in objectsToSelect) - { - HitObject? current = hitObjects.FirstOrDefault(x => composer.HandleHitObjectSelection(x, objectInfo)); - - if (current == null) - continue; - - selectedObjects.Add(current); - hitObjects = hitObjects.Where(x => x != current && x.StartTime >= current.StartTime).ToList(); - } - - // Stable behavior - // - always selects the next closest object when `objectsGroup` only has one (combo) item - if (objectsToSelect.Length != 1 || objectsGroup.Contains('|')) - return selectedObjects; - - HitObject? nextClosest = editorHitObjects.FirstOrDefault(x => x.StartTime >= position); - if (nextClosest == null) - return selectedObjects; - - if (nextClosest.StartTime <= (selectedObjects.FirstOrDefault()?.StartTime ?? position)) - { - selectedObjects.Clear(); - selectedObjects.Add(nextClosest); - } - - return selectedObjects; + return (times[0] * 60 + times[1]) * 1000 + times[2]; } } } diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index f6cddcc0d2..52a525e84f 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -529,17 +529,11 @@ namespace osu.Game.Rulesets.Edit public virtual string ConvertSelectionToString() => string.Empty; /// - /// The custom logic that decides whether a HitObject should be selected when clicking an editor timestamp link + /// Each ruleset has it's own selection method /// - /// The hitObject being checked - /// A single hitObject's information created with - /// Whether a HitObject should be selected or not - public virtual bool HandleHitObjectSelection(HitObject hitObject, string objectInfo) => false; - - /// - /// A character that separates the selection in - /// - public virtual char ObjectSeparator => ','; + /// The given timestamp + /// The selected object information between the brackets + public virtual void SelectHitObjects(double timestamp, string objectDescription) { } #region IPositionSnapProvider diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 58c3ae809c..03d3e3a1f8 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1194,15 +1194,8 @@ namespace osu.Game.Screens.Edit if (Mode.Value != EditorScreenMode.Compose) Mode.Value = EditorScreenMode.Compose; - List selected = EditorTimestampParser.GetSelectedHitObjects( - currentScreen.Dependencies.Get(), - editorBeatmap.HitObjects.ToList(), - objectsGroup, - position - ); - - if (selected.Any()) - editorBeatmap.SelectedHitObjects.AddRange(selected); + // Let the Ruleset handle selection + currentScreen.Dependencies.Get().SelectHitObjects(position, objectsGroup); } public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); From 6ddecfd8062d6b1b0d62a6064d7d0b0b2c8d4760 Mon Sep 17 00:00:00 2001 From: ratinfx Date: Sat, 11 Nov 2023 14:13:06 +0100 Subject: [PATCH 1483/4092] Update Test cases --- .../TestSceneOpenEditorTimestampInMania.cs | 9 ++---- .../TestSceneOpenEditorTimestampInOsu.cs | 32 +++++-------------- 2 files changed, 11 insertions(+), 30 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneOpenEditorTimestampInMania.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneOpenEditorTimestampInMania.cs index 6ec5dcee4c..3c6a9f3b42 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneOpenEditorTimestampInMania.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneOpenEditorTimestampInMania.cs @@ -84,17 +84,14 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor [Test] public void TestUnusualSelection() { - addStepClickLink("00:00:000 (0|1)", "invalid link"); + addStepClickLink("00:00:000 (0|1)", "wrong offset"); AddAssert("snap to 1, select none", () => checkSnapAndSelectColumn(2_170)); addReset(); - addStepClickLink("00:00:000 (0)", "std link"); - AddAssert("snap and select 1", () => checkSnapAndSelectColumn(2_170, new List<(int, int)> - { (2_170, 2) }) - ); + addStepClickLink("00:00:000 (2)", "std link"); + AddAssert("snap to 1, select none", () => checkSnapAndSelectColumn(2_170)); addReset(); - // TODO: discuss - this selects the first 2 objects on Stable, do we want that or is this fine? addStepClickLink("00:00:000 (1,2)", "std link"); AddAssert("snap to 1, select none", () => checkSnapAndSelectColumn(2_170)); } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOpenEditorTimestampInOsu.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOpenEditorTimestampInOsu.cs index d69f482d29..93573b5ad8 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOpenEditorTimestampInOsu.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOpenEditorTimestampInOsu.cs @@ -71,40 +71,24 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { HitObject firstObject = null!; - addStepClickLink("00:00:000 (1,2,3)", "invalid offset"); - AddAssert("snap to next, select 1-2-3", () => + addStepClickLink("00:00:000 (0)", "invalid combo"); + AddAssert("snap to next, select none", () => { firstObject = EditorBeatmap.HitObjects.First(); - return checkSnapAndSelectCombo(firstObject.StartTime, 1, 2, 3); + return checkSnapAndSelectCombo(firstObject.StartTime); }); addReset(); - addStepClickLink("00:00:956 (2,3,4)", "invalid offset"); + addStepClickLink("00:00:000 (1)", "wrong offset"); + AddAssert("snap and select 1", () => checkSnapAndSelectCombo(firstObject.StartTime, 1)); + + addReset(); + addStepClickLink("00:00:956 (2,3,4)", "wrong offset"); AddAssert("snap to next, select 2-3-4", () => checkSnapAndSelectCombo(firstObject.StartTime, 2, 3, 4)); - addReset(); - addStepClickLink("00:00:000 (0)", "invalid offset"); - AddAssert("snap and select 1", () => checkSnapAndSelectCombo(firstObject.StartTime, 1)); - - addReset(); - addStepClickLink("00:00:000 (1)", "invalid offset"); - AddAssert("snap and select 1", () => checkSnapAndSelectCombo(firstObject.StartTime, 1)); - - addReset(); - addStepClickLink("00:00:000 (2)", "invalid offset"); - AddAssert("snap and select 1", () => checkSnapAndSelectCombo(firstObject.StartTime, 1)); - - addReset(); - addStepClickLink("00:00:000 (2,3)", "invalid offset"); - AddAssert("snap to 1, select 2-3", () => checkSnapAndSelectCombo(firstObject.StartTime, 2, 3)); - addReset(); addStepClickLink("00:00:956 (956|1,956|2)", "mania link"); AddAssert("snap to next, select none", () => checkSnapAndSelectCombo(firstObject.StartTime)); - - addReset(); - addStepClickLink("00:00:000 (0|1)", "mania link"); - AddAssert("snap to 1, select none", () => checkSnapAndSelectCombo(firstObject.StartTime)); } } } From 54b8244a18ad3e74d40962be20cab996ba63e90d Mon Sep 17 00:00:00 2001 From: cs Date: Sat, 11 Nov 2023 15:02:06 +0100 Subject: [PATCH 1484/4092] CI Fixup --- .../Sliders/Components/PathControlPointPiece.cs | 8 ++++---- .../Components/PathControlPointVisualiser.cs | 2 +- .../Sliders/SliderPlacementBlueprint.cs | 2 ++ .../Edit/OsuSliderDrawingSettingsProvider.cs | 10 ++++------ .../Visual/Gameplay/TestSceneBezierConverter.cs | 4 ++-- .../Visual/Gameplay/TestSceneSliderPath.cs | 4 ++-- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 11 +++++------ osu.Game/Rulesets/Objects/BezierConverter.cs | 12 ++++++------ .../Objects/Legacy/ConvertHitObjectParser.cs | 3 ++- osu.Game/Rulesets/Objects/SliderPath.cs | 3 +-- osu.Game/Rulesets/Objects/Types/PathType.cs | 16 ++++++++-------- 11 files changed, 37 insertions(+), 38 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 9658e5f6c3..53228cff82 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -261,17 +261,17 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components switch (pathType) { - case { SplineType: SplineType.Catmull }: + case { Type: SplineType.Catmull }: return colours.SeaFoam; - case { SplineType: SplineType.BSpline, Degree: null }: + case { Type: SplineType.BSpline, Degree: null }: return colours.PinkLighter; - case { SplineType: SplineType.BSpline, Degree: >= 1 }: + case { Type: SplineType.BSpline, Degree: >= 1 }: int idx = Math.Clamp(pathType.Degree.Value, 0, 3); return new[] { colours.PinkDarker, colours.PinkDark, colours.Pink, colours.PinkLight }[idx]; - case { SplineType: SplineType.PerfectCurve }: + case { Type: SplineType.PerfectCurve }: return colours.PurpleDark; default: diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index b5c9016538..4e85835652 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -242,7 +242,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { int indexInSegment = piece.PointsInSegment.IndexOf(piece.ControlPoint); - if (type.HasValue && type.Value.SplineType == SplineType.PerfectCurve) + if (type.HasValue && type.Value.Type == SplineType.PerfectCurve) { // Can't always create a circular arc out of 4 or more points, // so we split the segment into one 3-point circular arc segment diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index a5c6ae9465..20f11c3585 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -173,9 +173,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders setState(SliderPlacementState.Drawing); return true; } + return false; } } + return base.OnDragStart(e); } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs b/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs index ba2c39e1b5..ae772f53fc 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Edit Precision = 0.01f }; - private BindableInt sliderTolerance = new BindableInt(10) + private readonly BindableInt sliderTolerance = new BindableInt(10) { MinValue = 5, MaxValue = 100 @@ -27,8 +27,6 @@ namespace osu.Game.Rulesets.Osu.Edit private ExpandableSlider toleranceSlider = null!; - private EditorToolboxGroup? toolboxGroup; - public OsuSliderDrawingSettingsProvider() { sliderTolerance.BindValueChanged(v => @@ -47,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Edit public void AttachToToolbox(ExpandingToolboxContainer toolboxContainer) { - toolboxContainer.Add(toolboxGroup = new EditorToolboxGroup("drawing") + toolboxContainer.Add(new EditorToolboxGroup("drawing") { Children = new Drawable[] { @@ -60,8 +58,8 @@ namespace osu.Game.Rulesets.Osu.Edit sliderTolerance.BindValueChanged(e => { - toleranceSlider.ContractedLabelText = $"Tolerance: {e.NewValue:N0}"; - toleranceSlider.ExpandedLabelText = $"Tolerance: {e.NewValue:N0}"; + toleranceSlider.ContractedLabelText = $"C. P. S.: {e.NewValue:N0}"; + toleranceSlider.ExpandedLabelText = $"Control Point Spacing: {e.NewValue:N0}"; }, true); } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBezierConverter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBezierConverter.cs index 5eb82ccbdc..e2333011c7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBezierConverter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBezierConverter.cs @@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.Gameplay [TestCase(SplineType.Catmull, null)] [TestCase(SplineType.PerfectCurve, null)] public void TestSingleSegment(SplineType splineType, int? degree) - => AddStep("create path", () => path.ControlPoints.AddRange(createSegment(new PathType { SplineType = splineType, Degree = degree }, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + => AddStep("create path", () => path.ControlPoints.AddRange(createSegment(new PathType { Type = splineType, Degree = degree }, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); [TestCase(SplineType.Linear, null)] [TestCase(SplineType.BSpline, null)] @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("create path", () => { path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero)); - path.ControlPoints.AddRange(createSegment(new PathType { SplineType = splineType, Degree = degree }, new Vector2(0, 100), new Vector2(100), Vector2.Zero)); + path.ControlPoints.AddRange(createSegment(new PathType { Type = splineType, Degree = degree }, new Vector2(0, 100), new Vector2(100), Vector2.Zero)); }); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs index e4d99f6741..d44af45fe4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Gameplay [TestCase(SplineType.PerfectCurve, null)] public void TestSingleSegment(SplineType splineType, int? degree) => AddStep("create path", () => path.ControlPoints.AddRange(createSegment( - new PathType { SplineType = splineType, Degree = degree }, + new PathType { Type = splineType, Degree = degree }, Vector2.Zero, new Vector2(0, 100), new Vector2(100), @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("create path", () => { path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero)); - path.ControlPoints.AddRange(createSegment(new PathType { SplineType = splineType, Degree = degree }, new Vector2(0, 100), new Vector2(100), Vector2.Zero)); + path.ControlPoints.AddRange(createSegment(new PathType { Type = splineType, Degree = degree }, new Vector2(0, 100), new Vector2(100), Vector2.Zero)); }); } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 7029f61459..ff446206ac 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; @@ -456,23 +455,23 @@ namespace osu.Game.Beatmaps.Formats { switch (point.Type) { - case { SplineType: SplineType.BSpline, Degree: > 0 }: + case { Type: SplineType.BSpline, Degree: > 0 }: writer.Write($"B{point.Type.Value.Degree}|"); break; - case { SplineType: SplineType.BSpline, Degree: <= 0 }: + case { Type: SplineType.BSpline, Degree: <= 0 }: writer.Write("B|"); break; - case { SplineType: SplineType.Catmull }: + case { Type: SplineType.Catmull }: writer.Write("C|"); break; - case { SplineType: SplineType.PerfectCurve }: + case { Type: SplineType.PerfectCurve }: writer.Write("P|"); break; - case { SplineType: SplineType.Linear }: + case { Type: SplineType.Linear }: writer.Write("L|"); break; } diff --git a/osu.Game/Rulesets/Objects/BezierConverter.cs b/osu.Game/Rulesets/Objects/BezierConverter.cs index 74fbe7d8f9..ed86fc10e0 100644 --- a/osu.Game/Rulesets/Objects/BezierConverter.cs +++ b/osu.Game/Rulesets/Objects/BezierConverter.cs @@ -72,15 +72,15 @@ namespace osu.Game.Rulesets.Objects switch (segmentType) { - case { SplineType: SplineType.Catmull }: + case { Type: SplineType.Catmull }: result.AddRange(from segment in ConvertCatmullToBezierAnchors(segmentVertices) from v in segment select v + position); break; - case { SplineType: SplineType.Linear }: + case { Type: SplineType.Linear }: result.AddRange(from segment in ConvertLinearToBezierAnchors(segmentVertices) from v in segment select v + position); break; - case { SplineType: SplineType.PerfectCurve }: + case { Type: SplineType.PerfectCurve }: result.AddRange(ConvertCircleToBezierAnchors(segmentVertices).Select(v => v + position)); break; @@ -128,7 +128,7 @@ namespace osu.Game.Rulesets.Objects switch (segmentType) { - case { SplineType: SplineType.Catmull }: + case { Type: SplineType.Catmull }: foreach (var segment in ConvertCatmullToBezierAnchors(segmentVertices)) { for (int j = 0; j < segment.Length - 1; j++) @@ -139,7 +139,7 @@ namespace osu.Game.Rulesets.Objects break; - case { SplineType: SplineType.Linear }: + case { Type: SplineType.Linear }: foreach (var segment in ConvertLinearToBezierAnchors(segmentVertices)) { for (int j = 0; j < segment.Length - 1; j++) @@ -150,7 +150,7 @@ namespace osu.Game.Rulesets.Objects break; - case { SplineType: SplineType.PerfectCurve }: + case { Type: SplineType.PerfectCurve }: var circleResult = ConvertCircleToBezierAnchors(segmentVertices); for (int j = 0; j < circleResult.Length - 1; j++) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 30f4c092d9..6a13a897c4 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -228,7 +228,8 @@ namespace osu.Game.Rulesets.Objects.Legacy case 'B': if (input.Length > 1 && int.TryParse(input.Substring(1), out int degree) && degree > 0) - return new PathType { SplineType = SplineType.BSpline, Degree = degree }; + return new PathType { Type = SplineType.BSpline, Degree = degree }; + return new PathType(SplineType.BSpline); case 'L': diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 75f1ab868d..e9a192669f 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; using System.Linq; -using Microsoft.Toolkit.HighPerformance; using Newtonsoft.Json; using osu.Framework.Bindables; using osu.Framework.Caching; @@ -289,7 +288,7 @@ namespace osu.Game.Rulesets.Objects private List calculateSubPath(ReadOnlySpan subControlPoints, PathType type) { - switch (type.SplineType) + switch (type.Type) { case SplineType.Linear: return PathApproximator.LinearToPiecewiseLinear(subControlPoints); diff --git a/osu.Game/Rulesets/Objects/Types/PathType.cs b/osu.Game/Rulesets/Objects/Types/PathType.cs index 41472fd8b5..a6e8e173d4 100644 --- a/osu.Game/Rulesets/Objects/Types/PathType.cs +++ b/osu.Game/Rulesets/Objects/Types/PathType.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Objects.Types PerfectCurve } - public struct PathType + public readonly struct PathType { public static readonly PathType CATMULL = new PathType(SplineType.Catmull); public static readonly PathType BEZIER = new PathType(SplineType.BSpline); @@ -24,36 +24,36 @@ namespace osu.Game.Rulesets.Objects.Types /// /// The type of the spline that should be used to interpret the control points of the path. /// - public SplineType SplineType { get; init; } + public SplineType Type { get; init; } /// - /// The degree of a BSpline. Unused if is not . + /// The degree of a BSpline. Unused if is not . /// Null means the degree is equal to the number of control points, 1 means linear, 2 means quadratic, etc. /// public int? Degree { get; init; } public PathType(SplineType splineType) { - SplineType = splineType; + Type = splineType; Degree = null; } public override int GetHashCode() - => HashCode.Combine(SplineType, Degree); + => HashCode.Combine(Type, Degree); public override bool Equals(object? obj) => obj is PathType pathType && this == pathType; public static bool operator ==(PathType a, PathType b) - => a.SplineType == b.SplineType && a.Degree == b.Degree; + => a.Type == b.Type && a.Degree == b.Degree; public static bool operator !=(PathType a, PathType b) - => a.SplineType != b.SplineType || a.Degree != b.Degree; + => a.Type != b.Type || a.Degree != b.Degree; public static PathType BSpline(int degree) { Debug.Assert(degree > 0); - return new PathType { SplineType = SplineType.BSpline, Degree = degree }; + return new PathType { Type = SplineType.BSpline, Degree = degree }; } } } From c367697559dc8981ce601e44afa45a3ce46a6dc9 Mon Sep 17 00:00:00 2001 From: ratinfx Date: Sat, 11 Nov 2023 16:14:26 +0100 Subject: [PATCH 1485/4092] Changed TIME_REGEX to accept everything in the parentheses - changed osu-web link to the current value --- osu.Game/Rulesets/Edit/EditorTimestampParser.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/EditorTimestampParser.cs b/osu.Game/Rulesets/Edit/EditorTimestampParser.cs index 7d4247b269..b1488d298f 100644 --- a/osu.Game/Rulesets/Edit/EditorTimestampParser.cs +++ b/osu.Game/Rulesets/Edit/EditorTimestampParser.cs @@ -11,8 +11,8 @@ namespace osu.Game.Rulesets.Edit public static class EditorTimestampParser { // 00:00:000 (1,2,3) - test - // regex from https://github.com/ppy/osu-web/blob/651a9bac2b60d031edd7e33b8073a469bf11edaa/resources/assets/coffee/_classes/beatmap-discussion-helper.coffee#L10 - public static readonly Regex TIME_REGEX = new Regex(@"\b(((\d{2,}):([0-5]\d)[:.](\d{3}))(\s\((?:\d+[,|])*\d+\))?)"); + // osu-web regex: https://github.com/ppy/osu-web/blob/3b1698639244cfdaf0b41c68bfd651ea729ec2e3/resources/js/utils/beatmapset-discussion-helper.ts#L78 + public static readonly Regex TIME_REGEX = new Regex(@"\b(((\d{2,}):([0-5]\d)[:.](\d{3}))(\s\([^)]+\))?)"); public static string[] GetRegexGroups(string timestamp) { From 3e8c89e282c74874a8c6885228910d70b64d2e95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 12 Nov 2023 07:42:41 +0900 Subject: [PATCH 1486/4092] Fix one more reference to removed setting --- .../Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs index e9092bba1b..15a7b48323 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached(typeof(HealthProcessor))] private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); - protected override Drawable CreateArgonImplementation() => new ArgonHealthDisplay { Scale = new Vector2(0.6f), BarLength = { Value = 1f } }; + protected override Drawable CreateArgonImplementation() => new ArgonHealthDisplay { Scale = new Vector2(0.6f), Width = 1f }; protected override Drawable CreateDefaultImplementation() => new DefaultHealthDisplay { Scale = new Vector2(0.6f) }; protected override Drawable CreateLegacyImplementation() => new LegacyHealthDisplay { Scale = new Vector2(0.6f) }; From 4df1eb1b378cc4d5641ef4160b299f226e93dddf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 12 Nov 2023 16:15:33 +0900 Subject: [PATCH 1487/4092] Refactor logic and tooltip formatting --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 6 +- .../Overlays/Mods/BeatmapAttributesDisplay.cs | 60 +++++++++---------- 2 files changed, 31 insertions(+), 35 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 7a9250a60e..382ac29b41 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -238,14 +238,12 @@ namespace osu.Game.Rulesets.Catch { BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(baseDifficulty); - double preempt = adjustedDifficulty.ApproachRate < 5 ? - (1200.0 + 600.0 * (5 - adjustedDifficulty.ApproachRate) / 5) : - (1200.0 - 750.0 * (adjustedDifficulty.ApproachRate - 5) / 5); + double preempt = adjustedDifficulty.ApproachRate < 5 ? (1200.0 + 600.0 * (5 - adjustedDifficulty.ApproachRate) / 5) : (1200.0 - 750.0 * (adjustedDifficulty.ApproachRate - 5) / 5); + preempt /= rate; adjustedDifficulty.ApproachRate = (float)(preempt > 1200 ? ((1800 - preempt) / 120) : ((1200 - preempt) / 150 + 5)); return adjustedDifficulty ?? (BeatmapDifficulty)baseDifficulty; } - } } diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index 0a6f22d74a..5531ed5461 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -59,6 +59,8 @@ namespace osu.Game.Overlays.Mods private IBindable starDifficulty = null!; private BeatmapDifficulty? originalDifficulty; + private BeatmapDifficulty? adjustedDifficulty; + private bool haveRateChangedValues; private const float transition_duration = 250; @@ -171,17 +173,15 @@ namespace osu.Game.Overlays.Mods bpmDisplay.Current.Value = BeatmapInfo.Value.BPM * rate; - var adjustedDifficulty = new BeatmapDifficulty(BeatmapInfo.Value.Difficulty); + originalDifficulty = new BeatmapDifficulty(BeatmapInfo.Value.Difficulty); foreach (var mod in mods.Value.OfType()) - mod.ApplyToDifficulty(adjustedDifficulty); - - originalDifficulty = adjustedDifficulty; + mod.ApplyToDifficulty(originalDifficulty); Ruleset ruleset = gameRuleset.Value.CreateInstance(); - adjustedDifficulty = ruleset.GetRateAdjustedDifficulty(adjustedDifficulty, rate); + adjustedDifficulty = ruleset.GetRateAdjustedDifficulty(originalDifficulty, rate); - haveRateChangedValues = isDifferentArOd(originalDifficulty, adjustedDifficulty); + haveRateChangedValues = hasRateAdjustedProperties(originalDifficulty, adjustedDifficulty); approachRateDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(originalDifficulty.ApproachRate, adjustedDifficulty.ApproachRate); overallDifficultyDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(originalDifficulty.OverallDifficulty, adjustedDifficulty.OverallDifficulty); @@ -192,22 +192,34 @@ namespace osu.Game.Overlays.Mods overallDifficultyDisplay.Current.Value = adjustedDifficulty.OverallDifficulty; }); - private bool isDifferentArOd(BeatmapDifficulty? a, BeatmapDifficulty? b) - { - if (a == null && b == null) return false; - if (a == null || b == null) return true; - - if (!Precision.AlmostEquals(a.ApproachRate, b.ApproachRate, 0.01)) return true; - if (!Precision.AlmostEquals(a.OverallDifficulty, b.OverallDifficulty, 0.01)) return true; - - return false; - } - private void updateCollapsedState() { RightContent.FadeTo(Collapsed.Value && !IsHovered ? 0 : 1, transition_duration, Easing.OutQuint); } + public LocalisableString TooltipText + { + get + { + if (haveRateChangedValues) + { + return $"One or more values are being adjusted by mods that change speed." + + $" (AR {originalDifficulty?.ApproachRate ?? 0}→{adjustedDifficulty?.ApproachRate ?? 0}, " + + $"OD {originalDifficulty?.OverallDifficulty ?? 0}→{adjustedDifficulty?.OverallDifficulty ?? 0})"; + } + + return string.Empty; + } + } + + private static bool hasRateAdjustedProperties(BeatmapDifficulty a, BeatmapDifficulty b) + { + if (!Precision.AlmostEquals(a.ApproachRate, b.ApproachRate)) return true; + if (!Precision.AlmostEquals(a.OverallDifficulty, b.OverallDifficulty)) return true; + + return false; + } + private partial class BPMDisplay : RollingCounter { protected override double RollingDuration => 500; @@ -222,19 +234,5 @@ namespace osu.Game.Overlays.Mods UseFullGlyphHeight = false, }; } - - public LocalisableString TooltipText - { - get - { - if (haveRateChangedValues) - { - return LocalisableString.Format("Values are changed by mods that change speed.\n" + - "Original values: AR = {0}, OD = {1}", originalDifficulty?.ApproachRate ?? 0, originalDifficulty?.OverallDifficulty ?? 0); - } - - return ""; - } - } } } From a04f9aaef7870d8ee94b85598aaca1d25dc22840 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 12 Nov 2023 16:20:13 +0900 Subject: [PATCH 1488/4092] Apply various inspections --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 4 ++-- osu.Game.Rulesets.Osu/OsuRuleset.cs | 6 ++---- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 382ac29b41..7327fb4513 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -238,12 +238,12 @@ namespace osu.Game.Rulesets.Catch { BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(baseDifficulty); - double preempt = adjustedDifficulty.ApproachRate < 5 ? (1200.0 + 600.0 * (5 - adjustedDifficulty.ApproachRate) / 5) : (1200.0 - 750.0 * (adjustedDifficulty.ApproachRate - 5) / 5); + double preempt = adjustedDifficulty.ApproachRate < 6 ? (1200.0 + 600.0 * (5 - adjustedDifficulty.ApproachRate) / 5) : (1200.0 - 750.0 * (adjustedDifficulty.ApproachRate - 5) / 5); preempt /= rate; adjustedDifficulty.ApproachRate = (float)(preempt > 1200 ? ((1800 - preempt) / 120) : ((1200 - preempt) / 150 + 5)); - return adjustedDifficulty ?? (BeatmapDifficulty)baseDifficulty; + return adjustedDifficulty; } } } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 2d12e9ebc5..f4cf9409e8 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -334,9 +334,7 @@ namespace osu.Game.Rulesets.Osu { BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(baseDifficulty); - double preempt = adjustedDifficulty.ApproachRate < 5 ? - (1200.0 + 600.0 * (5 - adjustedDifficulty.ApproachRate) / 5) : - (1200.0 - 750.0 * (adjustedDifficulty.ApproachRate - 5) / 5); + double preempt = adjustedDifficulty.ApproachRate < 5 ? (1200.0 + 600.0 * (5 - adjustedDifficulty.ApproachRate) / 5) : (1200.0 - 750.0 * (adjustedDifficulty.ApproachRate - 5) / 5); preempt /= rate; adjustedDifficulty.ApproachRate = (float)(preempt > 1200 ? ((1800 - preempt) / 120) : ((1200 - preempt) / 150 + 5)); @@ -344,7 +342,7 @@ namespace osu.Game.Rulesets.Osu hitwindow /= rate; adjustedDifficulty.OverallDifficulty = (float)(80.0 - hitwindow) / 6; - return adjustedDifficulty ?? (BeatmapDifficulty)baseDifficulty; + return adjustedDifficulty; } } } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index aaf32d35f2..88d0087622 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -273,7 +273,7 @@ namespace osu.Game.Rulesets.Taiko hitwindow /= rate; adjustedDifficulty.OverallDifficulty = (float)(5 * (35 - hitwindow) / 15 + 5); - return adjustedDifficulty ?? (BeatmapDifficulty)baseDifficulty; + return adjustedDifficulty; } } } From 798e677c092a4ad02e0fe0a81163c14b57ff90d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 12 Nov 2023 15:12:04 +0900 Subject: [PATCH 1489/4092] Refactor `KeyCounterDisplay` to use autosize A previous attempt at this was unsuccessful due to a partially off-screen elements not getting the correct size early enough (see https://github.com/ppy/osu/issues/14793). This can be accounted for by setting `AlwaysPresent` when visibility is expected. This fixes [test failures](https://github.com/ppy/osu/actions/runs/6838444698/job/18595535795) due to the newly added `Width` / `Height` being persisted with floating-point errors (by not persisting the values in the first place, via `AutoSize.Both`). --- osu.Game/Screens/Play/ArgonKeyCounterDisplay.cs | 12 ------------ .../Play/HUD/DefaultKeyCounterDisplay.cs | 14 -------------- osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs | 17 ++++++++++++++++- 3 files changed, 16 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/Play/ArgonKeyCounterDisplay.cs b/osu.Game/Screens/Play/ArgonKeyCounterDisplay.cs index 984c2a7287..44b90fcad0 100644 --- a/osu.Game/Screens/Play/ArgonKeyCounterDisplay.cs +++ b/osu.Game/Screens/Play/ArgonKeyCounterDisplay.cs @@ -10,8 +10,6 @@ namespace osu.Game.Screens.Play { public partial class ArgonKeyCounterDisplay : KeyCounterDisplay { - private const int duration = 100; - protected override FillFlowContainer KeyFlow { get; } public ArgonKeyCounterDisplay() @@ -25,16 +23,6 @@ namespace osu.Game.Screens.Play }; } - protected override void Update() - { - base.Update(); - - Size = KeyFlow.Size; - } - protected override KeyCounter CreateCounter(InputTrigger trigger) => new ArgonKeyCounter(trigger); - - protected override void UpdateVisibility() - => KeyFlow.FadeTo(AlwaysVisible.Value || ConfigVisibility.Value ? 1 : 0, duration); } } diff --git a/osu.Game/Screens/Play/HUD/DefaultKeyCounterDisplay.cs b/osu.Game/Screens/Play/HUD/DefaultKeyCounterDisplay.cs index e459574243..e0f96d32bc 100644 --- a/osu.Game/Screens/Play/HUD/DefaultKeyCounterDisplay.cs +++ b/osu.Game/Screens/Play/HUD/DefaultKeyCounterDisplay.cs @@ -10,7 +10,6 @@ namespace osu.Game.Screens.Play.HUD { public partial class DefaultKeyCounterDisplay : KeyCounterDisplay { - private const int duration = 100; private const double key_fade_time = 80; protected override FillFlowContainer KeyFlow { get; } @@ -25,15 +24,6 @@ namespace osu.Game.Screens.Play.HUD }; } - protected override void Update() - { - base.Update(); - - // Don't use autosize as it will shrink to zero when KeyFlow is hidden. - // In turn this can cause the display to be masked off screen and never become visible again. - Size = KeyFlow.Size; - } - protected override KeyCounter CreateCounter(InputTrigger trigger) => new DefaultKeyCounter(trigger) { FadeTime = key_fade_time, @@ -41,10 +31,6 @@ namespace osu.Game.Screens.Play.HUD KeyUpTextColor = KeyUpTextColor, }; - protected override void UpdateVisibility() => - // Isolate changing visibility of the key counters from fading this component. - KeyFlow.FadeTo(AlwaysVisible.Value || ConfigVisibility.Value ? 1 : 0, duration); - private Color4 keyDownTextColor = Color4.DarkGray; public Color4 KeyDownTextColor diff --git a/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs b/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs index e7e866932e..0a5d6b763e 100644 --- a/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs +++ b/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs @@ -4,6 +4,7 @@ using System.Collections.Specialized; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Configuration; using osu.Game.Rulesets.UI; @@ -31,13 +32,27 @@ namespace osu.Game.Screens.Play.HUD [Resolved] private InputCountController controller { get; set; } = null!; - protected abstract void UpdateVisibility(); + private const int duration = 100; + + protected void UpdateVisibility() + { + bool visible = AlwaysVisible.Value || ConfigVisibility.Value; + + // Isolate changing visibility of the key counters from fading this component. + KeyFlow.FadeTo(visible ? 1 : 0, duration); + + // Ensure a valid size is immediately obtained even if partially off-screen + // See https://github.com/ppy/osu/issues/14793. + KeyFlow.AlwaysPresent = visible; + } protected abstract KeyCounter CreateCounter(InputTrigger trigger); [BackgroundDependencyLoader] private void load(OsuConfigManager config, DrawableRuleset? drawableRuleset) { + AutoSizeAxes = Axes.Both; + config.BindWith(OsuSetting.KeyOverlay, ConfigVisibility); if (drawableRuleset != null) From 31feeb5ddc616bb437c052b2b1680070c9d72563 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 12 Nov 2023 17:21:17 +0900 Subject: [PATCH 1490/4092] Disable new rider EAP inspection in test class --- osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index 93005271a9..d0a45856b2 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -203,6 +203,7 @@ namespace osu.Game.Tests.Visual.Ranking public IBeatmap Beatmap { get; } + // ReSharper disable once NotNullOrRequiredMemberIsNotInitialized public TestBeatmapConverter(IBeatmap beatmap) { Beatmap = beatmap; From 469b9e2546a0eea247f19bdc9e793170fe7c7b05 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 12 Nov 2023 17:28:15 +0900 Subject: [PATCH 1491/4092] Increase size and adjust positioning of max combo display in argon skin --- osu.Game/Skinning/ArgonSkin.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 715d5e4600..637467c748 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -182,15 +182,14 @@ namespace osu.Game.Skinning if (songProgress != null) { const float padding = 10; + // Hard to find this at runtime, so taken from the most expanded state during replay. + const float song_progress_offset_height = 36 + padding; songProgress.Position = new Vector2(0, -padding); songProgress.Scale = new Vector2(0.9f, 1); if (keyCounter != null && hitError != null) { - // Hard to find this at runtime, so taken from the most expanded state during replay. - const float song_progress_offset_height = 36 + padding; - keyCounter.Anchor = Anchor.BottomRight; keyCounter.Origin = Anchor.BottomRight; keyCounter.Position = new Vector2(-(hitError.Width + padding), -(padding * 2 + song_progress_offset_height)); @@ -200,7 +199,7 @@ namespace osu.Game.Skinning { combo.Anchor = Anchor.BottomLeft; combo.Origin = Anchor.BottomLeft; - combo.Position = new Vector2(hitError.Width + padding, -50); + combo.Position = new Vector2((hitError.Width + padding), -(padding * 2 + song_progress_offset_height)); } } } @@ -221,7 +220,10 @@ namespace osu.Game.Skinning new ArgonHealthDisplay(), new BoxElement(), new ArgonAccuracyCounter(), - new ArgonComboCounter(), + new ArgonComboCounter + { + Scale = new Vector2(1.3f) + }, new BarHitErrorMeter(), new BarHitErrorMeter(), new ArgonSongProgress(), From 4e7c40f1d7392376d68989ad5fa26e53430c55c6 Mon Sep 17 00:00:00 2001 From: ratinfx Date: Sun, 12 Nov 2023 14:58:46 +0100 Subject: [PATCH 1492/4092] Do Split and Parse before checking HitObjects --- .../Edit/ManiaHitObjectComposer.cs | 28 ++++++++----------- .../Edit/OsuHitObjectComposer.cs | 19 +++++-------- 2 files changed, 18 insertions(+), 29 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index b04d3f895d..d580f2d025 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -60,33 +60,27 @@ namespace osu.Game.Rulesets.Mania.Edit return; List remainingHitObjects = EditorBeatmap.HitObjects.Cast().Where(h => h.StartTime >= timestamp).ToList(); - string[] split = objectDescription.Split(',').ToArray(); + string[] splitDescription = objectDescription.Split(',').ToArray(); - for (int i = 0; i < split.Length; i++) + for (int i = 0; i < splitDescription.Length; i++) { - ManiaHitObject current = remainingHitObjects.FirstOrDefault(h => shouldBeSelected(h, split[i])); + string[] split = splitDescription[i].Split('|').ToArray(); + if (split.Length != 2) + continue; + + if (!double.TryParse(split[0], out double time) || !int.TryParse(split[1], out int column)) + continue; + + ManiaHitObject current = remainingHitObjects.FirstOrDefault(h => h.StartTime == time && h.Column == column); if (current == null) continue; EditorBeatmap.SelectedHitObjects.Add(current); - if (i < split.Length - 1) + if (i < splitDescription.Length - 1) remainingHitObjects = remainingHitObjects.Where(h => h != current && h.StartTime >= current.StartTime).ToList(); } } - - private bool shouldBeSelected(ManiaHitObject hitObject, string objectInfo) - { - string[] split = objectInfo.Split('|').ToArray(); - if (split.Length != 2) - return false; - - if (!double.TryParse(split[0], out double time) || !int.TryParse(split[1], out int column)) - return false; - - return hitObject.StartTime == time - && hitObject.Column == column; - } } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index e9c222b0e7..1dfa02fe83 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -115,30 +115,25 @@ namespace osu.Game.Rulesets.Osu.Edit return; List remainingHitObjects = EditorBeatmap.HitObjects.Cast().Where(h => h.StartTime >= timestamp).ToList(); - string[] split = objectDescription.Split(',').ToArray(); + string[] splitDescription = objectDescription.Split(',').ToArray(); - for (int i = 0; i < split.Length; i++) + for (int i = 0; i < splitDescription.Length; i++) { - OsuHitObject current = remainingHitObjects.FirstOrDefault(h => shouldBeSelected(h, split[i])); + if (!int.TryParse(splitDescription[i], out int combo) || combo < 1) + continue; + + OsuHitObject current = remainingHitObjects.FirstOrDefault(h => h.IndexInCurrentCombo + 1 == combo); if (current == null) continue; EditorBeatmap.SelectedHitObjects.Add(current); - if (i < split.Length - 1) + if (i < splitDescription.Length - 1) remainingHitObjects = remainingHitObjects.Where(h => h != current && h.StartTime >= current.StartTime).ToList(); } } - private bool shouldBeSelected(OsuHitObject hitObject, string objectInfo) - { - if (!int.TryParse(objectInfo, out int combo) || combo < 1) - return false; - - return hitObject.IndexInCurrentCombo + 1 == combo; - } - private DistanceSnapGrid distanceSnapGrid; private Container distanceSnapGridContainer; From fab6fc9adb633d82d88afe4c76b1313946032ab4 Mon Sep 17 00:00:00 2001 From: ratinfx Date: Sun, 12 Nov 2023 15:09:15 +0100 Subject: [PATCH 1493/4092] Updated comments, renamed method --- osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs | 1 + osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 1 + osu.Game/OsuGame.cs | 4 ++-- osu.Game/Rulesets/Edit/EditorTimestampParser.cs | 6 ++++-- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index d580f2d025..92ecea812c 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -49,6 +49,7 @@ namespace osu.Game.Rulesets.Mania.Edit new HoldNoteCompositionTool() }; + // 123|0,456|1,789|2 ... private static readonly Regex selection_regex = new Regex(@"^\d+\|\d+(,\d+\|\d+)*$"); public override string ConvertSelectionToString() diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 1dfa02fe83..2afbd83ce5 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -104,6 +104,7 @@ namespace osu.Game.Rulesets.Osu.Edit protected override ComposeBlueprintContainer CreateBlueprintContainer() => new OsuBlueprintContainer(this); + // 1,2,3,4 ... private static readonly Regex selection_regex = new Regex(@"^\d+(,\d+)*$"); public override string ConvertSelectionToString() diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index cde8ee1457..4e37481ba7 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -434,7 +434,7 @@ namespace osu.Game break; case LinkAction.OpenEditorTimestamp: - SeekToTimestamp(argString); + HandleTimestamp(argString); break; case LinkAction.JoinMultiplayerMatch: @@ -558,7 +558,7 @@ namespace osu.Game /// Seek to a given timestamp in the Editor and select relevant HitObjects if needed /// /// The timestamp and the selected objects - public void SeekToTimestamp(string timestamp) + public void HandleTimestamp(string timestamp) { if (ScreenStack.CurrentScreen is not Editor editor) { diff --git a/osu.Game/Rulesets/Edit/EditorTimestampParser.cs b/osu.Game/Rulesets/Edit/EditorTimestampParser.cs index b1488d298f..e36822cc63 100644 --- a/osu.Game/Rulesets/Edit/EditorTimestampParser.cs +++ b/osu.Game/Rulesets/Edit/EditorTimestampParser.cs @@ -10,16 +10,18 @@ namespace osu.Game.Rulesets.Edit { public static class EditorTimestampParser { - // 00:00:000 (1,2,3) - test - // osu-web regex: https://github.com/ppy/osu-web/blob/3b1698639244cfdaf0b41c68bfd651ea729ec2e3/resources/js/utils/beatmapset-discussion-helper.ts#L78 + // 00:00:000 (...) - test + // original osu-web regex: https://github.com/ppy/osu-web/blob/3b1698639244cfdaf0b41c68bfd651ea729ec2e3/resources/js/utils/beatmapset-discussion-helper.ts#L78 public static readonly Regex TIME_REGEX = new Regex(@"\b(((\d{2,}):([0-5]\d)[:.](\d{3}))(\s\([^)]+\))?)"); public static string[] GetRegexGroups(string timestamp) { Match match = TIME_REGEX.Match(timestamp); + string[] result = match.Success ? match.Groups.Values.Where(x => x is not Match && !x.Value.Contains(':')).Select(x => x.Value).ToArray() : Array.Empty(); + return result; } From 26d493986c318e2a390ecf02af10785f5ee760f1 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 12 Nov 2023 18:05:18 +0200 Subject: [PATCH 1494/4092] Fixed precision and updated AdvancedStats --- .../Overlays/Mods/BeatmapAttributesDisplay.cs | 4 +- .../Screens/Select/Details/AdvancedStats.cs | 48 +++++++++---------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index 5531ed5461..bf39666f83 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -204,8 +204,8 @@ namespace osu.Game.Overlays.Mods if (haveRateChangedValues) { return $"One or more values are being adjusted by mods that change speed." + - $" (AR {originalDifficulty?.ApproachRate ?? 0}→{adjustedDifficulty?.ApproachRate ?? 0}, " + - $"OD {originalDifficulty?.OverallDifficulty ?? 0}→{adjustedDifficulty?.OverallDifficulty ?? 0})"; + $" (AR {originalDifficulty?.ApproachRate ?? 0}→{(adjustedDifficulty?.ApproachRate ?? 0):0.0#}, " + + $"OD {originalDifficulty?.OverallDifficulty ?? 0}→{(adjustedDifficulty?.OverallDifficulty ?? 0):0.0#})"; } return string.Empty; diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index f84422ae65..25c18f7328 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -46,6 +46,7 @@ namespace osu.Game.Screens.Select.Details private readonly StatisticRow starDifficulty; private BeatmapDifficulty originalDifficulty; + private BeatmapDifficulty adjustedDifficulty; private bool haveRateChangedValues; private IBeatmapInfo beatmapInfo; @@ -120,24 +121,25 @@ namespace osu.Game.Screens.Select.Details private void updateStatistics() { IBeatmapDifficultyInfo baseDifficulty = BeatmapInfo?.Difficulty; - BeatmapDifficulty adjustedDifficulty = null; if (baseDifficulty != null) { - adjustedDifficulty = new BeatmapDifficulty(baseDifficulty); + originalDifficulty = new BeatmapDifficulty(baseDifficulty); foreach (var mod in mods.Value.OfType()) - mod.ApplyToDifficulty(adjustedDifficulty); + mod.ApplyToDifficulty(originalDifficulty); - originalDifficulty = adjustedDifficulty; + if (gameRuleset != null) + { + Ruleset ruleset = gameRuleset.Value.CreateInstance(); - Ruleset ruleset = gameRuleset.Value.CreateInstance(); - double rate = 1; - foreach (var mod in mods.Value.OfType()) - rate = mod.ApplyToRate(0, rate); - adjustedDifficulty = ruleset.GetRateAdjustedDifficulty(adjustedDifficulty, rate); + double rate = 1; + foreach (var mod in mods.Value.OfType()) + rate = mod.ApplyToRate(0, rate); - haveRateChangedValues = isDifferentArOd(originalDifficulty, adjustedDifficulty); + adjustedDifficulty = ruleset.GetRateAdjustedDifficulty(originalDifficulty, rate); + haveRateChangedValues = hasRateAdjustedProperties(originalDifficulty, adjustedDifficulty); + } } switch (BeatmapInfo?.Ruleset.OnlineID) @@ -202,31 +204,29 @@ namespace osu.Game.Screens.Select.Details starDifficultyCancellationSource?.Cancel(); } - private bool isDifferentArOd(BeatmapDifficulty a, BeatmapDifficulty b) - { - if (a == null && b == null) return false; - if (a == null || b == null) return true; - - if (!Precision.AlmostEquals(a.ApproachRate, b.ApproachRate, 0.01)) return true; - if (!Precision.AlmostEquals(a.OverallDifficulty, b.OverallDifficulty, 0.01)) return true; - - return false; - } - public LocalisableString TooltipText { get { if (haveRateChangedValues) { - return LocalisableString.Format("Values are changed by mods that change speed.\n" + - "Original values: AR = {0}, OD = {1}", originalDifficulty?.ApproachRate ?? 0, originalDifficulty?.OverallDifficulty ?? 0); + return $"One or more values are being adjusted by mods that change speed." + + $" (AR {originalDifficulty?.ApproachRate ?? 0}→{(adjustedDifficulty?.ApproachRate ?? 0):0.0#}, " + + $"OD {originalDifficulty?.OverallDifficulty ?? 0}→{(adjustedDifficulty?.OverallDifficulty ?? 0):0.0#})"; } - return ""; + return string.Empty; } } + private static bool hasRateAdjustedProperties(BeatmapDifficulty a, BeatmapDifficulty b) + { + if (!Precision.AlmostEquals(a.ApproachRate, b.ApproachRate)) return true; + if (!Precision.AlmostEquals(a.OverallDifficulty, b.OverallDifficulty)) return true; + + return false; + } + public partial class StatisticRow : Container, IHasAccentColour { private const float value_width = 25; From d123ba5bcef07e6893cb8c2d633e5c35b987bccf Mon Sep 17 00:00:00 2001 From: Poyo Date: Sun, 12 Nov 2023 11:13:38 -0800 Subject: [PATCH 1495/4092] Fix broken tests --- .../NonVisual/Ranking/UnstableRateTest.cs | 8 +++--- .../Gameplay/TestSceneUnstableRateCounter.cs | 4 ++- ...estSceneHitEventTimingDistributionGraph.cs | 28 +++++++++---------- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs b/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs index 27c8270f0f..bde3c37af7 100644 --- a/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs +++ b/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tests.NonVisual.Ranking public void TestDistributedHits() { var events = Enumerable.Range(-5, 11) - .Select(t => new HitEvent(t - 5, HitResult.Great, new HitObject(), null, null)); + .Select(t => new HitEvent(t - 5, 1.0, HitResult.Great, new HitObject(), null, null)); var unstableRate = new UnstableRate(events); @@ -33,9 +33,9 @@ namespace osu.Game.Tests.NonVisual.Ranking { var events = new[] { - new HitEvent(-100, HitResult.Miss, new HitObject(), null, null), - new HitEvent(0, HitResult.Great, new HitObject(), null, null), - new HitEvent(200, HitResult.Meh, new HitObject { HitWindows = HitWindows.Empty }, null, null), + new HitEvent(-100, 1.0, HitResult.Miss, new HitObject(), null, null), + new HitEvent(0, 1.0, HitResult.Great, new HitObject(), null, null), + new HitEvent(200, 1.0, HitResult.Meh, new HitObject { HitWindows = HitWindows.Empty }, null, null), }; var unstableRate = new UnstableRate(events); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs index d0e516ed39..f6c819b329 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs @@ -56,6 +56,7 @@ namespace osu.Game.Tests.Visual.Gameplay scoreProcessor.RevertResult( new JudgementResult(new HitCircle { HitWindows = hitWindows }, new Judgement()) { + GameplayRate = 1.0, TimeOffset = 25, Type = HitResult.Perfect, }); @@ -92,7 +93,7 @@ namespace osu.Game.Tests.Visual.Gameplay }); } - private void applyJudgement(double offsetMs, bool alt) + private void applyJudgement(double offsetMs, bool alt, double gameplayRate = 1.0) { double placement = offsetMs; @@ -105,6 +106,7 @@ namespace osu.Game.Tests.Visual.Gameplay scoreProcessor.ApplyResult(new JudgementResult(new HitCircle { HitWindows = hitWindows }, new Judgement()) { TimeOffset = placement, + GameplayRate = gameplayRate, Type = HitResult.Perfect, }); } diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs index a40cb41e2c..325a535731 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs @@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestAroundCentre() { - createTest(Enumerable.Range(-150, 300).Select(i => new HitEvent(i / 50f, HitResult.Perfect, placeholder_object, placeholder_object, null)).ToList()); + createTest(Enumerable.Range(-150, 300).Select(i => new HitEvent(i / 50f, 1.0, HitResult.Perfect, placeholder_object, placeholder_object, null)).ToList()); } [Test] @@ -57,12 +57,12 @@ namespace osu.Game.Tests.Visual.Ranking { createTest(new List { - new HitEvent(-7, HitResult.Perfect, placeholder_object, placeholder_object, null), - new HitEvent(-6, HitResult.Perfect, placeholder_object, placeholder_object, null), - new HitEvent(-5, HitResult.Perfect, placeholder_object, placeholder_object, null), - new HitEvent(5, HitResult.Perfect, placeholder_object, placeholder_object, null), - new HitEvent(6, HitResult.Perfect, placeholder_object, placeholder_object, null), - new HitEvent(7, HitResult.Perfect, placeholder_object, placeholder_object, null), + new HitEvent(-7, 1.0, HitResult.Perfect, placeholder_object, placeholder_object, null), + new HitEvent(-6, 1.0, HitResult.Perfect, placeholder_object, placeholder_object, null), + new HitEvent(-5, 1.0, HitResult.Perfect, placeholder_object, placeholder_object, null), + new HitEvent(5, 1.0, HitResult.Perfect, placeholder_object, placeholder_object, null), + new HitEvent(6, 1.0, HitResult.Perfect, placeholder_object, placeholder_object, null), + new HitEvent(7, 1.0, HitResult.Perfect, placeholder_object, placeholder_object, null), }); } @@ -78,7 +78,7 @@ namespace osu.Game.Tests.Visual.Ranking : offset > 16 ? HitResult.Good : offset > 8 ? HitResult.Great : HitResult.Perfect; - return new HitEvent(h.TimeOffset, result, placeholder_object, placeholder_object, null); + return new HitEvent(h.TimeOffset, 1.0, result, placeholder_object, placeholder_object, null); }).ToList()); } @@ -95,7 +95,7 @@ namespace osu.Game.Tests.Visual.Ranking : offset > 8 ? HitResult.Great : HitResult.Perfect; - return new HitEvent(h.TimeOffset, result, placeholder_object, placeholder_object, null); + return new HitEvent(h.TimeOffset, 1.0, result, placeholder_object, placeholder_object, null); }); var narrow = CreateDistributedHitEvents(0, 50).Select(h => { @@ -106,7 +106,7 @@ namespace osu.Game.Tests.Visual.Ranking : offset > 10 ? HitResult.Good : offset > 5 ? HitResult.Great : HitResult.Perfect; - return new HitEvent(h.TimeOffset, result, placeholder_object, placeholder_object, null); + return new HitEvent(h.TimeOffset, 1.0, result, placeholder_object, placeholder_object, null); }); createTest(wide.Concat(narrow).ToList()); } @@ -114,7 +114,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestZeroTimeOffset() { - createTest(Enumerable.Range(0, 100).Select(_ => new HitEvent(0, HitResult.Perfect, placeholder_object, placeholder_object, null)).ToList()); + createTest(Enumerable.Range(0, 100).Select(_ => new HitEvent(0, 1.0, HitResult.Perfect, placeholder_object, placeholder_object, null)).ToList()); } [Test] @@ -129,9 +129,9 @@ namespace osu.Game.Tests.Visual.Ranking createTest(Enumerable.Range(0, 100).Select(i => { if (i % 2 == 0) - return new HitEvent(0, HitResult.Perfect, placeholder_object, placeholder_object, null); + return new HitEvent(0, 1.0, HitResult.Perfect, placeholder_object, placeholder_object, null); - return new HitEvent(30, HitResult.Miss, placeholder_object, placeholder_object, null); + return new HitEvent(30, 1.0, HitResult.Miss, placeholder_object, placeholder_object, null); }).ToList()); } @@ -162,7 +162,7 @@ namespace osu.Game.Tests.Visual.Ranking int count = (int)(Math.Pow(range - Math.Abs(i - range), 2)) / 10; for (int j = 0; j < count; j++) - hitEvents.Add(new HitEvent(centre + i - range, HitResult.Perfect, placeholder_object, placeholder_object, null)); + hitEvents.Add(new HitEvent(centre + i - range, 1.0, HitResult.Perfect, placeholder_object, placeholder_object, null)); } return hitEvents; From 8c36604e58e84265d623f0ec19e3eec85370ef4d Mon Sep 17 00:00:00 2001 From: Poyo Date: Sun, 12 Nov 2023 11:16:06 -0800 Subject: [PATCH 1496/4092] Add rate-change UR tests --- .../NonVisual/Ranking/UnstableRateTest.cs | 32 +++++++++++++++++++ .../Gameplay/TestSceneUnstableRateCounter.cs | 22 +++++++++++++ 2 files changed, 54 insertions(+) diff --git a/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs b/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs index bde3c37af7..5a416d05d7 100644 --- a/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs +++ b/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs @@ -42,5 +42,37 @@ namespace osu.Game.Tests.NonVisual.Ranking Assert.AreEqual(0, unstableRate.Value); } + + [Test] + public void TestStaticRateChange() + { + var events = new[] + { + new HitEvent(-150, 1.5, HitResult.Great, new HitObject(), null, null), + new HitEvent(-150, 1.5, HitResult.Great, new HitObject(), null, null), + new HitEvent(150, 1.5, HitResult.Great, new HitObject(), null, null), + new HitEvent(150, 1.5, HitResult.Great, new HitObject(), null, null), + }; + + var unstableRate = new UnstableRate(events); + + Assert.AreEqual(10 * 100, unstableRate.Value); + } + + [Test] + public void TestDynamicRateChange() + { + var events = new[] + { + new HitEvent(-50, 0.5, HitResult.Great, new HitObject(), null, null), + new HitEvent(75, 0.75, HitResult.Great, new HitObject(), null, null), + new HitEvent(-100, 1.0, HitResult.Great, new HitObject(), null, null), + new HitEvent(125, 1.25, HitResult.Great, new HitObject(), null, null), + }; + + var unstableRate = new UnstableRate(events); + + Assert.AreEqual(10 * 100, unstableRate.Value); + } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs index f6c819b329..73ec6ea335 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -81,6 +82,27 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("UR = 250", () => counter.Current.Value == 250.0); } + [Test] + public void TestStaticRateChange() + { + AddStep("Create Display", recreateDisplay); + + AddRepeatStep("Set UR to 250 at 1.5x", () => applyJudgement(25, true, 1.5), 4); + + AddUntilStep("UR = 250/1.5", () => counter.Current.Value == Math.Round(250.0 / 1.5)); + } + + [Test] + public void TestDynamicRateChange() + { + AddStep("Create Display", recreateDisplay); + + AddRepeatStep("Set UR to 100 at 1.0x", () => applyJudgement(10, true, 1.0), 4); + AddRepeatStep("Bring UR to 100 at 1.5x", () => applyJudgement(15, true, 1.5), 4); + + AddUntilStep("UR = 100", () => counter.Current.Value == 100.0); + } + private void recreateDisplay() { Clear(); From e67725f5d6c9b6334c3e01a5717b06cd507a1f63 Mon Sep 17 00:00:00 2001 From: Poyo Date: Sun, 12 Nov 2023 12:14:19 -0800 Subject: [PATCH 1497/4092] Use IGameplayClock for rate --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 0843fd5bdc..4ae59dccb1 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -154,6 +154,9 @@ namespace osu.Game.Rulesets.Objects.Drawables [Resolved(CanBeNull = true)] private IPooledHitObjectProvider pooledObjectProvider { get; set; } + [Resolved] + private IGameplayClock gameplayClock { get; set; } = null!; + /// /// Whether the initialization logic in has applied. /// @@ -704,7 +707,7 @@ namespace osu.Game.Rulesets.Objects.Drawables } Result.RawTime = Time.Current; - Result.GameplayRate = Clock.Rate; + Result.GameplayRate = gameplayClock.GetTrueGameplayRate(); if (Result.HasResult) updateState(Result.IsHit ? ArmedState.Hit : ArmedState.Miss); From f794d4dc8384e0bba90d5063e5285a063adabcf8 Mon Sep 17 00:00:00 2001 From: Poyo Date: Sun, 12 Nov 2023 13:29:40 -0800 Subject: [PATCH 1498/4092] Allow gameplayClock to be null --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 4ae59dccb1..7cfb6aedf0 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -154,8 +154,8 @@ namespace osu.Game.Rulesets.Objects.Drawables [Resolved(CanBeNull = true)] private IPooledHitObjectProvider pooledObjectProvider { get; set; } - [Resolved] - private IGameplayClock gameplayClock { get; set; } = null!; + [Resolved(CanBeNull = true)] + private IGameplayClock gameplayClock { get; set; } /// /// Whether the initialization logic in has applied. @@ -707,7 +707,7 @@ namespace osu.Game.Rulesets.Objects.Drawables } Result.RawTime = Time.Current; - Result.GameplayRate = gameplayClock.GetTrueGameplayRate(); + Result.GameplayRate = gameplayClock?.GetTrueGameplayRate() ?? 1.0; if (Result.HasResult) updateState(Result.IsHit ? ArmedState.Hit : ArmedState.Miss); From c24b3543ab271536b2b98c3c4777a174bc0621c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Mu=CC=88ller-Ho=CC=88hne?= Date: Mon, 13 Nov 2023 12:47:12 +0900 Subject: [PATCH 1499/4092] Fix OnDragStart on macOS --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 20f11c3585..fe01b1b6d2 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -165,7 +165,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders if (HitObject.Path.ControlPoints.Count < 3) { var lastCp = HitObject.Path.ControlPoints.LastOrDefault(); - if (lastCp != cursor) + if (lastCp != cursor && HitObject.Path.ControlPoints.Count == 2) return false; bSplineBuilder.Clear(); From e6b4dfba369ee790ee0cee427aa702a935ce341b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Mu=CC=88ller-Ho=CC=88hne?= Date: Mon, 13 Nov 2023 12:49:59 +0900 Subject: [PATCH 1500/4092] Fix doubled control point at beginning of drawn slider --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index fe01b1b6d2..9ac28fe82a 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -195,7 +195,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { var cps = self.bSplineBuilder.GetControlPoints(); self.HitObject.Path.ControlPoints.RemoveRange(1, self.HitObject.Path.ControlPoints.Count - 1); - self.HitObject.Path.ControlPoints.AddRange(cps.Select(v => new PathControlPoint(v))); + self.HitObject.Path.ControlPoints.AddRange(cps.Skip(1).Select(v => new PathControlPoint(v))); }, this); } From 5fd55e55c08130773e72805ce7785b6da69031b6 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 13 Nov 2023 12:59:36 +0900 Subject: [PATCH 1501/4092] Add flag for combo end bonus to legacy processor --- osu.Game.Rulesets.Osu/Scoring/LegacyOsuHealthProcessor.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Scoring/LegacyOsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/LegacyOsuHealthProcessor.cs index 103569ffc3..e6db76e3b6 100644 --- a/osu.Game.Rulesets.Osu/Scoring/LegacyOsuHealthProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/LegacyOsuHealthProcessor.cs @@ -20,6 +20,8 @@ namespace osu.Game.Rulesets.Osu.Scoring private const double hp_slider_repeat = 4; private const double hp_slider_tick = 3; + public bool ComboEndBonus { get; set; } + private double lowestHpEver; private double lowestHpEnd; private double lowestHpComboEnd; @@ -129,7 +131,7 @@ namespace osu.Game.Rulesets.Osu.Scoring break; } - if (i == Beatmap.HitObjects.Count - 1 || ((OsuHitObject)Beatmap.HitObjects[i + 1]).NewCombo) + if (ComboEndBonus && (i == Beatmap.HitObjects.Count - 1 || ((OsuHitObject)Beatmap.HitObjects[i + 1]).NewCombo)) { increaseHp(hpMultiplierComboEnd * hp_combo_geki + hpMultiplierNormal * hp_hit_300); From 929570656d7a25fdea59141b1385a5a207b1bfc4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 13 Nov 2023 13:12:46 +0900 Subject: [PATCH 1502/4092] Disallow legacy health processor from being used for gameplay --- .../Scoring/LegacyOsuHealthProcessor.cs | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Scoring/LegacyOsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/LegacyOsuHealthProcessor.cs index e6db76e3b6..aa9312500b 100644 --- a/osu.Game.Rulesets.Osu/Scoring/LegacyOsuHealthProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/LegacyOsuHealthProcessor.cs @@ -5,13 +5,17 @@ using System; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Scoring { - // Reference implementation for osu!stable's HP drain. + /// + /// Reference implementation for osu!stable's HP drain. + /// Cannot be used for gameplay. + /// public partial class LegacyOsuHealthProcessor : LegacyDrainingHealthProcessor { private const double hp_bar_maximum = 200; @@ -20,8 +24,6 @@ namespace osu.Game.Rulesets.Osu.Scoring private const double hp_slider_repeat = 4; private const double hp_slider_tick = 3; - public bool ComboEndBonus { get; set; } - private double lowestHpEver; private double lowestHpEnd; private double lowestHpComboEnd; @@ -44,6 +46,18 @@ namespace osu.Game.Rulesets.Osu.Scoring base.ApplyBeatmap(beatmap); } + protected override void ApplyResultInternal(JudgementResult result) + { + if (!IsSimulating) + throw new NotSupportedException("The legacy osu! health processor is not supported for gameplay."); + } + + protected override void RevertResultInternal(JudgementResult result) + { + if (!IsSimulating) + throw new NotSupportedException("The legacy osu! health processor is not supported for gameplay."); + } + protected override void Reset(bool storeResults) { hpMultiplierNormal = 1; @@ -131,7 +145,7 @@ namespace osu.Game.Rulesets.Osu.Scoring break; } - if (ComboEndBonus && (i == Beatmap.HitObjects.Count - 1 || ((OsuHitObject)Beatmap.HitObjects[i + 1]).NewCombo)) + if (i == Beatmap.HitObjects.Count - 1 || ((OsuHitObject)Beatmap.HitObjects[i + 1]).NewCombo) { increaseHp(hpMultiplierComboEnd * hp_combo_geki + hpMultiplierNormal * hp_hit_300); From 7713da499f6cb08b81cfcaf571ad79fb36f20752 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 13 Nov 2023 13:12:56 +0900 Subject: [PATCH 1503/4092] Make osu ruleset use the new health processor --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 607b83d379..aaf0ab41a0 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -48,6 +48,8 @@ namespace osu.Game.Rulesets.Osu public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(); + public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new OsuHealthProcessor(drainStartTime); + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap, this); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap); From 98e6b7744bf58db8db7e348a4935fe9c5fe06e78 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 13 Nov 2023 13:46:47 +0900 Subject: [PATCH 1504/4092] Cleanup --- .../Scoring/LegacyOsuHealthProcessor.cs | 11 +-- .../Scoring/OsuHealthProcessor.cs | 12 +-- .../Scoring/DrainingHealthProcessor.cs | 54 ++++++----- .../Scoring/LegacyDrainingHealthProcessor.cs | 90 ------------------- 4 files changed, 47 insertions(+), 120 deletions(-) delete mode 100644 osu.Game/Rulesets/Scoring/LegacyDrainingHealthProcessor.cs diff --git a/osu.Game.Rulesets.Osu/Scoring/LegacyOsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/LegacyOsuHealthProcessor.cs index aa9312500b..f918868715 100644 --- a/osu.Game.Rulesets.Osu/Scoring/LegacyOsuHealthProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/LegacyOsuHealthProcessor.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Scoring /// Reference implementation for osu!stable's HP drain. /// Cannot be used for gameplay. /// - public partial class LegacyOsuHealthProcessor : LegacyDrainingHealthProcessor + public partial class LegacyOsuHealthProcessor : DrainingHealthProcessor { private const double hp_bar_maximum = 200; private const double hp_combo_geki = 14; @@ -24,6 +24,9 @@ namespace osu.Game.Rulesets.Osu.Scoring private const double hp_slider_repeat = 4; private const double hp_slider_tick = 3; + public Action? OnIterationFail; + public Action? OnIterationSuccess; + private double lowestHpEver; private double lowestHpEnd; private double lowestHpComboEnd; @@ -187,13 +190,11 @@ namespace osu.Game.Rulesets.Osu.Scoring if (fail) { - if (Log) - Console.WriteLine($"FAILED drop {testDrop / hp_bar_maximum}: {failReason}"); + OnIterationFail?.Invoke($"FAILED drop {testDrop / hp_bar_maximum}: {failReason}"); continue; } - if (Log) - Console.WriteLine($"PASSED drop {testDrop / hp_bar_maximum}"); + OnIterationSuccess?.Invoke($"PASSED drop {testDrop / hp_bar_maximum}"); return testDrop / hp_bar_maximum; } while (true); diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs index 2266cf9d33..3a096fca88 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Judgements; @@ -12,8 +13,11 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Scoring { - public partial class OsuHealthProcessor : LegacyDrainingHealthProcessor + public partial class OsuHealthProcessor : DrainingHealthProcessor { + public Action? OnIterationFail; + public Action? OnIterationSuccess; + private double lowestHpEver; private double lowestHpEnd; private double hpRecoveryAvailable; @@ -141,13 +145,11 @@ namespace osu.Game.Rulesets.Osu.Scoring if (fail) { - if (Log) - Console.WriteLine($"FAILED drop {testDrop}: {failReason}"); + OnIterationFail?.Invoke($"FAILED drop {testDrop}: {failReason}"); continue; } - if (Log) - Console.WriteLine($"PASSED drop {testDrop}"); + OnIterationSuccess?.Invoke($"PASSED drop {testDrop}"); return testDrop; } while (true); diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs index 2f81aa735e..a8808d08e5 100644 --- a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs @@ -41,15 +41,29 @@ namespace osu.Game.Rulesets.Scoring ///
private const double max_health_target = 0.4; - private IBeatmap beatmap; - private double gameplayEndTime; + /// + /// The drain rate as a proportion of the total health drained per millisecond. + /// + public double DrainRate { get; private set; } = 1; - private readonly double drainStartTime; - private readonly double drainLenience; + /// + /// The beatmap. + /// + protected IBeatmap Beatmap { get; private set; } + + /// + /// The time at which health starts draining. + /// + protected readonly double DrainStartTime; + + /// + /// An amount of lenience to apply to the drain rate. + /// + protected readonly double DrainLenience; private readonly List<(double time, double health)> healthIncreases = new List<(double, double)>(); + private double gameplayEndTime; private double targetMinimumHealth; - private double drainRate = 1; private PeriodTracker noDrainPeriodTracker; @@ -63,8 +77,8 @@ namespace osu.Game.Rulesets.Scoring /// A value of 1 completely removes drain. public DrainingHealthProcessor(double drainStartTime, double drainLenience = 0) { - this.drainStartTime = drainStartTime; - this.drainLenience = Math.Clamp(drainLenience, 0, 1); + DrainStartTime = drainStartTime; + DrainLenience = Math.Clamp(drainLenience, 0, 1); } protected override void Update() @@ -75,16 +89,16 @@ namespace osu.Game.Rulesets.Scoring return; // When jumping in and out of gameplay time within a single frame, health should only be drained for the period within the gameplay time - double lastGameplayTime = Math.Clamp(Time.Current - Time.Elapsed, drainStartTime, gameplayEndTime); - double currentGameplayTime = Math.Clamp(Time.Current, drainStartTime, gameplayEndTime); + double lastGameplayTime = Math.Clamp(Time.Current - Time.Elapsed, DrainStartTime, gameplayEndTime); + double currentGameplayTime = Math.Clamp(Time.Current, DrainStartTime, gameplayEndTime); - if (drainLenience < 1) - Health.Value -= drainRate * (currentGameplayTime - lastGameplayTime); + if (DrainLenience < 1) + Health.Value -= DrainRate * (currentGameplayTime - lastGameplayTime); } public override void ApplyBeatmap(IBeatmap beatmap) { - this.beatmap = beatmap; + Beatmap = beatmap; if (beatmap.HitObjects.Count > 0) gameplayEndTime = beatmap.HitObjects[^1].GetEndTime(); @@ -105,7 +119,7 @@ namespace osu.Game.Rulesets.Scoring targetMinimumHealth = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, min_health_target, mid_health_target, max_health_target); // Add back a portion of the amount of HP to be drained, depending on the lenience requested. - targetMinimumHealth += drainLenience * (1 - targetMinimumHealth); + targetMinimumHealth += DrainLenience * (1 - targetMinimumHealth); // Ensure the target HP is within an acceptable range. targetMinimumHealth = Math.Clamp(targetMinimumHealth, 0, 1); @@ -125,15 +139,15 @@ namespace osu.Game.Rulesets.Scoring { base.Reset(storeResults); - drainRate = 1; + DrainRate = 1; if (storeResults) - drainRate = computeDrainRate(); + DrainRate = ComputeDrainRate(); healthIncreases.Clear(); } - private double computeDrainRate() + protected virtual double ComputeDrainRate() { if (healthIncreases.Count <= 1) return 0; @@ -152,17 +166,17 @@ namespace osu.Game.Rulesets.Scoring for (int i = 0; i < healthIncreases.Count; i++) { double currentTime = healthIncreases[i].time; - double lastTime = i > 0 ? healthIncreases[i - 1].time : drainStartTime; + double lastTime = i > 0 ? healthIncreases[i - 1].time : DrainStartTime; // Subtract any break time from the duration since the last object - if (beatmap.Breaks.Count > 0) + if (Beatmap.Breaks.Count > 0) { // Advance the last break occuring before the current time - while (currentBreak + 1 < beatmap.Breaks.Count && beatmap.Breaks[currentBreak + 1].EndTime < currentTime) + while (currentBreak + 1 < Beatmap.Breaks.Count && Beatmap.Breaks[currentBreak + 1].EndTime < currentTime) currentBreak++; if (currentBreak >= 0) - lastTime = Math.Max(lastTime, beatmap.Breaks[currentBreak].EndTime); + lastTime = Math.Max(lastTime, Beatmap.Breaks[currentBreak].EndTime); } // Apply health adjustments diff --git a/osu.Game/Rulesets/Scoring/LegacyDrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/LegacyDrainingHealthProcessor.cs deleted file mode 100644 index 5d2426e4b7..0000000000 --- a/osu.Game/Rulesets/Scoring/LegacyDrainingHealthProcessor.cs +++ /dev/null @@ -1,90 +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 System; -using System.Linq; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Objects; -using osu.Game.Utils; - -namespace osu.Game.Rulesets.Scoring -{ - /// - /// A which continuously drains health.
- /// At HP=0, the minimum health reached for a perfect play is 95%.
- /// At HP=5, the minimum health reached for a perfect play is 70%.
- /// At HP=10, the minimum health reached for a perfect play is 30%. - ///
- public abstract partial class LegacyDrainingHealthProcessor : HealthProcessor - { - protected double DrainStartTime { get; } - protected double GameplayEndTime { get; private set; } - - protected IBeatmap Beatmap { get; private set; } - protected PeriodTracker NoDrainPeriodTracker { get; private set; } - - public bool Log { get; set; } - - public double DrainRate { get; private set; } - - /// - /// Creates a new . - /// - /// The time after which draining should begin. - protected LegacyDrainingHealthProcessor(double drainStartTime) - { - DrainStartTime = drainStartTime; - } - - protected override void Update() - { - base.Update(); - - if (NoDrainPeriodTracker?.IsInAny(Time.Current) == true) - return; - - // When jumping in and out of gameplay time within a single frame, health should only be drained for the period within the gameplay time - double lastGameplayTime = Math.Clamp(Time.Current - Time.Elapsed, DrainStartTime, GameplayEndTime); - double currentGameplayTime = Math.Clamp(Time.Current, DrainStartTime, GameplayEndTime); - - Health.Value -= DrainRate * (currentGameplayTime - lastGameplayTime); - } - - public override void ApplyBeatmap(IBeatmap beatmap) - { - Beatmap = beatmap; - - if (beatmap.HitObjects.Count > 0) - GameplayEndTime = beatmap.HitObjects[^1].GetEndTime(); - - NoDrainPeriodTracker = new PeriodTracker(beatmap.Breaks.Select(breakPeriod => new Period( - beatmap.HitObjects - .Select(hitObject => hitObject.GetEndTime()) - .Where(endTime => endTime <= breakPeriod.StartTime) - .DefaultIfEmpty(double.MinValue) - .Last(), - beatmap.HitObjects - .Select(hitObject => hitObject.StartTime) - .Where(startTime => startTime >= breakPeriod.EndTime) - .DefaultIfEmpty(double.MaxValue) - .First() - ))); - - base.ApplyBeatmap(beatmap); - } - - protected override void Reset(bool storeResults) - { - base.Reset(storeResults); - - DrainRate = 1; - - if (storeResults) - DrainRate = ComputeDrainRate(); - } - - protected abstract double ComputeDrainRate(); - } -} From 8ad8764947835e9ce4070338b6e1ea81016df2b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Nov 2023 14:06:18 +0900 Subject: [PATCH 1505/4092] 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 2870696c03..1f6a65c450 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 f1159f58b9..70525a5c59 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From 65b41138a347718297f71509e6a1dbc30d468543 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 13 Nov 2023 14:06:24 +0900 Subject: [PATCH 1506/4092] Add option to disable combo end --- osu.Game.Rulesets.Osu/Scoring/LegacyOsuHealthProcessor.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Scoring/LegacyOsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/LegacyOsuHealthProcessor.cs index f918868715..e92c3c9b97 100644 --- a/osu.Game.Rulesets.Osu/Scoring/LegacyOsuHealthProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/LegacyOsuHealthProcessor.cs @@ -26,6 +26,7 @@ namespace osu.Game.Rulesets.Osu.Scoring public Action? OnIterationFail; public Action? OnIterationSuccess; + public bool ApplyComboEndBonus { get; set; } = true; private double lowestHpEver; private double lowestHpEnd; @@ -148,7 +149,7 @@ namespace osu.Game.Rulesets.Osu.Scoring break; } - if (i == Beatmap.HitObjects.Count - 1 || ((OsuHitObject)Beatmap.HitObjects[i + 1]).NewCombo) + if (ApplyComboEndBonus && (i == Beatmap.HitObjects.Count - 1 || ((OsuHitObject)Beatmap.HitObjects[i + 1]).NewCombo)) { increaseHp(hpMultiplierComboEnd * hp_combo_geki + hpMultiplierNormal * hp_hit_300); From 35d4c483d78399fa3322b7a1db79110da36a759f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 13 Nov 2023 14:06:34 +0900 Subject: [PATCH 1507/4092] Improve commenting around small tick/large tick health --- osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs index 3a096fca88..5802f8fc0d 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs @@ -185,12 +185,12 @@ namespace osu.Game.Rulesets.Osu.Scoring return IBeatmapDifficultyInfo.DifficultyRange(Beatmap.Difficulty.DrainRate, -0.03, -0.125, -0.2); case HitResult.SmallTickHit: - // This result is always as a result of the slider tail. + // This result always comes from the slider tail, which is judged the same as a repeat. increase = 0.02; break; case HitResult.LargeTickHit: - // This result is either a result of a slider tick or a repeat. + // This result comes from either a slider tick or repeat. increase = hitObject is SliderTick ? 0.015 : 0.02; break; From fa976a5aa0eeb94dccbcd58b00d51a1c9d7e52df Mon Sep 17 00:00:00 2001 From: cs Date: Mon, 13 Nov 2023 08:24:09 +0100 Subject: [PATCH 1508/4092] Fix code style/quality issues --- .../TestSceneJuiceStreamSelectionBlueprint.cs | 6 +++--- .../JuiceStreamPathTest.cs | 2 +- .../Checks/CheckOffscreenObjectsTest.cs | 2 +- .../TestSceneOsuEditorSelectInvalidPath.cs | 2 +- .../TestScenePathControlPointVisualiser.cs | 8 ++++---- .../TestSceneSliderControlPointPiece.cs | 18 ++++++++--------- .../Editor/TestSceneSliderLengthValidity.cs | 2 +- .../TestSceneSliderPlacementBlueprint.cs | 16 +++++++-------- .../Editor/TestSceneSliderReversal.cs | 2 +- .../Editor/TestSceneSliderSnapping.cs | 10 +++++----- .../Editor/TestSceneSliderSplitting.cs | 20 +++++++++---------- .../TestSceneSlider.cs | 6 +++--- .../TestSceneSliderInput.cs | 2 +- .../TestSceneSliderSnaking.cs | 6 +++--- .../Components/PathControlPointPiece.cs | 14 ++++++------- .../Components/PathControlPointVisualiser.cs | 2 +- .../Sliders/SliderPlacementBlueprint.cs | 2 +- .../Edit/OsuSliderDrawingSettingsProvider.cs | 8 +++++--- .../Formats/LegacyBeatmapDecoderTest.cs | 8 ++++---- .../Gameplay/TestSceneBezierConverter.cs | 8 ++++---- .../Visual/Gameplay/TestSceneSliderPath.cs | 4 ++-- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 18 +++++++---------- .../Edit/ComposerDistanceSnapProvider.cs | 2 +- osu.Game/Rulesets/Edit/IToolboxAttachment.cs | 10 ++++++++++ osu.Game/Rulesets/Objects/BezierConverter.cs | 8 ++++---- .../Objects/Legacy/ConvertHitObjectParser.cs | 10 +++++----- .../Rulesets/Objects/SliderPathExtensions.cs | 2 +- osu.Game/Rulesets/Objects/Types/PathType.cs | 12 +++++++---- osu.Game/osu.Game.csproj | 2 +- 29 files changed, 112 insertions(+), 100 deletions(-) create mode 100644 osu.Game/Rulesets/Edit/IToolboxAttachment.cs diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs index 16b51d414a..c96f32d87c 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs @@ -140,7 +140,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor AddStep("update hit object path", () => { - hitObject.Path = new SliderPath(PathType.PERFECTCURVE, new[] + hitObject.Path = new SliderPath(PathType.PERFECT_CURVE, new[] { Vector2.Zero, new Vector2(100, 100), @@ -190,14 +190,14 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor [Test] public void TestVertexResampling() { - addBlueprintStep(100, 100, new SliderPath(PathType.PERFECTCURVE, new[] + addBlueprintStep(100, 100, new SliderPath(PathType.PERFECT_CURVE, new[] { Vector2.Zero, new Vector2(100, 100), new Vector2(50, 200), }), 0.5); AddAssert("1 vertex per 1 nested HO", () => getVertices().Count == hitObject.NestedHitObjects.Count); - AddAssert("slider path not yet changed", () => hitObject.Path.ControlPoints[0].Type == PathType.PERFECTCURVE); + AddAssert("slider path not yet changed", () => hitObject.Path.ControlPoints[0].Type == PathType.PERFECT_CURVE); addAddVertexSteps(150, 150); AddAssert("slider path change to linear", () => hitObject.Path.ControlPoints[0].Type == PathType.LINEAR); } diff --git a/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs b/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs index 82f24633b5..9fb55fc057 100644 --- a/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs @@ -154,7 +154,7 @@ namespace osu.Game.Rulesets.Catch.Tests } while (rng.Next(2) != 0); int length = sliderPath.ControlPoints.Count - start + 1; - sliderPath.ControlPoints[start].Type = length <= 2 ? PathType.LINEAR : length == 3 ? PathType.PERFECTCURVE : PathType.BEZIER; + sliderPath.ControlPoints[start].Type = length <= 2 ? PathType.LINEAR : length == 3 ? PathType.PERFECT_CURVE : PathType.BEZIER; } while (rng.Next(3) != 0); if (rng.Next(5) == 0) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs index 8612a8eb57..5db6dc6cdd 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs @@ -214,7 +214,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks Path = new SliderPath(new[] { // Circular arc shoots over the top of the screen. - new PathControlPoint(new Vector2(0, 0), PathType.PERFECTCURVE), + new PathControlPoint(new Vector2(0, 0), PathType.PERFECT_CURVE), new PathControlPoint(new Vector2(-100, -200)), new PathControlPoint(new Vector2(100, -200)) }), diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorSelectInvalidPath.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorSelectInvalidPath.cs index 7ea4d40b90..43dae38004 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorSelectInvalidPath.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorSelectInvalidPath.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor PathControlPoint[] points = { - new PathControlPoint(new Vector2(0), PathType.PERFECTCURVE), + new PathControlPoint(new Vector2(0), PathType.PERFECT_CURVE), new PathControlPoint(new Vector2(-100, 0)), new PathControlPoint(new Vector2(100, 20)) }; diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs index 16800997f4..811ecf53e9 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor addContextMenuItemStep("Perfect curve"); assertControlPointPathType(0, PathType.BEZIER); - assertControlPointPathType(1, PathType.PERFECTCURVE); + assertControlPointPathType(1, PathType.PERFECT_CURVE); assertControlPointPathType(3, PathType.BEZIER); } @@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor addContextMenuItemStep("Perfect curve"); assertControlPointPathType(0, PathType.BEZIER); - assertControlPointPathType(2, PathType.PERFECTCURVE); + assertControlPointPathType(2, PathType.PERFECT_CURVE); assertControlPointPathType(4, null); } @@ -124,7 +124,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor addContextMenuItemStep("Perfect curve"); assertControlPointPathType(0, PathType.LINEAR); - assertControlPointPathType(1, PathType.PERFECTCURVE); + assertControlPointPathType(1, PathType.PERFECT_CURVE); assertControlPointPathType(3, PathType.LINEAR); } @@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor createVisualiser(true); addControlPointStep(new Vector2(200), PathType.BEZIER); - addControlPointStep(new Vector2(300), PathType.PERFECTCURVE); + addControlPointStep(new Vector2(300), PathType.PERFECT_CURVE); addControlPointStep(new Vector2(500, 300)); addControlPointStep(new Vector2(700, 200), PathType.BEZIER); addControlPointStep(new Vector2(500, 100)); diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs index 1d8d2cf01a..99ced30ffe 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs @@ -38,9 +38,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor Position = new Vector2(256, 192), Path = new SliderPath(new[] { - new PathControlPoint(Vector2.Zero, PathType.PERFECTCURVE), + new PathControlPoint(Vector2.Zero, PathType.PERFECT_CURVE), new PathControlPoint(new Vector2(150, 150)), - new PathControlPoint(new Vector2(300, 0), PathType.PERFECTCURVE), + new PathControlPoint(new Vector2(300, 0), PathType.PERFECT_CURVE), new PathControlPoint(new Vector2(400, 0)), new PathControlPoint(new Vector2(400, 150)) }) @@ -182,7 +182,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); assertControlPointPosition(1, new Vector2(150, 50)); - assertControlPointType(0, PathType.PERFECTCURVE); + assertControlPointType(0, PathType.PERFECT_CURVE); } [Test] @@ -210,7 +210,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("three control point pieces selected", () => this.ChildrenOfType>().Count(piece => piece.IsSelected.Value) == 3); assertControlPointPosition(2, new Vector2(450, 50)); - assertControlPointType(2, PathType.PERFECTCURVE); + assertControlPointType(2, PathType.PERFECT_CURVE); assertControlPointPosition(3, new Vector2(550, 50)); @@ -249,7 +249,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("slider moved", () => Precision.AlmostEquals(slider.Position, new Vector2(256, 192) + new Vector2(150, 50))); assertControlPointPosition(0, Vector2.Zero); - assertControlPointType(0, PathType.PERFECTCURVE); + assertControlPointType(0, PathType.PERFECT_CURVE); assertControlPointPosition(1, new Vector2(0, 100)); @@ -288,7 +288,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); assertControlPointPosition(1, new Vector2(150, 50)); - assertControlPointType(0, PathType.PERFECTCURVE); + assertControlPointType(0, PathType.PERFECT_CURVE); } [Test] @@ -304,7 +304,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); assertControlPointPosition(4, new Vector2(150, 150)); - assertControlPointType(2, PathType.PERFECTCURVE); + assertControlPointType(2, PathType.PERFECT_CURVE); } [Test] @@ -312,12 +312,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { AddStep("change type to bezier", () => slider.Path.ControlPoints[2].Type = PathType.BEZIER); AddStep("add point", () => slider.Path.ControlPoints.Add(new PathControlPoint(new Vector2(500, 10)))); - AddStep("change type to perfect", () => slider.Path.ControlPoints[3].Type = PathType.PERFECTCURVE); + AddStep("change type to perfect", () => slider.Path.ControlPoints[3].Type = PathType.PERFECT_CURVE); moveMouseToControlPoint(4); AddStep("hold", () => InputManager.PressButton(MouseButton.Left)); - assertControlPointType(3, PathType.PERFECTCURVE); + assertControlPointType(3, PathType.PERFECT_CURVE); addMovementStep(new Vector2(350, 0.01f)); AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderLengthValidity.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderLengthValidity.cs index 38ebeb7e8f..931c8c9e63 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderLengthValidity.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderLengthValidity.cs @@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor PathControlPoint[] points = { - new PathControlPoint(new Vector2(0), PathType.PERFECTCURVE), + new PathControlPoint(new Vector2(0), PathType.PERFECT_CURVE), new PathControlPoint(new Vector2(100, 0)), new PathControlPoint(new Vector2(0, 10)) }; diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index 4b120c1a3f..ecfc8105f1 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(3); assertControlPointPosition(1, new Vector2(100, 0)); - assertControlPointType(0, PathType.PERFECTCURVE); + assertControlPointType(0, PathType.PERFECT_CURVE); } [Test] @@ -172,7 +172,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(3); - assertControlPointType(0, PathType.PERFECTCURVE); + assertControlPointType(0, PathType.PERFECT_CURVE); } [Test] @@ -241,7 +241,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertControlPointPosition(1, new Vector2(100, 0)); assertControlPointPosition(2, new Vector2(100)); assertControlPointType(0, PathType.LINEAR); - assertControlPointType(1, PathType.PERFECTCURVE); + assertControlPointType(1, PathType.PERFECT_CURVE); } [Test] @@ -269,8 +269,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertControlPointPosition(2, new Vector2(100)); assertControlPointPosition(3, new Vector2(200, 100)); assertControlPointPosition(4, new Vector2(200)); - assertControlPointType(0, PathType.PERFECTCURVE); - assertControlPointType(2, PathType.PERFECTCURVE); + assertControlPointType(0, PathType.PERFECT_CURVE); + assertControlPointType(2, PathType.PERFECT_CURVE); } [Test] @@ -326,7 +326,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(3); - assertControlPointType(0, PathType.PERFECTCURVE); + assertControlPointType(0, PathType.PERFECT_CURVE); } [Test] @@ -347,7 +347,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(3); - assertControlPointType(0, PathType.PERFECTCURVE); + assertControlPointType(0, PathType.PERFECT_CURVE); } [Test] @@ -385,7 +385,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(3); - assertControlPointType(0, PathType.PERFECTCURVE); + assertControlPointType(0, PathType.PERFECT_CURVE); } private void addMovementStep(Vector2 position) => AddStep($"move mouse to {position}", () => InputManager.MoveMouseTo(InputManager.ToScreenSpace(position))); diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderReversal.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderReversal.cs index 0ddfc40946..a44c16a2e0 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderReversal.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderReversal.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private readonly PathControlPoint[][] paths = { createPathSegment( - PathType.PERFECTCURVE, + PathType.PERFECT_CURVE, new Vector2(200, -50), new Vector2(250, 0) ), diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs index c984d9168e..541fefb3a6 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { ControlPoints = { - new PathControlPoint(Vector2.Zero, PathType.PERFECTCURVE), + new PathControlPoint(Vector2.Zero, PathType.PERFECT_CURVE), new PathControlPoint(new Vector2(136, 205)), new PathControlPoint(new Vector2(-4, 226)) } @@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { OsuSelectionHandler selectionHandler; - AddAssert("first control point perfect", () => slider.Path.ControlPoints[0].Type == PathType.PERFECTCURVE); + AddAssert("first control point perfect", () => slider.Path.ControlPoints[0].Type == PathType.PERFECT_CURVE); AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider)); AddStep("rotate 90 degrees ccw", () => @@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor selectionHandler.HandleRotation(-90); }); - AddAssert("first control point still perfect", () => slider.Path.ControlPoints[0].Type == PathType.PERFECTCURVE); + AddAssert("first control point still perfect", () => slider.Path.ControlPoints[0].Type == PathType.PERFECT_CURVE); } [Test] @@ -223,7 +223,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { OsuSelectionHandler selectionHandler; - AddAssert("first control point perfect", () => slider.Path.ControlPoints[0].Type == PathType.PERFECTCURVE); + AddAssert("first control point perfect", () => slider.Path.ControlPoints[0].Type == PathType.PERFECT_CURVE); AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider)); AddStep("flip slider horizontally", () => @@ -232,7 +232,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor selectionHandler.OnPressed(new KeyBindingPressEvent(InputManager.CurrentState, GlobalAction.EditorFlipVertically)); }); - AddAssert("first control point still perfect", () => slider.Path.ControlPoints[0].Type == PathType.PERFECTCURVE); + AddAssert("first control point still perfect", () => slider.Path.ControlPoints[0].Type == PathType.PERFECT_CURVE); } [Test] diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs index cded9165f4..6c7733e68a 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs @@ -45,9 +45,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor Position = new Vector2(0, 50), Path = new SliderPath(new[] { - new PathControlPoint(Vector2.Zero, PathType.PERFECTCURVE), + new PathControlPoint(Vector2.Zero, PathType.PERFECT_CURVE), new PathControlPoint(new Vector2(150, 150)), - new PathControlPoint(new Vector2(300, 0), PathType.PERFECTCURVE), + new PathControlPoint(new Vector2(300, 0), PathType.PERFECT_CURVE), new PathControlPoint(new Vector2(400, 0)), new PathControlPoint(new Vector2(400, 150)) }) @@ -73,20 +73,20 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("slider split", () => slider is not null && EditorBeatmap.HitObjects.Count == 2 && sliderCreatedFor((Slider)EditorBeatmap.HitObjects[0], 0, EditorBeatmap.HitObjects[1].StartTime - split_gap, - (new Vector2(0, 50), PathType.PERFECTCURVE), + (new Vector2(0, 50), PathType.PERFECT_CURVE), (new Vector2(150, 200), null), (new Vector2(300, 50), null) ) && sliderCreatedFor((Slider)EditorBeatmap.HitObjects[1], slider.StartTime, endTime + split_gap, - (new Vector2(300, 50), PathType.PERFECTCURVE), + (new Vector2(300, 50), PathType.PERFECT_CURVE), (new Vector2(400, 50), null), (new Vector2(400, 200), null) )); AddStep("undo", () => Editor.Undo()); AddAssert("original slider restored", () => EditorBeatmap.HitObjects.Count == 1 && sliderCreatedFor((Slider)EditorBeatmap.HitObjects[0], 0, endTime, - (new Vector2(0, 50), PathType.PERFECTCURVE), + (new Vector2(0, 50), PathType.PERFECT_CURVE), (new Vector2(150, 200), null), - (new Vector2(300, 50), PathType.PERFECTCURVE), + (new Vector2(300, 50), PathType.PERFECT_CURVE), (new Vector2(400, 50), null), (new Vector2(400, 200), null) )); @@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor Position = new Vector2(0, 50), Path = new SliderPath(new[] { - new PathControlPoint(Vector2.Zero, PathType.PERFECTCURVE), + new PathControlPoint(Vector2.Zero, PathType.PERFECT_CURVE), new PathControlPoint(new Vector2(150, 150)), new PathControlPoint(new Vector2(300, 0), PathType.BEZIER), new PathControlPoint(new Vector2(400, 0)), @@ -139,7 +139,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("slider split", () => slider is not null && EditorBeatmap.HitObjects.Count == 3 && sliderCreatedFor((Slider)EditorBeatmap.HitObjects[0], 0, EditorBeatmap.HitObjects[1].StartTime - split_gap, - (new Vector2(0, 50), PathType.PERFECTCURVE), + (new Vector2(0, 50), PathType.PERFECT_CURVE), (new Vector2(150, 200), null), (new Vector2(300, 50), null) ) && sliderCreatedFor((Slider)EditorBeatmap.HitObjects[1], EditorBeatmap.HitObjects[0].GetEndTime() + split_gap, slider.StartTime - split_gap, @@ -165,9 +165,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor Position = new Vector2(0, 50), Path = new SliderPath(new[] { - new PathControlPoint(Vector2.Zero, PathType.PERFECTCURVE), + new PathControlPoint(Vector2.Zero, PathType.PERFECT_CURVE), new PathControlPoint(new Vector2(150, 150)), - new PathControlPoint(new Vector2(300, 0), PathType.PERFECTCURVE), + new PathControlPoint(new Vector2(300, 0), PathType.PERFECT_CURVE), new PathControlPoint(new Vector2(400, 0)), new PathControlPoint(new Vector2(400, 150)) }) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index 60003e7950..4600db8174 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -219,7 +219,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = Time.Current + time_offset, Position = new Vector2(239, 176), - Path = new SliderPath(PathType.PERFECTCURVE, new[] + Path = new SliderPath(PathType.PERFECT_CURVE, new[] { Vector2.Zero, new Vector2(154, 28), @@ -255,7 +255,7 @@ namespace osu.Game.Rulesets.Osu.Tests SliderVelocityMultiplier = speedMultiplier, StartTime = Time.Current + time_offset, Position = new Vector2(0, -(distance / 2)), - Path = new SliderPath(PathType.PERFECTCURVE, new[] + Path = new SliderPath(PathType.PERFECT_CURVE, new[] { Vector2.Zero, new Vector2(0, distance), @@ -273,7 +273,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = Time.Current + time_offset, Position = new Vector2(-max_length / 2, 0), - Path = new SliderPath(PathType.PERFECTCURVE, new[] + Path = new SliderPath(PathType.PERFECT_CURVE, new[] { Vector2.Zero, new Vector2(max_length / 2, max_length / 2), diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index 08836ef819..0f7dd8b7bc 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -478,7 +478,7 @@ namespace osu.Game.Rulesets.Osu.Tests StartTime = time_slider_start, Position = new Vector2(0, 0), SliderVelocityMultiplier = 0.1f, - Path = new SliderPath(PathType.PERFECTCURVE, new[] + Path = new SliderPath(PathType.PERFECT_CURVE, new[] { Vector2.Zero, new Vector2(slider_path_length, 0), diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index ebc5143aed..912b2b0626 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -217,7 +217,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = 3000, Position = new Vector2(100, 100), - Path = new SliderPath(PathType.PERFECTCURVE, new[] + Path = new SliderPath(PathType.PERFECT_CURVE, new[] { Vector2.Zero, new Vector2(300, 200) @@ -227,7 +227,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = 13000, Position = new Vector2(100, 100), - Path = new SliderPath(PathType.PERFECTCURVE, new[] + Path = new SliderPath(PathType.PERFECT_CURVE, new[] { Vector2.Zero, new Vector2(300, 200) @@ -238,7 +238,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = 23000, Position = new Vector2(100, 100), - Path = new SliderPath(PathType.PERFECTCURVE, new[] + Path = new SliderPath(PathType.PERFECT_CURVE, new[] { Vector2.Zero, new Vector2(300, 200) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 53228cff82..ac9048d5c7 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -221,7 +221,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components ///
private void updatePathType() { - if (ControlPoint.Type != PathType.PERFECTCURVE) + if (ControlPoint.Type != PathType.PERFECT_CURVE) return; if (PointsInSegment.Count > 3) @@ -259,19 +259,19 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components if (ControlPoint.Type is not PathType pathType) return colours.Yellow; - switch (pathType) + switch (pathType.Type) { - case { Type: SplineType.Catmull }: + case SplineType.Catmull: return colours.SeaFoam; - case { Type: SplineType.BSpline, Degree: null }: - return colours.PinkLighter; + case SplineType.BSpline: + if (!pathType.Degree.HasValue) + return colours.PinkLighter; - case { Type: SplineType.BSpline, Degree: >= 1 }: int idx = Math.Clamp(pathType.Degree.Value, 0, 3); return new[] { colours.PinkDarker, colours.PinkDark, colours.Pink, colours.PinkLight }[idx]; - case { Type: SplineType.PerfectCurve }: + case SplineType.PerfectCurve: return colours.PurpleDark; default: diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 4e85835652..5ab050ed48 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -368,7 +368,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components // todo: hide/disable items which aren't valid for selected points curveTypeItems.Add(createMenuItemForPathType(PathType.LINEAR)); - curveTypeItems.Add(createMenuItemForPathType(PathType.PERFECTCURVE)); + curveTypeItems.Add(createMenuItemForPathType(PathType.PERFECT_CURVE)); curveTypeItems.Add(createMenuItemForPathType(PathType.BEZIER)); curveTypeItems.Add(createMenuItemForPathType(PathType.BSpline(3))); curveTypeItems.Add(createMenuItemForPathType(PathType.CATMULL)); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 9ac28fe82a..c722f0fdbc 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -251,7 +251,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders break; case 3: - segmentStart.Type = PathType.PERFECTCURVE; + segmentStart.Type = PathType.PERFECT_CURVE; break; default: diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs b/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs index ae772f53fc..643732d90a 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Edit; namespace osu.Game.Rulesets.Osu.Edit { - public partial class OsuSliderDrawingSettingsProvider : Drawable, ISliderDrawingSettingsProvider + public partial class OsuSliderDrawingSettingsProvider : Drawable, ISliderDrawingSettingsProvider, IToolboxAttachment { public BindableFloat Tolerance { get; } = new BindableFloat(0.1f) { @@ -27,12 +27,14 @@ namespace osu.Game.Rulesets.Osu.Edit private ExpandableSlider toleranceSlider = null!; - public OsuSliderDrawingSettingsProvider() + protected override void LoadComplete() { + base.LoadComplete(); + sliderTolerance.BindValueChanged(v => { float newValue = v.NewValue / 100f; - if (!Precision.AlmostEquals(newValue, Tolerance.Value, 1e-7f)) + if (!Precision.AlmostEquals(newValue, Tolerance.Value)) Tolerance.Value = newValue; }); Tolerance.BindValueChanged(v => diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 18c21046fb..34b96bbd3f 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -808,7 +808,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var first = ((IHasPath)decoded.HitObjects[0]).Path; Assert.That(first.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero)); - Assert.That(first.ControlPoints[0].Type, Is.EqualTo(PathType.PERFECTCURVE)); + Assert.That(first.ControlPoints[0].Type, Is.EqualTo(PathType.PERFECT_CURVE)); Assert.That(first.ControlPoints[1].Position, Is.EqualTo(new Vector2(161, -244))); Assert.That(first.ControlPoints[1].Type, Is.EqualTo(null)); @@ -827,7 +827,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var second = ((IHasPath)decoded.HitObjects[1]).Path; Assert.That(second.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero)); - Assert.That(second.ControlPoints[0].Type, Is.EqualTo(PathType.PERFECTCURVE)); + Assert.That(second.ControlPoints[0].Type, Is.EqualTo(PathType.PERFECT_CURVE)); Assert.That(second.ControlPoints[1].Position, Is.EqualTo(new Vector2(161, -244))); Assert.That(second.ControlPoints[1].Type, Is.EqualTo(null)); Assert.That(second.ControlPoints[2].Position, Is.EqualTo(new Vector2(376, -3))); @@ -904,12 +904,12 @@ namespace osu.Game.Tests.Beatmaps.Formats var seventh = ((IHasPath)decoded.HitObjects[6]).Path; Assert.That(seventh.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero)); - Assert.That(seventh.ControlPoints[0].Type == PathType.PERFECTCURVE); + Assert.That(seventh.ControlPoints[0].Type == PathType.PERFECT_CURVE); Assert.That(seventh.ControlPoints[1].Position, Is.EqualTo(new Vector2(75, 145))); Assert.That(seventh.ControlPoints[1].Type == null); Assert.That(seventh.ControlPoints[2].Position, Is.EqualTo(new Vector2(170, 75))); - Assert.That(seventh.ControlPoints[2].Type == PathType.PERFECTCURVE); + Assert.That(seventh.ControlPoints[2].Type == PathType.PERFECT_CURVE); Assert.That(seventh.ControlPoints[3].Position, Is.EqualTo(new Vector2(300, 145))); Assert.That(seventh.ControlPoints[3].Type == null); Assert.That(seventh.ControlPoints[4].Position, Is.EqualTo(new Vector2(410, 20))); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBezierConverter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBezierConverter.cs index e2333011c7..27497f77be 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBezierConverter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBezierConverter.cs @@ -143,7 +143,7 @@ namespace osu.Game.Tests.Visual.Gameplay { path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(100, 0))); path.ControlPoints.AddRange(createSegment(PathType.BEZIER, new Vector2(100, 0), new Vector2(150, 30), new Vector2(100, 100))); - path.ControlPoints.AddRange(createSegment(PathType.PERFECTCURVE, new Vector2(100, 100), new Vector2(25, 50), Vector2.Zero)); + path.ControlPoints.AddRange(createSegment(PathType.PERFECT_CURVE, new Vector2(100, 100), new Vector2(25, 50), Vector2.Zero)); }); } @@ -159,7 +159,7 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("create path", () => { - path.ControlPoints.AddRange(createSegment(PathType.PERFECTCURVE, Vector2.Zero, new Vector2(width / 2, height), new Vector2(width, 0))); + path.ControlPoints.AddRange(createSegment(PathType.PERFECT_CURVE, Vector2.Zero, new Vector2(width / 2, height), new Vector2(width, 0))); }); } @@ -172,11 +172,11 @@ namespace osu.Game.Tests.Visual.Gameplay switch (points) { case 2: - path.ControlPoints.AddRange(createSegment(PathType.PERFECTCURVE, Vector2.Zero, new Vector2(0, 100))); + path.ControlPoints.AddRange(createSegment(PathType.PERFECT_CURVE, Vector2.Zero, new Vector2(0, 100))); break; case 4: - path.ControlPoints.AddRange(createSegment(PathType.PERFECTCURVE, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); + path.ControlPoints.AddRange(createSegment(PathType.PERFECT_CURVE, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); break; } }); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs index d44af45fe4..44a2e5fb9b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs @@ -149,11 +149,11 @@ namespace osu.Game.Tests.Visual.Gameplay switch (points) { case 2: - path.ControlPoints.AddRange(createSegment(PathType.PERFECTCURVE, Vector2.Zero, new Vector2(0, 100))); + path.ControlPoints.AddRange(createSegment(PathType.PERFECT_CURVE, Vector2.Zero, new Vector2(0, 100))); break; case 4: - path.ControlPoints.AddRange(createSegment(PathType.PERFECTCURVE, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); + path.ControlPoints.AddRange(createSegment(PathType.PERFECT_CURVE, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); break; } }); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index ff446206ac..b375a6f7ff 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -437,7 +437,7 @@ namespace osu.Game.Beatmaps.Formats // Explicit segments have a new format in which the type is injected into the middle of the control point string. // To preserve compatibility with osu-stable as much as possible, explicit segments with the same type are converted to use implicit segments by duplicating the control point. // One exception are consecutive perfect curves, which aren't supported in osu!stable and can lead to decoding issues if encoded as implicit segments - bool needsExplicitSegment = point.Type != lastType || point.Type == PathType.PERFECTCURVE; + bool needsExplicitSegment = point.Type != lastType || point.Type == PathType.PERFECT_CURVE; // Another exception to this is when the last two control points of the last segment were duplicated. This is not a scenario supported by osu!stable. // Lazer does not add implicit segments for the last two control points of _any_ explicit segment, so an explicit segment is forced in order to maintain consistency with the decoder. @@ -453,25 +453,21 @@ namespace osu.Game.Beatmaps.Formats if (needsExplicitSegment) { - switch (point.Type) + switch (point.Type?.Type) { - case { Type: SplineType.BSpline, Degree: > 0 }: - writer.Write($"B{point.Type.Value.Degree}|"); + case SplineType.BSpline: + writer.Write(point.Type.Value.Degree > 0 ? $"B{point.Type.Value.Degree}|" : "B|"); break; - case { Type: SplineType.BSpline, Degree: <= 0 }: - writer.Write("B|"); - break; - - case { Type: SplineType.Catmull }: + case SplineType.Catmull: writer.Write("C|"); break; - case { Type: SplineType.PerfectCurve }: + case SplineType.PerfectCurve: writer.Write("P|"); break; - case { Type: SplineType.Linear }: + case SplineType.Linear: writer.Write("L|"); break; } diff --git a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs index ddf539771d..68411d2b01 100644 --- a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs @@ -29,7 +29,7 @@ using osu.Game.Screens.Edit.Components.TernaryButtons; namespace osu.Game.Rulesets.Edit { - public abstract partial class ComposerDistanceSnapProvider : Component, IDistanceSnapProvider, IScrollBindingHandler + public abstract partial class ComposerDistanceSnapProvider : Component, IDistanceSnapProvider, IScrollBindingHandler, IToolboxAttachment { private const float adjust_step = 0.1f; diff --git a/osu.Game/Rulesets/Edit/IToolboxAttachment.cs b/osu.Game/Rulesets/Edit/IToolboxAttachment.cs new file mode 100644 index 0000000000..7d7c5980b2 --- /dev/null +++ b/osu.Game/Rulesets/Edit/IToolboxAttachment.cs @@ -0,0 +1,10 @@ +// 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.Edit +{ + public interface IToolboxAttachment + { + void AttachToToolbox(ExpandingToolboxContainer toolbox); + } +} diff --git a/osu.Game/Rulesets/Objects/BezierConverter.cs b/osu.Game/Rulesets/Objects/BezierConverter.cs index ed86fc10e0..5dc0839d37 100644 --- a/osu.Game/Rulesets/Objects/BezierConverter.cs +++ b/osu.Game/Rulesets/Objects/BezierConverter.cs @@ -126,9 +126,9 @@ namespace osu.Game.Rulesets.Objects var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1); var segmentType = controlPoints[start].Type ?? PathType.LINEAR; - switch (segmentType) + switch (segmentType.Type) { - case { Type: SplineType.Catmull }: + case SplineType.Catmull: foreach (var segment in ConvertCatmullToBezierAnchors(segmentVertices)) { for (int j = 0; j < segment.Length - 1; j++) @@ -139,7 +139,7 @@ namespace osu.Game.Rulesets.Objects break; - case { Type: SplineType.Linear }: + case SplineType.Linear: foreach (var segment in ConvertLinearToBezierAnchors(segmentVertices)) { for (int j = 0; j < segment.Length - 1; j++) @@ -150,7 +150,7 @@ namespace osu.Game.Rulesets.Objects break; - case { Type: SplineType.PerfectCurve }: + case SplineType.PerfectCurve: var circleResult = ConvertCircleToBezierAnchors(segmentVertices); for (int j = 0; j < circleResult.Length - 1; j++) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 6a13a897c4..92a92dca8f 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -224,19 +224,19 @@ namespace osu.Game.Rulesets.Objects.Legacy { default: case 'C': - return new PathType(SplineType.Catmull); + return PathType.CATMULL; case 'B': if (input.Length > 1 && int.TryParse(input.Substring(1), out int degree) && degree > 0) - return new PathType { Type = SplineType.BSpline, Degree = degree }; + return PathType.BSpline(degree); return new PathType(SplineType.BSpline); case 'L': - return new PathType(SplineType.Linear); + return PathType.LINEAR; case 'P': - return new PathType(SplineType.PerfectCurve); + return PathType.PERFECT_CURVE; } } @@ -323,7 +323,7 @@ namespace osu.Game.Rulesets.Objects.Legacy readPoint(endPoint, offset, out vertices[^1]); // Edge-case rules (to match stable). - if (type == PathType.PERFECTCURVE) + if (type == PathType.PERFECT_CURVE) { if (vertices.Length != 3) type = PathType.BEZIER; diff --git a/osu.Game/Rulesets/Objects/SliderPathExtensions.cs b/osu.Game/Rulesets/Objects/SliderPathExtensions.cs index d7e5e4574d..29b34ae4f0 100644 --- a/osu.Game/Rulesets/Objects/SliderPathExtensions.cs +++ b/osu.Game/Rulesets/Objects/SliderPathExtensions.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Objects inheritedLinearPoints.ForEach(p => p.Type = null); // Recalculate middle perfect curve control points at the end of the slider path. - if (controlPoints.Count >= 3 && controlPoints[^3].Type == PathType.PERFECTCURVE && controlPoints[^2].Type is null && segmentEnds.Any()) + if (controlPoints.Count >= 3 && controlPoints[^3].Type == PathType.PERFECT_CURVE && controlPoints[^2].Type is null && segmentEnds.Any()) { double lastSegmentStart = segmentEnds.Length > 1 ? segmentEnds[^2] : 0; double lastSegmentEnd = segmentEnds[^1]; diff --git a/osu.Game/Rulesets/Objects/Types/PathType.cs b/osu.Game/Rulesets/Objects/Types/PathType.cs index a6e8e173d4..4fb48bb8b4 100644 --- a/osu.Game/Rulesets/Objects/Types/PathType.cs +++ b/osu.Game/Rulesets/Objects/Types/PathType.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; namespace osu.Game.Rulesets.Objects.Types { @@ -14,12 +13,12 @@ namespace osu.Game.Rulesets.Objects.Types PerfectCurve } - public readonly struct PathType + public readonly struct PathType : IEquatable { public static readonly PathType CATMULL = new PathType(SplineType.Catmull); public static readonly PathType BEZIER = new PathType(SplineType.BSpline); public static readonly PathType LINEAR = new PathType(SplineType.Linear); - public static readonly PathType PERFECTCURVE = new PathType(SplineType.PerfectCurve); + public static readonly PathType PERFECT_CURVE = new PathType(SplineType.PerfectCurve); /// /// The type of the spline that should be used to interpret the control points of the path. @@ -52,8 +51,13 @@ namespace osu.Game.Rulesets.Objects.Types public static PathType BSpline(int degree) { - Debug.Assert(degree > 0); + if (degree <= 0) + throw new ArgumentOutOfRangeException(nameof(degree), "The degree of a B-Spline path must be greater than zero."); + return new PathType { Type = SplineType.BSpline, Degree = degree }; } + + public bool Equals(PathType other) + => Type == other.Type && Degree == other.Degree; } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index fc10f90e8f..8d42cd22e3 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + From e3137d575bbea46423c8124a9ade25fab18d03f2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 14 Nov 2023 01:11:17 +0900 Subject: [PATCH 1509/4092] Fix osu! and base HP processor break time implementation --- .../TestSceneOsuHealthProcessor.cs | 94 +++++++++++++++++++ .../Scoring/OsuHealthProcessor.cs | 25 ++--- .../TestSceneDrainingHealthProcessor.cs | 81 +++++++++++++++- .../Scoring/DrainingHealthProcessor.cs | 44 ++++----- 4 files changed, 203 insertions(+), 41 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneOsuHealthProcessor.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuHealthProcessor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuHealthProcessor.cs new file mode 100644 index 0000000000..f810bbf155 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuHealthProcessor.cs @@ -0,0 +1,94 @@ +// 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 NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Scoring; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class TestSceneOsuHealthProcessor + { + [Test] + public void TestNoBreak() + { + OsuHealthProcessor hp = new OsuHealthProcessor(-1000); + hp.ApplyBeatmap(new Beatmap + { + HitObjects = + { + new HitCircle { StartTime = 0 }, + new HitCircle { StartTime = 2000 } + } + }); + + Assert.That(hp.DrainRate, Is.EqualTo(1.4E-5).Within(0.1E-5)); + } + + [Test] + public void TestSingleBreak() + { + OsuHealthProcessor hp = new OsuHealthProcessor(-1000); + hp.ApplyBeatmap(new Beatmap + { + HitObjects = + { + new HitCircle { StartTime = 0 }, + new HitCircle { StartTime = 2000 } + }, + Breaks = + { + new BreakPeriod(500, 1500) + } + }); + + Assert.That(hp.DrainRate, Is.EqualTo(4.3E-5).Within(0.1E-5)); + } + + [Test] + public void TestOverlappingBreak() + { + OsuHealthProcessor hp = new OsuHealthProcessor(-1000); + hp.ApplyBeatmap(new Beatmap + { + HitObjects = + { + new HitCircle { StartTime = 0 }, + new HitCircle { StartTime = 2000 } + }, + Breaks = + { + new BreakPeriod(500, 1400), + new BreakPeriod(750, 1500), + } + }); + + Assert.That(hp.DrainRate, Is.EqualTo(4.3E-5).Within(0.1E-5)); + } + + [Test] + public void TestSequentialBreak() + { + OsuHealthProcessor hp = new OsuHealthProcessor(-1000); + hp.ApplyBeatmap(new Beatmap + { + HitObjects = + { + new HitCircle { StartTime = 0 }, + new HitCircle { StartTime = 2000 } + }, + Breaks = + { + new BreakPeriod(500, 1000), + new BreakPeriod(1000, 1500), + } + }); + + Assert.That(hp.DrainRate, Is.EqualTo(4.3E-5).Within(0.1E-5)); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs index 5802f8fc0d..3207c7fb51 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs @@ -3,9 +3,7 @@ using System; using System.Linq; -using osu.Framework.Logging; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; @@ -64,25 +62,16 @@ namespace osu.Game.Rulesets.Osu.Scoring { HitObject h = Beatmap.HitObjects[i]; - // Find active break (between current and lastTime) - double localLastTime = lastTime; - double breakTime = 0; - - // TODO: This doesn't handle overlapping/sequential breaks correctly (/b/614). - // Subtract any break time from the duration since the last object - if (Beatmap.Breaks.Count > 0 && currentBreak < Beatmap.Breaks.Count) + while (currentBreak < Beatmap.Breaks.Count && Beatmap.Breaks[currentBreak].EndTime <= h.StartTime) { - BreakPeriod e = Beatmap.Breaks[currentBreak]; - - if (e.StartTime >= localLastTime && e.EndTime <= h.StartTime) - { - // consider break start equal to object end time for version 8+ since drain stops during this time - breakTime = (Beatmap.BeatmapInfo.BeatmapVersion < 8) ? (e.EndTime - e.StartTime) : e.EndTime - localLastTime; - currentBreak++; - } + // If two hitobjects are separated by a break period, there is no drain for the full duration between the hitobjects. + // This differs from legacy (version < 8) beatmaps which continue draining until the break section is entered, + // but this shouldn't have a noticeable impact in practice. + lastTime = h.StartTime; + currentBreak++; } - reduceHp(testDrop * (h.StartTime - lastTime - breakTime)); + reduceHp(testDrop * (h.StartTime - lastTime)); lastTime = h.GetEndTime(); diff --git a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs index fd0bff101f..584a9e09c0 100644 --- a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs @@ -192,7 +192,8 @@ namespace osu.Game.Tests.Gameplay AddStep("apply perfect hit result", () => processor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new Judgement()) { Type = HitResult.Perfect })); AddAssert("not failed", () => !processor.HasFailed); - AddStep($"apply {resultApplied.ToString().ToLowerInvariant()} hit result", () => processor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new Judgement()) { Type = resultApplied })); + AddStep($"apply {resultApplied.ToString().ToLowerInvariant()} hit result", + () => processor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new Judgement()) { Type = resultApplied })); AddAssert("failed", () => processor.HasFailed); } @@ -232,6 +233,84 @@ namespace osu.Game.Tests.Gameplay assertHealthEqualTo(1); } + [Test] + public void TestNoBreakDrainRate() + { + DrainingHealthProcessor hp = new DrainingHealthProcessor(-1000); + hp.ApplyBeatmap(new Beatmap + { + HitObjects = + { + new JudgeableHitObject { StartTime = 0 }, + new JudgeableHitObject { StartTime = 2000 } + } + }); + + Assert.That(hp.DrainRate, Is.EqualTo(4.5E-5).Within(0.1E-5)); + } + + [Test] + public void TestSingleBreakDrainRate() + { + DrainingHealthProcessor hp = new DrainingHealthProcessor(-1000); + hp.ApplyBeatmap(new Beatmap + { + HitObjects = + { + new JudgeableHitObject { StartTime = 0 }, + new JudgeableHitObject { StartTime = 2000 } + }, + Breaks = + { + new BreakPeriod(500, 1500) + } + }); + + Assert.That(hp.DrainRate, Is.EqualTo(9.1E-5).Within(0.1E-5)); + } + + [Test] + public void TestOverlappingBreakDrainRate() + { + DrainingHealthProcessor hp = new DrainingHealthProcessor(-1000); + hp.ApplyBeatmap(new Beatmap + { + HitObjects = + { + new JudgeableHitObject { StartTime = 0 }, + new JudgeableHitObject { StartTime = 2000 } + }, + Breaks = + { + new BreakPeriod(500, 1400), + new BreakPeriod(750, 1500), + } + }); + + Assert.That(hp.DrainRate, Is.EqualTo(9.1E-5).Within(0.1E-5)); + } + + [Test] + public void TestSequentialBreakDrainRate() + { + DrainingHealthProcessor hp = new DrainingHealthProcessor(-1000); + hp.ApplyBeatmap(new Beatmap + { + HitObjects = + { + new JudgeableHitObject { StartTime = 0 }, + new JudgeableHitObject { StartTime = 2000 } + }, + Breaks = + { + new BreakPeriod(500, 1000), + new BreakPeriod(1000, 1500), + } + }); + + Assert.That(hp.DrainRate, Is.EqualTo(9.1E-5).Within(0.1E-5)); + } + private Beatmap createBeatmap(double startTime, double endTime, params BreakPeriod[] breaks) { var beatmap = new Beatmap diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs index a8808d08e5..3d4fb862fb 100644 --- a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs @@ -103,18 +103,20 @@ namespace osu.Game.Rulesets.Scoring if (beatmap.HitObjects.Count > 0) gameplayEndTime = beatmap.HitObjects[^1].GetEndTime(); - noDrainPeriodTracker = new PeriodTracker(beatmap.Breaks.Select(breakPeriod => new Period( - beatmap.HitObjects - .Select(hitObject => hitObject.GetEndTime()) - .Where(endTime => endTime <= breakPeriod.StartTime) - .DefaultIfEmpty(double.MinValue) - .Last(), - beatmap.HitObjects - .Select(hitObject => hitObject.StartTime) - .Where(startTime => startTime >= breakPeriod.EndTime) - .DefaultIfEmpty(double.MaxValue) - .First() - ))); + noDrainPeriodTracker = new PeriodTracker( + beatmap.Breaks.Select(breakPeriod => + new Period( + beatmap.HitObjects + .Select(hitObject => hitObject.GetEndTime()) + .Where(endTime => endTime <= breakPeriod.StartTime) + .DefaultIfEmpty(double.MinValue) + .Last(), + beatmap.HitObjects + .Select(hitObject => hitObject.StartTime) + .Where(startTime => startTime >= breakPeriod.EndTime) + .DefaultIfEmpty(double.MaxValue) + .First() + ))); targetMinimumHealth = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, min_health_target, mid_health_target, max_health_target); @@ -161,26 +163,24 @@ namespace osu.Game.Rulesets.Scoring { double currentHealth = 1; double lowestHealth = 1; - int currentBreak = -1; + int currentBreak = 0; for (int i = 0; i < healthIncreases.Count; i++) { double currentTime = healthIncreases[i].time; double lastTime = i > 0 ? healthIncreases[i - 1].time : DrainStartTime; - // Subtract any break time from the duration since the last object - if (Beatmap.Breaks.Count > 0) + while (currentBreak < Beatmap.Breaks.Count && Beatmap.Breaks[currentBreak].EndTime <= currentTime) { - // Advance the last break occuring before the current time - while (currentBreak + 1 < Beatmap.Breaks.Count && Beatmap.Breaks[currentBreak + 1].EndTime < currentTime) - currentBreak++; - - if (currentBreak >= 0) - lastTime = Math.Max(lastTime, Beatmap.Breaks[currentBreak].EndTime); + // If two hitobjects are separated by a break period, there is no drain for the full duration between the hitobjects. + // This differs from legacy (version < 8) beatmaps which continue draining until the break section is entered, + // but this shouldn't have a noticeable impact in practice. + lastTime = currentTime; + currentBreak++; } // Apply health adjustments - currentHealth -= (healthIncreases[i].time - lastTime) * result; + currentHealth -= (currentTime - lastTime) * result; lowestHealth = Math.Min(lowestHealth, currentHealth); currentHealth = Math.Min(1, currentHealth + healthIncreases[i].health); From 69c2c0e4278d4d419b3ee1ea11f4c8e491514247 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 14 Nov 2023 16:30:08 +0900 Subject: [PATCH 1510/4092] Fix touch device mod declared valid for multiplayer Mostly matters for web, so that it doesn't permit creation of playlist items with touch device inside. --- osu.Game/Rulesets/Mods/ModTouchDevice.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Rulesets/Mods/ModTouchDevice.cs b/osu.Game/Rulesets/Mods/ModTouchDevice.cs index b80b042f11..e91a398700 100644 --- a/osu.Game/Rulesets/Mods/ModTouchDevice.cs +++ b/osu.Game/Rulesets/Mods/ModTouchDevice.cs @@ -16,6 +16,8 @@ namespace osu.Game.Rulesets.Mods public sealed override LocalisableString Description => "Automatically applied to plays on devices with a touchscreen."; public sealed override double ScoreMultiplier => 1; public sealed override ModType Type => ModType.System; + public sealed override bool ValidForMultiplayer => false; + public sealed override bool ValidForMultiplayerAsFreeMod => false; public sealed override bool AlwaysValidForSubmission => true; public override Type[] IncompatibleMods => new[] { typeof(ICreateReplayData) }; } From 70d2de56695800a2ca14a027d32cf2c36153e8f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 14 Nov 2023 16:35:16 +0900 Subject: [PATCH 1511/4092] Move song select touch detector to solo implementation It should not run in multiplayer. Even if we wanted to allow touch-only playlist items at some point, the current behaviour of multiplayer song selects with respect to touch device mod is currently just broken. --- osu.Game/Screens/Select/PlaySongSelect.cs | 2 ++ osu.Game/Screens/Select/SongSelect.cs | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index fe13d6d5a8..86bebdc2ff 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -48,6 +48,8 @@ namespace osu.Game.Screens.Select private void load(OsuColour colours) { BeatmapOptions.AddButton(ButtonSystemStrings.Edit.ToSentence(), @"beatmap", FontAwesome.Solid.PencilAlt, colours.Yellow, () => Edit()); + + AddInternal(new SongSelectTouchInputDetector()); } protected void PresentScore(ScoreInfo score) => diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 03083672d5..dfea4e3794 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -279,7 +279,6 @@ namespace osu.Game.Screens.Select { RelativeSizeAxes = Axes.Both, }, - new SongSelectTouchInputDetector() }); if (ShowFooter) From 90ec6895d100dea67e282c75a57779ffa87d0f5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Mu=CC=88ller-Ho=CC=88hne?= Date: Tue, 14 Nov 2023 16:52:45 +0900 Subject: [PATCH 1512/4092] Automatic red control point generation & corner threshold --- .../Sliders/SliderPlacementBlueprint.cs | 28 +++++++++-- .../Edit/ISliderDrawingSettingsProvider.cs | 1 + .../Edit/OsuSliderDrawingSettingsProvider.cs | 46 +++++++++++++++++-- 3 files changed, 65 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index c722f0fdbc..69e2a40689 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -86,6 +86,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders bSplineBuilder.Tolerance = e.NewValue; updateSliderPathFromBSplineBuilder(); }, true); + + drawingSettingsProvider.CornerThreshold.BindValueChanged(e => + { + if (bSplineBuilder.CornerThreshold != e.NewValue) + bSplineBuilder.CornerThreshold = e.NewValue; + updateSliderPathFromBSplineBuilder(); + }, true); } [Resolved] @@ -100,8 +107,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders case SliderPlacementState.Initial: BeginPlacement(); - double? nearestSliderVelocity = (editorBeatmap.HitObjects - .LastOrDefault(h => h is Slider && h.GetEndTime() < HitObject.StartTime) as Slider)?.SliderVelocityMultiplier; + double? nearestSliderVelocity = (editorBeatmap + .HitObjects + .LastOrDefault(h => h is Slider && h.GetEndTime() < HitObject.StartTime) as Slider)?.SliderVelocityMultiplier; HitObject.SliderVelocityMultiplier = nearestSliderVelocity ?? 1; HitObject.Position = ToLocalSpace(result.ScreenSpacePosition); @@ -193,9 +201,19 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { Scheduler.AddOnce(static self => { - var cps = self.bSplineBuilder.GetControlPoints(); - self.HitObject.Path.ControlPoints.RemoveRange(1, self.HitObject.Path.ControlPoints.Count - 1); - self.HitObject.Path.ControlPoints.AddRange(cps.Skip(1).Select(v => new PathControlPoint(v))); + var cps = self.bSplineBuilder.ControlPoints; + var sliderCps = self.HitObject.Path.ControlPoints; + sliderCps.RemoveRange(1, sliderCps.Count - 1); + + // Add the control points from the BSpline builder while converting control points that repeat + // three or more times to a single PathControlPoint with linear type. + for (int i = 1; i < cps.Count; i++) + { + bool isSharp = i < cps.Count - 2 && cps[i] == cps[i + 1] && cps[i] == cps[i + 2]; + sliderCps.Add(new PathControlPoint(cps[i], isSharp ? PathType.BSpline(3) : null)); + if (isSharp) + i += 2; + } }, this); } diff --git a/osu.Game.Rulesets.Osu/Edit/ISliderDrawingSettingsProvider.cs b/osu.Game.Rulesets.Osu/Edit/ISliderDrawingSettingsProvider.cs index 1138588259..31ed98e1dd 100644 --- a/osu.Game.Rulesets.Osu/Edit/ISliderDrawingSettingsProvider.cs +++ b/osu.Game.Rulesets.Osu/Edit/ISliderDrawingSettingsProvider.cs @@ -8,5 +8,6 @@ namespace osu.Game.Rulesets.Osu.Edit public interface ISliderDrawingSettingsProvider { BindableFloat Tolerance { get; } + BindableFloat CornerThreshold { get; } } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs b/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs index 643732d90a..1fe1326f38 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs @@ -12,20 +12,34 @@ namespace osu.Game.Rulesets.Osu.Edit { public partial class OsuSliderDrawingSettingsProvider : Drawable, ISliderDrawingSettingsProvider, IToolboxAttachment { - public BindableFloat Tolerance { get; } = new BindableFloat(0.1f) + public BindableFloat Tolerance { get; } = new BindableFloat(1.5f) + { + MinValue = 0.05f, + MaxValue = 3f, + Precision = 0.01f + }; + + private readonly BindableInt sliderTolerance = new BindableInt(50) + { + MinValue = 5, + MaxValue = 100 + }; + + public BindableFloat CornerThreshold { get; } = new BindableFloat(0.4f) { MinValue = 0.05f, MaxValue = 1f, Precision = 0.01f }; - private readonly BindableInt sliderTolerance = new BindableInt(10) + private readonly BindableInt sliderCornerThreshold = new BindableInt(40) { MinValue = 5, MaxValue = 100 }; private ExpandableSlider toleranceSlider = null!; + private ExpandableSlider cornerThresholdSlider = null!; protected override void LoadComplete() { @@ -33,16 +47,28 @@ namespace osu.Game.Rulesets.Osu.Edit sliderTolerance.BindValueChanged(v => { - float newValue = v.NewValue / 100f; - if (!Precision.AlmostEquals(newValue, Tolerance.Value)) + float newValue = v.NewValue / 33f; + if (!Precision.AlmostEquals(newValue, Tolerance.Value, 1e-7f)) Tolerance.Value = newValue; }); Tolerance.BindValueChanged(v => { - int newValue = (int)Math.Round(v.NewValue * 100f); + int newValue = (int)Math.Round(v.NewValue * 33f); if (sliderTolerance.Value != newValue) sliderTolerance.Value = newValue; }); + sliderCornerThreshold.BindValueChanged(v => + { + float newValue = v.NewValue / 100f; + if (!Precision.AlmostEquals(newValue, CornerThreshold.Value, 1e-7f)) + CornerThreshold.Value = newValue; + }); + CornerThreshold.BindValueChanged(v => + { + int newValue = (int)Math.Round(v.NewValue * 100f); + if (sliderCornerThreshold.Value != newValue) + sliderCornerThreshold.Value = newValue; + }); } public void AttachToToolbox(ExpandingToolboxContainer toolboxContainer) @@ -54,6 +80,10 @@ namespace osu.Game.Rulesets.Osu.Edit toleranceSlider = new ExpandableSlider { Current = sliderTolerance + }, + cornerThresholdSlider = new ExpandableSlider + { + Current = sliderCornerThreshold } } }); @@ -63,6 +93,12 @@ namespace osu.Game.Rulesets.Osu.Edit toleranceSlider.ContractedLabelText = $"C. P. S.: {e.NewValue:N0}"; toleranceSlider.ExpandedLabelText = $"Control Point Spacing: {e.NewValue:N0}"; }, true); + + sliderCornerThreshold.BindValueChanged(e => + { + cornerThresholdSlider.ContractedLabelText = $"C. T.: {e.NewValue:N0}"; + cornerThresholdSlider.ExpandedLabelText = $"Corner Threshold: {e.NewValue:N0}"; + }, true); } } } From 83f8f03c7e3b173766e9c715bd7869929082600b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 14 Nov 2023 21:46:57 +0900 Subject: [PATCH 1513/4092] Fix argon health bar not relative sizing correctly --- osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index f4ce7d1633..4a5faafd8b 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -141,7 +141,13 @@ namespace osu.Game.Screens.Play.HUD Current.BindValueChanged(_ => Scheduler.AddOnce(updateCurrent), true); + // we're about to set `RelativeSizeAxes` depending on the value of `UseRelativeSize`. + // setting `RelativeSizeAxes` internally transforms absolute sizing to relative and back to keep the size the same, + // but that is not what we want in this case, since the width at this point is valid in the *target* sizing mode. + // to counteract this, store the numerical value here, and restore it after setting the correct initial relative sizing axes. + float previousWidth = Width; UseRelativeSize.BindValueChanged(v => RelativeSizeAxes = v.NewValue ? Axes.X : Axes.None, true); + Width = previousWidth; BarHeight.BindValueChanged(_ => updatePath(), true); } From a745642f764cf28b37800936186399f459cdd47d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Nov 2023 22:01:56 +0900 Subject: [PATCH 1514/4092] Fix customised argon skins no longer loading due to incorrect resource store spec --- .../Screens/Play/HUD/ArgonCounterTextComponent.cs | 14 +++++++------- osu.Game/Skinning/ArgonSkin.cs | 4 +--- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs index 56f60deae1..eabac9a3e6 100644 --- a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs +++ b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs @@ -9,11 +9,11 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; using osu.Framework.Localisation; using osu.Framework.Text; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Skinning; using osuTK; namespace osu.Game.Screens.Play.HUD @@ -133,30 +133,30 @@ namespace osu.Game.Screens.Play.HUD } [BackgroundDependencyLoader] - private void load(ISkinSource skin) + private void load(TextureStore textures) { Spacing = new Vector2(-2f, 0f); Font = new FontUsage(@"argon-counter", 1); - glyphStore = new GlyphStore(skin, getLookup); + glyphStore = new GlyphStore(textures, getLookup); } protected override TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) => base.CreateTextBuilder(glyphStore); private class GlyphStore : ITexturedGlyphLookupStore { - private readonly ISkin skin; + private readonly TextureStore textures; private readonly Func getLookup; - public GlyphStore(ISkin skin, Func getLookup) + public GlyphStore(TextureStore textures, Func getLookup) { - this.skin = skin; + this.textures = textures; this.getLookup = getLookup; } public ITexturedCharacterGlyph? Get(string fontName, char character) { string lookup = getLookup(character); - var texture = skin.GetTexture($"{fontName}-{lookup}"); + var texture = textures.Get($"Gameplay/Fonts/{fontName}-{lookup}"); if (texture == null) return null; diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 637467c748..226de4088c 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -8,7 +8,6 @@ using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; -using osu.Framework.IO.Stores; using osu.Game.Audio; using osu.Game.Beatmaps.Formats; using osu.Game.Extensions; @@ -44,8 +43,7 @@ namespace osu.Game.Skinning public ArgonSkin(SkinInfo skin, IStorageResourceProvider resources) : base( skin, - resources, - new NamespacedResourceStore(resources.Resources, "Skins/Argon") + resources ) { Resources = resources; From 9a7d7dda2ae564fef11f3e544be339da42abb8d8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Nov 2023 23:04:11 +0900 Subject: [PATCH 1515/4092] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 81600c1f72..9985afbd8b 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + From f5e1734de9c37b1ea9fc8aac79bdf662e5503da0 Mon Sep 17 00:00:00 2001 From: Poyo Date: Tue, 14 Nov 2023 13:51:55 -0800 Subject: [PATCH 1516/4092] Use soft-cast to access IGameplayClock --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 7cfb6aedf0..5abca168ed 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -154,9 +154,6 @@ namespace osu.Game.Rulesets.Objects.Drawables [Resolved(CanBeNull = true)] private IPooledHitObjectProvider pooledObjectProvider { get; set; } - [Resolved(CanBeNull = true)] - private IGameplayClock gameplayClock { get; set; } - /// /// Whether the initialization logic in has applied. /// @@ -707,7 +704,7 @@ namespace osu.Game.Rulesets.Objects.Drawables } Result.RawTime = Time.Current; - Result.GameplayRate = gameplayClock?.GetTrueGameplayRate() ?? 1.0; + Result.GameplayRate = (Clock as IGameplayClock)?.GetTrueGameplayRate() ?? 1.0; if (Result.HasResult) updateState(Result.IsHit ? ArmedState.Hit : ArmedState.Miss); From 535282ba7d90303058c429c564c9d473afaf8e99 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 14 Nov 2023 14:13:20 -0800 Subject: [PATCH 1517/4092] Use existing localisation for corner radius in `BoxElement` --- osu.Game/Skinning/Components/BoxElement.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Components/BoxElement.cs b/osu.Game/Skinning/Components/BoxElement.cs index 235f97ceef..f4f913d80a 100644 --- a/osu.Game/Skinning/Components/BoxElement.cs +++ b/osu.Game/Skinning/Components/BoxElement.cs @@ -7,6 +7,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Configuration; +using osu.Game.Localisation.SkinComponents; +using osu.Game.Overlays.Settings; using osuTK; using osuTK.Graphics; @@ -16,7 +18,8 @@ namespace osu.Game.Skinning.Components { public bool UsesFixedAnchor { get; set; } - [SettingSource("Corner rounding", "How round the corners of the box should be.")] + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.CornerRadius), nameof(SkinnableComponentStrings.CornerRadiusDescription), + SettingControlType = typeof(SettingsPercentageSlider))] public BindableFloat CornerRounding { get; } = new BindableFloat(1) { Precision = 0.01f, From 74fb1b5f81475121af75072367759a241aa4db38 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Nov 2023 10:40:59 +0900 Subject: [PATCH 1518/4092] Rename property to match expctations --- osu.Game/Skinning/Components/BoxElement.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/Components/BoxElement.cs b/osu.Game/Skinning/Components/BoxElement.cs index f4f913d80a..8b556418d2 100644 --- a/osu.Game/Skinning/Components/BoxElement.cs +++ b/osu.Game/Skinning/Components/BoxElement.cs @@ -20,7 +20,7 @@ namespace osu.Game.Skinning.Components [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.CornerRadius), nameof(SkinnableComponentStrings.CornerRadiusDescription), SettingControlType = typeof(SettingsPercentageSlider))] - public BindableFloat CornerRounding { get; } = new BindableFloat(1) + public new BindableFloat CornerRadius { get; } = new BindableFloat(1) { Precision = 0.01f, MinValue = 0, @@ -47,7 +47,7 @@ namespace osu.Game.Skinning.Components { base.Update(); - CornerRadius = CornerRounding.Value * Math.Min(DrawWidth, DrawHeight) * 0.5f; + base.CornerRadius = CornerRadius.Value * Math.Min(DrawWidth, DrawHeight) * 0.5f; } } } From 2264e1e2493b69d0455f57639e60e3e2e10f7e96 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Nov 2023 10:45:01 +0900 Subject: [PATCH 1519/4092] Change default value and range of `BoxElement`'s corner radius to match other usages --- osu.Game/Skinning/ArgonSkin.cs | 5 ++++- osu.Game/Skinning/Components/BoxElement.cs | 8 ++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 226de4088c..4588c62b0f 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -216,7 +216,10 @@ namespace osu.Game.Skinning }, new ArgonScoreCounter(), new ArgonHealthDisplay(), - new BoxElement(), + new BoxElement + { + CornerRadius = { Value = 0.5f } + }, new ArgonAccuracyCounter(), new ArgonComboCounter { diff --git a/osu.Game/Skinning/Components/BoxElement.cs b/osu.Game/Skinning/Components/BoxElement.cs index 8b556418d2..34d389728c 100644 --- a/osu.Game/Skinning/Components/BoxElement.cs +++ b/osu.Game/Skinning/Components/BoxElement.cs @@ -20,11 +20,11 @@ namespace osu.Game.Skinning.Components [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.CornerRadius), nameof(SkinnableComponentStrings.CornerRadiusDescription), SettingControlType = typeof(SettingsPercentageSlider))] - public new BindableFloat CornerRadius { get; } = new BindableFloat(1) + public new BindableFloat CornerRadius { get; } = new BindableFloat(0.25f) { - Precision = 0.01f, MinValue = 0, - MaxValue = 1, + MaxValue = 0.5f, + Precision = 0.01f }; public BoxElement() @@ -47,7 +47,7 @@ namespace osu.Game.Skinning.Components { base.Update(); - base.CornerRadius = CornerRadius.Value * Math.Min(DrawWidth, DrawHeight) * 0.5f; + base.CornerRadius = CornerRadius.Value * Math.Min(DrawWidth, DrawHeight); } } } From 159cf41f824546013c22ec454fe1bb33a460f2d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 15 Nov 2023 12:24:47 +0900 Subject: [PATCH 1520/4092] Fix default argon health bar width being zero Closes #25460. --- osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 4a5faafd8b..82203d7891 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -94,6 +94,13 @@ namespace osu.Game.Screens.Play.HUD public ArgonHealthDisplay() { AddLayout(drawSizeLayout); + + // sane default width specification. + // this only matters if the health display isn't part of the default skin + // (in which case width will be set to 300 via `ArgonSkin.GetDrawableComponent()`), + // and if the user hasn't applied their own modifications + // (which are applied via `SerialisedDrawableInfo.ApplySerialisedInfo()`). + Width = 0.98f; } [BackgroundDependencyLoader] From 71714f8f6e6bd7d2caf9214ff267c05e550352e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 15 Nov 2023 13:33:03 +0900 Subject: [PATCH 1521/4092] Add back test step for testing out argon health bar width --- .../Visual/Gameplay/TestSceneArgonHealthDisplay.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs index f51577bc84..6c364e41c7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs @@ -52,6 +52,12 @@ namespace osu.Game.Tests.Visual.Gameplay if (healthDisplay.IsNotNull()) healthDisplay.BarHeight.Value = val; }); + + AddSliderStep("Width", 0, 1f, 0.98f, val => + { + if (healthDisplay.IsNotNull()) + healthDisplay.Width = val; + }); } [Test] From 87ace7565dfffebbcfb34f48743406a0c68d1065 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 14 Nov 2023 20:44:33 -0800 Subject: [PATCH 1522/4092] Use existing localisation for argon counter component labels --- osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs | 2 +- osu.Game/Screens/Play/HUD/ArgonComboCounter.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs index 5f9441a5c4..55fdf24e8f 100644 --- a/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs @@ -64,7 +64,7 @@ namespace osu.Game.Screens.Play.HUD new Container { AutoSizeAxes = Axes.Both, - Child = wholePart = new ArgonCounterTextComponent(Anchor.TopRight, "ACCURACY") + Child = wholePart = new ArgonCounterTextComponent(Anchor.TopRight, BeatmapsetsStrings.ShowScoreboardHeadersAccuracy.ToUpper()) { RequiredDisplayDigits = { Value = 3 }, WireframeOpacity = { BindTarget = WireframeOpacity } diff --git a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs index ac710294ef..52af9b0247 100644 --- a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Play.HUD protected override LocalisableString FormatCount(int count) => $@"{count}x"; - protected override IHasText CreateText() => text = new ArgonCounterTextComponent(Anchor.TopLeft, "COMBO") + protected override IHasText CreateText() => text = new ArgonCounterTextComponent(Anchor.TopLeft, MatchesStrings.MatchScoreStatsCombo.ToUpper()) { WireframeOpacity = { BindTarget = WireframeOpacity }, }; From 62352ce5f3b6ce56c310a83dccd11d9eb15f90fa Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 14 Nov 2023 20:45:23 -0800 Subject: [PATCH 1523/4092] Add ability to toggle labels on argon counter components --- .../SkinnableComponentStrings.cs | 10 +++++++ .../Screens/Play/HUD/ArgonAccuracyCounter.cs | 28 ++++++++++++++++--- .../Screens/Play/HUD/ArgonComboCounter.cs | 7 +++++ .../Play/HUD/ArgonCounterTextComponent.cs | 4 ++- .../Screens/Play/HUD/ArgonScoreCounter.cs | 8 +++++- 5 files changed, 51 insertions(+), 6 deletions(-) diff --git a/osu.Game/Localisation/SkinComponents/SkinnableComponentStrings.cs b/osu.Game/Localisation/SkinComponents/SkinnableComponentStrings.cs index 7c11ea6ac6..639f5c9b16 100644 --- a/osu.Game/Localisation/SkinComponents/SkinnableComponentStrings.cs +++ b/osu.Game/Localisation/SkinComponents/SkinnableComponentStrings.cs @@ -49,6 +49,16 @@ namespace osu.Game.Localisation.SkinComponents /// public static LocalisableString CornerRadiusDescription => new TranslatableString(getKey(@"corner_radius_description"), "How rounded the corners should be."); + /// + /// "Show label" + /// + public static LocalisableString ShowLabel => new TranslatableString(getKey(@"show_label"), @"Show label"); + + /// + /// "Whether the label should be shown." + /// + public static LocalisableString ShowLabelDescription => new TranslatableString(getKey(@"show_label_description"), @"Whether the label should be shown."); + private static string getKey(string key) => $"{prefix}:{key}"; } } diff --git a/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs index 55fdf24e8f..521ad63426 100644 --- a/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs @@ -2,11 +2,14 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Configuration; +using osu.Game.Localisation.SkinComponents; +using osu.Game.Resources.Localisation.Web; using osu.Game.Skinning; using osuTK; @@ -25,20 +28,27 @@ namespace osu.Game.Screens.Play.HUD MaxValue = 1, }; + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.ShowLabel), nameof(SkinnableComponentStrings.ShowLabelDescription))] + public Bindable ShowLabel { get; } = new BindableBool(true); + public bool UsesFixedAnchor { get; set; } protected override IHasText CreateText() => new ArgonAccuracyTextComponent { WireframeOpacity = { BindTarget = WireframeOpacity }, + ShowLabel = { BindTarget = ShowLabel }, }; private partial class ArgonAccuracyTextComponent : CompositeDrawable, IHasText { private readonly ArgonCounterTextComponent wholePart; private readonly ArgonCounterTextComponent fractionPart; + private readonly ArgonCounterTextComponent percentText; public IBindable WireframeOpacity { get; } = new BindableFloat(); + public Bindable ShowLabel { get; } = new BindableBool(); + public LocalisableString Text { get => wholePart.Text; @@ -67,24 +77,34 @@ namespace osu.Game.Screens.Play.HUD Child = wholePart = new ArgonCounterTextComponent(Anchor.TopRight, BeatmapsetsStrings.ShowScoreboardHeadersAccuracy.ToUpper()) { RequiredDisplayDigits = { Value = 3 }, - WireframeOpacity = { BindTarget = WireframeOpacity } + WireframeOpacity = { BindTarget = WireframeOpacity }, + ShowLabel = { BindTarget = ShowLabel }, } }, fractionPart = new ArgonCounterTextComponent(Anchor.TopLeft) { - Margin = new MarginPadding { Top = 12f * 2f + 4f }, // +4 to account for the extra spaces above the digits. WireframeOpacity = { BindTarget = WireframeOpacity }, Scale = new Vector2(0.5f), }, - new ArgonCounterTextComponent(Anchor.TopLeft) + percentText = new ArgonCounterTextComponent(Anchor.TopLeft) { Text = @"%", - Margin = new MarginPadding { Top = 12f }, WireframeOpacity = { BindTarget = WireframeOpacity } }, } }; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ShowLabel.BindValueChanged(s => + { + fractionPart.Margin = new MarginPadding { Top = s.NewValue ? 12f * 2f + 4f : 4f }; // +4 to account for the extra spaces above the digits. + percentText.Margin = new MarginPadding { Top = s.NewValue ? 12f : 0 }; + }, true); + } } } } diff --git a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs index 52af9b0247..5ea7fd0b82 100644 --- a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs @@ -4,10 +4,13 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Configuration; +using osu.Game.Localisation.SkinComponents; +using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets.Scoring; using osuTK; using osuTK.Graphics; @@ -29,6 +32,9 @@ namespace osu.Game.Screens.Play.HUD MaxValue = 1, }; + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.ShowLabel), nameof(SkinnableComponentStrings.ShowLabelDescription))] + public Bindable ShowLabel { get; } = new BindableBool(true); + [BackgroundDependencyLoader] private void load(ScoreProcessor scoreProcessor) { @@ -56,6 +62,7 @@ namespace osu.Game.Screens.Play.HUD protected override IHasText CreateText() => text = new ArgonCounterTextComponent(Anchor.TopLeft, MatchesStrings.MatchScoreStatsCombo.ToUpper()) { WireframeOpacity = { BindTarget = WireframeOpacity }, + ShowLabel = { BindTarget = ShowLabel }, }; } } diff --git a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs index eabac9a3e6..d3fadb452b 100644 --- a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs +++ b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs @@ -26,6 +26,7 @@ namespace osu.Game.Screens.Play.HUD public IBindable WireframeOpacity { get; } = new BindableFloat(); public Bindable RequiredDisplayDigits { get; } = new BindableInt(); + public Bindable ShowLabel { get; } = new BindableBool(); public Container NumberContainer { get; private set; } @@ -56,7 +57,7 @@ namespace osu.Game.Screens.Play.HUD { labelText = new OsuSpriteText { - Alpha = label != null ? 1 : 0, + Alpha = 0, Text = label.GetValueOrDefault(), Font = OsuFont.Torus.With(size: 12, weight: FontWeight.Bold), Margin = new MarginPadding { Left = 2.5f }, @@ -114,6 +115,7 @@ namespace osu.Game.Screens.Play.HUD { base.LoadComplete(); WireframeOpacity.BindValueChanged(v => wireframesPart.Alpha = v.NewValue, true); + ShowLabel.BindValueChanged(s => labelText.Alpha = s.NewValue ? 1 : 0, true); } private partial class ArgonCounterSpriteText : OsuSpriteText diff --git a/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs b/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs index 0192fa3c02..d661cd67cc 100644 --- a/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs @@ -7,6 +7,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Configuration; +using osu.Game.Localisation.SkinComponents; +using osu.Game.Resources.Localisation.Web; using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD @@ -24,14 +26,18 @@ namespace osu.Game.Screens.Play.HUD MaxValue = 1, }; + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.ShowLabel), nameof(SkinnableComponentStrings.ShowLabelDescription))] + public Bindable ShowLabel { get; } = new BindableBool(); + public bool UsesFixedAnchor { get; set; } protected override LocalisableString FormatCount(long count) => count.ToLocalisableString(); - protected override IHasText CreateText() => new ArgonScoreTextComponent(Anchor.TopRight) + protected override IHasText CreateText() => new ArgonScoreTextComponent(Anchor.TopRight, BeatmapsetsStrings.ShowScoreboardHeadersScore.ToUpper()) { RequiredDisplayDigits = { BindTarget = RequiredDisplayDigits }, WireframeOpacity = { BindTarget = WireframeOpacity }, + ShowLabel = { BindTarget = ShowLabel }, }; private partial class ArgonScoreTextComponent : ArgonCounterTextComponent From a5e1dd8107a8770ab8698cc39a17df2e81847a8b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Nov 2023 14:07:51 +0900 Subject: [PATCH 1524/4092] Add test coverage of deserialising a modified Argon skin --- osu.Game.Tests/Skins/SkinDeserialisationTest.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs index f68250e0fa..c45eadeff2 100644 --- a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs +++ b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs @@ -15,6 +15,7 @@ using osu.Game.IO.Archives; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD.HitErrorMeters; using osu.Game.Skinning; +using osu.Game.Skinning.Components; using osu.Game.Tests.Resources; namespace osu.Game.Tests.Skins @@ -102,6 +103,20 @@ namespace osu.Game.Tests.Skins } } + [Test] + public void TestDeserialiseModifiedArgon() + { + using (var stream = TestResources.OpenResource("Archives/modified-argon-20231106.osk")) + using (var storage = new ZipArchiveReader(stream)) + { + var skin = new TestSkin(new SkinInfo(), null, storage); + + Assert.That(skin.LayoutInfos, Has.Count.EqualTo(2)); + Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(10)); + Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(PlayerName))); + } + } + [Test] public void TestDeserialiseModifiedClassic() { From aac1854d832ff8ad48333ed0afd968ab50dcf712 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Nov 2023 14:08:05 +0900 Subject: [PATCH 1525/4092] Add test coverage of layout retrievable after importing modified skins --- osu.Game.Tests/Skins/IO/ImportSkinTest.cs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index ab3e099c3a..98f7dc5444 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -9,10 +9,12 @@ using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Platform; +using osu.Framework.Testing; using osu.Game.Database; using osu.Game.Extensions; using osu.Game.IO; using osu.Game.Skinning; +using osu.Game.Tests.Resources; using SharpCompress.Archives.Zip; namespace osu.Game.Tests.Skins.IO @@ -21,6 +23,25 @@ namespace osu.Game.Tests.Skins.IO { #region Testing filename metadata inclusion + [TestCase("Archives/modified-classic-20220723.osk")] + [TestCase("Archives/modified-default-20230117.osk")] + [TestCase("Archives/modified-argon-20231106.osk")] + public Task TestImportModifiedSkinHasResources(string archive) => runSkinTest(async osu => + { + using (var stream = TestResources.OpenResource(archive)) + { + var imported = await loadSkinIntoOsu(osu, new ImportTask(stream, "skin.osk")); + + // When the import filename doesn't match, it should be appended (and update the skin.ini). + + var skinManager = osu.Dependencies.Get(); + + skinManager.CurrentSkinInfo.Value = imported; + + Assert.That(skinManager.CurrentSkin.Value.LayoutInfos.Count, Is.EqualTo(2)); + } + }); + [Test] public Task TestSingleImportDifferentFilename() => runSkinTest(async osu => { From ceeaf5b67c527c787b8d34eb3594207ed9ac14d7 Mon Sep 17 00:00:00 2001 From: cs Date: Wed, 15 Nov 2023 07:09:33 +0100 Subject: [PATCH 1526/4092] CI fixes and small tweaks --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 4 ++-- .../Edit/OsuSliderDrawingSettingsProvider.cs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 69e2a40689..df7d2c716b 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -108,8 +108,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders BeginPlacement(); double? nearestSliderVelocity = (editorBeatmap - .HitObjects - .LastOrDefault(h => h is Slider && h.GetEndTime() < HitObject.StartTime) as Slider)?.SliderVelocityMultiplier; + .HitObjects + .LastOrDefault(h => h is Slider && h.GetEndTime() < HitObject.StartTime) as Slider)?.SliderVelocityMultiplier; HitObject.SliderVelocityMultiplier = nearestSliderVelocity ?? 1; HitObject.Position = ToLocalSpace(result.ScreenSpacePosition); diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs b/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs index 1fe1326f38..4326b2e943 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Edit sliderTolerance.BindValueChanged(v => { float newValue = v.NewValue / 33f; - if (!Precision.AlmostEquals(newValue, Tolerance.Value, 1e-7f)) + if (!Precision.AlmostEquals(newValue, Tolerance.Value)) Tolerance.Value = newValue; }); Tolerance.BindValueChanged(v => @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Edit sliderCornerThreshold.BindValueChanged(v => { float newValue = v.NewValue / 100f; - if (!Precision.AlmostEquals(newValue, CornerThreshold.Value, 1e-7f)) + if (!Precision.AlmostEquals(newValue, CornerThreshold.Value)) CornerThreshold.Value = newValue; }); CornerThreshold.BindValueChanged(v => @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Edit public void AttachToToolbox(ExpandingToolboxContainer toolboxContainer) { - toolboxContainer.Add(new EditorToolboxGroup("drawing") + toolboxContainer.Add(new EditorToolboxGroup("slider") { Children = new Drawable[] { From 8cd1f08a923b1b38e7b096e59211c0714089d1e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 15 Nov 2023 13:33:12 +0900 Subject: [PATCH 1527/4092] Fix argon health bar folding in on itself --- .../Screens/Play/HUD/ArgonHealthDisplay.cs | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 82203d7891..372d2bab85 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -89,6 +89,11 @@ namespace osu.Game.Screens.Play.HUD public const float MAIN_PATH_RADIUS = 10f; + private const float curve_start_offset = 70; + private const float curve_end_offset = 40; + private const float padding = MAIN_PATH_RADIUS * 2; + private const float curve_smoothness = 10; + private readonly LayoutValue drawSizeLayout = new LayoutValue(Invalidation.DrawSize); public ArgonHealthDisplay() @@ -248,11 +253,17 @@ namespace osu.Game.Screens.Play.HUD private void updatePath() { - float barLength = DrawWidth - MAIN_PATH_RADIUS * 2; - float curveStart = barLength - 70; - float curveEnd = barLength - 40; + float usableWidth = DrawWidth - padding; - const float curve_smoothness = 10; + if (usableWidth < 0) enforceMinimumWidth(); + + // the display starts curving at `curve_start_offset` units from the right and ends curving at `curve_end_offset`. + // to ensure that the curve is symmetric when it starts being narrow enough, add a `curve_end_offset` to the left side too. + const float rescale_cutoff = curve_start_offset + curve_end_offset; + + float barLength = Math.Max(DrawWidth - padding, rescale_cutoff); + float curveStart = barLength - curve_start_offset; + float curveEnd = barLength - curve_end_offset; Vector2 diagonalDir = (new Vector2(curveEnd, BarHeight.Value) - new Vector2(curveStart, 0)).Normalized(); @@ -268,6 +279,9 @@ namespace osu.Game.Screens.Play.HUD new PathControlPoint(new Vector2(barLength, BarHeight.Value)), }); + if (DrawWidth - padding < rescale_cutoff) + rescalePathProportionally(); + List vertices = new List(); barPath.GetPathToProgress(vertices, 0.0, 1.0); @@ -276,6 +290,20 @@ namespace osu.Game.Screens.Play.HUD glowBar.Vertices = vertices; updatePathVertices(); + + void enforceMinimumWidth() + { + var relativeAxes = RelativeSizeAxes; + RelativeSizeAxes = Axes.None; + Width = padding; + RelativeSizeAxes = relativeAxes; + } + + void rescalePathProportionally() + { + foreach (var point in barPath.ControlPoints) + point.Position = new Vector2(point.Position.X / barLength * (DrawWidth - padding), point.Position.Y); + } } private void updatePathVertices() From 520642975bda8de23c51e2b350c637a93115b08c Mon Sep 17 00:00:00 2001 From: cs Date: Wed, 15 Nov 2023 07:45:09 +0100 Subject: [PATCH 1528/4092] Fix control point hover text and context menu --- .../Sliders/Components/PathControlPointPiece.cs | 2 +- .../Sliders/Components/PathControlPointVisualiser.cs | 2 +- osu.Game/Rulesets/Objects/Types/PathType.cs | 12 +++++++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index ac9048d5c7..03792d8520 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -279,6 +279,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components } } - public LocalisableString TooltipText => ControlPoint.Type.ToString() ?? string.Empty; + public LocalisableString TooltipText => ControlPoint.Type?.Description ?? string.Empty; } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 5ab050ed48..faae966d02 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -403,7 +403,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components int totalCount = Pieces.Count(p => p.IsSelected.Value); int countOfState = Pieces.Where(p => p.IsSelected.Value).Count(p => p.ControlPoint.Type == type); - var item = new TernaryStateRadioMenuItem(type == null ? "Inherit" : type.ToString().Humanize(), MenuItemType.Standard, _ => + var item = new TernaryStateRadioMenuItem(type == null ? "Inherit" : type!.Value.Description, MenuItemType.Standard, _ => { foreach (var p in Pieces.Where(p => p.IsSelected.Value)) updatePathType(p, type); diff --git a/osu.Game/Rulesets/Objects/Types/PathType.cs b/osu.Game/Rulesets/Objects/Types/PathType.cs index 4fb48bb8b4..e4249154e5 100644 --- a/osu.Game/Rulesets/Objects/Types/PathType.cs +++ b/osu.Game/Rulesets/Objects/Types/PathType.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Bindables; namespace osu.Game.Rulesets.Objects.Types { @@ -13,7 +14,7 @@ namespace osu.Game.Rulesets.Objects.Types PerfectCurve } - public readonly struct PathType : IEquatable + public readonly struct PathType : IEquatable, IHasDescription { public static readonly PathType CATMULL = new PathType(SplineType.Catmull); public static readonly PathType BEZIER = new PathType(SplineType.BSpline); @@ -31,6 +32,15 @@ namespace osu.Game.Rulesets.Objects.Types ///
public int? Degree { get; init; } + public string Description => Type switch + { + SplineType.Catmull => "Catmull", + SplineType.BSpline => Degree == null ? "Bezier" : "B-Spline", + SplineType.Linear => "Linear", + SplineType.PerfectCurve => "Perfect Curve", + _ => Type.ToString() + }; + public PathType(SplineType splineType) { Type = splineType; From 360864fd7b6dac30bda9a2cf4092a316b280427d Mon Sep 17 00:00:00 2001 From: cs Date: Wed, 15 Nov 2023 07:45:28 +0100 Subject: [PATCH 1529/4092] Hide catmull curve type when possible --- .../Sliders/Components/PathControlPointVisualiser.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index faae966d02..95c72a0a62 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -371,7 +371,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components curveTypeItems.Add(createMenuItemForPathType(PathType.PERFECT_CURVE)); curveTypeItems.Add(createMenuItemForPathType(PathType.BEZIER)); curveTypeItems.Add(createMenuItemForPathType(PathType.BSpline(3))); - curveTypeItems.Add(createMenuItemForPathType(PathType.CATMULL)); + + var hoveredPiece = Pieces.FirstOrDefault(p => p.IsHovered); + + if (hoveredPiece?.ControlPoint.Type == PathType.CATMULL) + curveTypeItems.Add(createMenuItemForPathType(PathType.CATMULL)); var menuItems = new List { From aa9deecafe7ad9150d82bfa7015d7e21cf3888b5 Mon Sep 17 00:00:00 2001 From: Joshua Hegedus Date: Fri, 10 Nov 2023 11:37:23 +0100 Subject: [PATCH 1530/4092] added missing comment, fixed incorrect visibility --- osu.Game/Users/Drawables/ClickableAvatar.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Users/Drawables/ClickableAvatar.cs b/osu.Game/Users/Drawables/ClickableAvatar.cs index ef451df95d..1f1960714f 100644 --- a/osu.Game/Users/Drawables/ClickableAvatar.cs +++ b/osu.Game/Users/Drawables/ClickableAvatar.cs @@ -33,7 +33,7 @@ namespace osu.Game.Users.Drawables /// A clickable avatar for the specified user, with UI sounds included. ///
/// The user. A null value will get a placeholder avatar. - /// + /// If set to true, the will be shown for the tooltip public ClickableAvatar(APIUser? user = null, bool showCardOnHover = false) { if (user?.Id != APIUser.SYSTEM_USER_ID) @@ -72,7 +72,11 @@ namespace osu.Game.Users.Drawables } protected override void PopIn() => this.FadeIn(150, Easing.OutQuint); - protected override void PopOut() => this.Delay(150).FadeOut(500, Easing.OutQuint); + protected override void PopOut() + { + this.Delay(150).FadeOut(500, Easing.OutQuint); + Clear(); + } public void Move(Vector2 pos) => Position = pos; From deef8998f73850d0e40247fa5854f042967e4687 Mon Sep 17 00:00:00 2001 From: Joshua Hegedus Date: Fri, 10 Nov 2023 11:45:31 +0100 Subject: [PATCH 1531/4092] reverted the change --- osu.Game/Users/Drawables/ClickableAvatar.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Users/Drawables/ClickableAvatar.cs b/osu.Game/Users/Drawables/ClickableAvatar.cs index 1f1960714f..26622a1f30 100644 --- a/osu.Game/Users/Drawables/ClickableAvatar.cs +++ b/osu.Game/Users/Drawables/ClickableAvatar.cs @@ -72,11 +72,7 @@ namespace osu.Game.Users.Drawables } protected override void PopIn() => this.FadeIn(150, Easing.OutQuint); - protected override void PopOut() - { - this.Delay(150).FadeOut(500, Easing.OutQuint); - Clear(); - } + protected override void PopOut() => this.Delay(150).FadeOut(500, Easing.OutQuint); public void Move(Vector2 pos) => Position = pos; From 7b987266d50db8b3c5668a27625f7635a6eeefbc Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 14 Nov 2023 16:15:22 -0800 Subject: [PATCH 1532/4092] Change behavior of some clickable avatars in line with web --- osu.Game/Overlays/BeatmapSet/AuthorInfo.cs | 2 +- osu.Game/Overlays/Comments/CommentsContainer.cs | 2 +- osu.Game/Overlays/Comments/DrawableComment.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs index 1d01495188..99ad5a5c7d 100644 --- a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs +++ b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs @@ -55,7 +55,7 @@ namespace osu.Game.Overlays.BeatmapSet AutoSizeAxes = Axes.Both, CornerRadius = 4, Masking = true, - Child = avatar = new UpdateableAvatar(showGuestOnNull: false) + Child = avatar = new UpdateableAvatar(showUserPanelOnHover: true, showGuestOnNull: false) { Size = new Vector2(height), }, diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index af5f4dd280..b4e9a80ff1 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -102,7 +102,7 @@ namespace osu.Game.Overlays.Comments Padding = new MarginPadding { Horizontal = WaveOverlayContainer.HORIZONTAL_PADDING, Vertical = 20 }, Children = new Drawable[] { - avatar = new UpdateableAvatar(api.LocalUser.Value) + avatar = new UpdateableAvatar(api.LocalUser.Value, isInteractive: false) { Size = new Vector2(50), CornerExponent = 2, diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index ba1c7ca8b2..ceae17aa5d 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -144,7 +144,7 @@ namespace osu.Game.Overlays.Comments Size = new Vector2(avatar_size), Children = new Drawable[] { - new UpdateableAvatar(Comment.User) + new UpdateableAvatar(Comment.User, showUserPanelOnHover: true) { Size = new Vector2(avatar_size), Masking = true, From b118999120ec13d4632f032429fbc3a26cc43db2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 15 Nov 2023 18:27:08 +0900 Subject: [PATCH 1533/4092] Remove unused using directive --- osu.Game.Tests/Skins/IO/ImportSkinTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index 98f7dc5444..606a5afac2 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Platform; -using osu.Framework.Testing; using osu.Game.Database; using osu.Game.Extensions; using osu.Game.IO; From 2987c0e802ec597ab7480c9d3623b423592669bc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Nov 2023 19:01:52 +0900 Subject: [PATCH 1534/4092] Add note about enfocing size methodology --- osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 372d2bab85..5e6130d3f8 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -293,9 +293,13 @@ namespace osu.Game.Screens.Play.HUD void enforceMinimumWidth() { - var relativeAxes = RelativeSizeAxes; + // Switch to absolute in order to be able to define a minimum width. + // Then switch back is required. Framework will handle the conversion for us. + Axes relativeAxes = RelativeSizeAxes; RelativeSizeAxes = Axes.None; + Width = padding; + RelativeSizeAxes = relativeAxes; } From a73c870712ff5e2bb6613db29dd8d45d71c1f516 Mon Sep 17 00:00:00 2001 From: Poyo Date: Wed, 15 Nov 2023 17:00:35 -0800 Subject: [PATCH 1535/4092] Allow GameplayRate to be nullable and assert before use --- osu.Game/Rulesets/Judgements/JudgementResult.cs | 2 +- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 +- osu.Game/Rulesets/Scoring/HitEvent.cs | 4 ++-- osu.Game/Rulesets/Scoring/HitEventExtensions.cs | 5 ++++- osu.Game/Rulesets/UI/Playfield.cs | 2 +- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/JudgementResult.cs b/osu.Game/Rulesets/Judgements/JudgementResult.cs index 603d470954..db621b4851 100644 --- a/osu.Game/Rulesets/Judgements/JudgementResult.cs +++ b/osu.Game/Rulesets/Judgements/JudgementResult.cs @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Judgements /// /// The gameplay rate at the time this occurred. /// - public double GameplayRate { get; internal set; } + public double? GameplayRate { get; internal set; } /// /// The combo prior to this occurring. diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 5abca168ed..baf13d8911 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -704,7 +704,7 @@ namespace osu.Game.Rulesets.Objects.Drawables } Result.RawTime = Time.Current; - Result.GameplayRate = (Clock as IGameplayClock)?.GetTrueGameplayRate() ?? 1.0; + Result.GameplayRate = (Clock as IGameplayClock)?.GetTrueGameplayRate(); if (Result.HasResult) updateState(Result.IsHit ? ArmedState.Hit : ArmedState.Miss); diff --git a/osu.Game/Rulesets/Scoring/HitEvent.cs b/osu.Game/Rulesets/Scoring/HitEvent.cs index afa654318b..1763190899 100644 --- a/osu.Game/Rulesets/Scoring/HitEvent.cs +++ b/osu.Game/Rulesets/Scoring/HitEvent.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Scoring /// /// The true gameplay rate at the time of the event. /// - public readonly double GameplayRate; + public readonly double? GameplayRate; /// /// The hit result. @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Scoring /// The that triggered the event. /// The previous . /// A position corresponding to the event. - public HitEvent(double timeOffset, double gameplayRate, HitResult result, HitObject hitObject, [CanBeNull] HitObject lastHitObject, [CanBeNull] Vector2? position) + public HitEvent(double timeOffset, double? gameplayRate, HitResult result, HitObject hitObject, [CanBeNull] HitObject lastHitObject, [CanBeNull] Vector2? position) { TimeOffset = timeOffset; GameplayRate = gameplayRate; diff --git a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs index a93385ef43..70a11ae760 100644 --- a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs +++ b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; namespace osu.Game.Rulesets.Scoring @@ -18,8 +19,10 @@ namespace osu.Game.Rulesets.Scoring /// public static double? CalculateUnstableRate(this IEnumerable hitEvents) { + Debug.Assert(!hitEvents.Any(ev => ev.GameplayRate == null)); + // Division by gameplay rate is to account for TimeOffset scaling with gameplay rate. - double[] timeOffsets = hitEvents.Where(affectsUnstableRate).Select(ev => ev.TimeOffset / ev.GameplayRate).ToArray(); + double[] timeOffsets = hitEvents.Where(affectsUnstableRate).Select(ev => ev.TimeOffset / ev.GameplayRate!.Value).ToArray(); return 10 * standardDeviation(timeOffsets); } diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 17baf8838c..eb29d8f30a 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -473,7 +473,7 @@ namespace osu.Game.Rulesets.UI private void onNewResult(DrawableHitObject drawable, JudgementResult result) { - Debug.Assert(result != null && drawable.Entry?.Result == result && result.RawTime != null && result.GameplayRate != 0.0); + Debug.Assert(result != null && drawable.Entry?.Result == result && result.RawTime != null && result.GameplayRate != null); judgedEntries.Push(drawable.Entry.AsNonNull()); NewResult?.Invoke(drawable, result); From 2b19cf6ce4b908880db4854a4f74d30edd7714ac Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 15 Nov 2023 19:43:25 -0800 Subject: [PATCH 1536/4092] Fix comment markdown style regression --- .../Visual/Online/TestSceneWikiMarkdownContainer.cs | 3 +-- .../Markdown/Footnotes/OsuMarkdownFootnoteTooltip.cs | 3 +-- osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs | 2 +- osu.Game/Overlays/Comments/CommentMarkdownContainer.cs | 4 ++-- osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs | 3 +-- osu.Game/Overlays/Wiki/WikiPanelContainer.cs | 2 +- 6 files changed, 7 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index 0aa0295f7d..d4b6bc2b91 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -10,7 +10,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Containers.Markdown; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; @@ -298,7 +297,7 @@ This is a line after the fenced code block! { public LinkInline Link; - public override MarkdownTextFlowContainer CreateTextFlow() => new TestMarkdownTextFlowContainer + public override OsuMarkdownTextFlowContainer CreateTextFlow() => new TestMarkdownTextFlowContainer { UrlAdded = link => Link = link, }; diff --git a/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnoteTooltip.cs b/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnoteTooltip.cs index af64913212..b9725de5f4 100644 --- a/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnoteTooltip.cs +++ b/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnoteTooltip.cs @@ -5,7 +5,6 @@ using Markdig.Extensions.Footnotes; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Containers.Markdown; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Game.Overlays; @@ -62,7 +61,7 @@ namespace osu.Game.Graphics.Containers.Markdown.Footnotes lastFootnote = Text = footnote; } - public override MarkdownTextFlowContainer CreateTextFlow() => new FootnoteMarkdownTextFlowContainer(); + public override OsuMarkdownTextFlowContainer CreateTextFlow() => new FootnoteMarkdownTextFlowContainer(); } private partial class FootnoteMarkdownTextFlowContainer : OsuMarkdownTextFlowContainer diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs index 5da785603a..b4031752db 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs @@ -63,7 +63,7 @@ namespace osu.Game.Graphics.Containers.Markdown Font = OsuFont.GetFont(Typeface.Inter, size: 14, weight: FontWeight.Regular), }; - public override MarkdownTextFlowContainer CreateTextFlow() => new OsuMarkdownTextFlowContainer(); + public override OsuMarkdownTextFlowContainer CreateTextFlow() => new OsuMarkdownTextFlowContainer(); protected override MarkdownHeading CreateHeading(HeadingBlock headingBlock) => new OsuMarkdownHeading(headingBlock); diff --git a/osu.Game/Overlays/Comments/CommentMarkdownContainer.cs b/osu.Game/Overlays/Comments/CommentMarkdownContainer.cs index e48a52c787..13446792aa 100644 --- a/osu.Game/Overlays/Comments/CommentMarkdownContainer.cs +++ b/osu.Game/Overlays/Comments/CommentMarkdownContainer.cs @@ -22,7 +22,7 @@ namespace osu.Game.Overlays.Comments protected override MarkdownHeading CreateHeading(HeadingBlock headingBlock) => new CommentMarkdownHeading(headingBlock); - public override MarkdownTextFlowContainer CreateTextFlow() => new CommentMarkdownTextFlowContainer(); + public override OsuMarkdownTextFlowContainer CreateTextFlow() => new CommentMarkdownTextFlowContainer(); private partial class CommentMarkdownHeading : OsuMarkdownHeading { @@ -49,7 +49,7 @@ namespace osu.Game.Overlays.Comments } } - private partial class CommentMarkdownTextFlowContainer : MarkdownTextFlowContainer + private partial class CommentMarkdownTextFlowContainer : OsuMarkdownTextFlowContainer { protected override void AddImage(LinkInline linkInline) => AddDrawable(new CommentMarkdownImage(linkInline.Url)); diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs index 9107ad342b..e6bfb75026 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs @@ -7,7 +7,6 @@ using Markdig.Extensions.Yaml; using Markdig.Syntax; using Markdig.Syntax.Inlines; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Containers.Markdown; using osu.Game.Graphics.Containers.Markdown; namespace osu.Game.Overlays.Wiki.Markdown @@ -53,7 +52,7 @@ namespace osu.Game.Overlays.Wiki.Markdown base.AddMarkdownComponent(markdownObject, container, level); } - public override MarkdownTextFlowContainer CreateTextFlow() => new WikiMarkdownTextFlowContainer(); + public override OsuMarkdownTextFlowContainer CreateTextFlow() => new WikiMarkdownTextFlowContainer(); private partial class WikiMarkdownTextFlowContainer : OsuMarkdownTextFlowContainer { diff --git a/osu.Game/Overlays/Wiki/WikiPanelContainer.cs b/osu.Game/Overlays/Wiki/WikiPanelContainer.cs index c5b71cfeb6..cbffe5732e 100644 --- a/osu.Game/Overlays/Wiki/WikiPanelContainer.cs +++ b/osu.Game/Overlays/Wiki/WikiPanelContainer.cs @@ -93,7 +93,7 @@ namespace osu.Game.Overlays.Wiki public override SpriteText CreateSpriteText() => base.CreateSpriteText().With(t => t.Font = t.Font.With(Typeface.Torus, weight: FontWeight.Bold)); - public override MarkdownTextFlowContainer CreateTextFlow() => base.CreateTextFlow().With(f => f.TextAnchor = Anchor.TopCentre); + public override OsuMarkdownTextFlowContainer CreateTextFlow() => base.CreateTextFlow().With(f => f.TextAnchor = Anchor.TopCentre); protected override MarkdownParagraph CreateParagraph(ParagraphBlock paragraphBlock, int level) => base.CreateParagraph(paragraphBlock, level).With(p => p.Margin = new MarginPadding { Bottom = 10 }); From dc2d5749651de89bd1e4d5bba615068bfce1ac30 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 15 Nov 2023 19:41:13 -0800 Subject: [PATCH 1537/4092] Fix comment markdown image not showing tooltips --- osu.Game.Tests/Visual/Online/TestSceneDrawableComment.cs | 2 +- osu.Game/Overlays/Comments/CommentMarkdownContainer.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneDrawableComment.cs b/osu.Game.Tests/Visual/Online/TestSceneDrawableComment.cs index 97e1cae11c..5e83dd4fb3 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDrawableComment.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDrawableComment.cs @@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Online new[] { "Plain", "This is plain comment" }, new[] { "Pinned", "This is pinned comment" }, new[] { "Link", "Please visit https://osu.ppy.sh" }, - new[] { "Big Image", "![](Backgrounds/bg1)" }, + new[] { "Big Image", "![](Backgrounds/bg1 \"Big Image\")" }, new[] { "Small Image", "![](Cursor/cursortrail)" }, new[] { diff --git a/osu.Game/Overlays/Comments/CommentMarkdownContainer.cs b/osu.Game/Overlays/Comments/CommentMarkdownContainer.cs index 13446792aa..51e1b863c7 100644 --- a/osu.Game/Overlays/Comments/CommentMarkdownContainer.cs +++ b/osu.Game/Overlays/Comments/CommentMarkdownContainer.cs @@ -51,12 +51,12 @@ namespace osu.Game.Overlays.Comments private partial class CommentMarkdownTextFlowContainer : OsuMarkdownTextFlowContainer { - protected override void AddImage(LinkInline linkInline) => AddDrawable(new CommentMarkdownImage(linkInline.Url)); + protected override void AddImage(LinkInline linkInline) => AddDrawable(new CommentMarkdownImage(linkInline)); - private partial class CommentMarkdownImage : MarkdownImage + private partial class CommentMarkdownImage : OsuMarkdownImage { - public CommentMarkdownImage(string url) - : base(url) + public CommentMarkdownImage(LinkInline linkInline) + : base(linkInline) { } From 39a3313929035bc50e308a5ecee8f568f85dbe76 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Nov 2023 14:11:01 +0900 Subject: [PATCH 1538/4092] Update tooltip description slightly --- .../SkinnableComponentStrings.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game/Localisation/SkinComponents/SkinnableComponentStrings.cs b/osu.Game/Localisation/SkinComponents/SkinnableComponentStrings.cs index 639f5c9b16..d5c8d5ccec 100644 --- a/osu.Game/Localisation/SkinComponents/SkinnableComponentStrings.cs +++ b/osu.Game/Localisation/SkinComponents/SkinnableComponentStrings.cs @@ -12,42 +12,42 @@ namespace osu.Game.Localisation.SkinComponents /// /// "Sprite name" /// - public static LocalisableString SpriteName => new TranslatableString(getKey(@"sprite_name"), "Sprite name"); + public static LocalisableString SpriteName => new TranslatableString(getKey(@"sprite_name"), @"Sprite name"); /// /// "The filename of the sprite" /// - public static LocalisableString SpriteNameDescription => new TranslatableString(getKey(@"sprite_name_description"), "The filename of the sprite"); + public static LocalisableString SpriteNameDescription => new TranslatableString(getKey(@"sprite_name_description"), @"The filename of the sprite"); /// /// "Font" /// - public static LocalisableString Font => new TranslatableString(getKey(@"font"), "Font"); + public static LocalisableString Font => new TranslatableString(getKey(@"font"), @"Font"); /// /// "The font to use." /// - public static LocalisableString FontDescription => new TranslatableString(getKey(@"font_description"), "The font to use."); + public static LocalisableString FontDescription => new TranslatableString(getKey(@"font_description"), @"The font to use."); /// /// "Text" /// - public static LocalisableString TextElementText => new TranslatableString(getKey(@"text_element_text"), "Text"); + public static LocalisableString TextElementText => new TranslatableString(getKey(@"text_element_text"), @"Text"); /// /// "The text to be displayed." /// - public static LocalisableString TextElementTextDescription => new TranslatableString(getKey(@"text_element_text_description"), "The text to be displayed."); + public static LocalisableString TextElementTextDescription => new TranslatableString(getKey(@"text_element_text_description"), @"The text to be displayed."); /// /// "Corner radius" /// - public static LocalisableString CornerRadius => new TranslatableString(getKey(@"corner_radius"), "Corner radius"); + public static LocalisableString CornerRadius => new TranslatableString(getKey(@"corner_radius"), @"Corner radius"); /// /// "How rounded the corners should be." /// - public static LocalisableString CornerRadiusDescription => new TranslatableString(getKey(@"corner_radius_description"), "How rounded the corners should be."); + public static LocalisableString CornerRadiusDescription => new TranslatableString(getKey(@"corner_radius_description"), @"How rounded the corners should be."); /// /// "Show label" @@ -55,10 +55,10 @@ namespace osu.Game.Localisation.SkinComponents public static LocalisableString ShowLabel => new TranslatableString(getKey(@"show_label"), @"Show label"); /// - /// "Whether the label should be shown." + /// "Whether the component's label should be shown." /// - public static LocalisableString ShowLabelDescription => new TranslatableString(getKey(@"show_label_description"), @"Whether the label should be shown."); + public static LocalisableString ShowLabelDescription => new TranslatableString(getKey(@"show_label_description"), @"Whether the component's label should be shown."); - private static string getKey(string key) => $"{prefix}:{key}"; + private static string getKey(string key) => $@"{prefix}:{key}"; } } From 73eda6c09c259e0f517ffa75a0affb8902ef1fd4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Nov 2023 14:18:49 +0900 Subject: [PATCH 1539/4092] Move non-matching default value to argon skin default speficiation instead --- osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs | 2 +- osu.Game/Skinning/ArgonSkin.cs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs b/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs index d661cd67cc..005f7e36a7 100644 --- a/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Play.HUD }; [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.ShowLabel), nameof(SkinnableComponentStrings.ShowLabelDescription))] - public Bindable ShowLabel { get; } = new BindableBool(); + public Bindable ShowLabel { get; } = new BindableBool(true); public bool UsesFixedAnchor { get; set; } diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 4588c62b0f..6fcab6a977 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -214,7 +214,10 @@ namespace osu.Game.Skinning Size = new Vector2(380, 72), Position = new Vector2(4, 5) }, - new ArgonScoreCounter(), + new ArgonScoreCounter + { + ShowLabel = { Value = false }, + }, new ArgonHealthDisplay(), new BoxElement { From 265ae6fd30697495ab6a3896cd37eac883cfdcaa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Nov 2023 15:14:32 +0900 Subject: [PATCH 1540/4092] Remove unused using refs --- osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs index 5802f8fc0d..9ed1820045 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs @@ -3,7 +3,6 @@ using System; using System.Linq; -using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Judgements; From 3c513d0b620232e4db4b1849dc869e855c9898b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Nov 2023 15:29:32 +0900 Subject: [PATCH 1541/4092] Refactor fail reason output to not perform string interpolation unless hooked --- .../Scoring/OsuHealthProcessor.cs | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs index 9ed1820045..7d6a05026c 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Scoring double currentHp; double currentHpUncapped; - do + while (true) { currentHp = 1; currentHpUncapped = 1; @@ -57,7 +57,6 @@ namespace osu.Game.Rulesets.Osu.Scoring double lastTime = DrainStartTime; int currentBreak = 0; bool fail = false; - string failReason = string.Empty; for (int i = 0; i < Beatmap.HitObjects.Count; i++) { @@ -92,7 +91,7 @@ namespace osu.Game.Rulesets.Osu.Scoring { fail = true; testDrop *= 0.96; - failReason = $"hp too low ({currentHp} < {lowestHpEver})"; + OnIterationFail?.Invoke($"FAILED drop {testDrop}: hp too low ({currentHp} < {lowestHpEver})"); break; } @@ -117,7 +116,7 @@ namespace osu.Game.Rulesets.Osu.Scoring { fail = true; testDrop *= 0.96; - failReason = $"overkill ({currentHp} - {hpOverkill} <= {lowestHpEver})"; + OnIterationFail?.Invoke($"FAILED drop {testDrop}: overkill ({currentHp} - {hpOverkill} <= {lowestHpEver})"); break; } @@ -129,7 +128,7 @@ namespace osu.Game.Rulesets.Osu.Scoring fail = true; testDrop *= 0.94; hpMultiplierNormal *= 1.01; - failReason = $"end hp too low ({currentHp} < {lowestHpEnd})"; + OnIterationFail?.Invoke($"FAILED drop {testDrop}: end hp too low ({currentHp} < {lowestHpEnd})"); } double recovery = (currentHpUncapped - 1) / Beatmap.HitObjects.Count; @@ -139,18 +138,15 @@ namespace osu.Game.Rulesets.Osu.Scoring fail = true; testDrop *= 0.96; hpMultiplierNormal *= 1.01; - failReason = $"recovery too low ({recovery} < {hpRecoveryAvailable})"; + OnIterationFail?.Invoke($"FAILED drop {testDrop}: recovery too low ({recovery} < {hpRecoveryAvailable})"); } - if (fail) + if (!fail) { - OnIterationFail?.Invoke($"FAILED drop {testDrop}: {failReason}"); - continue; + OnIterationSuccess?.Invoke($"PASSED drop {testDrop}"); + return testDrop; } - - OnIterationSuccess?.Invoke($"PASSED drop {testDrop}"); - return testDrop; - } while (true); + } void reduceHp(double amount) { From dbd4f26436c988a8d1e1afb6e893e82fedb25460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 16 Nov 2023 15:37:53 +0900 Subject: [PATCH 1542/4092] Use alternative method of scheduling storyboard unload --- osu.Game/Screens/BackgroundScreenStack.cs | 18 ------------------ .../Backgrounds/BackgroundScreenDefault.cs | 6 +++++- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/BackgroundScreenStack.cs b/osu.Game/Screens/BackgroundScreenStack.cs index 562b212561..99ca383b9f 100644 --- a/osu.Game/Screens/BackgroundScreenStack.cs +++ b/osu.Game/Screens/BackgroundScreenStack.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. -using System; using System.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.Screens; -using osu.Framework.Threading; -using osu.Game.Screens.Backgrounds; namespace osu.Game.Screens { @@ -36,20 +33,5 @@ namespace osu.Game.Screens base.Push(screen); return true; } - - /// - /// Schedules a delegate to run after 500ms, the time length of a background screen transition. - /// This is used in to dispose of the storyboard once the background screen is completely off-screen. - /// - /// - /// Late storyboard disposals cannot be achieved with any local scheduler from or any component inside it, - /// due to the screen becoming dead at the moment the transition finishes. And, on the frame that it is dead on, it will not receive an , - /// therefore not guaranteeing to dispose the storyboard at any period of time close to the end of the transition. - /// This might require reconsideration framework-side, possibly exposing a "death" event in or all s in general. - /// - /// The delegate - /// - /// - internal ScheduledDelegate ScheduleUntilTransitionEnd(Action action) => Scheduler.AddDelayed(action, BackgroundScreen.TRANSITION_LENGTH); } } diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 4583b3e4d6..e46b92795a 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Logging; +using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Threading; using osu.Framework.Utils; @@ -36,6 +37,9 @@ namespace osu.Game.Screens.Backgrounds [Resolved] private IBindable beatmap { get; set; } + [Resolved] + private GameHost gameHost { get; set; } + protected virtual bool AllowStoryboardBackground => true; public BackgroundScreenDefault(bool animateOnEnter = true) @@ -81,7 +85,7 @@ namespace osu.Game.Screens.Backgrounds Debug.Assert(backgroundScreenStack != null); if (background is BeatmapBackgroundWithStoryboard storyboardBackground) - storyboardUnloadDelegate = backgroundScreenStack.ScheduleUntilTransitionEnd(storyboardBackground.UnloadStoryboard); + storyboardUnloadDelegate = gameHost.UpdateThread.Scheduler.AddDelayed(storyboardBackground.UnloadStoryboard, TRANSITION_LENGTH); base.OnSuspending(e); } From 85e303ec54cd60b75f6b3cc4d0a457caafda4afc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Nov 2023 20:00:09 +0900 Subject: [PATCH 1543/4092] Add two factor step to api state flow --- osu.Game/Online/API/APIAccess.cs | 5 +++++ osu.Game/Online/API/DummyAPIAccess.cs | 11 ++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 4f586c8fff..a3ceb14a40 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -540,6 +540,11 @@ namespace osu.Game.Online.API /// Failing, + /// + /// Waiting on second factor authentication. + /// + RequiresAuthentication, + /// /// We are in the process of (re-)connecting. /// diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index d585124db6..4cf2152193 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -55,6 +55,7 @@ namespace osu.Game.Online.API private bool shouldFailNextLogin; private bool stayConnectingNextLogin; + private bool requiresTwoFactor; /// /// The current connectivity state of the API. @@ -115,7 +116,13 @@ namespace osu.Game.Online.API Id = DUMMY_USER_ID, }; - state.Value = APIState.Online; + if (requiresTwoFactor) + { + state.Value = APIState.RequiresAuthentication; + requiresTwoFactor = false; + } + else + state.Value = APIState.Online; } public void Logout() @@ -142,6 +149,8 @@ namespace osu.Game.Online.API IBindableList IAPIProvider.Friends => Friends; IBindable IAPIProvider.Activity => Activity; + public void RequireTwoFactor() => requiresTwoFactor = true; + /// /// During the next simulated login, the process will fail immediately. /// From 80c879e5ebed1d1d15b8d44de47f1b15ea77470c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Nov 2023 16:37:12 +0900 Subject: [PATCH 1544/4092] Adjust member ordering in `LoginForm` --- osu.Game/Overlays/Login/LoginForm.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Login/LoginForm.cs b/osu.Game/Overlays/Login/LoginForm.cs index 0eef55162f..80dfca93d2 100644 --- a/osu.Game/Overlays/Login/LoginForm.cs +++ b/osu.Game/Overlays/Login/LoginForm.cs @@ -32,13 +32,7 @@ namespace osu.Game.Overlays.Login public Action? RequestHide; - private void performLogin() - { - if (!string.IsNullOrEmpty(username.Text) && !string.IsNullOrEmpty(password.Text)) - api.Login(username.Text, password.Text); - else - shakeSignIn.Shake(); - } + public override bool AcceptsFocus => true; [BackgroundDependencyLoader(permitNulls: true)] private void load(OsuConfigManager config, AccountCreationOverlay accountCreation) @@ -144,7 +138,13 @@ namespace osu.Game.Overlays.Login } } - public override bool AcceptsFocus => true; + private void performLogin() + { + if (!string.IsNullOrEmpty(username.Text) && !string.IsNullOrEmpty(password.Text)) + api.Login(username.Text, password.Text); + else + shakeSignIn.Shake(); + } protected override bool OnClick(ClickEvent e) => true; From e9d4cf2e24de488c75b9cd0a52e2b524dfca5248 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Nov 2023 20:00:22 +0900 Subject: [PATCH 1545/4092] Setup basic form flow --- .../Visual/Menus/TestSceneLoginOverlay.cs | 13 +++++++++++++ osu.Game/Overlays/Login/LoginForm.cs | 5 +++++ osu.Game/Overlays/Login/LoginPanel.cs | 6 +++++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs b/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs index 0bc71924ce..30c0f6644e 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs @@ -36,6 +36,19 @@ namespace osu.Game.Tests.Visual.Menus AddStep("show login overlay", () => loginOverlay.Show()); } + [Test] + public void TestLoginTwoFactorSuccess() + { + AddStep("logout", () => + { + API.Logout(); + ((DummyAPIAccess)API).RequireTwoFactor(); + }); + + AddStep("enter password", () => loginOverlay.ChildrenOfType().First().Text = "password"); + AddStep("submit", () => loginOverlay.ChildrenOfType().First(b => b.Text.ToString() == "Sign in").TriggerClick()); + } + [Test] public void TestLoginSuccess() { diff --git a/osu.Game/Overlays/Login/LoginForm.cs b/osu.Game/Overlays/Login/LoginForm.cs index 80dfca93d2..291e92883b 100644 --- a/osu.Game/Overlays/Login/LoginForm.cs +++ b/osu.Game/Overlays/Login/LoginForm.cs @@ -21,6 +21,11 @@ using osu.Game.Localisation; namespace osu.Game.Overlays.Login { + public partial class AccountVerificationForm : FillFlowContainer + { + + } + public partial class LoginForm : FillFlowContainer { private TextBox username = null!; diff --git a/osu.Game/Overlays/Login/LoginPanel.cs b/osu.Game/Overlays/Login/LoginPanel.cs index 71ecf2e75a..59b9c6ab9f 100644 --- a/osu.Game/Overlays/Login/LoginPanel.cs +++ b/osu.Game/Overlays/Login/LoginPanel.cs @@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Login { private bool bounding = true; - private LoginForm? form; + private Drawable? form; [Resolved] private OsuColour colours { get; set; } = null!; @@ -81,6 +81,10 @@ namespace osu.Game.Overlays.Login }; break; + case APIState.RequiresAuthentication: + Child = form = new AccountVerificationForm(); + break; + case APIState.Failing: case APIState.Connecting: LinkFlowContainer linkFlow; From c4e461ba447eea892668e12967af659e17689b1a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Nov 2023 11:49:49 +0900 Subject: [PATCH 1546/4092] Add two factor auth form --- .../Overlays/Login/AccountVerificationForm.cs | 92 +++++++++++++++++++ osu.Game/Overlays/Login/LoginForm.cs | 5 - 2 files changed, 92 insertions(+), 5 deletions(-) create mode 100644 osu.Game/Overlays/Login/AccountVerificationForm.cs diff --git a/osu.Game/Overlays/Login/AccountVerificationForm.cs b/osu.Game/Overlays/Login/AccountVerificationForm.cs new file mode 100644 index 0000000000..a27cc01ed2 --- /dev/null +++ b/osu.Game/Overlays/Login/AccountVerificationForm.cs @@ -0,0 +1,92 @@ +// 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.Containers; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Settings; +using osuTK; + +namespace osu.Game.Overlays.Login +{ + public partial class AccountVerificationForm : FillFlowContainer + { + private OsuTextBox code = null!; + private LinkFlowContainer explainText = null!; + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Direction = FillDirection.Vertical; + Spacing = new Vector2(0, SettingsSection.ITEM_SPACING); + + Children = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = SettingsPanel.CONTENT_MARGINS }, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0f, SettingsSection.ITEM_SPACING), + Children = new Drawable[] + { + new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(weight: FontWeight.Regular)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Text = "An email has been sent to you with a verification code. Enter the code.", + }, + code = new OsuTextBox + { + PlaceholderText = "Enter code", + RelativeSizeAxes = Axes.X, + TabbableContentContainer = this + }, + explainText = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(weight: FontWeight.Regular)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, + new ErrorTextFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Alpha = 0, + }, + }, + }, + new LinkFlowContainer + { + Padding = new MarginPadding { Horizontal = SettingsPanel.CONTENT_MARGINS }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, + }; + + explainText.AddParagraph("Make sure to check your spam folder if you can't find the email."); + explainText.AddParagraph("If you can't access your email or have forgotten what you used, please follow the "); + explainText.AddLink("email recovery process here", () => { }); + explainText.AddText(". You can also "); + explainText.AddLink("request another code", () => { }); + explainText.AddText(" or "); + explainText.AddLink("sign out", () => { }); + explainText.AddText("."); + } + + public override bool AcceptsFocus => true; + + protected override bool OnClick(ClickEvent e) => true; + + protected override void OnFocus(FocusEvent e) + { + Schedule(() => { GetContainingInputManager().ChangeFocus(code); }); + } + } +} diff --git a/osu.Game/Overlays/Login/LoginForm.cs b/osu.Game/Overlays/Login/LoginForm.cs index 291e92883b..80dfca93d2 100644 --- a/osu.Game/Overlays/Login/LoginForm.cs +++ b/osu.Game/Overlays/Login/LoginForm.cs @@ -21,11 +21,6 @@ using osu.Game.Localisation; namespace osu.Game.Overlays.Login { - public partial class AccountVerificationForm : FillFlowContainer - { - - } - public partial class LoginForm : FillFlowContainer { private TextBox username = null!; From 0e4244a692227d6f08a6ec2a928ee3dcec69d4ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Nov 2023 16:38:07 +0900 Subject: [PATCH 1547/4092] Add `APIAccess` flow for 2fa --- osu.Game/Online/API/APIAccess.cs | 40 +++++++++++++++++++++++++-- osu.Game/Online/API/DummyAPIAccess.cs | 7 ++++- osu.Game/Online/API/IAPIProvider.cs | 6 ++++ osu.Game/Overlays/Login/LoginPanel.cs | 2 +- 4 files changed, 50 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index a3ceb14a40..f9dd97a8f2 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -48,6 +48,8 @@ namespace osu.Game.Online.API public string ProvidedUsername { get; private set; } + public string SecondFactorCode { get; private set; } + private string password; public IBindable LocalUser => localUser; @@ -183,6 +185,7 @@ namespace osu.Game.Online.API /// /// /// This method takes control of and transitions from to either + /// - (pending 2fa) /// - (successful connection) /// - (failed connection but retrying) /// - (failed and can't retry, clear credentials and require user interaction) @@ -190,8 +193,6 @@ namespace osu.Game.Online.API /// Whether the connection attempt was successful. private void attemptConnect() { - state.Value = APIState.Connecting; - if (localUser.IsDefault) { // Show a placeholder user if saved credentials are available. @@ -208,11 +209,14 @@ namespace osu.Game.Online.API if (!authentication.HasValidAccessToken) { + state.Value = APIState.Connecting; LastLoginError = null; try { authentication.AuthenticateWithLogin(ProvidedUsername, password); + state.Value = APIState.RequiresSecondFactorAuth; + return; } catch (Exception e) { @@ -225,6 +229,28 @@ namespace osu.Game.Online.API } } + if (state.Value == APIState.RequiresSecondFactorAuth) + { + if (string.IsNullOrEmpty(SecondFactorCode)) + return; + + state.Value = APIState.Connecting; + LastLoginError = null; + + // TODO: use code to ensure second factor authentication completed. + Thread.Sleep(1000); + bool success = SecondFactorCode == "00000000"; + SecondFactorCode = null; + + if (!success) + { + state.Value = APIState.RequiresSecondFactorAuth; + LastLoginError = new InvalidOperationException("Second factor auth failed"); + SecondFactorCode = null; + return; + } + } + var userReq = new GetUserRequest(); userReq.Failure += ex => { @@ -307,6 +333,13 @@ namespace osu.Game.Online.API this.password = password; } + public void AuthenticateSecondFactor(string code) + { + Debug.Assert(State.Value == APIState.RequiresSecondFactorAuth); + + SecondFactorCode = code; + } + public IHubClientConnector GetHubConnector(string clientName, string endpoint, bool preferMessagePack) => new HubClientConnector(clientName, endpoint, this, versionHash, preferMessagePack); @@ -493,6 +526,7 @@ namespace osu.Game.Online.API public void Logout() { password = null; + SecondFactorCode = null; authentication.Clear(); // Scheduled prior to state change such that the state changed event is invoked with the correct user and their friends present @@ -543,7 +577,7 @@ namespace osu.Game.Online.API /// /// Waiting on second factor authentication. /// - RequiresAuthentication, + RequiresSecondFactorAuth, /// /// We are in the process of (re-)connecting. diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 4cf2152193..bd833d39dd 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -118,13 +118,18 @@ namespace osu.Game.Online.API if (requiresTwoFactor) { - state.Value = APIState.RequiresAuthentication; + state.Value = APIState.RequiresSecondFactorAuth; requiresTwoFactor = false; } else state.Value = APIState.Online; } + public void AuthenticateSecondFactor(string code) + { + state.Value = APIState.Online; + } + public void Logout() { state.Value = APIState.Offline; diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index a1d7006c8c..5f99ad2f4d 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -106,6 +106,12 @@ namespace osu.Game.Online.API /// The user's password. void Login(string username, string password); + /// + /// Provide a second-factor authentication code for authentication. + /// + /// The 2FA code. + void AuthenticateSecondFactor(string code); + /// /// Log out the current user. /// diff --git a/osu.Game/Overlays/Login/LoginPanel.cs b/osu.Game/Overlays/Login/LoginPanel.cs index 59b9c6ab9f..f896d03231 100644 --- a/osu.Game/Overlays/Login/LoginPanel.cs +++ b/osu.Game/Overlays/Login/LoginPanel.cs @@ -81,7 +81,7 @@ namespace osu.Game.Overlays.Login }; break; - case APIState.RequiresAuthentication: + case APIState.RequiresSecondFactorAuth: Child = form = new AccountVerificationForm(); break; From 285f740e2a2aa3754445141fa2e974fa4b1d9cd4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Nov 2023 16:53:37 +0900 Subject: [PATCH 1548/4092] Update various components to handle new state --- osu.Game/Overlays/Login/LoginPanel.cs | 2 +- ...icationForm.cs => SecondFactorAuthForm.cs} | 32 +++++++++++++++---- .../Overlays/Toolbar/ToolbarUserButton.cs | 1 + 3 files changed, 28 insertions(+), 7 deletions(-) rename osu.Game/Overlays/Login/{AccountVerificationForm.cs => SecondFactorAuthForm.cs} (78%) diff --git a/osu.Game/Overlays/Login/LoginPanel.cs b/osu.Game/Overlays/Login/LoginPanel.cs index f896d03231..ce0b0a5a48 100644 --- a/osu.Game/Overlays/Login/LoginPanel.cs +++ b/osu.Game/Overlays/Login/LoginPanel.cs @@ -82,7 +82,7 @@ namespace osu.Game.Overlays.Login break; case APIState.RequiresSecondFactorAuth: - Child = form = new AccountVerificationForm(); + Child = form = new SecondFactorAuthForm(); break; case APIState.Failing: diff --git a/osu.Game/Overlays/Login/AccountVerificationForm.cs b/osu.Game/Overlays/Login/SecondFactorAuthForm.cs similarity index 78% rename from osu.Game/Overlays/Login/AccountVerificationForm.cs rename to osu.Game/Overlays/Login/SecondFactorAuthForm.cs index a27cc01ed2..cfec46c599 100644 --- a/osu.Game/Overlays/Login/AccountVerificationForm.cs +++ b/osu.Game/Overlays/Login/SecondFactorAuthForm.cs @@ -8,15 +8,20 @@ using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; using osu.Game.Overlays.Settings; using osuTK; namespace osu.Game.Overlays.Login { - public partial class AccountVerificationForm : FillFlowContainer + public partial class SecondFactorAuthForm : FillFlowContainer { - private OsuTextBox code = null!; + private OsuTextBox codeTextBox = null!; private LinkFlowContainer explainText = null!; + private ErrorTextFlowContainer errorText = null!; + + [Resolved] + private IAPIProvider api { get; set; } = null!; [BackgroundDependencyLoader] private void load() @@ -43,18 +48,18 @@ namespace osu.Game.Overlays.Login AutoSizeAxes = Axes.Y, Text = "An email has been sent to you with a verification code. Enter the code.", }, - code = new OsuTextBox + codeTextBox = new OsuTextBox { PlaceholderText = "Enter code", RelativeSizeAxes = Axes.X, - TabbableContentContainer = this + TabbableContentContainer = this, }, explainText = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(weight: FontWeight.Regular)) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, }, - new ErrorTextFlowContainer + errorText = new ErrorTextFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -78,6 +83,21 @@ namespace osu.Game.Overlays.Login explainText.AddText(" or "); explainText.AddLink("sign out", () => { }); explainText.AddText("."); + + codeTextBox.Current.BindValueChanged(code => + { + if (code.NewValue.Length == 8) + { + api.AuthenticateSecondFactor(code.NewValue); + codeTextBox.Current.Disabled = true; + } + }); + + if (api.LastLoginError?.Message is string error) + { + errorText.Alpha = 1; + errorText.AddErrors(new[] { error }); + } } public override bool AcceptsFocus => true; @@ -86,7 +106,7 @@ namespace osu.Game.Overlays.Login protected override void OnFocus(FocusEvent e) { - Schedule(() => { GetContainingInputManager().ChangeFocus(code); }); + Schedule(() => { GetContainingInputManager().ChangeFocus(codeTextBox); }); } } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs index 028decea1e..f0b5aed3cf 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs @@ -101,6 +101,7 @@ namespace osu.Game.Overlays.Toolbar switch (state.NewValue) { + case APIState.RequiresSecondFactorAuth: case APIState.Connecting: TooltipText = ToolbarStrings.Connecting; spinner.Show(); From f7fa9c90d66c9ba05f4bc50c1bc78274caa2c8cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Nov 2023 18:16:02 +0900 Subject: [PATCH 1549/4092] Add test coverage of 2FA flow --- .../Visual/Menus/TestSceneLoginOverlay.cs | 24 +++++++++---------- osu.Game/Online/API/DummyAPIAccess.cs | 13 ++++++---- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs b/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs index 30c0f6644e..0c5f5a321c 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs @@ -10,6 +10,7 @@ using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Overlays; +using osu.Game.Overlays.Login; using osu.Game.Users.Drawables; using osuTK.Input; @@ -36,28 +37,25 @@ namespace osu.Game.Tests.Visual.Menus AddStep("show login overlay", () => loginOverlay.Show()); } - [Test] - public void TestLoginTwoFactorSuccess() - { - AddStep("logout", () => - { - API.Logout(); - ((DummyAPIAccess)API).RequireTwoFactor(); - }); - - AddStep("enter password", () => loginOverlay.ChildrenOfType().First().Text = "password"); - AddStep("submit", () => loginOverlay.ChildrenOfType().First(b => b.Text.ToString() == "Sign in").TriggerClick()); - } - [Test] public void TestLoginSuccess() { AddStep("logout", () => API.Logout()); + assertAPIState(APIState.Offline); AddStep("enter password", () => loginOverlay.ChildrenOfType().First().Text = "password"); AddStep("submit", () => loginOverlay.ChildrenOfType().First(b => b.Text.ToString() == "Sign in").TriggerClick()); + + assertAPIState(APIState.RequiresSecondFactorAuth); + AddUntilStep("wait for second factor auth form", () => loginOverlay.ChildrenOfType().SingleOrDefault(), () => Is.Not.Null); + + AddStep("enter code", () => loginOverlay.ChildrenOfType().First().Text = "88800088"); + assertAPIState(APIState.Online); } + private void assertAPIState(APIState expected) => + AddUntilStep($"login state is {expected}", () => API.State.Value, () => Is.EqualTo(expected)); + [Test] public void TestLoginFailure() { diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index bd833d39dd..bd462ac23e 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -55,7 +55,7 @@ namespace osu.Game.Online.API private bool shouldFailNextLogin; private bool stayConnectingNextLogin; - private bool requiresTwoFactor; + private bool requiredSecondFactorAuth = true; /// /// The current connectivity state of the API. @@ -116,13 +116,15 @@ namespace osu.Game.Online.API Id = DUMMY_USER_ID, }; - if (requiresTwoFactor) + if (requiredSecondFactorAuth) { state.Value = APIState.RequiresSecondFactorAuth; - requiresTwoFactor = false; } else + { state.Value = APIState.Online; + requiredSecondFactorAuth = true; + } } public void AuthenticateSecondFactor(string code) @@ -154,7 +156,10 @@ namespace osu.Game.Online.API IBindableList IAPIProvider.Friends => Friends; IBindable IAPIProvider.Activity => Activity; - public void RequireTwoFactor() => requiresTwoFactor = true; + /// + /// Skip 2FA requirement for next login. + /// + public void SkipSecondFactor() => requiredSecondFactorAuth = false; /// /// During the next simulated login, the process will fail immediately. From 167f5b4ef401317b3721187147ec7a591288e092 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Nov 2023 18:21:06 +0900 Subject: [PATCH 1550/4092] Tidy up localisations and connect missing links --- osu.Game/Overlays/Login/SecondFactorAuthForm.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Login/SecondFactorAuthForm.cs b/osu.Game/Overlays/Login/SecondFactorAuthForm.cs index cfec46c599..60c04eedee 100644 --- a/osu.Game/Overlays/Login/SecondFactorAuthForm.cs +++ b/osu.Game/Overlays/Login/SecondFactorAuthForm.cs @@ -10,6 +10,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Overlays.Settings; +using osu.Game.Resources.Localisation.Web; using osuTK; namespace osu.Game.Overlays.Login @@ -75,13 +76,17 @@ namespace osu.Game.Overlays.Login }, }; - explainText.AddParagraph("Make sure to check your spam folder if you can't find the email."); + explainText.AddParagraph(UserVerificationStrings.BoxInfoCheckSpam); + // We can't support localisable strings with nested links yet. Not sure if we even can (probably need to allow markdown link formatting or something). explainText.AddParagraph("If you can't access your email or have forgotten what you used, please follow the "); - explainText.AddLink("email recovery process here", () => { }); + explainText.AddLink(UserVerificationStrings.BoxInfoRecoverLink, $"{api.WebsiteRootUrl}/home/password-reset"); explainText.AddText(". You can also "); - explainText.AddLink("request another code", () => { }); + explainText.AddLink(UserVerificationStrings.BoxInfoReissueLink, () => + { + // TODO: request another code. + }); explainText.AddText(" or "); - explainText.AddLink("sign out", () => { }); + explainText.AddLink(UserVerificationStrings.BoxInfoLogoutLink, () => { api.Logout(); }); explainText.AddText("."); codeTextBox.Current.BindValueChanged(code => From b88e3cd26f8eb3f7b90e8bc6a96097ae23d3773e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Nov 2023 20:16:23 +0900 Subject: [PATCH 1551/4092] Change `ResourceStore` provided to `Skin` to be a fallback, not replacement --- .../CatchSkinColourDecodingTest.cs | 4 ++-- .../Formats/LegacyBeatmapEncoderTest.cs | 4 ++-- .../Skins/SkinDeserialisationTest.cs | 4 ++-- .../Skins/TestSceneSkinResources.cs | 4 ++-- osu.Game/Skinning/DefaultLegacySkin.cs | 3 +-- osu.Game/Skinning/LegacyBeatmapSkin.cs | 2 +- osu.Game/Skinning/LegacySkin.cs | 7 +++---- osu.Game/Skinning/Skin.cs | 21 +++++++++++-------- osu.Game/Tests/Visual/SkinnableTestScene.cs | 4 ++-- 9 files changed, 27 insertions(+), 26 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/CatchSkinColourDecodingTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchSkinColourDecodingTest.cs index 72011042bc..74b02bab9b 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchSkinColourDecodingTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchSkinColourDecodingTest.cs @@ -28,9 +28,9 @@ namespace osu.Game.Rulesets.Catch.Tests private class TestLegacySkin : LegacySkin { - public TestLegacySkin(SkinInfo skin, IResourceStore storage) + public TestLegacySkin(SkinInfo skin, IResourceStore fallbackStore) // Bypass LegacySkinResourceStore to avoid returning null for retrieving files due to bad skin info (SkinInfo.Files = null). - : base(skin, null, storage) + : base(skin, null, fallbackStore) { } } diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index 5d9049ead7..9ff0fe874f 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -174,8 +174,8 @@ namespace osu.Game.Tests.Beatmaps.Formats private class TestLegacySkin : LegacySkin { - public TestLegacySkin(IResourceStore storage, string fileName) - : base(new SkinInfo { Name = "Test Skin", Creator = "Craftplacer" }, null, storage, fileName) + public TestLegacySkin(IResourceStore fallbackStore, string fileName) + : base(new SkinInfo { Name = "Test Skin", Creator = "Craftplacer" }, null, fallbackStore, fileName) { } } diff --git a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs index c45eadeff2..6423e061c5 100644 --- a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs +++ b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs @@ -149,8 +149,8 @@ namespace osu.Game.Tests.Skins private class TestSkin : Skin { - public TestSkin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore? storage = null, string configurationFilename = "skin.ini") - : base(skin, resources, storage, configurationFilename) + public TestSkin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore? fallbackStore = null, string configurationFilename = "skin.ini") + : base(skin, resources, fallbackStore, configurationFilename) { } diff --git a/osu.Game.Tests/Skins/TestSceneSkinResources.cs b/osu.Game.Tests/Skins/TestSceneSkinResources.cs index aaec319b57..e77affd817 100644 --- a/osu.Game.Tests/Skins/TestSceneSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneSkinResources.cs @@ -95,8 +95,8 @@ namespace osu.Game.Tests.Skins { public const string SAMPLE_NAME = "test-sample"; - public TestSkin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore? storage = null, string configurationFilename = "skin.ini") - : base(skin, resources, storage, configurationFilename) + public TestSkin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore? fallbackStore = null, string configurationFilename = "skin.ini") + : base(skin, resources, fallbackStore, configurationFilename) { } diff --git a/osu.Game/Skinning/DefaultLegacySkin.cs b/osu.Game/Skinning/DefaultLegacySkin.cs index fd9653e3e5..34ea0af122 100644 --- a/osu.Game/Skinning/DefaultLegacySkin.cs +++ b/osu.Game/Skinning/DefaultLegacySkin.cs @@ -31,8 +31,7 @@ namespace osu.Game.Skinning : base( skin, resources, - // In the case of the actual default legacy skin (ie. the fallback one, which a user hasn't applied any modifications to) we want to use the game provided resources. - skin.Protected ? new NamespacedResourceStore(resources.Resources, "Skins/Legacy") : null + new NamespacedResourceStore(resources.Resources, "Skins/Legacy") ) { Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255); diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index 90eb5fa013..d6ba6ea332 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -73,7 +73,7 @@ namespace osu.Game.Skinning // needs to be removed else it will cause incorrect skin behaviours. This is due to the config lookup having no context of which skin // it should be returning the version for. - Skin.LogLookupDebug(this, lookup, Skin.LookupDebugType.Miss); + LogLookupDebug(this, lookup, LookupDebugType.Miss); return null; } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index dc683f1dae..2e91770919 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -16,7 +16,6 @@ using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Game.Audio; using osu.Game.Beatmaps.Formats; -using osu.Game.Database; using osu.Game.Extensions; using osu.Game.IO; using osu.Game.Rulesets.Objects.Types; @@ -51,10 +50,10 @@ namespace osu.Game.Skinning /// /// The model for this skin. /// Access to raw game resources. - /// An optional store which will be used for looking up skin resources. If null, one will be created from realm pattern. + /// An optional fallback store which will be used for file lookups that are not serviced by realm user storage. /// The user-facing filename of the configuration file to be parsed. Can accept an .osu or skin.ini file. - protected LegacySkin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore? storage, string configurationFilename = @"skin.ini") - : base(skin, resources, storage, configurationFilename) + protected LegacySkin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore? fallbackStore, string configurationFilename = @"skin.ini") + : base(skin, resources, fallbackStore, configurationFilename) { } diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 1e312142d7..9ee69d033d 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -55,7 +55,7 @@ namespace osu.Game.Skinning where TLookup : notnull where TValue : notnull; - private readonly RealmBackedResourceStore? realmBackedStorage; + private readonly ResourceStore store = new ResourceStore(); public string Name { get; } @@ -64,9 +64,9 @@ namespace osu.Game.Skinning /// /// The skin's metadata. Usually a live realm object. /// Access to game-wide resources. - /// An optional store which will *replace* all file lookups that are usually sourced from . + /// An optional fallback store which will be used for file lookups that are not serviced by realm user storage. /// An optional filename to read the skin configuration from. If not provided, the configuration will be retrieved from the storage using "skin.ini". - protected Skin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore? storage = null, string configurationFilename = @"skin.ini") + protected Skin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore? fallbackStore = null, string configurationFilename = @"skin.ini") { Name = skin.Name; @@ -74,9 +74,9 @@ namespace osu.Game.Skinning { SkinInfo = skin.ToLive(resources.RealmAccess); - storage ??= realmBackedStorage = new RealmBackedResourceStore(SkinInfo, resources.Files, resources.RealmAccess); + store.AddStore(new RealmBackedResourceStore(SkinInfo, resources.Files, resources.RealmAccess)); - var samples = resources.AudioManager?.GetSampleStore(storage); + var samples = resources.AudioManager?.GetSampleStore(store); if (samples != null) { @@ -88,7 +88,7 @@ namespace osu.Game.Skinning } Samples = samples; - Textures = new TextureStore(resources.Renderer, CreateTextureLoaderStore(resources, storage)); + Textures = new TextureStore(resources.Renderer, CreateTextureLoaderStore(resources, store)); } else { @@ -96,7 +96,10 @@ namespace osu.Game.Skinning SkinInfo = skin.ToLiveUnmanaged(); } - var configurationStream = storage?.GetStream(configurationFilename); + if (fallbackStore != null) + store.AddStore(fallbackStore); + + var configurationStream = store.GetStream(configurationFilename); if (configurationStream != null) { @@ -119,7 +122,7 @@ namespace osu.Game.Skinning { string filename = $"{skinnableTarget}.json"; - byte[]? bytes = storage?.Get(filename); + byte[]? bytes = store?.Get(filename); if (bytes == null) continue; @@ -252,7 +255,7 @@ namespace osu.Game.Skinning Textures?.Dispose(); Samples?.Dispose(); - realmBackedStorage?.Dispose(); + store.Dispose(); } #endregion diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index aab1b72990..f371cf721f 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -201,8 +201,8 @@ namespace osu.Game.Tests.Visual { private readonly bool extrapolateAnimations; - public TestLegacySkin(SkinInfo skin, IResourceStore storage, IStorageResourceProvider resources, bool extrapolateAnimations) - : base(skin, resources, storage) + public TestLegacySkin(SkinInfo skin, IResourceStore fallbackStore, IStorageResourceProvider resources, bool extrapolateAnimations) + : base(skin, resources, fallbackStore) { this.extrapolateAnimations = extrapolateAnimations; } From 7472dc9bb5e1e410023ab98d778c5380d1d2f958 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Nov 2023 20:39:23 +0900 Subject: [PATCH 1552/4092] Update `APIState` checks --- osu.Game/Online/OnlineViewContainer.cs | 5 +++++ osu.Game/Overlays/AccountCreationOverlay.cs | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/osu.Game/Online/OnlineViewContainer.cs b/osu.Game/Online/OnlineViewContainer.cs index 46f64fbb61..824da152b2 100644 --- a/osu.Game/Online/OnlineViewContainer.cs +++ b/osu.Game/Online/OnlineViewContainer.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -79,10 +80,14 @@ namespace osu.Game.Online case APIState.Failing: case APIState.Connecting: + case APIState.RequiresSecondFactorAuth: PopContentOut(Content); LoadingSpinner.Show(); placeholder.FadeOut(transform_duration / 2, Easing.OutQuint); break; + + default: + throw new ArgumentOutOfRangeException(); } }); diff --git a/osu.Game/Overlays/AccountCreationOverlay.cs b/osu.Game/Overlays/AccountCreationOverlay.cs index ef2e055eae..576ee92b48 100644 --- a/osu.Game/Overlays/AccountCreationOverlay.cs +++ b/osu.Game/Overlays/AccountCreationOverlay.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -118,12 +119,16 @@ namespace osu.Game.Overlays break; case APIState.Connecting: + case APIState.RequiresSecondFactorAuth: break; case APIState.Online: scheduledHide?.Cancel(); scheduledHide = Schedule(Hide); break; + + default: + throw new ArgumentOutOfRangeException(); } } } From a1673160f12e08612aa42a9db89f8b9f2e6324f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Nov 2023 16:44:11 +0900 Subject: [PATCH 1553/4092] Refactor `OsuAutoGenerator` to allow custom SPM specifications --- osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 5a3d882ef0..acce8c03e8 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -339,6 +339,10 @@ namespace osu.Game.Rulesets.Osu.Replays AddFrameToReplay(startFrame); + // ~477 as per stable. + const float spin_rpm = 60000f / 20 * (180 / MathF.PI) / 360; + float radsPerMillisecond = MathUtils.DegreesToRadians(spin_rpm * 360) / 60000; + switch (h) { // We add intermediate frames for spinning / following a slider here. @@ -354,7 +358,7 @@ namespace osu.Game.Rulesets.Osu.Replays for (double nextFrame = h.StartTime + GetFrameDelay(h.StartTime); nextFrame < spinner.EndTime; nextFrame += GetFrameDelay(nextFrame)) { t = ApplyModsToTimeDelta(previousFrame, nextFrame) * spinnerDirection; - angle += (float)t / 20; + angle += (float)t * radsPerMillisecond; Vector2 pos = SPINNER_CENTRE + CirclePosition(angle, SPIN_RADIUS); AddFrameToReplay(new OsuReplayFrame((int)nextFrame, new Vector2(pos.X, pos.Y), action)); @@ -363,7 +367,7 @@ namespace osu.Game.Rulesets.Osu.Replays } t = ApplyModsToTimeDelta(previousFrame, spinner.EndTime) * spinnerDirection; - angle += (float)t / 20; + angle += (float)t * radsPerMillisecond; Vector2 endPosition = SPINNER_CENTRE + CirclePosition(angle, SPIN_RADIUS); From d1cea10f2197b243aff39ba16ee86b89f325fe44 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Nov 2023 16:52:40 +0900 Subject: [PATCH 1554/4092] Add note about localisation --- osu.Game/Screens/Select/Details/AdvancedStats.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 25c18f7328..85eb81eb9e 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -210,7 +210,8 @@ namespace osu.Game.Screens.Select.Details { if (haveRateChangedValues) { - return $"One or more values are being adjusted by mods that change speed." + + // Rather than localising this, it should be displayed in a better way (a custom tooltip which isn't a single super-long line). + return "One or more values are being adjusted by mods that change speed." + $" (AR {originalDifficulty?.ApproachRate ?? 0}→{(adjustedDifficulty?.ApproachRate ?? 0):0.0#}, " + $"OD {originalDifficulty?.OverallDifficulty ?? 0}→{(adjustedDifficulty?.OverallDifficulty ?? 0):0.0#})"; } From 9172632b0b7177a643c2c1c26ab69533998298d8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Nov 2023 17:04:02 +0900 Subject: [PATCH 1555/4092] Rename method and adjust xmldoc to be very explicit about how wrong this is --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 4 ++-- osu.Game.Rulesets.Osu/OsuRuleset.cs | 4 ++-- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 4 ++-- .../Overlays/Mods/BeatmapAttributesDisplay.cs | 2 +- osu.Game/Rulesets/Ruleset.cs | 19 ++++++++++--------- .../Screens/Select/Details/AdvancedStats.cs | 2 +- 6 files changed, 18 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 7327fb4513..94c8c36c09 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -234,9 +234,9 @@ namespace osu.Game.Rulesets.Catch }; } - public override BeatmapDifficulty GetRateAdjustedDifficulty(IBeatmapDifficultyInfo baseDifficulty, double rate) + public override BeatmapDifficulty GetRateAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, double rate) { - BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(baseDifficulty); + BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty); double preempt = adjustedDifficulty.ApproachRate < 6 ? (1200.0 + 600.0 * (5 - adjustedDifficulty.ApproachRate) / 5) : (1200.0 - 750.0 * (adjustedDifficulty.ApproachRate - 5) / 5); diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index f4cf9409e8..a99f0df066 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -330,9 +330,9 @@ namespace osu.Game.Rulesets.Osu public override RulesetSetupSection CreateEditorSetupSection() => new OsuSetupSection(); - public override BeatmapDifficulty GetRateAdjustedDifficulty(IBeatmapDifficultyInfo baseDifficulty, double rate) + public override BeatmapDifficulty GetRateAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, double rate) { - BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(baseDifficulty); + BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty); double preempt = adjustedDifficulty.ApproachRate < 5 ? (1200.0 + 600.0 * (5 - adjustedDifficulty.ApproachRate) / 5) : (1200.0 - 750.0 * (adjustedDifficulty.ApproachRate - 5) / 5); preempt /= rate; diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 88d0087622..6e5cdbf2d1 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -265,9 +265,9 @@ namespace osu.Game.Rulesets.Taiko }; } - public override BeatmapDifficulty GetRateAdjustedDifficulty(IBeatmapDifficultyInfo baseDifficulty, double rate) + public override BeatmapDifficulty GetRateAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, double rate) { - BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(baseDifficulty); + BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty); double hitwindow = 35.0 - 15.0 * (adjustedDifficulty.OverallDifficulty - 5) / 5; hitwindow /= rate; diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index bf39666f83..6270b2bb49 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -179,7 +179,7 @@ namespace osu.Game.Overlays.Mods mod.ApplyToDifficulty(originalDifficulty); Ruleset ruleset = gameRuleset.Value.CreateInstance(); - adjustedDifficulty = ruleset.GetRateAdjustedDifficulty(originalDifficulty, rate); + adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(originalDifficulty, rate); haveRateChangedValues = hasRateAdjustedProperties(originalDifficulty, adjustedDifficulty); diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index edfe691b86..c7c81ecb55 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -377,6 +377,16 @@ namespace osu.Game.Rulesets /// The display name. public virtual LocalisableString GetDisplayNameForHitResult(HitResult result) => result.GetLocalisableDescription(); + /// + /// Applies changes to difficulty attributes for presenting to a user a rough estimate of how rate adjust mods affect difficulty. + /// Importantly, this should NOT BE USED FOR ANY CALCULATIONS. + /// It is also not always correct, and arguably is never correct depending on your frame of mind. + /// + /// >The that will be adjusted. + /// The rate adjustment multiplier from mods. For example 1.5 for DT. + /// The adjusted difficulty attributes. + public virtual BeatmapDifficulty GetRateAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, double rate) => new BeatmapDifficulty(difficulty); + /// /// Creates ruleset-specific beatmap filter criteria to be used on the song select screen. /// @@ -391,14 +401,5 @@ namespace osu.Game.Rulesets /// Can be overridden to alter the difficulty section to the editor beatmap setup screen. /// public virtual DifficultySection? CreateEditorDifficultySection() => null; - - /// - /// Changes after they're adjusted according to rate. - /// Doesn't change any attributes by default. - /// - /// >The that will be adjusted. - /// Rate of the gameplay. For example 1.5 for DT. - /// Copy of difficulty info with values changed according to rate and ruleset-specific behaviour. - public virtual BeatmapDifficulty GetRateAdjustedDifficulty(IBeatmapDifficultyInfo baseDifficulty, double rate) => new BeatmapDifficulty(baseDifficulty); } } diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 85eb81eb9e..f617cb1d8e 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -137,7 +137,7 @@ namespace osu.Game.Screens.Select.Details foreach (var mod in mods.Value.OfType()) rate = mod.ApplyToRate(0, rate); - adjustedDifficulty = ruleset.GetRateAdjustedDifficulty(originalDifficulty, rate); + adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(originalDifficulty, rate); haveRateChangedValues = hasRateAdjustedProperties(originalDifficulty, adjustedDifficulty); } } From bd932a5417dd7bc4fba8b237832d556c27ea0d9b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Nov 2023 17:07:21 +0900 Subject: [PATCH 1556/4092] Update localisation analyser Pulls in https://github.com/ppy/osu-localisation-analyser/pull/60. --- .config/dotnet-tools.json | 4 ++-- osu.Game/osu.Game.csproj | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 3cecb0d07c..b8dc201559 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -21,10 +21,10 @@ ] }, "ppy.localisationanalyser.tools": { - "version": "2023.712.0", + "version": "2023.1117.0", "commands": [ "localisation" ] } } -} \ No newline at end of file +} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 9985afbd8b..10ca49c768 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -31,7 +31,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From c9c8ed7c77332502de4f946affbd8dc107b99dba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Nov 2023 18:41:09 +0900 Subject: [PATCH 1557/4092] Remove unused values --- osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs index 7d6a05026c..d1c9227c13 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs @@ -197,19 +197,10 @@ namespace osu.Game.Rulesets.Osu.Scoring increase = 0.011; break; - case HitResult.Good: - increase = 0.024; - break; - case HitResult.Great: increase = 0.03; break; - case HitResult.Perfect: - // 1.1 * Great. Unused. - increase = 0.033; - break; - case HitResult.SmallBonus: increase = 0.0085; break; From a556caae437b506b4d26258e0ab69008ccae2ec8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Nov 2023 18:41:54 +0900 Subject: [PATCH 1558/4092] Move default value out of switch statement --- osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs index d1c9227c13..8265ca1c33 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs @@ -166,7 +166,7 @@ namespace osu.Game.Rulesets.Osu.Scoring private double healthIncreaseFor(HitObject hitObject, HitResult result) { - double increase; + double increase = 0; switch (result) { @@ -208,10 +208,6 @@ namespace osu.Game.Rulesets.Osu.Scoring case HitResult.LargeBonus: increase = 0.01; break; - - default: - increase = 0; - break; } return hpMultiplierNormal * increase; From 2ab84fdaa3f6a73715797d7c9d40d5835153eb68 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Nov 2023 18:45:16 +0900 Subject: [PATCH 1559/4092] Use switch statement for type matching --- .../Scoring/OsuHealthProcessor.cs | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs index 8265ca1c33..0e0bc1916c 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs @@ -99,15 +99,21 @@ namespace osu.Game.Rulesets.Osu.Scoring double hpOverkill = Math.Max(0, hpReduction - currentHp); reduceHp(hpReduction); - if (h is Slider slider) + switch (h) { - foreach (var nested in slider.NestedHitObjects) - increaseHp(nested); - } - else if (h is Spinner spinner) - { - foreach (var nested in spinner.NestedHitObjects.Where(t => t is not SpinnerBonusTick)) - increaseHp(nested); + case Slider slider: + { + foreach (var nested in slider.NestedHitObjects) + increaseHp(nested); + break; + } + + case Spinner spinner: + { + foreach (var nested in spinner.NestedHitObjects.Where(t => t is not SpinnerBonusTick)) + increaseHp(nested); + break; + } } // Note: Because HP is capped during the above increases, long sliders (with many ticks) or spinners From fd3508254b835c2a9dcc07bdfe72c16cf8cec2f7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Nov 2023 18:49:19 +0900 Subject: [PATCH 1560/4092] Add note about break calculation method --- osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs index 0e0bc1916c..5a24f29330 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs @@ -68,6 +68,7 @@ namespace osu.Game.Rulesets.Osu.Scoring // TODO: This doesn't handle overlapping/sequential breaks correctly (/b/614). // Subtract any break time from the duration since the last object + // Note that this method is a bit convoluted, but matches stable code for compatibility. if (Beatmap.Breaks.Count > 0 && currentBreak < Beatmap.Breaks.Count) { BreakPeriod e = Beatmap.Breaks[currentBreak]; From 66f7b9fae1e0aaf86609bdbf3315870198e0f8b2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Nov 2023 19:09:24 +0900 Subject: [PATCH 1561/4092] Adjust slider follow circle animation to not abruptly scale on early ticks --- .../Skinning/Argon/ArgonFollowCircle.cs | 9 ++++++--- .../Skinning/Default/DefaultFollowCircle.cs | 9 ++++++--- .../Skinning/Legacy/LegacyFollowCircle.cs | 7 +++++-- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowCircle.cs index fca3e70236..ea21d71d5f 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowCircle.cs @@ -88,9 +88,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon protected override void OnSliderTick() { - this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 1.08f, 40, Easing.OutQuint) - .Then() - .ScaleTo(DrawableSliderBall.FOLLOW_AREA, 200f, Easing.OutQuint); + if (Scale.X >= DrawableSliderBall.FOLLOW_AREA * 0.98f) + { + this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 1.08f, 40, Easing.OutQuint) + .Then() + .ScaleTo(DrawableSliderBall.FOLLOW_AREA, 200f, Easing.OutQuint); + } } protected override void OnSliderBreak() diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs index 3c41d473f4..4adbfc3928 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs @@ -59,9 +59,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default protected override void OnSliderTick() { - this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 1.08f, 40, Easing.OutQuint) - .Then() - .ScaleTo(DrawableSliderBall.FOLLOW_AREA, 200f, Easing.OutQuint); + if (Scale.X >= DrawableSliderBall.FOLLOW_AREA * 0.98f) + { + this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 1.08f, 40, Easing.OutQuint) + .Then() + .ScaleTo(DrawableSliderBall.FOLLOW_AREA, 200f, Easing.OutQuint); + } } protected override void OnSliderBreak() diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs index f8dcb9e8a2..fa2bb9b2ad 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs @@ -44,8 +44,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy protected override void OnSliderTick() { - this.ScaleTo(2.2f) - .ScaleTo(2f, 200); + if (Scale.X >= 2f) + { + this.ScaleTo(2.2f) + .ScaleTo(2f, 200); + } } protected override void OnSliderBreak() From 307ec172cbcc49f0edf26aed318d5390666faafa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Nov 2023 23:48:48 +0900 Subject: [PATCH 1562/4092] Use simplified formula --- osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index acce8c03e8..9d63949dcc 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -340,7 +340,7 @@ namespace osu.Game.Rulesets.Osu.Replays AddFrameToReplay(startFrame); // ~477 as per stable. - const float spin_rpm = 60000f / 20 * (180 / MathF.PI) / 360; + const float spin_rpm = 0.05f / (2 * MathF.PI) * 60000; float radsPerMillisecond = MathUtils.DegreesToRadians(spin_rpm * 360) / 60000; switch (h) From d9cd546377e7e7b3072fe905b966cebe7eeff746 Mon Sep 17 00:00:00 2001 From: Poyo Date: Sat, 18 Nov 2023 12:09:37 -0800 Subject: [PATCH 1563/4092] Use rate fallback in DrawableHitObject --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 +- osu.Game/Rulesets/UI/Playfield.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index baf13d8911..5abca168ed 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -704,7 +704,7 @@ namespace osu.Game.Rulesets.Objects.Drawables } Result.RawTime = Time.Current; - Result.GameplayRate = (Clock as IGameplayClock)?.GetTrueGameplayRate(); + Result.GameplayRate = (Clock as IGameplayClock)?.GetTrueGameplayRate() ?? 1.0; if (Result.HasResult) updateState(Result.IsHit ? ArmedState.Hit : ArmedState.Miss); diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index eb29d8f30a..e9c35555c8 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -473,7 +473,7 @@ namespace osu.Game.Rulesets.UI private void onNewResult(DrawableHitObject drawable, JudgementResult result) { - Debug.Assert(result != null && drawable.Entry?.Result == result && result.RawTime != null && result.GameplayRate != null); + Debug.Assert(result != null && drawable.Entry?.Result == result && result.RawTime != null); judgedEntries.Push(drawable.Entry.AsNonNull()); NewResult?.Invoke(drawable, result); From 2cf16e29acf5e5f397ea6ab98bbd8a8a6c5528f5 Mon Sep 17 00:00:00 2001 From: Zyf Date: Sun, 19 Nov 2023 21:02:56 +0100 Subject: [PATCH 1564/4092] Revert "Scoring : Adds fields to Catch/Mania/Taiko Simulators too" This reverts commit a97915180f37a87ac5716acdb0bfcafffe9e6452. --- .../Difficulty/CatchLegacyScoreSimulator.cs | 10 +++------- .../Difficulty/ManiaLegacyScoreSimulator.cs | 3 --- .../Difficulty/TaikoLegacyScoreSimulator.cs | 10 +++------- 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyScoreSimulator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyScoreSimulator.cs index 284fd23aea..c79fd36d96 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyScoreSimulator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyScoreSimulator.cs @@ -20,12 +20,9 @@ namespace osu.Game.Rulesets.Catch.Difficulty public int ComboScore { get; private set; } - public int LegacyBonusScore { get; private set; } - - public int MaxCombo { get; private set; } - - public double BonusScoreRatio => LegacyBonusScore == 0 ? 0 : (double)modernBonusScore / LegacyBonusScore; + public double BonusScoreRatio => legacyBonusScore == 0 ? 0 : (double)modernBonusScore / legacyBonusScore; + private int legacyBonusScore; private int modernBonusScore; private int combo; @@ -77,7 +74,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty foreach (var obj in playableBeatmap.HitObjects) simulateHit(obj); - MaxCombo = combo; } private void simulateHit(HitObject hitObject) @@ -133,7 +129,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty if (isBonus) { - LegacyBonusScore += scoreIncrease; + legacyBonusScore += scoreIncrease; modernBonusScore += Judgement.ToNumericResult(bonusResult); } else diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaLegacyScoreSimulator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaLegacyScoreSimulator.cs index f09c911b98..e544428979 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaLegacyScoreSimulator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaLegacyScoreSimulator.cs @@ -14,8 +14,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty { public int AccuracyScore => 0; public int ComboScore { get; private set; } - public int LegacyBonusScore => 0; - public int MaxCombo { get; private set; } public double BonusScoreRatio => 0; public void Simulate(IWorkingBeatmap workingBeatmap, IBeatmap playableBeatmap, IReadOnlyList mods) @@ -25,7 +23,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty .Aggregate(1.0, (c, n) => c * n); ComboScore = (int)(1000000 * multiplier); - MaxCombo = playableBeatmap.GetMaxCombo(); } } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs index 239ec5765c..e77327d622 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs @@ -20,12 +20,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty public int ComboScore { get; private set; } - public int LegacyBonusScore { get; private set; } - - public int MaxCombo { get; private set; } - - public double BonusScoreRatio => LegacyBonusScore == 0 ? 0 : (double)modernBonusScore / LegacyBonusScore; + public double BonusScoreRatio => legacyBonusScore == 0 ? 0 : (double)modernBonusScore / legacyBonusScore; + private int legacyBonusScore; private int modernBonusScore; private int combo; @@ -83,7 +80,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty foreach (var obj in playableBeatmap.HitObjects) simulateHit(obj); - MaxCombo = combo; } private void simulateHit(HitObject hitObject) @@ -193,7 +189,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (isBonus) { - LegacyBonusScore += scoreIncrease; + legacyBonusScore += scoreIncrease; modernBonusScore += Judgement.ToNumericResult(bonusResult); } else From 432b88674b3d2e0eff6bbf80b26016eea6da0b2d Mon Sep 17 00:00:00 2001 From: Zyf Date: Mon, 20 Nov 2023 00:02:58 +0100 Subject: [PATCH 1565/4092] Scoring : change formula parameters to match survey results --- osu.Game/Database/StandardisedScoreMigrationTools.cs | 6 +++--- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index c38dd0e9cf..dfc2f8fb62 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -316,11 +316,11 @@ namespace osu.Game.Database 1.2 * (newLowerStrippedV3 + newUpperStrippedV3) / 2 ); - double newComboScoreProportion = (strippedV3 / maxStrippedV3) * score.Accuracy; + double newComboScoreProportion = (strippedV3 / maxStrippedV3); return (long)Math.Round(( - 700000 * newComboScoreProportion * score.Accuracy - + 300000 * Math.Pow(score.Accuracy, 8) + 500000 * newComboScoreProportion * score.Accuracy + + 500000 * Math.Pow(score.Accuracy, 5) + bonusProportion) * modMultiplier); case 1: diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 514630b351..adf8e318d3 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -301,7 +301,7 @@ namespace osu.Game.Rulesets.Scoring protected virtual double GetBonusScoreChange(JudgementResult result) => Judgement.ToNumericResult(result.Type); - protected virtual double GetComboScoreChange(JudgementResult result) => Judgement.ToNumericResult(result.Type) * Math.Pow(result.ComboAfterJudgement, COMBO_EXPONENT); + protected virtual double GetComboScoreChange(JudgementResult result) => Judgement.ToNumericResult(result.Judgement.MaxResult) * Math.Pow(result.ComboAfterJudgement, COMBO_EXPONENT); protected virtual void ApplyScoreChange(JudgementResult result) { @@ -325,8 +325,8 @@ namespace osu.Game.Rulesets.Scoring protected virtual double ComputeTotalScore(double comboProgress, double accuracyProgress, double bonusPortion) { - return 700000 * Accuracy.Value * comboProgress + - 300000 * Math.Pow(Accuracy.Value, 8) * accuracyProgress + + return 500000 * Accuracy.Value * comboProgress + + 500000 * Math.Pow(Accuracy.Value, 5) * accuracyProgress + bonusPortion; } From bfcca382007be5c91adfecfc08dc0a2fbad4cb36 Mon Sep 17 00:00:00 2001 From: Stedoss <29103029+Stedoss@users.noreply.github.com> Date: Mon, 20 Nov 2023 01:15:26 +0000 Subject: [PATCH 1566/4092] Handle login API state changes in `UserProfileOverlay` --- osu.Game/Overlays/UserProfileOverlay.cs | 46 ++++++++++++++++++++----- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 0ab842c907..d45a010e4a 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -42,6 +43,11 @@ namespace osu.Game.Overlays private ProfileSectionsContainer? sectionsContainer; private ProfileSectionTabControl? tabs; + private IUser? user; + private IRulesetInfo? ruleset; + + private IBindable apiUser = null!; + [Resolved] private RulesetStore rulesets { get; set; } = null!; @@ -58,17 +64,38 @@ namespace osu.Game.Overlays }); } + [BackgroundDependencyLoader] + private void load() + { + apiUser = API.LocalUser.GetBoundCopy(); + apiUser.BindValueChanged(_ => Schedule(() => + { + if (API.IsLoggedIn) + fetchAndSetContent(); + })); + } + protected override ProfileHeader CreateHeader() => new ProfileHeader(); protected override Color4 BackgroundColour => ColourProvider.Background5; - public void ShowUser(IUser user, IRulesetInfo? ruleset = null) + public void ShowUser(IUser userToShow, IRulesetInfo? userRuleset = null) { - if (user.OnlineID == APIUser.SYSTEM_USER_ID) + if (userToShow.OnlineID == APIUser.SYSTEM_USER_ID) return; + user = userToShow; + ruleset = userRuleset; + Show(); + fetchAndSetContent(); + } + + private void fetchAndSetContent() + { + Debug.Assert(user != null); + if (user.OnlineID == Header.User.Value?.User.Id && ruleset?.MatchesOnlineID(Header.User.Value?.Ruleset) == true) return; @@ -143,24 +170,27 @@ namespace osu.Game.Overlays sectionsContainer.ScrollToTop(); + if (!API.IsLoggedIn) + return; + userReq = user.OnlineID > 1 ? new GetUserRequest(user.OnlineID, ruleset) : new GetUserRequest(user.Username, ruleset); - userReq.Success += u => userLoadComplete(u, ruleset); + userReq.Success += userLoadComplete; API.Queue(userReq); loadingLayer.Show(); } - private void userLoadComplete(APIUser user, IRulesetInfo? ruleset) + private void userLoadComplete(APIUser loadedUser) { Debug.Assert(sections != null && sectionsContainer != null && tabs != null); - var actualRuleset = rulesets.GetRuleset(ruleset?.ShortName ?? user.PlayMode).AsNonNull(); + var actualRuleset = rulesets.GetRuleset(ruleset?.ShortName ?? loadedUser.PlayMode).AsNonNull(); - var userProfile = new UserProfileData(user, actualRuleset); + var userProfile = new UserProfileData(loadedUser, actualRuleset); Header.User.Value = userProfile; - if (user.ProfileOrder != null) + if (loadedUser.ProfileOrder != null) { - foreach (string id in user.ProfileOrder) + foreach (string id in loadedUser.ProfileOrder) { var sec = sections.FirstOrDefault(s => s.Identifier == id); From 1f88658bade025040f3c97a08020de9e21dba41e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Burgelin=20=28Zyfarok=29?= Date: Mon, 20 Nov 2023 03:05:46 +0100 Subject: [PATCH 1567/4092] Fix syntax --- osu.Game/Database/StandardisedScoreMigrationTools.cs | 2 ++ osu.Game/Rulesets/Scoring/Legacy/LegacyScoreAttributes.cs | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index dfc2f8fb62..685f852eb4 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -267,10 +267,12 @@ namespace osu.Game.Database { case 0: if (score.MaxCombo == 0 || score.Accuracy == 0) + { return (long)Math.Round(( 0 + 300000 * Math.Pow(score.Accuracy, 8) + bonusProportion) * modMultiplier); + } // Assumption : // - sliders and slider-ticks are uniformly spread arround the beatmap diff --git a/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreAttributes.cs b/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreAttributes.cs index c2f87be194..6f6740c641 100644 --- a/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreAttributes.cs +++ b/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreAttributes.cs @@ -29,6 +29,5 @@ namespace osu.Game.Rulesets.Scoring.Legacy /// The max combo of the legacy (ScoreV1) total score. /// public int MaxCombo; - } } From e182acf3e8c012fe36f8317d70ca7d6abe1803e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 11:50:28 +0900 Subject: [PATCH 1568/4092] Expand comment for clarification --- osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 9d63949dcc..1cf6bc91f0 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -339,7 +339,8 @@ namespace osu.Game.Rulesets.Osu.Replays AddFrameToReplay(startFrame); - // ~477 as per stable. + // 0.05 rad/ms, or ~477 RPM, as per stable. + // the redundant conversion from RPM to rad/ms is here for ease of testing custom SPM specs. const float spin_rpm = 0.05f / (2 * MathF.PI) * 60000; float radsPerMillisecond = MathUtils.DegreesToRadians(spin_rpm * 360) / 60000; From 33b592f1c723fb4824f2084337de9dbb940d2b1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 12:04:30 +0900 Subject: [PATCH 1569/4092] Update framework (again) --- 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 1f6a65c450..dd5a8996fb 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 70525a5c59..9a0832b4e7 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From 577cb9994ce54202bf2c07548389f0e6b701fa8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 12:24:32 +0900 Subject: [PATCH 1570/4092] Move static instances / construction methods closer together --- osu.Game/Rulesets/Objects/Types/PathType.cs | 38 ++++++++++----------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Types/PathType.cs b/osu.Game/Rulesets/Objects/Types/PathType.cs index e4249154e5..0e3adb4473 100644 --- a/osu.Game/Rulesets/Objects/Types/PathType.cs +++ b/osu.Game/Rulesets/Objects/Types/PathType.cs @@ -16,11 +16,6 @@ namespace osu.Game.Rulesets.Objects.Types public readonly struct PathType : IEquatable, IHasDescription { - public static readonly PathType CATMULL = new PathType(SplineType.Catmull); - public static readonly PathType BEZIER = new PathType(SplineType.BSpline); - public static readonly PathType LINEAR = new PathType(SplineType.Linear); - public static readonly PathType PERFECT_CURVE = new PathType(SplineType.PerfectCurve); - /// /// The type of the spline that should be used to interpret the control points of the path. /// @@ -32,6 +27,25 @@ namespace osu.Game.Rulesets.Objects.Types /// public int? Degree { get; init; } + public PathType(SplineType splineType) + { + Type = splineType; + Degree = null; + } + + public static readonly PathType CATMULL = new PathType(SplineType.Catmull); + public static readonly PathType BEZIER = new PathType(SplineType.BSpline); + public static readonly PathType LINEAR = new PathType(SplineType.Linear); + public static readonly PathType PERFECT_CURVE = new PathType(SplineType.PerfectCurve); + + public static PathType BSpline(int degree) + { + if (degree <= 0) + throw new ArgumentOutOfRangeException(nameof(degree), "The degree of a B-Spline path must be greater than zero."); + + return new PathType { Type = SplineType.BSpline, Degree = degree }; + } + public string Description => Type switch { SplineType.Catmull => "Catmull", @@ -41,12 +55,6 @@ namespace osu.Game.Rulesets.Objects.Types _ => Type.ToString() }; - public PathType(SplineType splineType) - { - Type = splineType; - Degree = null; - } - public override int GetHashCode() => HashCode.Combine(Type, Degree); @@ -59,14 +67,6 @@ namespace osu.Game.Rulesets.Objects.Types public static bool operator !=(PathType a, PathType b) => a.Type != b.Type || a.Degree != b.Degree; - public static PathType BSpline(int degree) - { - if (degree <= 0) - throw new ArgumentOutOfRangeException(nameof(degree), "The degree of a B-Spline path must be greater than zero."); - - return new PathType { Type = SplineType.BSpline, Degree = degree }; - } - public bool Equals(PathType other) => Type == other.Type && Degree == other.Degree; } From 25c1a900473de216ba5c07b306b4e49316ec7828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 12:25:44 +0900 Subject: [PATCH 1571/4092] Change switchexpr to standard switch statement --- osu.Game/Rulesets/Objects/Types/PathType.cs | 29 ++++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Types/PathType.cs b/osu.Game/Rulesets/Objects/Types/PathType.cs index 0e3adb4473..95ddcb8b05 100644 --- a/osu.Game/Rulesets/Objects/Types/PathType.cs +++ b/osu.Game/Rulesets/Objects/Types/PathType.cs @@ -46,14 +46,29 @@ namespace osu.Game.Rulesets.Objects.Types return new PathType { Type = SplineType.BSpline, Degree = degree }; } - public string Description => Type switch + public string Description { - SplineType.Catmull => "Catmull", - SplineType.BSpline => Degree == null ? "Bezier" : "B-Spline", - SplineType.Linear => "Linear", - SplineType.PerfectCurve => "Perfect Curve", - _ => Type.ToString() - }; + get + { + switch (Type) + { + case SplineType.Catmull: + return "Catmull"; + + case SplineType.BSpline: + return Degree == null ? "Bezier" : "B-Spline"; + + case SplineType.Linear: + return "Linear"; + + case SplineType.PerfectCurve: + return "Perfect Curve"; + + default: + return Type.ToString(); + } + } + } public override int GetHashCode() => HashCode.Combine(Type, Degree); From 7820c8ce4d558381f80444fcf3fb38035382a852 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 12:28:20 +0900 Subject: [PATCH 1572/4092] Decrease redundancy of equality implementations --- osu.Game/Rulesets/Objects/Types/PathType.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Types/PathType.cs b/osu.Game/Rulesets/Objects/Types/PathType.cs index 95ddcb8b05..37c1d0ab50 100644 --- a/osu.Game/Rulesets/Objects/Types/PathType.cs +++ b/osu.Game/Rulesets/Objects/Types/PathType.cs @@ -74,15 +74,12 @@ namespace osu.Game.Rulesets.Objects.Types => HashCode.Combine(Type, Degree); public override bool Equals(object? obj) - => obj is PathType pathType && this == pathType; - - public static bool operator ==(PathType a, PathType b) - => a.Type == b.Type && a.Degree == b.Degree; - - public static bool operator !=(PathType a, PathType b) - => a.Type != b.Type || a.Degree != b.Degree; + => obj is PathType pathType && Equals(pathType); public bool Equals(PathType other) => Type == other.Type && Degree == other.Degree; + + public static bool operator ==(PathType a, PathType b) => a.Equals(b); + public static bool operator !=(PathType a, PathType b) => !a.Equals(b); } } From 518dcc567b14cdf8ade2d449b20cdcbcfcf45e98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 12:41:22 +0900 Subject: [PATCH 1573/4092] Null-check `drawingSettingsProvider` As it's annotated as an optional dependency. --- .../Sliders/SliderPlacementBlueprint.cs | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index df7d2c716b..9b24415604 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -80,19 +80,22 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders base.LoadComplete(); inputManager = GetContainingInputManager(); - drawingSettingsProvider.Tolerance.BindValueChanged(e => + if (drawingSettingsProvider != null) { - if (bSplineBuilder.Tolerance != e.NewValue) - bSplineBuilder.Tolerance = e.NewValue; - updateSliderPathFromBSplineBuilder(); - }, true); + drawingSettingsProvider.Tolerance.BindValueChanged(e => + { + if (bSplineBuilder.Tolerance != e.NewValue) + bSplineBuilder.Tolerance = e.NewValue; + updateSliderPathFromBSplineBuilder(); + }, true); - drawingSettingsProvider.CornerThreshold.BindValueChanged(e => - { - if (bSplineBuilder.CornerThreshold != e.NewValue) - bSplineBuilder.CornerThreshold = e.NewValue; - updateSliderPathFromBSplineBuilder(); - }, true); + drawingSettingsProvider.CornerThreshold.BindValueChanged(e => + { + if (bSplineBuilder.CornerThreshold != e.NewValue) + bSplineBuilder.CornerThreshold = e.NewValue; + updateSliderPathFromBSplineBuilder(); + }, true); + } } [Resolved] From f46945a29439716ef10e5e82277cf5e338044094 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 12:42:16 +0900 Subject: [PATCH 1574/4092] Avoid one unnecessary path update from B-spline builder --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 9b24415604..50b4377ccd 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders if (bSplineBuilder.Tolerance != e.NewValue) bSplineBuilder.Tolerance = e.NewValue; updateSliderPathFromBSplineBuilder(); - }, true); + }); drawingSettingsProvider.CornerThreshold.BindValueChanged(e => { From 831884a64b74113f76bc59eed0466f232b71f7aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 13:00:12 +0900 Subject: [PATCH 1575/4092] Remove unused enum member --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 50b4377ccd..50abcf1233 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -344,8 +344,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { Initial, ControlPoints, - Drawing, - DrawingFinalization + Drawing } } } From affef85f2521246412949531e291fdbed4cf1272 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 13:02:51 +0900 Subject: [PATCH 1576/4092] Remove `ISliderDrawingSettingsProvider` Seems like excessive abstraction. --- .../Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 +- .../Edit/ISliderDrawingSettingsProvider.cs | 13 ------------- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 2 +- .../Edit/OsuSliderDrawingSettingsProvider.cs | 2 +- 4 files changed, 3 insertions(+), 16 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Edit/ISliderDrawingSettingsProvider.cs diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 50abcf1233..fb2c1d5149 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private IDistanceSnapProvider distanceSnapProvider { get; set; } [Resolved(CanBeNull = true)] - private ISliderDrawingSettingsProvider drawingSettingsProvider { get; set; } + private OsuSliderDrawingSettingsProvider drawingSettingsProvider { get; set; } private readonly IncrementalBSplineBuilder bSplineBuilder = new IncrementalBSplineBuilder(); diff --git a/osu.Game.Rulesets.Osu/Edit/ISliderDrawingSettingsProvider.cs b/osu.Game.Rulesets.Osu/Edit/ISliderDrawingSettingsProvider.cs deleted file mode 100644 index 31ed98e1dd..0000000000 --- a/osu.Game.Rulesets.Osu/Edit/ISliderDrawingSettingsProvider.cs +++ /dev/null @@ -1,13 +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.Framework.Bindables; - -namespace osu.Game.Rulesets.Osu.Edit -{ - public interface ISliderDrawingSettingsProvider - { - BindableFloat Tolerance { get; } - BindableFloat CornerThreshold { get; } - } -} diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index d958b558cf..0dc0d6fd31 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Osu.Edit [Cached(typeof(IDistanceSnapProvider))] protected readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider(); - [Cached(typeof(ISliderDrawingSettingsProvider))] + [Cached] protected readonly OsuSliderDrawingSettingsProvider SliderDrawingSettingsProvider = new OsuSliderDrawingSettingsProvider(); [BackgroundDependencyLoader] diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs b/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs index 4326b2e943..0126b366d5 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Edit; namespace osu.Game.Rulesets.Osu.Edit { - public partial class OsuSliderDrawingSettingsProvider : Drawable, ISliderDrawingSettingsProvider, IToolboxAttachment + public partial class OsuSliderDrawingSettingsProvider : Drawable, IToolboxAttachment { public BindableFloat Tolerance { get; } = new BindableFloat(1.5f) { From 5d1bac6d7a55a980e608fbf6bfa8744ceb30b18f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 13:17:43 +0900 Subject: [PATCH 1577/4092] Remove `IToolboxAttachment` for now The interface doesn't really do anything useful right now because it enforces a common contract, but all usages of the contract go through the concrete implementation, and it inflates the already-huge diff. --- .../Edit/OsuSliderDrawingSettingsProvider.cs | 2 +- osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs | 2 +- osu.Game/Rulesets/Edit/IToolboxAttachment.cs | 10 ---------- 3 files changed, 2 insertions(+), 12 deletions(-) delete mode 100644 osu.Game/Rulesets/Edit/IToolboxAttachment.cs diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs b/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs index 0126b366d5..e44f0265c8 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Edit; namespace osu.Game.Rulesets.Osu.Edit { - public partial class OsuSliderDrawingSettingsProvider : Drawable, IToolboxAttachment + public partial class OsuSliderDrawingSettingsProvider : Drawable { public BindableFloat Tolerance { get; } = new BindableFloat(1.5f) { diff --git a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs index 68411d2b01..ddf539771d 100644 --- a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs @@ -29,7 +29,7 @@ using osu.Game.Screens.Edit.Components.TernaryButtons; namespace osu.Game.Rulesets.Edit { - public abstract partial class ComposerDistanceSnapProvider : Component, IDistanceSnapProvider, IScrollBindingHandler, IToolboxAttachment + public abstract partial class ComposerDistanceSnapProvider : Component, IDistanceSnapProvider, IScrollBindingHandler { private const float adjust_step = 0.1f; diff --git a/osu.Game/Rulesets/Edit/IToolboxAttachment.cs b/osu.Game/Rulesets/Edit/IToolboxAttachment.cs deleted file mode 100644 index 7d7c5980b2..0000000000 --- a/osu.Game/Rulesets/Edit/IToolboxAttachment.cs +++ /dev/null @@ -1,10 +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.Edit -{ - public interface IToolboxAttachment - { - void AttachToToolbox(ExpandingToolboxContainer toolbox); - } -} From 487326a4c786aa89b597ea191ec83c701386736d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 13:22:36 +0900 Subject: [PATCH 1578/4092] Remove pattern matching syntax usage in switch Also throw on unknown types. --- osu.Game/Rulesets/Objects/BezierConverter.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Objects/BezierConverter.cs b/osu.Game/Rulesets/Objects/BezierConverter.cs index 5dc0839d37..4a68161899 100644 --- a/osu.Game/Rulesets/Objects/BezierConverter.cs +++ b/osu.Game/Rulesets/Objects/BezierConverter.cs @@ -70,21 +70,21 @@ namespace osu.Game.Rulesets.Objects var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1); var segmentType = controlPoints[start].Type ?? PathType.LINEAR; - switch (segmentType) + switch (segmentType.Type) { - case { Type: SplineType.Catmull }: + case SplineType.Catmull: result.AddRange(from segment in ConvertCatmullToBezierAnchors(segmentVertices) from v in segment select v + position); break; - case { Type: SplineType.Linear }: + case SplineType.Linear: result.AddRange(from segment in ConvertLinearToBezierAnchors(segmentVertices) from v in segment select v + position); break; - case { Type: SplineType.PerfectCurve }: + case SplineType.PerfectCurve: result.AddRange(ConvertCircleToBezierAnchors(segmentVertices).Select(v => v + position)); break; - default: + case SplineType.BSpline: if (segmentType.Degree != null) throw new NotImplementedException("BSpline conversion of arbitrary degree is not implemented."); @@ -94,6 +94,9 @@ namespace osu.Game.Rulesets.Objects } break; + + default: + throw new ArgumentOutOfRangeException(nameof(segmentType.Type), segmentType.Type, "Unsupported segment type found when converting to legacy Bezier"); } // Start the new segment at the current vertex @@ -160,13 +163,16 @@ namespace osu.Game.Rulesets.Objects break; - default: + case SplineType.BSpline: for (int j = 0; j < segmentVertices.Length - 1; j++) { result.Add(new PathControlPoint(segmentVertices[j], j == 0 ? segmentType : null)); } break; + + default: + throw new ArgumentOutOfRangeException(nameof(segmentType.Type), segmentType.Type, "Unsupported segment type found when converting to legacy Bezier"); } // Start the new segment at the current vertex From 80a3225bb28f315aeceda4dc5e10c2222faa0e1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 13:35:07 +0900 Subject: [PATCH 1579/4092] Use static `BEZIER` instead of allocating new every time --- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 92a92dca8f..411a9b0d63 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -230,7 +230,7 @@ namespace osu.Game.Rulesets.Objects.Legacy if (input.Length > 1 && int.TryParse(input.Substring(1), out int degree) && degree > 0) return PathType.BSpline(degree); - return new PathType(SplineType.BSpline); + return PathType.BEZIER; case 'L': return PathType.LINEAR; From 6d7d826b8b76f775bf59d600d493912a92950540 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 15:08:58 +0900 Subject: [PATCH 1580/4092] Fix incorrect legacy conversion when B-splines are used --- osu.Game/Database/LegacyBeatmapExporter.cs | 3 ++- osu.Game/Rulesets/Objects/BezierConverter.cs | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/LegacyBeatmapExporter.cs b/osu.Game/Database/LegacyBeatmapExporter.cs index 9ca12a79dd..69120ea885 100644 --- a/osu.Game/Database/LegacyBeatmapExporter.cs +++ b/osu.Game/Database/LegacyBeatmapExporter.cs @@ -85,7 +85,8 @@ namespace osu.Game.Database if (hasPath.Path.ControlPoints.Count > 1) hasPath.Path.ControlPoints[^1].Type = null; - if (BezierConverter.CountSegments(hasPath.Path.ControlPoints) <= 1) continue; + if (BezierConverter.CountSegments(hasPath.Path.ControlPoints) <= 1 + && hasPath.Path.ControlPoints[0].Type!.Value.Degree == null) continue; var newControlPoints = BezierConverter.ConvertToModernBezier(hasPath.Path.ControlPoints); diff --git a/osu.Game/Rulesets/Objects/BezierConverter.cs b/osu.Game/Rulesets/Objects/BezierConverter.cs index 4a68161899..638975630e 100644 --- a/osu.Game/Rulesets/Objects/BezierConverter.cs +++ b/osu.Game/Rulesets/Objects/BezierConverter.cs @@ -164,9 +164,13 @@ namespace osu.Game.Rulesets.Objects break; case SplineType.BSpline: - for (int j = 0; j < segmentVertices.Length - 1; j++) + var bSplineResult = segmentType.Degree == null + ? segmentVertices + : PathApproximator.BSplineToBezier(segmentVertices, segmentType.Degree.Value); + + for (int j = 0; j < bSplineResult.Length - 1; j++) { - result.Add(new PathControlPoint(segmentVertices[j], j == 0 ? segmentType : null)); + result.Add(new PathControlPoint(bSplineResult[j], j == 0 ? PathType.BEZIER : null)); } break; From 46d4587c97fc8a6d7b3a2ea0e98366fe149f2ed3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 15:34:01 +0900 Subject: [PATCH 1581/4092] Add test for slider drawing --- .../TestSceneSliderPlacementBlueprint.cs | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index ecfc8105f1..d1c94c9c9c 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -290,6 +290,27 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertControlPointType(0, PathType.LINEAR); } + [Test] + public void TestSliderDrawing() + { + addMovementStep(new Vector2(200)); + AddStep("press left button", () => InputManager.PressButton(MouseButton.Left)); + + addMovementStep(new Vector2(300, 200)); + addMovementStep(new Vector2(400, 200)); + addMovementStep(new Vector2(400, 300)); + addMovementStep(new Vector2(400)); + addMovementStep(new Vector2(300, 400)); + addMovementStep(new Vector2(200, 400)); + + AddStep("release left button", () => InputManager.ReleaseButton(MouseButton.Left)); + + assertPlaced(true); + assertLength(600, tolerance: 10); + assertControlPointCount(4); + assertControlPointType(0, PathType.BSpline(3)); + } + [Test] public void TestPlacePerfectCurveSegmentAlmostLinearlyExterior() { @@ -397,7 +418,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private void assertPlaced(bool expected) => AddAssert($"slider {(expected ? "placed" : "not placed")}", () => (getSlider() != null) == expected); - private void assertLength(double expected) => AddAssert($"slider length is {expected}", () => Precision.AlmostEquals(expected, getSlider().Distance, 1)); + private void assertLength(double expected, double tolerance = 1) => AddAssert($"slider length is {expected}±{tolerance}", () => Precision.AlmostEquals(expected, getSlider().Distance, tolerance)); private void assertControlPointCount(int expected) => AddAssert($"has {expected} control points", () => getSlider().Path.ControlPoints.Count == expected); From 5f302662be30eff9eb12318c40651f1f776fb09c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 15:34:23 +0900 Subject: [PATCH 1582/4092] Remove test terminally broken by introduction of slider drawing --- .../Editor/TestSceneSliderPlacementBlueprint.cs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index d1c94c9c9c..c7a21ba689 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -273,23 +273,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertControlPointType(2, PathType.PERFECT_CURVE); } - [Test] - public void TestBeginPlacementWithoutReleasingMouse() - { - addMovementStep(new Vector2(200)); - AddStep("press left button", () => InputManager.PressButton(MouseButton.Left)); - - addMovementStep(new Vector2(400, 200)); - AddStep("release left button", () => InputManager.ReleaseButton(MouseButton.Left)); - - addClickStep(MouseButton.Right); - - assertPlaced(true); - assertLength(200); - assertControlPointCount(2); - assertControlPointType(0, PathType.LINEAR); - } - [Test] public void TestSliderDrawing() { From 8e39dbbff1898830ab70c5b418ec31b661fede42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 15:41:26 +0900 Subject: [PATCH 1583/4092] Adjust casing of curve type menu items The "Perfect curve" one in particular... fixes test failures, as some tests were relying on this particular casing. But the new version feels more correct anyway, so it's whatever. --- osu.Game/Rulesets/Objects/Types/PathType.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Types/PathType.cs b/osu.Game/Rulesets/Objects/Types/PathType.cs index 37c1d0ab50..f84d43e3e7 100644 --- a/osu.Game/Rulesets/Objects/Types/PathType.cs +++ b/osu.Game/Rulesets/Objects/Types/PathType.cs @@ -56,13 +56,13 @@ namespace osu.Game.Rulesets.Objects.Types return "Catmull"; case SplineType.BSpline: - return Degree == null ? "Bezier" : "B-Spline"; + return Degree == null ? "Bezier" : "B-spline"; case SplineType.Linear: return "Linear"; case SplineType.PerfectCurve: - return "Perfect Curve"; + return "Perfect curve"; default: return Type.ToString(); From 43dbd65708de80e4e671e042e76496ffb7526d57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 15:53:25 +0900 Subject: [PATCH 1584/4092] Show Catmull as a control point type option if selection already contains it --- .../TestScenePathControlPointVisualiser.cs | 26 ++++++++++++++++++- .../Components/PathControlPointVisualiser.cs | 4 +-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs index 811ecf53e9..8234381283 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs @@ -148,6 +148,30 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertControlPointPathType(3, null); } + [Test] + public void TestCatmullAvailableIffSelectionContainsCatmull() + { + createVisualiser(true); + + addControlPointStep(new Vector2(200), PathType.CATMULL); + addControlPointStep(new Vector2(300)); + addControlPointStep(new Vector2(500, 300)); + addControlPointStep(new Vector2(700, 200)); + addControlPointStep(new Vector2(500, 100)); + + moveMouseToControlPoint(2); + AddStep("select first and third control point", () => + { + visualiser.Pieces[0].IsSelected.Value = true; + visualiser.Pieces[2].IsSelected.Value = true; + }); + addContextMenuItemStep("Catmull"); + + assertControlPointPathType(0, PathType.CATMULL); + assertControlPointPathType(2, PathType.CATMULL); + assertControlPointPathType(4, null); + } + private void createVisualiser(bool allowSelection) => AddStep("create visualiser", () => Child = visualiser = new PathControlPointVisualiser(slider, allowSelection) { Anchor = Anchor.Centre, @@ -158,7 +182,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private void addControlPointStep(Vector2 position, PathType? type) { - AddStep($"add {type} control point at {position}", () => + AddStep($"add {type?.Type} control point at {position}", () => { slider.Path.ControlPoints.Add(new PathControlPoint(position, type)); }); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 95c72a0a62..751ca7e753 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -372,9 +372,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components curveTypeItems.Add(createMenuItemForPathType(PathType.BEZIER)); curveTypeItems.Add(createMenuItemForPathType(PathType.BSpline(3))); - var hoveredPiece = Pieces.FirstOrDefault(p => p.IsHovered); - - if (hoveredPiece?.ControlPoint.Type == PathType.CATMULL) + if (selectedPieces.Any(piece => piece.ControlPoint.Type?.Type == SplineType.Catmull)) curveTypeItems.Add(createMenuItemForPathType(PathType.CATMULL)); var menuItems = new List From 4061417ac8cff9242cf5582fdb8b173f7095059e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 15:54:43 +0900 Subject: [PATCH 1585/4092] Decrease default value for slider tolerance Highly subjective change, but at 50 the drawing just did not feel responsive enough to input. --- osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs b/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs index e44f0265c8..7e72eee510 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Edit Precision = 0.01f }; - private readonly BindableInt sliderTolerance = new BindableInt(50) + private readonly BindableInt sliderTolerance = new BindableInt(40) { MinValue = 5, MaxValue = 100 From ded9981d07b8bc54fd43a53a76f6335aab5d636f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Nov 2023 17:55:01 +0900 Subject: [PATCH 1586/4092] 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 dd5a8996fb..8175869405 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 9a0832b4e7..a5425ba4c7 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From 492fd06c624b2d8d8ff65464798bcde72c4fd51c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Nov 2023 19:21:23 +0900 Subject: [PATCH 1587/4092] Remove unnecessary null override --- .../Blueprints/Sliders/Components/PathControlPointVisualiser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 751ca7e753..3128f46357 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -405,7 +405,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components int totalCount = Pieces.Count(p => p.IsSelected.Value); int countOfState = Pieces.Where(p => p.IsSelected.Value).Count(p => p.ControlPoint.Type == type); - var item = new TernaryStateRadioMenuItem(type == null ? "Inherit" : type!.Value.Description, MenuItemType.Standard, _ => + var item = new TernaryStateRadioMenuItem(type == null ? "Inherit" : type.Value.Description, MenuItemType.Standard, _ => { foreach (var p in Pieces.Where(p => p.IsSelected.Value)) updatePathType(p, type); From c9e8d66e1970fa8498761b4fb5fe868bf879d1d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 21:02:12 +0900 Subject: [PATCH 1588/4092] Improve xmldoc --- osu.Game/OsuGame.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 4e37481ba7..3fda18b7cb 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -555,9 +555,9 @@ namespace osu.Game public void ShowChangelogBuild(string updateStream, string version) => waitForReady(() => changelogOverlay, _ => changelogOverlay.ShowBuild(updateStream, version)); /// - /// Seek to a given timestamp in the Editor and select relevant HitObjects if needed + /// Seeks to the provided if the editor is currently open. + /// Can also select objects as indicated by the (depends on ruleset implementation). /// - /// The timestamp and the selected objects public void HandleTimestamp(string timestamp) { if (ScreenStack.CurrentScreen is not Editor editor) From 0e0ab66148ec99a0458b5102541e1a58206a2887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 21:27:24 +0900 Subject: [PATCH 1589/4092] Simplify parsing code Less methods, less smeared around logic, saner data types. --- osu.Game/Localisation/EditorStrings.cs | 5 --- .../Rulesets/Edit/EditorTimestampParser.cs | 41 ++++++++++++------- osu.Game/Screens/Edit/Editor.cs | 29 +++---------- 3 files changed, 32 insertions(+), 43 deletions(-) diff --git a/osu.Game/Localisation/EditorStrings.cs b/osu.Game/Localisation/EditorStrings.cs index 227dbc5e0c..e762455e8b 100644 --- a/osu.Game/Localisation/EditorStrings.cs +++ b/osu.Game/Localisation/EditorStrings.cs @@ -129,11 +129,6 @@ namespace osu.Game.Localisation ///
public static LocalisableString FailedToProcessTimestamp => new TranslatableString(getKey(@"failed_to_process_timestamp"), @"Failed to process timestamp"); - /// - /// "The timestamp was too long to process" - /// - public static LocalisableString TooLongTimestamp => new TranslatableString(getKey(@"too_long_timestamp"), @"The timestamp was too long to process"); - private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Rulesets/Edit/EditorTimestampParser.cs b/osu.Game/Rulesets/Edit/EditorTimestampParser.cs index e36822cc63..bdfdce432e 100644 --- a/osu.Game/Rulesets/Edit/EditorTimestampParser.cs +++ b/osu.Game/Rulesets/Edit/EditorTimestampParser.cs @@ -2,8 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; -using System.Linq; +using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; namespace osu.Game.Rulesets.Edit @@ -12,26 +11,40 @@ namespace osu.Game.Rulesets.Edit { // 00:00:000 (...) - test // original osu-web regex: https://github.com/ppy/osu-web/blob/3b1698639244cfdaf0b41c68bfd651ea729ec2e3/resources/js/utils/beatmapset-discussion-helper.ts#L78 - public static readonly Regex TIME_REGEX = new Regex(@"\b(((\d{2,}):([0-5]\d)[:.](\d{3}))(\s\([^)]+\))?)"); + public static readonly Regex TIME_REGEX = new Regex(@"\b(((?\d{2,}):(?[0-5]\d)[:.](?\d{3}))(?\s\([^)]+\))?)", RegexOptions.Compiled); - public static string[] GetRegexGroups(string timestamp) + public static bool TryParse(string timestamp, [NotNullWhen(true)] out TimeSpan? parsedTime, out string? parsedSelection) { Match match = TIME_REGEX.Match(timestamp); - string[] result = match.Success - ? match.Groups.Values.Where(x => x is not Match && !x.Value.Contains(':')).Select(x => x.Value).ToArray() - : Array.Empty(); + if (!match.Success) + { + parsedTime = null; + parsedSelection = null; + return false; + } - return result; - } + bool result = true; - public static double GetTotalMilliseconds(params string[] timesGroup) - { - int[] times = timesGroup.Select(int.Parse).ToArray(); + result &= int.TryParse(match.Groups[@"minutes"].Value, out int timeMin); + result &= int.TryParse(match.Groups[@"seconds"].Value, out int timeSec); + result &= int.TryParse(match.Groups[@"milliseconds"].Value, out int timeMsec); - Debug.Assert(times.Length == 3); + // somewhat sane limit for timestamp duration (10 hours). + result &= timeMin < 600; - return (times[0] * 60 + times[1]) * 1000 + times[2]; + if (!result) + { + parsedTime = null; + parsedSelection = null; + return false; + } + + parsedTime = TimeSpan.FromMinutes(timeMin) + TimeSpan.FromSeconds(timeSec) + TimeSpan.FromMilliseconds(timeMsec); + parsedSelection = match.Groups[@"selection"].Value.Trim(); + if (!string.IsNullOrEmpty(parsedSelection)) + parsedSelection = parsedSelection[1..^1]; + return true; } } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 03d3e3a1f8..c38e88cd02 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1141,9 +1141,7 @@ namespace osu.Game.Screens.Edit public void HandleTimestamp(string timestamp) { - string[] groups = EditorTimestampParser.GetRegexGroups(timestamp); - - if (groups.Length != 4 || string.IsNullOrEmpty(groups[0])) + if (!EditorTimestampParser.TryParse(timestamp, out var timeSpan, out string selection)) { Schedule(() => notifications?.Post(new SimpleNotification { @@ -1153,31 +1151,14 @@ namespace osu.Game.Screens.Edit return; } - string timeMin = groups[0]; - string timeSec = groups[1]; - string timeMss = groups[2]; - string objectsGroup = groups[3].Replace("(", "").Replace(")", "").Trim(); - - // Currently, lazer chat highlights infinite-long editor links like `10000000000:00:000 (1)` - // Limit timestamp link length at 30000 min (50 hr) to avoid parsing issues - if (string.IsNullOrEmpty(timeMin) || timeMin.Length > 5 || double.Parse(timeMin) > 30_000) - { - Schedule(() => notifications?.Post(new SimpleNotification - { - Icon = FontAwesome.Solid.ExclamationTriangle, - Text = EditorStrings.TooLongTimestamp - })); - return; - } - editorBeatmap.SelectedHitObjects.Clear(); if (clock.IsRunning) clock.Stop(); - double position = EditorTimestampParser.GetTotalMilliseconds(timeMin, timeSec, timeMss); + double position = timeSpan.Value.TotalMilliseconds; - if (string.IsNullOrEmpty(objectsGroup)) + if (string.IsNullOrEmpty(selection)) { clock.SeekSmoothlyTo(position); return; @@ -1194,8 +1175,8 @@ namespace osu.Game.Screens.Edit if (Mode.Value != EditorScreenMode.Compose) Mode.Value = EditorScreenMode.Compose; - // Let the Ruleset handle selection - currentScreen.Dependencies.Get().SelectHitObjects(position, objectsGroup); + // Delegate handling the selection to the ruleset. + currentScreen.Dependencies.Get().SelectHitObjects(position, selection); } public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); From 246aacb216d692b7bc54ae7fccebfd5869561d82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 21:29:19 +0900 Subject: [PATCH 1590/4092] Remove unnecessary guard Setting a bindable's value to something if that value is already there is a no-op (doesn't trigger bindings / callbacks). --- osu.Game/Screens/Edit/Editor.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index c38e88cd02..888f535cc6 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1172,8 +1172,7 @@ namespace osu.Game.Screens.Edit clock.SeekSmoothlyTo(position); - if (Mode.Value != EditorScreenMode.Compose) - Mode.Value = EditorScreenMode.Compose; + Mode.Value = EditorScreenMode.Compose; // Delegate handling the selection to the ruleset. currentScreen.Dependencies.Get().SelectHitObjects(position, selection); From 234ef6f92379b3cbf0c9a07f2334f2a068f9fe95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 21:34:36 +0900 Subject: [PATCH 1591/4092] Rectify selection keep-alive logic --- .../Components/EditorBlueprintContainer.cs | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs index a311054ffc..378d378be3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs @@ -51,10 +51,7 @@ namespace osu.Game.Screens.Edit.Compose.Components Beatmap.HitObjectAdded += AddBlueprintFor; Beatmap.HitObjectRemoved += RemoveBlueprintFor; - - // This makes sure HitObjects will have active Blueprints ready to display - // after clicking on an Editor Timestamp/Link - Beatmap.SelectedHitObjects.CollectionChanged += selectionChanged; + Beatmap.SelectedHitObjects.CollectionChanged += updateSelectionLifetime; if (Composer != null) { @@ -149,13 +146,23 @@ namespace osu.Game.Screens.Edit.Compose.Components SelectedItems.AddRange(Beatmap.HitObjects.Except(SelectedItems).ToArray()); } - private void selectionChanged(object sender, NotifyCollectionChangedEventArgs e) + /// + /// Ensures that newly-selected hitobjects are kept alive + /// and drops that keep-alive from newly-deselected objects. + /// + private void updateSelectionLifetime(object sender, NotifyCollectionChangedEventArgs e) { - if (e == null || e.Action != NotifyCollectionChangedAction.Add || e.NewItems == null) - return; + if (e.NewItems != null) + { + foreach (HitObject newSelection in e.NewItems) + Composer.Playfield.SetKeepAlive(newSelection, true); + } - foreach (HitObject item in e.NewItems) - Composer.Playfield.SetKeepAlive(item, true); + if (e.OldItems != null) + { + foreach (HitObject oldSelection in e.OldItems) + Composer.Playfield.SetKeepAlive(oldSelection, false); + } } protected override void OnBlueprintSelected(SelectionBlueprint blueprint) @@ -180,7 +187,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { Beatmap.HitObjectAdded -= AddBlueprintFor; Beatmap.HitObjectRemoved -= RemoveBlueprintFor; - Beatmap.SelectedHitObjects.CollectionChanged -= selectionChanged; + Beatmap.SelectedHitObjects.CollectionChanged -= updateSelectionLifetime; } usageEventBuffer?.Dispose(); From b6215b2809aac10f2bbf82403b76c6ca37f8d285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 21:37:29 +0900 Subject: [PATCH 1592/4092] Rename and document `SelectFromTimestamp` --- .../Edit/ManiaHitObjectComposer.cs | 2 +- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 2 +- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 13 +++++++++---- osu.Game/Screens/Edit/Editor.cs | 2 +- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 92ecea812c..df6c62da51 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Mania.Edit public override string ConvertSelectionToString() => string.Join(',', EditorBeatmap.SelectedHitObjects.Cast().OrderBy(h => h.StartTime).Select(h => $"{h.StartTime}|{h.Column}")); - public override void SelectHitObjects(double timestamp, string objectDescription) + public override void SelectFromTimestamp(double timestamp, string objectDescription) { if (!selection_regex.IsMatch(objectDescription)) return; diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 2afbd83ce5..585d692978 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Osu.Edit public override string ConvertSelectionToString() => string.Join(',', selectedHitObjects.Cast().OrderBy(h => h.StartTime).Select(h => (h.IndexInCurrentCombo + 1).ToString())); - public override void SelectHitObjects(double timestamp, string objectDescription) + public override void SelectFromTimestamp(double timestamp, string objectDescription) { if (!selection_regex.IsMatch(objectDescription)) return; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 52a525e84f..50e6393895 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -526,14 +526,19 @@ namespace osu.Game.Rulesets.Edit ///
public abstract bool CursorInPlacementArea { get; } + /// + /// Returns a string representing the current selection. + /// The inverse method to . + /// public virtual string ConvertSelectionToString() => string.Empty; /// - /// Each ruleset has it's own selection method + /// Selects objects based on the supplied and . + /// The inverse method to . /// - /// The given timestamp - /// The selected object information between the brackets - public virtual void SelectHitObjects(double timestamp, string objectDescription) { } + /// The time instant to seek to, in milliseconds. + /// The ruleset-specific description of objects to select at the given timestamp. + public virtual void SelectFromTimestamp(double timestamp, string objectDescription) { } #region IPositionSnapProvider diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 888f535cc6..fb34767315 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1175,7 +1175,7 @@ namespace osu.Game.Screens.Edit Mode.Value = EditorScreenMode.Compose; // Delegate handling the selection to the ruleset. - currentScreen.Dependencies.Get().SelectHitObjects(position, selection); + currentScreen.Dependencies.Get().SelectFromTimestamp(position, selection); } public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); From c16afeb34700b22a09932ad792ae801c0c5bffb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 21:57:07 +0900 Subject: [PATCH 1593/4092] Fix tests --- .../Editing/TestSceneOpenEditorTimestamp.cs | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs index bc31924e2c..86ceb73f7a 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs @@ -83,46 +83,42 @@ namespace osu.Game.Tests.Visual.Editing RulesetInfo rulesetInfo = new OsuRuleset().RulesetInfo; addStepClickLink("00:00:000", waitForSeek: false); - AddAssert("recieved 'must be in edit'", () => - Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEdit) == 1 - ); + AddAssert("received 'must be in edit'", + () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEdit), + () => Is.EqualTo(1)); AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo.Invoke()); - AddAssert("entered song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect); + AddUntilStep("entered song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect); addStepClickLink("00:00:000 (1)", waitForSeek: false); - AddAssert("recieved 'must be in edit'", () => - Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEdit) == 2 - ); + AddAssert("received 'must be in edit'", + () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEdit), + () => Is.EqualTo(2)); setUpEditor(rulesetInfo); - AddAssert("is editor Osu", () => editorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); + AddAssert("ruleset is osu!", () => editorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); addStepClickLink("00:000", "invalid link", waitForSeek: false); - AddAssert("recieved 'failed to process'", () => - Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp) == 1 - ); + AddAssert("received 'failed to process'", + () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp), + () => Is.EqualTo(1)); addStepClickLink("50000:00:000", "too long link", waitForSeek: false); - AddAssert("recieved 'too long'", () => - Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.TooLongTimestamp) == 1 - ); + AddAssert("received 'failed to process'", + () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp), + () => Is.EqualTo(2)); } [Test] public void TestHandleCurrentScreenChanges() { - const long long_link_value = 1_000 * 60 * 1_000; RulesetInfo rulesetInfo = new OsuRuleset().RulesetInfo; setUpEditor(rulesetInfo); - AddAssert("is editor Osu", () => editorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); + AddAssert("is osu! ruleset", () => editorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); - addStepClickLink("1000:00:000", "long link"); - AddAssert("moved to end of track", () => - editorClock.CurrentTime == long_link_value - || (editorClock.TrackLength < long_link_value && editorClock.CurrentTime == editorClock.TrackLength) - ); + addStepClickLink("100:00:000", "long link"); + AddUntilStep("moved to end of track", () => editorClock.CurrentTime, () => Is.EqualTo(editorClock.TrackLength)); addStepScreenModeTo(EditorScreenMode.SongSetup); addStepClickLink("00:00:000"); From 364a3f75e15001b785f185c41ba69e28edecf926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 22:03:25 +0900 Subject: [PATCH 1594/4092] Compile regexes --- osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs | 6 +++--- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index df6c62da51..e876ff1c81 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -49,12 +49,12 @@ namespace osu.Game.Rulesets.Mania.Edit new HoldNoteCompositionTool() }; - // 123|0,456|1,789|2 ... - private static readonly Regex selection_regex = new Regex(@"^\d+\|\d+(,\d+\|\d+)*$"); - public override string ConvertSelectionToString() => string.Join(',', EditorBeatmap.SelectedHitObjects.Cast().OrderBy(h => h.StartTime).Select(h => $"{h.StartTime}|{h.Column}")); + // 123|0,456|1,789|2 ... + private static readonly Regex selection_regex = new Regex(@"^\d+\|\d+(,\d+\|\d+)*$", RegexOptions.Compiled); + public override void SelectFromTimestamp(double timestamp, string objectDescription) { if (!selection_regex.IsMatch(objectDescription)) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 585d692978..c3fd36b933 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -104,12 +104,12 @@ namespace osu.Game.Rulesets.Osu.Edit protected override ComposeBlueprintContainer CreateBlueprintContainer() => new OsuBlueprintContainer(this); - // 1,2,3,4 ... - private static readonly Regex selection_regex = new Regex(@"^\d+(,\d+)*$"); - public override string ConvertSelectionToString() => string.Join(',', selectedHitObjects.Cast().OrderBy(h => h.StartTime).Select(h => (h.IndexInCurrentCombo + 1).ToString())); + // 1,2,3,4 ... + private static readonly Regex selection_regex = new Regex(@"^\d+(,\d+)*$", RegexOptions.Compiled); + public override void SelectFromTimestamp(double timestamp, string objectDescription) { if (!selection_regex.IsMatch(objectDescription)) From a8fc73695f30b3d22c260c8ea030f3034d1c21d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 22:04:08 +0900 Subject: [PATCH 1595/4092] Rename variable --- osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index e876ff1c81..967cdb0e54 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -61,11 +61,11 @@ namespace osu.Game.Rulesets.Mania.Edit return; List remainingHitObjects = EditorBeatmap.HitObjects.Cast().Where(h => h.StartTime >= timestamp).ToList(); - string[] splitDescription = objectDescription.Split(',').ToArray(); + string[] objectDescriptions = objectDescription.Split(',').ToArray(); - for (int i = 0; i < splitDescription.Length; i++) + for (int i = 0; i < objectDescriptions.Length; i++) { - string[] split = splitDescription[i].Split('|').ToArray(); + string[] split = objectDescriptions[i].Split('|').ToArray(); if (split.Length != 2) continue; @@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Mania.Edit EditorBeatmap.SelectedHitObjects.Add(current); - if (i < splitDescription.Length - 1) + if (i < objectDescriptions.Length - 1) remainingHitObjects = remainingHitObjects.Where(h => h != current && h.StartTime >= current.StartTime).ToList(); } } From 745a04a243b90a1b83da308ea3e8e6467c9faacc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Nov 2023 22:12:15 +0900 Subject: [PATCH 1596/4092] More test cleanup --- .../TestSceneOpenEditorTimestampInOsu.cs | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOpenEditorTimestampInOsu.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOpenEditorTimestampInOsu.cs index 93573b5ad8..753400e10e 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOpenEditorTimestampInOsu.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOpenEditorTimestampInOsu.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 System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -25,16 +26,17 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor addStepClickLink("00:00:000", "reset", false); } - private bool checkSnapAndSelectCombo(double startTime, params int[] comboNumbers) - { - bool checkCombos = comboNumbers.Any() - ? hasCombosInOrder(EditorBeatmap.SelectedHitObjects, comboNumbers) - : !EditorBeatmap.SelectedHitObjects.Any(); + private void checkSelection(Func startTime, params int[] comboNumbers) + => AddUntilStep($"seeked & selected {(comboNumbers.Any() ? string.Join(",", comboNumbers) : "nothing")}", () => + { + bool checkCombos = comboNumbers.Any() + ? hasCombosInOrder(EditorBeatmap.SelectedHitObjects, comboNumbers) + : !EditorBeatmap.SelectedHitObjects.Any(); - return EditorClock.CurrentTime == startTime - && EditorBeatmap.SelectedHitObjects.Count == comboNumbers.Length - && checkCombos; - } + return EditorClock.CurrentTime == startTime() + && EditorBeatmap.SelectedHitObjects.Count == comboNumbers.Length + && checkCombos; + }); private bool hasCombosInOrder(IEnumerable selected, params int[] comboNumbers) { @@ -51,19 +53,19 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor public void TestNormalSelection() { addStepClickLink("00:02:170 (1,2,3)"); - AddAssert("snap and select 1-2-3", () => checkSnapAndSelectCombo(2_170, 1, 2, 3)); + checkSelection(() => 2_170, 1, 2, 3); addReset(); addStepClickLink("00:04:748 (2,3,4,1,2)"); - AddAssert("snap and select 2-3-4-1-2", () => checkSnapAndSelectCombo(4_748, 2, 3, 4, 1, 2)); + checkSelection(() => 4_748, 2, 3, 4, 1, 2); addReset(); addStepClickLink("00:02:170 (1,1,1)"); - AddAssert("snap and select 1-1-1", () => checkSnapAndSelectCombo(2_170, 1, 1, 1)); + checkSelection(() => 2_170, 1, 1, 1); addReset(); addStepClickLink("00:02:873 (2,2,2,2)"); - AddAssert("snap and select 2-2-2-2", () => checkSnapAndSelectCombo(2_873, 2, 2, 2, 2)); + checkSelection(() => 2_873, 2, 2, 2, 2); } [Test] @@ -71,24 +73,22 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { HitObject firstObject = null!; + AddStep("retrieve first object", () => firstObject = EditorBeatmap.HitObjects.First()); + addStepClickLink("00:00:000 (0)", "invalid combo"); - AddAssert("snap to next, select none", () => - { - firstObject = EditorBeatmap.HitObjects.First(); - return checkSnapAndSelectCombo(firstObject.StartTime); - }); + checkSelection(() => firstObject.StartTime); addReset(); addStepClickLink("00:00:000 (1)", "wrong offset"); - AddAssert("snap and select 1", () => checkSnapAndSelectCombo(firstObject.StartTime, 1)); + checkSelection(() => firstObject.StartTime, 1); addReset(); addStepClickLink("00:00:956 (2,3,4)", "wrong offset"); - AddAssert("snap to next, select 2-3-4", () => checkSnapAndSelectCombo(firstObject.StartTime, 2, 3, 4)); + checkSelection(() => firstObject.StartTime, 2, 3, 4); addReset(); addStepClickLink("00:00:956 (956|1,956|2)", "mania link"); - AddAssert("snap to next, select none", () => checkSnapAndSelectCombo(firstObject.StartTime)); + checkSelection(() => firstObject.StartTime); } } } From 750bbc8a19cfdb0f1c3133e9bd86e02bacb51f0d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 00:17:25 +0900 Subject: [PATCH 1597/4092] Simplify null checks --- .../Sliders/Components/PathControlPointVisualiser.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 3128f46357..ef8a033014 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -242,7 +242,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { int indexInSegment = piece.PointsInSegment.IndexOf(piece.ControlPoint); - if (type.HasValue && type.Value.Type == SplineType.PerfectCurve) + if (type?.Type == SplineType.PerfectCurve) { // Can't always create a circular arc out of 4 or more points, // so we split the segment into one 3-point circular arc segment @@ -405,7 +405,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components int totalCount = Pieces.Count(p => p.IsSelected.Value); int countOfState = Pieces.Where(p => p.IsSelected.Value).Count(p => p.ControlPoint.Type == type); - var item = new TernaryStateRadioMenuItem(type == null ? "Inherit" : type.Value.Description, MenuItemType.Standard, _ => + var item = new TernaryStateRadioMenuItem(type?.Description ?? "Inherit", MenuItemType.Standard, _ => { foreach (var p in Pieces.Where(p => p.IsSelected.Value)) updatePathType(p, type); From 6f5c468a83cd0341616d01d10ef5345568a7d943 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 00:21:44 +0900 Subject: [PATCH 1598/4092] Rename settings class --- .../Blueprints/Sliders/SliderPlacementBlueprint.cs | 8 ++++---- ...ovider.cs => FreehandSliderSettingsProvider.cs} | 14 +++++++------- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 6 +++--- 3 files changed, 14 insertions(+), 14 deletions(-) rename osu.Game.Rulesets.Osu/Edit/{OsuSliderDrawingSettingsProvider.cs => FreehandSliderSettingsProvider.cs} (98%) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index fb2c1d5149..df8417d8ff 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private IDistanceSnapProvider distanceSnapProvider { get; set; } [Resolved(CanBeNull = true)] - private OsuSliderDrawingSettingsProvider drawingSettingsProvider { get; set; } + private FreehandSliderSettingsProvider freehandSettingsProvider { get; set; } private readonly IncrementalBSplineBuilder bSplineBuilder = new IncrementalBSplineBuilder(); @@ -80,16 +80,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders base.LoadComplete(); inputManager = GetContainingInputManager(); - if (drawingSettingsProvider != null) + if (freehandSettingsProvider != null) { - drawingSettingsProvider.Tolerance.BindValueChanged(e => + freehandSettingsProvider.Tolerance.BindValueChanged(e => { if (bSplineBuilder.Tolerance != e.NewValue) bSplineBuilder.Tolerance = e.NewValue; updateSliderPathFromBSplineBuilder(); }); - drawingSettingsProvider.CornerThreshold.BindValueChanged(e => + freehandSettingsProvider.CornerThreshold.BindValueChanged(e => { if (bSplineBuilder.CornerThreshold != e.NewValue) bSplineBuilder.CornerThreshold = e.NewValue; diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs b/osu.Game.Rulesets.Osu/Edit/FreehandSliderSettingsProvider.cs similarity index 98% rename from osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs rename to osu.Game.Rulesets.Osu/Edit/FreehandSliderSettingsProvider.cs index 7e72eee510..9ad2b5d0f5 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSliderDrawingSettingsProvider.cs +++ b/osu.Game.Rulesets.Osu/Edit/FreehandSliderSettingsProvider.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Edit; namespace osu.Game.Rulesets.Osu.Edit { - public partial class OsuSliderDrawingSettingsProvider : Drawable + public partial class FreehandSliderSettingsProvider : Drawable { public BindableFloat Tolerance { get; } = new BindableFloat(1.5f) { @@ -19,12 +19,6 @@ namespace osu.Game.Rulesets.Osu.Edit Precision = 0.01f }; - private readonly BindableInt sliderTolerance = new BindableInt(40) - { - MinValue = 5, - MaxValue = 100 - }; - public BindableFloat CornerThreshold { get; } = new BindableFloat(0.4f) { MinValue = 0.05f, @@ -32,6 +26,12 @@ namespace osu.Game.Rulesets.Osu.Edit Precision = 0.01f }; + private readonly BindableInt sliderTolerance = new BindableInt(40) + { + MinValue = 5, + MaxValue = 100 + }; + private readonly BindableInt sliderCornerThreshold = new BindableInt(40) { MinValue = 5, diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 0dc0d6fd31..7bf1a12149 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Edit protected readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider(); [Cached] - protected readonly OsuSliderDrawingSettingsProvider SliderDrawingSettingsProvider = new OsuSliderDrawingSettingsProvider(); + protected readonly FreehandSliderSettingsProvider FreehandSliderSettingsProvider = new FreehandSliderSettingsProvider(); [BackgroundDependencyLoader] private void load() @@ -102,8 +102,8 @@ namespace osu.Game.Rulesets.Osu.Edit RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, }); - AddInternal(SliderDrawingSettingsProvider); - SliderDrawingSettingsProvider.AttachToToolbox(RightToolbox); + AddInternal(FreehandSliderSettingsProvider); + FreehandSliderSettingsProvider.AttachToToolbox(RightToolbox); } protected override ComposeBlueprintContainer CreateBlueprintContainer() From 638c8f1adc2e135aff574e135c515adfbf384423 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 00:25:23 +0900 Subject: [PATCH 1599/4092] Get rid of weird cruft and non-standard flow --- .../Edit/FreehandSliderSettingsProvider.cs | 66 ++++++++++--------- .../Edit/OsuHitObjectComposer.cs | 5 +- 2 files changed, 37 insertions(+), 34 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/FreehandSliderSettingsProvider.cs b/osu.Game.Rulesets.Osu/Edit/FreehandSliderSettingsProvider.cs index 9ad2b5d0f5..f4ee130938 100644 --- a/osu.Game.Rulesets.Osu/Edit/FreehandSliderSettingsProvider.cs +++ b/osu.Game.Rulesets.Osu/Edit/FreehandSliderSettingsProvider.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Utils; @@ -10,8 +11,13 @@ using osu.Game.Rulesets.Edit; namespace osu.Game.Rulesets.Osu.Edit { - public partial class FreehandSliderSettingsProvider : Drawable + public partial class FreehandSliderSettingsProvider : EditorToolboxGroup { + public FreehandSliderSettingsProvider() + : base("slider") + { + } + public BindableFloat Tolerance { get; } = new BindableFloat(1.5f) { MinValue = 0.05f, @@ -41,6 +47,34 @@ namespace osu.Game.Rulesets.Osu.Edit private ExpandableSlider toleranceSlider = null!; private ExpandableSlider cornerThresholdSlider = null!; + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + toleranceSlider = new ExpandableSlider + { + Current = sliderTolerance + }, + cornerThresholdSlider = new ExpandableSlider + { + Current = sliderCornerThreshold + } + }; + + sliderTolerance.BindValueChanged(e => + { + toleranceSlider.ContractedLabelText = $"C. P. S.: {e.NewValue:N0}"; + toleranceSlider.ExpandedLabelText = $"Control Point Spacing: {e.NewValue:N0}"; + }, true); + + sliderCornerThreshold.BindValueChanged(e => + { + cornerThresholdSlider.ContractedLabelText = $"C. T.: {e.NewValue:N0}"; + cornerThresholdSlider.ExpandedLabelText = $"Corner Threshold: {e.NewValue:N0}"; + }, true); + } + protected override void LoadComplete() { base.LoadComplete(); @@ -70,35 +104,5 @@ namespace osu.Game.Rulesets.Osu.Edit sliderCornerThreshold.Value = newValue; }); } - - public void AttachToToolbox(ExpandingToolboxContainer toolboxContainer) - { - toolboxContainer.Add(new EditorToolboxGroup("slider") - { - Children = new Drawable[] - { - toleranceSlider = new ExpandableSlider - { - Current = sliderTolerance - }, - cornerThresholdSlider = new ExpandableSlider - { - Current = sliderCornerThreshold - } - } - }); - - sliderTolerance.BindValueChanged(e => - { - toleranceSlider.ContractedLabelText = $"C. P. S.: {e.NewValue:N0}"; - toleranceSlider.ExpandedLabelText = $"Control Point Spacing: {e.NewValue:N0}"; - }, true); - - sliderCornerThreshold.BindValueChanged(e => - { - cornerThresholdSlider.ContractedLabelText = $"C. T.: {e.NewValue:N0}"; - cornerThresholdSlider.ExpandedLabelText = $"Corner Threshold: {e.NewValue:N0}"; - }, true); - } } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 7bf1a12149..06584ef17a 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Edit protected readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider(); [Cached] - protected readonly FreehandSliderSettingsProvider FreehandSliderSettingsProvider = new FreehandSliderSettingsProvider(); + protected readonly FreehandSliderSettingsProvider FreehandlSliderToolboxGroup = new FreehandSliderSettingsProvider(); [BackgroundDependencyLoader] private void load() @@ -102,8 +102,7 @@ namespace osu.Game.Rulesets.Osu.Edit RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, }); - AddInternal(FreehandSliderSettingsProvider); - FreehandSliderSettingsProvider.AttachToToolbox(RightToolbox); + RightToolbox.Add(FreehandlSliderToolboxGroup); } protected override ComposeBlueprintContainer CreateBlueprintContainer() From e9f371a5811b72763a50fc1e5fffba5ef95e081f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 09:59:49 +0900 Subject: [PATCH 1600/4092] Refactor slider settings class --- .../Sliders/SliderPlacementBlueprint.cs | 8 +- ...vider.cs => FreehandSliderToolboxGroup.cs} | 74 +++++++++---------- .../Edit/OsuHitObjectComposer.cs | 2 +- 3 files changed, 38 insertions(+), 46 deletions(-) rename osu.Game.Rulesets.Osu/Edit/{FreehandSliderSettingsProvider.cs => FreehandSliderToolboxGroup.cs} (53%) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index df8417d8ff..f8b7642643 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private IDistanceSnapProvider distanceSnapProvider { get; set; } [Resolved(CanBeNull = true)] - private FreehandSliderSettingsProvider freehandSettingsProvider { get; set; } + private FreehandSliderToolboxGroup freehandToolboxGroup { get; set; } private readonly IncrementalBSplineBuilder bSplineBuilder = new IncrementalBSplineBuilder(); @@ -80,16 +80,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders base.LoadComplete(); inputManager = GetContainingInputManager(); - if (freehandSettingsProvider != null) + if (freehandToolboxGroup != null) { - freehandSettingsProvider.Tolerance.BindValueChanged(e => + freehandToolboxGroup.Tolerance.BindValueChanged(e => { if (bSplineBuilder.Tolerance != e.NewValue) bSplineBuilder.Tolerance = e.NewValue; updateSliderPathFromBSplineBuilder(); }); - freehandSettingsProvider.CornerThreshold.BindValueChanged(e => + freehandToolboxGroup.CornerThreshold.BindValueChanged(e => { if (bSplineBuilder.CornerThreshold != e.NewValue) bSplineBuilder.CornerThreshold = e.NewValue; diff --git a/osu.Game.Rulesets.Osu/Edit/FreehandSliderSettingsProvider.cs b/osu.Game.Rulesets.Osu/Edit/FreehandSliderToolboxGroup.cs similarity index 53% rename from osu.Game.Rulesets.Osu/Edit/FreehandSliderSettingsProvider.cs rename to osu.Game.Rulesets.Osu/Edit/FreehandSliderToolboxGroup.cs index f4ee130938..1974415d30 100644 --- a/osu.Game.Rulesets.Osu/Edit/FreehandSliderSettingsProvider.cs +++ b/osu.Game.Rulesets.Osu/Edit/FreehandSliderToolboxGroup.cs @@ -5,15 +5,14 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Utils; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; namespace osu.Game.Rulesets.Osu.Edit { - public partial class FreehandSliderSettingsProvider : EditorToolboxGroup + public partial class FreehandSliderToolboxGroup : EditorToolboxGroup { - public FreehandSliderSettingsProvider() + public FreehandSliderToolboxGroup() : base("slider") { } @@ -32,13 +31,14 @@ namespace osu.Game.Rulesets.Osu.Edit Precision = 0.01f }; - private readonly BindableInt sliderTolerance = new BindableInt(40) + // We map internal ranges to a more standard range of values for display to the user. + private readonly BindableInt displayTolerance = new BindableInt(40) { MinValue = 5, MaxValue = 100 }; - private readonly BindableInt sliderCornerThreshold = new BindableInt(40) + private readonly BindableInt displayCornerThreshold = new BindableInt(40) { MinValue = 5, MaxValue = 100 @@ -54,55 +54,47 @@ namespace osu.Game.Rulesets.Osu.Edit { toleranceSlider = new ExpandableSlider { - Current = sliderTolerance + Current = displayTolerance }, cornerThresholdSlider = new ExpandableSlider { - Current = sliderCornerThreshold + Current = displayCornerThreshold } }; - - sliderTolerance.BindValueChanged(e => - { - toleranceSlider.ContractedLabelText = $"C. P. S.: {e.NewValue:N0}"; - toleranceSlider.ExpandedLabelText = $"Control Point Spacing: {e.NewValue:N0}"; - }, true); - - sliderCornerThreshold.BindValueChanged(e => - { - cornerThresholdSlider.ContractedLabelText = $"C. T.: {e.NewValue:N0}"; - cornerThresholdSlider.ExpandedLabelText = $"Corner Threshold: {e.NewValue:N0}"; - }, true); } protected override void LoadComplete() { base.LoadComplete(); - sliderTolerance.BindValueChanged(v => + displayTolerance.BindValueChanged(tolerance => { - float newValue = v.NewValue / 33f; - if (!Precision.AlmostEquals(newValue, Tolerance.Value)) - Tolerance.Value = newValue; - }); - Tolerance.BindValueChanged(v => + toleranceSlider.ContractedLabelText = $"C. P. S.: {tolerance.NewValue:N0}"; + toleranceSlider.ExpandedLabelText = $"Control Point Spacing: {tolerance.NewValue:N0}"; + + Tolerance.Value = displayToInternalTolerance(tolerance.NewValue); + }, true); + + displayCornerThreshold.BindValueChanged(threshold => { - int newValue = (int)Math.Round(v.NewValue * 33f); - if (sliderTolerance.Value != newValue) - sliderTolerance.Value = newValue; - }); - sliderCornerThreshold.BindValueChanged(v => - { - float newValue = v.NewValue / 100f; - if (!Precision.AlmostEquals(newValue, CornerThreshold.Value)) - CornerThreshold.Value = newValue; - }); - CornerThreshold.BindValueChanged(v => - { - int newValue = (int)Math.Round(v.NewValue * 100f); - if (sliderCornerThreshold.Value != newValue) - sliderCornerThreshold.Value = newValue; - }); + cornerThresholdSlider.ContractedLabelText = $"C. T.: {threshold.NewValue:N0}"; + cornerThresholdSlider.ExpandedLabelText = $"Corner Threshold: {threshold.NewValue:N0}"; + + CornerThreshold.Value = displayToInternalCornerThreshold(threshold.NewValue); + }, true); + + Tolerance.BindValueChanged(tolerance => + displayTolerance.Value = internalToDisplayTolerance(tolerance.NewValue) + ); + CornerThreshold.BindValueChanged(threshold => + displayCornerThreshold.Value = internalToDisplayCornerThreshold(threshold.NewValue) + ); + + float displayToInternalTolerance(float v) => v / 33f; + int internalToDisplayTolerance(float v) => (int)Math.Round(v * 33f); + + float displayToInternalCornerThreshold(float v) => v / 100f; + int internalToDisplayCornerThreshold(float v) => (int)Math.Round(v * 100f); } } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 06584ef17a..061f72d72f 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Edit protected readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider(); [Cached] - protected readonly FreehandSliderSettingsProvider FreehandlSliderToolboxGroup = new FreehandSliderSettingsProvider(); + protected readonly FreehandSliderToolboxGroup FreehandlSliderToolboxGroup = new FreehandSliderToolboxGroup(); [BackgroundDependencyLoader] private void load() From 5514a53df171d035d7d3afdee56b38903f29dc07 Mon Sep 17 00:00:00 2001 From: Stedoss <29103029+Stedoss@users.noreply.github.com> Date: Tue, 21 Nov 2023 01:04:46 +0000 Subject: [PATCH 1601/4092] Pass ruleset to callback to prevent ruleset desync --- osu.Game/Overlays/UserProfileOverlay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index d45a010e4a..fd494f63e1 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -174,16 +174,16 @@ namespace osu.Game.Overlays return; userReq = user.OnlineID > 1 ? new GetUserRequest(user.OnlineID, ruleset) : new GetUserRequest(user.Username, ruleset); - userReq.Success += userLoadComplete; + userReq.Success += u => userLoadComplete(u, ruleset); API.Queue(userReq); loadingLayer.Show(); } - private void userLoadComplete(APIUser loadedUser) + private void userLoadComplete(APIUser loadedUser, IRulesetInfo? userRuleset) { Debug.Assert(sections != null && sectionsContainer != null && tabs != null); - var actualRuleset = rulesets.GetRuleset(ruleset?.ShortName ?? loadedUser.PlayMode).AsNonNull(); + var actualRuleset = rulesets.GetRuleset(userRuleset?.ShortName ?? loadedUser.PlayMode).AsNonNull(); var userProfile = new UserProfileData(loadedUser, actualRuleset); Header.User.Value = userProfile; From ec7b82f5e874e7728a10ba5fe5c5872218dac056 Mon Sep 17 00:00:00 2001 From: Stedoss <29103029+Stedoss@users.noreply.github.com> Date: Tue, 21 Nov 2023 01:55:08 +0000 Subject: [PATCH 1602/4092] Change early return to check for online `State` instead of `IsLoggedIn` --- osu.Game/Overlays/UserProfileOverlay.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index fd494f63e1..193651fa72 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -19,6 +19,7 @@ using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Online; +using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Profile; @@ -170,7 +171,7 @@ namespace osu.Game.Overlays sectionsContainer.ScrollToTop(); - if (!API.IsLoggedIn) + if (API.State.Value != APIState.Online) return; userReq = user.OnlineID > 1 ? new GetUserRequest(user.OnlineID, ruleset) : new GetUserRequest(user.Username, ruleset); From 826c82de474f1cf322500dcc10e2d2d38719e297 Mon Sep 17 00:00:00 2001 From: Stedoss <29103029+Stedoss@users.noreply.github.com> Date: Tue, 21 Nov 2023 01:56:37 +0000 Subject: [PATCH 1603/4092] Add unit test for handling login state in `UserProfileOverlay` --- .../Online/TestSceneUserProfileOverlay.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index a321a194a9..1375689075 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -78,6 +78,31 @@ namespace osu.Game.Tests.Visual.Online AddStep("complete request", () => pendingRequest.TriggerSuccess(TEST_USER)); } + [Test] + public void TestLogin() + { + GetUserRequest pendingRequest = null!; + + AddStep("set up request handling", () => + { + dummyAPI.HandleRequest = req => + { + if (dummyAPI.State.Value == APIState.Online && req is GetUserRequest getUserRequest) + { + pendingRequest = getUserRequest; + return true; + } + + return false; + }; + }); + AddStep("logout", () => dummyAPI.Logout()); + AddStep("show user", () => profile.ShowUser(new APIUser { Id = 1 })); + AddStep("login", () => dummyAPI.Login("username", "password")); + AddWaitStep("wait some", 3); + AddStep("complete request", () => pendingRequest.TriggerSuccess(TEST_USER)); + } + public static readonly APIUser TEST_USER = new APIUser { Username = @"Somebody", From 3680024e3102d5dcca617371105d4c3bf3787945 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 11:15:00 +0900 Subject: [PATCH 1604/4092] Fix tolerance not being transferred to blueprint in all cases --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index f8b7642643..5f072eb171 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders if (bSplineBuilder.Tolerance != e.NewValue) bSplineBuilder.Tolerance = e.NewValue; updateSliderPathFromBSplineBuilder(); - }); + }, true); freehandToolboxGroup.CornerThreshold.BindValueChanged(e => { From 405ab499e9dfd7c75477513aa38b344ffb901781 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 14:24:10 +0900 Subject: [PATCH 1605/4092] Allow context menus to have visible spacers --- .../Visual/Editing/TestSceneEditorMenuBar.cs | 22 +++++++------- osu.Game/Graphics/UserInterface/OsuMenu.cs | 30 ++++++++++++++++++- .../UserInterface/OsuMenuItemSpacer.cs | 13 ++++++++ osu.Game/Overlays/SkinEditor/SkinEditor.cs | 10 +++---- .../SkinEditor/SkinSelectionHandler.cs | 7 ++--- .../Edit/Components/Menus/EditorMenuBar.cs | 27 +---------------- .../Components/Menus/EditorMenuItemSpacer.cs | 13 -------- osu.Game/Screens/Edit/Editor.cs | 10 +++---- 8 files changed, 67 insertions(+), 65 deletions(-) create mode 100644 osu.Game/Graphics/UserInterface/OsuMenuItemSpacer.cs delete mode 100644 osu.Game/Screens/Edit/Components/Menus/EditorMenuItemSpacer.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorMenuBar.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorMenuBar.cs index dbcf66f005..fe47f5885d 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorMenuBar.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorMenuBar.cs @@ -34,51 +34,51 @@ namespace osu.Game.Tests.Visual.Editing { new MenuItem("File") { - Items = new[] + Items = new OsuMenuItem[] { new EditorMenuItem("Clear All Notes"), new EditorMenuItem("Open Difficulty..."), new EditorMenuItem("Save"), new EditorMenuItem("Create a new Difficulty..."), - new EditorMenuItemSpacer(), + new OsuMenuItemSpacer(), new EditorMenuItem("Revert to Saved"), new EditorMenuItem("Revert to Saved (Full)"), - new EditorMenuItemSpacer(), + new OsuMenuItemSpacer(), new EditorMenuItem("Test Beatmap"), new EditorMenuItem("Open AiMod"), - new EditorMenuItemSpacer(), + new OsuMenuItemSpacer(), new EditorMenuItem("Upload Beatmap..."), new EditorMenuItem("Export Package"), new EditorMenuItem("Export Map Package"), new EditorMenuItem("Import from..."), - new EditorMenuItemSpacer(), + new OsuMenuItemSpacer(), new EditorMenuItem("Open Song Folder"), new EditorMenuItem("Open .osu in Notepad"), new EditorMenuItem("Open .osb in Notepad"), - new EditorMenuItemSpacer(), + new OsuMenuItemSpacer(), new EditorMenuItem("Exit"), } }, new MenuItem("Timing") { - Items = new[] + Items = new OsuMenuItem[] { new EditorMenuItem("Time Signature"), new EditorMenuItem("Metronome Clicks"), - new EditorMenuItemSpacer(), + new OsuMenuItemSpacer(), new EditorMenuItem("Add Timing Section"), new EditorMenuItem("Add Inheriting Section"), new EditorMenuItem("Reset Current Section"), new EditorMenuItem("Delete Timing Section"), new EditorMenuItem("Resnap Current Section"), - new EditorMenuItemSpacer(), + new OsuMenuItemSpacer(), new EditorMenuItem("Timing Setup"), - new EditorMenuItemSpacer(), + new OsuMenuItemSpacer(), new EditorMenuItem("Resnap All Notes", MenuItemType.Destructive), new EditorMenuItem("Move all notes in time...", MenuItemType.Destructive), new EditorMenuItem("Recalculate Slider Lengths", MenuItemType.Destructive), new EditorMenuItem("Delete All Timing Sections", MenuItemType.Destructive), - new EditorMenuItemSpacer(), + new OsuMenuItemSpacer(), new EditorMenuItem("Set Current Position as Preview Point"), } }, diff --git a/osu.Game/Graphics/UserInterface/OsuMenu.cs b/osu.Game/Graphics/UserInterface/OsuMenu.cs index 73d57af793..e2aac297e3 100644 --- a/osu.Game/Graphics/UserInterface/OsuMenu.cs +++ b/osu.Game/Graphics/UserInterface/OsuMenu.cs @@ -6,13 +6,15 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; -using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; using osuTK; +using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface { @@ -78,6 +80,9 @@ namespace osu.Game.Graphics.UserInterface { case StatefulMenuItem stateful: return new DrawableStatefulMenuItem(stateful); + + case OsuMenuItemSpacer spacer: + return new DrawableSpacer(spacer); } return new DrawableOsuMenuItem(item); @@ -89,5 +94,28 @@ namespace osu.Game.Graphics.UserInterface { Anchor = Direction == Direction.Horizontal ? Anchor.BottomLeft : Anchor.TopRight }; + + protected partial class DrawableSpacer : DrawableOsuMenuItem + { + public DrawableSpacer(MenuItem item) + : base(item) + { + Scale = new Vector2(1, 0.6f); + + AddInternal(new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = BackgroundColourHover, + RelativeSizeAxes = Axes.X, + Height = 2f, + Width = 0.8f, + }); + } + + protected override bool OnHover(HoverEvent e) => true; + + protected override bool OnClick(ClickEvent e) => true; + } } } diff --git a/osu.Game/Graphics/UserInterface/OsuMenuItemSpacer.cs b/osu.Game/Graphics/UserInterface/OsuMenuItemSpacer.cs new file mode 100644 index 0000000000..8a3a928c60 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/OsuMenuItemSpacer.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.Graphics.UserInterface +{ + public class OsuMenuItemSpacer : OsuMenuItem + { + public OsuMenuItemSpacer() + : base(" ") + { + } + } +} diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 38eed55241..a816031668 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -151,23 +151,23 @@ namespace osu.Game.Overlays.SkinEditor { new MenuItem(CommonStrings.MenuBarFile) { - Items = new[] + Items = new OsuMenuItem[] { new EditorMenuItem(Web.CommonStrings.ButtonsSave, MenuItemType.Standard, () => Save()), new EditorMenuItem(CommonStrings.Export, MenuItemType.Standard, () => skins.ExportCurrentSkin()) { Action = { Disabled = !RuntimeInfo.IsDesktop } }, - new EditorMenuItemSpacer(), + new OsuMenuItemSpacer(), new EditorMenuItem(CommonStrings.RevertToDefault, MenuItemType.Destructive, () => dialogOverlay?.Push(new RevertConfirmDialog(revert))), - new EditorMenuItemSpacer(), + new OsuMenuItemSpacer(), new EditorMenuItem(CommonStrings.Exit, MenuItemType.Standard, () => skinEditorOverlay?.Hide()), }, }, new MenuItem(CommonStrings.MenuBarEdit) { - Items = new[] + Items = new OsuMenuItem[] { undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo), redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo), - new EditorMenuItemSpacer(), + new OsuMenuItemSpacer(), cutMenuItem = new EditorMenuItem(CommonStrings.Cut, MenuItemType.Standard, Cut), copyMenuItem = new EditorMenuItem(CommonStrings.Copy, MenuItemType.Standard, Copy), pasteMenuItem = new EditorMenuItem(CommonStrings.Paste, MenuItemType.Standard, Paste), diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index 52c012a15a..cf6fb60636 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -14,7 +14,6 @@ using osu.Framework.Utils; using osu.Game.Extensions; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; -using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Skinning; using osu.Game.Utils; @@ -249,7 +248,7 @@ namespace osu.Game.Overlays.SkinEditor Items = createAnchorItems((d, o) => ((Drawable)d).Origin == o, applyOrigins).ToArray() }; - yield return new EditorMenuItemSpacer(); + yield return new OsuMenuItemSpacer(); yield return new OsuMenuItem("Reset position", MenuItemType.Standard, () => { @@ -277,13 +276,13 @@ namespace osu.Game.Overlays.SkinEditor } }); - yield return new EditorMenuItemSpacer(); + yield return new OsuMenuItemSpacer(); yield return new OsuMenuItem("Bring to front", MenuItemType.Standard, () => skinEditor.BringSelectionToFront()); yield return new OsuMenuItem("Send to back", MenuItemType.Standard, () => skinEditor.SendSelectionToBack()); - yield return new EditorMenuItemSpacer(); + yield return new OsuMenuItemSpacer(); foreach (var item in base.GetContextMenuItemsForSelection(selection)) yield return item; diff --git a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs index fb0ae2df73..a67375b0a4 100644 --- a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs +++ b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs @@ -4,11 +4,9 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; @@ -151,7 +149,7 @@ namespace osu.Game.Screens.Edit.Components.Menus { switch (item) { - case EditorMenuItemSpacer spacer: + case OsuMenuItemSpacer spacer: return new DrawableSpacer(spacer); case StatefulMenuItem stateful: @@ -195,29 +193,6 @@ namespace osu.Game.Screens.Edit.Components.Menus Foreground.Padding = new MarginPadding { Vertical = 2 }; } } - - private partial class DrawableSpacer : DrawableOsuMenuItem - { - public DrawableSpacer(MenuItem item) - : base(item) - { - Scale = new Vector2(1, 0.6f); - - AddInternal(new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Colour = BackgroundColourHover, - RelativeSizeAxes = Axes.X, - Height = 2f, - Width = 0.8f, - }); - } - - protected override bool OnHover(HoverEvent e) => true; - - protected override bool OnClick(ClickEvent e) => true; - } } } } diff --git a/osu.Game/Screens/Edit/Components/Menus/EditorMenuItemSpacer.cs b/osu.Game/Screens/Edit/Components/Menus/EditorMenuItemSpacer.cs deleted file mode 100644 index 4e75a92e19..0000000000 --- a/osu.Game/Screens/Edit/Components/Menus/EditorMenuItemSpacer.cs +++ /dev/null @@ -1,13 +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.Screens.Edit.Components.Menus -{ - public class EditorMenuItemSpacer : EditorMenuItem - { - public EditorMenuItemSpacer() - : base(" ") - { - } - } -} diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 3136faf855..5d0cc64cc3 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -321,7 +321,7 @@ namespace osu.Game.Screens.Edit { undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo), redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo), - new EditorMenuItemSpacer(), + new OsuMenuItemSpacer(), cutMenuItem = new EditorMenuItem(CommonStrings.Cut, MenuItemType.Standard, Cut), copyMenuItem = new EditorMenuItem(CommonStrings.Copy, MenuItemType.Standard, Copy), pasteMenuItem = new EditorMenuItem(CommonStrings.Paste, MenuItemType.Standard, Paste), @@ -1005,12 +1005,12 @@ namespace osu.Game.Screens.Edit { createDifficultyCreationMenu(), createDifficultySwitchMenu(), - new EditorMenuItemSpacer(), + new OsuMenuItemSpacer(), new EditorMenuItem(EditorStrings.DeleteDifficulty, MenuItemType.Standard, deleteDifficulty) { Action = { Disabled = Beatmap.Value.BeatmapSetInfo.Beatmaps.Count < 2 } }, - new EditorMenuItemSpacer(), + new OsuMenuItemSpacer(), new EditorMenuItem(WebCommonStrings.ButtonsSave, MenuItemType.Standard, () => Save()), createExportMenu(), - new EditorMenuItemSpacer(), + new OsuMenuItemSpacer(), new EditorMenuItem(CommonStrings.Exit, MenuItemType.Standard, this.Exit) }; @@ -1130,7 +1130,7 @@ namespace osu.Game.Screens.Edit foreach (var rulesetBeatmaps in groupedOrderedBeatmaps) { if (difficultyItems.Count > 0) - difficultyItems.Add(new EditorMenuItemSpacer()); + difficultyItems.Add(new OsuMenuItemSpacer()); foreach (var beatmap in rulesetBeatmaps) { From 9718a802490519780c3d88f55a90037a20b26421 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 14:24:19 +0900 Subject: [PATCH 1606/4092] Add visible spacer between "inherit" and other curve types --- .../Blueprints/Sliders/Components/PathControlPointVisualiser.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index f891d23bbd..87e837cc71 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -369,6 +369,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components if (!selectedPieces.Contains(Pieces[0])) curveTypeItems.Add(createMenuItemForPathType(null)); + curveTypeItems.Add(new OsuMenuItemSpacer()); + // todo: hide/disable items which aren't valid for selected points curveTypeItems.Add(createMenuItemForPathType(PathType.Linear)); curveTypeItems.Add(createMenuItemForPathType(PathType.PerfectCurve)); From c9ee29028fbcf3056c82d5b5c182f862f68974fa Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Tue, 21 Nov 2023 16:54:20 +1100 Subject: [PATCH 1607/4092] Fix implicitly used method being named incorrectly --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index 24d5635104..83538a2f42 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -95,7 +95,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty yield return (ATTRIB_ID_APPROACH_RATE, ApproachRate); yield return (ATTRIB_ID_DIFFICULTY, StarRating); - if (ShouldSerializeFlashlightRating()) + if (ShouldSerializeFlashlightDifficulty()) yield return (ATTRIB_ID_FLASHLIGHT, FlashlightDifficulty); yield return (ATTRIB_ID_SLIDER_FACTOR, SliderFactor); @@ -128,7 +128,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty // unless the fields are also renamed. [UsedImplicitly] - public bool ShouldSerializeFlashlightRating() => Mods.Any(m => m is ModFlashlight); + public bool ShouldSerializeFlashlightDifficulty() => Mods.Any(m => m is ModFlashlight); #endregion } From 3afaafb1d9a19692cbd6750eda24cbd7d920c7d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 15:05:51 +0900 Subject: [PATCH 1608/4092] Reorder and simplify private helper methods --- .../TestSceneOpenEditorTimestampInMania.cs | 53 +++++----- .../TestSceneOpenEditorTimestampInOsu.cs | 65 ++++++------ .../Editing/TestSceneOpenEditorTimestamp.cs | 100 +++++++++--------- 3 files changed, 103 insertions(+), 115 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneOpenEditorTimestampInMania.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneOpenEditorTimestampInMania.cs index 3c6a9f3b42..05c881d284 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneOpenEditorTimestampInMania.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneOpenEditorTimestampInMania.cs @@ -14,35 +14,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor { protected override Ruleset CreateEditorRuleset() => new ManiaRuleset(); - private void addStepClickLink(string timestamp, string step = "", bool displayTimestamp = true) - { - AddStep(displayTimestamp ? $"{step} {timestamp}" : step, () => Editor.HandleTimestamp(timestamp)); - AddUntilStep("wait for seek", () => EditorClock.SeekingOrStopped.Value); - } - - private void addReset() - { - addStepClickLink("00:00:000", "reset", false); - } - - private bool checkSnapAndSelectColumn(double startTime, IReadOnlyCollection<(int, int)>? columnPairs = null) - { - bool checkColumns = columnPairs != null - ? EditorBeatmap.SelectedHitObjects.All(x => columnPairs.Any(col => isNoteAt(x, col.Item1, col.Item2))) - : !EditorBeatmap.SelectedHitObjects.Any(); - - return EditorClock.CurrentTime == startTime - && EditorBeatmap.SelectedHitObjects.Count == (columnPairs?.Count ?? 0) - && checkColumns; - } - - private bool isNoteAt(HitObject hitObject, double time, int column) - { - return hitObject is ManiaHitObject maniaHitObject - && maniaHitObject.StartTime == time - && maniaHitObject.Column == column; - } - [Test] public void TestNormalSelection() { @@ -95,5 +66,29 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor addStepClickLink("00:00:000 (1,2)", "std link"); AddAssert("snap to 1, select none", () => checkSnapAndSelectColumn(2_170)); } + + private void addStepClickLink(string timestamp, string step = "", bool displayTimestamp = true) + { + AddStep(displayTimestamp ? $"{step} {timestamp}" : step, () => Editor.HandleTimestamp(timestamp)); + AddUntilStep("wait for seek", () => EditorClock.SeekingOrStopped.Value); + } + + private void addReset() => addStepClickLink("00:00:000", "reset", false); + + private bool checkSnapAndSelectColumn(double startTime, IReadOnlyCollection<(int, int)>? columnPairs = null) + { + bool checkColumns = columnPairs != null + ? EditorBeatmap.SelectedHitObjects.All(x => columnPairs.Any(col => isNoteAt(x, col.Item1, col.Item2))) + : !EditorBeatmap.SelectedHitObjects.Any(); + + return EditorClock.CurrentTime == startTime + && EditorBeatmap.SelectedHitObjects.Count == (columnPairs?.Count ?? 0) + && checkColumns; + } + + private bool isNoteAt(HitObject hitObject, double time, int column) => + hitObject is ManiaHitObject maniaHitObject + && maniaHitObject.StartTime == time + && maniaHitObject.Column == column; } } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOpenEditorTimestampInOsu.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOpenEditorTimestampInOsu.cs index 753400e10e..943858652c 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOpenEditorTimestampInOsu.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOpenEditorTimestampInOsu.cs @@ -15,40 +15,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); - private void addStepClickLink(string timestamp, string step = "", bool displayTimestamp = true) - { - AddStep(displayTimestamp ? $"{step} {timestamp}" : step, () => Editor.HandleTimestamp(timestamp)); - AddUntilStep("wait for seek", () => EditorClock.SeekingOrStopped.Value); - } - - private void addReset() - { - addStepClickLink("00:00:000", "reset", false); - } - - private void checkSelection(Func startTime, params int[] comboNumbers) - => AddUntilStep($"seeked & selected {(comboNumbers.Any() ? string.Join(",", comboNumbers) : "nothing")}", () => - { - bool checkCombos = comboNumbers.Any() - ? hasCombosInOrder(EditorBeatmap.SelectedHitObjects, comboNumbers) - : !EditorBeatmap.SelectedHitObjects.Any(); - - return EditorClock.CurrentTime == startTime() - && EditorBeatmap.SelectedHitObjects.Count == comboNumbers.Length - && checkCombos; - }); - - private bool hasCombosInOrder(IEnumerable selected, params int[] comboNumbers) - { - List hitObjects = selected.ToList(); - if (hitObjects.Count != comboNumbers.Length) - return false; - - return !hitObjects.Select(x => (OsuHitObject)x) - .Where((x, i) => x.IndexInCurrentCombo + 1 != comboNumbers[i]) - .Any(); - } - [Test] public void TestNormalSelection() { @@ -90,5 +56,36 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor addStepClickLink("00:00:956 (956|1,956|2)", "mania link"); checkSelection(() => firstObject.StartTime); } + + private void addReset() => addStepClickLink("00:00:000", "reset", false); + + private void addStepClickLink(string timestamp, string step = "", bool displayTimestamp = true) + { + AddStep(displayTimestamp ? $"{step} {timestamp}" : step, () => Editor.HandleTimestamp(timestamp)); + AddUntilStep("wait for seek", () => EditorClock.SeekingOrStopped.Value); + } + + private void checkSelection(Func startTime, params int[] comboNumbers) + => AddUntilStep($"seeked & selected {(comboNumbers.Any() ? string.Join(",", comboNumbers) : "nothing")}", () => + { + bool checkCombos = comboNumbers.Any() + ? hasCombosInOrder(EditorBeatmap.SelectedHitObjects, comboNumbers) + : !EditorBeatmap.SelectedHitObjects.Any(); + + return EditorClock.CurrentTime == startTime() + && EditorBeatmap.SelectedHitObjects.Count == comboNumbers.Length + && checkCombos; + }); + + private bool hasCombosInOrder(IEnumerable selected, params int[] comboNumbers) + { + List hitObjects = selected.ToList(); + if (hitObjects.Count != comboNumbers.Length) + return false; + + return !hitObjects.Select(x => (OsuHitObject)x) + .Where((x, i) => x.IndexInCurrentCombo + 1 != comboNumbers[i]) + .Any(); + } } } diff --git a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs index 86ceb73f7a..dacc5887d0 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs @@ -25,58 +25,6 @@ namespace osu.Game.Tests.Visual.Editing private EditorBeatmap editorBeatmap => editor.ChildrenOfType().Single(); private EditorClock editorClock => editor.ChildrenOfType().Single(); - private void addStepClickLink(string timestamp, string step = "", bool waitForSeek = true) - { - AddStep($"{step} {timestamp}", () => - Game.HandleLink(new LinkDetails(LinkAction.OpenEditorTimestamp, timestamp)) - ); - - if (waitForSeek) - AddUntilStep("wait for seek", () => editorClock.SeekingOrStopped.Value); - } - - private void addStepScreenModeTo(EditorScreenMode screenMode) - { - AddStep("change screen to " + screenMode, () => editor.Mode.Value = screenMode); - } - - private void assertOnScreenAt(EditorScreenMode screen, double time, string text = "stayed in") - { - AddAssert($"{text} {screen} at {time}", () => - editor.Mode.Value == screen - && editorClock.CurrentTime == time - ); - } - - private void assertMovedScreenTo(EditorScreenMode screen, string text = "moved to") - { - AddAssert($"{text} {screen}", () => editor.Mode.Value == screen); - } - - private void setUpEditor(RulesetInfo ruleset) - { - BeatmapSetInfo beatmapSet = null!; - - AddStep("Import test beatmap", () => - Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely() - ); - AddStep("Retrieve beatmap", () => - beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach() - ); - AddStep("Present beatmap", () => Game.PresentBeatmap(beatmapSet)); - AddUntilStep("Wait for song select", () => - Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) - && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect - && songSelect.IsLoaded - ); - AddStep("Switch ruleset", () => Game.Ruleset.Value = ruleset); - AddStep("Open editor for ruleset", () => - ((PlaySongSelect)Game.ScreenStack.CurrentScreen) - .Edit(beatmapSet.Beatmaps.Last(beatmap => beatmap.Ruleset.Name == ruleset.Name)) - ); - AddUntilStep("Wait for editor open", () => editor.ReadyForUse); - } - [Test] public void TestErrorNotifications() { @@ -151,5 +99,53 @@ namespace osu.Game.Tests.Visual.Editing addStepClickLink("00:00:000"); assertOnScreenAt(EditorScreenMode.Compose, 0); } + + private void addStepClickLink(string timestamp, string step = "", bool waitForSeek = true) + { + AddStep($"{step} {timestamp}", () => + Game.HandleLink(new LinkDetails(LinkAction.OpenEditorTimestamp, timestamp)) + ); + + if (waitForSeek) + AddUntilStep("wait for seek", () => editorClock.SeekingOrStopped.Value); + } + + private void addStepScreenModeTo(EditorScreenMode screenMode) => + AddStep("change screen to " + screenMode, () => editor.Mode.Value = screenMode); + + private void assertOnScreenAt(EditorScreenMode screen, double time) + { + AddAssert($"stayed on {screen} at {time}", () => + editor.Mode.Value == screen + && editorClock.CurrentTime == time + ); + } + + private void assertMovedScreenTo(EditorScreenMode screen, string text = "moved to") => + AddAssert($"{text} {screen}", () => editor.Mode.Value == screen); + + private void setUpEditor(RulesetInfo ruleset) + { + BeatmapSetInfo beatmapSet = null!; + + AddStep("Import test beatmap", () => + Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely() + ); + AddStep("Retrieve beatmap", () => + beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach() + ); + AddStep("Present beatmap", () => Game.PresentBeatmap(beatmapSet)); + AddUntilStep("Wait for song select", () => + Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) + && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect + && songSelect.IsLoaded + ); + AddStep("Switch ruleset", () => Game.Ruleset.Value = ruleset); + AddStep("Open editor for ruleset", () => + ((PlaySongSelect)Game.ScreenStack.CurrentScreen) + .Edit(beatmapSet.Beatmaps.Last(beatmap => beatmap.Ruleset.Name == ruleset.Name)) + ); + AddUntilStep("Wait for editor open", () => editor.ReadyForUse); + } } } From 917a68eac3273f969a7b760cd7a258e475fcc23e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 15:08:15 +0900 Subject: [PATCH 1609/4092] Adjust localisablel strings and keys --- .../Visual/Editing/TestSceneOpenEditorTimestamp.cs | 8 ++++---- osu.Game/Localisation/EditorStrings.cs | 6 +++--- osu.Game/OsuGame.cs | 2 +- osu.Game/Screens/Edit/Editor.cs | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs index dacc5887d0..b6f89ee4e7 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Editing addStepClickLink("00:00:000", waitForSeek: false); AddAssert("received 'must be in edit'", - () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEdit), + () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEditorToHandleLinks), () => Is.EqualTo(1)); AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo.Invoke()); @@ -40,7 +40,7 @@ namespace osu.Game.Tests.Visual.Editing addStepClickLink("00:00:000 (1)", waitForSeek: false); AddAssert("received 'must be in edit'", - () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEdit), + () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEditorToHandleLinks), () => Is.EqualTo(2)); setUpEditor(rulesetInfo); @@ -48,12 +48,12 @@ namespace osu.Game.Tests.Visual.Editing addStepClickLink("00:000", "invalid link", waitForSeek: false); AddAssert("received 'failed to process'", - () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp), + () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToParseEditorLink), () => Is.EqualTo(1)); addStepClickLink("50000:00:000", "too long link", waitForSeek: false); AddAssert("received 'failed to process'", - () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToProcessTimestamp), + () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToParseEditorLink), () => Is.EqualTo(2)); } diff --git a/osu.Game/Localisation/EditorStrings.cs b/osu.Game/Localisation/EditorStrings.cs index e762455e8b..b20b5bc65a 100644 --- a/osu.Game/Localisation/EditorStrings.cs +++ b/osu.Game/Localisation/EditorStrings.cs @@ -122,12 +122,12 @@ namespace osu.Game.Localisation /// /// "Must be in edit mode to handle editor links" /// - public static LocalisableString MustBeInEdit => new TranslatableString(getKey(@"must_be_in_edit"), @"Must be in edit mode to handle editor links"); + public static LocalisableString MustBeInEditorToHandleLinks => new TranslatableString(getKey(@"must_be_in_editor_to_handle_links"), @"Must be in edit mode to handle editor links"); /// - /// "Failed to process timestamp" + /// "Failed to parse editor link" /// - public static LocalisableString FailedToProcessTimestamp => new TranslatableString(getKey(@"failed_to_process_timestamp"), @"Failed to process timestamp"); + public static LocalisableString FailedToParseEditorLink => new TranslatableString(getKey(@"failed_to_parse_edtior_link"), @"Failed to parse editor link"); private static string getKey(string key) => $@"{prefix}:{key}"; } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 3fda18b7cb..80a9853965 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -565,7 +565,7 @@ namespace osu.Game Schedule(() => Notifications.Post(new SimpleNotification { Icon = FontAwesome.Solid.ExclamationTriangle, - Text = EditorStrings.MustBeInEdit + Text = EditorStrings.MustBeInEditorToHandleLinks })); return; } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index fb34767315..7dd2666a7e 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1146,7 +1146,7 @@ namespace osu.Game.Screens.Edit Schedule(() => notifications?.Post(new SimpleNotification { Icon = FontAwesome.Solid.ExclamationTriangle, - Text = EditorStrings.FailedToProcessTimestamp + Text = EditorStrings.FailedToParseEditorLink })); return; } From 7c5345bf7e88ba364cd3606e424f4ae45b6760f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 15:09:33 +0900 Subject: [PATCH 1610/4092] Use `SimpleErrorNotification` for error display --- osu.Game/OsuGame.cs | 2 +- osu.Game/Screens/Edit/Editor.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 80a9853965..c4f93636e9 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -562,7 +562,7 @@ namespace osu.Game { if (ScreenStack.CurrentScreen is not Editor editor) { - Schedule(() => Notifications.Post(new SimpleNotification + Schedule(() => Notifications.Post(new SimpleErrorNotification { Icon = FontAwesome.Solid.ExclamationTriangle, Text = EditorStrings.MustBeInEditorToHandleLinks diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 7dd2666a7e..b3cebb41de 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1143,7 +1143,7 @@ namespace osu.Game.Screens.Edit { if (!EditorTimestampParser.TryParse(timestamp, out var timeSpan, out string selection)) { - Schedule(() => notifications?.Post(new SimpleNotification + Schedule(() => notifications?.Post(new SimpleErrorNotification { Icon = FontAwesome.Solid.ExclamationTriangle, Text = EditorStrings.FailedToParseEditorLink From 1c612e2e0c49e3d7ca01f562b091f791ea34f7cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 13 Nov 2023 14:35:07 +0900 Subject: [PATCH 1611/4092] Implement client-side disconnection flow --- osu.Game/Online/HubClientConnector.cs | 2 ++ osu.Game/Online/IHubClientConnector.cs | 5 +++++ osu.Game/Online/IStatefulUserHubClient.cs | 18 ++++++++++++++++++ .../Online/Multiplayer/IMultiplayerClient.cs | 2 +- .../Online/Multiplayer/MultiplayerClient.cs | 8 ++++++++ .../Multiplayer/OnlineMultiplayerClient.cs | 9 +++++++++ .../PersistentEndpointClientConnector.cs | 2 ++ osu.Game/Online/Spectator/ISpectatorClient.cs | 2 +- .../Online/Spectator/OnlineSpectatorClient.cs | 9 +++++++++ osu.Game/Online/Spectator/SpectatorClient.cs | 8 ++++++++ .../Multiplayer/TestMultiplayerClient.cs | 6 ++++++ .../Visual/Spectator/TestSpectatorClient.cs | 9 ++++++++- 12 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 osu.Game/Online/IStatefulUserHubClient.cs diff --git a/osu.Game/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs index 8fd79bd703..f5a8678613 100644 --- a/osu.Game/Online/HubClientConnector.cs +++ b/osu.Game/Online/HubClientConnector.cs @@ -102,6 +102,8 @@ namespace osu.Game.Online return Task.FromResult((PersistentEndpointClient)new HubClient(newConnection)); } + Task IHubClientConnector.Disconnect() => base.Disconnect(); + protected override string ClientName { get; } } } diff --git a/osu.Game/Online/IHubClientConnector.cs b/osu.Game/Online/IHubClientConnector.cs index 53c4897e73..052972e6b4 100644 --- a/osu.Game/Online/IHubClientConnector.cs +++ b/osu.Game/Online/IHubClientConnector.cs @@ -30,6 +30,11 @@ namespace osu.Game.Online ///
public Action? ConfigureConnection { get; set; } + /// + /// Forcefully disconnects the client from the server. + /// + Task Disconnect(); + /// /// Reconnect if already connected. /// diff --git a/osu.Game/Online/IStatefulUserHubClient.cs b/osu.Game/Online/IStatefulUserHubClient.cs new file mode 100644 index 0000000000..86105dd629 --- /dev/null +++ b/osu.Game/Online/IStatefulUserHubClient.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 System.Threading.Tasks; + +namespace osu.Game.Online +{ + /// + /// Common interface for clients of "stateful user hubs", i.e. server-side hubs + /// that preserve user state. + /// In the case of such hubs, concurrency constraints are enforced (only one client + /// can be connected at a time). + /// + public interface IStatefulUserHubClient + { + Task DisconnectRequested(); + } +} diff --git a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs index 327fb0d76a..a5fa49a94b 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs @@ -13,7 +13,7 @@ namespace osu.Game.Online.Multiplayer /// /// An interface defining a multiplayer client instance. /// - public interface IMultiplayerClient + public interface IMultiplayerClient : IStatefulUserHubClient { /// /// Signals that the room has changed state. diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 515a0dda08..4b351663d8 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -357,6 +357,8 @@ namespace osu.Game.Online.Multiplayer public abstract Task ChangeBeatmapAvailability(BeatmapAvailability newBeatmapAvailability); + public abstract Task DisconnectInternal(); + /// /// Change the local user's mods in the currently joined room. /// @@ -876,5 +878,11 @@ namespace osu.Game.Online.Multiplayer return tcs.Task; } + + Task IStatefulUserHubClient.DisconnectRequested() + { + Schedule(() => DisconnectInternal()); + return Task.CompletedTask; + } } } diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index 20ec030eac..e400132693 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -68,6 +68,7 @@ namespace osu.Game.Online.Multiplayer connection.On(nameof(IMultiplayerClient.PlaylistItemAdded), ((IMultiplayerClient)this).PlaylistItemAdded); connection.On(nameof(IMultiplayerClient.PlaylistItemRemoved), ((IMultiplayerClient)this).PlaylistItemRemoved); connection.On(nameof(IMultiplayerClient.PlaylistItemChanged), ((IMultiplayerClient)this).PlaylistItemChanged); + connection.On(nameof(IStatefulUserHubClient.DisconnectRequested), ((IMultiplayerClient)this).DisconnectRequested); }; IsConnected.BindTo(connector.IsConnected); @@ -255,6 +256,14 @@ namespace osu.Game.Online.Multiplayer return connection.InvokeAsync(nameof(IMultiplayerServer.RemovePlaylistItem), playlistItemId); } + public override Task DisconnectInternal() + { + if (connector == null) + return Task.CompletedTask; + + return connector.Disconnect(); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); diff --git a/osu.Game/Online/PersistentEndpointClientConnector.cs b/osu.Game/Online/PersistentEndpointClientConnector.cs index e33924047d..8c1b58a750 100644 --- a/osu.Game/Online/PersistentEndpointClientConnector.cs +++ b/osu.Game/Online/PersistentEndpointClientConnector.cs @@ -159,6 +159,8 @@ namespace osu.Game.Online await Task.Run(connect, default).ConfigureAwait(false); } + protected Task Disconnect() => disconnect(true); + private async Task disconnect(bool takeLock) { cancelExistingConnect(); diff --git a/osu.Game/Online/Spectator/ISpectatorClient.cs b/osu.Game/Online/Spectator/ISpectatorClient.cs index 9605604966..2dc2283c23 100644 --- a/osu.Game/Online/Spectator/ISpectatorClient.cs +++ b/osu.Game/Online/Spectator/ISpectatorClient.cs @@ -8,7 +8,7 @@ namespace osu.Game.Online.Spectator /// /// An interface defining a spectator client instance. /// - public interface ISpectatorClient + public interface ISpectatorClient : IStatefulUserHubClient { /// /// Signals that a user has begun a new play session. diff --git a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs index 3118e05053..cd68abdea6 100644 --- a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs +++ b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs @@ -42,6 +42,7 @@ namespace osu.Game.Online.Spectator connection.On(nameof(ISpectatorClient.UserSentFrames), ((ISpectatorClient)this).UserSentFrames); connection.On(nameof(ISpectatorClient.UserFinishedPlaying), ((ISpectatorClient)this).UserFinishedPlaying); connection.On(nameof(ISpectatorClient.UserScoreProcessed), ((ISpectatorClient)this).UserScoreProcessed); + connection.On(nameof(IStatefulUserHubClient.DisconnectRequested), ((IStatefulUserHubClient)this).DisconnectRequested); }; IsConnected.BindTo(connector.IsConnected); @@ -113,5 +114,13 @@ namespace osu.Game.Online.Spectator return connection.InvokeAsync(nameof(ISpectatorServer.EndWatchingUser), userId); } + + protected override Task DisconnectInternal() + { + if (connector == null) + return Task.CompletedTask; + + return connector.Disconnect(); + } } } diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 14e137caf1..9c78f27e15 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -174,6 +174,12 @@ namespace osu.Game.Online.Spectator return Task.CompletedTask; } + Task IStatefulUserHubClient.DisconnectRequested() + { + Schedule(() => DisconnectInternal()); + return Task.CompletedTask; + } + public void BeginPlaying(long? scoreToken, GameplayState state, Score score) { // This schedule is only here to match the one below in `EndPlaying`. @@ -291,6 +297,8 @@ namespace osu.Game.Online.Spectator protected abstract Task StopWatchingUserInternal(int userId); + protected abstract Task DisconnectInternal(); + protected override void Update() { base.Update(); diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 6007c7c076..577104db45 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -658,5 +658,11 @@ namespace osu.Game.Tests.Visual.Multiplayer PlayedAt = item.PlayedAt, StarRating = item.Beatmap.StarRating, }; + + public override Task DisconnectInternal() + { + isConnected.Value = false; + return Task.CompletedTask; + } } } diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index 5db08810ca..ce2eee8aa4 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -33,7 +33,8 @@ namespace osu.Game.Tests.Visual.Spectator public int FrameSendAttempts { get; private set; } - public override IBindable IsConnected { get; } = new Bindable(true); + public override IBindable IsConnected => isConnected; + private readonly BindableBool isConnected = new BindableBool(true); public IReadOnlyDictionary LastReceivedUserFrames => lastReceivedUserFrames; @@ -179,5 +180,11 @@ namespace osu.Game.Tests.Visual.Spectator State = SpectatedUserState.Playing }); } + + protected override Task DisconnectInternal() + { + isConnected.Value = false; + return Task.CompletedTask; + } } } From 2391035e4969ac3d1a6a505cd5b73ef0e71fc7db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 21 Nov 2023 14:33:18 +0900 Subject: [PATCH 1612/4092] Remove redundant `api` field from `HubClientConnector` --- osu.Game/Online/HubClientConnector.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs index f5a8678613..e7494e50cc 100644 --- a/osu.Game/Online/HubClientConnector.cs +++ b/osu.Game/Online/HubClientConnector.cs @@ -27,7 +27,6 @@ namespace osu.Game.Online private readonly string endpoint; private readonly string versionHash; private readonly bool preferMessagePack; - private readonly IAPIProvider api; /// /// The current connection opened by this connector. @@ -47,7 +46,6 @@ namespace osu.Game.Online { ClientName = clientName; this.endpoint = endpoint; - this.api = api; this.versionHash = versionHash; this.preferMessagePack = preferMessagePack; @@ -70,7 +68,7 @@ namespace osu.Game.Online options.Proxy.Credentials = CredentialCache.DefaultCredentials; } - options.Headers.Add("Authorization", $"Bearer {api.AccessToken}"); + options.Headers.Add("Authorization", $"Bearer {API.AccessToken}"); options.Headers.Add("OsuVersionHash", versionHash); }); From 42fada578e35418b23d17d5607e54d9e79ca0b9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 21 Nov 2023 14:39:33 +0900 Subject: [PATCH 1613/4092] Centralise and improve messaging around online state When the server requests a disconnect due to a user connecting via a second device, the client will now log the user out on the first device and show a notification informing them of the cause of disconnection. --- osu.Game/Online/HubClientConnector.cs | 6 +- .../Online/Multiplayer/MultiplayerClient.cs | 15 ++- osu.Game/Online/OnlineStatusNotifier.cs | 120 ++++++++++++++++++ .../Online/Spectator/OnlineSpectatorClient.cs | 10 +- osu.Game/Online/Spectator/SpectatorClient.cs | 11 +- osu.Game/OsuGame.cs | 1 + .../Screens/OnlinePlay/OnlinePlayScreen.cs | 5 +- .../Visual/Spectator/TestSpectatorClient.cs | 4 +- 8 files changed, 155 insertions(+), 17 deletions(-) create mode 100644 osu.Game/Online/OnlineStatusNotifier.cs diff --git a/osu.Game/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs index e7494e50cc..9d414deade 100644 --- a/osu.Game/Online/HubClientConnector.cs +++ b/osu.Game/Online/HubClientConnector.cs @@ -100,7 +100,11 @@ namespace osu.Game.Online return Task.FromResult((PersistentEndpointClient)new HubClient(newConnection)); } - Task IHubClientConnector.Disconnect() => base.Disconnect(); + async Task IHubClientConnector.Disconnect() + { + await Disconnect().ConfigureAwait(false); + API.Logout(); + } protected override string ClientName { get; } } diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 4b351663d8..79f46c2095 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -12,7 +12,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Development; using osu.Framework.Graphics; -using osu.Framework.Logging; using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; @@ -88,6 +87,11 @@ namespace osu.Game.Online.Multiplayer /// public event Action? ResultsReady; + /// + /// Invoked just prior to disconnection requested by the server via . + /// + public event Action? Disconnecting; + /// /// Whether the is currently connected. /// This is NOT thread safe and usage should be scheduled. @@ -155,10 +159,7 @@ namespace osu.Game.Online.Multiplayer { // clean up local room state on server disconnect. if (!connected.NewValue && Room != null) - { - Logger.Log("Clearing room due to multiplayer server connection loss.", LoggingTarget.Runtime, LogLevel.Important); LeaveRoom(); - } })); } @@ -881,7 +882,11 @@ namespace osu.Game.Online.Multiplayer Task IStatefulUserHubClient.DisconnectRequested() { - Schedule(() => DisconnectInternal()); + Schedule(() => + { + Disconnecting?.Invoke(); + DisconnectInternal(); + }); return Task.CompletedTask; } } diff --git a/osu.Game/Online/OnlineStatusNotifier.cs b/osu.Game/Online/OnlineStatusNotifier.cs new file mode 100644 index 0000000000..0cf672ac3c --- /dev/null +++ b/osu.Game/Online/OnlineStatusNotifier.cs @@ -0,0 +1,120 @@ +// 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.Bindables; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Screens; +using osu.Game.Online.API; +using osu.Game.Online.Multiplayer; +using osu.Game.Online.Spectator; +using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; +using osu.Game.Screens.OnlinePlay; + +namespace osu.Game.Online +{ + public partial class OnlineStatusNotifier : Component + { + private readonly Func getCurrentScreen; + + [Resolved] + private MultiplayerClient multiplayerClient { get; set; } = null!; + + [Resolved] + private SpectatorClient spectatorClient { get; set; } = null!; + + [Resolved] + private INotificationOverlay? notificationOverlay { get; set; } + + private IBindable apiState = null!; + private IBindable multiplayerState = null!; + private IBindable spectatorState = null!; + private bool forcedDisconnection; + + public OnlineStatusNotifier(Func getCurrentScreen) + { + this.getCurrentScreen = getCurrentScreen; + } + + [BackgroundDependencyLoader] + private void load(IAPIProvider api) + { + apiState = api.State.GetBoundCopy(); + multiplayerState = multiplayerClient.IsConnected.GetBoundCopy(); + spectatorState = spectatorClient.IsConnected.GetBoundCopy(); + + multiplayerClient.Disconnecting += notifyAboutForcedDisconnection; + spectatorClient.Disconnecting += notifyAboutForcedDisconnection; + } + + private void notifyAboutForcedDisconnection() + { + if (forcedDisconnection) + return; + + forcedDisconnection = true; + notificationOverlay?.Post(new SimpleErrorNotification + { + Icon = FontAwesome.Solid.ExclamationCircle, + Text = "You have been logged out on this device due to a login to your account on another device." + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + apiState.BindValueChanged(_ => + { + if (apiState.Value == APIState.Online) + forcedDisconnection = false; + + Scheduler.AddOnce(updateState); + }); + multiplayerState.BindValueChanged(_ => Scheduler.AddOnce(updateState)); + spectatorState.BindValueChanged(_ => Scheduler.AddOnce(updateState)); + } + + private void updateState() + { + if (forcedDisconnection) + return; + + if (apiState.Value == APIState.Offline && getCurrentScreen() is OnlinePlayScreen) + { + notificationOverlay?.Post(new SimpleErrorNotification + { + Icon = FontAwesome.Solid.ExclamationCircle, + Text = "API connection was lost. Can't continue with online play." + }); + return; + } + + if (!multiplayerClient.IsConnected.Value && multiplayerClient.Room != null) + { + notificationOverlay?.Post(new SimpleErrorNotification + { + Icon = FontAwesome.Solid.ExclamationCircle, + Text = "Connection to the multiplayer server was lost. Exiting multiplayer." + }); + } + + // TODO: handle spectator server failure somehow? + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (spectatorClient.IsNotNull()) + spectatorClient.Disconnecting -= notifyAboutForcedDisconnection; + + if (multiplayerClient.IsNotNull()) + multiplayerClient.Disconnecting -= notifyAboutForcedDisconnection; + } + } +} diff --git a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs index cd68abdea6..036cfa1d76 100644 --- a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs +++ b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs @@ -115,12 +115,14 @@ namespace osu.Game.Online.Spectator return connection.InvokeAsync(nameof(ISpectatorServer.EndWatchingUser), userId); } - protected override Task DisconnectInternal() + protected override async Task DisconnectInternal() { - if (connector == null) - return Task.CompletedTask; + await base.DisconnectInternal().ConfigureAwait(false); - return connector.Disconnect(); + if (connector == null) + return; + + await connector.Disconnect().ConfigureAwait(false); } } } diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 9c78f27e15..ca4ec52f4a 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -70,6 +70,11 @@ namespace osu.Game.Online.Spectator /// public virtual event Action? OnUserScoreProcessed; + /// + /// Invoked just prior to disconnection requested by the server via . + /// + public event Action? Disconnecting; + /// /// A dictionary containing all users currently being watched, with the number of watching components for each user. /// @@ -297,7 +302,11 @@ namespace osu.Game.Online.Spectator protected abstract Task StopWatchingUserInternal(int userId); - protected abstract Task DisconnectInternal(); + protected virtual Task DisconnectInternal() + { + Disconnecting?.Invoke(); + return Task.CompletedTask; + } protected override void Update() { diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 2f11964f6a..ed4bd21e93 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1054,6 +1054,7 @@ namespace osu.Game Add(difficultyRecommender); Add(externalLinkOpener = new ExternalLinkOpener()); Add(new MusicKeyBindingHandler()); + Add(new OnlineStatusNotifier(() => ScreenStack.CurrentScreen)); // side overlays which cancel each other. var singleDisplaySideOverlays = new OverlayContainer[] { Settings, Notifications, FirstRunOverlay }; diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs index f652e88f5a..9de458b5c6 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs @@ -71,7 +71,7 @@ namespace osu.Game.Screens.OnlinePlay screenStack = new OnlinePlaySubScreenStack { RelativeSizeAxes = Axes.Both }, new Header(ScreenTitle, screenStack), RoomManager, - ongoingOperationTracker + ongoingOperationTracker, } }; } @@ -79,10 +79,7 @@ namespace osu.Game.Screens.OnlinePlay private void onlineStateChanged(ValueChangedEvent state) => Schedule(() => { if (state.NewValue != APIState.Online) - { - Logger.Log("API connection was lost, can't continue with online play", LoggingTarget.Network, LogLevel.Important); Schedule(forcefullyExit); - } }); protected override void LoadComplete() diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index ce2eee8aa4..5aef85fa13 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -181,10 +181,10 @@ namespace osu.Game.Tests.Visual.Spectator }); } - protected override Task DisconnectInternal() + protected override async Task DisconnectInternal() { + await base.DisconnectInternal().ConfigureAwait(false); isConnected.Value = false; - return Task.CompletedTask; } } } From aa3ff151c095ec76a15d4fc8d5b6d021d2ff52e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 21 Nov 2023 14:41:03 +0900 Subject: [PATCH 1614/4092] Fix `RoomManager` attempting to part room when not online --- osu.Game/Screens/OnlinePlay/Components/RoomManager.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs index 539d5b74b3..e892f9280f 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs @@ -98,7 +98,9 @@ namespace osu.Game.Screens.OnlinePlay.Components if (JoinedRoom.Value == null) return; - api.Queue(new PartRoomRequest(joinedRoom.Value)); + if (api.State.Value == APIState.Online) + api.Queue(new PartRoomRequest(joinedRoom.Value)); + joinedRoom.Value = null; } From 314a7bf6f1058195ecf5995fc18a5e5bc1eb94fe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 11:33:05 +0900 Subject: [PATCH 1615/4092] Simplify `AddOnce` call to avoid `self` argument --- .../Sliders/SliderPlacementBlueprint.cs | 41 +++++++++---------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 5f072eb171..32fb7ad351 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -3,10 +3,12 @@ #nullable disable +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Events; @@ -84,16 +86,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { freehandToolboxGroup.Tolerance.BindValueChanged(e => { - if (bSplineBuilder.Tolerance != e.NewValue) - bSplineBuilder.Tolerance = e.NewValue; - updateSliderPathFromBSplineBuilder(); + bSplineBuilder.Tolerance = e.NewValue; + Scheduler.AddOnce(updateSliderPathFromBSplineBuilder); }, true); freehandToolboxGroup.CornerThreshold.BindValueChanged(e => { - if (bSplineBuilder.CornerThreshold != e.NewValue) - bSplineBuilder.CornerThreshold = e.NewValue; - updateSliderPathFromBSplineBuilder(); + bSplineBuilder.CornerThreshold = e.NewValue; + Scheduler.AddOnce(updateSliderPathFromBSplineBuilder); }, true); } } @@ -197,27 +197,24 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders base.OnDrag(e); bSplineBuilder.AddLinearPoint(ToLocalSpace(e.ScreenSpaceMousePosition) - HitObject.Position); - updateSliderPathFromBSplineBuilder(); + Scheduler.AddOnce(updateSliderPathFromBSplineBuilder); } private void updateSliderPathFromBSplineBuilder() { - Scheduler.AddOnce(static self => - { - var cps = self.bSplineBuilder.ControlPoints; - var sliderCps = self.HitObject.Path.ControlPoints; - sliderCps.RemoveRange(1, sliderCps.Count - 1); + IReadOnlyList builderPoints = bSplineBuilder.ControlPoints; + BindableList sliderPoints = HitObject.Path.ControlPoints; - // Add the control points from the BSpline builder while converting control points that repeat - // three or more times to a single PathControlPoint with linear type. - for (int i = 1; i < cps.Count; i++) - { - bool isSharp = i < cps.Count - 2 && cps[i] == cps[i + 1] && cps[i] == cps[i + 2]; - sliderCps.Add(new PathControlPoint(cps[i], isSharp ? PathType.BSpline(3) : null)); - if (isSharp) - i += 2; - } - }, this); + sliderPoints.RemoveRange(1, sliderPoints.Count - 1); + + // Add the control points from the BSpline builder while converting control points that repeat + // three or more times to a single PathControlPoint with linear type. + for (int i = 1; i < builderPoints.Count; i++) + { + bool isSharp = i < builderPoints.Count - 2 && builderPoints[i] == builderPoints[i + 1] && builderPoints[i] == builderPoints[i + 2]; + sliderPoints.Add(new PathControlPoint(builderPoints[i], isSharp ? PathType.BSpline(3) : null)); + if (isSharp) i += 2; + } } protected override void OnDragEnd(DragEndEvent e) From 0a5444d091de73c2cc9fe8360146248d1b06b39e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 13:05:31 +0900 Subject: [PATCH 1616/4092] Fix using the incorrect position for the first point --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 32fb7ad351..68f5b2d70f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -180,7 +180,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders return false; bSplineBuilder.Clear(); - bSplineBuilder.AddLinearPoint(ToLocalSpace(e.ScreenSpaceMousePosition) - HitObject.Position); + bSplineBuilder.AddLinearPoint(ToLocalSpace(e.ScreenSpaceMouseDownPosition) - HitObject.Position); setState(SliderPlacementState.Drawing); return true; } From e69e78ad4123a4d89915aa44286a8e637e49429d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 13:10:44 +0900 Subject: [PATCH 1617/4092] Refactor b-spline path conversion code to better handle linear segments --- .../Sliders/SliderPlacementBlueprint.cs | 44 +++++++++++++++---- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 68f5b2d70f..6e9cc26af4 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -8,7 +8,6 @@ using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Events; @@ -203,17 +202,44 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void updateSliderPathFromBSplineBuilder() { IReadOnlyList builderPoints = bSplineBuilder.ControlPoints; - BindableList sliderPoints = HitObject.Path.ControlPoints; - sliderPoints.RemoveRange(1, sliderPoints.Count - 1); + if (builderPoints.Count == 0) + return; - // Add the control points from the BSpline builder while converting control points that repeat - // three or more times to a single PathControlPoint with linear type. - for (int i = 1; i < builderPoints.Count; i++) + int lastSegmentStart = 0; + PathType? lastPathType = null; + + HitObject.Path.ControlPoints.Clear(); + + // Iterate through generated points, finding each segment and adding non-inheriting path types where appropriate. + // Importantly, the B-Spline builder returns three Vector2s at the same location when a new segment is to be started. + for (int i = 0; i < builderPoints.Count; i++) { - bool isSharp = i < builderPoints.Count - 2 && builderPoints[i] == builderPoints[i + 1] && builderPoints[i] == builderPoints[i + 2]; - sliderPoints.Add(new PathControlPoint(builderPoints[i], isSharp ? PathType.BSpline(3) : null)); - if (isSharp) i += 2; + bool isLastPoint = i == builderPoints.Count - 1; + bool isNewSegment = i < builderPoints.Count - 2 && builderPoints[i] == builderPoints[i + 1] && builderPoints[i] == builderPoints[i + 2]; + + if (isNewSegment || isLastPoint) + { + int pointsInSegment = i - lastSegmentStart; + + // Where possible, we can use the simpler LINEAR path type. + PathType? pathType = pointsInSegment == 1 ? PathType.LINEAR : PathType.BSpline(3); + + // Linear segments can be combined, as two adjacent linear sections are computationally the same as one with the points combined. + if (lastPathType == pathType && lastPathType == PathType.LINEAR) + pathType = null; + + HitObject.Path.ControlPoints.Add(new PathControlPoint(builderPoints[lastSegmentStart], pathType)); + for (int j = lastSegmentStart + 1; j < i; j++) + HitObject.Path.ControlPoints.Add(new PathControlPoint(builderPoints[j])); + + if (isLastPoint) + HitObject.Path.ControlPoints.Add(new PathControlPoint(builderPoints[i])); + + // Skip the redundant duplicated points (see isNewSegment above) which have been coalesced into a path type. + lastSegmentStart = (i += 2); + if (pathType != null) lastPathType = pathType; + } } } From 5175464c18dafc3931b723ae06dbb4375c7c0a77 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 13:35:04 +0900 Subject: [PATCH 1618/4092] Update test coverage (and add test coverage of curve drawing) --- .../TestSceneSliderPlacementBlueprint.cs | 37 ++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index c7a21ba689..8498263138 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using NUnit.Framework; using osu.Framework.Utils; using osu.Game.Rulesets.Edit; @@ -274,7 +275,30 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor } [Test] - public void TestSliderDrawing() + public void TestSliderDrawingCurve() + { + Vector2 startPoint = new Vector2(200); + + addMovementStep(startPoint); + AddStep("press left button", () => InputManager.PressButton(MouseButton.Left)); + + for (int i = 0; i < 20; i++) + addMovementStep(startPoint + new Vector2(i * 40, MathF.Sin(i * MathF.PI / 5) * 50)); + + AddStep("release left button", () => InputManager.ReleaseButton(MouseButton.Left)); + + assertPlaced(true); + assertLength(760, tolerance: 10); + assertControlPointCount(5); + assertControlPointType(0, PathType.BSpline(3)); + assertControlPointType(1, null); + assertControlPointType(2, null); + assertControlPointType(3, null); + assertControlPointType(4, null); + } + + [Test] + public void TestSliderDrawingLinear() { addMovementStep(new Vector2(200)); AddStep("press left button", () => InputManager.PressButton(MouseButton.Left)); @@ -291,7 +315,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertLength(600, tolerance: 10); assertControlPointCount(4); - assertControlPointType(0, PathType.BSpline(3)); + assertControlPointType(0, PathType.LINEAR); + assertControlPointType(1, null); + assertControlPointType(2, null); + assertControlPointType(3, null); } [Test] @@ -401,11 +428,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private void assertPlaced(bool expected) => AddAssert($"slider {(expected ? "placed" : "not placed")}", () => (getSlider() != null) == expected); - private void assertLength(double expected, double tolerance = 1) => AddAssert($"slider length is {expected}±{tolerance}", () => Precision.AlmostEquals(expected, getSlider().Distance, tolerance)); + private void assertLength(double expected, double tolerance = 1) => AddAssert($"slider length is {expected}±{tolerance}", () => getSlider().Distance, () => Is.EqualTo(expected).Within(tolerance)); - private void assertControlPointCount(int expected) => AddAssert($"has {expected} control points", () => getSlider().Path.ControlPoints.Count == expected); + private void assertControlPointCount(int expected) => AddAssert($"has {expected} control points", () => getSlider().Path.ControlPoints.Count, () => Is.EqualTo(expected)); - private void assertControlPointType(int index, PathType type) => AddAssert($"control point {index} is {type}", () => getSlider().Path.ControlPoints[index].Type == type); + private void assertControlPointType(int index, PathType? type) => AddAssert($"control point {index} is {type?.ToString() ?? "inherit"}", () => getSlider().Path.ControlPoints[index].Type, () => Is.EqualTo(type)); private void assertControlPointPosition(int index, Vector2 position) => AddAssert($"control point {index} at {position}", () => Precision.AlmostEquals(position, getSlider().Path.ControlPoints[index].Position, 1)); From 7bedbe42642384a20b681220b507fcc737325c1e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 13:36:06 +0900 Subject: [PATCH 1619/4092] Apply NRT to `SliderPlacementBlueprint` tests --- .../Editor/TestSceneSliderPlacementBlueprint.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index 8498263138..75778b3a7e 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using NUnit.Framework; using osu.Framework.Utils; @@ -428,16 +426,16 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private void assertPlaced(bool expected) => AddAssert($"slider {(expected ? "placed" : "not placed")}", () => (getSlider() != null) == expected); - private void assertLength(double expected, double tolerance = 1) => AddAssert($"slider length is {expected}±{tolerance}", () => getSlider().Distance, () => Is.EqualTo(expected).Within(tolerance)); + private void assertLength(double expected, double tolerance = 1) => AddAssert($"slider length is {expected}±{tolerance}", () => getSlider()!.Distance, () => Is.EqualTo(expected).Within(tolerance)); - private void assertControlPointCount(int expected) => AddAssert($"has {expected} control points", () => getSlider().Path.ControlPoints.Count, () => Is.EqualTo(expected)); + private void assertControlPointCount(int expected) => AddAssert($"has {expected} control points", () => getSlider()!.Path.ControlPoints.Count, () => Is.EqualTo(expected)); - private void assertControlPointType(int index, PathType? type) => AddAssert($"control point {index} is {type?.ToString() ?? "inherit"}", () => getSlider().Path.ControlPoints[index].Type, () => Is.EqualTo(type)); + private void assertControlPointType(int index, PathType? type) => AddAssert($"control point {index} is {type?.ToString() ?? "inherit"}", () => getSlider()!.Path.ControlPoints[index].Type, () => Is.EqualTo(type)); private void assertControlPointPosition(int index, Vector2 position) => - AddAssert($"control point {index} at {position}", () => Precision.AlmostEquals(position, getSlider().Path.ControlPoints[index].Position, 1)); + AddAssert($"control point {index} at {position}", () => Precision.AlmostEquals(position, getSlider()!.Path.ControlPoints[index].Position, 1)); - private Slider getSlider() => HitObjectContainer.Count > 0 ? ((DrawableSlider)HitObjectContainer[0]).HitObject : null; + private Slider? getSlider() => HitObjectContainer.Count > 0 ? ((DrawableSlider)HitObjectContainer[0]).HitObject : null; protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSlider((Slider)hitObject); protected override PlacementBlueprint CreateBlueprint() => new SliderPlacementBlueprint(); From 9c3f9db31838295cccc0ea4f771933dbb4fa2dc7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 14:02:08 +0900 Subject: [PATCH 1620/4092] Add failing test coverage of BSpline encoding parse failure --- .../Formats/LegacyBeatmapEncoderTest.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index 0dd277dc89..e847b61fbe 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -113,6 +113,33 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsTrue(areComboColoursEqual(expected.skin.Configuration, actual.skin.Configuration)); } + [Test] + public void TestEncodeBSplineCurveType() + { + var beatmap = new Beatmap + { + HitObjects = + { + new Slider + { + Path = new SliderPath(new[] + { + new PathControlPoint(Vector2.Zero, PathType.BSpline(3)), + new PathControlPoint(new Vector2(50)), + new PathControlPoint(new Vector2(100), PathType.BSpline(3)), + new PathControlPoint(new Vector2(150)) + }) + }, + } + }; + + var decodedAfterEncode = decodeFromLegacy(encodeToLegacy((beatmap, new TestLegacySkin(beatmaps_resource_store, string.Empty))), string.Empty); + var decodedSlider = (Slider)decodedAfterEncode.beatmap.HitObjects[0]; + Assert.That(decodedSlider.Path.ControlPoints.Count, Is.EqualTo(4)); + Assert.That(decodedSlider.Path.ControlPoints[0].Type, Is.EqualTo(PathType.BSpline(3))); + Assert.That(decodedSlider.Path.ControlPoints[2].Type, Is.EqualTo(PathType.BSpline(3))); + } + [Test] public void TestEncodeMultiSegmentSliderWithFloatingPointError() { From 3d094f84add9f669b2bf0e1bd41bd83da4fd01fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 14:02:15 +0900 Subject: [PATCH 1621/4092] Fix incorrect parsing of BSpline curve types --- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 411a9b0d63..f9e32fe26f 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -273,8 +273,8 @@ namespace osu.Game.Rulesets.Objects.Legacy while (++endIndex < pointSplit.Length) { - // Keep incrementing endIndex while it's not the start of a new segment (indicated by having a type descriptor of length 1). - if (pointSplit[endIndex].Length > 1) + // Keep incrementing endIndex while it's not the start of a new segment (indicated by having an alpha character at position 0). + if (!char.IsLetter(pointSplit[endIndex][0])) continue; // Multi-segmented sliders DON'T contain the end point as part of the current segment as it's assumed to be the start of the next segment. From ba6fbbe43c122413c4b4b81eb9450e77f3361f8b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 14:03:45 +0900 Subject: [PATCH 1622/4092] 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 8175869405..aa993485f3 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 a5425ba4c7..b43cb1b3f1 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From 92728ea56460dfe0c2135d9165ee8d35e49fe8ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 14:10:48 +0900 Subject: [PATCH 1623/4092] Simplify toolbox initialisation --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 061f72d72f..65acdb61ae 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -97,12 +97,12 @@ namespace osu.Game.Rulesets.Osu.Edit // we may be entering the screen with a selection already active updateDistanceSnapGrid(); - RightToolbox.Add(new TransformToolboxGroup - { - RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, - }); - - RightToolbox.Add(FreehandlSliderToolboxGroup); + RightToolbox.AddRange(new EditorToolboxGroup[] + { + new TransformToolboxGroup { RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, }, + FreehandlSliderToolboxGroup + } + ); } protected override ComposeBlueprintContainer CreateBlueprintContainer() From cf6f66b84f729a71e04a34b7d9acf9091dfbd7cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 14:37:06 +0900 Subject: [PATCH 1624/4092] Remove redundant `Clear()` call --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 6e9cc26af4..32397330c6 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -178,7 +178,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders if (lastCp != cursor && HitObject.Path.ControlPoints.Count == 2) return false; - bSplineBuilder.Clear(); bSplineBuilder.AddLinearPoint(ToLocalSpace(e.ScreenSpaceMouseDownPosition) - HitObject.Position); setState(SliderPlacementState.Drawing); return true; From 016de7be6a9c0a5709e966b6f2bb45349d8f38e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 14:51:09 +0900 Subject: [PATCH 1625/4092] Simplify drag handling code in `SliderPlacementBlueprint` --- .../Sliders/SliderPlacementBlueprint.cs | 47 +++++++------------ 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 32397330c6..bb4558171f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders controlPointVisualiser = new PathControlPointVisualiser(HitObject, false) }; - setState(SliderPlacementState.Initial); + state = SliderPlacementState.Initial; } protected override void LoadComplete() @@ -164,38 +164,30 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders protected override bool OnDragStart(DragStartEvent e) { - if (e.Button == MouseButton.Left) - { - switch (state) - { - case SliderPlacementState.Initial: - return true; + if (e.Button != MouseButton.Left) + return base.OnDragStart(e); - case SliderPlacementState.ControlPoints: - if (HitObject.Path.ControlPoints.Count < 3) - { - var lastCp = HitObject.Path.ControlPoints.LastOrDefault(); - if (lastCp != cursor && HitObject.Path.ControlPoints.Count == 2) - return false; + if (state != SliderPlacementState.ControlPoints) + return base.OnDragStart(e); - bSplineBuilder.AddLinearPoint(ToLocalSpace(e.ScreenSpaceMouseDownPosition) - HitObject.Position); - setState(SliderPlacementState.Drawing); - return true; - } + // Only enter drawing mode if no additional control points have been placed. + if (HitObject.Path.ControlPoints.Count > 2) + return base.OnDragStart(e); - return false; - } - } - - return base.OnDragStart(e); + bSplineBuilder.AddLinearPoint(ToLocalSpace(e.ScreenSpaceMouseDownPosition) - HitObject.Position); + state = SliderPlacementState.Drawing; + return true; } protected override void OnDrag(DragEvent e) { base.OnDrag(e); - bSplineBuilder.AddLinearPoint(ToLocalSpace(e.ScreenSpaceMousePosition) - HitObject.Position); - Scheduler.AddOnce(updateSliderPathFromBSplineBuilder); + if (state == SliderPlacementState.Drawing) + { + bSplineBuilder.AddLinearPoint(ToLocalSpace(e.ScreenSpaceMousePosition) - HitObject.Position); + Scheduler.AddOnce(updateSliderPathFromBSplineBuilder); + } } private void updateSliderPathFromBSplineBuilder() @@ -260,7 +252,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void beginCurve() { BeginPlacement(commitStart: true); - setState(SliderPlacementState.ControlPoints); + state = SliderPlacementState.ControlPoints; } private void endCurve() @@ -357,11 +349,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders tailCirclePiece.UpdateFrom(HitObject.TailCircle); } - private void setState(SliderPlacementState newState) - { - state = newState; - } - private enum SliderPlacementState { Initial, From a210469956ca340bb302f76679c49142e2e0ae69 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 14:52:21 +0900 Subject: [PATCH 1626/4092] Reorder methods --- .../Sliders/SliderPlacementBlueprint.cs | 106 +++++++++--------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index bb4558171f..397d6ffb91 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -190,50 +190,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders } } - private void updateSliderPathFromBSplineBuilder() - { - IReadOnlyList builderPoints = bSplineBuilder.ControlPoints; - - if (builderPoints.Count == 0) - return; - - int lastSegmentStart = 0; - PathType? lastPathType = null; - - HitObject.Path.ControlPoints.Clear(); - - // Iterate through generated points, finding each segment and adding non-inheriting path types where appropriate. - // Importantly, the B-Spline builder returns three Vector2s at the same location when a new segment is to be started. - for (int i = 0; i < builderPoints.Count; i++) - { - bool isLastPoint = i == builderPoints.Count - 1; - bool isNewSegment = i < builderPoints.Count - 2 && builderPoints[i] == builderPoints[i + 1] && builderPoints[i] == builderPoints[i + 2]; - - if (isNewSegment || isLastPoint) - { - int pointsInSegment = i - lastSegmentStart; - - // Where possible, we can use the simpler LINEAR path type. - PathType? pathType = pointsInSegment == 1 ? PathType.LINEAR : PathType.BSpline(3); - - // Linear segments can be combined, as two adjacent linear sections are computationally the same as one with the points combined. - if (lastPathType == pathType && lastPathType == PathType.LINEAR) - pathType = null; - - HitObject.Path.ControlPoints.Add(new PathControlPoint(builderPoints[lastSegmentStart], pathType)); - for (int j = lastSegmentStart + 1; j < i; j++) - HitObject.Path.ControlPoints.Add(new PathControlPoint(builderPoints[j])); - - if (isLastPoint) - HitObject.Path.ControlPoints.Add(new PathControlPoint(builderPoints[i])); - - // Skip the redundant duplicated points (see isNewSegment above) which have been coalesced into a path type. - lastSegmentStart = (i += 2); - if (pathType != null) lastPathType = pathType; - } - } - } - protected override void OnDragEnd(DragEndEvent e) { base.OnDragEnd(e); @@ -249,6 +205,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders base.OnMouseUp(e); } + protected override void Update() + { + base.Update(); + updateSlider(); + + // Maintain the path type in case it got defaulted to bezier at some point during the drag. + updatePathType(); + } + private void beginCurve() { BeginPlacement(commitStart: true); @@ -261,15 +226,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders EndPlacement(true); } - protected override void Update() - { - base.Update(); - updateSlider(); - - // Maintain the path type in case it got defaulted to bezier at some point during the drag. - updatePathType(); - } - private void updatePathType() { if (state == SliderPlacementState.Drawing) @@ -349,6 +305,50 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders tailCirclePiece.UpdateFrom(HitObject.TailCircle); } + private void updateSliderPathFromBSplineBuilder() + { + IReadOnlyList builderPoints = bSplineBuilder.ControlPoints; + + if (builderPoints.Count == 0) + return; + + int lastSegmentStart = 0; + PathType? lastPathType = null; + + HitObject.Path.ControlPoints.Clear(); + + // Iterate through generated points, finding each segment and adding non-inheriting path types where appropriate. + // Importantly, the B-Spline builder returns three Vector2s at the same location when a new segment is to be started. + for (int i = 0; i < builderPoints.Count; i++) + { + bool isLastPoint = i == builderPoints.Count - 1; + bool isNewSegment = i < builderPoints.Count - 2 && builderPoints[i] == builderPoints[i + 1] && builderPoints[i] == builderPoints[i + 2]; + + if (isNewSegment || isLastPoint) + { + int pointsInSegment = i - lastSegmentStart; + + // Where possible, we can use the simpler LINEAR path type. + PathType? pathType = pointsInSegment == 1 ? PathType.LINEAR : PathType.BSpline(3); + + // Linear segments can be combined, as two adjacent linear sections are computationally the same as one with the points combined. + if (lastPathType == pathType && lastPathType == PathType.LINEAR) + pathType = null; + + HitObject.Path.ControlPoints.Add(new PathControlPoint(builderPoints[lastSegmentStart], pathType)); + for (int j = lastSegmentStart + 1; j < i; j++) + HitObject.Path.ControlPoints.Add(new PathControlPoint(builderPoints[j])); + + if (isLastPoint) + HitObject.Path.ControlPoints.Add(new PathControlPoint(builderPoints[i])); + + // Skip the redundant duplicated points (see isNewSegment above) which have been coalesced into a path type. + lastSegmentStart = (i += 2); + if (pathType != null) lastPathType = pathType; + } + } + } + private enum SliderPlacementState { Initial, From 1660eb3c15b1075e93a64a2938e8e86451f5df84 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 15:31:24 +0900 Subject: [PATCH 1627/4092] Add failing test coverage of drag after point placement --- .../TestSceneSliderPlacementBlueprint.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index 75778b3a7e..7ac34bc6c8 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -272,6 +272,30 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertControlPointType(2, PathType.PERFECT_CURVE); } + [Test] + public void TestSliderDrawingDoesntActivateAfterNormalPlacement() + { + Vector2 startPoint = new Vector2(200); + + addMovementStep(startPoint); + addClickStep(MouseButton.Left); + + for (int i = 0; i < 20; i++) + { + if (i == 5) + AddStep("press left button", () => InputManager.PressButton(MouseButton.Left)); + addMovementStep(startPoint + new Vector2(i * 40, MathF.Sin(i * MathF.PI / 5) * 50)); + } + + AddStep("release left button", () => InputManager.ReleaseButton(MouseButton.Left)); + assertPlaced(false); + + addClickStep(MouseButton.Right); + assertPlaced(true); + + assertControlPointType(0, PathType.BEZIER); + } + [Test] public void TestSliderDrawingCurve() { From cc33e121258adb0d607fba5c6bf9897e1979eac5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 15:16:40 +0900 Subject: [PATCH 1628/4092] Fix dragging after one point already placed incorrectly entering drawing mode --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 397d6ffb91..63370350ed 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -171,7 +171,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders return base.OnDragStart(e); // Only enter drawing mode if no additional control points have been placed. - if (HitObject.Path.ControlPoints.Count > 2) + int controlPointCount = HitObject.Path.ControlPoints.Count; + if (controlPointCount > 2 || (controlPointCount == 2 && HitObject.Path.ControlPoints.Last() != cursor)) return base.OnDragStart(e); bSplineBuilder.AddLinearPoint(ToLocalSpace(e.ScreenSpaceMouseDownPosition) - HitObject.Position); From 4b2d8aa6a647b28c13f63dc3047cca04b8b8c398 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 15:31:39 +0900 Subject: [PATCH 1629/4092] Add `ToString` on `PathType` for better test output --- osu.Game/Rulesets/Objects/Types/PathType.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Rulesets/Objects/Types/PathType.cs b/osu.Game/Rulesets/Objects/Types/PathType.cs index f84d43e3e7..23f1ccf0bc 100644 --- a/osu.Game/Rulesets/Objects/Types/PathType.cs +++ b/osu.Game/Rulesets/Objects/Types/PathType.cs @@ -81,5 +81,7 @@ namespace osu.Game.Rulesets.Objects.Types public static bool operator ==(PathType a, PathType b) => a.Equals(b); public static bool operator !=(PathType a, PathType b) => !a.Equals(b); + + public override string ToString() => Description; } } From ed3874682365596f96fac6c8622821786b3645a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 21 Nov 2023 16:14:30 +0900 Subject: [PATCH 1630/4092] Fix spacer appearing on top of menu --- .../Sliders/Components/PathControlPointVisualiser.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 87e837cc71..3a80d04ab8 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -367,9 +367,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components List curveTypeItems = new List(); if (!selectedPieces.Contains(Pieces[0])) + { curveTypeItems.Add(createMenuItemForPathType(null)); - - curveTypeItems.Add(new OsuMenuItemSpacer()); + curveTypeItems.Add(new OsuMenuItemSpacer()); + } // todo: hide/disable items which aren't valid for selected points curveTypeItems.Add(createMenuItemForPathType(PathType.Linear)); From 85bddab52bb0cdc0419ebe3aea776bc4650d0c7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 21 Nov 2023 16:52:52 +0900 Subject: [PATCH 1631/4092] Refactor `OnlineStatusNotifier` to be more local --- osu.Game/Online/OnlineStatusNotifier.cs | 108 ++++++++++++++---------- 1 file changed, 62 insertions(+), 46 deletions(-) diff --git a/osu.Game/Online/OnlineStatusNotifier.cs b/osu.Game/Online/OnlineStatusNotifier.cs index 0cf672ac3c..0d846f7d27 100644 --- a/osu.Game/Online/OnlineStatusNotifier.cs +++ b/osu.Game/Online/OnlineStatusNotifier.cs @@ -17,6 +17,9 @@ using osu.Game.Screens.OnlinePlay; namespace osu.Game.Online { + /// + /// Handles various scenarios where connection is lost and we need to let the user know what and why. + /// public partial class OnlineStatusNotifier : Component { private readonly Func getCurrentScreen; @@ -33,7 +36,11 @@ namespace osu.Game.Online private IBindable apiState = null!; private IBindable multiplayerState = null!; private IBindable spectatorState = null!; - private bool forcedDisconnection; + + /// + /// This flag will be set to true when the user has been notified so we don't show more than one notification. + /// + private bool userNotified; public OnlineStatusNotifier(Func getCurrentScreen) { @@ -51,12 +58,63 @@ namespace osu.Game.Online spectatorClient.Disconnecting += notifyAboutForcedDisconnection; } + protected override void LoadComplete() + { + base.LoadComplete(); + + apiState.BindValueChanged(state => + { + if (state.NewValue == APIState.Online) + { + userNotified = false; + return; + } + + if (userNotified) return; + + if (state.NewValue == APIState.Offline && getCurrentScreen() is OnlinePlayScreen) + { + userNotified = true; + notificationOverlay?.Post(new SimpleErrorNotification + { + Icon = FontAwesome.Solid.ExclamationCircle, + Text = "Connection to API was lost. Can't continue with online play." + }); + } + }); + + multiplayerState.BindValueChanged(connected => Schedule(() => + { + if (connected.NewValue) + { + userNotified = false; + return; + } + + if (userNotified) return; + + if (multiplayerClient.Room != null) + { + userNotified = true; + notificationOverlay?.Post(new SimpleErrorNotification + { + Icon = FontAwesome.Solid.ExclamationCircle, + Text = "Connection to the multiplayer server was lost. Exiting multiplayer." + }); + } + })); + + spectatorState.BindValueChanged(_ => + { + // TODO: handle spectator server failure somehow? + }); + } + private void notifyAboutForcedDisconnection() { - if (forcedDisconnection) - return; + if (userNotified) return; - forcedDisconnection = true; + userNotified = true; notificationOverlay?.Post(new SimpleErrorNotification { Icon = FontAwesome.Solid.ExclamationCircle, @@ -64,48 +122,6 @@ namespace osu.Game.Online }); } - protected override void LoadComplete() - { - base.LoadComplete(); - - apiState.BindValueChanged(_ => - { - if (apiState.Value == APIState.Online) - forcedDisconnection = false; - - Scheduler.AddOnce(updateState); - }); - multiplayerState.BindValueChanged(_ => Scheduler.AddOnce(updateState)); - spectatorState.BindValueChanged(_ => Scheduler.AddOnce(updateState)); - } - - private void updateState() - { - if (forcedDisconnection) - return; - - if (apiState.Value == APIState.Offline && getCurrentScreen() is OnlinePlayScreen) - { - notificationOverlay?.Post(new SimpleErrorNotification - { - Icon = FontAwesome.Solid.ExclamationCircle, - Text = "API connection was lost. Can't continue with online play." - }); - return; - } - - if (!multiplayerClient.IsConnected.Value && multiplayerClient.Room != null) - { - notificationOverlay?.Post(new SimpleErrorNotification - { - Icon = FontAwesome.Solid.ExclamationCircle, - Text = "Connection to the multiplayer server was lost. Exiting multiplayer." - }); - } - - // TODO: handle spectator server failure somehow? - } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From aa749aeb731dba4743338f88e541c326b8e5c12e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Nov 2023 17:49:56 +0900 Subject: [PATCH 1632/4092] Save any unsaved changes in the skin editor when game changes screens Closes https://github.com/ppy/osu/issues/25494. --- osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs index 68d6b7ced5..cbe122395c 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs @@ -188,7 +188,10 @@ namespace osu.Game.Overlays.SkinEditor } if (skinEditor.State.Value == Visibility.Visible) + { + skinEditor.Save(false); skinEditor.UpdateTargetScreen(target); + } else { skinEditor.Hide(); From 8302ebcf1ae54b2f0bc3ba7f8ba01b185ffeda39 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 21 Nov 2023 19:18:02 +0900 Subject: [PATCH 1633/4092] Remove default DrainRate value --- osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs index a8808d08e5..4a651e2650 100644 --- a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Scoring /// /// The drain rate as a proportion of the total health drained per millisecond. /// - public double DrainRate { get; private set; } = 1; + public double DrainRate { get; private set; } /// /// The beatmap. @@ -139,8 +139,6 @@ namespace osu.Game.Rulesets.Scoring { base.Reset(storeResults); - DrainRate = 1; - if (storeResults) DrainRate = ComputeDrainRate(); From 16acec335f825eb93c791e64010f35e030f4ff69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Burgelin=20=28Zyf=29?= Date: Tue, 21 Nov 2023 11:27:03 +0100 Subject: [PATCH 1634/4092] Fix: update score migration of special case to match the new score MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Database/StandardisedScoreMigrationTools.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index 685f852eb4..a9569ac9ba 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -270,7 +270,7 @@ namespace osu.Game.Database { return (long)Math.Round(( 0 - + 300000 * Math.Pow(score.Accuracy, 8) + + 500000 * Math.Pow(score.Accuracy, 5) + bonusProportion) * modMultiplier); } From 901e45b6a4f134143d512c6cec378149c6bc4aea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Burgelin=20=28Zyfarok=29?= Date: Tue, 21 Nov 2023 11:54:07 +0100 Subject: [PATCH 1635/4092] Scoring conversion: remove mention of v3 + improve comments --- .../StandardisedScoreMigrationTools.cs | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index a9569ac9ba..fe51c6f130 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -275,50 +275,52 @@ namespace osu.Game.Database } // Assumption : - // - sliders and slider-ticks are uniformly spread arround the beatmap - // thus we can ignore them without losing much precision (consider a map of hit-circles only !) - // - the Ok/Meh hit results are uniformly spread in the score - // thus we can simplify and consider each hit result to be score.Accuracy without losing much precision - // What is strippedV1/strippedV3 : - // This is the ComboScore of v1/v3 were we remove all (map-)constant multipliers and accuracy multipliers (including hit results), - // based on the previous assumptions. For Scorev1, this is basically the sum of squared combos (because without sliders: object_count == combo). + // - sliders and slider-ticks are uniformly spread arround the beatmap, and thus can be ignored without losing much precision. + // We thus consider a map of hit-circles only, which gives objectCount == maximumCombo ! + // - the Ok/Meh hit results are uniformly spread in the score, and thus can be ignored without losing much precision. + // We simplify and consider each hit result to be equal to `300*score.Accuracy`, which allows us to isolate the accuracy multiplier. + // What is strippedV1/strippedNew : + // This is the ComboScore (of v1 / new score) were we remove all (map-)constant multipliers and accuracy multipliers (including hit results), + // based on the previous assumptions. + // We use integrals to approximate the sum of each object's combo contribution (thus the original combo exponent is increased by 1) + // For the maximum score, we thus integrate `f(combo) = 300*combo^exponent`, and ignoring the constant multipliers we get: double maxStrippedV1 = Math.Pow(maximumLegacyCombo, 2); - double maxStrippedV3 = Math.Pow(maximumLegacyCombo, 1 + ScoreProcessor.COMBO_EXPONENT); + double maxStrippedNew = Math.Pow(maximumLegacyCombo, 1 + ScoreProcessor.COMBO_EXPONENT); double strippedV1 = maxStrippedV1 * comboProportion / score.Accuracy; double strippedV1FromMaxCombo = Math.Pow(score.MaxCombo, 2); - double strippedV3FromMaxCombo = Math.Pow(score.MaxCombo, 1 + ScoreProcessor.COMBO_EXPONENT); + double strippedNewFromMaxCombo = Math.Pow(score.MaxCombo, 1 + ScoreProcessor.COMBO_EXPONENT); - // Compute approximate lower estimate scorev3 for that play + // Compute approximate lower estimate new score for that play // That is, a play were we made biggest amount of big combos (Repeat MaxCombo + 1 remaining big combo) // And didn't combo anything in the reminder of the map double possibleMaxComboRepeat = Math.Floor(strippedV1 / strippedV1FromMaxCombo); double strippedV1FromMaxComboRepeat = possibleMaxComboRepeat * strippedV1FromMaxCombo; double remainingStrippedV1 = strippedV1 - strippedV1FromMaxComboRepeat; double remainingCombo = Math.Sqrt(remainingStrippedV1); - double remainingStrippedV3 = Math.Pow(remainingCombo, 1 + ScoreProcessor.COMBO_EXPONENT); + double remainingStrippedNew = Math.Pow(remainingCombo, 1 + ScoreProcessor.COMBO_EXPONENT); - double newLowerStrippedV3 = (possibleMaxComboRepeat * strippedV3FromMaxCombo) + remainingStrippedV3; + double lowerStrippedNew = (possibleMaxComboRepeat * strippedNewFromMaxCombo) + remainingStrippedNew; - // Compute approximate upper estimate scorev3 for that play + // Compute approximate upper estimate new score for that play // That is, a play were all combos were equal (except MaxCombo) remainingStrippedV1 = strippedV1 - strippedV1FromMaxCombo; double remainingComboObjects = maximumLegacyCombo - score.MaxCombo - score.Statistics[HitResult.Miss]; double remainingAverageCombo = remainingComboObjects > 0 ? remainingStrippedV1 / remainingComboObjects : 0; - remainingStrippedV3 = remainingComboObjects * Math.Pow(remainingAverageCombo, ScoreProcessor.COMBO_EXPONENT); + remainingStrippedNew = remainingComboObjects * Math.Pow(remainingAverageCombo, ScoreProcessor.COMBO_EXPONENT); - double newUpperStrippedV3 = strippedV3FromMaxCombo + remainingStrippedV3; + double upperStrippedNew = strippedNewFromMaxCombo + remainingStrippedNew; // Approximate by combining lower and upper estimates // As the lower-estimate is very pessimistic, we use a 30/70 ratio // And cap it with 1.2 times the middle-point to avoid overstimates - double strippedV3 = Math.Min( - 0.3 * newLowerStrippedV3 + 0.7 * newUpperStrippedV3, - 1.2 * (newLowerStrippedV3 + newUpperStrippedV3) / 2 + double strippedNew = Math.Min( + 0.3 * lowerStrippedNew + 0.7 * upperStrippedNew, + 1.2 * (lowerStrippedNew + upperStrippedNew) / 2 ); - double newComboScoreProportion = (strippedV3 / maxStrippedV3); + double newComboScoreProportion = (strippedNew / maxStrippedNew); return (long)Math.Round(( 500000 * newComboScoreProportion * score.Accuracy From 671177e87120e4cacba35f824950d0847cbeacf6 Mon Sep 17 00:00:00 2001 From: yesseruser Date: Tue, 21 Nov 2023 19:02:23 +0100 Subject: [PATCH 1636/4092] Renamed UpdateableFlag to ClickableUpdateableFlag. --- osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs | 2 +- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 2 +- osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs | 2 +- osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs | 4 ++-- osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs | 4 ++-- osu.Game/Overlays/Rankings/CountryPill.cs | 4 ++-- osu.Game/Overlays/Rankings/Tables/RankingsTable.cs | 2 +- .../OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs | 2 +- osu.Game/Screens/Play/HUD/PlayerFlag.cs | 4 ++-- .../{UpdateableFlag.cs => ClickableUpdateableFlag.cs} | 4 ++-- osu.Game/Users/ExtendedUserPanel.cs | 2 +- 11 files changed, 16 insertions(+), 16 deletions(-) rename osu.Game/Users/Drawables/{UpdateableFlag.cs => ClickableUpdateableFlag.cs} (91%) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs b/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs index 0bc71924ce..7a7679c376 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs @@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual.Menus AddStep("click on flag", () => { - InputManager.MoveMouseTo(loginOverlay.ChildrenOfType().First()); + InputManager.MoveMouseTo(loginOverlay.ChildrenOfType().First()); InputManager.Click(MouseButton.Left); }); AddAssert("login overlay is hidden", () => loginOverlay.State.Value == Visibility.Hidden); diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 136c9cc8e7..114ef5db22 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -180,7 +180,7 @@ namespace osu.Game.Online.Leaderboards Masking = true, Children = new Drawable[] { - new UpdateableFlag(user.CountryCode) + new ClickableUpdateableFlag(user.CountryCode) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 1fc997fdad..e144a55a96 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -157,7 +157,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Margin = new MarginPadding { Right = horizontal_inset }, Text = score.DisplayAccuracy, }, - new UpdateableFlag(score.User.CountryCode) + new ClickableUpdateableFlag(score.User.CountryCode) { Size = new Vector2(19, 14), ShowPlaceholderOnUnknown = false, diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs index 9dc2ce204f..e38e6efd06 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs @@ -27,7 +27,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly UpdateableAvatar avatar; private readonly LinkFlowContainer usernameText; private readonly DrawableDate achievedOn; - private readonly UpdateableFlag flag; + private readonly ClickableUpdateableFlag flag; public TopScoreUserSection() { @@ -112,7 +112,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores }, } }, - flag = new UpdateableFlag + flag = new ClickableUpdateableFlag { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index 36bd8a5af5..15aaf333f4 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -41,7 +41,7 @@ namespace osu.Game.Overlays.Profile.Header private OsuSpriteText usernameText = null!; private ExternalLinkButton openUserExternally = null!; private OsuSpriteText titleText = null!; - private UpdateableFlag userFlag = null!; + private ClickableUpdateableFlag userFlag = null!; private OsuHoverContainer userCountryContainer = null!; private OsuSpriteText userCountryText = null!; private GroupBadgeFlow groupBadgeFlow = null!; @@ -162,7 +162,7 @@ namespace osu.Game.Overlays.Profile.Header Direction = FillDirection.Horizontal, Children = new Drawable[] { - userFlag = new UpdateableFlag + userFlag = new ClickableUpdateableFlag { Size = new Vector2(28, 20), ShowPlaceholderOnUnknown = false, diff --git a/osu.Game/Overlays/Rankings/CountryPill.cs b/osu.Game/Overlays/Rankings/CountryPill.cs index 294b6df34d..bfa7363de8 100644 --- a/osu.Game/Overlays/Rankings/CountryPill.cs +++ b/osu.Game/Overlays/Rankings/CountryPill.cs @@ -34,7 +34,7 @@ namespace osu.Game.Overlays.Rankings private readonly Container content; private readonly Box background; - private readonly UpdateableFlag flag; + private readonly ClickableUpdateableFlag flag; private readonly OsuSpriteText countryName; public CountryPill() @@ -74,7 +74,7 @@ namespace osu.Game.Overlays.Rankings Spacing = new Vector2(5, 0), Children = new Drawable[] { - flag = new UpdateableFlag + flag = new ClickableUpdateableFlag { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index 27d894cdc2..b68ecd709a 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -96,7 +96,7 @@ namespace osu.Game.Overlays.Rankings.Tables Margin = new MarginPadding { Bottom = row_spacing }, Children = new[] { - new UpdateableFlag(GetCountryCode(item)) + new ClickableUpdateableFlag(GetCountryCode(item)) { Size = new Vector2(28, 20), ShowPlaceholderOnUnknown = false, diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index c79c210e30..1f922073ec 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -123,7 +123,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants FillMode = FillMode.Fit, User = user }, - new UpdateableFlag + new ClickableUpdateableFlag { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game/Screens/Play/HUD/PlayerFlag.cs b/osu.Game/Screens/Play/HUD/PlayerFlag.cs index 85799c03d3..7234db71b5 100644 --- a/osu.Game/Screens/Play/HUD/PlayerFlag.cs +++ b/osu.Game/Screens/Play/HUD/PlayerFlag.cs @@ -12,14 +12,14 @@ namespace osu.Game.Screens.Play.HUD { public partial class PlayerFlag : CompositeDrawable, ISerialisableDrawable { - private readonly UpdateableFlag flag; + private readonly ClickableUpdateableFlag flag; private const float default_size = 40f; public PlayerFlag() { Size = new Vector2(default_size, default_size / 1.4f); - InternalChild = flag = new UpdateableFlag + InternalChild = flag = new ClickableUpdateableFlag { RelativeSizeAxes = Axes.Both, }; diff --git a/osu.Game/Users/Drawables/UpdateableFlag.cs b/osu.Game/Users/Drawables/ClickableUpdateableFlag.cs similarity index 91% rename from osu.Game/Users/Drawables/UpdateableFlag.cs rename to osu.Game/Users/Drawables/ClickableUpdateableFlag.cs index 8f8d7052e5..d19234fc17 100644 --- a/osu.Game/Users/Drawables/UpdateableFlag.cs +++ b/osu.Game/Users/Drawables/ClickableUpdateableFlag.cs @@ -11,7 +11,7 @@ using osu.Game.Overlays; namespace osu.Game.Users.Drawables { - public partial class UpdateableFlag : ModelBackedDrawable + public partial class ClickableUpdateableFlag : ModelBackedDrawable { public CountryCode CountryCode { @@ -30,7 +30,7 @@ namespace osu.Game.Users.Drawables /// public Action? Action; - public UpdateableFlag(CountryCode countryCode = CountryCode.Unknown) + public ClickableUpdateableFlag(CountryCode countryCode = CountryCode.Unknown) { CountryCode = countryCode; } diff --git a/osu.Game/Users/ExtendedUserPanel.cs b/osu.Game/Users/ExtendedUserPanel.cs index 3c1b68f9ef..e798c8cc11 100644 --- a/osu.Game/Users/ExtendedUserPanel.cs +++ b/osu.Game/Users/ExtendedUserPanel.cs @@ -52,7 +52,7 @@ namespace osu.Game.Users protected UpdateableAvatar CreateAvatar() => new UpdateableAvatar(User, false); - protected UpdateableFlag CreateFlag() => new UpdateableFlag(User.CountryCode) + protected ClickableUpdateableFlag CreateFlag() => new ClickableUpdateableFlag(User.CountryCode) { Size = new Vector2(36, 26), Action = Action, From cd7e0bf620c0fd1617f1d99038dbbb85e52acd39 Mon Sep 17 00:00:00 2001 From: yesseruser Date: Tue, 21 Nov 2023 19:27:33 +0100 Subject: [PATCH 1637/4092] Created and implemened a BaseUpdateableFlag. The tooltip still shows. --- osu.Game/Screens/Play/HUD/PlayerFlag.cs | 4 +- .../Users/Drawables/BaseUpdateableFlag.cs | 45 +++++++++++++++++++ .../Drawables/ClickableUpdateableFlag.cs | 13 +----- 3 files changed, 48 insertions(+), 14 deletions(-) create mode 100644 osu.Game/Users/Drawables/BaseUpdateableFlag.cs diff --git a/osu.Game/Screens/Play/HUD/PlayerFlag.cs b/osu.Game/Screens/Play/HUD/PlayerFlag.cs index 7234db71b5..f0fe8ad668 100644 --- a/osu.Game/Screens/Play/HUD/PlayerFlag.cs +++ b/osu.Game/Screens/Play/HUD/PlayerFlag.cs @@ -12,14 +12,14 @@ namespace osu.Game.Screens.Play.HUD { public partial class PlayerFlag : CompositeDrawable, ISerialisableDrawable { - private readonly ClickableUpdateableFlag flag; + private readonly BaseUpdateableFlag flag; private const float default_size = 40f; public PlayerFlag() { Size = new Vector2(default_size, default_size / 1.4f); - InternalChild = flag = new ClickableUpdateableFlag + InternalChild = flag = new BaseUpdateableFlag { RelativeSizeAxes = Axes.Both, }; diff --git a/osu.Game/Users/Drawables/BaseUpdateableFlag.cs b/osu.Game/Users/Drawables/BaseUpdateableFlag.cs new file mode 100644 index 0000000000..16a368c60c --- /dev/null +++ b/osu.Game/Users/Drawables/BaseUpdateableFlag.cs @@ -0,0 +1,45 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Users.Drawables +{ + public partial class BaseUpdateableFlag : ModelBackedDrawable + { + public CountryCode CountryCode + { + get => Model; + set => Model = value; + } + + /// + /// Whether to show a place holder on unknown country. + /// + public bool ShowPlaceholderOnUnknown = true; + + public BaseUpdateableFlag(CountryCode countryCode = CountryCode.Unknown) + { + CountryCode = countryCode; + } + + protected override Drawable? CreateDrawable(CountryCode countryCode) + { + if (countryCode == CountryCode.Unknown && !ShowPlaceholderOnUnknown) + return null; + + return new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new DrawableFlag(countryCode) + { + RelativeSizeAxes = Axes.Both + }, + } + }; + } + } +} diff --git a/osu.Game/Users/Drawables/ClickableUpdateableFlag.cs b/osu.Game/Users/Drawables/ClickableUpdateableFlag.cs index d19234fc17..e00fe3c009 100644 --- a/osu.Game/Users/Drawables/ClickableUpdateableFlag.cs +++ b/osu.Game/Users/Drawables/ClickableUpdateableFlag.cs @@ -11,19 +11,8 @@ using osu.Game.Overlays; namespace osu.Game.Users.Drawables { - public partial class ClickableUpdateableFlag : ModelBackedDrawable + public partial class ClickableUpdateableFlag : BaseUpdateableFlag { - public CountryCode CountryCode - { - get => Model; - set => Model = value; - } - - /// - /// Whether to show a place holder on unknown country. - /// - public bool ShowPlaceholderOnUnknown = true; - /// /// Perform an action in addition to showing the country ranking. /// This should be used to perform auxiliary tasks and not as a primary action for clicking a flag (to maintain a consistent UX). From b2f74325efb4f477b42320cca8cbd380895b12da Mon Sep 17 00:00:00 2001 From: yesseruser Date: Tue, 21 Nov 2023 19:51:35 +0100 Subject: [PATCH 1638/4092] Added a BaseDrawableFlag without a tooltip, and used it. The BaseDrawableFlag is used in a BaseUpdateableFlag so the tooltip is not shown, the ClickableUpdateableFlag still shows the tooltip. --- osu.Game/Users/Drawables/BaseDrawableFlag.cs | 29 +++++++++++++++++++ .../Users/Drawables/BaseUpdateableFlag.cs | 3 +- .../Drawables/ClickableUpdateableFlag.cs | 1 + osu.Game/Users/Drawables/DrawableFlag.cs | 14 +++------ 4 files changed, 36 insertions(+), 11 deletions(-) create mode 100644 osu.Game/Users/Drawables/BaseDrawableFlag.cs diff --git a/osu.Game/Users/Drawables/BaseDrawableFlag.cs b/osu.Game/Users/Drawables/BaseDrawableFlag.cs new file mode 100644 index 0000000000..90f8cd7b70 --- /dev/null +++ b/osu.Game/Users/Drawables/BaseDrawableFlag.cs @@ -0,0 +1,29 @@ +// 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.Sprites; +using osu.Framework.Graphics.Textures; + +namespace osu.Game.Users.Drawables +{ + public partial class BaseDrawableFlag : Sprite + { + protected readonly CountryCode CountryCode; + + public BaseDrawableFlag(CountryCode countryCode) + { + CountryCode = countryCode; + } + + [BackgroundDependencyLoader] + private void load(TextureStore ts) + { + ArgumentNullException.ThrowIfNull(ts); + + string textureName = CountryCode == CountryCode.Unknown ? "__" : CountryCode.ToString(); + Texture = ts.Get($@"Flags/{textureName}") ?? ts.Get(@"Flags/__"); + } + } +} diff --git a/osu.Game/Users/Drawables/BaseUpdateableFlag.cs b/osu.Game/Users/Drawables/BaseUpdateableFlag.cs index 16a368c60c..3a27238b19 100644 --- a/osu.Game/Users/Drawables/BaseUpdateableFlag.cs +++ b/osu.Game/Users/Drawables/BaseUpdateableFlag.cs @@ -34,7 +34,8 @@ namespace osu.Game.Users.Drawables RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - new DrawableFlag(countryCode) + // This is a BaseDrawableFlag which does not show a tooltip. + new BaseDrawableFlag(countryCode) { RelativeSizeAxes = Axes.Both }, diff --git a/osu.Game/Users/Drawables/ClickableUpdateableFlag.cs b/osu.Game/Users/Drawables/ClickableUpdateableFlag.cs index e00fe3c009..2eaa3661d1 100644 --- a/osu.Game/Users/Drawables/ClickableUpdateableFlag.cs +++ b/osu.Game/Users/Drawables/ClickableUpdateableFlag.cs @@ -34,6 +34,7 @@ namespace osu.Game.Users.Drawables RelativeSizeAxes = Axes.Both, Children = new Drawable[] { + // This is a DrawableFlag which implements IHasTooltip, so a tooltip is shown. new DrawableFlag(countryCode) { RelativeSizeAxes = Axes.Both diff --git a/osu.Game/Users/Drawables/DrawableFlag.cs b/osu.Game/Users/Drawables/DrawableFlag.cs index 289f68ee7f..aef34c093f 100644 --- a/osu.Game/Users/Drawables/DrawableFlag.cs +++ b/osu.Game/Users/Drawables/DrawableFlag.cs @@ -5,29 +5,23 @@ using System; using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Localisation; namespace osu.Game.Users.Drawables { - public partial class DrawableFlag : Sprite, IHasTooltip + public partial class DrawableFlag : BaseDrawableFlag, IHasTooltip { - private readonly CountryCode countryCode; + public LocalisableString TooltipText => CountryCode == CountryCode.Unknown ? string.Empty : CountryCode.GetDescription(); - public LocalisableString TooltipText => countryCode == CountryCode.Unknown ? string.Empty : countryCode.GetDescription(); - - public DrawableFlag(CountryCode countryCode) - { - this.countryCode = countryCode; - } + public DrawableFlag(CountryCode countryCode) : base(countryCode) { } [BackgroundDependencyLoader] private void load(TextureStore ts) { ArgumentNullException.ThrowIfNull(ts); - string textureName = countryCode == CountryCode.Unknown ? "__" : countryCode.ToString(); + string textureName = CountryCode == CountryCode.Unknown ? "__" : CountryCode.ToString(); Texture = ts.Get($@"Flags/{textureName}") ?? ts.Get(@"Flags/__"); } } From c98be5823db0818bf5eaf8bcab853fa8622d5257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 22 Nov 2023 07:52:28 +0900 Subject: [PATCH 1639/4092] 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 aa993485f3..ea08992710 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 b43cb1b3f1..53d5d6b010 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From aa724070653dc41df9e67c8476427752586b219f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 22 Nov 2023 07:55:39 +0900 Subject: [PATCH 1640/4092] Fix android compile failures due to invalid java version See https://github.com/ppy/osu-framework/pull/6057. --- .github/workflows/ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a8167ec4db..11c956bb71 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -108,6 +108,12 @@ jobs: - name: Checkout uses: actions/checkout@v3 + - name: Setup JDK 11 + uses: actions/setup-java@v3 + with: + distribution: microsoft + java-version: 11 + - name: Install .NET 6.0.x uses: actions/setup-dotnet@v3 with: From 0cf925dadf4e09fc6e8e8c14f2511f4ef8591a18 Mon Sep 17 00:00:00 2001 From: Poyo Date: Tue, 21 Nov 2023 15:18:04 -0800 Subject: [PATCH 1641/4092] Use better fallback Seems better to use the rate from a non-gameplay clock than to arbitrarily apply 1. --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 5abca168ed..1daaa24d57 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -704,7 +704,7 @@ namespace osu.Game.Rulesets.Objects.Drawables } Result.RawTime = Time.Current; - Result.GameplayRate = (Clock as IGameplayClock)?.GetTrueGameplayRate() ?? 1.0; + Result.GameplayRate = (Clock as IGameplayClock)?.GetTrueGameplayRate() ?? Clock.Rate; if (Result.HasResult) updateState(Result.IsHit ? ArmedState.Hit : ArmedState.Miss); From 3a0586a8f59ce286f986df00327cf7953fd62dd7 Mon Sep 17 00:00:00 2001 From: Poyo Date: Tue, 21 Nov 2023 15:19:04 -0800 Subject: [PATCH 1642/4092] Rewrite backwards assertion --- osu.Game/Rulesets/Scoring/HitEventExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs index 70a11ae760..9fb61c6cd9 100644 --- a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs +++ b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Scoring /// public static double? CalculateUnstableRate(this IEnumerable hitEvents) { - Debug.Assert(!hitEvents.Any(ev => ev.GameplayRate == null)); + Debug.Assert(hitEvents.All(ev => ev.GameplayRate != null)); // Division by gameplay rate is to account for TimeOffset scaling with gameplay rate. double[] timeOffsets = hitEvents.Where(affectsUnstableRate).Select(ev => ev.TimeOffset / ev.GameplayRate!.Value).ToArray(); From 04640b6fb0129aa880281d1883bf0b318a128fe0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Nov 2023 10:44:29 +0900 Subject: [PATCH 1643/4092] Improve commenting around `IHasCombo` interfaces Following discusion with smoogi IRL. --- osu.Game/Rulesets/Objects/Types/IHasCombo.cs | 6 ++++++ .../Rulesets/Objects/Types/IHasComboInformation.cs | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/osu.Game/Rulesets/Objects/Types/IHasCombo.cs b/osu.Game/Rulesets/Objects/Types/IHasCombo.cs index d1a4683a1d..5de5424bdc 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasCombo.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasCombo.cs @@ -16,6 +16,12 @@ namespace osu.Game.Rulesets.Objects.Types /// /// When starting a new combo, the offset of the new combo relative to the current one. /// + /// + /// This is generally a setting provided by a beatmap creator to choreograph interesting colour patterns + /// which can only be achieved by skipping combo colours with per-hitobject level. + /// + /// It is exposed via . + /// int ComboOffset { get; } } } diff --git a/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs b/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs index d34e71021f..3aa68197ec 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs @@ -12,6 +12,9 @@ namespace osu.Game.Rulesets.Objects.Types /// public interface IHasComboInformation : IHasCombo { + /// + /// Bindable exposure of . + /// Bindable IndexInCurrentComboBindable { get; } /// @@ -19,13 +22,21 @@ namespace osu.Game.Rulesets.Objects.Types /// int IndexInCurrentCombo { get; set; } + /// + /// Bindable exposure of . + /// Bindable ComboIndexBindable { get; } /// /// The index of this combo in relation to the beatmap. + /// + /// In other words, this is incremented by 1 each time a is reached. /// int ComboIndex { get; set; } + /// + /// Bindable exposure of . + /// Bindable ComboIndexWithOffsetsBindable { get; } /// @@ -39,6 +50,9 @@ namespace osu.Game.Rulesets.Objects.Types /// new bool NewCombo { get; set; } + /// + /// Bindable exposure of . + /// Bindable LastInComboBindable { get; } /// From fe15b26bd2f780ba6e45e81e64944841ec23a54d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Nov 2023 12:02:37 +0900 Subject: [PATCH 1644/4092] Refactor to use API state instead of logged in user state --- osu.Game/Overlays/UserProfileOverlay.cs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 193651fa72..d978fe905f 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -47,7 +47,7 @@ namespace osu.Game.Overlays private IUser? user; private IRulesetInfo? ruleset; - private IBindable apiUser = null!; + private readonly IBindable apiState = new Bindable(); [Resolved] private RulesetStore rulesets { get; set; } = null!; @@ -68,10 +68,10 @@ namespace osu.Game.Overlays [BackgroundDependencyLoader] private void load() { - apiUser = API.LocalUser.GetBoundCopy(); - apiUser.BindValueChanged(_ => Schedule(() => + apiState.BindTo(API.State); + apiState.BindValueChanged(state => Schedule(() => { - if (API.IsLoggedIn) + if (state.NewValue == APIState.Online && user != null) fetchAndSetContent(); })); } @@ -89,7 +89,6 @@ namespace osu.Game.Overlays ruleset = userRuleset; Show(); - fetchAndSetContent(); } @@ -171,13 +170,14 @@ namespace osu.Game.Overlays sectionsContainer.ScrollToTop(); - if (API.State.Value != APIState.Online) - return; + if (API.State.Value != APIState.Offline) + { + userReq = user.OnlineID > 1 ? new GetUserRequest(user.OnlineID, ruleset) : new GetUserRequest(user.Username, ruleset); + userReq.Success += u => userLoadComplete(u, ruleset); - userReq = user.OnlineID > 1 ? new GetUserRequest(user.OnlineID, ruleset) : new GetUserRequest(user.Username, ruleset); - userReq.Success += u => userLoadComplete(u, ruleset); - API.Queue(userReq); - loadingLayer.Show(); + API.Queue(userReq); + loadingLayer.Show(); + } } private void userLoadComplete(APIUser loadedUser, IRulesetInfo? userRuleset) From ad6af1d9b74302b1d0396baba0fad68f31f88441 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Nov 2023 12:03:42 +0900 Subject: [PATCH 1645/4092] Ensure only run once --- osu.Game/Overlays/UserProfileOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index d978fe905f..9840551d9f 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -72,7 +72,7 @@ namespace osu.Game.Overlays apiState.BindValueChanged(state => Schedule(() => { if (state.NewValue == APIState.Online && user != null) - fetchAndSetContent(); + Scheduler.AddOnce(fetchAndSetContent); })); } @@ -89,7 +89,7 @@ namespace osu.Game.Overlays ruleset = userRuleset; Show(); - fetchAndSetContent(); + Scheduler.AddOnce(fetchAndSetContent); } private void fetchAndSetContent() From cb4568c4a1e7d12fea7dda4a643878e9c2bae675 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 16 Nov 2023 20:21:11 +0900 Subject: [PATCH 1646/4092] Fix first object after break not starting a new combo --- .../Formats/LegacyBeatmapDecoderTest.cs | 15 ++++++++++++ .../TestSceneHitObjectAccentColour.cs | 3 --- .../Resources/break-between-objects.osu | 15 ++++++++++++ .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 23 +++++++++++++++++++ .../Objects/Legacy/Catch/ConvertHit.cs | 6 +---- .../Objects/Legacy/Catch/ConvertSlider.cs | 6 +---- .../Objects/Legacy/Catch/ConvertSpinner.cs | 6 +---- .../Objects/Legacy/ConvertHitObject.cs | 7 +++++- .../Rulesets/Objects/Legacy/Osu/ConvertHit.cs | 6 +---- .../Objects/Legacy/Osu/ConvertSlider.cs | 6 +---- .../Objects/Legacy/Osu/ConvertSpinner.cs | 6 +---- 11 files changed, 65 insertions(+), 34 deletions(-) create mode 100644 osu.Game.Tests/Resources/break-between-objects.osu diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 66151a51e6..be1993957f 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -1093,5 +1093,20 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(hitObject.Samples.Select(s => s.Volume), Has.All.EqualTo(70)); } } + + [Test] + public void TestNewComboAfterBreak() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + + using (var resStream = TestResources.OpenResource("break-between-objects.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + var beatmap = decoder.Decode(stream); + Assert.That(((IHasCombo)beatmap.HitObjects[0]).NewCombo, Is.True); + Assert.That(((IHasCombo)beatmap.HitObjects[1]).NewCombo, Is.True); + Assert.That(((IHasCombo)beatmap.HitObjects[2]).NewCombo, Is.False); + } + } } } diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs index f38c2c9416..acb14f86fc 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs @@ -94,9 +94,6 @@ namespace osu.Game.Tests.Gameplay private class TestHitObjectWithCombo : ConvertHitObject, IHasComboInformation { - public bool NewCombo { get; set; } - public int ComboOffset => 0; - public Bindable IndexInCurrentComboBindable { get; } = new Bindable(); public int IndexInCurrentCombo diff --git a/osu.Game.Tests/Resources/break-between-objects.osu b/osu.Game.Tests/Resources/break-between-objects.osu new file mode 100644 index 0000000000..91821e2c58 --- /dev/null +++ b/osu.Game.Tests/Resources/break-between-objects.osu @@ -0,0 +1,15 @@ +osu file format v14 + +[General] +Mode: 0 + +[Events] +2,200,1200 + +[TimingPoints] +0,307.692307692308,4,2,1,60,1,0 + +[HitObjects] +142,99,0,1,0,0:0:0:0: +323,88,3000,1,0,0:0:0:0: +323,88,4000,1,0,0:0:0:0: diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 8c5e4971d5..1ee4670ae2 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -93,6 +93,8 @@ namespace osu.Game.Beatmaps.Formats // The parsing order of hitobjects matters in mania difficulty calculation this.beatmap.HitObjects = this.beatmap.HitObjects.OrderBy(h => h.StartTime).ToList(); + postProcessBreaks(this.beatmap); + foreach (var hitObject in this.beatmap.HitObjects) { applyDefaults(hitObject); @@ -100,6 +102,27 @@ namespace osu.Game.Beatmaps.Formats } } + /// + /// Processes the beatmap such that a new combo is started the first hitobject following each break. + /// + private void postProcessBreaks(Beatmap beatmap) + { + int currentBreak = 0; + bool forceNewCombo = false; + + foreach (var h in beatmap.HitObjects.OfType()) + { + while (currentBreak < beatmap.Breaks.Count && beatmap.Breaks[currentBreak].EndTime < h.StartTime) + { + forceNewCombo = true; + currentBreak++; + } + + h.NewCombo |= forceNewCombo; + forceNewCombo = false; + } + } + private void applyDefaults(HitObject hitObject) { DifficultyControlPoint difficultyControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.DifficultyPointAt(hitObject.StartTime) ?? DifficultyControlPoint.DEFAULT; diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs index 12b4812824..96c779e79b 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs @@ -9,16 +9,12 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch /// /// Legacy osu!catch Hit-type, used for parsing Beatmaps. /// - internal sealed class ConvertHit : ConvertHitObject, IHasPosition, IHasCombo + internal sealed class ConvertHit : ConvertHitObject, IHasPosition { public float X => Position.X; public float Y => Position.Y; public Vector2 Position { get; set; } - - public bool NewCombo { get; set; } - - public int ComboOffset { get; set; } } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs index fb1afed3b4..bcf1c7fae2 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs @@ -9,16 +9,12 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch /// /// Legacy osu!catch Slider-type, used for parsing Beatmaps. /// - internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasPosition, IHasCombo + internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasPosition { public float X => Position.X; public float Y => Position.Y; public Vector2 Position { get; set; } - - public bool NewCombo { get; set; } - - public int ComboOffset { get; set; } } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs index 014494ec54..5ef3d51cb3 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs @@ -8,16 +8,12 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch /// /// Legacy osu!catch Spinner-type, used for parsing Beatmaps. /// - internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasXPosition, IHasCombo + internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasXPosition { public double EndTime => StartTime + Duration; public double Duration { get; set; } public float X => 256; // Required for CatchBeatmapConverter - - public bool NewCombo { get; set; } - - public int ComboOffset { get; set; } } } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs index 54dbd28c76..bb36aab0b3 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Objects.Legacy @@ -9,8 +10,12 @@ namespace osu.Game.Rulesets.Objects.Legacy /// /// A hit object only used for conversion, not actual gameplay. /// - internal abstract class ConvertHitObject : HitObject + internal abstract class ConvertHitObject : HitObject, IHasCombo { + public bool NewCombo { get; set; } + + public int ComboOffset { get; set; } + public override Judgement CreateJudgement() => new IgnoreJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs index 069366bad3..b7cd4b0dcc 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs @@ -9,16 +9,12 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu /// /// Legacy osu! Hit-type, used for parsing Beatmaps. /// - internal sealed class ConvertHit : ConvertHitObject, IHasPosition, IHasCombo + internal sealed class ConvertHit : ConvertHitObject, IHasPosition { public Vector2 Position { get; set; } public float X => Position.X; public float Y => Position.Y; - - public bool NewCombo { get; set; } - - public int ComboOffset { get; set; } } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs index 790af6cfc1..8c37154f95 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu /// /// Legacy osu! Slider-type, used for parsing Beatmaps. /// - internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasPosition, IHasCombo, IHasGenerateTicks + internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasPosition, IHasGenerateTicks { public Vector2 Position { get; set; } @@ -17,10 +17,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu public float Y => Position.Y; - public bool NewCombo { get; set; } - - public int ComboOffset { get; set; } - 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 index e9e5ca8c94..d6e24b6bbf 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu /// /// Legacy osu! Spinner-type, used for parsing Beatmaps. /// - internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasPosition, IHasCombo + internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasPosition { public double Duration { get; set; } @@ -20,9 +20,5 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu public float X => Position.X; public float Y => Position.Y; - - public bool NewCombo { get; set; } - - public int ComboOffset { get; set; } } } From f7fce1d714dc4d849aef066abe62d1feb4a3145f Mon Sep 17 00:00:00 2001 From: cs Date: Wed, 22 Nov 2023 09:55:32 +0100 Subject: [PATCH 1647/4092] Fix freehand-drawn sliders with distance snap --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 63370350ed..c35c6fdf95 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -175,6 +175,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders if (controlPointCount > 2 || (controlPointCount == 2 && HitObject.Path.ControlPoints.Last() != cursor)) return base.OnDragStart(e); + HitObject.Position = ToLocalSpace(e.ScreenSpaceMouseDownPosition); bSplineBuilder.AddLinearPoint(ToLocalSpace(e.ScreenSpaceMouseDownPosition) - HitObject.Position); state = SliderPlacementState.Drawing; return true; From 95b12082aed0bb40e998f8b0402be77cfb4639e1 Mon Sep 17 00:00:00 2001 From: cs Date: Wed, 22 Nov 2023 10:14:44 +0100 Subject: [PATCH 1648/4092] Update to respect distance snap This will cause freehand drawing to respect distance snap, instead changing the drawn path to start from the sliders start position and "snap" in a linear fashion to the cursor position from the position indicated by distance snap --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index c35c6fdf95..ac7b5e63e1 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders if (controlPointCount > 2 || (controlPointCount == 2 && HitObject.Path.ControlPoints.Last() != cursor)) return base.OnDragStart(e); - HitObject.Position = ToLocalSpace(e.ScreenSpaceMouseDownPosition); + bSplineBuilder.AddLinearPoint(Vector2.Zero); bSplineBuilder.AddLinearPoint(ToLocalSpace(e.ScreenSpaceMouseDownPosition) - HitObject.Position); state = SliderPlacementState.Drawing; return true; From aa8dc6bd8061a1eafa591b00fbefa706559e283a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Nov 2023 21:46:35 +0900 Subject: [PATCH 1649/4092] Attempt to fix intermittent failures on new tests See https://github.com/ppy/osu/pull/25418/checks?check_run_id=18886372597. --- .../Visual/Editing/TestSceneOpenEditorTimestamp.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs index b6f89ee4e7..2c8655a5f5 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Editing RulesetInfo rulesetInfo = new OsuRuleset().RulesetInfo; addStepClickLink("00:00:000", waitForSeek: false); - AddAssert("received 'must be in edit'", + AddUntilStep("received 'must be in edit'", () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEditorToHandleLinks), () => Is.EqualTo(1)); @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.Editing AddUntilStep("entered song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect); addStepClickLink("00:00:000 (1)", waitForSeek: false); - AddAssert("received 'must be in edit'", + AddUntilStep("received 'must be in edit'", () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEditorToHandleLinks), () => Is.EqualTo(2)); @@ -47,12 +47,12 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("ruleset is osu!", () => editorBeatmap.BeatmapInfo.Ruleset.Equals(rulesetInfo)); addStepClickLink("00:000", "invalid link", waitForSeek: false); - AddAssert("received 'failed to process'", + AddUntilStep("received 'failed to process'", () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToParseEditorLink), () => Is.EqualTo(1)); addStepClickLink("50000:00:000", "too long link", waitForSeek: false); - AddAssert("received 'failed to process'", + AddUntilStep("received 'failed to process'", () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.FailedToParseEditorLink), () => Is.EqualTo(2)); } From 7bc304f20ee27d7314fc06620cf1250acebba89f Mon Sep 17 00:00:00 2001 From: yesseruser Date: Wed, 22 Nov 2023 15:25:17 +0100 Subject: [PATCH 1650/4092] Revert "Added a BaseDrawableFlag without a tooltip, and used it." This reverts commit b2f74325efb4f477b42320cca8cbd380895b12da. --- osu.Game/Users/Drawables/BaseDrawableFlag.cs | 29 ------------------- .../Users/Drawables/BaseUpdateableFlag.cs | 3 +- .../Drawables/ClickableUpdateableFlag.cs | 1 - osu.Game/Users/Drawables/DrawableFlag.cs | 14 ++++++--- 4 files changed, 11 insertions(+), 36 deletions(-) delete mode 100644 osu.Game/Users/Drawables/BaseDrawableFlag.cs diff --git a/osu.Game/Users/Drawables/BaseDrawableFlag.cs b/osu.Game/Users/Drawables/BaseDrawableFlag.cs deleted file mode 100644 index 90f8cd7b70..0000000000 --- a/osu.Game/Users/Drawables/BaseDrawableFlag.cs +++ /dev/null @@ -1,29 +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 System; -using osu.Framework.Allocation; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; - -namespace osu.Game.Users.Drawables -{ - public partial class BaseDrawableFlag : Sprite - { - protected readonly CountryCode CountryCode; - - public BaseDrawableFlag(CountryCode countryCode) - { - CountryCode = countryCode; - } - - [BackgroundDependencyLoader] - private void load(TextureStore ts) - { - ArgumentNullException.ThrowIfNull(ts); - - string textureName = CountryCode == CountryCode.Unknown ? "__" : CountryCode.ToString(); - Texture = ts.Get($@"Flags/{textureName}") ?? ts.Get(@"Flags/__"); - } - } -} diff --git a/osu.Game/Users/Drawables/BaseUpdateableFlag.cs b/osu.Game/Users/Drawables/BaseUpdateableFlag.cs index 3a27238b19..16a368c60c 100644 --- a/osu.Game/Users/Drawables/BaseUpdateableFlag.cs +++ b/osu.Game/Users/Drawables/BaseUpdateableFlag.cs @@ -34,8 +34,7 @@ namespace osu.Game.Users.Drawables RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - // This is a BaseDrawableFlag which does not show a tooltip. - new BaseDrawableFlag(countryCode) + new DrawableFlag(countryCode) { RelativeSizeAxes = Axes.Both }, diff --git a/osu.Game/Users/Drawables/ClickableUpdateableFlag.cs b/osu.Game/Users/Drawables/ClickableUpdateableFlag.cs index 2eaa3661d1..e00fe3c009 100644 --- a/osu.Game/Users/Drawables/ClickableUpdateableFlag.cs +++ b/osu.Game/Users/Drawables/ClickableUpdateableFlag.cs @@ -34,7 +34,6 @@ namespace osu.Game.Users.Drawables RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - // This is a DrawableFlag which implements IHasTooltip, so a tooltip is shown. new DrawableFlag(countryCode) { RelativeSizeAxes = Axes.Both diff --git a/osu.Game/Users/Drawables/DrawableFlag.cs b/osu.Game/Users/Drawables/DrawableFlag.cs index aef34c093f..289f68ee7f 100644 --- a/osu.Game/Users/Drawables/DrawableFlag.cs +++ b/osu.Game/Users/Drawables/DrawableFlag.cs @@ -5,23 +5,29 @@ using System; using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Localisation; namespace osu.Game.Users.Drawables { - public partial class DrawableFlag : BaseDrawableFlag, IHasTooltip + public partial class DrawableFlag : Sprite, IHasTooltip { - public LocalisableString TooltipText => CountryCode == CountryCode.Unknown ? string.Empty : CountryCode.GetDescription(); + private readonly CountryCode countryCode; - public DrawableFlag(CountryCode countryCode) : base(countryCode) { } + public LocalisableString TooltipText => countryCode == CountryCode.Unknown ? string.Empty : countryCode.GetDescription(); + + public DrawableFlag(CountryCode countryCode) + { + this.countryCode = countryCode; + } [BackgroundDependencyLoader] private void load(TextureStore ts) { ArgumentNullException.ThrowIfNull(ts); - string textureName = CountryCode == CountryCode.Unknown ? "__" : CountryCode.ToString(); + string textureName = countryCode == CountryCode.Unknown ? "__" : countryCode.ToString(); Texture = ts.Get($@"Flags/{textureName}") ?? ts.Get(@"Flags/__"); } } From be8b59e59d28ac99620e0252215ad7a4acf82604 Mon Sep 17 00:00:00 2001 From: yesseruser Date: Wed, 22 Nov 2023 15:25:35 +0100 Subject: [PATCH 1651/4092] Revert "Created and implemened a BaseUpdateableFlag." This reverts commit cd7e0bf620c0fd1617f1d99038dbbb85e52acd39. --- osu.Game/Screens/Play/HUD/PlayerFlag.cs | 4 +- .../Users/Drawables/BaseUpdateableFlag.cs | 45 ------------------- .../Drawables/ClickableUpdateableFlag.cs | 13 +++++- 3 files changed, 14 insertions(+), 48 deletions(-) delete mode 100644 osu.Game/Users/Drawables/BaseUpdateableFlag.cs diff --git a/osu.Game/Screens/Play/HUD/PlayerFlag.cs b/osu.Game/Screens/Play/HUD/PlayerFlag.cs index f0fe8ad668..7234db71b5 100644 --- a/osu.Game/Screens/Play/HUD/PlayerFlag.cs +++ b/osu.Game/Screens/Play/HUD/PlayerFlag.cs @@ -12,14 +12,14 @@ namespace osu.Game.Screens.Play.HUD { public partial class PlayerFlag : CompositeDrawable, ISerialisableDrawable { - private readonly BaseUpdateableFlag flag; + private readonly ClickableUpdateableFlag flag; private const float default_size = 40f; public PlayerFlag() { Size = new Vector2(default_size, default_size / 1.4f); - InternalChild = flag = new BaseUpdateableFlag + InternalChild = flag = new ClickableUpdateableFlag { RelativeSizeAxes = Axes.Both, }; diff --git a/osu.Game/Users/Drawables/BaseUpdateableFlag.cs b/osu.Game/Users/Drawables/BaseUpdateableFlag.cs deleted file mode 100644 index 16a368c60c..0000000000 --- a/osu.Game/Users/Drawables/BaseUpdateableFlag.cs +++ /dev/null @@ -1,45 +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.Framework.Graphics; -using osu.Framework.Graphics.Containers; - -namespace osu.Game.Users.Drawables -{ - public partial class BaseUpdateableFlag : ModelBackedDrawable - { - public CountryCode CountryCode - { - get => Model; - set => Model = value; - } - - /// - /// Whether to show a place holder on unknown country. - /// - public bool ShowPlaceholderOnUnknown = true; - - public BaseUpdateableFlag(CountryCode countryCode = CountryCode.Unknown) - { - CountryCode = countryCode; - } - - protected override Drawable? CreateDrawable(CountryCode countryCode) - { - if (countryCode == CountryCode.Unknown && !ShowPlaceholderOnUnknown) - return null; - - return new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new DrawableFlag(countryCode) - { - RelativeSizeAxes = Axes.Both - }, - } - }; - } - } -} diff --git a/osu.Game/Users/Drawables/ClickableUpdateableFlag.cs b/osu.Game/Users/Drawables/ClickableUpdateableFlag.cs index e00fe3c009..d19234fc17 100644 --- a/osu.Game/Users/Drawables/ClickableUpdateableFlag.cs +++ b/osu.Game/Users/Drawables/ClickableUpdateableFlag.cs @@ -11,8 +11,19 @@ using osu.Game.Overlays; namespace osu.Game.Users.Drawables { - public partial class ClickableUpdateableFlag : BaseUpdateableFlag + public partial class ClickableUpdateableFlag : ModelBackedDrawable { + public CountryCode CountryCode + { + get => Model; + set => Model = value; + } + + /// + /// Whether to show a place holder on unknown country. + /// + public bool ShowPlaceholderOnUnknown = true; + /// /// Perform an action in addition to showing the country ranking. /// This should be used to perform auxiliary tasks and not as a primary action for clicking a flag (to maintain a consistent UX). From 08e0279d72d1f76f099c3cda6f6dd0c086123501 Mon Sep 17 00:00:00 2001 From: yesseruser Date: Wed, 22 Nov 2023 15:25:43 +0100 Subject: [PATCH 1652/4092] Revert "Renamed UpdateableFlag to ClickableUpdateableFlag." This reverts commit 671177e87120e4cacba35f824950d0847cbeacf6. --- osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs | 2 +- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 2 +- osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs | 2 +- osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs | 4 ++-- osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs | 4 ++-- osu.Game/Overlays/Rankings/CountryPill.cs | 4 ++-- osu.Game/Overlays/Rankings/Tables/RankingsTable.cs | 2 +- .../OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs | 2 +- osu.Game/Screens/Play/HUD/PlayerFlag.cs | 4 ++-- .../{ClickableUpdateableFlag.cs => UpdateableFlag.cs} | 4 ++-- osu.Game/Users/ExtendedUserPanel.cs | 2 +- 11 files changed, 16 insertions(+), 16 deletions(-) rename osu.Game/Users/Drawables/{ClickableUpdateableFlag.cs => UpdateableFlag.cs} (91%) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs b/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs index 7a7679c376..0bc71924ce 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs @@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual.Menus AddStep("click on flag", () => { - InputManager.MoveMouseTo(loginOverlay.ChildrenOfType().First()); + InputManager.MoveMouseTo(loginOverlay.ChildrenOfType().First()); InputManager.Click(MouseButton.Left); }); AddAssert("login overlay is hidden", () => loginOverlay.State.Value == Visibility.Hidden); diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 114ef5db22..136c9cc8e7 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -180,7 +180,7 @@ namespace osu.Game.Online.Leaderboards Masking = true, Children = new Drawable[] { - new ClickableUpdateableFlag(user.CountryCode) + new UpdateableFlag(user.CountryCode) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index e144a55a96..1fc997fdad 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -157,7 +157,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Margin = new MarginPadding { Right = horizontal_inset }, Text = score.DisplayAccuracy, }, - new ClickableUpdateableFlag(score.User.CountryCode) + new UpdateableFlag(score.User.CountryCode) { Size = new Vector2(19, 14), ShowPlaceholderOnUnknown = false, diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs index e38e6efd06..9dc2ce204f 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs @@ -27,7 +27,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly UpdateableAvatar avatar; private readonly LinkFlowContainer usernameText; private readonly DrawableDate achievedOn; - private readonly ClickableUpdateableFlag flag; + private readonly UpdateableFlag flag; public TopScoreUserSection() { @@ -112,7 +112,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores }, } }, - flag = new ClickableUpdateableFlag + flag = new UpdateableFlag { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index 15aaf333f4..36bd8a5af5 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -41,7 +41,7 @@ namespace osu.Game.Overlays.Profile.Header private OsuSpriteText usernameText = null!; private ExternalLinkButton openUserExternally = null!; private OsuSpriteText titleText = null!; - private ClickableUpdateableFlag userFlag = null!; + private UpdateableFlag userFlag = null!; private OsuHoverContainer userCountryContainer = null!; private OsuSpriteText userCountryText = null!; private GroupBadgeFlow groupBadgeFlow = null!; @@ -162,7 +162,7 @@ namespace osu.Game.Overlays.Profile.Header Direction = FillDirection.Horizontal, Children = new Drawable[] { - userFlag = new ClickableUpdateableFlag + userFlag = new UpdateableFlag { Size = new Vector2(28, 20), ShowPlaceholderOnUnknown = false, diff --git a/osu.Game/Overlays/Rankings/CountryPill.cs b/osu.Game/Overlays/Rankings/CountryPill.cs index bfa7363de8..294b6df34d 100644 --- a/osu.Game/Overlays/Rankings/CountryPill.cs +++ b/osu.Game/Overlays/Rankings/CountryPill.cs @@ -34,7 +34,7 @@ namespace osu.Game.Overlays.Rankings private readonly Container content; private readonly Box background; - private readonly ClickableUpdateableFlag flag; + private readonly UpdateableFlag flag; private readonly OsuSpriteText countryName; public CountryPill() @@ -74,7 +74,7 @@ namespace osu.Game.Overlays.Rankings Spacing = new Vector2(5, 0), Children = new Drawable[] { - flag = new ClickableUpdateableFlag + flag = new UpdateableFlag { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index b68ecd709a..27d894cdc2 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -96,7 +96,7 @@ namespace osu.Game.Overlays.Rankings.Tables Margin = new MarginPadding { Bottom = row_spacing }, Children = new[] { - new ClickableUpdateableFlag(GetCountryCode(item)) + new UpdateableFlag(GetCountryCode(item)) { Size = new Vector2(28, 20), ShowPlaceholderOnUnknown = false, diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index 1f922073ec..c79c210e30 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -123,7 +123,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants FillMode = FillMode.Fit, User = user }, - new ClickableUpdateableFlag + new UpdateableFlag { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game/Screens/Play/HUD/PlayerFlag.cs b/osu.Game/Screens/Play/HUD/PlayerFlag.cs index 7234db71b5..85799c03d3 100644 --- a/osu.Game/Screens/Play/HUD/PlayerFlag.cs +++ b/osu.Game/Screens/Play/HUD/PlayerFlag.cs @@ -12,14 +12,14 @@ namespace osu.Game.Screens.Play.HUD { public partial class PlayerFlag : CompositeDrawable, ISerialisableDrawable { - private readonly ClickableUpdateableFlag flag; + private readonly UpdateableFlag flag; private const float default_size = 40f; public PlayerFlag() { Size = new Vector2(default_size, default_size / 1.4f); - InternalChild = flag = new ClickableUpdateableFlag + InternalChild = flag = new UpdateableFlag { RelativeSizeAxes = Axes.Both, }; diff --git a/osu.Game/Users/Drawables/ClickableUpdateableFlag.cs b/osu.Game/Users/Drawables/UpdateableFlag.cs similarity index 91% rename from osu.Game/Users/Drawables/ClickableUpdateableFlag.cs rename to osu.Game/Users/Drawables/UpdateableFlag.cs index d19234fc17..8f8d7052e5 100644 --- a/osu.Game/Users/Drawables/ClickableUpdateableFlag.cs +++ b/osu.Game/Users/Drawables/UpdateableFlag.cs @@ -11,7 +11,7 @@ using osu.Game.Overlays; namespace osu.Game.Users.Drawables { - public partial class ClickableUpdateableFlag : ModelBackedDrawable + public partial class UpdateableFlag : ModelBackedDrawable { public CountryCode CountryCode { @@ -30,7 +30,7 @@ namespace osu.Game.Users.Drawables /// public Action? Action; - public ClickableUpdateableFlag(CountryCode countryCode = CountryCode.Unknown) + public UpdateableFlag(CountryCode countryCode = CountryCode.Unknown) { CountryCode = countryCode; } diff --git a/osu.Game/Users/ExtendedUserPanel.cs b/osu.Game/Users/ExtendedUserPanel.cs index e798c8cc11..3c1b68f9ef 100644 --- a/osu.Game/Users/ExtendedUserPanel.cs +++ b/osu.Game/Users/ExtendedUserPanel.cs @@ -52,7 +52,7 @@ namespace osu.Game.Users protected UpdateableAvatar CreateAvatar() => new UpdateableAvatar(User, false); - protected ClickableUpdateableFlag CreateFlag() => new ClickableUpdateableFlag(User.CountryCode) + protected UpdateableFlag CreateFlag() => new UpdateableFlag(User.CountryCode) { Size = new Vector2(36, 26), Action = Action, From 82fec4194d9cb676baa2ad8e35a863d21010394a Mon Sep 17 00:00:00 2001 From: yesseruser Date: Wed, 22 Nov 2023 15:45:32 +0100 Subject: [PATCH 1653/4092] Disabled RecievePositionalInputAtSubTree in PlayerFlag. --- osu.Game/Screens/Play/HUD/PlayerFlag.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/PlayerFlag.cs b/osu.Game/Screens/Play/HUD/PlayerFlag.cs index 85799c03d3..70ad078e34 100644 --- a/osu.Game/Screens/Play/HUD/PlayerFlag.cs +++ b/osu.Game/Screens/Play/HUD/PlayerFlag.cs @@ -12,6 +12,8 @@ namespace osu.Game.Screens.Play.HUD { public partial class PlayerFlag : CompositeDrawable, ISerialisableDrawable { + protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => false; + private readonly UpdateableFlag flag; private const float default_size = 40f; From 9fd0641238fe8f21ade0528476245d2e23205f56 Mon Sep 17 00:00:00 2001 From: Rowe Wilson Frederisk Holme Date: Thu, 23 Nov 2023 01:18:55 +0800 Subject: [PATCH 1654/4092] Remove manual changes to Xcode versions in CI --- .github/workflows/ci.yml | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 11c956bb71..103e4dbc30 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -127,24 +127,14 @@ jobs: build-only-ios: name: Build only (iOS) - # `macos-13` is required, because Xcode 14.3 is required (see below). - # TODO: can be changed to `macos-latest` once `macos-13` becomes latest (currently in beta) + # `macos-13` is required, because the newest Microsoft.iOS.Sdk versions require Xcode 14.3. + # TODO: can be changed to `macos-latest` once `macos-13` becomes latest (currently in beta: https://github.com/actions/runner-images/tree/main#available-images) runs-on: macos-13 timeout-minutes: 60 steps: - name: Checkout uses: actions/checkout@v3 - # newest Microsoft.iOS.Sdk versions require Xcode 14.3. - # 14.3 is currently not the default Xcode version (https://github.com/actions/runner-images/blob/main/images/macos/macos-13-Readme.md#xcode), - # so set it manually. - # TODO: remove when 14.3 becomes the default Xcode version. - - name: Set Xcode version - shell: bash - run: | - sudo xcode-select -s "/Applications/Xcode_14.3.app" - echo "MD_APPLE_SDK_ROOT=/Applications/Xcode_14.3.app" >> $GITHUB_ENV - - name: Install .NET 6.0.x uses: actions/setup-dotnet@v3 with: From 3441a9a5b50bc5093dd06c232a96c4a4cbb5ccc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 23 Nov 2023 08:12:34 +0900 Subject: [PATCH 1655/4092] Add test coverage for classic scoring overflowing in osu! ruleset --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index cba90b2ebe..c957ddd7d3 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -10,14 +10,17 @@ using NUnit.Framework; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko; using osu.Game.Rulesets.UI; using osu.Game.Scoring.Legacy; using osu.Game.Tests.Beatmaps; @@ -117,6 +120,35 @@ namespace osu.Game.Tests.Rulesets.Scoring Assert.That(scoreProcessor.GetDisplayScore(scoringMode), Is.EqualTo(expectedScore).Within(0.5d)); } + [TestCase(typeof(OsuRuleset))] + [TestCase(typeof(TaikoRuleset))] + [TestCase(typeof(CatchRuleset))] + [TestCase(typeof(ManiaRuleset))] + public void TestBeatmapWithALotOfObjectsDoesNotOverflowClassicScore(Type rulesetType) + { + const int object_count = 999999; + + var ruleset = (Ruleset)Activator.CreateInstance(rulesetType)!; + scoreProcessor = new ScoreProcessor(ruleset); + + var largeBeatmap = new TestBeatmap(ruleset.RulesetInfo) + { + HitObjects = new List(Enumerable.Repeat(new TestHitObject(HitResult.Great), object_count)) + }; + scoreProcessor.ApplyBeatmap(largeBeatmap); + + for (int i = 0; i < object_count; ++i) + { + var judgementResult = new JudgementResult(largeBeatmap.HitObjects[i], largeBeatmap.HitObjects[i].CreateJudgement()) + { + Type = HitResult.Great + }; + scoreProcessor.ApplyResult(judgementResult); + } + + Assert.That(scoreProcessor.GetDisplayScore(ScoringMode.Classic), Is.GreaterThan(0)); + } + [Test] public void TestEmptyBeatmap( [Values(ScoringMode.Standardised, ScoringMode.Classic)] From e28e0ef1cc5847ef4c596ea380c77270089bbcef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 23 Nov 2023 08:15:46 +0900 Subject: [PATCH 1656/4092] Fix classic scoring overflowing in osu! ruleset due to integer multiplication overflow Closes https://github.com/ppy/osu/issues/25545. --- osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs b/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs index f6ea5aa455..07c35a334f 100644 --- a/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs +++ b/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs @@ -50,7 +50,7 @@ namespace osu.Game.Scoring.Legacy switch (rulesetId) { case 0: - return (long)Math.Round((objectCount * objectCount * 32.57 + 100000) * standardisedTotalScore / ScoreProcessor.MAX_SCORE); + return (long)Math.Round((Math.Pow(objectCount, 2) * 32.57 + 100000) * standardisedTotalScore / ScoreProcessor.MAX_SCORE); case 1: return (long)Math.Round((objectCount * 1109 + 100000) * standardisedTotalScore / ScoreProcessor.MAX_SCORE); From 4edaaa30839a203eded9bfc16e1dee88b3fb6456 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 23 Nov 2023 09:45:38 +0900 Subject: [PATCH 1657/4092] Add test coverage of skin editor copy-paste --- .../Visual/Gameplay/TestSceneSkinEditor.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index 9cd87932c8..3c97700fb0 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -335,6 +335,40 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("value is less than default", () => hitErrorMeter.JudgementLineThickness.Value < hitErrorMeter.JudgementLineThickness.Default); } + [Test] + public void TestCopyPaste() + { + AddStep("paste", () => + { + InputManager.PressKey(Key.LControl); + InputManager.Key(Key.V); + InputManager.ReleaseKey(Key.LControl); + }); + // no assertions. just make sure nothing crashes. + + AddStep("select bar hit error blueprint", () => + { + var blueprint = skinEditor.ChildrenOfType().First(b => b.Item is BarHitErrorMeter); + skinEditor.SelectedComponents.Clear(); + skinEditor.SelectedComponents.Add(blueprint.Item); + }); + AddStep("copy", () => + { + InputManager.PressKey(Key.LControl); + InputManager.Key(Key.C); + InputManager.ReleaseKey(Key.LControl); + }); + AddStep("paste", () => + { + InputManager.PressKey(Key.LControl); + InputManager.Key(Key.V); + InputManager.ReleaseKey(Key.LControl); + }); + AddAssert("three hit error meters present", + () => skinEditor.ChildrenOfType().Count(b => b.Item is BarHitErrorMeter), + () => Is.EqualTo(3)); + } + protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); private partial class TestSkinEditorChangeHandler : SkinEditorChangeHandler From abbcdaa7f7ad076053daa246668e5020fd8e3462 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 23 Nov 2023 09:55:27 +0900 Subject: [PATCH 1658/4092] Fix skin editor crashing when pasting with nothing in clipboard --- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index a816031668..f972186333 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -510,6 +510,9 @@ namespace osu.Game.Overlays.SkinEditor protected void Paste() { + if (!canPaste.Value) + return; + changeHandler?.BeginChange(); var drawableInfo = JsonConvert.DeserializeObject(clipboard.Content.Value); From e8d3d26d1607d381718ae0bf3650aa6ad6239c80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 23 Nov 2023 09:26:31 +0900 Subject: [PATCH 1659/4092] Fix slider length not updating when adding new anchor via ctrl-click --- .../Components/PathControlPointVisualiser.cs | 12 ++++----- .../Sliders/SliderSelectionBlueprint.cs | 27 +++++++++++++------ 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 1a94d6253d..3add95b2b2 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -159,9 +159,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components if (allowSelection) d.RequestSelection = selectionRequested; - d.DragStarted = dragStarted; - d.DragInProgress = dragInProgress; - d.DragEnded = dragEnded; + d.DragStarted = DragStarted; + d.DragInProgress = DragInProgress; + d.DragEnded = DragEnded; })); Connections.Add(new PathControlPointConnectionPiece(hitObject, e.NewStartingIndex + i)); @@ -267,7 +267,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private int draggedControlPointIndex; private HashSet selectedControlPoints; - private void dragStarted(PathControlPoint controlPoint) + public void DragStarted(PathControlPoint controlPoint) { dragStartPositions = hitObject.Path.ControlPoints.Select(point => point.Position).ToArray(); dragPathTypes = hitObject.Path.ControlPoints.Select(point => point.Type).ToArray(); @@ -279,7 +279,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components changeHandler?.BeginChange(); } - private void dragInProgress(DragEvent e) + public void DragInProgress(DragEvent e) { Vector2[] oldControlPoints = hitObject.Path.ControlPoints.Select(cp => cp.Position).ToArray(); var oldPosition = hitObject.Position; @@ -341,7 +341,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components hitObject.Path.ControlPoints[i].Type = dragPathTypes[i]; } - private void dragEnded() => changeHandler?.EndChange(); + public void DragEnded() => changeHandler?.EndChange(); #endregion diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 80c4cee7f2..a4b8064f05 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -39,9 +39,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders [CanBeNull] protected PathControlPointVisualiser ControlPointVisualiser { get; private set; } - [Resolved(CanBeNull = true)] - private IPositionSnapProvider positionSnapProvider { get; set; } - [Resolved(CanBeNull = true)] private IDistanceSnapProvider distanceSnapProvider { get; set; } @@ -191,15 +188,29 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders [CanBeNull] private PathControlPoint placementControlPoint; - protected override bool OnDragStart(DragStartEvent e) => placementControlPoint != null; + protected override bool OnDragStart(DragStartEvent e) + { + if (placementControlPoint == null) + return base.OnDragStart(e); + + ControlPointVisualiser?.DragStarted(placementControlPoint); + return true; + } protected override void OnDrag(DragEvent e) { + base.OnDrag(e); + if (placementControlPoint != null) - { - var result = positionSnapProvider?.FindSnappedPositionAndTime(ToScreenSpace(e.MousePosition)); - placementControlPoint.Position = ToLocalSpace(result?.ScreenSpacePosition ?? ToScreenSpace(e.MousePosition)) - HitObject.Position; - } + ControlPointVisualiser?.DragInProgress(e); + } + + protected override void OnDragEnd(DragEndEvent e) + { + base.OnDragEnd(e); + + if (placementControlPoint != null) + ControlPointVisualiser?.DragEnded(); } protected override void OnMouseUp(MouseUpEvent e) From 74966cae6ad9a98454731f19123a7044ff2d7e0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 23 Nov 2023 10:31:58 +0900 Subject: [PATCH 1660/4092] Remove unused using directive --- osu.Game.Rulesets.Osu.Tests/TestSceneOsuHealthProcessor.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuHealthProcessor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuHealthProcessor.cs index f810bbf155..16f28c0212 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuHealthProcessor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuHealthProcessor.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 NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Timing; From 7998204cfe1e529bc684b54f64356882ec2c81a2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 23 Nov 2023 13:41:01 +0900 Subject: [PATCH 1661/4092] Fix combo/combo colouring issues around spinners --- .../Beatmaps/CatchBeatmapProcessor.cs | 16 ++++++ .../Objects/CatchHitObject.cs | 25 +++++++++ .../Beatmaps/OsuBeatmapProcessor.cs | 54 ++++++++++++------- osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 25 +++++++++ .../Formats/LegacyBeatmapDecoderTest.cs | 48 +++++++++++++++++ .../Resources/spinner-between-objects.osu | 38 +++++++++++++ osu.Game/Beatmaps/BeatmapProcessor.cs | 6 --- .../Legacy/Catch/ConvertHitObjectParser.cs | 38 ++++--------- .../Legacy/Osu/ConvertHitObjectParser.cs | 38 ++++--------- 9 files changed, 210 insertions(+), 78 deletions(-) create mode 100644 osu.Game.Tests/Resources/spinner-between-objects.osu diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index ab61b14ac4..7c81ca03d1 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -23,6 +23,22 @@ namespace osu.Game.Rulesets.Catch.Beatmaps { } + public override void PreProcess() + { + IHasComboInformation? lastObj = null; + + // For sanity, ensures that both the first hitobject and the first hitobject after a banana shower start a new combo. + // This is normally enforced by the legacy decoder, but is not enforced by the editor. + foreach (var obj in Beatmap.HitObjects.OfType()) + { + if (obj is not BananaShower && (lastObj == null || lastObj is BananaShower)) + obj.NewCombo = true; + lastObj = obj; + } + + base.PreProcess(); + } + public override void PostProcess() { base.PostProcess(); diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index b9fef6bf8c..d122758a4e 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -155,6 +155,31 @@ namespace osu.Game.Rulesets.Catch.Objects Scale = LegacyRulesetExtensions.CalculateScaleFromCircleSize(difficulty.CircleSize); } + public void UpdateComboInformation(IHasComboInformation? lastObj) + { + ComboIndex = lastObj?.ComboIndex ?? 0; + ComboIndexWithOffsets = lastObj?.ComboIndexWithOffsets ?? 0; + IndexInCurrentCombo = (lastObj?.IndexInCurrentCombo + 1) ?? 0; + + if (this is BananaShower) + { + // For the purpose of combo colours, spinners never start a new combo even if they are flagged as doing so. + return; + } + + // At decode time, the first hitobject in the beatmap and the first hitobject after a banana shower are both enforced to be a new combo, + // but this isn't directly enforced by the editor so the extra checks against the last hitobject are duplicated here. + if (NewCombo || lastObj == null || lastObj is BananaShower) + { + IndexInCurrentCombo = 0; + ComboIndex++; + ComboIndexWithOffsets += ComboOffset + 1; + + if (lastObj != null) + lastObj.LastInCombo = true; + } + } + protected override HitWindows CreateHitWindows() => HitWindows.Empty; #region Hit object conversion diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs index c081df3ac6..835c67ff19 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs @@ -2,9 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osuTK; @@ -19,6 +21,22 @@ namespace osu.Game.Rulesets.Osu.Beatmaps { } + public override void PreProcess() + { + IHasComboInformation? lastObj = null; + + // For sanity, ensures that both the first hitobject and the first hitobject after a spinner start a new combo. + // This is normally enforced by the legacy decoder, but is not enforced by the editor. + foreach (var obj in Beatmap.HitObjects.OfType()) + { + if (obj is not Spinner && (lastObj == null || lastObj is Spinner)) + obj.NewCombo = true; + lastObj = obj; + } + + base.PreProcess(); + } + public override void PostProcess() { base.PostProcess(); @@ -95,15 +113,15 @@ namespace osu.Game.Rulesets.Osu.Beatmaps { int n = i; /* We should check every note which has not yet got a stack. - * Consider the case we have two interwound stacks and this will make sense. - * - * o <-1 o <-2 - * o <-3 o <-4 - * - * We first process starting from 4 and handle 2, - * then we come backwards on the i loop iteration until we reach 3 and handle 1. - * 2 and 1 will be ignored in the i loop because they already have a stack value. - */ + * Consider the case we have two interwound stacks and this will make sense. + * + * o <-1 o <-2 + * o <-3 o <-4 + * + * We first process starting from 4 and handle 2, + * then we come backwards on the i loop iteration until we reach 3 and handle 1. + * 2 and 1 will be ignored in the i loop because they already have a stack value. + */ OsuHitObject objectI = beatmap.HitObjects[i]; if (objectI.StackHeight != 0 || objectI is Spinner) continue; @@ -111,9 +129,9 @@ namespace osu.Game.Rulesets.Osu.Beatmaps double stackThreshold = objectI.TimePreempt * beatmap.BeatmapInfo.StackLeniency; /* If this object is a hitcircle, then we enter this "special" case. - * It either ends with a stack of hitcircles only, or a stack of hitcircles that are underneath a slider. - * Any other case is handled by the "is Slider" code below this. - */ + * It either ends with a stack of hitcircles only, or a stack of hitcircles that are underneath a slider. + * Any other case is handled by the "is Slider" code below this. + */ if (objectI is HitCircle) { while (--n >= 0) @@ -135,10 +153,10 @@ namespace osu.Game.Rulesets.Osu.Beatmaps } /* This is a special case where hticircles are moved DOWN and RIGHT (negative stacking) if they are under the *last* slider in a stacked pattern. - * o==o <- slider is at original location - * o <- hitCircle has stack of -1 - * o <- hitCircle has stack of -2 - */ + * o==o <- slider is at original location + * o <- hitCircle has stack of -1 + * o <- hitCircle has stack of -2 + */ if (objectN is Slider && Vector2Extensions.Distance(objectN.EndPosition, objectI.Position) < stack_distance) { int offset = objectI.StackHeight - objectN.StackHeight + 1; @@ -169,8 +187,8 @@ namespace osu.Game.Rulesets.Osu.Beatmaps else if (objectI is Slider) { /* We have hit the first slider in a possible stack. - * From this point on, we ALWAYS stack positive regardless. - */ + * From this point on, we ALWAYS stack positive regardless. + */ while (--n >= startIndex) { OsuHitObject objectN = beatmap.HitObjects[n]; diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index d74d28c748..716c34d024 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -159,6 +159,31 @@ namespace osu.Game.Rulesets.Osu.Objects Scale = LegacyRulesetExtensions.CalculateScaleFromCircleSize(difficulty.CircleSize, true); } + public void UpdateComboInformation(IHasComboInformation? lastObj) + { + ComboIndex = lastObj?.ComboIndex ?? 0; + ComboIndexWithOffsets = lastObj?.ComboIndexWithOffsets ?? 0; + IndexInCurrentCombo = (lastObj?.IndexInCurrentCombo + 1) ?? 0; + + if (this is Spinner) + { + // For the purpose of combo colours, spinners never start a new combo even if they are flagged as doing so. + return; + } + + // At decode time, the first hitobject in the beatmap and the first hitobject after a spinner are both enforced to be a new combo, + // but this isn't directly enforced by the editor so the extra checks against the last hitobject are duplicated here. + if (NewCombo || lastObj == null || lastObj is Spinner) + { + IndexInCurrentCombo = 0; + ComboIndex++; + ComboIndexWithOffsets += ComboOffset + 1; + + if (lastObj != null) + lastObj.LastInCombo = true; + } + } + protected override HitWindows CreateHitWindows() => new OsuHitWindows(); } } diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index be1993957f..20f59384ab 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -12,6 +12,7 @@ using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Timing; using osu.Game.IO; +using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Mods; @@ -1108,5 +1109,52 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(((IHasCombo)beatmap.HitObjects[2]).NewCombo, Is.False); } } + + /// + /// Test cases that involve a spinner between two hitobjects. + /// + [Test] + public void TestSpinnerNewComboBetweenObjects([Values("osu", "catch")] string rulesetName) + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + + using (var resStream = TestResources.OpenResource("spinner-between-objects.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + Ruleset ruleset; + + switch (rulesetName) + { + case "osu": + ruleset = new OsuRuleset(); + break; + + case "catch": + ruleset = new CatchRuleset(); + break; + + default: + throw new ArgumentOutOfRangeException(nameof(rulesetName), rulesetName, null); + } + + var working = new TestWorkingBeatmap(decoder.Decode(stream)); + var playable = working.GetPlayableBeatmap(ruleset.RulesetInfo, Array.Empty()); + + // There's no good way to figure out these values other than to compare (in code) with osu!stable... + + Assert.That(((IHasComboInformation)playable.HitObjects[0]).ComboIndexWithOffsets, Is.EqualTo(1)); + Assert.That(((IHasComboInformation)playable.HitObjects[2]).ComboIndexWithOffsets, Is.EqualTo(2)); + Assert.That(((IHasComboInformation)playable.HitObjects[3]).ComboIndexWithOffsets, Is.EqualTo(2)); + Assert.That(((IHasComboInformation)playable.HitObjects[5]).ComboIndexWithOffsets, Is.EqualTo(3)); + Assert.That(((IHasComboInformation)playable.HitObjects[6]).ComboIndexWithOffsets, Is.EqualTo(3)); + Assert.That(((IHasComboInformation)playable.HitObjects[8]).ComboIndexWithOffsets, Is.EqualTo(4)); + Assert.That(((IHasComboInformation)playable.HitObjects[9]).ComboIndexWithOffsets, Is.EqualTo(4)); + Assert.That(((IHasComboInformation)playable.HitObjects[11]).ComboIndexWithOffsets, Is.EqualTo(5)); + Assert.That(((IHasComboInformation)playable.HitObjects[12]).ComboIndexWithOffsets, Is.EqualTo(6)); + Assert.That(((IHasComboInformation)playable.HitObjects[14]).ComboIndexWithOffsets, Is.EqualTo(7)); + Assert.That(((IHasComboInformation)playable.HitObjects[15]).ComboIndexWithOffsets, Is.EqualTo(8)); + Assert.That(((IHasComboInformation)playable.HitObjects[17]).ComboIndexWithOffsets, Is.EqualTo(9)); + } + } } } diff --git a/osu.Game.Tests/Resources/spinner-between-objects.osu b/osu.Game.Tests/Resources/spinner-between-objects.osu new file mode 100644 index 0000000000..03e61d965c --- /dev/null +++ b/osu.Game.Tests/Resources/spinner-between-objects.osu @@ -0,0 +1,38 @@ +osu file format v14 + +[General] +Mode: 0 + +[TimingPoints] +0,571.428571428571,4,2,1,5,1,0 + +[HitObjects] +// +C -> +C -> +C +104,95,0,5,0,0:0:0:0: +256,192,1000,12,0,2000,0:0:0:0: +178,171,3000,5,0,0:0:0:0: + +// -C -> +C -> +C +178,171,4000,1,0,0:0:0:0: +256,192,5000,12,0,6000,0:0:0:0: +178,171,7000,5,0,0:0:0:0: + +// -C -> -C -> +C +178,171,8000,1,0,0:0:0:0: +256,192,9000,8,0,10000,0:0:0:0: +178,171,11000,5,0,0:0:0:0: + +// -C -> -C -> -C +178,171,12000,1,0,0:0:0:0: +256,192,13000,8,0,14000,0:0:0:0: +178,171,15000,1,0,0:0:0:0: + +// +C -> -C -> -C +178,171,16000,5,0,0:0:0:0: +256,192,17000,8,0,18000,0:0:0:0: +178,171,19000,1,0,0:0:0:0: + +// +C -> +C -> -C +178,171,20000,5,0,0:0:0:0: +256,192,21000,12,0,22000,0:0:0:0: +178,171,23000,1,0,0:0:0:0: \ No newline at end of file diff --git a/osu.Game/Beatmaps/BeatmapProcessor.cs b/osu.Game/Beatmaps/BeatmapProcessor.cs index fb5313469f..89d6e9d3f8 100644 --- a/osu.Game/Beatmaps/BeatmapProcessor.cs +++ b/osu.Game/Beatmaps/BeatmapProcessor.cs @@ -24,12 +24,6 @@ namespace osu.Game.Beatmaps foreach (var obj in Beatmap.HitObjects.OfType()) { - if (lastObj == null) - { - // first hitobject should always be marked as a new combo for sanity. - obj.NewCombo = true; - } - obj.UpdateComboInformation(lastObj); lastObj = obj; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs index 4861e8b3f7..0ed5aef0cf 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs @@ -14,26 +14,19 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch /// public class ConvertHitObjectParser : Legacy.ConvertHitObjectParser { + private ConvertHitObject lastObject; + public ConvertHitObjectParser(double offset, int formatVersion) : base(offset, formatVersion) { } - private bool forceNewCombo; - private int extraComboOffset; - protected override HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset) { - newCombo |= forceNewCombo; - comboOffset += extraComboOffset; - - forceNewCombo = false; - extraComboOffset = 0; - - return new ConvertHit + return lastObject = new ConvertHit { Position = position, - NewCombo = newCombo, + NewCombo = FirstObject || lastObject is ConvertSpinner || newCombo, ComboOffset = comboOffset }; } @@ -41,16 +34,10 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount, IList> nodeSamples) { - newCombo |= forceNewCombo; - comboOffset += extraComboOffset; - - forceNewCombo = false; - extraComboOffset = 0; - - return new ConvertSlider + return lastObject = new ConvertSlider { Position = position, - NewCombo = FirstObject || newCombo, + NewCombo = FirstObject || lastObject is ConvertSpinner || newCombo, ComboOffset = comboOffset, Path = new SliderPath(controlPoints, length), NodeSamples = nodeSamples, @@ -60,20 +47,17 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) { - // Convert spinners don't create the new combo themselves, but force the next non-spinner hitobject to create a new combo - // Their combo offset is still added to that next hitobject's combo index - forceNewCombo |= FormatVersion <= 8 || newCombo; - extraComboOffset += comboOffset; - - return new ConvertSpinner + return lastObject = new ConvertSpinner { - Duration = duration + Duration = duration, + NewCombo = newCombo + // Spinners cannot have combo offset. }; } protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration) { - return null; + return lastObject = null; } } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs index 7a88a31bd5..8bb9600a7d 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs @@ -14,26 +14,19 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu /// public class ConvertHitObjectParser : Legacy.ConvertHitObjectParser { + private ConvertHitObject lastObject; + public ConvertHitObjectParser(double offset, int formatVersion) : base(offset, formatVersion) { } - private bool forceNewCombo; - private int extraComboOffset; - protected override HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset) { - newCombo |= forceNewCombo; - comboOffset += extraComboOffset; - - forceNewCombo = false; - extraComboOffset = 0; - - return new ConvertHit + return lastObject = new ConvertHit { Position = position, - NewCombo = FirstObject || newCombo, + NewCombo = FirstObject || lastObject is ConvertSpinner || newCombo, ComboOffset = comboOffset }; } @@ -41,16 +34,10 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount, IList> nodeSamples) { - newCombo |= forceNewCombo; - comboOffset += extraComboOffset; - - forceNewCombo = false; - extraComboOffset = 0; - - return new ConvertSlider + return lastObject = new ConvertSlider { Position = position, - NewCombo = FirstObject || newCombo, + NewCombo = FirstObject || lastObject is ConvertSpinner || newCombo, ComboOffset = comboOffset, Path = new SliderPath(controlPoints, length), NodeSamples = nodeSamples, @@ -60,21 +47,18 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) { - // Convert spinners don't create the new combo themselves, but force the next non-spinner hitobject to create a new combo - // Their combo offset is still added to that next hitobject's combo index - forceNewCombo |= FormatVersion <= 8 || newCombo; - extraComboOffset += comboOffset; - - return new ConvertSpinner + return lastObject = new ConvertSpinner { Position = position, - Duration = duration + Duration = duration, + NewCombo = newCombo + // Spinners cannot have combo offset. }; } protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration) { - return null; + return lastObject = null; } } } From 3da8a0cbed624319841b479e2cced91fa8608cd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 23 Nov 2023 14:00:42 +0900 Subject: [PATCH 1662/4092] Fix undo being broken when ctrl-click and dragging new point --- .../Blueprints/Sliders/SliderSelectionBlueprint.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index a4b8064f05..b3efe1c495 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -205,18 +205,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders ControlPointVisualiser?.DragInProgress(e); } - protected override void OnDragEnd(DragEndEvent e) - { - base.OnDragEnd(e); - - if (placementControlPoint != null) - ControlPointVisualiser?.DragEnded(); - } - protected override void OnMouseUp(MouseUpEvent e) { if (placementControlPoint != null) { + if (IsDragged) + ControlPointVisualiser?.DragEnded(); + placementControlPoint = null; changeHandler?.EndChange(); } From 191e8c5487ccd4ea1b85aa7f47e6454f5a180c1d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Nov 2023 16:39:05 +0900 Subject: [PATCH 1663/4092] Add note about skin editor reload jank --- osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs index cbe122395c..d1e7b97efc 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs @@ -210,6 +210,9 @@ namespace osu.Game.Overlays.SkinEditor // The skin editor doesn't work well if beatmap skins are being applied to the player screen. // To keep things simple, disable the setting game-wide while using the skin editor. + // + // This causes a full reload of the skin, which is pretty ugly. + // TODO: Investigate if we can avoid this when a beatmap skin is not being applied by the current beatmap. leasedBeatmapSkins = beatmapSkins.BeginLease(true); leasedBeatmapSkins.Value = false; } From a80a5be4ec01c179069b57ee384a7196670a0f85 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Nov 2023 17:11:40 +0900 Subject: [PATCH 1664/4092] Fix a couple of new r# inspections --- osu.Game/Beatmaps/Beatmap.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 4f81b26c3e..0cafd4e08a 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -119,12 +119,11 @@ namespace osu.Game.Beatmaps IBeatmap IBeatmap.Clone() => Clone(); public Beatmap Clone() => (Beatmap)MemberwiseClone(); + + public override string ToString() => BeatmapInfo.ToString(); } public class Beatmap : Beatmap { - public new Beatmap Clone() => (Beatmap)base.Clone(); - - public override string ToString() => BeatmapInfo?.ToString() ?? base.ToString(); } } From 5239fee947435bf4fe99b43d28a7fe0118ca64b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Nov 2023 17:15:03 +0900 Subject: [PATCH 1665/4092] Allow use of skin username/flag/avatar components outside of gameplay --- osu.Game/Screens/Play/HUD/PlayerAvatar.cs | 20 ++++++++++++++++++-- osu.Game/Screens/Play/HUD/PlayerFlag.cs | 22 ++++++++++++++++++++-- osu.Game/Skinning/Components/PlayerName.cs | 21 +++++++++++++++++++-- 3 files changed, 57 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/PlayerAvatar.cs b/osu.Game/Screens/Play/HUD/PlayerAvatar.cs index 1341a10d60..06d0f7bc9a 100644 --- a/osu.Game/Screens/Play/HUD/PlayerAvatar.cs +++ b/osu.Game/Screens/Play/HUD/PlayerAvatar.cs @@ -7,6 +7,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Configuration; using osu.Game.Localisation.SkinComponents; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Settings; using osu.Game.Skinning; using osu.Game.Users.Drawables; @@ -29,6 +31,14 @@ namespace osu.Game.Screens.Play.HUD private const float default_size = 80f; + [Resolved] + private GameplayState? gameplayState { get; set; } + + [Resolved] + private IAPIProvider api { get; set; } = null!; + + private IBindable? apiUser; + public PlayerAvatar() { Size = new Vector2(default_size); @@ -41,9 +51,15 @@ namespace osu.Game.Screens.Play.HUD } [BackgroundDependencyLoader] - private void load(GameplayState gameplayState) + private void load() { - avatar.User = gameplayState.Score.ScoreInfo.User; + if (gameplayState != null) + avatar.User = gameplayState.Score.ScoreInfo.User; + else + { + apiUser = api.LocalUser.GetBoundCopy(); + apiUser.BindValueChanged(u => avatar.User = u.NewValue, true); + } } protected override void LoadComplete() diff --git a/osu.Game/Screens/Play/HUD/PlayerFlag.cs b/osu.Game/Screens/Play/HUD/PlayerFlag.cs index 70ad078e34..c7e247d26a 100644 --- a/osu.Game/Screens/Play/HUD/PlayerFlag.cs +++ b/osu.Game/Screens/Play/HUD/PlayerFlag.cs @@ -2,8 +2,11 @@ // 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.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Skinning; using osu.Game.Users.Drawables; using osuTK; @@ -18,9 +21,18 @@ namespace osu.Game.Screens.Play.HUD private const float default_size = 40f; + [Resolved] + private GameplayState? gameplayState { get; set; } + + [Resolved] + private IAPIProvider api { get; set; } = null!; + + private IBindable? apiUser; + public PlayerFlag() { Size = new Vector2(default_size, default_size / 1.4f); + InternalChild = flag = new UpdateableFlag { RelativeSizeAxes = Axes.Both, @@ -28,9 +40,15 @@ namespace osu.Game.Screens.Play.HUD } [BackgroundDependencyLoader] - private void load(GameplayState gameplayState) + private void load() { - flag.CountryCode = gameplayState.Score.ScoreInfo.User.CountryCode; + if (gameplayState != null) + flag.CountryCode = gameplayState.Score.ScoreInfo.User.CountryCode; + else + { + apiUser = api.LocalUser.GetBoundCopy(); + apiUser.BindValueChanged(u => flag.CountryCode = u.NewValue.CountryCode, true); + } } public bool UsesFixedAnchor { get; set; } diff --git a/osu.Game/Skinning/Components/PlayerName.cs b/osu.Game/Skinning/Components/PlayerName.cs index 34ace53d47..21bf615bc6 100644 --- a/osu.Game/Skinning/Components/PlayerName.cs +++ b/osu.Game/Skinning/Components/PlayerName.cs @@ -3,9 +3,12 @@ using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Sprites; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Screens.Play; namespace osu.Game.Skinning.Components @@ -15,6 +18,14 @@ namespace osu.Game.Skinning.Components { private readonly OsuSpriteText text; + [Resolved] + private GameplayState? gameplayState { get; set; } + + [Resolved] + private IAPIProvider api { get; set; } = null!; + + private IBindable? apiUser; + public PlayerName() { AutoSizeAxes = Axes.Both; @@ -30,9 +41,15 @@ namespace osu.Game.Skinning.Components } [BackgroundDependencyLoader] - private void load(GameplayState gameplayState) + private void load() { - text.Text = gameplayState.Score.ScoreInfo.User.Username; + if (gameplayState != null) + text.Text = gameplayState.Score.ScoreInfo.User.Username; + else + { + apiUser = api.LocalUser.GetBoundCopy(); + apiUser.BindValueChanged(u => text.Text = u.NewValue.Username, true); + } } protected override void SetFont(FontUsage font) => text.Font = font.With(size: 40); From 268b965ee890a3ea10ea152465eae6faede94e6c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Nov 2023 17:28:16 +0900 Subject: [PATCH 1666/4092] Enable NRT on `Beatmap` --- osu.Game/Beatmaps/Beatmap.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 0cafd4e08a..6db9febf36 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Objects; @@ -26,8 +24,7 @@ namespace osu.Game.Beatmaps { difficulty = value; - if (beatmapInfo != null) - beatmapInfo.Difficulty = difficulty.Clone(); + beatmapInfo.Difficulty = difficulty.Clone(); } } @@ -40,8 +37,7 @@ namespace osu.Game.Beatmaps { beatmapInfo = value; - if (beatmapInfo?.Difficulty != null) - Difficulty = beatmapInfo.Difficulty.Clone(); + Difficulty = beatmapInfo.Difficulty.Clone(); } } From c2a44cf1185dd28897f5488829124d7533cfb337 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 23 Nov 2023 23:30:18 +0200 Subject: [PATCH 1667/4092] Made custom tooltip --- .../Mods/AdjustedAttributesTooltip.cs | 158 ++++++++++++++++++ .../Overlays/Mods/BeatmapAttributesDisplay.cs | 46 ++--- .../Screens/Select/Details/AdvancedStats.cs | 38 ++--- 3 files changed, 188 insertions(+), 54 deletions(-) create mode 100644 osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs diff --git a/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs b/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs new file mode 100644 index 0000000000..e6a554d1fb --- /dev/null +++ b/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs @@ -0,0 +1,158 @@ +// 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 osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Framework.Utils; +using osuTK; + +namespace osu.Game.Overlays.Mods +{ + public partial class AdjustedAttributesTooltip : CompositeDrawable, ITooltip + { + private Dictionary> attributes = new Dictionary>(); + + private Container content; + + private FillFlowContainer attributesFillFlow; + + [Resolved] + private OsuColour colours { get; set; } = null!; + + + public AdjustedAttributesTooltip() + { + // Need to be initialized in constructor to ensure accessability in AddAttribute function + InternalChild = content = new Container + { + AutoSizeAxes = Axes.Both + }; + attributesFillFlow = new FillFlowContainer + { + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both + }; + } + + [BackgroundDependencyLoader] + private void load() + { + AutoSizeAxes = Axes.Both; + + Masking = true; + CornerRadius = 15; + + content.AddRange(new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Gray1, + Alpha = 0.8f + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Padding = new MarginPadding {Vertical = 10, Horizontal = 15}, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = "One or more values are being adjusted by mods that change speed.", + }, + attributesFillFlow + } + } + }); + } + + private void checkAttributes() + { + foreach (var attribute in attributes) + { + if (!Precision.AlmostEquals(attribute.Value.Value.Old, attribute.Value.Value.New)) + { + content.Show(); + return; + } + } + content.Hide(); + } + + public void AddAttribute(string name) + { + Bindable newBindable = new Bindable(); + newBindable.BindValueChanged(_ => checkAttributes()); + attributes.Add(name, newBindable); + attributesFillFlow.Add(new AttributeDisplay(name, newBindable.GetBoundCopy())); + } + public void UpdateAttribute(string name, double oldValue, double newValue) + { + Bindable attribute = attributes[name]; + + OldNewPair attributeValue = attribute.Value; + attributeValue.Old = oldValue; + attributeValue.New = newValue; + + attribute.Value = attributeValue; + } + + protected override void Update() + { + } + public void SetContent(object content) + { + } + + public void Move(Vector2 pos) + { + Position = pos; + } + + private struct OldNewPair + { + public double Old, New; + } + + private partial class AttributeDisplay : CompositeDrawable + { + public Bindable AttributeValues = new Bindable(); + public string AttributeName; + + private OsuSpriteText text = new OsuSpriteText + { + Font = OsuFont.Default.With(weight: FontWeight.Bold) + }; + public AttributeDisplay(string name, Bindable boundCopy) + { + AutoSizeAxes = Axes.Both; + + AttributeName = name; + AttributeValues = boundCopy; + InternalChild = text; + AttributeValues.BindValueChanged(_ => update(), true); + } + + private void update() + { + if (Precision.AlmostEquals(AttributeValues.Value.Old, AttributeValues.Value.New)) + { + Hide(); + } + else + { + Show(); + text.Text = $"{AttributeName}: {(AttributeValues.Value.Old):0.0#} → {(AttributeValues.Value.New):0.0#}"; + } + } + } + } +} diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index 6270b2bb49..fa8f9cb7a4 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Framework.Input.Events; using osu.Framework.Localisation; -using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Configuration; @@ -28,7 +27,7 @@ namespace osu.Game.Overlays.Mods /// On the mod select overlay, this provides a local updating view of BPM, star rating and other /// difficulty attributes so the user can have a better insight into what mods are changing. /// - public partial class BeatmapAttributesDisplay : ModFooterInformationDisplay, IHasTooltip + public partial class BeatmapAttributesDisplay : ModFooterInformationDisplay, IHasCustomTooltip { private StarRatingDisplay starRatingDisplay = null!; private BPMDisplay bpmDisplay = null!; @@ -58,10 +57,11 @@ namespace osu.Game.Overlays.Mods private CancellationTokenSource? cancellationSource; private IBindable starDifficulty = null!; - private BeatmapDifficulty? originalDifficulty; - private BeatmapDifficulty? adjustedDifficulty; + private AdjustedAttributesTooltip rateAdjustTooltip = null!; + + public ITooltip GetCustomTooltip() => rateAdjustTooltip; + public object TooltipContent => this; - private bool haveRateChangedValues; private const float transition_duration = 250; @@ -70,6 +70,8 @@ namespace osu.Game.Overlays.Mods { const float shear = ShearedOverlayContainer.SHEAR; + rateAdjustTooltip = new AdjustedAttributesTooltip(); + LeftContent.AddRange(new Drawable[] { starRatingDisplay = new StarRatingDisplay(default, animated: true) @@ -105,7 +107,6 @@ namespace osu.Game.Overlays.Mods mods.BindValueChanged(_ => { modSettingChangeTracker?.Dispose(); - modSettingChangeTracker = new ModSettingChangeTracker(mods.Value); modSettingChangeTracker.SettingChanged += _ => updateValues(); updateValues(); @@ -125,6 +126,9 @@ namespace osu.Game.Overlays.Mods BeatmapInfo.BindValueChanged(_ => updateValues(), true); + rateAdjustTooltip.AddAttribute("AR"); + rateAdjustTooltip.AddAttribute("OD"); + updateCollapsedState(); } @@ -173,15 +177,16 @@ namespace osu.Game.Overlays.Mods bpmDisplay.Current.Value = BeatmapInfo.Value.BPM * rate; - originalDifficulty = new BeatmapDifficulty(BeatmapInfo.Value.Difficulty); + BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(BeatmapInfo.Value.Difficulty); foreach (var mod in mods.Value.OfType()) mod.ApplyToDifficulty(originalDifficulty); Ruleset ruleset = gameRuleset.Value.CreateInstance(); - adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(originalDifficulty, rate); + BeatmapDifficulty adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(originalDifficulty, rate); - haveRateChangedValues = hasRateAdjustedProperties(originalDifficulty, adjustedDifficulty); + rateAdjustTooltip.UpdateAttribute("AR", originalDifficulty.ApproachRate, adjustedDifficulty.ApproachRate); + rateAdjustTooltip.UpdateAttribute("OD", originalDifficulty.OverallDifficulty, adjustedDifficulty.OverallDifficulty); approachRateDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(originalDifficulty.ApproachRate, adjustedDifficulty.ApproachRate); overallDifficultyDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(originalDifficulty.OverallDifficulty, adjustedDifficulty.OverallDifficulty); @@ -197,29 +202,6 @@ namespace osu.Game.Overlays.Mods RightContent.FadeTo(Collapsed.Value && !IsHovered ? 0 : 1, transition_duration, Easing.OutQuint); } - public LocalisableString TooltipText - { - get - { - if (haveRateChangedValues) - { - return $"One or more values are being adjusted by mods that change speed." + - $" (AR {originalDifficulty?.ApproachRate ?? 0}→{(adjustedDifficulty?.ApproachRate ?? 0):0.0#}, " + - $"OD {originalDifficulty?.OverallDifficulty ?? 0}→{(adjustedDifficulty?.OverallDifficulty ?? 0):0.0#})"; - } - - return string.Empty; - } - } - - private static bool hasRateAdjustedProperties(BeatmapDifficulty a, BeatmapDifficulty b) - { - if (!Precision.AlmostEquals(a.ApproachRate, b.ApproachRate)) return true; - if (!Precision.AlmostEquals(a.OverallDifficulty, b.OverallDifficulty)) return true; - - return false; - } - private partial class BPMDisplay : RollingCounter { protected override double RollingDuration => 500; diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index f617cb1d8e..d3a6a88c68 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -26,10 +26,11 @@ using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; +using osu.Game.Overlays.Mods; namespace osu.Game.Screens.Select.Details { - public partial class AdvancedStats : Container, IHasTooltip + public partial class AdvancedStats : Container, IHasCustomTooltip { [Resolved] private BeatmapDifficultyCache difficultyCache { get; set; } @@ -45,9 +46,9 @@ namespace osu.Game.Screens.Select.Details protected readonly StatisticRow FirstValue, HpDrain, Accuracy, ApproachRate; private readonly StatisticRow starDifficulty; - private BeatmapDifficulty originalDifficulty; - private BeatmapDifficulty adjustedDifficulty; - private bool haveRateChangedValues; + private AdjustedAttributesTooltip rateAdjustTooltip; + public ITooltip GetCustomTooltip() => rateAdjustTooltip; + public object TooltipContent => this; private IBeatmapInfo beatmapInfo; @@ -85,6 +86,7 @@ namespace osu.Game.Screens.Select.Details private void load(OsuColour colours) { starDifficulty.AccentColour = colours.Yellow; + rateAdjustTooltip = new AdjustedAttributesTooltip(); } protected override void LoadComplete() @@ -99,6 +101,9 @@ namespace osu.Game.Screens.Select.Details gameRuleset.BindValueChanged(_ => updateStatistics()); mods.BindValueChanged(modsChanged, true); + + rateAdjustTooltip.AddAttribute("AR"); + rateAdjustTooltip.AddAttribute("OD"); } private ModSettingChangeTracker modSettingChangeTracker; @@ -121,14 +126,17 @@ namespace osu.Game.Screens.Select.Details private void updateStatistics() { IBeatmapDifficultyInfo baseDifficulty = BeatmapInfo?.Difficulty; + BeatmapDifficulty adjustedDifficulty = null; if (baseDifficulty != null) { - originalDifficulty = new BeatmapDifficulty(baseDifficulty); + BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(baseDifficulty); foreach (var mod in mods.Value.OfType()) mod.ApplyToDifficulty(originalDifficulty); + adjustedDifficulty = originalDifficulty; + if (gameRuleset != null) { Ruleset ruleset = gameRuleset.Value.CreateInstance(); @@ -138,7 +146,9 @@ namespace osu.Game.Screens.Select.Details rate = mod.ApplyToRate(0, rate); adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(originalDifficulty, rate); - haveRateChangedValues = hasRateAdjustedProperties(originalDifficulty, adjustedDifficulty); + + rateAdjustTooltip.UpdateAttribute("AR", originalDifficulty.ApproachRate, adjustedDifficulty.ApproachRate); + rateAdjustTooltip.UpdateAttribute("OD", originalDifficulty.OverallDifficulty, adjustedDifficulty.OverallDifficulty); } } @@ -204,22 +214,6 @@ namespace osu.Game.Screens.Select.Details starDifficultyCancellationSource?.Cancel(); } - public LocalisableString TooltipText - { - get - { - if (haveRateChangedValues) - { - // Rather than localising this, it should be displayed in a better way (a custom tooltip which isn't a single super-long line). - return "One or more values are being adjusted by mods that change speed." + - $" (AR {originalDifficulty?.ApproachRate ?? 0}→{(adjustedDifficulty?.ApproachRate ?? 0):0.0#}, " + - $"OD {originalDifficulty?.OverallDifficulty ?? 0}→{(adjustedDifficulty?.OverallDifficulty ?? 0):0.0#})"; - } - - return string.Empty; - } - } - private static bool hasRateAdjustedProperties(BeatmapDifficulty a, BeatmapDifficulty b) { if (!Precision.AlmostEquals(a.ApproachRate, b.ApproachRate)) return true; From 93e3156868d674dd3f9a79729c5561859d96f063 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Fri, 24 Nov 2023 01:07:37 +0200 Subject: [PATCH 1668/4092] slight format changes --- osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs b/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs index e6a554d1fb..3e800bfaf1 100644 --- a/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs +++ b/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs @@ -26,7 +26,6 @@ namespace osu.Game.Overlays.Mods [Resolved] private OsuColour colours { get; set; } = null!; - public AdjustedAttributesTooltip() { // Need to be initialized in constructor to ensure accessability in AddAttribute function @@ -60,7 +59,7 @@ namespace osu.Game.Overlays.Mods new FillFlowContainer { AutoSizeAxes = Axes.Both, - Padding = new MarginPadding {Vertical = 10, Horizontal = 15}, + Padding = new MarginPadding { Vertical = 10, Horizontal = 15 }, Direction = FillDirection.Vertical, Children = new Drawable[] { @@ -85,6 +84,7 @@ namespace osu.Game.Overlays.Mods } } content.Hide(); + } public void AddAttribute(string name) @@ -94,6 +94,7 @@ namespace osu.Game.Overlays.Mods attributes.Add(name, newBindable); attributesFillFlow.Add(new AttributeDisplay(name, newBindable.GetBoundCopy())); } + public void UpdateAttribute(string name, double oldValue, double newValue) { Bindable attribute = attributes[name]; @@ -124,7 +125,7 @@ namespace osu.Game.Overlays.Mods private partial class AttributeDisplay : CompositeDrawable { - public Bindable AttributeValues = new Bindable(); + public readonly Bindable AttributeValues; public string AttributeName; private OsuSpriteText text = new OsuSpriteText From 10e16e4b04c58774573c1b8fff66ee46b717bcdc Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 24 Nov 2023 09:46:06 +0900 Subject: [PATCH 1669/4092] Fix handling of combo offset without new combo, and incorrect lazer tests --- .../Formats/LegacyBeatmapDecoderTest.cs | 24 +++++++++---------- .../Resources/hitobject-combo-offset.osu | 12 +++++----- .../Legacy/Catch/ConvertHitObjectParser.cs | 4 ++-- .../Legacy/Osu/ConvertHitObjectParser.cs | 4 ++-- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index cccceaf58e..dcfe8ecb41 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -434,12 +434,12 @@ namespace osu.Game.Tests.Beatmaps.Formats new OsuBeatmapProcessor(converted).PreProcess(); new OsuBeatmapProcessor(converted).PostProcess(); - Assert.AreEqual(4, ((IHasComboInformation)converted.HitObjects.ElementAt(0)).ComboIndexWithOffsets); - Assert.AreEqual(5, ((IHasComboInformation)converted.HitObjects.ElementAt(2)).ComboIndexWithOffsets); - Assert.AreEqual(5, ((IHasComboInformation)converted.HitObjects.ElementAt(4)).ComboIndexWithOffsets); - Assert.AreEqual(6, ((IHasComboInformation)converted.HitObjects.ElementAt(6)).ComboIndexWithOffsets); - Assert.AreEqual(11, ((IHasComboInformation)converted.HitObjects.ElementAt(8)).ComboIndexWithOffsets); - Assert.AreEqual(14, ((IHasComboInformation)converted.HitObjects.ElementAt(11)).ComboIndexWithOffsets); + Assert.AreEqual(1, ((IHasComboInformation)converted.HitObjects.ElementAt(0)).ComboIndexWithOffsets); + Assert.AreEqual(2, ((IHasComboInformation)converted.HitObjects.ElementAt(2)).ComboIndexWithOffsets); + Assert.AreEqual(3, ((IHasComboInformation)converted.HitObjects.ElementAt(4)).ComboIndexWithOffsets); + Assert.AreEqual(4, ((IHasComboInformation)converted.HitObjects.ElementAt(6)).ComboIndexWithOffsets); + Assert.AreEqual(8, ((IHasComboInformation)converted.HitObjects.ElementAt(8)).ComboIndexWithOffsets); + Assert.AreEqual(9, ((IHasComboInformation)converted.HitObjects.ElementAt(11)).ComboIndexWithOffsets); } } @@ -457,12 +457,12 @@ namespace osu.Game.Tests.Beatmaps.Formats new CatchBeatmapProcessor(converted).PreProcess(); new CatchBeatmapProcessor(converted).PostProcess(); - Assert.AreEqual(4, ((IHasComboInformation)converted.HitObjects.ElementAt(0)).ComboIndexWithOffsets); - Assert.AreEqual(5, ((IHasComboInformation)converted.HitObjects.ElementAt(2)).ComboIndexWithOffsets); - Assert.AreEqual(5, ((IHasComboInformation)converted.HitObjects.ElementAt(4)).ComboIndexWithOffsets); - Assert.AreEqual(6, ((IHasComboInformation)converted.HitObjects.ElementAt(6)).ComboIndexWithOffsets); - Assert.AreEqual(11, ((IHasComboInformation)converted.HitObjects.ElementAt(8)).ComboIndexWithOffsets); - Assert.AreEqual(14, ((IHasComboInformation)converted.HitObjects.ElementAt(11)).ComboIndexWithOffsets); + Assert.AreEqual(1, ((IHasComboInformation)converted.HitObjects.ElementAt(0)).ComboIndexWithOffsets); + Assert.AreEqual(2, ((IHasComboInformation)converted.HitObjects.ElementAt(2)).ComboIndexWithOffsets); + Assert.AreEqual(3, ((IHasComboInformation)converted.HitObjects.ElementAt(4)).ComboIndexWithOffsets); + Assert.AreEqual(4, ((IHasComboInformation)converted.HitObjects.ElementAt(6)).ComboIndexWithOffsets); + Assert.AreEqual(8, ((IHasComboInformation)converted.HitObjects.ElementAt(8)).ComboIndexWithOffsets); + Assert.AreEqual(9, ((IHasComboInformation)converted.HitObjects.ElementAt(11)).ComboIndexWithOffsets); } } diff --git a/osu.Game.Tests/Resources/hitobject-combo-offset.osu b/osu.Game.Tests/Resources/hitobject-combo-offset.osu index d39a3e8548..9f39229d87 100644 --- a/osu.Game.Tests/Resources/hitobject-combo-offset.osu +++ b/osu.Game.Tests/Resources/hitobject-combo-offset.osu @@ -3,30 +3,30 @@ osu file format v14 [HitObjects] // Circle with combo offset (3) 255,193,1000,49,0,0:0:0:0: -// Combo index = 4 +// Combo index = 1 // Spinner with new combo followed by circle with no new combo 256,192,2000,12,0,2000,0:0:0:0: 255,193,3000,1,0,0:0:0:0: -// Combo index = 5 +// Combo index = 2 // Spinner without new combo followed by circle with no new combo 256,192,4000,8,0,5000,0:0:0:0: 255,193,6000,1,0,0:0:0:0: -// Combo index = 5 +// Combo index = 3 // Spinner without new combo followed by circle with new combo 256,192,7000,8,0,8000,0:0:0:0: 255,193,9000,5,0,0:0:0:0: -// Combo index = 6 +// Combo index = 4 // Spinner with new combo and offset (1) followed by circle with new combo and offset (3) 256,192,10000,28,0,11000,0:0:0:0: 255,193,12000,53,0,0:0:0:0: -// Combo index = 11 +// Combo index = 8 // Spinner with new combo and offset (2) followed by slider with no new combo followed by circle with no new combo 256,192,13000,44,0,14000,0:0:0:0: 256,192,15000,8,0,16000,0:0:0:0: 255,193,17000,1,0,0:0:0:0: -// Combo index = 14 \ No newline at end of file +// Combo index = 9 \ No newline at end of file diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs index 0ed5aef0cf..a5c1a73fa7 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch { Position = position, NewCombo = FirstObject || lastObject is ConvertSpinner || newCombo, - ComboOffset = comboOffset + ComboOffset = newCombo ? comboOffset : 0 }; } @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch { Position = position, NewCombo = FirstObject || lastObject is ConvertSpinner || newCombo, - ComboOffset = comboOffset, + ComboOffset = newCombo ? comboOffset : 0, Path = new SliderPath(controlPoints, length), NodeSamples = nodeSamples, RepeatCount = repeatCount diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs index 8bb9600a7d..43c346b621 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu { Position = position, NewCombo = FirstObject || lastObject is ConvertSpinner || newCombo, - ComboOffset = comboOffset + ComboOffset = newCombo ? comboOffset : 0 }; } @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu { Position = position, NewCombo = FirstObject || lastObject is ConvertSpinner || newCombo, - ComboOffset = comboOffset, + ComboOffset = newCombo ? comboOffset : 0, Path = new SliderPath(controlPoints, length), NodeSamples = nodeSamples, RepeatCount = repeatCount From 039f8e62421d13d23f532e146cadd97ec89e78d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Nov 2023 10:25:23 +0900 Subject: [PATCH 1670/4092] Add note about shared code --- osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs | 2 ++ osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index d122758a4e..17ff8afb87 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -157,6 +157,8 @@ namespace osu.Game.Rulesets.Catch.Objects public void UpdateComboInformation(IHasComboInformation? lastObj) { + // Note that this implementation is shared with the osu! ruleset's implementation. + // If a change is made here, OsuHitObject.cs should also be updated. ComboIndex = lastObj?.ComboIndex ?? 0; ComboIndexWithOffsets = lastObj?.ComboIndexWithOffsets ?? 0; IndexInCurrentCombo = (lastObj?.IndexInCurrentCombo + 1) ?? 0; diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 716c34d024..21f7b4b22d 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -161,6 +161,8 @@ namespace osu.Game.Rulesets.Osu.Objects public void UpdateComboInformation(IHasComboInformation? lastObj) { + // Note that this implementation is shared with the osu!catch ruleset's implementation. + // If a change is made here, CatchHitObject.cs should also be updated. ComboIndex = lastObj?.ComboIndex ?? 0; ComboIndexWithOffsets = lastObj?.ComboIndexWithOffsets ?? 0; IndexInCurrentCombo = (lastObj?.IndexInCurrentCombo + 1) ?? 0; From 7590bae445b331aa9d5d2bb4c1dd3730a9cb97c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 22 Nov 2023 12:50:08 +0900 Subject: [PATCH 1671/4092] Rename and comment everything in score migration code Hopefully, _hopefully_, makes all this a little bit less inscrutable. --- .../StandardisedScoreMigrationTools.cs | 98 ++++++++++++------- 1 file changed, 62 insertions(+), 36 deletions(-) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index fe51c6f130..77421908de 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -274,53 +274,79 @@ namespace osu.Game.Database + bonusProportion) * modMultiplier); } - // Assumption : - // - sliders and slider-ticks are uniformly spread arround the beatmap, and thus can be ignored without losing much precision. - // We thus consider a map of hit-circles only, which gives objectCount == maximumCombo ! + // Assumptions: + // - sliders and slider ticks are uniformly distributed in the beatmap, and thus can be ignored without losing much precision. + // We thus consider a map of hit-circles only, which gives objectCount == maximumCombo. // - the Ok/Meh hit results are uniformly spread in the score, and thus can be ignored without losing much precision. - // We simplify and consider each hit result to be equal to `300*score.Accuracy`, which allows us to isolate the accuracy multiplier. - // What is strippedV1/strippedNew : - // This is the ComboScore (of v1 / new score) were we remove all (map-)constant multipliers and accuracy multipliers (including hit results), - // based on the previous assumptions. - // We use integrals to approximate the sum of each object's combo contribution (thus the original combo exponent is increased by 1) - // For the maximum score, we thus integrate `f(combo) = 300*combo^exponent`, and ignoring the constant multipliers we get: - double maxStrippedV1 = Math.Pow(maximumLegacyCombo, 2); - double maxStrippedNew = Math.Pow(maximumLegacyCombo, 1 + ScoreProcessor.COMBO_EXPONENT); + // We simplify and consider each hit result to have the same hit value of `300 * score.Accuracy` + // (which represents the average hit value over the entire play), + // which allows us to isolate the accuracy multiplier. - double strippedV1 = maxStrippedV1 * comboProportion / score.Accuracy; + // This is a very ballpark estimate of the maximum magnitude of the combo portion in score V1. + // It is derived by assuming a full combo play and summing up the contribution to combo portion from each individual object. + // Because each object's combo contribution is proportional to the current combo at the time of judgement, + // this can be roughly represented by summing / integrating f(combo) = combo. + // All mod- and beatmap-dependent multipliers and constants are not included here, + // as we will only be using the magnitude of this to compute ratios. + double maximumAchievableComboPortionInScoreV1 = Math.Pow(maximumLegacyCombo, 2); + // Similarly, estimate the maximum magnitude of the combo portion in standardised score. + // Roughly corresponds to integrating f(combo) = combo ^ COMBO_EXPONENT (omitting constants) + double maximumAchievableComboPortionInStandardisedScore = Math.Pow(maximumLegacyCombo, 1 + ScoreProcessor.COMBO_EXPONENT); - double strippedV1FromMaxCombo = Math.Pow(score.MaxCombo, 2); - double strippedNewFromMaxCombo = Math.Pow(score.MaxCombo, 1 + ScoreProcessor.COMBO_EXPONENT); + double comboPortionInScoreV1 = maximumAchievableComboPortionInScoreV1 * comboProportion / score.Accuracy; - // Compute approximate lower estimate new score for that play - // That is, a play were we made biggest amount of big combos (Repeat MaxCombo + 1 remaining big combo) - // And didn't combo anything in the reminder of the map - double possibleMaxComboRepeat = Math.Floor(strippedV1 / strippedV1FromMaxCombo); - double strippedV1FromMaxComboRepeat = possibleMaxComboRepeat * strippedV1FromMaxCombo; - double remainingStrippedV1 = strippedV1 - strippedV1FromMaxComboRepeat; - double remainingCombo = Math.Sqrt(remainingStrippedV1); - double remainingStrippedNew = Math.Pow(remainingCombo, 1 + ScoreProcessor.COMBO_EXPONENT); + // This is - roughly - how much score, in the combo portion, the longest combo on this particular play would gain in score V1. + double comboPortionFromLongestComboInScoreV1 = Math.Pow(score.MaxCombo, 2); + // Same for standardised score. + double comboPortionFromLongestComboInStandardisedScore = Math.Pow(score.MaxCombo, 1 + ScoreProcessor.COMBO_EXPONENT); - double lowerStrippedNew = (possibleMaxComboRepeat * strippedNewFromMaxCombo) + remainingStrippedNew; + // Calculate how many times the longest combo the user has achieved in the play can repeat + // without exceeding the combo portion in score V1 as achieved by the player. + // This is a pessimistic estimate; it intentionally does not operate on object count and uses only score instead. + double maximumOccurrencesOfLongestCombo = Math.Floor(comboPortionInScoreV1 / comboPortionFromLongestComboInScoreV1); + double comboPortionFromRepeatedLongestCombosInScoreV1 = maximumOccurrencesOfLongestCombo * comboPortionFromLongestComboInScoreV1; - // Compute approximate upper estimate new score for that play - // That is, a play were all combos were equal (except MaxCombo) - remainingStrippedV1 = strippedV1 - strippedV1FromMaxCombo; - double remainingComboObjects = maximumLegacyCombo - score.MaxCombo - score.Statistics[HitResult.Miss]; - double remainingAverageCombo = remainingComboObjects > 0 ? remainingStrippedV1 / remainingComboObjects : 0; - remainingStrippedNew = remainingComboObjects * Math.Pow(remainingAverageCombo, ScoreProcessor.COMBO_EXPONENT); + double remainingComboPortionInScoreV1 = comboPortionInScoreV1 - comboPortionFromRepeatedLongestCombosInScoreV1; + // `remainingComboPortionInScoreV1` is in the "score ballpark" realm, which means it's proportional to combo squared. + // To convert that back to a raw combo length, we need to take the square root... + double remainingCombo = Math.Sqrt(remainingComboPortionInScoreV1); + // ...and then based on that raw combo length, we calculate how much this last combo is worth in standardised score. + double remainingComboPortionInStandardisedScore = Math.Pow(remainingCombo, 1 + ScoreProcessor.COMBO_EXPONENT); - double upperStrippedNew = strippedNewFromMaxCombo + remainingStrippedNew; + double lowerEstimateOfComboPortionInStandardisedScore + = maximumOccurrencesOfLongestCombo * comboPortionFromLongestComboInStandardisedScore + + remainingComboPortionInStandardisedScore; - // Approximate by combining lower and upper estimates + // Compute approximate upper estimate new score for that play. + // This time, divide the remaining combo among remaining objects equally to achieve longest possible combo lengths. + // There is no rigorous proof that doing this will yield a correct upper bound, but it seems to work out in practice. + remainingComboPortionInScoreV1 = comboPortionInScoreV1 - comboPortionFromLongestComboInScoreV1; + double remainingCountOfObjectsGivingCombo = maximumLegacyCombo - score.MaxCombo - score.Statistics[HitResult.Miss]; + // Because we assumed all combos were equal, `remainingComboPortionInScoreV1` + // can be approximated by n * x^2, wherein n is the assumed number of equal combos, + // and x is the assumed length of every one of those combos. + // The remaining count of objects giving combo is, using those terms, equal to n * x. + // Therefore, dividing the two will result in x, i.e. the assumed length of the remaining combos. + double lengthOfRemainingCombos = remainingCountOfObjectsGivingCombo > 0 + ? remainingComboPortionInScoreV1 / remainingCountOfObjectsGivingCombo + : 0; + // In standardised scoring, each combo yields a score proportional to combo length to the power 1 + COMBO_EXPONENT. + // Using the symbols introduced above, that would be x ^ 1.5 per combo, n times (because there are n assumed equal-length combos). + // However, because `remainingCountOfObjectsGivingCombo` - using the symbols introduced above - is assumed to be equal to n * x, + // we can skip adding the 1 and just multiply by x ^ 0.5. + remainingComboPortionInStandardisedScore = remainingCountOfObjectsGivingCombo * Math.Pow(lengthOfRemainingCombos, ScoreProcessor.COMBO_EXPONENT); + + double upperEstimateOfComboPortionInStandardisedScore = comboPortionFromLongestComboInStandardisedScore + remainingComboPortionInStandardisedScore; + + // Approximate by combining lower and upper estimates. // As the lower-estimate is very pessimistic, we use a 30/70 ratio - // And cap it with 1.2 times the middle-point to avoid overstimates - double strippedNew = Math.Min( - 0.3 * lowerStrippedNew + 0.7 * upperStrippedNew, - 1.2 * (lowerStrippedNew + upperStrippedNew) / 2 + // and cap it with 1.2 times the middle-point to avoid overestimates. + double estimatedComboPortionInStandardisedScore = Math.Min( + 0.3 * lowerEstimateOfComboPortionInStandardisedScore + 0.7 * upperEstimateOfComboPortionInStandardisedScore, + 1.2 * (lowerEstimateOfComboPortionInStandardisedScore + upperEstimateOfComboPortionInStandardisedScore) / 2 ); - double newComboScoreProportion = (strippedNew / maxStrippedNew); + double newComboScoreProportion = estimatedComboPortionInStandardisedScore / maximumAchievableComboPortionInStandardisedScore; return (long)Math.Round(( 500000 * newComboScoreProportion * score.Accuracy From 2a6885164ced88450bd17bc24aea91d55a08343d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 24 Nov 2023 10:46:21 +0900 Subject: [PATCH 1672/4092] Update tests to match new scoring expectations --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index c957ddd7d3..fd041d3dd0 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -45,11 +45,11 @@ namespace osu.Game.Tests.Rulesets.Scoring }; } - [TestCase(ScoringMode.Standardised, HitResult.Meh, 116_667)] - [TestCase(ScoringMode.Standardised, HitResult.Ok, 233_338)] + [TestCase(ScoringMode.Standardised, HitResult.Meh, 83_398)] + [TestCase(ScoringMode.Standardised, HitResult.Ok, 168_724)] [TestCase(ScoringMode.Standardised, HitResult.Great, 1_000_000)] - [TestCase(ScoringMode.Classic, HitResult.Meh, 11_670)] - [TestCase(ScoringMode.Classic, HitResult.Ok, 23_341)] + [TestCase(ScoringMode.Classic, HitResult.Meh, 8_343)] + [TestCase(ScoringMode.Classic, HitResult.Ok, 16_878)] [TestCase(ScoringMode.Classic, HitResult.Great, 100_033)] public void TestSingleOsuHit(ScoringMode scoringMode, HitResult hitResult, int expectedScore) { @@ -75,27 +75,27 @@ namespace osu.Game.Tests.Rulesets.Scoring /// This test intentionally misses the 3rd hitobject to achieve lower than 75% accuracy and 50% max combo. /// [TestCase(ScoringMode.Standardised, HitResult.Miss, HitResult.Great, 0)] - [TestCase(ScoringMode.Standardised, HitResult.Meh, HitResult.Great, 79_333)] - [TestCase(ScoringMode.Standardised, HitResult.Ok, HitResult.Great, 158_667)] - [TestCase(ScoringMode.Standardised, HitResult.Good, HitResult.Perfect, 317_626)] - [TestCase(ScoringMode.Standardised, HitResult.Great, HitResult.Great, 492_894)] - [TestCase(ScoringMode.Standardised, HitResult.Perfect, HitResult.Perfect, 492_894)] + [TestCase(ScoringMode.Standardised, HitResult.Meh, HitResult.Great, 34_734)] + [TestCase(ScoringMode.Standardised, HitResult.Ok, HitResult.Great, 69_925)] + [TestCase(ScoringMode.Standardised, HitResult.Good, HitResult.Perfect, 154_499)] + [TestCase(ScoringMode.Standardised, HitResult.Great, HitResult.Great, 326_963)] + [TestCase(ScoringMode.Standardised, HitResult.Perfect, HitResult.Perfect, 326_963)] [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, HitResult.SmallTickHit, 0)] - [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, HitResult.SmallTickHit, 541_894)] + [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, HitResult.SmallTickHit, 493_652)] [TestCase(ScoringMode.Standardised, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] - [TestCase(ScoringMode.Standardised, HitResult.LargeTickHit, HitResult.LargeTickHit, 492_894)] + [TestCase(ScoringMode.Standardised, HitResult.LargeTickHit, HitResult.LargeTickHit, 326_963)] [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 1_000_030)] [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 1_000_150)] [TestCase(ScoringMode.Classic, HitResult.Miss, HitResult.Great, 0)] - [TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 7_975)] - [TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 15_949)] - [TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 31_928)] - [TestCase(ScoringMode.Classic, HitResult.Great, HitResult.Great, 49_546)] - [TestCase(ScoringMode.Classic, HitResult.Perfect, HitResult.Perfect, 49_546)] + [TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 3_492)] + [TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 7_029)] + [TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 15_530)] + [TestCase(ScoringMode.Classic, HitResult.Great, HitResult.Great, 32_867)] + [TestCase(ScoringMode.Classic, HitResult.Perfect, HitResult.Perfect, 32_867)] [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, HitResult.SmallTickHit, 0)] - [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 54_189)] + [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 49_365)] [TestCase(ScoringMode.Classic, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] - [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 49_289)] + [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 32_696)] [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 100_003)] [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 100_015)] public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, int expectedScore) From 61d5a890f724a9bca05fd8e0842e47eeae7626a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 24 Nov 2023 12:37:02 +0900 Subject: [PATCH 1673/4092] Update links to figma library --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9f7d88f5c7..4106641adb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -59,7 +59,7 @@ The [issue tracker](https://github.com/ppy/osu/issues) should provide plenty of In the case of simple issues, a direct PR is okay. However, if you decide to work on an existing issue which doesn't seem trivial, **please ask us first**. This way we can try to estimate if it is a good fit for you and provide the correct direction on how to address it. In addition, note that while we do not rule out external contributors from working on roadmapped issues, we will generally prefer to handle them ourselves unless they're not very time sensitive. -If you'd like to propose a subjective change to one of the visual aspects of the game, or there is a bigger task you'd like to work on, but there is no corresponding issue or discussion thread yet for it, **please open a discussion or issue first** to avoid wasted effort. This in particular applies if you want to work on [one of the available designs from the osu! public Figma library](https://www.figma.com/file/6m10GiGEncVFWmgOoSyakH/osu!-Figma-Library). +If you'd like to propose a subjective change to one of the visual aspects of the game, or there is a bigger task you'd like to work on, but there is no corresponding issue or discussion thread yet for it, **please open a discussion or issue first** to avoid wasted effort. This in particular applies if you want to work on [one of the available designs from the osu! Figma master library](https://www.figma.com/file/VIkXMYNPMtQem2RJg9k2iQ/Master-Library). Aside from the above, below is a brief checklist of things to watch out when you're preparing your code changes: @@ -85,4 +85,4 @@ If you're uncertain about some part of the codebase or some inner workings of th - [Development roadmap](https://github.com/orgs/ppy/projects/7/views/6): What the core team is currently working on - [`ppy/osu-framework` wiki](https://github.com/ppy/osu-framework/wiki): Contains introductory information about osu!framework, the bespoke 2D game framework we use for the game - [`ppy/osu` wiki](https://github.com/ppy/osu/wiki): Contains articles about various technical aspects of the game -- [Public Figma library](https://www.figma.com/file/6m10GiGEncVFWmgOoSyakH/osu!-Figma-Library): Contains finished and draft designs for osu! +- [Figma master library](https://www.figma.com/file/VIkXMYNPMtQem2RJg9k2iQ/Master-Library): Contains finished and draft designs for osu! From 537b0ae870d3fb4e1023c8418ce7badb71cb8cb2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Nov 2023 12:47:40 +0900 Subject: [PATCH 1674/4092] Add silly annotation for now (more new r# rules) --- osu.Game/Screens/Ranking/ScorePanel.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 1d332d6b27..1f7ba3692a 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using JetBrains.Annotations; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -82,6 +83,7 @@ namespace osu.Game.Screens.Ranking private static readonly Color4 contracted_top_layer_colour = Color4Extensions.FromHex("#353535"); private static readonly Color4 contracted_middle_layer_colour = Color4Extensions.FromHex("#353535"); + [CanBeNull] public event Action StateChanged; /// From 95c00f966627c00648ed4c72848962676aad8eff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Nov 2023 12:45:55 +0900 Subject: [PATCH 1675/4092] Add `HexaconIcons` lookup to allow usage with `SpriteIcon` --- osu.Game/Graphics/HexaconsIcons.cs | 112 ++++++++++++++++++ osu.Game/OsuGameBase.cs | 2 + .../Play/HUD/ArgonCounterTextComponent.cs | 2 +- osu.Game/Skinning/LegacySpriteText.cs | 2 +- 4 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Graphics/HexaconsIcons.cs diff --git a/osu.Game/Graphics/HexaconsIcons.cs b/osu.Game/Graphics/HexaconsIcons.cs new file mode 100644 index 0000000000..9b0fb30963 --- /dev/null +++ b/osu.Game/Graphics/HexaconsIcons.cs @@ -0,0 +1,112 @@ +// 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.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Text; + +namespace osu.Game.Graphics +{ + public static class HexaconsIcons + { + public const string FONT_NAME = "Icons/Hexacons"; + + public static IconUsage Editor => get(HexaconsMapping.editor); + + private static IconUsage get(HexaconsMapping icon) + { + return new IconUsage((char)icon, FONT_NAME); + } + + // Basically just converting to something we can use in a `char` lookup for FontStore/GlyphStore compatibility. + // Names should match filenames in resources. + private enum HexaconsMapping + { + beatmap_packs, + beatmap, + calendar, + chart, + community, + contests, + devtools, + download, + editor, + featured_artist, + home, + messaging, + music, + news, + notification, + profile, + rankings, + search, + settings, + social, + store, + tournament, + wiki, + } + + public class HexaconsStore : ITextureStore, ITexturedGlyphLookupStore + { + private readonly TextureStore textures; + + public HexaconsStore(TextureStore textures) + { + this.textures = textures; + } + + public void Dispose() + { + textures.Dispose(); + } + + public ITexturedCharacterGlyph? Get(string? fontName, char character) + { + if (fontName == FONT_NAME) + return new Glyph(textures.Get($"{fontName}/{((HexaconsMapping)character).ToString().Replace("_", "-")}")); + + return null; + } + + public Task GetAsync(string fontName, char character) => Task.Run(() => Get(fontName, character)); + + public Texture? Get(string name, WrapMode wrapModeS, WrapMode wrapModeT) => null; + + public Texture Get(string name) => throw new NotImplementedException(); + + public Task GetAsync(string name, CancellationToken cancellationToken = default) => throw new NotImplementedException(); + + public Stream GetStream(string name) => throw new NotImplementedException(); + + public IEnumerable GetAvailableResources() => throw new NotImplementedException(); + + public Task GetAsync(string name, WrapMode wrapModeS, WrapMode wrapModeT, CancellationToken cancellationToken = default) => throw new NotImplementedException(); + + public class Glyph : ITexturedCharacterGlyph + { + public float XOffset => default; + public float YOffset => default; + public float XAdvance => default; + public float Baseline => default; + public char Character => default; + + public float GetKerning(T lastGlyph) where T : ICharacterGlyph => throw new NotImplementedException(); + + public Texture Texture { get; } + public float Width => Texture.Width; + public float Height => Texture.Height; + + public Glyph(Texture texture) + { + Texture = texture; + } + } + } + } +} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 228edc8952..2d8024a45a 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -477,6 +477,8 @@ namespace osu.Game AddFont(Resources, @"Fonts/Venera/Venera-Light"); AddFont(Resources, @"Fonts/Venera/Venera-Bold"); AddFont(Resources, @"Fonts/Venera/Venera-Black"); + + Fonts.AddStore(new HexaconsIcons.HexaconsStore(Textures)); } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => diff --git a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs index d3fadb452b..2a3f4365cb 100644 --- a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs +++ b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs @@ -155,7 +155,7 @@ namespace osu.Game.Screens.Play.HUD this.getLookup = getLookup; } - public ITexturedCharacterGlyph? Get(string fontName, char character) + public ITexturedCharacterGlyph? Get(string? fontName, char character) { string lookup = getLookup(character); var texture = textures.Get($"Gameplay/Fonts/{fontName}-{lookup}"); diff --git a/osu.Game/Skinning/LegacySpriteText.cs b/osu.Game/Skinning/LegacySpriteText.cs index 041a32e8de..8aefa50252 100644 --- a/osu.Game/Skinning/LegacySpriteText.cs +++ b/osu.Game/Skinning/LegacySpriteText.cs @@ -63,7 +63,7 @@ namespace osu.Game.Skinning this.maxSize = maxSize; } - public ITexturedCharacterGlyph? Get(string fontName, char character) + public ITexturedCharacterGlyph? Get(string? fontName, char character) { string lookup = getLookupName(character); From 340227a06d65c6b8bb1edf2e4834ab04d7f809ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Nov 2023 13:16:04 +0900 Subject: [PATCH 1676/4092] Replace all hexacon lookups with strongly typed properties --- .../UserInterface/TestSceneOverlayHeader.cs | 3 +- osu.Game/Graphics/HexaconsIcons.cs | 27 +++++++++-- .../BeatmapListing/BeatmapListingHeader.cs | 3 +- .../Overlays/BeatmapSet/BeatmapSetHeader.cs | 3 +- .../Overlays/Changelog/ChangelogHeader.cs | 3 +- osu.Game/Overlays/Chat/ChatOverlayTopBar.cs | 4 +- osu.Game/Overlays/ChatOverlay.cs | 4 +- .../Dashboard/DashboardOverlayHeader.cs | 3 +- osu.Game/Overlays/FullscreenOverlay.cs | 3 +- osu.Game/Overlays/INamedOverlayComponent.cs | 3 +- osu.Game/Overlays/News/NewsHeader.cs | 3 +- osu.Game/Overlays/NotificationOverlay.cs | 4 +- osu.Game/Overlays/NowPlayingOverlay.cs | 2 +- osu.Game/Overlays/OverlayTitle.cs | 45 ++++++------------- osu.Game/Overlays/Profile/ProfileHeader.cs | 3 +- .../Rankings/RankingsOverlayHeader.cs | 3 +- osu.Game/Overlays/SettingsOverlay.cs | 4 +- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 12 ++--- .../Overlays/Toolbar/ToolbarHomeButton.cs | 3 +- .../Toolbar/ToolbarOverlayToggleButton.cs | 2 +- osu.Game/Overlays/Wiki/WikiHeader.cs | 3 +- .../Edit/Components/Menus/EditorMenuBar.cs | 4 +- .../Screens/Edit/Setup/SetupScreenHeader.cs | 3 +- 23 files changed, 82 insertions(+), 65 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs index a927b0931b..55a04b129c 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; using osu.Framework.Allocation; using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; using osuTK.Graphics; namespace osu.Game.Tests.Visual.UserInterface @@ -153,7 +154,7 @@ namespace osu.Game.Tests.Visual.UserInterface public TestTitle() { Title = "title"; - IconTexture = "Icons/changelog"; + Icon = HexaconsIcons.Devtools; } } } diff --git a/osu.Game/Graphics/HexaconsIcons.cs b/osu.Game/Graphics/HexaconsIcons.cs index 9b0fb30963..3eee5d7197 100644 --- a/osu.Game/Graphics/HexaconsIcons.cs +++ b/osu.Game/Graphics/HexaconsIcons.cs @@ -16,12 +16,31 @@ namespace osu.Game.Graphics { public const string FONT_NAME = "Icons/Hexacons"; + public static IconUsage BeatmapPacks => get(HexaconsMapping.beatmap_packs); + public static IconUsage Beatmap => get(HexaconsMapping.beatmap); + public static IconUsage Calendar => get(HexaconsMapping.calendar); + public static IconUsage Chart => get(HexaconsMapping.chart); + public static IconUsage Community => get(HexaconsMapping.community); + public static IconUsage Contests => get(HexaconsMapping.contests); + public static IconUsage Devtools => get(HexaconsMapping.devtools); + public static IconUsage Download => get(HexaconsMapping.download); public static IconUsage Editor => get(HexaconsMapping.editor); + public static IconUsage FeaturedArtist => get(HexaconsMapping.featured_artist); + public static IconUsage Home => get(HexaconsMapping.home); + public static IconUsage Messaging => get(HexaconsMapping.messaging); + public static IconUsage Music => get(HexaconsMapping.music); + public static IconUsage News => get(HexaconsMapping.news); + public static IconUsage Notification => get(HexaconsMapping.notification); + public static IconUsage Profile => get(HexaconsMapping.profile); + public static IconUsage Rankings => get(HexaconsMapping.rankings); + public static IconUsage Search => get(HexaconsMapping.search); + public static IconUsage Settings => get(HexaconsMapping.settings); + public static IconUsage Social => get(HexaconsMapping.social); + public static IconUsage Store => get(HexaconsMapping.store); + public static IconUsage Tournament => get(HexaconsMapping.tournament); + public static IconUsage Wiki => get(HexaconsMapping.wiki); - private static IconUsage get(HexaconsMapping icon) - { - return new IconUsage((char)icon, FONT_NAME); - } + private static IconUsage get(HexaconsMapping icon) => new IconUsage((char)icon, FONT_NAME); // Basically just converting to something we can use in a `char` lookup for FontStore/GlyphStore compatibility. // Names should match filenames in resources. diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs index 3336c383ff..27fab82bf3 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs @@ -4,6 +4,7 @@ #nullable disable using osu.Framework.Graphics; +using osu.Game.Graphics; using osu.Game.Localisation; using osu.Game.Resources.Localisation.Web; @@ -23,7 +24,7 @@ namespace osu.Game.Overlays.BeatmapListing { Title = PageTitleStrings.MainBeatmapsetsControllerIndex; Description = NamedOverlayComponentStrings.BeatmapListingDescription; - IconTexture = "Icons/Hexacons/beatmap"; + Icon = HexaconsIcons.Beatmap; } } } diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs index 858742648c..eced27f35e 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs @@ -9,6 +9,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Effects; using osu.Framework.Localisation; +using osu.Game.Graphics; using osu.Game.Online.API.Requests.Responses; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; @@ -59,7 +60,7 @@ namespace osu.Game.Overlays.BeatmapSet public BeatmapHeaderTitle() { Title = PageTitleStrings.MainBeatmapsetsControllerShow; - IconTexture = "Icons/Hexacons/beatmap"; + Icon = HexaconsIcons.Beatmap; } } } diff --git a/osu.Game/Overlays/Changelog/ChangelogHeader.cs b/osu.Game/Overlays/Changelog/ChangelogHeader.cs index e9be67e977..61ea9dc4db 100644 --- a/osu.Game/Overlays/Changelog/ChangelogHeader.cs +++ b/osu.Game/Overlays/Changelog/ChangelogHeader.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; +using osu.Game.Graphics; using osu.Game.Localisation; using osu.Game.Online.API.Requests.Responses; using osu.Game.Resources.Localisation.Web; @@ -123,7 +124,7 @@ namespace osu.Game.Overlays.Changelog { Title = PageTitleStrings.MainChangelogControllerDefault; Description = NamedOverlayComponentStrings.ChangelogDescription; - IconTexture = "Icons/Hexacons/devtools"; + Icon = HexaconsIcons.Devtools; } } } diff --git a/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs b/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs index 0410174dc1..c6f63a72b9 100644 --- a/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs +++ b/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs @@ -45,11 +45,11 @@ namespace osu.Game.Overlays.Chat { new Drawable[] { - new Sprite + new SpriteIcon { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Texture = textures.Get("Icons/Hexacons/messaging"), + Icon = HexaconsIcons.Social, Size = new Vector2(18), }, // Placeholder text diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 724f77ad71..5cf2ac6c86 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -11,11 +11,13 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Configuration; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; @@ -29,7 +31,7 @@ namespace osu.Game.Overlays { public partial class ChatOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent, IKeyBindingHandler { - public string IconTexture => "Icons/Hexacons/messaging"; + public IconUsage Icon => HexaconsIcons.Messaging; public LocalisableString Title => ChatStrings.HeaderTitle; public LocalisableString Description => ChatStrings.HeaderDescription; diff --git a/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs index 0f4697e33c..b9d869c2ec 100644 --- a/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs +++ b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs @@ -3,6 +3,7 @@ using System.ComponentModel; using osu.Framework.Localisation; +using osu.Game.Graphics; using osu.Game.Localisation; using osu.Game.Resources.Localisation.Web; @@ -18,7 +19,7 @@ namespace osu.Game.Overlays.Dashboard { Title = PageTitleStrings.MainHomeControllerIndex; Description = NamedOverlayComponentStrings.DashboardDescription; - IconTexture = "Icons/Hexacons/social"; + Icon = HexaconsIcons.Social; } } } diff --git a/osu.Game/Overlays/FullscreenOverlay.cs b/osu.Game/Overlays/FullscreenOverlay.cs index 6ee045c492..6ddf1eecf0 100644 --- a/osu.Game/Overlays/FullscreenOverlay.cs +++ b/osu.Game/Overlays/FullscreenOverlay.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Graphics.Containers; using osu.Game.Online.API; @@ -17,7 +18,7 @@ namespace osu.Game.Overlays public abstract partial class FullscreenOverlay : WaveOverlayContainer, INamedOverlayComponent where T : OverlayHeader { - public virtual string IconTexture => Header.Title.IconTexture; + public virtual IconUsage Icon => Header.Title.Icon; public virtual LocalisableString Title => Header.Title.Title; public virtual LocalisableString Description => Header.Title.Description; diff --git a/osu.Game/Overlays/INamedOverlayComponent.cs b/osu.Game/Overlays/INamedOverlayComponent.cs index 65664b12e7..ef3c029aac 100644 --- a/osu.Game/Overlays/INamedOverlayComponent.cs +++ b/osu.Game/Overlays/INamedOverlayComponent.cs @@ -1,13 +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 osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; namespace osu.Game.Overlays { public interface INamedOverlayComponent { - string IconTexture { get; } + IconUsage Icon { get; } LocalisableString Title { get; } diff --git a/osu.Game/Overlays/News/NewsHeader.cs b/osu.Game/Overlays/News/NewsHeader.cs index 44e2f6a8cb..f237ed66f2 100644 --- a/osu.Game/Overlays/News/NewsHeader.cs +++ b/osu.Game/Overlays/News/NewsHeader.cs @@ -7,6 +7,7 @@ using System; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Localisation; +using osu.Game.Graphics; using osu.Game.Localisation; using osu.Game.Resources.Localisation.Web; @@ -68,7 +69,7 @@ namespace osu.Game.Overlays.News { Title = PageTitleStrings.MainNewsControllerDefault; Description = NamedOverlayComponentStrings.NewsDescription; - IconTexture = "Icons/Hexacons/news"; + Icon = HexaconsIcons.News; } } } diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 81233b4343..c3ddb228ea 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -13,9 +13,11 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Framework.Logging; using osu.Framework.Threading; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Overlays.Notifications; using osu.Game.Resources.Localisation.Web; @@ -27,7 +29,7 @@ namespace osu.Game.Overlays { public partial class NotificationOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent, INotificationOverlay { - public string IconTexture => "Icons/Hexacons/notification"; + public IconUsage Icon => HexaconsIcons.Notification; public LocalisableString Title => NotificationsStrings.HeaderTitle; public LocalisableString Description => NotificationsStrings.HeaderDescription; diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 5bbf18a959..425ff0935d 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -29,7 +29,7 @@ namespace osu.Game.Overlays { public partial class NowPlayingOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent { - public string IconTexture => "Icons/Hexacons/music"; + public IconUsage Icon => HexaconsIcons.Music; public LocalisableString Title => NowPlayingStrings.HeaderTitle; public LocalisableString Description => NowPlayingStrings.HeaderDescription; diff --git a/osu.Game/Overlays/OverlayTitle.cs b/osu.Game/Overlays/OverlayTitle.cs index 1d207e5f7d..a2ff7032b5 100644 --- a/osu.Game/Overlays/OverlayTitle.cs +++ b/osu.Game/Overlays/OverlayTitle.cs @@ -1,13 +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 osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -20,7 +16,7 @@ namespace osu.Game.Overlays public const float ICON_SIZE = 30; private readonly OsuSpriteText titleText; - private readonly Container icon; + private readonly Container iconContainer; private LocalisableString title; @@ -32,12 +28,20 @@ namespace osu.Game.Overlays public LocalisableString Description { get; protected set; } - private string iconTexture; + private IconUsage icon; - public string IconTexture + public IconUsage Icon { - get => iconTexture; - protected set => icon.Child = new OverlayTitleIcon(iconTexture = value); + get => icon; + protected set => iconContainer.Child = new SpriteIcon + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fit, + + Icon = icon = value, + }; } protected OverlayTitle() @@ -51,7 +55,7 @@ namespace osu.Game.Overlays Direction = FillDirection.Horizontal, Children = new Drawable[] { - icon = new Container + iconContainer = new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -68,26 +72,5 @@ namespace osu.Game.Overlays } }; } - - private partial class OverlayTitleIcon : Sprite - { - private readonly string textureName; - - public OverlayTitleIcon(string textureName) - { - this.textureName = textureName; - - RelativeSizeAxes = Axes.Both; - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - FillMode = FillMode.Fit; - } - - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - Texture = textures.Get(textureName); - } - } } } diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 80d48ae09e..78343d08f1 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; +using osu.Game.Graphics; using osu.Game.Overlays.Profile.Header; using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Resources.Localisation.Web; @@ -86,7 +87,7 @@ namespace osu.Game.Overlays.Profile public ProfileHeaderTitle() { Title = PageTitleStrings.MainUsersControllerDefault; - IconTexture = "Icons/Hexacons/profile"; + Icon = HexaconsIcons.Profile; } } } diff --git a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs index 44f278a237..63128fb73d 100644 --- a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs +++ b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Game.Localisation; using osu.Game.Resources.Localisation.Web; using osu.Framework.Graphics; +using osu.Game.Graphics; using osu.Game.Rulesets; using osu.Game.Users; @@ -35,7 +36,7 @@ namespace osu.Game.Overlays.Rankings { Title = PageTitleStrings.MainRankingControllerDefault; Description = NamedOverlayComponentStrings.RankingsDescription; - IconTexture = "Icons/Hexacons/rankings"; + Icon = HexaconsIcons.Rankings; } } } diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs index 291281124c..746d451343 100644 --- a/osu.Game/Overlays/SettingsOverlay.cs +++ b/osu.Game/Overlays/SettingsOverlay.cs @@ -12,14 +12,16 @@ using osu.Game.Overlays.Settings.Sections.Input; using osuTK.Graphics; using System.Collections.Generic; using osu.Framework.Bindables; +using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Game.Graphics; using osu.Game.Localisation; namespace osu.Game.Overlays { public partial class SettingsOverlay : SettingsPanel, INamedOverlayComponent { - public string IconTexture => "Icons/Hexacons/settings"; + public IconUsage Icon => HexaconsIcons.Settings; public LocalisableString Title => SettingsStrings.HeaderTitle; public LocalisableString Description => SettingsStrings.HeaderDescription; diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index e181322dda..08bcb6bd8a 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -10,12 +10,11 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Game.Database; using osu.Framework.Localisation; +using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; @@ -36,16 +35,13 @@ namespace osu.Game.Overlays.Toolbar IconContainer.Show(); } - [Resolved] - private TextureStore textures { get; set; } = null!; - [Resolved] private ReadableKeyCombinationProvider keyCombinationProvider { get; set; } = null!; - public void SetIcon(string texture) => - SetIcon(new Sprite + public void SetIcon(IconUsage icon) => + SetIcon(new SpriteIcon { - Texture = textures.Get(texture), + Icon = icon, }); public LocalisableString Text diff --git a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs index ba2c8282c5..ded0229d67 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Game.Graphics; using osu.Game.Input.Bindings; using osu.Game.Localisation; @@ -20,7 +21,7 @@ namespace osu.Game.Overlays.Toolbar { TooltipMain = ToolbarStrings.HomeHeaderTitle; TooltipSub = ToolbarStrings.HomeHeaderDescription; - SetIcon("Icons/Hexacons/home"); + SetIcon(HexaconsIcons.Home); } } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs index 7bd48174db..78c976111b 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs @@ -39,7 +39,7 @@ namespace osu.Game.Overlays.Toolbar { TooltipMain = named.Title; TooltipSub = named.Description; - SetIcon(named.IconTexture); + SetIcon(named.Icon); } } } diff --git a/osu.Game/Overlays/Wiki/WikiHeader.cs b/osu.Game/Overlays/Wiki/WikiHeader.cs index 9317813fc4..9e9e565684 100644 --- a/osu.Game/Overlays/Wiki/WikiHeader.cs +++ b/osu.Game/Overlays/Wiki/WikiHeader.cs @@ -8,6 +8,7 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Localisation; +using osu.Game.Graphics; using osu.Game.Localisation; using osu.Game.Online.API.Requests.Responses; using osu.Game.Resources.Localisation.Web; @@ -81,7 +82,7 @@ namespace osu.Game.Overlays.Wiki { Title = PageTitleStrings.MainWikiControllerDefault; Description = NamedOverlayComponentStrings.WikiDescription; - IconTexture = "Icons/Hexacons/wiki"; + Icon = HexaconsIcons.Wiki; } } } diff --git a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs index a67375b0a4..5c77672d90 100644 --- a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs +++ b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs @@ -46,12 +46,12 @@ namespace osu.Game.Screens.Edit.Components.Menus Padding = new MarginPadding(8), Children = new Drawable[] { - new Sprite + new SpriteIcon { Size = new Vector2(26), Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Texture = textures.Get("Icons/Hexacons/editor"), + Icon = HexaconsIcons.Editor, }, text = new TextFlowContainer { diff --git a/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs b/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs index 788beba9d9..93448c4394 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Overlays; using osuTK.Graphics; @@ -79,7 +80,7 @@ namespace osu.Game.Screens.Edit.Setup { Title = EditorSetupStrings.BeatmapSetup.ToLower(); Description = EditorSetupStrings.BeatmapSetupDescription; - IconTexture = "Icons/Hexacons/social"; + Icon = HexaconsIcons.Social; } } From 5905ca64925f4cf4b3ce7d07974e39a353df18e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Nov 2023 10:56:59 +0900 Subject: [PATCH 1677/4092] Add second level menu for skin editors --- .../TestSceneBeatmapEditorNavigation.cs | 2 +- .../UserInterface/TestSceneButtonSystem.cs | 2 +- osu.Game/Screens/Menu/ButtonSystem.cs | 19 +++++++++++++++++-- osu.Game/Screens/Menu/MainMenu.cs | 10 +++++++++- 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs index b79b61202b..ce266a2d77 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs @@ -239,7 +239,7 @@ namespace osu.Game.Tests.Visual.Navigation { AddUntilStep("wait for dialog overlay", () => Game.ChildrenOfType().SingleOrDefault() != null); - AddStep("open editor", () => Game.ChildrenOfType().Single().OnEdit.Invoke()); + AddStep("open editor", () => Game.ChildrenOfType().Single().OnEditBeatmap.Invoke()); AddUntilStep("wait for editor", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.IsLoaded); AddStep("click on file", () => { diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneButtonSystem.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneButtonSystem.cs index ac811aeb65..1de47aee69 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneButtonSystem.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneButtonSystem.cs @@ -97,7 +97,7 @@ namespace osu.Game.Tests.Visual.UserInterface break; case Key.E: - buttons.OnEdit = action; + buttons.OnEditBeatmap = action; break; case Key.D: diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index a0cf9f5322..79739e4f0c 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -13,6 +13,7 @@ using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -40,7 +41,8 @@ namespace osu.Game.Screens.Menu private readonly IBindable isIdle = new BindableBool(); - public Action OnEdit; + public Action OnEditBeatmap; + public Action OnEditSkin; public Action OnExit; public Action OnBeatmapListing; public Action OnSolo; @@ -84,6 +86,7 @@ namespace osu.Game.Screens.Menu private readonly List buttonsTopLevel = new List(); private readonly List buttonsPlay = new List(); + private readonly List buttonsEdit = new List(); private Sample sampleBackToLogo; private Sample sampleLogoSwoosh; @@ -105,6 +108,11 @@ namespace osu.Game.Screens.Menu buttonArea.AddRange(new Drawable[] { new MainMenuButton(ButtonSystemStrings.Settings, string.Empty, FontAwesome.Solid.Cog, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O), + backButton = new MainMenuButton(ButtonSystemStrings.Back, @"back-to-top", OsuIcon.LeftCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, + -WEDGE_WIDTH) + { + VisibleState = ButtonSystemState.Edit, + }, backButton = new MainMenuButton(ButtonSystemStrings.Back, @"back-to-top", OsuIcon.LeftCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, -WEDGE_WIDTH) { @@ -133,14 +141,19 @@ namespace osu.Game.Screens.Menu buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Playlists, @"button-default-select", OsuIcon.Charts, new Color4(94, 63, 186, 255), onPlaylists, 0, Key.L)); buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play); + buttonsEdit.Add(new MainMenuButton(CommonStrings.Beatmaps.ToLower(), @"button-default-select", FontAwesome.Solid.User, new Color4(238, 170, 0, 255), () => OnEditBeatmap?.Invoke(), WEDGE_WIDTH, Key.B)); + buttonsEdit.Add(new MainMenuButton(CommonStrings.Skins.ToLower(), @"button-default-select", FontAwesome.Solid.Users, new Color4(220, 160, 0, 255), () => OnEditSkin?.Invoke(), 0, Key.S)); + buttonsEdit.ForEach(b => b.VisibleState = ButtonSystemState.Edit); + buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Play, @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P)); - buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Edit, @"button-default-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E)); + buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Edit, @"button-default-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => State = ButtonSystemState.Edit, 0, Key.E)); buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Browse, @"button-default-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.B, Key.D)); if (host.CanExit) buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Exit, string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), () => OnExit?.Invoke(), 0, Key.Q)); buttonArea.AddRange(buttonsPlay); + buttonArea.AddRange(buttonsEdit); buttonArea.AddRange(buttonsTopLevel); buttonArea.ForEach(b => @@ -270,6 +283,7 @@ namespace osu.Game.Screens.Menu return true; + case ButtonSystemState.Edit: case ButtonSystemState.Play: StopSamplePlayback(); backButton.TriggerClick(); @@ -414,6 +428,7 @@ namespace osu.Game.Screens.Menu Initial, TopLevel, Play, + Edit, EnteringMode, } } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 0f73707544..c25e62d69e 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -25,6 +25,7 @@ using osu.Game.Input.Bindings; using osu.Game.IO; using osu.Game.Online.API; using osu.Game.Overlays; +using osu.Game.Overlays.SkinEditor; using osu.Game.Rulesets; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Edit; @@ -93,6 +94,9 @@ namespace osu.Game.Screens.Menu private Sample reappearSampleSwoosh; + [Resolved(canBeNull: true)] + private SkinEditorOverlay skinEditor { get; set; } + [BackgroundDependencyLoader(true)] private void load(BeatmapListingOverlay beatmapListing, SettingsOverlay settings, OsuConfigManager config, SessionStatics statics, AudioManager audio) { @@ -120,11 +124,15 @@ namespace osu.Game.Screens.Menu { Buttons = new ButtonSystem { - OnEdit = delegate + OnEditBeatmap = () => { Beatmap.SetDefault(); this.Push(new EditorLoader()); }, + OnEditSkin = () => + { + skinEditor?.Show(); + }, OnSolo = loadSoloSongSelect, OnMultiplayer = () => this.Push(new Multiplayer()), OnPlaylists = () => this.Push(new Playlists()), From 7e59a1d0be3eb3aae50f17e1a361b6a357286194 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Nov 2023 11:00:22 +0900 Subject: [PATCH 1678/4092] Apply NRT to `ButtonSystem` --- .../Editing/TestSceneOpenEditorTimestamp.cs | 2 +- .../TestSceneBeatmapEditorNavigation.cs | 2 +- osu.Game/Screens/Menu/ButtonSystem.cs | 52 +++++++++---------- 3 files changed, 26 insertions(+), 30 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs index 2c8655a5f5..1a754d5145 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.Editing () => Game.Notifications.AllNotifications.Count(x => x.Text == EditorStrings.MustBeInEditorToHandleLinks), () => Is.EqualTo(1)); - AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo.Invoke()); + AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo?.Invoke()); AddUntilStep("entered song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect); addStepClickLink("00:00:000 (1)", waitForSeek: false); diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs index ce266a2d77..9930349b1b 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs @@ -239,7 +239,7 @@ namespace osu.Game.Tests.Visual.Navigation { AddUntilStep("wait for dialog overlay", () => Game.ChildrenOfType().SingleOrDefault() != null); - AddStep("open editor", () => Game.ChildrenOfType().Single().OnEditBeatmap.Invoke()); + AddStep("open editor", () => Game.ChildrenOfType().Single().OnEditBeatmap?.Invoke()); AddUntilStep("wait for editor", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.IsLoaded); AddStep("click on file", () => { diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 79739e4f0c..c4c7599fd7 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.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 osu.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -37,24 +34,23 @@ namespace osu.Game.Screens.Menu { public partial class ButtonSystem : Container, IStateful, IKeyBindingHandler { - public event Action StateChanged; - - private readonly IBindable isIdle = new BindableBool(); - - public Action OnEditBeatmap; - public Action OnEditSkin; - public Action OnExit; - public Action OnBeatmapListing; - public Action OnSolo; - public Action OnSettings; - public Action OnMultiplayer; - public Action OnPlaylists; - public const float BUTTON_WIDTH = 140f; public const float WEDGE_WIDTH = 20; - [CanBeNull] - private OsuLogo logo; + public event Action? StateChanged; + + public Action? OnEditBeatmap; + public Action? OnEditSkin; + public Action? OnExit; + public Action? OnBeatmapListing; + public Action? OnSolo; + public Action? OnSettings; + public Action? OnMultiplayer; + public Action? OnPlaylists; + + private readonly IBindable isIdle = new BindableBool(); + + private OsuLogo? logo; /// /// Assign the that this ButtonSystem should manage the position of. @@ -88,8 +84,8 @@ namespace osu.Game.Screens.Menu private readonly List buttonsPlay = new List(); private readonly List buttonsEdit = new List(); - private Sample sampleBackToLogo; - private Sample sampleLogoSwoosh; + private Sample? sampleBackToLogo; + private Sample? sampleLogoSwoosh; private readonly LogoTrackingContainer logoTrackingContainer; @@ -124,17 +120,17 @@ namespace osu.Game.Screens.Menu buttonArea.Flow.CentreTarget = logoTrackingContainer.LogoFacade; } - [Resolved(CanBeNull = true)] - private OsuGame game { get; set; } + [Resolved] + private IAPIProvider api { get; set; } = null!; [Resolved] - private IAPIProvider api { get; set; } + private OsuGame? game { get; set; } - [Resolved(CanBeNull = true)] - private LoginOverlay loginOverlay { get; set; } + [Resolved] + private LoginOverlay? loginOverlay { get; set; } - [BackgroundDependencyLoader(true)] - private void load(AudioManager audio, IdleTracker idleTracker, GameHost host) + [BackgroundDependencyLoader] + private void load(AudioManager audio, IdleTracker? idleTracker, GameHost host) { buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Solo, @"button-default-select", FontAwesome.Solid.User, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P)); buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Multi, @"button-default-select", FontAwesome.Solid.Users, new Color4(94, 63, 186, 255), onMultiplayer, 0, Key.M)); @@ -354,7 +350,7 @@ namespace osu.Game.Screens.Menu } } - private ScheduledDelegate logoDelayedAction; + private ScheduledDelegate? logoDelayedAction; private void updateLogoState(ButtonSystemState lastState = ButtonSystemState.Initial) { From 8ad414488a630787453fe953bb1c70d327daa992 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Nov 2023 11:02:30 +0900 Subject: [PATCH 1679/4092] Play out previous transforms immediately to avoid flow issues with multiple sub menus --- osu.Game/Screens/Menu/ButtonSystem.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index c4c7599fd7..a954fb50b7 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -338,6 +338,8 @@ namespace osu.Game.Screens.Menu Logger.Log($"{nameof(ButtonSystem)}'s state changed from {lastState} to {state}"); + buttonArea.FinishTransforms(true); + using (buttonArea.BeginDelayedSequence(lastState == ButtonSystemState.Initial ? 150 : 0)) { buttonArea.ButtonSystemState = state; From 1d1b3ca9826127ad986bfd3afc35810622602406 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Nov 2023 11:05:57 +0900 Subject: [PATCH 1680/4092] Apply NRT to `MainMenuButton` --- osu.Game/Screens/Menu/MainMenuButton.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenuButton.cs b/osu.Game/Screens/Menu/MainMenuButton.cs index 63fc34b4fb..daf8451899 100644 --- a/osu.Game/Screens/Menu/MainMenuButton.cs +++ b/osu.Game/Screens/Menu/MainMenuButton.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Framework; @@ -33,7 +31,7 @@ namespace osu.Game.Screens.Menu /// public partial class MainMenuButton : BeatSyncedContainer, IStateful { - public event Action StateChanged; + public event Action? StateChanged; public readonly Key[] TriggerKeys; @@ -48,14 +46,14 @@ namespace osu.Game.Screens.Menu /// public ButtonSystemState VisibleState = ButtonSystemState.TopLevel; - private readonly Action clickAction; - private Sample sampleClick; - private Sample sampleHover; - private SampleChannel sampleChannel; + private readonly Action? clickAction; + private Sample? sampleClick; + private Sample? sampleHover; + private SampleChannel? sampleChannel; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos); - public MainMenuButton(LocalisableString text, string sampleName, IconUsage symbol, Color4 colour, Action clickAction = null, float extraWidth = 0, params Key[] triggerKeys) + public MainMenuButton(LocalisableString text, string sampleName, IconUsage symbol, Color4 colour, Action? clickAction = null, float extraWidth = 0, params Key[] triggerKeys) { this.sampleName = sampleName; this.clickAction = clickAction; From a069a673fa1866e06451ba620daa2191df0e8403 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Nov 2023 11:15:18 +0900 Subject: [PATCH 1681/4092] Allow buttons to be displayed on more than one state (and share the back button) --- osu.Game/Screens/Menu/ButtonSystem.cs | 8 ++------ osu.Game/Screens/Menu/MainMenuButton.cs | 18 ++++++++++++++---- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index a954fb50b7..995cdaf450 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -107,12 +107,8 @@ namespace osu.Game.Screens.Menu backButton = new MainMenuButton(ButtonSystemStrings.Back, @"back-to-top", OsuIcon.LeftCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, -WEDGE_WIDTH) { - VisibleState = ButtonSystemState.Edit, - }, - backButton = new MainMenuButton(ButtonSystemStrings.Back, @"back-to-top", OsuIcon.LeftCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, - -WEDGE_WIDTH) - { - VisibleState = ButtonSystemState.Play, + VisibleStateMin = ButtonSystemState.Play, + VisibleStateMax = ButtonSystemState.Edit, }, logoTrackingContainer.LogoFacade.With(d => d.Scale = new Vector2(0.74f)) }); diff --git a/osu.Game/Screens/Menu/MainMenuButton.cs b/osu.Game/Screens/Menu/MainMenuButton.cs index daf8451899..bc638b44ac 100644 --- a/osu.Game/Screens/Menu/MainMenuButton.cs +++ b/osu.Game/Screens/Menu/MainMenuButton.cs @@ -42,9 +42,19 @@ namespace osu.Game.Screens.Menu private readonly string sampleName; /// - /// The menu state for which we are visible for. + /// The menu state for which we are visible for (assuming only one). /// - public ButtonSystemState VisibleState = ButtonSystemState.TopLevel; + public ButtonSystemState VisibleState + { + set + { + VisibleStateMin = value; + VisibleStateMax = value; + } + } + + public ButtonSystemState VisibleStateMin = ButtonSystemState.TopLevel; + public ButtonSystemState VisibleStateMax = ButtonSystemState.TopLevel; private readonly Action? clickAction; private Sample? sampleClick; @@ -313,9 +323,9 @@ namespace osu.Game.Screens.Menu break; default: - if (value == VisibleState) + if (value <= VisibleStateMax && value >= VisibleStateMin) State = ButtonState.Expanded; - else if (value < VisibleState) + else if (value < VisibleStateMin) State = ButtonState.Contracted; else State = ButtonState.Exploded; From a02b6a5bcb23af26a6fee502a9423b517f75b112 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Nov 2023 11:28:06 +0900 Subject: [PATCH 1682/4092] Update menu key shortcut test coverage --- .../UserInterface/TestSceneButtonSystem.cs | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneButtonSystem.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneButtonSystem.cs index 1de47aee69..8f72be37df 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneButtonSystem.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneButtonSystem.cs @@ -67,14 +67,15 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("Enter mode", performEnterMode); } - [TestCase(Key.P, true)] - [TestCase(Key.M, true)] - [TestCase(Key.L, true)] - [TestCase(Key.E, false)] - [TestCase(Key.D, false)] - [TestCase(Key.Q, false)] - [TestCase(Key.O, false)] - public void TestShortcutKeys(Key key, bool entersPlay) + [TestCase(Key.P, Key.P)] + [TestCase(Key.M, Key.P)] + [TestCase(Key.L, Key.P)] + [TestCase(Key.B, Key.E)] + [TestCase(Key.S, Key.E)] + [TestCase(Key.D, null)] + [TestCase(Key.Q, null)] + [TestCase(Key.O, null)] + public void TestShortcutKeys(Key key, Key? subMenuEnterKey) { int activationCount = -1; AddStep("set up action", () => @@ -96,10 +97,14 @@ namespace osu.Game.Tests.Visual.UserInterface buttons.OnPlaylists = action; break; - case Key.E: + case Key.B: buttons.OnEditBeatmap = action; break; + case Key.S: + buttons.OnEditSkin = action; + break; + case Key.D: buttons.OnBeatmapListing = action; break; @@ -117,10 +122,10 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep($"press {key}", () => InputManager.Key(key)); AddAssert("state is top level", () => buttons.State == ButtonSystemState.TopLevel); - if (entersPlay) + if (subMenuEnterKey != null) { - AddStep("press P", () => InputManager.Key(Key.P)); - AddAssert("state is play", () => buttons.State == ButtonSystemState.Play); + AddStep($"press {subMenuEnterKey}", () => InputManager.Key(subMenuEnterKey.Value)); + AddAssert("state is not top menu", () => buttons.State != ButtonSystemState.TopLevel); } AddStep($"press {key}", () => InputManager.Key(key)); From c7f1fd23e76170ee9c52973022ff0d630b69917f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 24 Nov 2023 06:09:41 +0300 Subject: [PATCH 1683/4092] Implement spinner glow --- .../Skinning/Argon/ArgonSpinnerProgressArc.cs | 122 +++++++++++++++++- 1 file changed, 120 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerProgressArc.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerProgressArc.cs index 31cdc0dc0f..b719138d33 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerProgressArc.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerProgressArc.cs @@ -2,10 +2,17 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Runtime.InteropServices; 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.Primitives; +using osu.Framework.Graphics.Rendering; +using osu.Framework.Graphics.Shaders; +using osu.Framework.Graphics.Shaders.Types; +using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.UserInterface; using osu.Framework.Utils; using osu.Game.Rulesets.Objects.Drawables; @@ -19,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon private const float arc_fill = 0.15f; private const float arc_radius = 0.12f; - private CircularProgress fill = null!; + private ProgressFill fill = null!; private DrawableSpinner spinner = null!; @@ -45,13 +52,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon InnerRadius = arc_radius, RoundedCaps = true, }, - fill = new CircularProgress + fill = new ProgressFill { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, InnerRadius = arc_radius, RoundedCaps = true, + GlowColour = new Color4(171, 255, 255, 255) } }; } @@ -67,5 +75,115 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon fill.Rotation = (float)(90 - fill.Current.Value * 180); } + + private partial class ProgressFill : CircularProgress + { + private Color4 glowColour = Color4.White; + + public Color4 GlowColour + { + get => glowColour; + set + { + glowColour = value; + Invalidate(Invalidation.DrawNode); + } + } + + private Texture glowTexture = null!; + private IShader glowShader = null!; + private float glowSize; + + [BackgroundDependencyLoader] + private void load(TextureStore textures, ShaderManager shaders) + { + glowTexture = textures.Get("Gameplay/osu/spinner-glow"); + glowShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, "SpinnerGlow"); + glowSize = Blur.KernelSize(50); // Half of the maximum blur sigma in the design (which is 100) + } + + protected override DrawNode CreateDrawNode() => new ProgressFillDrawNode(this); + + private class ProgressFillDrawNode : CircularProgressDrawNode + { + protected new ProgressFill Source => (ProgressFill)base.Source; + + public ProgressFillDrawNode(CircularProgress source) + : base(source) + { + } + + private Texture glowTexture = null!; + private IShader glowShader = null!; + private Quad glowQuad; + private float relativeGlowSize; + private Color4 glowColour; + + public override void ApplyState() + { + base.ApplyState(); + + glowTexture = Source.glowTexture; + glowShader = Source.glowShader; + glowColour = Source.glowColour; + + // Inflated draw quad by the size of the blur. + glowQuad = Source.ToScreenSpace(DrawRectangle.Inflate(Source.glowSize)); + relativeGlowSize = Source.glowSize / Source.DrawSize.X; + } + + public override void Draw(IRenderer renderer) + { + base.Draw(renderer); + drawGlow(renderer); + } + + private void drawGlow(IRenderer renderer) + { + renderer.SetBlend(BlendingParameters.Additive); + + glowShader.Bind(); + bindGlowUniformResources(glowShader, renderer); + + ColourInfo col = DrawColourInfo.Colour; + col.ApplyChild(glowColour); + + renderer.DrawQuad(glowTexture, glowQuad, col); + + glowShader.Unbind(); + } + + private IUniformBuffer? progressGlowParametersBuffer; + + private void bindGlowUniformResources(IShader shader, IRenderer renderer) + { + progressGlowParametersBuffer ??= renderer.CreateUniformBuffer(); + progressGlowParametersBuffer.Data = new ProgressGlowParameters + { + InnerRadius = InnerRadius, + Progress = Progress, + TexelSize = TexelSize, + GlowSize = relativeGlowSize + }; + + shader.BindUniformBlock("m_ProgressGlowParameters", progressGlowParametersBuffer); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + progressGlowParametersBuffer?.Dispose(); + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + private record struct ProgressGlowParameters + { + public UniformFloat InnerRadius; + public UniformFloat Progress; + public UniformFloat TexelSize; + public UniformFloat GlowSize; + } + } + } } } From b8179aa875dcb27746bf1e9f236117c3f9bdb30c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Nov 2023 13:23:35 +0900 Subject: [PATCH 1684/4092] Use better(?) icons and full strings --- osu.Game/Localisation/EditorStrings.cs | 5 +++++ osu.Game/Screens/Menu/ButtonSystem.cs | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Localisation/EditorStrings.cs b/osu.Game/Localisation/EditorStrings.cs index b20b5bc65a..6ad12f54df 100644 --- a/osu.Game/Localisation/EditorStrings.cs +++ b/osu.Game/Localisation/EditorStrings.cs @@ -9,6 +9,11 @@ namespace osu.Game.Localisation { private const string prefix = @"osu.Game.Resources.Localisation.Editor"; + /// + /// "Beatmap editor" + /// + public static LocalisableString BeatmapEditor => new TranslatableString(getKey(@"beatmap_editor"), @"Beatmap editor"); + /// /// "Waveform opacity" /// diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 995cdaf450..8173375c29 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -133,8 +133,8 @@ namespace osu.Game.Screens.Menu buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Playlists, @"button-default-select", OsuIcon.Charts, new Color4(94, 63, 186, 255), onPlaylists, 0, Key.L)); buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play); - buttonsEdit.Add(new MainMenuButton(CommonStrings.Beatmaps.ToLower(), @"button-default-select", FontAwesome.Solid.User, new Color4(238, 170, 0, 255), () => OnEditBeatmap?.Invoke(), WEDGE_WIDTH, Key.B)); - buttonsEdit.Add(new MainMenuButton(CommonStrings.Skins.ToLower(), @"button-default-select", FontAwesome.Solid.Users, new Color4(220, 160, 0, 255), () => OnEditSkin?.Invoke(), 0, Key.S)); + buttonsEdit.Add(new MainMenuButton(EditorStrings.BeatmapEditor.ToLower(), @"button-default-select", HexaconsIcons.Beatmap, new Color4(238, 170, 0, 255), () => OnEditBeatmap?.Invoke(), WEDGE_WIDTH, Key.B)); + buttonsEdit.Add(new MainMenuButton(SkinEditorStrings.SkinEditor.ToLower(), @"button-default-select", HexaconsIcons.Editor, new Color4(220, 160, 0, 255), () => OnEditSkin?.Invoke(), 0, Key.S)); buttonsEdit.ForEach(b => b.VisibleState = ButtonSystemState.Edit); buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Play, @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P)); From 62a04a93c837db0aef81557585120877f513660c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 23 Nov 2023 16:22:34 +0900 Subject: [PATCH 1685/4092] Implement legacy catch health processor --- .../Scoring/CatchHealthProcessor.cs | 15 ++ .../Scoring/LegacyCatchHealthProcessor.cs | 237 ++++++++++++++++++ 2 files changed, 252 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/Scoring/CatchHealthProcessor.cs create mode 100644 osu.Game.Rulesets.Catch/Scoring/LegacyCatchHealthProcessor.cs diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchHealthProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchHealthProcessor.cs new file mode 100644 index 0000000000..7e8162bdfa --- /dev/null +++ b/osu.Game.Rulesets.Catch/Scoring/CatchHealthProcessor.cs @@ -0,0 +1,15 @@ +// 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.Scoring; + +namespace osu.Game.Rulesets.Catch.Scoring +{ + public partial class CatchHealthProcessor : DrainingHealthProcessor + { + public CatchHealthProcessor(double drainStartTime) + : base(drainStartTime) + { + } + } +} diff --git a/osu.Game.Rulesets.Catch/Scoring/LegacyCatchHealthProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/LegacyCatchHealthProcessor.cs new file mode 100644 index 0000000000..ef13ba222c --- /dev/null +++ b/osu.Game.Rulesets.Catch/Scoring/LegacyCatchHealthProcessor.cs @@ -0,0 +1,237 @@ +// 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.Collections.Generic; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Catch.Scoring +{ + /// + /// Reference implementation for osu!stable's HP drain. + /// Cannot be used for gameplay. + /// + public partial class LegacyCatchHealthProcessor : DrainingHealthProcessor + { + private const double hp_bar_maximum = 200; + private const double hp_combo_geki = 14; + private const double hp_hit_300 = 6; + private const double hp_slider_tick = 3; + + public Action? OnIterationFail; + public Action? OnIterationSuccess; + public bool ApplyComboEndBonus { get; set; } = true; + + private double lowestHpEver; + private double lowestHpEnd; + private double lowestHpComboEnd; + private double hpRecoveryAvailable; + private double hpMultiplierNormal; + private double hpMultiplierComboEnd; + + public LegacyCatchHealthProcessor(double drainStartTime) + : base(drainStartTime) + { + } + + public override void ApplyBeatmap(IBeatmap beatmap) + { + lowestHpEver = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 195, 160, 60); + lowestHpComboEnd = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 198, 170, 80); + lowestHpEnd = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 198, 180, 80); + hpRecoveryAvailable = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 8, 4, 0); + + base.ApplyBeatmap(beatmap); + } + + protected override void ApplyResultInternal(JudgementResult result) + { + if (!IsSimulating) + throw new NotSupportedException("The legacy catch health processor is not supported for gameplay."); + } + + protected override void RevertResultInternal(JudgementResult result) + { + if (!IsSimulating) + throw new NotSupportedException("The legacy catch health processor is not supported for gameplay."); + } + + protected override void Reset(bool storeResults) + { + hpMultiplierNormal = 1; + hpMultiplierComboEnd = 1; + + base.Reset(storeResults); + } + + protected override double ComputeDrainRate() + { + double testDrop = 0.05; + double currentHp; + double currentHpUncapped; + + List<(HitObject hitObject, bool newCombo)> allObjects = enumerateHitObjects(Beatmap).Where(h => h.hitObject is Fruit || h.hitObject is Droplet || h.hitObject is Banana).ToList(); + + do + { + currentHp = hp_bar_maximum; + currentHpUncapped = hp_bar_maximum; + + double lowestHp = currentHp; + double lastTime = DrainStartTime; + int currentBreak = 0; + bool fail = false; + int comboTooLowCount = 0; + string failReason = string.Empty; + + for (int i = 0; i < allObjects.Count; i++) + { + HitObject h = allObjects[i].hitObject; + + // Find active break (between current and lastTime) + double localLastTime = lastTime; + double breakTime = 0; + + // Subtract any break time from the duration since the last object + if (Beatmap.Breaks.Count > 0 && currentBreak < Beatmap.Breaks.Count) + { + BreakPeriod e = Beatmap.Breaks[currentBreak]; + + if (e.StartTime >= localLastTime && e.EndTime <= h.StartTime) + { + // consider break start equal to object end time for version 8+ since drain stops during this time + breakTime = (Beatmap.BeatmapInfo.BeatmapVersion < 8) ? (e.EndTime - e.StartTime) : e.EndTime - localLastTime; + currentBreak++; + } + } + + reduceHp(testDrop * (h.StartTime - lastTime - breakTime)); + + lastTime = h.GetEndTime(); + + if (currentHp < lowestHp) + lowestHp = currentHp; + + if (currentHp <= lowestHpEver) + { + fail = true; + testDrop *= 0.96; + failReason = $"hp too low ({currentHp / hp_bar_maximum} < {lowestHpEver / hp_bar_maximum})"; + break; + } + + switch (h) + { + case Fruit: + if (ApplyComboEndBonus && (i == allObjects.Count - 1 || allObjects[i + 1].newCombo)) + { + increaseHp(hpMultiplierComboEnd * hp_combo_geki + hpMultiplierNormal * hp_hit_300); + + if (currentHp < lowestHpComboEnd) + { + if (++comboTooLowCount > 2) + { + hpMultiplierComboEnd *= 1.07; + hpMultiplierNormal *= 1.03; + fail = true; + failReason = $"combo end hp too low ({currentHp / hp_bar_maximum} < {lowestHpComboEnd / hp_bar_maximum})"; + } + } + } + else + increaseHp(hpMultiplierNormal * hp_hit_300); + + break; + + case Banana: + increaseHp(hpMultiplierNormal / 2); + break; + + case TinyDroplet: + increaseHp(hpMultiplierNormal * hp_slider_tick * 0.1); + break; + + case Droplet: + increaseHp(hpMultiplierNormal * hp_slider_tick); + break; + } + + if (fail) + break; + } + + if (!fail && currentHp < lowestHpEnd) + { + fail = true; + testDrop *= 0.94; + hpMultiplierComboEnd *= 1.01; + hpMultiplierNormal *= 1.01; + failReason = $"end hp too low ({currentHp / hp_bar_maximum} < {lowestHpEnd / hp_bar_maximum})"; + } + + double recovery = (currentHpUncapped - hp_bar_maximum) / allObjects.Count; + + if (!fail && recovery < hpRecoveryAvailable) + { + fail = true; + testDrop *= 0.96; + hpMultiplierComboEnd *= 1.02; + hpMultiplierNormal *= 1.01; + failReason = $"recovery too low ({recovery / hp_bar_maximum} < {hpRecoveryAvailable / hp_bar_maximum})"; + } + + if (fail) + { + OnIterationFail?.Invoke($"FAILED drop {testDrop / hp_bar_maximum}: {failReason}"); + continue; + } + + OnIterationSuccess?.Invoke($"PASSED drop {testDrop / hp_bar_maximum}"); + return testDrop / hp_bar_maximum; + } while (true); + + void reduceHp(double amount) + { + currentHpUncapped = Math.Max(0, currentHpUncapped - amount); + currentHp = Math.Max(0, currentHp - amount); + } + + void increaseHp(double amount) + { + currentHpUncapped += amount; + currentHp = Math.Max(0, Math.Min(hp_bar_maximum, currentHp + amount)); + } + } + + private IEnumerable<(HitObject hitObject, bool newCombo)> enumerateHitObjects(IBeatmap beatmap) + { + return enumerateRecursively(beatmap.HitObjects); + + static IEnumerable<(HitObject hitObject, bool newCombo)> enumerateRecursively(IEnumerable hitObjects) + { + foreach (var hitObject in hitObjects) + { + // The combo end will either be attached to the hitobject itself if it has no children, or the very first child if it has children. + bool newCombo = (hitObject as IHasComboInformation)?.NewCombo ?? false; + + foreach ((HitObject nested, bool _) in enumerateRecursively(hitObject.NestedHitObjects)) + { + yield return (nested, newCombo); + + // Since the combo was attached to the first child, don't attach it to any other child or the parenting hitobject itself. + newCombo = false; + } + + yield return (hitObject, newCombo); + } + } + } + } +} From acf3de5e25f343f492bbf33135bb2160126a94e6 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 24 Nov 2023 13:22:46 +0900 Subject: [PATCH 1686/4092] Add CatchHealthProcessor, following legacy calculations --- .../Scoring/CatchHealthProcessor.cs | 160 ++++++++++++++++++ 1 file changed, 160 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchHealthProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchHealthProcessor.cs index 7e8162bdfa..1f6a696f98 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchHealthProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchHealthProcessor.cs @@ -1,15 +1,175 @@ // 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.Collections.Generic; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Catch.Scoring { public partial class CatchHealthProcessor : DrainingHealthProcessor { + public Action? OnIterationFail; + public Action? OnIterationSuccess; + + private double lowestHpEver; + private double lowestHpEnd; + private double hpRecoveryAvailable; + private double hpMultiplierNormal; + public CatchHealthProcessor(double drainStartTime) : base(drainStartTime) { } + + public override void ApplyBeatmap(IBeatmap beatmap) + { + lowestHpEver = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 0.975, 0.8, 0.3); + lowestHpEnd = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 0.99, 0.9, 0.4); + hpRecoveryAvailable = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 0.04, 0.02, 0); + + base.ApplyBeatmap(beatmap); + } + + protected override void Reset(bool storeResults) + { + hpMultiplierNormal = 1; + base.Reset(storeResults); + } + + protected override double ComputeDrainRate() + { + double testDrop = 0.00025; + double currentHp; + double currentHpUncapped; + + while (true) + { + currentHp = 1; + currentHpUncapped = 1; + + double lowestHp = currentHp; + double lastTime = DrainStartTime; + int currentBreak = 0; + bool fail = false; + + List allObjects = EnumerateHitObjects(Beatmap).Where(h => h is Fruit || h is Droplet || h is Banana).ToList(); + + for (int i = 0; i < allObjects.Count; i++) + { + HitObject h = allObjects[i]; + + double localLastTime = lastTime; + double breakTime = 0; + + if (Beatmap.Breaks.Count > 0 && currentBreak < Beatmap.Breaks.Count) + { + BreakPeriod e = Beatmap.Breaks[currentBreak]; + + if (e.StartTime >= localLastTime && e.EndTime <= h.StartTime) + { + // consider break start equal to object end time for version 8+ since drain stops during this time + breakTime = (Beatmap.BeatmapInfo.BeatmapVersion < 8) ? (e.EndTime - e.StartTime) : e.EndTime - localLastTime; + currentBreak++; + } + } + + reduceHp(testDrop * (h.StartTime - lastTime - breakTime)); + + lastTime = h.GetEndTime(); + + if (currentHp < lowestHp) + lowestHp = currentHp; + + if (currentHp <= lowestHpEver) + { + fail = true; + testDrop *= 0.96; + OnIterationFail?.Invoke($"FAILED drop {testDrop}: hp too low ({currentHp} < {lowestHpEver})"); + break; + } + + increaseHp(h); + } + + if (!fail && currentHp < lowestHpEnd) + { + fail = true; + testDrop *= 0.94; + hpMultiplierNormal *= 1.01; + OnIterationFail?.Invoke($"FAILED drop {testDrop}: end hp too low ({currentHp} < {lowestHpEnd})"); + } + + double recovery = (currentHpUncapped - 1) / allObjects.Count; + + if (!fail && recovery < hpRecoveryAvailable) + { + fail = true; + testDrop *= 0.96; + hpMultiplierNormal *= 1.01; + OnIterationFail?.Invoke($"FAILED drop {testDrop}: recovery too low ({recovery} < {hpRecoveryAvailable})"); + } + + if (!fail) + { + OnIterationSuccess?.Invoke($"PASSED drop {testDrop}"); + return testDrop; + } + } + + void reduceHp(double amount) + { + currentHpUncapped = Math.Max(0, currentHpUncapped - amount); + currentHp = Math.Max(0, currentHp - amount); + } + + void increaseHp(HitObject hitObject) + { + double amount = healthIncreaseFor(hitObject.CreateJudgement().MaxResult); + currentHpUncapped += amount; + currentHp = Math.Max(0, Math.Min(1, currentHp + amount)); + } + } + + protected override double GetHealthIncreaseFor(JudgementResult result) => healthIncreaseFor(result.Type); + + private double healthIncreaseFor(HitResult result) + { + double increase = 0; + + switch (result) + { + case HitResult.SmallTickMiss: + return 0; + + case HitResult.LargeTickMiss: + case HitResult.Miss: + return IBeatmapDifficultyInfo.DifficultyRange(Beatmap.Difficulty.DrainRate, -0.03, -0.125, -0.2); + + case HitResult.SmallTickHit: + increase = 0.0015; + break; + + case HitResult.LargeTickHit: + increase = 0.015; + break; + + case HitResult.Great: + increase = 0.03; + break; + + case HitResult.LargeBonus: + increase = 0.0025; + break; + } + + return hpMultiplierNormal * increase; + } } } From 4ba6450c7703bbd44e97a5b3180a5afc4a5115ab Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 24 Nov 2023 13:32:14 +0900 Subject: [PATCH 1687/4092] Use better break calculation --- .../Scoring/CatchHealthProcessor.cs | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchHealthProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchHealthProcessor.cs index 1f6a696f98..6d831ad223 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchHealthProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchHealthProcessor.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; @@ -65,22 +64,16 @@ namespace osu.Game.Rulesets.Catch.Scoring { HitObject h = allObjects[i]; - double localLastTime = lastTime; - double breakTime = 0; - - if (Beatmap.Breaks.Count > 0 && currentBreak < Beatmap.Breaks.Count) + while (currentBreak < Beatmap.Breaks.Count && Beatmap.Breaks[currentBreak].EndTime <= h.StartTime) { - BreakPeriod e = Beatmap.Breaks[currentBreak]; - - if (e.StartTime >= localLastTime && e.EndTime <= h.StartTime) - { - // consider break start equal to object end time for version 8+ since drain stops during this time - breakTime = (Beatmap.BeatmapInfo.BeatmapVersion < 8) ? (e.EndTime - e.StartTime) : e.EndTime - localLastTime; - currentBreak++; - } + // If two hitobjects are separated by a break period, there is no drain for the full duration between the hitobjects. + // This differs from legacy (version < 8) beatmaps which continue draining until the break section is entered, + // but this shouldn't have a noticeable impact in practice. + lastTime = h.StartTime; + currentBreak++; } - reduceHp(testDrop * (h.StartTime - lastTime - breakTime)); + reduceHp(testDrop * (h.StartTime - lastTime)); lastTime = h.GetEndTime(); From bb662676347c79343da3ba480693ff5c272b6878 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 24 Nov 2023 13:46:41 +0900 Subject: [PATCH 1688/4092] Actually use CatchHealthProcessor for the ruleset --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 9ceb78893e..013a709663 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -39,6 +39,8 @@ namespace osu.Game.Rulesets.Catch public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(); + public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new CatchHealthProcessor(drainStartTime); + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap, this); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap); From 4c7d2bb0fbae426e533572ad905531677f3f27fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Nov 2023 15:14:25 +0900 Subject: [PATCH 1689/4092] Apply NRT to `SpectatorScreen` --- osu.Game/Screens/Spectate/SpectatorScreen.cs | 31 +++++++++----------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 48b5c210b8..f9d5355663 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -1,13 +1,10 @@ // 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.Diagnostics; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; @@ -33,22 +30,27 @@ namespace osu.Game.Screens.Spectate private readonly List users = new List(); [Resolved] - private BeatmapManager beatmaps { get; set; } + private BeatmapManager beatmaps { get; set; } = null!; [Resolved] - private RulesetStore rulesets { get; set; } + private RulesetStore rulesets { get; set; } = null!; [Resolved] - private SpectatorClient spectatorClient { get; set; } + private SpectatorClient spectatorClient { get; set; } = null!; [Resolved] - private UserLookupCache userLookupCache { get; set; } + private UserLookupCache userLookupCache { get; set; } = null!; + + [Resolved] + private RealmAccess realm { get; set; } = null!; private readonly IBindableDictionary userStates = new BindableDictionary(); private readonly Dictionary userMap = new Dictionary(); private readonly Dictionary gameplayStates = new Dictionary(); + private IDisposable? realmSubscription; + /// /// Creates a new . /// @@ -58,11 +60,6 @@ namespace osu.Game.Screens.Spectate this.users.AddRange(users); } - [Resolved] - private RealmAccess realm { get; set; } - - private IDisposable realmSubscription; - protected override void LoadComplete() { base.LoadComplete(); @@ -90,7 +87,7 @@ namespace osu.Game.Screens.Spectate })); } - private void beatmapsChanged(IRealmCollection items, ChangeSet changes) + private void beatmapsChanged(IRealmCollection items, ChangeSet? changes) { if (changes?.InsertedIndices == null) return; @@ -109,7 +106,7 @@ namespace osu.Game.Screens.Spectate } } - private void onUserStatesChanged(object sender, NotifyDictionaryChangedEventArgs e) + private void onUserStatesChanged(object? sender, NotifyDictionaryChangedEventArgs e) { switch (e.Action) { @@ -207,14 +204,14 @@ namespace osu.Game.Screens.Spectate ///
/// The user whose state has changed. /// The new state. - protected abstract void OnNewPlayingUserState(int userId, [NotNull] SpectatorState spectatorState); + protected abstract void OnNewPlayingUserState(int userId, SpectatorState spectatorState); /// /// Starts gameplay for a user. /// /// The user to start gameplay for. /// The gameplay state. - protected abstract void StartGameplay(int userId, [NotNull] SpectatorGameplayState spectatorGameplayState); + protected abstract void StartGameplay(int userId, SpectatorGameplayState spectatorGameplayState); /// /// Quits gameplay for a user. @@ -243,7 +240,7 @@ namespace osu.Game.Screens.Spectate { base.Dispose(isDisposing); - if (spectatorClient != null) + if (spectatorClient.IsNotNull()) { foreach ((int userId, var _) in userMap) spectatorClient.StopWatchingUser(userId); From dabbdf674bc7cab4bbb414ef451c2af158ff8be4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Nov 2023 15:19:04 +0900 Subject: [PATCH 1690/4092] Rename `SoloSpectator` to `SoloSpectatorScreen` --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs | 8 ++++---- osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs | 2 +- .../Play/{SoloSpectator.cs => SoloSpectatorScreen.cs} | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) rename osu.Game/Screens/Play/{SoloSpectator.cs => SoloSpectatorScreen.cs} (98%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index ffd034e4d2..db74f7d673 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.Gameplay private TestSpectatorClient spectatorClient => dependenciesScreen.SpectatorClient; private DependenciesScreen dependenciesScreen; - private SoloSpectator spectatorScreen; + private SoloSpectatorScreen spectatorScreen; private BeatmapSetInfo importedBeatmap; private int importedBeatmapId; @@ -127,7 +127,7 @@ namespace osu.Game.Tests.Visual.Gameplay { loadSpectatingScreen(); - AddAssert("screen hasn't changed", () => Stack.CurrentScreen is SoloSpectator); + AddAssert("screen hasn't changed", () => Stack.CurrentScreen is SoloSpectatorScreen); start(); waitForPlayer(); @@ -255,7 +255,7 @@ namespace osu.Game.Tests.Visual.Gameplay start(-1234); sendFrames(); - AddAssert("screen didn't change", () => Stack.CurrentScreen is SoloSpectator); + AddAssert("screen didn't change", () => Stack.CurrentScreen is SoloSpectatorScreen); } [Test] @@ -381,7 +381,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void loadSpectatingScreen() { - AddStep("load spectator", () => LoadScreen(spectatorScreen = new SoloSpectator(streamingUser))); + AddStep("load spectator", () => LoadScreen(spectatorScreen = new SoloSpectatorScreen(streamingUser))); AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded); } diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index 02f0a6e80d..6967a61204 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -212,7 +212,7 @@ namespace osu.Game.Overlays.Dashboard Text = "Spectate", Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Action = () => performer?.PerformFromScreen(s => s.Push(new SoloSpectator(User))), + Action = () => performer?.PerformFromScreen(s => s.Push(new SoloSpectatorScreen(User))), Enabled = { Value = User.Id != api.LocalUser.Value.Id } } } diff --git a/osu.Game/Screens/Play/SoloSpectator.cs b/osu.Game/Screens/Play/SoloSpectatorScreen.cs similarity index 98% rename from osu.Game/Screens/Play/SoloSpectator.cs rename to osu.Game/Screens/Play/SoloSpectatorScreen.cs index f5af2684d3..59b7d87129 100644 --- a/osu.Game/Screens/Play/SoloSpectator.cs +++ b/osu.Game/Screens/Play/SoloSpectatorScreen.cs @@ -33,7 +33,7 @@ using osuTK; namespace osu.Game.Screens.Play { [Cached(typeof(IPreviewTrackOwner))] - public partial class SoloSpectator : SpectatorScreen, IPreviewTrackOwner + public partial class SoloSpectatorScreen : SpectatorScreen, IPreviewTrackOwner { [NotNull] private readonly APIUser targetUser; @@ -67,7 +67,7 @@ namespace osu.Game.Screens.Play private APIBeatmapSet beatmapSet; - public SoloSpectator([NotNull] APIUser targetUser) + public SoloSpectatorScreen([NotNull] APIUser targetUser) : base(targetUser.Id) { this.targetUser = targetUser; From 335e8efff769c4ccd5cb4b9c66687a1cc16f941d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Nov 2023 15:56:49 +0900 Subject: [PATCH 1691/4092] Apply NRT to `SoloSpecatorScreen` --- osu.Game/Screens/Play/SoloSpectatorScreen.cs | 38 ++++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Play/SoloSpectatorScreen.cs b/osu.Game/Screens/Play/SoloSpectatorScreen.cs index 59b7d87129..7acdc51fb3 100644 --- a/osu.Game/Screens/Play/SoloSpectatorScreen.cs +++ b/osu.Game/Screens/Play/SoloSpectatorScreen.cs @@ -1,10 +1,7 @@ // 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.Diagnostics; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -35,39 +32,38 @@ namespace osu.Game.Screens.Play [Cached(typeof(IPreviewTrackOwner))] public partial class SoloSpectatorScreen : SpectatorScreen, IPreviewTrackOwner { - [NotNull] - private readonly APIUser targetUser; + [Resolved] + private IAPIProvider api { get; set; } = null!; [Resolved] - private IAPIProvider api { get; set; } + private PreviewTrackManager previewTrackManager { get; set; } = null!; [Resolved] - private PreviewTrackManager previewTrackManager { get; set; } + private BeatmapManager beatmaps { get; set; } = null!; [Resolved] - private BeatmapManager beatmaps { get; set; } - - [Resolved] - private BeatmapModelDownloader beatmapDownloader { get; set; } + private BeatmapModelDownloader beatmapDownloader { get; set; } = null!; [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); - private Container beatmapPanelContainer; - private RoundedButton watchButton; - private SettingsCheckbox automaticDownload; + private Container beatmapPanelContainer = null!; + private RoundedButton watchButton = null!; + private SettingsCheckbox automaticDownload = null!; + + private readonly APIUser targetUser; /// /// The player's immediate online gameplay state. /// This doesn't always reflect the gameplay state being watched. /// - private SpectatorGameplayState immediateSpectatorGameplayState; + private SpectatorGameplayState? immediateSpectatorGameplayState; - private GetBeatmapSetRequest onlineBeatmapRequest; + private GetBeatmapSetRequest? onlineBeatmapRequest; - private APIBeatmapSet beatmapSet; + private APIBeatmapSet? beatmapSet; - public SoloSpectatorScreen([NotNull] APIUser targetUser) + public SoloSpectatorScreen(APIUser targetUser) : base(targetUser.Id) { this.targetUser = targetUser; @@ -199,10 +195,12 @@ namespace osu.Game.Screens.Play previewTrackManager.StopAnyPlaying(this); } - private ScheduledDelegate scheduledStart; + private ScheduledDelegate? scheduledStart; - private void scheduleStart(SpectatorGameplayState spectatorGameplayState) + private void scheduleStart(SpectatorGameplayState? spectatorGameplayState) { + Debug.Assert(spectatorGameplayState != null); + // This function may be called multiple times in quick succession once the screen becomes current again. scheduledStart?.Cancel(); scheduledStart = Schedule(() => From e06d79281dc682fadf83cac72bd0bfc2561e3e65 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Nov 2023 16:40:22 +0900 Subject: [PATCH 1692/4092] Update tests to work with nested screen --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index db74f7d673..9a1fd660d5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -100,19 +100,18 @@ namespace osu.Game.Tests.Visual.Gameplay start(); - AddUntilStep("wait for player loader", () => (Stack.CurrentScreen as PlayerLoader)?.IsLoaded == true); + AddUntilStep("wait for player loader", () => this.ChildrenOfType().SingleOrDefault()?.IsLoaded == true); AddUntilStep("queue send frames on player load", () => { - var loadingPlayer = (Stack.CurrentScreen as PlayerLoader)?.CurrentPlayer; + var loadingPlayer = this.ChildrenOfType().SingleOrDefault()?.CurrentPlayer; if (loadingPlayer == null) return false; loadingPlayer.OnLoadComplete += _ => - { spectatorClient.SendFramesFromUser(streamingUser.Id, 10, gameplay_start); - }; + return true; }); @@ -360,12 +359,12 @@ namespace osu.Game.Tests.Visual.Gameplay private OsuFramedReplayInputHandler replayHandler => (OsuFramedReplayInputHandler)Stack.ChildrenOfType().First().ReplayInputHandler; - private Player player => Stack.CurrentScreen as Player; + private Player player => this.ChildrenOfType().Single(); private double currentFrameStableTime => player.ChildrenOfType().First().CurrentTime; - private void waitForPlayer() => AddUntilStep("wait for player", () => (Stack.CurrentScreen as Player)?.IsLoaded == true); + private void waitForPlayer() => AddUntilStep("wait for player", () => this.ChildrenOfType().SingleOrDefault()?.IsLoaded == true); private void start(int? beatmapId = null) => AddStep("start play", () => spectatorClient.SendStartPlay(streamingUser.Id, beatmapId ?? importedBeatmapId)); From ed5375536f1cf96446617b68251354a7f458c2c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Nov 2023 17:14:07 +0900 Subject: [PATCH 1693/4092] Reduce access to various fields and events in `SpectatorClient` Restore some `virtual` specs to appease Moq --- osu.Game/Online/Spectator/SpectatorClient.cs | 22 ++++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 14e137caf1..e7435adf29 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -46,9 +46,9 @@ namespace osu.Game.Online.Spectator public IBindableList PlayingUsers => playingUsers; /// - /// Whether the local user is playing. + /// Whether the spectated user is playing. /// - protected internal bool IsPlaying { get; private set; } + private bool isPlaying { get; set; } /// /// Called whenever new frames arrive from the server. @@ -58,17 +58,17 @@ namespace osu.Game.Online.Spectator /// /// Called whenever a user starts a play session, or immediately if the user is being watched and currently in a play session. /// - public virtual event Action? OnUserBeganPlaying; + public event Action? OnUserBeganPlaying; /// /// Called whenever a user finishes a play session. /// - public virtual event Action? OnUserFinishedPlaying; + public event Action? OnUserFinishedPlaying; /// /// Called whenever a user-submitted score has been fully processed. /// - public virtual event Action? OnUserScoreProcessed; + public event Action? OnUserScoreProcessed; /// /// A dictionary containing all users currently being watched, with the number of watching components for each user. @@ -114,7 +114,7 @@ namespace osu.Game.Online.Spectator } // re-send state in case it wasn't received - if (IsPlaying) + if (isPlaying) // TODO: this is likely sent out of order after a reconnect scenario. needs further consideration. BeginPlayingInternal(currentScoreToken, currentState); } @@ -179,10 +179,10 @@ namespace osu.Game.Online.Spectator // This schedule is only here to match the one below in `EndPlaying`. Schedule(() => { - if (IsPlaying) + if (isPlaying) throw new InvalidOperationException($"Cannot invoke {nameof(BeginPlaying)} when already playing"); - IsPlaying = true; + isPlaying = true; // transfer state at point of beginning play currentState.BeatmapID = score.ScoreInfo.BeatmapInfo!.OnlineID; @@ -202,7 +202,7 @@ namespace osu.Game.Online.Spectator public void HandleFrame(ReplayFrame frame) => Schedule(() => { - if (!IsPlaying) + if (!isPlaying) { Logger.Log($"Frames arrived at {nameof(SpectatorClient)} outside of gameplay scope and will be ignored."); return; @@ -224,7 +224,7 @@ namespace osu.Game.Online.Spectator // We probably need to find a better way to handle this... Schedule(() => { - if (!IsPlaying) + if (!isPlaying) return; // Disposal can take some time, leading to EndPlaying potentially being called after a future play session. @@ -235,7 +235,7 @@ namespace osu.Game.Online.Spectator if (pendingFrames.Count > 0) purgePendingFrames(); - IsPlaying = false; + isPlaying = false; currentBeatmap = null; if (state.HasPassed) From 73ad92189b0b8026ec966a8051d72d04637f8ead Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Nov 2023 12:07:01 +0900 Subject: [PATCH 1694/4092] Add test coverage of remote player quitting immediately quitting locally --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 9a1fd660d5..e3a10c655a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -332,6 +332,8 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("send quit", () => spectatorClient.SendEndPlay(streamingUser.Id)); AddUntilStep("state is quit", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Quit); + AddAssert("wait for player exit", () => Stack.CurrentScreen is SoloSpectatorScreen); + start(); sendFrames(); waitForPlayer(); From b024065857bbfbf2953153d3ccc2ee90171f8e52 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Nov 2023 14:21:52 +0900 Subject: [PATCH 1695/4092] Remove implicit schedule of `abstract` methods in `SpectatorScreen` This allows each implementation to have control over scheduling. Without this, the solo implementation would not be able to handle quit events while watching a player, as it would push a child (gameplay) screen to the stack where the `SpectatorScreen` would usually be. --- .../Spectate/MultiSpectatorScreen.cs | 8 +++---- osu.Game/Screens/Play/SoloSpectatorScreen.cs | 22 ++++++++++++------- osu.Game/Screens/Spectate/SpectatorScreen.cs | 9 +++++--- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index c1b1127542..eb2980fe1e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -228,7 +228,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { } - protected override void StartGameplay(int userId, SpectatorGameplayState spectatorGameplayState) + protected override void StartGameplay(int userId, SpectatorGameplayState spectatorGameplayState) => Schedule(() => { var playerArea = instances.Single(i => i.UserId == userId); @@ -242,9 +242,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate return; playerArea.LoadScore(spectatorGameplayState.Score); - } + }); - protected override void QuitGameplay(int userId) + protected override void QuitGameplay(int userId) => Schedule(() => { RemoveUser(userId); @@ -252,7 +252,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate instance.FadeColour(colours.Gray4, 400, Easing.OutQuint); syncManager.RemoveManagedClock(instance.SpectatorPlayerClock); - } + }); public override bool OnBackButton() { diff --git a/osu.Game/Screens/Play/SoloSpectatorScreen.cs b/osu.Game/Screens/Play/SoloSpectatorScreen.cs index 7acdc51fb3..770018a0c8 100644 --- a/osu.Game/Screens/Play/SoloSpectatorScreen.cs +++ b/osu.Game/Screens/Play/SoloSpectatorScreen.cs @@ -164,27 +164,33 @@ namespace osu.Game.Screens.Play automaticDownload.Current.BindValueChanged(_ => checkForAutomaticDownload()); } - protected override void OnNewPlayingUserState(int userId, SpectatorState spectatorState) + protected override void OnNewPlayingUserState(int userId, SpectatorState spectatorState) => Schedule(() => { clearDisplay(); showBeatmapPanel(spectatorState); - } + }); - protected override void StartGameplay(int userId, SpectatorGameplayState spectatorGameplayState) + protected override void StartGameplay(int userId, SpectatorGameplayState spectatorGameplayState) => Schedule(() => { immediateSpectatorGameplayState = spectatorGameplayState; watchButton.Enabled.Value = true; scheduleStart(spectatorGameplayState); - } + }); protected override void QuitGameplay(int userId) { - scheduledStart?.Cancel(); - immediateSpectatorGameplayState = null; - watchButton.Enabled.Value = false; + // Importantly, don't schedule this call, as a child screen may be present (and will cause the schedule to not be run as expected). + this.MakeCurrent(); - clearDisplay(); + Schedule(() => + { + scheduledStart?.Cancel(); + immediateSpectatorGameplayState = null; + watchButton.Enabled.Value = false; + + clearDisplay(); + }); } private void clearDisplay() diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index f9d5355663..21f96c83f5 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -129,7 +129,7 @@ namespace osu.Game.Screens.Spectate switch (newState.State) { case SpectatedUserState.Playing: - Schedule(() => OnNewPlayingUserState(userId, newState)); + OnNewPlayingUserState(userId, newState); startGameplay(userId); break; @@ -173,7 +173,7 @@ namespace osu.Game.Screens.Spectate var gameplayState = new SpectatorGameplayState(score, resolvedRuleset, beatmaps.GetWorkingBeatmap(resolvedBeatmap)); gameplayStates[userId] = gameplayState; - Schedule(() => StartGameplay(userId, gameplayState)); + StartGameplay(userId, gameplayState); } /// @@ -196,11 +196,12 @@ namespace osu.Game.Screens.Spectate markReceivedAllFrames(userId); gameplayStates.Remove(userId); - Schedule(() => QuitGameplay(userId)); + QuitGameplay(userId); } /// /// Invoked when a spectated user's state has changed to a new state indicating the player is currently playing. + /// Thread safety is not guaranteed – should be scheduled as required. /// /// The user whose state has changed. /// The new state. @@ -208,6 +209,7 @@ namespace osu.Game.Screens.Spectate /// /// Starts gameplay for a user. + /// Thread safety is not guaranteed – should be scheduled as required. /// /// The user to start gameplay for. /// The gameplay state. @@ -215,6 +217,7 @@ namespace osu.Game.Screens.Spectate /// /// Quits gameplay for a user. + /// Thread safety is not guaranteed – should be scheduled as required. /// /// The user to quit gameplay for. protected abstract void QuitGameplay(int userId); From 51e2ce500d67c4f634d75af59b04ba69302ef44f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Nov 2023 12:45:03 +0900 Subject: [PATCH 1696/4092] Add test coverage of remote user failing immediately failing locally --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index e3a10c655a..f5e4c5da51 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -352,6 +352,8 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("send failed", () => spectatorClient.SendEndPlay(streamingUser.Id, SpectatedUserState.Failed)); AddUntilStep("state is failed", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Failed); + AddUntilStep("wait for player to fail", () => player.GameplayState.HasFailed); + start(); sendFrames(); waitForPlayer(); From ca93fdc94b07bd1bd539d72265ea65edfe8fa6a0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Nov 2023 17:53:35 +0900 Subject: [PATCH 1697/4092] Add visualisation of when a spectated player fails Create a new stack each time for isolation and safety --- .../Spectate/MultiSpectatorScreen.cs | 5 ++++ osu.Game/Screens/Play/SoloSpectatorPlayer.cs | 4 +-- osu.Game/Screens/Play/SoloSpectatorScreen.cs | 14 ++++++++++- osu.Game/Screens/Play/SpectatorPlayer.cs | 12 ++++++++- osu.Game/Screens/Spectate/SpectatorScreen.cs | 25 +++++++++++++++++++ 5 files changed, 56 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index eb2980fe1e..e2159f0e3b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -244,6 +244,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate playerArea.LoadScore(spectatorGameplayState.Score); }); + protected override void FailGameplay(int userId) + { + // We probably want to visualise this in the future. + } + protected override void QuitGameplay(int userId) => Schedule(() => { RemoveUser(userId); diff --git a/osu.Game/Screens/Play/SoloSpectatorPlayer.cs b/osu.Game/Screens/Play/SoloSpectatorPlayer.cs index c9d1f4acaa..8d25a0148d 100644 --- a/osu.Game/Screens/Play/SoloSpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SoloSpectatorPlayer.cs @@ -17,8 +17,8 @@ namespace osu.Game.Screens.Play protected override UserActivity InitialActivity => new UserActivity.SpectatingUser(Score.ScoreInfo); - public SoloSpectatorPlayer(Score score, PlayerConfiguration configuration = null) - : base(score, configuration) + public SoloSpectatorPlayer(Score score) + : base(score, new PlayerConfiguration { AllowUserInteraction = false }) { this.score = score; } diff --git a/osu.Game/Screens/Play/SoloSpectatorScreen.cs b/osu.Game/Screens/Play/SoloSpectatorScreen.cs index 770018a0c8..d4a9fc4b30 100644 --- a/osu.Game/Screens/Play/SoloSpectatorScreen.cs +++ b/osu.Game/Screens/Play/SoloSpectatorScreen.cs @@ -178,6 +178,19 @@ namespace osu.Game.Screens.Play scheduleStart(spectatorGameplayState); }); + protected override void FailGameplay(int userId) + { + if (this.GetChildScreen() is SpectatorPlayer player) + player.AllowFail(); + + Schedule(() => + { + scheduledStart?.Cancel(); + immediateSpectatorGameplayState = null; + clearDisplay(); + }); + } + protected override void QuitGameplay(int userId) { // Importantly, don't schedule this call, as a child screen may be present (and will cause the schedule to not be run as expected). @@ -187,7 +200,6 @@ namespace osu.Game.Screens.Play { scheduledStart?.Cancel(); immediateSpectatorGameplayState = null; - watchButton.Enabled.Value = false; clearDisplay(); }); diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index 30a5ac3741..bc95fa190c 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -25,7 +25,15 @@ namespace osu.Game.Screens.Play private readonly Score score; - protected override bool CheckModsAllowFailure() => false; // todo: better support starting mid-way through beatmap + protected override bool CheckModsAllowFailure() + { + if (!allowFail) + return false; + + return base.CheckModsAllowFailure(); + } + + private bool allowFail; protected SpectatorPlayer(Score score, PlayerConfiguration configuration = null) : base(configuration) @@ -123,5 +131,7 @@ namespace osu.Game.Screens.Play if (SpectatorClient != null) SpectatorClient.OnNewFrames -= userSentFrames; } + + public void AllowFail() => allowFail = true; } } diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 21f96c83f5..c4aef3c878 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -137,6 +137,10 @@ namespace osu.Game.Screens.Spectate markReceivedAllFrames(userId); break; + case SpectatedUserState.Failed: + failGameplay(userId); + break; + case SpectatedUserState.Quit: quitGameplay(userId); break; @@ -185,6 +189,20 @@ namespace osu.Game.Screens.Spectate gameplayState.Score.Replay.HasReceivedAllFrames = true; } + private void failGameplay(int userId) + { + if (!userMap.ContainsKey(userId)) + return; + + if (!gameplayStates.ContainsKey(userId)) + return; + + markReceivedAllFrames(userId); + + gameplayStates.Remove(userId); + FailGameplay(userId); + } + private void quitGameplay(int userId) { if (!userMap.ContainsKey(userId)) @@ -222,6 +240,13 @@ namespace osu.Game.Screens.Spectate /// The user to quit gameplay for. protected abstract void QuitGameplay(int userId); + /// + /// Fails gameplay for a user. + /// Thread safety is not guaranteed – should be scheduled as required. + /// + /// The user to fail gameplay for. + protected abstract void FailGameplay(int userId); + /// /// Stops spectating a user. /// From 8375dd72d6e691ef393aecf1e2c3c418f46cd128 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Nov 2023 10:01:59 +0900 Subject: [PATCH 1698/4092] Add xmldoc to new `AllowFail` method --- osu.Game/Screens/Play/Player.cs | 7 +++++++ osu.Game/Screens/Play/SpectatorPlayer.cs | 8 ++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 8c7fc551ba..ad725e1a90 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -894,6 +894,13 @@ namespace osu.Game.Screens.Play #region Fail Logic + /// + /// Invoked when gameplay has permanently failed. + /// + protected virtual void OnFail() + { + } + protected FailOverlay FailOverlay { get; private set; } private FailAnimationContainer failAnimationContainer; diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index bc95fa190c..d1404ac184 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -68,6 +68,12 @@ namespace osu.Game.Screens.Play }, true); } + /// + /// Should be called when it is apparent that the player being spectated has failed. + /// This will subsequently stop blocking the fail screen from displaying (usually done out of safety). + /// + public void AllowFail() => allowFail = true; + protected override void StartGameplay() { base.StartGameplay(); @@ -131,7 +137,5 @@ namespace osu.Game.Screens.Play if (SpectatorClient != null) SpectatorClient.OnNewFrames -= userSentFrames; } - - public void AllowFail() => allowFail = true; } } From 4ad3cb3b4952aeb09847fe4e20fd581eaf8a52a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Nov 2023 18:20:22 +0900 Subject: [PATCH 1699/4092] Submit and send failed spectator state more aggressively --- osu.Game/Screens/Play/SubmittingPlayer.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 30fecbe149..14aaa8f638 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -54,6 +54,8 @@ namespace osu.Game.Screens.Play } AddInternal(new PlayerTouchInputDetector()); + + HealthProcessor.Failed += onFail; } protected override void LoadAsyncComplete() @@ -165,10 +167,21 @@ namespace osu.Game.Screens.Play spectatorClient.BeginPlaying(token, GameplayState, Score); } + private bool onFail() + { + submitFromFailOrQuit(); + return true; + } + public override bool OnExiting(ScreenExitEvent e) { bool exiting = base.OnExiting(e); + submitFromFailOrQuit(); + return exiting; + } + private void submitFromFailOrQuit() + { if (LoadedBeatmapSuccessfully) { Task.Run(async () => @@ -177,8 +190,6 @@ namespace osu.Game.Screens.Play spectatorClient.EndPlaying(GameplayState); }).FireAndForget(); } - - return exiting; } /// From ef5dd245894e3ea4b8658929054b92276b91c4e4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Nov 2023 10:28:15 +0900 Subject: [PATCH 1700/4092] Update failing test coverage and fix `onFail` being called too often --- .../Visual/Gameplay/TestScenePlayerScoreSubmission.cs | 1 - osu.Game/Screens/Play/Player.cs | 1 + osu.Game/Screens/Play/SubmittingPlayer.cs | 7 +++---- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index 1a7ea20cc0..f75a2656ef 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -179,7 +179,6 @@ namespace osu.Game.Tests.Visual.Gameplay addFakeHit(); AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); - AddStep("exit", () => Player.Exit()); AddUntilStep("wait for submission", () => Player.SubmittedScore != null); AddAssert("ensure failing submission", () => Player.SubmittedScore.ScoreInfo.Passed == false); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ad725e1a90..b469ab443c 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -933,6 +933,7 @@ namespace osu.Game.Screens.Play if (GameplayState.Mods.OfType().Any(m => m.RestartOnFail)) Restart(true); + OnFail(); return true; } diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 14aaa8f638..785164178a 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -54,8 +54,6 @@ namespace osu.Game.Screens.Play } AddInternal(new PlayerTouchInputDetector()); - - HealthProcessor.Failed += onFail; } protected override void LoadAsyncComplete() @@ -167,10 +165,11 @@ namespace osu.Game.Screens.Play spectatorClient.BeginPlaying(token, GameplayState, Score); } - private bool onFail() + protected override void OnFail() { + base.OnFail(); + submitFromFailOrQuit(); - return true; } public override bool OnExiting(ScreenExitEvent e) From d3a55d83c000dd1bf0174a9811585e214d605346 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Nov 2023 11:05:52 +0900 Subject: [PATCH 1701/4092] Schedule `FailScore` inside `onFail` instead of `onFailComplete` --- osu.Game/Screens/Play/Player.cs | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index b469ab443c..ff00b52f71 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -930,10 +930,22 @@ namespace osu.Game.Screens.Play failAnimationContainer.Start(); - if (GameplayState.Mods.OfType().Any(m => m.RestartOnFail)) - Restart(true); + // Failures can be triggered either by a judgement, or by a mod. + // + // For the case of a judgement, due to ordering considerations, ScoreProcessor will not have received + // the final judgement which triggered the failure yet (see DrawableRuleset.NewResult handling above). + // + // A schedule here ensures that any lingering judgements from the current frame are applied before we + // finalise the score as "failed". + Schedule(() => + { + ScoreProcessor.FailScore(Score.ScoreInfo); + OnFail(); + + if (GameplayState.Mods.OfType().Any(m => m.RestartOnFail)) + Restart(true); + }); - OnFail(); return true; } @@ -942,11 +954,6 @@ namespace osu.Game.Screens.Play /// private void onFailComplete() { - // fail completion is a good point to mark a score as failed, - // since the last judgement that caused the fail only applies to score processor after onFail. - // todo: this should probably be handled better. - ScoreProcessor.FailScore(Score.ScoreInfo); - GameplayClockContainer.Stop(); FailOverlay.Retries = RestartCount; From 7ceb49fbc095ae9b8bd91a6bf4c6c2d8763186f6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Nov 2023 14:58:57 +0900 Subject: [PATCH 1702/4092] Add extra test coverage and handle case where still at loading screen --- .../Visual/Gameplay/TestSceneSpectator.cs | 64 +++++++++++++------ osu.Game/Screens/Play/SoloSpectatorScreen.cs | 33 +++++----- 2 files changed, 61 insertions(+), 36 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index f5e4c5da51..1c7ede2b19 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual.Gameplay start(); - waitForPlayer(); + waitForPlayerCurrent(); sendFrames(startTime: gameplay_start); @@ -115,7 +115,7 @@ namespace osu.Game.Tests.Visual.Gameplay return true; }); - waitForPlayer(); + waitForPlayerCurrent(); AddUntilStep("state is playing", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Playing); AddAssert("time is greater than seek target", () => currentFrameStableTime, () => Is.GreaterThan(gameplay_start)); @@ -129,7 +129,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("screen hasn't changed", () => Stack.CurrentScreen is SoloSpectatorScreen); start(); - waitForPlayer(); + waitForPlayerCurrent(); sendFrames(); AddAssert("ensure frames arrived", () => replayHandler.HasFrames); @@ -155,7 +155,7 @@ namespace osu.Game.Tests.Visual.Gameplay loadSpectatingScreen(); start(); - waitForPlayer(); + waitForPlayerCurrent(); checkPaused(true); // send enough frames to ensure play won't be paused @@ -171,7 +171,7 @@ namespace osu.Game.Tests.Visual.Gameplay sendFrames(300); loadSpectatingScreen(); - waitForPlayer(); + waitForPlayerCurrent(); sendFrames(300); @@ -186,7 +186,7 @@ namespace osu.Game.Tests.Visual.Gameplay start(); sendFrames(); - waitForPlayer(); + waitForPlayerCurrent(); Player lastPlayer = null; AddStep("store first player", () => lastPlayer = player); @@ -194,7 +194,7 @@ namespace osu.Game.Tests.Visual.Gameplay start(); sendFrames(); - waitForPlayer(); + waitForPlayerCurrent(); AddAssert("player is different", () => lastPlayer != player); } @@ -205,7 +205,7 @@ namespace osu.Game.Tests.Visual.Gameplay start(); - waitForPlayer(); + waitForPlayerCurrent(); checkPaused(true); sendFrames(); @@ -223,7 +223,7 @@ namespace osu.Game.Tests.Visual.Gameplay start(); sendFrames(); - waitForPlayer(); + waitForPlayerCurrent(); AddStep("stop spectating", () => (Stack.CurrentScreen as Player)?.Exit()); AddUntilStep("spectating stopped", () => spectatorScreen.GetChildScreen() == null); @@ -236,14 +236,14 @@ namespace osu.Game.Tests.Visual.Gameplay start(); sendFrames(); - waitForPlayer(); + waitForPlayerCurrent(); AddStep("stop spectating", () => (Stack.CurrentScreen as Player)?.Exit()); AddUntilStep("spectating stopped", () => spectatorScreen.GetChildScreen() == null); // host starts playing a new session start(); - waitForPlayer(); + waitForPlayerCurrent(); } [Test] @@ -298,7 +298,7 @@ namespace osu.Game.Tests.Visual.Gameplay start(); sendFrames(); - waitForPlayer(); + waitForPlayerCurrent(); AddUntilStep("state is playing", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Playing); } @@ -309,14 +309,14 @@ namespace osu.Game.Tests.Visual.Gameplay start(); sendFrames(); - waitForPlayer(); + waitForPlayerCurrent(); AddStep("send passed", () => spectatorClient.SendEndPlay(streamingUser.Id, SpectatedUserState.Passed)); AddUntilStep("state is passed", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Passed); start(); sendFrames(); - waitForPlayer(); + waitForPlayerCurrent(); AddUntilStep("state is playing", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Playing); } @@ -327,7 +327,7 @@ namespace osu.Game.Tests.Visual.Gameplay start(); sendFrames(); - waitForPlayer(); + waitForPlayerCurrent(); AddStep("send quit", () => spectatorClient.SendEndPlay(streamingUser.Id)); AddUntilStep("state is quit", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Quit); @@ -336,18 +336,19 @@ namespace osu.Game.Tests.Visual.Gameplay start(); sendFrames(); - waitForPlayer(); + waitForPlayerCurrent(); AddUntilStep("state is playing", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Playing); } [Test] - public void TestFailedState() + public void TestFailedStateDuringPlay() { loadSpectatingScreen(); start(); sendFrames(); - waitForPlayer(); + + waitForPlayerCurrent(); AddStep("send failed", () => spectatorClient.SendEndPlay(streamingUser.Id, SpectatedUserState.Failed)); AddUntilStep("state is failed", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Failed); @@ -356,7 +357,28 @@ namespace osu.Game.Tests.Visual.Gameplay start(); sendFrames(); - waitForPlayer(); + waitForPlayerCurrent(); + AddUntilStep("state is playing", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Playing); + } + + [Test] + public void TestFailedStateDuringLoading() + { + loadSpectatingScreen(); + + start(); + sendFrames(); + + waitForPlayerLoader(); + + AddStep("send failed", () => spectatorClient.SendEndPlay(streamingUser.Id, SpectatedUserState.Failed)); + AddUntilStep("state is failed", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Failed); + + AddAssert("wait for player exit", () => Stack.CurrentScreen is SoloSpectatorScreen); + + start(); + sendFrames(); + waitForPlayerCurrent(); AddUntilStep("state is playing", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Playing); } @@ -368,7 +390,9 @@ namespace osu.Game.Tests.Visual.Gameplay private double currentFrameStableTime => player.ChildrenOfType().First().CurrentTime; - private void waitForPlayer() => AddUntilStep("wait for player", () => this.ChildrenOfType().SingleOrDefault()?.IsLoaded == true); + private void waitForPlayerLoader() => AddUntilStep("wait for loading", () => this.ChildrenOfType().SingleOrDefault()?.IsLoaded == true); + + private void waitForPlayerCurrent() => AddUntilStep("wait for player current", () => this.ChildrenOfType().SingleOrDefault()?.IsCurrentScreen() == true); private void start(int? beatmapId = null) => AddStep("start play", () => spectatorClient.SendStartPlay(streamingUser.Id, beatmapId ?? importedBeatmapId)); diff --git a/osu.Game/Screens/Play/SoloSpectatorScreen.cs b/osu.Game/Screens/Play/SoloSpectatorScreen.cs index d4a9fc4b30..2db751402c 100644 --- a/osu.Game/Screens/Play/SoloSpectatorScreen.cs +++ b/osu.Game/Screens/Play/SoloSpectatorScreen.cs @@ -180,31 +180,32 @@ namespace osu.Game.Screens.Play protected override void FailGameplay(int userId) { - if (this.GetChildScreen() is SpectatorPlayer player) - player.AllowFail(); - - Schedule(() => + if (this.GetChildScreen() is SpectatorPlayerLoader loader) { - scheduledStart?.Cancel(); - immediateSpectatorGameplayState = null; - clearDisplay(); - }); + if (loader.GetChildScreen() is SpectatorPlayer player) + { + player.AllowFail(); + resetStartState(); + } + else + QuitGameplay(userId); + } } protected override void QuitGameplay(int userId) { // Importantly, don't schedule this call, as a child screen may be present (and will cause the schedule to not be run as expected). this.MakeCurrent(); - - Schedule(() => - { - scheduledStart?.Cancel(); - immediateSpectatorGameplayState = null; - - clearDisplay(); - }); + resetStartState(); } + private void resetStartState() => Schedule(() => + { + scheduledStart?.Cancel(); + immediateSpectatorGameplayState = null; + clearDisplay(); + }); + private void clearDisplay() { watchButton.Enabled.Value = false; From 289cda71b236d98f622f5c8920ba91dd14bb40b8 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 24 Nov 2023 15:06:51 +0900 Subject: [PATCH 1703/4092] Fix inspections --- osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs | 8 ++++---- osu.Game/Screens/Menu/ButtonSystem.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs index 4bcd6b100a..459a8b0df5 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs @@ -97,7 +97,7 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestFromSongSelectWithFilter([Values] ScorePresentType type) { - AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo.Invoke()); + AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo?.Invoke()); AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded); AddStep("filter to nothing", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).FilterControl.CurrentTextSearch.Value = "fdsajkl;fgewq"); @@ -110,7 +110,7 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestFromSongSelectWithConvertRulesetChange([Values] ScorePresentType type) { - AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo.Invoke()); + AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo?.Invoke()); AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded); AddStep("set convert to false", () => Game.LocalConfig.SetValue(OsuSetting.ShowConvertedBeatmaps, false)); @@ -122,7 +122,7 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestFromSongSelect([Values] ScorePresentType type) { - AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo.Invoke()); + AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo?.Invoke()); AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded); var firstImport = importScore(1); @@ -135,7 +135,7 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestFromSongSelectDifferentRuleset([Values] ScorePresentType type) { - AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo.Invoke()); + AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo?.Invoke()); AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded); var firstImport = importScore(1); diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 8173375c29..78eb410a48 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -56,7 +56,7 @@ namespace osu.Game.Screens.Menu /// Assign the that this ButtonSystem should manage the position of. /// /// The instance of the logo to be assigned. If null, we are suspending from the screen that uses this ButtonSystem. - public void SetOsuLogo(OsuLogo logo) + public void SetOsuLogo(OsuLogo? logo) { this.logo = logo; From c126c46e2d25bcf2b1853ef80b869a230a9bb8ff Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 24 Nov 2023 15:43:57 +0900 Subject: [PATCH 1704/4092] Remove legacy implementations (moved to osu-tools) --- .../Scoring/LegacyCatchHealthProcessor.cs | 237 ------------------ .../Scoring/LegacyOsuHealthProcessor.cs | 215 ---------------- 2 files changed, 452 deletions(-) delete mode 100644 osu.Game.Rulesets.Catch/Scoring/LegacyCatchHealthProcessor.cs delete mode 100644 osu.Game.Rulesets.Osu/Scoring/LegacyOsuHealthProcessor.cs diff --git a/osu.Game.Rulesets.Catch/Scoring/LegacyCatchHealthProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/LegacyCatchHealthProcessor.cs deleted file mode 100644 index ef13ba222c..0000000000 --- a/osu.Game.Rulesets.Catch/Scoring/LegacyCatchHealthProcessor.cs +++ /dev/null @@ -1,237 +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 System; -using System.Collections.Generic; -using System.Linq; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Timing; -using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Catch.Scoring -{ - /// - /// Reference implementation for osu!stable's HP drain. - /// Cannot be used for gameplay. - /// - public partial class LegacyCatchHealthProcessor : DrainingHealthProcessor - { - private const double hp_bar_maximum = 200; - private const double hp_combo_geki = 14; - private const double hp_hit_300 = 6; - private const double hp_slider_tick = 3; - - public Action? OnIterationFail; - public Action? OnIterationSuccess; - public bool ApplyComboEndBonus { get; set; } = true; - - private double lowestHpEver; - private double lowestHpEnd; - private double lowestHpComboEnd; - private double hpRecoveryAvailable; - private double hpMultiplierNormal; - private double hpMultiplierComboEnd; - - public LegacyCatchHealthProcessor(double drainStartTime) - : base(drainStartTime) - { - } - - public override void ApplyBeatmap(IBeatmap beatmap) - { - lowestHpEver = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 195, 160, 60); - lowestHpComboEnd = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 198, 170, 80); - lowestHpEnd = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 198, 180, 80); - hpRecoveryAvailable = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 8, 4, 0); - - base.ApplyBeatmap(beatmap); - } - - protected override void ApplyResultInternal(JudgementResult result) - { - if (!IsSimulating) - throw new NotSupportedException("The legacy catch health processor is not supported for gameplay."); - } - - protected override void RevertResultInternal(JudgementResult result) - { - if (!IsSimulating) - throw new NotSupportedException("The legacy catch health processor is not supported for gameplay."); - } - - protected override void Reset(bool storeResults) - { - hpMultiplierNormal = 1; - hpMultiplierComboEnd = 1; - - base.Reset(storeResults); - } - - protected override double ComputeDrainRate() - { - double testDrop = 0.05; - double currentHp; - double currentHpUncapped; - - List<(HitObject hitObject, bool newCombo)> allObjects = enumerateHitObjects(Beatmap).Where(h => h.hitObject is Fruit || h.hitObject is Droplet || h.hitObject is Banana).ToList(); - - do - { - currentHp = hp_bar_maximum; - currentHpUncapped = hp_bar_maximum; - - double lowestHp = currentHp; - double lastTime = DrainStartTime; - int currentBreak = 0; - bool fail = false; - int comboTooLowCount = 0; - string failReason = string.Empty; - - for (int i = 0; i < allObjects.Count; i++) - { - HitObject h = allObjects[i].hitObject; - - // Find active break (between current and lastTime) - double localLastTime = lastTime; - double breakTime = 0; - - // Subtract any break time from the duration since the last object - if (Beatmap.Breaks.Count > 0 && currentBreak < Beatmap.Breaks.Count) - { - BreakPeriod e = Beatmap.Breaks[currentBreak]; - - if (e.StartTime >= localLastTime && e.EndTime <= h.StartTime) - { - // consider break start equal to object end time for version 8+ since drain stops during this time - breakTime = (Beatmap.BeatmapInfo.BeatmapVersion < 8) ? (e.EndTime - e.StartTime) : e.EndTime - localLastTime; - currentBreak++; - } - } - - reduceHp(testDrop * (h.StartTime - lastTime - breakTime)); - - lastTime = h.GetEndTime(); - - if (currentHp < lowestHp) - lowestHp = currentHp; - - if (currentHp <= lowestHpEver) - { - fail = true; - testDrop *= 0.96; - failReason = $"hp too low ({currentHp / hp_bar_maximum} < {lowestHpEver / hp_bar_maximum})"; - break; - } - - switch (h) - { - case Fruit: - if (ApplyComboEndBonus && (i == allObjects.Count - 1 || allObjects[i + 1].newCombo)) - { - increaseHp(hpMultiplierComboEnd * hp_combo_geki + hpMultiplierNormal * hp_hit_300); - - if (currentHp < lowestHpComboEnd) - { - if (++comboTooLowCount > 2) - { - hpMultiplierComboEnd *= 1.07; - hpMultiplierNormal *= 1.03; - fail = true; - failReason = $"combo end hp too low ({currentHp / hp_bar_maximum} < {lowestHpComboEnd / hp_bar_maximum})"; - } - } - } - else - increaseHp(hpMultiplierNormal * hp_hit_300); - - break; - - case Banana: - increaseHp(hpMultiplierNormal / 2); - break; - - case TinyDroplet: - increaseHp(hpMultiplierNormal * hp_slider_tick * 0.1); - break; - - case Droplet: - increaseHp(hpMultiplierNormal * hp_slider_tick); - break; - } - - if (fail) - break; - } - - if (!fail && currentHp < lowestHpEnd) - { - fail = true; - testDrop *= 0.94; - hpMultiplierComboEnd *= 1.01; - hpMultiplierNormal *= 1.01; - failReason = $"end hp too low ({currentHp / hp_bar_maximum} < {lowestHpEnd / hp_bar_maximum})"; - } - - double recovery = (currentHpUncapped - hp_bar_maximum) / allObjects.Count; - - if (!fail && recovery < hpRecoveryAvailable) - { - fail = true; - testDrop *= 0.96; - hpMultiplierComboEnd *= 1.02; - hpMultiplierNormal *= 1.01; - failReason = $"recovery too low ({recovery / hp_bar_maximum} < {hpRecoveryAvailable / hp_bar_maximum})"; - } - - if (fail) - { - OnIterationFail?.Invoke($"FAILED drop {testDrop / hp_bar_maximum}: {failReason}"); - continue; - } - - OnIterationSuccess?.Invoke($"PASSED drop {testDrop / hp_bar_maximum}"); - return testDrop / hp_bar_maximum; - } while (true); - - void reduceHp(double amount) - { - currentHpUncapped = Math.Max(0, currentHpUncapped - amount); - currentHp = Math.Max(0, currentHp - amount); - } - - void increaseHp(double amount) - { - currentHpUncapped += amount; - currentHp = Math.Max(0, Math.Min(hp_bar_maximum, currentHp + amount)); - } - } - - private IEnumerable<(HitObject hitObject, bool newCombo)> enumerateHitObjects(IBeatmap beatmap) - { - return enumerateRecursively(beatmap.HitObjects); - - static IEnumerable<(HitObject hitObject, bool newCombo)> enumerateRecursively(IEnumerable hitObjects) - { - foreach (var hitObject in hitObjects) - { - // The combo end will either be attached to the hitobject itself if it has no children, or the very first child if it has children. - bool newCombo = (hitObject as IHasComboInformation)?.NewCombo ?? false; - - foreach ((HitObject nested, bool _) in enumerateRecursively(hitObject.NestedHitObjects)) - { - yield return (nested, newCombo); - - // Since the combo was attached to the first child, don't attach it to any other child or the parenting hitobject itself. - newCombo = false; - } - - yield return (hitObject, newCombo); - } - } - } - } -} diff --git a/osu.Game.Rulesets.Osu/Scoring/LegacyOsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/LegacyOsuHealthProcessor.cs deleted file mode 100644 index e92c3c9b97..0000000000 --- a/osu.Game.Rulesets.Osu/Scoring/LegacyOsuHealthProcessor.cs +++ /dev/null @@ -1,215 +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 System; -using System.Linq; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Timing; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Osu.Scoring -{ - /// - /// Reference implementation for osu!stable's HP drain. - /// Cannot be used for gameplay. - /// - public partial class LegacyOsuHealthProcessor : DrainingHealthProcessor - { - private const double hp_bar_maximum = 200; - private const double hp_combo_geki = 14; - private const double hp_hit_300 = 6; - private const double hp_slider_repeat = 4; - private const double hp_slider_tick = 3; - - public Action? OnIterationFail; - public Action? OnIterationSuccess; - public bool ApplyComboEndBonus { get; set; } = true; - - private double lowestHpEver; - private double lowestHpEnd; - private double lowestHpComboEnd; - private double hpRecoveryAvailable; - private double hpMultiplierNormal; - private double hpMultiplierComboEnd; - - public LegacyOsuHealthProcessor(double drainStartTime) - : base(drainStartTime) - { - } - - public override void ApplyBeatmap(IBeatmap beatmap) - { - lowestHpEver = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 195, 160, 60); - lowestHpComboEnd = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 198, 170, 80); - lowestHpEnd = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 198, 180, 80); - hpRecoveryAvailable = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 8, 4, 0); - - base.ApplyBeatmap(beatmap); - } - - protected override void ApplyResultInternal(JudgementResult result) - { - if (!IsSimulating) - throw new NotSupportedException("The legacy osu! health processor is not supported for gameplay."); - } - - protected override void RevertResultInternal(JudgementResult result) - { - if (!IsSimulating) - throw new NotSupportedException("The legacy osu! health processor is not supported for gameplay."); - } - - protected override void Reset(bool storeResults) - { - hpMultiplierNormal = 1; - hpMultiplierComboEnd = 1; - - base.Reset(storeResults); - } - - protected override double ComputeDrainRate() - { - double testDrop = 0.05; - double currentHp; - double currentHpUncapped; - - do - { - currentHp = hp_bar_maximum; - currentHpUncapped = hp_bar_maximum; - - double lowestHp = currentHp; - double lastTime = DrainStartTime; - int currentBreak = 0; - bool fail = false; - int comboTooLowCount = 0; - string failReason = string.Empty; - - for (int i = 0; i < Beatmap.HitObjects.Count; i++) - { - HitObject h = Beatmap.HitObjects[i]; - - // Find active break (between current and lastTime) - double localLastTime = lastTime; - double breakTime = 0; - - // Subtract any break time from the duration since the last object - if (Beatmap.Breaks.Count > 0 && currentBreak < Beatmap.Breaks.Count) - { - BreakPeriod e = Beatmap.Breaks[currentBreak]; - - if (e.StartTime >= localLastTime && e.EndTime <= h.StartTime) - { - // consider break start equal to object end time for version 8+ since drain stops during this time - breakTime = (Beatmap.BeatmapInfo.BeatmapVersion < 8) ? (e.EndTime - e.StartTime) : e.EndTime - localLastTime; - currentBreak++; - } - } - - reduceHp(testDrop * (h.StartTime - lastTime - breakTime)); - - lastTime = h.GetEndTime(); - - if (currentHp < lowestHp) - lowestHp = currentHp; - - if (currentHp <= lowestHpEver) - { - fail = true; - testDrop *= 0.96; - failReason = $"hp too low ({currentHp / hp_bar_maximum} < {lowestHpEver / hp_bar_maximum})"; - break; - } - - double hpReduction = testDrop * (h.GetEndTime() - h.StartTime); - double hpOverkill = Math.Max(0, hpReduction - currentHp); - reduceHp(hpReduction); - - if (h is Slider slider) - { - for (int j = 0; j < slider.RepeatCount + 2; j++) - increaseHp(hpMultiplierNormal * hp_slider_repeat); - foreach (var _ in slider.NestedHitObjects.OfType()) - increaseHp(hpMultiplierNormal * hp_slider_tick); - } - else if (h is Spinner spinner) - { - foreach (var _ in spinner.NestedHitObjects.Where(t => t is not SpinnerBonusTick)) - increaseHp(hpMultiplierNormal * 1.7); - } - - if (hpOverkill > 0 && currentHp - hpOverkill <= lowestHpEver) - { - fail = true; - testDrop *= 0.96; - failReason = $"overkill ({currentHp / hp_bar_maximum} - {hpOverkill / hp_bar_maximum} <= {lowestHpEver / hp_bar_maximum})"; - break; - } - - if (ApplyComboEndBonus && (i == Beatmap.HitObjects.Count - 1 || ((OsuHitObject)Beatmap.HitObjects[i + 1]).NewCombo)) - { - increaseHp(hpMultiplierComboEnd * hp_combo_geki + hpMultiplierNormal * hp_hit_300); - - if (currentHp < lowestHpComboEnd) - { - if (++comboTooLowCount > 2) - { - hpMultiplierComboEnd *= 1.07; - hpMultiplierNormal *= 1.03; - fail = true; - failReason = $"combo end hp too low ({currentHp / hp_bar_maximum} < {lowestHpComboEnd / hp_bar_maximum})"; - break; - } - } - } - else - increaseHp(hpMultiplierNormal * hp_hit_300); - } - - if (!fail && currentHp < lowestHpEnd) - { - fail = true; - testDrop *= 0.94; - hpMultiplierComboEnd *= 1.01; - hpMultiplierNormal *= 1.01; - failReason = $"end hp too low ({currentHp / hp_bar_maximum} < {lowestHpEnd / hp_bar_maximum})"; - } - - double recovery = (currentHpUncapped - hp_bar_maximum) / Beatmap.HitObjects.Count; - - if (!fail && recovery < hpRecoveryAvailable) - { - fail = true; - testDrop *= 0.96; - hpMultiplierComboEnd *= 1.02; - hpMultiplierNormal *= 1.01; - failReason = $"recovery too low ({recovery / hp_bar_maximum} < {hpRecoveryAvailable / hp_bar_maximum})"; - } - - if (fail) - { - OnIterationFail?.Invoke($"FAILED drop {testDrop / hp_bar_maximum}: {failReason}"); - continue; - } - - OnIterationSuccess?.Invoke($"PASSED drop {testDrop / hp_bar_maximum}"); - return testDrop / hp_bar_maximum; - } while (true); - - void reduceHp(double amount) - { - currentHpUncapped = Math.Max(0, currentHpUncapped - amount); - currentHp = Math.Max(0, currentHp - amount); - } - - void increaseHp(double amount) - { - currentHpUncapped += amount; - currentHp = Math.Max(0, Math.Min(hp_bar_maximum, currentHp + amount)); - } - } - } -} From 36b45d34f7415327f80ed1a243a30af73390805f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Nov 2023 16:39:29 +0900 Subject: [PATCH 1705/4092] Check drag location on mouse down instead of drag start to avoid lenience issues --- osu.Game/Overlays/ChatOverlay.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 724f77ad71..54d5952bc3 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -251,10 +251,14 @@ namespace osu.Game.Overlays { } + protected override bool OnMouseDown(MouseDownEvent e) + { + isDraggingTopBar = topBar.DragBar.IsHovered; + return base.OnMouseDown(e); + } + protected override bool OnDragStart(DragStartEvent e) { - isDraggingTopBar = topBar.IsHovered; - if (!isDraggingTopBar) return base.OnDragStart(e); From 7600595e5dba09c0cd6b3a034be86b427dfc6a03 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Nov 2023 16:39:54 +0900 Subject: [PATCH 1706/4092] Add drag bar on chat overlay to better signal resizability --- osu.Game/Overlays/Chat/ChatOverlayTopBar.cs | 80 +++++++++++++++++++-- 1 file changed, 76 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs b/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs index 0410174dc1..44cb07ca91 100644 --- a/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs +++ b/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs @@ -2,7 +2,6 @@ // 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; @@ -23,6 +22,8 @@ namespace osu.Game.Overlays.Chat private Color4 backgroundColour; + public Drawable DragBar = null!; + [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider, TextureStore textures) { @@ -50,7 +51,7 @@ namespace osu.Game.Overlays.Chat Anchor = Anchor.Centre, Origin = Anchor.Centre, Texture = textures.Get("Icons/Hexacons/messaging"), - Size = new Vector2(18), + Size = new Vector2(24), }, // Placeholder text new OsuSpriteText @@ -64,19 +65,90 @@ namespace osu.Game.Overlays.Chat }, }, }, + DragBar = new DragArea + { + Alpha = 0, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = colourProvider.Background4, + } }; } protected override bool OnHover(HoverEvent e) { - background.FadeColour(backgroundColour.Lighten(0.1f), 300, Easing.OutQuint); + DragBar.FadeIn(100); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - background.FadeColour(backgroundColour, 300, Easing.OutQuint); + DragBar.FadeOut(100); base.OnHoverLost(e); } + + private partial class DragArea : CompositeDrawable + { + private readonly Circle circle; + + public DragArea() + { + AutoSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + circle = new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(150, 7), + Margin = new MarginPadding(12), + } + }; + } + + protected override bool OnHover(HoverEvent e) + { + updateScale(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateScale(); + base.OnHoverLost(e); + } + + private bool dragging; + + protected override bool OnMouseDown(MouseDownEvent e) + { + dragging = true; + updateScale(); + return base.OnMouseDown(e); + } + + protected override void OnMouseUp(MouseUpEvent e) + { + dragging = false; + updateScale(); + base.OnMouseUp(e); + } + + private void updateScale() + { + if (dragging || IsHovered) + circle.FadeIn(100); + else + circle.FadeTo(0.6f, 100); + + if (dragging) + circle.ScaleTo(1f, 400, Easing.OutQuint); + else if (IsHovered) + circle.ScaleTo(1.05f, 400, Easing.OutElasticHalf); + else + circle.ScaleTo(1f, 500, Easing.OutQuint); + } + } } } From 59800821da1afe62e520af591b172eb34c80bee2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Nov 2023 16:44:18 +0900 Subject: [PATCH 1707/4092] 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 ea08992710..ddaa371014 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 53d5d6b010..c11dfd06f3 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From 3b41480beffb8eda3ea8428f1d1a02dc81cfd4c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Nov 2023 17:46:02 +0900 Subject: [PATCH 1708/4092] Always show drag bar on mobile --- osu.Game/Overlays/Chat/ChatOverlayTopBar.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs b/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs index 44cb07ca91..807bb26502 100644 --- a/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs +++ b/osu.Game/Overlays/Chat/ChatOverlayTopBar.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.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -67,7 +68,7 @@ namespace osu.Game.Overlays.Chat }, DragBar = new DragArea { - Alpha = 0, + Alpha = RuntimeInfo.IsMobile ? 1 : 0, Anchor = Anchor.Centre, Origin = Anchor.Centre, Colour = colourProvider.Background4, @@ -77,13 +78,15 @@ namespace osu.Game.Overlays.Chat protected override bool OnHover(HoverEvent e) { - DragBar.FadeIn(100); + if (!RuntimeInfo.IsMobile) + DragBar.FadeIn(100); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - DragBar.FadeOut(100); + if (!RuntimeInfo.IsMobile) + DragBar.FadeOut(100); base.OnHoverLost(e); } From 290c3d63492315e700ff57d468585c0a8d3bc52a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Nov 2023 17:46:23 +0900 Subject: [PATCH 1709/4092] Clean up left-overs --- osu.Game/Overlays/Chat/ChatOverlayTopBar.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs b/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs index 807bb26502..330c991ce8 100644 --- a/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs +++ b/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs @@ -13,27 +13,22 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Resources.Localisation.Web; using osuTK; -using osuTK.Graphics; namespace osu.Game.Overlays.Chat { public partial class ChatOverlayTopBar : Container { - private Box background = null!; - - private Color4 backgroundColour; - public Drawable DragBar = null!; [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider, TextureStore textures) { - Children = new Drawable[] + Children = new[] { - background = new Box + new Box { RelativeSizeAxes = Axes.Both, - Colour = backgroundColour = colourProvider.Background3, + Colour = colourProvider.Background3, }, new GridContainer { From 95229cb33607ba8ddc43af8da387830f36f63d48 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Nov 2023 17:19:03 +0900 Subject: [PATCH 1710/4092] Show gameplay when loading the skin editor from the main menu --- .../Overlays/SkinEditor/SkinEditorOverlay.cs | 65 +++++++++++++++++++ .../SkinEditor/SkinEditorSceneLibrary.cs | 32 +-------- 2 files changed, 68 insertions(+), 29 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs index d1e7b97efc..16fc6b6ec6 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs @@ -1,7 +1,10 @@ // 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.Collections.Generic; using System.Diagnostics; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -9,12 +12,21 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Screens; +using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Scoring; using osu.Game.Screens; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components; +using osu.Game.Screens.Menu; +using osu.Game.Screens.Play; +using osu.Game.Screens.Select; +using osu.Game.Utils; using osuTK; namespace osu.Game.Overlays.SkinEditor @@ -31,12 +43,21 @@ namespace osu.Game.Overlays.SkinEditor private SkinEditor? skinEditor; + [Resolved] + private IPerformFromScreenRunner? performer { get; set; } + [Cached] public readonly EditorClipboard Clipboard = new EditorClipboard(); [Resolved] private OsuGame game { get; set; } = null!; + [Resolved] + private IBindable ruleset { get; set; } = null!; + + [Resolved] + private Bindable> mods { get; set; } = null!; + private OsuScreen? lastTargetScreen; private Vector2 lastDrawSize; @@ -72,6 +93,9 @@ namespace osu.Game.Overlays.SkinEditor { globallyDisableBeatmapSkinSetting(); + if (lastTargetScreen is MainMenu) + PresentGameplay(); + if (skinEditor != null) { skinEditor.Show(); @@ -105,6 +129,28 @@ namespace osu.Game.Overlays.SkinEditor globallyReenableBeatmapSkinSetting(); } + public void PresentGameplay() + { + performer?.PerformFromScreen(screen => + { + if (screen is Player) + return; + + var replayGeneratingMod = ruleset.Value.CreateInstance().GetAutoplayMod(); + + IReadOnlyList usableMods = mods.Value; + + if (replayGeneratingMod != null) + usableMods = usableMods.Append(replayGeneratingMod).ToArray(); + + if (!ModUtils.CheckCompatibleSet(usableMods, out var invalid)) + mods.Value = mods.Value.Except(invalid).ToArray(); + + if (replayGeneratingMod != null) + screen.Push(new EndlessPlayer((beatmap, mods) => replayGeneratingMod.CreateScoreFromReplayData(beatmap, mods))); + }, new[] { typeof(Player), typeof(PlaySongSelect) }); + } + protected override void Update() { base.Update(); @@ -222,5 +268,24 @@ namespace osu.Game.Overlays.SkinEditor leasedBeatmapSkins?.Return(); leasedBeatmapSkins = null; } + + private partial class EndlessPlayer : ReplayPlayer + { + public EndlessPlayer(Func, Score> createScore) + : base(createScore, new PlayerConfiguration + { + ShowResults = false, + }) + { + } + + protected override void Update() + { + base.Update(); + + if (GameplayState.HasPassed) + GameplayClockContainer.Seek(0); + } + } } } diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorSceneLibrary.cs b/osu.Game/Overlays/SkinEditor/SkinEditorSceneLibrary.cs index 9b021632cf..7fa33ddcf5 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorSceneLibrary.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorSceneLibrary.cs @@ -1,10 +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 System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -14,12 +11,8 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; using osu.Game.Screens; -using osu.Game.Screens.Play; using osu.Game.Screens.Select; -using osu.Game.Utils; using osuTK; namespace osu.Game.Overlays.SkinEditor @@ -36,10 +29,7 @@ namespace osu.Game.Overlays.SkinEditor private IPerformFromScreenRunner? performer { get; set; } [Resolved] - private IBindable ruleset { get; set; } = null!; - - [Resolved] - private Bindable> mods { get; set; } = null!; + private SkinEditorOverlay skinEditorOverlay { get; set; } = null!; public SkinEditorSceneLibrary() { @@ -96,24 +86,7 @@ namespace osu.Game.Overlays.SkinEditor Text = SkinEditorStrings.Gameplay, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Action = () => performer?.PerformFromScreen(screen => - { - if (screen is Player) - return; - - var replayGeneratingMod = ruleset.Value.CreateInstance().GetAutoplayMod(); - - IReadOnlyList usableMods = mods.Value; - - if (replayGeneratingMod != null) - usableMods = usableMods.Append(replayGeneratingMod).ToArray(); - - if (!ModUtils.CheckCompatibleSet(usableMods, out var invalid)) - mods.Value = mods.Value.Except(invalid).ToArray(); - - if (replayGeneratingMod != null) - screen.Push(new PlayerLoader(() => new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateScoreFromReplayData(beatmap, mods)))); - }, new[] { typeof(Player), typeof(PlaySongSelect) }) + Action = () => skinEditorOverlay.PresentGameplay(), }, } }, @@ -137,5 +110,6 @@ namespace osu.Game.Overlays.SkinEditor Content.CornerRadius = 5; } } + } } From 7153c823e8405713579f1a9d6c72f5ac5c2572b2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Nov 2023 17:34:08 +0900 Subject: [PATCH 1711/4092] Choose a better beatmap if the intro is still playing Also skip intro time. --- osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs index 16fc6b6ec6..18d1c4c62b 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs @@ -52,12 +52,18 @@ namespace osu.Game.Overlays.SkinEditor [Resolved] private OsuGame game { get; set; } = null!; + [Resolved] + private MusicController music { get; set; } = null!; + [Resolved] private IBindable ruleset { get; set; } = null!; [Resolved] private Bindable> mods { get; set; } = null!; + [Resolved] + private IBindable beatmap { get; set; } = null!; + private OsuScreen? lastTargetScreen; private Vector2 lastDrawSize; @@ -133,6 +139,14 @@ namespace osu.Game.Overlays.SkinEditor { performer?.PerformFromScreen(screen => { + // If we're playing the intro, switch away to another beatmap. + if (beatmap.Value.BeatmapSetInfo.Protected) + { + music.NextTrack(); + Schedule(PresentGameplay); + return; + } + if (screen is Player) return; @@ -275,6 +289,7 @@ namespace osu.Game.Overlays.SkinEditor : base(createScore, new PlayerConfiguration { ShowResults = false, + AutomaticallySkipIntro = true, }) { } From 8314f656a3107b27fc82ddbad448de4c70bdf41d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 24 Nov 2023 17:32:18 +0900 Subject: [PATCH 1712/4092] Encapsulate common HP logic from osu and catch HP calculations --- .../Scoring/CatchHealthProcessor.cs | 121 +------------- .../Scoring/OsuHealthProcessor.cs | 155 ++--------------- .../Scoring/LegacyDrainingHealthProcessor.cs | 158 ++++++++++++++++++ 3 files changed, 179 insertions(+), 255 deletions(-) create mode 100644 osu.Game/Rulesets/Scoring/LegacyDrainingHealthProcessor.cs diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchHealthProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchHealthProcessor.cs index 6d831ad223..c3cc488941 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchHealthProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchHealthProcessor.cs @@ -1,138 +1,27 @@ // 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.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Catch.Scoring { - public partial class CatchHealthProcessor : DrainingHealthProcessor + public partial class CatchHealthProcessor : LegacyDrainingHealthProcessor { - public Action? OnIterationFail; - public Action? OnIterationSuccess; - - private double lowestHpEver; - private double lowestHpEnd; - private double hpRecoveryAvailable; - private double hpMultiplierNormal; - public CatchHealthProcessor(double drainStartTime) : base(drainStartTime) { } - public override void ApplyBeatmap(IBeatmap beatmap) - { - lowestHpEver = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 0.975, 0.8, 0.3); - lowestHpEnd = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 0.99, 0.9, 0.4); - hpRecoveryAvailable = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 0.04, 0.02, 0); + protected override IEnumerable EnumerateTopLevelHitObjects() => EnumerateHitObjects(Beatmap).Where(h => h is Fruit || h is Droplet || h is Banana); - base.ApplyBeatmap(beatmap); - } + protected override IEnumerable EnumerateNestedHitObjects(HitObject hitObject) => Enumerable.Empty(); - protected override void Reset(bool storeResults) - { - hpMultiplierNormal = 1; - base.Reset(storeResults); - } - - protected override double ComputeDrainRate() - { - double testDrop = 0.00025; - double currentHp; - double currentHpUncapped; - - while (true) - { - currentHp = 1; - currentHpUncapped = 1; - - double lowestHp = currentHp; - double lastTime = DrainStartTime; - int currentBreak = 0; - bool fail = false; - - List allObjects = EnumerateHitObjects(Beatmap).Where(h => h is Fruit || h is Droplet || h is Banana).ToList(); - - for (int i = 0; i < allObjects.Count; i++) - { - HitObject h = allObjects[i]; - - while (currentBreak < Beatmap.Breaks.Count && Beatmap.Breaks[currentBreak].EndTime <= h.StartTime) - { - // If two hitobjects are separated by a break period, there is no drain for the full duration between the hitobjects. - // This differs from legacy (version < 8) beatmaps which continue draining until the break section is entered, - // but this shouldn't have a noticeable impact in practice. - lastTime = h.StartTime; - currentBreak++; - } - - reduceHp(testDrop * (h.StartTime - lastTime)); - - lastTime = h.GetEndTime(); - - if (currentHp < lowestHp) - lowestHp = currentHp; - - if (currentHp <= lowestHpEver) - { - fail = true; - testDrop *= 0.96; - OnIterationFail?.Invoke($"FAILED drop {testDrop}: hp too low ({currentHp} < {lowestHpEver})"); - break; - } - - increaseHp(h); - } - - if (!fail && currentHp < lowestHpEnd) - { - fail = true; - testDrop *= 0.94; - hpMultiplierNormal *= 1.01; - OnIterationFail?.Invoke($"FAILED drop {testDrop}: end hp too low ({currentHp} < {lowestHpEnd})"); - } - - double recovery = (currentHpUncapped - 1) / allObjects.Count; - - if (!fail && recovery < hpRecoveryAvailable) - { - fail = true; - testDrop *= 0.96; - hpMultiplierNormal *= 1.01; - OnIterationFail?.Invoke($"FAILED drop {testDrop}: recovery too low ({recovery} < {hpRecoveryAvailable})"); - } - - if (!fail) - { - OnIterationSuccess?.Invoke($"PASSED drop {testDrop}"); - return testDrop; - } - } - - void reduceHp(double amount) - { - currentHpUncapped = Math.Max(0, currentHpUncapped - amount); - currentHp = Math.Max(0, currentHp - amount); - } - - void increaseHp(HitObject hitObject) - { - double amount = healthIncreaseFor(hitObject.CreateJudgement().MaxResult); - currentHpUncapped += amount; - currentHp = Math.Max(0, Math.Min(1, currentHp + amount)); - } - } - - protected override double GetHealthIncreaseFor(JudgementResult result) => healthIncreaseFor(result.Type); - - private double healthIncreaseFor(HitResult result) + protected override double GetHealthIncreaseFor(HitObject hitObject, HitResult result) { double increase = 0; @@ -162,7 +51,7 @@ namespace osu.Game.Rulesets.Catch.Scoring break; } - return hpMultiplierNormal * increase; + return HpMultiplierNormal * increase; } } } diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs index 3c124b3162..7025a7be65 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs @@ -1,166 +1,43 @@ // 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.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; -using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Scoring { - public partial class OsuHealthProcessor : DrainingHealthProcessor + public partial class OsuHealthProcessor : LegacyDrainingHealthProcessor { - public Action? OnIterationFail; - public Action? OnIterationSuccess; - - private double lowestHpEver; - private double lowestHpEnd; - private double hpRecoveryAvailable; - private double hpMultiplierNormal; - public OsuHealthProcessor(double drainStartTime) : base(drainStartTime) { } - public override void ApplyBeatmap(IBeatmap beatmap) + protected override IEnumerable EnumerateTopLevelHitObjects() => Beatmap.HitObjects; + + protected override IEnumerable EnumerateNestedHitObjects(HitObject hitObject) { - lowestHpEver = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 0.975, 0.8, 0.3); - lowestHpEnd = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 0.99, 0.9, 0.4); - hpRecoveryAvailable = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 0.04, 0.02, 0); - - base.ApplyBeatmap(beatmap); - } - - protected override void Reset(bool storeResults) - { - hpMultiplierNormal = 1; - base.Reset(storeResults); - } - - protected override double ComputeDrainRate() - { - double testDrop = 0.00025; - double currentHp; - double currentHpUncapped; - - while (true) + switch (hitObject) { - currentHp = 1; - currentHpUncapped = 1; + case Slider slider: + foreach (var nested in slider.NestedHitObjects) + yield return nested; - double lowestHp = currentHp; - double lastTime = DrainStartTime; - int currentBreak = 0; - bool fail = false; + break; - for (int i = 0; i < Beatmap.HitObjects.Count; i++) - { - HitObject h = Beatmap.HitObjects[i]; + case Spinner spinner: + foreach (var nested in spinner.NestedHitObjects.Where(t => t is not SpinnerBonusTick)) + yield return nested; - while (currentBreak < Beatmap.Breaks.Count && Beatmap.Breaks[currentBreak].EndTime <= h.StartTime) - { - // If two hitobjects are separated by a break period, there is no drain for the full duration between the hitobjects. - // This differs from legacy (version < 8) beatmaps which continue draining until the break section is entered, - // but this shouldn't have a noticeable impact in practice. - lastTime = h.StartTime; - currentBreak++; - } - - reduceHp(testDrop * (h.StartTime - lastTime)); - - lastTime = h.GetEndTime(); - - if (currentHp < lowestHp) - lowestHp = currentHp; - - if (currentHp <= lowestHpEver) - { - fail = true; - testDrop *= 0.96; - OnIterationFail?.Invoke($"FAILED drop {testDrop}: hp too low ({currentHp} < {lowestHpEver})"); - break; - } - - double hpReduction = testDrop * (h.GetEndTime() - h.StartTime); - double hpOverkill = Math.Max(0, hpReduction - currentHp); - reduceHp(hpReduction); - - switch (h) - { - case Slider slider: - { - foreach (var nested in slider.NestedHitObjects) - increaseHp(nested); - break; - } - - case Spinner spinner: - { - foreach (var nested in spinner.NestedHitObjects.Where(t => t is not SpinnerBonusTick)) - increaseHp(nested); - break; - } - } - - // Note: Because HP is capped during the above increases, long sliders (with many ticks) or spinners - // will appear to overkill at lower drain levels than they should. However, it is also not correct to simply use the uncapped version. - if (hpOverkill > 0 && currentHp - hpOverkill <= lowestHpEver) - { - fail = true; - testDrop *= 0.96; - OnIterationFail?.Invoke($"FAILED drop {testDrop}: overkill ({currentHp} - {hpOverkill} <= {lowestHpEver})"); - break; - } - - increaseHp(h); - } - - if (!fail && currentHp < lowestHpEnd) - { - fail = true; - testDrop *= 0.94; - hpMultiplierNormal *= 1.01; - OnIterationFail?.Invoke($"FAILED drop {testDrop}: end hp too low ({currentHp} < {lowestHpEnd})"); - } - - double recovery = (currentHpUncapped - 1) / Beatmap.HitObjects.Count; - - if (!fail && recovery < hpRecoveryAvailable) - { - fail = true; - testDrop *= 0.96; - hpMultiplierNormal *= 1.01; - OnIterationFail?.Invoke($"FAILED drop {testDrop}: recovery too low ({recovery} < {hpRecoveryAvailable})"); - } - - if (!fail) - { - OnIterationSuccess?.Invoke($"PASSED drop {testDrop}"); - return testDrop; - } - } - - void reduceHp(double amount) - { - currentHpUncapped = Math.Max(0, currentHpUncapped - amount); - currentHp = Math.Max(0, currentHp - amount); - } - - void increaseHp(HitObject hitObject) - { - double amount = healthIncreaseFor(hitObject, hitObject.CreateJudgement().MaxResult); - currentHpUncapped += amount; - currentHp = Math.Max(0, Math.Min(1, currentHp + amount)); + break; } } - protected override double GetHealthIncreaseFor(JudgementResult result) => healthIncreaseFor(result.HitObject, result.Type); - - private double healthIncreaseFor(HitObject hitObject, HitResult result) + protected override double GetHealthIncreaseFor(HitObject hitObject, HitResult result) { double increase = 0; @@ -206,7 +83,7 @@ namespace osu.Game.Rulesets.Osu.Scoring break; } - return hpMultiplierNormal * increase; + return HpMultiplierNormal * increase; } } } diff --git a/osu.Game/Rulesets/Scoring/LegacyDrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/LegacyDrainingHealthProcessor.cs new file mode 100644 index 0000000000..ce2f7d5624 --- /dev/null +++ b/osu.Game/Rulesets/Scoring/LegacyDrainingHealthProcessor.cs @@ -0,0 +1,158 @@ +// 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.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Scoring +{ + /// + /// A that matches legacy drain rate calculations as best as possible. + /// + public abstract partial class LegacyDrainingHealthProcessor : DrainingHealthProcessor + { + public Action? OnIterationFail; + public Action? OnIterationSuccess; + + protected double HpMultiplierNormal { get; private set; } + + private double lowestHpEver; + private double lowestHpEnd; + private double hpRecoveryAvailable; + + protected LegacyDrainingHealthProcessor(double drainStartTime) + : base(drainStartTime) + { + } + + public override void ApplyBeatmap(IBeatmap beatmap) + { + lowestHpEver = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 0.975, 0.8, 0.3); + lowestHpEnd = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 0.99, 0.9, 0.4); + hpRecoveryAvailable = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 0.04, 0.02, 0); + + base.ApplyBeatmap(beatmap); + } + + protected override void Reset(bool storeResults) + { + HpMultiplierNormal = 1; + base.Reset(storeResults); + } + + protected override double ComputeDrainRate() + { + double testDrop = 0.00025; + double currentHp; + double currentHpUncapped; + + while (true) + { + currentHp = 1; + currentHpUncapped = 1; + + double lowestHp = currentHp; + double lastTime = DrainStartTime; + int currentBreak = 0; + bool fail = false; + int topLevelObjectCount = 0; + + foreach (var h in EnumerateTopLevelHitObjects()) + { + topLevelObjectCount++; + + while (currentBreak < Beatmap.Breaks.Count && Beatmap.Breaks[currentBreak].EndTime <= h.StartTime) + { + // If two hitobjects are separated by a break period, there is no drain for the full duration between the hitobjects. + // This differs from legacy (version < 8) beatmaps which continue draining until the break section is entered, + // but this shouldn't have a noticeable impact in practice. + lastTime = h.StartTime; + currentBreak++; + } + + reduceHp(testDrop * (h.StartTime - lastTime)); + + lastTime = h.GetEndTime(); + + if (currentHp < lowestHp) + lowestHp = currentHp; + + if (currentHp <= lowestHpEver) + { + fail = true; + testDrop *= 0.96; + OnIterationFail?.Invoke($"FAILED drop {testDrop}: hp too low ({currentHp} < {lowestHpEver})"); + break; + } + + double hpReduction = testDrop * (h.GetEndTime() - h.StartTime); + double hpOverkill = Math.Max(0, hpReduction - currentHp); + reduceHp(hpReduction); + + foreach (var nested in EnumerateNestedHitObjects(h)) + increaseHp(nested); + + // Note: Because HP is capped during the above increases, long sliders (with many ticks) or spinners + // will appear to overkill at lower drain levels than they should. However, it is also not correct to simply use the uncapped version. + if (hpOverkill > 0 && currentHp - hpOverkill <= lowestHpEver) + { + fail = true; + testDrop *= 0.96; + OnIterationFail?.Invoke($"FAILED drop {testDrop}: overkill ({currentHp} - {hpOverkill} <= {lowestHpEver})"); + break; + } + + increaseHp(h); + } + + if (!fail && currentHp < lowestHpEnd) + { + fail = true; + testDrop *= 0.94; + HpMultiplierNormal *= 1.01; + OnIterationFail?.Invoke($"FAILED drop {testDrop}: end hp too low ({currentHp} < {lowestHpEnd})"); + } + + double recovery = (currentHpUncapped - 1) / Math.Max(1, topLevelObjectCount); + + if (!fail && recovery < hpRecoveryAvailable) + { + fail = true; + testDrop *= 0.96; + HpMultiplierNormal *= 1.01; + OnIterationFail?.Invoke($"FAILED drop {testDrop}: recovery too low ({recovery} < {hpRecoveryAvailable})"); + } + + if (!fail) + { + OnIterationSuccess?.Invoke($"PASSED drop {testDrop}"); + return testDrop; + } + } + + void reduceHp(double amount) + { + currentHpUncapped = Math.Max(0, currentHpUncapped - amount); + currentHp = Math.Max(0, currentHp - amount); + } + + void increaseHp(HitObject hitObject) + { + double amount = GetHealthIncreaseFor(hitObject, hitObject.CreateJudgement().MaxResult); + currentHpUncapped += amount; + currentHp = Math.Max(0, Math.Min(1, currentHp + amount)); + } + } + + protected sealed override double GetHealthIncreaseFor(JudgementResult result) => GetHealthIncreaseFor(result.HitObject, result.Type); + + protected abstract IEnumerable EnumerateTopLevelHitObjects(); + + protected abstract IEnumerable EnumerateNestedHitObjects(HitObject hitObject); + + protected abstract double GetHealthIncreaseFor(HitObject hitObject, HitResult result); + } +} From a44edfdeddadaac693c63312fb6d3cc14593ee0e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Nov 2023 19:37:57 +0900 Subject: [PATCH 1713/4092] Fix incorrect sample for top level edit button --- osu.Game/Screens/Menu/ButtonSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 78eb410a48..259efad8b3 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -138,7 +138,7 @@ namespace osu.Game.Screens.Menu buttonsEdit.ForEach(b => b.VisibleState = ButtonSystemState.Edit); buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Play, @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P)); - buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Edit, @"button-default-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => State = ButtonSystemState.Edit, 0, Key.E)); + buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Edit, @"button-play-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => State = ButtonSystemState.Edit, 0, Key.E)); buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Browse, @"button-default-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.B, Key.D)); if (host.CanExit) From 901561533600690974d0c89756cc850dca5ec7d3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Nov 2023 19:42:30 +0900 Subject: [PATCH 1714/4092] Update test to work with new drag bar location --- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 55e6b54af7..8a2a66f60f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -180,11 +180,8 @@ namespace osu.Game.Tests.Visual.Online }); AddStep("Show overlay", () => chatOverlay.Show()); AddAssert("Overlay uses config height", () => chatOverlay.Height == configChatHeight.Default); - AddStep("Click top bar", () => - { - InputManager.MoveMouseTo(chatOverlayTopBar); - InputManager.PressButton(MouseButton.Left); - }); + AddStep("Move mouse to drag bar", () => InputManager.MoveMouseTo(chatOverlayTopBar.DragBar)); + AddStep("Click drag bar", () => InputManager.PressButton(MouseButton.Left)); AddStep("Drag overlay to new height", () => InputManager.MoveMouseTo(chatOverlayTopBar, new Vector2(0, -300))); AddStep("Stop dragging", () => InputManager.ReleaseButton(MouseButton.Left)); AddStep("Store new height", () => newHeight = chatOverlay.Height); From a6cf1e5d2eabb3cd5f32da0b213e20c1ab601a91 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 25 Nov 2023 00:53:25 +0900 Subject: [PATCH 1715/4092] Make osu! logo do something when in edit submenu --- osu.Game/Screens/Menu/ButtonSystem.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 259efad8b3..ca5bef985e 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -311,6 +311,10 @@ namespace osu.Game.Screens.Menu case ButtonSystemState.Play: buttonsPlay.First().TriggerClick(); return false; + + case ButtonSystemState.Edit: + buttonsEdit.First().TriggerClick(); + return false; } } From 27f9dfccc46429f2956d6ce4c3759620178fabf1 Mon Sep 17 00:00:00 2001 From: Zyf Date: Fri, 24 Nov 2023 22:05:24 +0100 Subject: [PATCH 1716/4092] Fix scoring-conversion when miss-count is 0 --- osu.Game/Database/StandardisedScoreMigrationTools.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index 77421908de..457262a1de 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -321,7 +321,7 @@ namespace osu.Game.Database // This time, divide the remaining combo among remaining objects equally to achieve longest possible combo lengths. // There is no rigorous proof that doing this will yield a correct upper bound, but it seems to work out in practice. remainingComboPortionInScoreV1 = comboPortionInScoreV1 - comboPortionFromLongestComboInScoreV1; - double remainingCountOfObjectsGivingCombo = maximumLegacyCombo - score.MaxCombo - score.Statistics[HitResult.Miss]; + double remainingCountOfObjectsGivingCombo = maximumLegacyCombo - score.MaxCombo - score.Statistics.GetValueOrDefault(HitResult.Miss); // Because we assumed all combos were equal, `remainingComboPortionInScoreV1` // can be approximated by n * x^2, wherein n is the assumed number of equal combos, // and x is the assumed length of every one of those combos. From 71e5654b645c07da60b7acbfbe60fe71021f9c45 Mon Sep 17 00:00:00 2001 From: Zyf Date: Fri, 24 Nov 2023 23:07:27 +0100 Subject: [PATCH 1717/4092] Account for legacyAccScore in score conversion --- .../Difficulty/CatchLegacyScoreSimulator.cs | 1 + .../Difficulty/TaikoLegacyScoreSimulator.cs | 1 + .../Database/StandardisedScoreMigrationTools.cs | 14 +++++++------- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyScoreSimulator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyScoreSimulator.cs index 746f5713e4..ed27e11208 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyScoreSimulator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyScoreSimulator.cs @@ -74,6 +74,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty simulateHit(obj, ref attributes); attributes.BonusScoreRatio = legacyBonusScore == 0 ? 0 : (double)standardisedBonusScore / legacyBonusScore; + attributes.BonusScore = legacyBonusScore; return attributes; } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs index 6a3eb68a22..1db44592b8 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs @@ -75,6 +75,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty simulateHit(obj, ref attributes); attributes.BonusScoreRatio = legacyBonusScore == 0 ? 0 : (double)standardisedBonusScore / legacyBonusScore; + attributes.BonusScore = legacyBonusScore; return attributes; } diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index 457262a1de..6484500bcc 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -250,15 +250,14 @@ namespace osu.Game.Database long maximumLegacyComboScore = (long)Math.Round(attributes.ComboScore * legacyModMultiplier); double maximumLegacyBonusRatio = attributes.BonusScoreRatio; long maximumLegacyBonusScore = attributes.BonusScore; - int maximumLegacyCombo = attributes.MaxCombo; + double legacyAccScore = maximumLegacyAccuracyScore * score.Accuracy; + // We can not separate the ComboScore from the BonusScore, so we keep the bonus in the ratio. + double comboProportion = + ((double)score.LegacyTotalScore - legacyAccScore) / (maximumLegacyComboScore + maximumLegacyBonusScore); + + // We assume the bonus proportion only makes up the rest of the score that exceeds maximumLegacyBaseScore. long maximumLegacyBaseScore = maximumLegacyAccuracyScore + maximumLegacyComboScore; - long maximumLegacyTotalScore = maximumLegacyBaseScore + maximumLegacyBonusScore; - - // The combo proportion is calculated as a proportion of maximumLegacyTotalScore. - double comboProportion = (double)score.LegacyTotalScore / maximumLegacyTotalScore; - - // The bonus proportion makes up the rest of the score that exceeds maximumLegacyBaseScore. double bonusProportion = Math.Max(0, ((long)score.LegacyTotalScore - maximumLegacyBaseScore) * maximumLegacyBonusRatio); double modMultiplier = score.Mods.Select(m => m.ScoreMultiplier).Aggregate(1.0, (c, n) => c * n); @@ -288,6 +287,7 @@ namespace osu.Game.Database // this can be roughly represented by summing / integrating f(combo) = combo. // All mod- and beatmap-dependent multipliers and constants are not included here, // as we will only be using the magnitude of this to compute ratios. + int maximumLegacyCombo = attributes.MaxCombo; double maximumAchievableComboPortionInScoreV1 = Math.Pow(maximumLegacyCombo, 2); // Similarly, estimate the maximum magnitude of the combo portion in standardised score. // Roughly corresponds to integrating f(combo) = combo ^ COMBO_EXPONENT (omitting constants) From 68fca007577f29c9b57e7df2e33b6dd09ee6d7b2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 25 Nov 2023 02:40:30 +0300 Subject: [PATCH 1718/4092] Improve handling of beatmap collection changes in `CollectionDropdown` Co-authored-by: Dean Herbert --- osu.Game/Collections/CollectionDropdown.cs | 72 +++++++++++++--------- 1 file changed, 44 insertions(+), 28 deletions(-) diff --git a/osu.Game/Collections/CollectionDropdown.cs b/osu.Game/Collections/CollectionDropdown.cs index e435992381..299594b0a0 100644 --- a/osu.Game/Collections/CollectionDropdown.cs +++ b/osu.Game/Collections/CollectionDropdown.cs @@ -43,11 +43,13 @@ namespace osu.Game.Collections private IDisposable? realmSubscription; + private readonly CollectionFilterMenuItem allBeatmapsItem = new AllBeatmapsCollectionFilterMenuItem(); + public CollectionDropdown() { ItemSource = filters; - Current.Value = new AllBeatmapsCollectionFilterMenuItem(); + Current.Value = allBeatmapsItem; } protected override void LoadComplete() @@ -61,37 +63,51 @@ namespace osu.Game.Collections private void collectionsChanged(IRealmCollection collections, ChangeSet? changes) { - var selectedItem = SelectedItem?.Value?.Collection; - - var allBeatmaps = new AllBeatmapsCollectionFilterMenuItem(); - - filters.Clear(); - filters.Add(allBeatmaps); - filters.AddRange(collections.Select(c => new CollectionFilterMenuItem(c.ToLive(realm)))); - - if (ShowManageCollectionsItem) - filters.Add(new ManageCollectionsFilterMenuItem()); - - // This current update and schedule is required to work around dropdown headers not updating text even when the selected item - // changes. It's not great but honestly the whole dropdown menu structure isn't great. This needs to be fixed, but I'll issue - // a warning that it's going to be a frustrating journey. - Current.Value = allBeatmaps; - Schedule(() => + if (changes == null) { - // current may have changed before the scheduled call is run. - if (Current.Value != allBeatmaps) - return; - - Current.Value = filters.SingleOrDefault(f => f.Collection != null && f.Collection.ID == selectedItem?.ID) ?? filters[0]; - }); - - // Trigger a re-filter if the current item was in the change set. - if (selectedItem != null && changes != null) + filters.Add(allBeatmapsItem); + filters.AddRange(collections.Select(c => new CollectionFilterMenuItem(c.ToLive(realm)))); + if (ShowManageCollectionsItem) + filters.Add(new ManageCollectionsFilterMenuItem()); + } + else { - foreach (int index in changes.ModifiedIndices) + foreach (int i in changes.DeletedIndices) + filters.RemoveAt(i + 1); + + foreach (int i in changes.InsertedIndices) + filters.Insert(i + 1, new CollectionFilterMenuItem(collections[i].ToLive(realm))); + + var selectedItem = SelectedItem?.Value; + + foreach (int i in changes.NewModifiedIndices) { - if (collections[index].ID == selectedItem.ID) + var updatedItem = collections[i]; + + // This is responsible for updating the state of the +/- button and the collection's name. + // TODO: we can probably make the menu items update with changes to avoid this. + filters.RemoveAt(i + 1); + filters.Insert(i + 1, new CollectionFilterMenuItem(updatedItem.ToLive(realm))); + + if (updatedItem.ID == selectedItem?.Collection?.ID) + { + // This current update and schedule is required to work around dropdown headers not updating text even when the selected item + // changes. It's not great but honestly the whole dropdown menu structure isn't great. This needs to be fixed, but I'll issue + // a warning that it's going to be a frustrating journey. + Current.Value = allBeatmapsItem; + Schedule(() => + { + // current may have changed before the scheduled call is run. + if (Current.Value != allBeatmapsItem) + return; + + Current.Value = filters.SingleOrDefault(f => f.Collection?.ID == selectedItem.Collection?.ID) ?? filters[0]; + }); + + // Trigger an external re-filter if the current item was in the change set. RequestFilter?.Invoke(); + break; + } } } } From 6f66819e5152de82d807522089465b79a82b3850 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 27 Nov 2023 10:44:50 +0900 Subject: [PATCH 1719/4092] Privatise setter --- osu.Game/Overlays/Chat/ChatOverlayTopBar.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs b/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs index 1cea198300..84fd342493 100644 --- a/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs +++ b/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs @@ -18,7 +18,7 @@ namespace osu.Game.Overlays.Chat { public partial class ChatOverlayTopBar : Container { - public Drawable DragBar = null!; + public Drawable DragBar { get; private set; } = null!; [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider, TextureStore textures) From 7f788058cdadf4f8196b4095afdf2c265b81edab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 27 Nov 2023 11:35:10 +0900 Subject: [PATCH 1720/4092] Revert incorrect xmldoc change --- osu.Game/Online/Spectator/SpectatorClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index e7435adf29..47e2dd807a 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -46,7 +46,7 @@ namespace osu.Game.Online.Spectator public IBindableList PlayingUsers => playingUsers; /// - /// Whether the spectated user is playing. + /// Whether the local user is playing. /// private bool isPlaying { get; set; } From 3f48f4acdff18744317665661eb0119154eceff0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 27 Nov 2023 12:06:08 +0900 Subject: [PATCH 1721/4092] Remove blank line --- osu.Game/Overlays/SkinEditor/SkinEditorSceneLibrary.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorSceneLibrary.cs b/osu.Game/Overlays/SkinEditor/SkinEditorSceneLibrary.cs index 7fa33ddcf5..a682285549 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorSceneLibrary.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorSceneLibrary.cs @@ -110,6 +110,5 @@ namespace osu.Game.Overlays.SkinEditor Content.CornerRadius = 5; } } - } } From 7e3bb5f8dba1a3357a6a6729a557d1e0b13714c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 27 Nov 2023 12:09:13 +0900 Subject: [PATCH 1722/4092] Make skin editor overlay dependency nullable to fix tests --- osu.Game/Overlays/SkinEditor/SkinEditorSceneLibrary.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorSceneLibrary.cs b/osu.Game/Overlays/SkinEditor/SkinEditorSceneLibrary.cs index a682285549..5a283c0e8d 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorSceneLibrary.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorSceneLibrary.cs @@ -29,7 +29,7 @@ namespace osu.Game.Overlays.SkinEditor private IPerformFromScreenRunner? performer { get; set; } [Resolved] - private SkinEditorOverlay skinEditorOverlay { get; set; } = null!; + private SkinEditorOverlay? skinEditorOverlay { get; set; } public SkinEditorSceneLibrary() { @@ -86,7 +86,7 @@ namespace osu.Game.Overlays.SkinEditor Text = SkinEditorStrings.Gameplay, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Action = () => skinEditorOverlay.PresentGameplay(), + Action = () => skinEditorOverlay?.PresentGameplay(), }, } }, From a4be28a2aebf4bf14c72a1aee35d60fad654c88e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Nov 2023 17:53:06 +0900 Subject: [PATCH 1723/4092] Don't show buttons on fail overlay when player interaction is disabled --- osu.Game/Screens/Play/FailOverlay.cs | 15 +++++++++++++-- osu.Game/Screens/Play/Player.cs | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/FailOverlay.cs b/osu.Game/Screens/Play/FailOverlay.cs index abfc401998..210ae5ceb6 100644 --- a/osu.Game/Screens/Play/FailOverlay.cs +++ b/osu.Game/Screens/Play/FailOverlay.cs @@ -26,11 +26,22 @@ namespace osu.Game.Screens.Play public override LocalisableString Header => GameplayMenuOverlayStrings.FailedHeader; + private readonly bool showButtons; + + public FailOverlay(bool showButtons = true) + { + this.showButtons = showButtons; + } + [BackgroundDependencyLoader] private void load(OsuColour colours) { - AddButton(GameplayMenuOverlayStrings.Retry, colours.YellowDark, () => OnRetry?.Invoke()); - AddButton(GameplayMenuOverlayStrings.Quit, new Color4(170, 27, 39, 255), () => OnQuit?.Invoke()); + if (showButtons) + { + AddButton(GameplayMenuOverlayStrings.Retry, colours.YellowDark, () => OnRetry?.Invoke()); + AddButton(GameplayMenuOverlayStrings.Quit, new Color4(170, 27, 39, 255), () => OnQuit?.Invoke()); + } + // from #10339 maybe this is a better visual effect Add(new Container { diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ff00b52f71..1c97efcff7 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -267,7 +267,7 @@ namespace osu.Game.Screens.Play createGameplayComponents(Beatmap.Value) } }, - FailOverlay = new FailOverlay + FailOverlay = new FailOverlay(Configuration.AllowUserInteraction) { SaveReplay = async () => await prepareAndImportScoreAsync(true).ConfigureAwait(false), OnRetry = () => Restart(), From d9242278105febc065dcc57b4bd318a89dcb190a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 24 Nov 2023 18:04:57 +0900 Subject: [PATCH 1724/4092] Add `ManiaHealthProcessor` that uses the legacy drain rate algorithm --- .../Scoring/ManiaHealthProcessor.cs | 52 ++++++++++++++++--- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaHealthProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaHealthProcessor.cs index e63a037ca9..183550eb7b 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaHealthProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaHealthProcessor.cs @@ -1,23 +1,61 @@ // 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.Judgements; +using System.Collections.Generic; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Scoring { - public partial class ManiaHealthProcessor : DrainingHealthProcessor + public partial class ManiaHealthProcessor : LegacyDrainingHealthProcessor { - /// public ManiaHealthProcessor(double drainStartTime) - : base(drainStartTime, 1.0) + : base(drainStartTime) { } - protected override HitResult GetSimulatedHitResult(Judgement judgement) + protected override IEnumerable EnumerateTopLevelHitObjects() => Beatmap.HitObjects; + + protected override IEnumerable EnumerateNestedHitObjects(HitObject hitObject) => hitObject.NestedHitObjects; + + protected override double GetHealthIncreaseFor(HitObject hitObject, HitResult result) { - // Users are not expected to attain perfect judgements for all notes due to the tighter hit window. - return judgement.MaxResult == HitResult.Perfect ? HitResult.Great : judgement.MaxResult; + double increase = 0; + + switch (result) + { + case HitResult.Miss: + switch (hitObject) + { + case HeadNote: + case TailNote: + return -(Beatmap.Difficulty.DrainRate + 1) * 0.00375; + + default: + return -(Beatmap.Difficulty.DrainRate + 1) * 0.0075; + } + + case HitResult.Meh: + return -(Beatmap.Difficulty.DrainRate + 1) * 0.0016; + + case HitResult.Ok: + return 0; + + case HitResult.Good: + increase = 0.004 - Beatmap.Difficulty.DrainRate * 0.0004; + break; + + case HitResult.Great: + increase = 0.005 - Beatmap.Difficulty.DrainRate * 0.0005; + break; + + case HitResult.Perfect: + increase = 0.0055 - Beatmap.Difficulty.DrainRate * 0.0005; + break; + } + + return HpMultiplierNormal * increase; } } } From 3f73610ee765b5543e851627efdd64ecba93eccf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 27 Nov 2023 15:06:11 +0900 Subject: [PATCH 1725/4092] Update framework ^& resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 4 ++-- osu.iOS.props | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index ea08992710..3b90b1675c 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 53d5d6b010..7e5c5be4ea 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From ff18f80559acc1a1d6fd6dd709d2cbb0ff98c710 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Nov 2023 16:56:11 +0900 Subject: [PATCH 1726/4092] Apply NRT to `UpdateSettings` --- .../Sections/General/UpdateSettings.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 2f68b3a82f..6dc3cc5f18 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Threading.Tasks; using osu.Framework; using osu.Framework.Allocation; @@ -21,17 +19,17 @@ namespace osu.Game.Overlays.Settings.Sections.General { public partial class UpdateSettings : SettingsSubsection { - [Resolved(CanBeNull = true)] - private UpdateManager updateManager { get; set; } - protected override LocalisableString Header => GeneralSettingsStrings.UpdateHeader; - private SettingsButton checkForUpdatesButton; + private SettingsButton checkForUpdatesButton = null!; - [Resolved(CanBeNull = true)] - private INotificationOverlay notifications { get; set; } + [Resolved] + private UpdateManager? updateManager { get; set; } - [BackgroundDependencyLoader(true)] + [Resolved] + private INotificationOverlay? notifications { get; set; } + + [BackgroundDependencyLoader] private void load(Storage storage, OsuConfigManager config, OsuGame game) { Add(new SettingsEnumDropdown @@ -77,7 +75,7 @@ namespace osu.Game.Overlays.Settings.Sections.General Add(new SettingsButton { Text = GeneralSettingsStrings.ChangeFolderLocation, - Action = () => game?.PerformFromScreen(menu => menu.Push(new MigrationSelectScreen())) + Action = () => game.PerformFromScreen(menu => menu.Push(new MigrationSelectScreen())) }); } } From 11f1f4423704a205fa231c55e1f52b9574ee57c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Nov 2023 17:13:11 +0900 Subject: [PATCH 1727/4092] Add button to compress log files for bug submission --- .../Sections/General/UpdateSettings.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 6dc3cc5f18..5f74b5ecb9 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -14,6 +14,7 @@ using osu.Game.Localisation; using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Settings.Sections.Maintenance; using osu.Game.Updater; +using SharpCompress.Archives.Zip; namespace osu.Game.Overlays.Settings.Sections.General { @@ -72,6 +73,29 @@ namespace osu.Game.Overlays.Settings.Sections.General Action = () => storage.PresentExternally(), }); + Add(new SettingsButton + { + Text = "Compress log files", + Keywords = new[] { @"bug", "report", "logs" }, + Action = () => + { + var logStorage = storage.GetStorageForDirectory(@"logs"); + + const string archive_filename = "exports/compressed-logs.zip"; + + using (var outStream = storage.CreateFileSafely(archive_filename)) + using (var zip = ZipArchive.Create()) + { + foreach (string? f in logStorage.GetFiles(string.Empty, "*.log")) + zip.AddEntry(f, logStorage.GetStream(f), true); + + zip.SaveTo(outStream); + } + + storage.PresentFileExternally(archive_filename); + }, + }); + Add(new SettingsButton { Text = GeneralSettingsStrings.ChangeFolderLocation, From 4c2819dbc2d5bd88af61a00ebe58d0ce6a1247ea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Nov 2023 17:59:05 +0900 Subject: [PATCH 1728/4092] Update button text and make localisable --- osu.Game/Localisation/GeneralSettingsStrings.cs | 5 +++++ .../Overlays/Settings/Sections/General/UpdateSettings.cs | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Localisation/GeneralSettingsStrings.cs b/osu.Game/Localisation/GeneralSettingsStrings.cs index ebf57d8109..42623f4632 100644 --- a/osu.Game/Localisation/GeneralSettingsStrings.cs +++ b/osu.Game/Localisation/GeneralSettingsStrings.cs @@ -49,6 +49,11 @@ namespace osu.Game.Localisation /// public static LocalisableString OpenOsuFolder => new TranslatableString(getKey(@"open_osu_folder"), @"Open osu! folder"); + /// + /// "Export logs" + /// + public static LocalisableString ExportLogs => new TranslatableString(getKey(@"export_logs"), @"Export logs"); + /// /// "Change folder location..." /// diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 5f74b5ecb9..e2c9de1807 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -75,8 +75,8 @@ namespace osu.Game.Overlays.Settings.Sections.General Add(new SettingsButton { - Text = "Compress log files", - Keywords = new[] { @"bug", "report", "logs" }, + Text = GeneralSettingsStrings.ExportLogs, + Keywords = new[] { @"bug", "report", "logs", "files" }, Action = () => { var logStorage = storage.GetStorageForDirectory(@"logs"); From 51de98f34186b799c0e51e6434a1783302df6f57 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Nov 2023 17:59:21 +0900 Subject: [PATCH 1729/4092] Use `Logger.Storage` rather than locally querying --- osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index e2c9de1807..398e4b39af 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Game.Configuration; @@ -79,7 +80,7 @@ namespace osu.Game.Overlays.Settings.Sections.General Keywords = new[] { @"bug", "report", "logs", "files" }, Action = () => { - var logStorage = storage.GetStorageForDirectory(@"logs"); + var logStorage = Logger.Storage; const string archive_filename = "exports/compressed-logs.zip"; From 26855a2c04f623e87c1a357e3dab3cda241dd075 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 28 Nov 2023 21:14:34 +0900 Subject: [PATCH 1730/4092] Add failing test --- .../Formats/LegacyBeatmapDecoderTest.cs | 32 +++++++++++++++++++ .../Resources/custom-slider-length.osu | 19 +++++++++++ 2 files changed, 51 insertions(+) create mode 100644 osu.Game.Tests/Resources/custom-slider-length.osu diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index dcfe8ecb41..02432a1935 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -15,12 +15,14 @@ using osu.Game.IO; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Catch.Beatmaps; +using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Mods; 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.Beatmaps; +using osu.Game.Rulesets.Taiko; using osu.Game.Skinning; using osu.Game.Tests.Resources; using osuTK; @@ -1156,5 +1158,35 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(((IHasComboInformation)playable.HitObjects[17]).ComboIndexWithOffsets, Is.EqualTo(9)); } } + + [Test] + public void TestSliderConversionWithCustomDistance([Values("taiko", "mania")] string rulesetName) + { + using (var resStream = TestResources.OpenResource("custom-slider-length.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + Ruleset ruleset; + + switch (rulesetName) + { + case "taiko": + ruleset = new TaikoRuleset(); + break; + + case "mania": + ruleset = new ManiaRuleset(); + break; + + default: + throw new ArgumentOutOfRangeException(nameof(rulesetName), rulesetName, null); + } + + var decoder = Decoder.GetDecoder(stream); + var working = new TestWorkingBeatmap(decoder.Decode(stream)); + IBeatmap beatmap = working.GetPlayableBeatmap(ruleset.RulesetInfo, Array.Empty()); + + Assert.That(beatmap.HitObjects[0].GetEndTime(), Is.EqualTo(3153)); + } + } } } diff --git a/osu.Game.Tests/Resources/custom-slider-length.osu b/osu.Game.Tests/Resources/custom-slider-length.osu new file mode 100644 index 0000000000..f7529918a9 --- /dev/null +++ b/osu.Game.Tests/Resources/custom-slider-length.osu @@ -0,0 +1,19 @@ +osu file format v14 + +[General] +Mode: 0 + +[Difficulty] +HPDrainRate:6 +CircleSize:7 +OverallDifficulty:7 +ApproachRate:10 +SliderMultiplier:1.7 +SliderTickRate:1 + +[TimingPoints] +29,333.333333333333,4,1,0,100,1,0 +29,-10000,4,1,0,100,0,0 + +[HitObjects] +256,192,29,6,0,P|384:192|384:192,1,159.375 \ No newline at end of file From 16577829e27696d27f3ae99129a1c9f3a16639d3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 28 Nov 2023 21:14:56 +0900 Subject: [PATCH 1731/4092] Fix mania and taiko slider conversion distance --- .../Patterns/Legacy/DistanceObjectPatternGenerator.cs | 9 ++++++++- .../Beatmaps/TaikoBeatmapConverter.cs | 10 +++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index cce0944564..8aa128be33 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -60,8 +60,15 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy SpanCount = repeatsData?.SpanCount() ?? 1; StartTime = (int)Math.Round(hitObject.StartTime); + double distance; + + if (hitObject is IHasPath pathData) + distance = pathData.Path.ExpectedDistance.Value ?? 0; + else + distance = distanceData.Distance; + // This matches stable's calculation. - EndTime = (int)Math.Floor(StartTime + distanceData.Distance * beatLength * SpanCount * 0.01 / beatmap.Difficulty.SliderMultiplier); + EndTime = (int)Math.Floor(StartTime + distance * beatLength * SpanCount * 0.01 / beatmap.Difficulty.SliderMultiplier); SegmentDuration = (EndTime - StartTime) / SpanCount; } diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index e46e2ec09c..4827ec76aa 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -182,7 +182,15 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps // The true distance, accounting for any repeats. This ends up being the drum roll distance later int spans = (obj as IHasRepeats)?.SpanCount() ?? 1; - double distance = distanceData.Distance * spans * LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER; + + double distance; + + if (obj is IHasPath pathData) + distance = pathData.Path.ExpectedDistance.Value ?? 0; + else + distance = distanceData.Distance; + + distance *= spans * LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER; TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime); From 979bbf0d810fb080e883c04c51c53677dd9f01cd Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 28 Nov 2023 22:12:23 +0900 Subject: [PATCH 1732/4092] Wrap echo in double quotes --- .github/workflows/diffcalc.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/diffcalc.yml b/.github/workflows/diffcalc.yml index d4150208d3..5f16e09040 100644 --- a/.github/workflows/diffcalc.yml +++ b/.github/workflows/diffcalc.yml @@ -189,8 +189,8 @@ jobs: COMMENT_BODY: ${{ github.event.comment.body }} run: | # Add comment environment - echo $COMMENT_BODY | sed -r 's/\r$//' | grep -E '^\w+=' | while read -r line; do - opt=$(echo ${line} | cut -d '=' -f1) + echo "$COMMENT_BODY" | sed -r 's/\r$//' | grep -E '^\w+=' | while read -r line; do + opt=$(echo "${line}" | cut -d '=' -f1) sed -i "s;^${opt}=.*$;${line};" "${{ needs.directory.outputs.GENERATOR_ENV }}" done From c3ddf773b75c2d2bd64bf918a4c275ea8ae971f5 Mon Sep 17 00:00:00 2001 From: Rodrigo Pina Date: Tue, 28 Nov 2023 14:56:07 +0000 Subject: [PATCH 1733/4092] # osu.Game.Tournament.Models + Add: New property BanCount in TournamentRound to save the number of bans # osu.Game.Tournament/Screens + Add: New slider setting in RoundEditorScreen to select the number of bans per round * Change: Modified setNextMode behavior to get the round ban count, and select bans accordingly --- osu.Game.Tournament/Models/TournamentRound.cs | 1 + .../Screens/Editors/RoundEditorScreen.cs | 6 ++++++ osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs | 11 +++++++++-- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/Models/TournamentRound.cs b/osu.Game.Tournament/Models/TournamentRound.cs index a92bab690e..7aa8bbb44f 100644 --- a/osu.Game.Tournament/Models/TournamentRound.cs +++ b/osu.Game.Tournament/Models/TournamentRound.cs @@ -18,6 +18,7 @@ namespace osu.Game.Tournament.Models public readonly Bindable Description = new Bindable(string.Empty); public readonly BindableInt BestOf = new BindableInt(9) { Default = 9, MinValue = 3, MaxValue = 23 }; + public readonly BindableInt BanCount = new BindableInt(1) { Default = 1, MinValue = 0, MaxValue = 5 }; [JsonProperty] public readonly BindableList Beatmaps = new BindableList(); diff --git a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs index f887c41749..253cca8c98 100644 --- a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs @@ -82,6 +82,12 @@ namespace osu.Game.Tournament.Screens.Editors Current = Model.StartDate }, new SettingsSlider + { + LabelText = "# of Bans", + Width = 0.33f, + Current = Model.BanCount + }, + new SettingsSlider { LabelText = "Best of", Width = 0.33f, diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs index f80f43bb77..5f5fb873f4 100644 --- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs +++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs @@ -146,17 +146,24 @@ namespace osu.Game.Tournament.Screens.MapPool private void setNextMode() { + int banCount = 2; + if (CurrentMatch.Value == null) return; + if (CurrentMatch.Value.Round.Value != null) + { + banCount = CurrentMatch.Value.Round.Value.BanCount.Value * 2; + } + const TeamColour roll_winner = TeamColour.Red; //todo: draw from match var nextColour = (CurrentMatch.Value.PicksBans.LastOrDefault()?.Team ?? roll_winner) == TeamColour.Red ? TeamColour.Blue : TeamColour.Red; - if (pickType == ChoiceType.Ban && CurrentMatch.Value.PicksBans.Count(p => p.Type == ChoiceType.Ban) >= 2) + if (pickType == ChoiceType.Ban && CurrentMatch.Value.PicksBans.Count(p => p.Type == ChoiceType.Ban) >= banCount) setMode(pickColour, ChoiceType.Pick); else - setMode(nextColour, CurrentMatch.Value.PicksBans.Count(p => p.Type == ChoiceType.Ban) >= 2 ? ChoiceType.Pick : ChoiceType.Ban); + setMode(nextColour, CurrentMatch.Value.PicksBans.Count(p => p.Type == ChoiceType.Ban) >= banCount ? ChoiceType.Pick : ChoiceType.Ban); } protected override bool OnMouseDown(MouseDownEvent e) From 2dd12a67257c8f201d307b240f5753e43fc75c86 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Nov 2023 15:49:28 +0900 Subject: [PATCH 1734/4092] Improve logic around map pool mode changes --- .../Screens/MapPool/MapPoolScreen.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs index 5f5fb873f4..5148c0eaf4 100644 --- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs +++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs @@ -136,34 +136,34 @@ namespace osu.Game.Tournament.Screens.MapPool pickColour = colour; pickType = choiceType; - static Color4 setColour(bool active) => active ? Color4.White : Color4.Gray; - buttonRedBan.Colour = setColour(pickColour == TeamColour.Red && pickType == ChoiceType.Ban); buttonBlueBan.Colour = setColour(pickColour == TeamColour.Blue && pickType == ChoiceType.Ban); buttonRedPick.Colour = setColour(pickColour == TeamColour.Red && pickType == ChoiceType.Pick); buttonBluePick.Colour = setColour(pickColour == TeamColour.Blue && pickType == ChoiceType.Pick); + + static Color4 setColour(bool active) => active ? Color4.White : Color4.Gray; } private void setNextMode() { - int banCount = 2; - - if (CurrentMatch.Value == null) + if (CurrentMatch.Value?.Round.Value == null) return; - if (CurrentMatch.Value.Round.Value != null) - { - banCount = CurrentMatch.Value.Round.Value.BanCount.Value * 2; - } + int totalBansRequired = CurrentMatch.Value.Round.Value.BanCount.Value * 2; const TeamColour roll_winner = TeamColour.Red; //todo: draw from match var nextColour = (CurrentMatch.Value.PicksBans.LastOrDefault()?.Team ?? roll_winner) == TeamColour.Red ? TeamColour.Blue : TeamColour.Red; - if (pickType == ChoiceType.Ban && CurrentMatch.Value.PicksBans.Count(p => p.Type == ChoiceType.Ban) >= banCount) - setMode(pickColour, ChoiceType.Pick); - else - setMode(nextColour, CurrentMatch.Value.PicksBans.Count(p => p.Type == ChoiceType.Ban) >= banCount ? ChoiceType.Pick : ChoiceType.Ban); + bool hasAllBans = CurrentMatch.Value.PicksBans.Count(p => p.Type == ChoiceType.Ban) >= totalBansRequired; + + if (hasAllBans && pickType == ChoiceType.Ban) + { + // When switching from bans to picks, we don't rotate the team colour. + nextColour = pickColour; + } + + setMode(nextColour, hasAllBans ? ChoiceType.Pick : ChoiceType.Ban); } protected override bool OnMouseDown(MouseDownEvent e) From 301d503b0b52b4ba7ba16be201efeff7a5040e64 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 29 Nov 2023 16:41:19 +0900 Subject: [PATCH 1735/4092] Add another source of FP inaccuracy to match osu!stable --- osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 4827ec76aa..2393a248eb 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -190,7 +190,9 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps else distance = distanceData.Distance; - distance *= spans * LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER; + // Do not combine the following two lines! + distance *= LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER; + distance *= spans; TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime); From 1c3bcbd54812f30c1170f2f5a0ab150220505d67 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 29 Nov 2023 17:30:21 +0900 Subject: [PATCH 1736/4092] Use IHasPath instead of IHasDistance for mania/taiko --- .../Beatmaps/ManiaBeatmapConverter.cs | 4 ++-- ...rator.cs => PathObjectPatternGenerator.cs} | 20 +++++-------------- .../Beatmaps/TaikoBeatmapConverter.cs | 14 ++++--------- 3 files changed, 11 insertions(+), 27 deletions(-) rename osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/{DistanceObjectPatternGenerator.cs => PathObjectPatternGenerator.cs} (96%) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index aaef69f119..ccfe1501bd 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -174,9 +174,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps switch (original) { - case IHasDistance: + case IHasPath: { - var generator = new DistanceObjectPatternGenerator(Random, original, beatmap, lastPattern, originalBeatmap); + var generator = new PathObjectPatternGenerator(Random, original, beatmap, lastPattern, originalBeatmap); conversion = generator; var positionData = original as IHasPosition; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PathObjectPatternGenerator.cs similarity index 96% rename from osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs rename to osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PathObjectPatternGenerator.cs index 8aa128be33..4922915c7d 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PathObjectPatternGenerator.cs @@ -22,13 +22,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// /// A pattern generator for IHasDistance hit objects. /// - internal class DistanceObjectPatternGenerator : PatternGenerator + internal class PathObjectPatternGenerator : PatternGenerator { - /// - /// Base osu! slider scoring distance. - /// - private const float osu_base_scoring_distance = 100; - public readonly int StartTime; public readonly int EndTime; public readonly int SegmentDuration; @@ -36,17 +31,17 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy private PatternType convertType; - public DistanceObjectPatternGenerator(LegacyRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap) + public PathObjectPatternGenerator(LegacyRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap) : base(random, hitObject, beatmap, previousPattern, originalBeatmap) { convertType = PatternType.None; if (!Beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime).KiaiMode) convertType = PatternType.LowProbability; - var distanceData = hitObject as IHasDistance; + var pathData = hitObject as IHasPath; var repeatsData = hitObject as IHasRepeats; - Debug.Assert(distanceData != null); + Debug.Assert(pathData != null); TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); @@ -60,12 +55,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy SpanCount = repeatsData?.SpanCount() ?? 1; StartTime = (int)Math.Round(hitObject.StartTime); - double distance; - - if (hitObject is IHasPath pathData) - distance = pathData.Path.ExpectedDistance.Value ?? 0; - else - distance = distanceData.Distance; + double distance = pathData.Path.ExpectedDistance.Value ?? 0; // This matches stable's calculation. EndTime = (int)Math.Floor(StartTime + distance * beatLength * SpanCount * 0.01 / beatmap.Difficulty.SliderMultiplier); diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 2393a248eb..2551321ff2 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -109,9 +109,9 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps switch (obj) { - case IHasDistance distanceData: + case IHasPath pathData: { - if (shouldConvertSliderToHits(obj, beatmap, distanceData, out int taikoDuration, out double tickSpacing)) + if (shouldConvertSliderToHits(obj, beatmap, pathData, out int taikoDuration, out double tickSpacing)) { IList> allSamples = obj is IHasPathWithRepeats curveData ? curveData.NodeSamples : new List>(new[] { samples }); @@ -174,7 +174,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps } } - private bool shouldConvertSliderToHits(HitObject obj, IBeatmap beatmap, IHasDistance distanceData, out int taikoDuration, out double tickSpacing) + private bool shouldConvertSliderToHits(HitObject obj, IBeatmap beatmap, IHasPath pathData, out int taikoDuration, out double tickSpacing) { // DO NOT CHANGE OR REFACTOR ANYTHING IN HERE WITHOUT TESTING AGAINST _ALL_ BEATMAPS. // Some of these calculations look redundant, but they are not - extremely small floating point errors are introduced to maintain 1:1 compatibility with stable. @@ -182,13 +182,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps // The true distance, accounting for any repeats. This ends up being the drum roll distance later int spans = (obj as IHasRepeats)?.SpanCount() ?? 1; - - double distance; - - if (obj is IHasPath pathData) - distance = pathData.Path.ExpectedDistance.Value ?? 0; - else - distance = distanceData.Distance; + double distance = pathData.Path.ExpectedDistance.Value ?? 0; // Do not combine the following two lines! distance *= LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER; From 295a1b01d6257980a688551e7aaa0fdd99b49257 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 29 Nov 2023 19:05:24 +0900 Subject: [PATCH 1737/4092] Adjust catch score grade cutoffs --- .../Scoring/CatchScoreProcessor.cs | 53 +++++++++ .../Visual/Ranking/TestSceneAccuracyCircle.cs | 110 +++++++++++------- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 4 +- .../Expanded/Accuracy/AccuracyCircle.cs | 69 ++++++----- 4 files changed, 160 insertions(+), 76 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index 9323296b7f..66c76f9b17 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -4,11 +4,19 @@ using System; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Catch.Scoring { public partial class CatchScoreProcessor : ScoreProcessor { + private const double accuracy_cutoff_x = 1; + private const double accuracy_cutoff_s = 0.98; + private const double accuracy_cutoff_a = 0.94; + private const double accuracy_cutoff_b = 0.9; + private const double accuracy_cutoff_c = 0.85; + private const double accuracy_cutoff_d = 0; + private const int combo_cap = 200; private const double combo_base = 4; @@ -26,5 +34,50 @@ namespace osu.Game.Rulesets.Catch.Scoring protected override double GetComboScoreChange(JudgementResult result) => Judgement.ToNumericResult(result.Type) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(combo_cap, combo_base)); + + public override ScoreRank RankFromAccuracy(double accuracy) + { + if (accuracy == accuracy_cutoff_x) + return ScoreRank.X; + if (accuracy >= accuracy_cutoff_s) + return ScoreRank.S; + if (accuracy >= accuracy_cutoff_a) + return ScoreRank.A; + if (accuracy >= accuracy_cutoff_b) + return ScoreRank.B; + if (accuracy >= accuracy_cutoff_c) + return ScoreRank.C; + + return ScoreRank.D; + } + + public override double AccuracyCutoffFromRank(ScoreRank rank) + { + switch (rank) + { + case ScoreRank.X: + case ScoreRank.XH: + return accuracy_cutoff_x; + + case ScoreRank.S: + case ScoreRank.SH: + return accuracy_cutoff_s; + + case ScoreRank.A: + return accuracy_cutoff_a; + + case ScoreRank.B: + return accuracy_cutoff_b; + + case ScoreRank.C: + return accuracy_cutoff_c; + + case ScoreRank.D: + return accuracy_cutoff_d; + + default: + throw new ArgumentOutOfRangeException(nameof(rank), rank, null); + } + } } } diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs index 03b168c72c..435dd77120 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs @@ -9,6 +9,8 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; @@ -22,31 +24,48 @@ namespace osu.Game.Tests.Visual.Ranking { public partial class TestSceneAccuracyCircle : OsuTestScene { - [TestCase(0)] - [TestCase(0.2)] - [TestCase(0.5)] - [TestCase(0.6999)] - [TestCase(0.7)] - [TestCase(0.75)] - [TestCase(0.7999)] - [TestCase(0.8)] - [TestCase(0.85)] - [TestCase(0.8999)] - [TestCase(0.9)] - [TestCase(0.925)] - [TestCase(0.9499)] - [TestCase(0.95)] - [TestCase(0.975)] - [TestCase(0.9999)] - [TestCase(1)] - public void TestRank(double accuracy) + [Test] + public void TestOsuRank() { - var score = createScore(accuracy, ScoreProcessor.RankFromAccuracy(accuracy)); - - addCircleStep(score); + addCircleStep(createScore(0, new OsuRuleset())); + addCircleStep(createScore(0.5, new OsuRuleset())); + addCircleStep(createScore(0.699, new OsuRuleset())); + addCircleStep(createScore(0.7, new OsuRuleset())); + addCircleStep(createScore(0.75, new OsuRuleset())); + addCircleStep(createScore(0.799, new OsuRuleset())); + addCircleStep(createScore(0.8, new OsuRuleset())); + addCircleStep(createScore(0.85, new OsuRuleset())); + addCircleStep(createScore(0.899, new OsuRuleset())); + addCircleStep(createScore(0.9, new OsuRuleset())); + addCircleStep(createScore(0.925, new OsuRuleset())); + addCircleStep(createScore(0.9499, new OsuRuleset())); + addCircleStep(createScore(0.95, new OsuRuleset())); + addCircleStep(createScore(0.975, new OsuRuleset())); + addCircleStep(createScore(0.99, new OsuRuleset())); + addCircleStep(createScore(1, new OsuRuleset())); } - private void addCircleStep(ScoreInfo score) => AddStep("add panel", () => + [Test] + public void TestCatchRank() + { + addCircleStep(createScore(0, new CatchRuleset())); + addCircleStep(createScore(0.5, new CatchRuleset())); + addCircleStep(createScore(0.8499, new CatchRuleset())); + addCircleStep(createScore(0.85, new CatchRuleset())); + addCircleStep(createScore(0.875, new CatchRuleset())); + addCircleStep(createScore(0.899, new CatchRuleset())); + addCircleStep(createScore(0.9, new CatchRuleset())); + addCircleStep(createScore(0.925, new CatchRuleset())); + addCircleStep(createScore(0.9399, new CatchRuleset())); + addCircleStep(createScore(0.94, new CatchRuleset())); + addCircleStep(createScore(0.9675, new CatchRuleset())); + addCircleStep(createScore(0.9799, new CatchRuleset())); + addCircleStep(createScore(0.98, new CatchRuleset())); + addCircleStep(createScore(0.99, new CatchRuleset())); + addCircleStep(createScore(1, new CatchRuleset())); + } + + private void addCircleStep(ScoreInfo score) => AddStep($"add panel ({score.DisplayAccuracy})", () => { Children = new Drawable[] { @@ -73,28 +92,33 @@ namespace osu.Game.Tests.Visual.Ranking }; }); - private ScoreInfo createScore(double accuracy, ScoreRank rank) => new ScoreInfo + private ScoreInfo createScore(double accuracy, Ruleset ruleset) { - User = new APIUser + var scoreProcessor = ruleset.CreateScoreProcessor(); + + return new ScoreInfo { - Id = 2, - Username = "peppy", - }, - BeatmapInfo = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, - Ruleset = new OsuRuleset().RulesetInfo, - Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, - TotalScore = 2845370, - Accuracy = accuracy, - MaxCombo = 999, - Rank = rank, - Date = DateTimeOffset.Now, - Statistics = - { - { HitResult.Miss, 1 }, - { HitResult.Meh, 50 }, - { HitResult.Good, 100 }, - { HitResult.Great, 300 }, - } - }; + User = new APIUser + { + Id = 2, + Username = "peppy", + }, + BeatmapInfo = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, + Ruleset = ruleset.RulesetInfo, + Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, + TotalScore = 2845370, + Accuracy = accuracy, + MaxCombo = 999, + Rank = scoreProcessor.RankFromAccuracy(accuracy), + Date = DateTimeOffset.Now, + Statistics = + { + { HitResult.Miss, 1 }, + { HitResult.Meh, 50 }, + { HitResult.Good, 100 }, + { HitResult.Great, 300 }, + } + }; + } } } diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 4e899479bd..92336b2c21 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -446,7 +446,7 @@ namespace osu.Game.Rulesets.Scoring /// /// Given an accuracy (0..1), return the correct . /// - public static ScoreRank RankFromAccuracy(double accuracy) + public virtual ScoreRank RankFromAccuracy(double accuracy) { if (accuracy == accuracy_cutoff_x) return ScoreRank.X; @@ -466,7 +466,7 @@ namespace osu.Game.Rulesets.Scoring /// Given a , return the cutoff accuracy (0..1). /// Accuracy must be greater than or equal to the cutoff to qualify for the provided rank. /// - public static double AccuracyCutoffFromRank(ScoreRank rank) + public virtual double AccuracyCutoffFromRank(ScoreRank rank) { switch (rank) { diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 2ec4270c3c..80ff872312 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -29,13 +29,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy /// public partial class AccuracyCircle : CompositeDrawable { - private static readonly double accuracy_x = ScoreProcessor.AccuracyCutoffFromRank(ScoreRank.X); - private static readonly double accuracy_s = ScoreProcessor.AccuracyCutoffFromRank(ScoreRank.S); - private static readonly double accuracy_a = ScoreProcessor.AccuracyCutoffFromRank(ScoreRank.A); - private static readonly double accuracy_b = ScoreProcessor.AccuracyCutoffFromRank(ScoreRank.B); - private static readonly double accuracy_c = ScoreProcessor.AccuracyCutoffFromRank(ScoreRank.C); - private static readonly double accuracy_d = ScoreProcessor.AccuracyCutoffFromRank(ScoreRank.D); - /// /// Duration for the transforms causing this component to appear. /// @@ -110,12 +103,26 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy private double lastTickPlaybackTime; private bool isTicking; + private readonly double accuracyX; + private readonly double accuracyS; + private readonly double accuracyA; + private readonly double accuracyB; + private readonly double accuracyC; + private readonly double accuracyD; private readonly bool withFlair; public AccuracyCircle(ScoreInfo score, bool withFlair = false) { this.score = score; this.withFlair = withFlair; + + ScoreProcessor scoreProcessor = score.Ruleset.CreateInstance().CreateScoreProcessor(); + accuracyX = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.X); + accuracyS = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.S); + accuracyA = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.A); + accuracyB = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.B); + accuracyC = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.C); + accuracyD = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.D); } [BackgroundDependencyLoader] @@ -158,49 +165,49 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy RelativeSizeAxes = Axes.Both, Colour = OsuColour.ForRank(ScoreRank.X), InnerRadius = RANK_CIRCLE_RADIUS, - Current = { Value = accuracy_x } + Current = { Value = accuracyX } }, new CircularProgress { RelativeSizeAxes = Axes.Both, Colour = OsuColour.ForRank(ScoreRank.S), InnerRadius = RANK_CIRCLE_RADIUS, - Current = { Value = accuracy_x - virtual_ss_percentage } + Current = { Value = accuracyX - virtual_ss_percentage } }, new CircularProgress { RelativeSizeAxes = Axes.Both, Colour = OsuColour.ForRank(ScoreRank.A), InnerRadius = RANK_CIRCLE_RADIUS, - Current = { Value = accuracy_s } + Current = { Value = accuracyS } }, new CircularProgress { RelativeSizeAxes = Axes.Both, Colour = OsuColour.ForRank(ScoreRank.B), InnerRadius = RANK_CIRCLE_RADIUS, - Current = { Value = accuracy_a } + Current = { Value = accuracyA } }, new CircularProgress { RelativeSizeAxes = Axes.Both, Colour = OsuColour.ForRank(ScoreRank.C), InnerRadius = RANK_CIRCLE_RADIUS, - Current = { Value = accuracy_b } + Current = { Value = accuracyB } }, new CircularProgress { RelativeSizeAxes = Axes.Both, Colour = OsuColour.ForRank(ScoreRank.D), InnerRadius = RANK_CIRCLE_RADIUS, - Current = { Value = accuracy_c } + Current = { Value = accuracyC } }, - new RankNotch((float)accuracy_x), - new RankNotch((float)(accuracy_x - virtual_ss_percentage)), - new RankNotch((float)accuracy_s), - new RankNotch((float)accuracy_a), - new RankNotch((float)accuracy_b), - new RankNotch((float)accuracy_c), + new RankNotch((float)accuracyX), + new RankNotch((float)(accuracyX - virtual_ss_percentage)), + new RankNotch((float)accuracyS), + new RankNotch((float)accuracyA), + new RankNotch((float)accuracyB), + new RankNotch((float)accuracyC), new BufferedContainer { Name = "Graded circle mask", @@ -229,12 +236,12 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy Children = new[] { // The S and A badges are moved down slightly to prevent collision with the SS badge. - new RankBadge(accuracy_x, accuracy_x, getRank(ScoreRank.X)), - new RankBadge(accuracy_s, Interpolation.Lerp(accuracy_s, (accuracy_x - virtual_ss_percentage), 0.25), getRank(ScoreRank.S)), - new RankBadge(accuracy_a, Interpolation.Lerp(accuracy_a, accuracy_s, 0.25), getRank(ScoreRank.A)), - new RankBadge(accuracy_b, Interpolation.Lerp(accuracy_b, accuracy_a, 0.5), getRank(ScoreRank.B)), - new RankBadge(accuracy_c, Interpolation.Lerp(accuracy_c, accuracy_b, 0.5), getRank(ScoreRank.C)), - new RankBadge(accuracy_d, Interpolation.Lerp(accuracy_d, accuracy_c, 0.5), getRank(ScoreRank.D)), + new RankBadge(accuracyX, accuracyX, getRank(ScoreRank.X)), + new RankBadge(accuracyS, Interpolation.Lerp(accuracyS, (accuracyX - virtual_ss_percentage), 0.25), getRank(ScoreRank.S)), + new RankBadge(accuracyA, Interpolation.Lerp(accuracyA, accuracyS, 0.25), getRank(ScoreRank.A)), + new RankBadge(accuracyB, Interpolation.Lerp(accuracyB, accuracyA, 0.5), getRank(ScoreRank.B)), + new RankBadge(accuracyC, Interpolation.Lerp(accuracyC, accuracyB, 0.5), getRank(ScoreRank.C)), + new RankBadge(accuracyD, Interpolation.Lerp(accuracyD, accuracyC, 0.5), getRank(ScoreRank.D)), } }, rankText = new RankText(score.Rank) @@ -280,10 +287,10 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy double targetAccuracy = score.Accuracy; double[] notchPercentages = { - accuracy_s, - accuracy_a, - accuracy_b, - accuracy_c, + accuracyS, + accuracyA, + accuracyB, + accuracyC, }; // Ensure the gauge overshoots or undershoots a bit so it doesn't land in the gaps of the inner graded circle (caused by `RankNotch`es), @@ -302,7 +309,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy if (score.Rank == ScoreRank.X || score.Rank == ScoreRank.XH) targetAccuracy = 1; else - targetAccuracy = Math.Min(accuracy_x - virtual_ss_percentage - NOTCH_WIDTH_PERCENTAGE / 2, targetAccuracy); + targetAccuracy = Math.Min(accuracyX - virtual_ss_percentage - NOTCH_WIDTH_PERCENTAGE / 2, targetAccuracy); // The accuracy circle gauge visually fills up a bit too much. // This wouldn't normally matter but we want it to align properly with the inner graded circle in the above cases. @@ -339,7 +346,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy if (badge.Accuracy > score.Accuracy) continue; - using (BeginDelayedSequence(inverseEasing(ACCURACY_TRANSFORM_EASING, Math.Min(accuracy_x - virtual_ss_percentage, badge.Accuracy) / targetAccuracy) * ACCURACY_TRANSFORM_DURATION)) + using (BeginDelayedSequence(inverseEasing(ACCURACY_TRANSFORM_EASING, Math.Min(accuracyX - virtual_ss_percentage, badge.Accuracy) / targetAccuracy) * ACCURACY_TRANSFORM_DURATION)) { badge.Appear(); From 60d6c0fe53ebd1b5d9761cc1151f3a44473948e4 Mon Sep 17 00:00:00 2001 From: Rodrigo Pina Date: Wed, 29 Nov 2023 11:22:07 +0000 Subject: [PATCH 1738/4092] Changed ban order to match typical tournament ban structure --- osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs index 5148c0eaf4..60abddfe67 100644 --- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs +++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs @@ -153,7 +153,9 @@ namespace osu.Game.Tournament.Screens.MapPool const TeamColour roll_winner = TeamColour.Red; //todo: draw from match - var nextColour = (CurrentMatch.Value.PicksBans.LastOrDefault()?.Team ?? roll_winner) == TeamColour.Red ? TeamColour.Blue : TeamColour.Red; + var previousBan = CurrentMatch.Value.PicksBans.LastOrDefault()?.Team ?? roll_winner; + + var nextColour = (CurrentMatch.Value.PicksBans.Count() >= 2 ? CurrentMatch.Value.PicksBans[^2]?.Team : previousBan) == TeamColour.Red ? TeamColour.Blue : TeamColour.Red; bool hasAllBans = CurrentMatch.Value.PicksBans.Count(p => p.Type == ChoiceType.Ban) >= totalBansRequired; From 1cfcaee121c7f5b62610a751d1251b752c7dc794 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Nov 2023 20:29:50 +0900 Subject: [PATCH 1739/4092] Reorder badges so that SS shows above others This isn't perfect and probably needs much more consideration, but let's at least give the "better" ranks more visibility by bringing them to the front. Of note, this is only important due to the changes to osu!catch accuracy-grade cutoffs, which brings things closer in proximity than ever before. --- .../Ranking/Expanded/Accuracy/AccuracyCircle.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 80ff872312..8cbca74466 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -235,13 +235,13 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy Padding = new MarginPadding { Vertical = -15, Horizontal = -20 }, Children = new[] { - // The S and A badges are moved down slightly to prevent collision with the SS badge. - new RankBadge(accuracyX, accuracyX, getRank(ScoreRank.X)), - new RankBadge(accuracyS, Interpolation.Lerp(accuracyS, (accuracyX - virtual_ss_percentage), 0.25), getRank(ScoreRank.S)), - new RankBadge(accuracyA, Interpolation.Lerp(accuracyA, accuracyS, 0.25), getRank(ScoreRank.A)), - new RankBadge(accuracyB, Interpolation.Lerp(accuracyB, accuracyA, 0.5), getRank(ScoreRank.B)), - new RankBadge(accuracyC, Interpolation.Lerp(accuracyC, accuracyB, 0.5), getRank(ScoreRank.C)), new RankBadge(accuracyD, Interpolation.Lerp(accuracyD, accuracyC, 0.5), getRank(ScoreRank.D)), + new RankBadge(accuracyC, Interpolation.Lerp(accuracyC, accuracyB, 0.5), getRank(ScoreRank.C)), + new RankBadge(accuracyB, Interpolation.Lerp(accuracyB, accuracyA, 0.5), getRank(ScoreRank.B)), + // The S and A badges are moved down slightly to prevent collision with the SS badge. + new RankBadge(accuracyA, Interpolation.Lerp(accuracyA, accuracyS, 0.25), getRank(ScoreRank.A)), + new RankBadge(accuracyS, Interpolation.Lerp(accuracyS, (accuracyX - virtual_ss_percentage), 0.25), getRank(ScoreRank.S)), + new RankBadge(accuracyX, accuracyX, getRank(ScoreRank.X)), } }, rankText = new RankText(score.Rank) From a33a4c4d1d7094c6fe2b200ab14f8fd8a4439413 Mon Sep 17 00:00:00 2001 From: Rodrigo Pina Date: Wed, 29 Nov 2023 11:31:15 +0000 Subject: [PATCH 1740/4092] Fixed issue where pick order was following ban order structure --- osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs index 60abddfe67..9da55cc607 100644 --- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs +++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs @@ -155,10 +155,14 @@ namespace osu.Game.Tournament.Screens.MapPool var previousBan = CurrentMatch.Value.PicksBans.LastOrDefault()?.Team ?? roll_winner; - var nextColour = (CurrentMatch.Value.PicksBans.Count() >= 2 ? CurrentMatch.Value.PicksBans[^2]?.Team : previousBan) == TeamColour.Red ? TeamColour.Blue : TeamColour.Red; + var nextColour = previousBan == TeamColour.Red ? TeamColour.Blue : TeamColour.Red; bool hasAllBans = CurrentMatch.Value.PicksBans.Count(p => p.Type == ChoiceType.Ban) >= totalBansRequired; + if (!hasAllBans) + // If it's the third ban or later, we need to check if it's the team's first or second ban in a row + nextColour = (CurrentMatch.Value.PicksBans.Count() >= 2 ? CurrentMatch.Value.PicksBans[^2]?.Team : previousBan) == TeamColour.Red ? TeamColour.Blue : TeamColour.Red; + if (hasAllBans && pickType == ChoiceType.Ban) { // When switching from bans to picks, we don't rotate the team colour. From 3553717cc6238251cd7a0dd95d4749584c23504c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Nov 2023 21:28:25 +0900 Subject: [PATCH 1741/4092] Fix results screen not including slider end misses in tick count --- osu.Game/Scoring/ScoreInfo.cs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index d712702331..5545ba552e 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -342,23 +342,7 @@ namespace osu.Game.Scoring switch (r.result) { case HitResult.SmallTickHit: - { - int total = value + Statistics.GetValueOrDefault(HitResult.SmallTickMiss); - if (total > 0) - yield return new HitResultDisplayStatistic(r.result, value, total, r.displayName); - - break; - } - case HitResult.LargeTickHit: - { - int total = value + Statistics.GetValueOrDefault(HitResult.LargeTickMiss); - if (total > 0) - yield return new HitResultDisplayStatistic(r.result, value, total, r.displayName); - - break; - } - case HitResult.LargeBonus: case HitResult.SmallBonus: if (MaximumStatistics.TryGetValue(r.result, out int count) && count > 0) From ecbf07c52acbc247a7825c102b8a10cb88ea333e Mon Sep 17 00:00:00 2001 From: Rodrigo Pina Date: Thu, 30 Nov 2023 02:56:23 +0000 Subject: [PATCH 1742/4092] Replace Count() from CurrentMatch.Value.PicksBans with property alternative --- osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs index 9da55cc607..1223fd8464 100644 --- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs +++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs @@ -161,7 +161,7 @@ namespace osu.Game.Tournament.Screens.MapPool if (!hasAllBans) // If it's the third ban or later, we need to check if it's the team's first or second ban in a row - nextColour = (CurrentMatch.Value.PicksBans.Count() >= 2 ? CurrentMatch.Value.PicksBans[^2]?.Team : previousBan) == TeamColour.Red ? TeamColour.Blue : TeamColour.Red; + nextColour = (CurrentMatch.Value.PicksBans.Count >= 2 ? CurrentMatch.Value.PicksBans[^2]?.Team : previousBan) == TeamColour.Red ? TeamColour.Blue : TeamColour.Red; if (hasAllBans && pickType == ChoiceType.Ban) { From 831fe5b9f2a833dc8d1dbd4c4340376c10c854cc Mon Sep 17 00:00:00 2001 From: Susko3 Date: Thu, 30 Nov 2023 14:36:23 +0100 Subject: [PATCH 1743/4092] Use collection assert to ease debugging --- .../Visual/Online/TestSceneChatLink.cs | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs index 7616b9b83c..70eca64084 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs @@ -107,27 +107,11 @@ namespace osu.Game.Tests.Visual.Online textContainer.Add(newLine); }); - AddAssert($"msg #{index} has {linkAmount} link(s)", () => newLine.Message.Links.Count == linkAmount); - AddAssert($"msg #{index} has the right action", hasExpectedActions); + AddAssert($"msg #{index} has {linkAmount} link(s)", () => newLine.Message.Links, () => Has.Count.EqualTo(linkAmount)); + AddAssert($"msg #{index} has the right action", () => newLine.Message.Links.Select(l => l.Action), () => Is.EqualTo(expectedActions)); //AddAssert($"msg #{index} is " + (isAction ? "italic" : "not italic"), () => newLine.ContentFlow.Any() && isAction == isItalic()); AddAssert($"msg #{index} shows {linkAmount} link(s)", isShowingLinks); - bool hasExpectedActions() - { - var expectedActionsList = expectedActions.ToList(); - - if (expectedActionsList.Count != newLine.Message.Links.Count) - return false; - - for (int i = 0; i < newLine.Message.Links.Count; i++) - { - var action = newLine.Message.Links[i].Action; - if (action != expectedActions[i]) return false; - } - - return true; - } - //bool isItalic() => newLine.ContentFlow.Where(d => d is OsuSpriteText).Cast().All(sprite => sprite.Font.Italics); bool isShowingLinks() From 9a32c0368e6d03153ac21070cf5a979e200df674 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Thu, 30 Nov 2023 14:38:58 +0100 Subject: [PATCH 1744/4092] Remove redundant `linkAmount` parameter --- .../Visual/Online/TestSceneChatLink.cs | 59 +++++++++---------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs index 70eca64084..1ab7e3257f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs @@ -63,40 +63,40 @@ namespace osu.Game.Tests.Visual.Online addMessageWithChecks("test!"); addMessageWithChecks("dev.ppy.sh!"); - addMessageWithChecks("https://dev.ppy.sh!", 1, expectedActions: LinkAction.External); - addMessageWithChecks("http://dev.ppy.sh!", 1, expectedActions: LinkAction.External); - addMessageWithChecks("forgothttps://dev.ppy.sh!", 1, expectedActions: LinkAction.External); - addMessageWithChecks("forgothttp://dev.ppy.sh!", 1, expectedActions: LinkAction.External); - addMessageWithChecks("00:12:345 (1,2) - Test?", 1, expectedActions: LinkAction.OpenEditorTimestamp); - addMessageWithChecks("Wiki link for tasty [[Performance Points]]", 1, expectedActions: LinkAction.OpenWiki); - addMessageWithChecks("(osu forums)[https://dev.ppy.sh/forum] (old link format)", 1, expectedActions: LinkAction.External); - addMessageWithChecks("[https://dev.ppy.sh/home New site] (new link format)", 1, expectedActions: LinkAction.External); - addMessageWithChecks("[osu forums](https://dev.ppy.sh/forum) (new link format 2)", 1, expectedActions: LinkAction.External); - addMessageWithChecks("[https://dev.ppy.sh/home This is only a link to the new osu webpage but this is supposed to test word wrap.]", 1, expectedActions: LinkAction.External); - addMessageWithChecks("is now listening to [https://dev.ppy.sh/s/93523 IMAGE -MATERIAL- ]", 1, true, expectedActions: LinkAction.OpenBeatmapSet); - addMessageWithChecks("is now playing [https://dev.ppy.sh/b/252238 IMAGE -MATERIAL- ]", 1, true, expectedActions: LinkAction.OpenBeatmap); - addMessageWithChecks("Let's (try)[https://dev.ppy.sh/home] [https://dev.ppy.sh/b/252238 multiple links] https://dev.ppy.sh/home", 3, + addMessageWithChecks("https://dev.ppy.sh!", expectedActions: LinkAction.External); + addMessageWithChecks("http://dev.ppy.sh!", expectedActions: LinkAction.External); + addMessageWithChecks("forgothttps://dev.ppy.sh!", expectedActions: LinkAction.External); + addMessageWithChecks("forgothttp://dev.ppy.sh!", expectedActions: LinkAction.External); + addMessageWithChecks("00:12:345 (1,2) - Test?", expectedActions: LinkAction.OpenEditorTimestamp); + addMessageWithChecks("Wiki link for tasty [[Performance Points]]", expectedActions: LinkAction.OpenWiki); + addMessageWithChecks("(osu forums)[https://dev.ppy.sh/forum] (old link format)", expectedActions: LinkAction.External); + addMessageWithChecks("[https://dev.ppy.sh/home New site] (new link format)", expectedActions: LinkAction.External); + addMessageWithChecks("[osu forums](https://dev.ppy.sh/forum) (new link format 2)", expectedActions: LinkAction.External); + addMessageWithChecks("[https://dev.ppy.sh/home This is only a link to the new osu webpage but this is supposed to test word wrap.]", expectedActions: LinkAction.External); + addMessageWithChecks("is now listening to [https://dev.ppy.sh/s/93523 IMAGE -MATERIAL- ]", isAction: true, expectedActions: LinkAction.OpenBeatmapSet); + addMessageWithChecks("is now playing [https://dev.ppy.sh/b/252238 IMAGE -MATERIAL- ]", isAction: true, expectedActions: LinkAction.OpenBeatmap); + addMessageWithChecks("Let's (try)[https://dev.ppy.sh/home] [https://dev.ppy.sh/b/252238 multiple links] https://dev.ppy.sh/home", expectedActions: new[] { LinkAction.External, LinkAction.OpenBeatmap, LinkAction.External }); - addMessageWithChecks("[https://dev.ppy.sh/home New link format with escaped [and \\[ paired] braces]", 1, expectedActions: LinkAction.External); - addMessageWithChecks("[Markdown link format with escaped [and \\[ paired] braces](https://dev.ppy.sh/home)", 1, expectedActions: LinkAction.External); - addMessageWithChecks("(Old link format with escaped (and \\( paired) parentheses)[https://dev.ppy.sh/home] and [[also a rogue wiki link]]", 2, + addMessageWithChecks("[https://dev.ppy.sh/home New link format with escaped [and \\[ paired] braces]", expectedActions: LinkAction.External); + addMessageWithChecks("[Markdown link format with escaped [and \\[ paired] braces](https://dev.ppy.sh/home)", expectedActions: LinkAction.External); + addMessageWithChecks("(Old link format with escaped (and \\( paired) parentheses)[https://dev.ppy.sh/home] and [[also a rogue wiki link]]", expectedActions: new[] { LinkAction.External, LinkAction.OpenWiki }); // note that there's 0 links here (they get removed if a channel is not found) addMessageWithChecks("#lobby or #osu would be blue (and work) in the ChatDisplay test (when a proper ChatOverlay is present)."); - addMessageWithChecks("I am important!", 0, false, true); - addMessageWithChecks("feels important", 0, true, true); - addMessageWithChecks("likes to post this [https://dev.ppy.sh/home link].", 1, true, true, expectedActions: LinkAction.External); - addMessageWithChecks("Join my multiplayer game osump://12346.", 1, expectedActions: LinkAction.JoinMultiplayerMatch); - addMessageWithChecks("Join my multiplayer gameosump://12346.", 1, expectedActions: LinkAction.JoinMultiplayerMatch); - addMessageWithChecks("Join my [multiplayer game](osump://12346).", 1, expectedActions: LinkAction.JoinMultiplayerMatch); - addMessageWithChecks($"Join my [#english]({OsuGameBase.OSU_PROTOCOL}chan/#english).", 1, expectedActions: LinkAction.OpenChannel); - addMessageWithChecks($"Join my {OsuGameBase.OSU_PROTOCOL}chan/#english.", 1, expectedActions: LinkAction.OpenChannel); - addMessageWithChecks($"Join my{OsuGameBase.OSU_PROTOCOL}chan/#english.", 1, expectedActions: LinkAction.OpenChannel); - addMessageWithChecks("Join my #english or #japanese channels.", 2, expectedActions: new[] { LinkAction.OpenChannel, LinkAction.OpenChannel }); - addMessageWithChecks("Join my #english or #nonexistent #hashtag channels.", 1, expectedActions: LinkAction.OpenChannel); + addMessageWithChecks("I am important!", isAction: false, isImportant: true); + addMessageWithChecks("feels important", isAction: true, isImportant: true); + addMessageWithChecks("likes to post this [https://dev.ppy.sh/home link].", isAction: true, isImportant: true, expectedActions: LinkAction.External); + addMessageWithChecks("Join my multiplayer game osump://12346.", expectedActions: LinkAction.JoinMultiplayerMatch); + addMessageWithChecks("Join my multiplayer gameosump://12346.", expectedActions: LinkAction.JoinMultiplayerMatch); + addMessageWithChecks("Join my [multiplayer game](osump://12346).", expectedActions: LinkAction.JoinMultiplayerMatch); + addMessageWithChecks($"Join my [#english]({OsuGameBase.OSU_PROTOCOL}chan/#english).", expectedActions: LinkAction.OpenChannel); + addMessageWithChecks($"Join my {OsuGameBase.OSU_PROTOCOL}chan/#english.", expectedActions: LinkAction.OpenChannel); + addMessageWithChecks($"Join my{OsuGameBase.OSU_PROTOCOL}chan/#english.", expectedActions: LinkAction.OpenChannel); + addMessageWithChecks("Join my #english or #japanese channels.", expectedActions: new[] { LinkAction.OpenChannel, LinkAction.OpenChannel }); + addMessageWithChecks("Join my #english or #nonexistent #hashtag channels.", expectedActions: LinkAction.OpenChannel); addMessageWithChecks("Hello world\uD83D\uDE12(<--This is an emoji). There are more:\uD83D\uDE10\uD83D\uDE00,\uD83D\uDE20"); - void addMessageWithChecks(string text, int linkAmount = 0, bool isAction = false, bool isImportant = false, params LinkAction[] expectedActions) + void addMessageWithChecks(string text, bool isAction = false, bool isImportant = false, params LinkAction[] expectedActions) { ChatLine newLine = null; int index = messageIndex++; @@ -107,10 +107,9 @@ namespace osu.Game.Tests.Visual.Online textContainer.Add(newLine); }); - AddAssert($"msg #{index} has {linkAmount} link(s)", () => newLine.Message.Links, () => Has.Count.EqualTo(linkAmount)); AddAssert($"msg #{index} has the right action", () => newLine.Message.Links.Select(l => l.Action), () => Is.EqualTo(expectedActions)); //AddAssert($"msg #{index} is " + (isAction ? "italic" : "not italic"), () => newLine.ContentFlow.Any() && isAction == isItalic()); - AddAssert($"msg #{index} shows {linkAmount} link(s)", isShowingLinks); + AddAssert($"msg #{index} shows {expectedActions.Length} link(s)", isShowingLinks); //bool isItalic() => newLine.ContentFlow.Where(d => d is OsuSpriteText).Cast().All(sprite => sprite.Font.Italics); From d2324cd8f9ca27da010d26d542ccd80d7a3a29d8 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 30 Nov 2023 10:20:01 -0800 Subject: [PATCH 1745/4092] Fix chat overlay top bar icon being incorrect --- osu.Game/Overlays/Chat/ChatOverlayTopBar.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs b/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs index 84fd342493..4fc9fbb6d5 100644 --- a/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs +++ b/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs @@ -46,7 +46,7 @@ namespace osu.Game.Overlays.Chat { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Icon = HexaconsIcons.Social, + Icon = HexaconsIcons.Messaging, Size = new Vector2(24), }, // Placeholder text From 3ca3f254925603f953163ad9f7d694b18865cd13 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Thu, 30 Nov 2023 23:25:28 +0100 Subject: [PATCH 1746/4092] Extract local method --- .../Visual/Online/TestSceneChatLink.cs | 45 +++++++++---------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs index 1ab7e3257f..a0fcdf4337 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs @@ -59,8 +59,6 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestLinksGeneral() { - int messageIndex = 0; - addMessageWithChecks("test!"); addMessageWithChecks("dev.ppy.sh!"); addMessageWithChecks("https://dev.ppy.sh!", expectedActions: LinkAction.External); @@ -95,36 +93,35 @@ namespace osu.Game.Tests.Visual.Online addMessageWithChecks("Join my #english or #japanese channels.", expectedActions: new[] { LinkAction.OpenChannel, LinkAction.OpenChannel }); addMessageWithChecks("Join my #english or #nonexistent #hashtag channels.", expectedActions: LinkAction.OpenChannel); addMessageWithChecks("Hello world\uD83D\uDE12(<--This is an emoji). There are more:\uD83D\uDE10\uD83D\uDE00,\uD83D\uDE20"); + } - void addMessageWithChecks(string text, bool isAction = false, bool isImportant = false, params LinkAction[] expectedActions) + private void addMessageWithChecks(string text, bool isAction = false, bool isImportant = false, params LinkAction[] expectedActions) + { + ChatLine newLine = null; + + AddStep("add message", () => { - ChatLine newLine = null; - int index = messageIndex++; + newLine = new ChatLine(new DummyMessage(text, isAction, isImportant)); + textContainer.Add(newLine); + }); - AddStep("add message", () => - { - newLine = new ChatLine(new DummyMessage(text, isAction, isImportant, index)); - textContainer.Add(newLine); - }); + AddAssert($"msg has the right action", () => newLine.Message.Links.Select(l => l.Action), () => Is.EqualTo(expectedActions)); + //AddAssert($"msg is " + (isAction ? "italic" : "not italic"), () => newLine.ContentFlow.Any() && isAction == isItalic()); + AddAssert($"msg shows {expectedActions.Length} link(s)", isShowingLinks); - AddAssert($"msg #{index} has the right action", () => newLine.Message.Links.Select(l => l.Action), () => Is.EqualTo(expectedActions)); - //AddAssert($"msg #{index} is " + (isAction ? "italic" : "not italic"), () => newLine.ContentFlow.Any() && isAction == isItalic()); - AddAssert($"msg #{index} shows {expectedActions.Length} link(s)", isShowingLinks); + //bool isItalic() => newLine.ContentFlow.Where(d => d is OsuSpriteText).Cast().All(sprite => sprite.Font.Italics); - //bool isItalic() => newLine.ContentFlow.Where(d => d is OsuSpriteText).Cast().All(sprite => sprite.Font.Italics); + bool isShowingLinks() + { + bool hasBackground = !string.IsNullOrEmpty(newLine.Message.Sender.Colour); - bool isShowingLinks() - { - bool hasBackground = !string.IsNullOrEmpty(newLine.Message.Sender.Colour); + Color4 textColour = isAction && hasBackground ? Color4Extensions.FromHex(newLine.Message.Sender.Colour) : Color4.White; - Color4 textColour = isAction && hasBackground ? Color4Extensions.FromHex(newLine.Message.Sender.Colour) : Color4.White; + var linkCompilers = newLine.DrawableContentFlow.Where(d => d is DrawableLinkCompiler).ToList(); + var linkSprites = linkCompilers.SelectMany(comp => ((DrawableLinkCompiler)comp).Parts); - var linkCompilers = newLine.DrawableContentFlow.Where(d => d is DrawableLinkCompiler).ToList(); - var linkSprites = linkCompilers.SelectMany(comp => ((DrawableLinkCompiler)comp).Parts); - - return linkSprites.All(d => d.Colour == linkColour) - && newLine.DrawableContentFlow.Except(linkSprites.Concat(linkCompilers)).All(d => d.Colour == textColour); - } + return linkSprites.All(d => d.Colour == linkColour) + && newLine.DrawableContentFlow.Except(linkSprites.Concat(linkCompilers)).All(d => d.Colour == textColour); } } From 52ffbcc6db7e21eca7f49ac6d124b8b01c12ed4d Mon Sep 17 00:00:00 2001 From: Susko3 Date: Thu, 30 Nov 2023 23:29:22 +0100 Subject: [PATCH 1747/4092] Move special cases to `TestCase`'d test --- osu.Game.Tests/Visual/Online/TestSceneChatLink.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs index a0fcdf4337..52eebeb9ce 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs @@ -71,8 +71,6 @@ namespace osu.Game.Tests.Visual.Online addMessageWithChecks("[https://dev.ppy.sh/home New site] (new link format)", expectedActions: LinkAction.External); addMessageWithChecks("[osu forums](https://dev.ppy.sh/forum) (new link format 2)", expectedActions: LinkAction.External); addMessageWithChecks("[https://dev.ppy.sh/home This is only a link to the new osu webpage but this is supposed to test word wrap.]", expectedActions: LinkAction.External); - addMessageWithChecks("is now listening to [https://dev.ppy.sh/s/93523 IMAGE -MATERIAL- ]", isAction: true, expectedActions: LinkAction.OpenBeatmapSet); - addMessageWithChecks("is now playing [https://dev.ppy.sh/b/252238 IMAGE -MATERIAL- ]", isAction: true, expectedActions: LinkAction.OpenBeatmap); addMessageWithChecks("Let's (try)[https://dev.ppy.sh/home] [https://dev.ppy.sh/b/252238 multiple links] https://dev.ppy.sh/home", expectedActions: new[] { LinkAction.External, LinkAction.OpenBeatmap, LinkAction.External }); addMessageWithChecks("[https://dev.ppy.sh/home New link format with escaped [and \\[ paired] braces]", expectedActions: LinkAction.External); @@ -81,9 +79,6 @@ namespace osu.Game.Tests.Visual.Online expectedActions: new[] { LinkAction.External, LinkAction.OpenWiki }); // note that there's 0 links here (they get removed if a channel is not found) addMessageWithChecks("#lobby or #osu would be blue (and work) in the ChatDisplay test (when a proper ChatOverlay is present)."); - addMessageWithChecks("I am important!", isAction: false, isImportant: true); - addMessageWithChecks("feels important", isAction: true, isImportant: true); - addMessageWithChecks("likes to post this [https://dev.ppy.sh/home link].", isAction: true, isImportant: true, expectedActions: LinkAction.External); addMessageWithChecks("Join my multiplayer game osump://12346.", expectedActions: LinkAction.JoinMultiplayerMatch); addMessageWithChecks("Join my multiplayer gameosump://12346.", expectedActions: LinkAction.JoinMultiplayerMatch); addMessageWithChecks("Join my [multiplayer game](osump://12346).", expectedActions: LinkAction.JoinMultiplayerMatch); @@ -95,6 +90,16 @@ namespace osu.Game.Tests.Visual.Online addMessageWithChecks("Hello world\uD83D\uDE12(<--This is an emoji). There are more:\uD83D\uDE10\uD83D\uDE00,\uD83D\uDE20"); } + [TestCase("is now listening to [https://dev.ppy.sh/s/93523 IMAGE -MATERIAL- ]", true, false, LinkAction.OpenBeatmapSet)] + [TestCase("is now playing [https://dev.ppy.sh/b/252238 IMAGE -MATERIAL- ]", true, false, LinkAction.OpenBeatmap)] + [TestCase("I am important!", false, true)] + [TestCase("feels important", true, true)] + [TestCase("likes to post this [https://dev.ppy.sh/home link].", true, true, LinkAction.External)] + public void TestActionAndImportantLinks(string text, bool isAction, bool isImportant, params LinkAction[] expectedActions) + { + addMessageWithChecks(text, isAction, isImportant, expectedActions); + } + private void addMessageWithChecks(string text, bool isAction = false, bool isImportant = false, params LinkAction[] expectedActions) { ChatLine newLine = null; From 0520ac265fa57bbbe7f07dd9e63a966f332414d8 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Thu, 30 Nov 2023 23:34:43 +0100 Subject: [PATCH 1748/4092] Move to `TestCase`-based test --- .../Visual/Online/TestSceneChatLink.cs | 59 +++++++++---------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs index 52eebeb9ce..ae94d1887e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs @@ -56,38 +56,35 @@ namespace osu.Game.Tests.Visual.Online textContainer.Clear(); }); - [Test] - public void TestLinksGeneral() + [TestCase("test!")] + [TestCase("dev.ppy.sh!")] + [TestCase("https://dev.ppy.sh!", LinkAction.External)] + [TestCase("http://dev.ppy.sh!", LinkAction.External)] + [TestCase("forgothttps://dev.ppy.sh!", LinkAction.External)] + [TestCase("forgothttp://dev.ppy.sh!", LinkAction.External)] + [TestCase("00:12:345 (1,2) - Test?", LinkAction.OpenEditorTimestamp)] + [TestCase("Wiki link for tasty [[Performance Points]]", LinkAction.OpenWiki)] + [TestCase("(osu forums)[https://dev.ppy.sh/forum] (old link format)", LinkAction.External)] + [TestCase("[https://dev.ppy.sh/home New site] (new link format)", LinkAction.External)] + [TestCase("[osu forums](https://dev.ppy.sh/forum) (new link format 2)", LinkAction.External)] + [TestCase("[https://dev.ppy.sh/home This is only a link to the new osu webpage but this is supposed to test word wrap.]", LinkAction.External)] + [TestCase("Let's (try)[https://dev.ppy.sh/home] [https://dev.ppy.sh/b/252238 multiple links] https://dev.ppy.sh/home", LinkAction.External, LinkAction.OpenBeatmap, LinkAction.External)] + [TestCase("[https://dev.ppy.sh/home New link format with escaped [and \\[ paired] braces]", LinkAction.External)] + [TestCase("[Markdown link format with escaped [and \\[ paired] braces](https://dev.ppy.sh/home)", LinkAction.External)] + [TestCase("(Old link format with escaped (and \\( paired) parentheses)[https://dev.ppy.sh/home] and [[also a rogue wiki link]]", LinkAction.External, LinkAction.OpenWiki)] + [TestCase("#lobby or #osu would be blue (and work) in the ChatDisplay test (when a proper ChatOverlay is present).")] // note that there's 0 links here (they get removed if a channel is not found) + [TestCase("Join my multiplayer game osump://12346.", LinkAction.JoinMultiplayerMatch)] + [TestCase("Join my multiplayer gameosump://12346.", LinkAction.JoinMultiplayerMatch)] + [TestCase("Join my [multiplayer game](osump://12346).", LinkAction.JoinMultiplayerMatch)] + [TestCase($"Join my [#english]({OsuGameBase.OSU_PROTOCOL}chan/#english).", LinkAction.OpenChannel)] + [TestCase($"Join my {OsuGameBase.OSU_PROTOCOL}chan/#english.", LinkAction.OpenChannel)] + [TestCase($"Join my{OsuGameBase.OSU_PROTOCOL}chan/#english.", LinkAction.OpenChannel)] + [TestCase("Join my #english or #japanese channels.", LinkAction.OpenChannel, LinkAction.OpenChannel)] + [TestCase("Join my #english or #nonexistent #hashtag channels.", LinkAction.OpenChannel)] + [TestCase("Hello world\uD83D\uDE12(<--This is an emoji). There are more:\uD83D\uDE10\uD83D\uDE00,\uD83D\uDE20")] + public void TestLinksGeneral(string text, params LinkAction[] actions) { - addMessageWithChecks("test!"); - addMessageWithChecks("dev.ppy.sh!"); - addMessageWithChecks("https://dev.ppy.sh!", expectedActions: LinkAction.External); - addMessageWithChecks("http://dev.ppy.sh!", expectedActions: LinkAction.External); - addMessageWithChecks("forgothttps://dev.ppy.sh!", expectedActions: LinkAction.External); - addMessageWithChecks("forgothttp://dev.ppy.sh!", expectedActions: LinkAction.External); - addMessageWithChecks("00:12:345 (1,2) - Test?", expectedActions: LinkAction.OpenEditorTimestamp); - addMessageWithChecks("Wiki link for tasty [[Performance Points]]", expectedActions: LinkAction.OpenWiki); - addMessageWithChecks("(osu forums)[https://dev.ppy.sh/forum] (old link format)", expectedActions: LinkAction.External); - addMessageWithChecks("[https://dev.ppy.sh/home New site] (new link format)", expectedActions: LinkAction.External); - addMessageWithChecks("[osu forums](https://dev.ppy.sh/forum) (new link format 2)", expectedActions: LinkAction.External); - addMessageWithChecks("[https://dev.ppy.sh/home This is only a link to the new osu webpage but this is supposed to test word wrap.]", expectedActions: LinkAction.External); - addMessageWithChecks("Let's (try)[https://dev.ppy.sh/home] [https://dev.ppy.sh/b/252238 multiple links] https://dev.ppy.sh/home", - expectedActions: new[] { LinkAction.External, LinkAction.OpenBeatmap, LinkAction.External }); - addMessageWithChecks("[https://dev.ppy.sh/home New link format with escaped [and \\[ paired] braces]", expectedActions: LinkAction.External); - addMessageWithChecks("[Markdown link format with escaped [and \\[ paired] braces](https://dev.ppy.sh/home)", expectedActions: LinkAction.External); - addMessageWithChecks("(Old link format with escaped (and \\( paired) parentheses)[https://dev.ppy.sh/home] and [[also a rogue wiki link]]", - expectedActions: new[] { LinkAction.External, LinkAction.OpenWiki }); - // note that there's 0 links here (they get removed if a channel is not found) - addMessageWithChecks("#lobby or #osu would be blue (and work) in the ChatDisplay test (when a proper ChatOverlay is present)."); - addMessageWithChecks("Join my multiplayer game osump://12346.", expectedActions: LinkAction.JoinMultiplayerMatch); - addMessageWithChecks("Join my multiplayer gameosump://12346.", expectedActions: LinkAction.JoinMultiplayerMatch); - addMessageWithChecks("Join my [multiplayer game](osump://12346).", expectedActions: LinkAction.JoinMultiplayerMatch); - addMessageWithChecks($"Join my [#english]({OsuGameBase.OSU_PROTOCOL}chan/#english).", expectedActions: LinkAction.OpenChannel); - addMessageWithChecks($"Join my {OsuGameBase.OSU_PROTOCOL}chan/#english.", expectedActions: LinkAction.OpenChannel); - addMessageWithChecks($"Join my{OsuGameBase.OSU_PROTOCOL}chan/#english.", expectedActions: LinkAction.OpenChannel); - addMessageWithChecks("Join my #english or #japanese channels.", expectedActions: new[] { LinkAction.OpenChannel, LinkAction.OpenChannel }); - addMessageWithChecks("Join my #english or #nonexistent #hashtag channels.", expectedActions: LinkAction.OpenChannel); - addMessageWithChecks("Hello world\uD83D\uDE12(<--This is an emoji). There are more:\uD83D\uDE10\uD83D\uDE00,\uD83D\uDE20"); + addMessageWithChecks(text, expectedActions: actions); } [TestCase("is now listening to [https://dev.ppy.sh/s/93523 IMAGE -MATERIAL- ]", true, false, LinkAction.OpenBeatmapSet)] From 7b1ccb38cb8070cdcf576b4e14c9437589f74867 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Thu, 30 Nov 2023 23:35:10 +0100 Subject: [PATCH 1749/4092] Remove redundant string interpolation --- osu.Game.Tests/Visual/Online/TestSceneChatLink.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs index ae94d1887e..f4dd4289c3 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs @@ -107,8 +107,8 @@ namespace osu.Game.Tests.Visual.Online textContainer.Add(newLine); }); - AddAssert($"msg has the right action", () => newLine.Message.Links.Select(l => l.Action), () => Is.EqualTo(expectedActions)); - //AddAssert($"msg is " + (isAction ? "italic" : "not italic"), () => newLine.ContentFlow.Any() && isAction == isItalic()); + AddAssert("msg has the right action", () => newLine.Message.Links.Select(l => l.Action), () => Is.EqualTo(expectedActions)); + //AddAssert("msg is " + (isAction ? "italic" : "not italic"), () => newLine.ContentFlow.Any() && isAction == isItalic()); AddAssert($"msg shows {expectedActions.Length} link(s)", isShowingLinks); //bool isItalic() => newLine.ContentFlow.Where(d => d is OsuSpriteText).Cast().All(sprite => sprite.Font.Italics); From 21fedff54fbc675947022b61138ca49b90e49f21 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Fri, 1 Dec 2023 00:29:37 +0100 Subject: [PATCH 1750/4092] Check number of links shown --- osu.Game.Tests/Visual/Online/TestSceneChatLink.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs index f4dd4289c3..5947c01b96 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs @@ -123,7 +123,8 @@ namespace osu.Game.Tests.Visual.Online var linkSprites = linkCompilers.SelectMany(comp => ((DrawableLinkCompiler)comp).Parts); return linkSprites.All(d => d.Colour == linkColour) - && newLine.DrawableContentFlow.Except(linkSprites.Concat(linkCompilers)).All(d => d.Colour == textColour); + && newLine.DrawableContentFlow.Except(linkSprites.Concat(linkCompilers)).All(d => d.Colour == textColour) + && linkCompilers.Count == expectedActions.Length; } } From fba6349c6528793328d3125222b0328f253764fa Mon Sep 17 00:00:00 2001 From: Susko3 Date: Fri, 1 Dec 2023 00:36:40 +0100 Subject: [PATCH 1751/4092] Remove unused properties --- osu.Game.Tests/Visual/Online/TestSceneChatLink.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs index 5947c01b96..b9a65f734f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs @@ -167,21 +167,12 @@ namespace osu.Game.Tests.Visual.Online { private static long messageCounter; - internal static readonly APIUser TEST_SENDER_BACKGROUND = new APIUser - { - Username = @"i-am-important", - Id = 42, - Colour = "#250cc9", - }; - internal static readonly APIUser TEST_SENDER = new APIUser { Username = @"Somebody", Id = 1, }; - public new DateTimeOffset Timestamp = DateTimeOffset.Now; - public DummyMessage(string text, bool isAction = false, bool isImportant = false, int number = 0) : base(messageCounter++) { From 1a33bfbb3a8d0b298651844c4b9ea4bbff141629 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Fri, 1 Dec 2023 00:37:16 +0100 Subject: [PATCH 1752/4092] Enable NRT --- osu.Game.Tests/Visual/Online/TestSceneChatLink.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs index b9a65f734f..8d4ff82303 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using NUnit.Framework; @@ -99,7 +97,7 @@ namespace osu.Game.Tests.Visual.Online private void addMessageWithChecks(string text, bool isAction = false, bool isImportant = false, params LinkAction[] expectedActions) { - ChatLine newLine = null; + ChatLine newLine = null!; AddStep("add message", () => { @@ -138,7 +136,7 @@ namespace osu.Game.Tests.Visual.Online addEchoWithWait("[https://dev.ppy.sh/forum let's try multiple words too!]"); addEchoWithWait("(long loading times! clickable while loading?)[https://dev.ppy.sh/home]", null, 5000); - void addEchoWithWait(string text, string completeText = null, double delay = 250) + void addEchoWithWait(string text, string? completeText = null, double delay = 250) { int index = messageIndex++; From 7f9ae55f5eab245e2051d0a85744031289424ad4 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Fri, 1 Dec 2023 00:45:35 +0100 Subject: [PATCH 1753/4092] Add passing tests --- osu.Game.Tests/Visual/Online/TestSceneChatLink.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs index 8d4ff82303..2dde1447e4 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs @@ -60,7 +60,9 @@ namespace osu.Game.Tests.Visual.Online [TestCase("http://dev.ppy.sh!", LinkAction.External)] [TestCase("forgothttps://dev.ppy.sh!", LinkAction.External)] [TestCase("forgothttp://dev.ppy.sh!", LinkAction.External)] + [TestCase("00:12:345 - Test?", LinkAction.OpenEditorTimestamp)] [TestCase("00:12:345 (1,2) - Test?", LinkAction.OpenEditorTimestamp)] + [TestCase($"{OsuGameBase.OSU_PROTOCOL}edit/00:12:345 - Test?", LinkAction.OpenEditorTimestamp)] [TestCase("Wiki link for tasty [[Performance Points]]", LinkAction.OpenWiki)] [TestCase("(osu forums)[https://dev.ppy.sh/forum] (old link format)", LinkAction.External)] [TestCase("[https://dev.ppy.sh/home New site] (new link format)", LinkAction.External)] From c395ae2460fbbc7e8329060732fa132f1df7ca01 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Fri, 1 Dec 2023 00:49:21 +0100 Subject: [PATCH 1754/4092] Add failing tests They throws `ArgumentOutOfRangeException` on the first drawable Update() --- osu.Game.Tests/Visual/Online/TestSceneChatLink.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs index 2dde1447e4..e77ff5c1cd 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs @@ -63,6 +63,8 @@ namespace osu.Game.Tests.Visual.Online [TestCase("00:12:345 - Test?", LinkAction.OpenEditorTimestamp)] [TestCase("00:12:345 (1,2) - Test?", LinkAction.OpenEditorTimestamp)] [TestCase($"{OsuGameBase.OSU_PROTOCOL}edit/00:12:345 - Test?", LinkAction.OpenEditorTimestamp)] + [TestCase($"{OsuGameBase.OSU_PROTOCOL}edit/00:12:345 (1,2) - Test?", LinkAction.OpenEditorTimestamp)] + [TestCase($"{OsuGameBase.OSU_PROTOCOL}00:12:345 - not an editor timestamp", LinkAction.External)] [TestCase("Wiki link for tasty [[Performance Points]]", LinkAction.OpenWiki)] [TestCase("(osu forums)[https://dev.ppy.sh/forum] (old link format)", LinkAction.External)] [TestCase("[https://dev.ppy.sh/home New site] (new link format)", LinkAction.External)] From 152c7e513ea98e0e25f3e3461c81307b6bd61376 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Thu, 30 Nov 2023 22:59:02 +0100 Subject: [PATCH 1755/4092] Ignore overlapping links instead of crashing --- osu.Game/Graphics/Containers/LinkFlowContainer.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 40e883f8ac..aa72996fff 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -12,6 +12,7 @@ using System.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; +using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Online; using osu.Game.Users; @@ -47,9 +48,16 @@ namespace osu.Game.Graphics.Containers foreach (var link in links) { + string displayText = text.Substring(link.Index, link.Length); + + if (previousLinkEnd > link.Index) + { + Logger.Log($@"Link ""{link.Url}"" with text ""{displayText}"" overlaps previous link, ignoring."); + continue; + } + AddText(text[previousLinkEnd..link.Index]); - string displayText = text.Substring(link.Index, link.Length); object linkArgument = link.Argument; string tooltip = displayText == link.Url ? null : link.Url; From 30bdd2d4c0eaa6922ced5f44c589df93d50c9518 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Fri, 1 Dec 2023 01:07:23 +0100 Subject: [PATCH 1756/4092] Extract `Overlaps()` logic to accept generic index and length --- osu.Game/Online/Chat/MessageFormatter.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 9a194dba47..078af667d1 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -364,7 +364,9 @@ namespace osu.Game.Online.Chat Argument = argument; } - public bool Overlaps(Link otherLink) => Index < otherLink.Index + otherLink.Length && otherLink.Index < Index + Length; + public bool Overlaps(Link otherLink) => Overlaps(otherLink.Index, otherLink.Length); + + public bool Overlaps(int index, int length) => Index < index + length && index < Index + Length; public int CompareTo(Link? otherLink) => Index > otherLink?.Index ? 1 : -1; } From d3517998cff60db3d80d38a9fc71460ce1707258 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Fri, 1 Dec 2023 01:08:22 +0100 Subject: [PATCH 1757/4092] Use common `Overlaps()` logic This actually fixes the problem and makes the tests pass --- osu.Game/Online/Chat/MessageFormatter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 078af667d1..c5256c3c74 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -85,8 +85,8 @@ namespace osu.Game.Online.Chat if (escapeChars != null) displayText = escapeChars.Aggregate(displayText, (current, c) => current.Replace($"\\{c}", c.ToString())); - // Check for encapsulated links - if (result.Links.Find(l => (l.Index <= index && l.Index + l.Length >= index + m.Length) || (index <= l.Index && index + m.Length >= l.Index + l.Length)) == null) + // Check for overlapping links + if (!result.Links.Exists(l => l.Overlaps(index, m.Length))) { result.Text = result.Text.Remove(index, m.Length).Insert(index, displayText); From 894c31753b1c1737ef2dd7ecdf7440d9ef739cee Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 1 Dec 2023 15:31:06 +0900 Subject: [PATCH 1758/4092] Add initial support for aborting multiplayer games --- .../Multiplayer/IMultiplayerRoomServer.cs | 5 +++ .../Online/Multiplayer/MultiplayerClient.cs | 2 ++ .../Multiplayer/OnlineMultiplayerClient.cs | 10 ++++++ .../Multiplayer/Match/ConfirmAbortDialog.cs | 33 +++++++++++++++++++ .../Multiplayer/Match/MatchStartControl.cs | 27 +++++++++++++-- .../Match/MultiplayerReadyButton.cs | 31 ++++++++++++----- .../Multiplayer/TestMultiplayerClient.cs | 6 ++++ 7 files changed, 104 insertions(+), 10 deletions(-) create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Match/ConfirmAbortDialog.cs diff --git a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs index b7a608581c..15a8b42457 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs @@ -82,6 +82,11 @@ namespace osu.Game.Online.Multiplayer ///
Task AbortGameplay(); + /// + /// Real. + /// + Task AbortGameplayReal(); + /// /// Adds an item to the playlist. /// diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 79f46c2095..140380d679 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -374,6 +374,8 @@ namespace osu.Game.Online.Multiplayer public abstract Task AbortGameplay(); + public abstract Task AbortGameplayReal(); + public abstract Task AddPlaylistItem(MultiplayerPlaylistItem item); public abstract Task EditPlaylistItem(MultiplayerPlaylistItem item); diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index e400132693..47f4205dfd 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -226,6 +226,16 @@ namespace osu.Game.Online.Multiplayer return connection.InvokeAsync(nameof(IMultiplayerServer.AbortGameplay)); } + public override Task AbortGameplayReal() + { + if (!IsConnected.Value) + return Task.CompletedTask; + + Debug.Assert(connection != null); + + return connection.InvokeAsync(nameof(IMultiplayerServer.AbortGameplayReal)); + } + public override Task AddPlaylistItem(MultiplayerPlaylistItem item) { if (!IsConnected.Value) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ConfirmAbortDialog.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ConfirmAbortDialog.cs new file mode 100644 index 0000000000..8aca96a918 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ConfirmAbortDialog.cs @@ -0,0 +1,33 @@ +// 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.Graphics.Sprites; +using osu.Game.Overlays.Dialog; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match +{ + public partial class ConfirmAbortDialog : PopupDialog + { + public ConfirmAbortDialog(Action onConfirm, Action onCancel) + { + HeaderText = "Are you sure you want to go abort the match?"; + + Icon = FontAwesome.Solid.ExclamationTriangle; + + Buttons = new PopupDialogButton[] + { + new PopupDialogDangerousButton + { + Text = @"Yes", + Action = onConfirm + }, + new PopupDialogCancelButton + { + Text = @"No I didn't mean to", + Action = onCancel + }, + }; + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs index 44e18dd2bb..d44878f7c3 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs @@ -16,6 +16,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Threading; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.Countdown; +using osu.Game.Overlays; using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match @@ -28,6 +29,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match [CanBeNull] private IDisposable clickOperation; + [Resolved(canBeNull: true)] + private IDialogOverlay dialogOverlay { get; set; } + private Sample sampleReady; private Sample sampleReadyAll; private Sample sampleUnready; @@ -109,8 +113,23 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match Debug.Assert(clickOperation == null); clickOperation = ongoingOperationTracker.BeginOperation(); - if (isReady() && Client.IsHost && !Room.ActiveCountdowns.Any(c => c is MatchStartCountdown)) - startMatch(); + if (Client.IsHost) + { + if (Room.State == MultiplayerRoomState.Open) + { + if (isReady() && !Room.ActiveCountdowns.Any(c => c is MatchStartCountdown)) + startMatch(); + else + toggleReady(); + } + else + { + if (dialogOverlay == null) + abortMatch(); + else + dialogOverlay.Push(new ConfirmAbortDialog(abortMatch, endOperation)); + } + } else toggleReady(); @@ -128,6 +147,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match // gameplay was not started due to an exception; unblock button. endOperation(); }); + + void abortMatch() => Client.AbortGameplayReal().FireAndForget(endOperation, _ => endOperation()); } private void startCountdown(TimeSpan duration) @@ -198,6 +219,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match if (localUser?.State == MultiplayerUserState.Spectating) readyButton.Enabled.Value &= Client.IsHost && newCountReady > 0 && !Room.ActiveCountdowns.Any(c => c is MatchStartCountdown); + readyButton.Enabled.Value = true; + if (newCountReady == countReady) return; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index 1be573bdb8..8a9d027469 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -158,7 +158,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match Text = room.Host?.Equals(localUser) == true ? $"Start match {countText}" : $"Waiting for host... {countText}"; + break; + case MultiplayerUserState.Idle: + if (room.State == MultiplayerRoomState.Open || room.Host?.Equals(localUser) != true) + { + Text = "Ready"; + break; + } + + Text = "Abort!"; break; } } @@ -204,17 +213,23 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match setYellow(); break; + + case MultiplayerUserState.Idle: + if (room.State == MultiplayerRoomState.Open || room.Host?.Equals(localUser) != true) + { + setGreen(); + break; + } + + setRed(); + break; } - void setYellow() - { - BackgroundColour = colours.YellowDark; - } + void setYellow() => BackgroundColour = colours.YellowDark; - void setGreen() - { - BackgroundColour = colours.Green; - } + void setGreen() => BackgroundColour = colours.Green; + + void setRed() => BackgroundColour = colours.Red; } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 577104db45..a73c3a72a2 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -396,6 +396,12 @@ namespace osu.Game.Tests.Visual.Multiplayer return Task.CompletedTask; } + public override Task AbortGameplayReal() + { + // Todo: + return Task.CompletedTask; + } + public async Task AddUserPlaylistItem(int userId, MultiplayerPlaylistItem item) { Debug.Assert(ServerRoom != null); From a94180c8c6cde22814da67de2ff0e78be9285609 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 1 Dec 2023 18:26:59 +0900 Subject: [PATCH 1759/4092] Rename LoadAborted -> GameplayAborted, AbortGameplayReal -> AbortMatch --- .../Online/Multiplayer/GameplayAbortReason.cs | 11 +++++++++++ .../Online/Multiplayer/IMultiplayerClient.cs | 11 ++++++----- .../Online/Multiplayer/IMultiplayerRoomServer.cs | 10 +++++----- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 10 +++++----- .../Multiplayer/OnlineMultiplayerClient.cs | 6 +++--- .../Multiplayer/Match/MatchStartControl.cs | 2 +- .../OnlinePlay/Multiplayer/Multiplayer.cs | 16 +++++++++++++--- .../Visual/Multiplayer/TestMultiplayerClient.cs | 2 +- 8 files changed, 45 insertions(+), 23 deletions(-) create mode 100644 osu.Game/Online/Multiplayer/GameplayAbortReason.cs diff --git a/osu.Game/Online/Multiplayer/GameplayAbortReason.cs b/osu.Game/Online/Multiplayer/GameplayAbortReason.cs new file mode 100644 index 0000000000..15151ea68b --- /dev/null +++ b/osu.Game/Online/Multiplayer/GameplayAbortReason.cs @@ -0,0 +1,11 @@ +// 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.Online.Multiplayer +{ + public enum GameplayAbortReason + { + LoadTookTooLong, + HostAbortedTheMatch + } +} diff --git a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs index a5fa49a94b..0452d8b79c 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs @@ -107,17 +107,18 @@ namespace osu.Game.Online.Multiplayer ///
Task LoadRequested(); - /// - /// Signals that loading of gameplay is to be aborted. - /// - Task LoadAborted(); - /// /// Signals that gameplay has started. /// All users in the or states should begin gameplay as soon as possible. /// Task GameplayStarted(); + /// + /// Signals that gameplay has been aborted. + /// + /// The reason why gameplay was aborted. + Task GameplayAborted(GameplayAbortReason reason); + /// /// Signals that the match has ended, all players have finished and results are ready to be displayed. /// diff --git a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs index 15a8b42457..55f00b447f 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs @@ -77,16 +77,16 @@ namespace osu.Game.Online.Multiplayer /// If an attempt to start the game occurs when the game's (or users') state disallows it. Task StartMatch(); + /// + /// As the host of a room, aborts an on-going match. + /// + Task AbortMatch(); + /// /// Aborts an ongoing gameplay load. /// Task AbortGameplay(); - /// - /// Real. - /// - Task AbortGameplayReal(); - /// /// Adds an item to the playlist. /// diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 140380d679..bbf0e3697a 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -73,9 +73,9 @@ namespace osu.Game.Online.Multiplayer public virtual event Action? LoadRequested; /// - /// Invoked when the multiplayer server requests loading of play to be aborted. + /// Invoked when the multiplayer server requests gameplay to be aborted. /// - public event Action? LoadAborted; + public event Action? GameplayAborted; /// /// Invoked when the multiplayer server requests gameplay to be started. @@ -374,7 +374,7 @@ namespace osu.Game.Online.Multiplayer public abstract Task AbortGameplay(); - public abstract Task AbortGameplayReal(); + public abstract Task AbortMatch(); public abstract Task AddPlaylistItem(MultiplayerPlaylistItem item); @@ -684,14 +684,14 @@ namespace osu.Game.Online.Multiplayer return Task.CompletedTask; } - Task IMultiplayerClient.LoadAborted() + Task IMultiplayerClient.GameplayAborted(GameplayAbortReason reason) { Scheduler.Add(() => { if (Room == null) return; - LoadAborted?.Invoke(); + GameplayAborted?.Invoke(reason); }, false); return Task.CompletedTask; diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index 47f4205dfd..40436d730e 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -58,7 +58,7 @@ namespace osu.Game.Online.Multiplayer connection.On(nameof(IMultiplayerClient.UserStateChanged), ((IMultiplayerClient)this).UserStateChanged); connection.On(nameof(IMultiplayerClient.LoadRequested), ((IMultiplayerClient)this).LoadRequested); connection.On(nameof(IMultiplayerClient.GameplayStarted), ((IMultiplayerClient)this).GameplayStarted); - connection.On(nameof(IMultiplayerClient.LoadAborted), ((IMultiplayerClient)this).LoadAborted); + connection.On(nameof(IMultiplayerClient.GameplayAborted), ((IMultiplayerClient)this).GameplayAborted); connection.On(nameof(IMultiplayerClient.ResultsReady), ((IMultiplayerClient)this).ResultsReady); connection.On>(nameof(IMultiplayerClient.UserModsChanged), ((IMultiplayerClient)this).UserModsChanged); connection.On(nameof(IMultiplayerClient.UserBeatmapAvailabilityChanged), ((IMultiplayerClient)this).UserBeatmapAvailabilityChanged); @@ -226,14 +226,14 @@ namespace osu.Game.Online.Multiplayer return connection.InvokeAsync(nameof(IMultiplayerServer.AbortGameplay)); } - public override Task AbortGameplayReal() + public override Task AbortMatch() { if (!IsConnected.Value) return Task.CompletedTask; Debug.Assert(connection != null); - return connection.InvokeAsync(nameof(IMultiplayerServer.AbortGameplayReal)); + return connection.InvokeAsync(nameof(IMultiplayerServer.AbortMatch)); } public override Task AddPlaylistItem(MultiplayerPlaylistItem item) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs index d44878f7c3..8ca5d61ab4 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs @@ -148,7 +148,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match endOperation(); }); - void abortMatch() => Client.AbortGameplayReal().FireAndForget(endOperation, _ => endOperation()); + void abortMatch() => Client.AbortMatch().FireAndForget(endOperation, _ => endOperation()); } private void startCountdown(TimeSpan duration) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs index edf5ce276a..7d27725775 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs @@ -23,7 +23,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer base.LoadComplete(); client.RoomUpdated += onRoomUpdated; - client.LoadAborted += onLoadAborted; + client.GameplayAborted += onGameplayAborted; onRoomUpdated(); } @@ -39,12 +39,22 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer transitionFromResults(); } - private void onLoadAborted() + private void onGameplayAborted(GameplayAbortReason reason) { // If the server aborts gameplay for this user (due to loading too slow), exit gameplay screens. if (!this.IsCurrentScreen()) { - Logger.Log("Gameplay aborted because loading the beatmap took too long.", LoggingTarget.Runtime, LogLevel.Important); + switch (reason) + { + case GameplayAbortReason.LoadTookTooLong: + Logger.Log("Gameplay aborted because loading the beatmap took too long.", LoggingTarget.Runtime, LogLevel.Important); + break; + + case GameplayAbortReason.HostAbortedTheMatch: + Logger.Log("The host aborted the match.", LoggingTarget.Runtime, LogLevel.Important); + break; + } + this.MakeCurrent(); } } diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index a73c3a72a2..3af8f9c5db 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -396,7 +396,7 @@ namespace osu.Game.Tests.Visual.Multiplayer return Task.CompletedTask; } - public override Task AbortGameplayReal() + public override Task AbortMatch() { // Todo: return Task.CompletedTask; From 15c9416244a47e9d5fb713a99ddd9ce77b5403bd Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 1 Dec 2023 18:47:40 +0900 Subject: [PATCH 1760/4092] Rename method --- .../Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs index 8ca5d61ab4..a21c85e316 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs @@ -60,7 +60,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { RelativeSizeAxes = Axes.Both, Size = Vector2.One, - Action = onReadyClick, + Action = onReadyButtonClick, }, countdownButton = new MultiplayerCountdownButton { @@ -105,7 +105,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match endOperation(); } - private void onReadyClick() + private void onReadyButtonClick() { if (Room == null) return; From 1b0fc8ca9d8740d9c61aa20dcbace2f72357cd96 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 1 Dec 2023 19:02:27 +0900 Subject: [PATCH 1761/4092] Refactor --- .../Multiplayer/Match/ConfirmAbortDialog.cs | 2 +- .../Match/MultiplayerReadyButton.cs | 24 ++++++------------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ConfirmAbortDialog.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ConfirmAbortDialog.cs index 8aca96a918..06f2b2c8f6 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ConfirmAbortDialog.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ConfirmAbortDialog.cs @@ -11,7 +11,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { public ConfirmAbortDialog(Action onConfirm, Action onCancel) { - HeaderText = "Are you sure you want to go abort the match?"; + HeaderText = "Are you sure you want to abort the match?"; Icon = FontAwesome.Solid.ExclamationTriangle; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index 8a9d027469..368e5210de 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -155,19 +155,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match case MultiplayerUserState.Spectating: case MultiplayerUserState.Ready: - Text = room.Host?.Equals(localUser) == true + Text = multiplayerClient.IsHost ? $"Start match {countText}" : $"Waiting for host... {countText}"; break; - case MultiplayerUserState.Idle: - if (room.State == MultiplayerRoomState.Open || room.Host?.Equals(localUser) != true) - { - Text = "Ready"; - break; - } - - Text = "Abort!"; + // Show the abort button for the host as long as gameplay is in progress. + case MultiplayerUserState when multiplayerClient.IsHost && room.State != MultiplayerRoomState.Open: + Text = "Abort the match"; break; } } @@ -207,20 +202,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match case MultiplayerUserState.Spectating: case MultiplayerUserState.Ready: - if (room?.Host?.Equals(localUser) == true && !room.ActiveCountdowns.Any(c => c is MatchStartCountdown)) + if (multiplayerClient.IsHost && !room.ActiveCountdowns.Any(c => c is MatchStartCountdown)) setGreen(); else setYellow(); break; - case MultiplayerUserState.Idle: - if (room.State == MultiplayerRoomState.Open || room.Host?.Equals(localUser) != true) - { - setGreen(); - break; - } - + // Show the abort button for the host as long as gameplay is in progress. + case MultiplayerUserState when multiplayerClient.IsHost && room.State != MultiplayerRoomState.Open: setRed(); break; } From e0eea07a3fa201cb227856213f7831524d635cef Mon Sep 17 00:00:00 2001 From: Susko3 Date: Fri, 1 Dec 2023 13:00:34 +0100 Subject: [PATCH 1762/4092] Request `READ_EXTERNAL_STORAGE` on older android versions --- osu.Android/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Android/AndroidManifest.xml b/osu.Android/AndroidManifest.xml index af102a1e4e..2fb4d1f850 100644 --- a/osu.Android/AndroidManifest.xml +++ b/osu.Android/AndroidManifest.xml @@ -5,4 +5,5 @@ + \ No newline at end of file From cdaff30aa6e69582d18d7034728c2c615cbb44fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 1 Dec 2023 13:24:51 +0100 Subject: [PATCH 1763/4092] 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 3b90b1675c..6609db3027 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 7e5c5be4ea..a6b3527466 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From d97ae8df6a916c75c1fca2c9ab80919039c9b3d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 1 Dec 2023 13:31:09 +0100 Subject: [PATCH 1764/4092] Remove commented out italic code It's a holdover from the Exo days (anyone remember those?) and hasn't been relevant for years, so why keep it. --- osu.Game.Tests/Visual/Online/TestSceneChatLink.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs index 8d4ff82303..af7cc003a5 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs @@ -106,11 +106,8 @@ namespace osu.Game.Tests.Visual.Online }); AddAssert("msg has the right action", () => newLine.Message.Links.Select(l => l.Action), () => Is.EqualTo(expectedActions)); - //AddAssert("msg is " + (isAction ? "italic" : "not italic"), () => newLine.ContentFlow.Any() && isAction == isItalic()); AddAssert($"msg shows {expectedActions.Length} link(s)", isShowingLinks); - //bool isItalic() => newLine.ContentFlow.Where(d => d is OsuSpriteText).Cast().All(sprite => sprite.Font.Italics); - bool isShowingLinks() { bool hasBackground = !string.IsNullOrEmpty(newLine.Message.Sender.Colour); From f3530a79b18817e3ca1fd005933add63ac75f82e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 1 Dec 2023 21:34:20 +0900 Subject: [PATCH 1765/4092] Add test --- .../Multiplayer/TestSceneMatchStartControl.cs | 25 +++++++++++++++++++ .../Multiplayer/TestMultiplayerClient.cs | 6 ++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs index 6d309078e6..f8719ba80c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs @@ -378,6 +378,31 @@ namespace osu.Game.Tests.Visual.Multiplayer }, users); } + [Test] + public void TestAbortMatch() + { + multiplayerClient.Setup(m => m.StartMatch()) + .Callback(() => + { + multiplayerClient.Raise(m => m.LoadRequested -= null); + multiplayerClient.Object.Room!.State = MultiplayerRoomState.WaitingForLoad; + + // The local user state doesn't really matter, so let's do the same as the base implementation for these tests. + changeUserState(localUser.UserID, MultiplayerUserState.Idle); + }); + + multiplayerClient.Setup(m => m.AbortMatch()) + .Callback(() => + { + multiplayerClient.Object.Room!.State = MultiplayerRoomState.Open; + raiseRoomUpdated(); + }); + + ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); + } + private void verifyGameplayStartFlow() { checkLocalUserState(MultiplayerUserState.Ready); diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 3af8f9c5db..4c3deac1d7 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -396,10 +396,10 @@ namespace osu.Game.Tests.Visual.Multiplayer return Task.CompletedTask; } - public override Task AbortMatch() + public override async Task AbortMatch() { - // Todo: - return Task.CompletedTask; + ChangeUserState(api.LocalUser.Value.Id, MultiplayerUserState.Idle); + await ((IMultiplayerClient)this).GameplayAborted(GameplayAbortReason.HostAbortedTheMatch).ConfigureAwait(false); } public async Task AddUserPlaylistItem(int userId, MultiplayerPlaylistItem item) From abb4c943a7f36a5e8fb4ada240cfb66ed433a9e0 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Fri, 1 Dec 2023 18:35:57 +0100 Subject: [PATCH 1766/4092] Rename to more readable names --- osu.Game/Online/Chat/MessageFormatter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index c5256c3c74..f055633d64 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -366,7 +366,7 @@ namespace osu.Game.Online.Chat public bool Overlaps(Link otherLink) => Overlaps(otherLink.Index, otherLink.Length); - public bool Overlaps(int index, int length) => Index < index + length && index < Index + Length; + public bool Overlaps(int otherIndex, int otherLength) => Index < otherIndex + otherLength && otherIndex < Index + Length; public int CompareTo(Link? otherLink) => Index > otherLink?.Index ? 1 : -1; } From 230278f2c94230803f11310b592dcbc15043274c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 3 Dec 2023 01:45:15 +0900 Subject: [PATCH 1767/4092] Once again remove Mania passive HP drain --- .../ManiaHealthProcessorTest.cs | 31 +++++++++++++++++++ .../Scoring/ManiaHealthProcessor.cs | 8 +++++ 2 files changed, 39 insertions(+) create mode 100644 osu.Game.Rulesets.Mania.Tests/ManiaHealthProcessorTest.cs diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaHealthProcessorTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaHealthProcessorTest.cs new file mode 100644 index 0000000000..315849f7de --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/ManiaHealthProcessorTest.cs @@ -0,0 +1,31 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Scoring; + +namespace osu.Game.Rulesets.Mania.Tests +{ + [TestFixture] + public class ManiaHealthProcessorTest + { + [Test] + public void TestNoDrain() + { + var processor = new ManiaHealthProcessor(0); + processor.ApplyBeatmap(new ManiaBeatmap(new StageDefinition(4)) + { + HitObjects = + { + new Note { StartTime = 0 }, + new Note { StartTime = 1000 }, + } + }); + + // No matter what, mania doesn't have passive HP drain. + Assert.That(processor.DrainRate, Is.Zero); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaHealthProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaHealthProcessor.cs index 183550eb7b..34b1787a71 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaHealthProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaHealthProcessor.cs @@ -15,6 +15,14 @@ namespace osu.Game.Rulesets.Mania.Scoring { } + protected override double ComputeDrainRate() + { + // Base call is run only to compute HP recovery. + base.ComputeDrainRate(); + + return 0; + } + protected override IEnumerable EnumerateTopLevelHitObjects() => Beatmap.HitObjects; protected override IEnumerable EnumerateNestedHitObjects(HitObject hitObject) => hitObject.NestedHitObjects; From dc588e6d56a8e88eb408d75f7b7d2cb1780e5ef9 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 3 Dec 2023 04:58:17 +0300 Subject: [PATCH 1768/4092] Implement OsuModDepth --- osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs | 163 ++++++++++++++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 3 +- 2 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs new file mode 100644 index 0000000000..9c0e34d0c8 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs @@ -0,0 +1,163 @@ +using System; +using System.Linq; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Configuration; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModDepth : ModWithVisibilityAdjustment, IUpdatableByPlayfield, IApplicableToDrawableRuleset + { + public override string Name => "Depth"; + public override string Acronym => "DH"; + public override IconUsage? Icon => FontAwesome.Solid.Cube; + public override ModType Type => ModType.Fun; + public override LocalisableString Description => "3D. Almost."; + public override double ScoreMultiplier => 1; + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModFreezeFrame), typeof(ModWithVisibilityAdjustment) }).ToArray(); + + private static readonly Vector3 camera_position = new Vector3(OsuPlayfield.BASE_SIZE.X * 0.5f, OsuPlayfield.BASE_SIZE.Y * 0.5f, -100); + + [SettingSource("Maximum Depth", "How far away object appear.", 0)] + public BindableFloat MaxDepth { get; } = new BindableFloat(100) + { + Precision = 10, + MinValue = 50, + MaxValue = 200 + }; + + [SettingSource("Show Approach Circles", "Whether approach circles should be visible.", 1)] + public BindableBool ShowApproachCircles { get; } = new BindableBool(true); + + protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) => applyTransform(hitObject, state); + + protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) => applyTransform(hitObject, state); + + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + // Hide judgment displays and follow points as they won't make any sense. + // Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart. + drawableRuleset.Playfield.DisplayJudgements.Value = false; + (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); + } + + public override void ApplyToDrawableHitObject(DrawableHitObject dho) + { + base.ApplyToDrawableHitObject(dho); + + switch (dho) + { + case DrawableSliderHead: + case DrawableSliderTail: + case DrawableSliderTick: + case DrawableSliderRepeat: + return; + + case DrawableHitCircle: + case DrawableSlider: + dho.Anchor = Anchor.Centre; + break; + } + } + + private void applyTransform(DrawableHitObject drawable, ArmedState state) + { + switch (drawable) + { + case DrawableSliderHead head: + if (!ShowApproachCircles.Value) + { + var hitObject = (OsuHitObject)drawable.HitObject; + double appearTime = hitObject.StartTime - hitObject.TimePreempt; + + using (head.BeginAbsoluteSequence(appearTime)) + head.ApproachCircle.Hide(); + } + + break; + + case DrawableSliderTail: + case DrawableSliderTick: + case DrawableSliderRepeat: + return; + + case DrawableHitCircle circle: + + if (!ShowApproachCircles.Value) + { + var hitObject = (OsuHitObject)drawable.HitObject; + double appearTime = hitObject.StartTime - hitObject.TimePreempt; + + using (circle.BeginAbsoluteSequence(appearTime)) + circle.ApproachCircle.Hide(); + } + + setStartPosition(drawable); + break; + + case DrawableSlider: + setStartPosition(drawable); + break; + } + } + + private void setStartPosition(DrawableHitObject drawable) + { + var hitObject = (OsuHitObject)drawable.HitObject; + + float d = mappedDepth(MaxDepth.Value); + + using (drawable.BeginAbsoluteSequence(hitObject.StartTime - hitObject.TimePreempt)) + { + drawable.MoveTo(positionAtDepth(d, hitObject.Position)); + drawable.ScaleTo(new Vector2(d)); + } + } + + public void Update(Playfield playfield) + { + double time = playfield.Time.Current; + + foreach (var drawable in playfield.HitObjectContainer.AliveObjects) + { + switch (drawable) + { + case DrawableSliderHead: + case DrawableSliderTail: + case DrawableSliderTick: + case DrawableSliderRepeat: + continue; + + case DrawableHitCircle: + case DrawableSlider: + var hitObject = (OsuHitObject)drawable.HitObject; + + double appearTime = hitObject.StartTime - hitObject.TimePreempt; + double moveDuration = hitObject.TimePreempt; + float z = time > appearTime + moveDuration ? 0 : (MaxDepth.Value - (float)((time - appearTime) / moveDuration * MaxDepth.Value)); + + float d = mappedDepth(z); + drawable.Position = positionAtDepth(d, hitObject.Position); + drawable.Scale = new Vector2(d); + break; + } + } + } + + private static float mappedDepth(float depth) => 100 / (depth - camera_position.Z); + + private static Vector2 positionAtDepth(float mappedDepth, Vector2 positionAtZeroDepth) + { + return (positionAtZeroDepth - camera_position.Xy) * mappedDepth; + } + } +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index aaf0ab41a0..50e6a5b572 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -211,7 +211,8 @@ namespace osu.Game.Rulesets.Osu new ModAdaptiveSpeed(), new OsuModFreezeFrame(), new OsuModBubbles(), - new OsuModSynesthesia() + new OsuModSynesthesia(), + new OsuModDepth() }; case ModType.System: From cf6e50f73c5f0fabd35141602187981c86842a44 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 3 Dec 2023 05:07:40 +0300 Subject: [PATCH 1769/4092] Add header and fix typo --- osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs index 9c0e34d0c8..b2d52aef42 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using System; using System.Linq; using osu.Framework.Bindables; @@ -27,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Mods private static readonly Vector3 camera_position = new Vector3(OsuPlayfield.BASE_SIZE.X * 0.5f, OsuPlayfield.BASE_SIZE.Y * 0.5f, -100); - [SettingSource("Maximum Depth", "How far away object appear.", 0)] + [SettingSource("Maximum depth", "How far away objects appear.", 0)] public BindableFloat MaxDepth { get; } = new BindableFloat(100) { Precision = 10, From 937689ee6bb4eeee18b007ea153b36e5c8e6a961 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 3 Dec 2023 05:39:44 +0300 Subject: [PATCH 1770/4092] Add OsuModDepth as incompatable to other mods --- osu.Game.Rulesets.Osu/Mods/OsuModFreezeFrame.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModTargetPractice.cs | 3 ++- osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 2 +- 10 files changed, 11 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFreezeFrame.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFreezeFrame.cs index f1197ce0cd..06cb9c3419 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFreezeFrame.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFreezeFrame.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override LocalisableString Description => "Burn the notes into your memory."; //Alters the transforms of the approach circles, breaking the effects of these mods. - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModApproachDifferent), typeof(OsuModTransform) }).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModApproachDifferent), typeof(OsuModTransform), typeof(OsuModDepth) }).ToArray(); public override ModType Type => ModType.Fun; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index dd2befef4e..6dc0d5d522 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override LocalisableString Description => @"Play with no approach circles and fading circles/sliders."; public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1; - public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn) }; + public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModDepth) }; public const double FADE_IN_DURATION_MULTIPLIER = 0.4; public const double FADE_OUT_DURATION_MULTIPLIER = 0.3; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs index c8c4cd6a14..befee4af5a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Fun; public override LocalisableString Description => "No need to chase the circles – your cursor is a magnet!"; public override double ScoreMultiplier => 0.5; - public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModRelax), typeof(OsuModRepel), typeof(OsuModBubbles) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModRelax), typeof(OsuModRepel), typeof(OsuModBubbles), typeof(OsuModDepth) }; [SettingSource("Attraction strength", "How strong the pull is.", 0)] public BindableFloat AttractionStrength { get; } = new BindableFloat(0.5f) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs index 6f1206382a..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) }; + public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModObjectScaleTween), typeof(OsuModDepth) }; protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index 28d459cedb..91feb33931 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Fun; public override LocalisableString Description => "Hit objects run away!"; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModMagnetised), typeof(OsuModBubbles) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModMagnetised), typeof(OsuModBubbles), typeof(OsuModDepth) }; [SettingSource("Repulsion strength", "How strong the repulsion is.", 0)] public BindableFloat RepulsionStrength { get; } = new BindableFloat(0.5f) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs index b0533d0cfa..59a1342480 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods // todo: this mod needs to be incompatible with "hidden" due to forcing the circle to remain opaque, // further implementation will be required for supporting that. - public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModObjectScaleTween), typeof(OsuModHidden) }; + public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModObjectScaleTween), typeof(OsuModHidden), typeof(OsuModDepth) }; private const int rotate_offset = 360; private const float rotate_starting_width = 2; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTargetPractice.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTargetPractice.cs index 77cf340b95..a5846efdfe 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTargetPractice.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTargetPractice.cs @@ -47,7 +47,8 @@ namespace osu.Game.Rulesets.Osu.Mods typeof(OsuModRandom), typeof(OsuModSpunOut), typeof(OsuModStrictTracking), - typeof(OsuModSuddenDeath) + typeof(OsuModSuddenDeath), + typeof(OsuModDepth) }).ToArray(); [SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SettingsNumberBox))] diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index 25d05a88a8..9671f53bea 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override LocalisableString Description => "Put your faith in the approach circles..."; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles) }; + public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles), typeof(OsuModDepth) }; protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs index 92a499e735..b6907af119 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Fun; public override LocalisableString Description => "Everything rotates. EVERYTHING."; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModWiggle), typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModFreezeFrame) }).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModWiggle), typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModFreezeFrame), typeof(OsuModDepth) }).ToArray(); private float theta; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index a45338d91f..d14a821541 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Fun; public override LocalisableString Description => "They just won't stay still..."; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform), typeof(OsuModMagnetised), typeof(OsuModRepel) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform), typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModDepth) }; private const int wiggle_duration = 100; // (ms) Higher = fewer wiggles From ebcde63caa4aef0696c977c04bbd5d3e2f2ed5da Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 3 Dec 2023 17:13:47 +0300 Subject: [PATCH 1771/4092] Don't override hitobjects anchor --- osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs index b2d52aef42..5ae9fc4fac 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs @@ -53,25 +53,6 @@ namespace osu.Game.Rulesets.Osu.Mods (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); } - public override void ApplyToDrawableHitObject(DrawableHitObject dho) - { - base.ApplyToDrawableHitObject(dho); - - switch (dho) - { - case DrawableSliderHead: - case DrawableSliderTail: - case DrawableSliderTick: - case DrawableSliderRepeat: - return; - - case DrawableHitCircle: - case DrawableSlider: - dho.Anchor = Anchor.Centre; - break; - } - } - private void applyTransform(DrawableHitObject drawable, ArmedState state) { switch (drawable) @@ -160,7 +141,7 @@ namespace osu.Game.Rulesets.Osu.Mods private static Vector2 positionAtDepth(float mappedDepth, Vector2 positionAtZeroDepth) { - return (positionAtZeroDepth - camera_position.Xy) * mappedDepth; + return (positionAtZeroDepth - camera_position.Xy) * mappedDepth + camera_position.Xy; } } } From b90000f7b70f2840d6ef683aacb6043f78add0e3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 3 Dec 2023 17:15:52 +0300 Subject: [PATCH 1772/4092] Simplify objects depth calculation --- osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs index 5ae9fc4fac..f2ea8b9698 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; @@ -126,8 +127,7 @@ namespace osu.Game.Rulesets.Osu.Mods var hitObject = (OsuHitObject)drawable.HitObject; double appearTime = hitObject.StartTime - hitObject.TimePreempt; - double moveDuration = hitObject.TimePreempt; - float z = time > appearTime + moveDuration ? 0 : (MaxDepth.Value - (float)((time - appearTime) / moveDuration * MaxDepth.Value)); + float z = Interpolation.ValueAt(Math.Clamp(time, appearTime, hitObject.StartTime), MaxDepth.Value, 0f, appearTime, hitObject.StartTime); float d = mappedDepth(z); drawable.Position = positionAtDepth(d, hitObject.Position); From 595bc9398a37596ad98760f0e32448b33741b9bd Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 3 Dec 2023 20:14:00 +0100 Subject: [PATCH 1773/4092] update to new builder control point signature --- .../Sliders/SliderPlacementBlueprint.cs | 37 ++++++++----------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index ac7b5e63e1..584c8fabbf 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -309,12 +309,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void updateSliderPathFromBSplineBuilder() { - IReadOnlyList builderPoints = bSplineBuilder.ControlPoints; + IReadOnlyList> builderPoints = bSplineBuilder.ControlPoints; if (builderPoints.Count == 0) return; - int lastSegmentStart = 0; PathType? lastPathType = null; HitObject.Path.ControlPoints.Clear(); @@ -323,31 +322,25 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders // Importantly, the B-Spline builder returns three Vector2s at the same location when a new segment is to be started. for (int i = 0; i < builderPoints.Count; i++) { - bool isLastPoint = i == builderPoints.Count - 1; - bool isNewSegment = i < builderPoints.Count - 2 && builderPoints[i] == builderPoints[i + 1] && builderPoints[i] == builderPoints[i + 2]; + bool isLastSegment = i == builderPoints.Count - 1; + var segment = builderPoints[i]; + int pointsInSegment = segment.Count; - if (isNewSegment || isLastPoint) - { - int pointsInSegment = i - lastSegmentStart; + // Where possible, we can use the simpler LINEAR path type. + PathType? pathType = pointsInSegment == 1 ? PathType.LINEAR : PathType.BSpline(3); - // Where possible, we can use the simpler LINEAR path type. - PathType? pathType = pointsInSegment == 1 ? PathType.LINEAR : PathType.BSpline(3); + // Linear segments can be combined, as two adjacent linear sections are computationally the same as one with the points combined. + if (lastPathType == pathType && lastPathType == PathType.LINEAR) + pathType = null; - // Linear segments can be combined, as two adjacent linear sections are computationally the same as one with the points combined. - if (lastPathType == pathType && lastPathType == PathType.LINEAR) - pathType = null; + HitObject.Path.ControlPoints.Add(new PathControlPoint(segment[0], pathType)); + for (int j = 1; j < pointsInSegment - 1; j++) + HitObject.Path.ControlPoints.Add(new PathControlPoint(segment[j])); - HitObject.Path.ControlPoints.Add(new PathControlPoint(builderPoints[lastSegmentStart], pathType)); - for (int j = lastSegmentStart + 1; j < i; j++) - HitObject.Path.ControlPoints.Add(new PathControlPoint(builderPoints[j])); + if (isLastSegment) + HitObject.Path.ControlPoints.Add(new PathControlPoint(segment[pointsInSegment - 1])); - if (isLastPoint) - HitObject.Path.ControlPoints.Add(new PathControlPoint(builderPoints[i])); - - // Skip the redundant duplicated points (see isNewSegment above) which have been coalesced into a path type. - lastSegmentStart = (i += 2); - if (pathType != null) lastPathType = pathType; - } + if (pathType != null) lastPathType = pathType; } } From 4cd6efc8f750e8dddc65f23f22e94d1be38fa3bc Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 3 Dec 2023 20:53:05 +0100 Subject: [PATCH 1774/4092] update default parameters --- osu.Game.Rulesets.Osu/Edit/FreehandSliderToolboxGroup.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/FreehandSliderToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/FreehandSliderToolboxGroup.cs index 1974415d30..87dd8636c9 100644 --- a/osu.Game.Rulesets.Osu/Edit/FreehandSliderToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/FreehandSliderToolboxGroup.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Edit { } - public BindableFloat Tolerance { get; } = new BindableFloat(1.5f) + public BindableFloat Tolerance { get; } = new BindableFloat(2f) { MinValue = 0.05f, MaxValue = 3f, @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Edit }; // We map internal ranges to a more standard range of values for display to the user. - private readonly BindableInt displayTolerance = new BindableInt(40) + private readonly BindableInt displayTolerance = new BindableInt(66) { MinValue = 5, MaxValue = 100 From 8873824107a2e29eb914536aec11addddb421857 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 3 Dec 2023 20:53:25 +0100 Subject: [PATCH 1775/4092] fix control points being cleared --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 584c8fabbf..bc32acda96 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -311,7 +311,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { IReadOnlyList> builderPoints = bSplineBuilder.ControlPoints; - if (builderPoints.Count == 0) + if (builderPoints.Count == 0 || builderPoints[0].Count == 0) return; PathType? lastPathType = null; @@ -326,6 +326,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders var segment = builderPoints[i]; int pointsInSegment = segment.Count; + if (segment.Count == 0) + continue; + // Where possible, we can use the simpler LINEAR path type. PathType? pathType = pointsInSegment == 1 ? PathType.LINEAR : PathType.BSpline(3); From ba2cc0243c03e014bb360aca82ad61f17df828f6 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 3 Dec 2023 21:10:01 +0100 Subject: [PATCH 1776/4092] update comment --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index bc32acda96..14de35965e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -318,8 +318,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders HitObject.Path.ControlPoints.Clear(); - // Iterate through generated points, finding each segment and adding non-inheriting path types where appropriate. - // Importantly, the B-Spline builder returns three Vector2s at the same location when a new segment is to be started. + // Iterate through generated segments and adding non-inheriting path types where appropriate. for (int i = 0; i < builderPoints.Count; i++) { bool isLastSegment = i == builderPoints.Count - 1; From 34b5264616aa784439153110eb2b93d542c1968f Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 3 Dec 2023 21:13:27 +0100 Subject: [PATCH 1777/4092] fix the linear segment --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 14de35965e..f98eadb817 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -329,7 +329,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders continue; // Where possible, we can use the simpler LINEAR path type. - PathType? pathType = pointsInSegment == 1 ? PathType.LINEAR : PathType.BSpline(3); + PathType? pathType = pointsInSegment == 2 ? PathType.LINEAR : PathType.BSpline(3); // Linear segments can be combined, as two adjacent linear sections are computationally the same as one with the points combined. if (lastPathType == pathType && lastPathType == PathType.LINEAR) From bcf2effae9b9f4ee80e652940834533d96d0c044 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 3 Dec 2023 21:22:04 +0100 Subject: [PATCH 1778/4092] Remove the Linear segment simplification because it just makes things harder to edit afterwards if it made some segment linear type when you actually intended there to be some curve --- .../Blueprints/Sliders/SliderPlacementBlueprint.cs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index f98eadb817..5f1066d92d 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -314,8 +314,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders if (builderPoints.Count == 0 || builderPoints[0].Count == 0) return; - PathType? lastPathType = null; - HitObject.Path.ControlPoints.Clear(); // Iterate through generated segments and adding non-inheriting path types where appropriate. @@ -328,21 +326,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders if (segment.Count == 0) continue; - // Where possible, we can use the simpler LINEAR path type. - PathType? pathType = pointsInSegment == 2 ? PathType.LINEAR : PathType.BSpline(3); - - // Linear segments can be combined, as two adjacent linear sections are computationally the same as one with the points combined. - if (lastPathType == pathType && lastPathType == PathType.LINEAR) - pathType = null; - - HitObject.Path.ControlPoints.Add(new PathControlPoint(segment[0], pathType)); + HitObject.Path.ControlPoints.Add(new PathControlPoint(segment[0], PathType.BSpline(3))); for (int j = 1; j < pointsInSegment - 1; j++) HitObject.Path.ControlPoints.Add(new PathControlPoint(segment[j])); if (isLastSegment) HitObject.Path.ControlPoints.Add(new PathControlPoint(segment[pointsInSegment - 1])); - - if (pathType != null) lastPathType = pathType; } } From ca55a7b2bfb913f0de9d733a4213650484ad3296 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 3 Dec 2023 21:43:37 +0100 Subject: [PATCH 1779/4092] call builder finish before ending curve --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 5f1066d92d..4413fc4bf4 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -197,7 +197,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders base.OnDragEnd(e); if (state == SliderPlacementState.Drawing) + { + bSplineBuilder.Finish(); + updateSliderPathFromBSplineBuilder(); endCurve(); + } } protected override void OnMouseUp(MouseUpEvent e) From b3d1a9ee2e0665e4a9adc5088db33823615e9f23 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 3 Dec 2023 22:03:51 +0100 Subject: [PATCH 1780/4092] Dont snap expected distance while drawing This makes it 10 billion times smoother to draw, very nice --- .../Blueprints/Sliders/SliderPlacementBlueprint.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 4413fc4bf4..c40f90bfa3 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -200,6 +200,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { bSplineBuilder.Finish(); updateSliderPathFromBSplineBuilder(); + + // Change the state so it will snap the expected distance in endCurve. + state = SliderPlacementState.Finishing; endCurve(); } } @@ -304,7 +307,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void updateSlider() { - HitObject.Path.ExpectedDistance.Value = distanceSnapProvider?.FindSnappedDistance(HitObject, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance; + if (state == SliderPlacementState.Drawing) + HitObject.Path.ExpectedDistance.Value = (float)HitObject.Path.CalculatedDistance; + else + HitObject.Path.ExpectedDistance.Value = distanceSnapProvider?.FindSnappedDistance(HitObject, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance; bodyPiece.UpdateFrom(HitObject); headCirclePiece.UpdateFrom(HitObject.HeadCircle); @@ -343,7 +349,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { Initial, ControlPoints, - Drawing + Drawing, + Finishing } } } From 060141866c4d9e60f8a0d1172318d89fdb821206 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 3 Dec 2023 22:06:07 +0100 Subject: [PATCH 1781/4092] Update SliderPlacementBlueprint.cs --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index c40f90bfa3..e13a46d0cd 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -331,17 +331,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { bool isLastSegment = i == builderPoints.Count - 1; var segment = builderPoints[i]; - int pointsInSegment = segment.Count; if (segment.Count == 0) continue; HitObject.Path.ControlPoints.Add(new PathControlPoint(segment[0], PathType.BSpline(3))); - for (int j = 1; j < pointsInSegment - 1; j++) + for (int j = 1; j < segment.Count - 1; j++) HitObject.Path.ControlPoints.Add(new PathControlPoint(segment[j])); if (isLastSegment) - HitObject.Path.ControlPoints.Add(new PathControlPoint(segment[pointsInSegment - 1])); + HitObject.Path.ControlPoints.Add(new PathControlPoint(segment[^1])); } } From d0acb7f4f9e601f10b7650a217329cfb0b041158 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 4 Dec 2023 06:08:31 +0900 Subject: [PATCH 1782/4092] Improve commenting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game.Rulesets.Mania/Scoring/ManiaHealthProcessor.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaHealthProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaHealthProcessor.cs index 34b1787a71..a33eac83c2 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaHealthProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaHealthProcessor.cs @@ -17,7 +17,8 @@ namespace osu.Game.Rulesets.Mania.Scoring protected override double ComputeDrainRate() { - // Base call is run only to compute HP recovery. + // Base call is run only to compute HP recovery (namely, `HpMultiplierNormal`). + // This closely mirrors (broken) behaviour of stable and as such is preserved unchanged. base.ComputeDrainRate(); return 0; From 13b7f2fa42e68fe72ec0835260b6b84411d3dbbc Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 3 Dec 2023 22:42:48 +0100 Subject: [PATCH 1783/4092] Fix test cases --- .../Editor/TestSceneSliderPlacementBlueprint.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index 7ac34bc6c8..f889999fe3 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -310,7 +310,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("release left button", () => InputManager.ReleaseButton(MouseButton.Left)); assertPlaced(true); - assertLength(760, tolerance: 10); + assertLength(808, tolerance: 10); assertControlPointCount(5); assertControlPointType(0, PathType.BSpline(3)); assertControlPointType(1, null); @@ -337,9 +337,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertLength(600, tolerance: 10); assertControlPointCount(4); - assertControlPointType(0, PathType.LINEAR); - assertControlPointType(1, null); - assertControlPointType(2, null); + assertControlPointType(0, PathType.BSpline(3)); + assertControlPointType(1, PathType.BSpline(3)); + assertControlPointType(2, PathType.BSpline(3)); assertControlPointType(3, null); } From b56a78c6ecaa9f47e0a8de9b8c4d567975b3c500 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 4 Dec 2023 08:51:21 +0900 Subject: [PATCH 1784/4092] Adjust with framework changes --- osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerProgressArc.cs | 2 +- osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs | 2 +- osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs | 2 +- osu.Game/Graphics/Backgrounds/Triangles.cs | 2 +- osu.Game/Graphics/Backgrounds/TrianglesV2.cs | 2 +- osu.Game/Graphics/UserInterface/BarGraph.cs | 2 +- osu.Game/Graphics/UserInterface/SegmentedGraph.cs | 2 +- osu.Game/Rulesets/Mods/ModFlashlight.cs | 2 +- osu.Game/Screens/Menu/LogoVisualisation.cs | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerProgressArc.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerProgressArc.cs index b719138d33..88769442a1 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerProgressArc.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerProgressArc.cs @@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon relativeGlowSize = Source.glowSize / Source.DrawSize.X; } - public override void Draw(IRenderer renderer) + protected override void Draw(IRenderer renderer) { base.Draw(renderer); drawGlow(renderer); diff --git a/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs b/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs index d818c8baee..9838cb2c37 100644 --- a/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs +++ b/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs @@ -231,7 +231,7 @@ namespace osu.Game.Rulesets.Osu.Skinning points.AddRange(Source.SmokePoints.Skip(firstVisiblePointIndex).Take(futurePointIndex - firstVisiblePointIndex)); } - public sealed override void Draw(IRenderer renderer) + protected sealed override void Draw(IRenderer renderer) { base.Draw(renderer); diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index a29faac5a0..95a052dadb 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -258,7 +258,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private IUniformBuffer cursorTrailParameters; - public override void Draw(IRenderer renderer) + protected override void Draw(IRenderer renderer) { base.Draw(renderer); diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index 0ee42c69d5..1a78c1ec5e 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -286,7 +286,7 @@ namespace osu.Game.Graphics.Backgrounds private IUniformBuffer borderDataBuffer; - public override void Draw(IRenderer renderer) + protected override void Draw(IRenderer renderer) { base.Draw(renderer); diff --git a/osu.Game/Graphics/Backgrounds/TrianglesV2.cs b/osu.Game/Graphics/Backgrounds/TrianglesV2.cs index 750e96440d..a20fd78569 100644 --- a/osu.Game/Graphics/Backgrounds/TrianglesV2.cs +++ b/osu.Game/Graphics/Backgrounds/TrianglesV2.cs @@ -228,7 +228,7 @@ namespace osu.Game.Graphics.Backgrounds private IUniformBuffer? borderDataBuffer; - public override void Draw(IRenderer renderer) + protected override void Draw(IRenderer renderer) { base.Draw(renderer); diff --git a/osu.Game/Graphics/UserInterface/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs index 0ac987e85b..e7592128b0 100644 --- a/osu.Game/Graphics/UserInterface/BarGraph.cs +++ b/osu.Game/Graphics/UserInterface/BarGraph.cs @@ -134,7 +134,7 @@ namespace osu.Game.Graphics.UserInterface lengths.AddRange(Source.bars.InstantaneousLengths); } - public override void Draw(IRenderer renderer) + protected override void Draw(IRenderer renderer) { base.Draw(renderer); diff --git a/osu.Game/Graphics/UserInterface/SegmentedGraph.cs b/osu.Game/Graphics/UserInterface/SegmentedGraph.cs index 91971e5af9..9f467687a4 100644 --- a/osu.Game/Graphics/UserInterface/SegmentedGraph.cs +++ b/osu.Game/Graphics/UserInterface/SegmentedGraph.cs @@ -221,7 +221,7 @@ namespace osu.Game.Graphics.UserInterface tierColours.AddRange(Source.tierColours); } - public override void Draw(IRenderer renderer) + protected override void Draw(IRenderer renderer) { base.Draw(renderer); diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 215fc877dc..95406cc9e6 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -252,7 +252,7 @@ namespace osu.Game.Rulesets.Mods private IUniformBuffer? flashlightParametersBuffer; - public override void Draw(IRenderer renderer) + protected override void Draw(IRenderer renderer) { base.Draw(renderer); diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index fa26cfab46..b722b83280 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -189,7 +189,7 @@ namespace osu.Game.Screens.Menu Source.frequencyAmplitudes.AsSpan().CopyTo(audioData); } - public override void Draw(IRenderer renderer) + protected override void Draw(IRenderer renderer) { base.Draw(renderer); From c2644a5d5e208214f383714b606c5e442bc04a28 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 4 Dec 2023 10:18:37 +0900 Subject: [PATCH 1785/4092] Correctly implement button enabled state --- .../Visual/Multiplayer/TestSceneMatchStartControl.cs | 1 + .../OnlinePlay/Multiplayer/Match/MatchStartControl.cs | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs index f8719ba80c..c64dea3f59 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs @@ -401,6 +401,7 @@ namespace osu.Game.Tests.Visual.Multiplayer ClickButtonWhenEnabled(); ClickButtonWhenEnabled(); ClickButtonWhenEnabled(); + AddStep("check abort request received", () => multiplayerClient.Verify(m => m.AbortMatch(), Times.Once)); } private void verifyGameplayStartFlow() diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs index a21c85e316..e61735fe61 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs @@ -130,7 +130,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match dialogOverlay.Push(new ConfirmAbortDialog(abortMatch, endOperation)); } } - else + else if (Room.State != MultiplayerRoomState.Closed) toggleReady(); bool isReady() => Client.LocalUser?.State == MultiplayerUserState.Ready || Client.LocalUser?.State == MultiplayerUserState.Spectating; @@ -210,7 +210,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match } readyButton.Enabled.Value = countdownButton.Enabled.Value = - Room.State == MultiplayerRoomState.Open + Room.State != MultiplayerRoomState.Closed && CurrentPlaylistItem.Value?.ID == Room.Settings.PlaylistItemId && !Room.Playlist.Single(i => i.ID == Room.Settings.PlaylistItemId).Expired && !operationInProgress.Value; @@ -219,7 +219,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match if (localUser?.State == MultiplayerUserState.Spectating) readyButton.Enabled.Value &= Client.IsHost && newCountReady > 0 && !Room.ActiveCountdowns.Any(c => c is MatchStartCountdown); - readyButton.Enabled.Value = true; + // When the local user is not the host, the button should only be enabled when no match is in progress. + if (!Client.IsHost) + readyButton.Enabled.Value &= Room.State == MultiplayerRoomState.Open; if (newCountReady == countReady) return; From 9ccd33a1ecc61e684ca92b30781fb55b669a9016 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 4 Dec 2023 10:20:53 +0900 Subject: [PATCH 1786/4092] Add comments to test --- .../Visual/Multiplayer/TestSceneMatchStartControl.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs index c64dea3f59..750968fc75 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs @@ -398,8 +398,13 @@ namespace osu.Game.Tests.Visual.Multiplayer raiseRoomUpdated(); }); + // Ready ClickButtonWhenEnabled(); + + // Start match ClickButtonWhenEnabled(); + + // Abort ClickButtonWhenEnabled(); AddStep("check abort request received", () => multiplayerClient.Verify(m => m.AbortMatch(), Times.Once)); } From 8587652869ea92665de36af0f1bc4a9079ab3c8a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 4 Dec 2023 11:00:11 +0900 Subject: [PATCH 1787/4092] Fix countdown button being enabled --- .../Multiplayer/TestSceneMatchStartControl.cs | 32 +++++++++++-------- .../Multiplayer/Match/MatchStartControl.cs | 3 ++ 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs index 750968fc75..2d61c26a6b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs @@ -381,28 +381,32 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestAbortMatch() { - multiplayerClient.Setup(m => m.StartMatch()) - .Callback(() => - { - multiplayerClient.Raise(m => m.LoadRequested -= null); - multiplayerClient.Object.Room!.State = MultiplayerRoomState.WaitingForLoad; + AddStep("setup client", () => + { + multiplayerClient.Setup(m => m.StartMatch()) + .Callback(() => + { + multiplayerClient.Raise(m => m.LoadRequested -= null); + multiplayerClient.Object.Room!.State = MultiplayerRoomState.WaitingForLoad; - // The local user state doesn't really matter, so let's do the same as the base implementation for these tests. - changeUserState(localUser.UserID, MultiplayerUserState.Idle); - }); + // The local user state doesn't really matter, so let's do the same as the base implementation for these tests. + changeUserState(localUser.UserID, MultiplayerUserState.Idle); + }); - multiplayerClient.Setup(m => m.AbortMatch()) - .Callback(() => - { - multiplayerClient.Object.Room!.State = MultiplayerRoomState.Open; - raiseRoomUpdated(); - }); + multiplayerClient.Setup(m => m.AbortMatch()) + .Callback(() => + { + multiplayerClient.Object.Room!.State = MultiplayerRoomState.Open; + raiseRoomUpdated(); + }); + }); // Ready ClickButtonWhenEnabled(); // Start match ClickButtonWhenEnabled(); + AddUntilStep("countdown button disabled", () => !this.ChildrenOfType().Single().Enabled.Value); // Abort ClickButtonWhenEnabled(); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs index e61735fe61..99934acaae 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs @@ -223,6 +223,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match if (!Client.IsHost) readyButton.Enabled.Value &= Room.State == MultiplayerRoomState.Open; + // At all times, the countdown button should only be enabled when no match is in progress. + countdownButton.Enabled.Value &= Room.State == MultiplayerRoomState.Open; + if (newCountReady == countReady) return; From c2a4a6d8cb0cbc8a00b98489b6496f1e843c9afc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Dec 2023 12:10:31 +0900 Subject: [PATCH 1788/4092] Add inline comment matching framework --- osu.Android/AndroidManifest.xml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Android/AndroidManifest.xml b/osu.Android/AndroidManifest.xml index 2fb4d1f850..492d48542e 100644 --- a/osu.Android/AndroidManifest.xml +++ b/osu.Android/AndroidManifest.xml @@ -5,5 +5,12 @@ + - \ No newline at end of file + From c755bcbec4167c9924d6d6ee5c980c491cc4f2dd Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 4 Dec 2023 14:30:08 +0900 Subject: [PATCH 1789/4092] Add failing test --- .../CatchBeatmapConversionTest.cs | 1 + .../pixel-jump-expected-conversion.json | 34 +++++++++++++++++++ .../Resources/Testing/Beatmaps/pixel-jump.osu | 23 +++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/pixel-jump-expected-conversion.json create mode 100644 osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/pixel-jump.osu diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs index baca8166d1..a11aaa5e29 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs @@ -27,6 +27,7 @@ namespace osu.Game.Rulesets.Catch.Tests [TestCase("hardrock-spinner", new[] { typeof(CatchModHardRock) })] [TestCase("right-bound-hr-offset", new[] { typeof(CatchModHardRock) })] [TestCase("basic-hyperdash")] + [TestCase("pixel-jump")] public new void Test(string name, params Type[] mods) => base.Test(name, mods); protected override IEnumerable CreateConvertValue(HitObject hitObject) diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/pixel-jump-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/pixel-jump-expected-conversion.json new file mode 100644 index 0000000000..c9fbaf92a3 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/pixel-jump-expected-conversion.json @@ -0,0 +1,34 @@ +{ + "Mappings": [ + { + "StartTime": 23143.0, + "Objects": [ + { + "StartTime": 23143.0, + "Position": 307.0, + "HyperDash": false + }, + { + "StartTime": 23226.0, + "Position": 354.644958, + "HyperDash": false + } + ] + }, + { + "StartTime": 23310.0, + "Objects": [ + { + "StartTime": 23310.0, + "Position": 214.0, + "HyperDash": false + }, + { + "StartTime": 23393.0, + "Position": 154.841156, + "HyperDash": false + } + ] + } + ] +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/pixel-jump.osu b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/pixel-jump.osu new file mode 100644 index 0000000000..6f470e77e5 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/pixel-jump.osu @@ -0,0 +1,23 @@ +osu file format v14 + +[General] +StackLeniency: 0.2 +Mode: 0 + +[Difficulty] +HPDrainRate:5.5 +CircleSize:4 +OverallDifficulty:8.6 +ApproachRate:9.4 +SliderMultiplier:2 +SliderTickRate:1 + +[TimingPoints] +310,333.333333333333,4,2,1,45,1,0 +23142,-83.3333333333333,4,2,1,70,0,0 +23225,-83.3333333333333,4,2,1,5,0,0 +23309,-83.3333333333333,4,2,1,75,0,0 + +[HitObjects] +307,184,23143,2,0,P|330:160|366:150,1,59.9999981689454,2|0,0:1|0:0,0:0:0:0: +214,335,23310,2,0,L|149:324,1,59.9999981689454,10|0,0:0|0:0,0:0:0:0: \ No newline at end of file From 6f73d78bc90a2ec78f1eb3137363db40ece6af2a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 4 Dec 2023 14:32:14 +0900 Subject: [PATCH 1790/4092] Replicate integer calculations for catch hyperdash generation --- osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index 7c81ca03d1..50e6fd9673 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -247,7 +247,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps currentObject.DistanceToHyperDash = 0; int thisDirection = nextObject.EffectiveX > currentObject.EffectiveX ? 1 : -1; - double timeToNext = nextObject.StartTime - currentObject.StartTime - 1000f / 60f / 4; // 1/4th of a frame of grace time, taken from osu-stable + double timeToNext = (int)nextObject.StartTime - (int)currentObject.StartTime - 1000f / 60f / 4; // 1/4th of a frame of grace time, taken from osu-stable double distanceToNext = Math.Abs(nextObject.EffectiveX - currentObject.EffectiveX) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth); float distanceToHyper = (float)(timeToNext * Catcher.BASE_DASH_SPEED - distanceToNext); From ec5c7d7830dfac2f37a8e3d6cbc14967c8473f21 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 4 Dec 2023 09:43:09 +0300 Subject: [PATCH 1791/4092] Add deceleration and rework depth handling --- osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs | 105 ++++++++++------------ 1 file changed, 49 insertions(+), 56 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs index f2ea8b9698..5054c75e97 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs @@ -4,7 +4,6 @@ using System; using System.Linq; using osu.Framework.Bindables; -using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Framework.Utils; @@ -30,6 +29,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModFreezeFrame), typeof(ModWithVisibilityAdjustment) }).ToArray(); private static readonly Vector3 camera_position = new Vector3(OsuPlayfield.BASE_SIZE.X * 0.5f, OsuPlayfield.BASE_SIZE.Y * 0.5f, -100); + private readonly float minDepth = depthForScale(1.5f); [SettingSource("Maximum depth", "How far away objects appear.", 0)] public BindableFloat MaxDepth { get; } = new BindableFloat(100) @@ -58,25 +58,7 @@ namespace osu.Game.Rulesets.Osu.Mods { switch (drawable) { - case DrawableSliderHead head: - if (!ShowApproachCircles.Value) - { - var hitObject = (OsuHitObject)drawable.HitObject; - double appearTime = hitObject.StartTime - hitObject.TimePreempt; - - using (head.BeginAbsoluteSequence(appearTime)) - head.ApproachCircle.Hide(); - } - - break; - - case DrawableSliderTail: - case DrawableSliderTick: - case DrawableSliderRepeat: - return; - case DrawableHitCircle circle: - if (!ShowApproachCircles.Value) { var hitObject = (OsuHitObject)drawable.HitObject; @@ -86,25 +68,7 @@ namespace osu.Game.Rulesets.Osu.Mods circle.ApproachCircle.Hide(); } - setStartPosition(drawable); break; - - case DrawableSlider: - setStartPosition(drawable); - break; - } - } - - private void setStartPosition(DrawableHitObject drawable) - { - var hitObject = (OsuHitObject)drawable.HitObject; - - float d = mappedDepth(MaxDepth.Value); - - using (drawable.BeginAbsoluteSequence(hitObject.StartTime - hitObject.TimePreempt)) - { - drawable.MoveTo(positionAtDepth(d, hitObject.Position)); - drawable.ScaleTo(new Vector2(d)); } } @@ -114,34 +78,63 @@ namespace osu.Game.Rulesets.Osu.Mods foreach (var drawable in playfield.HitObjectContainer.AliveObjects) { - switch (drawable) - { - case DrawableSliderHead: - case DrawableSliderTail: - case DrawableSliderTick: - case DrawableSliderRepeat: - continue; + if (drawable is not DrawableOsuHitObject d) + continue; + switch (d) + { case DrawableHitCircle: case DrawableSlider: - var hitObject = (OsuHitObject)drawable.HitObject; - - double appearTime = hitObject.StartTime - hitObject.TimePreempt; - float z = Interpolation.ValueAt(Math.Clamp(time, appearTime, hitObject.StartTime), MaxDepth.Value, 0f, appearTime, hitObject.StartTime); - - float d = mappedDepth(z); - drawable.Position = positionAtDepth(d, hitObject.Position); - drawable.Scale = new Vector2(d); + processObject(time, d); break; } } } - private static float mappedDepth(float depth) => 100 / (depth - camera_position.Z); - - private static Vector2 positionAtDepth(float mappedDepth, Vector2 positionAtZeroDepth) + private void processObject(double time, DrawableOsuHitObject drawable) { - return (positionAtZeroDepth - camera_position.Xy) * mappedDepth + camera_position.Xy; + var hitObject = drawable.HitObject; + + double baseSpeed = MaxDepth.Value / hitObject.TimePreempt; + double hitObjectDuration = hitObject is Slider s ? s.Duration : 0.0; + double offsetAfterStartTime = hitObjectDuration + hitObject.MaximumJudgementOffset + 500; + double slowSpeed = -minDepth / offsetAfterStartTime; + + float decelerationDistance = MaxDepth.Value * 0.2f; + double decelerationTime = (slowSpeed - baseSpeed) * 2 * decelerationDistance / (slowSpeed * slowSpeed - baseSpeed * baseSpeed); + + float z; + + if (time < hitObject.StartTime - decelerationTime) + { + double appearTime = hitObject.StartTime - hitObject.TimePreempt; + float fullDistance = decelerationDistance + (float)(baseSpeed * (hitObject.TimePreempt - decelerationTime)); + z = Interpolation.ValueAt(Math.Max(time, appearTime), fullDistance, decelerationDistance, appearTime, hitObject.StartTime - decelerationTime); + } + else if (time < hitObject.StartTime) + { + double timeOffset = time - (hitObject.StartTime - decelerationTime); + double deceleration = (slowSpeed - baseSpeed) / decelerationTime; + z = decelerationDistance - (float)(baseSpeed * timeOffset + deceleration * timeOffset * timeOffset * 0.5); + } + else + { + double endTime = hitObject.StartTime + offsetAfterStartTime; + z = Interpolation.ValueAt(Math.Min(time, endTime), 0f, minDepth, hitObject.StartTime, endTime); + } + + float scale = scaleForDepth(z); + drawable.Position = positionAtDepth(scale, hitObject.Position); + drawable.Scale = new Vector2(scale); + } + + private static float scaleForDepth(float depth) => 100 / (depth - camera_position.Z); + + private static float depthForScale(float scale) => 100 / scale + camera_position.Z; + + private static Vector2 positionAtDepth(float scale, Vector2 positionAtZeroDepth) + { + return (positionAtZeroDepth - camera_position.Xy) * scale + camera_position.Xy; } } } From bb198e0c5a96120ac3ac3139879ccc47265eac72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 4 Dec 2023 09:26:23 +0100 Subject: [PATCH 1792/4092] Add test coverage for missing hyperdashes on simultaneous notes --- .../TestSceneHyperDash.cs | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs index 3c222662f5..445fa42b36 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Catch.Tests AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash); - for (int i = 0; i < 9; i++) + for (int i = 0; i < 11; i++) { int count = i + 1; AddUntilStep($"wait for hyperdash #{count}", () => hyperDashCount >= count); @@ -100,12 +100,22 @@ namespace osu.Game.Rulesets.Catch.Tests }) }, 1); + createObjects(() => new Fruit { X = right_x }, count: 2, spacing: 0, spacingAfterGroup: 400); + createObjects(() => new TestJuiceStream(left_x) + { + Path = new SliderPath(new[] + { + new PathControlPoint(Vector2.Zero), + new PathControlPoint(new Vector2(0, 300)) + }) + }, count: 1, spacingAfterGroup: 150); + createObjects(() => new Fruit { X = left_x }, count: 1, spacing: 0, spacingAfterGroup: 400); + createObjects(() => new Fruit { X = right_x }, count: 2, spacing: 0); + return beatmap; - void createObjects(Func createObject, int count = 3) + void createObjects(Func createObject, int count = 3, float spacing = 140, float spacingAfterGroup = 700) { - const float spacing = 140; - for (int i = 0; i < count; i++) { var hitObject = createObject(); @@ -113,7 +123,7 @@ namespace osu.Game.Rulesets.Catch.Tests beatmap.HitObjects.Add(hitObject); } - startTime += 700; + startTime += spacingAfterGroup; } } From fcb6f40666bc351c8bd8122314978ce33ac2993e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 4 Dec 2023 09:30:18 +0100 Subject: [PATCH 1793/4092] Prioritise hyperfruit over non-hyperfruit if simultaneous In case of simultaneous hyperfruit and non-hyperfruit - which happen to occur on some aspire maps - the desired behaviour is to hyperdash. This did not previously occur, due to annoying details in how `HitObjectContainer` is structured. `HitObjectContainer`'s drawable comparer determines the order of updating the objects. One could say that forcing the hyperfruit to be updated last, after normal fruit, could help; unfortunately this is complicated by the existence of juice streams and the fact that while a juice stream can be terminated by a normal fruit that is coincidental with a hyperfruit, the two are not comparable directly using the comparer in any feasible way. Therefore, apply a `Catcher`-level workaround that intends to handle this locally; in short, if a hyperdash was toggled in a given frame, it cannot be toggled off again in the same frame. This yields the desired behaviour. --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 0c2c157d10..147850a9b7 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -126,6 +126,7 @@ namespace osu.Game.Rulesets.Catch.UI private Color4 hyperDashColour = DEFAULT_HYPER_DASH_COLOUR; + private double? lastHyperDashStartTime; private double hyperDashModifier = 1; private int hyperDashDirection; private float hyperDashTargetPosition; @@ -233,16 +234,23 @@ namespace osu.Game.Rulesets.Catch.UI // droplet doesn't affect the catcher state if (hitObject is TinyDroplet) return; - if (result.IsHit && hitObject.HyperDashTarget is CatchHitObject target) + // if a hyper fruit was already handled this frame, just go where it says to go. + // this special-cases some aspire maps that have doubled-up objects (one hyper, one not) at the same time instant. + // handling this "properly" elsewhere is impossible as there is no feasible way to ensure + // that the hyperfruit gets judged second (especially if it coincides with a last fruit in a juice stream). + if (lastHyperDashStartTime != Time.Current) { - double timeDifference = target.StartTime - hitObject.StartTime; - double positionDifference = target.EffectiveX - X; - double velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0); + if (result.IsHit && hitObject.HyperDashTarget is CatchHitObject target) + { + double timeDifference = target.StartTime - hitObject.StartTime; + double positionDifference = target.EffectiveX - X; + double velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0); - SetHyperDashState(Math.Abs(velocity) / BASE_DASH_SPEED, target.EffectiveX); + SetHyperDashState(Math.Abs(velocity) / BASE_DASH_SPEED, target.EffectiveX); + } + else + SetHyperDashState(); } - else - SetHyperDashState(); if (result.IsHit) CurrentState = hitObject.Kiai ? CatcherAnimationState.Kiai : CatcherAnimationState.Idle; @@ -292,6 +300,8 @@ namespace osu.Game.Rulesets.Catch.UI if (wasHyperDashing) runHyperDashStateTransition(false); + + lastHyperDashStartTime = null; } else { @@ -301,6 +311,8 @@ namespace osu.Game.Rulesets.Catch.UI if (!wasHyperDashing) runHyperDashStateTransition(true); + + lastHyperDashStartTime = Time.Current; } } From 7deff70b4ae1710c0d66495d52aafbb6a8b3cd37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 4 Dec 2023 13:34:36 +0100 Subject: [PATCH 1794/4092] Extract beatmap import steps from gameplay scene switch helper --- .../TestSceneSkinEditorNavigation.cs | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs index c17a9ddf5f..28855830e1 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs @@ -38,6 +38,9 @@ namespace osu.Game.Tests.Visual.Navigation advanceToSongSelect(); openSkinEditor(); + AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); + AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + switchToGameplayScene(); BarHitErrorMeter hitErrorMeter = null; @@ -98,6 +101,10 @@ namespace osu.Game.Tests.Visual.Navigation { advanceToSongSelect(); openSkinEditor(); + + AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); + AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + switchToGameplayScene(); AddUntilStep("wait for components", () => skinEditor.ChildrenOfType().Any()); @@ -162,6 +169,9 @@ namespace osu.Game.Tests.Visual.Navigation openSkinEditor(); AddStep("select DT", () => Game.SelectedMods.Value = new Mod[] { new OsuModDoubleTime() }); + AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); + AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + switchToGameplayScene(); AddAssert("DT still selected", () => ((Player)Game.ScreenStack.CurrentScreen).Mods.Value.Single() is OsuModDoubleTime); @@ -174,6 +184,9 @@ namespace osu.Game.Tests.Visual.Navigation openSkinEditor(); AddStep("select relax and spun out", () => Game.SelectedMods.Value = new Mod[] { new OsuModRelax(), new OsuModSpunOut() }); + AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); + AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + switchToGameplayScene(); AddAssert("no mod selected", () => !((Player)Game.ScreenStack.CurrentScreen).Mods.Value.Any()); @@ -186,6 +199,9 @@ namespace osu.Game.Tests.Visual.Navigation openSkinEditor(); AddStep("select autoplay", () => Game.SelectedMods.Value = new Mod[] { new OsuModAutoplay() }); + AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); + AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + switchToGameplayScene(); AddAssert("no mod selected", () => !((Player)Game.ScreenStack.CurrentScreen).Mods.Value.Any()); @@ -198,6 +214,9 @@ namespace osu.Game.Tests.Visual.Navigation openSkinEditor(); AddStep("select cinema", () => Game.SelectedMods.Value = new Mod[] { new OsuModCinema() }); + AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); + AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + switchToGameplayScene(); AddAssert("no mod selected", () => !((Player)Game.ScreenStack.CurrentScreen).Mods.Value.Any()); @@ -266,9 +285,6 @@ namespace osu.Game.Tests.Visual.Navigation private void switchToGameplayScene() { - AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); - AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); - AddStep("Click gameplay scene button", () => { InputManager.MoveMouseTo(skinEditor.ChildrenOfType().First(b => b.Text.ToString() == "Gameplay")); From d3e94cd5bfaa328032c12c44cb32d08b9b8f8728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 4 Dec 2023 13:50:52 +0100 Subject: [PATCH 1795/4092] Add test coverage for crashing on empty beatmap --- .../Navigation/TestSceneSkinEditorNavigation.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs index 28855830e1..62b53f9bac 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs @@ -12,9 +12,11 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Framework.Threading; +using osu.Game.Online.API; using osu.Game.Overlays.Settings; using osu.Game.Overlays.SkinEditor; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Play; @@ -259,6 +261,19 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert("editor sidebars not empty", () => skinEditor.ChildrenOfType().SelectMany(sidebar => sidebar.Children).Count(), () => Is.GreaterThan(0)); } + [Test] + public void TestOpenSkinEditorGameplaySceneOnBeatmapWithNoObjects() + { + AddStep("set dummy beatmap", () => Game.Beatmap.SetDefault()); + advanceToSongSelect(); + + AddStep("create empty beatmap", () => Game.BeatmapManager.CreateNew(new OsuRuleset().RulesetInfo, new GuestUser())); + AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + + openSkinEditor(); + switchToGameplayScene(); + } + private void advanceToSongSelect() { PushAndConfirm(() => songSelect = new TestPlaySongSelect()); From 7c041df0f1875a8274597b626cb465f05143304a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 4 Dec 2023 13:52:41 +0100 Subject: [PATCH 1796/4092] Add test coverage for crashing on dummy beatmap --- .../Visual/Navigation/TestSceneSkinEditorNavigation.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs index 62b53f9bac..4424b8cef6 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs @@ -274,6 +274,15 @@ namespace osu.Game.Tests.Visual.Navigation switchToGameplayScene(); } + [Test] + public void TestOpenSkinEditorGameplaySceneWhenDummyBeatmapActive() + { + AddStep("set dummy beatmap", () => Game.Beatmap.SetDefault()); + + openSkinEditor(); + switchToGameplayScene(); + } + private void advanceToSongSelect() { PushAndConfirm(() => songSelect = new TestPlaySongSelect()); From 43312619e36c6d57a0c904adfd7a4d12890cf867 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 4 Dec 2023 13:58:07 +0100 Subject: [PATCH 1797/4092] Add test coverage for crashing on wrong ruleset --- .../Navigation/TestSceneSkinEditorNavigation.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs index 4424b8cef6..74e6ba1566 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs @@ -13,6 +13,7 @@ using osu.Framework.Screens; using osu.Framework.Testing; using osu.Framework.Threading; using osu.Game.Online.API; +using osu.Game.Beatmaps; using osu.Game.Overlays.Settings; using osu.Game.Overlays.SkinEditor; using osu.Game.Rulesets.Mods; @@ -283,6 +284,22 @@ namespace osu.Game.Tests.Visual.Navigation switchToGameplayScene(); } + [Test] + public void TestOpenSkinEditorGameplaySceneWhenDifferentRulesetActive() + { + BeatmapSetInfo beatmapSet = null!; + + AddStep("import beatmap", () => beatmapSet = BeatmapImportHelper.LoadQuickOszIntoOsu(Game).GetResultSafely()); + AddStep("select mania difficulty", () => + { + var beatmap = beatmapSet.Beatmaps.First(b => b.Ruleset.OnlineID == 3); + Game.Beatmap.Value = Game.BeatmapManager.GetWorkingBeatmap(beatmap); + }); + + openSkinEditor(); + switchToGameplayScene(); + } + private void advanceToSongSelect() { PushAndConfirm(() => songSelect = new TestPlaySongSelect()); From 055fb5bd8f482d4fc23658f259a9f9108cf4bc29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 4 Dec 2023 14:14:07 +0100 Subject: [PATCH 1798/4092] Do not set initial activity in skin editor endless player Seems like an overreach to say that the user is "watching a replay" there. --- osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs index 18d1c4c62b..6e64f5b786 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs @@ -26,6 +26,7 @@ using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Menu; using osu.Game.Screens.Play; using osu.Game.Screens.Select; +using osu.Game.Users; using osu.Game.Utils; using osuTK; @@ -285,6 +286,8 @@ namespace osu.Game.Overlays.SkinEditor private partial class EndlessPlayer : ReplayPlayer { + protected override UserActivity? InitialActivity => null; + public EndlessPlayer(Func, Score> createScore) : base(createScore, new PlayerConfiguration { From 9d39b70e38d6beef46e0ad3a7fad03acf0673dc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 4 Dec 2023 14:14:26 +0100 Subject: [PATCH 1799/4092] Fix endless player not handling load failure --- osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs index 6e64f5b786..46658bb993 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs @@ -297,10 +297,21 @@ namespace osu.Game.Overlays.SkinEditor { } + protected override void LoadComplete() + { + base.LoadComplete(); + + if (!LoadedBeatmapSuccessfully) + Scheduler.AddDelayed(this.Exit, 3000); + } + protected override void Update() { base.Update(); + if (!LoadedBeatmapSuccessfully) + return; + if (GameplayState.HasPassed) GameplayClockContainer.Seek(0); } From 063694f5444a3d2b22409e45c121a67470800446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 4 Dec 2023 14:20:03 +0100 Subject: [PATCH 1800/4092] Do not attempt to load gameplay scene if current beatmap is dummy --- .../Visual/Navigation/TestSceneSkinEditorNavigation.cs | 1 - osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs | 6 ++++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs index 74e6ba1566..fa85c8c9f8 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs @@ -281,7 +281,6 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("set dummy beatmap", () => Game.Beatmap.SetDefault()); openSkinEditor(); - switchToGameplayScene(); } [Test] diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs index 46658bb993..ae118836c8 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs @@ -140,6 +140,12 @@ namespace osu.Game.Overlays.SkinEditor { performer?.PerformFromScreen(screen => { + if (beatmap.Value is DummyWorkingBeatmap) + { + // presume we don't have anything good to play and just bail. + return; + } + // If we're playing the intro, switch away to another beatmap. if (beatmap.Value.BeatmapSetInfo.Protected) { From 8754fa40f4251bd5c94752eb3fadccaf2e95a490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 4 Dec 2023 14:23:38 +0100 Subject: [PATCH 1801/4092] Source autoplay mod from beatmap about to be presented rather than ambient global --- osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs index ae118836c8..d5c0cd89cf 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs @@ -157,7 +157,7 @@ namespace osu.Game.Overlays.SkinEditor if (screen is Player) return; - var replayGeneratingMod = ruleset.Value.CreateInstance().GetAutoplayMod(); + var replayGeneratingMod = beatmap.Value.BeatmapInfo.Ruleset.CreateInstance().GetAutoplayMod(); IReadOnlyList usableMods = mods.Value; From 5512298d60e84950f15ab9d3cc2453e2a0a7a74d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 4 Dec 2023 15:01:23 +0100 Subject: [PATCH 1802/4092] Trim unused resolved bindable --- osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs index d5c0cd89cf..0821e603d1 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs @@ -56,9 +56,6 @@ namespace osu.Game.Overlays.SkinEditor [Resolved] private MusicController music { get; set; } = null!; - [Resolved] - private IBindable ruleset { get; set; } = null!; - [Resolved] private Bindable> mods { get; set; } = null!; From c5a08a07118204227dc26a4c1177908ab77f531f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Dec 2023 23:06:08 +0900 Subject: [PATCH 1803/4092] Remove unused using statement --- osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs index 0821e603d1..be7ddd115b 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs @@ -17,7 +17,6 @@ using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; -using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens; From 0e474928580f369645381dae6a9a1388a3cd0391 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Mon, 4 Dec 2023 20:17:22 +0100 Subject: [PATCH 1804/4092] Uncomment net6.0 code and remove old code --- osu.Game/Overlays/BeatmapListingOverlay.cs | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index f8784504b8..a645683c5f 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -183,9 +183,7 @@ namespace osu.Game.Overlays // new results may contain beatmaps from a previous page, // this is dodgy but matches web behaviour for now. // see: https://github.com/ppy/osu-web/issues/9270 - // todo: replace custom equality compraer with ExceptBy in net6.0 - // newCards = newCards.ExceptBy(foundContent.Select(c => c.BeatmapSet.OnlineID), c => c.BeatmapSet.OnlineID); - newCards = newCards.Except(foundContent, BeatmapCardEqualityComparer.Default); + newCards = newCards.ExceptBy(foundContent.Select(c => c.BeatmapSet.OnlineID), c => c.BeatmapSet.OnlineID); panelLoadTask = LoadComponentsAsync(newCards, loaded => { @@ -378,21 +376,5 @@ namespace osu.Game.Overlays if (shouldShowMore) filterControl.FetchNextPage(); } - - private class BeatmapCardEqualityComparer : IEqualityComparer - { - public static BeatmapCardEqualityComparer Default { get; } = new BeatmapCardEqualityComparer(); - - public bool Equals(BeatmapCard x, BeatmapCard y) - { - if (ReferenceEquals(x, y)) return true; - if (ReferenceEquals(x, null)) return false; - if (ReferenceEquals(y, null)) return false; - - return x.BeatmapSet.Equals(y.BeatmapSet); - } - - public int GetHashCode(BeatmapCard obj) => obj.BeatmapSet.GetHashCode(); - } } } From 17577a660654cd2e7b5f8a61405064ebed5aacc4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 28 Nov 2023 04:40:28 +0300 Subject: [PATCH 1805/4092] Add visual test case for late miss in argon health display --- .../Gameplay/TestSceneArgonHealthDisplay.cs | 73 +++++++++++++++---- 1 file changed, 57 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs index 6c364e41c7..3197de42d0 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs @@ -24,6 +24,23 @@ namespace osu.Game.Tests.Visual.Gameplay private ArgonHealthDisplay healthDisplay = null!; + protected override void LoadComplete() + { + base.LoadComplete(); + + AddSliderStep("Height", 0, 64, 0, val => + { + if (healthDisplay.IsNotNull()) + healthDisplay.BarHeight.Value = val; + }); + + AddSliderStep("Width", 0, 1f, 0.98f, val => + { + if (healthDisplay.IsNotNull()) + healthDisplay.Width = val; + }); + } + [SetUpSteps] public void SetUpSteps() { @@ -46,27 +63,12 @@ namespace osu.Game.Tests.Visual.Gameplay }, }; }); - - AddSliderStep("Height", 0, 64, 0, val => - { - if (healthDisplay.IsNotNull()) - healthDisplay.BarHeight.Value = val; - }); - - AddSliderStep("Width", 0, 1f, 0.98f, val => - { - if (healthDisplay.IsNotNull()) - healthDisplay.Width = val; - }); } [Test] public void TestHealthDisplayIncrementing() { - AddRepeatStep("apply miss judgement", delegate - { - healthProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Miss }); - }, 5); + AddRepeatStep("apply miss judgement", applyMiss, 5); AddRepeatStep(@"decrease hp slightly", delegate { @@ -87,5 +89,44 @@ namespace osu.Game.Tests.Visual.Gameplay }); }, 3); } + + [Test] + public void TestLateMissAfterConsequentMisses() + { + AddUntilStep("wait for health", () => healthDisplay.Current.Value == 1); + AddStep("apply sequence", () => + { + for (int i = 0; i < 10; i++) + applyMiss(); + + Scheduler.AddDelayed(applyMiss, 500 + 30); + }); + } + + [Test] + public void TestMissAlmostExactlyAfterLastMissAnimation() + { + AddUntilStep("wait for health", () => healthDisplay.Current.Value == 1); + AddStep("apply sequence", () => + { + const double interval = 500 + 15; + + for (int i = 0; i < 5; i++) + { + if (i % 2 == 0) + Scheduler.AddDelayed(applyMiss, i * interval); + else + { + Scheduler.AddDelayed(applyMiss, i * interval); + Scheduler.AddDelayed(applyMiss, i * interval); + } + } + }); + } + + private void applyMiss() + { + healthProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Miss }); + } } } From 5723715ea01b8790699ff2562303caa0d7e6b6ee Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 4 Dec 2023 23:23:48 +0300 Subject: [PATCH 1806/4092] Fix `ArgonHealthDisplay` sometimes behaving weirdly on miss judgements --- .../Screens/Play/HUD/ArgonHealthDisplay.cs | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 3f6791d226..2bef6c312f 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -15,6 +15,7 @@ using osu.Framework.Layout; using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Configuration; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Skinning; @@ -54,6 +55,8 @@ namespace osu.Game.Screens.Play.HUD private ScheduledDelegate? resetMissBarDelegate; + private bool displayingMiss => resetMissBarDelegate != null; + private readonly List missBarVertices = new List(); private readonly List healthBarVertices = new List(); @@ -147,10 +150,13 @@ namespace osu.Game.Screens.Play.HUD }; } + private JudgementResult? pendingJudgementResult; + protected override void LoadComplete() { base.LoadComplete(); + HealthProcessor.NewJudgement += result => pendingJudgementResult = result; Current.BindValueChanged(_ => Scheduler.AddOnce(updateCurrent), true); // we're about to set `RelativeSizeAxes` depending on the value of `UseRelativeSize`. @@ -166,13 +172,22 @@ namespace osu.Game.Screens.Play.HUD private void updateCurrent() { - if (Current.Value >= GlowBarValue) finishMissDisplay(); + var result = pendingJudgementResult; + + if (Current.Value >= GlowBarValue) + finishMissDisplay(); double time = Current.Value > GlowBarValue ? 500 : 250; // TODO: this should probably use interpolation in update. this.TransformTo(nameof(HealthBarValue), Current.Value, time, Easing.OutQuint); - if (resetMissBarDelegate == null) this.TransformTo(nameof(GlowBarValue), Current.Value, time, Easing.OutQuint); + + if (result != null && !result.IsHit) + triggerMissDisplay(); + else if (!displayingMiss) + this.TransformTo(nameof(GlowBarValue), Current.Value, time, Easing.OutQuint); + + pendingJudgementResult = null; } protected override void Update() @@ -196,7 +211,7 @@ namespace osu.Game.Screens.Play.HUD mainBar.TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour.Opacity(0.8f)) .TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour, 300, Easing.OutQuint); - if (resetMissBarDelegate == null) + if (!displayingMiss) { glowBar.TransformTo(nameof(BarPath.BarColour), Colour4.White, 30, Easing.OutQuint) .Then() @@ -208,20 +223,10 @@ namespace osu.Game.Screens.Play.HUD } } - protected override void Miss() + private void triggerMissDisplay() { - base.Miss(); - - if (resetMissBarDelegate != null) - { - resetMissBarDelegate.Cancel(); - resetMissBarDelegate = null; - } - else - { - // Reset any ongoing animation immediately, else things get weird. - this.TransformTo(nameof(GlowBarValue), HealthBarValue); - } + resetMissBarDelegate?.Cancel(); + resetMissBarDelegate = null; this.Delay(500).Schedule(() => { @@ -238,7 +243,7 @@ namespace osu.Game.Screens.Play.HUD private void finishMissDisplay() { - if (resetMissBarDelegate == null) + if (!displayingMiss) return; if (Current.Value > 0) From 4d82a555942d1b6e2e30a0c219a160feec598f81 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 4 Dec 2023 23:23:58 +0300 Subject: [PATCH 1807/4092] Remove method for being unused --- osu.Game/Screens/Play/HUD/HealthDisplay.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HealthDisplay.cs b/osu.Game/Screens/Play/HUD/HealthDisplay.cs index fdbce15b40..13dc05229e 100644 --- a/osu.Game/Screens/Play/HUD/HealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HealthDisplay.cs @@ -45,14 +45,6 @@ namespace osu.Game.Screens.Play.HUD { } - /// - /// Triggered when a resulted in the player losing health. - /// Calls to this method are debounced. - /// - protected virtual void Miss() - { - } - [Resolved] private HUDOverlay? hudOverlay { get; set; } @@ -122,8 +114,6 @@ namespace osu.Game.Screens.Play.HUD { if (judgement.IsHit && judgement.Type != HitResult.IgnoreHit) Scheduler.AddOnce(Flash); - else if (judgement.Judgement.HealthIncreaseFor(judgement) < 0) - Scheduler.AddOnce(Miss); } protected override void Dispose(bool isDisposing) From 629e64d50aa712d226480fe6c91c3ad933bb6a04 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 4 Dec 2023 23:55:31 +0300 Subject: [PATCH 1808/4092] Fix `ArgonHealthDisplay` not displaying miss correctly during initial transition --- osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs | 7 +++++++ osu.Game/Screens/Play/HUD/HealthDisplay.cs | 10 ++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 2bef6c312f..de591cdb31 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -204,6 +204,13 @@ namespace osu.Game.Screens.Play.HUD glowBar.Alpha = (float)Interpolation.DampContinuously(glowBar.Alpha, GlowBarValue > 0 ? 1 : 0, 40, Time.Elapsed); } + protected override void FinishInitialAnimation(double value) + { + base.FinishInitialAnimation(value); + this.TransformTo(nameof(HealthBarValue), value, 500, Easing.OutQuint); + this.TransformTo(nameof(GlowBarValue), value, 250, Easing.OutQuint); + } + protected override void Flash() { base.Flash(); diff --git a/osu.Game/Screens/Play/HUD/HealthDisplay.cs b/osu.Game/Screens/Play/HUD/HealthDisplay.cs index 13dc05229e..7747036826 100644 --- a/osu.Game/Screens/Play/HUD/HealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HealthDisplay.cs @@ -58,7 +58,9 @@ namespace osu.Game.Screens.Play.HUD health = HealthProcessor.Health.GetBoundCopy(); health.BindValueChanged(h => { - finishInitialAnimation(); + if (initialIncrease != null) + FinishInitialAnimation(h.OldValue); + Current.Value = h.NewValue; }); @@ -90,16 +92,16 @@ namespace osu.Game.Screens.Play.HUD Scheduler.AddOnce(Flash); if (newValue >= health.Value) - finishInitialAnimation(); + FinishInitialAnimation(health.Value); }, increase_delay, true); } - private void finishInitialAnimation() + protected virtual void FinishInitialAnimation(double value) { if (initialIncrease == null) return; - initialIncrease?.Cancel(); + initialIncrease.Cancel(); initialIncrease = null; // aside from the repeating `initialIncrease` scheduled task, From 8756dd25c6d7cff903b0ec9e2fce1a2af48e197c Mon Sep 17 00:00:00 2001 From: Guido <75315940+vegguid@users.noreply.github.com> Date: Mon, 4 Dec 2023 22:51:56 +0100 Subject: [PATCH 1809/4092] Changed file chooser in resource selection to show file name when file is selected --- osu.Game/Localisation/EditorSetupStrings.cs | 10 ---------- osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 9 ++------- 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/osu.Game/Localisation/EditorSetupStrings.cs b/osu.Game/Localisation/EditorSetupStrings.cs index 401411365b..eff6f9e6b8 100644 --- a/osu.Game/Localisation/EditorSetupStrings.cs +++ b/osu.Game/Localisation/EditorSetupStrings.cs @@ -179,21 +179,11 @@ namespace osu.Game.Localisation /// public static LocalisableString ClickToSelectTrack => new TranslatableString(getKey(@"click_to_select_track"), @"Click to select a track"); - /// - /// "Click to replace the track" - /// - public static LocalisableString ClickToReplaceTrack => new TranslatableString(getKey(@"click_to_replace_track"), @"Click to replace the track"); - /// /// "Click to select a background image" /// public static LocalisableString ClickToSelectBackground => new TranslatableString(getKey(@"click_to_select_background"), @"Click to select a background image"); - /// - /// "Click to replace the background image" - /// - public static LocalisableString ClickToReplaceBackground => new TranslatableString(getKey(@"click_to_replace_background"), @"Click to replace the background image"); - /// /// "Ruleset ({0})" /// diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 8c84ad90ba..f6d20319cb 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -146,13 +146,8 @@ namespace osu.Game.Screens.Edit.Setup private void updatePlaceholderText() { - audioTrackChooser.Text = audioTrackChooser.Current.Value == null - ? EditorSetupStrings.ClickToSelectTrack - : EditorSetupStrings.ClickToReplaceTrack; - - backgroundChooser.Text = backgroundChooser.Current.Value == null - ? EditorSetupStrings.ClickToSelectBackground - : EditorSetupStrings.ClickToReplaceBackground; + audioTrackChooser.Text = audioTrackChooser.Current.Value?.Name ?? EditorSetupStrings.ClickToSelectTrack; + backgroundChooser.Text = backgroundChooser.Current.Value?.Name ?? EditorSetupStrings.ClickToSelectBackground; } } } From 68907fe1ba93fec9b513c1c6e6072567e7913bb5 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 5 Dec 2023 02:48:11 +0300 Subject: [PATCH 1810/4092] Cleanup pass --- osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs | 34 +++++++++++------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs index 5054c75e97..18d4fef5e8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs @@ -6,7 +6,6 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; -using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; @@ -78,30 +77,29 @@ namespace osu.Game.Rulesets.Osu.Mods foreach (var drawable in playfield.HitObjectContainer.AliveObjects) { - if (drawable is not DrawableOsuHitObject d) - continue; - - switch (d) + switch (drawable) { - case DrawableHitCircle: - case DrawableSlider: - processObject(time, d); + case DrawableHitCircle circle: + processObject(time, circle, 0); + break; + + case DrawableSlider slider: + processObject(time, slider, slider.HitObject.Duration); break; } } } - private void processObject(double time, DrawableOsuHitObject drawable) + private void processObject(double time, DrawableOsuHitObject drawable, double duration) { var hitObject = drawable.HitObject; double baseSpeed = MaxDepth.Value / hitObject.TimePreempt; - double hitObjectDuration = hitObject is Slider s ? s.Duration : 0.0; - double offsetAfterStartTime = hitObjectDuration + hitObject.MaximumJudgementOffset + 500; - double slowSpeed = -minDepth / offsetAfterStartTime; + double offsetAfterStartTime = duration + hitObject.MaximumJudgementOffset + 500; + double slowSpeed = Math.Min(-minDepth / offsetAfterStartTime, baseSpeed); - float decelerationDistance = MaxDepth.Value * 0.2f; - double decelerationTime = (slowSpeed - baseSpeed) * 2 * decelerationDistance / (slowSpeed * slowSpeed - baseSpeed * baseSpeed); + double decelerationTime = hitObject.TimePreempt * 0.2; + float decelerationDistance = (float)(decelerationTime * (baseSpeed + slowSpeed) * 0.5); float z; @@ -109,7 +107,7 @@ namespace osu.Game.Rulesets.Osu.Mods { double appearTime = hitObject.StartTime - hitObject.TimePreempt; float fullDistance = decelerationDistance + (float)(baseSpeed * (hitObject.TimePreempt - decelerationTime)); - z = Interpolation.ValueAt(Math.Max(time, appearTime), fullDistance, decelerationDistance, appearTime, hitObject.StartTime - decelerationTime); + z = fullDistance - (float)((Math.Max(time, appearTime) - appearTime) * baseSpeed); } else if (time < hitObject.StartTime) { @@ -120,11 +118,11 @@ namespace osu.Game.Rulesets.Osu.Mods else { double endTime = hitObject.StartTime + offsetAfterStartTime; - z = Interpolation.ValueAt(Math.Min(time, endTime), 0f, minDepth, hitObject.StartTime, endTime); + z = -(float)((Math.Min(time, endTime) - hitObject.StartTime) * slowSpeed); } float scale = scaleForDepth(z); - drawable.Position = positionAtDepth(scale, hitObject.Position); + drawable.Position = toPlayfieldPosition(scale, hitObject.Position); drawable.Scale = new Vector2(scale); } @@ -132,7 +130,7 @@ namespace osu.Game.Rulesets.Osu.Mods private static float depthForScale(float scale) => 100 / scale + camera_position.Z; - private static Vector2 positionAtDepth(float scale, Vector2 positionAtZeroDepth) + private static Vector2 toPlayfieldPosition(float scale, Vector2 positionAtZeroDepth) { return (positionAtZeroDepth - camera_position.Xy) * scale + camera_position.Xy; } From b90de83f33d330baaf37d71759ac081dd37c04ea Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 5 Dec 2023 11:57:51 +0900 Subject: [PATCH 1811/4092] Replicate integer calculations for tiny tick conversion --- .../CatchBeatmapConversionTest.cs | 1 + .../Objects/JuiceStream.cs | 2 +- .../tiny-ticks-expected-conversion.json | 44 +++++++++++++++++++ .../Resources/Testing/Beatmaps/tiny-ticks.osu | 21 +++++++++ 4 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/tiny-ticks-expected-conversion.json create mode 100644 osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/tiny-ticks.osu diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs index a11aaa5e29..d70b171ff2 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs @@ -28,6 +28,7 @@ namespace osu.Game.Rulesets.Catch.Tests [TestCase("right-bound-hr-offset", new[] { typeof(CatchModHardRock) })] [TestCase("basic-hyperdash")] [TestCase("pixel-jump")] + [TestCase("tiny-ticks")] public new void Test(string name, params Type[] mods) => base.Test(name, mods); protected override IEnumerable CreateConvertValue(HitObject hitObject) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index fb1a86d8c0..019a67a70a 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -82,7 +82,7 @@ namespace osu.Game.Rulesets.Catch.Objects // generate tiny droplets since the last point if (lastEvent != null) { - double sinceLastTick = e.Time - lastEvent.Value.Time; + double sinceLastTick = (int)e.Time - (int)lastEvent.Value.Time; if (sinceLastTick > 80) { diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/tiny-ticks-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/tiny-ticks-expected-conversion.json new file mode 100644 index 0000000000..7a9e848a7b --- /dev/null +++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/tiny-ticks-expected-conversion.json @@ -0,0 +1,44 @@ +{ + "Mappings": [ + { + "StartTime": 27002.0, + "Objects": [ + { + "StartTime": 27002.0, + "Position": 326.0, + "HyperDash": false + }, + { + "StartTime": 27102.0, + "Position": 267.416656, + "HyperDash": false + }, + { + "StartTime": 27238.0, + "Position": 217.484329, + "HyperDash": false + } + ] + }, + { + "StartTime": 27318.0, + "Objects": [ + { + "StartTime": 27318.0, + "Position": 215.0, + "HyperDash": false + }, + { + "StartTime": 27418.0, + "Position": 251.682343, + "HyperDash": false + }, + { + "StartTime": 27554.0, + "Position": 323.347046, + "HyperDash": false + } + ] + } + ] +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/tiny-ticks.osu b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/tiny-ticks.osu new file mode 100644 index 0000000000..7808bd0764 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/tiny-ticks.osu @@ -0,0 +1,21 @@ +osu file format v14 + +[General] +StackLeniency: 0.7 +Mode: 0 + +[Difficulty] +HPDrainRate:4.7 +CircleSize:3.7 +OverallDifficulty:8.4 +ApproachRate:9 +SliderMultiplier:1.57 +SliderTickRate:1 + +[TimingPoints] +476,315.789473684211,4,2,1,50,1,0 +18160,-103.092783505155,4,2,1,70,0,0 + +[HitObjects] +326,119,27002,6,0,P|266:96|196:111,1,114.217502701372,14|0,0:2|0:0,0:0:0:0: +215,85,27318,2,0,P|271:80|323:102,1,114.217502701372,8|0,0:2|0:0,0:0:0:0: \ No newline at end of file From 7fda38d0b02496f4e255e496a73def950deca6a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Dec 2023 14:12:25 +0900 Subject: [PATCH 1812/4092] Use ordinal comparison when searching at song select Bypasses various overheads. In theory should be fine? (until it's not on some language) --- osu.Game/Screens/Select/FilterCriteria.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 812a16c484..811f623ee5 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -176,13 +176,15 @@ namespace osu.Game.Screens.Select { default: case MatchMode.Substring: - return value.Contains(SearchTerm, StringComparison.InvariantCultureIgnoreCase); + // Note that we are using ordinal here to avoid performance issues caused by globalisation concerns. + // See https://github.com/ppy/osu/issues/11571 / https://github.com/dotnet/docs/issues/18423. + return value.Contains(SearchTerm, StringComparison.OrdinalIgnoreCase); case MatchMode.IsolatedPhrase: return Regex.IsMatch(value, $@"(^|\s){Regex.Escape(searchTerm)}($|\s)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); case MatchMode.FullPhrase: - return CultureInfo.InvariantCulture.CompareInfo.Compare(value, searchTerm, CompareOptions.IgnoreCase) == 0; + return CultureInfo.InvariantCulture.CompareInfo.Compare(value, searchTerm, CompareOptions.OrdinalIgnoreCase) == 0; } } From 27e778ae09364981ce5e2768110efde51b4a3f7b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Dec 2023 14:15:05 +0900 Subject: [PATCH 1813/4092] Avoid sorting items when already in the correct sort order --- .../Screens/Select/Carousel/CarouselGroup.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index 9302578038..c353ee98ae 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -86,16 +86,20 @@ namespace osu.Game.Screens.Select.Carousel items.ForEach(c => c.Filter(criteria)); - criteriaComparer = Comparer.Create((x, y) => + // Sorting is expensive, so only perform if it's actually changed. + if (lastCriteria?.Sort != criteria.Sort) { - int comparison = x.CompareTo(criteria, y); - if (comparison != 0) - return comparison; + criteriaComparer = Comparer.Create((x, y) => + { + int comparison = x.CompareTo(criteria, y); + if (comparison != 0) + return comparison; - return x.ItemID.CompareTo(y.ItemID); - }); + return x.ItemID.CompareTo(y.ItemID); + }); - items.Sort(criteriaComparer); + items.Sort(criteriaComparer); + } lastCriteria = criteria; } From 7d602c792da2446375e73d1ed211f3d7f62f432f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 5 Dec 2023 15:09:55 +0900 Subject: [PATCH 1814/4092] Fix legacy tick distance in JuiceStream generation --- .../CatchBeatmapConversionTest.cs | 1 + .../Beatmaps/CatchBeatmapConverter.cs | 4 ++ .../Objects/JuiceStream.cs | 8 ++- .../v8-tick-distance-expected-conversion.json | 54 +++++++++++++++++++ .../Testing/Beatmaps/v8-tick-distance.osu | 19 +++++++ 5 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/v8-tick-distance-expected-conversion.json create mode 100644 osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/v8-tick-distance.osu diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs index d70b171ff2..d1fe213a32 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs @@ -29,6 +29,7 @@ namespace osu.Game.Rulesets.Catch.Tests [TestCase("basic-hyperdash")] [TestCase("pixel-jump")] [TestCase("tiny-ticks")] + [TestCase("v8-tick-distance")] public new void Test(string name, params Type[] mods) => base.Test(name, mods); protected override IEnumerable CreateConvertValue(HitObject hitObject) diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs index 6a24c26844..8c460586b0 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -9,6 +9,7 @@ using System.Threading; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Beatmaps.Legacy; namespace osu.Game.Rulesets.Catch.Beatmaps { @@ -42,6 +43,9 @@ namespace osu.Game.Rulesets.Catch.Beatmaps NewCombo = comboData?.NewCombo ?? false, ComboOffset = comboData?.ComboOffset ?? 0, LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y, + // prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance. + // this results in more (or less) ticks being generated in SliderVelocityMultiplierBindable.Value = value; } + /// + /// An extra multiplier that affects the number of s generated by this . + /// An increase in this value increases , which reduces the number of ticks generated. + /// + public double TickDistanceMultiplier = 1; + [JsonIgnore] private double velocityFactor; @@ -51,7 +57,7 @@ namespace osu.Game.Rulesets.Catch.Objects public double Velocity => velocityFactor * SliderVelocityMultiplier; [JsonIgnore] - public double TickDistance => tickDistanceFactor * SliderVelocityMultiplier; + public double TickDistance => tickDistanceFactor * TickDistanceMultiplier; /// /// The length of one span of this . diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/v8-tick-distance-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/v8-tick-distance-expected-conversion.json new file mode 100644 index 0000000000..82167f37dd --- /dev/null +++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/v8-tick-distance-expected-conversion.json @@ -0,0 +1,54 @@ +{ + "Mappings": [ + { + "StartTime": 81593.0, + "Objects": [ + { + "StartTime": 81593.0, + "Position": 384.0, + "HyperDash": false + }, + { + "StartTime": 81652.0, + "Position": 377.608948, + "HyperDash": false + }, + { + "StartTime": 81712.0, + "Position": 390.3638, + "HyperDash": false + }, + { + "StartTime": 81772.0, + "Position": 407.118683, + "HyperDash": false + }, + { + "StartTime": 81832.0, + "Position": 433.873535, + "HyperDash": false + }, + { + "StartTime": 81891.0, + "Position": 444.482483, + "HyperDash": false + }, + { + "StartTime": 81951.0, + "Position": 437.237366, + "HyperDash": false + }, + { + "StartTime": 82011.0, + "Position": 443.992218, + "HyperDash": false + }, + { + "StartTime": 82107.0, + "Position": 459.0, + "HyperDash": false + } + ] + } + ] +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/v8-tick-distance.osu b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/v8-tick-distance.osu new file mode 100644 index 0000000000..9fdba9dc0b --- /dev/null +++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/v8-tick-distance.osu @@ -0,0 +1,19 @@ +osu file format v7 + +[General] +StackLeniency: 0.7 +Mode: 0 + +[Difficulty] +HPDrainRate:5 +CircleSize:4 +OverallDifficulty:8 +SliderMultiplier:1 +SliderTickRate:1 + +[TimingPoints] +336,342.857142857143,4,1,0,100,1,0 +81588,-200,4,2,0,100,0,0 + +[HitObjects] +384,72,81593,2,12,B|464:72,1,75,4|4 \ No newline at end of file From 42010574b5d27d8ca6925a8b21ba7a61e4391926 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Dec 2023 14:53:47 +0900 Subject: [PATCH 1815/4092] Avoid list construction when doing filtering --- osu.Game/Beatmaps/BeatmapInfoExtensions.cs | 22 +++++++++---------- .../Beatmaps/BeatmapMetadataInfoExtensions.cs | 19 +++++++++++++++- .../Select/Carousel/CarouselBeatmap.cs | 21 +----------------- 3 files changed, 29 insertions(+), 33 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfoExtensions.cs b/osu.Game/Beatmaps/BeatmapInfoExtensions.cs index 3aab9a24e1..a3bc03acc8 100644 --- a/osu.Game/Beatmaps/BeatmapInfoExtensions.cs +++ b/osu.Game/Beatmaps/BeatmapInfoExtensions.cs @@ -1,8 +1,8 @@ // 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 osu.Framework.Localisation; +using osu.Game.Screens.Select; namespace osu.Game.Beatmaps { @@ -29,20 +29,18 @@ namespace osu.Game.Beatmaps return new RomanisableString($"{metadata.GetPreferred(true)}".Trim(), $"{metadata.GetPreferred(false)}".Trim()); } - public static List GetSearchableTerms(this IBeatmapInfo beatmapInfo) + public static bool Match(this IBeatmapInfo beatmapInfo, params FilterCriteria.OptionalTextFilter[] filters) { - var termsList = new List(BeatmapMetadataInfoExtensions.MAX_SEARCHABLE_TERM_COUNT + 1); - - addIfNotNull(beatmapInfo.DifficultyName); - - BeatmapMetadataInfoExtensions.CollectSearchableTerms(beatmapInfo.Metadata, termsList); - return termsList; - - void addIfNotNull(string? s) + foreach (var filter in filters) { - if (!string.IsNullOrEmpty(s)) - termsList.Add(s); + if (filter.Matches(beatmapInfo.DifficultyName)) + return true; + + if (BeatmapMetadataInfoExtensions.Match(beatmapInfo.Metadata, filters)) + return true; } + + return false; } private static string getVersionString(IBeatmapInfo beatmapInfo) => string.IsNullOrEmpty(beatmapInfo.DifficultyName) ? string.Empty : $"[{beatmapInfo.DifficultyName}]"; diff --git a/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs b/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs index be96a66614..ee3afdaef5 100644 --- a/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs +++ b/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs @@ -3,11 +3,14 @@ using System.Collections.Generic; using osu.Framework.Localisation; +using osu.Game.Screens.Select; namespace osu.Game.Beatmaps { public static class BeatmapMetadataInfoExtensions { + internal const int MAX_SEARCHABLE_TERM_COUNT = 7; + /// /// An array of all searchable terms provided in contained metadata. /// @@ -18,7 +21,21 @@ namespace osu.Game.Beatmaps return termsList.ToArray(); } - internal const int MAX_SEARCHABLE_TERM_COUNT = 7; + public static bool Match(IBeatmapMetadataInfo metadataInfo, FilterCriteria.OptionalTextFilter[] filters) + { + foreach (var filter in filters) + { + if (filter.Matches(metadataInfo.Author.Username)) return true; + if (filter.Matches(metadataInfo.Artist)) return true; + if (filter.Matches(metadataInfo.ArtistUnicode)) return true; + if (filter.Matches(metadataInfo.Title)) return true; + if (filter.Matches(metadataInfo.TitleUnicode)) return true; + if (filter.Matches(metadataInfo.Source)) return true; + if (filter.Matches(metadataInfo.Tags)) return true; + } + + return false; + } internal static void CollectSearchableTerms(IBeatmapMetadataInfo metadataInfo, IList termsList) { diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 1d40862df7..8b891a035c 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -66,26 +66,7 @@ namespace osu.Game.Screens.Select.Carousel if (criteria.SearchTerms.Length > 0) { - var searchableTerms = BeatmapInfo.GetSearchableTerms(); - - foreach (FilterCriteria.OptionalTextFilter criteriaTerm in criteria.SearchTerms) - { - bool any = false; - - // ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator - foreach (string searchTerm in searchableTerms) - { - if (!criteriaTerm.Matches(searchTerm)) continue; - - any = true; - break; - } - - if (any) continue; - - match = false; - break; - } + match = BeatmapInfo.Match(criteria.SearchTerms); // if a match wasn't found via text matching of terms, do a second catch-all check matching against online IDs. // this should be done after text matching so we can prioritise matching numbers in metadata. From 45e499778f8696087defd6c116970f918428539f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Dec 2023 15:28:56 +0900 Subject: [PATCH 1816/4092] Search terms before performing other criteria checks Very minor, but putting the more common case towards the start of the method allows early return. --- .../Select/Carousel/CarouselBeatmap.cs | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 8b891a035c..45594bd0e8 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -41,6 +41,21 @@ namespace osu.Game.Screens.Select.Carousel return match; } + if (criteria.SearchTerms.Length > 0) + { + match = BeatmapInfo.Match(criteria.SearchTerms); + + // if a match wasn't found via text matching of terms, do a second catch-all check matching against online IDs. + // this should be done after text matching so we can prioritise matching numbers in metadata. + if (!match && criteria.SearchNumber.HasValue) + { + match = (BeatmapInfo.OnlineID == criteria.SearchNumber.Value) || + (BeatmapInfo.BeatmapSet?.OnlineID == criteria.SearchNumber.Value); + } + } + + if (!match) return false; + match &= !criteria.StarDifficulty.HasFilter || criteria.StarDifficulty.IsInRange(BeatmapInfo.StarRating); match &= !criteria.ApproachRate.HasFilter || criteria.ApproachRate.IsInRange(BeatmapInfo.Difficulty.ApproachRate); match &= !criteria.DrainRate.HasFilter || criteria.DrainRate.IsInRange(BeatmapInfo.Difficulty.DrainRate); @@ -64,21 +79,6 @@ namespace osu.Game.Screens.Select.Carousel if (!match) return false; - if (criteria.SearchTerms.Length > 0) - { - match = BeatmapInfo.Match(criteria.SearchTerms); - - // if a match wasn't found via text matching of terms, do a second catch-all check matching against online IDs. - // this should be done after text matching so we can prioritise matching numbers in metadata. - if (!match && criteria.SearchNumber.HasValue) - { - match = (BeatmapInfo.OnlineID == criteria.SearchNumber.Value) || - (BeatmapInfo.BeatmapSet?.OnlineID == criteria.SearchNumber.Value); - } - } - - if (!match) return false; - match &= criteria.CollectionBeatmapMD5Hashes?.Contains(BeatmapInfo.MD5Hash) ?? true; if (match && criteria.RulesetCriteria != null) match &= criteria.RulesetCriteria.Matches(BeatmapInfo); From 3aaba3183b91d7a2608002929ab236604c99b2bc Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 5 Dec 2023 15:39:23 +0900 Subject: [PATCH 1817/4092] Match stable precision when generating catch bananas --- .../CatchBeatmapConversionTest.cs | 1 + .../Objects/BananaShower.cs | 14 +- ...spinner-precision-expected-conversion.json | 649 ++++++++++++++++++ .../Testing/Beatmaps/spinner-precision.osu | 20 + 4 files changed, 677 insertions(+), 7 deletions(-) create mode 100644 osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-precision-expected-conversion.json create mode 100644 osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-precision.osu diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs index d1fe213a32..5528ce0bfa 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs @@ -30,6 +30,7 @@ namespace osu.Game.Rulesets.Catch.Tests [TestCase("pixel-jump")] [TestCase("tiny-ticks")] [TestCase("v8-tick-distance")] + [TestCase("spinner-precision")] public new void Test(string name, params Type[] mods) => base.Test(name, mods); protected override IEnumerable CreateConvertValue(HitObject hitObject) diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs index b05c8e5f77..abeb7fe61d 100644 --- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs @@ -23,29 +23,29 @@ namespace osu.Game.Rulesets.Catch.Objects private void createBananas(CancellationToken cancellationToken) { - double spacing = Duration; + int startTime = (int)StartTime; + int endTime = (int)EndTime; + float spacing = (float)(EndTime - StartTime); while (spacing > 100) spacing /= 2; if (spacing <= 0) return; - double time = StartTime; - int i = 0; + int count = 0; - while (time <= EndTime) + for (float time = startTime; time <= endTime; time += spacing) { cancellationToken.ThrowIfCancellationRequested(); AddNested(new Banana { StartTime = time, - BananaIndex = i, + BananaIndex = count, Samples = new List { new Banana.BananaHitSampleInfo(CreateHitSampleInfo().Volume) } }); - time += spacing; - i++; + count++; } } diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-precision-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-precision-expected-conversion.json new file mode 100644 index 0000000000..95a0c8b34e --- /dev/null +++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-precision-expected-conversion.json @@ -0,0 +1,649 @@ +{ + "Mappings": [ + { + "StartTime": 276419.0, + "Objects": [ + { + "StartTime": 276419.0, + "Position": 65.0, + "HyperDash": false + }, + { + "StartTime": 276494.0, + "Position": 482.0, + "HyperDash": false + }, + { + "StartTime": 276569.0, + "Position": 164.0, + "HyperDash": false + }, + { + "StartTime": 276645.0, + "Position": 315.0, + "HyperDash": false + }, + { + "StartTime": 276720.0, + "Position": 145.0, + "HyperDash": false + }, + { + "StartTime": 276795.0, + "Position": 159.0, + "HyperDash": false + }, + { + "StartTime": 276871.0, + "Position": 310.0, + "HyperDash": false + }, + { + "StartTime": 276946.0, + "Position": 441.0, + "HyperDash": false + }, + { + "StartTime": 277021.0, + "Position": 428.0, + "HyperDash": false + }, + { + "StartTime": 277097.0, + "Position": 243.0, + "HyperDash": false + }, + { + "StartTime": 277172.0, + "Position": 422.0, + "HyperDash": false + }, + { + "StartTime": 277247.0, + "Position": 481.0, + "HyperDash": false + }, + { + "StartTime": 277323.0, + "Position": 104.0, + "HyperDash": false + }, + { + "StartTime": 277398.0, + "Position": 473.0, + "HyperDash": false + }, + { + "StartTime": 277473.0, + "Position": 135.0, + "HyperDash": false + }, + { + "StartTime": 277549.0, + "Position": 360.0, + "HyperDash": false + }, + { + "StartTime": 277624.0, + "Position": 123.0, + "HyperDash": false + }, + { + "StartTime": 277699.0, + "Position": 42.0, + "HyperDash": false + }, + { + "StartTime": 277775.0, + "Position": 393.0, + "HyperDash": false + }, + { + "StartTime": 277850.0, + "Position": 75.0, + "HyperDash": false + }, + { + "StartTime": 277925.0, + "Position": 377.0, + "HyperDash": false + }, + { + "StartTime": 278001.0, + "Position": 354.0, + "HyperDash": false + }, + { + "StartTime": 278076.0, + "Position": 287.0, + "HyperDash": false + }, + { + "StartTime": 278151.0, + "Position": 361.0, + "HyperDash": false + }, + { + "StartTime": 278227.0, + "Position": 479.0, + "HyperDash": false + }, + { + "StartTime": 278302.0, + "Position": 346.0, + "HyperDash": false + }, + { + "StartTime": 278377.0, + "Position": 266.0, + "HyperDash": false + }, + { + "StartTime": 278453.0, + "Position": 400.0, + "HyperDash": false + }, + { + "StartTime": 278528.0, + "Position": 202.0, + "HyperDash": false + }, + { + "StartTime": 278603.0, + "Position": 500.0, + "HyperDash": false + }, + { + "StartTime": 278679.0, + "Position": 80.0, + "HyperDash": false + }, + { + "StartTime": 278754.0, + "Position": 399.0, + "HyperDash": false + }, + { + "StartTime": 278830.0, + "Position": 455.0, + "HyperDash": false + }, + { + "StartTime": 278905.0, + "Position": 105.0, + "HyperDash": false + }, + { + "StartTime": 278980.0, + "Position": 100.0, + "HyperDash": false + }, + { + "StartTime": 279056.0, + "Position": 195.0, + "HyperDash": false + }, + { + "StartTime": 279131.0, + "Position": 106.0, + "HyperDash": false + }, + { + "StartTime": 279206.0, + "Position": 305.0, + "HyperDash": false + }, + { + "StartTime": 279282.0, + "Position": 225.0, + "HyperDash": false + }, + { + "StartTime": 279357.0, + "Position": 79.0, + "HyperDash": false + }, + { + "StartTime": 279432.0, + "Position": 38.0, + "HyperDash": false + }, + { + "StartTime": 279508.0, + "Position": 99.0, + "HyperDash": false + }, + { + "StartTime": 279583.0, + "Position": 79.0, + "HyperDash": false + }, + { + "StartTime": 279658.0, + "Position": 169.0, + "HyperDash": false + }, + { + "StartTime": 279734.0, + "Position": 238.0, + "HyperDash": false + }, + { + "StartTime": 279809.0, + "Position": 511.0, + "HyperDash": false + }, + { + "StartTime": 279884.0, + "Position": 58.0, + "HyperDash": false + }, + { + "StartTime": 279960.0, + "Position": 368.0, + "HyperDash": false + }, + { + "StartTime": 280035.0, + "Position": 52.0, + "HyperDash": false + }, + { + "StartTime": 280110.0, + "Position": 327.0, + "HyperDash": false + }, + { + "StartTime": 280186.0, + "Position": 226.0, + "HyperDash": false + }, + { + "StartTime": 280261.0, + "Position": 110.0, + "HyperDash": false + }, + { + "StartTime": 280336.0, + "Position": 3.0, + "HyperDash": false + }, + { + "StartTime": 280412.0, + "Position": 26.0, + "HyperDash": false + }, + { + "StartTime": 280487.0, + "Position": 173.0, + "HyperDash": false + }, + { + "StartTime": 280562.0, + "Position": 18.0, + "HyperDash": false + }, + { + "StartTime": 280638.0, + "Position": 310.0, + "HyperDash": false + }, + { + "StartTime": 280713.0, + "Position": 394.0, + "HyperDash": false + }, + { + "StartTime": 280788.0, + "Position": 406.0, + "HyperDash": false + }, + { + "StartTime": 280864.0, + "Position": 262.0, + "HyperDash": false + }, + { + "StartTime": 280939.0, + "Position": 278.0, + "HyperDash": false + }, + { + "StartTime": 281014.0, + "Position": 171.0, + "HyperDash": false + }, + { + "StartTime": 281090.0, + "Position": 22.0, + "HyperDash": false + }, + { + "StartTime": 281165.0, + "Position": 187.0, + "HyperDash": false + }, + { + "StartTime": 281241.0, + "Position": 124.0, + "HyperDash": false + }, + { + "StartTime": 281316.0, + "Position": 454.0, + "HyperDash": false + }, + { + "StartTime": 281391.0, + "Position": 16.0, + "HyperDash": false + }, + { + "StartTime": 281467.0, + "Position": 61.0, + "HyperDash": false + }, + { + "StartTime": 281542.0, + "Position": 161.0, + "HyperDash": false + }, + { + "StartTime": 281617.0, + "Position": 243.0, + "HyperDash": false + }, + { + "StartTime": 281693.0, + "Position": 375.0, + "HyperDash": false + }, + { + "StartTime": 281768.0, + "Position": 247.0, + "HyperDash": false + }, + { + "StartTime": 281843.0, + "Position": 162.0, + "HyperDash": false + }, + { + "StartTime": 281919.0, + "Position": 383.0, + "HyperDash": false + }, + { + "StartTime": 281994.0, + "Position": 127.0, + "HyperDash": false + }, + { + "StartTime": 282069.0, + "Position": 161.0, + "HyperDash": false + }, + { + "StartTime": 282145.0, + "Position": 332.0, + "HyperDash": false + }, + { + "StartTime": 282220.0, + "Position": 356.0, + "HyperDash": false + }, + { + "StartTime": 282295.0, + "Position": 362.0, + "HyperDash": false + }, + { + "StartTime": 282371.0, + "Position": 347.0, + "HyperDash": false + }, + { + "StartTime": 282446.0, + "Position": 252.0, + "HyperDash": false + }, + { + "StartTime": 282521.0, + "Position": 477.0, + "HyperDash": false + }, + { + "StartTime": 282597.0, + "Position": 358.0, + "HyperDash": false + }, + { + "StartTime": 282672.0, + "Position": 17.0, + "HyperDash": false + }, + { + "StartTime": 282747.0, + "Position": 399.0, + "HyperDash": false + }, + { + "StartTime": 282823.0, + "Position": 280.0, + "HyperDash": false + }, + { + "StartTime": 282898.0, + "Position": 304.0, + "HyperDash": false + }, + { + "StartTime": 282973.0, + "Position": 221.0, + "HyperDash": false + }, + { + "StartTime": 283049.0, + "Position": 407.0, + "HyperDash": false + }, + { + "StartTime": 283124.0, + "Position": 287.0, + "HyperDash": false + }, + { + "StartTime": 283199.0, + "Position": 135.0, + "HyperDash": false + }, + { + "StartTime": 283275.0, + "Position": 437.0, + "HyperDash": false + }, + { + "StartTime": 283350.0, + "Position": 289.0, + "HyperDash": false + }, + { + "StartTime": 283425.0, + "Position": 464.0, + "HyperDash": false + }, + { + "StartTime": 283501.0, + "Position": 36.0, + "HyperDash": false + }, + { + "StartTime": 283576.0, + "Position": 378.0, + "HyperDash": false + }, + { + "StartTime": 283652.0, + "Position": 297.0, + "HyperDash": false + }, + { + "StartTime": 283727.0, + "Position": 418.0, + "HyperDash": false + }, + { + "StartTime": 283802.0, + "Position": 329.0, + "HyperDash": false + }, + { + "StartTime": 283878.0, + "Position": 338.0, + "HyperDash": false + }, + { + "StartTime": 283953.0, + "Position": 394.0, + "HyperDash": false + }, + { + "StartTime": 284028.0, + "Position": 40.0, + "HyperDash": false + }, + { + "StartTime": 284104.0, + "Position": 13.0, + "HyperDash": false + }, + { + "StartTime": 284179.0, + "Position": 80.0, + "HyperDash": false + }, + { + "StartTime": 284254.0, + "Position": 138.0, + "HyperDash": false + }, + { + "StartTime": 284330.0, + "Position": 311.0, + "HyperDash": false + }, + { + "StartTime": 284405.0, + "Position": 216.0, + "HyperDash": false + }, + { + "StartTime": 284480.0, + "Position": 310.0, + "HyperDash": false + }, + { + "StartTime": 284556.0, + "Position": 397.0, + "HyperDash": false + }, + { + "StartTime": 284631.0, + "Position": 214.0, + "HyperDash": false + }, + { + "StartTime": 284706.0, + "Position": 505.0, + "HyperDash": false + }, + { + "StartTime": 284782.0, + "Position": 173.0, + "HyperDash": false + }, + { + "StartTime": 284857.0, + "Position": 295.0, + "HyperDash": false + }, + { + "StartTime": 284932.0, + "Position": 199.0, + "HyperDash": false + }, + { + "StartTime": 285008.0, + "Position": 494.0, + "HyperDash": false + }, + { + "StartTime": 285083.0, + "Position": 293.0, + "HyperDash": false + }, + { + "StartTime": 285158.0, + "Position": 115.0, + "HyperDash": false + }, + { + "StartTime": 285234.0, + "Position": 412.0, + "HyperDash": false + }, + { + "StartTime": 285309.0, + "Position": 506.0, + "HyperDash": false + }, + { + "StartTime": 285384.0, + "Position": 293.0, + "HyperDash": false + }, + { + "StartTime": 285460.0, + "Position": 346.0, + "HyperDash": false + }, + { + "StartTime": 285535.0, + "Position": 117.0, + "HyperDash": false + }, + { + "StartTime": 285610.0, + "Position": 285.0, + "HyperDash": false + }, + { + "StartTime": 285686.0, + "Position": 17.0, + "HyperDash": false + }, + { + "StartTime": 285761.0, + "Position": 238.0, + "HyperDash": false + }, + { + "StartTime": 285836.0, + "Position": 222.0, + "HyperDash": false + }, + { + "StartTime": 285912.0, + "Position": 450.0, + "HyperDash": false + }, + { + "StartTime": 285987.0, + "Position": 67.0, + "HyperDash": false + } + ] + } + ] +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-precision.osu b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-precision.osu new file mode 100644 index 0000000000..2ba1fea357 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-precision.osu @@ -0,0 +1,20 @@ +osu file format v14 + +[General] +StackLeniency: 0.8 +Mode: 0 + +[Difficulty] +HPDrainRate:5 +CircleSize:3 +OverallDifficulty:8 +ApproachRate:9.2 +SliderMultiplier:1.7 +SliderTickRate:1 + +[TimingPoints] +276254,995.850622406639,4,2,1,70,1,0 +276254,-100,4,2,1,70,0,0 + +[HitObjects] +256,192,276419,12,4,286062,2:3:0:0: From f317e06da14040d2fb5fbbc7375bbf12c729c1e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Dec 2023 16:54:44 +0900 Subject: [PATCH 1818/4092] Use `DangerousActionDialog` --- .../Overlays/Dialog/DangerousActionDialog.cs | 8 ++++++- .../Multiplayer/Match/ConfirmAbortDialog.cs | 22 ++----------------- .../Multiplayer/Match/MatchStartControl.cs | 12 ++++++++++ 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/osu.Game/Overlays/Dialog/DangerousActionDialog.cs b/osu.Game/Overlays/Dialog/DangerousActionDialog.cs index c86570386f..42a3ff827c 100644 --- a/osu.Game/Overlays/Dialog/DangerousActionDialog.cs +++ b/osu.Game/Overlays/Dialog/DangerousActionDialog.cs @@ -23,6 +23,11 @@ namespace osu.Game.Overlays.Dialog /// protected Action? DangerousAction { get; set; } + /// + /// The action to perform if cancelled. + /// + protected Action? CancelAction { get; set; } + protected DangerousActionDialog() { HeaderText = DeleteConfirmationDialogStrings.HeaderText; @@ -38,7 +43,8 @@ namespace osu.Game.Overlays.Dialog }, new PopupDialogCancelButton { - Text = DeleteConfirmationDialogStrings.Cancel + Text = DeleteConfirmationDialogStrings.Cancel, + Action = () => CancelAction?.Invoke() } }; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ConfirmAbortDialog.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ConfirmAbortDialog.cs index 06f2b2c8f6..0793981f41 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ConfirmAbortDialog.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ConfirmAbortDialog.cs @@ -1,33 +1,15 @@ // 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.Graphics.Sprites; using osu.Game.Overlays.Dialog; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { - public partial class ConfirmAbortDialog : PopupDialog + public partial class ConfirmAbortDialog : DangerousActionDialog { - public ConfirmAbortDialog(Action onConfirm, Action onCancel) + public ConfirmAbortDialog() { HeaderText = "Are you sure you want to abort the match?"; - - Icon = FontAwesome.Solid.ExclamationTriangle; - - Buttons = new PopupDialogButton[] - { - new PopupDialogDangerousButton - { - Text = @"Yes", - Action = onConfirm - }, - new PopupDialogCancelButton - { - Text = @"No I didn't mean to", - Action = onCancel - }, - }; } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs index 99934acaae..ba3508b24f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs @@ -17,6 +17,7 @@ using osu.Framework.Threading; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.Countdown; using osu.Game.Overlays; +using osu.Game.Overlays.Dialog; using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match @@ -247,5 +248,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match countReady = newCountReady; }); } + + public partial class ConfirmAbortDialog : DangerousActionDialog + { + public ConfirmAbortDialog(Action abortMatch, Action cancel) + { + HeaderText = "Are you sure you want to abort the match?"; + + DangerousAction = abortMatch; + CancelAction = cancel; + } + } } } From 02178d8e611dfc3c8a8335f41c68d6fab5dfbe0c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Dec 2023 16:58:16 +0900 Subject: [PATCH 1819/4092] Remove usage of `case-when` --- .../Match/MultiplayerReadyButton.cs | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index 368e5210de..7ce3dde7c2 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -149,10 +149,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { switch (localUser?.State) { - default: - Text = "Ready"; - break; - case MultiplayerUserState.Spectating: case MultiplayerUserState.Ready: Text = multiplayerClient.IsHost @@ -160,9 +156,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match : $"Waiting for host... {countText}"; break; - // Show the abort button for the host as long as gameplay is in progress. - case MultiplayerUserState when multiplayerClient.IsHost && room.State != MultiplayerRoomState.Open: - Text = "Abort the match"; + default: + // Show the abort button for the host as long as gameplay is in progress. + if (multiplayerClient.IsHost && room.State != MultiplayerRoomState.Open) + Text = "Abort the match"; + else + Text = "Ready"; break; } } @@ -197,7 +196,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match switch (localUser?.State) { default: - setGreen(); + // Show the abort button for the host as long as gameplay is in progress. + if (multiplayerClient.IsHost && room.State != MultiplayerRoomState.Open) + setRed(); + else + setGreen(); break; case MultiplayerUserState.Spectating: @@ -208,11 +211,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match setYellow(); break; - - // Show the abort button for the host as long as gameplay is in progress. - case MultiplayerUserState when multiplayerClient.IsHost && room.State != MultiplayerRoomState.Open: - setRed(); - break; } void setYellow() => BackgroundColour = colours.YellowDark; From 8704dc3505a934f42f2259ec734bb072aa940282 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Dec 2023 18:20:27 +0900 Subject: [PATCH 1820/4092] Fix change in filter behaviour --- osu.Game/Beatmaps/BeatmapInfoExtensions.cs | 12 ++++++++---- .../Beatmaps/BeatmapMetadataInfoExtensions.cs | 19 ++++++++----------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfoExtensions.cs b/osu.Game/Beatmaps/BeatmapInfoExtensions.cs index a3bc03acc8..b00d0ba316 100644 --- a/osu.Game/Beatmaps/BeatmapInfoExtensions.cs +++ b/osu.Game/Beatmaps/BeatmapInfoExtensions.cs @@ -34,13 +34,17 @@ namespace osu.Game.Beatmaps foreach (var filter in filters) { if (filter.Matches(beatmapInfo.DifficultyName)) - return true; + continue; - if (BeatmapMetadataInfoExtensions.Match(beatmapInfo.Metadata, filters)) - return true; + if (BeatmapMetadataInfoExtensions.Match(beatmapInfo.Metadata, filter)) + continue; + + // failed to match a single filter at all - fail the whole match. + return false; } - return false; + // got through all filters without failing any - pass the whole match. + return true; } private static string getVersionString(IBeatmapInfo beatmapInfo) => string.IsNullOrEmpty(beatmapInfo.DifficultyName) ? string.Empty : $"[{beatmapInfo.DifficultyName}]"; diff --git a/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs b/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs index ee3afdaef5..198469dba6 100644 --- a/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs +++ b/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs @@ -21,18 +21,15 @@ namespace osu.Game.Beatmaps return termsList.ToArray(); } - public static bool Match(IBeatmapMetadataInfo metadataInfo, FilterCriteria.OptionalTextFilter[] filters) + public static bool Match(IBeatmapMetadataInfo metadataInfo, FilterCriteria.OptionalTextFilter filter) { - foreach (var filter in filters) - { - if (filter.Matches(metadataInfo.Author.Username)) return true; - if (filter.Matches(metadataInfo.Artist)) return true; - if (filter.Matches(metadataInfo.ArtistUnicode)) return true; - if (filter.Matches(metadataInfo.Title)) return true; - if (filter.Matches(metadataInfo.TitleUnicode)) return true; - if (filter.Matches(metadataInfo.Source)) return true; - if (filter.Matches(metadataInfo.Tags)) return true; - } + if (filter.Matches(metadataInfo.Author.Username)) return true; + if (filter.Matches(metadataInfo.Artist)) return true; + if (filter.Matches(metadataInfo.ArtistUnicode)) return true; + if (filter.Matches(metadataInfo.Title)) return true; + if (filter.Matches(metadataInfo.TitleUnicode)) return true; + if (filter.Matches(metadataInfo.Source)) return true; + if (filter.Matches(metadataInfo.Tags)) return true; return false; } From 4644c4e7a2c8676df418def1b6630e63253ebd81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 5 Dec 2023 12:43:32 +0100 Subject: [PATCH 1821/4092] Remove unused class --- .../Multiplayer/Match/ConfirmAbortDialog.cs | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Match/ConfirmAbortDialog.cs diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ConfirmAbortDialog.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ConfirmAbortDialog.cs deleted file mode 100644 index 0793981f41..0000000000 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ConfirmAbortDialog.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.Overlays.Dialog; - -namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match -{ - public partial class ConfirmAbortDialog : DangerousActionDialog - { - public ConfirmAbortDialog() - { - HeaderText = "Are you sure you want to abort the match?"; - } - } -} From cda55065e7f1e574bca8f9e8e21687ef2f4d686e Mon Sep 17 00:00:00 2001 From: Rodrigo Pina Date: Tue, 5 Dec 2023 12:56:24 +0000 Subject: [PATCH 1822/4092] Simplified ban order logic Implemented tests to make sure logic works as intended --- .../Screens/TestSceneMapPoolScreen.cs | 107 ++++++++++++++++++ .../Screens/MapPool/MapPoolScreen.cs | 26 +++-- 2 files changed, 126 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs index 7b2c1ba336..24dfb95317 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs @@ -5,6 +5,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; using osu.Framework.Testing; using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; @@ -151,6 +152,105 @@ namespace osu.Game.Tournament.Tests.Screens }); } + [Test] + public void TestSingleTeamBan() + { + AddStep("set ban count", () => Ladder.CurrentMatch.Value!.Round.Value!.BanCount.Value = 1); + + AddStep("load some maps", () => + { + Ladder.CurrentMatch.Value!.Round.Value!.Beatmaps.Clear(); + + for (int i = 0; i < 4; i++) + addBeatmap(); + }); + + AddStep("update displayed maps", () => Ladder.SplitMapPoolByMods.Value = false); + + AddStep("perform bans", () => + { + var tournamentMaps = screen.ChildrenOfType().ToList(); + + screen.ChildrenOfType().Where(btn => btn.Text == "Red Ban").First().TriggerClick(); + + PerformMapAction(tournamentMaps[0]); + PerformMapAction(tournamentMaps[1]); + }); + + AddAssert("ensure 1 ban per team", () => Ladder.CurrentMatch.Value!.PicksBans.Count() == 2 && Ladder.CurrentMatch.Value!.PicksBans.Last().Team == TeamColour.Blue); + + AddStep("reset match", () => + { + InputManager.UseParentInput = true; + Ladder.CurrentMatch.Value = new TournamentMatch(); + Ladder.CurrentMatch.Value = Ladder.Matches.First(); + Ladder.CurrentMatch.Value.PicksBans.Clear(); + }); + } + + [Test] + public void TestMultipleTeamBans() + { + AddStep("set ban count", () => Ladder.CurrentMatch.Value!.Round.Value!.BanCount.Value = 3); + + AddStep("load some maps", () => + { + Ladder.CurrentMatch.Value!.Round.Value!.Beatmaps.Clear(); + + for (int i = 0; i < 12; i++) + addBeatmap(); + }); + + AddStep("update displayed maps", () => Ladder.SplitMapPoolByMods.Value = false); + + AddStep("red team ban", () => + { + var tournamentMaps = screen.ChildrenOfType().ToList(); + + screen.ChildrenOfType().Where(btn => btn.Text == "Red Ban").First().TriggerClick(); + + PerformMapAction(tournamentMaps[0]); + }); + + AddAssert("ensure red team ban", () => Ladder.CurrentMatch.Value!.PicksBans.Last().Team == TeamColour.Red); + + AddStep("blue team bans", () => + { + var tournamentMaps = screen.ChildrenOfType().ToList(); + + PerformMapAction(tournamentMaps[1]); + PerformMapAction(tournamentMaps[2]); + }); + + AddAssert("ensure blue team double ban", () => Ladder.CurrentMatch.Value!.PicksBans.Count(ban => ban.Team == TeamColour.Blue) == 2); + + AddStep("red team bans", () => + { + var tournamentMaps = screen.ChildrenOfType().ToList(); + + PerformMapAction(tournamentMaps[3]); + PerformMapAction(tournamentMaps[4]); + }); + + AddAssert("ensure red team double ban", () => Ladder.CurrentMatch.Value!.PicksBans.Count(ban => ban.Team == TeamColour.Red) == 3); + + AddStep("blue team bans", () => + { + var tournamentMaps = screen.ChildrenOfType().ToList(); + + PerformMapAction(tournamentMaps[5]); + }); + + AddAssert("ensure blue team ban", () => Ladder.CurrentMatch.Value!.PicksBans.Last().Team == TeamColour.Blue); + + AddStep("reset match", () => + { + InputManager.UseParentInput = true; + Ladder.CurrentMatch.Value = new TournamentMatch(); + Ladder.CurrentMatch.Value = Ladder.Matches.First(); + Ladder.CurrentMatch.Value.PicksBans.Clear(); + }); + } private void addBeatmap(string mods = "NM") { Ladder.CurrentMatch.Value!.Round.Value!.Beatmaps.Add(new RoundBeatmap @@ -159,5 +259,12 @@ namespace osu.Game.Tournament.Tests.Screens Mods = mods }); } + + private void PerformMapAction(TournamentBeatmapPanel map) + { + InputManager.MoveMouseTo(map); + + InputManager.Click(osuTK.Input.MouseButton.Left); + } } } diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs index 1223fd8464..72134ccb51 100644 --- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs +++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs @@ -153,23 +153,35 @@ namespace osu.Game.Tournament.Screens.MapPool const TeamColour roll_winner = TeamColour.Red; //todo: draw from match - var previousBan = CurrentMatch.Value.PicksBans.LastOrDefault()?.Team ?? roll_winner; + var previousColour = CurrentMatch.Value.PicksBans.LastOrDefault()?.Team ?? roll_winner; - var nextColour = previousBan == TeamColour.Red ? TeamColour.Blue : TeamColour.Red; + TeamColour nextColour; bool hasAllBans = CurrentMatch.Value.PicksBans.Count(p => p.Type == ChoiceType.Ban) >= totalBansRequired; if (!hasAllBans) - // If it's the third ban or later, we need to check if it's the team's first or second ban in a row - nextColour = (CurrentMatch.Value.PicksBans.Count >= 2 ? CurrentMatch.Value.PicksBans[^2]?.Team : previousBan) == TeamColour.Red ? TeamColour.Blue : TeamColour.Red; - - if (hasAllBans && pickType == ChoiceType.Ban) { - // When switching from bans to picks, we don't rotate the team colour. + // Ban phase. + // Switch teams every second ban. + nextColour = CurrentMatch.Value.PicksBans.Count % 2 == 1 + ? getOppositeTeamColour(previousColour) + : previousColour; + } + else if (pickType == ChoiceType.Ban) + { + // Switching from bans to picks - stay with the last team that was banning. nextColour = pickColour; } + else + { + // Pick phase. + // Switch teams every pick. + nextColour = getOppositeTeamColour(previousColour); + } setMode(nextColour, hasAllBans ? ChoiceType.Pick : ChoiceType.Ban); + + TeamColour getOppositeTeamColour(TeamColour colour) => colour == TeamColour.Red ? TeamColour.Blue : TeamColour.Red; } protected override bool OnMouseDown(MouseDownEvent e) From 594ea4da5f5324bb4cce86b071acff13a9a1cb1d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 5 Dec 2023 16:00:20 +0300 Subject: [PATCH 1823/4092] Apply suggested behaviour --- osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs index 18d4fef5e8..16517a2f36 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs @@ -103,13 +103,7 @@ namespace osu.Game.Rulesets.Osu.Mods float z; - if (time < hitObject.StartTime - decelerationTime) - { - double appearTime = hitObject.StartTime - hitObject.TimePreempt; - float fullDistance = decelerationDistance + (float)(baseSpeed * (hitObject.TimePreempt - decelerationTime)); - z = fullDistance - (float)((Math.Max(time, appearTime) - appearTime) * baseSpeed); - } - else if (time < hitObject.StartTime) + if (time < hitObject.StartTime) { double timeOffset = time - (hitObject.StartTime - decelerationTime); double deceleration = (slowSpeed - baseSpeed) / decelerationTime; From 927cfe42570db2edb8e51cbcfd0a729e6b1cb6e5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 5 Dec 2023 19:44:01 +0300 Subject: [PATCH 1824/4092] Fix health processor event leaks --- .../Screens/Play/HUD/ArgonHealthDisplay.cs | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 2bef6c312f..e044db7bb2 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -156,8 +157,8 @@ namespace osu.Game.Screens.Play.HUD { base.LoadComplete(); - HealthProcessor.NewJudgement += result => pendingJudgementResult = result; - Current.BindValueChanged(_ => Scheduler.AddOnce(updateCurrent), true); + HealthProcessor.NewJudgement += onNewJudgement; + Current.BindValueChanged(onCurrentChanged, true); // we're about to set `RelativeSizeAxes` depending on the value of `UseRelativeSize`. // setting `RelativeSizeAxes` internally transforms absolute sizing to relative and back to keep the size the same, @@ -170,7 +171,12 @@ namespace osu.Game.Screens.Play.HUD BarHeight.BindValueChanged(_ => updatePath(), true); } - private void updateCurrent() + private void onNewJudgement(JudgementResult result) => pendingJudgementResult = result; + + private void onCurrentChanged(ValueChangedEvent valueChangedEvent) + => Scheduler.AddOnce(updateDisplay); + + private void updateDisplay() { var result = pendingJudgementResult; @@ -333,6 +339,14 @@ namespace osu.Game.Screens.Play.HUD mainBar.Position = healthBarVertices[0]; } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (HealthProcessor.IsNotNull()) + HealthProcessor.NewJudgement -= onNewJudgement; + } + private partial class BackgroundPath : SmoothPath { protected override Color4 ColourAt(float position) From 9496cdf42bf9d024ca40a0622677e60a233693ed Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 5 Dec 2023 19:44:14 +0300 Subject: [PATCH 1825/4092] Add explanatory note for scheduling --- osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index e044db7bb2..9993ca1ef6 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -174,6 +174,7 @@ namespace osu.Game.Screens.Play.HUD private void onNewJudgement(JudgementResult result) => pendingJudgementResult = result; private void onCurrentChanged(ValueChangedEvent valueChangedEvent) + // schedule display updates one frame later to ensure we know the judgement result causing this change (if there is one). => Scheduler.AddOnce(updateDisplay); private void updateDisplay() From 986f4fa407caf9e5906444b2fa4ec0cca5a4b68b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 5 Dec 2023 21:55:25 +0300 Subject: [PATCH 1826/4092] Add test scenarios for same-frame judgements --- .../Gameplay/TestSceneArgonHealthDisplay.cs | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs index 3197de42d0..d863755a85 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs @@ -83,13 +83,18 @@ namespace osu.Game.Tests.Visual.Gameplay AddRepeatStep(@"increase hp with flash", delegate { healthProcessor.Health.Value += 0.1f; - healthProcessor.ApplyResult(new JudgementResult(new HitCircle(), new OsuJudgement()) - { - Type = HitResult.Perfect - }); + applyPerfectHit(); }, 3); } + private void applyPerfectHit() + { + healthProcessor.ApplyResult(new JudgementResult(new HitCircle(), new OsuJudgement()) + { + Type = HitResult.Perfect + }); + } + [Test] public void TestLateMissAfterConsequentMisses() { @@ -124,6 +129,29 @@ namespace osu.Game.Tests.Visual.Gameplay }); } + [Test] + public void TestMissThenHitAtSameUpdateFrame() + { + AddUntilStep("wait for health", () => healthDisplay.Current.Value == 1); + AddStep("set half health", () => healthProcessor.Health.Value = 0.5f); + AddStep("apply miss and hit", () => + { + applyMiss(); + applyMiss(); + applyPerfectHit(); + applyPerfectHit(); + }); + AddWaitStep("wait", 3); + AddStep("apply miss and cancel with hit", () => + { + applyMiss(); + applyPerfectHit(); + applyPerfectHit(); + applyPerfectHit(); + applyPerfectHit(); + }); + } + private void applyMiss() { healthProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Miss }); From 20fd458fac9f85140328d22948fb33a53d2c2c78 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 5 Dec 2023 21:57:53 +0300 Subject: [PATCH 1827/4092] Perserve miss animation when followed by a hit at same frame --- .../Gameplay/TestSceneArgonHealthDisplay.cs | 3 +++ .../Screens/Play/HUD/ArgonHealthDisplay.cs | 21 ++++++++++--------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs index d863755a85..59819d781f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs @@ -134,6 +134,7 @@ namespace osu.Game.Tests.Visual.Gameplay { AddUntilStep("wait for health", () => healthDisplay.Current.Value == 1); AddStep("set half health", () => healthProcessor.Health.Value = 0.5f); + AddStep("apply miss and hit", () => { applyMiss(); @@ -141,7 +142,9 @@ namespace osu.Game.Tests.Visual.Gameplay applyPerfectHit(); applyPerfectHit(); }); + AddWaitStep("wait", 3); + AddStep("apply miss and cancel with hit", () => { applyMiss(); diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 9993ca1ef6..eaaf1c3c14 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -151,7 +151,7 @@ namespace osu.Game.Screens.Play.HUD }; } - private JudgementResult? pendingJudgementResult; + private bool pendingMissAnimation; protected override void LoadComplete() { @@ -171,7 +171,7 @@ namespace osu.Game.Screens.Play.HUD BarHeight.BindValueChanged(_ => updatePath(), true); } - private void onNewJudgement(JudgementResult result) => pendingJudgementResult = result; + private void onNewJudgement(JudgementResult result) => pendingMissAnimation |= !result.IsHit; private void onCurrentChanged(ValueChangedEvent valueChangedEvent) // schedule display updates one frame later to ensure we know the judgement result causing this change (if there is one). @@ -179,22 +179,23 @@ namespace osu.Game.Screens.Play.HUD private void updateDisplay() { - var result = pendingJudgementResult; + double newHealth = Current.Value; - if (Current.Value >= GlowBarValue) + if (newHealth >= GlowBarValue) finishMissDisplay(); - double time = Current.Value > GlowBarValue ? 500 : 250; + double time = newHealth > GlowBarValue ? 500 : 250; // TODO: this should probably use interpolation in update. - this.TransformTo(nameof(HealthBarValue), Current.Value, time, Easing.OutQuint); + this.TransformTo(nameof(HealthBarValue), newHealth, time, Easing.OutQuint); - if (result != null && !result.IsHit) + if (pendingMissAnimation && newHealth < GlowBarValue) triggerMissDisplay(); - else if (!displayingMiss) - this.TransformTo(nameof(GlowBarValue), Current.Value, time, Easing.OutQuint); - pendingJudgementResult = null; + pendingMissAnimation = false; + + if (!displayingMiss) + this.TransformTo(nameof(GlowBarValue), newHealth, time, Easing.OutQuint); } protected override void Update() From c55bfc03ee6f02f8234a80b8b0bf11022e763c34 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 5 Dec 2023 22:06:53 +0300 Subject: [PATCH 1828/4092] Move private method --- .../Gameplay/TestSceneArgonHealthDisplay.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs index 59819d781f..30fb4412f4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs @@ -87,14 +87,6 @@ namespace osu.Game.Tests.Visual.Gameplay }, 3); } - private void applyPerfectHit() - { - healthProcessor.ApplyResult(new JudgementResult(new HitCircle(), new OsuJudgement()) - { - Type = HitResult.Perfect - }); - } - [Test] public void TestLateMissAfterConsequentMisses() { @@ -159,5 +151,13 @@ namespace osu.Game.Tests.Visual.Gameplay { healthProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Miss }); } + + private void applyPerfectHit() + { + healthProcessor.ApplyResult(new JudgementResult(new HitCircle(), new OsuJudgement()) + { + Type = HitResult.Perfect + }); + } } } From a0813d18cab2cb5485d5a0f792819d8018ef1189 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 5 Dec 2023 22:47:08 +0300 Subject: [PATCH 1829/4092] `CalculatedTextSize` -> `FontSize` --- osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs | 2 +- osu.Game/Graphics/UserInterface/OsuTextBox.cs | 2 +- osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs index 123854a2dd..0be7b4dc48 100644 --- a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs @@ -23,7 +23,7 @@ namespace osu.Game.Graphics.UserInterface protected override Drawable GetDrawableCharacter(char c) => new FallingDownContainer { AutoSizeAxes = Axes.Both, - Child = new PasswordMaskChar(CalculatedTextSize), + Child = new PasswordMaskChar(FontSize), }; protected override bool AllowUniqueCharacterSamples => false; diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index 04ecfa7e9a..4742da6d0b 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -268,7 +268,7 @@ namespace osu.Game.Graphics.UserInterface protected override Drawable GetDrawableCharacter(char c) => new FallingDownContainer { AutoSizeAxes = Axes.Both, - Child = new OsuSpriteText { Text = c.ToString(), Font = OsuFont.GetFont(size: CalculatedTextSize) }, + Child = new OsuSpriteText { Text = c.ToString(), Font = OsuFont.GetFont(size: FontSize) }, }; protected override Caret CreateCaret() => caret = new OsuCaret diff --git a/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs b/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs index 3940bf8bca..131041e706 100644 --- a/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs +++ b/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs @@ -110,7 +110,7 @@ namespace osu.Game.Graphics.UserInterface BackgroundFocused = colourProvider.Background4; BackgroundUnfocused = colourProvider.Background4; - Placeholder.Font = OsuFont.GetFont(size: CalculatedTextSize, weight: FontWeight.SemiBold); + Placeholder.Font = OsuFont.GetFont(size: FontSize, weight: FontWeight.SemiBold); PlaceholderText = CommonStrings.InputSearch; CornerRadius = corner_radius; From b8b82f890172cd2a6dd70e57ad6ecf591dca073c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 5 Dec 2023 22:45:46 +0300 Subject: [PATCH 1830/4092] Handle back action in `OsuDropdown` rather than menu --- .../Graphics/UserInterface/OsuDropdown.cs | 35 +++++++++---------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs index b530172f3e..7a052c2298 100644 --- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs @@ -22,7 +22,7 @@ using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface { - public partial class OsuDropdown : Dropdown + public partial class OsuDropdown : Dropdown, IKeyBindingHandler { private const float corner_radius = 5; @@ -30,9 +30,23 @@ namespace osu.Game.Graphics.UserInterface protected override DropdownMenu CreateMenu() => new OsuDropdownMenu(); + public bool OnPressed(KeyBindingPressEvent e) + { + if (e.Repeat) return false; + + if (e.Action == GlobalAction.Back) + return Back(); + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + #region OsuDropdownMenu - protected partial class OsuDropdownMenu : DropdownMenu, IKeyBindingHandler + protected partial class OsuDropdownMenu : DropdownMenu { public override bool HandleNonPositionalInput => State == MenuState.Open; @@ -276,23 +290,6 @@ namespace osu.Game.Graphics.UserInterface } #endregion - - public bool OnPressed(KeyBindingPressEvent e) - { - if (e.Repeat) return false; - - if (e.Action == GlobalAction.Back) - { - State = MenuState.Closed; - return true; - } - - return false; - } - - public void OnReleased(KeyBindingReleaseEvent e) - { - } } #endregion From a67df766543cb2e967695675434885edd0431d7c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 5 Dec 2023 22:50:05 +0300 Subject: [PATCH 1831/4092] Add test coverage --- .../UserInterface/TestSceneOsuDropdown.cs | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuDropdown.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuDropdown.cs index b0548d7e9f..1678890b73 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuDropdown.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuDropdown.cs @@ -1,9 +1,17 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; +using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Framework.Input.States; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; +using osu.Game.Input.Bindings; namespace osu.Game.Tests.Visual.UserInterface { @@ -13,8 +21,29 @@ namespace osu.Game.Tests.Visual.UserInterface new OsuEnumDropdown { Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Origin = Anchor.TopCentre, Width = 150 }; + + [Test] + // todo: this can be written much better if ThemeComparisonTestScene has a manual input manager + public void TestBackAction() + { + AddStep("open", () => dropdown().ChildrenOfType().Single().Open()); + AddStep("press back", () => dropdown().OnPressed(new KeyBindingPressEvent(new InputState(), GlobalAction.Back))); + AddAssert("closed", () => dropdown().ChildrenOfType().Single().State == MenuState.Closed); + + AddStep("open", () => dropdown().ChildrenOfType().Single().Open()); + AddStep("type something", () => dropdown().ChildrenOfType().Single().SearchTerm.Value = "something"); + AddAssert("search bar visible", () => dropdown().ChildrenOfType().Single().State.Value == Visibility.Visible); + AddStep("press back", () => dropdown().OnPressed(new KeyBindingPressEvent(new InputState(), GlobalAction.Back))); + AddAssert("text clear", () => dropdown().ChildrenOfType().Single().SearchTerm.Value == string.Empty); + AddAssert("search bar hidden", () => dropdown().ChildrenOfType().Single().State.Value == Visibility.Hidden); + AddAssert("still open", () => dropdown().ChildrenOfType().Single().State == MenuState.Open); + AddStep("press back", () => dropdown().OnPressed(new KeyBindingPressEvent(new InputState(), GlobalAction.Back))); + AddAssert("closed", () => dropdown().ChildrenOfType().Single().State == MenuState.Closed); + + OsuEnumDropdown dropdown() => this.ChildrenOfType>().First(); + } } } From d92db8059e1b8804af4a88d9450cd837a0e97d6d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 5 Dec 2023 22:49:43 +0300 Subject: [PATCH 1832/4092] Add new dropdown properties to `SettingsDropdown` --- osu.Game/Overlays/Settings/SettingsDropdown.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game/Overlays/Settings/SettingsDropdown.cs b/osu.Game/Overlays/Settings/SettingsDropdown.cs index 5798d02e03..ec69224bb6 100644 --- a/osu.Game/Overlays/Settings/SettingsDropdown.cs +++ b/osu.Game/Overlays/Settings/SettingsDropdown.cs @@ -16,6 +16,18 @@ namespace osu.Game.Overlays.Settings { protected new OsuDropdown Control => (OsuDropdown)base.Control; + public bool AlwaysShowSearchBar + { + get => Control.AlwaysShowSearchBar; + set => Control.AlwaysShowSearchBar = value; + } + + public bool AllowNonContiguousMatching + { + get => Control.AllowNonContiguousMatching; + set => Control.AllowNonContiguousMatching = value; + } + public IEnumerable Items { get => Control.Items; From ee2e176082834d34350d2c9183d825b5468d7333 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 5 Dec 2023 22:52:48 +0300 Subject: [PATCH 1833/4092] Add osu! dropdown search bar implementation --- .../Graphics/UserInterface/OsuDropdown.cs | 79 ++++++++++++++++++- 1 file changed, 75 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs index 7a052c2298..96604275ea 100644 --- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs @@ -352,11 +352,82 @@ namespace osu.Game.Graphics.UserInterface AddInternal(new HoverClickSounds()); } - [BackgroundDependencyLoader(true)] - private void load(OverlayColourProvider? colourProvider, OsuColour colours) + [Resolved] + private OverlayColourProvider? colourProvider { get; set; } + + [Resolved] + private OsuColour colours { get; set; } = null!; + + protected override void LoadComplete() { - BackgroundColour = colourProvider?.Background5 ?? Color4.Black.Opacity(0.5f); - BackgroundColourHover = colourProvider?.Light4 ?? colours.PinkDarker; + base.LoadComplete(); + + SearchBar.State.ValueChanged += _ => updateColour(); + updateColour(); + } + + protected override bool OnHover(HoverEvent e) + { + updateColour(); + return false; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateColour(); + } + + private void updateColour() + { + bool hovered = Enabled.Value && IsHovered; + var hoveredColour = colourProvider?.Light4 ?? colours.PinkDarker; + var unhoveredColour = colourProvider?.Background5 ?? Color4.Black.Opacity(0.5f); + + if (SearchBar.State.Value == Visibility.Visible) + { + Icon.Colour = hovered ? hoveredColour.Lighten(0.5f) : Colour4.White; + Background.Colour = unhoveredColour; + } + else + { + Icon.Colour = Color4.White; + Background.Colour = hovered ? hoveredColour : unhoveredColour; + } + } + + protected override DropdownSearchBar CreateSearchBar() => new OsuDropdownSearchBar + { + Padding = new MarginPadding { Right = 36 }, + }; + + private partial class OsuDropdownSearchBar : DropdownSearchBar + { + protected override void PopIn() => this.FadeIn(); + + protected override void PopOut() => this.FadeOut(); + + protected override TextBox CreateTextBox() => new DropdownSearchTextBox + { + FontSize = OsuFont.Default.Size, + }; + + private partial class DropdownSearchTextBox : SearchTextBox + { + public DropdownSearchTextBox() + { + TextContainer.Margin = new MarginPadding { Top = 4f }; + } + + public override bool OnPressed(KeyBindingPressEvent e) + { + if (e.Action == GlobalAction.Back) + // this method is blocking Dropdown from receiving the back action, despite this text box residing in a separate input manager. + // to fix this properly, a local global action container needs to be added as well, but for simplicity, just don't handle the back action here. + return false; + + return base.OnPressed(e); + } + } } } } From d4aedaf22d730cf03eaf199c06df66939b86f9a8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 5 Dec 2023 22:53:24 +0300 Subject: [PATCH 1834/4092] Always show search bar in skin dropdown for visibility --- osu.Game/Overlays/Settings/Sections/SkinSection.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index e997e70157..40c54b26a0 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -57,9 +57,10 @@ namespace osu.Game.Overlays.Settings.Sections { skinDropdown = new SkinSettingsDropdown { + AlwaysShowSearchBar = true, LabelText = SkinSettingsStrings.CurrentSkin, Current = skins.CurrentSkinInfo, - Keywords = new[] { @"skins" } + Keywords = new[] { @"skins" }, }, new SettingsButton { From f45336a4f65126fbf258df1627074a089ce51852 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 5 Dec 2023 22:53:35 +0300 Subject: [PATCH 1835/4092] Make skin dropdown searching non-contiguous --- osu.Game/Overlays/Settings/Sections/SkinSection.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 40c54b26a0..1d057f42c0 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -58,6 +58,7 @@ namespace osu.Game.Overlays.Settings.Sections skinDropdown = new SkinSettingsDropdown { AlwaysShowSearchBar = true, + AllowNonContiguousMatching = true, LabelText = SkinSettingsStrings.CurrentSkin, Current = skins.CurrentSkinInfo, Keywords = new[] { @"skins" }, From 2c7db61a5c0263ce58e8cd5134acfd2df727e9ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 5 Dec 2023 21:19:35 +0100 Subject: [PATCH 1836/4092] Improve test --- .../Screens/TestSceneMapPoolScreen.cs | 137 ++++++++++++------ 1 file changed, 96 insertions(+), 41 deletions(-) diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs index 24dfb95317..b99735bda4 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs @@ -5,7 +5,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; -using osu.Framework.Input.Events; using osu.Framework.Testing; using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; @@ -161,23 +160,51 @@ namespace osu.Game.Tournament.Tests.Screens { Ladder.CurrentMatch.Value!.Round.Value!.Beatmaps.Clear(); - for (int i = 0; i < 4; i++) + for (int i = 0; i < 5; i++) addBeatmap(); }); AddStep("update displayed maps", () => Ladder.SplitMapPoolByMods.Value = false); - AddStep("perform bans", () => - { - var tournamentMaps = screen.ChildrenOfType().ToList(); + AddStep("start bans from blue team", () => screen.ChildrenOfType().First(btn => btn.Text == "Blue Ban").TriggerClick()); + AddStep("ban map", () => clickBeatmapPanel(0)); + AddAssert("one ban registered", () => Ladder.CurrentMatch.Value!.PicksBans, () => Has.Count.EqualTo(1)); + AddAssert("ban was blue's", + () => Ladder.CurrentMatch.Value!.PicksBans.Count(pb => pb.Type == ChoiceType.Ban && pb.Team == TeamColour.Blue), + () => Is.EqualTo(1)); - screen.ChildrenOfType().Where(btn => btn.Text == "Red Ban").First().TriggerClick(); + AddStep("ban map", () => clickBeatmapPanel(1)); + AddAssert("two bans registered", () => Ladder.CurrentMatch.Value!.PicksBans, () => Has.Count.EqualTo(2)); + AddAssert("one ban for red team", + () => Ladder.CurrentMatch.Value!.PicksBans.Count(pb => pb.Type == ChoiceType.Ban && pb.Team == TeamColour.Red), + () => Is.EqualTo(1)); + AddAssert("one ban for blue team", + () => Ladder.CurrentMatch.Value!.PicksBans.Count(pb => pb.Type == ChoiceType.Ban && pb.Team == TeamColour.Blue), + () => Is.EqualTo(1)); - PerformMapAction(tournamentMaps[0]); - PerformMapAction(tournamentMaps[1]); - }); + AddStep("pick map", () => clickBeatmapPanel(2)); + AddAssert("one pick registered", + () => Ladder.CurrentMatch.Value!.PicksBans.Count(pb => pb.Type == ChoiceType.Pick), + () => Is.EqualTo(1)); + AddAssert("pick was red's", + () => Ladder.CurrentMatch.Value!.PicksBans.Last().Team, + () => Is.EqualTo(TeamColour.Red)); - AddAssert("ensure 1 ban per team", () => Ladder.CurrentMatch.Value!.PicksBans.Count() == 2 && Ladder.CurrentMatch.Value!.PicksBans.Last().Team == TeamColour.Blue); + AddStep("pick map", () => clickBeatmapPanel(3)); + AddAssert("two picks registered", + () => Ladder.CurrentMatch.Value!.PicksBans.Count(pb => pb.Type == ChoiceType.Pick), + () => Is.EqualTo(2)); + AddAssert("pick was blue's", + () => Ladder.CurrentMatch.Value!.PicksBans.Last().Team, + () => Is.EqualTo(TeamColour.Blue)); + + AddStep("pick map", () => clickBeatmapPanel(4)); + AddAssert("three picks registered", + () => Ladder.CurrentMatch.Value!.PicksBans.Count(pb => pb.Type == ChoiceType.Pick), + () => Is.EqualTo(3)); + AddAssert("pick was red's", + () => Ladder.CurrentMatch.Value!.PicksBans.Last().Team, + () => Is.EqualTo(TeamColour.Red)); AddStep("reset match", () => { @@ -203,45 +230,73 @@ namespace osu.Game.Tournament.Tests.Screens AddStep("update displayed maps", () => Ladder.SplitMapPoolByMods.Value = false); - AddStep("red team ban", () => + AddStep("start bans with red team", () => screen.ChildrenOfType().First(btn => btn.Text == "Red Ban").TriggerClick()); + + AddStep("first ban", () => clickBeatmapPanel(0)); + AddAssert("red ban registered", + () => Ladder.CurrentMatch.Value!.PicksBans.Count(pb => pb.Type == ChoiceType.Ban && pb.Team == TeamColour.Red), + () => Is.EqualTo(1)); + + AddStep("ban two more maps", () => { - var tournamentMaps = screen.ChildrenOfType().ToList(); - - screen.ChildrenOfType().Where(btn => btn.Text == "Red Ban").First().TriggerClick(); - - PerformMapAction(tournamentMaps[0]); + clickBeatmapPanel(1); + clickBeatmapPanel(2); }); - AddAssert("ensure red team ban", () => Ladder.CurrentMatch.Value!.PicksBans.Last().Team == TeamColour.Red); + AddAssert("three bans registered", + () => Ladder.CurrentMatch.Value!.PicksBans.Count(pb => pb.Type == ChoiceType.Ban), + () => Is.EqualTo(3)); + AddAssert("both new bans for blue team", + () => Ladder.CurrentMatch.Value!.PicksBans.Count(pb => pb.Type == ChoiceType.Ban && pb.Team == TeamColour.Blue), + () => Is.EqualTo(2)); - AddStep("blue team bans", () => + AddStep("ban two more maps", () => { - var tournamentMaps = screen.ChildrenOfType().ToList(); - - PerformMapAction(tournamentMaps[1]); - PerformMapAction(tournamentMaps[2]); + clickBeatmapPanel(3); + clickBeatmapPanel(4); }); - AddAssert("ensure blue team double ban", () => Ladder.CurrentMatch.Value!.PicksBans.Count(ban => ban.Team == TeamColour.Blue) == 2); + AddAssert("five bans registered", + () => Ladder.CurrentMatch.Value!.PicksBans.Count(pb => pb.Type == ChoiceType.Ban), + () => Is.EqualTo(5)); + AddAssert("both new bans for red team", + () => Ladder.CurrentMatch.Value!.PicksBans.Count(pb => pb.Type == ChoiceType.Ban && pb.Team == TeamColour.Red), + () => Is.EqualTo(3)); - AddStep("red team bans", () => - { - var tournamentMaps = screen.ChildrenOfType().ToList(); + AddStep("ban last map", () => clickBeatmapPanel(5)); + AddAssert("six bans registered", + () => Ladder.CurrentMatch.Value!.PicksBans.Count(pb => pb.Type == ChoiceType.Ban), + () => Is.EqualTo(6)); + AddAssert("red banned three", + () => Ladder.CurrentMatch.Value!.PicksBans.Count(pb => pb.Type == ChoiceType.Ban && pb.Team == TeamColour.Red), + () => Is.EqualTo(3)); + AddAssert("blue banned three", + () => Ladder.CurrentMatch.Value!.PicksBans.Count(pb => pb.Type == ChoiceType.Ban && pb.Team == TeamColour.Blue), + () => Is.EqualTo(3)); - PerformMapAction(tournamentMaps[3]); - PerformMapAction(tournamentMaps[4]); - }); + AddStep("pick map", () => clickBeatmapPanel(6)); + AddAssert("one pick registered", + () => Ladder.CurrentMatch.Value!.PicksBans.Count(pb => pb.Type == ChoiceType.Pick), + () => Is.EqualTo(1)); + AddAssert("pick was blue's", + () => Ladder.CurrentMatch.Value!.PicksBans.Last().Team, + () => Is.EqualTo(TeamColour.Blue)); - AddAssert("ensure red team double ban", () => Ladder.CurrentMatch.Value!.PicksBans.Count(ban => ban.Team == TeamColour.Red) == 3); + AddStep("pick map", () => clickBeatmapPanel(7)); + AddAssert("two picks registered", + () => Ladder.CurrentMatch.Value!.PicksBans.Count(pb => pb.Type == ChoiceType.Pick), + () => Is.EqualTo(2)); + AddAssert("pick was red's", + () => Ladder.CurrentMatch.Value!.PicksBans.Last().Team, + () => Is.EqualTo(TeamColour.Red)); - AddStep("blue team bans", () => - { - var tournamentMaps = screen.ChildrenOfType().ToList(); - - PerformMapAction(tournamentMaps[5]); - }); - - AddAssert("ensure blue team ban", () => Ladder.CurrentMatch.Value!.PicksBans.Last().Team == TeamColour.Blue); + AddStep("pick map", () => clickBeatmapPanel(8)); + AddAssert("three picks registered", + () => Ladder.CurrentMatch.Value!.PicksBans.Count(pb => pb.Type == ChoiceType.Pick), + () => Is.EqualTo(3)); + AddAssert("pick was blue's", + () => Ladder.CurrentMatch.Value!.PicksBans.Last().Team, + () => Is.EqualTo(TeamColour.Blue)); AddStep("reset match", () => { @@ -251,6 +306,7 @@ namespace osu.Game.Tournament.Tests.Screens Ladder.CurrentMatch.Value.PicksBans.Clear(); }); } + private void addBeatmap(string mods = "NM") { Ladder.CurrentMatch.Value!.Round.Value!.Beatmaps.Add(new RoundBeatmap @@ -260,10 +316,9 @@ namespace osu.Game.Tournament.Tests.Screens }); } - private void PerformMapAction(TournamentBeatmapPanel map) + private void clickBeatmapPanel(int index) { - InputManager.MoveMouseTo(map); - + InputManager.MoveMouseTo(screen.ChildrenOfType().ElementAt(index)); InputManager.Click(osuTK.Input.MouseButton.Left); } } From 7392cc2fda72dd22d1daa767dab4d507c0073688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 5 Dec 2023 21:49:04 +0100 Subject: [PATCH 1837/4092] Fix headless test failures due to input handling idiosyncrasies --- .../Screens/TestSceneMapPoolScreen.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs index b99735bda4..c459de1e43 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs @@ -9,6 +9,7 @@ using osu.Framework.Testing; using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; using osu.Game.Tournament.Screens.MapPool; +using osuTK; namespace osu.Game.Tournament.Tests.Screens { @@ -19,7 +20,7 @@ namespace osu.Game.Tournament.Tests.Screens [BackgroundDependencyLoader] private void load() { - Add(screen = new MapPoolScreen { Width = 0.7f }); + Add(screen = new TestMapPoolScreen { Width = 0.7f }); } [SetUp] @@ -208,7 +209,6 @@ namespace osu.Game.Tournament.Tests.Screens AddStep("reset match", () => { - InputManager.UseParentInput = true; Ladder.CurrentMatch.Value = new TournamentMatch(); Ladder.CurrentMatch.Value = Ladder.Matches.First(); Ladder.CurrentMatch.Value.PicksBans.Clear(); @@ -300,7 +300,6 @@ namespace osu.Game.Tournament.Tests.Screens AddStep("reset match", () => { - InputManager.UseParentInput = true; Ladder.CurrentMatch.Value = new TournamentMatch(); Ladder.CurrentMatch.Value = Ladder.Matches.First(); Ladder.CurrentMatch.Value.PicksBans.Clear(); @@ -321,5 +320,16 @@ namespace osu.Game.Tournament.Tests.Screens InputManager.MoveMouseTo(screen.ChildrenOfType().ElementAt(index)); InputManager.Click(osuTK.Input.MouseButton.Left); } + + private partial class TestMapPoolScreen : MapPoolScreen + { + // this is a bit of a test-specific workaround. + // the way pick/ban is implemented is a bit funky; the screen itself is what handles the mouse there, + // rather than the beatmap panels themselves. + // in some extreme situations headless it may turn out that the panels overflow the screen, + // and as such picking stops working anymore outside of the bounds of the screen drawable. + // this override makes it so the screen sees all of the input at all times, making that impossible to happen. + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + } } } From 43701c5d47154e73012f06c26b08938c9517d1a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 5 Dec 2023 21:49:32 +0100 Subject: [PATCH 1838/4092] Prefer using statement to fully qualified name --- osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs index c459de1e43..2ef290ff49 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs @@ -10,6 +10,7 @@ using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; using osu.Game.Tournament.Screens.MapPool; using osuTK; +using osuTK.Input; namespace osu.Game.Tournament.Tests.Screens { @@ -318,7 +319,7 @@ namespace osu.Game.Tournament.Tests.Screens private void clickBeatmapPanel(int index) { InputManager.MoveMouseTo(screen.ChildrenOfType().ElementAt(index)); - InputManager.Click(osuTK.Input.MouseButton.Left); + InputManager.Click(MouseButton.Left); } private partial class TestMapPoolScreen : MapPoolScreen From 07f9f5c6d842d3c7c564f96576682b6fb54c50b4 Mon Sep 17 00:00:00 2001 From: POeticPotatoes Date: Wed, 6 Dec 2023 06:33:25 +0800 Subject: [PATCH 1839/4092] Remove hover checks for mod-copying menu item --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 136c9cc8e7..a76f4ae955 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -421,7 +421,7 @@ namespace osu.Game.Online.Leaderboards { List items = new List(); - if (Score.Mods.Length > 0 && modsContainer.Any(s => s.IsHovered) && songSelect != null) + if (Score.Mods.Length > 0 && songSelect != null) items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => songSelect.Mods.Value = Score.Mods)); if (Score.Files.Count > 0) From 8286d3896f4240ad920f94bf7e5735230d90d588 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Dec 2023 11:17:32 +0900 Subject: [PATCH 1840/4092] Fix searching at song select matching incorrect ruleset Regressed with https://github.com/ppy/osu/pull/25679. --- .../SongSelect/TestScenePlaySongSelect.cs | 18 ++++++++++++++++++ .../Screens/Select/Carousel/CarouselBeatmap.cs | 2 ++ 2 files changed, 20 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 7313bde8fe..84750d4c16 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -580,6 +580,24 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("start not requested", () => !startRequested); } + [Test] + public void TestSearchTextWithRulesetCriteria() + { + createSongSelect(); + + addRulesetImportStep(0); + + AddStep("disallow convert display", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, false)); + + AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo != null); + + AddStep("set filter to match all", () => songSelect!.FilterControl.CurrentTextSearch.Value = "Some"); + + changeRuleset(1); + + AddUntilStep("has no selection", () => songSelect!.Carousel.SelectedBeatmapInfo == null); + } + [TestCase(false)] [TestCase(true)] public void TestExternalBeatmapChangeWhileFiltered(bool differentRuleset) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 45594bd0e8..1ca4b371c3 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -41,6 +41,8 @@ namespace osu.Game.Screens.Select.Carousel return match; } + if (!match) return false; + if (criteria.SearchTerms.Length > 0) { match = BeatmapInfo.Match(criteria.SearchTerms); From ac67320b61d1459ab367e46c9100ab33379203a4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Dec 2023 11:50:43 +0900 Subject: [PATCH 1841/4092] Refactor for readability --- .../Screens/TestSceneMapPoolScreen.cs | 2 +- .../Screens/MapPool/MapPoolScreen.cs | 23 +++++++------------ 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs index 2ef290ff49..2911ba6acb 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs @@ -154,7 +154,7 @@ namespace osu.Game.Tournament.Tests.Screens } [Test] - public void TestSingleTeamBan() + public void TestPickBanOrder() { AddStep("set ban count", () => Ladder.CurrentMatch.Value!.Round.Value!.BanCount.Value = 1); diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs index 72134ccb51..665d3c131a 100644 --- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs +++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs @@ -151,9 +151,7 @@ namespace osu.Game.Tournament.Screens.MapPool int totalBansRequired = CurrentMatch.Value.Round.Value.BanCount.Value * 2; - const TeamColour roll_winner = TeamColour.Red; //todo: draw from match - - var previousColour = CurrentMatch.Value.PicksBans.LastOrDefault()?.Team ?? roll_winner; + TeamColour lastPickColour = CurrentMatch.Value.PicksBans.LastOrDefault()?.Team ?? TeamColour.Red; TeamColour nextColour; @@ -161,22 +159,17 @@ namespace osu.Game.Tournament.Screens.MapPool if (!hasAllBans) { - // Ban phase. - // Switch teams every second ban. + // Ban phase: switch teams every second ban. nextColour = CurrentMatch.Value.PicksBans.Count % 2 == 1 - ? getOppositeTeamColour(previousColour) - : previousColour; - } - else if (pickType == ChoiceType.Ban) - { - // Switching from bans to picks - stay with the last team that was banning. - nextColour = pickColour; + ? getOppositeTeamColour(lastPickColour) + : lastPickColour; } else { - // Pick phase. - // Switch teams every pick. - nextColour = getOppositeTeamColour(previousColour); + // Pick phase : switch teams every pick, except for the first pick which generally goes to the team that placed the last ban. + nextColour = pickType == ChoiceType.Pick + ? getOppositeTeamColour(lastPickColour) + : lastPickColour; } setMode(nextColour, hasAllBans ? ChoiceType.Pick : ChoiceType.Ban); From 1d1b85551000586cda3aef40ea26d447242bd754 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Dec 2023 11:57:04 +0900 Subject: [PATCH 1842/4092] Refactor test for readability --- .../Screens/TestSceneMapPoolScreen.cs | 47 +++++++------------ 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs index 2911ba6acb..dcf9dc47b9 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs @@ -169,44 +169,26 @@ namespace osu.Game.Tournament.Tests.Screens AddStep("update displayed maps", () => Ladder.SplitMapPoolByMods.Value = false); AddStep("start bans from blue team", () => screen.ChildrenOfType().First(btn => btn.Text == "Blue Ban").TriggerClick()); + AddStep("ban map", () => clickBeatmapPanel(0)); - AddAssert("one ban registered", () => Ladder.CurrentMatch.Value!.PicksBans, () => Has.Count.EqualTo(1)); - AddAssert("ban was blue's", - () => Ladder.CurrentMatch.Value!.PicksBans.Count(pb => pb.Type == ChoiceType.Ban && pb.Team == TeamColour.Blue), - () => Is.EqualTo(1)); + checkTotalPickBans(1); + checkLastPick(ChoiceType.Ban, TeamColour.Blue); AddStep("ban map", () => clickBeatmapPanel(1)); - AddAssert("two bans registered", () => Ladder.CurrentMatch.Value!.PicksBans, () => Has.Count.EqualTo(2)); - AddAssert("one ban for red team", - () => Ladder.CurrentMatch.Value!.PicksBans.Count(pb => pb.Type == ChoiceType.Ban && pb.Team == TeamColour.Red), - () => Is.EqualTo(1)); - AddAssert("one ban for blue team", - () => Ladder.CurrentMatch.Value!.PicksBans.Count(pb => pb.Type == ChoiceType.Ban && pb.Team == TeamColour.Blue), - () => Is.EqualTo(1)); + checkTotalPickBans(2); + checkLastPick(ChoiceType.Ban, TeamColour.Red); AddStep("pick map", () => clickBeatmapPanel(2)); - AddAssert("one pick registered", - () => Ladder.CurrentMatch.Value!.PicksBans.Count(pb => pb.Type == ChoiceType.Pick), - () => Is.EqualTo(1)); - AddAssert("pick was red's", - () => Ladder.CurrentMatch.Value!.PicksBans.Last().Team, - () => Is.EqualTo(TeamColour.Red)); + checkTotalPickBans(3); + checkLastPick(ChoiceType.Pick, TeamColour.Red); AddStep("pick map", () => clickBeatmapPanel(3)); - AddAssert("two picks registered", - () => Ladder.CurrentMatch.Value!.PicksBans.Count(pb => pb.Type == ChoiceType.Pick), - () => Is.EqualTo(2)); - AddAssert("pick was blue's", - () => Ladder.CurrentMatch.Value!.PicksBans.Last().Team, - () => Is.EqualTo(TeamColour.Blue)); + checkTotalPickBans(4); + checkLastPick(ChoiceType.Pick, TeamColour.Blue); AddStep("pick map", () => clickBeatmapPanel(4)); - AddAssert("three picks registered", - () => Ladder.CurrentMatch.Value!.PicksBans.Count(pb => pb.Type == ChoiceType.Pick), - () => Is.EqualTo(3)); - AddAssert("pick was red's", - () => Ladder.CurrentMatch.Value!.PicksBans.Last().Team, - () => Is.EqualTo(TeamColour.Red)); + checkTotalPickBans(5); + checkLastPick(ChoiceType.Pick, TeamColour.Red); AddStep("reset match", () => { @@ -214,6 +196,13 @@ namespace osu.Game.Tournament.Tests.Screens Ladder.CurrentMatch.Value = Ladder.Matches.First(); Ladder.CurrentMatch.Value.PicksBans.Clear(); }); + + void checkTotalPickBans(int expected) => AddAssert($"total pickbans is {expected}", () => Ladder.CurrentMatch.Value!.PicksBans, () => Has.Count.EqualTo(expected)); + + void checkLastPick(ChoiceType expectedChoice, TeamColour expectedColour) => + AddAssert($"last choice was {expectedChoice} by {expectedColour}", + () => Ladder.CurrentMatch.Value!.PicksBans.Select(pb => (pb.Type, pb.Team)).Last(), + () => Is.EqualTo((expectedChoice, expectedColour))); } [Test] From 73aaa0406a35786e07ac8c11b07e018522303d85 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Dec 2023 12:00:32 +0900 Subject: [PATCH 1843/4092] Add test coverage of multiple bans order --- .../Screens/TestSceneMapPoolScreen.cs | 61 ++++++++++++++++--- 1 file changed, 53 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs index dcf9dc47b9..5e535e2749 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs @@ -25,7 +25,14 @@ namespace osu.Game.Tournament.Tests.Screens } [SetUp] - public void SetUp() => Schedule(() => Ladder.SplitMapPoolByMods.Value = true); + public void SetUp() => Schedule(() => + { + Ladder.SplitMapPoolByMods.Value = true; + + Ladder.CurrentMatch.Value = new TournamentMatch(); + Ladder.CurrentMatch.Value = Ladder.Matches.First(); + Ladder.CurrentMatch.Value.PicksBans.Clear(); + }); [Test] public void TestFewMaps() @@ -153,6 +160,44 @@ namespace osu.Game.Tournament.Tests.Screens }); } + [Test] + public void TestBanOrderMultipleBans() + { + AddStep("set ban count", () => Ladder.CurrentMatch.Value!.Round.Value!.BanCount.Value = 2); + + AddStep("load some maps", () => + { + Ladder.CurrentMatch.Value!.Round.Value!.Beatmaps.Clear(); + + for (int i = 0; i < 5; i++) + addBeatmap(); + }); + + AddStep("update displayed maps", () => Ladder.SplitMapPoolByMods.Value = false); + + AddStep("start bans from blue team", () => screen.ChildrenOfType().First(btn => btn.Text == "Blue Ban").TriggerClick()); + + AddStep("ban map", () => clickBeatmapPanel(0)); + checkTotalPickBans(1); + checkLastPick(ChoiceType.Ban, TeamColour.Blue); + + AddStep("ban map", () => clickBeatmapPanel(1)); + checkTotalPickBans(2); + checkLastPick(ChoiceType.Ban, TeamColour.Red); + + AddStep("ban map", () => clickBeatmapPanel(2)); + checkTotalPickBans(3); + checkLastPick(ChoiceType.Ban, TeamColour.Red); + + AddStep("pick map", () => clickBeatmapPanel(3)); + checkTotalPickBans(4); + checkLastPick(ChoiceType.Ban, TeamColour.Blue); + + AddStep("pick map", () => clickBeatmapPanel(4)); + checkTotalPickBans(5); + checkLastPick(ChoiceType.Pick, TeamColour.Blue); + } + [Test] public void TestPickBanOrder() { @@ -196,13 +241,6 @@ namespace osu.Game.Tournament.Tests.Screens Ladder.CurrentMatch.Value = Ladder.Matches.First(); Ladder.CurrentMatch.Value.PicksBans.Clear(); }); - - void checkTotalPickBans(int expected) => AddAssert($"total pickbans is {expected}", () => Ladder.CurrentMatch.Value!.PicksBans, () => Has.Count.EqualTo(expected)); - - void checkLastPick(ChoiceType expectedChoice, TeamColour expectedColour) => - AddAssert($"last choice was {expectedChoice} by {expectedColour}", - () => Ladder.CurrentMatch.Value!.PicksBans.Select(pb => (pb.Type, pb.Team)).Last(), - () => Is.EqualTo((expectedChoice, expectedColour))); } [Test] @@ -296,6 +334,13 @@ namespace osu.Game.Tournament.Tests.Screens }); } + private void checkTotalPickBans(int expected) => AddAssert($"total pickbans is {expected}", () => Ladder.CurrentMatch.Value!.PicksBans, () => Has.Count.EqualTo(expected)); + + private void checkLastPick(ChoiceType expectedChoice, TeamColour expectedColour) => + AddAssert($"last choice was {expectedChoice} by {expectedColour}", + () => Ladder.CurrentMatch.Value!.PicksBans.Select(pb => (pb.Type, pb.Team)).Last(), + () => Is.EqualTo((expectedChoice, expectedColour))); + private void addBeatmap(string mods = "NM") { Ladder.CurrentMatch.Value!.Round.Value!.Beatmaps.Add(new RoundBeatmap From 49ca1ccb22792b973f49d409eec464de1ab75d74 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Dec 2023 12:03:54 +0900 Subject: [PATCH 1844/4092] Simplify state reset in test scene --- .../Screens/TestSceneMapPoolScreen.cs | 45 +++++++------------ 1 file changed, 17 insertions(+), 28 deletions(-) diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs index 5e535e2749..7e008a6897 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs @@ -24,14 +24,24 @@ namespace osu.Game.Tournament.Tests.Screens Add(screen = new TestMapPoolScreen { Width = 0.7f }); } - [SetUp] - public void SetUp() => Schedule(() => + [SetUpSteps] + public override void SetUpSteps() + { + AddStep("reset state", resetState); + } + + private void resetState() { Ladder.SplitMapPoolByMods.Value = true; Ladder.CurrentMatch.Value = new TournamentMatch(); Ladder.CurrentMatch.Value = Ladder.Matches.First(); Ladder.CurrentMatch.Value.PicksBans.Clear(); + } + + [SetUp] + public void SetUp() => Schedule(() => + { }); [Test] @@ -48,7 +58,6 @@ namespace osu.Game.Tournament.Tests.Screens AddStep("reset match", () => { Ladder.CurrentMatch.Value = new TournamentMatch(); - Ladder.CurrentMatch.Value = Ladder.Matches.First(); }); assertTwoWide(); @@ -65,11 +74,7 @@ namespace osu.Game.Tournament.Tests.Screens addBeatmap(); }); - AddStep("reset match", () => - { - Ladder.CurrentMatch.Value = new TournamentMatch(); - Ladder.CurrentMatch.Value = Ladder.Matches.First(); - }); + AddStep("reset state", resetState); assertTwoWide(); } @@ -85,11 +90,7 @@ namespace osu.Game.Tournament.Tests.Screens addBeatmap(); }); - AddStep("reset match", () => - { - Ladder.CurrentMatch.Value = new TournamentMatch(); - Ladder.CurrentMatch.Value = Ladder.Matches.First(); - }); + AddStep("reset state", resetState); assertThreeWide(); } @@ -105,11 +106,7 @@ namespace osu.Game.Tournament.Tests.Screens addBeatmap(i > 4 ? Ruleset.Value.CreateInstance().AllMods.ElementAt(i).Acronym : "NM"); }); - AddStep("reset match", () => - { - Ladder.CurrentMatch.Value = new TournamentMatch(); - Ladder.CurrentMatch.Value = Ladder.Matches.First(); - }); + AddStep("reset state", resetState); assertTwoWide(); } @@ -131,11 +128,7 @@ namespace osu.Game.Tournament.Tests.Screens addBeatmap(i > 4 ? Ruleset.Value.CreateInstance().AllMods.ElementAt(i).Acronym : "NM"); }); - AddStep("reset match", () => - { - Ladder.CurrentMatch.Value = new TournamentMatch(); - Ladder.CurrentMatch.Value = Ladder.Matches.First(); - }); + AddStep("reset state", resetState); assertThreeWide(); } @@ -153,11 +146,7 @@ namespace osu.Game.Tournament.Tests.Screens AddStep("disable splitting map pool by mods", () => Ladder.SplitMapPoolByMods.Value = false); - AddStep("reset match", () => - { - Ladder.CurrentMatch.Value = new TournamentMatch(); - Ladder.CurrentMatch.Value = Ladder.Matches.First(); - }); + AddStep("reset state", resetState); } [Test] From 639fac2d49e8d34292bd1487224b31edbaa1fee9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Dec 2023 12:09:33 +0900 Subject: [PATCH 1845/4092] Fix being able to change ruleset / beatmap when opening skin editor from main menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No tests because it would be silly to test this – it's already a well established behaviour and was just initialised incorrectly. --- osu.Game/Screens/Play/Player.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 1c97efcff7..48411e9c87 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -59,6 +59,10 @@ namespace osu.Game.Screens.Play protected override bool PlayExitSound => !isRestarting; + public override bool DisallowExternalBeatmapRulesetChanges => true; + + public override bool? AllowGlobalTrackControl => false; + protected override UserActivity InitialActivity => new UserActivity.InSoloGame(Beatmap.Value.BeatmapInfo, Ruleset.Value); public override float BackgroundParallaxAmount => 0.1f; From 2c44ca191592286b60f311fb10ab6d14fdfec4ee Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 5 Dec 2023 15:51:59 +0900 Subject: [PATCH 1846/4092] Add more test beatmaps Move test files to Catch.Tests project --- .../CatchBeatmapConversionTest.cs | 25 +- .../Beatmaps/103019-expected-conversion.json | 1 + .../Resources/Testing/Beatmaps/103019.osu | 329 ++++ .../Beatmaps/104973-expected-conversion.json | 1 + .../Resources/Testing/Beatmaps/104973.osu | 491 ++++++ .../Beatmaps/1284935-expected-conversion.json | 1 + .../Resources/Testing/Beatmaps/1284935.osu | 210 +++ .../Beatmaps/1431386-expected-conversion.json | 1 + .../Resources/Testing/Beatmaps/1431386.osu | 560 +++++++ .../Beatmaps/1597806-expected-conversion.json | 1 + .../Resources/Testing/Beatmaps/1597806.osu | 152 ++ .../Beatmaps/2190499-expected-conversion.json | 1 + .../Resources/Testing/Beatmaps/2190499.osu | 977 +++++++++++ .../Beatmaps/2571731-expected-conversion.json | 1 + .../Resources/Testing/Beatmaps/2571731.osu | 277 ++++ .../Beatmaps/2768615-expected-conversion.json | 1 + .../Resources/Testing/Beatmaps/2768615.osu | 200 +++ .../Beatmaps/2781126-expected-conversion.json | 1 + .../Resources/Testing/Beatmaps/2781126.osu | 908 +++++++++++ .../Beatmaps/3152510-expected-conversion.json | 1 + .../Resources/Testing/Beatmaps/3152510.osu | 468 ++++++ .../Beatmaps/3227428-expected-conversion.json | 1 + .../Resources/Testing/Beatmaps/3227428.osu | 142 ++ .../Beatmaps/3524302-expected-conversion.json | 1 + .../Resources/Testing/Beatmaps/3524302.osu | 889 ++++++++++ .../Beatmaps/3644427-expected-conversion.json | 1 + .../Resources/Testing/Beatmaps/3644427.osu | 1450 +++++++++++++++++ .../Beatmaps/3689906-expected-conversion.json | 1 + .../Resources/Testing/Beatmaps/3689906.osu | 942 +++++++++++ .../Beatmaps/37902-expected-conversion.json | 1 + .../Resources/Testing/Beatmaps/37902.osu | 230 +++ .../Beatmaps/39206-expected-conversion.json | 1 + .../Resources/Testing/Beatmaps/39206.osu | 524 ++++++ .../Beatmaps/3949367-expected-conversion.json | 1 + .../Resources/Testing/Beatmaps/3949367.osu | 832 ++++++++++ .../Beatmaps/42587-expected-conversion.json | 1 + .../Resources/Testing/Beatmaps/42587.osu | 528 ++++++ .../Beatmaps/50859-expected-conversion.json | 1 + .../Resources/Testing/Beatmaps/50859.osu | 290 ++++ .../Beatmaps/75858-expected-conversion.json | 1 + .../Resources/Testing/Beatmaps/75858.osu | 417 +++++ .../Beatmaps/871815-expected-conversion.json | 1 + .../Resources/Testing/Beatmaps/871815.osu | 165 ++ .../Beatmaps/basic-expected-conversion.json | 0 .../basic-hyperdash-expected-conversion.json | 0 .../Testing/Beatmaps/basic-hyperdash.osu | 0 .../Resources/Testing/Beatmaps/basic.osu | 54 +- .../Testing/Beatmaps/diffcalc-test.osu | 0 ...ock-repeat-slider-expected-conversion.json | 0 .../Beatmaps/hardrock-repeat-slider.osu | 0 .../hardrock-spinner-expected-conversion.json | 0 .../Testing/Beatmaps/hardrock-spinner.osu | 0 .../hardrock-stream-expected-conversion.json | 0 .../Testing/Beatmaps/hardrock-stream.osu | 0 .../pixel-jump-expected-conversion.json | 0 .../Resources/Testing/Beatmaps/pixel-jump.osu | 0 ...t-bound-hr-offset-expected-conversion.json | 0 .../Beatmaps/right-bound-hr-offset.osu | 0 .../Beatmaps/slider-expected-conversion.json | 0 .../Resources/Testing/Beatmaps/slider.osu | 0 ...inner-and-circles-expected-conversion.json | 0 .../Testing/Beatmaps/spinner-and-circles.osu | 0 .../Beatmaps/spinner-expected-conversion.json | 0 ...spinner-precision-expected-conversion.json | 0 .../Testing/Beatmaps/spinner-precision.osu | 0 .../Resources/Testing/Beatmaps/spinner.osu | 0 .../tiny-ticks-expected-conversion.json | 0 .../Resources/Testing/Beatmaps/tiny-ticks.osu | 0 .../v8-tick-distance-expected-conversion.json | 0 .../Testing/Beatmaps/v8-tick-distance.osu | 0 70 files changed, 11052 insertions(+), 29 deletions(-) create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/103019-expected-conversion.json create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/103019.osu create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/104973-expected-conversion.json create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/104973.osu create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/1284935-expected-conversion.json create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/1284935.osu create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/1431386-expected-conversion.json create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/1431386.osu create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/1597806-expected-conversion.json create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/1597806.osu create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/2190499-expected-conversion.json create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/2190499.osu create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/2571731-expected-conversion.json create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/2571731.osu create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/2768615-expected-conversion.json create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/2768615.osu create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/2781126-expected-conversion.json create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/2781126.osu create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3152510-expected-conversion.json create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3152510.osu create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3227428-expected-conversion.json create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3227428.osu create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3524302-expected-conversion.json create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3524302.osu create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3644427-expected-conversion.json create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3644427.osu create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3689906-expected-conversion.json create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3689906.osu create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/37902-expected-conversion.json create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/37902.osu create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/39206-expected-conversion.json create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/39206.osu create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3949367-expected-conversion.json create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3949367.osu create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/42587-expected-conversion.json create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/42587.osu create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/50859-expected-conversion.json create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/50859.osu create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/75858-expected-conversion.json create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/75858.osu create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/871815-expected-conversion.json create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/871815.osu rename {osu.Game.Rulesets.Catch => osu.Game.Rulesets.Catch.Tests}/Resources/Testing/Beatmaps/basic-expected-conversion.json (100%) rename {osu.Game.Rulesets.Catch => osu.Game.Rulesets.Catch.Tests}/Resources/Testing/Beatmaps/basic-hyperdash-expected-conversion.json (100%) rename {osu.Game.Rulesets.Catch => osu.Game.Rulesets.Catch.Tests}/Resources/Testing/Beatmaps/basic-hyperdash.osu (100%) rename {osu.Game.Rulesets.Catch => osu.Game.Rulesets.Catch.Tests}/Resources/Testing/Beatmaps/basic.osu (96%) rename {osu.Game.Rulesets.Catch => osu.Game.Rulesets.Catch.Tests}/Resources/Testing/Beatmaps/diffcalc-test.osu (100%) rename {osu.Game.Rulesets.Catch => osu.Game.Rulesets.Catch.Tests}/Resources/Testing/Beatmaps/hardrock-repeat-slider-expected-conversion.json (100%) rename {osu.Game.Rulesets.Catch => osu.Game.Rulesets.Catch.Tests}/Resources/Testing/Beatmaps/hardrock-repeat-slider.osu (100%) rename {osu.Game.Rulesets.Catch => osu.Game.Rulesets.Catch.Tests}/Resources/Testing/Beatmaps/hardrock-spinner-expected-conversion.json (100%) rename {osu.Game.Rulesets.Catch => osu.Game.Rulesets.Catch.Tests}/Resources/Testing/Beatmaps/hardrock-spinner.osu (100%) rename {osu.Game.Rulesets.Catch => osu.Game.Rulesets.Catch.Tests}/Resources/Testing/Beatmaps/hardrock-stream-expected-conversion.json (100%) rename {osu.Game.Rulesets.Catch => osu.Game.Rulesets.Catch.Tests}/Resources/Testing/Beatmaps/hardrock-stream.osu (100%) rename {osu.Game.Rulesets.Catch => osu.Game.Rulesets.Catch.Tests}/Resources/Testing/Beatmaps/pixel-jump-expected-conversion.json (100%) rename {osu.Game.Rulesets.Catch => osu.Game.Rulesets.Catch.Tests}/Resources/Testing/Beatmaps/pixel-jump.osu (100%) rename {osu.Game.Rulesets.Catch => osu.Game.Rulesets.Catch.Tests}/Resources/Testing/Beatmaps/right-bound-hr-offset-expected-conversion.json (100%) rename {osu.Game.Rulesets.Catch => osu.Game.Rulesets.Catch.Tests}/Resources/Testing/Beatmaps/right-bound-hr-offset.osu (100%) rename {osu.Game.Rulesets.Catch => osu.Game.Rulesets.Catch.Tests}/Resources/Testing/Beatmaps/slider-expected-conversion.json (100%) rename {osu.Game.Rulesets.Catch => osu.Game.Rulesets.Catch.Tests}/Resources/Testing/Beatmaps/slider.osu (100%) rename {osu.Game.Rulesets.Catch => osu.Game.Rulesets.Catch.Tests}/Resources/Testing/Beatmaps/spinner-and-circles-expected-conversion.json (100%) rename {osu.Game.Rulesets.Catch => osu.Game.Rulesets.Catch.Tests}/Resources/Testing/Beatmaps/spinner-and-circles.osu (100%) rename {osu.Game.Rulesets.Catch => osu.Game.Rulesets.Catch.Tests}/Resources/Testing/Beatmaps/spinner-expected-conversion.json (100%) rename {osu.Game.Rulesets.Catch => osu.Game.Rulesets.Catch.Tests}/Resources/Testing/Beatmaps/spinner-precision-expected-conversion.json (100%) rename {osu.Game.Rulesets.Catch => osu.Game.Rulesets.Catch.Tests}/Resources/Testing/Beatmaps/spinner-precision.osu (100%) rename {osu.Game.Rulesets.Catch => osu.Game.Rulesets.Catch.Tests}/Resources/Testing/Beatmaps/spinner.osu (100%) rename {osu.Game.Rulesets.Catch => osu.Game.Rulesets.Catch.Tests}/Resources/Testing/Beatmaps/tiny-ticks-expected-conversion.json (100%) rename {osu.Game.Rulesets.Catch => osu.Game.Rulesets.Catch.Tests}/Resources/Testing/Beatmaps/tiny-ticks.osu (100%) rename {osu.Game.Rulesets.Catch => osu.Game.Rulesets.Catch.Tests}/Resources/Testing/Beatmaps/v8-tick-distance-expected-conversion.json (100%) rename {osu.Game.Rulesets.Catch => osu.Game.Rulesets.Catch.Tests}/Resources/Testing/Beatmaps/v8-tick-distance.osu (100%) diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs index 5528ce0bfa..7572c6670f 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.Tests [TestFixture] public class CatchBeatmapConversionTest : BeatmapConversionTest { - protected override string ResourceAssembly => "osu.Game.Rulesets.Catch"; + protected override string ResourceAssembly => "osu.Game.Rulesets.Catch.Tests"; [TestCase("basic")] [TestCase("spinner")] @@ -31,6 +31,27 @@ namespace osu.Game.Rulesets.Catch.Tests [TestCase("tiny-ticks")] [TestCase("v8-tick-distance")] [TestCase("spinner-precision")] + [TestCase("37902", new[] { typeof(CatchModDoubleTime), typeof(CatchModHardRock), typeof(CatchModHidden) })] + [TestCase("39206", new[] { typeof(CatchModDoubleTime), typeof(CatchModHidden) })] + [TestCase("42587")] + [TestCase("50859", new[] { typeof(CatchModDoubleTime), typeof(CatchModHidden) })] + [TestCase("75858", new[] { typeof(CatchModHardRock), typeof(CatchModHidden) })] + [TestCase("103019", new[] { typeof(CatchModHidden) })] + [TestCase("104973", new[] { typeof(CatchModHardRock), typeof(CatchModHidden) })] + [TestCase("871815", new[] { typeof(CatchModDoubleTime), typeof(CatchModHidden) })] + [TestCase("1284935", new[] { typeof(CatchModDoubleTime), typeof(CatchModHardRock) })] + [TestCase("1431386", new[] { typeof(CatchModDoubleTime), typeof(CatchModHardRock), typeof(CatchModHidden) })] + [TestCase("1597806", new[] { typeof(CatchModDoubleTime), typeof(CatchModHidden) })] + [TestCase("2190499", new[] { typeof(CatchModDoubleTime), typeof(CatchModHidden) })] + [TestCase("2571731", new[] { typeof(CatchModHardRock), typeof(CatchModHidden) })] + [TestCase("2768615", new[] { typeof(CatchModDoubleTime), typeof(CatchModHardRock) })] + [TestCase("2781126", new[] { typeof(CatchModHidden) })] + [TestCase("3152510", new[] { typeof(CatchModDoubleTime) })] + [TestCase("3227428", new[] { typeof(CatchModHardRock), typeof(CatchModHidden) })] + [TestCase("3524302", new[] { typeof(CatchModDoubleTime), typeof(CatchModEasy) })] + [TestCase("3644427", new[] { typeof(CatchModEasy), typeof(CatchModFlashlight) })] + [TestCase("3689906", new[] { typeof(CatchModDoubleTime), typeof(CatchModEasy) })] + [TestCase("3949367", new[] { typeof(CatchModDoubleTime), typeof(CatchModEasy) })] public new void Test(string name, params Type[] mods) => base.Test(name, mods); protected override IEnumerable CreateConvertValue(HitObject hitObject) @@ -64,7 +85,7 @@ namespace osu.Game.Rulesets.Catch.Tests /// /// A sane value to account for osu!stable using ints everwhere. /// - private const float conversion_lenience = 2; + private const float conversion_lenience = 3; [JsonIgnore] public readonly CatchHitObject HitObject; diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/103019-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/103019-expected-conversion.json new file mode 100644 index 0000000000..f518db17a0 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/103019-expected-conversion.json @@ -0,0 +1 @@ +{"Mappings":[{"StartTime":571.0,"Objects":[{"StartTime":571.0,"Position":184.0,"HyperDash":false},{"StartTime":656.0,"Position":168.664017,"HyperDash":false},{"StartTime":742.0,"Position":196.577621,"HyperDash":false},{"StartTime":827.0,"Position":218.922379,"HyperDash":false},{"StartTime":913.0,"Position":255.565826,"HyperDash":false},{"StartTime":999.0,"Position":306.3156,"HyperDash":false},{"StartTime":1085.0,"Position":315.164,"HyperDash":false},{"StartTime":1152.0,"Position":325.552582,"HyperDash":false},{"StartTime":1256.0,"Position":328.091736,"HyperDash":false}]},{"StartTime":1599.0,"Objects":[{"StartTime":1599.0,"Position":256.0,"HyperDash":false},{"StartTime":1684.0,"Position":241.0,"HyperDash":false},{"StartTime":1770.0,"Position":256.0,"HyperDash":false},{"StartTime":1855.0,"Position":244.0,"HyperDash":false},{"StartTime":1941.0,"Position":256.0,"HyperDash":false},{"StartTime":2027.0,"Position":252.0,"HyperDash":false},{"StartTime":2113.0,"Position":256.0,"HyperDash":false},{"StartTime":2198.0,"Position":260.0,"HyperDash":false},{"StartTime":2284.0,"Position":256.0,"HyperDash":false},{"StartTime":2370.0,"Position":247.0,"HyperDash":false},{"StartTime":2456.0,"Position":256.0,"HyperDash":false},{"StartTime":2523.0,"Position":237.0,"HyperDash":false},{"StartTime":2627.0,"Position":256.0,"HyperDash":false}]},{"StartTime":2971.0,"Objects":[{"StartTime":2971.0,"Position":256.0,"HyperDash":false}]},{"StartTime":3313.0,"Objects":[{"StartTime":3313.0,"Position":128.0,"HyperDash":false}]},{"StartTime":3656.0,"Objects":[{"StartTime":3656.0,"Position":128.0,"HyperDash":false},{"StartTime":3741.0,"Position":119.0,"HyperDash":false},{"StartTime":3827.0,"Position":128.0,"HyperDash":false},{"StartTime":3894.0,"Position":146.0,"HyperDash":false},{"StartTime":3998.0,"Position":128.0,"HyperDash":false}]},{"StartTime":4342.0,"Objects":[{"StartTime":4342.0,"Position":384.0,"HyperDash":false},{"StartTime":4427.0,"Position":401.0,"HyperDash":false},{"StartTime":4513.0,"Position":384.0,"HyperDash":false},{"StartTime":4580.0,"Position":397.0,"HyperDash":false},{"StartTime":4684.0,"Position":384.0,"HyperDash":false}]},{"StartTime":4856.0,"Objects":[{"StartTime":4856.0,"Position":384.0,"HyperDash":false}]},{"StartTime":5028.0,"Objects":[{"StartTime":5028.0,"Position":384.0,"HyperDash":false}]},{"StartTime":5371.0,"Objects":[{"StartTime":5371.0,"Position":256.0,"HyperDash":false}]},{"StartTime":5713.0,"Objects":[{"StartTime":5713.0,"Position":256.0,"HyperDash":false}]},{"StartTime":6056.0,"Objects":[{"StartTime":6056.0,"Position":128.0,"HyperDash":false},{"StartTime":6141.0,"Position":88.01805,"HyperDash":false},{"StartTime":6227.0,"Position":72.0,"HyperDash":false},{"StartTime":6294.0,"Position":85.0,"HyperDash":false},{"StartTime":6398.0,"Position":72.0,"HyperDash":false}]},{"StartTime":6742.0,"Objects":[{"StartTime":6742.0,"Position":384.0,"HyperDash":false},{"StartTime":6827.0,"Position":410.981934,"HyperDash":false},{"StartTime":6913.0,"Position":440.0,"HyperDash":false},{"StartTime":6980.0,"Position":425.0,"HyperDash":false},{"StartTime":7084.0,"Position":440.0,"HyperDash":false}]},{"StartTime":7428.0,"Objects":[{"StartTime":7428.0,"Position":256.0,"HyperDash":false},{"StartTime":7513.0,"Position":243.6103,"HyperDash":false},{"StartTime":7599.0,"Position":259.546265,"HyperDash":false},{"StartTime":7684.0,"Position":282.3688,"HyperDash":false},{"StartTime":7770.0,"Position":257.824768,"HyperDash":false},{"StartTime":7856.0,"Position":253.344818,"HyperDash":false},{"StartTime":7942.0,"Position":259.546265,"HyperDash":false},{"StartTime":8009.0,"Position":232.678436,"HyperDash":false},{"StartTime":8113.0,"Position":256.0,"HyperDash":false}]},{"StartTime":8456.0,"Objects":[{"StartTime":8456.0,"Position":256.0,"HyperDash":false}]},{"StartTime":8799.0,"Objects":[{"StartTime":8799.0,"Position":428.0,"HyperDash":false},{"StartTime":8874.0,"Position":243.0,"HyperDash":false},{"StartTime":8949.0,"Position":422.0,"HyperDash":false},{"StartTime":9024.0,"Position":481.0,"HyperDash":false},{"StartTime":9099.0,"Position":104.0,"HyperDash":false},{"StartTime":9174.0,"Position":473.0,"HyperDash":false},{"StartTime":9249.0,"Position":135.0,"HyperDash":false},{"StartTime":9324.0,"Position":360.0,"HyperDash":false},{"StartTime":9399.0,"Position":123.0,"HyperDash":false},{"StartTime":9474.0,"Position":42.0,"HyperDash":false},{"StartTime":9549.0,"Position":393.0,"HyperDash":false},{"StartTime":9624.0,"Position":75.0,"HyperDash":false},{"StartTime":9699.0,"Position":377.0,"HyperDash":false},{"StartTime":9774.0,"Position":354.0,"HyperDash":false},{"StartTime":9849.0,"Position":287.0,"HyperDash":false},{"StartTime":9924.0,"Position":361.0,"HyperDash":false},{"StartTime":9999.0,"Position":479.0,"HyperDash":false},{"StartTime":10074.0,"Position":346.0,"HyperDash":false},{"StartTime":10149.0,"Position":266.0,"HyperDash":false},{"StartTime":10224.0,"Position":400.0,"HyperDash":false},{"StartTime":10299.0,"Position":202.0,"HyperDash":false},{"StartTime":10374.0,"Position":500.0,"HyperDash":false},{"StartTime":10449.0,"Position":80.0,"HyperDash":false},{"StartTime":10524.0,"Position":399.0,"HyperDash":false},{"StartTime":10599.0,"Position":455.0,"HyperDash":false},{"StartTime":10674.0,"Position":105.0,"HyperDash":false},{"StartTime":10749.0,"Position":100.0,"HyperDash":false},{"StartTime":10824.0,"Position":195.0,"HyperDash":false},{"StartTime":10899.0,"Position":106.0,"HyperDash":false},{"StartTime":10974.0,"Position":305.0,"HyperDash":false},{"StartTime":11049.0,"Position":225.0,"HyperDash":false},{"StartTime":11124.0,"Position":79.0,"HyperDash":false},{"StartTime":11199.0,"Position":38.0,"HyperDash":false}]},{"StartTime":11542.0,"Objects":[{"StartTime":11542.0,"Position":256.0,"HyperDash":false}]},{"StartTime":11885.0,"Objects":[{"StartTime":11885.0,"Position":60.0,"HyperDash":false},{"StartTime":11970.0,"Position":34.9856834,"HyperDash":false},{"StartTime":12056.0,"Position":54.15636,"HyperDash":false},{"StartTime":12141.0,"Position":60.52591,"HyperDash":false},{"StartTime":12227.0,"Position":114.312965,"HyperDash":false},{"StartTime":12313.0,"Position":82.90555,"HyperDash":false},{"StartTime":12399.0,"Position":54.15636,"HyperDash":false},{"StartTime":12466.0,"Position":53.6008873,"HyperDash":false},{"StartTime":12570.0,"Position":60.0,"HyperDash":false}]},{"StartTime":12913.0,"Objects":[{"StartTime":12913.0,"Position":256.0,"HyperDash":false}]},{"StartTime":13256.0,"Objects":[{"StartTime":13256.0,"Position":452.0,"HyperDash":false},{"StartTime":13341.0,"Position":477.0143,"HyperDash":false},{"StartTime":13427.0,"Position":457.843628,"HyperDash":false},{"StartTime":13512.0,"Position":425.4741,"HyperDash":false},{"StartTime":13598.0,"Position":397.687042,"HyperDash":false},{"StartTime":13684.0,"Position":442.094452,"HyperDash":false},{"StartTime":13770.0,"Position":457.843628,"HyperDash":false},{"StartTime":13837.0,"Position":471.3991,"HyperDash":false},{"StartTime":13941.0,"Position":452.0,"HyperDash":false}]},{"StartTime":14285.0,"Objects":[{"StartTime":14285.0,"Position":256.0,"HyperDash":false}]},{"StartTime":14799.0,"Objects":[{"StartTime":14799.0,"Position":88.0,"HyperDash":false},{"StartTime":14884.0,"Position":60.0,"HyperDash":false},{"StartTime":14970.0,"Position":88.0,"HyperDash":false},{"StartTime":15056.0,"Position":60.0,"HyperDash":false},{"StartTime":15141.0,"Position":88.0,"HyperDash":false},{"StartTime":15227.0,"Position":60.0,"HyperDash":false},{"StartTime":15313.0,"Position":88.0,"HyperDash":false},{"StartTime":15399.0,"Position":60.0,"HyperDash":false},{"StartTime":15484.0,"Position":88.0,"HyperDash":false},{"StartTime":15570.0,"Position":60.0,"HyperDash":false},{"StartTime":15656.0,"Position":88.0,"HyperDash":false}]},{"StartTime":15999.0,"Objects":[{"StartTime":15999.0,"Position":32.0,"HyperDash":false}]},{"StartTime":16171.0,"Objects":[{"StartTime":16171.0,"Position":96.0,"HyperDash":false}]},{"StartTime":16342.0,"Objects":[{"StartTime":16342.0,"Position":160.0,"HyperDash":false}]},{"StartTime":16685.0,"Objects":[{"StartTime":16685.0,"Position":224.0,"HyperDash":false}]},{"StartTime":17028.0,"Objects":[{"StartTime":17028.0,"Position":328.0,"HyperDash":false},{"StartTime":17095.0,"Position":334.2591,"HyperDash":false},{"StartTime":17199.0,"Position":349.0792,"HyperDash":false}]},{"StartTime":17371.0,"Objects":[{"StartTime":17371.0,"Position":412.0,"HyperDash":false},{"StartTime":17438.0,"Position":425.881073,"HyperDash":false},{"StartTime":17542.0,"Position":432.114349,"HyperDash":false}]},{"StartTime":17713.0,"Objects":[{"StartTime":17713.0,"Position":448.0,"HyperDash":false},{"StartTime":17780.0,"Position":467.063019,"HyperDash":false},{"StartTime":17884.0,"Position":511.9668,"HyperDash":false}]},{"StartTime":18056.0,"Objects":[{"StartTime":18056.0,"Position":472.0,"HyperDash":false},{"StartTime":18123.0,"Position":439.87265,"HyperDash":false},{"StartTime":18227.0,"Position":407.869,"HyperDash":false}]},{"StartTime":18399.0,"Objects":[{"StartTime":18399.0,"Position":388.0,"HyperDash":false},{"StartTime":18466.0,"Position":396.55722,"HyperDash":false},{"StartTime":18570.0,"Position":361.3475,"HyperDash":false}]},{"StartTime":18742.0,"Objects":[{"StartTime":18742.0,"Position":300.0,"HyperDash":false},{"StartTime":18809.0,"Position":305.44278,"HyperDash":false},{"StartTime":18913.0,"Position":326.6525,"HyperDash":false}]},{"StartTime":19085.0,"Objects":[{"StartTime":19085.0,"Position":344.0,"HyperDash":false}]},{"StartTime":19428.0,"Objects":[{"StartTime":19428.0,"Position":156.0,"HyperDash":false}]},{"StartTime":19771.0,"Objects":[{"StartTime":19771.0,"Position":256.0,"HyperDash":false}]},{"StartTime":20456.0,"Objects":[{"StartTime":20456.0,"Position":256.0,"HyperDash":false}]},{"StartTime":21142.0,"Objects":[{"StartTime":21142.0,"Position":124.0,"HyperDash":false}]},{"StartTime":21485.0,"Objects":[{"StartTime":21485.0,"Position":256.0,"HyperDash":false}]},{"StartTime":21828.0,"Objects":[{"StartTime":21828.0,"Position":388.0,"HyperDash":false}]},{"StartTime":22513.0,"Objects":[{"StartTime":22513.0,"Position":504.0,"HyperDash":false},{"StartTime":22580.0,"Position":476.5731,"HyperDash":false},{"StartTime":22684.0,"Position":434.0,"HyperDash":false}]},{"StartTime":22856.0,"Objects":[{"StartTime":22856.0,"Position":448.0,"HyperDash":false}]},{"StartTime":23028.0,"Objects":[{"StartTime":23028.0,"Position":376.0,"HyperDash":false}]},{"StartTime":23199.0,"Objects":[{"StartTime":23199.0,"Position":360.0,"HyperDash":false},{"StartTime":23266.0,"Position":347.5731,"HyperDash":false},{"StartTime":23370.0,"Position":290.0,"HyperDash":false}]},{"StartTime":23542.0,"Objects":[{"StartTime":23542.0,"Position":304.0,"HyperDash":false}]},{"StartTime":23713.0,"Objects":[{"StartTime":23713.0,"Position":232.0,"HyperDash":false}]},{"StartTime":23885.0,"Objects":[{"StartTime":23885.0,"Position":216.0,"HyperDash":false},{"StartTime":23952.0,"Position":172.5731,"HyperDash":false},{"StartTime":24056.0,"Position":146.0,"HyperDash":false}]},{"StartTime":24228.0,"Objects":[{"StartTime":24228.0,"Position":160.0,"HyperDash":false}]},{"StartTime":24399.0,"Objects":[{"StartTime":24399.0,"Position":88.0,"HyperDash":false}]},{"StartTime":24571.0,"Objects":[{"StartTime":24571.0,"Position":72.0,"HyperDash":false},{"StartTime":24656.0,"Position":54.2046776,"HyperDash":false},{"StartTime":24742.0,"Position":2.0,"HyperDash":false},{"StartTime":24809.0,"Position":32.4269028,"HyperDash":false},{"StartTime":24913.0,"Position":72.0,"HyperDash":false}]},{"StartTime":25256.0,"Objects":[{"StartTime":25256.0,"Position":8.0,"HyperDash":false},{"StartTime":25323.0,"Position":31.4269028,"HyperDash":false},{"StartTime":25427.0,"Position":78.0,"HyperDash":false}]},{"StartTime":25599.0,"Objects":[{"StartTime":25599.0,"Position":64.0,"HyperDash":false}]},{"StartTime":25771.0,"Objects":[{"StartTime":25771.0,"Position":136.0,"HyperDash":false}]},{"StartTime":25942.0,"Objects":[{"StartTime":25942.0,"Position":152.0,"HyperDash":false},{"StartTime":26009.0,"Position":187.4269,"HyperDash":false},{"StartTime":26113.0,"Position":222.0,"HyperDash":false}]},{"StartTime":26285.0,"Objects":[{"StartTime":26285.0,"Position":208.0,"HyperDash":false}]},{"StartTime":26456.0,"Objects":[{"StartTime":26456.0,"Position":280.0,"HyperDash":false}]},{"StartTime":26628.0,"Objects":[{"StartTime":26628.0,"Position":296.0,"HyperDash":false},{"StartTime":26695.0,"Position":322.4269,"HyperDash":false},{"StartTime":26799.0,"Position":366.0,"HyperDash":false}]},{"StartTime":26971.0,"Objects":[{"StartTime":26971.0,"Position":352.0,"HyperDash":false}]},{"StartTime":27142.0,"Objects":[{"StartTime":27142.0,"Position":424.0,"HyperDash":false}]},{"StartTime":27313.0,"Objects":[{"StartTime":27313.0,"Position":440.0,"HyperDash":false},{"StartTime":27398.0,"Position":489.795319,"HyperDash":false},{"StartTime":27484.0,"Position":510.0,"HyperDash":false},{"StartTime":27551.0,"Position":470.5731,"HyperDash":false},{"StartTime":27655.0,"Position":440.0,"HyperDash":false}]},{"StartTime":27999.0,"Objects":[{"StartTime":27999.0,"Position":40.0,"HyperDash":false},{"StartTime":28066.0,"Position":24.0,"HyperDash":false},{"StartTime":28170.0,"Position":40.0,"HyperDash":false}]},{"StartTime":28342.0,"Objects":[{"StartTime":28342.0,"Position":112.0,"HyperDash":false},{"StartTime":28427.0,"Position":112.0,"HyperDash":false},{"StartTime":28513.0,"Position":112.0,"HyperDash":false}]},{"StartTime":28685.0,"Objects":[{"StartTime":28685.0,"Position":184.0,"HyperDash":false},{"StartTime":28752.0,"Position":177.0,"HyperDash":false},{"StartTime":28856.0,"Position":184.0,"HyperDash":false}]},{"StartTime":29028.0,"Objects":[{"StartTime":29028.0,"Position":260.0,"HyperDash":false},{"StartTime":29113.0,"Position":260.0,"HyperDash":false},{"StartTime":29199.0,"Position":260.0,"HyperDash":false}]},{"StartTime":29371.0,"Objects":[{"StartTime":29371.0,"Position":336.0,"HyperDash":false},{"StartTime":29438.0,"Position":333.2137,"HyperDash":false},{"StartTime":29542.0,"Position":374.829,"HyperDash":false}]},{"StartTime":29713.0,"Objects":[{"StartTime":29713.0,"Position":440.0,"HyperDash":false},{"StartTime":29780.0,"Position":420.18338,"HyperDash":false},{"StartTime":29884.0,"Position":399.632172,"HyperDash":false}]},{"StartTime":30056.0,"Objects":[{"StartTime":30056.0,"Position":460.0,"HyperDash":false},{"StartTime":30141.0,"Position":479.41452,"HyperDash":false},{"StartTime":30227.0,"Position":460.0,"HyperDash":false},{"StartTime":30313.0,"Position":479.41452,"HyperDash":false},{"StartTime":30398.0,"Position":460.0,"HyperDash":false}]},{"StartTime":30742.0,"Objects":[{"StartTime":30742.0,"Position":328.0,"HyperDash":false},{"StartTime":30827.0,"Position":293.0,"HyperDash":false},{"StartTime":30913.0,"Position":328.0,"HyperDash":false},{"StartTime":30999.0,"Position":293.0,"HyperDash":false}]},{"StartTime":31085.0,"Objects":[{"StartTime":31085.0,"Position":256.0,"HyperDash":false},{"StartTime":31170.0,"Position":221.0,"HyperDash":false},{"StartTime":31256.0,"Position":256.0,"HyperDash":false},{"StartTime":31342.0,"Position":221.0,"HyperDash":false}]},{"StartTime":31428.0,"Objects":[{"StartTime":31428.0,"Position":184.0,"HyperDash":false},{"StartTime":31513.0,"Position":149.0,"HyperDash":false},{"StartTime":31599.0,"Position":184.0,"HyperDash":false},{"StartTime":31685.0,"Position":149.0,"HyperDash":false}]},{"StartTime":31771.0,"Objects":[{"StartTime":31771.0,"Position":112.0,"HyperDash":false},{"StartTime":31856.0,"Position":77.0,"HyperDash":false},{"StartTime":31942.0,"Position":112.0,"HyperDash":false},{"StartTime":32028.0,"Position":77.0,"HyperDash":false}]},{"StartTime":32113.0,"Objects":[{"StartTime":32113.0,"Position":40.0,"HyperDash":false}]},{"StartTime":32456.0,"Objects":[{"StartTime":32456.0,"Position":40.0,"HyperDash":false}]},{"StartTime":32799.0,"Objects":[{"StartTime":32799.0,"Position":184.0,"HyperDash":false}]},{"StartTime":33142.0,"Objects":[{"StartTime":33142.0,"Position":184.0,"HyperDash":false}]},{"StartTime":33485.0,"Objects":[{"StartTime":33485.0,"Position":304.0,"HyperDash":false},{"StartTime":33570.0,"Position":332.600983,"HyperDash":false},{"StartTime":33656.0,"Position":351.4796,"HyperDash":false},{"StartTime":33723.0,"Position":368.082733,"HyperDash":false},{"StartTime":33827.0,"Position":398.9592,"HyperDash":false}]},{"StartTime":34342.0,"Objects":[{"StartTime":34342.0,"Position":256.0,"HyperDash":false}]},{"StartTime":34513.0,"Objects":[{"StartTime":34513.0,"Position":256.0,"HyperDash":false}]},{"StartTime":34856.0,"Objects":[{"StartTime":34856.0,"Position":136.0,"HyperDash":false},{"StartTime":34941.0,"Position":152.0,"HyperDash":false},{"StartTime":35027.0,"Position":136.0,"HyperDash":false},{"StartTime":35094.0,"Position":150.0,"HyperDash":false},{"StartTime":35198.0,"Position":136.0,"HyperDash":false}]},{"StartTime":35371.0,"Objects":[{"StartTime":35371.0,"Position":104.0,"HyperDash":false},{"StartTime":35456.0,"Position":124.558014,"HyperDash":false},{"StartTime":35542.0,"Position":170.988922,"HyperDash":false},{"StartTime":35609.0,"Position":180.576416,"HyperDash":false},{"StartTime":35713.0,"Position":209.857956,"HyperDash":false}]},{"StartTime":35885.0,"Objects":[{"StartTime":35885.0,"Position":212.0,"HyperDash":false}]},{"StartTime":36228.0,"Objects":[{"StartTime":36228.0,"Position":408.0,"HyperDash":false},{"StartTime":36313.0,"Position":441.692383,"HyperDash":false},{"StartTime":36399.0,"Position":463.7653,"HyperDash":false},{"StartTime":36466.0,"Position":471.929932,"HyperDash":false},{"StartTime":36570.0,"Position":480.400452,"HyperDash":false}]},{"StartTime":37085.0,"Objects":[{"StartTime":37085.0,"Position":360.0,"HyperDash":false}]},{"StartTime":37256.0,"Objects":[{"StartTime":37256.0,"Position":360.0,"HyperDash":false}]},{"StartTime":37599.0,"Objects":[{"StartTime":37599.0,"Position":232.0,"HyperDash":false},{"StartTime":37684.0,"Position":186.367691,"HyperDash":false},{"StartTime":37770.0,"Position":175.2116,"HyperDash":false},{"StartTime":37837.0,"Position":153.710571,"HyperDash":false},{"StartTime":37941.0,"Position":106.279663,"HyperDash":false}]},{"StartTime":38113.0,"Objects":[{"StartTime":38113.0,"Position":56.0,"HyperDash":false},{"StartTime":38198.0,"Position":39.6659164,"HyperDash":false},{"StartTime":38284.0,"Position":38.9134,"HyperDash":false},{"StartTime":38351.0,"Position":31.39479,"HyperDash":false},{"StartTime":38455.0,"Position":85.0976944,"HyperDash":false}]},{"StartTime":38628.0,"Objects":[{"StartTime":38628.0,"Position":156.0,"HyperDash":false}]},{"StartTime":38971.0,"Objects":[{"StartTime":38971.0,"Position":256.0,"HyperDash":false},{"StartTime":39056.0,"Position":221.399033,"HyperDash":false},{"StartTime":39142.0,"Position":208.5204,"HyperDash":false},{"StartTime":39209.0,"Position":182.917267,"HyperDash":false},{"StartTime":39313.0,"Position":161.0408,"HyperDash":false}]},{"StartTime":39828.0,"Objects":[{"StartTime":39828.0,"Position":256.0,"HyperDash":false}]},{"StartTime":39999.0,"Objects":[{"StartTime":39999.0,"Position":256.0,"HyperDash":false}]},{"StartTime":40342.0,"Objects":[{"StartTime":40342.0,"Position":376.0,"HyperDash":false},{"StartTime":40427.0,"Position":392.0,"HyperDash":false},{"StartTime":40513.0,"Position":376.0,"HyperDash":false},{"StartTime":40580.0,"Position":369.0,"HyperDash":false},{"StartTime":40684.0,"Position":376.0,"HyperDash":false}]},{"StartTime":40856.0,"Objects":[{"StartTime":40856.0,"Position":408.0,"HyperDash":false},{"StartTime":40941.0,"Position":355.442,"HyperDash":false},{"StartTime":41027.0,"Position":341.011078,"HyperDash":false},{"StartTime":41094.0,"Position":333.423584,"HyperDash":false},{"StartTime":41198.0,"Position":302.142059,"HyperDash":false}]},{"StartTime":41371.0,"Objects":[{"StartTime":41371.0,"Position":300.0,"HyperDash":false}]},{"StartTime":41713.0,"Objects":[{"StartTime":41713.0,"Position":104.0,"HyperDash":false},{"StartTime":41798.0,"Position":74.30763,"HyperDash":false},{"StartTime":41884.0,"Position":48.23472,"HyperDash":false},{"StartTime":41951.0,"Position":33.07008,"HyperDash":false},{"StartTime":42055.0,"Position":31.59955,"HyperDash":false}]},{"StartTime":42571.0,"Objects":[{"StartTime":42571.0,"Position":152.0,"HyperDash":false}]},{"StartTime":42742.0,"Objects":[{"StartTime":42742.0,"Position":152.0,"HyperDash":false}]},{"StartTime":43085.0,"Objects":[{"StartTime":43085.0,"Position":256.0,"HyperDash":false},{"StartTime":43170.0,"Position":256.0,"HyperDash":false},{"StartTime":43256.0,"Position":256.0,"HyperDash":false},{"StartTime":43342.0,"Position":256.0,"HyperDash":false},{"StartTime":43427.0,"Position":256.0,"HyperDash":false},{"StartTime":43513.0,"Position":256.0,"HyperDash":false},{"StartTime":43599.0,"Position":256.0,"HyperDash":false},{"StartTime":43685.0,"Position":256.0,"HyperDash":false},{"StartTime":43770.0,"Position":256.0,"HyperDash":false}]},{"StartTime":44113.0,"Objects":[{"StartTime":44113.0,"Position":256.0,"HyperDash":false}]},{"StartTime":44456.0,"Objects":[{"StartTime":44456.0,"Position":124.0,"HyperDash":false}]},{"StartTime":44628.0,"Objects":[{"StartTime":44628.0,"Position":72.0,"HyperDash":false},{"StartTime":44713.0,"Position":40.92307,"HyperDash":false},{"StartTime":44799.0,"Position":52.98573,"HyperDash":false},{"StartTime":44884.0,"Position":77.93154,"HyperDash":false},{"StartTime":44970.0,"Position":95.82509,"HyperDash":false},{"StartTime":45038.0,"Position":118.951027,"HyperDash":false},{"StartTime":45142.0,"Position":163.988525,"HyperDash":false}]},{"StartTime":45485.0,"Objects":[{"StartTime":45485.0,"Position":256.0,"HyperDash":false}]},{"StartTime":45828.0,"Objects":[{"StartTime":45828.0,"Position":388.0,"HyperDash":false}]},{"StartTime":45999.0,"Objects":[{"StartTime":45999.0,"Position":440.0,"HyperDash":false},{"StartTime":46084.0,"Position":441.0769,"HyperDash":false},{"StartTime":46170.0,"Position":459.014282,"HyperDash":false},{"StartTime":46255.0,"Position":425.068451,"HyperDash":false},{"StartTime":46341.0,"Position":416.174927,"HyperDash":false},{"StartTime":46409.0,"Position":398.048981,"HyperDash":false},{"StartTime":46513.0,"Position":348.011475,"HyperDash":false}]},{"StartTime":46856.0,"Objects":[{"StartTime":46856.0,"Position":256.0,"HyperDash":false}]},{"StartTime":47199.0,"Objects":[{"StartTime":47199.0,"Position":256.0,"HyperDash":false},{"StartTime":47284.0,"Position":244.431641,"HyperDash":false},{"StartTime":47370.0,"Position":255.566513,"HyperDash":false},{"StartTime":47455.0,"Position":277.621033,"HyperDash":false},{"StartTime":47541.0,"Position":254.8021,"HyperDash":false},{"StartTime":47627.0,"Position":267.5996,"HyperDash":false},{"StartTime":47713.0,"Position":255.632889,"HyperDash":false},{"StartTime":47798.0,"Position":231.420425,"HyperDash":false},{"StartTime":47884.0,"Position":256.0,"HyperDash":false},{"StartTime":47970.0,"Position":247.424866,"HyperDash":false},{"StartTime":48056.0,"Position":255.699265,"HyperDash":false},{"StartTime":48123.0,"Position":258.327057,"HyperDash":false},{"StartTime":48227.0,"Position":254.8021,"HyperDash":false}]},{"StartTime":48571.0,"Objects":[{"StartTime":48571.0,"Position":392.0,"HyperDash":false},{"StartTime":48656.0,"Position":373.0,"HyperDash":false},{"StartTime":48742.0,"Position":392.0,"HyperDash":false},{"StartTime":48809.0,"Position":387.0,"HyperDash":false},{"StartTime":48913.0,"Position":392.0,"HyperDash":false}]},{"StartTime":49085.0,"Objects":[{"StartTime":49085.0,"Position":464.0,"HyperDash":false},{"StartTime":49170.0,"Position":434.350128,"HyperDash":false},{"StartTime":49256.0,"Position":431.4105,"HyperDash":false},{"StartTime":49341.0,"Position":405.503876,"HyperDash":false},{"StartTime":49427.0,"Position":365.203827,"HyperDash":false},{"StartTime":49495.0,"Position":336.536133,"HyperDash":false},{"StartTime":49599.0,"Position":324.364319,"HyperDash":false}]},{"StartTime":49942.0,"Objects":[{"StartTime":49942.0,"Position":187.0,"HyperDash":false},{"StartTime":50027.0,"Position":163.228943,"HyperDash":false},{"StartTime":50113.0,"Position":148.783264,"HyperDash":false},{"StartTime":50198.0,"Position":108.904266,"HyperDash":false},{"StartTime":50284.0,"Position":81.87666,"HyperDash":false},{"StartTime":50352.0,"Position":62.3181648,"HyperDash":false},{"StartTime":50456.0,"Position":47.9551849,"HyperDash":false}]},{"StartTime":50628.0,"Objects":[{"StartTime":50628.0,"Position":120.0,"HyperDash":false},{"StartTime":50713.0,"Position":106.0,"HyperDash":false},{"StartTime":50799.0,"Position":120.0,"HyperDash":false},{"StartTime":50866.0,"Position":135.0,"HyperDash":false},{"StartTime":50970.0,"Position":120.0,"HyperDash":false}]},{"StartTime":51313.0,"Objects":[{"StartTime":51313.0,"Position":257.0,"HyperDash":false},{"StartTime":51398.0,"Position":234.050232,"HyperDash":false},{"StartTime":51484.0,"Position":255.277374,"HyperDash":false},{"StartTime":51569.0,"Position":284.5524,"HyperDash":false},{"StartTime":51655.0,"Position":256.423248,"HyperDash":false},{"StartTime":51741.0,"Position":248.555389,"HyperDash":false},{"StartTime":51827.0,"Position":255.347473,"HyperDash":false},{"StartTime":51912.0,"Position":263.0151,"HyperDash":false},{"StartTime":51998.0,"Position":257.0,"HyperDash":false},{"StartTime":52084.0,"Position":228.030624,"HyperDash":false},{"StartTime":52170.0,"Position":255.417587,"HyperDash":false},{"StartTime":52237.0,"Position":278.820038,"HyperDash":false},{"StartTime":52341.0,"Position":256.423248,"HyperDash":false}]},{"StartTime":52685.0,"Objects":[{"StartTime":52685.0,"Position":256.0,"HyperDash":false}]},{"StartTime":53028.0,"Objects":[{"StartTime":53028.0,"Position":169.0,"HyperDash":false},{"StartTime":53113.0,"Position":148.767334,"HyperDash":false},{"StartTime":53199.0,"Position":102.039978,"HyperDash":false},{"StartTime":53284.0,"Position":65.15436,"HyperDash":false},{"StartTime":53370.0,"Position":56.49534,"HyperDash":false},{"StartTime":53438.0,"Position":50.6727638,"HyperDash":false},{"StartTime":53542.0,"Position":72.11841,"HyperDash":false}]},{"StartTime":53713.0,"Objects":[{"StartTime":53713.0,"Position":124.0,"HyperDash":false}]},{"StartTime":54056.0,"Objects":[{"StartTime":54056.0,"Position":68.0,"HyperDash":false},{"StartTime":54141.0,"Position":56.93203,"HyperDash":false},{"StartTime":54227.0,"Position":68.0,"HyperDash":false}]},{"StartTime":54399.0,"Objects":[{"StartTime":54399.0,"Position":156.0,"HyperDash":false}]},{"StartTime":54742.0,"Objects":[{"StartTime":54742.0,"Position":444.0,"HyperDash":false},{"StartTime":54827.0,"Position":455.067963,"HyperDash":false},{"StartTime":54913.0,"Position":444.0,"HyperDash":false}]},{"StartTime":55085.0,"Objects":[{"StartTime":55085.0,"Position":356.0,"HyperDash":false}]},{"StartTime":55428.0,"Objects":[{"StartTime":55428.0,"Position":356.0,"HyperDash":false},{"StartTime":55513.0,"Position":335.3816,"HyperDash":false},{"StartTime":55599.0,"Position":294.1601,"HyperDash":false},{"StartTime":55684.0,"Position":272.865723,"HyperDash":false},{"StartTime":55770.0,"Position":255.69072,"HyperDash":false},{"StartTime":55856.0,"Position":254.907425,"HyperDash":false},{"StartTime":55942.0,"Position":216.981689,"HyperDash":false},{"StartTime":56009.0,"Position":188.30954,"HyperDash":false},{"StartTime":56113.0,"Position":154.812271,"HyperDash":false}]},{"StartTime":56285.0,"Objects":[{"StartTime":56285.0,"Position":160.0,"HyperDash":false}]},{"StartTime":56456.0,"Objects":[{"StartTime":56456.0,"Position":92.0,"HyperDash":false}]},{"StartTime":56628.0,"Objects":[{"StartTime":56628.0,"Position":84.0,"HyperDash":false}]},{"StartTime":56799.0,"Objects":[{"StartTime":56799.0,"Position":156.0,"HyperDash":false},{"StartTime":56884.0,"Position":179.6867,"HyperDash":false},{"StartTime":56970.0,"Position":184.530014,"HyperDash":false},{"StartTime":57055.0,"Position":210.992,"HyperDash":false},{"StartTime":57141.0,"Position":239.917923,"HyperDash":false},{"StartTime":57227.0,"Position":197.399063,"HyperDash":false},{"StartTime":57313.0,"Position":182.462265,"HyperDash":false},{"StartTime":57380.0,"Position":158.21933,"HyperDash":false},{"StartTime":57484.0,"Position":155.038208,"HyperDash":false}]},{"StartTime":57656.0,"Objects":[{"StartTime":57656.0,"Position":92.0,"HyperDash":false}]},{"StartTime":57828.0,"Objects":[{"StartTime":57828.0,"Position":88.0,"HyperDash":false}]},{"StartTime":57999.0,"Objects":[{"StartTime":57999.0,"Position":148.0,"HyperDash":false}]},{"StartTime":58171.0,"Objects":[{"StartTime":58171.0,"Position":155.0,"HyperDash":false},{"StartTime":58256.0,"Position":190.6184,"HyperDash":false},{"StartTime":58342.0,"Position":216.83992,"HyperDash":false},{"StartTime":58427.0,"Position":255.134277,"HyperDash":false},{"StartTime":58513.0,"Position":255.3093,"HyperDash":false},{"StartTime":58599.0,"Position":262.09256,"HyperDash":false},{"StartTime":58685.0,"Position":294.0183,"HyperDash":false},{"StartTime":58752.0,"Position":306.69046,"HyperDash":false},{"StartTime":58856.0,"Position":356.187744,"HyperDash":false}]},{"StartTime":59028.0,"Objects":[{"StartTime":59028.0,"Position":356.0,"HyperDash":false}]},{"StartTime":59199.0,"Objects":[{"StartTime":59199.0,"Position":424.0,"HyperDash":false}]},{"StartTime":59371.0,"Objects":[{"StartTime":59371.0,"Position":428.0,"HyperDash":false}]},{"StartTime":59542.0,"Objects":[{"StartTime":59542.0,"Position":356.0,"HyperDash":false},{"StartTime":59627.0,"Position":337.313324,"HyperDash":false},{"StartTime":59713.0,"Position":327.469971,"HyperDash":false},{"StartTime":59798.0,"Position":290.008,"HyperDash":false},{"StartTime":59884.0,"Position":272.0821,"HyperDash":false},{"StartTime":59970.0,"Position":294.600952,"HyperDash":false},{"StartTime":60056.0,"Position":329.53775,"HyperDash":false},{"StartTime":60123.0,"Position":351.78067,"HyperDash":false},{"StartTime":60227.0,"Position":356.9618,"HyperDash":false}]},{"StartTime":60399.0,"Objects":[{"StartTime":60399.0,"Position":424.0,"HyperDash":false}]},{"StartTime":60571.0,"Objects":[{"StartTime":60571.0,"Position":428.0,"HyperDash":false}]},{"StartTime":60742.0,"Objects":[{"StartTime":60742.0,"Position":360.0,"HyperDash":false}]},{"StartTime":60913.0,"Objects":[{"StartTime":60913.0,"Position":284.0,"HyperDash":false},{"StartTime":60980.0,"Position":271.5731,"HyperDash":false},{"StartTime":61084.0,"Position":214.0,"HyperDash":false}]},{"StartTime":61256.0,"Objects":[{"StartTime":61256.0,"Position":136.0,"HyperDash":false}]},{"StartTime":61428.0,"Objects":[{"StartTime":61428.0,"Position":60.0,"HyperDash":false}]},{"StartTime":61513.0,"Objects":[{"StartTime":61513.0,"Position":60.0,"HyperDash":false}]},{"StartTime":61599.0,"Objects":[{"StartTime":61599.0,"Position":60.0,"HyperDash":false},{"StartTime":61666.0,"Position":65.0,"HyperDash":false},{"StartTime":61770.0,"Position":60.0,"HyperDash":false}]},{"StartTime":61942.0,"Objects":[{"StartTime":61942.0,"Position":60.0,"HyperDash":false}]},{"StartTime":62113.0,"Objects":[{"StartTime":62113.0,"Position":136.0,"HyperDash":false}]},{"StartTime":62199.0,"Objects":[{"StartTime":62199.0,"Position":136.0,"HyperDash":false}]},{"StartTime":62285.0,"Objects":[{"StartTime":62285.0,"Position":136.0,"HyperDash":false},{"StartTime":62352.0,"Position":120.0,"HyperDash":false},{"StartTime":62456.0,"Position":136.0,"HyperDash":false}]},{"StartTime":62628.0,"Objects":[{"StartTime":62628.0,"Position":212.0,"HyperDash":false}]},{"StartTime":62799.0,"Objects":[{"StartTime":62799.0,"Position":212.0,"HyperDash":false}]},{"StartTime":62885.0,"Objects":[{"StartTime":62885.0,"Position":212.0,"HyperDash":false}]},{"StartTime":62971.0,"Objects":[{"StartTime":62971.0,"Position":212.0,"HyperDash":false},{"StartTime":63038.0,"Position":195.0,"HyperDash":false},{"StartTime":63142.0,"Position":212.0,"HyperDash":false}]},{"StartTime":63313.0,"Objects":[{"StartTime":63313.0,"Position":136.0,"HyperDash":false},{"StartTime":63380.0,"Position":120.5731,"HyperDash":false},{"StartTime":63484.0,"Position":66.0,"HyperDash":false}]},{"StartTime":63656.0,"Objects":[{"StartTime":63656.0,"Position":356.0,"HyperDash":false},{"StartTime":63741.0,"Position":362.0,"HyperDash":false},{"StartTime":63827.0,"Position":347.0,"HyperDash":false},{"StartTime":63913.0,"Position":252.0,"HyperDash":false},{"StartTime":63999.0,"Position":477.0,"HyperDash":false},{"StartTime":64084.0,"Position":358.0,"HyperDash":false},{"StartTime":64170.0,"Position":17.0,"HyperDash":false},{"StartTime":64256.0,"Position":399.0,"HyperDash":false},{"StartTime":64342.0,"Position":280.0,"HyperDash":false},{"StartTime":64427.0,"Position":304.0,"HyperDash":false},{"StartTime":64513.0,"Position":221.0,"HyperDash":false},{"StartTime":64599.0,"Position":407.0,"HyperDash":false},{"StartTime":64685.0,"Position":287.0,"HyperDash":false},{"StartTime":64770.0,"Position":135.0,"HyperDash":false},{"StartTime":64856.0,"Position":437.0,"HyperDash":false},{"StartTime":64942.0,"Position":289.0,"HyperDash":false},{"StartTime":65028.0,"Position":464.0,"HyperDash":false}]},{"StartTime":65713.0,"Objects":[{"StartTime":65713.0,"Position":256.0,"HyperDash":false},{"StartTime":65798.0,"Position":256.0,"HyperDash":false},{"StartTime":65884.0,"Position":256.0,"HyperDash":false}]},{"StartTime":66056.0,"Objects":[{"StartTime":66056.0,"Position":288.0,"HyperDash":false}]},{"StartTime":66228.0,"Objects":[{"StartTime":66228.0,"Position":328.0,"HyperDash":false}]},{"StartTime":66399.0,"Objects":[{"StartTime":66399.0,"Position":400.0,"HyperDash":false},{"StartTime":66466.0,"Position":404.432526,"HyperDash":false},{"StartTime":66570.0,"Position":443.844757,"HyperDash":false}]},{"StartTime":66742.0,"Objects":[{"StartTime":66742.0,"Position":380.0,"HyperDash":false}]},{"StartTime":66913.0,"Objects":[{"StartTime":66913.0,"Position":444.0,"HyperDash":false},{"StartTime":66980.0,"Position":415.4034,"HyperDash":false},{"StartTime":67084.0,"Position":392.189362,"HyperDash":false}]},{"StartTime":67256.0,"Objects":[{"StartTime":67256.0,"Position":316.0,"HyperDash":false},{"StartTime":67323.0,"Position":306.150818,"HyperDash":false},{"StartTime":67427.0,"Position":300.033234,"HyperDash":false}]},{"StartTime":67599.0,"Objects":[{"StartTime":67599.0,"Position":224.0,"HyperDash":false},{"StartTime":67666.0,"Position":211.175949,"HyperDash":false},{"StartTime":67770.0,"Position":163.867111,"HyperDash":false}]},{"StartTime":67942.0,"Objects":[{"StartTime":67942.0,"Position":104.0,"HyperDash":false},{"StartTime":68009.0,"Position":130.849182,"HyperDash":false},{"StartTime":68113.0,"Position":119.966782,"HyperDash":false}]},{"StartTime":68285.0,"Objects":[{"StartTime":68285.0,"Position":80.0,"HyperDash":false},{"StartTime":68352.0,"Position":100.824059,"HyperDash":false},{"StartTime":68456.0,"Position":140.132889,"HyperDash":false}]},{"StartTime":68628.0,"Objects":[{"StartTime":68628.0,"Position":200.0,"HyperDash":false},{"StartTime":68713.0,"Position":188.823929,"HyperDash":false},{"StartTime":68799.0,"Position":213.728134,"HyperDash":false},{"StartTime":68866.0,"Position":223.349274,"HyperDash":false},{"StartTime":68970.0,"Position":200.0,"HyperDash":false}]},{"StartTime":69142.0,"Objects":[{"StartTime":69142.0,"Position":212.0,"HyperDash":false}]},{"StartTime":69313.0,"Objects":[{"StartTime":69313.0,"Position":256.0,"HyperDash":false}]},{"StartTime":69485.0,"Objects":[{"StartTime":69485.0,"Position":256.0,"HyperDash":false}]},{"StartTime":69656.0,"Objects":[{"StartTime":69656.0,"Position":292.0,"HyperDash":false}]},{"StartTime":69828.0,"Objects":[{"StartTime":69828.0,"Position":292.0,"HyperDash":false}]},{"StartTime":69999.0,"Objects":[{"StartTime":69999.0,"Position":368.0,"HyperDash":false}]},{"StartTime":70085.0,"Objects":[{"StartTime":70085.0,"Position":376.0,"HyperDash":false}]},{"StartTime":70171.0,"Objects":[{"StartTime":70171.0,"Position":384.0,"HyperDash":false}]},{"StartTime":70256.0,"Objects":[{"StartTime":70256.0,"Position":392.0,"HyperDash":false}]},{"StartTime":70342.0,"Objects":[{"StartTime":70342.0,"Position":400.0,"HyperDash":false}]},{"StartTime":70428.0,"Objects":[{"StartTime":70428.0,"Position":408.0,"HyperDash":false}]},{"StartTime":70513.0,"Objects":[{"StartTime":70513.0,"Position":416.0,"HyperDash":false},{"StartTime":70598.0,"Position":442.363953,"HyperDash":false},{"StartTime":70684.0,"Position":451.799652,"HyperDash":false},{"StartTime":70769.0,"Position":450.290955,"HyperDash":false},{"StartTime":70855.0,"Position":444.293518,"HyperDash":false},{"StartTime":70941.0,"Position":469.222717,"HyperDash":false},{"StartTime":71027.0,"Position":451.823273,"HyperDash":false},{"StartTime":71112.0,"Position":447.6526,"HyperDash":false},{"StartTime":71198.0,"Position":416.0,"HyperDash":false},{"StartTime":71284.0,"Position":452.508881,"HyperDash":false},{"StartTime":71370.0,"Position":451.846527,"HyperDash":false},{"StartTime":71437.0,"Position":457.989929,"HyperDash":false},{"StartTime":71541.0,"Position":444.293518,"HyperDash":false}]},{"StartTime":71885.0,"Objects":[{"StartTime":71885.0,"Position":312.0,"HyperDash":false}]},{"StartTime":72056.0,"Objects":[{"StartTime":72056.0,"Position":312.0,"HyperDash":false}]},{"StartTime":72228.0,"Objects":[{"StartTime":72228.0,"Position":224.0,"HyperDash":false}]},{"StartTime":72313.0,"Objects":[{"StartTime":72313.0,"Position":216.0,"HyperDash":false}]},{"StartTime":72399.0,"Objects":[{"StartTime":72399.0,"Position":208.0,"HyperDash":false}]},{"StartTime":72485.0,"Objects":[{"StartTime":72485.0,"Position":200.0,"HyperDash":false}]},{"StartTime":72571.0,"Objects":[{"StartTime":72571.0,"Position":192.0,"HyperDash":false}]},{"StartTime":72742.0,"Objects":[{"StartTime":72742.0,"Position":124.0,"HyperDash":false}]},{"StartTime":72913.0,"Objects":[{"StartTime":72913.0,"Position":48.0,"HyperDash":false},{"StartTime":72980.0,"Position":68.42183,"HyperDash":false},{"StartTime":73084.0,"Position":84.285,"HyperDash":false}]},{"StartTime":73256.0,"Objects":[{"StartTime":73256.0,"Position":44.0,"HyperDash":false},{"StartTime":73323.0,"Position":27.0,"HyperDash":false},{"StartTime":73427.0,"Position":44.0,"HyperDash":false}]},{"StartTime":73599.0,"Objects":[{"StartTime":73599.0,"Position":116.0,"HyperDash":false},{"StartTime":73666.0,"Position":134.0,"HyperDash":false},{"StartTime":73770.0,"Position":116.0,"HyperDash":false}]},{"StartTime":73942.0,"Objects":[{"StartTime":73942.0,"Position":188.0,"HyperDash":false},{"StartTime":74027.0,"Position":194.0,"HyperDash":false},{"StartTime":74113.0,"Position":188.0,"HyperDash":false},{"StartTime":74180.0,"Position":177.0,"HyperDash":false},{"StartTime":74284.0,"Position":188.0,"HyperDash":false}]},{"StartTime":74456.0,"Objects":[{"StartTime":74456.0,"Position":188.0,"HyperDash":false}]},{"StartTime":74628.0,"Objects":[{"StartTime":74628.0,"Position":260.0,"HyperDash":false},{"StartTime":74695.0,"Position":292.008942,"HyperDash":false},{"StartTime":74799.0,"Position":311.0676,"HyperDash":false}]},{"StartTime":74971.0,"Objects":[{"StartTime":74971.0,"Position":361.0,"HyperDash":false},{"StartTime":75038.0,"Position":333.214569,"HyperDash":false},{"StartTime":75142.0,"Position":310.502869,"HyperDash":false}]},{"StartTime":75313.0,"Objects":[{"StartTime":75313.0,"Position":260.0,"HyperDash":false},{"StartTime":75380.0,"Position":290.008942,"HyperDash":false},{"StartTime":75484.0,"Position":311.0676,"HyperDash":false}]},{"StartTime":75656.0,"Objects":[{"StartTime":75656.0,"Position":360.0,"HyperDash":false},{"StartTime":75723.0,"Position":337.803131,"HyperDash":false},{"StartTime":75827.0,"Position":311.005,"HyperDash":false}]},{"StartTime":75999.0,"Objects":[{"StartTime":75999.0,"Position":49.0,"HyperDash":false},{"StartTime":76063.0,"Position":21.0,"HyperDash":false},{"StartTime":76127.0,"Position":193.0,"HyperDash":false},{"StartTime":76191.0,"Position":52.0,"HyperDash":false},{"StartTime":76256.0,"Position":466.0,"HyperDash":false},{"StartTime":76320.0,"Position":135.0,"HyperDash":false},{"StartTime":76384.0,"Position":121.0,"HyperDash":false},{"StartTime":76449.0,"Position":427.0,"HyperDash":false},{"StartTime":76513.0,"Position":176.0,"HyperDash":false},{"StartTime":76577.0,"Position":96.0,"HyperDash":false},{"StartTime":76642.0,"Position":345.0,"HyperDash":false},{"StartTime":76706.0,"Position":11.0,"HyperDash":false},{"StartTime":76770.0,"Position":393.0,"HyperDash":false},{"StartTime":76835.0,"Position":440.0,"HyperDash":false},{"StartTime":76899.0,"Position":179.0,"HyperDash":false},{"StartTime":76963.0,"Position":470.0,"HyperDash":false},{"StartTime":77028.0,"Position":89.0,"HyperDash":false}]},{"StartTime":77371.0,"Objects":[{"StartTime":77371.0,"Position":48.0,"HyperDash":false},{"StartTime":77456.0,"Position":59.0,"HyperDash":false},{"StartTime":77542.0,"Position":48.0,"HyperDash":false},{"StartTime":77609.0,"Position":67.0,"HyperDash":false},{"StartTime":77713.0,"Position":48.0,"HyperDash":false}]},{"StartTime":78056.0,"Objects":[{"StartTime":78056.0,"Position":152.0,"HyperDash":false},{"StartTime":78141.0,"Position":162.0,"HyperDash":false},{"StartTime":78227.0,"Position":152.0,"HyperDash":false},{"StartTime":78294.0,"Position":135.0,"HyperDash":false},{"StartTime":78398.0,"Position":152.0,"HyperDash":false}]},{"StartTime":78742.0,"Objects":[{"StartTime":78742.0,"Position":152.0,"HyperDash":false},{"StartTime":78827.0,"Position":154.0,"HyperDash":false},{"StartTime":78913.0,"Position":152.0,"HyperDash":false},{"StartTime":78980.0,"Position":138.0,"HyperDash":false},{"StartTime":79084.0,"Position":152.0,"HyperDash":false}]},{"StartTime":79427.0,"Objects":[{"StartTime":79427.0,"Position":256.0,"HyperDash":false},{"StartTime":79512.0,"Position":248.0,"HyperDash":false},{"StartTime":79598.0,"Position":256.0,"HyperDash":false},{"StartTime":79665.0,"Position":270.0,"HyperDash":false},{"StartTime":79769.0,"Position":256.0,"HyperDash":false}]},{"StartTime":80113.0,"Objects":[{"StartTime":80113.0,"Position":256.0,"HyperDash":false},{"StartTime":80198.0,"Position":249.0,"HyperDash":false},{"StartTime":80284.0,"Position":256.0,"HyperDash":false},{"StartTime":80369.0,"Position":245.0,"HyperDash":false},{"StartTime":80455.0,"Position":256.0,"HyperDash":false},{"StartTime":80541.0,"Position":244.0,"HyperDash":false},{"StartTime":80627.0,"Position":256.0,"HyperDash":false},{"StartTime":80694.0,"Position":265.0,"HyperDash":false},{"StartTime":80798.0,"Position":256.0,"HyperDash":false}]},{"StartTime":81142.0,"Objects":[{"StartTime":81142.0,"Position":256.0,"HyperDash":false},{"StartTime":81227.0,"Position":292.744537,"HyperDash":false},{"StartTime":81313.0,"Position":325.897827,"HyperDash":false},{"StartTime":81398.0,"Position":358.642334,"HyperDash":false},{"StartTime":81484.0,"Position":396.0,"HyperDash":false},{"StartTime":81570.0,"Position":346.0511,"HyperDash":false},{"StartTime":81656.0,"Position":325.897827,"HyperDash":false},{"StartTime":81723.0,"Position":285.510956,"HyperDash":false},{"StartTime":81827.0,"Position":256.0,"HyperDash":false}]},{"StartTime":82171.0,"Objects":[{"StartTime":82171.0,"Position":468.0,"HyperDash":false}]},{"StartTime":82513.0,"Objects":[{"StartTime":82513.0,"Position":468.0,"HyperDash":false}]},{"StartTime":82856.0,"Objects":[{"StartTime":82856.0,"Position":352.0,"HyperDash":false},{"StartTime":82941.0,"Position":368.54422,"HyperDash":false},{"StartTime":83027.0,"Position":407.5205,"HyperDash":false},{"StartTime":83112.0,"Position":374.08432,"HyperDash":false},{"StartTime":83198.0,"Position":352.0,"HyperDash":false},{"StartTime":83266.0,"Position":371.819336,"HyperDash":false},{"StartTime":83370.0,"Position":407.5205,"HyperDash":false}]},{"StartTime":83542.0,"Objects":[{"StartTime":83542.0,"Position":448.0,"HyperDash":false}]},{"StartTime":83885.0,"Objects":[{"StartTime":83885.0,"Position":324.0,"HyperDash":false}]},{"StartTime":84228.0,"Objects":[{"StartTime":84228.0,"Position":160.0,"HyperDash":false},{"StartTime":84313.0,"Position":124.276367,"HyperDash":false},{"StartTime":84399.0,"Position":104.117874,"HyperDash":false},{"StartTime":84484.0,"Position":150.732773,"HyperDash":false},{"StartTime":84570.0,"Position":160.0,"HyperDash":false},{"StartTime":84638.0,"Position":132.038544,"HyperDash":false},{"StartTime":84742.0,"Position":104.117874,"HyperDash":false}]},{"StartTime":84913.0,"Objects":[{"StartTime":84913.0,"Position":64.0,"HyperDash":false}]},{"StartTime":85256.0,"Objects":[{"StartTime":85256.0,"Position":188.0,"HyperDash":false}]},{"StartTime":85599.0,"Objects":[{"StartTime":85599.0,"Position":352.0,"HyperDash":false},{"StartTime":85684.0,"Position":376.7821,"HyperDash":false},{"StartTime":85770.0,"Position":408.0,"HyperDash":false},{"StartTime":85855.0,"Position":395.326843,"HyperDash":false},{"StartTime":85941.0,"Position":352.0,"HyperDash":false},{"StartTime":86009.0,"Position":380.007782,"HyperDash":false},{"StartTime":86113.0,"Position":408.0,"HyperDash":false}]},{"StartTime":86285.0,"Objects":[{"StartTime":86285.0,"Position":356.0,"HyperDash":false}]},{"StartTime":86456.0,"Objects":[{"StartTime":86456.0,"Position":356.0,"HyperDash":false}]},{"StartTime":86628.0,"Objects":[{"StartTime":86628.0,"Position":356.0,"HyperDash":false}]},{"StartTime":86971.0,"Objects":[{"StartTime":86971.0,"Position":160.0,"HyperDash":false},{"StartTime":87056.0,"Position":162.926041,"HyperDash":false},{"StartTime":87142.0,"Position":133.659821,"HyperDash":false},{"StartTime":87227.0,"Position":161.695328,"HyperDash":false},{"StartTime":87313.0,"Position":160.0,"HyperDash":false},{"StartTime":87399.0,"Position":140.849136,"HyperDash":false},{"StartTime":87485.0,"Position":133.659821,"HyperDash":false},{"StartTime":87552.0,"Position":142.003632,"HyperDash":false},{"StartTime":87656.0,"Position":160.0,"HyperDash":false}]},{"StartTime":87999.0,"Objects":[{"StartTime":87999.0,"Position":256.0,"HyperDash":false}]},{"StartTime":88342.0,"Objects":[{"StartTime":88342.0,"Position":104.0,"HyperDash":false},{"StartTime":88427.0,"Position":100.104553,"HyperDash":false},{"StartTime":88513.0,"Position":76.88288,"HyperDash":false},{"StartTime":88598.0,"Position":61.4504166,"HyperDash":false},{"StartTime":88684.0,"Position":80.21796,"HyperDash":false},{"StartTime":88770.0,"Position":95.16115,"HyperDash":false},{"StartTime":88856.0,"Position":115.62986,"HyperDash":false},{"StartTime":88941.0,"Position":138.0912,"HyperDash":false},{"StartTime":89027.0,"Position":175.517044,"HyperDash":false},{"StartTime":89113.0,"Position":220.3342,"HyperDash":false},{"StartTime":89199.0,"Position":244.674866,"HyperDash":false},{"StartTime":89284.0,"Position":264.2282,"HyperDash":false},{"StartTime":89370.0,"Position":300.583649,"HyperDash":false},{"StartTime":89456.0,"Position":336.589539,"HyperDash":false},{"StartTime":89542.0,"Position":332.588257,"HyperDash":false},{"StartTime":89609.0,"Position":357.060455,"HyperDash":false},{"StartTime":89713.0,"Position":343.816345,"HyperDash":false}]},{"StartTime":89885.0,"Objects":[{"StartTime":89885.0,"Position":408.0,"HyperDash":false}]},{"StartTime":90056.0,"Objects":[{"StartTime":90056.0,"Position":416.0,"HyperDash":false}]},{"StartTime":90228.0,"Objects":[{"StartTime":90228.0,"Position":400.0,"HyperDash":false}]},{"StartTime":90399.0,"Objects":[{"StartTime":90399.0,"Position":360.0,"HyperDash":false},{"StartTime":90484.0,"Position":325.326477,"HyperDash":false},{"StartTime":90570.0,"Position":314.08017,"HyperDash":false},{"StartTime":90655.0,"Position":295.765259,"HyperDash":false},{"StartTime":90741.0,"Position":250.349167,"HyperDash":false},{"StartTime":90827.0,"Position":234.540588,"HyperDash":false},{"StartTime":90913.0,"Position":180.487732,"HyperDash":false},{"StartTime":90998.0,"Position":158.6242,"HyperDash":false},{"StartTime":91084.0,"Position":114.161362,"HyperDash":false},{"StartTime":91170.0,"Position":64.53248,"HyperDash":false},{"StartTime":91256.0,"Position":58.7642,"HyperDash":false},{"StartTime":91323.0,"Position":33.0224953,"HyperDash":false},{"StartTime":91427.0,"Position":23.1158314,"HyperDash":false}]},{"StartTime":91599.0,"Objects":[{"StartTime":91599.0,"Position":60.0,"HyperDash":false}]},{"StartTime":91771.0,"Objects":[{"StartTime":91771.0,"Position":24.0,"HyperDash":false},{"StartTime":91856.0,"Position":42.1049347,"HyperDash":false},{"StartTime":91942.0,"Position":82.55228,"HyperDash":false},{"StartTime":92009.0,"Position":124.493813,"HyperDash":false},{"StartTime":92113.0,"Position":141.104553,"HyperDash":false}]},{"StartTime":92285.0,"Objects":[{"StartTime":92285.0,"Position":339.0,"HyperDash":false},{"StartTime":92381.0,"Position":342.0,"HyperDash":false},{"StartTime":92477.0,"Position":249.0,"HyperDash":false},{"StartTime":92574.0,"Position":235.0,"HyperDash":false},{"StartTime":92670.0,"Position":323.0,"HyperDash":false},{"StartTime":92767.0,"Position":365.0,"HyperDash":false},{"StartTime":92863.0,"Position":74.0,"HyperDash":false},{"StartTime":92960.0,"Position":281.0,"HyperDash":false},{"StartTime":93056.0,"Position":398.0,"HyperDash":false},{"StartTime":93152.0,"Position":335.0,"HyperDash":false},{"StartTime":93249.0,"Position":388.0,"HyperDash":false},{"StartTime":93345.0,"Position":228.0,"HyperDash":false},{"StartTime":93442.0,"Position":323.0,"HyperDash":false},{"StartTime":93538.0,"Position":441.0,"HyperDash":false},{"StartTime":93635.0,"Position":442.0,"HyperDash":false},{"StartTime":93731.0,"Position":278.0,"HyperDash":false},{"StartTime":93828.0,"Position":90.0,"HyperDash":false}]},{"StartTime":94513.0,"Objects":[{"StartTime":94513.0,"Position":64.0,"HyperDash":false},{"StartTime":94598.0,"Position":68.14916,"HyperDash":false},{"StartTime":94684.0,"Position":62.2626343,"HyperDash":false},{"StartTime":94769.0,"Position":86.91272,"HyperDash":false},{"StartTime":94855.0,"Position":102.010681,"HyperDash":false},{"StartTime":94941.0,"Position":141.25354,"HyperDash":false},{"StartTime":95027.0,"Position":166.435471,"HyperDash":false},{"StartTime":95094.0,"Position":206.542572,"HyperDash":false},{"StartTime":95198.0,"Position":230.41568,"HyperDash":false}]},{"StartTime":95371.0,"Objects":[{"StartTime":95371.0,"Position":300.0,"HyperDash":false}]},{"StartTime":95542.0,"Objects":[{"StartTime":95542.0,"Position":340.0,"HyperDash":false}]},{"StartTime":95713.0,"Objects":[{"StartTime":95713.0,"Position":404.0,"HyperDash":false}]},{"StartTime":95885.0,"Objects":[{"StartTime":95885.0,"Position":448.0,"HyperDash":false},{"StartTime":95970.0,"Position":440.850861,"HyperDash":false},{"StartTime":96056.0,"Position":449.737366,"HyperDash":false},{"StartTime":96141.0,"Position":429.08728,"HyperDash":false},{"StartTime":96227.0,"Position":409.989319,"HyperDash":false},{"StartTime":96313.0,"Position":361.74646,"HyperDash":false},{"StartTime":96399.0,"Position":345.564545,"HyperDash":false},{"StartTime":96466.0,"Position":303.457428,"HyperDash":false},{"StartTime":96570.0,"Position":281.58432,"HyperDash":false}]},{"StartTime":96913.0,"Objects":[{"StartTime":96913.0,"Position":256.0,"HyperDash":false}]},{"StartTime":97256.0,"Objects":[{"StartTime":97256.0,"Position":464.0,"HyperDash":false},{"StartTime":97341.0,"Position":440.493042,"HyperDash":false},{"StartTime":97427.0,"Position":396.852631,"HyperDash":false},{"StartTime":97494.0,"Position":353.930542,"HyperDash":false},{"StartTime":97598.0,"Position":329.726563,"HyperDash":false}]},{"StartTime":97771.0,"Objects":[{"StartTime":97771.0,"Position":252.0,"HyperDash":false}]},{"StartTime":97942.0,"Objects":[{"StartTime":97942.0,"Position":176.0,"HyperDash":false},{"StartTime":98027.0,"Position":129.262726,"HyperDash":false},{"StartTime":98113.0,"Position":106.0,"HyperDash":false},{"StartTime":98198.0,"Position":154.620514,"HyperDash":false},{"StartTime":98284.0,"Position":176.0,"HyperDash":false},{"StartTime":98370.0,"Position":142.08757,"HyperDash":false},{"StartTime":98456.0,"Position":106.0,"HyperDash":false},{"StartTime":98541.0,"Position":132.795654,"HyperDash":false},{"StartTime":98627.0,"Position":176.0,"HyperDash":false},{"StartTime":98713.0,"Position":145.912415,"HyperDash":false},{"StartTime":98799.0,"Position":106.0,"HyperDash":false},{"StartTime":98884.0,"Position":134.9708,"HyperDash":false},{"StartTime":98970.0,"Position":176.0,"HyperDash":false},{"StartTime":99038.0,"Position":137.093414,"HyperDash":false},{"StartTime":99141.0,"Position":106.0,"HyperDash":false}]},{"StartTime":99313.0,"Objects":[{"StartTime":99313.0,"Position":28.0,"HyperDash":false},{"StartTime":99398.0,"Position":26.2349854,"HyperDash":false},{"StartTime":99484.0,"Position":17.7651138,"HyperDash":false},{"StartTime":99569.0,"Position":33.3757133,"HyperDash":false},{"StartTime":99655.0,"Position":31.6727753,"HyperDash":false},{"StartTime":99741.0,"Position":33.7641869,"HyperDash":false},{"StartTime":99827.0,"Position":72.2299042,"HyperDash":false},{"StartTime":99912.0,"Position":92.74443,"HyperDash":false},{"StartTime":99998.0,"Position":133.558716,"HyperDash":false},{"StartTime":100084.0,"Position":158.430649,"HyperDash":false},{"StartTime":100170.0,"Position":202.717,"HyperDash":false},{"StartTime":100237.0,"Position":217.776627,"HyperDash":false},{"StartTime":100341.0,"Position":255.047836,"HyperDash":false}]},{"StartTime":100685.0,"Objects":[{"StartTime":100685.0,"Position":484.0,"HyperDash":false},{"StartTime":100770.0,"Position":489.764954,"HyperDash":false},{"StartTime":100856.0,"Position":494.2329,"HyperDash":false},{"StartTime":100941.0,"Position":504.6108,"HyperDash":false},{"StartTime":101027.0,"Position":480.2734,"HyperDash":false},{"StartTime":101113.0,"Position":448.084351,"HyperDash":false},{"StartTime":101199.0,"Position":439.444244,"HyperDash":false},{"StartTime":101284.0,"Position":426.701172,"HyperDash":false},{"StartTime":101370.0,"Position":377.68396,"HyperDash":false},{"StartTime":101456.0,"Position":326.754578,"HyperDash":false},{"StartTime":101542.0,"Position":308.535339,"HyperDash":false},{"StartTime":101609.0,"Position":299.317078,"HyperDash":false},{"StartTime":101713.0,"Position":254.267319,"HyperDash":false}]},{"StartTime":102056.0,"Objects":[{"StartTime":102056.0,"Position":160.0,"HyperDash":false},{"StartTime":102141.0,"Position":171.288788,"HyperDash":false},{"StartTime":102227.0,"Position":190.8506,"HyperDash":false},{"StartTime":102312.0,"Position":206.4524,"HyperDash":false},{"StartTime":102398.0,"Position":256.016479,"HyperDash":false},{"StartTime":102484.0,"Position":285.578949,"HyperDash":false},{"StartTime":102570.0,"Position":321.497925,"HyperDash":false},{"StartTime":102655.0,"Position":361.7266,"HyperDash":false},{"StartTime":102741.0,"Position":352.033936,"HyperDash":false},{"StartTime":102827.0,"Position":339.916229,"HyperDash":false},{"StartTime":102913.0,"Position":321.497925,"HyperDash":false},{"StartTime":102998.0,"Position":307.967651,"HyperDash":false},{"StartTime":103084.0,"Position":256.424927,"HyperDash":false},{"StartTime":103170.0,"Position":225.84108,"HyperDash":false},{"StartTime":103256.0,"Position":190.8506,"HyperDash":false},{"StartTime":103323.0,"Position":189.241409,"HyperDash":false},{"StartTime":103427.0,"Position":160.0,"HyperDash":false}]},{"StartTime":103771.0,"Objects":[{"StartTime":103771.0,"Position":48.0,"HyperDash":false}]},{"StartTime":104113.0,"Objects":[{"StartTime":104113.0,"Position":256.0,"HyperDash":false}]},{"StartTime":104456.0,"Objects":[{"StartTime":104456.0,"Position":464.0,"HyperDash":false}]},{"StartTime":104799.0,"Objects":[{"StartTime":104799.0,"Position":352.0,"HyperDash":false}]},{"StartTime":104971.0,"Objects":[{"StartTime":104971.0,"Position":272.0,"HyperDash":false},{"StartTime":105056.0,"Position":237.0,"HyperDash":false},{"StartTime":105142.0,"Position":272.0,"HyperDash":false}]},{"StartTime":105313.0,"Objects":[{"StartTime":105313.0,"Position":240.0,"HyperDash":false},{"StartTime":105398.0,"Position":275.0,"HyperDash":false},{"StartTime":105484.0,"Position":240.0,"HyperDash":false}]},{"StartTime":105656.0,"Objects":[{"StartTime":105656.0,"Position":272.0,"HyperDash":false},{"StartTime":105741.0,"Position":237.0,"HyperDash":false},{"StartTime":105827.0,"Position":272.0,"HyperDash":false}]},{"StartTime":105999.0,"Objects":[{"StartTime":105999.0,"Position":240.0,"HyperDash":false},{"StartTime":106084.0,"Position":275.0,"HyperDash":false},{"StartTime":106170.0,"Position":240.0,"HyperDash":false}]},{"StartTime":106342.0,"Objects":[{"StartTime":106342.0,"Position":168.0,"HyperDash":false},{"StartTime":106409.0,"Position":144.031464,"HyperDash":false},{"StartTime":106513.0,"Position":104.274345,"HyperDash":false}]},{"StartTime":106685.0,"Objects":[{"StartTime":106685.0,"Position":56.0,"HyperDash":false},{"StartTime":106752.0,"Position":96.4269,"HyperDash":false},{"StartTime":106856.0,"Position":126.0,"HyperDash":false}]},{"StartTime":107028.0,"Objects":[{"StartTime":107028.0,"Position":104.0,"HyperDash":false},{"StartTime":107095.0,"Position":137.981888,"HyperDash":false},{"StartTime":107199.0,"Position":167.759735,"HyperDash":false}]},{"StartTime":107371.0,"Objects":[{"StartTime":107371.0,"Position":256.0,"HyperDash":false}]},{"StartTime":107542.0,"Objects":[{"StartTime":107542.0,"Position":344.0,"HyperDash":false},{"StartTime":107609.0,"Position":367.968536,"HyperDash":false},{"StartTime":107713.0,"Position":407.725647,"HyperDash":false}]},{"StartTime":107885.0,"Objects":[{"StartTime":107885.0,"Position":456.0,"HyperDash":false},{"StartTime":107952.0,"Position":441.5731,"HyperDash":false},{"StartTime":108056.0,"Position":386.0,"HyperDash":false}]},{"StartTime":108228.0,"Objects":[{"StartTime":108228.0,"Position":408.0,"HyperDash":false},{"StartTime":108295.0,"Position":368.018127,"HyperDash":false},{"StartTime":108399.0,"Position":344.240265,"HyperDash":false}]},{"StartTime":108571.0,"Objects":[{"StartTime":108571.0,"Position":256.0,"HyperDash":false},{"StartTime":108628.0,"Position":256.0,"HyperDash":false},{"StartTime":108685.0,"Position":256.0,"HyperDash":false},{"StartTime":108742.0,"Position":256.0,"HyperDash":false},{"StartTime":108799.0,"Position":256.0,"HyperDash":false},{"StartTime":108856.0,"Position":256.0,"HyperDash":false},{"StartTime":108913.0,"Position":256.0,"HyperDash":false}]},{"StartTime":108999.0,"Objects":[{"StartTime":108999.0,"Position":152.0,"HyperDash":false},{"StartTime":109057.0,"Position":321.0,"HyperDash":false},{"StartTime":109116.0,"Position":303.0,"HyperDash":false},{"StartTime":109175.0,"Position":259.0,"HyperDash":false},{"StartTime":109234.0,"Position":186.0,"HyperDash":false},{"StartTime":109293.0,"Position":140.0,"HyperDash":false},{"StartTime":109352.0,"Position":207.0,"HyperDash":false},{"StartTime":109411.0,"Position":278.0,"HyperDash":false},{"StartTime":109470.0,"Position":223.0,"HyperDash":false},{"StartTime":109529.0,"Position":389.0,"HyperDash":false},{"StartTime":109588.0,"Position":245.0,"HyperDash":false},{"StartTime":109647.0,"Position":400.0,"HyperDash":false},{"StartTime":109706.0,"Position":445.0,"HyperDash":false},{"StartTime":109765.0,"Position":443.0,"HyperDash":false},{"StartTime":109824.0,"Position":245.0,"HyperDash":false},{"StartTime":109883.0,"Position":259.0,"HyperDash":false},{"StartTime":109942.0,"Position":422.0,"HyperDash":false}]},{"StartTime":110285.0,"Objects":[{"StartTime":110285.0,"Position":168.0,"HyperDash":false},{"StartTime":110352.0,"Position":172.393753,"HyperDash":false},{"StartTime":110456.0,"Position":217.497467,"HyperDash":false}]},{"StartTime":110628.0,"Objects":[{"StartTime":110628.0,"Position":344.0,"HyperDash":false},{"StartTime":110695.0,"Position":329.606262,"HyperDash":false},{"StartTime":110799.0,"Position":294.502533,"HyperDash":false}]},{"StartTime":110971.0,"Objects":[{"StartTime":110971.0,"Position":207.0,"HyperDash":false},{"StartTime":111038.0,"Position":237.393753,"HyperDash":false},{"StartTime":111142.0,"Position":256.497467,"HyperDash":false}]},{"StartTime":111313.0,"Objects":[{"StartTime":111313.0,"Position":305.0,"HyperDash":false},{"StartTime":111380.0,"Position":285.606262,"HyperDash":false},{"StartTime":111484.0,"Position":255.502533,"HyperDash":false}]},{"StartTime":111656.0,"Objects":[{"StartTime":111656.0,"Position":216.0,"HyperDash":false},{"StartTime":111741.0,"Position":222.948441,"HyperDash":false},{"StartTime":111827.0,"Position":256.131561,"HyperDash":false},{"StartTime":111912.0,"Position":267.080017,"HyperDash":false},{"StartTime":111998.0,"Position":296.419617,"HyperDash":false},{"StartTime":112084.0,"Position":260.392944,"HyperDash":false},{"StartTime":112170.0,"Position":256.2098,"HyperDash":false},{"StartTime":112255.0,"Position":223.261353,"HyperDash":false},{"StartTime":112341.0,"Position":216.0,"HyperDash":false},{"StartTime":112427.0,"Position":243.1049,"HyperDash":false},{"StartTime":112513.0,"Position":256.288025,"HyperDash":false},{"StartTime":112580.0,"Position":290.012115,"HyperDash":false},{"StartTime":112684.0,"Position":296.419617,"HyperDash":false}]},{"StartTime":113028.0,"Objects":[{"StartTime":113028.0,"Position":352.0,"HyperDash":false}]},{"StartTime":113199.0,"Objects":[{"StartTime":113199.0,"Position":360.0,"HyperDash":false},{"StartTime":113284.0,"Position":364.341217,"HyperDash":false},{"StartTime":113370.0,"Position":360.0,"HyperDash":false}]},{"StartTime":113542.0,"Objects":[{"StartTime":113542.0,"Position":424.0,"HyperDash":false}]},{"StartTime":113713.0,"Objects":[{"StartTime":113713.0,"Position":352.0,"HyperDash":false}]},{"StartTime":113885.0,"Objects":[{"StartTime":113885.0,"Position":408.0,"HyperDash":false},{"StartTime":113970.0,"Position":441.203918,"HyperDash":false},{"StartTime":114056.0,"Position":408.0,"HyperDash":false}]},{"StartTime":114228.0,"Objects":[{"StartTime":114228.0,"Position":336.0,"HyperDash":false}]},{"StartTime":114399.0,"Objects":[{"StartTime":114399.0,"Position":352.0,"HyperDash":false}]},{"StartTime":114571.0,"Objects":[{"StartTime":114571.0,"Position":280.0,"HyperDash":false},{"StartTime":114656.0,"Position":248.695053,"HyperDash":false},{"StartTime":114742.0,"Position":280.0,"HyperDash":false}]},{"StartTime":114913.0,"Objects":[{"StartTime":114913.0,"Position":352.0,"HyperDash":false}]},{"StartTime":115085.0,"Objects":[{"StartTime":115085.0,"Position":296.0,"HyperDash":false}]},{"StartTime":115256.0,"Objects":[{"StartTime":115256.0,"Position":368.0,"HyperDash":false}]},{"StartTime":115428.0,"Objects":[{"StartTime":115428.0,"Position":424.0,"HyperDash":false}]},{"StartTime":115771.0,"Objects":[{"StartTime":115771.0,"Position":128.0,"HyperDash":false},{"StartTime":115838.0,"Position":111.239639,"HyperDash":false},{"StartTime":115942.0,"Position":60.5060654,"HyperDash":false}]},{"StartTime":116113.0,"Objects":[{"StartTime":116113.0,"Position":64.0,"HyperDash":false},{"StartTime":116180.0,"Position":98.76035,"HyperDash":false},{"StartTime":116284.0,"Position":131.493927,"HyperDash":false}]},{"StartTime":116456.0,"Objects":[{"StartTime":116456.0,"Position":136.0,"HyperDash":false},{"StartTime":116523.0,"Position":92.23965,"HyperDash":false},{"StartTime":116627.0,"Position":68.5060654,"HyperDash":false}]},{"StartTime":116798.0,"Objects":[{"StartTime":116798.0,"Position":72.0,"HyperDash":false},{"StartTime":116865.0,"Position":112.760361,"HyperDash":false},{"StartTime":116969.0,"Position":139.493927,"HyperDash":false}]},{"StartTime":117142.0,"Objects":[{"StartTime":117142.0,"Position":216.0,"HyperDash":false}]},{"StartTime":117313.0,"Objects":[{"StartTime":117313.0,"Position":216.0,"HyperDash":false}]},{"StartTime":117485.0,"Objects":[{"StartTime":117485.0,"Position":216.0,"HyperDash":false}]},{"StartTime":117828.0,"Objects":[{"StartTime":117828.0,"Position":296.0,"HyperDash":false}]},{"StartTime":117999.0,"Objects":[{"StartTime":117999.0,"Position":296.0,"HyperDash":false}]},{"StartTime":118171.0,"Objects":[{"StartTime":118171.0,"Position":296.0,"HyperDash":false}]},{"StartTime":118513.0,"Objects":[{"StartTime":118513.0,"Position":448.0,"HyperDash":false},{"StartTime":118580.0,"Position":418.681824,"HyperDash":false},{"StartTime":118684.0,"Position":391.038666,"HyperDash":false}]},{"StartTime":118856.0,"Objects":[{"StartTime":118856.0,"Position":392.0,"HyperDash":false}]},{"StartTime":118942.0,"Objects":[{"StartTime":118942.0,"Position":392.0,"HyperDash":false}]},{"StartTime":119028.0,"Objects":[{"StartTime":119028.0,"Position":392.0,"HyperDash":false}]},{"StartTime":119199.0,"Objects":[{"StartTime":119199.0,"Position":392.0,"HyperDash":false},{"StartTime":119284.0,"Position":392.0,"HyperDash":false},{"StartTime":119370.0,"Position":392.0,"HyperDash":false}]},{"StartTime":119542.0,"Objects":[{"StartTime":119542.0,"Position":464.0,"HyperDash":false}]},{"StartTime":119628.0,"Objects":[{"StartTime":119628.0,"Position":464.0,"HyperDash":false}]},{"StartTime":119713.0,"Objects":[{"StartTime":119713.0,"Position":464.0,"HyperDash":false}]},{"StartTime":119885.0,"Objects":[{"StartTime":119885.0,"Position":464.0,"HyperDash":false},{"StartTime":119970.0,"Position":443.501282,"HyperDash":false},{"StartTime":120056.0,"Position":394.59668,"HyperDash":false},{"StartTime":120141.0,"Position":351.097931,"HyperDash":false},{"StartTime":120227.0,"Position":325.193329,"HyperDash":false},{"StartTime":120313.0,"Position":287.288727,"HyperDash":false},{"StartTime":120399.0,"Position":255.384125,"HyperDash":false},{"StartTime":120484.0,"Position":213.8854,"HyperDash":false},{"StartTime":120570.0,"Position":185.980774,"HyperDash":false},{"StartTime":120656.0,"Position":160.0762,"HyperDash":false},{"StartTime":120742.0,"Position":116.1716,"HyperDash":false},{"StartTime":120809.0,"Position":79.9784546,"HyperDash":false},{"StartTime":120913.0,"Position":46.7682343,"HyperDash":false}]},{"StartTime":121256.0,"Objects":[{"StartTime":121256.0,"Position":441.0,"HyperDash":false},{"StartTime":121341.0,"Position":45.0,"HyperDash":false},{"StartTime":121427.0,"Position":92.0,"HyperDash":false},{"StartTime":121513.0,"Position":399.0,"HyperDash":false},{"StartTime":121598.0,"Position":494.0,"HyperDash":false},{"StartTime":121684.0,"Position":324.0,"HyperDash":false},{"StartTime":121770.0,"Position":31.0,"HyperDash":false},{"StartTime":121856.0,"Position":79.0,"HyperDash":false},{"StartTime":121941.0,"Position":163.0,"HyperDash":false},{"StartTime":122027.0,"Position":303.0,"HyperDash":false},{"StartTime":122113.0,"Position":462.0,"HyperDash":false},{"StartTime":122198.0,"Position":74.0,"HyperDash":false},{"StartTime":122284.0,"Position":4.0,"HyperDash":false},{"StartTime":122370.0,"Position":253.0,"HyperDash":false},{"StartTime":122456.0,"Position":159.0,"HyperDash":false},{"StartTime":122541.0,"Position":74.0,"HyperDash":false},{"StartTime":122627.0,"Position":242.0,"HyperDash":false},{"StartTime":122713.0,"Position":251.0,"HyperDash":false},{"StartTime":122798.0,"Position":146.0,"HyperDash":false},{"StartTime":122884.0,"Position":487.0,"HyperDash":false},{"StartTime":122970.0,"Position":294.0,"HyperDash":false},{"StartTime":123056.0,"Position":322.0,"HyperDash":false},{"StartTime":123141.0,"Position":208.0,"HyperDash":false},{"StartTime":123227.0,"Position":154.0,"HyperDash":false},{"StartTime":123313.0,"Position":476.0,"HyperDash":false},{"StartTime":123398.0,"Position":27.0,"HyperDash":false},{"StartTime":123484.0,"Position":377.0,"HyperDash":false},{"StartTime":123570.0,"Position":234.0,"HyperDash":false},{"StartTime":123656.0,"Position":459.0,"HyperDash":false},{"StartTime":123741.0,"Position":106.0,"HyperDash":false},{"StartTime":123827.0,"Position":321.0,"HyperDash":false},{"StartTime":123913.0,"Position":368.0,"HyperDash":false},{"StartTime":123999.0,"Position":488.0,"HyperDash":false}]}]} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/103019.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/103019.osu new file mode 100644 index 0000000000..2f3814c57b --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/103019.osu @@ -0,0 +1,329 @@ +osu file format v9 + +[General] +StackLeniency: 0.3 +Mode: 0 + +[Difficulty] +HPDrainRate:5 +CircleSize:4 +OverallDifficulty:5 +ApproachRate:7 +SliderMultiplier:1.4 +SliderTickRate:2 + +[Events] +//Background and Video events +//Break Periods +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Sound Samples +//Background Colour Transformations +3,100,163,162,255 + +[TimingPoints] +571.114342485549,342.857142857143,4,1,0,60,1,0 +11542,-100,4,1,0,90,0,0 +59542,-100,4,2,0,90,0,0 +60913,-100,4,1,0,90,0,0 +65028,-100,4,1,0,90,0,0 +66399,-100,4,1,0,90,0,1 +77371,-100,4,1,0,90,0,0 +86971,-200,4,1,0,90,0,0 +87999,-50,4,1,0,90,0,0 +88342,-100,4,1,0,90,0,0 +110285,-100,4,1,0,90,0,1 +119885,-100,4,1,0,100,0,0 +121574,-50,4,2,0,90,0,0 +121917,-50,4,2,0,80,0,0 +122260,-50,4,2,0,70,0,0 +122603,-50,4,2,0,60,0,0 +122946,-50,4,2,0,50,0,0 +123288,-50,4,2,0,40,0,0 +123631,-50,4,2,0,30,0,0 +123974,-50,4,2,0,5,0,0 + +[HitObjects] +184,192,571,6,0,B|168:104|256:45|344:104|328:192,1,280 +256,312,1599,2,0,B|256:160,3,140 +256,32,2971,5,0 +128,88,3313,1,0 +128,232,3656,2,0,B|128:328,2,70 +384,232,4342,2,0,B|384:328,2,70 +384,160,4856,1,0 +384,88,5028,1,0 +256,32,5371,37,2 +256,352,5713,1,2 +128,296,6056,22,0,B|72:272|72:272|72:192,1,140 +384,296,6742,2,0,B|440:272|440:272|440:192,1,140 +256,72,7428,2,0,B|224:136|296:136|256:208,2,140 +256,296,8456,1,2 +256,192,8799,12,0,11199 +256,192,11542,5,4 +60,192,11885,2,0,B|28:240|60:304|132:280,2,140,8|0|8 +256,192,12913,5,0 +452,192,13256,2,0,B|484:144|452:80|380:104,2,140,8|0|8 +256,192,14285,1,0 +88,64,14799,6,0,B|56:40,10,35 +32,192,15999,1,8 +96,220,16171,1,0 +160,248,16342,1,0 +224,120,16685,1,8 +328,24,17028,6,0,B|352:100,1,70 +412,56,17371,2,0,B|436:136,1,70,8|0 +448,192,17713,2,0,B|520:224,1,70 +472,280,18056,2,0,B|408:252,1,70,8|0 +388,320,18399,2,0,B|360:388,1,70 +300,348,18742,2,0,B|328:280,1,70,8|0 +344,216,19085,1,0 +156,192,19428,1,0 +256,76,19771,5,4 +256,76,20456,1,4 +124,324,21142,1,2 +256,372,21485,1,2 +388,324,21828,1,2 +504,32,22513,6,0,B|424:32,1,70,4|0 +448,104,22856,1,8 +376,104,23028,1,0 +360,32,23199,2,0,B|280:32,1,70 +304,104,23542,1,8 +232,104,23713,1,0 +216,32,23885,2,0,B|144:32,1,70 +160,104,24228,1,8 +88,104,24399,1,0 +72,32,24571,2,8,B|0:32,2,70,2|8|10 +8,352,25256,6,0,B|88:352,1,70 +64,280,25599,1,8 +136,280,25771,1,0 +152,352,25942,2,0,B|232:352,1,70 +208,280,26285,1,8 +280,280,26456,1,0 +296,352,26628,2,0,B|368:352,1,70 +352,280,26971,1,8 +424,280,27142,1,0 +440,352,27313,2,0,B|512:352,2,70,2|0|10 +40,228,27999,6,0,B|40:148,1,70,6|0 +112,196,28342,2,0,B|112:232,2,35,8|0|0 +184,156,28685,2,0,B|184:236,1,70,2|0 +260,188,29028,2,0,B|260:152,2,35,8|0|0 +336,188,29371,2,0,B|376:248,1,70,2|0 +440,216,29713,2,0,B|392:148,1,70,8|0 +460,124,30056,2,0,B|484:160,4,35,10|0|8|0|10 +328,72,30742,6,0,B|288:72,3,35 +256,72,31085,2,0,B|208:72,3,35,8|0|0|0 +184,72,31428,2,0,B|128:72,3,35,0|0|0|0 +112,72,31771,2,0,B|72:72,3,35,8|0|0|0 +40,72,32113,5,8 +40,216,32456,1,0 +184,216,32799,1,8 +184,360,33142,1,0 +304,288,33485,6,0,B|400:184,1,140,0|8 +256,32,34342,1,0 +256,32,34513,1,8 +136,112,34856,2,0,B|136:256,1,140,0|10 +104,320,35371,2,0,B|144:344|224:304|208:240,1,140,0|2 +212,192,35885,1,8 +408,336,36228,6,0,B|488:304|480:224,1,140,0|8 +360,56,37085,1,0 +360,56,37256,1,8 +232,120,37599,2,0,B|184:64|96:72,1,140,0|10 +56,120,38113,2,0,B|16:72|40:16|80:0|104:8,1,140,0|2 +156,4,38628,1,8 +256,100,38971,6,0,B|160:204,1,140,0|8 +256,352,39828,1,0 +256,352,39999,1,8 +376,272,40342,2,0,B|376:128,1,140,0|10 +408,64,40856,2,0,B|368:40|288:80|304:144,1,140,0|2 +300,192,41371,1,8 +104,48,41713,6,0,B|24:80|32:160,1,140,0|8 +152,328,42571,1,0 +152,328,42742,1,8 +256,232,43085,6,0,B|256:184,8,35,0|0|0|0|8|0|0|0|8 +256,92,44113,1,2 +124,140,44456,5,4 +72,92,44628,2,0,B|16:144|80:260|184:192,1,210 +256,92,45485,1,8 +388,140,45828,5,0 +440,92,45999,2,0,B|496:144|432:260|328:192,1,210 +256,92,46856,1,8 +256,232,47199,2,0,B|216:296|296:296|252:368,3,140,0|8|0|8 +392,320,48571,6,0,B|392:176,1,140,0|8 +464,184,49085,2,0,B|448:96|376:88|320:128|324:184,1,210,0|8 +187,170,49942,6,0,B|188:128|140:88|60:96|48:180,1,210,4|8 +120,180,50628,2,0,B|120:320,1,140,0|8 +257,363,51313,2,0,B|216:296|296:296|256:232,3,140,0|8|0|8 +256,92,52685,5,0 +169,196,53028,2,0,B|80:248|16:140|80:84,1,210,8|0 +124,140,53713,1,8 +68,268,54056,6,0,B|56:304,2,35 +156,296,54399,1,8 +444,268,54742,6,0,B|456:304,2,35 +356,296,55085,1,8 +356,96,55428,6,0,B|296:96|256:9|256:9|216:96|152:96,1,280 +160,20,56285,1,0 +92,56,56456,1,8 +84,132,56628,1,0 +156,96,56799,6,0,B|156:155|242:195|242:195|156:236|155:300,1,280 +92,252,57656,1,0 +88,328,57828,1,8 +148,376,57999,1,0 +155,299,58171,6,0,B|215:299|255:386|255:386|295:299|359:299,1,280 +356,376,59028,1,0 +424,336,59199,1,8 +428,260,59371,1,0 +356,298,59542,6,0,B|356:239|270:199|270:199|356:158|357:94,1,280 +424,140,60399,1,0 +428,64,60571,1,8 +360,24,60742,1,0 +284,24,60913,6,0,B|212:24,1,70 +136,24,61256,1,8 +60,24,61428,1,0 +60,36,61513,1,0 +60,48,61599,2,0,B|60:124,1,70 +60,196,61942,1,8 +136,196,62113,1,0 +136,184,62199,1,0 +136,172,62285,6,0,B|136:96,1,70 +212,104,62628,1,8 +212,180,62799,1,0 +212,192,62885,1,0 +212,204,62971,2,0,B|212:292,1,70,8|0 +136,272,63313,2,0,B|60:272,1,70,8|0 +256,192,63656,12,0,65028 +256,324,65713,6,0,B|256:360,2,35,0|0|0 +288,256,66056,1,0 +328,316,66228,1,8 +400,288,66399,6,0,B|448:264|448:204,1,70,6|0 +380,192,66742,1,8 +444,148,66913,2,0,B|420:100|360:100,1,70,2|0 +316,124,67256,2,0,B|292:96|292:96|300:64,1,70,0|10 +224,48,67599,2,0,B|196:72|196:72|164:64,1,70,0|2 +104,112,67942,2,0,B|128:140|128:140|120:172,1,70,0|10 +80,240,68285,2,0,B|108:264|108:264|140:256,1,70,0|2 +200,208,68628,2,0,B|216:128,2,70,0|10|0 +212,284,69142,5,2 +256,356,69313,1,0 +256,356,69485,1,0 +292,280,69656,1,2 +292,280,69828,1,0 +368,308,69999,1,0 +376,304,70085,1,0 +384,300,70171,1,10 +392,296,70256,1,0 +400,292,70342,1,0 +408,288,70428,1,0 +416,284,70513,2,8,B|472:240|444:156,3,140,10|10|10|10 +312,44,71885,5,2 +312,44,72056,1,0 +224,32,72228,1,8 +216,40,72313,1,0 +208,48,72399,1,2 +200,56,72485,1,0 +192,64,72571,1,0 +124,28,72742,1,0 +48,36,72913,2,0,B|60:84|100:104,1,70,10|0 +44,160,73256,6,0,B|44:228,1,70,2|0 +116,200,73599,2,0,B|116:272,1,70,10|0 +188,240,73942,2,0,B|188:312,2,70,2|0|10 +188,164,74456,1,0 +260,196,74628,6,0,B|324:256,1,70,2|0 +361,195,74971,2,0,B|311:243,1,70,8|2 +260,292,75313,2,0,B|324:232,1,70 +360,294,75656,2,0,B|311:244,1,70,10|0 +256,192,75999,12,0,77028 +48,192,77371,6,0,B|48:48,1,140 +152,192,78056,2,0,B|152:336,1,140 +152,192,78742,2,0,B|152:48,1,140 +256,192,79427,2,0,B|256:336,1,140 +256,192,80113,6,0,B|256:32,2,140 +256,304,81142,2,0,B|416:304,2,140 +468,304,82171,1,0 +468,304,82513,1,0 +352,112,82856,6,0,B|408:69,3,70 +448,128,83542,1,0 +324,192,83885,1,0 +160,112,84228,6,0,B|103:69,3,70 +64,128,84913,1,0 +188,192,85256,1,0 +352,272,85599,6,0,B|408:314,3,70 +356,364,86285,1,0 +356,364,86456,1,0 +356,364,86628,1,0 +160,272,86971,6,0,B|128:300,4,35 +256,64,87999,1,12 +104,80,88342,6,0,B|20:200|96:376|336:380|344:128,1,560,4|0 +408,100,89885,1,0 +416,168,90056,1,0 +400,236,90228,1,0 +360,296,90399,6,0,B|300:412|104:424|24:300|16:224,1,420 +60,196,91599,1,0 +24,136,91771,2,0,B|140:60,1,140 +256,192,92285,12,0,93828 +64,168,94513,6,0,B|24:272|168:376|244:280,1,280 +300,300,95371,1,0 +340,244,95542,1,0 +404,272,95713,1,0 +448,216,95885,6,0,B|488:112|344:8|268:104,1,280 +256,228,96913,1,0 +464,336,97256,6,0,B|388:296|388:364|320:324,1,140 +252,328,97771,1,0 +176,328,97942,2,0,B|104:328,7,70,0|0|0|0|0|0|8|0 +28,328,99313,6,0,B|-16:184|72:68|216:64|260:160,1,420,4|8 +484,328,100685,2,0,B|528:184|440:68|296:64|244:168,1,420,0|8 +160,264,102056,6,0,B|160:336|256:385|352:336|352:264,2,280,0|8|0 +48,152,103771,1,8 +256,72,104113,1,0 +464,152,104456,1,8 +352,264,104799,5,0 +272,264,104971,2,0,B|208:264,2,35,0|0|8 +240,184,105313,2,0,B|304:184,2,35 +272,104,105656,2,0,B|208:104,2,35,0|0|8 +240,28,105999,2,0,B|304:28,2,35 +168,64,106342,6,0,B|80:104,1,70,0|8 +56,192,106685,2,0,B|128:192,1,70 +104,291,107028,2,0,B|168:320,1,70,0|8 +256,192,107371,1,0 +344,64,107542,6,0,B|432:104,1,70,8|0 +456,192,107885,2,0,B|384:192,1,70,8|0 +408,291,108228,2,0,B|344:320,1,70,8|0 +256,192,108571,2,0,B|256:160,6,23.3333333333333,0|0|0|0|0|0|4 +256,192,108999,12,8,109942 +168,120,110285,6,0,B|232:56,1,70 +344,120,110628,2,0,B|280:56,1,70,8|0 +207,192,110971,2,0,B|271:128,1,70 +305,192,111313,2,0,B|241:128,1,70,8|0 +216,304,111656,2,0,B|256:247|256:247|296:304,3,140,0|8|0|8 +352,112,113028,5,0 +360,192,113199,2,0,B|368:256,2,35,0|0|8 +424,144,113542,1,0 +352,112,113713,1,0 +408,64,113885,2,0,B|456:48,2,35,0|0|8 +336,40,114228,1,0 +352,112,114399,1,0 +280,88,114571,2,0,B|248:72,2,35,0|0|8 +352,112,114913,1,0 +296,160,115085,1,8 +368,184,115256,1,8 +424,136,115428,1,8 +128,72,115771,6,0,B|88:56|88:88|56:72,1,70 +64,152,116113,2,0,B|104:168|104:136|136:152,1,70,8|0 +136,232,116456,2,0,B|96:216|96:248|64:232,1,70 +72,312,116798,2,0,B|112:328|112:296|144:312,1,70,8|0 +216,312,117142,5,0 +216,192,117313,1,0 +216,72,117485,1,8 +296,296,117828,5,0 +296,176,117999,1,0 +296,56,118171,1,8 +448,64,118513,6,0,B|392:104,1,70 +392,184,118856,1,8 +392,192,118942,1,0 +392,200,119028,1,0 +392,288,119199,2,0,B|392:328,2,35 +464,240,119542,1,8 +464,248,119628,1,0 +464,256,119713,1,0 +464,336,119885,6,2,B|256:360|256:360|48:336,1,420 +256,192,121256,12,0,123999 diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/104973-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/104973-expected-conversion.json new file mode 100644 index 0000000000..8a5fa1ab79 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/104973-expected-conversion.json @@ -0,0 +1 @@ +{"Mappings":[{"StartTime":11980.0,"Objects":[{"StartTime":11980.0,"Position":152.0,"HyperDash":false}]},{"StartTime":12313.0,"Objects":[{"StartTime":12313.0,"Position":344.0,"HyperDash":false}]},{"StartTime":12647.0,"Objects":[{"StartTime":12647.0,"Position":132.0,"HyperDash":false},{"StartTime":12730.0,"Position":96.8423157,"HyperDash":false},{"StartTime":12813.0,"Position":80.68463,"HyperDash":false},{"StartTime":12896.0,"Position":68.52695,"HyperDash":false},{"StartTime":12980.0,"Position":51.1263962,"HyperDash":false},{"StartTime":13054.0,"Position":84.0983,"HyperDash":false},{"StartTime":13128.0,"Position":104.070213,"HyperDash":false},{"StartTime":13202.0,"Position":106.04213,"HyperDash":false},{"StartTime":13313.0,"Position":132.0,"HyperDash":false}]},{"StartTime":13646.0,"Objects":[{"StartTime":13646.0,"Position":220.0,"HyperDash":false}]},{"StartTime":13980.0,"Objects":[{"StartTime":13980.0,"Position":240.0,"HyperDash":false},{"StartTime":14063.0,"Position":219.934647,"HyperDash":false},{"StartTime":14146.0,"Position":186.8693,"HyperDash":false},{"StartTime":14229.0,"Position":174.80394,"HyperDash":false},{"StartTime":14313.0,"Position":163.508881,"HyperDash":false},{"StartTime":14387.0,"Position":168.5069,"HyperDash":false},{"StartTime":14461.0,"Position":193.504929,"HyperDash":false},{"StartTime":14535.0,"Position":228.50296,"HyperDash":false},{"StartTime":14646.0,"Position":240.0,"HyperDash":false}]},{"StartTime":14980.0,"Objects":[{"StartTime":14980.0,"Position":316.0,"HyperDash":false}]},{"StartTime":15313.0,"Objects":[{"StartTime":15313.0,"Position":304.0,"HyperDash":false},{"StartTime":15387.0,"Position":327.87616,"HyperDash":false},{"StartTime":15461.0,"Position":334.752319,"HyperDash":false},{"StartTime":15535.0,"Position":368.628479,"HyperDash":false},{"StartTime":15646.0,"Position":393.442719,"HyperDash":false}]},{"StartTime":15980.0,"Objects":[{"StartTime":15980.0,"Position":496.0,"HyperDash":false},{"StartTime":16054.0,"Position":463.669525,"HyperDash":false},{"StartTime":16128.0,"Position":457.4449,"HyperDash":false},{"StartTime":16202.0,"Position":466.1673,"HyperDash":false},{"StartTime":16313.0,"Position":418.33728,"HyperDash":false}]},{"StartTime":16647.0,"Objects":[{"StartTime":16647.0,"Position":296.0,"HyperDash":false},{"StartTime":16730.0,"Position":288.3361,"HyperDash":false},{"StartTime":16813.0,"Position":260.278259,"HyperDash":false},{"StartTime":16896.0,"Position":216.255356,"HyperDash":false},{"StartTime":16980.0,"Position":202.409332,"HyperDash":false},{"StartTime":17063.0,"Position":195.537857,"HyperDash":false},{"StartTime":17146.0,"Position":159.494614,"HyperDash":false},{"StartTime":17229.0,"Position":152.264984,"HyperDash":false},{"StartTime":17313.0,"Position":133.499115,"HyperDash":false},{"StartTime":17396.0,"Position":132.895508,"HyperDash":false},{"StartTime":17480.0,"Position":160.2883,"HyperDash":false},{"StartTime":17563.0,"Position":181.309479,"HyperDash":false},{"StartTime":17647.0,"Position":202.409348,"HyperDash":false},{"StartTime":17721.0,"Position":206.570633,"HyperDash":false},{"StartTime":17795.0,"Position":222.901245,"HyperDash":false},{"StartTime":17869.0,"Position":247.13147,"HyperDash":false},{"StartTime":17980.0,"Position":296.0,"HyperDash":false}]},{"StartTime":18312.0,"Objects":[{"StartTime":18312.0,"Position":296.0,"HyperDash":false}]},{"StartTime":18646.0,"Objects":[{"StartTime":18646.0,"Position":276.0,"HyperDash":false}]},{"StartTime":18980.0,"Objects":[{"StartTime":18980.0,"Position":416.0,"HyperDash":false},{"StartTime":19054.0,"Position":407.972717,"HyperDash":false},{"StartTime":19128.0,"Position":394.945435,"HyperDash":false},{"StartTime":19202.0,"Position":393.918152,"HyperDash":false},{"StartTime":19313.0,"Position":384.377228,"HyperDash":false}]},{"StartTime":19646.0,"Objects":[{"StartTime":19646.0,"Position":160.0,"HyperDash":false}]},{"StartTime":19980.0,"Objects":[{"StartTime":19980.0,"Position":376.0,"HyperDash":false}]},{"StartTime":20313.0,"Objects":[{"StartTime":20313.0,"Position":168.0,"HyperDash":false},{"StartTime":20396.0,"Position":166.842316,"HyperDash":false},{"StartTime":20479.0,"Position":121.684631,"HyperDash":false},{"StartTime":20562.0,"Position":112.526947,"HyperDash":false},{"StartTime":20646.0,"Position":87.1263962,"HyperDash":false},{"StartTime":20720.0,"Position":118.0983,"HyperDash":false},{"StartTime":20794.0,"Position":140.070221,"HyperDash":false},{"StartTime":20868.0,"Position":158.04213,"HyperDash":false},{"StartTime":20979.0,"Position":168.0,"HyperDash":false}]},{"StartTime":21313.0,"Objects":[{"StartTime":21313.0,"Position":232.0,"HyperDash":false},{"StartTime":21396.0,"Position":222.713379,"HyperDash":false},{"StartTime":21479.0,"Position":200.426743,"HyperDash":false},{"StartTime":21562.0,"Position":168.140121,"HyperDash":false},{"StartTime":21646.0,"Position":134.560883,"HyperDash":false},{"StartTime":21720.0,"Position":139.21402,"HyperDash":false},{"StartTime":21794.0,"Position":182.867157,"HyperDash":false},{"StartTime":21868.0,"Position":187.5203,"HyperDash":false},{"StartTime":21979.0,"Position":232.0,"HyperDash":false}]},{"StartTime":22647.0,"Objects":[{"StartTime":22647.0,"Position":453.0,"HyperDash":false}]},{"StartTime":22980.0,"Objects":[{"StartTime":22980.0,"Position":363.0,"HyperDash":false}]},{"StartTime":23313.0,"Objects":[{"StartTime":23313.0,"Position":309.0,"HyperDash":false}]},{"StartTime":23647.0,"Objects":[{"StartTime":23647.0,"Position":448.0,"HyperDash":false}]},{"StartTime":23980.0,"Objects":[{"StartTime":23980.0,"Position":336.0,"HyperDash":false},{"StartTime":24063.0,"Position":321.0,"HyperDash":false},{"StartTime":24146.0,"Position":354.0,"HyperDash":false},{"StartTime":24229.0,"Position":328.0,"HyperDash":false},{"StartTime":24313.0,"Position":336.0,"HyperDash":false},{"StartTime":24387.0,"Position":352.0,"HyperDash":false},{"StartTime":24461.0,"Position":339.0,"HyperDash":false},{"StartTime":24535.0,"Position":326.0,"HyperDash":false},{"StartTime":24646.0,"Position":336.0,"HyperDash":false}]},{"StartTime":24980.0,"Objects":[{"StartTime":24980.0,"Position":176.0,"HyperDash":false}]},{"StartTime":25313.0,"Objects":[{"StartTime":25313.0,"Position":48.0,"HyperDash":false}]},{"StartTime":25647.0,"Objects":[{"StartTime":25647.0,"Position":228.0,"HyperDash":false}]},{"StartTime":25979.0,"Objects":[{"StartTime":25979.0,"Position":36.0,"HyperDash":false}]},{"StartTime":26313.0,"Objects":[{"StartTime":26313.0,"Position":176.0,"HyperDash":false}]},{"StartTime":26646.0,"Objects":[{"StartTime":26646.0,"Position":132.0,"HyperDash":false},{"StartTime":26729.0,"Position":157.86972,"HyperDash":false},{"StartTime":26812.0,"Position":171.739441,"HyperDash":false},{"StartTime":26895.0,"Position":206.609161,"HyperDash":false},{"StartTime":26979.0,"Position":231.7785,"HyperDash":false},{"StartTime":27053.0,"Position":191.605515,"HyperDash":false},{"StartTime":27127.0,"Position":171.43251,"HyperDash":false},{"StartTime":27201.0,"Position":173.2595,"HyperDash":false},{"StartTime":27312.0,"Position":132.0,"HyperDash":false}]},{"StartTime":27647.0,"Objects":[{"StartTime":27647.0,"Position":256.0,"HyperDash":false}]},{"StartTime":27980.0,"Objects":[{"StartTime":27980.0,"Position":404.0,"HyperDash":false},{"StartTime":28054.0,"Position":410.368652,"HyperDash":false},{"StartTime":28128.0,"Position":420.008545,"HyperDash":false},{"StartTime":28202.0,"Position":448.395325,"HyperDash":false},{"StartTime":28313.0,"Position":467.645935,"HyperDash":false}]},{"StartTime":28646.0,"Objects":[{"StartTime":28646.0,"Position":220.0,"HyperDash":false}]},{"StartTime":28980.0,"Objects":[{"StartTime":28980.0,"Position":348.0,"HyperDash":false}]},{"StartTime":29313.0,"Objects":[{"StartTime":29313.0,"Position":336.0,"HyperDash":false},{"StartTime":29387.0,"Position":303.6809,"HyperDash":false},{"StartTime":29461.0,"Position":303.67157,"HyperDash":false},{"StartTime":29535.0,"Position":277.6959,"HyperDash":false},{"StartTime":29646.0,"Position":260.962,"HyperDash":false}]},{"StartTime":29979.0,"Objects":[{"StartTime":29979.0,"Position":360.0,"HyperDash":false}]},{"StartTime":30313.0,"Objects":[{"StartTime":30313.0,"Position":248.0,"HyperDash":false}]},{"StartTime":30646.0,"Objects":[{"StartTime":30646.0,"Position":360.0,"HyperDash":false}]},{"StartTime":31313.0,"Objects":[{"StartTime":31313.0,"Position":24.0,"HyperDash":false}]},{"StartTime":31646.0,"Objects":[{"StartTime":31646.0,"Position":96.0,"HyperDash":false}]},{"StartTime":31979.0,"Objects":[{"StartTime":31979.0,"Position":116.0,"HyperDash":false}]},{"StartTime":32313.0,"Objects":[{"StartTime":32313.0,"Position":168.0,"HyperDash":false}]},{"StartTime":32647.0,"Objects":[{"StartTime":32647.0,"Position":360.0,"HyperDash":false}]},{"StartTime":33313.0,"Objects":[{"StartTime":33313.0,"Position":488.0,"HyperDash":false}]},{"StartTime":33647.0,"Objects":[{"StartTime":33647.0,"Position":488.0,"HyperDash":false},{"StartTime":33721.0,"Position":462.092926,"HyperDash":false},{"StartTime":33795.0,"Position":475.185822,"HyperDash":false},{"StartTime":33869.0,"Position":480.278748,"HyperDash":false},{"StartTime":33980.0,"Position":447.918121,"HyperDash":false}]},{"StartTime":34313.0,"Objects":[{"StartTime":34313.0,"Position":380.0,"HyperDash":false},{"StartTime":34396.0,"Position":378.853241,"HyperDash":false},{"StartTime":34479.0,"Position":357.6393,"HyperDash":false},{"StartTime":34544.0,"Position":376.301575,"HyperDash":false},{"StartTime":34646.0,"Position":380.0,"HyperDash":false}]},{"StartTime":34980.0,"Objects":[{"StartTime":34980.0,"Position":312.0,"HyperDash":false},{"StartTime":35054.0,"Position":293.8341,"HyperDash":false},{"StartTime":35128.0,"Position":274.729065,"HyperDash":false},{"StartTime":35202.0,"Position":262.3615,"HyperDash":false},{"StartTime":35313.0,"Position":217.647247,"HyperDash":false}]},{"StartTime":35646.0,"Objects":[{"StartTime":35646.0,"Position":116.0,"HyperDash":false},{"StartTime":35729.0,"Position":76.07507,"HyperDash":false},{"StartTime":35812.0,"Position":66.0,"HyperDash":false},{"StartTime":35877.0,"Position":82.36937,"HyperDash":false},{"StartTime":35979.0,"Position":116.0,"HyperDash":false}]},{"StartTime":36313.0,"Objects":[{"StartTime":36313.0,"Position":232.0,"HyperDash":false},{"StartTime":36387.0,"Position":244.2069,"HyperDash":false},{"StartTime":36461.0,"Position":281.2214,"HyperDash":false},{"StartTime":36535.0,"Position":306.592651,"HyperDash":false},{"StartTime":36646.0,"Position":327.491272,"HyperDash":false}]},{"StartTime":36813.0,"Objects":[{"StartTime":36813.0,"Position":356.0,"HyperDash":false},{"StartTime":36896.0,"Position":384.924927,"HyperDash":false},{"StartTime":36979.0,"Position":406.0,"HyperDash":false},{"StartTime":37044.0,"Position":376.6306,"HyperDash":false},{"StartTime":37146.0,"Position":356.0,"HyperDash":false}]},{"StartTime":37313.0,"Objects":[{"StartTime":37313.0,"Position":172.0,"HyperDash":false}]},{"StartTime":37646.0,"Objects":[{"StartTime":37646.0,"Position":176.0,"HyperDash":false},{"StartTime":37729.0,"Position":141.075073,"HyperDash":false},{"StartTime":37812.0,"Position":126.0,"HyperDash":false},{"StartTime":37877.0,"Position":152.36937,"HyperDash":false},{"StartTime":37979.0,"Position":176.0,"HyperDash":false}]},{"StartTime":38313.0,"Objects":[{"StartTime":38313.0,"Position":232.0,"HyperDash":false}]},{"StartTime":38647.0,"Objects":[{"StartTime":38647.0,"Position":60.0,"HyperDash":false}]},{"StartTime":38980.0,"Objects":[{"StartTime":38980.0,"Position":276.0,"HyperDash":false}]},{"StartTime":39313.0,"Objects":[{"StartTime":39313.0,"Position":60.0,"HyperDash":false},{"StartTime":39396.0,"Position":79.53542,"HyperDash":false},{"StartTime":39479.0,"Position":80.3983,"HyperDash":false},{"StartTime":39562.0,"Position":95.7953949,"HyperDash":false},{"StartTime":39646.0,"Position":96.9988861,"HyperDash":false},{"StartTime":39711.0,"Position":90.0326843,"HyperDash":false},{"StartTime":39813.0,"Position":130.8265,"HyperDash":false}]},{"StartTime":39980.0,"Objects":[{"StartTime":39980.0,"Position":148.0,"HyperDash":false},{"StartTime":40063.0,"Position":155.921555,"HyperDash":false},{"StartTime":40146.0,"Position":200.495987,"HyperDash":false},{"StartTime":40229.0,"Position":229.243881,"HyperDash":false},{"StartTime":40313.0,"Position":244.105148,"HyperDash":false},{"StartTime":40378.0,"Position":263.884155,"HyperDash":false},{"StartTime":40479.0,"Position":285.356873,"HyperDash":false}]},{"StartTime":40647.0,"Objects":[{"StartTime":40647.0,"Position":274.0,"HyperDash":false}]},{"StartTime":40980.0,"Objects":[{"StartTime":40980.0,"Position":392.0,"HyperDash":false},{"StartTime":41063.0,"Position":414.371643,"HyperDash":false},{"StartTime":41146.0,"Position":440.8901,"HyperDash":false},{"StartTime":41211.0,"Position":438.9507,"HyperDash":false},{"StartTime":41313.0,"Position":392.0,"HyperDash":false}]},{"StartTime":41647.0,"Objects":[{"StartTime":41647.0,"Position":292.0,"HyperDash":false},{"StartTime":41721.0,"Position":255.813812,"HyperDash":false},{"StartTime":41795.0,"Position":263.524658,"HyperDash":false},{"StartTime":41869.0,"Position":212.4873,"HyperDash":false},{"StartTime":41980.0,"Position":199.067825,"HyperDash":false}]},{"StartTime":42147.0,"Objects":[{"StartTime":42147.0,"Position":176.0,"HyperDash":false},{"StartTime":42212.0,"Position":183.0,"HyperDash":false},{"StartTime":42313.0,"Position":176.0,"HyperDash":false}]},{"StartTime":42480.0,"Objects":[{"StartTime":42480.0,"Position":140.0,"HyperDash":false},{"StartTime":42545.0,"Position":131.421692,"HyperDash":false},{"StartTime":42646.0,"Position":90.0,"HyperDash":false}]},{"StartTime":42980.0,"Objects":[{"StartTime":42980.0,"Position":210.0,"HyperDash":false},{"StartTime":43063.0,"Position":225.924927,"HyperDash":false},{"StartTime":43146.0,"Position":260.0,"HyperDash":false},{"StartTime":43211.0,"Position":233.63063,"HyperDash":false},{"StartTime":43313.0,"Position":210.0,"HyperDash":false}]},{"StartTime":43647.0,"Objects":[{"StartTime":43647.0,"Position":248.0,"HyperDash":false}]},{"StartTime":43980.0,"Objects":[{"StartTime":43980.0,"Position":264.0,"HyperDash":false}]},{"StartTime":44313.0,"Objects":[{"StartTime":44313.0,"Position":248.0,"HyperDash":false}]},{"StartTime":44647.0,"Objects":[{"StartTime":44647.0,"Position":344.0,"HyperDash":false},{"StartTime":44721.0,"Position":364.6328,"HyperDash":false},{"StartTime":44795.0,"Position":391.265625,"HyperDash":false},{"StartTime":44869.0,"Position":390.898438,"HyperDash":false},{"StartTime":44980.0,"Position":436.847656,"HyperDash":false}]},{"StartTime":45313.0,"Objects":[{"StartTime":45313.0,"Position":340.0,"HyperDash":false},{"StartTime":45387.0,"Position":332.927124,"HyperDash":false},{"StartTime":45461.0,"Position":320.854218,"HyperDash":false},{"StartTime":45535.0,"Position":286.7813,"HyperDash":false},{"StartTime":45646.0,"Position":272.172,"HyperDash":false}]},{"StartTime":45980.0,"Objects":[{"StartTime":45980.0,"Position":236.0,"HyperDash":false},{"StartTime":46054.0,"Position":231.452988,"HyperDash":false},{"StartTime":46128.0,"Position":230.905975,"HyperDash":false},{"StartTime":46202.0,"Position":205.358963,"HyperDash":false},{"StartTime":46313.0,"Position":197.538452,"HyperDash":false}]},{"StartTime":46647.0,"Objects":[{"StartTime":46647.0,"Position":92.0,"HyperDash":false},{"StartTime":46721.0,"Position":83.9194641,"HyperDash":false},{"StartTime":46795.0,"Position":66.01362,"HyperDash":false},{"StartTime":46869.0,"Position":83.9567261,"HyperDash":false},{"StartTime":46980.0,"Position":93.07765,"HyperDash":false}]},{"StartTime":47313.0,"Objects":[{"StartTime":47313.0,"Position":312.0,"HyperDash":false}]},{"StartTime":47647.0,"Objects":[{"StartTime":47647.0,"Position":324.0,"HyperDash":false},{"StartTime":47730.0,"Position":367.924927,"HyperDash":false},{"StartTime":47813.0,"Position":374.0,"HyperDash":false},{"StartTime":47878.0,"Position":351.6306,"HyperDash":false},{"StartTime":47980.0,"Position":324.0,"HyperDash":false}]},{"StartTime":48313.0,"Objects":[{"StartTime":48313.0,"Position":212.0,"HyperDash":false},{"StartTime":48387.0,"Position":201.990753,"HyperDash":false},{"StartTime":48461.0,"Position":179.8291,"HyperDash":false},{"StartTime":48535.0,"Position":186.227524,"HyperDash":false},{"StartTime":48646.0,"Position":213.404251,"HyperDash":false}]},{"StartTime":48980.0,"Objects":[{"StartTime":48980.0,"Position":428.0,"HyperDash":false}]},{"StartTime":49313.0,"Objects":[{"StartTime":49313.0,"Position":220.0,"HyperDash":false}]},{"StartTime":49647.0,"Objects":[{"StartTime":49647.0,"Position":256.0,"HyperDash":false},{"StartTime":49730.0,"Position":255.0,"HyperDash":false},{"StartTime":49813.0,"Position":256.0,"HyperDash":false},{"StartTime":49878.0,"Position":273.0,"HyperDash":false},{"StartTime":49980.0,"Position":256.0,"HyperDash":false}]},{"StartTime":50313.0,"Objects":[{"StartTime":50313.0,"Position":392.0,"HyperDash":false}]},{"StartTime":50647.0,"Objects":[{"StartTime":50647.0,"Position":256.0,"HyperDash":false}]},{"StartTime":50980.0,"Objects":[{"StartTime":50980.0,"Position":256.0,"HyperDash":false},{"StartTime":51063.0,"Position":268.0,"HyperDash":false},{"StartTime":51146.0,"Position":256.0,"HyperDash":false},{"StartTime":51211.0,"Position":267.0,"HyperDash":false},{"StartTime":51313.0,"Position":256.0,"HyperDash":false}]},{"StartTime":51647.0,"Objects":[{"StartTime":51647.0,"Position":200.0,"HyperDash":false},{"StartTime":51721.0,"Position":220.222229,"HyperDash":false},{"StartTime":51795.0,"Position":261.444458,"HyperDash":false},{"StartTime":51869.0,"Position":257.6667,"HyperDash":false},{"StartTime":51980.0,"Position":300.0,"HyperDash":false}]},{"StartTime":52647.0,"Objects":[{"StartTime":52647.0,"Position":136.0,"HyperDash":false}]},{"StartTime":52980.0,"Objects":[{"StartTime":52980.0,"Position":256.0,"HyperDash":false},{"StartTime":53063.0,"Position":291.9663,"HyperDash":false},{"StartTime":53146.0,"Position":284.932617,"HyperDash":false},{"StartTime":53229.0,"Position":307.898926,"HyperDash":false},{"StartTime":53313.0,"Position":340.117859,"HyperDash":false},{"StartTime":53387.0,"Position":325.425,"HyperDash":false},{"StartTime":53461.0,"Position":290.732147,"HyperDash":false},{"StartTime":53535.0,"Position":300.039276,"HyperDash":false},{"StartTime":53646.0,"Position":256.0,"HyperDash":false}]},{"StartTime":53980.0,"Objects":[{"StartTime":53980.0,"Position":384.0,"HyperDash":false}]},{"StartTime":54313.0,"Objects":[{"StartTime":54313.0,"Position":256.0,"HyperDash":false},{"StartTime":54396.0,"Position":223.033691,"HyperDash":false},{"StartTime":54479.0,"Position":213.067383,"HyperDash":false},{"StartTime":54562.0,"Position":180.101074,"HyperDash":false},{"StartTime":54646.0,"Position":171.882141,"HyperDash":false},{"StartTime":54720.0,"Position":184.575012,"HyperDash":false},{"StartTime":54794.0,"Position":201.267853,"HyperDash":false},{"StartTime":54868.0,"Position":218.960709,"HyperDash":false},{"StartTime":54979.0,"Position":256.0,"HyperDash":false}]},{"StartTime":55313.0,"Objects":[{"StartTime":55313.0,"Position":368.0,"HyperDash":false}]},{"StartTime":55647.0,"Objects":[{"StartTime":55647.0,"Position":256.0,"HyperDash":false},{"StartTime":55730.0,"Position":251.0,"HyperDash":false},{"StartTime":55813.0,"Position":251.0,"HyperDash":false},{"StartTime":55896.0,"Position":270.0,"HyperDash":false},{"StartTime":55980.0,"Position":256.0,"HyperDash":false},{"StartTime":56054.0,"Position":259.0,"HyperDash":false},{"StartTime":56128.0,"Position":244.0,"HyperDash":false},{"StartTime":56202.0,"Position":244.0,"HyperDash":false},{"StartTime":56313.0,"Position":256.0,"HyperDash":false}]},{"StartTime":56647.0,"Objects":[{"StartTime":56647.0,"Position":276.0,"HyperDash":false}]},{"StartTime":57313.0,"Objects":[{"StartTime":57313.0,"Position":488.0,"HyperDash":false}]},{"StartTime":57647.0,"Objects":[{"StartTime":57647.0,"Position":488.0,"HyperDash":false},{"StartTime":57721.0,"Position":481.4433,"HyperDash":false},{"StartTime":57795.0,"Position":483.349152,"HyperDash":false},{"StartTime":57869.0,"Position":454.0119,"HyperDash":false},{"StartTime":57980.0,"Position":458.509216,"HyperDash":false}]},{"StartTime":58313.0,"Objects":[{"StartTime":58313.0,"Position":360.0,"HyperDash":false},{"StartTime":58387.0,"Position":344.2625,"HyperDash":false},{"StartTime":58461.0,"Position":344.958252,"HyperDash":false},{"StartTime":58535.0,"Position":319.941345,"HyperDash":false},{"StartTime":58646.0,"Position":314.506317,"HyperDash":false}]},{"StartTime":58980.0,"Objects":[{"StartTime":58980.0,"Position":428.0,"HyperDash":false}]},{"StartTime":59313.0,"Objects":[{"StartTime":59313.0,"Position":260.0,"HyperDash":false}]},{"StartTime":59647.0,"Objects":[{"StartTime":59647.0,"Position":224.0,"HyperDash":false},{"StartTime":59730.0,"Position":233.954819,"HyperDash":false},{"StartTime":59813.0,"Position":211.873215,"HyperDash":false},{"StartTime":59878.0,"Position":207.570984,"HyperDash":false},{"StartTime":59980.0,"Position":224.0,"HyperDash":false}]},{"StartTime":60313.0,"Objects":[{"StartTime":60313.0,"Position":304.0,"HyperDash":false}]},{"StartTime":60647.0,"Objects":[{"StartTime":60647.0,"Position":208.0,"HyperDash":false}]},{"StartTime":60980.0,"Objects":[{"StartTime":60980.0,"Position":136.0,"HyperDash":false},{"StartTime":61063.0,"Position":100.414207,"HyperDash":false},{"StartTime":61146.0,"Position":86.6803,"HyperDash":false},{"StartTime":61211.0,"Position":91.78613,"HyperDash":false},{"StartTime":61313.0,"Position":136.0,"HyperDash":false}]},{"StartTime":61647.0,"Objects":[{"StartTime":61647.0,"Position":448.0,"HyperDash":false}]},{"StartTime":61980.0,"Objects":[{"StartTime":61980.0,"Position":256.0,"HyperDash":false}]},{"StartTime":62313.0,"Objects":[{"StartTime":62313.0,"Position":420.0,"HyperDash":false}]},{"StartTime":62647.0,"Objects":[{"StartTime":62647.0,"Position":228.0,"HyperDash":false}]},{"StartTime":62980.0,"Objects":[{"StartTime":62980.0,"Position":204.0,"HyperDash":false},{"StartTime":63063.0,"Position":197.227524,"HyperDash":false},{"StartTime":63146.0,"Position":154.305817,"HyperDash":false},{"StartTime":63211.0,"Position":183.556717,"HyperDash":false},{"StartTime":63313.0,"Position":204.0,"HyperDash":false}]},{"StartTime":63647.0,"Objects":[{"StartTime":63647.0,"Position":324.0,"HyperDash":false},{"StartTime":63721.0,"Position":356.66507,"HyperDash":false},{"StartTime":63795.0,"Position":328.949554,"HyperDash":false},{"StartTime":63869.0,"Position":341.904877,"HyperDash":false},{"StartTime":63980.0,"Position":341.121216,"HyperDash":false}]},{"StartTime":64313.0,"Objects":[{"StartTime":64313.0,"Position":180.0,"HyperDash":false}]},{"StartTime":64647.0,"Objects":[{"StartTime":64647.0,"Position":116.0,"HyperDash":false}]},{"StartTime":64980.0,"Objects":[{"StartTime":64980.0,"Position":36.0,"HyperDash":false},{"StartTime":65063.0,"Position":48.422226,"HyperDash":false},{"StartTime":65146.0,"Position":60.8444481,"HyperDash":false},{"StartTime":65229.0,"Position":42.26667,"HyperDash":false},{"StartTime":65313.0,"Position":61.7662659,"HyperDash":false},{"StartTime":65387.0,"Position":42.0404358,"HyperDash":false},{"StartTime":65461.0,"Position":31.3145981,"HyperDash":false},{"StartTime":65535.0,"Position":47.5887566,"HyperDash":false},{"StartTime":65646.0,"Position":36.0,"HyperDash":false}]},{"StartTime":65980.0,"Objects":[{"StartTime":65980.0,"Position":24.0,"HyperDash":false},{"StartTime":66063.0,"Position":33.5504036,"HyperDash":false},{"StartTime":66146.0,"Position":78.42056,"HyperDash":false},{"StartTime":66229.0,"Position":110.084938,"HyperDash":false},{"StartTime":66313.0,"Position":121.840134,"HyperDash":false},{"StartTime":66387.0,"Position":136.73616,"HyperDash":false},{"StartTime":66461.0,"Position":175.859619,"HyperDash":false},{"StartTime":66535.0,"Position":197.00679,"HyperDash":false},{"StartTime":66646.0,"Position":219.1586,"HyperDash":false}]},{"StartTime":66980.0,"Objects":[{"StartTime":66980.0,"Position":340.0,"HyperDash":false},{"StartTime":67054.0,"Position":368.672729,"HyperDash":false},{"StartTime":67128.0,"Position":380.049255,"HyperDash":false},{"StartTime":67202.0,"Position":406.45874,"HyperDash":false},{"StartTime":67313.0,"Position":423.819183,"HyperDash":false}]},{"StartTime":67647.0,"Objects":[{"StartTime":67647.0,"Position":436.0,"HyperDash":false},{"StartTime":67730.0,"Position":414.429535,"HyperDash":false},{"StartTime":67813.0,"Position":404.765259,"HyperDash":false},{"StartTime":67878.0,"Position":409.865173,"HyperDash":false},{"StartTime":67980.0,"Position":436.0,"HyperDash":false}]},{"StartTime":68313.0,"Objects":[{"StartTime":68313.0,"Position":468.0,"HyperDash":false}]},{"StartTime":68646.0,"Objects":[{"StartTime":68646.0,"Position":332.0,"HyperDash":false},{"StartTime":68720.0,"Position":334.127625,"HyperDash":false},{"StartTime":68794.0,"Position":293.255249,"HyperDash":false},{"StartTime":68868.0,"Position":281.382874,"HyperDash":false},{"StartTime":68979.0,"Position":256.074341,"HyperDash":false}]},{"StartTime":69313.0,"Objects":[{"StartTime":69313.0,"Position":272.0,"HyperDash":false},{"StartTime":69387.0,"Position":268.51,"HyperDash":false},{"StartTime":69461.0,"Position":219.019989,"HyperDash":false},{"StartTime":69535.0,"Position":233.529968,"HyperDash":false},{"StartTime":69646.0,"Position":188.794968,"HyperDash":false}]},{"StartTime":69980.0,"Objects":[{"StartTime":69980.0,"Position":208.0,"HyperDash":false},{"StartTime":70054.0,"Position":193.222229,"HyperDash":false},{"StartTime":70128.0,"Position":168.444443,"HyperDash":false},{"StartTime":70202.0,"Position":162.666656,"HyperDash":false},{"StartTime":70313.0,"Position":127.999992,"HyperDash":false}]},{"StartTime":70647.0,"Objects":[{"StartTime":70647.0,"Position":128.0,"HyperDash":false},{"StartTime":70721.0,"Position":108.251968,"HyperDash":false},{"StartTime":70795.0,"Position":105.503937,"HyperDash":false},{"StartTime":70869.0,"Position":59.7558975,"HyperDash":false},{"StartTime":70980.0,"Position":43.63385,"HyperDash":false}]},{"StartTime":71313.0,"Objects":[{"StartTime":71313.0,"Position":20.0,"HyperDash":false}]},{"StartTime":71647.0,"Objects":[{"StartTime":71647.0,"Position":72.0,"HyperDash":false},{"StartTime":71730.0,"Position":42.17414,"HyperDash":false},{"StartTime":71813.0,"Position":44.26499,"HyperDash":false},{"StartTime":71878.0,"Position":48.0091858,"HyperDash":false},{"StartTime":71980.0,"Position":72.0,"HyperDash":false}]},{"StartTime":72313.0,"Objects":[{"StartTime":72313.0,"Position":344.0,"HyperDash":false}]},{"StartTime":72647.0,"Objects":[{"StartTime":72647.0,"Position":256.0,"HyperDash":false}]},{"StartTime":72980.0,"Objects":[{"StartTime":72980.0,"Position":344.0,"HyperDash":false}]},{"StartTime":73313.0,"Objects":[{"StartTime":73313.0,"Position":192.0,"HyperDash":false}]},{"StartTime":73647.0,"Objects":[{"StartTime":73647.0,"Position":72.0,"HyperDash":false},{"StartTime":73730.0,"Position":35.075592,"HyperDash":false},{"StartTime":73813.0,"Position":34.03717,"HyperDash":false},{"StartTime":73878.0,"Position":44.7434921,"HyperDash":false},{"StartTime":73980.0,"Position":72.0,"HyperDash":false}]},{"StartTime":74313.0,"Objects":[{"StartTime":74313.0,"Position":208.0,"HyperDash":false}]},{"StartTime":74647.0,"Objects":[{"StartTime":74647.0,"Position":112.0,"HyperDash":false},{"StartTime":74730.0,"Position":135.84227,"HyperDash":false},{"StartTime":74813.0,"Position":152.1162,"HyperDash":false},{"StartTime":74896.0,"Position":171.061676,"HyperDash":false},{"StartTime":74980.0,"Position":196.921387,"HyperDash":false},{"StartTime":75063.0,"Position":218.520676,"HyperDash":false},{"StartTime":75146.0,"Position":260.403442,"HyperDash":false},{"StartTime":75229.0,"Position":258.21,"HyperDash":false},{"StartTime":75313.0,"Position":295.594574,"HyperDash":false},{"StartTime":75387.0,"Position":303.6625,"HyperDash":false},{"StartTime":75462.0,"Position":337.3289,"HyperDash":false},{"StartTime":75536.0,"Position":361.249237,"HyperDash":false},{"StartTime":75646.0,"Position":374.243744,"HyperDash":false}]},{"StartTime":75980.0,"Objects":[{"StartTime":75980.0,"Position":492.0,"HyperDash":false},{"StartTime":76063.0,"Position":469.9186,"HyperDash":false},{"StartTime":76146.0,"Position":442.890717,"HyperDash":false},{"StartTime":76229.0,"Position":461.403656,"HyperDash":false},{"StartTime":76313.0,"Position":454.9664,"HyperDash":false},{"StartTime":76387.0,"Position":453.878967,"HyperDash":false},{"StartTime":76461.0,"Position":434.64566,"HyperDash":false},{"StartTime":76535.0,"Position":431.048553,"HyperDash":false},{"StartTime":76646.0,"Position":439.4531,"HyperDash":false}]},{"StartTime":76980.0,"Objects":[{"StartTime":76980.0,"Position":320.0,"HyperDash":false},{"StartTime":77054.0,"Position":316.474152,"HyperDash":false},{"StartTime":77128.0,"Position":343.948273,"HyperDash":false},{"StartTime":77202.0,"Position":335.422424,"HyperDash":false},{"StartTime":77313.0,"Position":353.633636,"HyperDash":false}]},{"StartTime":77646.0,"Objects":[{"StartTime":77646.0,"Position":256.0,"HyperDash":false},{"StartTime":77720.0,"Position":272.0,"HyperDash":false},{"StartTime":77794.0,"Position":270.0,"HyperDash":false},{"StartTime":77868.0,"Position":249.0,"HyperDash":false},{"StartTime":77979.0,"Position":256.0,"HyperDash":false}]},{"StartTime":78313.0,"Objects":[{"StartTime":78313.0,"Position":192.0,"HyperDash":false},{"StartTime":78387.0,"Position":165.525864,"HyperDash":false},{"StartTime":78461.0,"Position":159.051712,"HyperDash":false},{"StartTime":78535.0,"Position":183.577576,"HyperDash":false},{"StartTime":78646.0,"Position":158.366364,"HyperDash":false}]},{"StartTime":78980.0,"Objects":[{"StartTime":78980.0,"Position":280.0,"HyperDash":false}]},{"StartTime":79313.0,"Objects":[{"StartTime":79313.0,"Position":320.0,"HyperDash":false},{"StartTime":79396.0,"Position":342.939819,"HyperDash":false},{"StartTime":79479.0,"Position":365.9111,"HyperDash":false},{"StartTime":79562.0,"Position":376.53537,"HyperDash":false},{"StartTime":79646.0,"Position":394.836121,"HyperDash":false},{"StartTime":79711.0,"Position":403.9664,"HyperDash":false},{"StartTime":79813.0,"Position":418.107727,"HyperDash":false}]},{"StartTime":79980.0,"Objects":[{"StartTime":79980.0,"Position":408.0,"HyperDash":false},{"StartTime":80063.0,"Position":393.190674,"HyperDash":false},{"StartTime":80146.0,"Position":340.936066,"HyperDash":false},{"StartTime":80229.0,"Position":331.749939,"HyperDash":false},{"StartTime":80313.0,"Position":313.736053,"HyperDash":false},{"StartTime":80378.0,"Position":308.810822,"HyperDash":false},{"StartTime":80480.0,"Position":274.773529,"HyperDash":false}]},{"StartTime":80647.0,"Objects":[{"StartTime":80647.0,"Position":236.0,"HyperDash":false},{"StartTime":80730.0,"Position":199.526276,"HyperDash":false},{"StartTime":80813.0,"Position":215.925659,"HyperDash":false},{"StartTime":80896.0,"Position":186.386475,"HyperDash":false},{"StartTime":80980.0,"Position":154.006546,"HyperDash":false},{"StartTime":81045.0,"Position":134.148682,"HyperDash":false},{"StartTime":81147.0,"Position":104.824638,"HyperDash":false}]},{"StartTime":81313.0,"Objects":[{"StartTime":81313.0,"Position":88.0,"HyperDash":false},{"StartTime":81396.0,"Position":110.135536,"HyperDash":false},{"StartTime":81479.0,"Position":112.874176,"HyperDash":false},{"StartTime":81562.0,"Position":127.188362,"HyperDash":false},{"StartTime":81646.0,"Position":144.1023,"HyperDash":false},{"StartTime":81711.0,"Position":162.4082,"HyperDash":false},{"StartTime":81813.0,"Position":185.452866,"HyperDash":false}]},{"StartTime":81980.0,"Objects":[{"StartTime":81980.0,"Position":240.0,"HyperDash":false}]},{"StartTime":82313.0,"Objects":[{"StartTime":82313.0,"Position":344.0,"HyperDash":false},{"StartTime":82396.0,"Position":356.924927,"HyperDash":false},{"StartTime":82479.0,"Position":394.0,"HyperDash":false},{"StartTime":82544.0,"Position":367.6306,"HyperDash":false},{"StartTime":82646.0,"Position":344.0,"HyperDash":false}]},{"StartTime":82980.0,"Objects":[{"StartTime":82980.0,"Position":96.0,"HyperDash":false}]},{"StartTime":83313.0,"Objects":[{"StartTime":83313.0,"Position":344.0,"HyperDash":false}]},{"StartTime":83647.0,"Objects":[{"StartTime":83647.0,"Position":436.0,"HyperDash":false}]},{"StartTime":83980.0,"Objects":[{"StartTime":83980.0,"Position":252.0,"HyperDash":false}]},{"StartTime":84313.0,"Objects":[{"StartTime":84313.0,"Position":228.0,"HyperDash":false},{"StartTime":84396.0,"Position":209.0,"HyperDash":false},{"StartTime":84479.0,"Position":228.0,"HyperDash":false},{"StartTime":84544.0,"Position":230.0,"HyperDash":false},{"StartTime":84646.0,"Position":228.0,"HyperDash":false}]},{"StartTime":84980.0,"Objects":[{"StartTime":84980.0,"Position":12.0,"HyperDash":false}]},{"StartTime":85313.0,"Objects":[{"StartTime":85313.0,"Position":228.0,"HyperDash":false}]},{"StartTime":85647.0,"Objects":[{"StartTime":85647.0,"Position":12.0,"HyperDash":false}]},{"StartTime":85980.0,"Objects":[{"StartTime":85980.0,"Position":228.0,"HyperDash":false}]},{"StartTime":86313.0,"Objects":[{"StartTime":86313.0,"Position":220.0,"HyperDash":false}]},{"StartTime":86647.0,"Objects":[{"StartTime":86647.0,"Position":104.0,"HyperDash":false}]},{"StartTime":86980.0,"Objects":[{"StartTime":86980.0,"Position":124.0,"HyperDash":false}]},{"StartTime":87313.0,"Objects":[{"StartTime":87313.0,"Position":104.0,"HyperDash":false},{"StartTime":87396.0,"Position":109.906219,"HyperDash":false},{"StartTime":87479.0,"Position":138.812454,"HyperDash":false},{"StartTime":87562.0,"Position":184.718689,"HyperDash":false},{"StartTime":87646.0,"Position":203.924988,"HyperDash":false},{"StartTime":87729.0,"Position":222.8312,"HyperDash":false},{"StartTime":87812.0,"Position":240.737427,"HyperDash":false},{"StartTime":87895.0,"Position":269.643677,"HyperDash":false},{"StartTime":87979.0,"Position":304.0,"HyperDash":false},{"StartTime":88062.0,"Position":273.2438,"HyperDash":false},{"StartTime":88146.0,"Position":243.0375,"HyperDash":false},{"StartTime":88229.0,"Position":229.131271,"HyperDash":false},{"StartTime":88313.0,"Position":203.924973,"HyperDash":false},{"StartTime":88387.0,"Position":182.719421,"HyperDash":false},{"StartTime":88461.0,"Position":174.513885,"HyperDash":false},{"StartTime":88535.0,"Position":126.308319,"HyperDash":false},{"StartTime":88646.0,"Position":104.0,"HyperDash":false}]},{"StartTime":88980.0,"Objects":[{"StartTime":88980.0,"Position":12.0,"HyperDash":false}]},{"StartTime":89313.0,"Objects":[{"StartTime":89313.0,"Position":196.0,"HyperDash":false}]},{"StartTime":89647.0,"Objects":[{"StartTime":89647.0,"Position":52.0,"HyperDash":false}]},{"StartTime":89980.0,"Objects":[{"StartTime":89980.0,"Position":244.0,"HyperDash":false},{"StartTime":90063.0,"Position":262.898438,"HyperDash":false},{"StartTime":90146.0,"Position":310.591949,"HyperDash":false},{"StartTime":90229.0,"Position":298.8366,"HyperDash":false},{"StartTime":90313.0,"Position":341.672577,"HyperDash":false},{"StartTime":90387.0,"Position":379.917847,"HyperDash":false},{"StartTime":90461.0,"Position":364.344666,"HyperDash":false},{"StartTime":90535.0,"Position":383.885345,"HyperDash":false},{"StartTime":90646.0,"Position":425.976227,"HyperDash":false}]},{"StartTime":90980.0,"Objects":[{"StartTime":90980.0,"Position":388.0,"HyperDash":false}]},{"StartTime":91313.0,"Objects":[{"StartTime":91313.0,"Position":312.0,"HyperDash":false},{"StartTime":91396.0,"Position":299.122223,"HyperDash":false},{"StartTime":91479.0,"Position":274.510773,"HyperDash":false},{"StartTime":91562.0,"Position":253.377548,"HyperDash":false},{"StartTime":91646.0,"Position":214.587158,"HyperDash":false},{"StartTime":91720.0,"Position":180.224533,"HyperDash":false},{"StartTime":91794.0,"Position":170.445953,"HyperDash":false},{"StartTime":91868.0,"Position":168.25264,"HyperDash":false},{"StartTime":91979.0,"Position":127.528435,"HyperDash":false}]},{"StartTime":92313.0,"Objects":[{"StartTime":92313.0,"Position":88.0,"HyperDash":false},{"StartTime":92387.0,"Position":105.606987,"HyperDash":false},{"StartTime":92461.0,"Position":128.524353,"HyperDash":false},{"StartTime":92535.0,"Position":143.583023,"HyperDash":false},{"StartTime":92646.0,"Position":182.5748,"HyperDash":false}]},{"StartTime":92980.0,"Objects":[{"StartTime":92980.0,"Position":292.0,"HyperDash":false},{"StartTime":93063.0,"Position":310.7525,"HyperDash":false},{"StartTime":93146.0,"Position":297.521576,"HyperDash":false},{"StartTime":93211.0,"Position":304.3826,"HyperDash":false},{"StartTime":93313.0,"Position":292.0,"HyperDash":false}]},{"StartTime":93647.0,"Objects":[{"StartTime":93647.0,"Position":260.0,"HyperDash":false}]},{"StartTime":93980.0,"Objects":[{"StartTime":93980.0,"Position":392.0,"HyperDash":false}]},{"StartTime":94313.0,"Objects":[{"StartTime":94313.0,"Position":424.0,"HyperDash":false}]},{"StartTime":94647.0,"Objects":[{"StartTime":94647.0,"Position":216.0,"HyperDash":false}]},{"StartTime":94980.0,"Objects":[{"StartTime":94980.0,"Position":200.0,"HyperDash":false},{"StartTime":95063.0,"Position":195.7525,"HyperDash":false},{"StartTime":95146.0,"Position":205.521576,"HyperDash":false},{"StartTime":95211.0,"Position":219.382584,"HyperDash":false},{"StartTime":95313.0,"Position":200.0,"HyperDash":false}]},{"StartTime":95647.0,"Objects":[{"StartTime":95647.0,"Position":80.0,"HyperDash":false}]},{"StartTime":95980.0,"Objects":[{"StartTime":95980.0,"Position":20.0,"HyperDash":false},{"StartTime":96063.0,"Position":23.3388672,"HyperDash":false},{"StartTime":96146.0,"Position":59.53566,"HyperDash":false},{"StartTime":96229.0,"Position":66.5166855,"HyperDash":false},{"StartTime":96313.0,"Position":108.143875,"HyperDash":false},{"StartTime":96387.0,"Position":118.3307,"HyperDash":false},{"StartTime":96461.0,"Position":144.318436,"HyperDash":false},{"StartTime":96535.0,"Position":175.625229,"HyperDash":false},{"StartTime":96646.0,"Position":203.7997,"HyperDash":false}]},{"StartTime":96980.0,"Objects":[{"StartTime":96980.0,"Position":396.0,"HyperDash":false}]},{"StartTime":97313.0,"Objects":[{"StartTime":97313.0,"Position":416.0,"HyperDash":false},{"StartTime":97396.0,"Position":391.7448,"HyperDash":false},{"StartTime":97479.0,"Position":402.383942,"HyperDash":false},{"StartTime":97562.0,"Position":373.653778,"HyperDash":false},{"StartTime":97646.0,"Position":341.410828,"HyperDash":false},{"StartTime":97720.0,"Position":351.982941,"HyperDash":false},{"StartTime":97794.0,"Position":395.896729,"HyperDash":false},{"StartTime":97868.0,"Position":388.3252,"HyperDash":false},{"StartTime":97979.0,"Position":416.0,"HyperDash":false}]},{"StartTime":98146.0,"Objects":[{"StartTime":98146.0,"Position":127.0,"HyperDash":false},{"StartTime":98224.0,"Position":161.0,"HyperDash":false},{"StartTime":98302.0,"Position":332.0,"HyperDash":false},{"StartTime":98380.0,"Position":356.0,"HyperDash":false},{"StartTime":98458.0,"Position":362.0,"HyperDash":false},{"StartTime":98536.0,"Position":347.0,"HyperDash":false},{"StartTime":98614.0,"Position":252.0,"HyperDash":false},{"StartTime":98692.0,"Position":477.0,"HyperDash":false},{"StartTime":98771.0,"Position":358.0,"HyperDash":false},{"StartTime":98849.0,"Position":17.0,"HyperDash":false},{"StartTime":98927.0,"Position":399.0,"HyperDash":false},{"StartTime":99005.0,"Position":280.0,"HyperDash":false},{"StartTime":99083.0,"Position":304.0,"HyperDash":false},{"StartTime":99161.0,"Position":221.0,"HyperDash":false},{"StartTime":99239.0,"Position":407.0,"HyperDash":false},{"StartTime":99317.0,"Position":287.0,"HyperDash":false},{"StartTime":99396.0,"Position":135.0,"HyperDash":false},{"StartTime":99474.0,"Position":437.0,"HyperDash":false},{"StartTime":99552.0,"Position":289.0,"HyperDash":false},{"StartTime":99630.0,"Position":464.0,"HyperDash":false},{"StartTime":99708.0,"Position":36.0,"HyperDash":false},{"StartTime":99786.0,"Position":378.0,"HyperDash":false},{"StartTime":99864.0,"Position":297.0,"HyperDash":false},{"StartTime":99942.0,"Position":418.0,"HyperDash":false},{"StartTime":100021.0,"Position":329.0,"HyperDash":false},{"StartTime":100099.0,"Position":338.0,"HyperDash":false},{"StartTime":100177.0,"Position":394.0,"HyperDash":false},{"StartTime":100255.0,"Position":40.0,"HyperDash":false},{"StartTime":100333.0,"Position":13.0,"HyperDash":false},{"StartTime":100411.0,"Position":80.0,"HyperDash":false},{"StartTime":100489.0,"Position":138.0,"HyperDash":false},{"StartTime":100567.0,"Position":311.0,"HyperDash":false},{"StartTime":100646.0,"Position":216.0,"HyperDash":false}]},{"StartTime":121313.0,"Objects":[{"StartTime":121313.0,"Position":104.0,"HyperDash":false},{"StartTime":121387.0,"Position":130.222229,"HyperDash":false},{"StartTime":121461.0,"Position":155.444443,"HyperDash":false},{"StartTime":121535.0,"Position":183.666672,"HyperDash":false},{"StartTime":121646.0,"Position":204.0,"HyperDash":false}]},{"StartTime":121980.0,"Objects":[{"StartTime":121980.0,"Position":176.0,"HyperDash":false},{"StartTime":122063.0,"Position":189.658371,"HyperDash":false},{"StartTime":122146.0,"Position":232.316742,"HyperDash":false},{"StartTime":122229.0,"Position":235.975128,"HyperDash":false},{"StartTime":122313.0,"Position":266.9065,"HyperDash":false},{"StartTime":122387.0,"Position":295.1079,"HyperDash":false},{"StartTime":122461.0,"Position":303.3094,"HyperDash":false},{"StartTime":122535.0,"Position":343.5108,"HyperDash":false},{"StartTime":122646.0,"Position":357.813,"HyperDash":false}]},{"StartTime":122980.0,"Objects":[{"StartTime":122980.0,"Position":240.0,"HyperDash":false},{"StartTime":123063.0,"Position":249.293518,"HyperDash":false},{"StartTime":123146.0,"Position":284.721375,"HyperDash":false},{"StartTime":123211.0,"Position":269.396881,"HyperDash":false},{"StartTime":123313.0,"Position":240.0,"HyperDash":false}]},{"StartTime":123647.0,"Objects":[{"StartTime":123647.0,"Position":136.0,"HyperDash":false},{"StartTime":123721.0,"Position":175.807312,"HyperDash":false},{"StartTime":123795.0,"Position":177.614624,"HyperDash":false},{"StartTime":123869.0,"Position":204.421951,"HyperDash":false},{"StartTime":123980.0,"Position":229.632919,"HyperDash":false}]},{"StartTime":124313.0,"Objects":[{"StartTime":124313.0,"Position":348.0,"HyperDash":false},{"StartTime":124387.0,"Position":311.12384,"HyperDash":false},{"StartTime":124461.0,"Position":301.247681,"HyperDash":false},{"StartTime":124535.0,"Position":296.371521,"HyperDash":false},{"StartTime":124646.0,"Position":258.557281,"HyperDash":false}]},{"StartTime":124980.0,"Objects":[{"StartTime":124980.0,"Position":132.0,"HyperDash":false}]},{"StartTime":125313.0,"Objects":[{"StartTime":125313.0,"Position":308.0,"HyperDash":false}]},{"StartTime":125647.0,"Objects":[{"StartTime":125647.0,"Position":192.0,"HyperDash":false}]},{"StartTime":125980.0,"Objects":[{"StartTime":125980.0,"Position":256.0,"HyperDash":false},{"StartTime":126063.0,"Position":236.0,"HyperDash":false},{"StartTime":126146.0,"Position":241.0,"HyperDash":false},{"StartTime":126229.0,"Position":259.0,"HyperDash":false},{"StartTime":126313.0,"Position":256.0,"HyperDash":false},{"StartTime":126387.0,"Position":266.0,"HyperDash":false},{"StartTime":126461.0,"Position":262.0,"HyperDash":false},{"StartTime":126535.0,"Position":251.0,"HyperDash":false},{"StartTime":126646.0,"Position":256.0,"HyperDash":false}]},{"StartTime":126980.0,"Objects":[{"StartTime":126980.0,"Position":456.0,"HyperDash":false}]},{"StartTime":127313.0,"Objects":[{"StartTime":127313.0,"Position":240.0,"HyperDash":false},{"StartTime":127396.0,"Position":206.706223,"HyperDash":false},{"StartTime":127479.0,"Position":204.91954,"HyperDash":false},{"StartTime":127562.0,"Position":169.054108,"HyperDash":false},{"StartTime":127646.0,"Position":141.47023,"HyperDash":false},{"StartTime":127720.0,"Position":125.911591,"HyperDash":false},{"StartTime":127794.0,"Position":94.83778,"HyperDash":false},{"StartTime":127868.0,"Position":101.478622,"HyperDash":false},{"StartTime":127979.0,"Position":61.6785927,"HyperDash":false}]},{"StartTime":128313.0,"Objects":[{"StartTime":128313.0,"Position":24.0,"HyperDash":false},{"StartTime":128387.0,"Position":48.1436577,"HyperDash":false},{"StartTime":128461.0,"Position":55.9805756,"HyperDash":false},{"StartTime":128535.0,"Position":105.202553,"HyperDash":false},{"StartTime":128646.0,"Position":122.252655,"HyperDash":false}]},{"StartTime":128980.0,"Objects":[{"StartTime":128980.0,"Position":240.0,"HyperDash":false},{"StartTime":129063.0,"Position":255.475082,"HyperDash":false},{"StartTime":129146.0,"Position":232.928925,"HyperDash":false},{"StartTime":129211.0,"Position":224.668167,"HyperDash":false},{"StartTime":129313.0,"Position":240.0,"HyperDash":false}]},{"StartTime":129647.0,"Objects":[{"StartTime":129647.0,"Position":208.0,"HyperDash":false},{"StartTime":129721.0,"Position":242.2032,"HyperDash":false},{"StartTime":129795.0,"Position":238.131622,"HyperDash":false},{"StartTime":129869.0,"Position":289.174744,"HyperDash":false},{"StartTime":129980.0,"Position":301.803345,"HyperDash":false}]},{"StartTime":130313.0,"Objects":[{"StartTime":130313.0,"Position":464.0,"HyperDash":false}]},{"StartTime":130647.0,"Objects":[{"StartTime":130647.0,"Position":312.0,"HyperDash":false}]},{"StartTime":130980.0,"Objects":[{"StartTime":130980.0,"Position":360.0,"HyperDash":false}]},{"StartTime":131313.0,"Objects":[{"StartTime":131313.0,"Position":312.0,"HyperDash":false}]},{"StartTime":131980.0,"Objects":[{"StartTime":131980.0,"Position":128.0,"HyperDash":false}]},{"StartTime":132313.0,"Objects":[{"StartTime":132313.0,"Position":108.0,"HyperDash":false}]},{"StartTime":132647.0,"Objects":[{"StartTime":132647.0,"Position":128.0,"HyperDash":false},{"StartTime":132721.0,"Position":135.994476,"HyperDash":false},{"StartTime":132795.0,"Position":180.585373,"HyperDash":false},{"StartTime":132869.0,"Position":207.755859,"HyperDash":false},{"StartTime":132980.0,"Position":224.793228,"HyperDash":false}]},{"StartTime":133147.0,"Objects":[{"StartTime":133147.0,"Position":288.0,"HyperDash":false}]},{"StartTime":133313.0,"Objects":[{"StartTime":133313.0,"Position":272.0,"HyperDash":false},{"StartTime":133387.0,"Position":276.649445,"HyperDash":false},{"StartTime":133461.0,"Position":249.773849,"HyperDash":false},{"StartTime":133535.0,"Position":218.139557,"HyperDash":false},{"StartTime":133646.0,"Position":186.0562,"HyperDash":false}]},{"StartTime":133980.0,"Objects":[{"StartTime":133980.0,"Position":68.0,"HyperDash":false}]},{"StartTime":134313.0,"Objects":[{"StartTime":134313.0,"Position":61.0,"HyperDash":false}]},{"StartTime":134647.0,"Objects":[{"StartTime":134647.0,"Position":88.0,"HyperDash":false},{"StartTime":134721.0,"Position":102.674133,"HyperDash":false},{"StartTime":134795.0,"Position":111.358536,"HyperDash":false},{"StartTime":134869.0,"Position":120.496475,"HyperDash":false},{"StartTime":134980.0,"Position":164.774765,"HyperDash":false}]},{"StartTime":135147.0,"Objects":[{"StartTime":135147.0,"Position":232.0,"HyperDash":false}]},{"StartTime":135313.0,"Objects":[{"StartTime":135313.0,"Position":244.0,"HyperDash":false},{"StartTime":135387.0,"Position":257.8205,"HyperDash":false},{"StartTime":135461.0,"Position":293.698364,"HyperDash":false},{"StartTime":135535.0,"Position":319.993317,"HyperDash":false},{"StartTime":135646.0,"Position":330.966125,"HyperDash":false}]},{"StartTime":135980.0,"Objects":[{"StartTime":135980.0,"Position":400.0,"HyperDash":false},{"StartTime":136054.0,"Position":393.3103,"HyperDash":false},{"StartTime":136128.0,"Position":410.291168,"HyperDash":false},{"StartTime":136202.0,"Position":374.1771,"HyperDash":false},{"StartTime":136313.0,"Position":363.078583,"HyperDash":false}]},{"StartTime":136647.0,"Objects":[{"StartTime":136647.0,"Position":168.0,"HyperDash":false}]},{"StartTime":136980.0,"Objects":[{"StartTime":136980.0,"Position":336.0,"HyperDash":false}]},{"StartTime":137313.0,"Objects":[{"StartTime":137313.0,"Position":240.0,"HyperDash":false},{"StartTime":137387.0,"Position":248.065033,"HyperDash":false},{"StartTime":137461.0,"Position":292.435242,"HyperDash":false},{"StartTime":137535.0,"Position":300.6758,"HyperDash":false},{"StartTime":137646.0,"Position":307.714966,"HyperDash":false}]},{"StartTime":137813.0,"Objects":[{"StartTime":137813.0,"Position":288.0,"HyperDash":false}]},{"StartTime":137980.0,"Objects":[{"StartTime":137980.0,"Position":276.0,"HyperDash":false},{"StartTime":138054.0,"Position":257.487183,"HyperDash":false},{"StartTime":138128.0,"Position":243.974365,"HyperDash":false},{"StartTime":138202.0,"Position":212.461533,"HyperDash":false},{"StartTime":138313.0,"Position":183.692291,"HyperDash":false}]},{"StartTime":138647.0,"Objects":[{"StartTime":138647.0,"Position":144.0,"HyperDash":false},{"StartTime":138721.0,"Position":108.367188,"HyperDash":false},{"StartTime":138795.0,"Position":83.73437,"HyperDash":false},{"StartTime":138869.0,"Position":69.10155,"HyperDash":false},{"StartTime":138980.0,"Position":51.1523361,"HyperDash":false}]},{"StartTime":139313.0,"Objects":[{"StartTime":139313.0,"Position":176.0,"HyperDash":false},{"StartTime":139387.0,"Position":150.773682,"HyperDash":false},{"StartTime":139461.0,"Position":141.547363,"HyperDash":false},{"StartTime":139535.0,"Position":131.321045,"HyperDash":false},{"StartTime":139646.0,"Position":111.981567,"HyperDash":false}]},{"StartTime":139980.0,"Objects":[{"StartTime":139980.0,"Position":252.0,"HyperDash":false},{"StartTime":140054.0,"Position":258.226318,"HyperDash":false},{"StartTime":140128.0,"Position":299.452637,"HyperDash":false},{"StartTime":140202.0,"Position":288.678955,"HyperDash":false},{"StartTime":140313.0,"Position":316.018433,"HyperDash":false}]},{"StartTime":140647.0,"Objects":[{"StartTime":140647.0,"Position":436.0,"HyperDash":false},{"StartTime":140730.0,"Position":419.370178,"HyperDash":false},{"StartTime":140813.0,"Position":421.2818,"HyperDash":false},{"StartTime":140896.0,"Position":393.820648,"HyperDash":false},{"StartTime":140980.0,"Position":367.0077,"HyperDash":false},{"StartTime":141054.0,"Position":362.243469,"HyperDash":false},{"StartTime":141128.0,"Position":320.487732,"HyperDash":false},{"StartTime":141202.0,"Position":303.0496,"HyperDash":false},{"StartTime":141313.0,"Position":272.1492,"HyperDash":false}]},{"StartTime":141647.0,"Objects":[{"StartTime":141647.0,"Position":152.0,"HyperDash":false},{"StartTime":141730.0,"Position":140.075073,"HyperDash":false},{"StartTime":141813.0,"Position":102.0,"HyperDash":false},{"StartTime":141878.0,"Position":106.36937,"HyperDash":false},{"StartTime":141980.0,"Position":152.0,"HyperDash":false}]},{"StartTime":142647.0,"Objects":[{"StartTime":142647.0,"Position":388.0,"HyperDash":false},{"StartTime":142730.0,"Position":394.674561,"HyperDash":false},{"StartTime":142813.0,"Position":424.3491,"HyperDash":false},{"StartTime":142896.0,"Position":448.023621,"HyperDash":false},{"StartTime":142980.0,"Position":466.935242,"HyperDash":false},{"StartTime":143054.0,"Position":446.394073,"HyperDash":false},{"StartTime":143128.0,"Position":426.8529,"HyperDash":false},{"StartTime":143202.0,"Position":417.311737,"HyperDash":false},{"StartTime":143313.0,"Position":388.0,"HyperDash":false}]},{"StartTime":143647.0,"Objects":[{"StartTime":143647.0,"Position":272.0,"HyperDash":false},{"StartTime":143721.0,"Position":277.467682,"HyperDash":false},{"StartTime":143795.0,"Position":265.935364,"HyperDash":false},{"StartTime":143869.0,"Position":247.403046,"HyperDash":false},{"StartTime":143980.0,"Position":251.604568,"HyperDash":false}]},{"StartTime":144313.0,"Objects":[{"StartTime":144313.0,"Position":250.0,"HyperDash":false}]},{"StartTime":144647.0,"Objects":[{"StartTime":144647.0,"Position":130.0,"HyperDash":false},{"StartTime":144730.0,"Position":126.174141,"HyperDash":false},{"StartTime":144813.0,"Position":102.264992,"HyperDash":false},{"StartTime":144878.0,"Position":130.009186,"HyperDash":false},{"StartTime":144980.0,"Position":130.0,"HyperDash":false}]},{"StartTime":145313.0,"Objects":[{"StartTime":145313.0,"Position":302.0,"HyperDash":false}]},{"StartTime":145647.0,"Objects":[{"StartTime":145647.0,"Position":98.0,"HyperDash":false}]},{"StartTime":145980.0,"Objects":[{"StartTime":145980.0,"Position":304.0,"HyperDash":false},{"StartTime":146045.0,"Position":329.9953,"HyperDash":false},{"StartTime":146146.0,"Position":349.957245,"HyperDash":false}]},{"StartTime":146480.0,"Objects":[{"StartTime":146480.0,"Position":400.0,"HyperDash":false},{"StartTime":146545.0,"Position":412.621429,"HyperDash":false},{"StartTime":146646.0,"Position":386.263947,"HyperDash":false}]},{"StartTime":146980.0,"Objects":[{"StartTime":146980.0,"Position":160.0,"HyperDash":false}]},{"StartTime":147313.0,"Objects":[{"StartTime":147313.0,"Position":152.0,"HyperDash":false},{"StartTime":147396.0,"Position":112.075073,"HyperDash":false},{"StartTime":147479.0,"Position":102.0,"HyperDash":false},{"StartTime":147562.0,"Position":121.774773,"HyperDash":false},{"StartTime":147646.0,"Position":152.0,"HyperDash":false},{"StartTime":147729.0,"Position":139.075073,"HyperDash":false},{"StartTime":147813.0,"Position":102.0,"HyperDash":false},{"StartTime":147878.0,"Position":112.669662,"HyperDash":false},{"StartTime":147979.0,"Position":152.0,"HyperDash":false}]},{"StartTime":148313.0,"Objects":[{"StartTime":148313.0,"Position":384.0,"HyperDash":false}]},{"StartTime":148647.0,"Objects":[{"StartTime":148647.0,"Position":360.0,"HyperDash":false},{"StartTime":148730.0,"Position":399.623871,"HyperDash":false},{"StartTime":148813.0,"Position":408.1816,"HyperDash":false},{"StartTime":148896.0,"Position":430.2179,"HyperDash":false},{"StartTime":148980.0,"Position":434.200317,"HyperDash":false},{"StartTime":149045.0,"Position":424.324982,"HyperDash":false},{"StartTime":149146.0,"Position":454.563965,"HyperDash":false}]},{"StartTime":149313.0,"Objects":[{"StartTime":149313.0,"Position":396.0,"HyperDash":false},{"StartTime":149396.0,"Position":387.613281,"HyperDash":false},{"StartTime":149479.0,"Position":406.6472,"HyperDash":false},{"StartTime":149562.0,"Position":410.1058,"HyperDash":false},{"StartTime":149646.0,"Position":424.7098,"HyperDash":false},{"StartTime":149711.0,"Position":445.476379,"HyperDash":false},{"StartTime":149813.0,"Position":427.845062,"HyperDash":false}]},{"StartTime":149980.0,"Objects":[{"StartTime":149980.0,"Position":426.0,"HyperDash":false}]},{"StartTime":150313.0,"Objects":[{"StartTime":150313.0,"Position":316.0,"HyperDash":false},{"StartTime":150396.0,"Position":342.7388,"HyperDash":false},{"StartTime":150479.0,"Position":357.6025,"HyperDash":false},{"StartTime":150544.0,"Position":351.486237,"HyperDash":false},{"StartTime":150646.0,"Position":316.0,"HyperDash":false}]},{"StartTime":150980.0,"Objects":[{"StartTime":150980.0,"Position":436.0,"HyperDash":false},{"StartTime":151054.0,"Position":413.307129,"HyperDash":false},{"StartTime":151128.0,"Position":416.6143,"HyperDash":false},{"StartTime":151202.0,"Position":385.921417,"HyperDash":false},{"StartTime":151313.0,"Position":351.882141,"HyperDash":false}]},{"StartTime":151480.0,"Objects":[{"StartTime":151480.0,"Position":296.0,"HyperDash":false},{"StartTime":151545.0,"Position":293.135956,"HyperDash":false},{"StartTime":151646.0,"Position":247.8241,"HyperDash":false}]},{"StartTime":151813.0,"Objects":[{"StartTime":151813.0,"Position":292.0,"HyperDash":false},{"StartTime":151878.0,"Position":304.3741,"HyperDash":false},{"StartTime":151979.0,"Position":287.847717,"HyperDash":false}]},{"StartTime":152147.0,"Objects":[{"StartTime":152147.0,"Position":248.0,"HyperDash":false},{"StartTime":152212.0,"Position":247.426376,"HyperDash":false},{"StartTime":152313.0,"Position":200.565826,"HyperDash":false}]},{"StartTime":152480.0,"Objects":[{"StartTime":152480.0,"Position":244.0,"HyperDash":false},{"StartTime":152545.0,"Position":240.712448,"HyperDash":false},{"StartTime":152646.0,"Position":238.157944,"HyperDash":false}]},{"StartTime":153313.0,"Objects":[{"StartTime":153313.0,"Position":276.0,"HyperDash":false}]},{"StartTime":153647.0,"Objects":[{"StartTime":153647.0,"Position":236.0,"HyperDash":false}]},{"StartTime":153980.0,"Objects":[{"StartTime":153980.0,"Position":256.0,"HyperDash":false},{"StartTime":154063.0,"Position":218.410385,"HyperDash":false},{"StartTime":154146.0,"Position":217.82077,"HyperDash":false},{"StartTime":154229.0,"Position":187.231171,"HyperDash":false},{"StartTime":154313.0,"Position":169.381439,"HyperDash":false},{"StartTime":154387.0,"Position":156.132874,"HyperDash":false},{"StartTime":154461.0,"Position":124.884308,"HyperDash":false},{"StartTime":154535.0,"Position":111.635742,"HyperDash":false},{"StartTime":154646.0,"Position":82.76289,"HyperDash":false}]},{"StartTime":154980.0,"Objects":[{"StartTime":154980.0,"Position":464.0,"HyperDash":false}]},{"StartTime":155313.0,"Objects":[{"StartTime":155313.0,"Position":140.0,"HyperDash":false},{"StartTime":155396.0,"Position":157.959641,"HyperDash":false},{"StartTime":155479.0,"Position":183.919281,"HyperDash":false},{"StartTime":155562.0,"Position":179.8789,"HyperDash":false},{"StartTime":155646.0,"Position":191.99469,"HyperDash":false},{"StartTime":155720.0,"Position":211.549072,"HyperDash":false},{"StartTime":155794.0,"Position":199.103455,"HyperDash":false},{"StartTime":155868.0,"Position":218.6578,"HyperDash":false},{"StartTime":155979.0,"Position":243.9894,"HyperDash":false}]},{"StartTime":156313.0,"Objects":[{"StartTime":156313.0,"Position":28.0,"HyperDash":false}]},{"StartTime":156647.0,"Objects":[{"StartTime":156647.0,"Position":84.0,"HyperDash":false},{"StartTime":156721.0,"Position":99.0253143,"HyperDash":false},{"StartTime":156795.0,"Position":91.05062,"HyperDash":false},{"StartTime":156869.0,"Position":100.075928,"HyperDash":false},{"StartTime":156980.0,"Position":133.613892,"HyperDash":false}]},{"StartTime":157147.0,"Objects":[{"StartTime":157147.0,"Position":180.0,"HyperDash":false}]},{"StartTime":157313.0,"Objects":[{"StartTime":157313.0,"Position":228.0,"HyperDash":false}]},{"StartTime":157647.0,"Objects":[{"StartTime":157647.0,"Position":324.0,"HyperDash":false},{"StartTime":157721.0,"Position":364.239532,"HyperDash":false},{"StartTime":157795.0,"Position":364.479065,"HyperDash":false},{"StartTime":157869.0,"Position":389.7186,"HyperDash":false},{"StartTime":157980.0,"Position":419.577881,"HyperDash":false}]},{"StartTime":158313.0,"Objects":[{"StartTime":158313.0,"Position":336.0,"HyperDash":false},{"StartTime":158387.0,"Position":312.2865,"HyperDash":false},{"StartTime":158461.0,"Position":300.573029,"HyperDash":false},{"StartTime":158535.0,"Position":297.859528,"HyperDash":false},{"StartTime":158646.0,"Position":265.2893,"HyperDash":false}]},{"StartTime":158980.0,"Objects":[{"StartTime":158980.0,"Position":80.0,"HyperDash":false}]},{"StartTime":159313.0,"Objects":[{"StartTime":159313.0,"Position":248.0,"HyperDash":false}]},{"StartTime":159646.0,"Objects":[{"StartTime":159646.0,"Position":48.0,"HyperDash":false},{"StartTime":159729.0,"Position":51.11805,"HyperDash":false},{"StartTime":159812.0,"Position":32.1886139,"HyperDash":false},{"StartTime":159877.0,"Position":24.3137436,"HyperDash":false},{"StartTime":159979.0,"Position":48.0,"HyperDash":false}]},{"StartTime":160313.0,"Objects":[{"StartTime":160313.0,"Position":200.0,"HyperDash":false}]},{"StartTime":160647.0,"Objects":[{"StartTime":160647.0,"Position":248.0,"HyperDash":false}]},{"StartTime":160980.0,"Objects":[{"StartTime":160980.0,"Position":440.0,"HyperDash":false}]},{"StartTime":161313.0,"Objects":[{"StartTime":161313.0,"Position":392.0,"HyperDash":false}]},{"StartTime":161980.0,"Objects":[{"StartTime":161980.0,"Position":120.0,"HyperDash":false}]},{"StartTime":162313.0,"Objects":[{"StartTime":162313.0,"Position":360.0,"HyperDash":false},{"StartTime":162396.0,"Position":370.924927,"HyperDash":false},{"StartTime":162479.0,"Position":394.849854,"HyperDash":false},{"StartTime":162562.0,"Position":440.77478,"HyperDash":false},{"StartTime":162646.0,"Position":460.0,"HyperDash":false},{"StartTime":162720.0,"Position":455.777771,"HyperDash":false},{"StartTime":162794.0,"Position":421.555542,"HyperDash":false},{"StartTime":162868.0,"Position":408.333344,"HyperDash":false},{"StartTime":162979.0,"Position":360.0,"HyperDash":false}]},{"StartTime":163313.0,"Objects":[{"StartTime":163313.0,"Position":48.0,"HyperDash":false}]},{"StartTime":163646.0,"Objects":[{"StartTime":163646.0,"Position":152.0,"HyperDash":false},{"StartTime":163729.0,"Position":137.075073,"HyperDash":false},{"StartTime":163812.0,"Position":112.150146,"HyperDash":false},{"StartTime":163895.0,"Position":86.22523,"HyperDash":false},{"StartTime":163979.0,"Position":52.0,"HyperDash":false},{"StartTime":164053.0,"Position":75.22222,"HyperDash":false},{"StartTime":164127.0,"Position":93.44444,"HyperDash":false},{"StartTime":164201.0,"Position":131.666656,"HyperDash":false},{"StartTime":164312.0,"Position":152.0,"HyperDash":false}]},{"StartTime":164647.0,"Objects":[{"StartTime":164647.0,"Position":256.0,"HyperDash":false}]},{"StartTime":164980.0,"Objects":[{"StartTime":164980.0,"Position":360.0,"HyperDash":false},{"StartTime":165063.0,"Position":391.924927,"HyperDash":false},{"StartTime":165146.0,"Position":415.849854,"HyperDash":false},{"StartTime":165229.0,"Position":439.77478,"HyperDash":false},{"StartTime":165313.0,"Position":460.0,"HyperDash":false},{"StartTime":165387.0,"Position":421.777771,"HyperDash":false},{"StartTime":165461.0,"Position":412.555542,"HyperDash":false},{"StartTime":165535.0,"Position":400.333344,"HyperDash":false},{"StartTime":165646.0,"Position":360.0,"HyperDash":false}]},{"StartTime":165980.0,"Objects":[{"StartTime":165980.0,"Position":48.0,"HyperDash":false}]},{"StartTime":166646.0,"Objects":[{"StartTime":166646.0,"Position":16.0,"HyperDash":false},{"StartTime":166720.0,"Position":33.9701347,"HyperDash":false},{"StartTime":166794.0,"Position":24.45197,"HyperDash":false},{"StartTime":166868.0,"Position":40.2451935,"HyperDash":false},{"StartTime":166979.0,"Position":44.51446,"HyperDash":false}]},{"StartTime":167313.0,"Objects":[{"StartTime":167313.0,"Position":116.0,"HyperDash":false},{"StartTime":167387.0,"Position":129.7839,"HyperDash":false},{"StartTime":167461.0,"Position":169.077835,"HyperDash":false},{"StartTime":167535.0,"Position":179.400436,"HyperDash":false},{"StartTime":167646.0,"Position":209.385559,"HyperDash":false}]},{"StartTime":167814.0,"Objects":[{"StartTime":167814.0,"Position":276.0,"HyperDash":false}]},{"StartTime":167980.0,"Objects":[{"StartTime":167980.0,"Position":288.0,"HyperDash":false},{"StartTime":168054.0,"Position":297.026276,"HyperDash":false},{"StartTime":168128.0,"Position":311.4158,"HyperDash":false},{"StartTime":168202.0,"Position":352.7142,"HyperDash":false},{"StartTime":168313.0,"Position":379.425873,"HyperDash":false}]},{"StartTime":168480.0,"Objects":[{"StartTime":168480.0,"Position":440.0,"HyperDash":false}]},{"StartTime":168647.0,"Objects":[{"StartTime":168647.0,"Position":428.0,"HyperDash":false},{"StartTime":168721.0,"Position":416.346558,"HyperDash":false},{"StartTime":168795.0,"Position":376.215485,"HyperDash":false},{"StartTime":168869.0,"Position":354.074921,"HyperDash":false},{"StartTime":168980.0,"Position":333.4033,"HyperDash":false}]},{"StartTime":169147.0,"Objects":[{"StartTime":169147.0,"Position":292.0,"HyperDash":false}]},{"StartTime":169313.0,"Objects":[{"StartTime":169313.0,"Position":260.0,"HyperDash":false},{"StartTime":169387.0,"Position":226.354462,"HyperDash":false},{"StartTime":169461.0,"Position":218.650589,"HyperDash":false},{"StartTime":169535.0,"Position":188.49968,"HyperDash":false},{"StartTime":169646.0,"Position":162.278046,"HyperDash":false}]},{"StartTime":169814.0,"Objects":[{"StartTime":169814.0,"Position":108.0,"HyperDash":false}]},{"StartTime":169980.0,"Objects":[{"StartTime":169980.0,"Position":88.0,"HyperDash":false},{"StartTime":170054.0,"Position":102.962883,"HyperDash":false},{"StartTime":170128.0,"Position":119.505386,"HyperDash":false},{"StartTime":170202.0,"Position":134.055634,"HyperDash":false},{"StartTime":170313.0,"Position":155.916748,"HyperDash":false}]},{"StartTime":170480.0,"Objects":[{"StartTime":170480.0,"Position":184.0,"HyperDash":false}]},{"StartTime":170647.0,"Objects":[{"StartTime":170647.0,"Position":232.0,"HyperDash":false},{"StartTime":170721.0,"Position":263.15802,"HyperDash":false},{"StartTime":170795.0,"Position":293.183655,"HyperDash":false},{"StartTime":170869.0,"Position":306.346649,"HyperDash":false},{"StartTime":170980.0,"Position":326.30188,"HyperDash":false}]},{"StartTime":171314.0,"Objects":[{"StartTime":171314.0,"Position":424.0,"HyperDash":false}]},{"StartTime":171647.0,"Objects":[{"StartTime":171647.0,"Position":404.0,"HyperDash":false}]},{"StartTime":171980.0,"Objects":[{"StartTime":171980.0,"Position":424.0,"HyperDash":false},{"StartTime":172054.0,"Position":432.217773,"HyperDash":false},{"StartTime":172128.0,"Position":396.404236,"HyperDash":false},{"StartTime":172202.0,"Position":412.493378,"HyperDash":false},{"StartTime":172313.0,"Position":371.9598,"HyperDash":false}]},{"StartTime":172480.0,"Objects":[{"StartTime":172480.0,"Position":312.0,"HyperDash":false}]},{"StartTime":172646.0,"Objects":[{"StartTime":172646.0,"Position":296.0,"HyperDash":false},{"StartTime":172729.0,"Position":266.640961,"HyperDash":false},{"StartTime":172812.0,"Position":246.785126,"HyperDash":false},{"StartTime":172895.0,"Position":204.6299,"HyperDash":false},{"StartTime":172979.0,"Position":199.5078,"HyperDash":false},{"StartTime":173053.0,"Position":230.801788,"HyperDash":false},{"StartTime":173127.0,"Position":226.161774,"HyperDash":false},{"StartTime":173201.0,"Position":272.241882,"HyperDash":false},{"StartTime":173312.0,"Position":296.0,"HyperDash":false}]},{"StartTime":173647.0,"Objects":[{"StartTime":173647.0,"Position":256.0,"HyperDash":false}]},{"StartTime":173980.0,"Objects":[{"StartTime":173980.0,"Position":164.0,"HyperDash":false},{"StartTime":174063.0,"Position":132.238632,"HyperDash":false},{"StartTime":174146.0,"Position":97.919014,"HyperDash":false},{"StartTime":174229.0,"Position":81.1318741,"HyperDash":false},{"StartTime":174313.0,"Position":74.66674,"HyperDash":false},{"StartTime":174387.0,"Position":104.74202,"HyperDash":false},{"StartTime":174461.0,"Position":110.645523,"HyperDash":false},{"StartTime":174535.0,"Position":122.876343,"HyperDash":false},{"StartTime":174646.0,"Position":164.0,"HyperDash":false}]},{"StartTime":174980.0,"Objects":[{"StartTime":174980.0,"Position":132.0,"HyperDash":false},{"StartTime":175054.0,"Position":123.056931,"HyperDash":false},{"StartTime":175128.0,"Position":102.477112,"HyperDash":false},{"StartTime":175202.0,"Position":92.91614,"HyperDash":false},{"StartTime":175313.0,"Position":105.479126,"HyperDash":false}]},{"StartTime":175646.0,"Objects":[{"StartTime":175646.0,"Position":212.0,"HyperDash":false},{"StartTime":175729.0,"Position":240.889877,"HyperDash":false},{"StartTime":175812.0,"Position":250.558151,"HyperDash":false},{"StartTime":175895.0,"Position":278.151367,"HyperDash":false},{"StartTime":175979.0,"Position":273.195679,"HyperDash":false},{"StartTime":176053.0,"Position":272.262177,"HyperDash":false},{"StartTime":176127.0,"Position":241.994537,"HyperDash":false},{"StartTime":176201.0,"Position":248.795273,"HyperDash":false},{"StartTime":176312.0,"Position":212.0,"HyperDash":false}]},{"StartTime":176647.0,"Objects":[{"StartTime":176647.0,"Position":212.0,"HyperDash":false}]},{"StartTime":177313.0,"Objects":[{"StartTime":177313.0,"Position":8.0,"HyperDash":false},{"StartTime":177387.0,"Position":10.2332268,"HyperDash":false},{"StartTime":177461.0,"Position":20.5555458,"HyperDash":false},{"StartTime":177535.0,"Position":40.06486,"HyperDash":false},{"StartTime":177646.0,"Position":79.97232,"HyperDash":false}]},{"StartTime":177980.0,"Objects":[{"StartTime":177980.0,"Position":200.0,"HyperDash":false},{"StartTime":178063.0,"Position":241.128418,"HyperDash":false},{"StartTime":178146.0,"Position":239.256821,"HyperDash":false},{"StartTime":178229.0,"Position":270.385223,"HyperDash":false},{"StartTime":178313.0,"Position":296.804352,"HyperDash":false},{"StartTime":178378.0,"Position":329.7001,"HyperDash":false},{"StartTime":178479.0,"Position":345.061157,"HyperDash":false}]},{"StartTime":178647.0,"Objects":[{"StartTime":178647.0,"Position":344.0,"HyperDash":false},{"StartTime":178730.0,"Position":319.3755,"HyperDash":false},{"StartTime":178813.0,"Position":279.750977,"HyperDash":false},{"StartTime":178896.0,"Position":284.126465,"HyperDash":false},{"StartTime":178980.0,"Position":245.205261,"HyperDash":false},{"StartTime":179045.0,"Position":217.92099,"HyperDash":false},{"StartTime":179147.0,"Position":195.659546,"HyperDash":false}]},{"StartTime":179313.0,"Objects":[{"StartTime":179313.0,"Position":196.0,"HyperDash":false},{"StartTime":179396.0,"Position":204.644592,"HyperDash":false},{"StartTime":179479.0,"Position":247.289169,"HyperDash":false},{"StartTime":179562.0,"Position":284.933777,"HyperDash":false},{"StartTime":179646.0,"Position":294.875275,"HyperDash":false},{"StartTime":179711.0,"Position":321.175262,"HyperDash":false},{"StartTime":179812.0,"Position":344.164429,"HyperDash":false}]},{"StartTime":179980.0,"Objects":[{"StartTime":179980.0,"Position":344.0,"HyperDash":false},{"StartTime":180063.0,"Position":304.223572,"HyperDash":false},{"StartTime":180146.0,"Position":297.447144,"HyperDash":false},{"StartTime":180229.0,"Position":264.670715,"HyperDash":false},{"StartTime":180313.0,"Position":244.595779,"HyperDash":false},{"StartTime":180378.0,"Position":243.192551,"HyperDash":false},{"StartTime":180480.0,"Position":194.744415,"HyperDash":false}]},{"StartTime":180647.0,"Objects":[{"StartTime":180647.0,"Position":136.0,"HyperDash":false},{"StartTime":180730.0,"Position":111.127846,"HyperDash":false},{"StartTime":180813.0,"Position":94.761,"HyperDash":false},{"StartTime":180896.0,"Position":98.9445953,"HyperDash":false},{"StartTime":180980.0,"Position":71.38005,"HyperDash":false},{"StartTime":181063.0,"Position":69.46596,"HyperDash":false},{"StartTime":181147.0,"Position":63.5731277,"HyperDash":false},{"StartTime":181230.0,"Position":82.42001,"HyperDash":false},{"StartTime":181313.0,"Position":71.28203,"HyperDash":false},{"StartTime":181387.0,"Position":81.29693,"HyperDash":false},{"StartTime":181462.0,"Position":106.020226,"HyperDash":false},{"StartTime":181536.0,"Position":117.600555,"HyperDash":false},{"StartTime":181647.0,"Position":136.0,"HyperDash":false}]},{"StartTime":181980.0,"Objects":[{"StartTime":181980.0,"Position":188.0,"HyperDash":false}]},{"StartTime":182647.0,"Objects":[{"StartTime":182647.0,"Position":168.0,"HyperDash":false}]},{"StartTime":182980.0,"Objects":[{"StartTime":182980.0,"Position":76.0,"HyperDash":false},{"StartTime":183063.0,"Position":66.09038,"HyperDash":false},{"StartTime":183146.0,"Position":30.0427513,"HyperDash":false},{"StartTime":183211.0,"Position":32.84601,"HyperDash":false},{"StartTime":183313.0,"Position":76.0,"HyperDash":false}]},{"StartTime":183647.0,"Objects":[{"StartTime":183647.0,"Position":356.0,"HyperDash":false}]},{"StartTime":183980.0,"Objects":[{"StartTime":183980.0,"Position":300.0,"HyperDash":false},{"StartTime":184063.0,"Position":315.398315,"HyperDash":false},{"StartTime":184146.0,"Position":337.263855,"HyperDash":false},{"StartTime":184229.0,"Position":376.114166,"HyperDash":false},{"StartTime":184313.0,"Position":398.933929,"HyperDash":false},{"StartTime":184387.0,"Position":361.090729,"HyperDash":false},{"StartTime":184461.0,"Position":359.967743,"HyperDash":false},{"StartTime":184535.0,"Position":348.762024,"HyperDash":false},{"StartTime":184646.0,"Position":300.0,"HyperDash":false}]},{"StartTime":184980.0,"Objects":[{"StartTime":184980.0,"Position":256.0,"HyperDash":false},{"StartTime":185063.0,"Position":211.878,"HyperDash":false},{"StartTime":185146.0,"Position":210.733841,"HyperDash":false},{"StartTime":185229.0,"Position":193.655289,"HyperDash":false},{"StartTime":185313.0,"Position":174.843628,"HyperDash":false},{"StartTime":185387.0,"Position":205.589539,"HyperDash":false},{"StartTime":185461.0,"Position":212.06926,"HyperDash":false},{"StartTime":185535.0,"Position":226.121918,"HyperDash":false},{"StartTime":185646.0,"Position":256.0,"HyperDash":false}]},{"StartTime":185980.0,"Objects":[{"StartTime":185980.0,"Position":344.0,"HyperDash":false}]},{"StartTime":186647.0,"Objects":[{"StartTime":186647.0,"Position":168.0,"HyperDash":false}]},{"StartTime":186980.0,"Objects":[{"StartTime":186980.0,"Position":316.0,"HyperDash":false}]},{"StartTime":187313.0,"Objects":[{"StartTime":187313.0,"Position":196.0,"HyperDash":false}]},{"StartTime":187980.0,"Objects":[{"StartTime":187980.0,"Position":408.0,"HyperDash":false}]},{"StartTime":188313.0,"Objects":[{"StartTime":188313.0,"Position":456.0,"HyperDash":false}]},{"StartTime":188647.0,"Objects":[{"StartTime":188647.0,"Position":320.0,"HyperDash":false}]},{"StartTime":188980.0,"Objects":[{"StartTime":188980.0,"Position":224.0,"HyperDash":false},{"StartTime":189063.0,"Position":203.261215,"HyperDash":false},{"StartTime":189146.0,"Position":182.397491,"HyperDash":false},{"StartTime":189211.0,"Position":196.513779,"HyperDash":false},{"StartTime":189313.0,"Position":224.0,"HyperDash":false}]},{"StartTime":189647.0,"Objects":[{"StartTime":189647.0,"Position":120.0,"HyperDash":false},{"StartTime":189730.0,"Position":102.325584,"HyperDash":false},{"StartTime":189813.0,"Position":70.5025253,"HyperDash":false},{"StartTime":189878.0,"Position":77.67722,"HyperDash":false},{"StartTime":189980.0,"Position":120.0,"HyperDash":false}]},{"StartTime":190313.0,"Objects":[{"StartTime":190313.0,"Position":96.0,"HyperDash":false},{"StartTime":190396.0,"Position":67.70647,"HyperDash":false},{"StartTime":190479.0,"Position":51.27864,"HyperDash":false},{"StartTime":190544.0,"Position":85.6031342,"HyperDash":false},{"StartTime":190646.0,"Position":96.0,"HyperDash":false}]},{"StartTime":190980.0,"Objects":[{"StartTime":190980.0,"Position":188.0,"HyperDash":false},{"StartTime":191054.0,"Position":204.489685,"HyperDash":false},{"StartTime":191128.0,"Position":220.740356,"HyperDash":false},{"StartTime":191202.0,"Position":229.801239,"HyperDash":false},{"StartTime":191313.0,"Position":258.899475,"HyperDash":false}]},{"StartTime":191646.0,"Objects":[{"StartTime":191646.0,"Position":320.0,"HyperDash":false},{"StartTime":191729.0,"Position":322.9096,"HyperDash":false},{"StartTime":191812.0,"Position":365.957245,"HyperDash":false},{"StartTime":191877.0,"Position":363.154,"HyperDash":false},{"StartTime":191979.0,"Position":320.0,"HyperDash":false}]},{"StartTime":192313.0,"Objects":[{"StartTime":192313.0,"Position":256.0,"HyperDash":false}]},{"StartTime":192646.0,"Objects":[{"StartTime":192646.0,"Position":376.0,"HyperDash":false}]},{"StartTime":192980.0,"Objects":[{"StartTime":192980.0,"Position":264.0,"HyperDash":false}]},{"StartTime":193313.0,"Objects":[{"StartTime":193313.0,"Position":376.0,"HyperDash":false}]},{"StartTime":193647.0,"Objects":[{"StartTime":193647.0,"Position":404.0,"HyperDash":false},{"StartTime":193730.0,"Position":427.956543,"HyperDash":false},{"StartTime":193813.0,"Position":436.009216,"HyperDash":false},{"StartTime":193878.0,"Position":419.609253,"HyperDash":false},{"StartTime":193980.0,"Position":404.0,"HyperDash":false}]},{"StartTime":194313.0,"Objects":[{"StartTime":194313.0,"Position":404.0,"HyperDash":false},{"StartTime":194387.0,"Position":411.0,"HyperDash":false},{"StartTime":194461.0,"Position":401.0,"HyperDash":false},{"StartTime":194535.0,"Position":423.0,"HyperDash":false},{"StartTime":194646.0,"Position":404.0,"HyperDash":false}]},{"StartTime":194980.0,"Objects":[{"StartTime":194980.0,"Position":344.0,"HyperDash":false},{"StartTime":195054.0,"Position":333.802856,"HyperDash":false},{"StartTime":195128.0,"Position":315.605743,"HyperDash":false},{"StartTime":195202.0,"Position":294.4086,"HyperDash":false},{"StartTime":195313.0,"Position":293.612885,"HyperDash":false}]},{"StartTime":195647.0,"Objects":[{"StartTime":195647.0,"Position":300.0,"HyperDash":false},{"StartTime":195721.0,"Position":295.574554,"HyperDash":false},{"StartTime":195795.0,"Position":256.1491,"HyperDash":false},{"StartTime":195869.0,"Position":239.723663,"HyperDash":false},{"StartTime":195980.0,"Position":208.08551,"HyperDash":false}]},{"StartTime":196313.0,"Objects":[{"StartTime":196313.0,"Position":300.0,"HyperDash":false},{"StartTime":196396.0,"Position":270.293732,"HyperDash":false},{"StartTime":196479.0,"Position":253.587433,"HyperDash":false},{"StartTime":196562.0,"Position":235.881165,"HyperDash":false},{"StartTime":196646.0,"Position":200.877213,"HyperDash":false},{"StartTime":196720.0,"Position":215.90448,"HyperDash":false},{"StartTime":196794.0,"Position":240.931778,"HyperDash":false},{"StartTime":196868.0,"Position":248.959076,"HyperDash":false},{"StartTime":196979.0,"Position":300.0,"HyperDash":false}]},{"StartTime":197313.0,"Objects":[{"StartTime":197313.0,"Position":420.0,"HyperDash":false}]},{"StartTime":197647.0,"Objects":[{"StartTime":197647.0,"Position":400.0,"HyperDash":false}]},{"StartTime":197980.0,"Objects":[{"StartTime":197980.0,"Position":300.0,"HyperDash":false},{"StartTime":198054.0,"Position":293.0362,"HyperDash":false},{"StartTime":198128.0,"Position":249.072357,"HyperDash":false},{"StartTime":198202.0,"Position":251.108551,"HyperDash":false},{"StartTime":198313.0,"Position":201.162827,"HyperDash":false}]},{"StartTime":198647.0,"Objects":[{"StartTime":198647.0,"Position":80.0,"HyperDash":false}]},{"StartTime":198980.0,"Objects":[{"StartTime":198980.0,"Position":60.0,"HyperDash":false}]},{"StartTime":199313.0,"Objects":[{"StartTime":199313.0,"Position":200.0,"HyperDash":false},{"StartTime":199387.0,"Position":224.2925,"HyperDash":false},{"StartTime":199461.0,"Position":246.710052,"HyperDash":false},{"StartTime":199535.0,"Position":263.1878,"HyperDash":false},{"StartTime":199646.0,"Position":270.120148,"HyperDash":false}]},{"StartTime":199813.0,"Objects":[{"StartTime":199813.0,"Position":296.0,"HyperDash":false}]},{"StartTime":199980.0,"Objects":[{"StartTime":199980.0,"Position":272.0,"HyperDash":false}]},{"StartTime":200313.0,"Objects":[{"StartTime":200313.0,"Position":56.0,"HyperDash":false}]},{"StartTime":200647.0,"Objects":[{"StartTime":200647.0,"Position":284.0,"HyperDash":false},{"StartTime":200721.0,"Position":297.376343,"HyperDash":false},{"StartTime":200795.0,"Position":265.3053,"HyperDash":false},{"StartTime":200869.0,"Position":263.664337,"HyperDash":false},{"StartTime":200980.0,"Position":247.205276,"HyperDash":false}]},{"StartTime":201147.0,"Objects":[{"StartTime":201147.0,"Position":196.0,"HyperDash":false}]},{"StartTime":201314.0,"Objects":[{"StartTime":201314.0,"Position":156.0,"HyperDash":false}]},{"StartTime":201647.0,"Objects":[{"StartTime":201647.0,"Position":172.0,"HyperDash":false}]},{"StartTime":201980.0,"Objects":[{"StartTime":201980.0,"Position":176.0,"HyperDash":false},{"StartTime":202054.0,"Position":140.8467,"HyperDash":false},{"StartTime":202128.0,"Position":146.271057,"HyperDash":false},{"StartTime":202202.0,"Position":100.283012,"HyperDash":false},{"StartTime":202313.0,"Position":85.75247,"HyperDash":false}]},{"StartTime":202480.0,"Objects":[{"StartTime":202480.0,"Position":48.0,"HyperDash":false}]},{"StartTime":202647.0,"Objects":[{"StartTime":202647.0,"Position":40.0,"HyperDash":false}]},{"StartTime":202980.0,"Objects":[{"StartTime":202980.0,"Position":164.0,"HyperDash":false}]},{"StartTime":203313.0,"Objects":[{"StartTime":203313.0,"Position":44.0,"HyperDash":false},{"StartTime":203396.0,"Position":54.64748,"HyperDash":false},{"StartTime":203479.0,"Position":34.0083046,"HyperDash":false},{"StartTime":203562.0,"Position":34.9480324,"HyperDash":false},{"StartTime":203646.0,"Position":41.9094124,"HyperDash":false},{"StartTime":203720.0,"Position":47.4630432,"HyperDash":false},{"StartTime":203794.0,"Position":38.06579,"HyperDash":false},{"StartTime":203868.0,"Position":41.2230949,"HyperDash":false},{"StartTime":203979.0,"Position":44.0,"HyperDash":false}]},{"StartTime":204313.0,"Objects":[{"StartTime":204313.0,"Position":152.0,"HyperDash":false},{"StartTime":204396.0,"Position":127.075073,"HyperDash":false},{"StartTime":204479.0,"Position":102.0,"HyperDash":false},{"StartTime":204544.0,"Position":132.36937,"HyperDash":false},{"StartTime":204646.0,"Position":152.0,"HyperDash":false}]},{"StartTime":204980.0,"Objects":[{"StartTime":204980.0,"Position":464.0,"HyperDash":false}]},{"StartTime":205313.0,"Objects":[{"StartTime":205313.0,"Position":272.0,"HyperDash":false},{"StartTime":205396.0,"Position":258.4456,"HyperDash":false},{"StartTime":205479.0,"Position":269.674164,"HyperDash":false},{"StartTime":205562.0,"Position":250.012192,"HyperDash":false},{"StartTime":205646.0,"Position":212.531021,"HyperDash":false},{"StartTime":205720.0,"Position":201.934311,"HyperDash":false},{"StartTime":205794.0,"Position":166.713287,"HyperDash":false},{"StartTime":205868.0,"Position":145.157013,"HyperDash":false},{"StartTime":205979.0,"Position":152.274872,"HyperDash":false}]},{"StartTime":206313.0,"Objects":[{"StartTime":206313.0,"Position":152.0,"HyperDash":false},{"StartTime":206396.0,"Position":157.0,"HyperDash":false},{"StartTime":206479.0,"Position":152.0,"HyperDash":false},{"StartTime":206544.0,"Position":163.0,"HyperDash":false},{"StartTime":206646.0,"Position":152.0,"HyperDash":false}]},{"StartTime":206980.0,"Objects":[{"StartTime":206980.0,"Position":172.0,"HyperDash":false}]},{"StartTime":207313.0,"Objects":[{"StartTime":207313.0,"Position":172.0,"HyperDash":false}]},{"StartTime":207646.0,"Objects":[{"StartTime":207646.0,"Position":152.0,"HyperDash":false},{"StartTime":207729.0,"Position":138.0,"HyperDash":false},{"StartTime":207812.0,"Position":152.0,"HyperDash":false},{"StartTime":207877.0,"Position":143.0,"HyperDash":false},{"StartTime":207979.0,"Position":152.0,"HyperDash":false}]},{"StartTime":208313.0,"Objects":[{"StartTime":208313.0,"Position":248.0,"HyperDash":false},{"StartTime":208387.0,"Position":239.45256,"HyperDash":false},{"StartTime":208461.0,"Position":243.221558,"HyperDash":false},{"StartTime":208535.0,"Position":244.170654,"HyperDash":false},{"StartTime":208646.0,"Position":250.445511,"HyperDash":false}]},{"StartTime":208980.0,"Objects":[{"StartTime":208980.0,"Position":353.0,"HyperDash":false},{"StartTime":209042.0,"Position":358.0,"HyperDash":false},{"StartTime":209105.0,"Position":447.0,"HyperDash":false},{"StartTime":209167.0,"Position":222.0,"HyperDash":false},{"StartTime":209230.0,"Position":382.0,"HyperDash":false},{"StartTime":209292.0,"Position":433.0,"HyperDash":false},{"StartTime":209355.0,"Position":450.0,"HyperDash":false},{"StartTime":209417.0,"Position":326.0,"HyperDash":false},{"StartTime":209480.0,"Position":414.0,"HyperDash":false},{"StartTime":209542.0,"Position":285.0,"HyperDash":false},{"StartTime":209605.0,"Position":336.0,"HyperDash":false},{"StartTime":209667.0,"Position":509.0,"HyperDash":false},{"StartTime":209730.0,"Position":334.0,"HyperDash":false},{"StartTime":209792.0,"Position":72.0,"HyperDash":false},{"StartTime":209855.0,"Position":425.0,"HyperDash":false},{"StartTime":209917.0,"Position":451.0,"HyperDash":false},{"StartTime":209980.0,"Position":220.0,"HyperDash":false}]},{"StartTime":210313.0,"Objects":[{"StartTime":210313.0,"Position":25.0,"HyperDash":false},{"StartTime":210375.0,"Position":77.0,"HyperDash":false},{"StartTime":210438.0,"Position":509.0,"HyperDash":false},{"StartTime":210500.0,"Position":90.0,"HyperDash":false},{"StartTime":210563.0,"Position":118.0,"HyperDash":false},{"StartTime":210625.0,"Position":58.0,"HyperDash":false},{"StartTime":210688.0,"Position":12.0,"HyperDash":false},{"StartTime":210750.0,"Position":215.0,"HyperDash":false},{"StartTime":210813.0,"Position":487.0,"HyperDash":false},{"StartTime":210875.0,"Position":446.0,"HyperDash":false},{"StartTime":210938.0,"Position":491.0,"HyperDash":false},{"StartTime":211000.0,"Position":459.0,"HyperDash":false},{"StartTime":211063.0,"Position":37.0,"HyperDash":false},{"StartTime":211125.0,"Position":291.0,"HyperDash":false},{"StartTime":211188.0,"Position":315.0,"HyperDash":false},{"StartTime":211250.0,"Position":35.0,"HyperDash":false},{"StartTime":211313.0,"Position":208.0,"HyperDash":false}]},{"StartTime":211980.0,"Objects":[{"StartTime":211980.0,"Position":440.0,"HyperDash":false},{"StartTime":212054.0,"Position":437.20932,"HyperDash":false},{"StartTime":212128.0,"Position":384.41864,"HyperDash":false},{"StartTime":212202.0,"Position":361.62793,"HyperDash":false},{"StartTime":212313.0,"Position":341.941925,"HyperDash":false}]},{"StartTime":212647.0,"Objects":[{"StartTime":212647.0,"Position":324.0,"HyperDash":false},{"StartTime":212730.0,"Position":307.11853,"HyperDash":false},{"StartTime":212813.0,"Position":283.23703,"HyperDash":false},{"StartTime":212896.0,"Position":247.35556,"HyperDash":false},{"StartTime":212980.0,"Position":236.210449,"HyperDash":false},{"StartTime":213054.0,"Position":224.70166,"HyperDash":false},{"StartTime":213128.0,"Position":185.192871,"HyperDash":false},{"StartTime":213202.0,"Position":194.684082,"HyperDash":false},{"StartTime":213313.0,"Position":148.420883,"HyperDash":false}]},{"StartTime":213647.0,"Objects":[{"StartTime":213647.0,"Position":12.0,"HyperDash":false}]},{"StartTime":213980.0,"Objects":[{"StartTime":213980.0,"Position":192.0,"HyperDash":false}]},{"StartTime":214313.0,"Objects":[{"StartTime":214313.0,"Position":312.0,"HyperDash":false}]},{"StartTime":214647.0,"Objects":[{"StartTime":214647.0,"Position":424.0,"HyperDash":false}]},{"StartTime":214980.0,"Objects":[{"StartTime":214980.0,"Position":472.0,"HyperDash":false},{"StartTime":215063.0,"Position":469.524933,"HyperDash":false},{"StartTime":215146.0,"Position":479.071075,"HyperDash":false},{"StartTime":215211.0,"Position":494.331818,"HyperDash":false},{"StartTime":215313.0,"Position":472.0,"HyperDash":false}]},{"StartTime":215647.0,"Objects":[{"StartTime":215647.0,"Position":352.0,"HyperDash":false},{"StartTime":215730.0,"Position":363.954834,"HyperDash":false},{"StartTime":215813.0,"Position":339.87323,"HyperDash":false},{"StartTime":215878.0,"Position":351.570984,"HyperDash":false},{"StartTime":215980.0,"Position":352.0,"HyperDash":false}]},{"StartTime":216313.0,"Objects":[{"StartTime":216313.0,"Position":256.0,"HyperDash":false}]},{"StartTime":216647.0,"Objects":[{"StartTime":216647.0,"Position":96.0,"HyperDash":false}]},{"StartTime":216980.0,"Objects":[{"StartTime":216980.0,"Position":208.0,"HyperDash":false}]},{"StartTime":217313.0,"Objects":[{"StartTime":217313.0,"Position":336.0,"HyperDash":false}]},{"StartTime":217647.0,"Objects":[{"StartTime":217647.0,"Position":360.0,"HyperDash":false},{"StartTime":217730.0,"Position":379.256866,"HyperDash":false},{"StartTime":217813.0,"Position":378.569519,"HyperDash":false},{"StartTime":217878.0,"Position":356.375916,"HyperDash":false},{"StartTime":217980.0,"Position":360.0,"HyperDash":false}]},{"StartTime":218313.0,"Objects":[{"StartTime":218313.0,"Position":248.0,"HyperDash":false},{"StartTime":218387.0,"Position":227.656219,"HyperDash":false},{"StartTime":218461.0,"Position":211.892563,"HyperDash":false},{"StartTime":218535.0,"Position":191.882538,"HyperDash":false},{"StartTime":218646.0,"Position":190.6999,"HyperDash":false}]},{"StartTime":218980.0,"Objects":[{"StartTime":218980.0,"Position":232.0,"HyperDash":false}]},{"StartTime":219313.0,"Objects":[{"StartTime":219313.0,"Position":152.0,"HyperDash":false}]},{"StartTime":219647.0,"Objects":[{"StartTime":219647.0,"Position":192.0,"HyperDash":false},{"StartTime":219721.0,"Position":214.85907,"HyperDash":false},{"StartTime":219795.0,"Position":222.038834,"HyperDash":false},{"StartTime":219869.0,"Position":223.900543,"HyperDash":false},{"StartTime":219980.0,"Position":247.507462,"HyperDash":false}]},{"StartTime":220313.0,"Objects":[{"StartTime":220313.0,"Position":344.0,"HyperDash":false},{"StartTime":220396.0,"Position":373.282257,"HyperDash":false},{"StartTime":220479.0,"Position":384.686676,"HyperDash":false},{"StartTime":220544.0,"Position":349.925171,"HyperDash":false},{"StartTime":220646.0,"Position":344.0,"HyperDash":false}]},{"StartTime":220980.0,"Objects":[{"StartTime":220980.0,"Position":320.0,"HyperDash":false},{"StartTime":221054.0,"Position":307.766663,"HyperDash":false},{"StartTime":221128.0,"Position":306.876526,"HyperDash":false},{"StartTime":221202.0,"Position":287.838531,"HyperDash":false},{"StartTime":221313.0,"Position":256.301666,"HyperDash":false}]},{"StartTime":221647.0,"Objects":[{"StartTime":221647.0,"Position":140.0,"HyperDash":false},{"StartTime":221730.0,"Position":123.227524,"HyperDash":false},{"StartTime":221813.0,"Position":90.30582,"HyperDash":false},{"StartTime":221878.0,"Position":121.556717,"HyperDash":false},{"StartTime":221980.0,"Position":140.0,"HyperDash":false}]},{"StartTime":222313.0,"Objects":[{"StartTime":222313.0,"Position":436.0,"HyperDash":false}]},{"StartTime":222647.0,"Objects":[{"StartTime":222647.0,"Position":316.0,"HyperDash":false}]},{"StartTime":222980.0,"Objects":[{"StartTime":222980.0,"Position":428.0,"HyperDash":false}]},{"StartTime":223313.0,"Objects":[{"StartTime":223313.0,"Position":252.0,"HyperDash":false}]},{"StartTime":223646.0,"Objects":[{"StartTime":223646.0,"Position":272.0,"HyperDash":false}]},{"StartTime":223980.0,"Objects":[{"StartTime":223980.0,"Position":380.0,"HyperDash":false}]},{"StartTime":224313.0,"Objects":[{"StartTime":224313.0,"Position":212.0,"HyperDash":false}]},{"StartTime":224647.0,"Objects":[{"StartTime":224647.0,"Position":192.0,"HyperDash":false}]},{"StartTime":224980.0,"Objects":[{"StartTime":224980.0,"Position":232.0,"HyperDash":false}]},{"StartTime":225313.0,"Objects":[{"StartTime":225313.0,"Position":232.0,"HyperDash":false}]},{"StartTime":225647.0,"Objects":[{"StartTime":225647.0,"Position":212.0,"HyperDash":false}]},{"StartTime":225980.0,"Objects":[{"StartTime":225980.0,"Position":212.0,"HyperDash":false},{"StartTime":226054.0,"Position":247.605728,"HyperDash":false},{"StartTime":226128.0,"Position":273.6619,"HyperDash":false},{"StartTime":226202.0,"Position":283.86673,"HyperDash":false},{"StartTime":226313.0,"Position":310.620728,"HyperDash":false}]},{"StartTime":226480.0,"Objects":[{"StartTime":226480.0,"Position":380.0,"HyperDash":false}]},{"StartTime":226647.0,"Objects":[{"StartTime":226647.0,"Position":400.0,"HyperDash":false}]},{"StartTime":226980.0,"Objects":[{"StartTime":226980.0,"Position":180.0,"HyperDash":false}]},{"StartTime":227313.0,"Objects":[{"StartTime":227313.0,"Position":372.0,"HyperDash":false},{"StartTime":227387.0,"Position":339.487122,"HyperDash":false},{"StartTime":227461.0,"Position":345.4503,"HyperDash":false},{"StartTime":227535.0,"Position":299.24823,"HyperDash":false},{"StartTime":227646.0,"Position":273.555176,"HyperDash":false}]},{"StartTime":227813.0,"Objects":[{"StartTime":227813.0,"Position":204.0,"HyperDash":false}]},{"StartTime":227980.0,"Objects":[{"StartTime":227980.0,"Position":212.0,"HyperDash":false}]},{"StartTime":228313.0,"Objects":[{"StartTime":228313.0,"Position":300.0,"HyperDash":false}]},{"StartTime":228647.0,"Objects":[{"StartTime":228647.0,"Position":212.0,"HyperDash":false}]},{"StartTime":228980.0,"Objects":[{"StartTime":228980.0,"Position":60.0,"HyperDash":false}]},{"StartTime":229147.0,"Objects":[{"StartTime":229147.0,"Position":136.0,"HyperDash":false}]},{"StartTime":229313.0,"Objects":[{"StartTime":229313.0,"Position":136.0,"HyperDash":false},{"StartTime":229396.0,"Position":126.907516,"HyperDash":false},{"StartTime":229479.0,"Position":112.738968,"HyperDash":false},{"StartTime":229562.0,"Position":135.404449,"HyperDash":false},{"StartTime":229646.0,"Position":130.813385,"HyperDash":false},{"StartTime":229720.0,"Position":122.399216,"HyperDash":false},{"StartTime":229794.0,"Position":152.142029,"HyperDash":false},{"StartTime":229868.0,"Position":137.941391,"HyperDash":false},{"StartTime":229979.0,"Position":150.917847,"HyperDash":false}]},{"StartTime":230313.0,"Objects":[{"StartTime":230313.0,"Position":352.0,"HyperDash":false}]},{"StartTime":230647.0,"Objects":[{"StartTime":230647.0,"Position":352.0,"HyperDash":false},{"StartTime":230730.0,"Position":366.5288,"HyperDash":false},{"StartTime":230813.0,"Position":373.811279,"HyperDash":false},{"StartTime":230896.0,"Position":365.95578,"HyperDash":false},{"StartTime":230980.0,"Position":365.109131,"HyperDash":false},{"StartTime":231054.0,"Position":343.7144,"HyperDash":false},{"StartTime":231128.0,"Position":374.024841,"HyperDash":false},{"StartTime":231202.0,"Position":338.171265,"HyperDash":false},{"StartTime":231313.0,"Position":349.468353,"HyperDash":false}]},{"StartTime":231647.0,"Objects":[{"StartTime":231647.0,"Position":236.0,"HyperDash":false},{"StartTime":231730.0,"Position":222.198776,"HyperDash":false},{"StartTime":231813.0,"Position":186.248138,"HyperDash":false},{"StartTime":231878.0,"Position":214.5214,"HyperDash":false},{"StartTime":231980.0,"Position":236.0,"HyperDash":false}]},{"StartTime":232313.0,"Objects":[{"StartTime":232313.0,"Position":316.0,"HyperDash":false}]},{"StartTime":232647.0,"Objects":[{"StartTime":232647.0,"Position":156.0,"HyperDash":false}]},{"StartTime":233313.0,"Objects":[{"StartTime":233313.0,"Position":256.0,"HyperDash":false},{"StartTime":233387.0,"Position":231.421722,"HyperDash":false},{"StartTime":233461.0,"Position":222.304459,"HyperDash":false},{"StartTime":233535.0,"Position":195.48584,"HyperDash":false},{"StartTime":233646.0,"Position":174.843628,"HyperDash":false}]},{"StartTime":233980.0,"Objects":[{"StartTime":233980.0,"Position":192.0,"HyperDash":false},{"StartTime":234063.0,"Position":220.6892,"HyperDash":false},{"StartTime":234146.0,"Position":257.786133,"HyperDash":false},{"StartTime":234229.0,"Position":260.765076,"HyperDash":false},{"StartTime":234313.0,"Position":285.29007,"HyperDash":false},{"StartTime":234396.0,"Position":317.35672,"HyperDash":false},{"StartTime":234479.0,"Position":321.969574,"HyperDash":false},{"StartTime":234562.0,"Position":349.117,"HyperDash":false},{"StartTime":234646.0,"Position":347.1605,"HyperDash":false},{"StartTime":234729.0,"Position":345.428131,"HyperDash":false},{"StartTime":234813.0,"Position":305.1539,"HyperDash":false},{"StartTime":234896.0,"Position":317.5711,"HyperDash":false},{"StartTime":234980.0,"Position":285.290039,"HyperDash":false},{"StartTime":235054.0,"Position":254.43042,"HyperDash":false},{"StartTime":235128.0,"Position":258.165863,"HyperDash":false},{"StartTime":235202.0,"Position":239.908249,"HyperDash":false},{"StartTime":235313.0,"Position":192.0,"HyperDash":false}]},{"StartTime":235647.0,"Objects":[{"StartTime":235647.0,"Position":164.0,"HyperDash":false}]},{"StartTime":235980.0,"Objects":[{"StartTime":235980.0,"Position":348.0,"HyperDash":false}]},{"StartTime":236313.0,"Objects":[{"StartTime":236313.0,"Position":256.0,"HyperDash":false},{"StartTime":236396.0,"Position":252.0,"HyperDash":false},{"StartTime":236479.0,"Position":256.0,"HyperDash":false},{"StartTime":236544.0,"Position":263.0,"HyperDash":false},{"StartTime":236646.0,"Position":256.0,"HyperDash":false}]},{"StartTime":236980.0,"Objects":[{"StartTime":236980.0,"Position":256.0,"HyperDash":false},{"StartTime":237063.0,"Position":268.0,"HyperDash":false},{"StartTime":237146.0,"Position":256.0,"HyperDash":false},{"StartTime":237211.0,"Position":262.0,"HyperDash":false},{"StartTime":237313.0,"Position":256.0,"HyperDash":false}]},{"StartTime":237647.0,"Objects":[{"StartTime":237647.0,"Position":276.0,"HyperDash":false}]},{"StartTime":237980.0,"Objects":[{"StartTime":237980.0,"Position":276.0,"HyperDash":false}]},{"StartTime":238313.0,"Objects":[{"StartTime":238313.0,"Position":344.0,"HyperDash":false},{"StartTime":238387.0,"Position":349.4431,"HyperDash":false},{"StartTime":238461.0,"Position":367.88623,"HyperDash":false},{"StartTime":238535.0,"Position":402.329346,"HyperDash":false},{"StartTime":238646.0,"Position":417.994019,"HyperDash":false}]},{"StartTime":238980.0,"Objects":[{"StartTime":238980.0,"Position":224.0,"HyperDash":false}]},{"StartTime":239147.0,"Objects":[{"StartTime":239147.0,"Position":328.0,"HyperDash":false}]},{"StartTime":239313.0,"Objects":[{"StartTime":239313.0,"Position":328.0,"HyperDash":false},{"StartTime":239387.0,"Position":303.777771,"HyperDash":false},{"StartTime":239461.0,"Position":283.555542,"HyperDash":false},{"StartTime":239535.0,"Position":243.333313,"HyperDash":false},{"StartTime":239646.0,"Position":228.0,"HyperDash":false}]},{"StartTime":239980.0,"Objects":[{"StartTime":239980.0,"Position":288.0,"HyperDash":false},{"StartTime":240054.0,"Position":273.789337,"HyperDash":false},{"StartTime":240128.0,"Position":255.578659,"HyperDash":false},{"StartTime":240202.0,"Position":211.368,"HyperDash":false},{"StartTime":240313.0,"Position":192.552,"HyperDash":false}]},{"StartTime":240647.0,"Objects":[{"StartTime":240647.0,"Position":72.0,"HyperDash":false}]},{"StartTime":240980.0,"Objects":[{"StartTime":240980.0,"Position":92.0,"HyperDash":false}]},{"StartTime":241313.0,"Objects":[{"StartTime":241313.0,"Position":92.0,"HyperDash":false}]},{"StartTime":241647.0,"Objects":[{"StartTime":241647.0,"Position":52.0,"HyperDash":false}]},{"StartTime":241980.0,"Objects":[{"StartTime":241980.0,"Position":152.0,"HyperDash":false},{"StartTime":242063.0,"Position":152.083969,"HyperDash":false},{"StartTime":242146.0,"Position":194.167923,"HyperDash":false},{"StartTime":242229.0,"Position":202.251892,"HyperDash":false},{"StartTime":242313.0,"Position":216.594238,"HyperDash":false},{"StartTime":242396.0,"Position":191.57486,"HyperDash":false},{"StartTime":242479.0,"Position":179.4909,"HyperDash":false},{"StartTime":242562.0,"Position":169.406937,"HyperDash":false},{"StartTime":242646.0,"Position":152.0,"HyperDash":false},{"StartTime":242720.0,"Position":158.210739,"HyperDash":false},{"StartTime":242795.0,"Position":179.744431,"HyperDash":false},{"StartTime":242869.0,"Position":185.084351,"HyperDash":false},{"StartTime":242980.0,"Position":216.594238,"HyperDash":false}]},{"StartTime":243313.0,"Objects":[{"StartTime":243313.0,"Position":216.0,"HyperDash":false}]},{"StartTime":243980.0,"Objects":[{"StartTime":243980.0,"Position":444.0,"HyperDash":false}]},{"StartTime":244313.0,"Objects":[{"StartTime":244313.0,"Position":292.0,"HyperDash":false}]},{"StartTime":244647.0,"Objects":[{"StartTime":244647.0,"Position":204.0,"HyperDash":false}]},{"StartTime":244980.0,"Objects":[{"StartTime":244980.0,"Position":52.0,"HyperDash":false}]},{"StartTime":245147.0,"Objects":[{"StartTime":245147.0,"Position":128.0,"HyperDash":false}]},{"StartTime":245313.0,"Objects":[{"StartTime":245313.0,"Position":128.0,"HyperDash":false},{"StartTime":245387.0,"Position":95.02887,"HyperDash":false},{"StartTime":245461.0,"Position":102.54911,"HyperDash":false},{"StartTime":245535.0,"Position":83.8343353,"HyperDash":false},{"StartTime":245646.0,"Position":76.92937,"HyperDash":false}]},{"StartTime":245980.0,"Objects":[{"StartTime":245980.0,"Position":52.0,"HyperDash":false}]},{"StartTime":246313.0,"Objects":[{"StartTime":246313.0,"Position":312.0,"HyperDash":false}]},{"StartTime":246480.0,"Objects":[{"StartTime":246480.0,"Position":192.0,"HyperDash":false}]},{"StartTime":246647.0,"Objects":[{"StartTime":246647.0,"Position":192.0,"HyperDash":false},{"StartTime":246730.0,"Position":188.38472,"HyperDash":false},{"StartTime":246813.0,"Position":225.710083,"HyperDash":false},{"StartTime":246896.0,"Position":227.818253,"HyperDash":false},{"StartTime":246980.0,"Position":260.7363,"HyperDash":false},{"StartTime":247054.0,"Position":259.404358,"HyperDash":false},{"StartTime":247128.0,"Position":316.934875,"HyperDash":false},{"StartTime":247202.0,"Position":301.161316,"HyperDash":false},{"StartTime":247313.0,"Position":350.4887,"HyperDash":false}]},{"StartTime":247646.0,"Objects":[{"StartTime":247646.0,"Position":436.0,"HyperDash":false}]},{"StartTime":247813.0,"Objects":[{"StartTime":247813.0,"Position":368.0,"HyperDash":false}]},{"StartTime":247980.0,"Objects":[{"StartTime":247980.0,"Position":402.0,"HyperDash":false},{"StartTime":248054.0,"Position":427.9642,"HyperDash":false},{"StartTime":248128.0,"Position":455.292267,"HyperDash":false},{"StartTime":248202.0,"Position":467.624146,"HyperDash":false},{"StartTime":248313.0,"Position":467.800751,"HyperDash":false}]},{"StartTime":248647.0,"Objects":[{"StartTime":248647.0,"Position":230.0,"HyperDash":false}]},{"StartTime":248980.0,"Objects":[{"StartTime":248980.0,"Position":467.0,"HyperDash":false},{"StartTime":249054.0,"Position":448.114563,"HyperDash":false},{"StartTime":249128.0,"Position":449.648,"HyperDash":false},{"StartTime":249202.0,"Position":452.133575,"HyperDash":false},{"StartTime":249313.0,"Position":426.641052,"HyperDash":false}]},{"StartTime":249647.0,"Objects":[{"StartTime":249647.0,"Position":205.0,"HyperDash":false}]},{"StartTime":249813.0,"Objects":[{"StartTime":249813.0,"Position":307.0,"HyperDash":false}]},{"StartTime":249980.0,"Objects":[{"StartTime":249980.0,"Position":200.0,"HyperDash":false}]},{"StartTime":250313.0,"Objects":[{"StartTime":250313.0,"Position":360.0,"HyperDash":false}]},{"StartTime":250647.0,"Objects":[{"StartTime":250647.0,"Position":200.0,"HyperDash":false}]},{"StartTime":250980.0,"Objects":[{"StartTime":250980.0,"Position":320.0,"HyperDash":false}]},{"StartTime":251313.0,"Objects":[{"StartTime":251313.0,"Position":240.0,"HyperDash":false}]},{"StartTime":251647.0,"Objects":[{"StartTime":251647.0,"Position":152.0,"HyperDash":false}]},{"StartTime":251980.0,"Objects":[{"StartTime":251980.0,"Position":280.0,"HyperDash":false}]},{"StartTime":252647.0,"Objects":[{"StartTime":252647.0,"Position":232.0,"HyperDash":false}]},{"StartTime":253313.0,"Objects":[{"StartTime":253313.0,"Position":280.0,"HyperDash":false}]},{"StartTime":253980.0,"Objects":[{"StartTime":253980.0,"Position":120.0,"HyperDash":false}]},{"StartTime":254646.0,"Objects":[{"StartTime":254646.0,"Position":392.0,"HyperDash":false}]},{"StartTime":255313.0,"Objects":[{"StartTime":255313.0,"Position":120.0,"HyperDash":false}]},{"StartTime":255647.0,"Objects":[{"StartTime":255647.0,"Position":256.0,"HyperDash":false}]},{"StartTime":255813.0,"Objects":[{"StartTime":255813.0,"Position":236.0,"HyperDash":false}]},{"StartTime":255980.0,"Objects":[{"StartTime":255980.0,"Position":276.0,"HyperDash":false}]},{"StartTime":256146.0,"Objects":[{"StartTime":256146.0,"Position":496.0,"HyperDash":false},{"StartTime":256216.0,"Position":27.0,"HyperDash":false},{"StartTime":256286.0,"Position":477.0,"HyperDash":false},{"StartTime":256356.0,"Position":163.0,"HyperDash":false},{"StartTime":256427.0,"Position":260.0,"HyperDash":false},{"StartTime":256497.0,"Position":253.0,"HyperDash":false},{"StartTime":256567.0,"Position":423.0,"HyperDash":false},{"StartTime":256638.0,"Position":367.0,"HyperDash":false},{"StartTime":256708.0,"Position":146.0,"HyperDash":false},{"StartTime":256778.0,"Position":322.0,"HyperDash":false},{"StartTime":256849.0,"Position":169.0,"HyperDash":false},{"StartTime":256919.0,"Position":159.0,"HyperDash":false},{"StartTime":256989.0,"Position":388.0,"HyperDash":false},{"StartTime":257060.0,"Position":67.0,"HyperDash":false},{"StartTime":257130.0,"Position":176.0,"HyperDash":false},{"StartTime":257200.0,"Position":371.0,"HyperDash":false},{"StartTime":257271.0,"Position":365.0,"HyperDash":false},{"StartTime":257341.0,"Position":104.0,"HyperDash":false},{"StartTime":257411.0,"Position":363.0,"HyperDash":false},{"StartTime":257481.0,"Position":75.0,"HyperDash":false},{"StartTime":257552.0,"Position":158.0,"HyperDash":false},{"StartTime":257622.0,"Position":98.0,"HyperDash":false},{"StartTime":257692.0,"Position":30.0,"HyperDash":false},{"StartTime":257763.0,"Position":164.0,"HyperDash":false},{"StartTime":257833.0,"Position":341.0,"HyperDash":false},{"StartTime":257903.0,"Position":18.0,"HyperDash":false},{"StartTime":257974.0,"Position":210.0,"HyperDash":false},{"StartTime":258044.0,"Position":420.0,"HyperDash":false},{"StartTime":258114.0,"Position":447.0,"HyperDash":false},{"StartTime":258185.0,"Position":78.0,"HyperDash":false},{"StartTime":258255.0,"Position":177.0,"HyperDash":false},{"StartTime":258325.0,"Position":305.0,"HyperDash":false},{"StartTime":258396.0,"Position":400.0,"HyperDash":false},{"StartTime":258466.0,"Position":462.0,"HyperDash":false},{"StartTime":258536.0,"Position":64.0,"HyperDash":false},{"StartTime":258606.0,"Position":458.0,"HyperDash":false},{"StartTime":258677.0,"Position":380.0,"HyperDash":false},{"StartTime":258747.0,"Position":65.0,"HyperDash":false},{"StartTime":258817.0,"Position":23.0,"HyperDash":false},{"StartTime":258888.0,"Position":379.0,"HyperDash":false},{"StartTime":258958.0,"Position":44.0,"HyperDash":false},{"StartTime":259028.0,"Position":485.0,"HyperDash":false},{"StartTime":259099.0,"Position":269.0,"HyperDash":false},{"StartTime":259169.0,"Position":155.0,"HyperDash":false},{"StartTime":259239.0,"Position":324.0,"HyperDash":false},{"StartTime":259310.0,"Position":149.0,"HyperDash":false},{"StartTime":259380.0,"Position":351.0,"HyperDash":false},{"StartTime":259450.0,"Position":385.0,"HyperDash":false},{"StartTime":259521.0,"Position":338.0,"HyperDash":false},{"StartTime":259591.0,"Position":322.0,"HyperDash":false},{"StartTime":259661.0,"Position":84.0,"HyperDash":false},{"StartTime":259731.0,"Position":342.0,"HyperDash":false},{"StartTime":259802.0,"Position":395.0,"HyperDash":false},{"StartTime":259872.0,"Position":72.0,"HyperDash":false},{"StartTime":259942.0,"Position":324.0,"HyperDash":false},{"StartTime":260013.0,"Position":67.0,"HyperDash":false},{"StartTime":260083.0,"Position":371.0,"HyperDash":false},{"StartTime":260153.0,"Position":446.0,"HyperDash":false},{"StartTime":260224.0,"Position":29.0,"HyperDash":false},{"StartTime":260294.0,"Position":22.0,"HyperDash":false},{"StartTime":260364.0,"Position":432.0,"HyperDash":false},{"StartTime":260435.0,"Position":12.0,"HyperDash":false},{"StartTime":260505.0,"Position":330.0,"HyperDash":false},{"StartTime":260575.0,"Position":419.0,"HyperDash":false},{"StartTime":260646.0,"Position":278.0,"HyperDash":false}]}]} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/104973.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/104973.osu new file mode 100644 index 0000000000..6edd8229a2 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/104973.osu @@ -0,0 +1,491 @@ +osu file format v9 + +[General] +StackLeniency: 0.7 +Mode: 0 + +[Difficulty] +HPDrainRate:6 +CircleSize:4 +OverallDifficulty:6 +ApproachRate:6 +SliderMultiplier:2 +SliderTickRate:2 + +[Events] +//Background and Video events +//Break Periods +2,100846,120263 +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Sound Samples +//Background Colour Transformations +3,100,163,162,255 + +[TimingPoints] +1980,666.666666666667,4,2,2,20,1,0 +12647,-100,4,2,2,42,0,0 +39646,-100,4,2,1,22,0,0 +39813,-100,4,2,2,42,0,0 +40313,-100,4,2,1,22,0,0 +40480,-100,4,2,2,42,0,0 +57980,-100,4,2,2,47,0,1 +75313,-100,4,2,1,22,0,1 +75646,-100,4,2,2,47,0,1 +79646,-100,4,2,1,22,0,1 +79813,-100,4,2,2,47,0,1 +80313,-100,4,2,1,22,0,1 +80480,-100,4,2,2,47,0,1 +80980,-100,4,2,1,22,0,1 +81146,-100,4,2,2,47,0,1 +81646,-100,4,2,1,22,0,1 +81813,-100,4,2,2,47,0,1 +100646,-100,4,2,2,42,0,0 +148980,-100,4,2,1,22,0,0 +149146,-100,4,2,2,42,0,0 +149646,-100,4,2,1,22,0,0 +149813,-100,4,2,2,42,0,0 +167313,-100,4,2,2,47,0,1 +178313,-100,4,2,1,22,0,1 +178480,-100,4,2,2,47,0,1 +178980,-100,4,2,1,22,0,1 +179146,-100,4,2,2,47,0,1 +179646,-100,4,2,1,22,0,1 +179813,-100,4,2,2,47,0,1 +180313,-100,4,2,1,22,0,1 +180480,-100,4,2,2,47,0,1 +187980,-100,4,2,2,42,0,0 +212646,-100,4,2,2,47,0,1 +260646,-100,4,2,2,42,0,0 + +[HitObjects] +152,72,11980,1,0 +248,144,12313,1,0 +132,176,12647,2,0,B|44:112,2,100,0|0|8 +132,176,13646,1,0 +240,232,13980,2,0,B|164:296,2,100,0|0|12 +240,232,14980,1,0 +304,128,15313,6,0,B|416:184,1,100,0|0 +496,240,15980,2,0,B|466:289|384:312,1,100,8|0 +296,304,16647,2,0,B|192:296|128:192,2,200,2|12|0 +296,184,18312,5,0 +296,184,18646,1,8 +416,184,18980,2,0,B|376:64,1,100,0|0 +268,116,19646,1,0 +268,116,19980,1,12 +168,184,20313,2,0,B|80:248,2,100,0|0|2 +232,80,21313,2,0,B|128:56,2,100,8|0|2 +453,174,22647,5,12 +408,284,22980,1,0 +336,188,23313,1,2 +448,236,23647,1,0 +336,188,23980,2,0,B|336:300,2,100,8|0|2 +256,60,24980,1,0 +112,104,25313,5,12 +228,136,25647,1,0 +132,208,25979,1,2 +176,96,26313,1,0 +132,208,26646,2,0,B|252:200,2,100,8|0|2 +256,292,27647,1,0 +404,280,27980,6,0,B|460:256|476:176,1,100,12|0 +348,184,28646,1,2 +348,184,28980,1,0 +336,64,29313,2,0,B|280:72|248:152,1,100,8|0 +304,236,29979,1,2 +304,236,30313,1,0 +304,236,30646,1,12 +24,120,31313,5,2 +60,264,31646,1,0 +96,120,31979,1,8 +132,264,32313,1,0 +264,192,32647,1,2 +488,108,33313,5,12 +488,108,33647,2,0,B|432:236,1,100,0|0 +380,300,34313,2,0,B|356:348,2,50,0|0|8 +312,200,34980,2,0,B|248:208|208:168,1,100,0|2 +116,112,35646,2,0,B|60:112,2,50,0|0|12 +232,80,36313,2,0,B|292:76|340:112,1,100,0|0 +356,156,36813,2,0,B|420:156,2,50,2|0|0 +296,156,37313,1,8 +176,156,37646,2,0,B|120:156,2,50,0|2|0 +176,156,38313,1,2 +60,128,38647,5,12 +168,88,38980,1,0 +60,128,39313,2,0,B|76:216|140:264,1,150,2|0 +148,312,39980,2,0,B|224:316|296:252,1,150,8|0 +285,261,40647,1,2 +392,204,40980,2,0,B|448:192,2,50,2|2|12 +292,140,41647,2,0,B|244:108|164:100,1,100 +176,160,42147,2,0,B|176:256,1,50,2|0 +140,258,42480,2,0,B|76:258,1,50,0|8 +210,258,42980,2,0,B|266:258,2,50,0|2|0 +257,147,43647,1,2 +256,28,43980,5,4 +256,28,44313,1,0 +344,108,44647,2,0,B|464:156,1,100,2|0 +340,216,45313,2,0,B|244:320,1,100,8|0 +236,176,45980,2,0,B|196:80,1,100,2|0 +92,144,46647,2,0,B|64:192|96:244,1,100,12|0 +204,192,47313,1,2 +324,192,47647,2,0,B|380:192,2,50,0|0|8 +212,144,48313,2,0,B|180:192|220:248,1,100,0|2 +324,192,48980,1,0 +324,192,49313,1,12 +256,292,49647,6,0,B|256:340,2,50,0|0|2 +324,192,50313,1,0 +324,192,50647,1,8 +256,92,50980,2,0,B|256:28,2,50,0|0|2 +200,200,51647,2,0,B|304:200,1,100,0|12 +136,24,52647,5,6 +256,112,52980,2,0,B|368:184,2,100,0|2|0 +376,24,53980,1,6 +256,112,54313,2,0,B|144:184,2,100,0|2|0 +256,264,55313,1,6 +256,112,55647,2,0,B|256:0,2,100,0|2|0 +256,112,56647,1,6 +488,48,57313,5,12 +488,48,57647,2,0,B|485:103|448:160,1,100,0|2 +360,72,58313,2,0,B|320:104|312:176,1,100,8|8 +428,200,58980,1,0 +344,288,59313,1,2 +224,288,59647,2,0,B|208:352,2,50,2|2|12 +256,172,60313,1,0 +256,172,60647,1,2 +136,192,60980,2,0,B|64:204,2,50,2|2|8 +256,172,61647,5,0 +352,244,61980,1,2 +420,144,62313,1,8 +324,72,62647,1,12 +204,72,62980,2,0,B|132:80,2,50,2|2|0 +324,72,63647,2,0,B|372:120|324:200,1,100,0|8 +252,244,64313,1,0 +148,184,64647,1,2 +36,224,64980,2,0,B|68:344,2,100,0|12|8 +24,104,65980,6,0,B|81:72|168:144|232:88,1,200,2|8 +340,84,66980,2,0,B|404:92|444:164,1,100,0|2 +436,252,67647,2,0,B|404:292|404:292,2,50,0|0|12 +436,252,68313,1,0 +332,192,68646,6,0,B|248:120,1,100,0|8 +272,248,69313,2,0,B|176:312,1,100,8|0 +208,184,69980,2,0,B|112:112,1,100,0|8 +128,244,70647,2,0,B|40:300,1,100,12|0 +20,180,71313,5,0 +72,72,71647,2,0,B|40:24,2,50,2|2|8 +192,80,72313,1,0 +300,132,72647,1,2 +300,252,72980,1,8 +192,304,73313,1,12 +72,320,73647,2,0,B|16:368,2,50,2|2|0 +112,208,74313,5,2 +112,208,74647,2,0,B|232:96|264:216|384:72,1,300,8|2 +492,104,75980,2,0,B|428:144|477:263|428:304,1,200,12|0 +320,268,76980,2,0,B|360:156,1,100,0|8 +256,76,77646,2,0,B|256:180,1,100,0|2 +192,268,78313,2,0,B|152:156,1,100,8|12 +216,68,78980,5,0 +320,128,79313,2,0,B|392:160|424:252,1,150,2|0 +408,276,79980,2,0,B|325:276|256:356,1,150,8|0 +236,336,80647,2,0,B|180:272|92:272,1,150,2|0 +88,236,81313,2,0,B|120:152|208:116,1,150,8|0 +224,112,81980,1,2 +344,116,82313,6,0,B|408:116,2,50,2|2|8 +252,192,82980,1,8 +344,268,83313,1,2 +436,192,83647,1,2 +344,116,83980,1,12 +228,80,84313,6,0,B|228:24,2,50,2|2|0 +120,132,84980,1,8 +120,252,85313,1,8 +120,132,85647,1,0 +120,252,85980,1,2 +224,192,86313,1,0 +104,192,86647,1,12 +104,192,86980,1,0 +104,192,87313,6,0,B|312:192,2,200,2|8|2 +12,112,88980,1,0 +104,192,89313,1,12 +124,72,89647,1,2 +244,56,89980,6,0,B|355:55|444:144,1,200,2|8 +416,248,90980,1,2 +312,308,91313,2,0,B|216:308|112:228,1,200,2|12 +88,124,92313,2,0,B|102:102|160:116|192:92,1,100,2|2 +292,144,92980,2,0,B|300:216,2,50,0|0|8 +280,24,93647,1,0 +392,68,93980,1,2 +408,188,94313,1,8 +320,272,94647,1,12 +200,284,94980,6,0,B|208:212,2,50,2|2|0 +80,260,95647,1,2 +20,156,95980,2,0,B|108:76|212:140,1,200,8|0 +304,204,96980,1,8 +416,252,97313,2,0,B|392:300|336:316,2,100,12|0|6 +256,192,98146,12,4,100646 +104,104,121313,6,0,B|216:104,1,100,12|0 +176,220,121980,2,0,B|368:132,1,200,2|8 +240,120,122980,2,0,B|320:80,2,50,2|2|0 +136,180,123647,2,0,B|264:228,1,100,0|12 +348,240,124313,2,0,B|252:288,1,100,0|2 +192,184,124980,1,2 +308,160,125313,1,8 +192,132,125647,1,0 +256,32,125980,6,0,B|256:240,1,200,2|12 +356,296,126980,1,0 +240,328,127313,2,0,B|128:360|56:264,1,200,2|8 +24,156,128313,2,0,B|76:148|80:176|128:164,1,100,2|0 +240,192,128980,2,0,B|232:248,2,50,2|2|12 +208,76,129647,2,0,B|268:72|312:112,1,100,2|0 +388,188,130313,1,0 +388,188,130647,1,8 +336,296,130980,1,0 +336,296,131313,1,2 +128,176,131980,5,12 +128,176,132313,1,2 +128,176,132647,2,0,B|171:149|240:168,1,100,2|0 +264,176,133147,1,0 +272,216,133313,2,0,B|239:264|176:256,1,100,8|0 +68,232,133980,1,2 +68,232,134313,1,0 +88,112,134647,6,0,B|115:65|176:48,1,100,12|2 +204,40,135147,1,0 +244,40,135313,2,0,B|316:48|356:120,1,100,2|0 +400,184,135980,2,0,B|408:248|336:292,1,100,8|0 +252,316,136647,1,2 +252,316,136980,1,0 +240,196,137313,6,0,B|288:180|312:116,1,100,12|2 +300,88,137813,1,0 +276,56,137980,2,0,B|180:16,1,100,2|0 +144,152,138647,2,0,B|24:200,1,100,8|0 +176,252,139313,2,0,B|96:348,1,100,2|0 +252,336,139980,2,0,B|332:240,1,100,12|0 +436,252,140647,2,0,B|382:158|258:151,1,200,2|8 +152,152,141647,2,0,B|104:152,2,50,2|2|0 +388,116,142647,6,0,B|496:32,2,100,12|0|2 +272,152,143647,2,0,B|252:248,1,100,2|8 +251,249,144313,1,2 +130,250,144647,2,0,B|98:298,2,50,2|2|0 +200,152,145313,1,12 +200,152,145647,1,2 +304,92,145980,6,0,B|360:68,1,50,0|2 +400,180,146480,2,0,B|384:236,1,50,0|8 +272,192,146980,1,0 +152,192,147313,2,0,B|96:192,4,50,2|0|2|0|12 +240,272,148313,5,0 +360,296,148647,2,0,B|448:240|456:176,1,150,2|0 +396,168,149313,2,0,B|428:120|428:8,1,150,8|0 +427,23,149980,1,2 +316,68,150313,2,0,B|364:36,2,50,2|2|12 +436,76,150980,2,0,B|324:148,1,100 +296,152,151480,6,0,B|224:172,1,50,2|2 +292,208,151813,2,0,B|288:256,1,50,0|8 +248,212,152147,2,0,B|176:236,1,50,2|2 +244,268,152480,2,0,B|236:336,1,50,0|0 +256,76,153313,5,12 +256,76,153647,1,0 +256,76,153980,2,0,B|48:196,1,200,2|8 +256,76,154980,1,0 +140,44,155313,2,0,B|252:228,1,200,2|12 +140,44,156313,1,0 +84,152,156647,6,0,B|148:264,1,100,2|2 +164,264,157147,1,0 +204,272,157313,1,8 +324,268,157647,2,0,B|428:236,1,100,2|2 +336,152,158313,2,0,B|248:64,1,100,0|12 +164,148,158980,5,0 +164,148,159313,1,2 +48,120,159646,2,0,B|24:48,2,50,2|0|8 +112,224,160313,1,0 +224,272,160647,1,2 +344,248,160980,1,0 +416,152,161313,1,12 +256,336,161980,5,6 +360,272,162313,2,0,B|464:272,2,100,0|8|0 +256,216,163313,1,6 +152,152,163646,2,0,B|48:152,2,100,0|8|0 +256,96,164647,1,6 +360,40,164980,2,0,B|464:40,2,100,0|8|0 +256,96,165980,1,6 +16,80,166646,6,0,B|24:136|56:200,1,100,12|0 +116,80,167313,2,0,B|158:111|220:112,1,100,2|2 +248,112,167814,1,0 +288,112,167980,2,0,B|341:115|384:152,1,100,12|8 +412,172,168480,1,0 +428,208,168647,2,0,B|380:248|300:208,1,100,2|2 +296,208,169147,1,0 +260,192,169313,6,0,B|212:168|140:184,1,100,12|2 +124,188,169814,1,0 +88,204,169980,2,0,B|96:260|200:284,1,100,2|2 +192,284,170480,1,0 +232,288,170647,2,0,B|288:296|336:256,1,100,8|8 +424,196,171314,1,2 +424,196,171647,1,2 +424,196,171980,6,0,B|416:136|360:108,1,100,12|0 +336,100,172480,1,2 +296,88,172646,2,0,B|248:72|192:104,2,100,2|0|8 +256,204,173647,1,8 +164,124,173980,2,0,B|108:112|68:164,2,100,0|0|12 +132,240,174980,2,0,B|92:280|108:344,1,100,2|0 +212,280,175646,2,0,B|272:264|276:184,2,100,2|8|2 +212,280,176647,1,2 +8,136,177313,6,0,B|29:82|104:64,1,100,12|0 +200,64,177980,2,0,B|352:104,1,150,2|0 +344,144,178647,2,0,B|184:168,1,150,8|0 +196,208,179313,2,0,B|348:232,1,150,2|0 +344,272,179980,2,0,B|184:288,1,150,12|0 +136,276,180647,2,0,B|58:233|64:140,2,150,2|2|2 +188,168,181980,1,2 +188,168,182647,5,12 +76,124,182980,2,0,B|20:100,2,50,2|2|0 +188,168,183647,1,8 +300,212,183980,2,0,B|356:228|428:204,2,100,8|0|2 +256,324,184980,2,0,B|200:316|168:260,2,100,0|12|0 +256,324,185980,1,2 +256,84,186647,5,8 +316,188,186980,1,0 +196,188,187313,1,2 +408,300,187980,5,12 +432,184,188313,1,0 +320,228,188647,1,2 +224,300,188980,2,0,B|176:332,2,50,0|0|8 +120,240,189647,2,0,B|64:248,2,50,0|0|2 +96,120,190313,2,0,B|48:96,2,50,0|0|12 +188,40,190980,2,0,B|236:60|272:132,1,100 +320,212,191646,2,0,B|376:236,2,50,0|0|8 +316,92,192313,1,0 +316,92,192646,1,2 +320,212,192980,1,2 +320,212,193313,1,12 +404,124,193647,6,0,B|444:76,2,50,0|0|2 +404,244,194313,2,0,B|404:356,1,100,0|8 +344,216,194980,2,0,B|288:312,1,100,0|2 +300,164,195647,2,0,B|188:212,1,100,0|12 +300,96,196313,2,0,B|180:80,2,100,0|2|0 +420,116,197313,1,8 +420,116,197647,1,0 +300,96,197980,2,0,B|196:80,1,100 +80,72,198647,1,12 +80,72,198980,1,0 +200,68,199313,6,0,B|256:88|272:140,1,100,2|0 +284,172,199813,1,0 +284,212,199980,1,8 +164,224,200313,1,8 +284,212,200647,6,0,B|288:276|228:316,1,100,2|0 +212,324,201147,1,0 +176,344,201314,1,12 +164,224,201647,1,8 +176,344,201980,6,0,B|124:352|72:296,1,100,2|0 +60,280,202480,1,0 +44,244,202647,1,8 +164,224,202980,1,8 +44,244,203313,6,0,B|24:196|44:140,2,100,2|0|12 +152,192,204313,2,0,B|80:192,2,50,2|2|0 +272,192,204980,1,2 +272,192,205313,2,0,B|272:104|153:100|152:200,1,200,8|0 +152,312,206313,6,0,B|152:360,2,50,2|2|12 +152,192,206980,1,0 +152,192,207313,1,14 +152,72,207646,2,0,B|152:16,2,50,0|0|2 +248,144,208313,2,0,B|272:192|240:272,1,100,0|12 +256,192,208980,12,12,209980 +256,192,210313,12,12,211313 +440,208,211980,6,0,B|320:184,1,100,12|0 +324,68,212647,2,0,B|148:164,1,200,2|8 +80,264,213647,1,8 +192,312,213980,1,2 +312,296,214313,1,2 +424,256,214647,1,12 +472,144,214980,6,0,B|480:88,2,50,2|2|0 +352,120,215647,2,0,B|336:56,2,50,2|2|8 +296,224,216313,1,0 +176,208,216647,1,0 +152,88,216980,1,8 +272,104,217313,1,12 +360,184,217647,6,0,B|392:264,2,50,2|2|0 +248,144,218313,2,0,B|200:176|184:248,1,100,0|8 +208,344,218980,1,8 +192,224,219313,1,2 +192,224,219647,2,0,B|200:176|248:144,1,100,0|12 +344,72,220313,2,0,B|400:32,2,50,2|0|2 +320,192,220980,2,0,B|296:248|224:288,1,100,0|8 +140,296,221647,2,0,B|68:304,2,50,2|0|2 +252,248,222313,5,0 +316,144,222647,1,12 +372,248,222980,1,0 +252,248,223313,1,2 +252,248,223646,5,8 +316,144,223980,1,2 +212,80,224313,1,0 +212,80,224647,5,2 +212,176,224980,1,8 +212,176,225313,1,12 +212,176,225647,1,0 +212,296,225980,6,0,B|266:312|316:296,1,100,2|0 +348,284,226480,1,2 +380,260,226647,1,8 +280,192,226980,1,8 +372,116,227313,2,0,B|319:99|268:116,1,100,2|0 +236,128,227813,1,2 +208,156,227980,1,12 +256,268,228313,1,0 +256,268,228647,1,2 +136,284,228980,5,2 +136,284,229147,1,0 +136,284,229313,2,0,B|115:183|160:60,1,200,8|0 +256,20,230313,1,0 +352,92,230647,2,0,B|385:194|336:332,1,200,12|0 +236,336,231647,2,0,B|156:344,2,50,0|0|8 +236,336,232313,1,2 +236,336,232647,1,2 +256,96,233313,6,0,B|200:104|168:160,1,100,12|0 +192,268,233980,2,0,B|304:260|352:148,2,200,2|8|2 +164,152,235647,1,0 +256,76,235980,1,12 +256,196,236313,2,0,B|256:260,2,50,2|2|0 +256,76,236980,2,0,B|256:20,2,50,2|2|8 +256,76,237647,1,8 +256,76,237980,1,2 +344,156,238313,2,0,B|432:236,1,100,0|12 +328,304,238980,5,2 +328,304,239147,1,0 +328,304,239313,2,0,B|192:304,1,100,2|0 +288,200,239980,2,0,B|160:160,1,100,8|8 +72,152,240647,1,2 +72,272,240980,1,0 +72,152,241313,1,12 +72,272,241647,1,0 +152,184,241980,2,0,B|240:80,3,100,2|0|8|0 +216,107,243313,1,2 +444,176,243980,5,12 +368,268,244313,1,0 +248,280,244647,1,2 +128,256,244980,1,2 +128,256,245147,1,0 +128,256,245313,2,0,B|80:216|72:144,1,100,8|8 +72,52,245980,5,2 +192,72,246313,1,2 +192,72,246480,1,0 +192,72,246647,2,0,B|248:160|368:192,1,200,12|8 +402,78,247646,5,2 +402,78,247813,1,0 +402,78,247980,2,0,B|453:111|474:166,1,100,8|8 +352,187,248647,1,2 +467,153,248980,2,0,B|459:217|419:249,1,100,0|12 +312,280,249647,5,2 +256,300,249813,1,0 +200,280,249980,1,2 +280,192,250313,1,0 +280,192,250647,1,8 +320,80,250980,1,0 +280,192,251313,1,2 +196,108,251647,1,0 +280,192,251980,1,12 +256,56,252647,5,2 +256,328,253313,1,2 +120,192,253980,1,2 +392,192,254646,1,2 +256,192,255313,1,2 +256,192,255647,1,2 +256,192,255813,1,0 +256,192,255980,1,12 +256,192,256146,12,4,260646 diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/1284935-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/1284935-expected-conversion.json new file mode 100644 index 0000000000..8976f6b066 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/1284935-expected-conversion.json @@ -0,0 +1 @@ +{"Mappings":[{"StartTime":707.0,"Objects":[{"StartTime":707.0,"Position":65.0,"HyperDash":false},{"StartTime":759.0,"Position":482.0,"HyperDash":false},{"StartTime":811.0,"Position":164.0,"HyperDash":false},{"StartTime":863.0,"Position":315.0,"HyperDash":false},{"StartTime":915.0,"Position":145.0,"HyperDash":false},{"StartTime":967.0,"Position":159.0,"HyperDash":false},{"StartTime":1019.0,"Position":310.0,"HyperDash":false},{"StartTime":1071.0,"Position":441.0,"HyperDash":false},{"StartTime":1123.0,"Position":428.0,"HyperDash":false},{"StartTime":1175.0,"Position":243.0,"HyperDash":false},{"StartTime":1227.0,"Position":422.0,"HyperDash":false},{"StartTime":1280.0,"Position":481.0,"HyperDash":false},{"StartTime":1332.0,"Position":104.0,"HyperDash":false},{"StartTime":1384.0,"Position":473.0,"HyperDash":false},{"StartTime":1436.0,"Position":135.0,"HyperDash":false},{"StartTime":1488.0,"Position":360.0,"HyperDash":false},{"StartTime":1540.0,"Position":123.0,"HyperDash":false},{"StartTime":1592.0,"Position":42.0,"HyperDash":false},{"StartTime":1644.0,"Position":393.0,"HyperDash":false},{"StartTime":1696.0,"Position":75.0,"HyperDash":false},{"StartTime":1748.0,"Position":377.0,"HyperDash":false},{"StartTime":1800.0,"Position":354.0,"HyperDash":false},{"StartTime":1853.0,"Position":287.0,"HyperDash":false},{"StartTime":1905.0,"Position":361.0,"HyperDash":false},{"StartTime":1957.0,"Position":479.0,"HyperDash":false},{"StartTime":2009.0,"Position":346.0,"HyperDash":false},{"StartTime":2061.0,"Position":266.0,"HyperDash":false},{"StartTime":2113.0,"Position":400.0,"HyperDash":false},{"StartTime":2165.0,"Position":202.0,"HyperDash":false},{"StartTime":2217.0,"Position":500.0,"HyperDash":false},{"StartTime":2269.0,"Position":80.0,"HyperDash":false},{"StartTime":2321.0,"Position":399.0,"HyperDash":false},{"StartTime":2374.0,"Position":455.0,"HyperDash":false}]},{"StartTime":2707.0,"Objects":[{"StartTime":2707.0,"Position":368.0,"HyperDash":false},{"StartTime":2781.0,"Position":333.777771,"HyperDash":false},{"StartTime":2855.0,"Position":339.555542,"HyperDash":false},{"StartTime":2929.0,"Position":289.3333,"HyperDash":false},{"StartTime":3040.0,"Position":268.0,"HyperDash":false}]},{"StartTime":3207.0,"Objects":[{"StartTime":3207.0,"Position":288.0,"HyperDash":false},{"StartTime":3272.0,"Position":291.748444,"HyperDash":false},{"StartTime":3373.0,"Position":300.12677,"HyperDash":false}]},{"StartTime":3707.0,"Objects":[{"StartTime":3707.0,"Position":192.0,"HyperDash":false},{"StartTime":3790.0,"Position":154.075073,"HyperDash":false},{"StartTime":3873.0,"Position":136.150146,"HyperDash":false},{"StartTime":3956.0,"Position":109.225227,"HyperDash":false},{"StartTime":4040.0,"Position":92.0,"HyperDash":false},{"StartTime":4114.0,"Position":105.222221,"HyperDash":false},{"StartTime":4188.0,"Position":131.444443,"HyperDash":false},{"StartTime":4262.0,"Position":153.666656,"HyperDash":false},{"StartTime":4373.0,"Position":192.0,"HyperDash":false}]},{"StartTime":4707.0,"Objects":[{"StartTime":4707.0,"Position":288.0,"HyperDash":false}]},{"StartTime":5041.0,"Objects":[{"StartTime":5041.0,"Position":144.0,"HyperDash":false}]},{"StartTime":5374.0,"Objects":[{"StartTime":5374.0,"Position":304.0,"HyperDash":false},{"StartTime":5457.0,"Position":335.611359,"HyperDash":false},{"StartTime":5540.0,"Position":342.222717,"HyperDash":false},{"StartTime":5623.0,"Position":344.834076,"HyperDash":false},{"StartTime":5707.0,"Position":374.657623,"HyperDash":false},{"StartTime":5790.0,"Position":377.268982,"HyperDash":false},{"StartTime":5873.0,"Position":405.880341,"HyperDash":false},{"StartTime":5956.0,"Position":430.4917,"HyperDash":false},{"StartTime":6040.0,"Position":445.421326,"HyperDash":false},{"StartTime":6123.0,"Position":408.916077,"HyperDash":false},{"StartTime":6206.0,"Position":410.3047,"HyperDash":false},{"StartTime":6289.0,"Position":405.693359,"HyperDash":false},{"StartTime":6373.0,"Position":374.8698,"HyperDash":false},{"StartTime":6447.0,"Position":375.168121,"HyperDash":false},{"StartTime":6522.0,"Position":334.254242,"HyperDash":false},{"StartTime":6596.0,"Position":316.552521,"HyperDash":false},{"StartTime":6707.0,"Position":304.0,"HyperDash":false}]},{"StartTime":7041.0,"Objects":[{"StartTime":7041.0,"Position":208.0,"HyperDash":false}]},{"StartTime":7374.0,"Objects":[{"StartTime":7374.0,"Position":304.0,"HyperDash":false},{"StartTime":7448.0,"Position":293.1427,"HyperDash":false},{"StartTime":7522.0,"Position":328.2854,"HyperDash":false},{"StartTime":7596.0,"Position":323.4281,"HyperDash":false},{"StartTime":7707.0,"Position":318.142151,"HyperDash":false}]},{"StartTime":8041.0,"Objects":[{"StartTime":8041.0,"Position":160.0,"HyperDash":false},{"StartTime":8115.0,"Position":156.777771,"HyperDash":false},{"StartTime":8189.0,"Position":98.55556,"HyperDash":false},{"StartTime":8263.0,"Position":87.33333,"HyperDash":false},{"StartTime":8374.0,"Position":60.0,"HyperDash":false}]},{"StartTime":8541.0,"Objects":[{"StartTime":8541.0,"Position":176.0,"HyperDash":false}]},{"StartTime":8707.0,"Objects":[{"StartTime":8707.0,"Position":160.0,"HyperDash":false},{"StartTime":8790.0,"Position":189.827057,"HyperDash":false},{"StartTime":8873.0,"Position":214.480759,"HyperDash":false},{"StartTime":8956.0,"Position":199.348236,"HyperDash":false},{"StartTime":9040.0,"Position":211.43425,"HyperDash":false},{"StartTime":9114.0,"Position":182.741974,"HyperDash":false},{"StartTime":9188.0,"Position":188.031326,"HyperDash":false},{"StartTime":9262.0,"Position":150.1092,"HyperDash":false},{"StartTime":9373.0,"Position":131.819717,"HyperDash":false}]},{"StartTime":9707.0,"Objects":[{"StartTime":9707.0,"Position":320.0,"HyperDash":false}]},{"StartTime":10041.0,"Objects":[{"StartTime":10041.0,"Position":352.0,"HyperDash":false},{"StartTime":10115.0,"Position":335.777771,"HyperDash":false},{"StartTime":10189.0,"Position":320.555542,"HyperDash":false},{"StartTime":10263.0,"Position":275.3333,"HyperDash":false},{"StartTime":10374.0,"Position":252.0,"HyperDash":false}]},{"StartTime":10707.0,"Objects":[{"StartTime":10707.0,"Position":416.0,"HyperDash":false},{"StartTime":10790.0,"Position":433.640656,"HyperDash":false},{"StartTime":10873.0,"Position":472.2328,"HyperDash":false},{"StartTime":10956.0,"Position":486.15274,"HyperDash":false},{"StartTime":11040.0,"Position":482.899384,"HyperDash":false},{"StartTime":11114.0,"Position":477.456268,"HyperDash":false},{"StartTime":11188.0,"Position":474.261353,"HyperDash":false},{"StartTime":11262.0,"Position":444.9807,"HyperDash":false},{"StartTime":11373.0,"Position":418.860382,"HyperDash":false}]},{"StartTime":11874.0,"Objects":[{"StartTime":11874.0,"Position":224.0,"HyperDash":false}]},{"StartTime":12041.0,"Objects":[{"StartTime":12041.0,"Position":160.0,"HyperDash":false},{"StartTime":12124.0,"Position":129.476608,"HyperDash":false},{"StartTime":12207.0,"Position":139.62587,"HyperDash":false},{"StartTime":12290.0,"Position":110.133484,"HyperDash":false},{"StartTime":12374.0,"Position":120.566429,"HyperDash":false},{"StartTime":12439.0,"Position":147.187912,"HyperDash":false},{"StartTime":12540.0,"Position":159.8762,"HyperDash":false}]},{"StartTime":12707.0,"Objects":[{"StartTime":12707.0,"Position":288.0,"HyperDash":false}]},{"StartTime":13374.0,"Objects":[{"StartTime":13374.0,"Position":464.0,"HyperDash":false},{"StartTime":13457.0,"Position":423.1,"HyperDash":false},{"StartTime":13540.0,"Position":431.2,"HyperDash":false},{"StartTime":13623.0,"Position":392.3,"HyperDash":false},{"StartTime":13707.0,"Position":364.1,"HyperDash":false},{"StartTime":13772.0,"Position":352.6,"HyperDash":false},{"StartTime":13874.0,"Position":314.0,"HyperDash":false}]},{"StartTime":14041.0,"Objects":[{"StartTime":14041.0,"Position":240.0,"HyperDash":false},{"StartTime":14124.0,"Position":215.182037,"HyperDash":false},{"StartTime":14207.0,"Position":213.6612,"HyperDash":false},{"StartTime":14290.0,"Position":180.052521,"HyperDash":false},{"StartTime":14374.0,"Position":198.218033,"HyperDash":false},{"StartTime":14439.0,"Position":203.99968,"HyperDash":false},{"StartTime":14541.0,"Position":239.397186,"HyperDash":false}]},{"StartTime":14707.0,"Objects":[{"StartTime":14707.0,"Position":320.0,"HyperDash":false},{"StartTime":14781.0,"Position":279.777771,"HyperDash":false},{"StartTime":14855.0,"Position":271.555542,"HyperDash":false},{"StartTime":14929.0,"Position":258.3333,"HyperDash":false},{"StartTime":15040.0,"Position":220.0,"HyperDash":false}]},{"StartTime":15374.0,"Objects":[{"StartTime":15374.0,"Position":320.0,"HyperDash":false},{"StartTime":15448.0,"Position":329.8606,"HyperDash":false},{"StartTime":15522.0,"Position":335.721161,"HyperDash":false},{"StartTime":15596.0,"Position":362.581757,"HyperDash":false},{"StartTime":15707.0,"Position":359.87262,"HyperDash":false}]},{"StartTime":16041.0,"Objects":[{"StartTime":16041.0,"Position":192.0,"HyperDash":false},{"StartTime":16115.0,"Position":166.777771,"HyperDash":false},{"StartTime":16189.0,"Position":161.555557,"HyperDash":false},{"StartTime":16263.0,"Position":112.333328,"HyperDash":false},{"StartTime":16374.0,"Position":92.0,"HyperDash":false}]},{"StartTime":16541.0,"Objects":[{"StartTime":16541.0,"Position":208.0,"HyperDash":false}]},{"StartTime":16707.0,"Objects":[{"StartTime":16707.0,"Position":176.0,"HyperDash":false}]},{"StartTime":17041.0,"Objects":[{"StartTime":17041.0,"Position":336.0,"HyperDash":false}]},{"StartTime":17207.0,"Objects":[{"StartTime":17207.0,"Position":288.0,"HyperDash":false},{"StartTime":17290.0,"Position":262.868042,"HyperDash":false},{"StartTime":17373.0,"Position":233.396667,"HyperDash":false},{"StartTime":17456.0,"Position":240.435333,"HyperDash":false},{"StartTime":17540.0,"Position":242.216,"HyperDash":false},{"StartTime":17605.0,"Position":250.097885,"HyperDash":false},{"StartTime":17707.0,"Position":281.828644,"HyperDash":false}]},{"StartTime":18041.0,"Objects":[{"StartTime":18041.0,"Position":480.0,"HyperDash":false}]},{"StartTime":18374.0,"Objects":[{"StartTime":18374.0,"Position":256.0,"HyperDash":false}]},{"StartTime":18707.0,"Objects":[{"StartTime":18707.0,"Position":416.0,"HyperDash":false},{"StartTime":18790.0,"Position":398.254333,"HyperDash":false},{"StartTime":18873.0,"Position":424.508667,"HyperDash":false},{"StartTime":18956.0,"Position":427.763,"HyperDash":false},{"StartTime":19040.0,"Position":425.044525,"HyperDash":false},{"StartTime":19105.0,"Position":408.809967,"HyperDash":false},{"StartTime":19207.0,"Position":429.580353,"HyperDash":false}]},{"StartTime":19374.0,"Objects":[{"StartTime":19374.0,"Position":336.0,"HyperDash":false},{"StartTime":19448.0,"Position":294.777771,"HyperDash":false},{"StartTime":19522.0,"Position":280.555542,"HyperDash":false},{"StartTime":19596.0,"Position":278.3333,"HyperDash":false},{"StartTime":19707.0,"Position":236.0,"HyperDash":false}]},{"StartTime":20041.0,"Objects":[{"StartTime":20041.0,"Position":256.0,"HyperDash":false},{"StartTime":20124.0,"Position":272.817963,"HyperDash":false},{"StartTime":20207.0,"Position":313.3388,"HyperDash":false},{"StartTime":20290.0,"Position":317.947479,"HyperDash":false},{"StartTime":20374.0,"Position":297.781982,"HyperDash":false},{"StartTime":20439.0,"Position":266.000336,"HyperDash":false},{"StartTime":20541.0,"Position":256.6028,"HyperDash":false}]},{"StartTime":20707.0,"Objects":[{"StartTime":20707.0,"Position":196.0,"HyperDash":false},{"StartTime":20781.0,"Position":169.13942,"HyperDash":false},{"StartTime":20855.0,"Position":192.278839,"HyperDash":false},{"StartTime":20929.0,"Position":170.418243,"HyperDash":false},{"StartTime":21040.0,"Position":156.12738,"HyperDash":false}]},{"StartTime":21374.0,"Objects":[{"StartTime":21374.0,"Position":320.0,"HyperDash":false},{"StartTime":21457.0,"Position":344.0784,"HyperDash":false},{"StartTime":21540.0,"Position":350.913055,"HyperDash":false},{"StartTime":21623.0,"Position":346.822418,"HyperDash":false},{"StartTime":21707.0,"Position":357.019379,"HyperDash":false},{"StartTime":21772.0,"Position":358.883179,"HyperDash":false},{"StartTime":21873.0,"Position":327.8019,"HyperDash":false}]},{"StartTime":22041.0,"Objects":[{"StartTime":22041.0,"Position":224.0,"HyperDash":false},{"StartTime":22115.0,"Position":183.777771,"HyperDash":false},{"StartTime":22189.0,"Position":175.555557,"HyperDash":false},{"StartTime":22263.0,"Position":140.333328,"HyperDash":false},{"StartTime":22374.0,"Position":124.0,"HyperDash":false}]},{"StartTime":22541.0,"Objects":[{"StartTime":22541.0,"Position":272.0,"HyperDash":false}]},{"StartTime":22707.0,"Objects":[{"StartTime":22707.0,"Position":204.0,"HyperDash":false}]},{"StartTime":23041.0,"Objects":[{"StartTime":23041.0,"Position":96.0,"HyperDash":false}]},{"StartTime":23374.0,"Objects":[{"StartTime":23374.0,"Position":208.0,"HyperDash":false},{"StartTime":23448.0,"Position":222.1427,"HyperDash":false},{"StartTime":23522.0,"Position":195.2854,"HyperDash":false},{"StartTime":23596.0,"Position":234.428085,"HyperDash":false},{"StartTime":23707.0,"Position":222.142136,"HyperDash":false}]},{"StartTime":24041.0,"Objects":[{"StartTime":24041.0,"Position":80.0,"HyperDash":false},{"StartTime":24124.0,"Position":113.9,"HyperDash":false},{"StartTime":24207.0,"Position":129.8,"HyperDash":false},{"StartTime":24290.0,"Position":153.7,"HyperDash":false},{"StartTime":24374.0,"Position":179.9,"HyperDash":false},{"StartTime":24439.0,"Position":201.4,"HyperDash":false},{"StartTime":24541.0,"Position":230.0,"HyperDash":false}]},{"StartTime":24707.0,"Objects":[{"StartTime":24707.0,"Position":112.0,"HyperDash":false}]},{"StartTime":25041.0,"Objects":[{"StartTime":25041.0,"Position":256.0,"HyperDash":false},{"StartTime":25106.0,"Position":250.808792,"HyperDash":false},{"StartTime":25207.0,"Position":240.188614,"HyperDash":false}]},{"StartTime":25541.0,"Objects":[{"StartTime":25541.0,"Position":352.0,"HyperDash":false},{"StartTime":25606.0,"Position":340.5016,"HyperDash":false},{"StartTime":25707.0,"Position":355.834839,"HyperDash":false}]},{"StartTime":26041.0,"Objects":[{"StartTime":26041.0,"Position":192.0,"HyperDash":false},{"StartTime":26115.0,"Position":191.8573,"HyperDash":false},{"StartTime":26189.0,"Position":173.7146,"HyperDash":false},{"StartTime":26263.0,"Position":175.571915,"HyperDash":false},{"StartTime":26374.0,"Position":177.857864,"HyperDash":false}]},{"StartTime":26707.0,"Objects":[{"StartTime":26707.0,"Position":272.0,"HyperDash":false},{"StartTime":26781.0,"Position":275.222229,"HyperDash":false},{"StartTime":26855.0,"Position":318.444458,"HyperDash":false},{"StartTime":26929.0,"Position":333.6667,"HyperDash":false},{"StartTime":27040.0,"Position":372.0,"HyperDash":false}]},{"StartTime":27207.0,"Objects":[{"StartTime":27207.0,"Position":256.0,"HyperDash":false}]},{"StartTime":27374.0,"Objects":[{"StartTime":27374.0,"Position":288.0,"HyperDash":false}]},{"StartTime":27707.0,"Objects":[{"StartTime":27707.0,"Position":416.0,"HyperDash":false},{"StartTime":27772.0,"Position":401.748444,"HyperDash":false},{"StartTime":27873.0,"Position":428.12677,"HyperDash":false}]},{"StartTime":28207.0,"Objects":[{"StartTime":28207.0,"Position":288.0,"HyperDash":false},{"StartTime":28281.0,"Position":250.777771,"HyperDash":false},{"StartTime":28355.0,"Position":249.555557,"HyperDash":false},{"StartTime":28429.0,"Position":219.333328,"HyperDash":false},{"StartTime":28540.0,"Position":188.0,"HyperDash":false}]},{"StartTime":28707.0,"Objects":[{"StartTime":28707.0,"Position":256.0,"HyperDash":false},{"StartTime":28781.0,"Position":253.111572,"HyperDash":false},{"StartTime":28855.0,"Position":249.223145,"HyperDash":false},{"StartTime":28929.0,"Position":256.334747,"HyperDash":false},{"StartTime":29040.0,"Position":270.0021,"HyperDash":false}]},{"StartTime":29374.0,"Objects":[{"StartTime":29374.0,"Position":128.0,"HyperDash":false},{"StartTime":29457.0,"Position":97.70407,"HyperDash":false},{"StartTime":29540.0,"Position":72.07541,"HyperDash":false},{"StartTime":29623.0,"Position":69.19281,"HyperDash":false},{"StartTime":29707.0,"Position":64.12629,"HyperDash":false},{"StartTime":29781.0,"Position":68.7450943,"HyperDash":false},{"StartTime":29855.0,"Position":93.5549545,"HyperDash":false},{"StartTime":29929.0,"Position":84.38264,"HyperDash":false},{"StartTime":30040.0,"Position":127.072174,"HyperDash":false}]},{"StartTime":30374.0,"Objects":[{"StartTime":30374.0,"Position":320.0,"HyperDash":false}]},{"StartTime":30707.0,"Objects":[{"StartTime":30707.0,"Position":416.0,"HyperDash":false}]},{"StartTime":30874.0,"Objects":[{"StartTime":30874.0,"Position":432.0,"HyperDash":false},{"StartTime":30948.0,"Position":429.1427,"HyperDash":false},{"StartTime":31022.0,"Position":455.2854,"HyperDash":false},{"StartTime":31096.0,"Position":422.4281,"HyperDash":false},{"StartTime":31207.0,"Position":446.142151,"HyperDash":false}]},{"StartTime":31374.0,"Objects":[{"StartTime":31374.0,"Position":320.0,"HyperDash":false}]},{"StartTime":31707.0,"Objects":[{"StartTime":31707.0,"Position":240.0,"HyperDash":false},{"StartTime":31772.0,"Position":250.251556,"HyperDash":false},{"StartTime":31873.0,"Position":227.873215,"HyperDash":false}]},{"StartTime":32041.0,"Objects":[{"StartTime":32041.0,"Position":304.0,"HyperDash":false},{"StartTime":32124.0,"Position":346.659271,"HyperDash":false},{"StartTime":32207.0,"Position":333.21402,"HyperDash":false},{"StartTime":32290.0,"Position":348.822571,"HyperDash":false},{"StartTime":32374.0,"Position":369.8608,"HyperDash":false},{"StartTime":32448.0,"Position":377.38208,"HyperDash":false},{"StartTime":32522.0,"Position":368.24884,"HyperDash":false},{"StartTime":32596.0,"Position":327.2163,"HyperDash":false},{"StartTime":32707.0,"Position":302.6493,"HyperDash":false}]},{"StartTime":33041.0,"Objects":[{"StartTime":33041.0,"Position":32.0,"HyperDash":false}]},{"StartTime":33374.0,"Objects":[{"StartTime":33374.0,"Position":304.0,"HyperDash":false}]},{"StartTime":33541.0,"Objects":[{"StartTime":33541.0,"Position":368.0,"HyperDash":false},{"StartTime":33615.0,"Position":362.176758,"HyperDash":false},{"StartTime":33689.0,"Position":375.77478,"HyperDash":false},{"StartTime":33763.0,"Position":391.632355,"HyperDash":false},{"StartTime":33874.0,"Position":367.9668,"HyperDash":false}]},{"StartTime":34207.0,"Objects":[{"StartTime":34207.0,"Position":80.0,"HyperDash":false}]},{"StartTime":34374.0,"Objects":[{"StartTime":34374.0,"Position":176.0,"HyperDash":false}]},{"StartTime":34541.0,"Objects":[{"StartTime":34541.0,"Position":80.0,"HyperDash":false}]},{"StartTime":34707.0,"Objects":[{"StartTime":34707.0,"Position":200.0,"HyperDash":false}]},{"StartTime":35041.0,"Objects":[{"StartTime":35041.0,"Position":336.0,"HyperDash":false},{"StartTime":35115.0,"Position":338.1427,"HyperDash":false},{"StartTime":35189.0,"Position":342.2854,"HyperDash":false},{"StartTime":35263.0,"Position":338.4281,"HyperDash":false},{"StartTime":35374.0,"Position":350.142151,"HyperDash":false}]},{"StartTime":35707.0,"Objects":[{"StartTime":35707.0,"Position":208.0,"HyperDash":false},{"StartTime":35772.0,"Position":217.808792,"HyperDash":false},{"StartTime":35873.0,"Position":192.188614,"HyperDash":false}]},{"StartTime":36207.0,"Objects":[{"StartTime":36207.0,"Position":336.0,"HyperDash":false},{"StartTime":36272.0,"Position":351.191223,"HyperDash":false},{"StartTime":36373.0,"Position":351.8114,"HyperDash":false}]},{"StartTime":36707.0,"Objects":[{"StartTime":36707.0,"Position":208.0,"HyperDash":false},{"StartTime":36781.0,"Position":178.777771,"HyperDash":false},{"StartTime":36855.0,"Position":179.555557,"HyperDash":false},{"StartTime":36929.0,"Position":125.333328,"HyperDash":false},{"StartTime":37040.0,"Position":108.0,"HyperDash":false}]},{"StartTime":37374.0,"Objects":[{"StartTime":37374.0,"Position":416.0,"HyperDash":false}]},{"StartTime":37541.0,"Objects":[{"StartTime":37541.0,"Position":320.0,"HyperDash":false},{"StartTime":37615.0,"Position":322.379059,"HyperDash":false},{"StartTime":37689.0,"Position":309.7581,"HyperDash":false},{"StartTime":37763.0,"Position":318.137146,"HyperDash":false},{"StartTime":37874.0,"Position":335.205719,"HyperDash":false}]},{"StartTime":38041.0,"Objects":[{"StartTime":38041.0,"Position":208.0,"HyperDash":false}]},{"StartTime":38374.0,"Objects":[{"StartTime":38374.0,"Position":416.0,"HyperDash":false},{"StartTime":38439.0,"Position":410.191223,"HyperDash":false},{"StartTime":38540.0,"Position":431.8114,"HyperDash":false}]},{"StartTime":38874.0,"Objects":[{"StartTime":38874.0,"Position":288.0,"HyperDash":false},{"StartTime":38939.0,"Position":273.808777,"HyperDash":false},{"StartTime":39040.0,"Position":272.1886,"HyperDash":false}]},{"StartTime":39207.0,"Objects":[{"StartTime":39207.0,"Position":336.0,"HyperDash":false},{"StartTime":39281.0,"Position":360.222229,"HyperDash":false},{"StartTime":39355.0,"Position":369.444458,"HyperDash":false},{"StartTime":39429.0,"Position":419.6667,"HyperDash":false},{"StartTime":39540.0,"Position":436.0,"HyperDash":false}]},{"StartTime":39707.0,"Objects":[{"StartTime":39707.0,"Position":320.0,"HyperDash":false}]},{"StartTime":40041.0,"Objects":[{"StartTime":40041.0,"Position":160.0,"HyperDash":false}]},{"StartTime":40374.0,"Objects":[{"StartTime":40374.0,"Position":384.0,"HyperDash":false},{"StartTime":40448.0,"Position":396.1427,"HyperDash":false},{"StartTime":40522.0,"Position":380.2854,"HyperDash":false},{"StartTime":40596.0,"Position":408.4281,"HyperDash":false},{"StartTime":40707.0,"Position":398.142151,"HyperDash":false}]},{"StartTime":41041.0,"Objects":[{"StartTime":41041.0,"Position":112.0,"HyperDash":false}]},{"StartTime":41374.0,"Objects":[{"StartTime":41374.0,"Position":132.0,"HyperDash":false}]},{"StartTime":41541.0,"Objects":[{"StartTime":41541.0,"Position":48.0,"HyperDash":false},{"StartTime":41615.0,"Position":31.8573036,"HyperDash":false},{"StartTime":41689.0,"Position":31.7146072,"HyperDash":false},{"StartTime":41763.0,"Position":40.571907,"HyperDash":false},{"StartTime":41874.0,"Position":33.8578644,"HyperDash":false}]},{"StartTime":42041.0,"Objects":[{"StartTime":42041.0,"Position":160.0,"HyperDash":false}]},{"StartTime":42374.0,"Objects":[{"StartTime":42374.0,"Position":320.0,"HyperDash":false}]},{"StartTime":42707.0,"Objects":[{"StartTime":42707.0,"Position":96.0,"HyperDash":false},{"StartTime":42790.0,"Position":64.13681,"HyperDash":false},{"StartTime":42873.0,"Position":72.60394,"HyperDash":false},{"StartTime":42956.0,"Position":54.03947,"HyperDash":false},{"StartTime":43040.0,"Position":54.30264,"HyperDash":false},{"StartTime":43105.0,"Position":72.19569,"HyperDash":false},{"StartTime":43206.0,"Position":95.39718,"HyperDash":false}]},{"StartTime":43374.0,"Objects":[{"StartTime":43374.0,"Position":224.0,"HyperDash":false}]},{"StartTime":43707.0,"Objects":[{"StartTime":43707.0,"Position":352.0,"HyperDash":false}]},{"StartTime":44040.0,"Objects":[{"StartTime":44040.0,"Position":160.0,"HyperDash":false}]},{"StartTime":44374.0,"Objects":[{"StartTime":44374.0,"Position":304.0,"HyperDash":false},{"StartTime":44457.0,"Position":309.591553,"HyperDash":false},{"StartTime":44540.0,"Position":325.605743,"HyperDash":false},{"StartTime":44623.0,"Position":365.538,"HyperDash":false},{"StartTime":44707.0,"Position":357.421478,"HyperDash":false},{"StartTime":44781.0,"Position":336.6104,"HyperDash":false},{"StartTime":44855.0,"Position":350.104462,"HyperDash":false},{"StartTime":44929.0,"Position":333.432159,"HyperDash":false},{"StartTime":45040.0,"Position":304.669952,"HyperDash":false}]},{"StartTime":45374.0,"Objects":[{"StartTime":45374.0,"Position":136.0,"HyperDash":false},{"StartTime":45457.0,"Position":127.769325,"HyperDash":false},{"StartTime":45540.0,"Position":88.53865,"HyperDash":false},{"StartTime":45623.0,"Position":83.30798,"HyperDash":false},{"StartTime":45707.0,"Position":70.88176,"HyperDash":false},{"StartTime":45790.0,"Position":61.6510925,"HyperDash":false},{"StartTime":45873.0,"Position":38.3226547,"HyperDash":false},{"StartTime":45956.0,"Position":42.4555435,"HyperDash":false},{"StartTime":46040.0,"Position":70.88177,"HyperDash":false},{"StartTime":46114.0,"Position":83.35248,"HyperDash":false},{"StartTime":46188.0,"Position":98.8232,"HyperDash":false},{"StartTime":46262.0,"Position":107.293922,"HyperDash":false},{"StartTime":46373.0,"Position":136.0,"HyperDash":false}]},{"StartTime":46874.0,"Objects":[{"StartTime":46874.0,"Position":368.0,"HyperDash":false},{"StartTime":46957.0,"Position":368.641052,"HyperDash":false},{"StartTime":47040.0,"Position":388.2821,"HyperDash":false},{"StartTime":47123.0,"Position":392.923126,"HyperDash":false},{"StartTime":47207.0,"Position":378.596,"HyperDash":false},{"StartTime":47272.0,"Position":362.664276,"HyperDash":false},{"StartTime":47374.0,"Position":383.9099,"HyperDash":false}]},{"StartTime":47707.0,"Objects":[{"StartTime":47707.0,"Position":160.0,"HyperDash":false}]},{"StartTime":48041.0,"Objects":[{"StartTime":48041.0,"Position":144.0,"HyperDash":false},{"StartTime":48124.0,"Position":140.536209,"HyperDash":false},{"StartTime":48207.0,"Position":120.072418,"HyperDash":false},{"StartTime":48290.0,"Position":77.60862,"HyperDash":false},{"StartTime":48374.0,"Position":81.95851,"HyperDash":false},{"StartTime":48457.0,"Position":51.4947128,"HyperDash":false},{"StartTime":48541.0,"Position":50.8446045,"HyperDash":false},{"StartTime":48624.0,"Position":47.308403,"HyperDash":false},{"StartTime":48707.0,"Position":81.7722,"HyperDash":false},{"StartTime":48781.0,"Position":97.5592041,"HyperDash":false},{"StartTime":48856.0,"Position":126.5325,"HyperDash":false},{"StartTime":48930.0,"Position":134.3195,"HyperDash":false},{"StartTime":49041.0,"Position":144.0,"HyperDash":false}]},{"StartTime":49374.0,"Objects":[{"StartTime":49374.0,"Position":256.0,"HyperDash":false},{"StartTime":49457.0,"Position":275.705048,"HyperDash":false},{"StartTime":49540.0,"Position":297.414978,"HyperDash":false},{"StartTime":49623.0,"Position":295.170868,"HyperDash":false},{"StartTime":49707.0,"Position":311.122,"HyperDash":false},{"StartTime":49790.0,"Position":299.525726,"HyperDash":false},{"StartTime":49873.0,"Position":296.3256,"HyperDash":false},{"StartTime":49956.0,"Position":290.4679,"HyperDash":false},{"StartTime":50040.0,"Position":301.014038,"HyperDash":false},{"StartTime":50114.0,"Position":289.537323,"HyperDash":false},{"StartTime":50189.0,"Position":285.4608,"HyperDash":false},{"StartTime":50263.0,"Position":241.873749,"HyperDash":false},{"StartTime":50373.0,"Position":235.304214,"HyperDash":false}]},{"StartTime":50707.0,"Objects":[{"StartTime":50707.0,"Position":384.0,"HyperDash":false},{"StartTime":50790.0,"Position":399.712433,"HyperDash":false},{"StartTime":50873.0,"Position":415.424866,"HyperDash":false},{"StartTime":50956.0,"Position":442.137268,"HyperDash":false},{"StartTime":51040.0,"Position":459.075165,"HyperDash":false},{"StartTime":51105.0,"Position":484.729462,"HyperDash":false},{"StartTime":51206.0,"Position":496.5,"HyperDash":false}]},{"StartTime":51374.0,"Objects":[{"StartTime":51374.0,"Position":400.0,"HyperDash":false}]},{"StartTime":51874.0,"Objects":[{"StartTime":51874.0,"Position":244.0,"HyperDash":false},{"StartTime":51957.0,"Position":220.5127,"HyperDash":false},{"StartTime":52040.0,"Position":194.025391,"HyperDash":false},{"StartTime":52123.0,"Position":197.538086,"HyperDash":false},{"StartTime":52207.0,"Position":169.828033,"HyperDash":false},{"StartTime":52272.0,"Position":151.350021,"HyperDash":false},{"StartTime":52374.0,"Position":132.630676,"HyperDash":false}]},{"StartTime":52707.0,"Objects":[{"StartTime":52707.0,"Position":208.0,"HyperDash":false},{"StartTime":52781.0,"Position":217.666672,"HyperDash":false},{"StartTime":52855.0,"Position":252.333344,"HyperDash":false},{"StartTime":52929.0,"Position":248.0,"HyperDash":false},{"StartTime":53040.0,"Position":283.0,"HyperDash":false}]},{"StartTime":53373.0,"Objects":[{"StartTime":53373.0,"Position":368.0,"HyperDash":false},{"StartTime":53447.0,"Position":389.547058,"HyperDash":false},{"StartTime":53521.0,"Position":360.0941,"HyperDash":false},{"StartTime":53595.0,"Position":373.641144,"HyperDash":false},{"StartTime":53706.0,"Position":379.4617,"HyperDash":false}]},{"StartTime":54040.0,"Objects":[{"StartTime":54040.0,"Position":255.0,"HyperDash":false},{"StartTime":54114.0,"Position":252.333328,"HyperDash":false},{"StartTime":54188.0,"Position":226.666656,"HyperDash":false},{"StartTime":54262.0,"Position":195.0,"HyperDash":false},{"StartTime":54373.0,"Position":180.0,"HyperDash":false}]},{"StartTime":54707.0,"Objects":[{"StartTime":54707.0,"Position":368.0,"HyperDash":false}]},{"StartTime":55374.0,"Objects":[{"StartTime":55374.0,"Position":160.0,"HyperDash":false},{"StartTime":55448.0,"Position":163.26001,"HyperDash":false},{"StartTime":55522.0,"Position":156.520035,"HyperDash":false},{"StartTime":55596.0,"Position":132.780045,"HyperDash":false},{"StartTime":55707.0,"Position":147.670074,"HyperDash":false}]},{"StartTime":56041.0,"Objects":[{"StartTime":56041.0,"Position":320.0,"HyperDash":false},{"StartTime":56115.0,"Position":345.222229,"HyperDash":false},{"StartTime":56189.0,"Position":369.444458,"HyperDash":false},{"StartTime":56263.0,"Position":402.6667,"HyperDash":false},{"StartTime":56374.0,"Position":420.0,"HyperDash":false}]},{"StartTime":56707.0,"Objects":[{"StartTime":56707.0,"Position":256.0,"HyperDash":false},{"StartTime":56781.0,"Position":245.8573,"HyperDash":false},{"StartTime":56855.0,"Position":239.7146,"HyperDash":false},{"StartTime":56929.0,"Position":253.571915,"HyperDash":false},{"StartTime":57040.0,"Position":241.857864,"HyperDash":false}]},{"StartTime":57207.0,"Objects":[{"StartTime":57207.0,"Position":328.0,"HyperDash":false},{"StartTime":57290.0,"Position":334.131958,"HyperDash":false},{"StartTime":57373.0,"Position":357.603333,"HyperDash":false},{"StartTime":57456.0,"Position":386.564667,"HyperDash":false},{"StartTime":57540.0,"Position":373.784027,"HyperDash":false},{"StartTime":57605.0,"Position":370.9021,"HyperDash":false},{"StartTime":57707.0,"Position":334.171356,"HyperDash":false}]},{"StartTime":58041.0,"Objects":[{"StartTime":58041.0,"Position":176.0,"HyperDash":false},{"StartTime":58115.0,"Position":153.777771,"HyperDash":false},{"StartTime":58189.0,"Position":113.555557,"HyperDash":false},{"StartTime":58263.0,"Position":124.333328,"HyperDash":false},{"StartTime":58374.0,"Position":76.0,"HyperDash":false}]},{"StartTime":58707.0,"Objects":[{"StartTime":58707.0,"Position":208.0,"HyperDash":false},{"StartTime":58790.0,"Position":235.924927,"HyperDash":false},{"StartTime":58873.0,"Position":258.849854,"HyperDash":false},{"StartTime":58956.0,"Position":280.77478,"HyperDash":false},{"StartTime":59040.0,"Position":308.0,"HyperDash":false},{"StartTime":59114.0,"Position":302.777771,"HyperDash":false},{"StartTime":59188.0,"Position":275.555542,"HyperDash":false},{"StartTime":59262.0,"Position":249.333344,"HyperDash":false},{"StartTime":59373.0,"Position":208.0,"HyperDash":false}]},{"StartTime":59707.0,"Objects":[{"StartTime":59707.0,"Position":64.0,"HyperDash":false}]},{"StartTime":59874.0,"Objects":[{"StartTime":59874.0,"Position":128.0,"HyperDash":false},{"StartTime":59948.0,"Position":144.1427,"HyperDash":false},{"StartTime":60022.0,"Position":119.2854,"HyperDash":false},{"StartTime":60096.0,"Position":142.428085,"HyperDash":false},{"StartTime":60207.0,"Position":142.142136,"HyperDash":false}]},{"StartTime":60374.0,"Objects":[{"StartTime":60374.0,"Position":80.0,"HyperDash":false},{"StartTime":60457.0,"Position":73.37541,"HyperDash":false},{"StartTime":60540.0,"Position":27.7508316,"HyperDash":false},{"StartTime":60623.0,"Position":45.1262474,"HyperDash":false},{"StartTime":60707.0,"Position":9.28933,"HyperDash":false},{"StartTime":60781.0,"Position":31.0028038,"HyperDash":false},{"StartTime":60855.0,"Position":29.71629,"HyperDash":false},{"StartTime":60929.0,"Position":68.42977,"HyperDash":false},{"StartTime":61040.0,"Position":80.0,"HyperDash":false}]},{"StartTime":61374.0,"Objects":[{"StartTime":61374.0,"Position":224.0,"HyperDash":false},{"StartTime":61457.0,"Position":240.728989,"HyperDash":false},{"StartTime":61540.0,"Position":281.499359,"HyperDash":false},{"StartTime":61623.0,"Position":285.145782,"HyperDash":false},{"StartTime":61707.0,"Position":295.647522,"HyperDash":false},{"StartTime":61781.0,"Position":273.401184,"HyperDash":false},{"StartTime":61855.0,"Position":266.0076,"HyperDash":false},{"StartTime":61929.0,"Position":282.02597,"HyperDash":false},{"StartTime":62040.0,"Position":233.9523,"HyperDash":false}]},{"StartTime":62374.0,"Objects":[{"StartTime":62374.0,"Position":96.0,"HyperDash":false}]},{"StartTime":62541.0,"Objects":[{"StartTime":62541.0,"Position":32.0,"HyperDash":false},{"StartTime":62615.0,"Position":2.15351486,"HyperDash":false},{"StartTime":62689.0,"Position":34.9851379,"HyperDash":false},{"StartTime":62763.0,"Position":0.0,"HyperDash":false},{"StartTime":62874.0,"Position":30.1482067,"HyperDash":false}]},{"StartTime":63041.0,"Objects":[{"StartTime":63041.0,"Position":92.0,"HyperDash":false},{"StartTime":63115.0,"Position":114.222221,"HyperDash":false},{"StartTime":63189.0,"Position":131.444443,"HyperDash":false},{"StartTime":63263.0,"Position":144.666672,"HyperDash":false},{"StartTime":63374.0,"Position":192.0,"HyperDash":false}]},{"StartTime":63707.0,"Objects":[{"StartTime":63707.0,"Position":468.0,"HyperDash":false}]},{"StartTime":64041.0,"Objects":[{"StartTime":64041.0,"Position":192.0,"HyperDash":false},{"StartTime":64124.0,"Position":153.075073,"HyperDash":false},{"StartTime":64207.0,"Position":159.150146,"HyperDash":false},{"StartTime":64290.0,"Position":101.225227,"HyperDash":false},{"StartTime":64374.0,"Position":92.0,"HyperDash":false},{"StartTime":64448.0,"Position":132.222229,"HyperDash":false},{"StartTime":64522.0,"Position":126.444443,"HyperDash":false},{"StartTime":64596.0,"Position":160.666656,"HyperDash":false},{"StartTime":64707.0,"Position":192.0,"HyperDash":false}]},{"StartTime":65041.0,"Objects":[{"StartTime":65041.0,"Position":336.0,"HyperDash":false},{"StartTime":65124.0,"Position":375.268738,"HyperDash":false},{"StartTime":65207.0,"Position":395.320221,"HyperDash":false},{"StartTime":65290.0,"Position":394.972534,"HyperDash":false},{"StartTime":65374.0,"Position":395.778046,"HyperDash":false},{"StartTime":65448.0,"Position":382.9742,"HyperDash":false},{"StartTime":65522.0,"Position":392.609863,"HyperDash":false},{"StartTime":65596.0,"Position":364.706543,"HyperDash":false},{"StartTime":65707.0,"Position":339.031464,"HyperDash":false}]},{"StartTime":66041.0,"Objects":[{"StartTime":66041.0,"Position":208.0,"HyperDash":false},{"StartTime":66115.0,"Position":218.222229,"HyperDash":false},{"StartTime":66189.0,"Position":241.444443,"HyperDash":false},{"StartTime":66263.0,"Position":289.6667,"HyperDash":false},{"StartTime":66374.0,"Position":308.0,"HyperDash":false}]},{"StartTime":66707.0,"Objects":[{"StartTime":66707.0,"Position":144.0,"HyperDash":false},{"StartTime":66781.0,"Position":125.777779,"HyperDash":false},{"StartTime":66855.0,"Position":106.555557,"HyperDash":false},{"StartTime":66929.0,"Position":90.33333,"HyperDash":false},{"StartTime":67040.0,"Position":44.0,"HyperDash":false}]},{"StartTime":67373.0,"Objects":[{"StartTime":67373.0,"Position":192.0,"HyperDash":false},{"StartTime":67447.0,"Position":186.1427,"HyperDash":false},{"StartTime":67521.0,"Position":209.2854,"HyperDash":false},{"StartTime":67595.0,"Position":193.428085,"HyperDash":false},{"StartTime":67706.0,"Position":206.142136,"HyperDash":false}]},{"StartTime":67874.0,"Objects":[{"StartTime":67874.0,"Position":120.0,"HyperDash":false},{"StartTime":67957.0,"Position":88.82533,"HyperDash":false},{"StartTime":68040.0,"Position":85.3476257,"HyperDash":false},{"StartTime":68123.0,"Position":65.43532,"HyperDash":false},{"StartTime":68207.0,"Position":74.31434,"HyperDash":false},{"StartTime":68272.0,"Position":73.27857,"HyperDash":false},{"StartTime":68373.0,"Position":113.828613,"HyperDash":false}]},{"StartTime":68707.0,"Objects":[{"StartTime":68707.0,"Position":272.0,"HyperDash":false},{"StartTime":68781.0,"Position":296.222229,"HyperDash":false},{"StartTime":68855.0,"Position":335.444458,"HyperDash":false},{"StartTime":68929.0,"Position":338.6667,"HyperDash":false},{"StartTime":69040.0,"Position":372.0,"HyperDash":false}]},{"StartTime":69374.0,"Objects":[{"StartTime":69374.0,"Position":237.0,"HyperDash":false},{"StartTime":69457.0,"Position":218.076126,"HyperDash":false},{"StartTime":69540.0,"Position":170.152252,"HyperDash":false},{"StartTime":69623.0,"Position":155.228363,"HyperDash":false},{"StartTime":69707.0,"Position":137.004211,"HyperDash":false},{"StartTime":69781.0,"Position":167.2255,"HyperDash":false},{"StartTime":69855.0,"Position":161.446777,"HyperDash":false},{"StartTime":69929.0,"Position":188.66806,"HyperDash":false},{"StartTime":70040.0,"Position":237.0,"HyperDash":false}]},{"StartTime":70373.0,"Objects":[{"StartTime":70373.0,"Position":384.0,"HyperDash":false}]},{"StartTime":70540.0,"Objects":[{"StartTime":70540.0,"Position":448.0,"HyperDash":false},{"StartTime":70614.0,"Position":454.1427,"HyperDash":false},{"StartTime":70688.0,"Position":466.2854,"HyperDash":false},{"StartTime":70762.0,"Position":467.4281,"HyperDash":false},{"StartTime":70873.0,"Position":462.142151,"HyperDash":false}]},{"StartTime":71040.0,"Objects":[{"StartTime":71040.0,"Position":400.0,"HyperDash":false},{"StartTime":71123.0,"Position":381.075073,"HyperDash":false},{"StartTime":71206.0,"Position":345.150146,"HyperDash":false},{"StartTime":71289.0,"Position":316.22522,"HyperDash":false},{"StartTime":71373.0,"Position":300.0,"HyperDash":false},{"StartTime":71447.0,"Position":336.222229,"HyperDash":false},{"StartTime":71521.0,"Position":347.444458,"HyperDash":false},{"StartTime":71595.0,"Position":384.666656,"HyperDash":false},{"StartTime":71706.0,"Position":400.0,"HyperDash":false}]},{"StartTime":72040.0,"Objects":[{"StartTime":72040.0,"Position":256.0,"HyperDash":false},{"StartTime":72123.0,"Position":241.4447,"HyperDash":false},{"StartTime":72206.0,"Position":212.00444,"HyperDash":false},{"StartTime":72289.0,"Position":222.925644,"HyperDash":false},{"StartTime":72373.0,"Position":198.3011,"HyperDash":false},{"StartTime":72447.0,"Position":185.363647,"HyperDash":false},{"StartTime":72521.0,"Position":217.711319,"HyperDash":false},{"StartTime":72595.0,"Position":229.9505,"HyperDash":false},{"StartTime":72706.0,"Position":232.077591,"HyperDash":false}]},{"StartTime":73040.0,"Objects":[{"StartTime":73040.0,"Position":400.0,"HyperDash":false}]},{"StartTime":73207.0,"Objects":[{"StartTime":73207.0,"Position":472.0,"HyperDash":false},{"StartTime":73281.0,"Position":462.583252,"HyperDash":false},{"StartTime":73355.0,"Position":487.166534,"HyperDash":false},{"StartTime":73429.0,"Position":462.7498,"HyperDash":false},{"StartTime":73540.0,"Position":479.1247,"HyperDash":false}]},{"StartTime":73707.0,"Objects":[{"StartTime":73707.0,"Position":416.0,"HyperDash":false},{"StartTime":73781.0,"Position":409.777771,"HyperDash":false},{"StartTime":73855.0,"Position":383.555542,"HyperDash":false},{"StartTime":73929.0,"Position":337.3333,"HyperDash":false},{"StartTime":74040.0,"Position":316.0,"HyperDash":false}]},{"StartTime":74373.0,"Objects":[{"StartTime":74373.0,"Position":36.0,"HyperDash":false}]},{"StartTime":74707.0,"Objects":[{"StartTime":74707.0,"Position":304.0,"HyperDash":false},{"StartTime":74790.0,"Position":334.939941,"HyperDash":false},{"StartTime":74873.0,"Position":359.879883,"HyperDash":false},{"StartTime":74956.0,"Position":382.819824,"HyperDash":false},{"StartTime":75040.0,"Position":384.0,"HyperDash":false},{"StartTime":75114.0,"Position":376.222229,"HyperDash":false},{"StartTime":75188.0,"Position":347.444458,"HyperDash":false},{"StartTime":75262.0,"Position":313.666656,"HyperDash":false},{"StartTime":75373.0,"Position":304.0,"HyperDash":false}]},{"StartTime":75707.0,"Objects":[{"StartTime":75707.0,"Position":160.0,"HyperDash":false},{"StartTime":75790.0,"Position":138.731277,"HyperDash":false},{"StartTime":75873.0,"Position":112.679764,"HyperDash":false},{"StartTime":75956.0,"Position":91.02745,"HyperDash":false},{"StartTime":76040.0,"Position":100.221947,"HyperDash":false},{"StartTime":76114.0,"Position":110.025749,"HyperDash":false},{"StartTime":76188.0,"Position":126.390106,"HyperDash":false},{"StartTime":76262.0,"Position":120.293419,"HyperDash":false},{"StartTime":76373.0,"Position":156.968521,"HyperDash":false}]},{"StartTime":76707.0,"Objects":[{"StartTime":76707.0,"Position":304.0,"HyperDash":false},{"StartTime":76781.0,"Position":341.222229,"HyperDash":false},{"StartTime":76855.0,"Position":337.444458,"HyperDash":false},{"StartTime":76929.0,"Position":358.6667,"HyperDash":false},{"StartTime":77040.0,"Position":404.0,"HyperDash":false}]},{"StartTime":77374.0,"Objects":[{"StartTime":77374.0,"Position":8.0,"HyperDash":false}]},{"StartTime":77707.0,"Objects":[{"StartTime":77707.0,"Position":450.0,"HyperDash":false},{"StartTime":77779.0,"Position":231.0,"HyperDash":false},{"StartTime":77852.0,"Position":118.0,"HyperDash":false},{"StartTime":77925.0,"Position":511.0,"HyperDash":false},{"StartTime":77998.0,"Position":333.0,"HyperDash":false},{"StartTime":78071.0,"Position":234.0,"HyperDash":false},{"StartTime":78144.0,"Position":228.0,"HyperDash":false},{"StartTime":78217.0,"Position":302.0,"HyperDash":false},{"StartTime":78290.0,"Position":390.0,"HyperDash":false},{"StartTime":78363.0,"Position":75.0,"HyperDash":false},{"StartTime":78436.0,"Position":506.0,"HyperDash":false},{"StartTime":78509.0,"Position":3.0,"HyperDash":false},{"StartTime":78582.0,"Position":289.0,"HyperDash":false},{"StartTime":78655.0,"Position":217.0,"HyperDash":false},{"StartTime":78728.0,"Position":447.0,"HyperDash":false},{"StartTime":78801.0,"Position":324.0,"HyperDash":false},{"StartTime":78874.0,"Position":183.0,"HyperDash":false},{"StartTime":78946.0,"Position":279.0,"HyperDash":false},{"StartTime":79019.0,"Position":157.0,"HyperDash":false},{"StartTime":79092.0,"Position":501.0,"HyperDash":false},{"StartTime":79165.0,"Position":215.0,"HyperDash":false},{"StartTime":79238.0,"Position":79.0,"HyperDash":false},{"StartTime":79311.0,"Position":337.0,"HyperDash":false},{"StartTime":79384.0,"Position":380.0,"HyperDash":false},{"StartTime":79457.0,"Position":348.0,"HyperDash":false},{"StartTime":79530.0,"Position":225.0,"HyperDash":false},{"StartTime":79603.0,"Position":363.0,"HyperDash":false},{"StartTime":79676.0,"Position":96.0,"HyperDash":false},{"StartTime":79749.0,"Position":104.0,"HyperDash":false},{"StartTime":79822.0,"Position":173.0,"HyperDash":false},{"StartTime":79895.0,"Position":373.0,"HyperDash":false},{"StartTime":79968.0,"Position":424.0,"HyperDash":false},{"StartTime":80041.0,"Position":268.0,"HyperDash":false}]},{"StartTime":82374.0,"Objects":[{"StartTime":82374.0,"Position":368.0,"HyperDash":false}]},{"StartTime":82707.0,"Objects":[{"StartTime":82707.0,"Position":224.0,"HyperDash":false},{"StartTime":82790.0,"Position":220.606583,"HyperDash":false},{"StartTime":82873.0,"Position":192.709763,"HyperDash":false},{"StartTime":82956.0,"Position":172.4607,"HyperDash":false},{"StartTime":83040.0,"Position":181.183929,"HyperDash":false},{"StartTime":83123.0,"Position":190.276581,"HyperDash":false},{"StartTime":83206.0,"Position":175.345276,"HyperDash":false},{"StartTime":83289.0,"Position":168.272736,"HyperDash":false},{"StartTime":83373.0,"Position":181.259979,"HyperDash":false},{"StartTime":83447.0,"Position":182.439926,"HyperDash":false},{"StartTime":83522.0,"Position":186.502777,"HyperDash":false},{"StartTime":83596.0,"Position":213.74353,"HyperDash":false},{"StartTime":83707.0,"Position":224.286057,"HyperDash":false}]},{"StartTime":84041.0,"Objects":[{"StartTime":84041.0,"Position":368.0,"HyperDash":false},{"StartTime":84124.0,"Position":366.238831,"HyperDash":false},{"StartTime":84207.0,"Position":382.477631,"HyperDash":false},{"StartTime":84290.0,"Position":376.716461,"HyperDash":false},{"StartTime":84374.0,"Position":372.9702,"HyperDash":false},{"StartTime":84457.0,"Position":381.209045,"HyperDash":false},{"StartTime":84540.0,"Position":367.447845,"HyperDash":false},{"StartTime":84623.0,"Position":364.686676,"HyperDash":false},{"StartTime":84707.0,"Position":377.94043,"HyperDash":false},{"StartTime":84781.0,"Position":396.044922,"HyperDash":false},{"StartTime":84856.0,"Position":371.164337,"HyperDash":false},{"StartTime":84930.0,"Position":379.268829,"HyperDash":false},{"StartTime":85041.0,"Position":382.925568,"HyperDash":false}]},{"StartTime":85374.0,"Objects":[{"StartTime":85374.0,"Position":240.0,"HyperDash":false},{"StartTime":85457.0,"Position":214.595383,"HyperDash":false},{"StartTime":85540.0,"Position":206.182877,"HyperDash":false},{"StartTime":85623.0,"Position":175.034424,"HyperDash":false},{"StartTime":85707.0,"Position":168.007141,"HyperDash":false},{"StartTime":85781.0,"Position":185.660355,"HyperDash":false},{"StartTime":85855.0,"Position":200.138123,"HyperDash":false},{"StartTime":85929.0,"Position":194.945816,"HyperDash":false},{"StartTime":86040.0,"Position":235.646591,"HyperDash":false}]},{"StartTime":86374.0,"Objects":[{"StartTime":86374.0,"Position":496.0,"HyperDash":false}]},{"StartTime":86707.0,"Objects":[{"StartTime":86707.0,"Position":224.0,"HyperDash":false},{"StartTime":86781.0,"Position":185.777771,"HyperDash":false},{"StartTime":86855.0,"Position":181.555557,"HyperDash":false},{"StartTime":86929.0,"Position":172.333328,"HyperDash":false},{"StartTime":87040.0,"Position":124.0,"HyperDash":false}]},{"StartTime":87374.0,"Objects":[{"StartTime":87374.0,"Position":256.0,"HyperDash":false},{"StartTime":87448.0,"Position":281.222229,"HyperDash":false},{"StartTime":87522.0,"Position":307.444458,"HyperDash":false},{"StartTime":87596.0,"Position":307.6667,"HyperDash":false},{"StartTime":87707.0,"Position":356.0,"HyperDash":false}]},{"StartTime":88041.0,"Objects":[{"StartTime":88041.0,"Position":184.0,"HyperDash":false}]},{"StartTime":88374.0,"Objects":[{"StartTime":88374.0,"Position":352.0,"HyperDash":false},{"StartTime":88448.0,"Position":358.1427,"HyperDash":false},{"StartTime":88522.0,"Position":353.2854,"HyperDash":false},{"StartTime":88596.0,"Position":361.4281,"HyperDash":false},{"StartTime":88707.0,"Position":366.142151,"HyperDash":false}]},{"StartTime":89041.0,"Objects":[{"StartTime":89041.0,"Position":80.0,"HyperDash":false}]},{"StartTime":89374.0,"Objects":[{"StartTime":89374.0,"Position":366.0,"HyperDash":false},{"StartTime":89457.0,"Position":408.9072,"HyperDash":false},{"StartTime":89540.0,"Position":411.8144,"HyperDash":false},{"StartTime":89623.0,"Position":438.7216,"HyperDash":false},{"StartTime":89707.0,"Position":465.928864,"HyperDash":false},{"StartTime":89781.0,"Position":460.722473,"HyperDash":false},{"StartTime":89855.0,"Position":437.516052,"HyperDash":false},{"StartTime":89929.0,"Position":403.309631,"HyperDash":false},{"StartTime":90040.0,"Position":366.0,"HyperDash":false}]},{"StartTime":90374.0,"Objects":[{"StartTime":90374.0,"Position":24.0,"HyperDash":false}]},{"StartTime":90707.0,"Objects":[{"StartTime":90707.0,"Position":368.0,"HyperDash":false},{"StartTime":90781.0,"Position":386.704376,"HyperDash":false},{"StartTime":90855.0,"Position":388.408722,"HyperDash":false},{"StartTime":90929.0,"Position":374.1131,"HyperDash":false},{"StartTime":91040.0,"Position":375.669647,"HyperDash":false}]},{"StartTime":91374.0,"Objects":[{"StartTime":91374.0,"Position":256.0,"HyperDash":false},{"StartTime":91448.0,"Position":246.777771,"HyperDash":false},{"StartTime":91522.0,"Position":220.555557,"HyperDash":false},{"StartTime":91596.0,"Position":188.333328,"HyperDash":false},{"StartTime":91707.0,"Position":156.0,"HyperDash":false}]},{"StartTime":92041.0,"Objects":[{"StartTime":92041.0,"Position":256.0,"HyperDash":false},{"StartTime":92115.0,"Position":291.222229,"HyperDash":false},{"StartTime":92189.0,"Position":285.444458,"HyperDash":false},{"StartTime":92263.0,"Position":313.6667,"HyperDash":false},{"StartTime":92374.0,"Position":356.0,"HyperDash":false}]},{"StartTime":92707.0,"Objects":[{"StartTime":92707.0,"Position":224.0,"HyperDash":false},{"StartTime":92781.0,"Position":189.777771,"HyperDash":false},{"StartTime":92855.0,"Position":181.555557,"HyperDash":false},{"StartTime":92929.0,"Position":141.333328,"HyperDash":false},{"StartTime":93040.0,"Position":124.0,"HyperDash":false}]},{"StartTime":93374.0,"Objects":[{"StartTime":93374.0,"Position":392.0,"HyperDash":false}]},{"StartTime":93707.0,"Objects":[{"StartTime":93707.0,"Position":128.0,"HyperDash":false},{"StartTime":93790.0,"Position":108.075073,"HyperDash":false},{"StartTime":93873.0,"Position":94.15015,"HyperDash":false},{"StartTime":93956.0,"Position":33.2252274,"HyperDash":false},{"StartTime":94040.0,"Position":28.0,"HyperDash":false},{"StartTime":94114.0,"Position":51.22222,"HyperDash":false},{"StartTime":94188.0,"Position":75.44444,"HyperDash":false},{"StartTime":94262.0,"Position":111.666664,"HyperDash":false},{"StartTime":94373.0,"Position":128.0,"HyperDash":false}]},{"StartTime":94707.0,"Objects":[{"StartTime":94707.0,"Position":256.0,"HyperDash":false},{"StartTime":94781.0,"Position":264.704376,"HyperDash":false},{"StartTime":94855.0,"Position":261.408722,"HyperDash":false},{"StartTime":94929.0,"Position":261.1131,"HyperDash":false},{"StartTime":95040.0,"Position":263.669647,"HyperDash":false}]},{"StartTime":95374.0,"Objects":[{"StartTime":95374.0,"Position":24.0,"HyperDash":false}]},{"StartTime":95540.0,"Objects":[{"StartTime":95540.0,"Position":96.0,"HyperDash":false}]},{"StartTime":95707.0,"Objects":[{"StartTime":95707.0,"Position":48.0,"HyperDash":false}]},{"StartTime":96041.0,"Objects":[{"StartTime":96041.0,"Position":168.0,"HyperDash":false},{"StartTime":96115.0,"Position":188.222229,"HyperDash":false},{"StartTime":96189.0,"Position":219.444443,"HyperDash":false},{"StartTime":96263.0,"Position":222.666672,"HyperDash":false},{"StartTime":96374.0,"Position":268.0,"HyperDash":false}]},{"StartTime":96707.0,"Objects":[{"StartTime":96707.0,"Position":152.0,"HyperDash":false},{"StartTime":96781.0,"Position":144.295639,"HyperDash":false},{"StartTime":96855.0,"Position":165.591263,"HyperDash":false},{"StartTime":96929.0,"Position":143.8869,"HyperDash":false},{"StartTime":97040.0,"Position":144.330353,"HyperDash":false}]},{"StartTime":97374.0,"Objects":[{"StartTime":97374.0,"Position":280.0,"HyperDash":false},{"StartTime":97457.0,"Position":300.248535,"HyperDash":false},{"StartTime":97540.0,"Position":317.463043,"HyperDash":false},{"StartTime":97623.0,"Position":329.187042,"HyperDash":false},{"StartTime":97707.0,"Position":369.215424,"HyperDash":false},{"StartTime":97781.0,"Position":392.887115,"HyperDash":false},{"StartTime":97855.0,"Position":394.493958,"HyperDash":false},{"StartTime":97929.0,"Position":416.841644,"HyperDash":false},{"StartTime":98040.0,"Position":422.157837,"HyperDash":false}]},{"StartTime":98707.0,"Objects":[{"StartTime":98707.0,"Position":144.0,"HyperDash":false}]},{"StartTime":99040.0,"Objects":[{"StartTime":99040.0,"Position":229.0,"HyperDash":false},{"StartTime":99138.0,"Position":51.0,"HyperDash":false},{"StartTime":99237.0,"Position":199.0,"HyperDash":false},{"StartTime":99336.0,"Position":208.0,"HyperDash":false},{"StartTime":99435.0,"Position":173.0,"HyperDash":false},{"StartTime":99534.0,"Position":367.0,"HyperDash":false},{"StartTime":99633.0,"Position":193.0,"HyperDash":false},{"StartTime":99732.0,"Position":488.0,"HyperDash":false},{"StartTime":99831.0,"Position":314.0,"HyperDash":false},{"StartTime":99930.0,"Position":135.0,"HyperDash":false},{"StartTime":100029.0,"Position":399.0,"HyperDash":false},{"StartTime":100128.0,"Position":404.0,"HyperDash":false},{"StartTime":100227.0,"Position":152.0,"HyperDash":false},{"StartTime":100326.0,"Position":353.0,"HyperDash":false},{"StartTime":100425.0,"Position":358.0,"HyperDash":false},{"StartTime":100524.0,"Position":447.0,"HyperDash":false},{"StartTime":100623.0,"Position":222.0,"HyperDash":false},{"StartTime":100722.0,"Position":382.0,"HyperDash":false},{"StartTime":100821.0,"Position":433.0,"HyperDash":false},{"StartTime":100920.0,"Position":450.0,"HyperDash":false},{"StartTime":101019.0,"Position":326.0,"HyperDash":false},{"StartTime":101118.0,"Position":414.0,"HyperDash":false},{"StartTime":101216.0,"Position":285.0,"HyperDash":false},{"StartTime":101315.0,"Position":336.0,"HyperDash":false},{"StartTime":101414.0,"Position":509.0,"HyperDash":false},{"StartTime":101513.0,"Position":334.0,"HyperDash":false},{"StartTime":101612.0,"Position":72.0,"HyperDash":false},{"StartTime":101711.0,"Position":425.0,"HyperDash":false},{"StartTime":101810.0,"Position":451.0,"HyperDash":false},{"StartTime":101909.0,"Position":220.0,"HyperDash":false},{"StartTime":102008.0,"Position":25.0,"HyperDash":false},{"StartTime":102107.0,"Position":77.0,"HyperDash":false},{"StartTime":102206.0,"Position":509.0,"HyperDash":false},{"StartTime":102305.0,"Position":90.0,"HyperDash":false},{"StartTime":102404.0,"Position":118.0,"HyperDash":false},{"StartTime":102503.0,"Position":58.0,"HyperDash":false},{"StartTime":102602.0,"Position":12.0,"HyperDash":false},{"StartTime":102701.0,"Position":215.0,"HyperDash":false},{"StartTime":102800.0,"Position":487.0,"HyperDash":false},{"StartTime":102899.0,"Position":446.0,"HyperDash":false},{"StartTime":102998.0,"Position":491.0,"HyperDash":false},{"StartTime":103097.0,"Position":459.0,"HyperDash":false},{"StartTime":103196.0,"Position":37.0,"HyperDash":false},{"StartTime":103294.0,"Position":291.0,"HyperDash":false},{"StartTime":103393.0,"Position":315.0,"HyperDash":false},{"StartTime":103492.0,"Position":35.0,"HyperDash":false},{"StartTime":103591.0,"Position":208.0,"HyperDash":false},{"StartTime":103690.0,"Position":504.0,"HyperDash":false},{"StartTime":103789.0,"Position":296.0,"HyperDash":false},{"StartTime":103888.0,"Position":105.0,"HyperDash":false},{"StartTime":103987.0,"Position":488.0,"HyperDash":false},{"StartTime":104086.0,"Position":230.0,"HyperDash":false},{"StartTime":104185.0,"Position":446.0,"HyperDash":false},{"StartTime":104284.0,"Position":241.0,"HyperDash":false},{"StartTime":104383.0,"Position":413.0,"HyperDash":false},{"StartTime":104482.0,"Position":357.0,"HyperDash":false},{"StartTime":104581.0,"Position":256.0,"HyperDash":false},{"StartTime":104680.0,"Position":192.0,"HyperDash":false},{"StartTime":104779.0,"Position":116.0,"HyperDash":false},{"StartTime":104878.0,"Position":397.0,"HyperDash":false},{"StartTime":104977.0,"Position":422.0,"HyperDash":false},{"StartTime":105076.0,"Position":230.0,"HyperDash":false},{"StartTime":105175.0,"Position":479.0,"HyperDash":false},{"StartTime":105274.0,"Position":276.0,"HyperDash":false},{"StartTime":105373.0,"Position":423.0,"HyperDash":false}]}]} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/1284935.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/1284935.osu new file mode 100644 index 0000000000..a0ed6b190e --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/1284935.osu @@ -0,0 +1,210 @@ +osu file format v14 + +[General] +StackLeniency: 0.7 +Mode: 2 + +[Difficulty] +HPDrainRate:3 +CircleSize:2.5 +OverallDifficulty:6 +ApproachRate:6 +SliderMultiplier:1 +SliderTickRate:1 + +[Events] +//Background and Video events +//Break Periods +2,80241,81249 +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Sound Samples + +[TimingPoints] +41,333.333333333333,4,2,1,50,1,0 +707,-100,4,2,1,50,0,0 +2707,-100,4,2,1,85,0,0 +12040,-86.9565217391304,4,2,1,85,0,0 +12707,-100,4,2,1,85,0,0 +13374,-100,4,2,1,85,0,0 +34207,-100,4,2,1,75,0,0 +34374,-100,4,2,1,65,0,0 +34540,-100,4,2,1,55,0,0 +34707,-100,4,2,1,85,0,0 +45374,-133.333333333333,4,2,1,85,0,0 +54707,-133.333333333333,4,2,1,30,0,0 +56040,-100,4,2,1,85,0,1 +72040,-125,4,2,1,85,0,0 +72707,-100,4,2,1,85,0,0 +74707,-125,4,2,1,85,0,0 +75207,-100,4,2,1,85,0,0 +82374,-200,4,2,1,85,0,0 +85374,-100,4,2,1,85,0,0 +88040,-100,4,2,1,85,0,1 +98707,-100,4,2,1,85,0,0 +99040,-100,4,2,1,20,0,0 + +[HitObjects] +256,192,707,12,0,2374,0:0:0:0: +368,64,2707,6,2,L|256:64,1,100,2|2,0:0|0:0,0:0:0:0: +288,128,3207,2,2,L|304:192,1,50,2|8,0:0|0:0,0:0:0:0: +192,192,3707,6,2,L|64:192,2,100,2|2|2,0:0|0:0|0:0,0:0:0:0: +288,192,4707,1,8,0:0:0:0: +144,128,5041,1,10,0:0:0:0: +304,288,5374,6,6,L|448:144,2,200,6|8|0,0:0|0:0|0:0,0:0:0:0: +208,288,7041,1,0,0:0:0:0: +304,160,7374,2,10,L|320:48,1,100,10|8,0:0|0:0,0:0:0:0: +160,32,8041,6,6,L|48:32,1,100,6|2,0:0|0:0,0:0:0:0: +112,80,8541,1,0,0:0:0:0: +160,128,8707,2,8,P|208:160|128:232,1,200,8|6,0:0|0:0,0:0:0:0: +224,256,9707,5,2,0:0:0:0: +352,224,10041,2,8,L|240:224,1,100,8|2,0:0|0:0,0:0:0:0: +416,336,10707,6,6,P|464:320|416:216,1,200,6|12,0:0|0:0,0:0:0:0: +224,96,11874,1,2,0:0:0:0: +160,96,12041,2,2,P|116:152|160:232,1,172.500003290176,2|2,0:0|0:0,0:0:0:0: +224,232,12707,1,2,0:0:0:0: +464,64,13374,6,6,L|304:64,1,150,6|2,0:0|0:0,0:0:0:0: +240,64,14041,2,8,P|192:112|240:160,1,150,8|2,0:0|0:0,0:0:0:0: +320,160,14707,6,2,L|208:160,1,100,2|0,0:0|0:0,0:0:0:0: +320,256,15374,2,8,L|360:164,1,100,8|8,0:0|0:0,0:0:0:0: +192,64,16041,6,4,L|80:64,1,100,4|2,0:0|0:0,0:0:0:0: +144,80,16541,1,2,0:0:0:0: +192,96,16707,1,8,0:0:0:0: +336,96,17041,1,2,0:0:0:0: +288,96,17207,6,2,P|240:128|288:192,1,150,2|0,0:0|0:0,0:0:0:0: +384,192,18041,1,8,0:0:0:0: +256,192,18374,1,2,0:0:0:0: +416,192,18707,6,6,L|432:16,1,150,6|2,0:0|0:0,0:0:0:0: +336,32,19374,2,8,L|224:32,1,100,8|0,0:0|0:0,0:0:0:0: +256,32,20041,6,2,P|304:80|256:128,1,150,2|2,0:0|0:0,0:0:0:0: +196,128,20707,2,8,L|156:220,1,100,8|8,0:0|0:0,0:0:0:0: +320,224,21374,6,6,P|360:288|320:352,1,150,6|2,0:0|0:0,0:0:0:0: +224,352,22041,2,8,L|112:352,1,100,8|2,0:0|0:0,0:0:0:0: +192,224,22541,1,2,0:0:0:0: +204,272,22707,5,2,0:0:0:0: +96,288,23041,1,0,0:0:0:0: +208,288,23374,2,8,L|224:176,1,100,8|0,0:0|0:0,0:0:0:0: +80,96,24041,6,6,L|240:96,1,150,6|0,0:0|0:0,0:0:0:0: +176,96,24707,1,8,0:0:0:0: +256,128,25041,6,2,L|240:80,1,50,2|0,0:0|0:0,0:0:0:0: +352,96,25541,2,2,L|356:44,1,50,2|2,0:0|0:0,0:0:0:0: +192,176,26041,2,8,L|176:288,1,100,8|8,0:0|0:0,0:0:0:0: +272,336,26707,6,0,L|384:336,1,100,0|2,0:0|0:0,0:0:0:0: +320,288,27207,1,0,0:0:0:0: +272,240,27374,1,8,0:0:0:0: +416,240,27707,2,2,L|432:176,1,50,2|0,0:0|0:0,0:0:0:0: +288,176,28207,6,2,L|176:176,1,100,2|0,0:0|0:0,0:0:0:0: +256,368,28707,2,8,L|270:269,1,100,8|8,0:0|0:0,0:0:0:0: +128,256,29374,6,6,P|64:192|128:128,1,200,6|8,0:0|0:0,0:0:0:0: +224,128,30374,1,2,0:0:0:0: +368,128,30707,5,6,0:0:0:0: +432,128,30874,2,2,L|448:240,1,100,2|0,0:0|0:0,0:0:0:0: +384,256,31374,1,8,0:0:0:0: +240,256,31707,2,8,L|224:192,1,50,8|0,0:0|0:0,0:0:0:0: +304,192,32041,6,14,P|352:176|288:80,1,200,14|12,0:0|0:0,0:0:0:0: +160,80,33041,1,0,0:0:0:0: +304,80,33374,5,12,0:0:0:0: +368,80,33541,2,2,P|380:128|368:176,1,100,2|2,0:0|0:0,0:0:0:0: +224,176,34207,1,8,3:0:0:0: +176,176,34374,1,8,3:0:0:0: +128,176,34541,1,8,3:0:0:0: +200,144,34707,5,6,0:0:0:0: +336,272,35041,2,8,L|352:160,1,100,8|2,0:0|0:0,0:0:0:0: +208,144,35707,2,8,L|192:192,1,50,8|0,0:0|0:0,0:0:0:0: +336,208,36207,2,2,L|352:160,1,50,2|8,0:0|0:0,0:0:0:0: +208,160,36707,2,2,L|96:160,1,100,2|8,0:0|0:0,0:0:0:0: +256,160,37374,5,2,0:0:0:0: +320,160,37541,2,0,L|336:264,1,100,0|2,0:0|0:0,0:0:0:0: +272,272,38041,1,0,0:0:0:0: +416,272,38374,2,8,L|432:224,1,50,8|0,0:0|0:0,0:0:0:0: +288,224,38874,6,2,L|272:176,1,50,2|8,0:0|0:0,0:0:0:0: +336,160,39207,2,2,L|448:160,1,100,2|2,0:0|0:0,0:0:0:0: +384,160,39707,1,8,0:0:0:0: +240,160,40041,5,4,0:0:0:0: +384,64,40374,2,8,L|400:176,1,100,8|2,0:0|0:0,0:0:0:0: +256,176,41041,1,8,0:0:0:0: +112,176,41374,5,2,0:0:0:0: +48,224,41541,2,0,L|32:112,1,100,0|0,0:0|0:0,0:0:0:0: +96,112,42041,1,2,0:0:0:0: +240,112,42374,1,8,0:0:0:0: +96,112,42707,6,4,P|48:160|96:208,1,150,4|2,0:0|0:0,0:0:0:0: +160,208,43374,1,2,0:0:0:0: +288,208,43707,1,8,0:0:0:0: +160,208,44040,5,6,0:0:0:0: +304,288,44374,2,8,P|352:240|288:128,1,200,8|8,0:0|0:0,0:0:0:0: +136,128,45374,6,6,L|24:192,2,112.500004291535,6|0|0,0:0|0:0|0:0,0:0:0:0: +368,128,46874,2,0,L|384:240,1,112.500004291535,0|2,0:0|0:0,0:0:0:0: +272,256,47707,1,0,0:0:0:0: +144,256,48041,6,6,L|48:191,2,112.500004291535,6|0|0,0:0|0:0|0:0,0:0:0:0: +256,256,49374,2,2,P|304:224|224:112,1,225.000008583069,2|8,0:0|0:0,0:0:0:0: +384,96,50707,6,6,L|496:96,1,112.500004291535,6|0,0:0|0:0,0:0:0:0: +448,96,51374,1,8,0:0:0:0: +244,92,51874,2,2,L|132:108,1,112.500004291535,0|2,0:0|0:0,0:0:0:0: +208,288,52707,2,8,L|288:288,1,75.0000028610231,8|0,0:0|0:0,0:0:0:0: +368,288,53373,6,6,L|383:191,1,75.0000028610231,6|2,0:0|0:0,0:0:0:0: +255,192,54040,2,8,L|176:192,1,75.0000028610231,8|2,0:0|0:0,0:0:0:0: +272,80,54707,1,0,0:0:0:0: +160,272,55374,6,2,L|144:176,1,75.0000028610231,2|2,0:0|0:0,0:0:0:0: +320,144,56041,6,6,L|432:144,1,100,6|8,0:0|0:0,0:0:0:0: +256,240,56707,2,2,L|240:128,1,100,2|8,0:0|0:0,0:0:0:0: +328,112,57207,2,0,P|376:144|328:208,1,150,2|8,0:0|0:0,0:0:0:0: +176,208,58041,2,2,L|64:208,1,100,2|8,0:0|0:0,0:0:0:0: +208,208,58707,6,2,L|320:208,2,100,2|8|2,0:0|0:0|0:0,0:0:0:0: +64,208,59707,1,8,0:0:0:0: +128,208,59874,2,0,L|144:96,1,100,0|0,0:0|0:0,0:0:0:0: +80,96,60374,2,8,L|8:168,2,100,8|2|8,0:0|0:0|0:0,0:0:0:0: +224,96,61374,6,6,P|296:152|224:208,1,200,6|2,0:0|0:0,0:0:0:0: +96,224,62374,1,8,0:0:0:0: +32,224,62541,6,0,P|16:168|32:128,1,100,0|2,0:0|0:0,0:0:0:0: +92,112,63041,2,8,L|204:112,1,100,8|2,0:0|0:0,0:0:0:0: +336,112,63707,1,8,0:0:0:0: +192,112,64041,6,2,L|64:112,2,100,2|8|2,0:0|0:0|0:0,0:0:0:0: +336,112,65041,2,8,P|384:144|336:256,1,200,8|8,0:0|0:0,0:0:0:0: +208,256,66041,2,8,L|320:256,1,100,8|8,0:0|0:0,0:0:0:0: +144,160,66707,6,4,L|32:160,1,100,4|8,0:0|0:0,0:0:0:0: +192,256,67373,2,2,L|208:144,1,100,2|8,0:0|0:0,0:0:0:0: +120,128,67874,2,0,P|72:160|120:224,1,150,0|8,0:0|0:0,0:0:0:0: +272,224,68707,2,2,L|384:224,1,100,2|8,0:0|0:0,0:0:0:0: +237,223,69374,6,2,L|128:224,2,100,2|8|2,0:0|0:0|0:0,0:0:0:0: +384,208,70373,1,0,0:0:0:0: +448,208,70540,2,2,L|464:96,1,100,2|2,0:0|0:0,0:0:0:0: +400,96,71040,2,2,L|288:96,2,100,10|8|10,0:0|0:0|0:0,0:0:0:0: +256,96,72040,6,6,P|200:136|232:208,1,160,6|2,0:0|0:0,0:0:0:0: +400,208,73040,1,2,0:0:0:0: +472,208,73207,6,2,L|480:96,1,100,2|0,0:0|0:0,0:0:0:0: +416,80,73707,2,0,L|316:80,1,100,0|8,0:0|0:0,0:0:0:0: +176,80,74373,1,0,0:0:0:0: +304,80,74707,6,6,L|400:80,2,80,6|0|12,0:0|0:0|0:0,0:0:0:0: +160,80,75707,2,0,P|112:112|160:224,1,200,0|2,0:0|0:0,0:0:0:0: +304,224,76707,6,8,L|416:224,1,100,10|8,0:0|0:0,0:0:0:0: +212,224,77374,1,12,0:0:0:0: +256,192,77707,12,2,80041,0:0:0:0: +368,192,82374,5,0,0:0:0:0: +224,192,82707,2,6,P|176:160|224:104,1,150,6|0,0:0|0:0,0:0:0:0: +368,80,84041,2,6,L|384:240,1,150,6|0,0:0|0:0,0:0:0:0: +240,256,85374,6,6,P|168:212|240:160,1,200,6|10,0:0|0:0,0:0:0:0: +368,160,86374,1,0,0:0:0:0: +224,160,86707,6,8,L|112:160,1,100,8|0,0:0|0:0,0:0:0:0: +256,128,87374,2,8,L|368:128,1,100,8|2,0:0|0:0,0:0:0:0: +184,128,88041,5,6,0:0:0:0: +352,128,88374,2,8,L|368:240,1,100,8|8,0:0|0:0,0:0:0:0: +224,240,89041,1,8,0:0:0:0: +366,228,89374,6,0,L|472:224,2,100,2|8|8,0:0|0:0|0:0,0:0:0:0: +248,240,90374,1,8,0:0:0:0: +368,232,90707,6,0,L|376:128,1,100,2|8,0:0|0:0,0:0:0:0: +256,104,91374,2,0,L|152:104,1,100,8|8,0:0|0:0,0:0:0:0: +256,240,92041,6,2,L|368:240,1,100,2|8,0:0|0:0,0:0:0:0: +224,240,92707,2,8,L|120:240,1,100,8|2,0:0|0:0,0:0:0:0: +256,144,93374,5,6,0:0:0:0: +128,144,93707,2,8,L|16:144,2,100,8|8|8,0:0|0:0|0:0,0:0:0:0: +256,144,94707,6,2,L|264:248,1,100,2|8,0:0|0:0,0:0:0:0: +144,312,95374,1,8,0:0:0:0: +96,312,95540,1,8,0:0:0:0: +48,312,95707,1,8,0:0:0:0: +168,208,96041,6,6,L|272:208,1,100,6|0,0:0|0:0,0:0:0:0: +152,104,96707,2,8,L|144:208,1,100 +280,296,97374,6,8,P|369:254|422:171,1,200,10|8,0:0|0:0,0:0:0:0: +144,144,98707,1,14,0:0:0:0: +256,192,99040,12,0,105373,0:0:0:0: diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/1431386-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/1431386-expected-conversion.json new file mode 100644 index 0000000000..de879d0d1c --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/1431386-expected-conversion.json @@ -0,0 +1 @@ +{"Mappings":[{"StartTime":534.0,"Objects":[{"StartTime":534.0,"Position":333.0,"HyperDash":false},{"StartTime":589.0,"Position":336.445465,"HyperDash":false},{"StartTime":645.0,"Position":359.226318,"HyperDash":false},{"StartTime":701.0,"Position":386.604523,"HyperDash":false},{"StartTime":757.0,"Position":424.50647,"HyperDash":false},{"StartTime":813.0,"Position":446.4084,"HyperDash":false},{"StartTime":869.0,"Position":450.310333,"HyperDash":false},{"StartTime":925.0,"Position":468.21228,"HyperDash":false},{"StartTime":981.0,"Position":489.2919,"HyperDash":false},{"StartTime":1032.0,"Position":456.3446,"HyperDash":false},{"StartTime":1084.0,"Position":444.864258,"HyperDash":false},{"StartTime":1135.0,"Position":422.7393,"HyperDash":false},{"StartTime":1187.0,"Position":412.2589,"HyperDash":false},{"StartTime":1238.0,"Position":412.133942,"HyperDash":false},{"StartTime":1290.0,"Position":383.653564,"HyperDash":false},{"StartTime":1341.0,"Position":353.512756,"HyperDash":false},{"StartTime":1429.0,"Position":333.0,"HyperDash":false}]},{"StartTime":1877.0,"Objects":[{"StartTime":1877.0,"Position":182.0,"HyperDash":false}]},{"StartTime":2325.0,"Objects":[{"StartTime":2325.0,"Position":333.0,"HyperDash":false},{"StartTime":2380.0,"Position":357.239044,"HyperDash":false},{"StartTime":2436.0,"Position":352.8279,"HyperDash":false},{"StartTime":2492.0,"Position":382.475677,"HyperDash":false},{"StartTime":2548.0,"Position":429.244324,"HyperDash":false},{"StartTime":2604.0,"Position":448.013,"HyperDash":false},{"StartTime":2660.0,"Position":463.721436,"HyperDash":false},{"StartTime":2716.0,"Position":462.368317,"HyperDash":false},{"StartTime":2772.0,"Position":490.190643,"HyperDash":false},{"StartTime":2823.0,"Position":455.473358,"HyperDash":false},{"StartTime":2875.0,"Position":467.2298,"HyperDash":false},{"StartTime":2926.0,"Position":431.3082,"HyperDash":false},{"StartTime":2978.0,"Position":421.951569,"HyperDash":false},{"StartTime":3029.0,"Position":384.947937,"HyperDash":false},{"StartTime":3081.0,"Position":377.6223,"HyperDash":false},{"StartTime":3132.0,"Position":362.782471,"HyperDash":false},{"StartTime":3220.0,"Position":333.0,"HyperDash":false}]},{"StartTime":3668.0,"Objects":[{"StartTime":3668.0,"Position":182.0,"HyperDash":false}]},{"StartTime":4116.0,"Objects":[{"StartTime":4116.0,"Position":26.0,"HyperDash":false},{"StartTime":4171.0,"Position":40.9041862,"HyperDash":false},{"StartTime":4227.0,"Position":12.82481,"HyperDash":false},{"StartTime":4283.0,"Position":9.745436,"HyperDash":false},{"StartTime":4339.0,"Position":29.67428,"HyperDash":false},{"StartTime":4433.0,"Position":12.1371651,"HyperDash":false},{"StartTime":4563.0,"Position":26.0,"HyperDash":false}]},{"StartTime":5011.0,"Objects":[{"StartTime":5011.0,"Position":20.0,"HyperDash":false},{"StartTime":5104.0,"Position":66.26816,"HyperDash":false},{"StartTime":5234.0,"Position":97.6557159,"HyperDash":false}]},{"StartTime":5459.0,"Objects":[{"StartTime":5459.0,"Position":178.0,"HyperDash":false},{"StartTime":5552.0,"Position":226.229477,"HyperDash":false},{"StartTime":5682.0,"Position":255.569565,"HyperDash":false}]},{"StartTime":5907.0,"Objects":[{"StartTime":5907.0,"Position":308.0,"HyperDash":false},{"StartTime":5990.0,"Position":336.486633,"HyperDash":false},{"StartTime":6074.0,"Position":360.3285,"HyperDash":false},{"StartTime":6158.0,"Position":396.17038,"HyperDash":false},{"StartTime":6242.0,"Position":427.1899,"HyperDash":false},{"StartTime":6317.0,"Position":419.723,"HyperDash":false},{"StartTime":6392.0,"Position":368.078461,"HyperDash":false},{"StartTime":6467.0,"Position":352.433929,"HyperDash":false},{"StartTime":6578.0,"Position":308.0,"HyperDash":false}]},{"StartTime":6802.0,"Objects":[{"StartTime":6802.0,"Position":224.0,"HyperDash":false},{"StartTime":6853.0,"Position":226.916428,"HyperDash":false},{"StartTime":6904.0,"Position":222.886032,"HyperDash":false},{"StartTime":6956.0,"Position":216.946533,"HyperDash":false},{"StartTime":7007.0,"Position":211.428284,"HyperDash":false},{"StartTime":7058.0,"Position":212.341827,"HyperDash":false},{"StartTime":7110.0,"Position":205.693756,"HyperDash":false},{"StartTime":7161.0,"Position":183.379547,"HyperDash":false},{"StartTime":7249.0,"Position":212.117065,"HyperDash":false}]},{"StartTime":7698.0,"Objects":[{"StartTime":7698.0,"Position":372.0,"HyperDash":false},{"StartTime":7791.0,"Position":392.5109,"HyperDash":false},{"StartTime":7921.0,"Position":392.363617,"HyperDash":false}]},{"StartTime":8145.0,"Objects":[{"StartTime":8145.0,"Position":390.0,"HyperDash":false},{"StartTime":8228.0,"Position":407.6116,"HyperDash":false},{"StartTime":8312.0,"Position":434.579956,"HyperDash":false},{"StartTime":8396.0,"Position":497.5483,"HyperDash":false},{"StartTime":8480.0,"Position":509.695038,"HyperDash":false},{"StartTime":8555.0,"Position":475.115967,"HyperDash":false},{"StartTime":8630.0,"Position":472.3585,"HyperDash":false},{"StartTime":8705.0,"Position":432.601044,"HyperDash":false},{"StartTime":8816.0,"Position":390.0,"HyperDash":false}]},{"StartTime":9041.0,"Objects":[{"StartTime":9041.0,"Position":330.0,"HyperDash":false},{"StartTime":9134.0,"Position":286.7251,"HyperDash":false},{"StartTime":9264.0,"Position":250.211823,"HyperDash":false}]},{"StartTime":9489.0,"Objects":[{"StartTime":9489.0,"Position":171.0,"HyperDash":false},{"StartTime":9582.0,"Position":139.4586,"HyperDash":false},{"StartTime":9712.0,"Position":92.77017,"HyperDash":false}]},{"StartTime":9936.0,"Objects":[{"StartTime":9936.0,"Position":9.0,"HyperDash":false},{"StartTime":10019.0,"Position":0.0,"HyperDash":false},{"StartTime":10103.0,"Position":4.53266668,"HyperDash":false},{"StartTime":10187.0,"Position":0.0,"HyperDash":false},{"StartTime":10271.0,"Position":0.02520752,"HyperDash":false},{"StartTime":10346.0,"Position":0.0,"HyperDash":false},{"StartTime":10421.0,"Position":12.0244074,"HyperDash":false},{"StartTime":10496.0,"Position":0.0,"HyperDash":false},{"StartTime":10607.0,"Position":9.0,"HyperDash":false}]},{"StartTime":10832.0,"Objects":[{"StartTime":10832.0,"Position":28.0,"HyperDash":false},{"StartTime":10925.0,"Position":40.7889824,"HyperDash":false},{"StartTime":11055.0,"Position":105.537766,"HyperDash":false}]},{"StartTime":11280.0,"Objects":[{"StartTime":11280.0,"Position":263.0,"HyperDash":false}]},{"StartTime":11728.0,"Objects":[{"StartTime":11728.0,"Position":343.0,"HyperDash":false},{"StartTime":11811.0,"Position":365.302277,"HyperDash":false},{"StartTime":11895.0,"Position":388.675323,"HyperDash":false},{"StartTime":11979.0,"Position":437.668274,"HyperDash":false},{"StartTime":12063.0,"Position":459.2406,"HyperDash":false},{"StartTime":12138.0,"Position":431.2186,"HyperDash":false},{"StartTime":12213.0,"Position":423.446381,"HyperDash":false},{"StartTime":12288.0,"Position":362.9473,"HyperDash":false},{"StartTime":12399.0,"Position":343.0,"HyperDash":false}]},{"StartTime":12623.0,"Objects":[{"StartTime":12623.0,"Position":290.0,"HyperDash":false},{"StartTime":12716.0,"Position":297.645538,"HyperDash":false},{"StartTime":12846.0,"Position":296.3436,"HyperDash":false}]},{"StartTime":13071.0,"Objects":[{"StartTime":13071.0,"Position":265.0,"HyperDash":false},{"StartTime":13164.0,"Position":251.816544,"HyperDash":false},{"StartTime":13294.0,"Position":186.7354,"HyperDash":false}]},{"StartTime":13519.0,"Objects":[{"StartTime":13519.0,"Position":123.0,"HyperDash":false},{"StartTime":13602.0,"Position":103.378716,"HyperDash":false},{"StartTime":13686.0,"Position":73.40055,"HyperDash":false},{"StartTime":13770.0,"Position":37.4223862,"HyperDash":false},{"StartTime":13854.0,"Position":3.26579285,"HyperDash":false},{"StartTime":13929.0,"Position":36.85356,"HyperDash":false},{"StartTime":14004.0,"Position":72.61978,"HyperDash":false},{"StartTime":14079.0,"Position":68.38599,"HyperDash":false},{"StartTime":14190.0,"Position":123.0,"HyperDash":false}]},{"StartTime":14414.0,"Objects":[{"StartTime":14414.0,"Position":371.0,"HyperDash":false}]},{"StartTime":14862.0,"Objects":[{"StartTime":14862.0,"Position":184.0,"HyperDash":false},{"StartTime":14955.0,"Position":212.186356,"HyperDash":false},{"StartTime":15085.0,"Position":261.4036,"HyperDash":false}]},{"StartTime":15310.0,"Objects":[{"StartTime":15310.0,"Position":343.0,"HyperDash":false},{"StartTime":15393.0,"Position":362.374176,"HyperDash":false},{"StartTime":15477.0,"Position":407.102234,"HyperDash":false},{"StartTime":15561.0,"Position":440.8303,"HyperDash":false},{"StartTime":15645.0,"Position":461.735352,"HyperDash":false},{"StartTime":15720.0,"Position":439.369354,"HyperDash":false},{"StartTime":15795.0,"Position":398.826447,"HyperDash":false},{"StartTime":15870.0,"Position":372.283539,"HyperDash":false},{"StartTime":15981.0,"Position":343.0,"HyperDash":false}]},{"StartTime":16205.0,"Objects":[{"StartTime":16205.0,"Position":128.0,"HyperDash":false}]},{"StartTime":16653.0,"Objects":[{"StartTime":16653.0,"Position":219.0,"HyperDash":false},{"StartTime":16746.0,"Position":193.135818,"HyperDash":false},{"StartTime":16876.0,"Position":141.577332,"HyperDash":false}]},{"StartTime":17101.0,"Objects":[{"StartTime":17101.0,"Position":65.0,"HyperDash":false},{"StartTime":17184.0,"Position":56.4695549,"HyperDash":false},{"StartTime":17268.0,"Position":32.94629,"HyperDash":false},{"StartTime":17352.0,"Position":29.3506489,"HyperDash":false},{"StartTime":17436.0,"Position":17.0841427,"HyperDash":false},{"StartTime":17511.0,"Position":18.8012981,"HyperDash":false},{"StartTime":17586.0,"Position":4.30590057,"HyperDash":false},{"StartTime":17661.0,"Position":19.18378,"HyperDash":false},{"StartTime":17772.0,"Position":65.0,"HyperDash":false}]},{"StartTime":17996.0,"Objects":[{"StartTime":17996.0,"Position":144.0,"HyperDash":false},{"StartTime":18089.0,"Position":144.091827,"HyperDash":false},{"StartTime":18219.0,"Position":137.026642,"HyperDash":false}]},{"StartTime":18444.0,"Objects":[{"StartTime":18444.0,"Position":156.0,"HyperDash":false},{"StartTime":18537.0,"Position":195.74173,"HyperDash":false},{"StartTime":18667.0,"Position":233.945068,"HyperDash":false}]},{"StartTime":18892.0,"Objects":[{"StartTime":18892.0,"Position":309.0,"HyperDash":false},{"StartTime":18975.0,"Position":331.4903,"HyperDash":false},{"StartTime":19059.0,"Position":371.3359,"HyperDash":false},{"StartTime":19143.0,"Position":396.1815,"HyperDash":false},{"StartTime":19227.0,"Position":428.204742,"HyperDash":false},{"StartTime":19302.0,"Position":418.734558,"HyperDash":false},{"StartTime":19377.0,"Position":358.08667,"HyperDash":false},{"StartTime":19452.0,"Position":359.438843,"HyperDash":false},{"StartTime":19563.0,"Position":309.0,"HyperDash":false}]},{"StartTime":19787.0,"Objects":[{"StartTime":19787.0,"Position":237.0,"HyperDash":false},{"StartTime":19880.0,"Position":210.372055,"HyperDash":false},{"StartTime":20010.0,"Position":234.5058,"HyperDash":false}]},{"StartTime":20235.0,"Objects":[{"StartTime":20235.0,"Position":296.0,"HyperDash":false},{"StartTime":20328.0,"Position":335.3686,"HyperDash":false},{"StartTime":20458.0,"Position":374.402649,"HyperDash":false}]},{"StartTime":20683.0,"Objects":[{"StartTime":20683.0,"Position":441.0,"HyperDash":false},{"StartTime":20766.0,"Position":438.742676,"HyperDash":false},{"StartTime":20850.0,"Position":413.918945,"HyperDash":false},{"StartTime":20934.0,"Position":420.274963,"HyperDash":false},{"StartTime":21018.0,"Position":440.574921,"HyperDash":false},{"StartTime":21093.0,"Position":428.433563,"HyperDash":false},{"StartTime":21168.0,"Position":429.064026,"HyperDash":false},{"StartTime":21243.0,"Position":410.101563,"HyperDash":false},{"StartTime":21354.0,"Position":441.0,"HyperDash":false}]},{"StartTime":21578.0,"Objects":[{"StartTime":21578.0,"Position":501.0,"HyperDash":false}]},{"StartTime":22026.0,"Objects":[{"StartTime":22026.0,"Position":386.0,"HyperDash":false},{"StartTime":22081.0,"Position":374.485016,"HyperDash":false},{"StartTime":22137.0,"Position":357.487,"HyperDash":false},{"StartTime":22193.0,"Position":318.665863,"HyperDash":false},{"StartTime":22249.0,"Position":311.3857,"HyperDash":false},{"StartTime":22305.0,"Position":300.98407,"HyperDash":false},{"StartTime":22361.0,"Position":266.708557,"HyperDash":false},{"StartTime":22417.0,"Position":256.6825,"HyperDash":false},{"StartTime":22473.0,"Position":240.899826,"HyperDash":false},{"StartTime":22529.0,"Position":227.386124,"HyperDash":false},{"StartTime":22585.0,"Position":225.861679,"HyperDash":false},{"StartTime":22641.0,"Position":185.350357,"HyperDash":false},{"StartTime":22697.0,"Position":169.039291,"HyperDash":false},{"StartTime":22753.0,"Position":131.207657,"HyperDash":false},{"StartTime":22809.0,"Position":115.215012,"HyperDash":false},{"StartTime":22865.0,"Position":108.42057,"HyperDash":false},{"StartTime":22921.0,"Position":89.93976,"HyperDash":false},{"StartTime":22977.0,"Position":126.071373,"HyperDash":false},{"StartTime":23033.0,"Position":140.858871,"HyperDash":false},{"StartTime":23089.0,"Position":159.8509,"HyperDash":false},{"StartTime":23145.0,"Position":166.689056,"HyperDash":false},{"StartTime":23201.0,"Position":205.013,"HyperDash":false},{"StartTime":23257.0,"Position":197.5373,"HyperDash":false},{"StartTime":23313.0,"Position":239.081787,"HyperDash":false},{"StartTime":23369.0,"Position":240.611664,"HyperDash":false},{"StartTime":23420.0,"Position":243.039688,"HyperDash":false},{"StartTime":23472.0,"Position":272.749237,"HyperDash":false},{"StartTime":23523.0,"Position":272.238831,"HyperDash":false},{"StartTime":23575.0,"Position":317.028137,"HyperDash":false},{"StartTime":23626.0,"Position":306.314117,"HyperDash":false},{"StartTime":23678.0,"Position":335.531525,"HyperDash":false},{"StartTime":23729.0,"Position":341.698853,"HyperDash":false},{"StartTime":23817.0,"Position":386.0,"HyperDash":false}]},{"StartTime":24041.0,"Objects":[{"StartTime":24041.0,"Position":465.0,"HyperDash":false}]},{"StartTime":24265.0,"Objects":[{"StartTime":24265.0,"Position":497.0,"HyperDash":false},{"StartTime":24348.0,"Position":488.55304,"HyperDash":false},{"StartTime":24432.0,"Position":484.0766,"HyperDash":false},{"StartTime":24516.0,"Position":480.600128,"HyperDash":false},{"StartTime":24600.0,"Position":487.108948,"HyperDash":false},{"StartTime":24675.0,"Position":484.305328,"HyperDash":false},{"StartTime":24750.0,"Position":486.516449,"HyperDash":false},{"StartTime":24825.0,"Position":507.727539,"HyperDash":false},{"StartTime":24936.0,"Position":497.0,"HyperDash":false}]},{"StartTime":25160.0,"Objects":[{"StartTime":25160.0,"Position":410.0,"HyperDash":false},{"StartTime":25253.0,"Position":380.109436,"HyperDash":false},{"StartTime":25383.0,"Position":332.0014,"HyperDash":false}]},{"StartTime":25608.0,"Objects":[{"StartTime":25608.0,"Position":262.0,"HyperDash":false},{"StartTime":25701.0,"Position":218.3702,"HyperDash":false},{"StartTime":25831.0,"Position":184.296768,"HyperDash":false}]},{"StartTime":26056.0,"Objects":[{"StartTime":26056.0,"Position":136.0,"HyperDash":false},{"StartTime":26139.0,"Position":126.098541,"HyperDash":false},{"StartTime":26223.0,"Position":125.222366,"HyperDash":false},{"StartTime":26307.0,"Position":138.346191,"HyperDash":false},{"StartTime":26391.0,"Position":144.482666,"HyperDash":false},{"StartTime":26466.0,"Position":145.59903,"HyperDash":false},{"StartTime":26541.0,"Position":121.702759,"HyperDash":false},{"StartTime":26616.0,"Position":138.806488,"HyperDash":false},{"StartTime":26727.0,"Position":136.0,"HyperDash":false}]},{"StartTime":26951.0,"Objects":[{"StartTime":26951.0,"Position":67.0,"HyperDash":false}]},{"StartTime":27399.0,"Objects":[{"StartTime":27399.0,"Position":118.0,"HyperDash":false},{"StartTime":27454.0,"Position":149.263077,"HyperDash":false},{"StartTime":27510.0,"Position":152.985062,"HyperDash":false},{"StartTime":27566.0,"Position":191.9209,"HyperDash":false},{"StartTime":27622.0,"Position":186.1002,"HyperDash":false},{"StartTime":27678.0,"Position":201.49527,"HyperDash":false},{"StartTime":27734.0,"Position":213.367828,"HyperDash":false},{"StartTime":27790.0,"Position":256.814331,"HyperDash":false},{"StartTime":27846.0,"Position":246.461456,"HyperDash":false},{"StartTime":27940.0,"Position":268.489075,"HyperDash":false},{"StartTime":28070.0,"Position":233.472458,"HyperDash":false}]},{"StartTime":28295.0,"Objects":[{"StartTime":28295.0,"Position":162.0,"HyperDash":false},{"StartTime":28350.0,"Position":164.220917,"HyperDash":false},{"StartTime":28406.0,"Position":194.79129,"HyperDash":false},{"StartTime":28462.0,"Position":226.3617,"HyperDash":false},{"StartTime":28518.0,"Position":251.932068,"HyperDash":false},{"StartTime":28574.0,"Position":246.033783,"HyperDash":false},{"StartTime":28630.0,"Position":264.13385,"HyperDash":false},{"StartTime":28686.0,"Position":278.233948,"HyperDash":false},{"StartTime":28742.0,"Position":316.344543,"HyperDash":false},{"StartTime":28836.0,"Position":335.418,"HyperDash":false},{"StartTime":28966.0,"Position":395.157867,"HyperDash":false}]},{"StartTime":29190.0,"Objects":[{"StartTime":29190.0,"Position":481.0,"HyperDash":false}]},{"StartTime":29414.0,"Objects":[{"StartTime":29414.0,"Position":499.0,"HyperDash":false}]},{"StartTime":29638.0,"Objects":[{"StartTime":29638.0,"Position":454.0,"HyperDash":false},{"StartTime":29721.0,"Position":464.071655,"HyperDash":false},{"StartTime":29805.0,"Position":475.192383,"HyperDash":false},{"StartTime":29889.0,"Position":456.3131,"HyperDash":false},{"StartTime":29973.0,"Position":470.458374,"HyperDash":false},{"StartTime":30048.0,"Position":459.80368,"HyperDash":false},{"StartTime":30123.0,"Position":473.124451,"HyperDash":false},{"StartTime":30198.0,"Position":468.445251,"HyperDash":false},{"StartTime":30309.0,"Position":454.0,"HyperDash":false}]},{"StartTime":30533.0,"Objects":[{"StartTime":30533.0,"Position":375.0,"HyperDash":false},{"StartTime":30626.0,"Position":348.741882,"HyperDash":false},{"StartTime":30756.0,"Position":297.814758,"HyperDash":false}]},{"StartTime":30981.0,"Objects":[{"StartTime":30981.0,"Position":220.0,"HyperDash":false},{"StartTime":31036.0,"Position":200.494568,"HyperDash":false},{"StartTime":31092.0,"Position":189.8578,"HyperDash":false},{"StartTime":31148.0,"Position":158.568909,"HyperDash":false},{"StartTime":31204.0,"Position":137.831863,"HyperDash":false},{"StartTime":31260.0,"Position":143.862488,"HyperDash":false},{"StartTime":31316.0,"Position":99.86672,"HyperDash":false},{"StartTime":31372.0,"Position":85.05304,"HyperDash":false},{"StartTime":31428.0,"Position":65.47009,"HyperDash":false},{"StartTime":31479.0,"Position":97.9493561,"HyperDash":false},{"StartTime":31531.0,"Position":85.30683,"HyperDash":false},{"StartTime":31582.0,"Position":136.499527,"HyperDash":false},{"StartTime":31634.0,"Position":141.072418,"HyperDash":false},{"StartTime":31685.0,"Position":152.152847,"HyperDash":false},{"StartTime":31737.0,"Position":182.289108,"HyperDash":false},{"StartTime":31788.0,"Position":190.604156,"HyperDash":false},{"StartTime":31876.0,"Position":220.0,"HyperDash":false}]},{"StartTime":32325.0,"Objects":[{"StartTime":32325.0,"Position":365.0,"HyperDash":false}]},{"StartTime":32772.0,"Objects":[{"StartTime":32772.0,"Position":480.0,"HyperDash":false},{"StartTime":32823.0,"Position":493.32843,"HyperDash":false},{"StartTime":32874.0,"Position":464.65686,"HyperDash":false},{"StartTime":32926.0,"Position":458.9525,"HyperDash":false},{"StartTime":32977.0,"Position":466.280945,"HyperDash":false},{"StartTime":33028.0,"Position":453.609375,"HyperDash":false},{"StartTime":33080.0,"Position":465.905029,"HyperDash":false},{"StartTime":33131.0,"Position":473.233459,"HyperDash":false},{"StartTime":33219.0,"Position":465.349182,"HyperDash":false}]},{"StartTime":33444.0,"Objects":[{"StartTime":33444.0,"Position":322.0,"HyperDash":false}]},{"StartTime":33668.0,"Objects":[{"StartTime":33668.0,"Position":323.0,"HyperDash":false},{"StartTime":33761.0,"Position":290.802338,"HyperDash":false},{"StartTime":33891.0,"Position":243.397018,"HyperDash":false}]},{"StartTime":34116.0,"Objects":[{"StartTime":34116.0,"Position":162.0,"HyperDash":false},{"StartTime":34209.0,"Position":126.802353,"HyperDash":false},{"StartTime":34339.0,"Position":82.39702,"HyperDash":false}]},{"StartTime":34563.0,"Objects":[{"StartTime":34563.0,"Position":31.0,"HyperDash":false},{"StartTime":34618.0,"Position":38.3338165,"HyperDash":false},{"StartTime":34674.0,"Position":12.5252123,"HyperDash":false},{"StartTime":34730.0,"Position":24.94529,"HyperDash":false},{"StartTime":34786.0,"Position":0.0,"HyperDash":false},{"StartTime":34842.0,"Position":7.506119,"HyperDash":false},{"StartTime":34898.0,"Position":0.0,"HyperDash":false},{"StartTime":34954.0,"Position":18.1432285,"HyperDash":false},{"StartTime":35010.0,"Position":21.8685,"HyperDash":false},{"StartTime":35061.0,"Position":25.771328,"HyperDash":false},{"StartTime":35113.0,"Position":7.32367039,"HyperDash":false},{"StartTime":35164.0,"Position":0.0,"HyperDash":false},{"StartTime":35216.0,"Position":12.3119221,"HyperDash":false},{"StartTime":35267.0,"Position":14.6618919,"HyperDash":false},{"StartTime":35319.0,"Position":12.9432926,"HyperDash":false},{"StartTime":35370.0,"Position":0.05334282,"HyperDash":false},{"StartTime":35458.0,"Position":31.0,"HyperDash":false}]},{"StartTime":35907.0,"Objects":[{"StartTime":35907.0,"Position":183.0,"HyperDash":false}]},{"StartTime":36354.0,"Objects":[{"StartTime":36354.0,"Position":336.0,"HyperDash":false},{"StartTime":36409.0,"Position":332.550262,"HyperDash":false},{"StartTime":36465.0,"Position":357.661743,"HyperDash":false},{"StartTime":36521.0,"Position":395.893524,"HyperDash":false},{"StartTime":36577.0,"Position":398.9578,"HyperDash":false},{"StartTime":36633.0,"Position":441.6068,"HyperDash":false},{"StartTime":36689.0,"Position":459.563568,"HyperDash":false},{"StartTime":36745.0,"Position":458.55127,"HyperDash":false},{"StartTime":36801.0,"Position":485.465271,"HyperDash":false},{"StartTime":36852.0,"Position":448.681152,"HyperDash":false},{"StartTime":36904.0,"Position":431.13797,"HyperDash":false},{"StartTime":36955.0,"Position":444.931641,"HyperDash":false},{"StartTime":37007.0,"Position":413.575562,"HyperDash":false},{"StartTime":37058.0,"Position":398.977661,"HyperDash":false},{"StartTime":37110.0,"Position":374.650665,"HyperDash":false},{"StartTime":37161.0,"Position":348.4818,"HyperDash":false},{"StartTime":37249.0,"Position":336.0,"HyperDash":false}]},{"StartTime":37474.0,"Objects":[{"StartTime":37474.0,"Position":278.0,"HyperDash":false}]},{"StartTime":37698.0,"Objects":[{"StartTime":37698.0,"Position":218.0,"HyperDash":false},{"StartTime":37791.0,"Position":186.661133,"HyperDash":false},{"StartTime":37921.0,"Position":141.792221,"HyperDash":false}]},{"StartTime":38145.0,"Objects":[{"StartTime":38145.0,"Position":55.0,"HyperDash":false},{"StartTime":38196.0,"Position":55.39138,"HyperDash":false},{"StartTime":38247.0,"Position":17.7827568,"HyperDash":false},{"StartTime":38299.0,"Position":25.8781147,"HyperDash":false},{"StartTime":38350.0,"Position":15.6772919,"HyperDash":false},{"StartTime":38401.0,"Position":46.47647,"HyperDash":false},{"StartTime":38453.0,"Position":19.3305359,"HyperDash":false},{"StartTime":38504.0,"Position":58.12971,"HyperDash":false},{"StartTime":38592.0,"Position":45.9596672,"HyperDash":false}]},{"StartTime":39041.0,"Objects":[{"StartTime":39041.0,"Position":188.0,"HyperDash":false},{"StartTime":39092.0,"Position":206.608627,"HyperDash":false},{"StartTime":39143.0,"Position":207.217239,"HyperDash":false},{"StartTime":39195.0,"Position":212.121887,"HyperDash":false},{"StartTime":39246.0,"Position":222.322708,"HyperDash":false},{"StartTime":39297.0,"Position":209.523529,"HyperDash":false},{"StartTime":39349.0,"Position":205.669464,"HyperDash":false},{"StartTime":39400.0,"Position":188.870285,"HyperDash":false},{"StartTime":39488.0,"Position":197.040329,"HyperDash":false}]},{"StartTime":39936.0,"Objects":[{"StartTime":39936.0,"Position":305.0,"HyperDash":false},{"StartTime":39987.0,"Position":326.221222,"HyperDash":false},{"StartTime":40038.0,"Position":329.12558,"HyperDash":false},{"StartTime":40090.0,"Position":351.555145,"HyperDash":false},{"StartTime":40141.0,"Position":355.340942,"HyperDash":false},{"StartTime":40192.0,"Position":390.523621,"HyperDash":false},{"StartTime":40244.0,"Position":399.5398,"HyperDash":false},{"StartTime":40295.0,"Position":402.617462,"HyperDash":false},{"StartTime":40383.0,"Position":452.46936,"HyperDash":false}]},{"StartTime":40832.0,"Objects":[{"StartTime":40832.0,"Position":486.0,"HyperDash":false},{"StartTime":40915.0,"Position":469.7972,"HyperDash":false},{"StartTime":40999.0,"Position":481.8138,"HyperDash":false},{"StartTime":41083.0,"Position":457.634216,"HyperDash":false},{"StartTime":41167.0,"Position":437.2155,"HyperDash":false},{"StartTime":41242.0,"Position":451.25293,"HyperDash":false},{"StartTime":41317.0,"Position":459.7593,"HyperDash":false},{"StartTime":41392.0,"Position":473.703156,"HyperDash":false},{"StartTime":41503.0,"Position":486.0,"HyperDash":false}]},{"StartTime":41728.0,"Objects":[{"StartTime":41728.0,"Position":415.0,"HyperDash":false},{"StartTime":41783.0,"Position":390.7221,"HyperDash":false},{"StartTime":41839.0,"Position":366.340027,"HyperDash":false},{"StartTime":41895.0,"Position":357.472321,"HyperDash":false},{"StartTime":41951.0,"Position":323.4682,"HyperDash":false},{"StartTime":42007.0,"Position":318.667938,"HyperDash":false},{"StartTime":42063.0,"Position":313.410736,"HyperDash":false},{"StartTime":42119.0,"Position":269.011841,"HyperDash":false},{"StartTime":42175.0,"Position":262.671448,"HyperDash":false},{"StartTime":42226.0,"Position":272.1187,"HyperDash":false},{"StartTime":42278.0,"Position":312.04538,"HyperDash":false},{"StartTime":42329.0,"Position":293.437958,"HyperDash":false},{"StartTime":42381.0,"Position":345.712128,"HyperDash":false},{"StartTime":42432.0,"Position":366.896667,"HyperDash":false},{"StartTime":42484.0,"Position":350.446564,"HyperDash":false},{"StartTime":42535.0,"Position":369.3803,"HyperDash":false},{"StartTime":42623.0,"Position":415.0,"HyperDash":false}]},{"StartTime":43071.0,"Objects":[{"StartTime":43071.0,"Position":353.0,"HyperDash":false}]},{"StartTime":43519.0,"Objects":[{"StartTime":43519.0,"Position":181.0,"HyperDash":false},{"StartTime":43570.0,"Position":174.8302,"HyperDash":false},{"StartTime":43621.0,"Position":156.660385,"HyperDash":false},{"StartTime":43673.0,"Position":141.134308,"HyperDash":false},{"StartTime":43724.0,"Position":99.9645,"HyperDash":false},{"StartTime":43775.0,"Position":75.79469,"HyperDash":false},{"StartTime":43827.0,"Position":67.26861,"HyperDash":false},{"StartTime":43878.0,"Position":66.0988159,"HyperDash":false},{"StartTime":43966.0,"Position":21.7469788,"HyperDash":false}]},{"StartTime":44414.0,"Objects":[{"StartTime":44414.0,"Position":21.0,"HyperDash":false},{"StartTime":44465.0,"Position":38.1698074,"HyperDash":false},{"StartTime":44516.0,"Position":57.3396149,"HyperDash":false},{"StartTime":44568.0,"Position":68.86569,"HyperDash":false},{"StartTime":44619.0,"Position":110.0355,"HyperDash":false},{"StartTime":44670.0,"Position":121.205307,"HyperDash":false},{"StartTime":44722.0,"Position":123.731384,"HyperDash":false},{"StartTime":44773.0,"Position":164.901184,"HyperDash":false},{"StartTime":44861.0,"Position":180.253021,"HyperDash":false}]},{"StartTime":45086.0,"Objects":[{"StartTime":45086.0,"Position":328.0,"HyperDash":false}]},{"StartTime":45310.0,"Objects":[{"StartTime":45310.0,"Position":329.0,"HyperDash":false},{"StartTime":45365.0,"Position":332.211578,"HyperDash":false},{"StartTime":45421.0,"Position":367.175873,"HyperDash":false},{"StartTime":45477.0,"Position":371.022522,"HyperDash":false},{"StartTime":45533.0,"Position":395.233124,"HyperDash":false},{"StartTime":45589.0,"Position":413.246216,"HyperDash":false},{"StartTime":45645.0,"Position":433.6284,"HyperDash":false},{"StartTime":45701.0,"Position":457.874817,"HyperDash":false},{"StartTime":45757.0,"Position":467.659363,"HyperDash":false},{"StartTime":45813.0,"Position":493.610321,"HyperDash":false},{"StartTime":45869.0,"Position":491.524567,"HyperDash":false},{"StartTime":45925.0,"Position":475.219482,"HyperDash":false},{"StartTime":45981.0,"Position":499.624725,"HyperDash":false},{"StartTime":46037.0,"Position":471.774384,"HyperDash":false},{"StartTime":46093.0,"Position":462.734833,"HyperDash":false},{"StartTime":46149.0,"Position":450.75238,"HyperDash":false},{"StartTime":46205.0,"Position":451.0282,"HyperDash":false},{"StartTime":46256.0,"Position":439.419067,"HyperDash":false},{"StartTime":46308.0,"Position":413.8077,"HyperDash":false},{"StartTime":46359.0,"Position":423.184723,"HyperDash":false},{"StartTime":46411.0,"Position":393.298828,"HyperDash":false},{"StartTime":46462.0,"Position":384.2213,"HyperDash":false},{"StartTime":46514.0,"Position":355.668274,"HyperDash":false},{"StartTime":46565.0,"Position":316.77417,"HyperDash":false},{"StartTime":46653.0,"Position":303.770752,"HyperDash":false}]},{"StartTime":47101.0,"Objects":[{"StartTime":47101.0,"Position":257.0,"HyperDash":false},{"StartTime":47184.0,"Position":212.304276,"HyperDash":false},{"StartTime":47268.0,"Position":213.274872,"HyperDash":false},{"StartTime":47352.0,"Position":179.2254,"HyperDash":false},{"StartTime":47436.0,"Position":142.9541,"HyperDash":false},{"StartTime":47511.0,"Position":150.761337,"HyperDash":false},{"StartTime":47586.0,"Position":198.741776,"HyperDash":false},{"StartTime":47661.0,"Position":220.961136,"HyperDash":false},{"StartTime":47772.0,"Position":257.0,"HyperDash":false}]},{"StartTime":47996.0,"Objects":[{"StartTime":47996.0,"Position":336.0,"HyperDash":false}]},{"StartTime":48220.0,"Objects":[{"StartTime":48220.0,"Position":417.0,"HyperDash":false},{"StartTime":48275.0,"Position":444.6565,"HyperDash":false},{"StartTime":48331.0,"Position":441.67038,"HyperDash":false},{"StartTime":48387.0,"Position":472.684265,"HyperDash":false},{"StartTime":48443.0,"Position":496.876831,"HyperDash":false},{"StartTime":48537.0,"Position":462.460815,"HyperDash":false},{"StartTime":48667.0,"Position":417.0,"HyperDash":false}]},{"StartTime":48892.0,"Objects":[{"StartTime":48892.0,"Position":379.0,"HyperDash":false},{"StartTime":48985.0,"Position":356.006134,"HyperDash":false},{"StartTime":49115.0,"Position":302.860016,"HyperDash":false}]},{"StartTime":49339.0,"Objects":[{"StartTime":49339.0,"Position":218.0,"HyperDash":false},{"StartTime":49422.0,"Position":228.320267,"HyperDash":false},{"StartTime":49506.0,"Position":263.682922,"HyperDash":false},{"StartTime":49590.0,"Position":263.529572,"HyperDash":false},{"StartTime":49674.0,"Position":266.142761,"HyperDash":false},{"StartTime":49749.0,"Position":265.0218,"HyperDash":false},{"StartTime":49824.0,"Position":252.383118,"HyperDash":false},{"StartTime":49899.0,"Position":244.59021,"HyperDash":false},{"StartTime":50010.0,"Position":218.0,"HyperDash":false}]},{"StartTime":50235.0,"Objects":[{"StartTime":50235.0,"Position":142.0,"HyperDash":false},{"StartTime":50328.0,"Position":154.293335,"HyperDash":false},{"StartTime":50458.0,"Position":135.509842,"HyperDash":false}]},{"StartTime":50683.0,"Objects":[{"StartTime":50683.0,"Position":75.0,"HyperDash":false},{"StartTime":50734.0,"Position":106.62645,"HyperDash":false},{"StartTime":50785.0,"Position":89.7852249,"HyperDash":false},{"StartTime":50837.0,"Position":105.419983,"HyperDash":false},{"StartTime":50888.0,"Position":153.41716,"HyperDash":false},{"StartTime":50939.0,"Position":166.651077,"HyperDash":false},{"StartTime":50991.0,"Position":157.985535,"HyperDash":false},{"StartTime":51042.0,"Position":194.261,"HyperDash":false},{"StartTime":51130.0,"Position":222.110641,"HyperDash":false}]},{"StartTime":51354.0,"Objects":[{"StartTime":51354.0,"Position":295.0,"HyperDash":false},{"StartTime":51405.0,"Position":294.626465,"HyperDash":false},{"StartTime":51456.0,"Position":306.785217,"HyperDash":false},{"StartTime":51508.0,"Position":347.419983,"HyperDash":false},{"StartTime":51559.0,"Position":363.417175,"HyperDash":false},{"StartTime":51610.0,"Position":396.6511,"HyperDash":false},{"StartTime":51662.0,"Position":408.985535,"HyperDash":false},{"StartTime":51713.0,"Position":417.261,"HyperDash":false},{"StartTime":51801.0,"Position":442.110657,"HyperDash":false}]},{"StartTime":52026.0,"Objects":[{"StartTime":52026.0,"Position":498.0,"HyperDash":false}]},{"StartTime":52474.0,"Objects":[{"StartTime":52474.0,"Position":404.0,"HyperDash":false},{"StartTime":52567.0,"Position":378.721558,"HyperDash":false},{"StartTime":52697.0,"Position":324.2033,"HyperDash":false}]},{"StartTime":52922.0,"Objects":[{"StartTime":52922.0,"Position":251.0,"HyperDash":false},{"StartTime":53005.0,"Position":216.759811,"HyperDash":false},{"StartTime":53089.0,"Position":195.34903,"HyperDash":false},{"StartTime":53173.0,"Position":148.36676,"HyperDash":false},{"StartTime":53257.0,"Position":135.014374,"HyperDash":false},{"StartTime":53332.0,"Position":141.829834,"HyperDash":false},{"StartTime":53407.0,"Position":167.570328,"HyperDash":false},{"StartTime":53482.0,"Position":217.1065,"HyperDash":false},{"StartTime":53593.0,"Position":251.0,"HyperDash":false}]},{"StartTime":53817.0,"Objects":[{"StartTime":53817.0,"Position":298.0,"HyperDash":false},{"StartTime":53910.0,"Position":296.8232,"HyperDash":false},{"StartTime":54040.0,"Position":295.178223,"HyperDash":false}]},{"StartTime":54265.0,"Objects":[{"StartTime":54265.0,"Position":249.0,"HyperDash":false},{"StartTime":54316.0,"Position":240.835571,"HyperDash":false},{"StartTime":54367.0,"Position":194.671127,"HyperDash":false},{"StartTime":54419.0,"Position":191.150528,"HyperDash":false},{"StartTime":54470.0,"Position":170.708618,"HyperDash":false},{"StartTime":54521.0,"Position":161.552643,"HyperDash":false},{"StartTime":54573.0,"Position":158.896118,"HyperDash":false},{"StartTime":54624.0,"Position":134.782074,"HyperDash":false},{"StartTime":54712.0,"Position":92.52641,"HyperDash":false}]},{"StartTime":55160.0,"Objects":[{"StartTime":55160.0,"Position":8.0,"HyperDash":false},{"StartTime":55253.0,"Position":34.09524,"HyperDash":false},{"StartTime":55383.0,"Position":85.37553,"HyperDash":false}]},{"StartTime":55608.0,"Objects":[{"StartTime":55608.0,"Position":165.0,"HyperDash":false},{"StartTime":55701.0,"Position":183.095245,"HyperDash":false},{"StartTime":55831.0,"Position":242.375519,"HyperDash":false}]},{"StartTime":56056.0,"Objects":[{"StartTime":56056.0,"Position":329.0,"HyperDash":false},{"StartTime":56107.0,"Position":349.227417,"HyperDash":false},{"StartTime":56158.0,"Position":353.454865,"HyperDash":false},{"StartTime":56210.0,"Position":358.902435,"HyperDash":false},{"StartTime":56261.0,"Position":360.282623,"HyperDash":false},{"StartTime":56312.0,"Position":376.968658,"HyperDash":false},{"StartTime":56364.0,"Position":354.628937,"HyperDash":false},{"StartTime":56415.0,"Position":382.314972,"HyperDash":false},{"StartTime":56503.0,"Position":361.04776,"HyperDash":false}]},{"StartTime":56951.0,"Objects":[{"StartTime":56951.0,"Position":189.0,"HyperDash":false},{"StartTime":57044.0,"Position":142.707138,"HyperDash":false},{"StartTime":57174.0,"Position":111.099754,"HyperDash":false}]},{"StartTime":57399.0,"Objects":[{"StartTime":57399.0,"Position":44.0,"HyperDash":false},{"StartTime":57492.0,"Position":42.46508,"HyperDash":false},{"StartTime":57622.0,"Position":57.39981,"HyperDash":false}]},{"StartTime":57847.0,"Objects":[{"StartTime":57847.0,"Position":97.0,"HyperDash":false},{"StartTime":57898.0,"Position":128.653931,"HyperDash":false},{"StartTime":57949.0,"Position":137.733063,"HyperDash":false},{"StartTime":58001.0,"Position":141.3299,"HyperDash":false},{"StartTime":58052.0,"Position":175.3739,"HyperDash":false},{"StartTime":58103.0,"Position":188.865829,"HyperDash":false},{"StartTime":58155.0,"Position":184.813812,"HyperDash":false},{"StartTime":58206.0,"Position":222.592514,"HyperDash":false},{"StartTime":58294.0,"Position":246.818512,"HyperDash":false}]},{"StartTime":58742.0,"Objects":[{"StartTime":58742.0,"Position":396.0,"HyperDash":false},{"StartTime":58835.0,"Position":405.3873,"HyperDash":false},{"StartTime":58965.0,"Position":406.520081,"HyperDash":false}]},{"StartTime":59190.0,"Objects":[{"StartTime":59190.0,"Position":473.0,"HyperDash":false},{"StartTime":59283.0,"Position":484.6127,"HyperDash":false},{"StartTime":59413.0,"Position":462.479919,"HyperDash":false}]},{"StartTime":59638.0,"Objects":[{"StartTime":59638.0,"Position":450.0,"HyperDash":false},{"StartTime":59689.0,"Position":425.546051,"HyperDash":false},{"StartTime":59740.0,"Position":404.6286,"HyperDash":false},{"StartTime":59792.0,"Position":403.0906,"HyperDash":false},{"StartTime":59843.0,"Position":359.851471,"HyperDash":false},{"StartTime":59894.0,"Position":346.7696,"HyperDash":false},{"StartTime":59946.0,"Position":349.71637,"HyperDash":false},{"StartTime":59997.0,"Position":332.582275,"HyperDash":false},{"StartTime":60085.0,"Position":296.934937,"HyperDash":false}]},{"StartTime":60310.0,"Objects":[{"StartTime":60310.0,"Position":137.0,"HyperDash":false}]},{"StartTime":60534.0,"Objects":[{"StartTime":60534.0,"Position":127.0,"HyperDash":false},{"StartTime":60627.0,"Position":133.780716,"HyperDash":false},{"StartTime":60757.0,"Position":121.678482,"HyperDash":false}]},{"StartTime":60981.0,"Objects":[{"StartTime":60981.0,"Position":111.0,"HyperDash":false}]},{"StartTime":61429.0,"Objects":[{"StartTime":61429.0,"Position":110.0,"HyperDash":false},{"StartTime":61512.0,"Position":137.803375,"HyperDash":false},{"StartTime":61596.0,"Position":149.4081,"HyperDash":false},{"StartTime":61680.0,"Position":212.379776,"HyperDash":false},{"StartTime":61764.0,"Position":226.716034,"HyperDash":false},{"StartTime":61839.0,"Position":203.918869,"HyperDash":false},{"StartTime":61914.0,"Position":175.198227,"HyperDash":false},{"StartTime":61989.0,"Position":145.558578,"HyperDash":false},{"StartTime":62100.0,"Position":110.0,"HyperDash":false}]},{"StartTime":62325.0,"Objects":[{"StartTime":62325.0,"Position":22.0,"HyperDash":false},{"StartTime":62418.0,"Position":37.5815735,"HyperDash":false},{"StartTime":62548.0,"Position":18.5988235,"HyperDash":false}]},{"StartTime":62772.0,"Objects":[{"StartTime":62772.0,"Position":2.0,"HyperDash":false}]},{"StartTime":62996.0,"Objects":[{"StartTime":62996.0,"Position":76.0,"HyperDash":false}]},{"StartTime":63220.0,"Objects":[{"StartTime":63220.0,"Position":154.0,"HyperDash":false},{"StartTime":63313.0,"Position":199.111572,"HyperDash":false},{"StartTime":63443.0,"Position":232.57634,"HyperDash":false}]},{"StartTime":63668.0,"Objects":[{"StartTime":63668.0,"Position":307.0,"HyperDash":false},{"StartTime":63751.0,"Position":314.019135,"HyperDash":false},{"StartTime":63835.0,"Position":318.026459,"HyperDash":false},{"StartTime":63919.0,"Position":289.0338,"HyperDash":false},{"StartTime":64003.0,"Position":303.035217,"HyperDash":false},{"StartTime":64078.0,"Position":308.915619,"HyperDash":false},{"StartTime":64153.0,"Position":315.801941,"HyperDash":false},{"StartTime":64228.0,"Position":288.688263,"HyperDash":false},{"StartTime":64339.0,"Position":307.0,"HyperDash":false}]},{"StartTime":64563.0,"Objects":[{"StartTime":64563.0,"Position":311.0,"HyperDash":false},{"StartTime":64656.0,"Position":362.111572,"HyperDash":false},{"StartTime":64786.0,"Position":389.576324,"HyperDash":false}]},{"StartTime":65011.0,"Objects":[{"StartTime":65011.0,"Position":435.0,"HyperDash":false},{"StartTime":65062.0,"Position":440.232056,"HyperDash":false},{"StartTime":65113.0,"Position":422.4641,"HyperDash":false},{"StartTime":65165.0,"Position":444.6811,"HyperDash":false},{"StartTime":65216.0,"Position":423.913147,"HyperDash":false},{"StartTime":65267.0,"Position":441.145172,"HyperDash":false},{"StartTime":65319.0,"Position":427.362183,"HyperDash":false},{"StartTime":65370.0,"Position":412.594238,"HyperDash":false},{"StartTime":65458.0,"Position":428.269135,"HyperDash":false}]},{"StartTime":65683.0,"Objects":[{"StartTime":65683.0,"Position":350.0,"HyperDash":false},{"StartTime":65734.0,"Position":314.27713,"HyperDash":false},{"StartTime":65785.0,"Position":300.566528,"HyperDash":false},{"StartTime":65837.0,"Position":315.7566,"HyperDash":false},{"StartTime":65888.0,"Position":262.77713,"HyperDash":false},{"StartTime":65939.0,"Position":282.5542,"HyperDash":false},{"StartTime":65991.0,"Position":226.007614,"HyperDash":false},{"StartTime":66042.0,"Position":227.1106,"HyperDash":false},{"StartTime":66130.0,"Position":197.703339,"HyperDash":false}]},{"StartTime":66354.0,"Objects":[{"StartTime":66354.0,"Position":36.0,"HyperDash":false}]},{"StartTime":66802.0,"Objects":[{"StartTime":66802.0,"Position":44.0,"HyperDash":false},{"StartTime":66895.0,"Position":34.5778,"HyperDash":false},{"StartTime":67025.0,"Position":49.4306221,"HyperDash":false}]},{"StartTime":67250.0,"Objects":[{"StartTime":67250.0,"Position":131.0,"HyperDash":false},{"StartTime":67333.0,"Position":87.4688339,"HyperDash":false},{"StartTime":67417.0,"Position":59.51071,"HyperDash":false},{"StartTime":67501.0,"Position":67.3197,"HyperDash":false},{"StartTime":67585.0,"Position":34.3176,"HyperDash":false},{"StartTime":67660.0,"Position":32.04751,"HyperDash":false},{"StartTime":67735.0,"Position":85.74523,"HyperDash":false},{"StartTime":67810.0,"Position":81.77102,"HyperDash":false},{"StartTime":67921.0,"Position":131.0,"HyperDash":false}]},{"StartTime":68145.0,"Objects":[{"StartTime":68145.0,"Position":206.0,"HyperDash":false},{"StartTime":68238.0,"Position":241.281784,"HyperDash":false},{"StartTime":68368.0,"Position":285.804718,"HyperDash":false}]},{"StartTime":68593.0,"Objects":[{"StartTime":68593.0,"Position":354.0,"HyperDash":false},{"StartTime":68644.0,"Position":371.9797,"HyperDash":false},{"StartTime":68695.0,"Position":374.9594,"HyperDash":false},{"StartTime":68747.0,"Position":363.977966,"HyperDash":false},{"StartTime":68798.0,"Position":348.931732,"HyperDash":false},{"StartTime":68849.0,"Position":335.783875,"HyperDash":false},{"StartTime":68901.0,"Position":349.448822,"HyperDash":false},{"StartTime":68952.0,"Position":338.5818,"HyperDash":false},{"StartTime":69040.0,"Position":346.262146,"HyperDash":false}]},{"StartTime":69489.0,"Objects":[{"StartTime":69489.0,"Position":479.0,"HyperDash":false},{"StartTime":69582.0,"Position":463.7517,"HyperDash":false},{"StartTime":69712.0,"Position":471.2111,"HyperDash":false}]},{"StartTime":69936.0,"Objects":[{"StartTime":69936.0,"Position":395.0,"HyperDash":false},{"StartTime":70029.0,"Position":351.9091,"HyperDash":false},{"StartTime":70159.0,"Position":317.104523,"HyperDash":false}]},{"StartTime":70384.0,"Objects":[{"StartTime":70384.0,"Position":239.0,"HyperDash":false},{"StartTime":70435.0,"Position":235.932266,"HyperDash":false},{"StartTime":70486.0,"Position":206.714127,"HyperDash":false},{"StartTime":70538.0,"Position":191.116684,"HyperDash":false},{"StartTime":70589.0,"Position":179.00943,"HyperDash":false},{"StartTime":70640.0,"Position":139.19429,"HyperDash":false},{"StartTime":70692.0,"Position":141.486526,"HyperDash":false},{"StartTime":70743.0,"Position":106.327019,"HyperDash":false},{"StartTime":70831.0,"Position":87.14302,"HyperDash":false}]},{"StartTime":71280.0,"Objects":[{"StartTime":71280.0,"Position":11.0,"HyperDash":false},{"StartTime":71373.0,"Position":37.1006241,"HyperDash":false},{"StartTime":71503.0,"Position":90.3703156,"HyperDash":false}]},{"StartTime":71728.0,"Objects":[{"StartTime":71728.0,"Position":152.0,"HyperDash":false},{"StartTime":71821.0,"Position":193.100616,"HyperDash":false},{"StartTime":71951.0,"Position":231.370316,"HyperDash":false}]},{"StartTime":72175.0,"Objects":[{"StartTime":72175.0,"Position":271.0,"HyperDash":false},{"StartTime":72226.0,"Position":263.6878,"HyperDash":false},{"StartTime":72277.0,"Position":283.464,"HyperDash":false},{"StartTime":72329.0,"Position":257.4186,"HyperDash":false},{"StartTime":72380.0,"Position":278.35257,"HyperDash":false},{"StartTime":72431.0,"Position":304.125275,"HyperDash":false},{"StartTime":72483.0,"Position":296.814362,"HyperDash":false},{"StartTime":72534.0,"Position":316.538055,"HyperDash":false},{"StartTime":72622.0,"Position":338.266479,"HyperDash":false}]},{"StartTime":72847.0,"Objects":[{"StartTime":72847.0,"Position":505.0,"HyperDash":false}]},{"StartTime":73071.0,"Objects":[{"StartTime":73071.0,"Position":489.0,"HyperDash":false},{"StartTime":73164.0,"Position":469.365631,"HyperDash":false},{"StartTime":73294.0,"Position":482.683167,"HyperDash":false}]},{"StartTime":73519.0,"Objects":[{"StartTime":73519.0,"Position":408.0,"HyperDash":false},{"StartTime":73612.0,"Position":403.634369,"HyperDash":false},{"StartTime":73742.0,"Position":414.316833,"HyperDash":false}]},{"StartTime":73966.0,"Objects":[{"StartTime":73966.0,"Position":482.0,"HyperDash":false},{"StartTime":74017.0,"Position":472.133667,"HyperDash":false},{"StartTime":74068.0,"Position":425.9474,"HyperDash":false},{"StartTime":74120.0,"Position":412.437225,"HyperDash":false},{"StartTime":74171.0,"Position":412.766479,"HyperDash":false},{"StartTime":74222.0,"Position":404.367828,"HyperDash":false},{"StartTime":74274.0,"Position":384.1732,"HyperDash":false},{"StartTime":74325.0,"Position":361.954468,"HyperDash":false},{"StartTime":74413.0,"Position":325.429016,"HyperDash":false}]},{"StartTime":74862.0,"Objects":[{"StartTime":74862.0,"Position":157.0,"HyperDash":false},{"StartTime":74917.0,"Position":132.397827,"HyperDash":false},{"StartTime":74973.0,"Position":108.439255,"HyperDash":false},{"StartTime":75029.0,"Position":111.480682,"HyperDash":false},{"StartTime":75085.0,"Position":77.3439,"HyperDash":false},{"StartTime":75179.0,"Position":113.667587,"HyperDash":false},{"StartTime":75309.0,"Position":157.0,"HyperDash":false}]},{"StartTime":75534.0,"Objects":[{"StartTime":75534.0,"Position":381.0,"HyperDash":false}]},{"StartTime":75757.0,"Objects":[{"StartTime":75757.0,"Position":288.0,"HyperDash":false},{"StartTime":75812.0,"Position":322.1354,"HyperDash":false},{"StartTime":75868.0,"Position":327.117,"HyperDash":false},{"StartTime":75924.0,"Position":334.290924,"HyperDash":false},{"StartTime":75980.0,"Position":378.117737,"HyperDash":false},{"StartTime":76036.0,"Position":383.1031,"HyperDash":false},{"StartTime":76092.0,"Position":388.735718,"HyperDash":false},{"StartTime":76148.0,"Position":435.4911,"HyperDash":false},{"StartTime":76204.0,"Position":437.060059,"HyperDash":false},{"StartTime":76255.0,"Position":440.4263,"HyperDash":false},{"StartTime":76307.0,"Position":393.159027,"HyperDash":false},{"StartTime":76358.0,"Position":398.4255,"HyperDash":false},{"StartTime":76410.0,"Position":353.908081,"HyperDash":false},{"StartTime":76461.0,"Position":365.736755,"HyperDash":false},{"StartTime":76513.0,"Position":343.5878,"HyperDash":false},{"StartTime":76564.0,"Position":302.556519,"HyperDash":false},{"StartTime":76652.0,"Position":288.0,"HyperDash":false}]},{"StartTime":76877.0,"Objects":[{"StartTime":76877.0,"Position":225.0,"HyperDash":false},{"StartTime":76932.0,"Position":237.844727,"HyperDash":false},{"StartTime":76988.0,"Position":244.722977,"HyperDash":false},{"StartTime":77044.0,"Position":249.601242,"HyperDash":false},{"StartTime":77100.0,"Position":232.496277,"HyperDash":false},{"StartTime":77194.0,"Position":239.360245,"HyperDash":false},{"StartTime":77324.0,"Position":225.0,"HyperDash":false}]},{"StartTime":77548.0,"Objects":[{"StartTime":77548.0,"Position":172.0,"HyperDash":false},{"StartTime":77599.0,"Position":161.128448,"HyperDash":false},{"StartTime":77650.0,"Position":135.2569,"HyperDash":false},{"StartTime":77702.0,"Position":147.846878,"HyperDash":false},{"StartTime":77753.0,"Position":143.823837,"HyperDash":false},{"StartTime":77804.0,"Position":137.800812,"HyperDash":false},{"StartTime":77856.0,"Position":146.836151,"HyperDash":false},{"StartTime":77907.0,"Position":164.81311,"HyperDash":false},{"StartTime":77995.0,"Position":162.949844,"HyperDash":false}]},{"StartTime":78444.0,"Objects":[{"StartTime":78444.0,"Position":9.0,"HyperDash":false},{"StartTime":78495.0,"Position":32.8715477,"HyperDash":false},{"StartTime":78546.0,"Position":21.7430954,"HyperDash":false},{"StartTime":78598.0,"Position":50.15313,"HyperDash":false},{"StartTime":78649.0,"Position":21.1761589,"HyperDash":false},{"StartTime":78700.0,"Position":17.19919,"HyperDash":false},{"StartTime":78752.0,"Position":41.16385,"HyperDash":false},{"StartTime":78803.0,"Position":32.186882,"HyperDash":false},{"StartTime":78891.0,"Position":18.05015,"HyperDash":false}]},{"StartTime":79339.0,"Objects":[{"StartTime":79339.0,"Position":186.0,"HyperDash":false},{"StartTime":79390.0,"Position":199.306229,"HyperDash":false},{"StartTime":79441.0,"Position":219.682114,"HyperDash":false},{"StartTime":79493.0,"Position":224.118561,"HyperDash":false},{"StartTime":79544.0,"Position":227.689743,"HyperDash":false},{"StartTime":79595.0,"Position":241.25592,"HyperDash":false},{"StartTime":79647.0,"Position":265.72113,"HyperDash":false},{"StartTime":79698.0,"Position":285.940369,"HyperDash":false},{"StartTime":79786.0,"Position":327.296021,"HyperDash":false}]},{"StartTime":80011.0,"Objects":[{"StartTime":80011.0,"Position":461.0,"HyperDash":false}]},{"StartTime":80235.0,"Objects":[{"StartTime":80235.0,"Position":482.0,"HyperDash":false},{"StartTime":80328.0,"Position":471.961243,"HyperDash":false},{"StartTime":80458.0,"Position":472.315643,"HyperDash":false}]},{"StartTime":80683.0,"Objects":[{"StartTime":80683.0,"Position":392.0,"HyperDash":false},{"StartTime":80776.0,"Position":394.038757,"HyperDash":false},{"StartTime":80906.0,"Position":401.684357,"HyperDash":false}]},{"StartTime":81131.0,"Objects":[{"StartTime":81131.0,"Position":474.0,"HyperDash":false},{"StartTime":81182.0,"Position":450.511719,"HyperDash":false},{"StartTime":81233.0,"Position":460.8919,"HyperDash":false},{"StartTime":81285.0,"Position":418.0802,"HyperDash":false},{"StartTime":81336.0,"Position":403.0688,"HyperDash":false},{"StartTime":81387.0,"Position":402.833557,"HyperDash":false},{"StartTime":81439.0,"Position":375.354675,"HyperDash":false},{"StartTime":81490.0,"Position":367.6674,"HyperDash":false},{"StartTime":81578.0,"Position":323.0545,"HyperDash":false}]},{"StartTime":82026.0,"Objects":[{"StartTime":82026.0,"Position":148.0,"HyperDash":false},{"StartTime":82077.0,"Position":153.363663,"HyperDash":false},{"StartTime":82128.0,"Position":124.853226,"HyperDash":false},{"StartTime":82180.0,"Position":123.51664,"HyperDash":false},{"StartTime":82231.0,"Position":135.651062,"HyperDash":false},{"StartTime":82282.0,"Position":107.183319,"HyperDash":false},{"StartTime":82334.0,"Position":111.284645,"HyperDash":false},{"StartTime":82385.0,"Position":125.730865,"HyperDash":false},{"StartTime":82473.0,"Position":141.718521,"HyperDash":false}]},{"StartTime":82922.0,"Objects":[{"StartTime":82922.0,"Position":287.0,"HyperDash":false},{"StartTime":82977.0,"Position":306.298553,"HyperDash":false},{"StartTime":83033.0,"Position":321.504669,"HyperDash":false},{"StartTime":83089.0,"Position":339.161163,"HyperDash":false},{"StartTime":83145.0,"Position":367.092316,"HyperDash":false},{"StartTime":83201.0,"Position":394.0961,"HyperDash":false},{"StartTime":83257.0,"Position":406.969055,"HyperDash":false},{"StartTime":83313.0,"Position":412.5311,"HyperDash":false},{"StartTime":83369.0,"Position":442.643555,"HyperDash":false},{"StartTime":83425.0,"Position":445.5296,"HyperDash":false},{"StartTime":83481.0,"Position":420.6327,"HyperDash":false},{"StartTime":83537.0,"Position":415.785919,"HyperDash":false},{"StartTime":83593.0,"Position":369.41394,"HyperDash":false},{"StartTime":83649.0,"Position":368.030121,"HyperDash":false},{"StartTime":83705.0,"Position":376.311218,"HyperDash":false},{"StartTime":83761.0,"Position":349.831451,"HyperDash":false},{"StartTime":83817.0,"Position":357.0095,"HyperDash":false},{"StartTime":83868.0,"Position":377.510834,"HyperDash":false},{"StartTime":83920.0,"Position":394.548126,"HyperDash":false},{"StartTime":83971.0,"Position":406.9447,"HyperDash":false},{"StartTime":84023.0,"Position":383.802063,"HyperDash":false},{"StartTime":84074.0,"Position":391.380249,"HyperDash":false},{"StartTime":84126.0,"Position":407.693,"HyperDash":false},{"StartTime":84177.0,"Position":408.468567,"HyperDash":false},{"StartTime":84265.0,"Position":418.7769,"HyperDash":false}]},{"StartTime":84713.0,"Objects":[{"StartTime":84713.0,"Position":242.0,"HyperDash":false},{"StartTime":84796.0,"Position":214.531952,"HyperDash":false},{"StartTime":84880.0,"Position":201.708862,"HyperDash":false},{"StartTime":84964.0,"Position":158.885773,"HyperDash":false},{"StartTime":85048.0,"Position":122.885155,"HyperDash":false},{"StartTime":85123.0,"Position":159.3354,"HyperDash":false},{"StartTime":85198.0,"Position":171.963165,"HyperDash":false},{"StartTime":85273.0,"Position":220.590912,"HyperDash":false},{"StartTime":85384.0,"Position":242.0,"HyperDash":false}]},{"StartTime":85608.0,"Objects":[{"StartTime":85608.0,"Position":277.0,"HyperDash":false},{"StartTime":85659.0,"Position":273.8022,"HyperDash":false},{"StartTime":85710.0,"Position":272.42923,"HyperDash":false},{"StartTime":85762.0,"Position":256.8426,"HyperDash":false},{"StartTime":85813.0,"Position":245.819153,"HyperDash":false},{"StartTime":85864.0,"Position":210.419479,"HyperDash":false},{"StartTime":85916.0,"Position":177.694885,"HyperDash":false},{"StartTime":85967.0,"Position":180.692947,"HyperDash":false},{"StartTime":86055.0,"Position":144.3092,"HyperDash":false}]},{"StartTime":86504.0,"Objects":[{"StartTime":86504.0,"Position":11.0,"HyperDash":false}]},{"StartTime":93668.0,"Objects":[{"StartTime":93668.0,"Position":321.0,"HyperDash":false},{"StartTime":93723.0,"Position":305.388947,"HyperDash":false},{"StartTime":93779.0,"Position":291.399963,"HyperDash":false},{"StartTime":93835.0,"Position":280.52063,"HyperDash":false},{"StartTime":93891.0,"Position":248.606445,"HyperDash":false},{"StartTime":93947.0,"Position":235.53479,"HyperDash":false},{"StartTime":94003.0,"Position":224.107117,"HyperDash":false},{"StartTime":94059.0,"Position":224.84407,"HyperDash":false},{"StartTime":94115.0,"Position":200.017365,"HyperDash":false},{"StartTime":94171.0,"Position":199.067291,"HyperDash":false},{"StartTime":94227.0,"Position":212.384537,"HyperDash":false},{"StartTime":94283.0,"Position":199.112579,"HyperDash":false},{"StartTime":94339.0,"Position":222.5897,"HyperDash":false},{"StartTime":94395.0,"Position":253.0729,"HyperDash":false},{"StartTime":94451.0,"Position":253.947144,"HyperDash":false},{"StartTime":94507.0,"Position":271.304932,"HyperDash":false},{"StartTime":94563.0,"Position":305.412964,"HyperDash":false},{"StartTime":94619.0,"Position":307.6401,"HyperDash":false},{"StartTime":94675.0,"Position":267.302582,"HyperDash":false},{"StartTime":94731.0,"Position":251.416916,"HyperDash":false},{"StartTime":94787.0,"Position":222.898773,"HyperDash":false},{"StartTime":94843.0,"Position":211.3582,"HyperDash":false},{"StartTime":94899.0,"Position":213.529022,"HyperDash":false},{"StartTime":94955.0,"Position":210.1259,"HyperDash":false},{"StartTime":95011.0,"Position":199.942,"HyperDash":false},{"StartTime":95062.0,"Position":191.884583,"HyperDash":false},{"StartTime":95114.0,"Position":201.545059,"HyperDash":false},{"StartTime":95165.0,"Position":236.775665,"HyperDash":false},{"StartTime":95217.0,"Position":265.954834,"HyperDash":false},{"StartTime":95268.0,"Position":272.007324,"HyperDash":false},{"StartTime":95320.0,"Position":299.217743,"HyperDash":false},{"StartTime":95371.0,"Position":310.421265,"HyperDash":false},{"StartTime":95459.0,"Position":321.0,"HyperDash":false}]},{"StartTime":97250.0,"Objects":[{"StartTime":97250.0,"Position":321.0,"HyperDash":false},{"StartTime":97305.0,"Position":349.148315,"HyperDash":false},{"StartTime":97361.0,"Position":367.604675,"HyperDash":false},{"StartTime":97417.0,"Position":379.5581,"HyperDash":false},{"StartTime":97473.0,"Position":395.380951,"HyperDash":false},{"StartTime":97529.0,"Position":430.504242,"HyperDash":false},{"StartTime":97585.0,"Position":442.613251,"HyperDash":false},{"StartTime":97641.0,"Position":458.59317,"HyperDash":false},{"StartTime":97697.0,"Position":467.732544,"HyperDash":false},{"StartTime":97753.0,"Position":444.03418,"HyperDash":false},{"StartTime":97809.0,"Position":450.705536,"HyperDash":false},{"StartTime":97865.0,"Position":456.036621,"HyperDash":false},{"StartTime":97921.0,"Position":460.436,"HyperDash":false},{"StartTime":97977.0,"Position":445.266327,"HyperDash":false},{"StartTime":98033.0,"Position":456.866272,"HyperDash":false},{"StartTime":98089.0,"Position":449.4119,"HyperDash":false},{"StartTime":98145.0,"Position":462.917175,"HyperDash":false},{"StartTime":98201.0,"Position":468.532471,"HyperDash":false},{"StartTime":98257.0,"Position":451.935547,"HyperDash":false},{"StartTime":98313.0,"Position":433.2847,"HyperDash":false},{"StartTime":98369.0,"Position":426.406769,"HyperDash":false},{"StartTime":98425.0,"Position":449.975067,"HyperDash":false},{"StartTime":98481.0,"Position":460.606018,"HyperDash":false},{"StartTime":98537.0,"Position":447.910065,"HyperDash":false},{"StartTime":98593.0,"Position":467.586945,"HyperDash":false},{"StartTime":98644.0,"Position":441.353149,"HyperDash":false},{"StartTime":98696.0,"Position":439.723267,"HyperDash":false},{"StartTime":98747.0,"Position":415.4601,"HyperDash":false},{"StartTime":98799.0,"Position":412.9643,"HyperDash":false},{"StartTime":98850.0,"Position":398.1049,"HyperDash":false},{"StartTime":98902.0,"Position":376.557465,"HyperDash":false},{"StartTime":98953.0,"Position":359.5229,"HyperDash":false},{"StartTime":99041.0,"Position":321.0,"HyperDash":false}]},{"StartTime":100832.0,"Objects":[{"StartTime":100832.0,"Position":321.0,"HyperDash":false},{"StartTime":100887.0,"Position":321.469482,"HyperDash":false},{"StartTime":100943.0,"Position":295.742432,"HyperDash":false},{"StartTime":100999.0,"Position":267.1522,"HyperDash":false},{"StartTime":101055.0,"Position":261.835083,"HyperDash":false},{"StartTime":101111.0,"Position":216.475037,"HyperDash":false},{"StartTime":101167.0,"Position":227.328217,"HyperDash":false},{"StartTime":101223.0,"Position":189.1814,"HyperDash":false},{"StartTime":101279.0,"Position":176.034576,"HyperDash":false},{"StartTime":101335.0,"Position":138.745392,"HyperDash":false},{"StartTime":101391.0,"Position":148.387146,"HyperDash":false},{"StartTime":101447.0,"Position":103.028908,"HyperDash":false},{"StartTime":101503.0,"Position":107.670639,"HyperDash":false},{"StartTime":101559.0,"Position":73.03934,"HyperDash":false},{"StartTime":101615.0,"Position":45.58867,"HyperDash":false},{"StartTime":101671.0,"Position":34.2676964,"HyperDash":false},{"StartTime":101727.0,"Position":31.1845322,"HyperDash":false},{"StartTime":101783.0,"Position":59.98834,"HyperDash":false},{"StartTime":101839.0,"Position":63.2845459,"HyperDash":false},{"StartTime":101895.0,"Position":71.71911,"HyperDash":false},{"StartTime":101951.0,"Position":103.324966,"HyperDash":false},{"StartTime":102007.0,"Position":111.683212,"HyperDash":false},{"StartTime":102063.0,"Position":126.041473,"HyperDash":false},{"StartTime":102119.0,"Position":162.399689,"HyperDash":false},{"StartTime":102175.0,"Position":175.71051,"HyperDash":false},{"StartTime":102226.0,"Position":204.237076,"HyperDash":false},{"StartTime":102278.0,"Position":214.0877,"HyperDash":false},{"StartTime":102329.0,"Position":210.614273,"HyperDash":false},{"StartTime":102381.0,"Position":249.4649,"HyperDash":false},{"StartTime":102432.0,"Position":251.954224,"HyperDash":false},{"StartTime":102484.0,"Position":265.549072,"HyperDash":false},{"StartTime":102535.0,"Position":284.1342,"HyperDash":false},{"StartTime":102623.0,"Position":321.0,"HyperDash":false}]},{"StartTime":102847.0,"Objects":[{"StartTime":102847.0,"Position":385.0,"HyperDash":false}]},{"StartTime":103071.0,"Objects":[{"StartTime":103071.0,"Position":322.0,"HyperDash":false},{"StartTime":103154.0,"Position":309.4082,"HyperDash":false},{"StartTime":103238.0,"Position":253.459869,"HyperDash":false},{"StartTime":103322.0,"Position":230.511536,"HyperDash":false},{"StartTime":103406.0,"Position":202.384949,"HyperDash":false},{"StartTime":103481.0,"Position":227.946259,"HyperDash":false},{"StartTime":103556.0,"Position":269.685852,"HyperDash":false},{"StartTime":103631.0,"Position":282.4254,"HyperDash":false},{"StartTime":103742.0,"Position":322.0,"HyperDash":false}]},{"StartTime":103966.0,"Objects":[{"StartTime":103966.0,"Position":404.0,"HyperDash":false},{"StartTime":104059.0,"Position":389.203644,"HyperDash":false},{"StartTime":104189.0,"Position":389.111877,"HyperDash":false}]},{"StartTime":104414.0,"Objects":[{"StartTime":104414.0,"Position":308.0,"HyperDash":false},{"StartTime":104507.0,"Position":288.7421,"HyperDash":false},{"StartTime":104637.0,"Position":230.940414,"HyperDash":false}]},{"StartTime":104862.0,"Objects":[{"StartTime":104862.0,"Position":164.0,"HyperDash":false},{"StartTime":104945.0,"Position":150.511658,"HyperDash":false},{"StartTime":105029.0,"Position":96.6680145,"HyperDash":false},{"StartTime":105113.0,"Position":58.8243866,"HyperDash":false},{"StartTime":105197.0,"Position":44.8031158,"HyperDash":false},{"StartTime":105272.0,"Position":73.2715759,"HyperDash":false},{"StartTime":105347.0,"Position":112.917679,"HyperDash":false},{"StartTime":105422.0,"Position":127.563766,"HyperDash":false},{"StartTime":105533.0,"Position":164.0,"HyperDash":false}]},{"StartTime":105757.0,"Objects":[{"StartTime":105757.0,"Position":369.0,"HyperDash":false}]},{"StartTime":106205.0,"Objects":[{"StartTime":106205.0,"Position":276.0,"HyperDash":false},{"StartTime":106260.0,"Position":301.5404,"HyperDash":false},{"StartTime":106316.0,"Position":299.28067,"HyperDash":false},{"StartTime":106372.0,"Position":337.27,"HyperDash":false},{"StartTime":106428.0,"Position":348.8408,"HyperDash":false},{"StartTime":106484.0,"Position":372.279419,"HyperDash":false},{"StartTime":106540.0,"Position":407.057281,"HyperDash":false},{"StartTime":106596.0,"Position":399.472778,"HyperDash":false},{"StartTime":106652.0,"Position":415.2087,"HyperDash":false},{"StartTime":106746.0,"Position":444.522675,"HyperDash":false},{"StartTime":106876.0,"Position":427.9771,"HyperDash":false}]},{"StartTime":107101.0,"Objects":[{"StartTime":107101.0,"Position":354.0,"HyperDash":false},{"StartTime":107156.0,"Position":351.361053,"HyperDash":false},{"StartTime":107212.0,"Position":319.711761,"HyperDash":false},{"StartTime":107268.0,"Position":312.725647,"HyperDash":false},{"StartTime":107324.0,"Position":292.795166,"HyperDash":false},{"StartTime":107380.0,"Position":257.278931,"HyperDash":false},{"StartTime":107436.0,"Position":250.434189,"HyperDash":false},{"StartTime":107492.0,"Position":228.3952,"HyperDash":false},{"StartTime":107548.0,"Position":202.1942,"HyperDash":false},{"StartTime":107642.0,"Position":183.650848,"HyperDash":false},{"StartTime":107772.0,"Position":130.2209,"HyperDash":false}]},{"StartTime":107996.0,"Objects":[{"StartTime":107996.0,"Position":55.0,"HyperDash":false}]},{"StartTime":108220.0,"Objects":[{"StartTime":108220.0,"Position":0.0,"HyperDash":false}]},{"StartTime":108444.0,"Objects":[{"StartTime":108444.0,"Position":43.0,"HyperDash":false},{"StartTime":108527.0,"Position":26.517498,"HyperDash":false},{"StartTime":108611.0,"Position":31.01714,"HyperDash":false},{"StartTime":108695.0,"Position":26.516777,"HyperDash":false},{"StartTime":108779.0,"Position":37.0074844,"HyperDash":false},{"StartTime":108854.0,"Position":40.33816,"HyperDash":false},{"StartTime":108929.0,"Position":23.6777725,"HyperDash":false},{"StartTime":109004.0,"Position":46.01738,"HyperDash":false},{"StartTime":109115.0,"Position":43.0,"HyperDash":false}]},{"StartTime":109339.0,"Objects":[{"StartTime":109339.0,"Position":128.0,"HyperDash":false},{"StartTime":109432.0,"Position":177.210678,"HyperDash":false},{"StartTime":109562.0,"Position":204.080414,"HyperDash":false}]},{"StartTime":109787.0,"Objects":[{"StartTime":109787.0,"Position":242.0,"HyperDash":false},{"StartTime":109842.0,"Position":213.635727,"HyperDash":false},{"StartTime":109898.0,"Position":229.2922,"HyperDash":false},{"StartTime":109954.0,"Position":229.500839,"HyperDash":false},{"StartTime":110010.0,"Position":245.173721,"HyperDash":false},{"StartTime":110066.0,"Position":240.366425,"HyperDash":false},{"StartTime":110122.0,"Position":243.8476,"HyperDash":false},{"StartTime":110178.0,"Position":253.385529,"HyperDash":false},{"StartTime":110234.0,"Position":267.757416,"HyperDash":false},{"StartTime":110285.0,"Position":252.804428,"HyperDash":false},{"StartTime":110337.0,"Position":250.689026,"HyperDash":false},{"StartTime":110388.0,"Position":223.27919,"HyperDash":false},{"StartTime":110440.0,"Position":223.56842,"HyperDash":false},{"StartTime":110491.0,"Position":243.800873,"HyperDash":false},{"StartTime":110543.0,"Position":223.941116,"HyperDash":false},{"StartTime":110594.0,"Position":226.059952,"HyperDash":false},{"StartTime":110682.0,"Position":242.0,"HyperDash":false}]},{"StartTime":111131.0,"Objects":[{"StartTime":111131.0,"Position":411.0,"HyperDash":false}]},{"StartTime":111578.0,"Objects":[{"StartTime":111578.0,"Position":503.0,"HyperDash":false},{"StartTime":111629.0,"Position":490.995636,"HyperDash":false},{"StartTime":111680.0,"Position":478.9913,"HyperDash":false},{"StartTime":111732.0,"Position":511.947632,"HyperDash":false},{"StartTime":111783.0,"Position":502.9433,"HyperDash":false},{"StartTime":111834.0,"Position":488.938934,"HyperDash":false},{"StartTime":111886.0,"Position":497.8953,"HyperDash":false},{"StartTime":111937.0,"Position":485.89093,"HyperDash":false},{"StartTime":112025.0,"Position":485.432434,"HyperDash":false}]},{"StartTime":112250.0,"Objects":[{"StartTime":112250.0,"Position":326.0,"HyperDash":false}]},{"StartTime":112474.0,"Objects":[{"StartTime":112474.0,"Position":333.0,"HyperDash":false},{"StartTime":112567.0,"Position":318.79068,"HyperDash":false},{"StartTime":112697.0,"Position":253.369049,"HyperDash":false}]},{"StartTime":112922.0,"Objects":[{"StartTime":112922.0,"Position":175.0,"HyperDash":false},{"StartTime":113015.0,"Position":142.79068,"HyperDash":false},{"StartTime":113145.0,"Position":95.36904,"HyperDash":false}]},{"StartTime":113369.0,"Objects":[{"StartTime":113369.0,"Position":28.0,"HyperDash":false},{"StartTime":113424.0,"Position":14.5683556,"HyperDash":false},{"StartTime":113480.0,"Position":0.0,"HyperDash":false},{"StartTime":113536.0,"Position":28.3534565,"HyperDash":false},{"StartTime":113592.0,"Position":8.926472,"HyperDash":false},{"StartTime":113648.0,"Position":14.8988361,"HyperDash":false},{"StartTime":113704.0,"Position":13.3887749,"HyperDash":false},{"StartTime":113760.0,"Position":28.1702747,"HyperDash":false},{"StartTime":113816.0,"Position":34.34165,"HyperDash":false},{"StartTime":113867.0,"Position":36.0318,"HyperDash":false},{"StartTime":113919.0,"Position":12.4058609,"HyperDash":false},{"StartTime":113970.0,"Position":10.89321,"HyperDash":false},{"StartTime":114022.0,"Position":0.0,"HyperDash":false},{"StartTime":114073.0,"Position":10.8660927,"HyperDash":false},{"StartTime":114125.0,"Position":28.46455,"HyperDash":false},{"StartTime":114176.0,"Position":10.1406345,"HyperDash":false},{"StartTime":114264.0,"Position":28.0,"HyperDash":false}]},{"StartTime":114713.0,"Objects":[{"StartTime":114713.0,"Position":190.0,"HyperDash":false}]},{"StartTime":115160.0,"Objects":[{"StartTime":115160.0,"Position":349.0,"HyperDash":false},{"StartTime":115215.0,"Position":385.515045,"HyperDash":false},{"StartTime":115271.0,"Position":399.481323,"HyperDash":false},{"StartTime":115327.0,"Position":411.652283,"HyperDash":false},{"StartTime":115383.0,"Position":433.181549,"HyperDash":false},{"StartTime":115439.0,"Position":451.6266,"HyperDash":false},{"StartTime":115495.0,"Position":475.6881,"HyperDash":false},{"StartTime":115551.0,"Position":468.64,"HyperDash":false},{"StartTime":115607.0,"Position":501.696655,"HyperDash":false},{"StartTime":115658.0,"Position":478.782867,"HyperDash":false},{"StartTime":115710.0,"Position":452.2243,"HyperDash":false},{"StartTime":115761.0,"Position":461.569977,"HyperDash":false},{"StartTime":115813.0,"Position":418.9793,"HyperDash":false},{"StartTime":115864.0,"Position":433.325836,"HyperDash":false},{"StartTime":115916.0,"Position":398.248627,"HyperDash":false},{"StartTime":115967.0,"Position":379.308319,"HyperDash":false},{"StartTime":116055.0,"Position":349.0,"HyperDash":false}]},{"StartTime":116280.0,"Objects":[{"StartTime":116280.0,"Position":265.0,"HyperDash":false}]},{"StartTime":116504.0,"Objects":[{"StartTime":116504.0,"Position":224.0,"HyperDash":false},{"StartTime":116597.0,"Position":239.949112,"HyperDash":false},{"StartTime":116727.0,"Position":235.867233,"HyperDash":false}]},{"StartTime":116951.0,"Objects":[{"StartTime":116951.0,"Position":320.0,"HyperDash":false},{"StartTime":117002.0,"Position":342.006653,"HyperDash":false},{"StartTime":117053.0,"Position":362.0133,"HyperDash":false},{"StartTime":117105.0,"Position":374.373047,"HyperDash":false},{"StartTime":117156.0,"Position":403.3797,"HyperDash":false},{"StartTime":117207.0,"Position":400.386353,"HyperDash":false},{"StartTime":117259.0,"Position":441.7282,"HyperDash":false},{"StartTime":117310.0,"Position":459.303955,"HyperDash":false},{"StartTime":117398.0,"Position":476.6307,"HyperDash":false}]},{"StartTime":117847.0,"Objects":[{"StartTime":117847.0,"Position":501.0,"HyperDash":false},{"StartTime":117898.0,"Position":485.993347,"HyperDash":false},{"StartTime":117949.0,"Position":475.9867,"HyperDash":false},{"StartTime":118001.0,"Position":440.626953,"HyperDash":false},{"StartTime":118052.0,"Position":413.6203,"HyperDash":false},{"StartTime":118103.0,"Position":415.613647,"HyperDash":false},{"StartTime":118155.0,"Position":403.2718,"HyperDash":false},{"StartTime":118206.0,"Position":374.696045,"HyperDash":false},{"StartTime":118294.0,"Position":344.3693,"HyperDash":false}]},{"StartTime":118742.0,"Objects":[{"StartTime":118742.0,"Position":200.0,"HyperDash":false},{"StartTime":118793.0,"Position":169.013748,"HyperDash":false},{"StartTime":118844.0,"Position":149.781937,"HyperDash":false},{"StartTime":118896.0,"Position":136.378891,"HyperDash":false},{"StartTime":118947.0,"Position":111.942886,"HyperDash":false},{"StartTime":118998.0,"Position":96.68911,"HyperDash":false},{"StartTime":119050.0,"Position":81.5406,"HyperDash":false},{"StartTime":119101.0,"Position":83.7234955,"HyperDash":false},{"StartTime":119189.0,"Position":45.337368,"HyperDash":false}]},{"StartTime":119638.0,"Objects":[{"StartTime":119638.0,"Position":16.0,"HyperDash":false},{"StartTime":119721.0,"Position":11.22777,"HyperDash":false},{"StartTime":119805.0,"Position":40.49443,"HyperDash":false},{"StartTime":119889.0,"Position":35.76109,"HyperDash":false},{"StartTime":119973.0,"Position":29.0471916,"HyperDash":false},{"StartTime":120048.0,"Position":34.1499748,"HyperDash":false},{"StartTime":120123.0,"Position":6.23331642,"HyperDash":false},{"StartTime":120198.0,"Position":34.316658,"HyperDash":false},{"StartTime":120309.0,"Position":16.0,"HyperDash":false}]},{"StartTime":120534.0,"Objects":[{"StartTime":120534.0,"Position":88.0,"HyperDash":false},{"StartTime":120589.0,"Position":99.09209,"HyperDash":false},{"StartTime":120645.0,"Position":138.513123,"HyperDash":false},{"StartTime":120701.0,"Position":128.008957,"HyperDash":false},{"StartTime":120757.0,"Position":153.9049,"HyperDash":false},{"StartTime":120813.0,"Position":191.800842,"HyperDash":false},{"StartTime":120869.0,"Position":199.696777,"HyperDash":false},{"StartTime":120925.0,"Position":239.592712,"HyperDash":false},{"StartTime":120981.0,"Position":242.66629,"HyperDash":false},{"StartTime":121032.0,"Position":217.724426,"HyperDash":false},{"StartTime":121084.0,"Position":218.249634,"HyperDash":false},{"StartTime":121135.0,"Position":197.130127,"HyperDash":false},{"StartTime":121187.0,"Position":167.6553,"HyperDash":false},{"StartTime":121238.0,"Position":142.5358,"HyperDash":false},{"StartTime":121290.0,"Position":147.723633,"HyperDash":false},{"StartTime":121341.0,"Position":129.947342,"HyperDash":false},{"StartTime":121429.0,"Position":88.0,"HyperDash":false}]},{"StartTime":121877.0,"Objects":[{"StartTime":121877.0,"Position":172.0,"HyperDash":false}]},{"StartTime":122325.0,"Objects":[{"StartTime":122325.0,"Position":322.0,"HyperDash":false},{"StartTime":122376.0,"Position":324.2495,"HyperDash":false},{"StartTime":122427.0,"Position":355.02713,"HyperDash":false},{"StartTime":122479.0,"Position":331.196777,"HyperDash":false},{"StartTime":122530.0,"Position":366.3613,"HyperDash":false},{"StartTime":122581.0,"Position":338.580322,"HyperDash":false},{"StartTime":122633.0,"Position":352.663971,"HyperDash":false},{"StartTime":122684.0,"Position":329.8923,"HyperDash":false},{"StartTime":122772.0,"Position":326.6841,"HyperDash":false}]},{"StartTime":123220.0,"Objects":[{"StartTime":123220.0,"Position":150.0,"HyperDash":false},{"StartTime":123271.0,"Position":143.7505,"HyperDash":false},{"StartTime":123322.0,"Position":113.97287,"HyperDash":false},{"StartTime":123374.0,"Position":108.803215,"HyperDash":false},{"StartTime":123425.0,"Position":131.6387,"HyperDash":false},{"StartTime":123476.0,"Position":132.419678,"HyperDash":false},{"StartTime":123528.0,"Position":126.336021,"HyperDash":false},{"StartTime":123579.0,"Position":126.1077,"HyperDash":false},{"StartTime":123667.0,"Position":145.315887,"HyperDash":false}]},{"StartTime":123892.0,"Objects":[{"StartTime":123892.0,"Position":238.0,"HyperDash":false}]},{"StartTime":124116.0,"Objects":[{"StartTime":124116.0,"Position":277.0,"HyperDash":false},{"StartTime":124171.0,"Position":313.7125,"HyperDash":false},{"StartTime":124227.0,"Position":317.3544,"HyperDash":false},{"StartTime":124283.0,"Position":342.320557,"HyperDash":false},{"StartTime":124339.0,"Position":372.249237,"HyperDash":false},{"StartTime":124395.0,"Position":379.760651,"HyperDash":false},{"StartTime":124451.0,"Position":399.5314,"HyperDash":false},{"StartTime":124507.0,"Position":411.37323,"HyperDash":false},{"StartTime":124563.0,"Position":421.66275,"HyperDash":false},{"StartTime":124619.0,"Position":395.6441,"HyperDash":false},{"StartTime":124675.0,"Position":426.7424,"HyperDash":false},{"StartTime":124731.0,"Position":411.428467,"HyperDash":false},{"StartTime":124787.0,"Position":416.085144,"HyperDash":false},{"StartTime":124843.0,"Position":392.878662,"HyperDash":false},{"StartTime":124899.0,"Position":418.688171,"HyperDash":false},{"StartTime":124955.0,"Position":407.149261,"HyperDash":false},{"StartTime":125011.0,"Position":431.788025,"HyperDash":false},{"StartTime":125062.0,"Position":407.509033,"HyperDash":false},{"StartTime":125114.0,"Position":382.834656,"HyperDash":false},{"StartTime":125165.0,"Position":397.090271,"HyperDash":false},{"StartTime":125217.0,"Position":376.698334,"HyperDash":false},{"StartTime":125268.0,"Position":346.500427,"HyperDash":false},{"StartTime":125320.0,"Position":337.9348,"HyperDash":false},{"StartTime":125371.0,"Position":290.849426,"HyperDash":false},{"StartTime":125459.0,"Position":276.2673,"HyperDash":false}]},{"StartTime":125907.0,"Objects":[{"StartTime":125907.0,"Position":121.0,"HyperDash":false},{"StartTime":125990.0,"Position":113.836914,"HyperDash":false},{"StartTime":126074.0,"Position":142.708008,"HyperDash":false},{"StartTime":126158.0,"Position":144.5791,"HyperDash":false},{"StartTime":126242.0,"Position":132.467285,"HyperDash":false},{"StartTime":126317.0,"Position":121.9209,"HyperDash":false},{"StartTime":126392.0,"Position":135.357422,"HyperDash":false},{"StartTime":126467.0,"Position":109.793945,"HyperDash":false},{"StartTime":126578.0,"Position":121.0,"HyperDash":false}]},{"StartTime":126802.0,"Objects":[{"StartTime":126802.0,"Position":75.0,"HyperDash":false}]},{"StartTime":127026.0,"Objects":[{"StartTime":127026.0,"Position":88.0,"HyperDash":false},{"StartTime":127081.0,"Position":65.86594,"HyperDash":false},{"StartTime":127137.0,"Position":35.8985558,"HyperDash":false},{"StartTime":127193.0,"Position":35.9736977,"HyperDash":false},{"StartTime":127249.0,"Position":9.451545,"HyperDash":false},{"StartTime":127343.0,"Position":22.10696,"HyperDash":false},{"StartTime":127473.0,"Position":88.0,"HyperDash":false}]},{"StartTime":127698.0,"Objects":[{"StartTime":127698.0,"Position":171.0,"HyperDash":false},{"StartTime":127791.0,"Position":186.182022,"HyperDash":false},{"StartTime":127921.0,"Position":250.565491,"HyperDash":false}]},{"StartTime":128145.0,"Objects":[{"StartTime":128145.0,"Position":333.0,"HyperDash":false},{"StartTime":128228.0,"Position":360.710541,"HyperDash":false},{"StartTime":128312.0,"Position":382.321838,"HyperDash":false},{"StartTime":128396.0,"Position":416.1943,"HyperDash":false},{"StartTime":128480.0,"Position":447.073883,"HyperDash":false},{"StartTime":128555.0,"Position":406.767181,"HyperDash":false},{"StartTime":128630.0,"Position":392.017242,"HyperDash":false},{"StartTime":128705.0,"Position":359.007629,"HyperDash":false},{"StartTime":128816.0,"Position":333.0,"HyperDash":false}]},{"StartTime":129041.0,"Objects":[{"StartTime":129041.0,"Position":318.0,"HyperDash":false},{"StartTime":129134.0,"Position":308.0215,"HyperDash":false},{"StartTime":129264.0,"Position":313.2559,"HyperDash":false}]},{"StartTime":129489.0,"Objects":[{"StartTime":129489.0,"Position":304.0,"HyperDash":false},{"StartTime":129540.0,"Position":336.395416,"HyperDash":false},{"StartTime":129591.0,"Position":329.216034,"HyperDash":false},{"StartTime":129643.0,"Position":344.559479,"HyperDash":false},{"StartTime":129694.0,"Position":350.5508,"HyperDash":false},{"StartTime":129745.0,"Position":370.245239,"HyperDash":false},{"StartTime":129797.0,"Position":405.714478,"HyperDash":false},{"StartTime":129848.0,"Position":411.953125,"HyperDash":false},{"StartTime":129936.0,"Position":450.890564,"HyperDash":false}]},{"StartTime":130160.0,"Objects":[{"StartTime":130160.0,"Position":506.0,"HyperDash":false},{"StartTime":130211.0,"Position":502.234955,"HyperDash":false},{"StartTime":130262.0,"Position":491.46994,"HyperDash":false},{"StartTime":130314.0,"Position":497.6703,"HyperDash":false},{"StartTime":130365.0,"Position":512.0,"HyperDash":false},{"StartTime":130416.0,"Position":496.1402,"HyperDash":false},{"StartTime":130468.0,"Position":479.340546,"HyperDash":false},{"StartTime":130519.0,"Position":512.0,"HyperDash":false},{"StartTime":130607.0,"Position":490.529968,"HyperDash":false}]},{"StartTime":130832.0,"Objects":[{"StartTime":130832.0,"Position":477.0,"HyperDash":false}]},{"StartTime":131280.0,"Objects":[{"StartTime":131280.0,"Position":308.0,"HyperDash":false},{"StartTime":131373.0,"Position":272.2126,"HyperDash":false},{"StartTime":131503.0,"Position":230.958725,"HyperDash":false}]},{"StartTime":131728.0,"Objects":[{"StartTime":131728.0,"Position":142.0,"HyperDash":false},{"StartTime":131811.0,"Position":136.278381,"HyperDash":false},{"StartTime":131895.0,"Position":128.596268,"HyperDash":false},{"StartTime":131979.0,"Position":142.914154,"HyperDash":false},{"StartTime":132063.0,"Position":155.251785,"HyperDash":false},{"StartTime":132138.0,"Position":138.309143,"HyperDash":false},{"StartTime":132213.0,"Position":167.346741,"HyperDash":false},{"StartTime":132288.0,"Position":142.384354,"HyperDash":false},{"StartTime":132399.0,"Position":142.0,"HyperDash":false}]},{"StartTime":132623.0,"Objects":[{"StartTime":132623.0,"Position":55.0,"HyperDash":false},{"StartTime":132716.0,"Position":62.0249329,"HyperDash":false},{"StartTime":132846.0,"Position":45.4683838,"HyperDash":false}]},{"StartTime":133071.0,"Objects":[{"StartTime":133071.0,"Position":33.0,"HyperDash":false},{"StartTime":133122.0,"Position":34.36902,"HyperDash":false},{"StartTime":133173.0,"Position":49.2077179,"HyperDash":false},{"StartTime":133225.0,"Position":74.56708,"HyperDash":false},{"StartTime":133276.0,"Position":53.8811874,"HyperDash":false},{"StartTime":133327.0,"Position":100.066032,"HyperDash":false},{"StartTime":133379.0,"Position":104.080582,"HyperDash":false},{"StartTime":133430.0,"Position":129.9765,"HyperDash":false},{"StartTime":133518.0,"Position":146.874619,"HyperDash":false}]},{"StartTime":133966.0,"Objects":[{"StartTime":133966.0,"Position":275.0,"HyperDash":false},{"StartTime":134059.0,"Position":303.328827,"HyperDash":false},{"StartTime":134189.0,"Position":354.91748,"HyperDash":false}]},{"StartTime":134414.0,"Objects":[{"StartTime":134414.0,"Position":389.0,"HyperDash":false},{"StartTime":134507.0,"Position":407.328827,"HyperDash":false},{"StartTime":134637.0,"Position":468.91748,"HyperDash":false}]},{"StartTime":134862.0,"Objects":[{"StartTime":134862.0,"Position":503.0,"HyperDash":false},{"StartTime":134913.0,"Position":500.255981,"HyperDash":false},{"StartTime":134964.0,"Position":512.0,"HyperDash":false},{"StartTime":135016.0,"Position":512.0,"HyperDash":false},{"StartTime":135067.0,"Position":510.048553,"HyperDash":false},{"StartTime":135118.0,"Position":501.304535,"HyperDash":false},{"StartTime":135170.0,"Position":512.0,"HyperDash":false},{"StartTime":135221.0,"Position":497.906158,"HyperDash":false},{"StartTime":135309.0,"Position":492.781982,"HyperDash":false}]},{"StartTime":135757.0,"Objects":[{"StartTime":135757.0,"Position":318.0,"HyperDash":false},{"StartTime":135850.0,"Position":298.671173,"HyperDash":false},{"StartTime":135980.0,"Position":238.08252,"HyperDash":false}]},{"StartTime":136205.0,"Objects":[{"StartTime":136205.0,"Position":204.0,"HyperDash":false},{"StartTime":136298.0,"Position":187.671188,"HyperDash":false},{"StartTime":136428.0,"Position":124.082512,"HyperDash":false}]},{"StartTime":136653.0,"Objects":[{"StartTime":136653.0,"Position":49.0,"HyperDash":false},{"StartTime":136704.0,"Position":21.2460976,"HyperDash":false},{"StartTime":136755.0,"Position":43.23652,"HyperDash":false},{"StartTime":136807.0,"Position":42.04418,"HyperDash":false},{"StartTime":136858.0,"Position":2.98967361,"HyperDash":false},{"StartTime":136909.0,"Position":20.9194527,"HyperDash":false},{"StartTime":136961.0,"Position":10.7384281,"HyperDash":false},{"StartTime":137012.0,"Position":13.6708527,"HyperDash":false},{"StartTime":137100.0,"Position":38.2821579,"HyperDash":false}]},{"StartTime":137548.0,"Objects":[{"StartTime":137548.0,"Position":200.0,"HyperDash":false},{"StartTime":137641.0,"Position":223.082932,"HyperDash":false},{"StartTime":137771.0,"Position":220.570145,"HyperDash":false}]},{"StartTime":137996.0,"Objects":[{"StartTime":137996.0,"Position":204.0,"HyperDash":false},{"StartTime":138089.0,"Position":193.917068,"HyperDash":false},{"StartTime":138219.0,"Position":183.429855,"HyperDash":false}]},{"StartTime":138444.0,"Objects":[{"StartTime":138444.0,"Position":270.0,"HyperDash":false},{"StartTime":138495.0,"Position":302.4524,"HyperDash":false},{"StartTime":138546.0,"Position":317.9048,"HyperDash":false},{"StartTime":138598.0,"Position":317.679779,"HyperDash":false},{"StartTime":138649.0,"Position":319.346863,"HyperDash":false},{"StartTime":138700.0,"Position":371.213379,"HyperDash":false},{"StartTime":138752.0,"Position":387.4302,"HyperDash":false},{"StartTime":138803.0,"Position":406.2967,"HyperDash":false},{"StartTime":138891.0,"Position":422.1252,"HyperDash":false}]},{"StartTime":139116.0,"Objects":[{"StartTime":139116.0,"Position":490.0,"HyperDash":false}]},{"StartTime":139339.0,"Objects":[{"StartTime":139339.0,"Position":504.0,"HyperDash":false},{"StartTime":139432.0,"Position":500.723053,"HyperDash":false},{"StartTime":139562.0,"Position":490.562256,"HyperDash":false}]},{"StartTime":139787.0,"Objects":[{"StartTime":139787.0,"Position":370.0,"HyperDash":false}]},{"StartTime":140235.0,"Objects":[{"StartTime":140235.0,"Position":268.0,"HyperDash":false},{"StartTime":140318.0,"Position":268.7403,"HyperDash":false},{"StartTime":140402.0,"Position":257.822449,"HyperDash":false},{"StartTime":140486.0,"Position":262.227783,"HyperDash":false},{"StartTime":140570.0,"Position":276.9804,"HyperDash":false},{"StartTime":140645.0,"Position":293.53894,"HyperDash":false},{"StartTime":140720.0,"Position":260.337555,"HyperDash":false},{"StartTime":140795.0,"Position":257.3968,"HyperDash":false},{"StartTime":140906.0,"Position":268.0,"HyperDash":false}]},{"StartTime":141131.0,"Objects":[{"StartTime":141131.0,"Position":207.0,"HyperDash":false},{"StartTime":141224.0,"Position":178.663437,"HyperDash":false},{"StartTime":141354.0,"Position":127.063927,"HyperDash":false}]},{"StartTime":141578.0,"Objects":[{"StartTime":141578.0,"Position":39.0,"HyperDash":false}]},{"StartTime":141802.0,"Objects":[{"StartTime":141802.0,"Position":8.0,"HyperDash":false}]},{"StartTime":142026.0,"Objects":[{"StartTime":142026.0,"Position":71.0,"HyperDash":false},{"StartTime":142119.0,"Position":106.114151,"HyperDash":false},{"StartTime":142249.0,"Position":149.484,"HyperDash":false}]},{"StartTime":142474.0,"Objects":[{"StartTime":142474.0,"Position":220.0,"HyperDash":false},{"StartTime":142557.0,"Position":238.606583,"HyperDash":false},{"StartTime":142641.0,"Position":276.5699,"HyperDash":false},{"StartTime":142725.0,"Position":317.533142,"HyperDash":false},{"StartTime":142809.0,"Position":339.6748,"HyperDash":false},{"StartTime":142884.0,"Position":301.10022,"HyperDash":false},{"StartTime":142959.0,"Position":303.3473,"HyperDash":false},{"StartTime":143034.0,"Position":253.59436,"HyperDash":false},{"StartTime":143145.0,"Position":220.0,"HyperDash":false}]},{"StartTime":143369.0,"Objects":[{"StartTime":143369.0,"Position":158.0,"HyperDash":false},{"StartTime":143462.0,"Position":168.4163,"HyperDash":false},{"StartTime":143592.0,"Position":155.389526,"HyperDash":false}]},{"StartTime":143817.0,"Objects":[{"StartTime":143817.0,"Position":192.0,"HyperDash":false},{"StartTime":143868.0,"Position":227.725708,"HyperDash":false},{"StartTime":143919.0,"Position":234.856445,"HyperDash":false},{"StartTime":143971.0,"Position":256.358948,"HyperDash":false},{"StartTime":144022.0,"Position":248.750854,"HyperDash":false},{"StartTime":144073.0,"Position":277.396729,"HyperDash":false},{"StartTime":144125.0,"Position":293.7474,"HyperDash":false},{"StartTime":144176.0,"Position":303.68158,"HyperDash":false},{"StartTime":144264.0,"Position":346.96463,"HyperDash":false}]},{"StartTime":144489.0,"Objects":[{"StartTime":144489.0,"Position":431.0,"HyperDash":false},{"StartTime":144540.0,"Position":448.9708,"HyperDash":false},{"StartTime":144591.0,"Position":446.9416,"HyperDash":false},{"StartTime":144643.0,"Position":435.9314,"HyperDash":false},{"StartTime":144694.0,"Position":443.9022,"HyperDash":false},{"StartTime":144745.0,"Position":416.873,"HyperDash":false},{"StartTime":144797.0,"Position":434.8628,"HyperDash":false},{"StartTime":144848.0,"Position":447.8336,"HyperDash":false},{"StartTime":144936.0,"Position":439.508667,"HyperDash":false}]},{"StartTime":145160.0,"Objects":[{"StartTime":145160.0,"Position":456.0,"HyperDash":false}]},{"StartTime":145608.0,"Objects":[{"StartTime":145608.0,"Position":272.0,"HyperDash":false},{"StartTime":145701.0,"Position":244.790558,"HyperDash":false},{"StartTime":145831.0,"Position":193.216751,"HyperDash":false}]},{"StartTime":146056.0,"Objects":[{"StartTime":146056.0,"Position":127.0,"HyperDash":false},{"StartTime":146139.0,"Position":105.417236,"HyperDash":false},{"StartTime":146223.0,"Position":79.47805,"HyperDash":false},{"StartTime":146307.0,"Position":53.5388641,"HyperDash":false},{"StartTime":146391.0,"Position":7.421463,"HyperDash":false},{"StartTime":146466.0,"Position":38.974678,"HyperDash":false},{"StartTime":146541.0,"Position":69.7061,"HyperDash":false},{"StartTime":146616.0,"Position":94.4375,"HyperDash":false},{"StartTime":146727.0,"Position":127.0,"HyperDash":false}]},{"StartTime":146951.0,"Objects":[{"StartTime":146951.0,"Position":193.0,"HyperDash":false},{"StartTime":147044.0,"Position":209.412018,"HyperDash":false},{"StartTime":147174.0,"Position":186.467926,"HyperDash":false}]},{"StartTime":147399.0,"Objects":[{"StartTime":147399.0,"Position":109.0,"HyperDash":false},{"StartTime":147450.0,"Position":145.151154,"HyperDash":false},{"StartTime":147501.0,"Position":151.302292,"HyperDash":false},{"StartTime":147553.0,"Position":163.809341,"HyperDash":false},{"StartTime":147604.0,"Position":170.440277,"HyperDash":false},{"StartTime":147655.0,"Position":216.034668,"HyperDash":false},{"StartTime":147707.0,"Position":210.1249,"HyperDash":false},{"StartTime":147758.0,"Position":230.252777,"HyperDash":false},{"StartTime":147846.0,"Position":266.5323,"HyperDash":false}]},{"StartTime":148295.0,"Objects":[{"StartTime":148295.0,"Position":441.0,"HyperDash":false},{"StartTime":148388.0,"Position":425.532318,"HyperDash":false},{"StartTime":148518.0,"Position":444.6743,"HyperDash":false}]},{"StartTime":148742.0,"Objects":[{"StartTime":148742.0,"Position":482.0,"HyperDash":false},{"StartTime":148835.0,"Position":486.467682,"HyperDash":false},{"StartTime":148965.0,"Position":478.3257,"HyperDash":false}]},{"StartTime":149190.0,"Objects":[{"StartTime":149190.0,"Position":390.0,"HyperDash":false},{"StartTime":149241.0,"Position":390.926971,"HyperDash":false},{"StartTime":149292.0,"Position":346.853943,"HyperDash":false},{"StartTime":149344.0,"Position":355.206665,"HyperDash":false},{"StartTime":149395.0,"Position":318.011047,"HyperDash":false},{"StartTime":149446.0,"Position":311.81546,"HyperDash":false},{"StartTime":149498.0,"Position":296.263062,"HyperDash":false},{"StartTime":149549.0,"Position":268.067444,"HyperDash":false},{"StartTime":149637.0,"Position":235.671082,"HyperDash":false}]},{"StartTime":150086.0,"Objects":[{"StartTime":150086.0,"Position":59.0,"HyperDash":false},{"StartTime":150179.0,"Position":44.27435,"HyperDash":false},{"StartTime":150309.0,"Position":42.77816,"HyperDash":false}]},{"StartTime":150534.0,"Objects":[{"StartTime":150534.0,"Position":94.0,"HyperDash":false},{"StartTime":150627.0,"Position":87.7256546,"HyperDash":false},{"StartTime":150757.0,"Position":110.221848,"HyperDash":false}]},{"StartTime":150981.0,"Objects":[{"StartTime":150981.0,"Position":42.0,"HyperDash":false},{"StartTime":151032.0,"Position":70.85617,"HyperDash":false},{"StartTime":151083.0,"Position":55.8612671,"HyperDash":false},{"StartTime":151135.0,"Position":104.001328,"HyperDash":false},{"StartTime":151186.0,"Position":120.188065,"HyperDash":false},{"StartTime":151237.0,"Position":126.371735,"HyperDash":false},{"StartTime":151289.0,"Position":155.4776,"HyperDash":false},{"StartTime":151340.0,"Position":163.413391,"HyperDash":false},{"StartTime":151428.0,"Position":190.731277,"HyperDash":false}]},{"StartTime":151653.0,"Objects":[{"StartTime":151653.0,"Position":324.0,"HyperDash":false}]},{"StartTime":151877.0,"Objects":[{"StartTime":151877.0,"Position":335.0,"HyperDash":false},{"StartTime":151970.0,"Position":335.9098,"HyperDash":false},{"StartTime":152100.0,"Position":327.590118,"HyperDash":false}]},{"StartTime":152325.0,"Objects":[{"StartTime":152325.0,"Position":264.0,"HyperDash":false},{"StartTime":152418.0,"Position":284.0902,"HyperDash":false},{"StartTime":152548.0,"Position":271.409882,"HyperDash":false}]},{"StartTime":152772.0,"Objects":[{"StartTime":152772.0,"Position":318.0,"HyperDash":false},{"StartTime":152823.0,"Position":332.202423,"HyperDash":false},{"StartTime":152874.0,"Position":339.075562,"HyperDash":false},{"StartTime":152926.0,"Position":384.6346,"HyperDash":false},{"StartTime":152977.0,"Position":390.811829,"HyperDash":false},{"StartTime":153028.0,"Position":421.607452,"HyperDash":false},{"StartTime":153080.0,"Position":434.969727,"HyperDash":false},{"StartTime":153131.0,"Position":424.9186,"HyperDash":false},{"StartTime":153219.0,"Position":465.022461,"HyperDash":false}]},{"StartTime":153668.0,"Objects":[{"StartTime":153668.0,"Position":494.0,"HyperDash":false},{"StartTime":153723.0,"Position":509.7584,"HyperDash":false},{"StartTime":153779.0,"Position":498.566925,"HyperDash":false},{"StartTime":153835.0,"Position":490.375458,"HyperDash":false},{"StartTime":153891.0,"Position":505.209076,"HyperDash":false},{"StartTime":153985.0,"Position":512.0,"HyperDash":false},{"StartTime":154115.0,"Position":494.0,"HyperDash":false}]},{"StartTime":154339.0,"Objects":[{"StartTime":154339.0,"Position":317.0,"HyperDash":false}]},{"StartTime":154563.0,"Objects":[{"StartTime":154563.0,"Position":332.0,"HyperDash":false},{"StartTime":154618.0,"Position":328.824219,"HyperDash":false},{"StartTime":154674.0,"Position":290.48703,"HyperDash":false},{"StartTime":154730.0,"Position":281.624817,"HyperDash":false},{"StartTime":154786.0,"Position":266.622284,"HyperDash":false},{"StartTime":154842.0,"Position":240.852814,"HyperDash":false},{"StartTime":154898.0,"Position":204.669556,"HyperDash":false},{"StartTime":154954.0,"Position":191.449188,"HyperDash":false},{"StartTime":155010.0,"Position":180.362961,"HyperDash":false},{"StartTime":155061.0,"Position":184.570953,"HyperDash":false},{"StartTime":155113.0,"Position":203.339157,"HyperDash":false},{"StartTime":155164.0,"Position":238.630051,"HyperDash":false},{"StartTime":155216.0,"Position":245.871323,"HyperDash":false},{"StartTime":155267.0,"Position":266.0493,"HyperDash":false},{"StartTime":155319.0,"Position":266.5972,"HyperDash":false},{"StartTime":155370.0,"Position":309.515717,"HyperDash":false},{"StartTime":155458.0,"Position":332.0,"HyperDash":false}]},{"StartTime":155683.0,"Objects":[{"StartTime":155683.0,"Position":413.0,"HyperDash":false},{"StartTime":155738.0,"Position":442.436737,"HyperDash":false},{"StartTime":155794.0,"Position":439.2269,"HyperDash":false},{"StartTime":155850.0,"Position":485.017029,"HyperDash":false},{"StartTime":155906.0,"Position":491.9839,"HyperDash":false},{"StartTime":156000.0,"Position":476.9414,"HyperDash":false},{"StartTime":156130.0,"Position":413.0,"HyperDash":false}]},{"StartTime":156354.0,"Objects":[{"StartTime":156354.0,"Position":379.0,"HyperDash":false},{"StartTime":156405.0,"Position":353.171,"HyperDash":false},{"StartTime":156456.0,"Position":333.342,"HyperDash":false},{"StartTime":156508.0,"Position":342.93924,"HyperDash":false},{"StartTime":156559.0,"Position":316.817322,"HyperDash":false},{"StartTime":156610.0,"Position":287.6954,"HyperDash":false},{"StartTime":156662.0,"Position":273.21814,"HyperDash":false},{"StartTime":156713.0,"Position":261.096252,"HyperDash":false},{"StartTime":156801.0,"Position":228.827026,"HyperDash":false}]},{"StartTime":157250.0,"Objects":[{"StartTime":157250.0,"Position":103.0,"HyperDash":false},{"StartTime":157301.0,"Position":109.828995,"HyperDash":false},{"StartTime":157352.0,"Position":131.65799,"HyperDash":false},{"StartTime":157404.0,"Position":139.06076,"HyperDash":false},{"StartTime":157455.0,"Position":150.182678,"HyperDash":false},{"StartTime":157506.0,"Position":189.3046,"HyperDash":false},{"StartTime":157558.0,"Position":199.78186,"HyperDash":false},{"StartTime":157609.0,"Position":219.903763,"HyperDash":false},{"StartTime":157697.0,"Position":253.172974,"HyperDash":false}]},{"StartTime":158145.0,"Objects":[{"StartTime":158145.0,"Position":131.0,"HyperDash":false},{"StartTime":158196.0,"Position":95.01886,"HyperDash":false},{"StartTime":158247.0,"Position":97.78887,"HyperDash":false},{"StartTime":158299.0,"Position":63.4222565,"HyperDash":false},{"StartTime":158350.0,"Position":75.0872,"HyperDash":false},{"StartTime":158401.0,"Position":22.8652954,"HyperDash":false},{"StartTime":158453.0,"Position":45.94365,"HyperDash":false},{"StartTime":158504.0,"Position":0.0,"HyperDash":false},{"StartTime":158592.0,"Position":0.0,"HyperDash":false}]},{"StartTime":158817.0,"Objects":[{"StartTime":158817.0,"Position":29.0,"HyperDash":false}]},{"StartTime":159041.0,"Objects":[{"StartTime":159041.0,"Position":54.0,"HyperDash":false},{"StartTime":159134.0,"Position":95.1591644,"HyperDash":false},{"StartTime":159264.0,"Position":133.5107,"HyperDash":false}]},{"StartTime":159489.0,"Objects":[{"StartTime":159489.0,"Position":194.0,"HyperDash":false},{"StartTime":159582.0,"Position":246.159164,"HyperDash":false},{"StartTime":159712.0,"Position":273.510681,"HyperDash":false}]},{"StartTime":159936.0,"Objects":[{"StartTime":159936.0,"Position":354.0,"HyperDash":false},{"StartTime":159987.0,"Position":380.1903,"HyperDash":false},{"StartTime":160038.0,"Position":355.7923,"HyperDash":false},{"StartTime":160090.0,"Position":369.8352,"HyperDash":false},{"StartTime":160141.0,"Position":386.028046,"HyperDash":false},{"StartTime":160192.0,"Position":388.432159,"HyperDash":false},{"StartTime":160244.0,"Position":401.998,"HyperDash":false},{"StartTime":160295.0,"Position":400.752838,"HyperDash":false},{"StartTime":160383.0,"Position":376.419128,"HyperDash":false}]},{"StartTime":160832.0,"Objects":[{"StartTime":160832.0,"Position":242.0,"HyperDash":false},{"StartTime":160883.0,"Position":217.809677,"HyperDash":false},{"StartTime":160934.0,"Position":242.2077,"HyperDash":false},{"StartTime":160986.0,"Position":224.16481,"HyperDash":false},{"StartTime":161037.0,"Position":196.971954,"HyperDash":false},{"StartTime":161088.0,"Position":210.567825,"HyperDash":false},{"StartTime":161140.0,"Position":211.002029,"HyperDash":false},{"StartTime":161191.0,"Position":221.247162,"HyperDash":false},{"StartTime":161279.0,"Position":219.580887,"HyperDash":false}]},{"StartTime":161728.0,"Objects":[{"StartTime":161728.0,"Position":481.0,"HyperDash":false}]},{"StartTime":162175.0,"Objects":[{"StartTime":162175.0,"Position":182.0,"HyperDash":false},{"StartTime":162268.0,"Position":165.752014,"HyperDash":false},{"StartTime":162398.0,"Position":102.276337,"HyperDash":false}]},{"StartTime":162623.0,"Objects":[{"StartTime":162623.0,"Position":22.0,"HyperDash":false},{"StartTime":162706.0,"Position":2.907238,"HyperDash":false},{"StartTime":162790.0,"Position":4.71697235,"HyperDash":false},{"StartTime":162874.0,"Position":13.9768333,"HyperDash":false},{"StartTime":162958.0,"Position":19.6685238,"HyperDash":false},{"StartTime":163033.0,"Position":4.88709259,"HyperDash":false},{"StartTime":163108.0,"Position":2.06014729,"HyperDash":false},{"StartTime":163183.0,"Position":22.1771469,"HyperDash":false},{"StartTime":163294.0,"Position":22.0,"HyperDash":false}]},{"StartTime":163519.0,"Objects":[{"StartTime":163519.0,"Position":176.0,"HyperDash":false}]},{"StartTime":163966.0,"Objects":[{"StartTime":163966.0,"Position":202.0,"HyperDash":false},{"StartTime":164059.0,"Position":221.322418,"HyperDash":false},{"StartTime":164189.0,"Position":281.902161,"HyperDash":false}]},{"StartTime":164414.0,"Objects":[{"StartTime":164414.0,"Position":355.0,"HyperDash":false},{"StartTime":164497.0,"Position":383.562683,"HyperDash":false},{"StartTime":164581.0,"Position":395.4695,"HyperDash":false},{"StartTime":164665.0,"Position":445.511963,"HyperDash":false},{"StartTime":164749.0,"Position":470.7404,"HyperDash":false},{"StartTime":164824.0,"Position":455.970947,"HyperDash":false},{"StartTime":164899.0,"Position":418.028564,"HyperDash":false},{"StartTime":164974.0,"Position":389.1983,"HyperDash":false},{"StartTime":165085.0,"Position":355.0,"HyperDash":false}]},{"StartTime":165310.0,"Objects":[{"StartTime":165310.0,"Position":76.0,"HyperDash":false}]},{"StartTime":165757.0,"Objects":[{"StartTime":165757.0,"Position":110.0,"HyperDash":false},{"StartTime":165850.0,"Position":113.949112,"HyperDash":false},{"StartTime":165980.0,"Position":121.867233,"HyperDash":false}]},{"StartTime":166205.0,"Objects":[{"StartTime":166205.0,"Position":188.0,"HyperDash":false},{"StartTime":166288.0,"Position":201.562683,"HyperDash":false},{"StartTime":166372.0,"Position":258.4695,"HyperDash":false},{"StartTime":166456.0,"Position":274.511963,"HyperDash":false},{"StartTime":166540.0,"Position":303.7404,"HyperDash":false},{"StartTime":166615.0,"Position":289.970947,"HyperDash":false},{"StartTime":166690.0,"Position":270.028564,"HyperDash":false},{"StartTime":166765.0,"Position":234.1983,"HyperDash":false},{"StartTime":166876.0,"Position":188.0,"HyperDash":false}]},{"StartTime":167101.0,"Objects":[{"StartTime":167101.0,"Position":206.0,"HyperDash":false},{"StartTime":167156.0,"Position":213.034912,"HyperDash":false},{"StartTime":167212.0,"Position":234.0148,"HyperDash":false},{"StartTime":167268.0,"Position":239.378357,"HyperDash":false},{"StartTime":167324.0,"Position":266.7099,"HyperDash":false},{"StartTime":167380.0,"Position":287.58194,"HyperDash":false},{"StartTime":167436.0,"Position":299.568817,"HyperDash":false},{"StartTime":167492.0,"Position":321.242737,"HyperDash":false},{"StartTime":167548.0,"Position":354.299927,"HyperDash":false},{"StartTime":167599.0,"Position":342.30304,"HyperDash":false},{"StartTime":167651.0,"Position":309.123352,"HyperDash":false},{"StartTime":167702.0,"Position":297.94458,"HyperDash":false},{"StartTime":167754.0,"Position":296.413849,"HyperDash":false},{"StartTime":167805.0,"Position":257.5692,"HyperDash":false},{"StartTime":167857.0,"Position":266.036133,"HyperDash":false},{"StartTime":167908.0,"Position":246.8481,"HyperDash":false},{"StartTime":167996.0,"Position":206.0,"HyperDash":false}]},{"StartTime":168332.0,"Objects":[{"StartTime":168332.0,"Position":98.0,"HyperDash":false},{"StartTime":168406.0,"Position":81.25128,"HyperDash":false},{"StartTime":168481.0,"Position":82.4519,"HyperDash":false},{"StartTime":168556.0,"Position":92.65252,"HyperDash":false},{"StartTime":168667.0,"Position":81.0294342,"HyperDash":false}]},{"StartTime":168892.0,"Objects":[{"StartTime":168892.0,"Position":70.0,"HyperDash":false}]},{"StartTime":169339.0,"Objects":[{"StartTime":169339.0,"Position":246.0,"HyperDash":false},{"StartTime":169432.0,"Position":292.1293,"HyperDash":false},{"StartTime":169562.0,"Position":325.439056,"HyperDash":false}]},{"StartTime":169787.0,"Objects":[{"StartTime":169787.0,"Position":385.0,"HyperDash":false},{"StartTime":169870.0,"Position":405.562683,"HyperDash":false},{"StartTime":169954.0,"Position":452.4695,"HyperDash":false},{"StartTime":170038.0,"Position":472.511963,"HyperDash":false},{"StartTime":170122.0,"Position":500.7404,"HyperDash":false},{"StartTime":170197.0,"Position":462.970947,"HyperDash":false},{"StartTime":170272.0,"Position":454.028564,"HyperDash":false},{"StartTime":170347.0,"Position":408.1983,"HyperDash":false},{"StartTime":170458.0,"Position":385.0,"HyperDash":false}]},{"StartTime":170683.0,"Objects":[{"StartTime":170683.0,"Position":106.0,"HyperDash":false}]},{"StartTime":171131.0,"Objects":[{"StartTime":171131.0,"Position":161.0,"HyperDash":false},{"StartTime":171224.0,"Position":131.715057,"HyperDash":false},{"StartTime":171354.0,"Position":81.18773,"HyperDash":false}]},{"StartTime":171578.0,"Objects":[{"StartTime":171578.0,"Position":22.0,"HyperDash":false},{"StartTime":171661.0,"Position":0.907238,"HyperDash":false},{"StartTime":171745.0,"Position":5.71697235,"HyperDash":false},{"StartTime":171829.0,"Position":4.97683334,"HyperDash":false},{"StartTime":171913.0,"Position":19.6685238,"HyperDash":false},{"StartTime":171988.0,"Position":4.88709259,"HyperDash":false},{"StartTime":172063.0,"Position":20.0601463,"HyperDash":false},{"StartTime":172138.0,"Position":0.0,"HyperDash":false},{"StartTime":172249.0,"Position":22.0,"HyperDash":false}]},{"StartTime":172474.0,"Objects":[{"StartTime":172474.0,"Position":196.0,"HyperDash":false}]},{"StartTime":172922.0,"Objects":[{"StartTime":172922.0,"Position":279.0,"HyperDash":false},{"StartTime":173015.0,"Position":321.282318,"HyperDash":false},{"StartTime":173145.0,"Position":358.80603,"HyperDash":false}]},{"StartTime":173369.0,"Objects":[{"StartTime":173369.0,"Position":385.0,"HyperDash":false},{"StartTime":173452.0,"Position":403.562683,"HyperDash":false},{"StartTime":173536.0,"Position":426.4695,"HyperDash":false},{"StartTime":173620.0,"Position":488.511963,"HyperDash":false},{"StartTime":173704.0,"Position":500.7404,"HyperDash":false},{"StartTime":173779.0,"Position":482.970947,"HyperDash":false},{"StartTime":173854.0,"Position":456.028564,"HyperDash":false},{"StartTime":173929.0,"Position":421.1983,"HyperDash":false},{"StartTime":174040.0,"Position":385.0,"HyperDash":false}]},{"StartTime":174265.0,"Objects":[{"StartTime":174265.0,"Position":307.0,"HyperDash":false},{"StartTime":174358.0,"Position":261.853668,"HyperDash":false},{"StartTime":174488.0,"Position":227.52005,"HyperDash":false}]},{"StartTime":174713.0,"Objects":[{"StartTime":174713.0,"Position":148.0,"HyperDash":false},{"StartTime":174796.0,"Position":106.520546,"HyperDash":false},{"StartTime":174880.0,"Position":80.68592,"HyperDash":false},{"StartTime":174964.0,"Position":68.8512955,"HyperDash":false},{"StartTime":175048.0,"Position":28.83908,"HyperDash":false},{"StartTime":175123.0,"Position":59.2995529,"HyperDash":false},{"StartTime":175198.0,"Position":89.9376144,"HyperDash":false},{"StartTime":175273.0,"Position":125.575668,"HyperDash":false},{"StartTime":175384.0,"Position":148.0,"HyperDash":true}]},{"StartTime":175608.0,"Objects":[{"StartTime":175608.0,"Position":439.0,"HyperDash":false}]},{"StartTime":176056.0,"Objects":[{"StartTime":176056.0,"Position":387.0,"HyperDash":false},{"StartTime":176139.0,"Position":390.25885,"HyperDash":false},{"StartTime":176223.0,"Position":407.5621,"HyperDash":false},{"StartTime":176307.0,"Position":401.7594,"HyperDash":false},{"StartTime":176391.0,"Position":410.638336,"HyperDash":false},{"StartTime":176466.0,"Position":423.467438,"HyperDash":false},{"StartTime":176541.0,"Position":433.8284,"HyperDash":false},{"StartTime":176616.0,"Position":391.564453,"HyperDash":false},{"StartTime":176727.0,"Position":387.0,"HyperDash":false}]},{"StartTime":176951.0,"Objects":[{"StartTime":176951.0,"Position":302.0,"HyperDash":false},{"StartTime":177002.0,"Position":276.291016,"HyperDash":false},{"StartTime":177053.0,"Position":253.17688,"HyperDash":false},{"StartTime":177105.0,"Position":259.690979,"HyperDash":false},{"StartTime":177156.0,"Position":233.457672,"HyperDash":false},{"StartTime":177207.0,"Position":200.839844,"HyperDash":false},{"StartTime":177259.0,"Position":195.088776,"HyperDash":false},{"StartTime":177310.0,"Position":160.934647,"HyperDash":false},{"StartTime":177398.0,"Position":146.743591,"HyperDash":false}]},{"StartTime":177623.0,"Objects":[{"StartTime":177623.0,"Position":10.0,"HyperDash":false}]},{"StartTime":177847.0,"Objects":[{"StartTime":177847.0,"Position":93.0,"HyperDash":false},{"StartTime":177902.0,"Position":121.613495,"HyperDash":false},{"StartTime":177958.0,"Position":114.5836,"HyperDash":false},{"StartTime":178014.0,"Position":147.553711,"HyperDash":false},{"StartTime":178070.0,"Position":172.702118,"HyperDash":false},{"StartTime":178164.0,"Position":121.359177,"HyperDash":false},{"StartTime":178294.0,"Position":93.0,"HyperDash":false}]},{"StartTime":178519.0,"Objects":[{"StartTime":178519.0,"Position":20.0,"HyperDash":false},{"StartTime":178570.0,"Position":12.202383,"HyperDash":false},{"StartTime":178621.0,"Position":17.4776764,"HyperDash":false},{"StartTime":178673.0,"Position":22.8649712,"HyperDash":false},{"StartTime":178724.0,"Position":53.9413567,"HyperDash":false},{"StartTime":178775.0,"Position":54.51453,"HyperDash":false},{"StartTime":178827.0,"Position":76.44822,"HyperDash":false},{"StartTime":178878.0,"Position":73.7426147,"HyperDash":false},{"StartTime":178966.0,"Position":117.336555,"HyperDash":false}]},{"StartTime":179190.0,"Objects":[{"StartTime":179190.0,"Position":260.0,"HyperDash":false}]},{"StartTime":179638.0,"Objects":[{"StartTime":179638.0,"Position":381.0,"HyperDash":false},{"StartTime":179731.0,"Position":403.239,"HyperDash":false},{"StartTime":179861.0,"Position":460.702118,"HyperDash":false}]},{"StartTime":180086.0,"Objects":[{"StartTime":180086.0,"Position":499.0,"HyperDash":false},{"StartTime":180169.0,"Position":492.7418,"HyperDash":false},{"StartTime":180253.0,"Position":479.880341,"HyperDash":false},{"StartTime":180337.0,"Position":491.247253,"HyperDash":false},{"StartTime":180421.0,"Position":476.24173,"HyperDash":false},{"StartTime":180496.0,"Position":474.9095,"HyperDash":false},{"StartTime":180571.0,"Position":490.40033,"HyperDash":false},{"StartTime":180646.0,"Position":497.418976,"HyperDash":false},{"StartTime":180757.0,"Position":499.0,"HyperDash":false}]},{"StartTime":180981.0,"Objects":[{"StartTime":180981.0,"Position":350.0,"HyperDash":false}]},{"StartTime":181429.0,"Objects":[{"StartTime":181429.0,"Position":237.0,"HyperDash":false},{"StartTime":181522.0,"Position":219.747375,"HyperDash":false},{"StartTime":181652.0,"Position":157.265228,"HyperDash":false}]},{"StartTime":181877.0,"Objects":[{"StartTime":181877.0,"Position":69.0,"HyperDash":false},{"StartTime":181960.0,"Position":62.7165451,"HyperDash":false},{"StartTime":182044.0,"Position":51.0702744,"HyperDash":false},{"StartTime":182128.0,"Position":58.38651,"HyperDash":false},{"StartTime":182212.0,"Position":46.79955,"HyperDash":false},{"StartTime":182287.0,"Position":57.2412,"HyperDash":false},{"StartTime":182362.0,"Position":36.87273,"HyperDash":false},{"StartTime":182437.0,"Position":68.721756,"HyperDash":false},{"StartTime":182548.0,"Position":69.0,"HyperDash":false}]},{"StartTime":182772.0,"Objects":[{"StartTime":182772.0,"Position":156.0,"HyperDash":false}]},{"StartTime":182996.0,"Objects":[{"StartTime":182996.0,"Position":188.0,"HyperDash":false}]},{"StartTime":183220.0,"Objects":[{"StartTime":183220.0,"Position":258.0,"HyperDash":false},{"StartTime":183271.0,"Position":290.116547,"HyperDash":false},{"StartTime":183322.0,"Position":294.3538,"HyperDash":false},{"StartTime":183374.0,"Position":307.583344,"HyperDash":false},{"StartTime":183425.0,"Position":340.892883,"HyperDash":false},{"StartTime":183476.0,"Position":330.0654,"HyperDash":false},{"StartTime":183528.0,"Position":366.9192,"HyperDash":false},{"StartTime":183579.0,"Position":359.8023,"HyperDash":false},{"StartTime":183667.0,"Position":410.248352,"HyperDash":false}]},{"StartTime":184116.0,"Objects":[{"StartTime":184116.0,"Position":500.0,"HyperDash":false},{"StartTime":184199.0,"Position":507.066162,"HyperDash":false},{"StartTime":184283.0,"Position":497.157227,"HyperDash":false},{"StartTime":184367.0,"Position":504.2483,"HyperDash":false},{"StartTime":184451.0,"Position":508.3518,"HyperDash":false},{"StartTime":184526.0,"Position":505.497223,"HyperDash":false},{"StartTime":184601.0,"Position":509.630219,"HyperDash":false},{"StartTime":184676.0,"Position":505.763184,"HyperDash":false},{"StartTime":184787.0,"Position":500.0,"HyperDash":false}]},{"StartTime":185011.0,"Objects":[{"StartTime":185011.0,"Position":424.0,"HyperDash":false},{"StartTime":185104.0,"Position":408.773682,"HyperDash":false},{"StartTime":185234.0,"Position":345.858856,"HyperDash":false}]},{"StartTime":185459.0,"Objects":[{"StartTime":185459.0,"Position":273.0,"HyperDash":false},{"StartTime":185533.0,"Position":247.05632,"HyperDash":false},{"StartTime":185608.0,"Position":204.745392,"HyperDash":false},{"StartTime":185683.0,"Position":207.012665,"HyperDash":false},{"StartTime":185794.0,"Position":159.200455,"HyperDash":false}]},{"StartTime":186131.0,"Objects":[{"StartTime":186131.0,"Position":66.0,"HyperDash":false},{"StartTime":186182.0,"Position":77.52162,"HyperDash":false},{"StartTime":186233.0,"Position":97.0432358,"HyperDash":false},{"StartTime":186285.0,"Position":113.692917,"HyperDash":false},{"StartTime":186336.0,"Position":147.840286,"HyperDash":false},{"StartTime":186387.0,"Position":162.98764,"HyperDash":false},{"StartTime":186439.0,"Position":155.490845,"HyperDash":false},{"StartTime":186490.0,"Position":179.638214,"HyperDash":false},{"StartTime":186578.0,"Position":217.951324,"HyperDash":false}]},{"StartTime":186802.0,"Objects":[{"StartTime":186802.0,"Position":301.0,"HyperDash":false},{"StartTime":186895.0,"Position":319.187317,"HyperDash":false},{"StartTime":187025.0,"Position":380.578247,"HyperDash":false}]},{"StartTime":187250.0,"Objects":[{"StartTime":187250.0,"Position":468.0,"HyperDash":false},{"StartTime":187333.0,"Position":477.219818,"HyperDash":false},{"StartTime":187417.0,"Position":470.918518,"HyperDash":false},{"StartTime":187501.0,"Position":480.95874,"HyperDash":false},{"StartTime":187585.0,"Position":487.3309,"HyperDash":false},{"StartTime":187660.0,"Position":500.324768,"HyperDash":false},{"StartTime":187735.0,"Position":496.985931,"HyperDash":false},{"StartTime":187810.0,"Position":459.305664,"HyperDash":false},{"StartTime":187921.0,"Position":468.0,"HyperDash":false}]},{"StartTime":188145.0,"Objects":[{"StartTime":188145.0,"Position":372.0,"HyperDash":false}]},{"StartTime":188593.0,"Objects":[{"StartTime":188593.0,"Position":255.0,"HyperDash":false},{"StartTime":188686.0,"Position":237.844971,"HyperDash":false},{"StartTime":188816.0,"Position":175.499252,"HyperDash":false}]},{"StartTime":189041.0,"Objects":[{"StartTime":189041.0,"Position":140.0,"HyperDash":false},{"StartTime":189124.0,"Position":120.208252,"HyperDash":false},{"StartTime":189208.0,"Position":79.71341,"HyperDash":false},{"StartTime":189292.0,"Position":62.945713,"HyperDash":false},{"StartTime":189376.0,"Position":21.8198166,"HyperDash":false},{"StartTime":189451.0,"Position":43.38784,"HyperDash":false},{"StartTime":189526.0,"Position":60.00197,"HyperDash":false},{"StartTime":189601.0,"Position":110.413246,"HyperDash":false},{"StartTime":189712.0,"Position":140.0,"HyperDash":false}]},{"StartTime":189936.0,"Objects":[{"StartTime":189936.0,"Position":409.0,"HyperDash":false}]},{"StartTime":190384.0,"Objects":[{"StartTime":190384.0,"Position":297.0,"HyperDash":false},{"StartTime":190467.0,"Position":334.5554,"HyperDash":false},{"StartTime":190551.0,"Position":360.466858,"HyperDash":false},{"StartTime":190635.0,"Position":367.378357,"HyperDash":false},{"StartTime":190719.0,"Position":416.4679,"HyperDash":false},{"StartTime":190794.0,"Position":383.93924,"HyperDash":false},{"StartTime":190869.0,"Position":350.232544,"HyperDash":false},{"StartTime":190944.0,"Position":345.525879,"HyperDash":false},{"StartTime":191055.0,"Position":297.0,"HyperDash":false}]},{"StartTime":191280.0,"Objects":[{"StartTime":191280.0,"Position":233.0,"HyperDash":false},{"StartTime":191335.0,"Position":238.967834,"HyperDash":false},{"StartTime":191391.0,"Position":212.5211,"HyperDash":false},{"StartTime":191447.0,"Position":237.94754,"HyperDash":false},{"StartTime":191503.0,"Position":229.915482,"HyperDash":false},{"StartTime":191559.0,"Position":237.2686,"HyperDash":false},{"StartTime":191615.0,"Position":275.501129,"HyperDash":false},{"StartTime":191671.0,"Position":284.155334,"HyperDash":false},{"StartTime":191727.0,"Position":303.59848,"HyperDash":false},{"StartTime":191821.0,"Position":353.735931,"HyperDash":false},{"StartTime":191951.0,"Position":381.505432,"HyperDash":false}]},{"StartTime":192175.0,"Objects":[{"StartTime":192175.0,"Position":468.0,"HyperDash":false},{"StartTime":192258.0,"Position":482.7641,"HyperDash":false},{"StartTime":192342.0,"Position":450.513336,"HyperDash":false},{"StartTime":192426.0,"Position":466.262543,"HyperDash":false},{"StartTime":192510.0,"Position":463.004333,"HyperDash":false},{"StartTime":192585.0,"Position":465.113647,"HyperDash":false},{"StartTime":192660.0,"Position":470.2304,"HyperDash":false},{"StartTime":192735.0,"Position":465.3472,"HyperDash":false},{"StartTime":192846.0,"Position":468.0,"HyperDash":false}]},{"StartTime":193071.0,"Objects":[{"StartTime":193071.0,"Position":497.0,"HyperDash":false},{"StartTime":193126.0,"Position":512.0,"HyperDash":false},{"StartTime":193182.0,"Position":490.965454,"HyperDash":false},{"StartTime":193238.0,"Position":505.365143,"HyperDash":false},{"StartTime":193294.0,"Position":498.1796,"HyperDash":false},{"StartTime":193350.0,"Position":458.746429,"HyperDash":false},{"StartTime":193406.0,"Position":465.362274,"HyperDash":false},{"StartTime":193462.0,"Position":428.6823,"HyperDash":false},{"StartTime":193518.0,"Position":425.1735,"HyperDash":false},{"StartTime":193612.0,"Position":399.024475,"HyperDash":false},{"StartTime":193742.0,"Position":347.213928,"HyperDash":false}]},{"StartTime":193966.0,"Objects":[{"StartTime":193966.0,"Position":292.0,"HyperDash":false},{"StartTime":194049.0,"Position":284.2359,"HyperDash":false},{"StartTime":194133.0,"Position":296.486664,"HyperDash":false},{"StartTime":194217.0,"Position":289.737457,"HyperDash":false},{"StartTime":194301.0,"Position":296.995667,"HyperDash":false},{"StartTime":194376.0,"Position":298.886353,"HyperDash":false},{"StartTime":194451.0,"Position":301.7696,"HyperDash":false},{"StartTime":194526.0,"Position":303.6528,"HyperDash":false},{"StartTime":194637.0,"Position":292.0,"HyperDash":false}]},{"StartTime":194862.0,"Objects":[{"StartTime":194862.0,"Position":233.0,"HyperDash":false},{"StartTime":194917.0,"Position":233.672577,"HyperDash":false},{"StartTime":194973.0,"Position":188.020615,"HyperDash":false},{"StartTime":195029.0,"Position":185.026245,"HyperDash":false},{"StartTime":195085.0,"Position":146.409729,"HyperDash":false},{"StartTime":195141.0,"Position":122.932129,"HyperDash":false},{"StartTime":195197.0,"Position":132.166672,"HyperDash":false},{"StartTime":195253.0,"Position":111.858551,"HyperDash":false},{"StartTime":195309.0,"Position":94.3505554,"HyperDash":false},{"StartTime":195403.0,"Position":84.74842,"HyperDash":false},{"StartTime":195533.0,"Position":83.93751,"HyperDash":false}]},{"StartTime":195757.0,"Objects":[{"StartTime":195757.0,"Position":156.0,"HyperDash":false}]},{"StartTime":196205.0,"Objects":[{"StartTime":196205.0,"Position":292.0,"HyperDash":false},{"StartTime":196288.0,"Position":315.547729,"HyperDash":false},{"StartTime":196372.0,"Position":356.451416,"HyperDash":false},{"StartTime":196456.0,"Position":363.355164,"HyperDash":false},{"StartTime":196540.0,"Position":411.436859,"HyperDash":false},{"StartTime":196615.0,"Position":378.9151,"HyperDash":false},{"StartTime":196690.0,"Position":351.215363,"HyperDash":false},{"StartTime":196765.0,"Position":317.515625,"HyperDash":false},{"StartTime":196876.0,"Position":292.0,"HyperDash":false}]},{"StartTime":197101.0,"Objects":[{"StartTime":197101.0,"Position":224.0,"HyperDash":false},{"StartTime":197194.0,"Position":208.802353,"HyperDash":false},{"StartTime":197324.0,"Position":144.397034,"HyperDash":false}]},{"StartTime":197548.0,"Objects":[{"StartTime":197548.0,"Position":66.0,"HyperDash":false},{"StartTime":197631.0,"Position":48.2919579,"HyperDash":false},{"StartTime":197715.0,"Position":35.05251,"HyperDash":false},{"StartTime":197799.0,"Position":38.5374374,"HyperDash":false},{"StartTime":197883.0,"Position":11.4361858,"HyperDash":false},{"StartTime":197958.0,"Position":13.2977371,"HyperDash":false},{"StartTime":198033.0,"Position":27.7316284,"HyperDash":false},{"StartTime":198108.0,"Position":56.1405029,"HyperDash":false},{"StartTime":198219.0,"Position":66.0,"HyperDash":false}]},{"StartTime":198444.0,"Objects":[{"StartTime":198444.0,"Position":42.0,"HyperDash":false},{"StartTime":198499.0,"Position":55.76585,"HyperDash":false},{"StartTime":198555.0,"Position":34.16329,"HyperDash":false},{"StartTime":198611.0,"Position":54.15536,"HyperDash":false},{"StartTime":198667.0,"Position":77.49657,"HyperDash":false},{"StartTime":198723.0,"Position":71.00532,"HyperDash":false},{"StartTime":198779.0,"Position":105.424828,"HyperDash":false},{"StartTime":198835.0,"Position":125.435341,"HyperDash":false},{"StartTime":198891.0,"Position":136.756622,"HyperDash":false},{"StartTime":198985.0,"Position":174.4071,"HyperDash":false},{"StartTime":199115.0,"Position":215.646362,"HyperDash":false}]},{"StartTime":199339.0,"Objects":[{"StartTime":199339.0,"Position":292.0,"HyperDash":false},{"StartTime":199422.0,"Position":330.217377,"HyperDash":false},{"StartTime":199506.0,"Position":361.968842,"HyperDash":false},{"StartTime":199590.0,"Position":372.687,"HyperDash":false},{"StartTime":199674.0,"Position":408.582062,"HyperDash":false},{"StartTime":199749.0,"Position":367.224884,"HyperDash":false},{"StartTime":199824.0,"Position":343.6908,"HyperDash":false},{"StartTime":199899.0,"Position":338.7365,"HyperDash":false},{"StartTime":200010.0,"Position":292.0,"HyperDash":false}]},{"StartTime":200235.0,"Objects":[{"StartTime":200235.0,"Position":235.0,"HyperDash":false},{"StartTime":200290.0,"Position":235.309448,"HyperDash":false},{"StartTime":200346.0,"Position":241.240967,"HyperDash":false},{"StartTime":200402.0,"Position":245.969574,"HyperDash":false},{"StartTime":200458.0,"Position":247.421249,"HyperDash":false},{"StartTime":200514.0,"Position":241.446747,"HyperDash":false},{"StartTime":200570.0,"Position":272.996338,"HyperDash":false},{"StartTime":200626.0,"Position":270.733429,"HyperDash":false},{"StartTime":200682.0,"Position":286.54422,"HyperDash":false},{"StartTime":200776.0,"Position":331.074341,"HyperDash":false},{"StartTime":200906.0,"Position":359.601563,"HyperDash":false}]},{"StartTime":201131.0,"Objects":[{"StartTime":201131.0,"Position":447.0,"HyperDash":false}]},{"StartTime":201578.0,"Objects":[{"StartTime":201578.0,"Position":472.0,"HyperDash":false},{"StartTime":201671.0,"Position":420.90976,"HyperDash":false},{"StartTime":201801.0,"Position":392.654541,"HyperDash":false}]},{"StartTime":202026.0,"Objects":[{"StartTime":202026.0,"Position":323.0,"HyperDash":false},{"StartTime":202109.0,"Position":280.374054,"HyperDash":false},{"StartTime":202193.0,"Position":263.163239,"HyperDash":false},{"StartTime":202277.0,"Position":238.104523,"HyperDash":false},{"StartTime":202361.0,"Position":213.4443,"HyperDash":false},{"StartTime":202436.0,"Position":215.121521,"HyperDash":false},{"StartTime":202511.0,"Position":262.801849,"HyperDash":false},{"StartTime":202586.0,"Position":278.475128,"HyperDash":false},{"StartTime":202697.0,"Position":323.0,"HyperDash":false}]},{"StartTime":202922.0,"Objects":[{"StartTime":202922.0,"Position":370.0,"HyperDash":false}]},{"StartTime":203369.0,"Objects":[{"StartTime":203369.0,"Position":472.0,"HyperDash":false},{"StartTime":203462.0,"Position":457.79657,"HyperDash":false},{"StartTime":203592.0,"Position":459.52298,"HyperDash":false}]},{"StartTime":203817.0,"Objects":[{"StartTime":203817.0,"Position":373.0,"HyperDash":false},{"StartTime":203900.0,"Position":398.412079,"HyperDash":false},{"StartTime":203984.0,"Position":390.1198,"HyperDash":false},{"StartTime":204068.0,"Position":402.6163,"HyperDash":false},{"StartTime":204152.0,"Position":398.979218,"HyperDash":false},{"StartTime":204227.0,"Position":415.909515,"HyperDash":false},{"StartTime":204302.0,"Position":375.485352,"HyperDash":false},{"StartTime":204377.0,"Position":384.754333,"HyperDash":false},{"StartTime":204488.0,"Position":373.0,"HyperDash":false}]},{"StartTime":204713.0,"Objects":[{"StartTime":204713.0,"Position":294.0,"HyperDash":false},{"StartTime":204764.0,"Position":285.134979,"HyperDash":false},{"StartTime":204815.0,"Position":243.269958,"HyperDash":false},{"StartTime":204867.0,"Position":248.074249,"HyperDash":false},{"StartTime":204918.0,"Position":228.209229,"HyperDash":false},{"StartTime":204969.0,"Position":192.333786,"HyperDash":false},{"StartTime":205021.0,"Position":173.952545,"HyperDash":false},{"StartTime":205072.0,"Position":183.9248,"HyperDash":false},{"StartTime":205160.0,"Position":140.818085,"HyperDash":false}]},{"StartTime":205608.0,"Objects":[{"StartTime":205608.0,"Position":29.0,"HyperDash":false},{"StartTime":205659.0,"Position":63.86502,"HyperDash":false},{"StartTime":205710.0,"Position":61.7300453,"HyperDash":false},{"StartTime":205762.0,"Position":82.92575,"HyperDash":false},{"StartTime":205813.0,"Position":97.79077,"HyperDash":false},{"StartTime":205864.0,"Position":130.666214,"HyperDash":false},{"StartTime":205916.0,"Position":148.047455,"HyperDash":false},{"StartTime":205967.0,"Position":142.0752,"HyperDash":false},{"StartTime":206055.0,"Position":182.181915,"HyperDash":false}]},{"StartTime":206280.0,"Objects":[{"StartTime":206280.0,"Position":322.0,"HyperDash":false}]},{"StartTime":206504.0,"Objects":[{"StartTime":206504.0,"Position":344.0,"HyperDash":false},{"StartTime":206587.0,"Position":365.904449,"HyperDash":false},{"StartTime":206671.0,"Position":418.4734,"HyperDash":false},{"StartTime":206755.0,"Position":413.206177,"HyperDash":false},{"StartTime":206839.0,"Position":457.994324,"HyperDash":false},{"StartTime":206914.0,"Position":431.638641,"HyperDash":false},{"StartTime":206989.0,"Position":403.264984,"HyperDash":false},{"StartTime":207064.0,"Position":377.594177,"HyperDash":false},{"StartTime":207175.0,"Position":344.0,"HyperDash":false}]},{"StartTime":207399.0,"Objects":[{"StartTime":207399.0,"Position":294.0,"HyperDash":false},{"StartTime":207454.0,"Position":297.1099,"HyperDash":false},{"StartTime":207510.0,"Position":290.9207,"HyperDash":false},{"StartTime":207566.0,"Position":289.514343,"HyperDash":false},{"StartTime":207622.0,"Position":319.350433,"HyperDash":false},{"StartTime":207678.0,"Position":342.16394,"HyperDash":false},{"StartTime":207734.0,"Position":371.241455,"HyperDash":false},{"StartTime":207790.0,"Position":385.045563,"HyperDash":false},{"StartTime":207846.0,"Position":390.7907,"HyperDash":false},{"StartTime":207897.0,"Position":395.987244,"HyperDash":false},{"StartTime":207949.0,"Position":428.121765,"HyperDash":false},{"StartTime":208000.0,"Position":447.949615,"HyperDash":false},{"StartTime":208052.0,"Position":476.569366,"HyperDash":false},{"StartTime":208103.0,"Position":470.83667,"HyperDash":false},{"StartTime":208155.0,"Position":476.915344,"HyperDash":false},{"StartTime":208206.0,"Position":511.044159,"HyperDash":false},{"StartTime":208294.0,"Position":498.7854,"HyperDash":false}]},{"StartTime":215459.0,"Objects":[{"StartTime":215459.0,"Position":479.0,"HyperDash":false},{"StartTime":215542.0,"Position":229.0,"HyperDash":false},{"StartTime":215626.0,"Position":331.0,"HyperDash":false},{"StartTime":215710.0,"Position":226.0,"HyperDash":false},{"StartTime":215794.0,"Position":205.0,"HyperDash":false},{"StartTime":215878.0,"Position":472.0,"HyperDash":false},{"StartTime":215962.0,"Position":426.0,"HyperDash":false},{"StartTime":216046.0,"Position":340.0,"HyperDash":false},{"StartTime":216130.0,"Position":379.0,"HyperDash":false},{"StartTime":216214.0,"Position":21.0,"HyperDash":false},{"StartTime":216298.0,"Position":302.0,"HyperDash":false},{"StartTime":216382.0,"Position":148.0,"HyperDash":false},{"StartTime":216466.0,"Position":431.0,"HyperDash":false},{"StartTime":216550.0,"Position":424.0,"HyperDash":false},{"StartTime":216634.0,"Position":14.0,"HyperDash":false},{"StartTime":216718.0,"Position":423.0,"HyperDash":false},{"StartTime":216802.0,"Position":16.0,"HyperDash":false},{"StartTime":216885.0,"Position":284.0,"HyperDash":false},{"StartTime":216969.0,"Position":201.0,"HyperDash":false},{"StartTime":217053.0,"Position":29.0,"HyperDash":false},{"StartTime":217137.0,"Position":203.0,"HyperDash":false},{"StartTime":217221.0,"Position":129.0,"HyperDash":false},{"StartTime":217305.0,"Position":285.0,"HyperDash":false},{"StartTime":217389.0,"Position":254.0,"HyperDash":false},{"StartTime":217473.0,"Position":145.0,"HyperDash":false},{"StartTime":217557.0,"Position":230.0,"HyperDash":false},{"StartTime":217641.0,"Position":466.0,"HyperDash":false},{"StartTime":217725.0,"Position":86.0,"HyperDash":false},{"StartTime":217809.0,"Position":434.0,"HyperDash":false},{"StartTime":217893.0,"Position":159.0,"HyperDash":false},{"StartTime":217977.0,"Position":493.0,"HyperDash":false},{"StartTime":218061.0,"Position":191.0,"HyperDash":false},{"StartTime":218145.0,"Position":200.0,"HyperDash":false}]},{"StartTime":219041.0,"Objects":[{"StartTime":219041.0,"Position":205.0,"HyperDash":false},{"StartTime":219092.0,"Position":176.805145,"HyperDash":false},{"StartTime":219143.0,"Position":178.610275,"HyperDash":false},{"StartTime":219195.0,"Position":155.058655,"HyperDash":false},{"StartTime":219246.0,"Position":127.8638,"HyperDash":false},{"StartTime":219297.0,"Position":96.12851,"HyperDash":false},{"StartTime":219349.0,"Position":102.176147,"HyperDash":false},{"StartTime":219400.0,"Position":75.54979,"HyperDash":false},{"StartTime":219488.0,"Position":51.8611755,"HyperDash":false}]},{"StartTime":219936.0,"Objects":[{"StartTime":219936.0,"Position":75.0,"HyperDash":false},{"StartTime":219987.0,"Position":82.19486,"HyperDash":false},{"StartTime":220038.0,"Position":115.389725,"HyperDash":false},{"StartTime":220090.0,"Position":110.941345,"HyperDash":false},{"StartTime":220141.0,"Position":143.1362,"HyperDash":false},{"StartTime":220192.0,"Position":161.87149,"HyperDash":false},{"StartTime":220244.0,"Position":186.823853,"HyperDash":false},{"StartTime":220295.0,"Position":188.4502,"HyperDash":false},{"StartTime":220383.0,"Position":228.138824,"HyperDash":false}]},{"StartTime":220832.0,"Objects":[{"StartTime":220832.0,"Position":337.0,"HyperDash":false},{"StartTime":220915.0,"Position":317.352051,"HyperDash":false},{"StartTime":220999.0,"Position":312.6722,"HyperDash":false},{"StartTime":221083.0,"Position":337.992371,"HyperDash":false},{"StartTime":221167.0,"Position":326.29657,"HyperDash":false},{"StartTime":221242.0,"Position":334.67334,"HyperDash":false},{"StartTime":221317.0,"Position":327.066071,"HyperDash":false},{"StartTime":221392.0,"Position":352.458771,"HyperDash":false},{"StartTime":221503.0,"Position":337.0,"HyperDash":false}]},{"StartTime":221951.0,"Objects":[{"StartTime":221951.0,"Position":457.0,"HyperDash":false},{"StartTime":222006.0,"Position":446.041077,"HyperDash":false},{"StartTime":222062.0,"Position":457.04657,"HyperDash":false},{"StartTime":222118.0,"Position":470.052032,"HyperDash":false},{"StartTime":222174.0,"Position":449.0397,"HyperDash":false},{"StartTime":222268.0,"Position":464.369843,"HyperDash":false},{"StartTime":222398.0,"Position":457.0,"HyperDash":false}]},{"StartTime":222623.0,"Objects":[{"StartTime":222623.0,"Position":495.0,"HyperDash":false}]},{"StartTime":223071.0,"Objects":[{"StartTime":223071.0,"Position":331.0,"HyperDash":false},{"StartTime":223154.0,"Position":317.6592,"HyperDash":false},{"StartTime":223238.0,"Position":271.751648,"HyperDash":false},{"StartTime":223322.0,"Position":250.900024,"HyperDash":false},{"StartTime":223406.0,"Position":215.870728,"HyperDash":false},{"StartTime":223481.0,"Position":250.346268,"HyperDash":false},{"StartTime":223556.0,"Position":287.9995,"HyperDash":false},{"StartTime":223631.0,"Position":302.435822,"HyperDash":false},{"StartTime":223742.0,"Position":331.0,"HyperDash":false}]},{"StartTime":223966.0,"Objects":[{"StartTime":223966.0,"Position":399.0,"HyperDash":false}]},{"StartTime":224414.0,"Objects":[{"StartTime":224414.0,"Position":471.0,"HyperDash":false},{"StartTime":224488.0,"Position":457.712158,"HyperDash":false},{"StartTime":224563.0,"Position":447.379883,"HyperDash":false},{"StartTime":224638.0,"Position":456.0476,"HyperDash":false},{"StartTime":224749.0,"Position":456.115845,"HyperDash":false}]},{"StartTime":225086.0,"Objects":[{"StartTime":225086.0,"Position":326.0,"HyperDash":false},{"StartTime":225137.0,"Position":300.208832,"HyperDash":false},{"StartTime":225188.0,"Position":275.417664,"HyperDash":false},{"StartTime":225240.0,"Position":290.316833,"HyperDash":false},{"StartTime":225291.0,"Position":243.612946,"HyperDash":false},{"StartTime":225342.0,"Position":241.580139,"HyperDash":false},{"StartTime":225394.0,"Position":232.193756,"HyperDash":false},{"StartTime":225445.0,"Position":210.16098,"HyperDash":false},{"StartTime":225533.0,"Position":175.045547,"HyperDash":false}]},{"StartTime":225757.0,"Objects":[{"StartTime":225757.0,"Position":88.0,"HyperDash":false},{"StartTime":225850.0,"Position":65.83169,"HyperDash":false},{"StartTime":225980.0,"Position":74.3185,"HyperDash":false}]},{"StartTime":226205.0,"Objects":[{"StartTime":226205.0,"Position":140.0,"HyperDash":false},{"StartTime":226298.0,"Position":123.645569,"HyperDash":false},{"StartTime":226428.0,"Position":143.945816,"HyperDash":false}]},{"StartTime":226653.0,"Objects":[{"StartTime":226653.0,"Position":116.0,"HyperDash":false},{"StartTime":226736.0,"Position":106.660728,"HyperDash":false},{"StartTime":226820.0,"Position":50.7313728,"HyperDash":false},{"StartTime":226904.0,"Position":15.8698654,"HyperDash":false},{"StartTime":226988.0,"Position":3.21379948,"HyperDash":false},{"StartTime":227063.0,"Position":23.62399,"HyperDash":false},{"StartTime":227138.0,"Position":46.0249748,"HyperDash":false},{"StartTime":227213.0,"Position":81.71385,"HyperDash":false},{"StartTime":227324.0,"Position":116.0,"HyperDash":false}]},{"StartTime":227548.0,"Objects":[{"StartTime":227548.0,"Position":202.0,"HyperDash":false},{"StartTime":227641.0,"Position":228.322632,"HyperDash":false},{"StartTime":227771.0,"Position":281.902618,"HyperDash":false}]},{"StartTime":227996.0,"Objects":[{"StartTime":227996.0,"Position":370.0,"HyperDash":false},{"StartTime":228047.0,"Position":379.322418,"HyperDash":false},{"StartTime":228098.0,"Position":404.644836,"HyperDash":false},{"StartTime":228150.0,"Position":412.9706,"HyperDash":false},{"StartTime":228201.0,"Position":407.2122,"HyperDash":false},{"StartTime":228252.0,"Position":406.4538,"HyperDash":false},{"StartTime":228304.0,"Position":390.660919,"HyperDash":false},{"StartTime":228355.0,"Position":399.902527,"HyperDash":false},{"StartTime":228443.0,"Position":393.8684,"HyperDash":false}]},{"StartTime":228892.0,"Objects":[{"StartTime":228892.0,"Position":291.0,"HyperDash":false},{"StartTime":228985.0,"Position":255.7421,"HyperDash":false},{"StartTime":229115.0,"Position":211.252533,"HyperDash":false}]},{"StartTime":229339.0,"Objects":[{"StartTime":229339.0,"Position":136.0,"HyperDash":false},{"StartTime":229432.0,"Position":97.7420959,"HyperDash":false},{"StartTime":229562.0,"Position":56.25254,"HyperDash":false}]},{"StartTime":229787.0,"Objects":[{"StartTime":229787.0,"Position":20.0,"HyperDash":false},{"StartTime":229838.0,"Position":17.0399265,"HyperDash":false},{"StartTime":229889.0,"Position":21.079855,"HyperDash":false},{"StartTime":229941.0,"Position":25.0421333,"HyperDash":false},{"StartTime":229992.0,"Position":22.7113285,"HyperDash":false},{"StartTime":230043.0,"Position":24.4840775,"HyperDash":false},{"StartTime":230095.0,"Position":14.3101463,"HyperDash":false},{"StartTime":230146.0,"Position":9.403353,"HyperDash":false},{"StartTime":230234.0,"Position":15.3877077,"HyperDash":false}]},{"StartTime":230683.0,"Objects":[{"StartTime":230683.0,"Position":156.0,"HyperDash":false},{"StartTime":230776.0,"Position":173.746826,"HyperDash":false},{"StartTime":230906.0,"Position":186.4041,"HyperDash":false}]},{"StartTime":231131.0,"Objects":[{"StartTime":231131.0,"Position":264.0,"HyperDash":false},{"StartTime":231224.0,"Position":253.253189,"HyperDash":false},{"StartTime":231354.0,"Position":233.595917,"HyperDash":false}]},{"StartTime":231578.0,"Objects":[{"StartTime":231578.0,"Position":262.0,"HyperDash":false},{"StartTime":231629.0,"Position":267.8308,"HyperDash":false},{"StartTime":231680.0,"Position":297.661621,"HyperDash":false},{"StartTime":231732.0,"Position":320.886,"HyperDash":false},{"StartTime":231783.0,"Position":341.016968,"HyperDash":false},{"StartTime":231834.0,"Position":347.147919,"HyperDash":false},{"StartTime":231886.0,"Position":352.6344,"HyperDash":false},{"StartTime":231937.0,"Position":373.76535,"HyperDash":false},{"StartTime":232025.0,"Position":417.05014,"HyperDash":false}]},{"StartTime":232250.0,"Objects":[{"StartTime":232250.0,"Position":479.0,"HyperDash":false}]},{"StartTime":232474.0,"Objects":[{"StartTime":232474.0,"Position":500.0,"HyperDash":false},{"StartTime":232567.0,"Position":485.105865,"HyperDash":false},{"StartTime":232697.0,"Position":481.10556,"HyperDash":false}]},{"StartTime":232922.0,"Objects":[{"StartTime":232922.0,"Position":396.0,"HyperDash":false},{"StartTime":233015.0,"Position":344.7835,"HyperDash":false},{"StartTime":233145.0,"Position":320.1601,"HyperDash":false}]},{"StartTime":233369.0,"Objects":[{"StartTime":233369.0,"Position":264.0,"HyperDash":false},{"StartTime":233420.0,"Position":256.891846,"HyperDash":false},{"StartTime":233471.0,"Position":238.654755,"HyperDash":false},{"StartTime":233523.0,"Position":225.308167,"HyperDash":false},{"StartTime":233574.0,"Position":201.429947,"HyperDash":false},{"StartTime":233625.0,"Position":188.241165,"HyperDash":false},{"StartTime":233677.0,"Position":164.716415,"HyperDash":false},{"StartTime":233728.0,"Position":147.656219,"HyperDash":false},{"StartTime":233816.0,"Position":109.216957,"HyperDash":false}]},{"StartTime":234265.0,"Objects":[{"StartTime":234265.0,"Position":39.0,"HyperDash":false},{"StartTime":234320.0,"Position":18.3255081,"HyperDash":false},{"StartTime":234376.0,"Position":20.620575,"HyperDash":false},{"StartTime":234432.0,"Position":27.915638,"HyperDash":false},{"StartTime":234488.0,"Position":32.19548,"HyperDash":false},{"StartTime":234582.0,"Position":41.0421143,"HyperDash":false},{"StartTime":234712.0,"Position":39.0,"HyperDash":false}]},{"StartTime":234936.0,"Objects":[{"StartTime":234936.0,"Position":214.0,"HyperDash":false}]},{"StartTime":235160.0,"Objects":[{"StartTime":235160.0,"Position":206.0,"HyperDash":false},{"StartTime":235215.0,"Position":221.503036,"HyperDash":false},{"StartTime":235271.0,"Position":257.307831,"HyperDash":false},{"StartTime":235327.0,"Position":245.8396,"HyperDash":false},{"StartTime":235383.0,"Position":280.764069,"HyperDash":false},{"StartTime":235439.0,"Position":285.755646,"HyperDash":false},{"StartTime":235495.0,"Position":305.4811,"HyperDash":false},{"StartTime":235551.0,"Position":349.636,"HyperDash":false},{"StartTime":235607.0,"Position":359.014679,"HyperDash":false},{"StartTime":235658.0,"Position":335.625427,"HyperDash":false},{"StartTime":235710.0,"Position":307.9622,"HyperDash":false},{"StartTime":235761.0,"Position":298.080017,"HyperDash":false},{"StartTime":235813.0,"Position":295.555237,"HyperDash":false},{"StartTime":235864.0,"Position":258.349457,"HyperDash":false},{"StartTime":235916.0,"Position":266.998871,"HyperDash":false},{"StartTime":235967.0,"Position":250.476349,"HyperDash":false},{"StartTime":236055.0,"Position":206.0,"HyperDash":false}]},{"StartTime":236280.0,"Objects":[{"StartTime":236280.0,"Position":136.0,"HyperDash":false},{"StartTime":236335.0,"Position":133.3588,"HyperDash":false},{"StartTime":236391.0,"Position":108.360489,"HyperDash":false},{"StartTime":236447.0,"Position":81.36217,"HyperDash":false},{"StartTime":236503.0,"Position":56.1853027,"HyperDash":false},{"StartTime":236597.0,"Position":85.57534,"HyperDash":false},{"StartTime":236727.0,"Position":136.0,"HyperDash":false}]},{"StartTime":236951.0,"Objects":[{"StartTime":236951.0,"Position":203.0,"HyperDash":false},{"StartTime":237002.0,"Position":235.515,"HyperDash":false},{"StartTime":237053.0,"Position":231.03,"HyperDash":false},{"StartTime":237105.0,"Position":235.849213,"HyperDash":false},{"StartTime":237156.0,"Position":257.413,"HyperDash":false},{"StartTime":237207.0,"Position":301.49884,"HyperDash":false},{"StartTime":237259.0,"Position":304.939331,"HyperDash":false},{"StartTime":237310.0,"Position":305.025177,"HyperDash":false},{"StartTime":237398.0,"Position":353.232178,"HyperDash":false}]},{"StartTime":237847.0,"Objects":[{"StartTime":237847.0,"Position":468.0,"HyperDash":false},{"StartTime":237898.0,"Position":450.485,"HyperDash":false},{"StartTime":237949.0,"Position":421.97,"HyperDash":false},{"StartTime":238001.0,"Position":401.1508,"HyperDash":false},{"StartTime":238052.0,"Position":410.587,"HyperDash":false},{"StartTime":238103.0,"Position":391.50116,"HyperDash":false},{"StartTime":238155.0,"Position":374.060669,"HyperDash":false},{"StartTime":238206.0,"Position":362.974823,"HyperDash":false},{"StartTime":238294.0,"Position":317.767822,"HyperDash":false}]},{"StartTime":238742.0,"Objects":[{"StartTime":238742.0,"Position":180.0,"HyperDash":false},{"StartTime":238793.0,"Position":173.605637,"HyperDash":false},{"StartTime":238844.0,"Position":127.565094,"HyperDash":false},{"StartTime":238896.0,"Position":130.980515,"HyperDash":false},{"StartTime":238947.0,"Position":94.05988,"HyperDash":false},{"StartTime":238998.0,"Position":89.93131,"HyperDash":false},{"StartTime":239050.0,"Position":62.7224731,"HyperDash":false},{"StartTime":239101.0,"Position":61.4846268,"HyperDash":false},{"StartTime":239189.0,"Position":40.9435463,"HyperDash":false}]},{"StartTime":239414.0,"Objects":[{"StartTime":239414.0,"Position":1.0,"HyperDash":false}]},{"StartTime":239638.0,"Objects":[{"StartTime":239638.0,"Position":65.0,"HyperDash":false},{"StartTime":239731.0,"Position":90.205574,"HyperDash":false},{"StartTime":239861.0,"Position":144.621979,"HyperDash":false}]},{"StartTime":240086.0,"Objects":[{"StartTime":240086.0,"Position":205.0,"HyperDash":false},{"StartTime":240179.0,"Position":248.205566,"HyperDash":false},{"StartTime":240309.0,"Position":284.621979,"HyperDash":false}]},{"StartTime":240534.0,"Objects":[{"StartTime":240534.0,"Position":366.0,"HyperDash":false},{"StartTime":240585.0,"Position":363.81723,"HyperDash":false},{"StartTime":240636.0,"Position":373.2125,"HyperDash":false},{"StartTime":240688.0,"Position":391.223755,"HyperDash":false},{"StartTime":240739.0,"Position":374.622253,"HyperDash":false},{"StartTime":240790.0,"Position":403.4788,"HyperDash":false},{"StartTime":240842.0,"Position":402.731842,"HyperDash":false},{"StartTime":240893.0,"Position":374.42746,"HyperDash":false},{"StartTime":240981.0,"Position":383.571564,"HyperDash":false}]},{"StartTime":241429.0,"Objects":[{"StartTime":241429.0,"Position":238.0,"HyperDash":false},{"StartTime":241480.0,"Position":236.18277,"HyperDash":false},{"StartTime":241531.0,"Position":235.787491,"HyperDash":false},{"StartTime":241583.0,"Position":219.776245,"HyperDash":false},{"StartTime":241634.0,"Position":209.377747,"HyperDash":false},{"StartTime":241685.0,"Position":209.52121,"HyperDash":false},{"StartTime":241737.0,"Position":227.268158,"HyperDash":false},{"StartTime":241788.0,"Position":224.572525,"HyperDash":false},{"StartTime":241876.0,"Position":220.428436,"HyperDash":false}]},{"StartTime":242325.0,"Objects":[{"StartTime":242325.0,"Position":297.0,"HyperDash":false},{"StartTime":242380.0,"Position":294.727844,"HyperDash":false},{"StartTime":242436.0,"Position":344.7645,"HyperDash":false},{"StartTime":242492.0,"Position":365.6436,"HyperDash":false},{"StartTime":242548.0,"Position":374.1311,"HyperDash":false},{"StartTime":242604.0,"Position":400.993958,"HyperDash":false},{"StartTime":242660.0,"Position":429.0038,"HyperDash":false},{"StartTime":242716.0,"Position":433.924957,"HyperDash":false},{"StartTime":242772.0,"Position":449.6772,"HyperDash":false},{"StartTime":242823.0,"Position":429.0385,"HyperDash":false},{"StartTime":242875.0,"Position":395.5763,"HyperDash":false},{"StartTime":242926.0,"Position":382.350677,"HyperDash":false},{"StartTime":242978.0,"Position":386.8408,"HyperDash":false},{"StartTime":243029.0,"Position":359.934326,"HyperDash":false},{"StartTime":243081.0,"Position":325.105682,"HyperDash":false},{"StartTime":243132.0,"Position":316.240143,"HyperDash":false},{"StartTime":243220.0,"Position":297.0,"HyperDash":false}]},{"StartTime":243444.0,"Objects":[{"StartTime":243444.0,"Position":216.0,"HyperDash":false}]},{"StartTime":243668.0,"Objects":[{"StartTime":243668.0,"Position":136.0,"HyperDash":false},{"StartTime":243761.0,"Position":118.763763,"HyperDash":false},{"StartTime":243891.0,"Position":56.3044968,"HyperDash":false}]},{"StartTime":244116.0,"Objects":[{"StartTime":244116.0,"Position":2.0,"HyperDash":false},{"StartTime":244167.0,"Position":18.7359352,"HyperDash":false},{"StartTime":244218.0,"Position":16.5715733,"HyperDash":false},{"StartTime":244270.0,"Position":30.5787716,"HyperDash":false},{"StartTime":244321.0,"Position":26.5514565,"HyperDash":false},{"StartTime":244372.0,"Position":31.5832443,"HyperDash":false},{"StartTime":244424.0,"Position":12.6607952,"HyperDash":false},{"StartTime":244475.0,"Position":44.7331161,"HyperDash":false},{"StartTime":244563.0,"Position":29.33616,"HyperDash":false}]},{"StartTime":244787.0,"Objects":[{"StartTime":244787.0,"Position":5.0,"HyperDash":false}]},{"StartTime":245011.0,"Objects":[{"StartTime":245011.0,"Position":64.0,"HyperDash":false},{"StartTime":245104.0,"Position":111.355347,"HyperDash":false},{"StartTime":245234.0,"Position":143.98111,"HyperDash":false}]},{"StartTime":245459.0,"Objects":[{"StartTime":245459.0,"Position":223.0,"HyperDash":false},{"StartTime":245552.0,"Position":239.355347,"HyperDash":false},{"StartTime":245682.0,"Position":302.9811,"HyperDash":false}]},{"StartTime":245907.0,"Objects":[{"StartTime":245907.0,"Position":379.0,"HyperDash":false},{"StartTime":245958.0,"Position":388.482635,"HyperDash":false},{"StartTime":246009.0,"Position":389.9838,"HyperDash":false},{"StartTime":246061.0,"Position":389.548859,"HyperDash":false},{"StartTime":246112.0,"Position":388.03,"HyperDash":false},{"StartTime":246163.0,"Position":411.496918,"HyperDash":false},{"StartTime":246215.0,"Position":407.907623,"HyperDash":false},{"StartTime":246266.0,"Position":408.277283,"HyperDash":false},{"StartTime":246354.0,"Position":392.807251,"HyperDash":false}]},{"StartTime":246802.0,"Objects":[{"StartTime":246802.0,"Position":240.0,"HyperDash":false},{"StartTime":246895.0,"Position":229.351563,"HyperDash":false},{"StartTime":247025.0,"Position":219.99736,"HyperDash":false}]},{"StartTime":247250.0,"Objects":[{"StartTime":247250.0,"Position":152.0,"HyperDash":false},{"StartTime":247343.0,"Position":152.648453,"HyperDash":false},{"StartTime":247473.0,"Position":172.00264,"HyperDash":false}]},{"StartTime":247698.0,"Objects":[{"StartTime":247698.0,"Position":118.0,"HyperDash":false},{"StartTime":247749.0,"Position":132.050278,"HyperDash":false},{"StartTime":247800.0,"Position":136.635315,"HyperDash":false},{"StartTime":247852.0,"Position":178.833282,"HyperDash":false},{"StartTime":247903.0,"Position":187.755432,"HyperDash":false},{"StartTime":247954.0,"Position":198.440247,"HyperDash":false},{"StartTime":248006.0,"Position":210.910767,"HyperDash":false},{"StartTime":248057.0,"Position":231.14679,"HyperDash":false},{"StartTime":248145.0,"Position":263.981781,"HyperDash":false}]},{"StartTime":248593.0,"Objects":[{"StartTime":248593.0,"Position":427.0,"HyperDash":false},{"StartTime":248644.0,"Position":435.745178,"HyperDash":false},{"StartTime":248695.0,"Position":436.695557,"HyperDash":false},{"StartTime":248747.0,"Position":452.8285,"HyperDash":false},{"StartTime":248798.0,"Position":483.6382,"HyperDash":false},{"StartTime":248849.0,"Position":486.1087,"HyperDash":false},{"StartTime":248901.0,"Position":482.262,"HyperDash":false},{"StartTime":248952.0,"Position":487.997559,"HyperDash":false},{"StartTime":249040.0,"Position":466.941925,"HyperDash":false}]},{"StartTime":249489.0,"Objects":[{"StartTime":249489.0,"Position":411.0,"HyperDash":false},{"StartTime":249544.0,"Position":390.345581,"HyperDash":false},{"StartTime":249600.0,"Position":379.354645,"HyperDash":false},{"StartTime":249656.0,"Position":353.452728,"HyperDash":false},{"StartTime":249712.0,"Position":332.7199,"HyperDash":false},{"StartTime":249768.0,"Position":300.245636,"HyperDash":false},{"StartTime":249824.0,"Position":302.125122,"HyperDash":false},{"StartTime":249880.0,"Position":276.454742,"HyperDash":false},{"StartTime":249936.0,"Position":257.2194,"HyperDash":false},{"StartTime":249992.0,"Position":266.046967,"HyperDash":false},{"StartTime":250048.0,"Position":259.874542,"HyperDash":false},{"StartTime":250104.0,"Position":287.702118,"HyperDash":false},{"StartTime":250160.0,"Position":273.529663,"HyperDash":false},{"StartTime":250216.0,"Position":266.357239,"HyperDash":false},{"StartTime":250272.0,"Position":273.1848,"HyperDash":false},{"StartTime":250328.0,"Position":293.0124,"HyperDash":false},{"StartTime":250384.0,"Position":303.839966,"HyperDash":false},{"StartTime":250435.0,"Position":300.715057,"HyperDash":false},{"StartTime":250487.0,"Position":268.3105,"HyperDash":false},{"StartTime":250538.0,"Position":273.694855,"HyperDash":false},{"StartTime":250590.0,"Position":252.267029,"HyperDash":false},{"StartTime":250641.0,"Position":220.7485,"HyperDash":false},{"StartTime":250693.0,"Position":222.549347,"HyperDash":false},{"StartTime":250744.0,"Position":192.463943,"HyperDash":false},{"StartTime":250832.0,"Position":161.041138,"HyperDash":false}]},{"StartTime":251280.0,"Objects":[{"StartTime":251280.0,"Position":21.0,"HyperDash":false},{"StartTime":251363.0,"Position":30.73253,"HyperDash":false},{"StartTime":251447.0,"Position":6.497982,"HyperDash":false},{"StartTime":251531.0,"Position":32.2634354,"HyperDash":false},{"StartTime":251615.0,"Position":32.04535,"HyperDash":false},{"StartTime":251690.0,"Position":26.5926552,"HyperDash":false},{"StartTime":251765.0,"Position":30.1235,"HyperDash":false},{"StartTime":251840.0,"Position":42.65435,"HyperDash":false},{"StartTime":251951.0,"Position":21.0,"HyperDash":false}]},{"StartTime":252175.0,"Objects":[{"StartTime":252175.0,"Position":2.0,"HyperDash":false},{"StartTime":252226.0,"Position":0.0,"HyperDash":false},{"StartTime":252277.0,"Position":0.0,"HyperDash":false},{"StartTime":252329.0,"Position":0.0,"HyperDash":false},{"StartTime":252380.0,"Position":21.3323555,"HyperDash":false},{"StartTime":252431.0,"Position":0.887104034,"HyperDash":false},{"StartTime":252483.0,"Position":30.8665218,"HyperDash":false},{"StartTime":252534.0,"Position":43.70045,"HyperDash":false},{"StartTime":252622.0,"Position":61.3145256,"HyperDash":false}]},{"StartTime":253071.0,"Objects":[{"StartTime":253071.0,"Position":388.0,"HyperDash":false}]},{"StartTime":259563.0,"Objects":[{"StartTime":259563.0,"Position":293.0,"HyperDash":false},{"StartTime":259618.0,"Position":312.5664,"HyperDash":false},{"StartTime":259674.0,"Position":329.488525,"HyperDash":false},{"StartTime":259730.0,"Position":338.410675,"HyperDash":false},{"StartTime":259786.0,"Position":372.510681,"HyperDash":false},{"StartTime":259880.0,"Position":328.247833,"HyperDash":false},{"StartTime":260010.0,"Position":293.0,"HyperDash":false}]},{"StartTime":260235.0,"Objects":[{"StartTime":260235.0,"Position":46.0,"HyperDash":false}]},{"StartTime":260683.0,"Objects":[{"StartTime":260683.0,"Position":115.0,"HyperDash":false},{"StartTime":260766.0,"Position":105.132309,"HyperDash":false},{"StartTime":260850.0,"Position":48.58029,"HyperDash":false},{"StartTime":260934.0,"Position":44.7662964,"HyperDash":false},{"StartTime":261018.0,"Position":0.7381573,"HyperDash":false},{"StartTime":261093.0,"Position":18.1906548,"HyperDash":false},{"StartTime":261168.0,"Position":39.9074936,"HyperDash":false},{"StartTime":261243.0,"Position":61.8353424,"HyperDash":false},{"StartTime":261354.0,"Position":115.0,"HyperDash":false}]},{"StartTime":261578.0,"Objects":[{"StartTime":261578.0,"Position":189.0,"HyperDash":false},{"StartTime":261671.0,"Position":204.326355,"HyperDash":false},{"StartTime":261801.0,"Position":268.91156,"HyperDash":false}]},{"StartTime":262026.0,"Objects":[{"StartTime":262026.0,"Position":334.0,"HyperDash":false},{"StartTime":262119.0,"Position":377.326355,"HyperDash":false},{"StartTime":262249.0,"Position":413.91156,"HyperDash":false}]},{"StartTime":262474.0,"Objects":[{"StartTime":262474.0,"Position":480.0,"HyperDash":false},{"StartTime":262557.0,"Position":478.9318,"HyperDash":false},{"StartTime":262641.0,"Position":487.9834,"HyperDash":false},{"StartTime":262725.0,"Position":487.311127,"HyperDash":false},{"StartTime":262809.0,"Position":470.9604,"HyperDash":false},{"StartTime":262884.0,"Position":456.461121,"HyperDash":false},{"StartTime":262959.0,"Position":484.5402,"HyperDash":false},{"StartTime":263034.0,"Position":483.23175,"HyperDash":false},{"StartTime":263145.0,"Position":480.0,"HyperDash":false}]},{"StartTime":263369.0,"Objects":[{"StartTime":263369.0,"Position":497.0,"HyperDash":false},{"StartTime":263462.0,"Position":512.0,"HyperDash":false},{"StartTime":263592.0,"Position":495.6382,"HyperDash":false}]},{"StartTime":263817.0,"Objects":[{"StartTime":263817.0,"Position":374.0,"HyperDash":false}]},{"StartTime":264265.0,"Objects":[{"StartTime":264265.0,"Position":262.0,"HyperDash":false},{"StartTime":264348.0,"Position":218.500381,"HyperDash":false},{"StartTime":264432.0,"Position":198.645355,"HyperDash":false},{"StartTime":264516.0,"Position":171.790344,"HyperDash":false},{"StartTime":264600.0,"Position":142.7576,"HyperDash":false},{"StartTime":264675.0,"Position":162.23616,"HyperDash":false},{"StartTime":264750.0,"Position":212.892441,"HyperDash":false},{"StartTime":264825.0,"Position":225.5487,"HyperDash":false},{"StartTime":264936.0,"Position":262.0,"HyperDash":false}]},{"StartTime":265160.0,"Objects":[{"StartTime":265160.0,"Position":329.0,"HyperDash":false},{"StartTime":265253.0,"Position":325.012848,"HyperDash":false},{"StartTime":265383.0,"Position":319.4394,"HyperDash":false}]},{"StartTime":265608.0,"Objects":[{"StartTime":265608.0,"Position":254.0,"HyperDash":false},{"StartTime":265701.0,"Position":204.112839,"HyperDash":false},{"StartTime":265831.0,"Position":176.4014,"HyperDash":false}]},{"StartTime":266056.0,"Objects":[{"StartTime":266056.0,"Position":95.0,"HyperDash":false},{"StartTime":266139.0,"Position":98.58954,"HyperDash":false},{"StartTime":266223.0,"Position":69.13798,"HyperDash":false},{"StartTime":266307.0,"Position":66.6864243,"HyperDash":false},{"StartTime":266391.0,"Position":81.214325,"HyperDash":false},{"StartTime":266466.0,"Position":67.27553,"HyperDash":false},{"StartTime":266541.0,"Position":104.357269,"HyperDash":false},{"StartTime":266616.0,"Position":108.439018,"HyperDash":false},{"StartTime":266727.0,"Position":95.0,"HyperDash":false}]},{"StartTime":266951.0,"Objects":[{"StartTime":266951.0,"Position":146.0,"HyperDash":false}]},{"StartTime":267175.0,"Objects":[{"StartTime":267175.0,"Position":210.0,"HyperDash":false}]},{"StartTime":267399.0,"Objects":[{"StartTime":267399.0,"Position":264.0,"HyperDash":false},{"StartTime":267492.0,"Position":314.92868,"HyperDash":false},{"StartTime":267622.0,"Position":342.981537,"HyperDash":false}]},{"StartTime":267847.0,"Objects":[{"StartTime":267847.0,"Position":395.0,"HyperDash":false},{"StartTime":267930.0,"Position":404.894226,"HyperDash":false},{"StartTime":268014.0,"Position":439.331,"HyperDash":false},{"StartTime":268098.0,"Position":471.236,"HyperDash":false},{"StartTime":268182.0,"Position":509.962616,"HyperDash":false},{"StartTime":268257.0,"Position":484.739044,"HyperDash":false},{"StartTime":268332.0,"Position":448.118866,"HyperDash":false},{"StartTime":268407.0,"Position":434.531128,"HyperDash":false},{"StartTime":268518.0,"Position":395.0,"HyperDash":false}]},{"StartTime":268742.0,"Objects":[{"StartTime":268742.0,"Position":326.0,"HyperDash":false},{"StartTime":268797.0,"Position":303.009155,"HyperDash":false},{"StartTime":268853.0,"Position":275.691162,"HyperDash":false},{"StartTime":268909.0,"Position":273.2251,"HyperDash":false},{"StartTime":268965.0,"Position":238.87439,"HyperDash":false},{"StartTime":269021.0,"Position":234.523651,"HyperDash":false},{"StartTime":269077.0,"Position":225.172928,"HyperDash":false},{"StartTime":269133.0,"Position":194.8222,"HyperDash":false},{"StartTime":269189.0,"Position":174.471481,"HyperDash":false},{"StartTime":269283.0,"Position":186.943192,"HyperDash":false},{"StartTime":269413.0,"Position":214.870941,"HyperDash":false}]}]} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/1431386.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/1431386.osu new file mode 100644 index 0000000000..aa82b6ef8c --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/1431386.osu @@ -0,0 +1,560 @@ +osu file format v14 + +[General] +StackLeniency: 0.7 +Mode: 0 + +[Difficulty] +HPDrainRate:4 +CircleSize:3.3 +OverallDifficulty:4 +ApproachRate:5 +SliderMultiplier:1.6 +SliderTickRate:1 + +[Events] +//Background and Video events +//Break Periods +2,86704,92468 +2,208494,214259 +2,253271,258363 +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Sound Samples + +[TimingPoints] +534,447.761194029851,4,2,1,40,1,0 +4116,-100,4,2,1,20,0,0 +4563,-100,4,2,1,40,0,0 +5011,-100,4,2,1,60,0,0 +5459,-100,4,2,1,80,0,0 +5907,-100,4,2,1,80,0,0 +6242,-100,4,2,1,80,0,0 +6578,-100,4,2,1,80,0,0 +6802,-100,4,2,1,80,0,0 +7250,-100,4,2,1,80,0,0 +7922,-100,4,2,1,80,0,0 +14862,-100,4,2,1,80,0,0 +16653,-100,4,2,1,80,0,0 +22922,-100,4,2,1,80,0,0 +23369,-100,4,2,1,80,0,0 +24041,-100,4,2,1,80,0,0 +24265,-100,4,2,1,80,0,0 +26728,-100,4,2,1,80,0,0 +26951,-100,4,2,1,80,0,0 +27175,-100,4,2,1,80,0,0 +27399,-100,4,2,1,80,0,0 +29414,-100,4,2,1,80,0,0 +30981,-100,4,2,1,80,0,0 +38145,-100,4,2,1,80,0,0 +40832,-100,4,2,1,80,0,0 +41728,-100,4,2,1,80,0,0 +45310,-100,4,2,1,40,0,0 +45757,-100,4,2,1,40,0,0 +46205,-100,4,2,1,60,0,0 +46653,-100,4,2,1,80,0,0 +47101,-100,4,2,1,80,0,1 +47436,-100,4,2,1,80,0,1 +47772,-100,4,2,1,80,0,1 +47996,-100,4,2,1,80,0,0 +48892,-100,4,2,1,80,0,1 +56504,-100,4,2,1,80,0,1 +56728,-100,4,2,1,80,0,1 +58295,-100,4,2,1,80,0,1 +58519,-100,4,2,1,80,0,1 +62772,-100,4,2,1,80,0,1 +63220,-100,4,2,1,80,0,1 +63444,-100,4,2,1,80,0,1 +63668,-100,4,2,1,80,0,1 +70832,-100,4,2,1,80,0,1 +71056,-100,4,2,1,80,0,1 +72623,-100,4,2,1,80,0,1 +73071,-100,4,2,1,80,0,1 +73519,-100,4,2,1,80,0,1 +73966,-100,4,2,1,80,0,1 +75757,-100,4,2,1,80,0,1 +75981,-100,4,2,1,80,0,1 +77325,-100,4,2,1,80,0,1 +77548,-100,4,2,1,80,0,0 +80235,-100,4,2,1,80,0,0 +81131,-100,4,2,1,80,0,0 +82922,-100,4,2,1,80,0,0 +84713,-100,4,2,1,80,0,1 +85048,-100,4,2,1,80,0,1 +85384,-100,4,2,1,80,0,1 +85608,-100,4,2,1,80,0,1 +86056,-100,4,2,1,80,0,0 +93444,-100,4,2,1,80,0,0 +93668,-100,4,2,1,80,0,0 +101728,-100,4,2,1,80,0,0 +102175,-100,4,2,0,80,0,0 +102623,-100,4,2,1,80,0,0 +102847,-100,4,2,1,80,0,0 +103071,-100,4,2,1,80,0,0 +116951,-100,4,2,1,80,0,0 +119638,-100,4,2,1,80,0,0 +120534,-100,4,2,1,80,0,0 +124116,-100,4,2,0,80,0,0 +125459,-100,4,2,1,80,0,0 +125907,-100,4,2,1,80,0,1 +126242,-100,4,2,1,80,0,1 +126578,-100,4,2,1,80,0,1 +126802,-100,4,2,1,80,0,0 +127474,-100,4,2,1,80,0,0 +127698,-100,4,2,1,80,0,1 +135310,-100,4,2,1,80,0,1 +135534,-100,4,2,1,80,0,1 +137101,-100,4,2,1,80,0,1 +137325,-100,4,2,1,80,0,1 +142250,-100,4,2,1,80,0,1 +142474,-100,4,2,1,80,0,1 +149638,-100,4,2,1,80,0,1 +149862,-100,4,2,1,80,0,1 +151429,-100,4,2,1,80,0,1 +151877,-100,4,2,1,80,0,1 +152325,-100,4,2,1,80,0,1 +152772,-100,4,2,1,80,0,1 +154563,-100,4,2,1,80,0,1 +154787,-100,4,2,1,80,0,1 +156354,-100,4,2,1,80,0,0 +159041,-100,4,2,1,80,0,0 +159936,-100,4,2,1,80,0,0 +161168,-100,4,2,0,80,0,0 +161728,-100,4,2,1,80,0,0 +162623,-100,4,2,1,80,0,1 +163519,-100,4,2,1,80,0,0 +164414,-100,4,2,1,80,0,1 +165310,-100,4,2,1,80,0,0 +166205,-100,4,2,1,80,0,1 +167101,-100,4,2,1,80,0,0 +168332,-100,4,2,1,80,0,0 +168892,-100,4,2,1,80,0,0 +169787,-100,4,2,1,80,0,1 +170683,-100,4,2,1,80,0,0 +171578,-100,4,2,1,80,0,1 +172474,-100,4,2,1,80,0,0 +173369,-100,4,2,1,80,0,1 +173705,-100,4,2,1,80,0,0 +173929,-100,4,2,0,80,0,0 +174265,-100,4,2,1,80,0,0 +175048,-100,4,2,1,80,0,0 +175608,-100,4,2,1,80,0,0 +175832,-100,4,2,1,80,0,0 +176056,-100,4,2,1,80,0,1 +186131,-100,4,2,1,80,0,1 +186354,-100,4,2,1,80,0,1 +190384,-100,4,2,1,80,0,0 +206504,-100,4,2,1,80,0,0 +206839,-100,4,2,1,80,0,0 +207175,-100,4,2,1,80,0,0 +207399,-100,4,2,1,80,0,0 +207623,-100,4,2,1,80,0,0 +208071,-100,4,2,1,80,0,0 +208295,-100,4,2,1,80,0,0 +219041,-100,4,2,1,80,0,0 +220832,-100,4,2,1,80,0,0 +223071,-100,4,2,1,80,0,1 +223407,-100,4,2,1,80,0,0 +224414,-100,4,2,1,80,0,1 +225086,-100,4,2,1,80,0,1 +225422,-100,4,2,1,80,0,0 +226205,-100,4,2,1,80,0,1 +230235,-100,4,2,1,80,0,1 +230683,-100,4,2,1,80,0,1 +232026,-100,4,2,1,80,0,1 +232474,-100,4,2,1,80,0,1 +232922,-100,4,2,1,80,0,1 +233369,-100,4,2,1,80,0,1 +236951,-100,4,2,1,80,0,0 +239414,-100,4,2,1,80,0,0 +240533,-100,4,2,1,80,0,0 +244116,-100,4,2,1,80,0,0 +247698,-100,4,2,1,80,0,0 +250384,-100,4,2,1,80,0,0 +251280,-100,4,2,1,80,0,0 +251616,-100,4,2,1,80,0,0 +251951,-100,4,2,1,80,0,0 +252175,-100,4,2,1,80,0,0 +252623,-100,4,2,1,80,0,0 +253071,-100,4,2,1,80,0,0 +259563,-100,4,2,1,80,0,0 +260235,-100,4,2,1,80,0,0 +263593,-100,4,2,1,80,0,0 +263817,-100,4,2,1,80,0,0 +267399,-100,4,2,1,80,0,0 +268742,-100,4,2,1,80,0,0 +269414,-100,4,2,1,5,0,0 + +[HitObjects] +333,114,534,6,0,B|379:97|379:97|497:110,2,160,4|0|0,3:2|0:2|0:2,0:0:0:0: +182,204,1877,1,0,0:2:0:0: +333,290,2325,6,0,B|385:301|385:301|441:292|441:292|497:303,2,160,4|0|0,3:2|0:2|0:2,0:0:0:0: +182,204,3668,1,0,0:2:0:0: +26,121,4116,6,0,L|30:34,2,80,12|8|8,3:2|0:2|0:2,0:0:0:0: +20,297,5011,2,0,P|58:297|100:311,1,80,8|8,0:2|0:2,0:0:0:0: +178,348,5459,2,0,P|217:335|258:335,1,80,8|8,0:2|0:2,0:0:0:0: +308,264,5907,6,0,L|445:280,2,120,12|12|12,3:2|3:2|3:2,0:0:0:0: +224,234,6802,2,0,P|197:171|223:66,1,160,12|12,3:2|0:2,0:0:0:0: +372,10,7698,6,0,P|389:44|391:94,1,80,4|8,3:2|0:2,0:0:0:0: +390,173,8145,2,0,L|516:164,2,120,0|0|8,3:2|0:2|0:2,0:0:0:0: +330,237,9041,2,0,L|234:230,1,80,0|8,3:2|0:2,0:0:0:0: +171,190,9489,6,0,P|118:184|79:197,1,80,0|8,3:2|0:2,0:0:0:0: +9,219,9936,2,0,L|0:99,2,120,0|0|8,3:2|0:2|0:2,0:0:0:0: +28,305,10832,2,0,P|67:300|105:311,1,80,0|8,3:2|0:2,0:0:0:0: +184,353,11280,5,0,3:2:0:0: +343,277,11728,2,0,P|404:295|470:285,2,120,0|0|8,3:2|0:2|0:2,0:0:0:0: +290,206,12623,2,0,L|297:118,1,80,0|8,3:2|0:2,0:0:0:0: +265,43,13071,6,0,P|222:34|179:42,1,80,0|8,3:2|0:2,0:0:0:0: +123,100,13519,2,0,L|3:92,2,120,0|0|0,3:2|0:2|0:2,0:0:0:0: +187,160,14414,1,8,0:2:0:0: +184,336,14862,6,0,P|218:348|274:342,1,80,4|2,3:2|0:2,0:0:0:0: +343,310,15310,2,0,L|466:328,2,120,2|2|0,0:2|0:2|3:2,0:0:0:0: +297,234,16205,1,8,0:2:0:0: +219,76,16653,6,0,P|176:72|131:90,1,80,4|8,3:2|0:2,0:0:0:0: +65,129,17101,2,0,P|26:85|17:27,2,120,0|0|8,3:2|0:2|0:2,0:0:0:0: +144,170,17996,2,0,L|137:250,1,80,0|8,3:2|0:2,0:0:0:0: +156,336,18444,6,0,P|198:347|241:341,1,80,0|8,3:2|0:2,0:0:0:0: +309,296,18892,2,0,L|430:310,2,120,0|0|8,3:2|0:2|0:2,0:0:0:0: +237,245,19787,2,0,P|229:197|236:162,1,80,0|8,3:2|0:2,0:0:0:0: +296,103,20235,6,0,P|344:95|379:102,1,80,0|8,3:2|0:2,0:0:0:0: +441,157,20683,2,0,P|423:95|448:35,2,120,0|0|8,3:2|0:2|0:2,0:0:0:0: +501,220,21578,1,0,3:2:0:0: +386,353,22026,6,0,B|304:367|241:304|241:304|164:362|79:328,2,320,0|2|0,3:2|3:2|3:2,0:0:0:0: +465,315,24041,5,12,0:2:0:0: +497,233,24265,2,0,L|486:100,2,120,0|0|8,3:2|0:2|0:2,0:0:0:0: +410,247,25160,2,0,P|365:251|331:241,1,80,0|8,3:2|0:2,0:0:0:0: +262,187,25608,6,0,P|223:176|183:181,1,80,0|8,3:2|0:2,0:0:0:0: +136,254,26056,2,0,L|145:381,2,120,0|0|8,3:2|0:2|0:2,0:0:0:0: +67,198,26951,1,0,3:2:0:0: +118,29,27399,6,0,P|170:19|228:167,1,240,4|8,3:2|0:2,0:0:0:0: +162,107,28295,2,0,B|240:90|240:90|316:114|316:114|409:97,1,240,4|8,3:2|0:2,0:0:0:0: +481,84,29190,5,0,3:2:0:0: +499,170,29414,1,12,0:2:0:0: +454,246,29638,2,0,L|472:376,2,120,8|8|0,0:2|3:2|0:0,0:0:0:0: +375,205,30533,2,0,P|329:207|286:227,1,80,8|8,0:2|0:2,0:0:0:0: +220,263,30981,6,0,P|144:238|52:250,2,160,4|2|2,3:3|3:3|3:3,0:0:0:0: +365,362,32325,1,2,3:3:0:0: +480,229,32772,6,0,L|464:55,1,160,2|2,3:3|3:3,0:0:0:0: +393,18,33444,1,10,0:3:0:0: +323,72,33668,2,0,L|243:64,1,80,2|10,3:3|0:3,0:0:0:0: +162,27,34116,2,0,L|82:35,1,80,2|10,3:3|0:3,0:0:0:0: +31,106,34563,6,0,P|9:176|23:263,2,160,2|2|2,3:3|3:3|3:3,0:0:0:0: +183,194,35907,1,2,3:3:0:0: +336,278,36354,6,0,P|407:241|496:243,2,160,2|2|0,3:3|3:3|3:2,0:0:0:0: +278,344,37474,1,0,3:2:0:0: +218,278,37698,2,0,P|180:262|137:257,1,80,8|0,0:2|0:2,0:0:0:0: +55,272,38145,6,0,B|29:230|29:230|47:114,1,160,4|8,3:2|0:2,0:0:0:0: +188,16,39041,2,0,B|214:58|214:58|196:174,1,160,2|8,3:3|0:2,0:0:0:0: +305,306,39936,6,0,B|348:305|380:330|380:330|405:305|459:300,1,160,2|8,3:3|0:2,0:0:0:0: +486,127,40832,2,0,P|475:67|430:19,2,120,4|12|12,3:2|0:2|0:2,0:0:0:0: +415,180,41728,6,0,P|334:166|260:194,2,160,8|8|8,0:2|0:2|0:2,0:0:0:0: +353,344,43071,1,8,0:2:0:0: +181,303,43519,6,0,L|16:319,1,160,8|8,0:2|0:2,0:0:0:0: +21,142,44414,2,0,L|186:158,1,160,8|8,0:2|0:2,0:0:0:0: +257,114,45086,1,0,3:2:0:0: +329,63,45310,6,0,P|485:169|281:268,1,480,12|8,0:2|0:2,0:0:0:0: +257,114,47101,6,0,B|200:92|200:92|130:110,2,120,12|12|12,0:2|0:2|0:2,0:0:0:0: +336,151,47996,1,12,0:2:0:0: +417,185,48220,2,0,L|507:180,2,80,8|8|8,0:2|0:2|0:2,0:0:0:0: +379,264,48892,6,0,P|338:281|294:280,1,80,4|10,3:2|0:2,0:0:0:0: +218,257,49339,2,0,P|257:302|263:376,2,120,0|0|10,3:2|0:2|0:2,0:0:0:0: +142,210,50235,2,0,L|135:124,1,80,0|10,3:2|0:2,0:0:0:0: +75,65,50683,6,0,P|132:92|231:60,1,160,0|0,3:2|0:0,0:0:0:0: +295,116,51354,2,0,P|352:89|451:121,1,160,10|10,0:2|0:2,0:0:0:0: +498,180,52026,1,0,3:2:0:0: +404,329,52474,6,0,L|320:323,1,80,0|10,3:2|0:2,0:0:0:0: +251,272,52922,2,0,P|206:288|132:281,2,120,0|0|10,3:2|0:2|0:2,0:0:0:0: +298,196,53817,2,0,L|295:111,1,80,0|10,3:2|0:2,0:0:0:0: +249,40,54265,6,0,B|189:34|189:34|145:50|145:50|73:41,1,160,0|0,3:2|3:2,0:0:0:0: +8,197,55160,2,0,P|46:210|95:206,1,80,0|10,3:2|0:2,0:0:0:0: +165,171,55608,2,0,P|203:158|252:162,1,80,0|10,3:2|0:2,0:0:0:0: +329,173,56056,6,0,B|368:223|368:223|361:320,1,160,4|0,3:2|3:2,0:0:0:0: +189,360,56951,2,0,P|146:358|102:342,1,80,0|10,3:2|0:2,0:0:0:0: +44,288,57399,2,0,P|46:245|62:201,1,80,0|10,3:2|0:2,0:0:0:0: +97,131,57847,6,0,B|153:113|203:139|203:139|258:107,1,160,0|0,3:2|3:2,0:0:0:0: +396,20,58742,2,0,L|409:118,1,80,0|10,3:2|0:2,0:0:0:0: +473,156,59190,2,0,L|460:254,1,80,0|10,3:2|0:2,0:0:0:0: +450,322,59638,6,0,P|380:312|293:343,1,160,4|4,3:2|3:2,0:0:0:0: +215,373,60310,1,10,0:2:0:0: +127,363,60534,2,0,L|121:273,1,80,4|10,3:2|0:0,0:0:0:0: +116,195,60981,1,4,3:2:0:0: +110,18,61429,6,0,P|166:33|232:23,2,120,4|0|10,3:2|0:2|0:2,0:0:0:0: +22,13,62325,2,0,L|18:107,1,80,0|0,3:2|0:2,0:0:0:0: +10,180,62772,1,8,0:2:0:0: +76,238,62996,1,0,3:2:0:0: +154,197,63220,6,0,P|194:194|242:207,1,80,0|14,3:2|0:2,0:0:0:0: +307,250,63668,2,0,L|303:371,2,120,0|0|10,3:2|0:2|0:2,0:0:0:0: +311,162,64563,2,0,P|351:159|399:172,1,80,0|10,3:2|0:2,0:0:0:0: +435,243,65011,6,0,L|427:53,1,160,0|0,3:2|0:0,0:0:0:0: +350,41,65683,2,0,P|282:66|196:50,1,160,10|10,0:2|0:2,0:0:0:0: +116,17,66354,1,0,3:2:0:0: +44,177,66802,6,0,P|40:219|56:268,1,80,0|10,3:2|0:2,0:0:0:0: +131,287,67250,2,0,P|83:294|29:360,2,120,0|0|10,3:2|0:2|0:2,0:0:0:0: +206,332,68145,2,0,L|306:325,1,80,0|10,3:2|0:2,0:0:0:0: +354,270,68593,6,0,B|360:215|360:215|340:168|340:168|348:100,1,160,0|0,3:2|3:2,0:0:0:0: +479,230,69489,2,0,L|470:322,1,80,0|10,3:2|0:2,0:0:0:0: +395,354,69936,2,0,P|357:363|307:352,1,80,0|10,3:2|0:2,0:0:0:0: +239,314,70384,6,0,B|179:303|125:325|125:325|84:301,1,160,4|0,3:2|3:2,0:0:0:0: +11,143,71280,2,0,L|114:130,1,80,0|10,3:2|0:2,0:0:0:0: +152,69,71728,2,0,L|255:82,1,80,0|10,3:2|0:0,0:0:0:0: +271,157,72175,6,0,P|271:100|345:26,1,160,4|4,3:2|3:2,0:0:0:0: +425,16,72847,1,10,0:2:0:0: +489,75,73071,2,0,L|481:176,1,80,4|10,3:2|0:2,0:0:0:0: +408,203,73519,2,0,L|416:304,1,80,4|0,3:2|0:0,0:0:0:0: +482,338,73966,6,0,B|402:317|398:370|320:339,1,160,4|0,3:2|3:2,0:0:0:0: +157,287,74862,2,0,L|71:295,2,80,0|10|0,3:2|0:2|3:2,0:0:0:0: +226,231,75534,1,10,0:2:0:0: +288,169,75757,6,0,P|357:197|451:165,2,160,4|0|0,3:2|3:2|3:2,0:0:0:0: +225,106,76877,2,0,L|233:21,2,80,0|8|0,0:0|0:2|3:2,0:0:0:0: +172,176,77548,6,0,B|145:218|145:218|165:339,1,160,4|12,3:2|0:2,0:0:0:0: +9,239,78444,2,0,B|36:197|36:197|16:76,1,160,0|12,3:2|0:2,0:0:0:0: +186,37,79339,6,0,P|236:79|349:68,1,160,0|12,3:2|0:2,0:0:0:0: +405,37,80011,1,0,0:2:0:0: +482,77,80235,2,0,L|472:159,1,80,0|0,3:2|3:2,0:0:0:0: +392,195,80683,2,0,L|402:277,1,80,12|8,0:2|0:2,0:0:0:0: +474,324,81131,6,0,P|422:301|298:337,1,160,6|2,3:2|0:2,0:0:0:0: +148,296,82026,2,0,P|125:244|161:120,1,160,6|2,3:2|0:2,0:0:0:0: +287,44,82922,6,0,B|364:10|444:39|444:39|356:94|357:165|357:165|441:232|413:331,1,480,6|10,3:2|0:2,0:0:0:0: +242,304,84713,6,0,L|111:320,2,120,12|12|12,3:2|3:2|3:2,0:0:0:0: +277,223,85608,2,0,P|214:163|127:159,1,160,12|8,3:2|0:2,0:0:0:0: +11,270,86504,5,4,3:2:0:0: +321,111,93668,6,0,P|261:76|315:180,2,320,4|0|4,3:2|0:0|3:2,0:0:0:0: +321,111,97250,6,0,B|393:147|468:85|468:85|424:181|468:248,2,320,0|0|0,3:2|0:0|3:2,0:0:0:0: +321,111,100832,6,0,B|284:85|246:78|246:78|175:111|175:111|91:89|91:89|56:104|31:129,2,320,0|2|0,3:2|0:2|3:2,0:0:0:0: +385,170,102847,5,12,0:2:0:0: +322,231,103071,2,0,L|185:220,2,120,0|0|8,3:2|0:2|0:2,0:0:0:0: +404,262,103966,2,0,P|401:311|382:350,1,80,0|8,3:2|0:2,0:0:0:0: +308,374,104414,6,0,P|259:371|220:352,1,80,0|8,3:2|0:2,0:0:0:0: +164,300,104862,2,0,L|35:315,2,120,0|0|8,3:2|0:2|0:2,0:0:0:0: +202,221,105757,1,0,3:2:0:0: +276,61,106205,6,0,P|371:63|426:190,1,240,4|8,3:2|0:2,0:0:0:0: +354,230,107101,2,0,B|280:260|202:209|202:209|162:220|122:249,1,240,4|8,3:2|0:2,0:0:0:0: +55,290,107996,5,0,3:2:0:0: +0,220,108220,1,12,0:2:0:0: +43,143,108444,2,0,L|37:23,2,120,8|8|0,0:2|3:2|0:2,0:0:0:0: +128,164,109339,2,0,P|167:161|212:139,1,80,8|8,0:2|0:2,0:0:0:0: +242,64,109787,6,0,P|227:110|276:215,2,160,4|2|2,3:3|3:3|3:3,0:0:0:0: +411,14,111131,1,2,3:3:0:0: +503,163,111578,6,0,L|484:335,1,160,2|2,3:3|3:3,0:0:0:0: +405,360,112250,1,10,0:3:0:0: +333,308,112474,2,0,L|250:316,1,80,2|10,3:3|0:3,0:0:0:0: +175,357,112922,2,0,L|92:349,1,80,2|10,3:3|0:3,0:0:0:0: +28,292,113369,6,0,P|13:201|47:120,2,160,2|2|2,3:3|3:3|3:3,0:0:0:0: +190,222,114713,1,2,3:3:0:0: +349,148,115160,6,0,B|433:133|419:192|504:176,2,160,2|2|0,3:3|3:3|3:2,0:0:0:0: +265,176,116280,1,0,3:2:0:0: +224,254,116504,2,0,L|239:354,1,80,8|0,0:2|0:2,0:0:0:0: +320,357,116951,6,0,B|428:339|428:339|485:355,1,160,4|8,3:2|0:2,0:0:0:0: +501,176,117847,2,0,B|393:194|393:194|336:178,1,160,2|8,3:3|0:2,0:0:0:0: +200,78,118742,6,0,B|159:68|120:86|120:86|86:64|44:71,1,160,2|8,3:3|0:2,0:0:0:0: +16,244,119638,2,0,L|30:372,2,120,4|12|12,3:2|0:2|0:2,0:0:0:0: +88,193,120534,6,0,B|142:216|142:216|266:202,2,160,8|8|8,0:2|0:2|0:2,0:0:0:0: +172,38,121877,1,8,0:2:0:0: +322,129,122325,6,0,P|351:191|322:281,1,160,8|8,0:2|0:2,0:0:0:0: +150,284,123220,2,0,P|121:222|150:132,1,160,8|8,0:2|0:2,0:0:0:0: +194,63,123892,1,0,3:2:0:0: +277,35,124116,6,0,B|353:64|424:16|424:16|380:99|432:172|432:172|347:133|256:169,1,480,4|8,0:2|0:2,0:0:0:0: +121,246,125907,6,0,L|133:371,2,120,12|12|12,3:2|3:2|3:2,0:0:0:0: +104,160,126802,1,12,3:2:0:0: +88,72,127026,2,0,P|49:66|10:73,2,80,8|8|8,0:2|0:2|0:2,0:0:0:0: +171,103,127698,6,0,L|257:94,1,80,4|10,3:2|0:2,0:0:0:0: +333,66,128145,2,0,P|395:41|463:49,2,120,0|0|10,3:2|0:2|0:2,0:0:0:0: +318,153,129041,2,0,L|312:254,1,80,0|10,3:2|0:2,0:0:0:0: +304,320,129489,6,0,P|367:359|471:352,1,160,0|0,3:2|0:0,0:0:0:0: +506,291,130160,2,0,L|489:116,1,160,10|10,0:2|0:2,0:0:0:0: +483,43,130832,1,0,3:2:0:0: +308,67,131280,6,0,P|270:81|216:76,1,80,0|10,3:2|0:2,0:0:0:0: +142,85,131728,2,0,L|157:220,2,120,0|0|10,3:2|0:2|0:2,0:0:0:0: +55,69,132623,2,0,L|43:169,1,80,0|10,3:2|0:2,0:0:0:0: +33,235,133071,6,0,P|65:294|164:331,1,160,0|0,3:2|3:2,0:0:0:0: +275,210,133966,2,0,L|363:214,1,80,0|10,3:2|0:2,0:0:0:0: +389,294,134414,2,0,L|477:290,1,80,0|10,3:2|0:2,0:0:0:0: +503,208,134862,6,0,B|511:92|511:92|489:44,1,160,4|0,3:2|3:2,0:0:0:0: +318,30,135757,2,0,L|230:34,1,80,0|10,3:2|0:2,0:0:0:0: +204,114,136205,2,0,L|116:110,1,80,0|10,3:2|0:2,0:0:0:0: +49,62,136653,6,0,B|15:110|19:171|19:171|42:219,1,160,0|0,3:2|3:2,0:0:0:0: +200,278,137548,2,0,P|215:245|220:193,1,80,0|10,3:2|0:2,0:0:0:0: +204,114,137996,2,0,P|189:81|184:29,1,80,0|10,3:2|0:2,0:0:0:0: +270,23,138444,6,0,B|322:48|322:48|446:22,1,160,4|4,3:2|3:2,0:0:0:0: +490,83,139116,1,10,0:2:0:0: +504,169,139339,2,0,P|503:213|486:254,1,80,4|10,3:2|0:2,0:0:0:0: +428,309,139787,1,4,3:2:0:0: +268,241,140235,6,0,P|272:303|278:371,2,120,4|0|10,3:2|0:2|0:2,0:0:0:0: +207,176,141131,2,0,L|107:180,1,80,0|0,3:2|0:2,0:0:0:0: +39,184,141578,1,8,0:2:0:0: +8,101,141802,1,0,3:2:0:0: +71,40,142026,6,0,P|127:30|162:36,1,80,0|14,3:2|0:2,0:0:0:0: +220,85,142474,2,0,L|342:76,2,120,0|0|10,3:2|0:2|0:2,0:0:0:0: +158,148,143369,2,0,P|150:196|161:241,1,80,0|10,3:2|0:2,0:0:0:0: +192,306,143817,6,0,B|282:279|272:350|373:314,1,160,0|0,3:2|0:0,0:0:0:0: +431,294,144489,2,0,L|440:125,1,160,10|10,0:2|0:2,0:0:0:0: +448,46,145160,1,0,3:2:0:0: +272,31,145608,6,0,P|223:30|180:43,1,80,0|10,3:2|0:2,0:0:0:0: +127,96,146056,2,0,L|8:86,2,120,0|0|10,3:2|0:2|0:2,0:0:0:0: +193,154,146951,2,0,P|194:203|181:246,1,80,0|10,3:2|0:2,0:0:0:0: +109,276,147399,6,0,B|165:270|165:270|212:283|212:283|271:276,1,160,0|0,3:2|3:2,0:0:0:0: +441,253,148295,2,0,L|445:166,1,80,0|10,3:2|0:2,0:0:0:0: +482,93,148742,2,0,L|478:6,1,80,0|10,3:2|0:2,0:0:0:0: +390,23,149190,6,0,B|351:44|351:44|215:33,1,160,4|0,3:2|3:2,0:0:0:0: +59,21,150086,2,0,P|43:61|44:104,1,80,0|10,3:2|0:2,0:0:0:0: +94,169,150534,2,0,P|110:209|109:252,1,80,0|10,3:2|0:2,0:0:0:0: +42,301,150981,6,0,P|112:280|190:309,1,160,4|4,3:2|3:2,0:0:0:0: +257,368,151653,1,10,0:2:0:0: +335,327,151877,2,0,L|327:241,1,80,4|10,3:2|0:2,0:0:0:0: +264,185,152325,2,0,L|272:99,1,80,12|8,0:0|0:0,0:0:0:0: +318,30,152772,6,0,P|392:21|479:78,1,160,4|0,3:2|3:2,0:0:0:0: +494,234,153668,2,0,L|509:340,2,80,0|10|0,3:2|0:2|3:2,0:0:0:0: +413,198,154339,1,10,0:2:0:0: +332,234,154563,6,0,P|275:249|179:220,2,160,4|0|0,3:2|3:2|3:2,0:0:0:0: +413,198,155683,2,0,L|500:212,2,80,0|8|0,0:2|0:2|3:2,0:0:0:0: +379,116,156354,6,0,B|340:88|340:88|200:105,1,160,4|12,3:2|0:2,0:0:0:0: +103,225,157250,2,0,B|142:197|142:197|282:214,1,160,0|12,3:2|0:2,0:0:0:0: +131,338,158145,6,0,P|53:330|-1:274,1,160,0|12,3:2|0:2,0:0:0:0: +14,187,158817,1,0,0:2:0:0: +54,108,159041,2,0,L|144:98,1,80,0|0,3:2|3:2,0:0:0:0: +194,35,159489,2,0,L|284:45,1,80,12|8,0:2|0:2,0:0:0:0: +354,78,159936,6,0,P|379:136|369:252,1,160,4|0,3:2|0:2,0:0:0:0: +242,346,160832,2,0,P|217:288|227:172,1,160,4|0,3:2|0:2,0:0:0:0: +354,78,161728,5,4,3:2:0:0: +182,37,162175,2,0,L|98:30,1,80,10|2,0:2|3:2,0:0:0:0: +22,68,162623,2,0,B|5:128|5:128|20:185,2,120,12|12|12,0:2|0:2|0:2,0:0:0:0: +98,112,163519,5,4,3:2:0:0: +202,253,163966,2,0,L|303:248,1,80,10|2,0:2|3:2,0:0:0:0: +355,199,164414,2,0,B|415:182|415:182|472:197,2,120,12|12|12,0:2|0:2|0:2,0:0:0:0: +274,161,165310,5,4,3:2:0:0: +110,225,165757,2,0,L|125:325,1,80,10|2,0:2|3:2,0:0:0:0: +188,362,166205,2,0,B|248:379|248:379|305:364,2,120,12|12|12,0:2|0:2|0:2,0:0:0:0: +206,275,167101,6,0,P|262:242|380:262,2,160,4|12|12,3:2|0:2|0:2,0:0:0:0: +98,352,168332,2,0,L|78:212,1,120,4|8,3:2|0:2,0:0:0:0: +74,144,168892,5,4,3:2:0:0: +246,110,169339,2,0,L|330:120,1,80,10|2,0:2|3:2,0:0:0:0: +385,184,169787,2,0,B|445:167|445:167|502:182,2,120,12|12|12,0:2|0:2|0:2,0:0:0:0: +304,221,170683,5,4,3:2:0:0: +161,117,171131,2,0,L|59:124,1,80,10|2,0:2|3:2,0:0:0:0: +22,188,171578,2,0,B|5:248|5:248|20:305,2,120,12|12|12,0:2|0:2|0:2,0:0:0:0: +108,207,172474,1,4,3:2:0:0: +279,244,172922,6,0,L|365:238,1,80,10|2,0:2|3:2,0:0:0:0: +385,154,173369,2,0,B|445:171|445:171|502:156,2,120,12|12|4,0:2|0:2|0:3,0:0:0:0: +307,111,174265,6,0,L|211:122,1,80,6|2,0:2|3:2,0:0:0:0: +148,159,174713,2,0,L|5:142,2,120,14|6|0,0:2|3:2|0:2,0:0:0:0: +222,206,175608,1,8,0:2:0:0: +387,266,176056,6,0,P|416:206|409:150,2,120,6|2|8,3:2|0:2|0:2,0:0:0:0: +302,291,176951,2,0,B|212:264|234:332|122:296,1,160,0|2,3:2|3:2,0:0:0:0: +66,266,177623,1,10,0:2:0:0: +93,182,177847,6,0,L|197:173,2,80,2|10|2,3:2|0:2|3:2,0:0:0:0: +20,131,178519,2,0,P|47:58|150:26,1,160,10|8,0:2|0:2,0:0:0:0: +205,17,179190,1,0,3:2:0:0: +381,12,179638,6,0,L|485:21,1,80,2|10,3:2|0:2,0:0:0:0: +499,99,180086,2,0,P|500:152|472:220,2,120,2|2|10,3:2|0:2|0:2,0:0:0:0: +411,111,180981,1,2,3:2:0:0: +237,142,181429,6,0,L|139:134,1,80,2|10,3:2|0:2,0:0:0:0: +69,124,181877,2,0,P|48:55|48:-4,2,120,2|2|12,3:2|0:2|0:2,0:0:0:0: +102,205,182772,1,4,3:2:0:0: +172,258,182996,1,12,0:2:0:0: +258,276,183220,6,0,B|350:261|319:316|412:306,1,160,2|2,3:2|3:2,0:0:0:0: +500,154,184116,2,0,L|509:25,2,120,2|2|10,3:2|0:2|0:2,0:0:0:0: +424,198,185011,6,0,P|354:203|335:196,1,80,2|10,3:2|0:2,0:0:0:0: +273,148,185459,2,0,P|185:136|141:162,1,120,2|2,3:2|0:2,0:0:0:0: +66,243,186131,2,0,B|108:269|108:269|218:257,1,160,14|10,0:2|0:2,0:0:0:0: +301,230,186802,6,0,L|398:240,1,80,2|10,3:2|0:2,0:0:0:0: +468,250,187250,2,0,P|483:178|488:111,2,120,2|2|10,3:2|0:2|0:2,0:0:0:0: +430,329,188145,1,2,3:2:0:0: +255,364,188593,6,0,L|157:353,1,80,2|14,3:2|0:2,0:0:0:0: +140,274,189041,2,0,P|68:289|1:294,2,120,2|2|0,3:2|3:2|0:0,0:0:0:0: +205,215,189936,1,0,3:2:0:0: +297,64,190384,6,0,L|424:52,2,120,4|0|10,3:3|0:2|0:3,0:0:0:0: +233,125,191280,2,0,P|263:228|384:244,1,240,2|10,3:3|0:3,0:0:0:0: +468,231,192175,6,0,L|462:375,2,120,2|0|10,3:3|0:2|0:3,0:0:0:0: +497,146,193071,2,0,P|461:39|348:26,1,240,2|10,3:3|0:3,0:0:0:0: +292,94,193966,6,0,L|298:238,2,120,2|0|10,3:3|0:2|0:3,0:0:0:0: +233,27,194862,2,0,P|120:39|84:147,1,240,2|10,3:3|0:3,0:0:0:0: +120,227,195757,5,2,3:3:0:0: +292,261,196205,2,0,L|436:247,2,120,2|0|10,3:3|0:2|0:3,0:0:0:0: +224,317,197101,2,0,L|124:307,1,80,2|10,3:3|0:3,0:0:0:0: +66,267,197548,6,0,P|49:324|12:370,2,120,6|0|10,3:3|0:2|0:3,0:0:0:0: +42,181,198444,2,0,P|104:79|251:69,1,240,2|10,3:3|0:3,0:0:0:0: +292,100,199339,6,0,B|344:83|344:83|418:94,2,120,2|0|10,3:3|0:2|0:3,0:0:0:0: +235,168,200235,2,0,P|259:282|359:341,1,240,2|10,3:3|0:3,0:0:0:0: +447,330,201131,5,6,3:3:0:0: +472,156,201578,2,0,L|371:143,1,80,2|10,3:3|0:3,0:0:0:0: +323,90,202026,2,0,P|264:83|212:50,2,120,6|0|10,3:3|0:2|0:3,0:0:0:0: +370,15,202922,5,6,3:3:0:0: +472,156,203369,2,0,L|457:251,1,80,2|10,3:3|0:3,0:0:0:0: +373,256,203817,2,0,P|397:327|399:371,2,120,6|0|10,3:3|0:2|0:3,0:0:0:0: +294,214,204713,6,0,B|224:243|224:243|111:225,1,160,6|6,3:3|3:3,0:0:0:0: +29,93,205608,2,0,B|99:64|99:64|212:82,1,160,6|10,3:3|0:3,0:0:0:0: +267,99,206280,1,2,3:3:0:0: +344,141,206504,6,0,P|407:124|472:149,2,120,12|12|12,3:2|3:2|3:2,0:0:0:0: +294,214,207399,2,0,P|325:292|499:216,1,320,12|4,3:2|3:2,0:0:0:0: +256,192,215459,12,6,218145,3:2:0:0: +205,114,219041,6,0,B|119:107|119:107|44:141,1,160,12|12,3:2|3:2,0:0:0:0: +75,311,219936,2,0,B|161:318|161:318|236:284,1,160,12|12,3:2|3:2,0:0:0:0: +337,149,220832,6,0,L|325:15,2,120,12|12|12,3:2|3:2|3:2,0:0:0:0: +457,277,221951,2,0,L|447:377,2,80,0|8|8,3:2|0:2|0:2,0:0:0:0: +471,189,222623,5,2,0:2:0:0: +331,81,223071,2,0,B|279:103|279:103|200:94,2,120,4|8|0,3:2|0:2|3:2,0:0:0:0: +399,26,223966,1,8,0:2:0:0: +471,189,224414,6,0,L|453:333,1,120,12|12,3:2|3:2,0:0:0:0: +326,335,225086,2,0,B|276:306|276:306|149:326,1,160,12|0,3:2|0:2,0:0:0:0: +88,340,225757,2,0,P|75:299|76:251,1,80,8|8,0:2|0:2,0:0:0:0: +140,204,226205,6,0,L|144:123,1,80,4|10,3:2|0:2,0:0:0:0: +116,40,226653,2,0,P|58:49|3:25,2,120,0|0|10,3:2|0:2|0:2,0:0:0:0: +202,21,227548,2,0,L|283:25,1,80,0|10,3:2|0:2,0:0:0:0: +370,29,227996,6,0,B|404:72|404:72|392:196,1,160,0|0,3:2|3:2,0:0:0:0: +291,320,228892,2,0,L|178:329,1,80,0|10,3:2|0:2,0:0:0:0: +136,373,229339,2,0,L|23:364,1,80,0|10,3:2|0:2,0:0:0:0: +20,285,229787,6,0,B|8:231|8:231|24:183|24:183|14:121,1,160,4|0,3:2|3:2,0:0:0:0: +156,24,230683,2,0,P|182:74|187:103,1,80,0|10,3:2|0:2,0:0:0:0: +264,138,231131,2,0,P|238:188|233:217,1,80,0|10,3:2|0:2,0:0:0:0: +262,293,231578,6,0,B|312:314|312:314|440:299,1,160,4|4,3:2|3:2,0:0:0:0: +479,239,232250,1,10,0:2:0:0: +500,153,232474,2,0,P|499:119|481:77,1,80,4|10,3:2|0:2,0:0:0:0: +396,50,232922,2,0,P|362:51|320:69,1,80,4|0,3:2|0:0,0:0:0:0: +264,138,233369,6,0,B|173:153|201:102|101:116,1,160,4|0,3:2|3:2,0:0:0:0: +39,277,234265,2,0,L|32:359,2,80,0|10|0,3:2|0:2|3:2,0:0:0:0: +123,252,234936,1,10,0:2:0:0: +206,225,235160,6,0,P|261:245|383:213,2,160,4|0|0,3:2|3:2|3:2,0:0:0:0: +136,169,236280,2,0,L|48:175,2,80,0|8|0,0:2|0:2|3:2,0:0:0:0: +203,112,236951,6,0,B|253:81|253:81|377:98,1,160,4|12,3:2|0:2,0:0:0:0: +468,228,237847,2,0,B|418:197|418:197|294:214,1,160,0|12,3:2|0:2,0:0:0:0: +180,321,238742,6,0,P|120:328|31:252,1,160,0|12,3:2|0:2,0:0:0:0: +16,188,239414,1,0,0:2:0:0: +65,115,239638,2,0,L|147:107,1,80,0|0,3:2|3:2,0:0:0:0: +205,43,240086,2,0,L|287:51,1,80,12|0,0:2|0:2,0:0:0:0: +366,83,240534,6,0,P|389:155|382:244,1,160,0|12,3:2|0:2,0:0:0:0: +238,338,241429,2,0,P|215:266|222:177,1,160,0|12,3:2|0:2,0:0:0:0: +297,24,242325,6,0,P|369:54|462:47,2,160,0|12|0,3:2|0:2|3:2,0:0:0:0: +216,60,243444,1,0,3:2:0:0: +136,96,243668,2,0,L|56:89,1,80,8|8,0:2|0:2,0:0:0:0: +2,18,244116,6,0,P|26:102|26:206,1,160,4|0,3:2|3:2,0:0:0:0: +5,259,244787,1,8,0:2:0:0: +64,324,245011,2,0,L|156:326,1,80,0|8,3:2|0:2,0:0:0:0: +223,364,245459,2,0,L|315:362,1,80,0|8,3:2|0:2,0:0:0:0: +379,318,245907,6,0,P|395:247|390:145,1,160,4|0,3:2|3:2,0:0:0:0: +240,72,246802,2,0,P|225:116|220:149,1,80,8|8,3:2|3:2,0:0:0:0: +152,205,247250,2,0,P|167:249|172:282,1,80,8|8,3:2|3:2,0:0:0:0: +118,352,247698,6,0,P|174:314|275:316,1,160,4|0,3:2|0:0,0:0:0:0: +427,377,248593,2,0,P|465:321|463:220,1,160,4|0,3:2|0:0,0:0:0:0: +411,63,249489,6,0,B|326:66|257:31|257:31|306:192|306:192|227:143|142:154,1,480,4|10,3:2|0:2,0:0:0:0: +21,259,251280,6,0,L|32:378,2,120,12|12|12,3:2|3:2|3:2,0:0:0:0: +2,173,252175,2,0,P|19:77|84:24,1,160,12|12,3:2|0:2,0:0:0:0: +236,14,253071,5,4,3:2:0:0: +293,276,259563,6,0,L|392:265,2,80,12|4|12,0:2|3:2|0:2,0:0:0:0: +219,324,260235,5,0,3:2:0:0: +115,181,260683,2,0,P|59:205|-18:200,2,120,0|0|8,3:2|0:2|0:2,0:0:0:0: +189,133,261578,2,0,L|274:137,1,80,0|8,3:2|0:2,0:0:0:0: +334,195,262026,6,0,L|419:191,1,80,0|8,3:2|0:2,0:0:0:0: +480,132,262474,2,0,P|469:74|471:13,2,120,0|0|8,3:2|0:2|0:2,0:0:0:0: +497,218,263369,2,0,P|500:258|494:305,1,80,0|8,3:2|0:2,0:0:0:0: +434,361,263817,5,0,3:2:0:0: +262,319,264265,2,0,L|138:333,2,120,0|0|8,3:2|0:2|0:2,0:0:0:0: +329,262,265160,2,0,L|316:154,1,80,0|8,3:2|0:2,0:0:0:0: +254,123,265608,6,0,P|205:120|161:140,1,80,0|8,3:2|0:2,0:0:0:0: +95,164,266056,2,0,L|78:17,2,120,0|0|0,3:2|0:2|0:2,0:0:0:0: +112,250,266951,1,8,0:2:0:0: +178,308,267175,1,8,0:2:0:0: +264,289,267399,6,0,P|301:284|368:300,1,80,2|2,3:2|0:2,0:0:0:0: +395,218,267847,2,0,P|451:236|510:225,2,120,2|2|0,0:2|0:2|3:2,0:0:0:0: +326,162,268742,6,0,B|274:185|274:185|158:154|158:154|231:119,1,240,12|0,0:2|0:0,0:0:0:0: diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/1597806-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/1597806-expected-conversion.json new file mode 100644 index 0000000000..c14bdf1453 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/1597806-expected-conversion.json @@ -0,0 +1 @@ +{"Mappings":[{"StartTime":42.0,"Objects":[{"StartTime":42.0,"Position":288.0,"HyperDash":false},{"StartTime":124.0,"Position":246.095245,"HyperDash":false},{"StartTime":242.0,"Position":217.560577,"HyperDash":false}]},{"StartTime":443.0,"Objects":[{"StartTime":443.0,"Position":125.0,"HyperDash":false},{"StartTime":525.0,"Position":139.98082,"HyperDash":false},{"StartTime":643.0,"Position":171.8503,"HyperDash":false}]},{"StartTime":845.0,"Objects":[{"StartTime":845.0,"Position":95.0,"HyperDash":false},{"StartTime":927.0,"Position":92.32658,"HyperDash":false},{"StartTime":1045.0,"Position":117.385254,"HyperDash":false}]},{"StartTime":1247.0,"Objects":[{"StartTime":1247.0,"Position":250.0,"HyperDash":false},{"StartTime":1329.0,"Position":235.381714,"HyperDash":false},{"StartTime":1447.0,"Position":177.760284,"HyperDash":false}]},{"StartTime":1649.0,"Objects":[{"StartTime":1649.0,"Position":277.0,"HyperDash":false},{"StartTime":1731.0,"Position":323.6183,"HyperDash":false},{"StartTime":1849.0,"Position":349.239716,"HyperDash":false}]},{"StartTime":2051.0,"Objects":[{"StartTime":2051.0,"Position":448.0,"HyperDash":false},{"StartTime":2133.0,"Position":419.48,"HyperDash":false},{"StartTime":2251.0,"Position":376.0,"HyperDash":false}]},{"StartTime":2453.0,"Objects":[{"StartTime":2453.0,"Position":499.0,"HyperDash":false},{"StartTime":2535.0,"Position":501.8066,"HyperDash":false},{"StartTime":2653.0,"Position":496.029449,"HyperDash":false}]},{"StartTime":2855.0,"Objects":[{"StartTime":2855.0,"Position":397.0,"HyperDash":false},{"StartTime":2937.0,"Position":385.11322,"HyperDash":false},{"StartTime":3055.0,"Position":393.946869,"HyperDash":false}]},{"StartTime":3257.0,"Objects":[{"StartTime":3257.0,"Position":295.0,"HyperDash":false},{"StartTime":3339.0,"Position":290.097748,"HyperDash":false},{"StartTime":3457.0,"Position":291.69043,"HyperDash":false}]},{"StartTime":3658.0,"Objects":[{"StartTime":3658.0,"Position":134.0,"HyperDash":false},{"StartTime":3740.0,"Position":152.636856,"HyperDash":false},{"StartTime":3858.0,"Position":208.724045,"HyperDash":false}]},{"StartTime":4060.0,"Objects":[{"StartTime":4060.0,"Position":95.0,"HyperDash":false},{"StartTime":4142.0,"Position":103.823456,"HyperDash":false},{"StartTime":4260.0,"Position":126.276718,"HyperDash":false}]},{"StartTime":4462.0,"Objects":[{"StartTime":4462.0,"Position":217.0,"HyperDash":false},{"StartTime":4544.0,"Position":213.344086,"HyperDash":false},{"StartTime":4662.0,"Position":173.936813,"HyperDash":false}]},{"StartTime":4864.0,"Objects":[{"StartTime":4864.0,"Position":268.0,"HyperDash":false},{"StartTime":4946.0,"Position":265.3267,"HyperDash":false},{"StartTime":5064.0,"Position":265.68042,"HyperDash":false}]},{"StartTime":5266.0,"Objects":[{"StartTime":5266.0,"Position":418.0,"HyperDash":false},{"StartTime":5348.0,"Position":385.913544,"HyperDash":false},{"StartTime":5466.0,"Position":354.807678,"HyperDash":false}]},{"StartTime":5668.0,"Objects":[{"StartTime":5668.0,"Position":356.0,"HyperDash":false},{"StartTime":5750.0,"Position":390.300568,"HyperDash":false},{"StartTime":5868.0,"Position":421.002045,"HyperDash":false}]},{"StartTime":6070.0,"Objects":[{"StartTime":6070.0,"Position":265.0,"HyperDash":false},{"StartTime":6152.0,"Position":215.764069,"HyperDash":false},{"StartTime":6270.0,"Position":191.253845,"HyperDash":false}]},{"StartTime":6472.0,"Objects":[{"StartTime":6472.0,"Position":35.0,"HyperDash":false},{"StartTime":6554.0,"Position":56.2359238,"HyperDash":false},{"StartTime":6672.0,"Position":108.746155,"HyperDash":false}]},{"StartTime":6873.0,"Objects":[{"StartTime":6873.0,"Position":265.0,"HyperDash":false},{"StartTime":6955.0,"Position":252.764084,"HyperDash":false},{"StartTime":7073.0,"Position":191.253845,"HyperDash":false}]},{"StartTime":7275.0,"Objects":[{"StartTime":7275.0,"Position":323.0,"HyperDash":false},{"StartTime":7357.0,"Position":370.397949,"HyperDash":false},{"StartTime":7475.0,"Position":390.779846,"HyperDash":false}]},{"StartTime":7677.0,"Objects":[{"StartTime":7677.0,"Position":493.0,"HyperDash":false},{"StartTime":7759.0,"Position":475.602051,"HyperDash":false},{"StartTime":7877.0,"Position":425.220154,"HyperDash":false}]},{"StartTime":8079.0,"Objects":[{"StartTime":8079.0,"Position":323.0,"HyperDash":false},{"StartTime":8161.0,"Position":345.397949,"HyperDash":false},{"StartTime":8279.0,"Position":390.779846,"HyperDash":false}]},{"StartTime":8481.0,"Objects":[{"StartTime":8481.0,"Position":273.0,"HyperDash":false}]},{"StartTime":8682.0,"Objects":[{"StartTime":8682.0,"Position":187.0,"HyperDash":false}]},{"StartTime":8883.0,"Objects":[{"StartTime":8883.0,"Position":101.0,"HyperDash":false}]},{"StartTime":9084.0,"Objects":[{"StartTime":9084.0,"Position":187.0,"HyperDash":false}]},{"StartTime":9285.0,"Objects":[{"StartTime":9285.0,"Position":101.0,"HyperDash":false}]},{"StartTime":9486.0,"Objects":[{"StartTime":9486.0,"Position":15.0,"HyperDash":false}]},{"StartTime":9687.0,"Objects":[{"StartTime":9687.0,"Position":187.0,"HyperDash":false},{"StartTime":9769.0,"Position":140.010742,"HyperDash":false},{"StartTime":9887.0,"Position":113.855469,"HyperDash":false}]},{"StartTime":10088.0,"Objects":[{"StartTime":10088.0,"Position":264.0,"HyperDash":false},{"StartTime":10170.0,"Position":285.762848,"HyperDash":false},{"StartTime":10288.0,"Position":285.372772,"HyperDash":false}]},{"StartTime":10490.0,"Objects":[{"StartTime":10490.0,"Position":287.0,"HyperDash":false},{"StartTime":10572.0,"Position":302.9239,"HyperDash":false},{"StartTime":10690.0,"Position":338.033844,"HyperDash":false}]},{"StartTime":10892.0,"Objects":[{"StartTime":10892.0,"Position":422.0,"HyperDash":false},{"StartTime":10974.0,"Position":429.5159,"HyperDash":false},{"StartTime":11092.0,"Position":417.753174,"HyperDash":false}]},{"StartTime":11294.0,"Objects":[{"StartTime":11294.0,"Position":287.0,"HyperDash":false},{"StartTime":11376.0,"Position":299.820526,"HyperDash":false},{"StartTime":11494.0,"Position":348.207428,"HyperDash":false}]},{"StartTime":11696.0,"Objects":[{"StartTime":11696.0,"Position":166.0,"HyperDash":false},{"StartTime":11778.0,"Position":186.67955,"HyperDash":false},{"StartTime":11896.0,"Position":232.26709,"HyperDash":false}]},{"StartTime":12098.0,"Objects":[{"StartTime":12098.0,"Position":332.0,"HyperDash":false},{"StartTime":12180.0,"Position":300.8351,"HyperDash":false},{"StartTime":12298.0,"Position":258.427124,"HyperDash":false}]},{"StartTime":12500.0,"Objects":[{"StartTime":12500.0,"Position":394.0,"HyperDash":false},{"StartTime":12582.0,"Position":438.1649,"HyperDash":false},{"StartTime":12700.0,"Position":467.572876,"HyperDash":false}]},{"StartTime":12902.0,"Objects":[{"StartTime":12902.0,"Position":332.0,"HyperDash":false},{"StartTime":12984.0,"Position":286.8351,"HyperDash":false},{"StartTime":13102.0,"Position":258.427124,"HyperDash":false}]},{"StartTime":13303.0,"Objects":[{"StartTime":13303.0,"Position":413.0,"HyperDash":false},{"StartTime":13385.0,"Position":402.2547,"HyperDash":false},{"StartTime":13503.0,"Position":417.4853,"HyperDash":false}]},{"StartTime":13705.0,"Objects":[{"StartTime":13705.0,"Position":327.0,"HyperDash":false},{"StartTime":13787.0,"Position":319.2547,"HyperDash":false},{"StartTime":13905.0,"Position":331.4853,"HyperDash":false}]},{"StartTime":14107.0,"Objects":[{"StartTime":14107.0,"Position":241.0,"HyperDash":false},{"StartTime":14189.0,"Position":262.25473,"HyperDash":false},{"StartTime":14307.0,"Position":245.485291,"HyperDash":false}]},{"StartTime":14509.0,"Objects":[{"StartTime":14509.0,"Position":118.0,"HyperDash":false},{"StartTime":14591.0,"Position":165.460083,"HyperDash":false},{"StartTime":14709.0,"Position":192.2929,"HyperDash":false}]},{"StartTime":14911.0,"Objects":[{"StartTime":14911.0,"Position":297.0,"HyperDash":false},{"StartTime":14993.0,"Position":276.830261,"HyperDash":false},{"StartTime":15111.0,"Position":250.244568,"HyperDash":false}]},{"StartTime":15313.0,"Objects":[{"StartTime":15313.0,"Position":273.0,"HyperDash":false},{"StartTime":15395.0,"Position":254.357025,"HyperDash":false},{"StartTime":15513.0,"Position":244.602539,"HyperDash":false}]},{"StartTime":15715.0,"Objects":[{"StartTime":15715.0,"Position":235.0,"HyperDash":false},{"StartTime":15797.0,"Position":262.7597,"HyperDash":false},{"StartTime":15915.0,"Position":306.7758,"HyperDash":false}]},{"StartTime":16117.0,"Objects":[{"StartTime":16117.0,"Position":441.0,"HyperDash":false},{"StartTime":16199.0,"Position":431.2403,"HyperDash":false},{"StartTime":16317.0,"Position":369.2242,"HyperDash":false}]},{"StartTime":16518.0,"Objects":[{"StartTime":16518.0,"Position":235.0,"HyperDash":false},{"StartTime":16600.0,"Position":257.7597,"HyperDash":false},{"StartTime":16718.0,"Position":306.7758,"HyperDash":false}]},{"StartTime":16920.0,"Objects":[{"StartTime":16920.0,"Position":436.0,"HyperDash":false},{"StartTime":17002.0,"Position":444.7306,"HyperDash":false},{"StartTime":17120.0,"Position":445.098969,"HyperDash":false}]},{"StartTime":17322.0,"Objects":[{"StartTime":17322.0,"Position":345.0,"HyperDash":false},{"StartTime":17404.0,"Position":386.333862,"HyperDash":false},{"StartTime":17522.0,"Position":414.106964,"HyperDash":false}]},{"StartTime":17724.0,"Objects":[{"StartTime":17724.0,"Position":208.0,"HyperDash":false},{"StartTime":17806.0,"Position":249.6,"HyperDash":false},{"StartTime":17924.0,"Position":268.0,"HyperDash":false}]},{"StartTime":18126.0,"Objects":[{"StartTime":18126.0,"Position":187.0,"HyperDash":false}]},{"StartTime":18528.0,"Objects":[{"StartTime":18528.0,"Position":187.0,"HyperDash":false}]},{"StartTime":18930.0,"Objects":[{"StartTime":18930.0,"Position":187.0,"HyperDash":false}]},{"StartTime":19332.0,"Objects":[{"StartTime":19332.0,"Position":187.0,"HyperDash":false}]},{"StartTime":19532.0,"Objects":[{"StartTime":19532.0,"Position":187.0,"HyperDash":false}]},{"StartTime":19733.0,"Objects":[{"StartTime":19733.0,"Position":345.0,"HyperDash":false}]},{"StartTime":19933.0,"Objects":[{"StartTime":19933.0,"Position":257.0,"HyperDash":false}]},{"StartTime":20135.0,"Objects":[{"StartTime":20135.0,"Position":471.0,"HyperDash":false}]},{"StartTime":20335.0,"Objects":[{"StartTime":20335.0,"Position":384.0,"HyperDash":false}]},{"StartTime":20537.0,"Objects":[{"StartTime":20537.0,"Position":284.0,"HyperDash":false}]},{"StartTime":20737.0,"Objects":[{"StartTime":20737.0,"Position":371.0,"HyperDash":false}]},{"StartTime":20938.0,"Objects":[{"StartTime":20938.0,"Position":157.0,"HyperDash":false}]},{"StartTime":21140.0,"Objects":[{"StartTime":21140.0,"Position":244.0,"HyperDash":false}]},{"StartTime":21340.0,"Objects":[{"StartTime":21340.0,"Position":188.0,"HyperDash":false}]},{"StartTime":21542.0,"Objects":[{"StartTime":21542.0,"Position":188.0,"HyperDash":false}]},{"StartTime":21743.0,"Objects":[{"StartTime":21743.0,"Position":345.0,"HyperDash":false}]},{"StartTime":21944.0,"Objects":[{"StartTime":21944.0,"Position":250.0,"HyperDash":false}]},{"StartTime":22145.0,"Objects":[{"StartTime":22145.0,"Position":419.0,"HyperDash":false},{"StartTime":22227.0,"Position":405.25,"HyperDash":false},{"StartTime":22345.0,"Position":344.0,"HyperDash":false}]},{"StartTime":22547.0,"Objects":[{"StartTime":22547.0,"Position":196.0,"HyperDash":false},{"StartTime":22629.0,"Position":241.75,"HyperDash":false},{"StartTime":22747.0,"Position":271.0,"HyperDash":false}]},{"StartTime":22948.0,"Objects":[{"StartTime":22948.0,"Position":419.0,"HyperDash":false}]},{"StartTime":23149.0,"Objects":[{"StartTime":23149.0,"Position":344.0,"HyperDash":false}]},{"StartTime":23350.0,"Objects":[{"StartTime":23350.0,"Position":305.0,"HyperDash":false},{"StartTime":23432.0,"Position":326.616516,"HyperDash":false},{"StartTime":23550.0,"Position":306.871063,"HyperDash":false}]},{"StartTime":23752.0,"Objects":[{"StartTime":23752.0,"Position":240.0,"HyperDash":false},{"StartTime":23834.0,"Position":244.383484,"HyperDash":false},{"StartTime":23952.0,"Position":238.128937,"HyperDash":false}]},{"StartTime":24154.0,"Objects":[{"StartTime":24154.0,"Position":429.0,"HyperDash":false},{"StartTime":24236.0,"Position":381.322876,"HyperDash":false},{"StartTime":24354.0,"Position":354.177734,"HyperDash":false}]},{"StartTime":24556.0,"Objects":[{"StartTime":24556.0,"Position":232.0,"HyperDash":false},{"StartTime":24638.0,"Position":267.677124,"HyperDash":false},{"StartTime":24756.0,"Position":306.822266,"HyperDash":false}]},{"StartTime":24958.0,"Objects":[{"StartTime":24958.0,"Position":429.0,"HyperDash":false},{"StartTime":25040.0,"Position":386.322876,"HyperDash":false},{"StartTime":25158.0,"Position":354.177734,"HyperDash":false}]},{"StartTime":25360.0,"Objects":[{"StartTime":25360.0,"Position":501.0,"HyperDash":false}]},{"StartTime":25561.0,"Objects":[{"StartTime":25561.0,"Position":429.0,"HyperDash":false}]},{"StartTime":25762.0,"Objects":[{"StartTime":25762.0,"Position":491.0,"HyperDash":false},{"StartTime":25844.0,"Position":475.629547,"HyperDash":false},{"StartTime":25962.0,"Position":490.096466,"HyperDash":false}]},{"StartTime":26163.0,"Objects":[{"StartTime":26163.0,"Position":372.0,"HyperDash":false},{"StartTime":26245.0,"Position":390.370453,"HyperDash":false},{"StartTime":26363.0,"Position":372.903534,"HyperDash":false}]},{"StartTime":26565.0,"Objects":[{"StartTime":26565.0,"Position":372.0,"HyperDash":false}]},{"StartTime":26766.0,"Objects":[{"StartTime":26766.0,"Position":431.0,"HyperDash":false}]},{"StartTime":26967.0,"Objects":[{"StartTime":26967.0,"Position":372.0,"HyperDash":false}]},{"StartTime":27168.0,"Objects":[{"StartTime":27168.0,"Position":314.0,"HyperDash":false}]},{"StartTime":27369.0,"Objects":[{"StartTime":27369.0,"Position":254.0,"HyperDash":false}]},{"StartTime":27570.0,"Objects":[{"StartTime":27570.0,"Position":313.0,"HyperDash":false}]},{"StartTime":27771.0,"Objects":[{"StartTime":27771.0,"Position":372.0,"HyperDash":false},{"StartTime":27821.0,"Position":382.6753,"HyperDash":false},{"StartTime":27871.0,"Position":425.3506,"HyperDash":false},{"StartTime":27921.0,"Position":431.0259,"HyperDash":false},{"StartTime":27971.0,"Position":436.7012,"HyperDash":false},{"StartTime":28021.0,"Position":466.3765,"HyperDash":false},{"StartTime":28071.0,"Position":463.0,"HyperDash":false},{"StartTime":28121.0,"Position":473.0,"HyperDash":false},{"StartTime":28172.0,"Position":473.0,"HyperDash":false},{"StartTime":28222.0,"Position":457.0,"HyperDash":false},{"StartTime":28272.0,"Position":481.0,"HyperDash":false},{"StartTime":28322.0,"Position":460.0,"HyperDash":false},{"StartTime":28373.0,"Position":444.1494,"HyperDash":false},{"StartTime":28423.0,"Position":440.474121,"HyperDash":false},{"StartTime":28473.0,"Position":415.7988,"HyperDash":false},{"StartTime":28523.0,"Position":416.1235,"HyperDash":false},{"StartTime":28574.0,"Position":389.0747,"HyperDash":false},{"StartTime":28656.0,"Position":375.447235,"HyperDash":false},{"StartTime":28775.0,"Position":314.0,"HyperDash":false}]},{"StartTime":28977.0,"Objects":[{"StartTime":28977.0,"Position":185.0,"HyperDash":false},{"StartTime":29068.0,"Position":200.514755,"HyperDash":false},{"StartTime":29159.0,"Position":255.02951,"HyperDash":false},{"StartTime":29250.0,"Position":301.5764,"HyperDash":false},{"StartTime":29378.0,"Position":328.3668,"HyperDash":false}]},{"StartTime":29579.0,"Objects":[{"StartTime":29579.0,"Position":256.0,"HyperDash":false},{"StartTime":29679.0,"Position":256.0,"HyperDash":false},{"StartTime":29779.0,"Position":256.0,"HyperDash":false}]},{"StartTime":30182.0,"Objects":[{"StartTime":30182.0,"Position":467.0,"HyperDash":false}]},{"StartTime":30383.0,"Objects":[{"StartTime":30383.0,"Position":395.0,"HyperDash":false}]},{"StartTime":30584.0,"Objects":[{"StartTime":30584.0,"Position":323.0,"HyperDash":false}]},{"StartTime":30785.0,"Objects":[{"StartTime":30785.0,"Position":251.0,"HyperDash":false}]},{"StartTime":30986.0,"Objects":[{"StartTime":30986.0,"Position":179.0,"HyperDash":false}]},{"StartTime":31187.0,"Objects":[{"StartTime":31187.0,"Position":107.0,"HyperDash":false}]},{"StartTime":31388.0,"Objects":[{"StartTime":31388.0,"Position":35.0,"HyperDash":false},{"StartTime":31479.0,"Position":45.0,"HyperDash":false},{"StartTime":31570.0,"Position":45.0,"HyperDash":false},{"StartTime":31661.0,"Position":39.0,"HyperDash":false},{"StartTime":31789.0,"Position":35.0,"HyperDash":false}]},{"StartTime":31991.0,"Objects":[{"StartTime":31991.0,"Position":105.0,"HyperDash":false},{"StartTime":32091.0,"Position":142.5,"HyperDash":false},{"StartTime":32191.0,"Position":105.0,"HyperDash":false}]},{"StartTime":32593.0,"Objects":[{"StartTime":32593.0,"Position":314.0,"HyperDash":false}]},{"StartTime":32794.0,"Objects":[{"StartTime":32794.0,"Position":434.0,"HyperDash":false}]},{"StartTime":32995.0,"Objects":[{"StartTime":32995.0,"Position":314.0,"HyperDash":false}]},{"StartTime":33196.0,"Objects":[{"StartTime":33196.0,"Position":434.0,"HyperDash":false}]},{"StartTime":33397.0,"Objects":[{"StartTime":33397.0,"Position":314.0,"HyperDash":false}]},{"StartTime":33598.0,"Objects":[{"StartTime":33598.0,"Position":434.0,"HyperDash":false}]},{"StartTime":33799.0,"Objects":[{"StartTime":33799.0,"Position":314.0,"HyperDash":false},{"StartTime":33881.0,"Position":336.929565,"HyperDash":false},{"StartTime":33999.0,"Position":352.8526,"HyperDash":false}]},{"StartTime":34201.0,"Objects":[{"StartTime":34201.0,"Position":117.0,"HyperDash":false},{"StartTime":34283.0,"Position":163.741074,"HyperDash":false},{"StartTime":34401.0,"Position":191.978241,"HyperDash":false}]},{"StartTime":34603.0,"Objects":[{"StartTime":34603.0,"Position":56.0,"HyperDash":false},{"StartTime":34685.0,"Position":55.48987,"HyperDash":false},{"StartTime":34803.0,"Position":91.34114,"HyperDash":false}]},{"StartTime":35005.0,"Objects":[{"StartTime":35005.0,"Position":192.0,"HyperDash":false},{"StartTime":35087.0,"Position":172.904892,"HyperDash":false},{"StartTime":35205.0,"Position":152.743652,"HyperDash":false}]},{"StartTime":35407.0,"Objects":[{"StartTime":35407.0,"Position":389.0,"HyperDash":false},{"StartTime":35489.0,"Position":348.2696,"HyperDash":false},{"StartTime":35607.0,"Position":314.0478,"HyperDash":false}]},{"StartTime":35808.0,"Objects":[{"StartTime":35808.0,"Position":450.0,"HyperDash":false},{"StartTime":35890.0,"Position":440.377838,"HyperDash":false},{"StartTime":36008.0,"Position":414.3362,"HyperDash":false}]},{"StartTime":36210.0,"Objects":[{"StartTime":36210.0,"Position":314.0,"HyperDash":false}]},{"StartTime":36612.0,"Objects":[{"StartTime":36612.0,"Position":123.0,"HyperDash":false}]},{"StartTime":36813.0,"Objects":[{"StartTime":36813.0,"Position":230.0,"HyperDash":false}]},{"StartTime":37014.0,"Objects":[{"StartTime":37014.0,"Position":337.0,"HyperDash":false}]},{"StartTime":37215.0,"Objects":[{"StartTime":37215.0,"Position":230.0,"HyperDash":false}]},{"StartTime":37416.0,"Objects":[{"StartTime":37416.0,"Position":232.0,"HyperDash":false}]},{"StartTime":37516.0,"Objects":[{"StartTime":37516.0,"Position":232.0,"HyperDash":false}]},{"StartTime":37617.0,"Objects":[{"StartTime":37617.0,"Position":232.0,"HyperDash":false}]}]} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/1597806.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/1597806.osu new file mode 100644 index 0000000000..b9ce7a927d --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/1597806.osu @@ -0,0 +1,152 @@ +osu file format v14 + +[General] +StackLeniency: 0.4 +Mode: 0 + +[Difficulty] +HPDrainRate:6 +CircleSize:7 +OverallDifficulty:8 +ApproachRate:8 +SliderMultiplier:1.5 +SliderTickRate:1 + +[Events] +//Background and Video events +//Break Periods +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Sound Samples + +[TimingPoints] +42,401.875418620228,3,1,0,50,1,0 +18528,-100,3,1,0,40,0,0 +18930,-100,3,1,0,30,0,0 +19332,-100,3,1,0,50,0,1 +24154,-100,3,1,0,50,0,1 +27771,-100,3,1,0,50,0,0 +28977,-100,3,1,0,50,0,0 +30182,-100,3,1,0,50,0,0 +31388,-100,3,1,0,50,0,0 +32593,-100,3,1,0,50,0,0 +33799,-100,3,1,0,50,0,0 + +[HitObjects] +288,165,42,6,0,P|255:181|209:175,1,75,4|0,0:0|0:0,0:0:0:0: +125,38,443,2,0,P|155:58|173:101,1,75,8|0,0:0|0:0,0:0:0:0: +95,236,845,2,0,P|97:199|125:162,1,75,8|0,0:0|0:0,0:0:0:0: +250,271,1247,6,0,L|164:295,1,75,8|0,0:0|0:0,0:0:0:0: +277,199,1649,2,0,L|363:175,1,75,8|0,0:0|0:0,0:0:0:0: +448,85,2051,2,0,L|376:106,1,75,8|0,0:0|0:0,0:0:0:0: +499,211,2453,6,0,P|502:258|491:298,1,75,4|0,0:0|0:0,0:0:0:0: +397,374,2855,2,0,P|400:336|394:300,1,75,8|0,0:0|0:0,0:0:0:0: +295,211,3257,2,0,P|298:248|292:284,1,75,8|0,0:0|0:0,0:0:0:0: +134,307,3658,6,0,L|227:315,1,75,8|0,0:0|0:0,0:0:0:0: +95,143,4060,2,0,L|134:228,1,75,8|0,0:0|0:0,0:0:0:0: +217,28,4462,2,0,L|163:105,1,75,8|0,0:0|0:0,0:0:0:0: +268,199,4864,6,0,P|261:152|270:113,1,75,4|0,0:0|0:0,0:0:0:0: +418,214,5266,2,0,P|380:243|342:255,1,75,8|0,0:0|0:0,0:0:0:0: +356,76,5668,2,0,P|400:93|429:120,1,75,4|0,0:0|0:0,0:0:0:0: +265,125,6070,6,0,L|184:140,1,75,8|0,0:0|0:0,0:0:0:0: +35,204,6472,2,0,L|116:219,1,75,8|0,0:0|0:0,0:0:0:0: +265,283,6873,2,0,L|184:298,1,75,4|0,0:0|0:0,0:0:0:0: +323,195,7275,6,0,P|366:203|403:237,1,75,8|0,0:0|0:0,0:0:0:0: +493,117,7677,2,0,P|450:125|413:159,1,75,8|0,0:0|0:0,0:0:0:0: +323,39,8079,2,0,P|366:47|403:81,1,75,8|0,0:0|0:0,0:0:0:0: +273,140,8481,5,4,0:0:0:0: +187,31,8682,1,0,0:0:0:0: +101,140,8883,1,8,0:0:0:0: +187,249,9084,1,0,0:0:0:0: +101,358,9285,1,8,0:0:0:0: +15,249,9486,1,0,0:0:0:0: +187,249,9687,6,0,L|112:266,1,75,4|0,0:0|0:0,0:0:0:0: +264,181,10088,2,0,L|286:107,1,75,8|0,0:0|0:0,0:0:0:0: +287,283,10490,2,0,L|339:339,1,75,8|0,0:0|0:0,0:0:0:0: +422,222,10892,6,0,P|425:180|411:133,1,75,8|0,0:0|0:0,0:0:0:0: +287,283,11294,2,0,P|324:264|358:228,1,75,8|0,0:0|0:0,0:0:0:0: +166,196,11696,2,0,P|200:219|248:230,1,75,8|0,0:0|0:0,0:0:0:0: +332,83,12098,6,0,L|236:102,1,75,4|0,0:0|0:0,0:0:0:0: +394,139,12500,2,0,L|490:158,1,75,8|0,0:0|0:0,0:0:0:0: +332,195,12902,2,0,L|236:214,1,75,8|0,0:0|0:0,0:0:0:0: +413,321,13303,6,0,P|419:253|399:213,1,75,8|0,0:0|0:0,0:0:0:0: +327,121,13705,2,0,P|333:189|313:229,1,75,8|0,0:0|0:0,0:0:0:0: +241,321,14107,2,0,P|247:253|227:213,1,75,8|0,0:0|0:0,0:0:0:0: +118,175,14509,6,0,L|212:188,1,75,4|0,0:0|0:0,0:0:0:0: +297,100,14911,2,0,L|238:174,1,75,8|0,0:0|0:0,0:0:0:0: +273,292,15313,2,0,L|237:204,1,75,4|0,0:0|0:0,0:0:0:0: +235,357,15715,6,0,P|272:368|321:351,1,75,8|0,0:0|0:0,0:0:0:0: +441,286,16117,2,0,P|404:297|355:280,1,75,8|0,0:0|0:0,0:0:0:0: +235,215,16518,2,0,P|272:226|321:209,1,75,4|0,0:0|0:0,0:0:0:0: +436,127,16920,6,0,L|447:217,1,75,8|0,0:0|0:0,0:0:0:0: +345,22,17322,2,0,L|428:57,1,75,8|0,0:0|0:0,0:0:0:0: +208,48,17724,2,0,L|280:-6,1,75,4|0,0:0|0:0,0:0:0:0: +187,162,18126,5,4,0:0:0:0: +187,162,18528,1,8,0:0:0:0: +187,162,18930,1,8,0:0:0:0: +187,162,19332,5,4,0:0:0:0: +187,263,19532,1,0,0:0:0:0: +345,107,19733,1,8,0:0:0:0: +257,157,19933,1,0,0:0:0:0: +471,216,20135,1,8,0:0:0:0: +384,165,20335,1,0,0:0:0:0: +284,300,20537,5,4,0:0:0:0: +371,249,20737,1,0,0:0:0:0: +157,190,20938,1,8,0:0:0:0: +244,241,21140,1,0,0:0:0:0: +188,27,21340,1,4,0:0:0:0: +188,127,21542,1,0,0:0:0:0: +345,40,21743,5,4,0:0:0:0: +250,77,21944,1,0,0:0:0:0: +419,147,22145,2,0,L|328:147,1,75,4|0,0:0|0:0,0:0:0:0: +196,219,22547,2,0,L|287:219,1,75,8|0,0:0|0:0,0:0:0:0: +419,291,22948,5,4,0:0:0:0: +344,224,23149,1,0,0:0:0:0: +305,352,23350,2,0,P|310:313|305:269,1,75,4|0,0:0|0:0,0:0:0:0: +240,122,23752,2,0,P|235:161|240:205,1,75,8|0,0:0|0:0,0:0:0:0: +429,207,24154,6,0,L|342:213,1,75,8|0,0:0|0:0,0:0:0:0: +232,272,24556,2,0,L|319:278,1,75,8|0,0:0|0:0,0:0:0:0: +429,337,24958,2,0,L|342:343,1,75,8|0,0:0|0:0,0:0:0:0: +501,280,25360,5,4,0:0:0:0: +429,207,25561,1,0,0:0:0:0: +491,62,25762,2,0,L|490:145,1,75,4|0,0:0|0:0,0:0:0:0: +372,236,26163,2,0,L|373:153,1,75,8|0,0:0|0:0,0:0:0:0: +372,7,26565,5,4,0:0:0:0: +431,121,26766,1,0,0:0:0:0: +372,236,26967,1,8,0:0:0:0: +314,121,27168,1,0,0:0:0:0: +254,236,27369,1,8,0:0:0:0: +313,351,27570,1,0,0:0:0:0: +372,236,27771,6,0,B|473:236|473:236|473:121|473:121|306:121,1,375,4|0,0:0|0:0,0:0:0:0: +185,192,28977,6,0,B|256:214|256:214|328:192,1,150,4|0,0:0|0:0,0:0:0:0: +256,94,29579,6,0,L|256:43,2,37.5,4|0|8,0:0|0:0|0:0,0:0:0:0: +467,188,30182,5,4,0:0:0:0: +395,307,30383,1,0,0:0:0:0: +323,188,30584,1,8,0:0:0:0: +251,307,30785,1,0,0:0:0:0: +179,188,30986,1,8,0:0:0:0: +107,307,31187,1,0,0:0:0:0: +35,188,31388,6,0,L|35:39,1,150,4|0,0:0|0:0,0:0:0:0: +105,116,31991,2,0,L|154:116,2,37.5,4|0|8,0:0|0:0|0:0,0:0:0:0: +314,4,32593,5,4,0:0:0:0: +434,66,32794,1,0,0:0:0:0: +314,128,32995,1,8,0:0:0:0: +434,190,33196,1,0,0:0:0:0: +314,252,33397,1,8,0:0:0:0: +434,314,33598,1,0,0:0:0:0: +314,384,33799,6,0,L|357:313,1,75,4|0,0:0|0:0,0:0:0:0: +117,340,34201,2,0,L|200:342,1,75,8|0,0:0|0:0,0:0:0:0: +56,148,34603,2,0,L|95:221,1,75,8|0,0:0|0:0,0:0:0:0: +192,0,35005,2,0,L|149:70,1,75,4|0,0:0|0:0,0:0:0:0: +389,42,35407,2,0,L|305:39,1,75,8|0,0:0|0:0,0:0:0:0: +450,234,35808,2,0,L|410:160,1,75,8|0,0:0|0:0,0:0:0:0: +314,384,36210,1,4,0:0:0:0: +123,192,36612,5,4,0:0:0:0: +230,327,36813,1,0,0:0:0:0: +337,192,37014,1,8,0:0:0:0: +230,57,37215,1,0,0:0:0:0: +232,193,37416,5,4,0:0:0:0: +232,193,37516,1,0,0:0:0:0: +232,193,37617,1,8,0:0:0:0: diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/2190499-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/2190499-expected-conversion.json new file mode 100644 index 0000000000..fb919302d9 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/2190499-expected-conversion.json @@ -0,0 +1 @@ +{"Mappings":[{"StartTime":1739.0,"Objects":[{"StartTime":1739.0,"Position":367.0,"HyperDash":false},{"StartTime":1817.0,"Position":334.444244,"HyperDash":false},{"StartTime":1896.0,"Position":321.501648,"HyperDash":false},{"StartTime":1975.0,"Position":314.851929,"HyperDash":false},{"StartTime":2054.0,"Position":307.22052,"HyperDash":false},{"StartTime":2124.0,"Position":315.042236,"HyperDash":false},{"StartTime":2194.0,"Position":286.526184,"HyperDash":false},{"StartTime":2264.0,"Position":269.750366,"HyperDash":false},{"StartTime":2370.0,"Position":246.8734,"HyperDash":false}]},{"StartTime":3002.0,"Objects":[{"StartTime":3002.0,"Position":161.0,"HyperDash":false},{"StartTime":3080.0,"Position":160.934677,"HyperDash":false},{"StartTime":3159.0,"Position":181.699768,"HyperDash":false},{"StartTime":3238.0,"Position":189.906845,"HyperDash":false},{"StartTime":3317.0,"Position":212.345535,"HyperDash":false},{"StartTime":3387.0,"Position":233.989639,"HyperDash":false},{"StartTime":3457.0,"Position":230.043228,"HyperDash":false},{"StartTime":3527.0,"Position":221.436966,"HyperDash":false},{"StartTime":3633.0,"Position":233.8208,"HyperDash":false}]},{"StartTime":4265.0,"Objects":[{"StartTime":4265.0,"Position":47.0,"HyperDash":false},{"StartTime":4334.0,"Position":63.4803925,"HyperDash":false},{"StartTime":4404.0,"Position":52.9349747,"HyperDash":false},{"StartTime":4474.0,"Position":77.8079,"HyperDash":false},{"StartTime":4580.0,"Position":111.004265,"HyperDash":false}]},{"StartTime":4897.0,"Objects":[{"StartTime":4897.0,"Position":235.0,"HyperDash":false},{"StartTime":4975.0,"Position":269.900024,"HyperDash":false},{"StartTime":5054.0,"Position":287.131927,"HyperDash":false},{"StartTime":5133.0,"Position":301.489746,"HyperDash":false},{"StartTime":5212.0,"Position":305.972,"HyperDash":false},{"StartTime":5273.0,"Position":301.195129,"HyperDash":false},{"StartTime":5370.0,"Position":334.383362,"HyperDash":false}]},{"StartTime":5528.0,"Objects":[{"StartTime":5528.0,"Position":372.0,"HyperDash":false},{"StartTime":5606.0,"Position":378.156158,"HyperDash":false},{"StartTime":5685.0,"Position":354.0074,"HyperDash":false},{"StartTime":5764.0,"Position":357.6568,"HyperDash":false},{"StartTime":5843.0,"Position":349.1154,"HyperDash":false},{"StartTime":5913.0,"Position":341.790833,"HyperDash":false},{"StartTime":5983.0,"Position":342.120453,"HyperDash":false},{"StartTime":6053.0,"Position":355.1087,"HyperDash":false},{"StartTime":6159.0,"Position":339.322418,"HyperDash":false}]},{"StartTime":6791.0,"Objects":[{"StartTime":6791.0,"Position":55.0,"HyperDash":false},{"StartTime":6860.0,"Position":49.3883972,"HyperDash":false},{"StartTime":6930.0,"Position":57.4187775,"HyperDash":false},{"StartTime":7000.0,"Position":74.47069,"HyperDash":false},{"StartTime":7106.0,"Position":114.237465,"HyperDash":false}]},{"StartTime":7423.0,"Objects":[{"StartTime":7423.0,"Position":240.0,"HyperDash":false},{"StartTime":7501.0,"Position":237.8742,"HyperDash":false},{"StartTime":7580.0,"Position":228.717819,"HyperDash":false},{"StartTime":7659.0,"Position":200.180328,"HyperDash":false},{"StartTime":7738.0,"Position":193.621948,"HyperDash":false},{"StartTime":7799.0,"Position":189.067337,"HyperDash":false},{"StartTime":7896.0,"Position":188.528091,"HyperDash":false}]},{"StartTime":8054.0,"Objects":[{"StartTime":8054.0,"Position":273.0,"HyperDash":false},{"StartTime":8132.0,"Position":306.8755,"HyperDash":false},{"StartTime":8211.0,"Position":298.36087,"HyperDash":false},{"StartTime":8290.0,"Position":326.902954,"HyperDash":false},{"StartTime":8369.0,"Position":340.2283,"HyperDash":false},{"StartTime":8439.0,"Position":337.1718,"HyperDash":false},{"StartTime":8509.0,"Position":325.47818,"HyperDash":false},{"StartTime":8579.0,"Position":310.550323,"HyperDash":false},{"StartTime":8685.0,"Position":273.0,"HyperDash":false}]},{"StartTime":9002.0,"Objects":[{"StartTime":9002.0,"Position":147.0,"HyperDash":false},{"StartTime":9062.0,"Position":147.914444,"HyperDash":false},{"StartTime":9159.0,"Position":110.615738,"HyperDash":false}]},{"StartTime":9318.0,"Objects":[{"StartTime":9318.0,"Position":59.0,"HyperDash":false},{"StartTime":9396.0,"Position":72.10824,"HyperDash":false},{"StartTime":9475.0,"Position":59.51161,"HyperDash":false},{"StartTime":9554.0,"Position":28.2636833,"HyperDash":false},{"StartTime":9633.0,"Position":39.3327179,"HyperDash":false},{"StartTime":9703.0,"Position":49.56317,"HyperDash":false},{"StartTime":9773.0,"Position":37.3222733,"HyperDash":false},{"StartTime":9843.0,"Position":38.56619,"HyperDash":false},{"StartTime":9949.0,"Position":59.0,"HyperDash":false}]},{"StartTime":10265.0,"Objects":[{"StartTime":10265.0,"Position":133.0,"HyperDash":false}]},{"StartTime":10581.0,"Objects":[{"StartTime":10581.0,"Position":494.0,"HyperDash":false},{"StartTime":10659.0,"Position":135.0,"HyperDash":false},{"StartTime":10738.0,"Position":30.0,"HyperDash":false},{"StartTime":10817.0,"Position":11.0,"HyperDash":false},{"StartTime":10896.0,"Position":239.0,"HyperDash":false},{"StartTime":10975.0,"Position":505.0,"HyperDash":false},{"StartTime":11054.0,"Position":353.0,"HyperDash":false},{"StartTime":11133.0,"Position":136.0,"HyperDash":false},{"StartTime":11212.0,"Position":135.0,"HyperDash":false},{"StartTime":11291.0,"Position":346.0,"HyperDash":false},{"StartTime":11370.0,"Position":39.0,"HyperDash":false},{"StartTime":11449.0,"Position":300.0,"HyperDash":false},{"StartTime":11528.0,"Position":398.0,"HyperDash":false},{"StartTime":11607.0,"Position":151.0,"HyperDash":false},{"StartTime":11686.0,"Position":73.0,"HyperDash":false},{"StartTime":11765.0,"Position":311.0,"HyperDash":false},{"StartTime":11844.0,"Position":90.0,"HyperDash":false}]},{"StartTime":13107.0,"Objects":[{"StartTime":13107.0,"Position":264.0,"HyperDash":false},{"StartTime":13185.0,"Position":477.0,"HyperDash":false},{"StartTime":13264.0,"Position":473.0,"HyperDash":false},{"StartTime":13343.0,"Position":120.0,"HyperDash":false},{"StartTime":13422.0,"Position":115.0,"HyperDash":false},{"StartTime":13501.0,"Position":163.0,"HyperDash":false},{"StartTime":13580.0,"Position":447.0,"HyperDash":false},{"StartTime":13659.0,"Position":72.0,"HyperDash":false},{"StartTime":13738.0,"Position":257.0,"HyperDash":false},{"StartTime":13817.0,"Position":153.0,"HyperDash":false},{"StartTime":13896.0,"Position":388.0,"HyperDash":false},{"StartTime":13975.0,"Position":336.0,"HyperDash":false},{"StartTime":14054.0,"Position":13.0,"HyperDash":false},{"StartTime":14133.0,"Position":429.0,"HyperDash":false},{"StartTime":14212.0,"Position":381.0,"HyperDash":false},{"StartTime":14291.0,"Position":186.0,"HyperDash":false},{"StartTime":14370.0,"Position":267.0,"HyperDash":false}]},{"StartTime":15633.0,"Objects":[{"StartTime":15633.0,"Position":74.0,"HyperDash":false},{"StartTime":15711.0,"Position":95.2118149,"HyperDash":false},{"StartTime":15790.0,"Position":106.218536,"HyperDash":false},{"StartTime":15869.0,"Position":134.014709,"HyperDash":false},{"StartTime":15948.0,"Position":138.984512,"HyperDash":false},{"StartTime":16008.0,"Position":164.493423,"HyperDash":false},{"StartTime":16068.0,"Position":149.9466,"HyperDash":false},{"StartTime":16128.0,"Position":159.583786,"HyperDash":false},{"StartTime":16225.0,"Position":198.210388,"HyperDash":false}]},{"StartTime":17844.0,"Objects":[{"StartTime":17844.0,"Position":189.0,"HyperDash":false}]},{"StartTime":18160.0,"Objects":[{"StartTime":18160.0,"Position":189.0,"HyperDash":false},{"StartTime":18220.0,"Position":203.848145,"HyperDash":false},{"StartTime":18317.0,"Position":255.355225,"HyperDash":false}]},{"StartTime":18476.0,"Objects":[{"StartTime":18476.0,"Position":402.0,"HyperDash":false},{"StartTime":18536.0,"Position":355.556671,"HyperDash":false},{"StartTime":18633.0,"Position":335.176544,"HyperDash":false}]},{"StartTime":18791.0,"Objects":[{"StartTime":18791.0,"Position":383.0,"HyperDash":false},{"StartTime":18860.0,"Position":387.0126,"HyperDash":false},{"StartTime":18930.0,"Position":402.196167,"HyperDash":false},{"StartTime":19000.0,"Position":402.402679,"HyperDash":false},{"StartTime":19106.0,"Position":403.979065,"HyperDash":false}]},{"StartTime":19265.0,"Objects":[{"StartTime":19265.0,"Position":254.0,"HyperDash":false}]},{"StartTime":19423.0,"Objects":[{"StartTime":19423.0,"Position":178.0,"HyperDash":false},{"StartTime":19483.0,"Position":149.544052,"HyperDash":false},{"StartTime":19580.0,"Position":105.085159,"HyperDash":false}]},{"StartTime":19739.0,"Objects":[{"StartTime":19739.0,"Position":245.0,"HyperDash":false},{"StartTime":19799.0,"Position":290.012634,"HyperDash":false},{"StartTime":19896.0,"Position":317.941528,"HyperDash":false}]},{"StartTime":20054.0,"Objects":[{"StartTime":20054.0,"Position":287.0,"HyperDash":false},{"StartTime":20123.0,"Position":275.9874,"HyperDash":false},{"StartTime":20193.0,"Position":286.803833,"HyperDash":false},{"StartTime":20263.0,"Position":255.597321,"HyperDash":false},{"StartTime":20369.0,"Position":266.020935,"HyperDash":false}]},{"StartTime":20528.0,"Objects":[{"StartTime":20528.0,"Position":167.0,"HyperDash":false}]},{"StartTime":20686.0,"Objects":[{"StartTime":20686.0,"Position":110.0,"HyperDash":false},{"StartTime":20746.0,"Position":87.56889,"HyperDash":false},{"StartTime":20843.0,"Position":44.4256668,"HyperDash":false}]},{"StartTime":21002.0,"Objects":[{"StartTime":21002.0,"Position":158.0,"HyperDash":false},{"StartTime":21062.0,"Position":174.0545,"HyperDash":false},{"StartTime":21159.0,"Position":223.260239,"HyperDash":false}]},{"StartTime":21318.0,"Objects":[{"StartTime":21318.0,"Position":105.0,"HyperDash":false},{"StartTime":21378.0,"Position":86.56889,"HyperDash":false},{"StartTime":21475.0,"Position":39.4256668,"HyperDash":false}]},{"StartTime":21634.0,"Objects":[{"StartTime":21634.0,"Position":153.0,"HyperDash":false},{"StartTime":21694.0,"Position":191.0545,"HyperDash":false},{"StartTime":21791.0,"Position":218.260239,"HyperDash":false}]},{"StartTime":21949.0,"Objects":[{"StartTime":21949.0,"Position":321.0,"HyperDash":false}]},{"StartTime":22107.0,"Objects":[{"StartTime":22107.0,"Position":372.0,"HyperDash":false}]},{"StartTime":22265.0,"Objects":[{"StartTime":22265.0,"Position":345.0,"HyperDash":false},{"StartTime":22325.0,"Position":332.141785,"HyperDash":false},{"StartTime":22422.0,"Position":327.377563,"HyperDash":false}]},{"StartTime":22581.0,"Objects":[{"StartTime":22581.0,"Position":413.0,"HyperDash":false}]},{"StartTime":22739.0,"Objects":[{"StartTime":22739.0,"Position":442.0,"HyperDash":false}]},{"StartTime":22897.0,"Objects":[{"StartTime":22897.0,"Position":409.0,"HyperDash":false},{"StartTime":22957.0,"Position":364.564,"HyperDash":false},{"StartTime":23054.0,"Position":338.834,"HyperDash":false}]},{"StartTime":23212.0,"Objects":[{"StartTime":23212.0,"Position":205.0,"HyperDash":false},{"StartTime":23272.0,"Position":218.780579,"HyperDash":false},{"StartTime":23369.0,"Position":224.420334,"HyperDash":false}]},{"StartTime":23528.0,"Objects":[{"StartTime":23528.0,"Position":73.0,"HyperDash":false},{"StartTime":23588.0,"Position":68.21941,"HyperDash":false},{"StartTime":23685.0,"Position":53.5796623,"HyperDash":false}]},{"StartTime":23844.0,"Objects":[{"StartTime":23844.0,"Position":240.0,"HyperDash":false},{"StartTime":23904.0,"Position":234.999878,"HyperDash":false},{"StartTime":24001.0,"Position":220.82193,"HyperDash":false}]},{"StartTime":24160.0,"Objects":[{"StartTime":24160.0,"Position":88.0,"HyperDash":false},{"StartTime":24220.0,"Position":60.3964767,"HyperDash":false},{"StartTime":24317.0,"Position":68.93455,"HyperDash":false}]},{"StartTime":24476.0,"Objects":[{"StartTime":24476.0,"Position":206.0,"HyperDash":false},{"StartTime":24536.0,"Position":215.970291,"HyperDash":false},{"StartTime":24633.0,"Position":281.8056,"HyperDash":false}]},{"StartTime":24791.0,"Objects":[{"StartTime":24791.0,"Position":425.0,"HyperDash":false},{"StartTime":24851.0,"Position":385.029724,"HyperDash":false},{"StartTime":24948.0,"Position":349.1944,"HyperDash":false}]},{"StartTime":25107.0,"Objects":[{"StartTime":25107.0,"Position":196.0,"HyperDash":false},{"StartTime":25167.0,"Position":233.970291,"HyperDash":false},{"StartTime":25264.0,"Position":271.8056,"HyperDash":false}]},{"StartTime":25423.0,"Objects":[{"StartTime":25423.0,"Position":415.0,"HyperDash":false}]},{"StartTime":25581.0,"Objects":[{"StartTime":25581.0,"Position":363.0,"HyperDash":false}]},{"StartTime":25739.0,"Objects":[{"StartTime":25739.0,"Position":263.0,"HyperDash":false},{"StartTime":25799.0,"Position":263.286316,"HyperDash":false},{"StartTime":25896.0,"Position":278.260681,"HyperDash":false}]},{"StartTime":26054.0,"Objects":[{"StartTime":26054.0,"Position":418.0,"HyperDash":false},{"StartTime":26114.0,"Position":438.267456,"HyperDash":false},{"StartTime":26211.0,"Position":432.8865,"HyperDash":false}]},{"StartTime":26370.0,"Objects":[{"StartTime":26370.0,"Position":251.0,"HyperDash":false},{"StartTime":26430.0,"Position":272.286316,"HyperDash":false},{"StartTime":26527.0,"Position":266.260681,"HyperDash":false}]},{"StartTime":26686.0,"Objects":[{"StartTime":26686.0,"Position":406.0,"HyperDash":false},{"StartTime":26746.0,"Position":403.267456,"HyperDash":false},{"StartTime":26843.0,"Position":420.8865,"HyperDash":false}]},{"StartTime":27002.0,"Objects":[{"StartTime":27002.0,"Position":326.0,"HyperDash":false},{"StartTime":27102.0,"Position":263.416656,"HyperDash":false},{"StartTime":27238.0,"Position":217.484329,"HyperDash":false}]},{"StartTime":27318.0,"Objects":[{"StartTime":27318.0,"Position":215.0,"HyperDash":false},{"StartTime":27418.0,"Position":244.682343,"HyperDash":false},{"StartTime":27554.0,"Position":323.347046,"HyperDash":false}]},{"StartTime":27633.0,"Objects":[{"StartTime":27633.0,"Position":324.0,"HyperDash":false},{"StartTime":27702.0,"Position":307.889435,"HyperDash":false},{"StartTime":27772.0,"Position":261.760223,"HyperDash":false},{"StartTime":27842.0,"Position":231.054062,"HyperDash":false},{"StartTime":27948.0,"Position":179.503586,"HyperDash":false}]},{"StartTime":28265.0,"Objects":[{"StartTime":28265.0,"Position":65.0,"HyperDash":false},{"StartTime":28334.0,"Position":61.494606,"HyperDash":false},{"StartTime":28404.0,"Position":60.7236862,"HyperDash":false},{"StartTime":28474.0,"Position":89.86115,"HyperDash":false},{"StartTime":28580.0,"Position":97.86348,"HyperDash":false}]},{"StartTime":28739.0,"Objects":[{"StartTime":28739.0,"Position":153.0,"HyperDash":false}]},{"StartTime":28897.0,"Objects":[{"StartTime":28897.0,"Position":153.0,"HyperDash":false}]},{"StartTime":29054.0,"Objects":[{"StartTime":29054.0,"Position":215.0,"HyperDash":false},{"StartTime":29114.0,"Position":247.582169,"HyperDash":false},{"StartTime":29211.0,"Position":279.019775,"HyperDash":false}]},{"StartTime":29370.0,"Objects":[{"StartTime":29370.0,"Position":332.0,"HyperDash":false},{"StartTime":29430.0,"Position":288.006042,"HyperDash":false},{"StartTime":29527.0,"Position":267.917969,"HyperDash":false}]},{"StartTime":29686.0,"Objects":[{"StartTime":29686.0,"Position":371.0,"HyperDash":false}]},{"StartTime":29844.0,"Objects":[{"StartTime":29844.0,"Position":371.0,"HyperDash":false}]},{"StartTime":30002.0,"Objects":[{"StartTime":30002.0,"Position":444.0,"HyperDash":false}]},{"StartTime":30160.0,"Objects":[{"StartTime":30160.0,"Position":444.0,"HyperDash":false},{"StartTime":30220.0,"Position":451.1502,"HyperDash":false},{"StartTime":30317.0,"Position":463.328674,"HyperDash":false}]},{"StartTime":30476.0,"Objects":[{"StartTime":30476.0,"Position":393.0,"HyperDash":false},{"StartTime":30536.0,"Position":364.8498,"HyperDash":false},{"StartTime":30633.0,"Position":373.671326,"HyperDash":false}]},{"StartTime":30791.0,"Objects":[{"StartTime":30791.0,"Position":265.0,"HyperDash":false},{"StartTime":30851.0,"Position":250.101547,"HyperDash":false},{"StartTime":30948.0,"Position":197.232391,"HyperDash":false}]},{"StartTime":31107.0,"Objects":[{"StartTime":31107.0,"Position":80.0,"HyperDash":false},{"StartTime":31167.0,"Position":86.86766,"HyperDash":false},{"StartTime":31264.0,"Position":147.687042,"HyperDash":false}]},{"StartTime":31423.0,"Objects":[{"StartTime":31423.0,"Position":124.0,"HyperDash":false},{"StartTime":31483.0,"Position":115.084091,"HyperDash":false},{"StartTime":31580.0,"Position":56.1867,"HyperDash":false}]},{"StartTime":31739.0,"Objects":[{"StartTime":31739.0,"Position":164.0,"HyperDash":false}]},{"StartTime":31897.0,"Objects":[{"StartTime":31897.0,"Position":164.0,"HyperDash":false},{"StartTime":31957.0,"Position":198.867661,"HyperDash":false},{"StartTime":32054.0,"Position":231.687042,"HyperDash":false}]},{"StartTime":32212.0,"Objects":[{"StartTime":32212.0,"Position":365.0,"HyperDash":false}]},{"StartTime":32370.0,"Objects":[{"StartTime":32370.0,"Position":365.0,"HyperDash":false},{"StartTime":32430.0,"Position":375.563446,"HyperDash":false},{"StartTime":32527.0,"Position":383.7374,"HyperDash":false}]},{"StartTime":32686.0,"Objects":[{"StartTime":32686.0,"Position":488.0,"HyperDash":false},{"StartTime":32755.0,"Position":478.588562,"HyperDash":false},{"StartTime":32825.0,"Position":485.259918,"HyperDash":false},{"StartTime":32895.0,"Position":471.216827,"HyperDash":false},{"StartTime":33001.0,"Position":467.5074,"HyperDash":false}]},{"StartTime":33160.0,"Objects":[{"StartTime":33160.0,"Position":406.0,"HyperDash":false}]},{"StartTime":33318.0,"Objects":[{"StartTime":33318.0,"Position":277.0,"HyperDash":false},{"StartTime":33387.0,"Position":249.144089,"HyperDash":false},{"StartTime":33457.0,"Position":211.528214,"HyperDash":false},{"StartTime":33527.0,"Position":207.367355,"HyperDash":false},{"StartTime":33633.0,"Position":161.097717,"HyperDash":false}]},{"StartTime":33791.0,"Objects":[{"StartTime":33791.0,"Position":283.0,"HyperDash":false}]},{"StartTime":33949.0,"Objects":[{"StartTime":33949.0,"Position":283.0,"HyperDash":false}]},{"StartTime":34107.0,"Objects":[{"StartTime":34107.0,"Position":158.0,"HyperDash":false},{"StartTime":34167.0,"Position":122.620682,"HyperDash":false},{"StartTime":34264.0,"Position":93.20762,"HyperDash":false}]},{"StartTime":34423.0,"Objects":[{"StartTime":34423.0,"Position":19.0,"HyperDash":false},{"StartTime":34483.0,"Position":37.9395447,"HyperDash":false},{"StartTime":34580.0,"Position":83.68275,"HyperDash":false}]},{"StartTime":34739.0,"Objects":[{"StartTime":34739.0,"Position":158.0,"HyperDash":false}]},{"StartTime":34897.0,"Objects":[{"StartTime":34897.0,"Position":158.0,"HyperDash":false}]},{"StartTime":35054.0,"Objects":[{"StartTime":35054.0,"Position":204.0,"HyperDash":false}]},{"StartTime":35212.0,"Objects":[{"StartTime":35212.0,"Position":204.0,"HyperDash":false},{"StartTime":35272.0,"Position":191.310013,"HyperDash":false},{"StartTime":35369.0,"Position":216.139023,"HyperDash":false}]},{"StartTime":35528.0,"Objects":[{"StartTime":35528.0,"Position":345.0,"HyperDash":false},{"StartTime":35588.0,"Position":339.8689,"HyperDash":false},{"StartTime":35685.0,"Position":332.011932,"HyperDash":false}]},{"StartTime":35844.0,"Objects":[{"StartTime":35844.0,"Position":461.0,"HyperDash":false},{"StartTime":35922.0,"Position":426.3106,"HyperDash":false},{"StartTime":36001.0,"Position":379.89856,"HyperDash":false},{"StartTime":36080.0,"Position":350.1098,"HyperDash":false},{"StartTime":36159.0,"Position":330.750031,"HyperDash":false},{"StartTime":36229.0,"Position":366.784668,"HyperDash":false},{"StartTime":36299.0,"Position":388.860931,"HyperDash":false},{"StartTime":36369.0,"Position":414.029144,"HyperDash":false},{"StartTime":36475.0,"Position":461.0,"HyperDash":false}]},{"StartTime":36791.0,"Objects":[{"StartTime":36791.0,"Position":248.0,"HyperDash":false}]},{"StartTime":36949.0,"Objects":[{"StartTime":36949.0,"Position":248.0,"HyperDash":false},{"StartTime":37009.0,"Position":242.674042,"HyperDash":false},{"StartTime":37106.0,"Position":261.06012,"HyperDash":false}]},{"StartTime":37265.0,"Objects":[{"StartTime":37265.0,"Position":189.0,"HyperDash":false}]},{"StartTime":37423.0,"Objects":[{"StartTime":37423.0,"Position":130.0,"HyperDash":false},{"StartTime":37483.0,"Position":98.5721054,"HyperDash":false},{"StartTime":37580.0,"Position":66.22712,"HyperDash":false}]},{"StartTime":37739.0,"Objects":[{"StartTime":37739.0,"Position":32.0,"HyperDash":false}]},{"StartTime":37897.0,"Objects":[{"StartTime":37897.0,"Position":79.0,"HyperDash":false}]},{"StartTime":38054.0,"Objects":[{"StartTime":38054.0,"Position":126.0,"HyperDash":false}]},{"StartTime":38212.0,"Objects":[{"StartTime":38212.0,"Position":67.0,"HyperDash":false}]},{"StartTime":38370.0,"Objects":[{"StartTime":38370.0,"Position":189.0,"HyperDash":false},{"StartTime":38439.0,"Position":208.518677,"HyperDash":false},{"StartTime":38509.0,"Position":230.192429,"HyperDash":false},{"StartTime":38579.0,"Position":265.933716,"HyperDash":false},{"StartTime":38685.0,"Position":294.0829,"HyperDash":false}]},{"StartTime":38844.0,"Objects":[{"StartTime":38844.0,"Position":281.0,"HyperDash":false},{"StartTime":38922.0,"Position":240.8632,"HyperDash":false},{"StartTime":39001.0,"Position":224.955368,"HyperDash":false},{"StartTime":39062.0,"Position":248.9081,"HyperDash":false},{"StartTime":39159.0,"Position":281.0,"HyperDash":false}]},{"StartTime":39318.0,"Objects":[{"StartTime":39318.0,"Position":367.0,"HyperDash":false},{"StartTime":39378.0,"Position":404.815552,"HyperDash":false},{"StartTime":39475.0,"Position":423.320465,"HyperDash":false}]},{"StartTime":39633.0,"Objects":[{"StartTime":39633.0,"Position":493.0,"HyperDash":false}]},{"StartTime":39791.0,"Objects":[{"StartTime":39791.0,"Position":493.0,"HyperDash":false},{"StartTime":39851.0,"Position":484.550964,"HyperDash":false},{"StartTime":39948.0,"Position":499.675018,"HyperDash":false}]},{"StartTime":40107.0,"Objects":[{"StartTime":40107.0,"Position":450.0,"HyperDash":false},{"StartTime":40167.0,"Position":441.449036,"HyperDash":false},{"StartTime":40264.0,"Position":443.324982,"HyperDash":false}]},{"StartTime":40423.0,"Objects":[{"StartTime":40423.0,"Position":379.0,"HyperDash":false}]},{"StartTime":40581.0,"Objects":[{"StartTime":40581.0,"Position":379.0,"HyperDash":false}]},{"StartTime":40739.0,"Objects":[{"StartTime":40739.0,"Position":312.0,"HyperDash":false},{"StartTime":40808.0,"Position":302.798431,"HyperDash":false},{"StartTime":40878.0,"Position":242.555145,"HyperDash":false},{"StartTime":40948.0,"Position":257.265869,"HyperDash":false},{"StartTime":41054.0,"Position":204.051636,"HyperDash":false}]},{"StartTime":41212.0,"Objects":[{"StartTime":41212.0,"Position":120.0,"HyperDash":false},{"StartTime":41290.0,"Position":125.193611,"HyperDash":false},{"StartTime":41369.0,"Position":107.002182,"HyperDash":false},{"StartTime":41430.0,"Position":88.91298,"HyperDash":false},{"StartTime":41527.0,"Position":120.0,"HyperDash":false}]},{"StartTime":41686.0,"Objects":[{"StartTime":41686.0,"Position":195.0,"HyperDash":false},{"StartTime":41746.0,"Position":179.895126,"HyperDash":false},{"StartTime":41843.0,"Position":181.9226,"HyperDash":false}]},{"StartTime":42002.0,"Objects":[{"StartTime":42002.0,"Position":81.0,"HyperDash":false}]},{"StartTime":42160.0,"Objects":[{"StartTime":42160.0,"Position":81.0,"HyperDash":false}]},{"StartTime":42318.0,"Objects":[{"StartTime":42318.0,"Position":157.0,"HyperDash":false}]},{"StartTime":42476.0,"Objects":[{"StartTime":42476.0,"Position":157.0,"HyperDash":false},{"StartTime":42536.0,"Position":192.028351,"HyperDash":false},{"StartTime":42633.0,"Position":217.2575,"HyperDash":false}]},{"StartTime":42791.0,"Objects":[{"StartTime":42791.0,"Position":314.0,"HyperDash":false},{"StartTime":42851.0,"Position":349.048828,"HyperDash":false},{"StartTime":42948.0,"Position":374.311127,"HyperDash":false}]},{"StartTime":43107.0,"Objects":[{"StartTime":43107.0,"Position":224.0,"HyperDash":false},{"StartTime":43176.0,"Position":212.567841,"HyperDash":false},{"StartTime":43246.0,"Position":162.7526,"HyperDash":false},{"StartTime":43316.0,"Position":129.937347,"HyperDash":false},{"StartTime":43422.0,"Position":103.331413,"HyperDash":false}]},{"StartTime":43581.0,"Objects":[{"StartTime":43581.0,"Position":18.0,"HyperDash":false},{"StartTime":43659.0,"Position":17.9562778,"HyperDash":false},{"StartTime":43738.0,"Position":25.988636,"HyperDash":false},{"StartTime":43799.0,"Position":35.9199829,"HyperDash":false},{"StartTime":43896.0,"Position":18.0,"HyperDash":false}]},{"StartTime":44054.0,"Objects":[{"StartTime":44054.0,"Position":118.0,"HyperDash":false},{"StartTime":44114.0,"Position":113.573334,"HyperDash":false},{"StartTime":44211.0,"Position":109.033562,"HyperDash":false}]},{"StartTime":44370.0,"Objects":[{"StartTime":44370.0,"Position":32.0,"HyperDash":false}]},{"StartTime":44528.0,"Objects":[{"StartTime":44528.0,"Position":32.0,"HyperDash":false},{"StartTime":44588.0,"Position":34.55097,"HyperDash":false},{"StartTime":44685.0,"Position":38.6750336,"HyperDash":false}]},{"StartTime":44844.0,"Objects":[{"StartTime":44844.0,"Position":131.0,"HyperDash":false}]},{"StartTime":45002.0,"Objects":[{"StartTime":45002.0,"Position":131.0,"HyperDash":false},{"StartTime":45062.0,"Position":121.323151,"HyperDash":false},{"StartTime":45159.0,"Position":123.99559,"HyperDash":false}]},{"StartTime":45318.0,"Objects":[{"StartTime":45318.0,"Position":215.0,"HyperDash":false}]},{"StartTime":45476.0,"Objects":[{"StartTime":45476.0,"Position":215.0,"HyperDash":false},{"StartTime":45536.0,"Position":253.997345,"HyperDash":false},{"StartTime":45633.0,"Position":275.176361,"HyperDash":false}]},{"StartTime":45791.0,"Objects":[{"StartTime":45791.0,"Position":362.0,"HyperDash":false}]},{"StartTime":45949.0,"Objects":[{"StartTime":45949.0,"Position":362.0,"HyperDash":false}]},{"StartTime":46107.0,"Objects":[{"StartTime":46107.0,"Position":350.0,"HyperDash":false},{"StartTime":46167.0,"Position":360.8421,"HyperDash":false},{"StartTime":46264.0,"Position":354.8202,"HyperDash":false}]},{"StartTime":46423.0,"Objects":[{"StartTime":46423.0,"Position":421.0,"HyperDash":false}]},{"StartTime":46581.0,"Objects":[{"StartTime":46581.0,"Position":421.0,"HyperDash":false}]},{"StartTime":46739.0,"Objects":[{"StartTime":46739.0,"Position":343.0,"HyperDash":false},{"StartTime":46799.0,"Position":312.973572,"HyperDash":false},{"StartTime":46896.0,"Position":282.7475,"HyperDash":false}]},{"StartTime":47054.0,"Objects":[{"StartTime":47054.0,"Position":212.0,"HyperDash":false}]},{"StartTime":47212.0,"Objects":[{"StartTime":47212.0,"Position":176.0,"HyperDash":false}]},{"StartTime":47370.0,"Objects":[{"StartTime":47370.0,"Position":104.0,"HyperDash":false}]},{"StartTime":47449.0,"Objects":[{"StartTime":47449.0,"Position":104.0,"HyperDash":false}]},{"StartTime":47528.0,"Objects":[{"StartTime":47528.0,"Position":104.0,"HyperDash":false},{"StartTime":47628.0,"Position":115.182846,"HyperDash":false},{"StartTime":47764.0,"Position":88.40525,"HyperDash":false}]},{"StartTime":47844.0,"Objects":[{"StartTime":47844.0,"Position":73.0,"HyperDash":false},{"StartTime":47944.0,"Position":61.8171539,"HyperDash":false},{"StartTime":48080.0,"Position":88.59475,"HyperDash":false}]},{"StartTime":48160.0,"Objects":[{"StartTime":48160.0,"Position":108.0,"HyperDash":false}]},{"StartTime":48476.0,"Objects":[{"StartTime":48476.0,"Position":108.0,"HyperDash":false},{"StartTime":48536.0,"Position":130.401276,"HyperDash":false},{"StartTime":48633.0,"Position":176.902069,"HyperDash":false}]},{"StartTime":48791.0,"Objects":[{"StartTime":48791.0,"Position":259.0,"HyperDash":false},{"StartTime":48851.0,"Position":213.142151,"HyperDash":false},{"StartTime":48948.0,"Position":190.198868,"HyperDash":false}]},{"StartTime":49107.0,"Objects":[{"StartTime":49107.0,"Position":329.0,"HyperDash":false},{"StartTime":49176.0,"Position":349.21228,"HyperDash":false},{"StartTime":49246.0,"Position":377.302368,"HyperDash":false},{"StartTime":49316.0,"Position":400.9881,"HyperDash":false},{"StartTime":49422.0,"Position":453.358215,"HyperDash":false}]},{"StartTime":49581.0,"Objects":[{"StartTime":49581.0,"Position":328.0,"HyperDash":false}]},{"StartTime":49739.0,"Objects":[{"StartTime":49739.0,"Position":472.0,"HyperDash":false},{"StartTime":49799.0,"Position":465.138245,"HyperDash":false},{"StartTime":49896.0,"Position":454.808044,"HyperDash":false}]},{"StartTime":50054.0,"Objects":[{"StartTime":50054.0,"Position":324.0,"HyperDash":false},{"StartTime":50114.0,"Position":308.1143,"HyperDash":false},{"StartTime":50211.0,"Position":306.059082,"HyperDash":false}]},{"StartTime":50370.0,"Objects":[{"StartTime":50370.0,"Position":190.0,"HyperDash":false},{"StartTime":50439.0,"Position":173.841064,"HyperDash":false},{"StartTime":50509.0,"Position":134.478058,"HyperDash":false},{"StartTime":50579.0,"Position":91.0187149,"HyperDash":false},{"StartTime":50685.0,"Position":84.73538,"HyperDash":false}]},{"StartTime":50844.0,"Objects":[{"StartTime":50844.0,"Position":206.0,"HyperDash":false}]},{"StartTime":51002.0,"Objects":[{"StartTime":51002.0,"Position":313.0,"HyperDash":false},{"StartTime":51062.0,"Position":334.872467,"HyperDash":false},{"StartTime":51159.0,"Position":326.793732,"HyperDash":false}]},{"StartTime":51318.0,"Objects":[{"StartTime":51318.0,"Position":223.0,"HyperDash":false},{"StartTime":51378.0,"Position":206.316574,"HyperDash":false},{"StartTime":51475.0,"Position":208.626953,"HyperDash":false}]},{"StartTime":51633.0,"Objects":[{"StartTime":51633.0,"Position":268.0,"HyperDash":false},{"StartTime":51693.0,"Position":280.674469,"HyperDash":false},{"StartTime":51790.0,"Position":337.344574,"HyperDash":false}]},{"StartTime":51949.0,"Objects":[{"StartTime":51949.0,"Position":382.0,"HyperDash":false},{"StartTime":52009.0,"Position":340.1904,"HyperDash":false},{"StartTime":52106.0,"Position":312.41745,"HyperDash":false}]},{"StartTime":52265.0,"Objects":[{"StartTime":52265.0,"Position":191.0,"HyperDash":false},{"StartTime":52334.0,"Position":178.67337,"HyperDash":false},{"StartTime":52404.0,"Position":200.576569,"HyperDash":false},{"StartTime":52474.0,"Position":208.688736,"HyperDash":false},{"StartTime":52580.0,"Position":218.40831,"HyperDash":false}]},{"StartTime":52739.0,"Objects":[{"StartTime":52739.0,"Position":145.0,"HyperDash":false}]},{"StartTime":52897.0,"Objects":[{"StartTime":52897.0,"Position":75.0,"HyperDash":false},{"StartTime":52957.0,"Position":116.384956,"HyperDash":false},{"StartTime":53054.0,"Position":143.260788,"HyperDash":false}]},{"StartTime":53212.0,"Objects":[{"StartTime":53212.0,"Position":223.0,"HyperDash":false},{"StartTime":53272.0,"Position":252.329483,"HyperDash":false},{"StartTime":53369.0,"Position":291.407745,"HyperDash":false}]},{"StartTime":53528.0,"Objects":[{"StartTime":53528.0,"Position":423.0,"HyperDash":false}]},{"StartTime":53686.0,"Objects":[{"StartTime":53686.0,"Position":383.0,"HyperDash":false},{"StartTime":53746.0,"Position":384.076538,"HyperDash":false},{"StartTime":53843.0,"Position":360.5784,"HyperDash":false}]},{"StartTime":54002.0,"Objects":[{"StartTime":54002.0,"Position":445.0,"HyperDash":false},{"StartTime":54062.0,"Position":446.261871,"HyperDash":false},{"StartTime":54159.0,"Position":421.7946,"HyperDash":false}]},{"StartTime":54318.0,"Objects":[{"StartTime":54318.0,"Position":346.0,"HyperDash":false}]},{"StartTime":54476.0,"Objects":[{"StartTime":54476.0,"Position":268.0,"HyperDash":false},{"StartTime":54536.0,"Position":223.1805,"HyperDash":false},{"StartTime":54633.0,"Position":196.522339,"HyperDash":false}]},{"StartTime":54791.0,"Objects":[{"StartTime":54791.0,"Position":79.0,"HyperDash":false},{"StartTime":54860.0,"Position":75.98414,"HyperDash":false},{"StartTime":54930.0,"Position":115.252335,"HyperDash":false},{"StartTime":55000.0,"Position":116.421585,"HyperDash":false},{"StartTime":55106.0,"Position":110.769684,"HyperDash":false}]},{"StartTime":55265.0,"Objects":[{"StartTime":55265.0,"Position":38.0,"HyperDash":false},{"StartTime":55325.0,"Position":23.5258217,"HyperDash":false},{"StartTime":55422.0,"Position":54.2080231,"HyperDash":false}]},{"StartTime":55581.0,"Objects":[{"StartTime":55581.0,"Position":189.0,"HyperDash":false}]},{"StartTime":55739.0,"Objects":[{"StartTime":55739.0,"Position":125.0,"HyperDash":false},{"StartTime":55799.0,"Position":137.109589,"HyperDash":false},{"StartTime":55896.0,"Position":141.0302,"HyperDash":false}]},{"StartTime":56054.0,"Objects":[{"StartTime":56054.0,"Position":279.0,"HyperDash":false},{"StartTime":56114.0,"Position":308.761017,"HyperDash":false},{"StartTime":56211.0,"Position":351.217346,"HyperDash":false}]},{"StartTime":56370.0,"Objects":[{"StartTime":56370.0,"Position":470.0,"HyperDash":false},{"StartTime":56430.0,"Position":449.3282,"HyperDash":false},{"StartTime":56527.0,"Position":397.632721,"HyperDash":false}]},{"StartTime":56686.0,"Objects":[{"StartTime":56686.0,"Position":438.0,"HyperDash":false},{"StartTime":56746.0,"Position":427.2124,"HyperDash":false},{"StartTime":56843.0,"Position":445.736,"HyperDash":false}]},{"StartTime":57002.0,"Objects":[{"StartTime":57002.0,"Position":287.0,"HyperDash":false},{"StartTime":57062.0,"Position":284.352478,"HyperDash":false},{"StartTime":57159.0,"Position":294.1198,"HyperDash":false}]},{"StartTime":57318.0,"Objects":[{"StartTime":57318.0,"Position":334.0,"HyperDash":false},{"StartTime":57396.0,"Position":298.0179,"HyperDash":false},{"StartTime":57475.0,"Position":334.0,"HyperDash":false},{"StartTime":57554.0,"Position":298.0179,"HyperDash":false}]},{"StartTime":57633.0,"Objects":[{"StartTime":57633.0,"Position":230.0,"HyperDash":false},{"StartTime":57693.0,"Position":208.152359,"HyperDash":false},{"StartTime":57790.0,"Position":164.896362,"HyperDash":false}]},{"StartTime":57949.0,"Objects":[{"StartTime":57949.0,"Position":42.0,"HyperDash":false},{"StartTime":58009.0,"Position":61.24403,"HyperDash":false},{"StartTime":58106.0,"Position":66.01679,"HyperDash":false}]},{"StartTime":58265.0,"Objects":[{"StartTime":58265.0,"Position":188.0,"HyperDash":false},{"StartTime":58325.0,"Position":163.755981,"HyperDash":false},{"StartTime":58422.0,"Position":163.983215,"HyperDash":false}]},{"StartTime":58581.0,"Objects":[{"StartTime":58581.0,"Position":230.0,"HyperDash":false},{"StartTime":58641.0,"Position":261.006683,"HyperDash":false},{"StartTime":58738.0,"Position":299.391846,"HyperDash":false}]},{"StartTime":58897.0,"Objects":[{"StartTime":58897.0,"Position":146.0,"HyperDash":false},{"StartTime":58957.0,"Position":115.275429,"HyperDash":false},{"StartTime":59054.0,"Position":76.5043,"HyperDash":false}]},{"StartTime":59212.0,"Objects":[{"StartTime":59212.0,"Position":293.0,"HyperDash":false},{"StartTime":59281.0,"Position":302.5204,"HyperDash":false},{"StartTime":59351.0,"Position":304.419159,"HyperDash":false},{"StartTime":59421.0,"Position":314.467,"HyperDash":false},{"StartTime":59527.0,"Position":318.606537,"HyperDash":false}]},{"StartTime":59686.0,"Objects":[{"StartTime":59686.0,"Position":224.0,"HyperDash":false}]},{"StartTime":59844.0,"Objects":[{"StartTime":59844.0,"Position":405.0,"HyperDash":false},{"StartTime":59904.0,"Position":420.876434,"HyperDash":false},{"StartTime":60001.0,"Position":412.2616,"HyperDash":false}]},{"StartTime":60160.0,"Objects":[{"StartTime":60160.0,"Position":500.0,"HyperDash":false},{"StartTime":60220.0,"Position":492.536743,"HyperDash":false},{"StartTime":60317.0,"Position":429.739,"HyperDash":false}]},{"StartTime":60476.0,"Objects":[{"StartTime":60476.0,"Position":303.0,"HyperDash":false},{"StartTime":60545.0,"Position":319.948517,"HyperDash":false},{"StartTime":60615.0,"Position":348.183044,"HyperDash":false},{"StartTime":60685.0,"Position":402.9306,"HyperDash":false},{"StartTime":60791.0,"Position":439.958466,"HyperDash":false}]},{"StartTime":60949.0,"Objects":[{"StartTime":60949.0,"Position":311.0,"HyperDash":false}]},{"StartTime":61107.0,"Objects":[{"StartTime":61107.0,"Position":143.0,"HyperDash":false},{"StartTime":61167.0,"Position":158.994171,"HyperDash":false},{"StartTime":61264.0,"Position":156.7843,"HyperDash":false}]},{"StartTime":61423.0,"Objects":[{"StartTime":61423.0,"Position":63.0,"HyperDash":false},{"StartTime":61483.0,"Position":43.900074,"HyperDash":false},{"StartTime":61580.0,"Position":76.12121,"HyperDash":false}]},{"StartTime":61739.0,"Objects":[{"StartTime":61739.0,"Position":160.0,"HyperDash":false},{"StartTime":61799.0,"Position":167.994171,"HyperDash":false},{"StartTime":61896.0,"Position":173.7843,"HyperDash":false}]},{"StartTime":62055.0,"Objects":[{"StartTime":62055.0,"Position":80.0,"HyperDash":false},{"StartTime":62115.0,"Position":65.90008,"HyperDash":false},{"StartTime":62212.0,"Position":93.12121,"HyperDash":false}]},{"StartTime":62370.0,"Objects":[{"StartTime":62370.0,"Position":184.0,"HyperDash":false},{"StartTime":62439.0,"Position":195.225571,"HyperDash":false},{"StartTime":62509.0,"Position":239.72702,"HyperDash":false},{"StartTime":62579.0,"Position":260.956116,"HyperDash":false},{"StartTime":62685.0,"Position":306.492645,"HyperDash":false}]},{"StartTime":62844.0,"Objects":[{"StartTime":62844.0,"Position":406.0,"HyperDash":false}]},{"StartTime":63002.0,"Objects":[{"StartTime":63002.0,"Position":473.0,"HyperDash":false},{"StartTime":63062.0,"Position":481.5252,"HyperDash":false},{"StartTime":63159.0,"Position":455.637146,"HyperDash":false}]},{"StartTime":63318.0,"Objects":[{"StartTime":63318.0,"Position":331.0,"HyperDash":false},{"StartTime":63378.0,"Position":349.711639,"HyperDash":false},{"StartTime":63475.0,"Position":347.2463,"HyperDash":false}]},{"StartTime":63633.0,"Objects":[{"StartTime":63633.0,"Position":234.0,"HyperDash":false}]},{"StartTime":63791.0,"Objects":[{"StartTime":63791.0,"Position":160.0,"HyperDash":false},{"StartTime":63851.0,"Position":187.355438,"HyperDash":false},{"StartTime":63948.0,"Position":231.69101,"HyperDash":false}]},{"StartTime":64107.0,"Objects":[{"StartTime":64107.0,"Position":147.0,"HyperDash":false},{"StartTime":64167.0,"Position":126.032143,"HyperDash":false},{"StartTime":64264.0,"Position":74.96641,"HyperDash":false}]},{"StartTime":64423.0,"Objects":[{"StartTime":64423.0,"Position":35.0,"HyperDash":false}]},{"StartTime":64581.0,"Objects":[{"StartTime":64581.0,"Position":148.0,"HyperDash":false},{"StartTime":64641.0,"Position":112.032143,"HyperDash":false},{"StartTime":64738.0,"Position":75.96641,"HyperDash":false}]},{"StartTime":64897.0,"Objects":[{"StartTime":64897.0,"Position":18.0,"HyperDash":false}]},{"StartTime":65054.0,"Objects":[{"StartTime":65054.0,"Position":133.0,"HyperDash":false},{"StartTime":65114.0,"Position":141.7638,"HyperDash":false},{"StartTime":65211.0,"Position":148.7033,"HyperDash":false}]},{"StartTime":65370.0,"Objects":[{"StartTime":65370.0,"Position":224.0,"HyperDash":false},{"StartTime":65439.0,"Position":210.371689,"HyperDash":false},{"StartTime":65509.0,"Position":211.28067,"HyperDash":false},{"StartTime":65579.0,"Position":215.692825,"HyperDash":false},{"StartTime":65685.0,"Position":246.723145,"HyperDash":false}]},{"StartTime":65844.0,"Objects":[{"StartTime":65844.0,"Position":367.0,"HyperDash":false},{"StartTime":65904.0,"Position":394.7037,"HyperDash":false},{"StartTime":66001.0,"Position":437.557922,"HyperDash":false}]},{"StartTime":66160.0,"Objects":[{"StartTime":66160.0,"Position":456.0,"HyperDash":false},{"StartTime":66220.0,"Position":443.412323,"HyperDash":false},{"StartTime":66317.0,"Position":430.542175,"HyperDash":false}]},{"StartTime":66476.0,"Objects":[{"StartTime":66476.0,"Position":310.0,"HyperDash":false},{"StartTime":66536.0,"Position":332.587646,"HyperDash":false},{"StartTime":66633.0,"Position":335.457825,"HyperDash":false}]},{"StartTime":66791.0,"Objects":[{"StartTime":66791.0,"Position":452.0,"HyperDash":false},{"StartTime":66851.0,"Position":421.412354,"HyperDash":false},{"StartTime":66948.0,"Position":426.542175,"HyperDash":false}]},{"StartTime":67107.0,"Objects":[{"StartTime":67107.0,"Position":250.0,"HyperDash":false},{"StartTime":67167.0,"Position":259.587646,"HyperDash":false},{"StartTime":67264.0,"Position":275.457825,"HyperDash":false}]},{"StartTime":67423.0,"Objects":[{"StartTime":67423.0,"Position":143.0,"HyperDash":false},{"StartTime":67483.0,"Position":107.965904,"HyperDash":false},{"StartTime":67580.0,"Position":67.02744,"HyperDash":false}]},{"StartTime":67739.0,"Objects":[{"StartTime":67739.0,"Position":8.0,"HyperDash":false},{"StartTime":67799.0,"Position":39.0340958,"HyperDash":false},{"StartTime":67896.0,"Position":83.97256,"HyperDash":false}]},{"StartTime":68054.0,"Objects":[{"StartTime":68054.0,"Position":153.0,"HyperDash":false},{"StartTime":68123.0,"Position":136.712723,"HyperDash":false},{"StartTime":68193.0,"Position":96.94302,"HyperDash":false},{"StartTime":68263.0,"Position":47.1733246,"HyperDash":false},{"StartTime":68369.0,"Position":1.03634644,"HyperDash":false}]},{"StartTime":68686.0,"Objects":[{"StartTime":68686.0,"Position":162.0,"HyperDash":false},{"StartTime":68764.0,"Position":140.279373,"HyperDash":false},{"StartTime":68843.0,"Position":149.194489,"HyperDash":false},{"StartTime":68904.0,"Position":160.855484,"HyperDash":false},{"StartTime":69001.0,"Position":162.0,"HyperDash":false}]},{"StartTime":69160.0,"Objects":[{"StartTime":69160.0,"Position":264.0,"HyperDash":false}]},{"StartTime":69318.0,"Objects":[{"StartTime":69318.0,"Position":264.0,"HyperDash":false},{"StartTime":69387.0,"Position":293.878754,"HyperDash":false},{"StartTime":69457.0,"Position":308.962463,"HyperDash":false},{"StartTime":69527.0,"Position":326.259735,"HyperDash":false},{"StartTime":69633.0,"Position":376.5735,"HyperDash":false}]},{"StartTime":69791.0,"Objects":[{"StartTime":69791.0,"Position":477.0,"HyperDash":false},{"StartTime":69851.0,"Position":473.9998,"HyperDash":false},{"StartTime":69948.0,"Position":451.330017,"HyperDash":false}]},{"StartTime":70107.0,"Objects":[{"StartTime":70107.0,"Position":352.0,"HyperDash":false}]},{"StartTime":70265.0,"Objects":[{"StartTime":70265.0,"Position":352.0,"HyperDash":false},{"StartTime":70325.0,"Position":355.88562,"HyperDash":false},{"StartTime":70422.0,"Position":377.063232,"HyperDash":false}]},{"StartTime":70581.0,"Objects":[{"StartTime":70581.0,"Position":252.0,"HyperDash":false},{"StartTime":70650.0,"Position":243.345444,"HyperDash":false},{"StartTime":70720.0,"Position":190.933167,"HyperDash":false},{"StartTime":70790.0,"Position":181.559875,"HyperDash":false},{"StartTime":70896.0,"Position":139.968262,"HyperDash":false}]},{"StartTime":71212.0,"Objects":[{"StartTime":71212.0,"Position":139.0,"HyperDash":false},{"StartTime":71272.0,"Position":142.551361,"HyperDash":false},{"StartTime":71369.0,"Position":117.237366,"HyperDash":false}]},{"StartTime":71528.0,"Objects":[{"StartTime":71528.0,"Position":197.0,"HyperDash":false}]},{"StartTime":71686.0,"Objects":[{"StartTime":71686.0,"Position":197.0,"HyperDash":false}]},{"StartTime":71844.0,"Objects":[{"StartTime":71844.0,"Position":246.0,"HyperDash":false},{"StartTime":71904.0,"Position":274.251373,"HyperDash":false},{"StartTime":72001.0,"Position":310.825043,"HyperDash":false}]},{"StartTime":72160.0,"Objects":[{"StartTime":72160.0,"Position":382.0,"HyperDash":false}]},{"StartTime":72318.0,"Objects":[{"StartTime":72318.0,"Position":382.0,"HyperDash":false},{"StartTime":72387.0,"Position":375.660431,"HyperDash":false},{"StartTime":72457.0,"Position":397.292725,"HyperDash":false},{"StartTime":72527.0,"Position":400.838165,"HyperDash":false},{"StartTime":72633.0,"Position":413.841949,"HyperDash":false}]},{"StartTime":72791.0,"Objects":[{"StartTime":72791.0,"Position":483.0,"HyperDash":false},{"StartTime":72851.0,"Position":443.387177,"HyperDash":false},{"StartTime":72948.0,"Position":422.070221,"HyperDash":false}]},{"StartTime":73107.0,"Objects":[{"StartTime":73107.0,"Position":316.0,"HyperDash":false}]},{"StartTime":73265.0,"Objects":[{"StartTime":73265.0,"Position":316.0,"HyperDash":false}]},{"StartTime":73423.0,"Objects":[{"StartTime":73423.0,"Position":213.0,"HyperDash":false},{"StartTime":73483.0,"Position":236.624237,"HyperDash":false},{"StartTime":73580.0,"Position":274.115784,"HyperDash":false}]},{"StartTime":73739.0,"Objects":[{"StartTime":73739.0,"Position":151.0,"HyperDash":false},{"StartTime":73808.0,"Position":168.107178,"HyperDash":false},{"StartTime":73878.0,"Position":188.948715,"HyperDash":false},{"StartTime":73948.0,"Position":173.253,"HyperDash":false},{"StartTime":74054.0,"Position":186.276779,"HyperDash":false}]},{"StartTime":74212.0,"Objects":[{"StartTime":74212.0,"Position":71.0,"HyperDash":false}]},{"StartTime":74370.0,"Objects":[{"StartTime":74370.0,"Position":71.0,"HyperDash":false},{"StartTime":74439.0,"Position":73.10717,"HyperDash":false},{"StartTime":74509.0,"Position":99.94872,"HyperDash":false},{"StartTime":74579.0,"Position":82.253,"HyperDash":false},{"StartTime":74685.0,"Position":106.276787,"HyperDash":false}]},{"StartTime":74844.0,"Objects":[{"StartTime":74844.0,"Position":217.0,"HyperDash":false},{"StartTime":74904.0,"Position":195.833557,"HyperDash":false},{"StartTime":75001.0,"Position":203.228043,"HyperDash":false}]},{"StartTime":75160.0,"Objects":[{"StartTime":75160.0,"Position":292.0,"HyperDash":false},{"StartTime":75220.0,"Position":322.137878,"HyperDash":false},{"StartTime":75317.0,"Position":355.583832,"HyperDash":false}]},{"StartTime":75476.0,"Objects":[{"StartTime":75476.0,"Position":470.0,"HyperDash":false}]},{"StartTime":75633.0,"Objects":[{"StartTime":75633.0,"Position":470.0,"HyperDash":false},{"StartTime":75702.0,"Position":451.070618,"HyperDash":false},{"StartTime":75772.0,"Position":423.6466,"HyperDash":false},{"StartTime":75842.0,"Position":385.354156,"HyperDash":false},{"StartTime":75948.0,"Position":339.91098,"HyperDash":false}]},{"StartTime":76265.0,"Objects":[{"StartTime":76265.0,"Position":339.0,"HyperDash":false},{"StartTime":76325.0,"Position":330.2449,"HyperDash":false},{"StartTime":76422.0,"Position":356.729736,"HyperDash":false}]},{"StartTime":76581.0,"Objects":[{"StartTime":76581.0,"Position":274.0,"HyperDash":false}]},{"StartTime":76739.0,"Objects":[{"StartTime":76739.0,"Position":274.0,"HyperDash":false}]},{"StartTime":76897.0,"Objects":[{"StartTime":76897.0,"Position":196.0,"HyperDash":false},{"StartTime":76957.0,"Position":202.336975,"HyperDash":false},{"StartTime":77054.0,"Position":177.609283,"HyperDash":false}]},{"StartTime":77212.0,"Objects":[{"StartTime":77212.0,"Position":76.0,"HyperDash":false},{"StartTime":77272.0,"Position":87.663,"HyperDash":false},{"StartTime":77369.0,"Position":94.3907,"HyperDash":false}]},{"StartTime":77528.0,"Objects":[{"StartTime":77528.0,"Position":193.0,"HyperDash":false},{"StartTime":77588.0,"Position":215.246429,"HyperDash":false},{"StartTime":77685.0,"Position":255.401413,"HyperDash":false}]},{"StartTime":77844.0,"Objects":[{"StartTime":77844.0,"Position":363.0,"HyperDash":false},{"StartTime":77904.0,"Position":335.063263,"HyperDash":false},{"StartTime":78001.0,"Position":300.441956,"HyperDash":false}]},{"StartTime":78160.0,"Objects":[{"StartTime":78160.0,"Position":424.0,"HyperDash":false},{"StartTime":78229.0,"Position":425.201782,"HyperDash":false},{"StartTime":78299.0,"Position":406.9763,"HyperDash":false},{"StartTime":78369.0,"Position":392.725616,"HyperDash":false},{"StartTime":78475.0,"Position":375.221161,"HyperDash":false}]},{"StartTime":78791.0,"Objects":[{"StartTime":78791.0,"Position":375.0,"HyperDash":false}]},{"StartTime":87633.0,"Objects":[{"StartTime":87633.0,"Position":59.0,"HyperDash":false},{"StartTime":87733.0,"Position":109.695786,"HyperDash":false},{"StartTime":87869.0,"Position":154.94931,"HyperDash":false}]},{"StartTime":87949.0,"Objects":[{"StartTime":87949.0,"Position":157.0,"HyperDash":false},{"StartTime":88049.0,"Position":98.90486,"HyperDash":false},{"StartTime":88185.0,"Position":61.01484,"HyperDash":false}]},{"StartTime":88265.0,"Objects":[{"StartTime":88265.0,"Position":65.0,"HyperDash":false},{"StartTime":88365.0,"Position":107.226257,"HyperDash":false},{"StartTime":88501.0,"Position":160.443985,"HyperDash":false}]},{"StartTime":88581.0,"Objects":[{"StartTime":88581.0,"Position":162.0,"HyperDash":false}]},{"StartTime":88897.0,"Objects":[{"StartTime":88897.0,"Position":410.0,"HyperDash":false},{"StartTime":88957.0,"Position":434.139282,"HyperDash":false},{"StartTime":89054.0,"Position":430.5437,"HyperDash":false}]},{"StartTime":89212.0,"Objects":[{"StartTime":89212.0,"Position":329.0,"HyperDash":false}]},{"StartTime":89370.0,"Objects":[{"StartTime":89370.0,"Position":237.0,"HyperDash":false},{"StartTime":89430.0,"Position":206.860718,"HyperDash":false},{"StartTime":89527.0,"Position":216.4563,"HyperDash":false}]},{"StartTime":89686.0,"Objects":[{"StartTime":89686.0,"Position":412.0,"HyperDash":false},{"StartTime":89746.0,"Position":427.040955,"HyperDash":false},{"StartTime":89843.0,"Position":390.8584,"HyperDash":false}]},{"StartTime":90002.0,"Objects":[{"StartTime":90002.0,"Position":224.0,"HyperDash":false},{"StartTime":90071.0,"Position":193.575424,"HyperDash":false},{"StartTime":90141.0,"Position":140.9065,"HyperDash":false},{"StartTime":90211.0,"Position":134.488129,"HyperDash":false},{"StartTime":90317.0,"Position":98.64927,"HyperDash":false}]},{"StartTime":90476.0,"Objects":[{"StartTime":90476.0,"Position":198.0,"HyperDash":false}]},{"StartTime":90633.0,"Objects":[{"StartTime":90633.0,"Position":197.0,"HyperDash":false}]},{"StartTime":90791.0,"Objects":[{"StartTime":90791.0,"Position":85.0,"HyperDash":false},{"StartTime":90851.0,"Position":83.48808,"HyperDash":false},{"StartTime":90948.0,"Position":98.0172348,"HyperDash":false}]},{"StartTime":91107.0,"Objects":[{"StartTime":91107.0,"Position":308.0,"HyperDash":false},{"StartTime":91167.0,"Position":319.751,"HyperDash":false},{"StartTime":91264.0,"Position":319.957062,"HyperDash":false}]},{"StartTime":91423.0,"Objects":[{"StartTime":91423.0,"Position":210.0,"HyperDash":false},{"StartTime":91483.0,"Position":236.879288,"HyperDash":false},{"StartTime":91580.0,"Position":290.540375,"HyperDash":false}]},{"StartTime":91739.0,"Objects":[{"StartTime":91739.0,"Position":196.0,"HyperDash":false}]},{"StartTime":91897.0,"Objects":[{"StartTime":91897.0,"Position":305.0,"HyperDash":false},{"StartTime":91957.0,"Position":317.8793,"HyperDash":false},{"StartTime":92054.0,"Position":385.540375,"HyperDash":false}]},{"StartTime":92212.0,"Objects":[{"StartTime":92212.0,"Position":212.0,"HyperDash":false},{"StartTime":92272.0,"Position":221.879288,"HyperDash":false},{"StartTime":92369.0,"Position":292.540375,"HyperDash":false}]},{"StartTime":92528.0,"Objects":[{"StartTime":92528.0,"Position":446.0,"HyperDash":false},{"StartTime":92597.0,"Position":444.5924,"HyperDash":false},{"StartTime":92667.0,"Position":489.460175,"HyperDash":false},{"StartTime":92737.0,"Position":462.152466,"HyperDash":false},{"StartTime":92843.0,"Position":484.6515,"HyperDash":false}]},{"StartTime":93002.0,"Objects":[{"StartTime":93002.0,"Position":286.0,"HyperDash":false}]},{"StartTime":93160.0,"Objects":[{"StartTime":93160.0,"Position":368.0,"HyperDash":false}]},{"StartTime":93318.0,"Objects":[{"StartTime":93318.0,"Position":268.0,"HyperDash":false},{"StartTime":93378.0,"Position":258.177734,"HyperDash":false},{"StartTime":93475.0,"Position":188.322281,"HyperDash":false}]},{"StartTime":93633.0,"Objects":[{"StartTime":93633.0,"Position":349.0,"HyperDash":false},{"StartTime":93693.0,"Position":301.103668,"HyperDash":false},{"StartTime":93790.0,"Position":269.135223,"HyperDash":false}]},{"StartTime":93949.0,"Objects":[{"StartTime":93949.0,"Position":138.0,"HyperDash":false},{"StartTime":94009.0,"Position":122.494843,"HyperDash":false},{"StartTime":94106.0,"Position":107.101913,"HyperDash":false}]},{"StartTime":94265.0,"Objects":[{"StartTime":94265.0,"Position":148.0,"HyperDash":false}]},{"StartTime":94423.0,"Objects":[{"StartTime":94423.0,"Position":22.0,"HyperDash":false},{"StartTime":94483.0,"Position":32.5051575,"HyperDash":false},{"StartTime":94580.0,"Position":52.89809,"HyperDash":false}]},{"StartTime":94739.0,"Objects":[{"StartTime":94739.0,"Position":243.0,"HyperDash":false},{"StartTime":94799.0,"Position":236.5184,"HyperDash":false},{"StartTime":94896.0,"Position":272.894073,"HyperDash":false}]},{"StartTime":95054.0,"Objects":[{"StartTime":95054.0,"Position":438.0,"HyperDash":false},{"StartTime":95123.0,"Position":388.7492,"HyperDash":false},{"StartTime":95193.0,"Position":392.3104,"HyperDash":false},{"StartTime":95263.0,"Position":331.956,"HyperDash":false},{"StartTime":95369.0,"Position":294.5527,"HyperDash":false}]},{"StartTime":95528.0,"Objects":[{"StartTime":95528.0,"Position":254.0,"HyperDash":false},{"StartTime":95588.0,"Position":290.0384,"HyperDash":false},{"StartTime":95685.0,"Position":283.842346,"HyperDash":false}]},{"StartTime":95844.0,"Objects":[{"StartTime":95844.0,"Position":427.0,"HyperDash":false},{"StartTime":95904.0,"Position":416.4857,"HyperDash":false},{"StartTime":96001.0,"Position":442.904083,"HyperDash":false}]},{"StartTime":96160.0,"Objects":[{"StartTime":96160.0,"Position":279.0,"HyperDash":false},{"StartTime":96220.0,"Position":299.0384,"HyperDash":false},{"StartTime":96317.0,"Position":308.842346,"HyperDash":false}]},{"StartTime":96476.0,"Objects":[{"StartTime":96476.0,"Position":225.0,"HyperDash":false},{"StartTime":96536.0,"Position":210.338287,"HyperDash":false},{"StartTime":96633.0,"Position":143.344467,"HyperDash":false}]},{"StartTime":96791.0,"Objects":[{"StartTime":96791.0,"Position":288.0,"HyperDash":false}]},{"StartTime":96949.0,"Objects":[{"StartTime":96949.0,"Position":180.0,"HyperDash":false},{"StartTime":97009.0,"Position":166.338287,"HyperDash":false},{"StartTime":97106.0,"Position":98.3444748,"HyperDash":false}]},{"StartTime":97265.0,"Objects":[{"StartTime":97265.0,"Position":274.0,"HyperDash":false},{"StartTime":97325.0,"Position":309.692352,"HyperDash":false},{"StartTime":97422.0,"Position":355.842438,"HyperDash":false}]},{"StartTime":97581.0,"Objects":[{"StartTime":97581.0,"Position":417.0,"HyperDash":false}]},{"StartTime":97739.0,"Objects":[{"StartTime":97739.0,"Position":420.0,"HyperDash":false},{"StartTime":97799.0,"Position":396.8472,"HyperDash":false},{"StartTime":97896.0,"Position":380.9233,"HyperDash":false}]},{"StartTime":98054.0,"Objects":[{"StartTime":98054.0,"Position":346.0,"HyperDash":false}]},{"StartTime":98212.0,"Objects":[{"StartTime":98212.0,"Position":299.0,"HyperDash":false}]},{"StartTime":98370.0,"Objects":[{"StartTime":98370.0,"Position":337.0,"HyperDash":false}]},{"StartTime":98528.0,"Objects":[{"StartTime":98528.0,"Position":290.0,"HyperDash":false}]},{"StartTime":98686.0,"Objects":[{"StartTime":98686.0,"Position":170.0,"HyperDash":false},{"StartTime":98746.0,"Position":129.894,"HyperDash":false},{"StartTime":98843.0,"Position":88.38194,"HyperDash":false}]},{"StartTime":99002.0,"Objects":[{"StartTime":99002.0,"Position":45.0,"HyperDash":false},{"StartTime":99062.0,"Position":73.99868,"HyperDash":false},{"StartTime":99159.0,"Position":87.32532,"HyperDash":false}]},{"StartTime":99318.0,"Objects":[{"StartTime":99318.0,"Position":164.0,"HyperDash":false}]},{"StartTime":99476.0,"Objects":[{"StartTime":99476.0,"Position":146.0,"HyperDash":false},{"StartTime":99536.0,"Position":113.96389,"HyperDash":false},{"StartTime":99633.0,"Position":66.87661,"HyperDash":false}]},{"StartTime":99791.0,"Objects":[{"StartTime":99791.0,"Position":163.0,"HyperDash":false},{"StartTime":99851.0,"Position":182.9796,"HyperDash":false},{"StartTime":99948.0,"Position":242.314056,"HyperDash":false}]},{"StartTime":100107.0,"Objects":[{"StartTime":100107.0,"Position":306.0,"HyperDash":false},{"StartTime":100176.0,"Position":272.841949,"HyperDash":false},{"StartTime":100246.0,"Position":282.58606,"HyperDash":false},{"StartTime":100316.0,"Position":262.382751,"HyperDash":false},{"StartTime":100422.0,"Position":258.4074,"HyperDash":false}]},{"StartTime":100581.0,"Objects":[{"StartTime":100581.0,"Position":446.0,"HyperDash":false}]},{"StartTime":100739.0,"Objects":[{"StartTime":100739.0,"Position":376.0,"HyperDash":false},{"StartTime":100799.0,"Position":361.111847,"HyperDash":false},{"StartTime":100896.0,"Position":305.5532,"HyperDash":false}]},{"StartTime":101054.0,"Objects":[{"StartTime":101054.0,"Position":236.0,"HyperDash":false}]},{"StartTime":101212.0,"Objects":[{"StartTime":101212.0,"Position":402.0,"HyperDash":false},{"StartTime":101272.0,"Position":446.655,"HyperDash":false},{"StartTime":101369.0,"Position":481.3611,"HyperDash":false}]},{"StartTime":101528.0,"Objects":[{"StartTime":101528.0,"Position":334.0,"HyperDash":false},{"StartTime":101588.0,"Position":334.394165,"HyperDash":false},{"StartTime":101685.0,"Position":350.023041,"HyperDash":false}]},{"StartTime":101844.0,"Objects":[{"StartTime":101844.0,"Position":219.0,"HyperDash":false}]},{"StartTime":102002.0,"Objects":[{"StartTime":102002.0,"Position":177.0,"HyperDash":false},{"StartTime":102062.0,"Position":159.9585,"HyperDash":false},{"StartTime":102159.0,"Position":98.64363,"HyperDash":false}]},{"StartTime":102318.0,"Objects":[{"StartTime":102318.0,"Position":140.0,"HyperDash":false},{"StartTime":102378.0,"Position":163.494385,"HyperDash":false},{"StartTime":102475.0,"Position":218.169327,"HyperDash":false}]},{"StartTime":102633.0,"Objects":[{"StartTime":102633.0,"Position":22.0,"HyperDash":false},{"StartTime":102702.0,"Position":31.6368866,"HyperDash":false},{"StartTime":102772.0,"Position":59.88773,"HyperDash":false},{"StartTime":102842.0,"Position":57.2475433,"HyperDash":false},{"StartTime":102948.0,"Position":67.89443,"HyperDash":false}]},{"StartTime":103107.0,"Objects":[{"StartTime":103107.0,"Position":182.0,"HyperDash":false}]},{"StartTime":103265.0,"Objects":[{"StartTime":103265.0,"Position":200.0,"HyperDash":false},{"StartTime":103325.0,"Position":221.459839,"HyperDash":false},{"StartTime":103422.0,"Position":217.979309,"HyperDash":false}]},{"StartTime":103581.0,"Objects":[{"StartTime":103581.0,"Position":337.0,"HyperDash":false}]},{"StartTime":103739.0,"Objects":[{"StartTime":103739.0,"Position":331.0,"HyperDash":false},{"StartTime":103799.0,"Position":312.540161,"HyperDash":false},{"StartTime":103896.0,"Position":313.0207,"HyperDash":false}]},{"StartTime":104054.0,"Objects":[{"StartTime":104054.0,"Position":194.0,"HyperDash":false},{"StartTime":104123.0,"Position":231.002213,"HyperDash":false},{"StartTime":104193.0,"Position":276.3082,"HyperDash":false},{"StartTime":104263.0,"Position":277.368225,"HyperDash":false},{"StartTime":104369.0,"Position":325.1272,"HyperDash":false}]},{"StartTime":104528.0,"Objects":[{"StartTime":104528.0,"Position":142.0,"HyperDash":false},{"StartTime":104588.0,"Position":118.666763,"HyperDash":false},{"StartTime":104685.0,"Position":61.4790764,"HyperDash":false}]},{"StartTime":104844.0,"Objects":[{"StartTime":104844.0,"Position":187.0,"HyperDash":false},{"StartTime":104904.0,"Position":140.796371,"HyperDash":false},{"StartTime":105001.0,"Position":106.642426,"HyperDash":false}]},{"StartTime":105160.0,"Objects":[{"StartTime":105160.0,"Position":210.0,"HyperDash":false},{"StartTime":105220.0,"Position":216.543152,"HyperDash":false},{"StartTime":105317.0,"Position":232.886017,"HyperDash":false}]},{"StartTime":105476.0,"Objects":[{"StartTime":105476.0,"Position":339.0,"HyperDash":false},{"StartTime":105536.0,"Position":350.726563,"HyperDash":false},{"StartTime":105633.0,"Position":361.889038,"HyperDash":false}]},{"StartTime":105791.0,"Objects":[{"StartTime":105791.0,"Position":309.0,"HyperDash":false}]},{"StartTime":105949.0,"Objects":[{"StartTime":105949.0,"Position":454.0,"HyperDash":false},{"StartTime":106009.0,"Position":420.0147,"HyperDash":false},{"StartTime":106106.0,"Position":430.975983,"HyperDash":false}]},{"StartTime":106265.0,"Objects":[{"StartTime":106265.0,"Position":246.0,"HyperDash":false},{"StartTime":106325.0,"Position":244.2446,"HyperDash":false},{"StartTime":106422.0,"Position":268.0487,"HyperDash":false}]},{"StartTime":106581.0,"Objects":[{"StartTime":106581.0,"Position":133.0,"HyperDash":false},{"StartTime":106641.0,"Position":103.963638,"HyperDash":false},{"StartTime":106738.0,"Position":49.17154,"HyperDash":false}]},{"StartTime":106897.0,"Objects":[{"StartTime":106897.0,"Position":260.0,"HyperDash":false},{"StartTime":106957.0,"Position":304.036346,"HyperDash":false},{"StartTime":107054.0,"Position":343.828461,"HyperDash":false}]},{"StartTime":107212.0,"Objects":[{"StartTime":107212.0,"Position":127.0,"HyperDash":false},{"StartTime":107272.0,"Position":104.963638,"HyperDash":false},{"StartTime":107369.0,"Position":43.17154,"HyperDash":false}]},{"StartTime":107528.0,"Objects":[{"StartTime":107528.0,"Position":254.0,"HyperDash":false},{"StartTime":107588.0,"Position":292.036346,"HyperDash":false},{"StartTime":107685.0,"Position":337.828461,"HyperDash":false}]},{"StartTime":107844.0,"Objects":[{"StartTime":107844.0,"Position":479.0,"HyperDash":false}]},{"StartTime":108002.0,"Objects":[{"StartTime":108002.0,"Position":411.0,"HyperDash":false}]},{"StartTime":108160.0,"Objects":[{"StartTime":108160.0,"Position":400.0,"HyperDash":false}]},{"StartTime":108318.0,"Objects":[{"StartTime":108318.0,"Position":488.0,"HyperDash":false}]},{"StartTime":108476.0,"Objects":[{"StartTime":108476.0,"Position":319.0,"HyperDash":false},{"StartTime":108536.0,"Position":311.9797,"HyperDash":false},{"StartTime":108633.0,"Position":313.713531,"HyperDash":false}]},{"StartTime":108791.0,"Objects":[{"StartTime":108791.0,"Position":298.0,"HyperDash":false}]},{"StartTime":108949.0,"Objects":[{"StartTime":108949.0,"Position":220.0,"HyperDash":false}]},{"StartTime":109107.0,"Objects":[{"StartTime":109107.0,"Position":163.0,"HyperDash":false}]},{"StartTime":110212.0,"Objects":[{"StartTime":110212.0,"Position":160.0,"HyperDash":false}]},{"StartTime":111002.0,"Objects":[{"StartTime":111002.0,"Position":160.0,"HyperDash":false},{"StartTime":111102.0,"Position":170.38269,"HyperDash":false},{"StartTime":111238.0,"Position":193.050369,"HyperDash":false}]},{"StartTime":111318.0,"Objects":[{"StartTime":111318.0,"Position":214.0,"HyperDash":false},{"StartTime":111378.0,"Position":214.7743,"HyperDash":false},{"StartTime":111475.0,"Position":186.350418,"HyperDash":false}]},{"StartTime":111554.0,"Objects":[{"StartTime":111554.0,"Position":202.0,"HyperDash":false}]},{"StartTime":111633.0,"Objects":[{"StartTime":111633.0,"Position":202.0,"HyperDash":false}]},{"StartTime":112739.0,"Objects":[{"StartTime":112739.0,"Position":197.0,"HyperDash":false}]},{"StartTime":113528.0,"Objects":[{"StartTime":113528.0,"Position":197.0,"HyperDash":false},{"StartTime":113628.0,"Position":234.305908,"HyperDash":false},{"StartTime":113764.0,"Position":282.864716,"HyperDash":false}]},{"StartTime":113844.0,"Objects":[{"StartTime":113844.0,"Position":293.0,"HyperDash":false},{"StartTime":113904.0,"Position":330.591919,"HyperDash":false},{"StartTime":114001.0,"Position":348.628937,"HyperDash":false}]},{"StartTime":114081.0,"Objects":[{"StartTime":114081.0,"Position":413.0,"HyperDash":false}]},{"StartTime":114160.0,"Objects":[{"StartTime":114160.0,"Position":413.0,"HyperDash":false},{"StartTime":114220.0,"Position":422.708557,"HyperDash":false},{"StartTime":114317.0,"Position":432.737671,"HyperDash":false}]},{"StartTime":114476.0,"Objects":[{"StartTime":114476.0,"Position":328.0,"HyperDash":false}]},{"StartTime":114633.0,"Objects":[{"StartTime":114633.0,"Position":388.0,"HyperDash":false},{"StartTime":114693.0,"Position":376.2914,"HyperDash":false},{"StartTime":114790.0,"Position":368.262329,"HyperDash":false}]},{"StartTime":114949.0,"Objects":[{"StartTime":114949.0,"Position":218.0,"HyperDash":false},{"StartTime":115009.0,"Position":239.708572,"HyperDash":false},{"StartTime":115106.0,"Position":237.737671,"HyperDash":false}]},{"StartTime":115265.0,"Objects":[{"StartTime":115265.0,"Position":114.0,"HyperDash":false},{"StartTime":115334.0,"Position":111.665535,"HyperDash":false},{"StartTime":115404.0,"Position":90.91377,"HyperDash":false},{"StartTime":115474.0,"Position":109.772278,"HyperDash":false},{"StartTime":115580.0,"Position":75.52312,"HyperDash":false}]},{"StartTime":115739.0,"Objects":[{"StartTime":115739.0,"Position":206.0,"HyperDash":false},{"StartTime":115799.0,"Position":204.020508,"HyperDash":false},{"StartTime":115896.0,"Position":138.2174,"HyperDash":false}]},{"StartTime":116054.0,"Objects":[{"StartTime":116054.0,"Position":247.0,"HyperDash":false},{"StartTime":116114.0,"Position":266.224854,"HyperDash":false},{"StartTime":116211.0,"Position":314.45578,"HyperDash":false}]},{"StartTime":116370.0,"Objects":[{"StartTime":116370.0,"Position":406.0,"HyperDash":false},{"StartTime":116430.0,"Position":420.447754,"HyperDash":false},{"StartTime":116527.0,"Position":416.6286,"HyperDash":false}]},{"StartTime":116686.0,"Objects":[{"StartTime":116686.0,"Position":477.0,"HyperDash":false},{"StartTime":116746.0,"Position":430.998718,"HyperDash":false},{"StartTime":116843.0,"Position":396.846619,"HyperDash":false}]},{"StartTime":117002.0,"Objects":[{"StartTime":117002.0,"Position":286.0,"HyperDash":false}]},{"StartTime":117160.0,"Objects":[{"StartTime":117160.0,"Position":210.0,"HyperDash":false},{"StartTime":117220.0,"Position":257.991272,"HyperDash":false},{"StartTime":117317.0,"Position":289.721649,"HyperDash":false}]},{"StartTime":117476.0,"Objects":[{"StartTime":117476.0,"Position":205.0,"HyperDash":false},{"StartTime":117536.0,"Position":227.503632,"HyperDash":false},{"StartTime":117633.0,"Position":225.386322,"HyperDash":false}]},{"StartTime":117791.0,"Objects":[{"StartTime":117791.0,"Position":80.0,"HyperDash":false},{"StartTime":117860.0,"Position":86.16029,"HyperDash":false},{"StartTime":117930.0,"Position":123.197845,"HyperDash":false},{"StartTime":118000.0,"Position":137.463959,"HyperDash":false},{"StartTime":118106.0,"Position":126.215904,"HyperDash":false}]},{"StartTime":118265.0,"Objects":[{"StartTime":118265.0,"Position":279.0,"HyperDash":false}]},{"StartTime":118423.0,"Objects":[{"StartTime":118423.0,"Position":243.0,"HyperDash":false}]},{"StartTime":118581.0,"Objects":[{"StartTime":118581.0,"Position":306.0,"HyperDash":false}]},{"StartTime":118739.0,"Objects":[{"StartTime":118739.0,"Position":325.0,"HyperDash":false}]},{"StartTime":118897.0,"Objects":[{"StartTime":118897.0,"Position":330.0,"HyperDash":false}]},{"StartTime":119054.0,"Objects":[{"StartTime":119054.0,"Position":402.0,"HyperDash":false}]},{"StartTime":119528.0,"Objects":[{"StartTime":119528.0,"Position":402.0,"HyperDash":false},{"StartTime":119606.0,"Position":383.853424,"HyperDash":false},{"StartTime":119685.0,"Position":340.13028,"HyperDash":false},{"StartTime":119764.0,"Position":306.988525,"HyperDash":false},{"StartTime":119843.0,"Position":292.3493,"HyperDash":false},{"StartTime":119893.0,"Position":278.4925,"HyperDash":false},{"StartTime":119943.0,"Position":251.240692,"HyperDash":false},{"StartTime":119993.0,"Position":219.893433,"HyperDash":false},{"StartTime":120080.0,"Position":193.130447,"HyperDash":false}]},{"StartTime":120160.0,"Objects":[{"StartTime":120160.0,"Position":184.0,"HyperDash":false},{"StartTime":120238.0,"Position":212.882935,"HyperDash":false},{"StartTime":120317.0,"Position":263.114868,"HyperDash":false},{"StartTime":120396.0,"Position":296.998444,"HyperDash":false},{"StartTime":120475.0,"Position":297.759338,"HyperDash":false},{"StartTime":120525.0,"Position":324.634857,"HyperDash":false},{"StartTime":120575.0,"Position":317.226563,"HyperDash":false},{"StartTime":120625.0,"Position":338.548218,"HyperDash":false},{"StartTime":120712.0,"Position":391.822418,"HyperDash":false}]},{"StartTime":120791.0,"Objects":[{"StartTime":120791.0,"Position":385.0,"HyperDash":false},{"StartTime":120869.0,"Position":362.920959,"HyperDash":false},{"StartTime":120948.0,"Position":322.3987,"HyperDash":false},{"StartTime":121027.0,"Position":293.5699,"HyperDash":false},{"StartTime":121106.0,"Position":276.9177,"HyperDash":false},{"StartTime":121156.0,"Position":259.442749,"HyperDash":false},{"StartTime":121206.0,"Position":226.077545,"HyperDash":false},{"StartTime":121256.0,"Position":199.502151,"HyperDash":false},{"StartTime":121343.0,"Position":177.427231,"HyperDash":false}]},{"StartTime":121423.0,"Objects":[{"StartTime":121423.0,"Position":171.0,"HyperDash":false},{"StartTime":121501.0,"Position":194.8829,"HyperDash":false},{"StartTime":121580.0,"Position":232.113251,"HyperDash":false},{"StartTime":121659.0,"Position":263.966827,"HyperDash":false},{"StartTime":121738.0,"Position":284.605957,"HyperDash":false},{"StartTime":121788.0,"Position":288.396973,"HyperDash":false},{"StartTime":121838.0,"Position":333.9621,"HyperDash":false},{"StartTime":121888.0,"Position":331.2913,"HyperDash":false},{"StartTime":121975.0,"Position":378.575928,"HyperDash":false}]},{"StartTime":122054.0,"Objects":[{"StartTime":122054.0,"Position":373.0,"HyperDash":false},{"StartTime":122132.0,"Position":332.9511,"HyperDash":false},{"StartTime":122211.0,"Position":316.390533,"HyperDash":false},{"StartTime":122290.0,"Position":285.1506,"HyperDash":false},{"StartTime":122369.0,"Position":264.59256,"HyperDash":false},{"StartTime":122419.0,"Position":264.4234,"HyperDash":false},{"StartTime":122469.0,"Position":223.247314,"HyperDash":false},{"StartTime":122519.0,"Position":204.743011,"HyperDash":false},{"StartTime":122606.0,"Position":165.707657,"HyperDash":false}]},{"StartTime":122686.0,"Objects":[{"StartTime":122686.0,"Position":156.0,"HyperDash":false},{"StartTime":122786.0,"Position":141.985443,"HyperDash":false},{"StartTime":122922.0,"Position":110.21537,"HyperDash":false}]},{"StartTime":123002.0,"Objects":[{"StartTime":123002.0,"Position":129.0,"HyperDash":false},{"StartTime":123062.0,"Position":124.04425,"HyperDash":false},{"StartTime":123159.0,"Position":151.706512,"HyperDash":false}]},{"StartTime":123318.0,"Objects":[{"StartTime":123318.0,"Position":247.0,"HyperDash":false}]},{"StartTime":123475.0,"Objects":[{"StartTime":123475.0,"Position":278.0,"HyperDash":false}]},{"StartTime":123633.0,"Objects":[{"StartTime":123633.0,"Position":339.0,"HyperDash":false}]},{"StartTime":123791.0,"Objects":[{"StartTime":123791.0,"Position":272.0,"HyperDash":false}]},{"StartTime":123949.0,"Objects":[{"StartTime":123949.0,"Position":224.0,"HyperDash":false}]},{"StartTime":124107.0,"Objects":[{"StartTime":124107.0,"Position":286.0,"HyperDash":false}]},{"StartTime":124265.0,"Objects":[{"StartTime":124265.0,"Position":374.0,"HyperDash":false},{"StartTime":124325.0,"Position":390.564056,"HyperDash":false},{"StartTime":124422.0,"Position":454.897156,"HyperDash":false}]},{"StartTime":124581.0,"Objects":[{"StartTime":124581.0,"Position":368.0,"HyperDash":false}]},{"StartTime":124739.0,"Objects":[{"StartTime":124739.0,"Position":222.0,"HyperDash":false},{"StartTime":124799.0,"Position":189.435959,"HyperDash":false},{"StartTime":124896.0,"Position":141.102829,"HyperDash":false}]},{"StartTime":125054.0,"Objects":[{"StartTime":125054.0,"Position":62.0,"HyperDash":false},{"StartTime":125114.0,"Position":76.30468,"HyperDash":false},{"StartTime":125211.0,"Position":87.89828,"HyperDash":false}]},{"StartTime":125370.0,"Objects":[{"StartTime":125370.0,"Position":261.0,"HyperDash":false},{"StartTime":125430.0,"Position":244.695313,"HyperDash":false},{"StartTime":125527.0,"Position":235.101715,"HyperDash":false}]},{"StartTime":125686.0,"Objects":[{"StartTime":125686.0,"Position":86.0,"HyperDash":false},{"StartTime":125746.0,"Position":49.1613235,"HyperDash":false},{"StartTime":125843.0,"Position":5.8006506,"HyperDash":false}]},{"StartTime":126002.0,"Objects":[{"StartTime":126002.0,"Position":164.0,"HyperDash":false}]},{"StartTime":126160.0,"Objects":[{"StartTime":126160.0,"Position":235.0,"HyperDash":false},{"StartTime":126220.0,"Position":269.911163,"HyperDash":false},{"StartTime":126317.0,"Position":315.594666,"HyperDash":false}]},{"StartTime":126476.0,"Objects":[{"StartTime":126476.0,"Position":454.0,"HyperDash":false},{"StartTime":126536.0,"Position":435.099762,"HyperDash":false},{"StartTime":126633.0,"Position":373.83255,"HyperDash":false}]},{"StartTime":126791.0,"Objects":[{"StartTime":126791.0,"Position":407.0,"HyperDash":false},{"StartTime":126851.0,"Position":407.6067,"HyperDash":false},{"StartTime":126948.0,"Position":400.7375,"HyperDash":false}]},{"StartTime":127107.0,"Objects":[{"StartTime":127107.0,"Position":274.0,"HyperDash":false},{"StartTime":127167.0,"Position":260.302338,"HyperDash":false},{"StartTime":127264.0,"Position":266.941132,"HyperDash":false}]},{"StartTime":127423.0,"Objects":[{"StartTime":127423.0,"Position":421.0,"HyperDash":false},{"StartTime":127483.0,"Position":428.6067,"HyperDash":false},{"StartTime":127580.0,"Position":414.7375,"HyperDash":false}]},{"StartTime":127739.0,"Objects":[{"StartTime":127739.0,"Position":288.0,"HyperDash":false},{"StartTime":127799.0,"Position":302.302338,"HyperDash":false},{"StartTime":127896.0,"Position":280.941132,"HyperDash":false}]},{"StartTime":128054.0,"Objects":[{"StartTime":128054.0,"Position":247.0,"HyperDash":false}]},{"StartTime":128212.0,"Objects":[{"StartTime":128212.0,"Position":212.0,"HyperDash":false}]},{"StartTime":128370.0,"Objects":[{"StartTime":128370.0,"Position":251.0,"HyperDash":false}]},{"StartTime":128528.0,"Objects":[{"StartTime":128528.0,"Position":216.0,"HyperDash":false}]},{"StartTime":128686.0,"Objects":[{"StartTime":128686.0,"Position":81.0,"HyperDash":false},{"StartTime":128746.0,"Position":91.28703,"HyperDash":false},{"StartTime":128843.0,"Position":86.9844,"HyperDash":false}]},{"StartTime":129002.0,"Objects":[{"StartTime":129002.0,"Position":100.0,"HyperDash":false}]},{"StartTime":129160.0,"Objects":[{"StartTime":129160.0,"Position":163.0,"HyperDash":false}]},{"StartTime":129318.0,"Objects":[{"StartTime":129318.0,"Position":91.0,"HyperDash":false}]},{"StartTime":134370.0,"Objects":[{"StartTime":134370.0,"Position":300.0,"HyperDash":false}]},{"StartTime":135633.0,"Objects":[{"StartTime":135633.0,"Position":300.0,"HyperDash":false}]},{"StartTime":136897.0,"Objects":[{"StartTime":136897.0,"Position":300.0,"HyperDash":false},{"StartTime":136997.0,"Position":279.788757,"HyperDash":false},{"StartTime":137133.0,"Position":203.92157,"HyperDash":false}]},{"StartTime":137212.0,"Objects":[{"StartTime":137212.0,"Position":200.0,"HyperDash":false},{"StartTime":137312.0,"Position":227.884033,"HyperDash":false},{"StartTime":137448.0,"Position":295.992767,"HyperDash":false}]},{"StartTime":137528.0,"Objects":[{"StartTime":137528.0,"Position":293.0,"HyperDash":false},{"StartTime":137628.0,"Position":249.348679,"HyperDash":false},{"StartTime":137764.0,"Position":196.522751,"HyperDash":false}]},{"StartTime":137844.0,"Objects":[{"StartTime":137844.0,"Position":193.0,"HyperDash":false}]},{"StartTime":138160.0,"Objects":[{"StartTime":138160.0,"Position":337.0,"HyperDash":false},{"StartTime":138220.0,"Position":361.473083,"HyperDash":false},{"StartTime":138317.0,"Position":359.068726,"HyperDash":false}]},{"StartTime":138476.0,"Objects":[{"StartTime":138476.0,"Position":277.0,"HyperDash":false}]},{"StartTime":138633.0,"Objects":[{"StartTime":138633.0,"Position":355.0,"HyperDash":false},{"StartTime":138702.0,"Position":356.575073,"HyperDash":false},{"StartTime":138772.0,"Position":392.665771,"HyperDash":false},{"StartTime":138842.0,"Position":386.999573,"HyperDash":false},{"StartTime":138948.0,"Position":381.707275,"HyperDash":false}]},{"StartTime":139107.0,"Objects":[{"StartTime":139107.0,"Position":276.0,"HyperDash":false}]},{"StartTime":139265.0,"Objects":[{"StartTime":139265.0,"Position":276.0,"HyperDash":false}]},{"StartTime":139423.0,"Objects":[{"StartTime":139423.0,"Position":209.0,"HyperDash":false},{"StartTime":139483.0,"Position":198.122162,"HyperDash":false},{"StartTime":139580.0,"Position":145.227173,"HyperDash":false}]},{"StartTime":139739.0,"Objects":[{"StartTime":139739.0,"Position":68.0,"HyperDash":false}]},{"StartTime":139896.0,"Objects":[{"StartTime":139896.0,"Position":213.0,"HyperDash":false},{"StartTime":139965.0,"Position":198.023071,"HyperDash":false},{"StartTime":140035.0,"Position":135.780731,"HyperDash":false},{"StartTime":140105.0,"Position":105.911324,"HyperDash":false},{"StartTime":140211.0,"Position":80.0672455,"HyperDash":false}]},{"StartTime":140370.0,"Objects":[{"StartTime":140370.0,"Position":207.0,"HyperDash":false}]},{"StartTime":140528.0,"Objects":[{"StartTime":140528.0,"Position":207.0,"HyperDash":false}]},{"StartTime":140686.0,"Objects":[{"StartTime":140686.0,"Position":308.0,"HyperDash":false},{"StartTime":140746.0,"Position":291.8725,"HyperDash":false},{"StartTime":140843.0,"Position":295.128967,"HyperDash":false}]},{"StartTime":141002.0,"Objects":[{"StartTime":141002.0,"Position":421.0,"HyperDash":false}]},{"StartTime":141160.0,"Objects":[{"StartTime":141160.0,"Position":293.0,"HyperDash":false},{"StartTime":141229.0,"Position":272.132019,"HyperDash":false},{"StartTime":141299.0,"Position":276.853546,"HyperDash":false},{"StartTime":141369.0,"Position":287.3533,"HyperDash":false},{"StartTime":141475.0,"Position":261.940155,"HyperDash":false}]},{"StartTime":141633.0,"Objects":[{"StartTime":141633.0,"Position":392.0,"HyperDash":false}]},{"StartTime":141791.0,"Objects":[{"StartTime":141791.0,"Position":392.0,"HyperDash":false}]},{"StartTime":142265.0,"Objects":[{"StartTime":142265.0,"Position":392.0,"HyperDash":false},{"StartTime":142365.0,"Position":391.062164,"HyperDash":false},{"StartTime":142501.0,"Position":338.346161,"HyperDash":false}]},{"StartTime":142581.0,"Objects":[{"StartTime":142581.0,"Position":326.0,"HyperDash":false},{"StartTime":142650.0,"Position":311.6683,"HyperDash":false},{"StartTime":142720.0,"Position":268.562744,"HyperDash":false},{"StartTime":142790.0,"Position":260.483276,"HyperDash":false},{"StartTime":142896.0,"Position":203.358475,"HyperDash":false}]},{"StartTime":143212.0,"Objects":[{"StartTime":143212.0,"Position":203.0,"HyperDash":false}]},{"StartTime":144476.0,"Objects":[{"StartTime":144476.0,"Position":214.0,"HyperDash":false}]},{"StartTime":145739.0,"Objects":[{"StartTime":145739.0,"Position":214.0,"HyperDash":false},{"StartTime":145839.0,"Position":245.348236,"HyperDash":false},{"StartTime":145975.0,"Position":258.064423,"HyperDash":false}]},{"StartTime":146054.0,"Objects":[{"StartTime":146054.0,"Position":248.0,"HyperDash":false},{"StartTime":146154.0,"Position":238.651749,"HyperDash":false},{"StartTime":146290.0,"Position":203.935547,"HyperDash":false}]},{"StartTime":146370.0,"Objects":[{"StartTime":146370.0,"Position":218.0,"HyperDash":false},{"StartTime":146470.0,"Position":271.72702,"HyperDash":false},{"StartTime":146606.0,"Position":316.220551,"HyperDash":false}]},{"StartTime":146686.0,"Objects":[{"StartTime":146686.0,"Position":326.0,"HyperDash":false}]},{"StartTime":147002.0,"Objects":[{"StartTime":147002.0,"Position":440.0,"HyperDash":false},{"StartTime":147062.0,"Position":454.914642,"HyperDash":false},{"StartTime":147159.0,"Position":431.926636,"HyperDash":false}]},{"StartTime":147318.0,"Objects":[{"StartTime":147318.0,"Position":346.0,"HyperDash":false}]},{"StartTime":147476.0,"Objects":[{"StartTime":147476.0,"Position":457.0,"HyperDash":false},{"StartTime":147545.0,"Position":452.315582,"HyperDash":false},{"StartTime":147615.0,"Position":433.5778,"HyperDash":false},{"StartTime":147685.0,"Position":450.839966,"HyperDash":false},{"StartTime":147791.0,"Position":440.179871,"HyperDash":false}]},{"StartTime":147949.0,"Objects":[{"StartTime":147949.0,"Position":326.0,"HyperDash":false}]},{"StartTime":148107.0,"Objects":[{"StartTime":148107.0,"Position":326.0,"HyperDash":false}]},{"StartTime":148265.0,"Objects":[{"StartTime":148265.0,"Position":170.0,"HyperDash":false},{"StartTime":148325.0,"Position":169.085358,"HyperDash":false},{"StartTime":148422.0,"Position":178.073349,"HyperDash":false}]},{"StartTime":148581.0,"Objects":[{"StartTime":148581.0,"Position":264.0,"HyperDash":false}]},{"StartTime":148739.0,"Objects":[{"StartTime":148739.0,"Position":153.0,"HyperDash":false},{"StartTime":148808.0,"Position":154.6844,"HyperDash":false},{"StartTime":148878.0,"Position":166.422211,"HyperDash":false},{"StartTime":148948.0,"Position":158.160019,"HyperDash":false},{"StartTime":149054.0,"Position":169.820129,"HyperDash":false}]},{"StartTime":149212.0,"Objects":[{"StartTime":149212.0,"Position":284.0,"HyperDash":false}]},{"StartTime":149370.0,"Objects":[{"StartTime":149370.0,"Position":284.0,"HyperDash":false}]},{"StartTime":149528.0,"Objects":[{"StartTime":149528.0,"Position":403.0,"HyperDash":false},{"StartTime":149588.0,"Position":399.914642,"HyperDash":false},{"StartTime":149685.0,"Position":394.926636,"HyperDash":false}]},{"StartTime":149844.0,"Objects":[{"StartTime":149844.0,"Position":309.0,"HyperDash":false}]},{"StartTime":150002.0,"Objects":[{"StartTime":150002.0,"Position":420.0,"HyperDash":false},{"StartTime":150071.0,"Position":421.315582,"HyperDash":false},{"StartTime":150141.0,"Position":430.5778,"HyperDash":false},{"StartTime":150211.0,"Position":409.839966,"HyperDash":false},{"StartTime":150317.0,"Position":403.179871,"HyperDash":false}]},{"StartTime":150475.0,"Objects":[{"StartTime":150475.0,"Position":289.0,"HyperDash":false}]},{"StartTime":150633.0,"Objects":[{"StartTime":150633.0,"Position":289.0,"HyperDash":false}]},{"StartTime":151107.0,"Objects":[{"StartTime":151107.0,"Position":97.0,"HyperDash":false},{"StartTime":151207.0,"Position":135.296875,"HyperDash":false},{"StartTime":151343.0,"Position":191.738083,"HyperDash":false}]},{"StartTime":151423.0,"Objects":[{"StartTime":151423.0,"Position":198.0,"HyperDash":false},{"StartTime":151492.0,"Position":183.569153,"HyperDash":false},{"StartTime":151562.0,"Position":141.428131,"HyperDash":false},{"StartTime":151632.0,"Position":136.803146,"HyperDash":false},{"StartTime":151738.0,"Position":137.3734,"HyperDash":false}]},{"StartTime":152054.0,"Objects":[{"StartTime":152054.0,"Position":297.0,"HyperDash":false},{"StartTime":152123.0,"Position":331.7846,"HyperDash":false},{"StartTime":152193.0,"Position":338.116882,"HyperDash":false},{"StartTime":152263.0,"Position":352.270721,"HyperDash":false},{"StartTime":152369.0,"Position":408.0906,"HyperDash":false}]},{"StartTime":152528.0,"Objects":[{"StartTime":152528.0,"Position":281.0,"HyperDash":false}]},{"StartTime":152686.0,"Objects":[{"StartTime":152686.0,"Position":446.0,"HyperDash":false},{"StartTime":152755.0,"Position":492.2877,"HyperDash":false},{"StartTime":152825.0,"Position":490.710327,"HyperDash":false},{"StartTime":152895.0,"Position":503.465729,"HyperDash":false},{"StartTime":153001.0,"Position":492.445526,"HyperDash":false}]},{"StartTime":153160.0,"Objects":[{"StartTime":153160.0,"Position":343.0,"HyperDash":false}]},{"StartTime":153318.0,"Objects":[{"StartTime":153318.0,"Position":297.0,"HyperDash":false},{"StartTime":153387.0,"Position":262.8003,"HyperDash":false},{"StartTime":153457.0,"Position":234.212128,"HyperDash":false},{"StartTime":153527.0,"Position":214.138336,"HyperDash":false},{"StartTime":153633.0,"Position":166.492523,"HyperDash":false}]},{"StartTime":153791.0,"Objects":[{"StartTime":153791.0,"Position":116.0,"HyperDash":false},{"StartTime":153860.0,"Position":144.280365,"HyperDash":false},{"StartTime":153930.0,"Position":132.43898,"HyperDash":false},{"StartTime":154000.0,"Position":140.923447,"HyperDash":false},{"StartTime":154106.0,"Position":158.507156,"HyperDash":false}]},{"StartTime":154265.0,"Objects":[{"StartTime":154265.0,"Position":264.0,"HyperDash":false},{"StartTime":154325.0,"Position":233.864212,"HyperDash":false},{"StartTime":154422.0,"Position":235.824112,"HyperDash":false}]},{"StartTime":154581.0,"Objects":[{"StartTime":154581.0,"Position":152.0,"HyperDash":false},{"StartTime":154650.0,"Position":125.809914,"HyperDash":false},{"StartTime":154720.0,"Position":104.5544,"HyperDash":false},{"StartTime":154790.0,"Position":63.7936554,"HyperDash":false},{"StartTime":154896.0,"Position":32.2917,"HyperDash":false}]},{"StartTime":155054.0,"Objects":[{"StartTime":155054.0,"Position":191.0,"HyperDash":false}]},{"StartTime":155212.0,"Objects":[{"StartTime":155212.0,"Position":264.0,"HyperDash":false},{"StartTime":155281.0,"Position":311.2232,"HyperDash":false},{"StartTime":155351.0,"Position":339.435272,"HyperDash":false},{"StartTime":155421.0,"Position":368.023529,"HyperDash":false},{"StartTime":155527.0,"Position":382.984161,"HyperDash":false}]},{"StartTime":155686.0,"Objects":[{"StartTime":155686.0,"Position":212.0,"HyperDash":false}]},{"StartTime":155844.0,"Objects":[{"StartTime":155844.0,"Position":405.0,"HyperDash":false},{"StartTime":155913.0,"Position":398.1627,"HyperDash":false},{"StartTime":155983.0,"Position":377.19342,"HyperDash":false},{"StartTime":156053.0,"Position":363.4817,"HyperDash":false},{"StartTime":156159.0,"Position":358.190857,"HyperDash":false}]},{"StartTime":156318.0,"Objects":[{"StartTime":156318.0,"Position":158.0,"HyperDash":false},{"StartTime":156387.0,"Position":166.012711,"HyperDash":false},{"StartTime":156457.0,"Position":151.858978,"HyperDash":false},{"StartTime":156527.0,"Position":139.665756,"HyperDash":false},{"StartTime":156633.0,"Position":111.16011,"HyperDash":false}]},{"StartTime":156791.0,"Objects":[{"StartTime":156791.0,"Position":9.0,"HyperDash":false},{"StartTime":156851.0,"Position":37.5505524,"HyperDash":false},{"StartTime":156948.0,"Position":77.09072,"HyperDash":false}]},{"StartTime":157107.0,"Objects":[{"StartTime":157107.0,"Position":270.0,"HyperDash":false},{"StartTime":157176.0,"Position":221.1834,"HyperDash":false},{"StartTime":157246.0,"Position":202.467163,"HyperDash":false},{"StartTime":157316.0,"Position":188.839691,"HyperDash":false},{"StartTime":157422.0,"Position":171.541748,"HyperDash":false}]},{"StartTime":157581.0,"Objects":[{"StartTime":157581.0,"Position":288.0,"HyperDash":false},{"StartTime":157650.0,"Position":334.9065,"HyperDash":false},{"StartTime":157720.0,"Position":351.398132,"HyperDash":false},{"StartTime":157790.0,"Position":383.620148,"HyperDash":false},{"StartTime":157896.0,"Position":385.24826,"HyperDash":false}]},{"StartTime":158054.0,"Objects":[{"StartTime":158054.0,"Position":248.0,"HyperDash":false},{"StartTime":158114.0,"Position":269.238434,"HyperDash":false},{"StartTime":158211.0,"Position":320.739136,"HyperDash":false}]},{"StartTime":158370.0,"Objects":[{"StartTime":158370.0,"Position":490.0,"HyperDash":false},{"StartTime":158439.0,"Position":483.703064,"HyperDash":false},{"StartTime":158509.0,"Position":456.281219,"HyperDash":false},{"StartTime":158579.0,"Position":428.409576,"HyperDash":false},{"StartTime":158685.0,"Position":432.63913,"HyperDash":false}]},{"StartTime":158844.0,"Objects":[{"StartTime":158844.0,"Position":467.0,"HyperDash":false},{"StartTime":158913.0,"Position":441.987579,"HyperDash":false},{"StartTime":158983.0,"Position":453.374176,"HyperDash":false},{"StartTime":159053.0,"Position":445.3904,"HyperDash":false},{"StartTime":159159.0,"Position":409.514771,"HyperDash":false}]},{"StartTime":159318.0,"Objects":[{"StartTime":159318.0,"Position":248.0,"HyperDash":false},{"StartTime":159378.0,"Position":264.964264,"HyperDash":false},{"StartTime":159475.0,"Position":321.196442,"HyperDash":false}]},{"StartTime":159633.0,"Objects":[{"StartTime":159633.0,"Position":320.0,"HyperDash":false}]},{"StartTime":160897.0,"Objects":[{"StartTime":160897.0,"Position":118.0,"HyperDash":false},{"StartTime":160997.0,"Position":109.104843,"HyperDash":false},{"StartTime":161133.0,"Position":125.327431,"HyperDash":false}]},{"StartTime":161212.0,"Objects":[{"StartTime":161212.0,"Position":146.0,"HyperDash":false},{"StartTime":161312.0,"Position":129.61203,"HyperDash":false},{"StartTime":161448.0,"Position":138.0044,"HyperDash":false}]},{"StartTime":161528.0,"Objects":[{"StartTime":161528.0,"Position":158.0,"HyperDash":false},{"StartTime":161628.0,"Position":162.38797,"HyperDash":false},{"StartTime":161764.0,"Position":165.9956,"HyperDash":false}]},{"StartTime":161844.0,"Objects":[{"StartTime":161844.0,"Position":185.0,"HyperDash":false}]},{"StartTime":162160.0,"Objects":[{"StartTime":162160.0,"Position":39.0,"HyperDash":false},{"StartTime":162220.0,"Position":18.8607216,"HyperDash":false},{"StartTime":162317.0,"Position":18.4563026,"HyperDash":false}]},{"StartTime":162475.0,"Objects":[{"StartTime":162475.0,"Position":153.0,"HyperDash":false}]},{"StartTime":162633.0,"Objects":[{"StartTime":162633.0,"Position":221.0,"HyperDash":false},{"StartTime":162693.0,"Position":242.139282,"HyperDash":false},{"StartTime":162790.0,"Position":241.543686,"HyperDash":false}]},{"StartTime":162949.0,"Objects":[{"StartTime":162949.0,"Position":64.0,"HyperDash":false},{"StartTime":163009.0,"Position":77.95903,"HyperDash":false},{"StartTime":163106.0,"Position":85.14159,"HyperDash":false}]},{"StartTime":163265.0,"Objects":[{"StartTime":163265.0,"Position":244.0,"HyperDash":false},{"StartTime":163334.0,"Position":288.4246,"HyperDash":false},{"StartTime":163404.0,"Position":325.0935,"HyperDash":false},{"StartTime":163474.0,"Position":362.511841,"HyperDash":false},{"StartTime":163580.0,"Position":369.3507,"HyperDash":false}]},{"StartTime":163739.0,"Objects":[{"StartTime":163739.0,"Position":322.0,"HyperDash":false}]},{"StartTime":163896.0,"Objects":[{"StartTime":163896.0,"Position":282.0,"HyperDash":false}]},{"StartTime":164054.0,"Objects":[{"StartTime":164054.0,"Position":419.0,"HyperDash":false},{"StartTime":164114.0,"Position":421.511932,"HyperDash":false},{"StartTime":164211.0,"Position":405.982758,"HyperDash":false}]},{"StartTime":164370.0,"Objects":[{"StartTime":164370.0,"Position":214.0,"HyperDash":false},{"StartTime":164430.0,"Position":211.248978,"HyperDash":false},{"StartTime":164527.0,"Position":202.042938,"HyperDash":false}]},{"StartTime":164686.0,"Objects":[{"StartTime":164686.0,"Position":295.0,"HyperDash":false},{"StartTime":164746.0,"Position":278.1207,"HyperDash":false},{"StartTime":164843.0,"Position":214.459625,"HyperDash":false}]},{"StartTime":165002.0,"Objects":[{"StartTime":165002.0,"Position":305.0,"HyperDash":false}]},{"StartTime":165160.0,"Objects":[{"StartTime":165160.0,"Position":209.0,"HyperDash":false},{"StartTime":165220.0,"Position":165.120712,"HyperDash":false},{"StartTime":165317.0,"Position":128.459641,"HyperDash":false}]},{"StartTime":165475.0,"Objects":[{"StartTime":165475.0,"Position":294.0,"HyperDash":false},{"StartTime":165535.0,"Position":279.1207,"HyperDash":false},{"StartTime":165632.0,"Position":213.459625,"HyperDash":false}]},{"StartTime":165791.0,"Objects":[{"StartTime":165791.0,"Position":66.0,"HyperDash":false},{"StartTime":165860.0,"Position":45.40761,"HyperDash":false},{"StartTime":165930.0,"Position":38.5398445,"HyperDash":false},{"StartTime":166000.0,"Position":12.8475342,"HyperDash":false},{"StartTime":166106.0,"Position":27.3485184,"HyperDash":false}]},{"StartTime":166265.0,"Objects":[{"StartTime":166265.0,"Position":226.0,"HyperDash":false}]},{"StartTime":166423.0,"Objects":[{"StartTime":166423.0,"Position":144.0,"HyperDash":false}]},{"StartTime":166581.0,"Objects":[{"StartTime":166581.0,"Position":244.0,"HyperDash":false},{"StartTime":166641.0,"Position":283.822266,"HyperDash":false},{"StartTime":166738.0,"Position":323.677734,"HyperDash":false}]},{"StartTime":166896.0,"Objects":[{"StartTime":166896.0,"Position":163.0,"HyperDash":false},{"StartTime":166956.0,"Position":176.896317,"HyperDash":false},{"StartTime":167053.0,"Position":242.864777,"HyperDash":false}]},{"StartTime":167212.0,"Objects":[{"StartTime":167212.0,"Position":374.0,"HyperDash":false},{"StartTime":167272.0,"Position":398.505157,"HyperDash":false},{"StartTime":167369.0,"Position":404.8981,"HyperDash":false}]},{"StartTime":167528.0,"Objects":[{"StartTime":167528.0,"Position":364.0,"HyperDash":false}]},{"StartTime":167686.0,"Objects":[{"StartTime":167686.0,"Position":490.0,"HyperDash":false},{"StartTime":167746.0,"Position":467.494843,"HyperDash":false},{"StartTime":167843.0,"Position":459.101929,"HyperDash":false}]},{"StartTime":168002.0,"Objects":[{"StartTime":168002.0,"Position":269.0,"HyperDash":false},{"StartTime":168062.0,"Position":244.4816,"HyperDash":false},{"StartTime":168159.0,"Position":239.105927,"HyperDash":false}]},{"StartTime":168317.0,"Objects":[{"StartTime":168317.0,"Position":74.0,"HyperDash":false},{"StartTime":168386.0,"Position":97.2507858,"HyperDash":false},{"StartTime":168456.0,"Position":150.689621,"HyperDash":false},{"StartTime":168526.0,"Position":165.044,"HyperDash":false},{"StartTime":168632.0,"Position":217.447281,"HyperDash":false}]},{"StartTime":168791.0,"Objects":[{"StartTime":168791.0,"Position":258.0,"HyperDash":false},{"StartTime":168851.0,"Position":231.961609,"HyperDash":false},{"StartTime":168948.0,"Position":228.157639,"HyperDash":false}]},{"StartTime":169107.0,"Objects":[{"StartTime":169107.0,"Position":85.0,"HyperDash":false},{"StartTime":169167.0,"Position":90.51432,"HyperDash":false},{"StartTime":169264.0,"Position":69.0959244,"HyperDash":false}]},{"StartTime":169423.0,"Objects":[{"StartTime":169423.0,"Position":233.0,"HyperDash":false},{"StartTime":169483.0,"Position":208.961609,"HyperDash":false},{"StartTime":169580.0,"Position":203.157639,"HyperDash":false}]},{"StartTime":169739.0,"Objects":[{"StartTime":169739.0,"Position":296.0,"HyperDash":false},{"StartTime":169799.0,"Position":315.6617,"HyperDash":false},{"StartTime":169896.0,"Position":377.655518,"HyperDash":false}]},{"StartTime":170054.0,"Objects":[{"StartTime":170054.0,"Position":224.0,"HyperDash":false}]},{"StartTime":170212.0,"Objects":[{"StartTime":170212.0,"Position":331.0,"HyperDash":false},{"StartTime":170272.0,"Position":367.6617,"HyperDash":false},{"StartTime":170369.0,"Position":412.655518,"HyperDash":false}]},{"StartTime":170528.0,"Objects":[{"StartTime":170528.0,"Position":238.0,"HyperDash":false},{"StartTime":170588.0,"Position":203.307648,"HyperDash":false},{"StartTime":170685.0,"Position":156.157562,"HyperDash":false}]},{"StartTime":170844.0,"Objects":[{"StartTime":170844.0,"Position":95.0,"HyperDash":false}]},{"StartTime":171002.0,"Objects":[{"StartTime":171002.0,"Position":92.0,"HyperDash":false},{"StartTime":171062.0,"Position":123.152824,"HyperDash":false},{"StartTime":171159.0,"Position":131.076691,"HyperDash":false}]},{"StartTime":171317.0,"Objects":[{"StartTime":171317.0,"Position":243.0,"HyperDash":false}]},{"StartTime":171475.0,"Objects":[{"StartTime":171475.0,"Position":218.0,"HyperDash":false}]},{"StartTime":171633.0,"Objects":[{"StartTime":171633.0,"Position":237.0,"HyperDash":false}]},{"StartTime":171791.0,"Objects":[{"StartTime":171791.0,"Position":212.0,"HyperDash":false}]},{"StartTime":171949.0,"Objects":[{"StartTime":171949.0,"Position":328.0,"HyperDash":false},{"StartTime":172009.0,"Position":361.2498,"HyperDash":false},{"StartTime":172106.0,"Position":407.426453,"HyperDash":false}]},{"StartTime":172265.0,"Objects":[{"StartTime":172265.0,"Position":447.0,"HyperDash":false},{"StartTime":172325.0,"Position":412.0013,"HyperDash":false},{"StartTime":172422.0,"Position":404.674683,"HyperDash":false}]},{"StartTime":172581.0,"Objects":[{"StartTime":172581.0,"Position":349.0,"HyperDash":false}]},{"StartTime":172739.0,"Objects":[{"StartTime":172739.0,"Position":337.0,"HyperDash":false},{"StartTime":172799.0,"Position":372.2498,"HyperDash":false},{"StartTime":172896.0,"Position":416.426453,"HyperDash":false}]},{"StartTime":173054.0,"Objects":[{"StartTime":173054.0,"Position":335.0,"HyperDash":false},{"StartTime":173114.0,"Position":295.0204,"HyperDash":false},{"StartTime":173211.0,"Position":255.685944,"HyperDash":false}]},{"StartTime":173370.0,"Objects":[{"StartTime":173370.0,"Position":195.0,"HyperDash":false},{"StartTime":173439.0,"Position":205.158081,"HyperDash":false},{"StartTime":173509.0,"Position":223.41394,"HyperDash":false},{"StartTime":173579.0,"Position":259.617249,"HyperDash":false},{"StartTime":173685.0,"Position":242.5926,"HyperDash":false}]},{"StartTime":173844.0,"Objects":[{"StartTime":173844.0,"Position":66.0,"HyperDash":false}]},{"StartTime":174002.0,"Objects":[{"StartTime":174002.0,"Position":125.0,"HyperDash":false},{"StartTime":174062.0,"Position":137.888153,"HyperDash":false},{"StartTime":174159.0,"Position":195.446823,"HyperDash":false}]},{"StartTime":174317.0,"Objects":[{"StartTime":174317.0,"Position":276.0,"HyperDash":false}]},{"StartTime":174475.0,"Objects":[{"StartTime":174475.0,"Position":104.0,"HyperDash":false},{"StartTime":174535.0,"Position":70.345,"HyperDash":false},{"StartTime":174632.0,"Position":24.6388855,"HyperDash":false}]},{"StartTime":174791.0,"Objects":[{"StartTime":174791.0,"Position":178.0,"HyperDash":false},{"StartTime":174851.0,"Position":167.60582,"HyperDash":false},{"StartTime":174948.0,"Position":161.976974,"HyperDash":false}]},{"StartTime":175107.0,"Objects":[{"StartTime":175107.0,"Position":293.0,"HyperDash":false}]},{"StartTime":175265.0,"Objects":[{"StartTime":175265.0,"Position":335.0,"HyperDash":false},{"StartTime":175325.0,"Position":377.0415,"HyperDash":false},{"StartTime":175422.0,"Position":413.356354,"HyperDash":false}]},{"StartTime":175581.0,"Objects":[{"StartTime":175581.0,"Position":366.0,"HyperDash":false},{"StartTime":175641.0,"Position":334.5056,"HyperDash":false},{"StartTime":175738.0,"Position":287.8307,"HyperDash":false}]},{"StartTime":175896.0,"Objects":[{"StartTime":175896.0,"Position":490.0,"HyperDash":false},{"StartTime":175965.0,"Position":458.363129,"HyperDash":false},{"StartTime":176035.0,"Position":474.112274,"HyperDash":false},{"StartTime":176105.0,"Position":466.752441,"HyperDash":false},{"StartTime":176211.0,"Position":444.10556,"HyperDash":false}]},{"StartTime":176370.0,"Objects":[{"StartTime":176370.0,"Position":330.0,"HyperDash":false}]},{"StartTime":176528.0,"Objects":[{"StartTime":176528.0,"Position":312.0,"HyperDash":false},{"StartTime":176588.0,"Position":298.540161,"HyperDash":false},{"StartTime":176685.0,"Position":294.0207,"HyperDash":false}]},{"StartTime":176844.0,"Objects":[{"StartTime":176844.0,"Position":175.0,"HyperDash":false}]},{"StartTime":177002.0,"Objects":[{"StartTime":177002.0,"Position":181.0,"HyperDash":false},{"StartTime":177062.0,"Position":170.459839,"HyperDash":false},{"StartTime":177159.0,"Position":198.979309,"HyperDash":false}]},{"StartTime":177317.0,"Objects":[{"StartTime":177317.0,"Position":318.0,"HyperDash":false},{"StartTime":177386.0,"Position":284.9978,"HyperDash":false},{"StartTime":177456.0,"Position":269.6918,"HyperDash":false},{"StartTime":177526.0,"Position":237.63176,"HyperDash":false},{"StartTime":177632.0,"Position":186.8728,"HyperDash":false}]},{"StartTime":177791.0,"Objects":[{"StartTime":177791.0,"Position":370.0,"HyperDash":false},{"StartTime":177851.0,"Position":406.333221,"HyperDash":false},{"StartTime":177948.0,"Position":450.520935,"HyperDash":false}]},{"StartTime":178107.0,"Objects":[{"StartTime":178107.0,"Position":325.0,"HyperDash":false},{"StartTime":178167.0,"Position":339.2036,"HyperDash":false},{"StartTime":178264.0,"Position":405.357574,"HyperDash":false}]},{"StartTime":178423.0,"Objects":[{"StartTime":178423.0,"Position":302.0,"HyperDash":false},{"StartTime":178483.0,"Position":291.456818,"HyperDash":false},{"StartTime":178580.0,"Position":279.113953,"HyperDash":false}]},{"StartTime":178739.0,"Objects":[{"StartTime":178739.0,"Position":173.0,"HyperDash":false},{"StartTime":178799.0,"Position":164.273453,"HyperDash":false},{"StartTime":178896.0,"Position":150.110962,"HyperDash":false}]},{"StartTime":179054.0,"Objects":[{"StartTime":179054.0,"Position":203.0,"HyperDash":false}]},{"StartTime":179212.0,"Objects":[{"StartTime":179212.0,"Position":58.0,"HyperDash":false},{"StartTime":179272.0,"Position":71.98529,"HyperDash":false},{"StartTime":179369.0,"Position":81.024,"HyperDash":false}]},{"StartTime":179528.0,"Objects":[{"StartTime":179528.0,"Position":266.0,"HyperDash":false},{"StartTime":179588.0,"Position":270.755371,"HyperDash":false},{"StartTime":179685.0,"Position":243.9513,"HyperDash":false}]},{"StartTime":179844.0,"Objects":[{"StartTime":179844.0,"Position":379.0,"HyperDash":false},{"StartTime":179904.0,"Position":407.036346,"HyperDash":false},{"StartTime":180001.0,"Position":462.828461,"HyperDash":false}]},{"StartTime":180160.0,"Objects":[{"StartTime":180160.0,"Position":252.0,"HyperDash":false},{"StartTime":180220.0,"Position":217.963638,"HyperDash":false},{"StartTime":180317.0,"Position":168.171539,"HyperDash":false}]},{"StartTime":180475.0,"Objects":[{"StartTime":180475.0,"Position":385.0,"HyperDash":false},{"StartTime":180535.0,"Position":434.036346,"HyperDash":false},{"StartTime":180632.0,"Position":468.828461,"HyperDash":false}]},{"StartTime":180791.0,"Objects":[{"StartTime":180791.0,"Position":258.0,"HyperDash":false},{"StartTime":180851.0,"Position":241.963638,"HyperDash":false},{"StartTime":180948.0,"Position":174.171539,"HyperDash":false}]},{"StartTime":181107.0,"Objects":[{"StartTime":181107.0,"Position":295.0,"HyperDash":false}]},{"StartTime":181265.0,"Objects":[{"StartTime":181265.0,"Position":334.0,"HyperDash":false}]},{"StartTime":181423.0,"Objects":[{"StartTime":181423.0,"Position":306.0,"HyperDash":false}]},{"StartTime":181581.0,"Objects":[{"StartTime":181581.0,"Position":347.0,"HyperDash":false}]},{"StartTime":181739.0,"Objects":[{"StartTime":181739.0,"Position":317.0,"HyperDash":false},{"StartTime":181799.0,"Position":323.0203,"HyperDash":false},{"StartTime":181896.0,"Position":322.286469,"HyperDash":false}]},{"StartTime":182054.0,"Objects":[{"StartTime":182054.0,"Position":237.0,"HyperDash":false}]},{"StartTime":182212.0,"Objects":[{"StartTime":182212.0,"Position":440.0,"HyperDash":false}]},{"StartTime":182370.0,"Objects":[{"StartTime":182370.0,"Position":225.0,"HyperDash":false}]},{"StartTime":183476.0,"Objects":[{"StartTime":183476.0,"Position":173.0,"HyperDash":false}]},{"StartTime":184265.0,"Objects":[{"StartTime":184265.0,"Position":173.0,"HyperDash":false},{"StartTime":184365.0,"Position":228.359283,"HyperDash":false},{"StartTime":184501.0,"Position":263.5279,"HyperDash":false}]},{"StartTime":184581.0,"Objects":[{"StartTime":184581.0,"Position":266.0,"HyperDash":false},{"StartTime":184641.0,"Position":259.91507,"HyperDash":false},{"StartTime":184738.0,"Position":205.594452,"HyperDash":false}]},{"StartTime":184818.0,"Objects":[{"StartTime":184818.0,"Position":180.0,"HyperDash":false}]},{"StartTime":184897.0,"Objects":[{"StartTime":184897.0,"Position":180.0,"HyperDash":false}]},{"StartTime":186002.0,"Objects":[{"StartTime":186002.0,"Position":402.0,"HyperDash":false}]},{"StartTime":186791.0,"Objects":[{"StartTime":186791.0,"Position":402.0,"HyperDash":false},{"StartTime":186891.0,"Position":364.639435,"HyperDash":false},{"StartTime":187027.0,"Position":311.469055,"HyperDash":false}]},{"StartTime":187107.0,"Objects":[{"StartTime":187107.0,"Position":309.0,"HyperDash":false},{"StartTime":187167.0,"Position":345.0628,"HyperDash":false},{"StartTime":187264.0,"Position":369.347656,"HyperDash":false}]},{"StartTime":187344.0,"Objects":[{"StartTime":187344.0,"Position":432.0,"HyperDash":false}]},{"StartTime":187423.0,"Objects":[{"StartTime":187423.0,"Position":432.0,"HyperDash":false},{"StartTime":187483.0,"Position":431.965149,"HyperDash":false},{"StartTime":187580.0,"Position":414.448761,"HyperDash":false}]},{"StartTime":187739.0,"Objects":[{"StartTime":187739.0,"Position":460.0,"HyperDash":false}]},{"StartTime":187897.0,"Objects":[{"StartTime":187897.0,"Position":270.0,"HyperDash":false},{"StartTime":187957.0,"Position":264.1196,"HyperDash":false},{"StartTime":188054.0,"Position":252.031967,"HyperDash":false}]},{"StartTime":188212.0,"Objects":[{"StartTime":188212.0,"Position":345.0,"HyperDash":false},{"StartTime":188272.0,"Position":362.0573,"HyperDash":false},{"StartTime":188369.0,"Position":362.009369,"HyperDash":false}]},{"StartTime":188528.0,"Objects":[{"StartTime":188528.0,"Position":223.0,"HyperDash":false},{"StartTime":188597.0,"Position":173.194031,"HyperDash":false},{"StartTime":188667.0,"Position":151.2194,"HyperDash":false},{"StartTime":188737.0,"Position":127.234268,"HyperDash":false},{"StartTime":188843.0,"Position":90.09637,"HyperDash":false}]},{"StartTime":189002.0,"Objects":[{"StartTime":189002.0,"Position":195.0,"HyperDash":false},{"StartTime":189062.0,"Position":228.972458,"HyperDash":false},{"StartTime":189159.0,"Position":277.218262,"HyperDash":false}]},{"StartTime":189318.0,"Objects":[{"StartTime":189318.0,"Position":315.0,"HyperDash":false},{"StartTime":189378.0,"Position":267.027557,"HyperDash":false},{"StartTime":189475.0,"Position":232.781723,"HyperDash":false}]},{"StartTime":189633.0,"Objects":[{"StartTime":189633.0,"Position":426.0,"HyperDash":false},{"StartTime":189693.0,"Position":416.778778,"HyperDash":false},{"StartTime":189790.0,"Position":397.035126,"HyperDash":false}]},{"StartTime":189949.0,"Objects":[{"StartTime":189949.0,"Position":370.0,"HyperDash":false},{"StartTime":190018.0,"Position":378.220642,"HyperDash":false},{"StartTime":190088.0,"Position":331.990845,"HyperDash":false},{"StartTime":190158.0,"Position":340.3975,"HyperDash":false},{"StartTime":190264.0,"Position":316.019745,"HyperDash":false}]},{"StartTime":190423.0,"Objects":[{"StartTime":190423.0,"Position":190.0,"HyperDash":false},{"StartTime":190483.0,"Position":164.497772,"HyperDash":false},{"StartTime":190580.0,"Position":110.287689,"HyperDash":false}]},{"StartTime":190739.0,"Objects":[{"StartTime":190739.0,"Position":221.0,"HyperDash":false},{"StartTime":190799.0,"Position":269.972839,"HyperDash":false},{"StartTime":190896.0,"Position":300.6956,"HyperDash":false}]},{"StartTime":191054.0,"Objects":[{"StartTime":191054.0,"Position":189.0,"HyperDash":false}]},{"StartTime":191212.0,"Objects":[{"StartTime":191212.0,"Position":378.0,"HyperDash":false},{"StartTime":191281.0,"Position":369.800842,"HyperDash":false},{"StartTime":191351.0,"Position":353.057861,"HyperDash":false},{"StartTime":191421.0,"Position":343.24173,"HyperDash":false},{"StartTime":191527.0,"Position":338.951782,"HyperDash":false}]},{"StartTime":191686.0,"Objects":[{"StartTime":191686.0,"Position":465.0,"HyperDash":false}]},{"StartTime":191844.0,"Objects":[{"StartTime":191844.0,"Position":363.0,"HyperDash":false},{"StartTime":191904.0,"Position":354.1089,"HyperDash":false},{"StartTime":192001.0,"Position":353.0403,"HyperDash":false}]},{"StartTime":192160.0,"Objects":[{"StartTime":192160.0,"Position":421.0,"HyperDash":false}]},{"StartTime":192318.0,"Objects":[{"StartTime":192318.0,"Position":421.0,"HyperDash":false}]},{"StartTime":192791.0,"Objects":[{"StartTime":192791.0,"Position":221.0,"HyperDash":false},{"StartTime":192869.0,"Position":265.146576,"HyperDash":false},{"StartTime":192948.0,"Position":280.86972,"HyperDash":false},{"StartTime":193027.0,"Position":309.011475,"HyperDash":false},{"StartTime":193106.0,"Position":330.6507,"HyperDash":false},{"StartTime":193156.0,"Position":343.5075,"HyperDash":false},{"StartTime":193206.0,"Position":362.759338,"HyperDash":false},{"StartTime":193256.0,"Position":379.106567,"HyperDash":false},{"StartTime":193343.0,"Position":429.869537,"HyperDash":false}]},{"StartTime":193423.0,"Objects":[{"StartTime":193423.0,"Position":439.0,"HyperDash":false},{"StartTime":193501.0,"Position":382.117065,"HyperDash":false},{"StartTime":193580.0,"Position":381.885132,"HyperDash":false},{"StartTime":193659.0,"Position":348.001556,"HyperDash":false},{"StartTime":193738.0,"Position":325.240662,"HyperDash":false},{"StartTime":193788.0,"Position":320.365143,"HyperDash":false},{"StartTime":193838.0,"Position":291.773438,"HyperDash":false},{"StartTime":193888.0,"Position":291.451782,"HyperDash":false},{"StartTime":193975.0,"Position":231.177582,"HyperDash":false}]},{"StartTime":194054.0,"Objects":[{"StartTime":194054.0,"Position":238.0,"HyperDash":false},{"StartTime":194132.0,"Position":276.079041,"HyperDash":false},{"StartTime":194211.0,"Position":303.6013,"HyperDash":false},{"StartTime":194290.0,"Position":315.4301,"HyperDash":false},{"StartTime":194369.0,"Position":346.0823,"HyperDash":false},{"StartTime":194419.0,"Position":358.557251,"HyperDash":false},{"StartTime":194469.0,"Position":382.922455,"HyperDash":false},{"StartTime":194519.0,"Position":399.497833,"HyperDash":false},{"StartTime":194606.0,"Position":445.572784,"HyperDash":false}]},{"StartTime":194686.0,"Objects":[{"StartTime":194686.0,"Position":452.0,"HyperDash":false},{"StartTime":194764.0,"Position":418.1171,"HyperDash":false},{"StartTime":194843.0,"Position":389.886749,"HyperDash":false},{"StartTime":194922.0,"Position":346.033173,"HyperDash":false},{"StartTime":195001.0,"Position":338.394043,"HyperDash":false},{"StartTime":195051.0,"Position":308.603027,"HyperDash":false},{"StartTime":195101.0,"Position":307.0379,"HyperDash":false},{"StartTime":195151.0,"Position":300.7087,"HyperDash":false},{"StartTime":195238.0,"Position":244.424088,"HyperDash":false}]},{"StartTime":195317.0,"Objects":[{"StartTime":195317.0,"Position":250.0,"HyperDash":false},{"StartTime":195395.0,"Position":280.0489,"HyperDash":false},{"StartTime":195474.0,"Position":338.609467,"HyperDash":false},{"StartTime":195553.0,"Position":354.8494,"HyperDash":false},{"StartTime":195632.0,"Position":358.40744,"HyperDash":false},{"StartTime":195682.0,"Position":381.576569,"HyperDash":false},{"StartTime":195732.0,"Position":402.7527,"HyperDash":false},{"StartTime":195782.0,"Position":430.257,"HyperDash":false},{"StartTime":195869.0,"Position":457.292328,"HyperDash":false}]},{"StartTime":195949.0,"Objects":[{"StartTime":195949.0,"Position":461.0,"HyperDash":false},{"StartTime":196049.0,"Position":446.167847,"HyperDash":false},{"StartTime":196185.0,"Position":438.391785,"HyperDash":false}]},{"StartTime":196265.0,"Objects":[{"StartTime":196265.0,"Position":411.0,"HyperDash":false},{"StartTime":196325.0,"Position":383.214722,"HyperDash":false},{"StartTime":196422.0,"Position":343.5428,"HyperDash":false}]},{"StartTime":196581.0,"Objects":[{"StartTime":196581.0,"Position":136.0,"HyperDash":false}]},{"StartTime":196739.0,"Objects":[{"StartTime":196739.0,"Position":314.0,"HyperDash":false}]},{"StartTime":196897.0,"Objects":[{"StartTime":196897.0,"Position":120.0,"HyperDash":false}]},{"StartTime":197055.0,"Objects":[{"StartTime":197055.0,"Position":298.0,"HyperDash":false}]},{"StartTime":197212.0,"Objects":[{"StartTime":197212.0,"Position":104.0,"HyperDash":false},{"StartTime":197272.0,"Position":85.28295,"HyperDash":false},{"StartTime":197369.0,"Position":92.47838,"HyperDash":false}]},{"StartTime":197528.0,"Objects":[{"StartTime":197528.0,"Position":136.0,"HyperDash":false},{"StartTime":197588.0,"Position":176.664658,"HyperDash":false},{"StartTime":197685.0,"Position":211.9784,"HyperDash":false}]},{"StartTime":197844.0,"Objects":[{"StartTime":197844.0,"Position":384.0,"HyperDash":false}]},{"StartTime":198002.0,"Objects":[{"StartTime":198002.0,"Position":317.0,"HyperDash":false},{"StartTime":198062.0,"Position":278.335327,"HyperDash":false},{"StartTime":198159.0,"Position":241.0216,"HyperDash":false}]},{"StartTime":198318.0,"Objects":[{"StartTime":198318.0,"Position":373.0,"HyperDash":false},{"StartTime":198378.0,"Position":422.153229,"HyperDash":false},{"StartTime":198475.0,"Position":448.229248,"HyperDash":false}]},{"StartTime":198633.0,"Objects":[{"StartTime":198633.0,"Position":436.0,"HyperDash":false},{"StartTime":198693.0,"Position":422.984,"HyperDash":false},{"StartTime":198790.0,"Position":412.4418,"HyperDash":false}]},{"StartTime":198949.0,"Objects":[{"StartTime":198949.0,"Position":264.0,"HyperDash":false},{"StartTime":199009.0,"Position":276.016,"HyperDash":false},{"StartTime":199106.0,"Position":287.5582,"HyperDash":false}]},{"StartTime":199265.0,"Objects":[{"StartTime":199265.0,"Position":242.0,"HyperDash":false}]},{"StartTime":199423.0,"Objects":[{"StartTime":199423.0,"Position":414.0,"HyperDash":false},{"StartTime":199483.0,"Position":411.984,"HyperDash":false},{"StartTime":199580.0,"Position":390.4418,"HyperDash":false}]},{"StartTime":199739.0,"Objects":[{"StartTime":199739.0,"Position":214.0,"HyperDash":false},{"StartTime":199799.0,"Position":212.821,"HyperDash":false},{"StartTime":199896.0,"Position":190.064774,"HyperDash":false}]},{"StartTime":200054.0,"Objects":[{"StartTime":200054.0,"Position":38.0,"HyperDash":false},{"StartTime":200114.0,"Position":47.9374542,"HyperDash":false},{"StartTime":200211.0,"Position":48.30301,"HyperDash":false}]},{"StartTime":200370.0,"Objects":[{"StartTime":200370.0,"Position":86.0,"HyperDash":false},{"StartTime":200430.0,"Position":89.79463,"HyperDash":false},{"StartTime":200527.0,"Position":95.92929,"HyperDash":false}]},{"StartTime":200686.0,"Objects":[{"StartTime":200686.0,"Position":48.0,"HyperDash":false},{"StartTime":200746.0,"Position":62.9374542,"HyperDash":false},{"StartTime":200843.0,"Position":58.30301,"HyperDash":false}]},{"StartTime":201002.0,"Objects":[{"StartTime":201002.0,"Position":96.0,"HyperDash":false},{"StartTime":201062.0,"Position":89.79463,"HyperDash":false},{"StartTime":201159.0,"Position":105.929291,"HyperDash":false}]},{"StartTime":201318.0,"Objects":[{"StartTime":201318.0,"Position":223.0,"HyperDash":false}]},{"StartTime":201476.0,"Objects":[{"StartTime":201476.0,"Position":211.0,"HyperDash":false}]},{"StartTime":201633.0,"Objects":[{"StartTime":201633.0,"Position":239.0,"HyperDash":false}]},{"StartTime":201791.0,"Objects":[{"StartTime":201791.0,"Position":227.0,"HyperDash":false}]},{"StartTime":201949.0,"Objects":[{"StartTime":201949.0,"Position":255.0,"HyperDash":false},{"StartTime":202009.0,"Position":263.68692,"HyperDash":false},{"StartTime":202106.0,"Position":243.714127,"HyperDash":false}]},{"StartTime":202265.0,"Objects":[{"StartTime":202265.0,"Position":218.0,"HyperDash":false}]},{"StartTime":202423.0,"Objects":[{"StartTime":202423.0,"Position":309.0,"HyperDash":false}]},{"StartTime":202581.0,"Objects":[{"StartTime":202581.0,"Position":328.0,"HyperDash":false}]},{"StartTime":203528.0,"Objects":[{"StartTime":203528.0,"Position":459.0,"HyperDash":false},{"StartTime":203588.0,"Position":448.977936,"HyperDash":false},{"StartTime":203685.0,"Position":398.758942,"HyperDash":false}]},{"StartTime":203844.0,"Objects":[{"StartTime":203844.0,"Position":305.0,"HyperDash":false}]},{"StartTime":204002.0,"Objects":[{"StartTime":204002.0,"Position":305.0,"HyperDash":false}]},{"StartTime":204160.0,"Objects":[{"StartTime":204160.0,"Position":264.0,"HyperDash":false}]},{"StartTime":204318.0,"Objects":[{"StartTime":204318.0,"Position":264.0,"HyperDash":false}]},{"StartTime":204476.0,"Objects":[{"StartTime":204476.0,"Position":210.0,"HyperDash":false}]},{"StartTime":204633.0,"Objects":[{"StartTime":204633.0,"Position":210.0,"HyperDash":false},{"StartTime":204693.0,"Position":211.007629,"HyperDash":false},{"StartTime":204790.0,"Position":204.786621,"HyperDash":false}]},{"StartTime":204949.0,"Objects":[{"StartTime":204949.0,"Position":62.0,"HyperDash":false},{"StartTime":205009.0,"Position":74.99237,"HyperDash":false},{"StartTime":205106.0,"Position":67.21338,"HyperDash":false}]},{"StartTime":205265.0,"Objects":[{"StartTime":205265.0,"Position":192.0,"HyperDash":false},{"StartTime":205325.0,"Position":214.8626,"HyperDash":false},{"StartTime":205422.0,"Position":262.080139,"HyperDash":false}]},{"StartTime":205581.0,"Objects":[{"StartTime":205581.0,"Position":398.0,"HyperDash":false},{"StartTime":205641.0,"Position":358.8581,"HyperDash":false},{"StartTime":205738.0,"Position":327.74704,"HyperDash":false}]},{"StartTime":205897.0,"Objects":[{"StartTime":205897.0,"Position":407.0,"HyperDash":false}]},{"StartTime":206054.0,"Objects":[{"StartTime":206054.0,"Position":493.0,"HyperDash":false},{"StartTime":206114.0,"Position":493.732544,"HyperDash":false},{"StartTime":206211.0,"Position":478.1135,"HyperDash":false}]},{"StartTime":206370.0,"Objects":[{"StartTime":206370.0,"Position":311.0,"HyperDash":false},{"StartTime":206430.0,"Position":296.786255,"HyperDash":false},{"StartTime":206527.0,"Position":239.579437,"HyperDash":false}]},{"StartTime":206686.0,"Objects":[{"StartTime":206686.0,"Position":76.0,"HyperDash":false}]},{"StartTime":206844.0,"Objects":[{"StartTime":206844.0,"Position":76.0,"HyperDash":false}]},{"StartTime":207002.0,"Objects":[{"StartTime":207002.0,"Position":186.0,"HyperDash":false}]},{"StartTime":207160.0,"Objects":[{"StartTime":207160.0,"Position":186.0,"HyperDash":false},{"StartTime":207220.0,"Position":211.157623,"HyperDash":false},{"StartTime":207317.0,"Position":257.432068,"HyperDash":false}]},{"StartTime":207476.0,"Objects":[{"StartTime":207476.0,"Position":102.0,"HyperDash":false},{"StartTime":207545.0,"Position":104.631119,"HyperDash":false},{"StartTime":207615.0,"Position":116.053741,"HyperDash":false},{"StartTime":207685.0,"Position":129.854782,"HyperDash":false},{"StartTime":207791.0,"Position":145.055069,"HyperDash":false}]},{"StartTime":207949.0,"Objects":[{"StartTime":207949.0,"Position":73.0,"HyperDash":false}]},{"StartTime":208107.0,"Objects":[{"StartTime":208107.0,"Position":73.0,"HyperDash":false}]},{"StartTime":208265.0,"Objects":[{"StartTime":208265.0,"Position":188.0,"HyperDash":false}]},{"StartTime":208423.0,"Objects":[{"StartTime":208423.0,"Position":188.0,"HyperDash":false},{"StartTime":208483.0,"Position":197.04393,"HyperDash":false},{"StartTime":208580.0,"Position":259.303467,"HyperDash":false}]},{"StartTime":208739.0,"Objects":[{"StartTime":208739.0,"Position":356.0,"HyperDash":false}]},{"StartTime":208897.0,"Objects":[{"StartTime":208897.0,"Position":428.0,"HyperDash":false},{"StartTime":208957.0,"Position":429.1922,"HyperDash":false},{"StartTime":209054.0,"Position":459.666473,"HyperDash":false}]},{"StartTime":209212.0,"Objects":[{"StartTime":209212.0,"Position":320.0,"HyperDash":false}]},{"StartTime":209370.0,"Objects":[{"StartTime":209370.0,"Position":320.0,"HyperDash":false}]},{"StartTime":209528.0,"Objects":[{"StartTime":209528.0,"Position":347.0,"HyperDash":false}]},{"StartTime":209686.0,"Objects":[{"StartTime":209686.0,"Position":347.0,"HyperDash":false}]},{"StartTime":209844.0,"Objects":[{"StartTime":209844.0,"Position":228.0,"HyperDash":false}]},{"StartTime":210002.0,"Objects":[{"StartTime":210002.0,"Position":135.0,"HyperDash":false},{"StartTime":210071.0,"Position":121.854248,"HyperDash":false},{"StartTime":210141.0,"Position":131.1977,"HyperDash":false},{"StartTime":210211.0,"Position":101.2941,"HyperDash":false},{"StartTime":210317.0,"Position":107.741356,"HyperDash":false}]},{"StartTime":210476.0,"Objects":[{"StartTime":210476.0,"Position":226.0,"HyperDash":false}]},{"StartTime":210633.0,"Objects":[{"StartTime":210633.0,"Position":226.0,"HyperDash":false}]},{"StartTime":210791.0,"Objects":[{"StartTime":210791.0,"Position":188.0,"HyperDash":false},{"StartTime":210851.0,"Position":221.829361,"HyperDash":false},{"StartTime":210948.0,"Position":216.115952,"HyperDash":false}]},{"StartTime":211107.0,"Objects":[{"StartTime":211107.0,"Position":289.0,"HyperDash":false}]},{"StartTime":211265.0,"Objects":[{"StartTime":211265.0,"Position":289.0,"HyperDash":false}]},{"StartTime":211423.0,"Objects":[{"StartTime":211423.0,"Position":357.0,"HyperDash":false},{"StartTime":211483.0,"Position":351.170654,"HyperDash":false},{"StartTime":211580.0,"Position":328.884064,"HyperDash":false}]},{"StartTime":211739.0,"Objects":[{"StartTime":211739.0,"Position":320.0,"HyperDash":false}]},{"StartTime":211897.0,"Objects":[{"StartTime":211897.0,"Position":420.0,"HyperDash":false},{"StartTime":211966.0,"Position":438.684967,"HyperDash":false},{"StartTime":212036.0,"Position":420.642761,"HyperDash":false},{"StartTime":212106.0,"Position":454.598969,"HyperDash":false},{"StartTime":212212.0,"Position":437.382416,"HyperDash":false}]},{"StartTime":212370.0,"Objects":[{"StartTime":212370.0,"Position":330.0,"HyperDash":false}]},{"StartTime":212528.0,"Objects":[{"StartTime":212528.0,"Position":188.0,"HyperDash":false},{"StartTime":212597.0,"Position":177.5667,"HyperDash":false},{"StartTime":212667.0,"Position":199.229538,"HyperDash":false},{"StartTime":212737.0,"Position":175.06488,"HyperDash":false},{"StartTime":212843.0,"Position":205.139709,"HyperDash":false}]},{"StartTime":213002.0,"Objects":[{"StartTime":213002.0,"Position":89.0,"HyperDash":false}]},{"StartTime":213160.0,"Objects":[{"StartTime":213160.0,"Position":89.0,"HyperDash":false}]},{"StartTime":213318.0,"Objects":[{"StartTime":213318.0,"Position":205.0,"HyperDash":false},{"StartTime":213378.0,"Position":224.953186,"HyperDash":false},{"StartTime":213475.0,"Position":276.3385,"HyperDash":false}]},{"StartTime":213633.0,"Objects":[{"StartTime":213633.0,"Position":355.0,"HyperDash":false}]},{"StartTime":213791.0,"Objects":[{"StartTime":213791.0,"Position":355.0,"HyperDash":false}]},{"StartTime":213949.0,"Objects":[{"StartTime":213949.0,"Position":377.0,"HyperDash":false},{"StartTime":214009.0,"Position":374.1648,"HyperDash":false},{"StartTime":214106.0,"Position":356.636047,"HyperDash":false}]},{"StartTime":214265.0,"Objects":[{"StartTime":214265.0,"Position":229.0,"HyperDash":false},{"StartTime":214325.0,"Position":222.07782,"HyperDash":false},{"StartTime":214422.0,"Position":207.805984,"HyperDash":false}]},{"StartTime":214581.0,"Objects":[{"StartTime":214581.0,"Position":109.0,"HyperDash":false}]},{"StartTime":214739.0,"Objects":[{"StartTime":214739.0,"Position":109.0,"HyperDash":false}]},{"StartTime":214897.0,"Objects":[{"StartTime":214897.0,"Position":176.0,"HyperDash":false},{"StartTime":214957.0,"Position":219.19249,"HyperDash":false},{"StartTime":215054.0,"Position":248.6392,"HyperDash":false}]},{"StartTime":215212.0,"Objects":[{"StartTime":215212.0,"Position":343.0,"HyperDash":false}]},{"StartTime":215370.0,"Objects":[{"StartTime":215370.0,"Position":343.0,"HyperDash":false}]},{"StartTime":215528.0,"Objects":[{"StartTime":215528.0,"Position":304.0,"HyperDash":false}]},{"StartTime":215686.0,"Objects":[{"StartTime":215686.0,"Position":304.0,"HyperDash":false}]},{"StartTime":215844.0,"Objects":[{"StartTime":215844.0,"Position":425.0,"HyperDash":false},{"StartTime":215904.0,"Position":443.940369,"HyperDash":false},{"StartTime":216001.0,"Position":497.363678,"HyperDash":false}]},{"StartTime":216160.0,"Objects":[{"StartTime":216160.0,"Position":386.0,"HyperDash":false},{"StartTime":216220.0,"Position":369.1159,"HyperDash":false},{"StartTime":216317.0,"Position":313.428955,"HyperDash":false}]},{"StartTime":216476.0,"Objects":[{"StartTime":216476.0,"Position":269.0,"HyperDash":false},{"StartTime":216545.0,"Position":292.429657,"HyperDash":false},{"StartTime":216615.0,"Position":293.77887,"HyperDash":false},{"StartTime":216685.0,"Position":296.7586,"HyperDash":false},{"StartTime":216791.0,"Position":316.2445,"HyperDash":false}]},{"StartTime":216949.0,"Objects":[{"StartTime":216949.0,"Position":343.0,"HyperDash":false}]},{"StartTime":217107.0,"Objects":[{"StartTime":217107.0,"Position":192.0,"HyperDash":false},{"StartTime":217167.0,"Position":199.294876,"HyperDash":false},{"StartTime":217264.0,"Position":180.090454,"HyperDash":false}]},{"StartTime":217423.0,"Objects":[{"StartTime":217423.0,"Position":73.0,"HyperDash":false}]},{"StartTime":217581.0,"Objects":[{"StartTime":217581.0,"Position":73.0,"HyperDash":false}]},{"StartTime":217739.0,"Objects":[{"StartTime":217739.0,"Position":197.0,"HyperDash":false},{"StartTime":217808.0,"Position":242.080475,"HyperDash":false},{"StartTime":217878.0,"Position":248.160492,"HyperDash":false},{"StartTime":217948.0,"Position":291.815369,"HyperDash":false},{"StartTime":218054.0,"Position":323.144318,"HyperDash":false}]},{"StartTime":218212.0,"Objects":[{"StartTime":218212.0,"Position":194.0,"HyperDash":false}]},{"StartTime":218370.0,"Objects":[{"StartTime":218370.0,"Position":345.0,"HyperDash":false},{"StartTime":218430.0,"Position":355.6937,"HyperDash":false},{"StartTime":218527.0,"Position":419.238617,"HyperDash":false}]},{"StartTime":218686.0,"Objects":[{"StartTime":218686.0,"Position":416.0,"HyperDash":false},{"StartTime":218746.0,"Position":402.107758,"HyperDash":false},{"StartTime":218843.0,"Position":341.536041,"HyperDash":false}]},{"StartTime":219002.0,"Objects":[{"StartTime":219002.0,"Position":485.0,"HyperDash":false},{"StartTime":219071.0,"Position":454.952484,"HyperDash":false},{"StartTime":219141.0,"Position":458.110535,"HyperDash":false},{"StartTime":219211.0,"Position":430.9237,"HyperDash":false},{"StartTime":219317.0,"Position":435.739746,"HyperDash":false}]},{"StartTime":219476.0,"Objects":[{"StartTime":219476.0,"Position":339.0,"HyperDash":false}]},{"StartTime":219633.0,"Objects":[{"StartTime":219633.0,"Position":374.0,"HyperDash":false},{"StartTime":219702.0,"Position":396.047546,"HyperDash":false},{"StartTime":219772.0,"Position":388.889465,"HyperDash":false},{"StartTime":219842.0,"Position":400.076324,"HyperDash":false},{"StartTime":219948.0,"Position":423.260254,"HyperDash":false}]},{"StartTime":220107.0,"Objects":[{"StartTime":220107.0,"Position":248.0,"HyperDash":false}]},{"StartTime":220265.0,"Objects":[{"StartTime":220265.0,"Position":201.0,"HyperDash":false}]},{"StartTime":220423.0,"Objects":[{"StartTime":220423.0,"Position":201.0,"HyperDash":false}]},{"StartTime":220581.0,"Objects":[{"StartTime":220581.0,"Position":239.0,"HyperDash":false}]},{"StartTime":220739.0,"Objects":[{"StartTime":220739.0,"Position":239.0,"HyperDash":false}]},{"StartTime":220897.0,"Objects":[{"StartTime":220897.0,"Position":122.0,"HyperDash":false},{"StartTime":220957.0,"Position":106.407677,"HyperDash":false},{"StartTime":221054.0,"Position":49.1845436,"HyperDash":false}]},{"StartTime":221212.0,"Objects":[{"StartTime":221212.0,"Position":257.0,"HyperDash":false},{"StartTime":221272.0,"Position":297.787933,"HyperDash":false},{"StartTime":221369.0,"Position":329.733826,"HyperDash":false}]},{"StartTime":221528.0,"Objects":[{"StartTime":221528.0,"Position":442.0,"HyperDash":false},{"StartTime":221588.0,"Position":442.869934,"HyperDash":false},{"StartTime":221685.0,"Position":436.426361,"HyperDash":false}]},{"StartTime":221844.0,"Objects":[{"StartTime":221844.0,"Position":417.0,"HyperDash":false},{"StartTime":221904.0,"Position":411.709747,"HyperDash":false},{"StartTime":222001.0,"Position":411.0072,"HyperDash":false}]},{"StartTime":222160.0,"Objects":[{"StartTime":222160.0,"Position":336.0,"HyperDash":false},{"StartTime":222220.0,"Position":351.869934,"HyperDash":false},{"StartTime":222317.0,"Position":330.426361,"HyperDash":false}]},{"StartTime":222476.0,"Objects":[{"StartTime":222476.0,"Position":311.0,"HyperDash":false},{"StartTime":222536.0,"Position":310.709747,"HyperDash":false},{"StartTime":222633.0,"Position":305.0072,"HyperDash":false}]},{"StartTime":222791.0,"Objects":[{"StartTime":222791.0,"Position":165.0,"HyperDash":false}]},{"StartTime":222949.0,"Objects":[{"StartTime":222949.0,"Position":143.0,"HyperDash":false}]},{"StartTime":223107.0,"Objects":[{"StartTime":223107.0,"Position":156.0,"HyperDash":false}]},{"StartTime":223265.0,"Objects":[{"StartTime":223265.0,"Position":125.0,"HyperDash":false}]},{"StartTime":223423.0,"Objects":[{"StartTime":223423.0,"Position":142.0,"HyperDash":false},{"StartTime":223483.0,"Position":119.964447,"HyperDash":false},{"StartTime":223580.0,"Position":66.02364,"HyperDash":false}]},{"StartTime":223739.0,"Objects":[{"StartTime":223739.0,"Position":209.0,"HyperDash":false}]},{"StartTime":223897.0,"Objects":[{"StartTime":223897.0,"Position":3.0,"HyperDash":false}]},{"StartTime":224054.0,"Objects":[{"StartTime":224054.0,"Position":111.0,"HyperDash":false}]},{"StartTime":234160.0,"Objects":[{"StartTime":234160.0,"Position":82.0,"HyperDash":false}]},{"StartTime":234476.0,"Objects":[{"StartTime":234476.0,"Position":82.0,"HyperDash":false}]},{"StartTime":234791.0,"Objects":[{"StartTime":234791.0,"Position":82.0,"HyperDash":false}]},{"StartTime":235107.0,"Objects":[{"StartTime":235107.0,"Position":82.0,"HyperDash":false}]},{"StartTime":235423.0,"Objects":[{"StartTime":235423.0,"Position":312.0,"HyperDash":false},{"StartTime":235483.0,"Position":357.5692,"HyperDash":false},{"StartTime":235580.0,"Position":391.4958,"HyperDash":false}]},{"StartTime":235739.0,"Objects":[{"StartTime":235739.0,"Position":262.0,"HyperDash":false}]},{"StartTime":235897.0,"Objects":[{"StartTime":235897.0,"Position":170.0,"HyperDash":false},{"StartTime":235957.0,"Position":146.430771,"HyperDash":false},{"StartTime":236054.0,"Position":90.5042,"HyperDash":false}]},{"StartTime":236212.0,"Objects":[{"StartTime":236212.0,"Position":83.0,"HyperDash":false},{"StartTime":236272.0,"Position":102.111885,"HyperDash":false},{"StartTime":236369.0,"Position":108.48745,"HyperDash":false}]},{"StartTime":236528.0,"Objects":[{"StartTime":236528.0,"Position":258.0,"HyperDash":false},{"StartTime":236597.0,"Position":241.951874,"HyperDash":false},{"StartTime":236667.0,"Position":212.802032,"HyperDash":false},{"StartTime":236737.0,"Position":200.97171,"HyperDash":false},{"StartTime":236843.0,"Position":210.516815,"HyperDash":false}]},{"StartTime":237002.0,"Objects":[{"StartTime":237002.0,"Position":327.0,"HyperDash":false}]},{"StartTime":237160.0,"Objects":[{"StartTime":237160.0,"Position":170.0,"HyperDash":false}]},{"StartTime":237318.0,"Objects":[{"StartTime":237318.0,"Position":316.0,"HyperDash":false},{"StartTime":237378.0,"Position":364.7829,"HyperDash":false},{"StartTime":237475.0,"Position":397.227,"HyperDash":false}]},{"StartTime":237633.0,"Objects":[{"StartTime":237633.0,"Position":417.0,"HyperDash":false},{"StartTime":237693.0,"Position":394.217072,"HyperDash":false},{"StartTime":237790.0,"Position":335.773,"HyperDash":false}]},{"StartTime":237949.0,"Objects":[{"StartTime":237949.0,"Position":153.0,"HyperDash":false},{"StartTime":238018.0,"Position":178.837616,"HyperDash":false},{"StartTime":238088.0,"Position":163.454758,"HyperDash":false},{"StartTime":238158.0,"Position":190.438675,"HyperDash":false},{"StartTime":238264.0,"Position":188.068771,"HyperDash":false}]},{"StartTime":238423.0,"Objects":[{"StartTime":238423.0,"Position":81.0,"HyperDash":false},{"StartTime":238483.0,"Position":68.7763062,"HyperDash":false},{"StartTime":238580.0,"Position":95.3198,"HyperDash":false}]},{"StartTime":238739.0,"Objects":[{"StartTime":238739.0,"Position":277.0,"HyperDash":false},{"StartTime":238799.0,"Position":285.009674,"HyperDash":false},{"StartTime":238896.0,"Position":291.003174,"HyperDash":false}]},{"StartTime":239054.0,"Objects":[{"StartTime":239054.0,"Position":429.0,"HyperDash":false},{"StartTime":239123.0,"Position":409.879852,"HyperDash":false},{"StartTime":239193.0,"Position":394.502,"HyperDash":false},{"StartTime":239263.0,"Position":421.1194,"HyperDash":false},{"StartTime":239369.0,"Position":401.762024,"HyperDash":false}]},{"StartTime":239528.0,"Objects":[{"StartTime":239528.0,"Position":252.0,"HyperDash":false}]},{"StartTime":239686.0,"Objects":[{"StartTime":239686.0,"Position":383.0,"HyperDash":false}]},{"StartTime":239844.0,"Objects":[{"StartTime":239844.0,"Position":224.0,"HyperDash":false},{"StartTime":239904.0,"Position":248.6068,"HyperDash":false},{"StartTime":240001.0,"Position":243.923813,"HyperDash":false}]},{"StartTime":240160.0,"Objects":[{"StartTime":240160.0,"Position":282.0,"HyperDash":false},{"StartTime":240220.0,"Position":294.4477,"HyperDash":false},{"StartTime":240317.0,"Position":300.9552,"HyperDash":false}]},{"StartTime":240476.0,"Objects":[{"StartTime":240476.0,"Position":155.0,"HyperDash":false},{"StartTime":240536.0,"Position":139.565125,"HyperDash":false},{"StartTime":240633.0,"Position":75.8260956,"HyperDash":false}]},{"StartTime":240791.0,"Objects":[{"StartTime":240791.0,"Position":177.0,"HyperDash":false}]},{"StartTime":240949.0,"Objects":[{"StartTime":240949.0,"Position":285.0,"HyperDash":false},{"StartTime":241009.0,"Position":297.434875,"HyperDash":false},{"StartTime":241106.0,"Position":364.1739,"HyperDash":false}]},{"StartTime":241265.0,"Objects":[{"StartTime":241265.0,"Position":190.0,"HyperDash":false},{"StartTime":241325.0,"Position":151.565109,"HyperDash":false},{"StartTime":241422.0,"Position":110.826096,"HyperDash":true}]},{"StartTime":241581.0,"Objects":[{"StartTime":241581.0,"Position":350.0,"HyperDash":false},{"StartTime":241650.0,"Position":379.1303,"HyperDash":false},{"StartTime":241720.0,"Position":386.2259,"HyperDash":false},{"StartTime":241790.0,"Position":365.848328,"HyperDash":false},{"StartTime":241896.0,"Position":367.289581,"HyperDash":false}]},{"StartTime":242054.0,"Objects":[{"StartTime":242054.0,"Position":172.0,"HyperDash":false},{"StartTime":242114.0,"Position":207.784363,"HyperDash":false},{"StartTime":242211.0,"Position":249.567841,"HyperDash":false}]},{"StartTime":242370.0,"Objects":[{"StartTime":242370.0,"Position":94.0,"HyperDash":false},{"StartTime":242430.0,"Position":107.155533,"HyperDash":false},{"StartTime":242527.0,"Position":172.076752,"HyperDash":false}]},{"StartTime":242686.0,"Objects":[{"StartTime":242686.0,"Position":256.0,"HyperDash":false},{"StartTime":242746.0,"Position":221.664886,"HyperDash":false},{"StartTime":242843.0,"Position":177.734055,"HyperDash":false}]},{"StartTime":243002.0,"Objects":[{"StartTime":243002.0,"Position":291.0,"HyperDash":false},{"StartTime":243062.0,"Position":288.7001,"HyperDash":false},{"StartTime":243159.0,"Position":309.460449,"HyperDash":false}]},{"StartTime":243318.0,"Objects":[{"StartTime":243318.0,"Position":386.0,"HyperDash":false}]},{"StartTime":243476.0,"Objects":[{"StartTime":243476.0,"Position":225.0,"HyperDash":false},{"StartTime":243536.0,"Position":221.299881,"HyperDash":false},{"StartTime":243633.0,"Position":206.539551,"HyperDash":false}]},{"StartTime":243791.0,"Objects":[{"StartTime":243791.0,"Position":406.0,"HyperDash":false},{"StartTime":243851.0,"Position":381.939,"HyperDash":false},{"StartTime":243948.0,"Position":386.849457,"HyperDash":false}]},{"StartTime":244107.0,"Objects":[{"StartTime":244107.0,"Position":308.0,"HyperDash":false}]},{"StartTime":244265.0,"Objects":[{"StartTime":244265.0,"Position":246.0,"HyperDash":false},{"StartTime":244325.0,"Position":196.524536,"HyperDash":false},{"StartTime":244422.0,"Position":163.999634,"HyperDash":false}]},{"StartTime":244581.0,"Objects":[{"StartTime":244581.0,"Position":89.0,"HyperDash":false}]},{"StartTime":244739.0,"Objects":[{"StartTime":244739.0,"Position":89.0,"HyperDash":false}]},{"StartTime":244897.0,"Objects":[{"StartTime":244897.0,"Position":242.0,"HyperDash":false},{"StartTime":244957.0,"Position":212.524536,"HyperDash":false},{"StartTime":245054.0,"Position":159.999634,"HyperDash":false}]},{"StartTime":245212.0,"Objects":[{"StartTime":245212.0,"Position":189.0,"HyperDash":false}]},{"StartTime":245370.0,"Objects":[{"StartTime":245370.0,"Position":189.0,"HyperDash":false}]},{"StartTime":245528.0,"Objects":[{"StartTime":245528.0,"Position":311.0,"HyperDash":false},{"StartTime":245588.0,"Position":334.7987,"HyperDash":false},{"StartTime":245685.0,"Position":390.993317,"HyperDash":false}]},{"StartTime":245844.0,"Objects":[{"StartTime":245844.0,"Position":400.0,"HyperDash":false}]},{"StartTime":246002.0,"Objects":[{"StartTime":246002.0,"Position":250.0,"HyperDash":false},{"StartTime":246062.0,"Position":220.210785,"HyperDash":false},{"StartTime":246159.0,"Position":170.042587,"HyperDash":false}]},{"StartTime":246318.0,"Objects":[{"StartTime":246318.0,"Position":320.0,"HyperDash":false},{"StartTime":246378.0,"Position":337.9858,"HyperDash":false},{"StartTime":246475.0,"Position":399.7238,"HyperDash":false}]},{"StartTime":246633.0,"Objects":[{"StartTime":246633.0,"Position":488.0,"HyperDash":false},{"StartTime":246693.0,"Position":475.33725,"HyperDash":false},{"StartTime":246790.0,"Position":466.066925,"HyperDash":false}]},{"StartTime":246949.0,"Objects":[{"StartTime":246949.0,"Position":314.0,"HyperDash":false},{"StartTime":247009.0,"Position":298.7121,"HyperDash":false},{"StartTime":247106.0,"Position":292.039,"HyperDash":false}]},{"StartTime":247265.0,"Objects":[{"StartTime":247265.0,"Position":202.0,"HyperDash":false},{"StartTime":247334.0,"Position":159.634674,"HyperDash":false},{"StartTime":247404.0,"Position":149.680634,"HyperDash":false},{"StartTime":247474.0,"Position":95.09981,"HyperDash":false},{"StartTime":247580.0,"Position":69.26001,"HyperDash":false}]},{"StartTime":247739.0,"Objects":[{"StartTime":247739.0,"Position":190.0,"HyperDash":false}]},{"StartTime":247897.0,"Objects":[{"StartTime":247897.0,"Position":200.0,"HyperDash":false}]},{"StartTime":248054.0,"Objects":[{"StartTime":248054.0,"Position":188.0,"HyperDash":false},{"StartTime":248114.0,"Position":208.2239,"HyperDash":false},{"StartTime":248211.0,"Position":262.024536,"HyperDash":false}]},{"StartTime":248370.0,"Objects":[{"StartTime":248370.0,"Position":342.0,"HyperDash":false}]},{"StartTime":248528.0,"Objects":[{"StartTime":248528.0,"Position":338.0,"HyperDash":false},{"StartTime":248588.0,"Position":338.277985,"HyperDash":false},{"StartTime":248685.0,"Position":366.8771,"HyperDash":false}]},{"StartTime":248844.0,"Objects":[{"StartTime":248844.0,"Position":290.0,"HyperDash":false},{"StartTime":248904.0,"Position":284.053131,"HyperDash":false},{"StartTime":249001.0,"Position":319.062073,"HyperDash":false}]},{"StartTime":249160.0,"Objects":[{"StartTime":249160.0,"Position":432.0,"HyperDash":false},{"StartTime":249220.0,"Position":451.277985,"HyperDash":false},{"StartTime":249317.0,"Position":460.877136,"HyperDash":false}]},{"StartTime":249476.0,"Objects":[{"StartTime":249476.0,"Position":384.0,"HyperDash":false},{"StartTime":249536.0,"Position":383.053131,"HyperDash":false},{"StartTime":249633.0,"Position":413.062042,"HyperDash":false}]},{"StartTime":249791.0,"Objects":[{"StartTime":249791.0,"Position":449.0,"HyperDash":false},{"StartTime":249860.0,"Position":463.458252,"HyperDash":false},{"StartTime":249930.0,"Position":466.69632,"HyperDash":false},{"StartTime":250000.0,"Position":482.1586,"HyperDash":false},{"StartTime":250106.0,"Position":487.1767,"HyperDash":false}]},{"StartTime":250265.0,"Objects":[{"StartTime":250265.0,"Position":351.0,"HyperDash":false}]},{"StartTime":250423.0,"Objects":[{"StartTime":250423.0,"Position":312.0,"HyperDash":false}]},{"StartTime":250581.0,"Objects":[{"StartTime":250581.0,"Position":196.0,"HyperDash":false},{"StartTime":250641.0,"Position":227.257263,"HyperDash":false},{"StartTime":250738.0,"Position":222.828583,"HyperDash":false}]},{"StartTime":250897.0,"Objects":[{"StartTime":250897.0,"Position":161.0,"HyperDash":false}]},{"StartTime":251054.0,"Objects":[{"StartTime":251054.0,"Position":88.0,"HyperDash":false},{"StartTime":251114.0,"Position":72.74277,"HyperDash":false},{"StartTime":251211.0,"Position":61.1714363,"HyperDash":false}]},{"StartTime":251370.0,"Objects":[{"StartTime":251370.0,"Position":188.0,"HyperDash":false},{"StartTime":251430.0,"Position":165.064133,"HyperDash":false},{"StartTime":251527.0,"Position":160.9748,"HyperDash":false}]},{"StartTime":251686.0,"Objects":[{"StartTime":251686.0,"Position":206.0,"HyperDash":false},{"StartTime":251746.0,"Position":254.490585,"HyperDash":false},{"StartTime":251843.0,"Position":286.597961,"HyperDash":false}]},{"StartTime":252002.0,"Objects":[{"StartTime":252002.0,"Position":381.0,"HyperDash":false},{"StartTime":252062.0,"Position":344.076172,"HyperDash":false},{"StartTime":252159.0,"Position":300.619,"HyperDash":false}]},{"StartTime":252318.0,"Objects":[{"StartTime":252318.0,"Position":430.0,"HyperDash":false}]},{"StartTime":252476.0,"Objects":[{"StartTime":252476.0,"Position":440.0,"HyperDash":false},{"StartTime":252536.0,"Position":447.263672,"HyperDash":false},{"StartTime":252633.0,"Position":467.223053,"HyperDash":false}]},{"StartTime":252791.0,"Objects":[{"StartTime":252791.0,"Position":349.0,"HyperDash":false},{"StartTime":252851.0,"Position":324.82547,"HyperDash":false},{"StartTime":252948.0,"Position":321.497559,"HyperDash":false}]},{"StartTime":253107.0,"Objects":[{"StartTime":253107.0,"Position":217.0,"HyperDash":false}]},{"StartTime":253265.0,"Objects":[{"StartTime":253265.0,"Position":229.0,"HyperDash":false}]},{"StartTime":253423.0,"Objects":[{"StartTime":253423.0,"Position":235.0,"HyperDash":false}]},{"StartTime":253581.0,"Objects":[{"StartTime":253581.0,"Position":225.0,"HyperDash":false},{"StartTime":253641.0,"Position":189.989166,"HyperDash":false},{"StartTime":253738.0,"Position":150.638168,"HyperDash":false}]},{"StartTime":253897.0,"Objects":[{"StartTime":253897.0,"Position":318.0,"HyperDash":false}]},{"StartTime":254054.0,"Objects":[{"StartTime":254054.0,"Position":337.0,"HyperDash":false}]},{"StartTime":254212.0,"Objects":[{"StartTime":254212.0,"Position":407.0,"HyperDash":false}]},{"StartTime":254291.0,"Objects":[{"StartTime":254291.0,"Position":407.0,"HyperDash":false}]},{"StartTime":254370.0,"Objects":[{"StartTime":254370.0,"Position":407.0,"HyperDash":false},{"StartTime":254430.0,"Position":396.4197,"HyperDash":false},{"StartTime":254527.0,"Position":415.948242,"HyperDash":false}]},{"StartTime":254686.0,"Objects":[{"StartTime":254686.0,"Position":282.0,"HyperDash":false}]},{"StartTime":254844.0,"Objects":[{"StartTime":254844.0,"Position":314.0,"HyperDash":false},{"StartTime":254904.0,"Position":328.5803,"HyperDash":false},{"StartTime":255001.0,"Position":305.051758,"HyperDash":false}]},{"StartTime":255160.0,"Objects":[{"StartTime":255160.0,"Position":150.0,"HyperDash":false}]},{"StartTime":255318.0,"Objects":[{"StartTime":255318.0,"Position":297.0,"HyperDash":true}]},{"StartTime":255476.0,"Objects":[{"StartTime":255476.0,"Position":74.0,"HyperDash":false}]},{"StartTime":255633.0,"Objects":[{"StartTime":255633.0,"Position":184.0,"HyperDash":false}]},{"StartTime":259423.0,"Objects":[{"StartTime":259423.0,"Position":66.0,"HyperDash":false},{"StartTime":259483.0,"Position":83.09656,"HyperDash":false},{"StartTime":259580.0,"Position":123.771538,"HyperDash":false}]},{"StartTime":259739.0,"Objects":[{"StartTime":259739.0,"Position":227.0,"HyperDash":false},{"StartTime":259799.0,"Position":259.148071,"HyperDash":false},{"StartTime":259896.0,"Position":284.876556,"HyperDash":false}]},{"StartTime":260054.0,"Objects":[{"StartTime":260054.0,"Position":374.0,"HyperDash":false}]},{"StartTime":260212.0,"Objects":[{"StartTime":260212.0,"Position":399.0,"HyperDash":false}]},{"StartTime":260370.0,"Objects":[{"StartTime":260370.0,"Position":455.0,"HyperDash":false}]},{"StartTime":260528.0,"Objects":[{"StartTime":260528.0,"Position":396.0,"HyperDash":false}]},{"StartTime":260686.0,"Objects":[{"StartTime":260686.0,"Position":288.0,"HyperDash":false},{"StartTime":260746.0,"Position":257.008453,"HyperDash":false},{"StartTime":260843.0,"Position":211.3641,"HyperDash":false}]},{"StartTime":261002.0,"Objects":[{"StartTime":261002.0,"Position":83.0,"HyperDash":false}]},{"StartTime":261160.0,"Objects":[{"StartTime":261160.0,"Position":120.0,"HyperDash":false},{"StartTime":261220.0,"Position":138.656952,"HyperDash":false},{"StartTime":261317.0,"Position":149.021484,"HyperDash":false}]},{"StartTime":261476.0,"Objects":[{"StartTime":261476.0,"Position":168.0,"HyperDash":false},{"StartTime":261536.0,"Position":191.8636,"HyperDash":false},{"StartTime":261633.0,"Position":196.8266,"HyperDash":false}]},{"StartTime":261791.0,"Objects":[{"StartTime":261791.0,"Position":300.0,"HyperDash":false},{"StartTime":261860.0,"Position":319.492554,"HyperDash":false},{"StartTime":261930.0,"Position":380.197144,"HyperDash":false},{"StartTime":262000.0,"Position":391.054535,"HyperDash":false},{"StartTime":262106.0,"Position":437.8109,"HyperDash":false}]},{"StartTime":262265.0,"Objects":[{"StartTime":262265.0,"Position":319.0,"HyperDash":false},{"StartTime":262325.0,"Position":323.6614,"HyperDash":false},{"StartTime":262422.0,"Position":301.140259,"HyperDash":false}]},{"StartTime":262581.0,"Objects":[{"StartTime":262581.0,"Position":160.0,"HyperDash":false},{"StartTime":262641.0,"Position":149.948944,"HyperDash":false},{"StartTime":262738.0,"Position":141.732,"HyperDash":false}]},{"StartTime":262897.0,"Objects":[{"StartTime":262897.0,"Position":297.0,"HyperDash":false},{"StartTime":262957.0,"Position":272.6614,"HyperDash":false},{"StartTime":263054.0,"Position":279.140259,"HyperDash":false}]},{"StartTime":263212.0,"Objects":[{"StartTime":263212.0,"Position":430.0,"HyperDash":false},{"StartTime":263272.0,"Position":455.104431,"HyperDash":false},{"StartTime":263369.0,"Position":510.4512,"HyperDash":false}]},{"StartTime":263528.0,"Objects":[{"StartTime":263528.0,"Position":401.0,"HyperDash":false}]},{"StartTime":263686.0,"Objects":[{"StartTime":263686.0,"Position":282.0,"HyperDash":false},{"StartTime":263746.0,"Position":270.895569,"HyperDash":false},{"StartTime":263843.0,"Position":201.548782,"HyperDash":false}]},{"StartTime":264002.0,"Objects":[{"StartTime":264002.0,"Position":124.0,"HyperDash":false},{"StartTime":264062.0,"Position":170.993927,"HyperDash":false},{"StartTime":264159.0,"Position":204.329208,"HyperDash":false}]},{"StartTime":264318.0,"Objects":[{"StartTime":264318.0,"Position":93.0,"HyperDash":false}]},{"StartTime":264476.0,"Objects":[{"StartTime":264476.0,"Position":61.0,"HyperDash":false},{"StartTime":264536.0,"Position":72.74982,"HyperDash":false},{"StartTime":264633.0,"Position":76.9942856,"HyperDash":false}]},{"StartTime":264791.0,"Objects":[{"StartTime":264791.0,"Position":229.0,"HyperDash":false},{"StartTime":264851.0,"Position":210.380234,"HyperDash":false},{"StartTime":264948.0,"Position":212.7894,"HyperDash":false}]},{"StartTime":265107.0,"Objects":[{"StartTime":265107.0,"Position":358.0,"HyperDash":false},{"StartTime":265167.0,"Position":382.749847,"HyperDash":false},{"StartTime":265264.0,"Position":373.9943,"HyperDash":false}]},{"StartTime":265423.0,"Objects":[{"StartTime":265423.0,"Position":470.0,"HyperDash":false}]},{"StartTime":265581.0,"Objects":[{"StartTime":265581.0,"Position":470.0,"HyperDash":false}]},{"StartTime":266054.0,"Objects":[{"StartTime":266054.0,"Position":149.0,"HyperDash":false},{"StartTime":266132.0,"Position":167.136108,"HyperDash":false},{"StartTime":266211.0,"Position":211.849609,"HyperDash":false},{"StartTime":266290.0,"Position":233.369949,"HyperDash":false},{"StartTime":266369.0,"Position":230.355377,"HyperDash":false},{"StartTime":266419.0,"Position":248.772461,"HyperDash":false},{"StartTime":266469.0,"Position":258.240936,"HyperDash":false},{"StartTime":266519.0,"Position":225.7301,"HyperDash":false},{"StartTime":266606.0,"Position":243.291763,"HyperDash":false}]},{"StartTime":266686.0,"Objects":[{"StartTime":266686.0,"Position":253.0,"HyperDash":false},{"StartTime":266764.0,"Position":255.375717,"HyperDash":false},{"StartTime":266843.0,"Position":240.91098,"HyperDash":false},{"StartTime":266922.0,"Position":228.636566,"HyperDash":false},{"StartTime":267001.0,"Position":225.662109,"HyperDash":false},{"StartTime":267051.0,"Position":218.509918,"HyperDash":false},{"StartTime":267101.0,"Position":217.651337,"HyperDash":false},{"StartTime":267151.0,"Position":202.171875,"HyperDash":false},{"StartTime":267238.0,"Position":158.415985,"HyperDash":false}]},{"StartTime":267318.0,"Objects":[{"StartTime":267318.0,"Position":168.0,"HyperDash":false},{"StartTime":267396.0,"Position":192.136108,"HyperDash":false},{"StartTime":267475.0,"Position":198.849609,"HyperDash":false},{"StartTime":267554.0,"Position":251.369949,"HyperDash":false},{"StartTime":267633.0,"Position":249.355377,"HyperDash":false},{"StartTime":267683.0,"Position":270.772461,"HyperDash":false},{"StartTime":267733.0,"Position":256.240936,"HyperDash":false},{"StartTime":267783.0,"Position":261.7301,"HyperDash":false},{"StartTime":267870.0,"Position":262.291779,"HyperDash":false}]},{"StartTime":267949.0,"Objects":[{"StartTime":267949.0,"Position":272.0,"HyperDash":false},{"StartTime":268027.0,"Position":258.375732,"HyperDash":false},{"StartTime":268106.0,"Position":255.91098,"HyperDash":false},{"StartTime":268185.0,"Position":277.636566,"HyperDash":false},{"StartTime":268264.0,"Position":244.662109,"HyperDash":false},{"StartTime":268314.0,"Position":220.509918,"HyperDash":false},{"StartTime":268364.0,"Position":225.651337,"HyperDash":false},{"StartTime":268414.0,"Position":209.171875,"HyperDash":false},{"StartTime":268501.0,"Position":177.415985,"HyperDash":false}]},{"StartTime":268581.0,"Objects":[{"StartTime":268581.0,"Position":187.0,"HyperDash":false},{"StartTime":268659.0,"Position":202.237671,"HyperDash":false},{"StartTime":268738.0,"Position":233.0073,"HyperDash":false},{"StartTime":268817.0,"Position":262.5497,"HyperDash":false},{"StartTime":268896.0,"Position":268.5099,"HyperDash":false},{"StartTime":268946.0,"Position":291.870758,"HyperDash":false},{"StartTime":268996.0,"Position":273.257019,"HyperDash":false},{"StartTime":269046.0,"Position":299.637756,"HyperDash":false},{"StartTime":269133.0,"Position":280.97876,"HyperDash":false}]},{"StartTime":269212.0,"Objects":[{"StartTime":269212.0,"Position":294.0,"HyperDash":false},{"StartTime":269312.0,"Position":315.9435,"HyperDash":false},{"StartTime":269448.0,"Position":321.1469,"HyperDash":false}]},{"StartTime":269528.0,"Objects":[{"StartTime":269528.0,"Position":340.0,"HyperDash":false},{"StartTime":269588.0,"Position":365.320465,"HyperDash":false},{"StartTime":269685.0,"Position":377.5064,"HyperDash":false}]},{"StartTime":269844.0,"Objects":[{"StartTime":269844.0,"Position":447.0,"HyperDash":false}]},{"StartTime":270002.0,"Objects":[{"StartTime":270002.0,"Position":465.0,"HyperDash":false}]},{"StartTime":270160.0,"Objects":[{"StartTime":270160.0,"Position":450.0,"HyperDash":false}]},{"StartTime":270318.0,"Objects":[{"StartTime":270318.0,"Position":468.0,"HyperDash":false}]},{"StartTime":270476.0,"Objects":[{"StartTime":270476.0,"Position":344.0,"HyperDash":false},{"StartTime":270536.0,"Position":326.693817,"HyperDash":false},{"StartTime":270633.0,"Position":270.128,"HyperDash":false}]},{"StartTime":270791.0,"Objects":[{"StartTime":270791.0,"Position":146.0,"HyperDash":false},{"StartTime":270851.0,"Position":119.05838,"HyperDash":false},{"StartTime":270948.0,"Position":124.892738,"HyperDash":false}]},{"StartTime":271107.0,"Objects":[{"StartTime":271107.0,"Position":264.0,"HyperDash":false}]},{"StartTime":271265.0,"Objects":[{"StartTime":271265.0,"Position":218.0,"HyperDash":false},{"StartTime":271325.0,"Position":192.312866,"HyperDash":false},{"StartTime":271422.0,"Position":147.21402,"HyperDash":false}]},{"StartTime":271581.0,"Objects":[{"StartTime":271581.0,"Position":245.0,"HyperDash":false},{"StartTime":271641.0,"Position":271.938019,"HyperDash":false},{"StartTime":271738.0,"Position":315.481079,"HyperDash":false}]},{"StartTime":271897.0,"Objects":[{"StartTime":271897.0,"Position":349.0,"HyperDash":false},{"StartTime":271957.0,"Position":327.700134,"HyperDash":false},{"StartTime":272054.0,"Position":336.267517,"HyperDash":false}]},{"StartTime":272212.0,"Objects":[{"StartTime":272212.0,"Position":446.0,"HyperDash":false},{"StartTime":272272.0,"Position":462.1508,"HyperDash":false},{"StartTime":272369.0,"Position":432.882324,"HyperDash":false}]},{"StartTime":272528.0,"Objects":[{"StartTime":272528.0,"Position":324.0,"HyperDash":false}]},{"StartTime":272686.0,"Objects":[{"StartTime":272686.0,"Position":415.0,"HyperDash":false},{"StartTime":272746.0,"Position":460.961884,"HyperDash":false},{"StartTime":272843.0,"Position":493.6076,"HyperDash":false}]},{"StartTime":273002.0,"Objects":[{"StartTime":273002.0,"Position":349.0,"HyperDash":false},{"StartTime":273062.0,"Position":319.039642,"HyperDash":false},{"StartTime":273159.0,"Position":270.206818,"HyperDash":false}]},{"StartTime":273318.0,"Objects":[{"StartTime":273318.0,"Position":148.0,"HyperDash":false},{"StartTime":273378.0,"Position":142.55928,"HyperDash":false},{"StartTime":273475.0,"Position":125.789063,"HyperDash":false}]},{"StartTime":273633.0,"Objects":[{"StartTime":273633.0,"Position":199.0,"HyperDash":false}]},{"StartTime":273791.0,"Objects":[{"StartTime":273791.0,"Position":247.0,"HyperDash":false},{"StartTime":273851.0,"Position":242.4407,"HyperDash":false},{"StartTime":273948.0,"Position":269.210938,"HyperDash":false}]},{"StartTime":274107.0,"Objects":[{"StartTime":274107.0,"Position":242.0,"HyperDash":false}]},{"StartTime":274265.0,"Objects":[{"StartTime":274265.0,"Position":143.0,"HyperDash":false},{"StartTime":274325.0,"Position":126.55928,"HyperDash":false},{"StartTime":274422.0,"Position":120.789063,"HyperDash":false}]},{"StartTime":274581.0,"Objects":[{"StartTime":274581.0,"Position":272.0,"HyperDash":false},{"StartTime":274641.0,"Position":314.038574,"HyperDash":false},{"StartTime":274738.0,"Position":355.8343,"HyperDash":false}]},{"StartTime":274897.0,"Objects":[{"StartTime":274897.0,"Position":488.0,"HyperDash":false},{"StartTime":274957.0,"Position":461.961426,"HyperDash":false},{"StartTime":275054.0,"Position":404.1657,"HyperDash":false}]},{"StartTime":275212.0,"Objects":[{"StartTime":275212.0,"Position":285.0,"HyperDash":false}]},{"StartTime":275370.0,"Objects":[{"StartTime":275370.0,"Position":315.0,"HyperDash":false}]},{"StartTime":275528.0,"Objects":[{"StartTime":275528.0,"Position":283.0,"HyperDash":false}]},{"StartTime":275686.0,"Objects":[{"StartTime":275686.0,"Position":313.0,"HyperDash":false}]},{"StartTime":275844.0,"Objects":[{"StartTime":275844.0,"Position":254.0,"HyperDash":false}]},{"StartTime":278370.0,"Objects":[{"StartTime":278370.0,"Position":71.0,"HyperDash":false},{"StartTime":278470.0,"Position":130.124451,"HyperDash":false},{"StartTime":278606.0,"Position":152.874481,"HyperDash":false}]},{"StartTime":278686.0,"Objects":[{"StartTime":278686.0,"Position":256.0,"HyperDash":false},{"StartTime":278786.0,"Position":279.959045,"HyperDash":false},{"StartTime":278922.0,"Position":336.141327,"HyperDash":false}]},{"StartTime":279002.0,"Objects":[{"StartTime":279002.0,"Position":351.0,"HyperDash":false},{"StartTime":279102.0,"Position":306.33313,"HyperDash":false},{"StartTime":279238.0,"Position":260.928619,"HyperDash":false}]},{"StartTime":279318.0,"Objects":[{"StartTime":279318.0,"Position":149.0,"HyperDash":false},{"StartTime":279418.0,"Position":144.369186,"HyperDash":false},{"StartTime":279554.0,"Position":58.3702,"HyperDash":true}]},{"StartTime":279633.0,"Objects":[{"StartTime":279633.0,"Position":205.0,"HyperDash":false}]},{"StartTime":280265.0,"Objects":[{"StartTime":280265.0,"Position":480.0,"HyperDash":false},{"StartTime":280343.0,"Position":474.7398,"HyperDash":false},{"StartTime":280422.0,"Position":458.350433,"HyperDash":false},{"StartTime":280501.0,"Position":451.037842,"HyperDash":false},{"StartTime":280580.0,"Position":422.829529,"HyperDash":false},{"StartTime":280659.0,"Position":414.7673,"HyperDash":false},{"StartTime":280738.0,"Position":394.904449,"HyperDash":false},{"StartTime":280817.0,"Position":370.3106,"HyperDash":false},{"StartTime":280896.0,"Position":368.073456,"HyperDash":false},{"StartTime":280975.0,"Position":348.296478,"HyperDash":false},{"StartTime":281054.0,"Position":338.1456,"HyperDash":false},{"StartTime":281133.0,"Position":314.726532,"HyperDash":false},{"StartTime":281212.0,"Position":321.195465,"HyperDash":false},{"StartTime":281291.0,"Position":328.64563,"HyperDash":false},{"StartTime":281370.0,"Position":292.093872,"HyperDash":false},{"StartTime":281449.0,"Position":310.49472,"HyperDash":false},{"StartTime":281528.0,"Position":288.733521,"HyperDash":false},{"StartTime":281606.0,"Position":290.7206,"HyperDash":false},{"StartTime":281685.0,"Position":288.1208,"HyperDash":false},{"StartTime":281764.0,"Position":272.7766,"HyperDash":false},{"StartTime":281843.0,"Position":266.504364,"HyperDash":false},{"StartTime":281922.0,"Position":241.107452,"HyperDash":false},{"StartTime":282001.0,"Position":268.358948,"HyperDash":false},{"StartTime":282080.0,"Position":230.079391,"HyperDash":false},{"StartTime":282159.0,"Position":242.0971,"HyperDash":false},{"StartTime":282238.0,"Position":243.277161,"HyperDash":false},{"StartTime":282317.0,"Position":222.536377,"HyperDash":false},{"StartTime":282396.0,"Position":223.8562,"HyperDash":false},{"StartTime":282475.0,"Position":205.2843,"HyperDash":false},{"StartTime":282554.0,"Position":197.88031,"HyperDash":false},{"StartTime":282633.0,"Position":198.803864,"HyperDash":false},{"StartTime":282712.0,"Position":166.135483,"HyperDash":false},{"StartTime":282791.0,"Position":156.019943,"HyperDash":false},{"StartTime":282870.0,"Position":155.553528,"HyperDash":false},{"StartTime":282949.0,"Position":129.81575,"HyperDash":false},{"StartTime":283028.0,"Position":128.8722,"HyperDash":false},{"StartTime":283107.0,"Position":100.768196,"HyperDash":false},{"StartTime":283176.0,"Position":72.3494644,"HyperDash":false},{"StartTime":283246.0,"Position":88.6788,"HyperDash":false},{"StartTime":283316.0,"Position":61.952446,"HyperDash":false},{"StartTime":283422.0,"Position":43.60075,"HyperDash":false}]}]} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/2190499.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/2190499.osu new file mode 100644 index 0000000000..c0df81b7e4 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/2190499.osu @@ -0,0 +1,977 @@ +osu file format v14 + +[General] +StackLeniency: 0.7 +Mode: 0 + +[Difficulty] +HPDrainRate:4.7 +CircleSize:3.7 +OverallDifficulty:8.4 +ApproachRate:9 +SliderMultiplier:1.57 +SliderTickRate:1 + +[Events] +//Background and Video events +//Break Periods +2,78991,87033 +2,129518,133770 +2,224254,233560 +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Layer 4 (Overlay) +//Storyboard Sound Samples + +[TimingPoints] +476,315.789473684211,4,2,1,50,1,0 +1739,-212.76595744681,4,2,1,50,0,0 +17054,-212.76595744681,4,2,1,5,0,0 +17133,-212.76595744681,4,2,1,50,0,0 +17370,-212.76595744681,4,2,1,5,0,0 +17449,-212.76595744681,4,2,1,50,0,0 +17686,-212.76595744681,4,2,1,50,0,0 +17765,-212.76595744681,4,2,1,5,0,0 +17844,-212.76595744681,4,2,1,50,0,0 +18160,-103.092783505155,4,2,1,70,0,0 +27239,-212.76595744681,4,2,1,70,0,0 +27318,-103.092783505155,4,2,1,70,0,0 +27554,-212.76595744681,4,2,1,70,0,0 +27633,-103.092783505155,4,2,1,70,0,0 +28265,-114.942528735633,4,2,1,60,0,0 +38370,-129.87012987013,4,2,1,50,0,0 +47765,-129.87012987013,4,2,1,5,0,0 +47844,-129.87012987013,4,2,1,50,0,0 +48081,-129.87012987013,4,2,1,5,0,0 +48160,-129.87012987013,4,2,1,50,0,0 +48476,-103.092783505155,4,2,1,70,0,0 +68686,-114.942528735633,4,2,1,60,0,0 +78791,-129.87012987013,4,2,1,50,0,0 +79344,-129.87012987013,4,2,1,5,0,0 +79423,-129.87012987013,4,2,1,50,0,0 +81870,-129.87012987013,4,2,1,5,0,0 +81949,-129.87012987013,4,2,1,50,0,0 +82502,-129.87012987013,4,2,1,5,0,0 +82581,-129.87012987013,4,2,1,50,0,0 +87633,-114.942528735633,4,2,1,60,0,0 +87870,-114.942528735633,4,2,1,5,0,0 +87949,-114.942528735633,4,2,1,60,0,0 +88186,-114.942528735633,4,2,1,5,0,0 +88265,-114.942528735633,4,2,1,60,0,0 +88502,-114.942528735633,4,2,1,5,0,0 +88581,-114.942528735633,4,2,1,60,0,0 +88897,-93.4579439252336,4,2,1,75,0,0 +109107,-129.87012987013,4,2,1,70,0,0 +111239,-129.87012987013,4,2,1,5,0,0 +111318,-129.87012987013,4,2,1,70,0,0 +113765,-129.87012987013,4,2,1,5,0,0 +113844,-129.87012987013,4,2,1,70,0,0 +114160,-93.4579439252336,4,2,1,75,0,0 +119054,-103.092783505155,4,2,1,75,0,0 +119212,-103.092783505155,4,2,1,70,0,0 +119449,-103.092783505155,4,2,1,5,0,0 +119528,-103.092783505155,4,2,1,70,0,0 +120081,-103.092783505155,4,2,1,5,0,0 +120160,-103.092783505155,4,2,1,70,0,0 +120712,-103.092783505155,4,2,1,5,0,0 +120791,-103.092783505155,4,2,1,70,0,0 +121344,-103.092783505155,4,2,1,5,0,0 +121423,-103.092783505155,4,2,1,70,0,0 +121976,-103.092783505155,4,2,1,5,0,0 +122054,-103.092783505155,4,2,1,70,0,0 +122607,-103.092783505155,4,2,1,5,0,0 +122686,-103.092783505155,4,2,1,70,0,0 +122923,-103.092783505155,4,2,1,5,0,0 +123002,-103.092783505155,4,2,1,70,0,0 +124265,-93.4579439252336,4,2,1,75,0,0 +129318,-129.87012987013,4,2,1,50,0,0 +133502,-129.87012987013,4,2,1,5,0,0 +133581,-129.87012987013,4,2,1,50,0,0 +134370,-114.942528735633,4,2,1,70,0,0 +137133,-114.942528735633,4,2,1,5,0,0 +137212,-114.942528735633,4,2,1,70,0,0 +137449,-114.942528735633,4,2,1,5,0,0 +137528,-114.942528735633,4,2,1,70,0,0 +137765,-114.942528735633,4,2,1,5,0,0 +137844,-114.942528735633,4,2,1,70,0,0 +142502,-114.942528735633,4,2,1,5,0,0 +142581,-114.942528735633,4,2,1,70,0,0 +145976,-114.942528735633,4,2,1,5,0,0 +146054,-114.942528735633,4,2,1,70,0,0 +146291,-114.942528735633,4,2,1,5,0,0 +146370,-114.942528735633,4,2,1,70,0,0 +146607,-114.942528735633,4,2,1,5,0,0 +146686,-114.942528735633,4,2,1,70,0,0 +151344,-114.942528735633,4,2,1,5,0,0 +151423,-114.942528735633,4,2,1,70,0,0 +152054,-103.092783505155,4,2,1,70,0,0 +161133,-103.092783505155,4,2,1,5,0,0 +161212,-103.092783505155,4,2,1,70,0,0 +161449,-103.092783505155,4,2,1,5,0,0 +161528,-103.092783505155,4,2,1,70,0,0 +161765,-103.092783505155,4,2,1,5,0,0 +161844,-103.092783505155,4,2,1,70,0,0 +162160,-93.4579439252336,4,2,1,75,0,0 +182370,-129.87012987013,4,2,1,70,0,0 +184502,-129.87012987013,4,2,1,5,0,0 +184581,-129.87012987013,4,2,1,70,0,0 +187028,-129.87012987013,4,2,1,5,0,0 +187107,-129.87012987013,4,2,1,70,0,0 +187423,-93.4579439252336,4,2,1,75,0,0 +192318,-103.092783505155,4,2,1,75,0,0 +192476,-103.092783505155,4,2,1,70,0,0 +192712,-103.092783505155,4,2,1,5,0,0 +192791,-103.092783505155,4,2,1,70,0,0 +193344,-103.092783505155,4,2,1,5,0,0 +193423,-103.092783505155,4,2,1,70,0,0 +193976,-103.092783505155,4,2,1,5,0,0 +194054,-103.092783505155,4,2,1,70,0,0 +194607,-103.092783505155,4,2,1,5,0,0 +194686,-103.092783505155,4,2,1,70,0,0 +195239,-103.092783505155,4,2,1,5,0,0 +195318,-103.092783505155,4,2,1,70,0,0 +195870,-103.092783505155,4,2,1,5,0,0 +195949,-103.092783505155,4,2,1,70,0,0 +196186,-103.092783505155,4,2,1,5,0,0 +196265,-103.092783505155,4,2,1,70,0,0 +197528,-93.4579439252336,4,2,1,75,0,0 +202581,-129.87012987013,4,2,1,70,0,0 +203844,-103.092783505155,4,2,1,70,0,0 +224054,-129.87012987013,4,2,1,60,0,0 +235423,-93.4579439252336,4,2,1,75,0,0 +255633,-129.87012987013,4,2,1,60,0,0 +260686,-93.4579439252336,4,2,1,75,0,0 +265581,-103.092783505155,4,2,1,75,0,0 +265739,-103.092783505155,4,2,1,70,0,0 +265976,-103.092783505155,4,2,1,5,0,0 +266054,-103.092783505155,4,2,1,70,0,0 +266607,-103.092783505155,4,2,1,5,0,0 +266686,-103.092783505155,4,2,1,70,0,0 +267239,-103.092783505155,4,2,1,5,0,0 +267318,-103.092783505155,4,2,1,70,0,0 +267870,-103.092783505155,4,2,1,5,0,0 +267949,-103.092783505155,4,2,1,70,0,0 +268502,-103.092783505155,4,2,1,5,0,0 +268581,-103.092783505155,4,2,1,70,0,0 +269133,-103.092783505155,4,2,1,5,0,0 +269212,-103.092783505155,4,2,1,70,0,0 +269449,-103.092783505155,4,2,1,5,0,0 +269528,-103.092783505155,4,2,1,70,0,0 +270791,-93.4579439252336,4,2,1,75,0,0 +275844,-129.87012987013,4,2,1,60,0,0 +278370,-93.4579439252336,4,2,1,75,0,0 +278607,-93.4579439252336,4,2,1,5,0,0 +278686,-93.4579439252336,4,2,1,75,0,0 +278923,-93.4579439252336,4,2,1,5,0,0 +279002,-78.7401574803149,4,2,1,75,0,0 +279239,-78.7401574803149,4,2,1,5,0,0 +279318,-78.7401574803149,4,2,1,75,0,0 +279554,-78.7401574803149,4,2,1,5,0,0 +279633,-129.87012987013,4,2,1,70,0,0 +280265,-270.270270270271,4,2,1,70,0,0 +283423,-270.270270270271,4,2,1,10,0,0 + +[HitObjects] +367,158,1739,6,0,B|277:179|338:219|236:236,1,147.579997748108,2|2,0:0|0:0,0:0:0:0: +161,20,3002,6,0,P|188:41|234:156,1,147.579997748108,2|2,0:0|0:0,0:0:0:0: +47,263,4265,6,0,P|91:234|115:230,1,73.789998874054,2|2,0:0|0:0,0:0:0:0: +235,344,4897,2,0,P|299:349|342:311,1,110.684998311081,2|2,0:0|0:0,0:0:0:0: +372,233,5528,2,0,P|351:171|339:79,1,147.579997748108,2|0,0:0|0:0,0:0:0:0: +55,109,6791,6,0,P|89:141|126:149,1,73.789998874054,2|2,0:0|0:0,0:0:0:0: +240,23,7423,2,0,P|203:58|189:121,1,110.684998311081,2|2,0:0|0:0,0:0:0:0: +273,203,8054,2,0,P|300:186|348:175,2,73.789998874054,2|2|2,0:0|0:0|0:0,0:0:0:0: +147,324,9002,2,0,P|124:323|97:314,1,36.894999437027,2|2,0:0|0:0,0:0:0:0: +59,247,9318,6,0,P|51:213|39:175,2,73.789998874054,2|2|2,0:0|0:0|0:0,0:0:0:0: +133,53,10265,1,2,0:0:0:0: +256,192,10581,12,0,11844,0:0:0:0: +256,192,13107,12,0,14370,0:0:0:0: +74,66,15633,6,0,B|151:62|120:116|198:112,1,138.356247888851,2|2,0:0|0:0,0:0:0:0: +189,105,17844,5,4,0:0:0:0: +189,105,18160,6,0,P|222:130|274:136,1,76.1450018009148,6|2,1:2|0:0,0:0:0:0: +402,27,18476,2,0,P|365:36|335:59,1,76.1450018009148,10|0,0:2|0:0,0:0:0:0: +383,259,18791,2,0,P|400:173|404:106,1,152.29000360183,2|10,1:2|0:2,0:0:0:0: +254,55,19265,1,0,0:0:0:0: +178,227,19423,6,0,P|140:242|92:242,1,76.1450018009148,2|0,1:2|0:0,0:0:0:0: +245,84,19739,2,0,P|282:86|317:100,1,76.1450018009148,10|0,0:2|0:0,0:0:0:0: +287,315,20054,2,0,P|270:229|266:162,1,152.29000360183,2|8,1:2|0:2,0:0:0:0: +167,252,20528,1,0,0:0:0:0: +110,91,20686,6,0,P|77:65|24:58,1,76.1450018009148,6|0,1:2|0:0,0:0:0:0: +158,225,21002,2,0,P|194:214|223:190,1,76.1450018009148,10|0,0:2|0:0,0:0:0:0: +105,73,21318,2,0,P|72:47|19:40,1,76.1450018009148,2|0,1:2|0:0,0:0:0:0: +153,207,21634,2,0,P|189:196|218:172,1,76.1450018009148,10|0,0:2|0:0,0:0:0:0: +321,19,21949,5,6,1:2:0:0: +372,198,22107,1,2,0:0:0:0: +345,14,22265,2,0,P|334:50|326:104,1,76.1450018009148,10|2,0:2|0:0,0:0:0:0: +413,295,22581,1,6,1:2:0:0: +442,141,22739,1,10,0:2:0:0: +409,316,22897,2,0,P|370:337|316:337,1,76.1450018009148,10|2,0:2|0:0,0:0:0:0: +205,239,23212,6,0,P|219:282|226:330,1,76.1450018009148,6|0,1:2|0:0,0:0:0:0: +73,189,23528,2,0,P|59:232|52:280,1,76.1450018009148,10|0,0:2|0:0,0:0:0:0: +240,312,23844,2,0,P|233:275|221:239,1,76.1450018009148,2|0,1:2|0:0,0:0:0:0: +88,189,24160,2,0,P|76:225|69:262,1,76.1450018009148,10|0,0:2|0:0,0:0:0:0: +206,54,24476,6,0,L|301:45,1,76.1450018009148,6|0,1:2|0:0,0:0:0:0: +425,174,24791,2,0,L|330:165,1,76.1450018009148,10|0,0:2|0:0,0:0:0:0: +196,41,25107,2,0,L|291:32,1,76.1450018009148,2|0,1:2|0:0,0:0:0:0: +415,161,25423,1,10,0:2:0:0: +363,43,25581,1,0,0:0:0:0: +263,180,25739,6,0,P|272:216|279:261,1,76.1450018009148,6|0,1:2|0:0,0:0:0:0: +418,374,26054,2,0,P|424:336|433:299,1,76.1450018009148,10|0,0:2|0:0,0:0:0:0: +251,184,26370,2,0,P|260:220|267:265,1,76.1450018009148,6|0,1:2|0:0,0:0:0:0: +406,378,26686,2,0,P|412:340|421:303,1,76.1450018009148,10|0,0:2|0:0,0:0:0:0: +326,119,27002,6,0,P|266:96|196:111,1,114.217502701372,14|0,0:2|0:0,0:0:0:0: +215,85,27318,2,0,P|271:80|323:102,1,114.217502701372,8|0,0:2|0:0,0:0:0:0: +324,89,27633,2,0,P|250:68|174:92,1,152.29000360183,12|4,0:2|0:2,0:0:0:0: +65,343,28265,6,0,B|57:248|105:312|97:183,1,136.590001146309,6|8,1:2|0:2,0:0:0:0: +153,332,28739,1,2,1:2:0:0: +153,332,28897,1,2,0:0:0:0: +215,226,29054,2,0,P|247:210|288:209,1,68.2950005731545,2|8,1:2|0:2,0:0:0:0: +332,322,29370,2,0,P|298:319|267:303,1,68.2950005731545,2|0,0:0|1:2,0:0:0:0: +371,217,29686,1,2,0:0:0:0: +371,217,29844,1,10,0:2:0:0: +444,302,30002,1,2,1:2:0:0: +444,302,30160,2,0,P|460:262|462:211,1,68.2950005731545,2|0,0:0|1:2,0:0:0:0: +393,130,30476,2,0,P|377:90|375:39,1,68.2950005731545,10|0,0:2|0:0,0:0:0:0: +265,134,30791,6,0,L|169:122,1,68.2950005731545,2|0,1:2|0:0,0:0:0:0: +80,53,31107,2,0,L|147:44,1,68.2950005731545,10|0,0:2|1:2,0:0:0:0: +124,189,31423,2,0,L|57:181,1,68.2950005731545,2|0,0:0|1:2,0:0:0:0: +164,296,31739,1,10,0:2:0:0: +164,296,31897,2,0,L|231:287,1,68.2950005731545,2|0,0:0|1:2,0:0:0:0: +365,211,32212,1,2,0:0:0:0: +365,211,32370,2,0,P|379:246|384:289,1,68.2950005731545,10|0,0:2|1:2,0:0:0:0: +488,162,32686,2,0,P|472:228|468:310,1,136.590001146309,2|8,0:0|0:2,0:0:0:0: +406,132,33160,1,0,1:2:0:0: +277,224,33318,6,0,B|197:212|245:168|149:160,1,136.590001146309,6|8,1:2|0:2,0:0:0:0: +283,146,33791,1,2,1:2:0:0: +283,146,33949,1,2,0:0:0:0: +158,238,34107,2,0,P|123:253|68:253,1,68.2950005731545,2|8,1:2|0:2,0:0:0:0: +19,126,34423,2,0,P|52:130|83:144,1,68.2950005731545,2|0,1:2|1:2,0:0:0:0: +158,238,34739,1,2,0:0:0:0: +158,238,34897,1,10,0:2:0:0: +204,124,35054,1,2,1:2:0:0: +204,124,35212,2,0,P|213:84|217:31,1,68.2950005731545,2|2,0:0|1:2,0:0:0:0: +345,175,35528,2,0,P|336:141|332:108,1,68.2950005731545,10|0,0:2|1:2,0:0:0:0: +461,237,35844,6,0,P|424:218|324:207,2,136.590001146309,2|10|2,1:2|0:2|1:2,0:0:0:0: +248,360,36791,1,10,0:2:0:0: +248,360,36949,2,0,P|259:318|261:281,1,68.2950005731545,2|8,0:0|0:2,0:0:0:0: +189,145,37265,5,2,1:2:0:0: +130,295,37423,2,0,P|96:312|48:311,1,68.2950005731545,10|0,0:2|1:2,0:0:0:0: +32,119,37739,5,10,0:2:0:0: +79,229,37897,1,0,1:2:0:0: +126,47,38054,5,12,0:2:0:0: +67,202,38212,1,0,1:2:0:0: +189,145,38370,6,0,P|236:139|304:205,1,120.889997601975,4|2,1:2|0:0,0:0:0:0: +281,297,38844,2,0,P|256:311|215:316,2,60.4449988009873,2|2|2,0:0|0:0|0:0,0:0:0:0: +367,240,39318,2,0,P|396:245|423:259,1,60.4449988009873,2|2,0:0|0:0,0:0:0:0: +493,325,39633,1,2,0:0:0:0: +493,325,39791,2,0,L|500:262,1,60.4449988009873,2|2,1:2|0:0,0:0:0:0: +450,183,40107,2,0,L|443:120,1,60.4449988009873,2|2,0:0|0:0,0:0:0:0: +379,41,40423,1,2,1:2:0:0: +379,41,40581,1,2,0:0:0:0: +312,120,40739,6,0,B|229:114|279:80|188:72,1,120.889997601975,2|2,0:0|0:0,0:0:0:0: +120,125,41212,2,0,P|107:98|107:68,2,60.4449988009873,2|2|2,0:0|0:0|0:0,0:0:0:0: +195,158,41686,2,0,P|195:187|182:215,1,60.4449988009873,2|2,0:0|0:0,0:0:0:0: +81,267,42002,1,2,0:0:0:0: +81,267,42160,1,2,0:0:0:0: +157,335,42318,1,2,1:2:0:0: +157,335,42476,2,0,L|233:329,1,60.4449988009873,2|0,0:0|0:0,0:0:0:0: +314,250,42791,2,0,L|374:254,1,60.4449988009873,2|2,1:2|0:0,0:0:0:0: +224,343,43107,6,0,L|92:351,1,120.889997601975,2|0,0:0|0:0,0:0:0:0: +18,308,43581,2,0,L|26:248,2,60.4449988009873,2|2|2,1:2|0:0|0:0,0:0:0:0: +118,245,44054,2,0,L|109:185,1,60.4449988009873,2|2,0:0|0:0,0:0:0:0: +32,119,44370,1,2,0:0:0:0: +32,119,44528,2,0,L|39:56,1,60.4449988009873,2|2,0:0|0:0,0:0:0:0: +131,30,44844,1,2,1:2:0:0: +131,30,45002,2,0,L|124:90,1,60.4449988009873,2|2,0:0|0:0,0:0:0:0: +215,147,45318,1,2,0:0:0:0: +215,147,45476,2,0,L|289:140,1,60.4449988009873,2|2,1:2|0:0,0:0:0:0: +362,98,45791,5,2,0:0:0:0: +362,98,45949,1,2,1:2:0:0: +350,203,46107,2,0,L|356:278,1,60.4449988009873,2|0,0:0|0:0,0:0:0:0: +421,352,46423,1,2,0:0:0:0: +421,352,46581,1,2,1:2:0:0: +343,276,46739,2,0,L|268:282,1,60.4449988009873,2|0,0:0|0:0,0:0:0:0: +212,353,47054,5,2,0:0:0:0: +176,245,47212,1,2,1:2:0:0: +104,346,47370,1,2,0:0:0:0: +104,346,47449,1,2,0:0:0:0: +104,346,47528,2,0,P|96:290|81:231,1,90.6674982014809,2|0,1:2|0:0,0:0:0:0: +73,246,47844,2,0,P|81:190|96:131,1,90.6674982014809,2|0,1:2|0:0,0:0:0:0: +108,144,48160,1,4,0:2:0:0: +108,144,48476,6,0,P|146:167|197:167,1,76.1450018009148,6|0,1:2|0:0,0:0:0:0: +259,24,48791,2,0,P|221:29|190:50,1,76.1450018009148,10|0,0:2|1:2,0:0:0:0: +329,179,49107,2,0,B|429:161|369:117|469:97,1,152.29000360183,2|8,0:0|0:2,0:0:0:0: +328,96,49581,1,0,0:0:0:0: +472,190,49739,6,0,P|462:222|454:274,1,76.1450018009148,6|0,1:2|0:0,0:0:0:0: +324,372,50054,2,0,P|317:334|306:298,1,76.1450018009148,10|0,0:2|1:2,0:0:0:0: +190,174,50370,2,0,P|128:184|85:268,1,152.29000360183,2|8,0:0|0:2,0:0:0:0: +206,294,50844,1,0,0:0:0:0: +313,170,51002,6,0,P|323:125|328:78,1,76.1450018009148,6|0,1:2|0:0,0:0:0:0: +223,271,51318,2,0,P|212:226|208:179,1,76.1450018009148,10|0,0:2|1:2,0:0:0:0: +268,40,51633,2,0,P|302:19|358:19,1,76.1450018009148,2|0,0:0|1:2,0:0:0:0: +382,195,51949,2,0,P|344:189|312:169,1,76.1450018009148,10|0,0:2|0:0,0:0:0:0: +191,14,52265,6,0,B|176:109|235:65|217:167,1,152.29000360183,6|10,1:2|0:2,0:0:0:0: +145,291,52739,1,0,1:2:0:0: +75,165,52897,2,0,P|106:144|152:135,1,76.1450018009148,2|0,0:0|1:2,0:0:0:0: +223,271,53212,2,0,P|254:292|291:300,1,76.1450018009148,10|0,0:2|0:0,0:0:0:0: +423,166,53528,5,2,1:2:0:0: +383,316,53686,2,0,P|364:275|364:218,1,76.1450018009148,2|8,0:0|0:2,0:0:0:0: +445,94,54002,2,0,P|439:131|422:165,1,76.1450018009148,2|0,1:2|0:0,0:0:0:0: +346,37,54318,1,2,1:2:0:0: +268,179,54476,2,0,P|230:173|196:156,1,76.1450018009148,10|0,0:2|0:0,0:0:0:0: +79,28,54791,6,0,P|101:82|110:184,1,152.29000360183,2|10,1:2|0:2,0:0:0:0: +38,334,55265,2,0,P|44:293|61:244,1,76.1450018009148,2|0,1:2|0:0,0:0:0:0: +189,362,55581,1,0,1:2:0:0: +125,198,55739,2,0,P|135:234|141:272,1,76.1450018009148,10|0,0:2|0:0,0:0:0:0: +279,380,56054,6,0,P|329:379|372:344,1,76.1450018009148,2|0,1:2|0:0,0:0:0:0: +470,222,56370,2,0,P|432:219|397:234,1,76.1450018009148,10|0,0:2|1:2,0:0:0:0: +438,384,56686,2,0,P|444:338|446:293,1,76.1450018009148,2|0,0:0|1:2,0:0:0:0: +287,222,57002,2,0,P|289:259|294:297,1,76.1450018009148,10|0,0:2|0:0,0:0:0:0: +334,124,57318,6,0,P|311:115|285:110,3,38.0725009004574,6|2|2|2,1:2|0:0|0:0|0:0,0:0:0:0: +230,148,57633,2,0,P|201:173|146:180,1,76.1450018009148,6|2,1:2|0:0,0:0:0:0: +42,81,57949,2,0,P|56:112|68:176,1,76.1450018009148,6|0,1:2|0:0,0:0:0:0: +188,17,58265,2,0,P|174:48|162:112,1,76.1450018009148,14|0,0:2|0:0,0:0:0:0: +230,245,58581,6,0,P|265:266|320:270,1,76.1450018009148,6|0,1:2|0:0,0:0:0:0: +146,162,58897,2,0,P|108:169|76:189,1,76.1450018009148,10|0,0:2|1:2,0:0:0:0: +293,188,59212,2,0,P|315:102|318:24,1,152.29000360183,2|8,0:2|0:2,0:0:0:0: +224,147,59686,1,0,0:0:0:0: +405,82,59844,6,0,P|407:124|415:170,1,76.1450018009148,2|0,1:2|0:0,0:0:0:0: +500,268,60160,2,0,P|467:249|410:247,1,76.1450018009148,10|0,0:2|1:2,0:0:0:0: +303,384,60476,2,0,B|401:376|349:337|442:328,1,152.29000360183,2|8,0:0|0:2,0:0:0:0: +311,298,60949,1,0,0:0:0:0: +143,368,61107,6,0,P|155:325|155:273,1,76.1450018009148,2|0,1:2|0:0,0:0:0:0: +63,156,61423,2,0,P|65:193|76:230,1,76.1450018009148,10|0,0:2|1:2,0:0:0:0: +160,367,61739,2,0,P|172:324|172:272,1,76.1450018009148,2|0,0:0|1:2,0:0:0:0: +80,155,62055,2,0,P|82:192|93:229,1,76.1450018009148,10|0,0:2|0:0,0:0:0:0: +184,86,62370,6,0,B|260:109|205:146|318:171,1,152.29000360183,2|10,1:2|0:2,0:0:0:0: +406,65,62844,1,0,1:2:0:0: +473,202,63002,2,0,P|462:240|454:292,1,76.1450018009148,2|0,0:0|1:2,0:0:0:0: +331,146,63318,2,0,P|341:184|349:236,1,76.1450018009148,10|0,0:2|0:0,0:0:0:0: +234,347,63633,1,2,1:2:0:0: +160,216,63791,6,0,P|202:198|234:200,1,76.1450018009148,2|8,0:0|0:2,0:0:0:0: +147,367,64107,2,0,P|109:366|75:350,1,76.1450018009148,2|0,1:2|0:0,0:0:0:0: +35,213,64423,1,2,1:2:0:0: +148,349,64581,2,0,P|110:348|76:332,1,76.1450018009148,10|0,0:2|0:0,0:0:0:0: +18,190,64897,5,2,1:2:0:0: +133,269,65054,2,0,P|143:231|150:180,1,76.1450018009148,2|8,0:0|0:2,0:0:0:0: +224,55,65370,2,0,P|231:127|249:214,1,152.29000360183,2|0,1:2|1:2,0:0:0:0: +367,345,65844,2,0,P|405:365|463:364,1,76.1450018009148,10|0,0:2|0:0,0:0:0:0: +456,181,66160,6,0,P|439:219|428:272,1,76.1450018009148,2|0,1:2|0:0,0:0:0:0: +310,127,66476,2,0,P|327:165|338:218,1,76.1450018009148,10|0,0:2|1:2,0:0:0:0: +452,31,66791,2,0,P|435:69|424:122,1,76.1450018009148,2|0,0:0|1:2,0:0:0:0: +250,41,67107,2,0,P|267:79|278:132,1,76.1450018009148,10|0,0:2|0:0,0:0:0:0: +143,235,67423,6,0,L|54:241,1,76.1450018009148,6|0,1:2|0:0,0:0:0:0: +8,75,67739,2,0,L|97:81,1,76.1450018009148,4|0,1:2|0:0,0:0:0:0: +153,254,68054,2,0,L|-30:266,1,152.29000360183,4|8,1:2|0:2,0:0:0:0: +162,272,68686,6,0,P|153:306|149:343,2,68.2950005731545,6|2|10,1:2|0:0|0:2,0:0:0:0: +264,197,69160,1,2,1:2:0:0: +264,197,69318,2,0,B|339:217|287:248|378:266,1,136.590001146309,2|10,1:2|0:2,0:0:0:0: +477,162,69791,2,0,P|462:186|451:227,1,68.2950005731545,2|0,0:0|1:2,0:0:0:0: +352,127,70107,1,2,1:2:0:0: +352,127,70265,2,0,P|369:156|377:189,1,68.2950005731545,10|0,0:2|1:2,0:0:0:0: +252,75,70581,2,0,B|176:96|234:131|127:146,1,136.590001146309,2|8,1:2|0:2,0:0:0:0: +139,143,71212,6,0,P|125:177|114:231,1,68.2950005731545,2|0,1:2|0:0,0:0:0:0: +197,312,71528,1,10,0:2:0:0: +197,312,71686,1,2,1:2:0:0: +246,212,71844,2,0,P|281:197|322:197,1,68.2950005731545,2|2,1:2|0:0,0:0:0:0: +382,297,72160,1,10,0:2:0:0: +382,297,72318,2,0,P|395:222|417:157,1,136.590001146309,2|0,0:0|1:2,0:0:0:0: +483,40,72791,2,0,P|454:60|408:66,1,68.2950005731545,10|0,0:2|1:2,0:0:0:0: +316,8,73107,1,2,1:2:0:0: +316,8,73265,1,8,0:2:0:0: +213,106,73423,2,0,P|240:125|273:132,1,68.2950005731545,8|0,0:2|0:0,0:0:0:0: +151,36,73739,6,0,P|176:103|187:195,1,136.590001146309,2|10,1:2|0:2,0:0:0:0: +71,297,74212,1,2,1:2:0:0: +71,297,74370,2,0,P|96:230|107:138,1,136.590001146309,2|8,1:2|0:2,0:0:0:0: +217,308,74844,2,0,P|205:264|205:212,1,68.2950005731545,2|0,0:0|1:2,0:0:0:0: +292,129,75160,2,0,P|321:113|364:113,1,68.2950005731545,2|8,1:2|0:2,0:0:0:0: +470,226,75476,1,2,1:2:0:0: +470,226,75633,2,0,P|407:200|322:187,1,136.590001146309,2|10,1:2|0:2,0:0:0:0: +339,187,76265,6,0,P|351:221|357:255,1,68.2950005731545,2|2,1:2|0:0,0:0:0:0: +274,344,76581,1,10,0:2:0:0: +274,344,76739,1,2,1:2:0:0: +196,237,76897,2,0,P|183:277|174:332,1,68.2950005731545,2|0,1:2|0:0,0:0:0:0: +76,200,77212,2,0,P|89:240|98:295,1,68.2950005731545,10|0,0:2|0:0,0:0:0:0: +193,110,77528,6,0,P|225:91|266:91,1,68.2950005731545,2|2,1:2|0:0,0:0:0:0: +363,209,77844,2,0,P|329:205|300:187,1,68.2950005731545,2|2,1:2|0:0,0:0:0:0: +424,69,78160,2,0,P|392:129|373:223,1,136.590001146309,2|10,1:2|0:2,0:0:0:0: +375,195,78791,5,6,0:0:0:0: +59,101,87633,6,0,P|100:79|160:79,1,102.442500859732,2|0,1:2|0:0,0:0:0:0: +157,92,87949,2,0,P|106:92|61:115,1,102.442500859732,2|0,1:2|0:0,0:0:0:0: +65,127,88265,2,0,P|110:103|160:103,1,102.442500859732,2|0,1:2|0:0,0:0:0:0: +162,116,88581,1,6,0:2:0:0: +410,340,88897,6,0,P|428:292|428:236,1,83.9949974366761,6|0,1:2|0:0,0:0:0:0: +329,109,89212,1,10,0:2:0:0: +237,283,89370,2,0,P|219:235|219:179,1,83.9949974366761,2|0,0:2|1:2,0:0:0:0: +412,90,89686,2,0,P|407:131|391:170,1,83.9949974366761,2|8,0:2|0:2,0:0:0:0: +224,11,90002,6,0,P|132:31|99:124,1,167.989994873352,2|2,0:2|0:2,0:0:0:0: +198,242,90476,1,8,0:2:0:0: +197,90,90633,1,2,0:2:0:0: +85,257,90791,2,0,P|94:304|99:355,1,83.9949974366761,6|0,1:2|0:0,0:0:0:0: +308,229,91107,2,0,P|311:187|320:146,1,83.9949974366761,14|0,0:2|0:0,0:0:0:0: +210,341,91423,6,0,P|251:326|325:317,1,83.9949974366761,6|0,1:2|0:0,0:0:0:0: +196,202,91739,1,10,0:2:0:0: +305,335,91897,2,0,P|346:350|420:359,1,83.9949974366761,2|0,0:2|1:2,0:0:0:0: +212,222,92212,2,0,P|253:207|327:198,1,83.9949974366761,2|8,0:2|0:2,0:0:0:0: +446,275,92528,6,0,P|480:177|483:88,1,167.989994873352,2|2,0:2|0:2,0:0:0:0: +286,70,93002,1,8,0:2:0:0: +368,232,93160,1,2,0:2:0:0: +268,50,93318,2,0,P|230:33|158:30,1,83.9949974366761,6|0,1:2|0:0,0:0:0:0: +349,208,93633,2,0,P|310:225|269:230,1,83.9949974366761,14|0,0:2|0:0,0:0:0:0: +138,89,93949,6,0,P|116:133|104:208,1,83.9949974366761,6|0,1:2|0:0,0:0:0:0: +148,304,94265,1,10,0:2:0:0: +22,167,94423,2,0,P|44:211|56:286,1,83.9949974366761,2|0,0:2|1:2,0:0:0:0: +243,347,94739,2,0,P|254:306|273:269,1,83.9949974366761,2|8,0:2|0:2,0:0:0:0: +438,109,95054,6,0,B|340:127|418:167|266:192,1,167.989994873352,6|0,0:2|0:0,0:0:0:0: +254,24,95528,2,0,P|277:62|282:122,1,83.9949974366761,10|0,0:2|0:0,0:0:0:0: +427,285,95844,2,0,P|428:243|443:204,1,83.9949974366761,2|0,1:2|0:0,0:0:0:0: +279,25,96160,2,0,P|302:63|307:123,1,83.9949974366761,10|0,0:2|0:0,0:0:0:0: +225,237,96476,6,0,P|184:225|105:216,1,83.9949974366761,6|0,1:2|0:0,0:0:0:0: +288,132,96791,1,10,0:2:0:0: +180,316,96949,2,0,P|139:328|60:337,1,83.9949974366761,2|0,0:2|1:2,0:0:0:0: +274,159,97265,2,0,P|315:166|355:177,1,83.9949974366761,2|8,0:2|0:2,0:0:0:0: +417,302,97581,1,2,0:2:0:0: +420,94,97739,6,0,P|393:134|376:202,1,83.9949974366761,2|0,1:2|0:0,0:0:0:0: +346,384,98054,1,10,0:2:0:0: +299,208,98212,1,0,0:0:0:0: +337,355,98370,1,2,1:2:0:0: +290,179,98528,1,0,0:0:0:0: +170,364,98686,2,0,P|124:378|65:374,1,83.9949974366761,10|0,0:2|0:0,0:0:0:0: +45,139,99002,6,0,P|70:172|96:263,1,83.9949974366761,6|0,1:2|0:0,0:0:0:0: +164,51,99318,1,10,0:2:0:0: +146,275,99476,2,0,P|106:294|39:288,1,83.9949974366761,2|0,0:2|1:2,0:0:0:0: +163,76,99791,2,0,P|204:78|242:96,1,83.9949974366761,2|8,0:2|0:2,0:0:0:0: +306,272,100107,6,0,P|261:187|261:103,1,167.989994873352,6|2,0:2|0:2,0:0:0:0: +446,105,100581,1,8,0:2:0:0: +376,319,100739,2,0,P|345:348|305:358,1,83.9949974366761,2|0,0:2|1:2,0:0:0:0: +236,147,101054,1,0,0:0:0:0: +402,242,101212,2,0,P|443:245|481:228,1,83.9949974366761,8|0,0:2|0:0,0:0:0:0: +334,39,101528,6,0,P|346:82|350:135,1,83.9949974366761,6|0,1:2|0:0,0:0:0:0: +219,239,101844,1,10,0:2:0:0: +177,71,102002,2,0,P|137:51|71:45,1,83.9949974366761,2|0,0:2|1:2,0:0:0:0: +140,267,102318,2,0,P|181:258|218:239,1,83.9949974366761,2|8,0:2|0:2,0:0:0:0: +22,135,102633,6,0,P|64:254|68:317,1,167.989994873352,6|2,0:2|0:2,0:0:0:0: +182,139,103107,1,8,0:2:0:0: +200,320,103265,2,0,P|209:272|222:225,1,83.9949974366761,2|0,0:2|1:2,0:0:0:0: +337,118,103581,1,0,0:0:0:0: +331,305,103739,2,0,P|322:257|309:210,1,83.9949974366761,8|0,0:2|0:0,0:0:0:0: +194,51,104054,6,0,B|300:74|225:123|355:155,1,167.989994873352,6|10,1:2|0:2,0:0:0:0: +142,226,104528,2,0,P|91:244|21:238,1,83.9949974366761,2|0,0:2|1:2,0:0:0:0: +187,83,104844,2,0,P|148:67|106:63,1,83.9949974366761,2|8,0:2|0:2,0:0:0:0: +210,283,105160,6,0,P|229:235|232:181,1,83.9949974366761,6|0,0:2|1:2,0:0:0:0: +339,35,105476,2,0,P|345:76|362:115,1,83.9949974366761,2|8,0:2|0:2,0:0:0:0: +309,282,105791,1,2,0:2:0:0: +454,125,105949,2,0,P|437:163|431:204,1,83.9949974366761,2|0,1:2|0:0,0:0:0:0: +246,91,106265,2,0,P|262:129|268:170,1,83.9949974366761,10|0,0:2|0:0,0:0:0:0: +133,354,106581,6,0,L|22:361,1,83.9949974366761,6|2,1:2|0:2,0:0:0:0: +260,193,106897,2,0,L|371:200,1,83.9949974366761,10|2,0:2|0:2,0:0:0:0: +127,339,107212,2,0,L|16:346,1,83.9949974366761,2|2,1:2|0:2,0:0:0:0: +254,178,107528,2,0,L|365:185,1,83.9949974366761,10|2,0:2|0:2,0:0:0:0: +479,344,107844,5,2,1:2:0:0: +411,172,108002,1,10,0:2:0:0: +400,363,108160,1,2,1:2:0:0: +488,188,108318,1,2,0:2:0:0: +319,384,108476,2,0,L|312:273,1,83.9949974366761,10|10,0:2|0:2,0:0:0:0: +298,87,108791,1,2,1:2:0:0: +220,275,108949,1,10,0:2:0:0: +163,74,109107,5,14,0:2:0:0: +160,0,110212,5,14,0:2:0:0: +160,0,111002,6,0,P|188:57|194:109,1,90.6674982014809,14|0,0:2|0:0,0:0:0:0: +214,98,111318,2,0,P|191:137|182:176,1,60.4449988009873,14|0,0:2|1:2,0:0:0:0: +202,188,111554,1,0,1:2:0:0: +202,188,111633,1,6,1:2:0:0: +197,204,112739,5,14,0:2:0:0: +197,204,113528,2,0,P|242:224|311:221,1,90.6674982014809,14|0,0:0|0:0,0:0:0:0: +293,200,113844,2,0,P|333:181|366:180,1,60.4449988009873,14|0,0:0|1:0,0:0:0:0: +413,235,114081,5,0,1:0:0:0: +413,235,114160,2,0,P|420:193|433:153,1,83.9949974366761,6|0,1:2|0:0,0:0:0:0: +328,286,114476,1,10,0:2:0:0: +388,95,114633,2,0,P|381:53|368:13,1,83.9949974366761,2|0,0:2|1:2,0:0:0:0: +218,171,114949,2,0,P|225:129|238:89,1,83.9949974366761,2|8,0:0|0:2,0:0:0:0: +114,267,115265,6,0,P|99:177|71:93,1,167.989994873352,6|0,0:0|0:0,0:0:0:0: +206,327,115739,2,0,P|174:359|99:370,1,83.9949974366761,10|0,0:2|0:0,0:0:0:0: +247,175,116054,2,0,P|285:190|314:220,1,83.9949974366761,2|0,1:2|0:0,0:0:0:0: +406,380,116370,2,0,P|411:328|422:274,1,83.9949974366761,8|0,0:0|0:0,0:0:0:0: +477,101,116686,6,0,P|432:104|382:131,1,83.9949974366761,6|0,1:2|0:0,0:0:0:0: +286,270,117002,1,10,0:2:0:0: +210,82,117160,2,0,P|251:84|289:101,1,83.9949974366761,2|0,0:0|1:2,0:0:0:0: +205,284,117476,2,0,P|220:236|227:166,1,83.9949974366761,2|8,0:0|0:2,0:0:0:0: +80,62,117791,6,0,P|113:131|123:259,1,167.989994873352,2|2,0:0|0:0,0:0:0:0: +279,362,118265,5,10,0:2:0:0: +243,170,118423,1,2,0:0:0:0: +306,359,118581,5,2,1:2:0:0: +325,169,118739,1,2,0:0:0:0: +330,355,118897,5,8,0:2:0:0: +402,171,119054,1,10,0:2:0:0: +402,171,119528,6,0,B|239:156|377:58|170:31,1,266.507506303202,12|0,0:2|0:0,0:0:0:0: +184,44,120160,2,0,B|357:69|233:164|392:180,1,266.507506303202,12|0,0:0|0:0,0:0:0:0: +385,190,120791,2,0,B|227:174|351:79|178:54,1,266.507506303202,12|0,0:0|0:0,0:0:0:0: +171,64,121423,2,0,B|344:89|220:184|378:200,1,266.507506303202,12|0,0:0|0:0,0:0:0:0: +373,211,122054,2,0,B|214:194|338:99|165:74,1,266.507506303202,12|0,0:0|0:0,0:0:0:0: +156,90,122686,2,0,P|127:134|109:220,1,114.217502701372,12|0,0:0|0:0,0:0:0:0: +129,218,123002,6,0,P|144:261|158:324,1,76.1450018009148,0|10,1:2|0:2,0:0:0:0: +247,142,123318,1,8,0:2:0:0: +278,283,123475,1,2,1:2:0:0: +339,100,123633,1,8,0:2:0:0: +272,251,123791,1,8,0:2:0:0: +224,58,123949,1,8,0:2:0:0: +286,225,124107,1,8,0:2:0:0: +374,24,124265,6,0,P|414:9|473:9,1,83.9949974366761,6|0,1:2|0:0,0:0:0:0: +368,190,124581,1,10,0:2:0:0: +222,28,124739,2,0,P|182:13|123:13,1,83.9949974366761,2|0,0:0|1:2,0:0:0:0: +62,237,125054,2,0,P|82:187|90:129,1,83.9949974366761,2|8,0:0|0:2,0:0:0:0: +261,271,125370,2,0,P|241:221|233:163,1,83.9949974366761,2|0,0:0|1:2,0:0:0:0: +86,328,125686,2,0,P|37:328|-12:298,1,83.9949974366761,2|8,0:0|0:2,0:0:0:0: +164,160,126002,1,2,0:0:0:0: +235,355,126160,2,0,P|276:356|315:341,1,83.9949974366761,2|0,1:2|0:0,0:0:0:0: +454,180,126476,2,0,P|415:164|373:166,1,83.9949974366761,10|0,0:2|0:0,0:0:0:0: +407,347,126791,6,0,L|399:240,1,83.9949974366761,6|2,1:2|0:2,0:0:0:0: +274,71,127107,2,0,L|267:154,1,83.9949974366761,14|2,0:2|0:0,0:0:0:0: +421,337,127423,2,0,L|413:230,1,83.9949974366761,6|2,1:2|0:0,0:0:0:0: +288,61,127739,2,0,L|281:144,1,83.9949974366761,14|2,0:2|0:0,0:0:0:0: +247,369,128054,5,2,1:2:0:0: +212,184,128212,1,10,0:2:0:0: +251,384,128370,1,10,0:2:0:0: +216,204,128528,1,2,0:0:0:0: +81,380,128686,2,0,L|87:296,1,83.9949974366761,10|10,0:2|0:2,0:0:0:0: +100,65,129002,1,2,1:2:0:0: +163,261,129160,1,10,0:2:0:0: +91,165,129318,5,4,0:2:0:0: +300,51,134370,5,6,1:2:0:0: +300,51,135633,5,4,1:2:0:0: +300,51,136897,6,0,P|260:72|200:81,1,102.442500859732,4|2,1:2|0:0,0:0:0:0: +200,72,137212,2,0,P|250:64|296:41,1,102.442500859732,4|0,1:2|0:0,0:0:0:0: +293,33,137528,2,0,P|247:55|196:63,1,102.442500859732,6|2,1:2|0:0,0:0:0:0: +193,44,137844,1,12,0:2:0:0: +337,298,138160,6,0,P|355:259|359:217,1,68.2950005731545,6|0,1:2|0:0,0:0:0:0: +277,157,138476,1,10,0:2:0:0: +355,302,138633,2,0,P|379:215|380:139,1,136.590001146309,2|2,1:2|1:2,0:0:0:0: +276,58,139107,1,10,0:2:0:0: +276,58,139265,1,2,1:2:0:0: +209,217,139423,6,0,P|170:235|128:239,1,68.2950005731545,6|0,1:2|0:0,0:0:0:0: +68,157,139739,1,10,0:2:0:0: +213,235,139896,2,0,P|126:259|50:260,1,136.590001146309,2|2,1:2|1:2,0:0:0:0: +207,118,140370,1,10,0:2:0:0: +207,118,140528,1,2,1:2:0:0: +308,272,140686,6,0,P|299:306|295:361,1,68.2950005731545,6|0,1:2|0:0,0:0:0:0: +421,220,141002,1,10,0:2:0:0: +293,252,141160,2,0,P|273:317|262:384,1,136.590001146309,2|2,1:2|1:2,0:0:0:0: +392,137,141633,1,10,0:2:0:0: +392,137,141791,1,2,1:2:0:0: +392,137,142265,6,0,P|384:93|322:62,1,102.442500859732,6|0,1:2|0:0,0:0:0:0: +326,79,142581,2,0,P|281:103|200:75,1,136.590001146309,6|12,1:2|0:2,0:0:0:0: +203,78,143212,5,6,1:2:0:0: +214,89,144476,5,4,1:2:0:0: +214,90,145739,6,0,P|249:146|260:207,1,102.442500859732,6|0,1:2|0:0,0:0:0:0: +248,201,146054,2,0,P|213:257|202:318,1,102.442500859732,6|0,1:2|0:0,0:0:0:0: +218,313,146370,2,0,P|265:294|316:291,1,102.442500859732,6|0,1:2|0:0,0:0:0:0: +326,305,146686,1,14,0:2:0:0: +440,83,147002,6,0,L|430:167,1,68.2950005731545,6|0,1:2|0:0,0:0:0:0: +346,18,147318,1,10,0:2:0:0: +457,94,147476,2,0,L|440:231,1,136.590001146309,2|2,1:2|1:2,0:0:0:0: +326,305,147949,1,10,0:2:0:0: +326,305,148107,1,2,1:2:0:0: +170,162,148265,6,0,L|180:246,1,68.2950005731545,6|0,1:2|0:0,0:0:0:0: +264,97,148581,1,10,0:2:0:0: +153,173,148739,2,0,L|170:310,1,136.590001146309,2|2,1:2|1:2,0:0:0:0: +284,384,149212,1,10,0:2:0:0: +284,384,149370,1,2,1:2:0:0: +403,159,149528,6,0,L|393:243,1,68.2950005731545,6|0,1:2|0:0,0:0:0:0: +309,94,149844,1,10,0:2:0:0: +420,170,150002,2,0,L|403:307,1,136.590001146309,2|2,1:2|1:2,0:0:0:0: +289,381,150475,1,10,0:2:0:0: +289,381,150633,1,2,1:2:0:0: +97,68,151107,6,0,P|140:48|196:63,1,102.442500859732,4|0,1:2|0:0,0:0:0:0: +198,79,151423,2,0,P|154:129|139:218,1,136.590001146309,4|12,1:2|0:0,0:0:0:0: +297,317,152054,6,0,B|391:288|336:242|424:215,1,152.29000360183,6|8,1:2|0:2,0:0:0:0: +281,212,152528,1,0,1:2:0:0: +446,306,152686,2,0,P|490:265|476:153,1,152.29000360183,2|8,0:0|0:2,0:0:0:0: +343,142,153160,1,2,1:2:0:0: +297,317,153318,6,0,P|226:345|155:276,1,152.29000360183,6|8,1:2|0:2,0:0:0:0: +116,157,153791,2,0,P|150:228|158:309,1,152.29000360183,0|0,1:2|1:2,0:0:0:0: +264,170,154265,2,0,P|244:206|235:263,1,76.1450018009148,10|0,0:2|1:2,0:0:0:0: +152,77,154581,6,0,P|84:75|30:158,1,152.29000360183,6|8,1:2|0:2,0:0:0:0: +191,214,155054,1,0,1:2:0:0: +264,60,155212,2,0,P|331:58|385:141,1,152.29000360183,2|8,0:0|0:2,0:0:0:0: +212,171,155686,1,0,1:2:0:0: +405,112,155844,6,0,P|379:165|357:279,1,152.29000360183,6|8,1:2|0:2,0:0:0:0: +158,360,156318,2,0,P|142:285|111:216,1,152.29000360183,0|0,1:2|1:2,0:0:0:0: +9,64,156791,2,0,P|45:87|104:95,1,76.1450018009148,10|0,0:2|1:2,0:0:0:0: +270,12,157107,6,0,P|187:35|179:115,1,152.29000360183,6|8,1:2|0:2,0:0:0:0: +288,228,157581,2,0,P|370:204|378:124,1,152.29000360183,0|0,1:2|1:2,0:0:0:0: +248,83,158054,2,0,P|280:97|327:97,1,76.1450018009148,10|0,0:2|1:2,0:0:0:0: +490,16,158370,6,0,P|451:77|433:186,1,152.29000360183,6|8,1:2|0:2,0:0:0:0: +467,312,158844,2,0,P|449:238|409:173,1,152.29000360183,0|0,1:2|1:2,0:0:0:0: +248,208,159318,2,0,P|292:207|331:188,1,76.1450018009148,8|0,0:2|1:2,0:0:0:0: +320,98,159633,5,2,0:0:0:0: +118,79,160897,6,0,L|127:219,1,114.217502701372,2|0,1:2|0:0,0:0:0:0: +146,197,161212,2,0,L|138:83,1,114.217502701372,2|0,1:2|0:0,0:0:0:0: +158,87,161528,2,0,L|166:201,1,114.217502701372,2|0,1:2|0:0,0:0:0:0: +185,203,161844,1,6,0:2:0:0: +39,359,162160,6,0,P|21:311|21:255,1,83.9949974366761,6|0,1:2|0:0,0:0:0:0: +153,158,162475,1,10,0:2:0:0: +221,372,162633,2,0,P|239:324|239:268,1,83.9949974366761,2|0,0:2|1:2,0:0:0:0: +64,135,162949,2,0,P|69:176|85:215,1,83.9949974366761,2|8,0:2|0:2,0:0:0:0: +244,41,163265,6,0,P|336:61|369:154,1,167.989994873352,2|2,0:2|0:2,0:0:0:0: +322,264,163739,1,8,0:2:0:0: +282,124,163896,1,2,0:2:0:0: +419,289,164054,2,0,P|410:336|405:387,1,83.9949974366761,6|0,1:2|0:0,0:0:0:0: +214,234,164370,2,0,P|211:192|202:151,1,83.9949974366761,14|0,0:2|0:0,0:0:0:0: +295,355,164686,6,0,P|254:340|180:331,1,83.9949974366761,6|0,1:2|0:0,0:0:0:0: +305,196,165002,1,10,0:2:0:0: +209,350,165160,2,0,P|168:365|94:374,1,83.9949974366761,2|0,0:2|1:2,0:0:0:0: +294,219,165475,2,0,P|253:204|179:195,1,83.9949974366761,2|8,0:2|0:2,0:0:0:0: +66,275,165791,6,0,P|32:177|29:88,1,167.989994873352,2|2,0:2|0:2,0:0:0:0: +226,70,166265,1,8,0:2:0:0: +144,232,166423,1,2,0:2:0:0: +244,50,166581,2,0,P|282:33|354:30,1,83.9949974366761,6|0,1:2|0:0,0:0:0:0: +163,208,166896,2,0,P|202:225|243:230,1,83.9949974366761,14|0,0:2|0:0,0:0:0:0: +374,89,167212,6,0,P|396:133|408:208,1,83.9949974366761,6|0,1:2|0:0,0:0:0:0: +364,304,167528,1,10,0:2:0:0: +490,167,167686,2,0,P|468:211|456:286,1,83.9949974366761,2|0,0:2|1:2,0:0:0:0: +269,347,168002,2,0,P|258:306|239:269,1,83.9949974366761,2|8,0:2|0:2,0:0:0:0: +74,109,168317,6,0,B|172:127|94:167|246:192,1,167.989994873352,6|0,0:2|0:0,0:0:0:0: +258,24,168791,2,0,P|235:62|230:122,1,83.9949974366761,10|0,0:2|0:0,0:0:0:0: +85,285,169107,2,0,P|84:243|69:204,1,83.9949974366761,2|0,1:2|0:0,0:0:0:0: +233,25,169423,2,0,P|210:63|205:123,1,83.9949974366761,10|0,0:2|0:0,0:0:0:0: +296,252,169739,6,0,P|337:240|416:231,1,83.9949974366761,6|0,1:2|0:0,0:0:0:0: +224,132,170054,1,10,0:2:0:0: +331,331,170212,2,0,P|372:343|451:352,1,83.9949974366761,2|0,0:2|1:2,0:0:0:0: +238,159,170528,2,0,P|197:166|157:177,1,83.9949974366761,2|8,0:2|0:2,0:0:0:0: +95,302,170844,1,2,0:2:0:0: +92,94,171002,6,0,P|119:134|136:202,1,83.9949974366761,2|0,1:2|0:0,0:0:0:0: +243,353,171317,1,10,0:2:0:0: +218,162,171475,1,0,0:0:0:0: +237,323,171633,1,2,1:2:0:0: +212,132,171791,1,0,0:0:0:0: +328,311,171949,2,0,P|372:330|433:321,1,83.9949974366761,10|0,0:2|0:0,0:0:0:0: +447,131,172265,6,0,P|422:164|396:255,1,83.9949974366761,6|0,1:2|0:0,0:0:0:0: +349,97,172581,1,10,0:2:0:0: +337,298,172739,2,0,P|381:317|442:308,1,83.9949974366761,2|0,0:2|1:2,0:0:0:0: +335,81,173054,2,0,P|294:83|256:101,1,83.9949974366761,2|8,0:2|0:2,0:0:0:0: +195,272,173370,6,0,P|240:187|240:103,1,167.989994873352,6|2,0:2|0:2,0:0:0:0: +66,105,173844,1,8,0:2:0:0: +125,318,174002,2,0,P|156:347|196:357,1,83.9949974366761,2|0,0:2|1:2,0:0:0:0: +276,147,174317,1,0,0:0:0:0: +104,236,174475,2,0,P|63:239|25:222,1,83.9949974366761,8|0,0:2|0:0,0:0:0:0: +178,39,174791,6,0,P|166:82|162:135,1,83.9949974366761,6|0,1:2|0:0,0:0:0:0: +293,239,175107,1,10,0:2:0:0: +335,71,175265,2,0,P|375:51|441:45,1,83.9949974366761,2|0,0:2|1:2,0:0:0:0: +366,284,175581,2,0,P|325:275|288:256,1,83.9949974366761,2|8,0:2|0:2,0:0:0:0: +490,135,175896,6,0,P|448:254|444:317,1,167.989994873352,6|2,0:2|0:2,0:0:0:0: +330,139,176370,1,8,0:2:0:0: +312,320,176528,2,0,P|303:272|290:225,1,83.9949974366761,2|0,0:2|1:2,0:0:0:0: +175,118,176844,1,0,0:0:0:0: +181,305,177002,2,0,P|190:257|203:210,1,83.9949974366761,8|0,0:2|0:0,0:0:0:0: +318,51,177317,6,0,B|212:74|287:123|157:155,1,167.989994873352,6|10,1:2|0:2,0:0:0:0: +370,226,177791,2,0,P|421:244|491:238,1,83.9949974366761,2|0,0:2|1:2,0:0:0:0: +325,83,178107,2,0,P|364:67|406:63,1,83.9949974366761,2|8,0:2|0:2,0:0:0:0: +302,283,178423,6,0,P|283:235|280:181,1,83.9949974366761,6|0,0:2|1:2,0:0:0:0: +173,35,178739,2,0,P|167:76|150:115,1,83.9949974366761,2|8,0:2|0:2,0:0:0:0: +203,282,179054,1,2,0:2:0:0: +58,125,179212,2,0,P|75:163|81:204,1,83.9949974366761,2|0,1:2|0:0,0:0:0:0: +266,91,179528,2,0,P|250:129|244:170,1,83.9949974366761,10|0,0:2|0:0,0:0:0:0: +379,354,179844,6,0,L|490:361,1,83.9949974366761,6|2,1:2|0:2,0:0:0:0: +252,193,180160,2,0,L|141:200,1,83.9949974366761,10|2,0:2|0:2,0:0:0:0: +385,339,180475,2,0,L|496:346,1,83.9949974366761,2|2,1:2|0:2,0:0:0:0: +258,178,180791,2,0,L|147:185,1,83.9949974366761,10|2,0:2|0:2,0:0:0:0: +295,333,181107,5,2,1:2:0:0: +334,153,181265,1,10,0:2:0:0: +306,325,181423,1,2,1:2:0:0: +347,148,181581,1,2,0:2:0:0: +317,319,181739,2,0,L|324:208,1,83.9949974366761,10|10,0:2|0:2,0:0:0:0: +237,65,182054,1,2,1:2:0:0: +440,112,182212,1,10,0:2:0:0: +225,77,182370,5,14,0:2:0:0: +173,281,183476,5,14,0:2:0:0: +173,281,184265,6,0,L|263:276,1,90.6674982014809,14|0,0:2|0:0,0:0:0:0: +266,265,184581,2,0,L|183:268,1,60.4449988009873,14|0,0:2|1:2,0:0:0:0: +180,254,184818,1,0,1:2:0:0: +180,254,184897,1,6,1:2:0:0: +402,65,186002,5,14,0:2:0:0: +402,65,186791,6,0,L|311:60,1,90.6674982014809,14|0,0:0|0:0,0:0:0:0: +309,49,187107,2,0,L|397:54,1,60.4449988009873,14|0,0:0|1:0,0:0:0:0: +432,107,187344,5,0,1:0:0:0: +432,107,187423,2,0,P|420:151|413:220,1,83.9949974366761,6|0,1:2|0:0,0:0:0:0: +460,324,187739,1,10,0:2:0:0: +270,233,187897,2,0,P|263:191|252:151,1,83.9949974366761,2|0,0:2|1:2,0:0:0:0: +345,361,188212,2,0,P|351:319|362:279,1,83.9949974366761,2|8,0:0|0:2,0:0:0:0: +223,129,188528,6,0,B|121:153|190:198|70:228,1,167.989994873352,6|0,0:0|0:0,0:0:0:0: +195,36,189002,2,0,P|245:36|304:61,1,83.9949974366761,10|0,0:2|0:0,0:0:0:0: +315,225,189318,2,0,P|265:225|206:200,1,83.9949974366761,2|0,1:2|0:0,0:0:0:0: +426,87,189633,2,0,P|406:131|393:207,1,83.9949974366761,8|0,0:0|0:0,0:0:0:0: +370,384,189949,6,0,P|344:289|302:200,1,167.989994873352,6|10,1:2|0:2,0:0:0:0: +190,82,190423,2,0,P|153:65|108:64,1,83.9949974366761,2|0,0:0|1:2,0:0:0:0: +221,254,190739,2,0,P|262:253|300:236,1,83.9949974366761,2|8,0:0|0:2,0:0:0:0: +189,116,191054,1,2,0:0:0:0: +378,11,191212,6,0,P|348:92|339:178,1,167.989994873352,2|10,1:2|0:2,0:0:0:0: +465,289,191686,1,2,0:0:0:0: +363,105,191844,2,0,P|354:153|356:219,1,83.9949974366761,2|2,1:2|0:0,0:0:0:0: +421,369,192160,1,8,0:2:0:0: +421,369,192318,1,10,0:2:0:0: +221,263,192791,6,0,B|384:248|246:150|453:123,1,266.507506303202,12|0,0:2|0:0,0:0:0:0: +439,136,193423,2,0,B|266:161|390:256|231:272,1,266.507506303202,12|0,0:0|0:0,0:0:0:0: +238,282,194054,2,0,B|396:266|272:171|445:146,1,266.507506303202,12|0,0:0|0:0,0:0:0:0: +452,156,194686,2,0,B|279:181|403:276|245:292,1,266.507506303202,12|0,0:0|0:0,0:0:0:0: +250,303,195317,2,0,B|409:286|285:191|458:166,1,266.507506303202,12|0,0:0|0:0,0:0:0:0: +461,188,195949,2,0,P|458:133|433:68,1,114.217502701372,12|0,0:0|0:0,0:0:0:0: +411,61,196265,6,0,P|376:85|317:90,1,76.1450018009148,0|10,1:2|0:2,0:0:0:0: +136,47,196581,1,8,0:2:0:0: +314,7,196739,1,2,1:2:0:0: +120,52,196897,1,8,0:2:0:0: +298,12,197055,1,8,0:2:0:0: +104,58,197212,2,0,P|96:101|91:167,1,76.1450018009148,8|8,0:2|0:2,0:0:0:0: +136,317,197528,6,0,P|205:285|247:284,1,83.9949974366761,6|0,1:2|0:0,0:0:0:0: +384,371,197844,1,10,0:2:0:0: +317,207,198002,2,0,P|248:175|206:174,1,83.9949974366761,2|0,0:0|1:2,0:0:0:0: +373,345,198318,2,0,P|413:334|448:311,1,83.9949974366761,2|8,0:0|0:2,0:0:0:0: +436,127,198633,6,0,P|419:169|412:229,1,83.9949974366761,2|0,0:0|1:2,0:0:0:0: +264,23,198949,2,0,P|281:65|288:125,1,83.9949974366761,2|8,0:0|0:2,0:0:0:0: +242,254,199265,1,2,0:0:0:0: +414,124,199423,2,0,P|397:166|390:226,1,83.9949974366761,2|0,1:2|0:0,0:0:0:0: +214,266,199739,2,0,P|206:224|190:186,1,83.9949974366761,10|0,0:2|0:0,0:0:0:0: +38,39,200054,6,0,L|49:128,1,83.9949974366761,6|2,1:2|0:2,0:0:0:0: +86,302,200370,2,0,L|96:218,1,83.9949974366761,14|2,0:2|0:0,0:0:0:0: +48,34,200686,2,0,L|59:123,1,83.9949974366761,6|2,1:2|0:0,0:0:0:0: +96,297,201002,2,0,L|106:213,1,83.9949974366761,14|2,0:2|0:0,0:0:0:0: +223,68,201318,5,2,1:2:0:0: +211,238,201476,1,10,0:2:0:0: +239,61,201633,1,10,0:2:0:0: +227,231,201791,1,2,0:0:0:0: +255,52,201949,2,0,L|239:170,1,83.9949974366761,10|10,0:2|0:2,0:0:0:0: +218,340,202265,1,2,1:2:0:0: +309,179,202423,1,10,0:2:0:0: +328,301,202581,5,6,0:2:0:0: +459,23,203528,6,0,L|374:30,1,60.4449988009873,14|2,0:2|0:2,0:0:0:0: +305,177,203844,5,6,1:2:0:0: +305,177,204002,1,2,0:2:0:0: +264,26,204160,1,10,0:2:0:0: +264,26,204318,1,2,0:2:0:0: +210,186,204476,1,2,1:2:0:0: +210,186,204633,2,0,L|203:288,1,76.1450018009148,2|8,0:2|0:2,0:0:0:0: +62,159,204949,6,0,L|69:261,1,76.1450018009148,2|0,0:0|1:2,0:0:0:0: +192,357,205265,2,0,P|232:356|272:325,1,76.1450018009148,2|8,0:0|0:2,0:0:0:0: +398,216,205581,2,0,P|365:197|327:197,1,76.1450018009148,2|4,0:0|1:2,0:0:0:0: +407,341,205897,1,2,0:0:0:0: +493,184,206054,2,0,P|487:146|478:109,1,76.1450018009148,14|2,0:2|0:0,0:0:0:0: +311,23,206370,6,0,P|278:40|225:40,1,76.1450018009148,4|2,1:2|0:0,0:0:0:0: +76,13,206686,1,10,0:2:0:0: +76,13,206844,1,2,0:0:0:0: +186,145,207002,1,2,1:2:0:0: +186,145,207160,2,0,P|219:162|257:164,1,76.1450018009148,2|10,0:0|0:2,0:0:0:0: +102,30,207476,6,0,P|132:97|145:198,1,152.29000360183,2|0,0:0|0:0,0:0:0:0: +73,352,207949,1,8,0:2:0:0: +73,352,208107,1,0,0:0:0:0: +188,244,208265,1,6,1:2:0:0: +188,244,208423,2,0,P|245:224|279:232,1,76.1450018009148,2|14,0:0|0:2,0:0:0:0: +356,326,208739,1,2,0:0:0:0: +428,170,208897,6,0,P|450:206|462:261,1,76.1450018009148,6|2,1:2|0:0,0:0:0:0: +320,106,209212,1,10,0:2:0:0: +320,106,209370,1,2,0:0:0:0: +347,275,209528,1,2,1:2:0:0: +347,275,209686,1,2,0:0:0:0: +228,135,209844,1,10,0:2:0:0: +135,283,210002,6,0,B|142:192|95:232|109:126,1,152.29000360183,6|2,0:0|0:0,0:0:0:0: +226,12,210476,1,8,0:2:0:0: +226,12,210633,1,2,0:0:0:0: +188,167,210791,2,0,P|210:206|215:264,1,76.1450018009148,2|0,1:2|0:0,0:0:0:0: +289,102,211107,1,10,0:2:0:0: +289,102,211265,1,2,0:0:0:0: +357,254,211423,6,0,P|335:293|330:351,1,76.1450018009148,6|0,1:2|0:0,0:0:0:0: +320,177,211739,1,8,0:2:0:0: +420,337,211897,2,0,P|438:270|437:185,1,152.29000360183,2|2,0:0|0:0,0:0:0:0: +330,24,212370,1,8,0:2:0:0: +188,167,212528,6,0,P|186:242|205:316,1,152.29000360183,6|0,0:0|0:0,0:0:0:0: +89,221,213002,1,12,0:2:0:0: +89,221,213160,1,0,0:0:0:0: +205,316,213318,2,0,P|247:311|292:280,1,76.1450018009148,4|0,1:2|0:0,0:0:0:0: +355,148,213633,1,12,0:2:0:0: +355,148,213791,1,0,0:0:0:0: +377,310,213949,6,0,P|360:265|358:210,1,76.1450018009148,6|2,1:2|0:0,0:0:0:0: +229,84,214265,2,0,P|223:121|208:156,1,76.1450018009148,10|0,0:2|0:0,0:0:0:0: +109,231,214581,1,2,1:2:0:0: +109,231,214739,1,2,0:0:0:0: +176,22,214897,2,0,P|211:7|249:5,1,76.1450018009148,10|4,0:2|0:0,0:0:0:0: +343,176,215212,5,2,1:2:0:0: +343,176,215370,1,2,0:0:0:0: +304,15,215528,1,10,0:2:0:0: +304,15,215686,1,2,0:0:0:0: +425,197,215844,2,0,P|459:212|516:204,1,76.1450018009148,2|0,1:2|0:0,0:0:0:0: +386,33,216160,2,0,P|348:32|313:47,1,76.1450018009148,8|0,0:2|0:0,0:0:0:0: +269,217,216476,6,0,P|303:301|320:394,1,152.29000360183,6|10,1:2|0:2,0:0:0:0: +343,178,216949,1,0,0:0:0:0: +192,259,217107,2,0,P|183:301|180:354,1,76.1450018009148,2|2,1:2|0:0,0:0:0:0: +73,212,217423,1,10,0:2:0:0: +73,212,217581,1,4,0:0:0:0: +197,75,217739,6,0,B|295:94|237:137|354:161,1,152.29000360183,2|8,1:2|0:2,0:0:0:0: +194,159,218212,1,0,0:0:0:0: +345,61,218370,2,0,P|394:48|452:48,1,76.1450018009148,6|0,1:2|0:0,0:0:0:0: +416,260,218686,2,0,P|378:255|341:245,1,76.1450018009148,14|0,0:2|0:0,0:0:0:0: +485,93,219002,6,0,P|451:161|435:252,1,152.29000360183,6|8,1:2|0:2,0:0:0:0: +339,360,219476,1,0,0:0:0:0: +374,147,219633,2,0,P|408:215|424:306,1,152.29000360183,0|10,1:2|0:2,0:0:0:0: +248,368,220107,1,6,0:0:0:0: +201,179,220265,5,2,1:2:0:0: +201,179,220423,1,2,0:0:0:0: +239,341,220581,1,10,0:2:0:0: +239,341,220739,1,2,0:0:0:0: +122,203,220897,2,0,P|88:189|38:184,1,76.1450018009148,2|0,1:2|0:0,0:0:0:0: +257,253,221212,2,0,P|294:247|329:233,1,76.1450018009148,8|0,0:2|0:0,0:0:0:0: +442,40,221528,6,0,L|434:149,1,76.1450018009148,6|2,1:2|0:0,0:0:0:0: +417,284,221844,2,0,L|411:208,1,76.1450018009148,10|2,0:2|0:0,0:0:0:0: +336,36,222160,2,0,L|328:145,1,76.1450018009148,2|2,1:2|0:0,0:0:0:0: +311,280,222476,2,0,L|305:204,1,76.1450018009148,10|2,0:2|0:0,0:0:0:0: +165,91,222791,5,2,1:2:0:0: +143,229,222949,1,10,0:2:0:0: +156,57,223107,1,10,0:2:0:0: +125,249,223265,1,2,0:0:0:0: +142,30,223423,2,0,L|67:25,1,76.1450018009148,2|10,1:2|0:2,0:0:0:0: +209,171,223739,1,2,1:2:0:0: +3,159,223897,1,10,0:2:0:0: +111,129,224054,5,6,0:2:0:0: +82,60,234160,5,2,1:2:0:0: +82,60,234476,5,2,1:2:0:0: +82,60,234791,5,2,1:2:0:0: +82,60,235107,5,6,0:2:0:0: +312,238,235423,6,0,P|360:258|414:258,1,83.9949974366761,6|0,1:2|0:0,0:0:0:0: +262,105,235739,1,10,0:2:0:0: +170,284,235897,2,0,P|122:304|68:304,1,83.9949974366761,2|0,0:0|1:2,0:0:0:0: +83,113,236212,2,0,P|101:157|110:208,1,83.9949974366761,2|8,0:0|0:2,0:0:0:0: +258,40,236528,6,0,P|226:117|210:207,1,167.989994873352,2|2,0:0|0:0,0:0:0:0: +327,323,237002,1,10,0:2:0:0: +170,284,237160,1,2,0:0:0:0: +316,147,237318,2,0,P|361:132|413:134,1,83.9949974366761,6|0,1:2|0:0,0:0:0:0: +417,319,237633,2,0,P|372:304|320:306,1,83.9949974366761,14|0,0:2|0:0,0:0:0:0: +153,376,237949,6,0,P|177:308|188:208,1,167.989994873352,6|10,1:2|0:2,0:0:0:0: +81,67,238423,2,0,P|85:113|102:165,1,83.9949974366761,2|0,0:0|1:2,0:0:0:0: +277,190,238739,2,0,P|288:149|291:107,1,83.9949974366761,2|8,0:0|0:2,0:0:0:0: +429,281,239054,6,0,P|412:222|402:108,1,167.989994873352,2|2,0:0|0:0,0:0:0:0: +252,12,239528,1,10,0:2:0:0: +383,93,239686,1,2,0:0:0:0: +224,0,239844,2,0,P|237:44|245:90,1,83.9949974366761,6|0,1:2|0:0,0:0:0:0: +282,275,240160,2,0,P|294:234|301:193,1,83.9949974366761,14|0,0:2|0:0,0:0:0:0: +155,74,240476,6,0,P|110:54|58:51,1,83.9949974366761,6|0,1:2|0:0,0:0:0:0: +177,214,240791,1,10,0:2:0:0: +285,27,240949,2,0,P|330:7|382:4,1,83.9949974366761,2|0,0:0|1:2,0:0:0:0: +190,181,241265,2,0,P|145:161|93:158,1,83.9949974366761,2|8,0:0|0:2,0:0:0:0: +350,91,241581,6,0,P|370:154|363:271,1,167.989994873352,6|0,0:0|0:0,0:0:0:0: +172,349,242054,2,0,P|212:328|267:318,1,83.9949974366761,10|0,0:2|0:0,0:0:0:0: +94,180,242370,2,0,P|134:200|189:210,1,83.9949974366761,2|0,1:2|0:0,0:0:0:0: +256,347,242686,2,0,P|215:357|177:376,1,83.9949974366761,10|0,0:2|0:0,0:0:0:0: +291,209,243002,6,0,P|306:160|309:104,1,83.9949974366761,6|0,1:2|0:0,0:0:0:0: +386,277,243318,1,10,0:2:0:0: +225,165,243476,2,0,P|210:116|207:60,1,83.9949974366761,2|0,0:0|1:2,0:0:0:0: +406,36,243791,2,0,P|400:77|387:117,1,83.9949974366761,2|8,0:0|0:2,0:0:0:0: +308,225,244107,1,2,0:0:0:0: +246,15,244265,6,0,P|196:10|149:27,1,83.9949974366761,2|2,1:2|0:0,0:0:0:0: +89,217,244581,1,10,0:2:0:0: +89,217,244739,1,2,0:0:0:0: +242,41,244897,2,0,P|192:36|145:53,1,83.9949974366761,2|2,1:2|0:0,0:0:0:0: +189,240,245212,1,10,0:2:0:0: +189,240,245370,1,2,0:0:0:0: +311,93,245528,6,0,P|355:75|401:75,1,83.9949974366761,6|0,1:2|0:0,0:0:0:0: +400,292,245844,1,10,0:2:0:0: +250,154,246002,2,0,P|211:137|170:134,1,83.9949974366761,2|0,0:0|1:2,0:0:0:0: +320,311,246318,2,0,P|361:308|399:291,1,83.9949974366761,2|8,0:0|0:2,0:0:0:0: +488,108,246633,6,0,P|474:150|464:206,1,83.9949974366761,6|0,0:0|1:2,0:0:0:0: +314,323,246949,2,0,P|305:281|292:242,1,83.9949974366761,2|8,0:0|0:2,0:0:0:0: +202,67,247265,2,0,P|163:54|67:145,1,167.989994873352,2|0,0:0|0:0,0:0:0:0: +190,256,247739,1,8,0:2:0:0: +200,100,247897,1,0,0:0:0:0: +188,283,248054,6,0,P|228:311|277:313,1,83.9949974366761,6|0,1:2|0:0,0:0:0:0: +342,145,248370,1,10,0:2:0:0: +338,350,248528,2,0,P|359:307|368:260,1,83.9949974366761,2|0,0:0|1:2,0:0:0:0: +290,80,248844,2,0,P|300:120|319:158,1,83.9949974366761,2|8,0:0|0:2,0:0:0:0: +432,320,249160,6,0,P|453:277|462:230,1,83.9949974366761,6|0,0:0|1:2,0:0:0:0: +384,50,249476,2,0,P|394:90|413:128,1,83.9949974366761,2|8,0:0|0:2,0:0:0:0: +449,329,249791,2,0,P|479:256|487:165,1,167.989994873352,2|0,0:0|0:0,0:0:0:0: +351,34,250265,1,12,0:2:0:0: +312,187,250423,1,0,0:0:0:0: +196,18,250581,6,0,P|215:60|224:126,1,83.9949974366761,6|0,1:2|0:0,0:0:0:0: +161,257,250897,1,10,0:2:0:0: +88,110,251054,2,0,P|69:152|60:218,1,83.9949974366761,2|0,0:0|1:2,0:0:0:0: +188,336,251370,2,0,P|178:295|161:257,1,83.9949974366761,2|8,0:0|0:2,0:0:0:0: +206,65,251686,6,0,P|265:46|305:46,1,83.9949974366761,6|0,0:0|1:2,0:0:0:0: +381,245,252002,2,0,P|339:240|300:224,1,83.9949974366761,2|8,0:0|0:2,0:0:0:0: +430,103,252318,1,2,0:0:0:0: +440,308,252476,2,0,P|463:261|466:209,1,83.9949974366761,2|0,1:2|0:0,0:0:0:0: +349,86,252791,2,0,P|342:127|322:163,1,83.9949974366761,10|0,0:2|0:0,0:0:0:0: +217,345,253107,5,6,1:2:0:0: +229,190,253265,1,2,0:2:0:0: +235,365,253423,1,10,0:2:0:0: +225,169,253581,2,0,P|187:144|119:129,1,83.9949974366761,2|2,0:2|1:2,0:0:0:0: +318,271,253897,1,2,0:2:0:0: +337,90,254054,1,10,0:2:0:0: +407,267,254212,5,2,0:2:0:0: +407,267,254291,1,2,0:2:0:0: +407,267,254370,2,0,L|419:155,1,83.9949974366761,2|10,1:2|0:2,0:0:0:0: +282,25,254686,1,10,0:2:0:0: +314,248,254844,2,0,L|302:136,1,83.9949974366761,2|10,0:2|0:2,0:0:0:0: +150,22,255160,1,10,0:2:0:0: +297,137,255318,1,2,1:2:0:0: +74,180,255476,1,10,0:2:0:0: +184,109,255633,5,6,0:0:0:0: +66,184,259423,6,0,P|114:169|135:169,1,60.4449988009873,6|0,1:2|1:2,0:0:0:0: +227,278,259739,2,0,P|254:289|284:293,1,60.4449988009873,0|0,1:2|1:2,0:0:0:0: +374,106,260054,1,6,1:2:0:0: +399,293,260212,1,2,1:2:0:0: +455,78,260370,1,8,0:2:0:0: +396,261,260528,1,8,0:2:0:0: +288,83,260686,6,0,P|242:58|191:51,1,83.9949974366761,6|0,1:2|0:0,0:0:0:0: +83,215,261002,1,10,0:2:0:0: +120,39,261160,2,0,P|139:75|150:135,1,83.9949974366761,2|0,0:2|1:2,0:0:0:0: +168,286,261476,2,0,P|177:245|197:208,1,83.9949974366761,2|8,0:0|0:2,0:0:0:0: +300,62,261791,6,0,B|402:90|337:130|449:151,1,167.989994873352,6|0,0:0|0:0,0:0:0:0: +319,285,262265,2,0,P|306:238|300:177,1,83.9949974366761,10|0,0:2|0:0,0:0:0:0: +160,42,262581,2,0,P|153:83|142:123,1,83.9949974366761,2|0,1:2|0:0,0:0:0:0: +297,272,262897,2,0,P|284:225|278:164,1,83.9949974366761,8|0,0:0|0:0,0:0:0:0: +430,55,263212,6,0,P|470:39|518:40,1,83.9949974366761,6|0,1:2|0:0,0:0:0:0: +401,194,263528,1,10,0:2:0:0: +282,28,263686,2,0,P|242:12|194:13,1,83.9949974366761,2|0,0:0|1:2,0:0:0:0: +124,200,264002,2,0,P|165:199|204:183,1,83.9949974366761,2|8,0:0|0:2,0:0:0:0: +93,85,264318,1,2,0:0:0:0: +61,277,264476,6,0,P|72:313|77:364,1,83.9949974366761,2|2,1:2|0:0,0:0:0:0: +229,203,264791,2,0,P|217:239|213:290,1,83.9949974366761,10|2,0:2|0:0,0:0:0:0: +358,126,265107,2,0,P|369:162|374:213,1,83.9949974366761,2|2,1:2|0:0,0:0:0:0: +470,69,265423,1,8,0:2:0:0: +470,69,265581,1,10,0:2:0:0: +149,40,266054,6,0,P|184:78|242:292,1,266.507506303202,12|0,0:2|0:0,0:0:0:0: +253,277,266686,2,0,P|233:146|158:37,1,266.507506303202,12|0,0:0|0:0,0:0:0:0: +168,33,267318,2,0,P|203:71|261:285,1,266.507506303202,12|0,0:0|0:0,0:0:0:0: +272,270,267949,2,0,P|252:139|177:30,1,266.507506303202,12|0,0:0|0:0,0:0:0:0: +187,23,268581,2,0,P|262:131|281:262,1,266.507506303202,12|0,0:0|0:0,0:0:0:0: +294,261,269212,2,0,P|303:193|327:142,1,114.217502701372,12|0,0:0|0:0,0:0:0:0: +340,145,269528,6,0,P|363:175|378:212,1,76.1450018009148,0|10,1:2|0:2,0:0:0:0: +447,373,269844,1,8,0:2:0:0: +465,198,270002,1,2,1:2:0:0: +450,358,270160,1,8,0:2:0:0: +468,183,270318,1,8,0:2:0:0: +344,367,270476,2,0,P|303:380|248:380,1,76.1450018009148,8|8,0:2|0:2,0:0:0:0: +146,242,270791,6,0,P|129:193|126:127,1,83.9949974366761,6|0,1:2|0:0,0:0:0:0: +264,74,271107,1,10,0:2:0:0: +218,287,271265,2,0,P|181:318|119:326,1,83.9949974366761,2|0,0:0|1:2,0:0:0:0: +245,153,271581,2,0,P|284:165|315:193,1,83.9949974366761,2|8,0:0|0:2,0:0:0:0: +349,382,271897,6,0,P|337:335|337:292,1,83.9949974366761,2|0,0:0|1:2,0:0:0:0: +446,128,272212,2,0,P|444:169|433:210,1,83.9949974366761,2|8,0:0|0:2,0:0:0:0: +324,72,272528,1,2,0:0:0:0: +415,294,272686,2,0,P|464:289|506:260,1,83.9949974366761,2|0,1:2|0:0,0:0:0:0: +349,149,273002,2,0,P|307:151|270:170,1,83.9949974366761,10|0,0:2|0:0,0:0:0:0: +148,303,273318,6,0,P|129:259|127:210,1,83.9949974366761,6|2,1:2|0:2,0:0:0:0: +199,70,273633,1,14,0:2:0:0: +247,249,273791,2,0,P|266:205|268:156,1,83.9949974366761,2|6,0:0|1:2,0:0:0:0: +242,3,274107,1,2,0:0:0:0: +143,195,274265,2,0,P|124:151|122:102,1,83.9949974366761,14|2,0:2|0:0,0:0:0:0: +272,13,274581,6,0,L|385:20,1,83.9949974366761,2|10,1:2|0:2,0:0:0:0: +488,195,274897,2,0,L|375:202,1,83.9949974366761,10|2,0:2|0:0,0:0:0:0: +285,37,275212,1,10,0:2:0:0: +315,233,275370,1,10,0:2:0:0: +283,20,275528,1,2,1:2:0:0: +313,216,275686,1,10,0:2:0:0: +254,127,275844,5,6,0:2:0:0: +71,80,278370,6,0,B|118:74|166:40|166:40|130:88,1,125.992496155014,12|0,0:0|0:0,0:0:0:0: +256,61,278686,2,0,B|242:43|242:43|291:77|337:83,1,125.992496155014,8|0,0:0|0:0,0:0:0:0: +351,186,279002,2,0,B|297:179|242:141|242:141|261:165,1,149.542498859081,12|0,0:0|0:0,0:0:0:0: +149,163,279318,2,0,B|167:138|167:138|112:176|59:183,1,149.542498859081,8|0,0:0|0:0,0:0:0:0: +205,229,279633,5,14,0:2:0:0: +480,25,280265,6,0,B|160:57|384:313|32:345,1,580.900014182129,6|0,1:2|0:0,0:0:0:0: diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/2571731-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/2571731-expected-conversion.json new file mode 100644 index 0000000000..1817ef4742 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/2571731-expected-conversion.json @@ -0,0 +1 @@ +{"Mappings":[{"StartTime":1254.0,"Objects":[{"StartTime":1254.0,"Position":229.0,"HyperDash":false},{"StartTime":1332.0,"Position":187.422012,"HyperDash":false},{"StartTime":1410.0,"Position":169.115814,"HyperDash":false},{"StartTime":1488.0,"Position":162.374466,"HyperDash":false},{"StartTime":1566.0,"Position":160.452332,"HyperDash":false},{"StartTime":1635.0,"Position":181.787521,"HyperDash":false},{"StartTime":1704.0,"Position":177.835266,"HyperDash":false},{"StartTime":1773.0,"Position":194.2059,"HyperDash":false},{"StartTime":1878.0,"Position":230.367538,"HyperDash":false}]},{"StartTime":2191.0,"Objects":[{"StartTime":2191.0,"Position":470.0,"HyperDash":false}]},{"StartTime":2504.0,"Objects":[{"StartTime":2504.0,"Position":228.0,"HyperDash":false},{"StartTime":2573.0,"Position":188.673691,"HyperDash":false},{"StartTime":2642.0,"Position":171.347382,"HyperDash":false},{"StartTime":2711.0,"Position":143.021057,"HyperDash":false},{"StartTime":2816.0,"Position":118.002762,"HyperDash":false}]},{"StartTime":3129.0,"Objects":[{"StartTime":3129.0,"Position":231.0,"HyperDash":false},{"StartTime":3198.0,"Position":251.326309,"HyperDash":false},{"StartTime":3267.0,"Position":293.652618,"HyperDash":false},{"StartTime":3336.0,"Position":307.978943,"HyperDash":false},{"StartTime":3441.0,"Position":340.997253,"HyperDash":false}]},{"StartTime":3754.0,"Objects":[{"StartTime":3754.0,"Position":465.0,"HyperDash":false},{"StartTime":3832.0,"Position":458.7602,"HyperDash":false},{"StartTime":3910.0,"Position":469.2429,"HyperDash":false},{"StartTime":3988.0,"Position":435.549255,"HyperDash":false},{"StartTime":4066.0,"Position":439.174347,"HyperDash":false},{"StartTime":4135.0,"Position":439.5499,"HyperDash":false},{"StartTime":4204.0,"Position":417.896637,"HyperDash":false},{"StartTime":4273.0,"Position":391.087067,"HyperDash":false},{"StartTime":4379.0,"Position":341.072845,"HyperDash":false}]},{"StartTime":4691.0,"Objects":[{"StartTime":4691.0,"Position":131.0,"HyperDash":false}]},{"StartTime":5004.0,"Objects":[{"StartTime":5004.0,"Position":365.0,"HyperDash":false},{"StartTime":5073.0,"Position":357.3771,"HyperDash":false},{"StartTime":5142.0,"Position":348.754242,"HyperDash":false},{"StartTime":5211.0,"Position":379.131348,"HyperDash":false},{"StartTime":5316.0,"Position":366.705231,"HyperDash":false}]},{"StartTime":5629.0,"Objects":[{"StartTime":5629.0,"Position":228.0,"HyperDash":false},{"StartTime":5698.0,"Position":247.324,"HyperDash":false},{"StartTime":5767.0,"Position":280.648,"HyperDash":false},{"StartTime":5836.0,"Position":285.972,"HyperDash":false},{"StartTime":5941.0,"Position":337.9868,"HyperDash":false}]},{"StartTime":6254.0,"Objects":[{"StartTime":6254.0,"Position":197.0,"HyperDash":false},{"StartTime":6332.0,"Position":165.6015,"HyperDash":false},{"StartTime":6410.0,"Position":141.904709,"HyperDash":false},{"StartTime":6488.0,"Position":132.042267,"HyperDash":false},{"StartTime":6566.0,"Position":96.16125,"HyperDash":false},{"StartTime":6635.0,"Position":60.9876633,"HyperDash":false},{"StartTime":6704.0,"Position":51.4006424,"HyperDash":false},{"StartTime":6773.0,"Position":71.82723,"HyperDash":false},{"StartTime":6879.0,"Position":54.095974,"HyperDash":false}]},{"StartTime":7191.0,"Objects":[{"StartTime":7191.0,"Position":283.0,"HyperDash":false}]},{"StartTime":7504.0,"Objects":[{"StartTime":7504.0,"Position":290.0,"HyperDash":false},{"StartTime":7573.0,"Position":310.169,"HyperDash":false},{"StartTime":7642.0,"Position":295.338,"HyperDash":false},{"StartTime":7711.0,"Position":292.507019,"HyperDash":false},{"StartTime":7816.0,"Position":304.3294,"HyperDash":false}]},{"StartTime":8129.0,"Objects":[{"StartTime":8129.0,"Position":48.0,"HyperDash":false}]},{"StartTime":8441.0,"Objects":[{"StartTime":8441.0,"Position":308.0,"HyperDash":false}]},{"StartTime":8754.0,"Objects":[{"StartTime":8754.0,"Position":168.0,"HyperDash":false},{"StartTime":8823.0,"Position":142.687775,"HyperDash":false},{"StartTime":8892.0,"Position":138.375549,"HyperDash":false},{"StartTime":8961.0,"Position":89.06334,"HyperDash":false},{"StartTime":9066.0,"Position":58.0664749,"HyperDash":false}]},{"StartTime":9379.0,"Objects":[{"StartTime":9379.0,"Position":226.0,"HyperDash":false},{"StartTime":9448.0,"Position":255.312714,"HyperDash":false},{"StartTime":9517.0,"Position":287.625427,"HyperDash":false},{"StartTime":9586.0,"Position":315.938171,"HyperDash":false},{"StartTime":9691.0,"Position":335.9358,"HyperDash":false}]},{"StartTime":10004.0,"Objects":[{"StartTime":10004.0,"Position":477.0,"HyperDash":false},{"StartTime":10062.0,"Position":43.0,"HyperDash":false},{"StartTime":10121.0,"Position":494.0,"HyperDash":false},{"StartTime":10179.0,"Position":135.0,"HyperDash":false},{"StartTime":10238.0,"Position":30.0,"HyperDash":false},{"StartTime":10296.0,"Position":11.0,"HyperDash":false},{"StartTime":10355.0,"Position":239.0,"HyperDash":false},{"StartTime":10413.0,"Position":505.0,"HyperDash":false},{"StartTime":10472.0,"Position":353.0,"HyperDash":false},{"StartTime":10531.0,"Position":136.0,"HyperDash":false},{"StartTime":10589.0,"Position":135.0,"HyperDash":false},{"StartTime":10648.0,"Position":346.0,"HyperDash":false},{"StartTime":10706.0,"Position":39.0,"HyperDash":false},{"StartTime":10765.0,"Position":300.0,"HyperDash":false},{"StartTime":10823.0,"Position":398.0,"HyperDash":false},{"StartTime":10882.0,"Position":151.0,"HyperDash":false},{"StartTime":10941.0,"Position":73.0,"HyperDash":false}]},{"StartTime":11254.0,"Objects":[{"StartTime":11254.0,"Position":173.0,"HyperDash":false},{"StartTime":11332.0,"Position":151.138428,"HyperDash":false},{"StartTime":11410.0,"Position":132.025146,"HyperDash":false},{"StartTime":11488.0,"Position":91.37633,"HyperDash":false},{"StartTime":11566.0,"Position":80.290535,"HyperDash":false},{"StartTime":11635.0,"Position":61.6581879,"HyperDash":false},{"StartTime":11704.0,"Position":80.94798,"HyperDash":false},{"StartTime":11773.0,"Position":108.710762,"HyperDash":false},{"StartTime":11879.0,"Position":120.303291,"HyperDash":false}]},{"StartTime":12191.0,"Objects":[{"StartTime":12191.0,"Position":348.0,"HyperDash":false}]},{"StartTime":12504.0,"Objects":[{"StartTime":12504.0,"Position":119.0,"HyperDash":false},{"StartTime":12573.0,"Position":113.579384,"HyperDash":false},{"StartTime":12642.0,"Position":114.15876,"HyperDash":false},{"StartTime":12711.0,"Position":93.7381439,"HyperDash":false},{"StartTime":12816.0,"Position":108.054588,"HyperDash":false}]},{"StartTime":13129.0,"Objects":[{"StartTime":13129.0,"Position":246.0,"HyperDash":false},{"StartTime":13198.0,"Position":207.673676,"HyperDash":false},{"StartTime":13267.0,"Position":196.347351,"HyperDash":false},{"StartTime":13336.0,"Position":190.021027,"HyperDash":false},{"StartTime":13441.0,"Position":136.002686,"HyperDash":false}]},{"StartTime":13754.0,"Objects":[{"StartTime":13754.0,"Position":290.0,"HyperDash":false},{"StartTime":13832.0,"Position":327.611237,"HyperDash":false},{"StartTime":13910.0,"Position":353.671875,"HyperDash":false},{"StartTime":13988.0,"Position":363.8803,"HyperDash":false},{"StartTime":14066.0,"Position":380.478455,"HyperDash":false},{"StartTime":14135.0,"Position":374.964325,"HyperDash":false},{"StartTime":14204.0,"Position":392.930969,"HyperDash":false},{"StartTime":14273.0,"Position":350.32254,"HyperDash":false},{"StartTime":14379.0,"Position":329.753876,"HyperDash":false}]},{"StartTime":14691.0,"Objects":[{"StartTime":14691.0,"Position":80.0,"HyperDash":false}]},{"StartTime":15004.0,"Objects":[{"StartTime":15004.0,"Position":335.0,"HyperDash":false},{"StartTime":15082.0,"Position":297.345367,"HyperDash":false},{"StartTime":15160.0,"Position":285.690735,"HyperDash":false},{"StartTime":15238.0,"Position":243.036133,"HyperDash":false},{"StartTime":15316.0,"Position":228.3815,"HyperDash":false},{"StartTime":15385.0,"Position":192.802429,"HyperDash":false},{"StartTime":15454.0,"Position":180.223328,"HyperDash":false},{"StartTime":15523.0,"Position":144.644241,"HyperDash":false},{"StartTime":15628.0,"Position":121.763031,"HyperDash":false}]},{"StartTime":15941.0,"Objects":[{"StartTime":15941.0,"Position":475.0,"HyperDash":false}]},{"StartTime":16254.0,"Objects":[{"StartTime":16254.0,"Position":120.0,"HyperDash":false},{"StartTime":16332.0,"Position":112.4255,"HyperDash":false},{"StartTime":16410.0,"Position":115.254448,"HyperDash":false},{"StartTime":16488.0,"Position":125.264832,"HyperDash":false},{"StartTime":16566.0,"Position":150.934219,"HyperDash":false},{"StartTime":16635.0,"Position":164.5551,"HyperDash":false},{"StartTime":16704.0,"Position":205.001038,"HyperDash":false},{"StartTime":16773.0,"Position":217.329178,"HyperDash":false},{"StartTime":16879.0,"Position":251.47258,"HyperDash":false}]},{"StartTime":17191.0,"Objects":[{"StartTime":17191.0,"Position":405.0,"HyperDash":false}]},{"StartTime":17504.0,"Objects":[{"StartTime":17504.0,"Position":250.0,"HyperDash":false},{"StartTime":17582.0,"Position":210.776382,"HyperDash":false},{"StartTime":17660.0,"Position":183.552765,"HyperDash":false},{"StartTime":17738.0,"Position":153.329163,"HyperDash":false},{"StartTime":17816.0,"Position":141.10556,"HyperDash":false},{"StartTime":17885.0,"Position":161.187988,"HyperDash":false},{"StartTime":17954.0,"Position":192.2704,"HyperDash":false},{"StartTime":18023.0,"Position":194.352844,"HyperDash":false},{"StartTime":18128.0,"Position":250.0,"HyperDash":false}]},{"StartTime":18441.0,"Objects":[{"StartTime":18441.0,"Position":403.0,"HyperDash":false}]},{"StartTime":18754.0,"Objects":[{"StartTime":18754.0,"Position":250.0,"HyperDash":false},{"StartTime":18832.0,"Position":222.847168,"HyperDash":false},{"StartTime":18910.0,"Position":212.124878,"HyperDash":false},{"StartTime":18988.0,"Position":180.067841,"HyperDash":false},{"StartTime":19066.0,"Position":177.484818,"HyperDash":false},{"StartTime":19135.0,"Position":173.0804,"HyperDash":false},{"StartTime":19204.0,"Position":184.026764,"HyperDash":false},{"StartTime":19273.0,"Position":200.533371,"HyperDash":false},{"StartTime":19378.0,"Position":250.553848,"HyperDash":false}]},{"StartTime":19691.0,"Objects":[{"StartTime":19691.0,"Position":404.0,"HyperDash":false}]},{"StartTime":20004.0,"Objects":[{"StartTime":20004.0,"Position":249.0,"HyperDash":false}]},{"StartTime":20316.0,"Objects":[{"StartTime":20316.0,"Position":241.0,"HyperDash":false}]},{"StartTime":20629.0,"Objects":[{"StartTime":20629.0,"Position":239.0,"HyperDash":false}]},{"StartTime":20941.0,"Objects":[{"StartTime":20941.0,"Position":399.0,"HyperDash":false}]},{"StartTime":21254.0,"Objects":[{"StartTime":21254.0,"Position":240.0,"HyperDash":false},{"StartTime":21332.0,"Position":230.589066,"HyperDash":false},{"StartTime":21410.0,"Position":196.0483,"HyperDash":false},{"StartTime":21488.0,"Position":180.546143,"HyperDash":false},{"StartTime":21566.0,"Position":140.131409,"HyperDash":false},{"StartTime":21635.0,"Position":118.503845,"HyperDash":false},{"StartTime":21704.0,"Position":118.550331,"HyperDash":false},{"StartTime":21773.0,"Position":116.676407,"HyperDash":false},{"StartTime":21878.0,"Position":101.092834,"HyperDash":false}]},{"StartTime":22191.0,"Objects":[{"StartTime":22191.0,"Position":372.0,"HyperDash":false}]},{"StartTime":22504.0,"Objects":[{"StartTime":22504.0,"Position":386.0,"HyperDash":false},{"StartTime":22573.0,"Position":375.75766,"HyperDash":false},{"StartTime":22642.0,"Position":377.51532,"HyperDash":false},{"StartTime":22711.0,"Position":375.273,"HyperDash":false},{"StartTime":22816.0,"Position":398.469452,"HyperDash":false}]},{"StartTime":23129.0,"Objects":[{"StartTime":23129.0,"Position":264.0,"HyperDash":false},{"StartTime":23198.0,"Position":242.675385,"HyperDash":false},{"StartTime":23267.0,"Position":201.350769,"HyperDash":false},{"StartTime":23336.0,"Position":197.026169,"HyperDash":false},{"StartTime":23441.0,"Position":154.010468,"HyperDash":false}]},{"StartTime":23754.0,"Objects":[{"StartTime":23754.0,"Position":292.0,"HyperDash":false},{"StartTime":23832.0,"Position":331.63,"HyperDash":false},{"StartTime":23910.0,"Position":331.0578,"HyperDash":false},{"StartTime":23988.0,"Position":348.8628,"HyperDash":false},{"StartTime":24066.0,"Position":360.9073,"HyperDash":false},{"StartTime":24135.0,"Position":365.5993,"HyperDash":false},{"StartTime":24204.0,"Position":351.536926,"HyperDash":false},{"StartTime":24273.0,"Position":324.124176,"HyperDash":false},{"StartTime":24378.0,"Position":290.904083,"HyperDash":false}]},{"StartTime":24691.0,"Objects":[{"StartTime":24691.0,"Position":24.0,"HyperDash":false}]},{"StartTime":25004.0,"Objects":[{"StartTime":25004.0,"Position":285.0,"HyperDash":false},{"StartTime":25082.0,"Position":313.548859,"HyperDash":false},{"StartTime":25160.0,"Position":324.0977,"HyperDash":false},{"StartTime":25238.0,"Position":345.646545,"HyperDash":false},{"StartTime":25316.0,"Position":375.195374,"HyperDash":false},{"StartTime":25385.0,"Position":374.248322,"HyperDash":false},{"StartTime":25454.0,"Position":330.30127,"HyperDash":false},{"StartTime":25523.0,"Position":315.354218,"HyperDash":false},{"StartTime":25628.0,"Position":285.0,"HyperDash":false}]},{"StartTime":25941.0,"Objects":[{"StartTime":25941.0,"Position":465.0,"HyperDash":false}]},{"StartTime":26254.0,"Objects":[{"StartTime":26254.0,"Position":284.0,"HyperDash":false},{"StartTime":26332.0,"Position":297.848755,"HyperDash":false},{"StartTime":26410.0,"Position":271.5647,"HyperDash":false},{"StartTime":26488.0,"Position":314.9722,"HyperDash":false},{"StartTime":26566.0,"Position":313.667419,"HyperDash":false},{"StartTime":26635.0,"Position":327.281128,"HyperDash":false},{"StartTime":26704.0,"Position":359.75528,"HyperDash":false},{"StartTime":26773.0,"Position":373.334259,"HyperDash":false},{"StartTime":26879.0,"Position":411.1939,"HyperDash":false}]},{"StartTime":27191.0,"Objects":[{"StartTime":27191.0,"Position":108.0,"HyperDash":false}]},{"StartTime":27504.0,"Objects":[{"StartTime":27504.0,"Position":124.0,"HyperDash":false},{"StartTime":27573.0,"Position":136.610931,"HyperDash":false},{"StartTime":27642.0,"Position":107.221848,"HyperDash":false},{"StartTime":27711.0,"Position":100.832764,"HyperDash":false},{"StartTime":27816.0,"Position":113.197212,"HyperDash":false}]},{"StartTime":28129.0,"Objects":[{"StartTime":28129.0,"Position":250.0,"HyperDash":false},{"StartTime":28198.0,"Position":219.04863,"HyperDash":false},{"StartTime":28267.0,"Position":184.097244,"HyperDash":false},{"StartTime":28336.0,"Position":174.145874,"HyperDash":false},{"StartTime":28441.0,"Position":141.69812,"HyperDash":false}]},{"StartTime":28754.0,"Objects":[{"StartTime":28754.0,"Position":284.0,"HyperDash":false},{"StartTime":28832.0,"Position":316.245941,"HyperDash":false},{"StartTime":28910.0,"Position":338.3189,"HyperDash":false},{"StartTime":28988.0,"Position":359.1247,"HyperDash":false},{"StartTime":29066.0,"Position":381.5727,"HyperDash":false},{"StartTime":29135.0,"Position":393.2293,"HyperDash":false},{"StartTime":29204.0,"Position":421.124542,"HyperDash":false},{"StartTime":29273.0,"Position":400.894043,"HyperDash":false},{"StartTime":29379.0,"Position":415.7917,"HyperDash":false}]},{"StartTime":29691.0,"Objects":[{"StartTime":29691.0,"Position":135.0,"HyperDash":false}]},{"StartTime":30004.0,"Objects":[{"StartTime":30004.0,"Position":416.0,"HyperDash":false}]},{"StartTime":30316.0,"Objects":[{"StartTime":30316.0,"Position":456.0,"HyperDash":false}]},{"StartTime":30629.0,"Objects":[{"StartTime":30629.0,"Position":294.0,"HyperDash":false},{"StartTime":30698.0,"Position":268.673065,"HyperDash":false},{"StartTime":30767.0,"Position":233.346161,"HyperDash":false},{"StartTime":30836.0,"Position":224.019226,"HyperDash":false},{"StartTime":30941.0,"Position":184.0,"HyperDash":false}]},{"StartTime":31254.0,"Objects":[{"StartTime":31254.0,"Position":351.0,"HyperDash":false},{"StartTime":31332.0,"Position":388.3603,"HyperDash":false},{"StartTime":31410.0,"Position":400.4129,"HyperDash":false},{"StartTime":31488.0,"Position":406.2265,"HyperDash":false},{"StartTime":31566.0,"Position":441.396118,"HyperDash":false},{"StartTime":31635.0,"Position":452.909637,"HyperDash":false},{"StartTime":31704.0,"Position":447.317657,"HyperDash":false},{"StartTime":31773.0,"Position":421.671265,"HyperDash":false},{"StartTime":31878.0,"Position":416.158752,"HyperDash":false}]},{"StartTime":32191.0,"Objects":[{"StartTime":32191.0,"Position":149.0,"HyperDash":false}]},{"StartTime":32504.0,"Objects":[{"StartTime":32504.0,"Position":144.0,"HyperDash":false},{"StartTime":32582.0,"Position":152.122482,"HyperDash":false},{"StartTime":32660.0,"Position":187.244965,"HyperDash":false},{"StartTime":32738.0,"Position":234.367462,"HyperDash":false},{"StartTime":32816.0,"Position":252.489944,"HyperDash":false},{"StartTime":32885.0,"Position":292.4829,"HyperDash":false},{"StartTime":32954.0,"Position":314.4759,"HyperDash":false},{"StartTime":33023.0,"Position":317.468872,"HyperDash":false},{"StartTime":33129.0,"Position":361.327637,"HyperDash":false}]},{"StartTime":33440.0,"Objects":[{"StartTime":33440.0,"Position":201.0,"HyperDash":false}]},{"StartTime":33597.0,"Objects":[{"StartTime":33597.0,"Position":140.0,"HyperDash":false},{"StartTime":33675.0,"Position":97.84354,"HyperDash":false},{"StartTime":33753.0,"Position":83.79872,"HyperDash":false},{"StartTime":33831.0,"Position":111.743484,"HyperDash":false},{"StartTime":33909.0,"Position":105.502647,"HyperDash":false},{"StartTime":33969.0,"Position":122.537575,"HyperDash":false},{"StartTime":34065.0,"Position":148.435272,"HyperDash":false}]},{"StartTime":34379.0,"Objects":[{"StartTime":34379.0,"Position":239.0,"HyperDash":false},{"StartTime":34448.0,"Position":211.884613,"HyperDash":false},{"StartTime":34517.0,"Position":177.769226,"HyperDash":false},{"StartTime":34586.0,"Position":165.653839,"HyperDash":false},{"StartTime":34691.0,"Position":129.956512,"HyperDash":false}]},{"StartTime":35004.0,"Objects":[{"StartTime":35004.0,"Position":264.0,"HyperDash":false},{"StartTime":35073.0,"Position":298.150146,"HyperDash":false},{"StartTime":35142.0,"Position":294.300323,"HyperDash":false},{"StartTime":35211.0,"Position":332.45047,"HyperDash":false},{"StartTime":35316.0,"Position":373.2007,"HyperDash":false}]},{"StartTime":35629.0,"Objects":[{"StartTime":35629.0,"Position":223.0,"HyperDash":false},{"StartTime":35698.0,"Position":205.019867,"HyperDash":false},{"StartTime":35767.0,"Position":232.039749,"HyperDash":false},{"StartTime":35836.0,"Position":201.059616,"HyperDash":false},{"StartTime":35941.0,"Position":218.568115,"HyperDash":false}]},{"StartTime":36254.0,"Objects":[{"StartTime":36254.0,"Position":379.0,"HyperDash":false}]},{"StartTime":37035.0,"Objects":[{"StartTime":37035.0,"Position":398.0,"HyperDash":false},{"StartTime":37134.0,"Position":416.853973,"HyperDash":false},{"StartTime":37269.0,"Position":402.3821,"HyperDash":false}]},{"StartTime":37504.0,"Objects":[{"StartTime":37504.0,"Position":284.0,"HyperDash":false},{"StartTime":37573.0,"Position":268.747223,"HyperDash":false},{"StartTime":37642.0,"Position":235.494461,"HyperDash":false},{"StartTime":37711.0,"Position":210.2417,"HyperDash":false},{"StartTime":37816.0,"Position":174.335327,"HyperDash":false}]},{"StartTime":38129.0,"Objects":[{"StartTime":38129.0,"Position":305.0,"HyperDash":false},{"StartTime":38198.0,"Position":341.240051,"HyperDash":false},{"StartTime":38267.0,"Position":355.480072,"HyperDash":false},{"StartTime":38336.0,"Position":378.7201,"HyperDash":false},{"StartTime":38441.0,"Position":414.607117,"HyperDash":false}]},{"StartTime":38597.0,"Objects":[{"StartTime":38597.0,"Position":415.0,"HyperDash":false},{"StartTime":38675.0,"Position":374.605652,"HyperDash":false},{"StartTime":38753.0,"Position":363.2113,"HyperDash":false},{"StartTime":38831.0,"Position":320.816956,"HyperDash":false},{"StartTime":38909.0,"Position":305.4226,"HyperDash":false},{"StartTime":38969.0,"Position":265.350037,"HyperDash":false},{"StartTime":39065.0,"Position":250.633942,"HyperDash":false}]},{"StartTime":39379.0,"Objects":[{"StartTime":39379.0,"Position":113.0,"HyperDash":false},{"StartTime":39448.0,"Position":113.075348,"HyperDash":false},{"StartTime":39517.0,"Position":104.150688,"HyperDash":false},{"StartTime":39586.0,"Position":88.2260361,"HyperDash":false},{"StartTime":39691.0,"Position":104.297211,"HyperDash":false}]},{"StartTime":40004.0,"Objects":[{"StartTime":40004.0,"Position":385.0,"HyperDash":false}]},{"StartTime":40316.0,"Objects":[{"StartTime":40316.0,"Position":250.0,"HyperDash":false}]},{"StartTime":40629.0,"Objects":[{"StartTime":40629.0,"Position":256.0,"HyperDash":false}]},{"StartTime":40941.0,"Objects":[{"StartTime":40941.0,"Position":89.0,"HyperDash":false}]},{"StartTime":41254.0,"Objects":[{"StartTime":41254.0,"Position":256.0,"HyperDash":false},{"StartTime":41332.0,"Position":267.961151,"HyperDash":false},{"StartTime":41410.0,"Position":316.030029,"HyperDash":false},{"StartTime":41488.0,"Position":332.626129,"HyperDash":false},{"StartTime":41566.0,"Position":354.210022,"HyperDash":false},{"StartTime":41644.0,"Position":353.98996,"HyperDash":false},{"StartTime":41722.0,"Position":371.766144,"HyperDash":false},{"StartTime":41800.0,"Position":358.108276,"HyperDash":false},{"StartTime":41879.0,"Position":354.210022,"HyperDash":false},{"StartTime":41948.0,"Position":326.1343,"HyperDash":false},{"StartTime":42017.0,"Position":316.00824,"HyperDash":false},{"StartTime":42086.0,"Position":278.452332,"HyperDash":false},{"StartTime":42191.0,"Position":256.0,"HyperDash":false}]},{"StartTime":42504.0,"Objects":[{"StartTime":42504.0,"Position":98.0,"HyperDash":false},{"StartTime":42582.0,"Position":125.961151,"HyperDash":false},{"StartTime":42660.0,"Position":167.030014,"HyperDash":false},{"StartTime":42738.0,"Position":165.626129,"HyperDash":false},{"StartTime":42816.0,"Position":196.210022,"HyperDash":false},{"StartTime":42894.0,"Position":225.98996,"HyperDash":false},{"StartTime":42972.0,"Position":213.766159,"HyperDash":false},{"StartTime":43050.0,"Position":190.108276,"HyperDash":false},{"StartTime":43129.0,"Position":196.210022,"HyperDash":false},{"StartTime":43198.0,"Position":197.134308,"HyperDash":false},{"StartTime":43267.0,"Position":141.00824,"HyperDash":false},{"StartTime":43336.0,"Position":118.452332,"HyperDash":false},{"StartTime":43441.0,"Position":98.0,"HyperDash":false}]},{"StartTime":43754.0,"Objects":[{"StartTime":43754.0,"Position":249.0,"HyperDash":false},{"StartTime":43832.0,"Position":233.529724,"HyperDash":false},{"StartTime":43910.0,"Position":206.059448,"HyperDash":false},{"StartTime":43988.0,"Position":181.589172,"HyperDash":false},{"StartTime":44066.0,"Position":139.1189,"HyperDash":false},{"StartTime":44144.0,"Position":97.64862,"HyperDash":false},{"StartTime":44222.0,"Position":84.00227,"HyperDash":false},{"StartTime":44300.0,"Position":107.296448,"HyperDash":false},{"StartTime":44378.0,"Position":138.766724,"HyperDash":false},{"StartTime":44447.0,"Position":162.067352,"HyperDash":false},{"StartTime":44516.0,"Position":187.367981,"HyperDash":false},{"StartTime":44585.0,"Position":204.66861,"HyperDash":false},{"StartTime":44691.0,"Position":249.0,"HyperDash":false}]},{"StartTime":45004.0,"Objects":[{"StartTime":45004.0,"Position":466.0,"HyperDash":false},{"StartTime":45062.0,"Position":56.0,"HyperDash":false},{"StartTime":45121.0,"Position":109.0,"HyperDash":false},{"StartTime":45179.0,"Position":482.0,"HyperDash":false},{"StartTime":45238.0,"Position":147.0,"HyperDash":false},{"StartTime":45296.0,"Position":285.0,"HyperDash":false},{"StartTime":45355.0,"Position":452.0,"HyperDash":false},{"StartTime":45413.0,"Position":419.0,"HyperDash":false},{"StartTime":45472.0,"Position":269.0,"HyperDash":false},{"StartTime":45531.0,"Position":249.0,"HyperDash":false},{"StartTime":45589.0,"Position":233.0,"HyperDash":false},{"StartTime":45648.0,"Position":449.0,"HyperDash":false},{"StartTime":45706.0,"Position":411.0,"HyperDash":false},{"StartTime":45765.0,"Position":75.0,"HyperDash":false},{"StartTime":45823.0,"Position":474.0,"HyperDash":false},{"StartTime":45882.0,"Position":176.0,"HyperDash":false},{"StartTime":45941.0,"Position":1.0,"HyperDash":false}]},{"StartTime":46254.0,"Objects":[{"StartTime":46254.0,"Position":332.0,"HyperDash":false},{"StartTime":46332.0,"Position":341.35257,"HyperDash":false},{"StartTime":46410.0,"Position":382.281,"HyperDash":false},{"StartTime":46488.0,"Position":401.661774,"HyperDash":false},{"StartTime":46566.0,"Position":424.539063,"HyperDash":false},{"StartTime":46644.0,"Position":451.522461,"HyperDash":false},{"StartTime":46722.0,"Position":436.847626,"HyperDash":false},{"StartTime":46800.0,"Position":445.602142,"HyperDash":false},{"StartTime":46879.0,"Position":424.539063,"HyperDash":false},{"StartTime":46948.0,"Position":396.89386,"HyperDash":false},{"StartTime":47017.0,"Position":399.831055,"HyperDash":false},{"StartTime":47086.0,"Position":370.600555,"HyperDash":false},{"StartTime":47191.0,"Position":332.0,"HyperDash":false}]},{"StartTime":47504.0,"Objects":[{"StartTime":47504.0,"Position":180.0,"HyperDash":false},{"StartTime":47582.0,"Position":148.647415,"HyperDash":false},{"StartTime":47660.0,"Position":119.718979,"HyperDash":false},{"StartTime":47738.0,"Position":115.338226,"HyperDash":false},{"StartTime":47816.0,"Position":87.4609146,"HyperDash":false},{"StartTime":47894.0,"Position":96.4775,"HyperDash":false},{"StartTime":47972.0,"Position":75.15235,"HyperDash":false},{"StartTime":48050.0,"Position":64.3978348,"HyperDash":false},{"StartTime":48129.0,"Position":87.4609146,"HyperDash":false},{"StartTime":48198.0,"Position":116.106155,"HyperDash":false},{"StartTime":48267.0,"Position":126.168938,"HyperDash":false},{"StartTime":48336.0,"Position":133.399445,"HyperDash":false},{"StartTime":48441.0,"Position":180.0,"HyperDash":false}]},{"StartTime":48754.0,"Objects":[{"StartTime":48754.0,"Position":335.0,"HyperDash":false},{"StartTime":48832.0,"Position":313.529724,"HyperDash":false},{"StartTime":48910.0,"Position":282.059448,"HyperDash":false},{"StartTime":48988.0,"Position":233.589188,"HyperDash":false},{"StartTime":49066.0,"Position":225.118927,"HyperDash":false},{"StartTime":49144.0,"Position":202.648651,"HyperDash":false},{"StartTime":49222.0,"Position":170.002289,"HyperDash":false},{"StartTime":49300.0,"Position":213.296478,"HyperDash":false},{"StartTime":49379.0,"Position":225.118927,"HyperDash":false},{"StartTime":49448.0,"Position":239.41954,"HyperDash":false},{"StartTime":49517.0,"Position":280.720154,"HyperDash":false},{"StartTime":49586.0,"Position":280.020782,"HyperDash":false},{"StartTime":49691.0,"Position":335.0,"HyperDash":false}]},{"StartTime":50004.0,"Objects":[{"StartTime":50004.0,"Position":93.0,"HyperDash":false},{"StartTime":50062.0,"Position":267.0,"HyperDash":false},{"StartTime":50121.0,"Position":276.0,"HyperDash":false},{"StartTime":50179.0,"Position":367.0,"HyperDash":false},{"StartTime":50238.0,"Position":409.0,"HyperDash":false},{"StartTime":50296.0,"Position":117.0,"HyperDash":false},{"StartTime":50355.0,"Position":226.0,"HyperDash":false},{"StartTime":50413.0,"Position":469.0,"HyperDash":false},{"StartTime":50472.0,"Position":267.0,"HyperDash":false},{"StartTime":50531.0,"Position":477.0,"HyperDash":false},{"StartTime":50589.0,"Position":282.0,"HyperDash":false},{"StartTime":50648.0,"Position":216.0,"HyperDash":false},{"StartTime":50706.0,"Position":106.0,"HyperDash":false},{"StartTime":50765.0,"Position":353.0,"HyperDash":false},{"StartTime":50823.0,"Position":162.0,"HyperDash":false},{"StartTime":50882.0,"Position":473.0,"HyperDash":false},{"StartTime":50941.0,"Position":260.0,"HyperDash":false}]},{"StartTime":51254.0,"Objects":[{"StartTime":51254.0,"Position":119.0,"HyperDash":false},{"StartTime":51353.0,"Position":121.8725,"HyperDash":false},{"StartTime":51488.0,"Position":106.880455,"HyperDash":false}]},{"StartTime":51722.0,"Objects":[{"StartTime":51722.0,"Position":230.0,"HyperDash":false},{"StartTime":51800.0,"Position":213.529388,"HyperDash":false},{"StartTime":51878.0,"Position":222.058777,"HyperDash":false},{"StartTime":51956.0,"Position":243.588165,"HyperDash":false},{"StartTime":52034.0,"Position":244.117569,"HyperDash":false},{"StartTime":52094.0,"Position":256.8325,"HyperDash":false},{"StartTime":52190.0,"Position":251.176346,"HyperDash":false}]},{"StartTime":52504.0,"Objects":[{"StartTime":52504.0,"Position":373.0,"HyperDash":false},{"StartTime":52603.0,"Position":384.1275,"HyperDash":false},{"StartTime":52738.0,"Position":385.119537,"HyperDash":false}]},{"StartTime":52972.0,"Objects":[{"StartTime":52972.0,"Position":269.0,"HyperDash":false},{"StartTime":53050.0,"Position":243.9549,"HyperDash":false},{"StartTime":53128.0,"Position":227.363922,"HyperDash":false},{"StartTime":53206.0,"Position":246.705673,"HyperDash":false},{"StartTime":53284.0,"Position":238.662781,"HyperDash":false},{"StartTime":53344.0,"Position":267.1444,"HyperDash":false},{"StartTime":53440.0,"Position":274.832428,"HyperDash":false}]},{"StartTime":53754.0,"Objects":[{"StartTime":53754.0,"Position":424.0,"HyperDash":false},{"StartTime":53853.0,"Position":394.183075,"HyperDash":false},{"StartTime":53988.0,"Position":341.705444,"HyperDash":false}]},{"StartTime":54222.0,"Objects":[{"StartTime":54222.0,"Position":228.0,"HyperDash":false},{"StartTime":54300.0,"Position":248.405014,"HyperDash":false},{"StartTime":54378.0,"Position":299.810028,"HyperDash":false},{"StartTime":54456.0,"Position":312.215027,"HyperDash":false},{"StartTime":54534.0,"Position":337.620056,"HyperDash":false},{"StartTime":54594.0,"Position":374.7008,"HyperDash":false},{"StartTime":54690.0,"Position":392.430054,"HyperDash":false}]},{"StartTime":55004.0,"Objects":[{"StartTime":55004.0,"Position":241.0,"HyperDash":false},{"StartTime":55103.0,"Position":294.816925,"HyperDash":false},{"StartTime":55238.0,"Position":323.294556,"HyperDash":false}]},{"StartTime":55472.0,"Objects":[{"StartTime":55472.0,"Position":437.0,"HyperDash":false},{"StartTime":55550.0,"Position":398.595,"HyperDash":false},{"StartTime":55628.0,"Position":394.189972,"HyperDash":false},{"StartTime":55706.0,"Position":340.784973,"HyperDash":false},{"StartTime":55784.0,"Position":327.379944,"HyperDash":false},{"StartTime":55844.0,"Position":318.2992,"HyperDash":false},{"StartTime":55940.0,"Position":272.569946,"HyperDash":false}]},{"StartTime":56254.0,"Objects":[{"StartTime":56254.0,"Position":3.0,"HyperDash":false}]},{"StartTime":56488.0,"Objects":[{"StartTime":56488.0,"Position":260.0,"HyperDash":false},{"StartTime":56587.0,"Position":252.118042,"HyperDash":false},{"StartTime":56722.0,"Position":269.733521,"HyperDash":false}]},{"StartTime":56957.0,"Objects":[{"StartTime":56957.0,"Position":162.0,"HyperDash":false},{"StartTime":57056.0,"Position":138.870941,"HyperDash":false},{"StartTime":57191.0,"Position":81.3313,"HyperDash":false}]},{"StartTime":57504.0,"Objects":[{"StartTime":57504.0,"Position":402.0,"HyperDash":false}]},{"StartTime":57738.0,"Objects":[{"StartTime":57738.0,"Position":363.0,"HyperDash":false},{"StartTime":57837.0,"Position":344.432251,"HyperDash":false},{"StartTime":57972.0,"Position":281.2944,"HyperDash":false}]},{"StartTime":58207.0,"Objects":[{"StartTime":58207.0,"Position":174.0,"HyperDash":false},{"StartTime":58306.0,"Position":158.870941,"HyperDash":false},{"StartTime":58441.0,"Position":93.3313,"HyperDash":false}]},{"StartTime":58754.0,"Objects":[{"StartTime":58754.0,"Position":261.0,"HyperDash":false},{"StartTime":58832.0,"Position":243.6731,"HyperDash":false},{"StartTime":58910.0,"Position":205.346176,"HyperDash":false},{"StartTime":58988.0,"Position":162.019257,"HyperDash":false},{"StartTime":59066.0,"Position":151.692352,"HyperDash":false},{"StartTime":59144.0,"Position":119.365463,"HyperDash":false},{"StartTime":59222.0,"Position":96.86337,"HyperDash":false},{"StartTime":59300.0,"Position":110.015121,"HyperDash":false},{"StartTime":59379.0,"Position":151.692352,"HyperDash":false},{"StartTime":59448.0,"Position":182.866165,"HyperDash":false},{"StartTime":59517.0,"Position":214.039978,"HyperDash":false},{"StartTime":59586.0,"Position":217.213776,"HyperDash":false},{"StartTime":59691.0,"Position":261.0,"HyperDash":false}]},{"StartTime":60004.0,"Objects":[{"StartTime":60004.0,"Position":456.0,"HyperDash":false},{"StartTime":60062.0,"Position":371.0,"HyperDash":false},{"StartTime":60121.0,"Position":73.0,"HyperDash":false},{"StartTime":60179.0,"Position":190.0,"HyperDash":false},{"StartTime":60238.0,"Position":180.0,"HyperDash":false},{"StartTime":60296.0,"Position":461.0,"HyperDash":false},{"StartTime":60355.0,"Position":433.0,"HyperDash":false},{"StartTime":60413.0,"Position":275.0,"HyperDash":false},{"StartTime":60472.0,"Position":395.0,"HyperDash":false},{"StartTime":60531.0,"Position":473.0,"HyperDash":false},{"StartTime":60589.0,"Position":192.0,"HyperDash":false},{"StartTime":60648.0,"Position":362.0,"HyperDash":false},{"StartTime":60706.0,"Position":7.0,"HyperDash":false},{"StartTime":60765.0,"Position":500.0,"HyperDash":false},{"StartTime":60823.0,"Position":487.0,"HyperDash":false},{"StartTime":60882.0,"Position":487.0,"HyperDash":false},{"StartTime":60941.0,"Position":213.0,"HyperDash":false}]},{"StartTime":71254.0,"Objects":[{"StartTime":71254.0,"Position":258.0,"HyperDash":false}]},{"StartTime":71722.0,"Objects":[{"StartTime":71722.0,"Position":69.0,"HyperDash":false},{"StartTime":71800.0,"Position":67.48298,"HyperDash":false},{"StartTime":71878.0,"Position":70.96595,"HyperDash":false},{"StartTime":71956.0,"Position":82.44893,"HyperDash":false},{"StartTime":72034.0,"Position":62.9319038,"HyperDash":false},{"StartTime":72094.0,"Position":61.76496,"HyperDash":false},{"StartTime":72190.0,"Position":59.8978577,"HyperDash":false}]},{"StartTime":72504.0,"Objects":[{"StartTime":72504.0,"Position":381.0,"HyperDash":false}]},{"StartTime":73754.0,"Objects":[{"StartTime":73754.0,"Position":254.0,"HyperDash":false}]},{"StartTime":74222.0,"Objects":[{"StartTime":74222.0,"Position":443.0,"HyperDash":false},{"StartTime":74300.0,"Position":452.517029,"HyperDash":false},{"StartTime":74378.0,"Position":430.034058,"HyperDash":false},{"StartTime":74456.0,"Position":439.5511,"HyperDash":false},{"StartTime":74534.0,"Position":449.068085,"HyperDash":false},{"StartTime":74594.0,"Position":435.235046,"HyperDash":false},{"StartTime":74690.0,"Position":452.102142,"HyperDash":false}]},{"StartTime":75004.0,"Objects":[{"StartTime":75004.0,"Position":131.0,"HyperDash":false}]},{"StartTime":76254.0,"Objects":[{"StartTime":76254.0,"Position":136.0,"HyperDash":false}]},{"StartTime":76722.0,"Objects":[{"StartTime":76722.0,"Position":349.0,"HyperDash":false},{"StartTime":76800.0,"Position":304.5004,"HyperDash":false},{"StartTime":76878.0,"Position":313.000824,"HyperDash":false},{"StartTime":76956.0,"Position":264.501221,"HyperDash":false},{"StartTime":77034.0,"Position":239.001617,"HyperDash":false},{"StartTime":77094.0,"Position":209.848083,"HyperDash":false},{"StartTime":77190.0,"Position":184.002426,"HyperDash":false}]},{"StartTime":77504.0,"Objects":[{"StartTime":77504.0,"Position":350.0,"HyperDash":false}]},{"StartTime":78754.0,"Objects":[{"StartTime":78754.0,"Position":376.0,"HyperDash":false}]},{"StartTime":79222.0,"Objects":[{"StartTime":79222.0,"Position":163.0,"HyperDash":false},{"StartTime":79300.0,"Position":186.499588,"HyperDash":false},{"StartTime":79378.0,"Position":226.999191,"HyperDash":false},{"StartTime":79456.0,"Position":256.498779,"HyperDash":false},{"StartTime":79534.0,"Position":272.998383,"HyperDash":false},{"StartTime":79594.0,"Position":280.151917,"HyperDash":false},{"StartTime":79690.0,"Position":327.997559,"HyperDash":false}]},{"StartTime":80004.0,"Objects":[{"StartTime":80004.0,"Position":11.0,"HyperDash":false}]},{"StartTime":80316.0,"Objects":[{"StartTime":80316.0,"Position":165.0,"HyperDash":false}]},{"StartTime":80629.0,"Objects":[{"StartTime":80629.0,"Position":11.0,"HyperDash":false}]},{"StartTime":80941.0,"Objects":[{"StartTime":80941.0,"Position":192.0,"HyperDash":false}]},{"StartTime":81254.0,"Objects":[{"StartTime":81254.0,"Position":336.0,"HyperDash":false},{"StartTime":81323.0,"Position":296.6767,"HyperDash":false},{"StartTime":81392.0,"Position":293.3534,"HyperDash":false},{"StartTime":81461.0,"Position":281.03006,"HyperDash":false},{"StartTime":81566.0,"Position":226.016357,"HyperDash":false}]},{"StartTime":81879.0,"Objects":[{"StartTime":81879.0,"Position":366.0,"HyperDash":false},{"StartTime":81948.0,"Position":396.3233,"HyperDash":false},{"StartTime":82017.0,"Position":429.6466,"HyperDash":false},{"StartTime":82086.0,"Position":448.96994,"HyperDash":false},{"StartTime":82191.0,"Position":475.983643,"HyperDash":false}]},{"StartTime":82504.0,"Objects":[{"StartTime":82504.0,"Position":156.0,"HyperDash":false}]},{"StartTime":82816.0,"Objects":[{"StartTime":82816.0,"Position":292.0,"HyperDash":false}]},{"StartTime":83129.0,"Objects":[{"StartTime":83129.0,"Position":100.0,"HyperDash":false}]},{"StartTime":83441.0,"Objects":[{"StartTime":83441.0,"Position":248.0,"HyperDash":false}]},{"StartTime":83754.0,"Objects":[{"StartTime":83754.0,"Position":390.0,"HyperDash":false},{"StartTime":83832.0,"Position":372.544983,"HyperDash":false},{"StartTime":83910.0,"Position":344.089935,"HyperDash":false},{"StartTime":83988.0,"Position":308.634918,"HyperDash":false},{"StartTime":84066.0,"Position":280.003876,"HyperDash":false},{"StartTime":84135.0,"Position":301.115051,"HyperDash":false},{"StartTime":84204.0,"Position":341.4022,"HyperDash":false},{"StartTime":84273.0,"Position":359.6893,"HyperDash":false},{"StartTime":84379.0,"Position":390.0,"HyperDash":false}]},{"StartTime":85004.0,"Objects":[{"StartTime":85004.0,"Position":104.0,"HyperDash":false}]},{"StartTime":86254.0,"Objects":[{"StartTime":86254.0,"Position":324.0,"HyperDash":false}]},{"StartTime":86566.0,"Objects":[{"StartTime":86566.0,"Position":422.0,"HyperDash":false}]},{"StartTime":86879.0,"Objects":[{"StartTime":86879.0,"Position":470.0,"HyperDash":false}]},{"StartTime":87191.0,"Objects":[{"StartTime":87191.0,"Position":352.0,"HyperDash":false}]},{"StartTime":87504.0,"Objects":[{"StartTime":87504.0,"Position":287.0,"HyperDash":false},{"StartTime":87573.0,"Position":317.323242,"HyperDash":false},{"StartTime":87642.0,"Position":340.646484,"HyperDash":false},{"StartTime":87711.0,"Position":343.969727,"HyperDash":false},{"StartTime":87816.0,"Position":396.983368,"HyperDash":false}]},{"StartTime":88129.0,"Objects":[{"StartTime":88129.0,"Position":265.0,"HyperDash":false},{"StartTime":88198.0,"Position":237.676239,"HyperDash":false},{"StartTime":88267.0,"Position":223.352478,"HyperDash":false},{"StartTime":88336.0,"Position":206.028717,"HyperDash":false},{"StartTime":88441.0,"Position":155.014313,"HyperDash":false}]},{"StartTime":88754.0,"Objects":[{"StartTime":88754.0,"Position":475.0,"HyperDash":false}]},{"StartTime":89066.0,"Objects":[{"StartTime":89066.0,"Position":341.0,"HyperDash":false}]},{"StartTime":89379.0,"Objects":[{"StartTime":89379.0,"Position":432.0,"HyperDash":false}]},{"StartTime":89691.0,"Objects":[{"StartTime":89691.0,"Position":264.0,"HyperDash":false}]},{"StartTime":90004.0,"Objects":[{"StartTime":90004.0,"Position":255.0,"HyperDash":false},{"StartTime":90062.0,"Position":294.0,"HyperDash":false},{"StartTime":90121.0,"Position":354.0,"HyperDash":false},{"StartTime":90179.0,"Position":270.0,"HyperDash":false},{"StartTime":90238.0,"Position":362.0,"HyperDash":false},{"StartTime":90296.0,"Position":255.0,"HyperDash":false},{"StartTime":90355.0,"Position":203.0,"HyperDash":false},{"StartTime":90413.0,"Position":67.0,"HyperDash":false},{"StartTime":90472.0,"Position":112.0,"HyperDash":false},{"StartTime":90531.0,"Position":326.0,"HyperDash":false},{"StartTime":90589.0,"Position":219.0,"HyperDash":false},{"StartTime":90648.0,"Position":351.0,"HyperDash":false},{"StartTime":90706.0,"Position":477.0,"HyperDash":false},{"StartTime":90765.0,"Position":439.0,"HyperDash":false},{"StartTime":90823.0,"Position":471.0,"HyperDash":false},{"StartTime":90882.0,"Position":449.0,"HyperDash":false},{"StartTime":90941.0,"Position":295.0,"HyperDash":false}]},{"StartTime":91254.0,"Objects":[{"StartTime":91254.0,"Position":140.0,"HyperDash":false},{"StartTime":91332.0,"Position":109.180054,"HyperDash":false},{"StartTime":91410.0,"Position":88.36357,"HyperDash":false},{"StartTime":91488.0,"Position":92.07944,"HyperDash":false},{"StartTime":91566.0,"Position":69.79061,"HyperDash":false},{"StartTime":91635.0,"Position":78.01627,"HyperDash":false},{"StartTime":91704.0,"Position":103.148987,"HyperDash":false},{"StartTime":91773.0,"Position":121.717133,"HyperDash":false},{"StartTime":91878.0,"Position":140.090958,"HyperDash":false}]},{"StartTime":92191.0,"Objects":[{"StartTime":92191.0,"Position":380.0,"HyperDash":false}]},{"StartTime":92504.0,"Objects":[{"StartTime":92504.0,"Position":405.0,"HyperDash":false},{"StartTime":92573.0,"Position":381.6738,"HyperDash":false},{"StartTime":92642.0,"Position":369.347565,"HyperDash":false},{"StartTime":92711.0,"Position":341.021362,"HyperDash":false},{"StartTime":92816.0,"Position":295.0032,"HyperDash":false}]},{"StartTime":93129.0,"Objects":[{"StartTime":93129.0,"Position":154.0,"HyperDash":false},{"StartTime":93198.0,"Position":177.324478,"HyperDash":false},{"StartTime":93267.0,"Position":215.648956,"HyperDash":false},{"StartTime":93336.0,"Position":211.973434,"HyperDash":false},{"StartTime":93441.0,"Position":263.988922,"HyperDash":false}]},{"StartTime":93754.0,"Objects":[{"StartTime":93754.0,"Position":135.0,"HyperDash":false},{"StartTime":93832.0,"Position":153.455765,"HyperDash":false},{"StartTime":93910.0,"Position":177.911545,"HyperDash":false},{"StartTime":93988.0,"Position":219.36731,"HyperDash":false},{"StartTime":94066.0,"Position":244.82309,"HyperDash":false},{"StartTime":94135.0,"Position":274.1109,"HyperDash":false},{"StartTime":94204.0,"Position":309.398682,"HyperDash":false},{"StartTime":94273.0,"Position":297.686462,"HyperDash":false},{"StartTime":94379.0,"Position":354.998169,"HyperDash":false}]},{"StartTime":94691.0,"Objects":[{"StartTime":94691.0,"Position":98.0,"HyperDash":false}]},{"StartTime":95004.0,"Objects":[{"StartTime":95004.0,"Position":354.0,"HyperDash":false},{"StartTime":95073.0,"Position":330.775818,"HyperDash":false},{"StartTime":95142.0,"Position":308.551636,"HyperDash":false},{"StartTime":95211.0,"Position":298.327454,"HyperDash":false},{"StartTime":95316.0,"Position":244.464569,"HyperDash":false}]},{"StartTime":95629.0,"Objects":[{"StartTime":95629.0,"Position":97.0,"HyperDash":false},{"StartTime":95698.0,"Position":100.173164,"HyperDash":false},{"StartTime":95767.0,"Position":91.34632,"HyperDash":false},{"StartTime":95836.0,"Position":85.5194855,"HyperDash":false},{"StartTime":95941.0,"Position":79.69604,"HyperDash":false}]},{"StartTime":96254.0,"Objects":[{"StartTime":96254.0,"Position":238.0,"HyperDash":false},{"StartTime":96332.0,"Position":263.193329,"HyperDash":false},{"StartTime":96410.0,"Position":297.398376,"HyperDash":false},{"StartTime":96488.0,"Position":296.3632,"HyperDash":false},{"StartTime":96566.0,"Position":315.395416,"HyperDash":false},{"StartTime":96635.0,"Position":328.299622,"HyperDash":false},{"StartTime":96704.0,"Position":295.141235,"HyperDash":false},{"StartTime":96773.0,"Position":274.06,"HyperDash":false},{"StartTime":96878.0,"Position":241.776031,"HyperDash":false}]},{"StartTime":97191.0,"Objects":[{"StartTime":97191.0,"Position":497.0,"HyperDash":false}]},{"StartTime":97504.0,"Objects":[{"StartTime":97504.0,"Position":252.0,"HyperDash":false},{"StartTime":97582.0,"Position":214.922928,"HyperDash":false},{"StartTime":97660.0,"Position":177.845856,"HyperDash":false},{"StartTime":97738.0,"Position":185.7688,"HyperDash":false},{"StartTime":97816.0,"Position":143.518143,"HyperDash":false},{"StartTime":97885.0,"Position":175.297363,"HyperDash":false},{"StartTime":97954.0,"Position":187.250168,"HyperDash":false},{"StartTime":98023.0,"Position":222.202957,"HyperDash":false},{"StartTime":98129.0,"Position":252.0,"HyperDash":false}]},{"StartTime":98441.0,"Objects":[{"StartTime":98441.0,"Position":363.0,"HyperDash":false}]},{"StartTime":98754.0,"Objects":[{"StartTime":98754.0,"Position":223.0,"HyperDash":false},{"StartTime":98823.0,"Position":195.875366,"HyperDash":false},{"StartTime":98892.0,"Position":193.750732,"HyperDash":false},{"StartTime":98961.0,"Position":151.626083,"HyperDash":false},{"StartTime":99066.0,"Position":113.914696,"HyperDash":false}]},{"StartTime":99379.0,"Objects":[{"StartTime":99379.0,"Position":494.0,"HyperDash":false}]},{"StartTime":99691.0,"Objects":[{"StartTime":99691.0,"Position":298.0,"HyperDash":false}]},{"StartTime":100004.0,"Objects":[{"StartTime":100004.0,"Position":236.0,"HyperDash":false},{"StartTime":100082.0,"Position":203.256851,"HyperDash":false},{"StartTime":100160.0,"Position":168.009674,"HyperDash":false},{"StartTime":100238.0,"Position":177.094879,"HyperDash":false},{"StartTime":100316.0,"Position":141.201492,"HyperDash":false},{"StartTime":100385.0,"Position":127.635262,"HyperDash":false},{"StartTime":100454.0,"Position":110.29274,"HyperDash":false},{"StartTime":100523.0,"Position":110.453743,"HyperDash":false},{"StartTime":100628.0,"Position":102.687973,"HyperDash":false}]},{"StartTime":100941.0,"Objects":[{"StartTime":100941.0,"Position":349.0,"HyperDash":false}]},{"StartTime":101254.0,"Objects":[{"StartTime":101254.0,"Position":383.0,"HyperDash":false},{"StartTime":101332.0,"Position":419.957733,"HyperDash":false},{"StartTime":101410.0,"Position":426.299622,"HyperDash":false},{"StartTime":101488.0,"Position":445.490662,"HyperDash":false},{"StartTime":101566.0,"Position":455.829254,"HyperDash":false},{"StartTime":101635.0,"Position":451.601563,"HyperDash":false},{"StartTime":101704.0,"Position":456.379,"HyperDash":false},{"StartTime":101773.0,"Position":416.516266,"HyperDash":false},{"StartTime":101879.0,"Position":388.265564,"HyperDash":false}]},{"StartTime":102191.0,"Objects":[{"StartTime":102191.0,"Position":135.0,"HyperDash":false}]},{"StartTime":102504.0,"Objects":[{"StartTime":102504.0,"Position":123.0,"HyperDash":false},{"StartTime":102573.0,"Position":136.53656,"HyperDash":false},{"StartTime":102642.0,"Position":127.073128,"HyperDash":false},{"StartTime":102711.0,"Position":116.609695,"HyperDash":false},{"StartTime":102816.0,"Position":107.339249,"HyperDash":false}]},{"StartTime":103129.0,"Objects":[{"StartTime":103129.0,"Position":252.0,"HyperDash":false},{"StartTime":103198.0,"Position":284.326935,"HyperDash":false},{"StartTime":103267.0,"Position":309.653839,"HyperDash":false},{"StartTime":103336.0,"Position":338.980774,"HyperDash":false},{"StartTime":103441.0,"Position":362.0,"HyperDash":false}]},{"StartTime":103754.0,"Objects":[{"StartTime":103754.0,"Position":215.0,"HyperDash":false},{"StartTime":103832.0,"Position":174.651,"HyperDash":false},{"StartTime":103910.0,"Position":155.666367,"HyperDash":false},{"StartTime":103988.0,"Position":122.457794,"HyperDash":false},{"StartTime":104066.0,"Position":113.4155,"HyperDash":false},{"StartTime":104135.0,"Position":85.44563,"HyperDash":false},{"StartTime":104204.0,"Position":103.505188,"HyperDash":false},{"StartTime":104273.0,"Position":78.09056,"HyperDash":false},{"StartTime":104379.0,"Position":76.11086,"HyperDash":false}]},{"StartTime":104691.0,"Objects":[{"StartTime":104691.0,"Position":353.0,"HyperDash":false}]},{"StartTime":105004.0,"Objects":[{"StartTime":105004.0,"Position":359.0,"HyperDash":false},{"StartTime":105073.0,"Position":360.169861,"HyperDash":false},{"StartTime":105142.0,"Position":374.3397,"HyperDash":false},{"StartTime":105211.0,"Position":369.509552,"HyperDash":false},{"StartTime":105316.0,"Position":368.8115,"HyperDash":false}]},{"StartTime":105629.0,"Objects":[{"StartTime":105629.0,"Position":215.0,"HyperDash":false},{"StartTime":105698.0,"Position":245.2746,"HyperDash":false},{"StartTime":105767.0,"Position":263.5492,"HyperDash":false},{"StartTime":105836.0,"Position":298.8238,"HyperDash":false},{"StartTime":105941.0,"Position":324.7634,"HyperDash":false}]},{"StartTime":106254.0,"Objects":[{"StartTime":106254.0,"Position":164.0,"HyperDash":false},{"StartTime":106332.0,"Position":181.330521,"HyperDash":false},{"StartTime":106410.0,"Position":231.661041,"HyperDash":false},{"StartTime":106488.0,"Position":258.991547,"HyperDash":false},{"StartTime":106566.0,"Position":273.322083,"HyperDash":false},{"StartTime":106635.0,"Position":308.499084,"HyperDash":false},{"StartTime":106704.0,"Position":315.6761,"HyperDash":false},{"StartTime":106773.0,"Position":330.8531,"HyperDash":false},{"StartTime":106878.0,"Position":382.644135,"HyperDash":false}]},{"StartTime":107191.0,"Objects":[{"StartTime":107191.0,"Position":64.0,"HyperDash":false}]},{"StartTime":107504.0,"Objects":[{"StartTime":107504.0,"Position":390.0,"HyperDash":false},{"StartTime":107582.0,"Position":367.983734,"HyperDash":false},{"StartTime":107660.0,"Position":346.967468,"HyperDash":false},{"StartTime":107738.0,"Position":308.9512,"HyperDash":false},{"StartTime":107816.0,"Position":281.934967,"HyperDash":false},{"StartTime":107885.0,"Position":292.833954,"HyperDash":false},{"StartTime":107954.0,"Position":315.732941,"HyperDash":false},{"StartTime":108023.0,"Position":344.631958,"HyperDash":false},{"StartTime":108128.0,"Position":390.0,"HyperDash":false}]},{"StartTime":108441.0,"Objects":[{"StartTime":108441.0,"Position":219.0,"HyperDash":false}]},{"StartTime":108754.0,"Objects":[{"StartTime":108754.0,"Position":99.0,"HyperDash":false},{"StartTime":108823.0,"Position":80.78087,"HyperDash":false},{"StartTime":108892.0,"Position":79.56174,"HyperDash":false},{"StartTime":108961.0,"Position":79.34261,"HyperDash":false},{"StartTime":109066.0,"Position":88.9656754,"HyperDash":false}]},{"StartTime":109379.0,"Objects":[{"StartTime":109379.0,"Position":228.0,"HyperDash":false},{"StartTime":109448.0,"Position":259.2614,"HyperDash":false},{"StartTime":109517.0,"Position":268.522858,"HyperDash":false},{"StartTime":109586.0,"Position":318.7843,"HyperDash":false},{"StartTime":109691.0,"Position":337.703827,"HyperDash":false}]},{"StartTime":110004.0,"Objects":[{"StartTime":110004.0,"Position":183.0,"HyperDash":false},{"StartTime":110082.0,"Position":220.45372,"HyperDash":false},{"StartTime":110160.0,"Position":245.90744,"HyperDash":false},{"StartTime":110238.0,"Position":248.361145,"HyperDash":false},{"StartTime":110316.0,"Position":292.81488,"HyperDash":false},{"StartTime":110385.0,"Position":310.10083,"HyperDash":false},{"StartTime":110454.0,"Position":355.386841,"HyperDash":false},{"StartTime":110523.0,"Position":350.6728,"HyperDash":false},{"StartTime":110628.0,"Position":402.6297,"HyperDash":false}]},{"StartTime":110941.0,"Objects":[{"StartTime":110941.0,"Position":108.0,"HyperDash":false}]},{"StartTime":111254.0,"Objects":[{"StartTime":111254.0,"Position":114.0,"HyperDash":false},{"StartTime":111323.0,"Position":102.780869,"HyperDash":false},{"StartTime":111392.0,"Position":118.561737,"HyperDash":false},{"StartTime":111461.0,"Position":104.342613,"HyperDash":false},{"StartTime":111566.0,"Position":103.965675,"HyperDash":false}]},{"StartTime":111879.0,"Objects":[{"StartTime":111879.0,"Position":243.0,"HyperDash":false},{"StartTime":111948.0,"Position":284.2614,"HyperDash":false},{"StartTime":112017.0,"Position":284.522858,"HyperDash":false},{"StartTime":112086.0,"Position":327.7843,"HyperDash":false},{"StartTime":112191.0,"Position":352.703827,"HyperDash":false}]},{"StartTime":112504.0,"Objects":[{"StartTime":112504.0,"Position":198.0,"HyperDash":false},{"StartTime":112582.0,"Position":234.45372,"HyperDash":false},{"StartTime":112660.0,"Position":250.90744,"HyperDash":false},{"StartTime":112738.0,"Position":271.361145,"HyperDash":false},{"StartTime":112816.0,"Position":307.81488,"HyperDash":false},{"StartTime":112885.0,"Position":345.10083,"HyperDash":false},{"StartTime":112954.0,"Position":347.386841,"HyperDash":false},{"StartTime":113023.0,"Position":393.6728,"HyperDash":false},{"StartTime":113128.0,"Position":417.6297,"HyperDash":false}]},{"StartTime":113441.0,"Objects":[{"StartTime":113441.0,"Position":123.0,"HyperDash":false}]},{"StartTime":113754.0,"Objects":[{"StartTime":113754.0,"Position":151.0,"HyperDash":false}]},{"StartTime":113910.0,"Objects":[{"StartTime":113910.0,"Position":103.0,"HyperDash":false}]},{"StartTime":114379.0,"Objects":[{"StartTime":114379.0,"Position":314.0,"HyperDash":false},{"StartTime":114478.0,"Position":299.4618,"HyperDash":false},{"StartTime":114613.0,"Position":319.818817,"HyperDash":false}]},{"StartTime":114847.0,"Objects":[{"StartTime":114847.0,"Position":211.0,"HyperDash":false},{"StartTime":114925.0,"Position":253.437714,"HyperDash":false},{"StartTime":115003.0,"Position":252.875427,"HyperDash":false},{"StartTime":115081.0,"Position":297.313171,"HyperDash":false},{"StartTime":115159.0,"Position":320.7509,"HyperDash":false},{"StartTime":115219.0,"Position":346.8568,"HyperDash":false},{"StartTime":115315.0,"Position":375.6263,"HyperDash":false}]},{"StartTime":115629.0,"Objects":[{"StartTime":115629.0,"Position":141.0,"HyperDash":false}]},{"StartTime":115941.0,"Objects":[{"StartTime":115941.0,"Position":407.0,"HyperDash":false}]}]} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/2571731.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/2571731.osu new file mode 100644 index 0000000000..bef278e769 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/2571731.osu @@ -0,0 +1,277 @@ +osu file format v14 + +[General] +StackLeniency: 0.7 +Mode: 2 + +[Difficulty] +HPDrainRate:2 +CircleSize:2 +OverallDifficulty:6 +ApproachRate:6 +SliderMultiplier:1.1 +SliderTickRate:1 + +[Events] +//Background and Video events +//Break Periods +2,61250,70204 +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Layer 4 (Overlay) +//Storyboard Sound Samples + +[TimingPoints] +4,312.5,4,2,1,70,1,0 +7425,-100,4,2,1,75,0,0 +8754,-100,4,2,1,80,0,0 +9379,-100,4,2,1,80,0,0 +10004,-100,4,2,1,40,0,0 +10472,-100,4,2,1,55,0,0 +10629,-100,4,2,1,65,0,0 +10941,-100,4,2,1,70,0,0 +11254,-100,4,2,3,65,0,0 +12504,-100,4,2,3,65,0,0 +21254,-100,4,2,1,70,0,0 +30629,-100,4,2,1,70,0,0 +30941,-100,4,2,1,70,0,0 +31254,-100,4,2,1,70,0,0 +32191,-100,4,2,1,70,0,0 +32504,-100,4,2,1,70,0,0 +35629,-100,4,2,1,70,0,0 +36254,-100,4,2,1,70,0,0 +37035,-100,4,2,1,70,0,0 +37504,-100,4,2,1,70,0,0 +41722,-100,4,2,1,70,0,0 +42504,-100,4,2,1,70,0,0 +42816,-100,4,2,1,70,0,0 +43754,-100,4,2,1,70,0,0 +44222,-100,4,2,1,70,0,0 +44691,-100,4,2,1,70,0,0 +45004,-100,4,2,1,70,0,0 +45941,-100,4,2,1,70,0,0 +46254,-100,4,2,1,70,0,0 +46722,-100,4,2,1,70,0,0 +47504,-100,4,2,1,70,0,0 +47816,-100,4,2,1,70,0,0 +48441,-100,4,2,1,70,0,0 +48754,-100,4,2,1,70,0,0 +49222,-100,4,2,1,70,0,0 +50004,-100,4,2,1,70,0,0 +50082,-100,4,2,1,25,0,0 +50160,-100,4,2,1,25,0,0 +50238,-100,4,2,1,25,0,0 +50316,-100,4,2,1,25,0,0 +50394,-100,4,2,1,25,0,0 +50472,-100,4,2,1,25,0,0 +50550,-100,4,2,1,25,0,0 +50629,-100,4,2,1,25,0,0 +50707,-100,4,2,1,25,0,0 +50785,-100,4,2,1,25,0,0 +50863,-100,4,2,1,25,0,0 +50941,-100,4,2,1,70,0,0 +51254,-100,4,2,1,70,0,0 +53754,-100,4,2,1,70,0,0 +55004,-100,4,2,1,70,0,0 +56722,-100,4,2,1,70,0,0 +57972,-100,4,2,1,70,0,0 +58754,-100,4,2,1,70,0,0 +59847,-100,4,1,1,65,0,0 +71254,-100,4,2,1,70,0,0 +81254,-100,4,2,1,70,0,0 +83754,-100,4,2,1,70,0,0 +86254,-100,4,2,1,70,0,0 +87504,-100,4,2,1,70,0,0 +91254,-100,4,2,1,70,0,1 +93754,-100,4,2,1,70,0,1 +95004,-100,4,2,1,70,0,1 +100004,-100,4,2,1,70,0,1 +100550,-100,4,2,1,70,0,1 +100941,-100,4,2,1,70,0,1 +103754,-100,4,2,1,70,0,1 +105004,-100,4,2,1,70,0,1 +108754,-100,4,2,1,70,0,1 +110004,-100,4,2,1,70,0,1 +110629,-100,4,2,1,70,0,1 +112504,-100,4,2,1,70,0,1 +113129,-100,4,2,1,70,0,1 +113754,-100,4,2,1,70,0,1 +114379,-100,4,2,1,70,0,1 +114847,-100,4,2,1,70,0,1 +115863,-100,4,2,1,70,0,0 +115941,-100,4,2,1,70,0,1 +116019,-100,4,2,1,70,0,0 + +[HitObjects] +229,264,1254,6,0,P|161:183|254:125,1,220,4|8,1:2|0:0,0:0:0:0: +362,120,2191,1,2,0:0:0:0: +228,119,2504,2,0,L|87:118,1,110,0|2,1:0|0:0,0:0:0:0: +231,216,3129,2,0,L|372:215,1,110,8|2,0:0|0:0,0:0:0:0: +465,214,3754,6,0,P|439:111|303:80,1,220,0|10,1:0|0:0,0:0:0:0: +217,117,4691,1,2,0:0:0:0: +365,123,5004,2,0,L|367:252,1,110,0|2,1:0|0:0,0:0:0:0: +228,313,5629,2,0,L|357:315,1,110,8|2,2:0|0:0,0:0:0:0: +197,303,6254,6,0,P|98:270|59:136,1,220,4|8,1:2|0:0,0:0:0:0: +171,156,7191,1,2,0:0:0:0: +290,138,7504,2,0,L|308:275,1,110,0|2,1:0|0:0,0:0:0:0: +178,249,8129,1,8,0:0:0:0: +308,247,8441,1,2,0:0:0:0: +168,249,8754,6,0,L|53:245,1,110,4|0,1:2|3:0,0:0:0:0: +226,153,9379,2,0,L|343:149,1,110,2|2,1:3|3:2,0:0:0:0: +256,192,10004,12,2,10941,1:3:0:0: +173,329,11254,6,0,P|79:249|178:220,1,220,4|2,0:0|1:3,0:0:0:0: +263,211,12191,1,10,0:0:0:0: +119,212,12504,2,0,L|103:52,1,110,2|8,1:3|0:0,0:0:0:0: +246,65,13129,2,0,L|103:66,1,110,2|8,1:3|0:0,0:0:0:0: +290,64,13754,6,0,P|384:120|284:162,1,220,6|2,1:3|1:3,0:0:0:0: +182,220,14691,1,8,0:0:0:0: +335,208,15004,2,0,L|75:142,1,220,2|2,1:3|1:3,0:0:0:0: +275,153,15941,1,8,0:0:0:0: +120,151,16254,6,0,P|157:258|268:282,1,220,6|2,1:3|1:3,0:0:0:0: +405,290,17191,1,10,0:0:0:0: +250,286,17504,2,0,L|96:264,2,110,6|8|2,1:3|0:0|1:3,0:0:0:0: +403,275,18441,1,8,0:0:0:0: +250,286,18754,6,0,P|186:189|264:160,1,220,6|6,1:3|1:3,0:0:0:0: +404,157,19691,1,8,0:0:0:0: +249,151,20004,5,6,1:3:0:0: +245,233,20316,1,8,0:0:0:0: +240,317,20629,1,8,0:0:0:0: +399,222,20941,1,4,1:2:0:0: +240,317,21254,6,0,P|140:279|114:128,1,220,4|2,1:2|1:3,0:0:0:0: +243,184,22191,1,8,1:0:0:0: +386,178,22504,2,0,L|403:327,1,110,0|8,1:0|1:0,0:0:0:0: +264,338,23129,2,0,L|119:336,1,110,2|8,1:3|1:0,0:0:0:0: +292,300,23754,6,0,P|361:228|270:161,1,220,0|2,1:0|1:3,0:0:0:0: +147,160,24691,1,8,1:0:0:0: +285,124,25004,2,0,L|391:50,2,110,4|10|2,1:0|1:0|1:0,0:0:0:0: +428,128,25941,1,0,1:0:0:0: +284,130,26254,6,0,P|319:238|428:274,1,220,4|2,1:2|1:3,0:0:0:0: +268,276,27191,1,8,1:0:0:0: +124,277,27504,2,0,L|109:125,1,110,0|8,1:0|0:0,0:0:0:0: +250,126,28129,2,0,L|115:102,1,110,2|8,1:3|1:0,0:0:0:0: +284,96,28754,6,0,P|385:143|411:266,1,220,4|2,1:0|1:0,0:0:0:0: +273,240,29691,1,8,1:0:0:0: +416,236,30004,5,4,1:2:0:0: +436,94,30316,1,8,1:0:0:0: +294,75,30629,2,0,L|144:75,1,110,6|0,1:0|3:0,0:0:0:0: +351,138,31254,6,0,P|441:184|405:291,1,220,4|8,0:0|0:0,0:0:0:0: +277,246,32191,1,2,1:2:0:0: +144,299,32504,2,0,L|411:257,1,220,4|8,1:0|1:0,0:0:0:0: +201,244,33440,1,4,1:0:0:0: +140,283,33597,6,0,P|98:231|162:160,1,165,4|8,1:2|0:0,0:0:0:0: +239,112,34379,2,0,L|126:97,1,110,4|8,3:0|1:0,0:0:0:0: +264,173,35004,6,0,L|396:189,1,110,0|8,1:0|1:0,0:0:0:0: +223,227,35629,2,0,L|218:103,1,110,10|2,1:0|1:2,0:0:0:0: +379,115,36254,1,4,1:2:0:0: +398,117,37035,2,0,L|403:211,1,82.5,4|4,1:0|1:0,0:0:0:0: +284,252,37504,6,0,L|169:243,1,110,0|2,1:0|0:0,0:0:0:0: +305,327,38129,2,0,L|423:317,1,110,2|8,0:0|1:2,0:0:0:0: +415,223,38597,6,0,L|233:207,1,165,4|14,1:2|1:0,0:0:0:0: +113,252,39379,2,0,L|103:126,1,110,6|6,1:0|1:0,0:0:0:0: +244,114,40004,5,4,1:2:0:0: +250,185,40316,1,2,2:0:0:0: +253,260,40629,1,2,1:2:0:0: +89,272,40941,1,0,1:0:0:0: +256,305,41254,6,0,P|342:288|366:191,2,165,4|4|8,1:0|1:0|2:0,0:0:0:0: +98,202,42504,2,0,P|184:185|208:88,2,165,4|4|8,1:0|1:0|1:0,0:0:0:0: +249,82,43754,6,0,L|58:83,2,165,4|4|8,1:0|1:0|1:0,0:0:0:0: +256,192,45004,12,2,45941,1:0:0:0: +332,305,46254,6,0,P|396:289|434:187,2,165,4|0|0,1:0|1:0|1:0,0:0:0:0: +180,305,47504,2,0,P|116:289|78:187,2,165,4|4|0,1:0|1:0|1:0,0:0:0:0: +335,231,48754,6,0,L|145:232,2,165,4|4|8,1:0|1:0|1:0,0:0:0:0: +256,192,50004,12,2,50941,0:0:0:0: +119,198,51254,6,0,L|104:299,1,82.5,4|4,1:0|1:0,0:0:0:0: +230,290,51722,2,0,L|252:120,1,165,4|2,1:0|1:0,0:0:0:0: +373,113,52504,6,0,L|388:214,1,82.5,4|4,1:0|1:0,0:0:0:0: +269,207,52972,2,0,P|240:107|282:67,1,165,4|2,1:0|1:0,0:0:0:0: +424,88,53754,6,0,L|325:81,1,82.5,4|4,1:0|1:0,0:0:0:0: +228,196,54222,2,0,L|408:181,1,165,4|2,1:0|1:0,0:0:0:0: +241,238,55004,6,0,L|340:231,1,82.5,4|4,1:0|1:0,0:0:0:0: +437,346,55472,2,0,L|257:331,1,165,2|10,1:0|1:0,0:0:0:0: +130,320,56254,5,4,1:2:0:0: +260,244,56488,2,0,L|272:143,1,82.5,2|2,1:0|1:0,0:0:0:0: +162,106,56957,2,0,L|64:127,1,82.5,2|2,1:0|1:0,0:0:0:0: +233,322,57504,5,2,1:0:0:0: +363,246,57738,2,0,L|270:233,1,82.5,2|2,1:0|1:0,0:0:0:0: +174,230,58207,2,0,L|76:251,1,82.5,2|2,1:0|1:0,0:0:0:0: +261,143,58754,6,0,L|76:124,2,165,4|4|4,1:2|1:2|1:2,0:0:0:0: +256,192,60004,12,0,60941,1:0:0:0: +258,195,71254,5,0,1:0:0:0: +69,186,71722,2,0,L|59:367,1,165,2|2,0:0|0:0,0:0:0:0: +220,198,72504,1,2,0:0:0:0: +254,195,73754,5,4,0:0:0:0: +443,186,74222,2,0,L|453:367,1,165,2|2,0:0|0:0,0:0:0:0: +292,198,75004,1,2,0:0:0:0: +136,196,76254,5,4,0:0:0:0: +349,161,76722,2,0,L|165:160,1,165,2|2,0:0|0:0,0:0:0:0: +350,161,77504,1,2,0:0:0:0: +376,196,78754,5,4,0:0:0:0: +163,161,79222,2,0,L|347:160,1,165,0|2,0:0|0:0,0:0:0:0: +179,253,80004,1,2,0:0:0:0: +88,255,80316,1,2,0:0:0:0: +88,255,80629,1,2,0:0:0:0: +192,256,80941,1,6,1:2:0:0: +336,252,81254,6,0,L|220:254,1,110,4|10,1:2|1:0,0:0:0:0: +366,149,81879,2,0,L|482:151,1,110,4|10,1:2|1:0,0:0:0:0: +319,41,82504,1,4,1:2:0:0: +224,96,82816,1,8,1:0:0:0: +196,202,83129,1,4,1:2:0:0: +248,298,83441,1,10,1:0:0:0: +390,323,83754,6,0,L|271:324,2,110,4|2|2,1:2|0:0|0:0,0:0:0:0: +104,321,85004,1,4,1:2:0:0: +324,329,86254,5,4,1:0:0:0: +422,281,86566,1,4,1:0:0:0: +446,173,86879,1,4,1:0:0:0: +411,68,87191,1,4,1:0:0:0: +287,49,87504,6,0,L|402:51,1,110,4|6,1:0|1:0,0:0:0:0: +265,155,88129,2,0,L|141:153,1,110,4|6,1:0|1:0,0:0:0:0: +308,153,88754,5,2,1:0:0:0: +408,197,89066,1,2,1:0:0:0: +432,304,89379,1,2,1:0:0:0: +348,374,89691,1,2,1:0:0:0: +256,192,90004,12,4,90941,1:2:0:0: +140,282,91254,6,0,P|71:226|156:145,1,220,4|4,1:2|1:0,0:0:0:0: +268,155,92191,1,8,1:0:0:0: +405,152,92504,2,0,L|274:151,1,110,2|10,1:0|1:0,0:0:0:0: +154,250,93129,2,0,L|295:252,1,110,2|10,1:0|1:0,0:0:0:0: +135,329,93754,6,0,L|380:330,1,220,4|0,1:2|1:0,0:0:0:0: +239,290,94691,1,8,1:0:0:0: +354,223,95004,2,0,L|213:210,1,110,4|8,1:0|1:0,0:0:0:0: +97,240,95629,2,0,L|79:127,1,110,2|10,1:0|1:0,0:0:0:0: +238,55,96254,6,0,P|313:95|229:166,1,220,4|0,1:2|1:0,0:0:0:0: +363,205,97191,1,8,1:0:0:0: +252,247,97504,2,0,L|115:270,2,110,2|10|2,1:0|1:0|1:0,0:0:0:0: +363,287,98441,1,10,1:0:0:0: +223,343,98754,6,0,L|92:326,1,110,4|10,1:2|1:0,0:0:0:0: +293,262,99379,1,2,1:0:0:0: +396,244,99691,1,8,1:0:0:0: +236,219,100004,6,0,P|160:186|103:55,1,220,4|0,1:2|1:0,0:0:0:0: +226,68,100941,1,0,1:0:0:0: +383,69,101254,6,0,P|456:139|387:208,1,220,4|2,1:2|1:0,0:0:0:0: +261,244,102191,1,10,1:0:0:0: +123,311,102504,2,0,L|102:165,1,110,2|10,1:0|1:0,0:0:0:0: +252,178,103129,2,0,L|386:178,1,110,2|10,1:0|1:0,0:0:0:0: +215,263,103754,6,0,P|123:241|79:117,1,220,4|2,1:2|1:0,0:0:0:0: +216,121,104691,1,10,1:0:0:0: +359,106,105004,2,0,L|371:240,1,110,4|8,1:0|1:0,0:0:0:0: +215,312,105629,2,0,L|352:321,1,110,2|10,1:0|1:0,0:0:0:0: +164,359,106254,6,0,L|424:330,1,220,4|2,1:2|1:0,0:0:0:0: +244,297,107191,1,10,1:0:0:0: +390,278,107504,2,0,L|269:255,2,110,2|10|2,1:0|1:0|1:0,0:0:0:0: +244,276,108441,1,10,1:0:0:0: +99,281,108754,6,0,L|87:150,1,110,4|10,1:2|1:0,0:0:0:0: +228,146,109379,2,0,L|364:136,1,110,2|10,1:0|1:0,0:0:0:0: +183,278,110004,6,0,L|424:264,1,220,4|2,1:2|0:0,0:0:0:0: +266,255,110941,1,10,1:0:0:0: +114,283,111254,6,0,L|102:152,1,110,4|10,0:0|1:0,0:0:0:0: +243,148,111879,2,0,L|379:138,1,110,2|10,1:0|1:0,0:0:0:0: +198,280,112504,6,0,L|439:266,1,220,4|2,1:2|1:0,0:0:0:0: +281,257,113441,1,8,1:0:0:0: +137,295,113754,5,4,1:2:0:0: +127,239,113910,1,4,1:2:0:0: +314,108,114379,2,0,L|321:207,1,82.5,4|4,0:0|0:0,0:0:0:0: +211,254,114847,6,0,L|389:266,1,165,6|6,1:0|1:0,0:0:0:0: +265,275,115629,1,4,1:0:0:0: +407,299,115941,1,4,1:2:0:0: diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/2768615-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/2768615-expected-conversion.json new file mode 100644 index 0000000000..2ebebdbe7a --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/2768615-expected-conversion.json @@ -0,0 +1 @@ +{"Mappings":[{"StartTime":514.0,"Objects":[{"StartTime":514.0,"Position":6.0,"HyperDash":false},{"StartTime":586.0,"Position":0.0,"HyperDash":false},{"StartTime":695.0,"Position":8.064062,"HyperDash":false}]},{"StartTime":877.0,"Objects":[{"StartTime":877.0,"Position":20.0,"HyperDash":false}]},{"StartTime":1059.0,"Objects":[{"StartTime":1059.0,"Position":14.0,"HyperDash":false},{"StartTime":1131.0,"Position":3.79381847,"HyperDash":false},{"StartTime":1240.0,"Position":15.99557,"HyperDash":false}]},{"StartTime":1604.0,"Objects":[{"StartTime":1604.0,"Position":21.0,"HyperDash":false},{"StartTime":1685.0,"Position":19.13056,"HyperDash":false},{"StartTime":1767.0,"Position":38.2750778,"HyperDash":false},{"StartTime":1849.0,"Position":41.4195938,"HyperDash":false},{"StartTime":1967.0,"Position":26.0665855,"HyperDash":false}]},{"StartTime":2332.0,"Objects":[{"StartTime":2332.0,"Position":28.0,"HyperDash":false}]},{"StartTime":2513.0,"Objects":[{"StartTime":2513.0,"Position":27.0,"HyperDash":false},{"StartTime":2576.0,"Position":28.8067379,"HyperDash":false},{"StartTime":2640.0,"Position":27.6262817,"HyperDash":false},{"StartTime":2703.0,"Position":14.43302,"HyperDash":false},{"StartTime":2767.0,"Position":22.2525616,"HyperDash":false},{"StartTime":2831.0,"Position":19.0721054,"HyperDash":false},{"StartTime":2894.0,"Position":27.8788433,"HyperDash":false},{"StartTime":2958.0,"Position":46.6983871,"HyperDash":false},{"StartTime":3058.0,"Position":33.9789238,"HyperDash":false}]},{"StartTime":3423.0,"Objects":[{"StartTime":3423.0,"Position":46.0,"HyperDash":false},{"StartTime":3495.0,"Position":50.821064,"HyperDash":false},{"StartTime":3604.0,"Position":48.064064,"HyperDash":false}]},{"StartTime":3786.0,"Objects":[{"StartTime":3786.0,"Position":60.0,"HyperDash":false}]},{"StartTime":3968.0,"Objects":[{"StartTime":3968.0,"Position":54.0,"HyperDash":false},{"StartTime":4040.0,"Position":45.79382,"HyperDash":false},{"StartTime":4149.0,"Position":55.99557,"HyperDash":false}]},{"StartTime":4513.0,"Objects":[{"StartTime":4513.0,"Position":61.0,"HyperDash":false},{"StartTime":4585.0,"Position":66.82106,"HyperDash":false},{"StartTime":4694.0,"Position":63.064064,"HyperDash":false}]},{"StartTime":4877.0,"Objects":[{"StartTime":4877.0,"Position":65.0,"HyperDash":false},{"StartTime":4967.0,"Position":66.0687,"HyperDash":false},{"StartTime":5058.0,"Position":65.0,"HyperDash":false}]},{"StartTime":5241.0,"Objects":[{"StartTime":5241.0,"Position":68.0,"HyperDash":false},{"StartTime":5313.0,"Position":49.8210678,"HyperDash":false},{"StartTime":5422.0,"Position":70.064064,"HyperDash":false}]},{"StartTime":5604.0,"Objects":[{"StartTime":5604.0,"Position":77.0,"HyperDash":false},{"StartTime":5660.0,"Position":67.75663,"HyperDash":false},{"StartTime":5717.0,"Position":94.50892,"HyperDash":false},{"StartTime":5774.0,"Position":93.26121,"HyperDash":false},{"StartTime":5831.0,"Position":76.0135,"HyperDash":false},{"StartTime":5926.0,"Position":89.42635,"HyperDash":false},{"StartTime":6058.0,"Position":77.0,"HyperDash":false}]},{"StartTime":6332.0,"Objects":[{"StartTime":6332.0,"Position":96.0,"HyperDash":false}]},{"StartTime":6513.0,"Objects":[{"StartTime":6513.0,"Position":80.0,"HyperDash":false}]},{"StartTime":6877.0,"Objects":[{"StartTime":6877.0,"Position":108.0,"HyperDash":false}]},{"StartTime":7059.0,"Objects":[{"StartTime":7059.0,"Position":96.0,"HyperDash":false},{"StartTime":7131.0,"Position":91.4434738,"HyperDash":false},{"StartTime":7240.0,"Position":98.95893,"HyperDash":false}]},{"StartTime":7423.0,"Objects":[{"StartTime":7423.0,"Position":101.0,"HyperDash":false},{"StartTime":7495.0,"Position":79.8501,"HyperDash":false},{"StartTime":7604.0,"Position":96.87991,"HyperDash":false}]},{"StartTime":7786.0,"Objects":[{"StartTime":7786.0,"Position":115.0,"HyperDash":false}]},{"StartTime":7968.0,"Objects":[{"StartTime":7968.0,"Position":95.0,"HyperDash":false}]},{"StartTime":8150.0,"Objects":[{"StartTime":8150.0,"Position":127.0,"HyperDash":false}]},{"StartTime":8332.0,"Objects":[{"StartTime":8332.0,"Position":110.0,"HyperDash":false},{"StartTime":8404.0,"Position":123.196411,"HyperDash":false},{"StartTime":8513.0,"Position":105.877426,"HyperDash":false}]},{"StartTime":8695.0,"Objects":[{"StartTime":8695.0,"Position":110.0,"HyperDash":false},{"StartTime":8767.0,"Position":104.810783,"HyperDash":false},{"StartTime":8876.0,"Position":113.827438,"HyperDash":false}]},{"StartTime":9241.0,"Objects":[{"StartTime":9241.0,"Position":138.0,"HyperDash":false}]},{"StartTime":9423.0,"Objects":[{"StartTime":9423.0,"Position":131.0,"HyperDash":false},{"StartTime":9495.0,"Position":133.615219,"HyperDash":false},{"StartTime":9604.0,"Position":128.964035,"HyperDash":false}]},{"StartTime":9786.0,"Objects":[{"StartTime":9786.0,"Position":143.0,"HyperDash":false}]},{"StartTime":9968.0,"Objects":[{"StartTime":9968.0,"Position":136.0,"HyperDash":false},{"StartTime":10027.0,"Position":122.678848,"HyperDash":false},{"StartTime":10086.0,"Position":134.039719,"HyperDash":false},{"StartTime":10145.0,"Position":136.077042,"HyperDash":false},{"StartTime":10240.0,"Position":132.939224,"HyperDash":false}]},{"StartTime":10332.0,"Objects":[{"StartTime":10332.0,"Position":139.0,"HyperDash":false},{"StartTime":10391.0,"Position":156.013535,"HyperDash":false},{"StartTime":10450.0,"Position":128.715408,"HyperDash":false},{"StartTime":10509.0,"Position":125.074265,"HyperDash":false},{"StartTime":10604.0,"Position":141.969208,"HyperDash":false}]},{"StartTime":10695.0,"Objects":[{"StartTime":10695.0,"Position":150.0,"HyperDash":false}]},{"StartTime":10877.0,"Objects":[{"StartTime":10877.0,"Position":146.0,"HyperDash":false}]},{"StartTime":11059.0,"Objects":[{"StartTime":11059.0,"Position":156.0,"HyperDash":false}]},{"StartTime":11241.0,"Objects":[{"StartTime":11241.0,"Position":150.0,"HyperDash":false}]},{"StartTime":11423.0,"Objects":[{"StartTime":11423.0,"Position":156.0,"HyperDash":false},{"StartTime":11513.0,"Position":158.989288,"HyperDash":false}]},{"StartTime":11604.0,"Objects":[{"StartTime":11604.0,"Position":157.0,"HyperDash":false}]},{"StartTime":11695.0,"Objects":[{"StartTime":11695.0,"Position":163.0,"HyperDash":false}]},{"StartTime":11786.0,"Objects":[{"StartTime":11786.0,"Position":161.0,"HyperDash":false},{"StartTime":11876.0,"Position":161.977341,"HyperDash":false}]},{"StartTime":11968.0,"Objects":[{"StartTime":11968.0,"Position":165.0,"HyperDash":false},{"StartTime":12058.0,"Position":165.977341,"HyperDash":false}]},{"StartTime":12150.0,"Objects":[{"StartTime":12150.0,"Position":166.0,"HyperDash":false},{"StartTime":12222.0,"Position":150.82106,"HyperDash":false},{"StartTime":12331.0,"Position":168.064056,"HyperDash":false}]},{"StartTime":12513.0,"Objects":[{"StartTime":12513.0,"Position":180.0,"HyperDash":false}]},{"StartTime":12695.0,"Objects":[{"StartTime":12695.0,"Position":174.0,"HyperDash":false},{"StartTime":12747.0,"Position":187.463669,"HyperDash":false},{"StartTime":12799.0,"Position":191.927322,"HyperDash":false},{"StartTime":12851.0,"Position":174.390991,"HyperDash":false},{"StartTime":12904.0,"Position":168.863571,"HyperDash":false},{"StartTime":12956.0,"Position":175.32724,"HyperDash":false},{"StartTime":13008.0,"Position":195.7909,"HyperDash":false},{"StartTime":13060.0,"Position":171.254562,"HyperDash":false},{"StartTime":13149.0,"Position":178.048141,"HyperDash":false}]},{"StartTime":13241.0,"Objects":[{"StartTime":13241.0,"Position":183.0,"HyperDash":false},{"StartTime":13313.0,"Position":187.17894,"HyperDash":false},{"StartTime":13422.0,"Position":180.935944,"HyperDash":false}]},{"StartTime":13604.0,"Objects":[{"StartTime":13604.0,"Position":191.0,"HyperDash":false}]},{"StartTime":13786.0,"Objects":[{"StartTime":13786.0,"Position":185.0,"HyperDash":false}]},{"StartTime":13968.0,"Objects":[{"StartTime":13968.0,"Position":197.0,"HyperDash":false}]},{"StartTime":14150.0,"Objects":[{"StartTime":14150.0,"Position":191.0,"HyperDash":false},{"StartTime":14213.0,"Position":204.4671,"HyperDash":false},{"StartTime":14277.0,"Position":208.941635,"HyperDash":false},{"StartTime":14340.0,"Position":209.408737,"HyperDash":false},{"StartTime":14404.0,"Position":207.88327,"HyperDash":false},{"StartTime":14468.0,"Position":210.357788,"HyperDash":false},{"StartTime":14531.0,"Position":202.82489,"HyperDash":false},{"StartTime":14595.0,"Position":177.299423,"HyperDash":false},{"StartTime":14695.0,"Position":195.040863,"HyperDash":false}]},{"StartTime":15059.0,"Objects":[{"StartTime":15059.0,"Position":217.0,"HyperDash":false}]},{"StartTime":15150.0,"Objects":[{"StartTime":15150.0,"Position":197.0,"HyperDash":false}]},{"StartTime":15240.0,"Objects":[{"StartTime":15240.0,"Position":219.0,"HyperDash":false}]},{"StartTime":15422.0,"Objects":[{"StartTime":15422.0,"Position":209.0,"HyperDash":false}]},{"StartTime":15604.0,"Objects":[{"StartTime":15604.0,"Position":214.0,"HyperDash":false},{"StartTime":15656.0,"Position":219.463669,"HyperDash":false},{"StartTime":15708.0,"Position":202.927322,"HyperDash":false},{"StartTime":15760.0,"Position":200.390991,"HyperDash":false},{"StartTime":15813.0,"Position":233.863571,"HyperDash":false},{"StartTime":15865.0,"Position":208.32724,"HyperDash":false},{"StartTime":15917.0,"Position":232.7909,"HyperDash":false},{"StartTime":15969.0,"Position":220.254562,"HyperDash":false},{"StartTime":16058.0,"Position":218.048141,"HyperDash":false}]},{"StartTime":16150.0,"Objects":[{"StartTime":16150.0,"Position":223.0,"HyperDash":false},{"StartTime":16218.0,"Position":212.065735,"HyperDash":false},{"StartTime":16286.0,"Position":221.13147,"HyperDash":false},{"StartTime":16422.0,"Position":223.0,"HyperDash":false}]},{"StartTime":16513.0,"Objects":[{"StartTime":16513.0,"Position":231.0,"HyperDash":false}]},{"StartTime":16695.0,"Objects":[{"StartTime":16695.0,"Position":227.0,"HyperDash":false}]},{"StartTime":16785.0,"Objects":[{"StartTime":16785.0,"Position":233.0,"HyperDash":false}]},{"StartTime":16877.0,"Objects":[{"StartTime":16877.0,"Position":231.0,"HyperDash":false},{"StartTime":16949.0,"Position":232.82106,"HyperDash":false},{"StartTime":17058.0,"Position":233.064056,"HyperDash":false}]},{"StartTime":17241.0,"Objects":[{"StartTime":17241.0,"Position":236.0,"HyperDash":false},{"StartTime":17297.0,"Position":226.466782,"HyperDash":false},{"StartTime":17354.0,"Position":236.9419,"HyperDash":false},{"StartTime":17411.0,"Position":219.417,"HyperDash":false},{"StartTime":17468.0,"Position":237.89212,"HyperDash":false},{"StartTime":17563.0,"Position":221.100266,"HyperDash":false},{"StartTime":17695.0,"Position":236.0,"HyperDash":false}]},{"StartTime":18150.0,"Objects":[{"StartTime":18150.0,"Position":254.0,"HyperDash":false}]},{"StartTime":18331.0,"Objects":[{"StartTime":18331.0,"Position":242.0,"HyperDash":false}]},{"StartTime":18695.0,"Objects":[{"StartTime":18695.0,"Position":264.0,"HyperDash":false}]},{"StartTime":18877.0,"Objects":[{"StartTime":18877.0,"Position":250.0,"HyperDash":false}]},{"StartTime":19059.0,"Objects":[{"StartTime":19059.0,"Position":261.0,"HyperDash":false},{"StartTime":19149.0,"Position":273.538757,"HyperDash":false},{"StartTime":19240.0,"Position":265.1201,"HyperDash":false},{"StartTime":19313.0,"Position":252.953827,"HyperDash":false},{"StartTime":19422.0,"Position":261.0,"HyperDash":false}]},{"StartTime":19604.0,"Objects":[{"StartTime":19604.0,"Position":267.0,"HyperDash":false}]},{"StartTime":19786.0,"Objects":[{"StartTime":19786.0,"Position":271.0,"HyperDash":false}]},{"StartTime":19876.0,"Objects":[{"StartTime":19876.0,"Position":269.0,"HyperDash":false}]},{"StartTime":19968.0,"Objects":[{"StartTime":19968.0,"Position":271.0,"HyperDash":false},{"StartTime":20058.0,"Position":271.71347,"HyperDash":false},{"StartTime":20149.0,"Position":271.0,"HyperDash":false}]},{"StartTime":20331.0,"Objects":[{"StartTime":20331.0,"Position":278.0,"HyperDash":false}]},{"StartTime":20422.0,"Objects":[{"StartTime":20422.0,"Position":276.0,"HyperDash":false},{"StartTime":20494.0,"Position":256.991028,"HyperDash":false},{"StartTime":20603.0,"Position":276.034363,"HyperDash":false}]},{"StartTime":20695.0,"Objects":[{"StartTime":20695.0,"Position":281.0,"HyperDash":false},{"StartTime":20767.0,"Position":276.1846,"HyperDash":false},{"StartTime":20876.0,"Position":282.9045,"HyperDash":false}]},{"StartTime":21059.0,"Objects":[{"StartTime":21059.0,"Position":290.0,"HyperDash":false}]},{"StartTime":21240.0,"Objects":[{"StartTime":21240.0,"Position":291.0,"HyperDash":false},{"StartTime":21312.0,"Position":274.790222,"HyperDash":false},{"StartTime":21421.0,"Position":290.691162,"HyperDash":false}]},{"StartTime":21604.0,"Objects":[{"StartTime":21604.0,"Position":301.0,"HyperDash":false}]},{"StartTime":21786.0,"Objects":[{"StartTime":21786.0,"Position":296.0,"HyperDash":false},{"StartTime":21858.0,"Position":307.443481,"HyperDash":false},{"StartTime":21967.0,"Position":298.958923,"HyperDash":false}]},{"StartTime":22150.0,"Objects":[{"StartTime":22150.0,"Position":301.0,"HyperDash":false},{"StartTime":22222.0,"Position":295.69693,"HyperDash":false},{"StartTime":22331.0,"Position":296.97644,"HyperDash":false}]},{"StartTime":22513.0,"Objects":[{"StartTime":22513.0,"Position":315.0,"HyperDash":false}]},{"StartTime":22695.0,"Objects":[{"StartTime":22695.0,"Position":307.0,"HyperDash":false}]},{"StartTime":22786.0,"Objects":[{"StartTime":22786.0,"Position":315.0,"HyperDash":false}]},{"StartTime":22877.0,"Objects":[{"StartTime":22877.0,"Position":307.0,"HyperDash":false}]},{"StartTime":22968.0,"Objects":[{"StartTime":22968.0,"Position":319.0,"HyperDash":false}]},{"StartTime":23059.0,"Objects":[{"StartTime":23059.0,"Position":309.0,"HyperDash":false}]},{"StartTime":23150.0,"Objects":[{"StartTime":23150.0,"Position":321.0,"HyperDash":false}]},{"StartTime":23240.0,"Objects":[{"StartTime":23240.0,"Position":316.0,"HyperDash":false},{"StartTime":23330.0,"Position":305.998932,"HyperDash":false}]},{"StartTime":23421.0,"Objects":[{"StartTime":23421.0,"Position":332.0,"HyperDash":false}]},{"StartTime":23604.0,"Objects":[{"StartTime":23604.0,"Position":319.0,"HyperDash":false},{"StartTime":23694.0,"Position":319.977325,"HyperDash":false}]},{"StartTime":23786.0,"Objects":[{"StartTime":23786.0,"Position":323.0,"HyperDash":false},{"StartTime":23876.0,"Position":323.977325,"HyperDash":false}]},{"StartTime":23968.0,"Objects":[{"StartTime":23968.0,"Position":332.0,"HyperDash":false}]},{"StartTime":24150.0,"Objects":[{"StartTime":24150.0,"Position":328.0,"HyperDash":false},{"StartTime":24222.0,"Position":344.178925,"HyperDash":false},{"StartTime":24331.0,"Position":325.935944,"HyperDash":false}]},{"StartTime":24513.0,"Objects":[{"StartTime":24513.0,"Position":336.0,"HyperDash":false}]},{"StartTime":24695.0,"Objects":[{"StartTime":24695.0,"Position":340.0,"HyperDash":false}]},{"StartTime":24787.0,"Objects":[{"StartTime":24787.0,"Position":337.0,"HyperDash":false},{"StartTime":24877.0,"Position":336.002228,"HyperDash":false}]},{"StartTime":25059.0,"Objects":[{"StartTime":25059.0,"Position":341.0,"HyperDash":false},{"StartTime":25131.0,"Position":323.178925,"HyperDash":false},{"StartTime":25240.0,"Position":338.935944,"HyperDash":false}]},{"StartTime":25422.0,"Objects":[{"StartTime":25422.0,"Position":363.0,"HyperDash":false}]},{"StartTime":25604.0,"Objects":[{"StartTime":25604.0,"Position":351.0,"HyperDash":false},{"StartTime":25694.0,"Position":351.997772,"HyperDash":false},{"StartTime":25785.0,"Position":351.0,"HyperDash":false}]},{"StartTime":25968.0,"Objects":[{"StartTime":25968.0,"Position":356.0,"HyperDash":false}]},{"StartTime":26059.0,"Objects":[{"StartTime":26059.0,"Position":354.0,"HyperDash":false}]},{"StartTime":26149.0,"Objects":[{"StartTime":26149.0,"Position":356.0,"HyperDash":false},{"StartTime":26239.0,"Position":362.0235,"HyperDash":false},{"StartTime":26330.0,"Position":358.064056,"HyperDash":false},{"StartTime":26403.0,"Position":376.239563,"HyperDash":false},{"StartTime":26512.0,"Position":356.0,"HyperDash":false}]},{"StartTime":26877.0,"Objects":[{"StartTime":26877.0,"Position":374.0,"HyperDash":false}]},{"StartTime":27059.0,"Objects":[{"StartTime":27059.0,"Position":364.0,"HyperDash":false}]},{"StartTime":27149.0,"Objects":[{"StartTime":27149.0,"Position":376.0,"HyperDash":false}]},{"StartTime":27240.0,"Objects":[{"StartTime":27240.0,"Position":371.0,"HyperDash":false},{"StartTime":27330.0,"Position":371.0,"HyperDash":false},{"StartTime":27421.0,"Position":371.0,"HyperDash":false}]},{"StartTime":27604.0,"Objects":[{"StartTime":27604.0,"Position":381.0,"HyperDash":false}]},{"StartTime":27696.0,"Objects":[{"StartTime":27696.0,"Position":377.0,"HyperDash":false},{"StartTime":27786.0,"Position":377.71347,"HyperDash":false}]},{"StartTime":27968.0,"Objects":[{"StartTime":27968.0,"Position":381.0,"HyperDash":false},{"StartTime":28040.0,"Position":390.178925,"HyperDash":false},{"StartTime":28149.0,"Position":378.935944,"HyperDash":false}]},{"StartTime":28331.0,"Objects":[{"StartTime":28331.0,"Position":393.0,"HyperDash":false}]},{"StartTime":28513.0,"Objects":[{"StartTime":28513.0,"Position":385.0,"HyperDash":false}]},{"StartTime":28604.0,"Objects":[{"StartTime":28604.0,"Position":395.0,"HyperDash":false}]},{"StartTime":28695.0,"Objects":[{"StartTime":28695.0,"Position":391.0,"HyperDash":false},{"StartTime":28767.0,"Position":401.821075,"HyperDash":false},{"StartTime":28876.0,"Position":393.064056,"HyperDash":false}]},{"StartTime":29059.0,"Objects":[{"StartTime":29059.0,"Position":398.0,"HyperDash":false},{"StartTime":29115.0,"Position":401.513763,"HyperDash":false},{"StartTime":29172.0,"Position":404.01886,"HyperDash":false},{"StartTime":29229.0,"Position":412.523956,"HyperDash":false},{"StartTime":29286.0,"Position":396.029053,"HyperDash":false},{"StartTime":29381.0,"Position":381.8539,"HyperDash":false},{"StartTime":29513.0,"Position":398.0,"HyperDash":false}]},{"StartTime":29786.0,"Objects":[{"StartTime":29786.0,"Position":416.0,"HyperDash":false}]},{"StartTime":29967.0,"Objects":[{"StartTime":29967.0,"Position":400.0,"HyperDash":false}]},{"StartTime":30331.0,"Objects":[{"StartTime":30331.0,"Position":426.0,"HyperDash":false}]},{"StartTime":30513.0,"Objects":[{"StartTime":30513.0,"Position":408.0,"HyperDash":false}]},{"StartTime":30605.0,"Objects":[{"StartTime":30605.0,"Position":418.0,"HyperDash":false},{"StartTime":30695.0,"Position":418.71347,"HyperDash":false}]},{"StartTime":30877.0,"Objects":[{"StartTime":30877.0,"Position":421.0,"HyperDash":false},{"StartTime":30949.0,"Position":422.1499,"HyperDash":false},{"StartTime":31058.0,"Position":425.1201,"HyperDash":false}]},{"StartTime":31240.0,"Objects":[{"StartTime":31240.0,"Position":427.0,"HyperDash":false}]},{"StartTime":31422.0,"Objects":[{"StartTime":31422.0,"Position":431.0,"HyperDash":false}]},{"StartTime":31513.0,"Objects":[{"StartTime":31513.0,"Position":429.0,"HyperDash":false}]},{"StartTime":31603.0,"Objects":[{"StartTime":31603.0,"Position":433.0,"HyperDash":false}]},{"StartTime":31695.0,"Objects":[{"StartTime":31695.0,"Position":419.0,"HyperDash":false}]},{"StartTime":31786.0,"Objects":[{"StartTime":31786.0,"Position":434.0,"HyperDash":false},{"StartTime":31858.0,"Position":444.725983,"HyperDash":false},{"StartTime":31967.0,"Position":435.019226,"HyperDash":false}]},{"StartTime":32149.0,"Objects":[{"StartTime":32149.0,"Position":442.0,"HyperDash":false},{"StartTime":32221.0,"Position":443.390778,"HyperDash":false},{"StartTime":32330.0,"Position":440.069153,"HyperDash":false}]},{"StartTime":32695.0,"Objects":[{"StartTime":32695.0,"Position":452.0,"HyperDash":false}]},{"StartTime":32877.0,"Objects":[{"StartTime":32877.0,"Position":451.0,"HyperDash":false},{"StartTime":32949.0,"Position":434.066742,"HyperDash":false},{"StartTime":33058.0,"Position":450.7317,"HyperDash":false}]},{"StartTime":33240.0,"Objects":[{"StartTime":33240.0,"Position":461.0,"HyperDash":false}]},{"StartTime":33422.0,"Objects":[{"StartTime":33422.0,"Position":451.0,"HyperDash":false}]},{"StartTime":33513.0,"Objects":[{"StartTime":33513.0,"Position":457.0,"HyperDash":false},{"StartTime":33585.0,"Position":444.147736,"HyperDash":false},{"StartTime":33694.0,"Position":445.292816,"HyperDash":false}]},{"StartTime":33786.0,"Objects":[{"StartTime":33786.0,"Position":479.0,"HyperDash":false}]},{"StartTime":33877.0,"Objects":[{"StartTime":33877.0,"Position":462.0,"HyperDash":false},{"StartTime":33967.0,"Position":462.0,"HyperDash":false}]},{"StartTime":34149.0,"Objects":[{"StartTime":34149.0,"Position":470.0,"HyperDash":false}]},{"StartTime":34331.0,"Objects":[{"StartTime":34331.0,"Position":450.0,"HyperDash":false}]},{"StartTime":34422.0,"Objects":[{"StartTime":34422.0,"Position":450.0,"HyperDash":false}]},{"StartTime":34513.0,"Objects":[{"StartTime":34513.0,"Position":490.0,"HyperDash":false}]},{"StartTime":34604.0,"Objects":[{"StartTime":34604.0,"Position":472.0,"HyperDash":false}]},{"StartTime":34695.0,"Objects":[{"StartTime":34695.0,"Position":489.0,"HyperDash":false}]},{"StartTime":34877.0,"Objects":[{"StartTime":34877.0,"Position":476.0,"HyperDash":false},{"StartTime":34967.0,"Position":474.8665,"HyperDash":false}]},{"StartTime":35059.0,"Objects":[{"StartTime":35059.0,"Position":483.0,"HyperDash":false}]},{"StartTime":35240.0,"Objects":[{"StartTime":35240.0,"Position":479.0,"HyperDash":false},{"StartTime":35330.0,"Position":479.977325,"HyperDash":false}]},{"StartTime":35422.0,"Objects":[{"StartTime":35422.0,"Position":483.0,"HyperDash":false},{"StartTime":35512.0,"Position":483.977325,"HyperDash":false}]},{"StartTime":35604.0,"Objects":[{"StartTime":35604.0,"Position":287.0,"HyperDash":false},{"StartTime":35692.0,"Position":361.0,"HyperDash":false},{"StartTime":35780.0,"Position":479.0,"HyperDash":false},{"StartTime":35868.0,"Position":346.0,"HyperDash":false},{"StartTime":35956.0,"Position":266.0,"HyperDash":false},{"StartTime":36044.0,"Position":400.0,"HyperDash":false},{"StartTime":36132.0,"Position":202.0,"HyperDash":false},{"StartTime":36220.0,"Position":500.0,"HyperDash":false},{"StartTime":36308.0,"Position":80.0,"HyperDash":false},{"StartTime":36396.0,"Position":399.0,"HyperDash":false},{"StartTime":36484.0,"Position":455.0,"HyperDash":false},{"StartTime":36572.0,"Position":105.0,"HyperDash":false},{"StartTime":36660.0,"Position":100.0,"HyperDash":false},{"StartTime":36748.0,"Position":195.0,"HyperDash":false},{"StartTime":36836.0,"Position":106.0,"HyperDash":false},{"StartTime":36924.0,"Position":305.0,"HyperDash":false},{"StartTime":37013.0,"Position":225.0,"HyperDash":false}]},{"StartTime":37059.0,"Objects":[{"StartTime":37059.0,"Position":79.0,"HyperDash":false},{"StartTime":37124.0,"Position":38.0,"HyperDash":false},{"StartTime":37189.0,"Position":99.0,"HyperDash":false},{"StartTime":37254.0,"Position":79.0,"HyperDash":false},{"StartTime":37320.0,"Position":169.0,"HyperDash":false},{"StartTime":37385.0,"Position":238.0,"HyperDash":false},{"StartTime":37450.0,"Position":511.0,"HyperDash":false},{"StartTime":37516.0,"Position":58.0,"HyperDash":false},{"StartTime":37581.0,"Position":368.0,"HyperDash":false},{"StartTime":37646.0,"Position":52.0,"HyperDash":false},{"StartTime":37712.0,"Position":327.0,"HyperDash":false},{"StartTime":37777.0,"Position":226.0,"HyperDash":false},{"StartTime":37842.0,"Position":110.0,"HyperDash":false},{"StartTime":37908.0,"Position":3.0,"HyperDash":false},{"StartTime":37973.0,"Position":26.0,"HyperDash":false},{"StartTime":38038.0,"Position":173.0,"HyperDash":false},{"StartTime":38104.0,"Position":18.0,"HyperDash":false},{"StartTime":38169.0,"Position":310.0,"HyperDash":false},{"StartTime":38234.0,"Position":394.0,"HyperDash":false},{"StartTime":38299.0,"Position":406.0,"HyperDash":false},{"StartTime":38365.0,"Position":262.0,"HyperDash":false},{"StartTime":38430.0,"Position":278.0,"HyperDash":false},{"StartTime":38495.0,"Position":171.0,"HyperDash":false},{"StartTime":38561.0,"Position":22.0,"HyperDash":false},{"StartTime":38626.0,"Position":187.0,"HyperDash":false},{"StartTime":38691.0,"Position":124.0,"HyperDash":false},{"StartTime":38757.0,"Position":454.0,"HyperDash":false},{"StartTime":38822.0,"Position":16.0,"HyperDash":false},{"StartTime":38887.0,"Position":61.0,"HyperDash":false},{"StartTime":38953.0,"Position":161.0,"HyperDash":false},{"StartTime":39018.0,"Position":243.0,"HyperDash":false},{"StartTime":39083.0,"Position":375.0,"HyperDash":false},{"StartTime":39149.0,"Position":247.0,"HyperDash":false}]},{"StartTime":40695.0,"Objects":[{"StartTime":40695.0,"Position":496.0,"HyperDash":false},{"StartTime":40767.0,"Position":489.6517,"HyperDash":false},{"StartTime":40876.0,"Position":495.903229,"HyperDash":false}]},{"StartTime":41059.0,"Objects":[{"StartTime":41059.0,"Position":510.0,"HyperDash":false}]},{"StartTime":41150.0,"Objects":[{"StartTime":41150.0,"Position":498.0,"HyperDash":false}]},{"StartTime":41240.0,"Objects":[{"StartTime":41240.0,"Position":505.0,"HyperDash":false},{"StartTime":41285.0,"Position":505.934265,"HyperDash":false},{"StartTime":41330.0,"Position":505.0,"HyperDash":false},{"StartTime":41376.0,"Position":505.934265,"HyperDash":false}]}]} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/2768615.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/2768615.osu new file mode 100644 index 0000000000..19439172cd --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/2768615.osu @@ -0,0 +1,200 @@ +osu file format v14 + +[General] +StackLeniency: 0.4 +Mode: 0 + +[Difficulty] +HPDrainRate:4.5 +CircleSize:9 +OverallDifficulty:8 +ApproachRate:8 +SliderMultiplier:1.2 +SliderTickRate:1 + +[Events] +//Background and Video events +//Break Periods +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Layer 4 (Overlay) +//Storyboard Sound Samples + +[TimingPoints] +514,727.272727272727,4,2,1,50,1,0 +9241,-83.3333333333333,4,2,1,50,0,0 +9968,-100,4,2,1,50,0,0 +10241,-100,4,2,99,5,0,0 +10332,-100,4,2,1,50,0,0 +10604,-100,4,2,99,5,0,0 +10695,-100,4,2,1,50,0,0 +11423,-66.6666666666667,4,2,1,50,0,0 +12150,-100,4,2,1,50,0,0 +13150,-100,4,2,99,5,0,0 +13241,-100,4,2,1,50,0,0 +16059,-100,4,2,99,5,0,0 +16150,-100,4,2,1,50,0,0 +17241,909.090909090909,4,2,1,50,1,0 +17241,-83.3333333333333,4,2,1,50,0,0 +18150,727.272727272727,4,2,1,50,1,0 +20604,-100,4,2,99,5,0,0 +20695,-100,4,2,1,50,0,0 +21059,-83.3333333333333,4,2,1,50,0,0 +21786,-100,4,2,1,50,0,0 +23240,-66.6666666666667,4,2,1,50,0,0 +23968,-100,4,2,1,50,0,0 +32695,-83.3333333333333,4,2,1,50,0,0 +33422,-100,4,2,1,50,0,0 +33695,-100,4,2,99,5,0,0 +33786,-100,4,2,1,50,0,0 +34877,-66.6666666666667,4,2,1,50,0,0 +37013,-100,4,2,1,45,0,0 +39149,-100,4,2,1,40,0,0 +40695,-66.6666666666667,4,2,1,35,0,0 + +[HitObjects] +6,0,514,6,0,L|8:29,1,30,2|0,3:2|0:0,0:0:0:0: +14,66,877,1,0,0:0:0:0: +14,66,1059,2,0,L|16:36,1,30,0|0,0:0|1:0,0:0:0:0: +21,31,1604,6,0,L|26:90,1,60,2|0,0:0|3:0,0:0:0:0: +27,186,2332,1,0,3:0:0:0: +27,186,2513,2,0,L|34:96,1,90,2|0,0:0|0:0,0:0:0:0: +46,269,3423,38,0,L|48:298,1,30,2|0,3:2|0:0,0:0:0:0: +54,335,3786,1,0,0:0:0:0: +54,335,3968,2,0,L|56:305,1,30,0|0,0:0|1:0,0:0:0:0: +61,300,4513,6,0,L|63:271,1,30,2|0,0:0|0:0,0:0:0:0: +65,200,4877,2,0,L|66:186,2,15,0|0|0,3:2|0:0|0:0,0:0:0:0: +68,265,5241,2,0,L|70:236,1,30,2|0,3:2|0:0,0:0:0:0: +77,335,5604,6,0,L|76:373,2,37.5,0|0|0,1:0|3:0|3:0,0:0:0:0: +86,152,6332,21,2,3:2:0:0: +88,199,6513,1,2,0:0:0:0: +94,157,6877,5,2,0:0:0:0: +96,204,7059,2,0,P|100:218|99:233,1,30,2|0,1:2|0:0,0:0:0:0: +101,161,7423,2,0,P|96:146|97:131,1,30,2|0,0:2|0:1,0:0:0:0: +106,85,7786,37,0,3:3:0:0: +105,49,7968,1,0,0:3:0:0: +111,82,8150,1,2,3:3:0:0: +110,45,8332,2,0,P|110:30|106:16,1,30,2|0,0:3|0:0,0:0:0:0: +110,87,8695,2,0,P|110:102|114:117,1,30,2|0,0:3|0:0,0:0:0:0: +126,290,9241,5,4,3:3:0:0: +131,232,9423,2,0,B|134:220|124:211|124:211|129:218|129:223,1,35.9999989013672,8|0,0:3|0:0,0:0:0:0: +136,297,9786,37,4,0:3:0:0: +136,234,9968,2,0,P|138:212|133:190,1,45,2|0,1:2|0:0,0:0:0:0: +139,191,10332,2,0,P|144:212|142:235,1,45,2|0,0:0|0:0,0:0:0:0: +146,264,10695,5,2,3:1:0:0: +148,306,10877,1,2,0:1:0:0: +151,267,11059,1,2,3:1:0:0: +153,309,11241,1,2,0:1:0:0: +156,351,11423,6,0,B|158:362|158:362|159:354|159:350,1,22.5000008583069,2|0,1:0|0:0,0:0:0:0: +158,311,11604,1,2,0:3:0:0: +160,289,11695,1,2,0:3:0:0: +161,267,11786,2,0,L|162:244,1,22.5000008583069,2|0,0:3|0:0,0:0:0:0: +165,268,11968,2,0,L|166:245,1,22.5000008583069,2|0,0:3|0:0,0:0:0:0: +166,187,12150,22,0,L|168:158,1,30,2|0,3:2|0:0,0:0:0:0: +174,121,12513,1,2,0:0:0:0: +174,121,12695,2,0,L|178:195,1,75,2|0,0:0|0:0,0:0:0:0: +183,199,13241,2,0,L|181:170,1,30,2|0,0:0|0:0,0:0:0:0: +186,72,13604,5,0,3:0:0:0: +188,35,13786,1,0,3:0:0:0: +191,0,13968,1,0,3:2:0:0: +191,0,14150,2,0,L|195:89,1,90,2|2,0:0|0:0,0:0:0:0: +206,181,15059,37,2,3:2:0:0: +207,167,15150,1,2,0:0:0:0: +208,152,15240,1,2,0:0:0:0: +214,115,15422,1,2,0:0:0:0: +214,115,15604,2,0,L|218:189,1,75,2|0,0:0|0:0,0:0:0:0: +223,193,16150,6,0,L|221:169,2,22.5,2|2|2,0:0|0:0|0:0,0:0:0:0: +226,228,16513,1,2,3:1:0:0: +229,263,16695,1,2,0:1:0:0: +230,277,16785,1,2,0:1:0:0: +231,292,16877,2,0,L|233:321,1,30,2|0,3:1|0:0,0:0:0:0: +236,218,17241,6,0,L|238:180,2,35.9999989013672,0|0|0,1:0|3:0|3:0,0:0:0:0: +246,362,18150,21,2,3:2:0:0: +248,315,18331,1,2,0:0:0:0: +253,358,18695,5,2,0:0:0:0: +257,310,18877,1,2,1:2:0:0: +261,354,19059,2,0,P|266:369|265:384,2,30,2|0|0,0:0|0:1|0:0,0:0:0:0: +266,305,19604,37,0,3:0:0:0: +269,254,19786,1,2,0:3:0:0: +270,240,19876,1,2,0:3:0:0: +271,225,19968,2,0,L|272:204,2,15,2|2|2,3:1|0:1|0:1,0:0:0:0: +275,301,20331,1,2,1:0:0:0: +276,316,20422,2,0,B|277:328|277:328|275:332|275:332|276:345,1,30,2|0,0:1|0:0,0:0:0:0: +281,349,20695,2,0,B|278:335|287:332|282:316,1,30,2|0,0:1|0:0,0:0:0:0: +286,142,21059,5,4,3:0:0:0: +291,199,21240,2,0,B|291:209|283:214|283:214|281:204|291:199,1,35.9999989013672,8|0,0:3|0:0,0:0:0:0: +296,139,21604,37,4,0:3:0:0: +296,197,21786,2,0,P|300:211|299:226,1,30,2|0,1:1|0:0,0:0:0:0: +301,291,22150,2,0,P|297:276|297:261,1,30,2|0,0:1|0:0,0:0:0:0: +306,136,22513,5,2,3:3:0:0: +311,97,22695,1,2,0:3:0:0: +311,97,22786,1,2,0:3:0:0: +311,97,22877,1,2,3:1:0:0: +313,106,22968,1,2,0:1:0:0: +314,115,23059,1,2,0:1:0:0: +315,124,23150,1,2,0:1:0:0: +316,133,23240,6,0,B|308:125|308:125|306:136,1,22.5000008583069,2|0,1:1|0:0,0:0:0:0: +319,168,23421,5,2,0:1:0:0: +319,201,23604,38,0,L|320:224,1,22.5000008583069,2|0,0:3|0:0,0:0:0:0: +323,200,23786,2,0,L|324:223,1,22.5000008583069,2|0,0:3|0:0,0:0:0:0: +328,297,23968,21,2,3:2:0:0: +328,297,24150,2,0,L|326:268,1,30,2|0,0:0|0:0,0:0:0:0: +331,373,24513,1,2,0:0:0:0: +338,344,24695,1,2,1:0:0:0: +337,329,24787,2,0,L|336:314,1,15,2|0,0:1|0:0,0:0:0:0: +341,239,25059,2,0,L|339:210,1,30,2|0,0:1|0:0,0:0:0:0: +351,122,25422,5,2,3:2:0:0: +351,122,25604,2,0,L|352:107,2,15,2|2|2,0:0|0:0|3:2,0:0:0:0: +354,195,25968,1,2,0:3:0:0: +355,180,26059,1,2,0:3:0:0: +356,165,26149,2,0,L|358:136,2,30,2|0|0,1:3|0:0|0:0,0:0:0:0: +366,79,26877,37,2,3:2:0:0: +369,44,27059,1,2,0:1:0:0: +370,30,27149,1,2,0:1:0:0: +371,15,27240,2,0,L|371:0,2,15,2|2|2,0:1|0:1|0:1,0:0:0:0: +376,101,27604,1,2,1:1:0:0: +377,86,27696,2,0,L|378:65,1,15,2|0,0:1|0:0,0:0:0:0: +381,138,27968,2,0,L|379:167,1,30,2|0,0:1|0:0,0:0:0:0: +386,277,28331,5,2,3:3:0:0: +389,242,28513,1,2,0:3:0:0: +390,227,28604,1,2,0:3:0:0: +391,212,28695,2,0,L|393:183,1,30,2|2,0:3|0:3,0:0:0:0: +398,293,29059,6,0,L|396:331,2,37.5,2|2|2,1:0|3:1|3:1,0:0:0:0: +406,83,29786,21,2,3:2:0:0: +408,130,29967,1,2,0:0:0:0: +413,87,30331,5,2,0:0:0:0: +417,135,30513,1,2,1:0:0:0: +418,150,30605,2,0,L|419:171,1,15,2|0,0:1|0:0,0:0:0:0: +421,91,30877,2,0,P|426:76|425:61,1,30,2|0,0:1|0:0,0:0:0:0: +426,140,31240,37,2,3:2:0:0: +429,193,31422,1,2,0:1:0:0: +430,208,31513,1,2,0:1:0:0: +431,223,31603,1,2,0:1:0:0: +433,237,31695,1,2,0:1:0:0: +434,252,31786,2,0,P|436:237|435:222,1,30,2|0,0:2|1:0,0:0:0:0: +442,296,32149,2,0,P|439:310|440:325,1,30,2|0,0:0|0:0,0:0:0:0: +446,120,32695,5,4,3:0:0:0: +451,63,32877,2,0,B|448:54|448:54|441:49|441:49|443:57|443:57|451:63,1,35.9999989013672,8|0,0:3|0:0,0:0:0:0: +456,123,33240,37,4,0:3:0:0: +456,65,33422,1,2,1:0:0:0: +457,50,33513,2,0,P|451:31|443:20,1,30,2|0,0:1|0:0,0:0:0:0: +461,0,33786,1,2,0:1:0:0: +462,15,33877,2,0,L|462:29,1,15,2|0,0:1|0:0,0:0:0:0: +466,127,34149,5,2,3:3:0:0: +470,180,34331,1,2,0:3:0:0: +470,180,34422,1,2,0:3:0:0: +470,180,34513,1,2,3:1:0:0: +471,171,34604,1,2,0:1:0:0: +472,162,34695,1,2,0:1:0:0: +476,130,34877,6,0,B|486:125|486:125|481:127|475:126,1,22.5000008583069,2|0,1:1|0:0,0:0:0:0: +479,95,35059,5,2,0:1:0:0: +479,62,35240,38,0,L|480:39,1,22.5000008583069,2|0,0:3|0:0,0:0:0:0: +483,63,35422,2,0,L|484:40,1,22.5000008583069,2|0,0:3|0:0,0:0:0:0: +256,192,35604,12,4,37013,0:3:0:0: +256,192,37059,12,4,39149,0:3:0:0: +496,360,40695,6,0,B|498:344|498:344|495:329|495:329|496:314,1,45.0000017166138,2|0,3:1|0:0,0:0:0:0: +503,270,41059,1,2,0:1:0:0: +504,262,41150,1,2,0:1:0:0: +505,254,41240,2,0,L|506:242,3,11.2500004291535,2|0|0|0,0:1|0:0|0:0|0:0,0:0:0:0: diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/2781126-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/2781126-expected-conversion.json new file mode 100644 index 0000000000..e03f6ae672 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/2781126-expected-conversion.json @@ -0,0 +1 @@ +{"Mappings":[{"StartTime":313.0,"Objects":[{"StartTime":313.0,"Position":65.0,"HyperDash":false},{"StartTime":366.0,"Position":482.0,"HyperDash":false},{"StartTime":420.0,"Position":164.0,"HyperDash":false},{"StartTime":474.0,"Position":315.0,"HyperDash":false},{"StartTime":528.0,"Position":145.0,"HyperDash":false},{"StartTime":582.0,"Position":159.0,"HyperDash":false},{"StartTime":636.0,"Position":310.0,"HyperDash":false},{"StartTime":690.0,"Position":441.0,"HyperDash":false},{"StartTime":744.0,"Position":428.0,"HyperDash":false},{"StartTime":797.0,"Position":243.0,"HyperDash":false},{"StartTime":851.0,"Position":422.0,"HyperDash":false},{"StartTime":905.0,"Position":481.0,"HyperDash":false},{"StartTime":959.0,"Position":104.0,"HyperDash":false},{"StartTime":1013.0,"Position":473.0,"HyperDash":false},{"StartTime":1067.0,"Position":135.0,"HyperDash":false},{"StartTime":1121.0,"Position":360.0,"HyperDash":false},{"StartTime":1175.0,"Position":123.0,"HyperDash":false}]},{"StartTime":1348.0,"Objects":[{"StartTime":1348.0,"Position":224.0,"HyperDash":false}]},{"StartTime":1434.0,"Objects":[{"StartTime":1434.0,"Position":177.0,"HyperDash":false}]},{"StartTime":1520.0,"Objects":[{"StartTime":1520.0,"Position":179.0,"HyperDash":false}]},{"StartTime":1606.0,"Objects":[{"StartTime":1606.0,"Position":227.0,"HyperDash":false}]},{"StartTime":1693.0,"Objects":[{"StartTime":1693.0,"Position":292.0,"HyperDash":false},{"StartTime":1779.0,"Position":295.206116,"HyperDash":true}]},{"StartTime":1865.0,"Objects":[{"StartTime":1865.0,"Position":116.0,"HyperDash":false},{"StartTime":1951.0,"Position":99.0,"HyperDash":false},{"StartTime":2037.0,"Position":117.970421,"HyperDash":false},{"StartTime":2105.0,"Position":172.025635,"HyperDash":false},{"StartTime":2209.0,"Position":206.639481,"HyperDash":false}]},{"StartTime":2296.0,"Objects":[{"StartTime":2296.0,"Position":116.0,"HyperDash":false}]},{"StartTime":2382.0,"Objects":[{"StartTime":2382.0,"Position":26.0,"HyperDash":false},{"StartTime":2450.0,"Position":34.6324959,"HyperDash":false},{"StartTime":2554.0,"Position":22.54102,"HyperDash":true}]},{"StartTime":2727.0,"Objects":[{"StartTime":2727.0,"Position":292.0,"HyperDash":false},{"StartTime":2795.0,"Position":337.5814,"HyperDash":false},{"StartTime":2899.0,"Position":382.0,"HyperDash":false}]},{"StartTime":2986.0,"Objects":[{"StartTime":2986.0,"Position":328.0,"HyperDash":false}]},{"StartTime":3072.0,"Objects":[{"StartTime":3072.0,"Position":276.0,"HyperDash":false}]},{"StartTime":3244.0,"Objects":[{"StartTime":3244.0,"Position":448.0,"HyperDash":false}]},{"StartTime":3417.0,"Objects":[{"StartTime":3417.0,"Position":268.0,"HyperDash":false},{"StartTime":3485.0,"Position":236.41861,"HyperDash":false},{"StartTime":3589.0,"Position":178.0,"HyperDash":false}]},{"StartTime":3675.0,"Objects":[{"StartTime":3675.0,"Position":244.0,"HyperDash":false}]},{"StartTime":3762.0,"Objects":[{"StartTime":3762.0,"Position":178.0,"HyperDash":false},{"StartTime":3830.0,"Position":149.41861,"HyperDash":false},{"StartTime":3934.0,"Position":88.0,"HyperDash":true}]},{"StartTime":4106.0,"Objects":[{"StartTime":4106.0,"Position":444.0,"HyperDash":false},{"StartTime":4192.0,"Position":447.737061,"HyperDash":false}]},{"StartTime":4279.0,"Objects":[{"StartTime":4279.0,"Position":376.0,"HyperDash":false},{"StartTime":4365.0,"Position":372.262939,"HyperDash":false}]},{"StartTime":4451.0,"Objects":[{"StartTime":4451.0,"Position":300.0,"HyperDash":false}]},{"StartTime":4624.0,"Objects":[{"StartTime":4624.0,"Position":472.0,"HyperDash":false},{"StartTime":4710.0,"Position":475.451355,"HyperDash":true}]},{"StartTime":4796.0,"Objects":[{"StartTime":4796.0,"Position":296.0,"HyperDash":false},{"StartTime":4864.0,"Position":285.639862,"HyperDash":false},{"StartTime":4968.0,"Position":274.157928,"HyperDash":false}]},{"StartTime":5055.0,"Objects":[{"StartTime":5055.0,"Position":366.0,"HyperDash":false}]},{"StartTime":5141.0,"Objects":[{"StartTime":5141.0,"Position":456.0,"HyperDash":false},{"StartTime":5209.0,"Position":405.4186,"HyperDash":false},{"StartTime":5313.0,"Position":366.0,"HyperDash":true}]},{"StartTime":5486.0,"Objects":[{"StartTime":5486.0,"Position":112.0,"HyperDash":false},{"StartTime":5554.0,"Position":144.58139,"HyperDash":false},{"StartTime":5658.0,"Position":202.0,"HyperDash":false}]},{"StartTime":5744.0,"Objects":[{"StartTime":5744.0,"Position":268.0,"HyperDash":false}]},{"StartTime":5831.0,"Objects":[{"StartTime":5831.0,"Position":202.0,"HyperDash":false}]},{"StartTime":6003.0,"Objects":[{"StartTime":6003.0,"Position":360.0,"HyperDash":false}]},{"StartTime":6175.0,"Objects":[{"StartTime":6175.0,"Position":192.0,"HyperDash":false},{"StartTime":6243.0,"Position":146.41861,"HyperDash":false},{"StartTime":6347.0,"Position":102.0,"HyperDash":false}]},{"StartTime":6434.0,"Objects":[{"StartTime":6434.0,"Position":172.0,"HyperDash":false}]},{"StartTime":6520.0,"Objects":[{"StartTime":6520.0,"Position":102.0,"HyperDash":false},{"StartTime":6588.0,"Position":71.4186,"HyperDash":false},{"StartTime":6692.0,"Position":12.0,"HyperDash":true}]},{"StartTime":6865.0,"Objects":[{"StartTime":6865.0,"Position":288.0,"HyperDash":false}]},{"StartTime":6951.0,"Objects":[{"StartTime":6951.0,"Position":335.0,"HyperDash":false}]},{"StartTime":7037.0,"Objects":[{"StartTime":7037.0,"Position":333.0,"HyperDash":false}]},{"StartTime":7124.0,"Objects":[{"StartTime":7124.0,"Position":285.0,"HyperDash":false}]},{"StartTime":7210.0,"Objects":[{"StartTime":7210.0,"Position":220.0,"HyperDash":false},{"StartTime":7296.0,"Position":216.793884,"HyperDash":false}]},{"StartTime":7382.0,"Objects":[{"StartTime":7382.0,"Position":320.0,"HyperDash":false}]},{"StartTime":7555.0,"Objects":[{"StartTime":7555.0,"Position":204.0,"HyperDash":true}]},{"StartTime":7727.0,"Objects":[{"StartTime":7727.0,"Position":456.0,"HyperDash":false}]},{"StartTime":7813.0,"Objects":[{"StartTime":7813.0,"Position":460.0,"HyperDash":false}]},{"StartTime":7900.0,"Objects":[{"StartTime":7900.0,"Position":464.0,"HyperDash":false},{"StartTime":7968.0,"Position":437.4186,"HyperDash":false},{"StartTime":8072.0,"Position":374.0,"HyperDash":true}]},{"StartTime":8244.0,"Objects":[{"StartTime":8244.0,"Position":120.0,"HyperDash":false},{"StartTime":8312.0,"Position":159.58139,"HyperDash":false},{"StartTime":8416.0,"Position":210.0,"HyperDash":false}]},{"StartTime":8503.0,"Objects":[{"StartTime":8503.0,"Position":280.0,"HyperDash":false}]},{"StartTime":8589.0,"Objects":[{"StartTime":8589.0,"Position":348.0,"HyperDash":false}]},{"StartTime":8762.0,"Objects":[{"StartTime":8762.0,"Position":176.0,"HyperDash":false}]},{"StartTime":8934.0,"Objects":[{"StartTime":8934.0,"Position":354.0,"HyperDash":false},{"StartTime":9002.0,"Position":379.5814,"HyperDash":false},{"StartTime":9106.0,"Position":444.0,"HyperDash":false}]},{"StartTime":9193.0,"Objects":[{"StartTime":9193.0,"Position":374.0,"HyperDash":false}]},{"StartTime":9279.0,"Objects":[{"StartTime":9279.0,"Position":306.0,"HyperDash":false},{"StartTime":9347.0,"Position":331.5814,"HyperDash":false},{"StartTime":9451.0,"Position":396.0,"HyperDash":true}]},{"StartTime":9624.0,"Objects":[{"StartTime":9624.0,"Position":148.0,"HyperDash":false},{"StartTime":9710.0,"Position":104.34359,"HyperDash":false}]},{"StartTime":9796.0,"Objects":[{"StartTime":9796.0,"Position":176.0,"HyperDash":false},{"StartTime":9882.0,"Position":219.6564,"HyperDash":false}]},{"StartTime":9969.0,"Objects":[{"StartTime":9969.0,"Position":148.0,"HyperDash":false}]},{"StartTime":10141.0,"Objects":[{"StartTime":10141.0,"Position":308.0,"HyperDash":false}]},{"StartTime":10313.0,"Objects":[{"StartTime":10313.0,"Position":140.0,"HyperDash":true}]},{"StartTime":10486.0,"Objects":[{"StartTime":10486.0,"Position":396.0,"HyperDash":false},{"StartTime":10572.0,"Position":441.0,"HyperDash":false},{"StartTime":10658.0,"Position":396.0,"HyperDash":false}]},{"StartTime":10831.0,"Objects":[{"StartTime":10831.0,"Position":228.0,"HyperDash":true}]},{"StartTime":11003.0,"Objects":[{"StartTime":11003.0,"Position":460.0,"HyperDash":false},{"StartTime":11089.0,"Position":482.326263,"HyperDash":false}]},{"StartTime":11175.0,"Objects":[{"StartTime":11175.0,"Position":392.0,"HyperDash":false},{"StartTime":11261.0,"Position":414.326263,"HyperDash":false}]},{"StartTime":11348.0,"Objects":[{"StartTime":11348.0,"Position":324.0,"HyperDash":false},{"StartTime":11434.0,"Position":345.614166,"HyperDash":false}]},{"StartTime":11520.0,"Objects":[{"StartTime":11520.0,"Position":260.0,"HyperDash":false},{"StartTime":11606.0,"Position":282.326263,"HyperDash":false}]},{"StartTime":11693.0,"Objects":[{"StartTime":11693.0,"Position":384.0,"HyperDash":false}]},{"StartTime":11865.0,"Objects":[{"StartTime":11865.0,"Position":220.0,"HyperDash":false},{"StartTime":11951.0,"Position":175.0,"HyperDash":true}]},{"StartTime":12037.0,"Objects":[{"StartTime":12037.0,"Position":400.0,"HyperDash":false},{"StartTime":12123.0,"Position":463.25,"HyperDash":false},{"StartTime":12209.0,"Position":488.0,"HyperDash":false},{"StartTime":12295.0,"Position":488.0,"HyperDash":true}]},{"StartTime":12382.0,"Objects":[{"StartTime":12382.0,"Position":284.0,"HyperDash":false},{"StartTime":12450.0,"Position":255.41861,"HyperDash":false},{"StartTime":12554.0,"Position":194.0,"HyperDash":false}]},{"StartTime":12641.0,"Objects":[{"StartTime":12641.0,"Position":264.0,"HyperDash":true}]},{"StartTime":12727.0,"Objects":[{"StartTime":12727.0,"Position":436.0,"HyperDash":false}]},{"StartTime":12900.0,"Objects":[{"StartTime":12900.0,"Position":328.0,"HyperDash":false},{"StartTime":12986.0,"Position":324.793884,"HyperDash":false}]},{"StartTime":13072.0,"Objects":[{"StartTime":13072.0,"Position":424.0,"HyperDash":false},{"StartTime":13140.0,"Position":437.3675,"HyperDash":false},{"StartTime":13244.0,"Position":427.458984,"HyperDash":false}]},{"StartTime":13331.0,"Objects":[{"StartTime":13331.0,"Position":360.0,"HyperDash":true}]},{"StartTime":13417.0,"Objects":[{"StartTime":13417.0,"Position":208.0,"HyperDash":false},{"StartTime":13485.0,"Position":174.41861,"HyperDash":false},{"StartTime":13589.0,"Position":118.0,"HyperDash":false}]},{"StartTime":13762.0,"Objects":[{"StartTime":13762.0,"Position":292.0,"HyperDash":false},{"StartTime":13830.0,"Position":274.545563,"HyperDash":false},{"StartTime":13934.0,"Position":295.909363,"HyperDash":false}]},{"StartTime":14020.0,"Objects":[{"StartTime":14020.0,"Position":228.0,"HyperDash":true}]},{"StartTime":14106.0,"Objects":[{"StartTime":14106.0,"Position":408.0,"HyperDash":false},{"StartTime":14174.0,"Position":426.5814,"HyperDash":false},{"StartTime":14278.0,"Position":498.0,"HyperDash":true}]},{"StartTime":14451.0,"Objects":[{"StartTime":14451.0,"Position":228.0,"HyperDash":false},{"StartTime":14519.0,"Position":266.5814,"HyperDash":false},{"StartTime":14623.0,"Position":318.0,"HyperDash":true}]},{"StartTime":14796.0,"Objects":[{"StartTime":14796.0,"Position":48.0,"HyperDash":false},{"StartTime":14864.0,"Position":91.5814,"HyperDash":false},{"StartTime":14968.0,"Position":138.0,"HyperDash":true}]},{"StartTime":15141.0,"Objects":[{"StartTime":15141.0,"Position":392.0,"HyperDash":false},{"StartTime":15227.0,"Position":394.993347,"HyperDash":false}]},{"StartTime":15313.0,"Objects":[{"StartTime":15313.0,"Position":320.0,"HyperDash":false},{"StartTime":15399.0,"Position":317.006653,"HyperDash":true}]},{"StartTime":15486.0,"Objects":[{"StartTime":15486.0,"Position":488.0,"HyperDash":false}]},{"StartTime":15658.0,"Objects":[{"StartTime":15658.0,"Position":388.0,"HyperDash":false},{"StartTime":15744.0,"Position":343.0,"HyperDash":false}]},{"StartTime":15831.0,"Objects":[{"StartTime":15831.0,"Position":240.0,"HyperDash":false},{"StartTime":15899.0,"Position":231.454437,"HyperDash":false},{"StartTime":16003.0,"Position":236.090652,"HyperDash":false}]},{"StartTime":16089.0,"Objects":[{"StartTime":16089.0,"Position":304.0,"HyperDash":true}]},{"StartTime":16175.0,"Objects":[{"StartTime":16175.0,"Position":132.0,"HyperDash":false},{"StartTime":16243.0,"Position":99.4186,"HyperDash":false},{"StartTime":16347.0,"Position":42.0,"HyperDash":true}]},{"StartTime":16520.0,"Objects":[{"StartTime":16520.0,"Position":312.0,"HyperDash":false},{"StartTime":16588.0,"Position":274.4186,"HyperDash":false},{"StartTime":16692.0,"Position":222.0,"HyperDash":false}]},{"StartTime":16779.0,"Objects":[{"StartTime":16779.0,"Position":152.0,"HyperDash":true}]},{"StartTime":16865.0,"Objects":[{"StartTime":16865.0,"Position":328.0,"HyperDash":false},{"StartTime":16933.0,"Position":309.4186,"HyperDash":false},{"StartTime":17037.0,"Position":238.0,"HyperDash":false}]},{"StartTime":17210.0,"Objects":[{"StartTime":17210.0,"Position":328.0,"HyperDash":false}]},{"StartTime":17382.0,"Objects":[{"StartTime":17382.0,"Position":164.0,"HyperDash":false},{"StartTime":17468.0,"Position":160.54866,"HyperDash":true}]},{"StartTime":17555.0,"Objects":[{"StartTime":17555.0,"Position":336.0,"HyperDash":false},{"StartTime":17623.0,"Position":354.5814,"HyperDash":false},{"StartTime":17727.0,"Position":426.0,"HyperDash":true}]},{"StartTime":17900.0,"Objects":[{"StartTime":17900.0,"Position":152.0,"HyperDash":false}]},{"StartTime":17986.0,"Objects":[{"StartTime":17986.0,"Position":155.0,"HyperDash":false}]},{"StartTime":18072.0,"Objects":[{"StartTime":18072.0,"Position":192.0,"HyperDash":false}]},{"StartTime":18158.0,"Objects":[{"StartTime":18158.0,"Position":252.0,"HyperDash":true}]},{"StartTime":18244.0,"Objects":[{"StartTime":18244.0,"Position":404.0,"HyperDash":false},{"StartTime":18312.0,"Position":416.481262,"HyperDash":false},{"StartTime":18416.0,"Position":407.746735,"HyperDash":true}]},{"StartTime":18589.0,"Objects":[{"StartTime":18589.0,"Position":156.0,"HyperDash":false},{"StartTime":18657.0,"Position":101.4186,"HyperDash":false},{"StartTime":18761.0,"Position":66.0,"HyperDash":false}]},{"StartTime":18848.0,"Objects":[{"StartTime":18848.0,"Position":136.0,"HyperDash":true}]},{"StartTime":18934.0,"Objects":[{"StartTime":18934.0,"Position":304.0,"HyperDash":false},{"StartTime":19002.0,"Position":346.5814,"HyperDash":false},{"StartTime":19106.0,"Position":394.0,"HyperDash":true}]},{"StartTime":19279.0,"Objects":[{"StartTime":19279.0,"Position":120.0,"HyperDash":false},{"StartTime":19347.0,"Position":113.063881,"HyperDash":false},{"StartTime":19451.0,"Position":111.495346,"HyperDash":false}]},{"StartTime":19537.0,"Objects":[{"StartTime":19537.0,"Position":180.0,"HyperDash":true}]},{"StartTime":19624.0,"Objects":[{"StartTime":19624.0,"Position":360.0,"HyperDash":false},{"StartTime":19692.0,"Position":315.4186,"HyperDash":false},{"StartTime":19796.0,"Position":270.0,"HyperDash":true}]},{"StartTime":19969.0,"Objects":[{"StartTime":19969.0,"Position":32.0,"HyperDash":false},{"StartTime":20037.0,"Position":60.581398,"HyperDash":false},{"StartTime":20141.0,"Position":122.0,"HyperDash":false}]},{"StartTime":20227.0,"Objects":[{"StartTime":20227.0,"Position":188.0,"HyperDash":true}]},{"StartTime":20313.0,"Objects":[{"StartTime":20313.0,"Position":16.0,"HyperDash":false},{"StartTime":20381.0,"Position":51.581398,"HyperDash":false},{"StartTime":20485.0,"Position":106.0,"HyperDash":true}]},{"StartTime":20658.0,"Objects":[{"StartTime":20658.0,"Position":368.0,"HyperDash":false},{"StartTime":20744.0,"Position":320.104462,"HyperDash":false},{"StartTime":20830.0,"Position":260.0,"HyperDash":false},{"StartTime":20916.0,"Position":298.686646,"HyperDash":false},{"StartTime":21002.0,"Position":368.0,"HyperDash":false},{"StartTime":21070.0,"Position":333.8027,"HyperDash":false},{"StartTime":21175.0,"Position":260.0,"HyperDash":true}]},{"StartTime":21348.0,"Objects":[{"StartTime":21348.0,"Position":496.0,"HyperDash":false}]},{"StartTime":21520.0,"Objects":[{"StartTime":21520.0,"Position":324.0,"HyperDash":false}]},{"StartTime":21693.0,"Objects":[{"StartTime":21693.0,"Position":496.0,"HyperDash":false}]},{"StartTime":21865.0,"Objects":[{"StartTime":21865.0,"Position":388.0,"HyperDash":false},{"StartTime":21951.0,"Position":343.0,"HyperDash":true}]},{"StartTime":22037.0,"Objects":[{"StartTime":22037.0,"Position":144.0,"HyperDash":false}]},{"StartTime":22210.0,"Objects":[{"StartTime":22210.0,"Position":252.0,"HyperDash":false},{"StartTime":22296.0,"Position":231.875381,"HyperDash":false}]},{"StartTime":22382.0,"Objects":[{"StartTime":22382.0,"Position":312.0,"HyperDash":false},{"StartTime":22468.0,"Position":291.8754,"HyperDash":false}]},{"StartTime":22555.0,"Objects":[{"StartTime":22555.0,"Position":372.0,"HyperDash":false},{"StartTime":22641.0,"Position":351.8754,"HyperDash":true}]},{"StartTime":22727.0,"Objects":[{"StartTime":22727.0,"Position":180.0,"HyperDash":false},{"StartTime":22795.0,"Position":226.58139,"HyperDash":false},{"StartTime":22899.0,"Position":270.0,"HyperDash":false}]},{"StartTime":22986.0,"Objects":[{"StartTime":22986.0,"Position":208.0,"HyperDash":true}]},{"StartTime":23072.0,"Objects":[{"StartTime":23072.0,"Position":436.0,"HyperDash":false},{"StartTime":23158.0,"Position":486.800659,"HyperDash":false},{"StartTime":23244.0,"Position":494.721924,"HyperDash":false},{"StartTime":23330.0,"Position":435.854675,"HyperDash":true}]},{"StartTime":23417.0,"Objects":[{"StartTime":23417.0,"Position":208.0,"HyperDash":false},{"StartTime":23503.0,"Position":163.75,"HyperDash":false},{"StartTime":23589.0,"Position":95.5,"HyperDash":false},{"StartTime":23657.0,"Position":134.976746,"HyperDash":false},{"StartTime":23761.0,"Position":208.0,"HyperDash":false}]},{"StartTime":23934.0,"Objects":[{"StartTime":23934.0,"Position":312.0,"HyperDash":false}]},{"StartTime":24020.0,"Objects":[{"StartTime":24020.0,"Position":220.0,"HyperDash":false}]},{"StartTime":24106.0,"Objects":[{"StartTime":24106.0,"Position":128.0,"HyperDash":false},{"StartTime":24174.0,"Position":164.58139,"HyperDash":false},{"StartTime":24278.0,"Position":218.0,"HyperDash":false}]},{"StartTime":24451.0,"Objects":[{"StartTime":24451.0,"Position":392.0,"HyperDash":false}]},{"StartTime":24537.0,"Objects":[{"StartTime":24537.0,"Position":444.0,"HyperDash":false}]},{"StartTime":24624.0,"Objects":[{"StartTime":24624.0,"Position":444.0,"HyperDash":false}]},{"StartTime":24710.0,"Objects":[{"StartTime":24710.0,"Position":392.0,"HyperDash":true}]},{"StartTime":24796.0,"Objects":[{"StartTime":24796.0,"Position":212.0,"HyperDash":false},{"StartTime":24882.0,"Position":244.0,"HyperDash":false},{"StartTime":24968.0,"Position":302.0,"HyperDash":false},{"StartTime":25036.0,"Position":269.4186,"HyperDash":false},{"StartTime":25140.0,"Position":212.0,"HyperDash":false}]},{"StartTime":25313.0,"Objects":[{"StartTime":25313.0,"Position":320.0,"HyperDash":false}]},{"StartTime":25400.0,"Objects":[{"StartTime":25400.0,"Position":384.0,"HyperDash":false}]},{"StartTime":25486.0,"Objects":[{"StartTime":25486.0,"Position":284.0,"HyperDash":false},{"StartTime":25554.0,"Position":267.4186,"HyperDash":false},{"StartTime":25658.0,"Position":194.0,"HyperDash":true}]},{"StartTime":25831.0,"Objects":[{"StartTime":25831.0,"Position":448.0,"HyperDash":false},{"StartTime":25917.0,"Position":444.548645,"HyperDash":false}]},{"StartTime":26003.0,"Objects":[{"StartTime":26003.0,"Position":344.0,"HyperDash":false},{"StartTime":26089.0,"Position":299.0,"HyperDash":true}]},{"StartTime":26175.0,"Objects":[{"StartTime":26175.0,"Position":128.0,"HyperDash":false},{"StartTime":26261.0,"Position":80.0,"HyperDash":false},{"StartTime":26347.0,"Position":38.0,"HyperDash":false},{"StartTime":26415.0,"Position":73.58139,"HyperDash":false},{"StartTime":26519.0,"Position":128.0,"HyperDash":false}]},{"StartTime":26693.0,"Objects":[{"StartTime":26693.0,"Position":236.0,"HyperDash":false}]},{"StartTime":26779.0,"Objects":[{"StartTime":26779.0,"Position":299.0,"HyperDash":false}]},{"StartTime":26865.0,"Objects":[{"StartTime":26865.0,"Position":362.0,"HyperDash":false}]},{"StartTime":27037.0,"Objects":[{"StartTime":27037.0,"Position":196.0,"HyperDash":false}]},{"StartTime":27210.0,"Objects":[{"StartTime":27210.0,"Position":352.0,"HyperDash":false}]},{"StartTime":27296.0,"Objects":[{"StartTime":27296.0,"Position":352.0,"HyperDash":false}]},{"StartTime":27382.0,"Objects":[{"StartTime":27382.0,"Position":312.0,"HyperDash":false}]},{"StartTime":27469.0,"Objects":[{"StartTime":27469.0,"Position":248.0,"HyperDash":true}]},{"StartTime":27555.0,"Objects":[{"StartTime":27555.0,"Position":412.0,"HyperDash":false},{"StartTime":27641.0,"Position":349.0,"HyperDash":false},{"StartTime":27727.0,"Position":322.0,"HyperDash":false},{"StartTime":27795.0,"Position":343.5814,"HyperDash":false},{"StartTime":27899.0,"Position":412.0,"HyperDash":false}]},{"StartTime":28072.0,"Objects":[{"StartTime":28072.0,"Position":304.0,"HyperDash":false}]},{"StartTime":28158.0,"Objects":[{"StartTime":28158.0,"Position":396.0,"HyperDash":false}]},{"StartTime":28244.0,"Objects":[{"StartTime":28244.0,"Position":488.0,"HyperDash":false},{"StartTime":28312.0,"Position":451.4186,"HyperDash":false},{"StartTime":28416.0,"Position":398.0,"HyperDash":true}]},{"StartTime":28589.0,"Objects":[{"StartTime":28589.0,"Position":88.0,"HyperDash":false}]},{"StartTime":28934.0,"Objects":[{"StartTime":28934.0,"Position":340.0,"HyperDash":false},{"StartTime":29002.0,"Position":358.545563,"HyperDash":false},{"StartTime":29106.0,"Position":343.909363,"HyperDash":false}]},{"StartTime":29279.0,"Objects":[{"StartTime":29279.0,"Position":172.0,"HyperDash":false},{"StartTime":29347.0,"Position":182.577881,"HyperDash":false},{"StartTime":29451.0,"Position":168.402878,"HyperDash":false}]},{"StartTime":29537.0,"Objects":[{"StartTime":29537.0,"Position":268.0,"HyperDash":false}]},{"StartTime":29624.0,"Objects":[{"StartTime":29624.0,"Position":368.0,"HyperDash":false},{"StartTime":29692.0,"Position":343.4186,"HyperDash":false},{"StartTime":29796.0,"Position":278.0,"HyperDash":false}]},{"StartTime":29969.0,"Objects":[{"StartTime":29969.0,"Position":452.0,"HyperDash":false},{"StartTime":30055.0,"Position":459.397949,"HyperDash":false},{"StartTime":30141.0,"Position":452.0,"HyperDash":true}]},{"StartTime":30313.0,"Objects":[{"StartTime":30313.0,"Position":200.0,"HyperDash":false},{"StartTime":30381.0,"Position":196.454437,"HyperDash":false},{"StartTime":30485.0,"Position":196.090652,"HyperDash":false}]},{"StartTime":30658.0,"Objects":[{"StartTime":30658.0,"Position":368.0,"HyperDash":false},{"StartTime":30726.0,"Position":349.4186,"HyperDash":false},{"StartTime":30830.0,"Position":278.0,"HyperDash":false}]},{"StartTime":30917.0,"Objects":[{"StartTime":30917.0,"Position":380.0,"HyperDash":false}]},{"StartTime":31003.0,"Objects":[{"StartTime":31003.0,"Position":480.0,"HyperDash":false},{"StartTime":31071.0,"Position":435.4186,"HyperDash":false},{"StartTime":31175.0,"Position":390.0,"HyperDash":true}]},{"StartTime":31348.0,"Objects":[{"StartTime":31348.0,"Position":128.0,"HyperDash":false},{"StartTime":31434.0,"Position":124.54866,"HyperDash":false}]},{"StartTime":31520.0,"Objects":[{"StartTime":31520.0,"Position":228.0,"HyperDash":false},{"StartTime":31606.0,"Position":273.0,"HyperDash":true}]},{"StartTime":31693.0,"Objects":[{"StartTime":31693.0,"Position":88.0,"HyperDash":false},{"StartTime":31761.0,"Position":101.632492,"HyperDash":false},{"StartTime":31865.0,"Position":84.5410156,"HyperDash":false}]},{"StartTime":32037.0,"Objects":[{"StartTime":32037.0,"Position":256.0,"HyperDash":false},{"StartTime":32105.0,"Position":278.5814,"HyperDash":false},{"StartTime":32209.0,"Position":346.0,"HyperDash":false}]},{"StartTime":32296.0,"Objects":[{"StartTime":32296.0,"Position":246.0,"HyperDash":false}]},{"StartTime":32382.0,"Objects":[{"StartTime":32382.0,"Position":148.0,"HyperDash":false},{"StartTime":32450.0,"Position":101.4186,"HyperDash":false},{"StartTime":32554.0,"Position":58.0,"HyperDash":false}]},{"StartTime":32727.0,"Objects":[{"StartTime":32727.0,"Position":232.0,"HyperDash":false}]},{"StartTime":32813.0,"Objects":[{"StartTime":32813.0,"Position":180.0,"HyperDash":false}]},{"StartTime":32900.0,"Objects":[{"StartTime":32900.0,"Position":124.0,"HyperDash":true}]},{"StartTime":33072.0,"Objects":[{"StartTime":33072.0,"Position":376.0,"HyperDash":false},{"StartTime":33140.0,"Position":415.5814,"HyperDash":false},{"StartTime":33244.0,"Position":466.0,"HyperDash":false}]},{"StartTime":33417.0,"Objects":[{"StartTime":33417.0,"Position":300.0,"HyperDash":false},{"StartTime":33485.0,"Position":323.5814,"HyperDash":false},{"StartTime":33589.0,"Position":390.0,"HyperDash":false}]},{"StartTime":33762.0,"Objects":[{"StartTime":33762.0,"Position":220.0,"HyperDash":false},{"StartTime":33830.0,"Position":200.41861,"HyperDash":false},{"StartTime":33934.0,"Position":130.0,"HyperDash":true}]},{"StartTime":34106.0,"Objects":[{"StartTime":34106.0,"Position":416.0,"HyperDash":false},{"StartTime":34149.0,"Position":438.5,"HyperDash":false},{"StartTime":34192.0,"Position":416.0,"HyperDash":false},{"StartTime":34235.0,"Position":438.5,"HyperDash":false},{"StartTime":34278.0,"Position":416.0,"HyperDash":false},{"StartTime":34321.0,"Position":438.5,"HyperDash":false},{"StartTime":34364.0,"Position":416.0,"HyperDash":false},{"StartTime":34407.0,"Position":438.5,"HyperDash":true}]},{"StartTime":34451.0,"Objects":[{"StartTime":34451.0,"Position":265.0,"HyperDash":false},{"StartTime":34519.0,"Position":199.6279,"HyperDash":false},{"StartTime":34623.0,"Position":130.0,"HyperDash":false}]},{"StartTime":34796.0,"Objects":[{"StartTime":34796.0,"Position":300.0,"HyperDash":false},{"StartTime":34864.0,"Position":300.496857,"HyperDash":false},{"StartTime":34968.0,"Position":303.786133,"HyperDash":false}]},{"StartTime":35141.0,"Objects":[{"StartTime":35141.0,"Position":140.0,"HyperDash":true}]},{"StartTime":35313.0,"Objects":[{"StartTime":35313.0,"Position":376.0,"HyperDash":false}]},{"StartTime":35486.0,"Objects":[{"StartTime":35486.0,"Position":268.0,"HyperDash":false},{"StartTime":35554.0,"Position":253.518738,"HyperDash":false},{"StartTime":35658.0,"Position":264.253265,"HyperDash":true}]},{"StartTime":35831.0,"Objects":[{"StartTime":35831.0,"Position":496.0,"HyperDash":false},{"StartTime":35899.0,"Position":454.4186,"HyperDash":false},{"StartTime":36003.0,"Position":406.0,"HyperDash":false}]},{"StartTime":36175.0,"Objects":[{"StartTime":36175.0,"Position":236.0,"HyperDash":false},{"StartTime":36243.0,"Position":192.41861,"HyperDash":false},{"StartTime":36347.0,"Position":146.0,"HyperDash":true}]},{"StartTime":36520.0,"Objects":[{"StartTime":36520.0,"Position":400.0,"HyperDash":false}]},{"StartTime":36693.0,"Objects":[{"StartTime":36693.0,"Position":236.0,"HyperDash":true}]},{"StartTime":36865.0,"Objects":[{"StartTime":36865.0,"Position":476.0,"HyperDash":false}]},{"StartTime":36951.0,"Objects":[{"StartTime":36951.0,"Position":476.0,"HyperDash":false}]},{"StartTime":37037.0,"Objects":[{"StartTime":37037.0,"Position":434.0,"HyperDash":false}]},{"StartTime":37124.0,"Objects":[{"StartTime":37124.0,"Position":369.0,"HyperDash":true}]},{"StartTime":37210.0,"Objects":[{"StartTime":37210.0,"Position":196.0,"HyperDash":false},{"StartTime":37278.0,"Position":151.41861,"HyperDash":false},{"StartTime":37382.0,"Position":106.0,"HyperDash":false}]},{"StartTime":37555.0,"Objects":[{"StartTime":37555.0,"Position":272.0,"HyperDash":false},{"StartTime":37623.0,"Position":302.5814,"HyperDash":false},{"StartTime":37727.0,"Position":362.0,"HyperDash":false}]},{"StartTime":37900.0,"Objects":[{"StartTime":37900.0,"Position":196.0,"HyperDash":true}]},{"StartTime":38072.0,"Objects":[{"StartTime":38072.0,"Position":432.0,"HyperDash":false}]},{"StartTime":38244.0,"Objects":[{"StartTime":38244.0,"Position":324.0,"HyperDash":false}]},{"StartTime":38331.0,"Objects":[{"StartTime":38331.0,"Position":272.0,"HyperDash":false}]},{"StartTime":38417.0,"Objects":[{"StartTime":38417.0,"Position":224.0,"HyperDash":true}]},{"StartTime":38589.0,"Objects":[{"StartTime":38589.0,"Position":488.0,"HyperDash":false},{"StartTime":38657.0,"Position":483.690765,"HyperDash":false},{"StartTime":38761.0,"Position":489.747253,"HyperDash":false}]},{"StartTime":38934.0,"Objects":[{"StartTime":38934.0,"Position":324.0,"HyperDash":false},{"StartTime":39002.0,"Position":339.316925,"HyperDash":false},{"StartTime":39106.0,"Position":327.331055,"HyperDash":true}]},{"StartTime":39279.0,"Objects":[{"StartTime":39279.0,"Position":88.0,"HyperDash":false}]},{"StartTime":39451.0,"Objects":[{"StartTime":39451.0,"Position":256.0,"HyperDash":true}]},{"StartTime":39624.0,"Objects":[{"StartTime":39624.0,"Position":16.0,"HyperDash":true}]},{"StartTime":39969.0,"Objects":[{"StartTime":39969.0,"Position":428.0,"HyperDash":false},{"StartTime":40055.0,"Position":475.928162,"HyperDash":false},{"StartTime":40141.0,"Position":473.713928,"HyperDash":false},{"StartTime":40227.0,"Position":429.731,"HyperDash":false}]},{"StartTime":40313.0,"Objects":[{"StartTime":40313.0,"Position":328.0,"HyperDash":false},{"StartTime":40399.0,"Position":262.213257,"HyperDash":false},{"StartTime":40485.0,"Position":239.814941,"HyperDash":false},{"StartTime":40571.0,"Position":239.657425,"HyperDash":true}]},{"StartTime":40658.0,"Objects":[{"StartTime":40658.0,"Position":412.0,"HyperDash":false},{"StartTime":40744.0,"Position":464.25,"HyperDash":false},{"StartTime":40830.0,"Position":497.294128,"HyperDash":false},{"StartTime":40916.0,"Position":499.8483,"HyperDash":true}]},{"StartTime":41003.0,"Objects":[{"StartTime":41003.0,"Position":272.0,"HyperDash":false},{"StartTime":41089.0,"Position":253.0,"HyperDash":false},{"StartTime":41175.0,"Position":300.4706,"HyperDash":false},{"StartTime":41261.0,"Position":356.6626,"HyperDash":true}]},{"StartTime":41348.0,"Objects":[{"StartTime":41348.0,"Position":116.0,"HyperDash":false},{"StartTime":41434.0,"Position":72.26963,"HyperDash":false},{"StartTime":41520.0,"Position":61.0594635,"HyperDash":false},{"StartTime":41606.0,"Position":119.336884,"HyperDash":true}]},{"StartTime":41693.0,"Objects":[{"StartTime":41693.0,"Position":340.0,"HyperDash":false},{"StartTime":41779.0,"Position":288.5,"HyperDash":false},{"StartTime":41865.0,"Position":204.999985,"HyperDash":false},{"StartTime":41951.0,"Position":137.5,"HyperDash":true}]},{"StartTime":42037.0,"Objects":[{"StartTime":42037.0,"Position":312.0,"HyperDash":false}]},{"StartTime":42210.0,"Objects":[{"StartTime":42210.0,"Position":164.0,"HyperDash":false}]},{"StartTime":42382.0,"Objects":[{"StartTime":42382.0,"Position":324.0,"HyperDash":false}]},{"StartTime":42555.0,"Objects":[{"StartTime":42555.0,"Position":152.0,"HyperDash":true}]},{"StartTime":42727.0,"Objects":[{"StartTime":42727.0,"Position":404.0,"HyperDash":false}]},{"StartTime":42813.0,"Objects":[{"StartTime":42813.0,"Position":460.0,"HyperDash":false}]},{"StartTime":42900.0,"Objects":[{"StartTime":42900.0,"Position":460.0,"HyperDash":false}]},{"StartTime":42986.0,"Objects":[{"StartTime":42986.0,"Position":404.0,"HyperDash":true}]},{"StartTime":43072.0,"Objects":[{"StartTime":43072.0,"Position":208.0,"HyperDash":false},{"StartTime":43158.0,"Position":204.54866,"HyperDash":false}]},{"StartTime":43244.0,"Objects":[{"StartTime":43244.0,"Position":280.0,"HyperDash":false},{"StartTime":43330.0,"Position":283.451355,"HyperDash":true}]},{"StartTime":43417.0,"Objects":[{"StartTime":43417.0,"Position":104.0,"HyperDash":false},{"StartTime":43503.0,"Position":59.0,"HyperDash":true}]},{"StartTime":43589.0,"Objects":[{"StartTime":43589.0,"Position":240.0,"HyperDash":false},{"StartTime":43675.0,"Position":285.0,"HyperDash":true}]},{"StartTime":43762.0,"Objects":[{"StartTime":43762.0,"Position":80.0,"HyperDash":false},{"StartTime":43848.0,"Position":125.0,"HyperDash":true}]},{"StartTime":43934.0,"Objects":[{"StartTime":43934.0,"Position":372.0,"HyperDash":false},{"StartTime":44020.0,"Position":327.0,"HyperDash":true}]},{"StartTime":44106.0,"Objects":[{"StartTime":44106.0,"Position":124.0,"HyperDash":true}]},{"StartTime":44279.0,"Objects":[{"StartTime":44279.0,"Position":368.0,"HyperDash":true}]},{"StartTime":44451.0,"Objects":[{"StartTime":44451.0,"Position":116.0,"HyperDash":false},{"StartTime":44537.0,"Position":71.0,"HyperDash":false}]},{"StartTime":44624.0,"Objects":[{"StartTime":44624.0,"Position":172.0,"HyperDash":false},{"StartTime":44710.0,"Position":127.0,"HyperDash":true}]},{"StartTime":44796.0,"Objects":[{"StartTime":44796.0,"Position":300.0,"HyperDash":false},{"StartTime":44882.0,"Position":356.5,"HyperDash":false},{"StartTime":44968.0,"Position":435.0,"HyperDash":false},{"StartTime":45011.0,"Position":468.75,"HyperDash":true}]},{"StartTime":45141.0,"Objects":[{"StartTime":45141.0,"Position":260.0,"HyperDash":false},{"StartTime":45227.0,"Position":345.5,"HyperDash":false},{"StartTime":45313.0,"Position":395.0,"HyperDash":false},{"StartTime":45356.0,"Position":428.75,"HyperDash":true}]},{"StartTime":45486.0,"Objects":[{"StartTime":45486.0,"Position":176.0,"HyperDash":false}]},{"StartTime":45507.0,"Objects":[{"StartTime":45507.0,"Position":158.0,"HyperDash":false}]},{"StartTime":45529.0,"Objects":[{"StartTime":45529.0,"Position":143.0,"HyperDash":false}]},{"StartTime":45550.0,"Objects":[{"StartTime":45550.0,"Position":129.0,"HyperDash":false}]},{"StartTime":45572.0,"Objects":[{"StartTime":45572.0,"Position":119.0,"HyperDash":false}]},{"StartTime":45594.0,"Objects":[{"StartTime":45594.0,"Position":111.0,"HyperDash":false}]},{"StartTime":45615.0,"Objects":[{"StartTime":45615.0,"Position":108.0,"HyperDash":false}]},{"StartTime":45637.0,"Objects":[{"StartTime":45637.0,"Position":108.0,"HyperDash":false}]},{"StartTime":45658.0,"Objects":[{"StartTime":45658.0,"Position":112.0,"HyperDash":false}]},{"StartTime":45680.0,"Objects":[{"StartTime":45680.0,"Position":120.0,"HyperDash":false}]},{"StartTime":45701.0,"Objects":[{"StartTime":45701.0,"Position":131.0,"HyperDash":false}]},{"StartTime":45723.0,"Objects":[{"StartTime":45723.0,"Position":145.0,"HyperDash":false}]},{"StartTime":45744.0,"Objects":[{"StartTime":45744.0,"Position":161.0,"HyperDash":false}]},{"StartTime":45766.0,"Objects":[{"StartTime":45766.0,"Position":178.0,"HyperDash":false}]},{"StartTime":45787.0,"Objects":[{"StartTime":45787.0,"Position":196.0,"HyperDash":false}]},{"StartTime":45809.0,"Objects":[{"StartTime":45809.0,"Position":214.0,"HyperDash":false}]},{"StartTime":45831.0,"Objects":[{"StartTime":45831.0,"Position":240.0,"HyperDash":false}]},{"StartTime":45852.0,"Objects":[{"StartTime":45852.0,"Position":257.0,"HyperDash":false}]},{"StartTime":45874.0,"Objects":[{"StartTime":45874.0,"Position":275.0,"HyperDash":false}]},{"StartTime":45895.0,"Objects":[{"StartTime":45895.0,"Position":291.0,"HyperDash":false}]},{"StartTime":45917.0,"Objects":[{"StartTime":45917.0,"Position":304.0,"HyperDash":false}]},{"StartTime":45938.0,"Objects":[{"StartTime":45938.0,"Position":315.0,"HyperDash":false}]},{"StartTime":45960.0,"Objects":[{"StartTime":45960.0,"Position":323.0,"HyperDash":false}]},{"StartTime":45981.0,"Objects":[{"StartTime":45981.0,"Position":327.0,"HyperDash":false}]},{"StartTime":46003.0,"Objects":[{"StartTime":46003.0,"Position":327.0,"HyperDash":false}]},{"StartTime":46025.0,"Objects":[{"StartTime":46025.0,"Position":324.0,"HyperDash":false}]},{"StartTime":46046.0,"Objects":[{"StartTime":46046.0,"Position":317.0,"HyperDash":false}]},{"StartTime":46068.0,"Objects":[{"StartTime":46068.0,"Position":306.0,"HyperDash":false}]},{"StartTime":46089.0,"Objects":[{"StartTime":46089.0,"Position":293.0,"HyperDash":false}]},{"StartTime":46111.0,"Objects":[{"StartTime":46111.0,"Position":277.0,"HyperDash":false}]},{"StartTime":46132.0,"Objects":[{"StartTime":46132.0,"Position":260.0,"HyperDash":true}]},{"StartTime":46175.0,"Objects":[{"StartTime":46175.0,"Position":76.0,"HyperDash":false},{"StartTime":46243.0,"Position":41.627903,"HyperDash":false},{"StartTime":46347.0,"Position":75.00001,"HyperDash":false}]},{"StartTime":46434.0,"Objects":[{"StartTime":46434.0,"Position":120.0,"HyperDash":true}]},{"StartTime":46520.0,"Objects":[{"StartTime":46520.0,"Position":280.0,"HyperDash":false},{"StartTime":46588.0,"Position":298.5814,"HyperDash":false},{"StartTime":46692.0,"Position":370.0,"HyperDash":false}]},{"StartTime":46779.0,"Objects":[{"StartTime":46779.0,"Position":324.0,"HyperDash":true}]},{"StartTime":46865.0,"Objects":[{"StartTime":46865.0,"Position":152.0,"HyperDash":false},{"StartTime":46951.0,"Position":107.0,"HyperDash":false}]},{"StartTime":47037.0,"Objects":[{"StartTime":47037.0,"Position":172.0,"HyperDash":false},{"StartTime":47123.0,"Position":127.0,"HyperDash":true}]},{"StartTime":47210.0,"Objects":[{"StartTime":47210.0,"Position":336.0,"HyperDash":false}]},{"StartTime":47253.0,"Objects":[{"StartTime":47253.0,"Position":363.0,"HyperDash":false}]},{"StartTime":47296.0,"Objects":[{"StartTime":47296.0,"Position":384.0,"HyperDash":false}]},{"StartTime":47339.0,"Objects":[{"StartTime":47339.0,"Position":393.0,"HyperDash":false}]},{"StartTime":47382.0,"Objects":[{"StartTime":47382.0,"Position":389.0,"HyperDash":false}]},{"StartTime":47425.0,"Objects":[{"StartTime":47425.0,"Position":372.0,"HyperDash":false}]},{"StartTime":47469.0,"Objects":[{"StartTime":47469.0,"Position":347.0,"HyperDash":true}]},{"StartTime":47555.0,"Objects":[{"StartTime":47555.0,"Position":168.0,"HyperDash":false},{"StartTime":47623.0,"Position":126.41861,"HyperDash":false},{"StartTime":47727.0,"Position":78.0,"HyperDash":false}]},{"StartTime":47900.0,"Objects":[{"StartTime":47900.0,"Position":244.0,"HyperDash":false},{"StartTime":47968.0,"Position":214.41861,"HyperDash":false},{"StartTime":48072.0,"Position":154.0,"HyperDash":true}]},{"StartTime":48244.0,"Objects":[{"StartTime":48244.0,"Position":400.0,"HyperDash":false},{"StartTime":48330.0,"Position":403.451355,"HyperDash":false}]},{"StartTime":48503.0,"Objects":[{"StartTime":48503.0,"Position":312.0,"HyperDash":true}]},{"StartTime":48589.0,"Objects":[{"StartTime":48589.0,"Position":140.0,"HyperDash":false}]},{"StartTime":48762.0,"Objects":[{"StartTime":48762.0,"Position":248.0,"HyperDash":true}]},{"StartTime":48934.0,"Objects":[{"StartTime":48934.0,"Position":16.0,"HyperDash":false},{"StartTime":49020.0,"Position":61.0,"HyperDash":false}]},{"StartTime":49193.0,"Objects":[{"StartTime":49193.0,"Position":160.0,"HyperDash":true}]},{"StartTime":49279.0,"Objects":[{"StartTime":49279.0,"Position":16.0,"HyperDash":false},{"StartTime":49365.0,"Position":20.0741081,"HyperDash":false}]},{"StartTime":49451.0,"Objects":[{"StartTime":49451.0,"Position":76.0,"HyperDash":false},{"StartTime":49537.0,"Position":121.0,"HyperDash":true}]},{"StartTime":49624.0,"Objects":[{"StartTime":49624.0,"Position":304.0,"HyperDash":false}]},{"StartTime":49667.0,"Objects":[{"StartTime":49667.0,"Position":317.0,"HyperDash":false}]},{"StartTime":49710.0,"Objects":[{"StartTime":49710.0,"Position":326.0,"HyperDash":false}]},{"StartTime":49753.0,"Objects":[{"StartTime":49753.0,"Position":328.0,"HyperDash":false}]},{"StartTime":49796.0,"Objects":[{"StartTime":49796.0,"Position":325.0,"HyperDash":false}]},{"StartTime":49839.0,"Objects":[{"StartTime":49839.0,"Position":316.0,"HyperDash":false}]},{"StartTime":49882.0,"Objects":[{"StartTime":49882.0,"Position":301.0,"HyperDash":true}]},{"StartTime":49969.0,"Objects":[{"StartTime":49969.0,"Position":120.0,"HyperDash":false}]},{"StartTime":50055.0,"Objects":[{"StartTime":50055.0,"Position":52.0,"HyperDash":false}]},{"StartTime":50141.0,"Objects":[{"StartTime":50141.0,"Position":120.0,"HyperDash":false}]},{"StartTime":50313.0,"Objects":[{"StartTime":50313.0,"Position":288.0,"HyperDash":false}]},{"StartTime":50400.0,"Objects":[{"StartTime":50400.0,"Position":332.0,"HyperDash":false}]},{"StartTime":50486.0,"Objects":[{"StartTime":50486.0,"Position":328.0,"HyperDash":false}]},{"StartTime":50572.0,"Objects":[{"StartTime":50572.0,"Position":280.0,"HyperDash":true}]},{"StartTime":50658.0,"Objects":[{"StartTime":50658.0,"Position":104.0,"HyperDash":false},{"StartTime":50744.0,"Position":59.0,"HyperDash":false}]},{"StartTime":50831.0,"Objects":[{"StartTime":50831.0,"Position":104.0,"HyperDash":false},{"StartTime":50917.0,"Position":149.0,"HyperDash":true}]},{"StartTime":51003.0,"Objects":[{"StartTime":51003.0,"Position":328.0,"HyperDash":false},{"StartTime":51071.0,"Position":375.5814,"HyperDash":false},{"StartTime":51175.0,"Position":334.0,"HyperDash":false}]},{"StartTime":51262.0,"Objects":[{"StartTime":51262.0,"Position":280.0,"HyperDash":true}]},{"StartTime":51348.0,"Objects":[{"StartTime":51348.0,"Position":128.0,"HyperDash":true},{"StartTime":51405.0,"Position":204.775,"HyperDash":false},{"StartTime":51498.0,"Position":364.25,"HyperDash":false}]},{"StartTime":51520.0,"Objects":[{"StartTime":51520.0,"Position":364.0,"HyperDash":false},{"StartTime":51588.0,"Position":296.6279,"HyperDash":false},{"StartTime":51692.0,"Position":229.0,"HyperDash":true}]},{"StartTime":51736.0,"Objects":[{"StartTime":51736.0,"Position":368.0,"HyperDash":false},{"StartTime":51865.0,"Position":435.5,"HyperDash":false}]},{"StartTime":51951.0,"Objects":[{"StartTime":51951.0,"Position":380.0,"HyperDash":true}]},{"StartTime":52037.0,"Objects":[{"StartTime":52037.0,"Position":204.0,"HyperDash":false},{"StartTime":52123.0,"Position":136.5,"HyperDash":false}]},{"StartTime":52210.0,"Objects":[{"StartTime":52210.0,"Position":223.0,"HyperDash":false},{"StartTime":52296.0,"Position":155.5,"HyperDash":true}]},{"StartTime":52382.0,"Objects":[{"StartTime":52382.0,"Position":388.0,"HyperDash":false},{"StartTime":52468.0,"Position":455.5,"HyperDash":false}]},{"StartTime":52555.0,"Objects":[{"StartTime":52555.0,"Position":368.0,"HyperDash":false},{"StartTime":52641.0,"Position":435.5,"HyperDash":true}]},{"StartTime":52727.0,"Objects":[{"StartTime":52727.0,"Position":224.0,"HyperDash":false}]},{"StartTime":52770.0,"Objects":[{"StartTime":52770.0,"Position":194.0,"HyperDash":false}]},{"StartTime":52813.0,"Objects":[{"StartTime":52813.0,"Position":169.0,"HyperDash":false}]},{"StartTime":52856.0,"Objects":[{"StartTime":52856.0,"Position":149.0,"HyperDash":false}]},{"StartTime":52900.0,"Objects":[{"StartTime":52900.0,"Position":137.0,"HyperDash":false}]},{"StartTime":52943.0,"Objects":[{"StartTime":52943.0,"Position":134.0,"HyperDash":false}]},{"StartTime":52986.0,"Objects":[{"StartTime":52986.0,"Position":141.0,"HyperDash":true}]},{"StartTime":53072.0,"Objects":[{"StartTime":53072.0,"Position":368.0,"HyperDash":true},{"StartTime":53140.0,"Position":260.0465,"HyperDash":false},{"StartTime":53244.0,"Position":143.0,"HyperDash":true}]},{"StartTime":53417.0,"Objects":[{"StartTime":53417.0,"Position":444.0,"HyperDash":true},{"StartTime":53485.0,"Position":358.0465,"HyperDash":false},{"StartTime":53589.0,"Position":219.0,"HyperDash":true}]},{"StartTime":53762.0,"Objects":[{"StartTime":53762.0,"Position":488.0,"HyperDash":false},{"StartTime":53830.0,"Position":475.545563,"HyperDash":false},{"StartTime":53934.0,"Position":491.909363,"HyperDash":false}]},{"StartTime":54106.0,"Objects":[{"StartTime":54106.0,"Position":336.0,"HyperDash":false}]},{"StartTime":54193.0,"Objects":[{"StartTime":54193.0,"Position":280.0,"HyperDash":false}]},{"StartTime":54279.0,"Objects":[{"StartTime":54279.0,"Position":228.0,"HyperDash":false}]},{"StartTime":54451.0,"Objects":[{"StartTime":54451.0,"Position":392.0,"HyperDash":false},{"StartTime":54494.0,"Position":394.4847,"HyperDash":true}]},{"StartTime":54624.0,"Objects":[{"StartTime":54624.0,"Position":188.0,"HyperDash":false},{"StartTime":54667.0,"Position":185.089874,"HyperDash":true}]},{"StartTime":54796.0,"Objects":[{"StartTime":54796.0,"Position":408.0,"HyperDash":false},{"StartTime":54882.0,"Position":363.0,"HyperDash":true}]},{"StartTime":54969.0,"Objects":[{"StartTime":54969.0,"Position":136.0,"HyperDash":false},{"StartTime":55055.0,"Position":181.0,"HyperDash":true}]},{"StartTime":55141.0,"Objects":[{"StartTime":55141.0,"Position":384.0,"HyperDash":false},{"StartTime":55198.0,"Position":345.1965,"HyperDash":false},{"StartTime":55255.0,"Position":294.0,"HyperDash":false},{"StartTime":55370.0,"Position":384.0,"HyperDash":true}]},{"StartTime":55486.0,"Objects":[{"StartTime":55486.0,"Position":172.0,"HyperDash":false}]},{"StartTime":55601.0,"Objects":[{"StartTime":55601.0,"Position":280.0,"HyperDash":false}]},{"StartTime":55716.0,"Objects":[{"StartTime":55716.0,"Position":388.0,"HyperDash":true}]},{"StartTime":55831.0,"Objects":[{"StartTime":55831.0,"Position":164.0,"HyperDash":false},{"StartTime":55888.0,"Position":147.131012,"HyperDash":false},{"StartTime":55945.0,"Position":104.0,"HyperDash":false},{"StartTime":56060.0,"Position":164.0,"HyperDash":true}]},{"StartTime":56175.0,"Objects":[{"StartTime":56175.0,"Position":340.0,"HyperDash":false}]},{"StartTime":56290.0,"Objects":[{"StartTime":56290.0,"Position":412.0,"HyperDash":false}]},{"StartTime":56405.0,"Objects":[{"StartTime":56405.0,"Position":412.0,"HyperDash":true}]},{"StartTime":56520.0,"Objects":[{"StartTime":56520.0,"Position":212.0,"HyperDash":false},{"StartTime":56606.0,"Position":148.1791,"HyperDash":false},{"StartTime":56692.0,"Position":144.4465,"HyperDash":false},{"StartTime":56778.0,"Position":188.107758,"HyperDash":false},{"StartTime":56864.0,"Position":241.294647,"HyperDash":false},{"StartTime":56950.0,"Position":307.139038,"HyperDash":false},{"StartTime":57037.0,"Position":323.301819,"HyperDash":false},{"StartTime":57123.0,"Position":297.7406,"HyperDash":true}]},{"StartTime":57210.0,"Objects":[{"StartTime":57210.0,"Position":128.0,"HyperDash":false}]},{"StartTime":57231.0,"Objects":[{"StartTime":57231.0,"Position":112.0,"HyperDash":false}]},{"StartTime":57253.0,"Objects":[{"StartTime":57253.0,"Position":97.0,"HyperDash":false}]},{"StartTime":57275.0,"Objects":[{"StartTime":57275.0,"Position":83.0,"HyperDash":false}]},{"StartTime":57296.0,"Objects":[{"StartTime":57296.0,"Position":70.0,"HyperDash":false}]},{"StartTime":57318.0,"Objects":[{"StartTime":57318.0,"Position":57.0,"HyperDash":false}]},{"StartTime":57339.0,"Objects":[{"StartTime":57339.0,"Position":46.0,"HyperDash":false}]},{"StartTime":57361.0,"Objects":[{"StartTime":57361.0,"Position":35.0,"HyperDash":false}]},{"StartTime":57382.0,"Objects":[{"StartTime":57382.0,"Position":26.0,"HyperDash":false}]},{"StartTime":57404.0,"Objects":[{"StartTime":57404.0,"Position":19.0,"HyperDash":false}]},{"StartTime":57425.0,"Objects":[{"StartTime":57425.0,"Position":13.0,"HyperDash":false}]},{"StartTime":57447.0,"Objects":[{"StartTime":57447.0,"Position":8.0,"HyperDash":false}]},{"StartTime":57469.0,"Objects":[{"StartTime":57469.0,"Position":5.0,"HyperDash":false}]},{"StartTime":57490.0,"Objects":[{"StartTime":57490.0,"Position":3.0,"HyperDash":false}]},{"StartTime":57512.0,"Objects":[{"StartTime":57512.0,"Position":3.0,"HyperDash":false}]},{"StartTime":57533.0,"Objects":[{"StartTime":57533.0,"Position":5.0,"HyperDash":false}]},{"StartTime":57555.0,"Objects":[{"StartTime":57555.0,"Position":8.0,"HyperDash":false}]},{"StartTime":57576.0,"Objects":[{"StartTime":57576.0,"Position":12.0,"HyperDash":false}]},{"StartTime":57598.0,"Objects":[{"StartTime":57598.0,"Position":18.0,"HyperDash":false}]},{"StartTime":57619.0,"Objects":[{"StartTime":57619.0,"Position":26.0,"HyperDash":false}]},{"StartTime":57641.0,"Objects":[{"StartTime":57641.0,"Position":35.0,"HyperDash":false}]},{"StartTime":57662.0,"Objects":[{"StartTime":57662.0,"Position":45.0,"HyperDash":false}]},{"StartTime":57684.0,"Objects":[{"StartTime":57684.0,"Position":56.0,"HyperDash":false}]},{"StartTime":57706.0,"Objects":[{"StartTime":57706.0,"Position":69.0,"HyperDash":false}]},{"StartTime":57727.0,"Objects":[{"StartTime":57727.0,"Position":82.0,"HyperDash":false}]},{"StartTime":57749.0,"Objects":[{"StartTime":57749.0,"Position":96.0,"HyperDash":false}]},{"StartTime":57770.0,"Objects":[{"StartTime":57770.0,"Position":111.0,"HyperDash":false}]},{"StartTime":57792.0,"Objects":[{"StartTime":57792.0,"Position":126.0,"HyperDash":false}]},{"StartTime":57813.0,"Objects":[{"StartTime":57813.0,"Position":142.0,"HyperDash":false}]},{"StartTime":57835.0,"Objects":[{"StartTime":57835.0,"Position":158.0,"HyperDash":false}]},{"StartTime":57856.0,"Objects":[{"StartTime":57856.0,"Position":174.0,"HyperDash":true}]},{"StartTime":57900.0,"Objects":[{"StartTime":57900.0,"Position":312.0,"HyperDash":false},{"StartTime":57968.0,"Position":371.3721,"HyperDash":false},{"StartTime":58072.0,"Position":447.0,"HyperDash":false}]},{"StartTime":58158.0,"Objects":[{"StartTime":58158.0,"Position":392.0,"HyperDash":true}]},{"StartTime":58244.0,"Objects":[{"StartTime":58244.0,"Position":216.0,"HyperDash":false},{"StartTime":58330.0,"Position":159.75,"HyperDash":false}]},{"StartTime":58417.0,"Objects":[{"StartTime":58417.0,"Position":232.0,"HyperDash":false},{"StartTime":58503.0,"Position":175.75,"HyperDash":true}]},{"StartTime":58589.0,"Objects":[{"StartTime":58589.0,"Position":20.0,"HyperDash":false},{"StartTime":58657.0,"Position":49.581398,"HyperDash":false},{"StartTime":58761.0,"Position":110.0,"HyperDash":false}]},{"StartTime":58934.0,"Objects":[{"StartTime":58934.0,"Position":276.0,"HyperDash":false},{"StartTime":59002.0,"Position":233.41861,"HyperDash":false},{"StartTime":59106.0,"Position":186.0,"HyperDash":true}]},{"StartTime":59279.0,"Objects":[{"StartTime":59279.0,"Position":440.0,"HyperDash":false}]},{"StartTime":59322.0,"Objects":[{"StartTime":59322.0,"Position":466.0,"HyperDash":false}]},{"StartTime":59365.0,"Objects":[{"StartTime":59365.0,"Position":484.0,"HyperDash":false}]},{"StartTime":59408.0,"Objects":[{"StartTime":59408.0,"Position":491.0,"HyperDash":false}]},{"StartTime":59451.0,"Objects":[{"StartTime":59451.0,"Position":484.0,"HyperDash":false}]},{"StartTime":59537.0,"Objects":[{"StartTime":59537.0,"Position":428.0,"HyperDash":true}]},{"StartTime":59624.0,"Objects":[{"StartTime":59624.0,"Position":260.0,"HyperDash":false},{"StartTime":59710.0,"Position":215.0,"HyperDash":false},{"StartTime":59796.0,"Position":260.0,"HyperDash":true}]},{"StartTime":59969.0,"Objects":[{"StartTime":59969.0,"Position":494.0,"HyperDash":false},{"StartTime":60055.0,"Position":497.451355,"HyperDash":false}]},{"StartTime":60227.0,"Objects":[{"StartTime":60227.0,"Position":392.0,"HyperDash":true}]},{"StartTime":60313.0,"Objects":[{"StartTime":60313.0,"Position":212.0,"HyperDash":false}]},{"StartTime":60486.0,"Objects":[{"StartTime":60486.0,"Position":356.0,"HyperDash":true}]},{"StartTime":60658.0,"Objects":[{"StartTime":60658.0,"Position":104.0,"HyperDash":false},{"StartTime":60744.0,"Position":100.262955,"HyperDash":false}]},{"StartTime":60917.0,"Objects":[{"StartTime":60917.0,"Position":204.0,"HyperDash":true}]},{"StartTime":61003.0,"Objects":[{"StartTime":61003.0,"Position":384.0,"HyperDash":false},{"StartTime":61089.0,"Position":339.0,"HyperDash":true}]},{"StartTime":61175.0,"Objects":[{"StartTime":61175.0,"Position":159.0,"HyperDash":false},{"StartTime":61261.0,"Position":226.5,"HyperDash":true}]},{"StartTime":61348.0,"Objects":[{"StartTime":61348.0,"Position":72.0,"HyperDash":false}]},{"StartTime":61434.0,"Objects":[{"StartTime":61434.0,"Position":9.0,"HyperDash":false}]},{"StartTime":61520.0,"Objects":[{"StartTime":61520.0,"Position":9.0,"HyperDash":false}]},{"StartTime":61606.0,"Objects":[{"StartTime":61606.0,"Position":70.0,"HyperDash":true}]},{"StartTime":61693.0,"Objects":[{"StartTime":61693.0,"Position":250.0,"HyperDash":false},{"StartTime":61761.0,"Position":304.5814,"HyperDash":false},{"StartTime":61865.0,"Position":340.0,"HyperDash":false}]},{"StartTime":62037.0,"Objects":[{"StartTime":62037.0,"Position":184.0,"HyperDash":false},{"StartTime":62105.0,"Position":143.41861,"HyperDash":false},{"StartTime":62209.0,"Position":178.0,"HyperDash":false}]},{"StartTime":62296.0,"Objects":[{"StartTime":62296.0,"Position":232.0,"HyperDash":true}]},{"StartTime":62382.0,"Objects":[{"StartTime":62382.0,"Position":384.0,"HyperDash":true},{"StartTime":62439.0,"Position":294.225,"HyperDash":false},{"StartTime":62532.0,"Position":147.749985,"HyperDash":false}]},{"StartTime":62555.0,"Objects":[{"StartTime":62555.0,"Position":148.0,"HyperDash":false},{"StartTime":62623.0,"Position":216.3721,"HyperDash":false},{"StartTime":62727.0,"Position":283.0,"HyperDash":true}]},{"StartTime":62770.0,"Objects":[{"StartTime":62770.0,"Position":144.0,"HyperDash":false},{"StartTime":62899.0,"Position":76.5,"HyperDash":false}]},{"StartTime":62986.0,"Objects":[{"StartTime":62986.0,"Position":132.0,"HyperDash":true}]},{"StartTime":63072.0,"Objects":[{"StartTime":63072.0,"Position":300.0,"HyperDash":false},{"StartTime":63158.0,"Position":345.0,"HyperDash":true}]},{"StartTime":63244.0,"Objects":[{"StartTime":63244.0,"Position":184.0,"HyperDash":false},{"StartTime":63330.0,"Position":229.0,"HyperDash":true}]},{"StartTime":63417.0,"Objects":[{"StartTime":63417.0,"Position":64.0,"HyperDash":false},{"StartTime":63503.0,"Position":19.0,"HyperDash":true}]},{"StartTime":63589.0,"Objects":[{"StartTime":63589.0,"Position":184.0,"HyperDash":false},{"StartTime":63675.0,"Position":139.0,"HyperDash":true}]},{"StartTime":63762.0,"Objects":[{"StartTime":63762.0,"Position":345.0,"HyperDash":false}]},{"StartTime":63805.0,"Objects":[{"StartTime":63805.0,"Position":375.0,"HyperDash":false}]},{"StartTime":63848.0,"Objects":[{"StartTime":63848.0,"Position":400.0,"HyperDash":false}]},{"StartTime":63891.0,"Objects":[{"StartTime":63891.0,"Position":420.0,"HyperDash":false}]},{"StartTime":63934.0,"Objects":[{"StartTime":63934.0,"Position":432.0,"HyperDash":false}]},{"StartTime":63977.0,"Objects":[{"StartTime":63977.0,"Position":435.0,"HyperDash":false}]},{"StartTime":64020.0,"Objects":[{"StartTime":64020.0,"Position":428.0,"HyperDash":true}]},{"StartTime":64106.0,"Objects":[{"StartTime":64106.0,"Position":224.0,"HyperDash":true},{"StartTime":64174.0,"Position":296.9535,"HyperDash":false},{"StartTime":64278.0,"Position":449.0,"HyperDash":true}]},{"StartTime":64451.0,"Objects":[{"StartTime":64451.0,"Position":148.0,"HyperDash":true},{"StartTime":64519.0,"Position":253.953491,"HyperDash":false},{"StartTime":64623.0,"Position":373.0,"HyperDash":true}]},{"StartTime":64796.0,"Objects":[{"StartTime":64796.0,"Position":120.0,"HyperDash":true}]},{"StartTime":64911.0,"Objects":[{"StartTime":64911.0,"Position":324.0,"HyperDash":true}]},{"StartTime":65026.0,"Objects":[{"StartTime":65026.0,"Position":120.0,"HyperDash":true}]},{"StartTime":65141.0,"Objects":[{"StartTime":65141.0,"Position":336.0,"HyperDash":false}]},{"StartTime":65256.0,"Objects":[{"StartTime":65256.0,"Position":222.0,"HyperDash":false}]},{"StartTime":65371.0,"Objects":[{"StartTime":65371.0,"Position":108.0,"HyperDash":true}]},{"StartTime":65486.0,"Objects":[{"StartTime":65486.0,"Position":336.0,"HyperDash":false}]},{"StartTime":65601.0,"Objects":[{"StartTime":65601.0,"Position":444.0,"HyperDash":false}]},{"StartTime":65716.0,"Objects":[{"StartTime":65716.0,"Position":336.0,"HyperDash":true}]},{"StartTime":65831.0,"Objects":[{"StartTime":65831.0,"Position":144.0,"HyperDash":false}]},{"StartTime":65946.0,"Objects":[{"StartTime":65946.0,"Position":252.0,"HyperDash":false}]},{"StartTime":66060.0,"Objects":[{"StartTime":66060.0,"Position":144.0,"HyperDash":true}]},{"StartTime":66175.0,"Objects":[{"StartTime":66175.0,"Position":360.0,"HyperDash":false},{"StartTime":66243.0,"Position":398.5814,"HyperDash":false},{"StartTime":66347.0,"Position":450.0,"HyperDash":false}]},{"StartTime":66434.0,"Objects":[{"StartTime":66434.0,"Position":396.0,"HyperDash":true}]},{"StartTime":66520.0,"Objects":[{"StartTime":66520.0,"Position":224.0,"HyperDash":false}]},{"StartTime":66693.0,"Objects":[{"StartTime":66693.0,"Position":388.0,"HyperDash":true}]},{"StartTime":66865.0,"Objects":[{"StartTime":66865.0,"Position":124.0,"HyperDash":false},{"StartTime":66951.0,"Position":120.54866,"HyperDash":false}]},{"StartTime":67037.0,"Objects":[{"StartTime":67037.0,"Position":204.0,"HyperDash":false},{"StartTime":67123.0,"Position":200.54866,"HyperDash":true}]},{"StartTime":67210.0,"Objects":[{"StartTime":67210.0,"Position":368.0,"HyperDash":false}]},{"StartTime":67382.0,"Objects":[{"StartTime":67382.0,"Position":204.0,"HyperDash":true}]},{"StartTime":67555.0,"Objects":[{"StartTime":67555.0,"Position":476.0,"HyperDash":false}]},{"StartTime":67900.0,"Objects":[{"StartTime":67900.0,"Position":188.0,"HyperDash":false}]},{"StartTime":68244.0,"Objects":[{"StartTime":68244.0,"Position":488.0,"HyperDash":false}]},{"StartTime":68417.0,"Objects":[{"StartTime":68417.0,"Position":356.0,"HyperDash":false},{"StartTime":68503.0,"Position":423.5,"HyperDash":true}]},{"StartTime":68589.0,"Objects":[{"StartTime":68589.0,"Position":172.0,"HyperDash":false},{"StartTime":68657.0,"Position":166.454437,"HyperDash":false},{"StartTime":68761.0,"Position":168.090652,"HyperDash":true}]},{"StartTime":68934.0,"Objects":[{"StartTime":68934.0,"Position":484.0,"HyperDash":false}]},{"StartTime":69279.0,"Objects":[{"StartTime":69279.0,"Position":368.0,"HyperDash":false},{"StartTime":69343.0,"Position":52.0,"HyperDash":false},{"StartTime":69408.0,"Position":327.0,"HyperDash":false},{"StartTime":69472.0,"Position":226.0,"HyperDash":false},{"StartTime":69537.0,"Position":110.0,"HyperDash":false},{"StartTime":69602.0,"Position":3.0,"HyperDash":false},{"StartTime":69666.0,"Position":26.0,"HyperDash":false},{"StartTime":69731.0,"Position":173.0,"HyperDash":false},{"StartTime":69796.0,"Position":18.0,"HyperDash":false},{"StartTime":69860.0,"Position":310.0,"HyperDash":false},{"StartTime":69925.0,"Position":394.0,"HyperDash":false},{"StartTime":69990.0,"Position":406.0,"HyperDash":false},{"StartTime":70054.0,"Position":262.0,"HyperDash":false},{"StartTime":70119.0,"Position":278.0,"HyperDash":false},{"StartTime":70184.0,"Position":171.0,"HyperDash":false},{"StartTime":70248.0,"Position":22.0,"HyperDash":false},{"StartTime":70313.0,"Position":187.0,"HyperDash":false},{"StartTime":70378.0,"Position":124.0,"HyperDash":false},{"StartTime":70442.0,"Position":454.0,"HyperDash":false},{"StartTime":70507.0,"Position":16.0,"HyperDash":false},{"StartTime":70572.0,"Position":61.0,"HyperDash":false},{"StartTime":70636.0,"Position":161.0,"HyperDash":false},{"StartTime":70701.0,"Position":243.0,"HyperDash":false},{"StartTime":70766.0,"Position":375.0,"HyperDash":false},{"StartTime":70830.0,"Position":247.0,"HyperDash":false},{"StartTime":70895.0,"Position":162.0,"HyperDash":false},{"StartTime":70960.0,"Position":383.0,"HyperDash":false},{"StartTime":71024.0,"Position":127.0,"HyperDash":false},{"StartTime":71089.0,"Position":161.0,"HyperDash":false},{"StartTime":71154.0,"Position":332.0,"HyperDash":false},{"StartTime":71218.0,"Position":356.0,"HyperDash":false},{"StartTime":71283.0,"Position":362.0,"HyperDash":false},{"StartTime":71348.0,"Position":347.0,"HyperDash":false}]},{"StartTime":71693.0,"Objects":[{"StartTime":71693.0,"Position":232.0,"HyperDash":false},{"StartTime":71714.0,"Position":229.7937,"HyperDash":false},{"StartTime":71736.0,"Position":232.0,"HyperDash":false},{"StartTime":71757.0,"Position":229.7937,"HyperDash":false},{"StartTime":71779.0,"Position":232.0,"HyperDash":false},{"StartTime":71800.0,"Position":229.7937,"HyperDash":false},{"StartTime":71822.0,"Position":232.0,"HyperDash":false},{"StartTime":71843.0,"Position":229.7937,"HyperDash":false},{"StartTime":71865.0,"Position":232.0,"HyperDash":false},{"StartTime":71886.0,"Position":229.7937,"HyperDash":false},{"StartTime":71908.0,"Position":232.0,"HyperDash":false},{"StartTime":71930.0,"Position":229.7937,"HyperDash":false},{"StartTime":71951.0,"Position":232.0,"HyperDash":false},{"StartTime":71973.0,"Position":229.7937,"HyperDash":false},{"StartTime":71994.0,"Position":232.0,"HyperDash":false}]},{"StartTime":72037.0,"Objects":[{"StartTime":72037.0,"Position":272.0,"HyperDash":false},{"StartTime":72058.0,"Position":277.46347,"HyperDash":false},{"StartTime":72080.0,"Position":272.0,"HyperDash":false},{"StartTime":72101.0,"Position":277.46347,"HyperDash":false},{"StartTime":72123.0,"Position":272.0,"HyperDash":false},{"StartTime":72144.0,"Position":277.46347,"HyperDash":false},{"StartTime":72166.0,"Position":272.0,"HyperDash":false},{"StartTime":72187.0,"Position":277.46347,"HyperDash":false},{"StartTime":72209.0,"Position":272.0,"HyperDash":false},{"StartTime":72230.0,"Position":277.46347,"HyperDash":false},{"StartTime":72252.0,"Position":272.0,"HyperDash":false},{"StartTime":72274.0,"Position":277.46347,"HyperDash":false},{"StartTime":72295.0,"Position":272.0,"HyperDash":false},{"StartTime":72317.0,"Position":277.46347,"HyperDash":false},{"StartTime":72338.0,"Position":272.0,"HyperDash":false}]},{"StartTime":72382.0,"Objects":[{"StartTime":72382.0,"Position":316.0,"HyperDash":false},{"StartTime":72403.0,"Position":324.5015,"HyperDash":false},{"StartTime":72425.0,"Position":316.0,"HyperDash":false},{"StartTime":72446.0,"Position":324.5015,"HyperDash":false},{"StartTime":72468.0,"Position":316.0,"HyperDash":false},{"StartTime":72489.0,"Position":324.5015,"HyperDash":false},{"StartTime":72511.0,"Position":316.0,"HyperDash":false},{"StartTime":72532.0,"Position":324.5015,"HyperDash":false},{"StartTime":72554.0,"Position":316.0,"HyperDash":false},{"StartTime":72575.0,"Position":324.5015,"HyperDash":false},{"StartTime":72597.0,"Position":316.0,"HyperDash":false},{"StartTime":72619.0,"Position":324.5015,"HyperDash":false},{"StartTime":72640.0,"Position":316.0,"HyperDash":false},{"StartTime":72662.0,"Position":324.5015,"HyperDash":false},{"StartTime":72683.0,"Position":316.0,"HyperDash":false}]},{"StartTime":72727.0,"Objects":[{"StartTime":72727.0,"Position":360.0,"HyperDash":false},{"StartTime":72748.0,"Position":368.5015,"HyperDash":false},{"StartTime":72770.0,"Position":360.0,"HyperDash":false},{"StartTime":72791.0,"Position":368.5015,"HyperDash":false},{"StartTime":72813.0,"Position":360.0,"HyperDash":false},{"StartTime":72834.0,"Position":368.5015,"HyperDash":false},{"StartTime":72856.0,"Position":360.0,"HyperDash":false},{"StartTime":72877.0,"Position":368.5015,"HyperDash":false},{"StartTime":72899.0,"Position":360.0,"HyperDash":false},{"StartTime":72920.0,"Position":368.5015,"HyperDash":false},{"StartTime":72942.0,"Position":360.0,"HyperDash":false},{"StartTime":72964.0,"Position":368.5015,"HyperDash":false},{"StartTime":72985.0,"Position":360.0,"HyperDash":false},{"StartTime":73007.0,"Position":368.5015,"HyperDash":false},{"StartTime":73028.0,"Position":360.0,"HyperDash":true}]},{"StartTime":73072.0,"Objects":[{"StartTime":73072.0,"Position":256.0,"HyperDash":false}]},{"StartTime":73094.0,"Objects":[{"StartTime":73094.0,"Position":244.0,"HyperDash":false}]},{"StartTime":73115.0,"Objects":[{"StartTime":73115.0,"Position":233.0,"HyperDash":false}]},{"StartTime":73137.0,"Objects":[{"StartTime":73137.0,"Position":224.0,"HyperDash":false}]},{"StartTime":73158.0,"Objects":[{"StartTime":73158.0,"Position":215.0,"HyperDash":false}]},{"StartTime":73180.0,"Objects":[{"StartTime":73180.0,"Position":209.0,"HyperDash":false}]},{"StartTime":73201.0,"Objects":[{"StartTime":73201.0,"Position":205.0,"HyperDash":false}]},{"StartTime":73223.0,"Objects":[{"StartTime":73223.0,"Position":202.0,"HyperDash":false}]},{"StartTime":73244.0,"Objects":[{"StartTime":73244.0,"Position":203.0,"HyperDash":false}]},{"StartTime":73266.0,"Objects":[{"StartTime":73266.0,"Position":205.0,"HyperDash":false}]},{"StartTime":73287.0,"Objects":[{"StartTime":73287.0,"Position":210.0,"HyperDash":false}]},{"StartTime":73309.0,"Objects":[{"StartTime":73309.0,"Position":217.0,"HyperDash":false}]},{"StartTime":73331.0,"Objects":[{"StartTime":73331.0,"Position":226.0,"HyperDash":false}]},{"StartTime":73352.0,"Objects":[{"StartTime":73352.0,"Position":236.0,"HyperDash":false}]},{"StartTime":73374.0,"Objects":[{"StartTime":73374.0,"Position":247.0,"HyperDash":false}]},{"StartTime":73395.0,"Objects":[{"StartTime":73395.0,"Position":258.0,"HyperDash":false}]},{"StartTime":73417.0,"Objects":[{"StartTime":73417.0,"Position":270.0,"HyperDash":false}]},{"StartTime":73438.0,"Objects":[{"StartTime":73438.0,"Position":281.0,"HyperDash":false}]},{"StartTime":73460.0,"Objects":[{"StartTime":73460.0,"Position":291.0,"HyperDash":false}]},{"StartTime":73481.0,"Objects":[{"StartTime":73481.0,"Position":300.0,"HyperDash":false}]},{"StartTime":73503.0,"Objects":[{"StartTime":73503.0,"Position":307.0,"HyperDash":false}]},{"StartTime":73525.0,"Objects":[{"StartTime":73525.0,"Position":313.0,"HyperDash":false}]},{"StartTime":73546.0,"Objects":[{"StartTime":73546.0,"Position":316.0,"HyperDash":false}]},{"StartTime":73568.0,"Objects":[{"StartTime":73568.0,"Position":317.0,"HyperDash":false}]},{"StartTime":73589.0,"Objects":[{"StartTime":73589.0,"Position":315.0,"HyperDash":false}]},{"StartTime":73611.0,"Objects":[{"StartTime":73611.0,"Position":311.0,"HyperDash":false}]},{"StartTime":73632.0,"Objects":[{"StartTime":73632.0,"Position":305.0,"HyperDash":false}]},{"StartTime":73654.0,"Objects":[{"StartTime":73654.0,"Position":297.0,"HyperDash":false}]},{"StartTime":73675.0,"Objects":[{"StartTime":73675.0,"Position":288.0,"HyperDash":false}]},{"StartTime":73697.0,"Objects":[{"StartTime":73697.0,"Position":277.0,"HyperDash":false}]},{"StartTime":73719.0,"Objects":[{"StartTime":73719.0,"Position":266.0,"HyperDash":true}]},{"StartTime":73762.0,"Objects":[{"StartTime":73762.0,"Position":164.0,"HyperDash":false}]},{"StartTime":73783.0,"Objects":[{"StartTime":73783.0,"Position":153.0,"HyperDash":false}]},{"StartTime":73805.0,"Objects":[{"StartTime":73805.0,"Position":143.0,"HyperDash":false}]},{"StartTime":73826.0,"Objects":[{"StartTime":73826.0,"Position":133.0,"HyperDash":false}]},{"StartTime":73848.0,"Objects":[{"StartTime":73848.0,"Position":124.0,"HyperDash":false}]},{"StartTime":73869.0,"Objects":[{"StartTime":73869.0,"Position":115.0,"HyperDash":false}]},{"StartTime":73891.0,"Objects":[{"StartTime":73891.0,"Position":108.0,"HyperDash":false}]},{"StartTime":73912.0,"Objects":[{"StartTime":73912.0,"Position":101.0,"HyperDash":false}]},{"StartTime":73934.0,"Objects":[{"StartTime":73934.0,"Position":95.0,"HyperDash":false}]},{"StartTime":73956.0,"Objects":[{"StartTime":73956.0,"Position":90.0,"HyperDash":false}]},{"StartTime":73977.0,"Objects":[{"StartTime":73977.0,"Position":85.0,"HyperDash":false}]},{"StartTime":73999.0,"Objects":[{"StartTime":73999.0,"Position":82.0,"HyperDash":false}]},{"StartTime":74020.0,"Objects":[{"StartTime":74020.0,"Position":80.0,"HyperDash":false}]},{"StartTime":74042.0,"Objects":[{"StartTime":74042.0,"Position":79.0,"HyperDash":false}]},{"StartTime":74063.0,"Objects":[{"StartTime":74063.0,"Position":79.0,"HyperDash":true}]},{"StartTime":74106.0,"Objects":[{"StartTime":74106.0,"Position":180.0,"HyperDash":false}]},{"StartTime":74128.0,"Objects":[{"StartTime":74128.0,"Position":190.0,"HyperDash":false}]},{"StartTime":74150.0,"Objects":[{"StartTime":74150.0,"Position":200.0,"HyperDash":false}]},{"StartTime":74171.0,"Objects":[{"StartTime":74171.0,"Position":210.0,"HyperDash":false}]},{"StartTime":74193.0,"Objects":[{"StartTime":74193.0,"Position":219.0,"HyperDash":false}]},{"StartTime":74214.0,"Objects":[{"StartTime":74214.0,"Position":228.0,"HyperDash":false}]},{"StartTime":74236.0,"Objects":[{"StartTime":74236.0,"Position":235.0,"HyperDash":false}]},{"StartTime":74257.0,"Objects":[{"StartTime":74257.0,"Position":242.0,"HyperDash":false}]},{"StartTime":74279.0,"Objects":[{"StartTime":74279.0,"Position":248.0,"HyperDash":false}]},{"StartTime":74300.0,"Objects":[{"StartTime":74300.0,"Position":253.0,"HyperDash":false}]},{"StartTime":74322.0,"Objects":[{"StartTime":74322.0,"Position":258.0,"HyperDash":false}]},{"StartTime":74344.0,"Objects":[{"StartTime":74344.0,"Position":261.0,"HyperDash":false}]},{"StartTime":74365.0,"Objects":[{"StartTime":74365.0,"Position":263.0,"HyperDash":false}]},{"StartTime":74387.0,"Objects":[{"StartTime":74387.0,"Position":264.0,"HyperDash":false}]},{"StartTime":74408.0,"Objects":[{"StartTime":74408.0,"Position":264.0,"HyperDash":true}]},{"StartTime":74451.0,"Objects":[{"StartTime":74451.0,"Position":148.0,"HyperDash":false},{"StartTime":74519.0,"Position":111.4186,"HyperDash":false},{"StartTime":74623.0,"Position":58.0,"HyperDash":false}]},{"StartTime":74796.0,"Objects":[{"StartTime":74796.0,"Position":196.0,"HyperDash":false},{"StartTime":74864.0,"Position":187.840836,"HyperDash":false},{"StartTime":74968.0,"Position":193.068,"HyperDash":false}]},{"StartTime":75141.0,"Objects":[{"StartTime":75141.0,"Position":328.0,"HyperDash":false},{"StartTime":75209.0,"Position":324.84082,"HyperDash":false},{"StartTime":75313.0,"Position":325.068,"HyperDash":false}]},{"StartTime":75486.0,"Objects":[{"StartTime":75486.0,"Position":228.0,"HyperDash":false}]},{"StartTime":75658.0,"Objects":[{"StartTime":75658.0,"Position":396.0,"HyperDash":true}]},{"StartTime":75831.0,"Objects":[{"StartTime":75831.0,"Position":124.0,"HyperDash":false}]},{"StartTime":76003.0,"Objects":[{"StartTime":76003.0,"Position":36.0,"HyperDash":false}]},{"StartTime":76175.0,"Objects":[{"StartTime":76175.0,"Position":36.0,"HyperDash":false}]},{"StartTime":76348.0,"Objects":[{"StartTime":76348.0,"Position":124.0,"HyperDash":false}]},{"StartTime":76520.0,"Objects":[{"StartTime":76520.0,"Position":292.0,"HyperDash":false},{"StartTime":76588.0,"Position":308.15918,"HyperDash":false},{"StartTime":76692.0,"Position":294.932,"HyperDash":false}]},{"StartTime":76865.0,"Objects":[{"StartTime":76865.0,"Position":192.0,"HyperDash":false},{"StartTime":76933.0,"Position":210.48027,"HyperDash":false},{"StartTime":77037.0,"Position":195.744232,"HyperDash":false}]},{"StartTime":77210.0,"Objects":[{"StartTime":77210.0,"Position":368.0,"HyperDash":false},{"StartTime":77296.0,"Position":391.6784,"HyperDash":false},{"StartTime":77382.0,"Position":424.106964,"HyperDash":false},{"StartTime":77450.0,"Position":426.0137,"HyperDash":false},{"StartTime":77554.0,"Position":368.760162,"HyperDash":false}]},{"StartTime":77727.0,"Objects":[{"StartTime":77727.0,"Position":272.0,"HyperDash":false}]},{"StartTime":77900.0,"Objects":[{"StartTime":77900.0,"Position":176.0,"HyperDash":false},{"StartTime":77968.0,"Position":181.840836,"HyperDash":false},{"StartTime":78072.0,"Position":173.068,"HyperDash":false}]},{"StartTime":78244.0,"Objects":[{"StartTime":78244.0,"Position":272.0,"HyperDash":false}]},{"StartTime":78417.0,"Objects":[{"StartTime":78417.0,"Position":104.0,"HyperDash":true}]},{"StartTime":78589.0,"Objects":[{"StartTime":78589.0,"Position":380.0,"HyperDash":false},{"StartTime":78675.0,"Position":393.75,"HyperDash":false},{"StartTime":78761.0,"Position":447.5,"HyperDash":false},{"StartTime":78829.0,"Position":419.813965,"HyperDash":false},{"StartTime":78933.0,"Position":380.0,"HyperDash":false}]},{"StartTime":79106.0,"Objects":[{"StartTime":79106.0,"Position":284.0,"HyperDash":false}]},{"StartTime":79279.0,"Objects":[{"StartTime":79279.0,"Position":116.0,"HyperDash":false},{"StartTime":79347.0,"Position":99.84083,"HyperDash":false},{"StartTime":79451.0,"Position":113.067986,"HyperDash":false}]},{"StartTime":79624.0,"Objects":[{"StartTime":79624.0,"Position":216.0,"HyperDash":false},{"StartTime":79692.0,"Position":223.68605,"HyperDash":false},{"StartTime":79796.0,"Position":283.5,"HyperDash":false}]},{"StartTime":79882.0,"Objects":[{"StartTime":79882.0,"Position":324.0,"HyperDash":true}]},{"StartTime":79969.0,"Objects":[{"StartTime":79969.0,"Position":152.0,"HyperDash":false},{"StartTime":80055.0,"Position":111.0,"HyperDash":false},{"StartTime":80141.0,"Position":62.0,"HyperDash":false},{"StartTime":80209.0,"Position":99.58139,"HyperDash":false},{"StartTime":80313.0,"Position":152.0,"HyperDash":false}]},{"StartTime":80486.0,"Objects":[{"StartTime":80486.0,"Position":248.0,"HyperDash":false}]},{"StartTime":80658.0,"Objects":[{"StartTime":80658.0,"Position":416.0,"HyperDash":false},{"StartTime":80726.0,"Position":434.4803,"HyperDash":false},{"StartTime":80830.0,"Position":419.744232,"HyperDash":false}]},{"StartTime":81003.0,"Objects":[{"StartTime":81003.0,"Position":324.0,"HyperDash":false},{"StartTime":81071.0,"Position":308.313965,"HyperDash":false},{"StartTime":81175.0,"Position":256.5,"HyperDash":false}]},{"StartTime":81262.0,"Objects":[{"StartTime":81262.0,"Position":208.0,"HyperDash":true}]},{"StartTime":81348.0,"Objects":[{"StartTime":81348.0,"Position":384.0,"HyperDash":false},{"StartTime":81434.0,"Position":431.0,"HyperDash":false},{"StartTime":81520.0,"Position":474.0,"HyperDash":false},{"StartTime":81588.0,"Position":446.4186,"HyperDash":false},{"StartTime":81692.0,"Position":384.0,"HyperDash":false}]},{"StartTime":81865.0,"Objects":[{"StartTime":81865.0,"Position":212.0,"HyperDash":true}]},{"StartTime":82037.0,"Objects":[{"StartTime":82037.0,"Position":444.0,"HyperDash":false},{"StartTime":82105.0,"Position":438.4026,"HyperDash":false},{"StartTime":82209.0,"Position":447.547729,"HyperDash":true}]},{"StartTime":82382.0,"Objects":[{"StartTime":82382.0,"Position":212.0,"HyperDash":false}]},{"StartTime":82469.0,"Objects":[{"StartTime":82469.0,"Position":172.0,"HyperDash":false}]},{"StartTime":82555.0,"Objects":[{"StartTime":82555.0,"Position":132.0,"HyperDash":true}]},{"StartTime":82727.0,"Objects":[{"StartTime":82727.0,"Position":432.0,"HyperDash":false},{"StartTime":82813.0,"Position":480.699646,"HyperDash":false},{"StartTime":82899.0,"Position":500.151184,"HyperDash":false},{"StartTime":82967.0,"Position":468.371918,"HyperDash":false},{"StartTime":83071.0,"Position":432.553162,"HyperDash":false}]},{"StartTime":83244.0,"Objects":[{"StartTime":83244.0,"Position":272.0,"HyperDash":false}]},{"StartTime":83417.0,"Objects":[{"StartTime":83417.0,"Position":440.0,"HyperDash":false},{"StartTime":83485.0,"Position":421.4803,"HyperDash":false},{"StartTime":83589.0,"Position":443.744232,"HyperDash":true}]},{"StartTime":83762.0,"Objects":[{"StartTime":83762.0,"Position":200.0,"HyperDash":false}]},{"StartTime":83934.0,"Objects":[{"StartTime":83934.0,"Position":352.0,"HyperDash":true}]},{"StartTime":84106.0,"Objects":[{"StartTime":84106.0,"Position":104.0,"HyperDash":false},{"StartTime":84192.0,"Position":62.0,"HyperDash":false},{"StartTime":84278.0,"Position":14.0,"HyperDash":false},{"StartTime":84346.0,"Position":49.58139,"HyperDash":false},{"StartTime":84450.0,"Position":104.0,"HyperDash":false}]},{"StartTime":84624.0,"Objects":[{"StartTime":84624.0,"Position":272.0,"HyperDash":false}]},{"StartTime":84796.0,"Objects":[{"StartTime":84796.0,"Position":112.0,"HyperDash":false},{"StartTime":84864.0,"Position":120.667366,"HyperDash":false},{"StartTime":84968.0,"Position":108.629211,"HyperDash":false}]},{"StartTime":85055.0,"Objects":[{"StartTime":85055.0,"Position":164.0,"HyperDash":false}]},{"StartTime":85141.0,"Objects":[{"StartTime":85141.0,"Position":216.0,"HyperDash":false},{"StartTime":85209.0,"Position":224.68605,"HyperDash":false},{"StartTime":85313.0,"Position":283.5,"HyperDash":true}]},{"StartTime":85486.0,"Objects":[{"StartTime":85486.0,"Position":32.0,"HyperDash":false},{"StartTime":85572.0,"Position":0.299288034,"HyperDash":false},{"StartTime":85658.0,"Position":4.98187447,"HyperDash":false},{"StartTime":85744.0,"Position":36.15001,"HyperDash":false}]},{"StartTime":85831.0,"Objects":[{"StartTime":85831.0,"Position":108.0,"HyperDash":false},{"StartTime":85899.0,"Position":145.58139,"HyperDash":false},{"StartTime":86003.0,"Position":198.0,"HyperDash":false}]},{"StartTime":86175.0,"Objects":[{"StartTime":86175.0,"Position":20.0,"HyperDash":false}]},{"StartTime":86348.0,"Objects":[{"StartTime":86348.0,"Position":128.0,"HyperDash":false},{"StartTime":86434.0,"Position":173.0,"HyperDash":true}]},{"StartTime":86520.0,"Objects":[{"StartTime":86520.0,"Position":344.0,"HyperDash":false},{"StartTime":86588.0,"Position":325.4186,"HyperDash":false},{"StartTime":86692.0,"Position":254.0,"HyperDash":false}]},{"StartTime":86865.0,"Objects":[{"StartTime":86865.0,"Position":436.0,"HyperDash":false},{"StartTime":86933.0,"Position":448.3675,"HyperDash":false},{"StartTime":87037.0,"Position":439.458984,"HyperDash":false}]},{"StartTime":87124.0,"Objects":[{"StartTime":87124.0,"Position":375.0,"HyperDash":false}]},{"StartTime":87210.0,"Objects":[{"StartTime":87210.0,"Position":312.0,"HyperDash":false}]},{"StartTime":87382.0,"Objects":[{"StartTime":87382.0,"Position":472.0,"HyperDash":false}]},{"StartTime":87555.0,"Objects":[{"StartTime":87555.0,"Position":300.0,"HyperDash":false},{"StartTime":87623.0,"Position":293.518738,"HyperDash":false},{"StartTime":87727.0,"Position":296.253265,"HyperDash":false}]},{"StartTime":87813.0,"Objects":[{"StartTime":87813.0,"Position":360.0,"HyperDash":true}]},{"StartTime":87900.0,"Objects":[{"StartTime":87900.0,"Position":196.0,"HyperDash":false},{"StartTime":87968.0,"Position":147.41861,"HyperDash":false},{"StartTime":88072.0,"Position":106.0,"HyperDash":false}]},{"StartTime":88244.0,"Objects":[{"StartTime":88244.0,"Position":276.0,"HyperDash":false},{"StartTime":88312.0,"Position":320.5814,"HyperDash":false},{"StartTime":88416.0,"Position":366.0,"HyperDash":false}]},{"StartTime":88503.0,"Objects":[{"StartTime":88503.0,"Position":312.0,"HyperDash":false}]},{"StartTime":88589.0,"Objects":[{"StartTime":88589.0,"Position":260.0,"HyperDash":false}]},{"StartTime":88762.0,"Objects":[{"StartTime":88762.0,"Position":440.0,"HyperDash":true}]},{"StartTime":88934.0,"Objects":[{"StartTime":88934.0,"Position":192.0,"HyperDash":false},{"StartTime":89002.0,"Position":158.41861,"HyperDash":false},{"StartTime":89106.0,"Position":102.0,"HyperDash":false}]},{"StartTime":89193.0,"Objects":[{"StartTime":89193.0,"Position":164.0,"HyperDash":false}]},{"StartTime":89279.0,"Objects":[{"StartTime":89279.0,"Position":228.0,"HyperDash":false},{"StartTime":89347.0,"Position":188.41861,"HyperDash":false},{"StartTime":89451.0,"Position":138.0,"HyperDash":false}]},{"StartTime":89624.0,"Objects":[{"StartTime":89624.0,"Position":306.0,"HyperDash":false},{"StartTime":89692.0,"Position":334.5814,"HyperDash":false},{"StartTime":89796.0,"Position":396.0,"HyperDash":false}]},{"StartTime":89882.0,"Objects":[{"StartTime":89882.0,"Position":450.0,"HyperDash":false}]},{"StartTime":89969.0,"Objects":[{"StartTime":89969.0,"Position":396.0,"HyperDash":false}]},{"StartTime":90141.0,"Objects":[{"StartTime":90141.0,"Position":228.0,"HyperDash":false}]},{"StartTime":90313.0,"Objects":[{"StartTime":90313.0,"Position":396.0,"HyperDash":false},{"StartTime":90381.0,"Position":408.481262,"HyperDash":false},{"StartTime":90485.0,"Position":399.746735,"HyperDash":false}]},{"StartTime":90572.0,"Objects":[{"StartTime":90572.0,"Position":332.0,"HyperDash":false}]},{"StartTime":90658.0,"Objects":[{"StartTime":90658.0,"Position":264.0,"HyperDash":false},{"StartTime":90726.0,"Position":289.5814,"HyperDash":false},{"StartTime":90830.0,"Position":354.0,"HyperDash":false}]},{"StartTime":91003.0,"Objects":[{"StartTime":91003.0,"Position":184.0,"HyperDash":false},{"StartTime":91071.0,"Position":167.41861,"HyperDash":false},{"StartTime":91175.0,"Position":94.0,"HyperDash":false}]},{"StartTime":91262.0,"Objects":[{"StartTime":91262.0,"Position":148.0,"HyperDash":false}]},{"StartTime":91348.0,"Objects":[{"StartTime":91348.0,"Position":200.0,"HyperDash":false}]},{"StartTime":91520.0,"Objects":[{"StartTime":91520.0,"Position":32.0,"HyperDash":true}]},{"StartTime":91693.0,"Objects":[{"StartTime":91693.0,"Position":296.0,"HyperDash":false},{"StartTime":91761.0,"Position":318.5814,"HyperDash":false},{"StartTime":91865.0,"Position":302.0,"HyperDash":false}]},{"StartTime":91951.0,"Objects":[{"StartTime":91951.0,"Position":240.0,"HyperDash":false}]},{"StartTime":92037.0,"Objects":[{"StartTime":92037.0,"Position":136.0,"HyperDash":false},{"StartTime":92123.0,"Position":133.503845,"HyperDash":false}]},{"StartTime":92210.0,"Objects":[{"StartTime":92210.0,"Position":196.0,"HyperDash":false},{"StartTime":92296.0,"Position":199.206116,"HyperDash":true}]},{"StartTime":92382.0,"Objects":[{"StartTime":92382.0,"Position":48.0,"HyperDash":false},{"StartTime":92450.0,"Position":10.418602,"HyperDash":false},{"StartTime":92554.0,"Position":50.0,"HyperDash":false}]},{"StartTime":92641.0,"Objects":[{"StartTime":92641.0,"Position":120.0,"HyperDash":false}]},{"StartTime":92727.0,"Objects":[{"StartTime":92727.0,"Position":188.0,"HyperDash":false}]},{"StartTime":92900.0,"Objects":[{"StartTime":92900.0,"Position":360.0,"HyperDash":true}]},{"StartTime":93072.0,"Objects":[{"StartTime":93072.0,"Position":123.0,"HyperDash":false},{"StartTime":93140.0,"Position":135.518738,"HyperDash":false},{"StartTime":93244.0,"Position":119.25325,"HyperDash":false}]},{"StartTime":93331.0,"Objects":[{"StartTime":93331.0,"Position":188.0,"HyperDash":true}]},{"StartTime":93417.0,"Objects":[{"StartTime":93417.0,"Position":368.0,"HyperDash":false},{"StartTime":93503.0,"Position":413.0,"HyperDash":false},{"StartTime":93589.0,"Position":368.0,"HyperDash":true}]},{"StartTime":93762.0,"Objects":[{"StartTime":93762.0,"Position":96.0,"HyperDash":false}]},{"StartTime":93848.0,"Objects":[{"StartTime":93848.0,"Position":53.0,"HyperDash":false}]},{"StartTime":93934.0,"Objects":[{"StartTime":93934.0,"Position":45.0,"HyperDash":false}]},{"StartTime":94020.0,"Objects":[{"StartTime":94020.0,"Position":75.0,"HyperDash":false}]},{"StartTime":94106.0,"Objects":[{"StartTime":94106.0,"Position":128.0,"HyperDash":false}]},{"StartTime":94279.0,"Objects":[{"StartTime":94279.0,"Position":316.0,"HyperDash":true}]},{"StartTime":94451.0,"Objects":[{"StartTime":94451.0,"Position":48.0,"HyperDash":false},{"StartTime":94519.0,"Position":51.57788,"HyperDash":false},{"StartTime":94623.0,"Position":44.4028778,"HyperDash":false}]},{"StartTime":94710.0,"Objects":[{"StartTime":94710.0,"Position":112.0,"HyperDash":true}]},{"StartTime":94796.0,"Objects":[{"StartTime":94796.0,"Position":300.0,"HyperDash":false}]},{"StartTime":94969.0,"Objects":[{"StartTime":94969.0,"Position":416.0,"HyperDash":false},{"StartTime":95055.0,"Position":371.0,"HyperDash":true}]},{"StartTime":95141.0,"Objects":[{"StartTime":95141.0,"Position":180.0,"HyperDash":false}]},{"StartTime":95227.0,"Objects":[{"StartTime":95227.0,"Position":128.0,"HyperDash":false}]},{"StartTime":95313.0,"Objects":[{"StartTime":95313.0,"Position":76.0,"HyperDash":false}]},{"StartTime":95486.0,"Objects":[{"StartTime":95486.0,"Position":248.0,"HyperDash":false}]},{"StartTime":95658.0,"Objects":[{"StartTime":95658.0,"Position":68.0,"HyperDash":true}]},{"StartTime":95831.0,"Objects":[{"StartTime":95831.0,"Position":348.0,"HyperDash":false},{"StartTime":95899.0,"Position":373.5814,"HyperDash":false},{"StartTime":96003.0,"Position":438.0,"HyperDash":true}]},{"StartTime":96175.0,"Objects":[{"StartTime":96175.0,"Position":176.0,"HyperDash":false},{"StartTime":96261.0,"Position":102.839455,"HyperDash":false},{"StartTime":96347.0,"Position":70.72701,"HyperDash":false},{"StartTime":96433.0,"Position":95.68428,"HyperDash":false},{"StartTime":96519.0,"Position":200.009659,"HyperDash":false},{"StartTime":96605.0,"Position":276.00058,"HyperDash":false},{"StartTime":96692.0,"Position":280.8676,"HyperDash":false},{"StartTime":96821.0,"Position":179.5454,"HyperDash":false}]},{"StartTime":96865.0,"Objects":[{"StartTime":96865.0,"Position":156.0,"HyperDash":false},{"StartTime":96951.0,"Position":90.61737,"HyperDash":false},{"StartTime":97037.0,"Position":78.41168,"HyperDash":false},{"StartTime":97123.0,"Position":117.060234,"HyperDash":false},{"StartTime":97209.0,"Position":173.374588,"HyperDash":false},{"StartTime":97295.0,"Position":216.741837,"HyperDash":false},{"StartTime":97382.0,"Position":244.734863,"HyperDash":false},{"StartTime":97511.0,"Position":177.973221,"HyperDash":false}]},{"StartTime":97555.0,"Objects":[{"StartTime":97555.0,"Position":144.0,"HyperDash":false},{"StartTime":97641.0,"Position":112.369415,"HyperDash":false},{"StartTime":97727.0,"Position":88.02037,"HyperDash":false},{"StartTime":97813.0,"Position":103.779022,"HyperDash":false},{"StartTime":97899.0,"Position":143.185013,"HyperDash":false},{"StartTime":97985.0,"Position":200.21698,"HyperDash":false},{"StartTime":98072.0,"Position":207.316086,"HyperDash":false},{"StartTime":98140.0,"Position":203.132828,"HyperDash":false},{"StartTime":98244.0,"Position":161.018463,"HyperDash":false}]},{"StartTime":99279.0,"Objects":[{"StartTime":99279.0,"Position":164.0,"HyperDash":false}]},{"StartTime":99451.0,"Objects":[{"StartTime":99451.0,"Position":324.0,"HyperDash":false},{"StartTime":99494.0,"Position":333.138123,"HyperDash":false},{"StartTime":99537.0,"Position":324.0,"HyperDash":false},{"StartTime":99580.0,"Position":333.138123,"HyperDash":true}]},{"StartTime":99624.0,"Objects":[{"StartTime":99624.0,"Position":204.0,"HyperDash":false},{"StartTime":99692.0,"Position":148.6279,"HyperDash":false},{"StartTime":99796.0,"Position":69.0,"HyperDash":true}]},{"StartTime":99969.0,"Objects":[{"StartTime":99969.0,"Position":340.0,"HyperDash":false},{"StartTime":100037.0,"Position":303.6279,"HyperDash":false},{"StartTime":100141.0,"Position":205.0,"HyperDash":true}]},{"StartTime":100313.0,"Objects":[{"StartTime":100313.0,"Position":472.0,"HyperDash":true}]},{"StartTime":100658.0,"Objects":[{"StartTime":100658.0,"Position":64.0,"HyperDash":false},{"StartTime":100744.0,"Position":10.0,"HyperDash":false},{"StartTime":100830.0,"Position":64.0,"HyperDash":true}]},{"StartTime":101003.0,"Objects":[{"StartTime":101003.0,"Position":336.0,"HyperDash":false}]},{"StartTime":101175.0,"Objects":[{"StartTime":101175.0,"Position":176.0,"HyperDash":true}]},{"StartTime":101348.0,"Objects":[{"StartTime":101348.0,"Position":448.0,"HyperDash":false},{"StartTime":101416.0,"Position":502.697662,"HyperDash":false},{"StartTime":101520.0,"Position":444.0,"HyperDash":false}]},{"StartTime":101606.0,"Objects":[{"StartTime":101606.0,"Position":384.0,"HyperDash":true}]},{"StartTime":101693.0,"Objects":[{"StartTime":101693.0,"Position":220.0,"HyperDash":false},{"StartTime":101761.0,"Position":270.697662,"HyperDash":false},{"StartTime":101865.0,"Position":328.0,"HyperDash":false}]},{"StartTime":101951.0,"Objects":[{"StartTime":101951.0,"Position":264.0,"HyperDash":true}]},{"StartTime":102037.0,"Objects":[{"StartTime":102037.0,"Position":112.0,"HyperDash":false}]},{"StartTime":102124.0,"Objects":[{"StartTime":102124.0,"Position":56.0,"HyperDash":false}]},{"StartTime":102210.0,"Objects":[{"StartTime":102210.0,"Position":56.0,"HyperDash":true}]},{"StartTime":102382.0,"Objects":[{"StartTime":102382.0,"Position":344.0,"HyperDash":true}]},{"StartTime":102555.0,"Objects":[{"StartTime":102555.0,"Position":56.0,"HyperDash":true}]},{"StartTime":102727.0,"Objects":[{"StartTime":102727.0,"Position":368.0,"HyperDash":false},{"StartTime":102795.0,"Position":407.851135,"HyperDash":false},{"StartTime":102899.0,"Position":390.870453,"HyperDash":false}]},{"StartTime":102986.0,"Objects":[{"StartTime":102986.0,"Position":332.0,"HyperDash":true}]},{"StartTime":103072.0,"Objects":[{"StartTime":103072.0,"Position":168.0,"HyperDash":false},{"StartTime":103140.0,"Position":110.302322,"HyperDash":false},{"StartTime":103244.0,"Position":60.0,"HyperDash":false}]},{"StartTime":103331.0,"Objects":[{"StartTime":103331.0,"Position":120.0,"HyperDash":true}]},{"StartTime":103417.0,"Objects":[{"StartTime":103417.0,"Position":304.0,"HyperDash":false}]},{"StartTime":103503.0,"Objects":[{"StartTime":103503.0,"Position":364.0,"HyperDash":false}]},{"StartTime":103589.0,"Objects":[{"StartTime":103589.0,"Position":424.0,"HyperDash":true}]},{"StartTime":103762.0,"Objects":[{"StartTime":103762.0,"Position":152.0,"HyperDash":false}]},{"StartTime":103934.0,"Objects":[{"StartTime":103934.0,"Position":316.0,"HyperDash":true}]},{"StartTime":104106.0,"Objects":[{"StartTime":104106.0,"Position":56.0,"HyperDash":false},{"StartTime":104174.0,"Position":18.3023262,"HyperDash":false},{"StartTime":104278.0,"Position":59.9999962,"HyperDash":false}]},{"StartTime":104365.0,"Objects":[{"StartTime":104365.0,"Position":116.0,"HyperDash":true}]},{"StartTime":104451.0,"Objects":[{"StartTime":104451.0,"Position":304.0,"HyperDash":false},{"StartTime":104519.0,"Position":357.697662,"HyperDash":false},{"StartTime":104623.0,"Position":412.0,"HyperDash":false}]},{"StartTime":104710.0,"Objects":[{"StartTime":104710.0,"Position":356.0,"HyperDash":true}]},{"StartTime":104796.0,"Objects":[{"StartTime":104796.0,"Position":168.0,"HyperDash":false},{"StartTime":104882.0,"Position":114.0,"HyperDash":false},{"StartTime":104968.0,"Position":168.0,"HyperDash":true}]},{"StartTime":105141.0,"Objects":[{"StartTime":105141.0,"Position":440.0,"HyperDash":true}]},{"StartTime":105313.0,"Objects":[{"StartTime":105313.0,"Position":144.0,"HyperDash":true}]},{"StartTime":105486.0,"Objects":[{"StartTime":105486.0,"Position":468.0,"HyperDash":false},{"StartTime":105554.0,"Position":451.0,"HyperDash":false},{"StartTime":105658.0,"Position":412.0,"HyperDash":false}]},{"StartTime":105744.0,"Objects":[{"StartTime":105744.0,"Position":360.0,"HyperDash":true}]},{"StartTime":105831.0,"Objects":[{"StartTime":105831.0,"Position":164.0,"HyperDash":false},{"StartTime":105899.0,"Position":224.697678,"HyperDash":false},{"StartTime":106003.0,"Position":272.0,"HyperDash":false}]},{"StartTime":106089.0,"Objects":[{"StartTime":106089.0,"Position":212.0,"HyperDash":true}]},{"StartTime":106175.0,"Objects":[{"StartTime":106175.0,"Position":24.0,"HyperDash":false}]},{"StartTime":106262.0,"Objects":[{"StartTime":106262.0,"Position":20.0,"HyperDash":false}]},{"StartTime":106348.0,"Objects":[{"StartTime":106348.0,"Position":16.0,"HyperDash":true}]},{"StartTime":106520.0,"Objects":[{"StartTime":106520.0,"Position":296.0,"HyperDash":false}]},{"StartTime":106693.0,"Objects":[{"StartTime":106693.0,"Position":132.0,"HyperDash":true}]},{"StartTime":106865.0,"Objects":[{"StartTime":106865.0,"Position":400.0,"HyperDash":false},{"StartTime":106933.0,"Position":443.234375,"HyperDash":false},{"StartTime":107037.0,"Position":447.13623,"HyperDash":false}]},{"StartTime":107124.0,"Objects":[{"StartTime":107124.0,"Position":388.0,"HyperDash":true}]},{"StartTime":107210.0,"Objects":[{"StartTime":107210.0,"Position":196.0,"HyperDash":false},{"StartTime":107278.0,"Position":142.302322,"HyperDash":false},{"StartTime":107382.0,"Position":88.0,"HyperDash":false}]},{"StartTime":107469.0,"Objects":[{"StartTime":107469.0,"Position":148.0,"HyperDash":true}]},{"StartTime":107555.0,"Objects":[{"StartTime":107555.0,"Position":304.0,"HyperDash":false}]},{"StartTime":107641.0,"Objects":[{"StartTime":107641.0,"Position":358.0,"HyperDash":false}]},{"StartTime":107727.0,"Objects":[{"StartTime":107727.0,"Position":412.0,"HyperDash":true}]},{"StartTime":107900.0,"Objects":[{"StartTime":107900.0,"Position":136.0,"HyperDash":true}]},{"StartTime":108072.0,"Objects":[{"StartTime":108072.0,"Position":432.0,"HyperDash":true}]},{"StartTime":108244.0,"Objects":[{"StartTime":108244.0,"Position":160.0,"HyperDash":false},{"StartTime":108312.0,"Position":129.302322,"HyperDash":false},{"StartTime":108416.0,"Position":52.0,"HyperDash":false}]},{"StartTime":108503.0,"Objects":[{"StartTime":108503.0,"Position":112.0,"HyperDash":true}]},{"StartTime":108589.0,"Objects":[{"StartTime":108589.0,"Position":300.0,"HyperDash":false},{"StartTime":108657.0,"Position":249.302338,"HyperDash":false},{"StartTime":108761.0,"Position":192.0,"HyperDash":false}]},{"StartTime":108848.0,"Objects":[{"StartTime":108848.0,"Position":248.0,"HyperDash":true}]},{"StartTime":108934.0,"Objects":[{"StartTime":108934.0,"Position":436.0,"HyperDash":false},{"StartTime":109020.0,"Position":490.0,"HyperDash":false},{"StartTime":109106.0,"Position":436.0,"HyperDash":true}]},{"StartTime":109279.0,"Objects":[{"StartTime":109279.0,"Position":164.0,"HyperDash":false}]},{"StartTime":109451.0,"Objects":[{"StartTime":109451.0,"Position":324.0,"HyperDash":true}]},{"StartTime":109624.0,"Objects":[{"StartTime":109624.0,"Position":52.0,"HyperDash":false},{"StartTime":109692.0,"Position":35.4452477,"HyperDash":false},{"StartTime":109796.0,"Position":52.0779152,"HyperDash":false}]},{"StartTime":109882.0,"Objects":[{"StartTime":109882.0,"Position":112.0,"HyperDash":true}]},{"StartTime":109969.0,"Objects":[{"StartTime":109969.0,"Position":316.0,"HyperDash":false},{"StartTime":110037.0,"Position":270.302338,"HyperDash":false},{"StartTime":110141.0,"Position":208.0,"HyperDash":false}]},{"StartTime":110227.0,"Objects":[{"StartTime":110227.0,"Position":268.0,"HyperDash":true}]},{"StartTime":110313.0,"Objects":[{"StartTime":110313.0,"Position":456.0,"HyperDash":false},{"StartTime":110381.0,"Position":440.422455,"HyperDash":false},{"StartTime":110485.0,"Position":459.598,"HyperDash":false}]},{"StartTime":110658.0,"Objects":[{"StartTime":110658.0,"Position":292.0,"HyperDash":false},{"StartTime":110726.0,"Position":273.422455,"HyperDash":false},{"StartTime":110830.0,"Position":295.598,"HyperDash":true}]},{"StartTime":111003.0,"Objects":[{"StartTime":111003.0,"Position":32.0,"HyperDash":false}]},{"StartTime":111118.0,"Objects":[{"StartTime":111118.0,"Position":140.0,"HyperDash":false}]},{"StartTime":111233.0,"Objects":[{"StartTime":111233.0,"Position":248.0,"HyperDash":true}]},{"StartTime":111348.0,"Objects":[{"StartTime":111348.0,"Position":44.0,"HyperDash":false},{"StartTime":111405.0,"Position":62.84279,"HyperDash":false},{"StartTime":111462.0,"Position":116.0,"HyperDash":false},{"StartTime":111577.0,"Position":44.0,"HyperDash":true}]},{"StartTime":111693.0,"Objects":[{"StartTime":111693.0,"Position":320.0,"HyperDash":false}]},{"StartTime":111779.0,"Objects":[{"StartTime":111779.0,"Position":392.0,"HyperDash":false}]},{"StartTime":111865.0,"Objects":[{"StartTime":111865.0,"Position":464.0,"HyperDash":true}]},{"StartTime":112037.0,"Objects":[{"StartTime":112037.0,"Position":196.0,"HyperDash":false}]},{"StartTime":112210.0,"Objects":[{"StartTime":112210.0,"Position":364.0,"HyperDash":true}]},{"StartTime":112382.0,"Objects":[{"StartTime":112382.0,"Position":92.0,"HyperDash":false},{"StartTime":112450.0,"Position":150.697678,"HyperDash":false},{"StartTime":112554.0,"Position":200.0,"HyperDash":false}]},{"StartTime":112641.0,"Objects":[{"StartTime":112641.0,"Position":140.0,"HyperDash":true}]},{"StartTime":112727.0,"Objects":[{"StartTime":112727.0,"Position":356.0,"HyperDash":false},{"StartTime":112795.0,"Position":379.697662,"HyperDash":false},{"StartTime":112899.0,"Position":352.0,"HyperDash":false}]},{"StartTime":112986.0,"Objects":[{"StartTime":112986.0,"Position":292.0,"HyperDash":true}]},{"StartTime":113072.0,"Objects":[{"StartTime":113072.0,"Position":96.0,"HyperDash":false}]},{"StartTime":113158.0,"Objects":[{"StartTime":113158.0,"Position":36.0,"HyperDash":false}]},{"StartTime":113244.0,"Objects":[{"StartTime":113244.0,"Position":96.0,"HyperDash":true}]},{"StartTime":113417.0,"Objects":[{"StartTime":113417.0,"Position":368.0,"HyperDash":true}]},{"StartTime":113589.0,"Objects":[{"StartTime":113589.0,"Position":72.0,"HyperDash":true}]},{"StartTime":113762.0,"Objects":[{"StartTime":113762.0,"Position":364.0,"HyperDash":false},{"StartTime":113830.0,"Position":340.302338,"HyperDash":false},{"StartTime":113934.0,"Position":256.0,"HyperDash":false}]},{"StartTime":114020.0,"Objects":[{"StartTime":114020.0,"Position":316.0,"HyperDash":true}]},{"StartTime":114106.0,"Objects":[{"StartTime":114106.0,"Position":120.0,"HyperDash":false},{"StartTime":114174.0,"Position":143.697678,"HyperDash":false},{"StartTime":114278.0,"Position":228.0,"HyperDash":false}]},{"StartTime":114365.0,"Objects":[{"StartTime":114365.0,"Position":168.0,"HyperDash":true}]},{"StartTime":114451.0,"Objects":[{"StartTime":114451.0,"Position":384.0,"HyperDash":false}]},{"StartTime":114537.0,"Objects":[{"StartTime":114537.0,"Position":444.0,"HyperDash":false}]},{"StartTime":114624.0,"Objects":[{"StartTime":114624.0,"Position":444.0,"HyperDash":true}]},{"StartTime":114796.0,"Objects":[{"StartTime":114796.0,"Position":176.0,"HyperDash":false}]},{"StartTime":114969.0,"Objects":[{"StartTime":114969.0,"Position":344.0,"HyperDash":true}]},{"StartTime":115141.0,"Objects":[{"StartTime":115141.0,"Position":76.0,"HyperDash":false},{"StartTime":115209.0,"Position":33.3023262,"HyperDash":false},{"StartTime":115313.0,"Position":20.0,"HyperDash":false}]},{"StartTime":115400.0,"Objects":[{"StartTime":115400.0,"Position":80.0,"HyperDash":true}]},{"StartTime":115486.0,"Objects":[{"StartTime":115486.0,"Position":284.0,"HyperDash":false},{"StartTime":115554.0,"Position":236.302322,"HyperDash":false},{"StartTime":115658.0,"Position":176.0,"HyperDash":false}]},{"StartTime":115744.0,"Objects":[{"StartTime":115744.0,"Position":236.0,"HyperDash":true}]},{"StartTime":115831.0,"Objects":[{"StartTime":115831.0,"Position":28.0,"HyperDash":false},{"StartTime":115917.0,"Position":82.0,"HyperDash":false},{"StartTime":116003.0,"Position":28.0,"HyperDash":true}]},{"StartTime":116175.0,"Objects":[{"StartTime":116175.0,"Position":300.0,"HyperDash":false}]},{"StartTime":116348.0,"Objects":[{"StartTime":116348.0,"Position":132.0,"HyperDash":true}]},{"StartTime":116520.0,"Objects":[{"StartTime":116520.0,"Position":408.0,"HyperDash":false},{"StartTime":116588.0,"Position":351.302338,"HyperDash":false},{"StartTime":116692.0,"Position":300.0,"HyperDash":false}]},{"StartTime":116779.0,"Objects":[{"StartTime":116779.0,"Position":360.0,"HyperDash":true}]},{"StartTime":116865.0,"Objects":[{"StartTime":116865.0,"Position":156.0,"HyperDash":false},{"StartTime":116933.0,"Position":184.697678,"HyperDash":false},{"StartTime":117037.0,"Position":264.0,"HyperDash":false}]},{"StartTime":117124.0,"Objects":[{"StartTime":117124.0,"Position":204.0,"HyperDash":true}]},{"StartTime":117210.0,"Objects":[{"StartTime":117210.0,"Position":384.0,"HyperDash":false}]},{"StartTime":117296.0,"Objects":[{"StartTime":117296.0,"Position":444.0,"HyperDash":false}]},{"StartTime":117382.0,"Objects":[{"StartTime":117382.0,"Position":504.0,"HyperDash":true}]},{"StartTime":117555.0,"Objects":[{"StartTime":117555.0,"Position":228.0,"HyperDash":false},{"StartTime":117623.0,"Position":287.697662,"HyperDash":false},{"StartTime":117727.0,"Position":336.0,"HyperDash":true}]},{"StartTime":117900.0,"Objects":[{"StartTime":117900.0,"Position":60.0,"HyperDash":false},{"StartTime":117968.0,"Position":86.69768,"HyperDash":false},{"StartTime":118072.0,"Position":168.0,"HyperDash":false}]},{"StartTime":118158.0,"Objects":[{"StartTime":118158.0,"Position":108.0,"HyperDash":true}]},{"StartTime":118244.0,"Objects":[{"StartTime":118244.0,"Position":324.0,"HyperDash":false},{"StartTime":118312.0,"Position":384.697662,"HyperDash":false},{"StartTime":118416.0,"Position":380.0,"HyperDash":false}]},{"StartTime":118503.0,"Objects":[{"StartTime":118503.0,"Position":320.0,"HyperDash":true}]},{"StartTime":118589.0,"Objects":[{"StartTime":118589.0,"Position":132.0,"HyperDash":false}]},{"StartTime":118675.0,"Objects":[{"StartTime":118675.0,"Position":72.0,"HyperDash":false}]},{"StartTime":118762.0,"Objects":[{"StartTime":118762.0,"Position":132.0,"HyperDash":true}]},{"StartTime":118934.0,"Objects":[{"StartTime":118934.0,"Position":428.0,"HyperDash":true}]},{"StartTime":119106.0,"Objects":[{"StartTime":119106.0,"Position":80.0,"HyperDash":true}]},{"StartTime":119279.0,"Objects":[{"StartTime":119279.0,"Position":352.0,"HyperDash":false},{"StartTime":119347.0,"Position":288.6279,"HyperDash":false},{"StartTime":119451.0,"Position":217.0,"HyperDash":false}]},{"StartTime":119537.0,"Objects":[{"StartTime":119537.0,"Position":148.0,"HyperDash":true}]},{"StartTime":119624.0,"Objects":[{"StartTime":119624.0,"Position":388.0,"HyperDash":false},{"StartTime":119692.0,"Position":336.6279,"HyperDash":false},{"StartTime":119796.0,"Position":253.0,"HyperDash":false}]},{"StartTime":119882.0,"Objects":[{"StartTime":119882.0,"Position":320.0,"HyperDash":true}]},{"StartTime":119969.0,"Objects":[{"StartTime":119969.0,"Position":100.0,"HyperDash":false},{"StartTime":120055.0,"Position":46.0,"HyperDash":false},{"StartTime":120141.0,"Position":100.0,"HyperDash":true}]},{"StartTime":120313.0,"Objects":[{"StartTime":120313.0,"Position":384.0,"HyperDash":true}]},{"StartTime":120486.0,"Objects":[{"StartTime":120486.0,"Position":112.0,"HyperDash":true}]},{"StartTime":120658.0,"Objects":[{"StartTime":120658.0,"Position":408.0,"HyperDash":false},{"StartTime":120726.0,"Position":466.697662,"HyperDash":false},{"StartTime":120830.0,"Position":412.0,"HyperDash":false}]},{"StartTime":120917.0,"Objects":[{"StartTime":120917.0,"Position":348.0,"HyperDash":true}]},{"StartTime":121003.0,"Objects":[{"StartTime":121003.0,"Position":132.0,"HyperDash":false},{"StartTime":121071.0,"Position":77.837204,"HyperDash":false},{"StartTime":121175.0,"Position":127.999992,"HyperDash":false}]},{"StartTime":121262.0,"Objects":[{"StartTime":121262.0,"Position":196.0,"HyperDash":true}]},{"StartTime":121348.0,"Objects":[{"StartTime":121348.0,"Position":384.0,"HyperDash":false},{"StartTime":121434.0,"Position":387.368439,"HyperDash":true}]},{"StartTime":121520.0,"Objects":[{"StartTime":121520.0,"Position":188.0,"HyperDash":false},{"StartTime":121606.0,"Position":184.631577,"HyperDash":true}]},{"StartTime":121693.0,"Objects":[{"StartTime":121693.0,"Position":400.0,"HyperDash":false},{"StartTime":121779.0,"Position":346.0,"HyperDash":true}]},{"StartTime":121865.0,"Objects":[{"StartTime":121865.0,"Position":128.0,"HyperDash":false},{"StartTime":121951.0,"Position":124.407974,"HyperDash":true}]},{"StartTime":122037.0,"Objects":[{"StartTime":122037.0,"Position":336.0,"HyperDash":false},{"StartTime":122123.0,"Position":282.0,"HyperDash":true}]},{"StartTime":122210.0,"Objects":[{"StartTime":122210.0,"Position":484.0,"HyperDash":false},{"StartTime":122296.0,"Position":486.696625,"HyperDash":true}]},{"StartTime":122382.0,"Objects":[{"StartTime":122382.0,"Position":272.0,"HyperDash":false},{"StartTime":122468.0,"Position":326.0,"HyperDash":true}]},{"StartTime":122555.0,"Objects":[{"StartTime":122555.0,"Position":108.0,"HyperDash":false},{"StartTime":122641.0,"Position":54.0,"HyperDash":true}]},{"StartTime":122727.0,"Objects":[{"StartTime":122727.0,"Position":280.0,"HyperDash":false}]},{"StartTime":122813.0,"Objects":[{"StartTime":122813.0,"Position":347.0,"HyperDash":false}]},{"StartTime":122900.0,"Objects":[{"StartTime":122900.0,"Position":415.0,"HyperDash":false}]},{"StartTime":123072.0,"Objects":[{"StartTime":123072.0,"Position":256.0,"HyperDash":false}]},{"StartTime":123158.0,"Objects":[{"StartTime":123158.0,"Position":308.0,"HyperDash":false}]},{"StartTime":123244.0,"Objects":[{"StartTime":123244.0,"Position":360.0,"HyperDash":false}]},{"StartTime":123417.0,"Objects":[{"StartTime":123417.0,"Position":228.0,"HyperDash":false}]},{"StartTime":123503.0,"Objects":[{"StartTime":123503.0,"Position":260.0,"HyperDash":false}]},{"StartTime":123589.0,"Objects":[{"StartTime":123589.0,"Position":292.0,"HyperDash":false}]},{"StartTime":123762.0,"Objects":[{"StartTime":123762.0,"Position":188.0,"HyperDash":false}]},{"StartTime":123848.0,"Objects":[{"StartTime":123848.0,"Position":196.0,"HyperDash":false}]},{"StartTime":123934.0,"Objects":[{"StartTime":123934.0,"Position":204.0,"HyperDash":false}]},{"StartTime":124106.0,"Objects":[{"StartTime":124106.0,"Position":311.0,"HyperDash":false},{"StartTime":124170.0,"Position":216.0,"HyperDash":false},{"StartTime":124235.0,"Position":310.0,"HyperDash":false},{"StartTime":124299.0,"Position":397.0,"HyperDash":false},{"StartTime":124364.0,"Position":214.0,"HyperDash":false},{"StartTime":124429.0,"Position":505.0,"HyperDash":false},{"StartTime":124493.0,"Position":173.0,"HyperDash":false},{"StartTime":124558.0,"Position":295.0,"HyperDash":false},{"StartTime":124623.0,"Position":199.0,"HyperDash":false},{"StartTime":124687.0,"Position":494.0,"HyperDash":false},{"StartTime":124752.0,"Position":293.0,"HyperDash":false},{"StartTime":124817.0,"Position":115.0,"HyperDash":false},{"StartTime":124881.0,"Position":412.0,"HyperDash":false},{"StartTime":124946.0,"Position":506.0,"HyperDash":false},{"StartTime":125011.0,"Position":293.0,"HyperDash":false},{"StartTime":125075.0,"Position":346.0,"HyperDash":false},{"StartTime":125140.0,"Position":117.0,"HyperDash":false},{"StartTime":125205.0,"Position":285.0,"HyperDash":false},{"StartTime":125269.0,"Position":17.0,"HyperDash":false},{"StartTime":125334.0,"Position":238.0,"HyperDash":false},{"StartTime":125399.0,"Position":222.0,"HyperDash":false},{"StartTime":125463.0,"Position":450.0,"HyperDash":false},{"StartTime":125528.0,"Position":67.0,"HyperDash":false},{"StartTime":125593.0,"Position":219.0,"HyperDash":false},{"StartTime":125657.0,"Position":307.0,"HyperDash":false},{"StartTime":125722.0,"Position":367.0,"HyperDash":false},{"StartTime":125787.0,"Position":412.0,"HyperDash":false},{"StartTime":125851.0,"Position":413.0,"HyperDash":false},{"StartTime":125916.0,"Position":143.0,"HyperDash":false},{"StartTime":125981.0,"Position":339.0,"HyperDash":false},{"StartTime":126045.0,"Position":342.0,"HyperDash":false},{"StartTime":126110.0,"Position":249.0,"HyperDash":false},{"StartTime":126175.0,"Position":235.0,"HyperDash":false},{"StartTime":126239.0,"Position":323.0,"HyperDash":false},{"StartTime":126304.0,"Position":365.0,"HyperDash":false},{"StartTime":126368.0,"Position":74.0,"HyperDash":false},{"StartTime":126433.0,"Position":281.0,"HyperDash":false},{"StartTime":126498.0,"Position":398.0,"HyperDash":false},{"StartTime":126562.0,"Position":335.0,"HyperDash":false},{"StartTime":126627.0,"Position":388.0,"HyperDash":false},{"StartTime":126692.0,"Position":228.0,"HyperDash":false},{"StartTime":126756.0,"Position":323.0,"HyperDash":false},{"StartTime":126821.0,"Position":441.0,"HyperDash":false},{"StartTime":126886.0,"Position":442.0,"HyperDash":false},{"StartTime":126950.0,"Position":278.0,"HyperDash":false},{"StartTime":127015.0,"Position":90.0,"HyperDash":false},{"StartTime":127080.0,"Position":409.0,"HyperDash":false},{"StartTime":127144.0,"Position":377.0,"HyperDash":false},{"StartTime":127209.0,"Position":457.0,"HyperDash":false},{"StartTime":127274.0,"Position":409.0,"HyperDash":false},{"StartTime":127338.0,"Position":43.0,"HyperDash":false},{"StartTime":127403.0,"Position":162.0,"HyperDash":false},{"StartTime":127468.0,"Position":341.0,"HyperDash":false},{"StartTime":127532.0,"Position":72.0,"HyperDash":false},{"StartTime":127597.0,"Position":135.0,"HyperDash":false},{"StartTime":127662.0,"Position":252.0,"HyperDash":false},{"StartTime":127726.0,"Position":446.0,"HyperDash":false},{"StartTime":127791.0,"Position":284.0,"HyperDash":false},{"StartTime":127856.0,"Position":70.0,"HyperDash":false},{"StartTime":127920.0,"Position":494.0,"HyperDash":false},{"StartTime":127985.0,"Position":463.0,"HyperDash":false},{"StartTime":128050.0,"Position":277.0,"HyperDash":false},{"StartTime":128114.0,"Position":425.0,"HyperDash":false},{"StartTime":128179.0,"Position":281.0,"HyperDash":false},{"StartTime":128244.0,"Position":3.0,"HyperDash":false},{"StartTime":128308.0,"Position":346.0,"HyperDash":false},{"StartTime":128373.0,"Position":350.0,"HyperDash":false},{"StartTime":128437.0,"Position":217.0,"HyperDash":false},{"StartTime":128502.0,"Position":455.0,"HyperDash":false},{"StartTime":128567.0,"Position":229.0,"HyperDash":false},{"StartTime":128631.0,"Position":51.0,"HyperDash":false},{"StartTime":128696.0,"Position":199.0,"HyperDash":false},{"StartTime":128761.0,"Position":208.0,"HyperDash":false},{"StartTime":128825.0,"Position":173.0,"HyperDash":false},{"StartTime":128890.0,"Position":367.0,"HyperDash":false},{"StartTime":128955.0,"Position":193.0,"HyperDash":false},{"StartTime":129019.0,"Position":488.0,"HyperDash":false},{"StartTime":129084.0,"Position":314.0,"HyperDash":false},{"StartTime":129149.0,"Position":135.0,"HyperDash":false},{"StartTime":129213.0,"Position":399.0,"HyperDash":false},{"StartTime":129278.0,"Position":404.0,"HyperDash":false},{"StartTime":129343.0,"Position":152.0,"HyperDash":false},{"StartTime":129407.0,"Position":353.0,"HyperDash":false},{"StartTime":129472.0,"Position":358.0,"HyperDash":false},{"StartTime":129537.0,"Position":447.0,"HyperDash":false},{"StartTime":129601.0,"Position":222.0,"HyperDash":false},{"StartTime":129666.0,"Position":382.0,"HyperDash":false},{"StartTime":129731.0,"Position":433.0,"HyperDash":false},{"StartTime":129795.0,"Position":450.0,"HyperDash":false},{"StartTime":129860.0,"Position":326.0,"HyperDash":false},{"StartTime":129925.0,"Position":414.0,"HyperDash":false},{"StartTime":129989.0,"Position":285.0,"HyperDash":false},{"StartTime":130054.0,"Position":336.0,"HyperDash":false},{"StartTime":130119.0,"Position":509.0,"HyperDash":false},{"StartTime":130183.0,"Position":334.0,"HyperDash":false},{"StartTime":130248.0,"Position":72.0,"HyperDash":false},{"StartTime":130313.0,"Position":425.0,"HyperDash":false},{"StartTime":130377.0,"Position":451.0,"HyperDash":false},{"StartTime":130442.0,"Position":220.0,"HyperDash":false},{"StartTime":130506.0,"Position":25.0,"HyperDash":false},{"StartTime":130571.0,"Position":77.0,"HyperDash":false},{"StartTime":130636.0,"Position":509.0,"HyperDash":false},{"StartTime":130700.0,"Position":90.0,"HyperDash":false},{"StartTime":130765.0,"Position":118.0,"HyperDash":false},{"StartTime":130830.0,"Position":58.0,"HyperDash":false},{"StartTime":130894.0,"Position":12.0,"HyperDash":false},{"StartTime":130959.0,"Position":215.0,"HyperDash":false},{"StartTime":131024.0,"Position":487.0,"HyperDash":false},{"StartTime":131088.0,"Position":446.0,"HyperDash":false},{"StartTime":131153.0,"Position":491.0,"HyperDash":false},{"StartTime":131218.0,"Position":459.0,"HyperDash":false},{"StartTime":131282.0,"Position":37.0,"HyperDash":false},{"StartTime":131347.0,"Position":291.0,"HyperDash":false},{"StartTime":131412.0,"Position":315.0,"HyperDash":false},{"StartTime":131476.0,"Position":35.0,"HyperDash":false},{"StartTime":131541.0,"Position":208.0,"HyperDash":false},{"StartTime":131606.0,"Position":504.0,"HyperDash":false},{"StartTime":131670.0,"Position":296.0,"HyperDash":false},{"StartTime":131735.0,"Position":105.0,"HyperDash":false},{"StartTime":131800.0,"Position":488.0,"HyperDash":false},{"StartTime":131864.0,"Position":230.0,"HyperDash":false},{"StartTime":131929.0,"Position":446.0,"HyperDash":false},{"StartTime":131994.0,"Position":241.0,"HyperDash":false},{"StartTime":132058.0,"Position":413.0,"HyperDash":false},{"StartTime":132123.0,"Position":357.0,"HyperDash":false},{"StartTime":132188.0,"Position":256.0,"HyperDash":false},{"StartTime":132252.0,"Position":192.0,"HyperDash":false},{"StartTime":132317.0,"Position":116.0,"HyperDash":false},{"StartTime":132382.0,"Position":397.0,"HyperDash":false}]}]} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/2781126.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/2781126.osu new file mode 100644 index 0000000000..af7cd296d7 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/2781126.osu @@ -0,0 +1,908 @@ +osu file format v14 + +[General] +StackLeniency: 0.7 +Mode: 2 + +[Difficulty] +HPDrainRate:6 +CircleSize:4.5 +OverallDifficulty:9.5 +ApproachRate:9.5 +SliderMultiplier:1.8 +SliderTickRate:2 + +[Events] +//Background and Video events +//Break Periods +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Layer 4 (Overlay) +//Storyboard Sound Samples + +[TimingPoints] +-31,344.827586206897,4,2,1,15,1,0 +486,-100,4,2,1,50,0,0 +658,-100,4,2,1,55,0,0 +831,-100,4,2,1,60,0,0 +1003,-100,4,2,1,65,0,0 +1175,-100,4,2,1,5,0,0 +1348,-100,4,2,1,80,0,0 +11089,-100,4,2,2,40,0,0 +11175,-100,4,2,2,45,0,0 +11262,-100,4,2,2,50,0,0 +11348,-100,4,2,2,55,0,0 +11434,-100,4,2,2,60,0,0 +11520,-100,4,2,2,65,0,0 +11606,-100,4,2,2,70,0,0 +11693,-100,4,2,1,75,0,0 +11865,-100,4,2,1,70,0,0 +12037,-80,4,2,1,75,0,0 +12296,-100,4,2,1,100,0,0 +12382,-100,4,2,1,85,0,0 +20658,-83.3333333333333,4,2,1,85,0,0 +21175,-100,4,2,1,85,0,0 +22037,-100,4,2,1,80,0,0 +22210,-100,4,2,2,50,0,0 +22727,-100,4,2,1,80,0,0 +23072,-66.6666666666667,4,2,1,100,0,0 +23331,-100,4,2,1,100,0,0 +23417,-80,4,2,1,75,0,0 +23762,-100,4,2,1,100,0,0 +34451,-66.6666666666667,4,2,1,80,0,0 +34624,-100,4,2,1,80,0,0 +39969,-80,4,2,1,80,0,0 +40658,-80,4,2,1,100,0,0 +41348,-66.6666666666667,4,2,1,100,0,0 +42037,-100,4,2,1,100,0,0 +44106,-66.6666666666667,4,2,1,70,0,0 +44279,-100,4,2,1,100,0,0 +44796,-66.6666666666667,4,2,1,100,0,0 +46175,-66.6666666666667,4,2,1,90,0,1 +46348,-100,4,2,1,90,0,1 +51348,-33.3333333333333,4,2,1,90,0,1 +51520,-66.6666666666667,4,2,1,90,0,1 +51693,-100,4,2,1,100,0,1 +52037,-66.6666666666667,4,2,1,100,0,1 +52727,-100,4,2,1,100,0,1 +53072,-40,4,2,1,100,0,1 +53762,-100,4,2,1,100,0,1 +55141,-66.6666666666667,4,2,1,100,0,1 +55371,-100,4,2,1,100,0,1 +56520,-66.6666666666667,4,2,1,90,0,0 +56606,-66.6666666666667,4,2,2,47,0,0 +56693,-66.6666666666667,4,2,1,54,0,0 +56779,-66.6666666666667,4,2,2,61,0,0 +56865,-66.6666666666667,4,2,1,68,0,0 +56951,-66.6666666666667,4,2,2,75,0,0 +57037,-66.6666666666667,4,2,1,81,0,0 +57124,-66.6666666666667,4,2,2,88,0,0 +57210,-100,4,2,1,90,0,0 +57900,-66.6666666666667,4,2,1,90,0,1 +58072,-100,4,2,1,90,0,1 +58244,-80,4,2,1,90,0,1 +58589,-100,4,2,1,90,0,1 +61175,-66.6666666666667,4,2,1,90,0,1 +61348,-100,4,2,1,90,0,1 +62382,-33.3333333333333,4,2,1,100,0,1 +62555,-66.6666666666667,4,2,1,100,0,1 +62770,-100,4,2,1,100,0,1 +64106,-40,4,2,1,100,0,1 +64796,-100,4,2,1,100,0,1 +66175,-100,4,2,1,80,0,0 +68417,-66.6666666666667,4,2,1,80,0,0 +68589,-100,4,2,1,80,0,0 +68934,-100,4,2,1,70,0,1 +69020,-100,4,2,1,10,0,0 +71003,-100,4,2,1,15,0,0 +71693,-100,4,2,1,20,0,0 +71865,-100,4,2,1,23,0,0 +72037,-100,4,2,1,26,0,0 +72210,-100,4,2,1,29,0,0 +72382,-100,4,2,1,32,0,0 +72555,-100,4,2,1,35,0,0 +72727,-100,4,2,1,38,0,0 +72900,-100,4,2,1,41,0,0 +73072,-100,4,2,1,44,0,0 +73244,-100,4,2,1,47,0,0 +73417,-100,4,2,1,50,0,0 +73589,-100,4,2,1,53,0,0 +73762,-100,4,2,1,56,0,0 +73934,-100,4,2,1,59,0,0 +74106,-100,4,2,1,62,0,0 +74279,-100,4,2,1,65,0,0 +74451,-100,4,2,1,40,0,0 +74624,-133.333333333333,4,2,1,40,0,0 +77210,-100,4,2,1,40,0,0 +77555,-133.333333333333,4,2,1,40,0,0 +79969,-100,4,2,1,40,0,0 +80313,-133.333333333333,4,2,1,40,0,0 +81348,-100,4,2,1,40,0,0 +81692,-133.333333333333,4,2,1,40,0,0 +82727,-86.9565217391304,4,2,1,40,0,0 +83072,-133.333333333333,4,2,1,40,0,0 +84106,-100,4,2,1,40,0,0 +84450,-133.333333333333,4,2,1,40,0,0 +85486,-100,4,2,2,50,0,0 +96175,-50,4,2,2,60,0,0 +96520,-50,4,2,1,55,0,0 +96822,-50,4,2,1,5,0,0 +96865,-66.6666666666667,4,2,1,50,0,0 +97210,-66.6666666666667,4,2,1,45,0,0 +97512,-66.6666666666667,4,2,1,5,0,0 +97555,-100,4,2,1,40,0,0 +97900,-100,4,2,1,35,0,0 +98244,-100,4,2,1,5,0,0 +99279,-100,4,2,1,75,0,0 +99624,-66.6666666666667,4,2,1,75,0,0 +99796,-66.6666666666667,4,2,1,75,0,0 +100313,-100,4,2,1,75,0,0 +100658,-83.3333333333333,4,2,1,90,0,1 +111578,-83.3333333333333,4,2,1,90,0,0 +111693,-83.3333333333333,4,2,1,90,0,1 +119279,-66.6666666666667,4,2,1,90,0,1 +119969,-83.3333333333333,4,2,1,90,0,1 +121003,-50,4,2,1,90,0,1 +121175,-83.3333333333333,4,2,1,90,0,1 +122727,-100,4,2,1,90,0,0 +123072,-100,4,2,1,50,0,0 +123417,-100,4,2,1,46,0,0 +123762,-100,4,2,1,42,0,0 +124106,-100,4,2,1,38,0,0 +124451,-100,4,2,1,35,0,0 +124796,-100,4,2,1,32,0,0 +125141,-100,4,2,1,29,0,0 +125486,-100,4,2,1,26,0,0 +125831,-100,4,2,1,24,0,0 +126175,-100,4,2,1,22,0,0 +126520,-100,4,2,1,20,0,0 +126865,-100,4,2,1,18,0,0 +127210,-100,4,2,1,16,0,0 +127555,-100,4,2,1,14,0,0 +127900,-100,4,2,1,12,0,0 +128244,-100,4,2,1,10,0,0 +128934,-100,4,2,1,9,0,0 +129624,-100,4,2,1,8,0,0 +130313,-100,4,2,1,7,0,0 +131003,-100,4,2,1,6,0,0 +131693,-100,4,2,1,5,0,0 + +[HitObjects] +256,192,313,12,2,1175,0:0:0:0: +224,192,1348,5,6,3:2:0:0: +177,157,1434,1,0,0:0:0:0: +179,100,1520,1,2,0:2:0:0: +227,68,1606,1,2,0:2:0:0: +292,68,1693,2,0,L|296:12,1,45,2|0,0:0|0:0,0:0:0:0: +116,192,1865,2,0,B|116:280|116:280|208:296,1,180,0|2,3:0|0:1,0:0:0:0: +116,280,2296,1,0,0:0:0:0: +26,264,2382,2,0,L|22:160,1,90,0|2,3:2|0:3,0:0:0:0: +292,192,2727,6,0,L|384:192,1,90,2|2,3:2|0:3,0:0:0:0: +328,192,2986,1,2,0:3:0:0: +276,192,3072,1,2,0:0:0:0: +448,192,3244,1,0,3:0:0:0: +268,96,3417,2,0,B|176:96,1,90,0|2,0:0|0:3,0:0:0:0: +244,96,3675,1,2,0:3:0:0: +178,96,3762,2,0,L|82:96,1,90,0|2,3:0|0:3,0:0:0:0: +444,304,4106,6,0,L|448:256,1,45,2|2,3:2|0:2,0:0:0:0: +376,256,4279,2,0,L|372:208,1,45,2|2,0:2|0:2,0:0:0:0: +300,192,4451,1,2,0:0:0:0: +472,136,4624,2,0,L|476:84,1,45,0|0,3:0|0:0,0:0:0:0: +296,28,4796,2,0,P|264:72|280:108,1,90,0|2,0:0|0:1,0:0:0:0: +366,152,5055,1,0,0:0:0:0: +456,211,5141,2,0,L|352:211,1,90,0|2,3:2|0:3,0:0:0:0: +112,192,5486,6,0,L|208:192,1,90,2|2,3:2|0:3,0:0:0:0: +268,192,5744,1,2,0:3:0:0: +202,192,5831,1,2,0:0:0:0: +360,192,6003,1,0,3:0:0:0: +192,284,6175,2,0,L|100:284,1,90,0|2,0:0|0:3,0:0:0:0: +172,284,6434,1,2,0:3:0:0: +102,284,6520,2,0,L|10:284,1,90,0|2,3:0|0:3,0:0:0:0: +288,284,6865,5,2,3:2:0:0: +335,249,6951,1,2,0:2:0:0: +333,192,7037,1,2,0:2:0:0: +285,160,7124,1,2,0:2:0:0: +220,160,7210,2,0,L|216:104,1,45,2|0,0:0|0:0,0:0:0:0: +320,56,7382,1,0,3:0:0:0: +204,56,7555,1,0,0:0:0:0: +456,52,7727,1,2,0:1:0:0: +460,104,7813,1,0,0:0:0:0: +464,160,7900,2,0,L|372:160,1,90,0|2,3:2|0:3,0:0:0:0: +120,160,8244,6,0,B|212:160,1,90,2|2,3:2|0:3,0:0:0:0: +280,160,8503,1,2,0:3:0:0: +348,160,8589,1,2,0:0:0:0: +176,160,8762,1,0,3:0:0:0: +354,160,8934,2,0,L|446:160,1,90,0|2,0:0|0:3,0:0:0:0: +374,160,9193,1,2,0:3:0:0: +306,160,9279,2,0,L|406:160,1,90,0|2,3:0|0:3,0:0:0:0: +148,56,9624,6,0,L|100:44,1,45,2|2,3:2|0:2,0:0:0:0: +176,120,9796,2,0,L|224:108,1,45,2|2,0:2|0:2,0:0:0:0: +148,56,9969,1,2,0:0:0:0: +308,56,10141,1,0,3:0:0:0: +140,120,10313,1,0,0:0:0:0: +396,192,10486,2,0,L|440:192,2,45,2|0|0,0:1|0:0|3:2,0:0:0:0: +228,192,10831,1,2,0:3:0:0: +460,312,11003,6,0,L|484:270,1,45,0|2,3:3|0:3,0:0:0:0: +392,288,11175,2,0,L|416:246,1,45,2|2,0:3|0:3,0:0:0:0: +324,264,11348,2,0,L|347:222,1,45,2|2,0:3|0:3,0:0:0:0: +260,232,11520,2,0,L|284:190,1,45,2|2,0:3|0:3,0:0:0:0: +384,192,11693,1,0,3:0:0:0: +220,188,11865,2,0,L|156:188,1,45,8|0,0:2|3:0,0:0:0:0: +400,192,12037,2,0,B|488:192|488:192|488:108,1,168.75,8|0,3:2|0:0,0:0:0:0: +284,56,12382,6,0,L|192:56,1,90,6|2,3:2|0:2,0:0:0:0: +264,56,12641,1,2,0:2:0:0: +436,56,12727,1,10,3:2:0:0: +328,56,12900,2,0,L|324:112,1,45,0|0,3:0|0:0,3:3:0:0: +424,112,13072,2,0,L|428:216,1,90,0|2,0:0|0:1,0:0:0:0: +360,200,13331,1,2,0:3:0:0: +208,200,13417,2,0,L|116:200,1,90,8|2,3:2|0:3,0:0:0:0: +292,200,13762,6,0,L|296:292,1,90,2|2,3:2|0:3,0:0:0:0: +228,292,14020,1,2,0:3:0:0: +408,288,14106,2,0,L|508:288,1,90,10|0,3:2|3:0,0:0:0:0: +228,192,14451,2,0,L|324:192,1,90,8|2,3:2|3:3,0:0:0:0: +48,192,14796,2,0,L|140:192,1,90,8|2,3:2|0:3,0:0:0:0: +392,192,15141,6,0,L|396:132,1,45,2|2,3:2|0:2,0:0:0:0: +320,120,15313,2,0,L|316:60,1,45,2|2,0:2|0:2,0:0:0:0: +488,60,15486,1,10,3:2:0:0: +388,60,15658,2,0,L|332:60,1,45,0|0,3:0|0:0,3:3:0:0: +240,60,15831,2,0,L|236:152,1,90,0|2,0:0|0:1,0:0:0:0: +304,152,16089,1,2,0:3:0:0: +132,152,16175,2,0,L|36:152,1,90,8|2,3:2|0:3,0:0:0:0: +312,256,16520,6,0,L|216:256,1,90,10|2,3:2|0:3,0:0:0:0: +152,256,16779,1,2,0:3:0:0: +328,328,16865,2,0,L|236:328,1,90,10|0,3:2|3:0,0:0:0:0: +328,328,17210,1,0,0:0:0:0: +164,328,17382,2,0,L|160:276,1,45,2|2,0:3|0:3,0:0:0:0: +336,240,17555,2,0,L|440:240,1,90,8|2,3:2|0:3,0:0:0:0: +152,56,17900,5,10,3:2:0:0: +155,114,17986,1,2,0:2:0:0: +192,160,18072,1,2,0:2:0:0: +252,168,18158,1,2,0:2:0:0: +404,168,18244,2,0,L|408:72,1,90,10|2,0:2|3:2,0:0:0:0: +156,232,18589,2,0,L|64:232,1,90,8|2,0:0|0:3,0:0:0:0: +136,232,18848,1,2,0:3:0:0: +304,232,18934,2,0,L|396:232,1,90,8|0,3:2|0:3,0:0:0:0: +120,76,19279,6,0,P|100:120|120:168,1,90,8|0,3:2|0:0,0:0:0:0: +180,160,19537,1,0,0:0:0:0: +360,160,19624,2,0,L|268:160,1,90,8|0,0:0|3:0,0:0:0:0: +32,316,19969,2,0,L|132:316,1,90,8|2,3:2|0:3,0:0:0:0: +188,316,20227,1,0,0:0:0:0: +16,232,20313,2,0,L|116:232,1,90,8|2,3:2|0:3,0:0:0:0: +368,232,20658,6,0,L|256:232,3,107.999996704102,10|10|10|8,3:2|0:2|3:2|3:2,0:0:0:0: +496,232,21348,1,8,0:0:0:0: +324,232,21520,1,8,3:2:0:0: +496,232,21693,1,8,3:2:0:0: +388,232,21865,2,0,L|332:232,1,45,8|8,3:2|3:2,0:0:0:0: +144,232,22037,5,2,3:2:0:0: +252,232,22210,2,0,L|232:192,1,45,2|0,3:0|0:0,0:0:0:0: +312,164,22382,2,0,L|292:124,1,45,2|0,3:0|0:0,0:0:0:0: +372,96,22555,2,0,L|352:56,1,45,2|0,3:0|0:0,0:0:0:0: +180,56,22727,2,0,L|276:56,1,90,2|8,3:2|0:0,0:0:0:0: +208,56,22986,1,0,3:0:0:0: +436,56,23072,2,0,P|504:104|436:168,1,202.500007724762,8|0,3:2|0:0,0:0:0:0: +208,192,23417,6,0,L|92:192,2,112.5,6|2|2,3:2|0:0|0:0,0:0:0:0: +312,192,23934,1,2,0:0:0:0: +220,192,24020,1,2,0:0:0:0: +128,192,24106,2,0,L|220:192,1,90,2|2,0:0|0:1,0:0:0:0: +392,192,24451,1,0,3:0:0:0: +444,176,24537,1,2,0:0:0:0: +444,120,24624,1,2,0:0:0:0: +392,100,24710,1,2,0:0:0:0: +212,276,24796,6,0,L|308:276,2,90,2|2|2,0:2|0:2|0:2,0:0:0:0: +320,276,25313,1,2,0:2:0:0: +384,276,25400,1,2,0:2:0:0: +284,352,25486,2,0,L|192:352,1,90,2|2,0:2|0:0,0:0:0:0: +448,276,25831,2,0,L|444:224,1,45,2|2,3:2|0:2,0:0:0:0: +344,192,26003,2,0,L|300:192,1,45,2|2,0:2|0:2,0:0:0:0: +128,192,26175,6,0,L|28:192,2,90,2|2|2,3:2|0:0|0:0,0:0:0:0: +236,192,26693,1,2,0:0:0:0: +299,192,26779,1,2,0:0:0:0: +362,192,26865,1,2,0:0:0:0: +196,192,27037,1,2,0:1:0:0: +352,192,27210,1,2,3:2:0:0: +352,128,27296,1,2,0:2:0:0: +312,80,27382,1,2,0:2:0:0: +248,80,27469,1,2,0:2:0:0: +412,80,27555,6,0,L|320:80,2,90,2|2|2,0:2|0:0|0:0,0:0:0:0: +304,80,28072,1,2,0:0:0:0: +396,80,28158,1,2,0:0:0:0: +488,80,28244,2,0,L|396:80,1,90,2|2,0:0|0:0,0:0:0:0: +88,80,28589,1,8,3:2:0:0: +340,80,28934,6,0,L|344:172,1,90,2|2,3:2|0:2,0:0:0:0: +172,192,29279,2,0,L|168:292,1,90,2|2,3:2|0:2,0:0:0:0: +268,284,29537,1,2,0:2:0:0: +368,284,29624,2,0,L|268:284,1,90,2|2,3:2|0:1,0:0:0:0: +452,284,29969,2,0,L|460:236,2,45,0|0|2,3:2|0:0|0:2,0:0:0:0: +200,372,30313,6,0,L|196:280,1,90,2|2,3:2|0:2,0:0:0:0: +368,160,30658,2,0,L|264:160,1,90,2|2,3:2|0:2,0:0:0:0: +380,160,30917,1,2,0:2:0:0: +480,160,31003,2,0,L|374:160,1,90,2|2,3:2|0:2,0:0:0:0: +128,192,31348,2,0,L|124:140,1,45,2|2,3:2|0:2,0:0:0:0: +228,104,31520,2,0,L|292:104,1,45,2|2,0:2|0:2,0:0:0:0: +88,148,31693,6,0,L|84:252,1,90,2|2,3:2|0:2,0:0:0:0: +256,236,32037,2,0,L|352:236,1,90,2|2,3:2|0:2,0:0:0:0: +246,236,32296,1,2,0:2:0:0: +148,236,32382,2,0,L|48:236,1,90,2|2,3:2|0:2,0:0:0:0: +232,68,32727,1,0,3:2:0:0: +180,68,32813,1,0,0:0:0:0: +124,68,32900,1,2,0:2:0:0: +376,68,33072,6,0,L|476:68,1,90,2|2,3:2|0:2,0:0:0:0: +300,192,33417,2,0,L|396:192,1,90,2|2,3:2|0:2,0:0:0:0: +220,192,33762,2,0,L|128:192,1,90,2|2,3:2|0:2,0:0:0:0: +416,192,34106,2,0,L|448:192,7,22.5,0|0|0|0|0|0|0|0,3:0|3:0|3:0|3:0|3:0|3:0|3:0|0:0,0:0:0:0: +265,192,34451,6,0,L|129:192,1,135.000005149842,10|2,3:2|0:2,0:0:0:0: +300,192,34796,2,0,L|304:97,1,90,2|2,3:2|0:2,0:0:0:0: +140,100,35141,1,10,3:2:0:0: +376,100,35313,1,2,0:1:0:0: +268,100,35486,2,0,L|264:196,1,90,0|2,3:2|0:2,0:0:0:0: +496,192,35831,6,0,L|404:192,1,90,10|2,3:2|0:2,0:0:0:0: +236,192,36175,2,0,L|140:192,1,90,2|2,3:2|0:2,0:0:0:0: +400,256,36520,1,10,3:2:0:0: +236,256,36693,1,2,0:2:0:0: +476,256,36865,1,2,3:2:0:0: +476,322,36951,1,0,0:0:0:0: +434,372,37037,1,2,0:2:0:0: +369,383,37124,1,0,0:0:0:0: +196,384,37210,6,0,L|104:384,1,90,10|2,3:2|0:2,0:0:0:0: +272,384,37555,2,0,L|368:384,1,90,2|2,3:2|0:2,0:0:0:0: +196,384,37900,1,10,3:2:0:0: +432,384,38072,1,2,0:1:0:0: +324,384,38244,1,0,3:2:0:0: +272,384,38331,1,0,0:0:0:0: +224,384,38417,1,2,0:2:0:0: +488,384,38589,6,0,L|490:281,1,90,10|2,3:2|0:2,0:0:0:0: +324,296,38934,2,0,L|328:188,1,90,2|2,3:2|0:2,0:0:0:0: +88,204,39279,1,10,3:2:0:0: +256,204,39451,1,2,0:2:0:0: +16,204,39624,1,10,3:2:0:0: +428,208,39969,6,0,P|480:152|428:92,1,168.75,8|2,3:2|0:0,0:0:0:0: +328,92,40313,2,0,P|256:120|240:204,1,168.75,10|0,3:2|0:0,0:0:0:0: +412,208,40658,2,0,B|496:208|496:208|500:296,1,168.75,8|0,3:2|0:0,0:0:0:0: +272,376,41003,2,0,B|272:292|272:292|360:288,1,168.75,8|0,3:2|0:0,0:0:0:0: +116,296,41348,6,0,P|52:224|120:176,1,202.500007724762,8|0,3:2|0:0,0:0:0:0: +340,176,41693,2,0,L|132:176,1,202.500007724762,8|0,3:2|0:0,0:0:0:0: +312,96,42037,1,8,3:2:0:0: +164,96,42210,1,8,3:2:0:0: +324,96,42382,1,8,3:2:0:0: +152,96,42555,1,8,3:2:0:0: +404,96,42727,5,8,3:2:0:0: +460,128,42813,1,8,3:2:0:0: +460,192,42900,1,8,3:2:0:0: +404,224,42986,1,8,3:2:0:0: +208,192,43072,2,0,L|204:244,1,45,8|8,3:2|3:2,0:0:0:0: +280,240,43244,2,0,L|284:292,1,45,8|8,3:2|3:2,0:0:0:0: +104,328,43417,6,8,L|48:328,1,45,8|8,3:2|3:2,0:0:0:0: +240,364,43589,2,8,L|300:364,1,45,8|8,3:2|3:2,0:0:0:0: +80,192,43762,2,0,L|136:192,1,45,8|8,3:2|3:2,0:0:0:0: +372,224,43934,2,0,L|316:224,1,45,8|8,3:2|3:2,0:0:0:0: +124,44,44106,5,8,3:2:0:0: +368,44,44279,1,2,0:0:0:0: +116,44,44451,2,0,L|64:44,1,45,2|2,0:0|0:0,0:0:0:0: +172,116,44624,2,0,L|112:116,1,45,2|2,0:0|0:0,0:0:0:0: +300,116,44796,2,0,L|476:116,1,168.750006437302,2|2,0:0|0:0,0:0:0:0: +260,192,45141,2,0,L|428:192,1,168.750006437302,2|2,0:0|0:0,0:0:0:0: +176,328,45486,5,2,3:2:0:0: +158,322,45507,1,0,0:0:0:0: +143,313,45529,1,0,0:0:0:0: +129,301,45550,1,0,0:0:0:0: +119,287,45572,1,0,0:0:0:0: +111,270,45594,1,0,0:0:0:0: +108,253,45615,1,0,0:0:0:0: +108,235,45637,1,0,0:0:0:0: +112,217,45658,1,0,0:0:0:0: +120,201,45680,1,0,0:0:0:0: +131,187,45701,1,0,0:0:0:0: +145,175,45723,1,0,0:0:0:0: +161,167,45744,1,0,0:0:0:0: +178,162,45766,1,0,0:0:0:0: +196,161,45787,1,0,0:0:0:0: +214,164,45809,1,0,0:0:0:0: +240,168,45831,1,0,0:0:0:0: +257,167,45852,1,0,0:0:0:0: +275,162,45874,1,0,0:0:0:0: +291,153,45895,1,0,0:0:0:0: +304,142,45917,1,0,0:0:0:0: +315,128,45938,1,0,0:0:0:0: +323,111,45960,1,0,0:0:0:0: +327,94,45981,1,0,0:0:0:0: +327,76,46003,1,0,0:0:0:0: +324,58,46025,1,0,0:0:0:0: +317,42,46046,1,0,0:0:0:0: +306,27,46068,1,0,0:0:0:0: +293,16,46089,1,0,0:0:0:0: +277,7,46111,1,0,0:0:0:0: +260,1,46132,1,0,0:0:0:0: +76,52,46175,6,0,B|8:52|8:52|80:52,1,135.000005149842,4|2,3:2|0:0,0:0:0:0: +120,52,46434,1,2,0:0:0:0: +280,52,46520,2,0,L|376:52,1,90,8|2,3:2|0:0,0:0:0:0: +324,52,46779,1,2,0:0:0:0: +152,136,46865,2,0,L|96:136,1,45,2|2,3:2|0:0,0:0:0:0: +172,208,47037,2,0,L|112:208,1,45,2|2,0:0|0:0,0:0:0:0: +336,192,47210,1,8,3:2:0:0: +363,202,47253,1,0,0:0:0:0: +384,224,47296,1,0,0:0:0:0: +393,252,47339,1,0,0:0:0:0: +389,282,47382,1,0,0:0:0:0: +372,306,47425,1,0,0:0:0:0: +347,322,47469,1,0,0:0:0:0: +168,324,47555,6,0,L|76:324,1,90,2|0,3:2|0:0,0:0:0:0: +244,208,47900,2,0,L|152:208,1,90,10|2,3:2|0:3,0:0:0:0: +400,208,48244,2,0,L|404:156,1,45,2|2,3:2|0:2,0:0:0:0: +312,76,48503,1,2,0:2:0:0: +140,76,48589,1,10,0:2:0:0: +248,76,48762,1,2,0:2:0:0: +16,76,48934,6,0,L|60:76,1,45,2|2,3:2|0:0,0:0:0:0: +160,76,49193,1,2,3:2:0:0: +16,76,49279,2,0,L|20:120,1,45,10|2,3:2|0:2,0:0:0:0: +76,164,49451,2,0,L|140:164,1,45,2|2,0:2|0:2,0:0:0:0: +304,192,49624,5,0,3:0:0:0: +317,209,49667,1,0,0:0:0:0: +326,230,49710,1,0,0:0:0:0: +328,252,49753,1,0,0:0:0:0: +325,274,49796,1,2,0:0:0:0: +316,295,49839,1,0,0:0:0:0: +301,312,49882,1,0,0:0:0:0: +120,312,49969,1,8,3:2:0:0: +52,312,50055,1,2,0:0:0:0: +120,312,50141,1,2,0:0:0:0: +288,312,50313,5,2,3:2:0:0: +332,273,50400,1,2,3:2:0:0: +328,215,50486,1,2,3:2:0:0: +280,184,50572,1,2,3:2:0:0: +104,92,50658,2,0,L|60:92,1,45,10|2,3:2|0:3,0:0:0:0: +104,184,50831,2,0,L|148:184,1,45,2|0,0:3|0:0,0:0:0:0: +328,215,51003,6,0,B|376:215|376:215|324:215,1,90,4|2,3:2|0:0,0:0:0:0: +280,215,51262,1,2,0:0:0:0: +128,296,51348,2,0,L|364:296,1,236.250009012223,8|0,3:2|0:0,0:0:0:0: +364,296,51520,2,0,L|224:296,1,135.000005149842,2|2,0:0|0:0,0:0:0:0: +368,144,51736,2,0,L|440:144,1,67.5,2|2,0:0|3:2,0:0:0:0: +380,144,51951,1,2,0:0:0:0: +204,64,52037,2,0,L|128:64,1,67.5000025749208,10|0,0:0|0:0,0:0:0:0: +223,64,52210,2,0,L|148:64,1,67.5000025749208,2|2,0:2|0:2,0:0:0:0: +388,240,52382,6,0,L|464:240,1,67.5000025749208,2|2,3:2|0:2,0:0:0:0: +368,144,52555,2,0,L|436:144,1,67.5000025749208,2|2,0:2|0:2,0:0:0:0: +224,144,52727,1,10,3:2:0:0: +194,150,52770,1,0,0:0:0:0: +169,165,52813,1,0,0:0:0:0: +149,188,52856,1,0,0:0:0:0: +137,215,52900,1,2,0:3:0:0: +134,245,52943,1,0,0:0:0:0: +141,274,52986,1,0,0:0:0:0: +368,348,53072,2,0,B|144:348,1,225,10|0,3:2|0:0,0:0:0:0: +444,272,53417,2,0,B|220:272,1,225,10|2,3:2|0:3,0:0:0:0: +488,184,53762,6,0,L|492:276,1,90,2|2,3:2|0:3,0:0:0:0: +336,184,54106,1,8,3:2:0:0: +280,184,54193,1,2,0:3:0:0: +228,184,54279,1,2,0:3:0:0: +392,276,54451,2,0,L|396:312,1,22.5,2|0,3:2|0:0,0:0:0:0: +188,328,54624,2,0,L|185:305,1,22.5,2|0,0:3|0:0,0:0:0:0: +408,108,54796,2,0,L|356:108,1,45,10|2,3:2|0:3,0:0:0:0: +136,176,54969,2,0,L|188:176,1,45,2|0,0:0|0:0,0:0:0:0: +384,192,55141,6,0,L|292:192,2,90.0000034332277,2|2|2,3:2|0:2|0:2,0:0:0:0: +172,272,55486,1,2,3:2:0:0: +280,272,55601,1,2,0:2:0:0: +388,272,55716,1,2,0:2:0:0: +164,192,55831,2,0,L|96:192,2,60,2|2|2,3:2|0:2|0:2,0:0:0:0: +340,192,56175,1,2,3:2:0:0: +412,192,56290,1,2,0:2:0:0: +412,120,56405,1,2,0:2:0:0: +212,120,56520,6,0,P|160:260|288:136,1,472.500018024445,0|0,3:2|0:0,0:0:0:0: +128,40,57210,5,2,3:2:0:0: +112,44,57231,1,0,0:0:0:0: +97,50,57253,1,0,0:0:0:0: +83,58,57275,1,0,0:0:0:0: +70,67,57296,1,0,0:0:0:0: +57,77,57318,1,0,0:0:0:0: +46,89,57339,1,0,0:0:0:0: +35,101,57361,1,0,0:0:0:0: +26,114,57382,1,0,0:0:0:0: +19,129,57404,1,0,0:0:0:0: +13,143,57425,1,0,0:0:0:0: +8,159,57447,1,0,0:0:0:0: +5,175,57469,1,0,0:0:0:0: +3,191,57490,1,0,0:0:0:0: +3,207,57512,1,0,0:0:0:0: +5,223,57533,1,0,0:0:0:0: +8,239,57555,1,0,0:0:0:0: +12,254,57576,1,0,0:0:0:0: +18,269,57598,1,0,0:0:0:0: +26,283,57619,1,0,0:0:0:0: +35,297,57641,1,0,0:0:0:0: +45,309,57662,1,0,0:0:0:0: +56,321,57684,1,0,0:0:0:0: +69,331,57706,1,0,0:0:0:0: +82,340,57727,1,0,0:0:0:0: +96,348,57749,1,0,0:0:0:0: +111,354,57770,1,0,0:0:0:0: +126,359,57792,1,0,0:0:0:0: +142,362,57813,1,0,0:0:0:0: +158,364,57835,1,0,0:0:0:0: +174,364,57856,1,0,0:0:0:0: +312,364,57900,6,0,L|448:364,1,135.000005149842,12|2,3:2|0:0,0:0:0:0: +392,364,58158,1,2,0:0:0:0: +216,192,58244,2,0,L|160:192,1,56.25,10|0,3:2|0:0,0:0:0:0: +232,124,58417,2,0,L|176:124,1,56.25,2|2,0:0|0:0,0:0:0:0: +20,192,58589,2,0,L|112:192,1,90,2|2,3:2|0:0,0:0:0:0: +276,264,58934,2,0,L|180:264,1,90,8|2,3:2|0:3,0:0:0:0: +440,264,59279,5,0,3:0:0:0: +466,250,59322,1,0,0:0:0:0: +484,226,59365,1,0,0:0:0:0: +491,198,59408,1,0,0:0:0:0: +484,168,59451,1,2,0:3:0:0: +428,128,59537,1,0,0:0:0:0: +260,128,59624,2,0,L|216:128,2,45,8|2|2,3:2|0:0|0:0,0:0:0:0: +494,129,59969,2,0,L|498:181,1,45,2|2,3:2|0:2,0:0:0:0: +392,260,60227,1,2,0:2:0:0: +212,260,60313,1,10,3:2:0:0: +356,260,60486,1,2,0:2:0:0: +104,64,60658,6,0,L|100:112,1,45,2|2,3:2|0:2,0:0:0:0: +204,192,60917,1,2,0:2:0:0: +384,128,61003,2,0,L|340:128,1,45,10|2,3:2|0:2,0:0:0:0: +159,192,61175,2,0,L|240:192,1,67.5000025749208,2|2,0:2|0:2,0:0:0:0: +72,192,61348,5,2,3:2:0:0: +9,228,61434,1,2,3:2:0:0: +9,300,61520,1,2,3:2:0:0: +70,336,61606,1,2,3:2:0:0: +250,272,61693,2,0,L|350:272,1,90,10|0,3:2|0:0,0:0:0:0: +184,215,62037,6,0,B|136:215|136:215|188:215,1,90,6|2,3:2|0:3,0:0:0:0: +232,215,62296,1,2,0:3:0:0: +384,296,62382,2,0,L|148:296,1,236.250009012223,8|0,0:0|0:0,0:0:0:0: +148,296,62555,2,0,L|288:296,1,135.000005149842,2|2,0:0|3:2,0:0:0:0: +144,144,62770,2,0,L|72:144,1,67.5,2|2,0:0|0:0,0:0:0:0: +132,144,62986,1,2,0:0:0:0: +300,64,63072,2,0,L|344:64,1,45,8|0,3:2|0:0,0:0:0:0: +184,192,63244,2,0,L|232:192,1,45,2|2,0:0|0:0,0:0:0:0: +64,64,63417,6,0,L|20:64,1,45,2|2,3:2|0:0,0:0:0:0: +184,192,63589,2,0,L|140:192,1,45,2|2,0:0|0:0,0:0:0:0: +345,64,63762,1,10,0:0:0:0: +375,70,63805,1,0,0:0:0:0: +400,85,63848,1,0,0:0:0:0: +420,108,63891,1,0,0:0:0:0: +432,135,63934,1,0,0:0:0:0: +435,165,63977,1,0,0:0:0:0: +428,194,64020,1,0,0:0:0:0: +224,344,64106,2,0,B|448:344,1,225,8|2,3:2|3:2,0:0:0:0: +148,268,64451,2,0,B|372:268,1,225,8|0,3:2|0:0,0:0:0:0: +120,344,64796,5,6,3:2:0:0: +324,344,64911,1,2,0:0:0:0: +120,344,65026,1,2,0:0:0:0: +336,168,65141,1,10,3:2:0:0: +222,168,65256,1,2,0:0:0:0: +108,168,65371,1,2,0:0:0:0: +336,92,65486,1,2,3:2:0:0: +444,92,65601,1,2,0:0:0:0: +336,92,65716,1,2,0:0:0:0: +144,92,65831,1,10,3:2:0:0: +252,92,65946,1,2,0:0:0:0: +144,92,66060,1,2,0:0:0:0: +360,288,66175,6,0,L|468:288,1,90,2|2,0:0|0:0,0:0:0:0: +396,288,66434,1,2,0:0:0:0: +224,192,66520,1,2,0:0:0:0: +388,192,66693,1,2,0:0:0:0: +124,316,66865,2,0,L|120:264,1,45,2|2,0:0|0:0,0:0:0:0: +204,352,67037,2,0,L|200:300,1,45,2|2,0:0|0:0,0:0:0:0: +368,192,67210,1,2,0:0:0:0: +204,192,67382,1,2,0:0:0:0: +476,192,67555,5,6,3:2:0:0: +188,192,67900,1,6,3:2:0:0: +488,192,68244,1,0,3:0:0:0: +356,192,68417,2,0,L|424:192,1,67.5000025749208,0|0,3:0|3:0,0:0:0:0: +172,192,68589,2,0,L|168:100,1,90,8|2,0:0|0:0,0:0:0:0: +484,60,68934,5,4,3:2:0:0: +256,192,69279,12,0,71348,0:0:0:0: +232,196,71693,6,0,L|228:176,14,11.25,2|0|0|0|0|0|0|0|0|0|0|0|0|0|0,0:0|0:0|0:0|0:0|0:0|0:0|0:0|0:0|0:0|0:0|0:0|0:0|0:0|0:0|0:0,0:0:0:0: +272,164,72037,2,0,L|282:146,14,11.25,2|0|0|0|0|0|0|0|0|0|0|0|0|0|0,0:0|0:0|0:0|0:0|0:0|0:0|0:0|0:0|0:0|0:0|0:0|0:0|0:0|0:0|0:0,0:0:0:0: +316,216,72382,6,0,L|331:203,14,11.25,2|0|0|0|0|0|0|0|0|0|0|0|0|0|0,0:0|0:0|0:0|0:0|0:0|0:0|0:0|0:0|0:0|0:0|0:0|0:0|0:0|0:0|0:0,0:0:0:0: +360,140,72727,2,0,L|375:127,14,11.25,2|0|0|0|0|0|0|0|0|0|0|0|0|0|0,0:0|0:0|0:0|0:0|0:0|0:0|0:0|0:0|0:0|0:0|0:0|0:0|0:0|0:0|0:0,0:0:0:0: +256,76,73072,5,2,0:0:0:0: +244,78,73094,1,0,0:0:0:0: +233,82,73115,1,0,0:0:0:0: +224,88,73137,1,0,0:0:0:0: +215,96,73158,1,0,0:0:0:0: +209,106,73180,1,0,0:0:0:0: +205,117,73201,1,0,0:0:0:0: +202,128,73223,1,0,0:0:0:0: +203,140,73244,1,0,0:0:0:0: +205,151,73266,1,0,0:0:0:0: +210,162,73287,1,0,0:0:0:0: +217,171,73309,1,0,0:0:0:0: +226,179,73331,1,0,0:0:0:0: +236,184,73352,1,0,0:0:0:0: +247,188,73374,1,0,0:0:0:0: +258,190,73395,1,0,0:0:0:0: +270,189,73417,1,0,0:0:0:0: +281,185,73438,1,0,0:0:0:0: +291,180,73460,1,0,0:0:0:0: +300,173,73481,1,0,0:0:0:0: +307,164,73503,1,0,0:0:0:0: +313,153,73525,1,0,0:0:0:0: +316,142,73546,1,0,0:0:0:0: +317,131,73568,1,0,0:0:0:0: +315,119,73589,1,0,0:0:0:0: +311,108,73611,1,0,0:0:0:0: +305,98,73632,1,0,0:0:0:0: +297,90,73654,1,0,0:0:0:0: +288,83,73675,1,0,0:0:0:0: +277,78,73697,1,0,0:0:0:0: +266,76,73719,1,0,0:0:0:0: +164,20,73762,5,2,0:0:0:0: +153,23,73783,1,0,0:0:0:0: +143,28,73805,1,0,0:0:0:0: +133,34,73826,1,0,0:0:0:0: +124,40,73848,1,0,0:0:0:0: +115,48,73869,1,0,0:0:0:0: +108,56,73891,1,0,0:0:0:0: +101,65,73912,1,0,0:0:0:0: +95,74,73934,1,0,0:0:0:0: +90,84,73956,1,0,0:0:0:0: +85,95,73977,1,0,0:0:0:0: +82,105,73999,1,0,0:0:0:0: +80,116,74020,1,0,0:0:0:0: +79,128,74042,1,0,0:0:0:0: +79,139,74063,1,0,0:0:0:0: +180,148,74106,1,2,0:0:0:0: +190,151,74128,1,0,0:0:0:0: +200,156,74150,1,0,0:0:0:0: +210,162,74171,1,0,0:0:0:0: +219,168,74193,1,0,0:0:0:0: +228,176,74214,1,0,0:0:0:0: +235,184,74236,1,0,0:0:0:0: +242,193,74257,1,0,0:0:0:0: +248,202,74279,1,0,0:0:0:0: +253,212,74300,1,0,0:0:0:0: +258,223,74322,1,0,0:0:0:0: +261,233,74344,1,0,0:0:0:0: +263,244,74365,1,0,0:0:0:0: +264,256,74387,1,0,0:0:0:0: +264,267,74408,1,0,0:0:0:0: +148,236,74451,6,0,L|52:236,1,90,6|0,0:1|0:0,0:0:0:0: +196,124,74796,2,0,L|192:32,1,67.5000025749208 +328,208,75141,2,0,L|324:116,1,67.5000025749208,0|0,0:0|0:0,0:0:0:0: +228,168,75486,1,2,0:0:0:0: +396,208,75658,1,2,0:0:0:0: +124,168,75831,5,2,0:0:0:0: +36,168,76003,1,0,0:0:0:0: +36,80,76175,1,0,0:0:0:0: +124,80,76348,1,0,0:0:0:0: +292,80,76520,2,0,L|296:172,1,67.5000025749208,2|0,0:0|0:0,0:0:0:0: +192,224,76865,2,0,L|196:152,1,67.5000025749208,0|0,0:0|0:0,0:0:0:0: +368,148,77210,6,0,P|424:204|368:268,1,180,2|0,0:0|0:0,0:0:0:0: +272,268,77727,1,0,0:0:0:0: +176,268,77900,2,0,L|172:360,1,67.5000025749208,0|0,0:0|0:0,0:0:0:0: +272,268,78244,1,2,0:0:0:0: +104,336,78417,1,2,0:0:0:0: +380,268,78589,6,0,L|456:268,2,67.5000025749208,2|0|0,0:0|0:0|0:0,0:0:0:0: +284,268,79106,1,0,0:0:0:0: +116,268,79279,2,0,L|112:176,1,67.5000025749208,2|0,0:0|0:0,0:0:0:0: +216,192,79624,2,0,L|312:192,1,67.5000025749208,0|0,0:0|0:0,0:0:0:0: +324,192,79882,1,0,0:0:0:0: +152,96,79969,6,0,B|56:96,2,90,2|0|0,0:0|0:0|0:0,0:0:0:0: +248,96,80486,1,0,0:0:0:0: +416,96,80658,2,0,L|420:168,1,67.5000025749208,2|0,0:0|0:0,0:0:0:0: +324,192,81003,2,0,L|252:192,1,67.5000025749208,0|0,0:0|0:0,0:0:0:0: +208,192,81262,1,0,0:0:0:0: +384,256,81348,6,0,B|480:256,2,90,2|0|0,0:0|0:0|0:0,0:0:0:0: +212,296,81865,1,2,0:0:0:0: +444,360,82037,2,0,L|448:284,1,67.5000025749208,2|0,0:0|0:0,0:0:0:0: +212,296,82382,1,2,0:0:0:0: +172,296,82469,1,0,0:0:0:0: +132,296,82555,1,0,0:0:0:0: +432,24,82727,6,0,P|500:80|432:148,1,207.000003948212,2|0,0:0|0:0,0:0:0:0: +272,148,83244,1,0,0:0:0:0: +440,148,83417,2,0,L|444:220,1,67.5000025749208,0|0,0:0|0:0,0:0:0:0: +200,148,83762,1,2,0:0:0:0: +352,148,83934,1,2,0:0:0:0: +104,148,84106,6,0,B|8:148,2,90,2|0|0,0:0|0:0|0:0,0:0:0:0: +272,196,84624,1,0,0:0:0:0: +112,148,84796,2,0,L|108:228,1,67.5000025749208,2|2,0:0|0:0,0:0:0:0: +164,216,85055,1,2,0:0:0:0: +216,216,85141,2,0,L|292:216,1,67.5000025749208,2|2,0:0|0:0,0:0:0:0: +32,216,85486,6,0,P|0:264|36:324,1,135,6|0,3:1|0:0,0:0:0:0: +108,324,85831,2,0,L|216:324,1,90,0|2,3:2|0:2,0:0:0:0: +20,324,86175,1,2,3:2:0:0: +128,324,86348,2,0,L|180:324,1,45,0|0,0:2|0:0,0:0:0:0: +344,192,86520,2,0,L|248:192,1,90,2|2,3:2|0:2,0:0:0:0: +436,312,86865,6,0,L|440:208,1,90,2|2,3:2|0:2,0:0:0:0: +375,208,87124,1,2,0:2:0:0: +312,192,87210,1,2,3:2:0:0: +472,192,87382,1,2,0:2:0:0: +300,192,87555,2,0,L|296:96,1,90,2|2,3:2|0:2,0:0:0:0: +360,100,87813,1,2,0:2:0:0: +196,100,87900,2,0,L|104:100,1,90,2|2,3:2|0:2,0:0:0:0: +276,16,88244,6,0,L|368:16,1,90,2|0,3:2|0:0,0:0:0:0: +312,16,88503,1,0,0:0:0:0: +260,16,88589,1,0,3:2:0:0: +440,16,88762,1,2,0:2:0:0: +192,16,88934,2,0,L|100:16,1,90,2|0,3:2|0:2,0:0:0:0: +164,16,89193,1,0,0:0:0:0: +228,16,89279,2,0,L|136:16,1,90,2|2,3:2|0:2,0:0:0:0: +306,112,89624,6,0,L|414:112,1,90,2|2,3:2|0:2,0:0:0:0: +450,112,89882,1,2,0:2:0:0: +396,112,89969,1,2,3:2:0:0: +228,112,90141,1,2,0:2:0:0: +396,112,90313,2,0,L|400:208,1,90,2|0,3:2|0:2,0:0:0:0: +332,204,90572,1,0,0:0:0:0: +264,204,90658,2,0,L|360:204,1,90,2|0,3:2|0:2,0:0:0:0: +184,204,91003,6,0,L|80:204,1,90,2|2,3:2|0:2,0:0:0:0: +148,204,91262,1,0,0:0:0:0: +200,204,91348,1,2,3:2:0:0: +32,204,91520,1,2,0:2:0:0: +296,204,91693,2,0,B|344:204|344:204|296:204,1,90,2|2,3:2|0:2,0:0:0:0: +240,204,91951,1,0,0:0:0:0: +136,204,92037,2,0,L|132:132,1,45,0|0,3:2|0:0,0:0:0:0: +196,112,92210,2,0,L|200:168,1,45,0|0,0:2|0:0,0:0:0:0: +48,204,92382,6,0,B|4:204|4:204|52:204,1,90,2|2,3:2|0:2,0:0:0:0: +120,204,92641,1,0,0:0:0:0: +188,204,92727,1,0,3:2:0:0: +360,204,92900,1,2,0:2:0:0: +123,293,93072,2,0,L|119:197,1,90,2|2,3:2|0:2,0:0:0:0: +188,204,93331,1,2,0:2:0:0: +368,204,93417,2,0,L|424:204,2,45,2|2|2,3:2|0:2|0:2,0:0:0:0: +96,204,93762,5,2,3:2:0:0: +53,169,93848,1,0,0:0:0:0: +45,114,93934,1,2,0:2:0:0: +75,69,94020,1,0,0:0:0:0: +128,55,94106,1,2,3:2:0:0: +316,56,94279,1,2,0:2:0:0: +48,52,94451,2,0,L|44:152,1,90,2|0,3:2|0:2,0:0:0:0: +112,160,94710,1,0,0:0:0:0: +300,160,94796,1,2,3:2:0:0: +416,160,94969,2,0,L|352:160,1,45,2|0,0:2|0:0,0:0:0:0: +180,232,95141,5,2,3:2:0:0: +128,232,95227,1,0,0:0:0:0: +76,232,95313,1,2,0:2:0:0: +248,232,95486,1,2,3:2:0:0: +68,232,95658,1,2,0:2:0:0: +348,232,95831,2,0,L|440:232,1,90,2|2,3:2|0:2,0:0:0:0: +176,232,96175,6,0,P|176:16|180:232,1,675,2|0,3:2|0:0,0:0:0:0: +156,232,96865,2,0,P|160:64|168:232,1,506.250019311906,0|0,0:0|0:0,0:0:0:0: +144,232,97555,2,0,P|148:112|152:232,1,360,0|0,0:0|0:0,0:0:0:0: +164,320,99279,5,0,3:0:0:0: +324,320,99451,2,0,L|340:284,3,22.5,8|8|8|8,0:2|0:2|0:2|0:2,0:0:0:0: +204,320,99624,2,0,L|64:320,1,135.000005149842,8|0,3:2|3:0,0:0:0:0: +340,228,99969,2,0,L|200:228,1,135.000005149842,8|0,3:2|3:0,0:0:0:0: +472,228,100313,1,8,3:2:0:0: +64,172,100658,6,0,L|8:172,2,53.9999983520508,4|2|2,3:2|0:0|0:0,0:0:0:0: +336,228,101003,1,10,3:2:0:0: +176,228,101175,1,2,0:0:0:0: +448,228,101348,2,0,B|500:228|500:228|444:228,1,107.999996704102,2|2,3:2|3:2,0:0:0:0: +384,228,101606,1,2,0:0:0:0: +220,128,101693,2,0,L|328:128,1,107.999996704102,8|2,3:2|0:0,0:0:0:0: +264,128,101951,1,2,0:0:0:0: +112,128,102037,5,2,3:2:0:0: +56,128,102124,1,2,0:0:0:0: +56,180,102210,1,2,0:0:0:0: +344,252,102382,1,8,3:2:0:0: +56,180,102555,1,2,0:0:0:0: +368,252,102727,2,0,P|400:304|388:352,1,107.999996704102,0|2,3:0|3:2,0:0:0:0: +332,348,102986,1,2,0:2:0:0: +168,348,103072,2,0,L|56:348,1,107.999996704102,10|2,3:2|0:0,0:0:0:0: +120,348,103331,1,0,0:0:0:0: +304,192,103417,5,0,3:2:0:0: +364,192,103503,1,2,0:0:0:0: +424,192,103589,1,2,0:0:0:0: +152,192,103762,1,10,3:2:0:0: +316,192,103934,1,2,0:0:0:0: +56,192,104106,2,0,B|4:192|4:192|60:192,1,107.999996704102,2|2,3:2|3:2,0:0:0:0: +116,192,104365,1,2,0:0:0:0: +304,192,104451,2,0,L|416:192,1,107.999996704102,8|2,3:2|0:0,0:0:0:0: +356,192,104710,1,2,0:0:0:0: +168,112,104796,6,0,L|112:112,2,53.9999983520508,2|2|2,3:2|0:0|0:0,0:0:0:0: +440,112,105141,1,8,3:2:0:0: +144,112,105313,1,2,0:0:0:0: +468,112,105486,2,0,B|468:60|468:60|412:60,1,107.999996704102,0|2,3:0|3:2,0:0:0:0: +360,60,105744,1,2,0:2:0:0: +164,192,105831,2,0,L|276:192,1,107.999996704102,10|2,3:2|0:0,0:0:0:0: +212,192,106089,1,2,0:0:0:0: +24,192,106175,5,2,3:2:0:0: +20,132,106262,1,2,0:0:0:0: +16,72,106348,1,2,0:0:0:0: +296,72,106520,1,8,3:2:0:0: +132,72,106693,1,2,0:0:0:0: +400,72,106865,2,0,P|448:108|440:164,1,107.999996704102,0|2,3:0|3:2,0:0:0:0: +388,192,107124,1,2,0:2:0:0: +196,192,107210,2,0,L|88:192,1,107.999996704102,10|2,3:2|0:0,0:0:0:0: +148,192,107469,1,2,0:0:0:0: +304,12,107555,5,2,3:2:0:0: +358,12,107641,1,2,0:0:0:0: +412,12,107727,1,2,0:0:0:0: +136,12,107900,1,8,3:2:0:0: +432,12,108072,1,2,0:0:0:0: +160,116,108244,2,0,L|52:116,1,107.999996704102,0|2,3:0|3:2,0:0:0:0: +112,116,108503,1,2,0:2:0:0: +300,192,108589,2,0,L|188:192,1,107.999996704102,10|2,3:2|0:0,0:0:0:0: +248,192,108848,1,2,0:0:0:0: +436,192,108934,6,0,L|496:192,2,53.9999983520508,2|2|2,3:2|0:0|0:0,0:0:0:0: +164,192,109279,1,8,3:2:0:0: +324,192,109451,1,2,0:0:0:0: +52,192,109624,2,0,P|24:235|60:280,1,107.999996704102,0|2,3:0|3:2,0:0:0:0: +112,276,109882,1,2,0:2:0:0: +316,276,109969,2,0,L|204:276,1,107.999996704102,10|2,3:2|0:0,0:0:0:0: +268,276,110227,1,2,0:0:0:0: +456,272,110313,6,0,L|460:152,1,107.999996704102,2|2,3:2|0:0,0:0:0:0: +292,276,110658,2,0,L|296:156,1,107.999996704102,8|2,3:2|0:0,0:0:0:0: +32,168,111003,1,8,3:2:0:0: +140,168,111118,1,2,0:0:0:0: +248,168,111233,1,2,0:0:0:0: +44,168,111348,2,0,L|124:168,2,71.9999978027344,10|2|2,3:2|0:2|0:2,0:0:0:0: +320,168,111693,5,4,3:2:0:0: +392,168,111779,1,2,0:0:0:0: +464,168,111865,1,2,0:0:0:0: +196,168,112037,1,10,3:2:0:0: +364,168,112210,1,2,0:0:0:0: +92,80,112382,2,0,L|204:80,1,107.999996704102,2|2,3:2|3:2,0:0:0:0: +140,80,112641,1,2,0:0:0:0: +356,80,112727,2,0,B|408:80|408:80|352:80,1,107.999996704102,8|2,3:2|0:0,0:0:0:0: +292,80,112986,1,2,0:0:0:0: +96,168,113072,5,2,3:2:0:0: +36,168,113158,1,2,0:0:0:0: +96,168,113244,1,2,0:0:0:0: +368,168,113417,1,8,3:2:0:0: +72,168,113589,1,2,0:0:0:0: +364,264,113762,2,0,L|252:264,1,107.999996704102,0|2,3:0|3:2,0:0:0:0: +316,264,114020,1,2,0:2:0:0: +120,344,114106,2,0,L|228:344,1,107.999996704102,10|2,3:2|0:0,0:0:0:0: +168,344,114365,1,2,0:0:0:0: +384,264,114451,5,2,3:2:0:0: +444,264,114537,1,2,0:0:0:0: +444,324,114624,1,2,0:0:0:0: +176,344,114796,1,8,3:2:0:0: +344,344,114969,1,2,0:0:0:0: +76,292,115141,2,0,B|20:292|20:292|20:344,1,107.999996704102,0|2,3:0|3:2,0:0:0:0: +80,344,115400,1,2,0:2:0:0: +284,192,115486,2,0,L|176:192,1,107.999996704102,10|2,3:2|0:0,0:0:0:0: +236,192,115744,1,2,0:0:0:0: +28,192,115831,6,0,L|84:192,2,53.9999983520508,2|2|2,3:2|0:0|0:0,0:0:0:0: +300,192,116175,1,8,3:2:0:0: +132,192,116348,1,2,0:0:0:0: +408,192,116520,2,0,L|300:192,1,107.999996704102,0|2,3:0|3:2,0:0:0:0: +360,192,116779,1,2,0:2:0:0: +156,84,116865,2,0,L|268:84,1,107.999996704102,10|2,3:2|0:0,0:0:0:0: +204,84,117124,1,2,0:0:0:0: +384,84,117210,5,10,3:2:0:0: +444,84,117296,1,2,0:0:0:0: +504,84,117382,1,2,0:0:0:0: +228,284,117555,2,0,L|344:284,1,107.999996704102,8|2,3:2|0:0,0:0:0:0: +60,192,117900,2,0,L|169:192,1,107.999996704102,8|2,3:2|3:2,0:0:0:0: +108,192,118158,1,2,0:2:0:0: +324,192,118244,2,0,B|380:192|380:192|380:140,1,107.999996704102,10|2,3:2|0:0,0:0:0:0: +320,112,118503,1,2,0:0:0:0: +132,112,118589,5,10,3:2:0:0: +72,112,118675,1,2,0:0:0:0: +132,112,118762,1,2,0:0:0:0: +428,140,118934,1,8,3:2:0:0: +80,112,119106,1,2,0:0:0:0: +352,192,119279,2,0,L|216:192,1,135.000005149842,8|2,3:2|3:2,0:0:0:0: +148,192,119537,1,2,0:2:0:0: +388,264,119624,2,0,L|252:264,1,135.000005149842,10|2,3:2|0:0,0:0:0:0: +320,264,119882,1,2,0:0:0:0: +100,264,119969,6,0,L|40:264,2,53.9999983520508,10|2|10,3:2|0:0|0:2,0:0:0:0: +384,192,120313,1,8,3:2:0:0: +112,192,120486,1,10,0:2:0:0: +408,192,120658,2,0,B|464:192|464:192|404:192,1,107.999996704102,8|10,3:2|3:2,0:0:0:0: +348,192,120917,1,2,0:2:0:0: +132,96,121003,2,0,B|40:96|40:96|134:96,1,180,10|10,3:2|0:2,0:0:0:0: +196,96,121262,1,2,0:0:0:0: +384,96,121348,6,0,L|388:160,1,53.9999983520508,10|0,3:2|0:0,0:0:0:0: +188,192,121520,2,0,L|184:256,1,53.9999983520508,10|0,0:2|0:0,0:0:0:0: +400,248,121693,2,0,L|336:248,1,53.9999983520508,8|0,3:2|0:0,0:0:0:0: +128,192,121865,2,0,L|124:252,1,53.9999983520508,10|0,0:2|0:0,0:0:0:0: +336,96,122037,6,0,L|276:96,1,53.9999983520508,8|0,3:2|0:0,0:0:0:0: +484,96,122210,2,0,L|488:176,1,53.9999983520508,10|2,3:2|0:2,0:0:0:0: +272,192,122382,2,0,L|328:192,1,53.9999983520508,10|0,3:2|0:0,0:0:0:0: +108,192,122555,2,0,L|52:192,1,53.9999983520508,8|0,0:2|0:0,0:0:0:0: +280,272,122727,5,8,3:2:0:0: +347,272,122813,1,0,0:0:0:0: +415,272,122900,1,0,0:0:0:0: +256,192,123072,1,2,0:0:0:0: +308,192,123158,1,0,0:0:0:0: +360,192,123244,1,0,0:0:0:0: +228,112,123417,5,2,0:0:0:0: +260,112,123503,1,0,0:0:0:0: +292,112,123589,1,0,0:0:0:0: +188,28,123762,1,2,0:0:0:0: +196,28,123848,1,0,0:0:0:0: +204,28,123934,1,0,0:0:0:0: +256,192,124106,12,0,132382,0:0:0:0: diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3152510-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3152510-expected-conversion.json new file mode 100644 index 0000000000..990550408d --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3152510-expected-conversion.json @@ -0,0 +1 @@ +{"Mappings":[{"StartTime":512.0,"Objects":[{"StartTime":512.0,"Position":368.0,"HyperDash":false},{"StartTime":573.0,"Position":353.0,"HyperDash":false},{"StartTime":670.0,"Position":368.0,"HyperDash":true}]},{"StartTime":829.0,"Objects":[{"StartTime":829.0,"Position":136.0,"HyperDash":false}]},{"StartTime":988.0,"Objects":[{"StartTime":988.0,"Position":272.0,"HyperDash":false}]},{"StartTime":1146.0,"Objects":[{"StartTime":1146.0,"Position":136.0,"HyperDash":false},{"StartTime":1207.0,"Position":125.0,"HyperDash":false},{"StartTime":1304.0,"Position":136.0,"HyperDash":true}]},{"StartTime":1464.0,"Objects":[{"StartTime":1464.0,"Position":368.0,"HyperDash":true}]},{"StartTime":1623.0,"Objects":[{"StartTime":1623.0,"Position":136.0,"HyperDash":false},{"StartTime":1702.0,"Position":87.4122238,"HyperDash":false},{"StartTime":1781.0,"Position":64.73344,"HyperDash":false},{"StartTime":1842.0,"Position":87.7600861,"HyperDash":false},{"StartTime":1940.0,"Position":119.014381,"HyperDash":false}]},{"StartTime":2019.0,"Objects":[{"StartTime":2019.0,"Position":176.0,"HyperDash":true}]},{"StartTime":2099.0,"Objects":[{"StartTime":2099.0,"Position":368.0,"HyperDash":false}]},{"StartTime":2258.0,"Objects":[{"StartTime":2258.0,"Position":232.0,"HyperDash":false}]},{"StartTime":2416.0,"Objects":[{"StartTime":2416.0,"Position":368.0,"HyperDash":false},{"StartTime":2477.0,"Position":369.0,"HyperDash":false},{"StartTime":2574.0,"Position":368.0,"HyperDash":true}]},{"StartTime":2734.0,"Objects":[{"StartTime":2734.0,"Position":136.0,"HyperDash":false},{"StartTime":2795.0,"Position":98.3227844,"HyperDash":false},{"StartTime":2892.0,"Position":41.0,"HyperDash":true}]},{"StartTime":3051.0,"Objects":[{"StartTime":3051.0,"Position":280.0,"HyperDash":false},{"StartTime":3112.0,"Position":301.677216,"HyperDash":false},{"StartTime":3209.0,"Position":375.0,"HyperDash":true}]},{"StartTime":3369.0,"Objects":[{"StartTime":3369.0,"Position":136.0,"HyperDash":false}]},{"StartTime":3527.0,"Objects":[{"StartTime":3527.0,"Position":272.0,"HyperDash":false}]},{"StartTime":3686.0,"Objects":[{"StartTime":3686.0,"Position":136.0,"HyperDash":false},{"StartTime":3747.0,"Position":128.0,"HyperDash":false},{"StartTime":3844.0,"Position":136.0,"HyperDash":true}]},{"StartTime":4004.0,"Objects":[{"StartTime":4004.0,"Position":384.0,"HyperDash":true}]},{"StartTime":4162.0,"Objects":[{"StartTime":4162.0,"Position":136.0,"HyperDash":false},{"StartTime":4241.0,"Position":171.350159,"HyperDash":false},{"StartTime":4320.0,"Position":230.700317,"HyperDash":false},{"StartTime":4381.0,"Position":281.261841,"HyperDash":false},{"StartTime":4479.0,"Position":326.0,"HyperDash":false}]},{"StartTime":4559.0,"Objects":[{"StartTime":4559.0,"Position":272.0,"HyperDash":true}]},{"StartTime":4638.0,"Objects":[{"StartTime":4638.0,"Position":80.0,"HyperDash":false}]},{"StartTime":4797.0,"Objects":[{"StartTime":4797.0,"Position":216.0,"HyperDash":false}]},{"StartTime":4956.0,"Objects":[{"StartTime":4956.0,"Position":80.0,"HyperDash":false},{"StartTime":5017.0,"Position":84.0,"HyperDash":false},{"StartTime":5114.0,"Position":80.0,"HyperDash":true}]},{"StartTime":5273.0,"Objects":[{"StartTime":5273.0,"Position":312.0,"HyperDash":false},{"StartTime":5334.0,"Position":266.322784,"HyperDash":false},{"StartTime":5431.0,"Position":217.0,"HyperDash":true}]},{"StartTime":5591.0,"Objects":[{"StartTime":5591.0,"Position":456.0,"HyperDash":false},{"StartTime":5652.0,"Position":461.0,"HyperDash":false},{"StartTime":5749.0,"Position":456.0,"HyperDash":true}]},{"StartTime":5908.0,"Objects":[{"StartTime":5908.0,"Position":216.0,"HyperDash":false}]},{"StartTime":6067.0,"Objects":[{"StartTime":6067.0,"Position":352.0,"HyperDash":false}]},{"StartTime":6226.0,"Objects":[{"StartTime":6226.0,"Position":216.0,"HyperDash":false},{"StartTime":6287.0,"Position":197.0,"HyperDash":false},{"StartTime":6384.0,"Position":216.0,"HyperDash":true}]},{"StartTime":6543.0,"Objects":[{"StartTime":6543.0,"Position":456.0,"HyperDash":true}]},{"StartTime":6702.0,"Objects":[{"StartTime":6702.0,"Position":216.0,"HyperDash":false},{"StartTime":6781.0,"Position":163.345444,"HyperDash":false},{"StartTime":6860.0,"Position":152.1179,"HyperDash":false},{"StartTime":6921.0,"Position":177.651291,"HyperDash":false},{"StartTime":7019.0,"Position":209.232849,"HyperDash":false}]},{"StartTime":7099.0,"Objects":[{"StartTime":7099.0,"Position":264.0,"HyperDash":true}]},{"StartTime":7178.0,"Objects":[{"StartTime":7178.0,"Position":456.0,"HyperDash":false}]},{"StartTime":7337.0,"Objects":[{"StartTime":7337.0,"Position":320.0,"HyperDash":false}]},{"StartTime":7496.0,"Objects":[{"StartTime":7496.0,"Position":456.0,"HyperDash":false},{"StartTime":7557.0,"Position":469.0,"HyperDash":false},{"StartTime":7654.0,"Position":456.0,"HyperDash":true}]},{"StartTime":7813.0,"Objects":[{"StartTime":7813.0,"Position":216.0,"HyperDash":false},{"StartTime":7874.0,"Position":171.322784,"HyperDash":false},{"StartTime":7971.0,"Position":121.0,"HyperDash":true}]},{"StartTime":8131.0,"Objects":[{"StartTime":8131.0,"Position":368.0,"HyperDash":false},{"StartTime":8192.0,"Position":351.0,"HyperDash":false},{"StartTime":8289.0,"Position":368.0,"HyperDash":true}]},{"StartTime":8448.0,"Objects":[{"StartTime":8448.0,"Position":128.0,"HyperDash":false}]},{"StartTime":8607.0,"Objects":[{"StartTime":8607.0,"Position":264.0,"HyperDash":false}]},{"StartTime":8765.0,"Objects":[{"StartTime":8765.0,"Position":128.0,"HyperDash":false},{"StartTime":8826.0,"Position":141.0,"HyperDash":false},{"StartTime":8923.0,"Position":128.0,"HyperDash":true}]},{"StartTime":9083.0,"Objects":[{"StartTime":9083.0,"Position":368.0,"HyperDash":true}]},{"StartTime":9242.0,"Objects":[{"StartTime":9242.0,"Position":128.0,"HyperDash":false},{"StartTime":9321.0,"Position":170.350159,"HyperDash":false},{"StartTime":9400.0,"Position":222.700317,"HyperDash":false},{"StartTime":9461.0,"Position":244.261841,"HyperDash":false},{"StartTime":9559.0,"Position":318.0,"HyperDash":false}]},{"StartTime":9638.0,"Objects":[{"StartTime":9638.0,"Position":264.0,"HyperDash":true}]},{"StartTime":9718.0,"Objects":[{"StartTime":9718.0,"Position":72.0,"HyperDash":false}]},{"StartTime":9877.0,"Objects":[{"StartTime":9877.0,"Position":208.0,"HyperDash":false}]},{"StartTime":10035.0,"Objects":[{"StartTime":10035.0,"Position":72.0,"HyperDash":false},{"StartTime":10096.0,"Position":68.0,"HyperDash":false},{"StartTime":10193.0,"Position":72.0,"HyperDash":true}]},{"StartTime":10353.0,"Objects":[{"StartTime":10353.0,"Position":312.0,"HyperDash":false},{"StartTime":10414.0,"Position":274.322784,"HyperDash":false},{"StartTime":10511.0,"Position":217.0,"HyperDash":true}]},{"StartTime":10670.0,"Objects":[{"StartTime":10670.0,"Position":464.0,"HyperDash":false},{"StartTime":10731.0,"Position":478.0,"HyperDash":false},{"StartTime":10828.0,"Position":464.0,"HyperDash":true}]},{"StartTime":10988.0,"Objects":[{"StartTime":10988.0,"Position":224.0,"HyperDash":false},{"StartTime":11049.0,"Position":209.0,"HyperDash":false},{"StartTime":11146.0,"Position":224.0,"HyperDash":false}]},{"StartTime":11305.0,"Objects":[{"StartTime":11305.0,"Position":360.0,"HyperDash":false}]},{"StartTime":11464.0,"Objects":[{"StartTime":11464.0,"Position":224.0,"HyperDash":true}]},{"StartTime":11623.0,"Objects":[{"StartTime":11623.0,"Position":464.0,"HyperDash":false}]},{"StartTime":11781.0,"Objects":[{"StartTime":11781.0,"Position":328.0,"HyperDash":false},{"StartTime":11842.0,"Position":309.0,"HyperDash":false},{"StartTime":11939.0,"Position":328.0,"HyperDash":false}]},{"StartTime":12099.0,"Objects":[{"StartTime":12099.0,"Position":464.0,"HyperDash":false},{"StartTime":12160.0,"Position":448.0,"HyperDash":false},{"StartTime":12257.0,"Position":464.0,"HyperDash":false}]},{"StartTime":12416.0,"Objects":[{"StartTime":12416.0,"Position":328.0,"HyperDash":false},{"StartTime":12477.0,"Position":377.677216,"HyperDash":false},{"StartTime":12574.0,"Position":423.0,"HyperDash":false}]},{"StartTime":12734.0,"Objects":[{"StartTime":12734.0,"Position":288.0,"HyperDash":false}]},{"StartTime":12892.0,"Objects":[{"StartTime":12892.0,"Position":424.0,"HyperDash":false},{"StartTime":12953.0,"Position":441.0,"HyperDash":false},{"StartTime":13050.0,"Position":424.0,"HyperDash":true}]},{"StartTime":13210.0,"Objects":[{"StartTime":13210.0,"Position":192.0,"HyperDash":false},{"StartTime":13271.0,"Position":191.0,"HyperDash":false},{"StartTime":13368.0,"Position":192.0,"HyperDash":true}]},{"StartTime":13527.0,"Objects":[{"StartTime":13527.0,"Position":424.0,"HyperDash":false},{"StartTime":13588.0,"Position":417.0,"HyperDash":false},{"StartTime":13685.0,"Position":424.0,"HyperDash":false}]},{"StartTime":13845.0,"Objects":[{"StartTime":13845.0,"Position":288.0,"HyperDash":false}]},{"StartTime":14004.0,"Objects":[{"StartTime":14004.0,"Position":424.0,"HyperDash":true}]},{"StartTime":14162.0,"Objects":[{"StartTime":14162.0,"Position":184.0,"HyperDash":false}]},{"StartTime":14321.0,"Objects":[{"StartTime":14321.0,"Position":320.0,"HyperDash":false},{"StartTime":14382.0,"Position":319.0,"HyperDash":false},{"StartTime":14479.0,"Position":320.0,"HyperDash":true}]},{"StartTime":14638.0,"Objects":[{"StartTime":14638.0,"Position":88.0,"HyperDash":false},{"StartTime":14699.0,"Position":107.0,"HyperDash":false},{"StartTime":14796.0,"Position":88.0,"HyperDash":false}]},{"StartTime":14956.0,"Objects":[{"StartTime":14956.0,"Position":224.0,"HyperDash":false}]},{"StartTime":15115.0,"Objects":[{"StartTime":15115.0,"Position":88.0,"HyperDash":false},{"StartTime":15176.0,"Position":82.0,"HyperDash":false},{"StartTime":15273.0,"Position":88.0,"HyperDash":true}]},{"StartTime":15432.0,"Objects":[{"StartTime":15432.0,"Position":328.0,"HyperDash":false},{"StartTime":15493.0,"Position":369.677216,"HyperDash":false},{"StartTime":15590.0,"Position":423.0,"HyperDash":true}]},{"StartTime":15750.0,"Objects":[{"StartTime":15750.0,"Position":192.0,"HyperDash":false}]},{"StartTime":15908.0,"Objects":[{"StartTime":15908.0,"Position":328.0,"HyperDash":false}]},{"StartTime":16067.0,"Objects":[{"StartTime":16067.0,"Position":192.0,"HyperDash":false},{"StartTime":16128.0,"Position":168.322784,"HyperDash":false},{"StartTime":16225.0,"Position":97.0,"HyperDash":false}]},{"StartTime":16385.0,"Objects":[{"StartTime":16385.0,"Position":232.0,"HyperDash":false}]},{"StartTime":16543.0,"Objects":[{"StartTime":16543.0,"Position":96.0,"HyperDash":true}]},{"StartTime":16702.0,"Objects":[{"StartTime":16702.0,"Position":336.0,"HyperDash":false}]},{"StartTime":16861.0,"Objects":[{"StartTime":16861.0,"Position":200.0,"HyperDash":false},{"StartTime":16922.0,"Position":217.0,"HyperDash":false},{"StartTime":17019.0,"Position":200.0,"HyperDash":true}]},{"StartTime":17178.0,"Objects":[{"StartTime":17178.0,"Position":440.0,"HyperDash":false}]},{"StartTime":17337.0,"Objects":[{"StartTime":17337.0,"Position":304.0,"HyperDash":false}]},{"StartTime":17496.0,"Objects":[{"StartTime":17496.0,"Position":408.0,"HyperDash":false},{"StartTime":17557.0,"Position":461.677216,"HyperDash":false},{"StartTime":17654.0,"Position":503.0,"HyperDash":false}]},{"StartTime":17813.0,"Objects":[{"StartTime":17813.0,"Position":360.0,"HyperDash":false}]},{"StartTime":17972.0,"Objects":[{"StartTime":17972.0,"Position":496.0,"HyperDash":false},{"StartTime":18033.0,"Position":511.0,"HyperDash":false},{"StartTime":18130.0,"Position":496.0,"HyperDash":true}]},{"StartTime":18289.0,"Objects":[{"StartTime":18289.0,"Position":256.0,"HyperDash":false},{"StartTime":18350.0,"Position":236.322784,"HyperDash":false},{"StartTime":18447.0,"Position":161.0,"HyperDash":true}]},{"StartTime":18607.0,"Objects":[{"StartTime":18607.0,"Position":392.0,"HyperDash":false},{"StartTime":18668.0,"Position":401.0,"HyperDash":false},{"StartTime":18765.0,"Position":392.0,"HyperDash":false}]},{"StartTime":18924.0,"Objects":[{"StartTime":18924.0,"Position":256.0,"HyperDash":false}]},{"StartTime":19083.0,"Objects":[{"StartTime":19083.0,"Position":392.0,"HyperDash":true}]},{"StartTime":19242.0,"Objects":[{"StartTime":19242.0,"Position":152.0,"HyperDash":false}]},{"StartTime":19400.0,"Objects":[{"StartTime":19400.0,"Position":288.0,"HyperDash":false},{"StartTime":19461.0,"Position":271.0,"HyperDash":false},{"StartTime":19558.0,"Position":288.0,"HyperDash":true}]},{"StartTime":19718.0,"Objects":[{"StartTime":19718.0,"Position":48.0,"HyperDash":false},{"StartTime":19779.0,"Position":53.0,"HyperDash":false},{"StartTime":19876.0,"Position":48.0,"HyperDash":false}]},{"StartTime":20035.0,"Objects":[{"StartTime":20035.0,"Position":168.0,"HyperDash":false}]},{"StartTime":20194.0,"Objects":[{"StartTime":20194.0,"Position":48.0,"HyperDash":false},{"StartTime":20273.0,"Position":83.30042,"HyperDash":false},{"StartTime":20352.0,"Position":142.600845,"HyperDash":false},{"StartTime":20431.0,"Position":207.90126,"HyperDash":false},{"StartTime":20511.0,"Position":237.800415,"HyperDash":false},{"StartTime":20572.0,"Position":290.323517,"HyperDash":false},{"StartTime":20670.0,"Position":333.0,"HyperDash":true}]},{"StartTime":20829.0,"Objects":[{"StartTime":20829.0,"Position":88.0,"HyperDash":false},{"StartTime":20890.0,"Position":91.0,"HyperDash":false},{"StartTime":20987.0,"Position":88.0,"HyperDash":false}]},{"StartTime":21146.0,"Objects":[{"StartTime":21146.0,"Position":232.0,"HyperDash":false},{"StartTime":21207.0,"Position":222.0,"HyperDash":false},{"StartTime":21304.0,"Position":232.0,"HyperDash":false}]},{"StartTime":21464.0,"Objects":[{"StartTime":21464.0,"Position":88.0,"HyperDash":false},{"StartTime":21525.0,"Position":125.677216,"HyperDash":false},{"StartTime":21622.0,"Position":183.0,"HyperDash":false}]},{"StartTime":21781.0,"Objects":[{"StartTime":21781.0,"Position":320.0,"HyperDash":false}]},{"StartTime":21940.0,"Objects":[{"StartTime":21940.0,"Position":184.0,"HyperDash":false},{"StartTime":22001.0,"Position":174.0,"HyperDash":false},{"StartTime":22098.0,"Position":184.0,"HyperDash":false}]},{"StartTime":22258.0,"Objects":[{"StartTime":22258.0,"Position":320.0,"HyperDash":false},{"StartTime":22319.0,"Position":320.0,"HyperDash":false},{"StartTime":22416.0,"Position":320.0,"HyperDash":false}]},{"StartTime":22575.0,"Objects":[{"StartTime":22575.0,"Position":184.0,"HyperDash":false},{"StartTime":22636.0,"Position":166.0,"HyperDash":false},{"StartTime":22733.0,"Position":184.0,"HyperDash":false}]},{"StartTime":22892.0,"Objects":[{"StartTime":22892.0,"Position":320.0,"HyperDash":false}]},{"StartTime":23051.0,"Objects":[{"StartTime":23051.0,"Position":184.0,"HyperDash":false},{"StartTime":23112.0,"Position":131.322784,"HyperDash":false},{"StartTime":23209.0,"Position":89.0,"HyperDash":true}]},{"StartTime":23369.0,"Objects":[{"StartTime":23369.0,"Position":328.0,"HyperDash":false},{"StartTime":23448.0,"Position":383.3004,"HyperDash":false},{"StartTime":23527.0,"Position":422.60083,"HyperDash":false},{"StartTime":23607.0,"Position":470.5,"HyperDash":false}]},{"StartTime":23686.0,"Objects":[{"StartTime":23686.0,"Position":416.0,"HyperDash":false}]},{"StartTime":23845.0,"Objects":[{"StartTime":23845.0,"Position":280.0,"HyperDash":false},{"StartTime":23924.0,"Position":212.649841,"HyperDash":false},{"StartTime":24003.0,"Position":185.0,"HyperDash":false},{"StartTime":24064.0,"Position":216.261841,"HyperDash":false},{"StartTime":24162.0,"Position":280.0,"HyperDash":false}]},{"StartTime":24321.0,"Objects":[{"StartTime":24321.0,"Position":424.0,"HyperDash":false}]},{"StartTime":24480.0,"Objects":[{"StartTime":24480.0,"Position":288.0,"HyperDash":false},{"StartTime":24559.0,"Position":324.350159,"HyperDash":false},{"StartTime":24638.0,"Position":382.700317,"HyperDash":false},{"StartTime":24699.0,"Position":417.261841,"HyperDash":false},{"StartTime":24797.0,"Position":478.0,"HyperDash":false}]},{"StartTime":24956.0,"Objects":[{"StartTime":24956.0,"Position":360.0,"HyperDash":false}]},{"StartTime":25115.0,"Objects":[{"StartTime":25115.0,"Position":224.0,"HyperDash":false}]},{"StartTime":25273.0,"Objects":[{"StartTime":25273.0,"Position":360.0,"HyperDash":false},{"StartTime":25352.0,"Position":360.0,"HyperDash":false}]},{"StartTime":25432.0,"Objects":[{"StartTime":25432.0,"Position":288.0,"HyperDash":false},{"StartTime":25511.0,"Position":288.0,"HyperDash":true}]},{"StartTime":25591.0,"Objects":[{"StartTime":25591.0,"Position":448.0,"HyperDash":false},{"StartTime":25652.0,"Position":465.0,"HyperDash":false},{"StartTime":25749.0,"Position":448.0,"HyperDash":true}]},{"StartTime":25908.0,"Objects":[{"StartTime":25908.0,"Position":208.0,"HyperDash":false},{"StartTime":25969.0,"Position":154.322784,"HyperDash":false},{"StartTime":26066.0,"Position":113.0,"HyperDash":false}]},{"StartTime":26226.0,"Objects":[{"StartTime":26226.0,"Position":248.0,"HyperDash":false},{"StartTime":26287.0,"Position":289.677216,"HyperDash":false},{"StartTime":26384.0,"Position":343.0,"HyperDash":false}]},{"StartTime":26543.0,"Objects":[{"StartTime":26543.0,"Position":208.0,"HyperDash":false},{"StartTime":26604.0,"Position":227.0,"HyperDash":false},{"StartTime":26701.0,"Position":208.0,"HyperDash":false}]},{"StartTime":26861.0,"Objects":[{"StartTime":26861.0,"Position":344.0,"HyperDash":false}]},{"StartTime":27019.0,"Objects":[{"StartTime":27019.0,"Position":208.0,"HyperDash":false},{"StartTime":27080.0,"Position":181.322784,"HyperDash":false},{"StartTime":27177.0,"Position":113.0,"HyperDash":false}]},{"StartTime":27337.0,"Objects":[{"StartTime":27337.0,"Position":248.0,"HyperDash":false},{"StartTime":27398.0,"Position":294.677216,"HyperDash":false},{"StartTime":27495.0,"Position":343.0,"HyperDash":false}]},{"StartTime":27654.0,"Objects":[{"StartTime":27654.0,"Position":208.0,"HyperDash":false}]},{"StartTime":27813.0,"Objects":[{"StartTime":27813.0,"Position":344.0,"HyperDash":false}]},{"StartTime":27972.0,"Objects":[{"StartTime":27972.0,"Position":208.0,"HyperDash":false}]},{"StartTime":28131.0,"Objects":[{"StartTime":28131.0,"Position":344.0,"HyperDash":false},{"StartTime":28192.0,"Position":384.677216,"HyperDash":false},{"StartTime":28289.0,"Position":439.0,"HyperDash":true}]},{"StartTime":28448.0,"Objects":[{"StartTime":28448.0,"Position":208.0,"HyperDash":false},{"StartTime":28527.0,"Position":167.699585,"HyperDash":false},{"StartTime":28606.0,"Position":113.399155,"HyperDash":false},{"StartTime":28686.0,"Position":65.5,"HyperDash":false}]},{"StartTime":28765.0,"Objects":[{"StartTime":28765.0,"Position":120.0,"HyperDash":false}]},{"StartTime":28924.0,"Objects":[{"StartTime":28924.0,"Position":256.0,"HyperDash":false},{"StartTime":29003.0,"Position":288.350159,"HyperDash":false},{"StartTime":29082.0,"Position":351.0,"HyperDash":false},{"StartTime":29143.0,"Position":311.738159,"HyperDash":false},{"StartTime":29241.0,"Position":256.0,"HyperDash":false}]},{"StartTime":29400.0,"Objects":[{"StartTime":29400.0,"Position":112.0,"HyperDash":false}]},{"StartTime":29559.0,"Objects":[{"StartTime":29559.0,"Position":248.0,"HyperDash":false},{"StartTime":29638.0,"Position":190.649841,"HyperDash":false},{"StartTime":29717.0,"Position":153.299683,"HyperDash":false},{"StartTime":29778.0,"Position":125.738174,"HyperDash":false},{"StartTime":29876.0,"Position":58.0,"HyperDash":false}]},{"StartTime":30035.0,"Objects":[{"StartTime":30035.0,"Position":192.0,"HyperDash":false}]},{"StartTime":30194.0,"Objects":[{"StartTime":30194.0,"Position":328.0,"HyperDash":false}]},{"StartTime":30353.0,"Objects":[{"StartTime":30353.0,"Position":192.0,"HyperDash":false},{"StartTime":30414.0,"Position":196.0,"HyperDash":false},{"StartTime":30511.0,"Position":192.0,"HyperDash":true}]},{"StartTime":30670.0,"Objects":[{"StartTime":30670.0,"Position":432.0,"HyperDash":false},{"StartTime":30749.0,"Position":384.5,"HyperDash":true}]},{"StartTime":30829.0,"Objects":[{"StartTime":30829.0,"Position":192.0,"HyperDash":false},{"StartTime":30908.0,"Position":144.5,"HyperDash":true}]},{"StartTime":30988.0,"Objects":[{"StartTime":30988.0,"Position":336.0,"HyperDash":false},{"StartTime":31049.0,"Position":326.0,"HyperDash":false},{"StartTime":31146.0,"Position":336.0,"HyperDash":false}]},{"StartTime":31305.0,"Objects":[{"StartTime":31305.0,"Position":208.0,"HyperDash":false},{"StartTime":31366.0,"Position":198.0,"HyperDash":false},{"StartTime":31463.0,"Position":208.0,"HyperDash":false}]},{"StartTime":31623.0,"Objects":[{"StartTime":31623.0,"Position":80.0,"HyperDash":false},{"StartTime":31684.0,"Position":87.0,"HyperDash":false},{"StartTime":31781.0,"Position":80.0,"HyperDash":false}]},{"StartTime":31940.0,"Objects":[{"StartTime":31940.0,"Position":208.0,"HyperDash":false}]},{"StartTime":32099.0,"Objects":[{"StartTime":32099.0,"Position":80.0,"HyperDash":false},{"StartTime":32160.0,"Position":130.677216,"HyperDash":false},{"StartTime":32257.0,"Position":175.0,"HyperDash":false}]},{"StartTime":32416.0,"Objects":[{"StartTime":32416.0,"Position":296.0,"HyperDash":false},{"StartTime":32477.0,"Position":303.0,"HyperDash":false},{"StartTime":32574.0,"Position":296.0,"HyperDash":false}]},{"StartTime":32734.0,"Objects":[{"StartTime":32734.0,"Position":176.0,"HyperDash":false},{"StartTime":32795.0,"Position":188.0,"HyperDash":false},{"StartTime":32892.0,"Position":176.0,"HyperDash":false}]},{"StartTime":33051.0,"Objects":[{"StartTime":33051.0,"Position":296.0,"HyperDash":false},{"StartTime":33130.0,"Position":250.649841,"HyperDash":false},{"StartTime":33209.0,"Position":201.0,"HyperDash":false},{"StartTime":33270.0,"Position":218.261841,"HyperDash":false},{"StartTime":33368.0,"Position":296.0,"HyperDash":true}]},{"StartTime":33527.0,"Objects":[{"StartTime":33527.0,"Position":48.0,"HyperDash":false}]},{"StartTime":33686.0,"Objects":[{"StartTime":33686.0,"Position":160.0,"HyperDash":false}]},{"StartTime":33845.0,"Objects":[{"StartTime":33845.0,"Position":272.0,"HyperDash":false}]},{"StartTime":34004.0,"Objects":[{"StartTime":34004.0,"Position":160.0,"HyperDash":false}]},{"StartTime":34162.0,"Objects":[{"StartTime":34162.0,"Position":304.0,"HyperDash":false},{"StartTime":34241.0,"Position":332.587769,"HyperDash":false},{"StartTime":34320.0,"Position":375.266571,"HyperDash":false},{"StartTime":34381.0,"Position":377.239929,"HyperDash":false},{"StartTime":34479.0,"Position":320.985657,"HyperDash":false}]},{"StartTime":34638.0,"Objects":[{"StartTime":34638.0,"Position":184.0,"HyperDash":false},{"StartTime":34717.0,"Position":224.350159,"HyperDash":false},{"StartTime":34796.0,"Position":278.700317,"HyperDash":false},{"StartTime":34857.0,"Position":313.261841,"HyperDash":false},{"StartTime":34955.0,"Position":374.0,"HyperDash":false}]},{"StartTime":35035.0,"Objects":[{"StartTime":35035.0,"Position":440.0,"HyperDash":false}]},{"StartTime":35115.0,"Objects":[{"StartTime":35115.0,"Position":376.0,"HyperDash":false}]},{"StartTime":35273.0,"Objects":[{"StartTime":35273.0,"Position":224.0,"HyperDash":false}]},{"StartTime":35432.0,"Objects":[{"StartTime":35432.0,"Position":368.0,"HyperDash":false},{"StartTime":35511.0,"Position":430.6414,"HyperDash":false},{"StartTime":35590.0,"Position":439.319336,"HyperDash":false},{"StartTime":35651.0,"Position":442.987732,"HyperDash":false},{"StartTime":35749.0,"Position":381.729523,"HyperDash":false}]},{"StartTime":35908.0,"Objects":[{"StartTime":35908.0,"Position":288.0,"HyperDash":true}]},{"StartTime":36067.0,"Objects":[{"StartTime":36067.0,"Position":72.0,"HyperDash":false}]},{"StartTime":36146.0,"Objects":[{"StartTime":36146.0,"Position":16.0,"HyperDash":false}]},{"StartTime":36226.0,"Objects":[{"StartTime":36226.0,"Position":16.0,"HyperDash":false}]},{"StartTime":36305.0,"Objects":[{"StartTime":36305.0,"Position":72.0,"HyperDash":true}]},{"StartTime":36385.0,"Objects":[{"StartTime":36385.0,"Position":264.0,"HyperDash":false}]},{"StartTime":36464.0,"Objects":[{"StartTime":36464.0,"Position":328.0,"HyperDash":false}]},{"StartTime":36543.0,"Objects":[{"StartTime":36543.0,"Position":264.0,"HyperDash":false}]},{"StartTime":36623.0,"Objects":[{"StartTime":36623.0,"Position":200.0,"HyperDash":true}]},{"StartTime":36702.0,"Objects":[{"StartTime":36702.0,"Position":392.0,"HyperDash":false},{"StartTime":36781.0,"Position":439.5,"HyperDash":true}]},{"StartTime":36861.0,"Objects":[{"StartTime":36861.0,"Position":232.0,"HyperDash":false},{"StartTime":36940.0,"Position":226.108353,"HyperDash":false}]},{"StartTime":37019.0,"Objects":[{"StartTime":37019.0,"Position":304.0,"HyperDash":false},{"StartTime":37098.0,"Position":315.520447,"HyperDash":true}]},{"StartTime":37178.0,"Objects":[{"StartTime":37178.0,"Position":104.0,"HyperDash":false},{"StartTime":37257.0,"Position":56.5,"HyperDash":true}]},{"StartTime":37337.0,"Objects":[{"StartTime":37337.0,"Position":264.0,"HyperDash":false},{"StartTime":37416.0,"Position":279.0208,"HyperDash":false}]},{"StartTime":37496.0,"Objects":[{"StartTime":37496.0,"Position":208.0,"HyperDash":false},{"StartTime":37575.0,"Position":201.282486,"HyperDash":true}]},{"StartTime":37654.0,"Objects":[{"StartTime":37654.0,"Position":392.0,"HyperDash":false}]},{"StartTime":37734.0,"Objects":[{"StartTime":37734.0,"Position":448.0,"HyperDash":false}]},{"StartTime":37813.0,"Objects":[{"StartTime":37813.0,"Position":448.0,"HyperDash":false}]},{"StartTime":37892.0,"Objects":[{"StartTime":37892.0,"Position":392.0,"HyperDash":true}]},{"StartTime":37972.0,"Objects":[{"StartTime":37972.0,"Position":192.0,"HyperDash":false},{"StartTime":38051.0,"Position":239.5,"HyperDash":true}]},{"StartTime":38131.0,"Objects":[{"StartTime":38131.0,"Position":410.0,"HyperDash":false},{"StartTime":38210.0,"Position":457.5,"HyperDash":true}]},{"StartTime":38289.0,"Objects":[{"StartTime":38289.0,"Position":264.0,"HyperDash":false},{"StartTime":38368.0,"Position":216.5,"HyperDash":true}]},{"StartTime":38448.0,"Objects":[{"StartTime":38448.0,"Position":448.0,"HyperDash":false},{"StartTime":38527.0,"Position":495.5,"HyperDash":true}]},{"StartTime":38607.0,"Objects":[{"StartTime":38607.0,"Position":296.0,"HyperDash":false}]},{"StartTime":38924.0,"Objects":[{"StartTime":38924.0,"Position":440.0,"HyperDash":false}]},{"StartTime":39242.0,"Objects":[{"StartTime":39242.0,"Position":296.0,"HyperDash":false}]},{"StartTime":39559.0,"Objects":[{"StartTime":39559.0,"Position":152.0,"HyperDash":false}]},{"StartTime":39877.0,"Objects":[{"StartTime":39877.0,"Position":352.0,"HyperDash":false},{"StartTime":39956.0,"Position":285.84314,"HyperDash":false},{"StartTime":40035.0,"Position":257.328156,"HyperDash":false},{"StartTime":40114.0,"Position":311.126862,"HyperDash":false},{"StartTime":40194.0,"Position":352.0,"HyperDash":false},{"StartTime":40273.0,"Position":315.962524,"HyperDash":false},{"StartTime":40353.0,"Position":257.328156,"HyperDash":false},{"StartTime":40432.0,"Position":295.60437,"HyperDash":false},{"StartTime":40511.0,"Position":352.0,"HyperDash":false},{"StartTime":40572.0,"Position":308.8265,"HyperDash":false},{"StartTime":40670.0,"Position":257.328156,"HyperDash":false}]},{"StartTime":40829.0,"Objects":[{"StartTime":40829.0,"Position":432.0,"HyperDash":true},{"StartTime":40890.0,"Position":349.476257,"HyperDash":false},{"StartTime":40987.0,"Position":218.25,"HyperDash":true}]},{"StartTime":41146.0,"Objects":[{"StartTime":41146.0,"Position":440.0,"HyperDash":false},{"StartTime":41225.0,"Position":485.905121,"HyperDash":false},{"StartTime":41304.0,"Position":484.021484,"HyperDash":false},{"StartTime":41384.0,"Position":448.07428,"HyperDash":true}]},{"StartTime":41464.0,"Objects":[{"StartTime":41464.0,"Position":256.0,"HyperDash":false}]},{"StartTime":41623.0,"Objects":[{"StartTime":41623.0,"Position":400.0,"HyperDash":true}]},{"StartTime":41781.0,"Objects":[{"StartTime":41781.0,"Position":168.0,"HyperDash":false},{"StartTime":41842.0,"Position":176.0,"HyperDash":false},{"StartTime":41939.0,"Position":168.0,"HyperDash":true}]},{"StartTime":42099.0,"Objects":[{"StartTime":42099.0,"Position":400.0,"HyperDash":false}]},{"StartTime":42258.0,"Objects":[{"StartTime":42258.0,"Position":256.0,"HyperDash":false},{"StartTime":42319.0,"Position":267.0,"HyperDash":false},{"StartTime":42416.0,"Position":256.0,"HyperDash":false}]},{"StartTime":42575.0,"Objects":[{"StartTime":42575.0,"Position":400.0,"HyperDash":false}]},{"StartTime":42734.0,"Objects":[{"StartTime":42734.0,"Position":256.0,"HyperDash":true}]},{"StartTime":42892.0,"Objects":[{"StartTime":42892.0,"Position":488.0,"HyperDash":false},{"StartTime":42953.0,"Position":480.0,"HyperDash":false},{"StartTime":43050.0,"Position":488.0,"HyperDash":true}]},{"StartTime":43210.0,"Objects":[{"StartTime":43210.0,"Position":256.0,"HyperDash":false}]},{"StartTime":43369.0,"Objects":[{"StartTime":43369.0,"Position":368.0,"HyperDash":false}]},{"StartTime":43527.0,"Objects":[{"StartTime":43527.0,"Position":480.0,"HyperDash":true}]},{"StartTime":43686.0,"Objects":[{"StartTime":43686.0,"Position":256.0,"HyperDash":false},{"StartTime":43747.0,"Position":223.322784,"HyperDash":false},{"StartTime":43844.0,"Position":161.0,"HyperDash":true}]},{"StartTime":44004.0,"Objects":[{"StartTime":44004.0,"Position":392.0,"HyperDash":false}]},{"StartTime":44162.0,"Objects":[{"StartTime":44162.0,"Position":248.0,"HyperDash":false},{"StartTime":44223.0,"Position":260.0,"HyperDash":false},{"StartTime":44320.0,"Position":248.0,"HyperDash":true}]},{"StartTime":44480.0,"Objects":[{"StartTime":44480.0,"Position":480.0,"HyperDash":false},{"StartTime":44541.0,"Position":475.0,"HyperDash":false},{"StartTime":44638.0,"Position":480.0,"HyperDash":true}]},{"StartTime":44797.0,"Objects":[{"StartTime":44797.0,"Position":248.0,"HyperDash":false},{"StartTime":44858.0,"Position":285.677216,"HyperDash":false},{"StartTime":44955.0,"Position":343.0,"HyperDash":true}]},{"StartTime":45115.0,"Objects":[{"StartTime":45115.0,"Position":104.0,"HyperDash":false},{"StartTime":45194.0,"Position":104.0,"HyperDash":true}]},{"StartTime":45273.0,"Objects":[{"StartTime":45273.0,"Position":296.0,"HyperDash":false}]},{"StartTime":45432.0,"Objects":[{"StartTime":45432.0,"Position":160.0,"HyperDash":true}]},{"StartTime":45591.0,"Objects":[{"StartTime":45591.0,"Position":392.0,"HyperDash":false},{"StartTime":45652.0,"Position":379.0,"HyperDash":false},{"StartTime":45749.0,"Position":392.0,"HyperDash":true}]},{"StartTime":45908.0,"Objects":[{"StartTime":45908.0,"Position":160.0,"HyperDash":true},{"StartTime":45969.0,"Position":245.523743,"HyperDash":false},{"StartTime":46066.0,"Position":373.75,"HyperDash":true}]},{"StartTime":46226.0,"Objects":[{"StartTime":46226.0,"Position":136.0,"HyperDash":false},{"StartTime":46305.0,"Position":111.869118,"HyperDash":false},{"StartTime":46384.0,"Position":80.52514,"HyperDash":false},{"StartTime":46464.0,"Position":110.225082,"HyperDash":true}]},{"StartTime":46543.0,"Objects":[{"StartTime":46543.0,"Position":304.0,"HyperDash":false}]},{"StartTime":46702.0,"Objects":[{"StartTime":46702.0,"Position":160.0,"HyperDash":true}]},{"StartTime":46861.0,"Objects":[{"StartTime":46861.0,"Position":400.0,"HyperDash":false},{"StartTime":46922.0,"Position":400.0,"HyperDash":false},{"StartTime":47019.0,"Position":400.0,"HyperDash":true}]},{"StartTime":47178.0,"Objects":[{"StartTime":47178.0,"Position":160.0,"HyperDash":false}]},{"StartTime":47337.0,"Objects":[{"StartTime":47337.0,"Position":296.0,"HyperDash":false},{"StartTime":47398.0,"Position":314.677216,"HyperDash":false},{"StartTime":47495.0,"Position":391.0,"HyperDash":false}]},{"StartTime":47654.0,"Objects":[{"StartTime":47654.0,"Position":248.0,"HyperDash":false}]},{"StartTime":47734.0,"Objects":[{"StartTime":47734.0,"Position":304.0,"HyperDash":false}]},{"StartTime":47813.0,"Objects":[{"StartTime":47813.0,"Position":360.0,"HyperDash":true}]},{"StartTime":47972.0,"Objects":[{"StartTime":47972.0,"Position":136.0,"HyperDash":false},{"StartTime":48033.0,"Position":122.0,"HyperDash":false},{"StartTime":48130.0,"Position":136.0,"HyperDash":true}]},{"StartTime":48289.0,"Objects":[{"StartTime":48289.0,"Position":376.0,"HyperDash":false}]},{"StartTime":48448.0,"Objects":[{"StartTime":48448.0,"Position":264.0,"HyperDash":false}]},{"StartTime":48607.0,"Objects":[{"StartTime":48607.0,"Position":152.0,"HyperDash":true}]},{"StartTime":48765.0,"Objects":[{"StartTime":48765.0,"Position":392.0,"HyperDash":false},{"StartTime":48826.0,"Position":391.0,"HyperDash":false},{"StartTime":48923.0,"Position":392.0,"HyperDash":true}]},{"StartTime":49083.0,"Objects":[{"StartTime":49083.0,"Position":160.0,"HyperDash":false}]},{"StartTime":49241.0,"Objects":[{"StartTime":49241.0,"Position":304.0,"HyperDash":false},{"StartTime":49302.0,"Position":321.0,"HyperDash":false},{"StartTime":49399.0,"Position":304.0,"HyperDash":true}]},{"StartTime":49559.0,"Objects":[{"StartTime":49559.0,"Position":64.0,"HyperDash":false},{"StartTime":49620.0,"Position":76.0,"HyperDash":false},{"StartTime":49717.0,"Position":64.0,"HyperDash":true}]},{"StartTime":49877.0,"Objects":[{"StartTime":49877.0,"Position":304.0,"HyperDash":false},{"StartTime":49938.0,"Position":278.322784,"HyperDash":false},{"StartTime":50035.0,"Position":209.0,"HyperDash":true}]},{"StartTime":50194.0,"Objects":[{"StartTime":50194.0,"Position":448.0,"HyperDash":false},{"StartTime":50255.0,"Position":446.0,"HyperDash":false},{"StartTime":50352.0,"Position":448.0,"HyperDash":true}]},{"StartTime":50511.0,"Objects":[{"StartTime":50511.0,"Position":208.0,"HyperDash":false},{"StartTime":50590.0,"Position":160.5,"HyperDash":true}]},{"StartTime":50670.0,"Objects":[{"StartTime":50670.0,"Position":352.0,"HyperDash":false},{"StartTime":50731.0,"Position":369.0,"HyperDash":false},{"StartTime":50828.0,"Position":352.0,"HyperDash":true}]},{"StartTime":50988.0,"Objects":[{"StartTime":50988.0,"Position":128.0,"HyperDash":true},{"StartTime":51049.0,"Position":201.523743,"HyperDash":false},{"StartTime":51146.0,"Position":341.75,"HyperDash":true}]},{"StartTime":51305.0,"Objects":[{"StartTime":51305.0,"Position":104.0,"HyperDash":false},{"StartTime":51384.0,"Position":76.12657,"HyperDash":false},{"StartTime":51463.0,"Position":49.38173,"HyperDash":false},{"StartTime":51543.0,"Position":79.5740662,"HyperDash":true}]},{"StartTime":51623.0,"Objects":[{"StartTime":51623.0,"Position":272.0,"HyperDash":false}]},{"StartTime":51781.0,"Objects":[{"StartTime":51781.0,"Position":128.0,"HyperDash":true}]},{"StartTime":51940.0,"Objects":[{"StartTime":51940.0,"Position":368.0,"HyperDash":false},{"StartTime":52001.0,"Position":357.0,"HyperDash":false},{"StartTime":52098.0,"Position":368.0,"HyperDash":true}]},{"StartTime":52258.0,"Objects":[{"StartTime":52258.0,"Position":128.0,"HyperDash":false}]},{"StartTime":52416.0,"Objects":[{"StartTime":52416.0,"Position":272.0,"HyperDash":false},{"StartTime":52477.0,"Position":276.0,"HyperDash":false},{"StartTime":52574.0,"Position":272.0,"HyperDash":false}]},{"StartTime":52734.0,"Objects":[{"StartTime":52734.0,"Position":128.0,"HyperDash":false}]},{"StartTime":52813.0,"Objects":[{"StartTime":52813.0,"Position":184.0,"HyperDash":false}]},{"StartTime":52892.0,"Objects":[{"StartTime":52892.0,"Position":240.0,"HyperDash":true}]},{"StartTime":53051.0,"Objects":[{"StartTime":53051.0,"Position":16.0,"HyperDash":false},{"StartTime":53112.0,"Position":4.0,"HyperDash":false},{"StartTime":53209.0,"Position":16.0,"HyperDash":true}]},{"StartTime":53369.0,"Objects":[{"StartTime":53369.0,"Position":264.0,"HyperDash":false}]},{"StartTime":53527.0,"Objects":[{"StartTime":53527.0,"Position":152.0,"HyperDash":false}]},{"StartTime":53686.0,"Objects":[{"StartTime":53686.0,"Position":40.0,"HyperDash":true}]},{"StartTime":53845.0,"Objects":[{"StartTime":53845.0,"Position":280.0,"HyperDash":false},{"StartTime":53906.0,"Position":296.0,"HyperDash":false},{"StartTime":54003.0,"Position":280.0,"HyperDash":true}]},{"StartTime":54162.0,"Objects":[{"StartTime":54162.0,"Position":56.0,"HyperDash":false}]},{"StartTime":54321.0,"Objects":[{"StartTime":54321.0,"Position":184.0,"HyperDash":false},{"StartTime":54382.0,"Position":208.677216,"HyperDash":false},{"StartTime":54479.0,"Position":279.0,"HyperDash":true}]},{"StartTime":54638.0,"Objects":[{"StartTime":54638.0,"Position":32.0,"HyperDash":false},{"StartTime":54699.0,"Position":31.0,"HyperDash":false},{"StartTime":54796.0,"Position":32.0,"HyperDash":true}]},{"StartTime":54956.0,"Objects":[{"StartTime":54956.0,"Position":264.0,"HyperDash":false},{"StartTime":55017.0,"Position":287.677216,"HyperDash":false},{"StartTime":55114.0,"Position":359.0,"HyperDash":true}]},{"StartTime":55274.0,"Objects":[{"StartTime":55274.0,"Position":120.0,"HyperDash":false},{"StartTime":55353.0,"Position":120.0,"HyperDash":true}]},{"StartTime":55432.0,"Objects":[{"StartTime":55432.0,"Position":312.0,"HyperDash":false}]},{"StartTime":55591.0,"Objects":[{"StartTime":55591.0,"Position":176.0,"HyperDash":true}]},{"StartTime":55750.0,"Objects":[{"StartTime":55750.0,"Position":408.0,"HyperDash":false},{"StartTime":55811.0,"Position":402.0,"HyperDash":false},{"StartTime":55908.0,"Position":408.0,"HyperDash":true}]},{"StartTime":56067.0,"Objects":[{"StartTime":56067.0,"Position":136.0,"HyperDash":true},{"StartTime":56128.0,"Position":210.523743,"HyperDash":false},{"StartTime":56225.0,"Position":349.75,"HyperDash":true}]},{"StartTime":56385.0,"Objects":[{"StartTime":56385.0,"Position":112.0,"HyperDash":false},{"StartTime":56464.0,"Position":63.0948868,"HyperDash":false},{"StartTime":56543.0,"Position":67.97851,"HyperDash":false},{"StartTime":56623.0,"Position":103.92572,"HyperDash":true}]},{"StartTime":56702.0,"Objects":[{"StartTime":56702.0,"Position":296.0,"HyperDash":false}]},{"StartTime":56861.0,"Objects":[{"StartTime":56861.0,"Position":152.0,"HyperDash":true}]},{"StartTime":57019.0,"Objects":[{"StartTime":57019.0,"Position":392.0,"HyperDash":false},{"StartTime":57080.0,"Position":387.0,"HyperDash":false},{"StartTime":57177.0,"Position":392.0,"HyperDash":true}]},{"StartTime":57337.0,"Objects":[{"StartTime":57337.0,"Position":152.0,"HyperDash":false}]},{"StartTime":57496.0,"Objects":[{"StartTime":57496.0,"Position":296.0,"HyperDash":false},{"StartTime":57557.0,"Position":346.677216,"HyperDash":false},{"StartTime":57654.0,"Position":391.0,"HyperDash":false}]},{"StartTime":57813.0,"Objects":[{"StartTime":57813.0,"Position":248.0,"HyperDash":false}]},{"StartTime":57972.0,"Objects":[{"StartTime":57972.0,"Position":392.0,"HyperDash":true}]},{"StartTime":58131.0,"Objects":[{"StartTime":58131.0,"Position":152.0,"HyperDash":false},{"StartTime":58192.0,"Position":155.0,"HyperDash":false},{"StartTime":58289.0,"Position":152.0,"HyperDash":true}]},{"StartTime":58448.0,"Objects":[{"StartTime":58448.0,"Position":392.0,"HyperDash":false}]},{"StartTime":58607.0,"Objects":[{"StartTime":58607.0,"Position":280.0,"HyperDash":false}]},{"StartTime":58765.0,"Objects":[{"StartTime":58765.0,"Position":168.0,"HyperDash":true}]},{"StartTime":58924.0,"Objects":[{"StartTime":58924.0,"Position":392.0,"HyperDash":false}]},{"StartTime":59083.0,"Objects":[{"StartTime":59083.0,"Position":248.0,"HyperDash":false},{"StartTime":59144.0,"Position":236.0,"HyperDash":false},{"StartTime":59241.0,"Position":248.0,"HyperDash":true}]},{"StartTime":59400.0,"Objects":[{"StartTime":59400.0,"Position":488.0,"HyperDash":false},{"StartTime":59461.0,"Position":476.0,"HyperDash":false},{"StartTime":59558.0,"Position":488.0,"HyperDash":true}]},{"StartTime":59718.0,"Objects":[{"StartTime":59718.0,"Position":248.0,"HyperDash":false},{"StartTime":59779.0,"Position":233.0,"HyperDash":false},{"StartTime":59876.0,"Position":248.0,"HyperDash":true}]},{"StartTime":60035.0,"Objects":[{"StartTime":60035.0,"Position":488.0,"HyperDash":false},{"StartTime":60114.0,"Position":436.649841,"HyperDash":false},{"StartTime":60193.0,"Position":393.299683,"HyperDash":false},{"StartTime":60254.0,"Position":337.738159,"HyperDash":false},{"StartTime":60352.0,"Position":298.0,"HyperDash":false}]},{"StartTime":60511.0,"Objects":[{"StartTime":60511.0,"Position":448.0,"HyperDash":false},{"StartTime":60572.0,"Position":448.0,"HyperDash":false},{"StartTime":60669.0,"Position":448.0,"HyperDash":true}]},{"StartTime":60829.0,"Objects":[{"StartTime":60829.0,"Position":200.0,"HyperDash":true}]},{"StartTime":60988.0,"Objects":[{"StartTime":60988.0,"Position":448.0,"HyperDash":false},{"StartTime":61067.0,"Position":495.5,"HyperDash":true}]},{"StartTime":61146.0,"Objects":[{"StartTime":61146.0,"Position":304.0,"HyperDash":false},{"StartTime":61225.0,"Position":256.5,"HyperDash":true}]},{"StartTime":61305.0,"Objects":[{"StartTime":61305.0,"Position":448.0,"HyperDash":false},{"StartTime":61384.0,"Position":495.5,"HyperDash":true}]},{"StartTime":61464.0,"Objects":[{"StartTime":61464.0,"Position":304.0,"HyperDash":false},{"StartTime":61543.0,"Position":273.8691,"HyperDash":false},{"StartTime":61622.0,"Position":248.525131,"HyperDash":false},{"StartTime":61702.0,"Position":278.225067,"HyperDash":true}]},{"StartTime":61781.0,"Objects":[{"StartTime":61781.0,"Position":448.0,"HyperDash":false},{"StartTime":61860.0,"Position":503.905121,"HyperDash":false},{"StartTime":61939.0,"Position":492.021484,"HyperDash":false},{"StartTime":62019.0,"Position":456.07428,"HyperDash":true}]},{"StartTime":62099.0,"Objects":[{"StartTime":62099.0,"Position":272.0,"HyperDash":false}]},{"StartTime":62258.0,"Objects":[{"StartTime":62258.0,"Position":408.0,"HyperDash":true}]},{"StartTime":62416.0,"Objects":[{"StartTime":62416.0,"Position":168.0,"HyperDash":false}]},{"StartTime":62575.0,"Objects":[{"StartTime":62575.0,"Position":312.0,"HyperDash":false},{"StartTime":62636.0,"Position":301.0,"HyperDash":false},{"StartTime":62733.0,"Position":312.0,"HyperDash":true}]},{"StartTime":62892.0,"Objects":[{"StartTime":62892.0,"Position":72.0,"HyperDash":false},{"StartTime":62953.0,"Position":58.0,"HyperDash":false},{"StartTime":63050.0,"Position":72.0,"HyperDash":true}]},{"StartTime":63210.0,"Objects":[{"StartTime":63210.0,"Position":312.0,"HyperDash":false}]},{"StartTime":63369.0,"Objects":[{"StartTime":63369.0,"Position":176.0,"HyperDash":false},{"StartTime":63430.0,"Position":194.0,"HyperDash":false},{"StartTime":63527.0,"Position":176.0,"HyperDash":false}]},{"StartTime":63686.0,"Objects":[{"StartTime":63686.0,"Position":312.0,"HyperDash":false}]},{"StartTime":63845.0,"Objects":[{"StartTime":63845.0,"Position":176.0,"HyperDash":true}]},{"StartTime":64004.0,"Objects":[{"StartTime":64004.0,"Position":408.0,"HyperDash":false},{"StartTime":64083.0,"Position":460.8734,"HyperDash":false},{"StartTime":64162.0,"Position":462.618256,"HyperDash":false},{"StartTime":64242.0,"Position":432.4259,"HyperDash":true}]},{"StartTime":64321.0,"Objects":[{"StartTime":64321.0,"Position":240.0,"HyperDash":false}]},{"StartTime":64480.0,"Objects":[{"StartTime":64480.0,"Position":376.0,"HyperDash":true}]},{"StartTime":64638.0,"Objects":[{"StartTime":64638.0,"Position":136.0,"HyperDash":false},{"StartTime":64699.0,"Position":119.0,"HyperDash":false},{"StartTime":64796.0,"Position":136.0,"HyperDash":false}]},{"StartTime":64956.0,"Objects":[{"StartTime":64956.0,"Position":272.0,"HyperDash":true}]},{"StartTime":65115.0,"Objects":[{"StartTime":65115.0,"Position":32.0,"HyperDash":false},{"StartTime":65176.0,"Position":26.0,"HyperDash":false},{"StartTime":65273.0,"Position":32.0,"HyperDash":true}]},{"StartTime":65432.0,"Objects":[{"StartTime":65432.0,"Position":272.0,"HyperDash":false},{"StartTime":65493.0,"Position":314.677216,"HyperDash":false},{"StartTime":65590.0,"Position":367.0,"HyperDash":true}]},{"StartTime":65750.0,"Objects":[{"StartTime":65750.0,"Position":128.0,"HyperDash":false}]},{"StartTime":65908.0,"Objects":[{"StartTime":65908.0,"Position":264.0,"HyperDash":false}]},{"StartTime":66067.0,"Objects":[{"StartTime":66067.0,"Position":128.0,"HyperDash":false},{"StartTime":66128.0,"Position":140.0,"HyperDash":false},{"StartTime":66225.0,"Position":128.0,"HyperDash":false}]},{"StartTime":66385.0,"Objects":[{"StartTime":66385.0,"Position":264.0,"HyperDash":true}]},{"StartTime":66543.0,"Objects":[{"StartTime":66543.0,"Position":32.0,"HyperDash":false},{"StartTime":66604.0,"Position":19.0,"HyperDash":false},{"StartTime":66701.0,"Position":32.0,"HyperDash":true}]},{"StartTime":66861.0,"Objects":[{"StartTime":66861.0,"Position":280.0,"HyperDash":false}]},{"StartTime":67019.0,"Objects":[{"StartTime":67019.0,"Position":144.0,"HyperDash":false}]},{"StartTime":67178.0,"Objects":[{"StartTime":67178.0,"Position":280.0,"HyperDash":false},{"StartTime":67239.0,"Position":302.677216,"HyperDash":false},{"StartTime":67336.0,"Position":375.0,"HyperDash":true}]},{"StartTime":67496.0,"Objects":[{"StartTime":67496.0,"Position":136.0,"HyperDash":false}]},{"StartTime":67654.0,"Objects":[{"StartTime":67654.0,"Position":272.0,"HyperDash":false},{"StartTime":67733.0,"Position":292.355682,"HyperDash":false},{"StartTime":67812.0,"Position":317.325684,"HyperDash":false},{"StartTime":67892.0,"Position":284.836639,"HyperDash":true}]},{"StartTime":67972.0,"Objects":[{"StartTime":67972.0,"Position":96.0,"HyperDash":false},{"StartTime":68033.0,"Position":82.0,"HyperDash":false},{"StartTime":68130.0,"Position":96.0,"HyperDash":true}]},{"StartTime":68289.0,"Objects":[{"StartTime":68289.0,"Position":328.0,"HyperDash":false}]},{"StartTime":68448.0,"Objects":[{"StartTime":68448.0,"Position":192.0,"HyperDash":false}]},{"StartTime":68607.0,"Objects":[{"StartTime":68607.0,"Position":328.0,"HyperDash":true}]},{"StartTime":68765.0,"Objects":[{"StartTime":68765.0,"Position":96.0,"HyperDash":false}]},{"StartTime":68924.0,"Objects":[{"StartTime":68924.0,"Position":232.0,"HyperDash":true}]},{"StartTime":69083.0,"Objects":[{"StartTime":69083.0,"Position":472.0,"HyperDash":false},{"StartTime":69144.0,"Position":478.0,"HyperDash":false},{"StartTime":69241.0,"Position":472.0,"HyperDash":false}]},{"StartTime":69400.0,"Objects":[{"StartTime":69400.0,"Position":368.0,"HyperDash":true}]},{"StartTime":69559.0,"Objects":[{"StartTime":69559.0,"Position":152.0,"HyperDash":false}]},{"StartTime":69718.0,"Objects":[{"StartTime":69718.0,"Position":288.0,"HyperDash":false}]},{"StartTime":69877.0,"Objects":[{"StartTime":69877.0,"Position":152.0,"HyperDash":true}]},{"StartTime":70035.0,"Objects":[{"StartTime":70035.0,"Position":384.0,"HyperDash":false}]},{"StartTime":70194.0,"Objects":[{"StartTime":70194.0,"Position":248.0,"HyperDash":false},{"StartTime":70255.0,"Position":261.0,"HyperDash":false},{"StartTime":70352.0,"Position":248.0,"HyperDash":false}]},{"StartTime":70511.0,"Objects":[{"StartTime":70511.0,"Position":384.0,"HyperDash":false}]},{"StartTime":70670.0,"Objects":[{"StartTime":70670.0,"Position":248.0,"HyperDash":false},{"StartTime":70749.0,"Position":194.869125,"HyperDash":false},{"StartTime":70828.0,"Position":192.525131,"HyperDash":false},{"StartTime":70908.0,"Position":222.225082,"HyperDash":true}]},{"StartTime":70988.0,"Objects":[{"StartTime":70988.0,"Position":416.0,"HyperDash":false},{"StartTime":71049.0,"Position":426.0,"HyperDash":false},{"StartTime":71146.0,"Position":416.0,"HyperDash":false}]},{"StartTime":71226.0,"Objects":[{"StartTime":71226.0,"Position":352.0,"HyperDash":true}]},{"StartTime":71305.0,"Objects":[{"StartTime":71305.0,"Position":168.0,"HyperDash":false},{"StartTime":71384.0,"Position":120.5,"HyperDash":true}]},{"StartTime":71464.0,"Objects":[{"StartTime":71464.0,"Position":312.0,"HyperDash":false},{"StartTime":71543.0,"Position":359.5,"HyperDash":true}]},{"StartTime":71623.0,"Objects":[{"StartTime":71623.0,"Position":168.0,"HyperDash":false},{"StartTime":71684.0,"Position":140.322784,"HyperDash":false},{"StartTime":71781.0,"Position":73.0,"HyperDash":true}]},{"StartTime":71940.0,"Objects":[{"StartTime":71940.0,"Position":312.0,"HyperDash":false}]},{"StartTime":72099.0,"Objects":[{"StartTime":72099.0,"Position":168.0,"HyperDash":false},{"StartTime":72160.0,"Position":138.322784,"HyperDash":false},{"StartTime":72257.0,"Position":73.0,"HyperDash":true}]},{"StartTime":72416.0,"Objects":[{"StartTime":72416.0,"Position":312.0,"HyperDash":false},{"StartTime":72477.0,"Position":310.0,"HyperDash":false},{"StartTime":72574.0,"Position":312.0,"HyperDash":false}]},{"StartTime":72734.0,"Objects":[{"StartTime":72734.0,"Position":176.0,"HyperDash":true}]},{"StartTime":72892.0,"Objects":[{"StartTime":72892.0,"Position":416.0,"HyperDash":false}]},{"StartTime":73051.0,"Objects":[{"StartTime":73051.0,"Position":280.0,"HyperDash":false},{"StartTime":73112.0,"Position":286.0,"HyperDash":false},{"StartTime":73209.0,"Position":280.0,"HyperDash":false}]},{"StartTime":73369.0,"Objects":[{"StartTime":73369.0,"Position":416.0,"HyperDash":true}]},{"StartTime":73527.0,"Objects":[{"StartTime":73527.0,"Position":176.0,"HyperDash":false},{"StartTime":73606.0,"Position":130.644318,"HyperDash":false},{"StartTime":73685.0,"Position":130.674316,"HyperDash":false},{"StartTime":73765.0,"Position":163.163345,"HyperDash":true}]},{"StartTime":73845.0,"Objects":[{"StartTime":73845.0,"Position":352.0,"HyperDash":false},{"StartTime":73906.0,"Position":371.0,"HyperDash":false},{"StartTime":74003.0,"Position":352.0,"HyperDash":true}]},{"StartTime":74162.0,"Objects":[{"StartTime":74162.0,"Position":104.0,"HyperDash":false}]},{"StartTime":74321.0,"Objects":[{"StartTime":74321.0,"Position":240.0,"HyperDash":false},{"StartTime":74382.0,"Position":235.0,"HyperDash":false},{"StartTime":74479.0,"Position":240.0,"HyperDash":false}]},{"StartTime":74638.0,"Objects":[{"StartTime":74638.0,"Position":104.0,"HyperDash":true}]},{"StartTime":74797.0,"Objects":[{"StartTime":74797.0,"Position":344.0,"HyperDash":false}]},{"StartTime":74956.0,"Objects":[{"StartTime":74956.0,"Position":208.0,"HyperDash":false}]},{"StartTime":75115.0,"Objects":[{"StartTime":75115.0,"Position":344.0,"HyperDash":true}]},{"StartTime":75273.0,"Objects":[{"StartTime":75273.0,"Position":104.0,"HyperDash":false},{"StartTime":75334.0,"Position":104.0,"HyperDash":false},{"StartTime":75431.0,"Position":104.0,"HyperDash":false}]},{"StartTime":75591.0,"Objects":[{"StartTime":75591.0,"Position":240.0,"HyperDash":true}]},{"StartTime":75750.0,"Objects":[{"StartTime":75750.0,"Position":16.0,"HyperDash":false}]},{"StartTime":75908.0,"Objects":[{"StartTime":75908.0,"Position":152.0,"HyperDash":false}]},{"StartTime":76067.0,"Objects":[{"StartTime":76067.0,"Position":16.0,"HyperDash":false},{"StartTime":76128.0,"Position":31.0,"HyperDash":false},{"StartTime":76225.0,"Position":16.0,"HyperDash":true}]},{"StartTime":76385.0,"Objects":[{"StartTime":76385.0,"Position":256.0,"HyperDash":false},{"StartTime":76446.0,"Position":276.677216,"HyperDash":false},{"StartTime":76543.0,"Position":351.0,"HyperDash":true}]},{"StartTime":76702.0,"Objects":[{"StartTime":76702.0,"Position":112.0,"HyperDash":false}]},{"StartTime":76861.0,"Objects":[{"StartTime":76861.0,"Position":248.0,"HyperDash":false}]},{"StartTime":77019.0,"Objects":[{"StartTime":77019.0,"Position":112.0,"HyperDash":false},{"StartTime":77080.0,"Position":129.0,"HyperDash":false},{"StartTime":77177.0,"Position":112.0,"HyperDash":false}]},{"StartTime":77258.0,"Objects":[{"StartTime":77258.0,"Position":176.0,"HyperDash":true}]},{"StartTime":77337.0,"Objects":[{"StartTime":77337.0,"Position":368.0,"HyperDash":false},{"StartTime":77398.0,"Position":371.0,"HyperDash":false},{"StartTime":77495.0,"Position":368.0,"HyperDash":false}]},{"StartTime":77654.0,"Objects":[{"StartTime":77654.0,"Position":232.0,"HyperDash":false}]},{"StartTime":77813.0,"Objects":[{"StartTime":77813.0,"Position":368.0,"HyperDash":true}]},{"StartTime":77972.0,"Objects":[{"StartTime":77972.0,"Position":80.0,"HyperDash":false}]},{"StartTime":79242.0,"Objects":[{"StartTime":79242.0,"Position":64.0,"HyperDash":false},{"StartTime":79303.0,"Position":60.0,"HyperDash":false},{"StartTime":79400.0,"Position":64.0,"HyperDash":true}]},{"StartTime":79559.0,"Objects":[{"StartTime":79559.0,"Position":296.0,"HyperDash":false}]},{"StartTime":79718.0,"Objects":[{"StartTime":79718.0,"Position":160.0,"HyperDash":false}]},{"StartTime":79876.0,"Objects":[{"StartTime":79876.0,"Position":296.0,"HyperDash":false},{"StartTime":79937.0,"Position":304.0,"HyperDash":false},{"StartTime":80034.0,"Position":296.0,"HyperDash":true}]},{"StartTime":80194.0,"Objects":[{"StartTime":80194.0,"Position":64.0,"HyperDash":true}]},{"StartTime":80353.0,"Objects":[{"StartTime":80353.0,"Position":296.0,"HyperDash":false},{"StartTime":80432.0,"Position":340.5878,"HyperDash":false},{"StartTime":80511.0,"Position":367.266571,"HyperDash":false},{"StartTime":80572.0,"Position":349.239929,"HyperDash":false},{"StartTime":80670.0,"Position":312.985657,"HyperDash":false}]},{"StartTime":80749.0,"Objects":[{"StartTime":80749.0,"Position":256.0,"HyperDash":true}]},{"StartTime":80829.0,"Objects":[{"StartTime":80829.0,"Position":64.0,"HyperDash":false}]},{"StartTime":80988.0,"Objects":[{"StartTime":80988.0,"Position":200.0,"HyperDash":false}]},{"StartTime":81146.0,"Objects":[{"StartTime":81146.0,"Position":64.0,"HyperDash":false},{"StartTime":81207.0,"Position":48.0,"HyperDash":false},{"StartTime":81304.0,"Position":64.0,"HyperDash":true}]},{"StartTime":81464.0,"Objects":[{"StartTime":81464.0,"Position":296.0,"HyperDash":false},{"StartTime":81525.0,"Position":325.677216,"HyperDash":false},{"StartTime":81622.0,"Position":391.0,"HyperDash":true}]},{"StartTime":81781.0,"Objects":[{"StartTime":81781.0,"Position":152.0,"HyperDash":false},{"StartTime":81842.0,"Position":97.3227844,"HyperDash":false},{"StartTime":81939.0,"Position":57.0,"HyperDash":true}]},{"StartTime":82099.0,"Objects":[{"StartTime":82099.0,"Position":296.0,"HyperDash":false}]},{"StartTime":82257.0,"Objects":[{"StartTime":82257.0,"Position":160.0,"HyperDash":false}]},{"StartTime":82416.0,"Objects":[{"StartTime":82416.0,"Position":296.0,"HyperDash":false},{"StartTime":82477.0,"Position":292.0,"HyperDash":false},{"StartTime":82574.0,"Position":296.0,"HyperDash":true}]},{"StartTime":82734.0,"Objects":[{"StartTime":82734.0,"Position":48.0,"HyperDash":true}]},{"StartTime":82892.0,"Objects":[{"StartTime":82892.0,"Position":296.0,"HyperDash":false},{"StartTime":82971.0,"Position":253.649841,"HyperDash":false},{"StartTime":83050.0,"Position":201.299683,"HyperDash":false},{"StartTime":83111.0,"Position":162.738174,"HyperDash":false},{"StartTime":83209.0,"Position":106.0,"HyperDash":false}]},{"StartTime":83289.0,"Objects":[{"StartTime":83289.0,"Position":160.0,"HyperDash":true}]},{"StartTime":83368.0,"Objects":[{"StartTime":83368.0,"Position":352.0,"HyperDash":false}]},{"StartTime":83527.0,"Objects":[{"StartTime":83527.0,"Position":216.0,"HyperDash":false}]},{"StartTime":83686.0,"Objects":[{"StartTime":83686.0,"Position":352.0,"HyperDash":false},{"StartTime":83747.0,"Position":368.0,"HyperDash":false},{"StartTime":83844.0,"Position":352.0,"HyperDash":true}]},{"StartTime":84003.0,"Objects":[{"StartTime":84003.0,"Position":120.0,"HyperDash":false},{"StartTime":84064.0,"Position":80.3227844,"HyperDash":false},{"StartTime":84161.0,"Position":25.0,"HyperDash":true}]},{"StartTime":84321.0,"Objects":[{"StartTime":84321.0,"Position":264.0,"HyperDash":false}]},{"StartTime":84480.0,"Objects":[{"StartTime":84480.0,"Position":128.0,"HyperDash":true}]},{"StartTime":84638.0,"Objects":[{"StartTime":84638.0,"Position":368.0,"HyperDash":false}]},{"StartTime":84797.0,"Objects":[{"StartTime":84797.0,"Position":464.0,"HyperDash":false}]},{"StartTime":84956.0,"Objects":[{"StartTime":84956.0,"Position":464.0,"HyperDash":false}]},{"StartTime":85115.0,"Objects":[{"StartTime":85115.0,"Position":368.0,"HyperDash":false}]},{"StartTime":85273.0,"Objects":[{"StartTime":85273.0,"Position":232.0,"HyperDash":true}]},{"StartTime":85432.0,"Objects":[{"StartTime":85432.0,"Position":472.0,"HyperDash":false},{"StartTime":85493.0,"Position":486.0,"HyperDash":false},{"StartTime":85590.0,"Position":472.0,"HyperDash":true}]},{"StartTime":85750.0,"Objects":[{"StartTime":85750.0,"Position":232.0,"HyperDash":false},{"StartTime":85811.0,"Position":219.0,"HyperDash":false},{"StartTime":85908.0,"Position":232.0,"HyperDash":false}]},{"StartTime":86067.0,"Objects":[{"StartTime":86067.0,"Position":368.0,"HyperDash":false}]},{"StartTime":86226.0,"Objects":[{"StartTime":86226.0,"Position":232.0,"HyperDash":false},{"StartTime":86287.0,"Position":194.322784,"HyperDash":false},{"StartTime":86384.0,"Position":137.0,"HyperDash":false}]},{"StartTime":86543.0,"Objects":[{"StartTime":86543.0,"Position":272.0,"HyperDash":false},{"StartTime":86604.0,"Position":296.677216,"HyperDash":false},{"StartTime":86701.0,"Position":367.0,"HyperDash":true}]},{"StartTime":86861.0,"Objects":[{"StartTime":86861.0,"Position":128.0,"HyperDash":false}]},{"StartTime":87019.0,"Objects":[{"StartTime":87019.0,"Position":264.0,"HyperDash":true}]},{"StartTime":87178.0,"Objects":[{"StartTime":87178.0,"Position":24.0,"HyperDash":false}]},{"StartTime":87337.0,"Objects":[{"StartTime":87337.0,"Position":24.0,"HyperDash":false}]},{"StartTime":87496.0,"Objects":[{"StartTime":87496.0,"Position":160.0,"HyperDash":false}]},{"StartTime":87654.0,"Objects":[{"StartTime":87654.0,"Position":24.0,"HyperDash":true}]},{"StartTime":87813.0,"Objects":[{"StartTime":87813.0,"Position":272.0,"HyperDash":true}]},{"StartTime":87972.0,"Objects":[{"StartTime":87972.0,"Position":24.0,"HyperDash":false}]},{"StartTime":88131.0,"Objects":[{"StartTime":88131.0,"Position":295.0,"HyperDash":false},{"StartTime":88210.0,"Position":311.0,"HyperDash":false},{"StartTime":88289.0,"Position":17.0,"HyperDash":false},{"StartTime":88368.0,"Position":467.0,"HyperDash":false},{"StartTime":88448.0,"Position":30.0,"HyperDash":false},{"StartTime":88527.0,"Position":218.0,"HyperDash":false},{"StartTime":88606.0,"Position":26.0,"HyperDash":false},{"StartTime":88686.0,"Position":16.0,"HyperDash":false},{"StartTime":88765.0,"Position":248.0,"HyperDash":false},{"StartTime":88844.0,"Position":100.0,"HyperDash":false},{"StartTime":88924.0,"Position":24.0,"HyperDash":false},{"StartTime":89003.0,"Position":66.0,"HyperDash":false},{"StartTime":89082.0,"Position":97.0,"HyperDash":false},{"StartTime":89162.0,"Position":267.0,"HyperDash":false},{"StartTime":89241.0,"Position":116.0,"HyperDash":false},{"StartTime":89320.0,"Position":451.0,"HyperDash":false},{"StartTime":89400.0,"Position":414.0,"HyperDash":false}]}]} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3152510.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3152510.osu new file mode 100644 index 0000000000..9d65d5cc19 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3152510.osu @@ -0,0 +1,468 @@ +osu file format v14 + +[General] +StackLeniency: 0.7 +Mode: 2 + +[Difficulty] +HPDrainRate:6 +CircleSize:4.2 +OverallDifficulty:9.2 +ApproachRate:9.2 +SliderMultiplier:1.9 +SliderTickRate:2 + +[Events] +//Background and Video events +//Break Periods +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Layer 4 (Overlay) +//Storyboard Sound Samples + +[TimingPoints] +512,317.460317460317,4,2,1,70,1,0 +2972,-100,4,2,1,5,0,0 +3051,-100,4,2,1,70,0,0 +8051,-100,4,2,1,5,0,0 +8131,-100,4,2,1,70,0,0 +10591,-100,4,2,1,5,0,0 +10670,-100,4,2,1,60,0,0 +12019,-100,4,2,1,5,0,0 +12099,-100,4,2,1,60,0,0 +12654,-100,4,2,1,5,0,0 +12734,-100,4,2,1,60,0,0 +14559,-100,4,2,1,5,0,0 +14638,-100,4,2,1,60,0,0 +17734,-100,4,2,1,5,0,0 +17813,-100,4,2,1,60,0,0 +21385,-100,4,2,1,5,0,0 +21464,-100,4,2,1,60,0,0 +22178,-100,4,2,1,5,0,0 +22257,-100,4,2,1,60,0,0 +30988,-100,4,2,1,50,0,0 +40829,-44.4444444444445,4,2,1,80,0,0 +41067,-44.4444444444445,4,2,1,5,0,0 +41146,-100,4,2,1,80,0,1 +41385,-100,4,2,1,5,0,1 +41464,-100,4,2,1,80,0,1 +45908,-44.4444444444445,4,2,1,80,0,1 +46146,-44.4444444444445,4,2,1,5,0,1 +46226,-100,4,2,1,80,0,1 +46464,-100,4,2,1,5,0,1 +46543,-100,4,2,1,80,0,1 +50988,-44.4444444444445,4,2,1,80,0,1 +51226,-44.4444444444445,4,2,1,5,0,1 +51305,-100,4,2,1,80,0,1 +51543,-100,4,2,1,5,0,1 +51622,-100,4,2,1,80,0,1 +56067,-44.4444444444445,4,2,1,80,0,1 +56305,-44.4444444444445,4,2,1,5,0,1 +56385,-100,4,2,1,80,0,1 +56623,-100,4,2,1,5,0,1 +56702,-100,4,2,1,80,0,1 +61464,-100,4,2,1,70,0,0 +63607,-100,4,2,1,5,0,0 +63686,-100,4,2,1,80,0,0 +66305,-100,4,2,1,5,0,0 +66384,-100,4,2,1,80,0,0 +77972,-100,4,2,1,60,0,0 +79242,-100,4,2,1,70,0,0 +84321,-100,4,2,1,60,0,0 +85670,-100,4,2,1,5,0,0 +85750,-100,4,2,1,60,0,0 +85988,-100,4,2,1,5,0,0 +86068,-100,4,2,1,60,0,0 +88131,-100,4,2,1,50,0,0 +88289,-100,4,2,1,45,0,0 +88448,-100,4,2,1,40,0,0 +88607,-100,4,2,1,35,0,0 +88765,-100,4,2,1,30,0,0 +88924,-100,4,2,1,25,0,0 +89083,-100,4,2,1,20,0,0 +89242,-100,4,2,1,15,0,0 +89400,-100,4,2,1,10,0,0 + +[HitObjects] +368,312,512,6,0,L|368:200,1,95,6|0,3:2|0:2,0:0:0:0: +136,152,829,1,8,0:2:0:0: +272,152,988,1,8,0:2:0:0: +136,192,1146,2,0,L|136:304,1,95,0|0,3:2|3:2,0:2:0:0: +368,96,1464,1,8,0:2:0:0: +136,256,1623,6,0,P|64:208|136:152,1,190,2|0,3:2|3:2,0:2:0:0: +176,144,2019,1,0,3:2:0:0: +368,160,2099,1,8,0:2:0:0: +232,112,2258,1,8,0:2:0:0: +368,224,2416,2,0,L|368:344,1,95,0|0,3:2|3:2,0:0:0:0: +136,152,2734,2,0,L|32:152,1,95,8|0,0:2|0:0,0:2:0:0: +280,176,3051,6,0,L|384:176,1,95,2|0,3:2|3:2,0:2:0:0: +136,96,3369,1,8,0:2:0:0: +272,96,3527,1,8,0:2:0:0: +136,160,3686,2,0,L|136:280,1,95,0|0,3:2|3:2,0:0:0:0: +384,56,4004,1,8,0:2:0:0: +136,216,4162,6,0,L|344:216,1,190,2|0,3:2|3:2,0:2:0:0: +272,168,4559,1,0,3:2:0:0: +80,136,4638,1,8,0:2:0:0: +216,96,4797,1,8,0:2:0:0: +80,192,4956,2,0,L|80:304,1,95,0|0,3:2|3:2,0:0:0:0: +312,144,5273,2,0,L|192:144,1,95,8|0,0:2|0:2,0:2:0:0: +456,184,5591,6,0,L|456:80,1,95,2|0,3:2|3:2,0:0:0:0: +216,264,5908,1,8,0:2:0:0: +352,264,6067,1,8,0:2:0:0: +216,264,6226,2,0,L|216:168,1,95,0|0,3:2|3:2,0:2:0:0: +456,144,6543,1,8,0:2:0:0: +216,184,6702,6,0,P|152:128|216:64,1,190,2|0,3:2|3:2,0:2:0:0: +264,56,7099,1,0,3:2:0:0: +456,184,7178,1,8,0:2:0:0: +320,152,7337,1,8,0:2:0:0: +456,224,7496,2,0,L|456:320,1,95,0|0,3:2|3:2,0:2:0:0: +216,192,7813,2,0,L|112:192,1,95,8|0,0:2|0:0,0:2:0:0: +368,184,8131,6,0,L|368:80,1,95,2|0,3:2|3:2,0:0:0:0: +128,272,8448,1,8,0:2:0:0: +264,264,8607,1,8,0:2:0:0: +128,216,8765,2,0,L|128:120,1,95,0|0,3:2|3:2,0:0:0:0: +368,136,9083,1,8,0:2:0:0: +128,272,9242,6,0,L|344:272,1,190,2|0,3:2|3:2,0:2:0:0: +264,224,9638,1,0,3:2:0:0: +72,144,9718,1,8,0:2:0:0: +208,128,9877,1,8,0:2:0:0: +72,200,10035,2,0,L|72:312,1,95,0|0,3:2|3:2,0:2:0:0: +312,288,10353,2,0,L|208:288,1,95,8|0,0:2|0:0,0:2:0:0: +464,192,10670,6,0,L|464:88,1,95,2|0,3:2|0:0,0:2:0:0: +224,192,10988,2,0,L|224:80,1,95,8|0,0:2|0:0,0:2:0:0: +360,200,11305,1,0,0:2:0:0: +224,192,11464,1,0,0:2:0:0: +464,320,11623,1,8,0:2:0:0: +328,264,11781,6,0,L|328:168,1,95,2|0,3:2|0:0,0:2:0:0: +464,232,12099,2,0,L|464:128,1,95,0|8,3:2|0:2,0:2:0:0: +328,184,12416,2,0,L|432:184,1,95,2|0,0:2|0:0,0:2:0:0: +288,120,12734,1,0,0:2:0:0: +424,128,12892,2,0,L|424:16,1,95,8|0,0:2|0:2,0:0:0:0: +192,192,13210,6,0,L|192:88,1,95,2|0,3:2|0:0,0:2:0:0: +424,200,13527,2,0,L|424:88,1,95,8|0,0:2|0:2,0:2:0:0: +288,176,13845,1,0,0:2:0:0: +424,176,14004,1,0,0:2:0:0: +184,288,14162,1,8,0:2:0:0: +320,248,14321,6,0,L|320:136,1,95,2|0,3:2|0:0,0:2:0:0: +88,176,14638,2,0,L|88:72,1,95,0|8,3:2|0:2,0:0:0:0: +224,176,14956,1,0,0:2:0:0: +88,224,15115,2,0,L|88:128,1,95,2|0,0:2|0:0,0:0:0:0: +328,224,15432,2,0,L|424:224,1,95,8|0,0:2|0:0,0:0:0:0: +192,184,15750,5,2,3:2:0:0: +328,168,15908,1,0,0:0:0:0: +192,240,16067,2,0,L|80:240,1,95,8|0,0:2|0:2,0:2:0:0: +232,168,16385,1,2,0:2:0:0: +96,144,16543,1,0,0:2:0:0: +336,288,16702,1,8,0:2:0:0: +200,256,16861,6,0,L|200:152,1,95,2|0,3:2|0:2,0:2:0:0: +440,168,17178,1,0,3:2:0:0: +304,160,17337,1,8,0:2:0:0: +408,160,17496,2,0,L|504:160,1,95 +360,192,17813,1,0,0:2:0:0: +496,144,17972,2,0,L|496:40,1,95,8|0,0:2|0:2,0:0:0:0: +256,288,18289,6,0,L|128:288,1,95,2|0,3:2|0:0,0:2:0:0: +392,256,18607,2,0,L|392:152,1,95,8|0,0:2|0:2,0:2:0:0: +256,224,18924,1,0,0:2:0:0: +392,224,19083,1,0,0:2:0:0: +152,288,19242,1,8,0:2:0:0: +288,224,19400,6,0,L|288:120,1,95,2|0,3:2|0:0,0:2:0:0: +48,192,19718,2,0,L|48:96,1,95 +168,168,20035,1,0,0:0:0:0: +48,248,20194,2,0,L|344:248,1,285 +88,320,20829,6,0,L|88:224,1,95,6|0,3:2|0:0,0:2:0:0: +232,176,21146,2,0,L|232:80,1,95,8|0,0:2|0:0,0:2:0:0: +88,176,21464,2,0,L|200:176,1,95,0|0,0:2|3:2,0:0:0:0: +320,168,21781,1,8,0:2:0:0: +184,312,21940,6,0,L|184:200,1,95,2|0,3:2|0:0,0:2:0:0: +320,224,22258,2,0,L|320:128,1,95,0|8,3:2|0:2,0:2:0:0: +184,336,22575,2,0,L|184:208,1,95,2|0,3:2|0:0,0:2:0:0: +320,280,22892,1,0,3:2:0:0: +184,264,23051,2,0,L|80:264,1,95,8|0,0:2|0:2,0:2:0:0: +328,216,23369,6,0,L|488:216,1,142.5,6|2,3:2|3:2,0:0:0:0: +416,160,23686,1,0,0:0:0:0: +280,120,23845,2,0,L|184:120,2,95,2|2|0,3:2|0:2|3:2,0:0:0:0: +424,232,24321,1,8,0:2:0:0: +288,176,24480,6,0,L|480:176,1,190,2|2,3:2|3:2,0:2:0:0: +360,120,24956,1,8,0:2:0:0: +224,280,25115,1,0,3:2:0:0: +360,224,25273,2,0,L|360:176,1,47.5,0|0,3:2|3:0,3:0:0:0: +288,152,25432,2,0,L|288:88,1,47.5,0|0,3:0|3:0,3:0:0:0: +448,176,25591,2,0,L|448:56,1,95,8|0,0:2|0:0,0:0:0:0: +208,312,25908,6,0,L|96:312,1,95,2|0,3:2|3:2,0:2:0:0: +248,240,26226,2,0,L|352:240,1,95,8|0,0:2|3:2,0:2:0:0: +208,184,26543,2,0,L|208:80,1,95,0|0,0:2|3:2,0:0:0:0: +344,80,26861,1,8,0:2:0:0: +208,240,27019,6,0,L|104:240,1,95,2|0,3:2|0:2,0:2:0:0: +248,176,27337,2,0,L|352:176,1,95,0|8,3:2|0:2,0:2:0:0: +208,80,27654,1,0,0:2:0:0: +344,248,27813,1,0,3:2:0:0: +208,152,27972,1,0,3:2:0:0: +344,152,28131,2,0,L|456:152,1,95,8|0,0:2|0:2,0:2:0:0: +208,216,28448,6,0,L|48:216,1,142.5,6|2,3:2|3:2,0:2:0:0: +120,160,28765,1,0,0:0:0:0: +256,120,28924,2,0,L|352:120,2,95,2|0|0,3:2|0:2|3:2,0:0:0:0: +112,232,29400,1,8,0:2:0:0: +248,176,29559,6,0,L|56:176,1,190,2|2,3:2|3:2,0:2:0:0: +192,128,30035,1,0,0:0:0:0: +328,184,30194,1,2,3:2:0:0: +192,200,30353,2,0,L|192:104,1,95,8|0,0:2|3:2,0:0:0:0: +432,184,30670,2,0,L|368:184,1,47.5,8|8,0:2|0:2,0:0:0:0: +192,256,30829,2,0,L|136:256,1,47.5,8|8,0:2|0:2,0:0:0:0: +336,304,30988,6,0,L|336:192,1,95,2|0,3:2|0:2,0:2:0:0: +208,176,31305,2,0,L|208:80,1,95,0|0,3:2|0:2,0:2:0:0: +80,192,31623,2,0,L|80:288,1,95,0|0,3:2|0:2,0:2:0:0: +208,224,31940,1,0,3:2:0:0: +80,192,32099,6,0,L|184:192,1,95,0|2,0:2|3:2,0:0:0:0: +296,176,32416,2,0,L|296:56,1,95,0|0,0:2|3:2,0:0:0:0: +176,128,32734,2,0,L|176:24,1,95,0|0,0:2|3:2,0:0:0:0: +296,224,33051,2,0,L|184:224,2,95,0|0|0,0:2|3:2|0:2,0:0:0:0: +48,144,33527,5,0,3:2:0:0: +160,144,33686,1,2,0:2:0:0: +272,144,33845,1,0,3:2:0:0: +160,144,34004,1,2,0:2:0:0: +304,272,34162,2,0,P|376:216|304:168,1,190,0|0,3:2|3:2,0:0:0:0: +184,160,34638,6,0,L|408:160,1,190,2|2,0:2|0:2,0:2:0:0: +440,160,35035,1,0,0:0:0:0: +376,120,35115,1,0,3:2:0:0: +224,248,35273,1,0,0:2:0:0: +368,184,35432,2,0,P|440:136|368:88,1,190,0|0,3:2|3:2,0:2:0:0: +288,80,35908,1,0,0:2:0:0: +72,328,36067,5,4,3:2:0:0: +16,296,36146,1,0,3:0:0:0: +16,240,36226,1,0,3:0:0:0: +72,208,36305,1,0,3:0:0:0: +264,168,36385,1,8,3:0:0:0: +328,168,36464,1,0,3:0:0:0: +264,168,36543,1,0,3:0:0:0: +200,168,36623,1,0,3:0:0:0: +392,272,36702,6,0,L|440:272,1,47.5,8|0,3:0|3:0,0:0:0:0: +232,280,36861,2,0,L|224:216,1,47.5,0|0,3:0|3:0,0:0:0:0: +304,208,37019,2,0,L|320:144,1,47.5,0|0,3:0|3:0,0:0:0:0: +104,96,37178,2,0,L|40:96,1,47.5,0|0,3:0|3:0,0:0:0:0: +264,344,37337,6,0,L|280:296,1,47.5,8|0,3:0|3:0,0:0:0:0: +208,264,37496,2,0,L|200:208,1,47.5,0|0,3:0|3:0,0:0:0:0: +392,192,37654,1,8,3:0:0:0: +448,152,37734,1,0,3:0:0:0: +448,96,37813,1,0,3:0:0:0: +392,64,37892,1,0,3:0:0:0: +192,192,37972,6,0,L|272:192,1,47.5,8|0,3:0|3:0,0:0:0:0: +410,263,38131,2,0,L|458:263,1,47.5,0|0,3:0|3:0,0:0:0:0: +264,160,38289,2,0,L|208:160,1,47.5,8|0,0:0|3:0,0:0:0:0: +448,208,38448,2,0,L|496:208,1,47.5,0|0,3:0|3:0,0:0:0:0: +296,224,38607,5,0,3:2:0:0: +440,152,38924,1,0,3:2:0:0: +296,160,39242,1,0,3:2:0:0: +152,128,39559,1,0,3:2:0:0: +352,264,39877,6,0,L|256:256,5,95 +432,192,40829,6,0,L|184:192,1,213.750008153916,2|0,0:2|0:0,0:2:0:0: +440,264,41146,6,0,P|488:216|440:168,1,142.5,6|0,3:2|0:0,0:2:0:0: +256,168,41464,1,8,0:2:0:0: +400,128,41623,1,8,0:2:0:0: +168,248,41781,2,0,L|168:152,1,95,0|0,3:2|3:2,0:0:0:0: +400,192,42099,1,8,0:2:0:0: +256,136,42258,6,0,L|256:32,1,95,2|0,3:2|0:2,0:2:0:0: +400,248,42575,1,0,3:2:0:0: +256,200,42734,1,8,0:2:0:0: +488,192,42892,2,0,L|488:96,1,95,8|0,0:2|3:2,0:2:0:0: +256,136,43210,1,0,3:2:0:0: +368,136,43369,1,8,0:2:0:0: +480,136,43527,1,0,0:2:0:0: +256,136,43686,6,0,L|136:136,1,95,2|0,3:2|3:2,0:0:0:0: +392,296,44004,1,8,0:2:0:0: +248,248,44162,2,0,L|248:136,1,95,8|0,0:2|3:2,0:0:0:0: +480,184,44480,2,0,L|480:80,1,95,0|8,3:2|0:2,0:0:0:0: +248,248,44797,6,0,L|352:248,1,95,2|0,3:2|0:0,0:2:0:0: +104,176,45115,2,0,L|104:104,1,47.5,0|0,3:2|3:2,0:0:0:0: +296,176,45273,1,8,0:2:0:0: +160,112,45432,1,8,0:2:0:0: +392,200,45591,2,0,L|392:88,1,95,0|0,3:2|3:2,0:0:0:0: +160,176,45908,2,0,L|376:176,1,213.750008153916,10|0,0:2|0:0,0:2:0:0: +136,288,46226,6,0,P|80:232|136:192,1,142.5,6|0,3:2|0:0,0:2:0:0: +304,192,46543,1,8,0:2:0:0: +160,128,46702,1,8,0:2:0:0: +400,296,46861,2,0,L|400:192,1,95,0|0,3:2|3:2,0:0:0:0: +160,72,47178,1,8,0:2:0:0: +296,72,47337,6,0,L|408:72,1,95,2|0,3:2|0:2,0:2:0:0: +248,168,47654,1,0,3:2:0:0: +304,152,47734,1,0,3:2:0:0: +360,128,47813,1,8,0:2:0:0: +136,224,47972,2,0,L|136:128,1,95,8|0,0:2|3:2,0:0:0:0: +376,64,48289,1,0,3:2:0:0: +264,64,48448,1,8,0:2:0:0: +152,64,48607,1,0,0:2:0:0: +392,168,48765,6,0,L|392:72,1,95,2|0,3:2|3:2,0:2:0:0: +160,320,49083,1,8,0:2:0:0: +304,272,49241,2,0,L|304:160,1,95,8|0,0:2|3:2,0:2:0:0: +64,208,49559,2,0,L|64:112,1,95,0|8,3:2|0:2,0:0:0:0: +304,272,49877,6,0,L|200:272,1,95,2|0,3:2|0:0,0:2:0:0: +448,192,50194,2,0,L|448:80,1,95,2|8,3:2|0:2,0:2:0:0: +208,96,50511,2,0,L|144:96,1,47.5,8|0,0:2|0:0,0:0:0:0: +352,96,50670,2,0,L|352:200,1,95,0|0,3:2|3:2,0:2:0:0: +128,160,50988,2,0,L|360:160,1,213.750008153916,2|0,3:2|0:0,0:2:0:0: +104,288,51305,6,0,P|48:240|104:192,1,142.5,6|0,0:2|0:0,0:2:0:0: +272,176,51623,1,8,0:2:0:0: +128,120,51781,1,8,0:2:0:0: +368,280,51940,2,0,L|368:176,1,95,0|0,3:2|3:2,0:0:0:0: +128,184,52258,1,8,0:2:0:0: +272,184,52416,6,0,L|272:80,1,95,2|0,3:2|0:2,0:2:0:0: +128,120,52734,1,0,3:2:0:0: +184,112,52813,1,0,3:2:0:0: +240,96,52892,1,8,0:2:0:0: +16,312,53051,2,0,L|16:208,1,95,8|0,0:2|3:2,0:0:0:0: +264,168,53369,1,0,3:2:0:0: +152,168,53527,1,8,0:2:0:0: +40,168,53686,1,0,0:2:0:0: +280,256,53845,6,0,L|280:136,1,95,2|0,3:2|3:2,0:0:0:0: +56,240,54162,1,8,0:2:0:0: +184,232,54321,2,0,L|304:232,1,95,8|0,0:2|3:2,0:0:0:0: +32,320,54638,2,0,L|32:224,1,95,0|8,3:2|0:2,0:0:0:0: +264,248,54956,6,0,L|368:248,1,95,2|0,3:2|0:0,0:2:0:0: +120,176,55274,2,0,L|120:104,1,47.5,2|0,3:2|0:0,0:0:0:0: +312,176,55432,1,8,0:2:0:0: +176,112,55591,1,8,0:2:0:0: +408,200,55750,2,0,L|408:88,1,95,0|0,0:0|3:2,0:0:0:0: +136,288,56067,2,0,L|352:288,1,213.750008153916,10|0,0:2|0:0,0:2:0:0: +112,224,56385,6,0,P|64:176|112:128,1,142.5,6|0,3:2|0:0,0:2:0:0: +296,192,56702,1,8,0:2:0:0: +152,128,56861,1,8,0:2:0:0: +392,296,57019,2,0,L|392:192,1,95,0|0,3:2|3:2,0:2:0:0: +152,184,57337,1,8,0:2:0:0: +296,192,57496,6,0,L|416:192,1,95,2|0,3:2|0:2,0:2:0:0: +248,120,57813,1,0,3:2:0:0: +392,80,57972,1,8,0:2:0:0: +152,288,58131,2,0,L|152:192,1,95,8|0,0:2|3:2,0:2:0:0: +392,184,58448,1,0,3:2:0:0: +280,192,58607,1,8,0:2:0:0: +168,192,58765,1,0,0:2:0:0: +392,272,58924,5,2,3:2:0:0: +248,224,59083,2,0,L|248:120,1,95,0|8,3:2|0:2,0:0:0:0: +488,192,59400,2,0,L|488:96,1,95,10|0,0:2|3:2,0:2:0:0: +248,160,59718,2,0,L|248:56,1,95,2|8,3:2|0:2,0:2:0:0: +488,256,60035,6,0,L|280:256,1,190,2|2,3:2|3:2,0:0:0:0: +448,336,60511,2,0,L|448:232,1,95,2|0,0:2|3:2,0:0:0:0: +200,200,60829,1,8,0:2:0:0: +448,336,60988,2,0,L|504:336,1,47.5,8|8,0:2|0:2,0:0:0:0: +304,208,61146,2,0,L|224:208,1,47.5,8|8,0:2|0:2,0:0:0:0: +448,280,61305,2,0,L|496:280,1,47.5,8|8,0:2|0:2,0:0:0:0: +304,288,61464,6,0,P|248:232|304:192,1,142.5,6|0,3:2|0:0,0:2:0:0: +448,224,61781,2,0,P|496:176|448:128,1,142.5,8|0,0:2|0:0,0:2:0:0: +272,184,62099,1,0,3:2:0:0: +408,128,62258,1,0,3:2:0:0: +168,200,62416,1,8,0:2:0:0: +312,152,62575,6,0,L|312:48,1,95,2|0,3:2|3:2,0:2:0:0: +72,144,62892,2,0,L|72:32,1,95,0|8,0:2|0:2,0:2:0:0: +312,304,63210,1,0,0:2:0:0: +176,232,63369,2,0,L|176:128,1,95,0|0,3:2|0:0,0:2:0:0: +312,232,63686,1,8,0:2:0:0: +176,232,63845,1,0,0:2:0:0: +408,232,64004,6,0,P|464:184|408:136,1,142.5,2|0,3:2|0:0,0:2:0:0: +240,120,64321,1,8,0:2:0:0: +376,64,64480,1,0,0:2:0:0: +136,272,64638,2,0,L|136:168,1,95,0|0,3:2|0:2,0:0:0:0: +272,288,64956,1,8,0:2:0:0: +32,192,65115,6,0,L|32:88,1,95,2|0,3:2|3:2,0:2:0:0: +272,136,65432,2,0,L|368:136,1,95,0|8,0:2|0:2,0:2:0:0: +128,240,65750,1,0,0:2:0:0: +264,192,65908,1,0,3:2:0:0: +128,184,66067,2,0,L|128:80,1,95,8|0,0:2|0:0,0:2:0:0: +264,128,66385,1,10,0:2:0:0: +32,144,66543,6,0,L|32:40,1,95,0|0,0:0|0:2,0:0:0:0: +280,240,66861,1,8,0:2:0:0: +144,240,67019,1,0,0:2:0:0: +280,240,67178,2,0,L|384:240,1,95,0|0,3:2|0:2,0:0:0:0: +136,104,67496,1,8,0:2:0:0: +272,136,67654,6,0,P|320:80|272:32,1,142.5,2|0,3:2|0:0,0:2:0:0: +96,80,67972,2,0,L|96:176,1,95,0|8,0:2|0:2,0:2:0:0: +328,232,68289,1,0,0:2:0:0: +192,224,68448,1,0,3:2:0:0: +328,232,68607,1,0,0:2:0:0: +96,152,68765,1,8,0:2:0:0: +232,136,68924,1,2,3:2:0:0: +472,296,69083,6,0,L|472:176,1,95,0|0,3:2|0:2,0:0:0:0: +368,168,69400,1,8,0:2:0:0: +152,192,69559,1,0,0:2:0:0: +288,160,69718,1,0,3:2:0:0: +152,144,69877,1,0,0:2:0:0: +384,184,70035,1,8,0:2:0:0: +248,168,70194,6,0,L|248:56,1,95,2|0,3:2|0:0,0:2:0:0: +384,120,70511,1,0,0:2:0:0: +248,112,70670,2,0,P|192:56|248:16,1,142.5,8|0,0:2|0:0,0:2:0:0: +416,128,70988,2,0,L|416:24,1,95,0|8,3:2|0:2,0:0:0:0: +352,128,71226,1,8,0:2:0:0: +168,192,71305,2,0,L|96:192,1,47.5,8|8,0:2|0:2,0:0:0:0: +312,208,71464,2,0,L|384:208,1,47.5,8|8,0:2|0:2,0:0:0:0: +168,272,71623,6,0,L|64:272,1,95,6|0,3:2|0:2,0:2:0:0: +312,312,71940,1,8,0:2:0:0: +168,248,72099,2,0,L|56:248,1,95,0|0,3:2|3:2,0:2:0:0: +312,200,72416,2,0,L|312:88,1,95,0|8,0:2|0:2,0:2:0:0: +176,80,72734,1,2,3:2:0:0: +416,264,72892,5,0,3:2:0:0: +280,192,73051,2,0,L|280:80,1,95,0|8,0:2|0:2,0:0:0:0: +416,200,73369,1,0,0:2:0:0: +176,184,73527,2,0,P|128:128|176:80,1,142.5,0|0,3:2|0:0,0:2:0:0: +352,192,73845,2,0,L|352:88,1,95,8|2,0:2|3:2,0:0:0:0: +104,192,74162,5,0,3:2:0:0: +240,144,74321,2,0,L|240:48,1,95,0|8,0:2|0:2,0:2:0:0: +104,104,74638,1,0,0:2:0:0: +344,304,74797,1,0,3:2:0:0: +208,256,74956,1,0,0:2:0:0: +344,240,75115,1,8,0:2:0:0: +104,184,75273,6,0,L|104:80,1,95,2|0,3:2|3:2,0:2:0:0: +240,72,75591,1,0,0:2:0:0: +16,312,75750,1,8,0:2:0:0: +152,320,75908,1,0,0:2:0:0: +16,264,76067,2,0,L|16:168,1,95,0|0,3:2|0:2,0:0:0:0: +256,192,76385,2,0,L|376:192,1,95,10|0,0:2|0:0,0:2:0:0: +112,128,76702,5,0,3:2:0:0: +248,120,76861,1,8,0:2:0:0: +112,176,77019,2,0,L|112:280,1,95,0|0,0:2|3:2,0:0:0:0: +176,176,77258,1,0,3:2:0:0: +368,176,77337,2,0,L|368:80,1,95,8|0,0:2|0:2,0:2:0:0: +232,152,77654,1,0,0:2:0:0: +368,160,77813,1,0,0:2:0:0: +80,96,77972,5,10,0:2:0:0: +64,296,79242,6,0,L|64:184,1,95,6|0,3:2|3:2,0:0:0:0: +296,136,79559,1,8,0:2:0:0: +160,136,79718,1,8,0:2:0:0: +296,176,79876,2,0,L|296:288,1,95,0|0,3:2|3:2,0:2:0:0: +64,80,80194,1,8,0:2:0:0: +296,240,80353,6,0,P|368:192|296:136,1,190,2|0,3:2|3:2,0:2:0:0: +256,128,80749,1,0,3:2:0:0: +64,144,80829,1,8,0:2:0:0: +200,96,80988,1,8,0:2:0:0: +64,208,81146,2,0,L|64:328,1,95,0|0,3:2|3:2,0:2:0:0: +296,136,81464,2,0,L|400:136,1,95,8|0,0:2|0:2,0:2:0:0: +152,160,81781,6,0,L|48:160,1,95,2|0,3:2|3:2,0:2:0:0: +296,80,82099,1,8,0:2:0:0: +160,80,82257,1,8,0:2:0:0: +296,144,82416,2,0,L|296:264,1,95,0|0,3:2|3:2,0:0:0:0: +48,40,82734,1,8,0:2:0:0: +296,200,82892,6,0,L|88:200,1,190,2|0,3:2|3:2,0:2:0:0: +160,152,83289,1,0,3:2:0:0: +352,120,83368,1,8,0:2:0:0: +216,80,83527,1,8,0:2:0:0: +352,176,83686,2,0,L|352:288,1,95,0|0,3:2|3:2,0:2:0:0: +120,128,84003,2,0,L|16:128,1,95,8|0,0:2|0:2,0:0:0:0: +264,232,84321,5,10,0:2:0:0: +128,152,84480,1,8,0:2:0:0: +368,320,84638,1,0,3:2:0:0: +464,272,84797,1,8,0:2:0:0: +464,184,84956,1,0,3:2:0:0: +368,136,85115,1,0,0:2:0:0: +232,104,85273,1,8,0:2:0:0: +472,344,85432,6,0,L|472:240,1,95,10|0,0:2|0:0,0:2:0:0: +232,160,85750,2,0,L|232:40,1,95,10|0,0:2|0:0,0:2:0:0: +368,144,86067,1,8,3:2:0:0: +232,208,86226,2,0,L|136:208,1,95,0|0,3:2|0:2,0:2:0:0: +272,64,86543,2,0,L|400:64,1,95,0|0,3:2|0:2,0:2:0:0: +128,320,86861,5,10,0:0:0:0: +264,272,87019,1,8,0:0:0:0: +24,224,87178,1,2,3:0:0:0: +24,128,87337,1,2,0:0:0:0: +160,104,87496,1,8,3:0:0:0: +24,104,87654,1,0,3:0:0:0: +272,144,87813,1,8,0:0:0:0: +24,56,87972,5,8,0:0:0:0: +256,192,88131,12,0,89400,0:0:0:0: diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3227428-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3227428-expected-conversion.json new file mode 100644 index 0000000000..bd1c6d658f --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3227428-expected-conversion.json @@ -0,0 +1 @@ +{"Mappings":[{"StartTime":22.0,"Objects":[{"StartTime":22.0,"Position":206.0,"HyperDash":false}]},{"StartTime":362.0,"Objects":[{"StartTime":362.0,"Position":137.0,"HyperDash":false},{"StartTime":447.0,"Position":104.571175,"HyperDash":false},{"StartTime":532.0,"Position":91.14235,"HyperDash":false},{"StartTime":617.0,"Position":81.71353,"HyperDash":false},{"StartTime":702.0,"Position":67.18218,"HyperDash":false},{"StartTime":778.0,"Position":97.66308,"HyperDash":false},{"StartTime":854.0,"Position":115.24649,"HyperDash":false},{"StartTime":930.0,"Position":114.82991,"HyperDash":false},{"StartTime":1043.0,"Position":137.0,"HyperDash":false}]},{"StartTime":1385.0,"Objects":[{"StartTime":1385.0,"Position":220.0,"HyperDash":false},{"StartTime":1465.0,"Position":233.326752,"HyperDash":false},{"StartTime":1546.0,"Position":231.079025,"HyperDash":false},{"StartTime":1626.0,"Position":246.1162,"HyperDash":false},{"StartTime":1707.0,"Position":246.012085,"HyperDash":false},{"StartTime":1788.0,"Position":253.358887,"HyperDash":false},{"StartTime":1868.0,"Position":266.303955,"HyperDash":false},{"StartTime":1949.0,"Position":247.094482,"HyperDash":false},{"StartTime":2066.0,"Position":224.02179,"HyperDash":false}]},{"StartTime":2408.0,"Objects":[{"StartTime":2408.0,"Position":160.0,"HyperDash":false},{"StartTime":2493.0,"Position":133.573441,"HyperDash":false},{"StartTime":2578.0,"Position":130.146881,"HyperDash":false},{"StartTime":2663.0,"Position":88.72033,"HyperDash":false},{"StartTime":2748.0,"Position":90.19126,"HyperDash":false},{"StartTime":2824.0,"Position":96.67014,"HyperDash":false},{"StartTime":2900.0,"Position":139.251526,"HyperDash":false},{"StartTime":2976.0,"Position":153.832932,"HyperDash":false},{"StartTime":3089.0,"Position":160.0,"HyperDash":false}]},{"StartTime":3772.0,"Objects":[{"StartTime":3772.0,"Position":340.0,"HyperDash":false}]},{"StartTime":4112.0,"Objects":[{"StartTime":4112.0,"Position":401.0,"HyperDash":false},{"StartTime":4192.0,"Position":414.4298,"HyperDash":false},{"StartTime":4273.0,"Position":393.865021,"HyperDash":false},{"StartTime":4353.0,"Position":385.29483,"HyperDash":false},{"StartTime":4434.0,"Position":415.730042,"HyperDash":false},{"StartTime":4515.0,"Position":398.165222,"HyperDash":false},{"StartTime":4595.0,"Position":407.595062,"HyperDash":false},{"StartTime":4676.0,"Position":389.030243,"HyperDash":false},{"StartTime":4793.0,"Position":404.658875,"HyperDash":false}]},{"StartTime":5135.0,"Objects":[{"StartTime":5135.0,"Position":343.0,"HyperDash":false},{"StartTime":5211.0,"Position":324.279724,"HyperDash":false},{"StartTime":5287.0,"Position":312.955536,"HyperDash":false},{"StartTime":5363.0,"Position":314.093536,"HyperDash":false},{"StartTime":5475.0,"Position":280.640778,"HyperDash":false}]},{"StartTime":5817.0,"Objects":[{"StartTime":5817.0,"Position":189.0,"HyperDash":false},{"StartTime":5902.0,"Position":156.58606,"HyperDash":false},{"StartTime":5987.0,"Position":135.172119,"HyperDash":false},{"StartTime":6072.0,"Position":120.758179,"HyperDash":false},{"StartTime":6157.0,"Position":119.241791,"HyperDash":false},{"StartTime":6233.0,"Position":147.709473,"HyperDash":false},{"StartTime":6309.0,"Position":167.279587,"HyperDash":false},{"StartTime":6385.0,"Position":164.8497,"HyperDash":false},{"StartTime":6498.0,"Position":189.0,"HyperDash":false}]},{"StartTime":6840.0,"Objects":[{"StartTime":6840.0,"Position":208.0,"HyperDash":false},{"StartTime":6920.0,"Position":217.418747,"HyperDash":false},{"StartTime":7001.0,"Position":240.042725,"HyperDash":false},{"StartTime":7081.0,"Position":276.4615,"HyperDash":false},{"StartTime":7162.0,"Position":268.085449,"HyperDash":false},{"StartTime":7243.0,"Position":295.709442,"HyperDash":false},{"StartTime":7323.0,"Position":320.128174,"HyperDash":false},{"StartTime":7404.0,"Position":340.752167,"HyperDash":false},{"StartTime":7521.0,"Position":347.7646,"HyperDash":false}]},{"StartTime":7862.0,"Objects":[{"StartTime":7862.0,"Position":416.0,"HyperDash":false},{"StartTime":7947.0,"Position":441.4566,"HyperDash":false},{"StartTime":8032.0,"Position":446.637848,"HyperDash":false},{"StartTime":8117.0,"Position":454.495941,"HyperDash":false},{"StartTime":8202.0,"Position":442.012817,"HyperDash":false},{"StartTime":8278.0,"Position":447.07373,"HyperDash":false},{"StartTime":8354.0,"Position":416.0334,"HyperDash":false},{"StartTime":8430.0,"Position":431.9387,"HyperDash":false},{"StartTime":8543.0,"Position":416.0,"HyperDash":false}]},{"StartTime":9226.0,"Objects":[{"StartTime":9226.0,"Position":275.0,"HyperDash":false}]},{"StartTime":9567.0,"Objects":[{"StartTime":9567.0,"Position":208.0,"HyperDash":false},{"StartTime":9652.0,"Position":187.257431,"HyperDash":false},{"StartTime":9737.0,"Position":176.772141,"HyperDash":false},{"StartTime":9822.0,"Position":203.593216,"HyperDash":false},{"StartTime":9907.0,"Position":172.761276,"HyperDash":false},{"StartTime":9992.0,"Position":193.308182,"HyperDash":false},{"StartTime":10077.0,"Position":178.273483,"HyperDash":false},{"StartTime":10162.0,"Position":164.69072,"HyperDash":false},{"StartTime":10248.0,"Position":175.555847,"HyperDash":false},{"StartTime":10328.0,"Position":175.714554,"HyperDash":false},{"StartTime":10409.0,"Position":165.140945,"HyperDash":false},{"StartTime":10490.0,"Position":176.860687,"HyperDash":false},{"StartTime":10571.0,"Position":161.863419,"HyperDash":false},{"StartTime":10651.0,"Position":168.073318,"HyperDash":false},{"StartTime":10732.0,"Position":197.565277,"HyperDash":false},{"StartTime":10813.0,"Position":183.264725,"HyperDash":false},{"StartTime":10930.0,"Position":208.0,"HyperDash":false}]},{"StartTime":11272.0,"Objects":[{"StartTime":11272.0,"Position":272.0,"HyperDash":false},{"StartTime":11348.0,"Position":267.478119,"HyperDash":false},{"StartTime":11424.0,"Position":297.956238,"HyperDash":false},{"StartTime":11500.0,"Position":307.4344,"HyperDash":false},{"StartTime":11612.0,"Position":341.244232,"HyperDash":false}]},{"StartTime":11953.0,"Objects":[{"StartTime":11953.0,"Position":397.0,"HyperDash":false},{"StartTime":12038.0,"Position":422.321472,"HyperDash":false},{"StartTime":12123.0,"Position":429.5693,"HyperDash":false},{"StartTime":12208.0,"Position":465.729126,"HyperDash":false},{"StartTime":12293.0,"Position":465.889526,"HyperDash":false},{"StartTime":12369.0,"Position":433.739624,"HyperDash":false},{"StartTime":12445.0,"Position":440.41095,"HyperDash":false},{"StartTime":12521.0,"Position":439.010223,"HyperDash":false},{"StartTime":12634.0,"Position":397.0,"HyperDash":false}]},{"StartTime":12976.0,"Objects":[{"StartTime":12976.0,"Position":309.0,"HyperDash":false},{"StartTime":13052.0,"Position":315.9078,"HyperDash":false},{"StartTime":13128.0,"Position":313.544037,"HyperDash":false},{"StartTime":13204.0,"Position":305.913849,"HyperDash":false},{"StartTime":13316.0,"Position":300.853455,"HyperDash":false}]},{"StartTime":13658.0,"Objects":[{"StartTime":13658.0,"Position":226.0,"HyperDash":false},{"StartTime":13738.0,"Position":220.477478,"HyperDash":false},{"StartTime":13819.0,"Position":214.759888,"HyperDash":false},{"StartTime":13899.0,"Position":167.911179,"HyperDash":false},{"StartTime":13980.0,"Position":163.303925,"HyperDash":false},{"StartTime":14061.0,"Position":139.940048,"HyperDash":false},{"StartTime":14141.0,"Position":139.7893,"HyperDash":false},{"StartTime":14222.0,"Position":130.218536,"HyperDash":false},{"StartTime":14339.0,"Position":106.02227,"HyperDash":false}]},{"StartTime":14681.0,"Objects":[{"StartTime":14681.0,"Position":71.0,"HyperDash":false}]},{"StartTime":15022.0,"Objects":[{"StartTime":15022.0,"Position":109.0,"HyperDash":false},{"StartTime":15102.0,"Position":125.26535,"HyperDash":false},{"StartTime":15183.0,"Position":125.837524,"HyperDash":false},{"StartTime":15263.0,"Position":141.6249,"HyperDash":false},{"StartTime":15344.0,"Position":175.222168,"HyperDash":false},{"StartTime":15425.0,"Position":198.591125,"HyperDash":false},{"StartTime":15505.0,"Position":206.724686,"HyperDash":false},{"StartTime":15586.0,"Position":225.222351,"HyperDash":false},{"StartTime":15703.0,"Position":228.196915,"HyperDash":false}]},{"StartTime":16044.0,"Objects":[{"StartTime":16044.0,"Position":305.0,"HyperDash":false},{"StartTime":16120.0,"Position":322.564636,"HyperDash":false},{"StartTime":16196.0,"Position":317.036682,"HyperDash":false},{"StartTime":16272.0,"Position":334.3818,"HyperDash":false},{"StartTime":16384.0,"Position":373.692017,"HyperDash":false}]},{"StartTime":16726.0,"Objects":[{"StartTime":16726.0,"Position":416.0,"HyperDash":false},{"StartTime":16811.0,"Position":436.3275,"HyperDash":false},{"StartTime":16896.0,"Position":458.65506,"HyperDash":false},{"StartTime":16981.0,"Position":460.982574,"HyperDash":false},{"StartTime":17066.0,"Position":485.412048,"HyperDash":false},{"StartTime":17142.0,"Position":473.021118,"HyperDash":false},{"StartTime":17218.0,"Position":452.528259,"HyperDash":false},{"StartTime":17294.0,"Position":456.035431,"HyperDash":false},{"StartTime":17407.0,"Position":416.0,"HyperDash":false}]},{"StartTime":17749.0,"Objects":[{"StartTime":17749.0,"Position":338.0,"HyperDash":false},{"StartTime":17829.0,"Position":314.194427,"HyperDash":false},{"StartTime":17910.0,"Position":335.3038,"HyperDash":false},{"StartTime":17990.0,"Position":298.49823,"HyperDash":false},{"StartTime":18071.0,"Position":319.4706,"HyperDash":false},{"StartTime":18152.0,"Position":321.945831,"HyperDash":false},{"StartTime":18232.0,"Position":300.43985,"HyperDash":false},{"StartTime":18313.0,"Position":300.91507,"HyperDash":false},{"StartTime":18430.0,"Position":305.712616,"HyperDash":false}]},{"StartTime":18772.0,"Objects":[{"StartTime":18772.0,"Position":293.0,"HyperDash":false}]},{"StartTime":19112.0,"Objects":[{"StartTime":19112.0,"Position":201.0,"HyperDash":false},{"StartTime":19192.0,"Position":184.726288,"HyperDash":false},{"StartTime":19273.0,"Position":174.249146,"HyperDash":false},{"StartTime":19353.0,"Position":136.975433,"HyperDash":false},{"StartTime":19434.0,"Position":143.498291,"HyperDash":false},{"StartTime":19515.0,"Position":130.021179,"HyperDash":false},{"StartTime":19595.0,"Position":94.91766,"HyperDash":false},{"StartTime":19676.0,"Position":90.74364,"HyperDash":false},{"StartTime":19793.0,"Position":63.3811569,"HyperDash":false}]},{"StartTime":20476.0,"Objects":[{"StartTime":20476.0,"Position":129.0,"HyperDash":false},{"StartTime":20556.0,"Position":157.2956,"HyperDash":false},{"StartTime":20637.0,"Position":156.794876,"HyperDash":false},{"StartTime":20717.0,"Position":179.090469,"HyperDash":false},{"StartTime":20798.0,"Position":181.589752,"HyperDash":false},{"StartTime":20879.0,"Position":214.089035,"HyperDash":false},{"StartTime":20959.0,"Position":246.179916,"HyperDash":false},{"StartTime":21040.0,"Position":240.353943,"HyperDash":false},{"StartTime":21157.0,"Position":266.716431,"HyperDash":false}]},{"StartTime":21499.0,"Objects":[{"StartTime":21499.0,"Position":352.0,"HyperDash":false},{"StartTime":21584.0,"Position":366.69278,"HyperDash":false},{"StartTime":21669.0,"Position":363.83432,"HyperDash":false},{"StartTime":21754.0,"Position":383.387665,"HyperDash":false},{"StartTime":21839.0,"Position":413.4099,"HyperDash":false},{"StartTime":21915.0,"Position":398.248138,"HyperDash":false},{"StartTime":21991.0,"Position":402.284668,"HyperDash":false},{"StartTime":22067.0,"Position":383.640961,"HyperDash":false},{"StartTime":22180.0,"Position":352.0,"HyperDash":false}]},{"StartTime":22522.0,"Objects":[{"StartTime":22522.0,"Position":337.0,"HyperDash":false}]},{"StartTime":22862.0,"Objects":[{"StartTime":22862.0,"Position":412.0,"HyperDash":false},{"StartTime":22938.0,"Position":422.0278,"HyperDash":false},{"StartTime":23014.0,"Position":407.799652,"HyperDash":false},{"StartTime":23090.0,"Position":425.315735,"HyperDash":false},{"StartTime":23202.0,"Position":405.6633,"HyperDash":false}]},{"StartTime":23885.0,"Objects":[{"StartTime":23885.0,"Position":214.0,"HyperDash":false},{"StartTime":23970.0,"Position":199.7902,"HyperDash":false},{"StartTime":24055.0,"Position":219.068054,"HyperDash":false},{"StartTime":24140.0,"Position":186.837479,"HyperDash":false},{"StartTime":24225.0,"Position":196.081757,"HyperDash":false},{"StartTime":24301.0,"Position":188.375519,"HyperDash":false},{"StartTime":24377.0,"Position":207.0842,"HyperDash":false},{"StartTime":24453.0,"Position":195.185028,"HyperDash":false},{"StartTime":24566.0,"Position":214.0,"HyperDash":false}]},{"StartTime":24908.0,"Objects":[{"StartTime":24908.0,"Position":301.0,"HyperDash":false},{"StartTime":24988.0,"Position":317.5747,"HyperDash":false},{"StartTime":25069.0,"Position":290.1566,"HyperDash":false},{"StartTime":25149.0,"Position":301.7313,"HyperDash":false},{"StartTime":25230.0,"Position":290.313171,"HyperDash":false},{"StartTime":25311.0,"Position":297.89505,"HyperDash":false},{"StartTime":25391.0,"Position":296.469727,"HyperDash":false},{"StartTime":25472.0,"Position":296.051636,"HyperDash":false},{"StartTime":25589.0,"Position":305.89212,"HyperDash":false}]},{"StartTime":25931.0,"Objects":[{"StartTime":25931.0,"Position":302.0,"HyperDash":false}]},{"StartTime":26612.0,"Objects":[{"StartTime":26612.0,"Position":131.0,"HyperDash":false}]},{"StartTime":26953.0,"Objects":[{"StartTime":26953.0,"Position":67.0,"HyperDash":false},{"StartTime":27029.0,"Position":61.19864,"HyperDash":false},{"StartTime":27105.0,"Position":60.3972778,"HyperDash":false},{"StartTime":27181.0,"Position":78.59592,"HyperDash":false},{"StartTime":27293.0,"Position":63.4149666,"HyperDash":false}]},{"StartTime":27635.0,"Objects":[{"StartTime":27635.0,"Position":96.0,"HyperDash":false},{"StartTime":27720.0,"Position":101.143433,"HyperDash":false},{"StartTime":27805.0,"Position":88.28687,"HyperDash":false},{"StartTime":27890.0,"Position":90.4303055,"HyperDash":false},{"StartTime":27975.0,"Position":104.586349,"HyperDash":false},{"StartTime":28051.0,"Position":87.68248,"HyperDash":false},{"StartTime":28127.0,"Position":96.76599,"HyperDash":false},{"StartTime":28203.0,"Position":101.84951,"HyperDash":false},{"StartTime":28316.0,"Position":96.0,"HyperDash":false}]},{"StartTime":28658.0,"Objects":[{"StartTime":28658.0,"Position":165.0,"HyperDash":false},{"StartTime":28738.0,"Position":161.813614,"HyperDash":false},{"StartTime":28819.0,"Position":196.82489,"HyperDash":false},{"StartTime":28899.0,"Position":225.6385,"HyperDash":false},{"StartTime":28980.0,"Position":225.64978,"HyperDash":false},{"StartTime":29061.0,"Position":260.60907,"HyperDash":false},{"StartTime":29141.0,"Position":251.337646,"HyperDash":false},{"StartTime":29222.0,"Position":265.2628,"HyperDash":false},{"StartTime":29339.0,"Position":299.265778,"HyperDash":false}]},{"StartTime":29681.0,"Objects":[{"StartTime":29681.0,"Position":385.0,"HyperDash":false},{"StartTime":29766.0,"Position":388.458282,"HyperDash":false},{"StartTime":29851.0,"Position":437.916565,"HyperDash":false},{"StartTime":29936.0,"Position":447.374817,"HyperDash":false},{"StartTime":30021.0,"Position":454.9358,"HyperDash":false},{"StartTime":30097.0,"Position":458.428741,"HyperDash":false},{"StartTime":30173.0,"Position":406.819,"HyperDash":false},{"StartTime":30249.0,"Position":402.209229,"HyperDash":false},{"StartTime":30362.0,"Position":385.0,"HyperDash":false}]},{"StartTime":31044.0,"Objects":[{"StartTime":31044.0,"Position":202.0,"HyperDash":false}]},{"StartTime":31385.0,"Objects":[{"StartTime":31385.0,"Position":197.0,"HyperDash":false},{"StartTime":31470.0,"Position":185.578781,"HyperDash":false},{"StartTime":31555.0,"Position":174.157562,"HyperDash":false},{"StartTime":31640.0,"Position":131.736343,"HyperDash":false},{"StartTime":31725.0,"Position":113.315117,"HyperDash":false},{"StartTime":31810.0,"Position":90.8939,"HyperDash":false},{"StartTime":31895.0,"Position":95.47269,"HyperDash":false},{"StartTime":31980.0,"Position":61.0514679,"HyperDash":false},{"StartTime":32066.0,"Position":57.3228149,"HyperDash":false},{"StartTime":32146.0,"Position":79.6167755,"HyperDash":false},{"StartTime":32227.0,"Position":103.21817,"HyperDash":false},{"StartTime":32308.0,"Position":96.81957,"HyperDash":false},{"StartTime":32389.0,"Position":116.420967,"HyperDash":false},{"StartTime":32469.0,"Position":149.817413,"HyperDash":false},{"StartTime":32550.0,"Position":165.418808,"HyperDash":false},{"StartTime":32631.0,"Position":180.0202,"HyperDash":false},{"StartTime":32748.0,"Position":197.0,"HyperDash":false}]},{"StartTime":33090.0,"Objects":[{"StartTime":33090.0,"Position":285.0,"HyperDash":false},{"StartTime":33175.0,"Position":283.775879,"HyperDash":false},{"StartTime":33260.0,"Position":292.551727,"HyperDash":false},{"StartTime":33345.0,"Position":281.3276,"HyperDash":false},{"StartTime":33430.0,"Position":288.108032,"HyperDash":false},{"StartTime":33506.0,"Position":280.418884,"HyperDash":false},{"StartTime":33582.0,"Position":305.725159,"HyperDash":false},{"StartTime":33658.0,"Position":281.031433,"HyperDash":false},{"StartTime":33771.0,"Position":285.0,"HyperDash":false}]},{"StartTime":34112.0,"Objects":[{"StartTime":34112.0,"Position":286.0,"HyperDash":false},{"StartTime":34188.0,"Position":286.694733,"HyperDash":false},{"StartTime":34264.0,"Position":302.389465,"HyperDash":false},{"StartTime":34340.0,"Position":272.0842,"HyperDash":false},{"StartTime":34452.0,"Position":289.108032,"HyperDash":false}]},{"StartTime":34794.0,"Objects":[{"StartTime":34794.0,"Position":373.0,"HyperDash":false},{"StartTime":34870.0,"Position":405.631622,"HyperDash":false},{"StartTime":34946.0,"Position":407.263245,"HyperDash":false},{"StartTime":35022.0,"Position":415.8949,"HyperDash":false},{"StartTime":35134.0,"Position":442.930969,"HyperDash":false}]},{"StartTime":35476.0,"Objects":[{"StartTime":35476.0,"Position":453.0,"HyperDash":false},{"StartTime":35556.0,"Position":463.4278,"HyperDash":false},{"StartTime":35637.0,"Position":456.885925,"HyperDash":false},{"StartTime":35717.0,"Position":475.313721,"HyperDash":false},{"StartTime":35798.0,"Position":450.771881,"HyperDash":false},{"StartTime":35879.0,"Position":441.79422,"HyperDash":false},{"StartTime":35959.0,"Position":445.1267,"HyperDash":false},{"StartTime":36040.0,"Position":428.388367,"HyperDash":false},{"StartTime":36157.0,"Position":438.09967,"HyperDash":false}]},{"StartTime":36499.0,"Objects":[{"StartTime":36499.0,"Position":362.0,"HyperDash":false}]},{"StartTime":36840.0,"Objects":[{"StartTime":36840.0,"Position":304.0,"HyperDash":false},{"StartTime":36920.0,"Position":297.5722,"HyperDash":false},{"StartTime":37001.0,"Position":304.114075,"HyperDash":false},{"StartTime":37081.0,"Position":297.686279,"HyperDash":false},{"StartTime":37162.0,"Position":292.228119,"HyperDash":false},{"StartTime":37243.0,"Position":315.20578,"HyperDash":false},{"StartTime":37323.0,"Position":301.8733,"HyperDash":false},{"StartTime":37404.0,"Position":324.611633,"HyperDash":false},{"StartTime":37521.0,"Position":318.90033,"HyperDash":false}]},{"StartTime":38203.0,"Objects":[{"StartTime":38203.0,"Position":160.0,"HyperDash":false},{"StartTime":38279.0,"Position":131.357956,"HyperDash":false},{"StartTime":38355.0,"Position":127.715912,"HyperDash":false},{"StartTime":38431.0,"Position":101.07386,"HyperDash":false},{"StartTime":38543.0,"Position":90.02242,"HyperDash":false}]},{"StartTime":38885.0,"Objects":[{"StartTime":38885.0,"Position":48.0,"HyperDash":false},{"StartTime":38970.0,"Position":51.7274666,"HyperDash":false},{"StartTime":39055.0,"Position":59.45493,"HyperDash":false},{"StartTime":39140.0,"Position":46.1823959,"HyperDash":false},{"StartTime":39225.0,"Position":50.91414,"HyperDash":false},{"StartTime":39301.0,"Position":30.2679787,"HyperDash":false},{"StartTime":39377.0,"Position":53.61754,"HyperDash":false},{"StartTime":39453.0,"Position":53.9671021,"HyperDash":false},{"StartTime":39566.0,"Position":48.0,"HyperDash":false}]},{"StartTime":40249.0,"Objects":[{"StartTime":40249.0,"Position":219.0,"HyperDash":false},{"StartTime":40325.0,"Position":234.6352,"HyperDash":false},{"StartTime":40401.0,"Position":232.270386,"HyperDash":false},{"StartTime":40477.0,"Position":246.905579,"HyperDash":false},{"StartTime":40589.0,"Position":288.94693,"HyperDash":false}]},{"StartTime":40931.0,"Objects":[{"StartTime":40931.0,"Position":379.0,"HyperDash":false},{"StartTime":41016.0,"Position":385.054565,"HyperDash":false},{"StartTime":41101.0,"Position":420.911255,"HyperDash":false},{"StartTime":41186.0,"Position":418.812378,"HyperDash":false},{"StartTime":41271.0,"Position":453.0934,"HyperDash":false},{"StartTime":41356.0,"Position":459.2142,"HyperDash":false},{"StartTime":41441.0,"Position":442.7846,"HyperDash":false},{"StartTime":41526.0,"Position":431.531372,"HyperDash":false},{"StartTime":41612.0,"Position":447.3299,"HyperDash":false},{"StartTime":41692.0,"Position":455.0114,"HyperDash":false},{"StartTime":41773.0,"Position":432.572144,"HyperDash":false},{"StartTime":41854.0,"Position":423.492432,"HyperDash":false},{"StartTime":41935.0,"Position":402.304352,"HyperDash":false},{"StartTime":42015.0,"Position":376.832733,"HyperDash":false},{"StartTime":42096.0,"Position":372.3723,"HyperDash":false},{"StartTime":42177.0,"Position":366.8342,"HyperDash":false},{"StartTime":42294.0,"Position":334.281647,"HyperDash":false}]},{"StartTime":42976.0,"Objects":[{"StartTime":42976.0,"Position":172.0,"HyperDash":false},{"StartTime":43056.0,"Position":148.8692,"HyperDash":false},{"StartTime":43137.0,"Position":157.674286,"HyperDash":false},{"StartTime":43217.0,"Position":139.543488,"HyperDash":false},{"StartTime":43298.0,"Position":162.348572,"HyperDash":false},{"StartTime":43379.0,"Position":134.066162,"HyperDash":false},{"StartTime":43459.0,"Position":174.156235,"HyperDash":false},{"StartTime":43540.0,"Position":170.297409,"HyperDash":false},{"StartTime":43657.0,"Position":167.279129,"HyperDash":false}]},{"StartTime":43999.0,"Objects":[{"StartTime":43999.0,"Position":255.0,"HyperDash":false},{"StartTime":44084.0,"Position":272.431122,"HyperDash":false},{"StartTime":44169.0,"Position":288.862274,"HyperDash":false},{"StartTime":44254.0,"Position":319.2934,"HyperDash":false},{"StartTime":44339.0,"Position":324.827057,"HyperDash":false},{"StartTime":44415.0,"Position":311.344116,"HyperDash":false},{"StartTime":44491.0,"Position":294.758636,"HyperDash":false},{"StartTime":44567.0,"Position":265.173157,"HyperDash":false},{"StartTime":44680.0,"Position":255.0,"HyperDash":false}]},{"StartTime":45022.0,"Objects":[{"StartTime":45022.0,"Position":163.0,"HyperDash":false},{"StartTime":45098.0,"Position":150.362976,"HyperDash":false},{"StartTime":45174.0,"Position":119.747879,"HyperDash":false},{"StartTime":45250.0,"Position":109.167084,"HyperDash":false},{"StartTime":45362.0,"Position":93.2945,"HyperDash":false}]},{"StartTime":45703.0,"Objects":[{"StartTime":45703.0,"Position":81.0,"HyperDash":false},{"StartTime":45779.0,"Position":67.62006,"HyperDash":false},{"StartTime":45855.0,"Position":93.24382,"HyperDash":false},{"StartTime":45931.0,"Position":89.85092,"HyperDash":false},{"StartTime":46043.0,"Position":98.2657852,"HyperDash":false}]},{"StartTime":46385.0,"Objects":[{"StartTime":46385.0,"Position":123.0,"HyperDash":false},{"StartTime":46465.0,"Position":115.7731,"HyperDash":false},{"StartTime":46546.0,"Position":133.424759,"HyperDash":false},{"StartTime":46626.0,"Position":169.264114,"HyperDash":false},{"StartTime":46707.0,"Position":177.250015,"HyperDash":false},{"StartTime":46788.0,"Position":189.772232,"HyperDash":false},{"StartTime":46868.0,"Position":199.175552,"HyperDash":false},{"StartTime":46949.0,"Position":219.42218,"HyperDash":false},{"StartTime":47066.0,"Position":250.349442,"HyperDash":false}]},{"StartTime":47408.0,"Objects":[{"StartTime":47408.0,"Position":339.0,"HyperDash":false},{"StartTime":47484.0,"Position":348.605347,"HyperDash":false},{"StartTime":47560.0,"Position":359.2107,"HyperDash":false},{"StartTime":47636.0,"Position":385.816,"HyperDash":false},{"StartTime":47748.0,"Position":408.813354,"HyperDash":false}]},{"StartTime":48431.0,"Objects":[{"StartTime":48431.0,"Position":436.0,"HyperDash":false},{"StartTime":48511.0,"Position":410.2269,"HyperDash":false},{"StartTime":48592.0,"Position":411.575226,"HyperDash":false},{"StartTime":48672.0,"Position":410.73587,"HyperDash":false},{"StartTime":48753.0,"Position":368.749969,"HyperDash":false},{"StartTime":48834.0,"Position":357.227753,"HyperDash":false},{"StartTime":48914.0,"Position":363.824432,"HyperDash":false},{"StartTime":48995.0,"Position":311.57782,"HyperDash":false},{"StartTime":49112.0,"Position":308.650574,"HyperDash":false}]},{"StartTime":49453.0,"Objects":[{"StartTime":49453.0,"Position":217.0,"HyperDash":false},{"StartTime":49538.0,"Position":226.735519,"HyperDash":false},{"StartTime":49623.0,"Position":224.662048,"HyperDash":false},{"StartTime":49708.0,"Position":184.780319,"HyperDash":false},{"StartTime":49793.0,"Position":197.063889,"HyperDash":false},{"StartTime":49869.0,"Position":185.218567,"HyperDash":false},{"StartTime":49945.0,"Position":217.554169,"HyperDash":false},{"StartTime":50021.0,"Position":222.04332,"HyperDash":false},{"StartTime":50134.0,"Position":217.0,"HyperDash":false}]},{"StartTime":50476.0,"Objects":[{"StartTime":50476.0,"Position":153.0,"HyperDash":false},{"StartTime":50552.0,"Position":152.801224,"HyperDash":false},{"StartTime":50628.0,"Position":114.521843,"HyperDash":false},{"StartTime":50704.0,"Position":93.1696854,"HyperDash":false},{"StartTime":50816.0,"Position":84.42975,"HyperDash":false}]},{"StartTime":51158.0,"Objects":[{"StartTime":51158.0,"Position":115.0,"HyperDash":false},{"StartTime":51238.0,"Position":126.432373,"HyperDash":false},{"StartTime":51319.0,"Position":159.057648,"HyperDash":false},{"StartTime":51399.0,"Position":160.49,"HyperDash":false},{"StartTime":51480.0,"Position":177.372131,"HyperDash":false},{"StartTime":51561.0,"Position":186.782,"HyperDash":false},{"StartTime":51641.0,"Position":225.989288,"HyperDash":false},{"StartTime":51722.0,"Position":235.399139,"HyperDash":false},{"StartTime":51839.0,"Position":250.10228,"HyperDash":false}]},{"StartTime":52181.0,"Objects":[{"StartTime":52181.0,"Position":339.0,"HyperDash":false}]},{"StartTime":52862.0,"Objects":[{"StartTime":52862.0,"Position":347.0,"HyperDash":false}]},{"StartTime":53203.0,"Objects":[{"StartTime":53203.0,"Position":253.0,"HyperDash":false},{"StartTime":53288.0,"Position":228.749466,"HyperDash":false},{"StartTime":53373.0,"Position":234.498917,"HyperDash":false},{"StartTime":53458.0,"Position":185.284058,"HyperDash":false},{"StartTime":53543.0,"Position":183.852417,"HyperDash":false},{"StartTime":53628.0,"Position":151.420776,"HyperDash":false},{"StartTime":53713.0,"Position":139.989136,"HyperDash":false},{"StartTime":53798.0,"Position":123.557495,"HyperDash":false},{"StartTime":53884.0,"Position":118.835892,"HyperDash":false},{"StartTime":53964.0,"Position":126.204315,"HyperDash":false},{"StartTime":54045.0,"Position":151.862686,"HyperDash":false},{"StartTime":54126.0,"Position":154.521072,"HyperDash":false},{"StartTime":54207.0,"Position":198.179474,"HyperDash":false},{"StartTime":54287.0,"Position":205.644547,"HyperDash":false},{"StartTime":54368.0,"Position":202.816391,"HyperDash":false},{"StartTime":54449.0,"Position":244.255142,"HyperDash":false},{"StartTime":54566.0,"Position":253.0,"HyperDash":false}]},{"StartTime":54908.0,"Objects":[{"StartTime":54908.0,"Position":343.0,"HyperDash":false}]},{"StartTime":55249.0,"Objects":[{"StartTime":55249.0,"Position":418.0,"HyperDash":false},{"StartTime":55325.0,"Position":411.540649,"HyperDash":false},{"StartTime":55401.0,"Position":412.081329,"HyperDash":false},{"StartTime":55477.0,"Position":412.621979,"HyperDash":false},{"StartTime":55589.0,"Position":429.366119,"HyperDash":false}]},{"StartTime":55931.0,"Objects":[{"StartTime":55931.0,"Position":415.0,"HyperDash":false},{"StartTime":56011.0,"Position":388.6705,"HyperDash":false},{"StartTime":56092.0,"Position":384.1857,"HyperDash":false},{"StartTime":56172.0,"Position":357.960541,"HyperDash":false},{"StartTime":56253.0,"Position":367.598083,"HyperDash":false},{"StartTime":56334.0,"Position":339.3097,"HyperDash":false},{"StartTime":56414.0,"Position":332.302979,"HyperDash":false},{"StartTime":56495.0,"Position":306.186432,"HyperDash":false},{"StartTime":56612.0,"Position":278.082428,"HyperDash":false}]},{"StartTime":56953.0,"Objects":[{"StartTime":56953.0,"Position":187.0,"HyperDash":false}]},{"StartTime":57294.0,"Objects":[{"StartTime":57294.0,"Position":96.0,"HyperDash":false},{"StartTime":57374.0,"Position":78.94491,"HyperDash":false},{"StartTime":57455.0,"Position":76.87663,"HyperDash":false},{"StartTime":57535.0,"Position":104.821541,"HyperDash":false},{"StartTime":57616.0,"Position":98.75326,"HyperDash":false},{"StartTime":57697.0,"Position":72.68498,"HyperDash":false},{"StartTime":57777.0,"Position":93.62989,"HyperDash":false},{"StartTime":57858.0,"Position":89.56161,"HyperDash":false},{"StartTime":57975.0,"Position":87.01854,"HyperDash":false}]},{"StartTime":58317.0,"Objects":[{"StartTime":58317.0,"Position":149.0,"HyperDash":false}]},{"StartTime":58658.0,"Objects":[{"StartTime":58658.0,"Position":239.0,"HyperDash":false},{"StartTime":58738.0,"Position":248.055084,"HyperDash":false},{"StartTime":58819.0,"Position":226.123367,"HyperDash":false},{"StartTime":58899.0,"Position":238.178467,"HyperDash":false},{"StartTime":58980.0,"Position":242.246735,"HyperDash":false},{"StartTime":59061.0,"Position":251.315018,"HyperDash":false},{"StartTime":59141.0,"Position":233.370117,"HyperDash":false},{"StartTime":59222.0,"Position":253.438385,"HyperDash":false},{"StartTime":59339.0,"Position":247.981461,"HyperDash":false}]},{"StartTime":60022.0,"Objects":[{"StartTime":60022.0,"Position":365.0,"HyperDash":false},{"StartTime":60098.0,"Position":378.011719,"HyperDash":false},{"StartTime":60174.0,"Position":393.785217,"HyperDash":false},{"StartTime":60250.0,"Position":402.2842,"HyperDash":false},{"StartTime":60362.0,"Position":430.07663,"HyperDash":false}]},{"StartTime":60703.0,"Objects":[{"StartTime":60703.0,"Position":436.0,"HyperDash":false},{"StartTime":60779.0,"Position":419.315674,"HyperDash":false},{"StartTime":60855.0,"Position":421.518646,"HyperDash":false},{"StartTime":60931.0,"Position":408.615723,"HyperDash":false},{"StartTime":61043.0,"Position":369.475067,"HyperDash":false}]},{"StartTime":61385.0,"Objects":[{"StartTime":61385.0,"Position":294.0,"HyperDash":false},{"StartTime":61465.0,"Position":267.3266,"HyperDash":false},{"StartTime":61546.0,"Position":256.9999,"HyperDash":false},{"StartTime":61626.0,"Position":281.482758,"HyperDash":false},{"StartTime":61707.0,"Position":273.83606,"HyperDash":false},{"StartTime":61788.0,"Position":247.226837,"HyperDash":false},{"StartTime":61868.0,"Position":269.576416,"HyperDash":false},{"StartTime":61949.0,"Position":261.865753,"HyperDash":false},{"StartTime":62066.0,"Position":290.626923,"HyperDash":false}]},{"StartTime":62408.0,"Objects":[{"StartTime":62408.0,"Position":368.0,"HyperDash":false}]},{"StartTime":62749.0,"Objects":[{"StartTime":62749.0,"Position":451.0,"HyperDash":false},{"StartTime":62829.0,"Position":437.402985,"HyperDash":false},{"StartTime":62910.0,"Position":465.8735,"HyperDash":false},{"StartTime":62990.0,"Position":468.8882,"HyperDash":false},{"StartTime":63071.0,"Position":481.67627,"HyperDash":false},{"StartTime":63152.0,"Position":473.464355,"HyperDash":false},{"StartTime":63232.0,"Position":462.279724,"HyperDash":false},{"StartTime":63313.0,"Position":466.06778,"HyperDash":false},{"StartTime":63430.0,"Position":454.872772,"HyperDash":false}]},{"StartTime":64112.0,"Objects":[{"StartTime":64112.0,"Position":288.0,"HyperDash":false},{"StartTime":64192.0,"Position":265.466431,"HyperDash":false},{"StartTime":64273.0,"Position":257.738678,"HyperDash":false},{"StartTime":64353.0,"Position":225.165848,"HyperDash":false},{"StartTime":64434.0,"Position":205.661438,"HyperDash":false},{"StartTime":64515.0,"Position":188.157013,"HyperDash":false},{"StartTime":64595.0,"Position":194.856354,"HyperDash":false},{"StartTime":64676.0,"Position":175.351929,"HyperDash":false},{"StartTime":64793.0,"Position":151.512222,"HyperDash":false}]},{"StartTime":65135.0,"Objects":[{"StartTime":65135.0,"Position":124.0,"HyperDash":false},{"StartTime":65220.0,"Position":116.567741,"HyperDash":false},{"StartTime":65305.0,"Position":71.23689,"HyperDash":false},{"StartTime":65390.0,"Position":69.06891,"HyperDash":false},{"StartTime":65475.0,"Position":55.0260773,"HyperDash":false},{"StartTime":65551.0,"Position":64.0663147,"HyperDash":false},{"StartTime":65627.0,"Position":87.38679,"HyperDash":false},{"StartTime":65703.0,"Position":117.8468,"HyperDash":false},{"StartTime":65816.0,"Position":124.0,"HyperDash":false}]},{"StartTime":66158.0,"Objects":[{"StartTime":66158.0,"Position":212.0,"HyperDash":false}]},{"StartTime":66499.0,"Objects":[{"StartTime":66499.0,"Position":190.0,"HyperDash":false},{"StartTime":66575.0,"Position":206.978012,"HyperDash":false},{"StartTime":66651.0,"Position":197.713776,"HyperDash":false},{"StartTime":66727.0,"Position":197.188354,"HyperDash":false},{"StartTime":66839.0,"Position":222.507156,"HyperDash":false}]},{"StartTime":67522.0,"Objects":[{"StartTime":67522.0,"Position":400.0,"HyperDash":false},{"StartTime":67598.0,"Position":417.733978,"HyperDash":false},{"StartTime":67674.0,"Position":418.6181,"HyperDash":false},{"StartTime":67750.0,"Position":419.6206,"HyperDash":false},{"StartTime":67862.0,"Position":432.309723,"HyperDash":false}]},{"StartTime":68203.0,"Objects":[{"StartTime":68203.0,"Position":441.0,"HyperDash":false},{"StartTime":68283.0,"Position":421.1902,"HyperDash":false},{"StartTime":68364.0,"Position":424.512329,"HyperDash":false},{"StartTime":68444.0,"Position":387.838,"HyperDash":false},{"StartTime":68525.0,"Position":400.321259,"HyperDash":false},{"StartTime":68606.0,"Position":351.733856,"HyperDash":false},{"StartTime":68686.0,"Position":346.847382,"HyperDash":false},{"StartTime":68767.0,"Position":347.826874,"HyperDash":false},{"StartTime":68884.0,"Position":315.044037,"HyperDash":false}]},{"StartTime":69226.0,"Objects":[{"StartTime":69226.0,"Position":271.0,"HyperDash":false},{"StartTime":69302.0,"Position":260.591034,"HyperDash":false},{"StartTime":69378.0,"Position":230.182068,"HyperDash":false},{"StartTime":69454.0,"Position":230.7731,"HyperDash":false},{"StartTime":69566.0,"Position":202.065155,"HyperDash":false}]},{"StartTime":70249.0,"Objects":[{"StartTime":70249.0,"Position":71.0,"HyperDash":false},{"StartTime":70329.0,"Position":88.54798,"HyperDash":false},{"StartTime":70410.0,"Position":83.29032,"HyperDash":false},{"StartTime":70490.0,"Position":120.8383,"HyperDash":false},{"StartTime":70571.0,"Position":138.779388,"HyperDash":false},{"StartTime":70652.0,"Position":166.2048,"HyperDash":false},{"StartTime":70732.0,"Position":159.427444,"HyperDash":false},{"StartTime":70813.0,"Position":172.852844,"HyperDash":false},{"StartTime":70930.0,"Position":206.578461,"HyperDash":false}]},{"StartTime":71272.0,"Objects":[{"StartTime":71272.0,"Position":285.0,"HyperDash":false},{"StartTime":71357.0,"Position":295.671265,"HyperDash":false},{"StartTime":71442.0,"Position":272.7821,"HyperDash":false},{"StartTime":71527.0,"Position":278.3155,"HyperDash":false},{"StartTime":71612.0,"Position":290.256958,"HyperDash":false},{"StartTime":71688.0,"Position":299.285034,"HyperDash":false},{"StartTime":71764.0,"Position":299.052734,"HyperDash":false},{"StartTime":71840.0,"Position":298.55127,"HyperDash":false},{"StartTime":71953.0,"Position":285.0,"HyperDash":false}]},{"StartTime":72294.0,"Objects":[{"StartTime":72294.0,"Position":257.0,"HyperDash":false},{"StartTime":72370.0,"Position":270.334442,"HyperDash":false},{"StartTime":72446.0,"Position":266.123962,"HyperDash":false},{"StartTime":72522.0,"Position":313.321533,"HyperDash":false},{"StartTime":72634.0,"Position":319.88623,"HyperDash":false}]},{"StartTime":72976.0,"Objects":[{"StartTime":72976.0,"Position":367.0,"HyperDash":false},{"StartTime":73056.0,"Position":386.222748,"HyperDash":false},{"StartTime":73137.0,"Position":399.63,"HyperDash":false},{"StartTime":73217.0,"Position":410.031372,"HyperDash":false},{"StartTime":73298.0,"Position":440.0546,"HyperDash":false},{"StartTime":73379.0,"Position":442.924225,"HyperDash":false},{"StartTime":73459.0,"Position":443.179749,"HyperDash":false},{"StartTime":73540.0,"Position":448.717773,"HyperDash":false},{"StartTime":73657.0,"Position":429.740936,"HyperDash":false}]},{"StartTime":73999.0,"Objects":[{"StartTime":73999.0,"Position":368.0,"HyperDash":false}]},{"StartTime":74681.0,"Objects":[{"StartTime":74681.0,"Position":2.0,"HyperDash":false}]},{"StartTime":75022.0,"Objects":[{"StartTime":75022.0,"Position":108.0,"HyperDash":false},{"StartTime":75107.0,"Position":87.6573639,"HyperDash":false},{"StartTime":75192.0,"Position":106.552719,"HyperDash":false},{"StartTime":75277.0,"Position":115.749847,"HyperDash":false},{"StartTime":75362.0,"Position":95.05077,"HyperDash":false},{"StartTime":75447.0,"Position":141.007874,"HyperDash":false},{"StartTime":75532.0,"Position":142.951065,"HyperDash":false},{"StartTime":75617.0,"Position":142.0284,"HyperDash":false},{"StartTime":75703.0,"Position":170.5651,"HyperDash":false},{"StartTime":75783.0,"Position":166.401367,"HyperDash":false},{"StartTime":75864.0,"Position":130.8922,"HyperDash":false},{"StartTime":75945.0,"Position":135.213562,"HyperDash":false},{"StartTime":76026.0,"Position":111.1313,"HyperDash":false},{"StartTime":76106.0,"Position":89.331665,"HyperDash":false},{"StartTime":76187.0,"Position":82.0756454,"HyperDash":false},{"StartTime":76268.0,"Position":84.71052,"HyperDash":false},{"StartTime":76385.0,"Position":108.0,"HyperDash":false}]},{"StartTime":76726.0,"Objects":[{"StartTime":76726.0,"Position":185.0,"HyperDash":false}]},{"StartTime":77067.0,"Objects":[{"StartTime":77067.0,"Position":134.0,"HyperDash":false},{"StartTime":77152.0,"Position":132.526932,"HyperDash":false},{"StartTime":77237.0,"Position":80.05387,"HyperDash":false},{"StartTime":77322.0,"Position":100.580811,"HyperDash":false},{"StartTime":77407.0,"Position":64.00496,"HyperDash":false},{"StartTime":77483.0,"Position":60.5251465,"HyperDash":false},{"StartTime":77559.0,"Position":95.1481247,"HyperDash":false},{"StartTime":77635.0,"Position":105.7711,"HyperDash":false},{"StartTime":77748.0,"Position":134.0,"HyperDash":false}]},{"StartTime":78090.0,"Objects":[{"StartTime":78090.0,"Position":225.0,"HyperDash":false},{"StartTime":78166.0,"Position":226.044952,"HyperDash":false},{"StartTime":78242.0,"Position":241.340271,"HyperDash":false},{"StartTime":78318.0,"Position":287.819031,"HyperDash":false},{"StartTime":78430.0,"Position":293.820984,"HyperDash":false}]},{"StartTime":78772.0,"Objects":[{"StartTime":78772.0,"Position":461.0,"HyperDash":false}]},{"StartTime":79112.0,"Objects":[{"StartTime":79112.0,"Position":429.0,"HyperDash":false},{"StartTime":79192.0,"Position":416.857025,"HyperDash":false},{"StartTime":79273.0,"Position":453.389954,"HyperDash":false},{"StartTime":79353.0,"Position":426.5255,"HyperDash":false},{"StartTime":79434.0,"Position":438.278229,"HyperDash":false},{"StartTime":79515.0,"Position":450.6304,"HyperDash":false},{"StartTime":79595.0,"Position":448.640656,"HyperDash":false},{"StartTime":79676.0,"Position":431.2529,"HyperDash":false},{"StartTime":79793.0,"Position":418.566681,"HyperDash":false}]},{"StartTime":80135.0,"Objects":[{"StartTime":80135.0,"Position":330.0,"HyperDash":false}]},{"StartTime":80476.0,"Objects":[{"StartTime":80476.0,"Position":239.0,"HyperDash":false},{"StartTime":80556.0,"Position":231.143,"HyperDash":false},{"StartTime":80637.0,"Position":222.610062,"HyperDash":false},{"StartTime":80717.0,"Position":240.474518,"HyperDash":false},{"StartTime":80798.0,"Position":227.721756,"HyperDash":false},{"StartTime":80879.0,"Position":221.3696,"HyperDash":false},{"StartTime":80959.0,"Position":225.35936,"HyperDash":false},{"StartTime":81040.0,"Position":255.747116,"HyperDash":false},{"StartTime":81157.0,"Position":249.43335,"HyperDash":false}]},{"StartTime":81840.0,"Objects":[{"StartTime":81840.0,"Position":372.0,"HyperDash":false},{"StartTime":81916.0,"Position":360.517242,"HyperDash":false},{"StartTime":81992.0,"Position":348.0345,"HyperDash":false},{"StartTime":82068.0,"Position":338.5517,"HyperDash":false},{"StartTime":82180.0,"Position":302.735016,"HyperDash":false}]},{"StartTime":82522.0,"Objects":[{"StartTime":82522.0,"Position":222.0,"HyperDash":false},{"StartTime":82607.0,"Position":195.592834,"HyperDash":false},{"StartTime":82692.0,"Position":198.185669,"HyperDash":false},{"StartTime":82777.0,"Position":161.7785,"HyperDash":false},{"StartTime":82862.0,"Position":152.268951,"HyperDash":false},{"StartTime":82938.0,"Position":160.730591,"HyperDash":false},{"StartTime":83014.0,"Position":191.294647,"HyperDash":false},{"StartTime":83090.0,"Position":194.8587,"HyperDash":false},{"StartTime":83203.0,"Position":222.0,"HyperDash":false}]},{"StartTime":83885.0,"Objects":[{"StartTime":83885.0,"Position":374.0,"HyperDash":false},{"StartTime":83961.0,"Position":382.5809,"HyperDash":false},{"StartTime":84037.0,"Position":345.4707,"HyperDash":false},{"StartTime":84113.0,"Position":351.689972,"HyperDash":false},{"StartTime":84225.0,"Position":335.561218,"HyperDash":false}]},{"StartTime":84567.0,"Objects":[{"StartTime":84567.0,"Position":246.0,"HyperDash":false},{"StartTime":84652.0,"Position":250.610764,"HyperDash":false},{"StartTime":84737.0,"Position":219.0491,"HyperDash":false},{"StartTime":84822.0,"Position":214.657715,"HyperDash":false},{"StartTime":84907.0,"Position":183.720428,"HyperDash":false},{"StartTime":84992.0,"Position":188.454575,"HyperDash":false},{"StartTime":85077.0,"Position":201.005173,"HyperDash":false},{"StartTime":85162.0,"Position":173.53,"HyperDash":false},{"StartTime":85248.0,"Position":196.9969,"HyperDash":false},{"StartTime":85333.0,"Position":206.210022,"HyperDash":false},{"StartTime":85418.0,"Position":224.027588,"HyperDash":false},{"StartTime":85503.0,"Position":233.208817,"HyperDash":false},{"StartTime":85589.0,"Position":242.6152,"HyperDash":false},{"StartTime":85674.0,"Position":246.61525,"HyperDash":false},{"StartTime":85759.0,"Position":258.8727,"HyperDash":false},{"StartTime":85844.0,"Position":298.942719,"HyperDash":false},{"StartTime":85930.0,"Position":302.666138,"HyperDash":false},{"StartTime":86015.0,"Position":288.350464,"HyperDash":false},{"StartTime":86100.0,"Position":286.2681,"HyperDash":false},{"StartTime":86185.0,"Position":256.988831,"HyperDash":false},{"StartTime":86271.0,"Position":229.786652,"HyperDash":false},{"StartTime":86356.0,"Position":240.490692,"HyperDash":false},{"StartTime":86441.0,"Position":214.260208,"HyperDash":false},{"StartTime":86526.0,"Position":191.387848,"HyperDash":false},{"StartTime":86612.0,"Position":197.0563,"HyperDash":false},{"StartTime":86692.0,"Position":212.729446,"HyperDash":false},{"StartTime":86773.0,"Position":181.9589,"HyperDash":false},{"StartTime":86854.0,"Position":206.823822,"HyperDash":false},{"StartTime":86935.0,"Position":185.277771,"HyperDash":false},{"StartTime":87015.0,"Position":222.114685,"HyperDash":false},{"StartTime":87096.0,"Position":227.322708,"HyperDash":false},{"StartTime":87177.0,"Position":214.614655,"HyperDash":false},{"StartTime":87294.0,"Position":246.0,"HyperDash":false}]},{"StartTime":87465.0,"Objects":[{"StartTime":87465.0,"Position":408.0,"HyperDash":false},{"StartTime":87547.0,"Position":243.0,"HyperDash":false},{"StartTime":87630.0,"Position":78.0,"HyperDash":false},{"StartTime":87712.0,"Position":172.0,"HyperDash":false},{"StartTime":87795.0,"Position":450.0,"HyperDash":false},{"StartTime":87877.0,"Position":231.0,"HyperDash":false},{"StartTime":87960.0,"Position":118.0,"HyperDash":false},{"StartTime":88042.0,"Position":511.0,"HyperDash":false},{"StartTime":88125.0,"Position":333.0,"HyperDash":false},{"StartTime":88208.0,"Position":234.0,"HyperDash":false},{"StartTime":88290.0,"Position":228.0,"HyperDash":false},{"StartTime":88373.0,"Position":302.0,"HyperDash":false},{"StartTime":88455.0,"Position":390.0,"HyperDash":false},{"StartTime":88538.0,"Position":75.0,"HyperDash":false},{"StartTime":88620.0,"Position":506.0,"HyperDash":false},{"StartTime":88703.0,"Position":3.0,"HyperDash":false},{"StartTime":88786.0,"Position":289.0,"HyperDash":false},{"StartTime":88868.0,"Position":217.0,"HyperDash":false},{"StartTime":88951.0,"Position":447.0,"HyperDash":false},{"StartTime":89033.0,"Position":324.0,"HyperDash":false},{"StartTime":89116.0,"Position":183.0,"HyperDash":false},{"StartTime":89198.0,"Position":279.0,"HyperDash":false},{"StartTime":89281.0,"Position":157.0,"HyperDash":false},{"StartTime":89363.0,"Position":501.0,"HyperDash":false},{"StartTime":89446.0,"Position":215.0,"HyperDash":false},{"StartTime":89529.0,"Position":79.0,"HyperDash":false},{"StartTime":89611.0,"Position":337.0,"HyperDash":false},{"StartTime":89694.0,"Position":380.0,"HyperDash":false},{"StartTime":89776.0,"Position":348.0,"HyperDash":false},{"StartTime":89859.0,"Position":225.0,"HyperDash":false},{"StartTime":89941.0,"Position":363.0,"HyperDash":false},{"StartTime":90024.0,"Position":96.0,"HyperDash":false},{"StartTime":90107.0,"Position":104.0,"HyperDash":false},{"StartTime":90189.0,"Position":173.0,"HyperDash":false},{"StartTime":90272.0,"Position":373.0,"HyperDash":false},{"StartTime":90354.0,"Position":424.0,"HyperDash":false},{"StartTime":90437.0,"Position":268.0,"HyperDash":false},{"StartTime":90519.0,"Position":373.0,"HyperDash":false},{"StartTime":90602.0,"Position":436.0,"HyperDash":false},{"StartTime":90684.0,"Position":190.0,"HyperDash":false},{"StartTime":90767.0,"Position":419.0,"HyperDash":false},{"StartTime":90850.0,"Position":158.0,"HyperDash":false},{"StartTime":90932.0,"Position":143.0,"HyperDash":false},{"StartTime":91015.0,"Position":266.0,"HyperDash":false},{"StartTime":91097.0,"Position":166.0,"HyperDash":false},{"StartTime":91180.0,"Position":297.0,"HyperDash":false},{"StartTime":91262.0,"Position":198.0,"HyperDash":false},{"StartTime":91345.0,"Position":241.0,"HyperDash":false},{"StartTime":91428.0,"Position":477.0,"HyperDash":false},{"StartTime":91510.0,"Position":371.0,"HyperDash":false},{"StartTime":91593.0,"Position":152.0,"HyperDash":false},{"StartTime":91675.0,"Position":321.0,"HyperDash":false},{"StartTime":91758.0,"Position":303.0,"HyperDash":false},{"StartTime":91840.0,"Position":259.0,"HyperDash":false},{"StartTime":91923.0,"Position":186.0,"HyperDash":false},{"StartTime":92005.0,"Position":140.0,"HyperDash":false},{"StartTime":92088.0,"Position":207.0,"HyperDash":false},{"StartTime":92171.0,"Position":278.0,"HyperDash":false},{"StartTime":92253.0,"Position":223.0,"HyperDash":false},{"StartTime":92336.0,"Position":389.0,"HyperDash":false},{"StartTime":92418.0,"Position":245.0,"HyperDash":false},{"StartTime":92501.0,"Position":400.0,"HyperDash":false},{"StartTime":92583.0,"Position":445.0,"HyperDash":false},{"StartTime":92666.0,"Position":443.0,"HyperDash":false},{"StartTime":92749.0,"Position":245.0,"HyperDash":false}]}]} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3227428.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3227428.osu new file mode 100644 index 0000000000..7f9cdb97cc --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3227428.osu @@ -0,0 +1,142 @@ +osu file format v14 + +[General] +StackLeniency: 0.7 +Mode: 0 + +[Difficulty] +HPDrainRate:4 +CircleSize:3.5 +OverallDifficulty:4 +ApproachRate:4 +SliderMultiplier:1.4 +SliderTickRate:1 + +[Events] +//Background and Video events +//Break Periods +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Layer 4 (Overlay) +//Storyboard Sound Samples + +[TimingPoints] +22,681.818181818182,4,2,1,60,1,0 +9908,-100,4,2,1,40,0,0 +10931,-100,4,2,1,67,0,0 +31726,-100,4,2,1,40,0,0 +33090,-100,4,2,1,67,0,0 +43658,-100,4,2,1,74,0,0 +53544,-100,4,2,1,50,0,0 +54908,-100,4,2,1,74,0,0 +75362,-100,4,2,1,50,0,0 +76726,-100,4,2,1,74,0,0 +86612,-100,4,2,1,67,0,0 +87294,-100,4,2,1,40,0,0 +87465,-100,4,2,1,67,0,0 +90022,-100,4,2,1,57,0,0 +91385,-100,4,2,1,37,0,0 +92067,-100,4,2,1,17,0,0 +92749,-100,4,2,1,5,0,0 + +[HitObjects] +206,12,22,5,0,2:0:0:0: +137,71,362,2,0,L|54:77,2,70,2|0|0,2:0|2:0|2:0,0:0:0:0: +220,108,1385,2,0,P|258:171|211:223,1,140,2|0,0:0|0:0,0:0:0:0: +160,283,2408,2,0,L|79:277,2,70,0|2|2,0:0|0:0|0:1,0:0:0:0: +340,303,3772,1,0,0:0:0:0: +401,235,4112,2,0,L|405:82,1,140,2|0,0:0|0:0,0:0:0:0: +343,27,5135,2,0,P|309:41|263:72,1,70,0|2,0:0|0:1,0:0:0:0: +189,63,5817,6,0,L|93:55,2,70,2|0|0,0:0|0:0|0:0,0:0:0:0: +208,151,6840,2,0,B|363:142,1,140,2|0,0:0|0:0,0:0:0:0: +416,202,7862,2,0,P|436:245|446:291,2,70,0|2|2,0:0|0:0|0:1,0:0:0:0: +275,86,9226,1,0,0:0:0:0: +208,151,9567,2,0,P|187:194|177:297,2,140,6|0|2,0:0|0:0|0:1,0:0:0:0: +272,87,11272,6,0,L|353:99,1,70,2|0,0:0|0:0,0:0:0:0: +397,169,11953,2,0,P|431:164|465:157,2,70,0|2|0,0:0|0:1|0:0,0:0:0:0: +309,196,12976,2,0,P|302:241|301:280,1,70 +226,317,13658,2,0,P|162:340|106:303,1,140,2|0,0:0|0:0,0:0:0:0: +71,218,14681,1,0,0:0:0:0: +109,135,15022,2,0,P|172:111|228:148,1,140,2|0,0:0|0:0,0:0:0:0: +305,192,16044,2,0,P|342:187|384:176,1,70,0|2,0:0|0:1,0:0:0:0: +416,99,16726,6,0,L|508:111,2,70,2|0|0,0:0|0:0|0:0,0:0:0:0: +338,58,17749,2,0,B|313:113|313:113|305:200,1,140,2|0,0:0|0:0,0:0:0:0: +293,287,18772,1,0,0:0:0:0: +201,278,19112,2,0,B|112:265|112:265|63:277,1,140,2|0,0:0|0:0,0:0:0:0: +129,107,20476,2,0,B|217:119|217:119|266:107,1,140,2|0,0:0|0:0,0:0:0:0: +352,75,21499,6,0,P|393:51|436:33,2,70,0|2|2,0:0|0:1|0:0,0:0:0:0: +337,165,22522,1,2,0:0:0:0: +412,214,22862,2,0,P|409:254|403:303,1,70,0|2,0:0|0:0,0:0:0:0: +214,306,23885,2,0,P|205:276|195:233,2,70,0|0|2,0:0|0:0|0:0,0:0:0:0: +301,331,24908,2,0,L|306:188,1,140,2|0,0:1|0:0,0:0:0:0: +302,99,25931,1,2,0:0:0:0: +131,34,26612,1,0,0:0:0:0: +67,99,26953,2,0,L|63:177,1,70,0|2,0:0|0:1,0:0:0:0: +96,254,27635,6,0,L|107:343,2,70,2|0|0,0:0|0:0|0:0,0:0:0:0: +165,194,28658,2,0,B|235:174|235:174|307:196,1,140,2|0,0:1|0:0,0:0:0:0: +385,223,29681,2,0,L|455:220,2,70,0|2|2,0:0|0:0|0:0,0:0:0:0: +202,223,31044,1,0,0:0:0:0: +197,132,31385,2,0,L|50:122,2,140,6|0|0,0:0|0:0|0:0,0:0:0:0: +285,111,33090,6,0,L|289:21,2,70,2|0|0,0:0|0:0|0:0,0:0:0:0: +286,202,34112,2,0,L|290:292,1,70,2|0,0:0|0:0,0:0:0:0: +373,306,34794,2,0,L|463:302,1,70 +453,212,35476,2,0,B|463:145|463:145|434:66,1,140,2|0,0:0|0:0,0:0:0:0: +362,25,36499,1,2,0:0:0:0: +304,95,36840,2,0,B|294:162|294:162|323:241,1,140,2|0,0:0|0:0,0:0:0:0: +160,319,38203,6,0,L|81:317,1,70,2|0,0:0|0:0,0:0:0:0: +48,235,38885,2,0,L|51:163,2,70,0|0|2,0:0|0:0|0:0,0:0:0:0: +219,295,40249,2,0,L|296:292,1,70,2|0,0:0|0:0,0:0:0:0: +379,284,40931,2,2,P|450:216|324:142,1,280,2|0,0:0|0:0,0:0:0:0: +172,210,42976,6,0,B|150:143|150:143|169:69,1,140,2|6,0:0|0:0,0:0:0:0: +255,54,43999,2,0,L|326:59,2,70,2|2|0,0:0|0:0|0:0,0:0:0:0: +163,56,45022,2,0,P|126:58|80:64,1,70,2|0,0:0|0:0,0:0:0:0: +81,153,45703,2,0,P|97:210|99:230,1,70 +123,308,46385,2,0,P|154:284|260:294,1,140,2|0,0:0|0:0,0:0:0:0: +339,307,47408,2,0,L|421:313,1,70,0|2,0:0|0:0,0:0:0:0: +436,132,48431,2,0,P|405:108|299:118,1,140,0|2,0:0|0:1,0:0:0:0: +217,111,49453,6,0,P|205:72|196:40,2,70,2|2|0,0:0|0:0|0:0,0:0:0:0: +153,175,50476,2,0,P|123:182|77:190,1,70,2|0,0:0|0:0,0:0:0:0: +115,274,51158,2,0,B|172:253|172:253|259:268,1,140,0|2,0:0|0:0,0:0:0:0: +339,247,52181,1,0,0:0:0:0: +343,65,52862,1,0,0:0:0:0: +253,81,53203,2,0,B|202:89|202:89|113:57,2,140,6|0|2,0:0|0:0|0:1,0:0:0:0: +343,65,54908,5,2,0:0:0:0: +418,116,55249,2,0,L|431:195,1,70 +415,279,55931,2,0,P|350:269|263:246,1,140,2|0,0:0|0:0,0:0:0:0: +187,254,56953,1,0,0:0:0:0: +96,242,57294,2,0,L|87:102,1,140,2|0,0:0|0:0,0:0:0:0: +149,35,58317,1,2,0:0:0:0: +239,29,58658,2,0,L|248:169,1,140,2|0,0:0|0:0,0:0:0:0: +365,304,60022,6,0,P|406:290|435:276,1,70,2|2,0:1|0:0,0:0:0:0: +436,187,60703,2,0,P|405:176|357:162,1,70 +294,217,61385,2,0,P|268:168|295:86,1,140,2|0,0:0|0:0,0:0:0:0: +368,43,62408,1,0,0:0:0:0: +451,79,62749,2,0,B|467:125|467:125|454:222,1,140,2|0,0:0|0:0,0:0:0:0: +288,290,64112,2,0,B|242:306|242:306|145:293,1,140 +124,206,65135,6,0,P|80:211|48:219,2,70,0|2|2,0:0|0:1|0:0,0:0:0:0: +212,184,66158,1,2,0:0:0:0: +190,95,66499,2,0,P|205:62|224:31,1,70,2|2,0:0|0:0,0:0:0:0: +400,67,67522,2,0,P|418:96|432:128,1,70 +441,219,68203,2,0,P|398:242|305:204,1,140,2|0,0:0|0:0,0:0:0:0: +271,136,69226,2,0,L|186:151,1,70,0|2,0:0|0:0,0:0:0:0: +71,275,70249,2,0,B|129:295|129:295|225:279,1,140,0|2,0:0|0:1,0:0:0:0: +285,236,71272,6,0,P|291:273|290:308,2,70,2|2|0,0:0|0:0|0:0,0:0:0:0: +257,150,72294,2,0,P|287:133|322:119,1,70,2|0,0:0|0:0,0:0:0:0: +367,42,72976,2,0,P|415:63|420:159,1,140,0|2,0:0|0:0,0:0:0:0: +368,210,73999,1,0,0:0:0:0: +185,209,74681,1,0,0:0:0:0: +108,159,75022,2,0,P|112:92|171:59,2,140,6|0|2,0:0|0:0|0:1,0:0:0:0: +185,209,76726,5,2,0:0:0:0: +134,284,77067,2,0,L|50:283,2,70,0|0|2,0:0|0:0|0:0,0:0:0:0: +225,289,78090,2,0,P|264:280|309:278,1,70 +385,274,78772,1,0,0:0:0:0: +429,194,79112,2,0,P|436:124|409:39,1,140,2|0,0:0|0:0,0:0:0:0: +330,33,80135,1,2,0:0:0:0: +239,38,80476,2,0,P|232:108|259:193,1,140,2|0,0:0|0:0,0:0:0:0: +372,316,81840,6,0,L|283:303,1,70,2|0,0:0|0:0,0:0:0:0: +222,262,82522,2,0,L|131:270,2,70,0|0|2,0:0|0:0|0:0,0:0:0:0: +374,161,83885,2,0,P|356:130|335:102,1,70 +246,110,84567,2,0,P|214:138|321:303,2,280,2|0|2,0:0|0:0|0:1,0:0:0:0: +256,192,87465,12,0,92749,0:0:0:0: diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3524302-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3524302-expected-conversion.json new file mode 100644 index 0000000000..b4ccc8da8f --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3524302-expected-conversion.json @@ -0,0 +1 @@ +{"Mappings":[{"StartTime":14259.0,"Objects":[{"StartTime":14259.0,"Position":65.0,"HyperDash":false},{"StartTime":14354.0,"Position":482.0,"HyperDash":false},{"StartTime":14450.0,"Position":164.0,"HyperDash":false},{"StartTime":14546.0,"Position":315.0,"HyperDash":false},{"StartTime":14642.0,"Position":145.0,"HyperDash":false},{"StartTime":14738.0,"Position":159.0,"HyperDash":false},{"StartTime":14833.0,"Position":310.0,"HyperDash":false},{"StartTime":14929.0,"Position":441.0,"HyperDash":false},{"StartTime":15025.0,"Position":428.0,"HyperDash":false},{"StartTime":15121.0,"Position":243.0,"HyperDash":false},{"StartTime":15217.0,"Position":422.0,"HyperDash":false},{"StartTime":15312.0,"Position":481.0,"HyperDash":false},{"StartTime":15408.0,"Position":104.0,"HyperDash":false},{"StartTime":15504.0,"Position":473.0,"HyperDash":false},{"StartTime":15600.0,"Position":135.0,"HyperDash":false},{"StartTime":15696.0,"Position":360.0,"HyperDash":false},{"StartTime":15792.0,"Position":123.0,"HyperDash":false},{"StartTime":15887.0,"Position":42.0,"HyperDash":false},{"StartTime":15983.0,"Position":393.0,"HyperDash":false},{"StartTime":16079.0,"Position":75.0,"HyperDash":false},{"StartTime":16175.0,"Position":377.0,"HyperDash":false},{"StartTime":16271.0,"Position":354.0,"HyperDash":false},{"StartTime":16366.0,"Position":287.0,"HyperDash":false},{"StartTime":16462.0,"Position":361.0,"HyperDash":false},{"StartTime":16558.0,"Position":479.0,"HyperDash":false},{"StartTime":16654.0,"Position":346.0,"HyperDash":false},{"StartTime":16750.0,"Position":266.0,"HyperDash":false},{"StartTime":16845.0,"Position":400.0,"HyperDash":false},{"StartTime":16941.0,"Position":202.0,"HyperDash":false},{"StartTime":17037.0,"Position":500.0,"HyperDash":false},{"StartTime":17133.0,"Position":80.0,"HyperDash":false},{"StartTime":17229.0,"Position":399.0,"HyperDash":false},{"StartTime":17325.0,"Position":455.0,"HyperDash":false}]},{"StartTime":17763.0,"Objects":[{"StartTime":17763.0,"Position":166.0,"HyperDash":false},{"StartTime":17854.0,"Position":153.171234,"HyperDash":false},{"StartTime":17981.0,"Position":164.014587,"HyperDash":false}]},{"StartTime":18201.0,"Objects":[{"StartTime":18201.0,"Position":358.0,"HyperDash":false},{"StartTime":18292.0,"Position":374.828766,"HyperDash":false},{"StartTime":18419.0,"Position":359.9854,"HyperDash":false}]},{"StartTime":18639.0,"Objects":[{"StartTime":18639.0,"Position":165.0,"HyperDash":false},{"StartTime":18730.0,"Position":95.399826,"HyperDash":false},{"StartTime":18857.0,"Position":27.0127716,"HyperDash":false}]},{"StartTime":18967.0,"Objects":[{"StartTime":18967.0,"Position":137.0,"HyperDash":false},{"StartTime":19076.0,"Position":205.993164,"HyperDash":false}]},{"StartTime":19296.0,"Objects":[{"StartTime":19296.0,"Position":25.0,"HyperDash":false}]},{"StartTime":19515.0,"Objects":[{"StartTime":19515.0,"Position":314.0,"HyperDash":false}]},{"StartTime":19624.0,"Objects":[{"StartTime":19624.0,"Position":350.0,"HyperDash":false}]},{"StartTime":19734.0,"Objects":[{"StartTime":19734.0,"Position":312.0,"HyperDash":false}]},{"StartTime":19953.0,"Objects":[{"StartTime":19953.0,"Position":118.0,"HyperDash":false},{"StartTime":20044.0,"Position":174.604065,"HyperDash":false},{"StartTime":20171.0,"Position":255.996536,"HyperDash":false}]},{"StartTime":20390.0,"Objects":[{"StartTime":20390.0,"Position":449.0,"HyperDash":false},{"StartTime":20481.0,"Position":437.183441,"HyperDash":false},{"StartTime":20608.0,"Position":451.835022,"HyperDash":false}]},{"StartTime":20828.0,"Objects":[{"StartTime":20828.0,"Position":271.0,"HyperDash":false}]},{"StartTime":21047.0,"Objects":[{"StartTime":21047.0,"Position":451.0,"HyperDash":true}]},{"StartTime":21266.0,"Objects":[{"StartTime":21266.0,"Position":133.0,"HyperDash":false}]},{"StartTime":21376.0,"Objects":[{"StartTime":21376.0,"Position":97.0,"HyperDash":false}]},{"StartTime":21485.0,"Objects":[{"StartTime":21485.0,"Position":136.0,"HyperDash":false}]},{"StartTime":21704.0,"Objects":[{"StartTime":21704.0,"Position":329.0,"HyperDash":false},{"StartTime":21795.0,"Position":323.8056,"HyperDash":false},{"StartTime":21922.0,"Position":330.929871,"HyperDash":false}]},{"StartTime":22142.0,"Objects":[{"StartTime":22142.0,"Position":136.0,"HyperDash":false},{"StartTime":22233.0,"Position":185.6055,"HyperDash":false},{"StartTime":22360.0,"Position":274.0,"HyperDash":false}]},{"StartTime":22471.0,"Objects":[{"StartTime":22471.0,"Position":385.0,"HyperDash":false},{"StartTime":22580.0,"Position":316.0,"HyperDash":false}]},{"StartTime":22799.0,"Objects":[{"StartTime":22799.0,"Position":136.0,"HyperDash":false}]},{"StartTime":23018.0,"Objects":[{"StartTime":23018.0,"Position":425.0,"HyperDash":false}]},{"StartTime":23128.0,"Objects":[{"StartTime":23128.0,"Position":461.0,"HyperDash":false}]},{"StartTime":23237.0,"Objects":[{"StartTime":23237.0,"Position":421.0,"HyperDash":false}]},{"StartTime":23456.0,"Objects":[{"StartTime":23456.0,"Position":227.0,"HyperDash":false},{"StartTime":23547.0,"Position":216.765884,"HyperDash":false},{"StartTime":23674.0,"Position":224.043533,"HyperDash":false}]},{"StartTime":23894.0,"Objects":[{"StartTime":23894.0,"Position":404.0,"HyperDash":false}]},{"StartTime":24113.0,"Objects":[{"StartTime":24113.0,"Position":224.0,"HyperDash":false}]},{"StartTime":24332.0,"Objects":[{"StartTime":24332.0,"Position":417.0,"HyperDash":false},{"StartTime":24423.0,"Position":412.811279,"HyperDash":false},{"StartTime":24550.0,"Position":418.943481,"HyperDash":false}]},{"StartTime":24661.0,"Objects":[{"StartTime":24661.0,"Position":341.0,"HyperDash":true}]},{"StartTime":24770.0,"Objects":[{"StartTime":24770.0,"Position":107.0,"HyperDash":false}]},{"StartTime":24880.0,"Objects":[{"StartTime":24880.0,"Position":69.0,"HyperDash":false}]},{"StartTime":24989.0,"Objects":[{"StartTime":24989.0,"Position":111.0,"HyperDash":false}]},{"StartTime":25208.0,"Objects":[{"StartTime":25208.0,"Position":304.0,"HyperDash":false},{"StartTime":25299.0,"Position":299.828766,"HyperDash":false},{"StartTime":25426.0,"Position":305.9854,"HyperDash":false}]},{"StartTime":25646.0,"Objects":[{"StartTime":25646.0,"Position":111.0,"HyperDash":false},{"StartTime":25737.0,"Position":124.585579,"HyperDash":false},{"StartTime":25864.0,"Position":110.007217,"HyperDash":false}]},{"StartTime":25974.0,"Objects":[{"StartTime":25974.0,"Position":220.0,"HyperDash":false},{"StartTime":26083.0,"Position":289.0,"HyperDash":false}]},{"StartTime":26303.0,"Objects":[{"StartTime":26303.0,"Position":108.0,"HyperDash":false}]},{"StartTime":26522.0,"Objects":[{"StartTime":26522.0,"Position":397.0,"HyperDash":false}]},{"StartTime":26631.0,"Objects":[{"StartTime":26631.0,"Position":432.0,"HyperDash":false}]},{"StartTime":26741.0,"Objects":[{"StartTime":26741.0,"Position":395.0,"HyperDash":false}]},{"StartTime":26960.0,"Objects":[{"StartTime":26960.0,"Position":215.0,"HyperDash":false}]},{"StartTime":27179.0,"Objects":[{"StartTime":27179.0,"Position":395.0,"HyperDash":false}]},{"StartTime":27398.0,"Objects":[{"StartTime":27398.0,"Position":201.0,"HyperDash":false},{"StartTime":27489.0,"Position":203.591461,"HyperDash":false},{"StartTime":27616.0,"Position":200.0213,"HyperDash":false}]},{"StartTime":27836.0,"Objects":[{"StartTime":27836.0,"Position":380.0,"HyperDash":false}]},{"StartTime":28055.0,"Objects":[{"StartTime":28055.0,"Position":200.0,"HyperDash":false}]},{"StartTime":28164.0,"Objects":[{"StartTime":28164.0,"Position":131.0,"HyperDash":true}]},{"StartTime":28274.0,"Objects":[{"StartTime":28274.0,"Position":365.0,"HyperDash":false},{"StartTime":28328.0,"Position":386.782776,"HyperDash":false},{"StartTime":28383.0,"Position":416.967926,"HyperDash":false},{"StartTime":28437.0,"Position":433.199036,"HyperDash":false},{"StartTime":28492.0,"Position":453.031036,"HyperDash":false},{"StartTime":28583.0,"Position":429.885376,"HyperDash":false},{"StartTime":28711.0,"Position":350.852478,"HyperDash":false}]},{"StartTime":28931.0,"Objects":[{"StartTime":28931.0,"Position":170.0,"HyperDash":false}]},{"StartTime":29150.0,"Objects":[{"StartTime":29150.0,"Position":349.0,"HyperDash":false},{"StartTime":29204.0,"Position":363.954376,"HyperDash":false},{"StartTime":29259.0,"Position":416.5623,"HyperDash":false},{"StartTime":29313.0,"Position":458.929749,"HyperDash":false},{"StartTime":29368.0,"Position":469.713531,"HyperDash":false},{"StartTime":29459.0,"Position":506.401428,"HyperDash":false},{"StartTime":29587.0,"Position":474.2096,"HyperDash":false}]},{"StartTime":30026.0,"Objects":[{"StartTime":30026.0,"Position":114.0,"HyperDash":false}]},{"StartTime":30244.0,"Objects":[{"StartTime":30244.0,"Position":292.0,"HyperDash":false}]},{"StartTime":30463.0,"Objects":[{"StartTime":30463.0,"Position":114.0,"HyperDash":false},{"StartTime":30554.0,"Position":104.591461,"HyperDash":false},{"StartTime":30681.0,"Position":113.0213,"HyperDash":false}]},{"StartTime":30901.0,"Objects":[{"StartTime":30901.0,"Position":307.0,"HyperDash":false},{"StartTime":30992.0,"Position":296.817017,"HyperDash":false},{"StartTime":31119.0,"Position":308.957245,"HyperDash":false}]},{"StartTime":31230.0,"Objects":[{"StartTime":31230.0,"Position":197.0,"HyperDash":false},{"StartTime":31339.0,"Position":128.007462,"HyperDash":false}]},{"StartTime":31558.0,"Objects":[{"StartTime":31558.0,"Position":417.0,"HyperDash":false},{"StartTime":31667.0,"Position":417.932343,"HyperDash":true}]},{"StartTime":31777.0,"Objects":[{"StartTime":31777.0,"Position":148.0,"HyperDash":false}]},{"StartTime":31887.0,"Objects":[{"StartTime":31887.0,"Position":78.0,"HyperDash":false}]},{"StartTime":31996.0,"Objects":[{"StartTime":31996.0,"Position":148.0,"HyperDash":false}]},{"StartTime":32215.0,"Objects":[{"StartTime":32215.0,"Position":341.0,"HyperDash":false},{"StartTime":32306.0,"Position":339.731537,"HyperDash":false},{"StartTime":32433.0,"Position":342.362183,"HyperDash":false}]},{"StartTime":32544.0,"Objects":[{"StartTime":32544.0,"Position":265.0,"HyperDash":false}]},{"StartTime":32653.0,"Objects":[{"StartTime":32653.0,"Position":155.0,"HyperDash":false},{"StartTime":32744.0,"Position":115.395584,"HyperDash":false},{"StartTime":32871.0,"Position":17.0026245,"HyperDash":false}]},{"StartTime":32982.0,"Objects":[{"StartTime":32982.0,"Position":93.0,"HyperDash":true}]},{"StartTime":33091.0,"Objects":[{"StartTime":33091.0,"Position":292.0,"HyperDash":false}]},{"StartTime":33310.0,"Objects":[{"StartTime":33310.0,"Position":112.0,"HyperDash":false},{"StartTime":33419.0,"Position":110.057106,"HyperDash":true}]},{"StartTime":33529.0,"Objects":[{"StartTime":33529.0,"Position":327.0,"HyperDash":false}]},{"StartTime":33639.0,"Objects":[{"StartTime":33639.0,"Position":396.0,"HyperDash":false}]},{"StartTime":33748.0,"Objects":[{"StartTime":33748.0,"Position":327.0,"HyperDash":false}]},{"StartTime":33967.0,"Objects":[{"StartTime":33967.0,"Position":133.0,"HyperDash":false},{"StartTime":34058.0,"Position":142.165222,"HyperDash":false},{"StartTime":34185.0,"Position":131.000214,"HyperDash":false}]},{"StartTime":34296.0,"Objects":[{"StartTime":34296.0,"Position":207.0,"HyperDash":false}]},{"StartTime":34405.0,"Objects":[{"StartTime":34405.0,"Position":316.0,"HyperDash":false},{"StartTime":34496.0,"Position":277.3945,"HyperDash":false},{"StartTime":34623.0,"Position":178.0,"HyperDash":false}]},{"StartTime":34734.0,"Objects":[{"StartTime":34734.0,"Position":254.0,"HyperDash":true}]},{"StartTime":34843.0,"Objects":[{"StartTime":34843.0,"Position":453.0,"HyperDash":false},{"StartTime":34934.0,"Position":448.526672,"HyperDash":false},{"StartTime":35061.0,"Position":455.260864,"HyperDash":false}]},{"StartTime":35172.0,"Objects":[{"StartTime":35172.0,"Position":378.0,"HyperDash":true}]},{"StartTime":35281.0,"Objects":[{"StartTime":35281.0,"Position":145.0,"HyperDash":false}]},{"StartTime":35390.0,"Objects":[{"StartTime":35390.0,"Position":76.0,"HyperDash":false}]},{"StartTime":35500.0,"Objects":[{"StartTime":35500.0,"Position":145.0,"HyperDash":false}]},{"StartTime":35719.0,"Objects":[{"StartTime":35719.0,"Position":338.0,"HyperDash":false},{"StartTime":35810.0,"Position":332.840851,"HyperDash":false},{"StartTime":35937.0,"Position":340.014374,"HyperDash":false}]},{"StartTime":36047.0,"Objects":[{"StartTime":36047.0,"Position":263.0,"HyperDash":false}]},{"StartTime":36157.0,"Objects":[{"StartTime":36157.0,"Position":165.0,"HyperDash":false}]},{"StartTime":36266.0,"Objects":[{"StartTime":36266.0,"Position":263.0,"HyperDash":false}]},{"StartTime":36376.0,"Objects":[{"StartTime":36376.0,"Position":339.0,"HyperDash":false}]},{"StartTime":36485.0,"Objects":[{"StartTime":36485.0,"Position":263.0,"HyperDash":true}]},{"StartTime":36595.0,"Objects":[{"StartTime":36595.0,"Position":61.0,"HyperDash":false},{"StartTime":36686.0,"Position":51.31877,"HyperDash":false},{"StartTime":36813.0,"Position":59.2572021,"HyperDash":false}]},{"StartTime":36923.0,"Objects":[{"StartTime":36923.0,"Position":135.0,"HyperDash":true}]},{"StartTime":37033.0,"Objects":[{"StartTime":37033.0,"Position":371.0,"HyperDash":false}]},{"StartTime":37142.0,"Objects":[{"StartTime":37142.0,"Position":439.0,"HyperDash":false}]},{"StartTime":37252.0,"Objects":[{"StartTime":37252.0,"Position":371.0,"HyperDash":false}]},{"StartTime":37471.0,"Objects":[{"StartTime":37471.0,"Position":177.0,"HyperDash":false},{"StartTime":37562.0,"Position":246.6055,"HyperDash":false},{"StartTime":37689.0,"Position":315.0,"HyperDash":false}]},{"StartTime":37799.0,"Objects":[{"StartTime":37799.0,"Position":238.0,"HyperDash":false}]},{"StartTime":37909.0,"Objects":[{"StartTime":37909.0,"Position":127.0,"HyperDash":false},{"StartTime":38000.0,"Position":113.171227,"HyperDash":false},{"StartTime":38127.0,"Position":125.014595,"HyperDash":false}]},{"StartTime":38237.0,"Objects":[{"StartTime":38237.0,"Position":201.0,"HyperDash":true}]},{"StartTime":38347.0,"Objects":[{"StartTime":38347.0,"Position":402.0,"HyperDash":false},{"StartTime":38438.0,"Position":395.763641,"HyperDash":false},{"StartTime":38565.0,"Position":404.707062,"HyperDash":false}]},{"StartTime":38675.0,"Objects":[{"StartTime":38675.0,"Position":328.0,"HyperDash":true}]},{"StartTime":38785.0,"Objects":[{"StartTime":38785.0,"Position":92.0,"HyperDash":false}]},{"StartTime":38894.0,"Objects":[{"StartTime":38894.0,"Position":23.0,"HyperDash":false}]},{"StartTime":39004.0,"Objects":[{"StartTime":39004.0,"Position":92.0,"HyperDash":false}]},{"StartTime":39223.0,"Objects":[{"StartTime":39223.0,"Position":285.0,"HyperDash":false},{"StartTime":39314.0,"Position":323.6055,"HyperDash":false},{"StartTime":39441.0,"Position":423.0,"HyperDash":false}]},{"StartTime":39551.0,"Objects":[{"StartTime":39551.0,"Position":346.0,"HyperDash":false}]},{"StartTime":39661.0,"Objects":[{"StartTime":39661.0,"Position":235.0,"HyperDash":false},{"StartTime":39770.0,"Position":234.054886,"HyperDash":false}]},{"StartTime":39880.0,"Objects":[{"StartTime":39880.0,"Position":344.0,"HyperDash":false},{"StartTime":39989.0,"Position":345.9429,"HyperDash":true}]},{"StartTime":40099.0,"Objects":[{"StartTime":40099.0,"Position":144.0,"HyperDash":false},{"StartTime":40190.0,"Position":89.39449,"HyperDash":false},{"StartTime":40317.0,"Position":6.0,"HyperDash":false}]},{"StartTime":40427.0,"Objects":[{"StartTime":40427.0,"Position":82.0,"HyperDash":true}]},{"StartTime":40536.0,"Objects":[{"StartTime":40536.0,"Position":315.0,"HyperDash":false}]},{"StartTime":40646.0,"Objects":[{"StartTime":40646.0,"Position":384.0,"HyperDash":false}]},{"StartTime":40755.0,"Objects":[{"StartTime":40755.0,"Position":315.0,"HyperDash":false}]},{"StartTime":40974.0,"Objects":[{"StartTime":40974.0,"Position":121.0,"HyperDash":false},{"StartTime":41065.0,"Position":106.171227,"HyperDash":false},{"StartTime":41192.0,"Position":119.014595,"HyperDash":false}]},{"StartTime":41303.0,"Objects":[{"StartTime":41303.0,"Position":195.0,"HyperDash":true}]},{"StartTime":41412.0,"Objects":[{"StartTime":41412.0,"Position":394.0,"HyperDash":false}]},{"StartTime":41631.0,"Objects":[{"StartTime":41631.0,"Position":214.0,"HyperDash":false}]},{"StartTime":41741.0,"Objects":[{"StartTime":41741.0,"Position":144.0,"HyperDash":false}]},{"StartTime":41850.0,"Objects":[{"StartTime":41850.0,"Position":214.0,"HyperDash":false}]},{"StartTime":42069.0,"Objects":[{"StartTime":42069.0,"Position":407.0,"HyperDash":false},{"StartTime":42178.0,"Position":476.0,"HyperDash":true}]},{"StartTime":42288.0,"Objects":[{"StartTime":42288.0,"Position":240.0,"HyperDash":false}]},{"StartTime":42398.0,"Objects":[{"StartTime":42398.0,"Position":170.0,"HyperDash":false}]},{"StartTime":42507.0,"Objects":[{"StartTime":42507.0,"Position":240.0,"HyperDash":false}]},{"StartTime":42726.0,"Objects":[{"StartTime":42726.0,"Position":419.0,"HyperDash":false}]},{"StartTime":42945.0,"Objects":[{"StartTime":42945.0,"Position":129.0,"HyperDash":false},{"StartTime":43054.0,"Position":128.028259,"HyperDash":false}]},{"StartTime":43164.0,"Objects":[{"StartTime":43164.0,"Position":238.0,"HyperDash":false},{"StartTime":43255.0,"Position":301.604065,"HyperDash":false},{"StartTime":43382.0,"Position":375.996582,"HyperDash":false}]},{"StartTime":43493.0,"Objects":[{"StartTime":43493.0,"Position":299.0,"HyperDash":false}]},{"StartTime":43602.0,"Objects":[{"StartTime":43602.0,"Position":195.0,"HyperDash":false}]},{"StartTime":43821.0,"Objects":[{"StartTime":43821.0,"Position":374.0,"HyperDash":false}]},{"StartTime":43931.0,"Objects":[{"StartTime":43931.0,"Position":376.0,"HyperDash":true}]},{"StartTime":44040.0,"Objects":[{"StartTime":44040.0,"Position":108.0,"HyperDash":false}]},{"StartTime":44150.0,"Objects":[{"StartTime":44150.0,"Position":106.0,"HyperDash":false}]},{"StartTime":44259.0,"Objects":[{"StartTime":44259.0,"Position":209.0,"HyperDash":false}]},{"StartTime":44478.0,"Objects":[{"StartTime":44478.0,"Position":388.0,"HyperDash":false}]},{"StartTime":44697.0,"Objects":[{"StartTime":44697.0,"Position":195.0,"HyperDash":false}]},{"StartTime":44916.0,"Objects":[{"StartTime":44916.0,"Position":484.0,"HyperDash":false}]},{"StartTime":45026.0,"Objects":[{"StartTime":45026.0,"Position":407.0,"HyperDash":false}]},{"StartTime":45244.0,"Objects":[{"StartTime":45244.0,"Position":213.0,"HyperDash":false}]},{"StartTime":45354.0,"Objects":[{"StartTime":45354.0,"Position":316.0,"HyperDash":false},{"StartTime":45445.0,"Position":386.604126,"HyperDash":false},{"StartTime":45572.0,"Position":453.996674,"HyperDash":true}]},{"StartTime":45792.0,"Objects":[{"StartTime":45792.0,"Position":103.0,"HyperDash":false},{"StartTime":45846.0,"Position":59.25476,"HyperDash":false},{"StartTime":45901.0,"Position":32.3233032,"HyperDash":false},{"StartTime":45955.0,"Position":30.0666771,"HyperDash":false},{"StartTime":46010.0,"Position":14.6513605,"HyperDash":false},{"StartTime":46101.0,"Position":45.94827,"HyperDash":false},{"StartTime":46229.0,"Position":114.217232,"HyperDash":false}]},{"StartTime":46449.0,"Objects":[{"StartTime":46449.0,"Position":294.0,"HyperDash":false},{"StartTime":46503.0,"Position":260.4281,"HyperDash":false},{"StartTime":46558.0,"Position":236.271561,"HyperDash":false},{"StartTime":46612.0,"Position":192.699677,"HyperDash":false},{"StartTime":46667.0,"Position":166.543121,"HyperDash":false},{"StartTime":46758.0,"Position":132.338623,"HyperDash":false},{"StartTime":46886.0,"Position":38.5015564,"HyperDash":false}]},{"StartTime":47106.0,"Objects":[{"StartTime":47106.0,"Position":204.0,"HyperDash":false}]},{"StartTime":47325.0,"Objects":[{"StartTime":47325.0,"Position":38.0,"HyperDash":true}]},{"StartTime":47544.0,"Objects":[{"StartTime":47544.0,"Position":355.0,"HyperDash":false},{"StartTime":47598.0,"Position":383.787079,"HyperDash":false},{"StartTime":47653.0,"Position":419.23172,"HyperDash":false},{"StartTime":47707.0,"Position":453.6615,"HyperDash":false},{"StartTime":47762.0,"Position":443.4226,"HyperDash":false},{"StartTime":47853.0,"Position":433.484253,"HyperDash":false},{"StartTime":47981.0,"Position":340.2439,"HyperDash":false}]},{"StartTime":48201.0,"Objects":[{"StartTime":48201.0,"Position":173.0,"HyperDash":false}]},{"StartTime":48420.0,"Objects":[{"StartTime":48420.0,"Position":338.0,"HyperDash":false},{"StartTime":48474.0,"Position":355.147217,"HyperDash":false},{"StartTime":48529.0,"Position":351.588867,"HyperDash":false},{"StartTime":48583.0,"Position":355.8642,"HyperDash":false},{"StartTime":48638.0,"Position":329.530029,"HyperDash":false},{"StartTime":48729.0,"Position":296.7339,"HyperDash":false},{"StartTime":48857.0,"Position":203.29097,"HyperDash":false}]},{"StartTime":49077.0,"Objects":[{"StartTime":49077.0,"Position":369.0,"HyperDash":true}]},{"StartTime":49296.0,"Objects":[{"StartTime":49296.0,"Position":51.0,"HyperDash":false},{"StartTime":49387.0,"Position":38.1829834,"HyperDash":false},{"StartTime":49514.0,"Position":49.04275,"HyperDash":false}]},{"StartTime":49734.0,"Objects":[{"StartTime":49734.0,"Position":229.0,"HyperDash":false},{"StartTime":49825.0,"Position":270.604065,"HyperDash":false},{"StartTime":49952.0,"Position":366.996582,"HyperDash":false}]},{"StartTime":50172.0,"Objects":[{"StartTime":50172.0,"Position":186.0,"HyperDash":false},{"StartTime":50263.0,"Position":121.395981,"HyperDash":false},{"StartTime":50390.0,"Position":48.00357,"HyperDash":false}]},{"StartTime":50609.0,"Objects":[{"StartTime":50609.0,"Position":227.0,"HyperDash":false}]},{"StartTime":50828.0,"Objects":[{"StartTime":50828.0,"Position":47.0,"HyperDash":true}]},{"StartTime":51047.0,"Objects":[{"StartTime":51047.0,"Position":347.0,"HyperDash":false},{"StartTime":51101.0,"Position":362.642029,"HyperDash":false},{"StartTime":51156.0,"Position":410.800537,"HyperDash":false},{"StartTime":51210.0,"Position":450.264282,"HyperDash":false},{"StartTime":51265.0,"Position":470.407257,"HyperDash":false},{"StartTime":51356.0,"Position":491.032837,"HyperDash":false},{"StartTime":51484.0,"Position":477.784576,"HyperDash":false}]},{"StartTime":51923.0,"Objects":[{"StartTime":51923.0,"Position":118.0,"HyperDash":false},{"StartTime":52014.0,"Position":119.348648,"HyperDash":false},{"StartTime":52141.0,"Position":119.0904,"HyperDash":false}]},{"StartTime":52361.0,"Objects":[{"StartTime":52361.0,"Position":313.0,"HyperDash":false}]},{"StartTime":52580.0,"Objects":[{"StartTime":52580.0,"Position":119.0,"HyperDash":true}]},{"StartTime":52799.0,"Objects":[{"StartTime":52799.0,"Position":436.0,"HyperDash":false},{"StartTime":52853.0,"Position":399.0876,"HyperDash":false},{"StartTime":52908.0,"Position":381.54715,"HyperDash":false},{"StartTime":52962.0,"Position":320.634735,"HyperDash":false},{"StartTime":53017.0,"Position":299.0943,"HyperDash":false},{"StartTime":53108.0,"Position":229.9456,"HyperDash":false},{"StartTime":53236.0,"Position":161.560608,"HyperDash":false}]},{"StartTime":53456.0,"Objects":[{"StartTime":53456.0,"Position":452.0,"HyperDash":false}]},{"StartTime":53566.0,"Objects":[{"StartTime":53566.0,"Position":489.0,"HyperDash":false}]},{"StartTime":53675.0,"Objects":[{"StartTime":53675.0,"Position":454.0,"HyperDash":false}]},{"StartTime":53894.0,"Objects":[{"StartTime":53894.0,"Position":274.0,"HyperDash":false}]},{"StartTime":54113.0,"Objects":[{"StartTime":54113.0,"Position":454.0,"HyperDash":false},{"StartTime":54204.0,"Position":399.395721,"HyperDash":false},{"StartTime":54331.0,"Position":316.00293,"HyperDash":true}]},{"StartTime":54551.0,"Objects":[{"StartTime":54551.0,"Position":24.0,"HyperDash":false},{"StartTime":54605.0,"Position":67.96123,"HyperDash":false},{"StartTime":54660.0,"Position":88.55135,"HyperDash":false},{"StartTime":54714.0,"Position":106.512581,"HyperDash":false},{"StartTime":54769.0,"Position":161.1027,"HyperDash":false},{"StartTime":54860.0,"Position":223.333679,"HyperDash":false},{"StartTime":54988.0,"Position":298.834351,"HyperDash":false}]},{"StartTime":55208.0,"Objects":[{"StartTime":55208.0,"Position":104.0,"HyperDash":false}]},{"StartTime":55317.0,"Objects":[{"StartTime":55317.0,"Position":62.0,"HyperDash":false}]},{"StartTime":55427.0,"Objects":[{"StartTime":55427.0,"Position":104.0,"HyperDash":false}]},{"StartTime":55646.0,"Objects":[{"StartTime":55646.0,"Position":393.0,"HyperDash":false},{"StartTime":55737.0,"Position":340.600342,"HyperDash":false},{"StartTime":55864.0,"Position":267.4712,"HyperDash":false}]},{"StartTime":56084.0,"Objects":[{"StartTime":56084.0,"Position":87.0,"HyperDash":true}]},{"StartTime":56303.0,"Objects":[{"StartTime":56303.0,"Position":432.0,"HyperDash":false},{"StartTime":56357.0,"Position":388.775055,"HyperDash":false},{"StartTime":56412.0,"Position":359.8976,"HyperDash":false},{"StartTime":56466.0,"Position":338.6523,"HyperDash":false},{"StartTime":56521.0,"Position":318.247742,"HyperDash":false},{"StartTime":56612.0,"Position":256.532,"HyperDash":false},{"StartTime":56740.0,"Position":183.343277,"HyperDash":false}]},{"StartTime":56960.0,"Objects":[{"StartTime":56960.0,"Position":365.0,"HyperDash":false}]},{"StartTime":57179.0,"Objects":[{"StartTime":57179.0,"Position":75.0,"HyperDash":false},{"StartTime":57270.0,"Position":148.586823,"HyperDash":false},{"StartTime":57397.0,"Position":212.955231,"HyperDash":false}]},{"StartTime":57617.0,"Objects":[{"StartTime":57617.0,"Position":407.0,"HyperDash":false},{"StartTime":57708.0,"Position":422.1916,"HyperDash":false},{"StartTime":57835.0,"Position":409.854553,"HyperDash":true}]},{"StartTime":58055.0,"Objects":[{"StartTime":58055.0,"Position":118.0,"HyperDash":false},{"StartTime":58109.0,"Position":145.079269,"HyperDash":false},{"StartTime":58164.0,"Position":167.789642,"HyperDash":false},{"StartTime":58218.0,"Position":202.8689,"HyperDash":false},{"StartTime":58273.0,"Position":255.579269,"HyperDash":false},{"StartTime":58328.0,"Position":291.2896,"HyperDash":false},{"StartTime":58383.0,"Position":325.0,"HyperDash":false},{"StartTime":58437.0,"Position":294.920746,"HyperDash":false},{"StartTime":58492.0,"Position":256.210358,"HyperDash":false},{"StartTime":58583.0,"Position":185.780487,"HyperDash":false},{"StartTime":58711.0,"Position":118.0,"HyperDash":false}]},{"StartTime":58931.0,"Objects":[{"StartTime":58931.0,"Position":312.0,"HyperDash":false},{"StartTime":58985.0,"Position":312.3719,"HyperDash":false},{"StartTime":59040.0,"Position":318.289,"HyperDash":false},{"StartTime":59094.0,"Position":279.119,"HyperDash":false},{"StartTime":59149.0,"Position":279.84906,"HyperDash":false},{"StartTime":59203.0,"Position":240.6435,"HyperDash":false},{"StartTime":59258.0,"Position":241.519592,"HyperDash":false},{"StartTime":59313.0,"Position":180.829834,"HyperDash":false},{"StartTime":59368.0,"Position":166.661133,"HyperDash":false},{"StartTime":59459.0,"Position":118.512878,"HyperDash":false},{"StartTime":59587.0,"Position":33.8594971,"HyperDash":true}]},{"StartTime":59807.0,"Objects":[{"StartTime":59807.0,"Position":380.0,"HyperDash":false},{"StartTime":59898.0,"Position":403.4091,"HyperDash":false},{"StartTime":60025.0,"Position":380.555023,"HyperDash":false}]},{"StartTime":60135.0,"Objects":[{"StartTime":60135.0,"Position":290.0,"HyperDash":false}]},{"StartTime":60244.0,"Objects":[{"StartTime":60244.0,"Position":380.0,"HyperDash":false},{"StartTime":60353.0,"Position":381.815155,"HyperDash":true}]},{"StartTime":60463.0,"Objects":[{"StartTime":60463.0,"Position":180.0,"HyperDash":false},{"StartTime":60572.0,"Position":111.0,"HyperDash":true}]},{"StartTime":60682.0,"Objects":[{"StartTime":60682.0,"Position":346.0,"HyperDash":false},{"StartTime":60791.0,"Position":346.0,"HyperDash":true}]},{"StartTime":60901.0,"Objects":[{"StartTime":60901.0,"Position":144.0,"HyperDash":true}]},{"StartTime":61011.0,"Objects":[{"StartTime":61011.0,"Position":345.0,"HyperDash":false}]},{"StartTime":61120.0,"Objects":[{"StartTime":61120.0,"Position":441.0,"HyperDash":false},{"StartTime":61211.0,"Position":474.310272,"HyperDash":false},{"StartTime":61338.0,"Position":439.1717,"HyperDash":false}]},{"StartTime":61449.0,"Objects":[{"StartTime":61449.0,"Position":355.0,"HyperDash":true}]},{"StartTime":61558.0,"Objects":[{"StartTime":61558.0,"Position":121.0,"HyperDash":false},{"StartTime":61667.0,"Position":120.041756,"HyperDash":true}]},{"StartTime":61777.0,"Objects":[{"StartTime":61777.0,"Position":321.0,"HyperDash":true}]},{"StartTime":61887.0,"Objects":[{"StartTime":61887.0,"Position":120.0,"HyperDash":false}]},{"StartTime":61996.0,"Objects":[{"StartTime":61996.0,"Position":23.0,"HyperDash":false},{"StartTime":62087.0,"Position":92.6042938,"HyperDash":false},{"StartTime":62214.0,"Position":160.997086,"HyperDash":false}]},{"StartTime":62325.0,"Objects":[{"StartTime":62325.0,"Position":63.0,"HyperDash":true}]},{"StartTime":62434.0,"Objects":[{"StartTime":62434.0,"Position":296.0,"HyperDash":false},{"StartTime":62543.0,"Position":296.971741,"HyperDash":false}]},{"StartTime":62653.0,"Objects":[{"StartTime":62653.0,"Position":199.0,"HyperDash":true}]},{"StartTime":62763.0,"Objects":[{"StartTime":62763.0,"Position":400.0,"HyperDash":false}]},{"StartTime":62872.0,"Objects":[{"StartTime":62872.0,"Position":303.0,"HyperDash":false},{"StartTime":62963.0,"Position":294.5297,"HyperDash":false},{"StartTime":63090.0,"Position":354.147156,"HyperDash":false}]},{"StartTime":63201.0,"Objects":[{"StartTime":63201.0,"Position":438.0,"HyperDash":true}]},{"StartTime":63310.0,"Objects":[{"StartTime":63310.0,"Position":204.0,"HyperDash":false},{"StartTime":63401.0,"Position":148.549332,"HyperDash":false},{"StartTime":63528.0,"Position":93.9026642,"HyperDash":false}]},{"StartTime":63639.0,"Objects":[{"StartTime":63639.0,"Position":184.0,"HyperDash":false}]},{"StartTime":63748.0,"Objects":[{"StartTime":63748.0,"Position":93.0,"HyperDash":false},{"StartTime":63857.0,"Position":92.17863,"HyperDash":true}]},{"StartTime":63967.0,"Objects":[{"StartTime":63967.0,"Position":293.0,"HyperDash":false},{"StartTime":64076.0,"Position":293.919922,"HyperDash":true}]},{"StartTime":64186.0,"Objects":[{"StartTime":64186.0,"Position":93.0,"HyperDash":true}]},{"StartTime":64296.0,"Objects":[{"StartTime":64296.0,"Position":293.0,"HyperDash":false},{"StartTime":64405.0,"Position":362.0,"HyperDash":true}]},{"StartTime":64515.0,"Objects":[{"StartTime":64515.0,"Position":160.0,"HyperDash":false}]},{"StartTime":64624.0,"Objects":[{"StartTime":64624.0,"Position":63.0,"HyperDash":false},{"StartTime":64715.0,"Position":16.49675,"HyperDash":false},{"StartTime":64842.0,"Position":70.69653,"HyperDash":false}]},{"StartTime":64953.0,"Objects":[{"StartTime":64953.0,"Position":154.0,"HyperDash":true}]},{"StartTime":65062.0,"Objects":[{"StartTime":65062.0,"Position":387.0,"HyperDash":false},{"StartTime":65171.0,"Position":318.007446,"HyperDash":true}]},{"StartTime":65281.0,"Objects":[{"StartTime":65281.0,"Position":116.0,"HyperDash":true}]},{"StartTime":65390.0,"Objects":[{"StartTime":65390.0,"Position":318.0,"HyperDash":false}]},{"StartTime":65500.0,"Objects":[{"StartTime":65500.0,"Position":415.0,"HyperDash":false},{"StartTime":65591.0,"Position":455.432068,"HyperDash":false},{"StartTime":65718.0,"Position":412.315582,"HyperDash":false}]},{"StartTime":65828.0,"Objects":[{"StartTime":65828.0,"Position":315.0,"HyperDash":true}]},{"StartTime":65938.0,"Objects":[{"StartTime":65938.0,"Position":79.0,"HyperDash":false},{"StartTime":66047.0,"Position":78.01439,"HyperDash":false}]},{"StartTime":66157.0,"Objects":[{"StartTime":66157.0,"Position":175.0,"HyperDash":true}]},{"StartTime":66266.0,"Objects":[{"StartTime":66266.0,"Position":374.0,"HyperDash":false}]},{"StartTime":66376.0,"Objects":[{"StartTime":66376.0,"Position":276.0,"HyperDash":false},{"StartTime":66467.0,"Position":321.6042,"HyperDash":false},{"StartTime":66594.0,"Position":413.996857,"HyperDash":false}]},{"StartTime":66704.0,"Objects":[{"StartTime":66704.0,"Position":331.0,"HyperDash":true}]},{"StartTime":66814.0,"Objects":[{"StartTime":66814.0,"Position":60.0,"HyperDash":false},{"StartTime":66905.0,"Position":21.5649185,"HyperDash":false},{"StartTime":67032.0,"Position":61.75552,"HyperDash":false}]},{"StartTime":67142.0,"Objects":[{"StartTime":67142.0,"Position":151.0,"HyperDash":false}]},{"StartTime":67252.0,"Objects":[{"StartTime":67252.0,"Position":61.0,"HyperDash":true}]},{"StartTime":67471.0,"Objects":[{"StartTime":67471.0,"Position":378.0,"HyperDash":false}]},{"StartTime":67580.0,"Objects":[{"StartTime":67580.0,"Position":422.0,"HyperDash":false}]},{"StartTime":67690.0,"Objects":[{"StartTime":67690.0,"Position":381.0,"HyperDash":false}]},{"StartTime":67799.0,"Objects":[{"StartTime":67799.0,"Position":305.0,"HyperDash":false}]},{"StartTime":67909.0,"Objects":[{"StartTime":67909.0,"Position":194.0,"HyperDash":false},{"StartTime":68018.0,"Position":193.103973,"HyperDash":true}]},{"StartTime":68128.0,"Objects":[{"StartTime":68128.0,"Position":428.0,"HyperDash":false},{"StartTime":68219.0,"Position":351.3945,"HyperDash":false},{"StartTime":68346.0,"Position":290.0,"HyperDash":false}]},{"StartTime":68456.0,"Objects":[{"StartTime":68456.0,"Position":373.0,"HyperDash":true}]},{"StartTime":68566.0,"Objects":[{"StartTime":68566.0,"Position":137.0,"HyperDash":false},{"StartTime":68675.0,"Position":135.057114,"HyperDash":false}]},{"StartTime":68785.0,"Objects":[{"StartTime":68785.0,"Position":245.0,"HyperDash":false},{"StartTime":68894.0,"Position":245.896027,"HyperDash":true}]},{"StartTime":69004.0,"Objects":[{"StartTime":69004.0,"Position":44.0,"HyperDash":false},{"StartTime":69095.0,"Position":103.604172,"HyperDash":false},{"StartTime":69222.0,"Position":181.9968,"HyperDash":false}]},{"StartTime":69332.0,"Objects":[{"StartTime":69332.0,"Position":98.0,"HyperDash":true}]},{"StartTime":69442.0,"Objects":[{"StartTime":69442.0,"Position":333.0,"HyperDash":false},{"StartTime":69551.0,"Position":334.768646,"HyperDash":true}]},{"StartTime":69661.0,"Objects":[{"StartTime":69661.0,"Position":133.0,"HyperDash":false}]},{"StartTime":69880.0,"Objects":[{"StartTime":69880.0,"Position":326.0,"HyperDash":false}]},{"StartTime":70099.0,"Objects":[{"StartTime":70099.0,"Position":133.0,"HyperDash":false},{"StartTime":70208.0,"Position":131.084076,"HyperDash":true}]},{"StartTime":70317.0,"Objects":[{"StartTime":70317.0,"Position":398.0,"HyperDash":false},{"StartTime":70371.0,"Position":358.896545,"HyperDash":false},{"StartTime":70426.0,"Position":310.16153,"HyperDash":false},{"StartTime":70480.0,"Position":280.058075,"HyperDash":false},{"StartTime":70535.0,"Position":260.323059,"HyperDash":false},{"StartTime":70626.0,"Position":200.8524,"HyperDash":false},{"StartTime":70754.0,"Position":122.014557,"HyperDash":true}]},{"StartTime":70974.0,"Objects":[{"StartTime":70974.0,"Position":468.0,"HyperDash":false},{"StartTime":71028.0,"Position":427.894928,"HyperDash":false},{"StartTime":71083.0,"Position":386.1583,"HyperDash":false},{"StartTime":71137.0,"Position":356.053223,"HyperDash":false},{"StartTime":71192.0,"Position":330.3166,"HyperDash":false},{"StartTime":71283.0,"Position":261.843262,"HyperDash":false},{"StartTime":71411.0,"Position":192.001617,"HyperDash":false}]},{"StartTime":71631.0,"Objects":[{"StartTime":71631.0,"Position":483.0,"HyperDash":false},{"StartTime":71722.0,"Position":425.3945,"HyperDash":false},{"StartTime":71849.0,"Position":345.0,"HyperDash":true}]},{"StartTime":72069.0,"Objects":[{"StartTime":72069.0,"Position":26.0,"HyperDash":false},{"StartTime":72123.0,"Position":46.07927,"HyperDash":false},{"StartTime":72178.0,"Position":95.7896347,"HyperDash":false},{"StartTime":72232.0,"Position":143.8689,"HyperDash":false},{"StartTime":72287.0,"Position":163.579269,"HyperDash":false},{"StartTime":72342.0,"Position":192.289627,"HyperDash":false},{"StartTime":72397.0,"Position":233.0,"HyperDash":false},{"StartTime":72451.0,"Position":215.920746,"HyperDash":false},{"StartTime":72506.0,"Position":164.210358,"HyperDash":false},{"StartTime":72597.0,"Position":121.780487,"HyperDash":false},{"StartTime":72725.0,"Position":26.0,"HyperDash":true}]},{"StartTime":72945.0,"Objects":[{"StartTime":72945.0,"Position":344.0,"HyperDash":false},{"StartTime":72999.0,"Position":395.749939,"HyperDash":false},{"StartTime":73054.0,"Position":392.115662,"HyperDash":false},{"StartTime":73108.0,"Position":414.29538,"HyperDash":false},{"StartTime":73163.0,"Position":437.262848,"HyperDash":false},{"StartTime":73254.0,"Position":425.923279,"HyperDash":false},{"StartTime":73382.0,"Position":338.6331,"HyperDash":false}]},{"StartTime":73493.0,"Objects":[{"StartTime":73493.0,"Position":247.0,"HyperDash":false}]},{"StartTime":73602.0,"Objects":[{"StartTime":73602.0,"Position":338.0,"HyperDash":true}]},{"StartTime":73712.0,"Objects":[{"StartTime":73712.0,"Position":102.0,"HyperDash":true}]},{"StartTime":73821.0,"Objects":[{"StartTime":73821.0,"Position":338.0,"HyperDash":false},{"StartTime":73912.0,"Position":386.8002,"HyperDash":false},{"StartTime":74039.0,"Position":335.152557,"HyperDash":false}]},{"StartTime":74150.0,"Objects":[{"StartTime":74150.0,"Position":244.0,"HyperDash":false}]},{"StartTime":74259.0,"Objects":[{"StartTime":74259.0,"Position":334.0,"HyperDash":false},{"StartTime":74368.0,"Position":334.958252,"HyperDash":true}]},{"StartTime":74478.0,"Objects":[{"StartTime":74478.0,"Position":133.0,"HyperDash":false},{"StartTime":74587.0,"Position":131.253723,"HyperDash":true}]},{"StartTime":74697.0,"Objects":[{"StartTime":74697.0,"Position":366.0,"HyperDash":false},{"StartTime":74806.0,"Position":366.896027,"HyperDash":true}]},{"StartTime":74916.0,"Objects":[{"StartTime":74916.0,"Position":165.0,"HyperDash":true}]},{"StartTime":75026.0,"Objects":[{"StartTime":75026.0,"Position":366.0,"HyperDash":false}]},{"StartTime":75135.0,"Objects":[{"StartTime":75135.0,"Position":462.0,"HyperDash":false},{"StartTime":75226.0,"Position":396.3945,"HyperDash":false},{"StartTime":75353.0,"Position":324.0,"HyperDash":false}]},{"StartTime":75463.0,"Objects":[{"StartTime":75463.0,"Position":407.0,"HyperDash":true}]},{"StartTime":75573.0,"Objects":[{"StartTime":75573.0,"Position":171.0,"HyperDash":false},{"StartTime":75682.0,"Position":169.3576,"HyperDash":true}]},{"StartTime":75792.0,"Objects":[{"StartTime":75792.0,"Position":370.0,"HyperDash":true}]},{"StartTime":75901.0,"Objects":[{"StartTime":75901.0,"Position":170.0,"HyperDash":false}]},{"StartTime":76011.0,"Objects":[{"StartTime":76011.0,"Position":72.0,"HyperDash":false},{"StartTime":76102.0,"Position":31.1678276,"HyperDash":false},{"StartTime":76229.0,"Position":82.8498,"HyperDash":false}]},{"StartTime":76339.0,"Objects":[{"StartTime":76339.0,"Position":179.0,"HyperDash":true}]},{"StartTime":76449.0,"Objects":[{"StartTime":76449.0,"Position":414.0,"HyperDash":false},{"StartTime":76558.0,"Position":483.0,"HyperDash":false}]},{"StartTime":76668.0,"Objects":[{"StartTime":76668.0,"Position":385.0,"HyperDash":true}]},{"StartTime":76777.0,"Objects":[{"StartTime":76777.0,"Position":185.0,"HyperDash":false}]},{"StartTime":76887.0,"Objects":[{"StartTime":76887.0,"Position":282.0,"HyperDash":false},{"StartTime":76978.0,"Position":335.60437,"HyperDash":false},{"StartTime":77105.0,"Position":419.9973,"HyperDash":false}]},{"StartTime":77215.0,"Objects":[{"StartTime":77215.0,"Position":336.0,"HyperDash":true}]},{"StartTime":77325.0,"Objects":[{"StartTime":77325.0,"Position":100.0,"HyperDash":false},{"StartTime":77416.0,"Position":88.294014,"HyperDash":false},{"StartTime":77543.0,"Position":102.248474,"HyperDash":false}]},{"StartTime":77653.0,"Objects":[{"StartTime":77653.0,"Position":192.0,"HyperDash":false}]},{"StartTime":77763.0,"Objects":[{"StartTime":77763.0,"Position":102.0,"HyperDash":false},{"StartTime":77872.0,"Position":100.2084,"HyperDash":true}]},{"StartTime":77982.0,"Objects":[{"StartTime":77982.0,"Position":301.0,"HyperDash":false},{"StartTime":78091.0,"Position":370.0,"HyperDash":true}]},{"StartTime":78201.0,"Objects":[{"StartTime":78201.0,"Position":134.0,"HyperDash":false},{"StartTime":78310.0,"Position":133.028259,"HyperDash":true}]},{"StartTime":78420.0,"Objects":[{"StartTime":78420.0,"Position":334.0,"HyperDash":true}]},{"StartTime":78529.0,"Objects":[{"StartTime":78529.0,"Position":135.0,"HyperDash":false}]},{"StartTime":78639.0,"Objects":[{"StartTime":78639.0,"Position":37.0,"HyperDash":false},{"StartTime":78730.0,"Position":18.6601868,"HyperDash":false},{"StartTime":78857.0,"Position":64.53217,"HyperDash":false}]},{"StartTime":78967.0,"Objects":[{"StartTime":78967.0,"Position":147.0,"HyperDash":true}]},{"StartTime":79077.0,"Objects":[{"StartTime":79077.0,"Position":382.0,"HyperDash":false},{"StartTime":79186.0,"Position":384.028534,"HyperDash":false}]},{"StartTime":79296.0,"Objects":[{"StartTime":79296.0,"Position":273.0,"HyperDash":false},{"StartTime":79405.0,"Position":270.971466,"HyperDash":true}]},{"StartTime":79515.0,"Objects":[{"StartTime":79515.0,"Position":472.0,"HyperDash":false},{"StartTime":79624.0,"Position":473.915924,"HyperDash":true}]},{"StartTime":79734.0,"Objects":[{"StartTime":79734.0,"Position":203.0,"HyperDash":false},{"StartTime":79843.0,"Position":134.006836,"HyperDash":false}]},{"StartTime":79953.0,"Objects":[{"StartTime":79953.0,"Position":244.0,"HyperDash":false},{"StartTime":80062.0,"Position":313.0,"HyperDash":true}]},{"StartTime":80172.0,"Objects":[{"StartTime":80172.0,"Position":111.0,"HyperDash":false},{"StartTime":80281.0,"Position":108.002831,"HyperDash":true}]},{"StartTime":80390.0,"Objects":[{"StartTime":80390.0,"Position":307.0,"HyperDash":false},{"StartTime":80499.0,"Position":376.0,"HyperDash":true}]},{"StartTime":80609.0,"Objects":[{"StartTime":80609.0,"Position":140.0,"HyperDash":false},{"StartTime":80718.0,"Position":71.0,"HyperDash":true}]},{"StartTime":80828.0,"Objects":[{"StartTime":80828.0,"Position":341.0,"HyperDash":false},{"StartTime":80919.0,"Position":398.6055,"HyperDash":false},{"StartTime":81046.0,"Position":479.0,"HyperDash":false}]},{"StartTime":81157.0,"Objects":[{"StartTime":81157.0,"Position":388.0,"HyperDash":false}]},{"StartTime":81266.0,"Objects":[{"StartTime":81266.0,"Position":476.0,"HyperDash":true}]},{"StartTime":81485.0,"Objects":[{"StartTime":81485.0,"Position":161.0,"HyperDash":false}]},{"StartTime":81595.0,"Objects":[{"StartTime":81595.0,"Position":124.0,"HyperDash":false}]},{"StartTime":81704.0,"Objects":[{"StartTime":81704.0,"Position":166.0,"HyperDash":false}]},{"StartTime":81814.0,"Objects":[{"StartTime":81814.0,"Position":242.0,"HyperDash":false}]},{"StartTime":81923.0,"Objects":[{"StartTime":81923.0,"Position":351.0,"HyperDash":false},{"StartTime":82032.0,"Position":351.9999,"HyperDash":true}]},{"StartTime":82142.0,"Objects":[{"StartTime":82142.0,"Position":150.0,"HyperDash":false}]},{"StartTime":82252.0,"Objects":[{"StartTime":82252.0,"Position":74.0,"HyperDash":false}]},{"StartTime":82361.0,"Objects":[{"StartTime":82361.0,"Position":84.0,"HyperDash":false}]},{"StartTime":82471.0,"Objects":[{"StartTime":82471.0,"Position":166.0,"HyperDash":true}]},{"StartTime":82580.0,"Objects":[{"StartTime":82580.0,"Position":399.0,"HyperDash":false}]},{"StartTime":82690.0,"Objects":[{"StartTime":82690.0,"Position":442.0,"HyperDash":false}]},{"StartTime":82799.0,"Objects":[{"StartTime":82799.0,"Position":399.0,"HyperDash":false}]},{"StartTime":82909.0,"Objects":[{"StartTime":82909.0,"Position":316.0,"HyperDash":false}]},{"StartTime":83018.0,"Objects":[{"StartTime":83018.0,"Position":206.0,"HyperDash":false},{"StartTime":83127.0,"Position":204.184845,"HyperDash":false}]},{"StartTime":83237.0,"Objects":[{"StartTime":83237.0,"Position":315.0,"HyperDash":false},{"StartTime":83346.0,"Position":315.971741,"HyperDash":true}]},{"StartTime":83456.0,"Objects":[{"StartTime":83456.0,"Position":80.0,"HyperDash":false},{"StartTime":83565.0,"Position":78.18484,"HyperDash":false}]},{"StartTime":83675.0,"Objects":[{"StartTime":83675.0,"Position":182.0,"HyperDash":false}]},{"StartTime":83894.0,"Objects":[{"StartTime":83894.0,"Position":375.0,"HyperDash":true}]},{"StartTime":84113.0,"Objects":[{"StartTime":84113.0,"Position":57.0,"HyperDash":false}]},{"StartTime":84223.0,"Objects":[{"StartTime":84223.0,"Position":133.0,"HyperDash":true}]},{"StartTime":84332.0,"Objects":[{"StartTime":84332.0,"Position":366.0,"HyperDash":false}]},{"StartTime":84442.0,"Objects":[{"StartTime":84442.0,"Position":405.0,"HyperDash":false}]},{"StartTime":84551.0,"Objects":[{"StartTime":84551.0,"Position":361.0,"HyperDash":false}]},{"StartTime":84661.0,"Objects":[{"StartTime":84661.0,"Position":284.0,"HyperDash":false}]},{"StartTime":84770.0,"Objects":[{"StartTime":84770.0,"Position":174.0,"HyperDash":false},{"StartTime":84879.0,"Position":172.184845,"HyperDash":true}]},{"StartTime":84989.0,"Objects":[{"StartTime":84989.0,"Position":442.0,"HyperDash":false}]},{"StartTime":85099.0,"Objects":[{"StartTime":85099.0,"Position":358.0,"HyperDash":false}]},{"StartTime":85208.0,"Objects":[{"StartTime":85208.0,"Position":321.0,"HyperDash":false}]},{"StartTime":85317.0,"Objects":[{"StartTime":85317.0,"Position":365.0,"HyperDash":false}]},{"StartTime":85427.0,"Objects":[{"StartTime":85427.0,"Position":475.0,"HyperDash":false},{"StartTime":85536.0,"Position":475.919922,"HyperDash":true}]},{"StartTime":85646.0,"Objects":[{"StartTime":85646.0,"Position":274.0,"HyperDash":false},{"StartTime":85755.0,"Position":273.103973,"HyperDash":false}]},{"StartTime":85865.0,"Objects":[{"StartTime":85865.0,"Position":363.0,"HyperDash":false}]},{"StartTime":85974.0,"Objects":[{"StartTime":85974.0,"Position":273.0,"HyperDash":true}]},{"StartTime":86084.0,"Objects":[{"StartTime":86084.0,"Position":71.0,"HyperDash":false},{"StartTime":86193.0,"Position":70.21596,"HyperDash":true}]},{"StartTime":86303.0,"Objects":[{"StartTime":86303.0,"Position":305.0,"HyperDash":false},{"StartTime":86412.0,"Position":305.0,"HyperDash":true}]},{"StartTime":86522.0,"Objects":[{"StartTime":86522.0,"Position":103.0,"HyperDash":true}]},{"StartTime":86631.0,"Objects":[{"StartTime":86631.0,"Position":305.0,"HyperDash":false},{"StartTime":86740.0,"Position":373.995,"HyperDash":true}]},{"StartTime":86960.0,"Objects":[{"StartTime":86960.0,"Position":55.0,"HyperDash":false},{"StartTime":87014.0,"Position":76.89231,"HyperDash":false},{"StartTime":87069.0,"Position":136.4433,"HyperDash":false},{"StartTime":87123.0,"Position":166.220535,"HyperDash":false},{"StartTime":87178.0,"Position":189.010239,"HyperDash":false},{"StartTime":87232.0,"Position":225.342209,"HyperDash":false},{"StartTime":87287.0,"Position":199.378647,"HyperDash":false},{"StartTime":87342.0,"Position":204.410217,"HyperDash":false},{"StartTime":87397.0,"Position":181.37085,"HyperDash":false},{"StartTime":87488.0,"Position":110.687065,"HyperDash":false},{"StartTime":87616.0,"Position":48.63235,"HyperDash":true}]},{"StartTime":87836.0,"Objects":[{"StartTime":87836.0,"Position":398.0,"HyperDash":false}]},{"StartTime":101412.0,"Objects":[{"StartTime":101412.0,"Position":77.0,"HyperDash":false}]},{"StartTime":101850.0,"Objects":[{"StartTime":101850.0,"Position":435.0,"HyperDash":false},{"StartTime":101941.0,"Position":437.939,"HyperDash":false},{"StartTime":102068.0,"Position":434.39502,"HyperDash":false}]},{"StartTime":102288.0,"Objects":[{"StartTime":102288.0,"Position":240.0,"HyperDash":false},{"StartTime":102379.0,"Position":174.395935,"HyperDash":false},{"StartTime":102506.0,"Position":102.003464,"HyperDash":false}]},{"StartTime":102726.0,"Objects":[{"StartTime":102726.0,"Position":296.0,"HyperDash":false},{"StartTime":102817.0,"Position":355.604065,"HyperDash":false},{"StartTime":102944.0,"Position":433.996521,"HyperDash":false}]},{"StartTime":103055.0,"Objects":[{"StartTime":103055.0,"Position":322.0,"HyperDash":false},{"StartTime":103164.0,"Position":253.0,"HyperDash":false}]},{"StartTime":103383.0,"Objects":[{"StartTime":103383.0,"Position":433.0,"HyperDash":false}]},{"StartTime":103602.0,"Objects":[{"StartTime":103602.0,"Position":145.0,"HyperDash":false}]},{"StartTime":103712.0,"Objects":[{"StartTime":103712.0,"Position":228.0,"HyperDash":false}]},{"StartTime":103821.0,"Objects":[{"StartTime":103821.0,"Position":283.0,"HyperDash":false}]},{"StartTime":104040.0,"Objects":[{"StartTime":104040.0,"Position":89.0,"HyperDash":false},{"StartTime":104131.0,"Position":77.58258,"HyperDash":false},{"StartTime":104258.0,"Position":88.00002,"HyperDash":false}]},{"StartTime":104478.0,"Objects":[{"StartTime":104478.0,"Position":268.0,"HyperDash":false}]},{"StartTime":104697.0,"Objects":[{"StartTime":104697.0,"Position":88.0,"HyperDash":false}]},{"StartTime":104916.0,"Objects":[{"StartTime":104916.0,"Position":281.0,"HyperDash":false},{"StartTime":105007.0,"Position":355.604126,"HyperDash":false},{"StartTime":105134.0,"Position":418.9967,"HyperDash":false}]},{"StartTime":105354.0,"Objects":[{"StartTime":105354.0,"Position":129.0,"HyperDash":false}]},{"StartTime":105463.0,"Objects":[{"StartTime":105463.0,"Position":211.0,"HyperDash":false}]},{"StartTime":105573.0,"Objects":[{"StartTime":105573.0,"Position":266.0,"HyperDash":false}]},{"StartTime":105792.0,"Objects":[{"StartTime":105792.0,"Position":72.0,"HyperDash":false},{"StartTime":105883.0,"Position":80.618515,"HyperDash":false},{"StartTime":106010.0,"Position":71.08611,"HyperDash":false}]},{"StartTime":106230.0,"Objects":[{"StartTime":106230.0,"Position":265.0,"HyperDash":false},{"StartTime":106321.0,"Position":197.395813,"HyperDash":false},{"StartTime":106448.0,"Position":127.003143,"HyperDash":false}]},{"StartTime":106558.0,"Objects":[{"StartTime":106558.0,"Position":237.0,"HyperDash":false},{"StartTime":106667.0,"Position":306.0,"HyperDash":false}]},{"StartTime":106887.0,"Objects":[{"StartTime":106887.0,"Position":126.0,"HyperDash":false}]},{"StartTime":107106.0,"Objects":[{"StartTime":107106.0,"Position":415.0,"HyperDash":false}]},{"StartTime":107215.0,"Objects":[{"StartTime":107215.0,"Position":332.0,"HyperDash":false}]},{"StartTime":107325.0,"Objects":[{"StartTime":107325.0,"Position":276.0,"HyperDash":false}]},{"StartTime":107544.0,"Objects":[{"StartTime":107544.0,"Position":469.0,"HyperDash":false},{"StartTime":107635.0,"Position":484.411469,"HyperDash":false},{"StartTime":107762.0,"Position":469.9857,"HyperDash":false}]},{"StartTime":107982.0,"Objects":[{"StartTime":107982.0,"Position":289.0,"HyperDash":false}]},{"StartTime":108201.0,"Objects":[{"StartTime":108201.0,"Position":469.0,"HyperDash":false}]},{"StartTime":108420.0,"Objects":[{"StartTime":108420.0,"Position":275.0,"HyperDash":false},{"StartTime":108511.0,"Position":208.3945,"HyperDash":false},{"StartTime":108638.0,"Position":137.0,"HyperDash":false}]},{"StartTime":108858.0,"Objects":[{"StartTime":108858.0,"Position":428.0,"HyperDash":false}]},{"StartTime":108967.0,"Objects":[{"StartTime":108967.0,"Position":345.0,"HyperDash":false}]},{"StartTime":109077.0,"Objects":[{"StartTime":109077.0,"Position":289.0,"HyperDash":false}]},{"StartTime":109296.0,"Objects":[{"StartTime":109296.0,"Position":482.0,"HyperDash":false},{"StartTime":109387.0,"Position":471.822845,"HyperDash":false},{"StartTime":109514.0,"Position":483.971222,"HyperDash":false}]},{"StartTime":109734.0,"Objects":[{"StartTime":109734.0,"Position":291.0,"HyperDash":false},{"StartTime":109825.0,"Position":335.604,"HyperDash":false},{"StartTime":109952.0,"Position":428.9964,"HyperDash":false}]},{"StartTime":110062.0,"Objects":[{"StartTime":110062.0,"Position":318.0,"HyperDash":false},{"StartTime":110171.0,"Position":249.005829,"HyperDash":false}]},{"StartTime":110390.0,"Objects":[{"StartTime":110390.0,"Position":428.0,"HyperDash":false}]},{"StartTime":110609.0,"Objects":[{"StartTime":110609.0,"Position":138.0,"HyperDash":false}]},{"StartTime":110719.0,"Objects":[{"StartTime":110719.0,"Position":215.0,"HyperDash":false}]},{"StartTime":110828.0,"Objects":[{"StartTime":110828.0,"Position":277.0,"HyperDash":false}]},{"StartTime":111047.0,"Objects":[{"StartTime":111047.0,"Position":83.0,"HyperDash":false},{"StartTime":111138.0,"Position":130.6055,"HyperDash":false},{"StartTime":111265.0,"Position":221.0,"HyperDash":false}]},{"StartTime":111485.0,"Objects":[{"StartTime":111485.0,"Position":26.0,"HyperDash":false},{"StartTime":111576.0,"Position":27.5795326,"HyperDash":false},{"StartTime":111703.0,"Position":24.9927273,"HyperDash":false}]},{"StartTime":111923.0,"Objects":[{"StartTime":111923.0,"Position":205.0,"HyperDash":false}]},{"StartTime":112142.0,"Objects":[{"StartTime":112142.0,"Position":25.0,"HyperDash":false}]},{"StartTime":112361.0,"Objects":[{"StartTime":112361.0,"Position":314.0,"HyperDash":false}]},{"StartTime":112471.0,"Objects":[{"StartTime":112471.0,"Position":230.0,"HyperDash":false}]},{"StartTime":112580.0,"Objects":[{"StartTime":112580.0,"Position":314.0,"HyperDash":false},{"StartTime":112634.0,"Position":339.679535,"HyperDash":false},{"StartTime":112689.0,"Position":397.080627,"HyperDash":false},{"StartTime":112743.0,"Position":405.857666,"HyperDash":false},{"StartTime":112798.0,"Position":405.816559,"HyperDash":false},{"StartTime":112889.0,"Position":384.664124,"HyperDash":false},{"StartTime":113017.0,"Position":303.560425,"HyperDash":false}]},{"StartTime":113237.0,"Objects":[{"StartTime":113237.0,"Position":109.0,"HyperDash":false},{"StartTime":113291.0,"Position":59.27102,"HyperDash":false},{"StartTime":113346.0,"Position":27.2394676,"HyperDash":false},{"StartTime":113400.0,"Position":36.17946,"HyperDash":false},{"StartTime":113455.0,"Position":19.1602039,"HyperDash":false},{"StartTime":113546.0,"Position":28.68512,"HyperDash":false},{"StartTime":113674.0,"Position":122.962029,"HyperDash":false}]},{"StartTime":114113.0,"Objects":[{"StartTime":114113.0,"Position":482.0,"HyperDash":false}]},{"StartTime":114332.0,"Objects":[{"StartTime":114332.0,"Position":288.0,"HyperDash":false}]},{"StartTime":114551.0,"Objects":[{"StartTime":114551.0,"Position":482.0,"HyperDash":false},{"StartTime":114642.0,"Position":428.3945,"HyperDash":false},{"StartTime":114769.0,"Position":344.0,"HyperDash":false}]},{"StartTime":114989.0,"Objects":[{"StartTime":114989.0,"Position":149.0,"HyperDash":false},{"StartTime":115080.0,"Position":207.6055,"HyperDash":false},{"StartTime":115207.0,"Position":287.0,"HyperDash":false}]},{"StartTime":115317.0,"Objects":[{"StartTime":115317.0,"Position":397.0,"HyperDash":false},{"StartTime":115426.0,"Position":328.004547,"HyperDash":false}]},{"StartTime":115646.0,"Objects":[{"StartTime":115646.0,"Position":133.0,"HyperDash":false},{"StartTime":115755.0,"Position":132.092178,"HyperDash":true}]},{"StartTime":115865.0,"Objects":[{"StartTime":115865.0,"Position":367.0,"HyperDash":false}]},{"StartTime":115974.0,"Objects":[{"StartTime":115974.0,"Position":284.0,"HyperDash":false}]},{"StartTime":116084.0,"Objects":[{"StartTime":116084.0,"Position":228.0,"HyperDash":false}]},{"StartTime":116303.0,"Objects":[{"StartTime":116303.0,"Position":421.0,"HyperDash":false},{"StartTime":116394.0,"Position":429.822845,"HyperDash":false},{"StartTime":116521.0,"Position":422.971222,"HyperDash":false}]},{"StartTime":116631.0,"Objects":[{"StartTime":116631.0,"Position":346.0,"HyperDash":false}]},{"StartTime":116741.0,"Objects":[{"StartTime":116741.0,"Position":235.0,"HyperDash":false},{"StartTime":116832.0,"Position":277.6042,"HyperDash":false},{"StartTime":116959.0,"Position":372.996857,"HyperDash":false}]},{"StartTime":117069.0,"Objects":[{"StartTime":117069.0,"Position":296.0,"HyperDash":true}]},{"StartTime":117179.0,"Objects":[{"StartTime":117179.0,"Position":94.0,"HyperDash":false}]},{"StartTime":117398.0,"Objects":[{"StartTime":117398.0,"Position":273.0,"HyperDash":false},{"StartTime":117507.0,"Position":341.99353,"HyperDash":true}]},{"StartTime":117617.0,"Objects":[{"StartTime":117617.0,"Position":129.0,"HyperDash":false}]},{"StartTime":117726.0,"Objects":[{"StartTime":117726.0,"Position":60.0,"HyperDash":false}]},{"StartTime":117836.0,"Objects":[{"StartTime":117836.0,"Position":131.0,"HyperDash":false}]},{"StartTime":118055.0,"Objects":[{"StartTime":118055.0,"Position":324.0,"HyperDash":false},{"StartTime":118146.0,"Position":262.3945,"HyperDash":false},{"StartTime":118273.0,"Position":186.0,"HyperDash":false}]},{"StartTime":118383.0,"Objects":[{"StartTime":118383.0,"Position":262.0,"HyperDash":false}]},{"StartTime":118493.0,"Objects":[{"StartTime":118493.0,"Position":372.0,"HyperDash":false},{"StartTime":118584.0,"Position":427.036163,"HyperDash":false},{"StartTime":118711.0,"Position":476.603577,"HyperDash":false}]},{"StartTime":118821.0,"Objects":[{"StartTime":118821.0,"Position":400.0,"HyperDash":true}]},{"StartTime":118931.0,"Objects":[{"StartTime":118931.0,"Position":198.0,"HyperDash":false}]},{"StartTime":119150.0,"Objects":[{"StartTime":119150.0,"Position":391.0,"HyperDash":false},{"StartTime":119259.0,"Position":391.8414,"HyperDash":true}]},{"StartTime":119369.0,"Objects":[{"StartTime":119369.0,"Position":156.0,"HyperDash":false}]},{"StartTime":119478.0,"Objects":[{"StartTime":119478.0,"Position":238.0,"HyperDash":false}]},{"StartTime":119588.0,"Objects":[{"StartTime":119588.0,"Position":293.0,"HyperDash":false}]},{"StartTime":119807.0,"Objects":[{"StartTime":119807.0,"Position":99.0,"HyperDash":false},{"StartTime":119898.0,"Position":105.171227,"HyperDash":false},{"StartTime":120025.0,"Position":97.014595,"HyperDash":false}]},{"StartTime":120135.0,"Objects":[{"StartTime":120135.0,"Position":174.0,"HyperDash":false}]},{"StartTime":120244.0,"Objects":[{"StartTime":120244.0,"Position":283.0,"HyperDash":false}]},{"StartTime":120354.0,"Objects":[{"StartTime":120354.0,"Position":333.0,"HyperDash":false}]},{"StartTime":120463.0,"Objects":[{"StartTime":120463.0,"Position":283.0,"HyperDash":false}]},{"StartTime":120573.0,"Objects":[{"StartTime":120573.0,"Position":185.0,"HyperDash":true}]},{"StartTime":120682.0,"Objects":[{"StartTime":120682.0,"Position":384.0,"HyperDash":false},{"StartTime":120773.0,"Position":427.280121,"HyperDash":false},{"StartTime":120900.0,"Position":482.186859,"HyperDash":false}]},{"StartTime":121011.0,"Objects":[{"StartTime":121011.0,"Position":412.0,"HyperDash":true}]},{"StartTime":121120.0,"Objects":[{"StartTime":121120.0,"Position":178.0,"HyperDash":false}]},{"StartTime":121230.0,"Objects":[{"StartTime":121230.0,"Position":108.0,"HyperDash":false}]},{"StartTime":121339.0,"Objects":[{"StartTime":121339.0,"Position":178.0,"HyperDash":false}]},{"StartTime":121558.0,"Objects":[{"StartTime":121558.0,"Position":371.0,"HyperDash":false},{"StartTime":121649.0,"Position":320.3945,"HyperDash":false},{"StartTime":121776.0,"Position":233.0,"HyperDash":false}]},{"StartTime":121887.0,"Objects":[{"StartTime":121887.0,"Position":309.0,"HyperDash":false}]},{"StartTime":121996.0,"Objects":[{"StartTime":121996.0,"Position":418.0,"HyperDash":false},{"StartTime":122087.0,"Position":443.873138,"HyperDash":false},{"StartTime":122214.0,"Position":414.947174,"HyperDash":false}]},{"StartTime":122325.0,"Objects":[{"StartTime":122325.0,"Position":337.0,"HyperDash":true}]},{"StartTime":122434.0,"Objects":[{"StartTime":122434.0,"Position":137.0,"HyperDash":false},{"StartTime":122525.0,"Position":79.57886,"HyperDash":false},{"StartTime":122652.0,"Position":25.39234,"HyperDash":false}]},{"StartTime":122763.0,"Objects":[{"StartTime":122763.0,"Position":102.0,"HyperDash":true}]},{"StartTime":122872.0,"Objects":[{"StartTime":122872.0,"Position":335.0,"HyperDash":false}]},{"StartTime":122982.0,"Objects":[{"StartTime":122982.0,"Position":251.0,"HyperDash":false}]},{"StartTime":123091.0,"Objects":[{"StartTime":123091.0,"Position":196.0,"HyperDash":false}]},{"StartTime":123310.0,"Objects":[{"StartTime":123310.0,"Position":389.0,"HyperDash":false},{"StartTime":123401.0,"Position":399.5055,"HyperDash":false},{"StartTime":123528.0,"Position":387.780823,"HyperDash":false}]},{"StartTime":123639.0,"Objects":[{"StartTime":123639.0,"Position":312.0,"HyperDash":false}]},{"StartTime":123748.0,"Objects":[{"StartTime":123748.0,"Position":202.0,"HyperDash":false},{"StartTime":123839.0,"Position":146.4552,"HyperDash":false},{"StartTime":123966.0,"Position":122.737045,"HyperDash":false}]},{"StartTime":124077.0,"Objects":[{"StartTime":124077.0,"Position":200.0,"HyperDash":true}]},{"StartTime":124186.0,"Objects":[{"StartTime":124186.0,"Position":399.0,"HyperDash":false}]},{"StartTime":124405.0,"Objects":[{"StartTime":124405.0,"Position":219.0,"HyperDash":false},{"StartTime":124514.0,"Position":150.0,"HyperDash":true}]},{"StartTime":124624.0,"Objects":[{"StartTime":124624.0,"Position":386.0,"HyperDash":false}]},{"StartTime":124734.0,"Objects":[{"StartTime":124734.0,"Position":455.0,"HyperDash":false}]},{"StartTime":124843.0,"Objects":[{"StartTime":124843.0,"Position":386.0,"HyperDash":false}]},{"StartTime":125062.0,"Objects":[{"StartTime":125062.0,"Position":192.0,"HyperDash":false},{"StartTime":125153.0,"Position":149.893311,"HyperDash":false},{"StartTime":125280.0,"Position":68.0014954,"HyperDash":false}]},{"StartTime":125390.0,"Objects":[{"StartTime":125390.0,"Position":144.0,"HyperDash":true}]},{"StartTime":125500.0,"Objects":[{"StartTime":125500.0,"Position":345.0,"HyperDash":false},{"StartTime":125591.0,"Position":419.1067,"HyperDash":false},{"StartTime":125718.0,"Position":468.9985,"HyperDash":false}]},{"StartTime":125828.0,"Objects":[{"StartTime":125828.0,"Position":393.0,"HyperDash":false}]},{"StartTime":125938.0,"Objects":[{"StartTime":125938.0,"Position":282.0,"HyperDash":false}]},{"StartTime":126157.0,"Objects":[{"StartTime":126157.0,"Position":475.0,"HyperDash":false},{"StartTime":126266.0,"Position":475.9078,"HyperDash":true}]},{"StartTime":126376.0,"Objects":[{"StartTime":126376.0,"Position":240.0,"HyperDash":false}]},{"StartTime":126485.0,"Objects":[{"StartTime":126485.0,"Position":322.0,"HyperDash":false}]},{"StartTime":126595.0,"Objects":[{"StartTime":126595.0,"Position":377.0,"HyperDash":false}]},{"StartTime":126814.0,"Objects":[{"StartTime":126814.0,"Position":183.0,"HyperDash":false}]},{"StartTime":127033.0,"Objects":[{"StartTime":127033.0,"Position":472.0,"HyperDash":false}]},{"StartTime":127142.0,"Objects":[{"StartTime":127142.0,"Position":389.0,"HyperDash":false}]},{"StartTime":127252.0,"Objects":[{"StartTime":127252.0,"Position":333.0,"HyperDash":false}]},{"StartTime":127471.0,"Objects":[{"StartTime":127471.0,"Position":153.0,"HyperDash":false},{"StartTime":127580.0,"Position":152.067657,"HyperDash":false}]},{"StartTime":127690.0,"Objects":[{"StartTime":127690.0,"Position":256.0,"HyperDash":false}]},{"StartTime":127909.0,"Objects":[{"StartTime":127909.0,"Position":76.0,"HyperDash":true}]},{"StartTime":128128.0,"Objects":[{"StartTime":128128.0,"Position":421.0,"HyperDash":false}]},{"StartTime":128237.0,"Objects":[{"StartTime":128237.0,"Position":423.0,"HyperDash":false}]},{"StartTime":128347.0,"Objects":[{"StartTime":128347.0,"Position":319.0,"HyperDash":false}]},{"StartTime":128566.0,"Objects":[{"StartTime":128566.0,"Position":139.0,"HyperDash":false}]},{"StartTime":128785.0,"Objects":[{"StartTime":128785.0,"Position":332.0,"HyperDash":false}]},{"StartTime":129004.0,"Objects":[{"StartTime":129004.0,"Position":42.0,"HyperDash":false}]},{"StartTime":129113.0,"Objects":[{"StartTime":129113.0,"Position":111.0,"HyperDash":false}]},{"StartTime":129332.0,"Objects":[{"StartTime":129332.0,"Position":304.0,"HyperDash":false},{"StartTime":129386.0,"Position":253.920715,"HyperDash":false},{"StartTime":129441.0,"Position":217.210358,"HyperDash":false},{"StartTime":129495.0,"Position":213.1311,"HyperDash":false},{"StartTime":129550.0,"Position":166.420731,"HyperDash":false},{"StartTime":129660.0,"Position":97.0,"HyperDash":true}]},{"StartTime":129880.0,"Objects":[{"StartTime":129880.0,"Position":408.0,"HyperDash":false},{"StartTime":129934.0,"Position":421.643433,"HyperDash":false},{"StartTime":129989.0,"Position":469.5894,"HyperDash":false},{"StartTime":130043.0,"Position":472.515442,"HyperDash":false},{"StartTime":130098.0,"Position":489.2183,"HyperDash":false},{"StartTime":130189.0,"Position":462.087952,"HyperDash":false},{"StartTime":130317.0,"Position":381.479523,"HyperDash":false}]},{"StartTime":130536.0,"Objects":[{"StartTime":130536.0,"Position":188.0,"HyperDash":false},{"StartTime":130590.0,"Position":224.105255,"HyperDash":false},{"StartTime":130645.0,"Position":273.8421,"HyperDash":false},{"StartTime":130699.0,"Position":301.947357,"HyperDash":false},{"StartTime":130754.0,"Position":325.6842,"HyperDash":false},{"StartTime":130845.0,"Position":391.1579,"HyperDash":false},{"StartTime":130973.0,"Position":464.0,"HyperDash":false}]},{"StartTime":131193.0,"Objects":[{"StartTime":131193.0,"Position":283.0,"HyperDash":false}]},{"StartTime":131412.0,"Objects":[{"StartTime":131412.0,"Position":463.0,"HyperDash":true}]},{"StartTime":131631.0,"Objects":[{"StartTime":131631.0,"Position":145.0,"HyperDash":false},{"StartTime":131685.0,"Position":104.253967,"HyperDash":false},{"StartTime":131740.0,"Position":82.46871,"HyperDash":false},{"StartTime":131794.0,"Position":46.8594933,"HyperDash":false},{"StartTime":131849.0,"Position":58.7363319,"HyperDash":false},{"StartTime":131940.0,"Position":65.76372,"HyperDash":false},{"StartTime":132068.0,"Position":161.933884,"HyperDash":false}]},{"StartTime":132288.0,"Objects":[{"StartTime":132288.0,"Position":342.0,"HyperDash":false}]},{"StartTime":132507.0,"Objects":[{"StartTime":132507.0,"Position":148.0,"HyperDash":false},{"StartTime":132598.0,"Position":150.628357,"HyperDash":false},{"StartTime":132725.0,"Position":147.1097,"HyperDash":false}]},{"StartTime":132945.0,"Objects":[{"StartTime":132945.0,"Position":327.0,"HyperDash":false}]},{"StartTime":133164.0,"Objects":[{"StartTime":133164.0,"Position":147.0,"HyperDash":true}]},{"StartTime":133383.0,"Objects":[{"StartTime":133383.0,"Position":464.0,"HyperDash":false},{"StartTime":133437.0,"Position":470.84375,"HyperDash":false},{"StartTime":133492.0,"Position":474.752625,"HyperDash":false},{"StartTime":133546.0,"Position":428.8388,"HyperDash":false},{"StartTime":133601.0,"Position":419.0386,"HyperDash":false},{"StartTime":133711.0,"Position":351.443878,"HyperDash":false}]},{"StartTime":133821.0,"Objects":[{"StartTime":133821.0,"Position":240.0,"HyperDash":false},{"StartTime":133875.0,"Position":265.918579,"HyperDash":false},{"StartTime":133930.0,"Position":308.4777,"HyperDash":false},{"StartTime":133984.0,"Position":356.101166,"HyperDash":false},{"StartTime":134039.0,"Position":367.7393,"HyperDash":false},{"StartTime":134130.0,"Position":392.480377,"HyperDash":false},{"StartTime":134258.0,"Position":390.924835,"HyperDash":false}]},{"StartTime":134478.0,"Objects":[{"StartTime":134478.0,"Position":196.0,"HyperDash":false},{"StartTime":134569.0,"Position":183.414413,"HyperDash":false},{"StartTime":134696.0,"Position":196.992783,"HyperDash":false}]},{"StartTime":134916.0,"Objects":[{"StartTime":134916.0,"Position":391.0,"HyperDash":true}]},{"StartTime":135135.0,"Objects":[{"StartTime":135135.0,"Position":73.0,"HyperDash":false},{"StartTime":135189.0,"Position":110.225349,"HyperDash":false},{"StartTime":135244.0,"Position":131.973389,"HyperDash":false},{"StartTime":135298.0,"Position":154.19873,"HyperDash":false},{"StartTime":135353.0,"Position":186.946777,"HyperDash":false},{"StartTime":135444.0,"Position":152.597885,"HyperDash":false},{"StartTime":135572.0,"Position":74.8510361,"HyperDash":false}]},{"StartTime":136011.0,"Objects":[{"StartTime":136011.0,"Position":434.0,"HyperDash":false},{"StartTime":136102.0,"Position":424.411469,"HyperDash":false},{"StartTime":136229.0,"Position":434.9857,"HyperDash":false}]},{"StartTime":136449.0,"Objects":[{"StartTime":136449.0,"Position":227.0,"HyperDash":false}]},{"StartTime":136668.0,"Objects":[{"StartTime":136668.0,"Position":434.0,"HyperDash":true}]},{"StartTime":136887.0,"Objects":[{"StartTime":136887.0,"Position":116.0,"HyperDash":false},{"StartTime":136941.0,"Position":169.105255,"HyperDash":false},{"StartTime":136996.0,"Position":171.8421,"HyperDash":false},{"StartTime":137050.0,"Position":216.947357,"HyperDash":false},{"StartTime":137105.0,"Position":253.6842,"HyperDash":false},{"StartTime":137196.0,"Position":316.1579,"HyperDash":false},{"StartTime":137324.0,"Position":392.0,"HyperDash":true}]},{"StartTime":137544.0,"Objects":[{"StartTime":137544.0,"Position":100.0,"HyperDash":false}]},{"StartTime":137653.0,"Objects":[{"StartTime":137653.0,"Position":182.0,"HyperDash":false}]},{"StartTime":137763.0,"Objects":[{"StartTime":137763.0,"Position":242.0,"HyperDash":false}]},{"StartTime":137982.0,"Objects":[{"StartTime":137982.0,"Position":62.0,"HyperDash":false}]},{"StartTime":138201.0,"Objects":[{"StartTime":138201.0,"Position":241.0,"HyperDash":false},{"StartTime":138292.0,"Position":173.399414,"HyperDash":false},{"StartTime":138419.0,"Position":103.011795,"HyperDash":true}]},{"StartTime":138639.0,"Objects":[{"StartTime":138639.0,"Position":421.0,"HyperDash":false},{"StartTime":138693.0,"Position":392.894928,"HyperDash":false},{"StartTime":138748.0,"Position":354.1583,"HyperDash":false},{"StartTime":138802.0,"Position":299.053223,"HyperDash":false},{"StartTime":138857.0,"Position":283.3166,"HyperDash":false},{"StartTime":138948.0,"Position":230.843246,"HyperDash":false},{"StartTime":139076.0,"Position":145.001617,"HyperDash":false}]},{"StartTime":139296.0,"Objects":[{"StartTime":139296.0,"Position":339.0,"HyperDash":false},{"StartTime":139405.0,"Position":339.884552,"HyperDash":false}]},{"StartTime":139515.0,"Objects":[{"StartTime":139515.0,"Position":235.0,"HyperDash":false}]},{"StartTime":139734.0,"Objects":[{"StartTime":139734.0,"Position":55.0,"HyperDash":false}]},{"StartTime":139953.0,"Objects":[{"StartTime":139953.0,"Position":344.0,"HyperDash":false},{"StartTime":140044.0,"Position":417.604126,"HyperDash":false},{"StartTime":140171.0,"Position":481.9967,"HyperDash":true}]},{"StartTime":140390.0,"Objects":[{"StartTime":140390.0,"Position":136.0,"HyperDash":false},{"StartTime":140481.0,"Position":128.599976,"HyperDash":false},{"StartTime":140608.0,"Position":135.041687,"HyperDash":false}]},{"StartTime":140828.0,"Objects":[{"StartTime":140828.0,"Position":328.0,"HyperDash":false}]},{"StartTime":141047.0,"Objects":[{"StartTime":141047.0,"Position":135.0,"HyperDash":false}]},{"StartTime":141266.0,"Objects":[{"StartTime":141266.0,"Position":342.0,"HyperDash":false}]},{"StartTime":141485.0,"Objects":[{"StartTime":141485.0,"Position":493.0,"HyperDash":false}]},{"StartTime":141704.0,"Objects":[{"StartTime":141704.0,"Position":299.0,"HyperDash":false}]},{"StartTime":141923.0,"Objects":[{"StartTime":141923.0,"Position":91.0,"HyperDash":false}]},{"StartTime":142142.0,"Objects":[{"StartTime":142142.0,"Position":380.0,"HyperDash":false},{"StartTime":142196.0,"Position":335.923767,"HyperDash":false},{"StartTime":142251.0,"Position":318.2165,"HyperDash":false},{"StartTime":142305.0,"Position":259.140259,"HyperDash":false},{"StartTime":142360.0,"Position":242.432953,"HyperDash":false},{"StartTime":142415.0,"Position":215.7257,"HyperDash":false},{"StartTime":142470.0,"Position":173.0184,"HyperDash":false},{"StartTime":142524.0,"Position":215.09462,"HyperDash":false},{"StartTime":142579.0,"Position":241.801926,"HyperDash":false},{"StartTime":142670.0,"Position":299.226685,"HyperDash":false},{"StartTime":142798.0,"Position":380.0,"HyperDash":false}]},{"StartTime":143018.0,"Objects":[{"StartTime":143018.0,"Position":185.0,"HyperDash":false},{"StartTime":143072.0,"Position":198.796173,"HyperDash":false},{"StartTime":143127.0,"Position":265.955566,"HyperDash":false},{"StartTime":143181.0,"Position":287.965,"HyperDash":false},{"StartTime":143236.0,"Position":318.749146,"HyperDash":false},{"StartTime":143290.0,"Position":347.800385,"HyperDash":false},{"StartTime":143345.0,"Position":395.071777,"HyperDash":false},{"StartTime":143400.0,"Position":413.8512,"HyperDash":false},{"StartTime":143455.0,"Position":420.164917,"HyperDash":false},{"StartTime":143546.0,"Position":449.768,"HyperDash":false},{"StartTime":143674.0,"Position":428.7935,"HyperDash":true}]},{"StartTime":143894.0,"Objects":[{"StartTime":143894.0,"Position":82.0,"HyperDash":false},{"StartTime":143985.0,"Position":35.4371643,"HyperDash":false},{"StartTime":144112.0,"Position":83.57783,"HyperDash":false}]},{"StartTime":144223.0,"Objects":[{"StartTime":144223.0,"Position":174.0,"HyperDash":false}]},{"StartTime":144332.0,"Objects":[{"StartTime":144332.0,"Position":84.0,"HyperDash":false},{"StartTime":144441.0,"Position":83.06765,"HyperDash":true}]},{"StartTime":144551.0,"Objects":[{"StartTime":144551.0,"Position":284.0,"HyperDash":false},{"StartTime":144660.0,"Position":353.0,"HyperDash":true}]},{"StartTime":144770.0,"Objects":[{"StartTime":144770.0,"Position":117.0,"HyperDash":false},{"StartTime":144879.0,"Position":48.0,"HyperDash":true}]},{"StartTime":144989.0,"Objects":[{"StartTime":144989.0,"Position":249.0,"HyperDash":true}]},{"StartTime":145099.0,"Objects":[{"StartTime":145099.0,"Position":48.0,"HyperDash":false}]},{"StartTime":145208.0,"Objects":[{"StartTime":145208.0,"Position":144.0,"HyperDash":false},{"StartTime":145299.0,"Position":184.508865,"HyperDash":false},{"StartTime":145426.0,"Position":138.552429,"HyperDash":false}]},{"StartTime":145536.0,"Objects":[{"StartTime":145536.0,"Position":55.0,"HyperDash":true}]},{"StartTime":145646.0,"Objects":[{"StartTime":145646.0,"Position":290.0,"HyperDash":false},{"StartTime":145755.0,"Position":358.994629,"HyperDash":true}]},{"StartTime":145865.0,"Objects":[{"StartTime":145865.0,"Position":157.0,"HyperDash":true}]},{"StartTime":145974.0,"Objects":[{"StartTime":145974.0,"Position":356.0,"HyperDash":false}]},{"StartTime":146084.0,"Objects":[{"StartTime":146084.0,"Position":453.0,"HyperDash":false},{"StartTime":146175.0,"Position":406.3945,"HyperDash":false},{"StartTime":146302.0,"Position":315.0,"HyperDash":false}]},{"StartTime":146412.0,"Objects":[{"StartTime":146412.0,"Position":412.0,"HyperDash":true}]},{"StartTime":146522.0,"Objects":[{"StartTime":146522.0,"Position":176.0,"HyperDash":false}]},{"StartTime":146631.0,"Objects":[{"StartTime":146631.0,"Position":272.0,"HyperDash":false},{"StartTime":146740.0,"Position":272.9078,"HyperDash":true}]},{"StartTime":146850.0,"Objects":[{"StartTime":146850.0,"Position":71.0,"HyperDash":false}]},{"StartTime":146960.0,"Objects":[{"StartTime":146960.0,"Position":168.0,"HyperDash":false},{"StartTime":147051.0,"Position":93.39449,"HyperDash":false},{"StartTime":147178.0,"Position":30.0000153,"HyperDash":false}]},{"StartTime":147288.0,"Objects":[{"StartTime":147288.0,"Position":113.0,"HyperDash":true}]},{"StartTime":147398.0,"Objects":[{"StartTime":147398.0,"Position":348.0,"HyperDash":false},{"StartTime":147489.0,"Position":401.9966,"HyperDash":false},{"StartTime":147616.0,"Position":345.685974,"HyperDash":false}]},{"StartTime":147726.0,"Objects":[{"StartTime":147726.0,"Position":255.0,"HyperDash":false}]},{"StartTime":147836.0,"Objects":[{"StartTime":147836.0,"Position":345.0,"HyperDash":false},{"StartTime":147945.0,"Position":347.028534,"HyperDash":true}]},{"StartTime":148055.0,"Objects":[{"StartTime":148055.0,"Position":145.0,"HyperDash":false}]},{"StartTime":148164.0,"Objects":[{"StartTime":148164.0,"Position":76.0,"HyperDash":true}]},{"StartTime":148274.0,"Objects":[{"StartTime":148274.0,"Position":280.0,"HyperDash":false},{"StartTime":148383.0,"Position":349.0,"HyperDash":true}]},{"StartTime":148493.0,"Objects":[{"StartTime":148493.0,"Position":147.0,"HyperDash":true}]},{"StartTime":148602.0,"Objects":[{"StartTime":148602.0,"Position":346.0,"HyperDash":false}]},{"StartTime":148712.0,"Objects":[{"StartTime":148712.0,"Position":248.0,"HyperDash":false},{"StartTime":148803.0,"Position":196.3945,"HyperDash":false},{"StartTime":148930.0,"Position":110.0,"HyperDash":false}]},{"StartTime":149040.0,"Objects":[{"StartTime":149040.0,"Position":193.0,"HyperDash":true}]},{"StartTime":149150.0,"Objects":[{"StartTime":149150.0,"Position":428.0,"HyperDash":false},{"StartTime":149241.0,"Position":448.54718,"HyperDash":false},{"StartTime":149368.0,"Position":427.29248,"HyperDash":true}]},{"StartTime":149478.0,"Objects":[{"StartTime":149478.0,"Position":226.0,"HyperDash":false}]},{"StartTime":149588.0,"Objects":[{"StartTime":149588.0,"Position":323.0,"HyperDash":false},{"StartTime":149679.0,"Position":392.6055,"HyperDash":false},{"StartTime":149806.0,"Position":461.0,"HyperDash":false}]},{"StartTime":149916.0,"Objects":[{"StartTime":149916.0,"Position":377.0,"HyperDash":true}]},{"StartTime":150026.0,"Objects":[{"StartTime":150026.0,"Position":141.0,"HyperDash":false}]},{"StartTime":150135.0,"Objects":[{"StartTime":150135.0,"Position":237.0,"HyperDash":false},{"StartTime":150244.0,"Position":238.915924,"HyperDash":true}]},{"StartTime":150354.0,"Objects":[{"StartTime":150354.0,"Position":37.0,"HyperDash":false}]},{"StartTime":150463.0,"Objects":[{"StartTime":150463.0,"Position":133.0,"HyperDash":false},{"StartTime":150554.0,"Position":160.2725,"HyperDash":false},{"StartTime":150681.0,"Position":126.154518,"HyperDash":false}]},{"StartTime":150792.0,"Objects":[{"StartTime":150792.0,"Position":42.0,"HyperDash":true}]},{"StartTime":150901.0,"Objects":[{"StartTime":150901.0,"Position":309.0,"HyperDash":false},{"StartTime":150992.0,"Position":376.6055,"HyperDash":false},{"StartTime":151119.0,"Position":447.0,"HyperDash":false}]},{"StartTime":151230.0,"Objects":[{"StartTime":151230.0,"Position":356.0,"HyperDash":false}]},{"StartTime":151339.0,"Objects":[{"StartTime":151339.0,"Position":445.0,"HyperDash":true}]},{"StartTime":151558.0,"Objects":[{"StartTime":151558.0,"Position":127.0,"HyperDash":false}]},{"StartTime":151668.0,"Objects":[{"StartTime":151668.0,"Position":203.0,"HyperDash":false}]},{"StartTime":151777.0,"Objects":[{"StartTime":151777.0,"Position":239.0,"HyperDash":false}]},{"StartTime":151887.0,"Objects":[{"StartTime":151887.0,"Position":196.0,"HyperDash":false}]},{"StartTime":151996.0,"Objects":[{"StartTime":151996.0,"Position":86.0,"HyperDash":false},{"StartTime":152105.0,"Position":84.23135,"HyperDash":true}]},{"StartTime":152215.0,"Objects":[{"StartTime":152215.0,"Position":285.0,"HyperDash":false},{"StartTime":152306.0,"Position":224.395935,"HyperDash":false},{"StartTime":152433.0,"Position":147.003464,"HyperDash":false}]},{"StartTime":152544.0,"Objects":[{"StartTime":152544.0,"Position":230.0,"HyperDash":true}]},{"StartTime":152653.0,"Objects":[{"StartTime":152653.0,"Position":463.0,"HyperDash":false},{"StartTime":152762.0,"Position":394.006836,"HyperDash":false}]},{"StartTime":152872.0,"Objects":[{"StartTime":152872.0,"Position":284.0,"HyperDash":false},{"StartTime":152981.0,"Position":282.231354,"HyperDash":true}]},{"StartTime":153091.0,"Objects":[{"StartTime":153091.0,"Position":483.0,"HyperDash":false},{"StartTime":153182.0,"Position":408.3958,"HyperDash":false},{"StartTime":153309.0,"Position":345.0032,"HyperDash":false}]},{"StartTime":153420.0,"Objects":[{"StartTime":153420.0,"Position":428.0,"HyperDash":true}]},{"StartTime":153529.0,"Objects":[{"StartTime":153529.0,"Position":227.0,"HyperDash":false},{"StartTime":153638.0,"Position":226.115463,"HyperDash":false}]},{"StartTime":153748.0,"Objects":[{"StartTime":153748.0,"Position":323.0,"HyperDash":false}]},{"StartTime":153967.0,"Objects":[{"StartTime":153967.0,"Position":33.0,"HyperDash":false},{"StartTime":154058.0,"Position":11.8165741,"HyperDash":false},{"StartTime":154185.0,"Position":30.1649818,"HyperDash":false}]},{"StartTime":154296.0,"Objects":[{"StartTime":154296.0,"Position":114.0,"HyperDash":true}]},{"StartTime":154405.0,"Objects":[{"StartTime":154405.0,"Position":381.0,"HyperDash":false},{"StartTime":154459.0,"Position":329.8956,"HyperDash":false},{"StartTime":154514.0,"Position":328.159637,"HyperDash":false},{"StartTime":154568.0,"Position":259.055237,"HyperDash":false},{"StartTime":154623.0,"Position":243.31926,"HyperDash":false},{"StartTime":154714.0,"Position":166.847,"HyperDash":false},{"StartTime":154842.0,"Position":105.006927,"HyperDash":true}]},{"StartTime":155062.0,"Objects":[{"StartTime":155062.0,"Position":451.0,"HyperDash":false},{"StartTime":155116.0,"Position":474.1808,"HyperDash":false},{"StartTime":155171.0,"Position":482.115234,"HyperDash":false},{"StartTime":155225.0,"Position":473.658417,"HyperDash":false},{"StartTime":155280.0,"Position":475.76123,"HyperDash":false},{"StartTime":155371.0,"Position":450.3246,"HyperDash":false},{"StartTime":155499.0,"Position":354.987061,"HyperDash":true}]},{"StartTime":155719.0,"Objects":[{"StartTime":155719.0,"Position":22.0,"HyperDash":false},{"StartTime":155810.0,"Position":63.60431,"HyperDash":false},{"StartTime":155937.0,"Position":159.997131,"HyperDash":true}]},{"StartTime":156157.0,"Objects":[{"StartTime":156157.0,"Position":478.0,"HyperDash":false},{"StartTime":156211.0,"Position":461.9211,"HyperDash":false},{"StartTime":156266.0,"Position":399.211151,"HyperDash":false},{"StartTime":156320.0,"Position":377.132263,"HyperDash":false},{"StartTime":156375.0,"Position":340.4223,"HyperDash":false},{"StartTime":156430.0,"Position":322.712341,"HyperDash":false},{"StartTime":156485.0,"Position":271.00235,"HyperDash":false},{"StartTime":156539.0,"Position":309.0812,"HyperDash":false},{"StartTime":156594.0,"Position":339.7912,"HyperDash":false},{"StartTime":156685.0,"Position":387.220428,"HyperDash":false},{"StartTime":156813.0,"Position":478.0,"HyperDash":true}]},{"StartTime":157033.0,"Objects":[{"StartTime":157033.0,"Position":159.0,"HyperDash":false},{"StartTime":157087.0,"Position":134.242828,"HyperDash":false},{"StartTime":157142.0,"Position":89.84937,"HyperDash":false},{"StartTime":157196.0,"Position":60.5968933,"HyperDash":false},{"StartTime":157251.0,"Position":65.38586,"HyperDash":false},{"StartTime":157342.0,"Position":103.223328,"HyperDash":false},{"StartTime":157470.0,"Position":163.359787,"HyperDash":false}]},{"StartTime":157580.0,"Objects":[{"StartTime":157580.0,"Position":254.0,"HyperDash":false}]},{"StartTime":157690.0,"Objects":[{"StartTime":157690.0,"Position":163.0,"HyperDash":true}]},{"StartTime":157799.0,"Objects":[{"StartTime":157799.0,"Position":396.0,"HyperDash":true}]},{"StartTime":157909.0,"Objects":[{"StartTime":157909.0,"Position":163.0,"HyperDash":false},{"StartTime":158000.0,"Position":136.677887,"HyperDash":false},{"StartTime":158127.0,"Position":164.098557,"HyperDash":false}]},{"StartTime":158237.0,"Objects":[{"StartTime":158237.0,"Position":255.0,"HyperDash":false}]},{"StartTime":158347.0,"Objects":[{"StartTime":158347.0,"Position":164.0,"HyperDash":false},{"StartTime":158456.0,"Position":162.135818,"HyperDash":true}]},{"StartTime":158566.0,"Objects":[{"StartTime":158566.0,"Position":363.0,"HyperDash":false},{"StartTime":158675.0,"Position":363.919922,"HyperDash":true}]},{"StartTime":158785.0,"Objects":[{"StartTime":158785.0,"Position":128.0,"HyperDash":false},{"StartTime":158894.0,"Position":196.994614,"HyperDash":true}]},{"StartTime":159004.0,"Objects":[{"StartTime":159004.0,"Position":398.0,"HyperDash":true}]},{"StartTime":159113.0,"Objects":[{"StartTime":159113.0,"Position":198.0,"HyperDash":false}]},{"StartTime":159223.0,"Objects":[{"StartTime":159223.0,"Position":100.0,"HyperDash":false},{"StartTime":159314.0,"Position":80.50117,"HyperDash":false},{"StartTime":159441.0,"Position":104.636375,"HyperDash":false}]},{"StartTime":159551.0,"Objects":[{"StartTime":159551.0,"Position":187.0,"HyperDash":true}]},{"StartTime":159661.0,"Objects":[{"StartTime":159661.0,"Position":422.0,"HyperDash":false},{"StartTime":159770.0,"Position":353.00705,"HyperDash":true}]},{"StartTime":159880.0,"Objects":[{"StartTime":159880.0,"Position":151.0,"HyperDash":true}]},{"StartTime":159989.0,"Objects":[{"StartTime":159989.0,"Position":350.0,"HyperDash":false}]},{"StartTime":160099.0,"Objects":[{"StartTime":160099.0,"Position":254.0,"HyperDash":false},{"StartTime":160190.0,"Position":324.6055,"HyperDash":false},{"StartTime":160317.0,"Position":392.0,"HyperDash":false}]},{"StartTime":160427.0,"Objects":[{"StartTime":160427.0,"Position":296.0,"HyperDash":true}]},{"StartTime":160536.0,"Objects":[{"StartTime":160536.0,"Position":62.0,"HyperDash":false},{"StartTime":160645.0,"Position":61.054882,"HyperDash":false}]},{"StartTime":160755.0,"Objects":[{"StartTime":160755.0,"Position":171.0,"HyperDash":false},{"StartTime":160864.0,"Position":240.0,"HyperDash":true}]},{"StartTime":160974.0,"Objects":[{"StartTime":160974.0,"Position":441.0,"HyperDash":false},{"StartTime":161065.0,"Position":460.246124,"HyperDash":false},{"StartTime":161192.0,"Position":438.9324,"HyperDash":false}]},{"StartTime":161303.0,"Objects":[{"StartTime":161303.0,"Position":354.0,"HyperDash":true}]},{"StartTime":161412.0,"Objects":[{"StartTime":161412.0,"Position":120.0,"HyperDash":false},{"StartTime":161503.0,"Position":188.6055,"HyperDash":false},{"StartTime":161630.0,"Position":258.0,"HyperDash":false}]},{"StartTime":161741.0,"Objects":[{"StartTime":161741.0,"Position":167.0,"HyperDash":false}]},{"StartTime":161850.0,"Objects":[{"StartTime":161850.0,"Position":256.0,"HyperDash":false},{"StartTime":161959.0,"Position":256.873352,"HyperDash":true}]},{"StartTime":162069.0,"Objects":[{"StartTime":162069.0,"Position":55.0,"HyperDash":false},{"StartTime":162178.0,"Position":53.2083969,"HyperDash":true}]},{"StartTime":162288.0,"Objects":[{"StartTime":162288.0,"Position":288.0,"HyperDash":false},{"StartTime":162397.0,"Position":357.0,"HyperDash":true}]},{"StartTime":162507.0,"Objects":[{"StartTime":162507.0,"Position":155.0,"HyperDash":true}]},{"StartTime":162617.0,"Objects":[{"StartTime":162617.0,"Position":356.0,"HyperDash":false}]},{"StartTime":162726.0,"Objects":[{"StartTime":162726.0,"Position":452.0,"HyperDash":false},{"StartTime":162817.0,"Position":467.2106,"HyperDash":false},{"StartTime":162944.0,"Position":448.8102,"HyperDash":false}]},{"StartTime":163055.0,"Objects":[{"StartTime":163055.0,"Position":364.0,"HyperDash":true}]},{"StartTime":163164.0,"Objects":[{"StartTime":163164.0,"Position":130.0,"HyperDash":false},{"StartTime":163273.0,"Position":128.231354,"HyperDash":false}]},{"StartTime":163383.0,"Objects":[{"StartTime":163383.0,"Position":239.0,"HyperDash":false},{"StartTime":163492.0,"Position":240.915924,"HyperDash":true}]},{"StartTime":163602.0,"Objects":[{"StartTime":163602.0,"Position":39.0,"HyperDash":false},{"StartTime":163711.0,"Position":108.0,"HyperDash":true}]},{"StartTime":163821.0,"Objects":[{"StartTime":163821.0,"Position":378.0,"HyperDash":false},{"StartTime":163930.0,"Position":379.0146,"HyperDash":false}]},{"StartTime":164040.0,"Objects":[{"StartTime":164040.0,"Position":268.0,"HyperDash":false},{"StartTime":164149.0,"Position":199.0,"HyperDash":true}]},{"StartTime":164259.0,"Objects":[{"StartTime":164259.0,"Position":400.0,"HyperDash":false},{"StartTime":164368.0,"Position":401.8897,"HyperDash":true}]},{"StartTime":164478.0,"Objects":[{"StartTime":164478.0,"Position":200.0,"HyperDash":false},{"StartTime":164587.0,"Position":131.0,"HyperDash":true}]},{"StartTime":164697.0,"Objects":[{"StartTime":164697.0,"Position":366.0,"HyperDash":false},{"StartTime":164806.0,"Position":434.995453,"HyperDash":true}]},{"StartTime":164916.0,"Objects":[{"StartTime":164916.0,"Position":164.0,"HyperDash":false},{"StartTime":165007.0,"Position":99.39598,"HyperDash":false},{"StartTime":165134.0,"Position":26.00357,"HyperDash":false}]},{"StartTime":165244.0,"Objects":[{"StartTime":165244.0,"Position":116.0,"HyperDash":false}]},{"StartTime":165354.0,"Objects":[{"StartTime":165354.0,"Position":27.0,"HyperDash":true}]},{"StartTime":165573.0,"Objects":[{"StartTime":165573.0,"Position":344.0,"HyperDash":false}]},{"StartTime":165682.0,"Objects":[{"StartTime":165682.0,"Position":381.0,"HyperDash":false}]},{"StartTime":165792.0,"Objects":[{"StartTime":165792.0,"Position":339.0,"HyperDash":false}]},{"StartTime":165901.0,"Objects":[{"StartTime":165901.0,"Position":263.0,"HyperDash":false}]},{"StartTime":166011.0,"Objects":[{"StartTime":166011.0,"Position":152.0,"HyperDash":false},{"StartTime":166120.0,"Position":151.092178,"HyperDash":true}]},{"StartTime":166230.0,"Objects":[{"StartTime":166230.0,"Position":352.0,"HyperDash":false}]},{"StartTime":166339.0,"Objects":[{"StartTime":166339.0,"Position":427.0,"HyperDash":false}]},{"StartTime":166449.0,"Objects":[{"StartTime":166449.0,"Position":464.0,"HyperDash":false}]},{"StartTime":166558.0,"Objects":[{"StartTime":166558.0,"Position":425.0,"HyperDash":true}]},{"StartTime":166668.0,"Objects":[{"StartTime":166668.0,"Position":189.0,"HyperDash":false}]},{"StartTime":166777.0,"Objects":[{"StartTime":166777.0,"Position":116.0,"HyperDash":false}]},{"StartTime":166887.0,"Objects":[{"StartTime":166887.0,"Position":125.0,"HyperDash":false}]},{"StartTime":166996.0,"Objects":[{"StartTime":166996.0,"Position":199.0,"HyperDash":false}]},{"StartTime":167106.0,"Objects":[{"StartTime":167106.0,"Position":309.0,"HyperDash":false},{"StartTime":167215.0,"Position":310.768646,"HyperDash":false}]},{"StartTime":167325.0,"Objects":[{"StartTime":167325.0,"Position":199.0,"HyperDash":false},{"StartTime":167434.0,"Position":197.084076,"HyperDash":true}]},{"StartTime":167544.0,"Objects":[{"StartTime":167544.0,"Position":398.0,"HyperDash":false},{"StartTime":167653.0,"Position":467.0,"HyperDash":false}]},{"StartTime":167763.0,"Objects":[{"StartTime":167763.0,"Position":356.0,"HyperDash":false},{"StartTime":167872.0,"Position":287.00647,"HyperDash":true}]},{"StartTime":167982.0,"Objects":[{"StartTime":167982.0,"Position":85.0,"HyperDash":false},{"StartTime":168091.0,"Position":16.0,"HyperDash":false}]},{"StartTime":168201.0,"Objects":[{"StartTime":168201.0,"Position":126.0,"HyperDash":false},{"StartTime":168310.0,"Position":195.0,"HyperDash":true}]},{"StartTime":168420.0,"Objects":[{"StartTime":168420.0,"Position":430.0,"HyperDash":false},{"StartTime":168474.0,"Position":467.7612,"HyperDash":false},{"StartTime":168529.0,"Position":476.801575,"HyperDash":false},{"StartTime":168583.0,"Position":504.865875,"HyperDash":false},{"StartTime":168638.0,"Position":482.8523,"HyperDash":false},{"StartTime":168729.0,"Position":447.068665,"HyperDash":false},{"StartTime":168857.0,"Position":367.438934,"HyperDash":false}]},{"StartTime":169077.0,"Objects":[{"StartTime":169077.0,"Position":174.0,"HyperDash":false}]},{"StartTime":169186.0,"Objects":[{"StartTime":169186.0,"Position":99.0,"HyperDash":false}]},{"StartTime":169296.0,"Objects":[{"StartTime":169296.0,"Position":67.0,"HyperDash":false}]},{"StartTime":169405.0,"Objects":[{"StartTime":169405.0,"Position":101.0,"HyperDash":false}]},{"StartTime":169515.0,"Objects":[{"StartTime":169515.0,"Position":176.0,"HyperDash":false}]},{"StartTime":169734.0,"Objects":[{"StartTime":169734.0,"Position":465.0,"HyperDash":false},{"StartTime":169825.0,"Position":484.828766,"HyperDash":false},{"StartTime":169952.0,"Position":466.9854,"HyperDash":false}]},{"StartTime":170062.0,"Objects":[{"StartTime":170062.0,"Position":390.0,"HyperDash":true}]},{"StartTime":170172.0,"Objects":[{"StartTime":170172.0,"Position":154.0,"HyperDash":false},{"StartTime":170226.0,"Position":188.078888,"HyperDash":false},{"StartTime":170281.0,"Position":228.788879,"HyperDash":false},{"StartTime":170335.0,"Position":239.867767,"HyperDash":false},{"StartTime":170390.0,"Position":291.577759,"HyperDash":false},{"StartTime":170500.0,"Position":360.997742,"HyperDash":true}]},{"StartTime":170609.0,"Objects":[{"StartTime":170609.0,"Position":127.0,"HyperDash":false},{"StartTime":170700.0,"Position":112.127007,"HyperDash":false},{"StartTime":170827.0,"Position":125.797905,"HyperDash":false}]},{"StartTime":170938.0,"Objects":[{"StartTime":170938.0,"Position":202.0,"HyperDash":true}]},{"StartTime":171047.0,"Objects":[{"StartTime":171047.0,"Position":401.0,"HyperDash":false},{"StartTime":171101.0,"Position":350.353882,"HyperDash":false},{"StartTime":171156.0,"Position":321.8849,"HyperDash":false},{"StartTime":171210.0,"Position":305.955536,"HyperDash":false},{"StartTime":171265.0,"Position":268.51535,"HyperDash":false},{"StartTime":171319.0,"Position":246.017654,"HyperDash":false},{"StartTime":171374.0,"Position":211.42424,"HyperDash":false},{"StartTime":171429.0,"Position":173.4286,"HyperDash":false},{"StartTime":171484.0,"Position":155.9888,"HyperDash":false},{"StartTime":171575.0,"Position":145.032578,"HyperDash":false},{"StartTime":171703.0,"Position":125.051888,"HyperDash":false}]},{"StartTime":171923.0,"Objects":[{"StartTime":171923.0,"Position":416.0,"HyperDash":false}]},{"StartTime":178712.0,"Objects":[{"StartTime":178712.0,"Position":85.0,"HyperDash":true}]},{"StartTime":178931.0,"Objects":[{"StartTime":178931.0,"Position":402.0,"HyperDash":false},{"StartTime":179022.0,"Position":430.926239,"HyperDash":false},{"StartTime":179149.0,"Position":400.1261,"HyperDash":false}]},{"StartTime":179259.0,"Objects":[{"StartTime":179259.0,"Position":323.0,"HyperDash":false}]},{"StartTime":179369.0,"Objects":[{"StartTime":179369.0,"Position":212.0,"HyperDash":false},{"StartTime":179460.0,"Position":173.1731,"HyperDash":false},{"StartTime":179587.0,"Position":94.04442,"HyperDash":false}]},{"StartTime":179697.0,"Objects":[{"StartTime":179697.0,"Position":170.0,"HyperDash":false}]},{"StartTime":179807.0,"Objects":[{"StartTime":179807.0,"Position":280.0,"HyperDash":false},{"StartTime":179898.0,"Position":342.6055,"HyperDash":false},{"StartTime":180025.0,"Position":418.0,"HyperDash":false}]},{"StartTime":180135.0,"Objects":[{"StartTime":180135.0,"Position":307.0,"HyperDash":false}]},{"StartTime":180244.0,"Objects":[{"StartTime":180244.0,"Position":238.0,"HyperDash":false}]},{"StartTime":180354.0,"Objects":[{"StartTime":180354.0,"Position":307.0,"HyperDash":false}]},{"StartTime":180463.0,"Objects":[{"StartTime":180463.0,"Position":417.0,"HyperDash":false},{"StartTime":180572.0,"Position":417.896027,"HyperDash":true}]},{"StartTime":180682.0,"Objects":[{"StartTime":180682.0,"Position":216.0,"HyperDash":false}]},{"StartTime":180792.0,"Objects":[{"StartTime":180792.0,"Position":313.0,"HyperDash":false}]},{"StartTime":180901.0,"Objects":[{"StartTime":180901.0,"Position":381.0,"HyperDash":false}]},{"StartTime":181011.0,"Objects":[{"StartTime":181011.0,"Position":313.0,"HyperDash":false}]},{"StartTime":181120.0,"Objects":[{"StartTime":181120.0,"Position":203.0,"HyperDash":false}]},{"StartTime":181230.0,"Objects":[{"StartTime":181230.0,"Position":133.0,"HyperDash":false}]},{"StartTime":181339.0,"Objects":[{"StartTime":181339.0,"Position":203.0,"HyperDash":false}]},{"StartTime":181558.0,"Objects":[{"StartTime":181558.0,"Position":396.0,"HyperDash":false},{"StartTime":181649.0,"Position":414.144623,"HyperDash":false},{"StartTime":181776.0,"Position":397.136444,"HyperDash":false}]},{"StartTime":181887.0,"Objects":[{"StartTime":181887.0,"Position":320.0,"HyperDash":false}]},{"StartTime":181996.0,"Objects":[{"StartTime":181996.0,"Position":210.0,"HyperDash":false},{"StartTime":182087.0,"Position":169.395859,"HyperDash":false},{"StartTime":182214.0,"Position":72.00328,"HyperDash":false}]},{"StartTime":182325.0,"Objects":[{"StartTime":182325.0,"Position":148.0,"HyperDash":true}]},{"StartTime":182434.0,"Objects":[{"StartTime":182434.0,"Position":347.0,"HyperDash":false}]},{"StartTime":182544.0,"Objects":[{"StartTime":182544.0,"Position":416.0,"HyperDash":false}]},{"StartTime":182653.0,"Objects":[{"StartTime":182653.0,"Position":347.0,"HyperDash":false}]},{"StartTime":182872.0,"Objects":[{"StartTime":182872.0,"Position":154.0,"HyperDash":false}]},{"StartTime":182982.0,"Objects":[{"StartTime":182982.0,"Position":85.0,"HyperDash":false}]},{"StartTime":183091.0,"Objects":[{"StartTime":183091.0,"Position":154.0,"HyperDash":false}]},{"StartTime":183310.0,"Objects":[{"StartTime":183310.0,"Position":347.0,"HyperDash":false},{"StartTime":183401.0,"Position":374.666382,"HyperDash":false},{"StartTime":183528.0,"Position":343.605865,"HyperDash":false}]},{"StartTime":183639.0,"Objects":[{"StartTime":183639.0,"Position":231.0,"HyperDash":false}]},{"StartTime":183748.0,"Objects":[{"StartTime":183748.0,"Position":162.0,"HyperDash":false}]},{"StartTime":183858.0,"Objects":[{"StartTime":183858.0,"Position":231.0,"HyperDash":false}]},{"StartTime":183967.0,"Objects":[{"StartTime":183967.0,"Position":343.0,"HyperDash":false},{"StartTime":184076.0,"Position":344.8897,"HyperDash":true}]},{"StartTime":184186.0,"Objects":[{"StartTime":184186.0,"Position":143.0,"HyperDash":false}]},{"StartTime":184405.0,"Objects":[{"StartTime":184405.0,"Position":323.0,"HyperDash":false}]},{"StartTime":184624.0,"Objects":[{"StartTime":184624.0,"Position":143.0,"HyperDash":false},{"StartTime":184715.0,"Position":105.191986,"HyperDash":false},{"StartTime":184842.0,"Position":143.952225,"HyperDash":false}]},{"StartTime":184953.0,"Objects":[{"StartTime":184953.0,"Position":221.0,"HyperDash":true}]},{"StartTime":185062.0,"Objects":[{"StartTime":185062.0,"Position":421.0,"HyperDash":false},{"StartTime":185116.0,"Position":402.9211,"HyperDash":false},{"StartTime":185171.0,"Position":371.211121,"HyperDash":false},{"StartTime":185225.0,"Position":307.1322,"HyperDash":false},{"StartTime":185280.0,"Position":283.422241,"HyperDash":false},{"StartTime":185335.0,"Position":234.712234,"HyperDash":false},{"StartTime":185390.0,"Position":214.002228,"HyperDash":false},{"StartTime":185444.0,"Position":264.081116,"HyperDash":false},{"StartTime":185499.0,"Position":282.791138,"HyperDash":false},{"StartTime":185590.0,"Position":328.2204,"HyperDash":false},{"StartTime":185718.0,"Position":421.0,"HyperDash":true}]},{"StartTime":185938.0,"Objects":[{"StartTime":185938.0,"Position":102.0,"HyperDash":false},{"StartTime":186029.0,"Position":81.6439056,"HyperDash":false},{"StartTime":186156.0,"Position":105.267693,"HyperDash":false}]},{"StartTime":186266.0,"Objects":[{"StartTime":186266.0,"Position":181.0,"HyperDash":false}]},{"StartTime":186376.0,"Objects":[{"StartTime":186376.0,"Position":291.0,"HyperDash":false},{"StartTime":186467.0,"Position":364.6055,"HyperDash":false},{"StartTime":186594.0,"Position":429.0,"HyperDash":false}]},{"StartTime":186704.0,"Objects":[{"StartTime":186704.0,"Position":352.0,"HyperDash":true}]},{"StartTime":186814.0,"Objects":[{"StartTime":186814.0,"Position":150.0,"HyperDash":false},{"StartTime":186905.0,"Position":147.9285,"HyperDash":false},{"StartTime":187032.0,"Position":146.246689,"HyperDash":false}]},{"StartTime":187142.0,"Objects":[{"StartTime":187142.0,"Position":257.0,"HyperDash":false}]},{"StartTime":187252.0,"Objects":[{"StartTime":187252.0,"Position":325.0,"HyperDash":false}]},{"StartTime":187361.0,"Objects":[{"StartTime":187361.0,"Position":253.0,"HyperDash":false}]},{"StartTime":187471.0,"Objects":[{"StartTime":187471.0,"Position":141.0,"HyperDash":false},{"StartTime":187580.0,"Position":72.0,"HyperDash":true}]},{"StartTime":187690.0,"Objects":[{"StartTime":187690.0,"Position":307.0,"HyperDash":false},{"StartTime":187781.0,"Position":334.582428,"HyperDash":false},{"StartTime":187908.0,"Position":308.8075,"HyperDash":false}]},{"StartTime":188128.0,"Objects":[{"StartTime":188128.0,"Position":113.0,"HyperDash":false},{"StartTime":188219.0,"Position":99.06281,"HyperDash":false},{"StartTime":188346.0,"Position":114.246552,"HyperDash":false}]},{"StartTime":188456.0,"Objects":[{"StartTime":188456.0,"Position":190.0,"HyperDash":true}]},{"StartTime":188566.0,"Objects":[{"StartTime":188566.0,"Position":391.0,"HyperDash":false}]},{"StartTime":188785.0,"Objects":[{"StartTime":188785.0,"Position":211.0,"HyperDash":false}]},{"StartTime":189004.0,"Objects":[{"StartTime":189004.0,"Position":390.0,"HyperDash":false},{"StartTime":189095.0,"Position":373.8,"HyperDash":false},{"StartTime":189222.0,"Position":391.916473,"HyperDash":true}]},{"StartTime":189442.0,"Objects":[{"StartTime":189442.0,"Position":73.0,"HyperDash":false}]},{"StartTime":189551.0,"Objects":[{"StartTime":189551.0,"Position":39.0,"HyperDash":false}]},{"StartTime":189661.0,"Objects":[{"StartTime":189661.0,"Position":76.0,"HyperDash":false}]},{"StartTime":189770.0,"Objects":[{"StartTime":189770.0,"Position":158.0,"HyperDash":false}]},{"StartTime":189880.0,"Objects":[{"StartTime":189880.0,"Position":268.0,"HyperDash":false},{"StartTime":189971.0,"Position":212.3957,"HyperDash":false},{"StartTime":190098.0,"Position":130.002914,"HyperDash":false}]},{"StartTime":190208.0,"Objects":[{"StartTime":190208.0,"Position":213.0,"HyperDash":true}]},{"StartTime":190317.0,"Objects":[{"StartTime":190317.0,"Position":412.0,"HyperDash":false},{"StartTime":190408.0,"Position":424.883728,"HyperDash":false},{"StartTime":190535.0,"Position":410.9749,"HyperDash":false}]},{"StartTime":190646.0,"Objects":[{"StartTime":190646.0,"Position":320.0,"HyperDash":false}]},{"StartTime":190755.0,"Objects":[{"StartTime":190755.0,"Position":230.0,"HyperDash":false}]},{"StartTime":190974.0,"Objects":[{"StartTime":190974.0,"Position":409.0,"HyperDash":true}]},{"StartTime":191193.0,"Objects":[{"StartTime":191193.0,"Position":91.0,"HyperDash":false},{"StartTime":191247.0,"Position":44.74952,"HyperDash":false},{"StartTime":191302.0,"Position":25.7194824,"HyperDash":false},{"StartTime":191356.0,"Position":28.6760178,"HyperDash":false},{"StartTime":191411.0,"Position":24.610136,"HyperDash":false},{"StartTime":191502.0,"Position":53.48176,"HyperDash":false},{"StartTime":191630.0,"Position":137.592667,"HyperDash":false}]},{"StartTime":191850.0,"Objects":[{"StartTime":191850.0,"Position":344.0,"HyperDash":false}]},{"StartTime":191960.0,"Objects":[{"StartTime":191960.0,"Position":427.0,"HyperDash":false}]},{"StartTime":192069.0,"Objects":[{"StartTime":192069.0,"Position":344.0,"HyperDash":false}]},{"StartTime":192288.0,"Objects":[{"StartTime":192288.0,"Position":138.0,"HyperDash":false}]},{"StartTime":192507.0,"Objects":[{"StartTime":192507.0,"Position":427.0,"HyperDash":false},{"StartTime":192598.0,"Position":442.391876,"HyperDash":false},{"StartTime":192725.0,"Position":427.938751,"HyperDash":true}]},{"StartTime":192945.0,"Objects":[{"StartTime":192945.0,"Position":81.0,"HyperDash":false},{"StartTime":193036.0,"Position":144.887146,"HyperDash":false},{"StartTime":193163.0,"Position":260.4,"HyperDash":false}]},{"StartTime":193383.0,"Objects":[{"StartTime":193383.0,"Position":81.0,"HyperDash":true},{"StartTime":193474.0,"Position":189.970917,"HyperDash":false},{"StartTime":193601.0,"Position":370.798462,"HyperDash":false}]},{"StartTime":193821.0,"Objects":[{"StartTime":193821.0,"Position":190.0,"HyperDash":false},{"StartTime":193912.0,"Position":279.887146,"HyperDash":false},{"StartTime":194039.0,"Position":369.4,"HyperDash":false}]},{"StartTime":194259.0,"Objects":[{"StartTime":194259.0,"Position":78.0,"HyperDash":true},{"StartTime":194350.0,"Position":207.970978,"HyperDash":false},{"StartTime":194477.0,"Position":367.798584,"HyperDash":true}]},{"StartTime":194697.0,"Objects":[{"StartTime":194697.0,"Position":76.0,"HyperDash":false},{"StartTime":194788.0,"Position":77.1591339,"HyperDash":false},{"StartTime":194915.0,"Position":73.98562,"HyperDash":false}]},{"StartTime":195135.0,"Objects":[{"StartTime":195135.0,"Position":365.0,"HyperDash":true},{"StartTime":195226.0,"Position":253.0291,"HyperDash":false},{"StartTime":195353.0,"Position":75.2016,"HyperDash":true}]},{"StartTime":195573.0,"Objects":[{"StartTime":195573.0,"Position":394.0,"HyperDash":false},{"StartTime":195664.0,"Position":392.411469,"HyperDash":false},{"StartTime":195791.0,"Position":394.9857,"HyperDash":false}]},{"StartTime":196011.0,"Objects":[{"StartTime":196011.0,"Position":105.0,"HyperDash":true},{"StartTime":196102.0,"Position":210.9709,"HyperDash":false},{"StartTime":196229.0,"Position":394.7984,"HyperDash":true}]},{"StartTime":196449.0,"Objects":[{"StartTime":196449.0,"Position":75.0,"HyperDash":true}]},{"StartTime":196668.0,"Objects":[{"StartTime":196668.0,"Position":422.0,"HyperDash":true},{"StartTime":196722.0,"Position":331.3793,"HyperDash":false},{"StartTime":196777.0,"Position":264.4323,"HyperDash":false},{"StartTime":196831.0,"Position":194.811615,"HyperDash":false},{"StartTime":196886.0,"Position":132.201477,"HyperDash":false},{"StartTime":196977.0,"Position":246.232452,"HyperDash":false},{"StartTime":197105.0,"Position":422.0,"HyperDash":true}]},{"StartTime":197325.0,"Objects":[{"StartTime":197325.0,"Position":75.0,"HyperDash":true},{"StartTime":197379.0,"Position":144.6207,"HyperDash":false},{"StartTime":197434.0,"Position":211.567688,"HyperDash":false},{"StartTime":197488.0,"Position":310.1884,"HyperDash":false},{"StartTime":197543.0,"Position":364.798523,"HyperDash":false},{"StartTime":197634.0,"Position":238.767548,"HyperDash":false},{"StartTime":197762.0,"Position":75.0,"HyperDash":true}]},{"StartTime":197982.0,"Objects":[{"StartTime":197982.0,"Position":395.0,"HyperDash":true}]},{"StartTime":198201.0,"Objects":[{"StartTime":198201.0,"Position":47.0,"HyperDash":true},{"StartTime":198292.0,"Position":164.970886,"HyperDash":false},{"StartTime":198419.0,"Position":336.7984,"HyperDash":false}]},{"StartTime":198639.0,"Objects":[{"StartTime":198639.0,"Position":142.0,"HyperDash":false},{"StartTime":198730.0,"Position":237.6467,"HyperDash":false},{"StartTime":198857.0,"Position":335.197571,"HyperDash":true}]},{"StartTime":199077.0,"Objects":[{"StartTime":199077.0,"Position":26.0,"HyperDash":true}]},{"StartTime":199296.0,"Objects":[{"StartTime":199296.0,"Position":371.0,"HyperDash":false},{"StartTime":199350.0,"Position":333.0469,"HyperDash":false},{"StartTime":199405.0,"Position":303.045837,"HyperDash":false},{"StartTime":199459.0,"Position":275.5022,"HyperDash":false},{"StartTime":199514.0,"Position":251.71991,"HyperDash":false},{"StartTime":199605.0,"Position":278.949951,"HyperDash":false},{"StartTime":199733.0,"Position":378.108917,"HyperDash":true}]},{"StartTime":199953.0,"Objects":[{"StartTime":199953.0,"Position":56.0,"HyperDash":false},{"StartTime":200007.0,"Position":103.078979,"HyperDash":false},{"StartTime":200062.0,"Position":109.78904,"HyperDash":false},{"StartTime":200116.0,"Position":145.868011,"HyperDash":false},{"StartTime":200171.0,"Position":193.578079,"HyperDash":false},{"StartTime":200226.0,"Position":229.288147,"HyperDash":false},{"StartTime":200281.0,"Position":262.99823,"HyperDash":false},{"StartTime":200335.0,"Position":225.91925,"HyperDash":false},{"StartTime":200390.0,"Position":194.209167,"HyperDash":false},{"StartTime":200481.0,"Position":139.779785,"HyperDash":false},{"StartTime":200609.0,"Position":56.0,"HyperDash":false}]},{"StartTime":200828.0,"Objects":[{"StartTime":200828.0,"Position":249.0,"HyperDash":false},{"StartTime":200937.0,"Position":250.56778,"HyperDash":false}]},{"StartTime":201047.0,"Objects":[{"StartTime":201047.0,"Position":160.0,"HyperDash":false}]},{"StartTime":201157.0,"Objects":[{"StartTime":201157.0,"Position":250.0,"HyperDash":true}]},{"StartTime":201266.0,"Objects":[{"StartTime":201266.0,"Position":50.0,"HyperDash":false}]},{"StartTime":201376.0,"Objects":[{"StartTime":201376.0,"Position":139.0,"HyperDash":false}]},{"StartTime":201485.0,"Objects":[{"StartTime":201485.0,"Position":50.0,"HyperDash":true}]},{"StartTime":201595.0,"Objects":[{"StartTime":201595.0,"Position":285.0,"HyperDash":true}]},{"StartTime":201704.0,"Objects":[{"StartTime":201704.0,"Position":50.0,"HyperDash":false},{"StartTime":201813.0,"Position":48.2537231,"HyperDash":true}]},{"StartTime":201923.0,"Objects":[{"StartTime":201923.0,"Position":249.0,"HyperDash":true}]},{"StartTime":202033.0,"Objects":[{"StartTime":202033.0,"Position":48.0,"HyperDash":false}]},{"StartTime":202142.0,"Objects":[{"StartTime":202142.0,"Position":141.0,"HyperDash":false},{"StartTime":202233.0,"Position":181.263123,"HyperDash":false},{"StartTime":202360.0,"Position":140.921326,"HyperDash":false}]},{"StartTime":202471.0,"Objects":[{"StartTime":202471.0,"Position":45.0,"HyperDash":true}]},{"StartTime":202580.0,"Objects":[{"StartTime":202580.0,"Position":278.0,"HyperDash":false}]},{"StartTime":202690.0,"Objects":[{"StartTime":202690.0,"Position":180.0,"HyperDash":false},{"StartTime":202799.0,"Position":179.028259,"HyperDash":true}]},{"StartTime":202909.0,"Objects":[{"StartTime":202909.0,"Position":380.0,"HyperDash":false}]},{"StartTime":203018.0,"Objects":[{"StartTime":203018.0,"Position":283.0,"HyperDash":false},{"StartTime":203109.0,"Position":343.604553,"HyperDash":false},{"StartTime":203236.0,"Position":420.997742,"HyperDash":false}]},{"StartTime":203347.0,"Objects":[{"StartTime":203347.0,"Position":337.0,"HyperDash":true}]},{"StartTime":203456.0,"Objects":[{"StartTime":203456.0,"Position":103.0,"HyperDash":false},{"StartTime":203547.0,"Position":60.25659,"HyperDash":false},{"StartTime":203674.0,"Position":111.501694,"HyperDash":false}]},{"StartTime":203785.0,"Objects":[{"StartTime":203785.0,"Position":202.0,"HyperDash":false}]},{"StartTime":203894.0,"Objects":[{"StartTime":203894.0,"Position":111.0,"HyperDash":false},{"StartTime":204003.0,"Position":109.296814,"HyperDash":true}]},{"StartTime":204113.0,"Objects":[{"StartTime":204113.0,"Position":310.0,"HyperDash":false},{"StartTime":204222.0,"Position":378.995667,"HyperDash":true}]},{"StartTime":204332.0,"Objects":[{"StartTime":204332.0,"Position":177.0,"HyperDash":true}]},{"StartTime":204442.0,"Objects":[{"StartTime":204442.0,"Position":378.0,"HyperDash":false},{"StartTime":204551.0,"Position":378.932343,"HyperDash":true}]},{"StartTime":204661.0,"Objects":[{"StartTime":204661.0,"Position":177.0,"HyperDash":false}]},{"StartTime":204770.0,"Objects":[{"StartTime":204770.0,"Position":80.0,"HyperDash":false},{"StartTime":204861.0,"Position":65.8601456,"HyperDash":false},{"StartTime":204988.0,"Position":78.31786,"HyperDash":false}]},{"StartTime":205099.0,"Objects":[{"StartTime":205099.0,"Position":162.0,"HyperDash":true}]},{"StartTime":205208.0,"Objects":[{"StartTime":205208.0,"Position":395.0,"HyperDash":false},{"StartTime":205317.0,"Position":326.0,"HyperDash":true}]},{"StartTime":205427.0,"Objects":[{"StartTime":205427.0,"Position":124.0,"HyperDash":true}]},{"StartTime":205536.0,"Objects":[{"StartTime":205536.0,"Position":323.0,"HyperDash":false}]},{"StartTime":205646.0,"Objects":[{"StartTime":205646.0,"Position":420.0,"HyperDash":false},{"StartTime":205737.0,"Position":379.3955,"HyperDash":false},{"StartTime":205864.0,"Position":282.002441,"HyperDash":false}]},{"StartTime":205974.0,"Objects":[{"StartTime":205974.0,"Position":379.0,"HyperDash":true}]},{"StartTime":206084.0,"Objects":[{"StartTime":206084.0,"Position":143.0,"HyperDash":false},{"StartTime":206193.0,"Position":74.02588,"HyperDash":false}]},{"StartTime":206303.0,"Objects":[{"StartTime":206303.0,"Position":171.0,"HyperDash":true}]},{"StartTime":206412.0,"Objects":[{"StartTime":206412.0,"Position":370.0,"HyperDash":false}]},{"StartTime":206522.0,"Objects":[{"StartTime":206522.0,"Position":467.0,"HyperDash":false},{"StartTime":206613.0,"Position":501.909729,"HyperDash":false},{"StartTime":206740.0,"Position":463.333649,"HyperDash":false}]},{"StartTime":206850.0,"Objects":[{"StartTime":206850.0,"Position":380.0,"HyperDash":true}]},{"StartTime":206960.0,"Objects":[{"StartTime":206960.0,"Position":109.0,"HyperDash":false},{"StartTime":207051.0,"Position":184.6055,"HyperDash":false},{"StartTime":207178.0,"Position":247.0,"HyperDash":false}]},{"StartTime":207288.0,"Objects":[{"StartTime":207288.0,"Position":156.0,"HyperDash":false}]},{"StartTime":207398.0,"Objects":[{"StartTime":207398.0,"Position":65.0,"HyperDash":true}]},{"StartTime":207617.0,"Objects":[{"StartTime":207617.0,"Position":382.0,"HyperDash":false}]},{"StartTime":207726.0,"Objects":[{"StartTime":207726.0,"Position":420.0,"HyperDash":false}]},{"StartTime":207836.0,"Objects":[{"StartTime":207836.0,"Position":378.0,"HyperDash":false}]},{"StartTime":207945.0,"Objects":[{"StartTime":207945.0,"Position":302.0,"HyperDash":false}]},{"StartTime":208055.0,"Objects":[{"StartTime":208055.0,"Position":191.0,"HyperDash":false},{"StartTime":208164.0,"Position":190.092178,"HyperDash":true}]},{"StartTime":208274.0,"Objects":[{"StartTime":208274.0,"Position":391.0,"HyperDash":false},{"StartTime":208365.0,"Position":402.309845,"HyperDash":false},{"StartTime":208492.0,"Position":381.4403,"HyperDash":false}]},{"StartTime":208602.0,"Objects":[{"StartTime":208602.0,"Position":298.0,"HyperDash":true}]},{"StartTime":208712.0,"Objects":[{"StartTime":208712.0,"Position":62.0,"HyperDash":false},{"StartTime":208821.0,"Position":61.1154556,"HyperDash":false}]},{"StartTime":208931.0,"Objects":[{"StartTime":208931.0,"Position":172.0,"HyperDash":false},{"StartTime":209040.0,"Position":240.99353,"HyperDash":true}]},{"StartTime":209150.0,"Objects":[{"StartTime":209150.0,"Position":442.0,"HyperDash":false},{"StartTime":209241.0,"Position":460.81012,"HyperDash":false},{"StartTime":209368.0,"Position":438.616364,"HyperDash":false}]},{"StartTime":209478.0,"Objects":[{"StartTime":209478.0,"Position":355.0,"HyperDash":true}]},{"StartTime":209588.0,"Objects":[{"StartTime":209588.0,"Position":119.0,"HyperDash":false},{"StartTime":209697.0,"Position":116.205,"HyperDash":false}]},{"StartTime":209807.0,"Objects":[{"StartTime":209807.0,"Position":220.0,"HyperDash":false}]},{"StartTime":210026.0,"Objects":[{"StartTime":210026.0,"Position":413.0,"HyperDash":false}]},{"StartTime":210244.0,"Objects":[{"StartTime":210244.0,"Position":124.0,"HyperDash":false},{"StartTime":210353.0,"Position":55.0,"HyperDash":true}]},{"StartTime":210463.0,"Objects":[{"StartTime":210463.0,"Position":325.0,"HyperDash":false},{"StartTime":210517.0,"Position":370.597351,"HyperDash":false},{"StartTime":210572.0,"Position":383.999176,"HyperDash":false},{"StartTime":210626.0,"Position":443.559265,"HyperDash":false},{"StartTime":210681.0,"Position":452.158966,"HyperDash":false},{"StartTime":210772.0,"Position":494.5323,"HyperDash":false},{"StartTime":210900.0,"Position":484.299774,"HyperDash":true}]},{"StartTime":211120.0,"Objects":[{"StartTime":211120.0,"Position":165.0,"HyperDash":false},{"StartTime":211174.0,"Position":212.105072,"HyperDash":false},{"StartTime":211229.0,"Position":213.841736,"HyperDash":false},{"StartTime":211283.0,"Position":247.946808,"HyperDash":false},{"StartTime":211338.0,"Position":302.683472,"HyperDash":false},{"StartTime":211429.0,"Position":349.15686,"HyperDash":false},{"StartTime":211557.0,"Position":440.9985,"HyperDash":true}]},{"StartTime":211777.0,"Objects":[{"StartTime":211777.0,"Position":149.0,"HyperDash":false},{"StartTime":211868.0,"Position":93.3959351,"HyperDash":false},{"StartTime":211995.0,"Position":11.0034637,"HyperDash":true}]},{"StartTime":212215.0,"Objects":[{"StartTime":212215.0,"Position":357.0,"HyperDash":false},{"StartTime":212269.0,"Position":341.920715,"HyperDash":false},{"StartTime":212324.0,"Position":294.210358,"HyperDash":false},{"StartTime":212378.0,"Position":264.1311,"HyperDash":false},{"StartTime":212433.0,"Position":219.420731,"HyperDash":false},{"StartTime":212488.0,"Position":202.710373,"HyperDash":false},{"StartTime":212543.0,"Position":150.0,"HyperDash":false},{"StartTime":212597.0,"Position":190.079254,"HyperDash":false},{"StartTime":212652.0,"Position":218.789642,"HyperDash":false},{"StartTime":212743.0,"Position":290.2195,"HyperDash":false},{"StartTime":212871.0,"Position":357.0,"HyperDash":true}]},{"StartTime":213091.0,"Objects":[{"StartTime":213091.0,"Position":65.0,"HyperDash":false},{"StartTime":213145.0,"Position":117.105263,"HyperDash":false},{"StartTime":213200.0,"Position":132.8421,"HyperDash":false},{"StartTime":213254.0,"Position":151.947357,"HyperDash":false},{"StartTime":213309.0,"Position":202.6842,"HyperDash":false},{"StartTime":213400.0,"Position":256.1579,"HyperDash":false},{"StartTime":213528.0,"Position":341.0,"HyperDash":false}]},{"StartTime":213639.0,"Objects":[{"StartTime":213639.0,"Position":250.0,"HyperDash":false}]},{"StartTime":213748.0,"Objects":[{"StartTime":213748.0,"Position":339.0,"HyperDash":true}]},{"StartTime":213858.0,"Objects":[{"StartTime":213858.0,"Position":103.0,"HyperDash":true}]},{"StartTime":213967.0,"Objects":[{"StartTime":213967.0,"Position":339.0,"HyperDash":false},{"StartTime":214058.0,"Position":364.006348,"HyperDash":false},{"StartTime":214185.0,"Position":336.10022,"HyperDash":false}]},{"StartTime":214296.0,"Objects":[{"StartTime":214296.0,"Position":245.0,"HyperDash":false}]},{"StartTime":214405.0,"Objects":[{"StartTime":214405.0,"Position":334.0,"HyperDash":false},{"StartTime":214514.0,"Position":335.746277,"HyperDash":true}]},{"StartTime":214624.0,"Objects":[{"StartTime":214624.0,"Position":134.0,"HyperDash":false},{"StartTime":214733.0,"Position":65.0045547,"HyperDash":true}]},{"StartTime":214843.0,"Objects":[{"StartTime":214843.0,"Position":300.0,"HyperDash":false},{"StartTime":214952.0,"Position":300.896027,"HyperDash":true}]},{"StartTime":215062.0,"Objects":[{"StartTime":215062.0,"Position":99.0,"HyperDash":true}]},{"StartTime":215172.0,"Objects":[{"StartTime":215172.0,"Position":300.0,"HyperDash":false}]},{"StartTime":215281.0,"Objects":[{"StartTime":215281.0,"Position":203.0,"HyperDash":false},{"StartTime":215372.0,"Position":151.402954,"HyperDash":false},{"StartTime":215499.0,"Position":65.02028,"HyperDash":false}]},{"StartTime":215609.0,"Objects":[{"StartTime":215609.0,"Position":148.0,"HyperDash":true}]},{"StartTime":215719.0,"Objects":[{"StartTime":215719.0,"Position":383.0,"HyperDash":false},{"StartTime":215828.0,"Position":314.0,"HyperDash":true}]},{"StartTime":215938.0,"Objects":[{"StartTime":215938.0,"Position":112.0,"HyperDash":true}]},{"StartTime":216047.0,"Objects":[{"StartTime":216047.0,"Position":311.0,"HyperDash":false}]},{"StartTime":216157.0,"Objects":[{"StartTime":216157.0,"Position":408.0,"HyperDash":false},{"StartTime":216248.0,"Position":431.067078,"HyperDash":false},{"StartTime":216375.0,"Position":402.494934,"HyperDash":false}]},{"StartTime":216485.0,"Objects":[{"StartTime":216485.0,"Position":305.0,"HyperDash":true}]},{"StartTime":216595.0,"Objects":[{"StartTime":216595.0,"Position":69.0,"HyperDash":false},{"StartTime":216704.0,"Position":68.16873,"HyperDash":false}]},{"StartTime":216814.0,"Objects":[{"StartTime":216814.0,"Position":179.0,"HyperDash":false},{"StartTime":216923.0,"Position":247.995117,"HyperDash":true}]},{"StartTime":217033.0,"Objects":[{"StartTime":217033.0,"Position":449.0,"HyperDash":false},{"StartTime":217142.0,"Position":380.0034,"HyperDash":true}]},{"StartTime":217252.0,"Objects":[{"StartTime":217252.0,"Position":178.0,"HyperDash":false},{"StartTime":217361.0,"Position":109.0,"HyperDash":true}]},{"StartTime":217471.0,"Objects":[{"StartTime":217471.0,"Position":344.0,"HyperDash":false},{"StartTime":217562.0,"Position":286.3945,"HyperDash":false},{"StartTime":217689.0,"Position":206.0,"HyperDash":false}]},{"StartTime":217799.0,"Objects":[{"StartTime":217799.0,"Position":289.0,"HyperDash":false}]},{"StartTime":217909.0,"Objects":[{"StartTime":217909.0,"Position":206.0,"HyperDash":false},{"StartTime":218018.0,"Position":205.092178,"HyperDash":true}]},{"StartTime":218128.0,"Objects":[{"StartTime":218128.0,"Position":406.0,"HyperDash":false},{"StartTime":218237.0,"Position":474.99353,"HyperDash":true}]},{"StartTime":218347.0,"Objects":[{"StartTime":218347.0,"Position":239.0,"HyperDash":false},{"StartTime":218456.0,"Position":170.005249,"HyperDash":true}]},{"StartTime":218566.0,"Objects":[{"StartTime":218566.0,"Position":371.0,"HyperDash":true}]},{"StartTime":218675.0,"Objects":[{"StartTime":218675.0,"Position":170.0,"HyperDash":false}]},{"StartTime":218785.0,"Objects":[{"StartTime":218785.0,"Position":267.0,"HyperDash":false},{"StartTime":218876.0,"Position":329.6045,"HyperDash":false},{"StartTime":219003.0,"Position":404.997559,"HyperDash":false}]},{"StartTime":219113.0,"Objects":[{"StartTime":219113.0,"Position":321.0,"HyperDash":true}]},{"StartTime":219223.0,"Objects":[{"StartTime":219223.0,"Position":85.0,"HyperDash":false},{"StartTime":219332.0,"Position":85.0,"HyperDash":true}]},{"StartTime":219442.0,"Objects":[{"StartTime":219442.0,"Position":286.0,"HyperDash":false},{"StartTime":219551.0,"Position":354.996,"HyperDash":true}]},{"StartTime":219661.0,"Objects":[{"StartTime":219661.0,"Position":119.0,"HyperDash":false},{"StartTime":219770.0,"Position":50.0000076,"HyperDash":true}]},{"StartTime":219880.0,"Objects":[{"StartTime":219880.0,"Position":320.0,"HyperDash":false}]},{"StartTime":219989.0,"Objects":[{"StartTime":219989.0,"Position":399.0,"HyperDash":false}]},{"StartTime":220099.0,"Objects":[{"StartTime":220099.0,"Position":402.0,"HyperDash":false}]},{"StartTime":220208.0,"Objects":[{"StartTime":220208.0,"Position":327.0,"HyperDash":true}]},{"StartTime":220317.0,"Objects":[{"StartTime":220317.0,"Position":129.0,"HyperDash":false},{"StartTime":220426.0,"Position":129.0,"HyperDash":true}]},{"StartTime":220536.0,"Objects":[{"StartTime":220536.0,"Position":330.0,"HyperDash":false},{"StartTime":220645.0,"Position":398.953857,"HyperDash":true}]},{"StartTime":220755.0,"Objects":[{"StartTime":220755.0,"Position":163.0,"HyperDash":false},{"StartTime":220864.0,"Position":94.00001,"HyperDash":true}]},{"StartTime":220974.0,"Objects":[{"StartTime":220974.0,"Position":364.0,"HyperDash":false}]},{"StartTime":221084.0,"Objects":[{"StartTime":221084.0,"Position":439.0,"HyperDash":false}]},{"StartTime":221193.0,"Objects":[{"StartTime":221193.0,"Position":426.0,"HyperDash":false}]},{"StartTime":221303.0,"Objects":[{"StartTime":221303.0,"Position":350.0,"HyperDash":false}]},{"StartTime":221412.0,"Objects":[{"StartTime":221412.0,"Position":240.0,"HyperDash":false},{"StartTime":221521.0,"Position":239.148209,"HyperDash":true}]},{"StartTime":221631.0,"Objects":[{"StartTime":221631.0,"Position":440.0,"HyperDash":false}]},{"StartTime":221741.0,"Objects":[{"StartTime":221741.0,"Position":472.0,"HyperDash":false}]},{"StartTime":221850.0,"Objects":[{"StartTime":221850.0,"Position":434.0,"HyperDash":false}]},{"StartTime":221960.0,"Objects":[{"StartTime":221960.0,"Position":357.0,"HyperDash":true}]},{"StartTime":222069.0,"Objects":[{"StartTime":222069.0,"Position":157.0,"HyperDash":false},{"StartTime":222178.0,"Position":88.06657,"HyperDash":true}]},{"StartTime":222288.0,"Objects":[{"StartTime":222288.0,"Position":289.0,"HyperDash":false},{"StartTime":222379.0,"Position":364.60202,"HyperDash":false},{"StartTime":222506.0,"Position":426.991669,"HyperDash":false}]},{"StartTime":222617.0,"Objects":[{"StartTime":222617.0,"Position":343.0,"HyperDash":true}]},{"StartTime":222726.0,"Objects":[{"StartTime":222726.0,"Position":109.0,"HyperDash":false},{"StartTime":222817.0,"Position":84.0503159,"HyperDash":false},{"StartTime":222944.0,"Position":116.766586,"HyperDash":false}]},{"StartTime":223055.0,"Objects":[{"StartTime":223055.0,"Position":207.0,"HyperDash":false}]},{"StartTime":223164.0,"Objects":[{"StartTime":223164.0,"Position":117.0,"HyperDash":false},{"StartTime":223273.0,"Position":114.6221,"HyperDash":true}]},{"StartTime":223383.0,"Objects":[{"StartTime":223383.0,"Position":315.0,"HyperDash":false},{"StartTime":223492.0,"Position":383.995117,"HyperDash":true}]},{"StartTime":223602.0,"Objects":[{"StartTime":223602.0,"Position":148.0,"HyperDash":false},{"StartTime":223711.0,"Position":145.971466,"HyperDash":false}]},{"StartTime":223821.0,"Objects":[{"StartTime":223821.0,"Position":256.0,"HyperDash":false},{"StartTime":223930.0,"Position":325.0,"HyperDash":true}]},{"StartTime":224040.0,"Objects":[{"StartTime":224040.0,"Position":123.0,"HyperDash":false},{"StartTime":224149.0,"Position":192.0,"HyperDash":true}]},{"StartTime":224259.0,"Objects":[{"StartTime":224259.0,"Position":393.0,"HyperDash":false},{"StartTime":224368.0,"Position":393.896027,"HyperDash":true}]},{"StartTime":224478.0,"Objects":[{"StartTime":224478.0,"Position":158.0,"HyperDash":false}]},{"StartTime":224588.0,"Objects":[{"StartTime":224588.0,"Position":82.0,"HyperDash":false}]},{"StartTime":224697.0,"Objects":[{"StartTime":224697.0,"Position":44.0,"HyperDash":false}]},{"StartTime":224807.0,"Objects":[{"StartTime":224807.0,"Position":86.0,"HyperDash":true}]},{"StartTime":224916.0,"Objects":[{"StartTime":224916.0,"Position":285.0,"HyperDash":false},{"StartTime":225025.0,"Position":353.996,"HyperDash":true}]},{"StartTime":225135.0,"Objects":[{"StartTime":225135.0,"Position":83.0,"HyperDash":false}]},{"StartTime":225244.0,"Objects":[{"StartTime":225244.0,"Position":41.0,"HyperDash":false}]},{"StartTime":225354.0,"Objects":[{"StartTime":225354.0,"Position":82.0,"HyperDash":false}]},{"StartTime":225463.0,"Objects":[{"StartTime":225463.0,"Position":157.0,"HyperDash":false}]},{"StartTime":225573.0,"Objects":[{"StartTime":225573.0,"Position":267.0,"HyperDash":false},{"StartTime":225682.0,"Position":267.0,"HyperDash":true}]},{"StartTime":225792.0,"Objects":[{"StartTime":225792.0,"Position":65.0,"HyperDash":false},{"StartTime":225901.0,"Position":64.19773,"HyperDash":false}]},{"StartTime":226011.0,"Objects":[{"StartTime":226011.0,"Position":154.0,"HyperDash":false}]},{"StartTime":226120.0,"Objects":[{"StartTime":226120.0,"Position":64.0,"HyperDash":true}]},{"StartTime":226230.0,"Objects":[{"StartTime":226230.0,"Position":299.0,"HyperDash":false}]},{"StartTime":226449.0,"Objects":[{"StartTime":226449.0,"Position":105.0,"HyperDash":false},{"StartTime":226558.0,"Position":104.115456,"HyperDash":true}]},{"StartTime":226668.0,"Objects":[{"StartTime":226668.0,"Position":305.0,"HyperDash":true}]},{"StartTime":226777.0,"Objects":[{"StartTime":226777.0,"Position":104.0,"HyperDash":false},{"StartTime":226886.0,"Position":35.0059738,"HyperDash":true}]},{"StartTime":227106.0,"Objects":[{"StartTime":227106.0,"Position":383.0,"HyperDash":false},{"StartTime":227160.0,"Position":350.499268,"HyperDash":false},{"StartTime":227215.0,"Position":324.281738,"HyperDash":false},{"StartTime":227269.0,"Position":266.25296,"HyperDash":false},{"StartTime":227324.0,"Position":247.835876,"HyperDash":false},{"StartTime":227378.0,"Position":218.959808,"HyperDash":false},{"StartTime":227433.0,"Position":168.075058,"HyperDash":false},{"StartTime":227488.0,"Position":136.432785,"HyperDash":false},{"StartTime":227543.0,"Position":126.625404,"HyperDash":false},{"StartTime":227597.0,"Position":101.627563,"HyperDash":false},{"StartTime":227652.0,"Position":86.03102,"HyperDash":false},{"StartTime":227707.0,"Position":60.6709824,"HyperDash":false},{"StartTime":227762.0,"Position":57.8545761,"HyperDash":false},{"StartTime":227853.0,"Position":59.5702324,"HyperDash":false},{"StartTime":227981.0,"Position":63.0289955,"HyperDash":false}]}]} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3524302.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3524302.osu new file mode 100644 index 0000000000..36f52c4ae2 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3524302.osu @@ -0,0 +1,889 @@ +osu file format v14 + +[General] +StackLeniency: 0.7 +Mode: 2 + +[Difficulty] +HPDrainRate:6 +CircleSize:4 +OverallDifficulty:9.2 +ApproachRate:9.2 +SliderMultiplier:2.76 +SliderTickRate:2 + +[Events] +//Background and Video events +//Break Periods +2,88036,100842 +2,172123,178142 +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Layer 4 (Overlay) +//Storyboard Sound Samples + +[TimingPoints] +245,437.956204379562,4,2,1,30,1,0 +17763,-100,4,2,1,65,0,0 +31777,-100,4,2,1,70,0,0 +45792,-100,4,2,1,75,0,0 +52799,-100,4,2,1,80,0,0 +59807,-100,4,2,1,85,0,1 +86960,-90.9090909090909,4,2,1,80,0,1 +87836,-100,4,2,1,75,0,0 +101850,-100,4,2,1,65,0,0 +115865,-100,4,2,1,70,0,0 +129880,-100,4,2,1,75,0,0 +136887,-100,4,2,1,80,0,0 +140828,-100,4,2,1,60,0,0 +141485,-100,4,2,1,65,0,0 +141704,-100,4,2,1,70,0,0 +141923,-100,4,2,1,75,0,0 +142142,-100,4,2,1,80,0,0 +143894,-100,4,2,1,85,0,1 +171923,-100,4,2,1,75,0,0 +178931,-100,4,2,1,75,0,0 +192945,-76.9230769230769,4,2,1,85,0,1 +193383,-47.6190476190476,4,2,1,85,0,1 +193821,-76.9230769230769,4,2,1,85,0,1 +194259,-47.6190476190476,4,2,1,85,0,1 +194697,-100,4,2,1,85,0,1 +195135,-47.6190476190476,4,2,1,85,0,1 +195573,-100,4,2,1,85,0,1 +196011,-47.6190476190476,4,2,1,85,0,1 +196449,-100,4,2,1,85,0,1 +196668,-47.6190476190476,4,2,1,85,0,1 +198639,-71.4285714285714,4,2,2,85,0,1 +199077,-100,4,2,2,85,0,1 +199296,-76.9230769230769,4,2,1,85,0,1 +199953,-100,4,2,1,80,0,0 +201704,-100,4,2,1,85,0,1 +227982,-100,4,2,1,30,0,0 + +[HitObjects] +256,192,14259,12,0,17325,0:0:0:0: +166,339,17763,6,0,L|164:200,1,138,2|0,1:2|0:0,0:0:0:0: +358,201,18201,2,0,L|360:62,1,138,0|0,1:2|0:0,0:0:0:0: +165,63,18639,2,0,L|18:65,1,138,2|2,1:2|0:0,0:0:0:0: +137,64,18967,2,0,L|208:65,1,69,2|0,0:0|1:2,0:0:0:0: +25,64,19296,1,2,0:0:0:0: +314,64,19515,5,2,1:2:0:0: +350,130,19624,1,0,0:0:0:0: +312,196,19734,1,2,0:0:0:0: +118,196,19953,2,0,L|259:197,1,138,2|2,1:2|0:0,0:0:0:0: +449,196,20390,2,0,L|452:342,1,138,2|2,1:2|0:0,0:0:0:0: +271,333,20828,1,2,1:2:0:0: +451,333,21047,1,2,0:0:0:0: +133,333,21266,5,2,1:2:0:0: +97,265,21376,1,0,0:0:0:0: +136,200,21485,1,0,0:0:0:0: +329,200,21704,2,0,L|331:57,1,138,0|0,1:2|0:0,0:0:0:0: +136,62,22142,2,0,L|297:62,1,138,2|2,1:2|0:0,0:0:0:0: +385,62,22471,2,0,L|294:62,1,69,2|0,0:0|1:2,0:0:0:0: +136,62,22799,1,2,0:0:0:0: +425,62,23018,5,2,1:2:0:0: +461,128,23128,1,0,0:0:0:0: +421,192,23237,1,2,0:0:0:0: +227,192,23456,2,0,L|224:332,1,138,2|2,1:2|0:0,0:0:0:0: +404,329,23894,1,2,1:2:0:0: +224,329,24113,1,2,0:0:0:0: +417,329,24332,2,0,L|419:187,1,138,2|2,1:2|0:0,0:0:0:0: +341,191,24661,1,2,0:0:0:0: +107,191,24770,5,2,1:2:0:0: +69,124,24880,1,0,0:0:0:0: +111,61,24989,1,0,0:0:0:0: +304,61,25208,2,0,L|306:200,1,138,0|0,1:2|0:0,0:0:0:0: +111,198,25646,2,0,L|110:337,1,138,2|0,1:2|0:0,0:0:0:0: +220,335,25974,2,0,L|292:335,1,69,2|0,0:0|1:2,0:0:0:0: +108,335,26303,1,2,0:0:0:0: +397,335,26522,5,2,1:2:0:0: +432,268,26631,1,0,0:0:0:0: +395,200,26741,1,2,0:0:0:0: +215,200,26960,1,2,1:2:0:0: +395,200,27179,1,2,0:0:0:0: +201,200,27398,2,0,L|200:59,1,138,2|0,1:2|0:0,0:0:0:0: +380,62,27836,1,0,1:2:0:0: +200,62,28055,1,2,0:0:0:0: +131,62,28164,1,2,0:0:0:0: +365,62,28274,6,0,P|452:120|350:202,1,276,2|0,1:2|0:0,0:0:0:0: +170,202,28931,1,2,0:0:0:0: +349,202,29150,2,0,P|415:208|474:382,1,276,2|0,0:0|0:0,0:0:0:0: +114,381,30026,5,0,1:2:0:0: +292,381,30244,1,8,0:3:0:0: +114,381,30463,2,0,L|113:240,1,138,8|0,0:3|0:0,0:0:0:0: +307,243,30901,2,0,L|309:102,1,138,4|0,0:3|0:0,0:0:0:0: +197,105,31230,2,0,L|129:106,1,69,4|0,0:3|1:2,0:0:0:0: +417,106,31558,2,0,L|418:180,1,69,0|0,3:2|0:0,0:0:0:0: +148,174,31777,5,2,1:2:0:0: +78,174,31887,1,0,0:0:0:0: +148,174,31996,1,0,0:0:0:0: +341,174,32215,2,0,P|354:234|340:315,1,138,0|0,3:2|0:0,0:0:0:0: +265,311,32544,1,0,1:2:0:0: +155,311,32653,2,0,L|-7:310,1,138,2|2,0:0|1:2,0:0:0:0: +93,310,32982,1,2,0:0:0:0: +292,310,33091,1,0,3:2:0:0: +112,310,33310,2,0,L|110:239,1,69,2|0,0:0|0:0,0:0:0:0: +327,242,33529,5,2,1:2:0:0: +396,242,33639,1,0,0:0:0:0: +327,242,33748,1,0,0:0:0:0: +133,242,33967,2,0,L|131:104,1,138,2|2,3:2|0:0,0:0:0:0: +207,104,34296,1,0,1:2:0:0: +316,104,34405,2,0,L|170:104,1,138,2|2,0:0|1:2,0:0:0:0: +254,104,34734,1,0,0:0:0:0: +453,104,34843,2,0,P|466:169|455:240,1,138,2|2,3:2|0:0,0:0:0:0: +378,239,35172,1,2,0:0:0:0: +145,239,35281,5,2,1:2:0:0: +76,239,35390,1,0,0:0:0:0: +145,239,35500,1,0,0:0:0:0: +338,239,35719,2,0,L|340:102,1,138,0|0,3:2|0:0,0:0:0:0: +263,101,36047,1,0,1:2:0:0: +165,101,36157,1,2,0:0:0:0: +263,101,36266,1,2,0:0:0:0: +339,101,36376,1,2,1:2:0:0: +263,101,36485,1,2,0:0:0:0: +61,101,36595,2,0,P|45:160|61:238,1,138,0|2,3:2|0:0,0:0:0:0: +135,234,36923,1,0,0:0:0:0: +371,233,37033,5,2,1:2:0:0: +439,233,37142,1,0,0:0:0:0: +371,233,37252,1,0,0:0:0:0: +177,233,37471,2,0,L|318:233,1,138,2|0,3:2|0:0,0:0:0:0: +238,233,37799,1,0,1:2:0:0: +127,233,37909,2,0,L|125:94,1,138,2|2,0:0|1:2,0:0:0:0: +201,95,38237,1,0,0:0:0:0: +402,95,38347,2,0,P|410:157|404:236,1,138,2|2,3:2|0:0,0:0:0:0: +328,232,38675,1,0,0:0:0:0: +92,233,38785,5,2,1:2:0:0: +23,233,38894,1,0,0:0:0:0: +92,233,39004,1,0,0:0:0:0: +285,233,39223,2,0,L|430:233,1,138,0|0,3:2|0:0,0:0:0:0: +346,233,39551,1,0,1:2:0:0: +235,233,39661,2,0,L|234:160,1,69,2|2,0:0|0:0,0:0:0:0: +344,164,39880,2,0,L|346:93,1,69,2|2,1:2|0:0,0:0:0:0: +144,95,40099,2,0,L|5:95,1,138,0|2,3:2|0:0,0:0:0:0: +82,95,40427,1,0,0:0:0:0: +315,95,40536,5,2,1:2:0:0: +384,95,40646,1,0,0:0:0:0: +315,95,40755,1,2,0:0:0:0: +121,95,40974,2,0,L|119:234,1,138,2|2,3:2|0:0,0:0:0:0: +195,232,41303,1,0,1:2:0:0: +394,232,41412,1,2,0:0:0:0: +214,232,41631,1,0,1:2:0:0: +144,232,41741,1,0,0:0:0:0: +214,232,41850,1,0,3:2:0:0: +407,232,42069,2,0,L|492:232,1,69,2|2,0:0|0:0,0:0:0:0: +240,232,42288,5,2,1:2:0:0: +170,232,42398,1,0,0:0:0:0: +240,232,42507,1,0,0:0:0:0: +419,232,42726,1,2,3:2:0:0: +129,232,42945,2,0,L|128:161,1,69,2|0,0:0|1:2,0:0:0:0: +238,163,43164,2,0,L|380:164,1,138,2|2,0:0|1:2,0:0:0:0: +299,163,43493,1,0,0:0:0:0: +195,163,43602,1,2,3:2:0:0: +374,163,43821,1,2,0:0:0:0: +376,93,43931,1,0,0:0:0:0: +108,163,44040,5,6,1:2:0:0: +106,93,44150,1,2,0:0:0:0: +209,93,44259,1,0,3:2:0:0: +388,93,44478,1,0,3:2:0:0: +195,93,44697,1,2,1:2:0:0: +484,93,44916,1,8,0:3:0:0: +407,93,45026,1,8,0:3:0:0: +213,93,45244,1,8,0:3:0:0: +316,93,45354,2,0,L|460:94,1,138,2|4,0:0|0:3,0:0:0:0: +103,93,45792,6,0,P|17:149|121:239,1,276,6|0,1:2|0:0,0:0:0:0: +294,241,46449,2,0,L|37:136,1,276,2|2,0:0|0:0,0:0:0:0: +204,136,47106,1,2,0:0:0:0: +38,136,47325,1,2,0:0:0:0: +355,136,47544,6,0,P|438:178|341:272,1,276,6|0,1:2|0:0,0:0:0:0: +173,271,48201,1,0,0:0:0:0: +338,271,48420,2,0,P|355:199|200:122,1,276,2|2,0:0|0:0,0:0:0:0: +369,120,49077,1,2,0:0:0:0: +51,120,49296,6,0,L|49:261,1,138,6|2,1:2|0:0,0:0:0:0: +229,257,49734,2,0,L|371:256,1,138,2|2,0:0|0:0,0:0:0:0: +186,256,50172,2,0,L|47:255,1,138,2|2,0:0|0:0,0:0:0:0: +227,255,50609,1,2,0:0:0:0: +47,255,50828,1,2,0:0:0:0: +347,254,51047,6,0,P|438:243|478:85,1,276,6|0,1:2|0:0,0:0:0:0: +118,84,51923,2,0,P|103:147|121:221,1,138,2|2,3:2|3:2,0:0:0:0: +313,217,52361,1,8,0:3:0:0: +119,217,52580,1,8,0:3:0:0: +436,217,52799,6,0,L|127:184,1,276,2|2,1:2|3:2,0:0:0:0: +452,187,53456,1,2,0:0:0:0: +489,128,53566,1,0,1:2:0:0: +454,68,53675,1,0,0:0:0:0: +274,68,53894,1,2,1:2:0:0: +454,68,54113,2,0,L|301:69,1,138,2|2,3:2|0:0,0:0:0:0: +24,68,54551,6,0,L|306:94,1,276,0|0,1:2|3:2,0:0:0:0: +104,93,55208,1,0,0:0:0:0: +62,93,55317,1,0,1:2:0:0: +104,93,55427,1,2,0:0:0:0: +393,93,55646,2,0,L|266:151,1,138,2|0,1:2|3:2,0:0:0:0: +87,150,56084,1,2,0:0:0:0: +432,116,56303,6,0,P|308:196|181:218,1,276,6|2,1:2|3:2,0:0:0:0: +365,218,56960,1,2,1:2:0:0: +75,218,57179,2,0,L|232:214,1,138,2|2,3:2|1:2,0:0:0:0: +407,214,57617,2,0,L|410:69,1,138,2|2,3:2|0:0,0:0:0:0: +118,76,58055,6,0,L|335:76,2,207,2|2|2,1:2|0:0|0:0,0:0:0:0: +312,76,58931,2,0,P|275:213|34:256,1,414,2|0,0:0|0:0,0:0:0:0: +380,255,59807,6,0,P|404:186|380:128,1,138,6|0,1:2|0:0,0:0:0:0: +290,128,60135,1,2,0:0:0:0: +380,128,60244,2,0,L|382:52,1,69,0|0,3:2|0:0,0:0:0:0: +180,59,60463,2,0,L|96:59,1,69,2|0,0:0|1:2,0:0:0:0: +346,59,60682,6,0,L|346:144,1,69,2|2,0:0|0:0,0:0:0:0: +144,128,60901,1,2,1:2:0:0: +345,128,61011,1,2,0:0:0:0: +441,128,61120,2,0,P|475:194|424:240,1,138,0|2,3:2|0:0,0:0:0:0: +355,236,61449,1,0,0:0:0:0: +121,236,61558,6,0,L|120:164,1,69,2|2,1:2|0:0,0:0:0:0: +321,167,61777,1,2,0:0:0:0: +120,167,61887,1,2,0:0:0:0: +23,167,61996,2,0,L|177:166,1,138,0|2,3:2|0:0,0:0:0:0: +63,166,62325,1,0,1:2:0:0: +296,166,62434,6,0,L|297:95,1,69,2|2,0:0|0:0,0:0:0:0: +199,97,62653,1,0,1:2:0:0: +400,97,62763,1,2,0:0:0:0: +303,97,62872,2,0,P|293:153|354:193,1,138,0|2,3:2|0:0,0:0:0:0: +438,192,63201,1,0,0:0:0:0: +204,192,63310,6,0,P|133:187|94:138,1,138,2|0,1:2|0:0,0:0:0:0: +184,137,63639,1,2,0:0:0:0: +93,137,63748,2,0,L|92:53,1,69,0|0,3:2|0:0,0:0:0:0: +293,68,63967,2,0,L|294:143,1,69,2|0,0:0|1:2,0:0:0:0: +93,137,64186,5,2,0:0:0:0: +293,136,64296,2,0,L|361:136,1,69,2|0,0:0|1:2,0:0:0:0: +160,136,64515,1,2,0:0:0:0: +63,136,64624,2,0,P|29:83|79:30,1,138,0|2,3:2|0:0,0:0:0:0: +154,31,64953,1,0,0:0:0:0: +387,31,65062,6,0,L|319:30,1,69,2|2,1:2|0:0,0:0:0:0: +116,29,65281,1,2,0:0:0:0: +318,29,65390,1,2,0:0:0:0: +415,29,65500,2,0,P|452:91|413:129,1,138,0|2,3:2|0:0,0:0:0:0: +315,129,65828,1,0,1:2:0:0: +79,129,65938,6,0,L|78:59,1,69,2|2,0:0|0:0,0:0:0:0: +175,60,66157,1,0,1:2:0:0: +374,60,66266,1,2,0:0:0:0: +276,60,66376,2,0,L|424:61,1,138,0|2,3:2|0:0,0:0:0:0: +331,60,66704,1,0,0:0:0:0: +60,60,66814,6,0,P|28:123|66:176,1,138,6|0,1:2|0:0,0:0:0:0: +151,173,67142,1,2,0:0:0:0: +61,173,67252,1,0,3:2:0:0: +378,173,67471,5,2,1:2:0:0: +422,111,67580,1,0,0:0:0:0: +381,46,67690,1,0,0:0:0:0: +305,44,67799,1,0,0:0:0:0: +194,44,67909,2,0,L|193:121,1,69,0|0,1:2|0:0,0:0:0:0: +428,112,68128,2,0,L|288:112,1,138,2|2,3:2|0:0,0:0:0:0: +373,112,68456,1,0,0:0:0:0: +137,112,68566,6,0,L|135:183,1,69,2|0,1:2|0:0,0:0:0:0: +245,181,68785,2,0,L|246:258,1,69,2|0,0:0|0:0,0:0:0:0: +44,249,69004,2,0,L|191:248,1,138,2|2,3:2|1:2,0:0:0:0: +98,248,69332,1,0,0:0:0:0: +333,248,69442,6,0,L|335:170,1,69,2|0,1:2|0:0,0:0:0:0: +133,179,69661,1,2,1:2:0:0: +326,179,69880,1,2,3:2:0:0: +133,179,70099,2,0,L|131:251,1,69,2|0,0:0|0:0,0:0:0:0: +398,247,70317,6,0,L|106:250,1,276,6|2,1:2|3:2,0:0:0:0: +468,249,70974,2,0,L|177:250,1,276,6|0,1:2|1:2,0:0:0:0: +483,249,71631,2,0,L|334:249,1,138,2|2,3:2|0:0,0:0:0:0: +26,249,72069,6,0,L|243:249,2,207,6|8|8,1:2|0:3|0:3,0:0:0:0: +344,249,72945,2,0,P|434:201|334:113,1,276,6|0,1:2|3:2,0:0:0:0: +247,111,73493,1,0,3:2:0:0: +338,111,73602,1,0,3:2:0:0: +102,111,73712,1,0,3:2:0:0: +338,111,73821,6,0,P|372:156|334:220,1,138,6|0,1:2|0:0,0:0:0:0: +244,219,74150,1,2,0:0:0:0: +334,219,74259,2,0,L|335:147,1,69,0|0,3:2|0:0,0:0:0:0: +133,150,74478,2,0,L|131:71,1,69,2|0,0:0|1:2,0:0:0:0: +366,81,74697,6,0,L|367:158,1,69,2|2,0:0|0:0,0:0:0:0: +165,149,74916,1,2,1:2:0:0: +366,149,75026,1,2,0:0:0:0: +462,149,75135,2,0,L|296:149,1,138,0|2,3:2|0:0,0:0:0:0: +407,149,75463,1,0,0:0:0:0: +171,149,75573,6,0,L|169:233,1,69,2|2,1:2|0:0,0:0:0:0: +370,217,75792,1,2,0:0:0:0: +170,217,75901,1,2,0:0:0:0: +72,217,76011,2,0,P|46:151|98:97,1,138,0|2,3:2|0:0,0:0:0:0: +179,102,76339,1,0,1:2:0:0: +414,102,76449,6,0,L|491:102,1,69,2|2,0:0|0:0,0:0:0:0: +385,102,76668,1,0,1:2:0:0: +185,102,76777,1,2,0:0:0:0: +282,102,76887,2,0,L|442:101,1,138,0|2,3:2|0:0,0:0:0:0: +336,101,77215,1,0,0:0:0:0: +100,101,77325,6,0,P|75:169|105:227,1,138,2|0,1:2|0:0,0:0:0:0: +192,224,77653,1,2,0:0:0:0: +102,224,77763,2,0,L|100:301,1,69,0|0,3:2|0:0,0:0:0:0: +301,292,77982,2,0,L|394:292,1,69,2|0,0:0|1:2,0:0:0:0: +134,292,78201,6,0,L|133:221,1,69,2|2,0:0|0:0,0:0:0:0: +334,223,78420,1,2,1:2:0:0: +135,223,78529,1,2,0:0:0:0: +37,223,78639,2,0,P|21:160|69:106,1,138,0|2,3:2|0:0,0:0:0:0: +147,107,78967,1,0,0:0:0:0: +382,107,79077,6,0,L|384:175,1,69,2|0,1:2|0:0,0:0:0:0: +273,175,79296,2,0,L|271:243,1,69,2|0,1:2|0:0,0:0:0:0: +472,243,79515,2,0,L|474:315,1,69,2|0,3:2|0:0,0:0:0:0: +203,311,79734,6,0,L|132:312,1,69,6|0,1:2|0:0,0:0:0:0: +244,311,79953,2,0,L|317:311,1,69,2|0,0:0|0:0,0:0:0:0: +111,311,80172,2,0,L|108:242,1,69,8|0,2:3|0:0,0:0:0:0: +307,242,80390,2,0,L|385:242,1,69,8|0,2:3|0:0,0:0:0:0: +140,242,80609,2,0,L|69:242,1,69,4|0,2:3|0:0,0:0:0:0: +341,242,80828,6,0,L|495:242,1,138,6|0,1:2|0:0,0:0:0:0: +388,242,81157,1,2,0:0:0:0: +476,242,81266,1,0,3:2:0:0: +161,242,81485,5,2,1:2:0:0: +124,175,81595,1,0,0:0:0:0: +166,112,81704,1,0,0:0:0:0: +242,106,81814,1,0,0:0:0:0: +351,106,81923,2,0,L|352:37,1,69,0|0,1:2|0:0,0:0:0:0: +150,37,82142,1,2,3:2:0:0: +74,50,82252,1,0,0:0:0:0: +84,124,82361,1,0,0:0:0:0: +166,131,82471,1,0,0:0:0:0: +399,131,82580,5,2,1:2:0:0: +442,193,82690,1,0,0:0:0:0: +399,255,82799,1,0,0:0:0:0: +316,261,82909,1,2,0:0:0:0: +206,261,83018,2,0,L|204:185,1,69,0|0,3:2|0:0,0:0:0:0: +315,192,83237,2,0,L|316:121,1,69,2|0,1:2|0:0,0:0:0:0: +80,123,83456,6,0,L|78:47,1,69,2|0,1:2|0:0,0:0:0:0: +182,54,83675,1,2,1:2:0:0: +375,54,83894,1,2,3:2:0:0: +57,54,84113,1,2,0:0:0:0: +133,54,84223,1,0,0:0:0:0: +366,54,84332,5,2,1:2:0:0: +405,119,84442,1,0,0:0:0:0: +361,180,84551,1,0,0:0:0:0: +284,180,84661,1,0,0:0:0:0: +174,180,84770,2,0,L|172:256,1,69,0|0,3:2|0:0,0:0:0:0: +442,248,84989,5,6,1:2:0:0: +358,248,85099,1,0,0:0:0:0: +321,183,85208,1,0,0:0:0:0: +365,123,85317,1,0,0:0:0:0: +475,123,85427,2,0,L|476:48,1,69,0|0,1:2|0:0,0:0:0:0: +274,54,85646,2,0,L|273:131,1,69,0|0,3:2|0:0,0:0:0:0: +363,122,85865,1,0,0:0:0:0: +273,122,85974,1,0,0:0:0:0: +71,122,86084,6,0,L|70:210,1,69,0|0,1:2|0:0,0:0:0:0: +305,190,86303,2,0,L|305:270,1,69,8|0,0:3|0:0,0:0:0:0: +103,259,86522,1,0,3:2:0:0: +305,259,86631,2,0,L|388:258,1,69,8|2,0:3|0:0,0:0:0:0: +55,258,86960,2,0,P|215:211|49:153,1,455.400013897705,2|0,1:2|0:0,0:0:0:0: +398,117,87836,5,6,1:2:0:0: +77,106,101412,5,0,3:2:0:0: +435,106,101850,6,0,P|450:162|434:240,1,138,2|0,1:2|0:0,0:0:0:0: +240,239,102288,2,0,L|99:240,1,138,0|0,1:2|0:0,0:0:0:0: +296,239,102726,2,0,L|437:238,1,138,2|0,1:2|0:0,0:0:0:0: +322,238,103055,2,0,L|243:238,1,69,2|0,0:0|1:2,0:0:0:0: +433,238,103383,1,2,0:0:0:0: +145,242,103602,5,2,1:2:0:0: +228,242,103712,1,0,0:0:0:0: +283,242,103821,1,2,0:0:0:0: +89,242,104040,2,0,L|88:104,1,138,2|2,1:2|0:0,0:0:0:0: +268,104,104478,1,2,1:2:0:0: +88,104,104697,1,2,0:0:0:0: +281,104,104916,2,0,L|426:105,1,138,2|0,1:2|0:0,0:0:0:0: +129,104,105354,5,2,1:2:0:0: +211,104,105463,1,0,0:0:0:0: +266,104,105573,1,0,0:0:0:0: +72,104,105792,2,0,L|71:255,1,138,0|0,1:2|0:0,0:0:0:0: +265,241,106230,2,0,L|117:242,1,138,2|2,1:2|0:0,0:0:0:0: +237,241,106558,2,0,L|307:241,1,69,2|0,0:0|1:2,0:0:0:0: +126,240,106887,1,2,0:0:0:0: +415,240,107106,5,2,1:2:0:0: +332,240,107215,1,0,0:0:0:0: +276,240,107325,1,2,0:0:0:0: +469,240,107544,2,0,L|470:100,1,138,2|2,1:2|0:0,0:0:0:0: +289,102,107982,1,2,1:2:0:0: +469,102,108201,1,2,0:0:0:0: +275,102,108420,2,0,L|138:102,1,138,2|0,1:2|0:0,0:0:0:0: +428,102,108858,5,2,1:2:0:0: +345,102,108967,1,0,0:0:0:0: +289,102,109077,1,0,0:0:0:0: +482,102,109296,2,0,L|484:242,1,138,0|0,1:2|0:0,0:0:0:0: +291,239,109734,2,0,L|429:240,1,138,2|0,1:2|0:0,0:0:0:0: +318,239,110062,2,0,L|241:238,1,69,2|0,0:0|1:2,0:0:0:0: +428,239,110390,1,2,0:0:0:0: +138,239,110609,5,2,1:2:0:0: +215,239,110719,1,0,0:0:0:0: +277,239,110828,1,2,0:0:0:0: +83,239,111047,2,0,L|229:239,1,138,2|2,1:2|0:0,0:0:0:0: +26,239,111485,2,0,L|25:102,1,138,2|0,1:2|0:0,0:0:0:0: +205,101,111923,1,0,1:2:0:0: +25,101,112142,1,2,0:0:0:0: +314,101,112361,5,2,1:2:0:0: +230,101,112471,1,2,0:0:0:0: +314,101,112580,2,0,P|399:137|304:230,1,276,2|2,0:0|0:0,0:0:0:0: +109,229,113237,2,0,P|23:186|123:101,1,276,2|0,0:0|0:0,0:0:0:0: +482,100,114113,5,0,1:2:0:0: +288,100,114332,1,8,0:3:0:0: +482,100,114551,2,0,L|324:100,1,138,8|0,0:3|0:0,0:0:0:0: +149,100,114989,2,0,L|292:100,1,138,4|0,0:3|0:0,0:0:0:0: +397,100,115317,2,0,L|310:101,1,69,4|0,0:3|1:2,0:0:0:0: +133,100,115646,2,0,L|132:176,1,69,0|0,3:2|0:0,0:0:0:0: +367,168,115865,5,2,1:2:0:0: +284,168,115974,1,0,0:0:0:0: +228,168,116084,1,0,0:0:0:0: +421,168,116303,2,0,L|423:308,1,138,0|0,3:2|0:0,0:0:0:0: +346,305,116631,1,0,1:2:0:0: +235,305,116741,2,0,L|383:306,1,138,2|2,0:0|1:2,0:0:0:0: +296,305,117069,1,2,0:0:0:0: +94,305,117179,1,0,3:2:0:0: +273,305,117398,2,0,L|346:306,1,69,2|0,0:0|0:0,0:0:0:0: +129,304,117617,5,2,1:2:0:0: +60,304,117726,1,0,0:0:0:0: +131,304,117836,1,0,0:0:0:0: +324,304,118055,2,0,L|177:304,1,138,2|2,3:2|0:0,0:0:0:0: +262,304,118383,1,0,1:2:0:0: +372,304,118493,2,0,P|443:286|477:233,1,138,2|2,0:0|1:2,0:0:0:0: +400,234,118821,1,0,0:0:0:0: +198,234,118931,1,2,3:2:0:0: +391,234,119150,2,0,L|392:152,1,69,0|0,0:0|0:0,0:0:0:0: +156,165,119369,5,2,1:2:0:0: +238,165,119478,1,0,0:0:0:0: +293,165,119588,1,0,0:0:0:0: +99,165,119807,2,0,L|97:26,1,138,0|0,3:2|0:0,0:0:0:0: +174,27,120135,1,0,1:2:0:0: +283,27,120244,1,2,0:0:0:0: +333,79,120354,1,2,0:0:0:0: +283,27,120463,1,2,1:2:0:0: +185,27,120573,1,2,0:0:0:0: +384,27,120682,2,0,P|442:41|483:113,1,138,0|2,3:2|0:0,0:0:0:0: +412,104,121011,1,0,0:0:0:0: +178,104,121120,5,2,1:2:0:0: +108,104,121230,1,0,0:0:0:0: +178,104,121339,1,0,0:0:0:0: +371,104,121558,2,0,L|224:104,1,138,2|0,3:2|0:0,0:0:0:0: +309,104,121887,1,0,1:2:0:0: +418,104,121996,2,0,P|446:171|408:227,1,138,2|2,0:0|1:2,0:0:0:0: +337,222,122325,1,0,0:0:0:0: +137,222,122434,2,0,P|64:206|23:153,1,138,2|0,3:2|0:0,0:0:0:0: +102,159,122763,1,0,0:0:0:0: +335,159,122872,5,2,1:2:0:0: +251,159,122982,1,0,0:0:0:0: +196,159,123091,1,0,0:0:0:0: +389,159,123310,2,0,P|406:239|386:293,1,138,0|0,3:2|0:0,0:0:0:0: +312,290,123639,1,0,1:2:0:0: +202,290,123748,2,0,P|128:246|123:199,1,138,2|2,0:0|1:2,0:0:0:0: +200,162,124077,1,2,0:0:0:0: +399,161,124186,1,0,3:2:0:0: +219,92,124405,2,0,L|148:92,1,69,2|0,0:0|0:0,0:0:0:0: +386,227,124624,5,2,1:2:0:0: +455,227,124734,1,0,0:0:0:0: +386,227,124843,1,2,0:0:0:0: +192,227,125062,2,0,P|106:213|67:181,1,138,2|2,3:2|0:0,0:0:0:0: +144,182,125390,1,0,1:2:0:0: +345,182,125500,2,0,P|431:168|470:136,1,138,2|0,0:0|1:2,0:0:0:0: +393,137,125828,1,0,0:0:0:0: +282,137,125938,1,0,3:2:0:0: +475,137,126157,2,0,L|476:213,1,69,2|0,0:0|0:0,0:0:0:0: +240,205,126376,5,2,1:2:0:0: +322,205,126485,1,0,0:0:0:0: +377,205,126595,1,0,0:0:0:0: +183,205,126814,1,2,3:2:0:0: +472,205,127033,1,2,0:0:0:0: +389,205,127142,1,0,1:2:0:0: +333,205,127252,1,0,0:0:0:0: +153,205,127471,2,0,L|152:131,1,69,2|0,1:2|0:0,0:0:0:0: +256,136,127690,1,2,3:2:0:0: +76,136,127909,1,2,0:0:0:0: +421,136,128128,5,6,1:2:0:0: +423,67,128237,1,2,0:0:0:0: +319,67,128347,1,2,3:2:0:0: +139,67,128566,1,2,3:2:0:0: +332,67,128785,1,2,1:2:0:0: +42,67,129004,1,8,0:3:0:0: +111,67,129113,1,8,0:3:0:0: +304,67,129332,2,0,L|72:67,1,207,8|4,0:3|0:3,0:0:0:0: +408,67,129880,6,0,P|490:129|379:199,1,276,6|0,1:2|0:0,0:0:0:0: +188,200,130536,2,0,L|483:200,1,276,2|2,0:0|0:0,0:0:0:0: +283,200,131193,1,2,0:0:0:0: +463,200,131412,1,2,0:0:0:0: +145,200,131631,6,0,P|59:138|164:60,1,276,6|0,1:2|0:0,0:0:0:0: +342,59,132288,1,0,0:0:0:0: +148,59,132507,2,0,L|147:214,1,138,2|2,0:0|0:0,0:0:0:0: +327,196,132945,1,2,0:0:0:0: +147,196,133164,1,2,0:0:0:0: +464,196,133383,6,0,P|469:249|351:316,1,207,6|0,1:2|0:0,0:0:0:0: +240,316,133821,2,0,P|354:311|391:173,1,276,2|2,0:0|0:0,0:0:0:0: +196,172,134478,2,0,L|197:33,1,138,2|2,0:0|0:0,0:0:0:0: +391,34,134916,1,2,0:0:0:0: +73,34,135135,6,0,B|188:112|188:112|68:30,1,276,6|0,1:2|0:0,0:0:0:0: +434,34,136011,2,0,L|435:174,1,138,2|2,3:2|3:2,0:0:0:0: +227,171,136449,1,8,0:3:0:0: +434,171,136668,1,8,0:3:0:0: +116,171,136887,6,0,L|412:171,1,276,2|2,1:2|3:2,0:0:0:0: +100,171,137544,1,2,0:0:0:0: +182,171,137653,1,0,1:2:0:0: +242,171,137763,1,0,0:0:0:0: +62,171,137982,1,2,1:2:0:0: +241,171,138201,2,0,L|88:169,1,138,2|2,3:2|0:0,0:0:0:0: +421,169,138639,6,0,L|128:168,1,276,0|0,1:2|3:2,0:0:0:0: +339,168,139296,2,0,L|340:90,1,69,0|0,0:0|1:2,0:0:0:0: +235,99,139515,1,2,0:0:0:0: +55,99,139734,1,2,1:2:0:0: +344,99,139953,2,0,L|489:98,1,138,0|2,3:2|0:0,0:0:0:0: +136,98,140390,6,0,L|135:242,1,138,6|2,1:2|0:0,0:0:0:0: +328,235,140828,1,2,3:2:0:0: +135,235,141047,1,2,3:2:0:0: +342,235,141266,1,2,3:2:0:0: +493,235,141485,1,2,3:2:0:0: +299,235,141704,1,2,3:2:0:0: +91,235,141923,1,2,3:2:0:0: +380,235,142142,6,0,L|155:232,2,207,2|2|2,1:2|0:0|0:0,0:0:0:0: +185,235,143018,2,0,P|347:232|428:19,1,414,2|0,0:0|0:0,0:0:0:0: +82,21,143894,6,0,P|50:85|84:135,1,138,6|0,1:2|0:0,0:0:0:0: +174,134,144223,1,2,0:0:0:0: +84,134,144332,2,0,L|83:208,1,69,0|0,3:2|0:0,0:0:0:0: +284,202,144551,2,0,L|368:202,1,69,2|0,0:0|1:2,0:0:0:0: +117,202,144770,6,0,L|46:202,1,69,2|2,0:0|0:0,0:0:0:0: +249,202,144989,1,2,1:2:0:0: +48,202,145099,1,2,0:0:0:0: +144,202,145208,2,0,P|180:157|139:100,1,138,0|2,3:2|0:0,0:0:0:0: +55,99,145536,1,0,0:0:0:0: +290,99,145646,6,0,L|370:98,1,69,2|2,1:2|0:0,0:0:0:0: +157,98,145865,1,2,0:0:0:0: +356,98,145974,1,2,0:0:0:0: +453,98,146084,2,0,L|277:98,1,138,0|2,3:2|0:0,0:0:0:0: +412,98,146412,1,0,1:2:0:0: +176,98,146522,5,2,0:0:0:0: +272,98,146631,2,0,L|273:174,1,69,2|0,0:0|1:2,0:0:0:0: +71,166,146850,1,2,0:0:0:0: +168,166,146960,2,0,L|27:166,1,138,0|2,3:2|0:0,0:0:0:0: +113,166,147288,1,0,0:0:0:0: +348,166,147398,6,0,P|385:115|346:62,1,138,2|0,1:2|0:0,0:0:0:0: +255,61,147726,1,2,0:0:0:0: +345,61,147836,2,0,L|347:129,1,69,0|0,3:2|0:0,0:0:0:0: +145,129,148055,1,2,0:0:0:0: +76,129,148164,1,0,1:2:0:0: +280,97,148274,6,0,L|360:97,1,69,2|2,0:0|0:0,0:0:0:0: +147,97,148493,1,2,1:2:0:0: +346,97,148602,1,2,0:0:0:0: +248,97,148712,2,0,L|103:97,1,138,0|2,3:2|0:0,0:0:0:0: +193,97,149040,1,0,0:0:0:0: +428,97,149150,6,0,P|459:168|420:215,1,138,2|0,1:2|0:0,0:0:0:0: +226,211,149478,1,2,0:0:0:0: +323,211,149588,2,0,L|466:211,1,138,0|2,3:2|0:0,0:0:0:0: +377,211,149916,1,0,1:2:0:0: +141,211,150026,5,2,0:0:0:0: +237,211,150135,2,0,L|239:139,1,69,2|0,0:0|1:2,0:0:0:0: +37,142,150354,1,2,0:0:0:0: +133,142,150463,2,0,P|166:75|119:40,1,138,0|2,3:2|0:0,0:0:0:0: +42,40,150792,1,0,0:0:0:0: +309,40,150901,6,0,L|465:40,1,138,6|0,1:2|0:0,0:0:0:0: +356,40,151230,1,2,0:0:0:0: +445,40,151339,1,0,3:2:0:0: +127,40,151558,5,2,1:2:0:0: +203,45,151668,1,0,0:0:0:0: +239,111,151777,1,0,0:0:0:0: +196,174,151887,1,0,0:0:0:0: +86,174,151996,2,0,L|84:252,1,69,0|0,1:2|0:0,0:0:0:0: +285,242,152215,2,0,L|144:241,1,138,2|2,3:2|0:0,0:0:0:0: +230,241,152544,1,0,0:0:0:0: +463,241,152653,6,0,L|392:240,1,69,0|0,1:2|0:0,0:0:0:0: +284,242,152872,2,0,L|282:164,1,69,2|0,0:0|0:0,0:0:0:0: +483,173,153091,2,0,L|336:172,1,138,2|2,3:2|1:2,0:0:0:0: +428,172,153420,1,0,0:0:0:0: +227,171,153529,6,0,L|226:93,1,69,2|0,1:2|0:0,0:0:0:0: +323,102,153748,1,2,1:2:0:0: +33,102,153967,2,0,L|30:248,1,138,2|2,3:2|0:0,0:0:0:0: +114,239,154296,1,0,0:0:0:0: +381,239,154405,6,0,L|99:237,1,276,6|2,1:2|3:2,0:0:0:0: +451,237,155062,2,0,P|488:148|355:78,1,276,6|0,1:2|1:2,0:0:0:0: +22,80,155719,2,0,L|177:81,1,138,2|2,3:2|0:0,0:0:0:0: +478,80,156157,6,0,L|268:81,2,207,6|8|8,1:2|0:3|0:3,0:0:0:0: +159,80,157033,2,0,P|66:140|166:218,1,276,6|0,1:2|3:2,0:0:0:0: +254,218,157580,1,0,3:2:0:0: +163,218,157690,1,0,3:2:0:0: +396,218,157799,1,0,3:2:0:0: +163,218,157909,6,0,P|132:155|167:100,1,138,6|0,1:2|0:0,0:0:0:0: +255,100,158237,1,2,0:0:0:0: +164,100,158347,2,0,L|162:174,1,69,0|0,3:2|0:0,0:0:0:0: +363,168,158566,2,0,L|364:243,1,69,2|0,0:0|1:2,0:0:0:0: +128,236,158785,6,0,L|208:237,1,69,2|2,0:0|0:0,0:0:0:0: +398,236,159004,1,2,1:2:0:0: +198,236,159113,1,2,0:0:0:0: +100,236,159223,2,0,P|73:178|105:116,1,138,0|2,3:2|0:0,0:0:0:0: +187,116,159551,1,0,0:0:0:0: +422,116,159661,6,0,L|352:115,1,69,2|0,1:2|0:0,0:0:0:0: +151,115,159880,1,2,0:0:0:0: +350,115,159989,1,2,0:0:0:0: +254,115,160099,2,0,L|426:115,1,138,0|2,3:2|0:0,0:0:0:0: +296,115,160427,1,0,1:2:0:0: +62,115,160536,6,0,L|61:188,1,69,2|0,0:0|0:0,0:0:0:0: +171,183,160755,2,0,L|250:183,1,69,2|0,1:2|0:0,0:0:0:0: +441,183,160974,2,0,P|470:243|434:305,1,138,2|2,3:2|0:0,0:0:0:0: +354,301,161303,1,0,0:0:0:0: +120,301,161412,6,0,L|271:301,1,138,2|0,1:2|0:0,0:0:0:0: +167,301,161741,1,2,0:0:0:0: +256,301,161850,2,0,L|257:222,1,69,0|0,3:2|0:0,0:0:0:0: +55,232,162069,2,0,L|53:155,1,69,2|0,0:0|1:2,0:0:0:0: +288,163,162288,6,0,L|363:163,1,69,2|0,0:0|0:0,0:0:0:0: +155,163,162507,1,2,1:2:0:0: +356,163,162617,1,2,0:0:0:0: +452,163,162726,2,0,P|475:235|443:293,1,138,0|2,3:2|0:0,0:0:0:0: +364,287,163055,1,0,0:0:0:0: +130,287,163164,6,0,L|128:209,1,69,2|0,1:2|0:0,0:0:0:0: +239,218,163383,2,0,L|241:146,1,69,2|0,1:2|0:0,0:0:0:0: +39,149,163602,2,0,L|120:149,1,69,2|0,3:2|0:0,0:0:0:0: +378,149,163821,6,0,L|379:81,1,69,6|0,1:2|0:0,0:0:0:0: +268,80,164040,2,0,L|172:80,1,69,2|0,0:0|0:0,0:0:0:0: +400,80,164259,2,0,L|402:153,1,69,8|0,2:3|0:0,0:0:0:0: +200,148,164478,2,0,L|112:148,1,69,8|0,2:3|0:0,0:0:0:0: +366,148,164697,2,0,L|453:149,1,69,4|0,2:3|0:0,0:0:0:0: +164,148,164916,6,0,L|25:149,1,138,6|0,1:2|0:0,0:0:0:0: +116,148,165244,1,2,0:0:0:0: +27,148,165354,1,0,3:2:0:0: +344,148,165573,5,2,1:2:0:0: +381,213,165682,1,0,0:0:0:0: +339,277,165792,1,0,0:0:0:0: +263,277,165901,1,0,0:0:0:0: +152,277,166011,2,0,L|151:353,1,69,0|0,1:2|0:0,0:0:0:0: +352,345,166230,1,2,3:2:0:0: +427,345,166339,1,0,0:0:0:0: +464,278,166449,1,0,0:0:0:0: +425,212,166558,1,0,0:0:0:0: +189,212,166668,5,2,1:2:0:0: +116,189,166777,1,0,0:0:0:0: +125,113,166887,1,0,0:0:0:0: +199,102,166996,1,2,0:0:0:0: +309,102,167106,2,0,L|311:180,1,69,0|0,3:2|0:0,0:0:0:0: +199,170,167325,2,0,L|197:242,1,69,2|0,1:2|0:0,0:0:0:0: +398,238,167544,6,0,L|483:238,1,69,2|0,1:2|0:0,0:0:0:0: +356,238,167763,2,0,L|283:237,1,69,2|0,1:2|0:0,0:0:0:0: +85,237,167982,2,0,L|11:237,1,69,2|0,3:2|0:0,0:0:0:0: +126,237,168201,2,0,L|206:237,1,69,2|0,0:0|0:0,0:0:0:0: +430,237,168420,6,0,P|487:176|366:86,1,276,2|0,1:2|3:2,0:0:0:0: +174,89,169077,1,2,1:2:0:0: +99,98,169186,1,0,0:0:0:0: +67,167,169296,1,0,0:0:0:0: +101,234,169405,1,0,0:0:0:0: +176,243,169515,1,0,1:2:0:0: +465,243,169734,2,0,L|467:104,1,138,0|0,3:2|1:2,0:0:0:0: +390,105,170062,1,0,0:0:0:0: +154,105,170172,6,0,L|367:106,1,207,2|2,1:2|0:0,0:0:0:0: +127,105,170609,2,0,P|104:181|130:237,1,138,0|2,3:2|0:0,0:0:0:0: +202,232,170938,1,2,0:0:0:0: +401,232,171047,2,0,P|176:204|125:49,1,414,2|0,1:2|0:0,0:0:0:0: +416,48,171923,5,2,1:2:0:0: +85,274,178712,5,0,3:2:0:0: +402,274,178931,6,0,P|428:204|398:150,1,138,2|2,1:2|0:0,0:0:0:0: +323,151,179259,1,2,0:0:0:0: +212,151,179369,2,0,P|134:143|92:99,1,138,2|2,1:2|0:0,0:0:0:0: +170,102,179697,1,2,0:0:0:0: +280,102,179807,2,0,L|429:102,1,138,2|2,1:2|0:0,0:0:0:0: +307,102,180135,1,2,0:0:0:0: +238,102,180244,1,0,1:2:0:0: +307,102,180354,1,2,0:0:0:0: +417,102,180463,2,0,L|418:179,1,69,2|0,0:0|0:0,0:0:0:0: +216,159,180682,5,2,1:2:0:0: +313,159,180792,1,2,0:0:0:0: +381,159,180901,1,2,0:0:0:0: +313,159,181011,1,2,0:0:0:0: +203,159,181120,1,2,1:2:0:0: +133,159,181230,1,2,0:0:0:0: +203,159,181339,1,2,0:0:0:0: +396,159,181558,2,0,P|422:224|388:292,1,138,2|2,1:2|0:0,0:0:0:0: +320,283,181887,1,2,0:0:0:0: +210,283,181996,2,0,L|65:282,1,138,0|0,1:2|0:0,0:0:0:0: +148,282,182325,1,0,0:0:0:0: +347,282,182434,5,2,1:2:0:0: +416,282,182544,1,2,0:0:0:0: +347,282,182653,1,2,0:0:0:0: +154,282,182872,1,2,1:2:0:0: +85,282,182982,1,2,0:0:0:0: +154,282,183091,1,2,0:0:0:0: +347,282,183310,2,0,P|373:217|342:159,1,138,2|2,1:2|0:0,0:0:0:0: +231,160,183639,1,2,0:0:0:0: +162,160,183748,1,0,1:2:0:0: +231,160,183858,1,2,0:0:0:0: +343,160,183967,2,0,L|345:87,1,69,2|0,0:0|0:0,0:0:0:0: +143,91,184186,5,2,1:2:0:0: +323,91,184405,1,8,0:3:0:0: +143,91,184624,2,0,P|118:168|149:218,1,138,8|2,0:3|0:0,0:0:0:0: +221,213,184953,1,2,0:0:0:0: +421,270,185062,2,0,L|206:271,2,207,4|4|0,0:3|0:3|3:2,0:0:0:0: +102,270,185938,6,0,P|72:198|110:155,1,138,2|2,1:2|0:0,0:0:0:0: +181,157,186266,1,2,0:0:0:0: +291,157,186376,2,0,L|432:157,1,138,2|2,3:2|0:0,0:0:0:0: +352,157,186704,1,2,1:2:0:0: +150,157,186814,2,0,P|128:221|149:291,1,138,2|2,0:0|1:2,0:0:0:0: +257,286,187142,1,2,0:0:0:0: +325,227,187252,1,0,3:2:0:0: +253,155,187361,1,2,0:0:0:0: +141,155,187471,2,0,L|52:155,1,69,2|0,0:0|0:0,0:0:0:0: +307,155,187690,6,0,P|325:214|306:292,1,138,0|0,1:2|0:0,0:0:0:0: +113,292,188128,2,0,P|100:235|115:156,1,138,0|0,3:2|0:0,0:0:0:0: +190,157,188456,1,0,1:2:0:0: +391,157,188566,1,0,0:0:0:0: +211,157,188785,1,0,1:2:0:0: +390,157,189004,2,0,L|392:13,1,138,0|0,3:2|0:0,0:0:0:0: +73,19,189442,5,2,1:2:0:0: +39,86,189551,1,2,0:0:0:0: +76,152,189661,1,2,0:0:0:0: +158,152,189770,1,2,0:0:0:0: +268,152,189880,2,0,L|114:153,1,138,2|2,3:2|0:0,0:0:0:0: +213,152,190208,1,2,1:2:0:0: +412,152,190317,2,0,P|430:226|409:286,1,138,2|2,0:0|1:2,0:0:0:0: +320,282,190646,1,2,0:0:0:0: +230,282,190755,1,0,3:2:0:0: +409,282,190974,1,2,0:0:0:0: +91,282,191193,6,0,P|23:224|137:141,1,276,0|0,1:2|3:2,0:0:0:0: +344,141,191850,1,0,1:2:0:0: +427,141,191960,1,0,1:2:0:0: +344,141,192069,1,2,3:2:0:0: +138,141,192288,1,0,1:2:0:0: +427,141,192507,2,0,L|428:288,1,138,2|0,3:2|0:0,0:0:0:0: +81,278,192945,6,0,L|266:278,1,179.39999178772,6|2,1:2|1:1,0:0:0:0: +81,278,193383,2,0,L|388:279,1,289.799991156006,2|2,1:1|1:1,0:0:0:0: +190,278,193821,2,0,L|381:278,1,179.39999178772,2|2,1:2|1:1,0:0:0:0: +78,278,194259,2,0,L|401:277,1,289.799991156006,2|2,1:1|1:1,0:0:0:0: +76,277,194697,6,0,L|74:140,1,138,2|2,1:2|1:1,0:0:0:0: +365,139,195135,2,0,L|59:138,1,289.799991156006,2|2,1:1|1:1,0:0:0:0: +394,138,195573,2,0,L|395:278,1,138,2|2,1:2|1:1,0:0:0:0: +105,276,196011,2,0,L|411:277,1,289.799991156006,2|2,1:1|1:1,0:0:0:0: +75,276,196449,5,2,3:2:0:0: +422,276,196668,2,0,L|108:275,2,289.799991156006,6|6|6,1:1|1:1|1:1,0:0:0:0: +75,276,197325,2,0,L|389:275,2,289.799991156006,6|6|6,1:1|1:1|1:1,0:0:0:0: +395,276,197982,1,6,1:1:0:0: +47,276,198201,6,0,L|349:277,1,289.799991156006,6|6,1:1|1:1,0:0:0:0: +142,276,198639,2,0,L|342:277,1,193.199994104004,14|14,2:3|2:3,0:0:0:0: +26,277,199077,1,14,2:3:0:0: +371,277,199296,2,0,P|254:202|378:86,1,358.79998357544,6|0,1:2|0:0,0:0:0:0: +56,81,199953,6,0,L|297:80,2,207,6|2|2,1:2|0:0|0:0,0:0:0:0: +249,81,200828,2,0,L|251:169,1,69,2|2,0:0|0:0,0:0:0:0: +160,149,201047,1,2,0:0:0:0: +250,149,201157,1,2,0:0:0:0: +50,149,201266,1,0,3:2:0:0: +139,149,201376,1,0,3:2:0:0: +50,149,201485,1,2,3:2:0:0: +285,149,201595,1,0,3:2:0:0: +50,149,201704,6,0,L|48:228,1,69,6|2,1:2|0:0,0:0:0:0: +249,217,201923,1,2,0:0:0:0: +48,217,202033,1,2,0:0:0:0: +141,217,202142,2,0,P|172:281|134:338,1,138,0|2,3:2|0:0,0:0:0:0: +45,333,202471,1,0,1:2:0:0: +278,333,202580,5,2,0:0:0:0: +180,333,202690,2,0,L|179:262,1,69,2|0,0:0|1:2,0:0:0:0: +380,264,202909,1,2,0:0:0:0: +283,264,203018,2,0,L|457:265,1,138,0|2,3:2|0:0,0:0:0:0: +337,264,203347,1,0,0:0:0:0: +103,264,203456,6,0,P|72:200|117:155,1,138,0|0,1:2|0:0,0:0:0:0: +202,156,203785,1,2,0:0:0:0: +111,156,203894,2,0,L|109:75,1,69,0|0,3:2|0:0,0:0:0:0: +310,87,204113,2,0,L|399:86,1,69,2|0,0:0|1:2,0:0:0:0: +177,86,204332,5,2,0:0:0:0: +378,86,204442,2,0,L|379:160,1,69,2|0,0:0|1:2,0:0:0:0: +177,154,204661,1,2,0:0:0:0: +80,154,204770,2,0,P|55:217|80:282,1,138,0|2,3:2|0:0,0:0:0:0: +162,280,205099,1,0,0:0:0:0: +395,280,205208,6,0,L|312:280,1,69,2|2,1:2|0:0,0:0:0:0: +124,280,205427,1,2,0:0:0:0: +323,280,205536,1,2,0:0:0:0: +420,280,205646,2,0,L|252:279,1,138,0|2,3:2|0:0,0:0:0:0: +379,279,205974,1,0,1:2:0:0: +143,279,206084,6,0,L|70:281,1,69,2|2,0:0|0:0,0:0:0:0: +171,280,206303,1,0,1:2:0:0: +370,280,206412,1,2,0:0:0:0: +467,280,206522,2,0,P|494:213|463:160,1,138,0|2,3:2|0:0,0:0:0:0: +380,160,206850,1,0,0:0:0:0: +109,160,206960,6,0,L|259:160,1,138,6|0,1:2|0:0,0:0:0:0: +156,160,207288,1,2,0:0:0:0: +65,160,207398,1,0,3:2:0:0: +382,160,207617,5,2,1:2:0:0: +420,224,207726,1,0,0:0:0:0: +378,288,207836,1,0,0:0:0:0: +302,288,207945,1,0,0:0:0:0: +191,288,208055,2,0,L|190:212,1,69,0|0,1:2|0:0,0:0:0:0: +391,219,208274,2,0,P|417:155|379:101,1,138,2|2,3:2|0:0,0:0:0:0: +298,102,208602,1,0,0:0:0:0: +62,102,208712,6,0,L|61:180,1,69,0|0,1:2|0:0,0:0:0:0: +172,170,208931,2,0,L|245:169,1,69,2|0,0:0|0:0,0:0:0:0: +442,169,209150,2,0,P|466:237|434:297,1,138,2|2,3:2|1:2,0:0:0:0: +355,292,209478,1,0,0:0:0:0: +119,292,209588,6,0,L|116:218,1,69,2|0,0:0|0:0,0:0:0:0: +220,223,209807,1,2,1:2:0:0: +413,223,210026,1,2,3:2:0:0: +124,223,210244,2,0,L|48:223,1,69,2|2,0:0|0:0,0:0:0:0: +325,223,210463,6,0,P|407:220|484:62,1,276,6|2,1:2|3:2,0:0:0:0: +165,63,211120,2,0,L|469:62,1,276,6|0,1:2|1:2,0:0:0:0: +149,62,211777,2,0,L|8:61,1,138,2|2,3:2|0:0,0:0:0:0: +357,61,212215,6,0,L|142:61,2,207,6|8|8,1:2|0:3|0:3,0:0:0:0: +65,61,213091,2,0,L|375:61,1,276,6|0,1:2|3:2,0:0:0:0: +250,61,213639,1,0,3:2:0:0: +339,61,213748,1,0,3:2:0:0: +103,61,213858,1,0,3:2:0:0: +339,61,213967,6,0,P|366:130|332:184,1,138,6|0,1:2|0:0,0:0:0:0: +245,180,214296,1,2,0:0:0:0: +334,180,214405,2,0,L|336:259,1,69,0|0,3:2|0:0,0:0:0:0: +134,248,214624,2,0,L|47:249,1,69,2|0,0:0|1:2,0:0:0:0: +300,248,214843,6,0,L|301:171,1,69,2|0,0:0|0:0,0:0:0:0: +99,179,215062,1,2,1:2:0:0: +300,179,215172,1,2,0:0:0:0: +203,179,215281,2,0,L|28:176,1,138,0|2,3:2|0:0,0:0:0:0: +148,176,215609,1,0,0:0:0:0: +383,176,215719,6,0,L|290:176,1,69,2|2,1:2|0:0,0:0:0:0: +112,176,215938,1,2,0:0:0:0: +311,176,216047,1,2,0:0:0:0: +408,176,216157,2,0,P|437:111|399:59,1,138,0|2,3:2|0:0,0:0:0:0: +305,60,216485,1,0,1:2:0:0: +69,60,216595,6,0,L|68:143,1,69,2|0,0:0|0:0,0:0:0:0: +179,128,216814,2,0,L|263:129,1,69,2|0,1:2|0:0,0:0:0:0: +449,128,217033,2,0,L|348:129,1,69,2|0,3:2|0:0,0:0:0:0: +178,128,217252,2,0,L|86:128,1,69,2|0,0:0|0:0,0:0:0:0: +344,128,217471,6,0,L|197:128,1,138,2|0,1:2|0:0,0:0:0:0: +289,128,217799,1,2,0:0:0:0: +206,128,217909,2,0,L|205:204,1,69,0|0,3:2|0:0,0:0:0:0: +406,196,218128,2,0,L|479:195,1,69,2|0,0:0|1:2,0:0:0:0: +239,195,218347,6,0,L|158:196,1,69,2|0,0:0|0:0,0:0:0:0: +371,195,218566,1,2,1:2:0:0: +170,195,218675,1,2,0:0:0:0: +267,195,218785,2,0,L|435:196,1,138,0|2,3:2|0:0,0:0:0:0: +321,195,219113,1,0,0:0:0:0: +85,195,219223,6,0,L|85:273,1,69,2|0,1:2|0:0,0:0:0:0: +286,264,219442,2,0,L|379:265,1,69,2|0,1:2|0:0,0:0:0:0: +119,264,219661,2,0,L|37:264,1,69,2|0,3:2|0:0,0:0:0:0: +320,264,219880,5,6,1:2:0:0: +399,257,219989,1,0,0:0:0:0: +402,180,220099,1,0,0:0:0:0: +327,170,220208,1,0,0:0:0:0: +129,120,220317,2,0,L|129:48,1,69,8|0,0:3|0:0,0:0:0:0: +330,51,220536,2,0,L|412:48,1,69,8|0,0:3|0:0,0:0:0:0: +163,48,220755,2,0,L|80:48,1,69,4|0,0:3|0:0,0:0:0:0: +364,52,220974,5,6,1:2:0:0: +439,64,221084,1,0,0:0:0:0: +426,139,221193,1,0,0:0:0:0: +350,146,221303,1,2,0:0:0:0: +240,146,221412,2,0,L|239:227,1,69,0|0,3:2|0:0,0:0:0:0: +440,214,221631,5,2,1:2:0:0: +472,282,221741,1,0,0:0:0:0: +434,346,221850,1,0,0:0:0:0: +357,352,221960,1,0,0:0:0:0: +157,352,222069,2,0,L|66:348,1,69,2|0,1:2|0:0,0:0:0:0: +289,348,222288,2,0,L|471:346,1,138,2|2,3:2|0:0,0:0:0:0: +343,346,222617,1,0,0:0:0:0: +109,346,222726,6,0,P|83:283|123:224,1,138,2|0,1:2|0:0,0:0:0:0: +207,227,223055,1,2,0:0:0:0: +117,227,223164,2,0,L|114:140,1,69,0|0,3:2|0:0,0:0:0:0: +315,158,223383,2,0,L|399:159,1,69,2|0,1:2|0:0,0:0:0:0: +148,158,223602,6,0,L|146:226,1,69,2|0,0:0|0:0,0:0:0:0: +256,226,223821,2,0,L|346:226,1,69,2|0,1:2|0:0,0:0:0:0: +123,226,224040,2,0,L|209:226,1,69,2|0,3:2|0:0,0:0:0:0: +393,226,224259,2,0,L|394:149,1,69,2|0,0:0|0:0,0:0:0:0: +158,157,224478,5,2,1:2:0:0: +82,163,224588,1,0,0:0:0:0: +44,228,224697,1,0,0:0:0:0: +86,291,224807,1,0,0:0:0:0: +285,291,224916,2,0,L|378:292,1,69,0|0,3:2|0:0,0:0:0:0: +83,291,225135,5,6,1:2:0:0: +41,227,225244,1,0,0:0:0:0: +82,163,225354,1,0,0:0:0:0: +157,156,225463,1,0,0:0:0:0: +267,156,225573,2,0,L|267:86,1,69,0|0,1:2|0:0,0:0:0:0: +65,87,225792,2,0,L|64:173,1,69,2|0,3:2|0:0,0:0:0:0: +154,155,226011,1,2,0:0:0:0: +64,155,226120,1,2,0:0:0:0: +299,155,226230,5,2,1:2:0:0: +105,155,226449,2,0,L|104:233,1,69,8|0,0:3|0:0,0:0:0:0: +305,223,226668,1,0,3:2:0:0: +104,223,226777,2,0,L|28:224,1,69,8|2,0:3|0:0,0:0:0:0: +383,353,227106,6,0,P|161:330|63:49,1,552,6|2,1:2|0:0,0:0:0:0: diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3644427-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3644427-expected-conversion.json new file mode 100644 index 0000000000..9d4210c71e --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3644427-expected-conversion.json @@ -0,0 +1 @@ +{"Mappings":[{"StartTime":22.0,"Objects":[{"StartTime":22.0,"Position":28.0,"HyperDash":false},{"StartTime":90.0,"Position":34.2658119,"HyperDash":false},{"StartTime":158.0,"Position":28.0,"HyperDash":false},{"StartTime":226.0,"Position":34.2658119,"HyperDash":false},{"StartTime":294.0,"Position":28.0,"HyperDash":false},{"StartTime":362.0,"Position":34.2658119,"HyperDash":false}]},{"StartTime":431.0,"Objects":[{"StartTime":431.0,"Position":106.0,"HyperDash":false},{"StartTime":499.0,"Position":114.369232,"HyperDash":false},{"StartTime":567.0,"Position":106.0,"HyperDash":false},{"StartTime":635.0,"Position":114.369232,"HyperDash":false},{"StartTime":703.0,"Position":106.0,"HyperDash":false},{"StartTime":771.0,"Position":114.369232,"HyperDash":false}]},{"StartTime":840.0,"Objects":[{"StartTime":840.0,"Position":207.0,"HyperDash":false},{"StartTime":891.0,"Position":229.046875,"HyperDash":false},{"StartTime":942.0,"Position":270.0929,"HyperDash":false},{"StartTime":993.0,"Position":315.1283,"HyperDash":false},{"StartTime":1044.0,"Position":355.531,"HyperDash":false},{"StartTime":1128.0,"Position":309.8878,"HyperDash":false},{"StartTime":1249.0,"Position":207.0,"HyperDash":false}]},{"StartTime":1385.0,"Objects":[{"StartTime":1385.0,"Position":313.0,"HyperDash":false},{"StartTime":1453.0,"Position":346.417664,"HyperDash":false},{"StartTime":1521.0,"Position":313.0,"HyperDash":false},{"StartTime":1589.0,"Position":346.417664,"HyperDash":false},{"StartTime":1657.0,"Position":313.0,"HyperDash":false},{"StartTime":1725.0,"Position":346.417664,"HyperDash":false}]},{"StartTime":1794.0,"Objects":[{"StartTime":1794.0,"Position":347.0,"HyperDash":false},{"StartTime":1862.0,"Position":379.8631,"HyperDash":false},{"StartTime":1930.0,"Position":347.0,"HyperDash":false},{"StartTime":1998.0,"Position":379.8631,"HyperDash":false},{"StartTime":2066.0,"Position":347.0,"HyperDash":false},{"StartTime":2134.0,"Position":379.8631,"HyperDash":false}]},{"StartTime":2203.0,"Objects":[{"StartTime":2203.0,"Position":415.0,"HyperDash":false},{"StartTime":2254.0,"Position":441.134857,"HyperDash":false},{"StartTime":2305.0,"Position":433.726776,"HyperDash":false},{"StartTime":2356.0,"Position":437.454437,"HyperDash":false},{"StartTime":2407.0,"Position":447.7087,"HyperDash":false},{"StartTime":2491.0,"Position":417.7042,"HyperDash":false},{"StartTime":2612.0,"Position":415.0,"HyperDash":false}]},{"StartTime":2749.0,"Objects":[{"StartTime":2749.0,"Position":235.0,"HyperDash":false},{"StartTime":2817.0,"Position":201.582336,"HyperDash":false},{"StartTime":2885.0,"Position":235.0,"HyperDash":false},{"StartTime":2953.0,"Position":201.582336,"HyperDash":false},{"StartTime":3021.0,"Position":235.0,"HyperDash":false},{"StartTime":3089.0,"Position":201.582336,"HyperDash":false}]},{"StartTime":3158.0,"Objects":[{"StartTime":3158.0,"Position":219.0,"HyperDash":false},{"StartTime":3226.0,"Position":229.565125,"HyperDash":false},{"StartTime":3294.0,"Position":219.0,"HyperDash":false},{"StartTime":3362.0,"Position":229.565125,"HyperDash":false},{"StartTime":3430.0,"Position":219.0,"HyperDash":false},{"StartTime":3498.0,"Position":229.565125,"HyperDash":false}]},{"StartTime":3567.0,"Objects":[{"StartTime":3567.0,"Position":299.0,"HyperDash":false},{"StartTime":3618.0,"Position":253.857819,"HyperDash":false},{"StartTime":3669.0,"Position":212.7368,"HyperDash":false},{"StartTime":3720.0,"Position":183.691315,"HyperDash":false},{"StartTime":3771.0,"Position":150.281219,"HyperDash":false},{"StartTime":3855.0,"Position":224.9364,"HyperDash":false},{"StartTime":3976.0,"Position":299.0,"HyperDash":false}]},{"StartTime":4112.0,"Objects":[{"StartTime":4112.0,"Position":234.0,"HyperDash":false},{"StartTime":4180.0,"Position":201.015152,"HyperDash":false},{"StartTime":4248.0,"Position":234.0,"HyperDash":false},{"StartTime":4316.0,"Position":201.015152,"HyperDash":false},{"StartTime":4384.0,"Position":234.0,"HyperDash":false},{"StartTime":4452.0,"Position":201.015152,"HyperDash":false}]},{"StartTime":4522.0,"Objects":[{"StartTime":4522.0,"Position":135.0,"HyperDash":false},{"StartTime":4590.0,"Position":102.015152,"HyperDash":false},{"StartTime":4658.0,"Position":135.0,"HyperDash":false},{"StartTime":4726.0,"Position":102.015152,"HyperDash":false},{"StartTime":4794.0,"Position":135.0,"HyperDash":false},{"StartTime":4862.0,"Position":102.015152,"HyperDash":false}]},{"StartTime":4931.0,"Objects":[{"StartTime":4931.0,"Position":35.0,"HyperDash":false},{"StartTime":4982.0,"Position":48.13485,"HyperDash":false},{"StartTime":5033.0,"Position":43.7267761,"HyperDash":false},{"StartTime":5084.0,"Position":63.454422,"HyperDash":false},{"StartTime":5135.0,"Position":67.7087,"HyperDash":false},{"StartTime":5219.0,"Position":33.7041931,"HyperDash":false},{"StartTime":5340.0,"Position":35.0,"HyperDash":false}]},{"StartTime":5476.0,"Objects":[{"StartTime":5476.0,"Position":22.0,"HyperDash":false},{"StartTime":5544.0,"Position":18.9217854,"HyperDash":false},{"StartTime":5612.0,"Position":22.0,"HyperDash":false},{"StartTime":5680.0,"Position":18.9217854,"HyperDash":false},{"StartTime":5748.0,"Position":22.0,"HyperDash":false},{"StartTime":5816.0,"Position":18.9217854,"HyperDash":false}]},{"StartTime":5885.0,"Objects":[{"StartTime":5885.0,"Position":120.0,"HyperDash":false},{"StartTime":5953.0,"Position":152.061676,"HyperDash":false},{"StartTime":6021.0,"Position":120.0,"HyperDash":false},{"StartTime":6089.0,"Position":152.061676,"HyperDash":false},{"StartTime":6157.0,"Position":120.0,"HyperDash":false},{"StartTime":6225.0,"Position":152.061676,"HyperDash":false}]},{"StartTime":6294.0,"Objects":[{"StartTime":6294.0,"Position":187.0,"HyperDash":false},{"StartTime":6345.0,"Position":140.953125,"HyperDash":false},{"StartTime":6396.0,"Position":130.9071,"HyperDash":false},{"StartTime":6447.0,"Position":92.8717041,"HyperDash":false},{"StartTime":6498.0,"Position":38.469,"HyperDash":false},{"StartTime":6582.0,"Position":112.112221,"HyperDash":false},{"StartTime":6703.0,"Position":187.0,"HyperDash":false}]},{"StartTime":6840.0,"Objects":[{"StartTime":6840.0,"Position":363.0,"HyperDash":false},{"StartTime":6908.0,"Position":359.921783,"HyperDash":false},{"StartTime":6976.0,"Position":363.0,"HyperDash":false},{"StartTime":7044.0,"Position":359.921783,"HyperDash":false},{"StartTime":7112.0,"Position":363.0,"HyperDash":false},{"StartTime":7180.0,"Position":359.921783,"HyperDash":false}]},{"StartTime":7249.0,"Objects":[{"StartTime":7249.0,"Position":411.0,"HyperDash":false},{"StartTime":7317.0,"Position":443.061676,"HyperDash":false},{"StartTime":7385.0,"Position":411.0,"HyperDash":false},{"StartTime":7453.0,"Position":443.061676,"HyperDash":false},{"StartTime":7521.0,"Position":411.0,"HyperDash":false},{"StartTime":7589.0,"Position":443.061676,"HyperDash":false}]},{"StartTime":7658.0,"Objects":[{"StartTime":7658.0,"Position":355.0,"HyperDash":false},{"StartTime":7709.0,"Position":356.134857,"HyperDash":false},{"StartTime":7760.0,"Position":355.726776,"HyperDash":false},{"StartTime":7811.0,"Position":391.454437,"HyperDash":false},{"StartTime":7862.0,"Position":387.7087,"HyperDash":false},{"StartTime":7946.0,"Position":367.7042,"HyperDash":false},{"StartTime":8067.0,"Position":355.0,"HyperDash":false}]},{"StartTime":8203.0,"Objects":[{"StartTime":8203.0,"Position":502.0,"HyperDash":false},{"StartTime":8271.0,"Position":508.2658,"HyperDash":false},{"StartTime":8339.0,"Position":502.0,"HyperDash":false},{"StartTime":8407.0,"Position":508.2658,"HyperDash":false},{"StartTime":8475.0,"Position":502.0,"HyperDash":false},{"StartTime":8543.0,"Position":508.2658,"HyperDash":false}]},{"StartTime":8612.0,"Objects":[{"StartTime":8612.0,"Position":419.0,"HyperDash":false},{"StartTime":8680.0,"Position":429.565125,"HyperDash":false},{"StartTime":8748.0,"Position":419.0,"HyperDash":false},{"StartTime":8816.0,"Position":429.565125,"HyperDash":false},{"StartTime":8884.0,"Position":419.0,"HyperDash":false},{"StartTime":8952.0,"Position":429.565125,"HyperDash":false}]},{"StartTime":9022.0,"Objects":[{"StartTime":9022.0,"Position":364.0,"HyperDash":false},{"StartTime":9073.0,"Position":405.046875,"HyperDash":false},{"StartTime":9124.0,"Position":423.0929,"HyperDash":false},{"StartTime":9175.0,"Position":471.1283,"HyperDash":false},{"StartTime":9226.0,"Position":512.0,"HyperDash":false},{"StartTime":9310.0,"Position":450.8878,"HyperDash":false},{"StartTime":9431.0,"Position":364.0,"HyperDash":false}]},{"StartTime":9567.0,"Objects":[{"StartTime":9567.0,"Position":233.0,"HyperDash":false},{"StartTime":9635.0,"Position":226.21344,"HyperDash":false},{"StartTime":9703.0,"Position":233.0,"HyperDash":false},{"StartTime":9771.0,"Position":226.21344,"HyperDash":false},{"StartTime":9839.0,"Position":233.0,"HyperDash":false},{"StartTime":9907.0,"Position":226.21344,"HyperDash":false}]},{"StartTime":9976.0,"Objects":[{"StartTime":9976.0,"Position":284.0,"HyperDash":false},{"StartTime":10044.0,"Position":302.4323,"HyperDash":false},{"StartTime":10112.0,"Position":284.0,"HyperDash":false},{"StartTime":10180.0,"Position":302.4323,"HyperDash":false},{"StartTime":10248.0,"Position":284.0,"HyperDash":false},{"StartTime":10316.0,"Position":302.4323,"HyperDash":false}]},{"StartTime":10385.0,"Objects":[{"StartTime":10385.0,"Position":245.0,"HyperDash":false},{"StartTime":10436.0,"Position":221.437592,"HyperDash":false},{"StartTime":10487.0,"Position":156.41156,"HyperDash":false},{"StartTime":10538.0,"Position":124.576111,"HyperDash":false},{"StartTime":10589.0,"Position":129.145676,"HyperDash":false},{"StartTime":10673.0,"Position":143.747086,"HyperDash":false},{"StartTime":10794.0,"Position":245.0,"HyperDash":false}]},{"StartTime":12021.0,"Objects":[{"StartTime":12021.0,"Position":407.0,"HyperDash":false},{"StartTime":12157.0,"Position":430.1819,"HyperDash":false}]},{"StartTime":12225.0,"Objects":[{"StartTime":12225.0,"Position":484.0,"HyperDash":false}]},{"StartTime":12293.0,"Objects":[{"StartTime":12293.0,"Position":484.0,"HyperDash":false},{"StartTime":12429.0,"Position":405.168243,"HyperDash":false}]},{"StartTime":12566.0,"Objects":[{"StartTime":12566.0,"Position":387.0,"HyperDash":false},{"StartTime":12617.0,"Position":436.7446,"HyperDash":false},{"StartTime":12668.0,"Position":476.301575,"HyperDash":false},{"StartTime":12719.0,"Position":481.9031,"HyperDash":false},{"StartTime":12770.0,"Position":487.317719,"HyperDash":false},{"StartTime":12854.0,"Position":463.006,"HyperDash":false},{"StartTime":12975.0,"Position":387.0,"HyperDash":false}]},{"StartTime":13111.0,"Objects":[{"StartTime":13111.0,"Position":274.0,"HyperDash":false},{"StartTime":13247.0,"Position":173.621216,"HyperDash":false}]},{"StartTime":13316.0,"Objects":[{"StartTime":13316.0,"Position":124.0,"HyperDash":false}]},{"StartTime":13384.0,"Objects":[{"StartTime":13384.0,"Position":124.0,"HyperDash":false},{"StartTime":13520.0,"Position":23.6840134,"HyperDash":false}]},{"StartTime":13657.0,"Objects":[{"StartTime":13657.0,"Position":24.0,"HyperDash":false},{"StartTime":13741.0,"Position":80.13487,"HyperDash":false},{"StartTime":13861.0,"Position":108.188271,"HyperDash":false}]},{"StartTime":14066.0,"Objects":[{"StartTime":14066.0,"Position":229.0,"HyperDash":false}]},{"StartTime":14202.0,"Objects":[{"StartTime":14202.0,"Position":328.0,"HyperDash":false},{"StartTime":14338.0,"Position":300.487976,"HyperDash":false}]},{"StartTime":14407.0,"Objects":[{"StartTime":14407.0,"Position":256.0,"HyperDash":false}]},{"StartTime":14475.0,"Objects":[{"StartTime":14475.0,"Position":256.0,"HyperDash":false},{"StartTime":14611.0,"Position":333.4403,"HyperDash":false}]},{"StartTime":14748.0,"Objects":[{"StartTime":14748.0,"Position":378.0,"HyperDash":false},{"StartTime":14799.0,"Position":434.77832,"HyperDash":false},{"StartTime":14850.0,"Position":445.8225,"HyperDash":false},{"StartTime":14901.0,"Position":485.582428,"HyperDash":false},{"StartTime":14952.0,"Position":497.584961,"HyperDash":false},{"StartTime":15036.0,"Position":476.6938,"HyperDash":false},{"StartTime":15157.0,"Position":378.0,"HyperDash":false}]},{"StartTime":15293.0,"Objects":[{"StartTime":15293.0,"Position":277.0,"HyperDash":false},{"StartTime":15429.0,"Position":252.447769,"HyperDash":false}]},{"StartTime":15498.0,"Objects":[{"StartTime":15498.0,"Position":212.0,"HyperDash":false}]},{"StartTime":15566.0,"Objects":[{"StartTime":15566.0,"Position":212.0,"HyperDash":false},{"StartTime":15702.0,"Position":236.552231,"HyperDash":false}]},{"StartTime":15839.0,"Objects":[{"StartTime":15839.0,"Position":256.0,"HyperDash":false},{"StartTime":15923.0,"Position":331.136047,"HyperDash":false},{"StartTime":16043.0,"Position":396.792,"HyperDash":false}]},{"StartTime":16248.0,"Objects":[{"StartTime":16248.0,"Position":473.0,"HyperDash":false}]},{"StartTime":16384.0,"Objects":[{"StartTime":16384.0,"Position":486.0,"HyperDash":false},{"StartTime":16520.0,"Position":397.151581,"HyperDash":false}]},{"StartTime":16589.0,"Objects":[{"StartTime":16589.0,"Position":382.0,"HyperDash":false}]},{"StartTime":16657.0,"Objects":[{"StartTime":16657.0,"Position":382.0,"HyperDash":false},{"StartTime":16793.0,"Position":297.671143,"HyperDash":false}]},{"StartTime":16930.0,"Objects":[{"StartTime":16930.0,"Position":201.0,"HyperDash":false},{"StartTime":16981.0,"Position":215.6601,"HyperDash":false},{"StartTime":17032.0,"Position":193.010483,"HyperDash":false},{"StartTime":17083.0,"Position":161.445236,"HyperDash":false},{"StartTime":17134.0,"Position":106.410675,"HyperDash":false},{"StartTime":17218.0,"Position":175.619278,"HyperDash":false},{"StartTime":17339.0,"Position":201.0,"HyperDash":false}]},{"StartTime":17475.0,"Objects":[{"StartTime":17475.0,"Position":40.0,"HyperDash":false},{"StartTime":17611.0,"Position":56.7687,"HyperDash":false}]},{"StartTime":17680.0,"Objects":[{"StartTime":17680.0,"Position":97.0,"HyperDash":false}]},{"StartTime":17748.0,"Objects":[{"StartTime":17748.0,"Position":97.0,"HyperDash":false},{"StartTime":17884.0,"Position":197.612183,"HyperDash":false}]},{"StartTime":18021.0,"Objects":[{"StartTime":18021.0,"Position":275.0,"HyperDash":false},{"StartTime":18105.0,"Position":227.99115,"HyperDash":false},{"StartTime":18225.0,"Position":263.429932,"HyperDash":false}]},{"StartTime":18430.0,"Objects":[{"StartTime":18430.0,"Position":415.0,"HyperDash":false}]},{"StartTime":18566.0,"Objects":[{"StartTime":18566.0,"Position":355.0,"HyperDash":false},{"StartTime":18702.0,"Position":450.052368,"HyperDash":false}]},{"StartTime":18771.0,"Objects":[{"StartTime":18771.0,"Position":486.0,"HyperDash":false}]},{"StartTime":18839.0,"Objects":[{"StartTime":18839.0,"Position":486.0,"HyperDash":false},{"StartTime":18975.0,"Position":451.9095,"HyperDash":false}]},{"StartTime":19111.0,"Objects":[{"StartTime":19111.0,"Position":476.0,"HyperDash":false},{"StartTime":19162.0,"Position":467.7854,"HyperDash":false},{"StartTime":19213.0,"Position":421.959442,"HyperDash":false},{"StartTime":19264.0,"Position":381.8976,"HyperDash":false},{"StartTime":19315.0,"Position":360.902435,"HyperDash":false},{"StartTime":19399.0,"Position":438.64032,"HyperDash":false},{"StartTime":19520.0,"Position":476.0,"HyperDash":false}]},{"StartTime":19657.0,"Objects":[{"StartTime":19657.0,"Position":306.0,"HyperDash":false},{"StartTime":19793.0,"Position":210.46254,"HyperDash":false}]},{"StartTime":19861.0,"Objects":[{"StartTime":19861.0,"Position":161.0,"HyperDash":false}]},{"StartTime":19930.0,"Objects":[{"StartTime":19930.0,"Position":161.0,"HyperDash":false},{"StartTime":20066.0,"Position":196.729462,"HyperDash":false}]},{"StartTime":20202.0,"Objects":[{"StartTime":20202.0,"Position":127.0,"HyperDash":false},{"StartTime":20338.0,"Position":32.14918,"HyperDash":false}]},{"StartTime":20475.0,"Objects":[{"StartTime":20475.0,"Position":41.0,"HyperDash":false}]},{"StartTime":20543.0,"Objects":[{"StartTime":20543.0,"Position":48.0,"HyperDash":false}]},{"StartTime":20611.0,"Objects":[{"StartTime":20611.0,"Position":64.0,"HyperDash":false}]},{"StartTime":20679.0,"Objects":[{"StartTime":20679.0,"Position":86.0,"HyperDash":false}]},{"StartTime":20748.0,"Objects":[{"StartTime":20748.0,"Position":111.0,"HyperDash":false},{"StartTime":20884.0,"Position":197.677109,"HyperDash":false}]},{"StartTime":20952.0,"Objects":[{"StartTime":20952.0,"Position":249.0,"HyperDash":false}]},{"StartTime":21021.0,"Objects":[{"StartTime":21021.0,"Position":249.0,"HyperDash":false},{"StartTime":21157.0,"Position":350.174561,"HyperDash":false}]},{"StartTime":21293.0,"Objects":[{"StartTime":21293.0,"Position":451.0,"HyperDash":false},{"StartTime":21377.0,"Position":450.080383,"HyperDash":false},{"StartTime":21497.0,"Position":406.784882,"HyperDash":false}]},{"StartTime":21702.0,"Objects":[{"StartTime":21702.0,"Position":398.0,"HyperDash":false}]},{"StartTime":21839.0,"Objects":[{"StartTime":21839.0,"Position":337.0,"HyperDash":false},{"StartTime":21975.0,"Position":245.5466,"HyperDash":false}]},{"StartTime":22043.0,"Objects":[{"StartTime":22043.0,"Position":202.0,"HyperDash":false}]},{"StartTime":22111.0,"Objects":[{"StartTime":22111.0,"Position":202.0,"HyperDash":false},{"StartTime":22247.0,"Position":175.162018,"HyperDash":false}]},{"StartTime":22384.0,"Objects":[{"StartTime":22384.0,"Position":7.0,"HyperDash":false}]},{"StartTime":22589.0,"Objects":[{"StartTime":22589.0,"Position":7.0,"HyperDash":false}]},{"StartTime":22793.0,"Objects":[{"StartTime":22793.0,"Position":7.0,"HyperDash":false}]},{"StartTime":22930.0,"Objects":[{"StartTime":22930.0,"Position":61.0,"HyperDash":false},{"StartTime":23066.0,"Position":50.69364,"HyperDash":false}]},{"StartTime":23134.0,"Objects":[{"StartTime":23134.0,"Position":92.0,"HyperDash":false}]},{"StartTime":23202.0,"Objects":[{"StartTime":23202.0,"Position":92.0,"HyperDash":false},{"StartTime":23338.0,"Position":179.7528,"HyperDash":false}]},{"StartTime":23475.0,"Objects":[{"StartTime":23475.0,"Position":262.0,"HyperDash":false},{"StartTime":23559.0,"Position":335.717,"HyperDash":false},{"StartTime":23679.0,"Position":354.8034,"HyperDash":false}]},{"StartTime":23884.0,"Objects":[{"StartTime":23884.0,"Position":467.0,"HyperDash":false}]},{"StartTime":24021.0,"Objects":[{"StartTime":24021.0,"Position":430.0,"HyperDash":false},{"StartTime":24157.0,"Position":329.387817,"HyperDash":false}]},{"StartTime":24225.0,"Objects":[{"StartTime":24225.0,"Position":284.0,"HyperDash":false}]},{"StartTime":24293.0,"Objects":[{"StartTime":24293.0,"Position":284.0,"HyperDash":false},{"StartTime":24429.0,"Position":261.101257,"HyperDash":false}]},{"StartTime":24566.0,"Objects":[{"StartTime":24566.0,"Position":386.0,"HyperDash":false}]},{"StartTime":24771.0,"Objects":[{"StartTime":24771.0,"Position":386.0,"HyperDash":false}]},{"StartTime":24975.0,"Objects":[{"StartTime":24975.0,"Position":386.0,"HyperDash":false}]},{"StartTime":25111.0,"Objects":[{"StartTime":25111.0,"Position":432.0,"HyperDash":false},{"StartTime":25247.0,"Position":447.35553,"HyperDash":false}]},{"StartTime":25316.0,"Objects":[{"StartTime":25316.0,"Position":416.0,"HyperDash":false}]},{"StartTime":25384.0,"Objects":[{"StartTime":25384.0,"Position":416.0,"HyperDash":false},{"StartTime":25520.0,"Position":316.536438,"HyperDash":false}]},{"StartTime":25657.0,"Objects":[{"StartTime":25657.0,"Position":219.0,"HyperDash":false},{"StartTime":25741.0,"Position":178.386673,"HyperDash":false},{"StartTime":25861.0,"Position":167.07811,"HyperDash":false}]},{"StartTime":26066.0,"Objects":[{"StartTime":26066.0,"Position":40.0,"HyperDash":false}]},{"StartTime":26202.0,"Objects":[{"StartTime":26202.0,"Position":28.0,"HyperDash":false},{"StartTime":26338.0,"Position":101.876366,"HyperDash":false}]},{"StartTime":26407.0,"Objects":[{"StartTime":26407.0,"Position":125.0,"HyperDash":false}]},{"StartTime":26475.0,"Objects":[{"StartTime":26475.0,"Position":125.0,"HyperDash":false},{"StartTime":26611.0,"Position":142.871735,"HyperDash":false}]},{"StartTime":26748.0,"Objects":[{"StartTime":26748.0,"Position":221.0,"HyperDash":false}]},{"StartTime":26953.0,"Objects":[{"StartTime":26953.0,"Position":221.0,"HyperDash":false}]},{"StartTime":27157.0,"Objects":[{"StartTime":27157.0,"Position":221.0,"HyperDash":false}]},{"StartTime":27293.0,"Objects":[{"StartTime":27293.0,"Position":379.0,"HyperDash":false},{"StartTime":27429.0,"Position":479.272156,"HyperDash":false}]},{"StartTime":27498.0,"Objects":[{"StartTime":27498.0,"Position":510.0,"HyperDash":false}]},{"StartTime":27566.0,"Objects":[{"StartTime":27566.0,"Position":510.0,"HyperDash":false},{"StartTime":27702.0,"Position":491.238281,"HyperDash":false}]},{"StartTime":27839.0,"Objects":[{"StartTime":27839.0,"Position":503.0,"HyperDash":false},{"StartTime":27923.0,"Position":457.324524,"HyperDash":false},{"StartTime":28043.0,"Position":381.6685,"HyperDash":false}]},{"StartTime":28248.0,"Objects":[{"StartTime":28248.0,"Position":256.0,"HyperDash":false}]},{"StartTime":28384.0,"Objects":[{"StartTime":28384.0,"Position":190.0,"HyperDash":false}]},{"StartTime":28521.0,"Objects":[{"StartTime":28521.0,"Position":269.0,"HyperDash":false}]},{"StartTime":28589.0,"Objects":[{"StartTime":28589.0,"Position":272.0,"HyperDash":false}]},{"StartTime":28657.0,"Objects":[{"StartTime":28657.0,"Position":275.0,"HyperDash":false},{"StartTime":28793.0,"Position":264.133636,"HyperDash":false}]},{"StartTime":28930.0,"Objects":[{"StartTime":28930.0,"Position":179.0,"HyperDash":false}]},{"StartTime":28998.0,"Objects":[{"StartTime":28998.0,"Position":154.0,"HyperDash":false}]},{"StartTime":29066.0,"Objects":[{"StartTime":29066.0,"Position":135.0,"HyperDash":false}]},{"StartTime":29134.0,"Objects":[{"StartTime":29134.0,"Position":122.0,"HyperDash":false}]},{"StartTime":29202.0,"Objects":[{"StartTime":29202.0,"Position":118.0,"HyperDash":false},{"StartTime":29270.0,"Position":107.667114,"HyperDash":false},{"StartTime":29338.0,"Position":118.0,"HyperDash":false},{"StartTime":29406.0,"Position":107.667114,"HyperDash":false}]},{"StartTime":29475.0,"Objects":[{"StartTime":29475.0,"Position":45.0,"HyperDash":false},{"StartTime":29543.0,"Position":4.39538574,"HyperDash":false},{"StartTime":29611.0,"Position":45.0,"HyperDash":false},{"StartTime":29679.0,"Position":4.39538574,"HyperDash":false}]},{"StartTime":29748.0,"Objects":[{"StartTime":29748.0,"Position":102.0,"HyperDash":false},{"StartTime":29816.0,"Position":142.604614,"HyperDash":false},{"StartTime":29884.0,"Position":102.0,"HyperDash":false},{"StartTime":29952.0,"Position":142.604614,"HyperDash":false}]},{"StartTime":30021.0,"Objects":[{"StartTime":30021.0,"Position":193.0,"HyperDash":false},{"StartTime":30089.0,"Position":205.21228,"HyperDash":false},{"StartTime":30157.0,"Position":193.0,"HyperDash":false},{"StartTime":30225.0,"Position":205.21228,"HyperDash":false}]},{"StartTime":30293.0,"Objects":[{"StartTime":30293.0,"Position":291.0,"HyperDash":false},{"StartTime":30361.0,"Position":302.9382,"HyperDash":false},{"StartTime":30429.0,"Position":291.0,"HyperDash":false},{"StartTime":30497.0,"Position":302.9382,"HyperDash":false}]},{"StartTime":30566.0,"Objects":[{"StartTime":30566.0,"Position":391.0,"HyperDash":false}]},{"StartTime":30634.0,"Objects":[{"StartTime":30634.0,"Position":400.0,"HyperDash":false}]},{"StartTime":30702.0,"Objects":[{"StartTime":30702.0,"Position":409.0,"HyperDash":false}]},{"StartTime":30839.0,"Objects":[{"StartTime":30839.0,"Position":434.0,"HyperDash":false}]},{"StartTime":30907.0,"Objects":[{"StartTime":30907.0,"Position":425.0,"HyperDash":false}]},{"StartTime":30975.0,"Objects":[{"StartTime":30975.0,"Position":416.0,"HyperDash":false}]},{"StartTime":31111.0,"Objects":[{"StartTime":31111.0,"Position":512.0,"HyperDash":false},{"StartTime":31179.0,"Position":499.154633,"HyperDash":false},{"StartTime":31247.0,"Position":512.0,"HyperDash":false}]},{"StartTime":31384.0,"Objects":[{"StartTime":31384.0,"Position":435.0,"HyperDash":false},{"StartTime":31452.0,"Position":446.9382,"HyperDash":false},{"StartTime":31520.0,"Position":435.0,"HyperDash":false}]},{"StartTime":31657.0,"Objects":[{"StartTime":31657.0,"Position":381.0,"HyperDash":false},{"StartTime":31725.0,"Position":340.211151,"HyperDash":false},{"StartTime":31793.0,"Position":381.0,"HyperDash":false},{"StartTime":31861.0,"Position":340.211151,"HyperDash":false}]},{"StartTime":31930.0,"Objects":[{"StartTime":31930.0,"Position":251.0,"HyperDash":false},{"StartTime":31998.0,"Position":210.395386,"HyperDash":false},{"StartTime":32066.0,"Position":251.0,"HyperDash":false},{"StartTime":32134.0,"Position":210.395386,"HyperDash":false}]},{"StartTime":32202.0,"Objects":[{"StartTime":32202.0,"Position":146.0,"HyperDash":false},{"StartTime":32270.0,"Position":158.21228,"HyperDash":false},{"StartTime":32338.0,"Position":146.0,"HyperDash":false},{"StartTime":32406.0,"Position":158.21228,"HyperDash":false}]},{"StartTime":32475.0,"Objects":[{"StartTime":32475.0,"Position":56.0,"HyperDash":false},{"StartTime":32543.0,"Position":68.21229,"HyperDash":false},{"StartTime":32611.0,"Position":56.0,"HyperDash":false},{"StartTime":32679.0,"Position":68.21229,"HyperDash":false}]},{"StartTime":32748.0,"Objects":[{"StartTime":32748.0,"Position":22.0,"HyperDash":false}]},{"StartTime":32816.0,"Objects":[{"StartTime":32816.0,"Position":25.0,"HyperDash":false}]},{"StartTime":32884.0,"Objects":[{"StartTime":32884.0,"Position":28.0,"HyperDash":false}]},{"StartTime":33021.0,"Objects":[{"StartTime":33021.0,"Position":93.0,"HyperDash":false}]},{"StartTime":33089.0,"Objects":[{"StartTime":33089.0,"Position":90.0,"HyperDash":false}]},{"StartTime":33157.0,"Objects":[{"StartTime":33157.0,"Position":87.0,"HyperDash":false}]},{"StartTime":33293.0,"Objects":[{"StartTime":33293.0,"Position":168.0,"HyperDash":false}]},{"StartTime":33361.0,"Objects":[{"StartTime":33361.0,"Position":176.0,"HyperDash":false}]},{"StartTime":33430.0,"Objects":[{"StartTime":33430.0,"Position":184.0,"HyperDash":false},{"StartTime":33566.0,"Position":268.439758,"HyperDash":false}]},{"StartTime":33839.0,"Objects":[{"StartTime":33839.0,"Position":274.0,"HyperDash":false},{"StartTime":33907.0,"Position":261.78772,"HyperDash":false},{"StartTime":33975.0,"Position":274.0,"HyperDash":false},{"StartTime":34043.0,"Position":261.78772,"HyperDash":false}]},{"StartTime":34112.0,"Objects":[{"StartTime":34112.0,"Position":330.0,"HyperDash":false},{"StartTime":34180.0,"Position":342.21228,"HyperDash":false},{"StartTime":34248.0,"Position":330.0,"HyperDash":false},{"StartTime":34316.0,"Position":342.21228,"HyperDash":false}]},{"StartTime":34384.0,"Objects":[{"StartTime":34384.0,"Position":422.0,"HyperDash":false},{"StartTime":34452.0,"Position":462.788849,"HyperDash":false},{"StartTime":34520.0,"Position":422.0,"HyperDash":false},{"StartTime":34588.0,"Position":462.788849,"HyperDash":false}]},{"StartTime":34657.0,"Objects":[{"StartTime":34657.0,"Position":461.0,"HyperDash":false},{"StartTime":34725.0,"Position":501.6046,"HyperDash":false},{"StartTime":34793.0,"Position":461.0,"HyperDash":false},{"StartTime":34861.0,"Position":501.6046,"HyperDash":false}]},{"StartTime":34930.0,"Objects":[{"StartTime":34930.0,"Position":448.0,"HyperDash":false}]},{"StartTime":34998.0,"Objects":[{"StartTime":34998.0,"Position":439.0,"HyperDash":false}]},{"StartTime":35066.0,"Objects":[{"StartTime":35066.0,"Position":430.0,"HyperDash":false}]},{"StartTime":35202.0,"Objects":[{"StartTime":35202.0,"Position":321.0,"HyperDash":false}]},{"StartTime":35270.0,"Objects":[{"StartTime":35270.0,"Position":312.0,"HyperDash":false}]},{"StartTime":35338.0,"Objects":[{"StartTime":35338.0,"Position":303.0,"HyperDash":false}]},{"StartTime":35475.0,"Objects":[{"StartTime":35475.0,"Position":269.0,"HyperDash":false},{"StartTime":35543.0,"Position":228.395386,"HyperDash":false},{"StartTime":35611.0,"Position":269.0,"HyperDash":false}]},{"StartTime":35748.0,"Objects":[{"StartTime":35748.0,"Position":162.0,"HyperDash":false},{"StartTime":35816.0,"Position":202.788834,"HyperDash":false},{"StartTime":35884.0,"Position":162.0,"HyperDash":false}]},{"StartTime":36021.0,"Objects":[{"StartTime":36021.0,"Position":87.0,"HyperDash":false},{"StartTime":36089.0,"Position":99.21229,"HyperDash":false},{"StartTime":36157.0,"Position":87.0,"HyperDash":false},{"StartTime":36225.0,"Position":99.21229,"HyperDash":false}]},{"StartTime":36294.0,"Objects":[{"StartTime":36294.0,"Position":31.0,"HyperDash":false},{"StartTime":36362.0,"Position":18.7877159,"HyperDash":false},{"StartTime":36430.0,"Position":31.0,"HyperDash":false},{"StartTime":36498.0,"Position":18.7877159,"HyperDash":false}]},{"StartTime":36566.0,"Objects":[{"StartTime":36566.0,"Position":101.0,"HyperDash":false},{"StartTime":36634.0,"Position":141.788834,"HyperDash":false},{"StartTime":36702.0,"Position":101.0,"HyperDash":false},{"StartTime":36770.0,"Position":141.788834,"HyperDash":false}]},{"StartTime":36839.0,"Objects":[{"StartTime":36839.0,"Position":184.0,"HyperDash":false},{"StartTime":36907.0,"Position":224.604614,"HyperDash":false},{"StartTime":36975.0,"Position":184.0,"HyperDash":false},{"StartTime":37043.0,"Position":224.604614,"HyperDash":false}]},{"StartTime":37111.0,"Objects":[{"StartTime":37111.0,"Position":304.0,"HyperDash":false}]},{"StartTime":37179.0,"Objects":[{"StartTime":37179.0,"Position":307.0,"HyperDash":false}]},{"StartTime":37247.0,"Objects":[{"StartTime":37247.0,"Position":310.0,"HyperDash":false}]},{"StartTime":37384.0,"Objects":[{"StartTime":37384.0,"Position":392.0,"HyperDash":false}]},{"StartTime":37452.0,"Objects":[{"StartTime":37452.0,"Position":395.0,"HyperDash":false}]},{"StartTime":37520.0,"Objects":[{"StartTime":37520.0,"Position":398.0,"HyperDash":false}]},{"StartTime":37657.0,"Objects":[{"StartTime":37657.0,"Position":341.0,"HyperDash":false},{"StartTime":37725.0,"Position":356.784119,"HyperDash":false},{"StartTime":37793.0,"Position":341.0,"HyperDash":false},{"StartTime":37861.0,"Position":356.784119,"HyperDash":false}]},{"StartTime":37930.0,"Objects":[{"StartTime":37930.0,"Position":352.0,"HyperDash":false},{"StartTime":37998.0,"Position":367.784119,"HyperDash":false},{"StartTime":38066.0,"Position":352.0,"HyperDash":false},{"StartTime":38134.0,"Position":367.784119,"HyperDash":false}]},{"StartTime":38202.0,"Objects":[{"StartTime":38202.0,"Position":449.0,"HyperDash":false},{"StartTime":38261.0,"Position":474.06,"HyperDash":false},{"StartTime":38320.0,"Position":479.854156,"HyperDash":false},{"StartTime":38379.0,"Position":497.1734,"HyperDash":false},{"StartTime":38474.0,"Position":487.267334,"HyperDash":false}]},{"StartTime":38748.0,"Objects":[{"StartTime":38748.0,"Position":487.0,"HyperDash":false},{"StartTime":38807.0,"Position":440.148621,"HyperDash":false},{"StartTime":38866.0,"Position":413.297272,"HyperDash":false},{"StartTime":38925.0,"Position":408.4459,"HyperDash":false},{"StartTime":39020.0,"Position":353.9903,"HyperDash":false}]},{"StartTime":39293.0,"Objects":[{"StartTime":39293.0,"Position":403.0,"HyperDash":false},{"StartTime":39352.0,"Position":361.224365,"HyperDash":false},{"StartTime":39411.0,"Position":329.028229,"HyperDash":false},{"StartTime":39470.0,"Position":319.025146,"HyperDash":false},{"StartTime":39565.0,"Position":277.9407,"HyperDash":false}]},{"StartTime":39702.0,"Objects":[{"StartTime":39702.0,"Position":277.0,"HyperDash":false}]},{"StartTime":39839.0,"Objects":[{"StartTime":39839.0,"Position":155.0,"HyperDash":false},{"StartTime":39975.0,"Position":184.883255,"HyperDash":false}]},{"StartTime":40111.0,"Objects":[{"StartTime":40111.0,"Position":65.0,"HyperDash":false}]},{"StartTime":40384.0,"Objects":[{"StartTime":40384.0,"Position":65.0,"HyperDash":false},{"StartTime":40520.0,"Position":148.5545,"HyperDash":false}]},{"StartTime":40657.0,"Objects":[{"StartTime":40657.0,"Position":90.0,"HyperDash":false},{"StartTime":40793.0,"Position":6.445488,"HyperDash":false}]},{"StartTime":40930.0,"Objects":[{"StartTime":40930.0,"Position":180.0,"HyperDash":false}]},{"StartTime":41066.0,"Objects":[{"StartTime":41066.0,"Position":280.0,"HyperDash":false}]},{"StartTime":41134.0,"Objects":[{"StartTime":41134.0,"Position":280.0,"HyperDash":false}]},{"StartTime":41202.0,"Objects":[{"StartTime":41202.0,"Position":280.0,"HyperDash":false},{"StartTime":41338.0,"Position":363.5545,"HyperDash":false}]},{"StartTime":41475.0,"Objects":[{"StartTime":41475.0,"Position":208.0,"HyperDash":false}]},{"StartTime":41611.0,"Objects":[{"StartTime":41611.0,"Position":208.0,"HyperDash":false}]},{"StartTime":41748.0,"Objects":[{"StartTime":41748.0,"Position":372.0,"HyperDash":false},{"StartTime":41884.0,"Position":288.4455,"HyperDash":false}]},{"StartTime":42021.0,"Objects":[{"StartTime":42021.0,"Position":170.0,"HyperDash":false},{"StartTime":42157.0,"Position":187.164719,"HyperDash":false}]},{"StartTime":42293.0,"Objects":[{"StartTime":42293.0,"Position":64.0,"HyperDash":false},{"StartTime":42361.0,"Position":71.60263,"HyperDash":false},{"StartTime":42429.0,"Position":64.0,"HyperDash":false},{"StartTime":42497.0,"Position":71.60263,"HyperDash":false}]},{"StartTime":42566.0,"Objects":[{"StartTime":42566.0,"Position":25.0,"HyperDash":false},{"StartTime":42625.0,"Position":29.524353,"HyperDash":false},{"StartTime":42684.0,"Position":56.72582,"HyperDash":false},{"StartTime":42743.0,"Position":46.7086868,"HyperDash":false},{"StartTime":42838.0,"Position":32.0564842,"HyperDash":false}]},{"StartTime":43111.0,"Objects":[{"StartTime":43111.0,"Position":32.0,"HyperDash":false},{"StartTime":43170.0,"Position":77.73514,"HyperDash":false},{"StartTime":43229.0,"Position":72.4702759,"HyperDash":false},{"StartTime":43288.0,"Position":123.205421,"HyperDash":false},{"StartTime":43383.0,"Position":164.473862,"HyperDash":false}]},{"StartTime":43657.0,"Objects":[{"StartTime":43657.0,"Position":420.0,"HyperDash":false},{"StartTime":43716.0,"Position":410.224365,"HyperDash":false},{"StartTime":43775.0,"Position":376.028229,"HyperDash":false},{"StartTime":43834.0,"Position":351.025146,"HyperDash":false},{"StartTime":43929.0,"Position":294.9407,"HyperDash":false}]},{"StartTime":44066.0,"Objects":[{"StartTime":44066.0,"Position":294.0,"HyperDash":false}]},{"StartTime":44202.0,"Objects":[{"StartTime":44202.0,"Position":204.0,"HyperDash":false},{"StartTime":44338.0,"Position":217.130188,"HyperDash":false}]},{"StartTime":44475.0,"Objects":[{"StartTime":44475.0,"Position":381.0,"HyperDash":false}]},{"StartTime":44748.0,"Objects":[{"StartTime":44748.0,"Position":381.0,"HyperDash":false},{"StartTime":44884.0,"Position":392.2908,"HyperDash":false}]},{"StartTime":45021.0,"Objects":[{"StartTime":45021.0,"Position":500.0,"HyperDash":false},{"StartTime":45157.0,"Position":488.7092,"HyperDash":false}]},{"StartTime":45293.0,"Objects":[{"StartTime":45293.0,"Position":285.0,"HyperDash":false}]},{"StartTime":45430.0,"Objects":[{"StartTime":45430.0,"Position":397.0,"HyperDash":false}]},{"StartTime":45498.0,"Objects":[{"StartTime":45498.0,"Position":397.0,"HyperDash":false}]},{"StartTime":45566.0,"Objects":[{"StartTime":45566.0,"Position":397.0,"HyperDash":false},{"StartTime":45702.0,"Position":385.7092,"HyperDash":false}]},{"StartTime":45839.0,"Objects":[{"StartTime":45839.0,"Position":208.0,"HyperDash":false}]},{"StartTime":45907.0,"Objects":[{"StartTime":45907.0,"Position":208.0,"HyperDash":false}]},{"StartTime":45975.0,"Objects":[{"StartTime":45975.0,"Position":208.0,"HyperDash":false},{"StartTime":46111.0,"Position":131.34523,"HyperDash":false}]},{"StartTime":46248.0,"Objects":[{"StartTime":46248.0,"Position":47.0,"HyperDash":false}]},{"StartTime":46316.0,"Objects":[{"StartTime":46316.0,"Position":54.0,"HyperDash":false}]},{"StartTime":46384.0,"Objects":[{"StartTime":46384.0,"Position":61.0,"HyperDash":false}]},{"StartTime":46521.0,"Objects":[{"StartTime":46521.0,"Position":118.0,"HyperDash":false},{"StartTime":46589.0,"Position":111.337379,"HyperDash":false},{"StartTime":46657.0,"Position":118.0,"HyperDash":false},{"StartTime":46725.0,"Position":111.337379,"HyperDash":false},{"StartTime":46793.0,"Position":118.0,"HyperDash":false},{"StartTime":46861.0,"Position":111.337379,"HyperDash":false}]},{"StartTime":46930.0,"Objects":[{"StartTime":46930.0,"Position":186.0,"HyperDash":false},{"StartTime":47066.0,"Position":274.623718,"HyperDash":false}]},{"StartTime":47202.0,"Objects":[{"StartTime":47202.0,"Position":446.0,"HyperDash":false},{"StartTime":47338.0,"Position":357.889038,"HyperDash":false}]},{"StartTime":47475.0,"Objects":[{"StartTime":47475.0,"Position":367.0,"HyperDash":false},{"StartTime":47611.0,"Position":390.840118,"HyperDash":false}]},{"StartTime":47748.0,"Objects":[{"StartTime":47748.0,"Position":297.0,"HyperDash":false},{"StartTime":47884.0,"Position":319.863068,"HyperDash":false}]},{"StartTime":48021.0,"Objects":[{"StartTime":48021.0,"Position":243.0,"HyperDash":false},{"StartTime":48157.0,"Position":143.595367,"HyperDash":false}]},{"StartTime":48293.0,"Objects":[{"StartTime":48293.0,"Position":188.0,"HyperDash":false}]},{"StartTime":48430.0,"Objects":[{"StartTime":48430.0,"Position":188.0,"HyperDash":false}]},{"StartTime":48566.0,"Objects":[{"StartTime":48566.0,"Position":59.0,"HyperDash":false},{"StartTime":48702.0,"Position":43.64902,"HyperDash":false}]},{"StartTime":48839.0,"Objects":[{"StartTime":48839.0,"Position":174.0,"HyperDash":false},{"StartTime":48975.0,"Position":273.404633,"HyperDash":false}]},{"StartTime":49111.0,"Objects":[{"StartTime":49111.0,"Position":423.0,"HyperDash":false},{"StartTime":49247.0,"Position":415.1793,"HyperDash":false}]},{"StartTime":49384.0,"Objects":[{"StartTime":49384.0,"Position":346.0,"HyperDash":false},{"StartTime":49520.0,"Position":433.371735,"HyperDash":true}]},{"StartTime":49657.0,"Objects":[{"StartTime":49657.0,"Position":217.0,"HyperDash":false}]},{"StartTime":49793.0,"Objects":[{"StartTime":49793.0,"Position":208.0,"HyperDash":false}]},{"StartTime":49861.0,"Objects":[{"StartTime":49861.0,"Position":208.0,"HyperDash":false}]},{"StartTime":49930.0,"Objects":[{"StartTime":49930.0,"Position":208.0,"HyperDash":false},{"StartTime":50066.0,"Position":107.101242,"HyperDash":false}]},{"StartTime":50202.0,"Objects":[{"StartTime":50202.0,"Position":45.0,"HyperDash":false}]},{"StartTime":50338.0,"Objects":[{"StartTime":50338.0,"Position":108.0,"HyperDash":false}]},{"StartTime":50475.0,"Objects":[{"StartTime":50475.0,"Position":107.0,"HyperDash":false}]},{"StartTime":50611.0,"Objects":[{"StartTime":50611.0,"Position":44.0,"HyperDash":false}]},{"StartTime":50748.0,"Objects":[{"StartTime":50748.0,"Position":70.0,"HyperDash":false},{"StartTime":50807.0,"Position":117.635452,"HyperDash":false},{"StartTime":50866.0,"Position":164.2709,"HyperDash":false},{"StartTime":50925.0,"Position":211.774979,"HyperDash":false},{"StartTime":51020.0,"Position":266.492462,"HyperDash":false}]},{"StartTime":51157.0,"Objects":[{"StartTime":51157.0,"Position":441.0,"HyperDash":false}]},{"StartTime":51225.0,"Objects":[{"StartTime":51225.0,"Position":434.0,"HyperDash":false}]},{"StartTime":51293.0,"Objects":[{"StartTime":51293.0,"Position":427.0,"HyperDash":false},{"StartTime":51429.0,"Position":405.05188,"HyperDash":false}]},{"StartTime":51566.0,"Objects":[{"StartTime":51566.0,"Position":482.0,"HyperDash":false},{"StartTime":51702.0,"Position":460.05188,"HyperDash":false}]},{"StartTime":51839.0,"Objects":[{"StartTime":51839.0,"Position":357.0,"HyperDash":false},{"StartTime":51975.0,"Position":265.6038,"HyperDash":false}]},{"StartTime":52111.0,"Objects":[{"StartTime":52111.0,"Position":119.0,"HyperDash":false},{"StartTime":52247.0,"Position":210.2502,"HyperDash":false}]},{"StartTime":52384.0,"Objects":[{"StartTime":52384.0,"Position":164.0,"HyperDash":false},{"StartTime":52520.0,"Position":74.00247,"HyperDash":false}]},{"StartTime":52657.0,"Objects":[{"StartTime":52657.0,"Position":0.0,"HyperDash":false}]},{"StartTime":52793.0,"Objects":[{"StartTime":52793.0,"Position":0.0,"HyperDash":false}]},{"StartTime":52930.0,"Objects":[{"StartTime":52930.0,"Position":124.0,"HyperDash":false},{"StartTime":53066.0,"Position":225.212341,"HyperDash":false}]},{"StartTime":53202.0,"Objects":[{"StartTime":53202.0,"Position":316.0,"HyperDash":false},{"StartTime":53338.0,"Position":303.34845,"HyperDash":false}]},{"StartTime":53475.0,"Objects":[{"StartTime":53475.0,"Position":332.0,"HyperDash":false},{"StartTime":53611.0,"Position":415.923523,"HyperDash":false}]},{"StartTime":53748.0,"Objects":[{"StartTime":53748.0,"Position":512.0,"HyperDash":false},{"StartTime":53884.0,"Position":428.076477,"HyperDash":false}]},{"StartTime":54021.0,"Objects":[{"StartTime":54021.0,"Position":512.0,"HyperDash":false}]},{"StartTime":54157.0,"Objects":[{"StartTime":54157.0,"Position":363.0,"HyperDash":false}]},{"StartTime":54225.0,"Objects":[{"StartTime":54225.0,"Position":363.0,"HyperDash":false}]},{"StartTime":54293.0,"Objects":[{"StartTime":54293.0,"Position":363.0,"HyperDash":false},{"StartTime":54429.0,"Position":262.3189,"HyperDash":false}]},{"StartTime":54566.0,"Objects":[{"StartTime":54566.0,"Position":308.0,"HyperDash":false}]},{"StartTime":54634.0,"Objects":[{"StartTime":54634.0,"Position":269.0,"HyperDash":false}]},{"StartTime":54702.0,"Objects":[{"StartTime":54702.0,"Position":227.0,"HyperDash":false}]},{"StartTime":54770.0,"Objects":[{"StartTime":54770.0,"Position":193.0,"HyperDash":false}]},{"StartTime":54838.0,"Objects":[{"StartTime":54838.0,"Position":175.0,"HyperDash":false}]},{"StartTime":54975.0,"Objects":[{"StartTime":54975.0,"Position":81.0,"HyperDash":false}]},{"StartTime":55043.0,"Objects":[{"StartTime":55043.0,"Position":74.0,"HyperDash":false}]},{"StartTime":55111.0,"Objects":[{"StartTime":55111.0,"Position":67.0,"HyperDash":false}]},{"StartTime":55248.0,"Objects":[{"StartTime":55248.0,"Position":18.0,"HyperDash":false},{"StartTime":55316.0,"Position":25.9951439,"HyperDash":false},{"StartTime":55384.0,"Position":18.0,"HyperDash":false},{"StartTime":55452.0,"Position":25.9951439,"HyperDash":false},{"StartTime":55520.0,"Position":18.0,"HyperDash":false},{"StartTime":55588.0,"Position":25.9951439,"HyperDash":false}]},{"StartTime":55657.0,"Objects":[{"StartTime":55657.0,"Position":87.0,"HyperDash":false},{"StartTime":55725.0,"Position":127.788834,"HyperDash":false},{"StartTime":55793.0,"Position":87.0,"HyperDash":false},{"StartTime":55861.0,"Position":127.788834,"HyperDash":false}]},{"StartTime":55929.0,"Objects":[{"StartTime":55929.0,"Position":175.0,"HyperDash":false},{"StartTime":55997.0,"Position":215.604614,"HyperDash":false},{"StartTime":56065.0,"Position":175.0,"HyperDash":false},{"StartTime":56133.0,"Position":215.604614,"HyperDash":false}]},{"StartTime":56202.0,"Objects":[{"StartTime":56202.0,"Position":295.0,"HyperDash":false},{"StartTime":56270.0,"Position":307.21228,"HyperDash":false},{"StartTime":56338.0,"Position":295.0,"HyperDash":false},{"StartTime":56406.0,"Position":307.21228,"HyperDash":false}]},{"StartTime":56475.0,"Objects":[{"StartTime":56475.0,"Position":265.0,"HyperDash":false},{"StartTime":56543.0,"Position":252.78772,"HyperDash":false},{"StartTime":56611.0,"Position":265.0,"HyperDash":false},{"StartTime":56679.0,"Position":252.78772,"HyperDash":false}]},{"StartTime":56748.0,"Objects":[{"StartTime":56748.0,"Position":327.0,"HyperDash":false}]},{"StartTime":56816.0,"Objects":[{"StartTime":56816.0,"Position":336.0,"HyperDash":false}]},{"StartTime":56884.0,"Objects":[{"StartTime":56884.0,"Position":345.0,"HyperDash":false}]},{"StartTime":57021.0,"Objects":[{"StartTime":57021.0,"Position":414.0,"HyperDash":false}]},{"StartTime":57089.0,"Objects":[{"StartTime":57089.0,"Position":423.0,"HyperDash":false}]},{"StartTime":57157.0,"Objects":[{"StartTime":57157.0,"Position":432.0,"HyperDash":false}]},{"StartTime":57293.0,"Objects":[{"StartTime":57293.0,"Position":502.0,"HyperDash":false},{"StartTime":57361.0,"Position":489.78772,"HyperDash":false},{"StartTime":57429.0,"Position":502.0,"HyperDash":false}]},{"StartTime":57566.0,"Objects":[{"StartTime":57566.0,"Position":431.0,"HyperDash":false},{"StartTime":57634.0,"Position":443.21228,"HyperDash":false},{"StartTime":57702.0,"Position":431.0,"HyperDash":false}]},{"StartTime":57839.0,"Objects":[{"StartTime":57839.0,"Position":356.0,"HyperDash":false},{"StartTime":57907.0,"Position":343.78772,"HyperDash":false},{"StartTime":57975.0,"Position":356.0,"HyperDash":false},{"StartTime":58043.0,"Position":343.78772,"HyperDash":false}]},{"StartTime":58112.0,"Objects":[{"StartTime":58112.0,"Position":294.0,"HyperDash":false},{"StartTime":58180.0,"Position":334.7076,"HyperDash":false},{"StartTime":58248.0,"Position":294.0,"HyperDash":false},{"StartTime":58316.0,"Position":334.7076,"HyperDash":false}]},{"StartTime":58384.0,"Objects":[{"StartTime":58384.0,"Position":205.0,"HyperDash":false},{"StartTime":58452.0,"Position":192.78772,"HyperDash":false},{"StartTime":58520.0,"Position":205.0,"HyperDash":false},{"StartTime":58588.0,"Position":192.78772,"HyperDash":false}]},{"StartTime":58657.0,"Objects":[{"StartTime":58657.0,"Position":151.0,"HyperDash":false},{"StartTime":58725.0,"Position":110.292381,"HyperDash":false},{"StartTime":58793.0,"Position":151.0,"HyperDash":false},{"StartTime":58861.0,"Position":110.292381,"HyperDash":false}]},{"StartTime":58930.0,"Objects":[{"StartTime":58930.0,"Position":21.0,"HyperDash":false}]},{"StartTime":58998.0,"Objects":[{"StartTime":58998.0,"Position":18.0,"HyperDash":false}]},{"StartTime":59066.0,"Objects":[{"StartTime":59066.0,"Position":15.0,"HyperDash":false}]},{"StartTime":59202.0,"Objects":[{"StartTime":59202.0,"Position":96.0,"HyperDash":false}]},{"StartTime":59270.0,"Objects":[{"StartTime":59270.0,"Position":93.0,"HyperDash":false}]},{"StartTime":59338.0,"Objects":[{"StartTime":59338.0,"Position":90.0,"HyperDash":false}]},{"StartTime":59475.0,"Objects":[{"StartTime":59475.0,"Position":38.0,"HyperDash":false}]},{"StartTime":59543.0,"Objects":[{"StartTime":59543.0,"Position":41.0,"HyperDash":false}]},{"StartTime":59611.0,"Objects":[{"StartTime":59611.0,"Position":44.0,"HyperDash":false},{"StartTime":59747.0,"Position":35.8773422,"HyperDash":false}]},{"StartTime":60021.0,"Objects":[{"StartTime":60021.0,"Position":227.0,"HyperDash":false},{"StartTime":60089.0,"Position":214.78772,"HyperDash":false},{"StartTime":60157.0,"Position":227.0,"HyperDash":false},{"StartTime":60225.0,"Position":214.78772,"HyperDash":false}]},{"StartTime":60294.0,"Objects":[{"StartTime":60294.0,"Position":257.0,"HyperDash":false},{"StartTime":60362.0,"Position":269.21228,"HyperDash":false},{"StartTime":60430.0,"Position":257.0,"HyperDash":false},{"StartTime":60498.0,"Position":269.21228,"HyperDash":false}]},{"StartTime":60566.0,"Objects":[{"StartTime":60566.0,"Position":357.0,"HyperDash":false},{"StartTime":60634.0,"Position":397.788849,"HyperDash":false},{"StartTime":60702.0,"Position":357.0,"HyperDash":false},{"StartTime":60770.0,"Position":397.788849,"HyperDash":false}]},{"StartTime":60838.0,"Objects":[{"StartTime":60838.0,"Position":445.0,"HyperDash":false},{"StartTime":60906.0,"Position":485.6046,"HyperDash":false},{"StartTime":60974.0,"Position":445.0,"HyperDash":false},{"StartTime":61042.0,"Position":485.6046,"HyperDash":false}]},{"StartTime":61111.0,"Objects":[{"StartTime":61111.0,"Position":496.0,"HyperDash":false}]},{"StartTime":61179.0,"Objects":[{"StartTime":61179.0,"Position":493.0,"HyperDash":false}]},{"StartTime":61247.0,"Objects":[{"StartTime":61247.0,"Position":490.0,"HyperDash":false}]},{"StartTime":61384.0,"Objects":[{"StartTime":61384.0,"Position":420.0,"HyperDash":false}]},{"StartTime":61452.0,"Objects":[{"StartTime":61452.0,"Position":417.0,"HyperDash":false}]},{"StartTime":61521.0,"Objects":[{"StartTime":61521.0,"Position":414.0,"HyperDash":false}]},{"StartTime":61657.0,"Objects":[{"StartTime":61657.0,"Position":389.0,"HyperDash":false},{"StartTime":61725.0,"Position":348.2924,"HyperDash":false},{"StartTime":61793.0,"Position":389.0,"HyperDash":false}]},{"StartTime":61930.0,"Objects":[{"StartTime":61930.0,"Position":277.0,"HyperDash":false},{"StartTime":61998.0,"Position":236.292389,"HyperDash":false},{"StartTime":62066.0,"Position":277.0,"HyperDash":false}]},{"StartTime":62202.0,"Objects":[{"StartTime":62202.0,"Position":161.0,"HyperDash":false},{"StartTime":62270.0,"Position":148.78772,"HyperDash":false},{"StartTime":62338.0,"Position":161.0,"HyperDash":false},{"StartTime":62406.0,"Position":148.78772,"HyperDash":false}]},{"StartTime":62475.0,"Objects":[{"StartTime":62475.0,"Position":142.0,"HyperDash":false},{"StartTime":62543.0,"Position":101.292381,"HyperDash":false},{"StartTime":62611.0,"Position":142.0,"HyperDash":false},{"StartTime":62679.0,"Position":101.292381,"HyperDash":false}]},{"StartTime":62748.0,"Objects":[{"StartTime":62748.0,"Position":2.0,"HyperDash":false},{"StartTime":62816.0,"Position":14.212285,"HyperDash":false},{"StartTime":62884.0,"Position":2.0,"HyperDash":false},{"StartTime":62952.0,"Position":14.212285,"HyperDash":false}]},{"StartTime":63021.0,"Objects":[{"StartTime":63021.0,"Position":0.0,"HyperDash":false},{"StartTime":63089.0,"Position":40.70762,"HyperDash":false},{"StartTime":63157.0,"Position":0.0,"HyperDash":false},{"StartTime":63225.0,"Position":40.70762,"HyperDash":false}]},{"StartTime":63293.0,"Objects":[{"StartTime":63293.0,"Position":95.0,"HyperDash":false}]},{"StartTime":63361.0,"Objects":[{"StartTime":63361.0,"Position":104.0,"HyperDash":false}]},{"StartTime":63429.0,"Objects":[{"StartTime":63429.0,"Position":113.0,"HyperDash":false}]},{"StartTime":63566.0,"Objects":[{"StartTime":63566.0,"Position":189.0,"HyperDash":false}]},{"StartTime":63634.0,"Objects":[{"StartTime":63634.0,"Position":198.0,"HyperDash":false}]},{"StartTime":63702.0,"Objects":[{"StartTime":63702.0,"Position":207.0,"HyperDash":false}]},{"StartTime":63839.0,"Objects":[{"StartTime":63839.0,"Position":281.0,"HyperDash":false},{"StartTime":63907.0,"Position":322.273315,"HyperDash":false},{"StartTime":63975.0,"Position":281.0,"HyperDash":false},{"StartTime":64043.0,"Position":322.273315,"HyperDash":false}]},{"StartTime":64111.0,"Objects":[{"StartTime":64111.0,"Position":362.0,"HyperDash":false},{"StartTime":64179.0,"Position":403.273315,"HyperDash":false},{"StartTime":64247.0,"Position":362.0,"HyperDash":false},{"StartTime":64315.0,"Position":403.273315,"HyperDash":false}]},{"StartTime":64384.0,"Objects":[{"StartTime":64384.0,"Position":478.0,"HyperDash":false},{"StartTime":64443.0,"Position":442.243439,"HyperDash":false},{"StartTime":64502.0,"Position":440.1484,"HyperDash":false},{"StartTime":64561.0,"Position":427.0997,"HyperDash":false},{"StartTime":64656.0,"Position":444.9422,"HyperDash":false}]},{"StartTime":64930.0,"Objects":[{"StartTime":64930.0,"Position":485.0,"HyperDash":false},{"StartTime":64989.0,"Position":461.072876,"HyperDash":false},{"StartTime":65048.0,"Position":436.145752,"HyperDash":false},{"StartTime":65107.0,"Position":402.2186,"HyperDash":false},{"StartTime":65202.0,"Position":351.641022,"HyperDash":false}]},{"StartTime":65475.0,"Objects":[{"StartTime":65475.0,"Position":222.0,"HyperDash":false},{"StartTime":65534.0,"Position":184.205688,"HyperDash":false},{"StartTime":65593.0,"Position":161.582535,"HyperDash":false},{"StartTime":65652.0,"Position":155.982361,"HyperDash":false},{"StartTime":65747.0,"Position":104.778061,"HyperDash":false}]},{"StartTime":65884.0,"Objects":[{"StartTime":65884.0,"Position":104.0,"HyperDash":false}]},{"StartTime":66021.0,"Objects":[{"StartTime":66021.0,"Position":16.0,"HyperDash":false},{"StartTime":66157.0,"Position":28.7026157,"HyperDash":false}]},{"StartTime":66225.0,"Objects":[{"StartTime":66225.0,"Position":28.0,"HyperDash":false}]},{"StartTime":66293.0,"Objects":[{"StartTime":66293.0,"Position":28.0,"HyperDash":false}]},{"StartTime":66566.0,"Objects":[{"StartTime":66566.0,"Position":90.0,"HyperDash":false},{"StartTime":66702.0,"Position":76.934906,"HyperDash":false}]},{"StartTime":66839.0,"Objects":[{"StartTime":66839.0,"Position":256.0,"HyperDash":false},{"StartTime":66975.0,"Position":242.9349,"HyperDash":false}]},{"StartTime":67111.0,"Objects":[{"StartTime":67111.0,"Position":186.0,"HyperDash":false}]},{"StartTime":67248.0,"Objects":[{"StartTime":67248.0,"Position":273.0,"HyperDash":false}]},{"StartTime":67316.0,"Objects":[{"StartTime":67316.0,"Position":273.0,"HyperDash":false}]},{"StartTime":67384.0,"Objects":[{"StartTime":67384.0,"Position":273.0,"HyperDash":false},{"StartTime":67520.0,"Position":357.364716,"HyperDash":false}]},{"StartTime":67657.0,"Objects":[{"StartTime":67657.0,"Position":471.0,"HyperDash":false}]},{"StartTime":67793.0,"Objects":[{"StartTime":67793.0,"Position":471.0,"HyperDash":false}]},{"StartTime":67930.0,"Objects":[{"StartTime":67930.0,"Position":392.0,"HyperDash":false},{"StartTime":68066.0,"Position":307.582184,"HyperDash":false}]},{"StartTime":68202.0,"Objects":[{"StartTime":68202.0,"Position":165.0,"HyperDash":false},{"StartTime":68338.0,"Position":178.0651,"HyperDash":false}]},{"StartTime":68475.0,"Objects":[{"StartTime":68475.0,"Position":266.0,"HyperDash":false},{"StartTime":68543.0,"Position":307.8938,"HyperDash":false},{"StartTime":68611.0,"Position":266.0,"HyperDash":false},{"StartTime":68679.0,"Position":307.8938,"HyperDash":false}]},{"StartTime":68748.0,"Objects":[{"StartTime":68748.0,"Position":358.0,"HyperDash":false},{"StartTime":68807.0,"Position":396.968262,"HyperDash":false},{"StartTime":68866.0,"Position":418.199738,"HyperDash":false},{"StartTime":68925.0,"Position":452.599548,"HyperDash":false},{"StartTime":69020.0,"Position":484.638855,"HyperDash":false}]},{"StartTime":69293.0,"Objects":[{"StartTime":69293.0,"Position":447.0,"HyperDash":false},{"StartTime":69352.0,"Position":453.674744,"HyperDash":false},{"StartTime":69411.0,"Position":437.3495,"HyperDash":false},{"StartTime":69470.0,"Position":444.024231,"HyperDash":false},{"StartTime":69565.0,"Position":468.551361,"HyperDash":false}]},{"StartTime":69839.0,"Objects":[{"StartTime":69839.0,"Position":343.0,"HyperDash":false},{"StartTime":69898.0,"Position":329.563446,"HyperDash":false},{"StartTime":69957.0,"Position":311.8805,"HyperDash":false},{"StartTime":70016.0,"Position":271.0514,"HyperDash":false},{"StartTime":70111.0,"Position":243.183487,"HyperDash":false}]},{"StartTime":70248.0,"Objects":[{"StartTime":70248.0,"Position":216.0,"HyperDash":false}]},{"StartTime":70316.0,"Objects":[{"StartTime":70316.0,"Position":216.0,"HyperDash":false}]},{"StartTime":70384.0,"Objects":[{"StartTime":70384.0,"Position":216.0,"HyperDash":false},{"StartTime":70520.0,"Position":154.538864,"HyperDash":false}]},{"StartTime":70657.0,"Objects":[{"StartTime":70657.0,"Position":58.0,"HyperDash":false}]},{"StartTime":70930.0,"Objects":[{"StartTime":70930.0,"Position":58.0,"HyperDash":false},{"StartTime":71066.0,"Position":48.7692032,"HyperDash":false}]},{"StartTime":71202.0,"Objects":[{"StartTime":71202.0,"Position":129.0,"HyperDash":false},{"StartTime":71338.0,"Position":138.2308,"HyperDash":false}]},{"StartTime":71475.0,"Objects":[{"StartTime":71475.0,"Position":132.0,"HyperDash":false}]},{"StartTime":71611.0,"Objects":[{"StartTime":71611.0,"Position":228.0,"HyperDash":false}]},{"StartTime":71680.0,"Objects":[{"StartTime":71680.0,"Position":228.0,"HyperDash":false}]},{"StartTime":71748.0,"Objects":[{"StartTime":71748.0,"Position":228.0,"HyperDash":false},{"StartTime":71884.0,"Position":312.5163,"HyperDash":false}]},{"StartTime":72021.0,"Objects":[{"StartTime":72021.0,"Position":382.0,"HyperDash":false}]},{"StartTime":72089.0,"Objects":[{"StartTime":72089.0,"Position":414.0,"HyperDash":false}]},{"StartTime":72157.0,"Objects":[{"StartTime":72157.0,"Position":448.0,"HyperDash":false}]},{"StartTime":72225.0,"Objects":[{"StartTime":72225.0,"Position":478.0,"HyperDash":false}]},{"StartTime":72293.0,"Objects":[{"StartTime":72293.0,"Position":500.0,"HyperDash":false}]},{"StartTime":72430.0,"Objects":[{"StartTime":72430.0,"Position":453.0,"HyperDash":false}]},{"StartTime":72498.0,"Objects":[{"StartTime":72498.0,"Position":449.0,"HyperDash":false}]},{"StartTime":72566.0,"Objects":[{"StartTime":72566.0,"Position":445.0,"HyperDash":false},{"StartTime":72634.0,"Position":427.8085,"HyperDash":false},{"StartTime":72702.0,"Position":445.0,"HyperDash":false}]},{"StartTime":72839.0,"Objects":[{"StartTime":72839.0,"Position":486.0,"HyperDash":false},{"StartTime":72907.0,"Position":502.9824,"HyperDash":false}]},{"StartTime":72975.0,"Objects":[{"StartTime":72975.0,"Position":414.0,"HyperDash":false},{"StartTime":73043.0,"Position":430.9824,"HyperDash":false}]},{"StartTime":73111.0,"Objects":[{"StartTime":73111.0,"Position":344.0,"HyperDash":false}]},{"StartTime":75293.0,"Objects":[{"StartTime":75293.0,"Position":62.0,"HyperDash":false}]},{"StartTime":76930.0,"Objects":[{"StartTime":76930.0,"Position":403.0,"HyperDash":false},{"StartTime":77020.0,"Position":467.2785,"HyperDash":false},{"StartTime":77111.0,"Position":403.0,"HyperDash":false},{"StartTime":77202.0,"Position":467.2785,"HyperDash":false},{"StartTime":77293.0,"Position":403.0,"HyperDash":false},{"StartTime":77384.0,"Position":467.2785,"HyperDash":false}]},{"StartTime":77475.0,"Objects":[{"StartTime":77475.0,"Position":412.0,"HyperDash":false},{"StartTime":77565.0,"Position":439.85,"HyperDash":false},{"StartTime":77656.0,"Position":412.0,"HyperDash":false}]},{"StartTime":77748.0,"Objects":[{"StartTime":77748.0,"Position":320.0,"HyperDash":false},{"StartTime":77838.0,"Position":313.270081,"HyperDash":false},{"StartTime":77929.0,"Position":320.0,"HyperDash":false}]},{"StartTime":78021.0,"Objects":[{"StartTime":78021.0,"Position":248.0,"HyperDash":false},{"StartTime":78111.0,"Position":275.85,"HyperDash":false},{"StartTime":78202.0,"Position":248.0,"HyperDash":false}]},{"StartTime":78294.0,"Objects":[{"StartTime":78294.0,"Position":156.0,"HyperDash":false},{"StartTime":78384.0,"Position":149.56723,"HyperDash":false},{"StartTime":78475.0,"Position":156.0,"HyperDash":false}]},{"StartTime":78566.0,"Objects":[{"StartTime":78566.0,"Position":97.0,"HyperDash":false}]},{"StartTime":78657.0,"Objects":[{"StartTime":78657.0,"Position":89.0,"HyperDash":false},{"StartTime":78747.0,"Position":22.422142,"HyperDash":false}]},{"StartTime":78839.0,"Objects":[{"StartTime":78839.0,"Position":10.0,"HyperDash":false}]},{"StartTime":78930.0,"Objects":[{"StartTime":78930.0,"Position":52.0,"HyperDash":false}]},{"StartTime":79021.0,"Objects":[{"StartTime":79021.0,"Position":106.0,"HyperDash":false}]},{"StartTime":79111.0,"Objects":[{"StartTime":79111.0,"Position":154.0,"HyperDash":false},{"StartTime":79170.0,"Position":200.598,"HyperDash":false},{"StartTime":79229.0,"Position":235.269073,"HyperDash":false},{"StartTime":79288.0,"Position":279.5065,"HyperDash":false},{"StartTime":79383.0,"Position":258.247284,"HyperDash":false}]},{"StartTime":79657.0,"Objects":[{"StartTime":79657.0,"Position":258.0,"HyperDash":false},{"StartTime":79747.0,"Position":190.279266,"HyperDash":false},{"StartTime":79838.0,"Position":258.0,"HyperDash":false}]},{"StartTime":79930.0,"Objects":[{"StartTime":79930.0,"Position":226.0,"HyperDash":false},{"StartTime":80020.0,"Position":158.966843,"HyperDash":false},{"StartTime":80111.0,"Position":226.0,"HyperDash":false}]},{"StartTime":80202.0,"Objects":[{"StartTime":80202.0,"Position":287.0,"HyperDash":false},{"StartTime":80292.0,"Position":354.0113,"HyperDash":false},{"StartTime":80383.0,"Position":287.0,"HyperDash":false}]},{"StartTime":80475.0,"Objects":[{"StartTime":80475.0,"Position":293.0,"HyperDash":false},{"StartTime":80565.0,"Position":354.718628,"HyperDash":false},{"StartTime":80656.0,"Position":293.0,"HyperDash":false}]},{"StartTime":80748.0,"Objects":[{"StartTime":80748.0,"Position":218.0,"HyperDash":false}]},{"StartTime":80839.0,"Objects":[{"StartTime":80839.0,"Position":209.0,"HyperDash":false},{"StartTime":80929.0,"Position":195.476837,"HyperDash":false}]},{"StartTime":81021.0,"Objects":[{"StartTime":81021.0,"Position":256.0,"HyperDash":false}]},{"StartTime":81111.0,"Objects":[{"StartTime":81111.0,"Position":299.0,"HyperDash":false}]},{"StartTime":81202.0,"Objects":[{"StartTime":81202.0,"Position":352.0,"HyperDash":false}]},{"StartTime":81293.0,"Objects":[{"StartTime":81293.0,"Position":398.0,"HyperDash":false},{"StartTime":81352.0,"Position":388.6871,"HyperDash":false},{"StartTime":81411.0,"Position":437.698456,"HyperDash":false},{"StartTime":81470.0,"Position":430.344421,"HyperDash":false},{"StartTime":81565.0,"Position":462.164764,"HyperDash":false}]},{"StartTime":81839.0,"Objects":[{"StartTime":81839.0,"Position":462.0,"HyperDash":false},{"StartTime":81929.0,"Position":398.4922,"HyperDash":false},{"StartTime":82020.0,"Position":462.0,"HyperDash":false}]},{"StartTime":82111.0,"Objects":[{"StartTime":82111.0,"Position":347.0,"HyperDash":false},{"StartTime":82201.0,"Position":301.8704,"HyperDash":false},{"StartTime":82292.0,"Position":347.0,"HyperDash":false}]},{"StartTime":82384.0,"Objects":[{"StartTime":82384.0,"Position":368.0,"HyperDash":false},{"StartTime":82474.0,"Position":323.2633,"HyperDash":false},{"StartTime":82565.0,"Position":368.0,"HyperDash":false}]},{"StartTime":82657.0,"Objects":[{"StartTime":82657.0,"Position":238.0,"HyperDash":false},{"StartTime":82747.0,"Position":223.616516,"HyperDash":false},{"StartTime":82838.0,"Position":238.0,"HyperDash":false}]},{"StartTime":82930.0,"Objects":[{"StartTime":82930.0,"Position":135.0,"HyperDash":false}]},{"StartTime":83021.0,"Objects":[{"StartTime":83021.0,"Position":139.0,"HyperDash":false},{"StartTime":83111.0,"Position":190.412811,"HyperDash":false}]},{"StartTime":83202.0,"Objects":[{"StartTime":83202.0,"Position":41.0,"HyperDash":false}]},{"StartTime":83293.0,"Objects":[{"StartTime":83293.0,"Position":83.0,"HyperDash":false}]},{"StartTime":83384.0,"Objects":[{"StartTime":83384.0,"Position":103.0,"HyperDash":false}]},{"StartTime":83475.0,"Objects":[{"StartTime":83475.0,"Position":99.0,"HyperDash":false},{"StartTime":83534.0,"Position":103.780617,"HyperDash":false},{"StartTime":83593.0,"Position":126.401306,"HyperDash":false},{"StartTime":83652.0,"Position":141.544952,"HyperDash":false},{"StartTime":83747.0,"Position":219.928558,"HyperDash":false}]},{"StartTime":84021.0,"Objects":[{"StartTime":84021.0,"Position":219.0,"HyperDash":false},{"StartTime":84111.0,"Position":155.1237,"HyperDash":false},{"StartTime":84202.0,"Position":219.0,"HyperDash":false}]},{"StartTime":84293.0,"Objects":[{"StartTime":84293.0,"Position":237.0,"HyperDash":false},{"StartTime":84383.0,"Position":181.530167,"HyperDash":false},{"StartTime":84474.0,"Position":237.0,"HyperDash":false}]},{"StartTime":84566.0,"Objects":[{"StartTime":84566.0,"Position":291.0,"HyperDash":false},{"StartTime":84656.0,"Position":354.876282,"HyperDash":false},{"StartTime":84747.0,"Position":291.0,"HyperDash":false}]},{"StartTime":84839.0,"Objects":[{"StartTime":84839.0,"Position":273.0,"HyperDash":false},{"StartTime":84929.0,"Position":328.1262,"HyperDash":false},{"StartTime":85020.0,"Position":273.0,"HyperDash":false}]},{"StartTime":85111.0,"Objects":[{"StartTime":85111.0,"Position":210.0,"HyperDash":false}]},{"StartTime":85202.0,"Objects":[{"StartTime":85202.0,"Position":199.0,"HyperDash":false},{"StartTime":85292.0,"Position":175.375092,"HyperDash":false}]},{"StartTime":85384.0,"Objects":[{"StartTime":85384.0,"Position":227.0,"HyperDash":false}]},{"StartTime":85475.0,"Objects":[{"StartTime":85475.0,"Position":280.0,"HyperDash":false}]},{"StartTime":85566.0,"Objects":[{"StartTime":85566.0,"Position":326.0,"HyperDash":false}]},{"StartTime":85657.0,"Objects":[{"StartTime":85657.0,"Position":380.0,"HyperDash":false},{"StartTime":85708.0,"Position":410.039581,"HyperDash":false},{"StartTime":85759.0,"Position":454.079163,"HyperDash":false},{"StartTime":85810.0,"Position":496.148,"HyperDash":false},{"StartTime":85861.0,"Position":512.0,"HyperDash":false},{"StartTime":85945.0,"Position":452.8782,"HyperDash":false},{"StartTime":86066.0,"Position":380.0,"HyperDash":false}]},{"StartTime":86202.0,"Objects":[{"StartTime":86202.0,"Position":414.0,"HyperDash":false},{"StartTime":86270.0,"Position":406.751984,"HyperDash":false},{"StartTime":86338.0,"Position":414.0,"HyperDash":false},{"StartTime":86406.0,"Position":406.751984,"HyperDash":false}]},{"StartTime":86475.0,"Objects":[{"StartTime":86475.0,"Position":313.0,"HyperDash":false},{"StartTime":86543.0,"Position":320.248016,"HyperDash":false},{"StartTime":86611.0,"Position":313.0,"HyperDash":false},{"StartTime":86679.0,"Position":320.248016,"HyperDash":false}]},{"StartTime":86748.0,"Objects":[{"StartTime":86748.0,"Position":229.0,"HyperDash":false},{"StartTime":86816.0,"Position":236.248016,"HyperDash":false}]},{"StartTime":86884.0,"Objects":[{"StartTime":86884.0,"Position":140.0,"HyperDash":false},{"StartTime":86952.0,"Position":147.248016,"HyperDash":false}]},{"StartTime":87021.0,"Objects":[{"StartTime":87021.0,"Position":51.0,"HyperDash":false},{"StartTime":87089.0,"Position":58.2480125,"HyperDash":false},{"StartTime":87157.0,"Position":51.0,"HyperDash":false},{"StartTime":87225.0,"Position":58.2480125,"HyperDash":false}]},{"StartTime":87293.0,"Objects":[{"StartTime":87293.0,"Position":41.0,"HyperDash":false},{"StartTime":87361.0,"Position":0.0,"HyperDash":false},{"StartTime":87429.0,"Position":41.0,"HyperDash":false}]},{"StartTime":87566.0,"Objects":[{"StartTime":87566.0,"Position":111.0,"HyperDash":false}]},{"StartTime":87634.0,"Objects":[{"StartTime":87634.0,"Position":119.0,"HyperDash":false}]},{"StartTime":87702.0,"Objects":[{"StartTime":87702.0,"Position":127.0,"HyperDash":false}]},{"StartTime":87839.0,"Objects":[{"StartTime":87839.0,"Position":152.0,"HyperDash":false},{"StartTime":87907.0,"Position":110.122604,"HyperDash":false},{"StartTime":87975.0,"Position":152.0,"HyperDash":false}]},{"StartTime":88112.0,"Objects":[{"StartTime":88112.0,"Position":222.0,"HyperDash":false}]},{"StartTime":88180.0,"Objects":[{"StartTime":88180.0,"Position":230.0,"HyperDash":false}]},{"StartTime":88248.0,"Objects":[{"StartTime":88248.0,"Position":238.0,"HyperDash":false}]},{"StartTime":88384.0,"Objects":[{"StartTime":88384.0,"Position":295.0,"HyperDash":false},{"StartTime":88452.0,"Position":336.8774,"HyperDash":false},{"StartTime":88520.0,"Position":295.0,"HyperDash":false},{"StartTime":88588.0,"Position":336.8774,"HyperDash":false}]},{"StartTime":88657.0,"Objects":[{"StartTime":88657.0,"Position":334.0,"HyperDash":false},{"StartTime":88725.0,"Position":375.8774,"HyperDash":false},{"StartTime":88793.0,"Position":334.0,"HyperDash":false},{"StartTime":88861.0,"Position":375.8774,"HyperDash":false}]},{"StartTime":88930.0,"Objects":[{"StartTime":88930.0,"Position":464.0,"HyperDash":false},{"StartTime":88998.0,"Position":471.248016,"HyperDash":false}]},{"StartTime":89066.0,"Objects":[{"StartTime":89066.0,"Position":449.0,"HyperDash":false},{"StartTime":89134.0,"Position":456.248016,"HyperDash":false}]},{"StartTime":89202.0,"Objects":[{"StartTime":89202.0,"Position":434.0,"HyperDash":false},{"StartTime":89270.0,"Position":441.248016,"HyperDash":false},{"StartTime":89338.0,"Position":434.0,"HyperDash":false},{"StartTime":89406.0,"Position":441.248016,"HyperDash":false}]},{"StartTime":89475.0,"Objects":[{"StartTime":89475.0,"Position":362.0,"HyperDash":false}]},{"StartTime":89543.0,"Objects":[{"StartTime":89543.0,"Position":360.0,"HyperDash":false}]},{"StartTime":89611.0,"Objects":[{"StartTime":89611.0,"Position":358.0,"HyperDash":false}]},{"StartTime":89748.0,"Objects":[{"StartTime":89748.0,"Position":288.0,"HyperDash":false}]},{"StartTime":89816.0,"Objects":[{"StartTime":89816.0,"Position":286.0,"HyperDash":false}]},{"StartTime":89884.0,"Objects":[{"StartTime":89884.0,"Position":284.0,"HyperDash":false}]},{"StartTime":90021.0,"Objects":[{"StartTime":90021.0,"Position":201.0,"HyperDash":false}]},{"StartTime":90089.0,"Objects":[{"StartTime":90089.0,"Position":193.0,"HyperDash":false}]},{"StartTime":90158.0,"Objects":[{"StartTime":90158.0,"Position":185.0,"HyperDash":false},{"StartTime":90294.0,"Position":100.560234,"HyperDash":false}]},{"StartTime":90566.0,"Objects":[{"StartTime":90566.0,"Position":67.0,"HyperDash":false},{"StartTime":90634.0,"Position":25.1226,"HyperDash":false},{"StartTime":90702.0,"Position":67.0,"HyperDash":false},{"StartTime":90770.0,"Position":25.1226,"HyperDash":false}]},{"StartTime":90839.0,"Objects":[{"StartTime":90839.0,"Position":50.0,"HyperDash":false},{"StartTime":90907.0,"Position":8.122601,"HyperDash":false},{"StartTime":90975.0,"Position":50.0,"HyperDash":false},{"StartTime":91043.0,"Position":8.122601,"HyperDash":false}]},{"StartTime":91111.0,"Objects":[{"StartTime":91111.0,"Position":147.0,"HyperDash":false},{"StartTime":91179.0,"Position":139.751984,"HyperDash":false}]},{"StartTime":91247.0,"Objects":[{"StartTime":91247.0,"Position":236.0,"HyperDash":false},{"StartTime":91315.0,"Position":228.751984,"HyperDash":false}]},{"StartTime":91384.0,"Objects":[{"StartTime":91384.0,"Position":325.0,"HyperDash":false},{"StartTime":91452.0,"Position":317.751984,"HyperDash":false},{"StartTime":91520.0,"Position":325.0,"HyperDash":false},{"StartTime":91588.0,"Position":317.751984,"HyperDash":false}]},{"StartTime":91657.0,"Objects":[{"StartTime":91657.0,"Position":257.0,"HyperDash":false},{"StartTime":91725.0,"Position":249.751984,"HyperDash":false},{"StartTime":91793.0,"Position":257.0,"HyperDash":false}]},{"StartTime":91930.0,"Objects":[{"StartTime":91930.0,"Position":154.0,"HyperDash":false}]},{"StartTime":91998.0,"Objects":[{"StartTime":91998.0,"Position":156.0,"HyperDash":false}]},{"StartTime":92066.0,"Objects":[{"StartTime":92066.0,"Position":158.0,"HyperDash":false}]},{"StartTime":92203.0,"Objects":[{"StartTime":92203.0,"Position":231.0,"HyperDash":false},{"StartTime":92271.0,"Position":238.248016,"HyperDash":false},{"StartTime":92339.0,"Position":231.0,"HyperDash":false}]},{"StartTime":92476.0,"Objects":[{"StartTime":92476.0,"Position":327.0,"HyperDash":false}]},{"StartTime":92544.0,"Objects":[{"StartTime":92544.0,"Position":329.0,"HyperDash":false}]},{"StartTime":92612.0,"Objects":[{"StartTime":92612.0,"Position":331.0,"HyperDash":false}]},{"StartTime":92748.0,"Objects":[{"StartTime":92748.0,"Position":431.0,"HyperDash":false},{"StartTime":92816.0,"Position":423.751984,"HyperDash":false},{"StartTime":92884.0,"Position":431.0,"HyperDash":false},{"StartTime":92952.0,"Position":423.751984,"HyperDash":false}]},{"StartTime":93021.0,"Objects":[{"StartTime":93021.0,"Position":503.0,"HyperDash":false},{"StartTime":93089.0,"Position":495.047729,"HyperDash":false},{"StartTime":93157.0,"Position":503.0,"HyperDash":false},{"StartTime":93225.0,"Position":495.047729,"HyperDash":false}]},{"StartTime":93293.0,"Objects":[{"StartTime":93293.0,"Position":457.0,"HyperDash":false},{"StartTime":93361.0,"Position":498.8774,"HyperDash":false}]},{"StartTime":93429.0,"Objects":[{"StartTime":93429.0,"Position":371.0,"HyperDash":false},{"StartTime":93497.0,"Position":412.8774,"HyperDash":false}]},{"StartTime":93566.0,"Objects":[{"StartTime":93566.0,"Position":286.0,"HyperDash":false},{"StartTime":93634.0,"Position":327.8774,"HyperDash":false},{"StartTime":93702.0,"Position":286.0,"HyperDash":false},{"StartTime":93770.0,"Position":327.8774,"HyperDash":false}]},{"StartTime":93839.0,"Objects":[{"StartTime":93839.0,"Position":195.0,"HyperDash":false}]},{"StartTime":93907.0,"Objects":[{"StartTime":93907.0,"Position":193.0,"HyperDash":false}]},{"StartTime":93975.0,"Objects":[{"StartTime":93975.0,"Position":191.0,"HyperDash":false}]},{"StartTime":94112.0,"Objects":[{"StartTime":94112.0,"Position":118.0,"HyperDash":false}]},{"StartTime":94180.0,"Objects":[{"StartTime":94180.0,"Position":120.0,"HyperDash":false}]},{"StartTime":94248.0,"Objects":[{"StartTime":94248.0,"Position":122.0,"HyperDash":false}]},{"StartTime":94385.0,"Objects":[{"StartTime":94385.0,"Position":145.0,"HyperDash":false}]},{"StartTime":94453.0,"Objects":[{"StartTime":94453.0,"Position":143.0,"HyperDash":false}]},{"StartTime":94522.0,"Objects":[{"StartTime":94522.0,"Position":141.0,"HyperDash":false},{"StartTime":94658.0,"Position":150.743042,"HyperDash":false}]},{"StartTime":94930.0,"Objects":[{"StartTime":94930.0,"Position":48.0,"HyperDash":false}]},{"StartTime":94998.0,"Objects":[{"StartTime":94998.0,"Position":41.0,"HyperDash":false}]},{"StartTime":95066.0,"Objects":[{"StartTime":95066.0,"Position":34.0,"HyperDash":false},{"StartTime":95134.0,"Position":75.8533,"HyperDash":false},{"StartTime":95202.0,"Position":34.0,"HyperDash":false},{"StartTime":95270.0,"Position":75.8533,"HyperDash":false}]},{"StartTime":95339.0,"Objects":[{"StartTime":95339.0,"Position":77.0,"HyperDash":false},{"StartTime":95407.0,"Position":118.8533,"HyperDash":false}]},{"StartTime":95475.0,"Objects":[{"StartTime":95475.0,"Position":37.0,"HyperDash":false},{"StartTime":95543.0,"Position":78.8533,"HyperDash":false},{"StartTime":95611.0,"Position":37.0,"HyperDash":false},{"StartTime":95679.0,"Position":78.8533,"HyperDash":false},{"StartTime":95747.0,"Position":37.0,"HyperDash":false},{"StartTime":95815.0,"Position":78.8533,"HyperDash":false},{"StartTime":95884.0,"Position":37.0,"HyperDash":false},{"StartTime":95952.0,"Position":78.8533,"HyperDash":false},{"StartTime":96020.0,"Position":37.0,"HyperDash":false}]},{"StartTime":104748.0,"Objects":[{"StartTime":104748.0,"Position":285.0,"HyperDash":false},{"StartTime":104884.0,"Position":196.3763,"HyperDash":false}]},{"StartTime":105020.0,"Objects":[{"StartTime":105020.0,"Position":372.0,"HyperDash":false},{"StartTime":105156.0,"Position":460.110962,"HyperDash":false}]},{"StartTime":105293.0,"Objects":[{"StartTime":105293.0,"Position":483.0,"HyperDash":false},{"StartTime":105429.0,"Position":506.840118,"HyperDash":false}]},{"StartTime":105566.0,"Objects":[{"StartTime":105566.0,"Position":381.0,"HyperDash":false},{"StartTime":105702.0,"Position":403.863068,"HyperDash":false}]},{"StartTime":105839.0,"Objects":[{"StartTime":105839.0,"Position":336.0,"HyperDash":false},{"StartTime":105975.0,"Position":236.595367,"HyperDash":false}]},{"StartTime":106111.0,"Objects":[{"StartTime":106111.0,"Position":190.0,"HyperDash":false}]},{"StartTime":106248.0,"Objects":[{"StartTime":106248.0,"Position":190.0,"HyperDash":false}]},{"StartTime":106384.0,"Objects":[{"StartTime":106384.0,"Position":66.0,"HyperDash":false},{"StartTime":106520.0,"Position":50.64902,"HyperDash":false}]},{"StartTime":106657.0,"Objects":[{"StartTime":106657.0,"Position":160.0,"HyperDash":false},{"StartTime":106793.0,"Position":256.028931,"HyperDash":false}]},{"StartTime":106929.0,"Objects":[{"StartTime":106929.0,"Position":419.0,"HyperDash":false},{"StartTime":107065.0,"Position":411.1793,"HyperDash":false}]},{"StartTime":107202.0,"Objects":[{"StartTime":107202.0,"Position":350.0,"HyperDash":false},{"StartTime":107338.0,"Position":437.371735,"HyperDash":false}]},{"StartTime":107475.0,"Objects":[{"StartTime":107475.0,"Position":500.0,"HyperDash":false}]},{"StartTime":107611.0,"Objects":[{"StartTime":107611.0,"Position":387.0,"HyperDash":false}]},{"StartTime":107679.0,"Objects":[{"StartTime":107679.0,"Position":387.0,"HyperDash":false}]},{"StartTime":107748.0,"Objects":[{"StartTime":107748.0,"Position":387.0,"HyperDash":false},{"StartTime":107884.0,"Position":286.101257,"HyperDash":false}]},{"StartTime":108020.0,"Objects":[{"StartTime":108020.0,"Position":126.0,"HyperDash":false}]},{"StartTime":108156.0,"Objects":[{"StartTime":108156.0,"Position":139.0,"HyperDash":false}]},{"StartTime":108293.0,"Objects":[{"StartTime":108293.0,"Position":213.0,"HyperDash":false}]},{"StartTime":108429.0,"Objects":[{"StartTime":108429.0,"Position":301.0,"HyperDash":false}]},{"StartTime":108566.0,"Objects":[{"StartTime":108566.0,"Position":267.0,"HyperDash":false},{"StartTime":108625.0,"Position":232.172058,"HyperDash":false},{"StartTime":108684.0,"Position":191.248871,"HyperDash":false},{"StartTime":108743.0,"Position":129.18779,"HyperDash":false},{"StartTime":108838.0,"Position":67.07219,"HyperDash":false}]},{"StartTime":108975.0,"Objects":[{"StartTime":108975.0,"Position":55.0,"HyperDash":false}]},{"StartTime":109043.0,"Objects":[{"StartTime":109043.0,"Position":44.0,"HyperDash":false}]},{"StartTime":109111.0,"Objects":[{"StartTime":109111.0,"Position":35.0,"HyperDash":false},{"StartTime":109247.0,"Position":134.610657,"HyperDash":false}]},{"StartTime":109384.0,"Objects":[{"StartTime":109384.0,"Position":279.0,"HyperDash":false},{"StartTime":109520.0,"Position":378.779877,"HyperDash":false}]},{"StartTime":109657.0,"Objects":[{"StartTime":109657.0,"Position":474.0,"HyperDash":false},{"StartTime":109793.0,"Position":414.009949,"HyperDash":false}]},{"StartTime":109929.0,"Objects":[{"StartTime":109929.0,"Position":357.0,"HyperDash":false},{"StartTime":110065.0,"Position":448.250183,"HyperDash":false}]},{"StartTime":110202.0,"Objects":[{"StartTime":110202.0,"Position":499.0,"HyperDash":false},{"StartTime":110338.0,"Position":409.002472,"HyperDash":false}]},{"StartTime":110475.0,"Objects":[{"StartTime":110475.0,"Position":280.0,"HyperDash":false}]},{"StartTime":110611.0,"Objects":[{"StartTime":110611.0,"Position":280.0,"HyperDash":false}]},{"StartTime":110748.0,"Objects":[{"StartTime":110748.0,"Position":357.0,"HyperDash":false},{"StartTime":110884.0,"Position":344.34845,"HyperDash":false}]},{"StartTime":111020.0,"Objects":[{"StartTime":111020.0,"Position":209.0,"HyperDash":false},{"StartTime":111156.0,"Position":196.34845,"HyperDash":false}]},{"StartTime":111293.0,"Objects":[{"StartTime":111293.0,"Position":65.0,"HyperDash":false},{"StartTime":111429.0,"Position":148.923523,"HyperDash":false}]},{"StartTime":111566.0,"Objects":[{"StartTime":111566.0,"Position":80.0,"HyperDash":false},{"StartTime":111702.0,"Position":78.81489,"HyperDash":false}]},{"StartTime":111839.0,"Objects":[{"StartTime":111839.0,"Position":148.0,"HyperDash":false}]},{"StartTime":111975.0,"Objects":[{"StartTime":111975.0,"Position":269.0,"HyperDash":false}]},{"StartTime":112043.0,"Objects":[{"StartTime":112043.0,"Position":269.0,"HyperDash":false}]},{"StartTime":112111.0,"Objects":[{"StartTime":112111.0,"Position":269.0,"HyperDash":false},{"StartTime":112247.0,"Position":369.6811,"HyperDash":false}]},{"StartTime":112384.0,"Objects":[{"StartTime":112384.0,"Position":369.0,"HyperDash":false}]},{"StartTime":112452.0,"Objects":[{"StartTime":112452.0,"Position":410.0,"HyperDash":false}]},{"StartTime":112520.0,"Objects":[{"StartTime":112520.0,"Position":450.0,"HyperDash":false}]},{"StartTime":112588.0,"Objects":[{"StartTime":112588.0,"Position":478.0,"HyperDash":false}]},{"StartTime":112656.0,"Objects":[{"StartTime":112656.0,"Position":487.0,"HyperDash":false}]},{"StartTime":112793.0,"Objects":[{"StartTime":112793.0,"Position":413.0,"HyperDash":false}]},{"StartTime":112861.0,"Objects":[{"StartTime":112861.0,"Position":371.0,"HyperDash":false}]},{"StartTime":112929.0,"Objects":[{"StartTime":112929.0,"Position":329.0,"HyperDash":false}]},{"StartTime":113066.0,"Objects":[{"StartTime":113066.0,"Position":259.0,"HyperDash":false},{"StartTime":113134.0,"Position":208.630585,"HyperDash":false},{"StartTime":113202.0,"Position":259.0,"HyperDash":false},{"StartTime":113270.0,"Position":208.630585,"HyperDash":false},{"StartTime":113338.0,"Position":259.0,"HyperDash":false},{"StartTime":113406.0,"Position":208.630585,"HyperDash":false},{"StartTime":113475.0,"Position":259.0,"HyperDash":false}]},{"StartTime":117839.0,"Objects":[{"StartTime":117839.0,"Position":352.0,"HyperDash":false},{"StartTime":117907.0,"Position":367.7046,"HyperDash":false},{"StartTime":117975.0,"Position":377.8776,"HyperDash":false},{"StartTime":118043.0,"Position":353.339722,"HyperDash":false},{"StartTime":118111.0,"Position":341.5588,"HyperDash":false},{"StartTime":118170.0,"Position":357.394043,"HyperDash":false},{"StartTime":118229.0,"Position":351.709229,"HyperDash":false},{"StartTime":118288.0,"Position":368.7251,"HyperDash":false},{"StartTime":118384.0,"Position":352.0,"HyperDash":false}]},{"StartTime":118521.0,"Objects":[{"StartTime":118521.0,"Position":435.0,"HyperDash":false}]},{"StartTime":118657.0,"Objects":[{"StartTime":118657.0,"Position":435.0,"HyperDash":false},{"StartTime":118716.0,"Position":424.944855,"HyperDash":false},{"StartTime":118775.0,"Position":373.775269,"HyperDash":false},{"StartTime":118834.0,"Position":349.8368,"HyperDash":false},{"StartTime":118929.0,"Position":316.293427,"HyperDash":false}]},{"StartTime":119203.0,"Objects":[{"StartTime":119203.0,"Position":353.0,"HyperDash":false}]},{"StartTime":119339.0,"Objects":[{"StartTime":119339.0,"Position":353.0,"HyperDash":false},{"StartTime":119398.0,"Position":364.062378,"HyperDash":false},{"StartTime":119457.0,"Position":397.124756,"HyperDash":false},{"StartTime":119516.0,"Position":439.1871,"HyperDash":false},{"StartTime":119611.0,"Position":486.982452,"HyperDash":true}]},{"StartTime":119748.0,"Objects":[{"StartTime":119748.0,"Position":273.0,"HyperDash":false}]},{"StartTime":120021.0,"Objects":[{"StartTime":120021.0,"Position":90.0,"HyperDash":false},{"StartTime":120089.0,"Position":108.62011,"HyperDash":false},{"StartTime":120157.0,"Position":95.3407,"HyperDash":false},{"StartTime":120225.0,"Position":76.87965,"HyperDash":false},{"StartTime":120293.0,"Position":40.5374374,"HyperDash":false},{"StartTime":120352.0,"Position":60.58837,"HyperDash":false},{"StartTime":120411.0,"Position":96.3111343,"HyperDash":false},{"StartTime":120470.0,"Position":80.33538,"HyperDash":false},{"StartTime":120566.0,"Position":90.0,"HyperDash":false}]},{"StartTime":120703.0,"Objects":[{"StartTime":120703.0,"Position":128.0,"HyperDash":false}]},{"StartTime":120839.0,"Objects":[{"StartTime":120839.0,"Position":128.0,"HyperDash":false},{"StartTime":120975.0,"Position":68.21395,"HyperDash":false}]},{"StartTime":121112.0,"Objects":[{"StartTime":121112.0,"Position":14.0,"HyperDash":false},{"StartTime":121180.0,"Position":34.0660934,"HyperDash":false},{"StartTime":121248.0,"Position":24.13219,"HyperDash":false},{"StartTime":121384.0,"Position":14.0,"HyperDash":false}]},{"StartTime":121521.0,"Objects":[{"StartTime":121521.0,"Position":68.0,"HyperDash":false},{"StartTime":121580.0,"Position":75.36682,"HyperDash":false},{"StartTime":121639.0,"Position":102.431969,"HyperDash":false},{"StartTime":121698.0,"Position":145.603821,"HyperDash":false},{"StartTime":121793.0,"Position":188.698318,"HyperDash":false}]},{"StartTime":121930.0,"Objects":[{"StartTime":121930.0,"Position":267.0,"HyperDash":false}]},{"StartTime":122202.0,"Objects":[{"StartTime":122202.0,"Position":267.0,"HyperDash":false},{"StartTime":122261.0,"Position":230.862274,"HyperDash":false},{"StartTime":122320.0,"Position":245.4149,"HyperDash":false},{"StartTime":122379.0,"Position":216.465454,"HyperDash":false},{"StartTime":122474.0,"Position":252.568588,"HyperDash":false}]},{"StartTime":122611.0,"Objects":[{"StartTime":122611.0,"Position":252.0,"HyperDash":false},{"StartTime":122670.0,"Position":237.2295,"HyperDash":false},{"StartTime":122729.0,"Position":198.886948,"HyperDash":false},{"StartTime":122788.0,"Position":171.432022,"HyperDash":false},{"StartTime":122883.0,"Position":120.432274,"HyperDash":false}]},{"StartTime":123021.0,"Objects":[{"StartTime":123021.0,"Position":58.0,"HyperDash":false},{"StartTime":123157.0,"Position":78.36528,"HyperDash":false}]},{"StartTime":123293.0,"Objects":[{"StartTime":123293.0,"Position":6.0,"HyperDash":false},{"StartTime":123429.0,"Position":88.6607361,"HyperDash":false}]},{"StartTime":123566.0,"Objects":[{"StartTime":123566.0,"Position":156.0,"HyperDash":false},{"StartTime":123702.0,"Position":224.188141,"HyperDash":false}]},{"StartTime":123839.0,"Objects":[{"StartTime":123839.0,"Position":349.0,"HyperDash":false}]},{"StartTime":123975.0,"Objects":[{"StartTime":123975.0,"Position":375.0,"HyperDash":false}]},{"StartTime":124111.0,"Objects":[{"StartTime":124111.0,"Position":456.0,"HyperDash":false},{"StartTime":124195.0,"Position":453.9612,"HyperDash":false},{"StartTime":124315.0,"Position":470.4772,"HyperDash":false}]},{"StartTime":124384.0,"Objects":[{"StartTime":124384.0,"Position":498.0,"HyperDash":false},{"StartTime":124443.0,"Position":452.638641,"HyperDash":false},{"StartTime":124502.0,"Position":424.858124,"HyperDash":false},{"StartTime":124561.0,"Position":402.805267,"HyperDash":false},{"StartTime":124656.0,"Position":400.806458,"HyperDash":false}]},{"StartTime":124793.0,"Objects":[{"StartTime":124793.0,"Position":400.0,"HyperDash":false}]},{"StartTime":124930.0,"Objects":[{"StartTime":124930.0,"Position":320.0,"HyperDash":false},{"StartTime":125020.0,"Position":265.6432,"HyperDash":false},{"StartTime":125111.0,"Position":320.0,"HyperDash":false}]},{"StartTime":125202.0,"Objects":[{"StartTime":125202.0,"Position":226.0,"HyperDash":false},{"StartTime":125292.0,"Position":184.534943,"HyperDash":false},{"StartTime":125383.0,"Position":226.0,"HyperDash":false}]},{"StartTime":125475.0,"Objects":[{"StartTime":125475.0,"Position":165.0,"HyperDash":false},{"StartTime":125565.0,"Position":148.008514,"HyperDash":false},{"StartTime":125656.0,"Position":165.0,"HyperDash":false}]},{"StartTime":125748.0,"Objects":[{"StartTime":125748.0,"Position":64.0,"HyperDash":false},{"StartTime":125838.0,"Position":76.2514648,"HyperDash":false},{"StartTime":125929.0,"Position":64.0,"HyperDash":false}]},{"StartTime":126021.0,"Objects":[{"StartTime":126021.0,"Position":98.0,"HyperDash":false},{"StartTime":126111.0,"Position":42.3349533,"HyperDash":false},{"StartTime":126202.0,"Position":98.0,"HyperDash":false}]},{"StartTime":126293.0,"Objects":[{"StartTime":126293.0,"Position":168.0,"HyperDash":false}]},{"StartTime":126384.0,"Objects":[{"StartTime":126384.0,"Position":176.0,"HyperDash":false},{"StartTime":126474.0,"Position":231.724014,"HyperDash":false}]},{"StartTime":126566.0,"Objects":[{"StartTime":126566.0,"Position":294.0,"HyperDash":false},{"StartTime":126625.0,"Position":304.065277,"HyperDash":false},{"StartTime":126684.0,"Position":289.130554,"HyperDash":false},{"StartTime":126743.0,"Position":270.195831,"HyperDash":false},{"StartTime":126838.0,"Position":275.86026,"HyperDash":false}]},{"StartTime":126975.0,"Objects":[{"StartTime":126975.0,"Position":269.0,"HyperDash":false},{"StartTime":127034.0,"Position":238.030014,"HyperDash":false},{"StartTime":127093.0,"Position":206.798035,"HyperDash":false},{"StartTime":127152.0,"Position":183.373825,"HyperDash":false},{"StartTime":127247.0,"Position":128.954315,"HyperDash":false}]},{"StartTime":127384.0,"Objects":[{"StartTime":127384.0,"Position":128.0,"HyperDash":false},{"StartTime":127443.0,"Position":104.877335,"HyperDash":false},{"StartTime":127502.0,"Position":66.8338852,"HyperDash":false},{"StartTime":127561.0,"Position":81.92623,"HyperDash":false},{"StartTime":127656.0,"Position":101.414925,"HyperDash":false}]},{"StartTime":127930.0,"Objects":[{"StartTime":127930.0,"Position":102.0,"HyperDash":false},{"StartTime":128066.0,"Position":185.98468,"HyperDash":false}]},{"StartTime":128202.0,"Objects":[{"StartTime":128202.0,"Position":268.0,"HyperDash":false},{"StartTime":128338.0,"Position":276.750061,"HyperDash":false}]},{"StartTime":128475.0,"Objects":[{"StartTime":128475.0,"Position":220.0,"HyperDash":false}]},{"StartTime":128611.0,"Objects":[{"StartTime":128611.0,"Position":246.0,"HyperDash":false}]},{"StartTime":128748.0,"Objects":[{"StartTime":128748.0,"Position":272.0,"HyperDash":false},{"StartTime":128838.0,"Position":259.471741,"HyperDash":false},{"StartTime":128929.0,"Position":272.0,"HyperDash":false}]},{"StartTime":129021.0,"Objects":[{"StartTime":129021.0,"Position":341.0,"HyperDash":false},{"StartTime":129111.0,"Position":356.802368,"HyperDash":false},{"StartTime":129202.0,"Position":341.0,"HyperDash":false}]},{"StartTime":129293.0,"Objects":[{"StartTime":129293.0,"Position":374.0,"HyperDash":false},{"StartTime":129383.0,"Position":414.349274,"HyperDash":false},{"StartTime":129474.0,"Position":374.0,"HyperDash":false}]},{"StartTime":129566.0,"Objects":[{"StartTime":129566.0,"Position":363.0,"HyperDash":false},{"StartTime":129656.0,"Position":417.3568,"HyperDash":false},{"StartTime":129747.0,"Position":363.0,"HyperDash":false}]},{"StartTime":129839.0,"Objects":[{"StartTime":129839.0,"Position":399.0,"HyperDash":false}]},{"StartTime":129930.0,"Objects":[{"StartTime":129930.0,"Position":363.0,"HyperDash":false}]},{"StartTime":130021.0,"Objects":[{"StartTime":130021.0,"Position":319.0,"HyperDash":false}]},{"StartTime":130111.0,"Objects":[{"StartTime":130111.0,"Position":274.0,"HyperDash":false}]},{"StartTime":130202.0,"Objects":[{"StartTime":130202.0,"Position":233.0,"HyperDash":false}]},{"StartTime":130293.0,"Objects":[{"StartTime":130293.0,"Position":188.0,"HyperDash":false}]},{"StartTime":130384.0,"Objects":[{"StartTime":130384.0,"Position":144.0,"HyperDash":false},{"StartTime":130443.0,"Position":136.688782,"HyperDash":false},{"StartTime":130502.0,"Position":118.278656,"HyperDash":false},{"StartTime":130561.0,"Position":153.723068,"HyperDash":false},{"StartTime":130656.0,"Position":190.433411,"HyperDash":false}]},{"StartTime":130793.0,"Objects":[{"StartTime":130793.0,"Position":282.0,"HyperDash":false}]},{"StartTime":130861.0,"Objects":[{"StartTime":130861.0,"Position":282.0,"HyperDash":false}]},{"StartTime":130930.0,"Objects":[{"StartTime":130930.0,"Position":282.0,"HyperDash":false},{"StartTime":130989.0,"Position":284.273651,"HyperDash":false},{"StartTime":131048.0,"Position":293.547333,"HyperDash":false},{"StartTime":131107.0,"Position":301.820984,"HyperDash":false},{"StartTime":131202.0,"Position":264.598328,"HyperDash":false}]},{"StartTime":131339.0,"Objects":[{"StartTime":131339.0,"Position":264.0,"HyperDash":false},{"StartTime":131398.0,"Position":248.803833,"HyperDash":false},{"StartTime":131457.0,"Position":204.483932,"HyperDash":false},{"StartTime":131516.0,"Position":177.141281,"HyperDash":false},{"StartTime":131611.0,"Position":107.439949,"HyperDash":false}]},{"StartTime":131748.0,"Objects":[{"StartTime":131748.0,"Position":107.0,"HyperDash":false},{"StartTime":131884.0,"Position":136.185135,"HyperDash":false}]},{"StartTime":132021.0,"Objects":[{"StartTime":132021.0,"Position":88.0,"HyperDash":false},{"StartTime":132080.0,"Position":51.873764,"HyperDash":false},{"StartTime":132139.0,"Position":55.46241,"HyperDash":false},{"StartTime":132198.0,"Position":72.92975,"HyperDash":false},{"StartTime":132293.0,"Position":100.14119,"HyperDash":false}]},{"StartTime":132430.0,"Objects":[{"StartTime":132430.0,"Position":100.0,"HyperDash":false},{"StartTime":132489.0,"Position":75.71915,"HyperDash":false},{"StartTime":132548.0,"Position":18.4710484,"HyperDash":false},{"StartTime":132607.0,"Position":27.815239,"HyperDash":false},{"StartTime":132702.0,"Position":100.250526,"HyperDash":false}]},{"StartTime":132839.0,"Objects":[{"StartTime":132839.0,"Position":100.0,"HyperDash":false},{"StartTime":132975.0,"Position":179.952286,"HyperDash":false}]},{"StartTime":133111.0,"Objects":[{"StartTime":133111.0,"Position":246.0,"HyperDash":false},{"StartTime":133247.0,"Position":327.362976,"HyperDash":false}]},{"StartTime":133384.0,"Objects":[{"StartTime":133384.0,"Position":390.0,"HyperDash":false}]},{"StartTime":133521.0,"Objects":[{"StartTime":133521.0,"Position":472.0,"HyperDash":false}]},{"StartTime":133657.0,"Objects":[{"StartTime":133657.0,"Position":491.0,"HyperDash":false}]},{"StartTime":133793.0,"Objects":[{"StartTime":133793.0,"Position":439.0,"HyperDash":false}]},{"StartTime":133930.0,"Objects":[{"StartTime":133930.0,"Position":420.0,"HyperDash":false}]},{"StartTime":134066.0,"Objects":[{"StartTime":134066.0,"Position":461.0,"HyperDash":false}]},{"StartTime":134202.0,"Objects":[{"StartTime":134202.0,"Position":448.0,"HyperDash":false}]},{"StartTime":134339.0,"Objects":[{"StartTime":134339.0,"Position":381.0,"HyperDash":false}]},{"StartTime":134475.0,"Objects":[{"StartTime":134475.0,"Position":296.0,"HyperDash":false}]},{"StartTime":134611.0,"Objects":[{"StartTime":134611.0,"Position":214.0,"HyperDash":false}]},{"StartTime":134748.0,"Objects":[{"StartTime":134748.0,"Position":164.0,"HyperDash":false},{"StartTime":134884.0,"Position":83.35544,"HyperDash":false}]},{"StartTime":135021.0,"Objects":[{"StartTime":135021.0,"Position":19.0,"HyperDash":false},{"StartTime":135157.0,"Position":99.57382,"HyperDash":false}]},{"StartTime":135293.0,"Objects":[{"StartTime":135293.0,"Position":25.0,"HyperDash":false},{"StartTime":135352.0,"Position":41.8271523,"HyperDash":false},{"StartTime":135411.0,"Position":95.72167,"HyperDash":false},{"StartTime":135470.0,"Position":108.490532,"HyperDash":false},{"StartTime":135565.0,"Position":179.471237,"HyperDash":false}]},{"StartTime":135702.0,"Objects":[{"StartTime":135702.0,"Position":252.0,"HyperDash":false}]},{"StartTime":135839.0,"Objects":[{"StartTime":135839.0,"Position":252.0,"HyperDash":false},{"StartTime":135975.0,"Position":241.337753,"HyperDash":false}]},{"StartTime":136111.0,"Objects":[{"StartTime":136111.0,"Position":175.0,"HyperDash":false},{"StartTime":136247.0,"Position":185.662247,"HyperDash":false}]},{"StartTime":136384.0,"Objects":[{"StartTime":136384.0,"Position":138.0,"HyperDash":false}]},{"StartTime":136521.0,"Objects":[{"StartTime":136521.0,"Position":194.0,"HyperDash":false}]},{"StartTime":136657.0,"Objects":[{"StartTime":136657.0,"Position":278.0,"HyperDash":false}]},{"StartTime":136793.0,"Objects":[{"StartTime":136793.0,"Position":360.0,"HyperDash":false}]},{"StartTime":136930.0,"Objects":[{"StartTime":136930.0,"Position":407.0,"HyperDash":false}]},{"StartTime":137066.0,"Objects":[{"StartTime":137066.0,"Position":447.0,"HyperDash":false}]},{"StartTime":137202.0,"Objects":[{"StartTime":137202.0,"Position":367.0,"HyperDash":false}]},{"StartTime":137338.0,"Objects":[{"StartTime":137338.0,"Position":407.0,"HyperDash":false}]},{"StartTime":137475.0,"Objects":[{"StartTime":137475.0,"Position":280.0,"HyperDash":false}]},{"StartTime":137611.0,"Objects":[{"StartTime":137611.0,"Position":194.0,"HyperDash":false}]},{"StartTime":137748.0,"Objects":[{"StartTime":137748.0,"Position":207.0,"HyperDash":false}]},{"StartTime":137884.0,"Objects":[{"StartTime":137884.0,"Position":293.0,"HyperDash":false}]},{"StartTime":138021.0,"Objects":[{"StartTime":138021.0,"Position":198.0,"HyperDash":false},{"StartTime":138080.0,"Position":186.6536,"HyperDash":false},{"StartTime":138139.0,"Position":165.980225,"HyperDash":false},{"StartTime":138198.0,"Position":108.5129,"HyperDash":false},{"StartTime":138293.0,"Position":60.4876747,"HyperDash":false}]},{"StartTime":138566.0,"Objects":[{"StartTime":138566.0,"Position":20.0,"HyperDash":false}]},{"StartTime":138657.0,"Objects":[{"StartTime":138657.0,"Position":67.0,"HyperDash":false}]},{"StartTime":138748.0,"Objects":[{"StartTime":138748.0,"Position":122.0,"HyperDash":false}]},{"StartTime":138839.0,"Objects":[{"StartTime":138839.0,"Position":178.0,"HyperDash":false}]},{"StartTime":138930.0,"Objects":[{"StartTime":138930.0,"Position":221.0,"HyperDash":false}]},{"StartTime":139021.0,"Objects":[{"StartTime":139021.0,"Position":244.0,"HyperDash":false}]},{"StartTime":139111.0,"Objects":[{"StartTime":139111.0,"Position":248.0,"HyperDash":false},{"StartTime":139201.0,"Position":233.246613,"HyperDash":false},{"StartTime":139292.0,"Position":248.0,"HyperDash":false}]},{"StartTime":139384.0,"Objects":[{"StartTime":139384.0,"Position":327.0,"HyperDash":false},{"StartTime":139468.0,"Position":372.042328,"HyperDash":false},{"StartTime":139588.0,"Position":453.388519,"HyperDash":false}]},{"StartTime":139657.0,"Objects":[{"StartTime":139657.0,"Position":489.0,"HyperDash":false},{"StartTime":139716.0,"Position":500.969269,"HyperDash":false},{"StartTime":139775.0,"Position":484.081482,"HyperDash":false},{"StartTime":139834.0,"Position":452.1301,"HyperDash":false},{"StartTime":139929.0,"Position":387.50766,"HyperDash":false}]},{"StartTime":140066.0,"Objects":[{"StartTime":140066.0,"Position":311.0,"HyperDash":false},{"StartTime":140125.0,"Position":300.9206,"HyperDash":false},{"StartTime":140184.0,"Position":285.442963,"HyperDash":false},{"StartTime":140243.0,"Position":239.63205,"HyperDash":false},{"StartTime":140338.0,"Position":189.411591,"HyperDash":false}]},{"StartTime":140475.0,"Objects":[{"StartTime":140475.0,"Position":118.0,"HyperDash":false},{"StartTime":140611.0,"Position":39.25152,"HyperDash":false}]},{"StartTime":140748.0,"Objects":[{"StartTime":140748.0,"Position":13.0,"HyperDash":false}]},{"StartTime":140884.0,"Objects":[{"StartTime":140884.0,"Position":93.0,"HyperDash":false}]},{"StartTime":141021.0,"Objects":[{"StartTime":141021.0,"Position":30.0,"HyperDash":false}]},{"StartTime":141157.0,"Objects":[{"StartTime":141157.0,"Position":91.0,"HyperDash":false},{"StartTime":141216.0,"Position":120.467026,"HyperDash":false},{"StartTime":141275.0,"Position":182.934052,"HyperDash":false},{"StartTime":141334.0,"Position":189.6184,"HyperDash":false},{"StartTime":141429.0,"Position":253.543488,"HyperDash":false}]},{"StartTime":141566.0,"Objects":[{"StartTime":141566.0,"Position":253.0,"HyperDash":false},{"StartTime":141702.0,"Position":252.173325,"HyperDash":false}]},{"StartTime":141839.0,"Objects":[{"StartTime":141839.0,"Position":302.0,"HyperDash":false},{"StartTime":141898.0,"Position":271.164337,"HyperDash":false},{"StartTime":141957.0,"Position":265.036316,"HyperDash":false},{"StartTime":142016.0,"Position":225.854385,"HyperDash":false},{"StartTime":142111.0,"Position":258.0618,"HyperDash":false}]},{"StartTime":142248.0,"Objects":[{"StartTime":142248.0,"Position":329.0,"HyperDash":false}]},{"StartTime":142384.0,"Objects":[{"StartTime":142384.0,"Position":401.0,"HyperDash":false},{"StartTime":142474.0,"Position":456.101929,"HyperDash":false},{"StartTime":142565.0,"Position":401.0,"HyperDash":false}]},{"StartTime":142657.0,"Objects":[{"StartTime":142657.0,"Position":430.0,"HyperDash":false},{"StartTime":142747.0,"Position":485.101929,"HyperDash":false},{"StartTime":142838.0,"Position":430.0,"HyperDash":false}]},{"StartTime":142930.0,"Objects":[{"StartTime":142930.0,"Position":474.0,"HyperDash":false}]},{"StartTime":143020.0,"Objects":[{"StartTime":143020.0,"Position":433.0,"HyperDash":false}]},{"StartTime":143111.0,"Objects":[{"StartTime":143111.0,"Position":389.0,"HyperDash":false}]},{"StartTime":143202.0,"Objects":[{"StartTime":143202.0,"Position":356.0,"HyperDash":false}]},{"StartTime":143293.0,"Objects":[{"StartTime":143293.0,"Position":347.0,"HyperDash":false}]},{"StartTime":143384.0,"Objects":[{"StartTime":143384.0,"Position":363.0,"HyperDash":false}]},{"StartTime":143475.0,"Objects":[{"StartTime":143475.0,"Position":403.0,"HyperDash":false},{"StartTime":143565.0,"Position":458.0956,"HyperDash":false}]},{"StartTime":143657.0,"Objects":[{"StartTime":143657.0,"Position":315.0,"HyperDash":false}]},{"StartTime":143748.0,"Objects":[{"StartTime":143748.0,"Position":303.0,"HyperDash":false},{"StartTime":143838.0,"Position":247.904388,"HyperDash":false}]},{"StartTime":143930.0,"Objects":[{"StartTime":143930.0,"Position":152.0,"HyperDash":false}]},{"StartTime":144021.0,"Objects":[{"StartTime":144021.0,"Position":140.0,"HyperDash":false},{"StartTime":144080.0,"Position":123.703255,"HyperDash":false},{"StartTime":144139.0,"Position":84.1756058,"HyperDash":false},{"StartTime":144198.0,"Position":55.1062,"HyperDash":false},{"StartTime":144293.0,"Position":34.28666,"HyperDash":false}]},{"StartTime":144430.0,"Objects":[{"StartTime":144430.0,"Position":34.0,"HyperDash":false},{"StartTime":144489.0,"Position":39.2494774,"HyperDash":false},{"StartTime":144548.0,"Position":42.4028931,"HyperDash":false},{"StartTime":144607.0,"Position":83.5909,"HyperDash":false},{"StartTime":144702.0,"Position":124.747566,"HyperDash":false}]},{"StartTime":144839.0,"Objects":[{"StartTime":144839.0,"Position":151.0,"HyperDash":false}]},{"StartTime":144975.0,"Objects":[{"StartTime":144975.0,"Position":151.0,"HyperDash":false}]},{"StartTime":145111.0,"Objects":[{"StartTime":145111.0,"Position":91.0,"HyperDash":false},{"StartTime":145247.0,"Position":6.988411,"HyperDash":false}]},{"StartTime":145384.0,"Objects":[{"StartTime":145384.0,"Position":124.0,"HyperDash":false},{"StartTime":145520.0,"Position":208.0116,"HyperDash":false}]},{"StartTime":145657.0,"Objects":[{"StartTime":145657.0,"Position":284.0,"HyperDash":false}]},{"StartTime":145793.0,"Objects":[{"StartTime":145793.0,"Position":330.0,"HyperDash":false}]},{"StartTime":145930.0,"Objects":[{"StartTime":145930.0,"Position":412.0,"HyperDash":false}]},{"StartTime":146066.0,"Objects":[{"StartTime":146066.0,"Position":494.0,"HyperDash":false}]},{"StartTime":146202.0,"Objects":[{"StartTime":146202.0,"Position":422.0,"HyperDash":false},{"StartTime":146261.0,"Position":374.5958,"HyperDash":false},{"StartTime":146320.0,"Position":340.97818,"HyperDash":false},{"StartTime":146379.0,"Position":321.774567,"HyperDash":false},{"StartTime":146474.0,"Position":273.590363,"HyperDash":false}]},{"StartTime":146611.0,"Objects":[{"StartTime":146611.0,"Position":273.0,"HyperDash":false}]},{"StartTime":146748.0,"Objects":[{"StartTime":146748.0,"Position":242.0,"HyperDash":false},{"StartTime":146884.0,"Position":179.186676,"HyperDash":false}]},{"StartTime":147021.0,"Objects":[{"StartTime":147021.0,"Position":33.0,"HyperDash":false},{"StartTime":147157.0,"Position":95.18677,"HyperDash":false}]},{"StartTime":147293.0,"Objects":[{"StartTime":147293.0,"Position":120.0,"HyperDash":false},{"StartTime":147383.0,"Position":174.276825,"HyperDash":false},{"StartTime":147474.0,"Position":120.0,"HyperDash":false}]},{"StartTime":147566.0,"Objects":[{"StartTime":147566.0,"Position":83.0,"HyperDash":false},{"StartTime":147702.0,"Position":0.0,"HyperDash":false}]},{"StartTime":147839.0,"Objects":[{"StartTime":147839.0,"Position":175.0,"HyperDash":false}]},{"StartTime":147975.0,"Objects":[{"StartTime":147975.0,"Position":256.0,"HyperDash":false}]},{"StartTime":148111.0,"Objects":[{"StartTime":148111.0,"Position":195.0,"HyperDash":false}]},{"StartTime":148248.0,"Objects":[{"StartTime":148248.0,"Position":300.0,"HyperDash":false}]},{"StartTime":148316.0,"Objects":[{"StartTime":148316.0,"Position":300.0,"HyperDash":false}]},{"StartTime":148384.0,"Objects":[{"StartTime":148384.0,"Position":300.0,"HyperDash":false},{"StartTime":148452.0,"Position":241.037445,"HyperDash":false},{"StartTime":148520.0,"Position":208.872025,"HyperDash":false},{"StartTime":148588.0,"Position":167.7589,"HyperDash":false},{"StartTime":148656.0,"Position":112.687119,"HyperDash":false},{"StartTime":148724.0,"Position":64.53349,"HyperDash":false},{"StartTime":148792.0,"Position":45.59254,"HyperDash":false},{"StartTime":148860.0,"Position":57.11486,"HyperDash":false},{"StartTime":148929.0,"Position":103.807991,"HyperDash":false},{"StartTime":148997.0,"Position":140.8881,"HyperDash":false},{"StartTime":149065.0,"Position":203.1637,"HyperDash":false},{"StartTime":149133.0,"Position":175.605743,"HyperDash":false},{"StartTime":149202.0,"Position":157.292023,"HyperDash":false},{"StartTime":149261.0,"Position":179.837387,"HyperDash":false},{"StartTime":149320.0,"Position":205.033386,"HyperDash":false},{"StartTime":149379.0,"Position":230.499939,"HyperDash":false},{"StartTime":149474.0,"Position":318.759552,"HyperDash":false}]},{"StartTime":149611.0,"Objects":[{"StartTime":149611.0,"Position":416.0,"HyperDash":false},{"StartTime":149679.0,"Position":459.112885,"HyperDash":false},{"StartTime":149747.0,"Position":483.116028,"HyperDash":false},{"StartTime":149883.0,"Position":416.0,"HyperDash":false}]},{"StartTime":150021.0,"Objects":[{"StartTime":150021.0,"Position":318.0,"HyperDash":false}]},{"StartTime":150157.0,"Objects":[{"StartTime":150157.0,"Position":318.0,"HyperDash":false}]},{"StartTime":150293.0,"Objects":[{"StartTime":150293.0,"Position":395.0,"HyperDash":false},{"StartTime":150429.0,"Position":388.707062,"HyperDash":false}]},{"StartTime":150566.0,"Objects":[{"StartTime":150566.0,"Position":502.0,"HyperDash":false}]},{"StartTime":150702.0,"Objects":[{"StartTime":150702.0,"Position":388.0,"HyperDash":false}]},{"StartTime":150839.0,"Objects":[{"StartTime":150839.0,"Position":388.0,"HyperDash":false}]},{"StartTime":150975.0,"Objects":[{"StartTime":150975.0,"Position":354.0,"HyperDash":false},{"StartTime":151043.0,"Position":308.8965,"HyperDash":false},{"StartTime":151111.0,"Position":257.082336,"HyperDash":false},{"StartTime":151179.0,"Position":200.233047,"HyperDash":false},{"StartTime":151247.0,"Position":179.148392,"HyperDash":false},{"StartTime":151315.0,"Position":121.330429,"HyperDash":false},{"StartTime":151383.0,"Position":91.3323,"HyperDash":false},{"StartTime":151451.0,"Position":105.334328,"HyperDash":false},{"StartTime":151520.0,"Position":163.270889,"HyperDash":false},{"StartTime":151588.0,"Position":222.52066,"HyperDash":false},{"StartTime":151656.0,"Position":236.967346,"HyperDash":false},{"StartTime":151724.0,"Position":197.664108,"HyperDash":false},{"StartTime":151793.0,"Position":170.657684,"HyperDash":false},{"StartTime":151929.0,"Position":122.385834,"HyperDash":false}]},{"StartTime":152066.0,"Objects":[{"StartTime":152066.0,"Position":37.0,"HyperDash":false},{"StartTime":152134.0,"Position":38.1493454,"HyperDash":false},{"StartTime":152202.0,"Position":25.2637386,"HyperDash":false},{"StartTime":152338.0,"Position":37.0,"HyperDash":false}]},{"StartTime":152475.0,"Objects":[{"StartTime":152475.0,"Position":73.0,"HyperDash":false},{"StartTime":152611.0,"Position":125.983765,"HyperDash":false}]},{"StartTime":152748.0,"Objects":[{"StartTime":152748.0,"Position":211.0,"HyperDash":false},{"StartTime":152807.0,"Position":232.132385,"HyperDash":false},{"StartTime":152866.0,"Position":265.062622,"HyperDash":false},{"StartTime":152925.0,"Position":293.685852,"HyperDash":false},{"StartTime":153020.0,"Position":353.2395,"HyperDash":false}]},{"StartTime":153157.0,"Objects":[{"StartTime":153157.0,"Position":499.0,"HyperDash":false},{"StartTime":153216.0,"Position":449.435883,"HyperDash":false},{"StartTime":153275.0,"Position":424.8718,"HyperDash":false},{"StartTime":153334.0,"Position":399.307678,"HyperDash":false},{"StartTime":153429.0,"Position":330.433258,"HyperDash":false}]},{"StartTime":153566.0,"Objects":[{"StartTime":153566.0,"Position":279.0,"HyperDash":false},{"StartTime":153702.0,"Position":299.1634,"HyperDash":false}]},{"StartTime":153839.0,"Objects":[{"StartTime":153839.0,"Position":236.0,"HyperDash":false}]},{"StartTime":153975.0,"Objects":[{"StartTime":153975.0,"Position":299.0,"HyperDash":false}]},{"StartTime":154111.0,"Objects":[{"StartTime":154111.0,"Position":375.0,"HyperDash":false}]},{"StartTime":154248.0,"Objects":[{"StartTime":154248.0,"Position":448.0,"HyperDash":false},{"StartTime":154316.0,"Position":448.704071,"HyperDash":false},{"StartTime":154384.0,"Position":459.51297,"HyperDash":false},{"StartTime":154452.0,"Position":410.957947,"HyperDash":false},{"StartTime":154520.0,"Position":385.861572,"HyperDash":false},{"StartTime":154570.0,"Position":337.801727,"HyperDash":false},{"StartTime":154657.0,"Position":317.3621,"HyperDash":false}]},{"StartTime":154930.0,"Objects":[{"StartTime":154930.0,"Position":41.0,"HyperDash":false}]},{"StartTime":155020.0,"Objects":[{"StartTime":155020.0,"Position":28.0,"HyperDash":false}]},{"StartTime":155111.0,"Objects":[{"StartTime":155111.0,"Position":40.0,"HyperDash":false}]},{"StartTime":155202.0,"Objects":[{"StartTime":155202.0,"Position":72.0,"HyperDash":false}]},{"StartTime":155293.0,"Objects":[{"StartTime":155293.0,"Position":115.0,"HyperDash":false}]},{"StartTime":155384.0,"Objects":[{"StartTime":155384.0,"Position":158.0,"HyperDash":false}]},{"StartTime":155475.0,"Objects":[{"StartTime":155475.0,"Position":198.0,"HyperDash":false}]},{"StartTime":155565.0,"Objects":[{"StartTime":155565.0,"Position":254.0,"HyperDash":false}]},{"StartTime":155656.0,"Objects":[{"StartTime":155656.0,"Position":309.0,"HyperDash":false}]},{"StartTime":155747.0,"Objects":[{"StartTime":155747.0,"Position":356.0,"HyperDash":false}]},{"StartTime":155838.0,"Objects":[{"StartTime":155838.0,"Position":392.0,"HyperDash":false}]},{"StartTime":155929.0,"Objects":[{"StartTime":155929.0,"Position":411.0,"HyperDash":false}]},{"StartTime":156021.0,"Objects":[{"StartTime":156021.0,"Position":411.0,"HyperDash":false},{"StartTime":156089.0,"Position":395.962219,"HyperDash":false},{"StartTime":156157.0,"Position":339.266174,"HyperDash":false},{"StartTime":156225.0,"Position":303.955,"HyperDash":false},{"StartTime":156293.0,"Position":318.589355,"HyperDash":false},{"StartTime":156361.0,"Position":368.6844,"HyperDash":false},{"StartTime":156429.0,"Position":387.036835,"HyperDash":false},{"StartTime":156497.0,"Position":393.426025,"HyperDash":false},{"StartTime":156566.0,"Position":373.163116,"HyperDash":false},{"StartTime":156625.0,"Position":341.85965,"HyperDash":false},{"StartTime":156684.0,"Position":283.9819,"HyperDash":false},{"StartTime":156743.0,"Position":246.838165,"HyperDash":false},{"StartTime":156839.0,"Position":212.31163,"HyperDash":false}]},{"StartTime":156907.0,"Objects":[{"StartTime":156907.0,"Position":213.0,"HyperDash":false}]},{"StartTime":156975.0,"Objects":[{"StartTime":156975.0,"Position":214.0,"HyperDash":false}]},{"StartTime":157043.0,"Objects":[{"StartTime":157043.0,"Position":215.0,"HyperDash":false}]},{"StartTime":157111.0,"Objects":[{"StartTime":157111.0,"Position":216.0,"HyperDash":false},{"StartTime":157247.0,"Position":114.869156,"HyperDash":false}]},{"StartTime":157384.0,"Objects":[{"StartTime":157384.0,"Position":3.0,"HyperDash":false},{"StartTime":157520.0,"Position":104.052589,"HyperDash":false}]},{"StartTime":157657.0,"Objects":[{"StartTime":157657.0,"Position":124.0,"HyperDash":false},{"StartTime":157793.0,"Position":225.052582,"HyperDash":false}]},{"StartTime":157930.0,"Objects":[{"StartTime":157930.0,"Position":13.0,"HyperDash":false},{"StartTime":158066.0,"Position":114.052589,"HyperDash":false}]},{"StartTime":158202.0,"Objects":[{"StartTime":158202.0,"Position":134.0,"HyperDash":false},{"StartTime":158338.0,"Position":235.052582,"HyperDash":false}]},{"StartTime":158475.0,"Objects":[{"StartTime":158475.0,"Position":23.0,"HyperDash":false},{"StartTime":158611.0,"Position":124.052589,"HyperDash":false}]},{"StartTime":158748.0,"Objects":[{"StartTime":158748.0,"Position":144.0,"HyperDash":false},{"StartTime":158884.0,"Position":245.052582,"HyperDash":false}]},{"StartTime":159021.0,"Objects":[{"StartTime":159021.0,"Position":33.0,"HyperDash":false},{"StartTime":159157.0,"Position":134.052582,"HyperDash":false}]},{"StartTime":159293.0,"Objects":[{"StartTime":159293.0,"Position":154.0,"HyperDash":false},{"StartTime":159429.0,"Position":255.052582,"HyperDash":false}]},{"StartTime":159566.0,"Objects":[{"StartTime":159566.0,"Position":43.0,"HyperDash":false},{"StartTime":159702.0,"Position":144.052582,"HyperDash":false}]},{"StartTime":159839.0,"Objects":[{"StartTime":159839.0,"Position":164.0,"HyperDash":false},{"StartTime":159975.0,"Position":265.052582,"HyperDash":false}]},{"StartTime":160112.0,"Objects":[{"StartTime":160112.0,"Position":53.0,"HyperDash":false},{"StartTime":160248.0,"Position":154.052582,"HyperDash":false}]},{"StartTime":160384.0,"Objects":[{"StartTime":160384.0,"Position":174.0,"HyperDash":false},{"StartTime":160520.0,"Position":275.052582,"HyperDash":false}]},{"StartTime":160657.0,"Objects":[{"StartTime":160657.0,"Position":63.0,"HyperDash":false},{"StartTime":160793.0,"Position":164.052582,"HyperDash":false}]},{"StartTime":160930.0,"Objects":[{"StartTime":160930.0,"Position":184.0,"HyperDash":false},{"StartTime":161066.0,"Position":285.052582,"HyperDash":true}]},{"StartTime":161202.0,"Objects":[{"StartTime":161202.0,"Position":73.0,"HyperDash":false},{"StartTime":161338.0,"Position":174.052582,"HyperDash":false}]},{"StartTime":161475.0,"Objects":[{"StartTime":161475.0,"Position":300.0,"HyperDash":false},{"StartTime":161611.0,"Position":401.130859,"HyperDash":false}]},{"StartTime":161748.0,"Objects":[{"StartTime":161748.0,"Position":512.0,"HyperDash":false},{"StartTime":161884.0,"Position":410.818481,"HyperDash":false}]},{"StartTime":162021.0,"Objects":[{"StartTime":162021.0,"Position":391.0,"HyperDash":false},{"StartTime":162157.0,"Position":289.818481,"HyperDash":false}]},{"StartTime":162294.0,"Objects":[{"StartTime":162294.0,"Position":502.0,"HyperDash":false},{"StartTime":162430.0,"Position":400.818481,"HyperDash":false}]},{"StartTime":162566.0,"Objects":[{"StartTime":162566.0,"Position":381.0,"HyperDash":false},{"StartTime":162702.0,"Position":279.818481,"HyperDash":false}]},{"StartTime":162839.0,"Objects":[{"StartTime":162839.0,"Position":492.0,"HyperDash":false},{"StartTime":162975.0,"Position":390.818481,"HyperDash":false}]},{"StartTime":163112.0,"Objects":[{"StartTime":163112.0,"Position":371.0,"HyperDash":false},{"StartTime":163248.0,"Position":269.818481,"HyperDash":false}]},{"StartTime":163385.0,"Objects":[{"StartTime":163385.0,"Position":482.0,"HyperDash":false},{"StartTime":163521.0,"Position":380.818481,"HyperDash":false}]},{"StartTime":163657.0,"Objects":[{"StartTime":163657.0,"Position":361.0,"HyperDash":false},{"StartTime":163793.0,"Position":259.818481,"HyperDash":false}]},{"StartTime":163930.0,"Objects":[{"StartTime":163930.0,"Position":472.0,"HyperDash":false},{"StartTime":164066.0,"Position":370.818481,"HyperDash":false}]},{"StartTime":164203.0,"Objects":[{"StartTime":164203.0,"Position":351.0,"HyperDash":false},{"StartTime":164339.0,"Position":249.818481,"HyperDash":false}]},{"StartTime":164476.0,"Objects":[{"StartTime":164476.0,"Position":462.0,"HyperDash":false},{"StartTime":164612.0,"Position":360.818481,"HyperDash":false}]},{"StartTime":164748.0,"Objects":[{"StartTime":164748.0,"Position":341.0,"HyperDash":false},{"StartTime":164884.0,"Position":239.818481,"HyperDash":false}]},{"StartTime":165021.0,"Objects":[{"StartTime":165021.0,"Position":452.0,"HyperDash":false},{"StartTime":165157.0,"Position":350.818481,"HyperDash":false}]},{"StartTime":165294.0,"Objects":[{"StartTime":165294.0,"Position":331.0,"HyperDash":false},{"StartTime":165430.0,"Position":229.818481,"HyperDash":false}]},{"StartTime":165566.0,"Objects":[{"StartTime":165566.0,"Position":396.0,"HyperDash":false}]},{"StartTime":165702.0,"Objects":[{"StartTime":165702.0,"Position":216.0,"HyperDash":false}]},{"StartTime":165771.0,"Objects":[{"StartTime":165771.0,"Position":216.0,"HyperDash":false}]},{"StartTime":165839.0,"Objects":[{"StartTime":165839.0,"Position":216.0,"HyperDash":false},{"StartTime":165975.0,"Position":229.287262,"HyperDash":false}]},{"StartTime":166112.0,"Objects":[{"StartTime":166112.0,"Position":103.0,"HyperDash":false},{"StartTime":166248.0,"Position":89.1300354,"HyperDash":false}]},{"StartTime":166385.0,"Objects":[{"StartTime":166385.0,"Position":218.0,"HyperDash":false},{"StartTime":166521.0,"Position":204.130035,"HyperDash":false}]},{"StartTime":166658.0,"Objects":[{"StartTime":166658.0,"Position":91.0,"HyperDash":false},{"StartTime":166794.0,"Position":77.1300354,"HyperDash":false}]},{"StartTime":166930.0,"Objects":[{"StartTime":166930.0,"Position":206.0,"HyperDash":false},{"StartTime":167066.0,"Position":192.130035,"HyperDash":false}]},{"StartTime":167203.0,"Objects":[{"StartTime":167203.0,"Position":79.0,"HyperDash":false},{"StartTime":167339.0,"Position":65.1300354,"HyperDash":false}]},{"StartTime":167476.0,"Objects":[{"StartTime":167476.0,"Position":194.0,"HyperDash":false},{"StartTime":167612.0,"Position":180.130035,"HyperDash":false}]},{"StartTime":167749.0,"Objects":[{"StartTime":167749.0,"Position":67.0,"HyperDash":false},{"StartTime":167885.0,"Position":53.1300354,"HyperDash":false}]},{"StartTime":168021.0,"Objects":[{"StartTime":168021.0,"Position":182.0,"HyperDash":false},{"StartTime":168157.0,"Position":168.130035,"HyperDash":false}]},{"StartTime":168294.0,"Objects":[{"StartTime":168294.0,"Position":55.0,"HyperDash":false},{"StartTime":168430.0,"Position":41.1300354,"HyperDash":false}]},{"StartTime":168567.0,"Objects":[{"StartTime":168567.0,"Position":170.0,"HyperDash":false},{"StartTime":168703.0,"Position":156.130035,"HyperDash":false}]},{"StartTime":168840.0,"Objects":[{"StartTime":168840.0,"Position":43.0,"HyperDash":false},{"StartTime":168976.0,"Position":29.1300373,"HyperDash":false}]},{"StartTime":169112.0,"Objects":[{"StartTime":169112.0,"Position":158.0,"HyperDash":false},{"StartTime":169248.0,"Position":144.130035,"HyperDash":false}]},{"StartTime":169385.0,"Objects":[{"StartTime":169385.0,"Position":31.0,"HyperDash":false},{"StartTime":169521.0,"Position":17.1300373,"HyperDash":false}]},{"StartTime":169658.0,"Objects":[{"StartTime":169658.0,"Position":146.0,"HyperDash":false},{"StartTime":169794.0,"Position":132.130035,"HyperDash":false}]},{"StartTime":169930.0,"Objects":[{"StartTime":169930.0,"Position":19.0,"HyperDash":false},{"StartTime":170066.0,"Position":5.13003731,"HyperDash":true}]},{"StartTime":170202.0,"Objects":[{"StartTime":170202.0,"Position":280.0,"HyperDash":false},{"StartTime":170338.0,"Position":266.712738,"HyperDash":false}]},{"StartTime":170475.0,"Objects":[{"StartTime":170475.0,"Position":393.0,"HyperDash":false},{"StartTime":170611.0,"Position":406.869965,"HyperDash":false}]},{"StartTime":170748.0,"Objects":[{"StartTime":170748.0,"Position":278.0,"HyperDash":false},{"StartTime":170884.0,"Position":291.869965,"HyperDash":false}]},{"StartTime":171021.0,"Objects":[{"StartTime":171021.0,"Position":405.0,"HyperDash":false},{"StartTime":171157.0,"Position":418.869965,"HyperDash":false}]},{"StartTime":171293.0,"Objects":[{"StartTime":171293.0,"Position":290.0,"HyperDash":false},{"StartTime":171429.0,"Position":303.869965,"HyperDash":false}]},{"StartTime":171566.0,"Objects":[{"StartTime":171566.0,"Position":417.0,"HyperDash":false},{"StartTime":171702.0,"Position":430.869965,"HyperDash":false}]},{"StartTime":171839.0,"Objects":[{"StartTime":171839.0,"Position":302.0,"HyperDash":false},{"StartTime":171975.0,"Position":315.869965,"HyperDash":false}]},{"StartTime":172112.0,"Objects":[{"StartTime":172112.0,"Position":429.0,"HyperDash":false},{"StartTime":172248.0,"Position":442.869965,"HyperDash":false}]},{"StartTime":172384.0,"Objects":[{"StartTime":172384.0,"Position":512.0,"HyperDash":false}]},{"StartTime":173278.0,"Objects":[{"StartTime":173278.0,"Position":512.0,"HyperDash":false},{"StartTime":173333.0,"Position":461.544647,"HyperDash":false},{"StartTime":173389.0,"Position":440.884155,"HyperDash":false},{"StartTime":173444.0,"Position":394.892883,"HyperDash":false},{"StartTime":173500.0,"Position":373.234924,"HyperDash":false},{"StartTime":173611.0,"Position":383.5925,"HyperDash":false}]},{"StartTime":173722.0,"Objects":[{"StartTime":173722.0,"Position":327.0,"HyperDash":false},{"StartTime":173796.0,"Position":271.28595,"HyperDash":false},{"StartTime":173870.0,"Position":327.0,"HyperDash":false},{"StartTime":173944.0,"Position":271.28595,"HyperDash":false},{"StartTime":174018.0,"Position":327.0,"HyperDash":false},{"StartTime":174092.0,"Position":271.28595,"HyperDash":false}]},{"StartTime":174166.0,"Objects":[{"StartTime":174166.0,"Position":178.0,"HyperDash":false},{"StartTime":174240.0,"Position":233.714035,"HyperDash":false},{"StartTime":174314.0,"Position":178.0,"HyperDash":false},{"StartTime":174388.0,"Position":233.714035,"HyperDash":false},{"StartTime":174462.0,"Position":178.0,"HyperDash":false},{"StartTime":174536.0,"Position":233.714035,"HyperDash":false}]},{"StartTime":174611.0,"Objects":[{"StartTime":174611.0,"Position":92.0,"HyperDash":false},{"StartTime":174685.0,"Position":36.285965,"HyperDash":false},{"StartTime":174759.0,"Position":92.0,"HyperDash":false}]},{"StartTime":174833.0,"Objects":[{"StartTime":174833.0,"Position":99.0,"HyperDash":false},{"StartTime":174907.0,"Position":43.285965,"HyperDash":false},{"StartTime":174981.0,"Position":99.0,"HyperDash":false}]},{"StartTime":175055.0,"Objects":[{"StartTime":175055.0,"Position":179.0,"HyperDash":false},{"StartTime":175166.0,"Position":178.317825,"HyperDash":false}]},{"StartTime":175278.0,"Objects":[{"StartTime":175278.0,"Position":84.0,"HyperDash":false}]},{"StartTime":175389.0,"Objects":[{"StartTime":175389.0,"Position":84.0,"HyperDash":false}]},{"StartTime":175500.0,"Objects":[{"StartTime":175500.0,"Position":84.0,"HyperDash":false},{"StartTime":175611.0,"Position":0.0,"HyperDash":false}]},{"StartTime":175722.0,"Objects":[{"StartTime":175722.0,"Position":176.0,"HyperDash":false},{"StartTime":175833.0,"Position":260.304535,"HyperDash":false}]},{"StartTime":175944.0,"Objects":[{"StartTime":175944.0,"Position":378.0,"HyperDash":false}]},{"StartTime":176055.0,"Objects":[{"StartTime":176055.0,"Position":359.0,"HyperDash":false}]},{"StartTime":176166.0,"Objects":[{"StartTime":176166.0,"Position":380.0,"HyperDash":false}]},{"StartTime":176278.0,"Objects":[{"StartTime":176278.0,"Position":437.0,"HyperDash":false}]},{"StartTime":176389.0,"Objects":[{"StartTime":176389.0,"Position":504.0,"HyperDash":false},{"StartTime":176500.0,"Position":500.1198,"HyperDash":false}]},{"StartTime":176611.0,"Objects":[{"StartTime":176611.0,"Position":464.0,"HyperDash":false},{"StartTime":176722.0,"Position":395.9769,"HyperDash":false}]},{"StartTime":176833.0,"Objects":[{"StartTime":176833.0,"Position":223.0,"HyperDash":false},{"StartTime":176888.0,"Position":222.049377,"HyperDash":false},{"StartTime":176944.0,"Position":265.432465,"HyperDash":false},{"StartTime":176999.0,"Position":288.0414,"HyperDash":false},{"StartTime":177055.0,"Position":311.180634,"HyperDash":false},{"StartTime":177166.0,"Position":320.170959,"HyperDash":false}]},{"StartTime":177278.0,"Objects":[{"StartTime":177278.0,"Position":314.0,"HyperDash":false}]},{"StartTime":177389.0,"Objects":[{"StartTime":177389.0,"Position":393.0,"HyperDash":false}]},{"StartTime":177500.0,"Objects":[{"StartTime":177500.0,"Position":393.0,"HyperDash":false},{"StartTime":177611.0,"Position":476.258362,"HyperDash":true}]},{"StartTime":177722.0,"Objects":[{"StartTime":177722.0,"Position":238.0,"HyperDash":false}]},{"StartTime":177833.0,"Objects":[{"StartTime":177833.0,"Position":238.0,"HyperDash":false}]},{"StartTime":177944.0,"Objects":[{"StartTime":177944.0,"Position":238.0,"HyperDash":false},{"StartTime":178055.0,"Position":154.741638,"HyperDash":false}]},{"StartTime":178166.0,"Objects":[{"StartTime":178166.0,"Position":51.0,"HyperDash":false},{"StartTime":178277.0,"Position":38.63025,"HyperDash":false}]},{"StartTime":178389.0,"Objects":[{"StartTime":178389.0,"Position":136.0,"HyperDash":false},{"StartTime":178500.0,"Position":149.298233,"HyperDash":false}]},{"StartTime":178611.0,"Objects":[{"StartTime":178611.0,"Position":311.0,"HyperDash":false},{"StartTime":178685.0,"Position":365.846741,"HyperDash":false},{"StartTime":178759.0,"Position":311.0,"HyperDash":false}]},{"StartTime":178833.0,"Objects":[{"StartTime":178833.0,"Position":361.0,"HyperDash":false},{"StartTime":178907.0,"Position":415.431976,"HyperDash":false},{"StartTime":178981.0,"Position":361.0,"HyperDash":false}]},{"StartTime":179055.0,"Objects":[{"StartTime":179055.0,"Position":368.0,"HyperDash":false},{"StartTime":179129.0,"Position":407.3476,"HyperDash":false},{"StartTime":179203.0,"Position":368.0,"HyperDash":false}]},{"StartTime":179278.0,"Objects":[{"StartTime":179278.0,"Position":330.0,"HyperDash":false},{"StartTime":179352.0,"Position":344.074615,"HyperDash":false},{"StartTime":179426.0,"Position":330.0,"HyperDash":false}]},{"StartTime":179500.0,"Objects":[{"StartTime":179500.0,"Position":442.0,"HyperDash":false}]},{"StartTime":179574.0,"Objects":[{"StartTime":179574.0,"Position":442.0,"HyperDash":false}]},{"StartTime":179648.0,"Objects":[{"StartTime":179648.0,"Position":442.0,"HyperDash":false}]},{"StartTime":179722.0,"Objects":[{"StartTime":179722.0,"Position":442.0,"HyperDash":false},{"StartTime":179796.0,"Position":427.7541,"HyperDash":false},{"StartTime":179870.0,"Position":442.0,"HyperDash":false}]},{"StartTime":179944.0,"Objects":[{"StartTime":179944.0,"Position":488.0,"HyperDash":false},{"StartTime":180037.0,"Position":417.2783,"HyperDash":false},{"StartTime":180166.0,"Position":350.759735,"HyperDash":false}]},{"StartTime":180389.0,"Objects":[{"StartTime":180389.0,"Position":114.0,"HyperDash":false},{"StartTime":180500.0,"Position":42.9713974,"HyperDash":false}]},{"StartTime":180611.0,"Objects":[{"StartTime":180611.0,"Position":0.0,"HyperDash":false},{"StartTime":180722.0,"Position":70.18777,"HyperDash":false}]},{"StartTime":180833.0,"Objects":[{"StartTime":180833.0,"Position":124.0,"HyperDash":false},{"StartTime":180944.0,"Position":110.17556,"HyperDash":false}]},{"StartTime":181055.0,"Objects":[{"StartTime":181055.0,"Position":201.0,"HyperDash":false},{"StartTime":181166.0,"Position":214.824432,"HyperDash":false}]},{"StartTime":181278.0,"Objects":[{"StartTime":181278.0,"Position":350.0,"HyperDash":false},{"StartTime":181389.0,"Position":414.6709,"HyperDash":false}]},{"StartTime":181500.0,"Objects":[{"StartTime":181500.0,"Position":497.0,"HyperDash":false},{"StartTime":181555.0,"Position":495.534149,"HyperDash":false},{"StartTime":181611.0,"Position":512.0,"HyperDash":false},{"StartTime":181722.0,"Position":497.0,"HyperDash":false}]},{"StartTime":181833.0,"Objects":[{"StartTime":181833.0,"Position":414.0,"HyperDash":false}]},{"StartTime":181944.0,"Objects":[{"StartTime":181944.0,"Position":414.0,"HyperDash":false},{"StartTime":182055.0,"Position":339.763519,"HyperDash":false}]},{"StartTime":182166.0,"Objects":[{"StartTime":182166.0,"Position":254.0,"HyperDash":false}]},{"StartTime":182278.0,"Objects":[{"StartTime":182278.0,"Position":186.0,"HyperDash":false}]},{"StartTime":182389.0,"Objects":[{"StartTime":182389.0,"Position":123.0,"HyperDash":false}]},{"StartTime":182500.0,"Objects":[{"StartTime":182500.0,"Position":89.0,"HyperDash":false}]},{"StartTime":182611.0,"Objects":[{"StartTime":182611.0,"Position":101.0,"HyperDash":false},{"StartTime":182666.0,"Position":108.090492,"HyperDash":false},{"StartTime":182722.0,"Position":101.513573,"HyperDash":false},{"StartTime":182777.0,"Position":95.97452,"HyperDash":false},{"StartTime":182833.0,"Position":76.8446,"HyperDash":false},{"StartTime":182944.0,"Position":79.74396,"HyperDash":false}]},{"StartTime":183055.0,"Objects":[{"StartTime":183055.0,"Position":0.0,"HyperDash":false},{"StartTime":183166.0,"Position":73.6922455,"HyperDash":false}]},{"StartTime":183278.0,"Objects":[{"StartTime":183278.0,"Position":176.0,"HyperDash":false},{"StartTime":183389.0,"Position":242.907639,"HyperDash":false}]},{"StartTime":183500.0,"Objects":[{"StartTime":183500.0,"Position":353.0,"HyperDash":false},{"StartTime":183611.0,"Position":361.0935,"HyperDash":false}]},{"StartTime":183722.0,"Objects":[{"StartTime":183722.0,"Position":473.0,"HyperDash":false},{"StartTime":183833.0,"Position":464.9065,"HyperDash":false}]},{"StartTime":183944.0,"Objects":[{"StartTime":183944.0,"Position":447.0,"HyperDash":false}]},{"StartTime":184055.0,"Objects":[{"StartTime":184055.0,"Position":447.0,"HyperDash":false}]},{"StartTime":184166.0,"Objects":[{"StartTime":184166.0,"Position":447.0,"HyperDash":false}]},{"StartTime":184277.0,"Objects":[{"StartTime":184277.0,"Position":463.0,"HyperDash":false}]},{"StartTime":184388.0,"Objects":[{"StartTime":184388.0,"Position":487.0,"HyperDash":false},{"StartTime":184499.0,"Position":478.9065,"HyperDash":false}]},{"StartTime":184611.0,"Objects":[{"StartTime":184611.0,"Position":344.0,"HyperDash":false},{"StartTime":184722.0,"Position":335.9065,"HyperDash":false}]},{"StartTime":184833.0,"Objects":[{"StartTime":184833.0,"Position":233.0,"HyperDash":false},{"StartTime":184944.0,"Position":153.960419,"HyperDash":false}]},{"StartTime":185055.0,"Objects":[{"StartTime":185055.0,"Position":19.0,"HyperDash":false},{"StartTime":185166.0,"Position":98.20671,"HyperDash":false}]},{"StartTime":185278.0,"Objects":[{"StartTime":185278.0,"Position":224.0,"HyperDash":false}]},{"StartTime":185389.0,"Objects":[{"StartTime":185389.0,"Position":229.0,"HyperDash":false}]},{"StartTime":185500.0,"Objects":[{"StartTime":185500.0,"Position":203.0,"HyperDash":false}]},{"StartTime":185611.0,"Objects":[{"StartTime":185611.0,"Position":148.0,"HyperDash":false}]},{"StartTime":185722.0,"Objects":[{"StartTime":185722.0,"Position":80.0,"HyperDash":false},{"StartTime":185833.0,"Position":31.5825539,"HyperDash":true}]},{"StartTime":185944.0,"Objects":[{"StartTime":185944.0,"Position":227.0,"HyperDash":false},{"StartTime":186018.0,"Position":266.7068,"HyperDash":false},{"StartTime":186092.0,"Position":227.0,"HyperDash":false}]},{"StartTime":186166.0,"Objects":[{"StartTime":186166.0,"Position":306.0,"HyperDash":false},{"StartTime":186240.0,"Position":360.619873,"HyperDash":false},{"StartTime":186314.0,"Position":306.0,"HyperDash":false}]},{"StartTime":186388.0,"Objects":[{"StartTime":186388.0,"Position":358.0,"HyperDash":false},{"StartTime":186462.0,"Position":412.8009,"HyperDash":false},{"StartTime":186536.0,"Position":358.0,"HyperDash":false}]},{"StartTime":186611.0,"Objects":[{"StartTime":186611.0,"Position":366.0,"HyperDash":false},{"StartTime":186685.0,"Position":406.4224,"HyperDash":false},{"StartTime":186759.0,"Position":366.0,"HyperDash":false}]},{"StartTime":186833.0,"Objects":[{"StartTime":186833.0,"Position":512.0,"HyperDash":false}]},{"StartTime":186907.0,"Objects":[{"StartTime":186907.0,"Position":512.0,"HyperDash":false}]},{"StartTime":186981.0,"Objects":[{"StartTime":186981.0,"Position":512.0,"HyperDash":false}]},{"StartTime":187055.0,"Objects":[{"StartTime":187055.0,"Position":512.0,"HyperDash":false},{"StartTime":187129.0,"Position":471.5776,"HyperDash":false},{"StartTime":187203.0,"Position":512.0,"HyperDash":false}]},{"StartTime":187277.0,"Objects":[{"StartTime":187277.0,"Position":469.0,"HyperDash":false},{"StartTime":187333.0,"Position":428.048767,"HyperDash":false},{"StartTime":187425.0,"Position":370.993652,"HyperDash":false}]},{"StartTime":187500.0,"Objects":[{"StartTime":187500.0,"Position":346.0,"HyperDash":false},{"StartTime":187555.0,"Position":324.884857,"HyperDash":false},{"StartTime":187611.0,"Position":281.551849,"HyperDash":false},{"StartTime":187666.0,"Position":288.422363,"HyperDash":false},{"StartTime":187722.0,"Position":306.796173,"HyperDash":false},{"StartTime":187833.0,"Position":357.976379,"HyperDash":false}]},{"StartTime":187944.0,"Objects":[{"StartTime":187944.0,"Position":326.0,"HyperDash":false}]},{"StartTime":188055.0,"Objects":[{"StartTime":188055.0,"Position":397.0,"HyperDash":false},{"StartTime":188166.0,"Position":479.230957,"HyperDash":true}]},{"StartTime":188278.0,"Objects":[{"StartTime":188278.0,"Position":269.0,"HyperDash":false}]},{"StartTime":188389.0,"Objects":[{"StartTime":188389.0,"Position":269.0,"HyperDash":false},{"StartTime":188500.0,"Position":220.272171,"HyperDash":false}]},{"StartTime":188611.0,"Objects":[{"StartTime":188611.0,"Position":209.0,"HyperDash":false},{"StartTime":188722.0,"Position":124.709274,"HyperDash":false}]},{"StartTime":188833.0,"Objects":[{"StartTime":188833.0,"Position":13.0,"HyperDash":false},{"StartTime":188944.0,"Position":97.2907257,"HyperDash":false}]},{"StartTime":189055.0,"Objects":[{"StartTime":189055.0,"Position":163.0,"HyperDash":false},{"StartTime":189166.0,"Position":78.7092743,"HyperDash":false}]},{"StartTime":189277.0,"Objects":[{"StartTime":189277.0,"Position":133.0,"HyperDash":false},{"StartTime":189388.0,"Position":217.403992,"HyperDash":false}]},{"StartTime":189499.0,"Objects":[{"StartTime":189499.0,"Position":248.0,"HyperDash":false},{"StartTime":189573.0,"Position":288.0694,"HyperDash":false},{"StartTime":189647.0,"Position":248.0,"HyperDash":false}]},{"StartTime":189721.0,"Objects":[{"StartTime":189721.0,"Position":309.0,"HyperDash":false},{"StartTime":189795.0,"Position":323.2212,"HyperDash":false},{"StartTime":189869.0,"Position":309.0,"HyperDash":false}]},{"StartTime":189944.0,"Objects":[{"StartTime":189944.0,"Position":414.0,"HyperDash":false},{"StartTime":190018.0,"Position":398.833527,"HyperDash":false},{"StartTime":190092.0,"Position":414.0,"HyperDash":false}]},{"StartTime":190166.0,"Objects":[{"StartTime":190166.0,"Position":468.0,"HyperDash":false},{"StartTime":190240.0,"Position":482.2459,"HyperDash":false},{"StartTime":190314.0,"Position":468.0,"HyperDash":false},{"StartTime":190388.0,"Position":482.2459,"HyperDash":false},{"StartTime":190462.0,"Position":468.0,"HyperDash":false},{"StartTime":190536.0,"Position":482.2459,"HyperDash":false}]},{"StartTime":190611.0,"Objects":[{"StartTime":190611.0,"Position":408.0,"HyperDash":false},{"StartTime":190685.0,"Position":422.909973,"HyperDash":false},{"StartTime":190759.0,"Position":408.0,"HyperDash":false}]},{"StartTime":190833.0,"Objects":[{"StartTime":190833.0,"Position":399.0,"HyperDash":false},{"StartTime":190907.0,"Position":413.2212,"HyperDash":false},{"StartTime":190981.0,"Position":399.0,"HyperDash":false}]},{"StartTime":191055.0,"Objects":[{"StartTime":191055.0,"Position":311.0,"HyperDash":false},{"StartTime":191148.0,"Position":357.4903,"HyperDash":false},{"StartTime":191277.0,"Position":394.428223,"HyperDash":false}]},{"StartTime":191389.0,"Objects":[{"StartTime":191389.0,"Position":272.0,"HyperDash":false}]},{"StartTime":191500.0,"Objects":[{"StartTime":191500.0,"Position":272.0,"HyperDash":false},{"StartTime":191611.0,"Position":336.857483,"HyperDash":false}]},{"StartTime":191722.0,"Objects":[{"StartTime":191722.0,"Position":461.0,"HyperDash":false},{"StartTime":191833.0,"Position":383.333038,"HyperDash":false}]},{"StartTime":191944.0,"Objects":[{"StartTime":191944.0,"Position":215.0,"HyperDash":false}]},{"StartTime":192055.0,"Objects":[{"StartTime":192055.0,"Position":189.0,"HyperDash":false}]},{"StartTime":192166.0,"Objects":[{"StartTime":192166.0,"Position":157.0,"HyperDash":false}]},{"StartTime":192277.0,"Objects":[{"StartTime":192277.0,"Position":123.0,"HyperDash":false}]},{"StartTime":192389.0,"Objects":[{"StartTime":192389.0,"Position":89.0,"HyperDash":false},{"StartTime":192500.0,"Position":17.9128532,"HyperDash":false}]},{"StartTime":192611.0,"Objects":[{"StartTime":192611.0,"Position":54.0,"HyperDash":false},{"StartTime":192722.0,"Position":52.84656,"HyperDash":false}]},{"StartTime":192833.0,"Objects":[{"StartTime":192833.0,"Position":208.0,"HyperDash":false},{"StartTime":192944.0,"Position":194.175568,"HyperDash":false}]},{"StartTime":193055.0,"Objects":[{"StartTime":193055.0,"Position":275.0,"HyperDash":false},{"StartTime":193166.0,"Position":288.824432,"HyperDash":false}]},{"StartTime":193277.0,"Objects":[{"StartTime":193277.0,"Position":415.0,"HyperDash":false}]},{"StartTime":193389.0,"Objects":[{"StartTime":193389.0,"Position":461.0,"HyperDash":false}]},{"StartTime":193500.0,"Objects":[{"StartTime":193500.0,"Position":458.0,"HyperDash":false}]},{"StartTime":193611.0,"Objects":[{"StartTime":193611.0,"Position":413.0,"HyperDash":false}]},{"StartTime":193722.0,"Objects":[{"StartTime":193722.0,"Position":329.0,"HyperDash":false},{"StartTime":193833.0,"Position":246.696991,"HyperDash":false}]},{"StartTime":193944.0,"Objects":[{"StartTime":193944.0,"Position":377.0,"HyperDash":false},{"StartTime":194055.0,"Position":459.303,"HyperDash":false}]},{"StartTime":194166.0,"Objects":[{"StartTime":194166.0,"Position":491.0,"HyperDash":false},{"StartTime":194259.0,"Position":480.782471,"HyperDash":false},{"StartTime":194388.0,"Position":427.072449,"HyperDash":true}]},{"StartTime":194611.0,"Objects":[{"StartTime":194611.0,"Position":51.0,"HyperDash":false},{"StartTime":194666.0,"Position":105.644623,"HyperDash":false},{"StartTime":194722.0,"Position":112.4984,"HyperDash":false},{"StartTime":194777.0,"Position":153.00119,"HyperDash":false},{"StartTime":194833.0,"Position":192.190445,"HyperDash":false},{"StartTime":194926.0,"Position":250.960892,"HyperDash":false},{"StartTime":195055.0,"Position":334.876526,"HyperDash":false}]},{"StartTime":195166.0,"Objects":[{"StartTime":195166.0,"Position":165.0,"HyperDash":false}]},{"StartTime":195277.0,"Objects":[{"StartTime":195277.0,"Position":201.0,"HyperDash":false},{"StartTime":195388.0,"Position":256.006256,"HyperDash":true}]},{"StartTime":195500.0,"Objects":[{"StartTime":195500.0,"Position":47.0,"HyperDash":false},{"StartTime":195611.0,"Position":70.89899,"HyperDash":false}]},{"StartTime":195722.0,"Objects":[{"StartTime":195722.0,"Position":238.0,"HyperDash":false}]},{"StartTime":195833.0,"Objects":[{"StartTime":195833.0,"Position":320.0,"HyperDash":false}]},{"StartTime":195944.0,"Objects":[{"StartTime":195944.0,"Position":402.0,"HyperDash":false}]},{"StartTime":196055.0,"Objects":[{"StartTime":196055.0,"Position":462.0,"HyperDash":false}]},{"StartTime":196166.0,"Objects":[{"StartTime":196166.0,"Position":484.0,"HyperDash":false},{"StartTime":196222.0,"Position":495.415375,"HyperDash":false},{"StartTime":196314.0,"Position":425.076019,"HyperDash":false}]},{"StartTime":196389.0,"Objects":[{"StartTime":196389.0,"Position":354.0,"HyperDash":false},{"StartTime":196463.0,"Position":360.907166,"HyperDash":false},{"StartTime":196537.0,"Position":354.0,"HyperDash":false}]},{"StartTime":196611.0,"Objects":[{"StartTime":196611.0,"Position":290.0,"HyperDash":false},{"StartTime":196685.0,"Position":296.907166,"HyperDash":false},{"StartTime":196759.0,"Position":290.0,"HyperDash":false},{"StartTime":196833.0,"Position":296.907166,"HyperDash":false}]},{"StartTime":196907.0,"Objects":[{"StartTime":196907.0,"Position":242.0,"HyperDash":false},{"StartTime":196981.0,"Position":233.986115,"HyperDash":false}]},{"StartTime":197055.0,"Objects":[{"StartTime":197055.0,"Position":192.0,"HyperDash":false},{"StartTime":197129.0,"Position":199.028641,"HyperDash":false},{"StartTime":197203.0,"Position":192.0,"HyperDash":false}]},{"StartTime":197277.0,"Objects":[{"StartTime":197277.0,"Position":108.0,"HyperDash":false},{"StartTime":197351.0,"Position":51.770916,"HyperDash":false},{"StartTime":197425.0,"Position":108.0,"HyperDash":false},{"StartTime":197499.0,"Position":51.770916,"HyperDash":false},{"StartTime":197573.0,"Position":108.0,"HyperDash":false},{"StartTime":197647.0,"Position":51.770916,"HyperDash":false}]},{"StartTime":197722.0,"Objects":[{"StartTime":197722.0,"Position":0.0,"HyperDash":false},{"StartTime":197815.0,"Position":60.3444443,"HyperDash":false},{"StartTime":197944.0,"Position":111.30835,"HyperDash":false}]},{"StartTime":198166.0,"Objects":[{"StartTime":198166.0,"Position":391.0,"HyperDash":false},{"StartTime":198240.0,"Position":446.9797,"HyperDash":false},{"StartTime":198314.0,"Position":391.0,"HyperDash":false},{"StartTime":198388.0,"Position":446.9797,"HyperDash":false},{"StartTime":198462.0,"Position":391.0,"HyperDash":false},{"StartTime":198536.0,"Position":446.9797,"HyperDash":false}]},{"StartTime":198611.0,"Objects":[{"StartTime":198611.0,"Position":317.0,"HyperDash":false}]},{"StartTime":198685.0,"Objects":[{"StartTime":198685.0,"Position":317.0,"HyperDash":false}]},{"StartTime":198759.0,"Objects":[{"StartTime":198759.0,"Position":317.0,"HyperDash":false}]},{"StartTime":198833.0,"Objects":[{"StartTime":198833.0,"Position":317.0,"HyperDash":false},{"StartTime":198907.0,"Position":261.0203,"HyperDash":false},{"StartTime":198981.0,"Position":317.0,"HyperDash":false}]},{"StartTime":199055.0,"Objects":[{"StartTime":199055.0,"Position":392.0,"HyperDash":false},{"StartTime":199129.0,"Position":400.7968,"HyperDash":false},{"StartTime":199203.0,"Position":392.0,"HyperDash":false},{"StartTime":199277.0,"Position":400.7968,"HyperDash":false},{"StartTime":199351.0,"Position":392.0,"HyperDash":false},{"StartTime":199425.0,"Position":400.7968,"HyperDash":false}]},{"StartTime":199500.0,"Objects":[{"StartTime":199500.0,"Position":494.0,"HyperDash":false},{"StartTime":199574.0,"Position":485.2032,"HyperDash":false},{"StartTime":199648.0,"Position":494.0,"HyperDash":false},{"StartTime":199722.0,"Position":485.2032,"HyperDash":false},{"StartTime":199796.0,"Position":494.0,"HyperDash":false},{"StartTime":199870.0,"Position":485.2032,"HyperDash":false}]},{"StartTime":199944.0,"Objects":[{"StartTime":199944.0,"Position":400.0,"HyperDash":false},{"StartTime":200018.0,"Position":344.0203,"HyperDash":false},{"StartTime":200092.0,"Position":400.0,"HyperDash":false},{"StartTime":200166.0,"Position":344.0203,"HyperDash":false},{"StartTime":200240.0,"Position":400.0,"HyperDash":false},{"StartTime":200314.0,"Position":344.0203,"HyperDash":false}]},{"StartTime":200389.0,"Objects":[{"StartTime":200389.0,"Position":267.0,"HyperDash":false}]},{"StartTime":200463.0,"Objects":[{"StartTime":200463.0,"Position":267.0,"HyperDash":false}]},{"StartTime":200537.0,"Objects":[{"StartTime":200537.0,"Position":267.0,"HyperDash":false}]},{"StartTime":200611.0,"Objects":[{"StartTime":200611.0,"Position":267.0,"HyperDash":false},{"StartTime":200685.0,"Position":211.0203,"HyperDash":false},{"StartTime":200759.0,"Position":267.0,"HyperDash":false}]},{"StartTime":200833.0,"Objects":[{"StartTime":200833.0,"Position":121.0,"HyperDash":false},{"StartTime":200907.0,"Position":112.203186,"HyperDash":false},{"StartTime":200981.0,"Position":121.0,"HyperDash":false},{"StartTime":201055.0,"Position":112.203186,"HyperDash":false},{"StartTime":201129.0,"Position":121.0,"HyperDash":false},{"StartTime":201203.0,"Position":112.203186,"HyperDash":false}]},{"StartTime":201277.0,"Objects":[{"StartTime":201277.0,"Position":179.0,"HyperDash":false},{"StartTime":201351.0,"Position":170.203186,"HyperDash":false},{"StartTime":201425.0,"Position":179.0,"HyperDash":false}]},{"StartTime":201500.0,"Objects":[{"StartTime":201500.0,"Position":67.0,"HyperDash":false},{"StartTime":201574.0,"Position":75.796814,"HyperDash":false},{"StartTime":201648.0,"Position":67.0,"HyperDash":false}]},{"StartTime":201722.0,"Objects":[{"StartTime":201722.0,"Position":11.0,"HyperDash":false}]},{"StartTime":201776.0,"Objects":[{"StartTime":201776.0,"Position":88.0,"HyperDash":false},{"StartTime":201830.0,"Position":257.0,"HyperDash":false},{"StartTime":201885.0,"Position":175.0,"HyperDash":false},{"StartTime":201940.0,"Position":38.0,"HyperDash":false},{"StartTime":201994.0,"Position":283.0,"HyperDash":false},{"StartTime":202049.0,"Position":138.0,"HyperDash":false},{"StartTime":202104.0,"Position":102.0,"HyperDash":false},{"StartTime":202158.0,"Position":494.0,"HyperDash":false},{"StartTime":202213.0,"Position":54.0,"HyperDash":false},{"StartTime":202268.0,"Position":29.0,"HyperDash":false},{"StartTime":202322.0,"Position":69.0,"HyperDash":false},{"StartTime":202377.0,"Position":110.0,"HyperDash":false},{"StartTime":202432.0,"Position":167.0,"HyperDash":false},{"StartTime":202486.0,"Position":56.0,"HyperDash":false},{"StartTime":202541.0,"Position":10.0,"HyperDash":false},{"StartTime":202596.0,"Position":308.0,"HyperDash":false},{"StartTime":202651.0,"Position":288.0,"HyperDash":false},{"StartTime":202705.0,"Position":57.0,"HyperDash":false},{"StartTime":202760.0,"Position":258.0,"HyperDash":false},{"StartTime":202815.0,"Position":180.0,"HyperDash":false},{"StartTime":202869.0,"Position":198.0,"HyperDash":false},{"StartTime":202924.0,"Position":211.0,"HyperDash":false},{"StartTime":202979.0,"Position":503.0,"HyperDash":false},{"StartTime":203033.0,"Position":324.0,"HyperDash":false},{"StartTime":203088.0,"Position":20.0,"HyperDash":false},{"StartTime":203143.0,"Position":169.0,"HyperDash":false},{"StartTime":203197.0,"Position":93.0,"HyperDash":false},{"StartTime":203252.0,"Position":267.0,"HyperDash":false},{"StartTime":203307.0,"Position":276.0,"HyperDash":false},{"StartTime":203361.0,"Position":367.0,"HyperDash":false},{"StartTime":203416.0,"Position":409.0,"HyperDash":false},{"StartTime":203471.0,"Position":117.0,"HyperDash":false},{"StartTime":203526.0,"Position":226.0,"HyperDash":false},{"StartTime":203580.0,"Position":469.0,"HyperDash":false},{"StartTime":203635.0,"Position":267.0,"HyperDash":false},{"StartTime":203690.0,"Position":477.0,"HyperDash":false},{"StartTime":203744.0,"Position":282.0,"HyperDash":false},{"StartTime":203799.0,"Position":216.0,"HyperDash":false},{"StartTime":203854.0,"Position":106.0,"HyperDash":false},{"StartTime":203908.0,"Position":353.0,"HyperDash":false},{"StartTime":203963.0,"Position":162.0,"HyperDash":false},{"StartTime":204018.0,"Position":473.0,"HyperDash":false},{"StartTime":204072.0,"Position":260.0,"HyperDash":false},{"StartTime":204127.0,"Position":367.0,"HyperDash":false},{"StartTime":204182.0,"Position":409.0,"HyperDash":false},{"StartTime":204236.0,"Position":145.0,"HyperDash":false},{"StartTime":204291.0,"Position":330.0,"HyperDash":false},{"StartTime":204346.0,"Position":104.0,"HyperDash":false},{"StartTime":204401.0,"Position":412.0,"HyperDash":false},{"StartTime":204455.0,"Position":104.0,"HyperDash":false},{"StartTime":204510.0,"Position":396.0,"HyperDash":false},{"StartTime":204565.0,"Position":192.0,"HyperDash":false},{"StartTime":204619.0,"Position":446.0,"HyperDash":false},{"StartTime":204674.0,"Position":110.0,"HyperDash":false},{"StartTime":204729.0,"Position":372.0,"HyperDash":false},{"StartTime":204783.0,"Position":100.0,"HyperDash":false},{"StartTime":204838.0,"Position":161.0,"HyperDash":false},{"StartTime":204893.0,"Position":456.0,"HyperDash":false},{"StartTime":204947.0,"Position":187.0,"HyperDash":false},{"StartTime":205002.0,"Position":99.0,"HyperDash":false},{"StartTime":205057.0,"Position":197.0,"HyperDash":false},{"StartTime":205111.0,"Position":116.0,"HyperDash":false},{"StartTime":205166.0,"Position":496.0,"HyperDash":false},{"StartTime":205221.0,"Position":143.0,"HyperDash":false},{"StartTime":205276.0,"Position":431.0,"HyperDash":false}]},{"StartTime":207943.0,"Objects":[{"StartTime":207943.0,"Position":171.0,"HyperDash":false},{"StartTime":208011.0,"Position":175.536011,"HyperDash":false},{"StartTime":208079.0,"Position":171.0,"HyperDash":false},{"StartTime":208147.0,"Position":175.536011,"HyperDash":false},{"StartTime":208215.0,"Position":171.0,"HyperDash":false},{"StartTime":208283.0,"Position":175.536011,"HyperDash":false},{"StartTime":208352.0,"Position":171.0,"HyperDash":false},{"StartTime":208420.0,"Position":175.536011,"HyperDash":false},{"StartTime":208488.0,"Position":171.0,"HyperDash":false},{"StartTime":208556.0,"Position":175.536011,"HyperDash":false},{"StartTime":208624.0,"Position":171.0,"HyperDash":false},{"StartTime":208693.0,"Position":175.536011,"HyperDash":false},{"StartTime":208761.0,"Position":171.0,"HyperDash":false},{"StartTime":208829.0,"Position":175.536011,"HyperDash":false},{"StartTime":208897.0,"Position":171.0,"HyperDash":false},{"StartTime":208965.0,"Position":175.536011,"HyperDash":false},{"StartTime":209033.0,"Position":171.0,"HyperDash":false},{"StartTime":209102.0,"Position":175.536011,"HyperDash":false},{"StartTime":209170.0,"Position":171.0,"HyperDash":false},{"StartTime":209238.0,"Position":175.536011,"HyperDash":false},{"StartTime":209306.0,"Position":171.0,"HyperDash":false},{"StartTime":209374.0,"Position":175.536011,"HyperDash":false},{"StartTime":209443.0,"Position":171.0,"HyperDash":false},{"StartTime":209511.0,"Position":175.536011,"HyperDash":false},{"StartTime":209579.0,"Position":171.0,"HyperDash":false},{"StartTime":209647.0,"Position":175.536011,"HyperDash":false},{"StartTime":209715.0,"Position":171.0,"HyperDash":false},{"StartTime":209783.0,"Position":175.536011,"HyperDash":false},{"StartTime":209852.0,"Position":171.0,"HyperDash":false},{"StartTime":209920.0,"Position":175.536011,"HyperDash":false},{"StartTime":209988.0,"Position":171.0,"HyperDash":false},{"StartTime":210056.0,"Position":175.536011,"HyperDash":false}]},{"StartTime":210124.0,"Objects":[{"StartTime":210124.0,"Position":85.0,"HyperDash":false}]},{"StartTime":210329.0,"Objects":[{"StartTime":210329.0,"Position":73.0,"HyperDash":false}]},{"StartTime":210533.0,"Objects":[{"StartTime":210533.0,"Position":243.0,"HyperDash":false}]},{"StartTime":210670.0,"Objects":[{"StartTime":210670.0,"Position":122.0,"HyperDash":false}]},{"StartTime":210875.0,"Objects":[{"StartTime":210875.0,"Position":61.0,"HyperDash":false}]},{"StartTime":211079.0,"Objects":[{"StartTime":211079.0,"Position":246.0,"HyperDash":false}]},{"StartTime":211215.0,"Objects":[{"StartTime":211215.0,"Position":294.0,"HyperDash":false},{"StartTime":211283.0,"Position":253.395386,"HyperDash":false},{"StartTime":211351.0,"Position":294.0,"HyperDash":false},{"StartTime":211419.0,"Position":253.395386,"HyperDash":false}]},{"StartTime":211488.0,"Objects":[{"StartTime":211488.0,"Position":369.0,"HyperDash":false},{"StartTime":211556.0,"Position":409.5123,"HyperDash":false},{"StartTime":211624.0,"Position":369.0,"HyperDash":false},{"StartTime":211692.0,"Position":409.5123,"HyperDash":false}]},{"StartTime":211761.0,"Objects":[{"StartTime":211761.0,"Position":319.0,"HyperDash":false},{"StartTime":211829.0,"Position":306.78772,"HyperDash":false},{"StartTime":211897.0,"Position":319.0,"HyperDash":false},{"StartTime":211965.0,"Position":306.78772,"HyperDash":false}]},{"StartTime":212033.0,"Objects":[{"StartTime":212033.0,"Position":221.0,"HyperDash":false},{"StartTime":212101.0,"Position":209.0618,"HyperDash":false},{"StartTime":212169.0,"Position":221.0,"HyperDash":false},{"StartTime":212237.0,"Position":209.0618,"HyperDash":false}]},{"StartTime":212306.0,"Objects":[{"StartTime":212306.0,"Position":121.0,"HyperDash":false}]},{"StartTime":212374.0,"Objects":[{"StartTime":212374.0,"Position":112.0,"HyperDash":false}]},{"StartTime":212442.0,"Objects":[{"StartTime":212442.0,"Position":103.0,"HyperDash":false}]},{"StartTime":212579.0,"Objects":[{"StartTime":212579.0,"Position":78.0,"HyperDash":false}]},{"StartTime":212647.0,"Objects":[{"StartTime":212647.0,"Position":87.0,"HyperDash":false}]},{"StartTime":212715.0,"Objects":[{"StartTime":212715.0,"Position":96.0,"HyperDash":false}]},{"StartTime":212851.0,"Objects":[{"StartTime":212851.0,"Position":0.0,"HyperDash":false},{"StartTime":212919.0,"Position":12.8453636,"HyperDash":false},{"StartTime":212987.0,"Position":0.0,"HyperDash":false}]},{"StartTime":213124.0,"Objects":[{"StartTime":213124.0,"Position":77.0,"HyperDash":false},{"StartTime":213192.0,"Position":65.0618057,"HyperDash":false},{"StartTime":213260.0,"Position":77.0,"HyperDash":false}]},{"StartTime":213397.0,"Objects":[{"StartTime":213397.0,"Position":131.0,"HyperDash":false},{"StartTime":213465.0,"Position":171.788834,"HyperDash":false},{"StartTime":213533.0,"Position":131.0,"HyperDash":false},{"StartTime":213601.0,"Position":171.788834,"HyperDash":false}]},{"StartTime":213670.0,"Objects":[{"StartTime":213670.0,"Position":261.0,"HyperDash":false},{"StartTime":213738.0,"Position":301.6046,"HyperDash":false},{"StartTime":213806.0,"Position":261.0,"HyperDash":false},{"StartTime":213874.0,"Position":301.6046,"HyperDash":false}]},{"StartTime":213942.0,"Objects":[{"StartTime":213942.0,"Position":366.0,"HyperDash":false},{"StartTime":214010.0,"Position":353.78772,"HyperDash":false},{"StartTime":214078.0,"Position":366.0,"HyperDash":false},{"StartTime":214146.0,"Position":353.78772,"HyperDash":false}]},{"StartTime":214215.0,"Objects":[{"StartTime":214215.0,"Position":456.0,"HyperDash":false},{"StartTime":214283.0,"Position":443.78772,"HyperDash":false},{"StartTime":214351.0,"Position":456.0,"HyperDash":false},{"StartTime":214419.0,"Position":443.78772,"HyperDash":false}]},{"StartTime":214488.0,"Objects":[{"StartTime":214488.0,"Position":490.0,"HyperDash":false}]},{"StartTime":214556.0,"Objects":[{"StartTime":214556.0,"Position":487.0,"HyperDash":false}]},{"StartTime":214624.0,"Objects":[{"StartTime":214624.0,"Position":484.0,"HyperDash":false}]},{"StartTime":214761.0,"Objects":[{"StartTime":214761.0,"Position":419.0,"HyperDash":false}]},{"StartTime":214829.0,"Objects":[{"StartTime":214829.0,"Position":422.0,"HyperDash":false}]},{"StartTime":214897.0,"Objects":[{"StartTime":214897.0,"Position":425.0,"HyperDash":false}]},{"StartTime":215033.0,"Objects":[{"StartTime":215033.0,"Position":344.0,"HyperDash":false}]},{"StartTime":215101.0,"Objects":[{"StartTime":215101.0,"Position":336.0,"HyperDash":false}]},{"StartTime":215170.0,"Objects":[{"StartTime":215170.0,"Position":328.0,"HyperDash":false},{"StartTime":215306.0,"Position":243.560242,"HyperDash":false}]},{"StartTime":215579.0,"Objects":[{"StartTime":215579.0,"Position":238.0,"HyperDash":false},{"StartTime":215647.0,"Position":250.21228,"HyperDash":false},{"StartTime":215715.0,"Position":238.0,"HyperDash":false},{"StartTime":215783.0,"Position":250.21228,"HyperDash":false}]},{"StartTime":215852.0,"Objects":[{"StartTime":215852.0,"Position":182.0,"HyperDash":false},{"StartTime":215920.0,"Position":169.78772,"HyperDash":false},{"StartTime":215988.0,"Position":182.0,"HyperDash":false},{"StartTime":216056.0,"Position":169.78772,"HyperDash":false}]},{"StartTime":216124.0,"Objects":[{"StartTime":216124.0,"Position":90.0,"HyperDash":false},{"StartTime":216192.0,"Position":49.2111626,"HyperDash":false},{"StartTime":216260.0,"Position":90.0,"HyperDash":false},{"StartTime":216328.0,"Position":49.2111626,"HyperDash":false}]},{"StartTime":216397.0,"Objects":[{"StartTime":216397.0,"Position":51.0,"HyperDash":false},{"StartTime":216465.0,"Position":10.3953857,"HyperDash":false},{"StartTime":216533.0,"Position":51.0,"HyperDash":false},{"StartTime":216601.0,"Position":10.3953857,"HyperDash":false}]},{"StartTime":216670.0,"Objects":[{"StartTime":216670.0,"Position":64.0,"HyperDash":false}]},{"StartTime":216738.0,"Objects":[{"StartTime":216738.0,"Position":73.0,"HyperDash":false}]},{"StartTime":216806.0,"Objects":[{"StartTime":216806.0,"Position":82.0,"HyperDash":false}]},{"StartTime":216942.0,"Objects":[{"StartTime":216942.0,"Position":191.0,"HyperDash":false}]},{"StartTime":217010.0,"Objects":[{"StartTime":217010.0,"Position":200.0,"HyperDash":false}]},{"StartTime":217078.0,"Objects":[{"StartTime":217078.0,"Position":209.0,"HyperDash":false}]},{"StartTime":217215.0,"Objects":[{"StartTime":217215.0,"Position":243.0,"HyperDash":false},{"StartTime":217283.0,"Position":283.6046,"HyperDash":false},{"StartTime":217351.0,"Position":243.0,"HyperDash":false}]},{"StartTime":217488.0,"Objects":[{"StartTime":217488.0,"Position":350.0,"HyperDash":false},{"StartTime":217556.0,"Position":309.211151,"HyperDash":false},{"StartTime":217624.0,"Position":350.0,"HyperDash":false}]},{"StartTime":217761.0,"Objects":[{"StartTime":217761.0,"Position":425.0,"HyperDash":false},{"StartTime":217829.0,"Position":412.78772,"HyperDash":false},{"StartTime":217897.0,"Position":425.0,"HyperDash":false},{"StartTime":217965.0,"Position":412.78772,"HyperDash":false}]},{"StartTime":218034.0,"Objects":[{"StartTime":218034.0,"Position":481.0,"HyperDash":false},{"StartTime":218102.0,"Position":493.21228,"HyperDash":false},{"StartTime":218170.0,"Position":481.0,"HyperDash":false},{"StartTime":218238.0,"Position":493.21228,"HyperDash":false}]},{"StartTime":218306.0,"Objects":[{"StartTime":218306.0,"Position":411.0,"HyperDash":false},{"StartTime":218374.0,"Position":370.211151,"HyperDash":false},{"StartTime":218442.0,"Position":411.0,"HyperDash":false},{"StartTime":218510.0,"Position":370.211151,"HyperDash":false}]},{"StartTime":218579.0,"Objects":[{"StartTime":218579.0,"Position":328.0,"HyperDash":false},{"StartTime":218647.0,"Position":287.3954,"HyperDash":false},{"StartTime":218715.0,"Position":328.0,"HyperDash":false},{"StartTime":218783.0,"Position":287.3954,"HyperDash":false}]},{"StartTime":218851.0,"Objects":[{"StartTime":218851.0,"Position":208.0,"HyperDash":false}]},{"StartTime":218919.0,"Objects":[{"StartTime":218919.0,"Position":205.0,"HyperDash":false}]},{"StartTime":218987.0,"Objects":[{"StartTime":218987.0,"Position":202.0,"HyperDash":false}]},{"StartTime":219124.0,"Objects":[{"StartTime":219124.0,"Position":120.0,"HyperDash":false}]},{"StartTime":219192.0,"Objects":[{"StartTime":219192.0,"Position":117.0,"HyperDash":false}]},{"StartTime":219260.0,"Objects":[{"StartTime":219260.0,"Position":114.0,"HyperDash":false}]},{"StartTime":219397.0,"Objects":[{"StartTime":219397.0,"Position":44.0,"HyperDash":false},{"StartTime":219465.0,"Position":51.91918,"HyperDash":false},{"StartTime":219533.0,"Position":44.0,"HyperDash":false},{"StartTime":219601.0,"Position":51.91918,"HyperDash":false},{"StartTime":219669.0,"Position":44.0,"HyperDash":false},{"StartTime":219737.0,"Position":51.91918,"HyperDash":false},{"StartTime":219806.0,"Position":44.0,"HyperDash":false},{"StartTime":219874.0,"Position":51.91918,"HyperDash":false}]},{"StartTime":219943.0,"Objects":[{"StartTime":219943.0,"Position":142.0,"HyperDash":false}]},{"StartTime":220011.0,"Objects":[{"StartTime":220011.0,"Position":146.0,"HyperDash":false}]},{"StartTime":220079.0,"Objects":[{"StartTime":220079.0,"Position":151.0,"HyperDash":false},{"StartTime":220147.0,"Position":192.8533,"HyperDash":false},{"StartTime":220215.0,"Position":151.0,"HyperDash":false},{"StartTime":220283.0,"Position":192.8533,"HyperDash":false}]},{"StartTime":220352.0,"Objects":[{"StartTime":220352.0,"Position":269.0,"HyperDash":false},{"StartTime":220420.0,"Position":310.8533,"HyperDash":false}]},{"StartTime":220488.0,"Objects":[{"StartTime":220488.0,"Position":320.0,"HyperDash":false},{"StartTime":220556.0,"Position":361.8533,"HyperDash":false},{"StartTime":220624.0,"Position":320.0,"HyperDash":false},{"StartTime":220692.0,"Position":361.8533,"HyperDash":false},{"StartTime":220760.0,"Position":320.0,"HyperDash":false},{"StartTime":220828.0,"Position":361.8533,"HyperDash":false},{"StartTime":220897.0,"Position":320.0,"HyperDash":false},{"StartTime":220965.0,"Position":361.8533,"HyperDash":false},{"StartTime":221033.0,"Position":320.0,"HyperDash":false}]},{"StartTime":222670.0,"Objects":[{"StartTime":222670.0,"Position":364.0,"HyperDash":false},{"StartTime":222738.0,"Position":404.113983,"HyperDash":false},{"StartTime":222806.0,"Position":364.0,"HyperDash":false},{"StartTime":222874.0,"Position":404.113983,"HyperDash":false},{"StartTime":222942.0,"Position":364.0,"HyperDash":false},{"StartTime":223010.0,"Position":404.113983,"HyperDash":false},{"StartTime":223079.0,"Position":364.0,"HyperDash":false},{"StartTime":223147.0,"Position":404.113983,"HyperDash":false}]},{"StartTime":223215.0,"Objects":[{"StartTime":223215.0,"Position":487.0,"HyperDash":false},{"StartTime":223351.0,"Position":471.39093,"HyperDash":false}]},{"StartTime":223488.0,"Objects":[{"StartTime":223488.0,"Position":437.0,"HyperDash":false},{"StartTime":223624.0,"Position":421.39093,"HyperDash":false}]},{"StartTime":223761.0,"Objects":[{"StartTime":223761.0,"Position":314.0,"HyperDash":false}]},{"StartTime":223897.0,"Objects":[{"StartTime":223897.0,"Position":240.0,"HyperDash":false}]},{"StartTime":223965.0,"Objects":[{"StartTime":223965.0,"Position":240.0,"HyperDash":false}]},{"StartTime":224033.0,"Objects":[{"StartTime":224033.0,"Position":240.0,"HyperDash":false},{"StartTime":224169.0,"Position":156.4455,"HyperDash":false}]},{"StartTime":224306.0,"Objects":[{"StartTime":224306.0,"Position":37.0,"HyperDash":false}]},{"StartTime":224443.0,"Objects":[{"StartTime":224443.0,"Position":37.0,"HyperDash":false}]},{"StartTime":224579.0,"Objects":[{"StartTime":224579.0,"Position":142.0,"HyperDash":false},{"StartTime":224715.0,"Position":225.463379,"HyperDash":false}]},{"StartTime":224852.0,"Objects":[{"StartTime":224852.0,"Position":304.0,"HyperDash":false},{"StartTime":224988.0,"Position":287.910675,"HyperDash":false}]},{"StartTime":225124.0,"Objects":[{"StartTime":225124.0,"Position":164.0,"HyperDash":false},{"StartTime":225192.0,"Position":172.139191,"HyperDash":false},{"StartTime":225260.0,"Position":164.0,"HyperDash":false},{"StartTime":225328.0,"Position":172.139191,"HyperDash":false}]},{"StartTime":225397.0,"Objects":[{"StartTime":225397.0,"Position":84.0,"HyperDash":false},{"StartTime":225533.0,"Position":144.172775,"HyperDash":false}]},{"StartTime":225670.0,"Objects":[{"StartTime":225670.0,"Position":86.0,"HyperDash":false},{"StartTime":225806.0,"Position":25.8272362,"HyperDash":false}]},{"StartTime":225943.0,"Objects":[{"StartTime":225943.0,"Position":39.0,"HyperDash":false},{"StartTime":226079.0,"Position":47.27571,"HyperDash":false}]},{"StartTime":226215.0,"Objects":[{"StartTime":226215.0,"Position":137.0,"HyperDash":false},{"StartTime":226351.0,"Position":128.724289,"HyperDash":false}]},{"StartTime":226488.0,"Objects":[{"StartTime":226488.0,"Position":237.0,"HyperDash":false},{"StartTime":226624.0,"Position":321.596161,"HyperDash":false}]},{"StartTime":226761.0,"Objects":[{"StartTime":226761.0,"Position":361.0,"HyperDash":false}]},{"StartTime":226897.0,"Objects":[{"StartTime":226897.0,"Position":361.0,"HyperDash":false}]},{"StartTime":227033.0,"Objects":[{"StartTime":227033.0,"Position":488.0,"HyperDash":false},{"StartTime":227169.0,"Position":479.724274,"HyperDash":false}]},{"StartTime":227306.0,"Objects":[{"StartTime":227306.0,"Position":429.0,"HyperDash":false},{"StartTime":227442.0,"Position":437.275726,"HyperDash":false}]},{"StartTime":227579.0,"Objects":[{"StartTime":227579.0,"Position":361.0,"HyperDash":false},{"StartTime":227715.0,"Position":346.8173,"HyperDash":false}]},{"StartTime":227852.0,"Objects":[{"StartTime":227852.0,"Position":195.0,"HyperDash":false},{"StartTime":227988.0,"Position":179.865,"HyperDash":false}]},{"StartTime":228124.0,"Objects":[{"StartTime":228124.0,"Position":211.0,"HyperDash":false}]},{"StartTime":228261.0,"Objects":[{"StartTime":228261.0,"Position":131.0,"HyperDash":false}]},{"StartTime":228329.0,"Objects":[{"StartTime":228329.0,"Position":131.0,"HyperDash":false}]},{"StartTime":228397.0,"Objects":[{"StartTime":228397.0,"Position":131.0,"HyperDash":false},{"StartTime":228533.0,"Position":46.3490829,"HyperDash":false}]},{"StartTime":228670.0,"Objects":[{"StartTime":228670.0,"Position":67.0,"HyperDash":false}]},{"StartTime":228738.0,"Objects":[{"StartTime":228738.0,"Position":59.0,"HyperDash":false}]},{"StartTime":228806.0,"Objects":[{"StartTime":228806.0,"Position":63.0,"HyperDash":false}]},{"StartTime":228874.0,"Objects":[{"StartTime":228874.0,"Position":79.0,"HyperDash":false}]},{"StartTime":228942.0,"Objects":[{"StartTime":228942.0,"Position":104.0,"HyperDash":false}]},{"StartTime":229079.0,"Objects":[{"StartTime":229079.0,"Position":210.0,"HyperDash":false}]},{"StartTime":229147.0,"Objects":[{"StartTime":229147.0,"Position":224.0,"HyperDash":false}]},{"StartTime":229215.0,"Objects":[{"StartTime":229215.0,"Position":238.0,"HyperDash":false},{"StartTime":229283.0,"Position":199.132248,"HyperDash":false},{"StartTime":229351.0,"Position":238.0,"HyperDash":false}]},{"StartTime":229488.0,"Objects":[{"StartTime":229488.0,"Position":353.0,"HyperDash":false},{"StartTime":229556.0,"Position":336.0176,"HyperDash":false}]},{"StartTime":229624.0,"Objects":[{"StartTime":229624.0,"Position":425.0,"HyperDash":false},{"StartTime":229692.0,"Position":408.0176,"HyperDash":false}]},{"StartTime":229760.0,"Objects":[{"StartTime":229760.0,"Position":495.0,"HyperDash":false}]},{"StartTime":231943.0,"Objects":[{"StartTime":231943.0,"Position":221.0,"HyperDash":false}]},{"StartTime":233579.0,"Objects":[{"StartTime":233579.0,"Position":102.0,"HyperDash":false},{"StartTime":233669.0,"Position":37.721508,"HyperDash":false},{"StartTime":233760.0,"Position":102.0,"HyperDash":false},{"StartTime":233851.0,"Position":37.721508,"HyperDash":false},{"StartTime":233942.0,"Position":102.0,"HyperDash":false},{"StartTime":234033.0,"Position":37.721508,"HyperDash":false}]},{"StartTime":234124.0,"Objects":[{"StartTime":234124.0,"Position":93.0,"HyperDash":false},{"StartTime":234214.0,"Position":65.15,"HyperDash":false},{"StartTime":234305.0,"Position":93.0,"HyperDash":false}]},{"StartTime":234397.0,"Objects":[{"StartTime":234397.0,"Position":185.0,"HyperDash":false},{"StartTime":234487.0,"Position":191.729935,"HyperDash":false},{"StartTime":234578.0,"Position":185.0,"HyperDash":false}]},{"StartTime":234670.0,"Objects":[{"StartTime":234670.0,"Position":257.0,"HyperDash":false},{"StartTime":234760.0,"Position":229.150009,"HyperDash":false},{"StartTime":234851.0,"Position":257.0,"HyperDash":false}]},{"StartTime":234943.0,"Objects":[{"StartTime":234943.0,"Position":349.0,"HyperDash":false},{"StartTime":235033.0,"Position":355.43277,"HyperDash":false},{"StartTime":235124.0,"Position":349.0,"HyperDash":false}]},{"StartTime":235215.0,"Objects":[{"StartTime":235215.0,"Position":431.0,"HyperDash":false}]},{"StartTime":235306.0,"Objects":[{"StartTime":235306.0,"Position":439.0,"HyperDash":false},{"StartTime":235396.0,"Position":505.57785,"HyperDash":false}]},{"StartTime":235488.0,"Objects":[{"StartTime":235488.0,"Position":502.0,"HyperDash":false}]},{"StartTime":235579.0,"Objects":[{"StartTime":235579.0,"Position":460.0,"HyperDash":false}]},{"StartTime":235670.0,"Objects":[{"StartTime":235670.0,"Position":406.0,"HyperDash":false}]},{"StartTime":235760.0,"Objects":[{"StartTime":235760.0,"Position":358.0,"HyperDash":false},{"StartTime":235819.0,"Position":304.872559,"HyperDash":false},{"StartTime":235878.0,"Position":274.370667,"HyperDash":false},{"StartTime":235937.0,"Position":254.265121,"HyperDash":false},{"StartTime":236032.0,"Position":204.708969,"HyperDash":false}]},{"StartTime":236306.0,"Objects":[{"StartTime":236306.0,"Position":204.0,"HyperDash":false},{"StartTime":236396.0,"Position":271.720734,"HyperDash":false},{"StartTime":236487.0,"Position":204.0,"HyperDash":false}]},{"StartTime":236579.0,"Objects":[{"StartTime":236579.0,"Position":161.0,"HyperDash":false},{"StartTime":236669.0,"Position":228.033157,"HyperDash":false},{"StartTime":236760.0,"Position":161.0,"HyperDash":false}]},{"StartTime":236852.0,"Objects":[{"StartTime":236852.0,"Position":77.0,"HyperDash":false},{"StartTime":236942.0,"Position":9.279259,"HyperDash":false},{"StartTime":237033.0,"Position":77.0,"HyperDash":false}]},{"StartTime":237125.0,"Objects":[{"StartTime":237125.0,"Position":120.0,"HyperDash":false},{"StartTime":237215.0,"Position":52.9668427,"HyperDash":false},{"StartTime":237306.0,"Position":120.0,"HyperDash":false}]},{"StartTime":237397.0,"Objects":[{"StartTime":237397.0,"Position":194.0,"HyperDash":false}]},{"StartTime":237488.0,"Objects":[{"StartTime":237488.0,"Position":203.0,"HyperDash":false},{"StartTime":237578.0,"Position":216.523163,"HyperDash":false}]},{"StartTime":237670.0,"Objects":[{"StartTime":237670.0,"Position":296.0,"HyperDash":false}]},{"StartTime":237760.0,"Objects":[{"StartTime":237760.0,"Position":349.0,"HyperDash":false}]},{"StartTime":237851.0,"Objects":[{"StartTime":237851.0,"Position":391.0,"HyperDash":false}]},{"StartTime":237942.0,"Objects":[{"StartTime":237942.0,"Position":400.0,"HyperDash":false},{"StartTime":238001.0,"Position":373.357147,"HyperDash":false},{"StartTime":238060.0,"Position":359.412537,"HyperDash":false},{"StartTime":238119.0,"Position":345.3219,"HyperDash":false},{"StartTime":238214.0,"Position":385.5706,"HyperDash":false}]},{"StartTime":238488.0,"Objects":[{"StartTime":238488.0,"Position":385.0,"HyperDash":false},{"StartTime":238578.0,"Position":370.624329,"HyperDash":false},{"StartTime":238669.0,"Position":385.0,"HyperDash":false}]},{"StartTime":238761.0,"Objects":[{"StartTime":238761.0,"Position":276.0,"HyperDash":false},{"StartTime":238851.0,"Position":295.94812,"HyperDash":false},{"StartTime":238942.0,"Position":276.0,"HyperDash":false}]},{"StartTime":239033.0,"Objects":[{"StartTime":239033.0,"Position":188.0,"HyperDash":false},{"StartTime":239123.0,"Position":208.0458,"HyperDash":false},{"StartTime":239214.0,"Position":188.0,"HyperDash":false}]},{"StartTime":239306.0,"Objects":[{"StartTime":239306.0,"Position":129.0,"HyperDash":false},{"StartTime":239396.0,"Position":177.393921,"HyperDash":false},{"StartTime":239487.0,"Position":129.0,"HyperDash":false}]},{"StartTime":239579.0,"Objects":[{"StartTime":239579.0,"Position":38.0,"HyperDash":false}]},{"StartTime":239670.0,"Objects":[{"StartTime":239670.0,"Position":32.0,"HyperDash":false},{"StartTime":239760.0,"Position":54.9123878,"HyperDash":false}]},{"StartTime":239851.0,"Objects":[{"StartTime":239851.0,"Position":20.0,"HyperDash":false}]},{"StartTime":239942.0,"Objects":[{"StartTime":239942.0,"Position":57.0,"HyperDash":false}]},{"StartTime":240033.0,"Objects":[{"StartTime":240033.0,"Position":108.0,"HyperDash":false}]},{"StartTime":240124.0,"Objects":[{"StartTime":240124.0,"Position":161.0,"HyperDash":false},{"StartTime":240183.0,"Position":220.613419,"HyperDash":false},{"StartTime":240242.0,"Position":252.59671,"HyperDash":false},{"StartTime":240301.0,"Position":306.131134,"HyperDash":false},{"StartTime":240396.0,"Position":360.49115,"HyperDash":false}]},{"StartTime":240670.0,"Objects":[{"StartTime":240670.0,"Position":360.0,"HyperDash":false},{"StartTime":240760.0,"Position":296.123718,"HyperDash":false},{"StartTime":240851.0,"Position":360.0,"HyperDash":false}]},{"StartTime":240942.0,"Objects":[{"StartTime":240942.0,"Position":460.0,"HyperDash":false},{"StartTime":241032.0,"Position":404.530151,"HyperDash":false},{"StartTime":241123.0,"Position":460.0,"HyperDash":false}]},{"StartTime":241215.0,"Objects":[{"StartTime":241215.0,"Position":448.0,"HyperDash":false},{"StartTime":241305.0,"Position":511.876282,"HyperDash":false},{"StartTime":241396.0,"Position":448.0,"HyperDash":false}]},{"StartTime":241488.0,"Objects":[{"StartTime":241488.0,"Position":430.0,"HyperDash":false},{"StartTime":241578.0,"Position":485.1262,"HyperDash":false},{"StartTime":241669.0,"Position":430.0,"HyperDash":false}]},{"StartTime":241760.0,"Objects":[{"StartTime":241760.0,"Position":365.0,"HyperDash":false}]},{"StartTime":241852.0,"Objects":[{"StartTime":241852.0,"Position":354.0,"HyperDash":false},{"StartTime":241942.0,"Position":330.3751,"HyperDash":false}]},{"StartTime":242033.0,"Objects":[{"StartTime":242033.0,"Position":244.0,"HyperDash":false}]},{"StartTime":242124.0,"Objects":[{"StartTime":242124.0,"Position":191.0,"HyperDash":false}]},{"StartTime":242215.0,"Objects":[{"StartTime":242215.0,"Position":145.0,"HyperDash":false}]},{"StartTime":242306.0,"Objects":[{"StartTime":242306.0,"Position":91.0,"HyperDash":false},{"StartTime":242396.0,"Position":116.832222,"HyperDash":false},{"StartTime":242487.0,"Position":96.35042,"HyperDash":false},{"StartTime":242560.0,"Position":123.330132,"HyperDash":false},{"StartTime":242669.0,"Position":91.0,"HyperDash":false}]},{"StartTime":242852.0,"Objects":[{"StartTime":242852.0,"Position":33.0,"HyperDash":false},{"StartTime":242920.0,"Position":40.2480125,"HyperDash":false},{"StartTime":242988.0,"Position":33.0,"HyperDash":false},{"StartTime":243056.0,"Position":40.2480125,"HyperDash":false}]},{"StartTime":243125.0,"Objects":[{"StartTime":243125.0,"Position":134.0,"HyperDash":false},{"StartTime":243193.0,"Position":126.751991,"HyperDash":false},{"StartTime":243261.0,"Position":134.0,"HyperDash":false},{"StartTime":243329.0,"Position":126.751991,"HyperDash":false}]},{"StartTime":243397.0,"Objects":[{"StartTime":243397.0,"Position":228.0,"HyperDash":false},{"StartTime":243465.0,"Position":269.713348,"HyperDash":false}]},{"StartTime":243534.0,"Objects":[{"StartTime":243534.0,"Position":251.0,"HyperDash":false},{"StartTime":243602.0,"Position":292.713348,"HyperDash":false}]},{"StartTime":243671.0,"Objects":[{"StartTime":243671.0,"Position":276.0,"HyperDash":false},{"StartTime":243739.0,"Position":317.713348,"HyperDash":false},{"StartTime":243807.0,"Position":276.0,"HyperDash":false},{"StartTime":243875.0,"Position":317.713348,"HyperDash":false}]},{"StartTime":243943.0,"Objects":[{"StartTime":243943.0,"Position":388.0,"HyperDash":false},{"StartTime":244011.0,"Position":380.751984,"HyperDash":false},{"StartTime":244079.0,"Position":388.0,"HyperDash":false}]},{"StartTime":244216.0,"Objects":[{"StartTime":244216.0,"Position":409.0,"HyperDash":false}]},{"StartTime":244284.0,"Objects":[{"StartTime":244284.0,"Position":407.0,"HyperDash":false}]},{"StartTime":244352.0,"Objects":[{"StartTime":244352.0,"Position":405.0,"HyperDash":false}]},{"StartTime":244489.0,"Objects":[{"StartTime":244489.0,"Position":495.0,"HyperDash":false},{"StartTime":244557.0,"Position":502.248016,"HyperDash":false},{"StartTime":244625.0,"Position":495.0,"HyperDash":false}]},{"StartTime":244762.0,"Objects":[{"StartTime":244762.0,"Position":426.0,"HyperDash":false}]},{"StartTime":244830.0,"Objects":[{"StartTime":244830.0,"Position":428.0,"HyperDash":false}]},{"StartTime":244898.0,"Objects":[{"StartTime":244898.0,"Position":430.0,"HyperDash":false}]},{"StartTime":245034.0,"Objects":[{"StartTime":245034.0,"Position":370.0,"HyperDash":false},{"StartTime":245102.0,"Position":328.1226,"HyperDash":false},{"StartTime":245170.0,"Position":370.0,"HyperDash":false},{"StartTime":245238.0,"Position":328.1226,"HyperDash":false}]},{"StartTime":245307.0,"Objects":[{"StartTime":245307.0,"Position":331.0,"HyperDash":false},{"StartTime":245375.0,"Position":289.1226,"HyperDash":false},{"StartTime":245443.0,"Position":331.0,"HyperDash":false},{"StartTime":245511.0,"Position":289.1226,"HyperDash":false}]},{"StartTime":245579.0,"Objects":[{"StartTime":245579.0,"Position":229.0,"HyperDash":false},{"StartTime":245647.0,"Position":235.986954,"HyperDash":false}]},{"StartTime":245716.0,"Objects":[{"StartTime":245716.0,"Position":140.0,"HyperDash":false},{"StartTime":245784.0,"Position":146.986954,"HyperDash":false}]},{"StartTime":245853.0,"Objects":[{"StartTime":245853.0,"Position":50.0,"HyperDash":false},{"StartTime":245921.0,"Position":56.9869576,"HyperDash":false},{"StartTime":245989.0,"Position":50.0,"HyperDash":false},{"StartTime":246057.0,"Position":56.9869576,"HyperDash":false}]},{"StartTime":246124.0,"Objects":[{"StartTime":246124.0,"Position":120.0,"HyperDash":false}]},{"StartTime":246193.0,"Objects":[{"StartTime":246193.0,"Position":122.0,"HyperDash":false}]},{"StartTime":246261.0,"Objects":[{"StartTime":246261.0,"Position":124.0,"HyperDash":false}]},{"StartTime":246397.0,"Objects":[{"StartTime":246397.0,"Position":171.0,"HyperDash":false}]},{"StartTime":246465.0,"Objects":[{"StartTime":246465.0,"Position":173.0,"HyperDash":false}]},{"StartTime":246533.0,"Objects":[{"StartTime":246533.0,"Position":175.0,"HyperDash":false}]},{"StartTime":246670.0,"Objects":[{"StartTime":246670.0,"Position":123.0,"HyperDash":false}]},{"StartTime":246738.0,"Objects":[{"StartTime":246738.0,"Position":125.0,"HyperDash":false}]},{"StartTime":246806.0,"Objects":[{"StartTime":246806.0,"Position":127.0,"HyperDash":false},{"StartTime":246942.0,"Position":118.059486,"HyperDash":false}]},{"StartTime":247215.0,"Objects":[{"StartTime":247215.0,"Position":289.0,"HyperDash":false},{"StartTime":247283.0,"Position":330.8774,"HyperDash":false},{"StartTime":247351.0,"Position":289.0,"HyperDash":false},{"StartTime":247419.0,"Position":330.8774,"HyperDash":false}]},{"StartTime":247488.0,"Objects":[{"StartTime":247488.0,"Position":306.0,"HyperDash":false},{"StartTime":247556.0,"Position":347.8774,"HyperDash":false},{"StartTime":247624.0,"Position":306.0,"HyperDash":false},{"StartTime":247692.0,"Position":347.8774,"HyperDash":false}]},{"StartTime":247761.0,"Objects":[{"StartTime":247761.0,"Position":440.0,"HyperDash":false},{"StartTime":247829.0,"Position":447.248016,"HyperDash":false}]},{"StartTime":247897.0,"Objects":[{"StartTime":247897.0,"Position":425.0,"HyperDash":false},{"StartTime":247965.0,"Position":432.248016,"HyperDash":false}]},{"StartTime":248033.0,"Objects":[{"StartTime":248033.0,"Position":410.0,"HyperDash":false},{"StartTime":248101.0,"Position":417.248016,"HyperDash":false},{"StartTime":248169.0,"Position":410.0,"HyperDash":false},{"StartTime":248237.0,"Position":417.248016,"HyperDash":false}]},{"StartTime":248306.0,"Objects":[{"StartTime":248306.0,"Position":346.0,"HyperDash":false},{"StartTime":248374.0,"Position":304.1226,"HyperDash":false},{"StartTime":248442.0,"Position":346.0,"HyperDash":false}]},{"StartTime":248579.0,"Objects":[{"StartTime":248579.0,"Position":287.0,"HyperDash":false}]},{"StartTime":248647.0,"Objects":[{"StartTime":248647.0,"Position":279.0,"HyperDash":false}]},{"StartTime":248715.0,"Objects":[{"StartTime":248715.0,"Position":271.0,"HyperDash":false}]},{"StartTime":248852.0,"Objects":[{"StartTime":248852.0,"Position":193.0,"HyperDash":false},{"StartTime":248920.0,"Position":151.1226,"HyperDash":false},{"StartTime":248988.0,"Position":193.0,"HyperDash":false}]},{"StartTime":249124.0,"Objects":[{"StartTime":249124.0,"Position":139.0,"HyperDash":false}]},{"StartTime":249194.0,"Objects":[{"StartTime":249194.0,"Position":131.0,"HyperDash":false}]},{"StartTime":249261.0,"Objects":[{"StartTime":249261.0,"Position":123.0,"HyperDash":false}]},{"StartTime":249397.0,"Objects":[{"StartTime":249397.0,"Position":53.0,"HyperDash":false},{"StartTime":249465.0,"Position":60.2480125,"HyperDash":false},{"StartTime":249533.0,"Position":53.0,"HyperDash":false},{"StartTime":249601.0,"Position":60.2480125,"HyperDash":false}]},{"StartTime":249670.0,"Objects":[{"StartTime":249670.0,"Position":0.0,"HyperDash":false},{"StartTime":249738.0,"Position":7.952265,"HyperDash":false},{"StartTime":249806.0,"Position":0.0,"HyperDash":false},{"StartTime":249874.0,"Position":7.952265,"HyperDash":false}]},{"StartTime":249943.0,"Objects":[{"StartTime":249943.0,"Position":41.0,"HyperDash":false},{"StartTime":250011.0,"Position":0.0,"HyperDash":false}]},{"StartTime":250079.0,"Objects":[{"StartTime":250079.0,"Position":127.0,"HyperDash":false},{"StartTime":250147.0,"Position":85.1226044,"HyperDash":false}]},{"StartTime":250215.0,"Objects":[{"StartTime":250215.0,"Position":212.0,"HyperDash":false},{"StartTime":250283.0,"Position":170.1226,"HyperDash":false},{"StartTime":250351.0,"Position":212.0,"HyperDash":false},{"StartTime":250419.0,"Position":170.1226,"HyperDash":false}]},{"StartTime":250488.0,"Objects":[{"StartTime":250488.0,"Position":210.0,"HyperDash":false}]},{"StartTime":250556.0,"Objects":[{"StartTime":250556.0,"Position":212.0,"HyperDash":false}]},{"StartTime":250624.0,"Objects":[{"StartTime":250624.0,"Position":214.0,"HyperDash":false}]},{"StartTime":250761.0,"Objects":[{"StartTime":250761.0,"Position":295.0,"HyperDash":false}]},{"StartTime":250829.0,"Objects":[{"StartTime":250829.0,"Position":293.0,"HyperDash":false}]},{"StartTime":250898.0,"Objects":[{"StartTime":250898.0,"Position":291.0,"HyperDash":false}]},{"StartTime":251033.0,"Objects":[{"StartTime":251033.0,"Position":235.0,"HyperDash":false}]},{"StartTime":251102.0,"Objects":[{"StartTime":251102.0,"Position":237.0,"HyperDash":false}]},{"StartTime":251170.0,"Objects":[{"StartTime":251170.0,"Position":239.0,"HyperDash":false},{"StartTime":251238.0,"Position":231.8979,"HyperDash":false},{"StartTime":251306.0,"Position":239.0,"HyperDash":false},{"StartTime":251374.0,"Position":231.8979,"HyperDash":false},{"StartTime":251442.0,"Position":239.0,"HyperDash":false},{"StartTime":251510.0,"Position":231.8979,"HyperDash":false}]},{"StartTime":251579.0,"Objects":[{"StartTime":251579.0,"Position":229.0,"HyperDash":false},{"StartTime":251715.0,"Position":317.623718,"HyperDash":false}]},{"StartTime":251852.0,"Objects":[{"StartTime":251852.0,"Position":475.0,"HyperDash":false},{"StartTime":251988.0,"Position":386.889038,"HyperDash":false}]},{"StartTime":252124.0,"Objects":[{"StartTime":252124.0,"Position":440.0,"HyperDash":false},{"StartTime":252260.0,"Position":463.840118,"HyperDash":false}]},{"StartTime":252397.0,"Objects":[{"StartTime":252397.0,"Position":297.0,"HyperDash":false},{"StartTime":252533.0,"Position":319.863068,"HyperDash":false}]},{"StartTime":252670.0,"Objects":[{"StartTime":252670.0,"Position":205.0,"HyperDash":false},{"StartTime":252806.0,"Position":105.595367,"HyperDash":false}]},{"StartTime":252942.0,"Objects":[{"StartTime":252942.0,"Position":42.0,"HyperDash":false}]},{"StartTime":253079.0,"Objects":[{"StartTime":253079.0,"Position":42.0,"HyperDash":false}]},{"StartTime":253215.0,"Objects":[{"StartTime":253215.0,"Position":1.0,"HyperDash":false},{"StartTime":253351.0,"Position":97.26073,"HyperDash":false}]},{"StartTime":253488.0,"Objects":[{"StartTime":253488.0,"Position":248.0,"HyperDash":false},{"StartTime":253624.0,"Position":148.595367,"HyperDash":true}]},{"StartTime":253760.0,"Objects":[{"StartTime":253760.0,"Position":408.0,"HyperDash":false},{"StartTime":253896.0,"Position":487.4551,"HyperDash":false}]},{"StartTime":254033.0,"Objects":[{"StartTime":254033.0,"Position":318.0,"HyperDash":false},{"StartTime":254169.0,"Position":309.7604,"HyperDash":false}]},{"StartTime":254306.0,"Objects":[{"StartTime":254306.0,"Position":202.0,"HyperDash":false}]},{"StartTime":254442.0,"Objects":[{"StartTime":254442.0,"Position":295.0,"HyperDash":false}]},{"StartTime":254510.0,"Objects":[{"StartTime":254510.0,"Position":295.0,"HyperDash":false}]},{"StartTime":254579.0,"Objects":[{"StartTime":254579.0,"Position":295.0,"HyperDash":false},{"StartTime":254715.0,"Position":395.898743,"HyperDash":false}]},{"StartTime":254851.0,"Objects":[{"StartTime":254851.0,"Position":486.0,"HyperDash":false}]},{"StartTime":254987.0,"Objects":[{"StartTime":254987.0,"Position":423.0,"HyperDash":false}]},{"StartTime":255124.0,"Objects":[{"StartTime":255124.0,"Position":424.0,"HyperDash":false}]},{"StartTime":255260.0,"Objects":[{"StartTime":255260.0,"Position":487.0,"HyperDash":false}]},{"StartTime":255397.0,"Objects":[{"StartTime":255397.0,"Position":412.0,"HyperDash":false},{"StartTime":255456.0,"Position":367.364532,"HyperDash":false},{"StartTime":255515.0,"Position":308.7291,"HyperDash":false},{"StartTime":255574.0,"Position":291.225,"HyperDash":false},{"StartTime":255669.0,"Position":215.507538,"HyperDash":false}]},{"StartTime":255806.0,"Objects":[{"StartTime":255806.0,"Position":80.0,"HyperDash":false}]},{"StartTime":255874.0,"Objects":[{"StartTime":255874.0,"Position":87.0,"HyperDash":false}]},{"StartTime":255942.0,"Objects":[{"StartTime":255942.0,"Position":94.0,"HyperDash":false},{"StartTime":256078.0,"Position":115.948105,"HyperDash":false}]},{"StartTime":256215.0,"Objects":[{"StartTime":256215.0,"Position":14.0,"HyperDash":false},{"StartTime":256351.0,"Position":35.94811,"HyperDash":false}]},{"StartTime":256488.0,"Objects":[{"StartTime":256488.0,"Position":172.0,"HyperDash":false},{"StartTime":256624.0,"Position":263.280975,"HyperDash":false}]},{"StartTime":256760.0,"Objects":[{"StartTime":256760.0,"Position":238.0,"HyperDash":false},{"StartTime":256896.0,"Position":146.7498,"HyperDash":false}]},{"StartTime":257033.0,"Objects":[{"StartTime":257033.0,"Position":115.0,"HyperDash":false},{"StartTime":257169.0,"Position":205.031708,"HyperDash":false}]},{"StartTime":257306.0,"Objects":[{"StartTime":257306.0,"Position":342.0,"HyperDash":false}]},{"StartTime":257442.0,"Objects":[{"StartTime":257442.0,"Position":342.0,"HyperDash":false}]},{"StartTime":257579.0,"Objects":[{"StartTime":257579.0,"Position":455.0,"HyperDash":false},{"StartTime":257715.0,"Position":467.65155,"HyperDash":false}]},{"StartTime":257851.0,"Objects":[{"StartTime":257851.0,"Position":381.0,"HyperDash":false},{"StartTime":257987.0,"Position":393.65155,"HyperDash":false}]},{"StartTime":258124.0,"Objects":[{"StartTime":258124.0,"Position":267.0,"HyperDash":false},{"StartTime":258260.0,"Position":183.076477,"HyperDash":false}]},{"StartTime":258397.0,"Objects":[{"StartTime":258397.0,"Position":95.0,"HyperDash":false},{"StartTime":258533.0,"Position":11.07647,"HyperDash":false}]},{"StartTime":258670.0,"Objects":[{"StartTime":258670.0,"Position":101.0,"HyperDash":false}]},{"StartTime":258806.0,"Objects":[{"StartTime":258806.0,"Position":22.0,"HyperDash":false}]},{"StartTime":258874.0,"Objects":[{"StartTime":258874.0,"Position":22.0,"HyperDash":false}]},{"StartTime":258942.0,"Objects":[{"StartTime":258942.0,"Position":22.0,"HyperDash":false},{"StartTime":259078.0,"Position":5.65008163,"HyperDash":false}]},{"StartTime":259215.0,"Objects":[{"StartTime":259215.0,"Position":158.0,"HyperDash":false}]},{"StartTime":259283.0,"Objects":[{"StartTime":259283.0,"Position":197.0,"HyperDash":false}]},{"StartTime":259351.0,"Objects":[{"StartTime":259351.0,"Position":239.0,"HyperDash":false}]},{"StartTime":259419.0,"Objects":[{"StartTime":259419.0,"Position":273.0,"HyperDash":false}]},{"StartTime":259487.0,"Objects":[{"StartTime":259487.0,"Position":291.0,"HyperDash":false}]},{"StartTime":259624.0,"Objects":[{"StartTime":259624.0,"Position":405.0,"HyperDash":false}]},{"StartTime":259692.0,"Objects":[{"StartTime":259692.0,"Position":415.0,"HyperDash":false}]},{"StartTime":259761.0,"Objects":[{"StartTime":259761.0,"Position":425.0,"HyperDash":false},{"StartTime":259829.0,"Position":436.342346,"HyperDash":false},{"StartTime":259897.0,"Position":425.0,"HyperDash":false}]},{"StartTime":260033.0,"Objects":[{"StartTime":260033.0,"Position":355.0,"HyperDash":false},{"StartTime":260101.0,"Position":366.342346,"HyperDash":false},{"StartTime":260169.0,"Position":355.0,"HyperDash":false},{"StartTime":260237.0,"Position":366.342346,"HyperDash":false}]},{"StartTime":260306.0,"Objects":[{"StartTime":260306.0,"Position":376.0,"HyperDash":false},{"StartTime":260442.0,"Position":287.376282,"HyperDash":false}]},{"StartTime":260578.0,"Objects":[{"StartTime":260578.0,"Position":112.0,"HyperDash":false},{"StartTime":260714.0,"Position":200.110962,"HyperDash":false}]},{"StartTime":260851.0,"Objects":[{"StartTime":260851.0,"Position":240.0,"HyperDash":false},{"StartTime":260987.0,"Position":140.825165,"HyperDash":false}]},{"StartTime":261124.0,"Objects":[{"StartTime":261124.0,"Position":1.0,"HyperDash":false},{"StartTime":261260.0,"Position":100.404633,"HyperDash":false}]},{"StartTime":261397.0,"Objects":[{"StartTime":261397.0,"Position":296.0,"HyperDash":false},{"StartTime":261533.0,"Position":196.595367,"HyperDash":false}]},{"StartTime":261669.0,"Objects":[{"StartTime":261669.0,"Position":324.0,"HyperDash":false}]},{"StartTime":261806.0,"Objects":[{"StartTime":261806.0,"Position":324.0,"HyperDash":false}]},{"StartTime":261942.0,"Objects":[{"StartTime":261942.0,"Position":445.0,"HyperDash":false},{"StartTime":262078.0,"Position":460.350983,"HyperDash":false}]},{"StartTime":262215.0,"Objects":[{"StartTime":262215.0,"Position":360.0,"HyperDash":false},{"StartTime":262351.0,"Position":456.028931,"HyperDash":false}]},{"StartTime":262487.0,"Objects":[{"StartTime":262487.0,"Position":274.0,"HyperDash":false},{"StartTime":262623.0,"Position":194.151871,"HyperDash":false}]},{"StartTime":262760.0,"Objects":[{"StartTime":262760.0,"Position":38.0,"HyperDash":false},{"StartTime":262896.0,"Position":125.37175,"HyperDash":false}]},{"StartTime":263033.0,"Objects":[{"StartTime":263033.0,"Position":194.0,"HyperDash":false}]},{"StartTime":263169.0,"Objects":[{"StartTime":263169.0,"Position":312.0,"HyperDash":false}]},{"StartTime":263237.0,"Objects":[{"StartTime":263237.0,"Position":312.0,"HyperDash":false}]},{"StartTime":263306.0,"Objects":[{"StartTime":263306.0,"Position":312.0,"HyperDash":false},{"StartTime":263442.0,"Position":412.898743,"HyperDash":false}]},{"StartTime":263578.0,"Objects":[{"StartTime":263578.0,"Position":503.0,"HyperDash":false}]},{"StartTime":263714.0,"Objects":[{"StartTime":263714.0,"Position":456.0,"HyperDash":false}]},{"StartTime":263851.0,"Objects":[{"StartTime":263851.0,"Position":367.0,"HyperDash":false}]},{"StartTime":263987.0,"Objects":[{"StartTime":263987.0,"Position":292.0,"HyperDash":false}]},{"StartTime":264124.0,"Objects":[{"StartTime":264124.0,"Position":206.0,"HyperDash":false},{"StartTime":264183.0,"Position":158.319275,"HyperDash":false},{"StartTime":264242.0,"Position":120.702431,"HyperDash":false},{"StartTime":264301.0,"Position":92.96848,"HyperDash":false},{"StartTime":264396.0,"Position":18.7677212,"HyperDash":false}]},{"StartTime":264533.0,"Objects":[{"StartTime":264533.0,"Position":173.0,"HyperDash":false}]},{"StartTime":264601.0,"Objects":[{"StartTime":264601.0,"Position":166.0,"HyperDash":false}]},{"StartTime":264669.0,"Objects":[{"StartTime":264669.0,"Position":159.0,"HyperDash":false},{"StartTime":264805.0,"Position":137.0519,"HyperDash":false}]},{"StartTime":264942.0,"Objects":[{"StartTime":264942.0,"Position":302.0,"HyperDash":false},{"StartTime":265078.0,"Position":280.834564,"HyperDash":false}]},{"StartTime":265215.0,"Objects":[{"StartTime":265215.0,"Position":399.0,"HyperDash":false},{"StartTime":265351.0,"Position":434.304535,"HyperDash":false}]},{"StartTime":265487.0,"Objects":[{"StartTime":265487.0,"Position":496.0,"HyperDash":false},{"StartTime":265623.0,"Position":404.622,"HyperDash":false}]},{"StartTime":265760.0,"Objects":[{"StartTime":265760.0,"Position":362.0,"HyperDash":false},{"StartTime":265896.0,"Position":452.0317,"HyperDash":false}]},{"StartTime":266033.0,"Objects":[{"StartTime":266033.0,"Position":288.0,"HyperDash":false}]},{"StartTime":266169.0,"Objects":[{"StartTime":266169.0,"Position":288.0,"HyperDash":false}]},{"StartTime":266306.0,"Objects":[{"StartTime":266306.0,"Position":171.0,"HyperDash":false},{"StartTime":266442.0,"Position":158.34845,"HyperDash":false}]},{"StartTime":266578.0,"Objects":[{"StartTime":266578.0,"Position":251.0,"HyperDash":false},{"StartTime":266714.0,"Position":238.34845,"HyperDash":false}]},{"StartTime":266851.0,"Objects":[{"StartTime":266851.0,"Position":56.0,"HyperDash":false},{"StartTime":266987.0,"Position":104.910339,"HyperDash":false}]},{"StartTime":267124.0,"Objects":[{"StartTime":267124.0,"Position":35.0,"HyperDash":false},{"StartTime":267260.0,"Position":33.814888,"HyperDash":false}]},{"StartTime":267397.0,"Objects":[{"StartTime":267397.0,"Position":123.0,"HyperDash":false}]},{"StartTime":267533.0,"Objects":[{"StartTime":267533.0,"Position":253.0,"HyperDash":false}]},{"StartTime":267601.0,"Objects":[{"StartTime":267601.0,"Position":253.0,"HyperDash":false}]},{"StartTime":267669.0,"Objects":[{"StartTime":267669.0,"Position":253.0,"HyperDash":false},{"StartTime":267805.0,"Position":353.6811,"HyperDash":false}]},{"StartTime":267942.0,"Objects":[{"StartTime":267942.0,"Position":463.0,"HyperDash":false}]},{"StartTime":268010.0,"Objects":[{"StartTime":268010.0,"Position":489.0,"HyperDash":false}]},{"StartTime":268078.0,"Objects":[{"StartTime":268078.0,"Position":498.0,"HyperDash":false}]},{"StartTime":268146.0,"Objects":[{"StartTime":268146.0,"Position":485.0,"HyperDash":false}]},{"StartTime":268214.0,"Objects":[{"StartTime":268214.0,"Position":455.0,"HyperDash":false}]},{"StartTime":268352.0,"Objects":[{"StartTime":268352.0,"Position":419.0,"HyperDash":false}]},{"StartTime":268420.0,"Objects":[{"StartTime":268420.0,"Position":403.0,"HyperDash":false}]},{"StartTime":268488.0,"Objects":[{"StartTime":268488.0,"Position":372.0,"HyperDash":false}]},{"StartTime":268556.0,"Objects":[{"StartTime":268556.0,"Position":332.0,"HyperDash":false}]},{"StartTime":268624.0,"Objects":[{"StartTime":268624.0,"Position":292.0,"HyperDash":false}]},{"StartTime":268761.0,"Objects":[{"StartTime":268761.0,"Position":231.0,"HyperDash":false},{"StartTime":268829.0,"Position":180.408112,"HyperDash":false},{"StartTime":268897.0,"Position":231.0,"HyperDash":false},{"StartTime":268965.0,"Position":180.408112,"HyperDash":false}]},{"StartTime":269033.0,"Objects":[{"StartTime":269033.0,"Position":96.0,"HyperDash":false},{"StartTime":269099.0,"Position":107.997719,"HyperDash":false},{"StartTime":269166.0,"Position":130.581879,"HyperDash":false},{"StartTime":269232.0,"Position":149.897186,"HyperDash":false},{"StartTime":269299.0,"Position":175.084061,"HyperDash":false},{"StartTime":269365.0,"Position":167.6238,"HyperDash":false},{"StartTime":269432.0,"Position":173.461578,"HyperDash":false},{"StartTime":269498.0,"Position":185.410263,"HyperDash":false},{"StartTime":269565.0,"Position":178.44928,"HyperDash":false},{"StartTime":269655.0,"Position":167.081726,"HyperDash":false},{"StartTime":269746.0,"Position":170.346115,"HyperDash":false},{"StartTime":269837.0,"Position":137.438,"HyperDash":false},{"StartTime":269964.0,"Position":125.546143,"HyperDash":false}]},{"StartTime":270097.0,"Objects":[{"StartTime":270097.0,"Position":121.0,"HyperDash":false},{"StartTime":270163.0,"Position":78.13265,"HyperDash":false},{"StartTime":270230.0,"Position":95.43977,"HyperDash":false},{"StartTime":270296.0,"Position":65.59505,"HyperDash":false},{"StartTime":270363.0,"Position":71.33265,"HyperDash":false},{"StartTime":270429.0,"Position":73.41984,"HyperDash":false},{"StartTime":270496.0,"Position":98.806366,"HyperDash":false},{"StartTime":270562.0,"Position":139.458054,"HyperDash":false},{"StartTime":270629.0,"Position":162.000076,"HyperDash":false},{"StartTime":270686.0,"Position":174.872726,"HyperDash":false},{"StartTime":270744.0,"Position":199.77951,"HyperDash":false},{"StartTime":270801.0,"Position":218.731812,"HyperDash":false},{"StartTime":270895.0,"Position":252.733368,"HyperDash":false}]},{"StartTime":271028.0,"Objects":[{"StartTime":271028.0,"Position":319.0,"HyperDash":false}]},{"StartTime":271161.0,"Objects":[{"StartTime":271161.0,"Position":312.0,"HyperDash":false},{"StartTime":271223.0,"Position":302.2162,"HyperDash":false},{"StartTime":271285.0,"Position":302.676941,"HyperDash":false},{"StartTime":271347.0,"Position":283.679169,"HyperDash":false},{"StartTime":271409.0,"Position":290.484436,"HyperDash":false},{"StartTime":271471.0,"Position":288.101379,"HyperDash":false},{"StartTime":271533.0,"Position":295.433258,"HyperDash":false},{"StartTime":271595.0,"Position":306.336884,"HyperDash":false},{"StartTime":271693.0,"Position":324.652863,"HyperDash":false}]},{"StartTime":271959.0,"Objects":[{"StartTime":271959.0,"Position":400.0,"HyperDash":false}]},{"StartTime":272225.0,"Objects":[{"StartTime":272225.0,"Position":400.0,"HyperDash":false},{"StartTime":272313.0,"Position":405.1424,"HyperDash":false},{"StartTime":272402.0,"Position":408.331879,"HyperDash":false},{"StartTime":272472.0,"Position":402.036774,"HyperDash":false},{"StartTime":272579.0,"Position":400.0,"HyperDash":false}]},{"StartTime":272758.0,"Objects":[{"StartTime":272758.0,"Position":442.0,"HyperDash":false},{"StartTime":272846.0,"Position":459.1424,"HyperDash":false},{"StartTime":272935.0,"Position":450.331879,"HyperDash":false},{"StartTime":273005.0,"Position":454.036774,"HyperDash":false},{"StartTime":273112.0,"Position":442.0,"HyperDash":false}]},{"StartTime":273290.0,"Objects":[{"StartTime":273290.0,"Position":512.0,"HyperDash":false},{"StartTime":273355.0,"Position":498.977875,"HyperDash":false},{"StartTime":273420.0,"Position":478.2446,"HyperDash":false},{"StartTime":273485.0,"Position":437.965363,"HyperDash":false},{"StartTime":273551.0,"Position":433.034943,"HyperDash":false},{"StartTime":273616.0,"Position":428.07312,"HyperDash":false},{"StartTime":273681.0,"Position":423.756653,"HyperDash":false},{"StartTime":273746.0,"Position":401.5979,"HyperDash":false},{"StartTime":273848.0,"Position":401.115448,"HyperDash":false}]},{"StartTime":274048.0,"Objects":[{"StartTime":274048.0,"Position":303.0,"HyperDash":false},{"StartTime":274129.0,"Position":308.57135,"HyperDash":false},{"StartTime":274247.0,"Position":297.033356,"HyperDash":false}]},{"StartTime":274498.0,"Objects":[{"StartTime":274498.0,"Position":202.0,"HyperDash":false},{"StartTime":274560.0,"Position":191.209839,"HyperDash":false},{"StartTime":274622.0,"Position":190.373,"HyperDash":false},{"StartTime":274747.0,"Position":202.0,"HyperDash":false}]},{"StartTime":274873.0,"Objects":[{"StartTime":274873.0,"Position":105.0,"HyperDash":false},{"StartTime":274939.0,"Position":120.3023,"HyperDash":false},{"StartTime":275006.0,"Position":107.624329,"HyperDash":false},{"StartTime":275139.0,"Position":105.0,"HyperDash":false}]},{"StartTime":275273.0,"Objects":[{"StartTime":275273.0,"Position":31.0,"HyperDash":false},{"StartTime":275349.0,"Position":42.15374,"HyperDash":false},{"StartTime":275426.0,"Position":47.4684143,"HyperDash":false},{"StartTime":275485.0,"Position":28.1921768,"HyperDash":false},{"StartTime":275580.0,"Position":31.0,"HyperDash":false}]},{"StartTime":275734.0,"Objects":[{"StartTime":275734.0,"Position":0.0,"HyperDash":false},{"StartTime":275813.0,"Position":0.0,"HyperDash":false},{"StartTime":275893.0,"Position":25.7255154,"HyperDash":false},{"StartTime":275955.0,"Position":16.8062725,"HyperDash":false},{"StartTime":276053.0,"Position":0.0,"HyperDash":false}]},{"StartTime":276254.0,"Objects":[{"StartTime":276254.0,"Position":21.0,"HyperDash":false}]},{"StartTime":276419.0,"Objects":[{"StartTime":276419.0,"Position":354.0,"HyperDash":false},{"StartTime":276494.0,"Position":270.0,"HyperDash":false},{"StartTime":276569.0,"Position":362.0,"HyperDash":false},{"StartTime":276645.0,"Position":255.0,"HyperDash":false},{"StartTime":276720.0,"Position":203.0,"HyperDash":false},{"StartTime":276795.0,"Position":67.0,"HyperDash":false},{"StartTime":276871.0,"Position":112.0,"HyperDash":false},{"StartTime":276946.0,"Position":326.0,"HyperDash":false},{"StartTime":277021.0,"Position":219.0,"HyperDash":false},{"StartTime":277097.0,"Position":351.0,"HyperDash":false},{"StartTime":277172.0,"Position":477.0,"HyperDash":false},{"StartTime":277247.0,"Position":439.0,"HyperDash":false},{"StartTime":277323.0,"Position":471.0,"HyperDash":false},{"StartTime":277398.0,"Position":449.0,"HyperDash":false},{"StartTime":277473.0,"Position":295.0,"HyperDash":false},{"StartTime":277549.0,"Position":217.0,"HyperDash":false},{"StartTime":277624.0,"Position":308.0,"HyperDash":false},{"StartTime":277699.0,"Position":430.0,"HyperDash":false},{"StartTime":277775.0,"Position":73.0,"HyperDash":false},{"StartTime":277850.0,"Position":53.0,"HyperDash":false},{"StartTime":277925.0,"Position":276.0,"HyperDash":false},{"StartTime":278001.0,"Position":289.0,"HyperDash":false},{"StartTime":278076.0,"Position":104.0,"HyperDash":false},{"StartTime":278151.0,"Position":212.0,"HyperDash":false},{"StartTime":278227.0,"Position":359.0,"HyperDash":false},{"StartTime":278302.0,"Position":500.0,"HyperDash":false},{"StartTime":278377.0,"Position":467.0,"HyperDash":false},{"StartTime":278453.0,"Position":303.0,"HyperDash":false},{"StartTime":278528.0,"Position":29.0,"HyperDash":false},{"StartTime":278603.0,"Position":482.0,"HyperDash":false},{"StartTime":278679.0,"Position":379.0,"HyperDash":false},{"StartTime":278754.0,"Position":93.0,"HyperDash":false},{"StartTime":278830.0,"Position":266.0,"HyperDash":false},{"StartTime":278905.0,"Position":342.0,"HyperDash":false},{"StartTime":278980.0,"Position":423.0,"HyperDash":false},{"StartTime":279056.0,"Position":190.0,"HyperDash":false},{"StartTime":279131.0,"Position":266.0,"HyperDash":false},{"StartTime":279206.0,"Position":56.0,"HyperDash":false},{"StartTime":279282.0,"Position":164.0,"HyperDash":false},{"StartTime":279357.0,"Position":44.0,"HyperDash":false},{"StartTime":279432.0,"Position":68.0,"HyperDash":false},{"StartTime":279508.0,"Position":476.0,"HyperDash":false},{"StartTime":279583.0,"Position":237.0,"HyperDash":false},{"StartTime":279658.0,"Position":146.0,"HyperDash":false},{"StartTime":279734.0,"Position":99.0,"HyperDash":false},{"StartTime":279809.0,"Position":52.0,"HyperDash":false},{"StartTime":279884.0,"Position":294.0,"HyperDash":false},{"StartTime":279960.0,"Position":346.0,"HyperDash":false},{"StartTime":280035.0,"Position":256.0,"HyperDash":false},{"StartTime":280110.0,"Position":353.0,"HyperDash":false},{"StartTime":280186.0,"Position":85.0,"HyperDash":false},{"StartTime":280261.0,"Position":473.0,"HyperDash":false},{"StartTime":280336.0,"Position":55.0,"HyperDash":false},{"StartTime":280412.0,"Position":158.0,"HyperDash":false},{"StartTime":280487.0,"Position":97.0,"HyperDash":false},{"StartTime":280562.0,"Position":288.0,"HyperDash":false},{"StartTime":280638.0,"Position":236.0,"HyperDash":false},{"StartTime":280713.0,"Position":226.0,"HyperDash":false},{"StartTime":280788.0,"Position":317.0,"HyperDash":false},{"StartTime":280864.0,"Position":227.0,"HyperDash":false},{"StartTime":280939.0,"Position":507.0,"HyperDash":false},{"StartTime":281014.0,"Position":144.0,"HyperDash":false},{"StartTime":281090.0,"Position":409.0,"HyperDash":false},{"StartTime":281165.0,"Position":76.0,"HyperDash":false},{"StartTime":281241.0,"Position":193.0,"HyperDash":false},{"StartTime":281316.0,"Position":456.0,"HyperDash":false},{"StartTime":281391.0,"Position":161.0,"HyperDash":false},{"StartTime":281467.0,"Position":417.0,"HyperDash":false},{"StartTime":281542.0,"Position":157.0,"HyperDash":false},{"StartTime":281617.0,"Position":464.0,"HyperDash":false},{"StartTime":281693.0,"Position":462.0,"HyperDash":false},{"StartTime":281768.0,"Position":254.0,"HyperDash":false},{"StartTime":281843.0,"Position":103.0,"HyperDash":false},{"StartTime":281919.0,"Position":125.0,"HyperDash":false},{"StartTime":281994.0,"Position":485.0,"HyperDash":false},{"StartTime":282069.0,"Position":350.0,"HyperDash":false},{"StartTime":282145.0,"Position":206.0,"HyperDash":false},{"StartTime":282220.0,"Position":285.0,"HyperDash":false},{"StartTime":282295.0,"Position":390.0,"HyperDash":false},{"StartTime":282371.0,"Position":463.0,"HyperDash":false},{"StartTime":282446.0,"Position":447.0,"HyperDash":false},{"StartTime":282521.0,"Position":126.0,"HyperDash":false},{"StartTime":282597.0,"Position":44.0,"HyperDash":false},{"StartTime":282672.0,"Position":451.0,"HyperDash":false},{"StartTime":282747.0,"Position":278.0,"HyperDash":false},{"StartTime":282823.0,"Position":24.0,"HyperDash":false},{"StartTime":282898.0,"Position":367.0,"HyperDash":false},{"StartTime":282973.0,"Position":221.0,"HyperDash":false},{"StartTime":283049.0,"Position":439.0,"HyperDash":false},{"StartTime":283124.0,"Position":243.0,"HyperDash":false},{"StartTime":283199.0,"Position":213.0,"HyperDash":false},{"StartTime":283275.0,"Position":120.0,"HyperDash":false},{"StartTime":283350.0,"Position":379.0,"HyperDash":false},{"StartTime":283425.0,"Position":353.0,"HyperDash":false},{"StartTime":283501.0,"Position":496.0,"HyperDash":false},{"StartTime":283576.0,"Position":288.0,"HyperDash":false},{"StartTime":283652.0,"Position":163.0,"HyperDash":false},{"StartTime":283727.0,"Position":314.0,"HyperDash":false},{"StartTime":283802.0,"Position":296.0,"HyperDash":false},{"StartTime":283878.0,"Position":488.0,"HyperDash":false},{"StartTime":283953.0,"Position":482.0,"HyperDash":false},{"StartTime":284028.0,"Position":321.0,"HyperDash":false},{"StartTime":284104.0,"Position":474.0,"HyperDash":false},{"StartTime":284179.0,"Position":252.0,"HyperDash":false},{"StartTime":284254.0,"Position":247.0,"HyperDash":false},{"StartTime":284330.0,"Position":406.0,"HyperDash":false},{"StartTime":284405.0,"Position":319.0,"HyperDash":false},{"StartTime":284480.0,"Position":253.0,"HyperDash":false},{"StartTime":284556.0,"Position":411.0,"HyperDash":false},{"StartTime":284631.0,"Position":205.0,"HyperDash":false},{"StartTime":284706.0,"Position":54.0,"HyperDash":false},{"StartTime":284782.0,"Position":224.0,"HyperDash":false},{"StartTime":284857.0,"Position":465.0,"HyperDash":false},{"StartTime":284932.0,"Position":432.0,"HyperDash":false},{"StartTime":285008.0,"Position":108.0,"HyperDash":false},{"StartTime":285083.0,"Position":95.0,"HyperDash":false},{"StartTime":285158.0,"Position":436.0,"HyperDash":false},{"StartTime":285234.0,"Position":61.0,"HyperDash":false},{"StartTime":285309.0,"Position":234.0,"HyperDash":false},{"StartTime":285384.0,"Position":394.0,"HyperDash":false},{"StartTime":285460.0,"Position":86.0,"HyperDash":false},{"StartTime":285535.0,"Position":491.0,"HyperDash":false},{"StartTime":285610.0,"Position":416.0,"HyperDash":false},{"StartTime":285686.0,"Position":44.0,"HyperDash":false},{"StartTime":285761.0,"Position":29.0,"HyperDash":false},{"StartTime":285836.0,"Position":402.0,"HyperDash":false},{"StartTime":285912.0,"Position":115.0,"HyperDash":false},{"StartTime":285987.0,"Position":87.0,"HyperDash":false}]},{"StartTime":286725.0,"Objects":[{"StartTime":286725.0,"Position":80.0,"HyperDash":false},{"StartTime":286776.0,"Position":116.003235,"HyperDash":false},{"StartTime":286827.0,"Position":150.517319,"HyperDash":false},{"StartTime":286878.0,"Position":201.896988,"HyperDash":false},{"StartTime":286930.0,"Position":241.944443,"HyperDash":false},{"StartTime":286981.0,"Position":259.183777,"HyperDash":false},{"StartTime":287032.0,"Position":320.093781,"HyperDash":false},{"StartTime":287084.0,"Position":319.821442,"HyperDash":false},{"StartTime":287135.0,"Position":270.5175,"HyperDash":false},{"StartTime":287186.0,"Position":225.266876,"HyperDash":false},{"StartTime":287238.0,"Position":212.995529,"HyperDash":false},{"StartTime":287289.0,"Position":225.29332,"HyperDash":false},{"StartTime":287340.0,"Position":285.537354,"HyperDash":false},{"StartTime":287392.0,"Position":301.644073,"HyperDash":false},{"StartTime":287443.0,"Position":366.0163,"HyperDash":false},{"StartTime":287494.0,"Position":394.099243,"HyperDash":false},{"StartTime":287582.0,"Position":465.1608,"HyperDash":false}]}]} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3644427.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3644427.osu new file mode 100644 index 0000000000..522f8d5a4a --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3644427.osu @@ -0,0 +1,1450 @@ +osu file format v14 + +[General] +StackLeniency: 0.8 +Mode: 0 + +[Difficulty] +HPDrainRate:5 +CircleSize:3 +OverallDifficulty:8 +ApproachRate:9.2 +SliderMultiplier:1.7 +SliderTickRate:1 + +[Events] +//Background and Video events +//Break Periods +2,96220,104148 +2,113675,117239 +2,205476,207343 +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Layer 4 (Overlay) +//Storyboard Sound Samples + +[TimingPoints] +22,272.727272727273,5,2,1,50,1,0 +22,-125,4,2,1,50,0,0 +840,-83.3333333333333,4,2,1,50,0,0 +1385,-125,4,2,1,50,0,0 +2203,-83.3333333333333,4,2,1,50,0,0 +2749,-125,4,2,1,50,0,0 +3567,-83.3333333333333,4,2,1,50,0,0 +4112,-125,4,2,1,50,0,0 +4931,-83.3333333333333,4,2,1,50,0,0 +5476,-125,4,2,1,50,0,0 +6294,-83.3333333333333,4,2,1,50,0,0 +6840,-125,4,2,1,50,0,0 +7658,-83.3333333333333,4,2,1,50,0,0 +8203,-125,4,2,1,50,0,0 +9022,-83.3333333333333,4,2,1,50,0,0 +9567,-125,4,2,1,50,0,0 +10385,-83.3333333333333,4,2,1,50,0,0 +10931,-125,4,2,1,50,0,0 +12021,272.727272727273,4,2,1,70,1,0 +12021,-83.3333333333333,4,2,1,70,0,0 +29475,-100,4,2,1,70,0,0 +38202,-125,4,2,1,50,0,0 +40384,-100,4,2,1,70,0,0 +42566,-125,4,2,1,50,0,0 +44748,-100,4,2,1,70,0,0 +46930,-83.3333333333333,4,2,1,80,0,1 +48702,-83.3333333333333,4,2,2,80,0,1 +48771,-83.3333333333333,4,2,1,80,0,1 +48975,-83.3333333333333,4,2,2,80,0,1 +49043,-83.3333333333333,4,2,1,80,0,1 +53066,-83.3333333333333,4,2,2,80,0,1 +53134,-83.3333333333333,4,2,1,80,0,1 +55657,-100,4,2,1,70,0,0 +64384,-125,4,2,1,60,0,0 +66566,-100,4,2,1,80,0,0 +68748,-125,4,2,1,60,0,0 +70930,-100,4,2,1,80,0,0 +73111,-100,4,2,1,50,0,0 +74202,-100,4,2,3,50,0,0 +74293,-100,4,2,2,50,0,0 +74475,-100,4,2,3,50,0,0 +74566,-100,4,2,2,50,0,0 +74748,-100,4,2,3,50,0,0 +74839,-100,4,2,2,50,0,0 +75021,-100,4,2,3,50,0,0 +75111,-100,4,2,2,50,0,0 +75293,-100,4,2,1,50,0,0 +76384,-100,4,2,4,50,0,0 +76475,-100,4,2,1,50,0,0 +76657,-100,4,2,4,50,0,0 +76748,-100,4,2,1,50,0,0 +76930,-83.3333333333333,4,2,1,55,0,0 +77475,-83.3333333333333,4,2,1,65,0,0 +86202,-100,4,2,1,75,0,0 +96021,-100,4,2,1,40,0,0 +103657,-100,4,2,2,50,0,0 +104202,-100,4,2,1,60,0,0 +104748,-83.3333333333333,4,2,1,80,0,1 +107066,-83.3333333333333,4,2,2,80,0,1 +107134,-83.3333333333333,4,2,1,80,0,1 +107339,-83.3333333333333,4,2,2,80,0,1 +107407,-83.3333333333333,4,2,1,80,0,1 +107611,-83.3333333333333,4,2,2,80,0,1 +107680,-83.3333333333333,4,2,1,80,0,1 +107884,-83.3333333333333,4,2,2,80,0,1 +107952,-83.3333333333333,4,2,1,80,0,1 +108157,-83.3333333333333,4,2,2,80,0,1 +108225,-83.3333333333333,4,2,1,80,0,1 +108430,-83.3333333333333,4,2,2,80,0,1 +108498,-83.3333333333333,4,2,1,80,0,1 +111430,-83.3333333333333,4,2,2,80,0,1 +111498,-83.3333333333333,4,2,1,80,0,1 +111702,-83.3333333333333,4,2,2,80,0,1 +111771,-83.3333333333333,4,2,1,80,0,1 +111975,-83.3333333333333,4,2,2,80,0,1 +112043,-83.3333333333333,4,2,1,80,0,1 +112248,-83.3333333333333,4,2,2,80,0,1 +112316,-83.3333333333333,4,2,1,80,0,1 +113475,-125,4,2,3,50,0,0 +113748,-125,4,2,4,50,0,0 +117839,-125,4,2,3,50,0,0 +117975,-125,4,2,1,50,0,0 +118111,-125,4,2,4,50,0,0 +118248,-125,4,2,1,50,0,0 +118384,-125,4,2,4,50,0,0 +118521,-125,4,2,1,50,0,0 +118657,-125,4,2,4,50,0,0 +118793,-125,4,2,1,50,0,0 +118930,-125,4,2,4,50,0,0 +119066,-125,4,2,1,50,0,0 +119202,-125,4,2,4,50,0,0 +119339,-125,4,2,1,50,0,0 +119475,-125,4,2,4,50,0,0 +119611,-125,4,2,1,50,0,0 +119748,-125,4,2,4,50,0,0 +119884,-125,4,2,1,50,0,0 +120021,-125,4,2,4,50,0,0 +120157,-125,4,2,1,50,0,0 +120293,-125,4,2,4,50,0,0 +120430,-125,4,2,1,50,0,0 +120566,-125,4,2,4,50,0,0 +120702,-125,4,2,1,50,0,0 +120839,-125,4,2,4,50,0,0 +120975,-125,4,2,1,50,0,0 +121111,-125,4,2,4,50,0,0 +121248,-125,4,2,1,50,0,0 +121384,-125,4,2,4,50,0,0 +121521,-125,4,2,1,50,0,0 +121657,-125,4,2,4,50,0,0 +121793,-125,4,2,1,50,0,0 +121930,-125,4,2,4,50,0,0 +122066,-125,4,2,1,50,0,0 +122202,-100,4,2,1,60,0,0 +148384,-83.3333333333333,4,2,1,60,0,0 +149611,-100,4,2,1,60,0,0 +150975,-83.3333333333333,4,2,1,60,0,0 +152066,-100,4,2,1,60,0,0 +156021,-83.3333333333333,4,2,1,60,0,0 +157111,-83.3333333333333,4,2,1,65,0,0 +172384,-83.3333333333333,4,2,3,65,0,0 +172566,-83.3333333333333,4,2,2,65,0,0 +172930,-83.3333333333333,4,2,1,65,0,0 +173067,210.526315789474,4,2,1,65,1,0 +173277,222.222222222222,4,2,1,85,1,0 +173277,-100,4,2,1,85,0,1 +207943,272.727272727273,4,2,1,50,1,1 +207943,-125,4,2,1,50,0,0 +211215,-100,4,2,1,70,0,0 +219943,-100,4,2,1,60,0,0 +223215,-100,4,2,1,80,0,0 +227715,-100,4,2,2,80,0,0 +227783,-100,4,2,1,80,0,0 +227988,-100,4,2,2,80,0,0 +228056,-100,4,2,1,80,0,0 +228261,-100,4,2,2,80,0,0 +228329,-100,4,2,1,80,0,0 +228533,-100,4,2,2,80,0,0 +228602,-100,4,2,1,80,0,0 +229761,-100,4,2,1,50,0,0 +230852,-100,4,2,3,50,0,0 +230943,-100,4,2,2,50,0,0 +231124,-100,4,2,3,50,0,0 +231215,-100,4,2,2,50,0,0 +231397,-100,4,2,3,50,0,0 +231488,-100,4,2,2,50,0,0 +231670,-100,4,2,3,50,0,0 +231761,-100,4,2,2,50,0,0 +231943,-100,4,2,1,50,0,0 +233033,-100,4,2,3,50,0,0 +233124,-100,4,2,1,50,0,0 +233306,-100,4,2,3,50,0,0 +233397,-100,4,2,1,50,0,0 +233579,-83.3333333333333,4,2,1,50,0,0 +234124,-83.3333333333333,4,2,1,65,0,0 +242852,-100,4,2,1,75,0,0 +251579,-83.3333333333333,4,2,1,80,0,1 +253897,-83.3333333333333,4,2,2,80,0,1 +253965,-83.3333333333333,4,2,1,80,0,1 +254170,-83.3333333333333,4,2,2,80,0,1 +254238,-83.3333333333333,4,2,1,80,0,1 +254443,-83.3333333333333,4,2,2,80,0,1 +254511,-83.3333333333333,4,2,1,80,0,1 +254715,-83.3333333333333,4,2,2,80,0,1 +254783,-83.3333333333333,4,2,1,80,0,1 +254988,-83.3333333333333,4,2,2,80,0,1 +255056,-83.3333333333333,4,2,1,80,0,1 +255261,-83.3333333333333,4,2,2,80,0,1 +255329,-83.3333333333333,4,2,1,80,0,1 +258261,-83.3333333333333,4,2,2,80,0,1 +258329,-83.3333333333333,4,2,1,80,0,1 +258533,-83.3333333333333,4,2,2,80,0,1 +258602,-83.3333333333333,4,2,1,80,0,1 +258806,-83.3333333333333,4,2,2,80,0,1 +258874,-83.3333333333333,4,2,1,80,0,1 +259079,-83.3333333333333,4,2,2,80,0,1 +259147,-100,4,2,1,80,0,1 +260033,-100,4,2,1,80,0,1 +260306,-83.3333333333333,4,2,1,90,0,1 +260313,-83.3333333333333,4,2,1,90,0,1 +262624,-83.3333333333333,4,2,2,90,0,1 +262693,-83.3333333333333,4,2,1,90,0,1 +262897,-83.3333333333333,4,2,2,90,0,1 +262965,-83.3333333333333,4,2,1,90,0,1 +263170,-83.3333333333333,4,2,2,90,0,1 +263238,-83.3333333333333,4,2,1,90,0,1 +263443,-83.3333333333333,4,2,2,90,0,1 +263511,-83.3333333333333,4,2,1,90,0,1 +263715,-83.3333333333333,4,2,2,90,0,1 +263783,-83.3333333333333,4,2,1,90,0,1 +263988,-83.3333333333333,4,2,2,90,0,1 +264056,-83.3333333333333,4,2,1,90,0,1 +266988,-83.3333333333333,4,2,2,90,0,1 +267056,-83.3333333333333,4,2,1,90,0,1 +267261,-83.3333333333333,4,2,2,90,0,1 +267329,-83.3333333333333,4,2,1,90,0,1 +267533,-83.3333333333333,4,2,2,90,0,1 +267602,-83.3333333333333,4,2,1,90,0,1 +267806,-83.3333333333333,4,2,2,90,0,1 +267874,-83.3333333333333,4,2,1,90,0,1 +269033,532.150776053215,4,2,1,60,1,1 +269033,-100,4,2,1,60,0,0 +269965,-100,4,2,1,5,0,0 +270097,-66.6666666666667,4,2,1,60,0,0 +272211,-100,4,2,1,60,0,0 +273282,-100,4,2,1,60,0,0 +273290,558.139534883721,4,2,1,60,1,0 +273848,600,4,2,1,60,1,0 +273848,-100,4,2,1,60,0,0 +274248,750,4,2,1,60,1,0 +274269,-100,4,2,1,60,0,0 +274498,750,4,2,1,60,1,0 +274498,-100,4,2,1,60,0,0 +274873,800,4,2,1,60,1,0 +274873,-100,4,2,1,60,0,0 +275273,923.076923076923,4,2,1,60,1,0 +275273,-100,4,2,1,60,0,0 +275734,960,4,2,1,60,1,0 +275734,-100,4,2,1,60,0,0 +276054,1200,4,2,1,60,1,0 +276254,995.850622406639,4,2,1,70,1,0 +276254,-100,4,2,1,70,0,0 +276257,-100,4,2,1,70,0,0 +277249,764.331210191083,4,2,1,70,1,0 +277257,-100,4,2,1,70,0,0 +277503,693.64161849711,4,2,1,70,1,0 +277737,-100,4,2,1,70,0,0 +278181,-100,4,2,1,70,0,0 +278196,631.578947368421,4,2,1,70,1,0 +278609,-100,4,2,1,70,0,0 +278617,588.235294117647,4,2,1,70,1,0 +278813,545.454545454546,4,2,1,70,1,0 +279009,-100,4,2,1,70,0,0 +279358,521.739130434783,4,2,1,70,1,0 +279372,-100,4,2,1,70,0,0 +279687,-100,4,2,1,70,0,0 +279705,718.562874251497,4,2,1,70,1,0 +279944,666.666666666667,4,2,1,70,1,0 +279947,-100,4,2,1,70,0,0 +280170,-100,4,2,1,70,0,0 +280604,-100,4,2,1,70,0,0 +280610,558.139534883721,4,2,1,70,1,0 +280889,521.739130434783,4,2,1,70,1,0 +281149,576.923076923077,4,2,1,70,1,0 +281436,-100,4,2,1,70,0,0 +281437,609.137055837563,4,2,1,70,1,0 +281736,-100,4,2,1,70,0,0 +281741,631.578947368421,4,2,1,70,1,0 +281843,-100,4,2,1,70,0,0 +282056,406.779661016949,4,2,1,70,1,0 +282259,415.22491349481,4,2,1,70,1,0 +282669,-100,4,2,1,70,0,0 +282674,428.571428571429,4,2,1,70,1,0 +283097,-100,4,2,1,70,0,0 +283497,-100,4,2,1,70,0,0 +283531,400,4,2,1,70,1,0 +283931,375,4,2,1,70,1,0 +284118,436.363636363636,4,2,1,70,1,0 +284247,-100,4,2,1,70,0,0 +284554,461.538461538462,4,2,1,70,1,0 +284647,-100,4,2,1,70,0,0 +285015,480,4,2,1,70,1,0 +285247,-100,4,2,1,70,0,0 +285255,500,4,2,1,70,1,0 +285599,-100,4,2,1,70,0,0 +285741,-100,4,2,1,70,0,0 +285755,369.230769230769,4,2,1,70,1,0 +286124,601.642483981269,4,2,1,70,1,0 +286725,857.142857142857,4,2,1,90,1,0 +286725,-25,4,2,1,90,0,0 + +[HitObjects] +28,123,22,6,0,L|40:187,5,34,6|2|2|2|2|2,3:2|1:2|1:2|3:2|1:2|1:2,0:0:0:0: +106,58,431,2,0,L|122:-5,5,34,2|2|2|2|2|2,3:2|1:2|1:2|3:2|1:2|1:2,0:0:0:0: +207,61,840,2,0,B|280:43|280:43|288:45|288:45|385:21,2,152.999995330811,6|6|6,3:2|3:2|3:2,0:0:0:0: +313,147,1385,6,0,L|377:159,5,34,6|2|2|2|2|2,3:2|1:2|1:2|3:2|1:2|1:2,0:0:0:0: +347,252,1794,2,0,L|396:239,5,34,2|2|2|2|2|2,3:2|1:2|1:2|3:2|1:2|1:2,0:0:0:0: +415,328,2203,2,0,B|433:255|433:255|431:247|431:247|455:150,2,152.999995330811,6|6|6,3:2|3:2|3:2,0:0:0:0: +235,343,2749,6,0,L|171:331,5,34,6|2|2|2|2|2,3:2|1:2|1:2|3:2|1:2|1:2,0:0:0:0: +219,239,3158,2,0,L|236:187,5,34,2|2|2|2|2|2,3:2|1:2|1:2|3:2|1:2|1:2,0:0:0:0: +299,136,3567,2,0,B|231:152|231:152|223:150|223:150|150:168,2,152.999995330811,6|6|6,3:2|3:2|3:2,0:0:0:0: +234,11,4112,6,0,L|182:-2,5,34,6|2|2|2|2|2,3:2|1:2|1:2|3:2|1:2|1:2,0:0:0:0: +135,70,4522,2,0,L|83:83,5,34,2|2|2|2|2|2,3:2|1:2|1:2|3:2|1:2|1:2,0:0:0:0: +35,15,4931,2,0,B|53:88|53:88|51:96|51:96|75:193,2,152.999995330811,6|6|6,3:2|3:2|3:2,0:0:0:0: +22,251,5476,6,0,L|17:306,5,34,6|2|2|2|2|2,3:2|1:2|1:2|3:2|1:2|1:2,0:0:0:0: +120,238,5885,2,0,L|171:256,5,34,2|2|2|2|2|2,3:2|1:2|1:2|3:2|1:2|1:2,0:0:0:0: +187,333,6294,2,0,B|114:351|114:351|106:349|106:349|9:373,2,152.999995330811,6|6|6,3:2|3:2|3:2,0:0:0:0: +363,340,6840,6,0,L|358:285,5,34,6|2|2|2|2|2,3:2|1:2|1:2|3:2|1:2|1:2,0:0:0:0: +411,223,7249,2,0,L|462:205,5,34,2|2|2|2|2|2,3:2|1:2|1:2|3:2|1:2|1:2,0:0:0:0: +355,148,7658,2,0,B|373:75|373:75|371:67|371:67|395:-30,2,152.999995330811,6|6|6,3:2|3:2|3:2,0:0:0:0: +502,158,8203,6,0,L|514:222,5,34,6|2|2|2|2|2,3:2|1:2|1:2|3:2|1:2|1:2,0:0:0:0: +419,236,8612,2,0,L|436:288,5,34,2|2|2|2|2|2,3:2|1:2|1:2|3:2|1:2|1:2,0:0:0:0: +364,341,9022,2,0,B|437:359|437:359|445:357|445:357|542:381,2,152.999995330811,6|6|6,3:2|3:2|3:2,0:0:0:0: +233,235,9567,6,0,L|222:181,5,34,6|2|2|2|2|2,3:2|1:2|1:2|3:2|1:2|1:2,0:0:0:0: +284,125,9976,2,0,L|304:94,5,34,2|2|2|2|2|2,3:2|1:2|1:2|3:2|1:2|1:2,0:0:0:0: +245,16,10385,6,0,P|171:23|132:125,2,152.999995330811,6|6|6,3:2|3:2|3:2,0:0:0:0: +407,374,12021,6,0,P|406:316|461:265,1,101.999996887207,6|8,3:2|2:2,0:0:0:0: +484,281,12225,1,2,3:2:0:0: +484,281,12293,2,0,P|429:260|401:212,1,101.999996887207,0|8,3:3|2:2,0:0:0:0: +387,125,12566,2,0,P|462:119|484:41,2,152.999995330811,2|2|10,3:2|3:2|3:2,0:0:0:0: +274,30,13111,6,0,L|141:54,1,101.999996887207,2|8,3:2|3:2,0:0:0:0: +124,33,13316,1,2,3:2:0:0: +124,33,13384,2,0,L|-1:56,1,101.999996887207,0|10,3:3|3:2,0:0:0:0: +24,154,13657,2,0,P|81:177|106:268,1,152.999995330811,2|2,3:2|3:2,0:0:0:0: +229,353,14066,1,10,3:2:0:0: +328,376,14202,6,0,P|324:316|293:277,1,101.999996887207,2|8,3:2|3:2,0:0:0:0: +256,265,14407,1,2,3:2:0:0: +256,265,14475,2,0,P|306:242|339:189,1,101.999996887207,0|10,3:3|3:2,0:0:0:0: +378,113,14748,2,0,P|449:120|500:192,2,152.999995330811,2|2|10,3:2|3:2|3:2,0:0:0:0: +277,8,15293,6,0,L|246:133,1,101.999996887207,2|8,3:2|3:2,0:0:0:0: +212,137,15498,1,2,3:2:0:0: +212,137,15566,2,0,L|243:262,1,101.999996887207,0|10,3:3|3:2,0:0:0:0: +256,336,15839,2,0,P|314:314|423:379,1,152.999995330811,2|2,3:2|3:2,0:0:0:0: +473,159,16248,1,10,3:2:0:0: +486,58,16384,6,0,P|431:61|387:116,1,101.999996887207,6|8,3:2|3:2,0:0:0:0: +382,142,16589,1,2,3:2:0:0: +382,142,16657,2,0,P|336:101|269:103,1,101.999996887207,0|10,3:3|3:2,0:0:0:0: +201,131,16930,2,0,P|189:74|105:47,2,152.999995330811,2|2|10,3:2|3:2|3:2,0:0:0:0: +40,174,17475,6,0,L|63:312,1,101.999996887207,2|8,3:2|3:2,0:0:0:0: +97,307,17680,1,2,3:2:0:0: +97,307,17748,2,0,L|235:284,1,101.999996887207,0|10,3:3|3:2,0:0:0:0: +275,223,18021,2,0,P|243:290|273:374,1,152.999995330811,2|2,3:2|3:2,0:0:0:0: +415,382,18430,1,10,3:2:0:0: +355,299,18566,6,0,P|394:279|466:297,1,101.999996887207,2|8,3:2|3:2,0:0:0:0: +486,250,18771,1,2,3:2:0:0: +486,250,18839,2,0,P|453:208|460:142,1,101.999996887207,0|10,3:3|3:2,0:0:0:0: +476,62,19111,2,0,P|444:116|342:98,2,152.999995330811,2|2|10,3:2|3:2|3:2,0:0:0:0: +306,4,19657,6,0,L|183:50,1,101.999996887207,6|8,3:2|3:2,0:0:0:0: +161,32,19861,1,2,3:2:0:0: +161,32,19930,2,0,L|207:155,1,101.999996887207,0|10,3:3|3:2,0:0:0:0: +127,201,20202,2,0,P|67:223|6:192,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +41,380,20475,1,0,1:1:0:0: +48,355,20543,1,8,2:3:0:0: +64,336,20611,1,8,2:3:0:0: +86,323,20679,1,4,2:3:0:0: +111,319,20748,6,0,P|172:336|208:385,1,101.999996887207,6|8,3:2|3:2,0:0:0:0: +249,382,20952,1,2,3:2:0:0: +249,382,21021,2,0,L|374:366,1,101.999996887207,0|10,3:3|3:2,0:0:0:0: +451,381,21293,2,0,P|460:339|385:240,1,152.999995330811,2|2,3:2|3:2,0:0:0:0: +398,95,21702,1,10,3:2:0:0: +337,177,21839,6,0,P|288:208|226:199,1,101.999996887207,2|8,3:2|3:2,0:0:0:0: +202,192,22043,1,2,3:2:0:0: +202,192,22111,2,0,L|172:82,1,101.999996887207,0|10,3:3|3:2,0:0:0:0: +7,86,22384,1,2,3:2:0:0: +7,86,22589,1,2,3:2:0:0: +7,86,22793,1,10,3:2:0:0: +61,245,22930,6,0,L|48:373,1,101.999996887207,2|8,3:2|3:2,0:0:0:0: +92,384,23134,1,2,3:2:0:0: +92,384,23202,2,0,P|149:373|187:330,1,101.999996887207,0|10,3:3|3:2,0:0:0:0: +262,283,23475,2,0,P|328:313|350:411,1,152.999995330811,2|2,3:2|3:2,0:0:0:0: +467,280,23884,1,10,3:2:0:0: +430,184,24021,6,0,L|310:204,1,101.999996887207,2|8,3:2|3:2,0:0:0:0: +284,192,24225,1,2,3:2:0:0: +284,192,24293,2,0,P|257:131|272:74,1,101.999996887207,0|10,3:3|3:2,0:0:0:0: +386,4,24566,1,2,3:2:0:0: +386,4,24771,1,2,3:2:0:0: +386,4,24975,1,10,3:2:0:0: +432,136,25111,6,0,P|427:195|465:245,1,101.999996887207,6|8,3:2|3:2,0:0:0:0: +416,272,25316,1,2,3:2:0:0: +416,272,25384,2,0,L|306:247,1,101.999996887207,0|10,3:3|3:2,0:0:0:0: +219,215,25657,2,0,P|172:266|191:388,1,152.999995330811,2|2,3:2|3:2,0:0:0:0: +40,259,26066,1,10,3:2:0:0: +28,157,26202,6,0,P|69:144|104:73,1,101.999996887207,2|8,3:2|3:2,0:0:0:0: +125,53,26407,1,2,3:2:0:0: +125,53,26475,2,0,L|146:171,1,101.999996887207,0|10,3:3|3:2,0:0:0:0: +221,307,26748,1,2,3:2:0:0: +221,307,26953,1,2,3:2:0:0: +221,307,27157,1,10,3:2:0:0: +379,281,27293,6,0,L|497:303,1,101.999996887207,2|8,3:2|3:2,0:0:0:0: +510,259,27498,1,2,3:2:0:0: +510,259,27566,2,0,P|514:209|471:147,1,101.999996887207,0|10,3:3|3:2,0:0:0:0: +503,62,27839,2,0,P|461:116|373:111,1,152.999995330811,2|2,3:2|3:2,0:0:0:0: +256,28,28248,1,10,3:2:0:0: +190,105,28384,5,8,2:3:0:0: +269,169,28521,1,4,2:3:0:0: +272,178,28589,1,8,2:3:0:0: +275,187,28657,2,0,L|260:327,1,101.999996887207,8|8,2:3|2:3,0:0:0:0: +179,345,28930,1,8,2:3:0:0: +154,338,28998,1,8,2:3:0:0: +135,322,29066,1,4,2:3:0:0: +122,300,29134,1,8,2:3:0:0: +118,275,29202,2,0,L|106:333,3,50.9999984436036,8|4|8|8,2:3|2:3|2:3|2:3,0:0:0:0: +45,207,29475,6,0,L|-10:224,3,42.5,14|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +102,137,29748,2,0,L|157:154,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +193,228,30021,2,0,L|205:268,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +291,311,30293,2,0,L|303:270,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +391,243,30566,5,10,3:2:0:0: +400,246,30634,1,0,3:3:0:0: +409,249,30702,1,0,3:3:0:0: +434,344,30839,1,10,3:2:0:0: +425,347,30907,1,0,3:3:0:0: +416,350,30975,1,0,3:3:0:0: +512,269,31111,2,0,L|499:228,2,42.5,10|0|0,3:2|3:3|3:3,0:0:0:0: +435,152,31384,2,0,L|447:111,2,42.5,10|0|0,3:2|3:3|3:3,0:0:0:0: +381,34,31657,6,0,L|340:46,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +251,83,31930,2,0,L|196:66,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +146,137,32202,2,0,L|158:177,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +56,112,32475,2,0,L|68:72,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +22,199,32748,5,10,3:2:0:0: +25,208,32816,1,0,3:3:0:0: +28,217,32884,1,0,3:3:0:0: +93,292,33021,1,10,3:2:0:0: +90,301,33089,1,0,3:3:0:0: +87,310,33157,1,0,3:3:0:0: +168,367,33293,1,10,3:2:0:0: +176,365,33361,1,0,3:3:0:0: +184,363,33430,2,0,L|288:375,1,85,0|10,3:3|3:2,0:0:0:0: +274,168,33839,6,0,L|262:128,3,42.5,14|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +330,66,34112,2,0,L|342:26,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +422,109,34384,2,0,L|463:121,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +461,218,34657,2,0,L|516:201,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +448,314,34930,5,10,3:2:0:0: +439,311,34998,1,0,3:3:0:0: +430,308,35066,1,0,3:3:0:0: +321,262,35202,1,10,3:2:0:0: +312,265,35270,1,0,3:3:0:0: +303,268,35338,1,0,3:3:0:0: +269,366,35475,2,0,L|214:349,2,42.5,10|0|0,3:2|3:3|3:3,0:0:0:0: +162,271,35748,2,0,L|203:259,2,42.5,10|0|0,3:2|3:3|3:3,0:0:0:0: +87,207,36021,6,0,L|99:167,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +31,105,36294,2,0,L|19:65,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +101,9,36566,2,0,L|142:21,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +184,108,36839,2,0,L|239:91,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +304,31,37111,5,10,3:2:0:0: +307,22,37179,1,0,3:3:0:0: +310,13,37247,1,0,3:3:0:0: +392,90,37384,1,10,3:2:0:0: +395,99,37452,1,0,3:3:0:0: +398,108,37520,1,0,3:3:0:0: +341,194,37657,2,0,L|363:249,3,42.5,8|8|8|8,2:3|2:3|2:3|2:3,0:0:0:0: +352,320,37930,2,0,L|374:375,3,42.5,4|4|4|4,2:3|2:3|2:3|2:3,0:0:0:0: +449,384,38202,6,0,P|490:343|470:247,1,136,6|2,3:2|2:2,0:0:0:0: +487,268,38748,2,0,L|351:239,1,136,2|2,1:2|2:2,0:0:0:0: +403,58,39293,2,0,B|330:66|368:102|248:108,1,136,2|2,3:2|3:2,0:0:0:0: +277,105,39702,1,2,3:2:0:0: +155,6,39839,2,0,P|184:59|184:92,1,68,2|0,1:2|1:1,0:0:0:0: +65,163,40111,1,2,1:2:0:0: +65,163,40384,6,0,L|156:180,1,85,6|2,3:2|1:2,0:0:0:0: +90,336,40657,2,0,L|-1:353,1,85,0|2,3:3|1:2,0:0:0:0: +180,280,40930,1,2,3:2:0:0: +280,304,41066,1,2,1:2:0:0: +280,304,41134,1,2,2:2:0:0: +280,304,41202,2,0,L|371:321,1,85,2|2,3:2|1:2,0:0:0:0: +208,384,41475,5,2,3:2:0:0: +208,384,41611,1,2,1:2:0:0: +372,304,41748,2,0,L|281:287,1,85,2|2,3:2|1:2,0:0:0:0: +170,216,42021,2,0,L|190:119,1,85,2|0,3:2|1:1,0:0:0:0: +64,75,42293,2,0,L|72:31,3,42.5,8|8|4|4,2:3|2:3|2:3|2:3,0:0:0:0: +25,148,42566,6,0,P|49:229|11:298,1,136,6|2,3:2|2:2,0:0:0:0: +32,274,43111,2,0,L|187:310,1,136,2|2,1:2|2:2,0:0:0:0: +420,179,43657,2,0,B|347:187|385:223|265:229,1,136,2|2,3:2|3:2,0:0:0:0: +294,226,44066,1,2,3:2:0:0: +204,146,44202,2,0,P|204:111|236:62,1,68,2|0,1:2|1:1,0:0:0:0: +381,14,44475,1,2,1:2:0:0: +381,14,44748,6,0,L|394:111,1,85,6|2,3:2|1:2,0:0:0:0: +500,237,45021,2,0,L|487:334,1,85,2|2,2:2|1:2,0:0:0:0: +285,242,45293,1,2,2:2:0:0: +397,200,45430,1,2,1:2:0:0: +397,200,45498,1,2,3:2:0:0: +397,200,45566,2,0,L|384:297,1,85,2|2,2:2|1:2,0:0:0:0: +208,318,45839,5,0,1:1:0:0: +208,318,45907,1,0,1:1:0:0: +208,318,45975,2,0,P|166:292|113:291,1,85,8|4,2:3|2:3,0:0:0:0: +47,227,46248,1,0,1:1:0:0: +54,185,46316,1,0,1:1:0:0: +61,143,46384,1,8,2:3:0:0: +118,57,46521,2,0,L|108:-6,5,42.5,8|8|4|4|4|4,2:3|2:3|2:3|2:3|2:3|2:3,0:0:0:0: +186,106,46930,6,0,P|246:93|289:35,1,101.999996887207,6|0,3:2|1:1,0:0:0:0: +446,47,47202,2,0,P|407:14|357:7,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +367,108,47475,2,0,L|392:212,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +297,383,47748,2,0,L|320:283,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +243,216,48021,6,0,L|143:239,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +188,88,48293,1,2,3:2:0:0: +188,88,48430,1,2,1:2:0:0: +59,159,48566,2,0,P|39:239|63:287,1,101.999996887207,2|0,3:2|3:3,0:0:0:0: +174,359,48839,2,0,L|274:382,1,101.999996887207,2|0,3:2|3:3,0:0:0:0: +423,310,49111,6,0,P|430:244|402:199,1,101.999996887207,6|2,3:2|1:2,0:0:0:0: +346,71,49384,2,0,P|399:110|452:108,1,101.999996887207,2|2,3:2|1:2,0:0:0:0: +217,12,49657,1,2,3:2:0:0: +208,152,49793,1,2,1:2:0:0: +208,152,49861,1,2,2:2:0:0: +208,152,49930,2,0,L|73:172,1,101.999996887207,2|2,3:2|1:2,0:0:0:0: +45,14,50202,5,2,3:2:0:0: +108,77,50338,1,2,1:2:0:0: +107,167,50475,1,2,3:2:0:0: +44,230,50611,1,2,1:2:0:0: +70,316,50748,2,0,B|165:332|165:332|180:346|180:346|302:361,1,203.999993774414,8|4,3:3|2:3,0:0:0:0: +441,286,51157,5,4,2:3:0:0: +434,296,51225,1,4,2:3:0:0: +427,306,51293,2,0,L|401:188,1,101.999996887207,6|0,3:2|1:1,0:0:0:0: +482,12,51566,2,0,L|456:130,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +357,113,51839,2,0,P|316:142|257:142,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +119,20,52111,2,0,P|169:22|210:51,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +164,143,52384,6,0,P|123:174|31:168,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +0,304,52657,1,2,3:2:0:0: +0,304,52793,1,2,1:2:0:0: +124,339,52930,2,0,L|236:353,1,101.999996887207,2|0,3:2|3:3,0:0:0:0: +316,242,53202,2,0,L|302:130,1,101.999996887207,2|0,3:2|3:1,0:0:0:0: +332,0,53475,6,0,P|389:17|424:69,1,101.999996887207,6|0,3:2|1:1,0:0:0:0: +512,147,53748,2,0,P|455:164|420:216,1,101.999996887207,8|0,2:3|1:1,0:0:0:0: +512,332,54021,1,0,3:3:0:0: +363,319,54157,1,0,1:1:0:0: +363,319,54225,1,0,2:2:0:0: +363,319,54293,2,0,L|246:300,1,101.999996887207,4|0,3:3|1:1,0:0:0:0: +308,164,54566,5,0,3:3:0:0: +269,181,54634,1,0,3:3:0:0: +227,177,54702,1,0,1:1:0:0: +193,153,54770,1,0,1:1:0:0: +175,116,54838,1,8,2:3:0:0: +81,73,54975,1,0,1:1:0:0: +74,115,55043,1,0,1:1:0:0: +67,157,55111,1,4,2:3:0:0: +18,247,55248,2,0,L|28:310,5,50.9999984436036,0|0|8|8|4|4,1:1|1:1|2:3|2:3|2:3|2:3,0:0:0:0: +87,361,55657,6,0,L|128:349,3,42.5,14|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +175,263,55929,2,0,L|230:280,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +295,228,56202,2,0,L|307:188,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +265,105,56475,2,0,L|253:65,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +327,8,56748,5,10,3:2:0:0: +336,11,56816,1,0,3:3:0:0: +345,14,56884,1,0,3:3:0:0: +414,83,57021,1,10,3:2:0:0: +423,80,57089,1,0,3:3:0:0: +432,77,57157,1,0,3:3:0:0: +502,143,57293,2,0,L|490:183,2,42.5,10|0|0,3:2|3:3|3:3,0:0:0:0: +431,255,57566,2,0,L|443:295,2,42.5,10|0|0,3:2|3:3|3:3,0:0:0:0: +356,334,57839,6,0,L|344:374,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +294,256,58112,2,0,L|334:244,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +205,299,58384,2,0,L|193:259,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +151,377,58657,2,0,L|111:365,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +21,328,58930,5,10,3:2:0:0: +18,337,58998,1,0,3:3:0:0: +15,346,59066,1,0,3:3:0:0: +96,263,59202,1,10,3:2:0:0: +93,254,59270,1,0,3:3:0:0: +90,245,59338,1,0,3:3:0:0: +38,161,59475,1,10,3:2:0:0: +41,152,59543,1,0,3:3:0:0: +44,143,59611,2,0,L|32:18,1,85,0|10,3:3|3:2,0:0:0:0: +227,20,60021,6,0,L|215:60,3,42.5,14|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +257,143,60294,2,0,L|269:183,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +357,143,60566,2,0,L|398:131,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +445,45,60838,2,0,L|500:62,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +496,149,61111,5,10,3:2:0:0: +493,158,61179,1,0,3:3:0:0: +490,167,61247,1,0,3:3:0:0: +420,245,61384,1,10,3:2:0:0: +417,236,61452,1,0,3:3:0:0: +414,227,61521,1,0,3:3:0:0: +389,337,61657,2,0,L|349:325,2,42.5,10|0|0,3:2|3:3|3:3,0:0:0:0: +277,266,61930,2,0,L|237:278,2,42.5,10|0|0,3:2|3:3|3:3,0:0:0:0: +161,214,62202,6,0,L|149:174,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +142,307,62475,2,0,L|102:295,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +2,292,62748,2,0,L|14:252,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +0,158,63021,2,0,L|40:146,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +95,70,63293,5,10,3:2:0:0: +104,73,63361,1,0,3:3:0:0: +113,76,63429,1,0,3:3:0:0: +189,141,63566,1,10,3:2:0:0: +198,138,63634,1,0,3:3:0:0: +207,135,63702,1,0,3:3:0:0: +281,59,63839,2,0,L|338:73,3,42.5,8|8|8|8,2:3|2:3|2:3|2:3,0:0:0:0: +362,142,64111,2,0,L|419:156,3,42.5,4|4|4|4,2:3|2:3|2:3|2:3,0:0:0:0: +478,112,64384,6,0,P|441:165|461:260,1,136,6|2,3:2|1:2,0:0:0:0: +485,364,64930,2,0,L|325:332,1,136,2|0,3:2|1:1,0:0:0:0: +222,294,65475,2,0,B|156:309|190:338|97:360,1,136,2|2,3:2|1:2,0:0:0:0: +104,358,65884,1,2,3:2:0:0: +16,285,66021,2,0,P|18:244|44:201,1,68,2|0,3:2|1:1,0:0:0:0: +28,219,66225,1,0,1:1:0:0: +28,219,66293,1,10,2:3:0:0: +90,145,66566,6,0,L|76:55,1,85,6|0,3:2|1:1,0:0:0:0: +256,0,66839,2,0,L|242:90,1,85,0|0,3:2|1:1,0:0:0:0: +186,179,67111,1,0,3:3:0:0: +273,263,67248,1,2,1:2:0:0: +273,263,67316,1,2,3:2:0:0: +273,263,67384,2,0,L|395:248,1,85,2|2,3:2|1:2,0:0:0:0: +471,151,67657,5,2,3:2:0:0: +471,151,67793,1,2,1:2:0:0: +392,272,67930,2,0,L|307:282,1,85,2|2,3:2|1:2,0:0:0:0: +165,327,68202,2,0,L|179:237,1,85,2|0,3:2|1:1,0:0:0:0: +266,112,68475,2,0,L|307:119,3,42.5,8|8|4|4,3:3|2:3|2:3|2:3,0:0:0:0: +358,51,68748,6,0,P|439:27|508:65,1,136,6|2,3:2|1:2,0:0:0:0: +447,174,69293,2,0,L|473:336,1,136,2|2,3:2|1:2,0:0:0:0: +343,253,69839,2,0,B|308:188|278:221|230:145,1,136,2|2,3:2|1:2,0:0:0:0: +216,58,70248,1,0,1:1:0:0: +216,58,70316,1,0,1:1:0:0: +216,58,70384,2,0,P|177:80|140:84,1,68,8|8,2:3|2:3,0:0:0:0: +58,36,70657,1,4,2:3:0:0: +58,36,70930,6,0,L|45:155,1,85,6|2,3:2|1:2,0:0:0:0: +129,284,71202,2,0,L|142:403,1,85,2|2,3:2|1:2,0:0:0:0: +132,180,71475,1,2,3:2:0:0: +228,241,71611,1,2,1:2:0:0: +228,241,71680,1,2,3:2:0:0: +228,241,71748,2,0,L|312:250,1,85,2|2,3:2|1:2,0:0:0:0: +382,363,72021,5,2,3:2:0:0: +414,371,72089,1,0,1:1:0:0: +448,367,72157,1,0,1:1:0:0: +478,351,72225,1,0,1:1:0:0: +500,326,72293,1,0,1:1:0:0: +453,220,72430,1,0,1:1:0:0: +449,206,72498,1,0,1:1:0:0: +445,192,72566,2,0,L|422:244,2,42.5,0|0|0,3:3|1:1|1:1,0:0:0:0: +486,110,72839,2,0,L|503:71,1,42.5,0|0,1:1|1:1,0:0:0:0: +414,68,72975,2,0,L|431:29,1,42.5,0|0,1:1|1:1,0:0:0:0: +344,23,73111,5,6,3:2:0:0: +62,180,75293,1,6,3:2:0:0: +403,350,76930,2,0,P|452:342|476:326,5,67.9999979248048,2|2|2|8|8|4,1:2|1:2|1:2|2:3|2:3|2:3,0:0:0:0: +412,257,77475,6,0,P|419:224|443:195,2,67.9999979248048,6|2|2,3:2|2:2|2:2,0:0:0:0: +320,230,77748,2,0,P|309:197|315:160,2,67.9999979248048,10|2|2,2:2|2:2|2:2,0:0:0:0: +248,289,78021,2,0,P|255:322|279:351,2,67.9999979248048,2|2|2,3:2|2:2|2:2,0:0:0:0: +156,316,78294,2,0,P|145:348|151:385,2,67.9999979248048,10|2|2,2:2|2:2|2:2,0:0:0:0: +97,240,78566,5,2,3:2:0:0: +89,250,78657,2,0,L|12:266,1,67.9999979248048,2|2,2:2|2:2,0:0:0:0: +10,169,78839,1,10,2:2:0:0: +52,134,78930,1,2,2:2:0:0: +106,132,79021,1,2,2:2:0:0: +154,154,79111,2,0,P|231:144|238:9,1,203.999993774414,2|10,3:2|2:2,0:0:0:0: +258,34,79657,6,0,L|170:26,2,67.9999979248048,2|2|2,3:2|2:2|2:2,0:0:0:0: +226,127,79930,2,0,L|138:142,2,67.9999979248048,10|2|2,2:2|2:2|2:2,0:0:0:0: +287,204,80202,2,0,L|374:219,2,67.9999979248048,2|2|2,3:2|2:2|2:2,0:0:0:0: +293,302,80475,2,0,L|373:339,2,67.9999979248048,10|2|2,2:2|2:2|2:2,0:0:0:0: +218,362,80748,5,2,3:2:0:0: +209,352,80839,2,0,P|194:313|204:265,1,67.9999979248048,2|2,2:2|2:2,0:0:0:0: +256,215,81021,1,10,2:2:0:0: +299,183,81111,1,2,2:2:0:0: +352,172,81202,1,2,2:2:0:0: +398,143,81293,2,0,B|402:238|466:224|462:346,1,203.999993774414,10|2,3:2|1:2,0:0:0:0: +462,332,81839,6,0,P|421:340|377:374,2,67.9999979248048,6|2|2,3:2|2:2|2:2,0:0:0:0: +347,273,82111,2,0,P|315:300|294:351,2,67.9999979248048,10|2|2,2:2|2:2|2:2,0:0:0:0: +368,179,82384,2,0,P|336:151|315:100,2,67.9999979248048,2|2|2,3:2|2:2|2:2,0:0:0:0: +238,172,82657,2,0,P|224:132|231:77,2,67.9999979248048,10|2|2,2:2|2:2|2:2,0:0:0:0: +135,75,82930,5,2,3:2:0:0: +139,58,83021,2,0,P|156:36|228:13,1,67.9999979248048,2|2,2:2|2:2,0:0:0:0: +41,127,83202,1,10,2:2:0:0: +83,161,83293,1,2,2:2:0:0: +103,211,83384,1,2,2:2:0:0: +99,265,83475,2,0,P|143:371|254:349,1,203.999993774414,2|10,3:2|2:2,0:0:0:0: +219,374,84021,6,0,L|156:351,2,67.9999979248048,2|2|2,3:2|2:2|2:2,0:0:0:0: +237,275,84293,2,0,L|182:236,2,67.9999979248048,10|2|2,2:2|2:2|2:2,0:0:0:0: +291,189,84566,2,0,L|354:166,2,67.9999979248048,2|2|2,3:2|2:2|2:2,0:0:0:0: +273,90,84839,2,0,L|327:51,2,67.9999979248048,10|2|2,2:2|2:2|2:2,0:0:0:0: +210,14,85111,5,2,3:2:0:0: +199,27,85202,2,0,P|177:68|182:118,1,67.9999979248048,2|2,2:2|2:2,0:0:0:0: +227,174,85384,1,2,1:2:0:0: +280,183,85475,1,2,1:2:0:0: +326,210,85566,1,2,1:2:0:0: +380,206,85657,2,0,B|477:182|477:182|551:217,2,152.999995330811,6|6|2,1:2|1:2|1:2,0:0:0:0: +414,298,86202,6,0,L|405:350,3,42.5,6|0|8|0,3:2|3:3|3:2|0:0,0:0:0:0: +313,333,86475,2,0,L|322:385,3,42.5,2|0|8|0,3:2|0:0|3:2|0:0,0:0:0:0: +229,285,86748,6,0,L|238:233,1,42.5,2|0,3:2|3:3,0:0:0:0: +140,308,86884,2,0,L|149:256,1,42.5,8|0,3:2|0:0,0:0:0:0: +51,334,87021,2,0,L|60:282,3,42.5,2|0|8|0,3:2|0:0|3:2|0:0,0:0:0:0: +41,200,87293,6,0,L|-11:209,2,42.5,2|0|8,3:2|3:3|3:2,0:0:0:0: +111,132,87566,1,2,3:2:0:0: +119,134,87634,1,0,3:3:0:0: +127,136,87702,1,8,3:2:0:0: +152,45,87839,2,0,L|100:36,2,42.5,2|0|8,3:2|3:3|3:2,0:0:0:0: +222,113,88112,1,2,3:2:0:0: +230,111,88180,1,0,3:3:0:0: +238,109,88248,1,8,3:2:0:0: +295,32,88384,6,0,L|347:23,3,42.5,2|0|8|0,3:2|3:3|3:2|0:0,0:0:0:0: +334,129,88657,2,0,L|386:138,3,42.5,2|0|8|0,3:2|0:0|3:2|0:0,0:0:0:0: +464,98,88930,6,0,L|473:150,1,42.5,2|0,3:2|3:3,0:0:0:0: +449,184,89066,2,0,L|458:236,1,42.5,8|0,3:2|0:0,0:0:0:0: +434,270,89202,2,0,L|443:322,3,42.5,2|0|8|0,3:2|0:0|3:2|0:0,0:0:0:0: +362,365,89475,5,2,3:2:0:0: +360,372,89543,1,0,3:3:0:0: +358,381,89611,1,8,3:2:0:0: +288,302,89748,1,2,3:2:0:0: +286,295,89816,1,0,3:3:0:0: +284,286,89884,1,8,3:2:0:0: +201,348,90021,1,2,3:2:0:0: +193,346,90089,1,0,3:3:0:0: +185,344,90158,2,0,L|81:356,1,85,8|2,3:2|3:2,0:0:0:0: +67,179,90566,6,0,L|15:170,3,42.5,6|0|8|0,3:2|3:3|3:2|0:0,0:0:0:0: +50,69,90839,2,0,L|-2:78,3,42.5,2|0|8|0,3:2|0:0|3:2|0:0,0:0:0:0: +147,88,91111,6,0,L|138:36,1,42.5,2|0,3:2|3:3,0:0:0:0: +236,111,91247,2,0,L|227:59,1,42.5,8|0,3:2|0:0,0:0:0:0: +325,137,91384,2,0,L|316:85,3,42.5,2|0|8|0,3:2|0:0|3:2|0:0,0:0:0:0: +257,207,91657,6,0,L|248:259,2,42.5,2|0|8,3:2|3:3|3:2,0:0:0:0: +154,263,91930,1,2,3:2:0:0: +156,271,91998,1,0,3:3:0:0: +158,279,92066,1,8,3:2:0:0: +231,342,92203,2,0,L|240:394,2,42.5,2|0|8,3:2|3:3|3:2,0:0:0:0: +327,324,92476,1,2,3:2:0:0: +329,316,92544,1,0,3:3:0:0: +331,308,92612,1,8,3:2:0:0: +431,315,92748,6,0,L|422:367,3,42.5,2|0|8|0,3:2|3:3|3:2|0:0,0:0:0:0: +503,248,93021,2,0,L|495:206,3,42.5,2|0|8|0,3:2|0:0|3:2|0:0,0:0:0:0: +457,113,93293,6,0,L|509:122,1,42.5,2|0,3:2|3:3,0:0:0:0: +371,79,93429,2,0,L|423:88,1,42.5,8|0,3:2|0:0,0:0:0:0: +286,47,93566,2,0,L|338:56,3,42.5,2|0|8|0,3:2|0:0|3:2|0:0,0:0:0:0: +195,22,93839,5,2,3:2:0:0: +193,29,93907,1,0,3:3:0:0: +191,38,93975,1,8,3:2:0:0: +118,104,94112,1,2,3:2:0:0: +120,111,94180,1,0,3:3:0:0: +122,120,94248,1,8,3:2:0:0: +145,217,94385,1,2,3:2:0:0: +143,225,94453,1,0,3:3:0:0: +141,233,94522,2,0,L|153:337,1,85,8|2,3:2|3:2,0:0:0:0: +48,13,94930,5,0,1:1:0:0: +41,21,94998,1,0,1:1:0:0: +34,29,95066,2,0,L|85:20,3,42.5,0|0|0|0,1:1|1:1|1:1|1:1,0:0:0:0: +77,103,95339,2,0,L|128:94,1,42.5,0|0,1:1|1:1,0:0:0:0: +37,192,95475,2,0,L|88:183,8,42.5,0|0|0|0|0|0|0|0|6,1:1|1:1|1:1|1:1|1:1|1:1|1:1|1:1|2:2,0:0:0:0: +285,375,104748,6,0,P|225:362|182:304,1,101.999996887207,6|0,3:2|1:1,0:0:0:0: +372,333,105020,2,0,P|411:300|461:293,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +483,207,105293,2,0,L|508:103,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +381,19,105566,2,0,L|404:119,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +336,191,105839,6,0,L|236:214,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +190,349,106111,1,2,3:2:0:0: +190,349,106248,1,2,1:2:0:0: +66,289,106384,2,0,P|46:209|70:161,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +160,78,106657,2,0,P|210:83|256:62,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +419,106,106929,6,0,P|426:40|398:-5,1,101.999996887207,6|2,3:2|3:2,0:0:0:0: +350,180,107202,2,0,P|403:219|456:217,1,101.999996887207,2|2,3:2|3:2,0:0:0:0: +500,297,107475,1,2,3:2:0:0: +387,370,107611,1,2,3:2:0:0: +387,370,107679,1,2,3:2:0:0: +387,370,107748,2,0,L|252:390,1,101.999996887207,2|2,3:2|3:2,0:0:0:0: +126,374,108020,5,2,3:2:0:0: +139,286,108156,1,2,3:2:0:0: +213,233,108293,1,2,3:2:0:0: +301,247,108429,1,2,3:2:0:0: +267,163,108566,2,0,B|156:202|174:128|41:180,1,203.999993774414,2|8,3:2|2:3,0:0:0:0: +55,35,108975,5,4,2:3:0:0: +44,28,109043,1,4,2:3:0:0: +35,21,109111,2,0,L|153:-5,1,101.999996887207,6|0,3:2|1:1,0:0:0:0: +279,66,109384,2,0,L|378:87,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +474,77,109657,2,0,P|455:30|405:-1,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +357,183,109929,2,0,P|407:185|448:214,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +499,342,110202,6,0,P|458:373|366:367,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +280,304,110475,1,2,3:2:0:0: +280,304,110611,1,2,1:2:0:0: +357,183,110748,2,0,L|343:71,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +209,0,111020,2,0,L|195:112,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +65,166,111293,6,0,P|122:183|157:235,1,101.999996887207,6|2,3:2|3:2,0:0:0:0: +80,384,111566,2,0,P|66:326|93:269,1,101.999996887207,2|2,3:2|3:2,0:0:0:0: +148,213,111839,1,2,3:2:0:0: +269,287,111975,1,2,3:2:0:0: +269,287,112043,1,2,3:2:0:0: +269,287,112111,2,0,L|386:268,1,101.999996887207,2|2,3:2|3:2,0:0:0:0: +369,170,112384,5,8,2:3:0:0: +410,177,112452,1,8,2:3:0:0: +450,164,112520,1,8,2:3:0:0: +478,133,112588,1,8,2:3:0:0: +487,93,112656,1,4,2:3:0:0: +413,21,112793,1,4,2:3:0:0: +371,14,112861,1,4,2:3:0:0: +329,7,112929,1,8,2:3:0:0: +259,85,113066,2,0,L|196:95,6,50.9999984436036,8|8|4|4|4|4|6,2:3|2:3|2:3|2:3|2:3|2:3|3:2,0:0:0:0: +352,256,117839,6,0,P|366:320|331:396,2,136,6|2|2,3:2|1:3|3:3,0:0:0:0: +435,212,118521,1,2,3:2:0:0: +435,212,118657,2,0,P|363:208|306:147,1,136,2|2,1:3|3:3,0:0:0:0: +353,23,119203,1,2,1:3:0:0: +353,23,119339,2,0,L|508:50,1,136,2|2,3:2|3:2,0:0:0:0: +273,80,119748,1,2,1:3:0:0: +90,125,120021,6,0,P|84:60|27:-1,2,136,2|2|2,3:3|1:3|2:3,0:0:0:0: +128,215,120703,1,2,3:2:0:0: +128,215,120839,2,0,P|74:237|59:256,1,68,2|2,1:3|3:2,0:0:0:0: +14,317,121112,2,0,L|25:390,2,68,2|2|2,3:3|3:2|1:3,0:0:0:0: +68,243,121521,2,0,P|141:288|214:276,1,136,2|0,3:2|3:0,0:0:0:0: +267,337,121930,1,2,1:3:0:0: +267,337,122202,6,0,P|231:282|271:168,1,170,6|2,3:2|1:2,0:0:0:0: +252,185,122611,2,0,P|214:243|97:224,1,170,2|2,2:2|3:2,0:0:0:0: +58,185,123021,2,0,P|61:139|92:90,1,85,2|2,1:2|2:2,0:0:0:0: +6,0,123293,6,0,L|102:23,1,85,2|2,3:2|2:2,0:0:0:0: +156,71,123566,2,0,B|186:37|186:37|261:16,1,85,2|2,1:2|3:2,0:0:0:0: +349,103,123839,1,2,2:2:0:0: +375,21,123975,1,2,3:2:0:0: +456,45,124111,2,0,L|472:185,1,127.5,2|0,1:2|0:0,0:0:0:0: +498,203,124384,6,0,P|450:212|405:327,1,170,2|2,3:2|1:2,0:0:0:0: +400,312,124793,1,0,0:0:0:0: +320,342,124930,2,0,P|288:345|244:372,2,56.6666666666667,2|2|2,3:2|2:2|2:2,0:0:0:0: +226,280,125202,2,0,P|199:298|175:343,2,56.6666666666667,2|2|2,1:2|2:2|2:2,0:0:0:0: +165,218,125475,6,0,P|151:188|152:137,2,56.6666666666667,2|2|2,3:2|2:2|2:2,0:0:0:0: +64,166,125748,2,0,P|67:133|94:90,2,56.6666666666667,2|2|2,1:2|2:2|2:2,0:0:0:0: +98,29,126021,2,0,P|65:26|18:45,2,56.6666666666667,2|2|2,3:2|2:2|2:2,0:0:0:0: +168,81,126293,1,2,1:2:0:0: +176,84,126384,2,0,P|208:86|256:67,1,56.6666666666667,2|2,2:2|2:2,0:0:0:0: +294,22,126566,6,0,L|272:227,1,170,6|2,3:2|1:2,0:0:0:0: +269,279,126975,2,0,P|216:221|108:227,1,170,2|2,2:2|3:2,0:0:0:0: +128,216,127384,2,0,P|84:282|118:385,1,170,2|2,1:2|3:2,0:0:0:0: +102,367,127930,6,0,L|211:350,1,85,2|2,1:2|3:2,0:0:0:0: +268,375,128202,2,0,B|286:335|286:335|274:283,1,85,2|2,2:2|3:2,0:0:0:0: +220,230,128475,1,2,1:2:0:0: +246,149,128611,1,2,2:2:0:0: +272,67,128748,6,0,P|269:35|242:-9,2,56.6666666666667,2|2|2,3:2|2:2|2:2,0:0:0:0: +341,119,129021,2,0,P|354:89|353:38,2,56.6666666666667,2|2|2,1:2|2:2|2:2,0:0:0:0: +374,198,129293,2,0,P|400:179|424:134,2,56.6666666666667,2|2|2,3:2|2:2|2:2,0:0:0:0: +363,283,129566,2,0,P|395:280|439:253,2,56.6666666666667,2|2|2,1:2|2:2|2:2,0:0:0:0: +399,365,129839,1,2,3:2:0:0: +363,336,129930,1,2,2:2:0:0: +319,321,130021,1,2,2:2:0:0: +274,327,130111,1,2,1:2:0:0: +233,348,130202,1,2,2:2:0:0: +188,355,130293,1,2,2:2:0:0: +144,341,130384,2,0,P|120:293|207:221,1,170,2|2,3:2|1:2,0:0:0:0: +282,129,130793,5,0,1:1:0:0: +282,129,130861,1,0,1:1:0:0: +282,129,130930,2,0,B|317:20|317:20|237:48,1,170,6|2,3:2|1:2,0:0:0:0: +264,38,131339,2,0,P|186:59|98:14,1,170,2|2,2:2|3:2,0:0:0:0: +107,24,131748,2,0,P|133:66|130:126,1,85,2|0,1:2|2:2,0:0:0:0: +88,171,132021,6,0,P|62:230|115:333,1,170,2|0,3:2|1:1,0:0:0:0: +100,322,132430,2,0,B|51:323|21:355|21:355|63:331|120:358,1,170,2|0,3:2|3:3,0:0:0:0: +100,350,132839,2,0,P|148:352|184:332,1,85,2|0,1:2|2:2,0:0:0:0: +246,281,133111,6,0,L|332:307,1,85,2|0,3:2|0:0,0:0:0:0: +390,362,133384,1,0,1:1:0:0: +472,339,133521,1,2,2:2:0:0: +491,256,133657,1,2,3:2:0:0: +439,188,133793,1,2,3:2:0:0: +420,104,133930,1,2,1:2:0:0: +461,29,134066,1,2,3:2:0:0: +448,181,134202,5,0,3:3:0:0: +381,127,134339,1,2,3:2:0:0: +296,115,134475,1,0,1:1:0:0: +214,139,134611,1,2,3:2:0:0: +164,208,134748,2,0,P|121:226|70:220,1,85,2|0,2:2|3:3,0:0:0:0: +19,113,135021,2,0,P|61:112|99:129,1,85,2|0,1:2|3:3,0:0:0:0: +25,309,135293,6,0,B|122:323|78:369|209:375,1,170,6|0,3:2|1:1,0:0:0:0: +252,328,135702,1,2,2:2:0:0: +252,328,135839,2,0,L|241:241,1,85,2|2,3:2|3:2,0:0:0:0: +175,190,136111,2,0,L|186:103,1,85,2|2,1:2|2:2,0:0:0:0: +138,34,136384,5,2,3:2:0:0: +194,98,136521,1,2,2:2:0:0: +278,109,136657,1,2,1:2:0:0: +360,89,136793,1,2,3:2:0:0: +407,17,136930,5,2,2:2:0:0: +447,139,137066,1,2,3:2:0:0: +367,239,137202,1,2,1:2:0:0: +407,361,137338,1,2,2:2:0:0: +280,384,137475,5,2,3:2:0:0: +194,371,137611,1,2,2:2:0:0: +207,285,137748,1,2,1:2:0:0: +293,298,137884,1,2,2:2:0:0: +198,273,138021,2,0,P|184:301|47:327,1,170,2|2,3:2|1:2,0:0:0:0: +20,80,138566,5,2,3:2:0:0: +67,49,138657,1,2,2:2:0:0: +122,40,138748,1,2,2:2:0:0: +178,47,138839,1,2,1:2:0:0: +221,83,138930,1,2,2:2:0:0: +244,135,139021,1,2,2:2:0:0: +248,190,139111,2,0,P|240:230|225:257,2,56.6666666666667,2|2|2,3:2|2:2|2:2,0:0:0:0: +327,154,139384,6,0,L|485:175,1,127.5,8|4,2:3|2:3,0:0:0:0: +489,146,139657,2,0,P|448:57|374:68,1,170,6|2,3:2|1:2,0:0:0:0: +311,20,140066,2,0,P|284:80|187:82,1,170,2|2,2:2|3:2,0:0:0:0: +118,35,140475,2,0,P|72:33|32:60,1,85,2|2,1:2|2:2,0:0:0:0: +13,133,140748,5,2,3:2:0:0: +93,158,140884,1,2,2:2:0:0: +30,216,141021,1,2,1:2:0:0: +91,338,141157,2,0,B|171:350|171:350|180:362|180:362|285:375,1,170,2|2,3:2|3:2,0:0:0:0: +253,371,141566,2,0,B|265:333|265:333|249:279,1,85,2|2,1:2|2:2,0:0:0:0: +302,220,141839,6,0,P|255:180|262:73,1,170,2|2,3:2|1:2,0:0:0:0: +329,31,142248,1,0,0:0:0:0: +401,75,142384,2,0,L|476:57,2,56.6666666666667,2|2|2,3:2|2:2|2:2,0:0:0:0: +430,153,142657,2,0,L|505:135,2,56.6666666666667,2|2|2,1:2|2:2|2:2,0:0:0:0: +474,226,142930,1,2,3:2:0:0: +433,207,143020,1,2,2:2:0:0: +389,215,143111,1,2,2:2:0:0: +356,246,143202,1,2,1:2:0:0: +347,289,143293,1,2,2:2:0:0: +363,331,143384,1,2,2:2:0:0: +403,353,143475,6,0,L|482:334,1,56.6666666666667,2|2,3:2|2:2,0:0:0:0: +315,310,143657,1,2,2:2:0:0: +303,314,143748,2,0,L|224:333,1,56.6666666666667,2|2,1:2|2:2,0:0:0:0: +152,306,143930,1,2,2:2:0:0: +140,310,144021,6,0,B|90:324|70:373|70:373|26:351|36:287,1,170,6|2,3:2|1:2,0:0:0:0: +34,314,144430,2,0,P|40:249|156:209,1,170,2|2,2:2|3:2,0:0:0:0: +151,40,144839,1,2,1:2:0:0: +151,40,144975,1,2,2:2:0:0: +91,111,145111,6,0,L|0:97,1,85,2|2,3:2|2:2,0:0:0:0: +124,200,145384,2,0,L|215:186,1,85,2|2,1:2|3:2,0:0:0:0: +284,148,145657,1,2,2:2:0:0: +330,77,145793,1,2,3:2:0:0: +412,55,145930,1,2,1:2:0:0: +494,75,146066,1,2,2:2:0:0: +422,196,146202,6,0,B|333:210|378:259|237:279,1,170,2|2,3:2|1:2,0:0:0:0: +273,272,146611,1,2,2:2:0:0: +242,384,146748,2,0,P|204:342|143:323,1,85,2|2,3:2|3:2,0:0:0:0: +33,327,147021,2,0,P|69:305|95:272,1,85,2|2,1:2|3:2,0:0:0:0: +120,188,147293,6,0,L|190:167,2,56.6666666666667,2|2|2,3:2|2:2|2:2,0:0:0:0: +83,110,147566,2,0,L|-14:91,1,85,2|0,1:2|0:0,0:0:0:0: +175,0,147839,1,2,3:2:0:0: +256,22,147975,1,2,1:2:0:0: +195,80,148111,1,2,1:2:0:0: +300,176,148248,5,0,1:1:0:0: +300,176,148316,1,0,1:1:0:0: +300,176,148384,2,0,B|165:59|28:174|28:174|85:282|220:240|220:240|95:264|150:399|277:387|218:278|354:337,1,815.999975097657,6|0,3:2|3:3,0:0:0:0: +416,358,149611,2,0,P|476:322|492:287,2,85,2|2|2,2:2|1:2|3:2,0:0:0:0: +318,324,150021,1,2,2:2:0:0: +318,324,150157,1,2,3:2:0:0: +395,257,150293,2,0,P|383:208|403:147,1,85,2|2,1:2|2:2,0:0:0:0: +502,55,150566,5,2,3:2:0:0: +388,174,150702,1,2,2:2:0:0: +388,174,150839,1,2,1:2:0:0: +354,23,150975,2,0,B|185:40|253:129|72:146|72:146|193:127|252:221|252:221|114:248|122:369,1,713.99997821045,2|0,2:2|1:1,0:0:0:0: +37,281,152066,2,0,P|24:322|28:375,2,85,2|2|2,3:2|2:2|3:2,0:0:0:0: +73,147,152475,2,0,P|120:193|129:237,1,85,2|2,3:2|3:2,0:0:0:0: +211,372,152748,6,0,P|247:328|376:346,1,170,4|2,3:2|1:2,0:0:0:0: +499,342,153157,2,0,L|323:365,1,170,2|2,2:2|3:2,0:0:0:0: +279,292,153566,2,0,L|300:206,1,85,2|2,1:2|2:2,0:0:0:0: +236,151,153839,5,2,3:2:0:0: +299,209,153975,1,2,2:2:0:0: +375,172,154111,1,2,1:2:0:0: +448,128,154248,2,0,B|479:97|461:40|461:40|346:20|305:110,1,255,2|0,3:2|1:1,0:0:0:0: +41,18,154930,5,2,3:2:0:0: +28,61,155020,1,2,2:2:0:0: +40,104,155111,1,2,2:2:0:0: +72,135,155202,1,2,1:2:0:0: +115,146,155293,1,2,2:2:0:0: +158,134,155384,1,2,2:2:0:0: +198,111,155475,1,2,3:2:0:0: +254,104,155565,1,2,2:2:0:0: +309,117,155656,1,2,2:2:0:0: +356,146,155747,1,2,1:2:0:0: +392,190,155838,1,2,2:2:0:0: +411,243,155929,1,2,2:2:0:0: +411,300,156021,6,0,B|389:376|282:346|282:264|334:228|334:228|440:151|406:51|345:3|259:6|200:62|212:132,1,611.999981323243,2|8,3:2|2:3,0:0:0:0: +213,110,156907,1,8,2:3:0:0: +214,120,156975,1,4,2:3:0:0: +215,130,157043,1,4,2:3:0:0: +216,140,157111,6,0,L|79:122,1,101.999996887207,6|0,3:2|2:2,0:0:0:0: +3,253,157384,2,0,L|105:267,1,101.999996887207,2|0,1:2|2:2,0:0:0:0: +124,138,157657,2,0,L|226:152,1,101.999996887207,2|0,3:2|3:3,0:0:0:0: +13,265,157930,2,0,L|115:279,1,101.999996887207,2|0,1:2|2:2,0:0:0:0: +134,150,158202,2,0,L|236:164,1,101.999996887207,2|0,3:2|2:2,0:0:0:0: +23,277,158475,2,0,L|125:291,1,101.999996887207,2|0,1:2|3:3,0:0:0:0: +144,162,158748,2,0,L|246:176,1,101.999996887207,2|0,2:2|3:3,0:0:0:0: +33,289,159021,2,0,L|135:303,1,101.999996887207,2|0,1:2|2:2,0:0:0:0: +154,174,159293,2,0,L|256:188,1,101.999996887207,2|0,3:2|2:2,0:0:0:0: +43,301,159566,2,0,L|145:315,1,101.999996887207,2|0,1:2|2:2,0:0:0:0: +164,186,159839,2,0,L|266:200,1,101.999996887207,2|0,3:2|3:3,0:0:0:0: +53,313,160112,2,0,L|155:327,1,101.999996887207,2|0,1:2|3:3,0:0:0:0: +174,198,160384,2,0,L|276:212,1,101.999996887207,2|0,3:2|3:3,0:0:0:0: +63,325,160657,2,0,L|165:339,1,101.999996887207,2|0,1:2|3:3,0:0:0:0: +184,210,160930,2,0,L|286:224,1,101.999996887207,2|0,2:2|3:3,0:0:0:0: +73,337,161202,2,0,L|175:351,1,101.999996887207,2|0,1:2|3:3,0:0:0:0: +300,105,161475,6,0,L|437:87,1,101.999996887207,6|0,3:2|2:2,0:0:0:0: +512,218,161748,2,0,L|410:231,1,101.999996887207,2|0,1:2|2:2,0:0:0:0: +391,103,162021,2,0,L|289:116,1,101.999996887207,2|0,3:2|3:3,0:0:0:0: +502,230,162294,2,0,L|400:243,1,101.999996887207,2|0,1:2|2:2,0:0:0:0: +381,115,162566,2,0,L|279:128,1,101.999996887207,2|0,3:2|2:2,0:0:0:0: +492,242,162839,2,0,L|390:255,1,101.999996887207,2|0,1:2|3:3,0:0:0:0: +371,127,163112,2,0,L|269:140,1,101.999996887207,2|0,2:2|3:3,0:0:0:0: +482,254,163385,2,0,L|380:267,1,101.999996887207,2|0,1:2|2:2,0:0:0:0: +361,139,163657,2,0,L|259:152,1,101.999996887207,2|0,3:2|2:2,0:0:0:0: +472,266,163930,2,0,L|370:279,1,101.999996887207,2|0,1:2|2:2,0:0:0:0: +351,151,164203,2,0,L|249:164,1,101.999996887207,2|0,3:2|3:3,0:0:0:0: +462,278,164476,2,0,L|360:291,1,101.999996887207,2|0,1:2|3:3,0:0:0:0: +341,163,164748,2,0,L|239:176,1,101.999996887207,2|0,3:2|3:3,0:0:0:0: +452,290,165021,2,0,L|350:303,1,101.999996887207,2|0,1:2|3:3,0:0:0:0: +331,175,165294,2,0,L|229:188,1,101.999996887207,2|0,2:2|3:3,0:0:0:0: +396,99,165566,1,2,1:2:0:0: +216,86,165702,5,0,1:1:0:0: +216,86,165771,1,0,1:1:0:0: +216,86,165839,2,0,L|234:223,1,101.999996887207,6|0,3:2|2:2,0:0:0:0: +103,299,166112,2,0,L|89:197,1,101.999996887207,2|0,1:2|2:2,0:0:0:0: +218,178,166385,2,0,L|204:76,1,101.999996887207,2|0,3:2|3:3,0:0:0:0: +91,289,166658,2,0,L|77:187,1,101.999996887207,2|0,1:2|2:2,0:0:0:0: +206,168,166930,2,0,L|192:66,1,101.999996887207,2|0,3:2|2:2,0:0:0:0: +79,279,167203,2,0,L|65:177,1,101.999996887207,2|0,1:2|3:3,0:0:0:0: +194,158,167476,2,0,L|180:56,1,101.999996887207,2|0,2:2|3:3,0:0:0:0: +67,269,167749,2,0,L|53:167,1,101.999996887207,2|0,1:2|2:2,0:0:0:0: +182,148,168021,2,0,L|168:46,1,101.999996887207,2|0,3:2|2:2,0:0:0:0: +55,259,168294,2,0,L|41:157,1,101.999996887207,2|0,1:2|2:2,0:0:0:0: +170,138,168567,2,0,L|156:36,1,101.999996887207,2|0,3:2|3:3,0:0:0:0: +43,249,168840,2,0,L|29:147,1,101.999996887207,2|0,1:2|3:3,0:0:0:0: +158,128,169112,2,0,L|144:26,1,101.999996887207,2|0,3:2|3:3,0:0:0:0: +31,239,169385,2,0,L|17:137,1,101.999996887207,2|0,1:2|3:3,0:0:0:0: +146,118,169658,2,0,L|132:16,1,101.999996887207,2|0,2:2|3:3,0:0:0:0: +19,229,169930,2,0,L|5:127,1,101.999996887207,2|0,1:2|3:3,0:0:0:0: +280,171,170202,6,0,L|262:308,1,101.999996887207,6|0,3:2|2:2,0:0:0:0: +393,384,170475,2,0,L|407:282,1,101.999996887207,2|0,1:2|2:2,0:0:0:0: +278,263,170748,2,0,L|292:161,1,101.999996887207,2|0,3:2|3:3,0:0:0:0: +405,374,171021,2,0,L|419:272,1,101.999996887207,2|0,1:2|2:2,0:0:0:0: +290,253,171293,2,0,L|304:151,1,101.999996887207,2|0,3:2|2:2,0:0:0:0: +417,364,171566,2,0,L|431:262,1,101.999996887207,2|0,1:2|3:3,0:0:0:0: +302,243,171839,2,0,L|316:141,1,101.999996887207,2|0,2:2|3:3,0:0:0:0: +429,354,172112,2,0,L|443:252,1,101.999996887207,2|0,1:2|2:2,0:0:0:0: +512,181,172384,1,2,3:3:0:0: +512,181,173278,6,0,P|452:146|386:277,1,255,6|0,3:2|0:0,0:0:0:0: +327,334,173722,2,0,L|257:321,5,56.6666666666667,0|0|0|2|2|2,3:3|0:0|0:0|3:2|2:2|2:2,0:0:0:0: +178,230,174166,2,0,L|248:217,5,56.6666666666667,2|2|2|2|2|2,3:2|2:2|2:2|3:2|2:2|2:2,0:0:0:0: +92,334,174611,2,0,L|22:321,2,56.6666666666667,2|2|2,3:2|2:2|2:2,0:0:0:0: +99,348,174833,2,0,L|29:335,2,56.6666666666667,2|0|0,3:2|0:0|0:0,0:0:0:0: +179,312,175055,6,0,P|188:278|169:215,1,85,2|2,3:2|1:2,0:0:0:0: +84,148,175278,1,2,3:2:0:0: +84,148,175389,1,2,1:2:0:0: +84,148,175500,2,0,L|-17:135,1,85,2|2,3:2|1:2,0:0:0:0: +176,61,175722,2,0,L|277:48,1,85,2|2,3:2|1:2,0:0:0:0: +378,32,175944,1,2,3:2:0:0: +359,97,176055,1,0,0:0:0:0: +380,161,176166,1,2,3:2:0:0: +437,198,176278,1,0,0:0:0:0: +504,198,176389,2,0,P|513:147|489:106,1,85,2|0,3:2|0:0,0:0:0:0: +464,293,176611,2,0,P|415:310|391:351,1,85,2|0,3:2|0:0,0:0:0:0: +223,292,176833,6,0,B|246:357|246:357|352:294|309:142,1,255,2|2,3:2|1:2,0:0:0:0: +314,26,177278,1,2,3:2:0:0: +393,73,177389,1,2,1:2:0:0: +393,73,177500,2,0,L|500:51,1,85,2|2,3:2|1:2,0:0:0:0: +238,144,177722,5,2,3:2:0:0: +238,144,177833,1,2,1:2:0:0: +238,144,177944,2,0,L|131:122,1,85,2|2,3:2|1:2,0:0:0:0: +51,179,178166,2,0,P|53:134|32:88,1,85,2|0,3:2|0:0,0:0:0:0: +136,321,178389,2,0,P|134:279|149:240,1,85,2|0,3:2|0:0,0:0:0:0: +311,365,178611,6,0,L|388:385,2,56.6666666666667,2|2|2,3:2|2:2|2:2,0:0:0:0: +361,293,178833,2,0,L|437:271,2,56.6666666666667,2|0|0,3:2|0:0|0:0,0:0:0:0: +368,205,179055,2,0,L|423:148,2,56.6666666666667,2|2|2,3:2|2:2|2:2,0:0:0:0: +330,125,179278,2,0,L|350:47,2,56.6666666666667,2|2|2,3:2|2:2|2:2,0:0:0:0: +442,29,179500,5,2,3:2:0:0: +442,29,179574,1,2,2:2:0:0: +442,29,179648,1,2,2:2:0:0: +442,29,179722,2,0,L|422:106,2,56.6666666666667,2|2|2,3:2|2:2|2:2,0:0:0:0: +488,149,179944,2,0,B|406:177|450:214|340:247,1,170,2|2,3:2|1:2,0:0:0:0: +114,91,180389,6,0,P|80:60|39:51,1,85,6|2,3:2|1:2,0:0:0:0: +0,130,180611,2,0,P|30:160|71:171,1,85,2|2,3:2|1:2,0:0:0:0: +124,301,180833,2,0,L|109:392,1,85,2|2,3:2|1:2,0:0:0:0: +201,378,181055,2,0,L|216:287,1,85,2|2,3:2|1:2,0:0:0:0: +350,243,181278,2,0,L|418:301,1,85,2|2,3:2|1:2,0:0:0:0: +497,261,181500,2,0,L|513:173,2,85,2|2|2,3:2|1:2|3:2,0:0:0:0: +414,298,181833,1,2,1:2:0:0: +414,298,181944,2,0,P|365:311|334:341,1,85,2|0,3:2|0:0,0:0:0:0: +254,216,182166,5,2,3:2:0:0: +186,206,182278,1,2,1:2:0:0: +123,233,182389,1,2,3:2:0:0: +89,291,182500,1,2,1:2:0:0: +101,357,182611,2,0,B|135:293|107:231|93:241|46:187|83:107,1,255,2|0,3:2|1:1,0:0:0:0: +0,29,183055,6,0,P|27:53|84:63,1,85,2|0,3:2|0:0,0:0:0:0: +176,171,183278,2,0,P|210:159|247:115,1,85,2|2,3:2|1:2,0:0:0:0: +353,40,183500,2,0,L|364:155,1,85,2|2,3:2|1:2,0:0:0:0: +473,10,183722,2,0,L|462:125,1,85,2|2,3:2|1:2,0:0:0:0: +447,199,183944,5,2,3:2:0:0: +447,199,184055,1,0,0:0:0:0: +447,199,184166,1,2,3:2:0:0: +463,223,184277,1,0,0:0:0:0: +487,237,184388,2,0,L|476:352,1,85,2|2,3:2|1:2,0:0:0:0: +344,381,184611,2,0,L|333:266,1,85,2|2,3:2|1:2,0:0:0:0: +233,174,184833,6,0,P|186:180|144:208,1,85,2|2,3:2|1:2,0:0:0:0: +19,319,185055,2,0,P|56:339|98:343,1,85,2|2,3:2|1:2,0:0:0:0: +224,268,185278,1,2,3:2:0:0: +229,200,185389,1,2,1:2:0:0: +203,136,185500,1,2,3:2:0:0: +148,95,185611,1,0,0:0:0:0: +80,84,185722,2,0,P|45:119|29:167,1,85,2|0,3:2|0:0,0:0:0:0: +227,49,185944,6,0,L|282:-7,2,56.6666666666667,2|2|2,3:2|2:2|2:2,0:0:0:0: +306,84,186166,2,0,L|382:63,2,56.6666666666667,2|2|2,3:2|2:2|2:2,0:0:0:0: +358,156,186388,2,0,L|434:176,2,56.6666666666667,2|2|2,3:2|2:2|2:2,0:0:0:0: +366,244,186611,2,0,L|423:300,2,56.6666666666667,2|2|2,3:2|2:2|2:2,0:0:0:0: +512,269,186833,5,2,3:2:0:0: +512,269,186907,1,2,2:2:0:0: +512,269,186981,1,2,0:0:0:0: +512,269,187055,2,0,L|455:213,2,56.6666666666667,2|0|0,1:2|0:0|0:0,0:0:0:0: +469,351,187277,2,0,P|423:346|367:392,1,113.333333333333,8|0,2:3|0:0,0:0:0:0: +346,383,187500,6,0,B|296:353|296:353|274:238|376:162,1,255,6|0,3:2|1:1,0:0:0:0: +326,22,187944,1,2,3:2:0:0: +397,68,188055,2,0,P|439:74|505:42,1,85,2|0,1:2|3:3,0:0:0:0: +269,143,188278,1,2,1:2:0:0: +269,143,188389,2,0,P|236:175|218:221,1,85,2|2,3:2|1:2,0:0:0:0: +209,352,188611,6,0,L|109:339,1,85,2|2,3:2|1:2,0:0:0:0: +13,230,188833,2,0,L|113:217,1,85,2|2,3:2|1:2,0:0:0:0: +163,98,189055,2,0,L|63:85,1,85,2|2,3:2|1:2,0:0:0:0: +133,9,189277,6,0,L|217:19,1,85,2|2,3:2|1:2,0:0:0:0: +248,145,189499,2,0,L|288:105,2,56.6666666666667,2|2|2,3:2|2:2|2:2,0:0:0:0: +309,248,189721,2,0,L|323:194,2,56.6666666666667,2|2|2,3:2|2:2|2:2,0:0:0:0: +414,304,189944,2,0,L|399:250,2,56.6666666666667,2|2|2,3:2|2:2|2:2,0:0:0:0: +468,194,190166,6,0,L|488:117,5,56.6666666666667,2|2|2|2|2|2,3:2|2:2|2:2|3:2|2:2|2:2,0:0:0:0: +408,16,190611,2,0,L|423:71,2,56.6666666666667,2|2|2,3:2|2:2|2:2,0:0:0:0: +399,25,190833,2,0,L|413:79,2,56.6666666666667,2|0|0,3:2|0:0|0:0,0:0:0:0: +311,21,191055,6,0,P|386:53|353:174,1,170,2|2,3:2|3:2,0:0:0:0: +272,212,191389,1,2,1:2:0:0: +272,212,191500,2,0,P|303:227|343:276,1,85,2|2,3:2|1:2,0:0:0:0: +461,327,191722,2,0,P|432:346|370:356,1,85,2|2,3:2|1:2,0:0:0:0: +215,380,191944,1,2,3:2:0:0: +189,357,192055,1,2,1:2:0:0: +157,343,192166,1,2,3:2:0:0: +123,340,192277,1,2,1:2:0:0: +89,347,192389,2,0,P|49:335|11:294,1,85,2|0,3:2|1:1,0:0:0:0: +54,172,192611,2,0,P|44:131|60:77,1,85,2|0,3:2|1:0,0:0:0:0: +208,24,192833,2,0,L|193:115,1,85,2|2,3:2|1:2,0:0:0:0: +275,157,193055,2,0,L|290:66,1,85,2|2,3:2|1:2,0:0:0:0: +415,27,193277,5,2,3:2:0:0: +461,98,193389,1,2,1:2:0:0: +458,182,193500,1,2,3:2:0:0: +413,254,193611,1,2,1:2:0:0: +329,269,193722,2,0,P|286:264|227:290,1,85,2|0,3:2|0:0,0:0:0:0: +377,373,193944,2,0,P|420:378|479:352,1,85,2|0,3:2|0:0,0:0:0:0: +491,288,194166,2,0,B|475:189|434:241|422:89,1,170,2|0,3:2|1:1,0:0:0:0: +51,35,194611,6,0,B|97:71|166:63|166:63|220:147|220:147|287:120|391:189,1,340,6|0,3:2|3:3,0:0:0:0: +165,279,195166,1,2,1:2:0:0: +201,189,195277,2,0,P|241:220|260:277,1,85,2|2,3:2|1:2,0:0:0:0: +47,321,195500,2,0,P|53:270|93:225,1,85,2|2,3:2|1:2,0:0:0:0: +238,346,195722,5,2,3:2:0:0: +320,365,195833,1,2,1:2:0:0: +402,345,195944,1,2,3:2:0:0: +462,285,196055,1,2,1:2:0:0: +484,203,196166,2,0,P|479:158|404:126,1,113.333333333333,2|2,3:2|0:0,0:0:0:0: +354,57,196389,6,0,L|361:0,2,56.6666666666667,2|2|2,3:2|2:2|2:2,0:0:0:0: +290,124,196611,2,0,L|297:67,3,56.6666666666667,2|2|2|2,3:2|2:2|2:2|3:2,0:0:0:0: +242,209,196907,2,0,L|234:265,1,56.6666666666667,2|2,2:2|2:2,0:0:0:0: +192,279,197055,2,0,L|199:335,2,56.6666666666667,2|2|2,3:2|2:2|2:2,0:0:0:0: +108,239,197277,2,0,L|52:232,5,56.6666666666667,2|2|2|2|2|2,3:2|2:2|2:2|3:2|2:2|2:2,0:0:0:0: +0,305,197722,2,0,P|65:299|94:417,1,170,2|2,3:2|3:2,0:0:0:0: +391,327,198166,6,0,L|461:316,5,56.6666666666667,2|2|2|2|2|2,3:2|0:2|0:2|3:2|0:2|0:2,0:0:0:0: +317,265,198611,1,2,3:2:0:0: +317,265,198685,1,2,0:2:0:0: +317,265,198759,1,2,0:2:0:0: +317,265,198833,2,0,L|247:254,2,56.6666666666667,2|2|0,3:2|0:0|0:0,0:0:0:0: +392,180,199055,2,0,L|403:110,5,56.6666666666667,2|2|2|2|0|0,3:2|0:2|0:2|3:2|0:0|0:0,0:0:0:0: +494,85,199500,2,0,L|483:15,5,56.6666666666667,2|2|2|2|0|0,3:2|0:2|0:2|3:2|0:0|0:0,0:0:0:0: +400,124,199944,6,0,L|330:113,5,56.6666666666667,2|2|2|2|2|2,3:2|0:2|0:2|3:2|0:2|0:2,0:0:0:0: +267,59,200389,1,2,3:2:0:0: +267,59,200463,1,2,0:2:0:0: +267,59,200537,1,2,0:2:0:0: +267,59,200611,2,0,L|197:70,2,56.6666666666667,2|2|0,3:2|0:0|0:0,0:0:0:0: +121,115,200833,2,0,L|110:45,5,56.6666666666667,2|2|2|2|2|2,3:2|0:2|0:2|3:2|0:2|0:2,0:0:0:0: +179,202,201277,2,0,L|168:272,2,56.6666666666667,2|0|0,1:2|0:0|0:0,0:0:0:0: +67,245,201500,2,0,L|78:315,2,56.6666666666667,8|0|0,2:3|0:0|0:0,0:0:0:0: +11,377,201722,5,4,3:2:0:0: +256,192,201776,12,4,205276,3:2:0:0: +171,17,207943,6,0,L|178:69,31,34,0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0,3:3|0:0|0:0|0:0|0:0|3:3|0:0|0:0|0:0|0:0|3:3|0:0|0:0|0:0|0:0|3:3|0:0|0:0|0:0|0:0|3:3|0:0|0:0|0:0|0:0|3:3|0:0|0:0|0:0|0:0|3:3|0:0,0:0:0:0: +85,45,210124,5,8,3:3:0:0: +73,234,210329,1,4,3:3:0:0: +243,150,210533,1,8,3:3:0:0: +122,74,210670,5,8,3:3:0:0: +61,252,210875,1,4,3:3:0:0: +246,215,211079,1,8,3:3:0:0: +294,296,211215,6,0,L|239:313,3,42.5,14|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +369,234,211488,2,0,L|410:247,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +319,156,211761,2,0,L|307:116,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +221,73,212033,2,0,L|209:114,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +121,141,212306,5,10,3:2:0:0: +112,138,212374,1,0,3:3:0:0: +103,135,212442,1,0,3:3:0:0: +78,40,212579,1,10,3:2:0:0: +87,37,212647,1,0,3:3:0:0: +96,34,212715,1,0,3:3:0:0: +0,115,212851,2,0,L|13:156,2,42.5,10|0|0,3:2|3:3|3:3,0:0:0:0: +77,232,213124,2,0,L|65:273,2,42.5,10|0|0,3:2|3:3|3:3,0:0:0:0: +131,350,213397,6,0,L|172:338,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +261,301,213670,2,0,L|316:318,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +366,247,213942,2,0,L|354:207,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +456,272,214215,2,0,L|444:312,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +490,185,214488,5,10,3:2:0:0: +487,176,214556,1,0,3:3:0:0: +484,167,214624,1,0,3:3:0:0: +419,92,214761,1,10,3:2:0:0: +422,83,214829,1,0,3:3:0:0: +425,74,214897,1,0,3:3:0:0: +344,17,215033,1,10,3:2:0:0: +336,19,215101,1,0,3:3:0:0: +328,21,215170,2,0,L|224:9,1,85,0|10,3:3|3:2,0:0:0:0: +238,216,215579,6,0,L|250:256,3,42.5,14|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +182,318,215852,2,0,L|170:358,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +90,275,216124,2,0,L|49:263,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +51,166,216397,2,0,L|-4:183,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +64,70,216670,5,10,3:2:0:0: +73,73,216738,1,0,3:3:0:0: +82,76,216806,1,0,3:3:0:0: +191,122,216942,1,10,3:2:0:0: +200,119,217010,1,0,3:3:0:0: +209,116,217078,1,0,3:3:0:0: +243,18,217215,2,0,L|298:35,2,42.5,10|0|0,3:2|3:3|3:3,0:0:0:0: +350,113,217488,2,0,L|309:125,2,42.5,10|0|0,3:2|3:3|3:3,0:0:0:0: +425,177,217761,6,0,L|413:217,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +481,279,218034,2,0,L|493:319,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +411,375,218306,2,0,L|370:363,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +328,276,218579,2,0,L|273:293,3,42.5,10|0|0|0,3:2|3:3|3:3|3:3,0:0:0:0: +208,353,218851,5,10,3:2:0:0: +205,362,218919,1,0,3:3:0:0: +202,371,218987,1,0,3:3:0:0: +120,294,219124,1,10,3:2:0:0: +117,285,219192,1,0,3:3:0:0: +114,276,219260,1,0,3:3:0:0: +44,203,219397,2,0,L|55:145,7,42.5,10|0|0|0|0|0|0|0,3:2|3:3|3:3|3:3|3:3|3:3|3:3|3:3,0:0:0:0: +142,171,219943,5,0,1:1:0:0: +146,181,220011,1,0,1:1:0:0: +151,190,220079,2,0,L|202:199,3,42.5,0|0|0|0,1:1|1:1|1:1|1:1,0:0:0:0: +269,153,220352,2,0,L|320:162,1,42.5,0|0,1:1|1:1,0:0:0:0: +320,248,220488,2,0,L|371:257,8,42.5,0|0|0|0|0|0|0|0|0,1:1|1:1|1:1|1:1|1:1|1:1|1:1|1:1|0:0,0:0:0:0: +364,28,222670,6,0,L|424:7,7,42.5,4|8|8|8|4|4|4|4,1:2|2:3|2:3|2:3|2:3|2:3|2:3|2:3,0:0:0:0: +487,58,223215,2,0,L|470:149,1,85,6|2,3:2|1:2,0:0:0:0: +437,312,223488,2,0,L|420:221,1,85,0|2,3:2|1:2,0:0:0:0: +314,245,223761,1,2,3:2:0:0: +240,320,223897,1,2,1:2:0:0: +240,320,223965,1,2,3:2:0:0: +240,320,224033,2,0,L|149:337,1,85,2|2,3:2|1:2,0:0:0:0: +37,266,224306,5,2,3:2:0:0: +37,266,224443,1,2,1:2:0:0: +142,352,224579,2,0,L|225:336,1,85,2|2,3:2|1:2,0:0:0:0: +304,206,224852,2,0,L|288:123,1,85,2|0,3:2|1:1,0:0:0:0: +164,41,225124,2,0,L|172:0,3,42.5,0|0|0|0,3:3|3:3|1:1|3:3,0:0:0:0: +84,68,225397,6,0,P|125:92|149:148,1,85,2|0,3:2|1:1,0:0:0:0: +86,190,225670,2,0,P|45:166|21:110,1,85,2|0,3:2|1:1,0:0:0:0: +39,266,225943,2,0,L|48:358,1,85,2|0,3:2|1:1,0:0:0:0: +137,365,226215,2,0,L|128:273,1,85,2|0,3:2|1:1,0:0:0:0: +237,209,226488,6,0,L|329:218,1,85,2|0,3:2|1:1,0:0:0:0: +361,127,226761,1,2,3:2:0:0: +361,127,226897,1,2,1:2:0:0: +488,185,227033,2,0,L|479:277,1,85,2|0,3:2|1:1,0:0:0:0: +429,362,227306,2,0,L|438:270,1,85,2|0,3:2|1:1,0:0:0:0: +361,127,227579,6,0,P|344:82|354:27,1,85,6|2,3:2|3:2,0:0:0:0: +195,127,227852,2,0,P|196:169|180:208,1,85,0|2,3:3|3:2,0:0:0:0: +211,346,228124,1,2,3:2:0:0: +131,297,228261,1,2,3:2:0:0: +131,297,228329,1,2,3:2:0:0: +131,297,228397,2,0,L|32:288,1,85,2|2,3:2|3:2,0:0:0:0: +67,158,228670,5,8,2:3:0:0: +59,126,228738,1,8,2:3:0:0: +63,92,228806,1,8,2:3:0:0: +79,62,228874,1,8,2:3:0:0: +104,40,228942,1,4,2:3:0:0: +210,91,229079,1,4,2:3:0:0: +224,95,229147,1,4,2:3:0:0: +238,99,229215,2,0,L|186:122,2,42.5,8|8|8,2:3|2:3|2:3,0:0:0:0: +353,24,229488,2,0,L|336:63,1,42.5,4|4,2:3|2:3,0:0:0:0: +425,66,229624,2,0,L|408:105,1,42.5,4|4,2:3|2:3,0:0:0:0: +495,111,229760,5,6,3:2:0:0: +221,375,231943,1,6,3:2:0:0: +102,54,233579,2,0,P|53:62|29:78,5,67.9999979248048,2|2|2|10|10|6,1:2|1:2|1:2|2:3|2:3|2:3,0:0:0:0: +93,147,234124,6,0,P|86:180|62:209,2,67.9999979248048,6|2|2,3:2|2:2|2:2,0:0:0:0: +185,174,234397,2,0,P|196:207|190:244,2,67.9999979248048,10|2|2,2:2|2:2|2:2,0:0:0:0: +257,115,234670,2,0,P|250:82|226:53,2,67.9999979248048,2|2|2,3:2|2:2|2:2,0:0:0:0: +349,88,234943,2,0,P|360:56|354:19,2,67.9999979248048,10|2|2,2:2|2:2|2:2,0:0:0:0: +431,140,235215,5,2,3:2:0:0: +439,130,235306,2,0,L|516:114,1,67.9999979248048,2|2,2:2|2:2,0:0:0:0: +502,215,235488,1,10,2:2:0:0: +460,250,235579,1,2,2:2:0:0: +406,252,235670,1,2,2:2:0:0: +358,230,235760,2,0,P|289:219|204:322,1,203.999993774414,2|10,3:2|2:2,0:0:0:0: +204,309,236306,6,0,L|292:317,2,67.9999979248048,2|2|2,3:2|2:2|2:2,0:0:0:0: +161,221,236579,2,0,L|249:206,2,67.9999979248048,10|2|2,2:2|2:2|2:2,0:0:0:0: +77,165,236852,2,0,L|-11:173,2,67.9999979248048,2|2|2,3:2|2:2|2:2,0:0:0:0: +120,77,237125,2,0,L|32:62,2,67.9999979248048,10|2|2,2:2|2:2|2:2,0:0:0:0: +194,12,237397,5,2,3:2:0:0: +203,22,237488,2,0,P|218:61|208:109,1,67.9999979248048,2|2,2:2|2:2,0:0:0:0: +296,151,237670,1,10,2:2:0:0: +349,144,237760,1,2,2:2:0:0: +391,109,237851,1,2,2:2:0:0: +400,55,237942,2,0,P|349:167|431:250,1,203.999993774414,10|2,3:2|1:2,0:0:0:0: +385,228,238488,6,0,P|371:267|378:322,2,67.9999979248048,6|2|2,3:2|2:2|2:2,0:0:0:0: +276,298,238761,2,0,P|283:339|317:382,2,67.9999979248048,10|2|2,2:2|2:2|2:2,0:0:0:0: +188,248,239033,2,0,P|196:206|229:162,2,67.9999979248048,2|2|2,3:2|2:2|2:2,0:0:0:0: +129,131,239306,2,0,P|156:98|207:77,2,67.9999979248048,10|2|2,2:2|2:2|2:2,0:0:0:0: +38,119,239579,5,2,3:2:0:0: +32,135,239670,2,0,P|35:162|86:218,1,67.9999979248048,2|2,2:2|2:2,0:0:0:0: +20,291,239851,1,10,2:2:0:0: +57,251,239942,1,2,2:2:0:0: +108,235,240033,1,2,2:2:0:0: +161,244,240124,2,0,B|269:295|276:214|401:275,1,203.999993774414,2|10,3:2|2:2,0:0:0:0: +360,258,240670,6,0,L|297:281,2,67.9999979248048,2|2|2,3:2|2:2|2:2,0:0:0:0: +460,308,240942,2,0,L|405:347,2,67.9999979248048,10|2|2,2:2|2:2|2:2,0:0:0:0: +448,213,241215,2,0,L|511:190,2,67.9999979248048,2|2|2,3:2|2:2|2:2,0:0:0:0: +430,114,241488,2,0,L|484:75,2,67.9999979248048,10|2|2,2:2|2:2|2:2,0:0:0:0: +365,38,241760,5,2,3:2:0:0: +354,51,241852,2,0,P|332:92|337:142,1,67.9999979248048,2|2,2:2|2:2,0:0:0:0: +244,165,242033,1,2,1:2:0:0: +191,156,242124,1,2,1:2:0:0: +145,129,242215,1,2,1:2:0:0: +91,133,242306,2,0,B|109:32|109:32|82:-34,2,135.99999584961,6|0|0,1:2|0:0|0:0,0:0:0:0: +33,221,242852,6,0,L|42:273,3,42.5,6|0|8|0,3:2|3:3|3:2|0:0,0:0:0:0: +134,256,243125,2,0,L|125:308,3,42.5,2|0|8|0,3:2|0:0|3:2|0:0,0:0:0:0: +228,299,243397,6,0,L|269:291,1,42.5,2|0,3:2|3:3,0:0:0:0: +251,210,243534,2,0,L|292:202,1,42.5,8|0,3:2|0:0,0:0:0:0: +276,120,243671,2,0,L|317:112,3,42.5,2|0|8|0,3:2|0:0|3:2|0:0,0:0:0:0: +388,48,243943,6,0,L|379:-4,2,42.5,2|0|8,3:2|3:3|3:2,0:0:0:0: +409,139,244216,1,2,3:2:0:0: +407,147,244284,1,0,3:3:0:0: +405,155,244352,1,8,3:2:0:0: +495,191,244489,2,0,L|504:139,2,42.5,2|0|8,3:2|3:3|3:2,0:0:0:0: +426,254,244762,1,2,3:2:0:0: +428,262,244830,1,0,3:3:0:0: +430,270,244898,1,8,3:2:0:0: +370,354,245034,6,0,L|318:363,3,42.5,2|0|8|0,3:2|3:3|3:2|0:0,0:0:0:0: +331,257,245307,2,0,L|279:248,3,42.5,2|0|8|0,3:2|0:0|3:2|0:0,0:0:0:0: +229,187,245579,6,0,L|236:145,1,42.5,2|0,3:2|3:3,0:0:0:0: +140,210,245716,2,0,L|147:168,1,42.5,8|0,3:2|0:0,0:0:0:0: +50,235,245853,2,0,L|57:193,3,42.5,2|0|8|0,3:2|0:0|3:2|0:0,0:0:0:0: +120,299,246124,5,2,3:2:0:0: +122,306,246193,1,0,3:3:0:0: +124,315,246261,1,8,3:2:0:0: +171,218,246397,1,2,3:2:0:0: +173,211,246465,1,0,3:3:0:0: +175,202,246533,1,8,3:2:0:0: +123,119,246670,1,2,3:2:0:0: +125,111,246738,1,0,3:3:0:0: +127,103,246806,2,0,L|116:-1,1,85,8|2,3:2|3:2,0:0:0:0: +289,8,247215,6,0,L|341:17,3,42.5,6|0|8|0,3:2|3:3|3:2|0:0,0:0:0:0: +306,118,247488,2,0,L|358:109,3,42.5,2|0|8|0,3:2|0:0|3:2|0:0,0:0:0:0: +440,82,247761,6,0,L|449:134,1,42.5,2|0,3:2|3:3,0:0:0:0: +425,168,247897,2,0,L|434:220,1,42.5,8|0,3:2|0:0,0:0:0:0: +410,254,248033,2,0,L|419:306,3,42.5,2|0|8|0,3:2|0:0|3:2|0:0,0:0:0:0: +346,361,248306,6,0,L|294:352,2,42.5,2|0|8,3:2|3:3|3:2,0:0:0:0: +287,258,248579,1,2,3:2:0:0: +279,260,248647,1,0,3:3:0:0: +271,262,248715,1,8,3:2:0:0: +193,320,248852,2,0,L|141:329,2,42.5,2|0|8,3:2|3:3|3:2,0:0:0:0: +139,231,249124,1,2,3:2:0:0: +131,229,249194,1,0,3:3:0:0: +123,227,249261,1,8,3:2:0:0: +53,294,249397,6,0,L|62:346,3,42.5,2|0|8|0,3:2|3:3|3:2|0:0,0:0:0:0: +0,214,249670,2,0,L|8:172,3,42.5,2|0|8|0,3:2|0:0|3:2|0:0,0:0:0:0: +41,78,249943,6,0,L|-11:87,1,42.5,2|0,3:2|3:3,0:0:0:0: +127,44,250079,2,0,L|75:53,1,42.5,8|0,3:2|0:0,0:0:0:0: +212,12,250215,2,0,L|160:21,3,42.5,2|0|8|0,3:2|0:0|3:2|0:0,0:0:0:0: +210,113,250488,5,2,3:2:0:0: +212,120,250556,1,0,3:3:0:0: +214,129,250624,1,8,3:2:0:0: +295,186,250761,1,2,3:2:0:0: +293,193,250829,1,0,3:3:0:0: +291,202,250898,1,8,3:2:0:0: +235,284,251033,1,2,3:2:0:0: +237,292,251102,1,0,3:3:0:0: +239,300,251170,2,0,L|229:359,5,42.5,8|0|2|0|8|0,3:2|3:3|3:2|3:3|3:2|3:3,0:0:0:0: +229,205,251579,6,0,P|289:218|332:276,1,101.999996887207,6|0,3:2|1:1,0:0:0:0: +475,279,251852,2,0,P|436:312|386:319,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +440,188,252124,2,0,L|465:84,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +297,1,252397,2,0,L|320:101,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +205,178,252670,6,0,L|105:155,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +42,63,252942,1,2,3:2:0:0: +42,63,253079,1,2,1:2:0:0: +1,237,253215,2,0,P|81:257|129:233,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +248,325,253488,2,0,L|148:348,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +408,308,253760,6,0,P|468:334|493:381,1,101.999996887207,6|2,3:2|3:2,0:0:0:0: +318,250,254033,2,0,P|300:202|310:153,1,101.999996887207,2|2,3:2|3:2,0:0:0:0: +202,8,254306,1,2,3:2:0:0: +295,60,254442,1,2,3:2:0:0: +295,60,254510,1,2,3:2:0:0: +295,60,254579,2,0,L|430:40,1,101.999996887207,2|2,3:2|3:2,0:0:0:0: +486,147,254851,5,2,3:2:0:0: +423,210,254987,1,2,3:2:0:0: +424,300,255124,1,2,3:2:0:0: +487,363,255260,1,2,3:2:0:0: +412,309,255397,2,0,B|317:325|317:325|302:339|302:339|180:354,1,203.999993774414,2|8,3:2|2:3,0:0:0:0: +80,349,255806,5,4,2:3:0:0: +87,359,255874,1,4,2:3:0:0: +94,369,255942,2,0,L|120:251,1,101.999996887207,6|0,3:2|1:1,0:0:0:0: +14,99,256215,2,0,L|40:217,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +172,177,256488,2,0,P|222:174|263:145,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +238,37,256760,2,0,P|188:39|147:68,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +115,269,257033,6,0,P|164:276|205:307,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +342,384,257306,1,2,3:2:0:0: +342,384,257442,1,2,1:2:0:0: +455,305,257579,2,0,L|469:193,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +381,25,257851,2,0,L|395:137,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +267,206,258124,6,0,P|210:189|175:137,1,101.999996887207,6|2,3:2|3:2,0:0:0:0: +95,26,258397,2,0,P|38:43|3:95,1,101.999996887207,2|2,3:2|3:2,0:0:0:0: +101,216,258670,1,2,3:2:0:0: +22,284,258806,1,2,3:2:0:0: +22,284,258874,1,2,3:2:0:0: +22,284,258942,2,0,L|3:401,1,101.999996887207,2|2,3:2|3:2,0:0:0:0: +158,357,259215,5,8,2:3:0:0: +197,374,259283,1,8,2:3:0:0: +239,370,259351,1,8,2:3:0:0: +273,346,259419,1,8,2:3:0:0: +291,309,259487,1,4,2:3:0:0: +405,309,259624,1,4,2:3:0:0: +415,315,259692,1,4,2:3:0:0: +425,321,259761,2,0,L|443:386,2,42.5,8|8|8,2:3|2:3|2:3,0:0:0:0: +355,215,260033,2,0,L|373:150,3,42.5,4|4|4|4,2:3|2:3|2:3|2:3,0:0:0:0: +376,74,260306,6,0,P|316:87|273:145,1,101.999996887207,6|0,3:2|1:1,0:0:0:0: +112,21,260578,2,0,P|151:54|201:61,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +240,204,260851,2,0,L|136:229,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +1,306,261124,2,0,L|101:329,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +296,380,261397,6,0,L|196:357,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +324,269,261669,1,2,3:2:0:0: +324,269,261806,1,2,1:2:0:0: +445,346,261942,2,0,P|465:266|441:218,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +360,112,262215,2,0,P|410:107|456:128,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +274,175,262487,6,0,P|213:148|188:101,1,101.999996887207,6|2,3:2|3:2,0:0:0:0: +38,82,262760,2,0,P|91:43|144:45,1,101.999996887207,2|2,3:2|3:2,0:0:0:0: +194,119,263033,1,2,3:2:0:0: +312,17,263169,1,2,3:2:0:0: +312,17,263237,1,2,3:2:0:0: +312,17,263306,2,0,L|447:37,1,101.999996887207,2|2,3:2|3:2,0:0:0:0: +503,159,263578,5,2,3:2:0:0: +456,234,263714,1,2,3:2:0:0: +367,254,263851,1,2,3:2:0:0: +292,207,263987,1,2,3:2:0:0: +206,230,264124,2,0,B|88:237|134:298|-8:302,1,203.999993774414,2|8,3:2|2:3,0:0:0:0: +173,364,264533,5,4,2:3:0:0: +166,375,264601,1,4,2:3:0:0: +159,384,264669,2,0,L|133:266,1,101.999996887207,6|0,3:2|1:1,0:0:0:0: +302,214,264942,2,0,L|281:313,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +399,384,265215,2,0,P|430:344|432:285,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +496,158,265487,2,0,P|455:187|404:189,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +362,12,265760,6,0,P|411:19|452:50,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +288,107,266033,1,2,3:2:0:0: +288,107,266169,1,2,1:2:0:0: +171,18,266306,2,0,L|157:130,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +251,304,266578,2,0,L|237:192,1,101.999996887207,2|0,3:2|1:1,0:0:0:0: +56,123,266851,6,0,P|68:171|104:206,1,101.999996887207,6|2,3:2|3:2,0:0:0:0: +35,378,267124,2,0,P|21:320|48:263,1,101.999996887207,2|2,3:2|3:2,0:0:0:0: +123,331,267397,1,2,3:2:0:0: +253,263,267533,1,2,3:2:0:0: +253,263,267601,1,2,3:2:0:0: +253,263,267669,2,0,L|370:282,1,101.999996887207,2|2,3:2|3:2,0:0:0:0: +463,369,267942,5,8,2:3:0:0: +489,336,268010,1,8,2:3:0:0: +498,295,268078,1,8,2:3:0:0: +485,256,268146,1,8,2:3:0:0: +455,228,268214,1,4,2:3:0:0: +419,94,268352,1,4,2:3:0:0: +403,133,268420,1,4,2:3:0:0: +372,161,268488,1,8,2:3:0:0: +332,169,268556,1,8,2:3:0:0: +292,157,268624,1,8,2:3:0:0: +231,79,268761,2,0,L|176:72,3,50.9999984436036,4|4|4|4,2:3|2:3|2:3|2:3,0:0:0:0: +96,25,269033,6,0,P|145:65|95:296,1,297.5,6|0,3:2|0:0,0:0:0:0: +121,370,270097,2,0,P|70:261|250:314,1,382.500014591218,6|0,3:2|3:3,0:0:0:0: +319,356,271028,1,0,3:3:0:0: +312,347,271161,6,0,P|281:282|332:105,1,255.000009727478,6|0,1:2|3:3,0:0:0:0: +400,56,271959,1,0,3:3:0:0: +400,56,272225,2,0,L|411:-18,2,56.6666666666667,0|0|0,1:1|1:1|1:1,0:0:0:0: +442,224,272758,2,0,L|453:150,2,56.6666666666667,0|0|0,1:1|1:1|1:1,0:0:0:0: +512,288,273290,6,0,P|443:291|403:383,1,170,6|2,3:2|3:2,0:0:0:0: +303,339,274048,2,0,L|294:254,1,56.6666666666667,0|0,3:3|3:3,0:0:0:0: +202,300,274498,6,0,L|184:260,2,28.3333333333333,0|0|0,1:1|1:1|1:1,0:0:0:0: +105,278,274873,2,0,L|109:235,2,28.3333333333333,8|8|8,2:3|2:3|2:3,0:0:0:0: +31,211,275273,2,0,L|56:176,2,28.3333333333333,4|4|4,2:3|2:3|2:3,0:0:0:0: +0,115,275734,2,0,L|39:97,2,28.3333333333333,4|0|0,2:3|3:3|3:3,0:0:0:0: +21,17,276254,5,6,3:2:0:0: +256,192,276419,12,4,286062,2:3:0:0: +80,113,286725,6,0,B|137:185|228:143|230:143|231:143|330:119|372:183|321:239|260:239|196:214|196:214|299:265|347:186|469:261,1,680,6|4,3:2|3:2,0:0:0:0: diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3689906-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3689906-expected-conversion.json new file mode 100644 index 0000000000..31743d99ac --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3689906-expected-conversion.json @@ -0,0 +1 @@ +{"Mappings":[{"StartTime":390.0,"Objects":[{"StartTime":390.0,"Position":124.0,"HyperDash":false},{"StartTime":480.0,"Position":109.0,"HyperDash":false},{"StartTime":571.0,"Position":124.0,"HyperDash":false},{"StartTime":644.0,"Position":121.0,"HyperDash":false},{"StartTime":753.0,"Position":124.0,"HyperDash":false}]},{"StartTime":935.0,"Objects":[{"StartTime":935.0,"Position":208.0,"HyperDash":false}]},{"StartTime":1117.0,"Objects":[{"StartTime":1117.0,"Position":380.0,"HyperDash":false},{"StartTime":1207.0,"Position":395.0,"HyperDash":false},{"StartTime":1298.0,"Position":380.0,"HyperDash":false},{"StartTime":1371.0,"Position":381.0,"HyperDash":false},{"StartTime":1480.0,"Position":380.0,"HyperDash":false}]},{"StartTime":1844.0,"Objects":[{"StartTime":1844.0,"Position":208.0,"HyperDash":false}]},{"StartTime":2208.0,"Objects":[{"StartTime":2208.0,"Position":360.0,"HyperDash":false}]},{"StartTime":2390.0,"Objects":[{"StartTime":2390.0,"Position":188.0,"HyperDash":false}]},{"StartTime":2480.0,"Objects":[{"StartTime":2480.0,"Position":152.0,"HyperDash":false}]},{"StartTime":2571.0,"Objects":[{"StartTime":2571.0,"Position":112.0,"HyperDash":false},{"StartTime":2643.0,"Position":111.0,"HyperDash":false},{"StartTime":2752.0,"Position":112.0,"HyperDash":false}]},{"StartTime":2935.0,"Objects":[{"StartTime":2935.0,"Position":196.0,"HyperDash":false}]},{"StartTime":3117.0,"Objects":[{"StartTime":3117.0,"Position":280.0,"HyperDash":false}]},{"StartTime":3299.0,"Objects":[{"StartTime":3299.0,"Position":196.0,"HyperDash":false}]},{"StartTime":3480.0,"Objects":[{"StartTime":3480.0,"Position":288.0,"HyperDash":false},{"StartTime":3570.0,"Position":273.0,"HyperDash":false},{"StartTime":3661.0,"Position":288.0,"HyperDash":false},{"StartTime":3734.0,"Position":276.0,"HyperDash":false},{"StartTime":3843.0,"Position":288.0,"HyperDash":false}]},{"StartTime":4026.0,"Objects":[{"StartTime":4026.0,"Position":116.0,"HyperDash":false}]},{"StartTime":4390.0,"Objects":[{"StartTime":4390.0,"Position":300.0,"HyperDash":false}]},{"StartTime":4753.0,"Objects":[{"StartTime":4753.0,"Position":28.0,"HyperDash":false},{"StartTime":4825.0,"Position":24.0,"HyperDash":false},{"StartTime":4934.0,"Position":28.0,"HyperDash":false}]},{"StartTime":5117.0,"Objects":[{"StartTime":5117.0,"Position":112.0,"HyperDash":false}]},{"StartTime":5299.0,"Objects":[{"StartTime":5299.0,"Position":20.0,"HyperDash":false}]},{"StartTime":5480.0,"Objects":[{"StartTime":5480.0,"Position":192.0,"HyperDash":false},{"StartTime":5570.0,"Position":248.148758,"HyperDash":false},{"StartTime":5661.0,"Position":277.0,"HyperDash":false},{"StartTime":5734.0,"Position":247.046844,"HyperDash":false},{"StartTime":5843.0,"Position":192.0,"HyperDash":false}]},{"StartTime":6208.0,"Objects":[{"StartTime":6208.0,"Position":484.0,"HyperDash":false},{"StartTime":6298.0,"Position":475.0,"HyperDash":false},{"StartTime":6389.0,"Position":484.0,"HyperDash":false},{"StartTime":6462.0,"Position":465.0,"HyperDash":false},{"StartTime":6571.0,"Position":484.0,"HyperDash":false}]},{"StartTime":6753.0,"Objects":[{"StartTime":6753.0,"Position":400.0,"HyperDash":false}]},{"StartTime":6935.0,"Objects":[{"StartTime":6935.0,"Position":228.0,"HyperDash":false},{"StartTime":7025.0,"Position":219.0,"HyperDash":false},{"StartTime":7116.0,"Position":228.0,"HyperDash":false},{"StartTime":7189.0,"Position":245.0,"HyperDash":false},{"StartTime":7298.0,"Position":228.0,"HyperDash":false}]},{"StartTime":7662.0,"Objects":[{"StartTime":7662.0,"Position":396.0,"HyperDash":false}]},{"StartTime":8026.0,"Objects":[{"StartTime":8026.0,"Position":244.0,"HyperDash":false}]},{"StartTime":8208.0,"Objects":[{"StartTime":8208.0,"Position":416.0,"HyperDash":false}]},{"StartTime":8298.0,"Objects":[{"StartTime":8298.0,"Position":452.0,"HyperDash":false}]},{"StartTime":8389.0,"Objects":[{"StartTime":8389.0,"Position":492.0,"HyperDash":false},{"StartTime":8461.0,"Position":505.0,"HyperDash":false},{"StartTime":8570.0,"Position":492.0,"HyperDash":false}]},{"StartTime":8753.0,"Objects":[{"StartTime":8753.0,"Position":396.0,"HyperDash":false}]},{"StartTime":8935.0,"Objects":[{"StartTime":8935.0,"Position":304.0,"HyperDash":false}]},{"StartTime":9117.0,"Objects":[{"StartTime":9117.0,"Position":212.0,"HyperDash":false}]},{"StartTime":9298.0,"Objects":[{"StartTime":9298.0,"Position":312.0,"HyperDash":false},{"StartTime":9388.0,"Position":304.0,"HyperDash":false},{"StartTime":9479.0,"Position":312.0,"HyperDash":false},{"StartTime":9552.0,"Position":325.0,"HyperDash":false},{"StartTime":9661.0,"Position":312.0,"HyperDash":false}]},{"StartTime":9844.0,"Objects":[{"StartTime":9844.0,"Position":140.0,"HyperDash":false}]},{"StartTime":10208.0,"Objects":[{"StartTime":10208.0,"Position":324.0,"HyperDash":false}]},{"StartTime":10571.0,"Objects":[{"StartTime":10571.0,"Position":136.0,"HyperDash":false},{"StartTime":10643.0,"Position":164.812149,"HyperDash":false},{"StartTime":10752.0,"Position":221.0,"HyperDash":false}]},{"StartTime":10935.0,"Objects":[{"StartTime":10935.0,"Position":128.0,"HyperDash":false},{"StartTime":11007.0,"Position":165.812149,"HyperDash":false},{"StartTime":11116.0,"Position":213.0,"HyperDash":false}]},{"StartTime":11299.0,"Objects":[{"StartTime":11299.0,"Position":384.0,"HyperDash":false}]},{"StartTime":11480.0,"Objects":[{"StartTime":11480.0,"Position":292.0,"HyperDash":false}]},{"StartTime":11662.0,"Objects":[{"StartTime":11662.0,"Position":200.0,"HyperDash":false}]},{"StartTime":12026.0,"Objects":[{"StartTime":12026.0,"Position":488.0,"HyperDash":false},{"StartTime":12116.0,"Position":473.0,"HyperDash":false},{"StartTime":12207.0,"Position":487.234161,"HyperDash":false},{"StartTime":12280.0,"Position":452.046844,"HyperDash":false},{"StartTime":12389.0,"Position":402.0,"HyperDash":false}]},{"StartTime":12571.0,"Objects":[{"StartTime":12571.0,"Position":316.0,"HyperDash":false}]},{"StartTime":12753.0,"Objects":[{"StartTime":12753.0,"Position":144.0,"HyperDash":false},{"StartTime":12843.0,"Position":158.0,"HyperDash":false},{"StartTime":12934.0,"Position":144.0,"HyperDash":false},{"StartTime":13007.0,"Position":125.0,"HyperDash":false},{"StartTime":13116.0,"Position":144.0,"HyperDash":false}]},{"StartTime":13480.0,"Objects":[{"StartTime":13480.0,"Position":314.0,"HyperDash":false},{"StartTime":13570.0,"Position":255.851257,"HyperDash":false},{"StartTime":13661.0,"Position":229.234161,"HyperDash":false},{"StartTime":13734.0,"Position":212.046844,"HyperDash":false},{"StartTime":13843.0,"Position":144.0,"HyperDash":false}]},{"StartTime":14026.0,"Objects":[{"StartTime":14026.0,"Position":144.0,"HyperDash":false}]},{"StartTime":14208.0,"Objects":[{"StartTime":14208.0,"Position":314.0,"HyperDash":false},{"StartTime":14280.0,"Position":346.812164,"HyperDash":false},{"StartTime":14389.0,"Position":399.0,"HyperDash":false}]},{"StartTime":14571.0,"Objects":[{"StartTime":14571.0,"Position":304.0,"HyperDash":false},{"StartTime":14643.0,"Position":297.0,"HyperDash":false},{"StartTime":14752.0,"Position":304.0,"HyperDash":false}]},{"StartTime":14935.0,"Objects":[{"StartTime":14935.0,"Position":132.0,"HyperDash":false},{"StartTime":15025.0,"Position":88.85124,"HyperDash":false},{"StartTime":15116.0,"Position":48.0,"HyperDash":false},{"StartTime":15189.0,"Position":42.0,"HyperDash":false},{"StartTime":15298.0,"Position":48.0,"HyperDash":false}]},{"StartTime":15480.0,"Objects":[{"StartTime":15480.0,"Position":132.0,"HyperDash":false}]},{"StartTime":15662.0,"Objects":[{"StartTime":15662.0,"Position":304.0,"HyperDash":false}]},{"StartTime":16026.0,"Objects":[{"StartTime":16026.0,"Position":132.0,"HyperDash":false}]},{"StartTime":16390.0,"Objects":[{"StartTime":16390.0,"Position":284.0,"HyperDash":false},{"StartTime":16462.0,"Position":289.0,"HyperDash":false},{"StartTime":16571.0,"Position":284.0,"HyperDash":false}]},{"StartTime":16753.0,"Objects":[{"StartTime":16753.0,"Position":192.0,"HyperDash":false}]},{"StartTime":16935.0,"Objects":[{"StartTime":16935.0,"Position":192.0,"HyperDash":false}]},{"StartTime":17117.0,"Objects":[{"StartTime":17117.0,"Position":364.0,"HyperDash":false},{"StartTime":17207.0,"Position":419.148743,"HyperDash":false},{"StartTime":17298.0,"Position":449.0,"HyperDash":false},{"StartTime":17371.0,"Position":432.046844,"HyperDash":false},{"StartTime":17480.0,"Position":364.0,"HyperDash":false}]},{"StartTime":17844.0,"Objects":[{"StartTime":17844.0,"Position":64.0,"HyperDash":false},{"StartTime":17916.0,"Position":81.0,"HyperDash":false},{"StartTime":18025.0,"Position":64.0,"HyperDash":false}]},{"StartTime":18208.0,"Objects":[{"StartTime":18208.0,"Position":148.0,"HyperDash":false},{"StartTime":18280.0,"Position":163.0,"HyperDash":false},{"StartTime":18389.0,"Position":148.0,"HyperDash":false}]},{"StartTime":18571.0,"Objects":[{"StartTime":18571.0,"Position":320.0,"HyperDash":false}]},{"StartTime":18935.0,"Objects":[{"StartTime":18935.0,"Position":132.0,"HyperDash":false}]},{"StartTime":19299.0,"Objects":[{"StartTime":19299.0,"Position":132.0,"HyperDash":false},{"StartTime":19389.0,"Position":191.148758,"HyperDash":false},{"StartTime":19480.0,"Position":216.765839,"HyperDash":false},{"StartTime":19553.0,"Position":233.953156,"HyperDash":false},{"StartTime":19662.0,"Position":302.0,"HyperDash":false}]},{"StartTime":19844.0,"Objects":[{"StartTime":19844.0,"Position":388.0,"HyperDash":false}]},{"StartTime":20026.0,"Objects":[{"StartTime":20026.0,"Position":216.0,"HyperDash":false},{"StartTime":20098.0,"Position":187.187851,"HyperDash":false},{"StartTime":20207.0,"Position":131.0,"HyperDash":false}]},{"StartTime":20390.0,"Objects":[{"StartTime":20390.0,"Position":224.0,"HyperDash":false},{"StartTime":20462.0,"Position":212.0,"HyperDash":false},{"StartTime":20571.0,"Position":224.0,"HyperDash":false}]},{"StartTime":20753.0,"Objects":[{"StartTime":20753.0,"Position":52.0,"HyperDash":false},{"StartTime":20843.0,"Position":37.0,"HyperDash":false},{"StartTime":20934.0,"Position":52.0,"HyperDash":false},{"StartTime":21007.0,"Position":74.95316,"HyperDash":false},{"StartTime":21116.0,"Position":134.0,"HyperDash":false}]},{"StartTime":21299.0,"Objects":[{"StartTime":21299.0,"Position":224.0,"HyperDash":false}]},{"StartTime":21480.0,"Objects":[{"StartTime":21480.0,"Position":396.0,"HyperDash":false}]},{"StartTime":21844.0,"Objects":[{"StartTime":21844.0,"Position":224.0,"HyperDash":false}]},{"StartTime":22026.0,"Objects":[{"StartTime":22026.0,"Position":132.0,"HyperDash":false}]},{"StartTime":22208.0,"Objects":[{"StartTime":22208.0,"Position":224.0,"HyperDash":false}]},{"StartTime":22299.0,"Objects":[{"StartTime":22299.0,"Position":176.0,"HyperDash":false}]},{"StartTime":22390.0,"Objects":[{"StartTime":22390.0,"Position":132.0,"HyperDash":false}]},{"StartTime":22571.0,"Objects":[{"StartTime":22571.0,"Position":232.0,"HyperDash":false}]},{"StartTime":22753.0,"Objects":[{"StartTime":22753.0,"Position":404.0,"HyperDash":false}]},{"StartTime":22935.0,"Objects":[{"StartTime":22935.0,"Position":232.0,"HyperDash":false},{"StartTime":23007.0,"Position":248.0,"HyperDash":false},{"StartTime":23116.0,"Position":232.0,"HyperDash":false}]},{"StartTime":23299.0,"Objects":[{"StartTime":23299.0,"Position":404.0,"HyperDash":false}]},{"StartTime":23389.0,"Objects":[{"StartTime":23389.0,"Position":448.0,"HyperDash":false}]},{"StartTime":23480.0,"Objects":[{"StartTime":23480.0,"Position":492.0,"HyperDash":true}]},{"StartTime":23662.0,"Objects":[{"StartTime":23662.0,"Position":212.0,"HyperDash":false},{"StartTime":23752.0,"Position":164.4215,"HyperDash":false},{"StartTime":23843.0,"Position":110.280991,"HyperDash":false},{"StartTime":23916.0,"Position":70.25621,"HyperDash":false},{"StartTime":24025.0,"Position":8.0,"HyperDash":false}]},{"StartTime":24208.0,"Objects":[{"StartTime":24208.0,"Position":92.0,"HyperDash":false}]},{"StartTime":24390.0,"Objects":[{"StartTime":24390.0,"Position":272.0,"HyperDash":false},{"StartTime":24462.0,"Position":262.0,"HyperDash":false},{"StartTime":24571.0,"Position":272.0,"HyperDash":false}]},{"StartTime":24753.0,"Objects":[{"StartTime":24753.0,"Position":180.0,"HyperDash":false}]},{"StartTime":25117.0,"Objects":[{"StartTime":25117.0,"Position":348.0,"HyperDash":false},{"StartTime":25189.0,"Position":314.187836,"HyperDash":false},{"StartTime":25298.0,"Position":263.0,"HyperDash":false}]},{"StartTime":25480.0,"Objects":[{"StartTime":25480.0,"Position":355.0,"HyperDash":false}]},{"StartTime":25662.0,"Objects":[{"StartTime":25662.0,"Position":179.0,"HyperDash":false}]},{"StartTime":25752.0,"Objects":[{"StartTime":25752.0,"Position":135.0,"HyperDash":false}]},{"StartTime":25843.0,"Objects":[{"StartTime":25843.0,"Position":91.0,"HyperDash":false},{"StartTime":25933.0,"Position":30.8512421,"HyperDash":false},{"StartTime":26024.0,"Position":6.0,"HyperDash":false},{"StartTime":26097.0,"Position":23.9531631,"HyperDash":false},{"StartTime":26206.0,"Position":91.0,"HyperDash":false}]},{"StartTime":26571.0,"Objects":[{"StartTime":26571.0,"Position":383.0,"HyperDash":false}]},{"StartTime":26753.0,"Objects":[{"StartTime":26753.0,"Position":299.0,"HyperDash":false},{"StartTime":26843.0,"Position":264.851257,"HyperDash":false},{"StartTime":26934.0,"Position":215.0,"HyperDash":false},{"StartTime":27007.0,"Position":195.0,"HyperDash":false},{"StartTime":27116.0,"Position":215.0,"HyperDash":false}]},{"StartTime":27299.0,"Objects":[{"StartTime":27299.0,"Position":391.0,"HyperDash":false}]},{"StartTime":27662.0,"Objects":[{"StartTime":27662.0,"Position":239.0,"HyperDash":false},{"StartTime":27734.0,"Position":234.0,"HyperDash":false},{"StartTime":27843.0,"Position":239.0,"HyperDash":false}]},{"StartTime":28026.0,"Objects":[{"StartTime":28026.0,"Position":323.0,"HyperDash":false}]},{"StartTime":28208.0,"Objects":[{"StartTime":28208.0,"Position":231.0,"HyperDash":false}]},{"StartTime":28390.0,"Objects":[{"StartTime":28390.0,"Position":315.0,"HyperDash":false}]},{"StartTime":28571.0,"Objects":[{"StartTime":28571.0,"Position":143.0,"HyperDash":false}]},{"StartTime":28753.0,"Objects":[{"StartTime":28753.0,"Position":315.0,"HyperDash":false}]},{"StartTime":28935.0,"Objects":[{"StartTime":28935.0,"Position":407.0,"HyperDash":false},{"StartTime":29025.0,"Position":446.57605,"HyperDash":false},{"StartTime":29116.0,"Position":508.0,"HyperDash":false},{"StartTime":29189.0,"Position":506.0,"HyperDash":false},{"StartTime":29298.0,"Position":508.0,"HyperDash":true}]},{"StartTime":29480.0,"Objects":[{"StartTime":29480.0,"Position":212.0,"HyperDash":false},{"StartTime":29570.0,"Position":178.4679,"HyperDash":false},{"StartTime":29661.0,"Position":110.374321,"HyperDash":false},{"StartTime":29752.0,"Position":113.0,"HyperDash":false},{"StartTime":29843.0,"Position":108.0,"HyperDash":false},{"StartTime":29916.0,"Position":158.8,"HyperDash":false},{"StartTime":30025.0,"Position":210.0,"HyperDash":false}]},{"StartTime":30208.0,"Objects":[{"StartTime":30208.0,"Position":304.0,"HyperDash":false},{"StartTime":30298.0,"Position":356.148743,"HyperDash":false},{"StartTime":30389.0,"Position":389.0,"HyperDash":false},{"StartTime":30462.0,"Position":359.046844,"HyperDash":false},{"StartTime":30571.0,"Position":304.0,"HyperDash":false}]},{"StartTime":30935.0,"Objects":[{"StartTime":30935.0,"Position":152.0,"HyperDash":false},{"StartTime":31007.0,"Position":159.0,"HyperDash":false},{"StartTime":31116.0,"Position":152.0,"HyperDash":false}]},{"StartTime":31299.0,"Objects":[{"StartTime":31299.0,"Position":236.0,"HyperDash":false},{"StartTime":31371.0,"Position":252.0,"HyperDash":false},{"StartTime":31480.0,"Position":236.0,"HyperDash":false}]},{"StartTime":31662.0,"Objects":[{"StartTime":31662.0,"Position":320.0,"HyperDash":false},{"StartTime":31752.0,"Position":262.851257,"HyperDash":false},{"StartTime":31843.0,"Position":235.0,"HyperDash":false},{"StartTime":31916.0,"Position":265.953156,"HyperDash":false},{"StartTime":32025.0,"Position":320.0,"HyperDash":false}]},{"StartTime":32390.0,"Objects":[{"StartTime":32390.0,"Position":136.0,"HyperDash":false},{"StartTime":32458.0,"Position":135.0,"HyperDash":false},{"StartTime":32526.0,"Position":346.0,"HyperDash":false},{"StartTime":32594.0,"Position":39.0,"HyperDash":false},{"StartTime":32662.0,"Position":300.0,"HyperDash":false},{"StartTime":32730.0,"Position":398.0,"HyperDash":false},{"StartTime":32798.0,"Position":151.0,"HyperDash":false},{"StartTime":32866.0,"Position":73.0,"HyperDash":false},{"StartTime":32935.0,"Position":311.0,"HyperDash":false},{"StartTime":33003.0,"Position":90.0,"HyperDash":false},{"StartTime":33071.0,"Position":264.0,"HyperDash":false},{"StartTime":33139.0,"Position":477.0,"HyperDash":false},{"StartTime":33207.0,"Position":473.0,"HyperDash":false},{"StartTime":33275.0,"Position":120.0,"HyperDash":false},{"StartTime":33343.0,"Position":115.0,"HyperDash":false},{"StartTime":33411.0,"Position":163.0,"HyperDash":false},{"StartTime":33480.0,"Position":447.0,"HyperDash":false}]},{"StartTime":33844.0,"Objects":[{"StartTime":33844.0,"Position":428.0,"HyperDash":false},{"StartTime":33934.0,"Position":428.0,"HyperDash":false},{"StartTime":34025.0,"Position":428.0,"HyperDash":false}]},{"StartTime":34208.0,"Objects":[{"StartTime":34208.0,"Position":256.0,"HyperDash":false},{"StartTime":34280.0,"Position":207.187851,"HyperDash":false},{"StartTime":34389.0,"Position":171.0,"HyperDash":false}]},{"StartTime":34480.0,"Objects":[{"StartTime":34480.0,"Position":216.0,"HyperDash":false}]},{"StartTime":34571.0,"Objects":[{"StartTime":34571.0,"Position":264.0,"HyperDash":false},{"StartTime":34661.0,"Position":306.5,"HyperDash":false},{"StartTime":34752.0,"Position":264.0,"HyperDash":false}]},{"StartTime":34935.0,"Objects":[{"StartTime":34935.0,"Position":92.0,"HyperDash":false},{"StartTime":35007.0,"Position":54.1878471,"HyperDash":false},{"StartTime":35116.0,"Position":7.0,"HyperDash":true}]},{"StartTime":35299.0,"Objects":[{"StartTime":35299.0,"Position":288.0,"HyperDash":false},{"StartTime":35389.0,"Position":341.578522,"HyperDash":false},{"StartTime":35480.0,"Position":389.719,"HyperDash":false},{"StartTime":35553.0,"Position":430.743774,"HyperDash":false},{"StartTime":35662.0,"Position":492.0,"HyperDash":false}]},{"StartTime":35844.0,"Objects":[{"StartTime":35844.0,"Position":400.0,"HyperDash":false}]},{"StartTime":36026.0,"Objects":[{"StartTime":36026.0,"Position":224.0,"HyperDash":false},{"StartTime":36098.0,"Position":203.187851,"HyperDash":false},{"StartTime":36207.0,"Position":139.0,"HyperDash":false}]},{"StartTime":36390.0,"Objects":[{"StartTime":36390.0,"Position":232.0,"HyperDash":false},{"StartTime":36462.0,"Position":229.0,"HyperDash":false},{"StartTime":36571.0,"Position":232.0,"HyperDash":false}]},{"StartTime":36753.0,"Objects":[{"StartTime":36753.0,"Position":56.0,"HyperDash":false},{"StartTime":36825.0,"Position":72.0,"HyperDash":false},{"StartTime":36934.0,"Position":56.0,"HyperDash":false}]},{"StartTime":37026.0,"Objects":[{"StartTime":37026.0,"Position":104.0,"HyperDash":false}]},{"StartTime":37117.0,"Objects":[{"StartTime":37117.0,"Position":152.0,"HyperDash":false}]},{"StartTime":37299.0,"Objects":[{"StartTime":37299.0,"Position":244.0,"HyperDash":false}]},{"StartTime":37480.0,"Objects":[{"StartTime":37480.0,"Position":152.0,"HyperDash":false},{"StartTime":37552.0,"Position":109.187851,"HyperDash":false},{"StartTime":37661.0,"Position":67.0,"HyperDash":false}]},{"StartTime":37844.0,"Objects":[{"StartTime":37844.0,"Position":244.0,"HyperDash":false},{"StartTime":37916.0,"Position":233.0,"HyperDash":false},{"StartTime":38025.0,"Position":244.0,"HyperDash":false}]},{"StartTime":38208.0,"Objects":[{"StartTime":38208.0,"Position":496.0,"HyperDash":false},{"StartTime":38298.0,"Position":482.0,"HyperDash":false},{"StartTime":38389.0,"Position":495.234161,"HyperDash":false},{"StartTime":38462.0,"Position":471.046844,"HyperDash":false},{"StartTime":38571.0,"Position":410.0,"HyperDash":false}]},{"StartTime":38753.0,"Objects":[{"StartTime":38753.0,"Position":504.0,"HyperDash":false},{"StartTime":38843.0,"Position":480.851257,"HyperDash":false},{"StartTime":38934.0,"Position":419.234161,"HyperDash":false},{"StartTime":39007.0,"Position":379.046844,"HyperDash":false},{"StartTime":39116.0,"Position":334.0,"HyperDash":false}]},{"StartTime":39299.0,"Objects":[{"StartTime":39299.0,"Position":156.0,"HyperDash":false},{"StartTime":39371.0,"Position":128.187851,"HyperDash":false},{"StartTime":39480.0,"Position":71.0,"HyperDash":false}]},{"StartTime":39662.0,"Objects":[{"StartTime":39662.0,"Position":252.0,"HyperDash":false},{"StartTime":39752.0,"Position":294.5,"HyperDash":false},{"StartTime":39843.0,"Position":252.0,"HyperDash":false}]},{"StartTime":40026.0,"Objects":[{"StartTime":40026.0,"Position":71.0,"HyperDash":false},{"StartTime":40098.0,"Position":83.0,"HyperDash":false},{"StartTime":40207.0,"Position":71.0,"HyperDash":false}]},{"StartTime":40390.0,"Objects":[{"StartTime":40390.0,"Position":164.0,"HyperDash":false},{"StartTime":40462.0,"Position":117.187851,"HyperDash":false},{"StartTime":40571.0,"Position":79.0,"HyperDash":false}]},{"StartTime":40753.0,"Objects":[{"StartTime":40753.0,"Position":256.0,"HyperDash":false},{"StartTime":40825.0,"Position":275.812164,"HyperDash":false},{"StartTime":40934.0,"Position":341.0,"HyperDash":false}]},{"StartTime":41117.0,"Objects":[{"StartTime":41117.0,"Position":84.0,"HyperDash":false},{"StartTime":41207.0,"Position":107.148758,"HyperDash":false},{"StartTime":41298.0,"Position":168.765839,"HyperDash":false},{"StartTime":41371.0,"Position":188.953156,"HyperDash":false},{"StartTime":41480.0,"Position":254.0,"HyperDash":false}]},{"StartTime":41662.0,"Objects":[{"StartTime":41662.0,"Position":432.0,"HyperDash":false},{"StartTime":41734.0,"Position":438.0,"HyperDash":false},{"StartTime":41843.0,"Position":432.0,"HyperDash":false}]},{"StartTime":42026.0,"Objects":[{"StartTime":42026.0,"Position":348.0,"HyperDash":false}]},{"StartTime":42208.0,"Objects":[{"StartTime":42208.0,"Position":432.0,"HyperDash":false},{"StartTime":42280.0,"Position":411.187836,"HyperDash":false},{"StartTime":42389.0,"Position":347.0,"HyperDash":false}]},{"StartTime":42571.0,"Objects":[{"StartTime":42571.0,"Position":176.0,"HyperDash":false},{"StartTime":42643.0,"Position":132.187851,"HyperDash":false},{"StartTime":42752.0,"Position":91.0,"HyperDash":false}]},{"StartTime":42844.0,"Objects":[{"StartTime":42844.0,"Position":132.0,"HyperDash":false}]},{"StartTime":42935.0,"Objects":[{"StartTime":42935.0,"Position":176.0,"HyperDash":false}]},{"StartTime":43117.0,"Objects":[{"StartTime":43117.0,"Position":260.0,"HyperDash":false},{"StartTime":43207.0,"Position":210.851242,"HyperDash":false},{"StartTime":43298.0,"Position":175.0,"HyperDash":false},{"StartTime":43371.0,"Position":218.953156,"HyperDash":false},{"StartTime":43480.0,"Position":260.0,"HyperDash":false}]},{"StartTime":43662.0,"Objects":[{"StartTime":43662.0,"Position":84.0,"HyperDash":false},{"StartTime":43734.0,"Position":93.0,"HyperDash":false},{"StartTime":43843.0,"Position":84.0,"HyperDash":false}]},{"StartTime":44026.0,"Objects":[{"StartTime":44026.0,"Position":336.0,"HyperDash":false},{"StartTime":44116.0,"Position":393.578522,"HyperDash":false},{"StartTime":44207.0,"Position":436.0,"HyperDash":false},{"StartTime":44280.0,"Position":442.0,"HyperDash":false},{"StartTime":44389.0,"Position":436.0,"HyperDash":false}]},{"StartTime":44571.0,"Objects":[{"StartTime":44571.0,"Position":344.0,"HyperDash":false}]},{"StartTime":44753.0,"Objects":[{"StartTime":44753.0,"Position":252.0,"HyperDash":false},{"StartTime":44825.0,"Position":246.0,"HyperDash":false},{"StartTime":44934.0,"Position":252.0,"HyperDash":false}]},{"StartTime":45117.0,"Objects":[{"StartTime":45117.0,"Position":428.0,"HyperDash":false},{"StartTime":45189.0,"Position":387.187836,"HyperDash":false},{"StartTime":45298.0,"Position":343.0,"HyperDash":false}]},{"StartTime":45480.0,"Objects":[{"StartTime":45480.0,"Position":164.0,"HyperDash":false}]},{"StartTime":45570.0,"Objects":[{"StartTime":45570.0,"Position":121.0,"HyperDash":false}]},{"StartTime":45661.0,"Objects":[{"StartTime":45661.0,"Position":79.0,"HyperDash":false}]},{"StartTime":45844.0,"Objects":[{"StartTime":45844.0,"Position":256.0,"HyperDash":false},{"StartTime":45916.0,"Position":275.0,"HyperDash":false},{"StartTime":46025.0,"Position":256.0,"HyperDash":false}]},{"StartTime":46208.0,"Objects":[{"StartTime":46208.0,"Position":160.0,"HyperDash":false},{"StartTime":46280.0,"Position":188.812149,"HyperDash":false},{"StartTime":46389.0,"Position":245.0,"HyperDash":false}]},{"StartTime":46571.0,"Objects":[{"StartTime":46571.0,"Position":68.0,"HyperDash":false},{"StartTime":46643.0,"Position":68.0,"HyperDash":false},{"StartTime":46752.0,"Position":68.0,"HyperDash":false}]},{"StartTime":46935.0,"Objects":[{"StartTime":46935.0,"Position":324.0,"HyperDash":false},{"StartTime":47025.0,"Position":381.148743,"HyperDash":false},{"StartTime":47116.0,"Position":409.0,"HyperDash":false},{"StartTime":47189.0,"Position":359.046844,"HyperDash":false},{"StartTime":47298.0,"Position":324.0,"HyperDash":false}]},{"StartTime":47480.0,"Objects":[{"StartTime":47480.0,"Position":154.0,"HyperDash":false},{"StartTime":47570.0,"Position":213.148758,"HyperDash":false},{"StartTime":47661.0,"Position":238.765839,"HyperDash":false},{"StartTime":47734.0,"Position":268.953156,"HyperDash":false},{"StartTime":47843.0,"Position":324.0,"HyperDash":false}]},{"StartTime":48026.0,"Objects":[{"StartTime":48026.0,"Position":420.0,"HyperDash":false},{"StartTime":48098.0,"Position":428.0,"HyperDash":false},{"StartTime":48207.0,"Position":420.0,"HyperDash":false}]},{"StartTime":48390.0,"Objects":[{"StartTime":48390.0,"Position":240.0,"HyperDash":false},{"StartTime":48462.0,"Position":205.187851,"HyperDash":false},{"StartTime":48571.0,"Position":155.0,"HyperDash":false}]},{"StartTime":48662.0,"Objects":[{"StartTime":48662.0,"Position":112.0,"HyperDash":false}]},{"StartTime":48753.0,"Objects":[{"StartTime":48753.0,"Position":68.0,"HyperDash":false}]},{"StartTime":48935.0,"Objects":[{"StartTime":48935.0,"Position":160.0,"HyperDash":false},{"StartTime":49025.0,"Position":132.851242,"HyperDash":false},{"StartTime":49116.0,"Position":75.0,"HyperDash":false},{"StartTime":49189.0,"Position":96.95316,"HyperDash":false},{"StartTime":49298.0,"Position":160.0,"HyperDash":false}]},{"StartTime":49480.0,"Objects":[{"StartTime":49480.0,"Position":336.0,"HyperDash":false},{"StartTime":49552.0,"Position":353.812164,"HyperDash":false},{"StartTime":49661.0,"Position":421.0,"HyperDash":false}]},{"StartTime":49844.0,"Objects":[{"StartTime":49844.0,"Position":164.0,"HyperDash":false},{"StartTime":49916.0,"Position":123.187851,"HyperDash":false},{"StartTime":50025.0,"Position":79.0,"HyperDash":false}]},{"StartTime":50117.0,"Objects":[{"StartTime":50117.0,"Position":79.0,"HyperDash":false}]},{"StartTime":50208.0,"Objects":[{"StartTime":50208.0,"Position":79.0,"HyperDash":false}]},{"StartTime":50390.0,"Objects":[{"StartTime":50390.0,"Position":172.0,"HyperDash":false},{"StartTime":50480.0,"Position":196.148758,"HyperDash":false},{"StartTime":50571.0,"Position":256.0,"HyperDash":false},{"StartTime":50644.0,"Position":261.0,"HyperDash":false},{"StartTime":50753.0,"Position":256.0,"HyperDash":false}]},{"StartTime":50935.0,"Objects":[{"StartTime":50935.0,"Position":80.0,"HyperDash":false},{"StartTime":51007.0,"Position":81.0,"HyperDash":false},{"StartTime":51116.0,"Position":80.0,"HyperDash":false}]},{"StartTime":51299.0,"Objects":[{"StartTime":51299.0,"Position":256.0,"HyperDash":false},{"StartTime":51389.0,"Position":296.148743,"HyperDash":false},{"StartTime":51480.0,"Position":340.765839,"HyperDash":false},{"StartTime":51553.0,"Position":371.953156,"HyperDash":false},{"StartTime":51662.0,"Position":426.0,"HyperDash":false}]},{"StartTime":51844.0,"Objects":[{"StartTime":51844.0,"Position":340.0,"HyperDash":false}]},{"StartTime":52026.0,"Objects":[{"StartTime":52026.0,"Position":426.0,"HyperDash":false},{"StartTime":52098.0,"Position":406.187836,"HyperDash":false},{"StartTime":52207.0,"Position":341.0,"HyperDash":false}]},{"StartTime":52390.0,"Objects":[{"StartTime":52390.0,"Position":164.0,"HyperDash":false},{"StartTime":52462.0,"Position":117.187851,"HyperDash":false},{"StartTime":52571.0,"Position":79.0,"HyperDash":true}]},{"StartTime":52753.0,"Objects":[{"StartTime":52753.0,"Position":336.0,"HyperDash":false},{"StartTime":52843.0,"Position":377.148743,"HyperDash":false},{"StartTime":52934.0,"Position":420.765839,"HyperDash":false},{"StartTime":53007.0,"Position":457.953156,"HyperDash":false},{"StartTime":53116.0,"Position":506.0,"HyperDash":false}]},{"StartTime":53299.0,"Objects":[{"StartTime":53299.0,"Position":328.0,"HyperDash":false},{"StartTime":53389.0,"Position":380.148743,"HyperDash":false},{"StartTime":53480.0,"Position":412.765839,"HyperDash":false},{"StartTime":53553.0,"Position":426.953156,"HyperDash":false},{"StartTime":53662.0,"Position":498.0,"HyperDash":false}]},{"StartTime":53844.0,"Objects":[{"StartTime":53844.0,"Position":412.0,"HyperDash":false},{"StartTime":53916.0,"Position":416.0,"HyperDash":false},{"StartTime":54025.0,"Position":412.0,"HyperDash":false}]},{"StartTime":54208.0,"Objects":[{"StartTime":54208.0,"Position":236.0,"HyperDash":false},{"StartTime":54280.0,"Position":207.187851,"HyperDash":false},{"StartTime":54389.0,"Position":151.0,"HyperDash":false}]},{"StartTime":54480.0,"Objects":[{"StartTime":54480.0,"Position":192.0,"HyperDash":false}]},{"StartTime":54571.0,"Objects":[{"StartTime":54571.0,"Position":236.0,"HyperDash":false}]},{"StartTime":54753.0,"Objects":[{"StartTime":54753.0,"Position":320.0,"HyperDash":false}]},{"StartTime":54935.0,"Objects":[{"StartTime":54935.0,"Position":236.0,"HyperDash":false}]},{"StartTime":55117.0,"Objects":[{"StartTime":55117.0,"Position":152.0,"HyperDash":false}]},{"StartTime":55299.0,"Objects":[{"StartTime":55299.0,"Position":328.0,"HyperDash":false},{"StartTime":55371.0,"Position":328.0,"HyperDash":false},{"StartTime":55480.0,"Position":328.0,"HyperDash":false}]},{"StartTime":55662.0,"Objects":[{"StartTime":55662.0,"Position":72.0,"HyperDash":false},{"StartTime":55734.0,"Position":54.0,"HyperDash":false},{"StartTime":55843.0,"Position":72.0,"HyperDash":false}]},{"StartTime":55935.0,"Objects":[{"StartTime":55935.0,"Position":116.0,"HyperDash":false}]},{"StartTime":56026.0,"Objects":[{"StartTime":56026.0,"Position":160.0,"HyperDash":false}]},{"StartTime":56208.0,"Objects":[{"StartTime":56208.0,"Position":244.0,"HyperDash":false},{"StartTime":56298.0,"Position":182.851242,"HyperDash":false},{"StartTime":56389.0,"Position":159.0,"HyperDash":false},{"StartTime":56462.0,"Position":181.953156,"HyperDash":false},{"StartTime":56571.0,"Position":244.0,"HyperDash":false}]},{"StartTime":56753.0,"Objects":[{"StartTime":56753.0,"Position":72.0,"HyperDash":false},{"StartTime":56825.0,"Position":81.0,"HyperDash":false},{"StartTime":56934.0,"Position":72.0,"HyperDash":false}]},{"StartTime":57117.0,"Objects":[{"StartTime":57117.0,"Position":248.0,"HyperDash":false},{"StartTime":57207.0,"Position":290.5,"HyperDash":false},{"StartTime":57298.0,"Position":248.0,"HyperDash":false}]},{"StartTime":57481.0,"Objects":[{"StartTime":57481.0,"Position":78.0,"HyperDash":false},{"StartTime":57553.0,"Position":71.67611,"HyperDash":false},{"StartTime":57662.0,"Position":79.69966,"HyperDash":false}]},{"StartTime":57844.0,"Objects":[{"StartTime":57844.0,"Position":164.0,"HyperDash":false},{"StartTime":57916.0,"Position":146.187851,"HyperDash":false},{"StartTime":58025.0,"Position":79.0,"HyperDash":false}]},{"StartTime":58208.0,"Objects":[{"StartTime":58208.0,"Position":248.0,"HyperDash":false},{"StartTime":58280.0,"Position":228.187851,"HyperDash":false},{"StartTime":58389.0,"Position":163.0,"HyperDash":false}]},{"StartTime":58571.0,"Objects":[{"StartTime":58571.0,"Position":416.0,"HyperDash":false},{"StartTime":58661.0,"Position":451.148743,"HyperDash":false},{"StartTime":58752.0,"Position":499.234161,"HyperDash":false},{"StartTime":58825.0,"Position":447.046844,"HyperDash":false},{"StartTime":58934.0,"Position":414.0,"HyperDash":false}]},{"StartTime":59117.0,"Objects":[{"StartTime":59117.0,"Position":320.0,"HyperDash":false}]},{"StartTime":59299.0,"Objects":[{"StartTime":59299.0,"Position":140.0,"HyperDash":false},{"StartTime":59389.0,"Position":111.851242,"HyperDash":false},{"StartTime":59480.0,"Position":55.0,"HyperDash":false},{"StartTime":59553.0,"Position":89.95316,"HyperDash":false},{"StartTime":59662.0,"Position":140.0,"HyperDash":false}]},{"StartTime":60026.0,"Objects":[{"StartTime":60026.0,"Position":428.0,"HyperDash":false},{"StartTime":60098.0,"Position":432.0,"HyperDash":false},{"StartTime":60207.0,"Position":428.0,"HyperDash":false}]},{"StartTime":60390.0,"Objects":[{"StartTime":60390.0,"Position":332.0,"HyperDash":false},{"StartTime":60462.0,"Position":362.812164,"HyperDash":false},{"StartTime":60571.0,"Position":417.0,"HyperDash":false}]},{"StartTime":60753.0,"Objects":[{"StartTime":60753.0,"Position":324.0,"HyperDash":false}]},{"StartTime":60843.0,"Objects":[{"StartTime":60843.0,"Position":366.0,"HyperDash":false}]},{"StartTime":60934.0,"Objects":[{"StartTime":60934.0,"Position":409.0,"HyperDash":false}]},{"StartTime":61117.0,"Objects":[{"StartTime":61117.0,"Position":228.0,"HyperDash":false},{"StartTime":61189.0,"Position":181.187851,"HyperDash":false},{"StartTime":61298.0,"Position":143.0,"HyperDash":false}]},{"StartTime":61480.0,"Objects":[{"StartTime":61480.0,"Position":324.0,"HyperDash":false},{"StartTime":61570.0,"Position":323.0,"HyperDash":false},{"StartTime":61661.0,"Position":324.0,"HyperDash":false},{"StartTime":61734.0,"Position":306.0,"HyperDash":false},{"StartTime":61843.0,"Position":324.0,"HyperDash":false}]},{"StartTime":62026.0,"Objects":[{"StartTime":62026.0,"Position":228.0,"HyperDash":false}]},{"StartTime":62208.0,"Objects":[{"StartTime":62208.0,"Position":408.0,"HyperDash":false},{"StartTime":62298.0,"Position":361.851257,"HyperDash":false},{"StartTime":62389.0,"Position":323.0,"HyperDash":false},{"StartTime":62462.0,"Position":339.953156,"HyperDash":false},{"StartTime":62571.0,"Position":408.0,"HyperDash":false}]},{"StartTime":62935.0,"Objects":[{"StartTime":62935.0,"Position":120.0,"HyperDash":false},{"StartTime":63025.0,"Position":77.5,"HyperDash":false},{"StartTime":63116.0,"Position":120.0,"HyperDash":false}]},{"StartTime":63299.0,"Objects":[{"StartTime":63299.0,"Position":216.0,"HyperDash":false},{"StartTime":63371.0,"Position":227.0,"HyperDash":false},{"StartTime":63480.0,"Position":216.0,"HyperDash":false}]},{"StartTime":63662.0,"Objects":[{"StartTime":63662.0,"Position":396.0,"HyperDash":false},{"StartTime":63734.0,"Position":343.187836,"HyperDash":false},{"StartTime":63843.0,"Position":311.0,"HyperDash":false}]},{"StartTime":64026.0,"Objects":[{"StartTime":64026.0,"Position":148.0,"HyperDash":false}]},{"StartTime":64208.0,"Objects":[{"StartTime":64208.0,"Position":320.0,"HyperDash":false}]},{"StartTime":64390.0,"Objects":[{"StartTime":64390.0,"Position":140.0,"HyperDash":false},{"StartTime":64480.0,"Position":114.851242,"HyperDash":false},{"StartTime":64571.0,"Position":56.0,"HyperDash":false},{"StartTime":64644.0,"Position":56.0,"HyperDash":false},{"StartTime":64753.0,"Position":56.0,"HyperDash":false}]},{"StartTime":64935.0,"Objects":[{"StartTime":64935.0,"Position":140.0,"HyperDash":false}]},{"StartTime":65117.0,"Objects":[{"StartTime":65117.0,"Position":396.0,"HyperDash":false},{"StartTime":65189.0,"Position":395.0,"HyperDash":false},{"StartTime":65298.0,"Position":396.0,"HyperDash":false}]},{"StartTime":65480.0,"Objects":[{"StartTime":65480.0,"Position":312.0,"HyperDash":false}]},{"StartTime":65662.0,"Objects":[{"StartTime":65662.0,"Position":404.0,"HyperDash":false}]},{"StartTime":65844.0,"Objects":[{"StartTime":65844.0,"Position":300.0,"HyperDash":false},{"StartTime":65916.0,"Position":278.187836,"HyperDash":false},{"StartTime":66025.0,"Position":215.0,"HyperDash":false}]},{"StartTime":66208.0,"Objects":[{"StartTime":66208.0,"Position":392.0,"HyperDash":false},{"StartTime":66280.0,"Position":394.0,"HyperDash":false},{"StartTime":66389.0,"Position":392.0,"HyperDash":false}]},{"StartTime":66571.0,"Objects":[{"StartTime":66571.0,"Position":136.0,"HyperDash":false},{"StartTime":66643.0,"Position":137.0,"HyperDash":false},{"StartTime":66752.0,"Position":136.0,"HyperDash":false}]},{"StartTime":66935.0,"Objects":[{"StartTime":66935.0,"Position":307.0,"HyperDash":false},{"StartTime":67007.0,"Position":327.812164,"HyperDash":false},{"StartTime":67116.0,"Position":392.0,"HyperDash":false}]},{"StartTime":67299.0,"Objects":[{"StartTime":67299.0,"Position":476.0,"HyperDash":false},{"StartTime":67371.0,"Position":479.0,"HyperDash":false},{"StartTime":67480.0,"Position":476.0,"HyperDash":false}]},{"StartTime":67662.0,"Objects":[{"StartTime":67662.0,"Position":307.0,"HyperDash":false},{"StartTime":67734.0,"Position":295.0,"HyperDash":false},{"StartTime":67843.0,"Position":307.0,"HyperDash":true}]},{"StartTime":68026.0,"Objects":[{"StartTime":68026.0,"Position":48.0,"HyperDash":false},{"StartTime":68098.0,"Position":74.81215,"HyperDash":false},{"StartTime":68207.0,"Position":133.0,"HyperDash":false}]},{"StartTime":68390.0,"Objects":[{"StartTime":68390.0,"Position":307.0,"HyperDash":false},{"StartTime":68462.0,"Position":288.0,"HyperDash":false},{"StartTime":68571.0,"Position":307.0,"HyperDash":false}]},{"StartTime":68753.0,"Objects":[{"StartTime":68753.0,"Position":222.0,"HyperDash":false},{"StartTime":68825.0,"Position":257.812134,"HyperDash":false},{"StartTime":68934.0,"Position":307.0,"HyperDash":false}]},{"StartTime":69117.0,"Objects":[{"StartTime":69117.0,"Position":136.0,"HyperDash":false},{"StartTime":69189.0,"Position":131.0,"HyperDash":false},{"StartTime":69298.0,"Position":136.0,"HyperDash":false}]},{"StartTime":69480.0,"Objects":[{"StartTime":69480.0,"Position":228.0,"HyperDash":false},{"StartTime":69552.0,"Position":175.187851,"HyperDash":false},{"StartTime":69661.0,"Position":143.0,"HyperDash":false}]},{"StartTime":69844.0,"Objects":[{"StartTime":69844.0,"Position":236.0,"HyperDash":false},{"StartTime":69916.0,"Position":254.812164,"HyperDash":false},{"StartTime":70025.0,"Position":321.0,"HyperDash":true}]},{"StartTime":70208.0,"Objects":[{"StartTime":70208.0,"Position":60.0,"HyperDash":false},{"StartTime":70298.0,"Position":66.0,"HyperDash":false},{"StartTime":70389.0,"Position":60.76584,"HyperDash":false},{"StartTime":70462.0,"Position":88.95316,"HyperDash":false},{"StartTime":70571.0,"Position":146.0,"HyperDash":false}]},{"StartTime":70753.0,"Objects":[{"StartTime":70753.0,"Position":232.0,"HyperDash":false}]},{"StartTime":70935.0,"Objects":[{"StartTime":70935.0,"Position":412.0,"HyperDash":false},{"StartTime":71025.0,"Position":356.851257,"HyperDash":false},{"StartTime":71116.0,"Position":327.0,"HyperDash":false},{"StartTime":71189.0,"Position":351.953156,"HyperDash":false},{"StartTime":71298.0,"Position":412.0,"HyperDash":false}]},{"StartTime":71662.0,"Objects":[{"StartTime":71662.0,"Position":124.0,"HyperDash":false},{"StartTime":71734.0,"Position":118.0,"HyperDash":false},{"StartTime":71843.0,"Position":124.0,"HyperDash":false}]},{"StartTime":72026.0,"Objects":[{"StartTime":72026.0,"Position":220.0,"HyperDash":false},{"StartTime":72098.0,"Position":242.812149,"HyperDash":false},{"StartTime":72207.0,"Position":305.0,"HyperDash":false}]},{"StartTime":72389.0,"Objects":[{"StartTime":72389.0,"Position":212.0,"HyperDash":false}]},{"StartTime":72571.0,"Objects":[{"StartTime":72571.0,"Position":316.0,"HyperDash":false}]},{"StartTime":72753.0,"Objects":[{"StartTime":72753.0,"Position":136.0,"HyperDash":false},{"StartTime":72825.0,"Position":102.187851,"HyperDash":false},{"StartTime":72934.0,"Position":51.0,"HyperDash":true}]},{"StartTime":73116.0,"Objects":[{"StartTime":73116.0,"Position":316.0,"HyperDash":false},{"StartTime":73206.0,"Position":344.148743,"HyperDash":false},{"StartTime":73297.0,"Position":400.0,"HyperDash":false},{"StartTime":73370.0,"Position":415.0,"HyperDash":false},{"StartTime":73479.0,"Position":400.0,"HyperDash":false}]},{"StartTime":73662.0,"Objects":[{"StartTime":73662.0,"Position":316.0,"HyperDash":false}]},{"StartTime":73844.0,"Objects":[{"StartTime":73844.0,"Position":144.0,"HyperDash":false}]},{"StartTime":74026.0,"Objects":[{"StartTime":74026.0,"Position":236.0,"HyperDash":false}]},{"StartTime":74208.0,"Objects":[{"StartTime":74208.0,"Position":328.0,"HyperDash":false}]},{"StartTime":74571.0,"Objects":[{"StartTime":74571.0,"Position":56.0,"HyperDash":false}]},{"StartTime":74753.0,"Objects":[{"StartTime":74753.0,"Position":228.0,"HyperDash":false}]},{"StartTime":74935.0,"Objects":[{"StartTime":74935.0,"Position":400.0,"HyperDash":false},{"StartTime":75007.0,"Position":389.0,"HyperDash":false},{"StartTime":75116.0,"Position":400.0,"HyperDash":false}]},{"StartTime":75298.0,"Objects":[{"StartTime":75298.0,"Position":308.0,"HyperDash":false},{"StartTime":75370.0,"Position":335.812164,"HyperDash":false},{"StartTime":75479.0,"Position":393.0,"HyperDash":false}]},{"StartTime":75662.0,"Objects":[{"StartTime":75662.0,"Position":232.0,"HyperDash":false}]},{"StartTime":75844.0,"Objects":[{"StartTime":75844.0,"Position":401.0,"HyperDash":false}]},{"StartTime":76026.0,"Objects":[{"StartTime":76026.0,"Position":224.0,"HyperDash":false},{"StartTime":76116.0,"Position":198.851242,"HyperDash":false},{"StartTime":76207.0,"Position":140.765839,"HyperDash":false},{"StartTime":76280.0,"Position":189.953156,"HyperDash":false},{"StartTime":76389.0,"Position":226.0,"HyperDash":false}]},{"StartTime":76571.0,"Objects":[{"StartTime":76571.0,"Position":312.0,"HyperDash":false}]},{"StartTime":76753.0,"Objects":[{"StartTime":76753.0,"Position":56.0,"HyperDash":false},{"StartTime":76825.0,"Position":74.0,"HyperDash":false},{"StartTime":76934.0,"Position":56.0,"HyperDash":false}]},{"StartTime":77116.0,"Objects":[{"StartTime":77116.0,"Position":140.0,"HyperDash":false}]},{"StartTime":77298.0,"Objects":[{"StartTime":77298.0,"Position":48.0,"HyperDash":false}]},{"StartTime":77480.0,"Objects":[{"StartTime":77480.0,"Position":148.0,"HyperDash":false},{"StartTime":77552.0,"Position":164.812149,"HyperDash":false},{"StartTime":77661.0,"Position":233.0,"HyperDash":false}]},{"StartTime":77844.0,"Objects":[{"StartTime":77844.0,"Position":408.0,"HyperDash":false},{"StartTime":77916.0,"Position":392.0,"HyperDash":false},{"StartTime":78025.0,"Position":408.0,"HyperDash":false}]},{"StartTime":78207.0,"Objects":[{"StartTime":78207.0,"Position":236.0,"HyperDash":false},{"StartTime":78279.0,"Position":281.812164,"HyperDash":false},{"StartTime":78388.0,"Position":321.0,"HyperDash":false}]},{"StartTime":78571.0,"Objects":[{"StartTime":78571.0,"Position":493.0,"HyperDash":false},{"StartTime":78643.0,"Position":471.187836,"HyperDash":false},{"StartTime":78752.0,"Position":408.0,"HyperDash":false}]},{"StartTime":78935.0,"Objects":[{"StartTime":78935.0,"Position":504.0,"HyperDash":false}]},{"StartTime":79117.0,"Objects":[{"StartTime":79117.0,"Position":332.0,"HyperDash":false}]},{"StartTime":79208.0,"Objects":[{"StartTime":79208.0,"Position":284.0,"HyperDash":false}]},{"StartTime":79298.0,"Objects":[{"StartTime":79298.0,"Position":236.0,"HyperDash":false},{"StartTime":79370.0,"Position":251.0,"HyperDash":false},{"StartTime":79479.0,"Position":236.0,"HyperDash":false}]},{"StartTime":79662.0,"Objects":[{"StartTime":79662.0,"Position":60.0,"HyperDash":false},{"StartTime":79734.0,"Position":52.0,"HyperDash":false},{"StartTime":79843.0,"Position":60.0,"HyperDash":false}]},{"StartTime":80026.0,"Objects":[{"StartTime":80026.0,"Position":236.0,"HyperDash":false},{"StartTime":80098.0,"Position":255.812164,"HyperDash":false},{"StartTime":80207.0,"Position":321.0,"HyperDash":false}]},{"StartTime":80389.0,"Objects":[{"StartTime":80389.0,"Position":228.0,"HyperDash":false}]},{"StartTime":80479.0,"Objects":[{"StartTime":80479.0,"Position":228.0,"HyperDash":false}]},{"StartTime":80570.0,"Objects":[{"StartTime":80570.0,"Position":228.0,"HyperDash":false}]},{"StartTime":80753.0,"Objects":[{"StartTime":80753.0,"Position":404.0,"HyperDash":false},{"StartTime":80825.0,"Position":400.0,"HyperDash":false},{"StartTime":80934.0,"Position":404.0,"HyperDash":false}]},{"StartTime":81116.0,"Objects":[{"StartTime":81116.0,"Position":227.0,"HyperDash":false},{"StartTime":81188.0,"Position":273.812164,"HyperDash":false},{"StartTime":81297.0,"Position":312.0,"HyperDash":false}]},{"StartTime":81480.0,"Objects":[{"StartTime":81480.0,"Position":404.0,"HyperDash":false},{"StartTime":81552.0,"Position":369.187836,"HyperDash":false},{"StartTime":81661.0,"Position":319.0,"HyperDash":false}]},{"StartTime":81844.0,"Objects":[{"StartTime":81844.0,"Position":133.0,"HyperDash":false},{"StartTime":81934.0,"Position":90.5,"HyperDash":false},{"StartTime":82025.0,"Position":133.0,"HyperDash":false}]},{"StartTime":82208.0,"Objects":[{"StartTime":82208.0,"Position":303.0,"HyperDash":false},{"StartTime":82280.0,"Position":269.187836,"HyperDash":false},{"StartTime":82389.0,"Position":218.0,"HyperDash":false}]},{"StartTime":82480.0,"Objects":[{"StartTime":82480.0,"Position":264.0,"HyperDash":false}]},{"StartTime":82572.0,"Objects":[{"StartTime":82572.0,"Position":313.0,"HyperDash":false},{"StartTime":82644.0,"Position":272.187836,"HyperDash":false},{"StartTime":82753.0,"Position":228.0,"HyperDash":false}]},{"StartTime":82935.0,"Objects":[{"StartTime":82935.0,"Position":48.0,"HyperDash":false},{"StartTime":83007.0,"Position":97.81215,"HyperDash":false},{"StartTime":83116.0,"Position":133.0,"HyperDash":true}]},{"StartTime":83299.0,"Objects":[{"StartTime":83299.0,"Position":392.0,"HyperDash":false},{"StartTime":83389.0,"Position":451.578522,"HyperDash":false},{"StartTime":83480.0,"Position":493.719,"HyperDash":false},{"StartTime":83553.0,"Position":512.0,"HyperDash":false},{"StartTime":83662.0,"Position":496.0,"HyperDash":false}]},{"StartTime":83753.0,"Objects":[{"StartTime":83753.0,"Position":452.0,"HyperDash":false}]},{"StartTime":83844.0,"Objects":[{"StartTime":83844.0,"Position":408.0,"HyperDash":false}]},{"StartTime":84026.0,"Objects":[{"StartTime":84026.0,"Position":324.0,"HyperDash":false},{"StartTime":84098.0,"Position":308.0,"HyperDash":false},{"StartTime":84207.0,"Position":324.0,"HyperDash":false}]},{"StartTime":84390.0,"Objects":[{"StartTime":84390.0,"Position":152.0,"HyperDash":false},{"StartTime":84480.0,"Position":152.0,"HyperDash":false}]},{"StartTime":84662.0,"Objects":[{"StartTime":84662.0,"Position":248.0,"HyperDash":false}]},{"StartTime":84753.0,"Objects":[{"StartTime":84753.0,"Position":248.0,"HyperDash":false},{"StartTime":84825.0,"Position":213.187851,"HyperDash":false},{"StartTime":84934.0,"Position":163.0,"HyperDash":false}]},{"StartTime":85117.0,"Objects":[{"StartTime":85117.0,"Position":332.0,"HyperDash":false},{"StartTime":85207.0,"Position":332.0,"HyperDash":false},{"StartTime":85298.0,"Position":332.0,"HyperDash":false}]},{"StartTime":85480.0,"Objects":[{"StartTime":85480.0,"Position":244.0,"HyperDash":false}]},{"StartTime":85662.0,"Objects":[{"StartTime":85662.0,"Position":332.0,"HyperDash":false}]},{"StartTime":85844.0,"Objects":[{"StartTime":85844.0,"Position":156.0,"HyperDash":false},{"StartTime":85916.0,"Position":105.187851,"HyperDash":false},{"StartTime":86025.0,"Position":71.0,"HyperDash":false}]},{"StartTime":86208.0,"Objects":[{"StartTime":86208.0,"Position":164.0,"HyperDash":false},{"StartTime":86280.0,"Position":185.812149,"HyperDash":false},{"StartTime":86389.0,"Position":249.0,"HyperDash":false}]},{"StartTime":86571.0,"Objects":[{"StartTime":86571.0,"Position":80.0,"HyperDash":false}]},{"StartTime":86661.0,"Objects":[{"StartTime":86661.0,"Position":122.0,"HyperDash":false}]},{"StartTime":86752.0,"Objects":[{"StartTime":86752.0,"Position":165.0,"HyperDash":false}]},{"StartTime":86935.0,"Objects":[{"StartTime":86935.0,"Position":252.0,"HyperDash":false}]},{"StartTime":87117.0,"Objects":[{"StartTime":87117.0,"Position":156.0,"HyperDash":false}]},{"StartTime":87299.0,"Objects":[{"StartTime":87299.0,"Position":328.0,"HyperDash":false},{"StartTime":87389.0,"Position":328.0,"HyperDash":false}]},{"StartTime":87662.0,"Objects":[{"StartTime":87662.0,"Position":152.0,"HyperDash":false},{"StartTime":87752.0,"Position":109.5,"HyperDash":false},{"StartTime":87843.0,"Position":152.0,"HyperDash":false}]},{"StartTime":88026.0,"Objects":[{"StartTime":88026.0,"Position":236.0,"HyperDash":false},{"StartTime":88098.0,"Position":190.187851,"HyperDash":false},{"StartTime":88207.0,"Position":151.0,"HyperDash":false}]},{"StartTime":88390.0,"Objects":[{"StartTime":88390.0,"Position":328.0,"HyperDash":false},{"StartTime":88462.0,"Position":320.0,"HyperDash":false},{"StartTime":88571.0,"Position":328.0,"HyperDash":false}]},{"StartTime":88753.0,"Objects":[{"StartTime":88753.0,"Position":152.0,"HyperDash":false},{"StartTime":88825.0,"Position":120.187851,"HyperDash":false},{"StartTime":88934.0,"Position":67.0,"HyperDash":false}]},{"StartTime":89117.0,"Objects":[{"StartTime":89117.0,"Position":324.0,"HyperDash":false},{"StartTime":89207.0,"Position":355.148743,"HyperDash":false},{"StartTime":89298.0,"Position":408.765839,"HyperDash":false},{"StartTime":89371.0,"Position":451.953156,"HyperDash":false},{"StartTime":89480.0,"Position":494.0,"HyperDash":false}]},{"StartTime":89571.0,"Objects":[{"StartTime":89571.0,"Position":452.0,"HyperDash":false}]},{"StartTime":89662.0,"Objects":[{"StartTime":89662.0,"Position":408.0,"HyperDash":false}]},{"StartTime":89844.0,"Objects":[{"StartTime":89844.0,"Position":324.0,"HyperDash":false},{"StartTime":89916.0,"Position":314.0,"HyperDash":false},{"StartTime":90025.0,"Position":324.0,"HyperDash":false}]},{"StartTime":90208.0,"Objects":[{"StartTime":90208.0,"Position":148.0,"HyperDash":false},{"StartTime":90298.0,"Position":148.0,"HyperDash":false}]},{"StartTime":90480.0,"Objects":[{"StartTime":90480.0,"Position":232.0,"HyperDash":false}]},{"StartTime":90571.0,"Objects":[{"StartTime":90571.0,"Position":284.0,"HyperDash":false},{"StartTime":90643.0,"Position":299.0,"HyperDash":false},{"StartTime":90752.0,"Position":284.0,"HyperDash":false}]},{"StartTime":90844.0,"Objects":[{"StartTime":90844.0,"Position":236.0,"HyperDash":false},{"StartTime":90916.0,"Position":193.187851,"HyperDash":false},{"StartTime":91025.0,"Position":151.0,"HyperDash":false}]},{"StartTime":91117.0,"Objects":[{"StartTime":91117.0,"Position":152.0,"HyperDash":false}]},{"StartTime":91299.0,"Objects":[{"StartTime":91299.0,"Position":236.0,"HyperDash":false}]},{"StartTime":91480.0,"Objects":[{"StartTime":91480.0,"Position":144.0,"HyperDash":false}]},{"StartTime":91662.0,"Objects":[{"StartTime":91662.0,"Position":320.0,"HyperDash":false},{"StartTime":91734.0,"Position":309.0,"HyperDash":false},{"StartTime":91843.0,"Position":320.0,"HyperDash":false}]},{"StartTime":92026.0,"Objects":[{"StartTime":92026.0,"Position":224.0,"HyperDash":false},{"StartTime":92098.0,"Position":177.187851,"HyperDash":false},{"StartTime":92207.0,"Position":139.0,"HyperDash":false}]},{"StartTime":92299.0,"Objects":[{"StartTime":92299.0,"Position":92.0,"HyperDash":false},{"StartTime":92371.0,"Position":115.812149,"HyperDash":false},{"StartTime":92480.0,"Position":177.0,"HyperDash":false}]},{"StartTime":92571.0,"Objects":[{"StartTime":92571.0,"Position":224.0,"HyperDash":false}]},{"StartTime":92753.0,"Objects":[{"StartTime":92753.0,"Position":132.0,"HyperDash":false},{"StartTime":92825.0,"Position":167.812149,"HyperDash":false},{"StartTime":92934.0,"Position":217.0,"HyperDash":false}]},{"StartTime":93117.0,"Objects":[{"StartTime":93117.0,"Position":392.0,"HyperDash":false},{"StartTime":93189.0,"Position":384.0,"HyperDash":false},{"StartTime":93298.0,"Position":392.0,"HyperDash":false}]},{"StartTime":93480.0,"Objects":[{"StartTime":93480.0,"Position":216.0,"HyperDash":false}]},{"StartTime":93570.0,"Objects":[{"StartTime":93570.0,"Position":173.0,"HyperDash":false}]},{"StartTime":93661.0,"Objects":[{"StartTime":93661.0,"Position":131.0,"HyperDash":false}]},{"StartTime":93844.0,"Objects":[{"StartTime":93844.0,"Position":224.0,"HyperDash":false}]},{"StartTime":93934.0,"Objects":[{"StartTime":93934.0,"Position":181.0,"HyperDash":false}]},{"StartTime":94025.0,"Objects":[{"StartTime":94025.0,"Position":139.0,"HyperDash":false}]},{"StartTime":94208.0,"Objects":[{"StartTime":94208.0,"Position":312.0,"HyperDash":false},{"StartTime":94280.0,"Position":363.812164,"HyperDash":false},{"StartTime":94389.0,"Position":397.0,"HyperDash":false}]},{"StartTime":94571.0,"Objects":[{"StartTime":94571.0,"Position":220.0,"HyperDash":false},{"StartTime":94643.0,"Position":192.187851,"HyperDash":false},{"StartTime":94752.0,"Position":135.0,"HyperDash":false}]},{"StartTime":94935.0,"Objects":[{"StartTime":94935.0,"Position":392.0,"HyperDash":false},{"StartTime":95007.0,"Position":440.812164,"HyperDash":false},{"StartTime":95116.0,"Position":477.0,"HyperDash":false}]},{"StartTime":95299.0,"Objects":[{"StartTime":95299.0,"Position":384.0,"HyperDash":false},{"StartTime":95371.0,"Position":389.0,"HyperDash":false},{"StartTime":95480.0,"Position":384.0,"HyperDash":false}]},{"StartTime":95662.0,"Objects":[{"StartTime":95662.0,"Position":212.0,"HyperDash":false}]},{"StartTime":95844.0,"Objects":[{"StartTime":95844.0,"Position":306.0,"HyperDash":false}]},{"StartTime":96026.0,"Objects":[{"StartTime":96026.0,"Position":477.0,"HyperDash":false},{"StartTime":96098.0,"Position":461.0,"HyperDash":false},{"StartTime":96207.0,"Position":477.0,"HyperDash":false}]},{"StartTime":96390.0,"Objects":[{"StartTime":96390.0,"Position":300.0,"HyperDash":false},{"StartTime":96462.0,"Position":249.187836,"HyperDash":false},{"StartTime":96571.0,"Position":215.0,"HyperDash":false}]},{"StartTime":96753.0,"Objects":[{"StartTime":96753.0,"Position":308.0,"HyperDash":false},{"StartTime":96825.0,"Position":320.0,"HyperDash":false},{"StartTime":96934.0,"Position":308.0,"HyperDash":false}]},{"StartTime":97117.0,"Objects":[{"StartTime":97117.0,"Position":136.0,"HyperDash":false}]},{"StartTime":97299.0,"Objects":[{"StartTime":97299.0,"Position":300.0,"HyperDash":false}]},{"StartTime":97480.0,"Objects":[{"StartTime":97480.0,"Position":128.0,"HyperDash":false},{"StartTime":97552.0,"Position":135.0,"HyperDash":false},{"StartTime":97661.0,"Position":128.0,"HyperDash":false}]},{"StartTime":97844.0,"Objects":[{"StartTime":97844.0,"Position":300.0,"HyperDash":false},{"StartTime":97916.0,"Position":248.187836,"HyperDash":false},{"StartTime":98025.0,"Position":215.0,"HyperDash":false}]},{"StartTime":98208.0,"Objects":[{"StartTime":98208.0,"Position":308.0,"HyperDash":false}]},{"StartTime":98298.0,"Objects":[{"StartTime":98298.0,"Position":308.0,"HyperDash":false}]},{"StartTime":98389.0,"Objects":[{"StartTime":98389.0,"Position":308.0,"HyperDash":false}]},{"StartTime":98571.0,"Objects":[{"StartTime":98571.0,"Position":136.0,"HyperDash":false},{"StartTime":98643.0,"Position":173.812149,"HyperDash":false},{"StartTime":98752.0,"Position":221.0,"HyperDash":false}]},{"StartTime":98935.0,"Objects":[{"StartTime":98935.0,"Position":404.0,"HyperDash":false},{"StartTime":99007.0,"Position":405.0,"HyperDash":false},{"StartTime":99116.0,"Position":404.0,"HyperDash":false}]},{"StartTime":99299.0,"Objects":[{"StartTime":99299.0,"Position":224.0,"HyperDash":false},{"StartTime":99371.0,"Position":198.187851,"HyperDash":false},{"StartTime":99480.0,"Position":139.0,"HyperDash":false}]},{"StartTime":99662.0,"Objects":[{"StartTime":99662.0,"Position":312.0,"HyperDash":false},{"StartTime":99734.0,"Position":297.0,"HyperDash":false},{"StartTime":99843.0,"Position":312.0,"HyperDash":false}]},{"StartTime":100026.0,"Objects":[{"StartTime":100026.0,"Position":220.0,"HyperDash":false}]},{"StartTime":100208.0,"Objects":[{"StartTime":100208.0,"Position":312.0,"HyperDash":false}]},{"StartTime":100390.0,"Objects":[{"StartTime":100390.0,"Position":136.0,"HyperDash":false},{"StartTime":100462.0,"Position":98.18785,"HyperDash":false},{"StartTime":100571.0,"Position":51.0,"HyperDash":true}]},{"StartTime":100753.0,"Objects":[{"StartTime":100753.0,"Position":308.0,"HyperDash":false},{"StartTime":100825.0,"Position":340.812164,"HyperDash":false},{"StartTime":100934.0,"Position":393.0,"HyperDash":false}]},{"StartTime":101117.0,"Objects":[{"StartTime":101117.0,"Position":216.0,"HyperDash":false},{"StartTime":101189.0,"Position":223.0,"HyperDash":false},{"StartTime":101298.0,"Position":216.0,"HyperDash":false}]},{"StartTime":101480.0,"Objects":[{"StartTime":101480.0,"Position":300.0,"HyperDash":false}]},{"StartTime":101662.0,"Objects":[{"StartTime":101662.0,"Position":208.0,"HyperDash":false}]},{"StartTime":101844.0,"Objects":[{"StartTime":101844.0,"Position":384.0,"HyperDash":false},{"StartTime":101916.0,"Position":372.0,"HyperDash":false},{"StartTime":102025.0,"Position":384.0,"HyperDash":false}]},{"StartTime":102208.0,"Objects":[{"StartTime":102208.0,"Position":208.0,"HyperDash":false},{"StartTime":102280.0,"Position":181.187851,"HyperDash":false},{"StartTime":102389.0,"Position":123.0,"HyperDash":false}]},{"StartTime":102571.0,"Objects":[{"StartTime":102571.0,"Position":216.0,"HyperDash":false},{"StartTime":102643.0,"Position":214.0,"HyperDash":false},{"StartTime":102752.0,"Position":216.0,"HyperDash":false}]},{"StartTime":102935.0,"Objects":[{"StartTime":102935.0,"Position":52.0,"HyperDash":false}]},{"StartTime":103117.0,"Objects":[{"StartTime":103117.0,"Position":224.0,"HyperDash":false}]},{"StartTime":103299.0,"Objects":[{"StartTime":103299.0,"Position":44.0,"HyperDash":false},{"StartTime":103371.0,"Position":43.0,"HyperDash":false},{"StartTime":103480.0,"Position":44.0,"HyperDash":false}]},{"StartTime":103662.0,"Objects":[{"StartTime":103662.0,"Position":136.0,"HyperDash":false},{"StartTime":103734.0,"Position":162.812149,"HyperDash":false},{"StartTime":103843.0,"Position":221.0,"HyperDash":false}]},{"StartTime":103935.0,"Objects":[{"StartTime":103935.0,"Position":268.0,"HyperDash":false}]},{"StartTime":104026.0,"Objects":[{"StartTime":104026.0,"Position":316.0,"HyperDash":false},{"StartTime":104098.0,"Position":314.0,"HyperDash":false},{"StartTime":104207.0,"Position":316.0,"HyperDash":false}]},{"StartTime":104390.0,"Objects":[{"StartTime":104390.0,"Position":140.0,"HyperDash":false},{"StartTime":104462.0,"Position":188.812149,"HyperDash":false},{"StartTime":104571.0,"Position":225.0,"HyperDash":false}]},{"StartTime":104753.0,"Objects":[{"StartTime":104753.0,"Position":400.0,"HyperDash":false},{"StartTime":104825.0,"Position":417.0,"HyperDash":false},{"StartTime":104934.0,"Position":400.0,"HyperDash":false}]},{"StartTime":105117.0,"Objects":[{"StartTime":105117.0,"Position":224.0,"HyperDash":false}]},{"StartTime":105207.0,"Objects":[{"StartTime":105207.0,"Position":181.0,"HyperDash":false}]},{"StartTime":105298.0,"Objects":[{"StartTime":105298.0,"Position":139.0,"HyperDash":false}]},{"StartTime":105480.0,"Objects":[{"StartTime":105480.0,"Position":309.0,"HyperDash":false},{"StartTime":105552.0,"Position":259.187836,"HyperDash":false},{"StartTime":105661.0,"Position":224.0,"HyperDash":false}]},{"StartTime":105844.0,"Objects":[{"StartTime":105844.0,"Position":128.0,"HyperDash":false}]},{"StartTime":106026.0,"Objects":[{"StartTime":106026.0,"Position":216.0,"HyperDash":false}]},{"StartTime":106208.0,"Objects":[{"StartTime":106208.0,"Position":393.0,"HyperDash":false},{"StartTime":106280.0,"Position":408.812164,"HyperDash":false},{"StartTime":106389.0,"Position":478.0,"HyperDash":true}]},{"StartTime":106571.0,"Objects":[{"StartTime":106571.0,"Position":216.0,"HyperDash":false},{"StartTime":106643.0,"Position":194.187851,"HyperDash":false},{"StartTime":106752.0,"Position":131.0,"HyperDash":false}]},{"StartTime":106844.0,"Objects":[{"StartTime":106844.0,"Position":84.0,"HyperDash":false}]},{"StartTime":106935.0,"Objects":[{"StartTime":106935.0,"Position":131.0,"HyperDash":false},{"StartTime":107007.0,"Position":171.812149,"HyperDash":false},{"StartTime":107116.0,"Position":216.0,"HyperDash":false}]},{"StartTime":107299.0,"Objects":[{"StartTime":107299.0,"Position":312.0,"HyperDash":false}]},{"StartTime":107480.0,"Objects":[{"StartTime":107480.0,"Position":212.0,"HyperDash":false}]},{"StartTime":107662.0,"Objects":[{"StartTime":107662.0,"Position":392.0,"HyperDash":false},{"StartTime":107734.0,"Position":372.0,"HyperDash":false},{"StartTime":107843.0,"Position":392.0,"HyperDash":false}]},{"StartTime":108026.0,"Objects":[{"StartTime":108026.0,"Position":136.0,"HyperDash":false},{"StartTime":108098.0,"Position":101.187851,"HyperDash":false},{"StartTime":108207.0,"Position":51.0,"HyperDash":false}]},{"StartTime":108390.0,"Objects":[{"StartTime":108390.0,"Position":144.0,"HyperDash":false},{"StartTime":108462.0,"Position":129.0,"HyperDash":false},{"StartTime":108571.0,"Position":144.0,"HyperDash":false}]},{"StartTime":108753.0,"Objects":[{"StartTime":108753.0,"Position":304.0,"HyperDash":false}]},{"StartTime":108935.0,"Objects":[{"StartTime":108935.0,"Position":140.0,"HyperDash":false}]},{"StartTime":109117.0,"Objects":[{"StartTime":109117.0,"Position":312.0,"HyperDash":false},{"StartTime":109189.0,"Position":293.0,"HyperDash":false},{"StartTime":109298.0,"Position":312.0,"HyperDash":false}]},{"StartTime":109480.0,"Objects":[{"StartTime":109480.0,"Position":56.0,"HyperDash":false},{"StartTime":109552.0,"Position":60.0,"HyperDash":false},{"StartTime":109661.0,"Position":56.0,"HyperDash":false}]},{"StartTime":109844.0,"Objects":[{"StartTime":109844.0,"Position":140.0,"HyperDash":false}]},{"StartTime":109934.0,"Objects":[{"StartTime":109934.0,"Position":182.0,"HyperDash":false}]},{"StartTime":110025.0,"Objects":[{"StartTime":110025.0,"Position":225.0,"HyperDash":false}]},{"StartTime":110208.0,"Objects":[{"StartTime":110208.0,"Position":56.0,"HyperDash":false}]},{"StartTime":110390.0,"Objects":[{"StartTime":110390.0,"Position":152.0,"HyperDash":false}]},{"StartTime":110571.0,"Objects":[{"StartTime":110571.0,"Position":52.0,"HyperDash":false},{"StartTime":110643.0,"Position":54.0,"HyperDash":false},{"StartTime":110752.0,"Position":52.0,"HyperDash":true}]},{"StartTime":110935.0,"Objects":[{"StartTime":110935.0,"Position":312.0,"HyperDash":false},{"StartTime":111007.0,"Position":362.812164,"HyperDash":false},{"StartTime":111116.0,"Position":397.0,"HyperDash":false}]},{"StartTime":111299.0,"Objects":[{"StartTime":111299.0,"Position":304.0,"HyperDash":false}]},{"StartTime":111480.0,"Objects":[{"StartTime":111480.0,"Position":404.0,"HyperDash":false}]},{"StartTime":111662.0,"Objects":[{"StartTime":111662.0,"Position":312.0,"HyperDash":false}]},{"StartTime":111752.0,"Objects":[{"StartTime":111752.0,"Position":269.0,"HyperDash":false}]},{"StartTime":111843.0,"Objects":[{"StartTime":111843.0,"Position":227.0,"HyperDash":false}]},{"StartTime":112026.0,"Objects":[{"StartTime":112026.0,"Position":328.0,"HyperDash":false},{"StartTime":112098.0,"Position":339.0,"HyperDash":false},{"StartTime":112207.0,"Position":328.0,"HyperDash":true}]},{"StartTime":112390.0,"Objects":[{"StartTime":112390.0,"Position":68.0,"HyperDash":false},{"StartTime":112462.0,"Position":70.0,"HyperDash":false},{"StartTime":112571.0,"Position":68.0,"HyperDash":false}]},{"StartTime":112753.0,"Objects":[{"StartTime":112753.0,"Position":160.0,"HyperDash":false},{"StartTime":112825.0,"Position":201.812149,"HyperDash":false},{"StartTime":112934.0,"Position":245.0,"HyperDash":false}]},{"StartTime":113117.0,"Objects":[{"StartTime":113117.0,"Position":420.0,"HyperDash":false},{"StartTime":113189.0,"Position":413.0,"HyperDash":false},{"StartTime":113298.0,"Position":420.0,"HyperDash":false}]},{"StartTime":113480.0,"Objects":[{"StartTime":113480.0,"Position":328.0,"HyperDash":false}]},{"StartTime":113570.0,"Objects":[{"StartTime":113570.0,"Position":285.0,"HyperDash":false}]},{"StartTime":113661.0,"Objects":[{"StartTime":113661.0,"Position":243.0,"HyperDash":false}]},{"StartTime":113844.0,"Objects":[{"StartTime":113844.0,"Position":492.0,"HyperDash":false},{"StartTime":113916.0,"Position":493.0,"HyperDash":false},{"StartTime":114025.0,"Position":492.0,"HyperDash":false}]},{"StartTime":114208.0,"Objects":[{"StartTime":114208.0,"Position":396.0,"HyperDash":false},{"StartTime":114280.0,"Position":346.187836,"HyperDash":false},{"StartTime":114389.0,"Position":311.0,"HyperDash":false}]},{"StartTime":114571.0,"Objects":[{"StartTime":114571.0,"Position":140.0,"HyperDash":false}]},{"StartTime":114753.0,"Objects":[{"StartTime":114753.0,"Position":311.0,"HyperDash":false}]},{"StartTime":114935.0,"Objects":[{"StartTime":114935.0,"Position":140.0,"HyperDash":false},{"StartTime":115007.0,"Position":121.0,"HyperDash":false},{"StartTime":115116.0,"Position":140.0,"HyperDash":false}]},{"StartTime":115299.0,"Objects":[{"StartTime":115299.0,"Position":396.0,"HyperDash":false},{"StartTime":115371.0,"Position":409.812164,"HyperDash":false},{"StartTime":115480.0,"Position":481.0,"HyperDash":false}]},{"StartTime":115662.0,"Objects":[{"StartTime":115662.0,"Position":308.0,"HyperDash":false},{"StartTime":115734.0,"Position":311.0,"HyperDash":false},{"StartTime":115843.0,"Position":308.0,"HyperDash":false}]},{"StartTime":116026.0,"Objects":[{"StartTime":116026.0,"Position":136.0,"HyperDash":false}]},{"StartTime":116208.0,"Objects":[{"StartTime":116208.0,"Position":228.0,"HyperDash":false}]},{"StartTime":116390.0,"Objects":[{"StartTime":116390.0,"Position":56.0,"HyperDash":false},{"StartTime":116462.0,"Position":56.0,"HyperDash":false},{"StartTime":116571.0,"Position":56.0,"HyperDash":false}]},{"StartTime":116753.0,"Objects":[{"StartTime":116753.0,"Position":312.0,"HyperDash":false},{"StartTime":116825.0,"Position":322.0,"HyperDash":false},{"StartTime":116934.0,"Position":312.0,"HyperDash":false}]},{"StartTime":117117.0,"Objects":[{"StartTime":117117.0,"Position":484.0,"HyperDash":false},{"StartTime":117207.0,"Position":484.0,"HyperDash":false},{"StartTime":117298.0,"Position":484.0,"HyperDash":false}]},{"StartTime":117480.0,"Objects":[{"StartTime":117480.0,"Position":392.0,"HyperDash":false}]},{"StartTime":117662.0,"Objects":[{"StartTime":117662.0,"Position":476.0,"HyperDash":false}]},{"StartTime":117844.0,"Objects":[{"StartTime":117844.0,"Position":304.0,"HyperDash":false}]},{"StartTime":117934.0,"Objects":[{"StartTime":117934.0,"Position":262.0,"HyperDash":false}]},{"StartTime":118025.0,"Objects":[{"StartTime":118025.0,"Position":219.0,"HyperDash":false}]},{"StartTime":118208.0,"Objects":[{"StartTime":118208.0,"Position":476.0,"HyperDash":false}]},{"StartTime":118299.0,"Objects":[{"StartTime":118299.0,"Position":476.0,"HyperDash":false}]},{"StartTime":118390.0,"Objects":[{"StartTime":118390.0,"Position":432.0,"HyperDash":false}]},{"StartTime":118571.0,"Objects":[{"StartTime":118571.0,"Position":260.0,"HyperDash":false}]},{"StartTime":118662.0,"Objects":[{"StartTime":118662.0,"Position":260.0,"HyperDash":false}]},{"StartTime":118753.0,"Objects":[{"StartTime":118753.0,"Position":260.0,"HyperDash":false}]},{"StartTime":118935.0,"Objects":[{"StartTime":118935.0,"Position":88.0,"HyperDash":false}]},{"StartTime":119026.0,"Objects":[{"StartTime":119026.0,"Position":88.0,"HyperDash":false}]},{"StartTime":119117.0,"Objects":[{"StartTime":119117.0,"Position":132.0,"HyperDash":false}]},{"StartTime":119299.0,"Objects":[{"StartTime":119299.0,"Position":304.0,"HyperDash":false},{"StartTime":119371.0,"Position":319.812164,"HyperDash":false},{"StartTime":119480.0,"Position":389.0,"HyperDash":true}]},{"StartTime":119662.0,"Objects":[{"StartTime":119662.0,"Position":112.0,"HyperDash":false}]},{"StartTime":120026.0,"Objects":[{"StartTime":120026.0,"Position":221.0,"HyperDash":false},{"StartTime":120111.0,"Position":407.0,"HyperDash":false},{"StartTime":120196.0,"Position":287.0,"HyperDash":false},{"StartTime":120281.0,"Position":135.0,"HyperDash":false},{"StartTime":120366.0,"Position":437.0,"HyperDash":false},{"StartTime":120452.0,"Position":289.0,"HyperDash":false},{"StartTime":120537.0,"Position":464.0,"HyperDash":false},{"StartTime":120622.0,"Position":36.0,"HyperDash":false},{"StartTime":120707.0,"Position":378.0,"HyperDash":false},{"StartTime":120792.0,"Position":297.0,"HyperDash":false},{"StartTime":120878.0,"Position":418.0,"HyperDash":false},{"StartTime":120963.0,"Position":329.0,"HyperDash":false},{"StartTime":121048.0,"Position":338.0,"HyperDash":false},{"StartTime":121133.0,"Position":394.0,"HyperDash":false},{"StartTime":121219.0,"Position":40.0,"HyperDash":false},{"StartTime":121304.0,"Position":13.0,"HyperDash":false},{"StartTime":121389.0,"Position":80.0,"HyperDash":false},{"StartTime":121474.0,"Position":138.0,"HyperDash":false},{"StartTime":121559.0,"Position":311.0,"HyperDash":false},{"StartTime":121645.0,"Position":216.0,"HyperDash":false},{"StartTime":121730.0,"Position":310.0,"HyperDash":false},{"StartTime":121815.0,"Position":397.0,"HyperDash":false},{"StartTime":121900.0,"Position":214.0,"HyperDash":false},{"StartTime":121986.0,"Position":505.0,"HyperDash":false},{"StartTime":122071.0,"Position":173.0,"HyperDash":false},{"StartTime":122156.0,"Position":295.0,"HyperDash":false},{"StartTime":122241.0,"Position":199.0,"HyperDash":false},{"StartTime":122326.0,"Position":494.0,"HyperDash":false},{"StartTime":122412.0,"Position":293.0,"HyperDash":false},{"StartTime":122497.0,"Position":115.0,"HyperDash":false},{"StartTime":122582.0,"Position":412.0,"HyperDash":false},{"StartTime":122667.0,"Position":506.0,"HyperDash":false},{"StartTime":122753.0,"Position":293.0,"HyperDash":false},{"StartTime":122838.0,"Position":346.0,"HyperDash":false},{"StartTime":122923.0,"Position":117.0,"HyperDash":false},{"StartTime":123008.0,"Position":285.0,"HyperDash":false},{"StartTime":123093.0,"Position":17.0,"HyperDash":false},{"StartTime":123179.0,"Position":238.0,"HyperDash":false},{"StartTime":123264.0,"Position":222.0,"HyperDash":false},{"StartTime":123349.0,"Position":450.0,"HyperDash":false},{"StartTime":123434.0,"Position":67.0,"HyperDash":false},{"StartTime":123519.0,"Position":219.0,"HyperDash":false},{"StartTime":123605.0,"Position":307.0,"HyperDash":false},{"StartTime":123690.0,"Position":367.0,"HyperDash":false},{"StartTime":123775.0,"Position":412.0,"HyperDash":false},{"StartTime":123860.0,"Position":413.0,"HyperDash":false},{"StartTime":123946.0,"Position":143.0,"HyperDash":false},{"StartTime":124031.0,"Position":339.0,"HyperDash":false},{"StartTime":124116.0,"Position":342.0,"HyperDash":false},{"StartTime":124201.0,"Position":249.0,"HyperDash":false},{"StartTime":124286.0,"Position":235.0,"HyperDash":false},{"StartTime":124372.0,"Position":323.0,"HyperDash":false},{"StartTime":124457.0,"Position":365.0,"HyperDash":false},{"StartTime":124542.0,"Position":74.0,"HyperDash":false},{"StartTime":124627.0,"Position":281.0,"HyperDash":false},{"StartTime":124713.0,"Position":398.0,"HyperDash":false},{"StartTime":124798.0,"Position":335.0,"HyperDash":false},{"StartTime":124883.0,"Position":388.0,"HyperDash":false},{"StartTime":124968.0,"Position":228.0,"HyperDash":false},{"StartTime":125053.0,"Position":323.0,"HyperDash":false},{"StartTime":125139.0,"Position":441.0,"HyperDash":false},{"StartTime":125224.0,"Position":442.0,"HyperDash":false},{"StartTime":125309.0,"Position":278.0,"HyperDash":false},{"StartTime":125394.0,"Position":90.0,"HyperDash":false},{"StartTime":125480.0,"Position":409.0,"HyperDash":false}]},{"StartTime":131299.0,"Objects":[{"StartTime":131299.0,"Position":296.0,"HyperDash":false},{"StartTime":131389.0,"Position":305.0,"HyperDash":false},{"StartTime":131480.0,"Position":296.0,"HyperDash":false},{"StartTime":131553.0,"Position":309.0,"HyperDash":false},{"StartTime":131662.0,"Position":296.0,"HyperDash":false}]},{"StartTime":132026.0,"Objects":[{"StartTime":132026.0,"Position":152.0,"HyperDash":false}]},{"StartTime":132208.0,"Objects":[{"StartTime":132208.0,"Position":244.0,"HyperDash":false}]},{"StartTime":132390.0,"Objects":[{"StartTime":132390.0,"Position":336.0,"HyperDash":false}]},{"StartTime":132571.0,"Objects":[{"StartTime":132571.0,"Position":244.0,"HyperDash":false}]},{"StartTime":132753.0,"Objects":[{"StartTime":132753.0,"Position":416.0,"HyperDash":false},{"StartTime":132843.0,"Position":402.0,"HyperDash":false},{"StartTime":132934.0,"Position":416.0,"HyperDash":false},{"StartTime":133025.0,"Position":411.0,"HyperDash":false},{"StartTime":133116.0,"Position":416.0,"HyperDash":false},{"StartTime":133207.0,"Position":416.0,"HyperDash":false},{"StartTime":133298.0,"Position":416.0,"HyperDash":false},{"StartTime":133371.0,"Position":427.0,"HyperDash":false},{"StartTime":133480.0,"Position":416.0,"HyperDash":false}]},{"StartTime":133844.0,"Objects":[{"StartTime":133844.0,"Position":280.0,"HyperDash":false}]},{"StartTime":134026.0,"Objects":[{"StartTime":134026.0,"Position":188.0,"HyperDash":false}]},{"StartTime":134208.0,"Objects":[{"StartTime":134208.0,"Position":16.0,"HyperDash":false},{"StartTime":134298.0,"Position":1.0,"HyperDash":false},{"StartTime":134389.0,"Position":16.0,"HyperDash":false},{"StartTime":134462.0,"Position":9.0,"HyperDash":false},{"StartTime":134571.0,"Position":16.0,"HyperDash":false}]},{"StartTime":134935.0,"Objects":[{"StartTime":134935.0,"Position":176.0,"HyperDash":false}]},{"StartTime":135299.0,"Objects":[{"StartTime":135299.0,"Position":32.0,"HyperDash":false}]},{"StartTime":135662.0,"Objects":[{"StartTime":135662.0,"Position":272.0,"HyperDash":false},{"StartTime":135752.0,"Position":255.0,"HyperDash":false},{"StartTime":135843.0,"Position":272.0,"HyperDash":false},{"StartTime":135916.0,"Position":286.0,"HyperDash":false},{"StartTime":136025.0,"Position":272.0,"HyperDash":false}]},{"StartTime":136390.0,"Objects":[{"StartTime":136390.0,"Position":428.0,"HyperDash":false},{"StartTime":136480.0,"Position":429.0,"HyperDash":false},{"StartTime":136571.0,"Position":428.0,"HyperDash":false},{"StartTime":136644.0,"Position":433.0,"HyperDash":false},{"StartTime":136753.0,"Position":428.0,"HyperDash":false}]},{"StartTime":137117.0,"Objects":[{"StartTime":137117.0,"Position":132.0,"HyperDash":false},{"StartTime":137207.0,"Position":168.09079,"HyperDash":false},{"StartTime":137298.0,"Position":216.649246,"HyperDash":false},{"StartTime":137389.0,"Position":265.2077,"HyperDash":false},{"StartTime":137480.0,"Position":302.0,"HyperDash":false},{"StartTime":137571.0,"Position":256.675385,"HyperDash":false},{"StartTime":137662.0,"Position":217.116913,"HyperDash":false},{"StartTime":137735.0,"Position":187.976624,"HyperDash":false},{"StartTime":137844.0,"Position":132.0,"HyperDash":false}]},{"StartTime":138571.0,"Objects":[{"StartTime":138571.0,"Position":336.0,"HyperDash":false},{"StartTime":138661.0,"Position":321.0,"HyperDash":false},{"StartTime":138752.0,"Position":336.0,"HyperDash":false},{"StartTime":138825.0,"Position":328.0,"HyperDash":false},{"StartTime":138934.0,"Position":336.0,"HyperDash":false}]},{"StartTime":139117.0,"Objects":[{"StartTime":139117.0,"Position":240.0,"HyperDash":false}]},{"StartTime":139299.0,"Objects":[{"StartTime":139299.0,"Position":336.0,"HyperDash":false}]},{"StartTime":139662.0,"Objects":[{"StartTime":139662.0,"Position":480.0,"HyperDash":false}]},{"StartTime":139844.0,"Objects":[{"StartTime":139844.0,"Position":388.0,"HyperDash":false}]},{"StartTime":140026.0,"Objects":[{"StartTime":140026.0,"Position":212.0,"HyperDash":false},{"StartTime":140116.0,"Position":200.0,"HyperDash":false},{"StartTime":140207.0,"Position":212.0,"HyperDash":false},{"StartTime":140298.0,"Position":229.0,"HyperDash":false},{"StartTime":140389.0,"Position":212.0,"HyperDash":false},{"StartTime":140480.0,"Position":203.0,"HyperDash":false},{"StartTime":140571.0,"Position":212.0,"HyperDash":false},{"StartTime":140644.0,"Position":211.0,"HyperDash":false},{"StartTime":140753.0,"Position":212.0,"HyperDash":false}]},{"StartTime":141480.0,"Objects":[{"StartTime":141480.0,"Position":448.0,"HyperDash":false},{"StartTime":141570.0,"Position":415.636353,"HyperDash":false},{"StartTime":141661.0,"Position":354.5,"HyperDash":false},{"StartTime":141734.0,"Position":391.84848,"HyperDash":false},{"StartTime":141843.0,"Position":448.0,"HyperDash":false}]},{"StartTime":142208.0,"Objects":[{"StartTime":142208.0,"Position":244.0,"HyperDash":false}]},{"StartTime":142390.0,"Objects":[{"StartTime":142390.0,"Position":348.0,"HyperDash":false}]},{"StartTime":142571.0,"Objects":[{"StartTime":142571.0,"Position":448.0,"HyperDash":false}]},{"StartTime":142935.0,"Objects":[{"StartTime":142935.0,"Position":152.0,"HyperDash":false},{"StartTime":143025.0,"Position":137.0,"HyperDash":false},{"StartTime":143116.0,"Position":152.0,"HyperDash":false},{"StartTime":143189.0,"Position":168.0,"HyperDash":false},{"StartTime":143298.0,"Position":152.0,"HyperDash":false}]},{"StartTime":143480.0,"Objects":[{"StartTime":143480.0,"Position":236.0,"HyperDash":false}]},{"StartTime":143662.0,"Objects":[{"StartTime":143662.0,"Position":144.0,"HyperDash":false},{"StartTime":143752.0,"Position":93.85124,"HyperDash":false},{"StartTime":143843.0,"Position":59.0,"HyperDash":false},{"StartTime":143916.0,"Position":76.95316,"HyperDash":false},{"StartTime":144025.0,"Position":144.0,"HyperDash":false}]},{"StartTime":144390.0,"Objects":[{"StartTime":144390.0,"Position":316.0,"HyperDash":false}]},{"StartTime":144571.0,"Objects":[{"StartTime":144571.0,"Position":232.0,"HyperDash":false}]},{"StartTime":144753.0,"Objects":[{"StartTime":144753.0,"Position":148.0,"HyperDash":false}]},{"StartTime":145117.0,"Objects":[{"StartTime":145117.0,"Position":316.0,"HyperDash":false},{"StartTime":145207.0,"Position":275.851257,"HyperDash":false},{"StartTime":145298.0,"Position":231.0,"HyperDash":false},{"StartTime":145371.0,"Position":279.953156,"HyperDash":false},{"StartTime":145480.0,"Position":316.0,"HyperDash":false}]},{"StartTime":145844.0,"Objects":[{"StartTime":145844.0,"Position":144.0,"HyperDash":false},{"StartTime":145916.0,"Position":147.0,"HyperDash":false},{"StartTime":146025.0,"Position":144.0,"HyperDash":false}]},{"StartTime":146208.0,"Objects":[{"StartTime":146208.0,"Position":228.0,"HyperDash":false}]},{"StartTime":146571.0,"Objects":[{"StartTime":146571.0,"Position":59.0,"HyperDash":false},{"StartTime":146661.0,"Position":108.148758,"HyperDash":false},{"StartTime":146752.0,"Position":144.0,"HyperDash":false},{"StartTime":146825.0,"Position":95.04684,"HyperDash":false},{"StartTime":146934.0,"Position":59.0,"HyperDash":false}]},{"StartTime":147299.0,"Objects":[{"StartTime":147299.0,"Position":228.0,"HyperDash":false},{"StartTime":147371.0,"Position":264.812164,"HyperDash":false},{"StartTime":147480.0,"Position":313.0,"HyperDash":false}]},{"StartTime":147662.0,"Objects":[{"StartTime":147662.0,"Position":220.0,"HyperDash":false},{"StartTime":147734.0,"Position":215.0,"HyperDash":false},{"StartTime":147843.0,"Position":220.0,"HyperDash":false}]},{"StartTime":148026.0,"Objects":[{"StartTime":148026.0,"Position":313.0,"HyperDash":false},{"StartTime":148098.0,"Position":313.0,"HyperDash":false},{"StartTime":148207.0,"Position":313.0,"HyperDash":false}]},{"StartTime":148390.0,"Objects":[{"StartTime":148390.0,"Position":228.0,"HyperDash":false}]},{"StartTime":148571.0,"Objects":[{"StartTime":148571.0,"Position":320.0,"HyperDash":false}]},{"StartTime":148753.0,"Objects":[{"StartTime":148753.0,"Position":64.0,"HyperDash":false},{"StartTime":148825.0,"Position":82.0,"HyperDash":false},{"StartTime":148934.0,"Position":64.0,"HyperDash":false}]},{"StartTime":149117.0,"Objects":[{"StartTime":149117.0,"Position":152.0,"HyperDash":false},{"StartTime":149189.0,"Position":148.0,"HyperDash":false},{"StartTime":149298.0,"Position":152.0,"HyperDash":false}]},{"StartTime":149480.0,"Objects":[{"StartTime":149480.0,"Position":328.0,"HyperDash":false}]},{"StartTime":149844.0,"Objects":[{"StartTime":149844.0,"Position":184.0,"HyperDash":false},{"StartTime":149916.0,"Position":215.812149,"HyperDash":false},{"StartTime":150025.0,"Position":269.0,"HyperDash":false}]},{"StartTime":150208.0,"Objects":[{"StartTime":150208.0,"Position":356.0,"HyperDash":false}]},{"StartTime":150571.0,"Objects":[{"StartTime":150571.0,"Position":204.0,"HyperDash":false},{"StartTime":150643.0,"Position":221.0,"HyperDash":false},{"StartTime":150752.0,"Position":204.0,"HyperDash":false}]},{"StartTime":150935.0,"Objects":[{"StartTime":150935.0,"Position":28.0,"HyperDash":false}]},{"StartTime":151299.0,"Objects":[{"StartTime":151299.0,"Position":172.0,"HyperDash":false},{"StartTime":151371.0,"Position":221.812149,"HyperDash":false},{"StartTime":151480.0,"Position":257.0,"HyperDash":false}]},{"StartTime":151662.0,"Objects":[{"StartTime":151662.0,"Position":164.0,"HyperDash":false},{"StartTime":151734.0,"Position":168.0,"HyperDash":false},{"StartTime":151843.0,"Position":164.0,"HyperDash":false}]},{"StartTime":152026.0,"Objects":[{"StartTime":152026.0,"Position":257.0,"HyperDash":false},{"StartTime":152098.0,"Position":274.0,"HyperDash":false},{"StartTime":152207.0,"Position":257.0,"HyperDash":false}]},{"StartTime":152390.0,"Objects":[{"StartTime":152390.0,"Position":432.0,"HyperDash":false}]},{"StartTime":152753.0,"Objects":[{"StartTime":152753.0,"Position":288.0,"HyperDash":false},{"StartTime":152825.0,"Position":271.187866,"HyperDash":false},{"StartTime":152934.0,"Position":203.0,"HyperDash":false}]},{"StartTime":153117.0,"Objects":[{"StartTime":153117.0,"Position":380.0,"HyperDash":false},{"StartTime":153189.0,"Position":381.0,"HyperDash":false},{"StartTime":153298.0,"Position":380.0,"HyperDash":false}]},{"StartTime":153480.0,"Objects":[{"StartTime":153480.0,"Position":288.0,"HyperDash":false},{"StartTime":153552.0,"Position":301.0,"HyperDash":false},{"StartTime":153661.0,"Position":288.0,"HyperDash":false}]},{"StartTime":153844.0,"Objects":[{"StartTime":153844.0,"Position":112.0,"HyperDash":false},{"StartTime":153916.0,"Position":121.0,"HyperDash":false},{"StartTime":154025.0,"Position":112.0,"HyperDash":false}]},{"StartTime":154208.0,"Objects":[{"StartTime":154208.0,"Position":203.0,"HyperDash":false},{"StartTime":154280.0,"Position":235.812149,"HyperDash":false},{"StartTime":154389.0,"Position":288.0,"HyperDash":false}]},{"StartTime":154571.0,"Objects":[{"StartTime":154571.0,"Position":32.0,"HyperDash":false},{"StartTime":154661.0,"Position":45.0,"HyperDash":false},{"StartTime":154752.0,"Position":32.0,"HyperDash":false},{"StartTime":154825.0,"Position":23.0,"HyperDash":false},{"StartTime":154934.0,"Position":32.0,"HyperDash":false}]},{"StartTime":155299.0,"Objects":[{"StartTime":155299.0,"Position":216.0,"HyperDash":false}]},{"StartTime":155480.0,"Objects":[{"StartTime":155480.0,"Position":124.0,"HyperDash":false}]},{"StartTime":155662.0,"Objects":[{"StartTime":155662.0,"Position":32.0,"HyperDash":false}]},{"StartTime":156026.0,"Objects":[{"StartTime":156026.0,"Position":216.0,"HyperDash":false},{"StartTime":156098.0,"Position":237.803421,"HyperDash":false},{"StartTime":156207.0,"Position":300.978058,"HyperDash":false}]},{"StartTime":156390.0,"Objects":[{"StartTime":156390.0,"Position":300.0,"HyperDash":false}]},{"StartTime":156753.0,"Objects":[{"StartTime":156753.0,"Position":132.0,"HyperDash":false},{"StartTime":156843.0,"Position":176.148758,"HyperDash":false},{"StartTime":156934.0,"Position":217.0,"HyperDash":false},{"StartTime":157007.0,"Position":167.046844,"HyperDash":false},{"StartTime":157116.0,"Position":132.0,"HyperDash":false}]},{"StartTime":157299.0,"Objects":[{"StartTime":157299.0,"Position":48.0,"HyperDash":false}]},{"StartTime":157480.0,"Objects":[{"StartTime":157480.0,"Position":140.0,"HyperDash":false},{"StartTime":157552.0,"Position":145.0,"HyperDash":false},{"StartTime":157661.0,"Position":140.0,"HyperDash":false}]},{"StartTime":157844.0,"Objects":[{"StartTime":157844.0,"Position":236.0,"HyperDash":false},{"StartTime":157916.0,"Position":252.0,"HyperDash":false},{"StartTime":158025.0,"Position":236.0,"HyperDash":false}]},{"StartTime":158208.0,"Objects":[{"StartTime":158208.0,"Position":412.0,"HyperDash":false},{"StartTime":158298.0,"Position":434.148743,"HyperDash":false},{"StartTime":158389.0,"Position":497.0,"HyperDash":false},{"StartTime":158462.0,"Position":464.046844,"HyperDash":false},{"StartTime":158571.0,"Position":412.0,"HyperDash":false}]},{"StartTime":158935.0,"Objects":[{"StartTime":158935.0,"Position":268.0,"HyperDash":false}]},{"StartTime":159117.0,"Objects":[{"StartTime":159117.0,"Position":344.0,"HyperDash":false}]},{"StartTime":159299.0,"Objects":[{"StartTime":159299.0,"Position":420.0,"HyperDash":false}]},{"StartTime":159480.0,"Objects":[{"StartTime":159480.0,"Position":496.0,"HyperDash":false}]},{"StartTime":159662.0,"Objects":[{"StartTime":159662.0,"Position":412.0,"HyperDash":false},{"StartTime":159734.0,"Position":448.812164,"HyperDash":false},{"StartTime":159843.0,"Position":497.0,"HyperDash":false}]},{"StartTime":160026.0,"Objects":[{"StartTime":160026.0,"Position":324.0,"HyperDash":false},{"StartTime":160098.0,"Position":341.0,"HyperDash":false},{"StartTime":160207.0,"Position":324.0,"HyperDash":false}]},{"StartTime":160390.0,"Objects":[{"StartTime":160390.0,"Position":68.0,"HyperDash":false},{"StartTime":160462.0,"Position":75.0,"HyperDash":false},{"StartTime":160571.0,"Position":68.0,"HyperDash":false}]},{"StartTime":160753.0,"Objects":[{"StartTime":160753.0,"Position":152.0,"HyperDash":false},{"StartTime":160825.0,"Position":187.812149,"HyperDash":false},{"StartTime":160934.0,"Position":237.0,"HyperDash":false}]},{"StartTime":161117.0,"Objects":[{"StartTime":161117.0,"Position":409.0,"HyperDash":false},{"StartTime":161189.0,"Position":409.0,"HyperDash":false},{"StartTime":161298.0,"Position":409.0,"HyperDash":false}]},{"StartTime":161480.0,"Objects":[{"StartTime":161480.0,"Position":324.0,"HyperDash":false},{"StartTime":161552.0,"Position":355.812164,"HyperDash":false},{"StartTime":161661.0,"Position":409.0,"HyperDash":false}]},{"StartTime":161844.0,"Objects":[{"StartTime":161844.0,"Position":313.0,"HyperDash":false},{"StartTime":161916.0,"Position":320.0,"HyperDash":false},{"StartTime":162025.0,"Position":313.0,"HyperDash":false}]},{"StartTime":162208.0,"Objects":[{"StartTime":162208.0,"Position":140.0,"HyperDash":false},{"StartTime":162280.0,"Position":128.0,"HyperDash":false},{"StartTime":162389.0,"Position":140.0,"HyperDash":false}]},{"StartTime":162480.0,"Objects":[{"StartTime":162480.0,"Position":184.0,"HyperDash":false}]},{"StartTime":162571.0,"Objects":[{"StartTime":162571.0,"Position":228.0,"HyperDash":false},{"StartTime":162643.0,"Position":255.812164,"HyperDash":false},{"StartTime":162752.0,"Position":313.0,"HyperDash":false}]},{"StartTime":162935.0,"Objects":[{"StartTime":162935.0,"Position":400.0,"HyperDash":false},{"StartTime":163007.0,"Position":417.0,"HyperDash":false},{"StartTime":163116.0,"Position":400.0,"HyperDash":false}]},{"StartTime":163299.0,"Objects":[{"StartTime":163299.0,"Position":217.0,"HyperDash":false},{"StartTime":163367.0,"Position":455.0,"HyperDash":false},{"StartTime":163435.0,"Position":229.0,"HyperDash":false},{"StartTime":163503.0,"Position":51.0,"HyperDash":false},{"StartTime":163571.0,"Position":199.0,"HyperDash":false},{"StartTime":163639.0,"Position":208.0,"HyperDash":false},{"StartTime":163707.0,"Position":173.0,"HyperDash":false},{"StartTime":163775.0,"Position":367.0,"HyperDash":false},{"StartTime":163844.0,"Position":193.0,"HyperDash":false},{"StartTime":163912.0,"Position":488.0,"HyperDash":false},{"StartTime":163980.0,"Position":314.0,"HyperDash":false},{"StartTime":164048.0,"Position":135.0,"HyperDash":false},{"StartTime":164116.0,"Position":399.0,"HyperDash":false},{"StartTime":164184.0,"Position":404.0,"HyperDash":false},{"StartTime":164252.0,"Position":152.0,"HyperDash":false},{"StartTime":164320.0,"Position":353.0,"HyperDash":false},{"StartTime":164389.0,"Position":358.0,"HyperDash":false}]},{"StartTime":164753.0,"Objects":[{"StartTime":164753.0,"Position":132.0,"HyperDash":false},{"StartTime":164843.0,"Position":132.0,"HyperDash":false},{"StartTime":164934.0,"Position":132.0,"HyperDash":false}]},{"StartTime":165117.0,"Objects":[{"StartTime":165117.0,"Position":304.0,"HyperDash":false}]},{"StartTime":165207.0,"Objects":[{"StartTime":165207.0,"Position":352.0,"HyperDash":false}]},{"StartTime":165298.0,"Objects":[{"StartTime":165298.0,"Position":372.0,"HyperDash":false}]},{"StartTime":165389.0,"Objects":[{"StartTime":165389.0,"Position":351.0,"HyperDash":false}]},{"StartTime":165480.0,"Objects":[{"StartTime":165480.0,"Position":303.0,"HyperDash":false}]},{"StartTime":165662.0,"Objects":[{"StartTime":165662.0,"Position":208.0,"HyperDash":false}]},{"StartTime":165844.0,"Objects":[{"StartTime":165844.0,"Position":388.0,"HyperDash":false},{"StartTime":165916.0,"Position":435.812164,"HyperDash":false},{"StartTime":166025.0,"Position":473.0,"HyperDash":false}]},{"StartTime":166208.0,"Objects":[{"StartTime":166208.0,"Position":216.0,"HyperDash":false},{"StartTime":166298.0,"Position":158.851242,"HyperDash":false},{"StartTime":166389.0,"Position":131.0,"HyperDash":false},{"StartTime":166462.0,"Position":155.953156,"HyperDash":false},{"StartTime":166571.0,"Position":216.0,"HyperDash":false}]},{"StartTime":166753.0,"Objects":[{"StartTime":166753.0,"Position":308.0,"HyperDash":false},{"StartTime":166843.0,"Position":274.851257,"HyperDash":false},{"StartTime":166934.0,"Position":223.234161,"HyperDash":false},{"StartTime":167007.0,"Position":206.046844,"HyperDash":false},{"StartTime":167116.0,"Position":138.0,"HyperDash":false}]},{"StartTime":167299.0,"Objects":[{"StartTime":167299.0,"Position":312.0,"HyperDash":false},{"StartTime":167371.0,"Position":305.0,"HyperDash":false},{"StartTime":167480.0,"Position":312.0,"HyperDash":false}]},{"StartTime":167662.0,"Objects":[{"StartTime":167662.0,"Position":138.0,"HyperDash":false},{"StartTime":167752.0,"Position":192.148758,"HyperDash":false},{"StartTime":167843.0,"Position":222.765839,"HyperDash":false},{"StartTime":167916.0,"Position":254.953156,"HyperDash":false},{"StartTime":168025.0,"Position":308.0,"HyperDash":false}]},{"StartTime":168208.0,"Objects":[{"StartTime":168208.0,"Position":404.0,"HyperDash":false},{"StartTime":168298.0,"Position":395.0,"HyperDash":false},{"StartTime":168389.0,"Position":403.234161,"HyperDash":false},{"StartTime":168462.0,"Position":382.046844,"HyperDash":false},{"StartTime":168571.0,"Position":318.0,"HyperDash":false}]},{"StartTime":168753.0,"Objects":[{"StartTime":168753.0,"Position":140.0,"HyperDash":false},{"StartTime":168825.0,"Position":131.0,"HyperDash":false},{"StartTime":168934.0,"Position":140.0,"HyperDash":false}]},{"StartTime":169117.0,"Objects":[{"StartTime":169117.0,"Position":320.0,"HyperDash":false},{"StartTime":169207.0,"Position":375.148743,"HyperDash":false},{"StartTime":169298.0,"Position":404.0,"HyperDash":false},{"StartTime":169371.0,"Position":419.0,"HyperDash":false},{"StartTime":169480.0,"Position":404.0,"HyperDash":false}]},{"StartTime":169662.0,"Objects":[{"StartTime":169662.0,"Position":232.0,"HyperDash":false},{"StartTime":169752.0,"Position":176.851242,"HyperDash":false},{"StartTime":169843.0,"Position":147.234161,"HyperDash":false},{"StartTime":169916.0,"Position":100.046837,"HyperDash":false},{"StartTime":170025.0,"Position":62.0,"HyperDash":false}]},{"StartTime":170208.0,"Objects":[{"StartTime":170208.0,"Position":232.0,"HyperDash":false},{"StartTime":170280.0,"Position":203.187851,"HyperDash":false},{"StartTime":170389.0,"Position":147.0,"HyperDash":false}]},{"StartTime":170571.0,"Objects":[{"StartTime":170571.0,"Position":52.0,"HyperDash":false},{"StartTime":170661.0,"Position":52.0,"HyperDash":false}]},{"StartTime":170753.0,"Objects":[{"StartTime":170753.0,"Position":100.0,"HyperDash":false}]},{"StartTime":170935.0,"Objects":[{"StartTime":170935.0,"Position":192.0,"HyperDash":false}]},{"StartTime":171117.0,"Objects":[{"StartTime":171117.0,"Position":448.0,"HyperDash":false},{"StartTime":171189.0,"Position":432.0,"HyperDash":false},{"StartTime":171298.0,"Position":448.0,"HyperDash":false}]},{"StartTime":171480.0,"Objects":[{"StartTime":171480.0,"Position":356.0,"HyperDash":false}]},{"StartTime":171662.0,"Objects":[{"StartTime":171662.0,"Position":184.0,"HyperDash":false},{"StartTime":171734.0,"Position":202.812149,"HyperDash":false},{"StartTime":171843.0,"Position":269.0,"HyperDash":false}]},{"StartTime":172026.0,"Objects":[{"StartTime":172026.0,"Position":20.0,"HyperDash":false},{"StartTime":172116.0,"Position":20.0,"HyperDash":false},{"StartTime":172207.0,"Position":20.0,"HyperDash":false}]},{"StartTime":172390.0,"Objects":[{"StartTime":172390.0,"Position":116.0,"HyperDash":false}]},{"StartTime":172571.0,"Objects":[{"StartTime":172571.0,"Position":32.0,"HyperDash":false}]},{"StartTime":172753.0,"Objects":[{"StartTime":172753.0,"Position":208.0,"HyperDash":false},{"StartTime":172825.0,"Position":252.812149,"HyperDash":false},{"StartTime":172934.0,"Position":293.0,"HyperDash":false}]},{"StartTime":173117.0,"Objects":[{"StartTime":173117.0,"Position":200.0,"HyperDash":false},{"StartTime":173189.0,"Position":212.0,"HyperDash":false},{"StartTime":173298.0,"Position":200.0,"HyperDash":false}]},{"StartTime":173480.0,"Objects":[{"StartTime":173480.0,"Position":376.0,"HyperDash":false},{"StartTime":173552.0,"Position":379.0,"HyperDash":false},{"StartTime":173661.0,"Position":376.0,"HyperDash":false}]},{"StartTime":173844.0,"Objects":[{"StartTime":173844.0,"Position":200.0,"HyperDash":false}]},{"StartTime":174026.0,"Objects":[{"StartTime":174026.0,"Position":116.0,"HyperDash":false},{"StartTime":174116.0,"Position":76.2682648,"HyperDash":false},{"StartTime":174207.0,"Position":64.10713,"HyperDash":false},{"StartTime":174280.0,"Position":75.55404,"HyperDash":false},{"StartTime":174389.0,"Position":115.499283,"HyperDash":false}]},{"StartTime":174571.0,"Objects":[{"StartTime":174571.0,"Position":372.0,"HyperDash":false},{"StartTime":174643.0,"Position":412.812164,"HyperDash":false},{"StartTime":174752.0,"Position":457.0,"HyperDash":false}]},{"StartTime":174935.0,"Objects":[{"StartTime":174935.0,"Position":280.0,"HyperDash":false},{"StartTime":175007.0,"Position":297.0,"HyperDash":false},{"StartTime":175116.0,"Position":280.0,"HyperDash":false}]},{"StartTime":175299.0,"Objects":[{"StartTime":175299.0,"Position":368.0,"HyperDash":false}]},{"StartTime":175480.0,"Objects":[{"StartTime":175480.0,"Position":192.0,"HyperDash":false},{"StartTime":175552.0,"Position":197.0,"HyperDash":false},{"StartTime":175661.0,"Position":192.0,"HyperDash":false}]},{"StartTime":175844.0,"Objects":[{"StartTime":175844.0,"Position":280.0,"HyperDash":false}]},{"StartTime":176026.0,"Objects":[{"StartTime":176026.0,"Position":453.0,"HyperDash":false},{"StartTime":176098.0,"Position":425.187836,"HyperDash":false},{"StartTime":176207.0,"Position":368.0,"HyperDash":false}]},{"StartTime":176390.0,"Objects":[{"StartTime":176390.0,"Position":112.0,"HyperDash":false},{"StartTime":176480.0,"Position":69.85124,"HyperDash":false},{"StartTime":176571.0,"Position":27.0,"HyperDash":false},{"StartTime":176644.0,"Position":44.9531631,"HyperDash":false},{"StartTime":176753.0,"Position":112.0,"HyperDash":false}]},{"StartTime":176935.0,"Objects":[{"StartTime":176935.0,"Position":292.0,"HyperDash":false},{"StartTime":177025.0,"Position":231.851242,"HyperDash":false},{"StartTime":177116.0,"Position":207.234161,"HyperDash":false},{"StartTime":177189.0,"Position":180.046844,"HyperDash":false},{"StartTime":177298.0,"Position":122.0,"HyperDash":false}]},{"StartTime":177480.0,"Objects":[{"StartTime":177480.0,"Position":304.0,"HyperDash":false},{"StartTime":177552.0,"Position":349.812164,"HyperDash":false},{"StartTime":177661.0,"Position":389.0,"HyperDash":false}]},{"StartTime":177844.0,"Objects":[{"StartTime":177844.0,"Position":132.0,"HyperDash":false},{"StartTime":177934.0,"Position":67.42149,"HyperDash":false},{"StartTime":178025.0,"Position":32.0,"HyperDash":false},{"StartTime":178098.0,"Position":18.0,"HyperDash":false},{"StartTime":178207.0,"Position":32.0,"HyperDash":false}]},{"StartTime":178390.0,"Objects":[{"StartTime":178390.0,"Position":208.0,"HyperDash":false},{"StartTime":178480.0,"Position":249.148758,"HyperDash":false},{"StartTime":178571.0,"Position":292.765839,"HyperDash":false},{"StartTime":178644.0,"Position":311.953156,"HyperDash":false},{"StartTime":178753.0,"Position":378.0,"HyperDash":false}]},{"StartTime":178935.0,"Objects":[{"StartTime":178935.0,"Position":284.0,"HyperDash":false},{"StartTime":179007.0,"Position":301.0,"HyperDash":false},{"StartTime":179116.0,"Position":284.0,"HyperDash":false}]},{"StartTime":179299.0,"Objects":[{"StartTime":179299.0,"Position":464.0,"HyperDash":false},{"StartTime":179371.0,"Position":479.0,"HyperDash":false},{"StartTime":179480.0,"Position":464.0,"HyperDash":false}]},{"StartTime":179662.0,"Objects":[{"StartTime":179662.0,"Position":380.0,"HyperDash":false}]},{"StartTime":179844.0,"Objects":[{"StartTime":179844.0,"Position":204.0,"HyperDash":false},{"StartTime":179934.0,"Position":249.148758,"HyperDash":false},{"StartTime":180025.0,"Position":288.765839,"HyperDash":false},{"StartTime":180098.0,"Position":306.953156,"HyperDash":false},{"StartTime":180207.0,"Position":374.0,"HyperDash":false}]},{"StartTime":180390.0,"Objects":[{"StartTime":180390.0,"Position":460.0,"HyperDash":false},{"StartTime":180462.0,"Position":450.0,"HyperDash":false},{"StartTime":180571.0,"Position":460.0,"HyperDash":false}]},{"StartTime":180753.0,"Objects":[{"StartTime":180753.0,"Position":284.0,"HyperDash":false},{"StartTime":180843.0,"Position":257.851257,"HyperDash":false},{"StartTime":180934.0,"Position":200.0,"HyperDash":false},{"StartTime":181007.0,"Position":192.0,"HyperDash":false},{"StartTime":181116.0,"Position":200.0,"HyperDash":false}]},{"StartTime":181299.0,"Objects":[{"StartTime":181299.0,"Position":380.0,"HyperDash":false},{"StartTime":181389.0,"Position":345.851257,"HyperDash":false},{"StartTime":181480.0,"Position":295.234161,"HyperDash":false},{"StartTime":181553.0,"Position":258.046844,"HyperDash":false},{"StartTime":181662.0,"Position":210.0,"HyperDash":false}]},{"StartTime":181844.0,"Objects":[{"StartTime":181844.0,"Position":302.0,"HyperDash":false},{"StartTime":181916.0,"Position":255.187836,"HyperDash":false},{"StartTime":182025.0,"Position":217.0,"HyperDash":false}]},{"StartTime":182208.0,"Objects":[{"StartTime":182208.0,"Position":124.0,"HyperDash":false},{"StartTime":182280.0,"Position":131.0,"HyperDash":false},{"StartTime":182389.0,"Position":124.0,"HyperDash":false}]},{"StartTime":182571.0,"Objects":[{"StartTime":182571.0,"Position":302.0,"HyperDash":false},{"StartTime":182643.0,"Position":248.187836,"HyperDash":false},{"StartTime":182752.0,"Position":217.0,"HyperDash":false}]},{"StartTime":182935.0,"Objects":[{"StartTime":182935.0,"Position":312.0,"HyperDash":false},{"StartTime":183025.0,"Position":354.5,"HyperDash":false},{"StartTime":183116.0,"Position":312.0,"HyperDash":false}]},{"StartTime":183299.0,"Objects":[{"StartTime":183299.0,"Position":132.0,"HyperDash":false},{"StartTime":183371.0,"Position":80.18785,"HyperDash":false},{"StartTime":183480.0,"Position":47.0,"HyperDash":true}]},{"StartTime":183662.0,"Objects":[{"StartTime":183662.0,"Position":312.0,"HyperDash":false},{"StartTime":183752.0,"Position":350.73175,"HyperDash":false},{"StartTime":183843.0,"Position":363.892883,"HyperDash":false},{"StartTime":183916.0,"Position":353.445984,"HyperDash":false},{"StartTime":184025.0,"Position":312.500732,"HyperDash":false}]},{"StartTime":184208.0,"Objects":[{"StartTime":184208.0,"Position":220.0,"HyperDash":false}]},{"StartTime":184390.0,"Objects":[{"StartTime":184390.0,"Position":324.0,"HyperDash":false},{"StartTime":184462.0,"Position":310.0,"HyperDash":false},{"StartTime":184571.0,"Position":324.0,"HyperDash":false}]},{"StartTime":184753.0,"Objects":[{"StartTime":184753.0,"Position":144.0,"HyperDash":false},{"StartTime":184825.0,"Position":142.0,"HyperDash":false},{"StartTime":184934.0,"Position":144.0,"HyperDash":false}]},{"StartTime":185117.0,"Objects":[{"StartTime":185117.0,"Position":324.0,"HyperDash":false},{"StartTime":185189.0,"Position":348.812164,"HyperDash":false},{"StartTime":185298.0,"Position":409.0,"HyperDash":false}]},{"StartTime":185480.0,"Objects":[{"StartTime":185480.0,"Position":232.0,"HyperDash":false},{"StartTime":185552.0,"Position":224.0,"HyperDash":false},{"StartTime":185661.0,"Position":232.0,"HyperDash":false}]},{"StartTime":185844.0,"Objects":[{"StartTime":185844.0,"Position":316.0,"HyperDash":false}]},{"StartTime":186026.0,"Objects":[{"StartTime":186026.0,"Position":232.0,"HyperDash":false}]},{"StartTime":186208.0,"Objects":[{"StartTime":186208.0,"Position":408.0,"HyperDash":false},{"StartTime":186280.0,"Position":427.0,"HyperDash":false},{"StartTime":186389.0,"Position":408.0,"HyperDash":false}]},{"StartTime":186571.0,"Objects":[{"StartTime":186571.0,"Position":152.0,"HyperDash":false},{"StartTime":186661.0,"Position":106.851242,"HyperDash":false},{"StartTime":186752.0,"Position":68.76584,"HyperDash":false},{"StartTime":186825.0,"Position":87.95316,"HyperDash":false},{"StartTime":186934.0,"Position":154.0,"HyperDash":false}]},{"StartTime":187117.0,"Objects":[{"StartTime":187117.0,"Position":332.0,"HyperDash":false},{"StartTime":187207.0,"Position":276.851257,"HyperDash":false},{"StartTime":187298.0,"Position":247.234161,"HyperDash":false},{"StartTime":187371.0,"Position":205.046844,"HyperDash":false},{"StartTime":187480.0,"Position":162.0,"HyperDash":false}]},{"StartTime":187662.0,"Objects":[{"StartTime":187662.0,"Position":76.0,"HyperDash":false},{"StartTime":187734.0,"Position":74.0,"HyperDash":false},{"StartTime":187843.0,"Position":76.0,"HyperDash":false}]},{"StartTime":188026.0,"Objects":[{"StartTime":188026.0,"Position":252.0,"HyperDash":false}]},{"StartTime":188116.0,"Objects":[{"StartTime":188116.0,"Position":294.0,"HyperDash":false}]},{"StartTime":188207.0,"Objects":[{"StartTime":188207.0,"Position":337.0,"HyperDash":false}]},{"StartTime":188390.0,"Objects":[{"StartTime":188390.0,"Position":176.0,"HyperDash":false}]},{"StartTime":188571.0,"Objects":[{"StartTime":188571.0,"Position":344.0,"HyperDash":false},{"StartTime":188661.0,"Position":370.214264,"HyperDash":false},{"StartTime":188752.0,"Position":396.42868,"HyperDash":false},{"StartTime":188825.0,"Position":403.238831,"HyperDash":false},{"StartTime":188934.0,"Position":343.061737,"HyperDash":false}]},{"StartTime":189117.0,"Objects":[{"StartTime":189117.0,"Position":168.0,"HyperDash":false},{"StartTime":189189.0,"Position":133.187851,"HyperDash":false},{"StartTime":189298.0,"Position":83.0,"HyperDash":true}]},{"StartTime":189480.0,"Objects":[{"StartTime":189480.0,"Position":344.0,"HyperDash":false},{"StartTime":189570.0,"Position":378.578522,"HyperDash":false},{"StartTime":189661.0,"Position":445.719,"HyperDash":false},{"StartTime":189734.0,"Position":443.0,"HyperDash":false},{"StartTime":189843.0,"Position":448.0,"HyperDash":false}]},{"StartTime":190026.0,"Objects":[{"StartTime":190026.0,"Position":352.0,"HyperDash":false},{"StartTime":190116.0,"Position":300.851257,"HyperDash":false},{"StartTime":190207.0,"Position":267.234161,"HyperDash":false},{"StartTime":190280.0,"Position":224.046844,"HyperDash":false},{"StartTime":190389.0,"Position":182.0,"HyperDash":false}]},{"StartTime":190571.0,"Objects":[{"StartTime":190571.0,"Position":276.0,"HyperDash":false},{"StartTime":190643.0,"Position":262.0,"HyperDash":false},{"StartTime":190752.0,"Position":276.0,"HyperDash":false}]},{"StartTime":190935.0,"Objects":[{"StartTime":190935.0,"Position":96.0,"HyperDash":false},{"StartTime":191007.0,"Position":114.0,"HyperDash":false},{"StartTime":191116.0,"Position":96.0,"HyperDash":false}]},{"StartTime":191299.0,"Objects":[{"StartTime":191299.0,"Position":192.0,"HyperDash":false},{"StartTime":191371.0,"Position":154.187851,"HyperDash":false},{"StartTime":191480.0,"Position":107.0,"HyperDash":false}]},{"StartTime":191662.0,"Objects":[{"StartTime":191662.0,"Position":284.0,"HyperDash":false},{"StartTime":191734.0,"Position":328.812164,"HyperDash":false},{"StartTime":191843.0,"Position":369.0,"HyperDash":false}]},{"StartTime":192026.0,"Objects":[{"StartTime":192026.0,"Position":464.0,"HyperDash":false},{"StartTime":192116.0,"Position":464.0,"HyperDash":false}]},{"StartTime":192208.0,"Objects":[{"StartTime":192208.0,"Position":420.0,"HyperDash":false}]},{"StartTime":192390.0,"Objects":[{"StartTime":192390.0,"Position":240.0,"HyperDash":false},{"StartTime":192480.0,"Position":193.851242,"HyperDash":false},{"StartTime":192571.0,"Position":155.234161,"HyperDash":false},{"StartTime":192644.0,"Position":139.046844,"HyperDash":false},{"StartTime":192753.0,"Position":70.0,"HyperDash":false}]},{"StartTime":192935.0,"Objects":[{"StartTime":192935.0,"Position":156.0,"HyperDash":false}]},{"StartTime":193117.0,"Objects":[{"StartTime":193117.0,"Position":64.0,"HyperDash":false},{"StartTime":193189.0,"Position":49.0,"HyperDash":false},{"StartTime":193298.0,"Position":64.0,"HyperDash":false}]},{"StartTime":193480.0,"Objects":[{"StartTime":193480.0,"Position":156.0,"HyperDash":false},{"StartTime":193552.0,"Position":173.0,"HyperDash":false},{"StartTime":193661.0,"Position":156.0,"HyperDash":false}]},{"StartTime":193844.0,"Objects":[{"StartTime":193844.0,"Position":332.0,"HyperDash":false},{"StartTime":193934.0,"Position":374.5,"HyperDash":false},{"StartTime":194025.0,"Position":332.0,"HyperDash":false}]},{"StartTime":194208.0,"Objects":[{"StartTime":194208.0,"Position":156.0,"HyperDash":false},{"StartTime":194280.0,"Position":194.812149,"HyperDash":false},{"StartTime":194389.0,"Position":241.0,"HyperDash":false}]},{"StartTime":194571.0,"Objects":[{"StartTime":194571.0,"Position":328.0,"HyperDash":false}]},{"StartTime":194753.0,"Objects":[{"StartTime":194753.0,"Position":236.0,"HyperDash":false}]},{"StartTime":194935.0,"Objects":[{"StartTime":194935.0,"Position":416.0,"HyperDash":false},{"StartTime":195007.0,"Position":430.0,"HyperDash":false},{"StartTime":195116.0,"Position":416.0,"HyperDash":false}]},{"StartTime":195299.0,"Objects":[{"StartTime":195299.0,"Position":160.0,"HyperDash":false},{"StartTime":195389.0,"Position":112.851242,"HyperDash":false},{"StartTime":195480.0,"Position":76.0,"HyperDash":false},{"StartTime":195553.0,"Position":72.0,"HyperDash":false},{"StartTime":195662.0,"Position":76.0,"HyperDash":false}]},{"StartTime":195844.0,"Objects":[{"StartTime":195844.0,"Position":164.0,"HyperDash":false},{"StartTime":195934.0,"Position":224.148758,"HyperDash":false},{"StartTime":196025.0,"Position":248.765839,"HyperDash":false},{"StartTime":196098.0,"Position":284.953156,"HyperDash":false},{"StartTime":196207.0,"Position":334.0,"HyperDash":false}]},{"StartTime":196389.0,"Objects":[{"StartTime":196389.0,"Position":240.0,"HyperDash":false},{"StartTime":196461.0,"Position":232.0,"HyperDash":false},{"StartTime":196570.0,"Position":240.0,"HyperDash":false}]},{"StartTime":196753.0,"Objects":[{"StartTime":196753.0,"Position":420.0,"HyperDash":false},{"StartTime":196825.0,"Position":435.0,"HyperDash":false},{"StartTime":196934.0,"Position":420.0,"HyperDash":false}]},{"StartTime":197026.0,"Objects":[{"StartTime":197026.0,"Position":372.0,"HyperDash":false}]},{"StartTime":197117.0,"Objects":[{"StartTime":197117.0,"Position":324.0,"HyperDash":false},{"StartTime":197189.0,"Position":282.187836,"HyperDash":false},{"StartTime":197298.0,"Position":239.0,"HyperDash":false}]},{"StartTime":197480.0,"Objects":[{"StartTime":197480.0,"Position":332.0,"HyperDash":false},{"StartTime":197552.0,"Position":346.0,"HyperDash":false},{"StartTime":197661.0,"Position":332.0,"HyperDash":false}]},{"StartTime":197844.0,"Objects":[{"StartTime":197844.0,"Position":152.0,"HyperDash":false},{"StartTime":197934.0,"Position":109.5,"HyperDash":false},{"StartTime":198025.0,"Position":152.0,"HyperDash":false}]},{"StartTime":198208.0,"Objects":[{"StartTime":198208.0,"Position":328.0,"HyperDash":false},{"StartTime":198298.0,"Position":387.148743,"HyperDash":false},{"StartTime":198389.0,"Position":412.765839,"HyperDash":false},{"StartTime":198462.0,"Position":458.953156,"HyperDash":false},{"StartTime":198571.0,"Position":498.0,"HyperDash":false}]},{"StartTime":198753.0,"Objects":[{"StartTime":198753.0,"Position":412.0,"HyperDash":false}]},{"StartTime":198935.0,"Objects":[{"StartTime":198935.0,"Position":236.0,"HyperDash":false},{"StartTime":199007.0,"Position":253.0,"HyperDash":false},{"StartTime":199116.0,"Position":236.0,"HyperDash":false}]},{"StartTime":199298.0,"Objects":[{"StartTime":199298.0,"Position":328.0,"HyperDash":false},{"StartTime":199370.0,"Position":276.187836,"HyperDash":false},{"StartTime":199479.0,"Position":243.0,"HyperDash":false}]},{"StartTime":199662.0,"Objects":[{"StartTime":199662.0,"Position":64.0,"HyperDash":false},{"StartTime":199734.0,"Position":66.0,"HyperDash":false},{"StartTime":199843.0,"Position":64.0,"HyperDash":false}]},{"StartTime":200026.0,"Objects":[{"StartTime":200026.0,"Position":160.0,"HyperDash":false}]},{"StartTime":200116.0,"Objects":[{"StartTime":200116.0,"Position":112.0,"HyperDash":false}]},{"StartTime":200207.0,"Objects":[{"StartTime":200207.0,"Position":64.0,"HyperDash":false}]},{"StartTime":200390.0,"Objects":[{"StartTime":200390.0,"Position":240.0,"HyperDash":false},{"StartTime":200462.0,"Position":232.0,"HyperDash":false},{"StartTime":200571.0,"Position":240.0,"HyperDash":false}]},{"StartTime":200753.0,"Objects":[{"StartTime":200753.0,"Position":416.0,"HyperDash":false},{"StartTime":200825.0,"Position":438.812164,"HyperDash":false},{"StartTime":200934.0,"Position":501.0,"HyperDash":true}]},{"StartTime":201117.0,"Objects":[{"StartTime":201117.0,"Position":240.0,"HyperDash":false},{"StartTime":201207.0,"Position":198.4215,"HyperDash":false},{"StartTime":201298.0,"Position":138.280991,"HyperDash":false},{"StartTime":201371.0,"Position":113.25621,"HyperDash":false},{"StartTime":201480.0,"Position":36.0,"HyperDash":false}]},{"StartTime":201662.0,"Objects":[{"StartTime":201662.0,"Position":128.0,"HyperDash":false},{"StartTime":201752.0,"Position":185.148758,"HyperDash":false},{"StartTime":201843.0,"Position":212.765839,"HyperDash":false},{"StartTime":201916.0,"Position":198.0,"HyperDash":false},{"StartTime":202025.0,"Position":216.0,"HyperDash":false}]},{"StartTime":202208.0,"Objects":[{"StartTime":202208.0,"Position":40.0,"HyperDash":false},{"StartTime":202280.0,"Position":56.0,"HyperDash":false},{"StartTime":202389.0,"Position":40.0,"HyperDash":false}]},{"StartTime":202571.0,"Objects":[{"StartTime":202571.0,"Position":216.0,"HyperDash":false},{"StartTime":202643.0,"Position":263.812134,"HyperDash":false},{"StartTime":202752.0,"Position":301.0,"HyperDash":false}]},{"StartTime":202844.0,"Objects":[{"StartTime":202844.0,"Position":348.0,"HyperDash":false}]},{"StartTime":202935.0,"Objects":[{"StartTime":202935.0,"Position":396.0,"HyperDash":false},{"StartTime":203007.0,"Position":411.0,"HyperDash":false},{"StartTime":203116.0,"Position":396.0,"HyperDash":false}]},{"StartTime":203299.0,"Objects":[{"StartTime":203299.0,"Position":492.0,"HyperDash":false},{"StartTime":203371.0,"Position":454.187836,"HyperDash":false},{"StartTime":203480.0,"Position":407.0,"HyperDash":false}]},{"StartTime":203662.0,"Objects":[{"StartTime":203662.0,"Position":232.0,"HyperDash":false},{"StartTime":203734.0,"Position":231.0,"HyperDash":false},{"StartTime":203843.0,"Position":232.0,"HyperDash":false}]},{"StartTime":204026.0,"Objects":[{"StartTime":204026.0,"Position":408.0,"HyperDash":false},{"StartTime":204116.0,"Position":436.148743,"HyperDash":false},{"StartTime":204207.0,"Position":493.0,"HyperDash":false},{"StartTime":204280.0,"Position":447.046844,"HyperDash":false},{"StartTime":204389.0,"Position":408.0,"HyperDash":false}]},{"StartTime":204571.0,"Objects":[{"StartTime":204571.0,"Position":316.0,"HyperDash":false},{"StartTime":204661.0,"Position":377.148743,"HyperDash":false},{"StartTime":204752.0,"Position":400.765839,"HyperDash":false},{"StartTime":204825.0,"Position":421.953156,"HyperDash":false},{"StartTime":204934.0,"Position":486.0,"HyperDash":false}]},{"StartTime":205117.0,"Objects":[{"StartTime":205117.0,"Position":308.0,"HyperDash":false},{"StartTime":205189.0,"Position":279.187836,"HyperDash":false},{"StartTime":205298.0,"Position":223.0,"HyperDash":false}]},{"StartTime":205480.0,"Objects":[{"StartTime":205480.0,"Position":48.0,"HyperDash":false},{"StartTime":205552.0,"Position":51.0,"HyperDash":false},{"StartTime":205661.0,"Position":48.0,"HyperDash":false}]},{"StartTime":205844.0,"Objects":[{"StartTime":205844.0,"Position":224.0,"HyperDash":false},{"StartTime":205916.0,"Position":246.812164,"HyperDash":false},{"StartTime":206025.0,"Position":309.0,"HyperDash":false}]},{"StartTime":206208.0,"Objects":[{"StartTime":206208.0,"Position":216.0,"HyperDash":false}]},{"StartTime":206390.0,"Objects":[{"StartTime":206390.0,"Position":320.0,"HyperDash":false}]},{"StartTime":206571.0,"Objects":[{"StartTime":206571.0,"Position":144.0,"HyperDash":false},{"StartTime":206643.0,"Position":107.187851,"HyperDash":false},{"StartTime":206752.0,"Position":59.0,"HyperDash":true}]},{"StartTime":206935.0,"Objects":[{"StartTime":206935.0,"Position":320.0,"HyperDash":false},{"StartTime":207007.0,"Position":361.812164,"HyperDash":false},{"StartTime":207116.0,"Position":405.0,"HyperDash":false}]},{"StartTime":207208.0,"Objects":[{"StartTime":207208.0,"Position":405.0,"HyperDash":false}]},{"StartTime":207299.0,"Objects":[{"StartTime":207299.0,"Position":405.0,"HyperDash":false}]},{"StartTime":207480.0,"Objects":[{"StartTime":207480.0,"Position":312.0,"HyperDash":false},{"StartTime":207570.0,"Position":265.367828,"HyperDash":false},{"StartTime":207661.0,"Position":263.844818,"HyperDash":false},{"StartTime":207734.0,"Position":266.8324,"HyperDash":false},{"StartTime":207843.0,"Position":312.8251,"HyperDash":false}]},{"StartTime":208026.0,"Objects":[{"StartTime":208026.0,"Position":488.0,"HyperDash":false},{"StartTime":208098.0,"Position":506.0,"HyperDash":false},{"StartTime":208207.0,"Position":488.0,"HyperDash":false}]},{"StartTime":208390.0,"Objects":[{"StartTime":208390.0,"Position":308.0,"HyperDash":false},{"StartTime":208462.0,"Position":292.187836,"HyperDash":false},{"StartTime":208571.0,"Position":223.0,"HyperDash":false}]},{"StartTime":208753.0,"Objects":[{"StartTime":208753.0,"Position":404.0,"HyperDash":false},{"StartTime":208825.0,"Position":411.0,"HyperDash":false},{"StartTime":208934.0,"Position":404.0,"HyperDash":false}]},{"StartTime":209117.0,"Objects":[{"StartTime":209117.0,"Position":308.0,"HyperDash":false}]},{"StartTime":209299.0,"Objects":[{"StartTime":209299.0,"Position":392.0,"HyperDash":false}]},{"StartTime":209480.0,"Objects":[{"StartTime":209480.0,"Position":216.0,"HyperDash":false},{"StartTime":209552.0,"Position":192.187851,"HyperDash":false},{"StartTime":209661.0,"Position":131.0,"HyperDash":false}]},{"StartTime":209844.0,"Objects":[{"StartTime":209844.0,"Position":308.0,"HyperDash":false},{"StartTime":209916.0,"Position":293.0,"HyperDash":false},{"StartTime":210025.0,"Position":308.0,"HyperDash":false}]},{"StartTime":210117.0,"Objects":[{"StartTime":210117.0,"Position":264.0,"HyperDash":false}]},{"StartTime":210208.0,"Objects":[{"StartTime":210208.0,"Position":220.0,"HyperDash":false}]},{"StartTime":210390.0,"Objects":[{"StartTime":210390.0,"Position":308.0,"HyperDash":false},{"StartTime":210480.0,"Position":347.148743,"HyperDash":false},{"StartTime":210571.0,"Position":392.765839,"HyperDash":false},{"StartTime":210644.0,"Position":414.953156,"HyperDash":false},{"StartTime":210753.0,"Position":478.0,"HyperDash":false}]},{"StartTime":210935.0,"Objects":[{"StartTime":210935.0,"Position":296.0,"HyperDash":false},{"StartTime":211007.0,"Position":313.0,"HyperDash":false},{"StartTime":211116.0,"Position":296.0,"HyperDash":false}]},{"StartTime":211299.0,"Objects":[{"StartTime":211299.0,"Position":120.0,"HyperDash":false}]},{"StartTime":211389.0,"Objects":[{"StartTime":211389.0,"Position":120.0,"HyperDash":false}]},{"StartTime":211480.0,"Objects":[{"StartTime":211480.0,"Position":120.0,"HyperDash":false}]},{"StartTime":211662.0,"Objects":[{"StartTime":211662.0,"Position":296.0,"HyperDash":false},{"StartTime":211734.0,"Position":276.187836,"HyperDash":false},{"StartTime":211843.0,"Position":211.0,"HyperDash":false}]},{"StartTime":212026.0,"Objects":[{"StartTime":212026.0,"Position":120.0,"HyperDash":false},{"StartTime":212098.0,"Position":122.0,"HyperDash":false},{"StartTime":212207.0,"Position":120.0,"HyperDash":false}]},{"StartTime":212390.0,"Objects":[{"StartTime":212390.0,"Position":296.0,"HyperDash":false}]},{"StartTime":212571.0,"Objects":[{"StartTime":212571.0,"Position":196.0,"HyperDash":true}]},{"StartTime":212753.0,"Objects":[{"StartTime":212753.0,"Position":456.0,"HyperDash":false},{"StartTime":212825.0,"Position":465.0,"HyperDash":false},{"StartTime":212934.0,"Position":456.0,"HyperDash":false}]},{"StartTime":213117.0,"Objects":[{"StartTime":213117.0,"Position":276.0,"HyperDash":false},{"StartTime":213189.0,"Position":223.187851,"HyperDash":false},{"StartTime":213298.0,"Position":191.0,"HyperDash":false}]},{"StartTime":213480.0,"Objects":[{"StartTime":213480.0,"Position":284.0,"HyperDash":false},{"StartTime":213552.0,"Position":282.0,"HyperDash":false},{"StartTime":213661.0,"Position":284.0,"HyperDash":false}]},{"StartTime":213844.0,"Objects":[{"StartTime":213844.0,"Position":104.0,"HyperDash":false},{"StartTime":213916.0,"Position":147.812149,"HyperDash":false},{"StartTime":214025.0,"Position":189.0,"HyperDash":true}]},{"StartTime":214208.0,"Objects":[{"StartTime":214208.0,"Position":448.0,"HyperDash":false},{"StartTime":214280.0,"Position":454.0,"HyperDash":false},{"StartTime":214389.0,"Position":448.0,"HyperDash":false}]},{"StartTime":214480.0,"Objects":[{"StartTime":214480.0,"Position":400.0,"HyperDash":false}]},{"StartTime":214571.0,"Objects":[{"StartTime":214571.0,"Position":352.0,"HyperDash":false}]},{"StartTime":214753.0,"Objects":[{"StartTime":214753.0,"Position":448.0,"HyperDash":false}]},{"StartTime":214935.0,"Objects":[{"StartTime":214935.0,"Position":272.0,"HyperDash":false},{"StartTime":215007.0,"Position":280.0,"HyperDash":false},{"StartTime":215116.0,"Position":272.0,"HyperDash":false}]},{"StartTime":215299.0,"Objects":[{"StartTime":215299.0,"Position":96.0,"HyperDash":false},{"StartTime":215371.0,"Position":74.18785,"HyperDash":false},{"StartTime":215480.0,"Position":11.0,"HyperDash":true}]},{"StartTime":215662.0,"Objects":[{"StartTime":215662.0,"Position":272.0,"HyperDash":false},{"StartTime":215734.0,"Position":321.812164,"HyperDash":false},{"StartTime":215843.0,"Position":357.0,"HyperDash":false}]},{"StartTime":216026.0,"Objects":[{"StartTime":216026.0,"Position":180.0,"HyperDash":false},{"StartTime":216098.0,"Position":185.0,"HyperDash":false},{"StartTime":216207.0,"Position":180.0,"HyperDash":false}]},{"StartTime":216390.0,"Objects":[{"StartTime":216390.0,"Position":356.0,"HyperDash":false}]},{"StartTime":216571.0,"Objects":[{"StartTime":216571.0,"Position":256.0,"HyperDash":false}]},{"StartTime":216753.0,"Objects":[{"StartTime":216753.0,"Position":436.0,"HyperDash":false},{"StartTime":216825.0,"Position":411.187836,"HyperDash":false},{"StartTime":216934.0,"Position":351.0,"HyperDash":false}]},{"StartTime":217117.0,"Objects":[{"StartTime":217117.0,"Position":96.0,"HyperDash":false},{"StartTime":217207.0,"Position":60.8512421,"HyperDash":false},{"StartTime":217298.0,"Position":12.7658386,"HyperDash":false},{"StartTime":217371.0,"Position":64.95316,"HyperDash":false},{"StartTime":217480.0,"Position":98.0,"HyperDash":false}]},{"StartTime":217662.0,"Objects":[{"StartTime":217662.0,"Position":276.0,"HyperDash":false},{"StartTime":217752.0,"Position":324.148743,"HyperDash":false},{"StartTime":217843.0,"Position":361.0,"HyperDash":false},{"StartTime":217916.0,"Position":327.046844,"HyperDash":false},{"StartTime":218025.0,"Position":276.0,"HyperDash":false}]},{"StartTime":218208.0,"Objects":[{"StartTime":218208.0,"Position":98.0,"HyperDash":false},{"StartTime":218280.0,"Position":87.0,"HyperDash":false},{"StartTime":218389.0,"Position":98.0,"HyperDash":true}]},{"StartTime":218571.0,"Objects":[{"StartTime":218571.0,"Position":360.0,"HyperDash":false},{"StartTime":218661.0,"Position":414.2143,"HyperDash":false},{"StartTime":218752.0,"Position":412.42868,"HyperDash":false},{"StartTime":218825.0,"Position":397.238861,"HyperDash":false},{"StartTime":218934.0,"Position":359.061737,"HyperDash":false}]},{"StartTime":219026.0,"Objects":[{"StartTime":219026.0,"Position":312.0,"HyperDash":false}]},{"StartTime":219117.0,"Objects":[{"StartTime":219117.0,"Position":264.0,"HyperDash":false}]},{"StartTime":219299.0,"Objects":[{"StartTime":219299.0,"Position":88.0,"HyperDash":false},{"StartTime":219371.0,"Position":104.812149,"HyperDash":false},{"StartTime":219480.0,"Position":173.0,"HyperDash":false}]},{"StartTime":219662.0,"Objects":[{"StartTime":219662.0,"Position":268.0,"HyperDash":false},{"StartTime":219734.0,"Position":274.0,"HyperDash":false},{"StartTime":219843.0,"Position":268.0,"HyperDash":false}]},{"StartTime":220026.0,"Objects":[{"StartTime":220026.0,"Position":88.0,"HyperDash":false},{"StartTime":220098.0,"Position":105.0,"HyperDash":false},{"StartTime":220207.0,"Position":88.0,"HyperDash":false}]},{"StartTime":220390.0,"Objects":[{"StartTime":220390.0,"Position":268.0,"HyperDash":false}]},{"StartTime":220571.0,"Objects":[{"StartTime":220571.0,"Position":180.0,"HyperDash":false}]},{"StartTime":220753.0,"Objects":[{"StartTime":220753.0,"Position":436.0,"HyperDash":false},{"StartTime":220825.0,"Position":425.0,"HyperDash":false},{"StartTime":220934.0,"Position":436.0,"HyperDash":false}]},{"StartTime":221117.0,"Objects":[{"StartTime":221117.0,"Position":260.0,"HyperDash":false},{"StartTime":221189.0,"Position":241.187851,"HyperDash":false},{"StartTime":221298.0,"Position":175.0,"HyperDash":true}]},{"StartTime":221480.0,"Objects":[{"StartTime":221480.0,"Position":436.0,"HyperDash":false},{"StartTime":221552.0,"Position":398.187836,"HyperDash":false},{"StartTime":221661.0,"Position":351.0,"HyperDash":false}]},{"StartTime":221753.0,"Objects":[{"StartTime":221753.0,"Position":308.0,"HyperDash":false}]},{"StartTime":221844.0,"Objects":[{"StartTime":221844.0,"Position":264.0,"HyperDash":false}]},{"StartTime":222026.0,"Objects":[{"StartTime":222026.0,"Position":356.0,"HyperDash":false}]},{"StartTime":222208.0,"Objects":[{"StartTime":222208.0,"Position":100.0,"HyperDash":false},{"StartTime":222280.0,"Position":74.18785,"HyperDash":false},{"StartTime":222389.0,"Position":15.0,"HyperDash":false}]},{"StartTime":222571.0,"Objects":[{"StartTime":222571.0,"Position":108.0,"HyperDash":false},{"StartTime":222643.0,"Position":119.0,"HyperDash":false},{"StartTime":222752.0,"Position":108.0,"HyperDash":true}]},{"StartTime":222935.0,"Objects":[{"StartTime":222935.0,"Position":368.0,"HyperDash":false},{"StartTime":223025.0,"Position":410.5,"HyperDash":false},{"StartTime":223116.0,"Position":368.0,"HyperDash":false}]},{"StartTime":223299.0,"Objects":[{"StartTime":223299.0,"Position":188.0,"HyperDash":false}]},{"StartTime":223480.0,"Objects":[{"StartTime":223480.0,"Position":280.0,"HyperDash":false}]},{"StartTime":223571.0,"Objects":[{"StartTime":223571.0,"Position":328.0,"HyperDash":false}]},{"StartTime":223662.0,"Objects":[{"StartTime":223662.0,"Position":376.0,"HyperDash":false},{"StartTime":223734.0,"Position":377.0,"HyperDash":false},{"StartTime":223843.0,"Position":376.0,"HyperDash":false}]},{"StartTime":224026.0,"Objects":[{"StartTime":224026.0,"Position":196.0,"HyperDash":false},{"StartTime":224098.0,"Position":161.187851,"HyperDash":false},{"StartTime":224207.0,"Position":111.0,"HyperDash":true}]},{"StartTime":224390.0,"Objects":[{"StartTime":224390.0,"Position":376.0,"HyperDash":false},{"StartTime":224480.0,"Position":398.812927,"HyperDash":false},{"StartTime":224571.0,"Position":435.886963,"HyperDash":false},{"StartTime":224644.0,"Position":405.66272,"HyperDash":false},{"StartTime":224753.0,"Position":375.3338,"HyperDash":false}]},{"StartTime":225117.0,"Objects":[{"StartTime":225117.0,"Position":96.0,"HyperDash":false},{"StartTime":225189.0,"Position":107.0,"HyperDash":false},{"StartTime":225298.0,"Position":96.0,"HyperDash":false}]},{"StartTime":225480.0,"Objects":[{"StartTime":225480.0,"Position":180.0,"HyperDash":false}]},{"StartTime":225662.0,"Objects":[{"StartTime":225662.0,"Position":356.0,"HyperDash":false}]},{"StartTime":225753.0,"Objects":[{"StartTime":225753.0,"Position":400.0,"HyperDash":false}]},{"StartTime":225844.0,"Objects":[{"StartTime":225844.0,"Position":444.0,"HyperDash":false},{"StartTime":225916.0,"Position":453.0,"HyperDash":false},{"StartTime":226025.0,"Position":444.0,"HyperDash":false}]},{"StartTime":226208.0,"Objects":[{"StartTime":226208.0,"Position":360.0,"HyperDash":false},{"StartTime":226280.0,"Position":323.187836,"HyperDash":false},{"StartTime":226389.0,"Position":275.0,"HyperDash":false}]},{"StartTime":226571.0,"Objects":[{"StartTime":226571.0,"Position":96.0,"HyperDash":false},{"StartTime":226643.0,"Position":104.0,"HyperDash":false},{"StartTime":226752.0,"Position":96.0,"HyperDash":false}]},{"StartTime":226935.0,"Objects":[{"StartTime":226935.0,"Position":181.0,"HyperDash":false},{"StartTime":227007.0,"Position":146.187851,"HyperDash":false},{"StartTime":227116.0,"Position":96.0,"HyperDash":false}]},{"StartTime":227299.0,"Objects":[{"StartTime":227299.0,"Position":276.0,"HyperDash":false},{"StartTime":227389.0,"Position":322.148743,"HyperDash":false},{"StartTime":227480.0,"Position":360.0,"HyperDash":false},{"StartTime":227553.0,"Position":357.0,"HyperDash":false},{"StartTime":227662.0,"Position":360.0,"HyperDash":false}]},{"StartTime":227844.0,"Objects":[{"StartTime":227844.0,"Position":276.0,"HyperDash":false}]},{"StartTime":228026.0,"Objects":[{"StartTime":228026.0,"Position":96.0,"HyperDash":false},{"StartTime":228098.0,"Position":82.0,"HyperDash":false},{"StartTime":228207.0,"Position":96.0,"HyperDash":false}]},{"StartTime":228390.0,"Objects":[{"StartTime":228390.0,"Position":180.0,"HyperDash":false},{"StartTime":228462.0,"Position":193.0,"HyperDash":false},{"StartTime":228571.0,"Position":180.0,"HyperDash":false}]},{"StartTime":228753.0,"Objects":[{"StartTime":228753.0,"Position":356.0,"HyperDash":false}]},{"StartTime":228935.0,"Objects":[{"StartTime":228935.0,"Position":440.0,"HyperDash":false}]},{"StartTime":229117.0,"Objects":[{"StartTime":229117.0,"Position":440.0,"HyperDash":false}]},{"StartTime":229299.0,"Objects":[{"StartTime":229299.0,"Position":356.0,"HyperDash":false}]},{"StartTime":229480.0,"Objects":[{"StartTime":229480.0,"Position":176.0,"HyperDash":false},{"StartTime":229552.0,"Position":177.0,"HyperDash":false},{"StartTime":229661.0,"Position":176.0,"HyperDash":false}]},{"StartTime":229844.0,"Objects":[{"StartTime":229844.0,"Position":264.0,"HyperDash":false}]},{"StartTime":229934.0,"Objects":[{"StartTime":229934.0,"Position":310.0,"HyperDash":false}]},{"StartTime":230025.0,"Objects":[{"StartTime":230025.0,"Position":356.0,"HyperDash":false}]},{"StartTime":230208.0,"Objects":[{"StartTime":230208.0,"Position":176.0,"HyperDash":false},{"StartTime":230298.0,"Position":147.851242,"HyperDash":false},{"StartTime":230389.0,"Position":91.23416,"HyperDash":false},{"StartTime":230462.0,"Position":41.0468369,"HyperDash":false},{"StartTime":230571.0,"Position":6.0,"HyperDash":false}]},{"StartTime":230753.0,"Objects":[{"StartTime":230753.0,"Position":92.0,"HyperDash":false}]},{"StartTime":230935.0,"Objects":[{"StartTime":230935.0,"Position":268.0,"HyperDash":false},{"StartTime":231007.0,"Position":314.812164,"HyperDash":false},{"StartTime":231116.0,"Position":353.0,"HyperDash":false}]},{"StartTime":231299.0,"Objects":[{"StartTime":231299.0,"Position":260.0,"HyperDash":false},{"StartTime":231371.0,"Position":259.0,"HyperDash":false},{"StartTime":231480.0,"Position":260.0,"HyperDash":false}]},{"StartTime":231571.0,"Objects":[{"StartTime":231571.0,"Position":308.0,"HyperDash":false}]},{"StartTime":231662.0,"Objects":[{"StartTime":231662.0,"Position":356.0,"HyperDash":false},{"StartTime":231752.0,"Position":386.148743,"HyperDash":false},{"StartTime":231843.0,"Position":440.0,"HyperDash":false},{"StartTime":231916.0,"Position":455.0,"HyperDash":false},{"StartTime":232025.0,"Position":440.0,"HyperDash":false}]},{"StartTime":232208.0,"Objects":[{"StartTime":232208.0,"Position":356.0,"HyperDash":false}]},{"StartTime":232390.0,"Objects":[{"StartTime":232390.0,"Position":180.0,"HyperDash":false},{"StartTime":232462.0,"Position":176.0,"HyperDash":false},{"StartTime":232571.0,"Position":180.0,"HyperDash":false}]},{"StartTime":232753.0,"Objects":[{"StartTime":232753.0,"Position":272.0,"HyperDash":false},{"StartTime":232843.0,"Position":272.0,"HyperDash":false},{"StartTime":232934.0,"Position":272.0,"HyperDash":false}]},{"StartTime":233117.0,"Objects":[{"StartTime":233117.0,"Position":92.0,"HyperDash":false},{"StartTime":233207.0,"Position":62.53518,"HyperDash":false},{"StartTime":233298.0,"Position":40.0084,"HyperDash":false},{"StartTime":233371.0,"Position":54.41962,"HyperDash":false},{"StartTime":233480.0,"Position":88.82208,"HyperDash":false}]},{"StartTime":233662.0,"Objects":[{"StartTime":233662.0,"Position":172.0,"HyperDash":false}]},{"StartTime":233844.0,"Objects":[{"StartTime":233844.0,"Position":352.0,"HyperDash":false},{"StartTime":233916.0,"Position":341.0,"HyperDash":false},{"StartTime":234025.0,"Position":352.0,"HyperDash":false}]},{"StartTime":234208.0,"Objects":[{"StartTime":234208.0,"Position":268.0,"HyperDash":false}]},{"StartTime":234390.0,"Objects":[{"StartTime":234390.0,"Position":360.0,"HyperDash":false}]},{"StartTime":234571.0,"Objects":[{"StartTime":234571.0,"Position":172.0,"HyperDash":false},{"StartTime":234661.0,"Position":172.0,"HyperDash":false},{"StartTime":234752.0,"Position":172.0,"HyperDash":false}]},{"StartTime":234935.0,"Objects":[{"StartTime":234935.0,"Position":268.0,"HyperDash":false},{"StartTime":235007.0,"Position":228.187851,"HyperDash":false},{"StartTime":235116.0,"Position":183.0,"HyperDash":false}]},{"StartTime":235298.0,"Objects":[{"StartTime":235298.0,"Position":364.0,"HyperDash":false},{"StartTime":235370.0,"Position":353.0,"HyperDash":false},{"StartTime":235479.0,"Position":364.0,"HyperDash":false}]},{"StartTime":235662.0,"Objects":[{"StartTime":235662.0,"Position":183.0,"HyperDash":false}]},{"StartTime":235752.0,"Objects":[{"StartTime":235752.0,"Position":140.0,"HyperDash":false}]},{"StartTime":235843.0,"Objects":[{"StartTime":235843.0,"Position":98.0,"HyperDash":true}]},{"StartTime":236026.0,"Objects":[{"StartTime":236026.0,"Position":376.0,"HyperDash":false}]},{"StartTime":236390.0,"Objects":[{"StartTime":236390.0,"Position":224.0,"HyperDash":false}]},{"StartTime":236753.0,"Objects":[{"StartTime":236753.0,"Position":496.0,"HyperDash":false},{"StartTime":236843.0,"Position":487.0,"HyperDash":false},{"StartTime":236934.0,"Position":496.0,"HyperDash":false},{"StartTime":237007.0,"Position":494.0,"HyperDash":false},{"StartTime":237116.0,"Position":496.0,"HyperDash":false}]},{"StartTime":237480.0,"Objects":[{"StartTime":237480.0,"Position":266.0,"HyperDash":false},{"StartTime":237548.0,"Position":100.0,"HyperDash":false},{"StartTime":237616.0,"Position":57.0,"HyperDash":false},{"StartTime":237684.0,"Position":199.0,"HyperDash":false},{"StartTime":237752.0,"Position":129.0,"HyperDash":false},{"StartTime":237820.0,"Position":232.0,"HyperDash":false},{"StartTime":237889.0,"Position":464.0,"HyperDash":false},{"StartTime":237957.0,"Position":364.0,"HyperDash":false},{"StartTime":238025.0,"Position":170.0,"HyperDash":false},{"StartTime":238093.0,"Position":496.0,"HyperDash":false},{"StartTime":238161.0,"Position":27.0,"HyperDash":false},{"StartTime":238230.0,"Position":477.0,"HyperDash":false},{"StartTime":238298.0,"Position":163.0,"HyperDash":false},{"StartTime":238366.0,"Position":260.0,"HyperDash":false},{"StartTime":238434.0,"Position":253.0,"HyperDash":false},{"StartTime":238502.0,"Position":423.0,"HyperDash":false},{"StartTime":238571.0,"Position":367.0,"HyperDash":false}]},{"StartTime":238935.0,"Objects":[{"StartTime":238935.0,"Position":256.0,"HyperDash":false},{"StartTime":239025.0,"Position":247.0,"HyperDash":false},{"StartTime":239116.0,"Position":256.0,"HyperDash":false},{"StartTime":239189.0,"Position":240.0,"HyperDash":false},{"StartTime":239298.0,"Position":256.0,"HyperDash":false}]},{"StartTime":239662.0,"Objects":[{"StartTime":239662.0,"Position":78.0,"HyperDash":false},{"StartTime":239713.0,"Position":446.0,"HyperDash":false},{"StartTime":239764.0,"Position":99.0,"HyperDash":false},{"StartTime":239815.0,"Position":155.0,"HyperDash":false},{"StartTime":239866.0,"Position":322.0,"HyperDash":false},{"StartTime":239917.0,"Position":261.0,"HyperDash":false},{"StartTime":239968.0,"Position":22.0,"HyperDash":false},{"StartTime":240019.0,"Position":481.0,"HyperDash":false},{"StartTime":240071.0,"Position":103.0,"HyperDash":false},{"StartTime":240122.0,"Position":316.0,"HyperDash":false},{"StartTime":240173.0,"Position":175.0,"HyperDash":false},{"StartTime":240224.0,"Position":48.0,"HyperDash":false},{"StartTime":240275.0,"Position":307.0,"HyperDash":false},{"StartTime":240326.0,"Position":375.0,"HyperDash":false},{"StartTime":240377.0,"Position":149.0,"HyperDash":false},{"StartTime":240429.0,"Position":250.0,"HyperDash":false},{"StartTime":240480.0,"Position":142.0,"HyperDash":false},{"StartTime":240531.0,"Position":170.0,"HyperDash":false},{"StartTime":240582.0,"Position":281.0,"HyperDash":false},{"StartTime":240633.0,"Position":444.0,"HyperDash":false},{"StartTime":240684.0,"Position":414.0,"HyperDash":false},{"StartTime":240735.0,"Position":321.0,"HyperDash":false},{"StartTime":240787.0,"Position":328.0,"HyperDash":false},{"StartTime":240838.0,"Position":32.0,"HyperDash":false},{"StartTime":240889.0,"Position":259.0,"HyperDash":false},{"StartTime":240940.0,"Position":169.0,"HyperDash":false},{"StartTime":240991.0,"Position":207.0,"HyperDash":false},{"StartTime":241042.0,"Position":464.0,"HyperDash":false},{"StartTime":241093.0,"Position":192.0,"HyperDash":false},{"StartTime":241145.0,"Position":317.0,"HyperDash":false},{"StartTime":241196.0,"Position":376.0,"HyperDash":false},{"StartTime":241247.0,"Position":100.0,"HyperDash":false},{"StartTime":241298.0,"Position":70.0,"HyperDash":false},{"StartTime":241349.0,"Position":287.0,"HyperDash":false},{"StartTime":241400.0,"Position":468.0,"HyperDash":false},{"StartTime":241451.0,"Position":58.0,"HyperDash":false},{"StartTime":241503.0,"Position":352.0,"HyperDash":false},{"StartTime":241554.0,"Position":305.0,"HyperDash":false},{"StartTime":241605.0,"Position":177.0,"HyperDash":false},{"StartTime":241656.0,"Position":414.0,"HyperDash":false},{"StartTime":241707.0,"Position":182.0,"HyperDash":false},{"StartTime":241758.0,"Position":174.0,"HyperDash":false},{"StartTime":241809.0,"Position":89.0,"HyperDash":false},{"StartTime":241861.0,"Position":254.0,"HyperDash":false},{"StartTime":241912.0,"Position":320.0,"HyperDash":false},{"StartTime":241963.0,"Position":406.0,"HyperDash":false},{"StartTime":242014.0,"Position":182.0,"HyperDash":false},{"StartTime":242065.0,"Position":301.0,"HyperDash":false},{"StartTime":242116.0,"Position":169.0,"HyperDash":false},{"StartTime":242167.0,"Position":470.0,"HyperDash":false},{"StartTime":242219.0,"Position":278.0,"HyperDash":false},{"StartTime":242270.0,"Position":146.0,"HyperDash":false},{"StartTime":242321.0,"Position":480.0,"HyperDash":false},{"StartTime":242372.0,"Position":41.0,"HyperDash":false},{"StartTime":242423.0,"Position":51.0,"HyperDash":false},{"StartTime":242474.0,"Position":295.0,"HyperDash":false},{"StartTime":242525.0,"Position":145.0,"HyperDash":false},{"StartTime":242577.0,"Position":237.0,"HyperDash":false},{"StartTime":242628.0,"Position":152.0,"HyperDash":false},{"StartTime":242679.0,"Position":500.0,"HyperDash":false},{"StartTime":242730.0,"Position":278.0,"HyperDash":false},{"StartTime":242781.0,"Position":174.0,"HyperDash":false},{"StartTime":242832.0,"Position":92.0,"HyperDash":false},{"StartTime":242883.0,"Position":248.0,"HyperDash":false},{"StartTime":242935.0,"Position":284.0,"HyperDash":false},{"StartTime":242986.0,"Position":296.0,"HyperDash":false},{"StartTime":243037.0,"Position":325.0,"HyperDash":false},{"StartTime":243088.0,"Position":116.0,"HyperDash":false},{"StartTime":243139.0,"Position":293.0,"HyperDash":false},{"StartTime":243190.0,"Position":511.0,"HyperDash":false},{"StartTime":243241.0,"Position":17.0,"HyperDash":false},{"StartTime":243292.0,"Position":64.0,"HyperDash":false},{"StartTime":243344.0,"Position":486.0,"HyperDash":false},{"StartTime":243395.0,"Position":209.0,"HyperDash":false},{"StartTime":243446.0,"Position":264.0,"HyperDash":false},{"StartTime":243497.0,"Position":47.0,"HyperDash":false},{"StartTime":243548.0,"Position":206.0,"HyperDash":false},{"StartTime":243599.0,"Position":353.0,"HyperDash":false},{"StartTime":243650.0,"Position":244.0,"HyperDash":false},{"StartTime":243702.0,"Position":157.0,"HyperDash":false},{"StartTime":243753.0,"Position":227.0,"HyperDash":false},{"StartTime":243804.0,"Position":167.0,"HyperDash":false},{"StartTime":243855.0,"Position":420.0,"HyperDash":false},{"StartTime":243906.0,"Position":103.0,"HyperDash":false},{"StartTime":243957.0,"Position":188.0,"HyperDash":false},{"StartTime":244008.0,"Position":300.0,"HyperDash":false},{"StartTime":244060.0,"Position":60.0,"HyperDash":false},{"StartTime":244111.0,"Position":120.0,"HyperDash":false},{"StartTime":244162.0,"Position":501.0,"HyperDash":false},{"StartTime":244213.0,"Position":341.0,"HyperDash":false},{"StartTime":244264.0,"Position":181.0,"HyperDash":false},{"StartTime":244315.0,"Position":337.0,"HyperDash":false},{"StartTime":244366.0,"Position":269.0,"HyperDash":false},{"StartTime":244418.0,"Position":398.0,"HyperDash":false},{"StartTime":244469.0,"Position":308.0,"HyperDash":false},{"StartTime":244520.0,"Position":323.0,"HyperDash":false},{"StartTime":244571.0,"Position":201.0,"HyperDash":false},{"StartTime":244622.0,"Position":204.0,"HyperDash":false},{"StartTime":244673.0,"Position":44.0,"HyperDash":false},{"StartTime":244724.0,"Position":217.0,"HyperDash":false},{"StartTime":244776.0,"Position":510.0,"HyperDash":false},{"StartTime":244827.0,"Position":324.0,"HyperDash":false},{"StartTime":244878.0,"Position":131.0,"HyperDash":false},{"StartTime":244929.0,"Position":13.0,"HyperDash":false},{"StartTime":244980.0,"Position":360.0,"HyperDash":false},{"StartTime":245031.0,"Position":510.0,"HyperDash":false},{"StartTime":245082.0,"Position":203.0,"HyperDash":false},{"StartTime":245134.0,"Position":416.0,"HyperDash":false},{"StartTime":245185.0,"Position":162.0,"HyperDash":false},{"StartTime":245236.0,"Position":277.0,"HyperDash":false},{"StartTime":245287.0,"Position":329.0,"HyperDash":false},{"StartTime":245338.0,"Position":357.0,"HyperDash":false},{"StartTime":245389.0,"Position":388.0,"HyperDash":false},{"StartTime":245440.0,"Position":87.0,"HyperDash":false},{"StartTime":245492.0,"Position":462.0,"HyperDash":false},{"StartTime":245543.0,"Position":357.0,"HyperDash":false},{"StartTime":245594.0,"Position":343.0,"HyperDash":false},{"StartTime":245645.0,"Position":248.0,"HyperDash":false},{"StartTime":245696.0,"Position":174.0,"HyperDash":false},{"StartTime":245747.0,"Position":112.0,"HyperDash":false},{"StartTime":245798.0,"Position":420.0,"HyperDash":false},{"StartTime":245850.0,"Position":229.0,"HyperDash":false},{"StartTime":245901.0,"Position":270.0,"HyperDash":false},{"StartTime":245952.0,"Position":3.0,"HyperDash":false},{"StartTime":246003.0,"Position":446.0,"HyperDash":false},{"StartTime":246054.0,"Position":78.0,"HyperDash":false},{"StartTime":246105.0,"Position":157.0,"HyperDash":false},{"StartTime":246156.0,"Position":344.0,"HyperDash":false},{"StartTime":246208.0,"Position":72.0,"HyperDash":false}]}]} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3689906.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3689906.osu new file mode 100644 index 0000000000..070143fcf1 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3689906.osu @@ -0,0 +1,942 @@ +osu file format v14 + +[General] +StackLeniency: 0.7 +Mode: 2 + +[Difficulty] +HPDrainRate:4 +CircleSize:3.2 +OverallDifficulty:8 +ApproachRate:8 +SliderMultiplier:1.7 +SliderTickRate:2 + +[Events] +//Background and Video events +//Break Periods +2,125844,129844 +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Layer 4 (Overlay) +//Storyboard Sound Samples + +[TimingPoints] +390,363.636363636364,4,2,1,60,1,0 +3480,-100,4,2,2,70,0,0 +3662,-100,4,2,1,60,0,0 +4753,-100,4,2,2,50,0,0 +4935,-100,4,2,1,60,0,0 +6208,-100,4,2,3,60,0,0 +6390,-100,4,2,1,60,0,0 +9299,-100,4,2,2,70,0,0 +9480,-100,4,2,1,60,0,0 +12026,-100,4,2,3,70,0,0 +12208,-100,4,2,1,70,0,0 +23662,-83.3333333333333,4,2,3,70,0,0 +24026,-100,4,2,1,80,0,0 +26753,-100,4,2,2,80,0,0 +26935,-100,4,2,1,80,0,0 +28935,-83.3333333333333,4,2,1,80,0,0 +29480,-83.3333333333333,4,2,3,70,0,0 +30026,-100,4,2,1,70,0,0 +30935,-100,4,2,1,30,0,0 +31662,-100,4,2,1,40,0,0 +32390,-100,4,2,1,30,0,0 +32753,-100,4,2,1,40,0,0 +33117,-100,4,2,1,50,0,0 +33480,-100,4,2,1,60,0,0 +33844,-100,4,2,1,70,0,0 +34117,-100,4,2,1,40,0,0 +34208,-100,4,2,1,70,0,0 +34299,-100,4,2,1,40,0,0 +34480,-100,4,2,1,70,0,0 +34662,-100,4,2,1,40,0,0 +34753,-100,4,2,1,70,0,0 +34935,-100,4,2,77,80,0,0 +35299,-83.3333333333333,4,2,3,80,0,0 +35662,-100,4,2,1,80,0,0 +38753,-100,4,2,1,80,0,0 +39117,-100,4,2,1,80,0,0 +44026,-83.3333333333333,4,2,1,80,0,0 +44390,-100,4,2,1,80,0,0 +46208,-100,4,2,1,80,0,0 +46571,-100,4,2,77,90,0,0 +46753,-100,4,2,1,80,0,0 +46935,-100,4,2,3,80,0,0 +47117,-100,4,2,1,80,0,0 +52390,-100,4,2,1,80,0,0 +52753,-100,4,2,1,80,0,0 +55662,-100,4,2,1,85,0,0 +57117,-100,4,2,1,90,0,0 +58208,-100,4,2,77,90,0,0 +58390,-100,4,2,1,80,0,0 +58571,-100,4,2,3,90,0,1 +58753,-100,4,2,1,90,0,1 +69844,-100,4,2,1,90,0,0 +70208,-100,4,2,3,90,0,1 +70390,-100,4,2,1,90,0,1 +82935,-100,4,2,77,90,0,1 +83299,-83.3333333333333,4,2,3,80,0,0 +83662,-100,4,2,1,80,0,0 +88753,-100,4,2,1,80,0,0 +89117,-100,4,2,1,80,0,0 +94571,-100,4,2,77,80,0,0 +94753,-100,4,2,1,80,0,0 +94935,-100,4,2,3,80,0,0 +95117,-100,4,2,1,80,0,0 +106208,-100,4,2,77,80,0,0 +106571,-100,4,2,1,80,0,0 +112390,-100,4,2,77,90,0,0 +112571,-100,4,2,1,80,0,0 +117117,-100,4,2,1,40,0,0 +117480,-100,4,2,1,50,0,0 +117844,-100,4,2,1,60,0,0 +118208,-100,4,2,1,55,0,0 +118571,-100,4,2,1,65,0,0 +118935,-100,4,2,1,75,0,0 +119299,-100,4,2,1,85,0,0 +119662,-100,4,2,3,100,0,0 +120026,-100,4,2,1,30,0,0 +125480,-100,4,2,1,5,0,0 +131299,-100,4,2,4,60,0,0 +136390,-100,4,2,4,60,0,0 +137117,-100,4,2,4,70,0,0 +137480,-100,4,2,4,50,0,0 +138571,-100,4,2,4,60,0,0 +141480,-90.9090909090909,4,2,4,50,0,0 +141844,-100,4,2,4,50,0,0 +142662,-100,4,2,78,50,0,0 +143117,-100,4,2,4,60,0,0 +148753,-100,4,2,78,50,0,0 +148935,-100,4,2,4,60,0,0 +153117,-100,4,2,4,50,0,0 +154571,-100,4,2,78,50,0,0 +154753,-100,4,2,4,60,0,0 +160390,-100,4,2,3,60,0,0 +160571,-100,4,2,4,60,0,0 +163299,-100,4,2,1,30,0,0 +163480,-100,4,2,5,30,0,0 +163571,-100,4,2,1,30,0,0 +163662,-100,4,2,1,40,0,0 +163844,-100,4,2,5,40,0,0 +163935,-100,4,2,1,40,0,0 +164026,-100,4,2,1,50,0,0 +164208,-100,4,2,5,50,0,0 +164299,-100,4,2,1,50,0,0 +164390,-100,4,2,1,60,0,0 +164571,-100,4,2,5,60,0,0 +164662,-100,4,2,1,60,0,0 +164753,-100,4,2,1,70,0,0 +165117,-100,4,2,1,70,0,0 +165480,-100,4,2,1,70,0,0 +165844,-100,4,2,77,80,0,0 +166208,-100,4,2,1,80,0,0 +174571,-100,4,2,1,80,0,0 +174935,-100,4,2,1,80,0,0 +177844,-83.3333333333333,4,2,1,80,0,0 +178208,-100,4,2,1,80,0,0 +186571,-100,4,2,1,80,0,0 +187117,-100,4,2,1,80,0,0 +187480,-100,4,2,1,80,0,0 +188571,-100,4,2,1,80,0,0 +188934,-100,4,2,1,80,0,0 +189480,-83.3333333333333,4,2,3,90,0,1 +189844,-100,4,2,1,90,0,1 +194208,-100,4,2,1,90,0,1 +194571,-100,4,2,1,90,0,1 +195844,-100,4,2,1,90,0,1 +196208,-100,4,2,1,90,0,1 +200753,-100,4,2,1,90,0,1 +200935,-100,4,2,1,90,0,0 +201117,-83.3333333333333,4,2,1,90,0,1 +201480,-100,4,2,1,90,0,1 +212026,-100,4,2,77,90,0,1 +212571,-100,4,2,1,90,0,0 +212753,-100,4,2,3,90,0,1 +212935,-100,4,2,1,90,0,1 +214026,-100,4,2,5,60,0,1 +214117,-100,4,2,1,90,0,1 +215480,-100,4,2,5,60,0,1 +215571,-100,4,2,1,90,0,1 +216935,-100,4,2,5,60,0,1 +217026,-100,4,2,1,90,0,1 +218390,-100,4,2,5,60,0,1 +218481,-100,4,2,1,90,0,1 +219844,-100,4,2,5,60,0,1 +219935,-100,4,2,1,90,0,1 +221299,-100,4,2,5,60,0,1 +221390,-100,4,2,1,90,0,1 +222753,-100,4,2,5,60,0,1 +222844,-100,4,2,1,90,0,1 +224208,-100,4,2,77,90,0,1 +224390,-83.3333333333333,4,2,3,80,0,0 +224753,-100,4,2,1,80,0,0 +225662,-100,4,2,5,60,0,0 +225753,-100,4,2,1,90,0,0 +226026,-100,4,2,5,60,0,0 +226117,-100,4,2,1,90,0,0 +227844,-100,4,2,5,60,0,0 +227935,-100,4,2,1,90,0,0 +228571,-100,4,2,5,60,0,0 +228662,-100,4,2,1,90,0,0 +230026,-100,4,2,5,60,0,0 +230117,-100,4,2,1,90,0,0 +230753,-100,4,2,5,60,0,0 +230844,-100,4,2,1,90,0,0 +231480,-100,4,2,5,60,0,0 +231571,-100,4,2,1,90,0,0 +232208,-100,4,2,5,60,0,0 +232299,-100,4,2,1,90,0,0 +233117,-100,4,2,1,35,0,0 +233480,-100,4,2,1,45,0,0 +233844,-100,4,2,1,55,0,0 +234208,-100,4,2,1,75,0,0 +234299,-100,4,2,1,65,0,0 +234390,-100,4,2,1,75,0,0 +234480,-100,4,2,1,65,0,0 +234571,-100,4,2,1,75,0,0 +234935,-100,4,2,1,85,0,0 +235299,-100,4,2,1,95,0,0 +235662,-100,4,2,1,85,0,0 +236026,-100,4,2,3,80,0,0 +236390,-100,4,2,4,70,0,0 +236753,-100,4,2,78,70,0,0 +237480,-100,4,2,0,50,0,0 +237844,-100,4,2,0,40,0,0 +238208,-100,4,2,0,30,0,0 +238571,-100,4,2,0,20,0,0 +238935,-100,4,2,78,50,0,0 +239662,-100,4,2,0,50,0,0 +240390,-100,4,2,0,45,0,0 +241117,-100,4,2,0,40,0,0 +241844,-100,4,2,0,35,0,0 +242571,-100,4,2,0,30,0,0 +243299,-100,4,2,0,25,0,0 +244026,-100,4,2,0,20,0,0 +244753,-100,4,2,0,15,0,0 +245480,-100,4,2,0,10,0,0 +246208,-100,4,2,0,5,0,0 + +[HitObjects] +124,320,390,6,0,L|124:128,1,170,4|0,0:0|0:0,0:0:0:0: +208,148,935,1,0,0:0:0:0: +380,192,1117,2,0,L|380:16,1,170,8|2,0:0|0:0,0:0:0:0: +208,24,1844,5,0,0:0:0:0: +360,24,2208,1,2,0:0:0:0: +188,24,2390,1,2,0:0:0:0: +152,24,2480,1,2,0:0:0:0: +112,24,2571,2,0,L|112:128,1,85,8|2,0:0|0:0,0:0:0:0: +196,108,2935,1,0,0:0:0:0: +280,108,3117,1,0,0:0:0:0: +196,108,3299,5,2,0:0:0:0: +288,108,3480,2,0,L|288:292,1,170,2|0,0:0|0:0,0:0:0:0: +116,312,4026,1,8,0:0:0:0: +300,280,4390,1,2,0:0:0:0: +28,192,4753,6,0,L|28:100,1,85,4|2,0:0|0:0,0:0:0:0: +112,108,5117,1,0,0:0:0:0: +20,108,5299,1,2,0:0:0:0: +192,108,5480,2,0,L|280:108,2,85,8|2|0,0:0|0:0|0:0,0:0:0:0: +484,364,6208,6,0,L|484:172,1,170,14|0,0:0|0:0,0:0:0:0: +400,192,6753,1,0,0:0:0:0: +228,236,6935,2,0,L|228:60,1,170,8|2,0:0|0:0,0:0:0:0: +396,64,7662,5,0,0:0:0:0: +244,64,8026,1,2,0:0:0:0: +416,64,8208,1,2,0:0:0:0: +452,64,8298,1,2,0:0:0:0: +492,64,8389,2,0,L|492:168,1,85,8|2,0:0|0:0,0:0:0:0: +396,148,8753,1,0,0:0:0:0: +304,148,8935,1,0,0:0:0:0: +212,148,9117,5,2,0:0:0:0: +312,148,9298,2,0,L|312:332,1,170,2|0,0:0|0:0,0:0:0:0: +140,352,9844,1,8,0:0:0:0: +324,320,10208,1,2,0:0:0:0: +136,192,10571,6,0,L|232:192,1,85,2|2,0:0|0:0,0:0:0:0: +128,192,10935,2,0,L|216:192,1,85,0|2,0:0|0:0,0:0:0:0: +384,192,11299,1,8,0:0:0:0: +292,192,11480,1,2,0:0:0:0: +200,192,11662,1,0,0:0:0:0: +488,192,12026,6,0,B|488:108|488:108|400:108,1,170,10|0,0:0|0:0,0:0:0:0: +316,108,12571,1,0,0:0:0:0: +144,108,12753,2,0,L|144:296,1,170,8|2,0:0|0:0,0:0:0:0: +314,278,13480,6,0,L|134:278,1,170,0|2,0:0|0:0,0:0:0:0: +144,278,14026,1,2,0:0:0:0: +314,278,14208,2,0,L|406:278,1,85,8|2,0:0|0:0,0:0:0:0: +304,276,14571,2,0,L|304:172,1,85,0|0,0:0|0:0,0:0:0:0: +132,192,14935,6,0,B|48:192|48:192|48:104,1,170,2|0,0:0|0:0,0:0:0:0: +132,104,15480,1,0,0:0:0:0: +304,48,15662,1,8,0:0:0:0: +132,104,16026,1,2,0:0:0:0: +284,104,16390,6,0,L|284:188,1,85,0|0,0:0|0:0,0:0:0:0: +192,192,16753,1,2,0:0:0:0: +192,192,16935,1,2,0:0:0:0: +364,192,17117,2,0,L|456:192,2,85,8|2|0,0:0|0:0|0:0,0:0:0:0: +64,192,17844,6,0,L|64:292,1,85,2|0,0:0|0:0,0:0:0:0: +148,192,18208,2,0,L|148:288,1,85,0|0,0:0|0:0,0:0:0:0: +320,192,18571,1,8,0:0:0:0: +132,192,18935,1,2,0:0:0:0: +132,192,19299,6,0,L|304:192,1,170,0|2,0:0|0:0,0:0:0:0: +388,192,19844,1,2,0:0:0:0: +216,192,20026,2,0,L|124:192,1,85,8|2,0:0|0:0,0:0:0:0: +224,192,20390,2,0,L|224:100,1,85,0|0,0:0|0:0,0:0:0:0: +52,20,20753,6,0,B|52:108|52:108|140:108,1,170,2|0,0:0|0:0,0:0:0:0: +224,107,21299,1,0,0:0:0:0: +396,192,21480,1,8,0:0:0:0: +224,192,21844,1,2,0:0:0:0: +132,192,22026,1,2,0:0:0:0: +224,192,22208,5,0,0:0:0:0: +176,192,22299,1,2,0:0:0:0: +132,192,22390,1,2,0:0:0:0: +232,192,22571,1,2,0:0:0:0: +404,192,22753,1,8,0:0:0:0: +232,192,22935,2,0,L|232:288,1,85,8|2,0:0|0:0,0:0:0:0: +404,277,23299,1,2,0:0:0:0: +448,276,23389,1,2,0:0:0:0: +492,276,23480,1,2,0:0:0:0: +212,192,23662,6,0,L|8:192,1,203.999993774414,10|0,0:0|0:0,0:0:0:0: +92,192,24208,1,0,0:0:0:0: +272,192,24390,2,0,L|272:96,1,85,8|0,0:0|0:0,0:0:0:0: +180,108,24753,1,2,0:0:0:0: +348,104,25117,6,0,L|252:104,1,85,0|0,0:0|0:0,0:0:0:0: +355,105,25480,1,2,0:0:0:0: +179,105,25662,1,2,0:0:0:0: +135,105,25752,1,2,0:0:0:0: +91,105,25843,2,0,L|7:105,2,85,8|2|0,0:0|0:0|0:0,0:0:0:0: +383,105,26571,5,2,0:0:0:0: +299,105,26753,2,0,B|215:105|215:105|215:193,1,170,2|0,0:0|0:0,0:0:0:0: +391,105,27299,1,8,0:0:0:0: +239,193,27662,2,0,L|239:281,1,85,2|0,0:0|0:0,0:0:0:0: +323,277,28026,5,0,0:0:0:0: +231,277,28208,1,2,0:0:0:0: +315,277,28390,1,0,0:0:0:0: +143,277,28571,1,2,0:0:0:0: +315,277,28753,1,8,0:0:0:0: +407,277,28935,2,0,B|508:276|508:276|508:168,1,203.999993774414,2|0,0:0|0:0,0:0:0:0: +212,192,29480,6,0,B|108:192|108:192|108:92|108:92|212:92,1,305.999990661621,6|0,0:0|0:0,0:0:0:0: +304,92,30208,2,0,L|392:92,2,85,2|0|2,0:0|0:0|0:0,0:0:0:0: +152,96,30935,6,0,L|152:180,1,85,2|2,0:0|0:0,0:0:0:0: +236,192,31299,2,0,L|236:296,1,85,2|2,0:0|0:0,0:0:0:0: +320,276,31662,2,0,L|232:276,2,85,2|2|2,0:0|0:0|0:0,0:0:0:0: +256,192,32390,12,8,33480,0:0:0:0: +428,192,33844,6,0,L|428:132,2,42.5,2|2|2,0:0|0:0|0:0,0:0:0:0: +256,192,34208,2,0,L|160:192,1,85,2|8,0:0|0:0,0:0:0:0: +216,192,34480,1,2,0:0:0:0: +264,192,34571,2,0,L|316:192,2,42.5,2|8|2,0:0|0:0|0:0,0:0:0:0: +92,192,34935,2,0,L|8:192,1,85,12|8,0:0|0:0,0:0:0:0: +288,192,35299,6,0,L|492:192,1,203.999993774414,10|8,3:2|3:2,3:3:0:0: +400,192,35844,1,2,3:2:0:0: +224,192,36026,2,0,L|136:192,1,85,0|2,3:2|3:2,0:0:0:0: +232,192,36390,2,0,L|232:104,1,85,8|0,3:2|3:2,3:3:0:0: +56,32,36753,6,0,L|56:116,1,85,6|0,3:2|3:2,3:3:0:0: +104,120,37026,1,0,3:2:0:0: +152,124,37117,1,8,3:2:0:0: +244,124,37299,1,2,3:2:0:0: +152,124,37480,2,0,L|64:124,1,85,0|2,3:2|3:2,0:0:0:0: +244,124,37844,2,0,L|244:216,1,85,8|0,3:2|3:2,3:3:0:0: +496,296,38208,6,0,B|496:212|496:212|408:212,1,170,6|8,3:2|3:2,3:3:0:0: +504,212,38753,2,0,L|324:212,1,170,2|2,3:2|3:2,3:3:0:0: +156,192,39299,2,0,L|60:192,1,85,8|0,3:2|3:2,3:3:0:0: +252,192,39662,6,0,L|312:192,2,42.5,6|2|2,3:2|3:2|3:2,3:3:0:0: +71,192,40026,2,0,L|71:92,1,85,8|2,3:2|3:2,3:3:0:0: +164,108,40390,2,0,L|80:108,1,85,0|2,3:2|3:2,0:0:0:0: +256,108,40753,2,0,L|340:108,1,85,8|0,3:2|3:2,3:3:0:0: +84,192,41117,6,0,L|276:192,1,170,6|8,3:2|3:2,3:3:0:0: +432,192,41662,2,0,L|432:104,1,85,2|0,3:2|3:2,3:3:0:0: +348,108,42026,1,2,3:2:0:0: +432,192,42208,2,0,L|348:192,1,85,8|0,3:2|3:2,3:3:0:0: +176,192,42571,6,0,L|84:192,1,85,6|0,3:2|3:2,3:3:0:0: +132,192,42844,1,0,3:2:0:0: +176,192,42935,1,8,3:2:0:0: +260,192,43117,2,0,L|176:192,2,85,2|0|2,3:2|3:2|3:2,3:3:0:0: +84,192,43662,2,0,L|84:288,1,85,8|0,3:2|3:2,3:3:0:0: +336,192,44026,6,0,B|436:192|436:192|436:296,1,203.999993774414,6|8,3:2|3:2,3:3:0:0: +344,296,44571,1,2,3:2:0:0: +252,296,44753,2,0,L|252:212,1,85,0|2,3:2|3:2,3:3:0:0: +428,192,45117,2,0,L|340:192,1,85,8|0,3:2|3:2,3:3:0:0: +164,192,45480,5,6,3:2:0:0: +121,192,45570,1,2,3:2:0:0: +79,192,45661,1,2,3:2:0:0: +256,192,45844,2,0,L|256:104,1,85,8|2,3:2|3:2,3:3:0:0: +160,104,46208,2,0,L|244:104,1,85,2|2,3:2|3:2,3:3:0:0: +68,32,46571,2,0,L|68:120,1,85,12|2,3:2|3:2,3:3:0:0: +324,192,46935,6,0,L|408:192,2,85,10|0|8,3:2|3:2|3:2,3:3:0:0: +154,192,47480,2,0,L|338:192,1,170,2|2,3:2|3:2,3:3:0:0: +420,192,48026,2,0,L|420:280,1,85,8|0,3:2|3:2,3:3:0:0: +240,328,48390,6,0,B|156:328,1,85,6|0,3:2|3:2,3:3:0:0: +112,328,48662,1,0,3:2:0:0: +68,328,48753,1,8,3:2:0:0: +160,244,48935,2,0,L|72:244,2,85,2|0|2,3:2|3:2|3:2,0:0:0:0: +336,244,49480,2,0,L|420:244,1,85,8|0,3:2|3:2,3:3:0:0: +164,116,49844,6,0,B|80:116,1,85,6|0,3:2|3:2,3:3:0:0: +79,116,50117,1,0,3:2:0:0: +79,116,50208,1,8,3:2:0:0: +172,116,50390,2,0,B|256:116|256:116|256:28,1,170,2|2,3:2|3:2,3:3:0:0: +80,30,50935,2,0,L|80:126,1,85,8|0,3:2|3:2,3:3:0:0: +256,192,51299,6,0,L|436:192,1,170,6|8,3:2|3:2,3:3:0:0: +340,192,51844,1,2,3:2:0:0: +426,192,52026,2,0,L|338:192,1,85,0|2,3:2|3:2,3:3:0:0: +164,192,52390,2,0,L|64:192,1,85,8|0,3:2|0:0,3:3:0:0: +336,72,52753,6,0,L|508:72,1,170,6|8,3:2|3:2,3:3:0:0: +328,160,53299,2,0,L|500:160,1,170,2|2,3:2|3:2,3:3:0:0: +412,160,53844,2,0,L|412:260,1,85,8|0,3:2|3:2,3:3:0:0: +236,192,54208,6,0,L|144:192,1,85,6|0,3:2|3:2,3:3:0:0: +192,192,54480,1,0,3:2:0:0: +236,192,54571,1,8,3:2:0:0: +320,192,54753,1,2,3:2:0:0: +236,192,54935,1,0,3:2:0:0: +152,192,55117,1,2,3:2:0:0: +328,192,55299,2,0,L|328:280,1,85,8|0,3:2|3:2,3:3:0:0: +72,192,55662,6,0,L|72:100,1,85,6|0,3:2|3:2,3:3:0:0: +116,104,55935,1,0,3:2:0:0: +160,100,56026,1,8,3:2:0:0: +244,100,56208,2,0,L|156:100,2,85,2|0|2,3:2|3:2|3:2,3:3:0:0: +72,107,56753,2,0,L|72:19,1,85,8|0,3:2|3:2,0:0:0:0: +248,192,57117,6,0,L|292:192,2,42.5,2|2|2,3:2|3:2|3:2,0:0:0:0: +78,192,57481,2,0,L|80:92,1,85,8|2,3:2|3:2,0:0:0:0: +164,107,57844,2,0,L|64:107,1,85,8|2,3:2|3:2,3:3:0:0: +248,192,58208,2,0,L|164:192,1,85,12|2,3:2|3:2,3:3:0:0: +416,192,58571,6,0,B|500:192|500:192|412:192,1,170,10|8,3:2|3:2,3:3:0:0: +320,192,59117,1,2,3:2:0:0: +140,192,59299,2,0,L|56:192,2,85,0|2|8,3:2|3:2|3:2,0:0:0:0: +428,192,60026,6,0,L|428:104,1,85,2|0,3:2|3:2,3:3:0:0: +332,108,60390,2,0,L|420:108,1,85,8|2,3:2|3:2,3:3:0:0: +324,108,60753,1,2,3:2:0:0: +366,108,60843,1,2,3:2:0:0: +409,108,60934,1,2,3:2:0:0: +228,108,61117,2,0,L|140:108,1,85,8|0,3:2|3:2,3:3:0:0: +324,108,61480,6,0,L|324:280,1,170,2|8,3:2|3:2,3:3:0:0: +228,280,62026,1,2,3:2:0:0: +408,192,62208,2,0,L|312:192,2,85,0|2|8,3:2|3:2|3:2,3:3:0:0: +120,192,62935,6,0,L|72:192,2,42.5,2|2|2,3:2|3:2|3:2,0:0:0:0: +216,192,63299,2,0,L|216:96,1,85,8|0,3:2|3:2,3:3:0:0: +396,60,63662,2,0,L|312:60,1,85,2|0,3:2|3:2,3:3:0:0: +148,192,64026,1,8,3:2:0:0: +320,60,64208,1,2,3:2:0:0: +140,192,64390,6,0,B|56:192|56:192|56:104,1,170,2|8,3:2|3:2,0:0:0:0: +140,104,64935,1,2,3:2:0:0: +396,145,65117,2,0,L|396:57,1,85,0|2,3:2|3:2,0:0:0:0: +312,61,65480,1,8,3:2:0:0: +404,61,65662,1,0,3:2:0:0: +300,60,65844,6,0,L|212:60,1,85,2|0,3:2|3:2,3:3:0:0: +392,60,66208,2,0,L|392:160,1,85,8|2,3:2|3:2,3:3:0:0: +136,192,66571,2,0,L|136:104,1,85,2|2,3:2|3:2,3:3:0:0: +307,145,66935,2,0,L|395:145,1,85,8|0,3:2|3:2,3:3:0:0: +476,144,67299,6,0,L|476:244,1,85,2|0,3:2|3:2,3:3:0:0: +307,145,67662,2,0,L|307:45,1,85,8|2,3:2|3:2,3:3:0:0: +48,192,68026,2,0,L|140:192,1,85,0|2,3:2|3:2,3:3:0:0: +307,145,68390,2,0,L|307:233,1,85,8|0,3:2|3:2,3:3:0:0: +222,230,68753,6,0,L|326:230,1,85,2|2,3:2|3:2,0:0:0:0: +136,228,69117,2,0,L|136:324,1,85,8|2,3:2|3:2,3:3:0:0: +228,312,69480,2,0,L|132:312,1,85,2|2,3:2|3:2,3:3:0:0: +236,312,69844,2,0,L|327:312,1,85,8|0,3:2|3:2,3:3:0:0: +60,312,70208,6,0,B|60:228|60:228|148:228,1,170,10|8,3:2|3:2,3:3:0:0: +232,228,70753,1,2,3:2:0:0: +412,192,70935,2,0,L|320:192,2,85,0|2|8,3:2|3:2|3:2,0:0:0:0: +124,192,71662,6,0,L|124:104,1,85,2|0,3:2|3:2,3:3:0:0: +220,108,72026,2,0,L|320:108,1,85,8|2,3:2|3:2,3:3:0:0: +212,108,72389,1,2,3:2:0:0: +316,108,72571,1,2,3:2:0:0: +136,108,72753,2,0,L|48:108,1,85,8|0,3:2|3:2,3:3:0:0: +316,108,73116,6,0,B|400:108|400:108|400:200,1,170,2|8,3:2|3:2,3:3:0:0: +316,192,73662,1,2,3:2:0:0: +144,192,73844,1,2,3:2:0:0: +236,192,74026,1,2,3:2:0:0: +328,192,74208,1,8,3:2:0:0: +56,192,74571,5,2,3:2:0:0: +228,192,74753,1,2,3:2:0:0: +400,192,74935,2,0,L|400:96,1,85,8|0,3:2|3:2,3:3:0:0: +308,108,75298,2,0,L|392:108,1,85,2|2,3:2|3:2,3:3:0:0: +232,192,75662,1,8,3:2:0:0: +401,107,75844,1,2,3:2:0:0: +224,192,76026,6,0,B|140:192|140:192|228:192,1,170,2|8,3:2|3:2,0:0:0:0: +312,192,76571,1,2,3:2:0:0: +56,192,76753,2,0,L|56:104,1,85,0|2,3:2|3:2,0:0:0:0: +140,108,77116,1,8,3:2:0:0: +48,108,77298,1,0,3:2:0:0: +148,107,77480,6,0,L|236:107,1,85,2|0,3:2|3:2,3:3:0:0: +408,108,77844,2,0,L|408:208,1,85,8|2,3:2|3:2,3:3:0:0: +236,192,78207,2,0,L|320:192,1,85,0|2,3:2|3:2,3:3:0:0: +493,193,78571,2,0,L|409:193,1,85,8|0,3:2|3:2,3:3:0:0: +504,192,78935,5,2,3:2:0:0: +332,192,79117,1,2,3:2:0:0: +284,192,79208,1,0,0:0:0:0: +236,192,79298,2,0,L|236:92,1,85,8|0,3:2|3:2,3:3:0:0: +60,28,79662,2,0,L|60:119,1,85,0|2,3:2|3:2,3:3:0:0: +236,107,80026,2,0,L|328:107,1,85,8|2,3:2|3:2,3:3:0:0: +228,108,80389,5,2,3:2:0:0: +228,150,80479,1,2,3:2:0:0: +228,193,80570,1,2,3:2:0:0: +404,192,80753,2,0,L|404:288,1,85,8|2,3:2|3:2,3:3:0:0: +227,280,81116,2,0,L|323:280,1,85,0|2,3:2|3:2,3:3:0:0: +404,277,81480,2,0,L|313:277,1,85,8|2,3:2|3:2,3:3:0:0: +133,193,81844,6,0,L|89:193,2,42.5,2|2|2,3:2|3:2|3:2,0:0:0:0: +303,193,82208,2,0,L|217:193,1,85,8|0,3:2|3:2,3:3:0:0: +264,192,82480,1,2,3:2:0:0: +313,193,82572,2,0,L|229:193,1,85,8|2,3:2|3:2,3:3:0:0: +48,193,82935,2,0,L|132:193,1,85,12|0,3:2|3:2,3:3:0:0: +392,192,83299,6,0,B|496:192|496:192|496:88,1,203.999993774414,10|8,3:2|3:2,0:0:0:0: +452,92,83753,1,0,3:2:0:0: +408,92,83844,1,2,3:2:0:0: +324,92,84026,2,0,L|324:-8,1,85,0|2,3:2|3:2,3:3:0:0: +152,8,84390,2,0,L|152:56,1,42.5,8|2,3:2|3:2,3:3:0:0: +248,92,84662,1,2,3:2:0:0: +248,92,84753,6,0,L|156:92,1,85,2|0,3:2|3:2,3:3:0:0: +332,92,85117,2,0,L|332:152,2,42.5,8|0|2,3:2|3:2|3:2,3:3:0:0: +244,192,85480,1,0,3:2:0:0: +332,92,85662,1,2,3:2:0:0: +156,192,85844,2,0,L|68:192,1,85,8|2,3:2|3:2,3:3:0:0: +164,192,86208,6,0,L|256:192,1,85,2|0,3:2|3:2,3:3:0:0: +80,296,86571,1,8,3:2:0:0: +122,296,86661,1,0,3:2:0:0: +165,296,86752,1,2,3:2:0:0: +252,296,86935,1,0,3:2:0:0: +156,296,87117,1,2,3:2:0:0: +328,296,87299,2,0,L|328:232,1,42.5,8|2,3:2|3:2,3:3:0:0: +152,192,87662,6,0,L|104:192,2,42.5,2|0|2,3:2|3:2|3:2,0:0:0:0: +236,192,88026,2,0,L|144:192,1,85,8|2,3:2|3:2,3:3:0:0: +328,192,88390,2,0,L|328:104,1,85,2|2,3:2|3:2,3:3:0:0: +152,32,88753,2,0,L|64:32,1,85,8|0,3:2|3:2,3:3:0:0: +324,32,89117,6,0,L|496:32,1,170,2|8,3:2|3:2,3:3:0:0: +452,32,89571,1,0,3:2:0:0: +408,32,89662,1,0,3:2:0:0: +324,32,89844,2,0,L|324:128,1,85,2|2,3:2|3:2,3:3:0:0: +148,192,90208,2,0,L|148:244,1,42.5,8|2,3:2|3:2,3:3:0:0: +232,192,90480,1,2,3:2:0:0: +284,192,90571,6,0,L|284:280,1,85,2|0,3:2|3:2,3:3:0:0: +236,316,90844,2,0,L|144:316,1,85,2|0,3:2|3:2,3:3:0:0: +152,316,91117,1,2,3:2:0:0: +236,316,91299,1,2,3:2:0:0: +144,316,91480,1,2,3:2:0:0: +320,316,91662,2,0,L|320:216,1,85,8|2,3:2|3:2,3:3:0:0: +224,192,92026,6,0,L|136:192,1,85,2|0,3:2|3:2,3:3:0:0: +92,192,92299,2,0,L|184:192,1,85,2|0,3:2|3:2,3:3:0:0: +224,192,92571,1,2,3:2:0:0: +132,192,92753,2,0,L|216:192,1,85,2|2,3:2|3:2,3:3:0:0: +392,192,93117,2,0,L|392:104,1,85,8|2,3:2|3:2,0:0:0:0: +216,44,93480,5,2,3:2:0:0: +173,44,93570,1,2,3:2:0:0: +131,44,93661,1,2,3:2:0:0: +224,128,93844,1,8,3:2:0:0: +181,128,93934,1,0,3:2:0:0: +139,128,94025,1,2,3:2:0:0: +312,128,94208,2,0,L|396:128,1,85,8|2,3:2|3:2,3:3:0:0: +220,224,94571,2,0,L|136:224,1,85,12|2,3:2|3:2,3:3:0:0: +392,224,94935,6,0,L|484:224,1,85,10|0,3:2|3:2,3:3:0:0: +384,224,95299,2,0,L|384:128,1,85,8|0,3:2|3:2,3:3:0:0: +212,224,95662,1,2,3:2:0:0: +306,224,95844,1,2,3:2:0:0: +477,224,96026,2,0,L|477:136,1,85,8|0,3:2|3:2,3:3:0:0: +300,136,96390,6,0,L|212:136,1,85,6|0,3:2|3:2,3:3:0:0: +308,136,96753,2,0,L|308:44,1,85,8|2,3:2|3:2,3:2:0:0: +136,136,97117,1,2,3:2:0:0: +300,136,97299,1,2,3:2:0:0: +128,136,97480,2,0,L|128:40,1,85,8|0,3:2|3:2,3:3:0:0: +300,136,97844,6,0,L|212:136,1,85,6|0,3:2|3:2,3:3:0:0: +308,136,98208,1,8,3:2:0:0: +308,93,98298,1,0,0:0:0:0: +308,51,98389,1,0,3:2:0:0: +136,40,98571,2,0,L|224:40,1,85,2|2,3:2|3:2,3:3:0:0: +404,140,98935,2,0,L|404:240,1,85,8|0,3:2|3:2,0:0:0:0: +224,288,99299,6,0,L|136:288,1,85,2|2,3:2|3:2,3:3:0:0: +312,288,99662,2,0,L|312:196,1,85,8|2,3:2|3:2,3:3:0:0: +220,192,100026,1,0,3:2:0:0: +312,288,100208,1,2,3:2:0:0: +136,192,100390,2,0,L|52:192,1,85,8|0,3:2|3:2,3:3:0:0: +308,192,100753,6,0,B|392:192,1,85,6|0,3:2|3:2,3:3:0:0: +216,192,101117,2,0,L|216:104,1,85,8|2,3:2|3:2,3:3:0:0: +300,108,101480,1,0,3:2:0:0: +208,108,101662,1,2,3:2:0:0: +384,108,101844,2,0,L|384:12,1,85,8|0,3:2|3:2,3:3:0:0: +208,108,102208,6,0,L|104:108,1,85,6|0,3:2|3:2,3:3:0:0: +216,108,102571,2,0,L|216:192,1,85,8|2,3:2|3:2,0:0:0:0: +52,108,102935,1,2,3:2:0:0: +224,192,103117,1,2,3:2:0:0: +44,108,103299,2,0,L|44:204,1,85,8|2,3:2|3:2,3:3:0:0: +136,192,103662,6,0,L|224:192,1,85,6|0,3:2|3:2,3:3:0:0: +268,192,103935,1,0,3:2:0:0: +316,192,104026,2,0,L|316:96,1,85,8|2,3:2|3:2,3:3:0:0: +140,36,104390,2,0,L|228:36,1,85,2|2,3:2|3:2,0:0:0:0: +400,36,104753,2,0,L|400:136,1,85,8|0,3:2|3:2,3:3:0:0: +224,192,105117,5,2,3:2:0:0: +181,192,105207,1,2,3:2:0:0: +139,192,105298,1,2,3:2:0:0: +309,192,105480,2,0,L|221:192,1,85,8|2,3:2|3:2,3:3:0:0: +128,192,105844,1,0,3:2:0:0: +216,192,106026,1,2,3:2:0:0: +393,192,106208,2,0,L|493:192,1,85,12|0,3:2|0:0,3:3:0:0: +216,276,106571,6,0,L|128:276,1,85,6|0,3:2|3:2,3:3:0:0: +84,276,106844,1,0,3:2:0:0: +131,276,106935,2,0,L|216:276,1,85,8|2,3:2|3:2,3:3:0:0: +312,276,107299,1,0,3:2:0:0: +212,276,107480,1,2,3:2:0:0: +392,276,107662,2,0,L|392:176,1,85,8|2,3:2|3:2,3:3:0:0: +136,192,108026,6,0,B|44:192,1,85,6|0,3:2|3:2,3:3:0:0: +144,192,108390,2,0,L|144:104,1,85,8|0,3:2|3:2,0:0:0:0: +304,68,108753,1,2,3:2:0:0: +140,192,108935,1,2,3:2:0:0: +312,68,109117,2,0,L|312:168,1,85,8|2,3:2|3:2,3:3:0:0: +56,192,109480,6,0,L|56:284,1,85,6|0,3:2|3:2,3:3:0:0: +140,280,109844,1,8,3:2:0:0: +182,280,109934,1,0,3:2:0:0: +225,280,110025,1,2,3:2:0:0: +56,277,110208,1,2,3:2:0:0: +152,280,110390,1,2,3:2:0:0: +52,277,110571,2,0,L|52:189,1,85,8|0,3:2|0:0,3:3:0:0: +312,192,110935,6,0,L|396:192,1,85,2|2,3:2|3:2,3:3:0:0: +304,192,111299,1,8,3:2:0:0: +404,192,111480,1,2,3:2:0:0: +312,192,111662,1,0,3:2:0:0: +269,192,111752,1,0,3:2:0:0: +227,192,111843,1,2,3:2:0:0: +328,192,112026,2,0,L|328:96,1,85,8|0,3:2|3:2,3:3:0:0: +68,192,112390,6,0,L|68:104,1,85,6|0,3:2|3:2,3:3:0:0: +160,108,112753,2,0,L|248:108,1,85,8|2,3:2|3:2,0:0:0:0: +420,108,113117,2,0,L|420:196,1,85,0|2,3:2|3:2,0:0:0:0: +328,192,113480,1,8,3:2:0:0: +285,192,113570,1,0,0:0:0:0: +243,192,113661,1,0,3:2:0:0: +492,192,113844,6,4,L|492:292,1,85,6|4,3:2|3:2,3:3:0:0: +396,276,114208,2,0,L|304:276,1,85,8|2,3:2|3:2,3:3:0:0: +140,276,114571,1,2,3:2:0:0: +311,276,114753,1,2,3:2:0:0: +140,276,114935,2,0,L|140:192,1,85,8|0,3:2|3:2,3:3:0:0: +396,192,115299,6,0,L|492:192,1,85,6|0,3:2|3:2,3:3:0:0: +308,192,115662,2,0,L|308:96,1,85,8|2,3:2|3:2,0:0:0:0: +136,192,116026,1,2,3:2:0:0: +228,192,116208,1,2,3:2:0:0: +56,192,116390,2,0,L|56:96,1,85,8|2,3:2|3:2,0:0:0:0: +312,192,116753,6,0,L|312:96,1,85,10|2,3:2|3:2,3:3:0:0: +484,28,117117,2,0,L|484:84,2,42.5,8|2|2,3:2|3:2|3:2,3:3:0:0: +392,28,117480,1,8,3:2:0:0: +476,28,117662,1,2,3:2:0:0: +304,28,117844,1,8,3:2:0:0: +262,28,117934,1,2,3:2:0:0: +219,28,118025,1,2,3:2:0:0: +476,28,118208,5,0,0:0:0:0: +476,28,118299,1,0,0:0:0:0: +432,28,118390,1,0,0:0:0:0: +260,132,118571,1,0,0:0:0:0: +260,132,118662,1,0,0:0:0:0: +260,132,118753,1,0,0:0:0:0: +88,236,118935,1,8,0:0:0:0: +88,236,119026,1,2,0:0:0:0: +132,236,119117,1,2,0:0:0:0: +304,288,119299,2,0,L|392:288,1,85,8|8,0:0|0:0,0:0:0:0: +112,236,119662,5,10,0:0:0:0: +256,192,120026,12,0,125480,0:0:0:0: +296,284,131299,6,0,L|296:108,1,170,4|0,0:0|0:0,0:0:0:0: +152,192,132026,1,2,0:0:0:0: +244,192,132208,1,0,0:0:0:0: +336,192,132390,1,0,0:0:0:0: +244,192,132571,1,0,0:0:0:0: +416,192,132753,6,0,L|416:20,2,170,2|0|0,0:0|0:0|0:0,0:0:0:0: +280,192,133844,1,0,0:0:0:0: +188,192,134026,1,0,0:0:0:0: +16,192,134208,6,0,L|16:16,1,170,2|0,0:0|0:0,0:0:0:0: +176,20,134935,1,2,0:0:0:0: +32,24,135299,1,0,0:0:0:0: +272,16,135662,6,0,L|272:192,1,170,2|0,0:0|0:0,0:0:0:0: +428,80,136390,2,0,L|428:272,1,170,2|0,0:0|0:0,0:0:0:0: +132,52,137117,6,0,B|304:52,2,170,4|8|8,0:0|0:0|0:0,0:0:0:0: +336,52,138571,6,0,L|336:224,1,170,2|0,0:0|0:0,0:0:0:0: +240,224,139117,1,0,0:0:0:0: +336,222,139299,1,2,0:0:0:0: +480,192,139662,1,2,0:0:0:0: +388,192,139844,1,0,0:0:0:0: +212,192,140026,6,0,L|212:364,2,170,2|0|2,0:0|0:0|0:0,0:0:0:0: +448,192,141480,6,0,L|344:192,2,93.5000028533936,8|8|8,0:0|0:0|0:0,0:0:0:0: +244,192,142208,1,8,0:0:0:0: +348,192,142390,1,8,0:0:0:0: +448,192,142571,1,8,0:0:0:0: +152,192,142935,6,0,L|152:12,1,170,4|0,0:0|0:0,0:0:0:0: +236,20,143480,1,0,0:0:0:0: +144,20,143662,2,0,L|60:20,2,85,2|0|0,0:0|0:0|0:0,0:0:0:0: +316,136,144390,5,2,0:0:0:0: +232,136,144571,1,0,0:0:0:0: +148,136,144753,1,0,0:0:0:0: +316,136,145117,2,0,L|232:136,2,85,2|0|0,0:0|0:0|0:0,0:0:0:0: +144,136,145844,6,0,L|144:224,1,85,2|0,0:0|0:0,0:0:0:0: +228,220,146208,1,2,0:0:0:0: +59,221,146571,2,0,L|159:221,2,85,2|0|0,0:0|0:0|0:0,0:0:0:0: +228,224,147299,6,0,L|312:224,1,85,2|0,0:0|0:0,0:0:0:0: +220,224,147662,2,0,L|220:320,1,85,0|0,0:0|0:0,0:0:0:0: +313,309,148026,2,0,L|313:225,1,85,2|0,0:0|0:0,0:0:0:0: +228,224,148390,1,0,0:0:0:0: +320,224,148571,1,0,0:0:0:0: +64,276,148753,6,0,L|64:192,1,85,4|0,0:0|0:0,0:0:0:0: +152,192,149117,2,0,L|152:104,1,85,2|0,0:0|0:0,0:0:0:0: +328,108,149480,1,2,0:0:0:0: +184,108,149844,2,0,L|268:108,1,85,2|0,0:0|0:0,0:0:0:0: +356,108,150208,5,2,0:0:0:0: +204,108,150571,2,0,L|204:208,1,85,2|0,0:0|0:0,0:0:0:0: +28,192,150935,1,2,0:0:0:0: +172,192,151299,2,0,L|256:192,1,85,2|0,0:0|0:0,0:0:0:0: +164,192,151662,6,0,L|164:292,1,85,2|0,0:0|0:0,0:0:0:0: +257,277,152026,2,0,L|257:193,1,85,2|0,0:0|0:0,0:0:0:0: +432,192,152390,1,2,0:0:0:0: +288,192,152753,2,0,L|200:192,1,85,2|0,0:0|0:0,0:0:0:0: +380,192,153117,6,0,L|380:104,1,85,8|8,0:0|0:0,0:0:0:0: +288,108,153480,2,0,L|288:20,1,85,8|0,0:0|0:0,0:0:0:0: +112,24,153844,2,0,L|112:108,1,85,8|8,0:0|0:0,0:0:0:0: +203,108,154208,2,0,L|291:108,1,85,8|0,0:0|0:0,0:0:0:0: +32,108,154571,6,0,L|32:288,1,170,4|0,0:0|0:0,0:0:0:0: +216,278,155299,1,2,0:0:0:0: +124,278,155480,1,0,0:0:0:0: +32,278,155662,1,0,0:0:0:0: +216,278,156026,6,0,L|304:280,1,85,8|0,0:0|0:0,0:0:0:0: +300,279,156390,1,0,0:0:0:0: +132,192,156753,2,0,L|220:192,2,85,2|0|0,0:0|0:0|0:0,0:0:0:0: +48,192,157299,1,0,0:0:0:0: +140,192,157480,6,0,L|140:104,1,85,8|0,0:0|0:0,0:0:0:0: +236,108,157844,2,0,L|236:20,1,85,0|0,0:0|0:0,0:0:0:0: +412,48,158208,2,0,L|496:48,2,85,2|0|0,0:0|0:0|0:0,0:0:0:0: +268,192,158935,5,8,0:0:0:0: +344,192,159117,1,8,0:0:0:0: +420,192,159299,1,8,0:0:0:0: +496,192,159480,1,8,0:0:0:0: +412,192,159662,2,0,L|496:192,1,85,2|0,0:0|0:0,0:0:0:0: +324,192,160026,2,0,L|324:104,1,85,2|0,0:0|0:0,0:0:0:0: +68,192,160390,6,0,L|68:108,1,85,10|0,0:0|0:0,0:0:0:0: +152,108,160753,2,0,L|240:108,1,85,8|0,0:0|0:0,0:0:0:0: +409,107,161117,2,0,L|409:191,1,85,2|2,0:0|0:0,0:0:0:0: +324,192,161480,2,0,L|412:192,1,85,8|0,0:0|0:0,0:0:0:0: +313,191,161844,6,0,L|313:299,1,85,2|0,0:0|0:0,0:0:0:0: +140,192,162208,2,0,L|140:284,1,85,8|0,0:0|0:0,0:0:0:0: +184,276,162480,1,0,0:0:0:0: +228,276,162571,2,0,L|312:276,1,85,2|2,0:0|0:0,0:0:0:0: +400,276,162935,2,0,L|400:192,1,85,8|8,0:0|0:0,0:0:0:0: +256,192,163299,12,8,164389,0:0:0:0: +132,192,164753,6,0,L|132:132,2,42.5,8|2|2,0:0|0:0|0:0,0:0:0:0: +304,192,165117,1,8,0:0:0:0: +352,173,165207,1,2,0:0:0:0: +372,125,165298,1,2,0:0:0:0: +351,78,165389,1,2,0:0:0:0: +303,59,165480,1,8,0:0:0:0: +208,60,165662,1,2,0:0:0:0: +388,8,165844,2,0,L|472:8,1,85,12|0,0:0|0:0,0:0:0:0: +216,192,166208,6,0,L|120:192,2,85,6|0|8,3:2|3:2|3:2,3:3:0:0: +308,192,166753,2,0,L|136:192,1,170,6|2,3:2|3:2,3:3:0:0: +312,192,167299,2,0,L|312:296,1,85,8|0,3:2|3:2,3:3:0:0: +138,192,167662,6,0,L|310:192,1,170,6|8,3:2|3:2,3:3:0:0: +404,192,168208,2,0,B|404:276|404:276|316:276,1,170,2|2,3:2|3:2,3:3:0:0: +140,336,168753,2,0,L|140:248,1,85,8|0,3:2|3:2,3:3:0:0: +320,192,169117,6,0,B|404:192|404:192|404:104,1,170,2|8,3:2|3:2,3:3:0:0: +232,32,169662,2,0,L|52:32,1,170,2|2,3:2|3:2,3:3:0:0: +232,32,170208,2,0,L|128:32,1,85,8|0,3:2|3:2,3:3:0:0: +52,32,170571,6,0,L|52:88,1,42.5,2|2,3:2|3:2,3:3:0:0: +100,76,170753,1,2,3:2:0:0: +192,76,170935,1,8,3:2:0:0: +448,192,171117,2,0,L|448:104,1,85,2|0,3:2|3:2,0:0:0:0: +356,104,171480,1,0,3:2:0:0: +184,192,171662,2,0,L|268:192,1,85,8|0,3:2|3:2,3:3:0:0: +20,192,172026,6,0,L|20:144,2,42.5,6|0|0,3:2|3:2|3:2,3:3:0:0: +116,192,172390,1,8,3:2:0:0: +32,192,172571,1,2,3:2:0:0: +208,192,172753,2,0,L|312:192,1,85,0|2,3:2|3:2,3:3:0:0: +200,192,173117,2,0,L|200:280,1,85,8|0,3:2|3:2,3:3:0:0: +376,192,173480,6,0,L|376:108,1,85,6|0,3:2|3:2,3:3:0:0: +200,192,173844,1,8,3:2:0:0: +116,192,174026,2,0,P|64:132|116:76,1,170,2|2,3:2|3:2,3:3:0:0: +372,76,174571,2,0,L|460:76,1,85,8|0,3:2|3:2,3:3:0:0: +280,76,174935,6,0,L|280:172,1,85,2|2,3:2|3:2,3:3:0:0: +368,192,175299,1,8,3:2:0:0: +192,192,175480,2,0,L|192:288,1,85,2|0,3:2|3:2,3:3:0:0: +280,308,175844,1,2,3:2:0:0: +453,192,176026,2,0,L|365:192,1,85,8|2,3:2|3:2,0:0:0:0: +112,192,176390,6,0,L|20:192,2,85,8|2|8,3:2|3:2|3:2,3:3:0:0: +292,192,176935,2,0,L|116:192,1,170,2|2,3:2|3:2,3:3:0:0: +304,192,177480,2,0,L|402:192,1,85,8|0,3:2|3:2,3:3:0:0: +132,192,177844,6,0,B|32:192|32:192|32:88,1,203.999993774414,6|8,3:2|3:2,3:3:0:0: +208,44,178390,2,0,L|380:44,1,170,6|2,3:2|3:2,3:3:0:0: +284,44,178935,2,0,L|284:140,1,85,8|0,3:2|3:2,3:3:0:0: +464,136,179299,6,0,L|464:232,1,85,2|0,3:2|3:2,3:3:0:0: +380,220,179662,1,8,3:2:0:0: +204,192,179844,2,0,L|376:192,1,170,2|2,3:2|1:3,3:3:0:0: +460,192,180390,2,0,L|460:92,1,85,8|0,3:2|3:2,3:3:0:0: +284,16,180753,6,0,B|200:16|200:16|200:104,1,170,2|8,3:2|3:2,3:3:0:0: +380,192,181299,2,0,L|204:192,1,170,2|2,3:2|3:2,3:3:0:0: +302,193,181844,2,0,L|210:193,1,85,8|0,3:2|3:2,3:3:0:0: +124,192,182208,6,0,L|124:288,1,85,2|2,3:2|3:2,3:3:0:0: +302,193,182571,2,0,L|210:193,1,85,8|2,3:2|3:2,0:0:0:0: +312,192,182935,2,0,L|360:192,2,42.5,0|0|2,3:2|3:2|3:2,3:3:0:0: +132,192,183299,2,0,L|32:192,1,85,8|0,3:2|3:2,3:3:0:0: +312,192,183662,6,0,P|364:248|312:308,1,170,6|8,3:2|3:2,3:3:0:0: +220,308,184208,1,2,3:2:0:0: +324,308,184390,2,0,L|324:216,1,85,0|2,3:2|3:2,3:3:0:0: +144,192,184753,2,0,L|144:280,1,85,8|0,3:2|3:2,3:3:0:0: +324,224,185117,6,0,L|408:224,1,85,2|2,3:2|3:2,3:3:0:0: +232,192,185480,2,0,L|232:96,1,85,8|2,3:2|3:2,3:3:0:0: +316,108,185844,1,0,3:2:0:0: +232,108,186026,1,2,3:2:0:0: +408,108,186208,2,0,L|408:16,1,85,8|0,3:2|3:2,3:3:0:0: +152,20,186571,6,0,B|68:20|68:20|156:20,1,170,6|0,3:2|3:2,3:3:0:0: +332,132,187117,2,0,L|152:132,1,170,6|2,3:2|3:2,3:3:0:0: +76,132,187662,2,0,L|76:216,1,85,8|0,3:2|3:2,3:3:0:0: +252,280,188026,5,2,3:2:0:0: +294,280,188116,1,2,3:2:0:0: +337,280,188207,1,2,3:2:0:0: +176,280,188390,1,8,3:2:0:0: +344,280,188571,2,0,P|396:232|344:168,1,170,6|2,3:2|3:2,3:3:0:0: +168,192,189117,2,0,L|80:192,1,85,8|0,3:2|3:2,3:3:0:0: +344,168,189480,6,0,B|448:168|448:168|448:64,1,203.999993774414,10|8,3:2|3:2,3:3:0:0: +352,68,190026,2,0,L|172:68,1,170,0|2,3:2|3:2,0:0:0:0: +276,68,190571,2,0,L|276:164,1,85,8|0,3:2|3:2,3:3:0:0: +96,192,190935,6,0,L|96:96,1,85,2|0,3:2|3:2,3:3:0:0: +192,104,191299,2,0,L|100:104,1,85,8|2,3:2|3:2,3:3:0:0: +284,192,191662,2,0,L|372:192,1,85,0|2,3:2|3:2,3:3:0:0: +464,192,192026,2,0,L|464:148,1,42.5,8|0,3:2|0:0,3:3:0:0: +420,132,192208,1,0,3:2:0:0: +240,192,192390,6,0,L|64:192,1,170,2|8,3:2|3:2,3:3:0:0: +156,192,192935,1,2,3:2:0:0: +64,192,193117,2,0,L|64:100,1,85,2|2,3:2|3:2,3:3:0:0: +156,192,193480,2,0,L|156:108,1,85,8|0,3:2|3:2,3:3:0:0: +332,192,193844,6,0,L|376:192,2,42.5,2|2|2,3:2|3:2|3:2,0:0:0:0: +156,192,194208,2,0,L|244:192,1,85,8|0,3:2|3:2,3:3:0:0: +328,192,194571,1,2,3:2:0:0: +236,192,194753,1,2,3:2:0:0: +416,192,194935,2,0,L|416:284,1,85,8|0,3:2|3:2,3:3:0:0: +160,336,195299,6,0,B|76:336|76:336|76:244,1,170,6|8,3:2|3:2,3:3:0:0: +164,192,195844,2,0,L|344:192,1,170,6|2,3:2|3:2,3:3:0:0: +240,192,196389,2,0,L|240:96,1,85,8|0,3:2|3:2,3:3:0:0: +420,68,196753,6,0,L|420:164,1,85,6|2,3:2|3:2,3:3:0:0: +372,156,197026,1,2,3:2:0:0: +324,156,197117,2,0,L|240:156,1,85,8|2,3:2|3:2,3:3:0:0: +332,156,197480,2,0,L|332:72,1,85,0|2,3:2|3:2,3:3:0:0: +152,20,197844,2,0,L|108:20,2,42.5,8|0|0,3:2|3:2|3:2,0:0:0:0: +328,192,198208,6,0,L|504:192,1,170,6|8,3:2|3:2,3:3:0:0: +412,192,198753,1,2,3:2:0:0: +236,192,198935,2,0,L|236:100,1,85,2|2,3:2|3:2,3:3:0:0: +328,192,199298,2,0,L|240:192,1,85,8|2,3:2|3:2,0:0:0:0: +64,192,199662,6,0,L|64:280,1,85,6|2,3:2|3:2,3:3:0:0: +160,276,200026,1,8,3:2:0:0: +112,276,200116,1,2,3:2:0:0: +64,277,200207,1,8,3:2:0:0: +240,192,200390,2,0,L|240:280,1,85,8|2,3:2|3:2,3:3:0:0: +416,192,200753,2,0,L|508:192,1,85,8|2,3:2|3:2,3:3:0:0: +240,192,201117,6,0,L|36:192,1,203.999993774414,6|8,3:2|3:2,3:3:0:0: +128,192,201662,2,0,B|216:192|216:192|216:104,1,170,2|2,3:2|3:2,0:0:0:0: +40,16,202208,2,0,L|40:104,1,85,8|0,3:2|3:2,3:3:0:0: +216,110,202571,6,0,L|308:110,1,85,6|2,3:2|3:2,3:3:0:0: +348,112,202844,1,2,3:2:0:0: +396,112,202935,2,0,L|396:24,1,85,8|2,3:2|3:2,3:3:0:0: +492,28,203299,2,0,L|404:28,1,85,4|2,3:2|3:2,3:3:0:0: +232,32,203662,2,0,L|232:116,1,85,8|0,3:2|3:2,0:0:0:0: +408,192,204026,6,0,L|500:192,2,85,6|2|8,3:2|3:2|3:2,3:3:0:0: +316,192,204571,2,0,L|492:192,1,170,2|2,3:2|3:2,0:0:0:0: +308,192,205117,2,0,L|220:192,1,85,8|0,3:2|3:2,3:3:0:0: +48,192,205480,6,0,L|48:284,1,85,2|2,3:2|3:2,3:3:0:0: +224,192,205844,2,0,L|312:192,1,85,8|2,3:2|3:2,0:0:0:0: +216,192,206208,1,2,3:2:0:0: +320,192,206390,1,2,3:2:0:0: +144,192,206571,2,0,L|60:192,1,85,8|2,3:2|3:2,3:3:0:0: +320,192,206935,6,0,L|408:192,1,85,6|2,3:2|3:2,3:3:0:0: +405,192,207208,1,2,3:2:0:0: +405,192,207299,1,8,3:2:0:0: +312,192,207480,2,0,P|264:136|312:68,1,170,2|0,3:2|3:2,0:0:0:0: +488,68,208026,2,0,L|488:152,1,85,8|2,3:2|3:2,3:3:0:0: +308,192,208390,6,0,L|220:192,1,85,6|2,3:2|3:2,3:3:0:0: +404,192,208753,2,0,L|404:280,1,85,8|2,3:2|3:2,3:3:0:0: +308,276,209117,1,4,3:2:0:0: +392,276,209299,1,2,3:2:0:0: +216,276,209480,2,0,L|120:276,1,85,8|2,3:2|3:2,3:3:0:0: +308,276,209844,6,0,L|308:192,1,85,6|2,3:2|3:2,3:3:0:0: +264,192,210117,1,2,3:2:0:0: +220,192,210208,1,8,3:2:0:0: +308,192,210390,2,0,L|480:192,1,170,6|2,3:2|3:2,3:3:0:0: +296,192,210935,2,0,L|296:100,1,85,8|2,3:2|3:2,3:3:0:0: +120,28,211299,5,2,3:2:0:0: +120,70,211389,1,2,3:2:0:0: +120,113,211480,1,2,3:2:0:0: +296,192,211662,2,0,L|200:192,1,85,8|8,3:2|3:2,3:3:0:0: +120,113,212026,2,0,L|120:200,1,85,12|0,3:2|3:2,3:3:0:0: +296,192,212390,1,12,3:2:0:0: +196,192,212571,1,2,3:2:0:0: +456,192,212753,6,0,L|456:280,1,85,10|0,3:2|3:2,3:3:0:0: +276,336,213117,2,0,L|180:336,1,85,8|2,3:2|3:2,0:0:0:0: +284,336,213480,2,0,L|284:240,1,85,2|2,3:2|3:2,0:0:0:0: +104,192,213844,2,0,L|188:192,1,85,8|2,3:2|3:2,0:0:0:0: +448,192,214208,6,0,L|448:100,1,85,2|2,3:2|3:2,3:3:0:0: +400,108,214480,1,2,3:2:0:0: +352,108,214571,1,8,3:2:0:0: +448,192,214753,1,2,3:2:0:0: +272,192,214935,2,0,L|272:108,1,85,0|2,3:2|3:2,3:3:0:0: +96,192,215299,2,0,L|8:192,1,85,8|2,3:2|3:2,0:0:0:0: +272,192,215662,6,0,L|360:192,1,85,6|2,3:2|3:2,3:3:0:0: +180,192,216026,2,0,L|180:104,1,85,8|2,3:2|3:2,3:3:0:0: +356,192,216390,1,2,3:2:0:0: +256,192,216571,1,2,3:2:0:0: +436,192,216753,2,0,L|332:192,1,85,8|2,3:2|3:2,3:3:0:0: +96,192,217117,6,0,B|12:192|12:192|100:192,1,170,2|8,3:2|3:2,3:3:0:0: +276,192,217662,2,0,L|364:192,2,85,2|2|2,3:2|3:2|3:2,0:0:0:0: +98,192,218208,2,0,L|98:104,1,85,8|2,3:2|3:2,3:3:0:0: +360,192,218571,6,0,P|412:128|360:80,1,170,6|8,3:2|3:2,3:3:0:0: +312,80,219026,1,2,3:2:0:0: +264,80,219117,1,2,3:2:0:0: +88,80,219299,2,0,L|172:80,1,85,4|2,3:2|3:2,3:3:0:0: +268,80,219662,2,0,L|268:168,1,85,8|2,3:2|3:2,3:3:0:0: +88,192,220026,6,0,L|88:280,1,85,6|2,3:2|3:2,3:3:0:0: +268,164,220390,1,8,3:2:0:0: +180,192,220571,1,2,3:2:0:0: +436,192,220753,2,0,L|436:96,1,85,0|2,0:0|3:2,0:0:0:0: +260,44,221117,2,0,L|168:44,1,85,8|2,3:2|3:2,3:3:0:0: +436,192,221480,6,0,L|352:192,1,85,6|2,3:2|3:2,3:3:0:0: +308,192,221753,1,2,3:2:0:0: +264,192,221844,1,8,3:2:0:0: +356,192,222026,1,2,3:2:0:0: +100,192,222208,2,0,L|16:192,1,85,4|2,3:2|3:2,3:3:0:0: +108,192,222571,2,0,L|108:104,1,85,8|2,3:2|3:2,3:3:0:0: +368,192,222935,6,0,L|416:192,2,42.5,2|2|2,3:2|3:2|3:2,3:3:0:0: +188,192,223299,1,8,3:2:0:0: +280,192,223480,1,0,3:2:0:0: +328,192,223571,1,2,3:2:0:0: +376,192,223662,2,0,L|376:104,1,85,8|2,3:2|3:2,0:0:0:0: +196,48,224026,2,0,L|104:48,1,85,8|0,3:2|0:0,0:0:0:0: +376,24,224390,6,0,P|436:96|376:168,1,203.999993774414,14|2,0:0|0:0,0:0:0:0: +96,192,225117,2,0,L|96:280,1,85,8|0,0:0|0:0,0:0:0:0: +180,276,225480,1,2,0:0:0:0: +356,192,225662,1,2,0:0:0:0: +400,192,225753,1,0,0:0:0:0: +444,192,225844,6,0,L|444:280,1,85,0|0,0:0|0:0,0:0:0:0: +360,276,226208,2,0,L|276:276,1,85,2|2,0:0|0:0,0:0:0:0: +96,192,226571,2,0,L|96:276,1,85,8|0,0:0|0:0,0:0:0:0: +181,277,226935,2,0,L|97:277,1,85,2|0,0:0|0:0,0:0:0:0: +276,192,227299,6,0,B|360:192|360:192|360:104,1,170,2|2,0:0|0:0,0:0:0:0: +276,104,227844,1,2,0:0:0:0: +96,104,228026,2,0,L|96:188,1,85,8|0,0:0|0:0,0:0:0:0: +180,192,228390,2,0,L|180:104,1,85,2|2,0:0|0:0,0:0:0:0: +356,192,228753,5,2,0:0:0:0: +440,192,228935,1,2,0:0:0:0: +440,108,229117,1,0,0:0:0:0: +356,108,229299,1,2,0:0:0:0: +176,108,229480,2,0,L|176:192,1,85,8|0,0:0|0:0,0:0:0:0: +264,192,229844,1,2,0:0:0:0: +310,192,229934,1,0,0:0:0:0: +356,192,230025,1,2,0:0:0:0: +176,192,230208,6,0,L|4:192,1,170,6|2,0:0|0:0,0:0:0:0: +92,192,230753,1,2,0:0:0:0: +268,192,230935,2,0,L|356:192,1,85,8|0,0:0|0:0,0:0:0:0: +260,192,231299,2,0,L|260:108,1,85,2|2,0:0|0:0,0:0:0:0: +308,104,231571,1,0,0:0:0:0: +356,104,231662,6,0,B|440:104|440:104|440:192,1,170,2|2,0:0|0:0,0:0:0:0: +356,192,232208,1,2,0:0:0:0: +180,192,232390,2,0,L|180:304,1,85,8|0,0:0|0:0,0:0:0:0: +272,280,232753,2,0,L|272:232,2,42.5,2|0|0,0:0|0:0|0:0,0:0:0:0: +92,280,233117,6,0,P|40:224|92:160,1,170,8|8,0:0|0:0,0:0:0:0: +172,160,233662,1,8,0:0:0:0: +352,160,233844,2,0,L|352:68,1,85,8|8,0:0|0:0,0:0:0:0: +268,76,234208,1,2,0:0:0:0: +360,76,234390,1,2,0:0:0:0: +172,160,234571,6,0,L|172:100,2,42.5,2|2|2,0:0|0:0|0:0,0:0:0:0: +268,192,234935,2,0,L|172:192,1,85,8|2,0:0|0:0,0:0:0:0: +364,192,235298,2,0,L|364:280,1,85,8|2,0:0|0:0,0:0:0:0: +183,192,235662,1,2,0:0:0:0: +140,192,235752,1,2,0:0:0:0: +98,192,235843,1,2,0:0:0:0: +376,192,236026,5,6,0:0:0:0: +224,192,236390,1,2,0:0:0:0: +496,192,236753,6,0,L|496:20,1,170,4|0,0:0|0:0,0:0:0:0: +256,192,237480,12,0,238571,0:0:0:0: +256,192,238935,6,0,L|256:368,1,170,4|0,0:0|0:0,0:0:0:0: +256,192,239662,12,0,246208,0:0:0:0: diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/37902-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/37902-expected-conversion.json new file mode 100644 index 0000000000..efc1144d05 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/37902-expected-conversion.json @@ -0,0 +1 @@ +{"Mappings":[{"StartTime":12017.0,"Objects":[{"StartTime":12017.0,"Position":48.0,"HyperDash":false},{"StartTime":12091.0,"Position":44.67537,"HyperDash":false},{"StartTime":12166.0,"Position":74.08286,"HyperDash":false},{"StartTime":12241.0,"Position":88.0374,"HyperDash":false},{"StartTime":12316.0,"Position":110.33316,"HyperDash":false},{"StartTime":12391.0,"Position":148.554672,"HyperDash":false},{"StartTime":12466.0,"Position":154.24501,"HyperDash":false},{"StartTime":12541.0,"Position":176.957489,"HyperDash":false},{"StartTime":12616.0,"Position":202.32959,"HyperDash":false},{"StartTime":12673.0,"Position":213.120667,"HyperDash":false},{"StartTime":12766.0,"Position":252.041336,"HyperDash":false}]},{"StartTime":13067.0,"Objects":[{"StartTime":13067.0,"Position":320.0,"HyperDash":false}]},{"StartTime":13367.0,"Objects":[{"StartTime":13367.0,"Position":464.0,"HyperDash":false}]},{"StartTime":13667.0,"Objects":[{"StartTime":13667.0,"Position":484.0,"HyperDash":false}]},{"StartTime":13966.0,"Objects":[{"StartTime":13966.0,"Position":444.0,"HyperDash":false}]},{"StartTime":14116.0,"Objects":[{"StartTime":14116.0,"Position":444.0,"HyperDash":false}]},{"StartTime":14416.0,"Objects":[{"StartTime":14416.0,"Position":464.0,"HyperDash":false},{"StartTime":14490.0,"Position":453.158569,"HyperDash":false},{"StartTime":14565.0,"Position":455.987671,"HyperDash":false},{"StartTime":14640.0,"Position":425.82608,"HyperDash":false},{"StartTime":14715.0,"Position":428.8319,"HyperDash":false},{"StartTime":14790.0,"Position":427.066162,"HyperDash":false},{"StartTime":14865.0,"Position":386.833649,"HyperDash":false},{"StartTime":14940.0,"Position":376.186218,"HyperDash":false},{"StartTime":15015.0,"Position":338.7702,"HyperDash":false},{"StartTime":15072.0,"Position":302.942566,"HyperDash":false},{"StartTime":15165.0,"Position":288.993134,"HyperDash":false}]},{"StartTime":15466.0,"Objects":[{"StartTime":15466.0,"Position":216.0,"HyperDash":false}]},{"StartTime":15766.0,"Objects":[{"StartTime":15766.0,"Position":72.0,"HyperDash":false}]},{"StartTime":16066.0,"Objects":[{"StartTime":16066.0,"Position":92.0,"HyperDash":false}]},{"StartTime":16366.0,"Objects":[{"StartTime":16366.0,"Position":52.0,"HyperDash":false}]},{"StartTime":16815.0,"Objects":[{"StartTime":16815.0,"Position":72.0,"HyperDash":false},{"StartTime":16889.0,"Position":79.642746,"HyperDash":false},{"StartTime":16964.0,"Position":89.107,"HyperDash":false},{"StartTime":17039.0,"Position":108.5208,"HyperDash":false},{"StartTime":17114.0,"Position":136.488754,"HyperDash":false},{"StartTime":17189.0,"Position":172.402725,"HyperDash":false},{"StartTime":17264.0,"Position":179.293137,"HyperDash":false},{"StartTime":17339.0,"Position":180.858765,"HyperDash":false},{"StartTime":17414.0,"Position":220.396072,"HyperDash":false},{"StartTime":17471.0,"Position":249.039856,"HyperDash":false},{"StartTime":17564.0,"Position":261.951355,"HyperDash":false}]},{"StartTime":17865.0,"Objects":[{"StartTime":17865.0,"Position":320.0,"HyperDash":false}]},{"StartTime":18165.0,"Objects":[{"StartTime":18165.0,"Position":432.0,"HyperDash":false}]},{"StartTime":18465.0,"Objects":[{"StartTime":18465.0,"Position":448.0,"HyperDash":false}]},{"StartTime":18765.0,"Objects":[{"StartTime":18765.0,"Position":504.0,"HyperDash":false}]},{"StartTime":18915.0,"Objects":[{"StartTime":18915.0,"Position":484.0,"HyperDash":false}]},{"StartTime":19215.0,"Objects":[{"StartTime":19215.0,"Position":504.0,"HyperDash":false},{"StartTime":19289.0,"Position":501.08197,"HyperDash":false},{"StartTime":19364.0,"Position":495.2131,"HyperDash":false},{"StartTime":19439.0,"Position":481.1128,"HyperDash":false},{"StartTime":19514.0,"Position":463.28656,"HyperDash":false},{"StartTime":19589.0,"Position":434.907227,"HyperDash":false},{"StartTime":19664.0,"Position":416.885864,"HyperDash":false},{"StartTime":19739.0,"Position":405.201477,"HyperDash":false},{"StartTime":19814.0,"Position":367.272461,"HyperDash":false},{"StartTime":19871.0,"Position":365.267731,"HyperDash":false},{"StartTime":19964.0,"Position":317.231384,"HyperDash":false}]},{"StartTime":20264.0,"Objects":[{"StartTime":20264.0,"Position":248.0,"HyperDash":false}]},{"StartTime":20564.0,"Objects":[{"StartTime":20564.0,"Position":268.0,"HyperDash":false}]},{"StartTime":20864.0,"Objects":[{"StartTime":20864.0,"Position":104.0,"HyperDash":false}]},{"StartTime":21164.0,"Objects":[{"StartTime":21164.0,"Position":248.0,"HyperDash":false}]},{"StartTime":21614.0,"Objects":[{"StartTime":21614.0,"Position":72.0,"HyperDash":false},{"StartTime":21688.0,"Position":89.44662,"HyperDash":false},{"StartTime":21763.0,"Position":74.09614,"HyperDash":false},{"StartTime":21838.0,"Position":60.5660629,"HyperDash":false},{"StartTime":21913.0,"Position":83.94954,"HyperDash":false},{"StartTime":21988.0,"Position":82.8251,"HyperDash":false},{"StartTime":22063.0,"Position":111.00235,"HyperDash":false},{"StartTime":22138.0,"Position":149.062637,"HyperDash":false},{"StartTime":22213.0,"Position":152.832413,"HyperDash":false},{"StartTime":22270.0,"Position":185.730072,"HyperDash":false},{"StartTime":22363.0,"Position":197.239868,"HyperDash":false}]},{"StartTime":22663.0,"Objects":[{"StartTime":22663.0,"Position":264.0,"HyperDash":false},{"StartTime":22737.0,"Position":291.67392,"HyperDash":false},{"StartTime":22812.0,"Position":313.532043,"HyperDash":false},{"StartTime":22887.0,"Position":338.985229,"HyperDash":false},{"StartTime":22962.0,"Position":361.614532,"HyperDash":false},{"StartTime":23037.0,"Position":383.778625,"HyperDash":false},{"StartTime":23112.0,"Position":403.659546,"HyperDash":false},{"StartTime":23187.0,"Position":404.466278,"HyperDash":false},{"StartTime":23262.0,"Position":433.744751,"HyperDash":false},{"StartTime":23337.0,"Position":430.5013,"HyperDash":false},{"StartTime":23412.0,"Position":450.112335,"HyperDash":false},{"StartTime":23469.0,"Position":448.32254,"HyperDash":false},{"StartTime":23562.0,"Position":455.8164,"HyperDash":false}]},{"StartTime":23863.0,"Objects":[{"StartTime":23863.0,"Position":456.0,"HyperDash":false},{"StartTime":23937.0,"Position":420.344849,"HyperDash":false},{"StartTime":24012.0,"Position":406.676758,"HyperDash":false},{"StartTime":24087.0,"Position":381.029877,"HyperDash":false},{"StartTime":24162.0,"Position":361.682678,"HyperDash":false},{"StartTime":24237.0,"Position":326.453217,"HyperDash":false},{"StartTime":24312.0,"Position":325.5777,"HyperDash":false},{"StartTime":24387.0,"Position":323.0864,"HyperDash":false},{"StartTime":24462.0,"Position":280.111542,"HyperDash":false},{"StartTime":24537.0,"Position":265.3847,"HyperDash":false},{"StartTime":24612.0,"Position":230.444534,"HyperDash":false},{"StartTime":24669.0,"Position":218.443909,"HyperDash":false},{"StartTime":24762.0,"Position":180.416458,"HyperDash":false}]},{"StartTime":25063.0,"Objects":[{"StartTime":25063.0,"Position":184.0,"HyperDash":false}]},{"StartTime":25662.0,"Objects":[{"StartTime":25662.0,"Position":204.0,"HyperDash":false}]},{"StartTime":26262.0,"Objects":[{"StartTime":26262.0,"Position":320.0,"HyperDash":false}]},{"StartTime":26862.0,"Objects":[{"StartTime":26862.0,"Position":300.0,"HyperDash":false}]},{"StartTime":27612.0,"Objects":[{"StartTime":27612.0,"Position":96.0,"HyperDash":false},{"StartTime":27686.0,"Position":93.6587143,"HyperDash":false},{"StartTime":27761.0,"Position":98.89105,"HyperDash":false},{"StartTime":27836.0,"Position":108.2196,"HyperDash":false},{"StartTime":27911.0,"Position":110.334862,"HyperDash":false},{"StartTime":27986.0,"Position":125.092537,"HyperDash":false},{"StartTime":28061.0,"Position":136.262375,"HyperDash":false},{"StartTime":28136.0,"Position":145.71701,"HyperDash":false},{"StartTime":28211.0,"Position":178.315811,"HyperDash":false},{"StartTime":28268.0,"Position":210.647934,"HyperDash":false},{"StartTime":28361.0,"Position":227.43338,"HyperDash":false}]},{"StartTime":28661.0,"Objects":[{"StartTime":28661.0,"Position":296.0,"HyperDash":false},{"StartTime":28735.0,"Position":302.1624,"HyperDash":false},{"StartTime":28810.0,"Position":292.488281,"HyperDash":false},{"StartTime":28885.0,"Position":289.777161,"HyperDash":false},{"StartTime":28960.0,"Position":280.749847,"HyperDash":false},{"StartTime":29035.0,"Position":254.2413,"HyperDash":false},{"StartTime":29110.0,"Position":259.131836,"HyperDash":false},{"StartTime":29185.0,"Position":252.401169,"HyperDash":false},{"StartTime":29260.0,"Position":227.176636,"HyperDash":false},{"StartTime":29335.0,"Position":210.735916,"HyperDash":false},{"StartTime":29410.0,"Position":186.45578,"HyperDash":false},{"StartTime":29467.0,"Position":186.31813,"HyperDash":false},{"StartTime":29560.0,"Position":140.066452,"HyperDash":false}]},{"StartTime":29861.0,"Objects":[{"StartTime":29861.0,"Position":72.0,"HyperDash":false},{"StartTime":29935.0,"Position":47.3521729,"HyperDash":false},{"StartTime":30010.0,"Position":57.88796,"HyperDash":false},{"StartTime":30085.0,"Position":33.71809,"HyperDash":false},{"StartTime":30160.0,"Position":48.9088,"HyperDash":false},{"StartTime":30235.0,"Position":57.53698,"HyperDash":false},{"StartTime":30310.0,"Position":45.7225456,"HyperDash":false},{"StartTime":30385.0,"Position":39.74385,"HyperDash":false},{"StartTime":30460.0,"Position":49.84991,"HyperDash":false},{"StartTime":30535.0,"Position":61.4995155,"HyperDash":false},{"StartTime":30610.0,"Position":64.31644,"HyperDash":false},{"StartTime":30667.0,"Position":81.55949,"HyperDash":false},{"StartTime":30760.0,"Position":93.98463,"HyperDash":false}]},{"StartTime":31060.0,"Objects":[{"StartTime":31060.0,"Position":160.0,"HyperDash":false}]},{"StartTime":31660.0,"Objects":[{"StartTime":31660.0,"Position":432.0,"HyperDash":false}]},{"StartTime":32260.0,"Objects":[{"StartTime":32260.0,"Position":412.0,"HyperDash":false}]},{"StartTime":32860.0,"Objects":[{"StartTime":32860.0,"Position":432.0,"HyperDash":false}]},{"StartTime":33610.0,"Objects":[{"StartTime":33610.0,"Position":256.0,"HyperDash":false},{"StartTime":33684.0,"Position":223.29216,"HyperDash":false},{"StartTime":33759.0,"Position":206.250412,"HyperDash":false},{"StartTime":33834.0,"Position":193.208679,"HyperDash":false},{"StartTime":33909.0,"Position":156.0,"HyperDash":false},{"StartTime":33984.0,"Position":175.874786,"HyperDash":false},{"StartTime":34059.0,"Position":205.916534,"HyperDash":false},{"StartTime":34116.0,"Position":211.948242,"HyperDash":false},{"StartTime":34209.0,"Position":256.0,"HyperDash":false}]},{"StartTime":34359.0,"Objects":[{"StartTime":34359.0,"Position":376.0,"HyperDash":false}]},{"StartTime":34659.0,"Objects":[{"StartTime":34659.0,"Position":256.0,"HyperDash":false}]},{"StartTime":34809.0,"Objects":[{"StartTime":34809.0,"Position":256.0,"HyperDash":false},{"StartTime":34883.0,"Position":283.707855,"HyperDash":false},{"StartTime":34958.0,"Position":305.749573,"HyperDash":false},{"StartTime":35033.0,"Position":327.791321,"HyperDash":false},{"StartTime":35108.0,"Position":356.0,"HyperDash":false},{"StartTime":35183.0,"Position":331.1252,"HyperDash":false},{"StartTime":35258.0,"Position":306.083466,"HyperDash":false},{"StartTime":35315.0,"Position":273.051758,"HyperDash":false},{"StartTime":35408.0,"Position":256.0,"HyperDash":false}]},{"StartTime":35559.0,"Objects":[{"StartTime":35559.0,"Position":128.0,"HyperDash":false}]},{"StartTime":35859.0,"Objects":[{"StartTime":35859.0,"Position":256.0,"HyperDash":false}]},{"StartTime":36009.0,"Objects":[{"StartTime":36009.0,"Position":256.0,"HyperDash":false},{"StartTime":36083.0,"Position":230.29216,"HyperDash":false},{"StartTime":36158.0,"Position":206.250412,"HyperDash":false},{"StartTime":36233.0,"Position":193.208679,"HyperDash":false},{"StartTime":36308.0,"Position":156.0,"HyperDash":false},{"StartTime":36383.0,"Position":191.874786,"HyperDash":false},{"StartTime":36458.0,"Position":205.916534,"HyperDash":false},{"StartTime":36515.0,"Position":241.948242,"HyperDash":false},{"StartTime":36608.0,"Position":256.0,"HyperDash":false}]},{"StartTime":36758.0,"Objects":[{"StartTime":36758.0,"Position":376.0,"HyperDash":false}]},{"StartTime":37058.0,"Objects":[{"StartTime":37058.0,"Position":328.0,"HyperDash":false},{"StartTime":37132.0,"Position":343.611969,"HyperDash":false},{"StartTime":37207.0,"Position":376.99472,"HyperDash":false},{"StartTime":37282.0,"Position":386.735321,"HyperDash":false},{"StartTime":37357.0,"Position":419.270874,"HyperDash":false},{"StartTime":37432.0,"Position":438.334564,"HyperDash":false},{"StartTime":37507.0,"Position":444.7913,"HyperDash":false},{"StartTime":37582.0,"Position":467.3238,"HyperDash":false},{"StartTime":37657.0,"Position":454.839142,"HyperDash":false},{"StartTime":37732.0,"Position":439.412842,"HyperDash":false},{"StartTime":37807.0,"Position":444.935333,"HyperDash":false},{"StartTime":37882.0,"Position":421.561951,"HyperDash":false},{"StartTime":37957.0,"Position":419.5829,"HyperDash":false},{"StartTime":38032.0,"Position":392.116547,"HyperDash":false},{"StartTime":38107.0,"Position":377.418579,"HyperDash":false},{"StartTime":38182.0,"Position":348.0527,"HyperDash":false},{"StartTime":38257.0,"Position":328.0,"HyperDash":false},{"StartTime":38332.0,"Position":347.832336,"HyperDash":false},{"StartTime":38407.0,"Position":377.206635,"HyperDash":false},{"StartTime":38482.0,"Position":402.925934,"HyperDash":false},{"StartTime":38557.0,"Position":419.42688,"HyperDash":false},{"StartTime":38632.0,"Position":422.448273,"HyperDash":false},{"StartTime":38707.0,"Position":444.8633,"HyperDash":false},{"StartTime":38764.0,"Position":446.1127,"HyperDash":false},{"StartTime":38857.0,"Position":454.839142,"HyperDash":false}]},{"StartTime":39607.0,"Objects":[{"StartTime":39607.0,"Position":440.0,"HyperDash":false}]},{"StartTime":39907.0,"Objects":[{"StartTime":39907.0,"Position":296.0,"HyperDash":false}]},{"StartTime":40207.0,"Objects":[{"StartTime":40207.0,"Position":316.0,"HyperDash":false}]},{"StartTime":40357.0,"Objects":[{"StartTime":40357.0,"Position":256.0,"HyperDash":false},{"StartTime":40431.0,"Position":212.250839,"HyperDash":false},{"StartTime":40506.0,"Position":206.167221,"HyperDash":false},{"StartTime":40563.0,"Position":200.103668,"HyperDash":false},{"StartTime":40656.0,"Position":156.0,"HyperDash":false}]},{"StartTime":41107.0,"Objects":[{"StartTime":41107.0,"Position":64.0,"HyperDash":false}]},{"StartTime":41407.0,"Objects":[{"StartTime":41407.0,"Position":256.0,"HyperDash":false}]},{"StartTime":41557.0,"Objects":[{"StartTime":41557.0,"Position":192.0,"HyperDash":false},{"StartTime":41631.0,"Position":213.749161,"HyperDash":false},{"StartTime":41706.0,"Position":241.832779,"HyperDash":false},{"StartTime":41763.0,"Position":251.896332,"HyperDash":false},{"StartTime":41856.0,"Position":292.0,"HyperDash":false}]},{"StartTime":42307.0,"Objects":[{"StartTime":42307.0,"Position":392.0,"HyperDash":false}]},{"StartTime":42606.0,"Objects":[{"StartTime":42606.0,"Position":288.0,"HyperDash":false}]},{"StartTime":42756.0,"Objects":[{"StartTime":42756.0,"Position":256.0,"HyperDash":false},{"StartTime":42830.0,"Position":220.250839,"HyperDash":false},{"StartTime":42905.0,"Position":206.167221,"HyperDash":false},{"StartTime":42962.0,"Position":205.103668,"HyperDash":false},{"StartTime":43055.0,"Position":156.0,"HyperDash":false}]},{"StartTime":43356.0,"Objects":[{"StartTime":43356.0,"Position":172.0,"HyperDash":false}]},{"StartTime":43506.0,"Objects":[{"StartTime":43506.0,"Position":144.0,"HyperDash":false}]},{"StartTime":43656.0,"Objects":[{"StartTime":43656.0,"Position":172.0,"HyperDash":false}]},{"StartTime":43956.0,"Objects":[{"StartTime":43956.0,"Position":288.0,"HyperDash":false}]},{"StartTime":44106.0,"Objects":[{"StartTime":44106.0,"Position":230.0,"HyperDash":false}]},{"StartTime":44256.0,"Objects":[{"StartTime":44256.0,"Position":250.0,"HyperDash":false}]},{"StartTime":44556.0,"Objects":[{"StartTime":44556.0,"Position":374.0,"HyperDash":false}]},{"StartTime":44706.0,"Objects":[{"StartTime":44706.0,"Position":302.0,"HyperDash":false}]},{"StartTime":44856.0,"Objects":[{"StartTime":44856.0,"Position":282.0,"HyperDash":false}]},{"StartTime":45605.0,"Objects":[{"StartTime":45605.0,"Position":256.0,"HyperDash":false},{"StartTime":45679.0,"Position":263.6996,"HyperDash":false},{"StartTime":45754.0,"Position":306.0,"HyperDash":false},{"StartTime":45829.0,"Position":275.233643,"HyperDash":false},{"StartTime":45904.0,"Position":256.0,"HyperDash":false},{"StartTime":45979.0,"Position":286.8331,"HyperDash":false},{"StartTime":46054.0,"Position":306.0,"HyperDash":false},{"StartTime":46129.0,"Position":293.100128,"HyperDash":false},{"StartTime":46204.0,"Position":256.0,"HyperDash":false},{"StartTime":46261.0,"Position":261.958618,"HyperDash":false},{"StartTime":46354.0,"Position":306.0,"HyperDash":false}]},{"StartTime":46655.0,"Objects":[{"StartTime":46655.0,"Position":376.0,"HyperDash":false}]},{"StartTime":46955.0,"Objects":[{"StartTime":46955.0,"Position":448.0,"HyperDash":false}]},{"StartTime":47255.0,"Objects":[{"StartTime":47255.0,"Position":459.0,"HyperDash":false}]},{"StartTime":47555.0,"Objects":[{"StartTime":47555.0,"Position":304.0,"HyperDash":false}]},{"StartTime":47705.0,"Objects":[{"StartTime":47705.0,"Position":376.0,"HyperDash":false}]},{"StartTime":48005.0,"Objects":[{"StartTime":48005.0,"Position":376.0,"HyperDash":false},{"StartTime":48079.0,"Position":381.749176,"HyperDash":false},{"StartTime":48154.0,"Position":426.0,"HyperDash":false},{"StartTime":48211.0,"Position":410.103668,"HyperDash":false},{"StartTime":48304.0,"Position":376.0,"HyperDash":false}]},{"StartTime":48454.0,"Objects":[{"StartTime":48454.0,"Position":232.0,"HyperDash":false}]},{"StartTime":48604.0,"Objects":[{"StartTime":48604.0,"Position":304.0,"HyperDash":false}]},{"StartTime":48754.0,"Objects":[{"StartTime":48754.0,"Position":224.0,"HyperDash":false}]},{"StartTime":49054.0,"Objects":[{"StartTime":49054.0,"Position":160.0,"HyperDash":false}]},{"StartTime":49354.0,"Objects":[{"StartTime":49354.0,"Position":80.0,"HyperDash":false}]},{"StartTime":49654.0,"Objects":[{"StartTime":49654.0,"Position":16.0,"HyperDash":false}]},{"StartTime":49954.0,"Objects":[{"StartTime":49954.0,"Position":80.0,"HyperDash":false}]},{"StartTime":50404.0,"Objects":[{"StartTime":50404.0,"Position":48.0,"HyperDash":false},{"StartTime":50460.0,"Position":52.7919464,"HyperDash":false},{"StartTime":50553.0,"Position":98.0,"HyperDash":false}]},{"StartTime":50704.0,"Objects":[{"StartTime":50704.0,"Position":136.0,"HyperDash":false},{"StartTime":50760.0,"Position":160.791946,"HyperDash":false},{"StartTime":50853.0,"Position":186.0,"HyperDash":false}]},{"StartTime":51003.0,"Objects":[{"StartTime":51003.0,"Position":224.0,"HyperDash":false},{"StartTime":51059.0,"Position":255.791946,"HyperDash":false},{"StartTime":51152.0,"Position":274.0,"HyperDash":false}]},{"StartTime":51453.0,"Objects":[{"StartTime":51453.0,"Position":400.0,"HyperDash":false}]},{"StartTime":51753.0,"Objects":[{"StartTime":51753.0,"Position":432.0,"HyperDash":false}]},{"StartTime":52053.0,"Objects":[{"StartTime":52053.0,"Position":488.0,"HyperDash":false}]},{"StartTime":52353.0,"Objects":[{"StartTime":52353.0,"Position":507.0,"HyperDash":false}]},{"StartTime":52503.0,"Objects":[{"StartTime":52503.0,"Position":508.0,"HyperDash":false}]},{"StartTime":52803.0,"Objects":[{"StartTime":52803.0,"Position":488.0,"HyperDash":false},{"StartTime":52877.0,"Position":473.278381,"HyperDash":false},{"StartTime":52952.0,"Position":438.0,"HyperDash":false},{"StartTime":53027.0,"Position":471.832977,"HyperDash":false},{"StartTime":53102.0,"Position":488.0,"HyperDash":false},{"StartTime":53159.0,"Position":476.069031,"HyperDash":false},{"StartTime":53252.0,"Position":438.0,"HyperDash":false}]},{"StartTime":53403.0,"Objects":[{"StartTime":53403.0,"Position":368.0,"HyperDash":false}]},{"StartTime":53553.0,"Objects":[{"StartTime":53553.0,"Position":368.0,"HyperDash":false},{"StartTime":53627.0,"Position":341.4955,"HyperDash":false},{"StartTime":53702.0,"Position":320.428864,"HyperDash":false},{"StartTime":53777.0,"Position":295.8305,"HyperDash":false},{"StartTime":53852.0,"Position":289.023224,"HyperDash":false},{"StartTime":53927.0,"Position":294.625671,"HyperDash":false},{"StartTime":54002.0,"Position":320.1455,"HyperDash":false},{"StartTime":54059.0,"Position":332.386719,"HyperDash":false},{"StartTime":54152.0,"Position":368.0,"HyperDash":false}]},{"StartTime":54452.0,"Objects":[{"StartTime":54452.0,"Position":368.0,"HyperDash":false},{"StartTime":54526.0,"Position":361.6083,"HyperDash":false},{"StartTime":54601.0,"Position":346.408356,"HyperDash":false},{"StartTime":54676.0,"Position":309.5409,"HyperDash":false},{"StartTime":54751.0,"Position":302.165344,"HyperDash":false},{"StartTime":54826.0,"Position":280.769684,"HyperDash":false},{"StartTime":54901.0,"Position":252.958511,"HyperDash":false},{"StartTime":54976.0,"Position":235.981033,"HyperDash":false},{"StartTime":55051.0,"Position":202.952667,"HyperDash":false},{"StartTime":55108.0,"Position":198.931656,"HyperDash":false},{"StartTime":55201.0,"Position":152.9338,"HyperDash":false}]},{"StartTime":60000.0,"Objects":[{"StartTime":60000.0,"Position":256.0,"HyperDash":false},{"StartTime":60074.0,"Position":256.3498,"HyperDash":false},{"StartTime":60149.0,"Position":264.8665,"HyperDash":false},{"StartTime":60224.0,"Position":286.383179,"HyperDash":false},{"StartTime":60299.0,"Position":305.899872,"HyperDash":false},{"StartTime":60374.0,"Position":314.416565,"HyperDash":false},{"StartTime":60449.0,"Position":335.933228,"HyperDash":false},{"StartTime":60524.0,"Position":344.449921,"HyperDash":false},{"StartTime":60599.0,"Position":355.9666,"HyperDash":false},{"StartTime":60656.0,"Position":381.4793,"HyperDash":false},{"StartTime":60749.0,"Position":381.0,"HyperDash":false}]},{"StartTime":61050.0,"Objects":[{"StartTime":61050.0,"Position":416.0,"HyperDash":false},{"StartTime":61124.0,"Position":413.0,"HyperDash":false},{"StartTime":61199.0,"Position":430.0,"HyperDash":false},{"StartTime":61274.0,"Position":403.0,"HyperDash":false},{"StartTime":61349.0,"Position":416.0,"HyperDash":false},{"StartTime":61424.0,"Position":404.0,"HyperDash":false},{"StartTime":61499.0,"Position":419.0,"HyperDash":false},{"StartTime":61574.0,"Position":426.0,"HyperDash":false},{"StartTime":61649.0,"Position":416.0,"HyperDash":false},{"StartTime":61715.0,"Position":396.0,"HyperDash":false},{"StartTime":61781.0,"Position":420.0,"HyperDash":false},{"StartTime":61847.0,"Position":421.0,"HyperDash":false},{"StartTime":61949.0,"Position":416.0,"HyperDash":false}]},{"StartTime":62250.0,"Objects":[{"StartTime":62250.0,"Position":416.0,"HyperDash":false},{"StartTime":62324.0,"Position":403.652954,"HyperDash":false},{"StartTime":62399.0,"Position":373.139038,"HyperDash":false},{"StartTime":62474.0,"Position":359.625122,"HyperDash":false},{"StartTime":62549.0,"Position":366.111237,"HyperDash":false},{"StartTime":62624.0,"Position":362.597321,"HyperDash":false},{"StartTime":62699.0,"Position":334.083435,"HyperDash":false},{"StartTime":62774.0,"Position":344.569519,"HyperDash":false},{"StartTime":62849.0,"Position":316.0556,"HyperDash":false},{"StartTime":62915.0,"Position":298.0434,"HyperDash":false},{"StartTime":62981.0,"Position":275.031158,"HyperDash":false},{"StartTime":63047.0,"Position":265.018921,"HyperDash":false},{"StartTime":63149.0,"Position":266.0,"HyperDash":false}]},{"StartTime":63449.0,"Objects":[{"StartTime":63449.0,"Position":232.0,"HyperDash":false},{"StartTime":63523.0,"Position":246.0,"HyperDash":false},{"StartTime":63598.0,"Position":233.0,"HyperDash":false},{"StartTime":63673.0,"Position":236.0,"HyperDash":false},{"StartTime":63748.0,"Position":232.0,"HyperDash":false},{"StartTime":63823.0,"Position":219.0,"HyperDash":false},{"StartTime":63898.0,"Position":231.0,"HyperDash":false},{"StartTime":63973.0,"Position":242.0,"HyperDash":false},{"StartTime":64048.0,"Position":232.0,"HyperDash":false},{"StartTime":64123.0,"Position":228.0,"HyperDash":false},{"StartTime":64198.0,"Position":215.0,"HyperDash":false},{"StartTime":64273.0,"Position":243.0,"HyperDash":false},{"StartTime":64348.0,"Position":232.0,"HyperDash":false},{"StartTime":64405.0,"Position":249.0,"HyperDash":false},{"StartTime":64498.0,"Position":232.0,"HyperDash":false}]},{"StartTime":64799.0,"Objects":[{"StartTime":64799.0,"Position":160.0,"HyperDash":false},{"StartTime":64873.0,"Position":144.3059,"HyperDash":false},{"StartTime":64948.0,"Position":110.278084,"HyperDash":false},{"StartTime":65023.0,"Position":84.25028,"HyperDash":false},{"StartTime":65098.0,"Position":60.0,"HyperDash":false},{"StartTime":65173.0,"Position":96.80534,"HyperDash":false},{"StartTime":65248.0,"Position":109.833145,"HyperDash":false},{"StartTime":65323.0,"Position":135.860962,"HyperDash":false},{"StartTime":65398.0,"Position":160.0,"HyperDash":false},{"StartTime":65473.0,"Position":122.08342,"HyperDash":false},{"StartTime":65548.0,"Position":110.055618,"HyperDash":false},{"StartTime":65605.0,"Position":79.03449,"HyperDash":false},{"StartTime":65698.0,"Position":60.0,"HyperDash":false}]},{"StartTime":65998.0,"Objects":[{"StartTime":65998.0,"Position":56.0,"HyperDash":false}]},{"StartTime":66298.0,"Objects":[{"StartTime":66298.0,"Position":36.0,"HyperDash":false}]},{"StartTime":66598.0,"Objects":[{"StartTime":66598.0,"Position":63.0,"HyperDash":false}]},{"StartTime":66898.0,"Objects":[{"StartTime":66898.0,"Position":200.0,"HyperDash":false}]},{"StartTime":67198.0,"Objects":[{"StartTime":67198.0,"Position":287.0,"HyperDash":false},{"StartTime":67272.0,"Position":341.0,"HyperDash":false},{"StartTime":67347.0,"Position":145.0,"HyperDash":false},{"StartTime":67422.0,"Position":84.0,"HyperDash":false},{"StartTime":67497.0,"Position":189.0,"HyperDash":false},{"StartTime":67572.0,"Position":498.0,"HyperDash":false},{"StartTime":67647.0,"Position":416.0,"HyperDash":false},{"StartTime":67722.0,"Position":211.0,"HyperDash":false},{"StartTime":67797.0,"Position":167.0,"HyperDash":false},{"StartTime":67872.0,"Position":466.0,"HyperDash":false},{"StartTime":67947.0,"Position":114.0,"HyperDash":false},{"StartTime":68022.0,"Position":125.0,"HyperDash":false},{"StartTime":68097.0,"Position":457.0,"HyperDash":false},{"StartTime":68172.0,"Position":131.0,"HyperDash":false},{"StartTime":68247.0,"Position":337.0,"HyperDash":false},{"StartTime":68322.0,"Position":39.0,"HyperDash":false},{"StartTime":68397.0,"Position":311.0,"HyperDash":false},{"StartTime":68472.0,"Position":208.0,"HyperDash":false},{"StartTime":68547.0,"Position":357.0,"HyperDash":false},{"StartTime":68622.0,"Position":240.0,"HyperDash":false},{"StartTime":68697.0,"Position":35.0,"HyperDash":false},{"StartTime":68772.0,"Position":254.0,"HyperDash":false},{"StartTime":68847.0,"Position":292.0,"HyperDash":false},{"StartTime":68922.0,"Position":369.0,"HyperDash":false},{"StartTime":68997.0,"Position":14.0,"HyperDash":false},{"StartTime":69072.0,"Position":390.0,"HyperDash":false},{"StartTime":69147.0,"Position":286.0,"HyperDash":false},{"StartTime":69222.0,"Position":92.0,"HyperDash":false},{"StartTime":69297.0,"Position":170.0,"HyperDash":false},{"StartTime":69372.0,"Position":93.0,"HyperDash":false},{"StartTime":69447.0,"Position":139.0,"HyperDash":false},{"StartTime":69522.0,"Position":301.0,"HyperDash":false},{"StartTime":69597.0,"Position":137.0,"HyperDash":false}]},{"StartTime":69897.0,"Objects":[{"StartTime":69897.0,"Position":256.0,"HyperDash":false}]},{"StartTime":70047.0,"Objects":[{"StartTime":70047.0,"Position":320.0,"HyperDash":false}]},{"StartTime":70197.0,"Objects":[{"StartTime":70197.0,"Position":340.0,"HyperDash":false}]},{"StartTime":70497.0,"Objects":[{"StartTime":70497.0,"Position":340.0,"HyperDash":false}]},{"StartTime":70797.0,"Objects":[{"StartTime":70797.0,"Position":300.0,"HyperDash":false}]},{"StartTime":71096.0,"Objects":[{"StartTime":71096.0,"Position":248.0,"HyperDash":false}]},{"StartTime":71246.0,"Objects":[{"StartTime":71246.0,"Position":168.0,"HyperDash":false}]},{"StartTime":71396.0,"Objects":[{"StartTime":71396.0,"Position":184.0,"HyperDash":false}]},{"StartTime":71696.0,"Objects":[{"StartTime":71696.0,"Position":24.0,"HyperDash":false}]},{"StartTime":71996.0,"Objects":[{"StartTime":71996.0,"Position":104.0,"HyperDash":false},{"StartTime":72070.0,"Position":79.25084,"HyperDash":false},{"StartTime":72145.0,"Position":54.0,"HyperDash":false},{"StartTime":72202.0,"Position":54.8963242,"HyperDash":false},{"StartTime":72295.0,"Position":104.0,"HyperDash":false}]},{"StartTime":72446.0,"Objects":[{"StartTime":72446.0,"Position":192.0,"HyperDash":false}]},{"StartTime":72746.0,"Objects":[{"StartTime":72746.0,"Position":16.0,"HyperDash":false}]},{"StartTime":73046.0,"Objects":[{"StartTime":73046.0,"Position":104.0,"HyperDash":false},{"StartTime":73120.0,"Position":123.738281,"HyperDash":false},{"StartTime":73195.0,"Position":123.252632,"HyperDash":false},{"StartTime":73270.0,"Position":144.834549,"HyperDash":false},{"StartTime":73345.0,"Position":166.952621,"HyperDash":false},{"StartTime":73420.0,"Position":208.089325,"HyperDash":false},{"StartTime":73495.0,"Position":215.686081,"HyperDash":false},{"StartTime":73570.0,"Position":248.499512,"HyperDash":false},{"StartTime":73645.0,"Position":265.421631,"HyperDash":false},{"StartTime":73720.0,"Position":275.398376,"HyperDash":false},{"StartTime":73795.0,"Position":315.4022,"HyperDash":false},{"StartTime":73870.0,"Position":351.418854,"HyperDash":false},{"StartTime":73945.0,"Position":365.440521,"HyperDash":false},{"StartTime":74002.0,"Position":402.458252,"HyperDash":false},{"StartTime":74095.0,"Position":415.4878,"HyperDash":false}]},{"StartTime":74395.0,"Objects":[{"StartTime":74395.0,"Position":416.0,"HyperDash":false},{"StartTime":74469.0,"Position":417.7021,"HyperDash":false},{"StartTime":74544.0,"Position":397.104645,"HyperDash":false},{"StartTime":74619.0,"Position":387.306122,"HyperDash":false},{"StartTime":74694.0,"Position":350.779144,"HyperDash":false},{"StartTime":74769.0,"Position":366.889374,"HyperDash":false},{"StartTime":74844.0,"Position":396.758,"HyperDash":false},{"StartTime":74919.0,"Position":408.543182,"HyperDash":false},{"StartTime":74994.0,"Position":416.0,"HyperDash":false},{"StartTime":75069.0,"Position":394.62265,"HyperDash":false},{"StartTime":75144.0,"Position":396.931335,"HyperDash":false},{"StartTime":75201.0,"Position":363.6833,"HyperDash":false},{"StartTime":75294.0,"Position":350.779144,"HyperDash":false}]},{"StartTime":75595.0,"Objects":[{"StartTime":75595.0,"Position":280.0,"HyperDash":false}]},{"StartTime":75895.0,"Objects":[{"StartTime":75895.0,"Position":136.0,"HyperDash":false}]},{"StartTime":76195.0,"Objects":[{"StartTime":76195.0,"Position":280.0,"HyperDash":false}]},{"StartTime":76345.0,"Objects":[{"StartTime":76345.0,"Position":208.0,"HyperDash":false}]},{"StartTime":76495.0,"Objects":[{"StartTime":76495.0,"Position":228.0,"HyperDash":false}]},{"StartTime":76794.0,"Objects":[{"StartTime":76794.0,"Position":21.0,"HyperDash":false},{"StartTime":76859.0,"Position":193.0,"HyperDash":false},{"StartTime":76925.0,"Position":52.0,"HyperDash":false},{"StartTime":76990.0,"Position":466.0,"HyperDash":false},{"StartTime":77056.0,"Position":135.0,"HyperDash":false},{"StartTime":77121.0,"Position":121.0,"HyperDash":false},{"StartTime":77187.0,"Position":427.0,"HyperDash":false},{"StartTime":77253.0,"Position":176.0,"HyperDash":false},{"StartTime":77318.0,"Position":96.0,"HyperDash":false},{"StartTime":77384.0,"Position":345.0,"HyperDash":false},{"StartTime":77449.0,"Position":11.0,"HyperDash":false},{"StartTime":77515.0,"Position":393.0,"HyperDash":false},{"StartTime":77581.0,"Position":440.0,"HyperDash":false},{"StartTime":77646.0,"Position":179.0,"HyperDash":false},{"StartTime":77712.0,"Position":470.0,"HyperDash":false},{"StartTime":77777.0,"Position":89.0,"HyperDash":false},{"StartTime":77843.0,"Position":408.0,"HyperDash":false},{"StartTime":77909.0,"Position":243.0,"HyperDash":false},{"StartTime":77974.0,"Position":78.0,"HyperDash":false},{"StartTime":78040.0,"Position":172.0,"HyperDash":false},{"StartTime":78105.0,"Position":450.0,"HyperDash":false},{"StartTime":78171.0,"Position":231.0,"HyperDash":false},{"StartTime":78237.0,"Position":118.0,"HyperDash":false},{"StartTime":78302.0,"Position":511.0,"HyperDash":false},{"StartTime":78368.0,"Position":333.0,"HyperDash":false},{"StartTime":78433.0,"Position":234.0,"HyperDash":false},{"StartTime":78499.0,"Position":228.0,"HyperDash":false},{"StartTime":78565.0,"Position":302.0,"HyperDash":false},{"StartTime":78630.0,"Position":390.0,"HyperDash":false},{"StartTime":78696.0,"Position":75.0,"HyperDash":false},{"StartTime":78761.0,"Position":506.0,"HyperDash":false},{"StartTime":78827.0,"Position":3.0,"HyperDash":false},{"StartTime":78893.0,"Position":289.0,"HyperDash":false}]},{"StartTime":79194.0,"Objects":[{"StartTime":79194.0,"Position":256.0,"HyperDash":false},{"StartTime":79268.0,"Position":249.6807,"HyperDash":false},{"StartTime":79343.0,"Position":245.6988,"HyperDash":false},{"StartTime":79418.0,"Position":237.299881,"HyperDash":false},{"StartTime":79493.0,"Position":211.7363,"HyperDash":false},{"StartTime":79550.0,"Position":208.713608,"HyperDash":false},{"StartTime":79643.0,"Position":165.0138,"HyperDash":false}]},{"StartTime":79793.0,"Objects":[{"StartTime":79793.0,"Position":128.0,"HyperDash":false},{"StartTime":79867.0,"Position":121.464394,"HyperDash":false},{"StartTime":79942.0,"Position":81.6096039,"HyperDash":false},{"StartTime":80017.0,"Position":52.0348129,"HyperDash":false},{"StartTime":80092.0,"Position":60.8326073,"HyperDash":false},{"StartTime":80149.0,"Position":53.9088326,"HyperDash":false},{"StartTime":80242.0,"Position":56.0562,"HyperDash":false}]},{"StartTime":80543.0,"Objects":[{"StartTime":80543.0,"Position":76.0,"HyperDash":false}]},{"StartTime":80843.0,"Objects":[{"StartTime":80843.0,"Position":56.0,"HyperDash":false}]},{"StartTime":81143.0,"Objects":[{"StartTime":81143.0,"Position":200.0,"HyperDash":false}]},{"StartTime":81443.0,"Objects":[{"StartTime":81443.0,"Position":180.0,"HyperDash":false}]},{"StartTime":81593.0,"Objects":[{"StartTime":81593.0,"Position":200.0,"HyperDash":false},{"StartTime":81667.0,"Position":218.643234,"HyperDash":false},{"StartTime":81742.0,"Position":249.328659,"HyperDash":false},{"StartTime":81817.0,"Position":278.3164,"HyperDash":false},{"StartTime":81892.0,"Position":296.1659,"HyperDash":false},{"StartTime":81967.0,"Position":318.451416,"HyperDash":false},{"StartTime":82042.0,"Position":336.820862,"HyperDash":false},{"StartTime":82117.0,"Position":362.178284,"HyperDash":false},{"StartTime":82192.0,"Position":369.602051,"HyperDash":false},{"StartTime":82267.0,"Position":338.393555,"HyperDash":false},{"StartTime":82342.0,"Position":337.067169,"HyperDash":false},{"StartTime":82417.0,"Position":321.719727,"HyperDash":false},{"StartTime":82492.0,"Position":296.459869,"HyperDash":false},{"StartTime":82567.0,"Position":256.630157,"HyperDash":false},{"StartTime":82642.0,"Position":249.6552,"HyperDash":false},{"StartTime":82699.0,"Position":228.9382,"HyperDash":false},{"StartTime":82792.0,"Position":200.0,"HyperDash":false}]},{"StartTime":82942.0,"Objects":[{"StartTime":82942.0,"Position":200.0,"HyperDash":false}]},{"StartTime":83242.0,"Objects":[{"StartTime":83242.0,"Position":180.0,"HyperDash":false}]},{"StartTime":83542.0,"Objects":[{"StartTime":83542.0,"Position":180.0,"HyperDash":false}]},{"StartTime":83692.0,"Objects":[{"StartTime":83692.0,"Position":220.0,"HyperDash":false}]},{"StartTime":83842.0,"Objects":[{"StartTime":83842.0,"Position":220.0,"HyperDash":false}]},{"StartTime":83992.0,"Objects":[{"StartTime":83992.0,"Position":200.0,"HyperDash":false},{"StartTime":84066.0,"Position":214.895981,"HyperDash":false},{"StartTime":84141.0,"Position":217.903488,"HyperDash":false},{"StartTime":84216.0,"Position":225.305542,"HyperDash":false},{"StartTime":84291.0,"Position":263.285431,"HyperDash":false},{"StartTime":84348.0,"Position":288.04718,"HyperDash":false},{"StartTime":84441.0,"Position":312.975067,"HyperDash":false}]},{"StartTime":84592.0,"Objects":[{"StartTime":84592.0,"Position":344.0,"HyperDash":false},{"StartTime":84666.0,"Position":386.711,"HyperDash":false},{"StartTime":84741.0,"Position":393.655243,"HyperDash":false},{"StartTime":84816.0,"Position":433.20578,"HyperDash":false},{"StartTime":84891.0,"Position":441.4496,"HyperDash":false},{"StartTime":84948.0,"Position":466.8295,"HyperDash":false},{"StartTime":85041.0,"Position":473.5803,"HyperDash":false}]},{"StartTime":85341.0,"Objects":[{"StartTime":85341.0,"Position":464.0,"HyperDash":false}]},{"StartTime":85641.0,"Objects":[{"StartTime":85641.0,"Position":480.0,"HyperDash":false}]},{"StartTime":85941.0,"Objects":[{"StartTime":85941.0,"Position":464.0,"HyperDash":false}]},{"StartTime":86241.0,"Objects":[{"StartTime":86241.0,"Position":336.0,"HyperDash":false}]},{"StartTime":86391.0,"Objects":[{"StartTime":86391.0,"Position":400.0,"HyperDash":false},{"StartTime":86465.0,"Position":384.340973,"HyperDash":false},{"StartTime":86540.0,"Position":350.5981,"HyperDash":false},{"StartTime":86615.0,"Position":323.677429,"HyperDash":false},{"StartTime":86690.0,"Position":304.500153,"HyperDash":false},{"StartTime":86765.0,"Position":291.3181,"HyperDash":false},{"StartTime":86840.0,"Position":264.219,"HyperDash":false},{"StartTime":86915.0,"Position":246.938583,"HyperDash":false},{"StartTime":86990.0,"Position":217.532028,"HyperDash":false},{"StartTime":87065.0,"Position":225.6241,"HyperDash":false},{"StartTime":87140.0,"Position":263.9408,"HyperDash":false},{"StartTime":87215.0,"Position":291.0559,"HyperDash":false},{"StartTime":87290.0,"Position":304.220367,"HyperDash":false},{"StartTime":87365.0,"Position":325.3685,"HyperDash":false},{"StartTime":87440.0,"Position":350.271576,"HyperDash":false},{"StartTime":87497.0,"Position":364.034241,"HyperDash":false},{"StartTime":87590.0,"Position":400.0,"HyperDash":false}]},{"StartTime":87741.0,"Objects":[{"StartTime":87741.0,"Position":400.0,"HyperDash":false}]},{"StartTime":88041.0,"Objects":[{"StartTime":88041.0,"Position":420.0,"HyperDash":false}]},{"StartTime":88340.0,"Objects":[{"StartTime":88340.0,"Position":380.0,"HyperDash":false}]},{"StartTime":88490.0,"Objects":[{"StartTime":88490.0,"Position":320.0,"HyperDash":false}]},{"StartTime":88640.0,"Objects":[{"StartTime":88640.0,"Position":314.0,"HyperDash":false}]},{"StartTime":88940.0,"Objects":[{"StartTime":88940.0,"Position":0.0,"HyperDash":false},{"StartTime":89033.0,"Position":111.0,"HyperDash":false},{"StartTime":89127.0,"Position":358.0,"HyperDash":false},{"StartTime":89221.0,"Position":476.0,"HyperDash":false},{"StartTime":89315.0,"Position":87.0,"HyperDash":false},{"StartTime":89408.0,"Position":33.0,"HyperDash":false},{"StartTime":89502.0,"Position":166.0,"HyperDash":false},{"StartTime":89596.0,"Position":275.0,"HyperDash":false},{"StartTime":89690.0,"Position":119.0,"HyperDash":false}]},{"StartTime":89990.0,"Objects":[{"StartTime":89990.0,"Position":56.0,"HyperDash":false}]},{"StartTime":90140.0,"Objects":[{"StartTime":90140.0,"Position":76.0,"HyperDash":false}]},{"StartTime":90290.0,"Objects":[{"StartTime":90290.0,"Position":36.0,"HyperDash":false}]},{"StartTime":90590.0,"Objects":[{"StartTime":90590.0,"Position":200.0,"HyperDash":false}]},{"StartTime":90740.0,"Objects":[{"StartTime":90740.0,"Position":160.0,"HyperDash":false},{"StartTime":90814.0,"Position":176.321808,"HyperDash":false},{"StartTime":90889.0,"Position":206.339,"HyperDash":false},{"StartTime":90964.0,"Position":213.574585,"HyperDash":false},{"StartTime":91039.0,"Position":236.2215,"HyperDash":false},{"StartTime":91114.0,"Position":240.839,"HyperDash":false},{"StartTime":91189.0,"Position":206.688522,"HyperDash":false},{"StartTime":91264.0,"Position":182.7456,"HyperDash":false},{"StartTime":91339.0,"Position":160.0,"HyperDash":false},{"StartTime":91414.0,"Position":183.5337,"HyperDash":false},{"StartTime":91489.0,"Position":206.513763,"HyperDash":false},{"StartTime":91546.0,"Position":220.042145,"HyperDash":false},{"StartTime":91639.0,"Position":236.2215,"HyperDash":false}]},{"StartTime":91939.0,"Objects":[{"StartTime":91939.0,"Position":264.0,"HyperDash":false}]},{"StartTime":92089.0,"Objects":[{"StartTime":92089.0,"Position":259.0,"HyperDash":false}]},{"StartTime":92389.0,"Objects":[{"StartTime":92389.0,"Position":408.0,"HyperDash":false}]},{"StartTime":92539.0,"Objects":[{"StartTime":92539.0,"Position":328.0,"HyperDash":false}]},{"StartTime":92689.0,"Objects":[{"StartTime":92689.0,"Position":400.0,"HyperDash":false}]},{"StartTime":92839.0,"Objects":[{"StartTime":92839.0,"Position":464.0,"HyperDash":false}]},{"StartTime":92989.0,"Objects":[{"StartTime":92989.0,"Position":484.0,"HyperDash":false}]},{"StartTime":93139.0,"Objects":[{"StartTime":93139.0,"Position":496.0,"HyperDash":false}]},{"StartTime":93439.0,"Objects":[{"StartTime":93439.0,"Position":496.0,"HyperDash":false},{"StartTime":93513.0,"Position":508.470551,"HyperDash":false},{"StartTime":93588.0,"Position":481.299042,"HyperDash":false},{"StartTime":93663.0,"Position":447.88858,"HyperDash":false},{"StartTime":93738.0,"Position":442.8401,"HyperDash":false},{"StartTime":93813.0,"Position":434.920868,"HyperDash":false},{"StartTime":93888.0,"Position":396.039459,"HyperDash":false},{"StartTime":93963.0,"Position":378.642273,"HyperDash":false},{"StartTime":94038.0,"Position":346.954773,"HyperDash":false},{"StartTime":94113.0,"Position":325.103058,"HyperDash":false},{"StartTime":94188.0,"Position":297.157654,"HyperDash":false},{"StartTime":94245.0,"Position":278.1643,"HyperDash":false},{"StartTime":94338.0,"Position":247.142532,"HyperDash":false}]},{"StartTime":94788.0,"Objects":[{"StartTime":94788.0,"Position":160.0,"HyperDash":false},{"StartTime":94862.0,"Position":178.0,"HyperDash":false},{"StartTime":94937.0,"Position":160.0,"HyperDash":false},{"StartTime":94994.0,"Position":156.0,"HyperDash":false},{"StartTime":95087.0,"Position":160.0,"HyperDash":false}]},{"StartTime":95238.0,"Objects":[{"StartTime":95238.0,"Position":180.0,"HyperDash":false}]},{"StartTime":95388.0,"Objects":[{"StartTime":95388.0,"Position":140.0,"HyperDash":false}]},{"StartTime":95538.0,"Objects":[{"StartTime":95538.0,"Position":160.0,"HyperDash":false},{"StartTime":95612.0,"Position":178.7418,"HyperDash":false},{"StartTime":95687.0,"Position":173.08049,"HyperDash":false},{"StartTime":95744.0,"Position":203.788971,"HyperDash":false},{"StartTime":95837.0,"Position":215.526108,"HyperDash":false}]},{"StartTime":96138.0,"Objects":[{"StartTime":96138.0,"Position":296.0,"HyperDash":false},{"StartTime":96212.0,"Position":337.7064,"HyperDash":false},{"StartTime":96287.0,"Position":345.412964,"HyperDash":false},{"StartTime":96344.0,"Position":376.616638,"HyperDash":false},{"StartTime":96437.0,"Position":391.160645,"HyperDash":false}]},{"StartTime":96737.0,"Objects":[{"StartTime":96737.0,"Position":464.0,"HyperDash":false}]},{"StartTime":96887.0,"Objects":[{"StartTime":96887.0,"Position":416.0,"HyperDash":false}]},{"StartTime":97187.0,"Objects":[{"StartTime":97187.0,"Position":440.0,"HyperDash":false},{"StartTime":97261.0,"Position":447.4056,"HyperDash":false},{"StartTime":97336.0,"Position":432.9317,"HyperDash":false},{"StartTime":97411.0,"Position":435.742554,"HyperDash":false},{"StartTime":97486.0,"Position":407.575775,"HyperDash":false},{"StartTime":97561.0,"Position":379.243927,"HyperDash":false},{"StartTime":97636.0,"Position":366.1177,"HyperDash":false},{"StartTime":97711.0,"Position":344.4165,"HyperDash":false},{"StartTime":97786.0,"Position":317.914917,"HyperDash":false},{"StartTime":97843.0,"Position":304.0325,"HyperDash":false},{"StartTime":97936.0,"Position":268.035461,"HyperDash":false}]},{"StartTime":98237.0,"Objects":[{"StartTime":98237.0,"Position":200.0,"HyperDash":false}]},{"StartTime":98537.0,"Objects":[{"StartTime":98537.0,"Position":56.0,"HyperDash":false}]},{"StartTime":98837.0,"Objects":[{"StartTime":98837.0,"Position":76.0,"HyperDash":false}]},{"StartTime":99137.0,"Objects":[{"StartTime":99137.0,"Position":56.0,"HyperDash":false},{"StartTime":99211.0,"Position":60.74916,"HyperDash":false},{"StartTime":99286.0,"Position":106.0,"HyperDash":false},{"StartTime":99343.0,"Position":88.1036758,"HyperDash":false},{"StartTime":99436.0,"Position":56.0,"HyperDash":false}]},{"StartTime":99586.0,"Objects":[{"StartTime":99586.0,"Position":56.0,"HyperDash":false},{"StartTime":99660.0,"Position":61.48453,"HyperDash":false},{"StartTime":99735.0,"Position":67.69644,"HyperDash":false},{"StartTime":99810.0,"Position":92.0094,"HyperDash":false},{"StartTime":99885.0,"Position":107.968033,"HyperDash":false},{"StartTime":99960.0,"Position":87.38017,"HyperDash":false},{"StartTime":100035.0,"Position":67.9301,"HyperDash":false},{"StartTime":100110.0,"Position":56.5835648,"HyperDash":false},{"StartTime":100185.0,"Position":56.0,"HyperDash":false},{"StartTime":100260.0,"Position":65.53404,"HyperDash":false},{"StartTime":100335.0,"Position":67.81326,"HyperDash":false},{"StartTime":100392.0,"Position":74.3646545,"HyperDash":false},{"StartTime":100485.0,"Position":107.968033,"HyperDash":false}]},{"StartTime":100636.0,"Objects":[{"StartTime":100636.0,"Position":144.0,"HyperDash":false}]},{"StartTime":100936.0,"Objects":[{"StartTime":100936.0,"Position":288.0,"HyperDash":false}]},{"StartTime":101236.0,"Objects":[{"StartTime":101236.0,"Position":268.0,"HyperDash":false}]},{"StartTime":101536.0,"Objects":[{"StartTime":101536.0,"Position":360.0,"HyperDash":false},{"StartTime":101610.0,"Position":381.602356,"HyperDash":false},{"StartTime":101685.0,"Position":408.7352,"HyperDash":false},{"StartTime":101760.0,"Position":420.921082,"HyperDash":false},{"StartTime":101835.0,"Position":450.0819,"HyperDash":false},{"StartTime":101910.0,"Position":480.7513,"HyperDash":false},{"StartTime":101985.0,"Position":478.132416,"HyperDash":false},{"StartTime":102042.0,"Position":481.652863,"HyperDash":false},{"StartTime":102135.0,"Position":495.055,"HyperDash":false}]},{"StartTime":102435.0,"Objects":[{"StartTime":102435.0,"Position":496.0,"HyperDash":false},{"StartTime":102509.0,"Position":478.3312,"HyperDash":false},{"StartTime":102584.0,"Position":446.623962,"HyperDash":false},{"StartTime":102659.0,"Position":441.667145,"HyperDash":false},{"StartTime":102734.0,"Position":400.097137,"HyperDash":false},{"StartTime":102809.0,"Position":373.617828,"HyperDash":false},{"StartTime":102884.0,"Position":361.791168,"HyperDash":false},{"StartTime":102941.0,"Position":366.174866,"HyperDash":false},{"StartTime":103034.0,"Position":334.736969,"HyperDash":false}]},{"StartTime":103335.0,"Objects":[{"StartTime":103335.0,"Position":288.0,"HyperDash":false}]},{"StartTime":103635.0,"Objects":[{"StartTime":103635.0,"Position":272.0,"HyperDash":false}]},{"StartTime":103935.0,"Objects":[{"StartTime":103935.0,"Position":176.0,"HyperDash":false}]},{"StartTime":104385.0,"Objects":[{"StartTime":104385.0,"Position":64.0,"HyperDash":false}]},{"StartTime":104535.0,"Objects":[{"StartTime":104535.0,"Position":120.0,"HyperDash":false}]},{"StartTime":104685.0,"Objects":[{"StartTime":104685.0,"Position":104.0,"HyperDash":false}]},{"StartTime":104835.0,"Objects":[{"StartTime":104835.0,"Position":140.0,"HyperDash":false}]},{"StartTime":104985.0,"Objects":[{"StartTime":104985.0,"Position":140.0,"HyperDash":false}]},{"StartTime":105135.0,"Objects":[{"StartTime":105135.0,"Position":120.0,"HyperDash":false},{"StartTime":105209.0,"Position":126.278061,"HyperDash":false},{"StartTime":105284.0,"Position":134.685547,"HyperDash":false},{"StartTime":105359.0,"Position":146.535583,"HyperDash":false},{"StartTime":105434.0,"Position":176.619583,"HyperDash":false},{"StartTime":105509.0,"Position":149.913956,"HyperDash":false},{"StartTime":105584.0,"Position":134.963058,"HyperDash":false},{"StartTime":105659.0,"Position":122.398125,"HyperDash":false},{"StartTime":105734.0,"Position":120.0,"HyperDash":false},{"StartTime":105809.0,"Position":138.336151,"HyperDash":false},{"StartTime":105884.0,"Position":134.8243,"HyperDash":false},{"StartTime":105941.0,"Position":165.701263,"HyperDash":false},{"StartTime":106034.0,"Position":176.619583,"HyperDash":false}]},{"StartTime":106334.0,"Objects":[{"StartTime":106334.0,"Position":248.0,"HyperDash":false},{"StartTime":106408.0,"Position":283.699738,"HyperDash":false},{"StartTime":106483.0,"Position":297.674133,"HyperDash":false},{"StartTime":106558.0,"Position":330.484833,"HyperDash":false},{"StartTime":106633.0,"Position":346.951965,"HyperDash":false},{"StartTime":106708.0,"Position":384.7983,"HyperDash":false},{"StartTime":106783.0,"Position":393.6557,"HyperDash":false},{"StartTime":106840.0,"Position":405.136719,"HyperDash":false},{"StartTime":106933.0,"Position":435.388184,"HyperDash":false}]},{"StartTime":107234.0,"Objects":[{"StartTime":107234.0,"Position":464.0,"HyperDash":false},{"StartTime":107308.0,"Position":456.621124,"HyperDash":false},{"StartTime":107383.0,"Position":471.782776,"HyperDash":false},{"StartTime":107458.0,"Position":457.492584,"HyperDash":false},{"StartTime":107533.0,"Position":461.751678,"HyperDash":false},{"StartTime":107608.0,"Position":448.0888,"HyperDash":false},{"StartTime":107683.0,"Position":429.117279,"HyperDash":false},{"StartTime":107740.0,"Position":423.223846,"HyperDash":false},{"StartTime":107833.0,"Position":382.2534,"HyperDash":false}]},{"StartTime":108134.0,"Objects":[{"StartTime":108134.0,"Position":24.0,"HyperDash":false}]},{"StartTime":108433.0,"Objects":[{"StartTime":108433.0,"Position":88.0,"HyperDash":false}]},{"StartTime":108733.0,"Objects":[{"StartTime":108733.0,"Position":200.0,"HyperDash":false}]},{"StartTime":108883.0,"Objects":[{"StartTime":108883.0,"Position":220.0,"HyperDash":false}]}]} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/37902.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/37902.osu new file mode 100644 index 0000000000..04942acb1e --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/37902.osu @@ -0,0 +1,230 @@ +osu file format v5 + +[General] +StackLeniency: 0.7 +Mode: 0 + +[Difficulty] +HPDrainRate:5 +CircleSize:3 +OverallDifficulty:5 +SliderMultiplier:1 +SliderTickRate:2 + +[Events] +//Break Periods +2,55404,58804 +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Sound Samples +//Background Colour Transformations +3,100,163,162,255 + +[TimingPoints] +2421,299.895036737142,4,1,0,100,1,0 +27079,-100,4,2,0,100,0,0 +27529,-100,4,1,0,100,0,0 +33077,-100,4,2,0,100,0,0 +33527,-100,4,1,0,100,0,0 +39075,-100,4,2,0,100,0,0 +39525,-100,4,1,0,100,0,0 +45073,-100,4,2,0,100,0,0 +53696,-100,4,1,0,100,0,0 +60000,-200,4,1,0,100,0,0 +64799,-100,4,1,0,100,0,0 + +[HitObjects] +48,192,12017,2,0,B|104:312|272:312,1,250,0|2 +320,312,13067,1,0 +392,312,13367,1,2 +464,312,13667,1,0 +464,240,13966,1,4 +464,240,14116,1,4 +464,168,14416,6,0,B|464:80|400:32|272:32,1,250,0|2 +216,32,15466,1,0 +144,32,15766,1,2 +72,32,16066,1,0 +72,104,16366,1,4 +72,208,16815,6,0,B|72:288|152:288|152:208|248:208|248:160|296:160,1,250,0|2 +320,128,17865,1,0 +376,88,18165,1,2 +440,64,18465,1,0 +504,48,18765,1,4 +504,48,18915,1,4 +504,120,19215,6,0,B|504:232|400:232|296:232,1,250,0|2 +248,232,20264,1,0 +248,160,20564,1,2 +176,160,20864,1,0 +176,232,21164,1,4 +72,232,21614,6,0,B|72:88|112:88|200:40,1,250,0|2 +264,32,22663,2,0,B|456:32|456:224,1,300,0|2 +456,280,23863,2,0,B|360:280|336:320|336:352|320:368|168:368,1,300,0|2 +184,296,25063,5,4 +184,152,25662,1,4 +320,152,26262,1,4 +320,296,26862,1,4 +96,296,27612,6,0,B|96:168|144:120|240:120,1,250,0|2 +296,120,28661,2,0,B|296:248|232:328|128:352,1,300,0|2 +72,352,29861,2,0,B|32:240|32:96|112:56,1,300,0|2 +160,64,31060,5,4 +296,64,31660,1,4 +432,64,32260,1,4 +432,200,32860,1,4 +256,192,33610,6,0,B|136:192,2,100 +256,192,34359,1,2 +256,264,34659,1,2 +256,264,34809,2,0,B|384:264,2,100 +256,264,35559,1,2 +256,336,35859,1,2 +256,336,36009,2,0,B|136:336,2,100 +256,336,36758,1,2 +328,336,37058,2,0,B|456:336|456:184,3,200,4|4|4|4 +440,40,39607,5,0 +368,40,39907,1,0 +296,40,40207,1,0 +256,40,40357,2,2,B|112:40,1,100 +88,120,41107,1,0 +160,120,41407,1,0 +192,120,41557,2,2,B|328:120,1,100 +360,192,42307,1,0 +288,192,42606,1,0 +256,192,42756,2,2,B|144:192,1,100,2|4 +158,262,43356,5,0 +158,262,43506,1,0 +158,262,43656,1,4 +230,262,43956,1,0 +230,262,44106,1,0 +230,262,44256,1,4 +302,262,44556,1,0 +302,262,44706,1,0 +302,262,44856,1,4 +256,88,45605,6,2,B|328:88,5,50,2|2|2|2|0|2 +376,88,46655,1,0 +448,88,46955,1,2 +448,160,47255,1,0 +376,160,47555,1,0 +376,160,47705,1,4 +376,232,48005,6,2,B|440:232,2,50 +336,232,48454,1,2 +304,232,48604,1,0 +264,232,48754,1,2 +192,232,49054,1,0 +120,232,49354,1,2 +48,232,49654,1,0 +48,160,49954,1,4 +48,56,50404,6,2,B|112:56,1,50 +136,56,50704,2,2,B|208:56,1,50 +224,56,51003,2,2,B|288:56,1,50,0|2 +344,56,51453,1,0 +416,56,51753,1,2 +488,56,52053,1,0 +488,128,52353,1,0 +488,128,52503,1,4 +488,200,52803,6,2,B|432:200,3,50 +400,200,53403,1,0 +368,200,53553,2,0,B|296:200|280:120,2,100,2|4|4 +368,272,54452,2,4,B|360:368|120:344,1,250,4|4 +256,288,60000,6,0,B|400:288,1,125 +416,288,61050,2,0,B|416:128,1,150 +416,104,62250,2,0,B|240:104,1,150,0|0 +232,104,63449,2,0,B|232:296,1,175,0|4 +160,280,64799,6,0,B|48:280,3,100,0|8|0|8 +56,208,65998,1,0 +56,136,66298,1,8 +56,64,66598,1,0 +128,64,66898,1,8 +256,192,67198,12,0,69597 +256,192,69897,5,8 +288,192,70047,1,0 +320,192,70197,1,0 +320,120,70497,1,8 +320,48,70797,1,0 +248,48,71096,1,8 +208,48,71246,1,0 +176,48,71396,1,0 +104,48,71696,1,8 +104,120,71996,6,0,B|16:120,2,50,0|0|8 +104,120,72446,1,2 +104,192,72746,1,2 +104,264,73046,2,2,B|104:352|264:352|424:352,1,350,2|4 +416,280,74395,6,0,B|416:216|320:216,3,100,0|8|0|8 +280,216,75595,1,0 +208,216,75895,1,8 +208,144,76195,1,0 +208,112,76345,1,0 +208,80,76495,1,8 +256,192,76794,12,0,78893 +256,192,79194,6,2,B|256:104|152:88,1,150,6|0 +128,88,79793,2,2,B|56:72|56:200,1,150,2|0 +56,264,80543,1,2 +56,336,80843,1,0 +128,336,81143,1,2 +200,336,81443,1,4 +200,336,81593,6,2,B|320:336|384:224,2,200,6|2|0 +200,336,82942,1,2 +200,264,83242,1,2 +200,192,83542,1,4 +200,160,83692,1,4 +200,128,83842,1,4 +200,96,83992,6,2,B|200:40|248:24|360:24,1,150,6|0 +344,24,84592,2,2,B|440:24|480:48|480:120,1,150,2|0 +472,144,85341,1,2 +472,216,85641,1,0 +472,288,85941,1,2 +400,288,86241,1,4 +400,288,86391,6,2,B|272:288|296:216|192:216,2,200,6|2|0 +400,288,87741,5,2 +400,216,88041,1,2 +400,144,88340,1,4 +360,144,88490,1,4 +320,144,88640,1,4 +256,192,88940,12,0,89690 +56,192,89990,5,0 +56,192,90140,1,0 +56,192,90290,1,8 +128,192,90590,1,0 +160,192,90740,2,2,B|224:192|248:104,3,100 +264,72,91939,1,4 +264,72,92089,1,4 +336,72,92389,5,0 +368,72,92539,1,0 +400,72,92689,1,8 +432,72,92839,1,0 +464,72,92989,1,0 +496,72,93139,1,2 +496,144,93439,2,2,B|496:256|232:256,1,300,2|4 +160,192,94788,6,0,B|160:136,2,50,0|0|8 +160,224,95238,1,0 +160,256,95388,1,0 +160,288,95538,2,2,B|160:360|238:362,1,100 +296,360,96138,2,2,B|376:360|416:312,1,100 +440,288,96737,1,4 +440,288,96887,1,4 +440,216,97187,6,0,B|440:80|264:80,1,250,0|2 +200,80,98237,1,2 +128,80,98537,1,2 +56,80,98837,1,4 +56,152,99137,6,0,B|136:152,2,50 +56,184,99586,2,0,B|56:264|144:264,3,100,8|8|8|8 +144,264,100636,1,4 +216,264,100936,1,4 +288,264,101236,1,4 +360,264,101536,2,0,B|464:264|496:136,1,200,4|0 +496,72,102435,6,0,B|360:72|320:208,1,200 +304,232,103335,1,2 +280,296,103635,1,0 +224,344,103935,1,4 +120,296,104385,5,8 +120,264,104535,1,0 +120,232,104685,1,8 +120,200,104835,1,0 +120,168,104985,1,8 +120,136,105135,2,4,B|120:64|216:56,3,100,0|4|4|4 +248,48,106334,2,0,B|376:48|416:88|464:128,1,200,4|0 +464,168,107234,2,0,B|488:248|456:312|376:320,1,200,0|4 +200,320,108134,5,4 +56,192,108433,1,4 +200,64,108733,1,4 +200,64,108883,1,4 diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/39206-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/39206-expected-conversion.json new file mode 100644 index 0000000000..35fcd88d4e --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/39206-expected-conversion.json @@ -0,0 +1 @@ +{"Mappings":[{"StartTime":678.0,"Objects":[{"StartTime":678.0,"Position":256.0,"HyperDash":false}]},{"StartTime":1021.0,"Objects":[{"StartTime":1021.0,"Position":456.0,"HyperDash":false}]},{"StartTime":1193.0,"Objects":[{"StartTime":1193.0,"Position":456.0,"HyperDash":false}]},{"StartTime":1364.0,"Objects":[{"StartTime":1364.0,"Position":456.0,"HyperDash":false}]},{"StartTime":1707.0,"Objects":[{"StartTime":1707.0,"Position":312.0,"HyperDash":false}]},{"StartTime":1878.0,"Objects":[{"StartTime":1878.0,"Position":312.0,"HyperDash":false}]},{"StartTime":2050.0,"Objects":[{"StartTime":2050.0,"Position":312.0,"HyperDash":false}]},{"StartTime":2393.0,"Objects":[{"StartTime":2393.0,"Position":168.0,"HyperDash":false}]},{"StartTime":2564.0,"Objects":[{"StartTime":2564.0,"Position":168.0,"HyperDash":false}]},{"StartTime":2736.0,"Objects":[{"StartTime":2736.0,"Position":168.0,"HyperDash":false}]},{"StartTime":3078.0,"Objects":[{"StartTime":3078.0,"Position":24.0,"HyperDash":false}]},{"StartTime":3250.0,"Objects":[{"StartTime":3250.0,"Position":24.0,"HyperDash":false}]},{"StartTime":3421.0,"Objects":[{"StartTime":3421.0,"Position":24.0,"HyperDash":false}]},{"StartTime":3764.0,"Objects":[{"StartTime":3764.0,"Position":56.0,"HyperDash":false}]},{"StartTime":3936.0,"Objects":[{"StartTime":3936.0,"Position":136.0,"HyperDash":false}]},{"StartTime":4107.0,"Objects":[{"StartTime":4107.0,"Position":216.0,"HyperDash":false}]},{"StartTime":4450.0,"Objects":[{"StartTime":4450.0,"Position":296.0,"HyperDash":false}]},{"StartTime":4621.0,"Objects":[{"StartTime":4621.0,"Position":376.0,"HyperDash":false}]},{"StartTime":4793.0,"Objects":[{"StartTime":4793.0,"Position":456.0,"HyperDash":false}]},{"StartTime":5135.0,"Objects":[{"StartTime":5135.0,"Position":456.0,"HyperDash":false}]},{"StartTime":5307.0,"Objects":[{"StartTime":5307.0,"Position":376.0,"HyperDash":false}]},{"StartTime":5478.0,"Objects":[{"StartTime":5478.0,"Position":296.0,"HyperDash":false}]},{"StartTime":5821.0,"Objects":[{"StartTime":5821.0,"Position":216.0,"HyperDash":false}]},{"StartTime":5993.0,"Objects":[{"StartTime":5993.0,"Position":136.0,"HyperDash":false}]},{"StartTime":6164.0,"Objects":[{"StartTime":6164.0,"Position":56.0,"HyperDash":false}]},{"StartTime":6507.0,"Objects":[{"StartTime":6507.0,"Position":24.0,"HyperDash":false},{"StartTime":6583.0,"Position":9.0,"HyperDash":false},{"StartTime":6660.0,"Position":13.0,"HyperDash":false},{"StartTime":6736.0,"Position":21.0,"HyperDash":false},{"StartTime":6849.0,"Position":24.0,"HyperDash":false}]},{"StartTime":7193.0,"Objects":[{"StartTime":7193.0,"Position":144.0,"HyperDash":false},{"StartTime":7269.0,"Position":159.0,"HyperDash":false},{"StartTime":7346.0,"Position":161.0,"HyperDash":false},{"StartTime":7422.0,"Position":145.0,"HyperDash":false},{"StartTime":7535.0,"Position":144.0,"HyperDash":false}]},{"StartTime":7878.0,"Objects":[{"StartTime":7878.0,"Position":256.0,"HyperDash":false},{"StartTime":7954.0,"Position":255.0,"HyperDash":false},{"StartTime":8031.0,"Position":241.0,"HyperDash":false},{"StartTime":8107.0,"Position":248.0,"HyperDash":false},{"StartTime":8220.0,"Position":256.0,"HyperDash":false}]},{"StartTime":8564.0,"Objects":[{"StartTime":8564.0,"Position":376.0,"HyperDash":false},{"StartTime":8640.0,"Position":364.0,"HyperDash":false},{"StartTime":8717.0,"Position":372.0,"HyperDash":false},{"StartTime":8793.0,"Position":390.0,"HyperDash":false},{"StartTime":8906.0,"Position":376.0,"HyperDash":false}]},{"StartTime":9250.0,"Objects":[{"StartTime":9250.0,"Position":488.0,"HyperDash":false},{"StartTime":9326.0,"Position":492.0,"HyperDash":false},{"StartTime":9403.0,"Position":479.0,"HyperDash":false},{"StartTime":9479.0,"Position":493.0,"HyperDash":false},{"StartTime":9592.0,"Position":488.0,"HyperDash":false}]},{"StartTime":9935.0,"Objects":[{"StartTime":9935.0,"Position":17.0,"HyperDash":false},{"StartTime":10004.0,"Position":433.0,"HyperDash":false},{"StartTime":10074.0,"Position":201.0,"HyperDash":false},{"StartTime":10144.0,"Position":244.0,"HyperDash":false},{"StartTime":10213.0,"Position":55.0,"HyperDash":false},{"StartTime":10283.0,"Position":166.0,"HyperDash":false},{"StartTime":10353.0,"Position":332.0,"HyperDash":false},{"StartTime":10422.0,"Position":460.0,"HyperDash":false},{"StartTime":10492.0,"Position":329.0,"HyperDash":false},{"StartTime":10562.0,"Position":156.0,"HyperDash":false},{"StartTime":10631.0,"Position":273.0,"HyperDash":false},{"StartTime":10701.0,"Position":57.0,"HyperDash":false},{"StartTime":10771.0,"Position":199.0,"HyperDash":false},{"StartTime":10840.0,"Position":485.0,"HyperDash":false},{"StartTime":10910.0,"Position":388.0,"HyperDash":false},{"StartTime":10980.0,"Position":470.0,"HyperDash":false},{"StartTime":11050.0,"Position":326.0,"HyperDash":false}]},{"StartTime":11307.0,"Objects":[{"StartTime":11307.0,"Position":40.0,"HyperDash":false}]},{"StartTime":11393.0,"Objects":[{"StartTime":11393.0,"Position":56.0,"HyperDash":false}]},{"StartTime":11478.0,"Objects":[{"StartTime":11478.0,"Position":80.0,"HyperDash":false}]},{"StartTime":11564.0,"Objects":[{"StartTime":11564.0,"Position":104.0,"HyperDash":false}]},{"StartTime":11650.0,"Objects":[{"StartTime":11650.0,"Position":128.0,"HyperDash":false},{"StartTime":11726.0,"Position":139.513672,"HyperDash":false},{"StartTime":11803.0,"Position":178.88179,"HyperDash":false},{"StartTime":11879.0,"Position":208.079636,"HyperDash":false},{"StartTime":11992.0,"Position":226.574265,"HyperDash":false}]},{"StartTime":12336.0,"Objects":[{"StartTime":12336.0,"Position":288.0,"HyperDash":false},{"StartTime":12412.0,"Position":273.486328,"HyperDash":false},{"StartTime":12489.0,"Position":256.118225,"HyperDash":false},{"StartTime":12565.0,"Position":223.920364,"HyperDash":false},{"StartTime":12678.0,"Position":189.425735,"HyperDash":false}]},{"StartTime":13021.0,"Objects":[{"StartTime":13021.0,"Position":344.0,"HyperDash":false},{"StartTime":13097.0,"Position":346.513672,"HyperDash":false},{"StartTime":13174.0,"Position":370.8818,"HyperDash":false},{"StartTime":13250.0,"Position":413.079651,"HyperDash":false},{"StartTime":13363.0,"Position":442.574249,"HyperDash":false}]},{"StartTime":13707.0,"Objects":[{"StartTime":13707.0,"Position":504.0,"HyperDash":false},{"StartTime":13783.0,"Position":490.486328,"HyperDash":false},{"StartTime":13860.0,"Position":453.1182,"HyperDash":false},{"StartTime":13936.0,"Position":440.920349,"HyperDash":false},{"StartTime":14049.0,"Position":405.425751,"HyperDash":false}]},{"StartTime":14221.0,"Objects":[{"StartTime":14221.0,"Position":328.0,"HyperDash":false}]},{"StartTime":14307.0,"Objects":[{"StartTime":14307.0,"Position":312.0,"HyperDash":false}]},{"StartTime":14393.0,"Objects":[{"StartTime":14393.0,"Position":296.0,"HyperDash":false},{"StartTime":14469.0,"Position":285.453,"HyperDash":false},{"StartTime":14546.0,"Position":295.793518,"HyperDash":false},{"StartTime":14622.0,"Position":253.246521,"HyperDash":false},{"StartTime":14735.0,"Position":257.538452,"HyperDash":false}]},{"StartTime":15078.0,"Objects":[{"StartTime":15078.0,"Position":160.0,"HyperDash":false},{"StartTime":15154.0,"Position":179.547012,"HyperDash":false},{"StartTime":15231.0,"Position":158.206482,"HyperDash":false},{"StartTime":15307.0,"Position":192.7535,"HyperDash":false},{"StartTime":15420.0,"Position":198.461548,"HyperDash":false}]},{"StartTime":15764.0,"Objects":[{"StartTime":15764.0,"Position":296.0,"HyperDash":false},{"StartTime":15840.0,"Position":298.453,"HyperDash":false},{"StartTime":15917.0,"Position":269.793518,"HyperDash":false},{"StartTime":15993.0,"Position":263.246521,"HyperDash":false},{"StartTime":16106.0,"Position":257.538452,"HyperDash":false}]},{"StartTime":16450.0,"Objects":[{"StartTime":16450.0,"Position":160.0,"HyperDash":false},{"StartTime":16526.0,"Position":168.547012,"HyperDash":false},{"StartTime":16603.0,"Position":183.206482,"HyperDash":false},{"StartTime":16679.0,"Position":170.7535,"HyperDash":false},{"StartTime":16792.0,"Position":198.461548,"HyperDash":false}]},{"StartTime":16964.0,"Objects":[{"StartTime":16964.0,"Position":112.0,"HyperDash":false}]},{"StartTime":17050.0,"Objects":[{"StartTime":17050.0,"Position":96.0,"HyperDash":false}]},{"StartTime":17136.0,"Objects":[{"StartTime":17136.0,"Position":88.0,"HyperDash":false},{"StartTime":17221.0,"Position":108.141563,"HyperDash":false},{"StartTime":17307.0,"Position":125.724724,"HyperDash":false},{"StartTime":17392.0,"Position":123.658127,"HyperDash":false},{"StartTime":17478.0,"Position":151.393967,"HyperDash":false},{"StartTime":17564.0,"Position":185.463791,"HyperDash":false},{"StartTime":17650.0,"Position":197.255447,"HyperDash":false},{"StartTime":17735.0,"Position":168.730637,"HyperDash":false},{"StartTime":17821.0,"Position":151.639252,"HyperDash":false},{"StartTime":17897.0,"Position":121.04126,"HyperDash":false},{"StartTime":17974.0,"Position":121.285477,"HyperDash":false},{"StartTime":18051.0,"Position":123.615044,"HyperDash":false},{"StartTime":18164.0,"Position":88.0,"HyperDash":false}]},{"StartTime":18507.0,"Objects":[{"StartTime":18507.0,"Position":424.0,"HyperDash":false},{"StartTime":18592.0,"Position":408.858429,"HyperDash":false},{"StartTime":18678.0,"Position":397.275269,"HyperDash":false},{"StartTime":18763.0,"Position":362.3419,"HyperDash":false},{"StartTime":18849.0,"Position":360.606018,"HyperDash":false},{"StartTime":18935.0,"Position":337.536224,"HyperDash":false},{"StartTime":19021.0,"Position":314.744537,"HyperDash":false},{"StartTime":19106.0,"Position":355.269379,"HyperDash":false},{"StartTime":19192.0,"Position":360.360748,"HyperDash":false},{"StartTime":19268.0,"Position":388.95874,"HyperDash":false},{"StartTime":19345.0,"Position":391.7145,"HyperDash":false},{"StartTime":19422.0,"Position":424.384949,"HyperDash":false},{"StartTime":19535.0,"Position":424.0,"HyperDash":false}]},{"StartTime":19707.0,"Objects":[{"StartTime":19707.0,"Position":368.0,"HyperDash":false}]},{"StartTime":19793.0,"Objects":[{"StartTime":19793.0,"Position":352.0,"HyperDash":false}]},{"StartTime":19878.0,"Objects":[{"StartTime":19878.0,"Position":336.0,"HyperDash":false},{"StartTime":19954.0,"Position":304.777771,"HyperDash":false},{"StartTime":20031.0,"Position":306.263153,"HyperDash":false},{"StartTime":20107.0,"Position":256.040924,"HyperDash":false},{"StartTime":20220.0,"Position":236.0,"HyperDash":false}]},{"StartTime":20564.0,"Objects":[{"StartTime":20564.0,"Position":136.0,"HyperDash":false},{"StartTime":20640.0,"Position":147.222229,"HyperDash":false},{"StartTime":20717.0,"Position":184.736847,"HyperDash":false},{"StartTime":20793.0,"Position":190.959076,"HyperDash":false},{"StartTime":20906.0,"Position":236.0,"HyperDash":false}]},{"StartTime":21250.0,"Objects":[{"StartTime":21250.0,"Position":392.0,"HyperDash":false},{"StartTime":21335.0,"Position":420.1406,"HyperDash":false},{"StartTime":21421.0,"Position":400.481,"HyperDash":false},{"StartTime":21506.0,"Position":414.916046,"HyperDash":false},{"StartTime":21592.0,"Position":414.21582,"HyperDash":false},{"StartTime":21660.0,"Position":403.507965,"HyperDash":false},{"StartTime":21764.0,"Position":397.683655,"HyperDash":true}]},{"StartTime":21936.0,"Objects":[{"StartTime":21936.0,"Position":120.0,"HyperDash":false},{"StartTime":22021.0,"Position":99.85941,"HyperDash":false},{"StartTime":22107.0,"Position":90.5190048,"HyperDash":false},{"StartTime":22192.0,"Position":91.0839539,"HyperDash":false},{"StartTime":22278.0,"Position":97.78417,"HyperDash":false},{"StartTime":22346.0,"Position":116.49202,"HyperDash":false},{"StartTime":22450.0,"Position":114.31633,"HyperDash":false}]},{"StartTime":22621.0,"Objects":[{"StartTime":22621.0,"Position":176.0,"HyperDash":false},{"StartTime":22706.0,"Position":203.4664,"HyperDash":false},{"StartTime":22792.0,"Position":212.3834,"HyperDash":false},{"StartTime":22877.0,"Position":234.448669,"HyperDash":false},{"StartTime":22963.0,"Position":266.38324,"HyperDash":false},{"StartTime":23031.0,"Position":276.057281,"HyperDash":false},{"StartTime":23135.0,"Position":297.221375,"HyperDash":false}]},{"StartTime":23307.0,"Objects":[{"StartTime":23307.0,"Position":297.0,"HyperDash":false}]},{"StartTime":23821.0,"Objects":[{"StartTime":23821.0,"Position":448.0,"HyperDash":false}]},{"StartTime":23993.0,"Objects":[{"StartTime":23993.0,"Position":352.0,"HyperDash":false},{"StartTime":24069.0,"Position":334.661774,"HyperDash":false},{"StartTime":24146.0,"Position":291.266022,"HyperDash":false},{"StartTime":24222.0,"Position":288.570435,"HyperDash":false},{"StartTime":24335.0,"Position":255.710861,"HyperDash":false}]},{"StartTime":24507.0,"Objects":[{"StartTime":24507.0,"Position":160.0,"HyperDash":false}]},{"StartTime":24593.0,"Objects":[{"StartTime":24593.0,"Position":160.0,"HyperDash":false}]},{"StartTime":24678.0,"Objects":[{"StartTime":24678.0,"Position":160.0,"HyperDash":false}]},{"StartTime":25021.0,"Objects":[{"StartTime":25021.0,"Position":88.0,"HyperDash":false}]},{"StartTime":25193.0,"Objects":[{"StartTime":25193.0,"Position":176.0,"HyperDash":false}]},{"StartTime":25364.0,"Objects":[{"StartTime":25364.0,"Position":256.0,"HyperDash":false}]},{"StartTime":25707.0,"Objects":[{"StartTime":25707.0,"Position":424.0,"HyperDash":false}]},{"StartTime":25878.0,"Objects":[{"StartTime":25878.0,"Position":448.0,"HyperDash":false}]},{"StartTime":26050.0,"Objects":[{"StartTime":26050.0,"Position":472.0,"HyperDash":false},{"StartTime":26135.0,"Position":467.1815,"HyperDash":false},{"StartTime":26221.0,"Position":430.508972,"HyperDash":false},{"StartTime":26306.0,"Position":426.762726,"HyperDash":false},{"StartTime":26392.0,"Position":386.21756,"HyperDash":false},{"StartTime":26460.0,"Position":355.4007,"HyperDash":false},{"StartTime":26564.0,"Position":336.352875,"HyperDash":false}]},{"StartTime":26736.0,"Objects":[{"StartTime":26736.0,"Position":304.0,"HyperDash":false},{"StartTime":26812.0,"Position":269.465637,"HyperDash":false},{"StartTime":26889.0,"Position":283.52124,"HyperDash":false},{"StartTime":26965.0,"Position":262.29248,"HyperDash":false},{"StartTime":27078.0,"Position":241.4827,"HyperDash":false}]},{"StartTime":27250.0,"Objects":[{"StartTime":27250.0,"Position":508.0,"HyperDash":false},{"StartTime":27303.0,"Position":417.0,"HyperDash":false},{"StartTime":27357.0,"Position":302.0,"HyperDash":false},{"StartTime":27410.0,"Position":132.0,"HyperDash":false},{"StartTime":27464.0,"Position":352.0,"HyperDash":false},{"StartTime":27517.0,"Position":174.0,"HyperDash":false},{"StartTime":27571.0,"Position":453.0,"HyperDash":false},{"StartTime":27624.0,"Position":205.0,"HyperDash":false},{"StartTime":27678.0,"Position":105.0,"HyperDash":false},{"StartTime":27732.0,"Position":213.0,"HyperDash":false},{"StartTime":27785.0,"Position":472.0,"HyperDash":false},{"StartTime":27839.0,"Position":251.0,"HyperDash":false},{"StartTime":27892.0,"Position":208.0,"HyperDash":false},{"StartTime":27946.0,"Position":261.0,"HyperDash":false},{"StartTime":27999.0,"Position":382.0,"HyperDash":false},{"StartTime":28053.0,"Position":170.0,"HyperDash":false},{"StartTime":28107.0,"Position":269.0,"HyperDash":false}]},{"StartTime":28621.0,"Objects":[{"StartTime":28621.0,"Position":32.0,"HyperDash":false}]},{"StartTime":28793.0,"Objects":[{"StartTime":28793.0,"Position":80.0,"HyperDash":false}]},{"StartTime":29307.0,"Objects":[{"StartTime":29307.0,"Position":352.0,"HyperDash":false}]},{"StartTime":29478.0,"Objects":[{"StartTime":29478.0,"Position":424.0,"HyperDash":false}]},{"StartTime":29650.0,"Objects":[{"StartTime":29650.0,"Position":472.0,"HyperDash":false}]},{"StartTime":29821.0,"Objects":[{"StartTime":29821.0,"Position":432.0,"HyperDash":false}]},{"StartTime":29993.0,"Objects":[{"StartTime":29993.0,"Position":360.0,"HyperDash":false}]},{"StartTime":30078.0,"Objects":[{"StartTime":30078.0,"Position":360.0,"HyperDash":false}]},{"StartTime":30164.0,"Objects":[{"StartTime":30164.0,"Position":360.0,"HyperDash":false}]},{"StartTime":30507.0,"Objects":[{"StartTime":30507.0,"Position":184.0,"HyperDash":false},{"StartTime":30592.0,"Position":194.11496,"HyperDash":false},{"StartTime":30678.0,"Position":206.360687,"HyperDash":false},{"StartTime":30745.0,"Position":207.599487,"HyperDash":false},{"StartTime":30849.0,"Position":184.0,"HyperDash":false}]},{"StartTime":31193.0,"Objects":[{"StartTime":31193.0,"Position":64.0,"HyperDash":false},{"StartTime":31278.0,"Position":59.6773758,"HyperDash":false},{"StartTime":31364.0,"Position":91.51566,"HyperDash":false},{"StartTime":31431.0,"Position":76.73467,"HyperDash":false},{"StartTime":31535.0,"Position":64.0,"HyperDash":false}]},{"StartTime":31878.0,"Objects":[{"StartTime":31878.0,"Position":352.0,"HyperDash":false},{"StartTime":31963.0,"Position":310.184479,"HyperDash":false},{"StartTime":32049.0,"Position":302.077,"HyperDash":false},{"StartTime":32116.0,"Position":332.637482,"HyperDash":false},{"StartTime":32220.0,"Position":352.0,"HyperDash":false}]},{"StartTime":32393.0,"Objects":[{"StartTime":32393.0,"Position":320.0,"HyperDash":false},{"StartTime":32435.0,"Position":297.345428,"HyperDash":false},{"StartTime":32478.0,"Position":320.0,"HyperDash":false},{"StartTime":32521.0,"Position":297.345428,"HyperDash":false},{"StartTime":32564.0,"Position":320.0,"HyperDash":false},{"StartTime":32607.0,"Position":297.345428,"HyperDash":false}]},{"StartTime":32736.0,"Objects":[{"StartTime":32736.0,"Position":342.0,"HyperDash":false},{"StartTime":32778.0,"Position":319.345428,"HyperDash":false},{"StartTime":32821.0,"Position":342.0,"HyperDash":false},{"StartTime":32864.0,"Position":319.345428,"HyperDash":false},{"StartTime":32907.0,"Position":342.0,"HyperDash":false},{"StartTime":32950.0,"Position":319.345428,"HyperDash":false}]},{"StartTime":33078.0,"Objects":[{"StartTime":33078.0,"Position":399.0,"HyperDash":false},{"StartTime":33120.0,"Position":376.345428,"HyperDash":false},{"StartTime":33163.0,"Position":399.0,"HyperDash":false},{"StartTime":33206.0,"Position":376.345428,"HyperDash":false},{"StartTime":33249.0,"Position":399.0,"HyperDash":false},{"StartTime":33292.0,"Position":376.345428,"HyperDash":false}]},{"StartTime":33421.0,"Objects":[{"StartTime":33421.0,"Position":422.0,"HyperDash":false},{"StartTime":33463.0,"Position":399.345428,"HyperDash":false},{"StartTime":33506.0,"Position":422.0,"HyperDash":false},{"StartTime":33549.0,"Position":399.345428,"HyperDash":false},{"StartTime":33592.0,"Position":422.0,"HyperDash":false},{"StartTime":33635.0,"Position":399.345428,"HyperDash":false},{"StartTime":33678.0,"Position":422.0,"HyperDash":false}]},{"StartTime":34107.0,"Objects":[{"StartTime":34107.0,"Position":368.0,"HyperDash":false}]},{"StartTime":34278.0,"Objects":[{"StartTime":34278.0,"Position":280.0,"HyperDash":false}]},{"StartTime":34793.0,"Objects":[{"StartTime":34793.0,"Position":280.0,"HyperDash":false}]},{"StartTime":34964.0,"Objects":[{"StartTime":34964.0,"Position":184.0,"HyperDash":false}]},{"StartTime":35136.0,"Objects":[{"StartTime":35136.0,"Position":112.0,"HyperDash":false}]},{"StartTime":35307.0,"Objects":[{"StartTime":35307.0,"Position":64.0,"HyperDash":false}]},{"StartTime":35478.0,"Objects":[{"StartTime":35478.0,"Position":32.0,"HyperDash":false}]},{"StartTime":35564.0,"Objects":[{"StartTime":35564.0,"Position":32.0,"HyperDash":false}]},{"StartTime":35650.0,"Objects":[{"StartTime":35650.0,"Position":32.0,"HyperDash":false}]},{"StartTime":35993.0,"Objects":[{"StartTime":35993.0,"Position":232.0,"HyperDash":false}]},{"StartTime":36164.0,"Objects":[{"StartTime":36164.0,"Position":328.0,"HyperDash":false}]},{"StartTime":36336.0,"Objects":[{"StartTime":36336.0,"Position":408.0,"HyperDash":false}]},{"StartTime":36507.0,"Objects":[{"StartTime":36507.0,"Position":464.0,"HyperDash":false}]},{"StartTime":36678.0,"Objects":[{"StartTime":36678.0,"Position":408.0,"HyperDash":false}]},{"StartTime":36850.0,"Objects":[{"StartTime":36850.0,"Position":328.0,"HyperDash":false}]},{"StartTime":37021.0,"Objects":[{"StartTime":37021.0,"Position":232.0,"HyperDash":false}]},{"StartTime":37535.0,"Objects":[{"StartTime":37535.0,"Position":72.0,"HyperDash":false}]},{"StartTime":37707.0,"Objects":[{"StartTime":37707.0,"Position":112.0,"HyperDash":false}]},{"StartTime":37878.0,"Objects":[{"StartTime":37878.0,"Position":144.0,"HyperDash":false},{"StartTime":37920.0,"Position":119.0,"HyperDash":false},{"StartTime":37963.0,"Position":144.0,"HyperDash":false},{"StartTime":38006.0,"Position":119.0,"HyperDash":false},{"StartTime":38049.0,"Position":144.0,"HyperDash":false}]},{"StartTime":38221.0,"Objects":[{"StartTime":38221.0,"Position":232.0,"HyperDash":false},{"StartTime":38263.0,"Position":207.0,"HyperDash":false},{"StartTime":38306.0,"Position":232.0,"HyperDash":false},{"StartTime":38349.0,"Position":207.0,"HyperDash":false},{"StartTime":38392.0,"Position":232.0,"HyperDash":false}]},{"StartTime":38564.0,"Objects":[{"StartTime":38564.0,"Position":320.0,"HyperDash":false},{"StartTime":38606.0,"Position":295.0,"HyperDash":false},{"StartTime":38649.0,"Position":320.0,"HyperDash":false},{"StartTime":38692.0,"Position":295.0,"HyperDash":false},{"StartTime":38735.0,"Position":320.0,"HyperDash":false}]},{"StartTime":38907.0,"Objects":[{"StartTime":38907.0,"Position":408.0,"HyperDash":false},{"StartTime":38949.0,"Position":383.0,"HyperDash":false},{"StartTime":38992.0,"Position":408.0,"HyperDash":false},{"StartTime":39035.0,"Position":383.0,"HyperDash":false},{"StartTime":39078.0,"Position":408.0,"HyperDash":false}]},{"StartTime":39593.0,"Objects":[{"StartTime":39593.0,"Position":304.0,"HyperDash":false}]},{"StartTime":39764.0,"Objects":[{"StartTime":39764.0,"Position":208.0,"HyperDash":false}]},{"StartTime":40278.0,"Objects":[{"StartTime":40278.0,"Position":40.0,"HyperDash":false}]},{"StartTime":40450.0,"Objects":[{"StartTime":40450.0,"Position":112.0,"HyperDash":false}]},{"StartTime":40621.0,"Objects":[{"StartTime":40621.0,"Position":200.0,"HyperDash":false}]},{"StartTime":40793.0,"Objects":[{"StartTime":40793.0,"Position":264.0,"HyperDash":false}]},{"StartTime":40964.0,"Objects":[{"StartTime":40964.0,"Position":352.0,"HyperDash":false}]},{"StartTime":41050.0,"Objects":[{"StartTime":41050.0,"Position":352.0,"HyperDash":false}]},{"StartTime":41135.0,"Objects":[{"StartTime":41135.0,"Position":352.0,"HyperDash":false}]},{"StartTime":41478.0,"Objects":[{"StartTime":41478.0,"Position":480.0,"HyperDash":false}]},{"StartTime":41650.0,"Objects":[{"StartTime":41650.0,"Position":422.0,"HyperDash":false}]},{"StartTime":41821.0,"Objects":[{"StartTime":41821.0,"Position":364.0,"HyperDash":false}]},{"StartTime":41993.0,"Objects":[{"StartTime":41993.0,"Position":422.0,"HyperDash":false}]},{"StartTime":42164.0,"Objects":[{"StartTime":42164.0,"Position":327.0,"HyperDash":false}]},{"StartTime":42335.0,"Objects":[{"StartTime":42335.0,"Position":226.0,"HyperDash":false}]},{"StartTime":42507.0,"Objects":[{"StartTime":42507.0,"Position":327.0,"HyperDash":false}]},{"StartTime":42678.0,"Objects":[{"StartTime":42678.0,"Position":381.0,"HyperDash":false}]},{"StartTime":42850.0,"Objects":[{"StartTime":42850.0,"Position":437.0,"HyperDash":false}]},{"StartTime":43021.0,"Objects":[{"StartTime":43021.0,"Position":381.0,"HyperDash":false}]},{"StartTime":43193.0,"Objects":[{"StartTime":43193.0,"Position":327.0,"HyperDash":false}]},{"StartTime":43278.0,"Objects":[{"StartTime":43278.0,"Position":16.0,"HyperDash":false},{"StartTime":43374.0,"Position":248.0,"HyperDash":false},{"StartTime":43471.0,"Position":100.0,"HyperDash":false},{"StartTime":43567.0,"Position":24.0,"HyperDash":false},{"StartTime":43664.0,"Position":66.0,"HyperDash":false},{"StartTime":43760.0,"Position":97.0,"HyperDash":false},{"StartTime":43857.0,"Position":267.0,"HyperDash":false},{"StartTime":43953.0,"Position":116.0,"HyperDash":false},{"StartTime":44050.0,"Position":451.0,"HyperDash":false}]},{"StartTime":44221.0,"Objects":[{"StartTime":44221.0,"Position":328.0,"HyperDash":false},{"StartTime":44297.0,"Position":357.352631,"HyperDash":false},{"StartTime":44374.0,"Position":374.9336,"HyperDash":false},{"StartTime":44450.0,"Position":395.286255,"HyperDash":false},{"StartTime":44563.0,"Position":406.086884,"HyperDash":false}]},{"StartTime":44907.0,"Objects":[{"StartTime":44907.0,"Position":184.0,"HyperDash":false},{"StartTime":44983.0,"Position":158.647354,"HyperDash":false},{"StartTime":45060.0,"Position":135.0664,"HyperDash":false},{"StartTime":45136.0,"Position":127.713745,"HyperDash":false},{"StartTime":45249.0,"Position":105.913116,"HyperDash":false}]},{"StartTime":45421.0,"Objects":[{"StartTime":45421.0,"Position":192.0,"HyperDash":false}]},{"StartTime":45507.0,"Objects":[{"StartTime":45507.0,"Position":192.0,"HyperDash":false}]},{"StartTime":45593.0,"Objects":[{"StartTime":45593.0,"Position":192.0,"HyperDash":false}]},{"StartTime":45764.0,"Objects":[{"StartTime":45764.0,"Position":106.0,"HyperDash":false}]},{"StartTime":45850.0,"Objects":[{"StartTime":45850.0,"Position":106.0,"HyperDash":false}]},{"StartTime":45935.0,"Objects":[{"StartTime":45935.0,"Position":106.0,"HyperDash":false}]},{"StartTime":46107.0,"Objects":[{"StartTime":46107.0,"Position":154.0,"HyperDash":false}]},{"StartTime":46278.0,"Objects":[{"StartTime":46278.0,"Position":237.0,"HyperDash":false}]},{"StartTime":46364.0,"Objects":[{"StartTime":46364.0,"Position":237.0,"HyperDash":false}]},{"StartTime":46450.0,"Objects":[{"StartTime":46450.0,"Position":237.0,"HyperDash":false}]},{"StartTime":46535.0,"Objects":[{"StartTime":46535.0,"Position":237.0,"HyperDash":false}]},{"StartTime":46621.0,"Objects":[{"StartTime":46621.0,"Position":237.0,"HyperDash":false}]},{"StartTime":46964.0,"Objects":[{"StartTime":46964.0,"Position":410.0,"HyperDash":false}]},{"StartTime":47135.0,"Objects":[{"StartTime":47135.0,"Position":410.0,"HyperDash":false}]},{"StartTime":47307.0,"Objects":[{"StartTime":47307.0,"Position":462.0,"HyperDash":false}]},{"StartTime":47478.0,"Objects":[{"StartTime":47478.0,"Position":462.0,"HyperDash":false}]},{"StartTime":47650.0,"Objects":[{"StartTime":47650.0,"Position":379.0,"HyperDash":false}]},{"StartTime":47821.0,"Objects":[{"StartTime":47821.0,"Position":379.0,"HyperDash":false}]},{"StartTime":47993.0,"Objects":[{"StartTime":47993.0,"Position":328.0,"HyperDash":false}]},{"StartTime":48164.0,"Objects":[{"StartTime":48164.0,"Position":328.0,"HyperDash":false}]},{"StartTime":48335.0,"Objects":[{"StartTime":48335.0,"Position":237.0,"HyperDash":false}]},{"StartTime":48507.0,"Objects":[{"StartTime":48507.0,"Position":328.0,"HyperDash":false}]},{"StartTime":48678.0,"Objects":[{"StartTime":48678.0,"Position":410.0,"HyperDash":false}]},{"StartTime":48935.0,"Objects":[{"StartTime":48935.0,"Position":264.0,"HyperDash":false}]},{"StartTime":49021.0,"Objects":[{"StartTime":49021.0,"Position":264.0,"HyperDash":false}]},{"StartTime":49193.0,"Objects":[{"StartTime":49193.0,"Position":304.0,"HyperDash":false}]},{"StartTime":49364.0,"Objects":[{"StartTime":49364.0,"Position":368.0,"HyperDash":false}]},{"StartTime":49707.0,"Objects":[{"StartTime":49707.0,"Position":368.0,"HyperDash":false},{"StartTime":49783.0,"Position":403.222229,"HyperDash":false},{"StartTime":49860.0,"Position":411.736847,"HyperDash":false},{"StartTime":49936.0,"Position":434.959076,"HyperDash":false},{"StartTime":50049.0,"Position":468.0,"HyperDash":false}]},{"StartTime":50393.0,"Objects":[{"StartTime":50393.0,"Position":280.0,"HyperDash":false},{"StartTime":50469.0,"Position":295.222229,"HyperDash":false},{"StartTime":50546.0,"Position":340.736847,"HyperDash":false},{"StartTime":50622.0,"Position":355.959076,"HyperDash":false},{"StartTime":50735.0,"Position":380.0,"HyperDash":false}]},{"StartTime":51250.0,"Objects":[{"StartTime":51250.0,"Position":88.0,"HyperDash":false},{"StartTime":51326.0,"Position":103.222221,"HyperDash":false},{"StartTime":51403.0,"Position":148.736847,"HyperDash":false},{"StartTime":51479.0,"Position":138.959076,"HyperDash":false},{"StartTime":51592.0,"Position":188.0,"HyperDash":false}]},{"StartTime":51764.0,"Objects":[{"StartTime":51764.0,"Position":264.0,"HyperDash":false}]},{"StartTime":51850.0,"Objects":[{"StartTime":51850.0,"Position":280.0,"HyperDash":false}]},{"StartTime":51935.0,"Objects":[{"StartTime":51935.0,"Position":296.0,"HyperDash":false}]},{"StartTime":52021.0,"Objects":[{"StartTime":52021.0,"Position":312.0,"HyperDash":false}]},{"StartTime":52107.0,"Objects":[{"StartTime":52107.0,"Position":328.0,"HyperDash":false}]},{"StartTime":52450.0,"Objects":[{"StartTime":52450.0,"Position":208.0,"HyperDash":false}]},{"StartTime":52621.0,"Objects":[{"StartTime":52621.0,"Position":304.0,"HyperDash":false}]},{"StartTime":52793.0,"Objects":[{"StartTime":52793.0,"Position":256.0,"HyperDash":false}]},{"StartTime":53135.0,"Objects":[{"StartTime":53135.0,"Position":208.0,"HyperDash":false}]},{"StartTime":53307.0,"Objects":[{"StartTime":53307.0,"Position":304.0,"HyperDash":false}]},{"StartTime":53478.0,"Objects":[{"StartTime":53478.0,"Position":208.0,"HyperDash":false}]},{"StartTime":53650.0,"Objects":[{"StartTime":53650.0,"Position":304.0,"HyperDash":false}]},{"StartTime":53821.0,"Objects":[{"StartTime":53821.0,"Position":208.0,"HyperDash":false}]},{"StartTime":53993.0,"Objects":[{"StartTime":53993.0,"Position":304.0,"HyperDash":false}]},{"StartTime":54164.0,"Objects":[{"StartTime":54164.0,"Position":247.0,"HyperDash":false},{"StartTime":54217.0,"Position":162.0,"HyperDash":false},{"StartTime":54271.0,"Position":383.0,"HyperDash":false},{"StartTime":54324.0,"Position":127.0,"HyperDash":false},{"StartTime":54378.0,"Position":161.0,"HyperDash":false},{"StartTime":54431.0,"Position":332.0,"HyperDash":false},{"StartTime":54485.0,"Position":356.0,"HyperDash":false},{"StartTime":54538.0,"Position":362.0,"HyperDash":false},{"StartTime":54592.0,"Position":347.0,"HyperDash":false},{"StartTime":54646.0,"Position":252.0,"HyperDash":false},{"StartTime":54699.0,"Position":477.0,"HyperDash":false},{"StartTime":54753.0,"Position":358.0,"HyperDash":false},{"StartTime":54806.0,"Position":17.0,"HyperDash":false},{"StartTime":54860.0,"Position":399.0,"HyperDash":false},{"StartTime":54913.0,"Position":280.0,"HyperDash":false},{"StartTime":54967.0,"Position":304.0,"HyperDash":false},{"StartTime":55021.0,"Position":221.0,"HyperDash":false}]},{"StartTime":55193.0,"Objects":[{"StartTime":55193.0,"Position":256.0,"HyperDash":false},{"StartTime":55269.0,"Position":251.286514,"HyperDash":false},{"StartTime":55346.0,"Position":219.366272,"HyperDash":false},{"StartTime":55422.0,"Position":195.652786,"HyperDash":false},{"StartTime":55535.0,"Position":185.289337,"HyperDash":false}]},{"StartTime":55878.0,"Objects":[{"StartTime":55878.0,"Position":256.0,"HyperDash":false},{"StartTime":55954.0,"Position":280.71347,"HyperDash":false},{"StartTime":56031.0,"Position":289.633728,"HyperDash":false},{"StartTime":56107.0,"Position":299.3472,"HyperDash":false},{"StartTime":56220.0,"Position":326.710663,"HyperDash":false}]},{"StartTime":56393.0,"Objects":[{"StartTime":56393.0,"Position":256.0,"HyperDash":false}]},{"StartTime":56564.0,"Objects":[{"StartTime":56564.0,"Position":160.0,"HyperDash":false}]},{"StartTime":56650.0,"Objects":[{"StartTime":56650.0,"Position":160.0,"HyperDash":false}]},{"StartTime":56735.0,"Objects":[{"StartTime":56735.0,"Position":160.0,"HyperDash":false}]},{"StartTime":56907.0,"Objects":[{"StartTime":56907.0,"Position":160.0,"HyperDash":false}]},{"StartTime":57078.0,"Objects":[{"StartTime":57078.0,"Position":256.0,"HyperDash":false}]},{"StartTime":57250.0,"Objects":[{"StartTime":57250.0,"Position":352.0,"HyperDash":false}]},{"StartTime":57335.0,"Objects":[{"StartTime":57335.0,"Position":360.0,"HyperDash":false}]},{"StartTime":57421.0,"Objects":[{"StartTime":57421.0,"Position":368.0,"HyperDash":false}]},{"StartTime":57507.0,"Objects":[{"StartTime":57507.0,"Position":376.0,"HyperDash":false}]},{"StartTime":57593.0,"Objects":[{"StartTime":57593.0,"Position":384.0,"HyperDash":false}]},{"StartTime":57935.0,"Objects":[{"StartTime":57935.0,"Position":472.0,"HyperDash":false}]},{"StartTime":58107.0,"Objects":[{"StartTime":58107.0,"Position":387.0,"HyperDash":false}]},{"StartTime":58278.0,"Objects":[{"StartTime":58278.0,"Position":284.0,"HyperDash":false}]},{"StartTime":58450.0,"Objects":[{"StartTime":58450.0,"Position":193.0,"HyperDash":false}]},{"StartTime":58621.0,"Objects":[{"StartTime":58621.0,"Position":139.0,"HyperDash":false}]},{"StartTime":58793.0,"Objects":[{"StartTime":58793.0,"Position":132.0,"HyperDash":false}]},{"StartTime":58964.0,"Objects":[{"StartTime":58964.0,"Position":174.0,"HyperDash":false}]},{"StartTime":59307.0,"Objects":[{"StartTime":59307.0,"Position":256.0,"HyperDash":false}]},{"StartTime":59478.0,"Objects":[{"StartTime":59478.0,"Position":208.0,"HyperDash":false}]},{"StartTime":59650.0,"Objects":[{"StartTime":59650.0,"Position":304.0,"HyperDash":false}]},{"StartTime":59821.0,"Objects":[{"StartTime":59821.0,"Position":344.0,"HyperDash":false}]},{"StartTime":59907.0,"Objects":[{"StartTime":59907.0,"Position":312.0,"HyperDash":false}]},{"StartTime":59993.0,"Objects":[{"StartTime":59993.0,"Position":280.0,"HyperDash":false}]},{"StartTime":60164.0,"Objects":[{"StartTime":60164.0,"Position":208.0,"HyperDash":false}]},{"StartTime":60335.0,"Objects":[{"StartTime":60335.0,"Position":304.0,"HyperDash":false}]},{"StartTime":60678.0,"Objects":[{"StartTime":60678.0,"Position":200.0,"HyperDash":false},{"StartTime":60754.0,"Position":175.647354,"HyperDash":false},{"StartTime":60831.0,"Position":176.0664,"HyperDash":false},{"StartTime":60907.0,"Position":137.713745,"HyperDash":false},{"StartTime":61020.0,"Position":121.913116,"HyperDash":false}]},{"StartTime":61364.0,"Objects":[{"StartTime":61364.0,"Position":312.0,"HyperDash":false},{"StartTime":61440.0,"Position":348.352631,"HyperDash":false},{"StartTime":61517.0,"Position":333.9336,"HyperDash":false},{"StartTime":61593.0,"Position":362.286255,"HyperDash":false},{"StartTime":61706.0,"Position":390.086884,"HyperDash":false}]},{"StartTime":62050.0,"Objects":[{"StartTime":62050.0,"Position":390.0,"HyperDash":false}]},{"StartTime":62393.0,"Objects":[{"StartTime":62393.0,"Position":121.0,"HyperDash":false}]},{"StartTime":62735.0,"Objects":[{"StartTime":62735.0,"Position":256.0,"HyperDash":false}]},{"StartTime":62821.0,"Objects":[{"StartTime":62821.0,"Position":256.0,"HyperDash":false}]},{"StartTime":62907.0,"Objects":[{"StartTime":62907.0,"Position":256.0,"HyperDash":false}]},{"StartTime":62993.0,"Objects":[{"StartTime":62993.0,"Position":256.0,"HyperDash":false}]},{"StartTime":63078.0,"Objects":[{"StartTime":63078.0,"Position":256.0,"HyperDash":false}]},{"StartTime":63421.0,"Objects":[{"StartTime":63421.0,"Position":432.0,"HyperDash":false}]},{"StartTime":63593.0,"Objects":[{"StartTime":63593.0,"Position":496.0,"HyperDash":false}]},{"StartTime":63764.0,"Objects":[{"StartTime":63764.0,"Position":496.0,"HyperDash":false}]},{"StartTime":63935.0,"Objects":[{"StartTime":63935.0,"Position":440.0,"HyperDash":false}]},{"StartTime":64107.0,"Objects":[{"StartTime":64107.0,"Position":352.0,"HyperDash":false}]},{"StartTime":64278.0,"Objects":[{"StartTime":64278.0,"Position":256.0,"HyperDash":false}]},{"StartTime":64450.0,"Objects":[{"StartTime":64450.0,"Position":160.0,"HyperDash":false}]},{"StartTime":64621.0,"Objects":[{"StartTime":64621.0,"Position":72.0,"HyperDash":false}]},{"StartTime":64793.0,"Objects":[{"StartTime":64793.0,"Position":8.0,"HyperDash":false}]},{"StartTime":64964.0,"Objects":[{"StartTime":64964.0,"Position":8.0,"HyperDash":false}]},{"StartTime":65135.0,"Objects":[{"StartTime":65135.0,"Position":56.0,"HyperDash":false}]},{"StartTime":65221.0,"Objects":[{"StartTime":65221.0,"Position":437.0,"HyperDash":false},{"StartTime":65317.0,"Position":289.0,"HyperDash":false},{"StartTime":65414.0,"Position":464.0,"HyperDash":false},{"StartTime":65510.0,"Position":36.0,"HyperDash":false},{"StartTime":65607.0,"Position":378.0,"HyperDash":false},{"StartTime":65703.0,"Position":297.0,"HyperDash":false},{"StartTime":65800.0,"Position":418.0,"HyperDash":false},{"StartTime":65896.0,"Position":329.0,"HyperDash":false},{"StartTime":65993.0,"Position":338.0,"HyperDash":false}]},{"StartTime":66164.0,"Objects":[{"StartTime":66164.0,"Position":296.0,"HyperDash":false},{"StartTime":66240.0,"Position":317.930573,"HyperDash":false},{"StartTime":66317.0,"Position":317.018127,"HyperDash":false},{"StartTime":66393.0,"Position":314.9487,"HyperDash":false},{"StartTime":66506.0,"Position":349.687561,"HyperDash":false}]},{"StartTime":66850.0,"Objects":[{"StartTime":66850.0,"Position":216.0,"HyperDash":false},{"StartTime":66926.0,"Position":184.069427,"HyperDash":false},{"StartTime":67003.0,"Position":174.981888,"HyperDash":false},{"StartTime":67079.0,"Position":196.051315,"HyperDash":false},{"StartTime":67192.0,"Position":162.312454,"HyperDash":false}]},{"StartTime":67535.0,"Objects":[{"StartTime":67535.0,"Position":296.0,"HyperDash":false},{"StartTime":67611.0,"Position":288.930573,"HyperDash":false},{"StartTime":67688.0,"Position":339.018127,"HyperDash":false},{"StartTime":67764.0,"Position":312.9487,"HyperDash":false},{"StartTime":67877.0,"Position":349.687561,"HyperDash":false}]},{"StartTime":67964.0,"Objects":[{"StartTime":67964.0,"Position":267.0,"HyperDash":false},{"StartTime":68060.0,"Position":477.0,"HyperDash":false},{"StartTime":68156.0,"Position":282.0,"HyperDash":false},{"StartTime":68253.0,"Position":216.0,"HyperDash":false},{"StartTime":68349.0,"Position":106.0,"HyperDash":false},{"StartTime":68445.0,"Position":353.0,"HyperDash":false},{"StartTime":68542.0,"Position":162.0,"HyperDash":false},{"StartTime":68638.0,"Position":473.0,"HyperDash":false},{"StartTime":68735.0,"Position":260.0,"HyperDash":false}]},{"StartTime":68907.0,"Objects":[{"StartTime":68907.0,"Position":304.0,"HyperDash":false},{"StartTime":68983.0,"Position":334.222229,"HyperDash":false},{"StartTime":69060.0,"Position":328.736847,"HyperDash":false},{"StartTime":69136.0,"Position":355.959076,"HyperDash":false},{"StartTime":69249.0,"Position":404.0,"HyperDash":false}]},{"StartTime":69593.0,"Objects":[{"StartTime":69593.0,"Position":208.0,"HyperDash":false},{"StartTime":69669.0,"Position":188.777771,"HyperDash":false},{"StartTime":69746.0,"Position":175.263153,"HyperDash":false},{"StartTime":69822.0,"Position":151.040924,"HyperDash":false},{"StartTime":69935.0,"Position":108.0,"HyperDash":false}]},{"StartTime":70278.0,"Objects":[{"StartTime":70278.0,"Position":304.0,"HyperDash":false},{"StartTime":70354.0,"Position":332.222229,"HyperDash":false},{"StartTime":70431.0,"Position":343.736847,"HyperDash":false},{"StartTime":70507.0,"Position":361.959076,"HyperDash":false},{"StartTime":70620.0,"Position":404.0,"HyperDash":false}]},{"StartTime":71307.0,"Objects":[{"StartTime":71307.0,"Position":56.0,"HyperDash":false},{"StartTime":71392.0,"Position":57.8317223,"HyperDash":false},{"StartTime":71478.0,"Position":43.0449677,"HyperDash":false},{"StartTime":71563.0,"Position":62.4877777,"HyperDash":false},{"StartTime":71649.0,"Position":54.65224,"HyperDash":false},{"StartTime":71725.0,"Position":50.5213776,"HyperDash":false},{"StartTime":71802.0,"Position":33.41652,"HyperDash":false},{"StartTime":71879.0,"Position":58.5728569,"HyperDash":false},{"StartTime":71992.0,"Position":56.0,"HyperDash":false}]},{"StartTime":72335.0,"Objects":[{"StartTime":72335.0,"Position":256.0,"HyperDash":false}]},{"StartTime":72507.0,"Objects":[{"StartTime":72507.0,"Position":328.0,"HyperDash":false}]},{"StartTime":72678.0,"Objects":[{"StartTime":72678.0,"Position":400.0,"HyperDash":false}]},{"StartTime":73021.0,"Objects":[{"StartTime":73021.0,"Position":400.0,"HyperDash":false},{"StartTime":73097.0,"Position":415.600647,"HyperDash":false},{"StartTime":73174.0,"Position":409.291138,"HyperDash":false},{"StartTime":73250.0,"Position":437.285278,"HyperDash":false},{"StartTime":73363.0,"Position":410.36676,"HyperDash":false}]},{"StartTime":73707.0,"Objects":[{"StartTime":73707.0,"Position":112.0,"HyperDash":false},{"StartTime":73783.0,"Position":117.399353,"HyperDash":false},{"StartTime":73860.0,"Position":79.7088547,"HyperDash":false},{"StartTime":73936.0,"Position":102.714714,"HyperDash":false},{"StartTime":74049.0,"Position":101.633247,"HyperDash":false}]},{"StartTime":74393.0,"Objects":[{"StartTime":74393.0,"Position":304.0,"HyperDash":false},{"StartTime":74469.0,"Position":301.197144,"HyperDash":false},{"StartTime":74546.0,"Position":342.5416,"HyperDash":false},{"StartTime":74622.0,"Position":349.738739,"HyperDash":false},{"StartTime":74735.0,"Position":354.387115,"HyperDash":false}]},{"StartTime":75078.0,"Objects":[{"StartTime":75078.0,"Position":304.0,"HyperDash":false},{"StartTime":75154.0,"Position":303.197144,"HyperDash":false},{"StartTime":75231.0,"Position":337.5416,"HyperDash":false},{"StartTime":75307.0,"Position":353.738739,"HyperDash":false},{"StartTime":75420.0,"Position":354.387115,"HyperDash":false}]},{"StartTime":75764.0,"Objects":[{"StartTime":75764.0,"Position":464.0,"HyperDash":false}]},{"StartTime":75935.0,"Objects":[{"StartTime":75935.0,"Position":384.0,"HyperDash":false}]},{"StartTime":76107.0,"Objects":[{"StartTime":76107.0,"Position":304.0,"HyperDash":false}]},{"StartTime":76278.0,"Objects":[{"StartTime":76278.0,"Position":232.0,"HyperDash":false}]},{"StartTime":76450.0,"Objects":[{"StartTime":76450.0,"Position":160.0,"HyperDash":false},{"StartTime":76535.0,"Position":135.0,"HyperDash":false},{"StartTime":76621.0,"Position":160.0,"HyperDash":false}]},{"StartTime":76793.0,"Objects":[{"StartTime":76793.0,"Position":80.0,"HyperDash":false}]},{"StartTime":77135.0,"Objects":[{"StartTime":77135.0,"Position":120.0,"HyperDash":false},{"StartTime":77211.0,"Position":119.7057,"HyperDash":false},{"StartTime":77288.0,"Position":91.15754,"HyperDash":false},{"StartTime":77364.0,"Position":60.8632431,"HyperDash":false},{"StartTime":77477.0,"Position":33.1756821,"HyperDash":false}]},{"StartTime":77821.0,"Objects":[{"StartTime":77821.0,"Position":232.0,"HyperDash":false},{"StartTime":77897.0,"Position":234.148621,"HyperDash":false},{"StartTime":77974.0,"Position":272.5492,"HyperDash":false},{"StartTime":78050.0,"Position":284.6978,"HyperDash":false},{"StartTime":78163.0,"Position":318.1688,"HyperDash":false}]},{"StartTime":78507.0,"Objects":[{"StartTime":78507.0,"Position":176.0,"HyperDash":false},{"StartTime":78583.0,"Position":142.7057,"HyperDash":false},{"StartTime":78660.0,"Position":129.157532,"HyperDash":false},{"StartTime":78736.0,"Position":124.863243,"HyperDash":false},{"StartTime":78849.0,"Position":89.17568,"HyperDash":false}]},{"StartTime":79193.0,"Objects":[{"StartTime":79193.0,"Position":288.0,"HyperDash":false},{"StartTime":79269.0,"Position":321.241455,"HyperDash":false},{"StartTime":79346.0,"Position":319.736084,"HyperDash":false},{"StartTime":79422.0,"Position":360.9775,"HyperDash":false},{"StartTime":79535.0,"Position":374.586517,"HyperDash":false}]},{"StartTime":79878.0,"Objects":[{"StartTime":79878.0,"Position":240.0,"HyperDash":false},{"StartTime":79954.0,"Position":209.7057,"HyperDash":false},{"StartTime":80031.0,"Position":189.157532,"HyperDash":false},{"StartTime":80107.0,"Position":196.863251,"HyperDash":false},{"StartTime":80220.0,"Position":153.17569,"HyperDash":false}]},{"StartTime":80564.0,"Objects":[{"StartTime":80564.0,"Position":32.0,"HyperDash":false}]},{"StartTime":80735.0,"Objects":[{"StartTime":80735.0,"Position":66.0,"HyperDash":false}]},{"StartTime":80907.0,"Objects":[{"StartTime":80907.0,"Position":161.0,"HyperDash":false}]},{"StartTime":81078.0,"Objects":[{"StartTime":81078.0,"Position":190.0,"HyperDash":false}]},{"StartTime":81250.0,"Objects":[{"StartTime":81250.0,"Position":285.0,"HyperDash":false}]},{"StartTime":81593.0,"Objects":[{"StartTime":81593.0,"Position":384.0,"HyperDash":false},{"StartTime":81652.0,"Position":401.608948,"HyperDash":false},{"StartTime":81712.0,"Position":403.3638,"HyperDash":false},{"StartTime":81772.0,"Position":419.118683,"HyperDash":false},{"StartTime":81832.0,"Position":416.873535,"HyperDash":false},{"StartTime":81891.0,"Position":412.482483,"HyperDash":false},{"StartTime":81951.0,"Position":417.237366,"HyperDash":false},{"StartTime":82011.0,"Position":431.992218,"HyperDash":false},{"StartTime":82107.0,"Position":459.0,"HyperDash":false}]},{"StartTime":82278.0,"Objects":[{"StartTime":82278.0,"Position":440.0,"HyperDash":false},{"StartTime":82345.0,"Position":418.133057,"HyperDash":false},{"StartTime":82449.0,"Position":412.264984,"HyperDash":false}]},{"StartTime":82621.0,"Objects":[{"StartTime":82621.0,"Position":320.0,"HyperDash":false},{"StartTime":82688.0,"Position":303.133057,"HyperDash":false},{"StartTime":82792.0,"Position":292.264984,"HyperDash":false}]},{"StartTime":82964.0,"Objects":[{"StartTime":82964.0,"Position":200.0,"HyperDash":false},{"StartTime":83031.0,"Position":187.133057,"HyperDash":false},{"StartTime":83135.0,"Position":172.264984,"HyperDash":false}]},{"StartTime":83307.0,"Objects":[{"StartTime":83307.0,"Position":248.0,"HyperDash":false}]},{"StartTime":83478.0,"Objects":[{"StartTime":83478.0,"Position":344.0,"HyperDash":false}]},{"StartTime":83650.0,"Objects":[{"StartTime":83650.0,"Position":448.0,"HyperDash":false}]},{"StartTime":83993.0,"Objects":[{"StartTime":83993.0,"Position":400.0,"HyperDash":false},{"StartTime":84060.0,"Position":372.409363,"HyperDash":false},{"StartTime":84164.0,"Position":350.0,"HyperDash":false}]},{"StartTime":84335.0,"Objects":[{"StartTime":84335.0,"Position":400.0,"HyperDash":false},{"StartTime":84402.0,"Position":438.590637,"HyperDash":false},{"StartTime":84506.0,"Position":450.0,"HyperDash":false}]},{"StartTime":84678.0,"Objects":[{"StartTime":84678.0,"Position":408.0,"HyperDash":false}]},{"StartTime":84850.0,"Objects":[{"StartTime":84850.0,"Position":304.0,"HyperDash":false}]},{"StartTime":85021.0,"Objects":[{"StartTime":85021.0,"Position":208.0,"HyperDash":false}]},{"StartTime":85364.0,"Objects":[{"StartTime":85364.0,"Position":208.0,"HyperDash":false},{"StartTime":85440.0,"Position":179.777771,"HyperDash":false},{"StartTime":85517.0,"Position":160.263153,"HyperDash":false},{"StartTime":85593.0,"Position":156.040924,"HyperDash":false},{"StartTime":85706.0,"Position":108.0,"HyperDash":false}]},{"StartTime":86050.0,"Objects":[{"StartTime":86050.0,"Position":304.0,"HyperDash":false},{"StartTime":86126.0,"Position":332.222229,"HyperDash":false},{"StartTime":86203.0,"Position":364.736847,"HyperDash":false},{"StartTime":86279.0,"Position":385.959076,"HyperDash":false},{"StartTime":86392.0,"Position":404.0,"HyperDash":false}]},{"StartTime":86735.0,"Objects":[{"StartTime":86735.0,"Position":480.0,"HyperDash":false}]},{"StartTime":86907.0,"Objects":[{"StartTime":86907.0,"Position":376.0,"HyperDash":false}]},{"StartTime":87078.0,"Objects":[{"StartTime":87078.0,"Position":272.0,"HyperDash":false}]},{"StartTime":87250.0,"Objects":[{"StartTime":87250.0,"Position":168.0,"HyperDash":false}]},{"StartTime":87421.0,"Objects":[{"StartTime":87421.0,"Position":64.0,"HyperDash":false},{"StartTime":87506.0,"Position":39.0,"HyperDash":false},{"StartTime":87592.0,"Position":64.0,"HyperDash":false}]},{"StartTime":87764.0,"Objects":[{"StartTime":87764.0,"Position":64.0,"HyperDash":false}]},{"StartTime":88107.0,"Objects":[{"StartTime":88107.0,"Position":208.0,"HyperDash":false},{"StartTime":88183.0,"Position":190.802856,"HyperDash":false},{"StartTime":88260.0,"Position":183.4584,"HyperDash":false},{"StartTime":88336.0,"Position":187.261261,"HyperDash":false},{"StartTime":88449.0,"Position":157.612885,"HyperDash":false}]},{"StartTime":88793.0,"Objects":[{"StartTime":88793.0,"Position":304.0,"HyperDash":false},{"StartTime":88869.0,"Position":300.197144,"HyperDash":false},{"StartTime":88946.0,"Position":313.5416,"HyperDash":false},{"StartTime":89022.0,"Position":334.738739,"HyperDash":false},{"StartTime":89135.0,"Position":354.387115,"HyperDash":false}]},{"StartTime":89478.0,"Objects":[{"StartTime":89478.0,"Position":208.0,"HyperDash":false},{"StartTime":89554.0,"Position":197.802872,"HyperDash":false},{"StartTime":89631.0,"Position":182.4584,"HyperDash":false},{"StartTime":89707.0,"Position":169.261261,"HyperDash":false},{"StartTime":89820.0,"Position":157.612885,"HyperDash":false}]},{"StartTime":90164.0,"Objects":[{"StartTime":90164.0,"Position":304.0,"HyperDash":false},{"StartTime":90249.0,"Position":316.8624,"HyperDash":false},{"StartTime":90335.0,"Position":304.0,"HyperDash":false}]},{"StartTime":90507.0,"Objects":[{"StartTime":90507.0,"Position":208.0,"HyperDash":false}]},{"StartTime":90850.0,"Objects":[{"StartTime":90850.0,"Position":56.0,"HyperDash":false}]},{"StartTime":91021.0,"Objects":[{"StartTime":91021.0,"Position":56.0,"HyperDash":false}]},{"StartTime":91193.0,"Objects":[{"StartTime":91193.0,"Position":144.0,"HyperDash":false}]},{"StartTime":91536.0,"Objects":[{"StartTime":91536.0,"Position":344.0,"HyperDash":false}]},{"StartTime":91707.0,"Objects":[{"StartTime":91707.0,"Position":424.0,"HyperDash":false}]},{"StartTime":91878.0,"Objects":[{"StartTime":91878.0,"Position":424.0,"HyperDash":false}]},{"StartTime":92050.0,"Objects":[{"StartTime":92050.0,"Position":344.0,"HyperDash":false}]},{"StartTime":92221.0,"Objects":[{"StartTime":92221.0,"Position":256.0,"HyperDash":false}]},{"StartTime":92564.0,"Objects":[{"StartTime":92564.0,"Position":160.0,"HyperDash":false},{"StartTime":92649.0,"Position":142.69455,"HyperDash":false},{"StartTime":92735.0,"Position":131.871,"HyperDash":false},{"StartTime":92820.0,"Position":111.443245,"HyperDash":false},{"StartTime":92906.0,"Position":100.496979,"HyperDash":false},{"StartTime":92974.0,"Position":108.492638,"HyperDash":false},{"StartTime":93078.0,"Position":101.630577,"HyperDash":true}]},{"StartTime":93250.0,"Objects":[{"StartTime":93250.0,"Position":352.0,"HyperDash":false},{"StartTime":93335.0,"Position":389.30545,"HyperDash":false},{"StartTime":93421.0,"Position":398.129,"HyperDash":false},{"StartTime":93506.0,"Position":421.556763,"HyperDash":false},{"StartTime":93592.0,"Position":411.503021,"HyperDash":false},{"StartTime":93660.0,"Position":408.507355,"HyperDash":false},{"StartTime":93764.0,"Position":410.369415,"HyperDash":false}]},{"StartTime":93936.0,"Objects":[{"StartTime":93936.0,"Position":256.0,"HyperDash":false}]},{"StartTime":94021.0,"Objects":[{"StartTime":94021.0,"Position":256.0,"HyperDash":false}]},{"StartTime":94107.0,"Objects":[{"StartTime":94107.0,"Position":256.0,"HyperDash":false}]},{"StartTime":94193.0,"Objects":[{"StartTime":94193.0,"Position":256.0,"HyperDash":false}]},{"StartTime":94278.0,"Objects":[{"StartTime":94278.0,"Position":256.0,"HyperDash":false}]},{"StartTime":94364.0,"Objects":[{"StartTime":94364.0,"Position":256.0,"HyperDash":false}]},{"StartTime":94450.0,"Objects":[{"StartTime":94450.0,"Position":256.0,"HyperDash":false}]},{"StartTime":94536.0,"Objects":[{"StartTime":94536.0,"Position":256.0,"HyperDash":false}]},{"StartTime":94621.0,"Objects":[{"StartTime":94621.0,"Position":256.0,"HyperDash":false},{"StartTime":94706.0,"Position":317.7076,"HyperDash":false},{"StartTime":94792.0,"Position":356.0,"HyperDash":false},{"StartTime":94859.0,"Position":307.8187,"HyperDash":false},{"StartTime":94963.0,"Position":256.0,"HyperDash":false}]},{"StartTime":95136.0,"Objects":[{"StartTime":95136.0,"Position":448.0,"HyperDash":false},{"StartTime":95203.0,"Position":427.818726,"HyperDash":false},{"StartTime":95307.0,"Position":348.0,"HyperDash":false}]},{"StartTime":95650.0,"Objects":[{"StartTime":95650.0,"Position":40.0,"HyperDash":false},{"StartTime":95735.0,"Position":86.81512,"HyperDash":false},{"StartTime":95821.0,"Position":118.086884,"HyperDash":false},{"StartTime":95888.0,"Position":133.0,"HyperDash":false},{"StartTime":95992.0,"Position":120.0,"HyperDash":false}]},{"StartTime":96336.0,"Objects":[{"StartTime":96336.0,"Position":480.0,"HyperDash":false},{"StartTime":96403.0,"Position":422.173248,"HyperDash":false},{"StartTime":96507.0,"Position":383.4571,"HyperDash":false}]},{"StartTime":96678.0,"Objects":[{"StartTime":96678.0,"Position":176.0,"HyperDash":false},{"StartTime":96745.0,"Position":193.826752,"HyperDash":false},{"StartTime":96849.0,"Position":272.5429,"HyperDash":false}]},{"StartTime":97021.0,"Objects":[{"StartTime":97021.0,"Position":440.0,"HyperDash":false},{"StartTime":97106.0,"Position":383.010834,"HyperDash":false},{"StartTime":97192.0,"Position":343.4571,"HyperDash":false},{"StartTime":97259.0,"Position":370.283844,"HyperDash":false},{"StartTime":97363.0,"Position":440.0,"HyperDash":false}]},{"StartTime":97707.0,"Objects":[{"StartTime":97707.0,"Position":40.0,"HyperDash":false},{"StartTime":97792.0,"Position":41.5126953,"HyperDash":false},{"StartTime":97878.0,"Position":39.0196533,"HyperDash":false},{"StartTime":97945.0,"Position":73.99493,"HyperDash":false},{"StartTime":98049.0,"Position":115.429176,"HyperDash":false}]},{"StartTime":98393.0,"Objects":[{"StartTime":98393.0,"Position":440.0,"HyperDash":false},{"StartTime":98478.0,"Position":400.2924,"HyperDash":false},{"StartTime":98564.0,"Position":340.0,"HyperDash":false},{"StartTime":98631.0,"Position":332.355255,"HyperDash":false},{"StartTime":98735.0,"Position":276.290741,"HyperDash":false}]},{"StartTime":99078.0,"Objects":[{"StartTime":99078.0,"Position":32.0,"HyperDash":false},{"StartTime":99145.0,"Position":65.70535,"HyperDash":false},{"StartTime":99249.0,"Position":102.710678,"HyperDash":false}]},{"StartTime":99421.0,"Objects":[{"StartTime":99421.0,"Position":296.0,"HyperDash":false},{"StartTime":99488.0,"Position":284.294647,"HyperDash":false},{"StartTime":99592.0,"Position":225.289322,"HyperDash":false}]},{"StartTime":99764.0,"Objects":[{"StartTime":99764.0,"Position":408.0,"HyperDash":false},{"StartTime":99849.0,"Position":457.1486,"HyperDash":false},{"StartTime":99935.0,"Position":478.7107,"HyperDash":false},{"StartTime":100002.0,"Position":469.0053,"HyperDash":false},{"StartTime":100106.0,"Position":408.0,"HyperDash":false}]},{"StartTime":100450.0,"Objects":[{"StartTime":100450.0,"Position":32.0,"HyperDash":false},{"StartTime":100535.0,"Position":80.7076,"HyperDash":false},{"StartTime":100621.0,"Position":132.0,"HyperDash":false},{"StartTime":100688.0,"Position":176.18129,"HyperDash":false},{"StartTime":100792.0,"Position":232.0,"HyperDash":false}]},{"StartTime":101136.0,"Objects":[{"StartTime":101136.0,"Position":480.0,"HyperDash":false},{"StartTime":101221.0,"Position":426.2924,"HyperDash":false},{"StartTime":101307.0,"Position":380.0,"HyperDash":false},{"StartTime":101374.0,"Position":346.818726,"HyperDash":false},{"StartTime":101478.0,"Position":280.0,"HyperDash":false}]},{"StartTime":101821.0,"Objects":[{"StartTime":101821.0,"Position":256.0,"HyperDash":false},{"StartTime":101906.0,"Position":250.0,"HyperDash":false},{"StartTime":101992.0,"Position":256.0,"HyperDash":false},{"StartTime":102059.0,"Position":256.0,"HyperDash":false},{"StartTime":102163.0,"Position":256.0,"HyperDash":false}]},{"StartTime":102336.0,"Objects":[{"StartTime":102336.0,"Position":256.0,"HyperDash":false}]},{"StartTime":102507.0,"Objects":[{"StartTime":102507.0,"Position":256.0,"HyperDash":false},{"StartTime":102592.0,"Position":261.0,"HyperDash":false},{"StartTime":102678.0,"Position":256.0,"HyperDash":false},{"StartTime":102745.0,"Position":274.0,"HyperDash":false},{"StartTime":102849.0,"Position":256.0,"HyperDash":false}]},{"StartTime":103193.0,"Objects":[{"StartTime":103193.0,"Position":432.0,"HyperDash":false}]},{"StartTime":103364.0,"Objects":[{"StartTime":103364.0,"Position":256.0,"HyperDash":false}]},{"StartTime":103536.0,"Objects":[{"StartTime":103536.0,"Position":80.0,"HyperDash":true}]},{"StartTime":103878.0,"Objects":[{"StartTime":103878.0,"Position":480.0,"HyperDash":false}]},{"StartTime":104050.0,"Objects":[{"StartTime":104050.0,"Position":408.0,"HyperDash":false}]},{"StartTime":104221.0,"Objects":[{"StartTime":104221.0,"Position":336.0,"HyperDash":false}]},{"StartTime":104393.0,"Objects":[{"StartTime":104393.0,"Position":264.0,"HyperDash":false}]},{"StartTime":104564.0,"Objects":[{"StartTime":104564.0,"Position":184.0,"HyperDash":false}]},{"StartTime":104736.0,"Objects":[{"StartTime":104736.0,"Position":104.0,"HyperDash":false}]},{"StartTime":104907.0,"Objects":[{"StartTime":104907.0,"Position":32.0,"HyperDash":false}]},{"StartTime":105593.0,"Objects":[{"StartTime":105593.0,"Position":376.0,"HyperDash":false},{"StartTime":105678.0,"Position":422.963257,"HyperDash":false}]},{"StartTime":105764.0,"Objects":[{"StartTime":105764.0,"Position":411.0,"HyperDash":false},{"StartTime":105849.0,"Position":461.0,"HyperDash":false}]},{"StartTime":105936.0,"Objects":[{"StartTime":105936.0,"Position":438.0,"HyperDash":false},{"StartTime":106021.0,"Position":486.1759,"HyperDash":false}]},{"StartTime":106107.0,"Objects":[{"StartTime":106107.0,"Position":447.0,"HyperDash":false},{"StartTime":106192.0,"Position":492.579346,"HyperDash":false}]},{"StartTime":106278.0,"Objects":[{"StartTime":106278.0,"Position":492.0,"HyperDash":false}]},{"StartTime":106621.0,"Objects":[{"StartTime":106621.0,"Position":120.0,"HyperDash":false},{"StartTime":106706.0,"Position":80.04692,"HyperDash":false},{"StartTime":106792.0,"Position":49.0288429,"HyperDash":false},{"StartTime":106859.0,"Position":45.9070129,"HyperDash":false},{"StartTime":106963.0,"Position":40.4979248,"HyperDash":false}]},{"StartTime":107307.0,"Objects":[{"StartTime":107307.0,"Position":400.0,"HyperDash":false},{"StartTime":107392.0,"Position":359.373077,"HyperDash":false}]},{"StartTime":107478.0,"Objects":[{"StartTime":107478.0,"Position":422.0,"HyperDash":false},{"StartTime":107563.0,"Position":374.933044,"HyperDash":false}]},{"StartTime":107650.0,"Objects":[{"StartTime":107650.0,"Position":436.0,"HyperDash":false},{"StartTime":107735.0,"Position":386.191254,"HyperDash":false}]},{"StartTime":107821.0,"Objects":[{"StartTime":107821.0,"Position":430.0,"HyperDash":false},{"StartTime":107906.0,"Position":380.633484,"HyperDash":false}]},{"StartTime":107993.0,"Objects":[{"StartTime":107993.0,"Position":410.0,"HyperDash":false},{"StartTime":108078.0,"Position":364.420654,"HyperDash":false}]},{"StartTime":108164.0,"Objects":[{"StartTime":108164.0,"Position":377.0,"HyperDash":false},{"StartTime":108249.0,"Position":339.099457,"HyperDash":false}]},{"StartTime":108336.0,"Objects":[{"StartTime":108336.0,"Position":343.0,"HyperDash":false}]},{"StartTime":108678.0,"Objects":[{"StartTime":108678.0,"Position":48.0,"HyperDash":false},{"StartTime":108763.0,"Position":72.7952957,"HyperDash":false},{"StartTime":108849.0,"Position":118.469154,"HyperDash":false},{"StartTime":108916.0,"Position":133.274063,"HyperDash":false},{"StartTime":109020.0,"Position":126.387558,"HyperDash":false}]},{"StartTime":109364.0,"Objects":[{"StartTime":109364.0,"Position":464.0,"HyperDash":false},{"StartTime":109449.0,"Position":407.88382,"HyperDash":false},{"StartTime":109535.0,"Position":392.819672,"HyperDash":false},{"StartTime":109602.0,"Position":400.819,"HyperDash":false},{"StartTime":109706.0,"Position":384.6482,"HyperDash":false}]},{"StartTime":110050.0,"Objects":[{"StartTime":110050.0,"Position":32.0,"HyperDash":false},{"StartTime":110135.0,"Position":80.1758957,"HyperDash":false}]},{"StartTime":110221.0,"Objects":[{"StartTime":110221.0,"Position":16.0,"HyperDash":false},{"StartTime":110306.0,"Position":59.1889458,"HyperDash":false}]},{"StartTime":110393.0,"Objects":[{"StartTime":110393.0,"Position":27.0,"HyperDash":false},{"StartTime":110478.0,"Position":62.3553429,"HyperDash":false}]},{"StartTime":110564.0,"Objects":[{"StartTime":110564.0,"Position":42.0,"HyperDash":false},{"StartTime":110649.0,"Position":66.13023,"HyperDash":false}]},{"StartTime":110736.0,"Objects":[{"StartTime":110736.0,"Position":76.0,"HyperDash":false},{"StartTime":110821.0,"Position":88.54811,"HyperDash":false}]},{"StartTime":110907.0,"Objects":[{"StartTime":110907.0,"Position":134.0,"HyperDash":false},{"StartTime":110992.0,"Position":133.107285,"HyperDash":false}]},{"StartTime":111078.0,"Objects":[{"StartTime":111078.0,"Position":134.0,"HyperDash":false}]},{"StartTime":111421.0,"Objects":[{"StartTime":111421.0,"Position":456.0,"HyperDash":false},{"StartTime":111506.0,"Position":404.2924,"HyperDash":false},{"StartTime":111592.0,"Position":356.0,"HyperDash":false},{"StartTime":111659.0,"Position":397.1813,"HyperDash":false},{"StartTime":111763.0,"Position":456.0,"HyperDash":false}]},{"StartTime":112107.0,"Objects":[{"StartTime":112107.0,"Position":56.0,"HyperDash":false},{"StartTime":112192.0,"Position":97.7076,"HyperDash":false},{"StartTime":112278.0,"Position":156.0,"HyperDash":false},{"StartTime":112345.0,"Position":112.81871,"HyperDash":false},{"StartTime":112449.0,"Position":56.0,"HyperDash":false}]},{"StartTime":112793.0,"Objects":[{"StartTime":112793.0,"Position":16.0,"HyperDash":false}]},{"StartTime":112964.0,"Objects":[{"StartTime":112964.0,"Position":96.0,"HyperDash":false}]},{"StartTime":113136.0,"Objects":[{"StartTime":113136.0,"Position":176.0,"HyperDash":false}]},{"StartTime":113307.0,"Objects":[{"StartTime":113307.0,"Position":256.0,"HyperDash":false}]},{"StartTime":113478.0,"Objects":[{"StartTime":113478.0,"Position":336.0,"HyperDash":false}]},{"StartTime":113650.0,"Objects":[{"StartTime":113650.0,"Position":416.0,"HyperDash":false}]},{"StartTime":113821.0,"Objects":[{"StartTime":113821.0,"Position":496.0,"HyperDash":false}]},{"StartTime":114164.0,"Objects":[{"StartTime":114164.0,"Position":312.0,"HyperDash":false}]},{"StartTime":114336.0,"Objects":[{"StartTime":114336.0,"Position":256.0,"HyperDash":false}]},{"StartTime":114507.0,"Objects":[{"StartTime":114507.0,"Position":192.0,"HyperDash":false}]},{"StartTime":114850.0,"Objects":[{"StartTime":114850.0,"Position":256.0,"HyperDash":false}]},{"StartTime":115021.0,"Objects":[{"StartTime":115021.0,"Position":344.0,"HyperDash":false}]},{"StartTime":115193.0,"Objects":[{"StartTime":115193.0,"Position":312.0,"HyperDash":false}]},{"StartTime":115364.0,"Objects":[{"StartTime":115364.0,"Position":208.0,"HyperDash":false}]},{"StartTime":115536.0,"Objects":[{"StartTime":115536.0,"Position":176.0,"HyperDash":false}]},{"StartTime":115707.0,"Objects":[{"StartTime":115707.0,"Position":256.0,"HyperDash":false}]},{"StartTime":115878.0,"Objects":[{"StartTime":115878.0,"Position":256.0,"HyperDash":false}]},{"StartTime":116564.0,"Objects":[{"StartTime":116564.0,"Position":120.0,"HyperDash":false},{"StartTime":116640.0,"Position":111.647354,"HyperDash":false},{"StartTime":116717.0,"Position":96.06639,"HyperDash":false},{"StartTime":116793.0,"Position":53.7137451,"HyperDash":false},{"StartTime":116906.0,"Position":41.9131165,"HyperDash":false}]},{"StartTime":117250.0,"Objects":[{"StartTime":117250.0,"Position":368.0,"HyperDash":false},{"StartTime":117326.0,"Position":376.156769,"HyperDash":false},{"StartTime":117403.0,"Position":397.605072,"HyperDash":false},{"StartTime":117479.0,"Position":440.761841,"HyperDash":false},{"StartTime":117592.0,"Position":467.705444,"HyperDash":false}]},{"StartTime":117936.0,"Objects":[{"StartTime":117936.0,"Position":72.0,"HyperDash":false},{"StartTime":118012.0,"Position":82.97272,"HyperDash":false},{"StartTime":118089.0,"Position":63.8529663,"HyperDash":false},{"StartTime":118165.0,"Position":65.82568,"HyperDash":false},{"StartTime":118278.0,"Position":40.377224,"HyperDash":false}]},{"StartTime":118621.0,"Objects":[{"StartTime":118621.0,"Position":368.0,"HyperDash":false},{"StartTime":118706.0,"Position":353.255585,"HyperDash":false},{"StartTime":118792.0,"Position":328.220032,"HyperDash":false},{"StartTime":118877.0,"Position":302.475647,"HyperDash":false},{"StartTime":118963.0,"Position":268.294556,"HyperDash":false},{"StartTime":119039.0,"Position":291.273438,"HyperDash":false},{"StartTime":119116.0,"Position":309.688965,"HyperDash":false},{"StartTime":119193.0,"Position":348.1045,"HyperDash":false},{"StartTime":119306.0,"Position":368.0,"HyperDash":false}]},{"StartTime":119650.0,"Objects":[{"StartTime":119650.0,"Position":392.0,"HyperDash":false}]},{"StartTime":119821.0,"Objects":[{"StartTime":119821.0,"Position":392.0,"HyperDash":false}]},{"StartTime":119993.0,"Objects":[{"StartTime":119993.0,"Position":448.0,"HyperDash":false}]},{"StartTime":120164.0,"Objects":[{"StartTime":120164.0,"Position":448.0,"HyperDash":false}]},{"StartTime":120336.0,"Objects":[{"StartTime":120336.0,"Position":480.0,"HyperDash":false}]},{"StartTime":120507.0,"Objects":[{"StartTime":120507.0,"Position":480.0,"HyperDash":false}]},{"StartTime":120678.0,"Objects":[{"StartTime":120678.0,"Position":480.0,"HyperDash":false}]},{"StartTime":121021.0,"Objects":[{"StartTime":121021.0,"Position":448.0,"HyperDash":false}]},{"StartTime":121107.0,"Objects":[{"StartTime":121107.0,"Position":440.0,"HyperDash":false}]},{"StartTime":121193.0,"Objects":[{"StartTime":121193.0,"Position":432.0,"HyperDash":false}]},{"StartTime":121278.0,"Objects":[{"StartTime":121278.0,"Position":424.0,"HyperDash":false}]},{"StartTime":121364.0,"Objects":[{"StartTime":121364.0,"Position":416.0,"HyperDash":false}]},{"StartTime":121621.0,"Objects":[{"StartTime":121621.0,"Position":312.0,"HyperDash":false}]},{"StartTime":121707.0,"Objects":[{"StartTime":121707.0,"Position":312.0,"HyperDash":false}]},{"StartTime":121878.0,"Objects":[{"StartTime":121878.0,"Position":232.0,"HyperDash":false}]},{"StartTime":122050.0,"Objects":[{"StartTime":122050.0,"Position":168.0,"HyperDash":false}]},{"StartTime":122393.0,"Objects":[{"StartTime":122393.0,"Position":352.0,"HyperDash":false}]},{"StartTime":122564.0,"Objects":[{"StartTime":122564.0,"Position":376.0,"HyperDash":false}]},{"StartTime":122736.0,"Objects":[{"StartTime":122736.0,"Position":352.0,"HyperDash":false}]},{"StartTime":123078.0,"Objects":[{"StartTime":123078.0,"Position":168.0,"HyperDash":false}]},{"StartTime":123250.0,"Objects":[{"StartTime":123250.0,"Position":144.0,"HyperDash":false}]},{"StartTime":123421.0,"Objects":[{"StartTime":123421.0,"Position":168.0,"HyperDash":false}]},{"StartTime":123936.0,"Objects":[{"StartTime":123936.0,"Position":400.0,"HyperDash":false}]},{"StartTime":124107.0,"Objects":[{"StartTime":124107.0,"Position":467.0,"HyperDash":false}]},{"StartTime":124278.0,"Objects":[{"StartTime":124278.0,"Position":400.0,"HyperDash":false}]},{"StartTime":124450.0,"Objects":[{"StartTime":124450.0,"Position":326.0,"HyperDash":false}]},{"StartTime":124536.0,"Objects":[{"StartTime":124536.0,"Position":320.0,"HyperDash":false}]},{"StartTime":124621.0,"Objects":[{"StartTime":124621.0,"Position":315.0,"HyperDash":false}]},{"StartTime":124707.0,"Objects":[{"StartTime":124707.0,"Position":309.0,"HyperDash":false}]},{"StartTime":124793.0,"Objects":[{"StartTime":124793.0,"Position":303.0,"HyperDash":false}]},{"StartTime":125136.0,"Objects":[{"StartTime":125136.0,"Position":112.0,"HyperDash":false}]},{"StartTime":125307.0,"Objects":[{"StartTime":125307.0,"Position":112.0,"HyperDash":false}]},{"StartTime":125478.0,"Objects":[{"StartTime":125478.0,"Position":44.0,"HyperDash":false}]},{"StartTime":125650.0,"Objects":[{"StartTime":125650.0,"Position":44.0,"HyperDash":false}]},{"StartTime":125821.0,"Objects":[{"StartTime":125821.0,"Position":112.0,"HyperDash":false}]},{"StartTime":125993.0,"Objects":[{"StartTime":125993.0,"Position":112.0,"HyperDash":false}]},{"StartTime":126164.0,"Objects":[{"StartTime":126164.0,"Position":184.0,"HyperDash":false}]},{"StartTime":126250.0,"Objects":[{"StartTime":126250.0,"Position":189.0,"HyperDash":false}]},{"StartTime":126336.0,"Objects":[{"StartTime":126336.0,"Position":195.0,"HyperDash":false}]},{"StartTime":126421.0,"Objects":[{"StartTime":126421.0,"Position":200.0,"HyperDash":false}]},{"StartTime":126507.0,"Objects":[{"StartTime":126507.0,"Position":206.0,"HyperDash":false}]},{"StartTime":126593.0,"Objects":[{"StartTime":126593.0,"Position":212.0,"HyperDash":false}]},{"StartTime":126678.0,"Objects":[{"StartTime":126678.0,"Position":217.0,"HyperDash":false}]},{"StartTime":126764.0,"Objects":[{"StartTime":126764.0,"Position":223.0,"HyperDash":false}]},{"StartTime":126850.0,"Objects":[{"StartTime":126850.0,"Position":229.0,"HyperDash":false}]},{"StartTime":127536.0,"Objects":[{"StartTime":127536.0,"Position":72.0,"HyperDash":false}]},{"StartTime":127707.0,"Objects":[{"StartTime":127707.0,"Position":72.0,"HyperDash":false}]},{"StartTime":127878.0,"Objects":[{"StartTime":127878.0,"Position":112.0,"HyperDash":false}]},{"StartTime":128050.0,"Objects":[{"StartTime":128050.0,"Position":112.0,"HyperDash":false}]},{"StartTime":128221.0,"Objects":[{"StartTime":128221.0,"Position":152.0,"HyperDash":false}]},{"StartTime":128393.0,"Objects":[{"StartTime":128393.0,"Position":152.0,"HyperDash":false}]},{"StartTime":128564.0,"Objects":[{"StartTime":128564.0,"Position":192.0,"HyperDash":false}]},{"StartTime":128736.0,"Objects":[{"StartTime":128736.0,"Position":192.0,"HyperDash":false}]},{"StartTime":128907.0,"Objects":[{"StartTime":128907.0,"Position":280.0,"HyperDash":false}]},{"StartTime":129250.0,"Objects":[{"StartTime":129250.0,"Position":296.0,"HyperDash":false}]},{"StartTime":129421.0,"Objects":[{"StartTime":129421.0,"Position":395.0,"HyperDash":false}]},{"StartTime":129593.0,"Objects":[{"StartTime":129593.0,"Position":395.0,"HyperDash":false}]},{"StartTime":129764.0,"Objects":[{"StartTime":129764.0,"Position":295.0,"HyperDash":false}]},{"StartTime":129936.0,"Objects":[{"StartTime":129936.0,"Position":295.0,"HyperDash":false}]},{"StartTime":130107.0,"Objects":[{"StartTime":130107.0,"Position":391.0,"HyperDash":false}]},{"StartTime":130278.0,"Objects":[{"StartTime":130278.0,"Position":391.0,"HyperDash":false}]},{"StartTime":130621.0,"Objects":[{"StartTime":130621.0,"Position":256.0,"HyperDash":false}]},{"StartTime":130793.0,"Objects":[{"StartTime":130793.0,"Position":168.0,"HyperDash":false}]},{"StartTime":130964.0,"Objects":[{"StartTime":130964.0,"Position":256.0,"HyperDash":false}]},{"StartTime":131136.0,"Objects":[{"StartTime":131136.0,"Position":344.0,"HyperDash":false}]},{"StartTime":131307.0,"Objects":[{"StartTime":131307.0,"Position":344.0,"HyperDash":false}]},{"StartTime":131478.0,"Objects":[{"StartTime":131478.0,"Position":344.0,"HyperDash":false}]},{"StartTime":131650.0,"Objects":[{"StartTime":131650.0,"Position":344.0,"HyperDash":false}]},{"StartTime":131993.0,"Objects":[{"StartTime":131993.0,"Position":168.0,"HyperDash":false}]},{"StartTime":132164.0,"Objects":[{"StartTime":132164.0,"Position":168.0,"HyperDash":false}]},{"StartTime":132336.0,"Objects":[{"StartTime":132336.0,"Position":168.0,"HyperDash":false}]},{"StartTime":132593.0,"Objects":[{"StartTime":132593.0,"Position":272.0,"HyperDash":false}]},{"StartTime":132678.0,"Objects":[{"StartTime":132678.0,"Position":272.0,"HyperDash":false}]},{"StartTime":132850.0,"Objects":[{"StartTime":132850.0,"Position":168.0,"HyperDash":false}]},{"StartTime":133021.0,"Objects":[{"StartTime":133021.0,"Position":168.0,"HyperDash":false}]},{"StartTime":133364.0,"Objects":[{"StartTime":133364.0,"Position":40.0,"HyperDash":false},{"StartTime":133440.0,"Position":47.0,"HyperDash":false},{"StartTime":133517.0,"Position":46.0,"HyperDash":false},{"StartTime":133593.0,"Position":45.0,"HyperDash":false},{"StartTime":133706.0,"Position":40.0,"HyperDash":false}]},{"StartTime":134050.0,"Objects":[{"StartTime":134050.0,"Position":208.0,"HyperDash":false},{"StartTime":134126.0,"Position":192.0,"HyperDash":false},{"StartTime":134203.0,"Position":205.0,"HyperDash":false},{"StartTime":134279.0,"Position":215.0,"HyperDash":false},{"StartTime":134392.0,"Position":208.0,"HyperDash":false}]},{"StartTime":134736.0,"Objects":[{"StartTime":134736.0,"Position":208.0,"HyperDash":false}]},{"StartTime":134907.0,"Objects":[{"StartTime":134907.0,"Position":208.0,"HyperDash":false}]},{"StartTime":135078.0,"Objects":[{"StartTime":135078.0,"Position":304.0,"HyperDash":false}]},{"StartTime":135250.0,"Objects":[{"StartTime":135250.0,"Position":304.0,"HyperDash":false}]},{"StartTime":135421.0,"Objects":[{"StartTime":135421.0,"Position":400.0,"HyperDash":false}]},{"StartTime":135593.0,"Objects":[{"StartTime":135593.0,"Position":400.0,"HyperDash":false}]},{"StartTime":135764.0,"Objects":[{"StartTime":135764.0,"Position":496.0,"HyperDash":false}]},{"StartTime":136107.0,"Objects":[{"StartTime":136107.0,"Position":296.0,"HyperDash":false}]},{"StartTime":136278.0,"Objects":[{"StartTime":136278.0,"Position":216.0,"HyperDash":false}]},{"StartTime":136450.0,"Objects":[{"StartTime":136450.0,"Position":296.0,"HyperDash":false}]},{"StartTime":136621.0,"Objects":[{"StartTime":136621.0,"Position":216.0,"HyperDash":false}]},{"StartTime":136793.0,"Objects":[{"StartTime":136793.0,"Position":296.0,"HyperDash":false}]},{"StartTime":136964.0,"Objects":[{"StartTime":136964.0,"Position":292.0,"HyperDash":false}]},{"StartTime":137050.0,"Objects":[{"StartTime":137050.0,"Position":300.0,"HyperDash":false}]},{"StartTime":137136.0,"Objects":[{"StartTime":137136.0,"Position":308.0,"HyperDash":false}]},{"StartTime":137307.0,"Objects":[{"StartTime":137307.0,"Position":220.0,"HyperDash":false}]},{"StartTime":137393.0,"Objects":[{"StartTime":137393.0,"Position":212.0,"HyperDash":false}]},{"StartTime":137478.0,"Objects":[{"StartTime":137478.0,"Position":204.0,"HyperDash":false}]},{"StartTime":137650.0,"Objects":[{"StartTime":137650.0,"Position":260.0,"HyperDash":false}]},{"StartTime":137736.0,"Objects":[{"StartTime":137736.0,"Position":260.0,"HyperDash":false}]},{"StartTime":137821.0,"Objects":[{"StartTime":137821.0,"Position":260.0,"HyperDash":false}]},{"StartTime":137993.0,"Objects":[{"StartTime":137993.0,"Position":441.0,"HyperDash":false},{"StartTime":138057.0,"Position":442.0,"HyperDash":false},{"StartTime":138121.0,"Position":278.0,"HyperDash":false},{"StartTime":138185.0,"Position":90.0,"HyperDash":false},{"StartTime":138250.0,"Position":409.0,"HyperDash":false},{"StartTime":138314.0,"Position":377.0,"HyperDash":false},{"StartTime":138378.0,"Position":457.0,"HyperDash":false},{"StartTime":138442.0,"Position":409.0,"HyperDash":false},{"StartTime":138507.0,"Position":43.0,"HyperDash":false},{"StartTime":138571.0,"Position":162.0,"HyperDash":false},{"StartTime":138635.0,"Position":341.0,"HyperDash":false},{"StartTime":138699.0,"Position":72.0,"HyperDash":false},{"StartTime":138764.0,"Position":135.0,"HyperDash":false},{"StartTime":138828.0,"Position":252.0,"HyperDash":false},{"StartTime":138892.0,"Position":446.0,"HyperDash":false},{"StartTime":138956.0,"Position":284.0,"HyperDash":false},{"StartTime":139021.0,"Position":70.0,"HyperDash":false}]},{"StartTime":139193.0,"Objects":[{"StartTime":139193.0,"Position":256.0,"HyperDash":false}]},{"StartTime":139536.0,"Objects":[{"StartTime":139536.0,"Position":256.0,"HyperDash":false},{"StartTime":139612.0,"Position":285.1111,"HyperDash":false},{"StartTime":139689.0,"Position":274.3684,"HyperDash":false},{"StartTime":139765.0,"Position":287.479523,"HyperDash":false},{"StartTime":139878.0,"Position":306.0,"HyperDash":false}]},{"StartTime":140221.0,"Objects":[{"StartTime":140221.0,"Position":256.0,"HyperDash":false},{"StartTime":140297.0,"Position":261.8889,"HyperDash":false},{"StartTime":140374.0,"Position":249.631577,"HyperDash":false},{"StartTime":140450.0,"Position":226.520462,"HyperDash":false},{"StartTime":140563.0,"Position":206.0,"HyperDash":false}]},{"StartTime":140907.0,"Objects":[{"StartTime":140907.0,"Position":256.0,"HyperDash":false},{"StartTime":140983.0,"Position":284.1111,"HyperDash":false},{"StartTime":141060.0,"Position":295.3684,"HyperDash":false},{"StartTime":141136.0,"Position":290.479523,"HyperDash":false},{"StartTime":141249.0,"Position":306.0,"HyperDash":false}]},{"StartTime":141593.0,"Objects":[{"StartTime":141593.0,"Position":256.0,"HyperDash":false},{"StartTime":141669.0,"Position":257.8889,"HyperDash":false},{"StartTime":141746.0,"Position":242.631577,"HyperDash":false},{"StartTime":141822.0,"Position":221.520462,"HyperDash":false},{"StartTime":141935.0,"Position":206.0,"HyperDash":false}]},{"StartTime":142278.0,"Objects":[{"StartTime":142278.0,"Position":425.0,"HyperDash":false},{"StartTime":142363.0,"Position":281.0,"HyperDash":false},{"StartTime":142449.0,"Position":3.0,"HyperDash":false},{"StartTime":142535.0,"Position":346.0,"HyperDash":false},{"StartTime":142620.0,"Position":350.0,"HyperDash":false},{"StartTime":142706.0,"Position":217.0,"HyperDash":false},{"StartTime":142792.0,"Position":455.0,"HyperDash":false},{"StartTime":142878.0,"Position":229.0,"HyperDash":false},{"StartTime":142963.0,"Position":51.0,"HyperDash":false},{"StartTime":143049.0,"Position":199.0,"HyperDash":false},{"StartTime":143135.0,"Position":208.0,"HyperDash":false},{"StartTime":143220.0,"Position":173.0,"HyperDash":false},{"StartTime":143306.0,"Position":367.0,"HyperDash":false},{"StartTime":143392.0,"Position":193.0,"HyperDash":false},{"StartTime":143478.0,"Position":488.0,"HyperDash":false},{"StartTime":143563.0,"Position":314.0,"HyperDash":false},{"StartTime":143649.0,"Position":135.0,"HyperDash":false},{"StartTime":143735.0,"Position":399.0,"HyperDash":false},{"StartTime":143820.0,"Position":404.0,"HyperDash":false},{"StartTime":143906.0,"Position":152.0,"HyperDash":false},{"StartTime":143992.0,"Position":353.0,"HyperDash":false},{"StartTime":144078.0,"Position":358.0,"HyperDash":false},{"StartTime":144163.0,"Position":447.0,"HyperDash":false},{"StartTime":144249.0,"Position":222.0,"HyperDash":false},{"StartTime":144335.0,"Position":382.0,"HyperDash":false},{"StartTime":144420.0,"Position":433.0,"HyperDash":false},{"StartTime":144506.0,"Position":450.0,"HyperDash":false},{"StartTime":144592.0,"Position":326.0,"HyperDash":false},{"StartTime":144678.0,"Position":414.0,"HyperDash":false},{"StartTime":144763.0,"Position":285.0,"HyperDash":false},{"StartTime":144849.0,"Position":336.0,"HyperDash":false},{"StartTime":144935.0,"Position":509.0,"HyperDash":false},{"StartTime":145021.0,"Position":334.0,"HyperDash":false}]}]} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/39206.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/39206.osu new file mode 100644 index 0000000000..3aeb80e9d5 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/39206.osu @@ -0,0 +1,524 @@ +osu file format v7 + +[General] +StackLeniency: 0.7 +Mode: 0 + +[Difficulty] +HPDrainRate:5 +CircleSize:4 +OverallDifficulty:8 +SliderMultiplier:1 +SliderTickRate:1 + +[Events] +//Break Periods +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Sound Samples +//Background Colour Transformations +3,100,163,162,255 + +[TimingPoints] +336,342.857142857143,4,1,0,100,1,0 +1020,-100,4,2,0,100,0,0 +21250,-100,4,1,0,100,0,0 +23131,-100,4,1,0,100,0,0 +26731,-100,4,1,0,100,0,0 +27931,-50,4,1,0,100,0,0 +28616,-100,4,1,0,100,0,0 +32388,-50,4,1,0,100,0,0 +34102,-100,4,1,0,100,0,0 +37874,-50,4,1,0,100,0,0 +39588,-100,4,1,0,100,0,0 +51759,-100,4,2,0,100,0,0 +52445,-100,4,1,0,100,0,0 +62730,-100,4,2,0,100,0,0 +63416,-100,4,2,0,100,0,0 +66159,-100,4,2,0,100,0,0 +81588,-200,4,2,0,100,0,0 +82278,-100,4,2,0,100,0,0 +85359,-100,4,2,0,100,0,0 +92564,-100,4,1,0,100,0,0 +94616,-50,4,1,0,100,0,0 +116559,-100,4,1,0,100,0,0 +139188,-200,4,2,0,100,0,0 + +[HitObjects] +256,192,678,1,0 +456,216,1021,5,2 +456,264,1193,1,2 +456,312,1364,1,2 +312,168,1707,1,2 +312,120,1878,1,2 +312,72,2050,1,2 +168,216,2393,1,2 +168,264,2564,1,2 +168,312,2736,1,2 +24,168,3078,1,2 +24,120,3250,1,2 +24,72,3421,1,2 +56,272,3764,5,2 +136,336,3936,1,2 +216,272,4107,1,2 +296,88,4450,1,2 +376,24,4621,1,2 +456,88,4793,1,2 +456,288,5135,1,2 +376,352,5307,1,2 +296,288,5478,1,2 +216,104,5821,1,2 +136,40,5993,1,2 +56,104,6164,1,2 +24,304,6507,6,2,B|24:200,1,100 +144,40,7193,2,2,B|144:144,1,100 +256,304,7878,2,2,B|256:200,1,100 +376,40,8564,2,2,B|376:144,1,100 +488,304,9250,2,2,B|488:200,1,100 +256,208,9935,12,0,11050 +40,104,11307,5,2 +56,88,11393,1,2 +80,72,11478,1,2 +104,64,11564,1,2 +128,56,11650,2,2,B|176:40|232:56,1,100 +288,248,12336,2,2,B|240:264|184:248,1,100 +344,120,13021,2,2,B|392:104|448:120,1,100 +504,312,13707,2,2,B|456:328|400:312,1,100 +328,264,14221,5,2 +312,264,14307,1,2 +296,264,14393,2,2,B|256:360,1,100 +160,184,15078,2,2,B|200:280,1,100,2|2 +296,104,15764,2,2,B|256:200,1,100 +160,24,16450,2,2,B|200:120,1,100 +112,160,16964,5,2 +96,176,17050,1,2 +88,200,17136,2,2,B|128:280|200:296,2,150 +424,184,18507,2,2,B|384:104|312:88,2,150 +368,256,19707,1,2 +352,256,19793,1,2 +336,256,19878,2,2,B|232:256,1,100 +136,80,20564,2,2,B|240:80,1,100 +392,208,21250,6,0,B|440:280|392:360,1,150 +120,176,21936,2,0,B|72:104|120:24,1,150 +176,112,22621,6,0,B|269:103|307:15,1,150 +297,35,23307,1,0 +448,296,23821,1,0 +352,328,23993,2,0,B|304:352|248:352,1,100 +160,352,24507,1,0 +160,352,24593,1,0 +160,352,24678,1,0 +88,168,25021,5,0 +176,112,25193,1,0 +256,56,25364,1,0 +424,160,25707,1,0 +448,256,25878,1,0 +472,352,26050,2,0,B|414:287|325:312,1,150,0|0 +304,216,26736,2,0,B|248:232|240:296,1,100 +256,208,27250,12,0,28107 +32,248,28621,5,0 +80,160,28793,1,0 +352,32,29307,1,0 +424,104,29478,1,0 +472,192,29650,1,0 +432,280,29821,1,0 +360,352,29993,1,0 +360,352,30078,1,0 +360,352,30164,1,0 +184,256,30507,6,0,B|208:208,2,50 +64,56,31193,2,0,B|93:100,2,50,0|0|0 +352,40,31878,2,0,B|298:43,2,50,0|0|0 +320,136,32393,6,0,B|290:122,5,25 +342,181,32736,2,0,B|312:167,5,25,0|0|0|0|0|0 +399,173,33078,2,0,B|369:159,5,25 +422,219,33421,2,0,B|392:205,6,25 +368,104,34107,5,0 +280,48,34278,1,0 +280,344,34793,1,0 +184,320,34964,1,0 +112,248,35136,1,0 +64,160,35307,1,0 +32,64,35478,1,0 +32,64,35564,1,0 +32,64,35650,1,0 +232,32,35993,5,0 +328,56,36164,1,0 +408,120,36336,1,0 +464,200,36507,1,0 +408,120,36678,1,0 +328,56,36850,1,0 +232,32,37021,1,0 +72,288,37535,5,0 +112,192,37707,1,0 +144,96,37878,6,0,B|112:96,4,25 +232,144,38221,2,0,B|200:144,4,25,0|0|0|0|0 +320,96,38564,2,0,B|288:96,4,25 +408,144,38907,2,0,B|376:144,4,25 +304,248,39593,5,0 +208,280,39764,1,0 +40,48,40278,1,0 +112,120,40450,1,0 +200,72,40621,1,0 +264,152,40793,1,0 +352,104,40964,1,0 +352,104,41050,1,0 +352,104,41135,1,0 +480,256,41478,5,0 +422,179,41650,1,0 +364,102,41821,1,0 +422,179,41993,1,0 +327,199,42164,1,0 +226,220,42335,1,0 +327,199,42507,1,0 +381,118,42678,1,0 +437,32,42850,1,0 +381,118,43021,1,0 +327,199,43193,1,0 +256,208,43278,12,0,44050 +328,184,44221,6,0,B|408:248,1,100 +184,200,44907,2,0,B|104:136,1,100 +192,88,45421,5,0 +192,88,45507,1,0 +192,88,45593,1,0 +106,135,45764,1,0 +106,135,45850,1,0 +106,135,45935,1,0 +154,219,46107,1,0 +237,170,46278,1,0 +237,170,46364,1,0 +237,170,46450,1,0 +237,170,46535,1,0 +237,170,46621,1,0 +410,70,46964,5,0 +410,70,47135,1,0 +462,160,47307,1,0 +462,160,47478,1,0 +379,209,47650,1,0 +379,209,47821,1,0 +328,119,47993,1,0 +328,119,48164,1,0 +237,170,48335,1,0 +328,119,48507,1,0 +410,71,48678,1,0 +264,88,48935,5,0 +264,88,49021,1,0 +304,184,49193,1,0 +368,256,49364,1,0 +368,256,49707,6,0,B|472:256,1,100,0|0 +280,184,50393,2,0,B|392:184,1,100 +88,248,51250,2,0,B|200:248,1,100 +264,312,51764,1,4 +280,312,51850,1,4 +296,312,51935,1,4 +312,312,52021,1,4 +328,312,52107,1,4 +208,152,52450,5,0 +304,152,52621,1,0 +256,64,52793,1,0 +208,256,53135,1,0 +304,256,53307,1,0 +208,216,53478,1,0 +304,216,53650,1,0 +208,176,53821,1,0 +304,176,53993,1,0 +256,208,54164,12,0,55021 +256,320,55193,6,0,B|184:248,1,100 +256,64,55878,2,0,B|328:136,1,100 +256,192,56393,5,4 +160,192,56564,1,4 +160,192,56650,1,4 +160,192,56735,1,4 +160,88,56907,1,4 +256,88,57078,1,4 +352,88,57250,1,0 +360,88,57335,1,0 +368,88,57421,1,0 +376,88,57507,1,0 +384,88,57593,1,0 +472,264,57935,5,0 +387,318,58107,1,0 +284,325,58278,1,0 +193,291,58450,1,0 +139,207,58621,1,0 +132,103,58793,1,0 +174,12,58964,1,0 +256,200,59307,5,0 +208,288,59478,1,0 +304,288,59650,1,0 +344,200,59821,1,0 +312,160,59907,1,0 +280,120,59993,1,0 +208,56,60164,1,0 +304,56,60335,1,0 +200,224,60678,6,0,B|120:288,1,100 +312,224,61364,2,0,B|392:288,1,100 +390,286,62050,1,0 +121,286,62393,1,0 +256,224,62735,1,4 +256,232,62821,1,4 +256,240,62907,1,4 +256,248,62993,1,4 +256,256,63078,1,4 +432,352,63421,5,2 +496,272,63593,1,2 +496,168,63764,1,2 +440,88,63935,1,2 +352,32,64107,1,2 +256,8,64278,1,2 +160,32,64450,1,2 +72,88,64621,1,2 +8,168,64793,1,2 +8,264,64964,1,2 +56,352,65135,1,2 +256,208,65221,12,4,65993 +296,232,66164,6,2,B|352:320,1,100 +216,160,66850,2,2,B|160:248,1,100 +296,88,67535,2,2,B|352:176,1,100 +256,208,67964,12,4,68735 +304,136,68907,6,2,B|408:136,1,100 +208,192,69593,2,2,B|104:192,1,100 +304,248,70278,2,2,B|408:248,1,100 +56,48,71307,6,0,B|24:88|56:144,2,100 +256,48,72335,1,2 +328,120,72507,1,2 +400,48,72678,1,2 +400,48,73021,2,4,B|440:88|408:144,1,100,4|0 +112,336,73707,2,4,B|72:296|104:240,1,100,4|0 +304,264,74393,6,2,B|360:360,1,100 +304,120,75078,2,2,B|360:24,1,100 +464,200,75764,1,2 +384,264,75935,1,2 +304,200,76107,1,2 +232,264,76278,1,2 +160,200,76450,2,4,B|120:200,2,25 +80,264,76793,1,4 +120,72,77135,6,2,B|29:124,1,100,2|2 +232,96,77821,2,2,B|322:43,1,100 +176,184,78507,2,2,B|85:236,1,100 +288,208,79193,2,2,B|378:156,1,100,2|2 +240,304,79878,2,2,B|149:356,1,100,2|2 +32,192,80564,5,2 +66,95,80735,1,2 +161,131,80907,1,2 +190,38,81078,1,2 +285,73,81250,1,2 +384,72,81593,2,12,B|464:72,1,75,4|4 +440,176,82278,6,0,B|408:224,1,50 +320,176,82621,2,0,B|288:128,1,50 +200,176,82964,2,0,B|168:224,1,50 +248,280,83307,1,2 +344,280,83478,1,2 +448,280,83650,1,2 +400,80,83993,2,0,L|344:80,1,50 +400,80,84335,2,0,L|456:80,1,50 +408,168,84678,1,2 +304,168,84850,1,2 +208,168,85021,1,2 +208,168,85364,6,0,B|104:168,1,100,0|4 +304,216,86050,2,0,B|408:216,1,100,0|4 +480,32,86735,1,2 +376,32,86907,1,2 +272,32,87078,1,2 +168,32,87250,1,2 +64,32,87421,2,2,B|16:32,2,25 +64,32,87764,1,2 +208,168,88107,6,0,B|152:72,1,100,0|2 +304,224,88793,2,0,B|360:128,1,100,0|4 +208,272,89478,2,0,B|152:176,1,100,0|4 +304,328,90164,2,2,B|328:288,2,25 +208,368,90507,1,2 +56,232,90850,5,2 +56,128,91021,1,2 +144,80,91193,1,2 +344,80,91536,5,2 +424,136,91707,1,2 +424,232,91878,1,2 +344,288,92050,1,2 +256,248,92221,1,2 +160,56,92564,6,0,B|80:104|104:192,1,150 +352,328,93250,2,0,B|432:280|408:192,1,150 +256,192,93936,1,0 +256,200,94021,1,0 +256,208,94107,1,0 +256,216,94193,1,0 +256,224,94278,1,0 +256,232,94364,1,0 +256,240,94450,1,0 +256,248,94536,1,4 +256,256,94621,6,0,B|360:256,2,100 +448,328,95136,2,0,B|344:328,1,100 +40,72,95650,2,0,L|120:136|120:240,1,200 +480,64,96336,2,0,B|380:37,1,100 +176,48,96678,2,0,B|276:75,1,100 +440,184,97021,2,0,B|340:157,2,100 +40,176,97707,6,0,L|39:278|120:343,1,200,0|0 +440,112,98393,2,0,L|337:112|272:31,1,200,0|0 +32,344,99078,2,0,B|111:265,1,100,0|0 +296,200,99421,2,0,B|217:279,1,100,0|0 +408,184,99764,2,0,B|487:105,2,100 +32,32,100450,6,0,L|232:32,1,200 +480,352,101136,2,0,L|280:352,1,200 +256,192,101821,2,0,B|256:296,2,100,0|0|0 +256,192,102336,1,0 +256,192,102507,2,0,B|256:88,2,100,0|0|0 +432,344,103193,5,0 +256,248,103364,1,0 +80,344,103536,1,0 +480,256,103878,5,0 +408,72,104050,1,0 +336,256,104221,1,0 +264,72,104393,1,0 +184,256,104564,1,0 +104,72,104736,1,0 +32,256,104907,1,8 +376,48,105593,6,0,B|428:29,1,50,0|0 +411,78,105764,2,0,B|467:78,1,50,0|0 +438,127,105936,2,0,B|492:142,1,50,0|0 +447,176,106107,2,0,B|498:199,1,50,0|0 +492,196,106278,1,8 +120,344,106621,2,0,B|13:289|43:167,1,200 +400,352,107307,6,0,B|354:319,1,50,0|0 +422,286,107478,2,0,B|369:267,1,50,0|0 +436,219,107650,2,0,B|379:214,1,50,0|0 +430,152,107821,2,0,B|374:161,1,50,0|0 +410,89,107993,2,0,B|359:112,1,50,0|0 +377,34,108164,2,0,B|334:71,1,50,0|0 +343,68,108336,1,8 +48,344,108678,6,0,B|154:289|124:167,1,200 +464,40,109364,2,0,B|357:94|387:216,1,200 +32,32,110050,6,0,B|86:17,1,50 +16,94,110221,2,0,B|64:66,1,50 +27,165,110393,2,0,B|67:125,1,50 +42,226,110564,2,0,B|69:177,1,50 +76,282,110736,2,0,B|90:228,1,50 +134,324,110907,2,0,B|133:268,1,50 +134,274,111078,1,8 +456,40,111421,6,0,B|352:40,2,100,0|0|8 +56,40,112107,2,0,B|160:40,2,100,0|0|8 +16,192,112793,5,0 +96,192,112964,1,0 +176,192,113136,1,0 +256,192,113307,1,0 +336,192,113478,1,0 +416,192,113650,1,0 +496,192,113821,1,8 +312,112,114164,5,0 +256,192,114336,1,0 +192,112,114507,1,0 +256,304,114850,5,0 +344,256,115021,1,0 +312,160,115193,1,0 +208,160,115364,1,0 +176,256,115536,1,0 +256,304,115707,1,0 +256,304,115878,1,2 +120,160,116564,6,0,B|40:96,1,100,0|0 +368,336,117250,2,0,B|472:328,1,100,0|0 +72,248,117936,2,0,B|40:344,1,100 +368,112,118621,2,0,B|264:104,2,100 +392,312,119650,5,0 +392,312,119821,1,0 +448,264,119993,1,0 +448,264,120164,1,0 +480,200,120336,1,0 +480,200,120507,1,0 +480,200,120678,1,0 +448,48,121021,5,0 +440,48,121107,1,0 +432,48,121193,1,0 +424,48,121278,1,0 +416,48,121364,1,0 +312,96,121621,1,0 +312,96,121707,1,0 +232,104,121878,1,0 +168,144,122050,1,0 +352,232,122393,5,0 +376,192,122564,1,0 +352,144,122736,1,0 +168,144,123078,1,0 +144,184,123250,1,0 +168,232,123421,1,0 +400,48,123936,5,0 +467,115,124107,1,0 +400,183,124278,1,0 +326,110,124450,1,0 +320,104,124536,1,0 +315,98,124621,1,0 +309,93,124707,1,0 +303,87,124793,1,0 +112,336,125136,5,0 +112,336,125307,1,0 +44,268,125478,1,0 +44,268,125650,1,0 +112,200,125821,1,0 +112,200,125993,1,0 +184,264,126164,1,0 +189,258,126250,1,0 +195,252,126336,1,0 +200,247,126421,1,0 +206,241,126507,1,0 +212,235,126593,1,0 +217,230,126678,1,0 +223,224,126764,1,0 +229,218,126850,1,0 +72,96,127536,5,0 +72,192,127707,1,0 +112,96,127878,1,0 +112,192,128050,1,0 +152,96,128221,1,0 +152,192,128393,1,0 +192,96,128564,1,0 +192,192,128736,1,0 +280,144,128907,1,0 +296,344,129250,5,0 +395,344,129421,1,0 +395,243,129593,1,0 +295,241,129764,1,0 +295,137,129936,1,0 +391,137,130107,1,0 +391,33,130278,1,0 +256,104,130621,5,0 +168,192,130793,1,0 +256,280,130964,1,0 +344,192,131136,1,0 +344,192,131307,1,0 +344,288,131478,1,0 +344,96,131650,1,0 +168,184,131993,5,0 +168,184,132164,1,0 +168,184,132336,1,0 +272,80,132593,1,0 +272,80,132678,1,0 +168,80,132850,1,0 +168,80,133021,1,0 +40,240,133364,6,0,B|40:344,1,100 +208,224,134050,2,0,B|208:120,1,100 +208,328,134736,1,0 +208,224,134907,1,0 +304,224,135078,1,0 +304,120,135250,1,0 +400,120,135421,1,0 +400,16,135593,1,0 +496,16,135764,1,0 +296,56,136107,5,0 +216,112,136278,1,0 +296,168,136450,1,0 +216,232,136621,1,0 +296,288,136793,1,0 +292,188,136964,5,4 +300,188,137050,1,4 +308,188,137136,1,4 +220,188,137307,1,4 +212,188,137393,1,4 +204,188,137478,1,4 +260,268,137650,1,4 +260,276,137736,1,4 +260,284,137821,1,4 +256,208,137993,12,4,139021 +256,16,139193,5,2 +256,112,139536,2,2,B|312:112,1,50,2|2 +256,200,140221,2,2,B|200:200,1,50,2|2 +256,288,140907,2,2,B|312:288,1,50,2|2 +256,376,141593,2,2,B|200:376,1,50 +256,208,142278,12,4,145021 diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3949367-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3949367-expected-conversion.json new file mode 100644 index 0000000000..da0e4e120a --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3949367-expected-conversion.json @@ -0,0 +1 @@ +{"Mappings":[{"StartTime":6003.0,"Objects":[{"StartTime":6003.0,"Position":64.0,"HyperDash":false}]},{"StartTime":6366.0,"Objects":[{"StartTime":6366.0,"Position":192.0,"HyperDash":false}]},{"StartTime":6730.0,"Objects":[{"StartTime":6730.0,"Position":64.0,"HyperDash":false}]},{"StartTime":7094.0,"Objects":[{"StartTime":7094.0,"Position":192.0,"HyperDash":false}]},{"StartTime":7457.0,"Objects":[{"StartTime":7457.0,"Position":320.0,"HyperDash":false}]},{"StartTime":7821.0,"Objects":[{"StartTime":7821.0,"Position":192.0,"HyperDash":false}]},{"StartTime":8185.0,"Objects":[{"StartTime":8185.0,"Position":320.0,"HyperDash":false}]},{"StartTime":8548.0,"Objects":[{"StartTime":8548.0,"Position":192.0,"HyperDash":false}]},{"StartTime":8912.0,"Objects":[{"StartTime":8912.0,"Position":320.0,"HyperDash":false}]},{"StartTime":9275.0,"Objects":[{"StartTime":9275.0,"Position":448.0,"HyperDash":false}]},{"StartTime":9639.0,"Objects":[{"StartTime":9639.0,"Position":320.0,"HyperDash":false}]},{"StartTime":10003.0,"Objects":[{"StartTime":10003.0,"Position":448.0,"HyperDash":false}]},{"StartTime":10366.0,"Objects":[{"StartTime":10366.0,"Position":65.0,"HyperDash":false},{"StartTime":10434.0,"Position":482.0,"HyperDash":false},{"StartTime":10502.0,"Position":164.0,"HyperDash":false},{"StartTime":10570.0,"Position":315.0,"HyperDash":false},{"StartTime":10638.0,"Position":145.0,"HyperDash":false},{"StartTime":10706.0,"Position":159.0,"HyperDash":false},{"StartTime":10775.0,"Position":310.0,"HyperDash":false},{"StartTime":10843.0,"Position":441.0,"HyperDash":false},{"StartTime":10911.0,"Position":428.0,"HyperDash":false},{"StartTime":10979.0,"Position":243.0,"HyperDash":false},{"StartTime":11047.0,"Position":422.0,"HyperDash":false},{"StartTime":11116.0,"Position":481.0,"HyperDash":false},{"StartTime":11184.0,"Position":104.0,"HyperDash":false},{"StartTime":11252.0,"Position":473.0,"HyperDash":false},{"StartTime":11320.0,"Position":135.0,"HyperDash":false},{"StartTime":11388.0,"Position":360.0,"HyperDash":false},{"StartTime":11457.0,"Position":123.0,"HyperDash":false}]},{"StartTime":11821.0,"Objects":[{"StartTime":11821.0,"Position":96.0,"HyperDash":false}]},{"StartTime":12003.0,"Objects":[{"StartTime":12003.0,"Position":176.0,"HyperDash":false},{"StartTime":12093.0,"Position":204.284271,"HyperDash":false},{"StartTime":12184.0,"Position":176.0,"HyperDash":false}]},{"StartTime":12366.0,"Objects":[{"StartTime":12366.0,"Position":64.0,"HyperDash":false}]},{"StartTime":12730.0,"Objects":[{"StartTime":12730.0,"Position":224.0,"HyperDash":false},{"StartTime":12820.0,"Position":252.284271,"HyperDash":false},{"StartTime":12911.0,"Position":224.0,"HyperDash":false}]},{"StartTime":13094.0,"Objects":[{"StartTime":13094.0,"Position":144.0,"HyperDash":false}]},{"StartTime":13275.0,"Objects":[{"StartTime":13275.0,"Position":320.0,"HyperDash":false}]},{"StartTime":13366.0,"Objects":[{"StartTime":13366.0,"Position":368.0,"HyperDash":false}]},{"StartTime":13457.0,"Objects":[{"StartTime":13457.0,"Position":320.0,"HyperDash":false}]},{"StartTime":13639.0,"Objects":[{"StartTime":13639.0,"Position":208.0,"HyperDash":false}]},{"StartTime":13730.0,"Objects":[{"StartTime":13730.0,"Position":160.0,"HyperDash":false},{"StartTime":13820.0,"Position":160.0,"HyperDash":false}]},{"StartTime":14003.0,"Objects":[{"StartTime":14003.0,"Position":240.0,"HyperDash":false}]},{"StartTime":14185.0,"Objects":[{"StartTime":14185.0,"Position":128.0,"HyperDash":false}]},{"StartTime":14366.0,"Objects":[{"StartTime":14366.0,"Position":208.0,"HyperDash":false},{"StartTime":14456.0,"Position":208.0,"HyperDash":false}]},{"StartTime":14548.0,"Objects":[{"StartTime":14548.0,"Position":160.0,"HyperDash":false}]},{"StartTime":14730.0,"Objects":[{"StartTime":14730.0,"Position":336.0,"HyperDash":false}]},{"StartTime":14912.0,"Objects":[{"StartTime":14912.0,"Position":256.0,"HyperDash":false},{"StartTime":15002.0,"Position":227.715729,"HyperDash":false},{"StartTime":15093.0,"Position":256.0,"HyperDash":false}]},{"StartTime":15275.0,"Objects":[{"StartTime":15275.0,"Position":368.0,"HyperDash":false}]},{"StartTime":15639.0,"Objects":[{"StartTime":15639.0,"Position":208.0,"HyperDash":false},{"StartTime":15729.0,"Position":179.715729,"HyperDash":false},{"StartTime":15820.0,"Position":208.0,"HyperDash":false}]},{"StartTime":16003.0,"Objects":[{"StartTime":16003.0,"Position":288.0,"HyperDash":false}]},{"StartTime":16185.0,"Objects":[{"StartTime":16185.0,"Position":112.0,"HyperDash":false}]},{"StartTime":16275.0,"Objects":[{"StartTime":16275.0,"Position":64.0,"HyperDash":false}]},{"StartTime":16366.0,"Objects":[{"StartTime":16366.0,"Position":112.0,"HyperDash":false}]},{"StartTime":16548.0,"Objects":[{"StartTime":16548.0,"Position":224.0,"HyperDash":false}]},{"StartTime":16639.0,"Objects":[{"StartTime":16639.0,"Position":272.0,"HyperDash":false},{"StartTime":16729.0,"Position":272.0,"HyperDash":false}]},{"StartTime":16912.0,"Objects":[{"StartTime":16912.0,"Position":160.0,"HyperDash":false}]},{"StartTime":17003.0,"Objects":[{"StartTime":17003.0,"Position":208.0,"HyperDash":false}]},{"StartTime":17094.0,"Objects":[{"StartTime":17094.0,"Position":256.0,"HyperDash":false}]},{"StartTime":17275.0,"Objects":[{"StartTime":17275.0,"Position":144.0,"HyperDash":false}]},{"StartTime":17366.0,"Objects":[{"StartTime":17366.0,"Position":80.0,"HyperDash":false}]},{"StartTime":17457.0,"Objects":[{"StartTime":17457.0,"Position":144.0,"HyperDash":false}]},{"StartTime":17639.0,"Objects":[{"StartTime":17639.0,"Position":320.0,"HyperDash":false}]},{"StartTime":17821.0,"Objects":[{"StartTime":17821.0,"Position":400.0,"HyperDash":false}]},{"StartTime":17912.0,"Objects":[{"StartTime":17912.0,"Position":352.0,"HyperDash":false}]},{"StartTime":18003.0,"Objects":[{"StartTime":18003.0,"Position":304.0,"HyperDash":false}]},{"StartTime":18185.0,"Objects":[{"StartTime":18185.0,"Position":416.0,"HyperDash":false},{"StartTime":18275.0,"Position":406.779816,"HyperDash":false},{"StartTime":18366.0,"Position":431.646057,"HyperDash":false},{"StartTime":18439.0,"Position":420.6284,"HyperDash":false},{"StartTime":18548.0,"Position":353.58432,"HyperDash":false}]},{"StartTime":18639.0,"Objects":[{"StartTime":18639.0,"Position":400.0,"HyperDash":false}]},{"StartTime":18730.0,"Objects":[{"StartTime":18730.0,"Position":448.0,"HyperDash":false}]},{"StartTime":18912.0,"Objects":[{"StartTime":18912.0,"Position":368.0,"HyperDash":false}]},{"StartTime":19094.0,"Objects":[{"StartTime":19094.0,"Position":192.0,"HyperDash":false}]},{"StartTime":19185.0,"Objects":[{"StartTime":19185.0,"Position":144.0,"HyperDash":false}]},{"StartTime":19275.0,"Objects":[{"StartTime":19275.0,"Position":192.0,"HyperDash":false}]},{"StartTime":19457.0,"Objects":[{"StartTime":19457.0,"Position":304.0,"HyperDash":false}]},{"StartTime":19548.0,"Objects":[{"StartTime":19548.0,"Position":352.0,"HyperDash":false},{"StartTime":19638.0,"Position":352.0,"HyperDash":false}]},{"StartTime":19821.0,"Objects":[{"StartTime":19821.0,"Position":272.0,"HyperDash":false}]},{"StartTime":20003.0,"Objects":[{"StartTime":20003.0,"Position":384.0,"HyperDash":false}]},{"StartTime":20185.0,"Objects":[{"StartTime":20185.0,"Position":304.0,"HyperDash":false},{"StartTime":20275.0,"Position":304.0,"HyperDash":false}]},{"StartTime":20366.0,"Objects":[{"StartTime":20366.0,"Position":352.0,"HyperDash":false}]},{"StartTime":20548.0,"Objects":[{"StartTime":20548.0,"Position":176.0,"HyperDash":false}]},{"StartTime":20730.0,"Objects":[{"StartTime":20730.0,"Position":96.0,"HyperDash":false}]},{"StartTime":20821.0,"Objects":[{"StartTime":20821.0,"Position":144.0,"HyperDash":false}]},{"StartTime":20912.0,"Objects":[{"StartTime":20912.0,"Position":192.0,"HyperDash":false}]},{"StartTime":21094.0,"Objects":[{"StartTime":21094.0,"Position":80.0,"HyperDash":false},{"StartTime":21184.0,"Position":82.2201843,"HyperDash":false},{"StartTime":21275.0,"Position":64.35393,"HyperDash":false},{"StartTime":21348.0,"Position":98.3716049,"HyperDash":false},{"StartTime":21457.0,"Position":142.41568,"HyperDash":false}]},{"StartTime":21548.0,"Objects":[{"StartTime":21548.0,"Position":96.0,"HyperDash":false}]},{"StartTime":21639.0,"Objects":[{"StartTime":21639.0,"Position":48.0,"HyperDash":false}]},{"StartTime":21821.0,"Objects":[{"StartTime":21821.0,"Position":128.0,"HyperDash":false}]},{"StartTime":22003.0,"Objects":[{"StartTime":22003.0,"Position":304.0,"HyperDash":false}]},{"StartTime":22094.0,"Objects":[{"StartTime":22094.0,"Position":352.0,"HyperDash":false}]},{"StartTime":22185.0,"Objects":[{"StartTime":22185.0,"Position":304.0,"HyperDash":false}]},{"StartTime":22366.0,"Objects":[{"StartTime":22366.0,"Position":192.0,"HyperDash":false}]},{"StartTime":22457.0,"Objects":[{"StartTime":22457.0,"Position":144.0,"HyperDash":false},{"StartTime":22547.0,"Position":144.0,"HyperDash":false}]},{"StartTime":22730.0,"Objects":[{"StartTime":22730.0,"Position":224.0,"HyperDash":false},{"StartTime":22820.0,"Position":191.366974,"HyperDash":false},{"StartTime":22911.0,"Position":144.293579,"HyperDash":false},{"StartTime":23002.0,"Position":168.779816,"HyperDash":false},{"StartTime":23093.0,"Position":223.85321,"HyperDash":false},{"StartTime":23166.0,"Position":182.0,"HyperDash":false},{"StartTime":23275.0,"Position":144.0,"HyperDash":true}]},{"StartTime":23457.0,"Objects":[{"StartTime":23457.0,"Position":400.0,"HyperDash":false}]},{"StartTime":23639.0,"Objects":[{"StartTime":23639.0,"Position":480.0,"HyperDash":false},{"StartTime":23729.0,"Position":480.0,"HyperDash":false}]},{"StartTime":23821.0,"Objects":[{"StartTime":23821.0,"Position":432.0,"HyperDash":false}]},{"StartTime":24003.0,"Objects":[{"StartTime":24003.0,"Position":320.0,"HyperDash":true}]},{"StartTime":24185.0,"Objects":[{"StartTime":24185.0,"Position":64.0,"HyperDash":false},{"StartTime":24257.0,"Position":62.7589569,"HyperDash":false},{"StartTime":24366.0,"Position":48.3107071,"HyperDash":false}]},{"StartTime":24457.0,"Objects":[{"StartTime":24457.0,"Position":96.0,"HyperDash":false}]},{"StartTime":24548.0,"Objects":[{"StartTime":24548.0,"Position":144.0,"HyperDash":false}]},{"StartTime":24730.0,"Objects":[{"StartTime":24730.0,"Position":64.0,"HyperDash":false}]},{"StartTime":24912.0,"Objects":[{"StartTime":24912.0,"Position":240.0,"HyperDash":false}]},{"StartTime":25094.0,"Objects":[{"StartTime":25094.0,"Position":320.0,"HyperDash":false},{"StartTime":25184.0,"Position":360.0,"HyperDash":false},{"StartTime":25275.0,"Position":320.0,"HyperDash":false}]},{"StartTime":25457.0,"Objects":[{"StartTime":25457.0,"Position":208.0,"HyperDash":true}]},{"StartTime":25639.0,"Objects":[{"StartTime":25639.0,"Position":464.0,"HyperDash":false},{"StartTime":25711.0,"Position":466.758942,"HyperDash":false},{"StartTime":25820.0,"Position":448.3107,"HyperDash":false}]},{"StartTime":26003.0,"Objects":[{"StartTime":26003.0,"Position":336.0,"HyperDash":false},{"StartTime":26075.0,"Position":333.758942,"HyperDash":false},{"StartTime":26184.0,"Position":320.3107,"HyperDash":false}]},{"StartTime":26366.0,"Objects":[{"StartTime":26366.0,"Position":496.0,"HyperDash":false}]},{"StartTime":26548.0,"Objects":[{"StartTime":26548.0,"Position":416.0,"HyperDash":false},{"StartTime":26638.0,"Position":416.0,"HyperDash":false}]},{"StartTime":26730.0,"Objects":[{"StartTime":26730.0,"Position":464.0,"HyperDash":false}]},{"StartTime":26912.0,"Objects":[{"StartTime":26912.0,"Position":352.0,"HyperDash":true}]},{"StartTime":27094.0,"Objects":[{"StartTime":27094.0,"Position":96.0,"HyperDash":false},{"StartTime":27166.0,"Position":79.75896,"HyperDash":false},{"StartTime":27275.0,"Position":80.31071,"HyperDash":false}]},{"StartTime":27457.0,"Objects":[{"StartTime":27457.0,"Position":192.0,"HyperDash":false}]},{"StartTime":27548.0,"Objects":[{"StartTime":27548.0,"Position":240.0,"HyperDash":false},{"StartTime":27638.0,"Position":240.0,"HyperDash":false}]},{"StartTime":27821.0,"Objects":[{"StartTime":27821.0,"Position":64.0,"HyperDash":false}]},{"StartTime":28003.0,"Objects":[{"StartTime":28003.0,"Position":176.0,"HyperDash":false}]},{"StartTime":28185.0,"Objects":[{"StartTime":28185.0,"Position":64.0,"HyperDash":false}]},{"StartTime":28275.0,"Objects":[{"StartTime":28275.0,"Position":16.0,"HyperDash":false},{"StartTime":28365.0,"Position":16.0,"HyperDash":false}]},{"StartTime":28548.0,"Objects":[{"StartTime":28548.0,"Position":272.0,"HyperDash":false},{"StartTime":28620.0,"Position":293.8232,"HyperDash":false},{"StartTime":28729.0,"Position":352.0,"HyperDash":false}]},{"StartTime":28912.0,"Objects":[{"StartTime":28912.0,"Position":240.0,"HyperDash":false},{"StartTime":28984.0,"Position":215.176788,"HyperDash":false},{"StartTime":29093.0,"Position":160.0,"HyperDash":true}]},{"StartTime":29275.0,"Objects":[{"StartTime":29275.0,"Position":416.0,"HyperDash":false}]},{"StartTime":29457.0,"Objects":[{"StartTime":29457.0,"Position":496.0,"HyperDash":false},{"StartTime":29547.0,"Position":496.0,"HyperDash":false}]},{"StartTime":29639.0,"Objects":[{"StartTime":29639.0,"Position":448.0,"HyperDash":false}]},{"StartTime":29821.0,"Objects":[{"StartTime":29821.0,"Position":336.0,"HyperDash":true}]},{"StartTime":30003.0,"Objects":[{"StartTime":30003.0,"Position":80.0,"HyperDash":false},{"StartTime":30075.0,"Position":74.90608,"HyperDash":false},{"StartTime":30184.0,"Position":32.0,"HyperDash":false}]},{"StartTime":30275.0,"Objects":[{"StartTime":30275.0,"Position":32.0,"HyperDash":false}]},{"StartTime":30366.0,"Objects":[{"StartTime":30366.0,"Position":64.0,"HyperDash":false}]},{"StartTime":30548.0,"Objects":[{"StartTime":30548.0,"Position":144.0,"HyperDash":false}]},{"StartTime":30730.0,"Objects":[{"StartTime":30730.0,"Position":320.0,"HyperDash":false}]},{"StartTime":30912.0,"Objects":[{"StartTime":30912.0,"Position":240.0,"HyperDash":false},{"StartTime":31002.0,"Position":200.0,"HyperDash":false},{"StartTime":31093.0,"Position":240.0,"HyperDash":false}]},{"StartTime":31275.0,"Objects":[{"StartTime":31275.0,"Position":352.0,"HyperDash":true}]},{"StartTime":31457.0,"Objects":[{"StartTime":31457.0,"Position":96.0,"HyperDash":false},{"StartTime":31529.0,"Position":96.75896,"HyperDash":false},{"StartTime":31638.0,"Position":80.31071,"HyperDash":false}]},{"StartTime":31821.0,"Objects":[{"StartTime":31821.0,"Position":192.0,"HyperDash":false}]},{"StartTime":32003.0,"Objects":[{"StartTime":32003.0,"Position":80.0,"HyperDash":false}]},{"StartTime":32185.0,"Objects":[{"StartTime":32185.0,"Position":256.0,"HyperDash":false}]},{"StartTime":32366.0,"Objects":[{"StartTime":32366.0,"Position":336.0,"HyperDash":false},{"StartTime":32456.0,"Position":336.0,"HyperDash":false}]},{"StartTime":32548.0,"Objects":[{"StartTime":32548.0,"Position":288.0,"HyperDash":false}]},{"StartTime":32730.0,"Objects":[{"StartTime":32730.0,"Position":400.0,"HyperDash":true}]},{"StartTime":32912.0,"Objects":[{"StartTime":32912.0,"Position":144.0,"HyperDash":false},{"StartTime":32984.0,"Position":149.758957,"HyperDash":false},{"StartTime":33093.0,"Position":128.310715,"HyperDash":false}]},{"StartTime":33275.0,"Objects":[{"StartTime":33275.0,"Position":240.0,"HyperDash":false}]},{"StartTime":33366.0,"Objects":[{"StartTime":33366.0,"Position":288.0,"HyperDash":false}]},{"StartTime":33457.0,"Objects":[{"StartTime":33457.0,"Position":240.0,"HyperDash":false}]},{"StartTime":33639.0,"Objects":[{"StartTime":33639.0,"Position":128.0,"HyperDash":false}]},{"StartTime":33821.0,"Objects":[{"StartTime":33821.0,"Position":240.0,"HyperDash":false}]},{"StartTime":34003.0,"Objects":[{"StartTime":34003.0,"Position":128.0,"HyperDash":false}]},{"StartTime":34094.0,"Objects":[{"StartTime":34094.0,"Position":80.0,"HyperDash":false},{"StartTime":34184.0,"Position":80.0,"HyperDash":true}]},{"StartTime":34366.0,"Objects":[{"StartTime":34366.0,"Position":336.0,"HyperDash":false},{"StartTime":34438.0,"Position":369.8232,"HyperDash":false},{"StartTime":34547.0,"Position":416.0,"HyperDash":false}]},{"StartTime":34730.0,"Objects":[{"StartTime":34730.0,"Position":240.0,"HyperDash":false},{"StartTime":34802.0,"Position":189.176788,"HyperDash":false},{"StartTime":34911.0,"Position":160.0,"HyperDash":true}]},{"StartTime":35094.0,"Objects":[{"StartTime":35094.0,"Position":432.0,"HyperDash":false},{"StartTime":35184.0,"Position":432.0,"HyperDash":false}]},{"StartTime":35275.0,"Objects":[{"StartTime":35275.0,"Position":384.0,"HyperDash":false}]},{"StartTime":35457.0,"Objects":[{"StartTime":35457.0,"Position":208.0,"HyperDash":false},{"StartTime":35529.0,"Position":159.176788,"HyperDash":false},{"StartTime":35638.0,"Position":128.0,"HyperDash":false}]},{"StartTime":35821.0,"Objects":[{"StartTime":35821.0,"Position":384.0,"HyperDash":false}]},{"StartTime":36003.0,"Objects":[{"StartTime":36003.0,"Position":464.0,"HyperDash":false}]},{"StartTime":36094.0,"Objects":[{"StartTime":36094.0,"Position":384.0,"HyperDash":false}]},{"StartTime":36185.0,"Objects":[{"StartTime":36185.0,"Position":336.0,"HyperDash":false}]},{"StartTime":36366.0,"Objects":[{"StartTime":36366.0,"Position":448.0,"HyperDash":true}]},{"StartTime":36548.0,"Objects":[{"StartTime":36548.0,"Position":192.0,"HyperDash":false},{"StartTime":36638.0,"Position":152.0,"HyperDash":false},{"StartTime":36729.0,"Position":192.0,"HyperDash":false}]},{"StartTime":36912.0,"Objects":[{"StartTime":36912.0,"Position":368.0,"HyperDash":false}]},{"StartTime":37003.0,"Objects":[{"StartTime":37003.0,"Position":416.0,"HyperDash":false},{"StartTime":37093.0,"Position":416.0,"HyperDash":true}]},{"StartTime":37275.0,"Objects":[{"StartTime":37275.0,"Position":160.0,"HyperDash":false},{"StartTime":37347.0,"Position":156.758957,"HyperDash":false},{"StartTime":37456.0,"Position":144.310715,"HyperDash":false}]},{"StartTime":37548.0,"Objects":[{"StartTime":37548.0,"Position":192.0,"HyperDash":false}]},{"StartTime":37639.0,"Objects":[{"StartTime":37639.0,"Position":272.0,"HyperDash":false}]},{"StartTime":37821.0,"Objects":[{"StartTime":37821.0,"Position":160.0,"HyperDash":true}]},{"StartTime":38003.0,"Objects":[{"StartTime":38003.0,"Position":416.0,"HyperDash":false}]},{"StartTime":38185.0,"Objects":[{"StartTime":38185.0,"Position":496.0,"HyperDash":false},{"StartTime":38275.0,"Position":496.0,"HyperDash":false}]},{"StartTime":38366.0,"Objects":[{"StartTime":38366.0,"Position":416.0,"HyperDash":false}]},{"StartTime":38548.0,"Objects":[{"StartTime":38548.0,"Position":496.0,"HyperDash":true}]},{"StartTime":38730.0,"Objects":[{"StartTime":38730.0,"Position":240.0,"HyperDash":false}]},{"StartTime":38821.0,"Objects":[{"StartTime":38821.0,"Position":192.0,"HyperDash":false}]},{"StartTime":38912.0,"Objects":[{"StartTime":38912.0,"Position":240.0,"HyperDash":false}]},{"StartTime":39094.0,"Objects":[{"StartTime":39094.0,"Position":352.0,"HyperDash":false},{"StartTime":39166.0,"Position":328.1768,"HyperDash":false},{"StartTime":39275.0,"Position":272.0,"HyperDash":true}]},{"StartTime":39457.0,"Objects":[{"StartTime":39457.0,"Position":16.0,"HyperDash":false},{"StartTime":39547.0,"Position":16.0,"HyperDash":false}]},{"StartTime":39639.0,"Objects":[{"StartTime":39639.0,"Position":64.0,"HyperDash":false}]},{"StartTime":39821.0,"Objects":[{"StartTime":39821.0,"Position":240.0,"HyperDash":false},{"StartTime":39911.0,"Position":211.715729,"HyperDash":false}]},{"StartTime":40003.0,"Objects":[{"StartTime":40003.0,"Position":160.0,"HyperDash":true}]},{"StartTime":40185.0,"Objects":[{"StartTime":40185.0,"Position":416.0,"HyperDash":false}]},{"StartTime":40275.0,"Objects":[{"StartTime":40275.0,"Position":464.0,"HyperDash":false}]},{"StartTime":40366.0,"Objects":[{"StartTime":40366.0,"Position":416.0,"HyperDash":false}]},{"StartTime":40548.0,"Objects":[{"StartTime":40548.0,"Position":240.0,"HyperDash":false}]},{"StartTime":40639.0,"Objects":[{"StartTime":40639.0,"Position":288.0,"HyperDash":false}]},{"StartTime":40730.0,"Objects":[{"StartTime":40730.0,"Position":336.0,"HyperDash":true}]},{"StartTime":40912.0,"Objects":[{"StartTime":40912.0,"Position":64.0,"HyperDash":false},{"StartTime":41002.0,"Position":64.0,"HyperDash":false}]},{"StartTime":41094.0,"Objects":[{"StartTime":41094.0,"Position":112.0,"HyperDash":false}]},{"StartTime":41275.0,"Objects":[{"StartTime":41275.0,"Position":288.0,"HyperDash":false},{"StartTime":41347.0,"Position":312.8232,"HyperDash":false},{"StartTime":41456.0,"Position":368.0,"HyperDash":false}]},{"StartTime":41639.0,"Objects":[{"StartTime":41639.0,"Position":112.0,"HyperDash":false}]},{"StartTime":41821.0,"Objects":[{"StartTime":41821.0,"Position":32.0,"HyperDash":false}]},{"StartTime":41912.0,"Objects":[{"StartTime":41912.0,"Position":112.0,"HyperDash":false}]},{"StartTime":42003.0,"Objects":[{"StartTime":42003.0,"Position":160.0,"HyperDash":false}]},{"StartTime":42185.0,"Objects":[{"StartTime":42185.0,"Position":48.0,"HyperDash":true}]},{"StartTime":42366.0,"Objects":[{"StartTime":42366.0,"Position":304.0,"HyperDash":false},{"StartTime":42438.0,"Position":338.8232,"HyperDash":false},{"StartTime":42547.0,"Position":384.0,"HyperDash":false}]},{"StartTime":42730.0,"Objects":[{"StartTime":42730.0,"Position":208.0,"HyperDash":false},{"StartTime":42802.0,"Position":174.176788,"HyperDash":false},{"StartTime":42911.0,"Position":128.0,"HyperDash":false}]},{"StartTime":43094.0,"Objects":[{"StartTime":43094.0,"Position":384.0,"HyperDash":false},{"StartTime":43166.0,"Position":407.241058,"HyperDash":false},{"StartTime":43275.0,"Position":399.6893,"HyperDash":false}]},{"StartTime":43366.0,"Objects":[{"StartTime":43366.0,"Position":352.0,"HyperDash":false}]},{"StartTime":43457.0,"Objects":[{"StartTime":43457.0,"Position":272.0,"HyperDash":false}]},{"StartTime":43639.0,"Objects":[{"StartTime":43639.0,"Position":384.0,"HyperDash":true}]},{"StartTime":43821.0,"Objects":[{"StartTime":43821.0,"Position":128.0,"HyperDash":false}]},{"StartTime":44003.0,"Objects":[{"StartTime":44003.0,"Position":48.0,"HyperDash":false},{"StartTime":44093.0,"Position":48.0,"HyperDash":false}]},{"StartTime":44185.0,"Objects":[{"StartTime":44185.0,"Position":128.0,"HyperDash":false}]},{"StartTime":44366.0,"Objects":[{"StartTime":44366.0,"Position":48.0,"HyperDash":true}]},{"StartTime":44548.0,"Objects":[{"StartTime":44548.0,"Position":304.0,"HyperDash":false}]},{"StartTime":44730.0,"Objects":[{"StartTime":44730.0,"Position":384.0,"HyperDash":false}]},{"StartTime":44821.0,"Objects":[{"StartTime":44821.0,"Position":336.0,"HyperDash":false}]},{"StartTime":44912.0,"Objects":[{"StartTime":44912.0,"Position":256.0,"HyperDash":false}]},{"StartTime":45094.0,"Objects":[{"StartTime":45094.0,"Position":368.0,"HyperDash":true}]},{"StartTime":45275.0,"Objects":[{"StartTime":45275.0,"Position":112.0,"HyperDash":false}]},{"StartTime":45366.0,"Objects":[{"StartTime":45366.0,"Position":64.0,"HyperDash":false}]},{"StartTime":45457.0,"Objects":[{"StartTime":45457.0,"Position":112.0,"HyperDash":false}]},{"StartTime":45639.0,"Objects":[{"StartTime":45639.0,"Position":288.0,"HyperDash":false}]},{"StartTime":45730.0,"Objects":[{"StartTime":45730.0,"Position":336.0,"HyperDash":false},{"StartTime":45820.0,"Position":336.0,"HyperDash":false}]},{"StartTime":46003.0,"Objects":[{"StartTime":46003.0,"Position":80.0,"HyperDash":false},{"StartTime":46093.0,"Position":80.0,"HyperDash":false}]},{"StartTime":46185.0,"Objects":[{"StartTime":46185.0,"Position":128.0,"HyperDash":false}]},{"StartTime":46366.0,"Objects":[{"StartTime":46366.0,"Position":304.0,"HyperDash":false}]},{"StartTime":46457.0,"Objects":[{"StartTime":46457.0,"Position":256.0,"HyperDash":false}]},{"StartTime":46548.0,"Objects":[{"StartTime":46548.0,"Position":208.0,"HyperDash":true}]},{"StartTime":46730.0,"Objects":[{"StartTime":46730.0,"Position":464.0,"HyperDash":false}]},{"StartTime":46912.0,"Objects":[{"StartTime":46912.0,"Position":45.0,"HyperDash":false},{"StartTime":46997.0,"Position":397.0,"HyperDash":false},{"StartTime":47082.0,"Position":342.0,"HyperDash":false},{"StartTime":47167.0,"Position":163.0,"HyperDash":false},{"StartTime":47252.0,"Position":278.0,"HyperDash":false},{"StartTime":47338.0,"Position":220.0,"HyperDash":false},{"StartTime":47423.0,"Position":253.0,"HyperDash":false},{"StartTime":47508.0,"Position":233.0,"HyperDash":false},{"StartTime":47593.0,"Position":97.0,"HyperDash":false},{"StartTime":47678.0,"Position":473.0,"HyperDash":false},{"StartTime":47764.0,"Position":189.0,"HyperDash":false},{"StartTime":47849.0,"Position":194.0,"HyperDash":false},{"StartTime":47934.0,"Position":107.0,"HyperDash":false},{"StartTime":48019.0,"Position":21.0,"HyperDash":false},{"StartTime":48105.0,"Position":461.0,"HyperDash":false},{"StartTime":48190.0,"Position":498.0,"HyperDash":false},{"StartTime":48275.0,"Position":184.0,"HyperDash":false},{"StartTime":48360.0,"Position":78.0,"HyperDash":false},{"StartTime":48445.0,"Position":338.0,"HyperDash":false},{"StartTime":48531.0,"Position":392.0,"HyperDash":false},{"StartTime":48616.0,"Position":335.0,"HyperDash":false},{"StartTime":48701.0,"Position":193.0,"HyperDash":false},{"StartTime":48786.0,"Position":478.0,"HyperDash":false},{"StartTime":48872.0,"Position":255.0,"HyperDash":false},{"StartTime":48957.0,"Position":175.0,"HyperDash":false},{"StartTime":49042.0,"Position":274.0,"HyperDash":false},{"StartTime":49127.0,"Position":442.0,"HyperDash":false},{"StartTime":49212.0,"Position":295.0,"HyperDash":false},{"StartTime":49298.0,"Position":311.0,"HyperDash":false},{"StartTime":49383.0,"Position":17.0,"HyperDash":false},{"StartTime":49468.0,"Position":467.0,"HyperDash":false},{"StartTime":49553.0,"Position":30.0,"HyperDash":false},{"StartTime":49639.0,"Position":218.0,"HyperDash":false}]},{"StartTime":52548.0,"Objects":[{"StartTime":52548.0,"Position":200.0,"HyperDash":false},{"StartTime":52620.0,"Position":175.758957,"HyperDash":false},{"StartTime":52729.0,"Position":184.310715,"HyperDash":false}]},{"StartTime":52912.0,"Objects":[{"StartTime":52912.0,"Position":280.0,"HyperDash":false},{"StartTime":52984.0,"Position":269.758942,"HyperDash":false},{"StartTime":53093.0,"Position":264.3107,"HyperDash":false}]},{"StartTime":53457.0,"Objects":[{"StartTime":53457.0,"Position":104.0,"HyperDash":false}]},{"StartTime":53639.0,"Objects":[{"StartTime":53639.0,"Position":184.0,"HyperDash":false}]},{"StartTime":54003.0,"Objects":[{"StartTime":54003.0,"Position":344.0,"HyperDash":false},{"StartTime":54075.0,"Position":333.241058,"HyperDash":false},{"StartTime":54184.0,"Position":359.6893,"HyperDash":false}]},{"StartTime":54366.0,"Objects":[{"StartTime":54366.0,"Position":256.0,"HyperDash":false},{"StartTime":54438.0,"Position":273.241058,"HyperDash":false},{"StartTime":54547.0,"Position":271.6893,"HyperDash":false}]},{"StartTime":54912.0,"Objects":[{"StartTime":54912.0,"Position":448.0,"HyperDash":false}]},{"StartTime":55094.0,"Objects":[{"StartTime":55094.0,"Position":360.0,"HyperDash":false}]},{"StartTime":55457.0,"Objects":[{"StartTime":55457.0,"Position":176.0,"HyperDash":false},{"StartTime":55529.0,"Position":150.758957,"HyperDash":false},{"StartTime":55638.0,"Position":160.310715,"HyperDash":false}]},{"StartTime":55821.0,"Objects":[{"StartTime":55821.0,"Position":272.0,"HyperDash":false},{"StartTime":55893.0,"Position":282.758942,"HyperDash":false},{"StartTime":56002.0,"Position":256.3107,"HyperDash":false}]},{"StartTime":56366.0,"Objects":[{"StartTime":56366.0,"Position":64.0,"HyperDash":false}]},{"StartTime":56548.0,"Objects":[{"StartTime":56548.0,"Position":160.0,"HyperDash":false}]},{"StartTime":56912.0,"Objects":[{"StartTime":56912.0,"Position":368.0,"HyperDash":false},{"StartTime":56984.0,"Position":383.241058,"HyperDash":false},{"StartTime":57093.0,"Position":383.6893,"HyperDash":false}]},{"StartTime":57275.0,"Objects":[{"StartTime":57275.0,"Position":264.0,"HyperDash":false},{"StartTime":57347.0,"Position":270.241058,"HyperDash":false},{"StartTime":57456.0,"Position":279.6893,"HyperDash":false}]},{"StartTime":57639.0,"Objects":[{"StartTime":57639.0,"Position":400.0,"HyperDash":false},{"StartTime":57711.0,"Position":367.1768,"HyperDash":false},{"StartTime":57820.0,"Position":320.0,"HyperDash":false}]},{"StartTime":58003.0,"Objects":[{"StartTime":58003.0,"Position":464.0,"HyperDash":false}]},{"StartTime":58185.0,"Objects":[{"StartTime":58185.0,"Position":320.0,"HyperDash":false}]},{"StartTime":58366.0,"Objects":[{"StartTime":58366.0,"Position":144.0,"HyperDash":false}]},{"StartTime":58548.0,"Objects":[{"StartTime":58548.0,"Position":224.0,"HyperDash":false},{"StartTime":58638.0,"Position":264.0,"HyperDash":false},{"StartTime":58729.0,"Position":224.0,"HyperDash":false}]},{"StartTime":58912.0,"Objects":[{"StartTime":58912.0,"Position":144.0,"HyperDash":false}]},{"StartTime":59094.0,"Objects":[{"StartTime":59094.0,"Position":16.0,"HyperDash":false},{"StartTime":59184.0,"Position":16.0,"HyperDash":false}]},{"StartTime":59275.0,"Objects":[{"StartTime":59275.0,"Position":64.0,"HyperDash":false}]},{"StartTime":59457.0,"Objects":[{"StartTime":59457.0,"Position":144.0,"HyperDash":false}]},{"StartTime":59639.0,"Objects":[{"StartTime":59639.0,"Position":64.0,"HyperDash":false}]},{"StartTime":59821.0,"Objects":[{"StartTime":59821.0,"Position":240.0,"HyperDash":false},{"StartTime":59911.0,"Position":240.0,"HyperDash":false}]},{"StartTime":60003.0,"Objects":[{"StartTime":60003.0,"Position":192.0,"HyperDash":false}]},{"StartTime":60185.0,"Objects":[{"StartTime":60185.0,"Position":80.0,"HyperDash":false}]},{"StartTime":60275.0,"Objects":[{"StartTime":60275.0,"Position":128.0,"HyperDash":false}]},{"StartTime":60366.0,"Objects":[{"StartTime":60366.0,"Position":176.0,"HyperDash":false}]},{"StartTime":60548.0,"Objects":[{"StartTime":60548.0,"Position":64.0,"HyperDash":false},{"StartTime":60638.0,"Position":64.0,"HyperDash":false}]},{"StartTime":60730.0,"Objects":[{"StartTime":60730.0,"Position":112.0,"HyperDash":false}]},{"StartTime":60912.0,"Objects":[{"StartTime":60912.0,"Position":224.0,"HyperDash":false},{"StartTime":60984.0,"Position":204.176788,"HyperDash":false},{"StartTime":61093.0,"Position":144.0,"HyperDash":false}]},{"StartTime":61275.0,"Objects":[{"StartTime":61275.0,"Position":320.0,"HyperDash":false}]},{"StartTime":61457.0,"Objects":[{"StartTime":61457.0,"Position":400.0,"HyperDash":false},{"StartTime":61547.0,"Position":400.0,"HyperDash":false}]},{"StartTime":61639.0,"Objects":[{"StartTime":61639.0,"Position":352.0,"HyperDash":false}]},{"StartTime":61821.0,"Objects":[{"StartTime":61821.0,"Position":432.0,"HyperDash":false}]},{"StartTime":62003.0,"Objects":[{"StartTime":62003.0,"Position":320.0,"HyperDash":false},{"StartTime":62093.0,"Position":320.0,"HyperDash":false}]},{"StartTime":62185.0,"Objects":[{"StartTime":62185.0,"Position":368.0,"HyperDash":false}]},{"StartTime":62366.0,"Objects":[{"StartTime":62366.0,"Position":448.0,"HyperDash":false}]},{"StartTime":62548.0,"Objects":[{"StartTime":62548.0,"Position":368.0,"HyperDash":false}]},{"StartTime":62730.0,"Objects":[{"StartTime":62730.0,"Position":192.0,"HyperDash":false}]},{"StartTime":62912.0,"Objects":[{"StartTime":62912.0,"Position":272.0,"HyperDash":false}]},{"StartTime":63094.0,"Objects":[{"StartTime":63094.0,"Position":192.0,"HyperDash":false},{"StartTime":63184.0,"Position":152.0,"HyperDash":false},{"StartTime":63275.0,"Position":192.0,"HyperDash":false}]},{"StartTime":63457.0,"Objects":[{"StartTime":63457.0,"Position":304.0,"HyperDash":false},{"StartTime":63529.0,"Position":274.1768,"HyperDash":false},{"StartTime":63638.0,"Position":224.0,"HyperDash":false}]},{"StartTime":63821.0,"Objects":[{"StartTime":63821.0,"Position":112.0,"HyperDash":false},{"StartTime":63893.0,"Position":144.823212,"HyperDash":false},{"StartTime":64002.0,"Position":192.0,"HyperDash":false}]},{"StartTime":64185.0,"Objects":[{"StartTime":64185.0,"Position":368.0,"HyperDash":false}]},{"StartTime":64366.0,"Objects":[{"StartTime":64366.0,"Position":288.0,"HyperDash":false},{"StartTime":64456.0,"Position":248.0,"HyperDash":false},{"StartTime":64547.0,"Position":288.0,"HyperDash":false}]},{"StartTime":64730.0,"Objects":[{"StartTime":64730.0,"Position":368.0,"HyperDash":false}]},{"StartTime":64912.0,"Objects":[{"StartTime":64912.0,"Position":448.0,"HyperDash":false}]},{"StartTime":65094.0,"Objects":[{"StartTime":65094.0,"Position":368.0,"HyperDash":false},{"StartTime":65184.0,"Position":328.0,"HyperDash":false},{"StartTime":65275.0,"Position":368.0,"HyperDash":false}]},{"StartTime":65457.0,"Objects":[{"StartTime":65457.0,"Position":448.0,"HyperDash":false}]},{"StartTime":65639.0,"Objects":[{"StartTime":65639.0,"Position":272.0,"HyperDash":false},{"StartTime":65729.0,"Position":272.0,"HyperDash":false}]},{"StartTime":65821.0,"Objects":[{"StartTime":65821.0,"Position":320.0,"HyperDash":false}]},{"StartTime":66003.0,"Objects":[{"StartTime":66003.0,"Position":432.0,"HyperDash":false}]},{"StartTime":66094.0,"Objects":[{"StartTime":66094.0,"Position":384.0,"HyperDash":false}]},{"StartTime":66185.0,"Objects":[{"StartTime":66185.0,"Position":336.0,"HyperDash":false}]},{"StartTime":66366.0,"Objects":[{"StartTime":66366.0,"Position":224.0,"HyperDash":false}]},{"StartTime":66457.0,"Objects":[{"StartTime":66457.0,"Position":272.0,"HyperDash":false}]},{"StartTime":66548.0,"Objects":[{"StartTime":66548.0,"Position":320.0,"HyperDash":false}]},{"StartTime":66730.0,"Objects":[{"StartTime":66730.0,"Position":432.0,"HyperDash":false}]},{"StartTime":66912.0,"Objects":[{"StartTime":66912.0,"Position":320.0,"HyperDash":false}]},{"StartTime":67094.0,"Objects":[{"StartTime":67094.0,"Position":144.0,"HyperDash":false}]},{"StartTime":67275.0,"Objects":[{"StartTime":67275.0,"Position":64.0,"HyperDash":false},{"StartTime":67365.0,"Position":64.0,"HyperDash":false}]},{"StartTime":67457.0,"Objects":[{"StartTime":67457.0,"Position":112.0,"HyperDash":false}]},{"StartTime":67639.0,"Objects":[{"StartTime":67639.0,"Position":192.0,"HyperDash":false}]},{"StartTime":67821.0,"Objects":[{"StartTime":67821.0,"Position":80.0,"HyperDash":false},{"StartTime":67911.0,"Position":92.64911,"HyperDash":false}]},{"StartTime":68003.0,"Objects":[{"StartTime":68003.0,"Position":144.0,"HyperDash":false}]},{"StartTime":68185.0,"Objects":[{"StartTime":68185.0,"Position":224.0,"HyperDash":false}]},{"StartTime":68366.0,"Objects":[{"StartTime":68366.0,"Position":144.0,"HyperDash":false}]},{"StartTime":68548.0,"Objects":[{"StartTime":68548.0,"Position":320.0,"HyperDash":false}]},{"StartTime":68730.0,"Objects":[{"StartTime":68730.0,"Position":400.0,"HyperDash":false},{"StartTime":68820.0,"Position":407.844635,"HyperDash":false}]},{"StartTime":69003.0,"Objects":[{"StartTime":69003.0,"Position":296.0,"HyperDash":false},{"StartTime":69093.0,"Position":256.0,"HyperDash":false}]},{"StartTime":69275.0,"Objects":[{"StartTime":69275.0,"Position":368.0,"HyperDash":false}]},{"StartTime":69457.0,"Objects":[{"StartTime":69457.0,"Position":256.0,"HyperDash":false}]},{"StartTime":69639.0,"Objects":[{"StartTime":69639.0,"Position":80.0,"HyperDash":false}]},{"StartTime":69821.0,"Objects":[{"StartTime":69821.0,"Position":192.0,"HyperDash":true}]},{"StartTime":70003.0,"Objects":[{"StartTime":70003.0,"Position":448.0,"HyperDash":false}]},{"StartTime":75821.0,"Objects":[{"StartTime":75821.0,"Position":160.0,"HyperDash":false},{"StartTime":75893.0,"Position":115.176788,"HyperDash":false},{"StartTime":76002.0,"Position":80.0,"HyperDash":false}]},{"StartTime":76185.0,"Objects":[{"StartTime":76185.0,"Position":160.0,"HyperDash":false},{"StartTime":76257.0,"Position":131.176788,"HyperDash":false},{"StartTime":76366.0,"Position":80.0,"HyperDash":false}]},{"StartTime":76548.0,"Objects":[{"StartTime":76548.0,"Position":160.0,"HyperDash":false}]},{"StartTime":76730.0,"Objects":[{"StartTime":76730.0,"Position":240.0,"HyperDash":false}]},{"StartTime":76912.0,"Objects":[{"StartTime":76912.0,"Position":240.0,"HyperDash":false}]},{"StartTime":77094.0,"Objects":[{"StartTime":77094.0,"Position":240.0,"HyperDash":false}]},{"StartTime":77275.0,"Objects":[{"StartTime":77275.0,"Position":368.0,"HyperDash":false},{"StartTime":77347.0,"Position":387.8232,"HyperDash":false},{"StartTime":77456.0,"Position":448.0,"HyperDash":false}]},{"StartTime":77639.0,"Objects":[{"StartTime":77639.0,"Position":368.0,"HyperDash":false},{"StartTime":77711.0,"Position":392.8232,"HyperDash":false},{"StartTime":77820.0,"Position":448.0,"HyperDash":false}]},{"StartTime":78003.0,"Objects":[{"StartTime":78003.0,"Position":352.0,"HyperDash":false}]},{"StartTime":78185.0,"Objects":[{"StartTime":78185.0,"Position":256.0,"HyperDash":false}]},{"StartTime":78366.0,"Objects":[{"StartTime":78366.0,"Position":256.0,"HyperDash":false}]},{"StartTime":78548.0,"Objects":[{"StartTime":78548.0,"Position":352.0,"HyperDash":false}]},{"StartTime":78730.0,"Objects":[{"StartTime":78730.0,"Position":176.0,"HyperDash":false},{"StartTime":78802.0,"Position":125.176788,"HyperDash":false},{"StartTime":78911.0,"Position":96.0,"HyperDash":false}]},{"StartTime":79094.0,"Objects":[{"StartTime":79094.0,"Position":176.0,"HyperDash":false},{"StartTime":79166.0,"Position":146.176788,"HyperDash":false},{"StartTime":79275.0,"Position":96.0,"HyperDash":false}]},{"StartTime":79457.0,"Objects":[{"StartTime":79457.0,"Position":192.0,"HyperDash":false}]},{"StartTime":79639.0,"Objects":[{"StartTime":79639.0,"Position":288.0,"HyperDash":false}]},{"StartTime":79821.0,"Objects":[{"StartTime":79821.0,"Position":192.0,"HyperDash":false}]},{"StartTime":80003.0,"Objects":[{"StartTime":80003.0,"Position":288.0,"HyperDash":false}]},{"StartTime":80185.0,"Objects":[{"StartTime":80185.0,"Position":194.0,"HyperDash":false},{"StartTime":80253.0,"Position":234.0,"HyperDash":false},{"StartTime":80321.0,"Position":179.0,"HyperDash":false},{"StartTime":80389.0,"Position":278.0,"HyperDash":false},{"StartTime":80457.0,"Position":474.0,"HyperDash":false},{"StartTime":80525.0,"Position":50.0,"HyperDash":false},{"StartTime":80593.0,"Position":458.0,"HyperDash":false},{"StartTime":80661.0,"Position":425.0,"HyperDash":false},{"StartTime":80730.0,"Position":466.0,"HyperDash":false},{"StartTime":80798.0,"Position":56.0,"HyperDash":false},{"StartTime":80866.0,"Position":109.0,"HyperDash":false},{"StartTime":80934.0,"Position":482.0,"HyperDash":false},{"StartTime":81002.0,"Position":147.0,"HyperDash":false},{"StartTime":81070.0,"Position":285.0,"HyperDash":false},{"StartTime":81138.0,"Position":452.0,"HyperDash":false},{"StartTime":81206.0,"Position":419.0,"HyperDash":false},{"StartTime":81275.0,"Position":269.0,"HyperDash":false}]},{"StartTime":81639.0,"Objects":[{"StartTime":81639.0,"Position":416.0,"HyperDash":false}]},{"StartTime":81821.0,"Objects":[{"StartTime":81821.0,"Position":336.0,"HyperDash":false},{"StartTime":81911.0,"Position":307.715729,"HyperDash":false},{"StartTime":82002.0,"Position":336.0,"HyperDash":false}]},{"StartTime":82185.0,"Objects":[{"StartTime":82185.0,"Position":448.0,"HyperDash":false}]},{"StartTime":82548.0,"Objects":[{"StartTime":82548.0,"Position":288.0,"HyperDash":false},{"StartTime":82638.0,"Position":259.715729,"HyperDash":false},{"StartTime":82729.0,"Position":288.0,"HyperDash":false}]},{"StartTime":82912.0,"Objects":[{"StartTime":82912.0,"Position":368.0,"HyperDash":false}]},{"StartTime":83094.0,"Objects":[{"StartTime":83094.0,"Position":192.0,"HyperDash":false}]},{"StartTime":83185.0,"Objects":[{"StartTime":83185.0,"Position":144.0,"HyperDash":false}]},{"StartTime":83275.0,"Objects":[{"StartTime":83275.0,"Position":192.0,"HyperDash":false}]},{"StartTime":83457.0,"Objects":[{"StartTime":83457.0,"Position":304.0,"HyperDash":false}]},{"StartTime":83548.0,"Objects":[{"StartTime":83548.0,"Position":352.0,"HyperDash":false},{"StartTime":83638.0,"Position":352.0,"HyperDash":false}]},{"StartTime":83821.0,"Objects":[{"StartTime":83821.0,"Position":272.0,"HyperDash":false}]},{"StartTime":84003.0,"Objects":[{"StartTime":84003.0,"Position":384.0,"HyperDash":false}]},{"StartTime":84185.0,"Objects":[{"StartTime":84185.0,"Position":304.0,"HyperDash":false},{"StartTime":84275.0,"Position":304.0,"HyperDash":false}]},{"StartTime":84366.0,"Objects":[{"StartTime":84366.0,"Position":352.0,"HyperDash":false}]},{"StartTime":84548.0,"Objects":[{"StartTime":84548.0,"Position":176.0,"HyperDash":false}]},{"StartTime":84730.0,"Objects":[{"StartTime":84730.0,"Position":256.0,"HyperDash":false},{"StartTime":84820.0,"Position":284.284271,"HyperDash":false},{"StartTime":84911.0,"Position":256.0,"HyperDash":false}]},{"StartTime":85094.0,"Objects":[{"StartTime":85094.0,"Position":144.0,"HyperDash":false}]},{"StartTime":85457.0,"Objects":[{"StartTime":85457.0,"Position":304.0,"HyperDash":false},{"StartTime":85547.0,"Position":332.284271,"HyperDash":false},{"StartTime":85638.0,"Position":304.0,"HyperDash":false}]},{"StartTime":85821.0,"Objects":[{"StartTime":85821.0,"Position":224.0,"HyperDash":false}]},{"StartTime":86003.0,"Objects":[{"StartTime":86003.0,"Position":400.0,"HyperDash":false}]},{"StartTime":86094.0,"Objects":[{"StartTime":86094.0,"Position":448.0,"HyperDash":false}]},{"StartTime":86185.0,"Objects":[{"StartTime":86185.0,"Position":400.0,"HyperDash":false}]},{"StartTime":86366.0,"Objects":[{"StartTime":86366.0,"Position":288.0,"HyperDash":false}]},{"StartTime":86457.0,"Objects":[{"StartTime":86457.0,"Position":240.0,"HyperDash":false},{"StartTime":86547.0,"Position":240.0,"HyperDash":false}]},{"StartTime":86730.0,"Objects":[{"StartTime":86730.0,"Position":352.0,"HyperDash":false}]},{"StartTime":86821.0,"Objects":[{"StartTime":86821.0,"Position":304.0,"HyperDash":false}]},{"StartTime":86912.0,"Objects":[{"StartTime":86912.0,"Position":256.0,"HyperDash":false}]},{"StartTime":87094.0,"Objects":[{"StartTime":87094.0,"Position":368.0,"HyperDash":false}]},{"StartTime":87185.0,"Objects":[{"StartTime":87185.0,"Position":432.0,"HyperDash":false}]},{"StartTime":87275.0,"Objects":[{"StartTime":87275.0,"Position":368.0,"HyperDash":false}]},{"StartTime":87457.0,"Objects":[{"StartTime":87457.0,"Position":192.0,"HyperDash":false}]},{"StartTime":87639.0,"Objects":[{"StartTime":87639.0,"Position":112.0,"HyperDash":false}]},{"StartTime":87730.0,"Objects":[{"StartTime":87730.0,"Position":160.0,"HyperDash":false}]},{"StartTime":87821.0,"Objects":[{"StartTime":87821.0,"Position":208.0,"HyperDash":false}]},{"StartTime":88003.0,"Objects":[{"StartTime":88003.0,"Position":96.0,"HyperDash":false},{"StartTime":88093.0,"Position":87.2201843,"HyperDash":false},{"StartTime":88184.0,"Position":80.35393,"HyperDash":false},{"StartTime":88257.0,"Position":98.3716049,"HyperDash":false},{"StartTime":88366.0,"Position":158.41568,"HyperDash":false}]},{"StartTime":88457.0,"Objects":[{"StartTime":88457.0,"Position":112.0,"HyperDash":false}]},{"StartTime":88548.0,"Objects":[{"StartTime":88548.0,"Position":64.0,"HyperDash":false}]},{"StartTime":88730.0,"Objects":[{"StartTime":88730.0,"Position":144.0,"HyperDash":false}]},{"StartTime":88912.0,"Objects":[{"StartTime":88912.0,"Position":320.0,"HyperDash":false}]},{"StartTime":89003.0,"Objects":[{"StartTime":89003.0,"Position":368.0,"HyperDash":false}]},{"StartTime":89094.0,"Objects":[{"StartTime":89094.0,"Position":320.0,"HyperDash":false}]},{"StartTime":89275.0,"Objects":[{"StartTime":89275.0,"Position":208.0,"HyperDash":false}]},{"StartTime":89366.0,"Objects":[{"StartTime":89366.0,"Position":160.0,"HyperDash":false},{"StartTime":89456.0,"Position":160.0,"HyperDash":false}]},{"StartTime":89639.0,"Objects":[{"StartTime":89639.0,"Position":240.0,"HyperDash":false}]},{"StartTime":89821.0,"Objects":[{"StartTime":89821.0,"Position":128.0,"HyperDash":false}]},{"StartTime":90003.0,"Objects":[{"StartTime":90003.0,"Position":208.0,"HyperDash":false},{"StartTime":90093.0,"Position":208.0,"HyperDash":false}]},{"StartTime":90185.0,"Objects":[{"StartTime":90185.0,"Position":160.0,"HyperDash":false}]},{"StartTime":90366.0,"Objects":[{"StartTime":90366.0,"Position":336.0,"HyperDash":false}]},{"StartTime":90548.0,"Objects":[{"StartTime":90548.0,"Position":416.0,"HyperDash":false}]},{"StartTime":90639.0,"Objects":[{"StartTime":90639.0,"Position":368.0,"HyperDash":false}]},{"StartTime":90730.0,"Objects":[{"StartTime":90730.0,"Position":320.0,"HyperDash":false}]},{"StartTime":90912.0,"Objects":[{"StartTime":90912.0,"Position":432.0,"HyperDash":false},{"StartTime":91002.0,"Position":446.779816,"HyperDash":false},{"StartTime":91093.0,"Position":447.646057,"HyperDash":false},{"StartTime":91166.0,"Position":416.6284,"HyperDash":false},{"StartTime":91275.0,"Position":369.58432,"HyperDash":false}]},{"StartTime":91366.0,"Objects":[{"StartTime":91366.0,"Position":416.0,"HyperDash":false}]},{"StartTime":91457.0,"Objects":[{"StartTime":91457.0,"Position":464.0,"HyperDash":false}]},{"StartTime":91639.0,"Objects":[{"StartTime":91639.0,"Position":384.0,"HyperDash":false}]},{"StartTime":91821.0,"Objects":[{"StartTime":91821.0,"Position":208.0,"HyperDash":false}]},{"StartTime":91912.0,"Objects":[{"StartTime":91912.0,"Position":160.0,"HyperDash":false}]},{"StartTime":92003.0,"Objects":[{"StartTime":92003.0,"Position":208.0,"HyperDash":false}]},{"StartTime":92185.0,"Objects":[{"StartTime":92185.0,"Position":320.0,"HyperDash":false}]},{"StartTime":92275.0,"Objects":[{"StartTime":92275.0,"Position":368.0,"HyperDash":false},{"StartTime":92365.0,"Position":368.0,"HyperDash":false}]},{"StartTime":92548.0,"Objects":[{"StartTime":92548.0,"Position":288.0,"HyperDash":false},{"StartTime":92638.0,"Position":241.366974,"HyperDash":false},{"StartTime":92729.0,"Position":208.293579,"HyperDash":false},{"StartTime":92820.0,"Position":262.779816,"HyperDash":false},{"StartTime":92911.0,"Position":287.8532,"HyperDash":false},{"StartTime":92984.0,"Position":304.0,"HyperDash":false},{"StartTime":93093.0,"Position":368.0,"HyperDash":true}]},{"StartTime":93275.0,"Objects":[{"StartTime":93275.0,"Position":112.0,"HyperDash":false}]},{"StartTime":93457.0,"Objects":[{"StartTime":93457.0,"Position":32.0,"HyperDash":false},{"StartTime":93547.0,"Position":32.0,"HyperDash":false}]},{"StartTime":93639.0,"Objects":[{"StartTime":93639.0,"Position":80.0,"HyperDash":false}]},{"StartTime":93821.0,"Objects":[{"StartTime":93821.0,"Position":192.0,"HyperDash":true}]},{"StartTime":94003.0,"Objects":[{"StartTime":94003.0,"Position":448.0,"HyperDash":false},{"StartTime":94075.0,"Position":436.241058,"HyperDash":false},{"StartTime":94184.0,"Position":463.6893,"HyperDash":false}]},{"StartTime":94275.0,"Objects":[{"StartTime":94275.0,"Position":416.0,"HyperDash":false}]},{"StartTime":94366.0,"Objects":[{"StartTime":94366.0,"Position":368.0,"HyperDash":false}]},{"StartTime":94548.0,"Objects":[{"StartTime":94548.0,"Position":448.0,"HyperDash":false}]},{"StartTime":94730.0,"Objects":[{"StartTime":94730.0,"Position":272.0,"HyperDash":false}]},{"StartTime":94912.0,"Objects":[{"StartTime":94912.0,"Position":192.0,"HyperDash":false},{"StartTime":95002.0,"Position":152.0,"HyperDash":false},{"StartTime":95093.0,"Position":192.0,"HyperDash":false}]},{"StartTime":95275.0,"Objects":[{"StartTime":95275.0,"Position":304.0,"HyperDash":true}]},{"StartTime":95457.0,"Objects":[{"StartTime":95457.0,"Position":48.0,"HyperDash":false},{"StartTime":95529.0,"Position":66.24104,"HyperDash":false},{"StartTime":95638.0,"Position":63.6892929,"HyperDash":false}]},{"StartTime":95821.0,"Objects":[{"StartTime":95821.0,"Position":176.0,"HyperDash":false},{"StartTime":95893.0,"Position":189.241043,"HyperDash":false},{"StartTime":96002.0,"Position":191.689285,"HyperDash":false}]},{"StartTime":96185.0,"Objects":[{"StartTime":96185.0,"Position":16.0,"HyperDash":false}]},{"StartTime":96366.0,"Objects":[{"StartTime":96366.0,"Position":96.0,"HyperDash":false},{"StartTime":96456.0,"Position":96.0,"HyperDash":false}]},{"StartTime":96548.0,"Objects":[{"StartTime":96548.0,"Position":48.0,"HyperDash":false}]},{"StartTime":96730.0,"Objects":[{"StartTime":96730.0,"Position":160.0,"HyperDash":true}]},{"StartTime":96912.0,"Objects":[{"StartTime":96912.0,"Position":416.0,"HyperDash":false},{"StartTime":96984.0,"Position":402.241058,"HyperDash":false},{"StartTime":97093.0,"Position":431.6893,"HyperDash":false}]},{"StartTime":97275.0,"Objects":[{"StartTime":97275.0,"Position":320.0,"HyperDash":false}]},{"StartTime":97366.0,"Objects":[{"StartTime":97366.0,"Position":272.0,"HyperDash":false},{"StartTime":97456.0,"Position":272.0,"HyperDash":false}]},{"StartTime":97639.0,"Objects":[{"StartTime":97639.0,"Position":448.0,"HyperDash":false}]},{"StartTime":97821.0,"Objects":[{"StartTime":97821.0,"Position":336.0,"HyperDash":false}]},{"StartTime":98003.0,"Objects":[{"StartTime":98003.0,"Position":448.0,"HyperDash":false}]},{"StartTime":98094.0,"Objects":[{"StartTime":98094.0,"Position":496.0,"HyperDash":false},{"StartTime":98184.0,"Position":496.0,"HyperDash":true}]},{"StartTime":98366.0,"Objects":[{"StartTime":98366.0,"Position":240.0,"HyperDash":false},{"StartTime":98438.0,"Position":199.221,"HyperDash":false},{"StartTime":98547.0,"Position":140.0,"HyperDash":false}]},{"StartTime":98730.0,"Objects":[{"StartTime":98730.0,"Position":240.0,"HyperDash":false},{"StartTime":98802.0,"Position":264.779,"HyperDash":false},{"StartTime":98911.0,"Position":340.0,"HyperDash":false}]},{"StartTime":99094.0,"Objects":[{"StartTime":99094.0,"Position":96.0,"HyperDash":false}]},{"StartTime":99275.0,"Objects":[{"StartTime":99275.0,"Position":16.0,"HyperDash":false},{"StartTime":99365.0,"Position":16.0,"HyperDash":false}]},{"StartTime":99457.0,"Objects":[{"StartTime":99457.0,"Position":64.0,"HyperDash":false}]},{"StartTime":99639.0,"Objects":[{"StartTime":99639.0,"Position":176.0,"HyperDash":true}]},{"StartTime":99821.0,"Objects":[{"StartTime":99821.0,"Position":432.0,"HyperDash":false},{"StartTime":99893.0,"Position":432.093933,"HyperDash":false},{"StartTime":100002.0,"Position":480.0,"HyperDash":false}]},{"StartTime":100094.0,"Objects":[{"StartTime":100094.0,"Position":480.0,"HyperDash":false}]},{"StartTime":100185.0,"Objects":[{"StartTime":100185.0,"Position":448.0,"HyperDash":false}]},{"StartTime":100366.0,"Objects":[{"StartTime":100366.0,"Position":368.0,"HyperDash":false}]},{"StartTime":100548.0,"Objects":[{"StartTime":100548.0,"Position":192.0,"HyperDash":false}]},{"StartTime":100730.0,"Objects":[{"StartTime":100730.0,"Position":272.0,"HyperDash":false},{"StartTime":100820.0,"Position":312.0,"HyperDash":false},{"StartTime":100911.0,"Position":272.0,"HyperDash":false}]},{"StartTime":101094.0,"Objects":[{"StartTime":101094.0,"Position":160.0,"HyperDash":true}]},{"StartTime":101275.0,"Objects":[{"StartTime":101275.0,"Position":416.0,"HyperDash":false},{"StartTime":101347.0,"Position":426.241058,"HyperDash":false},{"StartTime":101456.0,"Position":431.6893,"HyperDash":false}]},{"StartTime":101639.0,"Objects":[{"StartTime":101639.0,"Position":320.0,"HyperDash":false}]},{"StartTime":101821.0,"Objects":[{"StartTime":101821.0,"Position":432.0,"HyperDash":false}]},{"StartTime":102003.0,"Objects":[{"StartTime":102003.0,"Position":256.0,"HyperDash":false}]},{"StartTime":102185.0,"Objects":[{"StartTime":102185.0,"Position":176.0,"HyperDash":false},{"StartTime":102275.0,"Position":176.0,"HyperDash":false}]},{"StartTime":102366.0,"Objects":[{"StartTime":102366.0,"Position":224.0,"HyperDash":false}]},{"StartTime":102548.0,"Objects":[{"StartTime":102548.0,"Position":112.0,"HyperDash":true}]},{"StartTime":102730.0,"Objects":[{"StartTime":102730.0,"Position":368.0,"HyperDash":false},{"StartTime":102802.0,"Position":376.241058,"HyperDash":false},{"StartTime":102911.0,"Position":383.6893,"HyperDash":false}]},{"StartTime":103094.0,"Objects":[{"StartTime":103094.0,"Position":272.0,"HyperDash":false}]},{"StartTime":103185.0,"Objects":[{"StartTime":103185.0,"Position":224.0,"HyperDash":false}]},{"StartTime":103275.0,"Objects":[{"StartTime":103275.0,"Position":272.0,"HyperDash":false}]},{"StartTime":103457.0,"Objects":[{"StartTime":103457.0,"Position":384.0,"HyperDash":false}]},{"StartTime":103639.0,"Objects":[{"StartTime":103639.0,"Position":272.0,"HyperDash":false}]},{"StartTime":103821.0,"Objects":[{"StartTime":103821.0,"Position":384.0,"HyperDash":false}]},{"StartTime":103912.0,"Objects":[{"StartTime":103912.0,"Position":432.0,"HyperDash":false},{"StartTime":104002.0,"Position":432.0,"HyperDash":false}]},{"StartTime":104185.0,"Objects":[{"StartTime":104185.0,"Position":176.0,"HyperDash":false},{"StartTime":104257.0,"Position":161.176788,"HyperDash":false},{"StartTime":104366.0,"Position":96.0,"HyperDash":false}]},{"StartTime":104548.0,"Objects":[{"StartTime":104548.0,"Position":272.0,"HyperDash":false},{"StartTime":104620.0,"Position":314.8232,"HyperDash":false},{"StartTime":104729.0,"Position":352.0,"HyperDash":true}]},{"StartTime":104912.0,"Objects":[{"StartTime":104912.0,"Position":80.0,"HyperDash":false},{"StartTime":105002.0,"Position":80.0,"HyperDash":false}]},{"StartTime":105094.0,"Objects":[{"StartTime":105094.0,"Position":128.0,"HyperDash":false}]},{"StartTime":105275.0,"Objects":[{"StartTime":105275.0,"Position":304.0,"HyperDash":false},{"StartTime":105347.0,"Position":337.8232,"HyperDash":false},{"StartTime":105456.0,"Position":384.0,"HyperDash":false}]},{"StartTime":105639.0,"Objects":[{"StartTime":105639.0,"Position":128.0,"HyperDash":false}]},{"StartTime":105821.0,"Objects":[{"StartTime":105821.0,"Position":48.0,"HyperDash":false}]},{"StartTime":105912.0,"Objects":[{"StartTime":105912.0,"Position":128.0,"HyperDash":false}]},{"StartTime":106003.0,"Objects":[{"StartTime":106003.0,"Position":176.0,"HyperDash":false}]},{"StartTime":106185.0,"Objects":[{"StartTime":106185.0,"Position":64.0,"HyperDash":true}]},{"StartTime":106366.0,"Objects":[{"StartTime":106366.0,"Position":320.0,"HyperDash":false},{"StartTime":106456.0,"Position":360.0,"HyperDash":false},{"StartTime":106547.0,"Position":320.0,"HyperDash":false}]},{"StartTime":106730.0,"Objects":[{"StartTime":106730.0,"Position":144.0,"HyperDash":false}]},{"StartTime":106821.0,"Objects":[{"StartTime":106821.0,"Position":96.0,"HyperDash":false},{"StartTime":106911.0,"Position":96.0,"HyperDash":false}]},{"StartTime":107094.0,"Objects":[{"StartTime":107094.0,"Position":352.0,"HyperDash":false},{"StartTime":107166.0,"Position":366.241058,"HyperDash":false},{"StartTime":107275.0,"Position":367.6893,"HyperDash":false}]},{"StartTime":107366.0,"Objects":[{"StartTime":107366.0,"Position":320.0,"HyperDash":false}]},{"StartTime":107457.0,"Objects":[{"StartTime":107457.0,"Position":240.0,"HyperDash":false}]},{"StartTime":107639.0,"Objects":[{"StartTime":107639.0,"Position":352.0,"HyperDash":true}]},{"StartTime":107821.0,"Objects":[{"StartTime":107821.0,"Position":96.0,"HyperDash":false}]},{"StartTime":108003.0,"Objects":[{"StartTime":108003.0,"Position":16.0,"HyperDash":false},{"StartTime":108093.0,"Position":16.0,"HyperDash":false}]},{"StartTime":108185.0,"Objects":[{"StartTime":108185.0,"Position":96.0,"HyperDash":false}]},{"StartTime":108366.0,"Objects":[{"StartTime":108366.0,"Position":16.0,"HyperDash":true}]},{"StartTime":108548.0,"Objects":[{"StartTime":108548.0,"Position":272.0,"HyperDash":false}]},{"StartTime":108639.0,"Objects":[{"StartTime":108639.0,"Position":320.0,"HyperDash":false}]},{"StartTime":108730.0,"Objects":[{"StartTime":108730.0,"Position":272.0,"HyperDash":false}]},{"StartTime":108912.0,"Objects":[{"StartTime":108912.0,"Position":160.0,"HyperDash":false},{"StartTime":108984.0,"Position":184.823212,"HyperDash":false},{"StartTime":109093.0,"Position":240.0,"HyperDash":true}]},{"StartTime":109275.0,"Objects":[{"StartTime":109275.0,"Position":496.0,"HyperDash":false},{"StartTime":109365.0,"Position":496.0,"HyperDash":false}]},{"StartTime":109457.0,"Objects":[{"StartTime":109457.0,"Position":448.0,"HyperDash":false}]},{"StartTime":109639.0,"Objects":[{"StartTime":109639.0,"Position":272.0,"HyperDash":false},{"StartTime":109729.0,"Position":300.284271,"HyperDash":false}]},{"StartTime":109821.0,"Objects":[{"StartTime":109821.0,"Position":352.0,"HyperDash":true}]},{"StartTime":110003.0,"Objects":[{"StartTime":110003.0,"Position":96.0,"HyperDash":false}]},{"StartTime":110094.0,"Objects":[{"StartTime":110094.0,"Position":48.0,"HyperDash":false}]},{"StartTime":110185.0,"Objects":[{"StartTime":110185.0,"Position":96.0,"HyperDash":false}]},{"StartTime":110366.0,"Objects":[{"StartTime":110366.0,"Position":272.0,"HyperDash":false}]},{"StartTime":110457.0,"Objects":[{"StartTime":110457.0,"Position":224.0,"HyperDash":false}]},{"StartTime":110548.0,"Objects":[{"StartTime":110548.0,"Position":176.0,"HyperDash":true}]},{"StartTime":110730.0,"Objects":[{"StartTime":110730.0,"Position":448.0,"HyperDash":false},{"StartTime":110820.0,"Position":448.0,"HyperDash":false}]},{"StartTime":110912.0,"Objects":[{"StartTime":110912.0,"Position":400.0,"HyperDash":false}]},{"StartTime":111094.0,"Objects":[{"StartTime":111094.0,"Position":224.0,"HyperDash":false},{"StartTime":111166.0,"Position":193.176788,"HyperDash":false},{"StartTime":111275.0,"Position":144.0,"HyperDash":true}]},{"StartTime":111457.0,"Objects":[{"StartTime":111457.0,"Position":400.0,"HyperDash":false}]},{"StartTime":111639.0,"Objects":[{"StartTime":111639.0,"Position":480.0,"HyperDash":false}]},{"StartTime":111730.0,"Objects":[{"StartTime":111730.0,"Position":400.0,"HyperDash":false}]},{"StartTime":111821.0,"Objects":[{"StartTime":111821.0,"Position":352.0,"HyperDash":false}]},{"StartTime":112003.0,"Objects":[{"StartTime":112003.0,"Position":464.0,"HyperDash":true}]},{"StartTime":112185.0,"Objects":[{"StartTime":112185.0,"Position":208.0,"HyperDash":false},{"StartTime":112257.0,"Position":160.176788,"HyperDash":false},{"StartTime":112366.0,"Position":128.0,"HyperDash":false}]},{"StartTime":112548.0,"Objects":[{"StartTime":112548.0,"Position":304.0,"HyperDash":false},{"StartTime":112620.0,"Position":316.8232,"HyperDash":false},{"StartTime":112729.0,"Position":384.0,"HyperDash":false}]},{"StartTime":112912.0,"Objects":[{"StartTime":112912.0,"Position":128.0,"HyperDash":false},{"StartTime":112984.0,"Position":101.758957,"HyperDash":false},{"StartTime":113093.0,"Position":112.310707,"HyperDash":false}]},{"StartTime":113185.0,"Objects":[{"StartTime":113185.0,"Position":160.0,"HyperDash":false}]},{"StartTime":113275.0,"Objects":[{"StartTime":113275.0,"Position":240.0,"HyperDash":false}]},{"StartTime":113457.0,"Objects":[{"StartTime":113457.0,"Position":128.0,"HyperDash":true}]},{"StartTime":113639.0,"Objects":[{"StartTime":113639.0,"Position":384.0,"HyperDash":false}]},{"StartTime":113821.0,"Objects":[{"StartTime":113821.0,"Position":464.0,"HyperDash":false},{"StartTime":113911.0,"Position":464.0,"HyperDash":false}]},{"StartTime":114003.0,"Objects":[{"StartTime":114003.0,"Position":384.0,"HyperDash":false}]},{"StartTime":114185.0,"Objects":[{"StartTime":114185.0,"Position":464.0,"HyperDash":true}]},{"StartTime":114366.0,"Objects":[{"StartTime":114366.0,"Position":208.0,"HyperDash":false}]},{"StartTime":114548.0,"Objects":[{"StartTime":114548.0,"Position":128.0,"HyperDash":false}]},{"StartTime":114639.0,"Objects":[{"StartTime":114639.0,"Position":176.0,"HyperDash":false}]},{"StartTime":114730.0,"Objects":[{"StartTime":114730.0,"Position":256.0,"HyperDash":false}]},{"StartTime":114912.0,"Objects":[{"StartTime":114912.0,"Position":144.0,"HyperDash":true}]},{"StartTime":115094.0,"Objects":[{"StartTime":115094.0,"Position":400.0,"HyperDash":false}]},{"StartTime":115185.0,"Objects":[{"StartTime":115185.0,"Position":448.0,"HyperDash":false}]},{"StartTime":115275.0,"Objects":[{"StartTime":115275.0,"Position":400.0,"HyperDash":false}]},{"StartTime":115457.0,"Objects":[{"StartTime":115457.0,"Position":224.0,"HyperDash":false}]},{"StartTime":115548.0,"Objects":[{"StartTime":115548.0,"Position":176.0,"HyperDash":false},{"StartTime":115638.0,"Position":176.0,"HyperDash":false}]},{"StartTime":115821.0,"Objects":[{"StartTime":115821.0,"Position":432.0,"HyperDash":false},{"StartTime":115911.0,"Position":432.0,"HyperDash":false}]},{"StartTime":116003.0,"Objects":[{"StartTime":116003.0,"Position":384.0,"HyperDash":false}]},{"StartTime":116185.0,"Objects":[{"StartTime":116185.0,"Position":208.0,"HyperDash":false}]},{"StartTime":116275.0,"Objects":[{"StartTime":116275.0,"Position":256.0,"HyperDash":false}]},{"StartTime":116366.0,"Objects":[{"StartTime":116366.0,"Position":304.0,"HyperDash":true}]},{"StartTime":116548.0,"Objects":[{"StartTime":116548.0,"Position":48.0,"HyperDash":false}]},{"StartTime":116730.0,"Objects":[{"StartTime":116730.0,"Position":304.0,"HyperDash":false},{"StartTime":116815.0,"Position":221.0,"HyperDash":false},{"StartTime":116900.0,"Position":407.0,"HyperDash":false},{"StartTime":116985.0,"Position":287.0,"HyperDash":false},{"StartTime":117070.0,"Position":135.0,"HyperDash":false},{"StartTime":117156.0,"Position":437.0,"HyperDash":false},{"StartTime":117241.0,"Position":289.0,"HyperDash":false},{"StartTime":117326.0,"Position":464.0,"HyperDash":false},{"StartTime":117411.0,"Position":36.0,"HyperDash":false},{"StartTime":117496.0,"Position":378.0,"HyperDash":false},{"StartTime":117582.0,"Position":297.0,"HyperDash":false},{"StartTime":117667.0,"Position":418.0,"HyperDash":false},{"StartTime":117752.0,"Position":329.0,"HyperDash":false},{"StartTime":117837.0,"Position":338.0,"HyperDash":false},{"StartTime":117923.0,"Position":394.0,"HyperDash":false},{"StartTime":118008.0,"Position":40.0,"HyperDash":false},{"StartTime":118093.0,"Position":13.0,"HyperDash":false},{"StartTime":118178.0,"Position":80.0,"HyperDash":false},{"StartTime":118263.0,"Position":138.0,"HyperDash":false},{"StartTime":118349.0,"Position":311.0,"HyperDash":false},{"StartTime":118434.0,"Position":216.0,"HyperDash":false},{"StartTime":118519.0,"Position":310.0,"HyperDash":false},{"StartTime":118604.0,"Position":397.0,"HyperDash":false},{"StartTime":118690.0,"Position":214.0,"HyperDash":false},{"StartTime":118775.0,"Position":505.0,"HyperDash":false},{"StartTime":118860.0,"Position":173.0,"HyperDash":false},{"StartTime":118945.0,"Position":295.0,"HyperDash":false},{"StartTime":119030.0,"Position":199.0,"HyperDash":false},{"StartTime":119116.0,"Position":494.0,"HyperDash":false},{"StartTime":119201.0,"Position":293.0,"HyperDash":false},{"StartTime":119286.0,"Position":115.0,"HyperDash":false},{"StartTime":119371.0,"Position":412.0,"HyperDash":false},{"StartTime":119457.0,"Position":506.0,"HyperDash":false}]},{"StartTime":122366.0,"Objects":[{"StartTime":122366.0,"Position":312.0,"HyperDash":false},{"StartTime":122438.0,"Position":320.241058,"HyperDash":false},{"StartTime":122547.0,"Position":327.6893,"HyperDash":false}]},{"StartTime":122730.0,"Objects":[{"StartTime":122730.0,"Position":232.0,"HyperDash":false},{"StartTime":122802.0,"Position":233.241043,"HyperDash":false},{"StartTime":122911.0,"Position":247.689285,"HyperDash":false}]},{"StartTime":123275.0,"Objects":[{"StartTime":123275.0,"Position":408.0,"HyperDash":false}]},{"StartTime":123457.0,"Objects":[{"StartTime":123457.0,"Position":328.0,"HyperDash":false}]},{"StartTime":123821.0,"Objects":[{"StartTime":123821.0,"Position":168.0,"HyperDash":false},{"StartTime":123893.0,"Position":147.758957,"HyperDash":false},{"StartTime":124002.0,"Position":152.310715,"HyperDash":false}]},{"StartTime":124185.0,"Objects":[{"StartTime":124185.0,"Position":256.0,"HyperDash":false},{"StartTime":124257.0,"Position":241.758957,"HyperDash":false},{"StartTime":124366.0,"Position":240.310715,"HyperDash":false}]},{"StartTime":124730.0,"Objects":[{"StartTime":124730.0,"Position":64.0,"HyperDash":false}]},{"StartTime":124912.0,"Objects":[{"StartTime":124912.0,"Position":152.0,"HyperDash":false}]},{"StartTime":125275.0,"Objects":[{"StartTime":125275.0,"Position":336.0,"HyperDash":false},{"StartTime":125347.0,"Position":349.241058,"HyperDash":false},{"StartTime":125456.0,"Position":351.6893,"HyperDash":false}]},{"StartTime":125639.0,"Objects":[{"StartTime":125639.0,"Position":240.0,"HyperDash":false},{"StartTime":125711.0,"Position":260.241028,"HyperDash":false},{"StartTime":125820.0,"Position":255.689285,"HyperDash":false}]},{"StartTime":126185.0,"Objects":[{"StartTime":126185.0,"Position":448.0,"HyperDash":false}]},{"StartTime":126366.0,"Objects":[{"StartTime":126366.0,"Position":352.0,"HyperDash":false}]},{"StartTime":126730.0,"Objects":[{"StartTime":126730.0,"Position":144.0,"HyperDash":false},{"StartTime":126802.0,"Position":130.758957,"HyperDash":false},{"StartTime":126911.0,"Position":128.310715,"HyperDash":false}]},{"StartTime":127094.0,"Objects":[{"StartTime":127094.0,"Position":248.0,"HyperDash":false},{"StartTime":127166.0,"Position":256.758972,"HyperDash":false},{"StartTime":127275.0,"Position":232.310715,"HyperDash":false}]},{"StartTime":127457.0,"Objects":[{"StartTime":127457.0,"Position":112.0,"HyperDash":false},{"StartTime":127529.0,"Position":132.823212,"HyperDash":false},{"StartTime":127638.0,"Position":192.0,"HyperDash":false}]},{"StartTime":127821.0,"Objects":[{"StartTime":127821.0,"Position":48.0,"HyperDash":false}]},{"StartTime":128003.0,"Objects":[{"StartTime":128003.0,"Position":192.0,"HyperDash":false}]},{"StartTime":128185.0,"Objects":[{"StartTime":128185.0,"Position":368.0,"HyperDash":false}]},{"StartTime":128366.0,"Objects":[{"StartTime":128366.0,"Position":288.0,"HyperDash":false},{"StartTime":128456.0,"Position":248.0,"HyperDash":false},{"StartTime":128547.0,"Position":288.0,"HyperDash":false}]},{"StartTime":128730.0,"Objects":[{"StartTime":128730.0,"Position":368.0,"HyperDash":false}]},{"StartTime":128912.0,"Objects":[{"StartTime":128912.0,"Position":496.0,"HyperDash":false},{"StartTime":129002.0,"Position":496.0,"HyperDash":false}]},{"StartTime":129094.0,"Objects":[{"StartTime":129094.0,"Position":448.0,"HyperDash":false}]},{"StartTime":129275.0,"Objects":[{"StartTime":129275.0,"Position":368.0,"HyperDash":false}]},{"StartTime":129457.0,"Objects":[{"StartTime":129457.0,"Position":448.0,"HyperDash":false}]},{"StartTime":129639.0,"Objects":[{"StartTime":129639.0,"Position":272.0,"HyperDash":false},{"StartTime":129729.0,"Position":272.0,"HyperDash":false}]},{"StartTime":129821.0,"Objects":[{"StartTime":129821.0,"Position":320.0,"HyperDash":false}]},{"StartTime":130003.0,"Objects":[{"StartTime":130003.0,"Position":432.0,"HyperDash":false}]},{"StartTime":130094.0,"Objects":[{"StartTime":130094.0,"Position":384.0,"HyperDash":false}]},{"StartTime":130185.0,"Objects":[{"StartTime":130185.0,"Position":336.0,"HyperDash":false}]},{"StartTime":130366.0,"Objects":[{"StartTime":130366.0,"Position":448.0,"HyperDash":false},{"StartTime":130456.0,"Position":448.0,"HyperDash":false}]},{"StartTime":130548.0,"Objects":[{"StartTime":130548.0,"Position":400.0,"HyperDash":false}]},{"StartTime":130730.0,"Objects":[{"StartTime":130730.0,"Position":288.0,"HyperDash":false},{"StartTime":130802.0,"Position":307.8232,"HyperDash":false},{"StartTime":130911.0,"Position":368.0,"HyperDash":false}]},{"StartTime":131094.0,"Objects":[{"StartTime":131094.0,"Position":192.0,"HyperDash":false}]},{"StartTime":131275.0,"Objects":[{"StartTime":131275.0,"Position":112.0,"HyperDash":false},{"StartTime":131365.0,"Position":112.0,"HyperDash":false}]},{"StartTime":131457.0,"Objects":[{"StartTime":131457.0,"Position":160.0,"HyperDash":false}]},{"StartTime":131639.0,"Objects":[{"StartTime":131639.0,"Position":80.0,"HyperDash":false}]},{"StartTime":131821.0,"Objects":[{"StartTime":131821.0,"Position":192.0,"HyperDash":false},{"StartTime":131911.0,"Position":192.0,"HyperDash":false}]},{"StartTime":132003.0,"Objects":[{"StartTime":132003.0,"Position":144.0,"HyperDash":false}]},{"StartTime":132185.0,"Objects":[{"StartTime":132185.0,"Position":64.0,"HyperDash":false}]},{"StartTime":132366.0,"Objects":[{"StartTime":132366.0,"Position":144.0,"HyperDash":false}]},{"StartTime":132548.0,"Objects":[{"StartTime":132548.0,"Position":320.0,"HyperDash":false}]},{"StartTime":132730.0,"Objects":[{"StartTime":132730.0,"Position":240.0,"HyperDash":false}]},{"StartTime":132912.0,"Objects":[{"StartTime":132912.0,"Position":320.0,"HyperDash":false},{"StartTime":133002.0,"Position":360.0,"HyperDash":false},{"StartTime":133093.0,"Position":320.0,"HyperDash":false}]},{"StartTime":133275.0,"Objects":[{"StartTime":133275.0,"Position":208.0,"HyperDash":false},{"StartTime":133347.0,"Position":254.823212,"HyperDash":false},{"StartTime":133456.0,"Position":288.0,"HyperDash":false}]},{"StartTime":133639.0,"Objects":[{"StartTime":133639.0,"Position":400.0,"HyperDash":false},{"StartTime":133711.0,"Position":377.1768,"HyperDash":false},{"StartTime":133820.0,"Position":320.0,"HyperDash":false}]},{"StartTime":134003.0,"Objects":[{"StartTime":134003.0,"Position":144.0,"HyperDash":false}]},{"StartTime":134185.0,"Objects":[{"StartTime":134185.0,"Position":224.0,"HyperDash":false},{"StartTime":134275.0,"Position":264.0,"HyperDash":false},{"StartTime":134366.0,"Position":224.0,"HyperDash":false}]},{"StartTime":134548.0,"Objects":[{"StartTime":134548.0,"Position":144.0,"HyperDash":false}]},{"StartTime":134730.0,"Objects":[{"StartTime":134730.0,"Position":64.0,"HyperDash":false}]},{"StartTime":134912.0,"Objects":[{"StartTime":134912.0,"Position":144.0,"HyperDash":false},{"StartTime":135002.0,"Position":184.0,"HyperDash":false},{"StartTime":135093.0,"Position":144.0,"HyperDash":false}]},{"StartTime":135275.0,"Objects":[{"StartTime":135275.0,"Position":64.0,"HyperDash":false}]},{"StartTime":135457.0,"Objects":[{"StartTime":135457.0,"Position":240.0,"HyperDash":false},{"StartTime":135547.0,"Position":240.0,"HyperDash":false}]},{"StartTime":135639.0,"Objects":[{"StartTime":135639.0,"Position":192.0,"HyperDash":false}]},{"StartTime":135821.0,"Objects":[{"StartTime":135821.0,"Position":80.0,"HyperDash":false}]},{"StartTime":135912.0,"Objects":[{"StartTime":135912.0,"Position":128.0,"HyperDash":false}]},{"StartTime":136003.0,"Objects":[{"StartTime":136003.0,"Position":176.0,"HyperDash":false}]},{"StartTime":136185.0,"Objects":[{"StartTime":136185.0,"Position":288.0,"HyperDash":false}]},{"StartTime":136275.0,"Objects":[{"StartTime":136275.0,"Position":240.0,"HyperDash":false}]},{"StartTime":136366.0,"Objects":[{"StartTime":136366.0,"Position":192.0,"HyperDash":false}]},{"StartTime":136548.0,"Objects":[{"StartTime":136548.0,"Position":80.0,"HyperDash":false}]},{"StartTime":136730.0,"Objects":[{"StartTime":136730.0,"Position":192.0,"HyperDash":false}]},{"StartTime":136912.0,"Objects":[{"StartTime":136912.0,"Position":368.0,"HyperDash":false}]},{"StartTime":137094.0,"Objects":[{"StartTime":137094.0,"Position":448.0,"HyperDash":false},{"StartTime":137184.0,"Position":448.0,"HyperDash":false}]},{"StartTime":137275.0,"Objects":[{"StartTime":137275.0,"Position":400.0,"HyperDash":false}]},{"StartTime":137457.0,"Objects":[{"StartTime":137457.0,"Position":320.0,"HyperDash":false}]},{"StartTime":137639.0,"Objects":[{"StartTime":137639.0,"Position":432.0,"HyperDash":false},{"StartTime":137729.0,"Position":419.3509,"HyperDash":false}]},{"StartTime":137821.0,"Objects":[{"StartTime":137821.0,"Position":368.0,"HyperDash":false}]},{"StartTime":138003.0,"Objects":[{"StartTime":138003.0,"Position":288.0,"HyperDash":false}]},{"StartTime":138185.0,"Objects":[{"StartTime":138185.0,"Position":368.0,"HyperDash":false}]},{"StartTime":138366.0,"Objects":[{"StartTime":138366.0,"Position":192.0,"HyperDash":false}]},{"StartTime":138548.0,"Objects":[{"StartTime":138548.0,"Position":112.0,"HyperDash":false},{"StartTime":138638.0,"Position":104.155357,"HyperDash":false}]},{"StartTime":138821.0,"Objects":[{"StartTime":138821.0,"Position":216.0,"HyperDash":false},{"StartTime":138911.0,"Position":256.0,"HyperDash":false}]},{"StartTime":139094.0,"Objects":[{"StartTime":139094.0,"Position":144.0,"HyperDash":false}]},{"StartTime":139275.0,"Objects":[{"StartTime":139275.0,"Position":224.0,"HyperDash":false}]},{"StartTime":139457.0,"Objects":[{"StartTime":139457.0,"Position":48.0,"HyperDash":false}]},{"StartTime":139639.0,"Objects":[{"StartTime":139639.0,"Position":160.0,"HyperDash":true}]},{"StartTime":139821.0,"Objects":[{"StartTime":139821.0,"Position":416.0,"HyperDash":false}]},{"StartTime":140003.0,"Objects":[{"StartTime":140003.0,"Position":285.0,"HyperDash":false},{"StartTime":140056.0,"Position":17.0,"HyperDash":false},{"StartTime":140110.0,"Position":238.0,"HyperDash":false},{"StartTime":140164.0,"Position":222.0,"HyperDash":false},{"StartTime":140218.0,"Position":450.0,"HyperDash":false},{"StartTime":140272.0,"Position":67.0,"HyperDash":false},{"StartTime":140326.0,"Position":219.0,"HyperDash":false},{"StartTime":140380.0,"Position":307.0,"HyperDash":false},{"StartTime":140434.0,"Position":367.0,"HyperDash":false},{"StartTime":140488.0,"Position":412.0,"HyperDash":false},{"StartTime":140542.0,"Position":413.0,"HyperDash":false},{"StartTime":140596.0,"Position":143.0,"HyperDash":false},{"StartTime":140650.0,"Position":339.0,"HyperDash":false},{"StartTime":140704.0,"Position":342.0,"HyperDash":false},{"StartTime":140758.0,"Position":249.0,"HyperDash":false},{"StartTime":140812.0,"Position":235.0,"HyperDash":false},{"StartTime":140866.0,"Position":323.0,"HyperDash":false},{"StartTime":140920.0,"Position":365.0,"HyperDash":false},{"StartTime":140974.0,"Position":74.0,"HyperDash":false},{"StartTime":141028.0,"Position":281.0,"HyperDash":false},{"StartTime":141082.0,"Position":398.0,"HyperDash":false},{"StartTime":141136.0,"Position":335.0,"HyperDash":false},{"StartTime":141190.0,"Position":388.0,"HyperDash":false},{"StartTime":141244.0,"Position":228.0,"HyperDash":false},{"StartTime":141298.0,"Position":323.0,"HyperDash":false},{"StartTime":141352.0,"Position":441.0,"HyperDash":false},{"StartTime":141406.0,"Position":442.0,"HyperDash":false},{"StartTime":141460.0,"Position":278.0,"HyperDash":false},{"StartTime":141514.0,"Position":90.0,"HyperDash":false},{"StartTime":141568.0,"Position":409.0,"HyperDash":false},{"StartTime":141622.0,"Position":377.0,"HyperDash":false},{"StartTime":141676.0,"Position":457.0,"HyperDash":false},{"StartTime":141730.0,"Position":409.0,"HyperDash":false},{"StartTime":141783.0,"Position":43.0,"HyperDash":false},{"StartTime":141837.0,"Position":162.0,"HyperDash":false},{"StartTime":141891.0,"Position":341.0,"HyperDash":false},{"StartTime":141945.0,"Position":72.0,"HyperDash":false},{"StartTime":141999.0,"Position":135.0,"HyperDash":false},{"StartTime":142053.0,"Position":252.0,"HyperDash":false},{"StartTime":142107.0,"Position":446.0,"HyperDash":false},{"StartTime":142161.0,"Position":284.0,"HyperDash":false},{"StartTime":142215.0,"Position":70.0,"HyperDash":false},{"StartTime":142269.0,"Position":494.0,"HyperDash":false},{"StartTime":142323.0,"Position":463.0,"HyperDash":false},{"StartTime":142377.0,"Position":277.0,"HyperDash":false},{"StartTime":142431.0,"Position":425.0,"HyperDash":false},{"StartTime":142485.0,"Position":281.0,"HyperDash":false},{"StartTime":142539.0,"Position":3.0,"HyperDash":false},{"StartTime":142593.0,"Position":346.0,"HyperDash":false},{"StartTime":142647.0,"Position":350.0,"HyperDash":false},{"StartTime":142701.0,"Position":217.0,"HyperDash":false},{"StartTime":142755.0,"Position":455.0,"HyperDash":false},{"StartTime":142809.0,"Position":229.0,"HyperDash":false},{"StartTime":142863.0,"Position":51.0,"HyperDash":false},{"StartTime":142917.0,"Position":199.0,"HyperDash":false},{"StartTime":142971.0,"Position":208.0,"HyperDash":false},{"StartTime":143025.0,"Position":173.0,"HyperDash":false},{"StartTime":143079.0,"Position":367.0,"HyperDash":false},{"StartTime":143133.0,"Position":193.0,"HyperDash":false},{"StartTime":143187.0,"Position":488.0,"HyperDash":false},{"StartTime":143241.0,"Position":314.0,"HyperDash":false},{"StartTime":143295.0,"Position":135.0,"HyperDash":false},{"StartTime":143349.0,"Position":399.0,"HyperDash":false},{"StartTime":143403.0,"Position":404.0,"HyperDash":false},{"StartTime":143457.0,"Position":152.0,"HyperDash":false}]}]} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3949367.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3949367.osu new file mode 100644 index 0000000000..19fab1c61c --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/3949367.osu @@ -0,0 +1,832 @@ +osu file format v14 + +[General] +StackLeniency: 0.7 +Mode: 2 + +[Difficulty] +HPDrainRate:4 +CircleSize:3.5 +OverallDifficulty:8 +ApproachRate:8 +SliderMultiplier:1.6 +SliderTickRate:2 + +[Events] +//Background and Video events +//Break Periods +2,49839,51798 +2,70203,75071 +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Layer 4 (Overlay) +//Storyboard Sound Samples + +[TimingPoints] +185,363.636363636364,4,2,0,50,1,0 +7457,-100,4,2,0,55,0,0 +8912,-100,4,2,0,60,0,0 +10366,-100,4,2,0,65,0,0 +10730,-100,4,2,0,70,0,0 +11094,-100,4,2,0,75,0,0 +11457,-100,4,2,0,80,0,0 +11821,-100,4,1,1,80,0,0 +17821,-100,4,1,0,70,0,0 +17912,-100,4,1,1,80,0,0 +18003,-100,4,1,0,70,0,0 +18094,-100,4,1,1,80,0,0 +18366,-100,4,1,0,70,0,0 +18457,-100,4,1,1,80,0,0 +19275,-100,4,1,0,70,0,0 +19366,-100,4,1,1,80,0,0 +19457,-100,4,1,0,70,0,0 +19548,-100,4,1,1,80,0,0 +19821,-100,4,1,0,70,0,0 +19912,-100,4,1,1,80,0,0 +20639,-100,4,1,0,70,0,0 +21094,-100,4,1,1,80,0,0 +21185,-100,4,1,0,70,0,0 +21457,-100,4,1,1,80,0,0 +21548,-100,4,1,0,70,0,0 +21639,-100,4,1,1,80,0,0 +22094,-100,4,1,0,70,0,0 +22548,-100,4,1,1,80,0,0 +22639,-100,4,1,0,70,0,0 +22730,-100,4,2,0,60,0,0 +22912,-100,4,2,0,30,0,0 +23094,-100,4,2,0,60,0,0 +23276,-100,4,2,0,30,0,0 +23457,-100,4,3,1,80,0,0 +24185,-100,4,3,2,80,0,0 +24275,-100,4,3,1,80,0,0 +25275,-100,4,3,1,80,0,0 +25639,-100,4,3,2,80,0,0 +25730,-100,4,3,1,80,0,0 +26003,-100,4,3,1,80,0,0 +27094,-100,4,3,2,80,0,0 +27184,-100,4,3,1,80,0,0 +28548,-100,4,3,2,80,0,0 +28638,-100,4,3,1,80,0,0 +30003,-100,4,3,2,80,0,0 +30093,-100,4,3,1,80,0,0 +31094,-100,4,3,1,80,0,0 +31457,-100,4,3,2,80,0,0 +31548,-100,4,3,1,80,0,0 +31821,-100,4,3,1,80,0,0 +32185,-100,4,3,1,80,0,0 +32912,-100,4,3,2,80,0,0 +33002,-100,4,3,1,80,0,0 +34366,-100,4,3,2,80,0,0 +34456,-100,4,3,1,80,0,0 +35094,-100,4,3,3,90,0,1 +35821,-100,4,1,0,80,0,1 +35912,-100,4,3,3,90,0,1 +37275,-100,4,1,0,80,0,1 +37366,-100,4,3,3,90,0,1 +38730,-100,4,1,0,80,0,1 +38821,-100,4,3,3,90,0,1 +40185,-100,4,1,0,80,0,1 +40366,-100,4,3,3,90,0,1 +40457,-100,4,1,0,80,0,1 +40548,-100,4,3,3,90,0,1 +40639,-100,4,1,0,80,0,1 +40730,-100,4,3,3,90,0,1 +40821,-100,4,1,0,80,0,1 +40912,-100,4,3,3,90,0,1 +41639,-100,4,1,0,80,0,1 +41730,-100,4,3,3,90,0,1 +43093,-100,4,1,0,80,0,1 +43184,-100,4,3,3,90,0,1 +43457,-100,4,3,3,90,0,1 +43639,-100,4,3,3,90,0,1 +44548,-100,4,1,0,80,0,1 +44639,-100,4,3,3,90,0,1 +46003,-100,4,1,0,80,0,1 +46184,-100,4,3,3,90,0,1 +46275,-100,4,1,0,80,0,1 +46366,-100,4,3,3,90,0,1 +46457,-100,4,1,0,80,0,1 +46548,-100,4,3,3,90,0,1 +46639,-100,4,1,0,80,0,1 +46730,-100,4,1,2,80,0,0 +46912,-100,4,2,0,50,0,0 +51094,-100,4,2,0,55,0,0 +52548,-100,4,2,0,60,0,0 +54003,-100,4,2,0,65,0,0 +55457,-100,4,2,0,70,0,0 +56912,-100,4,2,0,75,0,0 +57639,-100,4,2,0,80,0,0 +58003,-100,4,2,0,85,0,0 +58366,-100,4,1,1,80,0,0 +58548,-100,4,2,1,60,0,0 +69275,-100,4,2,1,70,0,0 +69639,-100,4,2,1,80,0,0 +70003,-100,4,1,1,80,0,0 +70185,-100,4,2,1,50,0,0 +70548,-100,4,3,0,50,0,0 +71457,-100,4,2,1,50,0,0 +72003,-100,4,3,0,50,0,0 +72912,-100,4,2,1,60,0,0 +73457,-100,4,3,0,50,0,0 +74366,-100,4,2,1,60,0,0 +74912,-100,4,3,0,50,0,0 +75094,-100,4,2,1,60,0,0 +75275,-100,4,3,0,50,0,0 +75457,-100,4,2,1,60,0,0 +75639,-100,4,3,0,50,0,0 +75821,-100,4,2,1,70,0,0 +76003,-100,4,3,0,50,0,0 +76185,-100,4,2,1,70,0,0 +76366,-100,4,3,0,50,0,0 +76548,-100,4,2,1,70,0,0 +76730,-100,4,3,0,50,0,0 +76912,-100,4,2,1,70,0,0 +77094,-100,4,3,0,50,0,0 +77275,-100,4,2,1,70,0,0 +77457,-100,4,3,0,50,0,0 +77639,-100,4,2,1,70,0,0 +77820,-100,4,3,0,50,0,0 +78002,-100,4,2,1,70,0,0 +78184,-100,4,3,0,50,0,0 +78366,-100,4,2,1,70,0,0 +78548,-100,4,3,0,50,0,0 +78730,-100,4,2,1,75,0,0 +78912,-100,4,3,0,50,0,0 +79094,-100,4,2,1,75,0,0 +79275,-100,4,3,0,50,0,0 +79457,-100,4,2,1,75,0,0 +79639,-100,4,3,0,50,0,0 +79821,-100,4,2,1,75,0,0 +80003,-100,4,3,0,50,0,0 +80185,-100,4,2,1,75,0,0 +80367,-100,4,3,0,50,0,0 +80549,-100,4,2,1,75,0,0 +80730,-100,4,3,0,50,0,0 +80912,-100,4,2,1,80,0,0 +81094,-100,4,3,0,50,0,0 +81276,-100,4,2,1,80,0,0 +81458,-100,4,3,0,50,0,0 +81639,-100,4,1,1,80,0,0 +87639,-100,4,1,0,70,0,0 +87730,-100,4,1,1,80,0,0 +87821,-100,4,1,0,70,0,0 +87912,-100,4,1,1,80,0,0 +88184,-100,4,1,0,70,0,0 +88275,-100,4,1,1,80,0,0 +89093,-100,4,1,0,70,0,0 +89184,-100,4,1,1,80,0,0 +89275,-100,4,1,0,70,0,0 +89366,-100,4,1,1,80,0,0 +89639,-100,4,1,0,70,0,0 +89730,-100,4,1,1,80,0,0 +90457,-100,4,1,0,70,0,0 +90912,-100,4,1,1,80,0,0 +91003,-100,4,1,0,70,0,0 +91275,-100,4,1,1,80,0,0 +91366,-100,4,1,0,70,0,0 +91457,-100,4,1,1,80,0,0 +91912,-100,4,1,0,70,0,0 +92366,-100,4,1,1,80,0,0 +92457,-100,4,1,0,70,0,0 +92548,-100,4,2,0,60,0,0 +92594,-100,4,2,0,30,0,0 +92730,-100,4,2,0,60,0,0 +92776,-100,4,2,0,30,0,0 +92912,-100,4,2,0,60,0,0 +92958,-100,4,2,0,30,0,0 +93094,-100,4,2,0,60,0,0 +93140,-100,4,2,0,30,0,0 +93275,-100,4,3,1,80,0,0 +94003,-100,4,3,2,80,0,0 +94093,-100,4,3,1,80,0,0 +95094,-100,4,3,1,80,0,0 +95457,-100,4,3,2,80,0,0 +95548,-100,4,3,1,80,0,0 +95821,-100,4,3,1,80,0,0 +96912,-100,4,3,2,80,0,0 +97002,-100,4,3,1,80,0,0 +98366,-80,4,3,2,80,0,0 +98456,-80,4,3,1,80,0,0 +99094,-100,4,3,1,80,0,0 +99821,-100,4,3,2,80,0,0 +99911,-100,4,3,1,80,0,0 +100912,-100,4,3,1,80,0,0 +101275,-100,4,3,2,80,0,0 +101366,-100,4,3,1,80,0,0 +101639,-100,4,3,1,80,0,0 +102003,-100,4,3,1,80,0,0 +102730,-100,4,3,2,80,0,0 +102820,-100,4,3,1,80,0,0 +104184,-100,4,3,2,80,0,0 +104274,-100,4,3,1,80,0,0 +104912,-100,4,3,3,90,0,1 +105639,-100,4,1,0,80,0,1 +105730,-100,4,3,3,90,0,1 +107093,-100,4,1,0,80,0,1 +107184,-100,4,3,3,90,0,1 +108548,-100,4,1,0,80,0,1 +108639,-100,4,3,3,90,0,1 +110003,-100,4,1,0,80,0,1 +110184,-100,4,3,3,90,0,1 +110275,-100,4,1,0,80,0,1 +110366,-100,4,3,3,90,0,1 +110457,-100,4,1,0,80,0,1 +110548,-100,4,3,3,90,0,1 +110639,-100,4,1,0,80,0,1 +110730,-100,4,3,3,90,0,1 +111457,-100,4,1,0,80,0,1 +111548,-100,4,3,3,90,0,1 +112911,-100,4,1,0,80,0,1 +113002,-100,4,3,3,90,0,1 +113275,-100,4,3,3,90,0,1 +113457,-100,4,3,3,90,0,1 +114366,-100,4,1,0,80,0,1 +114457,-100,4,3,3,90,0,1 +115821,-100,4,1,0,80,0,1 +116002,-100,4,3,3,90,0,1 +116093,-100,4,1,0,80,0,1 +116184,-100,4,3,3,90,0,1 +116275,-100,4,1,0,80,0,1 +116366,-100,4,3,3,90,0,1 +116457,-100,4,1,0,80,0,1 +116548,-100,4,1,2,80,0,0 +116730,-100,4,2,0,50,0,0 +120912,-100,4,2,0,55,0,0 +122366,-100,4,2,0,60,0,0 +123821,-100,4,2,0,65,0,0 +125275,-100,4,2,0,70,0,0 +126730,-100,4,2,0,75,0,0 +127457,-100,4,2,0,80,0,0 +127821,-100,4,2,0,85,0,0 +128184,-100,4,1,1,80,0,0 +128366,-100,4,2,1,60,0,0 +139093,-100,4,2,1,70,0,0 +139457,-100,4,2,1,80,0,0 +139821,-100,4,1,1,80,0,0 +140003,-100,4,2,1,50,0,0 +140548,-100,4,2,1,45,0,0 +140912,-100,4,2,1,40,0,0 +141275,-100,4,2,1,35,0,0 +141639,-100,4,2,1,30,0,0 +142003,-100,4,2,1,25,0,0 +142366,-100,4,2,1,20,0,0 +142730,-100,4,2,1,15,0,0 +143094,-100,4,2,1,10,0,0 +143457,-100,4,2,1,5,0,0 + +[HitObjects] +64,192,6003,5,2,0:0:0:0: +192,192,6366,1,0,0:0:0:0: +64,192,6730,1,2,0:0:0:0: +192,192,7094,1,0,0:0:0:0: +320,192,7457,5,2,0:0:0:0: +192,192,7821,1,0,0:0:0:0: +320,192,8185,1,2,0:0:0:0: +192,192,8548,1,0,0:0:0:0: +320,192,8912,5,2,0:0:0:0: +448,192,9275,1,0,0:0:0:0: +320,192,9639,1,2,0:0:0:0: +448,192,10003,1,0,0:0:0:0: +256,192,10366,12,2,11457,0:0:0:0: +96,192,11821,5,14,0:0:0:0: +176,192,12003,2,0,L|208:160,2,40 +64,192,12366,1,2,0:0:0:0: +224,192,12730,2,0,L|252:220,2,40,2|0|2,0:0|0:0|0:0,0:0:0:0: +144,192,13094,1,2,0:0:0:0: +320,192,13275,5,10,0:0:0:0: +368,192,13366,1,0,0:0:0:0: +320,192,13457,1,0,0:0:0:0: +208,192,13639,1,0,0:0:0:0: +160,192,13730,2,0,L|160:144,1,40,0|2,0:0|0:0,0:0:0:0: +240,112,14003,1,0,0:0:0:0: +128,112,14185,1,2,0:0:0:0: +208,112,14366,2,0,L|208:160,1,40,2|0,0:0|0:0,0:0:0:0: +160,192,14548,1,2,0:0:0:0: +336,192,14730,5,10,0:0:0:0: +256,192,14912,2,0,L|224:224,2,40,0|0|0,0:0|0:0|0:0,0:0:0:0: +368,192,15275,1,2,0:0:0:0: +208,192,15639,2,0,L|180:164,2,40,2|0|2,0:0|0:0|0:0,0:0:0:0: +288,192,16003,1,2,0:0:0:0: +112,192,16185,5,10,0:0:0:0: +64,192,16275,1,0,0:0:0:0: +112,192,16366,1,0,0:0:0:0: +224,192,16548,1,0,0:0:0:0: +272,192,16639,2,0,L|272:240,1,40,0|2,0:0|0:0,0:0:0:0: +160,192,16912,1,0,0:0:0:0: +208,192,17003,1,0,0:0:0:0: +256,192,17094,1,2,0:0:0:0: +144,112,17275,1,2,0:0:0:0: +80,112,17366,1,0,0:0:0:0: +144,112,17457,1,2,0:0:0:0: +320,112,17639,5,10,0:0:0:0: +400,112,17821,1,0,0:0:0:0: +352,112,17912,1,0,0:0:0:0: +304,112,18003,1,0,0:0:0:0: +416,112,18185,2,0,B|432:192|432:192|352:192,1,160,2|2,0:0|0:0,0:0:0:0: +400,192,18639,1,0,0:0:0:0: +448,192,18730,1,2,0:0:0:0: +368,192,18912,1,2,0:0:0:0: +192,192,19094,5,10,0:0:0:0: +144,192,19185,1,0,0:0:0:0: +192,192,19275,1,0,0:0:0:0: +304,192,19457,1,0,0:0:0:0: +352,192,19548,2,0,L|352:240,1,40,0|2,0:0|0:0,0:0:0:0: +272,272,19821,1,0,0:0:0:0: +384,272,20003,1,2,0:0:0:0: +304,272,20185,2,0,L|304:224,1,40,2|0,0:0|0:0,0:0:0:0: +352,192,20366,1,2,0:0:0:0: +176,272,20548,5,10,0:0:0:0: +96,272,20730,1,0,0:0:0:0: +144,272,20821,1,0,0:0:0:0: +192,272,20912,1,0,0:0:0:0: +80,272,21094,2,0,B|64:192|64:192|144:192,1,160,2|2,0:0|0:0,0:0:0:0: +96,192,21548,1,0,0:0:0:0: +48,192,21639,1,2,0:0:0:0: +128,192,21821,1,2,0:0:0:0: +304,192,22003,5,10,0:0:0:0: +352,192,22094,1,0,0:0:0:0: +304,192,22185,1,0,0:0:0:0: +192,192,22366,1,0,0:0:0:0: +144,192,22457,2,0,L|144:144,1,40,0|2,0:0|0:0,0:0:0:0: +224,80,22730,6,0,B|144:80|144:80|224:80|224:80|144:80,1,240,2|2,0:0|0:0,0:0:0:0: +400,80,23457,5,12,0:0:0:0: +480,80,23639,2,0,L|480:128,1,40,2|0,0:0|0:0,0:0:0:0: +432,144,23821,1,2,0:0:0:0: +320,144,24003,1,8,0:0:0:0: +64,192,24185,2,0,L|48:112,1,80,10|2,0:0|0:0,0:0:0:0: +96,96,24457,1,0,0:0:0:0: +144,80,24548,1,0,0:0:0:0: +64,80,24730,1,2,0:0:0:0: +240,80,24912,5,8,0:0:0:0: +320,80,25094,2,0,L|368:80,2,40,2|0|2,0:0|0:0|0:0,0:0:0:0: +208,80,25457,1,8,0:0:0:0: +464,192,25639,2,0,L|448:112,1,80,8|2,0:0|2:0,0:0:0:0: +336,192,26003,2,0,L|320:112,1,80,2|2,0:0|2:0,0:0:0:0: +496,48,26366,5,8,0:0:0:0: +416,48,26548,2,0,L|416:96,1,40,2|0,0:0|0:0,0:0:0:0: +464,128,26730,1,2,0:0:0:0: +352,128,26912,1,8,0:0:0:0: +96,128,27094,2,0,L|80:48,1,80,10|2,0:0|0:0,0:0:0:0: +192,48,27457,1,0,0:0:0:0: +240,48,27548,2,0,L|240:96,1,40,0|2,0:0|0:0,0:0:0:0: +64,192,27821,5,8,0:0:0:0: +176,192,28003,1,2,0:0:0:0: +64,192,28185,1,2,0:0:0:0: +16,192,28275,2,0,L|16:144,1,40,0|8,0:0|0:0,0:0:0:0: +272,192,28548,2,0,L|352:192,1,80,10|10,0:0|0:0,0:0:0:0: +240,128,28912,2,0,L|160:128,1,80,8|10,0:0|0:0,0:0:0:0: +416,128,29275,5,12,0:0:0:0: +496,128,29457,2,0,L|496:80,1,40,2|0,0:0|0:0,0:0:0:0: +448,64,29639,1,2,0:0:0:0: +336,64,29821,1,8,0:0:0:0: +80,192,30003,2,0,L|32:128,1,80,10|2,0:0|0:0,0:0:0:0: +32,80,30275,1,0,0:0:0:0: +64,80,30366,1,0,0:0:0:0: +144,80,30548,1,2,0:0:0:0: +320,80,30730,5,8,0:0:0:0: +240,80,30912,2,0,L|192:80,2,40,2|0|2,0:0|0:0|0:0,0:0:0:0: +352,80,31275,1,8,0:0:0:0: +96,192,31457,2,0,L|80:112,1,80,8|2,0:0|2:0,0:0:0:0: +192,112,31821,1,2,0:0:0:0: +80,112,32003,1,2,2:0:0:0: +256,112,32185,5,8,0:0:0:0: +336,112,32366,2,0,L|336:160,1,40,2|0,0:0|0:0,0:0:0:0: +288,192,32548,1,2,0:0:0:0: +400,192,32730,1,8,0:0:0:0: +144,192,32912,2,0,L|128:112,1,80,10|2,0:0|0:0,0:0:0:0: +240,112,33275,1,0,0:0:0:0: +288,112,33366,1,0,0:0:0:0: +240,112,33457,1,2,0:0:0:0: +128,192,33639,5,8,0:0:0:0: +240,192,33821,1,2,0:0:0:0: +128,192,34003,1,2,0:0:0:0: +80,192,34094,2,0,L|80:144,1,40,0|8,0:0|0:0,0:0:0:0: +336,192,34366,2,0,L|416:192,1,80,10|0,0:0|0:0,0:0:0:0: +240,128,34730,2,0,L|160:128,1,80,10|0,0:0|0:0,0:0:0:0: +432,128,35094,6,0,L|432:80,1,40,12|0,0:0|0:0,0:0:0:0: +384,64,35275,1,2,0:0:0:0: +208,64,35457,2,0,L|128:64,1,80,8|0,0:0|0:0,0:0:0:0: +384,192,35821,1,8,0:0:0:0: +464,128,36003,1,2,0:0:0:0: +384,128,36094,1,2,0:0:0:0: +336,128,36185,1,8,0:0:0:0: +448,128,36366,1,2,0:0:0:0: +192,128,36548,6,0,L|144:128,2,40,8|0|2,0:0|0:0|0:0,0:0:0:0: +368,128,36912,1,8,0:0:0:0: +416,144,37003,2,0,L|416:192,1,40,0|2,0:0|0:0,0:0:0:0: +160,192,37275,2,0,L|144:112,1,80,8|2,0:0|0:0,0:0:0:0: +192,80,37548,1,2,0:0:0:0: +272,80,37639,1,8,0:0:0:0: +160,80,37821,1,2,0:0:0:0: +416,192,38003,5,8,0:0:0:0: +496,192,38185,2,0,L|496:144,1,40,2|0,0:0|0:0,0:0:0:0: +416,144,38366,1,8,0:0:0:0: +496,144,38548,1,0,0:0:0:0: +240,144,38730,1,8,0:0:0:0: +192,144,38821,1,0,0:0:0:0: +240,144,38912,1,2,0:0:0:0: +352,144,39094,2,0,L|272:144,1,80,8|2,0:0|0:0,0:0:0:0: +16,192,39457,6,0,L|16:144,1,40,8|0,0:0|0:0,0:0:0:0: +64,128,39639,1,2,0:0:0:0: +240,176,39821,2,0,L|208:144,1,40,8|0,0:0|0:0,0:0:0:0: +160,128,40003,1,2,0:0:0:0: +416,128,40185,1,8,0:0:0:0: +464,128,40275,1,0,0:0:0:0: +416,128,40366,1,2,0:0:0:0: +240,128,40548,1,8,0:0:0:0: +288,128,40639,1,0,0:0:0:0: +336,128,40730,1,2,0:0:0:0: +64,128,40912,6,0,L|64:80,1,40,12|0,0:0|0:0,0:0:0:0: +112,64,41094,1,2,0:0:0:0: +288,64,41275,2,0,L|368:64,1,80,8|0,0:0|0:0,0:0:0:0: +112,192,41639,1,8,0:0:0:0: +32,128,41821,1,2,0:0:0:0: +112,128,41912,1,2,0:0:0:0: +160,128,42003,1,8,0:0:0:0: +48,128,42185,1,2,0:0:0:0: +304,192,42366,6,0,L|384:192,1,80,8|2,0:0|0:0,0:0:0:0: +208,96,42730,2,0,L|128:96,1,80,8|2,0:0|0:0,0:0:0:0: +384,192,43094,2,0,L|400:272,1,80,8|2,0:0|0:0,0:0:0:0: +352,304,43366,1,2,0:0:0:0: +272,304,43457,1,8,0:0:0:0: +384,304,43639,1,2,0:0:0:0: +128,192,43821,5,8,0:0:0:0: +48,192,44003,2,0,L|48:144,1,40,2|0,0:0|0:0,0:0:0:0: +128,144,44185,1,8,0:0:0:0: +48,144,44366,1,2,0:0:0:0: +304,144,44548,1,8,0:0:0:0: +384,144,44730,1,2,0:0:0:0: +336,144,44821,1,0,0:0:0:0: +256,144,44912,1,8,0:0:0:0: +368,144,45094,1,2,0:0:0:0: +112,256,45275,5,8,0:0:0:0: +64,256,45366,1,0,0:0:0:0: +112,256,45457,1,2,0:0:0:0: +288,256,45639,1,8,0:0:0:0: +336,224,45730,2,0,L|336:176,1,40,0|2,0:0|0:0,0:0:0:0: +80,192,46003,2,0,L|80:152,1,40,8|0,0:0|0:0,0:0:0:0: +128,120,46185,1,2,0:0:0:0: +304,128,46366,1,8,0:0:0:0: +256,128,46457,1,0,0:0:0:0: +208,128,46548,1,2,0:0:0:0: +464,192,46730,5,12,0:0:0:0: +256,192,46912,12,2,49639,0:0:0:0: +200,192,52548,6,0,L|184:112,1,80,2|0,0:0|0:0,0:0:0:0: +280,192,52912,2,0,L|264:112,1,80,2|0,0:0|0:0,0:0:0:0: +104,112,53457,1,2,0:0:0:0: +184,113,53639,1,2,0:0:0:0: +344,192,54003,6,0,L|360:112,1,80,2|0,0:0|0:0,0:0:0:0: +256,192,54366,2,0,L|272:112,1,80,2|0,0:0|0:0,0:0:0:0: +448,112,54912,1,0,0:0:0:0: +360,112,55094,1,2,0:0:0:0: +176,192,55457,6,0,L|160:112,1,80,2|0,0:0|0:0,0:0:0:0: +272,192,55821,2,0,L|256:112,1,80,2|0,0:0|0:0,0:0:0:0: +64,112,56366,1,2,0:0:0:0: +160,112,56548,1,2,0:0:0:0: +368,192,56912,6,0,L|384:112,1,80,2|0,0:0|0:0,0:0:0:0: +264,192,57275,2,0,L|280:112,1,80,2|0,0:0|0:0,0:0:0:0: +400,112,57639,2,0,L|320:112,1,80,2|2,0:0|0:0,0:0:0:0: +464,112,58003,1,2,0:0:0:0: +320,112,58185,1,2,0:0:0:0: +144,112,58366,5,12,0:0:0:0: +224,112,58548,2,0,L|272:112,2,40,2|0|8,0:0|0:0|0:0,0:0:0:0: +144,112,58912,1,2,0:0:0:0: +16,192,59094,2,0,L|16:144,1,40,2|0,3:0|0:0,0:0:0:0: +64,112,59275,1,2,0:0:0:0: +144,112,59457,1,8,0:0:0:0: +64,112,59639,1,2,0:0:0:0: +240,192,59821,6,0,L|240:144,1,40,2|0,3:0|0:0,0:0:0:0: +192,128,60003,1,2,0:0:0:0: +80,192,60185,1,8,0:0:0:0: +128,192,60275,1,0,0:0:0:0: +176,192,60366,1,2,0:0:0:0: +64,192,60548,2,0,L|64:144,1,40,2|0,3:0|0:0,0:0:0:0: +112,128,60730,1,2,0:0:0:0: +224,128,60912,2,0,L|144:128,1,80,10|10,0:0|0:0,0:0:0:0: +320,128,61275,5,2,3:0:0:0: +400,128,61457,2,0,L|400:176,1,40,2|0,0:0|0:0,0:0:0:0: +352,192,61639,1,8,0:0:0:0: +432,192,61821,1,2,0:0:0:0: +320,192,62003,2,0,L|320:240,1,40,2|0,3:0|0:0,0:0:0:0: +368,272,62185,1,2,0:0:0:0: +448,272,62366,1,8,0:0:0:0: +368,272,62548,1,2,0:0:0:0: +192,272,62730,5,2,3:0:0:0: +272,272,62912,1,2,0:0:0:0: +192,272,63094,2,0,L|144:272,2,40,8|0|2,0:0|0:0|0:0,0:0:0:0: +304,192,63457,2,0,L|224:192,1,80,2|2,3:0|0:0,0:0:0:0: +112,112,63821,2,0,L|192:112,1,80,10|10,0:0|0:0,0:0:0:0: +368,112,64185,5,2,3:0:0:0: +288,112,64366,2,0,L|240:112,2,40,2|0|8,0:0|0:0|0:0,0:0:0:0: +368,112,64730,1,2,0:0:0:0: +448,192,64912,1,2,3:0:0:0: +368,192,65094,2,0,L|320:192,2,40,2|0|8,0:0|0:0|0:0,0:0:0:0: +448,192,65457,1,2,0:0:0:0: +272,192,65639,6,0,L|272:240,1,40,2|0,3:0|0:0,0:0:0:0: +320,256,65821,1,2,0:0:0:0: +432,192,66003,1,8,0:0:0:0: +384,192,66094,1,0,0:0:0:0: +336,192,66185,1,2,0:0:0:0: +224,112,66366,1,2,3:0:0:0: +272,112,66457,1,0,0:0:0:0: +320,112,66548,1,2,0:0:0:0: +432,112,66730,1,10,0:0:0:0: +320,112,66912,1,10,0:0:0:0: +144,112,67094,5,2,3:0:0:0: +64,112,67275,2,0,L|64:160,1,40,2|0,0:0|0:0,0:0:0:0: +112,176,67457,1,8,0:0:0:0: +192,176,67639,1,2,0:0:0:0: +80,176,67821,2,0,L|96:224,1,40,2|0,3:0|0:0,0:0:0:0: +144,256,68003,1,2,0:0:0:0: +224,256,68185,1,8,0:0:0:0: +144,256,68366,1,2,0:0:0:0: +320,192,68548,5,2,3:0:0:0: +400,112,68730,2,0,L|408:72,1,40,8|2,0:0|0:0,0:0:0:0: +296,64,69003,2,0,L|256:64,1,40,2|10,0:0|0:0,0:0:0:0: +368,192,69275,1,2,3:0:0:0: +256,192,69457,1,10,0:0:0:0: +80,192,69639,1,10,0:0:0:0: +192,192,69821,1,10,0:0:0:0: +448,192,70003,5,12,0:0:0:0: +160,192,75821,6,0,L|80:192,1,80,10|0,0:0|0:0,0:0:0:0: +160,112,76185,2,0,L|80:112,1,80,10|0,0:0|0:0,0:0:0:0: +160,112,76548,1,2,0:0:0:0: +240,112,76730,1,0,0:0:0:0: +240,112,76912,1,2,0:0:0:0: +240,112,77094,1,0,0:0:0:0: +368,192,77275,6,0,L|448:192,1,80,10|0,0:0|0:0,0:0:0:0: +368,112,77639,2,0,L|448:112,1,80,10|0,0:0|0:0,0:0:0:0: +352,112,78003,1,2,0:0:0:0: +256,112,78185,1,0,0:0:0:0: +256,208,78366,1,2,0:0:0:0: +352,208,78548,1,0,0:0:0:0: +176,192,78730,6,0,L|96:192,1,80,10|0,0:0|0:0,0:0:0:0: +176,112,79094,2,0,L|96:112,1,80,10|0,0:0|0:0,0:0:0:0: +192,112,79457,1,2,0:0:0:0: +288,112,79639,1,0,0:0:0:0: +192,208,79821,1,2,0:0:0:0: +288,208,80003,1,0,0:0:0:0: +256,192,80185,12,10,81275,0:0:0:0: +416,192,81639,5,14,0:0:0:0: +336,192,81821,2,0,L|304:160,2,40 +448,192,82185,1,2,0:0:0:0: +288,192,82548,2,0,L|260:220,2,40,2|0|2,0:0|0:0|0:0,0:0:0:0: +368,192,82912,1,2,0:0:0:0: +192,192,83094,5,10,0:0:0:0: +144,192,83185,1,0,0:0:0:0: +192,192,83275,1,0,0:0:0:0: +304,192,83457,1,0,0:0:0:0: +352,192,83548,2,0,L|352:144,1,40,0|2,0:0|0:0,0:0:0:0: +272,112,83821,1,0,0:0:0:0: +384,112,84003,1,2,0:0:0:0: +304,112,84185,2,0,L|304:160,1,40,2|0,0:0|0:0,0:0:0:0: +352,192,84366,1,2,0:0:0:0: +176,192,84548,5,10,0:0:0:0: +256,192,84730,2,0,L|288:224,2,40,0|0|0,0:0|0:0|0:0,0:0:0:0: +144,192,85094,1,2,0:0:0:0: +304,192,85457,2,0,L|332:164,2,40,2|0|2,0:0|0:0|0:0,0:0:0:0: +224,192,85821,1,2,0:0:0:0: +400,192,86003,5,10,0:0:0:0: +448,192,86094,1,0,0:0:0:0: +400,192,86185,1,0,0:0:0:0: +288,192,86366,1,0,0:0:0:0: +240,192,86457,2,0,L|240:240,1,40,0|2,0:0|0:0,0:0:0:0: +352,192,86730,1,0,0:0:0:0: +304,192,86821,1,0,0:0:0:0: +256,192,86912,1,2,0:0:0:0: +368,112,87094,1,2,0:0:0:0: +432,112,87185,1,0,0:0:0:0: +368,112,87275,1,2,0:0:0:0: +192,112,87457,5,10,0:0:0:0: +112,112,87639,1,0,0:0:0:0: +160,112,87730,1,0,0:0:0:0: +208,112,87821,1,0,0:0:0:0: +96,112,88003,2,0,B|80:192|80:192|160:192,1,160,2|2,0:0|0:0,0:0:0:0: +112,192,88457,1,0,0:0:0:0: +64,192,88548,1,2,0:0:0:0: +144,192,88730,1,2,0:0:0:0: +320,192,88912,5,10,0:0:0:0: +368,192,89003,1,0,0:0:0:0: +320,192,89094,1,0,0:0:0:0: +208,192,89275,1,0,0:0:0:0: +160,192,89366,2,0,L|160:240,1,40,0|2,0:0|0:0,0:0:0:0: +240,272,89639,1,0,0:0:0:0: +128,272,89821,1,2,0:0:0:0: +208,272,90003,2,0,L|208:224,1,40,2|0,0:0|0:0,0:0:0:0: +160,192,90185,1,2,0:0:0:0: +336,272,90366,5,10,0:0:0:0: +416,272,90548,1,0,0:0:0:0: +368,272,90639,1,0,0:0:0:0: +320,272,90730,1,0,0:0:0:0: +432,272,90912,2,0,B|448:192|448:192|368:192,1,160,2|2,0:0|0:0,0:0:0:0: +416,192,91366,1,0,0:0:0:0: +464,192,91457,1,2,0:0:0:0: +384,192,91639,1,2,0:0:0:0: +208,192,91821,5,10,0:0:0:0: +160,192,91912,1,0,0:0:0:0: +208,192,92003,1,0,0:0:0:0: +320,192,92185,1,0,0:0:0:0: +368,192,92275,2,0,L|368:144,1,40,0|2,0:0|0:0,0:0:0:0: +288,80,92548,6,0,B|208:80|208:80|288:80|288:80|368:80,1,240,2|2,0:0|0:0,0:0:0:0: +112,80,93275,5,12,0:0:0:0: +32,80,93457,2,0,L|32:128,1,40,2|0,0:0|0:0,0:0:0:0: +80,144,93639,1,2,0:0:0:0: +192,144,93821,1,8,0:0:0:0: +448,192,94003,2,0,L|464:112,1,80,10|2,0:0|0:0,0:0:0:0: +416,96,94275,1,0,0:0:0:0: +368,80,94366,1,0,0:0:0:0: +448,80,94548,1,2,0:0:0:0: +272,80,94730,5,8,0:0:0:0: +192,80,94912,2,0,L|144:80,2,40,2|0|2,0:0|0:0|0:0,0:0:0:0: +304,80,95275,1,8,0:0:0:0: +48,192,95457,2,0,L|64:112,1,80,8|2,0:0|2:0,0:0:0:0: +176,192,95821,2,0,L|192:112,1,80,2|2,0:0|2:0,0:0:0:0: +16,48,96185,5,8,0:0:0:0: +96,48,96366,2,0,L|96:96,1,40,2|0,0:0|0:0,0:0:0:0: +48,128,96548,1,2,0:0:0:0: +160,128,96730,1,8,0:0:0:0: +416,128,96912,2,0,L|432:48,1,80,10|2,0:0|0:0,0:0:0:0: +320,48,97275,1,0,0:0:0:0: +272,48,97366,2,0,L|272:96,1,40,0|2,0:0|0:0,0:0:0:0: +448,192,97639,5,8,0:0:0:0: +336,192,97821,1,2,0:0:0:0: +448,192,98003,1,2,0:0:0:0: +496,192,98094,2,0,L|496:144,1,40,0|8,0:0|0:0,0:0:0:0: +240,192,98366,2,0,L|128:192,1,100,10|10,0:0|0:0,0:0:0:0: +240,128,98730,2,0,L|352:128,1,100,8|10,0:0|0:0,0:0:0:0: +96,128,99094,5,12,0:0:0:0: +16,128,99275,2,0,L|16:80,1,40,2|0,0:0|0:0,0:0:0:0: +64,64,99457,1,2,0:0:0:0: +176,64,99639,1,8,0:0:0:0: +432,192,99821,2,0,L|480:128,1,80,10|2,0:0|0:0,0:0:0:0: +480,80,100094,1,0,0:0:0:0: +448,80,100185,1,0,0:0:0:0: +368,80,100366,1,2,0:0:0:0: +192,80,100548,5,8,0:0:0:0: +272,80,100730,2,0,L|320:80,2,40,2|0|2,0:0|0:0|0:0,0:0:0:0: +160,80,101094,1,8,0:0:0:0: +416,192,101275,2,0,L|432:112,1,80,8|2,0:0|2:0,0:0:0:0: +320,112,101639,1,2,0:0:0:0: +432,112,101821,1,2,2:0:0:0: +256,112,102003,5,8,0:0:0:0: +176,112,102185,2,0,L|176:160,1,40,2|0,0:0|0:0,0:0:0:0: +224,192,102366,1,2,0:0:0:0: +112,192,102548,1,8,0:0:0:0: +368,192,102730,2,0,L|384:112,1,80,10|2,0:0|0:0,0:0:0:0: +272,112,103094,1,0,0:0:0:0: +224,112,103185,1,0,0:0:0:0: +272,112,103275,1,2,0:0:0:0: +384,192,103457,5,8,0:0:0:0: +272,192,103639,1,2,0:0:0:0: +384,192,103821,1,2,0:0:0:0: +432,192,103912,2,0,L|432:144,1,40,0|8,0:0|0:0,0:0:0:0: +176,192,104185,2,0,L|96:192,1,80,10|0,0:0|0:0,0:0:0:0: +272,128,104548,2,0,L|352:128,1,80,10|0,0:0|0:0,0:0:0:0: +80,128,104912,6,0,L|80:80,1,40,12|0,0:0|0:0,0:0:0:0: +128,64,105094,1,2,0:0:0:0: +304,64,105275,2,0,L|384:64,1,80,8|0,0:0|0:0,0:0:0:0: +128,192,105639,1,8,0:0:0:0: +48,128,105821,1,2,0:0:0:0: +128,128,105912,1,2,0:0:0:0: +176,128,106003,1,8,0:0:0:0: +64,128,106185,1,2,0:0:0:0: +320,128,106366,6,0,L|368:128,2,40,8|0|2,0:0|0:0|0:0,0:0:0:0: +144,128,106730,1,8,0:0:0:0: +96,144,106821,2,0,L|96:192,1,40,0|2,0:0|0:0,0:0:0:0: +352,192,107094,2,0,L|368:112,1,80,8|2,0:0|0:0,0:0:0:0: +320,80,107366,1,2,0:0:0:0: +240,80,107457,1,8,0:0:0:0: +352,80,107639,1,2,0:0:0:0: +96,192,107821,5,8,0:0:0:0: +16,192,108003,2,0,L|16:144,1,40,2|0,0:0|0:0,0:0:0:0: +96,144,108185,1,8,0:0:0:0: +16,144,108366,1,0,0:0:0:0: +272,144,108548,1,8,0:0:0:0: +320,144,108639,1,0,0:0:0:0: +272,144,108730,1,2,0:0:0:0: +160,144,108912,2,0,L|240:144,1,80,8|2,0:0|0:0,0:0:0:0: +496,192,109275,6,0,L|496:144,1,40,8|0,0:0|0:0,0:0:0:0: +448,128,109457,1,2,0:0:0:0: +272,176,109639,2,0,L|304:144,1,40,8|0,0:0|0:0,0:0:0:0: +352,128,109821,1,2,0:0:0:0: +96,128,110003,1,8,0:0:0:0: +48,128,110094,1,0,0:0:0:0: +96,128,110185,1,2,0:0:0:0: +272,128,110366,1,8,0:0:0:0: +224,128,110457,1,0,0:0:0:0: +176,128,110548,1,2,0:0:0:0: +448,128,110730,6,0,L|448:80,1,40,12|0,0:0|0:0,0:0:0:0: +400,64,110912,1,2,0:0:0:0: +224,64,111094,2,0,L|144:64,1,80,8|0,0:0|0:0,0:0:0:0: +400,192,111457,1,8,0:0:0:0: +480,128,111639,1,2,0:0:0:0: +400,128,111730,1,2,0:0:0:0: +352,128,111821,1,8,0:0:0:0: +464,128,112003,1,2,0:0:0:0: +208,192,112185,6,0,L|128:192,1,80,8|2,0:0|0:0,0:0:0:0: +304,96,112548,2,0,L|384:96,1,80,8|2,0:0|0:0,0:0:0:0: +128,192,112912,2,0,L|112:272,1,80,8|2,0:0|0:0,0:0:0:0: +160,304,113185,1,2,0:0:0:0: +240,304,113275,1,8,0:0:0:0: +128,304,113457,1,2,0:0:0:0: +384,192,113639,5,8,0:0:0:0: +464,192,113821,2,0,L|464:144,1,40,2|0,0:0|0:0,0:0:0:0: +384,144,114003,1,8,0:0:0:0: +464,144,114185,1,2,0:0:0:0: +208,144,114366,1,8,0:0:0:0: +128,144,114548,1,2,0:0:0:0: +176,144,114639,1,0,0:0:0:0: +256,144,114730,1,8,0:0:0:0: +144,144,114912,1,2,0:0:0:0: +400,256,115094,5,8,0:0:0:0: +448,256,115185,1,0,0:0:0:0: +400,256,115275,1,2,0:0:0:0: +224,256,115457,1,8,0:0:0:0: +176,224,115548,2,0,L|176:176,1,40,0|2,0:0|0:0,0:0:0:0: +432,192,115821,2,0,L|432:152,1,40,8|0,0:0|0:0,0:0:0:0: +384,120,116003,1,2,0:0:0:0: +208,128,116185,1,8,0:0:0:0: +256,128,116275,1,0,0:0:0:0: +304,128,116366,1,2,0:0:0:0: +48,192,116548,5,12,0:0:0:0: +256,192,116730,12,2,119457,0:0:0:0: +312,192,122366,6,0,L|328:112,1,80,2|0,0:0|0:0,0:0:0:0: +232,192,122730,2,0,L|248:112,1,80,2|0,0:0|0:0,0:0:0:0: +408,112,123275,1,2,0:0:0:0: +328,113,123457,1,2,0:0:0:0: +168,192,123821,6,0,L|152:112,1,80,2|0,0:0|0:0,0:0:0:0: +256,192,124185,2,0,L|240:112,1,80,2|0,0:0|0:0,0:0:0:0: +64,112,124730,1,0,0:0:0:0: +152,112,124912,1,2,0:0:0:0: +336,192,125275,6,0,L|352:112,1,80,2|0,0:0|0:0,0:0:0:0: +240,192,125639,2,0,L|256:112,1,80,2|0,0:0|0:0,0:0:0:0: +448,112,126185,1,2,0:0:0:0: +352,112,126366,1,2,0:0:0:0: +144,192,126730,6,0,L|128:112,1,80,2|0,0:0|0:0,0:0:0:0: +248,192,127094,2,0,L|232:112,1,80,2|0,0:0|0:0,0:0:0:0: +112,112,127457,2,0,L|192:112,1,80,2|2,0:0|0:0,0:0:0:0: +48,112,127821,1,2,0:0:0:0: +192,112,128003,1,2,0:0:0:0: +368,112,128185,5,12,0:0:0:0: +288,112,128366,2,0,L|240:112,2,40,2|0|8,0:0|0:0|0:0,0:0:0:0: +368,112,128730,1,2,0:0:0:0: +496,192,128912,2,0,L|496:144,1,40,2|0,3:0|0:0,0:0:0:0: +448,112,129094,1,2,0:0:0:0: +368,112,129275,1,8,0:0:0:0: +448,112,129457,1,2,0:0:0:0: +272,192,129639,6,0,L|272:144,1,40,2|0,3:0|0:0,0:0:0:0: +320,128,129821,1,2,0:0:0:0: +432,192,130003,1,8,0:0:0:0: +384,192,130094,1,0,0:0:0:0: +336,192,130185,1,2,0:0:0:0: +448,192,130366,2,0,L|448:144,1,40,2|0,3:0|0:0,0:0:0:0: +400,128,130548,1,2,0:0:0:0: +288,128,130730,2,0,L|368:128,1,80,10|10,0:0|0:0,0:0:0:0: +192,128,131094,5,2,3:0:0:0: +112,128,131275,2,0,L|112:176,1,40,2|0,0:0|0:0,0:0:0:0: +160,192,131457,1,8,0:0:0:0: +80,192,131639,1,2,0:0:0:0: +192,192,131821,2,0,L|192:240,1,40,2|0,3:0|0:0,0:0:0:0: +144,272,132003,1,2,0:0:0:0: +64,272,132185,1,8,0:0:0:0: +144,272,132366,1,2,0:0:0:0: +320,272,132548,5,2,3:0:0:0: +240,272,132730,1,2,0:0:0:0: +320,272,132912,2,0,L|368:272,2,40,8|0|2,0:0|0:0|0:0,0:0:0:0: +208,192,133275,2,0,L|288:192,1,80,2|2,3:0|0:0,0:0:0:0: +400,112,133639,2,0,L|320:112,1,80,10|10,0:0|0:0,0:0:0:0: +144,112,134003,5,2,3:0:0:0: +224,112,134185,2,0,L|272:112,2,40,2|0|8,0:0|0:0|0:0,0:0:0:0: +144,112,134548,1,2,0:0:0:0: +64,192,134730,1,2,3:0:0:0: +144,192,134912,2,0,L|192:192,2,40,2|0|8,0:0|0:0|0:0,0:0:0:0: +64,192,135275,1,2,0:0:0:0: +240,192,135457,6,0,L|240:240,1,40,2|0,3:0|0:0,0:0:0:0: +192,256,135639,1,2,0:0:0:0: +80,192,135821,1,8,0:0:0:0: +128,192,135912,1,0,0:0:0:0: +176,192,136003,1,2,0:0:0:0: +288,112,136185,1,2,3:0:0:0: +240,112,136275,1,0,0:0:0:0: +192,112,136366,1,2,0:0:0:0: +80,112,136548,1,10,0:0:0:0: +192,112,136730,1,10,0:0:0:0: +368,112,136912,5,2,3:0:0:0: +448,112,137094,2,0,L|448:160,1,40,2|0,0:0|0:0,0:0:0:0: +400,176,137275,1,8,0:0:0:0: +320,176,137457,1,2,0:0:0:0: +432,176,137639,2,0,L|416:224,1,40,2|0,3:0|0:0,0:0:0:0: +368,256,137821,1,2,0:0:0:0: +288,256,138003,1,8,0:0:0:0: +368,256,138185,1,2,0:0:0:0: +192,192,138366,5,2,3:0:0:0: +112,112,138548,2,0,L|104:72,1,40,8|2,0:0|0:0,0:0:0:0: +216,64,138821,2,0,L|256:64,1,40,2|10,0:0|0:0,0:0:0:0: +144,192,139094,1,2,3:0:0:0: +224,192,139275,1,10,0:0:0:0: +48,192,139457,1,10,0:0:0:0: +160,192,139639,1,10,0:0:0:0: +416,192,139821,5,12,0:0:0:0: +256,192,140003,12,0,143457,0:0:0:0: diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/42587-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/42587-expected-conversion.json new file mode 100644 index 0000000000..42df40a57e --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/42587-expected-conversion.json @@ -0,0 +1 @@ +{"Mappings":[{"StartTime":24383.0,"Objects":[{"StartTime":24383.0,"Position":376.0,"HyperDash":false}]},{"StartTime":24478.0,"Objects":[{"StartTime":24478.0,"Position":392.0,"HyperDash":false}]},{"StartTime":24573.0,"Objects":[{"StartTime":24573.0,"Position":408.0,"HyperDash":false}]},{"StartTime":24763.0,"Objects":[{"StartTime":24763.0,"Position":448.0,"HyperDash":false},{"StartTime":24839.0,"Position":396.8095,"HyperDash":false},{"StartTime":24952.0,"Position":358.0,"HyperDash":false}]},{"StartTime":25143.0,"Objects":[{"StartTime":25143.0,"Position":280.0,"HyperDash":false}]},{"StartTime":25333.0,"Objects":[{"StartTime":25333.0,"Position":232.0,"HyperDash":false}]},{"StartTime":25523.0,"Objects":[{"StartTime":25523.0,"Position":152.0,"HyperDash":false},{"StartTime":25599.0,"Position":104.809525,"HyperDash":false},{"StartTime":25712.0,"Position":62.0,"HyperDash":false}]},{"StartTime":25902.0,"Objects":[{"StartTime":25902.0,"Position":32.0,"HyperDash":false}]},{"StartTime":26092.0,"Objects":[{"StartTime":26092.0,"Position":96.0,"HyperDash":false},{"StartTime":26186.0,"Position":119.750992,"HyperDash":false},{"StartTime":26281.0,"Position":168.644272,"HyperDash":false},{"StartTime":26376.0,"Position":215.703568,"HyperDash":false},{"StartTime":26471.0,"Position":244.0,"HyperDash":false},{"StartTime":26557.0,"Position":204.446625,"HyperDash":false},{"StartTime":26643.0,"Position":161.656128,"HyperDash":false},{"StartTime":26729.0,"Position":115.71936,"HyperDash":false},{"StartTime":26851.0,"Position":96.0,"HyperDash":false}]},{"StartTime":27042.0,"Objects":[{"StartTime":27042.0,"Position":96.0,"HyperDash":false}]},{"StartTime":27232.0,"Objects":[{"StartTime":27232.0,"Position":176.0,"HyperDash":false},{"StartTime":27308.0,"Position":204.190475,"HyperDash":false},{"StartTime":27421.0,"Position":266.0,"HyperDash":false}]},{"StartTime":27801.0,"Objects":[{"StartTime":27801.0,"Position":448.0,"HyperDash":false}]},{"StartTime":27991.0,"Objects":[{"StartTime":27991.0,"Position":360.0,"HyperDash":false}]},{"StartTime":28371.0,"Objects":[{"StartTime":28371.0,"Position":192.0,"HyperDash":false}]},{"StartTime":28561.0,"Objects":[{"StartTime":28561.0,"Position":280.0,"HyperDash":false}]},{"StartTime":28751.0,"Objects":[{"StartTime":28751.0,"Position":368.0,"HyperDash":false}]},{"StartTime":28940.0,"Objects":[{"StartTime":28940.0,"Position":456.0,"HyperDash":false}]},{"StartTime":29130.0,"Objects":[{"StartTime":29130.0,"Position":456.0,"HyperDash":false},{"StartTime":29224.0,"Position":417.249023,"HyperDash":false},{"StartTime":29319.0,"Position":394.3557,"HyperDash":false},{"StartTime":29414.0,"Position":367.296448,"HyperDash":false},{"StartTime":29509.0,"Position":308.0,"HyperDash":false},{"StartTime":29595.0,"Position":352.553375,"HyperDash":false},{"StartTime":29681.0,"Position":380.343872,"HyperDash":false},{"StartTime":29767.0,"Position":426.28064,"HyperDash":false},{"StartTime":29889.0,"Position":456.0,"HyperDash":false}]},{"StartTime":30080.0,"Objects":[{"StartTime":30080.0,"Position":456.0,"HyperDash":false}]},{"StartTime":30270.0,"Objects":[{"StartTime":30270.0,"Position":376.0,"HyperDash":false},{"StartTime":30346.0,"Position":320.8095,"HyperDash":false},{"StartTime":30459.0,"Position":286.0,"HyperDash":false}]},{"StartTime":30839.0,"Objects":[{"StartTime":30839.0,"Position":112.0,"HyperDash":false}]},{"StartTime":31029.0,"Objects":[{"StartTime":31029.0,"Position":176.0,"HyperDash":false}]},{"StartTime":31219.0,"Objects":[{"StartTime":31219.0,"Position":112.0,"HyperDash":false}]},{"StartTime":31314.0,"Objects":[{"StartTime":31314.0,"Position":112.0,"HyperDash":false}]},{"StartTime":31409.0,"Objects":[{"StartTime":31409.0,"Position":112.0,"HyperDash":false}]},{"StartTime":31599.0,"Objects":[{"StartTime":31599.0,"Position":176.0,"HyperDash":false}]},{"StartTime":31788.0,"Objects":[{"StartTime":31788.0,"Position":240.0,"HyperDash":false}]},{"StartTime":31978.0,"Objects":[{"StartTime":31978.0,"Position":176.0,"HyperDash":false}]},{"StartTime":32168.0,"Objects":[{"StartTime":32168.0,"Position":240.0,"HyperDash":false},{"StartTime":32262.0,"Position":264.85144,"HyperDash":false},{"StartTime":32357.0,"Position":329.8879,"HyperDash":false},{"StartTime":32452.0,"Position":373.9472,"HyperDash":false},{"StartTime":32547.0,"Position":402.243652,"HyperDash":false},{"StartTime":32633.0,"Position":374.690277,"HyperDash":false},{"StartTime":32719.0,"Position":312.89978,"HyperDash":false},{"StartTime":32805.0,"Position":266.934845,"HyperDash":false},{"StartTime":32927.0,"Position":240.0,"HyperDash":false}]},{"StartTime":33118.0,"Objects":[{"StartTime":33118.0,"Position":240.0,"HyperDash":false}]},{"StartTime":33307.0,"Objects":[{"StartTime":33307.0,"Position":328.0,"HyperDash":false},{"StartTime":33383.0,"Position":341.0,"HyperDash":false},{"StartTime":33496.0,"Position":298.93808,"HyperDash":false}]},{"StartTime":33877.0,"Objects":[{"StartTime":33877.0,"Position":136.0,"HyperDash":false}]},{"StartTime":34067.0,"Objects":[{"StartTime":34067.0,"Position":80.0,"HyperDash":false}]},{"StartTime":34257.0,"Objects":[{"StartTime":34257.0,"Position":24.0,"HyperDash":false}]},{"StartTime":34352.0,"Objects":[{"StartTime":34352.0,"Position":24.0,"HyperDash":false}]},{"StartTime":34447.0,"Objects":[{"StartTime":34447.0,"Position":24.0,"HyperDash":false}]},{"StartTime":34542.0,"Objects":[{"StartTime":34542.0,"Position":40.0,"HyperDash":false}]},{"StartTime":34637.0,"Objects":[{"StartTime":34637.0,"Position":56.0,"HyperDash":false}]},{"StartTime":34826.0,"Objects":[{"StartTime":34826.0,"Position":144.0,"HyperDash":false}]},{"StartTime":35016.0,"Objects":[{"StartTime":35016.0,"Position":232.0,"HyperDash":false}]},{"StartTime":35206.0,"Objects":[{"StartTime":35206.0,"Position":376.0,"HyperDash":false},{"StartTime":35300.0,"Position":413.2887,"HyperDash":false},{"StartTime":35395.0,"Position":455.196533,"HyperDash":false},{"StartTime":35472.0,"Position":429.631622,"HyperDash":false},{"StartTime":35585.0,"Position":376.0,"HyperDash":false}]},{"StartTime":35776.0,"Objects":[{"StartTime":35776.0,"Position":232.0,"HyperDash":false},{"StartTime":35852.0,"Position":181.590073,"HyperDash":false},{"StartTime":35965.0,"Position":152.803467,"HyperDash":false}]},{"StartTime":36156.0,"Objects":[{"StartTime":36156.0,"Position":304.0,"HyperDash":false}]},{"StartTime":36345.0,"Objects":[{"StartTime":36345.0,"Position":304.0,"HyperDash":false},{"StartTime":36421.0,"Position":300.0,"HyperDash":false},{"StartTime":36534.0,"Position":304.0,"HyperDash":false}]},{"StartTime":36725.0,"Objects":[{"StartTime":36725.0,"Position":112.0,"HyperDash":false},{"StartTime":36801.0,"Position":78.63026,"HyperDash":false},{"StartTime":36914.0,"Position":31.5015564,"HyperDash":false}]},{"StartTime":37105.0,"Objects":[{"StartTime":37105.0,"Position":112.0,"HyperDash":false},{"StartTime":37181.0,"Position":93.63026,"HyperDash":false},{"StartTime":37294.0,"Position":31.5015564,"HyperDash":false}]},{"StartTime":37485.0,"Objects":[{"StartTime":37485.0,"Position":112.0,"HyperDash":false}]},{"StartTime":37675.0,"Objects":[{"StartTime":37675.0,"Position":112.0,"HyperDash":false},{"StartTime":37769.0,"Position":57.8074036,"HyperDash":false},{"StartTime":37864.0,"Position":32.9893951,"HyperDash":false},{"StartTime":37941.0,"Position":45.885498,"HyperDash":false},{"StartTime":38054.0,"Position":112.0,"HyperDash":false}]},{"StartTime":38244.0,"Objects":[{"StartTime":38244.0,"Position":112.0,"HyperDash":false}]},{"StartTime":38434.0,"Objects":[{"StartTime":38434.0,"Position":32.0,"HyperDash":false}]},{"StartTime":38624.0,"Objects":[{"StartTime":38624.0,"Position":112.0,"HyperDash":false}]},{"StartTime":38814.0,"Objects":[{"StartTime":38814.0,"Position":32.0,"HyperDash":false}]},{"StartTime":39004.0,"Objects":[{"StartTime":39004.0,"Position":112.0,"HyperDash":false}]},{"StartTime":39194.0,"Objects":[{"StartTime":39194.0,"Position":200.0,"HyperDash":false},{"StartTime":39270.0,"Position":220.190475,"HyperDash":false},{"StartTime":39383.0,"Position":290.0,"HyperDash":false}]},{"StartTime":39573.0,"Objects":[{"StartTime":39573.0,"Position":384.0,"HyperDash":false},{"StartTime":39649.0,"Position":360.8095,"HyperDash":false},{"StartTime":39762.0,"Position":294.0,"HyperDash":false}]},{"StartTime":39953.0,"Objects":[{"StartTime":39953.0,"Position":200.0,"HyperDash":false},{"StartTime":40047.0,"Position":261.604553,"HyperDash":false},{"StartTime":40142.0,"Position":290.0,"HyperDash":false},{"StartTime":40237.0,"Position":244.237259,"HyperDash":false},{"StartTime":40332.0,"Position":200.0,"HyperDash":false},{"StartTime":40409.0,"Position":229.379608,"HyperDash":false},{"StartTime":40522.0,"Position":290.0,"HyperDash":false}]},{"StartTime":40713.0,"Objects":[{"StartTime":40713.0,"Position":408.0,"HyperDash":false}]},{"StartTime":40902.0,"Objects":[{"StartTime":40902.0,"Position":360.0,"HyperDash":false}]},{"StartTime":41092.0,"Objects":[{"StartTime":41092.0,"Position":280.0,"HyperDash":false},{"StartTime":41177.0,"Position":240.0683,"HyperDash":false},{"StartTime":41263.0,"Position":219.442886,"HyperDash":false},{"StartTime":41349.0,"Position":153.743317,"HyperDash":false},{"StartTime":41471.0,"Position":103.528549,"HyperDash":false}]},{"StartTime":41662.0,"Objects":[{"StartTime":41662.0,"Position":168.0,"HyperDash":false},{"StartTime":41756.0,"Position":130.327,"HyperDash":false},{"StartTime":41851.0,"Position":85.20778,"HyperDash":false},{"StartTime":41928.0,"Position":130.082031,"HyperDash":false},{"StartTime":42041.0,"Position":168.0,"HyperDash":false}]},{"StartTime":42232.0,"Objects":[{"StartTime":42232.0,"Position":264.0,"HyperDash":false},{"StartTime":42317.0,"Position":321.196838,"HyperDash":false},{"StartTime":42403.0,"Position":360.600128,"HyperDash":false},{"StartTime":42489.0,"Position":394.9759,"HyperDash":false},{"StartTime":42611.0,"Position":423.349854,"HyperDash":false}]},{"StartTime":42801.0,"Objects":[{"StartTime":42801.0,"Position":320.0,"HyperDash":false},{"StartTime":42877.0,"Position":322.0243,"HyperDash":false},{"StartTime":42990.0,"Position":282.757751,"HyperDash":false}]},{"StartTime":43181.0,"Objects":[{"StartTime":43181.0,"Position":184.0,"HyperDash":false},{"StartTime":43257.0,"Position":210.32988,"HyperDash":false},{"StartTime":43370.0,"Position":227.0967,"HyperDash":false}]},{"StartTime":43561.0,"Objects":[{"StartTime":43561.0,"Position":227.0,"HyperDash":false}]},{"StartTime":43751.0,"Objects":[{"StartTime":43751.0,"Position":192.0,"HyperDash":false},{"StartTime":43845.0,"Position":152.03096,"HyperDash":false},{"StartTime":43940.0,"Position":145.695374,"HyperDash":false},{"StartTime":44017.0,"Position":169.388275,"HyperDash":false},{"StartTime":44130.0,"Position":192.0,"HyperDash":false}]},{"StartTime":44320.0,"Objects":[{"StartTime":44320.0,"Position":128.0,"HyperDash":false},{"StartTime":44405.0,"Position":142.777267,"HyperDash":false},{"StartTime":44491.0,"Position":173.576416,"HyperDash":false},{"StartTime":44577.0,"Position":244.930679,"HyperDash":false},{"StartTime":44699.0,"Position":284.40564,"HyperDash":false}]},{"StartTime":44890.0,"Objects":[{"StartTime":44890.0,"Position":376.0,"HyperDash":false}]},{"StartTime":45270.0,"Objects":[{"StartTime":45270.0,"Position":440.0,"HyperDash":false}]},{"StartTime":45459.0,"Objects":[{"StartTime":45459.0,"Position":384.0,"HyperDash":false}]},{"StartTime":45649.0,"Objects":[{"StartTime":45649.0,"Position":304.0,"HyperDash":false}]},{"StartTime":45839.0,"Objects":[{"StartTime":45839.0,"Position":216.0,"HyperDash":false},{"StartTime":45924.0,"Position":169.327576,"HyperDash":false},{"StartTime":46010.0,"Position":156.035263,"HyperDash":false},{"StartTime":46096.0,"Position":110.220467,"HyperDash":false},{"StartTime":46218.0,"Position":68.60332,"HyperDash":false}]},{"StartTime":46409.0,"Objects":[{"StartTime":46409.0,"Position":56.0,"HyperDash":false}]},{"StartTime":46788.0,"Objects":[{"StartTime":46788.0,"Position":216.0,"HyperDash":false}]},{"StartTime":46978.0,"Objects":[{"StartTime":46978.0,"Position":296.0,"HyperDash":false}]},{"StartTime":47168.0,"Objects":[{"StartTime":47168.0,"Position":216.0,"HyperDash":false}]},{"StartTime":47358.0,"Objects":[{"StartTime":47358.0,"Position":296.0,"HyperDash":false}]},{"StartTime":47738.0,"Objects":[{"StartTime":47738.0,"Position":136.0,"HyperDash":false}]},{"StartTime":48118.0,"Objects":[{"StartTime":48118.0,"Position":376.0,"HyperDash":false}]},{"StartTime":48497.0,"Objects":[{"StartTime":48497.0,"Position":136.0,"HyperDash":false}]},{"StartTime":48877.0,"Objects":[{"StartTime":48877.0,"Position":376.0,"HyperDash":false},{"StartTime":48953.0,"Position":329.8095,"HyperDash":false},{"StartTime":49066.0,"Position":286.0,"HyperDash":false}]},{"StartTime":49257.0,"Objects":[{"StartTime":49257.0,"Position":192.0,"HyperDash":false}]},{"StartTime":49447.0,"Objects":[{"StartTime":49447.0,"Position":128.0,"HyperDash":false}]},{"StartTime":49637.0,"Objects":[{"StartTime":49637.0,"Position":216.0,"HyperDash":false},{"StartTime":49731.0,"Position":261.604553,"HyperDash":false},{"StartTime":49826.0,"Position":306.0,"HyperDash":false},{"StartTime":49921.0,"Position":251.237244,"HyperDash":false},{"StartTime":50016.0,"Position":216.0,"HyperDash":false},{"StartTime":50093.0,"Position":252.379608,"HyperDash":false},{"StartTime":50206.0,"Position":306.0,"HyperDash":false}]},{"StartTime":50396.0,"Objects":[{"StartTime":50396.0,"Position":400.0,"HyperDash":false},{"StartTime":50481.0,"Position":405.0538,"HyperDash":false},{"StartTime":50567.0,"Position":419.1245,"HyperDash":false},{"StartTime":50653.0,"Position":441.139374,"HyperDash":false},{"StartTime":50775.0,"Position":411.5338,"HyperDash":false}]},{"StartTime":50966.0,"Objects":[{"StartTime":50966.0,"Position":336.0,"HyperDash":false},{"StartTime":51042.0,"Position":300.082825,"HyperDash":false},{"StartTime":51155.0,"Position":279.0086,"HyperDash":false}]},{"StartTime":51345.0,"Objects":[{"StartTime":51345.0,"Position":208.0,"HyperDash":false}]},{"StartTime":51535.0,"Objects":[{"StartTime":51535.0,"Position":168.0,"HyperDash":false}]},{"StartTime":51725.0,"Objects":[{"StartTime":51725.0,"Position":120.0,"HyperDash":false}]},{"StartTime":51915.0,"Objects":[{"StartTime":51915.0,"Position":72.0,"HyperDash":false},{"StartTime":52000.0,"Position":46.90651,"HyperDash":false},{"StartTime":52086.0,"Position":66.65258,"HyperDash":false},{"StartTime":52172.0,"Position":76.80537,"HyperDash":false},{"StartTime":52294.0,"Position":126.392281,"HyperDash":false}]},{"StartTime":52485.0,"Objects":[{"StartTime":52485.0,"Position":216.0,"HyperDash":false}]},{"StartTime":52675.0,"Objects":[{"StartTime":52675.0,"Position":304.0,"HyperDash":false}]},{"StartTime":52864.0,"Objects":[{"StartTime":52864.0,"Position":232.0,"HyperDash":false}]},{"StartTime":53054.0,"Objects":[{"StartTime":53054.0,"Position":312.0,"HyperDash":false}]},{"StartTime":53244.0,"Objects":[{"StartTime":53244.0,"Position":288.0,"HyperDash":false},{"StartTime":53329.0,"Position":335.2697,"HyperDash":false},{"StartTime":53415.0,"Position":365.515228,"HyperDash":false},{"StartTime":53501.0,"Position":421.4718,"HyperDash":false},{"StartTime":53623.0,"Position":449.9475,"HyperDash":false}]},{"StartTime":53814.0,"Objects":[{"StartTime":53814.0,"Position":392.0,"HyperDash":false},{"StartTime":53890.0,"Position":357.8421,"HyperDash":false},{"StartTime":54003.0,"Position":349.331024,"HyperDash":false}]},{"StartTime":54194.0,"Objects":[{"StartTime":54194.0,"Position":280.0,"HyperDash":false},{"StartTime":54270.0,"Position":256.0476,"HyperDash":false},{"StartTime":54383.0,"Position":208.0,"HyperDash":false}]},{"StartTime":54573.0,"Objects":[{"StartTime":54573.0,"Position":176.0,"HyperDash":false}]},{"StartTime":54763.0,"Objects":[{"StartTime":54763.0,"Position":104.0,"HyperDash":false},{"StartTime":54829.0,"Position":110.83445,"HyperDash":false},{"StartTime":54896.0,"Position":93.97989,"HyperDash":false},{"StartTime":54962.0,"Position":92.00793,"HyperDash":false},{"StartTime":55029.0,"Position":91.28976,"HyperDash":false},{"StartTime":55096.0,"Position":107.652794,"HyperDash":false},{"StartTime":55162.0,"Position":137.152725,"HyperDash":false},{"StartTime":55229.0,"Position":132.523422,"HyperDash":false},{"StartTime":55332.0,"Position":193.99971,"HyperDash":false}]},{"StartTime":55523.0,"Objects":[{"StartTime":55523.0,"Position":216.0,"HyperDash":false}]},{"StartTime":55713.0,"Objects":[{"StartTime":55713.0,"Position":264.0,"HyperDash":false}]},{"StartTime":55902.0,"Objects":[{"StartTime":55902.0,"Position":352.0,"HyperDash":false}]},{"StartTime":56092.0,"Objects":[{"StartTime":56092.0,"Position":440.0,"HyperDash":false}]},{"StartTime":56282.0,"Objects":[{"StartTime":56282.0,"Position":352.0,"HyperDash":false}]},{"StartTime":56472.0,"Objects":[{"StartTime":56472.0,"Position":264.0,"HyperDash":false},{"StartTime":56538.0,"Position":236.1824,"HyperDash":false},{"StartTime":56605.0,"Position":201.573,"HyperDash":false},{"StartTime":56671.0,"Position":187.143539,"HyperDash":false},{"StartTime":56738.0,"Position":159.746231,"HyperDash":false},{"StartTime":56805.0,"Position":123.075737,"HyperDash":false},{"StartTime":56871.0,"Position":78.82073,"HyperDash":false},{"StartTime":56938.0,"Position":49.782032,"HyperDash":false},{"StartTime":57041.0,"Position":18.8103733,"HyperDash":false}]},{"StartTime":57421.0,"Objects":[{"StartTime":57421.0,"Position":160.0,"HyperDash":false}]},{"StartTime":57611.0,"Objects":[{"StartTime":57611.0,"Position":32.0,"HyperDash":false}]},{"StartTime":57801.0,"Objects":[{"StartTime":57801.0,"Position":160.0,"HyperDash":false}]},{"StartTime":57991.0,"Objects":[{"StartTime":57991.0,"Position":248.0,"HyperDash":false},{"StartTime":58057.0,"Position":279.8176,"HyperDash":false},{"StartTime":58124.0,"Position":314.427,"HyperDash":false},{"StartTime":58190.0,"Position":336.856445,"HyperDash":false},{"StartTime":58257.0,"Position":373.253754,"HyperDash":false},{"StartTime":58324.0,"Position":394.924255,"HyperDash":false},{"StartTime":58390.0,"Position":404.17926,"HyperDash":false},{"StartTime":58457.0,"Position":435.217957,"HyperDash":false},{"StartTime":58560.0,"Position":493.189636,"HyperDash":false}]},{"StartTime":58940.0,"Objects":[{"StartTime":58940.0,"Position":360.0,"HyperDash":false}]},{"StartTime":59130.0,"Objects":[{"StartTime":59130.0,"Position":256.0,"HyperDash":false}]},{"StartTime":59320.0,"Objects":[{"StartTime":59320.0,"Position":152.0,"HyperDash":false}]},{"StartTime":59510.0,"Objects":[{"StartTime":59510.0,"Position":168.0,"HyperDash":false},{"StartTime":59604.0,"Position":213.268082,"HyperDash":false},{"StartTime":59699.0,"Position":263.009766,"HyperDash":false},{"StartTime":59794.0,"Position":292.74585,"HyperDash":false},{"StartTime":59889.0,"Position":343.451233,"HyperDash":false},{"StartTime":59984.0,"Position":303.3675,"HyperDash":false},{"StartTime":60079.0,"Position":253.641876,"HyperDash":false},{"StartTime":60174.0,"Position":227.887085,"HyperDash":false},{"StartTime":60269.0,"Position":168.0,"HyperDash":false},{"StartTime":60355.0,"Position":189.407471,"HyperDash":false},{"StartTime":60441.0,"Position":257.7932,"HyperDash":false},{"StartTime":60527.0,"Position":268.439819,"HyperDash":false},{"StartTime":60649.0,"Position":343.451233,"HyperDash":false}]},{"StartTime":60839.0,"Objects":[{"StartTime":60839.0,"Position":408.0,"HyperDash":false}]},{"StartTime":60934.0,"Objects":[{"StartTime":60934.0,"Position":408.0,"HyperDash":false}]},{"StartTime":61029.0,"Objects":[{"StartTime":61029.0,"Position":408.0,"HyperDash":false},{"StartTime":61114.0,"Position":391.84967,"HyperDash":false},{"StartTime":61200.0,"Position":372.427,"HyperDash":false},{"StartTime":61286.0,"Position":329.0043,"HyperDash":false},{"StartTime":61408.0,"Position":304.776764,"HyperDash":false}]},{"StartTime":61599.0,"Objects":[{"StartTime":61599.0,"Position":304.0,"HyperDash":false}]},{"StartTime":61788.0,"Objects":[{"StartTime":61788.0,"Position":216.0,"HyperDash":false},{"StartTime":61873.0,"Position":231.980789,"HyperDash":false},{"StartTime":61959.0,"Position":262.282928,"HyperDash":false},{"StartTime":62045.0,"Position":291.630219,"HyperDash":false},{"StartTime":62167.0,"Position":318.820038,"HyperDash":false}]},{"StartTime":62358.0,"Objects":[{"StartTime":62358.0,"Position":319.0,"HyperDash":false}]},{"StartTime":62548.0,"Objects":[{"StartTime":62548.0,"Position":240.0,"HyperDash":false},{"StartTime":62642.0,"Position":251.786285,"HyperDash":false},{"StartTime":62737.0,"Position":294.0,"HyperDash":false},{"StartTime":62814.0,"Position":280.200531,"HyperDash":false},{"StartTime":62927.0,"Position":240.0,"HyperDash":false}]},{"StartTime":63118.0,"Objects":[{"StartTime":63118.0,"Position":192.0,"HyperDash":false},{"StartTime":63203.0,"Position":179.84967,"HyperDash":false},{"StartTime":63289.0,"Position":137.426987,"HyperDash":false},{"StartTime":63375.0,"Position":126.0043,"HyperDash":false},{"StartTime":63497.0,"Position":88.77678,"HyperDash":false}]},{"StartTime":63687.0,"Objects":[{"StartTime":63687.0,"Position":176.0,"HyperDash":false}]},{"StartTime":63877.0,"Objects":[{"StartTime":63877.0,"Position":264.0,"HyperDash":false}]},{"StartTime":64067.0,"Objects":[{"StartTime":64067.0,"Position":352.0,"HyperDash":false},{"StartTime":64152.0,"Position":394.99942,"HyperDash":false},{"StartTime":64238.0,"Position":401.898163,"HyperDash":false},{"StartTime":64324.0,"Position":421.559357,"HyperDash":false},{"StartTime":64446.0,"Position":422.749817,"HyperDash":false}]},{"StartTime":64637.0,"Objects":[{"StartTime":64637.0,"Position":352.0,"HyperDash":false}]},{"StartTime":64826.0,"Objects":[{"StartTime":64826.0,"Position":272.0,"HyperDash":false},{"StartTime":64902.0,"Position":280.7143,"HyperDash":false},{"StartTime":65015.0,"Position":326.0,"HyperDash":false}]},{"StartTime":65206.0,"Objects":[{"StartTime":65206.0,"Position":326.0,"HyperDash":false}]},{"StartTime":65396.0,"Objects":[{"StartTime":65396.0,"Position":272.0,"HyperDash":false},{"StartTime":65481.0,"Position":237.667328,"HyperDash":false},{"StartTime":65567.0,"Position":221.486267,"HyperDash":false},{"StartTime":65653.0,"Position":177.3163,"HyperDash":false},{"StartTime":65775.0,"Position":167.831314,"HyperDash":false}]},{"StartTime":65966.0,"Objects":[{"StartTime":65966.0,"Position":104.0,"HyperDash":false}]},{"StartTime":66156.0,"Objects":[{"StartTime":66156.0,"Position":48.0,"HyperDash":false}]},{"StartTime":66345.0,"Objects":[{"StartTime":66345.0,"Position":104.0,"HyperDash":false}]},{"StartTime":66535.0,"Objects":[{"StartTime":66535.0,"Position":56.0,"HyperDash":false}]},{"StartTime":66630.0,"Objects":[{"StartTime":66630.0,"Position":80.0,"HyperDash":false}]},{"StartTime":66725.0,"Objects":[{"StartTime":66725.0,"Position":104.0,"HyperDash":false}]},{"StartTime":66915.0,"Objects":[{"StartTime":66915.0,"Position":192.0,"HyperDash":false}]},{"StartTime":67105.0,"Objects":[{"StartTime":67105.0,"Position":280.0,"HyperDash":false},{"StartTime":67190.0,"Position":314.967377,"HyperDash":false},{"StartTime":67276.0,"Position":332.346161,"HyperDash":false},{"StartTime":67362.0,"Position":371.7249,"HyperDash":false},{"StartTime":67484.0,"Position":435.9134,"HyperDash":false}]},{"StartTime":67675.0,"Objects":[{"StartTime":67675.0,"Position":448.0,"HyperDash":false}]},{"StartTime":67864.0,"Objects":[{"StartTime":67864.0,"Position":456.0,"HyperDash":false},{"StartTime":67949.0,"Position":419.88092,"HyperDash":false},{"StartTime":68035.0,"Position":402.3487,"HyperDash":false},{"StartTime":68121.0,"Position":361.816467,"HyperDash":false},{"StartTime":68243.0,"Position":299.410278,"HyperDash":false}]},{"StartTime":68434.0,"Objects":[{"StartTime":68434.0,"Position":288.0,"HyperDash":false}]},{"StartTime":68624.0,"Objects":[{"StartTime":68624.0,"Position":208.0,"HyperDash":false}]},{"StartTime":68814.0,"Objects":[{"StartTime":68814.0,"Position":128.0,"HyperDash":false}]},{"StartTime":69004.0,"Objects":[{"StartTime":69004.0,"Position":48.0,"HyperDash":false}]},{"StartTime":69194.0,"Objects":[{"StartTime":69194.0,"Position":128.0,"HyperDash":false},{"StartTime":69279.0,"Position":167.7291,"HyperDash":false},{"StartTime":69365.0,"Position":176.461563,"HyperDash":false},{"StartTime":69451.0,"Position":208.0288,"HyperDash":false},{"StartTime":69573.0,"Position":193.863922,"HyperDash":false}]},{"StartTime":69763.0,"Objects":[{"StartTime":69763.0,"Position":256.0,"HyperDash":false}]},{"StartTime":69953.0,"Objects":[{"StartTime":69953.0,"Position":256.0,"HyperDash":false}]},{"StartTime":70143.0,"Objects":[{"StartTime":70143.0,"Position":318.0,"HyperDash":false},{"StartTime":70228.0,"Position":307.361053,"HyperDash":false},{"StartTime":70314.0,"Position":339.541077,"HyperDash":false},{"StartTime":70400.0,"Position":329.926361,"HyperDash":false},{"StartTime":70522.0,"Position":382.366,"HyperDash":false}]},{"StartTime":70902.0,"Objects":[{"StartTime":70902.0,"Position":256.0,"HyperDash":false},{"StartTime":70973.0,"Position":237.22084,"HyperDash":false},{"StartTime":71044.0,"Position":261.5726,"HyperDash":false},{"StartTime":71115.0,"Position":256.317383,"HyperDash":false},{"StartTime":71186.0,"Position":259.616821,"HyperDash":false},{"StartTime":71248.0,"Position":284.489624,"HyperDash":false},{"StartTime":71310.0,"Position":249.320618,"HyperDash":false},{"StartTime":71372.0,"Position":249.26532,"HyperDash":false},{"StartTime":71471.0,"Position":256.0,"HyperDash":false}]},{"StartTime":71662.0,"Objects":[{"StartTime":71662.0,"Position":256.0,"HyperDash":false}]},{"StartTime":72042.0,"Objects":[{"StartTime":72042.0,"Position":256.0,"HyperDash":false}]},{"StartTime":72421.0,"Objects":[{"StartTime":72421.0,"Position":160.0,"HyperDash":false}]},{"StartTime":72611.0,"Objects":[{"StartTime":72611.0,"Position":224.0,"HyperDash":false}]},{"StartTime":72801.0,"Objects":[{"StartTime":72801.0,"Position":288.0,"HyperDash":false}]},{"StartTime":72991.0,"Objects":[{"StartTime":72991.0,"Position":352.0,"HyperDash":false}]},{"StartTime":73181.0,"Objects":[{"StartTime":73181.0,"Position":408.0,"HyperDash":false}]},{"StartTime":73371.0,"Objects":[{"StartTime":73371.0,"Position":304.0,"HyperDash":false}]},{"StartTime":73561.0,"Objects":[{"StartTime":73561.0,"Position":208.0,"HyperDash":false}]},{"StartTime":73751.0,"Objects":[{"StartTime":73751.0,"Position":112.0,"HyperDash":false}]},{"StartTime":73940.0,"Objects":[{"StartTime":73940.0,"Position":160.0,"HyperDash":false}]},{"StartTime":74130.0,"Objects":[{"StartTime":74130.0,"Position":224.0,"HyperDash":false}]},{"StartTime":74225.0,"Objects":[{"StartTime":74225.0,"Position":248.0,"HyperDash":false}]},{"StartTime":74320.0,"Objects":[{"StartTime":74320.0,"Position":272.0,"HyperDash":false}]},{"StartTime":74415.0,"Objects":[{"StartTime":74415.0,"Position":296.0,"HyperDash":false}]},{"StartTime":74510.0,"Objects":[{"StartTime":74510.0,"Position":320.0,"HyperDash":false}]},{"StartTime":74605.0,"Objects":[{"StartTime":74605.0,"Position":344.0,"HyperDash":false}]},{"StartTime":74700.0,"Objects":[{"StartTime":74700.0,"Position":368.0,"HyperDash":false},{"StartTime":74785.0,"Position":391.4436,"HyperDash":false},{"StartTime":74871.0,"Position":429.4646,"HyperDash":false},{"StartTime":74957.0,"Position":450.2139,"HyperDash":false},{"StartTime":75079.0,"Position":476.639343,"HyperDash":false}]},{"StartTime":75270.0,"Objects":[{"StartTime":75270.0,"Position":368.0,"HyperDash":false}]},{"StartTime":75459.0,"Objects":[{"StartTime":75459.0,"Position":296.0,"HyperDash":false},{"StartTime":75535.0,"Position":252.914215,"HyperDash":false},{"StartTime":75648.0,"Position":210.849869,"HyperDash":false}]},{"StartTime":75839.0,"Objects":[{"StartTime":75839.0,"Position":144.0,"HyperDash":false}]},{"StartTime":76029.0,"Objects":[{"StartTime":76029.0,"Position":168.0,"HyperDash":false},{"StartTime":76114.0,"Position":202.25589,"HyperDash":false},{"StartTime":76200.0,"Position":242.877075,"HyperDash":false},{"StartTime":76286.0,"Position":302.62854,"HyperDash":false},{"StartTime":76408.0,"Position":345.765717,"HyperDash":false}]},{"StartTime":76599.0,"Objects":[{"StartTime":76599.0,"Position":344.0,"HyperDash":false},{"StartTime":76684.0,"Position":307.766968,"HyperDash":false},{"StartTime":76770.0,"Position":252.272888,"HyperDash":false},{"StartTime":76856.0,"Position":211.514786,"HyperDash":false},{"StartTime":76978.0,"Position":167.090546,"HyperDash":false}]},{"StartTime":77168.0,"Objects":[{"StartTime":77168.0,"Position":256.0,"HyperDash":false}]},{"StartTime":77358.0,"Objects":[{"StartTime":77358.0,"Position":256.0,"HyperDash":false}]},{"StartTime":77548.0,"Objects":[{"StartTime":77548.0,"Position":424.0,"HyperDash":false},{"StartTime":77633.0,"Position":417.615356,"HyperDash":false},{"StartTime":77719.0,"Position":433.1576,"HyperDash":false},{"StartTime":77805.0,"Position":439.338928,"HyperDash":false},{"StartTime":77927.0,"Position":425.7557,"HyperDash":false}]},{"StartTime":78118.0,"Objects":[{"StartTime":78118.0,"Position":296.0,"HyperDash":false},{"StartTime":78194.0,"Position":289.17218,"HyperDash":false},{"StartTime":78307.0,"Position":326.270264,"HyperDash":false}]},{"StartTime":78497.0,"Objects":[{"StartTime":78497.0,"Position":240.0,"HyperDash":false},{"StartTime":78573.0,"Position":252.172165,"HyperDash":false},{"StartTime":78686.0,"Position":270.270264,"HyperDash":false}]},{"StartTime":78877.0,"Objects":[{"StartTime":78877.0,"Position":168.0,"HyperDash":false},{"StartTime":78953.0,"Position":193.367844,"HyperDash":false},{"StartTime":79066.0,"Position":198.756882,"HyperDash":false}]},{"StartTime":79257.0,"Objects":[{"StartTime":79257.0,"Position":104.0,"HyperDash":false},{"StartTime":79333.0,"Position":113.367844,"HyperDash":false},{"StartTime":79446.0,"Position":134.756882,"HyperDash":false}]},{"StartTime":79637.0,"Objects":[{"StartTime":79637.0,"Position":48.0,"HyperDash":false},{"StartTime":79731.0,"Position":47.97381,"HyperDash":false},{"StartTime":79826.0,"Position":15.6918831,"HyperDash":false},{"StartTime":79903.0,"Position":19.7344723,"HyperDash":false},{"StartTime":80016.0,"Position":48.0,"HyperDash":false}]},{"StartTime":80206.0,"Objects":[{"StartTime":80206.0,"Position":48.0,"HyperDash":false}]},{"StartTime":80396.0,"Objects":[{"StartTime":80396.0,"Position":48.0,"HyperDash":false}]},{"StartTime":80586.0,"Objects":[{"StartTime":80586.0,"Position":48.0,"HyperDash":false},{"StartTime":80671.0,"Position":50.6653442,"HyperDash":false},{"StartTime":80757.0,"Position":72.3398361,"HyperDash":false},{"StartTime":80843.0,"Position":138.798065,"HyperDash":false},{"StartTime":80965.0,"Position":177.234756,"HyperDash":false}]},{"StartTime":81156.0,"Objects":[{"StartTime":81156.0,"Position":334.0,"HyperDash":false},{"StartTime":81241.0,"Position":384.0748,"HyperDash":false},{"StartTime":81327.0,"Position":430.608582,"HyperDash":false},{"StartTime":81413.0,"Position":423.721344,"HyperDash":false},{"StartTime":81535.0,"Position":463.472778,"HyperDash":false}]},{"StartTime":81725.0,"Objects":[{"StartTime":81725.0,"Position":256.0,"HyperDash":false}]},{"StartTime":81915.0,"Objects":[{"StartTime":81915.0,"Position":256.0,"HyperDash":false}]},{"StartTime":82105.0,"Objects":[{"StartTime":82105.0,"Position":48.0,"HyperDash":false},{"StartTime":82190.0,"Position":55.6653442,"HyperDash":false},{"StartTime":82276.0,"Position":92.3398361,"HyperDash":false},{"StartTime":82362.0,"Position":132.798065,"HyperDash":false},{"StartTime":82484.0,"Position":177.234756,"HyperDash":false}]},{"StartTime":82675.0,"Objects":[{"StartTime":82675.0,"Position":334.0,"HyperDash":false},{"StartTime":82760.0,"Position":361.0748,"HyperDash":false},{"StartTime":82846.0,"Position":397.608582,"HyperDash":false},{"StartTime":82932.0,"Position":421.721344,"HyperDash":false},{"StartTime":83054.0,"Position":463.472778,"HyperDash":false}]},{"StartTime":83244.0,"Objects":[{"StartTime":83244.0,"Position":256.0,"HyperDash":false}]},{"StartTime":83434.0,"Objects":[{"StartTime":83434.0,"Position":256.0,"HyperDash":false}]},{"StartTime":83624.0,"Objects":[{"StartTime":83624.0,"Position":177.0,"HyperDash":false},{"StartTime":83709.0,"Position":139.9757,"HyperDash":false},{"StartTime":83795.0,"Position":85.66393,"HyperDash":false},{"StartTime":83881.0,"Position":76.88606,"HyperDash":false},{"StartTime":84003.0,"Position":48.41881,"HyperDash":false}]},{"StartTime":84194.0,"Objects":[{"StartTime":84194.0,"Position":240.0,"HyperDash":false},{"StartTime":84270.0,"Position":217.612869,"HyperDash":false},{"StartTime":84383.0,"Position":151.997787,"HyperDash":false}]},{"StartTime":84573.0,"Objects":[{"StartTime":84573.0,"Position":40.0,"HyperDash":false},{"StartTime":84649.0,"Position":65.48768,"HyperDash":false},{"StartTime":84762.0,"Position":128.252258,"HyperDash":false}]},{"StartTime":84953.0,"Objects":[{"StartTime":84953.0,"Position":280.0,"HyperDash":false},{"StartTime":85029.0,"Position":237.890076,"HyperDash":false},{"StartTime":85142.0,"Position":192.68718,"HyperDash":false}]},{"StartTime":85333.0,"Objects":[{"StartTime":85333.0,"Position":392.0,"HyperDash":false},{"StartTime":85392.0,"Position":335.0,"HyperDash":false},{"StartTime":85451.0,"Position":193.0,"HyperDash":false},{"StartTime":85510.0,"Position":478.0,"HyperDash":false},{"StartTime":85570.0,"Position":255.0,"HyperDash":false},{"StartTime":85629.0,"Position":175.0,"HyperDash":false},{"StartTime":85688.0,"Position":274.0,"HyperDash":false},{"StartTime":85748.0,"Position":442.0,"HyperDash":false},{"StartTime":85807.0,"Position":295.0,"HyperDash":false},{"StartTime":85866.0,"Position":311.0,"HyperDash":false},{"StartTime":85926.0,"Position":17.0,"HyperDash":false},{"StartTime":85985.0,"Position":467.0,"HyperDash":false},{"StartTime":86044.0,"Position":30.0,"HyperDash":false},{"StartTime":86104.0,"Position":218.0,"HyperDash":false},{"StartTime":86163.0,"Position":26.0,"HyperDash":false},{"StartTime":86222.0,"Position":16.0,"HyperDash":false},{"StartTime":86282.0,"Position":248.0,"HyperDash":false}]},{"StartTime":86472.0,"Objects":[{"StartTime":86472.0,"Position":256.0,"HyperDash":false}]},{"StartTime":86662.0,"Objects":[{"StartTime":86662.0,"Position":128.0,"HyperDash":false}]},{"StartTime":86757.0,"Objects":[{"StartTime":86757.0,"Position":152.0,"HyperDash":false}]},{"StartTime":86852.0,"Objects":[{"StartTime":86852.0,"Position":176.0,"HyperDash":false},{"StartTime":86928.0,"Position":199.190475,"HyperDash":false},{"StartTime":87041.0,"Position":266.0,"HyperDash":false}]},{"StartTime":87232.0,"Objects":[{"StartTime":87232.0,"Position":360.0,"HyperDash":false},{"StartTime":87317.0,"Position":331.134338,"HyperDash":false},{"StartTime":87403.0,"Position":283.893768,"HyperDash":false},{"StartTime":87489.0,"Position":250.155975,"HyperDash":false},{"StartTime":87611.0,"Position":199.214035,"HyperDash":false}]},{"StartTime":87801.0,"Objects":[{"StartTime":87801.0,"Position":136.0,"HyperDash":false},{"StartTime":87877.0,"Position":153.190475,"HyperDash":false},{"StartTime":87990.0,"Position":226.0,"HyperDash":false}]},{"StartTime":88181.0,"Objects":[{"StartTime":88181.0,"Position":440.0,"HyperDash":false},{"StartTime":88266.0,"Position":401.6306,"HyperDash":false},{"StartTime":88352.0,"Position":361.417969,"HyperDash":false},{"StartTime":88438.0,"Position":315.9722,"HyperDash":false},{"StartTime":88560.0,"Position":286.761566,"HyperDash":false}]},{"StartTime":88751.0,"Objects":[{"StartTime":88751.0,"Position":72.0,"HyperDash":false},{"StartTime":88836.0,"Position":97.36939,"HyperDash":false},{"StartTime":88922.0,"Position":151.554047,"HyperDash":false},{"StartTime":89008.0,"Position":175.23497,"HyperDash":false},{"StartTime":89130.0,"Position":225.445587,"HyperDash":false}]},{"StartTime":89320.0,"Objects":[{"StartTime":89320.0,"Position":256.0,"HyperDash":false},{"StartTime":89414.0,"Position":256.0,"HyperDash":false},{"StartTime":89509.0,"Position":256.0,"HyperDash":false}]},{"StartTime":89700.0,"Objects":[{"StartTime":89700.0,"Position":488.0,"HyperDash":false},{"StartTime":89785.0,"Position":441.7927,"HyperDash":false},{"StartTime":89871.0,"Position":394.103729,"HyperDash":false},{"StartTime":89957.0,"Position":358.440735,"HyperDash":false},{"StartTime":90079.0,"Position":314.813538,"HyperDash":false}]},{"StartTime":90270.0,"Objects":[{"StartTime":90270.0,"Position":256.0,"HyperDash":false}]},{"StartTime":90459.0,"Objects":[{"StartTime":90459.0,"Position":160.0,"HyperDash":false}]},{"StartTime":90649.0,"Objects":[{"StartTime":90649.0,"Position":64.0,"HyperDash":false}]},{"StartTime":90839.0,"Objects":[{"StartTime":90839.0,"Position":160.0,"HyperDash":false}]},{"StartTime":91029.0,"Objects":[{"StartTime":91029.0,"Position":256.0,"HyperDash":false}]},{"StartTime":91219.0,"Objects":[{"StartTime":91219.0,"Position":352.0,"HyperDash":false}]},{"StartTime":91409.0,"Objects":[{"StartTime":91409.0,"Position":448.0,"HyperDash":false}]},{"StartTime":91599.0,"Objects":[{"StartTime":91599.0,"Position":352.0,"HyperDash":false}]},{"StartTime":91788.0,"Objects":[{"StartTime":91788.0,"Position":256.0,"HyperDash":false}]},{"StartTime":91978.0,"Objects":[{"StartTime":91978.0,"Position":256.0,"HyperDash":false}]},{"StartTime":92168.0,"Objects":[{"StartTime":92168.0,"Position":256.0,"HyperDash":false}]},{"StartTime":92358.0,"Objects":[{"StartTime":92358.0,"Position":256.0,"HyperDash":false},{"StartTime":92434.0,"Position":250.0,"HyperDash":false},{"StartTime":92547.0,"Position":256.0,"HyperDash":false}]},{"StartTime":92738.0,"Objects":[{"StartTime":92738.0,"Position":32.0,"HyperDash":false},{"StartTime":92823.0,"Position":61.3693924,"HyperDash":false},{"StartTime":92909.0,"Position":113.213722,"HyperDash":false},{"StartTime":92995.0,"Position":137.112122,"HyperDash":false},{"StartTime":93117.0,"Position":192.083252,"HyperDash":false}]},{"StartTime":93307.0,"Objects":[{"StartTime":93307.0,"Position":64.0,"HyperDash":false},{"StartTime":93383.0,"Position":90.59053,"HyperDash":false},{"StartTime":93496.0,"Position":127.639618,"HyperDash":false}]},{"StartTime":93687.0,"Objects":[{"StartTime":93687.0,"Position":256.0,"HyperDash":false},{"StartTime":93763.0,"Position":296.590546,"HyperDash":false},{"StartTime":93876.0,"Position":319.639618,"HyperDash":false}]},{"StartTime":94067.0,"Objects":[{"StartTime":94067.0,"Position":424.0,"HyperDash":false}]},{"StartTime":94257.0,"Objects":[{"StartTime":94257.0,"Position":256.0,"HyperDash":false},{"StartTime":94342.0,"Position":210.766815,"HyperDash":false},{"StartTime":94428.0,"Position":186.0,"HyperDash":false},{"StartTime":94514.0,"Position":209.0,"HyperDash":false},{"StartTime":94636.0,"Position":192.0,"HyperDash":false}]},{"StartTime":94826.0,"Objects":[{"StartTime":94826.0,"Position":328.0,"HyperDash":false},{"StartTime":94920.0,"Position":353.6438,"HyperDash":false},{"StartTime":95015.0,"Position":418.0,"HyperDash":false},{"StartTime":95092.0,"Position":396.667542,"HyperDash":false},{"StartTime":95205.0,"Position":328.0,"HyperDash":false}]},{"StartTime":95396.0,"Objects":[{"StartTime":95396.0,"Position":328.0,"HyperDash":false}]},{"StartTime":95586.0,"Objects":[{"StartTime":95586.0,"Position":328.0,"HyperDash":false}]},{"StartTime":95776.0,"Objects":[{"StartTime":95776.0,"Position":192.0,"HyperDash":false},{"StartTime":95861.0,"Position":170.3153,"HyperDash":false},{"StartTime":95947.0,"Position":94.6082153,"HyperDash":false},{"StartTime":96033.0,"Position":54.801857,"HyperDash":false},{"StartTime":96155.0,"Position":13.5809069,"HyperDash":false}]},{"StartTime":96345.0,"Objects":[{"StartTime":96345.0,"Position":56.0,"HyperDash":false},{"StartTime":96421.0,"Position":104.190475,"HyperDash":false},{"StartTime":96534.0,"Position":146.0,"HyperDash":false}]},{"StartTime":96725.0,"Objects":[{"StartTime":96725.0,"Position":232.0,"HyperDash":false}]},{"StartTime":96915.0,"Objects":[{"StartTime":96915.0,"Position":280.0,"HyperDash":false}]},{"StartTime":97105.0,"Objects":[{"StartTime":97105.0,"Position":360.0,"HyperDash":false},{"StartTime":97181.0,"Position":408.1905,"HyperDash":false},{"StartTime":97294.0,"Position":450.0,"HyperDash":false}]},{"StartTime":97485.0,"Objects":[{"StartTime":97485.0,"Position":458.0,"HyperDash":false},{"StartTime":97579.0,"Position":425.0,"HyperDash":false},{"StartTime":97674.0,"Position":466.0,"HyperDash":false},{"StartTime":97769.0,"Position":56.0,"HyperDash":false},{"StartTime":97864.0,"Position":109.0,"HyperDash":false},{"StartTime":97959.0,"Position":482.0,"HyperDash":false},{"StartTime":98054.0,"Position":147.0,"HyperDash":false},{"StartTime":98149.0,"Position":285.0,"HyperDash":false},{"StartTime":98244.0,"Position":452.0,"HyperDash":false},{"StartTime":98339.0,"Position":419.0,"HyperDash":false},{"StartTime":98434.0,"Position":269.0,"HyperDash":false},{"StartTime":98529.0,"Position":249.0,"HyperDash":false},{"StartTime":98624.0,"Position":233.0,"HyperDash":false},{"StartTime":98719.0,"Position":449.0,"HyperDash":false},{"StartTime":98814.0,"Position":411.0,"HyperDash":false},{"StartTime":98909.0,"Position":75.0,"HyperDash":false},{"StartTime":99004.0,"Position":474.0,"HyperDash":false}]},{"StartTime":111156.0,"Objects":[{"StartTime":111156.0,"Position":256.0,"HyperDash":false}]},{"StartTime":111915.0,"Objects":[{"StartTime":111915.0,"Position":256.0,"HyperDash":false}]},{"StartTime":112105.0,"Objects":[{"StartTime":112105.0,"Position":256.0,"HyperDash":false}]},{"StartTime":112295.0,"Objects":[{"StartTime":112295.0,"Position":256.0,"HyperDash":false}]},{"StartTime":112485.0,"Objects":[{"StartTime":112485.0,"Position":256.0,"HyperDash":false}]},{"StartTime":112675.0,"Objects":[{"StartTime":112675.0,"Position":328.0,"HyperDash":false},{"StartTime":112760.0,"Position":361.17868,"HyperDash":false},{"StartTime":112846.0,"Position":407.7525,"HyperDash":false},{"StartTime":112932.0,"Position":421.257233,"HyperDash":false},{"StartTime":113054.0,"Position":455.379,"HyperDash":false}]},{"StartTime":113244.0,"Objects":[{"StartTime":113244.0,"Position":456.0,"HyperDash":false}]},{"StartTime":113434.0,"Objects":[{"StartTime":113434.0,"Position":456.0,"HyperDash":false}]},{"StartTime":113624.0,"Objects":[{"StartTime":113624.0,"Position":368.0,"HyperDash":false},{"StartTime":113718.0,"Position":305.349274,"HyperDash":false},{"StartTime":113813.0,"Position":287.706543,"HyperDash":false},{"StartTime":113890.0,"Position":296.162018,"HyperDash":false},{"StartTime":114003.0,"Position":368.0,"HyperDash":false}]},{"StartTime":114194.0,"Objects":[{"StartTime":114194.0,"Position":456.0,"HyperDash":false},{"StartTime":114279.0,"Position":435.479034,"HyperDash":false},{"StartTime":114365.0,"Position":402.81424,"HyperDash":false},{"StartTime":114451.0,"Position":376.903717,"HyperDash":false},{"StartTime":114573.0,"Position":310.688843,"HyperDash":false}]},{"StartTime":114763.0,"Objects":[{"StartTime":114763.0,"Position":256.0,"HyperDash":false},{"StartTime":114839.0,"Position":203.173843,"HyperDash":false},{"StartTime":114952.0,"Position":176.330536,"HyperDash":false}]},{"StartTime":115143.0,"Objects":[{"StartTime":115143.0,"Position":112.0,"HyperDash":false}]},{"StartTime":115333.0,"Objects":[{"StartTime":115333.0,"Position":176.0,"HyperDash":false}]},{"StartTime":115523.0,"Objects":[{"StartTime":115523.0,"Position":240.0,"HyperDash":false}]},{"StartTime":115713.0,"Objects":[{"StartTime":115713.0,"Position":176.0,"HyperDash":false},{"StartTime":115798.0,"Position":197.9177,"HyperDash":false},{"StartTime":115884.0,"Position":227.720581,"HyperDash":false},{"StartTime":115970.0,"Position":273.524536,"HyperDash":false},{"StartTime":116092.0,"Position":328.682556,"HyperDash":false}]},{"StartTime":116282.0,"Objects":[{"StartTime":116282.0,"Position":296.0,"HyperDash":false}]},{"StartTime":116472.0,"Objects":[{"StartTime":116472.0,"Position":360.0,"HyperDash":false}]},{"StartTime":116662.0,"Objects":[{"StartTime":116662.0,"Position":448.0,"HyperDash":false},{"StartTime":116738.0,"Position":439.409454,"HyperDash":false},{"StartTime":116851.0,"Position":384.360382,"HyperDash":false}]},{"StartTime":117042.0,"Objects":[{"StartTime":117042.0,"Position":384.0,"HyperDash":false},{"StartTime":117127.0,"Position":354.734955,"HyperDash":false},{"StartTime":117213.0,"Position":299.665924,"HyperDash":false},{"StartTime":117299.0,"Position":257.5697,"HyperDash":false},{"StartTime":117421.0,"Position":234.549561,"HyperDash":false}]},{"StartTime":117611.0,"Objects":[{"StartTime":117611.0,"Position":280.0,"HyperDash":false},{"StartTime":117687.0,"Position":309.4148,"HyperDash":false},{"StartTime":117800.0,"Position":286.3127,"HyperDash":false}]},{"StartTime":117991.0,"Objects":[{"StartTime":117991.0,"Position":192.0,"HyperDash":false},{"StartTime":118067.0,"Position":177.6565,"HyperDash":false},{"StartTime":118180.0,"Position":196.931625,"HyperDash":false}]},{"StartTime":118561.0,"Objects":[{"StartTime":118561.0,"Position":248.0,"HyperDash":false}]},{"StartTime":118940.0,"Objects":[{"StartTime":118940.0,"Position":248.0,"HyperDash":false}]},{"StartTime":119320.0,"Objects":[{"StartTime":119320.0,"Position":248.0,"HyperDash":false}]},{"StartTime":119700.0,"Objects":[{"StartTime":119700.0,"Position":448.0,"HyperDash":false}]},{"StartTime":119890.0,"Objects":[{"StartTime":119890.0,"Position":384.0,"HyperDash":false}]},{"StartTime":120080.0,"Objects":[{"StartTime":120080.0,"Position":320.0,"HyperDash":false}]},{"StartTime":120270.0,"Objects":[{"StartTime":120270.0,"Position":256.0,"HyperDash":false},{"StartTime":120355.0,"Position":213.1814,"HyperDash":false},{"StartTime":120441.0,"Position":169.527481,"HyperDash":false},{"StartTime":120527.0,"Position":146.788452,"HyperDash":false},{"StartTime":120649.0,"Position":78.92116,"HyperDash":false}]},{"StartTime":120839.0,"Objects":[{"StartTime":120839.0,"Position":80.0,"HyperDash":false}]},{"StartTime":121219.0,"Objects":[{"StartTime":121219.0,"Position":32.0,"HyperDash":false}]},{"StartTime":121409.0,"Objects":[{"StartTime":121409.0,"Position":120.0,"HyperDash":false}]},{"StartTime":121599.0,"Objects":[{"StartTime":121599.0,"Position":208.0,"HyperDash":false}]},{"StartTime":121788.0,"Objects":[{"StartTime":121788.0,"Position":296.0,"HyperDash":false},{"StartTime":121873.0,"Position":324.8186,"HyperDash":false},{"StartTime":121959.0,"Position":394.4725,"HyperDash":false},{"StartTime":122045.0,"Position":403.211548,"HyperDash":false},{"StartTime":122167.0,"Position":473.078827,"HyperDash":false}]},{"StartTime":122358.0,"Objects":[{"StartTime":122358.0,"Position":472.0,"HyperDash":false}]},{"StartTime":122738.0,"Objects":[{"StartTime":122738.0,"Position":208.0,"HyperDash":false}]},{"StartTime":122928.0,"Objects":[{"StartTime":122928.0,"Position":256.0,"HyperDash":false}]},{"StartTime":123117.0,"Objects":[{"StartTime":123117.0,"Position":304.0,"HyperDash":false}]},{"StartTime":123307.0,"Objects":[{"StartTime":123307.0,"Position":256.0,"HyperDash":false}]},{"StartTime":123687.0,"Objects":[{"StartTime":123687.0,"Position":256.0,"HyperDash":false}]},{"StartTime":124067.0,"Objects":[{"StartTime":124067.0,"Position":256.0,"HyperDash":false}]},{"StartTime":124257.0,"Objects":[{"StartTime":124257.0,"Position":256.0,"HyperDash":false}]},{"StartTime":124447.0,"Objects":[{"StartTime":124447.0,"Position":256.0,"HyperDash":false}]},{"StartTime":124637.0,"Objects":[{"StartTime":124637.0,"Position":256.0,"HyperDash":false}]},{"StartTime":124732.0,"Objects":[{"StartTime":124732.0,"Position":256.0,"HyperDash":false}]},{"StartTime":124826.0,"Objects":[{"StartTime":124826.0,"Position":256.0,"HyperDash":false},{"StartTime":124911.0,"Position":294.15155,"HyperDash":false},{"StartTime":124997.0,"Position":348.2999,"HyperDash":false},{"StartTime":125083.0,"Position":367.688416,"HyperDash":false},{"StartTime":125205.0,"Position":375.982025,"HyperDash":false}]},{"StartTime":125396.0,"Objects":[{"StartTime":125396.0,"Position":456.0,"HyperDash":false}]},{"StartTime":125586.0,"Objects":[{"StartTime":125586.0,"Position":392.0,"HyperDash":false}]},{"StartTime":125776.0,"Objects":[{"StartTime":125776.0,"Position":304.0,"HyperDash":false},{"StartTime":125852.0,"Position":263.17807,"HyperDash":false},{"StartTime":125965.0,"Position":227.350739,"HyperDash":false}]},{"StartTime":126156.0,"Objects":[{"StartTime":126156.0,"Position":192.0,"HyperDash":false}]},{"StartTime":126345.0,"Objects":[{"StartTime":126345.0,"Position":160.0,"HyperDash":false},{"StartTime":126430.0,"Position":126.124176,"HyperDash":false},{"StartTime":126516.0,"Position":85.81763,"HyperDash":false},{"StartTime":126602.0,"Position":37.2244949,"HyperDash":false},{"StartTime":126724.0,"Position":32.57615,"HyperDash":false}]},{"StartTime":126915.0,"Objects":[{"StartTime":126915.0,"Position":120.0,"HyperDash":false},{"StartTime":126991.0,"Position":102.400024,"HyperDash":false},{"StartTime":127104.0,"Position":68.7711,"HyperDash":false}]},{"StartTime":127295.0,"Objects":[{"StartTime":127295.0,"Position":136.0,"HyperDash":false},{"StartTime":127389.0,"Position":114.741783,"HyperDash":false},{"StartTime":127484.0,"Position":83.06455,"HyperDash":false},{"StartTime":127561.0,"Position":120.434265,"HyperDash":false},{"StartTime":127674.0,"Position":136.0,"HyperDash":false}]},{"StartTime":127864.0,"Objects":[{"StartTime":127864.0,"Position":184.0,"HyperDash":false},{"StartTime":127949.0,"Position":200.744141,"HyperDash":false},{"StartTime":128035.0,"Position":221.767609,"HyperDash":false},{"StartTime":128121.0,"Position":262.7911,"HyperDash":false},{"StartTime":128243.0,"Position":289.8709,"HyperDash":false}]},{"StartTime":128434.0,"Objects":[{"StartTime":128434.0,"Position":384.0,"HyperDash":false}]},{"StartTime":128624.0,"Objects":[{"StartTime":128624.0,"Position":448.0,"HyperDash":false}]},{"StartTime":128814.0,"Objects":[{"StartTime":128814.0,"Position":448.0,"HyperDash":false},{"StartTime":128890.0,"Position":398.135345,"HyperDash":false},{"StartTime":129003.0,"Position":368.7576,"HyperDash":false}]},{"StartTime":129194.0,"Objects":[{"StartTime":129194.0,"Position":440.0,"HyperDash":false},{"StartTime":129279.0,"Position":389.377838,"HyperDash":false},{"StartTime":129365.0,"Position":370.438324,"HyperDash":false},{"StartTime":129451.0,"Position":329.820526,"HyperDash":false},{"StartTime":129573.0,"Position":267.138855,"HyperDash":false}]},{"StartTime":129763.0,"Objects":[{"StartTime":129763.0,"Position":208.0,"HyperDash":false}]},{"StartTime":129953.0,"Objects":[{"StartTime":129953.0,"Position":128.0,"HyperDash":false}]},{"StartTime":130143.0,"Objects":[{"StartTime":130143.0,"Position":208.0,"HyperDash":false}]},{"StartTime":130333.0,"Objects":[{"StartTime":130333.0,"Position":288.0,"HyperDash":false},{"StartTime":130409.0,"Position":333.1905,"HyperDash":false},{"StartTime":130522.0,"Position":378.0,"HyperDash":false}]},{"StartTime":130713.0,"Objects":[{"StartTime":130713.0,"Position":448.0,"HyperDash":false},{"StartTime":130789.0,"Position":411.8095,"HyperDash":false},{"StartTime":130902.0,"Position":358.0,"HyperDash":false}]},{"StartTime":131282.0,"Objects":[{"StartTime":131282.0,"Position":176.0,"HyperDash":false}]},{"StartTime":131662.0,"Objects":[{"StartTime":131662.0,"Position":360.0,"HyperDash":false}]},{"StartTime":131852.0,"Objects":[{"StartTime":131852.0,"Position":288.0,"HyperDash":false}]},{"StartTime":132042.0,"Objects":[{"StartTime":132042.0,"Position":200.0,"HyperDash":false}]},{"StartTime":132232.0,"Objects":[{"StartTime":132232.0,"Position":112.0,"HyperDash":false}]},{"StartTime":132421.0,"Objects":[{"StartTime":132421.0,"Position":96.0,"HyperDash":false},{"StartTime":132487.0,"Position":49.5101624,"HyperDash":false},{"StartTime":132554.0,"Position":57.3071823,"HyperDash":false},{"StartTime":132620.0,"Position":26.5927753,"HyperDash":false},{"StartTime":132687.0,"Position":15.30433,"HyperDash":false},{"StartTime":132754.0,"Position":15.7045517,"HyperDash":false},{"StartTime":132820.0,"Position":49.43814,"HyperDash":false},{"StartTime":132887.0,"Position":66.86148,"HyperDash":false},{"StartTime":132990.0,"Position":96.71054,"HyperDash":false}]},{"StartTime":133371.0,"Objects":[{"StartTime":133371.0,"Position":224.0,"HyperDash":false}]},{"StartTime":133561.0,"Objects":[{"StartTime":133561.0,"Position":312.0,"HyperDash":false}]},{"StartTime":133751.0,"Objects":[{"StartTime":133751.0,"Position":400.0,"HyperDash":false}]},{"StartTime":133940.0,"Objects":[{"StartTime":133940.0,"Position":416.0,"HyperDash":false},{"StartTime":134006.0,"Position":452.489838,"HyperDash":false},{"StartTime":134073.0,"Position":482.6928,"HyperDash":false},{"StartTime":134139.0,"Position":473.407227,"HyperDash":false},{"StartTime":134206.0,"Position":502.695679,"HyperDash":false},{"StartTime":134273.0,"Position":505.295471,"HyperDash":false},{"StartTime":134339.0,"Position":462.561859,"HyperDash":false},{"StartTime":134406.0,"Position":475.138519,"HyperDash":false},{"StartTime":134509.0,"Position":415.289459,"HyperDash":false}]},{"StartTime":134890.0,"Objects":[{"StartTime":134890.0,"Position":80.0,"HyperDash":false}]},{"StartTime":135080.0,"Objects":[{"StartTime":135080.0,"Position":160.0,"HyperDash":false}]},{"StartTime":135270.0,"Objects":[{"StartTime":135270.0,"Position":200.0,"HyperDash":false}]},{"StartTime":135459.0,"Objects":[{"StartTime":135459.0,"Position":280.0,"HyperDash":false}]},{"StartTime":135839.0,"Objects":[{"StartTime":135839.0,"Position":464.0,"HyperDash":false}]},{"StartTime":136029.0,"Objects":[{"StartTime":136029.0,"Position":376.0,"HyperDash":false}]},{"StartTime":136219.0,"Objects":[{"StartTime":136219.0,"Position":376.0,"HyperDash":false}]},{"StartTime":136409.0,"Objects":[{"StartTime":136409.0,"Position":280.0,"HyperDash":false}]},{"StartTime":136599.0,"Objects":[{"StartTime":136599.0,"Position":280.0,"HyperDash":false}]},{"StartTime":136978.0,"Objects":[{"StartTime":136978.0,"Position":56.0,"HyperDash":false},{"StartTime":137063.0,"Position":98.33999,"HyperDash":false},{"StartTime":137149.0,"Position":121.429718,"HyperDash":false},{"StartTime":137235.0,"Position":184.982086,"HyperDash":false},{"StartTime":137357.0,"Position":227.214722,"HyperDash":false}]},{"StartTime":137738.0,"Objects":[{"StartTime":137738.0,"Position":456.0,"HyperDash":false},{"StartTime":137823.0,"Position":411.66,"HyperDash":false},{"StartTime":137909.0,"Position":389.570282,"HyperDash":false},{"StartTime":137995.0,"Position":336.0179,"HyperDash":false},{"StartTime":138117.0,"Position":284.785278,"HyperDash":false}]},{"StartTime":138497.0,"Objects":[{"StartTime":138497.0,"Position":256.0,"HyperDash":false}]},{"StartTime":138687.0,"Objects":[{"StartTime":138687.0,"Position":200.0,"HyperDash":false}]},{"StartTime":138877.0,"Objects":[{"StartTime":138877.0,"Position":256.0,"HyperDash":false}]},{"StartTime":139067.0,"Objects":[{"StartTime":139067.0,"Position":312.0,"HyperDash":false},{"StartTime":139143.0,"Position":331.1905,"HyperDash":false},{"StartTime":139256.0,"Position":402.0,"HyperDash":false}]},{"StartTime":139447.0,"Objects":[{"StartTime":139447.0,"Position":400.0,"HyperDash":false},{"StartTime":139541.0,"Position":424.6438,"HyperDash":false},{"StartTime":139636.0,"Position":490.0,"HyperDash":false},{"StartTime":139713.0,"Position":436.667542,"HyperDash":false},{"StartTime":139826.0,"Position":400.0,"HyperDash":false}]},{"StartTime":140016.0,"Objects":[{"StartTime":140016.0,"Position":400.0,"HyperDash":false},{"StartTime":140101.0,"Position":405.018951,"HyperDash":false},{"StartTime":140187.0,"Position":337.8755,"HyperDash":false},{"StartTime":140273.0,"Position":336.351257,"HyperDash":false},{"StartTime":140395.0,"Position":259.5054,"HyperDash":false}]},{"StartTime":140586.0,"Objects":[{"StartTime":140586.0,"Position":224.0,"HyperDash":false}]},{"StartTime":140776.0,"Objects":[{"StartTime":140776.0,"Position":296.0,"HyperDash":false}]},{"StartTime":140966.0,"Objects":[{"StartTime":140966.0,"Position":224.0,"HyperDash":false}]},{"StartTime":141156.0,"Objects":[{"StartTime":141156.0,"Position":296.0,"HyperDash":false}]},{"StartTime":141345.0,"Objects":[{"StartTime":141345.0,"Position":256.0,"HyperDash":false},{"StartTime":141430.0,"Position":196.648087,"HyperDash":false},{"StartTime":141516.0,"Position":175.249878,"HyperDash":false},{"StartTime":141602.0,"Position":133.184525,"HyperDash":false},{"StartTime":141724.0,"Position":114.597687,"HyperDash":false}]},{"StartTime":141915.0,"Objects":[{"StartTime":141915.0,"Position":112.0,"HyperDash":false},{"StartTime":142009.0,"Position":98.0,"HyperDash":false},{"StartTime":142104.0,"Position":112.0,"HyperDash":false},{"StartTime":142181.0,"Position":98.0,"HyperDash":false},{"StartTime":142294.0,"Position":112.0,"HyperDash":false}]},{"StartTime":142485.0,"Objects":[{"StartTime":142485.0,"Position":112.0,"HyperDash":false}]},{"StartTime":142580.0,"Objects":[{"StartTime":142580.0,"Position":112.0,"HyperDash":false}]},{"StartTime":142675.0,"Objects":[{"StartTime":142675.0,"Position":112.0,"HyperDash":false}]},{"StartTime":142864.0,"Objects":[{"StartTime":142864.0,"Position":112.0,"HyperDash":false}]},{"StartTime":143054.0,"Objects":[{"StartTime":143054.0,"Position":232.0,"HyperDash":false},{"StartTime":143139.0,"Position":225.714432,"HyperDash":false},{"StartTime":143225.0,"Position":180.464615,"HyperDash":false},{"StartTime":143311.0,"Position":216.858948,"HyperDash":false},{"StartTime":143433.0,"Position":221.927963,"HyperDash":false}]},{"StartTime":143814.0,"Objects":[{"StartTime":143814.0,"Position":280.0,"HyperDash":false},{"StartTime":143899.0,"Position":293.285583,"HyperDash":false},{"StartTime":143985.0,"Position":317.53537,"HyperDash":false},{"StartTime":144071.0,"Position":329.141052,"HyperDash":false},{"StartTime":144193.0,"Position":290.072052,"HyperDash":false}]},{"StartTime":144573.0,"Objects":[{"StartTime":144573.0,"Position":256.0,"HyperDash":false}]},{"StartTime":144763.0,"Objects":[{"StartTime":144763.0,"Position":344.0,"HyperDash":false}]},{"StartTime":144953.0,"Objects":[{"StartTime":144953.0,"Position":416.0,"HyperDash":false}]},{"StartTime":145143.0,"Objects":[{"StartTime":145143.0,"Position":416.0,"HyperDash":false},{"StartTime":145228.0,"Position":392.6306,"HyperDash":false},{"StartTime":145314.0,"Position":338.7863,"HyperDash":false},{"StartTime":145400.0,"Position":289.941956,"HyperDash":false},{"StartTime":145522.0,"Position":236.0,"HyperDash":false}]},{"StartTime":145713.0,"Objects":[{"StartTime":145713.0,"Position":144.0,"HyperDash":false}]},{"StartTime":145902.0,"Objects":[{"StartTime":145902.0,"Position":80.0,"HyperDash":false}]},{"StartTime":146092.0,"Objects":[{"StartTime":146092.0,"Position":16.0,"HyperDash":false}]},{"StartTime":146472.0,"Objects":[{"StartTime":146472.0,"Position":256.0,"HyperDash":false}]},{"StartTime":146852.0,"Objects":[{"StartTime":146852.0,"Position":496.0,"HyperDash":false}]},{"StartTime":147137.0,"Objects":[{"StartTime":147137.0,"Position":352.0,"HyperDash":false}]},{"StartTime":147421.0,"Objects":[{"StartTime":147421.0,"Position":160.0,"HyperDash":false}]},{"StartTime":147611.0,"Objects":[{"StartTime":147611.0,"Position":256.0,"HyperDash":false}]},{"StartTime":147991.0,"Objects":[{"StartTime":147991.0,"Position":256.0,"HyperDash":false}]},{"StartTime":148371.0,"Objects":[{"StartTime":148371.0,"Position":256.0,"HyperDash":false}]},{"StartTime":148561.0,"Objects":[{"StartTime":148561.0,"Position":368.0,"HyperDash":false}]},{"StartTime":148751.0,"Objects":[{"StartTime":148751.0,"Position":256.0,"HyperDash":false}]},{"StartTime":148940.0,"Objects":[{"StartTime":148940.0,"Position":144.0,"HyperDash":false}]},{"StartTime":149130.0,"Objects":[{"StartTime":149130.0,"Position":288.0,"HyperDash":false}]},{"StartTime":149225.0,"Objects":[{"StartTime":149225.0,"Position":312.0,"HyperDash":false}]},{"StartTime":149320.0,"Objects":[{"StartTime":149320.0,"Position":336.0,"HyperDash":false}]},{"StartTime":149415.0,"Objects":[{"StartTime":149415.0,"Position":312.0,"HyperDash":false}]},{"StartTime":149510.0,"Objects":[{"StartTime":149510.0,"Position":288.0,"HyperDash":false}]},{"StartTime":149700.0,"Objects":[{"StartTime":149700.0,"Position":224.0,"HyperDash":false}]},{"StartTime":149795.0,"Objects":[{"StartTime":149795.0,"Position":200.0,"HyperDash":false}]},{"StartTime":149890.0,"Objects":[{"StartTime":149890.0,"Position":176.0,"HyperDash":false}]},{"StartTime":149985.0,"Objects":[{"StartTime":149985.0,"Position":200.0,"HyperDash":false}]},{"StartTime":150080.0,"Objects":[{"StartTime":150080.0,"Position":224.0,"HyperDash":false}]},{"StartTime":150175.0,"Objects":[{"StartTime":150175.0,"Position":256.0,"HyperDash":false}]},{"StartTime":150270.0,"Objects":[{"StartTime":150270.0,"Position":256.0,"HyperDash":false}]},{"StartTime":150649.0,"Objects":[{"StartTime":150649.0,"Position":168.0,"HyperDash":false},{"StartTime":150725.0,"Position":142.229309,"HyperDash":false},{"StartTime":150838.0,"Position":168.0,"HyperDash":false}]},{"StartTime":151029.0,"Objects":[{"StartTime":151029.0,"Position":344.0,"HyperDash":false},{"StartTime":151105.0,"Position":368.7707,"HyperDash":false},{"StartTime":151218.0,"Position":344.0,"HyperDash":false}]},{"StartTime":151409.0,"Objects":[{"StartTime":151409.0,"Position":256.0,"HyperDash":false}]},{"StartTime":151599.0,"Objects":[{"StartTime":151599.0,"Position":256.0,"HyperDash":false}]},{"StartTime":151694.0,"Objects":[{"StartTime":151694.0,"Position":256.0,"HyperDash":false}]},{"StartTime":151788.0,"Objects":[{"StartTime":151788.0,"Position":256.0,"HyperDash":false}]},{"StartTime":151978.0,"Objects":[{"StartTime":151978.0,"Position":464.0,"HyperDash":false},{"StartTime":152063.0,"Position":422.517944,"HyperDash":false},{"StartTime":152149.0,"Position":426.162018,"HyperDash":false},{"StartTime":152235.0,"Position":393.104584,"HyperDash":false},{"StartTime":152357.0,"Position":346.162628,"HyperDash":true}]},{"StartTime":152548.0,"Objects":[{"StartTime":152548.0,"Position":48.0,"HyperDash":false},{"StartTime":152633.0,"Position":100.48204,"HyperDash":false},{"StartTime":152719.0,"Position":77.83798,"HyperDash":false},{"StartTime":152805.0,"Position":114.895416,"HyperDash":false},{"StartTime":152927.0,"Position":165.837372,"HyperDash":false}]},{"StartTime":153118.0,"Objects":[{"StartTime":153118.0,"Position":256.0,"HyperDash":false}]},{"StartTime":153213.0,"Objects":[{"StartTime":153213.0,"Position":256.0,"HyperDash":false}]},{"StartTime":153307.0,"Objects":[{"StartTime":153307.0,"Position":256.0,"HyperDash":false}]},{"StartTime":153497.0,"Objects":[{"StartTime":153497.0,"Position":168.0,"HyperDash":false},{"StartTime":153582.0,"Position":217.34,"HyperDash":false},{"StartTime":153668.0,"Position":235.429718,"HyperDash":false},{"StartTime":153754.0,"Position":295.9821,"HyperDash":false},{"StartTime":153876.0,"Position":339.214722,"HyperDash":false}]},{"StartTime":154067.0,"Objects":[{"StartTime":154067.0,"Position":168.0,"HyperDash":false},{"StartTime":154143.0,"Position":134.40947,"HyperDash":false},{"StartTime":154256.0,"Position":104.3604,"HyperDash":false}]},{"StartTime":154447.0,"Objects":[{"StartTime":154447.0,"Position":344.0,"HyperDash":false},{"StartTime":154523.0,"Position":362.5905,"HyperDash":false},{"StartTime":154636.0,"Position":407.6396,"HyperDash":true}]},{"StartTime":154826.0,"Objects":[{"StartTime":154826.0,"Position":168.0,"HyperDash":false},{"StartTime":154902.0,"Position":150.40947,"HyperDash":false},{"StartTime":155015.0,"Position":104.3604,"HyperDash":false}]},{"StartTime":155206.0,"Objects":[{"StartTime":155206.0,"Position":344.0,"HyperDash":false},{"StartTime":155282.0,"Position":365.5905,"HyperDash":false},{"StartTime":155395.0,"Position":407.6396,"HyperDash":false}]},{"StartTime":155586.0,"Objects":[{"StartTime":155586.0,"Position":256.0,"HyperDash":false},{"StartTime":155680.0,"Position":270.830933,"HyperDash":false},{"StartTime":155775.0,"Position":254.810913,"HyperDash":false},{"StartTime":155852.0,"Position":238.329559,"HyperDash":false},{"StartTime":155965.0,"Position":256.0,"HyperDash":false}]},{"StartTime":156156.0,"Objects":[{"StartTime":156156.0,"Position":256.0,"HyperDash":false}]},{"StartTime":156345.0,"Objects":[{"StartTime":156345.0,"Position":256.0,"HyperDash":false}]},{"StartTime":156535.0,"Objects":[{"StartTime":156535.0,"Position":96.0,"HyperDash":false},{"StartTime":156620.0,"Position":138.369385,"HyperDash":false},{"StartTime":156706.0,"Position":196.213715,"HyperDash":false},{"StartTime":156792.0,"Position":213.399918,"HyperDash":false},{"StartTime":156914.0,"Position":244.507538,"HyperDash":false}]},{"StartTime":157105.0,"Objects":[{"StartTime":157105.0,"Position":152.0,"HyperDash":false},{"StartTime":157181.0,"Position":158.0,"HyperDash":false},{"StartTime":157294.0,"Position":122.301514,"HyperDash":false}]},{"StartTime":157485.0,"Objects":[{"StartTime":157485.0,"Position":32.0,"HyperDash":false},{"StartTime":157561.0,"Position":15.0,"HyperDash":false},{"StartTime":157674.0,"Position":61.6984863,"HyperDash":false}]},{"StartTime":157864.0,"Objects":[{"StartTime":157864.0,"Position":152.0,"HyperDash":true}]},{"StartTime":158054.0,"Objects":[{"StartTime":158054.0,"Position":416.0,"HyperDash":false},{"StartTime":158139.0,"Position":368.6306,"HyperDash":false},{"StartTime":158225.0,"Position":342.7863,"HyperDash":false},{"StartTime":158311.0,"Position":278.600067,"HyperDash":false},{"StartTime":158433.0,"Position":267.492462,"HyperDash":false}]},{"StartTime":158624.0,"Objects":[{"StartTime":158624.0,"Position":360.0,"HyperDash":false},{"StartTime":158700.0,"Position":345.0,"HyperDash":false},{"StartTime":158813.0,"Position":389.6985,"HyperDash":false}]},{"StartTime":159004.0,"Objects":[{"StartTime":159004.0,"Position":480.0,"HyperDash":false},{"StartTime":159080.0,"Position":483.0,"HyperDash":false},{"StartTime":159193.0,"Position":450.3015,"HyperDash":false}]},{"StartTime":159383.0,"Objects":[{"StartTime":159383.0,"Position":360.0,"HyperDash":false}]},{"StartTime":159573.0,"Objects":[{"StartTime":159573.0,"Position":255.0,"HyperDash":false},{"StartTime":159658.0,"Position":267.0,"HyperDash":false},{"StartTime":159744.0,"Position":265.0,"HyperDash":false},{"StartTime":159830.0,"Position":261.0,"HyperDash":false},{"StartTime":159952.0,"Position":255.0,"HyperDash":false}]},{"StartTime":160143.0,"Objects":[{"StartTime":160143.0,"Position":256.0,"HyperDash":false}]},{"StartTime":160333.0,"Objects":[{"StartTime":160333.0,"Position":376.0,"HyperDash":false}]},{"StartTime":160523.0,"Objects":[{"StartTime":160523.0,"Position":376.0,"HyperDash":false}]},{"StartTime":160713.0,"Objects":[{"StartTime":160713.0,"Position":256.0,"HyperDash":false}]},{"StartTime":160902.0,"Objects":[{"StartTime":160902.0,"Position":136.0,"HyperDash":false}]},{"StartTime":161092.0,"Objects":[{"StartTime":161092.0,"Position":136.0,"HyperDash":false}]},{"StartTime":161282.0,"Objects":[{"StartTime":161282.0,"Position":199.0,"HyperDash":false},{"StartTime":161341.0,"Position":494.0,"HyperDash":false},{"StartTime":161400.0,"Position":293.0,"HyperDash":false},{"StartTime":161460.0,"Position":115.0,"HyperDash":false},{"StartTime":161519.0,"Position":412.0,"HyperDash":false},{"StartTime":161578.0,"Position":506.0,"HyperDash":false},{"StartTime":161638.0,"Position":293.0,"HyperDash":false},{"StartTime":161697.0,"Position":346.0,"HyperDash":false},{"StartTime":161757.0,"Position":117.0,"HyperDash":false},{"StartTime":161816.0,"Position":285.0,"HyperDash":false},{"StartTime":161875.0,"Position":17.0,"HyperDash":false},{"StartTime":161935.0,"Position":238.0,"HyperDash":false},{"StartTime":161994.0,"Position":222.0,"HyperDash":false},{"StartTime":162053.0,"Position":450.0,"HyperDash":false},{"StartTime":162113.0,"Position":67.0,"HyperDash":false},{"StartTime":162172.0,"Position":219.0,"HyperDash":false},{"StartTime":162232.0,"Position":307.0,"HyperDash":false}]},{"StartTime":162421.0,"Objects":[{"StartTime":162421.0,"Position":256.0,"HyperDash":false}]},{"StartTime":162611.0,"Objects":[{"StartTime":162611.0,"Position":168.0,"HyperDash":false}]},{"StartTime":162706.0,"Objects":[{"StartTime":162706.0,"Position":152.0,"HyperDash":false}]},{"StartTime":162801.0,"Objects":[{"StartTime":162801.0,"Position":136.0,"HyperDash":false},{"StartTime":162886.0,"Position":184.369385,"HyperDash":false},{"StartTime":162972.0,"Position":235.213715,"HyperDash":false},{"StartTime":163058.0,"Position":243.058044,"HyperDash":false},{"StartTime":163180.0,"Position":306.314148,"HyperDash":false}]},{"StartTime":163371.0,"Objects":[{"StartTime":163371.0,"Position":392.0,"HyperDash":false},{"StartTime":163447.0,"Position":387.0,"HyperDash":false},{"StartTime":163560.0,"Position":392.0,"HyperDash":false}]},{"StartTime":163751.0,"Objects":[{"StartTime":163751.0,"Position":440.0,"HyperDash":false}]},{"StartTime":163940.0,"Objects":[{"StartTime":163940.0,"Position":344.0,"HyperDash":false}]},{"StartTime":164130.0,"Objects":[{"StartTime":164130.0,"Position":120.0,"HyperDash":false},{"StartTime":164215.0,"Position":96.0444,"HyperDash":false},{"StartTime":164301.0,"Position":55.6488266,"HyperDash":false},{"StartTime":164387.0,"Position":88.2046661,"HyperDash":false},{"StartTime":164509.0,"Position":93.82585,"HyperDash":false}]},{"StartTime":164700.0,"Objects":[{"StartTime":164700.0,"Position":232.0,"HyperDash":false},{"StartTime":164785.0,"Position":275.9556,"HyperDash":false},{"StartTime":164871.0,"Position":299.351166,"HyperDash":false},{"StartTime":164957.0,"Position":295.795349,"HyperDash":false},{"StartTime":165079.0,"Position":258.174164,"HyperDash":false}]},{"StartTime":165270.0,"Objects":[{"StartTime":165270.0,"Position":160.0,"HyperDash":false}]},{"StartTime":165459.0,"Objects":[{"StartTime":165459.0,"Position":160.0,"HyperDash":false}]},{"StartTime":165649.0,"Objects":[{"StartTime":165649.0,"Position":304.0,"HyperDash":false},{"StartTime":165734.0,"Position":324.3694,"HyperDash":false},{"StartTime":165820.0,"Position":364.7582,"HyperDash":false},{"StartTime":165906.0,"Position":401.273468,"HyperDash":false},{"StartTime":166028.0,"Position":446.4695,"HyperDash":false}]},{"StartTime":166219.0,"Objects":[{"StartTime":166219.0,"Position":320.0,"HyperDash":false},{"StartTime":166295.0,"Position":331.608,"HyperDash":false},{"StartTime":166408.0,"Position":376.222565,"HyperDash":false}]},{"StartTime":166599.0,"Objects":[{"StartTime":166599.0,"Position":456.0,"HyperDash":false},{"StartTime":166693.0,"Position":485.888763,"HyperDash":false},{"StartTime":166788.0,"Position":512.0,"HyperDash":false},{"StartTime":166865.0,"Position":508.525848,"HyperDash":false},{"StartTime":166978.0,"Position":456.0,"HyperDash":false}]},{"StartTime":167168.0,"Objects":[{"StartTime":167168.0,"Position":376.0,"HyperDash":false}]},{"StartTime":167358.0,"Objects":[{"StartTime":167358.0,"Position":376.0,"HyperDash":false},{"StartTime":167434.0,"Position":359.082825,"HyperDash":false},{"StartTime":167547.0,"Position":319.0086,"HyperDash":false}]},{"StartTime":167738.0,"Objects":[{"StartTime":167738.0,"Position":240.0,"HyperDash":false},{"StartTime":167814.0,"Position":227.391983,"HyperDash":false},{"StartTime":167927.0,"Position":183.777435,"HyperDash":false}]},{"StartTime":168118.0,"Objects":[{"StartTime":168118.0,"Position":112.0,"HyperDash":false},{"StartTime":168203.0,"Position":82.78144,"HyperDash":false},{"StartTime":168289.0,"Position":79.26619,"HyperDash":false},{"StartTime":168375.0,"Position":41.750946,"HyperDash":false},{"StartTime":168497.0,"Position":0.0,"HyperDash":true}]},{"StartTime":168687.0,"Objects":[{"StartTime":168687.0,"Position":256.0,"HyperDash":false},{"StartTime":168772.0,"Position":272.0,"HyperDash":false},{"StartTime":168858.0,"Position":270.0,"HyperDash":false},{"StartTime":168944.0,"Position":274.0,"HyperDash":false},{"StartTime":169066.0,"Position":256.0,"HyperDash":false}]},{"StartTime":169257.0,"Objects":[{"StartTime":169257.0,"Position":328.0,"HyperDash":false}]},{"StartTime":169447.0,"Objects":[{"StartTime":169447.0,"Position":256.0,"HyperDash":false}]},{"StartTime":169637.0,"Objects":[{"StartTime":169637.0,"Position":184.0,"HyperDash":false}]},{"StartTime":169827.0,"Objects":[{"StartTime":169827.0,"Position":256.0,"HyperDash":false}]},{"StartTime":170016.0,"Objects":[{"StartTime":170016.0,"Position":328.0,"HyperDash":true}]},{"StartTime":170206.0,"Objects":[{"StartTime":170206.0,"Position":32.0,"HyperDash":false},{"StartTime":170291.0,"Position":69.44879,"HyperDash":false},{"StartTime":170377.0,"Position":93.3499146,"HyperDash":false},{"StartTime":170463.0,"Position":153.251038,"HyperDash":false},{"StartTime":170585.0,"Position":203.43634,"HyperDash":true}]},{"StartTime":170776.0,"Objects":[{"StartTime":170776.0,"Position":480.0,"HyperDash":false},{"StartTime":170861.0,"Position":437.5512,"HyperDash":false},{"StartTime":170947.0,"Position":400.6501,"HyperDash":false},{"StartTime":171033.0,"Position":369.748962,"HyperDash":false},{"StartTime":171155.0,"Position":308.56366,"HyperDash":false}]},{"StartTime":171345.0,"Objects":[{"StartTime":171345.0,"Position":328.0,"HyperDash":false}]},{"StartTime":171535.0,"Objects":[{"StartTime":171535.0,"Position":184.0,"HyperDash":true}]},{"StartTime":171725.0,"Objects":[{"StartTime":171725.0,"Position":440.0,"HyperDash":false},{"StartTime":171810.0,"Position":393.6306,"HyperDash":false},{"StartTime":171896.0,"Position":358.7863,"HyperDash":false},{"StartTime":171982.0,"Position":322.941956,"HyperDash":false},{"StartTime":172104.0,"Position":260.0,"HyperDash":false}]},{"StartTime":172295.0,"Objects":[{"StartTime":172295.0,"Position":152.0,"HyperDash":false}]},{"StartTime":172485.0,"Objects":[{"StartTime":172485.0,"Position":192.0,"HyperDash":false}]},{"StartTime":172675.0,"Objects":[{"StartTime":172675.0,"Position":320.0,"HyperDash":false}]},{"StartTime":172864.0,"Objects":[{"StartTime":172864.0,"Position":360.0,"HyperDash":false}]},{"StartTime":173054.0,"Objects":[{"StartTime":173054.0,"Position":320.0,"HyperDash":false}]},{"StartTime":173244.0,"Objects":[{"StartTime":173244.0,"Position":192.0,"HyperDash":false}]},{"StartTime":173434.0,"Objects":[{"StartTime":173434.0,"Position":487.0,"HyperDash":false},{"StartTime":173528.0,"Position":53.0,"HyperDash":false},{"StartTime":173623.0,"Position":40.0,"HyperDash":false},{"StartTime":173718.0,"Position":153.0,"HyperDash":false},{"StartTime":173813.0,"Position":79.0,"HyperDash":false},{"StartTime":173908.0,"Position":488.0,"HyperDash":false},{"StartTime":174003.0,"Position":396.0,"HyperDash":false},{"StartTime":174098.0,"Position":428.0,"HyperDash":false},{"StartTime":174193.0,"Position":59.0,"HyperDash":false},{"StartTime":174288.0,"Position":255.0,"HyperDash":false},{"StartTime":174383.0,"Position":294.0,"HyperDash":false},{"StartTime":174478.0,"Position":354.0,"HyperDash":false},{"StartTime":174573.0,"Position":270.0,"HyperDash":false},{"StartTime":174668.0,"Position":362.0,"HyperDash":false},{"StartTime":174763.0,"Position":255.0,"HyperDash":false},{"StartTime":174858.0,"Position":203.0,"HyperDash":false},{"StartTime":174953.0,"Position":67.0,"HyperDash":false}]}]} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/42587.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/42587.osu new file mode 100644 index 0000000000..41366eab43 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/42587.osu @@ -0,0 +1,528 @@ +osu file format v6 + +[General] +StackLeniency: 0.7 +Mode: 0 + +[Difficulty] +HPDrainRate:8 +CircleSize:5 +OverallDifficulty:8 +SliderMultiplier:1.8 +SliderTickRate:0.5 + +[Events] +//Break Periods +2,99204,110406 +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Sound Samples +//Background Colour Transformations +3,100,0,0,0 + +[TimingPoints] +270,379.746835443038,4,2,1,85,1,0 +48782,-100,4,2,0,50,0,0 +48972,-100,4,2,0,85,0,0 +60744,-100,4,2,1,85,0,0 +60982,-100,4,2,1,65,0,0 +61171,-100,4,2,1,85,0,0 +71092,-100,4,2,1,40,0,0 +71282,-100,4,2,1,85,0,0 +71567,-100,4,2,0,60,0,0 +71852,-100,4,2,1,85,0,0 +72232,-100,4,2,1,85,0,0 +73086,-100,4,1,0,60,0,0 +74035,-100,4,1,1,50,0,0 +74890,-100,4,2,1,85,0,0 +81061,-100,4,2,1,60,0,0 +81251,-100,4,2,1,85,0,0 +82580,-100,4,2,1,60,0,0 +82770,-100,4,2,1,85,0,0 +86804,-100,4,2,1,60,0,0 +86947,-100,4,2,1,85,0,0 +87137,-100,4,2,1,60,0,0 +87326,-100,4,2,1,85,0,0 +88656,-100,4,2,1,60,0,0 +88845,-100,4,2,1,85,0,0 +92643,-100,4,2,1,60,0,0 +92833,-100,4,2,1,85,0,0 +93592,-100,4,2,1,60,0,0 +93782,-100,4,2,1,86,0,0 +94162,-100,4,2,1,60,0,0 +94352,-100,4,2,1,85,0,0 +95111,-100,4,2,1,60,0,0 +95301,-100,4,2,1,85,0,0 +98624,-100,4,1,1,70,0,0 +110966,-100,4,1,0,60,0,0 +112390,-100,4,2,1,85,0,0 +118371,-100,4,2,1,75,0,0 +118751,-100,4,2,1,65,0,0 +119130,-100,4,2,1,55,0,0 +119510,-100,4,2,1,85,0,0 +135934,-100,4,1,1,80,0,0 +136314,-100,4,2,1,85,0,0 +136883,-100,4,2,1,65,0,0 +137073,-100,4,2,1,85,0,0 +147516,-100,4,2,0,60,0,0 +147801,-100,4,2,1,85,0,0 +149035,-100,4,1,1,65,0,0 +149605,-100,4,1,1,75,0,0 +150459,-100,4,2,1,70,0,0 +150744,-100,4,2,1,85,0,0 +150934,-100,4,2,1,70,0,0 +151124,-100,4,2,1,85,0,0 +157010,-100,4,2,1,70,0,0 +157200,-100,4,2,1,85,0,0 +158529,-100,4,2,1,70,0,0 +158719,-100,4,2,1,85,0,0 +162754,-100,4,2,1,70,0,0 +162896,-100,4,2,1,85,0,0 +163466,-100,4,2,1,70,0,0 +163656,-100,4,2,1,85,0,0 +164035,-100,4,2,1,70,0,0 +164225,-100,4,2,1,85,0,0 +164605,-100,4,2,1,70,0,0 +164795,-100,4,2,1,85,0,0 +168592,-100,4,2,1,70,0,0 +168782,-100,4,2,1,85,0,0 +169542,-100,4,2,1,70,0,0 +169732,-100,4,2,1,85,0,0 +170111,-100,4,2,1,70,0,0 +170301,-100,4,2,1,85,0,0 +170681,-100,4,2,1,70,0,0 +170871,-100,4,2,1,85,0,0 +173339,-100,4,1,0,51,0,0 + +[HitObjects] +376,288,24383,1,0 +392,264,24478,1,0 +408,240,24573,1,8 +448,160,24763,2,0,B|344:160,1,90,0|0 +280,120,25143,1,0 +232,200,25333,1,0 +152,160,25523,2,0,B|56:160,1,90,0|0 +32,248,25902,1,0 +96,312,26092,6,0,L|144:376|264:376,2,180 +96,224,27042,1,0 +176,264,27232,2,0,B|288:264,1,90 +448,264,27801,1,0 +360,264,27991,1,0 +192,192,28371,1,0 +280,192,28561,1,0 +368,192,28751,1,0 +456,192,28940,1,0 +456,192,29130,6,0,L|408:128|288:128,2,180 +456,280,30080,1,0 +376,240,30270,2,0,B|256:240,1,90 +112,280,30839,1,0 +176,216,31029,1,0 +112,152,31219,1,0 +112,152,31314,1,0 +112,152,31409,1,0 +176,88,31599,1,0 +240,152,31788,1,0 +176,216,31978,1,0 +240,280,32168,6,0,L|296:328|416:328,2,180 +240,192,33118,1,0 +328,192,33307,2,0,L|328:152|288:96,1,90 +136,32,33877,1,0 +80,104,34067,1,0 +24,176,34257,1,0 +24,200,34352,1,0 +24,224,34447,1,0 +40,240,34542,1,0 +56,248,34637,1,0 +144,248,34826,1,0 +232,248,35016,1,0 +376,248,35206,6,0,L|408:248|464:288,2,90,0|0|0 +232,248,35776,2,0,L|200:248|144:288,1,90 +304,352,36156,1,0 +304,248,36345,2,0,B|304:152,1,90 +112,80,36725,6,0,B|16:128,1,90,8|0 +112,160,37105,2,0,B|16:208,1,90 +112,240,37485,1,0 +112,328,37675,6,0,B|24:376,2,90 +112,240,38244,1,0 +32,200,38434,1,0 +112,160,38624,1,0 +32,120,38814,1,0 +112,80,39004,1,0 +200,80,39194,6,0,B|304:80,1,90 +384,168,39573,2,0,B|272:168,1,90 +200,256,39953,2,0,B|296:256,3,90 +408,200,40713,5,0 +360,112,40902,1,0 +280,56,41092,2,0,B|192:24|88:64,1,180 +168,128,41662,2,0,B|120:136|64:176,2,90 +264,128,42232,2,0,B|384:128|448:232,1,180 +320,224,42801,2,0,B|280:312,1,90,0|0 +184,336,43181,2,0,B|232:248,1,90 +227,256,43561,1,0 +192,176,43751,6,0,B|144:256,2,90,0|0|0 +128,112,44320,2,0,B|184:32|304:40,1,180 +376,40,44890,1,0 +440,208,45270,5,0 +384,280,45459,1,0 +304,312,45649,1,0 +216,328,45839,2,0,B|141:308|112:292|56:216,1,180 +56,144,46409,1,0 +216,64,46788,5,0 +296,96,46978,1,0 +216,144,47168,1,0 +296,176,47358,1,0 +136,232,47738,1,0 +376,296,48118,1,0 +136,360,48497,1,0 +376,184,48877,6,0,B|256:184,1,90,4|0 +192,184,49257,1,0 +128,120,49447,1,0 +216,120,49637,2,0,B|328:120,3,90 +400,120,50396,2,0,B|472:200|400:304,1,180 +336,232,50966,2,0,B|264:320,1,90 +208,360,51345,5,0 +168,280,51535,1,0 +120,360,51725,1,0 +72,280,51915,2,0,B|48:176|136:112,1,180 +216,88,52485,1,0 +304,112,52675,1,0 +232,168,52864,5,0 +312,200,53054,1,0 +288,288,53244,2,0,B|368:320|456:232,1,180 +392,176,53814,2,0,B|336:72,1,90 +280,152,54194,2,0,B|184:80,1,90,0|0 +176,192,54573,1,0 +104,136,54763,2,0,B|48:248|104:336|208:344,1,270 +216,256,55523,1,0 +264,184,55713,1,0 +352,184,55902,5,0 +440,136,56092,1,0 +352,88,56282,1,0 +264,88,56472,2,0,B|144:-16|8:96,1,270 +160,152,57421,5,0 +32,216,57611,1,0 +160,280,57801,1,0 +248,312,57991,2,0,B|368:416|504:304,1,270 +360,192,58940,5,0 +256,192,59130,1,0 +152,192,59320,1,0 +168,96,59510,2,0,B|256:56|368:104,3,180 +408,136,60839,1,4 +408,136,60934,1,4 +408,136,61029,6,0,B|352:216|296:296,1,180,8|4 +304,283,61599,1,0 +216,280,61788,2,0,B|263:212|319:132,1,180,0|4 +319,132,62358,1,0 +240,96,62548,6,0,B|312:0,2,90,0|0|4 +192,168,63118,2,0,B|136:248|80:328,1,180,0|0 +176,312,63687,1,4 +264,312,63877,1,0 +352,312,64067,6,0,B|448:248|416:120,1,180,0|4 +352,208,64637,1,0 +272,168,64826,2,0,B|344:72,1,90 +326,96,65206,1,4 +272,24,65396,2,0,B|160:56|168:184,1,180 +104,96,65966,1,4 +48,168,66156,1,0 +104,232,66345,1,0 +56,312,66535,1,0 +80,328,66630,1,0 +104,344,66725,1,4 +192,312,66915,1,0 +280,344,67105,6,0,B|436:254,1,180,0|4 +448,168,67675,1,0 +456,80,67864,2,0,B|299:169,1,180,0|4 +288,256,68434,1,0 +208,296,68624,5,0 +128,256,68814,1,0 +48,296,69004,1,4 +128,256,69194,2,0,B|208:192|192:80,1,180 +256,32,69763,1,4 +256,32,69953,1,0 +318,96,70143,6,0,B|304:192|384:256,1,180,0|4 +256,120,70902,2,0,B|224:184|304:200|248:264,2,135 +256,32,71662,5,4 +256,32,72042,1,4 +160,144,72421,5,2 +224,144,72611,1,2 +288,144,72801,1,2 +352,144,72991,1,2 +408,216,73181,5,0 +304,216,73371,1,0 +208,216,73561,1,0 +112,216,73751,1,0 +160,288,73940,5,0 +224,288,74130,1,8 +248,288,74225,1,8 +272,288,74320,1,8 +296,288,74415,1,0 +320,288,74510,1,0 +344,288,74605,1,4 +368,288,74700,6,0,B|464:256|480:136,1,180,4|4 +368,64,75270,1,0 +296,176,75459,2,0,B|240:208|184:152,1,90 +144,64,75839,1,4 +168,328,76029,6,0,B|224:344|262:347|352:328,1,180,0|0 +344,192,76599,2,0,B|282:175|232:167|144:200,1,180,4|0 +256,256,77168,1,0 +256,256,77358,1,4 +424,256,77548,6,0,B|444:180|440:128|424:72,1,180 +296,32,78118,2,0,B|336:144,1,90,4|0 +240,264,78497,2,0,B|280:152,1,90 +168,32,78877,2,0,B|200:120,1,90,4|0 +104,264,79257,2,0,B|136:176,1,90 +48,120,79637,2,0,B|8:16,2,90,4|0|0 +48,120,80206,1,4 +48,120,80396,1,4 +48,256,80586,6,0,B|72:360|192:360,1,180,0|0 +334,359,81156,2,0,B|440:360|464:256,1,180,12|0 +256,192,81725,1,0 +256,192,81915,1,4 +48,128,82105,6,0,B|72:24|192:24,1,180,0|0 +334,25,82675,2,0,B|440:24|464:128,1,180,12|0 +256,192,83244,1,0 +256,192,83434,1,4 +177,24,83624,6,0,B|72:24|48:128,1,180 +240,96,84194,2,0,B|128:120,1,90,4|0 +40,208,84573,2,0,B|160:184,1,90,0|0 +280,216,84953,2,0,B|184:240,1,90,4|0 +256,208,85333,12,4,86282 +256,192,86472,5,4 +128,80,86662,5,4 +152,64,86757,1,4 +176,48,86852,2,0,B|288:48,1,90,12|0 +360,56,87232,2,0,L|288:112|176:112,1,180,12|0 +136,176,87801,2,0,B|240:176,1,90,0|4 +440,352,88181,6,0,L|389:352|344:312|272:360,1,180,0|0 +72,352,88751,2,0,L|122:352|168:312|240:360,1,180,12|0 +256,192,89320,2,0,B|256:240,2,45,0|0|4 +488,48,89700,6,0,B|389:33|304:88,1,180 +256,192,90270,1,4 +160,280,90459,1,0 +64,192,90649,1,0 +160,104,90839,1,0 +256,192,91029,1,4 +352,280,91219,1,0 +448,192,91409,1,0 +352,104,91599,1,0 +256,192,91788,1,4 +256,64,91978,1,0 +256,192,92168,1,0 +256,192,92358,2,0,B|256:304,1,90,4|4 +32,32,92738,6,0,L|144:32|200:88,1,180,8|0 +64,128,93307,2,0,B|127:191,1,90,4|0 +256,152,93687,2,0,B|319:215,1,90,8|0 +424,304,94067,1,4 +256,368,94257,6,0,L|192:328|192:216,1,180,8|0 +328,224,94826,2,0,B|440:224,2,90,4|0|8 +328,88,95396,1,0 +328,88,95586,1,4 +192,88,95776,6,0,B|104:67|12:88,1,180,0|2 +56,192,96345,2,0,B|176:192,1,90,6|2 +232,232,96725,1,2 +280,152,96915,1,2 +360,192,97105,2,12,B|472:192,1,90,6|2 +256,208,97485,12,4,99004 +256,352,111156,5,4 +256,192,111915,1,0 +256,192,112105,1,0 +256,192,112295,1,0 +256,104,112485,1,0 +328,48,112675,6,0,B|416:48|456:88|456:160,1,180 +456,232,113244,1,0 +456,320,113434,1,0 +368,336,113624,2,0,B|304:336|272:392,2,90 +456,320,114194,2,0,B|416:256|376:232|288:224,1,180 +256,296,114763,2,0,B|200:288|160:240,1,90 +112,192,115143,5,0 +176,256,115333,1,0 +240,192,115523,1,0 +176,128,115713,2,0,B|224:48|344:48,1,180 +296,128,116282,1,0 +360,192,116472,1,0 +448,192,116662,6,0,B|368:272,1,90 +384,352,117042,2,0,B|264:360|216:232,1,180 +280,192,117611,2,0,B|323:159|280:104,1,90 +192,112,117991,2,0,B|155:158|198:191,1,90 +248,360,118561,1,0 +248,296,118940,1,0 +248,232,119320,1,0 +448,240,119700,5,0 +384,304,119890,1,0 +320,240,120080,1,0 +256,304,120270,2,0,B|176:336|48:296,1,180 +80,304,120839,1,0 +32,32,121219,5,0 +120,136,121409,1,0 +208,32,121599,1,0 +296,136,121788,2,0,B|376:104|504:144,1,180 +472,136,122358,1,0 +208,192,122738,5,0 +256,112,122928,1,0 +304,192,123117,1,0 +256,272,123307,1,0 +256,48,123687,1,0 +256,336,124067,1,0 +256,248,124257,5,4 +256,160,124447,1,4 +256,72,124637,1,4 +256,72,124732,1,4 +256,72,124826,2,4,B|376:72|376:176,1,180,0|4 +456,224,125396,1,0 +392,288,125586,1,0 +304,288,125776,6,0,B|200:352,1,90,0|4 +192,248,126156,1,0 +160,336,126345,2,0,B|48:336|24:192,1,180,0|4 +120,224,126915,2,0,B|48:120,1,90 +136,96,127295,2,0,B|72:8,2,90,0|4|0 +184,168,127864,2,0,B|312:344,1,180,0|4 +384,312,128434,1,0 +448,256,128624,1,0 +448,168,128814,6,0,B|344:112,1,90,0|4 +440,72,129194,2,0,B|368:40|328:40|248:80,1,180 +208,136,129763,1,4 +128,184,129953,1,0 +208,232,130143,1,0 +288,184,130333,2,0,B|400:184,1,90,0|4 +448,248,130713,2,0,B|352:248,1,90 +176,248,131282,1,4 +360,248,131662,1,0 +288,192,131852,5,0 +200,192,132042,1,4 +112,192,132232,1,0 +96,288,132421,2,0,B|0:256|-32:144|112:88,1,270 +224,192,133371,5,0 +312,192,133561,1,4 +400,192,133751,1,0 +416,288,133940,2,0,B|512:256|544:144|400:88,1,270 +80,192,134890,5,0 +160,152,135080,1,4 +200,232,135270,1,0 +280,192,135459,1,0 +464,192,135839,1,4 +376,192,136029,1,0 +376,192,136219,1,0 +280,192,136409,1,4 +280,192,136599,1,4 +56,216,136978,6,0,B|144:272|256:200,1,180,8|4 +456,168,137738,2,0,B|368:112|256:184,1,180,0|4 +256,32,138497,5,0 +200,104,138687,1,0 +256,176,138877,1,4 +312,104,139067,2,0,B|424:104,1,90 +400,192,139447,2,0,B|504:192,2,90,0|4|0 +400,280,140016,6,0,B|400:368|232:352,1,180,0|4 +224,272,140586,1,0 +296,216,140776,1,0 +224,168,140966,1,0 +296,112,141156,1,4 +256,32,141345,2,0,B|107:25|115:113,1,180,0|0 +112,200,141915,2,0,B|112:312,2,90,4|0|0 +112,112,142485,1,0 +112,112,142580,1,0 +112,112,142675,1,4 +112,24,142864,1,0 +232,8,143054,6,0,B|152:96|248:208,1,180,0|4 +280,376,143814,2,0,B|360:288|264:176,1,180,0|4 +256,32,144573,5,0 +344,32,144763,1,0 +416,88,144953,1,4 +416,176,145143,2,0,B|232:176,1,180 +144,176,145713,1,4 +80,112,145902,1,0 +16,176,146092,5,0 +256,304,146472,1,4 +496,176,146852,1,0 +352,32,147137,1,0 +160,32,147421,1,0 +256,160,147611,5,4 +256,224,147991,1,4 +256,96,148371,5,2 +368,192,148561,1,2 +256,288,148751,1,2 +144,192,148940,1,2 +288,144,149130,5,0 +312,168,149225,1,8 +336,192,149320,1,0 +312,216,149415,1,8 +288,240,149510,1,0 +224,144,149700,5,0 +200,168,149795,1,8 +176,192,149890,1,0 +200,216,149985,1,8 +224,240,150080,1,0 +256,256,150175,1,8 +256,288,150270,1,0 +168,24,150649,6,4,L|152:56|168:80|168:128,1,90,8|0 +344,24,151029,2,0,L|360:56|344:80|344:128,1,90,12|0 +256,264,151409,1,8 +256,80,151599,1,0 +256,80,151694,1,0 +256,80,151788,1,4 +464,224,151978,6,0,L|424:240|424:240|440:280|328:280,1,180,0|0 +48,280,152548,2,0,L|88:264|88:264|72:224|184:224,1,180,4|0 +256,80,153118,1,0 +256,80,153213,1,0 +256,80,153307,1,4 +168,312,153497,6,0,B|256:368|368:296,1,180 +168,248,154067,2,0,B|96:176,1,90,4|0 +344,248,154447,2,0,B|416:176,1,90 +168,160,154826,2,0,B|96:88,1,90,4|0 +344,160,155206,2,0,B|416:88,1,90 +256,352,155586,2,0,B|280:312|216:296|272:248,2,90,4|0|0 +256,352,156156,1,4 +256,352,156345,1,4 +96,32,156535,6,0,L|208:32|264:120,1,180,0|0 +152,96,157105,2,0,L|152:144|112:184,1,90,12|0 +32,176,157485,2,0,L|32:224|64:256,1,90 +152,256,157864,1,4 +416,352,158054,6,0,L|304:352|248:264,1,180 +360,288,158624,2,0,L|360:240|400:200,1,90,12|0 +480,208,159004,2,0,L|480:160|448:128,1,90 +360,128,159383,1,4 +255,236,159573,6,0,B|255:52,1,180 +256,56,160143,1,4 +376,120,160333,1,0 +376,264,160523,1,0 +256,328,160713,1,0 +136,264,160902,1,4 +136,120,161092,1,0 +256,208,161282,12,4,162232 +256,192,162421,5,4 +168,320,162611,5,4 +152,336,162706,1,4 +136,352,162801,2,0,L|264:352|320:312,1,180,12|4 +392,352,163371,2,0,B|392:248,1,90,0|8 +440,184,163751,1,0 +344,184,163940,1,4 +120,32,164130,6,0,B|8:64|120:216,1,180,8|0 +232,136,164700,2,0,B|344:168|232:320,1,180,8|0 +160,360,165270,1,0 +160,360,165459,1,4 +304,360,165649,6,0,L|384:360|448:280,1,180 +320,288,166219,2,0,B|384:208,1,90,4|0 +456,120,166599,2,0,B|512:50,2,90,0|0|4 +376,216,167168,1,0 +376,88,167358,2,0,B|304:176,1,90 +240,120,167738,2,0,B|176:200,1,90,4|0 +112,144,168118,2,0,B|-16:304,1,180,0|4 +256,360,168687,6,0,B|256:168,1,180,8|0 +328,96,169257,1,4 +256,16,169447,1,0 +184,96,169637,1,8 +256,176,169827,1,0 +328,96,170016,1,4 +32,304,170206,6,0,B|232:240,1,180,8|0 +480,80,170776,2,0,B|280:144,1,180,8|0 +328,280,171345,1,0 +184,104,171535,1,4 +440,192,171725,6,4,B|248:192,1,180,0|2 +152,192,172295,1,2 +192,72,172485,1,2 +320,72,172675,1,2 +360,192,172864,1,2 +320,312,173054,1,2 +192,312,173244,1,2 +256,208,173434,12,4,174953 diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/50859-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/50859-expected-conversion.json new file mode 100644 index 0000000000..ee89090492 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/50859-expected-conversion.json @@ -0,0 +1 @@ +{"Mappings":[{"StartTime":179.0,"Objects":[{"StartTime":179.0,"Position":120.0,"HyperDash":false}]},{"StartTime":786.0,"Objects":[{"StartTime":786.0,"Position":311.0,"HyperDash":false},{"StartTime":852.0,"Position":322.1386,"HyperDash":false},{"StartTime":919.0,"Position":352.673279,"HyperDash":false},{"StartTime":986.0,"Position":387.207916,"HyperDash":false},{"StartTime":1089.0,"Position":431.0,"HyperDash":false}]},{"StartTime":1392.0,"Objects":[{"StartTime":1392.0,"Position":431.0,"HyperDash":false},{"StartTime":1458.0,"Position":419.8614,"HyperDash":false},{"StartTime":1525.0,"Position":395.326721,"HyperDash":false},{"StartTime":1592.0,"Position":352.792084,"HyperDash":false},{"StartTime":1695.0,"Position":311.0,"HyperDash":false}]},{"StartTime":1998.0,"Objects":[{"StartTime":1998.0,"Position":215.0,"HyperDash":false}]},{"StartTime":2301.0,"Objects":[{"StartTime":2301.0,"Position":119.0,"HyperDash":false},{"StartTime":2376.0,"Position":147.702972,"HyperDash":false},{"StartTime":2452.0,"Position":163.801971,"HyperDash":false},{"StartTime":2528.0,"Position":200.900986,"HyperDash":false},{"StartTime":2604.0,"Position":239.0,"HyperDash":false},{"StartTime":2679.0,"Position":264.702972,"HyperDash":false},{"StartTime":2755.0,"Position":312.801971,"HyperDash":false},{"StartTime":2831.0,"Position":332.901,"HyperDash":false},{"StartTime":2907.0,"Position":359.0,"HyperDash":false},{"StartTime":2973.0,"Position":390.1386,"HyperDash":false},{"StartTime":3040.0,"Position":392.673279,"HyperDash":false},{"StartTime":3107.0,"Position":429.207916,"HyperDash":false},{"StartTime":3210.0,"Position":479.0,"HyperDash":false}]},{"StartTime":3513.0,"Objects":[{"StartTime":3513.0,"Position":478.0,"HyperDash":false}]},{"StartTime":3816.0,"Objects":[{"StartTime":3816.0,"Position":382.0,"HyperDash":false},{"StartTime":3882.0,"Position":373.8614,"HyperDash":false},{"StartTime":3949.0,"Position":346.326721,"HyperDash":false},{"StartTime":4016.0,"Position":315.792084,"HyperDash":false},{"StartTime":4119.0,"Position":262.0,"HyperDash":false}]},{"StartTime":4422.0,"Objects":[{"StartTime":4422.0,"Position":166.0,"HyperDash":false},{"StartTime":4488.0,"Position":158.0,"HyperDash":false},{"StartTime":4555.0,"Position":149.0,"HyperDash":false},{"StartTime":4622.0,"Position":179.0,"HyperDash":false},{"StartTime":4725.0,"Position":166.0,"HyperDash":false}]},{"StartTime":5331.0,"Objects":[{"StartTime":5331.0,"Position":166.0,"HyperDash":false}]},{"StartTime":5634.0,"Objects":[{"StartTime":5634.0,"Position":261.0,"HyperDash":false},{"StartTime":5691.0,"Position":278.649017,"HyperDash":false},{"StartTime":5785.0,"Position":321.0,"HyperDash":false}]},{"StartTime":6089.0,"Objects":[{"StartTime":6089.0,"Position":321.0,"HyperDash":false},{"StartTime":6146.0,"Position":325.0,"HyperDash":false},{"StartTime":6240.0,"Position":321.0,"HyperDash":false}]},{"StartTime":6543.0,"Objects":[{"StartTime":6543.0,"Position":321.0,"HyperDash":false}]},{"StartTime":6998.0,"Objects":[{"StartTime":6998.0,"Position":465.0,"HyperDash":false},{"StartTime":7055.0,"Position":450.0,"HyperDash":false},{"StartTime":7149.0,"Position":465.0,"HyperDash":false}]},{"StartTime":7452.0,"Objects":[{"StartTime":7452.0,"Position":369.0,"HyperDash":false},{"StartTime":7518.0,"Position":365.0,"HyperDash":false},{"StartTime":7585.0,"Position":368.0,"HyperDash":false},{"StartTime":7652.0,"Position":383.0,"HyperDash":false},{"StartTime":7755.0,"Position":369.0,"HyperDash":false}]},{"StartTime":8058.0,"Objects":[{"StartTime":8058.0,"Position":464.0,"HyperDash":false}]},{"StartTime":8361.0,"Objects":[{"StartTime":8361.0,"Position":464.0,"HyperDash":false},{"StartTime":8427.0,"Position":422.8614,"HyperDash":false},{"StartTime":8494.0,"Position":392.326721,"HyperDash":false},{"StartTime":8561.0,"Position":368.792084,"HyperDash":false},{"StartTime":8664.0,"Position":344.0,"HyperDash":false}]},{"StartTime":8967.0,"Objects":[{"StartTime":8967.0,"Position":248.0,"HyperDash":false}]},{"StartTime":9270.0,"Objects":[{"StartTime":9270.0,"Position":200.0,"HyperDash":false}]},{"StartTime":9573.0,"Objects":[{"StartTime":9573.0,"Position":296.0,"HyperDash":false}]},{"StartTime":10180.0,"Objects":[{"StartTime":10180.0,"Position":275.0,"HyperDash":false}]},{"StartTime":10483.0,"Objects":[{"StartTime":10483.0,"Position":179.0,"HyperDash":false}]},{"StartTime":10786.0,"Objects":[{"StartTime":10786.0,"Position":179.0,"HyperDash":false},{"StartTime":10852.0,"Position":218.138611,"HyperDash":false},{"StartTime":10919.0,"Position":248.673264,"HyperDash":false},{"StartTime":10986.0,"Position":257.207916,"HyperDash":false},{"StartTime":11089.0,"Position":299.0,"HyperDash":false}]},{"StartTime":11392.0,"Objects":[{"StartTime":11392.0,"Position":299.0,"HyperDash":false}]},{"StartTime":11695.0,"Objects":[{"StartTime":11695.0,"Position":203.0,"HyperDash":false},{"StartTime":11752.0,"Position":173.351,"HyperDash":false},{"StartTime":11846.0,"Position":143.0,"HyperDash":false}]},{"StartTime":11998.0,"Objects":[{"StartTime":11998.0,"Position":94.0,"HyperDash":false}]},{"StartTime":12301.0,"Objects":[{"StartTime":12301.0,"Position":94.0,"HyperDash":false}]},{"StartTime":12604.0,"Objects":[{"StartTime":12604.0,"Position":189.0,"HyperDash":false}]},{"StartTime":13513.0,"Objects":[{"StartTime":13513.0,"Position":476.0,"HyperDash":false}]},{"StartTime":13816.0,"Objects":[{"StartTime":13816.0,"Position":380.0,"HyperDash":false}]},{"StartTime":14725.0,"Objects":[{"StartTime":14725.0,"Position":272.0,"HyperDash":false},{"StartTime":14782.0,"Position":248.351,"HyperDash":false},{"StartTime":14876.0,"Position":212.0,"HyperDash":false}]},{"StartTime":15028.0,"Objects":[{"StartTime":15028.0,"Position":177.0,"HyperDash":false},{"StartTime":15085.0,"Position":196.0,"HyperDash":false},{"StartTime":15179.0,"Position":177.0,"HyperDash":false}]},{"StartTime":15331.0,"Objects":[{"StartTime":15331.0,"Position":225.0,"HyperDash":false}]},{"StartTime":15483.0,"Objects":[{"StartTime":15483.0,"Position":273.0,"HyperDash":false}]},{"StartTime":15786.0,"Objects":[{"StartTime":15786.0,"Position":273.0,"HyperDash":false}]},{"StartTime":16089.0,"Objects":[{"StartTime":16089.0,"Position":273.0,"HyperDash":false}]},{"StartTime":16846.0,"Objects":[{"StartTime":16846.0,"Position":33.0,"HyperDash":false},{"StartTime":16903.0,"Position":27.0,"HyperDash":false},{"StartTime":16997.0,"Position":33.0,"HyperDash":false}]},{"StartTime":17149.0,"Objects":[{"StartTime":17149.0,"Position":33.0,"HyperDash":false}]},{"StartTime":17755.0,"Objects":[{"StartTime":17755.0,"Position":224.0,"HyperDash":false}]},{"StartTime":18967.0,"Objects":[{"StartTime":18967.0,"Position":277.0,"HyperDash":false}]},{"StartTime":19119.0,"Objects":[{"StartTime":19119.0,"Position":228.0,"HyperDash":false}]},{"StartTime":19270.0,"Objects":[{"StartTime":19270.0,"Position":181.0,"HyperDash":false}]},{"StartTime":19573.0,"Objects":[{"StartTime":19573.0,"Position":181.0,"HyperDash":false}]},{"StartTime":19876.0,"Objects":[{"StartTime":19876.0,"Position":181.0,"HyperDash":false}]},{"StartTime":20786.0,"Objects":[{"StartTime":20786.0,"Position":469.0,"HyperDash":false}]},{"StartTime":21089.0,"Objects":[{"StartTime":21089.0,"Position":373.0,"HyperDash":false}]},{"StartTime":21392.0,"Objects":[{"StartTime":21392.0,"Position":277.0,"HyperDash":false}]},{"StartTime":21998.0,"Objects":[{"StartTime":21998.0,"Position":243.0,"HyperDash":false}]},{"StartTime":22149.0,"Objects":[{"StartTime":22149.0,"Position":243.0,"HyperDash":false}]},{"StartTime":22301.0,"Objects":[{"StartTime":22301.0,"Position":243.0,"HyperDash":false}]},{"StartTime":22452.0,"Objects":[{"StartTime":22452.0,"Position":290.0,"HyperDash":false},{"StartTime":22509.0,"Position":295.0,"HyperDash":false},{"StartTime":22603.0,"Position":290.0,"HyperDash":false}]},{"StartTime":22755.0,"Objects":[{"StartTime":22755.0,"Position":290.0,"HyperDash":false}]},{"StartTime":23058.0,"Objects":[{"StartTime":23058.0,"Position":385.0,"HyperDash":false}]},{"StartTime":23361.0,"Objects":[{"StartTime":23361.0,"Position":385.0,"HyperDash":false}]},{"StartTime":24119.0,"Objects":[{"StartTime":24119.0,"Position":213.0,"HyperDash":false},{"StartTime":24176.0,"Position":203.351,"HyperDash":false},{"StartTime":24270.0,"Position":153.0,"HyperDash":false}]},{"StartTime":24422.0,"Objects":[{"StartTime":24422.0,"Position":104.0,"HyperDash":false}]},{"StartTime":25028.0,"Objects":[{"StartTime":25028.0,"Position":295.0,"HyperDash":false}]},{"StartTime":26240.0,"Objects":[{"StartTime":26240.0,"Position":56.0,"HyperDash":false}]},{"StartTime":26392.0,"Objects":[{"StartTime":26392.0,"Position":56.0,"HyperDash":false}]},{"StartTime":26543.0,"Objects":[{"StartTime":26543.0,"Position":56.0,"HyperDash":false}]},{"StartTime":26846.0,"Objects":[{"StartTime":26846.0,"Position":56.0,"HyperDash":false}]},{"StartTime":27149.0,"Objects":[{"StartTime":27149.0,"Position":151.0,"HyperDash":false}]},{"StartTime":28058.0,"Objects":[{"StartTime":28058.0,"Position":438.0,"HyperDash":false},{"StartTime":28124.0,"Position":455.0,"HyperDash":false},{"StartTime":28191.0,"Position":455.0,"HyperDash":false},{"StartTime":28258.0,"Position":453.0,"HyperDash":false},{"StartTime":28361.0,"Position":438.0,"HyperDash":false}]},{"StartTime":29270.0,"Objects":[{"StartTime":29270.0,"Position":184.0,"HyperDash":false},{"StartTime":29336.0,"Position":227.138611,"HyperDash":false},{"StartTime":29403.0,"Position":245.673264,"HyperDash":false},{"StartTime":29470.0,"Position":246.207916,"HyperDash":false},{"StartTime":29573.0,"Position":304.0,"HyperDash":false}]},{"StartTime":29876.0,"Objects":[{"StartTime":29876.0,"Position":399.0,"HyperDash":false}]},{"StartTime":30180.0,"Objects":[{"StartTime":30180.0,"Position":399.0,"HyperDash":false}]},{"StartTime":30483.0,"Objects":[{"StartTime":30483.0,"Position":303.0,"HyperDash":false},{"StartTime":30549.0,"Position":281.8614,"HyperDash":false},{"StartTime":30616.0,"Position":238.326736,"HyperDash":false},{"StartTime":30683.0,"Position":208.792084,"HyperDash":false},{"StartTime":30786.0,"Position":183.0,"HyperDash":false}]},{"StartTime":31089.0,"Objects":[{"StartTime":31089.0,"Position":115.0,"HyperDash":false},{"StartTime":31155.0,"Position":159.138611,"HyperDash":false},{"StartTime":31222.0,"Position":159.673264,"HyperDash":false},{"StartTime":31289.0,"Position":210.207916,"HyperDash":false},{"StartTime":31392.0,"Position":235.0,"HyperDash":false}]},{"StartTime":31695.0,"Objects":[{"StartTime":31695.0,"Position":330.0,"HyperDash":false}]},{"StartTime":31998.0,"Objects":[{"StartTime":31998.0,"Position":425.0,"HyperDash":false}]},{"StartTime":32301.0,"Objects":[{"StartTime":32301.0,"Position":425.0,"HyperDash":false},{"StartTime":32367.0,"Position":401.8614,"HyperDash":false},{"StartTime":32434.0,"Position":362.326721,"HyperDash":false},{"StartTime":32501.0,"Position":346.792084,"HyperDash":false},{"StartTime":32604.0,"Position":305.0,"HyperDash":false}]},{"StartTime":32907.0,"Objects":[{"StartTime":32907.0,"Position":209.0,"HyperDash":false},{"StartTime":32973.0,"Position":172.861389,"HyperDash":false},{"StartTime":33040.0,"Position":156.326736,"HyperDash":false},{"StartTime":33107.0,"Position":111.792084,"HyperDash":false},{"StartTime":33210.0,"Position":89.0,"HyperDash":false}]},{"StartTime":33513.0,"Objects":[{"StartTime":33513.0,"Position":89.0,"HyperDash":false}]},{"StartTime":33816.0,"Objects":[{"StartTime":33816.0,"Position":184.0,"HyperDash":false}]},{"StartTime":34119.0,"Objects":[{"StartTime":34119.0,"Position":279.0,"HyperDash":false}]},{"StartTime":34422.0,"Objects":[{"StartTime":34422.0,"Position":374.0,"HyperDash":false}]},{"StartTime":34725.0,"Objects":[{"StartTime":34725.0,"Position":469.0,"HyperDash":false},{"StartTime":34791.0,"Position":453.0,"HyperDash":false},{"StartTime":34858.0,"Position":477.0,"HyperDash":false},{"StartTime":34925.0,"Position":456.0,"HyperDash":false},{"StartTime":35028.0,"Position":469.0,"HyperDash":false}]},{"StartTime":35331.0,"Objects":[{"StartTime":35331.0,"Position":373.0,"HyperDash":false},{"StartTime":35397.0,"Position":326.8614,"HyperDash":false},{"StartTime":35464.0,"Position":315.326721,"HyperDash":false},{"StartTime":35531.0,"Position":282.792084,"HyperDash":false},{"StartTime":35634.0,"Position":253.0,"HyperDash":false}]},{"StartTime":35937.0,"Objects":[{"StartTime":35937.0,"Position":157.0,"HyperDash":false}]},{"StartTime":36240.0,"Objects":[{"StartTime":36240.0,"Position":157.0,"HyperDash":false}]},{"StartTime":36392.0,"Objects":[{"StartTime":36392.0,"Position":157.0,"HyperDash":false}]},{"StartTime":36543.0,"Objects":[{"StartTime":36543.0,"Position":204.0,"HyperDash":false},{"StartTime":36618.0,"Position":241.702972,"HyperDash":false},{"StartTime":36694.0,"Position":264.0,"HyperDash":false},{"StartTime":36752.0,"Position":239.227722,"HyperDash":false},{"StartTime":36846.0,"Position":204.0,"HyperDash":false}]},{"StartTime":36998.0,"Objects":[{"StartTime":36998.0,"Position":204.0,"HyperDash":false},{"StartTime":37055.0,"Position":221.0,"HyperDash":false},{"StartTime":37149.0,"Position":204.0,"HyperDash":false}]},{"StartTime":37301.0,"Objects":[{"StartTime":37301.0,"Position":205.0,"HyperDash":false}]},{"StartTime":37604.0,"Objects":[{"StartTime":37604.0,"Position":300.0,"HyperDash":false}]},{"StartTime":37907.0,"Objects":[{"StartTime":37907.0,"Position":300.0,"HyperDash":false}]},{"StartTime":38967.0,"Objects":[{"StartTime":38967.0,"Position":32.0,"HyperDash":false}]},{"StartTime":39573.0,"Objects":[{"StartTime":39573.0,"Position":32.0,"HyperDash":false}]},{"StartTime":40786.0,"Objects":[{"StartTime":40786.0,"Position":416.0,"HyperDash":false}]},{"StartTime":40937.0,"Objects":[{"StartTime":40937.0,"Position":416.0,"HyperDash":false}]},{"StartTime":41089.0,"Objects":[{"StartTime":41089.0,"Position":416.0,"HyperDash":false}]},{"StartTime":41392.0,"Objects":[{"StartTime":41392.0,"Position":320.0,"HyperDash":false}]},{"StartTime":41695.0,"Objects":[{"StartTime":41695.0,"Position":320.0,"HyperDash":false}]},{"StartTime":42604.0,"Objects":[{"StartTime":42604.0,"Position":48.0,"HyperDash":false},{"StartTime":42670.0,"Position":57.13861,"HyperDash":false},{"StartTime":42737.0,"Position":105.673264,"HyperDash":false},{"StartTime":42804.0,"Position":146.207916,"HyperDash":false},{"StartTime":42907.0,"Position":168.0,"HyperDash":false}]},{"StartTime":43210.0,"Objects":[{"StartTime":43210.0,"Position":263.0,"HyperDash":false}]},{"StartTime":43816.0,"Objects":[{"StartTime":43816.0,"Position":376.0,"HyperDash":false},{"StartTime":43891.0,"Position":326.594055,"HyperDash":false},{"StartTime":43967.0,"Position":256.396027,"HyperDash":false},{"StartTime":44043.0,"Position":200.198029,"HyperDash":false},{"StartTime":44119.0,"Position":136.0,"HyperDash":false},{"StartTime":44194.0,"Position":202.405945,"HyperDash":false},{"StartTime":44270.0,"Position":255.603943,"HyperDash":false},{"StartTime":44346.0,"Position":300.802,"HyperDash":false},{"StartTime":44422.0,"Position":376.0,"HyperDash":false},{"StartTime":44497.0,"Position":313.594055,"HyperDash":false},{"StartTime":44573.0,"Position":256.396027,"HyperDash":false},{"StartTime":44649.0,"Position":201.198044,"HyperDash":false},{"StartTime":44725.0,"Position":136.0,"HyperDash":false},{"StartTime":44800.0,"Position":204.405945,"HyperDash":false},{"StartTime":44876.0,"Position":255.603973,"HyperDash":false},{"StartTime":44952.0,"Position":305.801971,"HyperDash":false},{"StartTime":45028.0,"Position":376.0,"HyperDash":false},{"StartTime":45103.0,"Position":306.594055,"HyperDash":false},{"StartTime":45179.0,"Position":256.3961,"HyperDash":false},{"StartTime":45237.0,"Position":224.45549,"HyperDash":false},{"StartTime":45331.0,"Position":136.0,"HyperDash":false}]},{"StartTime":45634.0,"Objects":[{"StartTime":45634.0,"Position":376.0,"HyperDash":false},{"StartTime":45709.0,"Position":323.594055,"HyperDash":false},{"StartTime":45785.0,"Position":256.396027,"HyperDash":false},{"StartTime":45861.0,"Position":198.198029,"HyperDash":false},{"StartTime":45937.0,"Position":136.0,"HyperDash":false},{"StartTime":46012.0,"Position":176.405945,"HyperDash":false},{"StartTime":46088.0,"Position":255.603943,"HyperDash":false},{"StartTime":46164.0,"Position":318.802,"HyperDash":false},{"StartTime":46240.0,"Position":376.0,"HyperDash":false},{"StartTime":46315.0,"Position":324.594055,"HyperDash":false},{"StartTime":46391.0,"Position":256.396027,"HyperDash":false},{"StartTime":46467.0,"Position":199.198044,"HyperDash":false},{"StartTime":46543.0,"Position":136.0,"HyperDash":false},{"StartTime":46618.0,"Position":193.405945,"HyperDash":false},{"StartTime":46694.0,"Position":255.603973,"HyperDash":false},{"StartTime":46770.0,"Position":298.801971,"HyperDash":false},{"StartTime":46846.0,"Position":376.0,"HyperDash":false},{"StartTime":46921.0,"Position":327.594055,"HyperDash":false},{"StartTime":46997.0,"Position":256.3961,"HyperDash":false},{"StartTime":47055.0,"Position":217.45549,"HyperDash":false},{"StartTime":47149.0,"Position":136.0,"HyperDash":false}]},{"StartTime":47452.0,"Objects":[{"StartTime":47452.0,"Position":376.0,"HyperDash":false},{"StartTime":47527.0,"Position":327.594055,"HyperDash":false},{"StartTime":47603.0,"Position":256.396027,"HyperDash":false},{"StartTime":47679.0,"Position":189.198029,"HyperDash":false},{"StartTime":47755.0,"Position":136.0,"HyperDash":false},{"StartTime":47830.0,"Position":195.405945,"HyperDash":false},{"StartTime":47906.0,"Position":255.603943,"HyperDash":false},{"StartTime":47982.0,"Position":300.802,"HyperDash":false},{"StartTime":48058.0,"Position":376.0,"HyperDash":false},{"StartTime":48133.0,"Position":324.594055,"HyperDash":false},{"StartTime":48209.0,"Position":256.396027,"HyperDash":false},{"StartTime":48285.0,"Position":188.198044,"HyperDash":false},{"StartTime":48361.0,"Position":136.0,"HyperDash":false},{"StartTime":48436.0,"Position":199.405945,"HyperDash":false},{"StartTime":48512.0,"Position":255.603973,"HyperDash":false},{"StartTime":48588.0,"Position":310.801971,"HyperDash":false},{"StartTime":48664.0,"Position":376.0,"HyperDash":false},{"StartTime":48739.0,"Position":317.594055,"HyperDash":false},{"StartTime":48815.0,"Position":256.3961,"HyperDash":false},{"StartTime":48873.0,"Position":213.45549,"HyperDash":false},{"StartTime":48967.0,"Position":136.0,"HyperDash":false}]},{"StartTime":49270.0,"Objects":[{"StartTime":49270.0,"Position":376.0,"HyperDash":false},{"StartTime":49345.0,"Position":335.594482,"HyperDash":false},{"StartTime":49421.0,"Position":256.396881,"HyperDash":false},{"StartTime":49479.0,"Position":210.456619,"HyperDash":false},{"StartTime":49573.0,"Position":136.001678,"HyperDash":false}]},{"StartTime":49876.0,"Objects":[{"StartTime":49876.0,"Position":136.0,"HyperDash":false}]},{"StartTime":50180.0,"Objects":[{"StartTime":50180.0,"Position":328.0,"HyperDash":false}]},{"StartTime":50483.0,"Objects":[{"StartTime":50483.0,"Position":329.0,"HyperDash":false}]},{"StartTime":50786.0,"Objects":[{"StartTime":50786.0,"Position":136.0,"HyperDash":false}]},{"StartTime":50937.0,"Objects":[{"StartTime":50937.0,"Position":138.0,"HyperDash":false}]},{"StartTime":51089.0,"Objects":[{"StartTime":51089.0,"Position":138.0,"HyperDash":false},{"StartTime":51146.0,"Position":142.649,"HyperDash":false},{"StartTime":51240.0,"Position":198.0,"HyperDash":false}]},{"StartTime":51392.0,"Objects":[{"StartTime":51392.0,"Position":198.0,"HyperDash":false}]},{"StartTime":51543.0,"Objects":[{"StartTime":51543.0,"Position":246.0,"HyperDash":false}]},{"StartTime":51695.0,"Objects":[{"StartTime":51695.0,"Position":295.0,"HyperDash":false},{"StartTime":51752.0,"Position":303.649017,"HyperDash":false},{"StartTime":51846.0,"Position":355.0,"HyperDash":false}]},{"StartTime":52149.0,"Objects":[{"StartTime":52149.0,"Position":355.0,"HyperDash":false}]},{"StartTime":52452.0,"Objects":[{"StartTime":52452.0,"Position":260.0,"HyperDash":false}]},{"StartTime":53513.0,"Objects":[{"StartTime":53513.0,"Position":40.0,"HyperDash":false},{"StartTime":53588.0,"Position":68.70297,"HyperDash":false},{"StartTime":53664.0,"Position":116.801987,"HyperDash":false},{"StartTime":53740.0,"Position":141.900986,"HyperDash":false},{"StartTime":53816.0,"Position":160.0,"HyperDash":false},{"StartTime":53882.0,"Position":184.138626,"HyperDash":false},{"StartTime":53949.0,"Position":229.673264,"HyperDash":false},{"StartTime":54016.0,"Position":230.207916,"HyperDash":false},{"StartTime":54119.0,"Position":280.0,"HyperDash":false}]},{"StartTime":55331.0,"Objects":[{"StartTime":55331.0,"Position":40.0,"HyperDash":false},{"StartTime":55406.0,"Position":84.70297,"HyperDash":false},{"StartTime":55482.0,"Position":100.0,"HyperDash":false},{"StartTime":55540.0,"Position":64.22772,"HyperDash":false},{"StartTime":55634.0,"Position":40.0,"HyperDash":false}]},{"StartTime":55937.0,"Objects":[{"StartTime":55937.0,"Position":40.0,"HyperDash":false},{"StartTime":56003.0,"Position":29.0,"HyperDash":false},{"StartTime":56070.0,"Position":44.0,"HyperDash":false},{"StartTime":56137.0,"Position":28.0,"HyperDash":false},{"StartTime":56240.0,"Position":40.0,"HyperDash":false}]},{"StartTime":57149.0,"Objects":[{"StartTime":57149.0,"Position":300.0,"HyperDash":false},{"StartTime":57215.0,"Position":316.0,"HyperDash":false},{"StartTime":57282.0,"Position":288.0,"HyperDash":false},{"StartTime":57349.0,"Position":299.0,"HyperDash":false},{"StartTime":57452.0,"Position":300.0,"HyperDash":false}]},{"StartTime":58361.0,"Objects":[{"StartTime":58361.0,"Position":256.0,"HyperDash":false},{"StartTime":58418.0,"Position":265.649017,"HyperDash":false},{"StartTime":58512.0,"Position":316.0,"HyperDash":false}]},{"StartTime":58664.0,"Objects":[{"StartTime":58664.0,"Position":364.0,"HyperDash":false},{"StartTime":58721.0,"Position":358.0,"HyperDash":false},{"StartTime":58815.0,"Position":364.0,"HyperDash":false}]},{"StartTime":58967.0,"Objects":[{"StartTime":58967.0,"Position":329.0,"HyperDash":false}]},{"StartTime":59119.0,"Objects":[{"StartTime":59119.0,"Position":280.0,"HyperDash":false},{"StartTime":59176.0,"Position":249.350983,"HyperDash":false},{"StartTime":59270.0,"Position":220.0,"HyperDash":false}]},{"StartTime":59422.0,"Objects":[{"StartTime":59422.0,"Position":185.0,"HyperDash":false},{"StartTime":59479.0,"Position":176.0,"HyperDash":false},{"StartTime":59573.0,"Position":185.0,"HyperDash":false}]},{"StartTime":59876.0,"Objects":[{"StartTime":59876.0,"Position":185.0,"HyperDash":false}]},{"StartTime":60180.0,"Objects":[{"StartTime":60180.0,"Position":253.0,"HyperDash":false}]},{"StartTime":60331.0,"Objects":[{"StartTime":60331.0,"Position":253.0,"HyperDash":false}]},{"StartTime":60483.0,"Objects":[{"StartTime":60483.0,"Position":253.0,"HyperDash":false}]},{"StartTime":60634.0,"Objects":[{"StartTime":60634.0,"Position":253.0,"HyperDash":false}]},{"StartTime":60786.0,"Objects":[{"StartTime":60786.0,"Position":253.0,"HyperDash":false},{"StartTime":60861.0,"Position":218.297028,"HyperDash":false},{"StartTime":60937.0,"Position":193.0,"HyperDash":false},{"StartTime":61013.0,"Position":217.900986,"HyperDash":false},{"StartTime":61089.0,"Position":253.0,"HyperDash":false},{"StartTime":61164.0,"Position":237.297028,"HyperDash":false},{"StartTime":61240.0,"Position":193.0,"HyperDash":false},{"StartTime":61298.0,"Position":218.772278,"HyperDash":false},{"StartTime":61392.0,"Position":253.0,"HyperDash":false}]},{"StartTime":61695.0,"Objects":[{"StartTime":61695.0,"Position":253.0,"HyperDash":false}]},{"StartTime":61998.0,"Objects":[{"StartTime":61998.0,"Position":348.0,"HyperDash":false},{"StartTime":62073.0,"Position":336.0,"HyperDash":false},{"StartTime":62149.0,"Position":348.0,"HyperDash":false},{"StartTime":62225.0,"Position":336.0,"HyperDash":false},{"StartTime":62301.0,"Position":348.0,"HyperDash":false},{"StartTime":62376.0,"Position":333.0,"HyperDash":false},{"StartTime":62452.0,"Position":348.0,"HyperDash":false},{"StartTime":62510.0,"Position":344.0,"HyperDash":false},{"StartTime":62604.0,"Position":348.0,"HyperDash":false}]},{"StartTime":62755.0,"Objects":[{"StartTime":62755.0,"Position":348.0,"HyperDash":false}]},{"StartTime":62907.0,"Objects":[{"StartTime":62907.0,"Position":348.0,"HyperDash":false}]},{"StartTime":63058.0,"Objects":[{"StartTime":63058.0,"Position":348.0,"HyperDash":false}]},{"StartTime":63210.0,"Objects":[{"StartTime":63210.0,"Position":348.0,"HyperDash":false}]},{"StartTime":63513.0,"Objects":[{"StartTime":63513.0,"Position":252.0,"HyperDash":false}]},{"StartTime":63816.0,"Objects":[{"StartTime":63816.0,"Position":252.0,"HyperDash":false}]},{"StartTime":63967.0,"Objects":[{"StartTime":63967.0,"Position":252.0,"HyperDash":false},{"StartTime":64042.0,"Position":225.264313,"HyperDash":false},{"StartTime":64118.0,"Position":192.0,"HyperDash":false},{"StartTime":64194.0,"Position":203.0,"HyperDash":false},{"StartTime":64270.0,"Position":252.0,"HyperDash":false},{"StartTime":64327.0,"Position":229.268738,"HyperDash":false},{"StartTime":64421.0,"Position":192.0,"HyperDash":false}]},{"StartTime":64725.0,"Objects":[{"StartTime":64725.0,"Position":288.0,"HyperDash":false}]},{"StartTime":65028.0,"Objects":[{"StartTime":65028.0,"Position":383.0,"HyperDash":false}]},{"StartTime":65331.0,"Objects":[{"StartTime":65331.0,"Position":383.0,"HyperDash":false}]},{"StartTime":65634.0,"Objects":[{"StartTime":65634.0,"Position":287.0,"HyperDash":false},{"StartTime":65691.0,"Position":277.350983,"HyperDash":false},{"StartTime":65785.0,"Position":227.0,"HyperDash":false}]},{"StartTime":65937.0,"Objects":[{"StartTime":65937.0,"Position":178.0,"HyperDash":false}]},{"StartTime":66089.0,"Objects":[{"StartTime":66089.0,"Position":129.0,"HyperDash":false}]},{"StartTime":66240.0,"Objects":[{"StartTime":66240.0,"Position":81.0,"HyperDash":false}]},{"StartTime":66392.0,"Objects":[{"StartTime":66392.0,"Position":81.0,"HyperDash":false}]},{"StartTime":66543.0,"Objects":[{"StartTime":66543.0,"Position":81.0,"HyperDash":false},{"StartTime":66600.0,"Position":100.64901,"HyperDash":false},{"StartTime":66694.0,"Position":141.0,"HyperDash":false}]},{"StartTime":66846.0,"Objects":[{"StartTime":66846.0,"Position":189.0,"HyperDash":false},{"StartTime":66903.0,"Position":227.649,"HyperDash":false},{"StartTime":66997.0,"Position":249.0,"HyperDash":false}]},{"StartTime":67755.0,"Objects":[{"StartTime":67755.0,"Position":192.0,"HyperDash":false},{"StartTime":67812.0,"Position":205.649,"HyperDash":false},{"StartTime":67906.0,"Position":252.0,"HyperDash":false}]},{"StartTime":68058.0,"Objects":[{"StartTime":68058.0,"Position":300.0,"HyperDash":false}]},{"StartTime":68664.0,"Objects":[{"StartTime":68664.0,"Position":300.0,"HyperDash":false}]},{"StartTime":68967.0,"Objects":[{"StartTime":68967.0,"Position":300.0,"HyperDash":false}]},{"StartTime":69270.0,"Objects":[{"StartTime":69270.0,"Position":204.0,"HyperDash":false}]},{"StartTime":69876.0,"Objects":[{"StartTime":69876.0,"Position":395.0,"HyperDash":false},{"StartTime":69933.0,"Position":384.272858,"HyperDash":false},{"StartTime":70027.0,"Position":395.722839,"HyperDash":false}]},{"StartTime":70180.0,"Objects":[{"StartTime":70180.0,"Position":395.0,"HyperDash":false}]},{"StartTime":70483.0,"Objects":[{"StartTime":70483.0,"Position":296.0,"HyperDash":false}]},{"StartTime":70786.0,"Objects":[{"StartTime":70786.0,"Position":200.0,"HyperDash":false}]},{"StartTime":71695.0,"Objects":[{"StartTime":71695.0,"Position":200.0,"HyperDash":false}]},{"StartTime":71998.0,"Objects":[{"StartTime":71998.0,"Position":295.0,"HyperDash":false}]},{"StartTime":72907.0,"Objects":[{"StartTime":72907.0,"Position":91.0,"HyperDash":false}]},{"StartTime":73058.0,"Objects":[{"StartTime":73058.0,"Position":138.0,"HyperDash":false}]},{"StartTime":73210.0,"Objects":[{"StartTime":73210.0,"Position":186.0,"HyperDash":false}]},{"StartTime":73361.0,"Objects":[{"StartTime":73361.0,"Position":186.0,"HyperDash":false},{"StartTime":73418.0,"Position":194.649,"HyperDash":false},{"StartTime":73512.0,"Position":246.0,"HyperDash":false}]},{"StartTime":73664.0,"Objects":[{"StartTime":73664.0,"Position":294.0,"HyperDash":false},{"StartTime":73721.0,"Position":334.649017,"HyperDash":false},{"StartTime":73815.0,"Position":354.0,"HyperDash":false}]},{"StartTime":73967.0,"Objects":[{"StartTime":73967.0,"Position":354.0,"HyperDash":false}]},{"StartTime":74270.0,"Objects":[{"StartTime":74270.0,"Position":354.0,"HyperDash":false}]},{"StartTime":75331.0,"Objects":[{"StartTime":75331.0,"Position":40.0,"HyperDash":false}]},{"StartTime":75937.0,"Objects":[{"StartTime":75937.0,"Position":160.0,"HyperDash":false}]},{"StartTime":76089.0,"Objects":[{"StartTime":76089.0,"Position":160.0,"HyperDash":false}]},{"StartTime":76543.0,"Objects":[{"StartTime":76543.0,"Position":303.0,"HyperDash":false}]},{"StartTime":77149.0,"Objects":[{"StartTime":77149.0,"Position":160.0,"HyperDash":false},{"StartTime":77206.0,"Position":192.649,"HyperDash":false},{"StartTime":77300.0,"Position":220.0,"HyperDash":false}]},{"StartTime":77452.0,"Objects":[{"StartTime":77452.0,"Position":268.0,"HyperDash":false}]},{"StartTime":77755.0,"Objects":[{"StartTime":77755.0,"Position":268.0,"HyperDash":false}]},{"StartTime":78058.0,"Objects":[{"StartTime":78058.0,"Position":268.0,"HyperDash":false}]},{"StartTime":78361.0,"Objects":[{"StartTime":78361.0,"Position":363.0,"HyperDash":false},{"StartTime":78418.0,"Position":382.0,"HyperDash":false},{"StartTime":78512.0,"Position":363.0,"HyperDash":false}]},{"StartTime":78967.0,"Objects":[{"StartTime":78967.0,"Position":363.0,"HyperDash":false}]},{"StartTime":79270.0,"Objects":[{"StartTime":79270.0,"Position":267.0,"HyperDash":false},{"StartTime":79336.0,"Position":223.861389,"HyperDash":false},{"StartTime":79403.0,"Position":208.326736,"HyperDash":false},{"StartTime":79470.0,"Position":193.792084,"HyperDash":false},{"StartTime":79573.0,"Position":147.0,"HyperDash":false}]},{"StartTime":80180.0,"Objects":[{"StartTime":80180.0,"Position":96.0,"HyperDash":false},{"StartTime":80255.0,"Position":108.0,"HyperDash":false},{"StartTime":80331.0,"Position":83.0,"HyperDash":false},{"StartTime":80407.0,"Position":82.0,"HyperDash":false},{"StartTime":80483.0,"Position":96.0,"HyperDash":false},{"StartTime":80558.0,"Position":99.0,"HyperDash":false},{"StartTime":80634.0,"Position":82.0,"HyperDash":false},{"StartTime":80710.0,"Position":102.0,"HyperDash":false},{"StartTime":80786.0,"Position":96.0,"HyperDash":false},{"StartTime":80861.0,"Position":86.0,"HyperDash":false},{"StartTime":80937.0,"Position":89.0,"HyperDash":false},{"StartTime":81013.0,"Position":123.90097,"HyperDash":false},{"StartTime":81089.0,"Position":144.0,"HyperDash":false},{"StartTime":81155.0,"Position":177.138611,"HyperDash":false},{"StartTime":81222.0,"Position":194.673279,"HyperDash":false},{"StartTime":81289.0,"Position":229.207916,"HyperDash":false},{"StartTime":81392.0,"Position":264.0,"HyperDash":false}]},{"StartTime":81695.0,"Objects":[{"StartTime":81695.0,"Position":360.0,"HyperDash":false}]},{"StartTime":81998.0,"Objects":[{"StartTime":81998.0,"Position":455.0,"HyperDash":false},{"StartTime":82073.0,"Position":449.0,"HyperDash":false},{"StartTime":82149.0,"Position":448.0,"HyperDash":false},{"StartTime":82225.0,"Position":474.0,"HyperDash":false},{"StartTime":82301.0,"Position":455.0,"HyperDash":false},{"StartTime":82376.0,"Position":455.0,"HyperDash":false},{"StartTime":82452.0,"Position":470.0,"HyperDash":false},{"StartTime":82528.0,"Position":439.0,"HyperDash":false},{"StartTime":82604.0,"Position":455.0,"HyperDash":false},{"StartTime":82679.0,"Position":458.0,"HyperDash":false},{"StartTime":82755.0,"Position":451.0,"HyperDash":false},{"StartTime":82831.0,"Position":445.09903,"HyperDash":false},{"StartTime":82907.0,"Position":407.0,"HyperDash":false},{"StartTime":82982.0,"Position":392.297028,"HyperDash":false},{"StartTime":83058.0,"Position":335.198029,"HyperDash":false},{"StartTime":83134.0,"Position":301.09903,"HyperDash":false},{"StartTime":83210.0,"Position":287.0,"HyperDash":false},{"StartTime":83276.0,"Position":242.861389,"HyperDash":false},{"StartTime":83343.0,"Position":230.326721,"HyperDash":false},{"StartTime":83410.0,"Position":212.792053,"HyperDash":false},{"StartTime":83513.0,"Position":167.0,"HyperDash":false}]},{"StartTime":83816.0,"Objects":[{"StartTime":83816.0,"Position":124.0,"HyperDash":false},{"StartTime":83891.0,"Position":154.702972,"HyperDash":false},{"StartTime":83967.0,"Position":181.801987,"HyperDash":false},{"StartTime":84043.0,"Position":229.900986,"HyperDash":false},{"StartTime":84119.0,"Position":244.0,"HyperDash":false},{"StartTime":84194.0,"Position":287.702972,"HyperDash":false},{"StartTime":84270.0,"Position":290.801971,"HyperDash":false},{"StartTime":84346.0,"Position":332.901,"HyperDash":false},{"StartTime":84422.0,"Position":364.0,"HyperDash":false},{"StartTime":84497.0,"Position":367.0,"HyperDash":false},{"StartTime":84573.0,"Position":374.0,"HyperDash":false},{"StartTime":84649.0,"Position":360.0,"HyperDash":false},{"StartTime":84725.0,"Position":364.0,"HyperDash":false},{"StartTime":84791.0,"Position":368.0,"HyperDash":false},{"StartTime":84858.0,"Position":369.0,"HyperDash":false},{"StartTime":84925.0,"Position":364.0,"HyperDash":false},{"StartTime":85028.0,"Position":364.0,"HyperDash":false}]},{"StartTime":85331.0,"Objects":[{"StartTime":85331.0,"Position":268.0,"HyperDash":false}]},{"StartTime":85634.0,"Objects":[{"StartTime":85634.0,"Position":172.0,"HyperDash":false},{"StartTime":85709.0,"Position":124.288116,"HyperDash":false},{"StartTime":85785.0,"Position":93.18007,"HyperDash":false},{"StartTime":85861.0,"Position":71.07203,"HyperDash":false},{"StartTime":85937.0,"Position":52.0,"HyperDash":false},{"StartTime":86012.0,"Position":45.0,"HyperDash":false},{"StartTime":86088.0,"Position":68.0,"HyperDash":false},{"StartTime":86164.0,"Position":66.0,"HyperDash":false},{"StartTime":86240.0,"Position":52.0,"HyperDash":false},{"StartTime":86315.0,"Position":33.0,"HyperDash":false},{"StartTime":86391.0,"Position":34.0,"HyperDash":false},{"StartTime":86467.0,"Position":66.0,"HyperDash":false},{"StartTime":86543.0,"Position":76.10803,"HyperDash":false},{"StartTime":86618.0,"Position":109.819916,"HyperDash":false},{"StartTime":86694.0,"Position":132.927948,"HyperDash":false},{"StartTime":86770.0,"Position":153.036011,"HyperDash":false},{"StartTime":86846.0,"Position":196.144073,"HyperDash":false},{"StartTime":86921.0,"Position":235.855927,"HyperDash":false},{"StartTime":86997.0,"Position":237.963989,"HyperDash":false},{"StartTime":87073.0,"Position":282.072021,"HyperDash":false},{"StartTime":87149.0,"Position":316.0,"HyperDash":false},{"StartTime":87206.0,"Position":327.0,"HyperDash":false},{"StartTime":87300.0,"Position":316.0,"HyperDash":false}]},{"StartTime":87452.0,"Objects":[{"StartTime":87452.0,"Position":316.0,"HyperDash":false},{"StartTime":87518.0,"Position":297.0,"HyperDash":false},{"StartTime":87585.0,"Position":333.0,"HyperDash":false},{"StartTime":87652.0,"Position":325.0,"HyperDash":false},{"StartTime":87755.0,"Position":316.0,"HyperDash":false}]},{"StartTime":88058.0,"Objects":[{"StartTime":88058.0,"Position":411.0,"HyperDash":false},{"StartTime":88133.0,"Position":411.0,"HyperDash":false},{"StartTime":88209.0,"Position":410.0,"HyperDash":false},{"StartTime":88285.0,"Position":423.0,"HyperDash":false},{"StartTime":88361.0,"Position":411.0,"HyperDash":false},{"StartTime":88436.0,"Position":412.0,"HyperDash":false},{"StartTime":88512.0,"Position":398.0,"HyperDash":false},{"StartTime":88588.0,"Position":414.0,"HyperDash":false},{"StartTime":88664.0,"Position":411.0,"HyperDash":false},{"StartTime":88739.0,"Position":382.297028,"HyperDash":false},{"StartTime":88815.0,"Position":340.198,"HyperDash":false},{"StartTime":88891.0,"Position":331.09903,"HyperDash":false},{"StartTime":88967.0,"Position":299.0,"HyperDash":false},{"StartTime":89033.0,"Position":253.861389,"HyperDash":false},{"StartTime":89100.0,"Position":231.326721,"HyperDash":false},{"StartTime":89167.0,"Position":225.792084,"HyperDash":false},{"StartTime":89270.0,"Position":179.0,"HyperDash":false}]},{"StartTime":89876.0,"Objects":[{"StartTime":89876.0,"Position":176.0,"HyperDash":false},{"StartTime":89951.0,"Position":144.297028,"HyperDash":false},{"StartTime":90027.0,"Position":110.198013,"HyperDash":false},{"StartTime":90103.0,"Position":73.0990143,"HyperDash":false},{"StartTime":90179.0,"Position":56.0,"HyperDash":false},{"StartTime":90245.0,"Position":34.0,"HyperDash":false},{"StartTime":90312.0,"Position":29.0,"HyperDash":false},{"StartTime":90379.0,"Position":40.0,"HyperDash":false},{"StartTime":90482.0,"Position":40.0,"HyperDash":false}]},{"StartTime":91089.0,"Objects":[{"StartTime":91089.0,"Position":232.0,"HyperDash":false}]},{"StartTime":91695.0,"Objects":[{"StartTime":91695.0,"Position":423.0,"HyperDash":false},{"StartTime":91770.0,"Position":409.0,"HyperDash":false},{"StartTime":91846.0,"Position":424.0,"HyperDash":false},{"StartTime":91922.0,"Position":438.0,"HyperDash":false},{"StartTime":91998.0,"Position":423.0,"HyperDash":false},{"StartTime":92073.0,"Position":417.0,"HyperDash":false},{"StartTime":92149.0,"Position":420.198029,"HyperDash":false},{"StartTime":92225.0,"Position":354.099,"HyperDash":false},{"StartTime":92301.0,"Position":343.0,"HyperDash":false},{"StartTime":92376.0,"Position":331.297028,"HyperDash":false},{"StartTime":92452.0,"Position":266.198,"HyperDash":false},{"StartTime":92528.0,"Position":237.09903,"HyperDash":false},{"StartTime":92604.0,"Position":223.0,"HyperDash":false},{"StartTime":92670.0,"Position":208.861389,"HyperDash":false},{"StartTime":92737.0,"Position":185.326721,"HyperDash":false},{"StartTime":92804.0,"Position":135.792084,"HyperDash":false},{"StartTime":92907.0,"Position":103.0,"HyperDash":false}]},{"StartTime":93513.0,"Objects":[{"StartTime":93513.0,"Position":112.0,"HyperDash":false}]},{"StartTime":94119.0,"Objects":[{"StartTime":94119.0,"Position":303.0,"HyperDash":false}]},{"StartTime":94725.0,"Objects":[{"StartTime":94725.0,"Position":440.0,"HyperDash":false},{"StartTime":94800.0,"Position":426.0,"HyperDash":false},{"StartTime":94876.0,"Position":436.0,"HyperDash":false},{"StartTime":94952.0,"Position":453.0,"HyperDash":false},{"StartTime":95028.0,"Position":440.0,"HyperDash":false},{"StartTime":95103.0,"Position":440.0,"HyperDash":false},{"StartTime":95179.0,"Position":433.0,"HyperDash":false},{"StartTime":95255.0,"Position":456.0,"HyperDash":false},{"StartTime":95331.0,"Position":440.0,"HyperDash":false},{"StartTime":95406.0,"Position":449.0,"HyperDash":false},{"StartTime":95482.0,"Position":433.0,"HyperDash":false},{"StartTime":95558.0,"Position":456.0,"HyperDash":false},{"StartTime":95634.0,"Position":440.0,"HyperDash":false},{"StartTime":95700.0,"Position":439.0,"HyperDash":false},{"StartTime":95767.0,"Position":423.0,"HyperDash":false},{"StartTime":95834.0,"Position":428.0,"HyperDash":false},{"StartTime":95937.0,"Position":440.0,"HyperDash":false}]},{"StartTime":96543.0,"Objects":[{"StartTime":96543.0,"Position":216.0,"HyperDash":false},{"StartTime":96618.0,"Position":204.0,"HyperDash":false},{"StartTime":96694.0,"Position":208.0,"HyperDash":false},{"StartTime":96770.0,"Position":218.0,"HyperDash":false},{"StartTime":96846.0,"Position":216.0,"HyperDash":false},{"StartTime":96912.0,"Position":233.0,"HyperDash":false},{"StartTime":96979.0,"Position":225.0,"HyperDash":false},{"StartTime":97046.0,"Position":206.0,"HyperDash":false},{"StartTime":97149.0,"Position":216.0,"HyperDash":false}]},{"StartTime":97755.0,"Objects":[{"StartTime":97755.0,"Position":48.0,"HyperDash":false}]},{"StartTime":98361.0,"Objects":[{"StartTime":98361.0,"Position":216.0,"HyperDash":false}]},{"StartTime":98967.0,"Objects":[{"StartTime":98967.0,"Position":216.0,"HyperDash":false},{"StartTime":99042.0,"Position":231.0,"HyperDash":false},{"StartTime":99118.0,"Position":207.0,"HyperDash":false},{"StartTime":99194.0,"Position":205.0,"HyperDash":false},{"StartTime":99270.0,"Position":216.0,"HyperDash":false},{"StartTime":99345.0,"Position":206.0,"HyperDash":false},{"StartTime":99421.0,"Position":218.0,"HyperDash":false},{"StartTime":99497.0,"Position":208.0,"HyperDash":false},{"StartTime":99573.0,"Position":216.0,"HyperDash":false},{"StartTime":99648.0,"Position":234.0,"HyperDash":false},{"StartTime":99724.0,"Position":222.0,"HyperDash":false},{"StartTime":99800.0,"Position":231.0,"HyperDash":false},{"StartTime":99876.0,"Position":216.0,"HyperDash":false},{"StartTime":99942.0,"Position":200.0,"HyperDash":false},{"StartTime":100009.0,"Position":199.0,"HyperDash":false},{"StartTime":100076.0,"Position":228.0,"HyperDash":false},{"StartTime":100179.0,"Position":216.0,"HyperDash":false}]},{"StartTime":100786.0,"Objects":[{"StartTime":100786.0,"Position":216.0,"HyperDash":false}]},{"StartTime":101392.0,"Objects":[{"StartTime":101392.0,"Position":216.0,"HyperDash":false}]},{"StartTime":101998.0,"Objects":[{"StartTime":101998.0,"Position":356.0,"HyperDash":false},{"StartTime":102054.0,"Position":362.0,"HyperDash":false},{"StartTime":102111.0,"Position":347.0,"HyperDash":false},{"StartTime":102168.0,"Position":252.0,"HyperDash":false},{"StartTime":102225.0,"Position":477.0,"HyperDash":false},{"StartTime":102282.0,"Position":358.0,"HyperDash":false},{"StartTime":102338.0,"Position":17.0,"HyperDash":false},{"StartTime":102395.0,"Position":399.0,"HyperDash":false},{"StartTime":102452.0,"Position":280.0,"HyperDash":false},{"StartTime":102509.0,"Position":304.0,"HyperDash":false},{"StartTime":102566.0,"Position":221.0,"HyperDash":false},{"StartTime":102622.0,"Position":407.0,"HyperDash":false},{"StartTime":102679.0,"Position":287.0,"HyperDash":false},{"StartTime":102736.0,"Position":135.0,"HyperDash":false},{"StartTime":102793.0,"Position":437.0,"HyperDash":false},{"StartTime":102850.0,"Position":289.0,"HyperDash":false},{"StartTime":102907.0,"Position":464.0,"HyperDash":false},{"StartTime":102963.0,"Position":36.0,"HyperDash":false},{"StartTime":103020.0,"Position":378.0,"HyperDash":false},{"StartTime":103077.0,"Position":297.0,"HyperDash":false},{"StartTime":103134.0,"Position":418.0,"HyperDash":false},{"StartTime":103191.0,"Position":329.0,"HyperDash":false},{"StartTime":103247.0,"Position":338.0,"HyperDash":false},{"StartTime":103304.0,"Position":394.0,"HyperDash":false},{"StartTime":103361.0,"Position":40.0,"HyperDash":false},{"StartTime":103418.0,"Position":13.0,"HyperDash":false},{"StartTime":103475.0,"Position":80.0,"HyperDash":false},{"StartTime":103531.0,"Position":138.0,"HyperDash":false},{"StartTime":103588.0,"Position":311.0,"HyperDash":false},{"StartTime":103645.0,"Position":216.0,"HyperDash":false},{"StartTime":103702.0,"Position":310.0,"HyperDash":false},{"StartTime":103759.0,"Position":397.0,"HyperDash":false},{"StartTime":103816.0,"Position":214.0,"HyperDash":false},{"StartTime":103872.0,"Position":505.0,"HyperDash":false},{"StartTime":103929.0,"Position":173.0,"HyperDash":false},{"StartTime":103986.0,"Position":295.0,"HyperDash":false},{"StartTime":104043.0,"Position":199.0,"HyperDash":false},{"StartTime":104100.0,"Position":494.0,"HyperDash":false},{"StartTime":104156.0,"Position":293.0,"HyperDash":false},{"StartTime":104213.0,"Position":115.0,"HyperDash":false},{"StartTime":104270.0,"Position":412.0,"HyperDash":false},{"StartTime":104327.0,"Position":506.0,"HyperDash":false},{"StartTime":104384.0,"Position":293.0,"HyperDash":false},{"StartTime":104440.0,"Position":346.0,"HyperDash":false},{"StartTime":104497.0,"Position":117.0,"HyperDash":false},{"StartTime":104554.0,"Position":285.0,"HyperDash":false},{"StartTime":104611.0,"Position":17.0,"HyperDash":false},{"StartTime":104668.0,"Position":238.0,"HyperDash":false},{"StartTime":104725.0,"Position":222.0,"HyperDash":false},{"StartTime":104781.0,"Position":450.0,"HyperDash":false},{"StartTime":104838.0,"Position":67.0,"HyperDash":false},{"StartTime":104895.0,"Position":219.0,"HyperDash":false},{"StartTime":104952.0,"Position":307.0,"HyperDash":false},{"StartTime":105009.0,"Position":367.0,"HyperDash":false},{"StartTime":105065.0,"Position":412.0,"HyperDash":false},{"StartTime":105122.0,"Position":413.0,"HyperDash":false},{"StartTime":105179.0,"Position":143.0,"HyperDash":false},{"StartTime":105236.0,"Position":339.0,"HyperDash":false},{"StartTime":105293.0,"Position":342.0,"HyperDash":false},{"StartTime":105349.0,"Position":249.0,"HyperDash":false},{"StartTime":105406.0,"Position":235.0,"HyperDash":false},{"StartTime":105463.0,"Position":323.0,"HyperDash":false},{"StartTime":105520.0,"Position":365.0,"HyperDash":false},{"StartTime":105577.0,"Position":74.0,"HyperDash":false},{"StartTime":105634.0,"Position":281.0,"HyperDash":false},{"StartTime":105690.0,"Position":398.0,"HyperDash":false},{"StartTime":105747.0,"Position":335.0,"HyperDash":false},{"StartTime":105804.0,"Position":388.0,"HyperDash":false},{"StartTime":105861.0,"Position":228.0,"HyperDash":false},{"StartTime":105918.0,"Position":323.0,"HyperDash":false},{"StartTime":105974.0,"Position":441.0,"HyperDash":false},{"StartTime":106031.0,"Position":442.0,"HyperDash":false},{"StartTime":106088.0,"Position":278.0,"HyperDash":false},{"StartTime":106145.0,"Position":90.0,"HyperDash":false},{"StartTime":106202.0,"Position":409.0,"HyperDash":false},{"StartTime":106258.0,"Position":377.0,"HyperDash":false},{"StartTime":106315.0,"Position":457.0,"HyperDash":false},{"StartTime":106372.0,"Position":409.0,"HyperDash":false},{"StartTime":106429.0,"Position":43.0,"HyperDash":false},{"StartTime":106486.0,"Position":162.0,"HyperDash":false},{"StartTime":106543.0,"Position":341.0,"HyperDash":false},{"StartTime":106599.0,"Position":72.0,"HyperDash":false},{"StartTime":106656.0,"Position":135.0,"HyperDash":false},{"StartTime":106713.0,"Position":252.0,"HyperDash":false},{"StartTime":106770.0,"Position":446.0,"HyperDash":false},{"StartTime":106827.0,"Position":284.0,"HyperDash":false},{"StartTime":106883.0,"Position":70.0,"HyperDash":false},{"StartTime":106940.0,"Position":494.0,"HyperDash":false},{"StartTime":106997.0,"Position":463.0,"HyperDash":false},{"StartTime":107054.0,"Position":277.0,"HyperDash":false},{"StartTime":107111.0,"Position":425.0,"HyperDash":false},{"StartTime":107167.0,"Position":281.0,"HyperDash":false},{"StartTime":107224.0,"Position":3.0,"HyperDash":false},{"StartTime":107281.0,"Position":346.0,"HyperDash":false},{"StartTime":107338.0,"Position":350.0,"HyperDash":false},{"StartTime":107395.0,"Position":217.0,"HyperDash":false},{"StartTime":107452.0,"Position":455.0,"HyperDash":false},{"StartTime":107508.0,"Position":229.0,"HyperDash":false},{"StartTime":107565.0,"Position":51.0,"HyperDash":false},{"StartTime":107622.0,"Position":199.0,"HyperDash":false},{"StartTime":107679.0,"Position":208.0,"HyperDash":false},{"StartTime":107736.0,"Position":173.0,"HyperDash":false},{"StartTime":107792.0,"Position":367.0,"HyperDash":false},{"StartTime":107849.0,"Position":193.0,"HyperDash":false},{"StartTime":107906.0,"Position":488.0,"HyperDash":false},{"StartTime":107963.0,"Position":314.0,"HyperDash":false},{"StartTime":108020.0,"Position":135.0,"HyperDash":false},{"StartTime":108076.0,"Position":399.0,"HyperDash":false},{"StartTime":108133.0,"Position":404.0,"HyperDash":false},{"StartTime":108190.0,"Position":152.0,"HyperDash":false},{"StartTime":108247.0,"Position":353.0,"HyperDash":false},{"StartTime":108304.0,"Position":358.0,"HyperDash":false},{"StartTime":108361.0,"Position":447.0,"HyperDash":false},{"StartTime":108417.0,"Position":222.0,"HyperDash":false},{"StartTime":108474.0,"Position":382.0,"HyperDash":false},{"StartTime":108531.0,"Position":433.0,"HyperDash":false},{"StartTime":108588.0,"Position":450.0,"HyperDash":false},{"StartTime":108645.0,"Position":326.0,"HyperDash":false},{"StartTime":108701.0,"Position":414.0,"HyperDash":false},{"StartTime":108758.0,"Position":285.0,"HyperDash":false},{"StartTime":108815.0,"Position":336.0,"HyperDash":false},{"StartTime":108872.0,"Position":509.0,"HyperDash":false},{"StartTime":108929.0,"Position":334.0,"HyperDash":false},{"StartTime":108985.0,"Position":72.0,"HyperDash":false},{"StartTime":109042.0,"Position":425.0,"HyperDash":false},{"StartTime":109099.0,"Position":451.0,"HyperDash":false},{"StartTime":109156.0,"Position":220.0,"HyperDash":false},{"StartTime":109213.0,"Position":25.0,"HyperDash":false},{"StartTime":109270.0,"Position":77.0,"HyperDash":false}]},{"StartTime":111392.0,"Objects":[{"StartTime":111392.0,"Position":48.0,"HyperDash":false},{"StartTime":111449.0,"Position":89.64901,"HyperDash":false},{"StartTime":111543.0,"Position":108.0,"HyperDash":false}]},{"StartTime":111695.0,"Objects":[{"StartTime":111695.0,"Position":156.0,"HyperDash":false}]},{"StartTime":112301.0,"Objects":[{"StartTime":112301.0,"Position":347.0,"HyperDash":false},{"StartTime":112358.0,"Position":344.0,"HyperDash":false},{"StartTime":112452.0,"Position":347.0,"HyperDash":false}]},{"StartTime":112604.0,"Objects":[{"StartTime":112604.0,"Position":347.0,"HyperDash":false},{"StartTime":112661.0,"Position":343.0,"HyperDash":false},{"StartTime":112755.0,"Position":347.0,"HyperDash":false}]},{"StartTime":112907.0,"Objects":[{"StartTime":112907.0,"Position":347.0,"HyperDash":false}]},{"StartTime":113513.0,"Objects":[{"StartTime":113513.0,"Position":155.0,"HyperDash":false}]},{"StartTime":113664.0,"Objects":[{"StartTime":113664.0,"Position":155.0,"HyperDash":false}]},{"StartTime":113816.0,"Objects":[{"StartTime":113816.0,"Position":155.0,"HyperDash":false},{"StartTime":113891.0,"Position":169.702972,"HyperDash":false},{"StartTime":113967.0,"Position":201.801987,"HyperDash":false},{"StartTime":114043.0,"Position":248.900986,"HyperDash":false},{"StartTime":114119.0,"Position":275.0,"HyperDash":false},{"StartTime":114185.0,"Position":240.861389,"HyperDash":false},{"StartTime":114252.0,"Position":220.326736,"HyperDash":false},{"StartTime":114319.0,"Position":184.792084,"HyperDash":false},{"StartTime":114422.0,"Position":155.0,"HyperDash":false}]},{"StartTime":114725.0,"Objects":[{"StartTime":114725.0,"Position":155.0,"HyperDash":false},{"StartTime":114782.0,"Position":174.649,"HyperDash":false},{"StartTime":114876.0,"Position":215.0,"HyperDash":false}]},{"StartTime":115331.0,"Objects":[{"StartTime":115331.0,"Position":359.0,"HyperDash":false}]},{"StartTime":115634.0,"Objects":[{"StartTime":115634.0,"Position":359.0,"HyperDash":false},{"StartTime":115700.0,"Position":376.0,"HyperDash":false},{"StartTime":115767.0,"Position":358.0,"HyperDash":false},{"StartTime":115834.0,"Position":343.0,"HyperDash":false},{"StartTime":115937.0,"Position":359.0,"HyperDash":false}]},{"StartTime":116543.0,"Objects":[{"StartTime":116543.0,"Position":167.0,"HyperDash":false},{"StartTime":116600.0,"Position":186.0,"HyperDash":false},{"StartTime":116694.0,"Position":167.0,"HyperDash":false}]},{"StartTime":116846.0,"Objects":[{"StartTime":116846.0,"Position":167.0,"HyperDash":false}]},{"StartTime":116998.0,"Objects":[{"StartTime":116998.0,"Position":215.0,"HyperDash":false},{"StartTime":117055.0,"Position":232.649,"HyperDash":false},{"StartTime":117149.0,"Position":275.0,"HyperDash":false}]},{"StartTime":117301.0,"Objects":[{"StartTime":117301.0,"Position":323.0,"HyperDash":false}]},{"StartTime":117604.0,"Objects":[{"StartTime":117604.0,"Position":323.0,"HyperDash":false}]},{"StartTime":117907.0,"Objects":[{"StartTime":117907.0,"Position":227.0,"HyperDash":false}]},{"StartTime":118967.0,"Objects":[{"StartTime":118967.0,"Position":40.0,"HyperDash":false}]},{"StartTime":119573.0,"Objects":[{"StartTime":119573.0,"Position":231.0,"HyperDash":false}]},{"StartTime":120180.0,"Objects":[{"StartTime":120180.0,"Position":422.0,"HyperDash":false},{"StartTime":120255.0,"Position":413.0,"HyperDash":false},{"StartTime":120331.0,"Position":402.0,"HyperDash":false},{"StartTime":120407.0,"Position":413.0,"HyperDash":false},{"StartTime":120483.0,"Position":422.0,"HyperDash":false},{"StartTime":120549.0,"Position":440.0,"HyperDash":false},{"StartTime":120616.0,"Position":418.0,"HyperDash":false},{"StartTime":120683.0,"Position":433.0,"HyperDash":false},{"StartTime":120786.0,"Position":422.0,"HyperDash":false}]},{"StartTime":120937.0,"Objects":[{"StartTime":120937.0,"Position":373.0,"HyperDash":false}]},{"StartTime":121089.0,"Objects":[{"StartTime":121089.0,"Position":324.0,"HyperDash":false},{"StartTime":121155.0,"Position":293.8614,"HyperDash":false},{"StartTime":121222.0,"Position":274.326721,"HyperDash":false},{"StartTime":121289.0,"Position":262.792084,"HyperDash":false},{"StartTime":121392.0,"Position":204.0,"HyperDash":false}]},{"StartTime":121695.0,"Objects":[{"StartTime":121695.0,"Position":204.0,"HyperDash":false}]},{"StartTime":122604.0,"Objects":[{"StartTime":122604.0,"Position":40.0,"HyperDash":false}]},{"StartTime":122907.0,"Objects":[{"StartTime":122907.0,"Position":256.0,"HyperDash":false}]},{"StartTime":123210.0,"Objects":[{"StartTime":123210.0,"Position":472.0,"HyperDash":false}]},{"StartTime":123816.0,"Objects":[{"StartTime":123816.0,"Position":472.0,"HyperDash":false},{"StartTime":123891.0,"Position":427.297028,"HyperDash":false},{"StartTime":123967.0,"Position":429.198029,"HyperDash":false},{"StartTime":124043.0,"Position":387.099,"HyperDash":false},{"StartTime":124119.0,"Position":352.0,"HyperDash":false},{"StartTime":124194.0,"Position":317.297028,"HyperDash":false},{"StartTime":124270.0,"Position":277.198029,"HyperDash":false},{"StartTime":124346.0,"Position":258.099,"HyperDash":false},{"StartTime":124422.0,"Position":232.0,"HyperDash":false},{"StartTime":124497.0,"Position":217.297028,"HyperDash":false},{"StartTime":124573.0,"Position":174.198029,"HyperDash":false},{"StartTime":124649.0,"Position":134.09903,"HyperDash":false},{"StartTime":124725.0,"Position":112.0,"HyperDash":false},{"StartTime":124800.0,"Position":74.29706,"HyperDash":false},{"StartTime":124876.0,"Position":66.19803,"HyperDash":false},{"StartTime":124952.0,"Position":49.0,"HyperDash":false},{"StartTime":125028.0,"Position":32.0,"HyperDash":false},{"StartTime":125103.0,"Position":44.0,"HyperDash":false},{"StartTime":125179.0,"Position":49.0,"HyperDash":false},{"StartTime":125255.0,"Position":39.901,"HyperDash":false},{"StartTime":125331.0,"Position":88.0,"HyperDash":false},{"StartTime":125397.0,"Position":106.138611,"HyperDash":false},{"StartTime":125464.0,"Position":129.673279,"HyperDash":false},{"StartTime":125531.0,"Position":176.207947,"HyperDash":false},{"StartTime":125634.0,"Position":208.0,"HyperDash":false}]},{"StartTime":126240.0,"Objects":[{"StartTime":126240.0,"Position":399.0,"HyperDash":false}]},{"StartTime":126846.0,"Objects":[{"StartTime":126846.0,"Position":399.0,"HyperDash":false}]},{"StartTime":127452.0,"Objects":[{"StartTime":127452.0,"Position":315.0,"HyperDash":false},{"StartTime":127508.0,"Position":35.0,"HyperDash":false},{"StartTime":127565.0,"Position":208.0,"HyperDash":false},{"StartTime":127622.0,"Position":504.0,"HyperDash":false},{"StartTime":127679.0,"Position":296.0,"HyperDash":false},{"StartTime":127736.0,"Position":105.0,"HyperDash":false},{"StartTime":127792.0,"Position":488.0,"HyperDash":false},{"StartTime":127849.0,"Position":230.0,"HyperDash":false},{"StartTime":127906.0,"Position":446.0,"HyperDash":false},{"StartTime":127963.0,"Position":241.0,"HyperDash":false},{"StartTime":128020.0,"Position":413.0,"HyperDash":false},{"StartTime":128076.0,"Position":357.0,"HyperDash":false},{"StartTime":128133.0,"Position":256.0,"HyperDash":false},{"StartTime":128190.0,"Position":192.0,"HyperDash":false},{"StartTime":128247.0,"Position":116.0,"HyperDash":false},{"StartTime":128304.0,"Position":397.0,"HyperDash":false},{"StartTime":128361.0,"Position":422.0,"HyperDash":false},{"StartTime":128417.0,"Position":230.0,"HyperDash":false},{"StartTime":128474.0,"Position":479.0,"HyperDash":false},{"StartTime":128531.0,"Position":276.0,"HyperDash":false},{"StartTime":128588.0,"Position":423.0,"HyperDash":false},{"StartTime":128645.0,"Position":450.0,"HyperDash":false},{"StartTime":128701.0,"Position":336.0,"HyperDash":false},{"StartTime":128758.0,"Position":145.0,"HyperDash":false},{"StartTime":128815.0,"Position":30.0,"HyperDash":false},{"StartTime":128872.0,"Position":426.0,"HyperDash":false},{"StartTime":128929.0,"Position":394.0,"HyperDash":false},{"StartTime":128985.0,"Position":274.0,"HyperDash":false},{"StartTime":129042.0,"Position":44.0,"HyperDash":false},{"StartTime":129099.0,"Position":32.0,"HyperDash":false},{"StartTime":129156.0,"Position":10.0,"HyperDash":false},{"StartTime":129213.0,"Position":505.0,"HyperDash":false},{"StartTime":129270.0,"Position":321.0,"HyperDash":false}]},{"StartTime":129876.0,"Objects":[{"StartTime":129876.0,"Position":48.0,"HyperDash":false}]},{"StartTime":130483.0,"Objects":[{"StartTime":130483.0,"Position":144.0,"HyperDash":false}]},{"StartTime":131089.0,"Objects":[{"StartTime":131089.0,"Position":240.0,"HyperDash":false},{"StartTime":131164.0,"Position":239.851486,"HyperDash":false},{"StartTime":131240.0,"Position":286.901,"HyperDash":false},{"StartTime":131316.0,"Position":289.9505,"HyperDash":false},{"StartTime":131392.0,"Position":282.0,"HyperDash":false},{"StartTime":131467.0,"Position":312.8515,"HyperDash":false},{"StartTime":131543.0,"Position":328.901,"HyperDash":false},{"StartTime":131619.0,"Position":352.9505,"HyperDash":false},{"StartTime":131695.0,"Position":360.0,"HyperDash":false},{"StartTime":131770.0,"Position":349.0,"HyperDash":false},{"StartTime":131846.0,"Position":359.0,"HyperDash":false},{"StartTime":131922.0,"Position":362.0,"HyperDash":false},{"StartTime":131998.0,"Position":352.0,"HyperDash":false},{"StartTime":132073.0,"Position":356.0,"HyperDash":false},{"StartTime":132149.0,"Position":371.0,"HyperDash":false},{"StartTime":132225.0,"Position":346.0,"HyperDash":false},{"StartTime":132301.0,"Position":360.0,"HyperDash":false},{"StartTime":132372.0,"Position":328.9406,"HyperDash":false},{"StartTime":132443.0,"Position":335.8812,"HyperDash":false},{"StartTime":132514.0,"Position":328.821777,"HyperDash":false},{"StartTime":132586.0,"Position":302.564362,"HyperDash":false},{"StartTime":132657.0,"Position":285.504944,"HyperDash":false},{"StartTime":132728.0,"Position":274.445557,"HyperDash":false},{"StartTime":132799.0,"Position":246.386139,"HyperDash":false},{"StartTime":132907.0,"Position":240.0,"HyperDash":false}]},{"StartTime":133513.0,"Objects":[{"StartTime":133513.0,"Position":144.0,"HyperDash":false}]},{"StartTime":134119.0,"Objects":[{"StartTime":134119.0,"Position":144.0,"HyperDash":false}]},{"StartTime":134725.0,"Objects":[{"StartTime":134725.0,"Position":423.0,"HyperDash":false},{"StartTime":134781.0,"Position":367.0,"HyperDash":false},{"StartTime":134838.0,"Position":146.0,"HyperDash":false},{"StartTime":134895.0,"Position":322.0,"HyperDash":false},{"StartTime":134952.0,"Position":169.0,"HyperDash":false},{"StartTime":135009.0,"Position":159.0,"HyperDash":false},{"StartTime":135065.0,"Position":388.0,"HyperDash":false},{"StartTime":135122.0,"Position":67.0,"HyperDash":false},{"StartTime":135179.0,"Position":176.0,"HyperDash":false},{"StartTime":135236.0,"Position":371.0,"HyperDash":false},{"StartTime":135293.0,"Position":365.0,"HyperDash":false},{"StartTime":135349.0,"Position":104.0,"HyperDash":false},{"StartTime":135406.0,"Position":363.0,"HyperDash":false},{"StartTime":135463.0,"Position":75.0,"HyperDash":false},{"StartTime":135520.0,"Position":158.0,"HyperDash":false},{"StartTime":135577.0,"Position":98.0,"HyperDash":false},{"StartTime":135634.0,"Position":30.0,"HyperDash":false},{"StartTime":135690.0,"Position":164.0,"HyperDash":false},{"StartTime":135747.0,"Position":341.0,"HyperDash":false},{"StartTime":135804.0,"Position":18.0,"HyperDash":false},{"StartTime":135861.0,"Position":210.0,"HyperDash":false},{"StartTime":135918.0,"Position":420.0,"HyperDash":false},{"StartTime":135974.0,"Position":447.0,"HyperDash":false},{"StartTime":136031.0,"Position":78.0,"HyperDash":false},{"StartTime":136088.0,"Position":177.0,"HyperDash":false},{"StartTime":136145.0,"Position":305.0,"HyperDash":false},{"StartTime":136202.0,"Position":400.0,"HyperDash":false},{"StartTime":136258.0,"Position":462.0,"HyperDash":false},{"StartTime":136315.0,"Position":64.0,"HyperDash":false},{"StartTime":136372.0,"Position":458.0,"HyperDash":false},{"StartTime":136429.0,"Position":380.0,"HyperDash":false},{"StartTime":136486.0,"Position":65.0,"HyperDash":false},{"StartTime":136543.0,"Position":23.0,"HyperDash":false},{"StartTime":136599.0,"Position":379.0,"HyperDash":false},{"StartTime":136656.0,"Position":44.0,"HyperDash":false},{"StartTime":136713.0,"Position":485.0,"HyperDash":false},{"StartTime":136770.0,"Position":269.0,"HyperDash":false},{"StartTime":136827.0,"Position":155.0,"HyperDash":false},{"StartTime":136883.0,"Position":324.0,"HyperDash":false},{"StartTime":136940.0,"Position":149.0,"HyperDash":false},{"StartTime":136997.0,"Position":351.0,"HyperDash":false},{"StartTime":137054.0,"Position":385.0,"HyperDash":false},{"StartTime":137111.0,"Position":338.0,"HyperDash":false},{"StartTime":137167.0,"Position":322.0,"HyperDash":false},{"StartTime":137224.0,"Position":84.0,"HyperDash":false},{"StartTime":137281.0,"Position":342.0,"HyperDash":false},{"StartTime":137338.0,"Position":395.0,"HyperDash":false},{"StartTime":137395.0,"Position":72.0,"HyperDash":false},{"StartTime":137452.0,"Position":324.0,"HyperDash":false},{"StartTime":137508.0,"Position":67.0,"HyperDash":false},{"StartTime":137565.0,"Position":371.0,"HyperDash":false},{"StartTime":137622.0,"Position":446.0,"HyperDash":false},{"StartTime":137679.0,"Position":29.0,"HyperDash":false},{"StartTime":137736.0,"Position":22.0,"HyperDash":false},{"StartTime":137792.0,"Position":432.0,"HyperDash":false},{"StartTime":137849.0,"Position":12.0,"HyperDash":false},{"StartTime":137906.0,"Position":330.0,"HyperDash":false},{"StartTime":137963.0,"Position":419.0,"HyperDash":false},{"StartTime":138020.0,"Position":278.0,"HyperDash":false},{"StartTime":138076.0,"Position":202.0,"HyperDash":false},{"StartTime":138133.0,"Position":208.0,"HyperDash":false},{"StartTime":138190.0,"Position":21.0,"HyperDash":false},{"StartTime":138247.0,"Position":437.0,"HyperDash":false},{"StartTime":138304.0,"Position":312.0,"HyperDash":false},{"StartTime":138361.0,"Position":508.0,"HyperDash":false}]}]} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/50859.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/50859.osu new file mode 100644 index 0000000000..8272b8b1db --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/50859.osu @@ -0,0 +1,290 @@ +osu file format v7 + +[General] +StackLeniency: 0.5 +Mode: 0 + +[Difficulty] +HPDrainRate:6 +CircleSize:4 +OverallDifficulty:7 +SliderMultiplier:2.4 +SliderTickRate:2 + +[Events] +//Break Periods +2,109470,110492 +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Sound Samples +//Background Colour Transformations +3,100,163,162,255 + +[TimingPoints] +180,606.060606060606,3,2,1,20,1,0 +11528,-100,4,2,1,50,0,0 +28952,-100,4,2,1,20,0,0 +36452,-100,4,2,1,50,0,0 +43523,-50,4,2,2,20,0,0 +50921,-100,4,2,1,40,0,0 +51073,-100,4,2,1,60,0,0 +53371,-100,4,2,2,60,0,0 +54280,-100,4,2,1,60,0,0 +58195,-100,4,2,1,40,0,0 +65468,-100,4,2,1,60,0,0 +68801,-100,4,1,0,30,0,0 +69129,-100,4,1,1,30,0,0 +69407,-100,4,2,1,60,0,0 +75644,-100,4,1,0,35,0,0 +76680,-100,4,2,1,60,0,0 +78195,-100,4,2,1,40,0,0 +78649,-100,4,2,1,60,0,0 +87386,-100,4,1,2,30,0,0 +101856,-100,4,1,1,40,0,0 +109583,-100,4,2,2,60,0,0 +112008,-100,4,1,2,20,0,0 +113068,-100,4,2,2,60,0,0 +114583,-100,4,2,2,40,0,0 +115038,-100,4,2,2,60,0,0 +123523,-100,4,1,2,30,0,0 +124280,-100,4,1,0,30,0,0 +124583,-100,4,1,2,30,0,0 +124886,-100,4,1,0,30,0,0 +125189,-100,4,1,2,30,0,0 +125644,-100,4,1,1,30,0,0 +125947,-100,4,1,2,20,0,0 +127159,-100,4,1,1,60,0,0 +129583,-200,4,1,0,20,0,0 +134583,-200,4,1,1,0,0,0 + +[HitObjects] +120,72,179,1,0 +311,72,786,2,0,B|448:72,1,120,2|0 +431,167,1392,2,0,B|303:167,1,120,2|2 +215,167,1998,1,0 +119,167,2301,6,0,B|487:167,1,360,2|2 +478,261,3513,1,0 +382,261,3816,6,0,B|254:261,1,120 +166,261,4422,2,0,B|166:138,1,120,2|2 +166,332,5331,1,0 +261,332,5634,2,0,L|327:332,1,60,0|2 +321,235,6089,2,0,L|321:175,1,60,0|2 +321,79,6543,1,0 +465,79,6998,2,0,L|465:143,1,60,0|2 +369,139,7452,6,0,B|369:278,1,120 +464,259,8058,1,2 +464,163,8361,2,0,B|288:163,1,120,0|2 +248,163,8967,1,0 +200,243,9270,1,0 +296,243,9573,1,2 +275,37,10180,5,0 +179,37,10483,1,2 +179,132,10786,2,0,B|307:132,1,120,2|0 +299,227,11392,1,0 +203,227,11695,6,0,L|142:227,1,60 +94,227,11998,1,2 +94,131,12301,1,2 +189,131,12604,1,0 +476,131,13513,1,0 +380,131,13816,1,2 +272,23,14725,6,0,L|208:23,1,60,2|0 +177,57,15028,2,0,L|177:129,1,60 +225,117,15331,1,0 +273,117,15483,1,2 +273,211,15786,1,2 +273,306,16089,1,2 +33,306,16846,6,0,L|33:242,1,60 +33,197,17149,1,2 +224,197,17755,1,0 +277,50,18967,5,0 +228,50,19119,1,0 +181,50,19270,1,2 +181,145,19573,1,2 +181,240,19876,1,0 +469,240,20786,5,0 +373,240,21089,1,2 +277,240,21392,1,0 +243,350,21998,5,2 +243,302,22149,1,0 +243,254,22301,1,2 +290,254,22452,2,0,L|290:193,1,60 +290,146,22755,1,2 +385,146,23058,1,2 +385,241,23361,1,2 +213,68,24119,6,0,L|149:68,1,60,0|0 +104,68,24422,1,2 +295,68,25028,1,0 +56,64,26240,5,0 +56,64,26392,1,0 +56,64,26543,1,2 +56,159,26846,1,2 +151,159,27149,1,0 +438,159,28058,6,0,B|438:303,1,120,0|2 +184,192,29270,6,0,B|312:192,1,120,6|0 +399,192,29876,1,2 +399,95,30180,1,0 +303,95,30483,2,0,B|129:95,1,120,2|0 +115,162,31089,6,0,B|243:162,1,120,2|0 +330,162,31695,1,2 +425,162,31998,1,0 +425,257,32301,2,0,B|265:257,1,120,2|0 +209,257,32907,6,0,B|65:257,1,120,6|0 +89,160,33513,1,2 +184,160,33816,1,0 +279,160,34119,1,2 +374,160,34422,1,0 +469,160,34725,6,0,B|469:304,1,120,2|0 +373,280,35331,2,0,B|216:280,1,120,2|0 +157,280,35937,1,2 +157,184,36240,1,0 +157,135,36392,1,0 +204,135,36543,6,0,B|268:135,2,60,2|0|2 +204,183,36998,2,0,B|204:255,1,60 +205,291,37301,1,2 +300,291,37604,1,2 +300,195,37907,1,2 +32,32,38967,5,2 +32,223,39573,1,0 +416,223,40786,5,0 +416,176,40937,1,0 +416,128,41089,1,2 +320,128,41392,1,2 +320,224,41695,1,0 +48,128,42604,6,0,B|192:128,1,120,0|2 +263,128,43210,1,0 +376,192,43816,6,0,B|136:192,5,240,6|0|2|0|2|0 +376,248,45634,6,0,B|136:248,5,240,2|0|2|0|2|0 +376,184,47452,6,0,B|136:184,5,240,6|0|2|0|2|0 +376,248,49270,6,0,B|109:247,1,240,2|0 +136,136,49876,1,2 +328,136,50180,1,0 +329,326,50483,1,2 +136,328,50786,1,0 +138,278,50937,1,0 +138,229,51089,6,0,B|255:229,1,60,6|0 +198,180,51392,1,2 +246,180,51543,1,0 +295,180,51695,2,0,B|365:180,1,60,0|2 +355,84,52149,1,2 +260,84,52452,1,2 +40,344,53513,6,0,L|280:344,1,240,2|0 +40,40,55331,6,0,L|120:40,2,60,0|0|2 +40,135,55937,2,0,L|40:262,1,120,2|0 +300,132,57149,6,0,L|300:272,1,120,0|2 +256,192,58361,6,0,L|336:192,1,60,2|0 +364,192,58664,2,0,L|364:256,1,60,2|0 +329,286,58967,1,0 +280,286,59119,2,0,L|208:286,1,60,2|0 +185,251,59422,2,0,L|185:179,1,60,2|0 +185,95,59876,1,4 +253,163,60180,5,2 +253,163,60331,1,2 +253,163,60483,1,2 +253,163,60634,1,0 +253,211,60786,2,0,L|192:211,4,60,2|0|2|0|2 +253,115,61695,1,4 +348,115,61998,6,0,L|348:51,4,60,2|0|2|0|2 +348,162,62755,1,2 +348,210,62907,1,2 +348,257,63058,1,0 +348,257,63210,1,2 +252,257,63513,1,4 +252,161,63816,5,0 +252,113,63967,2,0,L|169:113,3,60,2|0|2|0 +288,113,64725,1,4 +383,113,65028,1,4 +383,208,65331,1,0 +287,208,65634,6,0,L|195:208,1,60,2|0 +178,208,65937,1,2 +129,208,66089,1,0 +81,208,66240,1,0 +81,256,66392,1,2 +81,303,66543,2,0,L|145:303,1,60,0|2 +189,303,66846,2,0,L|253:303,1,60,0|2 +192,48,67755,6,0,L|304:48,1,60 +300,48,68058,1,2 +300,239,68664,1,0 +300,143,68967,5,0 +204,143,69270,1,4 +395,143,69876,6,0,L|396:226,1,60,0|0 +395,251,70180,1,2 +296,248,70483,1,2 +200,248,70786,1,0 +200,40,71695,1,0 +295,40,71998,1,2 +91,243,72907,5,2 +138,243,73058,1,0 +186,243,73210,1,0 +186,290,73361,2,0,L|254:290,1,60,2|0 +294,290,73664,2,0,L|371:290,1,60,2|0 +354,241,73967,1,2 +354,145,74270,1,2 +40,40,75331,5,2 +160,208,75937,1,0 +160,208,76089,1,0 +303,208,76543,1,4 +160,80,77149,6,0,L|232:80,1,60,0|0 +268,80,77452,1,2 +268,175,77755,1,2 +268,270,78058,1,0 +363,270,78361,6,4,L|363:187,1,60 +363,65,78967,5,0 +267,65,79270,2,0,L|126:65,1,120,2|0 +96,32,80180,6,0,L|96:344|296:344,1,480 +360,344,81695,1,0 +455,344,81998,2,0,L|455:32|159:32,1,600,2|0 +124,99,83816,6,0,L|364:99|364:347,1,480 +268,339,85331,1,4 +172,339,85634,2,0,L|52:339|52:235|52:123|156:123|316:123|316:219,1,660 +316,231,87452,6,0,L|316:354,1,120 +411,351,88058,2,0,L|411:103|147:103,1,480,4|0 +176,296,89876,2,0,L|40:296|40:152,1,240 +232,191,91089,5,4 +423,191,91695,2,0,L|423:351|71:351,1,480,4|4 +112,167,93513,1,0 +303,167,94119,1,0 +440,280,94725,6,0,L|440:35,2,240,4|4|0 +216,280,96543,2,0,L|216:32,1,240,4|0 +48,160,97755,1,0 +216,40,98361,5,4 +216,352,98967,2,0,L|216:104,2,240,4|0|4 +216,32,100786,1,4 +216,352,101392,1,0 +256,192,101998,12,0,109270 +48,48,111392,6,0,L|128:48,1,60 +156,48,111695,1,2 +347,48,112301,2,0,L|347:112,1,60 +347,156,112604,2,0,L|347:220,1,60 +347,264,112907,1,4 +155,264,113513,5,0 +155,216,113664,1,0 +155,167,113816,2,0,L|275:167,2,120,2|2|0 +155,71,114725,6,4,L|217:71,1,60 +359,71,115331,5,0 +359,166,115634,2,0,L|359:296,1,120,2|0 +167,286,116543,6,0,L|167:205,1,60,2|0 +167,177,116846,1,0 +215,177,116998,2,0,L|281:177,1,60,2|0 +323,177,117301,5,2 +323,81,117604,1,2 +227,81,117907,1,2 +40,344,118967,5,2 +231,344,119573,1,0 +422,344,120180,6,0,L|422:50,1,240 +373,104,120937,1,0 +324,104,121089,2,0,L|204:104,1,120,2|2 +204,199,121695,1,0 +40,40,122604,5,0 +256,40,122907,1,2 +472,40,123210,1,0 +472,232,123816,6,2,L|32:232|32:336|240:336,1,720,2|2 +399,336,126240,1,8 +399,144,126846,1,8 +256,192,127452,12,0,129270 +48,192,129876,5,8 +144,192,130483,1,8 +240,192,131089,2,2,L|360:192|360:72|240:72,1,360,2|2 +144,72,133513,1,8 +144,167,134119,1,8 +256,192,134725,12,0,138361 diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/75858-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/75858-expected-conversion.json new file mode 100644 index 0000000000..d5db48bc8c --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/75858-expected-conversion.json @@ -0,0 +1 @@ +{"Mappings":[{"StartTime":1173.0,"Objects":[{"StartTime":1173.0,"Position":94.0,"HyperDash":false},{"StartTime":1251.0,"Position":94.284874,"HyperDash":false},{"StartTime":1330.0,"Position":115.604385,"HyperDash":false},{"StartTime":1409.0,"Position":141.8706,"HyperDash":false},{"StartTime":1488.0,"Position":178.597519,"HyperDash":false},{"StartTime":1566.0,"Position":199.289474,"HyperDash":false},{"StartTime":1645.0,"Position":202.258377,"HyperDash":false},{"StartTime":1724.0,"Position":219.14473,"HyperDash":false},{"StartTime":1839.0,"Position":247.271439,"HyperDash":false}]},{"StartTime":2506.0,"Objects":[{"StartTime":2506.0,"Position":398.0,"HyperDash":false}]},{"StartTime":3172.0,"Objects":[{"StartTime":3172.0,"Position":471.0,"HyperDash":false}]},{"StartTime":3839.0,"Objects":[{"StartTime":3839.0,"Position":320.0,"HyperDash":false},{"StartTime":3917.0,"Position":287.205841,"HyperDash":false},{"StartTime":3996.0,"Position":275.6191,"HyperDash":false},{"StartTime":4075.0,"Position":252.736725,"HyperDash":false},{"StartTime":4154.0,"Position":241.768585,"HyperDash":false},{"StartTime":4232.0,"Position":241.149734,"HyperDash":false},{"StartTime":4311.0,"Position":212.634354,"HyperDash":false},{"StartTime":4390.0,"Position":181.770065,"HyperDash":false},{"StartTime":4505.0,"Position":166.756821,"HyperDash":false}]},{"StartTime":5173.0,"Objects":[{"StartTime":5173.0,"Position":65.0,"HyperDash":false}]},{"StartTime":5839.0,"Objects":[{"StartTime":5839.0,"Position":233.0,"HyperDash":false}]},{"StartTime":6506.0,"Objects":[{"StartTime":6506.0,"Position":239.0,"HyperDash":false},{"StartTime":6584.0,"Position":262.183746,"HyperDash":false},{"StartTime":6663.0,"Position":257.084351,"HyperDash":false},{"StartTime":6742.0,"Position":286.044983,"HyperDash":false},{"StartTime":6821.0,"Position":331.921021,"HyperDash":false},{"StartTime":6899.0,"Position":349.51355,"HyperDash":false},{"StartTime":6978.0,"Position":364.384766,"HyperDash":false},{"StartTime":7057.0,"Position":362.333984,"HyperDash":false},{"StartTime":7172.0,"Position":397.8736,"HyperDash":false}]},{"StartTime":7839.0,"Objects":[{"StartTime":7839.0,"Position":493.0,"HyperDash":false}]},{"StartTime":8506.0,"Objects":[{"StartTime":8506.0,"Position":175.0,"HyperDash":false}]},{"StartTime":9173.0,"Objects":[{"StartTime":9173.0,"Position":223.0,"HyperDash":false}]},{"StartTime":9839.0,"Objects":[{"StartTime":9839.0,"Position":119.0,"HyperDash":false}]},{"StartTime":10506.0,"Objects":[{"StartTime":10506.0,"Position":423.0,"HyperDash":false}]},{"StartTime":10839.0,"Objects":[{"StartTime":10839.0,"Position":199.0,"HyperDash":false},{"StartTime":10917.0,"Position":173.819885,"HyperDash":false},{"StartTime":10996.0,"Position":195.248886,"HyperDash":false},{"StartTime":11075.0,"Position":168.416779,"HyperDash":false},{"StartTime":11154.0,"Position":168.359177,"HyperDash":false},{"StartTime":11232.0,"Position":140.23172,"HyperDash":false},{"StartTime":11311.0,"Position":141.862854,"HyperDash":false},{"StartTime":11390.0,"Position":135.414291,"HyperDash":false},{"StartTime":11505.0,"Position":122.606651,"HyperDash":false}]},{"StartTime":11839.0,"Objects":[{"StartTime":11839.0,"Position":42.0,"HyperDash":false}]},{"StartTime":12506.0,"Objects":[{"StartTime":12506.0,"Position":178.0,"HyperDash":false}]},{"StartTime":13172.0,"Objects":[{"StartTime":13172.0,"Position":263.0,"HyperDash":false},{"StartTime":13250.0,"Position":294.723358,"HyperDash":false},{"StartTime":13329.0,"Position":284.270325,"HyperDash":false},{"StartTime":13408.0,"Position":299.0951,"HyperDash":false},{"StartTime":13487.0,"Position":321.0376,"HyperDash":false},{"StartTime":13565.0,"Position":368.773682,"HyperDash":false},{"StartTime":13644.0,"Position":391.718231,"HyperDash":false},{"StartTime":13723.0,"Position":392.566528,"HyperDash":false},{"StartTime":13838.0,"Position":420.607758,"HyperDash":false}]},{"StartTime":14506.0,"Objects":[{"StartTime":14506.0,"Position":293.0,"HyperDash":false},{"StartTime":14584.0,"Position":277.467133,"HyperDash":false},{"StartTime":14663.0,"Position":271.341,"HyperDash":false},{"StartTime":14742.0,"Position":276.740753,"HyperDash":false},{"StartTime":14821.0,"Position":235.4871,"HyperDash":false},{"StartTime":14899.0,"Position":229.366821,"HyperDash":false},{"StartTime":14978.0,"Position":219.329987,"HyperDash":false},{"StartTime":15057.0,"Position":204.814072,"HyperDash":false},{"StartTime":15172.0,"Position":160.38443,"HyperDash":false}]},{"StartTime":15839.0,"Objects":[{"StartTime":15839.0,"Position":282.0,"HyperDash":false},{"StartTime":15917.0,"Position":307.532867,"HyperDash":false},{"StartTime":15996.0,"Position":317.659,"HyperDash":false},{"StartTime":16075.0,"Position":334.259247,"HyperDash":false},{"StartTime":16154.0,"Position":342.512878,"HyperDash":false},{"StartTime":16232.0,"Position":333.633179,"HyperDash":false},{"StartTime":16311.0,"Position":373.67,"HyperDash":false},{"StartTime":16390.0,"Position":375.1859,"HyperDash":false},{"StartTime":16505.0,"Position":414.61557,"HyperDash":false}]},{"StartTime":17172.0,"Objects":[{"StartTime":17172.0,"Position":416.0,"HyperDash":false}]},{"StartTime":17839.0,"Objects":[{"StartTime":17839.0,"Position":256.0,"HyperDash":false},{"StartTime":17920.0,"Position":221.750565,"HyperDash":false},{"StartTime":18001.0,"Position":235.501129,"HyperDash":false},{"StartTime":18082.0,"Position":190.251709,"HyperDash":false},{"StartTime":18163.0,"Position":195.002274,"HyperDash":false},{"StartTime":18244.0,"Position":183.643,"HyperDash":false},{"StartTime":18325.0,"Position":181.956619,"HyperDash":false},{"StartTime":18406.0,"Position":200.650589,"HyperDash":false},{"StartTime":18487.0,"Position":193.194916,"HyperDash":false},{"StartTime":18568.0,"Position":202.48082,"HyperDash":false},{"StartTime":18649.0,"Position":179.827667,"HyperDash":false},{"StartTime":18730.0,"Position":173.703339,"HyperDash":false},{"StartTime":18811.0,"Position":186.446991,"HyperDash":false},{"StartTime":18892.0,"Position":151.2917,"HyperDash":false},{"StartTime":18973.0,"Position":126.879227,"HyperDash":false},{"StartTime":19054.0,"Position":122.770569,"HyperDash":false},{"StartTime":19172.0,"Position":99.93324,"HyperDash":false}]},{"StartTime":19839.0,"Objects":[{"StartTime":19839.0,"Position":256.0,"HyperDash":false}]},{"StartTime":20173.0,"Objects":[{"StartTime":20173.0,"Position":123.0,"HyperDash":false},{"StartTime":20245.0,"Position":42.0,"HyperDash":false},{"StartTime":20318.0,"Position":393.0,"HyperDash":false},{"StartTime":20391.0,"Position":75.0,"HyperDash":false},{"StartTime":20464.0,"Position":377.0,"HyperDash":false},{"StartTime":20537.0,"Position":354.0,"HyperDash":false},{"StartTime":20610.0,"Position":287.0,"HyperDash":false},{"StartTime":20683.0,"Position":361.0,"HyperDash":false},{"StartTime":20756.0,"Position":479.0,"HyperDash":false},{"StartTime":20829.0,"Position":346.0,"HyperDash":false},{"StartTime":20902.0,"Position":266.0,"HyperDash":false},{"StartTime":20974.0,"Position":400.0,"HyperDash":false},{"StartTime":21047.0,"Position":202.0,"HyperDash":false},{"StartTime":21120.0,"Position":500.0,"HyperDash":false},{"StartTime":21193.0,"Position":80.0,"HyperDash":false},{"StartTime":21266.0,"Position":399.0,"HyperDash":false},{"StartTime":21339.0,"Position":455.0,"HyperDash":false},{"StartTime":21412.0,"Position":105.0,"HyperDash":false},{"StartTime":21485.0,"Position":100.0,"HyperDash":false},{"StartTime":21558.0,"Position":195.0,"HyperDash":false},{"StartTime":21631.0,"Position":106.0,"HyperDash":false},{"StartTime":21704.0,"Position":305.0,"HyperDash":false},{"StartTime":21776.0,"Position":225.0,"HyperDash":false},{"StartTime":21849.0,"Position":79.0,"HyperDash":false},{"StartTime":21922.0,"Position":38.0,"HyperDash":false},{"StartTime":21995.0,"Position":99.0,"HyperDash":false},{"StartTime":22068.0,"Position":79.0,"HyperDash":false},{"StartTime":22141.0,"Position":169.0,"HyperDash":false},{"StartTime":22214.0,"Position":238.0,"HyperDash":false},{"StartTime":22287.0,"Position":511.0,"HyperDash":false},{"StartTime":22360.0,"Position":58.0,"HyperDash":false},{"StartTime":22433.0,"Position":368.0,"HyperDash":false},{"StartTime":22506.0,"Position":52.0,"HyperDash":false}]},{"StartTime":22961.0,"Objects":[{"StartTime":22961.0,"Position":256.0,"HyperDash":false}]},{"StartTime":23415.0,"Objects":[{"StartTime":23415.0,"Position":236.0,"HyperDash":false}]},{"StartTime":23870.0,"Objects":[{"StartTime":23870.0,"Position":104.0,"HyperDash":false},{"StartTime":23926.0,"Position":115.299927,"HyperDash":false},{"StartTime":23983.0,"Position":150.004791,"HyperDash":false},{"StartTime":24040.0,"Position":149.768875,"HyperDash":false},{"StartTime":24097.0,"Position":172.51532,"HyperDash":false},{"StartTime":24192.0,"Position":154.09137,"HyperDash":false},{"StartTime":24324.0,"Position":104.0,"HyperDash":false}]},{"StartTime":24779.0,"Objects":[{"StartTime":24779.0,"Position":256.0,"HyperDash":false},{"StartTime":24835.0,"Position":262.6648,"HyperDash":false},{"StartTime":24892.0,"Position":294.163452,"HyperDash":false},{"StartTime":24949.0,"Position":301.429565,"HyperDash":false},{"StartTime":25006.0,"Position":329.7457,"HyperDash":false},{"StartTime":25101.0,"Position":304.427277,"HyperDash":false},{"StartTime":25233.0,"Position":256.0,"HyperDash":false}]},{"StartTime":25688.0,"Objects":[{"StartTime":25688.0,"Position":118.0,"HyperDash":false},{"StartTime":25783.0,"Position":160.2344,"HyperDash":false},{"StartTime":25915.0,"Position":196.5579,"HyperDash":false}]},{"StartTime":26142.0,"Objects":[{"StartTime":26142.0,"Position":321.0,"HyperDash":false}]},{"StartTime":26597.0,"Objects":[{"StartTime":26597.0,"Position":419.0,"HyperDash":false},{"StartTime":26692.0,"Position":383.782776,"HyperDash":false},{"StartTime":26824.0,"Position":341.8768,"HyperDash":false}]},{"StartTime":27052.0,"Objects":[{"StartTime":27052.0,"Position":185.0,"HyperDash":false}]},{"StartTime":27506.0,"Objects":[{"StartTime":27506.0,"Position":71.0,"HyperDash":false}]},{"StartTime":27733.0,"Objects":[{"StartTime":27733.0,"Position":97.0,"HyperDash":false},{"StartTime":27828.0,"Position":69.73373,"HyperDash":false},{"StartTime":27960.0,"Position":95.43024,"HyperDash":false}]},{"StartTime":28415.0,"Objects":[{"StartTime":28415.0,"Position":376.0,"HyperDash":false}]},{"StartTime":28642.0,"Objects":[{"StartTime":28642.0,"Position":313.0,"HyperDash":false},{"StartTime":28737.0,"Position":349.615631,"HyperDash":false},{"StartTime":28869.0,"Position":392.036163,"HyperDash":false}]},{"StartTime":29324.0,"Objects":[{"StartTime":29324.0,"Position":501.0,"HyperDash":false}]},{"StartTime":29552.0,"Objects":[{"StartTime":29552.0,"Position":411.0,"HyperDash":false}]},{"StartTime":29779.0,"Objects":[{"StartTime":29779.0,"Position":501.0,"HyperDash":false}]},{"StartTime":30233.0,"Objects":[{"StartTime":30233.0,"Position":311.0,"HyperDash":false}]},{"StartTime":30461.0,"Objects":[{"StartTime":30461.0,"Position":231.0,"HyperDash":false}]},{"StartTime":30688.0,"Objects":[{"StartTime":30688.0,"Position":151.0,"HyperDash":false},{"StartTime":30744.0,"Position":136.485382,"HyperDash":false},{"StartTime":30801.0,"Position":111.448036,"HyperDash":false},{"StartTime":30915.0,"Position":151.0,"HyperDash":false}]},{"StartTime":31142.0,"Objects":[{"StartTime":31142.0,"Position":364.0,"HyperDash":false}]},{"StartTime":31370.0,"Objects":[{"StartTime":31370.0,"Position":202.0,"HyperDash":false}]},{"StartTime":31597.0,"Objects":[{"StartTime":31597.0,"Position":194.0,"HyperDash":false},{"StartTime":31649.0,"Position":193.329712,"HyperDash":false},{"StartTime":31701.0,"Position":177.29129,"HyperDash":false},{"StartTime":31753.0,"Position":180.897339,"HyperDash":false},{"StartTime":31806.0,"Position":196.245209,"HyperDash":false},{"StartTime":31858.0,"Position":225.942978,"HyperDash":false},{"StartTime":31910.0,"Position":221.896729,"HyperDash":false},{"StartTime":31962.0,"Position":258.838379,"HyperDash":false},{"StartTime":32051.0,"Position":270.298431,"HyperDash":false}]},{"StartTime":32279.0,"Objects":[{"StartTime":32279.0,"Position":316.0,"HyperDash":false}]},{"StartTime":32506.0,"Objects":[{"StartTime":32506.0,"Position":273.0,"HyperDash":false},{"StartTime":32558.0,"Position":268.772461,"HyperDash":false},{"StartTime":32610.0,"Position":229.84964,"HyperDash":false},{"StartTime":32662.0,"Position":200.402817,"HyperDash":false},{"StartTime":32715.0,"Position":184.266861,"HyperDash":false},{"StartTime":32767.0,"Position":200.3539,"HyperDash":false},{"StartTime":32819.0,"Position":172.707291,"HyperDash":false},{"StartTime":32871.0,"Position":162.9561,"HyperDash":false},{"StartTime":32960.0,"Position":144.968948,"HyperDash":false}]},{"StartTime":33188.0,"Objects":[{"StartTime":33188.0,"Position":294.0,"HyperDash":false}]},{"StartTime":33415.0,"Objects":[{"StartTime":33415.0,"Position":295.0,"HyperDash":false},{"StartTime":33467.0,"Position":281.203522,"HyperDash":false},{"StartTime":33519.0,"Position":263.38385,"HyperDash":false},{"StartTime":33571.0,"Position":270.625458,"HyperDash":false},{"StartTime":33624.0,"Position":279.906525,"HyperDash":false},{"StartTime":33676.0,"Position":253.1824,"HyperDash":false},{"StartTime":33728.0,"Position":271.372864,"HyperDash":false},{"StartTime":33780.0,"Position":265.406738,"HyperDash":false},{"StartTime":33869.0,"Position":300.795166,"HyperDash":false}]},{"StartTime":34097.0,"Objects":[{"StartTime":34097.0,"Position":406.0,"HyperDash":false}]},{"StartTime":34324.0,"Objects":[{"StartTime":34324.0,"Position":372.0,"HyperDash":false},{"StartTime":34376.0,"Position":366.75238,"HyperDash":false},{"StartTime":34428.0,"Position":319.542755,"HyperDash":false},{"StartTime":34480.0,"Position":337.707428,"HyperDash":false},{"StartTime":34533.0,"Position":311.1586,"HyperDash":false},{"StartTime":34585.0,"Position":283.844269,"HyperDash":false},{"StartTime":34637.0,"Position":264.676025,"HyperDash":false},{"StartTime":34689.0,"Position":259.952332,"HyperDash":false},{"StartTime":34778.0,"Position":219.572815,"HyperDash":false}]},{"StartTime":35006.0,"Objects":[{"StartTime":35006.0,"Position":117.0,"HyperDash":false}]},{"StartTime":35233.0,"Objects":[{"StartTime":35233.0,"Position":107.0,"HyperDash":false},{"StartTime":35285.0,"Position":123.10994,"HyperDash":false},{"StartTime":35337.0,"Position":138.9144,"HyperDash":false},{"StartTime":35389.0,"Position":142.755829,"HyperDash":false},{"StartTime":35442.0,"Position":177.3442,"HyperDash":false},{"StartTime":35494.0,"Position":180.653748,"HyperDash":false},{"StartTime":35546.0,"Position":203.782745,"HyperDash":false},{"StartTime":35598.0,"Position":209.428528,"HyperDash":false},{"StartTime":35687.0,"Position":255.847168,"HyperDash":false}]},{"StartTime":35915.0,"Objects":[{"StartTime":35915.0,"Position":370.0,"HyperDash":false}]},{"StartTime":36142.0,"Objects":[{"StartTime":36142.0,"Position":330.0,"HyperDash":false}]},{"StartTime":36597.0,"Objects":[{"StartTime":36597.0,"Position":370.0,"HyperDash":false}]},{"StartTime":36824.0,"Objects":[{"StartTime":36824.0,"Position":416.0,"HyperDash":false}]},{"StartTime":37051.0,"Objects":[{"StartTime":37051.0,"Position":406.0,"HyperDash":false},{"StartTime":37103.0,"Position":403.974335,"HyperDash":false},{"StartTime":37155.0,"Position":389.16626,"HyperDash":false},{"StartTime":37207.0,"Position":364.729828,"HyperDash":false},{"StartTime":37260.0,"Position":356.0597,"HyperDash":false},{"StartTime":37312.0,"Position":363.056549,"HyperDash":false},{"StartTime":37364.0,"Position":339.779724,"HyperDash":false},{"StartTime":37416.0,"Position":319.443939,"HyperDash":false},{"StartTime":37505.0,"Position":295.632843,"HyperDash":false}]},{"StartTime":37733.0,"Objects":[{"StartTime":37733.0,"Position":161.0,"HyperDash":false}]},{"StartTime":37961.0,"Objects":[{"StartTime":37961.0,"Position":147.0,"HyperDash":false}]},{"StartTime":38074.0,"Objects":[{"StartTime":38074.0,"Position":161.0,"HyperDash":false}]},{"StartTime":38188.0,"Objects":[{"StartTime":38188.0,"Position":147.0,"HyperDash":false}]},{"StartTime":46142.0,"Objects":[{"StartTime":46142.0,"Position":105.0,"HyperDash":false},{"StartTime":46194.0,"Position":101.3565,"HyperDash":false},{"StartTime":46246.0,"Position":117.818565,"HyperDash":false},{"StartTime":46298.0,"Position":135.426117,"HyperDash":false},{"StartTime":46351.0,"Position":146.825043,"HyperDash":false},{"StartTime":46403.0,"Position":174.897232,"HyperDash":false},{"StartTime":46455.0,"Position":178.608673,"HyperDash":false},{"StartTime":46507.0,"Position":211.715851,"HyperDash":false},{"StartTime":46596.0,"Position":242.038391,"HyperDash":false}]},{"StartTime":47051.0,"Objects":[{"StartTime":47051.0,"Position":399.0,"HyperDash":false},{"StartTime":47107.0,"Position":433.4483,"HyperDash":false},{"StartTime":47164.0,"Position":427.2428,"HyperDash":false},{"StartTime":47221.0,"Position":452.0353,"HyperDash":false},{"StartTime":47278.0,"Position":477.822449,"HyperDash":false},{"StartTime":47373.0,"Position":461.8406,"HyperDash":false},{"StartTime":47505.0,"Position":399.0,"HyperDash":false}]},{"StartTime":47961.0,"Objects":[{"StartTime":47961.0,"Position":422.0,"HyperDash":false},{"StartTime":48013.0,"Position":393.6435,"HyperDash":false},{"StartTime":48065.0,"Position":415.181427,"HyperDash":false},{"StartTime":48117.0,"Position":403.573883,"HyperDash":false},{"StartTime":48170.0,"Position":352.174957,"HyperDash":false},{"StartTime":48222.0,"Position":336.102783,"HyperDash":false},{"StartTime":48274.0,"Position":346.391327,"HyperDash":false},{"StartTime":48326.0,"Position":328.284149,"HyperDash":false},{"StartTime":48415.0,"Position":284.9616,"HyperDash":false}]},{"StartTime":48870.0,"Objects":[{"StartTime":48870.0,"Position":128.0,"HyperDash":false},{"StartTime":48926.0,"Position":123.551682,"HyperDash":false},{"StartTime":48983.0,"Position":80.7571945,"HyperDash":false},{"StartTime":49040.0,"Position":54.96469,"HyperDash":false},{"StartTime":49097.0,"Position":49.17756,"HyperDash":false},{"StartTime":49192.0,"Position":78.1594,"HyperDash":false},{"StartTime":49324.0,"Position":128.0,"HyperDash":false}]},{"StartTime":49779.0,"Objects":[{"StartTime":49779.0,"Position":252.0,"HyperDash":false},{"StartTime":49831.0,"Position":281.5043,"HyperDash":false},{"StartTime":49883.0,"Position":284.787231,"HyperDash":false},{"StartTime":49935.0,"Position":303.602631,"HyperDash":false},{"StartTime":49988.0,"Position":315.098541,"HyperDash":false},{"StartTime":50040.0,"Position":356.3944,"HyperDash":false},{"StartTime":50092.0,"Position":367.7095,"HyperDash":false},{"StartTime":50144.0,"Position":369.952545,"HyperDash":false},{"StartTime":50233.0,"Position":407.8509,"HyperDash":false}]},{"StartTime":50688.0,"Objects":[{"StartTime":50688.0,"Position":248.0,"HyperDash":false}]},{"StartTime":50915.0,"Objects":[{"StartTime":50915.0,"Position":377.0,"HyperDash":false},{"StartTime":51010.0,"Position":359.866516,"HyperDash":false},{"StartTime":51142.0,"Position":298.613647,"HyperDash":false}]},{"StartTime":51370.0,"Objects":[{"StartTime":51370.0,"Position":161.0,"HyperDash":false}]},{"StartTime":51597.0,"Objects":[{"StartTime":51597.0,"Position":159.0,"HyperDash":false},{"StartTime":51692.0,"Position":111.3809,"HyperDash":false},{"StartTime":51824.0,"Position":81.1563339,"HyperDash":false}]},{"StartTime":52051.0,"Objects":[{"StartTime":52051.0,"Position":107.0,"HyperDash":false},{"StartTime":52146.0,"Position":72.66428,"HyperDash":false},{"StartTime":52278.0,"Position":28.8123531,"HyperDash":false}]},{"StartTime":52506.0,"Objects":[{"StartTime":52506.0,"Position":75.0,"HyperDash":false},{"StartTime":52558.0,"Position":76.23376,"HyperDash":false},{"StartTime":52610.0,"Position":99.55078,"HyperDash":false},{"StartTime":52662.0,"Position":117.824188,"HyperDash":false},{"StartTime":52715.0,"Position":140.248856,"HyperDash":false},{"StartTime":52767.0,"Position":167.9607,"HyperDash":false},{"StartTime":52819.0,"Position":172.073,"HyperDash":false},{"StartTime":52871.0,"Position":216.350311,"HyperDash":false},{"StartTime":52960.0,"Position":224.446579,"HyperDash":false}]},{"StartTime":53415.0,"Objects":[{"StartTime":53415.0,"Position":413.0,"HyperDash":false}]},{"StartTime":53642.0,"Objects":[{"StartTime":53642.0,"Position":321.0,"HyperDash":false}]},{"StartTime":53870.0,"Objects":[{"StartTime":53870.0,"Position":321.0,"HyperDash":false},{"StartTime":53922.0,"Position":347.217651,"HyperDash":false},{"StartTime":53974.0,"Position":342.931427,"HyperDash":false},{"StartTime":54026.0,"Position":379.435669,"HyperDash":false},{"StartTime":54079.0,"Position":363.721619,"HyperDash":false},{"StartTime":54131.0,"Position":366.5289,"HyperDash":false},{"StartTime":54183.0,"Position":366.0941,"HyperDash":false},{"StartTime":54235.0,"Position":367.430542,"HyperDash":false},{"StartTime":54324.0,"Position":367.2075,"HyperDash":false}]},{"StartTime":54551.0,"Objects":[{"StartTime":54551.0,"Position":310.0,"HyperDash":false}]},{"StartTime":54779.0,"Objects":[{"StartTime":54779.0,"Position":222.0,"HyperDash":false}]},{"StartTime":55233.0,"Objects":[{"StartTime":55233.0,"Position":310.0,"HyperDash":false}]},{"StartTime":55461.0,"Objects":[{"StartTime":55461.0,"Position":222.0,"HyperDash":false}]},{"StartTime":55688.0,"Objects":[{"StartTime":55688.0,"Position":266.0,"HyperDash":false},{"StartTime":55740.0,"Position":250.312454,"HyperDash":false},{"StartTime":55792.0,"Position":222.151321,"HyperDash":false},{"StartTime":55844.0,"Position":229.8381,"HyperDash":false},{"StartTime":55897.0,"Position":199.311859,"HyperDash":false},{"StartTime":55949.0,"Position":190.573822,"HyperDash":false},{"StartTime":56001.0,"Position":163.5982,"HyperDash":false},{"StartTime":56053.0,"Position":126.797623,"HyperDash":false},{"StartTime":56142.0,"Position":119.985596,"HyperDash":false}]},{"StartTime":56370.0,"Objects":[{"StartTime":56370.0,"Position":70.0,"HyperDash":false}]},{"StartTime":56597.0,"Objects":[{"StartTime":56597.0,"Position":128.0,"HyperDash":false}]},{"StartTime":57051.0,"Objects":[{"StartTime":57051.0,"Position":70.0,"HyperDash":false}]},{"StartTime":57279.0,"Objects":[{"StartTime":57279.0,"Position":128.0,"HyperDash":false}]},{"StartTime":57506.0,"Objects":[{"StartTime":57506.0,"Position":99.0,"HyperDash":false},{"StartTime":57558.0,"Position":95.98298,"HyperDash":false},{"StartTime":57610.0,"Position":141.1198,"HyperDash":false},{"StartTime":57662.0,"Position":153.374634,"HyperDash":false},{"StartTime":57715.0,"Position":146.589783,"HyperDash":false},{"StartTime":57767.0,"Position":186.773819,"HyperDash":false},{"StartTime":57819.0,"Position":202.087418,"HyperDash":false},{"StartTime":57871.0,"Position":227.361313,"HyperDash":false},{"StartTime":57960.0,"Position":249.971191,"HyperDash":false}]},{"StartTime":58188.0,"Objects":[{"StartTime":58188.0,"Position":398.0,"HyperDash":false}]},{"StartTime":58415.0,"Objects":[{"StartTime":58415.0,"Position":366.0,"HyperDash":false}]},{"StartTime":58642.0,"Objects":[{"StartTime":58642.0,"Position":401.0,"HyperDash":false},{"StartTime":58737.0,"Position":372.52832,"HyperDash":false},{"StartTime":58869.0,"Position":348.93866,"HyperDash":false}]},{"StartTime":59097.0,"Objects":[{"StartTime":59097.0,"Position":203.0,"HyperDash":false}]},{"StartTime":59324.0,"Objects":[{"StartTime":59324.0,"Position":337.0,"HyperDash":false},{"StartTime":59419.0,"Position":354.740051,"HyperDash":false},{"StartTime":59551.0,"Position":364.726837,"HyperDash":false}]},{"StartTime":59779.0,"Objects":[{"StartTime":59779.0,"Position":284.0,"HyperDash":false},{"StartTime":59831.0,"Position":267.281219,"HyperDash":false},{"StartTime":59883.0,"Position":257.357849,"HyperDash":false},{"StartTime":59935.0,"Position":220.054123,"HyperDash":false},{"StartTime":59988.0,"Position":220.521576,"HyperDash":false},{"StartTime":60040.0,"Position":193.393219,"HyperDash":false},{"StartTime":60092.0,"Position":176.168411,"HyperDash":false},{"StartTime":60144.0,"Position":151.876328,"HyperDash":false},{"StartTime":60233.0,"Position":130.344528,"HyperDash":false}]},{"StartTime":60688.0,"Objects":[{"StartTime":60688.0,"Position":41.0,"HyperDash":false}]},{"StartTime":61142.0,"Objects":[{"StartTime":61142.0,"Position":191.0,"HyperDash":false},{"StartTime":61237.0,"Position":220.210571,"HyperDash":false},{"StartTime":61369.0,"Position":265.576843,"HyperDash":false}]},{"StartTime":61597.0,"Objects":[{"StartTime":61597.0,"Position":254.0,"HyperDash":false},{"StartTime":61692.0,"Position":300.210571,"HyperDash":false},{"StartTime":61824.0,"Position":328.576843,"HyperDash":false}]},{"StartTime":62051.0,"Objects":[{"StartTime":62051.0,"Position":299.0,"HyperDash":false}]},{"StartTime":62279.0,"Objects":[{"StartTime":62279.0,"Position":319.0,"HyperDash":false},{"StartTime":62374.0,"Position":304.789429,"HyperDash":false},{"StartTime":62506.0,"Position":244.423172,"HyperDash":false}]},{"StartTime":62733.0,"Objects":[{"StartTime":62733.0,"Position":102.0,"HyperDash":false}]},{"StartTime":62961.0,"Objects":[{"StartTime":62961.0,"Position":80.0,"HyperDash":false}]},{"StartTime":63188.0,"Objects":[{"StartTime":63188.0,"Position":31.0,"HyperDash":false}]},{"StartTime":63415.0,"Objects":[{"StartTime":63415.0,"Position":31.0,"HyperDash":false},{"StartTime":63471.0,"Position":15.0,"HyperDash":false},{"StartTime":63528.0,"Position":13.0,"HyperDash":false},{"StartTime":63585.0,"Position":43.0,"HyperDash":false},{"StartTime":63642.0,"Position":31.0,"HyperDash":false},{"StartTime":63737.0,"Position":38.0,"HyperDash":false},{"StartTime":63869.0,"Position":31.0,"HyperDash":false}]},{"StartTime":64324.0,"Objects":[{"StartTime":64324.0,"Position":331.0,"HyperDash":false}]},{"StartTime":64779.0,"Objects":[{"StartTime":64779.0,"Position":335.0,"HyperDash":false},{"StartTime":64874.0,"Position":315.0,"HyperDash":false},{"StartTime":65006.0,"Position":335.0,"HyperDash":false}]},{"StartTime":65233.0,"Objects":[{"StartTime":65233.0,"Position":405.0,"HyperDash":false},{"StartTime":65328.0,"Position":404.0,"HyperDash":false},{"StartTime":65460.0,"Position":405.0,"HyperDash":false}]},{"StartTime":65688.0,"Objects":[{"StartTime":65688.0,"Position":475.0,"HyperDash":false}]},{"StartTime":65915.0,"Objects":[{"StartTime":65915.0,"Position":475.0,"HyperDash":false},{"StartTime":66010.0,"Position":460.0,"HyperDash":false},{"StartTime":66142.0,"Position":475.0,"HyperDash":false}]},{"StartTime":66370.0,"Objects":[{"StartTime":66370.0,"Position":335.0,"HyperDash":false}]},{"StartTime":66597.0,"Objects":[{"StartTime":66597.0,"Position":315.0,"HyperDash":false}]},{"StartTime":66824.0,"Objects":[{"StartTime":66824.0,"Position":189.0,"HyperDash":false}]},{"StartTime":67051.0,"Objects":[{"StartTime":67051.0,"Position":219.0,"HyperDash":false}]},{"StartTime":67279.0,"Objects":[{"StartTime":67279.0,"Position":159.0,"HyperDash":false}]},{"StartTime":67506.0,"Objects":[{"StartTime":67506.0,"Position":245.0,"HyperDash":false}]},{"StartTime":67733.0,"Objects":[{"StartTime":67733.0,"Position":255.0,"HyperDash":false}]},{"StartTime":67961.0,"Objects":[{"StartTime":67961.0,"Position":329.0,"HyperDash":false},{"StartTime":68056.0,"Position":343.033264,"HyperDash":false},{"StartTime":68188.0,"Position":407.932129,"HyperDash":false}]},{"StartTime":68415.0,"Objects":[{"StartTime":68415.0,"Position":427.0,"HyperDash":false},{"StartTime":68510.0,"Position":397.966736,"HyperDash":false},{"StartTime":68642.0,"Position":348.067871,"HyperDash":false}]},{"StartTime":68870.0,"Objects":[{"StartTime":68870.0,"Position":303.0,"HyperDash":false},{"StartTime":68965.0,"Position":338.033264,"HyperDash":false},{"StartTime":69097.0,"Position":381.932129,"HyperDash":false}]},{"StartTime":69324.0,"Objects":[{"StartTime":69324.0,"Position":401.0,"HyperDash":false},{"StartTime":69419.0,"Position":384.966736,"HyperDash":false},{"StartTime":69551.0,"Position":322.067871,"HyperDash":false}]},{"StartTime":69779.0,"Objects":[{"StartTime":69779.0,"Position":186.0,"HyperDash":false}]},{"StartTime":70006.0,"Objects":[{"StartTime":70006.0,"Position":298.0,"HyperDash":false}]},{"StartTime":70233.0,"Objects":[{"StartTime":70233.0,"Position":163.0,"HyperDash":false}]},{"StartTime":70461.0,"Objects":[{"StartTime":70461.0,"Position":143.0,"HyperDash":false}]},{"StartTime":70688.0,"Objects":[{"StartTime":70688.0,"Position":84.0,"HyperDash":false},{"StartTime":70744.0,"Position":78.72694,"HyperDash":false},{"StartTime":70801.0,"Position":82.25869,"HyperDash":false},{"StartTime":70858.0,"Position":70.21074,"HyperDash":false},{"StartTime":70915.0,"Position":84.71703,"HyperDash":false},{"StartTime":70971.0,"Position":78.31016,"HyperDash":false},{"StartTime":71028.0,"Position":58.2654724,"HyperDash":false},{"StartTime":71085.0,"Position":57.63716,"HyperDash":false},{"StartTime":71142.0,"Position":84.0,"HyperDash":false},{"StartTime":71237.0,"Position":54.4390259,"HyperDash":false},{"StartTime":71369.0,"Position":84.71703,"HyperDash":false}]},{"StartTime":71597.0,"Objects":[{"StartTime":71597.0,"Position":148.0,"HyperDash":false},{"StartTime":71649.0,"Position":165.786362,"HyperDash":false},{"StartTime":71701.0,"Position":179.3862,"HyperDash":false},{"StartTime":71753.0,"Position":206.911774,"HyperDash":false},{"StartTime":71806.0,"Position":197.271149,"HyperDash":false},{"StartTime":71858.0,"Position":230.514236,"HyperDash":false},{"StartTime":71910.0,"Position":245.834518,"HyperDash":false},{"StartTime":71962.0,"Position":272.100525,"HyperDash":false},{"StartTime":72051.0,"Position":300.802856,"HyperDash":false}]},{"StartTime":72506.0,"Objects":[{"StartTime":72506.0,"Position":374.0,"HyperDash":false},{"StartTime":72558.0,"Position":376.213623,"HyperDash":false},{"StartTime":72610.0,"Position":353.6138,"HyperDash":false},{"StartTime":72662.0,"Position":320.088226,"HyperDash":false},{"StartTime":72715.0,"Position":293.728851,"HyperDash":false},{"StartTime":72767.0,"Position":297.485779,"HyperDash":false},{"StartTime":72819.0,"Position":272.165466,"HyperDash":false},{"StartTime":72871.0,"Position":247.899475,"HyperDash":false},{"StartTime":72960.0,"Position":221.197159,"HyperDash":false}]},{"StartTime":73188.0,"Objects":[{"StartTime":73188.0,"Position":77.0,"HyperDash":false}]},{"StartTime":73415.0,"Objects":[{"StartTime":73415.0,"Position":213.0,"HyperDash":false},{"StartTime":73510.0,"Position":233.974548,"HyperDash":false},{"StartTime":73642.0,"Position":279.844421,"HyperDash":false}]},{"StartTime":73870.0,"Objects":[{"StartTime":73870.0,"Position":346.0,"HyperDash":false},{"StartTime":73965.0,"Position":336.709564,"HyperDash":false},{"StartTime":74097.0,"Position":297.516541,"HyperDash":false}]},{"StartTime":74324.0,"Objects":[{"StartTime":74324.0,"Position":222.0,"HyperDash":false}]},{"StartTime":74551.0,"Objects":[{"StartTime":74551.0,"Position":282.0,"HyperDash":false}]},{"StartTime":74779.0,"Objects":[{"StartTime":74779.0,"Position":252.0,"HyperDash":false},{"StartTime":74835.0,"Position":222.93634,"HyperDash":false},{"StartTime":74892.0,"Position":231.971436,"HyperDash":false},{"StartTime":74949.0,"Position":179.985031,"HyperDash":false},{"StartTime":75006.0,"Position":173.674133,"HyperDash":false},{"StartTime":75101.0,"Position":204.278076,"HyperDash":false},{"StartTime":75233.0,"Position":252.0,"HyperDash":false}]},{"StartTime":75688.0,"Objects":[{"StartTime":75688.0,"Position":194.0,"HyperDash":false},{"StartTime":75744.0,"Position":188.93634,"HyperDash":false},{"StartTime":75801.0,"Position":159.971436,"HyperDash":false},{"StartTime":75858.0,"Position":124.985031,"HyperDash":false},{"StartTime":75915.0,"Position":115.674141,"HyperDash":false},{"StartTime":76010.0,"Position":154.278076,"HyperDash":false},{"StartTime":76142.0,"Position":194.0,"HyperDash":false}]},{"StartTime":76597.0,"Objects":[{"StartTime":76597.0,"Position":347.0,"HyperDash":false}]},{"StartTime":76824.0,"Objects":[{"StartTime":76824.0,"Position":327.0,"HyperDash":false}]},{"StartTime":77051.0,"Objects":[{"StartTime":77051.0,"Position":351.0,"HyperDash":false}]},{"StartTime":77506.0,"Objects":[{"StartTime":77506.0,"Position":448.0,"HyperDash":false}]},{"StartTime":77733.0,"Objects":[{"StartTime":77733.0,"Position":368.0,"HyperDash":false}]},{"StartTime":77961.0,"Objects":[{"StartTime":77961.0,"Position":242.0,"HyperDash":false}]},{"StartTime":78415.0,"Objects":[{"StartTime":78415.0,"Position":50.0,"HyperDash":false}]},{"StartTime":78642.0,"Objects":[{"StartTime":78642.0,"Position":118.0,"HyperDash":false},{"StartTime":78737.0,"Position":102.772095,"HyperDash":false},{"StartTime":78869.0,"Position":62.0753326,"HyperDash":false}]},{"StartTime":79324.0,"Objects":[{"StartTime":79324.0,"Position":218.0,"HyperDash":false},{"StartTime":79419.0,"Position":253.274826,"HyperDash":false},{"StartTime":79551.0,"Position":294.3989,"HyperDash":false}]},{"StartTime":79779.0,"Objects":[{"StartTime":79779.0,"Position":443.0,"HyperDash":false}]},{"StartTime":80233.0,"Objects":[{"StartTime":80233.0,"Position":286.0,"HyperDash":false},{"StartTime":80289.0,"Position":301.1139,"HyperDash":false},{"StartTime":80346.0,"Position":277.211975,"HyperDash":false},{"StartTime":80403.0,"Position":273.310028,"HyperDash":false},{"StartTime":80460.0,"Position":282.4081,"HyperDash":false},{"StartTime":80555.0,"Position":290.911316,"HyperDash":false},{"StartTime":80687.0,"Position":286.0,"HyperDash":false}]},{"StartTime":81142.0,"Objects":[{"StartTime":81142.0,"Position":427.0,"HyperDash":false}]},{"StartTime":81370.0,"Objects":[{"StartTime":81370.0,"Position":423.0,"HyperDash":false}]},{"StartTime":81597.0,"Objects":[{"StartTime":81597.0,"Position":427.0,"HyperDash":false},{"StartTime":81653.0,"Position":415.357849,"HyperDash":false},{"StartTime":81710.0,"Position":429.752075,"HyperDash":false},{"StartTime":81824.0,"Position":427.0,"HyperDash":false}]},{"StartTime":82051.0,"Objects":[{"StartTime":82051.0,"Position":411.0,"HyperDash":false}]},{"StartTime":82279.0,"Objects":[{"StartTime":82279.0,"Position":301.0,"HyperDash":false}]},{"StartTime":82506.0,"Objects":[{"StartTime":82506.0,"Position":285.0,"HyperDash":false},{"StartTime":82558.0,"Position":275.960876,"HyperDash":false},{"StartTime":82610.0,"Position":258.1504,"HyperDash":false},{"StartTime":82662.0,"Position":240.960434,"HyperDash":false},{"StartTime":82715.0,"Position":213.29361,"HyperDash":false},{"StartTime":82767.0,"Position":177.039841,"HyperDash":false},{"StartTime":82819.0,"Position":192.027191,"HyperDash":false},{"StartTime":82871.0,"Position":162.507034,"HyperDash":false},{"StartTime":82960.0,"Position":132.24411,"HyperDash":false}]},{"StartTime":83188.0,"Objects":[{"StartTime":83188.0,"Position":246.0,"HyperDash":false}]},{"StartTime":83415.0,"Objects":[{"StartTime":83415.0,"Position":267.0,"HyperDash":false},{"StartTime":83467.0,"Position":277.3109,"HyperDash":false},{"StartTime":83519.0,"Position":280.6682,"HyperDash":false},{"StartTime":83571.0,"Position":302.941681,"HyperDash":false},{"StartTime":83624.0,"Position":298.1128,"HyperDash":false},{"StartTime":83676.0,"Position":291.2743,"HyperDash":false},{"StartTime":83728.0,"Position":290.638519,"HyperDash":false},{"StartTime":83780.0,"Position":254.4002,"HyperDash":false},{"StartTime":83869.0,"Position":250.128326,"HyperDash":false}]},{"StartTime":84097.0,"Objects":[{"StartTime":84097.0,"Position":161.0,"HyperDash":false}]},{"StartTime":84324.0,"Objects":[{"StartTime":84324.0,"Position":188.0,"HyperDash":false},{"StartTime":84376.0,"Position":211.223328,"HyperDash":false},{"StartTime":84428.0,"Position":235.527512,"HyperDash":false},{"StartTime":84480.0,"Position":225.64093,"HyperDash":false},{"StartTime":84533.0,"Position":278.681366,"HyperDash":false},{"StartTime":84585.0,"Position":283.741333,"HyperDash":false},{"StartTime":84637.0,"Position":282.889923,"HyperDash":false},{"StartTime":84689.0,"Position":320.765961,"HyperDash":false},{"StartTime":84778.0,"Position":329.839569,"HyperDash":false}]},{"StartTime":85006.0,"Objects":[{"StartTime":85006.0,"Position":177.0,"HyperDash":false}]},{"StartTime":85233.0,"Objects":[{"StartTime":85233.0,"Position":177.0,"HyperDash":false},{"StartTime":85285.0,"Position":165.201,"HyperDash":false},{"StartTime":85337.0,"Position":182.838409,"HyperDash":false},{"StartTime":85389.0,"Position":172.951111,"HyperDash":false},{"StartTime":85442.0,"Position":165.6638,"HyperDash":false},{"StartTime":85494.0,"Position":172.542755,"HyperDash":false},{"StartTime":85546.0,"Position":188.28334,"HyperDash":false},{"StartTime":85598.0,"Position":236.294235,"HyperDash":false},{"StartTime":85687.0,"Position":249.329514,"HyperDash":false}]},{"StartTime":85915.0,"Objects":[{"StartTime":85915.0,"Position":368.0,"HyperDash":false}]},{"StartTime":86142.0,"Objects":[{"StartTime":86142.0,"Position":404.0,"HyperDash":false},{"StartTime":86194.0,"Position":401.8277,"HyperDash":false},{"StartTime":86246.0,"Position":454.150146,"HyperDash":false},{"StartTime":86298.0,"Position":426.349945,"HyperDash":false},{"StartTime":86351.0,"Position":450.306671,"HyperDash":false},{"StartTime":86403.0,"Position":444.439728,"HyperDash":false},{"StartTime":86455.0,"Position":429.120422,"HyperDash":false},{"StartTime":86507.0,"Position":418.0135,"HyperDash":false},{"StartTime":86596.0,"Position":405.457642,"HyperDash":false}]},{"StartTime":86824.0,"Objects":[{"StartTime":86824.0,"Position":272.0,"HyperDash":false}]},{"StartTime":87051.0,"Objects":[{"StartTime":87051.0,"Position":220.0,"HyperDash":false}]},{"StartTime":87506.0,"Objects":[{"StartTime":87506.0,"Position":272.0,"HyperDash":false}]},{"StartTime":87733.0,"Objects":[{"StartTime":87733.0,"Position":192.0,"HyperDash":false}]},{"StartTime":87961.0,"Objects":[{"StartTime":87961.0,"Position":168.0,"HyperDash":false},{"StartTime":88013.0,"Position":180.07048,"HyperDash":false},{"StartTime":88065.0,"Position":145.3697,"HyperDash":false},{"StartTime":88117.0,"Position":181.001678,"HyperDash":false},{"StartTime":88170.0,"Position":158.001877,"HyperDash":false},{"StartTime":88222.0,"Position":177.868271,"HyperDash":false},{"StartTime":88274.0,"Position":202.302979,"HyperDash":false},{"StartTime":88326.0,"Position":215.876221,"HyperDash":false},{"StartTime":88415.0,"Position":224.187881,"HyperDash":false}]},{"StartTime":88642.0,"Objects":[{"StartTime":88642.0,"Position":363.0,"HyperDash":false}]},{"StartTime":88870.0,"Objects":[{"StartTime":88870.0,"Position":393.0,"HyperDash":false}]},{"StartTime":88983.0,"Objects":[{"StartTime":88983.0,"Position":363.0,"HyperDash":false}]},{"StartTime":89097.0,"Objects":[{"StartTime":89097.0,"Position":393.0,"HyperDash":false}]},{"StartTime":93415.0,"Objects":[{"StartTime":93415.0,"Position":330.0,"HyperDash":false},{"StartTime":93500.0,"Position":362.93335,"HyperDash":false},{"StartTime":93585.0,"Position":384.5453,"HyperDash":false},{"StartTime":93670.0,"Position":408.46228,"HyperDash":false},{"StartTime":93755.0,"Position":448.525055,"HyperDash":false},{"StartTime":93831.0,"Position":430.9859,"HyperDash":false},{"StartTime":93907.0,"Position":391.21936,"HyperDash":false},{"StartTime":93983.0,"Position":356.6329,"HyperDash":false},{"StartTime":94096.0,"Position":330.0,"HyperDash":false}]},{"StartTime":94324.0,"Objects":[{"StartTime":94324.0,"Position":55.0,"HyperDash":false}]},{"StartTime":94552.0,"Objects":[{"StartTime":94552.0,"Position":181.0,"HyperDash":false},{"StartTime":94665.0,"Position":145.222916,"HyperDash":false}]},{"StartTime":95233.0,"Objects":[{"StartTime":95233.0,"Position":181.0,"HyperDash":false},{"StartTime":95318.0,"Position":141.066635,"HyperDash":false},{"StartTime":95403.0,"Position":137.4547,"HyperDash":false},{"StartTime":95488.0,"Position":96.53772,"HyperDash":false},{"StartTime":95573.0,"Position":62.47494,"HyperDash":false},{"StartTime":95649.0,"Position":96.0141144,"HyperDash":false},{"StartTime":95725.0,"Position":128.78064,"HyperDash":false},{"StartTime":95801.0,"Position":133.367081,"HyperDash":false},{"StartTime":95914.0,"Position":181.0,"HyperDash":false}]},{"StartTime":96142.0,"Objects":[{"StartTime":96142.0,"Position":456.0,"HyperDash":false}]},{"StartTime":96370.0,"Objects":[{"StartTime":96370.0,"Position":330.0,"HyperDash":false},{"StartTime":96483.0,"Position":365.7771,"HyperDash":false}]},{"StartTime":97052.0,"Objects":[{"StartTime":97052.0,"Position":330.0,"HyperDash":false},{"StartTime":97137.0,"Position":369.93335,"HyperDash":false},{"StartTime":97222.0,"Position":380.5453,"HyperDash":false},{"StartTime":97307.0,"Position":411.46228,"HyperDash":false},{"StartTime":97392.0,"Position":448.525055,"HyperDash":false},{"StartTime":97468.0,"Position":429.9859,"HyperDash":false},{"StartTime":97544.0,"Position":391.21936,"HyperDash":false},{"StartTime":97620.0,"Position":384.6329,"HyperDash":false},{"StartTime":97733.0,"Position":330.0,"HyperDash":false}]},{"StartTime":97961.0,"Objects":[{"StartTime":97961.0,"Position":55.0,"HyperDash":false}]},{"StartTime":98188.0,"Objects":[{"StartTime":98188.0,"Position":181.0,"HyperDash":false},{"StartTime":98301.0,"Position":145.222916,"HyperDash":false}]},{"StartTime":98870.0,"Objects":[{"StartTime":98870.0,"Position":181.0,"HyperDash":false},{"StartTime":98955.0,"Position":139.066635,"HyperDash":false},{"StartTime":99040.0,"Position":124.4547,"HyperDash":false},{"StartTime":99125.0,"Position":111.53772,"HyperDash":false},{"StartTime":99210.0,"Position":62.47494,"HyperDash":false},{"StartTime":99286.0,"Position":89.0141144,"HyperDash":false},{"StartTime":99362.0,"Position":121.780647,"HyperDash":false},{"StartTime":99438.0,"Position":125.367081,"HyperDash":false},{"StartTime":99551.0,"Position":181.0,"HyperDash":false}]},{"StartTime":99779.0,"Objects":[{"StartTime":99779.0,"Position":456.0,"HyperDash":false}]},{"StartTime":100006.0,"Objects":[{"StartTime":100006.0,"Position":330.0,"HyperDash":false},{"StartTime":100119.0,"Position":365.7771,"HyperDash":false}]},{"StartTime":100688.0,"Objects":[{"StartTime":100688.0,"Position":454.0,"HyperDash":false},{"StartTime":100801.0,"Position":414.608643,"HyperDash":false}]},{"StartTime":101029.0,"Objects":[{"StartTime":101029.0,"Position":335.0,"HyperDash":false},{"StartTime":101142.0,"Position":295.465118,"HyperDash":false}]},{"StartTime":101370.0,"Objects":[{"StartTime":101370.0,"Position":162.0,"HyperDash":false}]},{"StartTime":101597.0,"Objects":[{"StartTime":101597.0,"Position":137.0,"HyperDash":false},{"StartTime":101692.0,"Position":96.96697,"HyperDash":false},{"StartTime":101824.0,"Position":57.5450439,"HyperDash":false}]},{"StartTime":101938.0,"Objects":[{"StartTime":101938.0,"Position":84.0,"HyperDash":false}]},{"StartTime":102506.0,"Objects":[{"StartTime":102506.0,"Position":57.0,"HyperDash":false},{"StartTime":102619.0,"Position":96.39134,"HyperDash":false}]},{"StartTime":102847.0,"Objects":[{"StartTime":102847.0,"Position":176.0,"HyperDash":false},{"StartTime":102960.0,"Position":215.520477,"HyperDash":false}]},{"StartTime":103188.0,"Objects":[{"StartTime":103188.0,"Position":350.0,"HyperDash":false}]},{"StartTime":103415.0,"Objects":[{"StartTime":103415.0,"Position":374.0,"HyperDash":false},{"StartTime":103510.0,"Position":415.03302,"HyperDash":false},{"StartTime":103642.0,"Position":453.454956,"HyperDash":false}]},{"StartTime":103756.0,"Objects":[{"StartTime":103756.0,"Position":427.0,"HyperDash":false}]},{"StartTime":104324.0,"Objects":[{"StartTime":104324.0,"Position":454.0,"HyperDash":false},{"StartTime":104437.0,"Position":414.608643,"HyperDash":false}]},{"StartTime":104665.0,"Objects":[{"StartTime":104665.0,"Position":335.0,"HyperDash":false},{"StartTime":104778.0,"Position":295.465118,"HyperDash":false}]},{"StartTime":105006.0,"Objects":[{"StartTime":105006.0,"Position":162.0,"HyperDash":false}]},{"StartTime":105120.0,"Objects":[{"StartTime":105120.0,"Position":190.0,"HyperDash":false}]},{"StartTime":105233.0,"Objects":[{"StartTime":105233.0,"Position":137.0,"HyperDash":false},{"StartTime":105328.0,"Position":83.96697,"HyperDash":false},{"StartTime":105460.0,"Position":57.5450439,"HyperDash":false}]},{"StartTime":105574.0,"Objects":[{"StartTime":105574.0,"Position":84.0,"HyperDash":false}]},{"StartTime":106142.0,"Objects":[{"StartTime":106142.0,"Position":57.0,"HyperDash":false},{"StartTime":106255.0,"Position":96.39134,"HyperDash":false}]},{"StartTime":106483.0,"Objects":[{"StartTime":106483.0,"Position":176.0,"HyperDash":false},{"StartTime":106596.0,"Position":215.520477,"HyperDash":false}]},{"StartTime":106824.0,"Objects":[{"StartTime":106824.0,"Position":295.0,"HyperDash":false},{"StartTime":106904.0,"Position":306.2746,"HyperDash":false},{"StartTime":106985.0,"Position":352.66098,"HyperDash":false},{"StartTime":107065.0,"Position":389.650146,"HyperDash":false},{"StartTime":107146.0,"Position":414.777618,"HyperDash":false},{"StartTime":107227.0,"Position":392.217163,"HyperDash":false},{"StartTime":107307.0,"Position":354.003235,"HyperDash":false},{"StartTime":107388.0,"Position":321.594,"HyperDash":false},{"StartTime":107505.0,"Position":294.390747,"HyperDash":false}]},{"StartTime":115233.0,"Objects":[{"StartTime":115233.0,"Position":114.0,"HyperDash":false},{"StartTime":115285.0,"Position":143.939957,"HyperDash":false},{"StartTime":115337.0,"Position":150.324554,"HyperDash":false},{"StartTime":115389.0,"Position":183.259644,"HyperDash":false},{"StartTime":115442.0,"Position":188.794647,"HyperDash":false},{"StartTime":115494.0,"Position":195.08873,"HyperDash":false},{"StartTime":115546.0,"Position":237.411819,"HyperDash":false},{"StartTime":115598.0,"Position":240.698227,"HyperDash":false},{"StartTime":115687.0,"Position":269.692047,"HyperDash":false}]},{"StartTime":115915.0,"Objects":[{"StartTime":115915.0,"Position":413.0,"HyperDash":false}]},{"StartTime":116142.0,"Objects":[{"StartTime":116142.0,"Position":419.0,"HyperDash":false},{"StartTime":116198.0,"Position":419.6598,"HyperDash":false},{"StartTime":116255.0,"Position":457.3915,"HyperDash":false},{"StartTime":116312.0,"Position":466.4778,"HyperDash":false},{"StartTime":116369.0,"Position":449.986969,"HyperDash":false},{"StartTime":116464.0,"Position":432.831818,"HyperDash":false},{"StartTime":116596.0,"Position":419.0,"HyperDash":false}]},{"StartTime":117052.0,"Objects":[{"StartTime":117052.0,"Position":366.0,"HyperDash":false},{"StartTime":117147.0,"Position":351.721741,"HyperDash":false},{"StartTime":117279.0,"Position":295.245026,"HyperDash":false}]},{"StartTime":117506.0,"Objects":[{"StartTime":117506.0,"Position":157.0,"HyperDash":false}]},{"StartTime":117733.0,"Objects":[{"StartTime":117733.0,"Position":141.0,"HyperDash":false}]},{"StartTime":117961.0,"Objects":[{"StartTime":117961.0,"Position":84.0,"HyperDash":false},{"StartTime":118017.0,"Position":70.0,"HyperDash":false},{"StartTime":118074.0,"Position":100.0,"HyperDash":false},{"StartTime":118131.0,"Position":96.0,"HyperDash":false},{"StartTime":118188.0,"Position":84.0,"HyperDash":false},{"StartTime":118244.0,"Position":72.0,"HyperDash":false},{"StartTime":118301.0,"Position":95.0,"HyperDash":false},{"StartTime":118358.0,"Position":100.0,"HyperDash":false},{"StartTime":118415.0,"Position":84.0,"HyperDash":false},{"StartTime":118510.0,"Position":103.0,"HyperDash":false},{"StartTime":118642.0,"Position":84.0,"HyperDash":false}]},{"StartTime":118870.0,"Objects":[{"StartTime":118870.0,"Position":86.0,"HyperDash":false}]},{"StartTime":119097.0,"Objects":[{"StartTime":119097.0,"Position":224.0,"HyperDash":false}]},{"StartTime":119324.0,"Objects":[{"StartTime":119324.0,"Position":226.0,"HyperDash":false}]},{"StartTime":119552.0,"Objects":[{"StartTime":119552.0,"Position":366.0,"HyperDash":false}]},{"StartTime":119779.0,"Objects":[{"StartTime":119779.0,"Position":368.0,"HyperDash":false},{"StartTime":119835.0,"Position":397.255524,"HyperDash":false},{"StartTime":119892.0,"Position":406.27597,"HyperDash":false},{"StartTime":119949.0,"Position":410.2929,"HyperDash":false},{"StartTime":120006.0,"Position":446.9768,"HyperDash":false},{"StartTime":120101.0,"Position":415.96817,"HyperDash":false},{"StartTime":120233.0,"Position":368.0,"HyperDash":false}]},{"StartTime":120688.0,"Objects":[{"StartTime":120688.0,"Position":407.0,"HyperDash":false}]},{"StartTime":120915.0,"Objects":[{"StartTime":120915.0,"Position":321.0,"HyperDash":false}]},{"StartTime":121142.0,"Objects":[{"StartTime":121142.0,"Position":286.0,"HyperDash":false},{"StartTime":121194.0,"Position":262.810974,"HyperDash":false},{"StartTime":121246.0,"Position":235.4965,"HyperDash":false},{"StartTime":121298.0,"Position":223.241028,"HyperDash":false},{"StartTime":121351.0,"Position":219.863861,"HyperDash":false},{"StartTime":121403.0,"Position":209.2498,"HyperDash":false},{"StartTime":121455.0,"Position":171.249588,"HyperDash":false},{"StartTime":121507.0,"Position":177.110733,"HyperDash":false},{"StartTime":121596.0,"Position":137.418732,"HyperDash":false}]},{"StartTime":121824.0,"Objects":[{"StartTime":121824.0,"Position":78.0,"HyperDash":false}]},{"StartTime":122052.0,"Objects":[{"StartTime":122052.0,"Position":102.0,"HyperDash":false},{"StartTime":122147.0,"Position":99.41432,"HyperDash":false},{"StartTime":122279.0,"Position":141.1235,"HyperDash":false}]},{"StartTime":122506.0,"Objects":[{"StartTime":122506.0,"Position":187.0,"HyperDash":false},{"StartTime":122558.0,"Position":192.496933,"HyperDash":false},{"StartTime":122610.0,"Position":237.792938,"HyperDash":false},{"StartTime":122662.0,"Position":249.932373,"HyperDash":false},{"StartTime":122715.0,"Position":261.228668,"HyperDash":false},{"StartTime":122767.0,"Position":286.120331,"HyperDash":false},{"StartTime":122819.0,"Position":293.076569,"HyperDash":false},{"StartTime":122871.0,"Position":298.186584,"HyperDash":false},{"StartTime":122960.0,"Position":344.480072,"HyperDash":false}]},{"StartTime":123188.0,"Objects":[{"StartTime":123188.0,"Position":450.0,"HyperDash":false}]},{"StartTime":123415.0,"Objects":[{"StartTime":123415.0,"Position":342.0,"HyperDash":false},{"StartTime":123467.0,"Position":304.888275,"HyperDash":false},{"StartTime":123519.0,"Position":292.63266,"HyperDash":false},{"StartTime":123571.0,"Position":276.625946,"HyperDash":false},{"StartTime":123624.0,"Position":263.464172,"HyperDash":false},{"StartTime":123676.0,"Position":249.683212,"HyperDash":false},{"StartTime":123728.0,"Position":225.779449,"HyperDash":false},{"StartTime":123780.0,"Position":234.624741,"HyperDash":false},{"StartTime":123869.0,"Position":184.843155,"HyperDash":false}]},{"StartTime":124097.0,"Objects":[{"StartTime":124097.0,"Position":52.0,"HyperDash":false}]},{"StartTime":124324.0,"Objects":[{"StartTime":124324.0,"Position":184.0,"HyperDash":false},{"StartTime":124376.0,"Position":195.443817,"HyperDash":false},{"StartTime":124428.0,"Position":216.739166,"HyperDash":false},{"StartTime":124480.0,"Position":252.924118,"HyperDash":false},{"StartTime":124533.0,"Position":262.274628,"HyperDash":false},{"StartTime":124585.0,"Position":290.18573,"HyperDash":false},{"StartTime":124637.0,"Position":307.118835,"HyperDash":false},{"StartTime":124689.0,"Position":304.171661,"HyperDash":false},{"StartTime":124778.0,"Position":341.434662,"HyperDash":false}]},{"StartTime":125006.0,"Objects":[{"StartTime":125006.0,"Position":437.0,"HyperDash":false}]},{"StartTime":125233.0,"Objects":[{"StartTime":125233.0,"Position":474.0,"HyperDash":false},{"StartTime":125328.0,"Position":482.109436,"HyperDash":false},{"StartTime":125460.0,"Position":475.3147,"HyperDash":false}]},{"StartTime":125688.0,"Objects":[{"StartTime":125688.0,"Position":437.0,"HyperDash":false},{"StartTime":125783.0,"Position":440.578949,"HyperDash":false},{"StartTime":125915.0,"Position":435.0608,"HyperDash":false}]},{"StartTime":126142.0,"Objects":[{"StartTime":126142.0,"Position":506.0,"HyperDash":false},{"StartTime":126194.0,"Position":472.674347,"HyperDash":false},{"StartTime":126246.0,"Position":456.3487,"HyperDash":false},{"StartTime":126298.0,"Position":448.023041,"HyperDash":false},{"StartTime":126351.0,"Position":433.344971,"HyperDash":false},{"StartTime":126403.0,"Position":411.019348,"HyperDash":false},{"StartTime":126455.0,"Position":390.693665,"HyperDash":false},{"StartTime":126507.0,"Position":380.368042,"HyperDash":false},{"StartTime":126596.0,"Position":346.003,"HyperDash":false}]},{"StartTime":127052.0,"Objects":[{"StartTime":127052.0,"Position":28.0,"HyperDash":false},{"StartTime":127104.0,"Position":56.3256531,"HyperDash":false},{"StartTime":127156.0,"Position":67.6513062,"HyperDash":false},{"StartTime":127208.0,"Position":71.97695,"HyperDash":false},{"StartTime":127261.0,"Position":111.655014,"HyperDash":false},{"StartTime":127313.0,"Position":136.980667,"HyperDash":false},{"StartTime":127365.0,"Position":146.30632,"HyperDash":false},{"StartTime":127417.0,"Position":174.631958,"HyperDash":false},{"StartTime":127506.0,"Position":187.997025,"HyperDash":false}]},{"StartTime":127733.0,"Objects":[{"StartTime":127733.0,"Position":342.0,"HyperDash":false}]},{"StartTime":127961.0,"Objects":[{"StartTime":127961.0,"Position":226.0,"HyperDash":false},{"StartTime":128017.0,"Position":203.38623,"HyperDash":false},{"StartTime":128074.0,"Position":210.367325,"HyperDash":false},{"StartTime":128131.0,"Position":229.320847,"HyperDash":false},{"StartTime":128188.0,"Position":223.423782,"HyperDash":false},{"StartTime":128283.0,"Position":206.484131,"HyperDash":false},{"StartTime":128415.0,"Position":226.0,"HyperDash":false}]},{"StartTime":128642.0,"Objects":[{"StartTime":128642.0,"Position":302.0,"HyperDash":false}]},{"StartTime":128870.0,"Objects":[{"StartTime":128870.0,"Position":314.0,"HyperDash":false}]},{"StartTime":129097.0,"Objects":[{"StartTime":129097.0,"Position":302.0,"HyperDash":false}]},{"StartTime":129324.0,"Objects":[{"StartTime":129324.0,"Position":314.0,"HyperDash":false}]},{"StartTime":129779.0,"Objects":[{"StartTime":129779.0,"Position":308.0,"HyperDash":false},{"StartTime":129835.0,"Position":334.61377,"HyperDash":false},{"StartTime":129892.0,"Position":326.6327,"HyperDash":false},{"StartTime":129949.0,"Position":328.679138,"HyperDash":false},{"StartTime":130006.0,"Position":310.576233,"HyperDash":false},{"StartTime":130101.0,"Position":331.515869,"HyperDash":false},{"StartTime":130233.0,"Position":308.0,"HyperDash":false}]},{"StartTime":130461.0,"Objects":[{"StartTime":130461.0,"Position":232.0,"HyperDash":false}]},{"StartTime":130688.0,"Objects":[{"StartTime":130688.0,"Position":220.0,"HyperDash":false}]},{"StartTime":130915.0,"Objects":[{"StartTime":130915.0,"Position":232.0,"HyperDash":false}]},{"StartTime":131142.0,"Objects":[{"StartTime":131142.0,"Position":220.0,"HyperDash":false}]},{"StartTime":131597.0,"Objects":[{"StartTime":131597.0,"Position":18.0,"HyperDash":false}]},{"StartTime":132052.0,"Objects":[{"StartTime":132052.0,"Position":278.0,"HyperDash":false}]},{"StartTime":132506.0,"Objects":[{"StartTime":132506.0,"Position":326.0,"HyperDash":false}]},{"StartTime":132961.0,"Objects":[{"StartTime":132961.0,"Position":430.0,"HyperDash":false}]},{"StartTime":133415.0,"Objects":[{"StartTime":133415.0,"Position":358.0,"HyperDash":false}]},{"StartTime":133870.0,"Objects":[{"StartTime":133870.0,"Position":122.0,"HyperDash":false}]},{"StartTime":134324.0,"Objects":[{"StartTime":134324.0,"Position":119.0,"HyperDash":false},{"StartTime":134419.0,"Position":68.98176,"HyperDash":false},{"StartTime":134551.0,"Position":42.8232956,"HyperDash":false}]},{"StartTime":134779.0,"Objects":[{"StartTime":134779.0,"Position":113.0,"HyperDash":false}]},{"StartTime":135233.0,"Objects":[{"StartTime":135233.0,"Position":243.0,"HyperDash":false}]},{"StartTime":135688.0,"Objects":[{"StartTime":135688.0,"Position":251.0,"HyperDash":false}]},{"StartTime":136142.0,"Objects":[{"StartTime":136142.0,"Position":406.0,"HyperDash":false}]},{"StartTime":136597.0,"Objects":[{"StartTime":136597.0,"Position":484.0,"HyperDash":false}]},{"StartTime":137052.0,"Objects":[{"StartTime":137052.0,"Position":352.0,"HyperDash":false}]},{"StartTime":137506.0,"Objects":[{"StartTime":137506.0,"Position":164.0,"HyperDash":false}]},{"StartTime":137961.0,"Objects":[{"StartTime":137961.0,"Position":178.0,"HyperDash":false},{"StartTime":138056.0,"Position":131.390686,"HyperDash":false},{"StartTime":138188.0,"Position":107.408012,"HyperDash":false}]},{"StartTime":138415.0,"Objects":[{"StartTime":138415.0,"Position":129.0,"HyperDash":false}]},{"StartTime":138870.0,"Objects":[{"StartTime":138870.0,"Position":247.0,"HyperDash":false},{"StartTime":138965.0,"Position":268.5533,"HyperDash":false},{"StartTime":139097.0,"Position":323.543732,"HyperDash":false}]},{"StartTime":139324.0,"Objects":[{"StartTime":139324.0,"Position":469.0,"HyperDash":false}]},{"StartTime":139779.0,"Objects":[{"StartTime":139779.0,"Position":309.0,"HyperDash":false},{"StartTime":139874.0,"Position":267.4467,"HyperDash":false},{"StartTime":140006.0,"Position":232.456268,"HyperDash":false}]},{"StartTime":140233.0,"Objects":[{"StartTime":140233.0,"Position":87.0,"HyperDash":false}]},{"StartTime":140688.0,"Objects":[{"StartTime":140688.0,"Position":109.0,"HyperDash":false}]},{"StartTime":140915.0,"Objects":[{"StartTime":140915.0,"Position":241.0,"HyperDash":false}]},{"StartTime":141142.0,"Objects":[{"StartTime":141142.0,"Position":243.0,"HyperDash":false}]},{"StartTime":141370.0,"Objects":[{"StartTime":141370.0,"Position":305.0,"HyperDash":false}]},{"StartTime":141597.0,"Objects":[{"StartTime":141597.0,"Position":349.0,"HyperDash":false}]},{"StartTime":141824.0,"Objects":[{"StartTime":141824.0,"Position":449.0,"HyperDash":false}]},{"StartTime":142052.0,"Objects":[{"StartTime":142052.0,"Position":493.0,"HyperDash":false}]},{"StartTime":142506.0,"Objects":[{"StartTime":142506.0,"Position":401.0,"HyperDash":false},{"StartTime":142562.0,"Position":403.0,"HyperDash":false},{"StartTime":142619.0,"Position":420.0,"HyperDash":false},{"StartTime":142676.0,"Position":407.0,"HyperDash":false},{"StartTime":142733.0,"Position":401.0,"HyperDash":false},{"StartTime":142828.0,"Position":411.0,"HyperDash":false},{"StartTime":142960.0,"Position":401.0,"HyperDash":false}]},{"StartTime":143415.0,"Objects":[{"StartTime":143415.0,"Position":246.0,"HyperDash":false},{"StartTime":143471.0,"Position":242.0,"HyperDash":false},{"StartTime":143528.0,"Position":264.0,"HyperDash":false},{"StartTime":143585.0,"Position":252.0,"HyperDash":false},{"StartTime":143642.0,"Position":246.0,"HyperDash":false},{"StartTime":143737.0,"Position":262.0,"HyperDash":false},{"StartTime":143869.0,"Position":246.0,"HyperDash":false}]},{"StartTime":144324.0,"Objects":[{"StartTime":144324.0,"Position":91.0,"HyperDash":false}]},{"StartTime":144552.0,"Objects":[{"StartTime":144552.0,"Position":45.0,"HyperDash":false}]},{"StartTime":144779.0,"Objects":[{"StartTime":144779.0,"Position":135.0,"HyperDash":false}]},{"StartTime":145006.0,"Objects":[{"StartTime":145006.0,"Position":45.0,"HyperDash":false}]},{"StartTime":145233.0,"Objects":[{"StartTime":145233.0,"Position":133.0,"HyperDash":false}]},{"StartTime":145688.0,"Objects":[{"StartTime":145688.0,"Position":337.0,"HyperDash":false}]},{"StartTime":145915.0,"Objects":[{"StartTime":145915.0,"Position":277.0,"HyperDash":false}]},{"StartTime":146142.0,"Objects":[{"StartTime":146142.0,"Position":386.0,"HyperDash":false}]},{"StartTime":146597.0,"Objects":[{"StartTime":146597.0,"Position":406.0,"HyperDash":false}]},{"StartTime":146824.0,"Objects":[{"StartTime":146824.0,"Position":320.0,"HyperDash":false}]},{"StartTime":147051.0,"Objects":[{"StartTime":147051.0,"Position":378.0,"HyperDash":false}]},{"StartTime":147506.0,"Objects":[{"StartTime":147506.0,"Position":320.0,"HyperDash":false}]},{"StartTime":147733.0,"Objects":[{"StartTime":147733.0,"Position":282.0,"HyperDash":false},{"StartTime":147828.0,"Position":269.560242,"HyperDash":false},{"StartTime":147960.0,"Position":205.662415,"HyperDash":false}]},{"StartTime":148415.0,"Objects":[{"StartTime":148415.0,"Position":234.0,"HyperDash":false},{"StartTime":148510.0,"Position":236.789261,"HyperDash":false},{"StartTime":148642.0,"Position":226.947067,"HyperDash":false}]},{"StartTime":148870.0,"Objects":[{"StartTime":148870.0,"Position":194.0,"HyperDash":false}]},{"StartTime":149324.0,"Objects":[{"StartTime":149324.0,"Position":88.0,"HyperDash":false},{"StartTime":149380.0,"Position":61.61062,"HyperDash":false},{"StartTime":149437.0,"Position":75.7172852,"HyperDash":false},{"StartTime":149494.0,"Position":64.72825,"HyperDash":false},{"StartTime":149551.0,"Position":71.9050446,"HyperDash":false},{"StartTime":149646.0,"Position":67.47814,"HyperDash":false},{"StartTime":149778.0,"Position":88.0,"HyperDash":false}]},{"StartTime":150233.0,"Objects":[{"StartTime":150233.0,"Position":120.0,"HyperDash":false},{"StartTime":150289.0,"Position":137.763626,"HyperDash":false},{"StartTime":150346.0,"Position":141.788849,"HyperDash":false},{"StartTime":150403.0,"Position":166.251251,"HyperDash":false},{"StartTime":150460.0,"Position":185.8204,"HyperDash":false},{"StartTime":150555.0,"Position":158.755432,"HyperDash":false},{"StartTime":150687.0,"Position":120.0,"HyperDash":false}]},{"StartTime":151142.0,"Objects":[{"StartTime":151142.0,"Position":276.0,"HyperDash":false},{"StartTime":151198.0,"Position":313.273468,"HyperDash":false},{"StartTime":151255.0,"Position":314.899536,"HyperDash":false},{"StartTime":151312.0,"Position":331.123352,"HyperDash":false},{"StartTime":151369.0,"Position":346.809448,"HyperDash":false},{"StartTime":151464.0,"Position":327.8075,"HyperDash":false},{"StartTime":151596.0,"Position":276.0,"HyperDash":false}]},{"StartTime":152051.0,"Objects":[{"StartTime":152051.0,"Position":384.0,"HyperDash":false},{"StartTime":152146.0,"Position":373.33017,"HyperDash":false},{"StartTime":152278.0,"Position":375.168457,"HyperDash":false}]},{"StartTime":152506.0,"Objects":[{"StartTime":152506.0,"Position":256.0,"HyperDash":false}]},{"StartTime":152733.0,"Objects":[{"StartTime":152733.0,"Position":218.0,"HyperDash":false}]},{"StartTime":152961.0,"Objects":[{"StartTime":152961.0,"Position":100.0,"HyperDash":false}]},{"StartTime":153188.0,"Objects":[{"StartTime":153188.0,"Position":104.0,"HyperDash":false}]},{"StartTime":153415.0,"Objects":[{"StartTime":153415.0,"Position":60.0,"HyperDash":false}]},{"StartTime":153870.0,"Objects":[{"StartTime":153870.0,"Position":241.0,"HyperDash":false},{"StartTime":153965.0,"Position":262.296783,"HyperDash":false},{"StartTime":154097.0,"Position":297.158661,"HyperDash":false}]},{"StartTime":154324.0,"Objects":[{"StartTime":154324.0,"Position":311.0,"HyperDash":false}]},{"StartTime":154779.0,"Objects":[{"StartTime":154779.0,"Position":365.0,"HyperDash":false},{"StartTime":154835.0,"Position":380.953857,"HyperDash":false},{"StartTime":154892.0,"Position":377.488251,"HyperDash":false},{"StartTime":154949.0,"Position":393.8036,"HyperDash":false},{"StartTime":155006.0,"Position":430.5609,"HyperDash":false},{"StartTime":155101.0,"Position":417.3473,"HyperDash":false},{"StartTime":155233.0,"Position":365.0,"HyperDash":false}]},{"StartTime":155688.0,"Objects":[{"StartTime":155688.0,"Position":179.0,"HyperDash":false}]},{"StartTime":155915.0,"Objects":[{"StartTime":155915.0,"Position":285.0,"HyperDash":false}]},{"StartTime":156142.0,"Objects":[{"StartTime":156142.0,"Position":154.0,"HyperDash":false}]},{"StartTime":156597.0,"Objects":[{"StartTime":156597.0,"Position":26.0,"HyperDash":false}]},{"StartTime":156824.0,"Objects":[{"StartTime":156824.0,"Position":166.0,"HyperDash":false},{"StartTime":156919.0,"Position":196.995117,"HyperDash":false},{"StartTime":157051.0,"Position":244.69249,"HyperDash":false}]},{"StartTime":157506.0,"Objects":[{"StartTime":157506.0,"Position":305.0,"HyperDash":false},{"StartTime":157601.0,"Position":339.2251,"HyperDash":false},{"StartTime":157733.0,"Position":383.441528,"HyperDash":false}]},{"StartTime":157961.0,"Objects":[{"StartTime":157961.0,"Position":461.0,"HyperDash":false}]},{"StartTime":158415.0,"Objects":[{"StartTime":158415.0,"Position":279.0,"HyperDash":false}]},{"StartTime":158642.0,"Objects":[{"StartTime":158642.0,"Position":370.0,"HyperDash":false}]},{"StartTime":158870.0,"Objects":[{"StartTime":158870.0,"Position":353.0,"HyperDash":false}]},{"StartTime":159324.0,"Objects":[{"StartTime":159324.0,"Position":140.0,"HyperDash":false}]},{"StartTime":159551.0,"Objects":[{"StartTime":159551.0,"Position":320.0,"HyperDash":false}]},{"StartTime":159779.0,"Objects":[{"StartTime":159779.0,"Position":399.0,"HyperDash":false}]},{"StartTime":160006.0,"Objects":[{"StartTime":160006.0,"Position":320.0,"HyperDash":false}]},{"StartTime":160233.0,"Objects":[{"StartTime":160233.0,"Position":255.0,"HyperDash":false},{"StartTime":160328.0,"Position":225.620453,"HyperDash":false},{"StartTime":160460.0,"Position":209.024933,"HyperDash":false}]},{"StartTime":160688.0,"Objects":[{"StartTime":160688.0,"Position":187.0,"HyperDash":false}]},{"StartTime":161142.0,"Objects":[{"StartTime":161142.0,"Position":354.0,"HyperDash":false},{"StartTime":161237.0,"Position":355.953247,"HyperDash":false},{"StartTime":161369.0,"Position":320.988251,"HyperDash":false}]},{"StartTime":161597.0,"Objects":[{"StartTime":161597.0,"Position":207.0,"HyperDash":false}]},{"StartTime":162051.0,"Objects":[{"StartTime":162051.0,"Position":43.0,"HyperDash":false}]},{"StartTime":162279.0,"Objects":[{"StartTime":162279.0,"Position":119.0,"HyperDash":false},{"StartTime":162374.0,"Position":150.19606,"HyperDash":false},{"StartTime":162506.0,"Position":180.9159,"HyperDash":false}]},{"StartTime":162961.0,"Objects":[{"StartTime":162961.0,"Position":195.0,"HyperDash":false},{"StartTime":163056.0,"Position":148.134537,"HyperDash":false},{"StartTime":163188.0,"Position":125.699371,"HyperDash":false}]},{"StartTime":163415.0,"Objects":[{"StartTime":163415.0,"Position":266.0,"HyperDash":false}]},{"StartTime":163870.0,"Objects":[{"StartTime":163870.0,"Position":337.0,"HyperDash":false},{"StartTime":163926.0,"Position":340.576416,"HyperDash":false},{"StartTime":163983.0,"Position":358.8032,"HyperDash":false},{"StartTime":164040.0,"Position":399.717,"HyperDash":false},{"StartTime":164097.0,"Position":413.786346,"HyperDash":false},{"StartTime":164192.0,"Position":398.392517,"HyperDash":false},{"StartTime":164324.0,"Position":337.0,"HyperDash":false}]},{"StartTime":164779.0,"Objects":[{"StartTime":164779.0,"Position":365.0,"HyperDash":false},{"StartTime":164874.0,"Position":341.216339,"HyperDash":false},{"StartTime":165006.0,"Position":289.0602,"HyperDash":false}]},{"StartTime":165233.0,"Objects":[{"StartTime":165233.0,"Position":164.0,"HyperDash":false}]},{"StartTime":165688.0,"Objects":[{"StartTime":165688.0,"Position":420.0,"HyperDash":false}]},{"StartTime":165915.0,"Objects":[{"StartTime":165915.0,"Position":347.0,"HyperDash":false},{"StartTime":166010.0,"Position":378.42804,"HyperDash":false},{"StartTime":166142.0,"Position":365.11972,"HyperDash":false}]},{"StartTime":166597.0,"Objects":[{"StartTime":166597.0,"Position":86.0,"HyperDash":false}]},{"StartTime":166824.0,"Objects":[{"StartTime":166824.0,"Position":212.0,"HyperDash":false}]},{"StartTime":167051.0,"Objects":[{"StartTime":167051.0,"Position":74.0,"HyperDash":false},{"StartTime":167107.0,"Position":65.55724,"HyperDash":false},{"StartTime":167164.0,"Position":36.62049,"HyperDash":false},{"StartTime":167278.0,"Position":74.0,"HyperDash":false}]},{"StartTime":167506.0,"Objects":[{"StartTime":167506.0,"Position":244.0,"HyperDash":false}]},{"StartTime":167733.0,"Objects":[{"StartTime":167733.0,"Position":166.0,"HyperDash":false}]},{"StartTime":167961.0,"Objects":[{"StartTime":167961.0,"Position":274.0,"HyperDash":false},{"StartTime":168013.0,"Position":301.951935,"HyperDash":false},{"StartTime":168065.0,"Position":319.251465,"HyperDash":false},{"StartTime":168117.0,"Position":329.4265,"HyperDash":false},{"StartTime":168170.0,"Position":343.4541,"HyperDash":false},{"StartTime":168222.0,"Position":376.318848,"HyperDash":false},{"StartTime":168274.0,"Position":385.979645,"HyperDash":false},{"StartTime":168326.0,"Position":398.919922,"HyperDash":false},{"StartTime":168415.0,"Position":410.559265,"HyperDash":false}]},{"StartTime":168642.0,"Objects":[{"StartTime":168642.0,"Position":266.0,"HyperDash":false}]},{"StartTime":168870.0,"Objects":[{"StartTime":168870.0,"Position":262.0,"HyperDash":false},{"StartTime":168922.0,"Position":264.549957,"HyperDash":false},{"StartTime":168974.0,"Position":244.610046,"HyperDash":false},{"StartTime":169026.0,"Position":261.6293,"HyperDash":false},{"StartTime":169079.0,"Position":278.3733,"HyperDash":false},{"StartTime":169131.0,"Position":294.3729,"HyperDash":false},{"StartTime":169183.0,"Position":290.6487,"HyperDash":false},{"StartTime":169235.0,"Position":313.168427,"HyperDash":false},{"StartTime":169324.0,"Position":333.1015,"HyperDash":false}]},{"StartTime":169551.0,"Objects":[{"StartTime":169551.0,"Position":391.0,"HyperDash":false}]},{"StartTime":169779.0,"Objects":[{"StartTime":169779.0,"Position":340.0,"HyperDash":false},{"StartTime":169831.0,"Position":319.4877,"HyperDash":false},{"StartTime":169883.0,"Position":321.416565,"HyperDash":false},{"StartTime":169935.0,"Position":292.492767,"HyperDash":false},{"StartTime":169988.0,"Position":257.8616,"HyperDash":false},{"StartTime":170040.0,"Position":232.613708,"HyperDash":false},{"StartTime":170092.0,"Position":235.74971,"HyperDash":false},{"StartTime":170144.0,"Position":224.646179,"HyperDash":false},{"StartTime":170233.0,"Position":191.472458,"HyperDash":false}]},{"StartTime":170461.0,"Objects":[{"StartTime":170461.0,"Position":300.0,"HyperDash":false}]},{"StartTime":170688.0,"Objects":[{"StartTime":170688.0,"Position":319.0,"HyperDash":false},{"StartTime":170740.0,"Position":317.817749,"HyperDash":false},{"StartTime":170792.0,"Position":324.806122,"HyperDash":false},{"StartTime":170844.0,"Position":327.30014,"HyperDash":false},{"StartTime":170897.0,"Position":341.6977,"HyperDash":false},{"StartTime":170949.0,"Position":331.214264,"HyperDash":false},{"StartTime":171001.0,"Position":340.934967,"HyperDash":false},{"StartTime":171053.0,"Position":335.773926,"HyperDash":false},{"StartTime":171142.0,"Position":303.6745,"HyperDash":false}]},{"StartTime":171370.0,"Objects":[{"StartTime":171370.0,"Position":157.0,"HyperDash":false}]},{"StartTime":171597.0,"Objects":[{"StartTime":171597.0,"Position":184.0,"HyperDash":false},{"StartTime":171649.0,"Position":177.816864,"HyperDash":false},{"StartTime":171701.0,"Position":167.695633,"HyperDash":false},{"StartTime":171753.0,"Position":168.327789,"HyperDash":false},{"StartTime":171806.0,"Position":156.99791,"HyperDash":false},{"StartTime":171858.0,"Position":147.82634,"HyperDash":false},{"StartTime":171910.0,"Position":166.402451,"HyperDash":false},{"StartTime":171962.0,"Position":147.244476,"HyperDash":false},{"StartTime":172051.0,"Position":180.821411,"HyperDash":false}]},{"StartTime":172279.0,"Objects":[{"StartTime":172279.0,"Position":296.0,"HyperDash":false}]},{"StartTime":172506.0,"Objects":[{"StartTime":172506.0,"Position":366.0,"HyperDash":false}]},{"StartTime":172961.0,"Objects":[{"StartTime":172961.0,"Position":296.0,"HyperDash":false}]},{"StartTime":173188.0,"Objects":[{"StartTime":173188.0,"Position":272.0,"HyperDash":false}]},{"StartTime":173415.0,"Objects":[{"StartTime":173415.0,"Position":216.0,"HyperDash":false},{"StartTime":173467.0,"Position":213.8114,"HyperDash":false},{"StartTime":173519.0,"Position":176.041458,"HyperDash":false},{"StartTime":173571.0,"Position":162.964,"HyperDash":false},{"StartTime":173624.0,"Position":126.355881,"HyperDash":false},{"StartTime":173676.0,"Position":137.035782,"HyperDash":false},{"StartTime":173728.0,"Position":92.75827,"HyperDash":false},{"StartTime":173780.0,"Position":98.66459,"HyperDash":false},{"StartTime":173869.0,"Position":60.0903053,"HyperDash":false}]},{"StartTime":174097.0,"Objects":[{"StartTime":174097.0,"Position":156.0,"HyperDash":false}]},{"StartTime":174324.0,"Objects":[{"StartTime":174324.0,"Position":150.0,"HyperDash":false}]},{"StartTime":174438.0,"Objects":[{"StartTime":174438.0,"Position":156.0,"HyperDash":false}]},{"StartTime":174551.0,"Objects":[{"StartTime":174551.0,"Position":150.0,"HyperDash":false}]}]} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/75858.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/75858.osu new file mode 100644 index 0000000000..637273efad --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/75858.osu @@ -0,0 +1,417 @@ +osu file format v8 + +[General] +StackLeniency: 0.6 +Mode: 0 + +[Difficulty] +HPDrainRate:5 +CircleSize:4 +OverallDifficulty:7 +ApproachRate:7 +SliderMultiplier:1.6 +SliderTickRate:0.5 + +[Events] +//Background and Video events +//Break Periods +2,38388,45242 +2,89297,92515 +2,107705,114333 +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Sound Samples +//Background Colour Transformations +3,100,163,162,255 + +[TimingPoints] +1173,666.666666666667,4,2,1,60,1,0 +2173,-100,4,2,1,50,0,0 +2839,-100,4,2,1,60,0,0 +4839,-100,4,2,1,50,0,0 +5506,-100,4,2,1,60,0,0 +7506,-100,4,2,1,50,0,0 +8173,-100,4,2,1,60,0,0 +10673,-200,4,2,1,60,0,0 +11173,-200,4,2,1,10,0,0 +11673,-100,4,2,1,60,0,0 +12839,-100,4,2,1,50,0,0 +13506,-100,4,2,1,60,0,0 +15506,-100,4,2,1,50,0,0 +16173,-100,4,2,1,60,0,0 +16839,-100,4,2,1,50,0,0 +17506,-100,4,2,1,60,0,0 +19506,-100,4,2,1,70,0,0 +20006,-100,4,2,1,30,0,0 +22052,454.545454545455,4,2,1,40,1,0 +23642,-100,4,2,2,70,0,0 +23870,-100,4,2,2,70,0,1 +38415,-100,4,2,2,70,0,0 +45915,-100,4,2,2,60,0,0 +52733,-100,4,2,2,25,0,0 +53188,-100,4,2,2,60,0,0 +60006,-100,4,2,2,25,0,0 +60460,-100,4,2,1,45,0,0 +67620,-100,4,2,1,50,0,0 +71483,-100,4,2,1,55,0,0 +74267,-100,4,2,1,70,0,0 +74665,-100,4,2,2,80,0,0 +74779,-100,4,2,2,80,0,1 +89324,-100,4,2,2,80,0,0 +92961,-100,4,2,2,65,0,0 +107279,-100,4,2,1,40,0,0 +107733,-100,4,2,1,45,0,0 +126029,-100,4,2,1,50,0,0 +128813,-100,4,2,1,60,0,0 +129211,-100,4,2,1,70,0,0 +129438,-100,4,2,1,55,0,0 +130631,-100,4,2,1,65,0,0 +131029,-100,4,2,1,75,0,0 +131370,-100,4,2,2,65,0,0 +145461,-100,4,2,2,75,0,0 +145688,-100,4,2,2,75,0,1 +160120,-100,4,2,2,80,0,0 +160233,-100,4,2,2,80,0,1 +174779,-100,4,2,2,80,0,0 + +[HitObjects] +94,279,1173,2,0,B|125:307|190:315|253:298,1,160,8|2 +398,247,2506,1,0 +471,104,3172,1,2 +320,51,3839,6,0,B|275:33|209:34|165:67,1,160,8|2 +65,190,5173,1,0 +149,325,5839,1,2 +239,192,6506,6,0,B|295:173|352:207|417:188,1,160,4|2 +493,320,7839,1,0 +334,340,8506,1,2 +199,253,9173,5,0 +171,95,9839,1,2 +271,219,10506,1,0 +199,253,10839,2,0,B|161:276|115:272,1,80,2|0 +42,266,11839,5,4 +110,121,12506,1,2 +263,168,13172,2,0,B|305:186|378:185|423:172,1,160,0|2 +293,75,14506,6,0,B|276:121|216:147|156:149,1,160,8|2 +282,251,15839,2,0,B|299:297|359:323|419:325,1,160,0|2 +416,164,17172,1,4 +256,148,17839,2,0,B|172:136|172:136|230:189|198:266|154:289|154:289|85:276,1,320,2|2 +256,148,19839,1,4 +256,192,20173,12,8,22506 +256,192,22961,5,8 +256,192,23415,1,8 +104,245,23870,6,0,B|132:284|196:282,2,80,2|0|2 +256,192,24779,2,0,B|314:192|348:238,2,80,2|0|2 +118,111,25688,2,0,B|165:91|229:112,1,80,0|2 +275,113,26142,1,0 +419,185,26597,2,0,B|383:204|338:204|337:204,1,80,2|0 +261,196,27052,1,2 +128,285,27506,5,0 +97,211,27733,2,0,B|82:168|96:131,1,80,2|0 +236,56,28415,1,0 +313,77,28642,2,0,B|356:91|411:84,1,80,0|2 +456,232,29324,1,0 +456,232,29552,1,2 +456,232,29779,1,0 +311,299,30233,1,0 +231,312,30461,1,2 +151,300,30688,2,0,B|98:292,2,40,0|0|0 +231,312,31142,5,2 +202,236,31370,1,0 +194,156,31597,2,0,B|188:101|218:47|274:27,1,160,2|2 +295,104,32279,1,0 +273,181,32506,2,0,B|218:179|150:144|144:96,1,160,2|0 +219,72,33188,1,2 +295,104,33415,2,0,B|252:152|265:239|328:269,1,160 +367,205,34097,1,0 +372,125,34324,2,0,B|323:95|242:92|191:148,1,160,2|0 +154,170,35006,1,2 +107,234,35233,6,0,B|134:267|226:268|262:230,1,160,0|2 +316,183,35915,1,0 +350,111,36142,1,2 +350,111,36597,1,0 +393,178,36824,1,2 +406,257,37051,2,0,B|402:303|344:360|271:363,1,160,0|2 +216,350,37733,1,0 +154,298,37961,5,2 +154,298,38074,1,2 +154,298,38188,1,2 +105,136,46142,6,0,B|125:91|191:64|257:74,1,160,0|2 +399,102,47051,2,0,B|486:117|485:117,2,80,0|2|0 +422,260,47961,2,0,B|402:305|336:332|270:322,1,160,0|2 +128,294,48870,2,0,B|41:279|42:279,2,80,0|2|0 +252,193,49779,6,0,B|297:168|358:163|436:186,1,160,0|2 +342,324,50688,1,0 +377,252,50915,2,0,B|335:241|293:260,1,80,2|0 +227,293,51370,1,0 +159,335,51597,2,0,B|118:354|78:347,1,80,2|0 +107,271,52051,2,0,B|56:280|16:255,1,80,2|0 +75,196,52506,2,0,B|132:204|191:190|229:151,1,160,4|0 +321,27,53415,5,2 +321,27,53642,1,0 +321,27,53870,2,0,B|376:37|403:124|352:180,1,160 +331,230,54551,1,2 +266,276,54779,1,0 +266,276,55233,5,2 +266,276,55461,1,0 +266,276,55688,2,0,B|208:296|133:275|108:219,1,160 +89,164,56370,1,2 +99,84,56597,1,0 +99,84,57051,5,2 +99,84,57279,1,0 +99,84,57506,2,0,B|128:116|201:127|254:108,1,160 +326,84,58188,1,2 +382,27,58415,1,0 +401,104,58642,2,0,B|392:148|345:160,1,80,0|2 +274,188,59097,1,0 +337,236,59324,2,0,B|374:265|364:310,1,80,2|0 +284,298,59779,2,0,B|243:334|169:279|128:318,1,160,4|0 +41,182,60688,5,0 +191,127,61142,2,0,B|276:94,1,80,2|0 +254,177,61597,2,0,B|339:144,1,80 +319,227,62051,1,2 +319,227,62279,2,0,B|234:260,1,80,0|8 +168,281,62733,1,0 +91,305,62961,1,2 +31,252,63188,1,0 +31,172,63415,2,0,B|31:88,2,80,0|2|2 +181,116,64324,5,8 +335,74,64779,2,0,B|335:162,1,80,2|0 +405,116,65233,2,0,B|405:198,1,80 +475,157,65688,1,2 +475,157,65915,2,0,B|475:69,1,80,0|8 +405,37,66370,1,0 +325,26,66597,1,2 +252,60,66824,1,8 +204,124,67051,1,0 +189,202,67279,1,2 +202,280,67506,1,10 +250,343,67733,1,0 +329,332,67961,6,0,B|432:315,1,80,0|8 +427,241,68415,2,0,B|324:258,1,80,2|8 +303,187,68870,2,0,B|406:170,1,80,0|8 +401,96,69324,2,0,B|298:113,1,80,2|8 +242,122,69779,5,0 +242,122,70006,1,8 +163,135,70233,1,2 +163,135,70461,1,8 +84,150,70688,2,0,B|60:195|95:243,3,80,0|2|2|0 +148,275,71597,6,0,B|180:305|252:312|305:295,1,160,4|10 +374,86,72506,2,0,B|342:56|270:49|217:66,1,160,4|10 +147,97,73188,1,0 +213,141,73415,2,0,B|286:189,1,80,8|2 +346,229,73870,2,0,B|282:313,1,80,8|2 +252,358,74324,1,10 +252,358,74551,1,10 +252,358,74779,6,0,B|208:373|169:356,2,80,0|0|2 +194,208,75688,2,0,B|150:193|111:210,2,80,2|0|2 +347,252,76597,1,0 +347,252,76824,1,2 +347,252,77051,1,0 +448,128,77506,1,0 +368,117,77733,1,0 +305,67,77961,1,2 +146,87,78415,5,0 +118,161,78642,2,0,B|99:205|41:224,1,80,2|0 +218,249,79324,2,0,B|252:272|301:266,1,80 +372,247,79779,1,2 +286,112,80233,2,0,B|282:23,2,80,0|2|0 +427,186,81142,1,0 +427,186,81370,1,2 +427,186,81597,2,0,B|431:244,2,40 +421,105,82051,5,0 +356,152,82279,1,0 +285,188,82506,2,0,B|236:212|160:202|130:174,1,160,2|2 +188,119,83188,1,0 +267,110,83415,2,0,B|303:160|289:236|225:276,1,160,2|0 +193,198,84097,1,2 +188,119,84324,2,0,B|240:128|312:104|337:51,1,160 +257,29,85006,1,0 +177,39,85233,2,0,B|160:93|191:163|284:166,1,160,2|0 +326,183,85915,1,2 +404,197,86142,6,0,B|455:212|468:261|448:314|380:320,1,160 +326,330,86824,1,0 +246,322,87051,1,2 +246,322,87506,1,0 +192,262,87733,1,2 +168,185,87961,2,0,B|148:132|174:73|235:44,1,160,0|2 +299,23,88642,1,0 +378,36,88870,5,2 +378,36,88983,1,2 +378,36,89097,1,2 +330,47,93415,6,0,B|388:28|453:36,2,120,2|0|2 +254,74,94324,1,0 +181,108,94552,2,0,B|129:134,1,40 +181,107,95233,6,0,B|123:88|58:96,2,120,2|0|2 +257,134,96142,1,0 +330,168,96370,2,0,B|382:194,1,40 +330,168,97052,6,0,B|388:149|453:157,2,120,2|0|2 +254,195,97961,1,0 +181,229,98188,2,0,B|129:255,1,40 +181,228,98870,6,0,B|123:209|58:217,2,120,2|0|2 +257,255,99779,1,0 +330,289,100006,2,0,B|382:315,1,40 +454,74,100688,6,0,B|403:83,1,40,2|0 +335,95,101029,2,0,B|270:105,1,40,2|0 +216,114,101370,1,0 +137,127,101597,2,0,B|89:139|30:126,1,80,2|0 +57,130,101938,1,0 +57,130,102506,6,0,B|108:139,1,40,2|0 +176,151,102847,2,0,B|240:161,1,40,2|0 +295,170,103188,1,0 +374,183,103415,2,0,B|422:195|481:182,1,80,2|0 +454,187,103756,1,0 +454,187,104324,6,0,B|403:196,1,40,2|0 +335,208,104665,2,0,B|270:218,1,40,2|0 +216,227,105006,1,0 +176,234,105120,1,0 +137,240,105233,2,0,B|89:252|30:239,1,80,2|0 +57,244,105574,1,0 +57,244,106142,6,0,B|108:253,1,40,2|0 +176,265,106483,2,0,B|240:275,1,40,2|0 +295,284,106824,2,0,B|320:300|372:301|408:283|408:283|371:256|318:256|291:287,1,240,0|12 +114,269,115233,6,0,B|145:293|228:297|281:282,1,160,0|2 +347,264,115915,1,0 +419,230,116142,2,0,B|452:197|450:147,2,80,0|0|2 +366,78,117052,2,0,B|330:116|275:110,1,80,8|0 +216,99,117506,1,2 +149,54,117733,1,0 +84,102,117961,2,0,B|84:216,3,80,0|2|2|0 +85,262,118870,5,8 +155,299,119097,1,0 +225,261,119324,1,2 +296,297,119552,1,0 +368,263,119779,2,0,B|411:250|461:267,2,80,0|0|2 +434,117,120688,1,0 +364,77,120915,1,8 +286,58,121142,2,0,B|229:48|161:67|126:113,1,160,2|0 +102,172,121824,1,10 +102,252,122052,2,0,B|104:301|150:325|152:324,1,80,2|0 +187,253,122506,6,0,B|228:231|312:284|368:259,1,160,8|2 +409,217,123188,1,0 +342,172,123415,2,0,B|297:185|225:140|184:159,1,160,0|2 +118,114,124097,1,0 +184,70,124324,2,0,B|226:47|319:101|365:76,1,160,8|2 +401,29,125006,1,0 +474,59,125233,2,0,B|496:100|472:142,1,80,0|2 +437,206,125688,2,0,B|415:251|442:297,1,80,2|0 +506,246,126142,6,0,B|342:247,1,160,0|10 +28,229,127052,2,0,B|192:228,1,160,0|10 +267,228,127733,1,0 +226,297,127961,2,0,B|202:340|232:391,2,80,8|0|10 +267,228,128642,1,0 +308,159,128870,1,8 +308,159,129097,1,10 +308,159,129324,1,4 +308,159,129779,6,0,B|332:202|302:253,2,80,10|0|10 +267,90,130461,1,0 +226,21,130688,1,10 +226,21,130915,1,10 +226,21,131142,1,6 +119,140,131597,5,2 +148,297,132052,1,0 +302,338,132506,1,2 +430,242,132961,1,0 +394,86,133415,1,2 +240,40,133870,1,0 +119,140,134324,2,0,B|81:168|17:153,1,80,2|0 +65,80,134779,1,0 +178,192,135233,5,2 +247,336,135688,1,0 +406,343,136142,1,2 +484,203,136597,1,0 +418,57,137052,1,2 +258,52,137506,1,0 +178,192,137961,2,0,B|141:228|91:227,1,80,2|2 +110,146,138415,1,0 +247,228,138870,6,0,B|282:250|337:247,1,80,2|0 +403,246,139324,1,0 +309,115,139779,2,0,B|274:93|219:96,1,80,2|0 +153,97,140233,1,0 +98,247,140688,1,2 +175,265,140915,1,0 +242,221,141142,1,2 +274,147,141370,1,0 +327,87,141597,1,2 +399,52,141824,1,0 +471,86,142052,1,0 +401,230,142506,6,0,B|401:323,2,80,2|0|0 +246,272,143415,2,0,B|246:365,2,80,2|0|0 +91,314,144324,1,2 +45,247,144552,1,0 +90,181,144779,1,2 +45,114,145006,1,0 +89,47,145233,1,2 +235,112,145688,5,0 +307,146,145915,1,0 +386,139,146142,1,2 +386,139,146597,1,2 +353,211,146824,1,0 +349,291,147051,1,2 +349,291,147506,1,0 +282,246,147733,2,0,B|245:222|179:226,1,80,2|0 +234,70,148415,2,0,B|247:122|216:167,1,80,2|0 +205,225,148870,1,2 +88,116,149324,6,0,B|56:159|77:205,2,80,0|2|0 +120,272,150233,2,0,B|139:307|193:313,2,80,2|0|2 +276,304,151142,2,0,B|324:298|364:252,2,80,0|2|0 +384,185,152051,2,0,B|399:140|372:104,1,80,0|2 +314,56,152506,1,0 +237,34,152733,1,0 +159,54,152961,5,2 +102,110,153188,1,0 +82,187,153415,1,2 +241,172,153870,2,0,B|296:192|303:250,1,80 +307,304,154324,1,2 +365,155,154779,2,0,B|389:116|435:115,2,80,0|2|0 +307,304,155688,1,2 +232,334,155915,1,0 +154,315,156142,1,2 +90,167,156597,5,0 +166,189,156824,2,0,B|211:202|257:182,1,80,2|0 +305,38,157506,2,0,B|345:21|392:34,1,80,2|0 +461,50,157961,1,2 +370,181,158415,1,0 +370,181,158642,1,2 +370,181,158870,1,0 +255,292,159324,1,0 +320,337,159551,1,2 +399,341,159779,5,2 +320,337,160006,1,0 +255,292,160233,2,0,B|209:264|205:203,1,80,2|0 +196,149,160688,1,2 +354,171,161142,2,0,B|352:219|305:256,1,80 +256,290,161597,1,2 +125,197,162051,1,0 +119,117,162279,2,0,B|138:78|187:70,1,80,2|0 +195,230,162961,2,0,B|143:232|114:179,1,80,2|0 +190,150,163415,1,2 +337,86,163870,6,0,B|372:64|421:70,2,80,0|2|0 +365,243,164779,2,0,B|328:272|260:256,1,80,2|0 +212,239,165233,1,2 +292,111,165688,1,0 +347,168,165915,2,0,B|377:201|362:257,1,80,2|0 +224,320,166597,1,0 +149,292,166824,1,2 +74,261,167051,2,0,B|32:245,2,40 +138,213,167506,5,2 +205,169,167733,1,0 +274,129,167961,2,0,B|328:113|400:144|414:196,1,160,2|0 +340,224,168642,1,0 +262,204,168870,2,0,B|249:152|288:80|343:74,1,160,2|0 +367,148,169551,1,2 +340,224,169779,2,0,B|298:191|219:196|180:244,1,160,0|2 +240,295,170461,1,0 +319,301,170688,2,0,B|355:264|345:184|301:156,1,160,2|0 +229,127,171370,1,2 +184,60,171597,6,0,B|131:94|134:176|208:218,1,160 +252,234,172279,1,0 +331,241,172506,1,2 +331,241,172961,1,0 +284,306,173188,1,2 +216,348,173415,2,0,B|171:368|94:370|56:347,1,160,0|2 +106,283,174097,1,0 +153,218,174324,5,2 +153,218,174438,1,2 +153,218,174551,1,2 diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/871815-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/871815-expected-conversion.json new file mode 100644 index 0000000000..c8ebf04ca4 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/871815-expected-conversion.json @@ -0,0 +1 @@ +{"Mappings":[{"StartTime":1459.0,"Objects":[{"StartTime":1459.0,"Position":150.0,"HyperDash":false}]},{"StartTime":1809.0,"Objects":[{"StartTime":1809.0,"Position":150.0,"HyperDash":false},{"StartTime":1896.0,"Position":172.185562,"HyperDash":false},{"StartTime":1984.0,"Position":224.798538,"HyperDash":false},{"StartTime":2072.0,"Position":262.41153,"HyperDash":false}]},{"StartTime":2160.0,"Objects":[{"StartTime":2160.0,"Position":281.0,"HyperDash":false},{"StartTime":2229.0,"Position":285.0371,"HyperDash":false},{"StartTime":2335.0,"Position":285.094574,"HyperDash":false}]},{"StartTime":2511.0,"Objects":[{"StartTime":2511.0,"Position":272.0,"HyperDash":false}]},{"StartTime":2687.0,"Objects":[{"StartTime":2687.0,"Position":367.0,"HyperDash":false},{"StartTime":2774.0,"Position":395.071045,"HyperDash":false},{"StartTime":2862.0,"Position":385.323761,"HyperDash":false},{"StartTime":2931.0,"Position":384.634644,"HyperDash":false},{"StartTime":3037.0,"Position":371.162445,"HyperDash":false}]},{"StartTime":3213.0,"Objects":[{"StartTime":3213.0,"Position":278.0,"HyperDash":false}]},{"StartTime":3388.0,"Objects":[{"StartTime":3388.0,"Position":113.0,"HyperDash":false}]},{"StartTime":3476.0,"Objects":[{"StartTime":3476.0,"Position":116.0,"HyperDash":false}]},{"StartTime":3564.0,"Objects":[{"StartTime":3564.0,"Position":121.0,"HyperDash":false},{"StartTime":3607.0,"Position":121.720619,"HyperDash":false},{"StartTime":3651.0,"Position":121.0,"HyperDash":false},{"StartTime":3695.0,"Position":121.720619,"HyperDash":false},{"StartTime":3739.0,"Position":121.0,"HyperDash":false},{"StartTime":3783.0,"Position":121.720619,"HyperDash":false},{"StartTime":3827.0,"Position":121.0,"HyperDash":false},{"StartTime":3871.0,"Position":121.720619,"HyperDash":false},{"StartTime":3914.0,"Position":121.0,"HyperDash":false}]},{"StartTime":4266.0,"Objects":[{"StartTime":4266.0,"Position":368.0,"HyperDash":false},{"StartTime":4353.0,"Position":394.044952,"HyperDash":false},{"StartTime":4441.0,"Position":430.602478,"HyperDash":false},{"StartTime":4510.0,"Position":451.933838,"HyperDash":false},{"StartTime":4616.0,"Position":498.848267,"HyperDash":false}]},{"StartTime":4792.0,"Objects":[{"StartTime":4792.0,"Position":440.0,"HyperDash":false}]},{"StartTime":4967.0,"Objects":[{"StartTime":4967.0,"Position":289.0,"HyperDash":false},{"StartTime":5036.0,"Position":249.229553,"HyperDash":false},{"StartTime":5142.0,"Position":216.522751,"HyperDash":false}]},{"StartTime":5318.0,"Objects":[{"StartTime":5318.0,"Position":105.0,"HyperDash":false}]},{"StartTime":5494.0,"Objects":[{"StartTime":5494.0,"Position":119.0,"HyperDash":false},{"StartTime":5581.0,"Position":96.97381,"HyperDash":false},{"StartTime":5669.0,"Position":97.10305,"HyperDash":false},{"StartTime":5738.0,"Position":108.834061,"HyperDash":false},{"StartTime":5844.0,"Position":132.852325,"HyperDash":false}]},{"StartTime":6020.0,"Objects":[{"StartTime":6020.0,"Position":192.0,"HyperDash":true}]},{"StartTime":6195.0,"Objects":[{"StartTime":6195.0,"Position":451.0,"HyperDash":false},{"StartTime":6282.0,"Position":417.845947,"HyperDash":false},{"StartTime":6370.0,"Position":392.282257,"HyperDash":false},{"StartTime":6439.0,"Position":343.915466,"HyperDash":false},{"StartTime":6545.0,"Position":323.918671,"HyperDash":false}]},{"StartTime":6722.0,"Objects":[{"StartTime":6722.0,"Position":380.0,"HyperDash":false}]},{"StartTime":6897.0,"Objects":[{"StartTime":6897.0,"Position":334.0,"HyperDash":false}]},{"StartTime":6985.0,"Objects":[{"StartTime":6985.0,"Position":334.0,"HyperDash":false}]},{"StartTime":7073.0,"Objects":[{"StartTime":7073.0,"Position":334.0,"HyperDash":false},{"StartTime":7160.0,"Position":336.334045,"HyperDash":false},{"StartTime":7248.0,"Position":347.94342,"HyperDash":false},{"StartTime":7317.0,"Position":360.6226,"HyperDash":false},{"StartTime":7423.0,"Position":326.399445,"HyperDash":false}]},{"StartTime":7599.0,"Objects":[{"StartTime":7599.0,"Position":281.0,"HyperDash":false}]},{"StartTime":7774.0,"Objects":[{"StartTime":7774.0,"Position":140.0,"HyperDash":false}]},{"StartTime":7950.0,"Objects":[{"StartTime":7950.0,"Position":274.0,"HyperDash":false}]},{"StartTime":8125.0,"Objects":[{"StartTime":8125.0,"Position":138.0,"HyperDash":false}]},{"StartTime":8301.0,"Objects":[{"StartTime":8301.0,"Position":266.0,"HyperDash":false},{"StartTime":8388.0,"Position":316.25592,"HyperDash":false},{"StartTime":8476.0,"Position":340.940063,"HyperDash":false},{"StartTime":8545.0,"Position":353.487854,"HyperDash":false},{"StartTime":8651.0,"Position":415.880127,"HyperDash":false}]},{"StartTime":8827.0,"Objects":[{"StartTime":8827.0,"Position":512.0,"HyperDash":false}]},{"StartTime":9002.0,"Objects":[{"StartTime":9002.0,"Position":490.0,"HyperDash":false},{"StartTime":9089.0,"Position":482.986267,"HyperDash":false},{"StartTime":9177.0,"Position":439.110077,"HyperDash":false},{"StartTime":9246.0,"Position":414.503052,"HyperDash":false},{"StartTime":9352.0,"Position":366.139923,"HyperDash":false}]},{"StartTime":9529.0,"Objects":[{"StartTime":9529.0,"Position":260.0,"HyperDash":false},{"StartTime":9598.0,"Position":239.881287,"HyperDash":false},{"StartTime":9704.0,"Position":261.6383,"HyperDash":false}]},{"StartTime":9792.0,"Objects":[{"StartTime":9792.0,"Position":267.0,"HyperDash":false}]},{"StartTime":9880.0,"Objects":[{"StartTime":9880.0,"Position":267.0,"HyperDash":false},{"StartTime":9967.0,"Position":234.350662,"HyperDash":false},{"StartTime":10055.0,"Position":203.079025,"HyperDash":false},{"StartTime":10124.0,"Position":187.624725,"HyperDash":false},{"StartTime":10230.0,"Position":130.615417,"HyperDash":false}]},{"StartTime":10406.0,"Objects":[{"StartTime":10406.0,"Position":185.0,"HyperDash":false}]},{"StartTime":10581.0,"Objects":[{"StartTime":10581.0,"Position":177.0,"HyperDash":false},{"StartTime":10650.0,"Position":191.354156,"HyperDash":false},{"StartTime":10756.0,"Position":249.516769,"HyperDash":false}]},{"StartTime":10932.0,"Objects":[{"StartTime":10932.0,"Position":352.0,"HyperDash":false}]},{"StartTime":11108.0,"Objects":[{"StartTime":11108.0,"Position":436.0,"HyperDash":false},{"StartTime":11177.0,"Position":446.0463,"HyperDash":false},{"StartTime":11283.0,"Position":509.668152,"HyperDash":false}]},{"StartTime":11458.0,"Objects":[{"StartTime":11458.0,"Position":368.0,"HyperDash":false},{"StartTime":11527.0,"Position":322.9537,"HyperDash":false},{"StartTime":11633.0,"Position":294.331848,"HyperDash":false}]},{"StartTime":11809.0,"Objects":[{"StartTime":11809.0,"Position":181.0,"HyperDash":false},{"StartTime":11878.0,"Position":187.9666,"HyperDash":false},{"StartTime":11984.0,"Position":184.937943,"HyperDash":false}]},{"StartTime":12160.0,"Objects":[{"StartTime":12160.0,"Position":221.0,"HyperDash":false}]},{"StartTime":12248.0,"Objects":[{"StartTime":12248.0,"Position":221.0,"HyperDash":false}]},{"StartTime":12336.0,"Objects":[{"StartTime":12336.0,"Position":221.0,"HyperDash":false},{"StartTime":12405.0,"Position":266.95636,"HyperDash":false},{"StartTime":12511.0,"Position":293.24704,"HyperDash":false}]},{"StartTime":12687.0,"Objects":[{"StartTime":12687.0,"Position":440.0,"HyperDash":false},{"StartTime":12774.0,"Position":402.903931,"HyperDash":false},{"StartTime":12862.0,"Position":366.3639,"HyperDash":false},{"StartTime":12931.0,"Position":335.8872,"HyperDash":false},{"StartTime":13037.0,"Position":292.814026,"HyperDash":false}]},{"StartTime":13213.0,"Objects":[{"StartTime":13213.0,"Position":330.0,"HyperDash":false}]},{"StartTime":13301.0,"Objects":[{"StartTime":13301.0,"Position":330.0,"HyperDash":false}]},{"StartTime":13388.0,"Objects":[{"StartTime":13388.0,"Position":330.0,"HyperDash":false},{"StartTime":13457.0,"Position":378.510529,"HyperDash":false},{"StartTime":13563.0,"Position":404.689636,"HyperDash":false}]},{"StartTime":13739.0,"Objects":[{"StartTime":13739.0,"Position":494.0,"HyperDash":false}]},{"StartTime":13915.0,"Objects":[{"StartTime":13915.0,"Position":321.0,"HyperDash":false}]},{"StartTime":14002.0,"Objects":[{"StartTime":14002.0,"Position":321.0,"HyperDash":false}]},{"StartTime":14090.0,"Objects":[{"StartTime":14090.0,"Position":321.0,"HyperDash":false},{"StartTime":14159.0,"Position":343.727631,"HyperDash":false},{"StartTime":14265.0,"Position":391.072754,"HyperDash":false}]},{"StartTime":14441.0,"Objects":[{"StartTime":14441.0,"Position":231.0,"HyperDash":false}]},{"StartTime":14616.0,"Objects":[{"StartTime":14616.0,"Position":188.0,"HyperDash":false},{"StartTime":14703.0,"Position":182.992142,"HyperDash":false},{"StartTime":14791.0,"Position":176.795868,"HyperDash":false},{"StartTime":14860.0,"Position":189.954117,"HyperDash":false},{"StartTime":14966.0,"Position":188.0,"HyperDash":false}]},{"StartTime":15143.0,"Objects":[{"StartTime":15143.0,"Position":125.0,"HyperDash":false},{"StartTime":15230.0,"Position":105.420952,"HyperDash":false},{"StartTime":15318.0,"Position":59.72222,"HyperDash":false},{"StartTime":15406.0,"Position":22.9492321,"HyperDash":false}]},{"StartTime":15494.0,"Objects":[{"StartTime":15494.0,"Position":17.0,"HyperDash":false},{"StartTime":15563.0,"Position":33.37393,"HyperDash":false},{"StartTime":15669.0,"Position":20.4846058,"HyperDash":false}]},{"StartTime":15844.0,"Objects":[{"StartTime":15844.0,"Position":29.0,"HyperDash":false}]},{"StartTime":16020.0,"Objects":[{"StartTime":16020.0,"Position":130.0,"HyperDash":false}]},{"StartTime":16108.0,"Objects":[{"StartTime":16108.0,"Position":130.0,"HyperDash":false}]},{"StartTime":16195.0,"Objects":[{"StartTime":16195.0,"Position":130.0,"HyperDash":false},{"StartTime":16264.0,"Position":176.33783,"HyperDash":false},{"StartTime":16370.0,"Position":203.709747,"HyperDash":false}]},{"StartTime":16546.0,"Objects":[{"StartTime":16546.0,"Position":287.0,"HyperDash":false}]},{"StartTime":16722.0,"Objects":[{"StartTime":16722.0,"Position":402.0,"HyperDash":false},{"StartTime":16791.0,"Position":440.382324,"HyperDash":false},{"StartTime":16897.0,"Position":476.5204,"HyperDash":false}]},{"StartTime":17073.0,"Objects":[{"StartTime":17073.0,"Position":326.0,"HyperDash":false},{"StartTime":17142.0,"Position":279.617676,"HyperDash":false},{"StartTime":17248.0,"Position":251.479614,"HyperDash":false}]},{"StartTime":17423.0,"Objects":[{"StartTime":17423.0,"Position":125.0,"HyperDash":false},{"StartTime":17492.0,"Position":122.322762,"HyperDash":false},{"StartTime":17598.0,"Position":119.049225,"HyperDash":false}]},{"StartTime":17774.0,"Objects":[{"StartTime":17774.0,"Position":125.0,"HyperDash":false}]},{"StartTime":17862.0,"Objects":[{"StartTime":17862.0,"Position":125.0,"HyperDash":false}]},{"StartTime":17950.0,"Objects":[{"StartTime":17950.0,"Position":125.0,"HyperDash":false},{"StartTime":18019.0,"Position":142.158081,"HyperDash":false},{"StartTime":18125.0,"Position":198.3747,"HyperDash":false}]},{"StartTime":18301.0,"Objects":[{"StartTime":18301.0,"Position":245.0,"HyperDash":false},{"StartTime":18388.0,"Position":193.484589,"HyperDash":false},{"StartTime":18476.0,"Position":170.83812,"HyperDash":false},{"StartTime":18545.0,"Position":133.486023,"HyperDash":false},{"StartTime":18651.0,"Position":97.91507,"HyperDash":false}]},{"StartTime":18827.0,"Objects":[{"StartTime":18827.0,"Position":15.0,"HyperDash":false}]},{"StartTime":18915.0,"Objects":[{"StartTime":18915.0,"Position":15.0,"HyperDash":false}]},{"StartTime":19002.0,"Objects":[{"StartTime":19002.0,"Position":15.0,"HyperDash":false},{"StartTime":19071.0,"Position":21.7103615,"HyperDash":false},{"StartTime":19177.0,"Position":4.26349068,"HyperDash":false}]},{"StartTime":19353.0,"Objects":[{"StartTime":19353.0,"Position":0.0,"HyperDash":false}]},{"StartTime":19529.0,"Objects":[{"StartTime":19529.0,"Position":137.0,"HyperDash":false},{"StartTime":19598.0,"Position":169.483047,"HyperDash":false},{"StartTime":19704.0,"Position":210.398544,"HyperDash":false}]},{"StartTime":19880.0,"Objects":[{"StartTime":19880.0,"Position":328.0,"HyperDash":false},{"StartTime":19949.0,"Position":319.217133,"HyperDash":false},{"StartTime":20055.0,"Position":318.546051,"HyperDash":false}]},{"StartTime":20230.0,"Objects":[{"StartTime":20230.0,"Position":264.0,"HyperDash":false}]},{"StartTime":20318.0,"Objects":[{"StartTime":20318.0,"Position":264.0,"HyperDash":false}]},{"StartTime":20406.0,"Objects":[{"StartTime":20406.0,"Position":264.0,"HyperDash":false},{"StartTime":20493.0,"Position":295.147522,"HyperDash":false},{"StartTime":20581.0,"Position":330.866455,"HyperDash":false},{"StartTime":20650.0,"Position":359.710419,"HyperDash":false},{"StartTime":20756.0,"Position":396.14447,"HyperDash":false}]},{"StartTime":21108.0,"Objects":[{"StartTime":21108.0,"Position":412.0,"HyperDash":false},{"StartTime":21195.0,"Position":395.0836,"HyperDash":false},{"StartTime":21283.0,"Position":414.179626,"HyperDash":false},{"StartTime":21370.0,"Position":423.2632,"HyperDash":false},{"StartTime":21458.0,"Position":416.359283,"HyperDash":false},{"StartTime":21528.0,"Position":397.23114,"HyperDash":false},{"StartTime":21634.0,"Position":418.551361,"HyperDash":false}]},{"StartTime":21809.0,"Objects":[{"StartTime":21809.0,"Position":496.0,"HyperDash":false},{"StartTime":21896.0,"Position":491.214264,"HyperDash":false},{"StartTime":21984.0,"Position":496.431,"HyperDash":false},{"StartTime":22053.0,"Position":504.600922,"HyperDash":false},{"StartTime":22159.0,"Position":496.862,"HyperDash":false}]},{"StartTime":22336.0,"Objects":[{"StartTime":22336.0,"Position":499.0,"HyperDash":false}]},{"StartTime":22511.0,"Objects":[{"StartTime":22511.0,"Position":379.0,"HyperDash":false},{"StartTime":22598.0,"Position":360.8092,"HyperDash":false},{"StartTime":22686.0,"Position":345.0908,"HyperDash":false},{"StartTime":22773.0,"Position":309.7649,"HyperDash":false},{"StartTime":22861.0,"Position":307.9739,"HyperDash":false},{"StartTime":22931.0,"Position":312.241852,"HyperDash":false},{"StartTime":23037.0,"Position":271.985718,"HyperDash":false}]},{"StartTime":23213.0,"Objects":[{"StartTime":23213.0,"Position":322.0,"HyperDash":false},{"StartTime":23300.0,"Position":336.858,"HyperDash":false},{"StartTime":23388.0,"Position":327.7828,"HyperDash":false},{"StartTime":23457.0,"Position":329.661743,"HyperDash":false},{"StartTime":23563.0,"Position":317.734131,"HyperDash":false}]},{"StartTime":23739.0,"Objects":[{"StartTime":23739.0,"Position":240.0,"HyperDash":false}]},{"StartTime":23915.0,"Objects":[{"StartTime":23915.0,"Position":345.0,"HyperDash":false},{"StartTime":23984.0,"Position":381.55426,"HyperDash":false},{"StartTime":24090.0,"Position":419.956451,"HyperDash":false}]},{"StartTime":24266.0,"Objects":[{"StartTime":24266.0,"Position":283.0,"HyperDash":false}]},{"StartTime":24441.0,"Objects":[{"StartTime":24441.0,"Position":111.0,"HyperDash":false},{"StartTime":24510.0,"Position":97.44574,"HyperDash":false},{"StartTime":24616.0,"Position":36.04355,"HyperDash":false}]},{"StartTime":24792.0,"Objects":[{"StartTime":24792.0,"Position":173.0,"HyperDash":false}]},{"StartTime":24967.0,"Objects":[{"StartTime":24967.0,"Position":263.0,"HyperDash":false}]},{"StartTime":25055.0,"Objects":[{"StartTime":25055.0,"Position":280.0,"HyperDash":false}]},{"StartTime":25143.0,"Objects":[{"StartTime":25143.0,"Position":297.0,"HyperDash":false}]},{"StartTime":25230.0,"Objects":[{"StartTime":25230.0,"Position":314.0,"HyperDash":false}]},{"StartTime":25318.0,"Objects":[{"StartTime":25318.0,"Position":337.0,"HyperDash":false},{"StartTime":25376.0,"Position":334.666473,"HyperDash":false},{"StartTime":25434.0,"Position":337.0,"HyperDash":false},{"StartTime":25493.0,"Position":334.666473,"HyperDash":false},{"StartTime":25551.0,"Position":337.0,"HyperDash":false},{"StartTime":25610.0,"Position":334.666473,"HyperDash":false},{"StartTime":25668.0,"Position":337.0,"HyperDash":false}]},{"StartTime":25844.0,"Objects":[{"StartTime":25844.0,"Position":447.0,"HyperDash":false}]},{"StartTime":26020.0,"Objects":[{"StartTime":26020.0,"Position":436.0,"HyperDash":false}]},{"StartTime":26195.0,"Objects":[{"StartTime":26195.0,"Position":297.0,"HyperDash":false}]},{"StartTime":26546.0,"Objects":[{"StartTime":26546.0,"Position":297.0,"HyperDash":false},{"StartTime":26633.0,"Position":249.353119,"HyperDash":false},{"StartTime":26721.0,"Position":227.527557,"HyperDash":false},{"StartTime":26790.0,"Position":188.1133,"HyperDash":false},{"StartTime":26896.0,"Position":156.074387,"HyperDash":false}]},{"StartTime":27072.0,"Objects":[{"StartTime":27072.0,"Position":51.0,"HyperDash":false}]},{"StartTime":27247.0,"Objects":[{"StartTime":27247.0,"Position":185.0,"HyperDash":false},{"StartTime":27316.0,"Position":218.59346,"HyperDash":false},{"StartTime":27422.0,"Position":258.538177,"HyperDash":false}]},{"StartTime":27598.0,"Objects":[{"StartTime":27598.0,"Position":436.0,"HyperDash":false},{"StartTime":27667.0,"Position":416.406555,"HyperDash":false},{"StartTime":27773.0,"Position":362.461823,"HyperDash":false}]},{"StartTime":27949.0,"Objects":[{"StartTime":27949.0,"Position":151.0,"HyperDash":false},{"StartTime":28036.0,"Position":189.972488,"HyperDash":false},{"StartTime":28124.0,"Position":223.203812,"HyperDash":false},{"StartTime":28193.0,"Position":242.7229,"HyperDash":false},{"StartTime":28299.0,"Position":296.7707,"HyperDash":false}]},{"StartTime":28475.0,"Objects":[{"StartTime":28475.0,"Position":223.0,"HyperDash":false}]},{"StartTime":28651.0,"Objects":[{"StartTime":28651.0,"Position":296.0,"HyperDash":false},{"StartTime":28738.0,"Position":337.803925,"HyperDash":false},{"StartTime":28826.0,"Position":368.138336,"HyperDash":false},{"StartTime":28895.0,"Position":404.540863,"HyperDash":false},{"StartTime":29001.0,"Position":440.327179,"HyperDash":false}]},{"StartTime":29177.0,"Objects":[{"StartTime":29177.0,"Position":486.0,"HyperDash":false}]},{"StartTime":29353.0,"Objects":[{"StartTime":29353.0,"Position":366.0,"HyperDash":false},{"StartTime":29422.0,"Position":350.499329,"HyperDash":false},{"StartTime":29528.0,"Position":293.446533,"HyperDash":false}]},{"StartTime":29703.0,"Objects":[{"StartTime":29703.0,"Position":169.0,"HyperDash":false}]},{"StartTime":29879.0,"Objects":[{"StartTime":29879.0,"Position":245.0,"HyperDash":false}]},{"StartTime":30054.0,"Objects":[{"StartTime":30054.0,"Position":126.0,"HyperDash":false},{"StartTime":30123.0,"Position":155.500671,"HyperDash":false},{"StartTime":30229.0,"Position":198.553482,"HyperDash":false}]},{"StartTime":30404.0,"Objects":[{"StartTime":30404.0,"Position":323.0,"HyperDash":false}]},{"StartTime":30580.0,"Objects":[{"StartTime":30580.0,"Position":247.0,"HyperDash":false}]},{"StartTime":30756.0,"Objects":[{"StartTime":30756.0,"Position":349.0,"HyperDash":false},{"StartTime":30843.0,"Position":365.629761,"HyperDash":false},{"StartTime":30931.0,"Position":422.0551,"HyperDash":false},{"StartTime":31000.0,"Position":454.551147,"HyperDash":false},{"StartTime":31106.0,"Position":495.5697,"HyperDash":false}]},{"StartTime":31282.0,"Objects":[{"StartTime":31282.0,"Position":423.0,"HyperDash":false}]},{"StartTime":31458.0,"Objects":[{"StartTime":31458.0,"Position":323.0,"HyperDash":false},{"StartTime":31545.0,"Position":295.370239,"HyperDash":false},{"StartTime":31633.0,"Position":249.944885,"HyperDash":false},{"StartTime":31702.0,"Position":223.448853,"HyperDash":false},{"StartTime":31808.0,"Position":176.4303,"HyperDash":false}]},{"StartTime":31984.0,"Objects":[{"StartTime":31984.0,"Position":247.0,"HyperDash":false}]},{"StartTime":32160.0,"Objects":[{"StartTime":32160.0,"Position":99.0,"HyperDash":false},{"StartTime":32247.0,"Position":84.41518,"HyperDash":false},{"StartTime":32335.0,"Position":83.6537247,"HyperDash":false},{"StartTime":32404.0,"Position":71.69304,"HyperDash":false},{"StartTime":32510.0,"Position":108.235535,"HyperDash":false}]},{"StartTime":32686.0,"Objects":[{"StartTime":32686.0,"Position":164.0,"HyperDash":false}]},{"StartTime":32861.0,"Objects":[{"StartTime":32861.0,"Position":323.0,"HyperDash":false},{"StartTime":32930.0,"Position":362.799,"HyperDash":false},{"StartTime":33036.0,"Position":396.638153,"HyperDash":false}]},{"StartTime":33212.0,"Objects":[{"StartTime":33212.0,"Position":164.0,"HyperDash":false},{"StartTime":33281.0,"Position":116.200989,"HyperDash":false},{"StartTime":33387.0,"Position":90.36186,"HyperDash":false}]},{"StartTime":33563.0,"Objects":[{"StartTime":33563.0,"Position":323.0,"HyperDash":false},{"StartTime":33632.0,"Position":336.507568,"HyperDash":false},{"StartTime":33738.0,"Position":323.911469,"HyperDash":true}]},{"StartTime":33914.0,"Objects":[{"StartTime":33914.0,"Position":78.0,"HyperDash":false},{"StartTime":33983.0,"Position":82.492424,"HyperDash":false},{"StartTime":34089.0,"Position":77.08854,"HyperDash":false}]},{"StartTime":34265.0,"Objects":[{"StartTime":34265.0,"Position":234.0,"HyperDash":false},{"StartTime":34352.0,"Position":191.5233,"HyperDash":false},{"StartTime":34440.0,"Position":164.09671,"HyperDash":false},{"StartTime":34509.0,"Position":134.647873,"HyperDash":false},{"StartTime":34615.0,"Position":89.6628342,"HyperDash":false}]},{"StartTime":34791.0,"Objects":[{"StartTime":34791.0,"Position":148.0,"HyperDash":false}]},{"StartTime":34967.0,"Objects":[{"StartTime":34967.0,"Position":175.0,"HyperDash":false},{"StartTime":35054.0,"Position":199.913467,"HyperDash":false},{"StartTime":35142.0,"Position":201.876785,"HyperDash":false},{"StartTime":35211.0,"Position":207.491714,"HyperDash":false},{"StartTime":35317.0,"Position":181.816238,"HyperDash":false}]},{"StartTime":35493.0,"Objects":[{"StartTime":35493.0,"Position":94.0,"HyperDash":false}]},{"StartTime":35668.0,"Objects":[{"StartTime":35668.0,"Position":95.0,"HyperDash":false},{"StartTime":35755.0,"Position":137.9405,"HyperDash":false},{"StartTime":35843.0,"Position":163.627121,"HyperDash":false},{"StartTime":35912.0,"Position":197.017715,"HyperDash":false},{"StartTime":36018.0,"Position":234.539215,"HyperDash":false}]},{"StartTime":36195.0,"Objects":[{"StartTime":36195.0,"Position":319.0,"HyperDash":false}]},{"StartTime":36370.0,"Objects":[{"StartTime":36370.0,"Position":251.0,"HyperDash":false},{"StartTime":36457.0,"Position":231.0595,"HyperDash":false},{"StartTime":36545.0,"Position":182.372879,"HyperDash":false},{"StartTime":36614.0,"Position":153.982285,"HyperDash":false},{"StartTime":36720.0,"Position":111.460777,"HyperDash":false}]},{"StartTime":36896.0,"Objects":[{"StartTime":36896.0,"Position":175.0,"HyperDash":false}]},{"StartTime":37072.0,"Objects":[{"StartTime":37072.0,"Position":229.0,"HyperDash":false}]},{"StartTime":37160.0,"Objects":[{"StartTime":37160.0,"Position":245.0,"HyperDash":false}]},{"StartTime":37247.0,"Objects":[{"StartTime":37247.0,"Position":261.0,"HyperDash":false}]},{"StartTime":37335.0,"Objects":[{"StartTime":37335.0,"Position":277.0,"HyperDash":false}]},{"StartTime":37423.0,"Objects":[{"StartTime":37423.0,"Position":292.0,"HyperDash":false},{"StartTime":37492.0,"Position":308.471649,"HyperDash":false},{"StartTime":37598.0,"Position":366.746948,"HyperDash":false}]},{"StartTime":37774.0,"Objects":[{"StartTime":37774.0,"Position":491.0,"HyperDash":false}]},{"StartTime":38124.0,"Objects":[{"StartTime":38124.0,"Position":491.0,"HyperDash":false}]},{"StartTime":38300.0,"Objects":[{"StartTime":38300.0,"Position":422.0,"HyperDash":false}]},{"StartTime":38475.0,"Objects":[{"StartTime":38475.0,"Position":388.0,"HyperDash":false}]},{"StartTime":38826.0,"Objects":[{"StartTime":38826.0,"Position":388.0,"HyperDash":false}]},{"StartTime":39002.0,"Objects":[{"StartTime":39002.0,"Position":270.0,"HyperDash":false}]},{"StartTime":39177.0,"Objects":[{"StartTime":39177.0,"Position":305.0,"HyperDash":false},{"StartTime":39264.0,"Position":31.0,"HyperDash":false},{"StartTime":39352.0,"Position":421.0,"HyperDash":false},{"StartTime":39440.0,"Position":145.0,"HyperDash":false},{"StartTime":39528.0,"Position":318.0,"HyperDash":false},{"StartTime":39615.0,"Position":249.0,"HyperDash":false},{"StartTime":39703.0,"Position":147.0,"HyperDash":false},{"StartTime":39791.0,"Position":302.0,"HyperDash":false},{"StartTime":39879.0,"Position":212.0,"HyperDash":false},{"StartTime":39966.0,"Position":427.0,"HyperDash":false},{"StartTime":40054.0,"Position":116.0,"HyperDash":false},{"StartTime":40142.0,"Position":508.0,"HyperDash":false},{"StartTime":40230.0,"Position":417.0,"HyperDash":false},{"StartTime":40317.0,"Position":302.0,"HyperDash":false},{"StartTime":40405.0,"Position":132.0,"HyperDash":false},{"StartTime":40493.0,"Position":352.0,"HyperDash":false},{"StartTime":40581.0,"Position":174.0,"HyperDash":false}]}]} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/871815.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/871815.osu new file mode 100644 index 0000000000..668c12fc0c --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/871815.osu @@ -0,0 +1,165 @@ +osu file format v14 + +[General] +StackLeniency: 0.3 +Mode: 0 + +[Difficulty] +HPDrainRate:6 +CircleSize:4 +OverallDifficulty:6 +ApproachRate:8.3 +SliderMultiplier:1.5 +SliderTickRate:2 + +[Events] +//Background and Video events +//Break Periods +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Sound Samples + +[TimingPoints] +1459,350.877192982456,4,2,1,45,1,0 +21108,-200,4,2,1,45,0,0 +23915,-100,4,2,1,45,0,0 +26546,350.877192982456,4,2,1,65,1,1 +40581,-100,4,2,1,45,0,0 + +[HitObjects] +150,114,1459,5,2,0:0:0:0: +150,114,1809,2,0,L|276:109,1,112.5,2|0,0:0|0:0,0:0:0:0: +281,109,2160,2,0,P|289:151|282:193,1,75,0|0,0:0|0:0,0:0:0:0: +272,298,2511,1,0,0:0:0:0: +367,70,2687,6,0,P|384:125|366:226,1,150,2|0,0:0|0:0,0:0:0:0: +278,289,3213,1,0,0:0:0:0: +113,221,3388,1,0,1:0:0:0: +116,235,3476,1,0,1:0:0:0: +121,248,3564,2,0,L|122:274,8,18.75,0|0|0|0|2|0|0|0|2,1:0|0:0|0:0|0:0|0:0|0:0|0:0|0:0|1:0,0:0:0:0: +368,70,4266,6,0,P|412:37|501:60,1,150,4|8,1:0|1:0,0:0:0:0: +440,119,4792,1,0,0:0:0:0: +289,84,4967,2,0,P|250:95|210:87,1,75,0|0,0:0|1:0,0:0:0:0: +105,24,5318,1,0,1:0:0:0: +119,191,5494,6,0,P|98:235|145:332,1,150,4|0,1:0|1:0,0:0:0:0: +192,253,6020,1,0,1:0:0:0: +451,314,6195,2,0,P|393:272|315:306,1,150,4|0,1:0|0:0,0:0:0:0: +380,360,6722,1,0,1:0:0:0: +334,189,6897,1,2,0:0:0:0: +334,189,6985,1,0,0:0:0:0: +334,189,7073,6,0,P|348:132|320:35,1,150,4|0,1:0|1:0,0:0:0:0: +281,256,7599,1,0,0:0:0:0: +140,171,7774,1,0,0:0:0:0: +274,290,7950,1,2,0:0:0:0: +138,135,8125,1,0,1:0:0:0: +266,321,8301,6,0,L|416:315,1,150,0|0,1:0|0:0,0:0:0:0: +512,307,8827,1,0,1:0:0:0: +490,150,9002,2,0,P|435:96|347:109,1,150,2|2,0:0|0:0,0:0:0:0: +260,59,9529,2,0,P|255:102|268:147,1,75,8|0,1:0|1:0,0:0:0:0: +267,164,9792,1,0,0:0:0:0: +267,164,9880,6,0,P|217:197|121:182,1,150,4|0,1:0|1:0,0:0:0:0: +185,106,10406,1,0,0:0:0:0: +177,283,10581,2,0,P|219:284|260:266,1,75,0|0,1:0|0:0,0:0:0:0: +352,225,10932,1,0,1:0:0:0: +436,132,11108,6,0,L|525:149,1,75,2|0,1:2|0:0,0:0:0:0: +368,30,11458,2,0,L|279:47,1,75,2|0,0:2|1:0,0:0:0:0: +181,124,11809,2,0,P|175:162|190:205,1,75,2|0,0:0|1:0,0:0:0:0: +221,325,12160,1,0,0:0:0:0: +221,325,12248,1,0,0:0:0:0: +221,325,12336,2,0,P|257:330|294:318,1,75,0|0,1:0|0:0,0:0:0:0: +440,318,12687,6,0,P|378:306|272:327,1,150,2|0,1:2|1:0,0:0:0:0: +330,209,13213,1,0,0:0:0:0: +330,209,13301,1,0,0:0:0:0: +330,209,13388,2,0,P|388:209|417:204,1,75,0|0,0:0|0:0,0:0:0:0: +494,149,13739,1,0,1:0:0:0: +321,99,13915,1,2,1:2:0:0: +321,99,14002,1,0,0:0:0:0: +321,99,14090,6,0,P|364:87|392:73,1,75,0|0,0:0|0:0,0:0:0:0: +231,160,14441,1,0,1:0:0:0: +188,259,14616,2,0,P|177:302|177:335,2,75,2|0|0,1:2|0:0|0:0,0:0:0:0: +125,87,15143,6,0,B|99:97|70:83|70:83|73:86|73:86|35:72|7:97,1,112.5,8|0,1:0|0:0,0:0:0:0: +17,99,15494,6,0,L|21:185,1,75,4|0,1:0|0:0,0:0:0:0: +29,282,15844,1,0,1:0:0:0: +130,334,16020,1,0,0:0:0:0: +130,334,16108,1,0,0:0:0:0: +130,334,16195,2,0,P|165:337|208:327,1,75,0|0,1:0|0:0,0:0:0:0: +287,251,16546,1,0,1:0:0:0: +402,165,16722,6,0,L|490:155,1,75,2|0,1:2|0:0,0:0:0:0: +326,67,17073,2,0,L|238:57,1,75,2|0,0:2|1:0,0:0:0:0: +125,41,17423,2,0,P|116:84|124:131,1,75,2|0,0:0|1:0,0:0:0:0: +125,238,17774,1,2,0:0:0:0: +125,238,17862,1,0,0:0:0:0: +125,238,17950,2,0,P|165:242|204:231,1,75,0|0,1:0|0:0,0:0:0:0: +245,344,18301,6,0,P|162:336|85:357,1,150,2|0,1:2|1:0,0:0:0:0: +15,271,18827,1,0,0:0:0:0: +15,271,18915,1,0,0:0:0:0: +15,271,19002,2,0,P|3:222|7:184,1,75,0|2,0:0|0:0,0:0:0:0: +0,85,19353,1,0,1:0:0:0: +137,68,19529,6,0,P|170:69|214:57,1,75,4|0,1:2|1:0,0:0:0:0: +328,191,19880,2,0,P|329:158|317:114,1,75,0|2,0:0|1:0,0:0:0:0: +264,261,20230,1,0,1:0:0:0: +264,261,20318,1,0,0:0:0:0: +264,261,20406,2,0,P|318:289|401:251,1,150,0|8,0:0|1:0,0:0:0:0: +412,245,21108,6,0,L|419:365,1,112.5,4|0,1:0|0:0,0:0:0:0: +496,259,21809,2,0,L|497:172,1,75,0|0,1:0|0:0,0:0:0:0: +499,82,22336,1,0,0:0:0:0: +379,42,22511,6,0,P|338:25|265:38,1,112.5,2|2,0:0|0:0,0:0:0:0: +322,179,23213,2,0,P|328:145|318:107,1,75,0|0,0:0|0:0,0:0:0:0: +240,150,23739,1,0,0:0:0:0: +345,271,23915,6,0,L|433:274,1,75,4|0,1:0|0:0,0:0:0:0: +283,331,24266,1,0,0:0:0:0: +111,275,24441,6,0,L|23:272,1,75,4|0,1:0|0:0,0:0:0:0: +173,215,24792,1,0,0:0:0:0: +263,127,24967,5,0,1:0:0:0: +280,119,25055,1,0,0:0:0:0: +297,112,25143,1,0,1:0:0:0: +314,105,25230,1,0,0:0:0:0: +337,95,25318,6,0,L|334:127,6,25,0|0|0|0|0|0|0,1:0|0:0|0:0|1:0|0:0|0:0|1:0,0:0:0:0: +447,46,25844,1,0,1:0:0:0: +436,197,26020,1,0,0:0:0:0: +297,263,26195,1,2,1:0:0:0: +297,263,26546,6,0,P|230:288|143:260,1,150,4|0,1:0|0:0,0:0:0:0: +51,182,27072,1,2,1:0:0:0: +185,111,27247,2,0,P|224:103|271:112,1,75,0|0,0:0|0:0,0:0:0:0: +436,197,27598,2,0,P|397:205|350:196,1,75,0|2,0:0|1:0,0:0:0:0: +151,269,27949,6,0,P|208:252|320:273,1,150,0|0,1:0|0:0,0:0:0:0: +223,342,28475,1,0,0:0:0:0: +296,262,28651,2,0,P|353:279|456:253,1,150,0|0,1:0|0:0,0:0:0:0: +486,133,29177,1,2,1:0:0:0: +366,52,29353,6,0,P|324:39|288:42,1,75,2|0,0:0|0:0,0:0:0:0: +169,61,29703,1,0,0:0:0:0: +245,149,29879,1,2,1:0:0:0: +126,258,30054,6,0,P|168:271|204:268,1,75,2|0,0:0|0:0,0:0:0:0: +323,249,30404,1,0,0:0:0:0: +247,161,30580,1,0,0:0:0:0: +349,54,30756,6,0,P|397:41|502:54,1,150,2|0,0:0|0:0,0:0:0:0: +423,138,31282,1,0,1:0:0:0: +323,249,31458,2,0,P|275:262|170:249,1,150,0|0,1:0|0:0,0:0:0:0: +247,161,31984,1,2,1:0:0:0: +99,42,32160,6,0,P|85:127|121:200,1,150,4|0,1:0|0:0,0:0:0:0: +164,309,32686,1,2,1:0:0:0: +323,249,32861,2,0,P|376:243|401:249,1,75,0|0,0:0|0:0,0:0:0:0: +164,309,33212,2,0,P|111:315|86:309,1,75,0|2,0:0|1:0,0:0:0:0: +323,249,33563,6,0,P|330:211|316:158,1,75,0|0,1:0|0:0,0:0:0:0: +78,57,33914,2,0,P|71:95|85:148,1,75,0|0,0:0|0:0,0:0:0:0: +234,300,34265,2,0,P|174:276|80:280,1,150,0|0,1:0|0:0,0:0:0:0: +148,364,34791,1,2,1:0:0:0: +175,186,34967,6,0,P|199:138|172:34,1,150,4|0,1:0|0:0,0:0:0:0: +94,115,35493,1,2,1:0:0:0: +95,260,35668,2,0,P|143:284|247:257,1,150,4|0,1:0|0:0,0:0:0:0: +319,199,36195,1,0,0:0:0:0: +251,89,36370,6,0,P|203:65|99:92,1,150,0|0,1:0|0:0,0:0:0:0: +175,186,36896,1,0,0:0:0:0: +229,329,37072,1,0,1:0:0:0: +245,337,37160,1,0,1:0:0:0: +261,345,37247,1,0,1:0:0:0: +277,353,37335,1,0,0:0:0:0: +292,361,37423,2,0,L|377:368,1,75,0|0,1:0|1:0,0:0:0:0: +491,315,37774,5,4,1:0:0:0: +491,315,38124,1,0,1:0:0:0: +422,209,38300,1,0,1:0:0:0: +388,68,38475,1,4,1:0:0:0: +388,68,38826,1,0,1:0:0:0: +270,153,39002,1,0,1:0:0:0: +256,192,39177,12,4,40581,1:0:0:0: diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/basic-expected-conversion.json similarity index 100% rename from osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-expected-conversion.json rename to osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/basic-expected-conversion.json diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-hyperdash-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/basic-hyperdash-expected-conversion.json similarity index 100% rename from osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-hyperdash-expected-conversion.json rename to osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/basic-hyperdash-expected-conversion.json diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-hyperdash.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/basic-hyperdash.osu similarity index 100% rename from osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-hyperdash.osu rename to osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/basic-hyperdash.osu diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/basic.osu similarity index 96% rename from osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic.osu rename to osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/basic.osu index 40b4409760..abd2ff2ee6 100644 --- a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic.osu +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/basic.osu @@ -1,27 +1,27 @@ -osu file format v14 - -[Difficulty] -HPDrainRate:6 -CircleSize:4 -OverallDifficulty:7 -ApproachRate:8.3 -SliderMultiplier:1.6 -SliderTickRate:1 - -[TimingPoints] -500,500,4,2,1,50,1,0 -13426,-100,4,3,1,45,0,0 -14884,-100,4,2,1,50,0,0 - -[HitObjects] -96,192,500,6,0,L|416:192,2,320 -256,192,3000,12,0,4000,0:0:0:0: -256,192,4500,12,0,5500,0:0:0:0: -256,192,6000,12,0,6500,0:0:0:0: -256,128,7000,6,0,L|352:128,4,80 -32,192,8500,6,0,B|32:384|256:384|256:192|256:192|256:0|512:0|512:192,1,800 -256,192,11500,12,0,12000,0:0:0:0: -512,320,12500,6,0,B|0:256|0:256|512:96|512:96|256:32,1,1280 -256,256,17000,6,0,L|160:256,4,80 -256,192,18500,12,0,19450,0:0:0:0: -216,231,19875,6,0,B|216:135|280:135|344:135|344:199|344:263|248:327|248:327|120:327|120:327|56:39|408:39|408:39|472:150|408:342,1,1280 +osu file format v14 + +[Difficulty] +HPDrainRate:6 +CircleSize:4 +OverallDifficulty:7 +ApproachRate:8.3 +SliderMultiplier:1.6 +SliderTickRate:1 + +[TimingPoints] +500,500,4,2,1,50,1,0 +13426,-100,4,3,1,45,0,0 +14884,-100,4,2,1,50,0,0 + +[HitObjects] +96,192,500,6,0,L|416:192,2,320 +256,192,3000,12,0,4000,0:0:0:0: +256,192,4500,12,0,5500,0:0:0:0: +256,192,6000,12,0,6500,0:0:0:0: +256,128,7000,6,0,L|352:128,4,80 +32,192,8500,6,0,B|32:384|256:384|256:192|256:192|256:0|512:0|512:192,1,800 +256,192,11500,12,0,12000,0:0:0:0: +512,320,12500,6,0,B|0:256|0:256|512:96|512:96|256:32,1,1280 +256,256,17000,6,0,L|160:256,4,80 +256,192,18500,12,0,19450,0:0:0:0: +216,231,19875,6,0,B|216:135|280:135|344:135|344:199|344:263|248:327|248:327|120:327|120:327|56:39|408:39|408:39|472:150|408:342,1,1280 diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/diffcalc-test.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/diffcalc-test.osu similarity index 100% rename from osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/diffcalc-test.osu rename to osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/diffcalc-test.osu diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/hardrock-repeat-slider-expected-conversion.json similarity index 100% rename from osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider-expected-conversion.json rename to osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/hardrock-repeat-slider-expected-conversion.json diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/hardrock-repeat-slider.osu similarity index 100% rename from osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider.osu rename to osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/hardrock-repeat-slider.osu diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/hardrock-spinner-expected-conversion.json similarity index 100% rename from osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner-expected-conversion.json rename to osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/hardrock-spinner-expected-conversion.json diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/hardrock-spinner.osu similarity index 100% rename from osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner.osu rename to osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/hardrock-spinner.osu diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/hardrock-stream-expected-conversion.json similarity index 100% rename from osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream-expected-conversion.json rename to osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/hardrock-stream-expected-conversion.json diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/hardrock-stream.osu similarity index 100% rename from osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream.osu rename to osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/hardrock-stream.osu diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/pixel-jump-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/pixel-jump-expected-conversion.json similarity index 100% rename from osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/pixel-jump-expected-conversion.json rename to osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/pixel-jump-expected-conversion.json diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/pixel-jump.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/pixel-jump.osu similarity index 100% rename from osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/pixel-jump.osu rename to osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/pixel-jump.osu diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/right-bound-hr-offset-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/right-bound-hr-offset-expected-conversion.json similarity index 100% rename from osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/right-bound-hr-offset-expected-conversion.json rename to osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/right-bound-hr-offset-expected-conversion.json diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/right-bound-hr-offset.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/right-bound-hr-offset.osu similarity index 100% rename from osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/right-bound-hr-offset.osu rename to osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/right-bound-hr-offset.osu diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/slider-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/slider-expected-conversion.json similarity index 100% rename from osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/slider-expected-conversion.json rename to osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/slider-expected-conversion.json diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/slider.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/slider.osu similarity index 100% rename from osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/slider.osu rename to osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/slider.osu diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-and-circles-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/spinner-and-circles-expected-conversion.json similarity index 100% rename from osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-and-circles-expected-conversion.json rename to osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/spinner-and-circles-expected-conversion.json diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-and-circles.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/spinner-and-circles.osu similarity index 100% rename from osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-and-circles.osu rename to osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/spinner-and-circles.osu diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/spinner-expected-conversion.json similarity index 100% rename from osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-expected-conversion.json rename to osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/spinner-expected-conversion.json diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-precision-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/spinner-precision-expected-conversion.json similarity index 100% rename from osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-precision-expected-conversion.json rename to osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/spinner-precision-expected-conversion.json diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-precision.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/spinner-precision.osu similarity index 100% rename from osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner-precision.osu rename to osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/spinner-precision.osu diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/spinner.osu similarity index 100% rename from osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/spinner.osu rename to osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/spinner.osu diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/tiny-ticks-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/tiny-ticks-expected-conversion.json similarity index 100% rename from osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/tiny-ticks-expected-conversion.json rename to osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/tiny-ticks-expected-conversion.json diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/tiny-ticks.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/tiny-ticks.osu similarity index 100% rename from osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/tiny-ticks.osu rename to osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/tiny-ticks.osu diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/v8-tick-distance-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/v8-tick-distance-expected-conversion.json similarity index 100% rename from osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/v8-tick-distance-expected-conversion.json rename to osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/v8-tick-distance-expected-conversion.json diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/v8-tick-distance.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/v8-tick-distance.osu similarity index 100% rename from osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/v8-tick-distance.osu rename to osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/v8-tick-distance.osu From 40ff95d586aa0e45079e6aff38fb99a5a3e9b0f2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 6 Dec 2023 12:19:12 +0900 Subject: [PATCH 1847/4092] Fix diffcalc tests --- osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs index 880316f177..6a70173c4a 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Catch.Tests { public class CatchDifficultyCalculatorTest : DifficultyCalculatorTest { - protected override string ResourceAssembly => "osu.Game.Rulesets.Catch"; + protected override string ResourceAssembly => "osu.Game.Rulesets.Catch.Tests"; [TestCase(4.0505463516206195d, 127, "diffcalc-test")] public void Test(double expectedStarRating, int expectedMaxCombo, string name) From 0af16732b81a39ba6347bb6c036fe6a5b5a71800 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Dec 2023 13:38:46 +0900 Subject: [PATCH 1848/4092] Change default slider velocity to 1.4 This is the default in osu!stable and plays better than 1.0. --- osu.Game/Beatmaps/BeatmapDifficulty.cs | 2 +- osu.Game/Screens/Edit/Setup/DifficultySection.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs index 217f3b89a4..ac2267380d 100644 --- a/osu.Game/Beatmaps/BeatmapDifficulty.cs +++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs @@ -18,7 +18,7 @@ namespace osu.Game.Beatmaps public float OverallDifficulty { get; set; } = IBeatmapDifficultyInfo.DEFAULT_DIFFICULTY; public float ApproachRate { get; set; } = IBeatmapDifficultyInfo.DEFAULT_DIFFICULTY; - public double SliderMultiplier { get; set; } = 1; + public double SliderMultiplier { get; set; } = 1.4; public double SliderTickRate { get; set; } = 1; public BeatmapDifficulty() diff --git a/osu.Game/Screens/Edit/Setup/DifficultySection.cs b/osu.Game/Screens/Edit/Setup/DifficultySection.cs index 1915b0cfd1..8028df6c0f 100644 --- a/osu.Game/Screens/Edit/Setup/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Setup/DifficultySection.cs @@ -88,7 +88,7 @@ namespace osu.Game.Screens.Edit.Setup Description = EditorSetupStrings.BaseVelocityDescription, Current = new BindableDouble(Beatmap.Difficulty.SliderMultiplier) { - Default = 1, + Default = 1.4, MinValue = 0.4, MaxValue = 3.6, Precision = 0.01f, From f9dd5bd828fc6f2df9677141649da5733eade565 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Dec 2023 13:39:59 +0900 Subject: [PATCH 1849/4092] Remove unused constant --- osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 2551321ff2..b9d65d2631 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -31,11 +31,6 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps ///
private const float osu_base_scoring_distance = 100; - /// - /// Drum roll distance that results in a duration of 1 speed-adjusted beat length. - /// - private const float taiko_base_distance = 100; - private readonly bool isForCurrentRuleset; public TaikoBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) From 79826dee58b62c72f19352a03bd3da4db829f725 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Dec 2023 13:50:10 +0900 Subject: [PATCH 1850/4092] Fix tests which were relying on `SliderMultiplier==1` --- osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs | 6 +++++- osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs | 6 +++++- .../Gameplay/TestSceneDrawableScrollingRuleset.cs | 12 +++++++++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs index 3c222662f5..8c179fe9a9 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs @@ -63,7 +63,11 @@ namespace osu.Game.Rulesets.Catch.Tests BeatmapInfo = { Ruleset = ruleset, - Difficulty = new BeatmapDifficulty { CircleSize = 3.6f } + Difficulty = new BeatmapDifficulty + { + CircleSize = 3.6f, + SliderMultiplier = 1, + }, } }; diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index d6a030ba64..716d2e0756 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -507,7 +507,11 @@ namespace osu.Game.Rulesets.Osu.Tests HitObjects = { slider }, BeatmapInfo = { - Difficulty = new BeatmapDifficulty { SliderTickRate = tickRate ?? 3 }, + Difficulty = new BeatmapDifficulty + { + SliderTickRate = tickRate ?? 3, + SliderMultiplier = 1, + }, Ruleset = new OsuRuleset().RulesetInfo, }, ControlPointInfo = cpi, diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index 4c898feb48..697fb787e6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -251,7 +251,17 @@ namespace osu.Game.Tests.Visual.Gameplay /// The . private IBeatmap createBeatmap(Func createAction = null) { - var beatmap = new Beatmap { BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo } }; + var beatmap = new Beatmap + { + BeatmapInfo = + { + Difficulty = new BeatmapDifficulty + { + SliderMultiplier = 1 + }, + Ruleset = new OsuRuleset().RulesetInfo + } + }; for (int i = 0; i < 10; i++) { From 160edcd2707d64aede30385eb9bf59ef4641fb7b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 6 Dec 2023 07:09:46 +0300 Subject: [PATCH 1851/4092] Move objects at a constant speed whenever possible --- osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs | 54 ++++++++++++++++++----- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs index 16517a2f36..0defebc1c3 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs @@ -27,8 +27,8 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModFreezeFrame), typeof(ModWithVisibilityAdjustment) }).ToArray(); - private static readonly Vector3 camera_position = new Vector3(OsuPlayfield.BASE_SIZE.X * 0.5f, OsuPlayfield.BASE_SIZE.Y * 0.5f, -100); - private readonly float minDepth = depthForScale(1.5f); + private static readonly Vector3 camera_position = new Vector3(OsuPlayfield.BASE_SIZE.X * 0.5f, OsuPlayfield.BASE_SIZE.Y * 0.5f, -200); + private readonly float sliderMinDepth = depthForScale(1.5f); // Depth at which slider's scale will be 1.5f [SettingSource("Maximum depth", "How far away objects appear.", 0)] public BindableFloat MaxDepth { get; } = new BindableFloat(100) @@ -80,30 +80,60 @@ namespace osu.Game.Rulesets.Osu.Mods switch (drawable) { case DrawableHitCircle circle: - processObject(time, circle, 0); + processHitObject(time, circle); break; case DrawableSlider slider: - processObject(time, slider, slider.HitObject.Duration); + processSlider(time, slider); break; } } } - private void processObject(double time, DrawableOsuHitObject drawable, double duration) + private void processHitObject(double time, DrawableOsuHitObject drawable) { var hitObject = drawable.HitObject; + // Circles are always moving at the constant speed. They'll fade out before reaching the camera even at extreme conditions (AR 11, max depth). + double speed = MaxDepth.Value / hitObject.TimePreempt; + double appearTime = hitObject.StartTime - hitObject.TimePreempt; + float z = MaxDepth.Value - (float)((Math.Max(time, appearTime) - appearTime) * speed); + + float scale = scaleForDepth(z); + drawable.Position = toPlayfieldPosition(scale, hitObject.Position); + drawable.Scale = new Vector2(scale); + } + + private void processSlider(double time, DrawableSlider drawableSlider) + { + var hitObject = drawableSlider.HitObject; + double baseSpeed = MaxDepth.Value / hitObject.TimePreempt; - double offsetAfterStartTime = duration + hitObject.MaximumJudgementOffset + 500; - double slowSpeed = Math.Min(-minDepth / offsetAfterStartTime, baseSpeed); + double appearTime = hitObject.StartTime - hitObject.TimePreempt; + + // Allow slider to move at a constant speed if its scale at the end time will be lower than 1.5f + float zEnd = MaxDepth.Value - (float)((Math.Max(hitObject.StartTime + hitObject.Duration, appearTime) - appearTime) * baseSpeed); + + if (zEnd > sliderMinDepth) + { + processHitObject(time, drawableSlider); + return; + } + + double offsetAfterStartTime = hitObject.Duration + 500; + double slowSpeed = Math.Min(-sliderMinDepth / offsetAfterStartTime, baseSpeed); double decelerationTime = hitObject.TimePreempt * 0.2; float decelerationDistance = (float)(decelerationTime * (baseSpeed + slowSpeed) * 0.5); float z; - if (time < hitObject.StartTime) + if (time < hitObject.StartTime - decelerationTime) + { + float fullDistance = decelerationDistance + (float)(baseSpeed * (hitObject.TimePreempt - decelerationTime)); + z = fullDistance - (float)((Math.Max(time, appearTime) - appearTime) * baseSpeed); + } + else if (time < hitObject.StartTime) { double timeOffset = time - (hitObject.StartTime - decelerationTime); double deceleration = (slowSpeed - baseSpeed) / decelerationTime; @@ -116,13 +146,13 @@ namespace osu.Game.Rulesets.Osu.Mods } float scale = scaleForDepth(z); - drawable.Position = toPlayfieldPosition(scale, hitObject.Position); - drawable.Scale = new Vector2(scale); + drawableSlider.Position = toPlayfieldPosition(scale, hitObject.Position); + drawableSlider.Scale = new Vector2(scale); } - private static float scaleForDepth(float depth) => 100 / (depth - camera_position.Z); + private static float scaleForDepth(float depth) => -camera_position.Z / Math.Max(1f, depth - camera_position.Z); - private static float depthForScale(float scale) => 100 / scale + camera_position.Z; + private static float depthForScale(float scale) => -camera_position.Z / scale + camera_position.Z; private static Vector2 toPlayfieldPosition(float scale, Vector2 positionAtZeroDepth) { From 394ea73055e5a7592daccde911bad0fd04f9093c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 6 Dec 2023 14:50:03 +0900 Subject: [PATCH 1852/4092] Add some comments where truncations were added --- osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs | 2 ++ osu.Game.Rulesets.Catch/Objects/BananaShower.cs | 1 + 2 files changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index 50e6fd9673..02d4cdbb94 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -247,6 +247,8 @@ namespace osu.Game.Rulesets.Catch.Beatmaps currentObject.DistanceToHyperDash = 0; int thisDirection = nextObject.EffectiveX > currentObject.EffectiveX ? 1 : -1; + + // Int truncation added to match osu!stable. double timeToNext = (int)nextObject.StartTime - (int)currentObject.StartTime - 1000f / 60f / 4; // 1/4th of a frame of grace time, taken from osu-stable double distanceToNext = Math.Abs(nextObject.EffectiveX - currentObject.EffectiveX) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth); float distanceToHyper = (float)(timeToNext * Catcher.BASE_DASH_SPEED - distanceToNext); diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs index abeb7fe61d..328cc2b52a 100644 --- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs @@ -23,6 +23,7 @@ namespace osu.Game.Rulesets.Catch.Objects private void createBananas(CancellationToken cancellationToken) { + // Int truncation added to match osu!stable. int startTime = (int)StartTime; int endTime = (int)EndTime; float spacing = (float)(EndTime - StartTime); From 43dc9082571901919914e43cbce34dd3db8ec89c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Dec 2023 15:59:29 +0900 Subject: [PATCH 1853/4092] Fix test value getting clobbered due to stupid stuff Don't even ask. Just smile and nod. --- .../Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index 697fb787e6..e4d39bb6de 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -198,7 +198,7 @@ namespace osu.Game.Tests.Visual.Gameplay { var beatmap = createBeatmap(); beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range }); - beatmap.Difficulty.SliderMultiplier = 2; + beatmap.BeatmapInfo.Difficulty.SliderMultiplier = 2; createTest(beatmap); AddStep("adjust time range", () => drawableRuleset.TimeRange.Value = 2000); @@ -237,7 +237,7 @@ namespace osu.Game.Tests.Visual.Gameplay }); private void assertPosition(int index, float relativeY) => AddAssert($"hitobject {index} at {relativeY}", - () => Precision.AlmostEquals(getDrawableHitObject(index)?.DrawPosition.Y ?? -1, yScale * relativeY)); + () => getDrawableHitObject(index)?.DrawPosition.Y / yScale ?? -1, () => Is.EqualTo(relativeY).Within(Precision.FLOAT_EPSILON)); private void setTime(double time) { From b5bae566c23afa718762527a224f96f0c0eba219 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Dec 2023 14:22:50 +0900 Subject: [PATCH 1854/4092] Fix incorrect slider velocity being written on export for osu!taiko beatmaps --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index b375a6f7ff..78c663195a 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -149,11 +149,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"OverallDifficulty: {beatmap.Difficulty.OverallDifficulty}")); writer.WriteLine(FormattableString.Invariant($"ApproachRate: {beatmap.Difficulty.ApproachRate}")); - // Taiko adjusts the slider multiplier (see: LEGACY_TAIKO_VELOCITY_MULTIPLIER) - writer.WriteLine(onlineRulesetID == 1 - ? FormattableString.Invariant($"SliderMultiplier: {beatmap.Difficulty.SliderMultiplier / LEGACY_TAIKO_VELOCITY_MULTIPLIER}") - : FormattableString.Invariant($"SliderMultiplier: {beatmap.Difficulty.SliderMultiplier}")); - + writer.WriteLine(FormattableString.Invariant($"SliderMultiplier: {beatmap.Difficulty.SliderMultiplier}")); writer.WriteLine(FormattableString.Invariant($"SliderTickRate: {beatmap.Difficulty.SliderTickRate}")); } From 1cb3c710ba316a8d6642f25b9a4472fc37c75a8d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Dec 2023 14:30:35 +0900 Subject: [PATCH 1855/4092] Remove complex implementation of taiko SV multiplier --- .../Editor/TestSceneTaikoEditorSaving.cs | 7 +-- .../Beatmaps/TaikoBeatmapConverter.cs | 43 ------------------- 2 files changed, 1 insertion(+), 49 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs index af7db2251b..1d5efb01e4 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs @@ -3,7 +3,6 @@ using NUnit.Framework; using osu.Framework.Utils; -using osu.Game.Rulesets.Taiko.Beatmaps; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Taiko.Tests.Editor @@ -27,11 +26,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Editor bool assertTaikoSliderMulitplier() { - // we can only assert value correctness on TaikoMultiplierAppliedDifficulty, because that is the final difficulty converted taiko beatmaps use. - // therefore, ensure that we have that difficulty type by calling .CopyFrom(), which is a no-op if the type is already correct. - var taikoDifficulty = new TaikoBeatmapConverter.TaikoMultiplierAppliedDifficulty(); - taikoDifficulty.CopyFrom(EditorBeatmap.Difficulty); - return Precision.AlmostEquals(taikoDifficulty.SliderMultiplier, 2); + return Precision.AlmostEquals(EditorBeatmap.Difficulty.SliderMultiplier, 2); } } } diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index b9d65d2631..5229d3ff23 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -10,7 +10,6 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Utils; using System.Threading; -using JetBrains.Annotations; using osu.Game.Audio; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Formats; @@ -43,12 +42,6 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps protected override Beatmap ConvertBeatmap(IBeatmap original, CancellationToken cancellationToken) { - if (!(original.Difficulty is TaikoMultiplierAppliedDifficulty)) - { - // Rewrite the beatmap info to add the slider velocity multiplier - original.Difficulty = new TaikoMultiplierAppliedDifficulty(original.Difficulty); - } - Beatmap converted = base.ConvertBeatmap(original, cancellationToken); if (original.BeatmapInfo.Ruleset.OnlineID == 0) @@ -218,41 +211,5 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps } protected override Beatmap CreateBeatmap() => new TaikoBeatmap(); - - // Important to note that this is subclassing a realm object. - // Realm doesn't allow this, but for now this can work since we aren't (in theory?) persisting this to the database. - // It is only used during beatmap conversion and processing. - internal class TaikoMultiplierAppliedDifficulty : BeatmapDifficulty - { - public TaikoMultiplierAppliedDifficulty(IBeatmapDifficultyInfo difficulty) - { - CopyFrom(difficulty); - } - - [UsedImplicitly] - public TaikoMultiplierAppliedDifficulty() - { - } - - #region Overrides of BeatmapDifficulty - - public override BeatmapDifficulty Clone() => new TaikoMultiplierAppliedDifficulty(this); - - public override void CopyTo(BeatmapDifficulty other) - { - base.CopyTo(other); - if (!(other is TaikoMultiplierAppliedDifficulty)) - other.SliderMultiplier /= LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER; - } - - public override void CopyFrom(IBeatmapDifficultyInfo other) - { - base.CopyFrom(other); - if (!(other is TaikoMultiplierAppliedDifficulty)) - SliderMultiplier *= LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER; - } - - #endregion - } } } From 8a0d152bcf74b3166019288f7860966624d25fe2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Dec 2023 14:57:52 +0900 Subject: [PATCH 1856/4092] Reapply legacy taiko velocity multiplier in all relevant places --- osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 2 +- osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 5229d3ff23..e63a65cd80 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -185,7 +185,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps else beatLength = timingPoint.BeatLength; - double sliderScoringPointDistance = osu_base_scoring_distance * beatmap.Difficulty.SliderMultiplier / beatmap.Difficulty.SliderTickRate; + double sliderScoringPointDistance = osu_base_scoring_distance * (beatmap.Difficulty.SliderMultiplier * LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER) / beatmap.Difficulty.SliderTickRate; // The velocity and duration of the taiko hit object - calculated as the velocity of a drum roll. double taikoVelocity = sliderScoringPointDistance * beatmap.Difficulty.SliderTickRate; diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 2a76782a08..6845ae2efa 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Taiko.Objects TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); EffectControlPoint effectPoint = controlPointInfo.EffectPointAt(StartTime); - double scoringDistance = base_distance * difficulty.SliderMultiplier * effectPoint.ScrollSpeed; + double scoringDistance = base_distance * (difficulty.SliderMultiplier * LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER) * effectPoint.ScrollSpeed; Velocity = scoringDistance / timingPoint.BeatLength; TickRate = difficulty.SliderTickRate == 3 ? 3 : 4; diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 2af4c0c2e8..24391d544f 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -10,6 +10,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Formats; using osu.Game.Configuration; using osu.Game.Input.Handlers; using osu.Game.Replays; @@ -70,7 +71,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected virtual double ComputeTimeRange() { // Taiko scrolls at a constant 100px per 1000ms. More notes become visible as the playfield is lengthened. - const float scroll_rate = 10; + const float scroll_rate = 10 / LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER; // Since the time range will depend on a positional value, it is referenced to the x480 pixel space. // Width is used because it defines how many notes fit on the playfield. From 1b50d1011ab7dcac5ff2135e13a10199d975a894 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Dec 2023 15:26:32 +0900 Subject: [PATCH 1857/4092] Move constant local to taiko --- .../Beatmaps/TaikoBeatmapConverter.cs | 16 +++++++++++++--- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 8 ++++---- .../UI/DrawableTaikoRuleset.cs | 4 ++-- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 6 ------ 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index e63a65cd80..5975458f16 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -12,13 +12,23 @@ using osu.Framework.Utils; using System.Threading; using osu.Game.Audio; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Beatmaps.Formats; using osu.Game.Rulesets.Objects.Legacy; namespace osu.Game.Rulesets.Taiko.Beatmaps { internal class TaikoBeatmapConverter : BeatmapConverter { + /// + /// A speed multiplier applied globally to osu!taiko. + /// + /// + /// osu! is generally slower than taiko, so a factor was historically added to increase speed for converts. + /// This must be used everywhere slider length or beat length is used in taiko. + /// + /// Of note, this has never been exposed to the end user, and is considered a hidden internal multiplier. + /// + public const float VELOCITY_MULTIPLIER = 1.4f; + /// /// Because swells are easier in taiko than spinners are in osu!, /// legacy taiko multiplies a factor when converting the number of required hits. @@ -173,7 +183,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps double distance = pathData.Path.ExpectedDistance.Value ?? 0; // Do not combine the following two lines! - distance *= LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER; + distance *= VELOCITY_MULTIPLIER; distance *= spans; TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime); @@ -185,7 +195,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps else beatLength = timingPoint.BeatLength; - double sliderScoringPointDistance = osu_base_scoring_distance * (beatmap.Difficulty.SliderMultiplier * LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER) / beatmap.Difficulty.SliderTickRate; + double sliderScoringPointDistance = osu_base_scoring_distance * (beatmap.Difficulty.SliderMultiplier * TaikoBeatmapConverter.VELOCITY_MULTIPLIER) / beatmap.Difficulty.SliderTickRate; // The velocity and duration of the taiko hit object - calculated as the velocity of a drum roll. double taikoVelocity = sliderScoringPointDistance * beatmap.Difficulty.SliderTickRate; diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 6845ae2efa..f3143de345 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -1,14 +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 osu.Game.Rulesets.Objects.Types; using System.Threading; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Beatmaps.Formats; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Beatmaps; using osuTK; namespace osu.Game.Rulesets.Taiko.Objects @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Taiko.Objects TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); EffectControlPoint effectPoint = controlPointInfo.EffectPointAt(StartTime); - double scoringDistance = base_distance * (difficulty.SliderMultiplier * LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER) * effectPoint.ScrollSpeed; + double scoringDistance = base_distance * (difficulty.SliderMultiplier * TaikoBeatmapConverter.VELOCITY_MULTIPLIER) * effectPoint.ScrollSpeed; Velocity = scoringDistance / timingPoint.BeatLength; TickRate = difficulty.SliderTickRate == 3 ? 3 : 4; @@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Taiko.Objects double IHasDistance.Distance => Duration * Velocity; SliderPath IHasPath.Path - => new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(1) }, ((IHasDistance)this).Distance / LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER); + => new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(1) }, ((IHasDistance)this).Distance / TaikoBeatmapConverter.VELOCITY_MULTIPLIER); #endregion } diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 24391d544f..88085dfe97 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -10,13 +10,13 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Formats; using osu.Game.Configuration; using osu.Game.Input.Handlers; using osu.Game.Replays; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Beatmaps; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Replays; using osu.Game.Rulesets.Timing; @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected virtual double ComputeTimeRange() { // Taiko scrolls at a constant 100px per 1000ms. More notes become visible as the playfield is lengthened. - const float scroll_rate = 10 / LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER; + const float scroll_rate = 10 / TaikoBeatmapConverter.VELOCITY_MULTIPLIER; // Since the time range will depend on a positional value, it is referenced to the x480 pixel space. // Width is used because it defines how many notes fit on the playfield. diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 78c663195a..290d29090a 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -23,12 +23,6 @@ namespace osu.Game.Beatmaps.Formats { public const int FIRST_LAZER_VERSION = 128; - /// - /// osu! is generally slower than taiko, so a factor is added to increase - /// speed. This must be used everywhere slider length or beat length is used. - /// - public const float LEGACY_TAIKO_VELOCITY_MULTIPLIER = 1.4f; - private readonly IBeatmap beatmap; private readonly ISkin? skin; From 51f9377e3de5181ad3a31697495e3e64deaa0c12 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Dec 2023 16:03:32 +0900 Subject: [PATCH 1858/4092] Remove pointless test --- .../Editor/TestSceneTaikoEditorSaving.cs | 33 ------------------- 1 file changed, 33 deletions(-) delete mode 100644 osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs deleted file mode 100644 index 1d5efb01e4..0000000000 --- a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs +++ /dev/null @@ -1,33 +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 NUnit.Framework; -using osu.Framework.Utils; -using osu.Game.Tests.Visual; - -namespace osu.Game.Rulesets.Taiko.Tests.Editor -{ - public partial class TestSceneTaikoEditorSaving : EditorSavingTestScene - { - protected override Ruleset CreateRuleset() => new TaikoRuleset(); - - [Test] - public void TestTaikoSliderMultiplier() - { - AddStep("Set slider multiplier", () => EditorBeatmap.Difficulty.SliderMultiplier = 2); - - SaveEditor(); - - AddAssert("Beatmap has correct slider multiplier", assertTaikoSliderMulitplier); - - ReloadEditorToSameBeatmap(); - - AddAssert("Beatmap still has correct slider multiplier", assertTaikoSliderMulitplier); - - bool assertTaikoSliderMulitplier() - { - return Precision.AlmostEquals(EditorBeatmap.Difficulty.SliderMultiplier, 2); - } - } - } -} From b0878e36cf39baeb92da9b646e2a47c62acf7891 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 6 Dec 2023 10:30:21 +0300 Subject: [PATCH 1859/4092] Fix stacks having incorrect position --- osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs index 0defebc1c3..f71acf95b8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Osu.Mods float z = MaxDepth.Value - (float)((Math.Max(time, appearTime) - appearTime) * speed); float scale = scaleForDepth(z); - drawable.Position = toPlayfieldPosition(scale, hitObject.Position); + drawable.Position = toPlayfieldPosition(scale, hitObject.StackedPosition); drawable.Scale = new Vector2(scale); } @@ -146,7 +146,7 @@ namespace osu.Game.Rulesets.Osu.Mods } float scale = scaleForDepth(z); - drawableSlider.Position = toPlayfieldPosition(scale, hitObject.Position); + drawableSlider.Position = toPlayfieldPosition(scale, hitObject.StackedPosition); drawableSlider.Scale = new Vector2(scale); } From 01c614935b2edc68a1ee37de654e22406890021d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Dec 2023 17:09:12 +0900 Subject: [PATCH 1860/4092] Revert "Remove pointless test" This reverts commit 51f9377e3de5181ad3a31697495e3e64deaa0c12. --- .../Editor/TestSceneTaikoEditorSaving.cs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs new file mode 100644 index 0000000000..1d5efb01e4 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs @@ -0,0 +1,33 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Utils; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Taiko.Tests.Editor +{ + public partial class TestSceneTaikoEditorSaving : EditorSavingTestScene + { + protected override Ruleset CreateRuleset() => new TaikoRuleset(); + + [Test] + public void TestTaikoSliderMultiplier() + { + AddStep("Set slider multiplier", () => EditorBeatmap.Difficulty.SliderMultiplier = 2); + + SaveEditor(); + + AddAssert("Beatmap has correct slider multiplier", assertTaikoSliderMulitplier); + + ReloadEditorToSameBeatmap(); + + AddAssert("Beatmap still has correct slider multiplier", assertTaikoSliderMulitplier); + + bool assertTaikoSliderMulitplier() + { + return Precision.AlmostEquals(EditorBeatmap.Difficulty.SliderMultiplier, 2); + } + } + } +} From 853d67f9cc587bb0f86cf751cd96d013eefd3488 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Dec 2023 17:11:28 +0900 Subject: [PATCH 1861/4092] Add test coverage of correct multiplier written to `.osu` file --- .../Editor/TestSceneTaikoEditorSaving.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs index 1d5efb01e4..64ce97da7b 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs @@ -1,9 +1,15 @@ // 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.Globalization; +using System.IO; +using System.Linq; using NUnit.Framework; +using osu.Framework.Extensions; using osu.Framework.Utils; using osu.Game.Tests.Visual; +using SharpCompress.Archives.Zip; namespace osu.Game.Rulesets.Taiko.Tests.Editor { @@ -11,6 +17,40 @@ namespace osu.Game.Rulesets.Taiko.Tests.Editor { protected override Ruleset CreateRuleset() => new TaikoRuleset(); + [Test] + public void TestTaikoSliderMultiplierInExport() + { + AddStep("Set slider multiplier", () => EditorBeatmap.Difficulty.SliderMultiplier = 2); + + SaveEditor(); + AddStep("export beatmap", () => Game.BeatmapManager.Export(EditorBeatmap.BeatmapInfo.BeatmapSet!).WaitSafely()); + + AddAssert("check slider multiplier correct in file", () => + { + string export = LocalStorage.GetFiles("exports").First(); + + using (var stream = LocalStorage.GetStream(export)) + using (var zip = ZipArchive.Open(stream)) + { + using (var osuStream = zip.Entries.First().OpenEntryStream()) + using (var reader = new StreamReader(osuStream)) + { + string? line; + + while ((line = reader.ReadLine()) != null) + { + if (line.StartsWith("SliderMultiplier", StringComparison.Ordinal)) + { + return float.Parse(line.Split(':', StringSplitOptions.TrimEntries).Last(), provider: CultureInfo.InvariantCulture); + } + } + } + } + + return 0; + }, () => Is.EqualTo(2)); + } + [Test] public void TestTaikoSliderMultiplier() { From 44beecb840fae9db881b862caad12e2d339d0956 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Dec 2023 17:16:20 +0900 Subject: [PATCH 1862/4092] Test multiple values, including default --- .../Editor/TestSceneTaikoEditorSaving.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs index 64ce97da7b..fb05502158 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs @@ -8,6 +8,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Extensions; using osu.Framework.Utils; +using osu.Game.Beatmaps; using osu.Game.Tests.Visual; using SharpCompress.Archives.Zip; @@ -17,10 +18,14 @@ namespace osu.Game.Rulesets.Taiko.Tests.Editor { protected override Ruleset CreateRuleset() => new TaikoRuleset(); - [Test] - public void TestTaikoSliderMultiplierInExport() + [TestCase(null)] + [TestCase(1f)] + [TestCase(2f)] + [TestCase(2.4f)] + public void TestTaikoSliderMultiplierInExport(float? multiplier) { - AddStep("Set slider multiplier", () => EditorBeatmap.Difficulty.SliderMultiplier = 2); + if (multiplier.HasValue) + AddStep("Set slider multiplier", () => EditorBeatmap.Difficulty.SliderMultiplier = multiplier.Value); SaveEditor(); AddStep("export beatmap", () => Game.BeatmapManager.Export(EditorBeatmap.BeatmapInfo.BeatmapSet!).WaitSafely()); @@ -48,7 +53,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Editor } return 0; - }, () => Is.EqualTo(2)); + }, () => Is.EqualTo(multiplier ?? new BeatmapDifficulty().SliderMultiplier).Within(Precision.FLOAT_EPSILON)); } [Test] From ca991f1f546f0a20333d49265513466056661426 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Dec 2023 17:18:35 +0900 Subject: [PATCH 1863/4092] Move flags local to `EndlessPlayer` --- osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs | 4 ++++ osu.Game/Screens/Play/Player.cs | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs index be7ddd115b..262ce263bd 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs @@ -290,6 +290,10 @@ namespace osu.Game.Overlays.SkinEditor { protected override UserActivity? InitialActivity => null; + public override bool DisallowExternalBeatmapRulesetChanges => true; + + public override bool? AllowGlobalTrackControl => false; + public EndlessPlayer(Func, Score> createScore) : base(createScore, new PlayerConfiguration { diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 48411e9c87..1c97efcff7 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -59,10 +59,6 @@ namespace osu.Game.Screens.Play protected override bool PlayExitSound => !isRestarting; - public override bool DisallowExternalBeatmapRulesetChanges => true; - - public override bool? AllowGlobalTrackControl => false; - protected override UserActivity InitialActivity => new UserActivity.InSoloGame(Beatmap.Value.BeatmapInfo, Ruleset.Value); public override float BackgroundParallaxAmount => 0.1f; From faf60cec919637b0c8b04b3c69b15b909cfd90f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 6 Dec 2023 10:11:41 +0100 Subject: [PATCH 1864/4092] Extend test coverage of skin editor open to fail better --- .../Visual/Navigation/TestSceneSkinEditorNavigation.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs index fa85c8c9f8..57f1b2fbe9 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs @@ -283,15 +283,17 @@ namespace osu.Game.Tests.Visual.Navigation openSkinEditor(); } - [Test] - public void TestOpenSkinEditorGameplaySceneWhenDifferentRulesetActive() + [TestCase(1)] + [TestCase(2)] + [TestCase(3)] + public void TestOpenSkinEditorGameplaySceneWhenDifferentRulesetActive(int rulesetId) { BeatmapSetInfo beatmapSet = null!; AddStep("import beatmap", () => beatmapSet = BeatmapImportHelper.LoadQuickOszIntoOsu(Game).GetResultSafely()); - AddStep("select mania difficulty", () => + AddStep($"select difficulty for ruleset w/ ID {rulesetId}", () => { - var beatmap = beatmapSet.Beatmaps.First(b => b.Ruleset.OnlineID == 3); + var beatmap = beatmapSet.Beatmaps.First(b => b.Ruleset.OnlineID == rulesetId); Game.Beatmap.Value = Game.BeatmapManager.GetWorkingBeatmap(beatmap); }); From f239d03d75aaeb02118f5a316c6febf135b291dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 6 Dec 2023 10:12:25 +0100 Subject: [PATCH 1865/4092] Forcibly change ruleset to correct one before entering gameplay from main menu Closes #25663 (again). As it turns out, in some scenarios it can be the case that the current game-global `Beatmap` is not valid for the current game-global `Ruleset`. The validity of one and the other in conjunction is only really validated by the song select screen; elsewhere there is no guarantee that the global beatmap is playable using the global ruleset. However, this only comes up in very specific circumstances, namely one: when trying to autoplay a catch beatmap with osu! ruleset globally active via the skin editor flow. `Player` is responsible for retrieving the beatmap to be played. It does so by invoking the appropriate beatmap converter and asking it if the beatmap can be converted: https://github.com/ppy/osu/blob/6d64538d7a3130df63574eb75a8ebe044154c799/osu.Game/Beatmaps/WorkingBeatmap.cs#L262-L266 If the code above throws, `Player` actually silently covers for this, by trying the beatmap's default ruleset instead: https://github.com/ppy/osu/blob/6d64538d7a3130df63574eb75a8ebe044154c799/osu.Game/Screens/Play/Player.cs#L529-L536 However, for the pairing of osu! ruleset and catch beatmap, this fails, as `OsuBeatmapConverter`'s condition necessary for permitting conversion is that the objects have a defined position: https://github.com/ppy/osu/blob/6d64538d7a3130df63574eb75a8ebe044154c799/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs#L25 which they will do, due to the fact that all catch beatmaps are really just osu! beatmaps but with conversion steps applied, and thus `Player` succeeds to load the catch beatmap in osu! ruleset. In the skin editor scenario, this would lead to the secondary failure of the skin editor trying to apply `CatchModAutoplay` on top of all of that, which would fail at the hard-cast of the beatmap to `CatchBeatmap`. --- osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs index 262ce263bd..bedaf12c9b 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs @@ -17,6 +17,7 @@ using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; +using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens; @@ -58,6 +59,9 @@ namespace osu.Game.Overlays.SkinEditor [Resolved] private Bindable> mods { get; set; } = null!; + [Resolved] + private Bindable ruleset { get; set; } = null!; + [Resolved] private IBindable beatmap { get; set; } = null!; @@ -153,7 +157,11 @@ namespace osu.Game.Overlays.SkinEditor if (screen is Player) return; - var replayGeneratingMod = beatmap.Value.BeatmapInfo.Ruleset.CreateInstance().GetAutoplayMod(); + // the validity of the current game-wide beatmap + ruleset combination is enforced by song select. + // if we're anywhere else, the state is unknown and may not make sense, so forcibly set something that does. + if (screen is not PlaySongSelect) + ruleset.Value = beatmap.Value.BeatmapInfo.Ruleset; + var replayGeneratingMod = ruleset.Value.CreateInstance().GetAutoplayMod(); IReadOnlyList usableMods = mods.Value; From b8694aba98631438d64d20820c05dd6351cee14f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Dec 2023 22:00:35 +0900 Subject: [PATCH 1866/4092] Remove unnecessary prefix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 5975458f16..010b1f0a7a 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -195,7 +195,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps else beatLength = timingPoint.BeatLength; - double sliderScoringPointDistance = osu_base_scoring_distance * (beatmap.Difficulty.SliderMultiplier * TaikoBeatmapConverter.VELOCITY_MULTIPLIER) / beatmap.Difficulty.SliderTickRate; + double sliderScoringPointDistance = osu_base_scoring_distance * (beatmap.Difficulty.SliderMultiplier * VELOCITY_MULTIPLIER) / beatmap.Difficulty.SliderTickRate; // The velocity and duration of the taiko hit object - calculated as the velocity of a drum roll. double taikoVelocity = sliderScoringPointDistance * beatmap.Difficulty.SliderTickRate; From a8f3a0533ac72da3a7abd1696770c27a21b4c692 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 6 Dec 2023 16:35:59 +0100 Subject: [PATCH 1867/4092] Use 4th order BSpline by default --- .../Editor/TestSceneSliderPlacementBlueprint.cs | 8 ++++---- .../Sliders/Components/PathControlPointVisualiser.cs | 2 +- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index f889999fe3..bbded55732 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -312,7 +312,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertLength(808, tolerance: 10); assertControlPointCount(5); - assertControlPointType(0, PathType.BSpline(3)); + assertControlPointType(0, PathType.BSpline(4)); assertControlPointType(1, null); assertControlPointType(2, null); assertControlPointType(3, null); @@ -337,9 +337,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertLength(600, tolerance: 10); assertControlPointCount(4); - assertControlPointType(0, PathType.BSpline(3)); - assertControlPointType(1, PathType.BSpline(3)); - assertControlPointType(2, PathType.BSpline(3)); + assertControlPointType(0, PathType.BSpline(4)); + assertControlPointType(1, PathType.BSpline(4)); + assertControlPointType(2, PathType.BSpline(4)); assertControlPointType(3, null); } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 3add95b2b2..24e2210b45 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -373,7 +373,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components curveTypeItems.Add(createMenuItemForPathType(PathType.LINEAR)); curveTypeItems.Add(createMenuItemForPathType(PathType.PERFECT_CURVE)); curveTypeItems.Add(createMenuItemForPathType(PathType.BEZIER)); - curveTypeItems.Add(createMenuItemForPathType(PathType.BSpline(3))); + curveTypeItems.Add(createMenuItemForPathType(PathType.BSpline(4))); if (selectedPieces.Any(piece => piece.ControlPoint.Type?.Type == SplineType.Catmull)) curveTypeItems.Add(createMenuItemForPathType(PathType.CATMULL)); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index e13a46d0cd..672ac43f2c 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders [Resolved(CanBeNull = true)] private FreehandSliderToolboxGroup freehandToolboxGroup { get; set; } - private readonly IncrementalBSplineBuilder bSplineBuilder = new IncrementalBSplineBuilder(); + private readonly IncrementalBSplineBuilder bSplineBuilder = new IncrementalBSplineBuilder { Degree = 4 }; protected override bool IsValidForPlacement => HitObject.Path.HasValidLength; @@ -239,7 +239,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { if (state == SliderPlacementState.Drawing) { - segmentStart.Type = PathType.BSpline(3); + segmentStart.Type = PathType.BSpline(4); return; } @@ -335,7 +335,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders if (segment.Count == 0) continue; - HitObject.Path.ControlPoints.Add(new PathControlPoint(segment[0], PathType.BSpline(3))); + HitObject.Path.ControlPoints.Add(new PathControlPoint(segment[0], PathType.BSpline(4))); for (int j = 1; j < segment.Count - 1; j++) HitObject.Path.ControlPoints.Add(new PathControlPoint(segment[j])); From 22287f3a7f3acc256f13d5759665e2dbf4a27983 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 6 Dec 2023 16:36:24 +0100 Subject: [PATCH 1868/4092] decrease max tolerance --- osu.Game.Rulesets.Osu/Edit/FreehandSliderToolboxGroup.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/FreehandSliderToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/FreehandSliderToolboxGroup.cs index 87dd8636c9..7d1eb4e270 100644 --- a/osu.Game.Rulesets.Osu/Edit/FreehandSliderToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/FreehandSliderToolboxGroup.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Edit public BindableFloat Tolerance { get; } = new BindableFloat(2f) { MinValue = 0.05f, - MaxValue = 3f, + MaxValue = 2.0f, Precision = 0.01f }; @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Edit }; // We map internal ranges to a more standard range of values for display to the user. - private readonly BindableInt displayTolerance = new BindableInt(66) + private readonly BindableInt displayTolerance = new BindableInt(100) { MinValue = 5, MaxValue = 100 @@ -90,8 +90,8 @@ namespace osu.Game.Rulesets.Osu.Edit displayCornerThreshold.Value = internalToDisplayCornerThreshold(threshold.NewValue) ); - float displayToInternalTolerance(float v) => v / 33f; - int internalToDisplayTolerance(float v) => (int)Math.Round(v * 33f); + float displayToInternalTolerance(float v) => v / 50f; + int internalToDisplayTolerance(float v) => (int)Math.Round(v * 50f); float displayToInternalCornerThreshold(float v) => v / 100f; int internalToDisplayCornerThreshold(float v) => (int)Math.Round(v * 100f); From cb823f367f92fdeaf4db1a7ea3f75777f5a787bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 6 Dec 2023 18:16:45 +0100 Subject: [PATCH 1869/4092] Simplify `UserActivity` for serialisability over the wire Up until now, the `UserActivity` class hierarchy contained things like beatmap info, room info, full replay info, etc. While this was convenient, it is soon going to be less so, as the data is sent over the wire to the spectator server so that the user's activity can be broadcast to other clients. To counteract this without creating a second separate and slimmed-down class hierarchy, slim down the `UserActivity` structure to contain the bare minimum amounts of data such that the structures aren't overly large and complex to serialise, but also contain enough data that they can be used by receiving clients directly without having to do beatmap or score lookups. --- osu.Desktop/DiscordRichPresence.cs | 36 +-- osu.Game/Online/Chat/NowPlayingCommand.cs | 21 +- osu.Game/Online/SignalRWorkaroundTypes.cs | 16 ++ osu.Game/Users/UserActivity.cs | 276 +++++++++++++++------- 4 files changed, 227 insertions(+), 122 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index caf0a1d9fd..c66725e3e3 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -9,7 +9,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Logging; -using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Extensions; using osu.Game.Online.API; @@ -95,17 +94,18 @@ namespace osu.Desktop if (status.Value is UserStatusOnline && activity.Value != null) { - presence.State = truncate(activity.Value.GetStatus(privacyMode.Value == DiscordRichPresenceMode.Limited)); - presence.Details = truncate(getDetails(activity.Value)); + bool hideIdentifiableInformation = privacyMode.Value == DiscordRichPresenceMode.Limited; + presence.State = truncate(activity.Value.GetStatus(hideIdentifiableInformation)); + presence.Details = truncate(activity.Value.GetDetails(hideIdentifiableInformation) ?? string.Empty); - if (getBeatmap(activity.Value) is IBeatmapInfo beatmap && beatmap.OnlineID > 0) + if (getBeatmapID(activity.Value) is int beatmapId && beatmapId > 0) { presence.Buttons = new[] { new Button { Label = "View beatmap", - Url = $@"{api.WebsiteRootUrl}/beatmapsets/{beatmap.BeatmapSet?.OnlineID}#{ruleset.Value.ShortName}/{beatmap.OnlineID}" + Url = $@"{api.WebsiteRootUrl}/beatmaps/{beatmapId}?mode={ruleset.Value.ShortName}" } }; } @@ -159,40 +159,20 @@ namespace osu.Desktop }); } - private IBeatmapInfo? getBeatmap(UserActivity activity) + private int? getBeatmapID(UserActivity activity) { switch (activity) { case UserActivity.InGame game: - return game.BeatmapInfo; + return game.BeatmapID; case UserActivity.EditingBeatmap edit: - return edit.BeatmapInfo; + return edit.BeatmapID; } return null; } - private string getDetails(UserActivity activity) - { - switch (activity) - { - case UserActivity.InGame game: - return game.BeatmapInfo.ToString() ?? string.Empty; - - case UserActivity.EditingBeatmap edit: - return edit.BeatmapInfo.ToString() ?? string.Empty; - - case UserActivity.WatchingReplay watching: - return watching.BeatmapInfo?.ToString() ?? string.Empty; - - case UserActivity.InLobby lobby: - return privacyMode.Value == DiscordRichPresenceMode.Limited ? string.Empty : lobby.Room.Name.Value; - } - - return string.Empty; - } - protected override void Dispose(bool isDisposing) { client.Dispose(); diff --git a/osu.Game/Online/Chat/NowPlayingCommand.cs b/osu.Game/Online/Chat/NowPlayingCommand.cs index e7018d6993..0e6f6f0bf6 100644 --- a/osu.Game/Online/Chat/NowPlayingCommand.cs +++ b/osu.Game/Online/Chat/NowPlayingCommand.cs @@ -7,7 +7,6 @@ using System.Text; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Rulesets; @@ -33,9 +32,6 @@ namespace osu.Game.Online.Chat [Resolved] private IBindable currentRuleset { get; set; } = null!; - [Resolved] - private LocalisationManager localisation { get; set; } = null!; - private readonly Channel? target; /// @@ -52,23 +48,28 @@ namespace osu.Game.Online.Chat base.LoadComplete(); string verb; - IBeatmapInfo beatmapInfo; + + int beatmapOnlineID; + string beatmapDisplayTitle; switch (api.Activity.Value) { case UserActivity.InGame game: verb = "playing"; - beatmapInfo = game.BeatmapInfo; + beatmapOnlineID = game.BeatmapID; + beatmapDisplayTitle = game.BeatmapDisplayTitle; break; case UserActivity.EditingBeatmap edit: verb = "editing"; - beatmapInfo = edit.BeatmapInfo; + beatmapOnlineID = edit.BeatmapID; + beatmapDisplayTitle = edit.BeatmapDisplayTitle; break; default: verb = "listening to"; - beatmapInfo = currentBeatmap.Value.BeatmapInfo; + beatmapOnlineID = currentBeatmap.Value.BeatmapInfo.OnlineID; + beatmapDisplayTitle = currentBeatmap.Value.BeatmapInfo.GetDisplayTitle(); break; } @@ -86,9 +87,7 @@ namespace osu.Game.Online.Chat string getBeatmapPart() { - string beatmapInfoString = localisation.GetLocalisedBindableString(beatmapInfo.GetDisplayTitleRomanisable()).Value; - - return beatmapInfo.OnlineID > 0 ? $"[{api.WebsiteRootUrl}/b/{beatmapInfo.OnlineID} {beatmapInfoString}]" : beatmapInfoString; + return beatmapOnlineID > 0 ? $"[{api.WebsiteRootUrl}/b/{beatmapOnlineID} {beatmapDisplayTitle}]" : beatmapDisplayTitle; } string getRulesetPart() diff --git a/osu.Game/Online/SignalRWorkaroundTypes.cs b/osu.Game/Online/SignalRWorkaroundTypes.cs index 0e3eb0aab0..59a12b3bf1 100644 --- a/osu.Game/Online/SignalRWorkaroundTypes.cs +++ b/osu.Game/Online/SignalRWorkaroundTypes.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.Countdown; using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; +using osu.Game.Users; namespace osu.Game.Online { @@ -18,6 +19,7 @@ namespace osu.Game.Online { internal static readonly IReadOnlyList<(Type derivedType, Type baseType)> BASE_TYPE_MAPPING = new[] { + // multiplayer (typeof(ChangeTeamRequest), typeof(MatchUserRequest)), (typeof(StartMatchCountdownRequest), typeof(MatchUserRequest)), (typeof(StopCountdownRequest), typeof(MatchUserRequest)), @@ -28,6 +30,20 @@ namespace osu.Game.Online (typeof(MatchStartCountdown), typeof(MultiplayerCountdown)), (typeof(ForceGameplayStartCountdown), typeof(MultiplayerCountdown)), (typeof(ServerShuttingDownCountdown), typeof(MultiplayerCountdown)), + + // metadata + (typeof(UserActivity.ChoosingBeatmap), typeof(UserActivity)), + (typeof(UserActivity.InSoloGame), typeof(UserActivity)), + (typeof(UserActivity.WatchingReplay), typeof(UserActivity)), + (typeof(UserActivity.SpectatingUser), typeof(UserActivity)), + (typeof(UserActivity.SearchingForLobby), typeof(UserActivity)), + (typeof(UserActivity.InLobby), typeof(UserActivity)), + (typeof(UserActivity.InMultiplayerGame), typeof(UserActivity)), + (typeof(UserActivity.SpectatingMultiplayerGame), typeof(UserActivity)), + (typeof(UserActivity.InPlaylistGame), typeof(UserActivity)), + (typeof(UserActivity.EditingBeatmap), typeof(UserActivity)), + (typeof(UserActivity.ModdingBeatmap), typeof(UserActivity)), + (typeof(UserActivity.TestingBeatmap), typeof(UserActivity)), }; } } diff --git a/osu.Game/Users/UserActivity.cs b/osu.Game/Users/UserActivity.cs index c82f642fdc..1b09666df6 100644 --- a/osu.Game/Users/UserActivity.cs +++ b/osu.Game/Users/UserActivity.cs @@ -1,8 +1,11 @@ // 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 MessagePack; using osu.Game.Beatmaps; using osu.Game.Graphics; +using osu.Game.Online; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Scoring; @@ -10,43 +13,84 @@ using osuTK.Graphics; namespace osu.Game.Users { + /// + /// Base class for all structures describing the user's current activity. + /// + /// + /// Warning: keep specs consistent with + /// . + /// + [Serializable] + [MessagePackObject] + [Union(11, typeof(ChoosingBeatmap))] + [Union(12, typeof(InSoloGame))] + [Union(13, typeof(WatchingReplay))] + [Union(14, typeof(SpectatingUser))] + [Union(21, typeof(SearchingForLobby))] + [Union(22, typeof(InLobby))] + [Union(23, typeof(InMultiplayerGame))] + [Union(24, typeof(SpectatingMultiplayerGame))] + [Union(31, typeof(InPlaylistGame))] + [Union(41, typeof(EditingBeatmap))] + [Union(42, typeof(ModdingBeatmap))] + [Union(43, typeof(TestingBeatmap))] public abstract class UserActivity { public abstract string GetStatus(bool hideIdentifiableInformation = false); + public virtual string? GetDetails(bool hideIdentifiableInformation = false) => null; public virtual Color4 GetAppropriateColour(OsuColour colours) => colours.GreenDarker; - public class ModdingBeatmap : EditingBeatmap - { - public override string GetStatus(bool hideIdentifiableInformation = false) => "Modding a beatmap"; - public override Color4 GetAppropriateColour(OsuColour colours) => colours.PurpleDark; - - public ModdingBeatmap(IBeatmapInfo info) - : base(info) - { - } - } - + [MessagePackObject] public class ChoosingBeatmap : UserActivity { public override string GetStatus(bool hideIdentifiableInformation = false) => "Choosing a beatmap"; } + [MessagePackObject] public abstract class InGame : UserActivity { - public IBeatmapInfo BeatmapInfo { get; } + [Key(0)] + public int BeatmapID { get; set; } - public IRulesetInfo Ruleset { get; } + [Key(1)] + public string BeatmapDisplayTitle { get; set; } = string.Empty; + + [Key(2)] + public int RulesetID { get; set; } + + [Key(3)] + public string RulesetPlayingVerb { get; set; } = string.Empty; // TODO: i'm going with this for now, but this is wasteful protected InGame(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset) { - BeatmapInfo = beatmapInfo; - Ruleset = ruleset; + BeatmapID = beatmapInfo.OnlineID; + BeatmapDisplayTitle = beatmapInfo.GetDisplayTitle(); + + RulesetID = ruleset.OnlineID; + RulesetPlayingVerb = ruleset.CreateInstance().PlayingVerb; } - public override string GetStatus(bool hideIdentifiableInformation = false) => Ruleset.CreateInstance().PlayingVerb; + [SerializationConstructor] + protected InGame() { } + + public override string GetStatus(bool hideIdentifiableInformation = false) => RulesetPlayingVerb; + public override string GetDetails(bool hideIdentifiableInformation = false) => BeatmapDisplayTitle; } + [MessagePackObject] + public class InSoloGame : InGame + { + public InSoloGame(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset) + : base(beatmapInfo, ruleset) + { + } + + [SerializationConstructor] + public InSoloGame() { } + } + + [MessagePackObject] public class InMultiplayerGame : InGame { public InMultiplayerGame(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset) @@ -54,9 +98,122 @@ namespace osu.Game.Users { } + [SerializationConstructor] + public InMultiplayerGame() + { + } + public override string GetStatus(bool hideIdentifiableInformation = false) => $@"{base.GetStatus(hideIdentifiableInformation)} with others"; } + [MessagePackObject] + public class InPlaylistGame : InGame + { + public InPlaylistGame(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset) + : base(beatmapInfo, ruleset) + { + } + + [SerializationConstructor] + public InPlaylistGame() { } + } + + [MessagePackObject] + public class TestingBeatmap : InGame + { + public TestingBeatmap(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset) + : base(beatmapInfo, ruleset) + { + } + + [SerializationConstructor] + public TestingBeatmap() { } + + public override string GetStatus(bool hideIdentifiableInformation = false) => "Testing a beatmap"; + } + + [MessagePackObject] + public class EditingBeatmap : UserActivity + { + [Key(0)] + public int BeatmapID { get; set; } + + [Key(1)] + public string BeatmapDisplayTitle { get; set; } = string.Empty; + + public EditingBeatmap(IBeatmapInfo info) + { + BeatmapID = info.OnlineID; + BeatmapDisplayTitle = info.GetDisplayTitle(); + } + + [SerializationConstructor] + public EditingBeatmap() { } + + public override string GetStatus(bool hideIdentifiableInformation = false) => @"Editing a beatmap"; + public override string GetDetails(bool hideIdentifiableInformation = false) => BeatmapDisplayTitle; + } + + [MessagePackObject] + public class ModdingBeatmap : EditingBeatmap + { + public ModdingBeatmap(IBeatmapInfo info) + : base(info) + { + } + + [SerializationConstructor] + public ModdingBeatmap() { } + + public override string GetStatus(bool hideIdentifiableInformation = false) => "Modding a beatmap"; + public override Color4 GetAppropriateColour(OsuColour colours) => colours.PurpleDark; + } + + [MessagePackObject] + public class WatchingReplay : UserActivity + { + [Key(0)] + public long ScoreID { get; set; } + + [Key(1)] + public string PlayerName { get; set; } = string.Empty; + + [Key(2)] + public int BeatmapID { get; set; } + + [Key(3)] + public string? BeatmapDisplayTitle { get; set; } + + public WatchingReplay(ScoreInfo score) + { + ScoreID = score.OnlineID; + PlayerName = score.User.Username; + BeatmapID = score.BeatmapInfo?.OnlineID ?? -1; + BeatmapDisplayTitle = score.BeatmapInfo?.GetDisplayTitle(); + } + + [SerializationConstructor] + public WatchingReplay() { } + + public override string GetStatus(bool hideIdentifiableInformation = false) => hideIdentifiableInformation ? @"Watching a replay" : $@"Watching {PlayerName}'s replay"; + public override string? GetDetails(bool hideIdentifiableInformation = false) => BeatmapDisplayTitle; + } + + [MessagePackObject] + public class SpectatingUser : WatchingReplay + { + public SpectatingUser(ScoreInfo score) + : base(score) + { + } + + [SerializationConstructor] + public SpectatingUser() { } + + public override string GetStatus(bool hideIdentifiableInformation = false) => hideIdentifiableInformation ? @"Spectating a user" : $@"Spectating {PlayerName}"; + } + + [MessagePackObject] public class SpectatingMultiplayerGame : InGame { public SpectatingMultiplayerGame(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset) @@ -64,88 +221,41 @@ namespace osu.Game.Users { } + [SerializationConstructor] + public SpectatingMultiplayerGame() { } + public override string GetStatus(bool hideIdentifiableInformation = false) => $"Watching others {base.GetStatus(hideIdentifiableInformation).ToLowerInvariant()}"; } - public class InPlaylistGame : InGame - { - public InPlaylistGame(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset) - : base(beatmapInfo, ruleset) - { - } - } - - public class InSoloGame : InGame - { - public InSoloGame(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset) - : base(beatmapInfo, ruleset) - { - } - } - - public class TestingBeatmap : InGame - { - public override string GetStatus(bool hideIdentifiableInformation = false) => "Testing a beatmap"; - - public TestingBeatmap(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset) - : base(beatmapInfo, ruleset) - { - } - } - - public class EditingBeatmap : UserActivity - { - public IBeatmapInfo BeatmapInfo { get; } - - public EditingBeatmap(IBeatmapInfo info) - { - BeatmapInfo = info; - } - - public override string GetStatus(bool hideIdentifiableInformation = false) => @"Editing a beatmap"; - } - - public class WatchingReplay : UserActivity - { - private readonly ScoreInfo score; - - protected string Username => score.User.Username; - - public BeatmapInfo? BeatmapInfo => score.BeatmapInfo; - - public WatchingReplay(ScoreInfo score) - { - this.score = score; - } - - public override string GetStatus(bool hideIdentifiableInformation = false) => hideIdentifiableInformation ? @"Watching a replay" : $@"Watching {Username}'s replay"; - } - - public class SpectatingUser : WatchingReplay - { - public override string GetStatus(bool hideIdentifiableInformation = false) => hideIdentifiableInformation ? @"Spectating a user" : $@"Spectating {Username}"; - - public SpectatingUser(ScoreInfo score) - : base(score) - { - } - } - + [MessagePackObject] public class SearchingForLobby : UserActivity { public override string GetStatus(bool hideIdentifiableInformation = false) => @"Looking for a lobby"; } + [MessagePackObject] public class InLobby : UserActivity { - public override string GetStatus(bool hideIdentifiableInformation = false) => @"In a lobby"; + [Key(0)] + public long RoomID { get; set; } - public readonly Room Room; + [Key(1)] + public string RoomName { get; set; } = string.Empty; public InLobby(Room room) { - Room = room; + RoomID = room.RoomID.Value ?? -1; + RoomName = room.Name.Value; } + + [SerializationConstructor] + public InLobby() { } + + public override string GetStatus(bool hideIdentifiableInformation = false) => @"In a lobby"; + + public override string? GetDetails(bool hideIdentifiableInformation = false) => hideIdentifiableInformation + ? null + : RoomName; } } } From d66fa093205320b3a09a20ded2632b68dbf3fe21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 6 Dec 2023 18:21:44 +0100 Subject: [PATCH 1870/4092] Simplify `UserStatus` to be an enumeration type There were absolutely no gains from having it be a reference type / class, only complications, especially when coming from the serialisation angle. --- osu.Desktop/DiscordRichPresence.cs | 6 +-- .../Online/TestSceneUserClickableAvatar.cs | 2 +- .../Visual/Online/TestSceneUserPanel.cs | 18 ++++---- osu.Game/Online/API/APIAccess.cs | 2 +- .../Online/API/Requests/Responses/APIUser.cs | 2 +- osu.Game/Overlays/Login/LoginPanel.cs | 6 +-- osu.Game/Users/ExtendedUserPanel.cs | 17 +++---- osu.Game/Users/UserStatus.cs | 46 +++++++++++-------- 8 files changed, 53 insertions(+), 46 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index c66725e3e3..f990fd55fc 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -33,7 +33,7 @@ namespace osu.Desktop [Resolved] private IAPIProvider api { get; set; } = null!; - private readonly IBindable status = new Bindable(); + private readonly IBindable status = new Bindable(); private readonly IBindable activity = new Bindable(); private readonly Bindable privacyMode = new Bindable(); @@ -86,13 +86,13 @@ namespace osu.Desktop if (!client.IsInitialized) return; - if (status.Value is UserStatusOffline || privacyMode.Value == DiscordRichPresenceMode.Off) + if (status.Value == UserStatus.Offline || privacyMode.Value == DiscordRichPresenceMode.Off) { client.ClearPresence(); return; } - if (status.Value is UserStatusOnline && activity.Value != null) + if (status.Value == UserStatus.Online && activity.Value != null) { bool hideIdentifiableInformation = privacyMode.Value == DiscordRichPresenceMode.Limited; presence.State = truncate(activity.Value.GetStatus(hideIdentifiableInformation)); diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs b/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs index 9edaa841b2..4539eae25f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs @@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Online Colour = color ?? "000000", Status = { - Value = new UserStatusOnline() + Value = UserStatus.Online }, }; diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index c61b572d8c..b3b8fd78d3 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Online public partial class TestSceneUserPanel : OsuTestScene { private readonly Bindable activity = new Bindable(); - private readonly Bindable status = new Bindable(); + private readonly Bindable status = new Bindable(); private UserGridPanel boundPanel1; private TestUserListPanel boundPanel2; @@ -66,7 +66,7 @@ namespace osu.Game.Tests.Visual.Online Id = 3103765, CountryCode = CountryCode.JP, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg", - Status = { Value = new UserStatusOnline() } + Status = { Value = UserStatus.Online } }) { Width = 300 }, boundPanel1 = new UserGridPanel(new APIUser { @@ -99,16 +99,16 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestUserStatus() { - AddStep("online", () => status.Value = new UserStatusOnline()); - AddStep("do not disturb", () => status.Value = new UserStatusDoNotDisturb()); - AddStep("offline", () => status.Value = new UserStatusOffline()); + AddStep("online", () => status.Value = UserStatus.Online); + AddStep("do not disturb", () => status.Value = UserStatus.DoNotDisturb); + AddStep("offline", () => status.Value = UserStatus.Offline); AddStep("null status", () => status.Value = null); } [Test] public void TestUserActivity() { - AddStep("set online status", () => status.Value = new UserStatusOnline()); + AddStep("set online status", () => status.Value = UserStatus.Online); AddStep("idle", () => activity.Value = null); AddStep("watching replay", () => activity.Value = new UserActivity.WatchingReplay(createScore(@"nats"))); @@ -127,12 +127,12 @@ namespace osu.Game.Tests.Visual.Online public void TestUserActivityChange() { AddAssert("visit message is visible", () => boundPanel2.LastVisitMessage.IsPresent); - AddStep("set online status", () => status.Value = new UserStatusOnline()); + AddStep("set online status", () => status.Value = UserStatus.Online); AddAssert("visit message is not visible", () => !boundPanel2.LastVisitMessage.IsPresent); AddStep("set choosing activity", () => activity.Value = new UserActivity.ChoosingBeatmap()); - AddStep("set offline status", () => status.Value = new UserStatusOffline()); + AddStep("set offline status", () => status.Value = UserStatus.Offline); AddAssert("visit message is visible", () => boundPanel2.LastVisitMessage.IsPresent); - AddStep("set online status", () => status.Value = new UserStatusOnline()); + AddStep("set online status", () => status.Value = UserStatus.Online); AddAssert("visit message is not visible", () => !boundPanel2.LastVisitMessage.IsPresent); } diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 4f586c8fff..21107d61fc 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -247,7 +247,7 @@ namespace osu.Game.Online.API userReq.Success += user => { // todo: save/pull from settings - user.Status.Value = new UserStatusOnline(); + user.Status.Value = UserStatus.Online; setLocalUser(user); diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index 2ee66453cf..56eec19fa1 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -43,7 +43,7 @@ namespace osu.Game.Online.API.Requests.Responses set => countryCodeString = value.ToString(); } - public readonly Bindable Status = new Bindable(); + public readonly Bindable Status = new Bindable(); public readonly Bindable Activity = new Bindable(); diff --git a/osu.Game/Overlays/Login/LoginPanel.cs b/osu.Game/Overlays/Login/LoginPanel.cs index 71ecf2e75a..19af95459f 100644 --- a/osu.Game/Overlays/Login/LoginPanel.cs +++ b/osu.Game/Overlays/Login/LoginPanel.cs @@ -148,17 +148,17 @@ namespace osu.Game.Overlays.Login switch (action.NewValue) { case UserAction.Online: - api.LocalUser.Value.Status.Value = new UserStatusOnline(); + api.LocalUser.Value.Status.Value = UserStatus.Online; dropdown.StatusColour = colours.Green; break; case UserAction.DoNotDisturb: - api.LocalUser.Value.Status.Value = new UserStatusDoNotDisturb(); + api.LocalUser.Value.Status.Value = UserStatus.DoNotDisturb; dropdown.StatusColour = colours.Red; break; case UserAction.AppearOffline: - api.LocalUser.Value.Status.Value = new UserStatusOffline(); + api.LocalUser.Value.Status.Value = UserStatus.Offline; dropdown.StatusColour = colours.Gray7; break; diff --git a/osu.Game/Users/ExtendedUserPanel.cs b/osu.Game/Users/ExtendedUserPanel.cs index 3c1b68f9ef..18fe852556 100644 --- a/osu.Game/Users/ExtendedUserPanel.cs +++ b/osu.Game/Users/ExtendedUserPanel.cs @@ -6,6 +6,7 @@ using osuTK; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; @@ -18,7 +19,7 @@ namespace osu.Game.Users { public abstract partial class ExtendedUserPanel : UserPanel { - public readonly Bindable Status = new Bindable(); + public readonly Bindable Status = new Bindable(); public readonly IBindable Activity = new Bindable(); @@ -97,14 +98,14 @@ namespace osu.Game.Users return statusContainer; } - private void displayStatus(UserStatus status, UserActivity activity = null) + private void displayStatus(UserStatus? status, UserActivity activity = null) { if (status != null) { - LastVisitMessage.FadeTo(status is UserStatusOffline && User.LastVisit.HasValue ? 1 : 0); + LastVisitMessage.FadeTo(status == UserStatus.Offline && User.LastVisit.HasValue ? 1 : 0); // Set status message based on activity (if we have one) and status is not offline - if (activity != null && !(status is UserStatusOffline)) + if (activity != null && status != UserStatus.Offline) { statusMessage.Text = activity.GetStatus(); statusIcon.FadeColour(activity.GetAppropriateColour(Colours), 500, Easing.OutQuint); @@ -112,8 +113,8 @@ namespace osu.Game.Users } // Otherwise use only status - statusMessage.Text = status.Message; - statusIcon.FadeColour(status.GetAppropriateColour(Colours), 500, Easing.OutQuint); + statusMessage.Text = status.GetLocalisableDescription(); + statusIcon.FadeColour(status.Value.GetAppropriateColour(Colours), 500, Easing.OutQuint); return; } @@ -121,11 +122,11 @@ namespace osu.Game.Users // Fallback to web status if local one is null if (User.IsOnline) { - Status.Value = new UserStatusOnline(); + Status.Value = UserStatus.Online; return; } - Status.Value = new UserStatusOffline(); + Status.Value = UserStatus.Offline; } protected override bool OnHover(HoverEvent e) diff --git a/osu.Game/Users/UserStatus.cs b/osu.Game/Users/UserStatus.cs index ffd86b78c7..cd25add4d1 100644 --- a/osu.Game/Users/UserStatus.cs +++ b/osu.Game/Users/UserStatus.cs @@ -1,6 +1,8 @@ // 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.ComponentModel; using osu.Framework.Localisation; using osuTK.Graphics; using osu.Game.Graphics; @@ -8,32 +10,36 @@ using osu.Game.Resources.Localisation.Web; namespace osu.Game.Users { - public abstract class UserStatus + public enum UserStatus { - public abstract LocalisableString Message { get; } - public abstract Color4 GetAppropriateColour(OsuColour colours); + [LocalisableDescription(typeof(UsersStrings), nameof(UsersStrings.StatusOffline))] + Offline, + + [Description("Do not disturb")] + DoNotDisturb, + + [LocalisableDescription(typeof(UsersStrings), nameof(UsersStrings.StatusOnline))] + Online, } - public class UserStatusOnline : UserStatus + public static class UserStatusExtensions { - public override LocalisableString Message => UsersStrings.StatusOnline; - public override Color4 GetAppropriateColour(OsuColour colours) => colours.GreenLight; - } + public static Color4 GetAppropriateColour(this UserStatus userStatus, OsuColour colours) + { + switch (userStatus) + { + case UserStatus.Offline: + return Color4.Black; - public abstract class UserStatusBusy : UserStatusOnline - { - public override Color4 GetAppropriateColour(OsuColour colours) => colours.YellowDark; - } + case UserStatus.DoNotDisturb: + return colours.RedDark; - public class UserStatusOffline : UserStatus - { - public override LocalisableString Message => UsersStrings.StatusOffline; - public override Color4 GetAppropriateColour(OsuColour colours) => Color4.Black; - } + case UserStatus.Online: + return colours.GreenDark; - public class UserStatusDoNotDisturb : UserStatus - { - public override LocalisableString Message => "Do not disturb"; - public override Color4 GetAppropriateColour(OsuColour colours) => colours.RedDark; + default: + throw new ArgumentOutOfRangeException(nameof(userStatus), userStatus, "Unsupported user status"); + } + } } } From 602550b9c233280d81cb65f8b39d1167a2ccaeca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 6 Dec 2023 19:31:42 +0100 Subject: [PATCH 1871/4092] Fix test failures --- osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs b/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs index fb36580a42..1e9b0317fb 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs @@ -11,8 +11,8 @@ using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.Chat; using osu.Game.Online.Rooms; -using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; using osu.Game.Users; namespace osu.Game.Tests.Visual.Online @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestPlayActivity() { - AddStep("Set activity", () => api.Activity.Value = new UserActivity.InSoloGame(new BeatmapInfo(), new RulesetInfo())); + AddStep("Set activity", () => api.Activity.Value = new UserActivity.InSoloGame(new BeatmapInfo(), new OsuRuleset().RulesetInfo)); AddStep("Run command", () => Add(new NowPlayingCommand(new Channel()))); @@ -82,7 +82,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestModPresence() { - AddStep("Set activity", () => api.Activity.Value = new UserActivity.InSoloGame(new BeatmapInfo(), new RulesetInfo())); + AddStep("Set activity", () => api.Activity.Value = new UserActivity.InSoloGame(new BeatmapInfo(), new OsuRuleset().RulesetInfo)); AddStep("Add Hidden mod", () => SelectedMods.Value = new[] { Ruleset.Value.CreateInstance().CreateMod() }); From 41c33f74f27822889290aea330ade6654eb6f5a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 6 Dec 2023 18:24:31 +0100 Subject: [PATCH 1872/4092] Extend metadata client with user presence-observing capabilities --- osu.Game/Online/Metadata/IMetadataClient.cs | 14 +- osu.Game/Online/Metadata/IMetadataServer.cs | 21 +++ osu.Game/Online/Metadata/MetadataClient.cs | 53 +++++++- .../Online/Metadata/OnlineMetadataClient.cs | 120 +++++++++++++++++- osu.Game/Online/OnlineStatusNotifier.cs | 8 ++ osu.Game/Users/UserPresence.cs | 28 ++++ 6 files changed, 239 insertions(+), 5 deletions(-) create mode 100644 osu.Game/Users/UserPresence.cs diff --git a/osu.Game/Online/Metadata/IMetadataClient.cs b/osu.Game/Online/Metadata/IMetadataClient.cs index ad1e7ebbaf..7102554ae9 100644 --- a/osu.Game/Online/Metadata/IMetadataClient.cs +++ b/osu.Game/Online/Metadata/IMetadataClient.cs @@ -2,11 +2,23 @@ // See the LICENCE file in the repository root for full licence text. using System.Threading.Tasks; +using osu.Game.Users; namespace osu.Game.Online.Metadata { - public interface IMetadataClient + /// + /// Interface for metadata-related remote procedure calls to be executed on the client side. + /// + public interface IMetadataClient : IStatefulUserHubClient { + /// + /// Delivers the set of requested to the client. + /// Task BeatmapSetsUpdated(BeatmapUpdates updates); + + /// + /// Delivers an update of the of the user with the supplied . + /// + Task UserPresenceUpdated(int userId, UserPresence? status); } } diff --git a/osu.Game/Online/Metadata/IMetadataServer.cs b/osu.Game/Online/Metadata/IMetadataServer.cs index 994f60f877..9780045333 100644 --- a/osu.Game/Online/Metadata/IMetadataServer.cs +++ b/osu.Game/Online/Metadata/IMetadataServer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Threading.Tasks; +using osu.Game.Users; namespace osu.Game.Online.Metadata { @@ -17,5 +18,25 @@ namespace osu.Game.Online.Metadata /// The last processed queue ID. /// Task GetChangesSince(int queueId); + + /// + /// Signals to the server that the current user's has changed. + /// + Task UpdateActivity(UserActivity? activity); + + /// + /// Signals to the server that the current user's has changed. + /// + Task UpdateStatus(UserStatus? status); + + /// + /// Signals to the server that the current user would like to begin receiving updates on other users' online presence. + /// + Task BeginWatchingUserPresence(); + + /// + /// Signals to the server that the current user would like to stop receiving updates on other users' online presence. + /// + Task EndWatchingUserPresence(); } } diff --git a/osu.Game/Online/Metadata/MetadataClient.cs b/osu.Game/Online/Metadata/MetadataClient.cs index d4e7540fe7..8e99a9b2cb 100644 --- a/osu.Game/Online/Metadata/MetadataClient.cs +++ b/osu.Game/Online/Metadata/MetadataClient.cs @@ -4,22 +4,71 @@ using System; using System.Linq; using System.Threading.Tasks; +using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Game.Users; namespace osu.Game.Online.Metadata { public abstract partial class MetadataClient : Component, IMetadataClient, IMetadataServer { - public abstract Task BeatmapSetsUpdated(BeatmapUpdates updates); + public abstract IBindable IsConnected { get; } + + #region Beatmap metadata updates public abstract Task GetChangesSince(int queueId); - public Action? ChangedBeatmapSetsArrived; + public abstract Task BeatmapSetsUpdated(BeatmapUpdates updates); + + public event Action? ChangedBeatmapSetsArrived; protected Task ProcessChanges(int[] beatmapSetIDs) { ChangedBeatmapSetsArrived?.Invoke(beatmapSetIDs.Distinct().ToArray()); return Task.CompletedTask; } + + #endregion + + #region User presence updates + + /// + /// Whether the client is currently receiving user presence updates from the server. + /// + public abstract IBindable IsWatchingUserPresence { get; } + + /// + /// Dictionary keyed by user ID containing all of the information about currently online users received from the server. + /// + public abstract IBindableDictionary UserStates { get; } + + /// + public abstract Task UpdateActivity(UserActivity? activity); + + /// + public abstract Task UpdateStatus(UserStatus? status); + + /// + public abstract Task BeginWatchingUserPresence(); + + /// + public abstract Task EndWatchingUserPresence(); + + /// + public abstract Task UserPresenceUpdated(int userId, UserPresence? presence); + + #endregion + + #region Disconnection handling + + public event Action? Disconnecting; + + public virtual Task DisconnectRequested() + { + Schedule(() => Disconnecting?.Invoke()); + return Task.CompletedTask; + } + + #endregion } } diff --git a/osu.Game/Online/Metadata/OnlineMetadataClient.cs b/osu.Game/Online/Metadata/OnlineMetadataClient.cs index 57311419f7..27093d7961 100644 --- a/osu.Game/Online/Metadata/OnlineMetadataClient.cs +++ b/osu.Game/Online/Metadata/OnlineMetadataClient.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; using osu.Framework.Allocation; @@ -10,17 +11,32 @@ using osu.Framework.Bindables; using osu.Framework.Logging; using osu.Game.Configuration; using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Users; namespace osu.Game.Online.Metadata { public partial class OnlineMetadataClient : MetadataClient { + public override IBindable IsConnected { get; } = new Bindable(); + + public override IBindable IsWatchingUserPresence => isWatchingUserPresence; + private readonly BindableBool isWatchingUserPresence = new BindableBool(); + + // ReSharper disable once InconsistentlySynchronizedField + public override IBindableDictionary UserStates => userStates; + private readonly BindableDictionary userStates = new BindableDictionary(); + private readonly string endpoint; private IHubClientConnector? connector; private Bindable lastQueueId = null!; + private IBindable localUser = null!; + private IBindable userActivity = null!; + private IBindable? userStatus; + private HubConnection? connection => connector?.CurrentConnection; public OnlineMetadataClient(EndpointConfiguration endpoints) @@ -33,7 +49,7 @@ namespace osu.Game.Online.Metadata { // Importantly, we are intentionally not using MessagePack here to correctly support derived class serialization. // More information on the limitations / reasoning can be found in osu-server-spectator's initialisation code. - connector = api.GetHubConnector(nameof(OnlineMetadataClient), endpoint); + connector = api.GetHubConnector(nameof(OnlineMetadataClient), endpoint, false); if (connector != null) { @@ -42,12 +58,37 @@ namespace osu.Game.Online.Metadata // this is kind of SILLY // https://github.com/dotnet/aspnetcore/issues/15198 connection.On(nameof(IMetadataClient.BeatmapSetsUpdated), ((IMetadataClient)this).BeatmapSetsUpdated); + connection.On(nameof(IMetadataClient.UserPresenceUpdated), ((IMetadataClient)this).UserPresenceUpdated); }; - connector.IsConnected.BindValueChanged(isConnectedChanged, true); + IsConnected.BindTo(connector.IsConnected); + IsConnected.BindValueChanged(isConnectedChanged, true); } lastQueueId = config.GetBindable(OsuSetting.LastProcessedMetadataId); + + localUser = api.LocalUser.GetBoundCopy(); + userActivity = api.Activity.GetBoundCopy()!; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + localUser.BindValueChanged(_ => + { + if (localUser.Value is not GuestUser) + { + userStatus = localUser.Value.Status.GetBoundCopy(); + userStatus.BindValueChanged(status => UpdateStatus(status.NewValue), true); + } + else + userStatus = null; + }, true); + userActivity.BindValueChanged(activity => + { + if (localUser.Value is not GuestUser) + UpdateActivity(activity.NewValue); + }, true); } private bool catchingUp; @@ -55,7 +96,17 @@ namespace osu.Game.Online.Metadata private void isConnectedChanged(ValueChangedEvent connected) { if (!connected.NewValue) + { + isWatchingUserPresence.Value = false; + userStates.Clear(); return; + } + + if (localUser.Value is not GuestUser) + { + UpdateActivity(userActivity.Value); + UpdateStatus(userStatus?.Value); + } if (lastQueueId.Value >= 0) { @@ -116,6 +167,71 @@ namespace osu.Game.Online.Metadata return connection.InvokeAsync(nameof(IMetadataServer.GetChangesSince), queueId); } + public override Task UpdateActivity(UserActivity? activity) + { + if (connector?.IsConnected.Value != true) + return Task.FromCanceled(new CancellationToken(true)); + + Debug.Assert(connection != null); + return connection.InvokeAsync(nameof(IMetadataServer.UpdateActivity), activity); + } + + public override Task UpdateStatus(UserStatus? status) + { + if (connector?.IsConnected.Value != true) + return Task.FromCanceled(new CancellationToken(true)); + + Debug.Assert(connection != null); + return connection.InvokeAsync(nameof(IMetadataServer.UpdateStatus), status); + } + + public override Task UserPresenceUpdated(int userId, UserPresence? presence) + { + lock (userStates) + { + if (presence != null) + userStates[userId] = presence.Value; + else + userStates.Remove(userId); + } + + return Task.CompletedTask; + } + + public override async Task BeginWatchingUserPresence() + { + if (connector?.IsConnected.Value != true) + throw new OperationCanceledException(); + + Debug.Assert(connection != null); + await connection.InvokeAsync(nameof(IMetadataServer.BeginWatchingUserPresence)).ConfigureAwait(false); + isWatchingUserPresence.Value = true; + } + + public override async Task EndWatchingUserPresence() + { + try + { + if (connector?.IsConnected.Value != true) + throw new OperationCanceledException(); + + // must happen synchronously before any remote calls to avoid misordering. + userStates.Clear(); + Debug.Assert(connection != null); + await connection.InvokeAsync(nameof(IMetadataServer.EndWatchingUserPresence)).ConfigureAwait(false); + } + finally + { + isWatchingUserPresence.Value = false; + } + } + + public override async Task DisconnectRequested() + { + await base.DisconnectRequested().ConfigureAwait(false); + await EndWatchingUserPresence().ConfigureAwait(false); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); diff --git a/osu.Game/Online/OnlineStatusNotifier.cs b/osu.Game/Online/OnlineStatusNotifier.cs index 0d846f7d27..c36e4ab894 100644 --- a/osu.Game/Online/OnlineStatusNotifier.cs +++ b/osu.Game/Online/OnlineStatusNotifier.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Screens; using osu.Game.Online.API; +using osu.Game.Online.Metadata; using osu.Game.Online.Multiplayer; using osu.Game.Online.Spectator; using osu.Game.Overlays; @@ -30,6 +31,9 @@ namespace osu.Game.Online [Resolved] private SpectatorClient spectatorClient { get; set; } = null!; + [Resolved] + private MetadataClient metadataClient { get; set; } = null!; + [Resolved] private INotificationOverlay? notificationOverlay { get; set; } @@ -56,6 +60,7 @@ namespace osu.Game.Online multiplayerClient.Disconnecting += notifyAboutForcedDisconnection; spectatorClient.Disconnecting += notifyAboutForcedDisconnection; + metadataClient.Disconnecting += notifyAboutForcedDisconnection; } protected override void LoadComplete() @@ -131,6 +136,9 @@ namespace osu.Game.Online if (multiplayerClient.IsNotNull()) multiplayerClient.Disconnecting -= notifyAboutForcedDisconnection; + + if (metadataClient.IsNotNull()) + metadataClient.Disconnecting -= notifyAboutForcedDisconnection; } } } diff --git a/osu.Game/Users/UserPresence.cs b/osu.Game/Users/UserPresence.cs new file mode 100644 index 0000000000..dff40a9889 --- /dev/null +++ b/osu.Game/Users/UserPresence.cs @@ -0,0 +1,28 @@ +// 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 MessagePack; + +namespace osu.Game.Users +{ + /// + /// Structure containing all relevant information about a user's online presence. + /// + [Serializable] + [MessagePackObject] + public struct UserPresence + { + /// + /// The user's current activity. + /// + [Key(0)] + public UserActivity? Activity { get; set; } + + /// + /// The user's current status. + /// + [Key(1)] + public UserStatus? Status { get; set; } + } +} From 54f3a622beb982ce82542db39b17e1917b6a6bc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 6 Dec 2023 18:25:16 +0100 Subject: [PATCH 1873/4092] Retrofit user presence watching into dashboard overlay --- ...ngDisplay.cs => CurrentlyOnlineDisplay.cs} | 113 ++++++++++++++---- .../Dashboard/DashboardOverlayHeader.cs | 2 +- osu.Game/Overlays/DashboardOverlay.cs | 33 ++++- 3 files changed, 124 insertions(+), 24 deletions(-) rename osu.Game/Overlays/Dashboard/{CurrentlyPlayingDisplay.cs => CurrentlyOnlineDisplay.cs} (63%) diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs similarity index 63% rename from osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs rename to osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs index 6967a61204..fe3151398f 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; @@ -20,6 +19,7 @@ using osu.Game.Database; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Metadata; using osu.Game.Online.Spectator; using osu.Game.Resources.Localisation.Web; using osu.Game.Screens; @@ -30,19 +30,27 @@ using osuTK; namespace osu.Game.Overlays.Dashboard { - internal partial class CurrentlyPlayingDisplay : CompositeDrawable + internal partial class CurrentlyOnlineDisplay : CompositeDrawable { private const float search_textbox_height = 40; private const float padding = 10; private readonly IBindableList playingUsers = new BindableList(); + private readonly IBindableDictionary onlineUsers = new BindableDictionary(); + private readonly Dictionary userPanels = new Dictionary(); - private SearchContainer userFlow; + private SearchContainer userFlow; private BasicSearchTextBox searchTextBox; + [Resolved] + private IAPIProvider api { get; set; } + [Resolved] private SpectatorClient spectatorClient { get; set; } + [Resolved] + private MetadataClient metadataClient { get; set; } + [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { @@ -72,7 +80,7 @@ namespace osu.Game.Overlays.Dashboard PlaceholderText = HomeStrings.SearchPlaceholder, }, }, - userFlow = new SearchContainer + userFlow = new SearchContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -97,6 +105,9 @@ namespace osu.Game.Overlays.Dashboard { base.LoadComplete(); + onlineUsers.BindTo(metadataClient.UserStates); + onlineUsers.BindCollectionChanged(onUserUpdated, true); + playingUsers.BindTo(spectatorClient.PlayingUsers); playingUsers.BindCollectionChanged(onPlayingUsersChanged, true); } @@ -108,15 +119,20 @@ namespace osu.Game.Overlays.Dashboard searchTextBox.TakeFocus(); } - private void onPlayingUsersChanged(object sender, NotifyCollectionChangedEventArgs e) => Schedule(() => + private void onUserUpdated(object sender, NotifyDictionaryChangedEventArgs e) => Schedule(() => { switch (e.Action) { - case NotifyCollectionChangedAction.Add: + case NotifyDictionaryChangedAction.Add: Debug.Assert(e.NewItems != null); - foreach (int userId in e.NewItems) + foreach (var kvp in e.NewItems) { + int userId = kvp.Key; + + if (userId == api.LocalUser.Value.Id) + continue; + users.GetUserAsync(userId).ContinueWith(task => { APIUser user = task.GetResultSafely(); @@ -126,40 +142,90 @@ namespace osu.Game.Overlays.Dashboard Schedule(() => { - // user may no longer be playing. - if (!playingUsers.Contains(user.Id)) - return; + // explicitly refetch the user's status. + // things may have changed in between the time of scheduling and the time of actual execution. + if (onlineUsers.TryGetValue(userId, out var updatedStatus)) + { + user.Activity.Value = updatedStatus.Activity; + user.Status.Value = updatedStatus.Status; + } - // TODO: remove this once online state is being updated more correctly. - user.IsOnline = true; - - userFlow.Add(createUserPanel(user)); + userFlow.Add(userPanels[userId] = createUserPanel(user)); }); }); } break; + case NotifyDictionaryChangedAction.Replace: + Debug.Assert(e.NewItems != null); + + foreach (var kvp in e.NewItems) + { + if (userPanels.TryGetValue(kvp.Key, out var panel)) + { + panel.User.Activity.Value = kvp.Value.Activity; + panel.User.Status.Value = kvp.Value.Status; + } + } + + break; + + case NotifyDictionaryChangedAction.Remove: + Debug.Assert(e.OldItems != null); + + foreach (var kvp in e.OldItems) + { + int userId = kvp.Key; + if (userPanels.Remove(userId, out var userPanel)) + userPanel.Expire(); + } + + break; + } + }); + + private void onPlayingUsersChanged(object sender, NotifyCollectionChangedEventArgs e) + { + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + Debug.Assert(e.NewItems != null); + + foreach (int userId in e.NewItems) + { + if (userPanels.TryGetValue(userId, out var panel)) + panel.CanSpectate.Value = userId != api.LocalUser.Value.Id; + } + + break; + case NotifyCollectionChangedAction.Remove: Debug.Assert(e.OldItems != null); foreach (int userId in e.OldItems) - userFlow.FirstOrDefault(card => card.User.Id == userId)?.Expire(); + { + if (userPanels.TryGetValue(userId, out var panel)) + panel.CanSpectate.Value = false; + } + break; } - }); + } - private PlayingUserPanel createUserPanel(APIUser user) => - new PlayingUserPanel(user).With(panel => + private OnlineUserPanel createUserPanel(APIUser user) => + new OnlineUserPanel(user).With(panel => { panel.Anchor = Anchor.TopCentre; panel.Origin = Anchor.TopCentre; }); - public partial class PlayingUserPanel : CompositeDrawable, IFilterable + public partial class OnlineUserPanel : CompositeDrawable, IFilterable { public readonly APIUser User; + public BindableBool CanSpectate { get; } = new BindableBool(); + public IEnumerable FilterTerms { get; } [Resolved(canBeNull: true)] @@ -178,7 +244,7 @@ namespace osu.Game.Overlays.Dashboard } } - public PlayingUserPanel(APIUser user) + public OnlineUserPanel(APIUser user) { User = user; @@ -188,7 +254,7 @@ namespace osu.Game.Overlays.Dashboard } [BackgroundDependencyLoader] - private void load(IAPIProvider api) + private void load() { InternalChildren = new Drawable[] { @@ -205,6 +271,9 @@ namespace osu.Game.Overlays.Dashboard RelativeSizeAxes = Axes.X, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, + // this is SHOCKING + Activity = { BindTarget = User.Activity }, + Status = { BindTarget = User.Status }, }, new PurpleRoundedButton { @@ -213,7 +282,7 @@ namespace osu.Game.Overlays.Dashboard Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Action = () => performer?.PerformFromScreen(s => s.Push(new SoloSpectatorScreen(User))), - Enabled = { Value = User.Id != api.LocalUser.Value.Id } + Enabled = { BindTarget = CanSpectate } } } }, diff --git a/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs index b9d869c2ec..104f0943dc 100644 --- a/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs +++ b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs @@ -29,7 +29,7 @@ namespace osu.Game.Overlays.Dashboard [LocalisableDescription(typeof(FriendsStrings), nameof(FriendsStrings.TitleCompact))] Friends, - [Description("Currently Playing")] + [Description("Currently online")] CurrentlyPlaying } } diff --git a/osu.Game/Overlays/DashboardOverlay.cs b/osu.Game/Overlays/DashboardOverlay.cs index 2f96421531..1861f892bd 100644 --- a/osu.Game/Overlays/DashboardOverlay.cs +++ b/osu.Game/Overlays/DashboardOverlay.cs @@ -2,6 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Containers; +using osu.Game.Online.Metadata; +using osu.Game.Online.Multiplayer; using osu.Game.Overlays.Dashboard; using osu.Game.Overlays.Dashboard.Friends; @@ -9,6 +14,11 @@ namespace osu.Game.Overlays { public partial class DashboardOverlay : TabbableOnlineOverlay { + [Resolved] + private MetadataClient metadataClient { get; set; } = null!; + + private IBindable metadataConnected = null!; + public DashboardOverlay() : base(OverlayColourScheme.Purple) { @@ -27,12 +37,33 @@ namespace osu.Game.Overlays break; case DashboardOverlayTabs.CurrentlyPlaying: - LoadDisplay(new CurrentlyPlayingDisplay()); + LoadDisplay(new CurrentlyOnlineDisplay()); break; default: throw new NotImplementedException($"Display for {tab} tab is not implemented"); } } + + protected override void LoadComplete() + { + base.LoadComplete(); + + metadataConnected = metadataClient.IsConnected.GetBoundCopy(); + metadataConnected.BindValueChanged(_ => updateUserPresenceState()); + State.BindValueChanged(_ => updateUserPresenceState()); + updateUserPresenceState(); + } + + private void updateUserPresenceState() + { + if (!metadataConnected.Value) + return; + + if (State.Value == Visibility.Visible) + metadataClient.BeginWatchingUserPresence().FireAndForget(); + else + metadataClient.EndWatchingUserPresence().FireAndForget(); + } } } From 86e003aec1cfc97014aebe00c71fe11e999dfae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 6 Dec 2023 18:25:30 +0100 Subject: [PATCH 1874/4092] Update currently online display tests --- ....cs => TestSceneCurrentlyOnlineDisplay.cs} | 33 ++++++-- .../Visual/Metadata/TestMetadataClient.cs | 81 +++++++++++++++++++ 2 files changed, 106 insertions(+), 8 deletions(-) rename osu.Game.Tests/Visual/Online/{TestSceneCurrentlyPlayingDisplay.cs => TestSceneCurrentlyOnlineDisplay.cs} (60%) create mode 100644 osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyOnlineDisplay.cs similarity index 60% rename from osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs rename to osu.Game.Tests/Visual/Online/TestSceneCurrentlyOnlineDisplay.cs index 5237238f63..7687cd195d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyOnlineDisplay.cs @@ -10,43 +10,50 @@ using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Database; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Metadata; using osu.Game.Online.Spectator; using osu.Game.Overlays; using osu.Game.Overlays.Dashboard; +using osu.Game.Screens.OnlinePlay.Match.Components; +using osu.Game.Tests.Visual.Metadata; using osu.Game.Tests.Visual.Spectator; using osu.Game.Users; namespace osu.Game.Tests.Visual.Online { - public partial class TestSceneCurrentlyPlayingDisplay : OsuTestScene + public partial class TestSceneCurrentlyOnlineDisplay : OsuTestScene { private readonly APIUser streamingUser = new APIUser { Id = 2, Username = "Test user" }; private TestSpectatorClient spectatorClient = null!; - private CurrentlyPlayingDisplay currentlyPlaying = null!; + private TestMetadataClient metadataClient = null!; + private CurrentlyOnlineDisplay currentlyOnline = null!; [SetUpSteps] public void SetUpSteps() { - AddStep("add streaming client", () => + AddStep("set up components", () => { spectatorClient = new TestSpectatorClient(); + metadataClient = new TestMetadataClient(); var lookupCache = new TestUserLookupCache(); Children = new Drawable[] { lookupCache, spectatorClient, + metadataClient, new DependencyProvidingContainer { RelativeSizeAxes = Axes.Both, CachedDependencies = new (Type, object)[] { (typeof(SpectatorClient), spectatorClient), + (typeof(MetadataClient), metadataClient), (typeof(UserLookupCache), lookupCache), (typeof(OverlayColourProvider), new OverlayColourProvider(OverlayColourScheme.Purple)), }, - Child = currentlyPlaying = new CurrentlyPlayingDisplay + Child = currentlyOnline = new CurrentlyOnlineDisplay { RelativeSizeAxes = Axes.Both, } @@ -58,10 +65,20 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestBasicDisplay() { - AddStep("Add playing user", () => spectatorClient.SendStartPlay(streamingUser.Id, 0)); - AddUntilStep("Panel loaded", () => currentlyPlaying.ChildrenOfType().FirstOrDefault()?.User.Id == 2); - AddStep("Remove playing user", () => spectatorClient.SendEndPlay(streamingUser.Id)); - AddUntilStep("Panel no longer present", () => !currentlyPlaying.ChildrenOfType().Any()); + AddStep("Begin watching user presence", () => metadataClient.BeginWatchingUserPresence()); + AddStep("Add online user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.ChoosingBeatmap() })); + AddUntilStep("Panel loaded", () => currentlyOnline.ChildrenOfType().FirstOrDefault()?.User.Id == 2); + AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType().First().Enabled.Value, () => Is.False); + + AddStep("User began playing", () => spectatorClient.SendStartPlay(streamingUser.Id, 0)); + AddAssert("Spectate button enabled", () => currentlyOnline.ChildrenOfType().First().Enabled.Value, () => Is.True); + + AddStep("User finished playing", () => spectatorClient.SendEndPlay(streamingUser.Id)); + AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType().First().Enabled.Value, () => Is.False); + + AddStep("Remove playing user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, null)); + AddUntilStep("Panel no longer present", () => !currentlyOnline.ChildrenOfType().Any()); + AddStep("End watching user presence", () => metadataClient.EndWatchingUserPresence()); } internal partial class TestUserLookupCache : UserLookupCache diff --git a/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs b/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs new file mode 100644 index 0000000000..16cbf879df --- /dev/null +++ b/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs @@ -0,0 +1,81 @@ +// 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.Threading.Tasks; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Online.API; +using osu.Game.Online.Metadata; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Metadata +{ + public partial class TestMetadataClient : MetadataClient + { + public override IBindable IsConnected => new BindableBool(true); + + public override IBindable IsWatchingUserPresence => isWatchingUserPresence; + private readonly BindableBool isWatchingUserPresence = new BindableBool(); + + public override IBindableDictionary UserStates => userStates; + private readonly BindableDictionary userStates = new BindableDictionary(); + + [Resolved] + private IAPIProvider api { get; set; } = null!; + + public override Task BeginWatchingUserPresence() + { + isWatchingUserPresence.Value = true; + return Task.CompletedTask; + } + + public override Task EndWatchingUserPresence() + { + isWatchingUserPresence.Value = false; + return Task.CompletedTask; + } + + public override Task UpdateActivity(UserActivity? activity) + { + if (isWatchingUserPresence.Value) + { + userStates.TryGetValue(api.LocalUser.Value.Id, out var localUserPresence); + localUserPresence = localUserPresence with { Activity = activity }; + userStates[api.LocalUser.Value.Id] = localUserPresence; + } + + return Task.CompletedTask; + } + + public override Task UpdateStatus(UserStatus? status) + { + if (isWatchingUserPresence.Value) + { + userStates.TryGetValue(api.LocalUser.Value.Id, out var localUserPresence); + localUserPresence = localUserPresence with { Status = status }; + userStates[api.LocalUser.Value.Id] = localUserPresence; + } + + return Task.CompletedTask; + } + + public override Task UserPresenceUpdated(int userId, UserPresence? presence) + { + if (isWatchingUserPresence.Value) + { + if (presence.HasValue) + userStates[userId] = presence.Value; + else + userStates.Remove(userId); + } + + return Task.CompletedTask; + } + + public override Task GetChangesSince(int queueId) + => Task.FromResult(new BeatmapUpdates(Array.Empty(), queueId)); + + public override Task BeatmapSetsUpdated(BeatmapUpdates updates) => Task.CompletedTask; + } +} From 37049d41b4850da8c15f5dedc0c7f06a6807be4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 6 Dec 2023 18:25:50 +0100 Subject: [PATCH 1875/4092] Show user's status as tooltip on the extended user panel --- osu.Game/Users/ExtendedUserPanel.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game/Users/ExtendedUserPanel.cs b/osu.Game/Users/ExtendedUserPanel.cs index 18fe852556..1359f5d792 100644 --- a/osu.Game/Users/ExtendedUserPanel.cs +++ b/osu.Game/Users/ExtendedUserPanel.cs @@ -9,10 +9,12 @@ using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Users.Drawables; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Users @@ -26,7 +28,7 @@ namespace osu.Game.Users protected TextFlowContainer LastVisitMessage { get; private set; } private StatusIcon statusIcon; - private OsuSpriteText statusMessage; + private StatusText statusMessage; protected ExtendedUserPanel(APIUser user) : base(user) @@ -88,7 +90,7 @@ namespace osu.Game.Users } })); - statusContainer.Add(statusMessage = new OsuSpriteText + statusContainer.Add(statusMessage = new StatusText { Anchor = alignment, Origin = alignment, @@ -108,12 +110,14 @@ namespace osu.Game.Users if (activity != null && status != UserStatus.Offline) { statusMessage.Text = activity.GetStatus(); + statusMessage.TooltipText = activity.GetDetails(); statusIcon.FadeColour(activity.GetAppropriateColour(Colours), 500, Easing.OutQuint); return; } // Otherwise use only status statusMessage.Text = status.GetLocalisableDescription(); + statusMessage.TooltipText = string.Empty; statusIcon.FadeColour(status.Value.GetAppropriateColour(Colours), 500, Easing.OutQuint); return; @@ -140,5 +144,10 @@ namespace osu.Game.Users BorderThickness = 0; base.OnHoverLost(e); } + + private partial class StatusText : OsuSpriteText, IHasTooltip + { + public LocalisableString TooltipText { get; set; } + } } } From 193047619210ce42a2a54e87dcce2bffb5ddab70 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 7 Dec 2023 00:26:13 +0100 Subject: [PATCH 1876/4092] Add circle arc segments --- .../Sliders/SliderPlacementBlueprint.cs | 77 ++++++++++++++++++- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 672ac43f2c..f5d0d82c2a 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -335,15 +336,85 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders if (segment.Count == 0) continue; - HitObject.Path.ControlPoints.Add(new PathControlPoint(segment[0], PathType.BSpline(4))); - for (int j = 1; j < segment.Count - 1; j++) - HitObject.Path.ControlPoints.Add(new PathControlPoint(segment[j])); + // Replace this segment with a circular arc if it is a reasonable substitute. + var circleArcSegment = tryCircleArc(segment); + + if (circleArcSegment is not null) + { + HitObject.Path.ControlPoints.Add(new PathControlPoint(circleArcSegment[0], PathType.PERFECT_CURVE)); + HitObject.Path.ControlPoints.Add(new PathControlPoint(circleArcSegment[1])); + } + else + { + HitObject.Path.ControlPoints.Add(new PathControlPoint(segment[0], PathType.BSpline(4))); + for (int j = 1; j < segment.Count - 1; j++) + HitObject.Path.ControlPoints.Add(new PathControlPoint(segment[j])); + } if (isLastSegment) HitObject.Path.ControlPoints.Add(new PathControlPoint(segment[^1])); } } + private Vector2[] tryCircleArc(List segment) + { + if (segment.Count < 3) return null; + + // Assume the segment creates a reasonable circular arc and then check if it reasonable + var points = PathApproximator.BSplineToPiecewiseLinear(segment.ToArray(), bSplineBuilder.Degree); + var circleArcControlPoints = new[] { points[0], points[points.Count / 2], points[^1] }; + var circleArc = new CircularArcProperties(circleArcControlPoints); + + if (!circleArc.IsValid) return null; + + double length = circleArc.ThetaRange * circleArc.Radius; + + if (length > 1000) return null; + + double loss = 0; + Vector2? lastPoint = null; + Vector2? lastVec = null; + int? lastDir = null; + double totalWinding = 0; + + // Loop through the points and check if they are not too far away from the circular arc. + // Also make sure it curves monotonically in one direction and at most one loop is done. + foreach (var point in points) + { + loss += Math.Pow((Vector2.Distance(point, circleArc.Centre) - circleArc.Radius) / length, 2); + + if (lastPoint.HasValue) + { + var vec = point - lastPoint.Value; + + if (lastVec.HasValue) + { + double dot = Vector2.Dot(vec, lastVec.Value); + double det = lastVec.Value.X * vec.Y - lastVec.Value.Y * vec.X; + double angle = Math.Atan2(det, dot); + int dir = Math.Sign(angle); + + if (dir == 0) + continue; + + if (lastDir.HasValue && dir != lastDir) + return null; // Curvature changed, like in an S-shape + + totalWinding += Math.Abs(angle); + lastDir = dir; + } + + lastVec = vec; + } + + lastPoint = point; + } + + loss /= points.Count; + + return loss > 0.002 || totalWinding > MathHelper.TwoPi ? null : circleArcControlPoints; + } + private enum SliderPlacementState { Initial, From 89859b85b7e8523171fe6e7d686bf4e6ecb99d3c Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 7 Dec 2023 00:43:34 +0100 Subject: [PATCH 1877/4092] add controllable leniency --- .../Sliders/SliderPlacementBlueprint.cs | 9 ++++-- .../Edit/FreehandSliderToolboxGroup.cs | 32 +++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index f5d0d82c2a..5878c0e11b 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -95,6 +95,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders bSplineBuilder.CornerThreshold = e.NewValue; Scheduler.AddOnce(updateSliderPathFromBSplineBuilder); }, true); + + freehandToolboxGroup.CircleThreshold.BindValueChanged(e => + { + Scheduler.AddOnce(updateSliderPathFromBSplineBuilder); + }, true); } } @@ -358,7 +363,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private Vector2[] tryCircleArc(List segment) { - if (segment.Count < 3) return null; + if (segment.Count < 3 || freehandToolboxGroup.CircleThreshold.Value == 0) return null; // Assume the segment creates a reasonable circular arc and then check if it reasonable var points = PathApproximator.BSplineToPiecewiseLinear(segment.ToArray(), bSplineBuilder.Degree); @@ -412,7 +417,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders loss /= points.Count; - return loss > 0.002 || totalWinding > MathHelper.TwoPi ? null : circleArcControlPoints; + return loss > freehandToolboxGroup.CircleThreshold.Value || totalWinding > MathHelper.TwoPi ? null : circleArcControlPoints; } private enum SliderPlacementState diff --git a/osu.Game.Rulesets.Osu/Edit/FreehandSliderToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/FreehandSliderToolboxGroup.cs index 7d1eb4e270..c574280267 100644 --- a/osu.Game.Rulesets.Osu/Edit/FreehandSliderToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/FreehandSliderToolboxGroup.cs @@ -31,6 +31,13 @@ namespace osu.Game.Rulesets.Osu.Edit Precision = 0.01f }; + public BindableFloat CircleThreshold { get; } = new BindableFloat(0.002f) + { + MinValue = 0f, + MaxValue = 0.005f, + Precision = 0.0001f + }; + // We map internal ranges to a more standard range of values for display to the user. private readonly BindableInt displayTolerance = new BindableInt(100) { @@ -44,8 +51,15 @@ namespace osu.Game.Rulesets.Osu.Edit MaxValue = 100 }; + private readonly BindableInt displayCircleThreshold = new BindableInt(40) + { + MinValue = 0, + MaxValue = 100 + }; + private ExpandableSlider toleranceSlider = null!; private ExpandableSlider cornerThresholdSlider = null!; + private ExpandableSlider circleThresholdSlider = null!; [BackgroundDependencyLoader] private void load() @@ -59,6 +73,10 @@ namespace osu.Game.Rulesets.Osu.Edit cornerThresholdSlider = new ExpandableSlider { Current = displayCornerThreshold + }, + circleThresholdSlider = new ExpandableSlider + { + Current = displayCircleThreshold } }; } @@ -83,18 +101,32 @@ namespace osu.Game.Rulesets.Osu.Edit CornerThreshold.Value = displayToInternalCornerThreshold(threshold.NewValue); }, true); + displayCircleThreshold.BindValueChanged(threshold => + { + circleThresholdSlider.ContractedLabelText = $"P. C. T.: {threshold.NewValue:N0}"; + circleThresholdSlider.ExpandedLabelText = $"Perfect Curve Threshold: {threshold.NewValue:N0}"; + + CircleThreshold.Value = displayToInternalCircleThreshold(threshold.NewValue); + }, true); + Tolerance.BindValueChanged(tolerance => displayTolerance.Value = internalToDisplayTolerance(tolerance.NewValue) ); CornerThreshold.BindValueChanged(threshold => displayCornerThreshold.Value = internalToDisplayCornerThreshold(threshold.NewValue) ); + CircleThreshold.BindValueChanged(threshold => + displayCircleThreshold.Value = internalToDisplayCircleThreshold(threshold.NewValue) + ); float displayToInternalTolerance(float v) => v / 50f; int internalToDisplayTolerance(float v) => (int)Math.Round(v * 50f); float displayToInternalCornerThreshold(float v) => v / 100f; int internalToDisplayCornerThreshold(float v) => (int)Math.Round(v * 100f); + + float displayToInternalCircleThreshold(float v) => v / 20000f; + int internalToDisplayCircleThreshold(float v) => (int)Math.Round(v * 20000f); } } } From a2ec75d824b4bc80198a8937944ed84d7f904158 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 7 Dec 2023 00:57:29 +0100 Subject: [PATCH 1878/4092] Fix illegal circle arc with center out of polygon --- .../Sliders/SliderPlacementBlueprint.cs | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 5878c0e11b..e5dada19bb 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -379,37 +379,56 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders double loss = 0; Vector2? lastPoint = null; Vector2? lastVec = null; + Vector2? lastVec2 = null; int? lastDir = null; + int? lastDir2 = null; double totalWinding = 0; // Loop through the points and check if they are not too far away from the circular arc. // Also make sure it curves monotonically in one direction and at most one loop is done. foreach (var point in points) { - loss += Math.Pow((Vector2.Distance(point, circleArc.Centre) - circleArc.Radius) / length, 2); + var vec = point - circleArc.Centre; + loss += Math.Pow((vec.Length - circleArc.Radius) / length, 2); + + if (lastVec.HasValue) + { + double det = lastVec.Value.X * vec.Y - lastVec.Value.Y * vec.X; + int dir = Math.Sign(det); + + if (dir == 0) + continue; + + if (lastDir.HasValue && dir != lastDir) + return null; // Circle center is not inside the polygon + + lastDir = dir; + } + + lastVec = vec; if (lastPoint.HasValue) { - var vec = point - lastPoint.Value; + var vec2 = point - lastPoint.Value; - if (lastVec.HasValue) + if (lastVec2.HasValue) { - double dot = Vector2.Dot(vec, lastVec.Value); - double det = lastVec.Value.X * vec.Y - lastVec.Value.Y * vec.X; + double dot = Vector2.Dot(vec2, lastVec2.Value); + double det = lastVec2.Value.X * vec2.Y - lastVec2.Value.Y * vec2.X; double angle = Math.Atan2(det, dot); - int dir = Math.Sign(angle); + int dir2 = Math.Sign(angle); - if (dir == 0) + if (dir2 == 0) continue; - if (lastDir.HasValue && dir != lastDir) + if (lastDir2.HasValue && dir2 != lastDir2) return null; // Curvature changed, like in an S-shape totalWinding += Math.Abs(angle); - lastDir = dir; + lastDir2 = dir2; } - lastVec = vec; + lastVec2 = vec2; } lastPoint = point; From 7b49db05d134676605efd0859016c78f9900ebee Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 7 Dec 2023 01:15:42 +0100 Subject: [PATCH 1879/4092] Update default parameters to be slightly better --- osu.Game.Rulesets.Osu/Edit/FreehandSliderToolboxGroup.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/FreehandSliderToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/FreehandSliderToolboxGroup.cs index c574280267..f17118ba34 100644 --- a/osu.Game.Rulesets.Osu/Edit/FreehandSliderToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/FreehandSliderToolboxGroup.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Edit { } - public BindableFloat Tolerance { get; } = new BindableFloat(2f) + public BindableFloat Tolerance { get; } = new BindableFloat(1.8f) { MinValue = 0.05f, MaxValue = 2.0f, @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Edit Precision = 0.01f }; - public BindableFloat CircleThreshold { get; } = new BindableFloat(0.002f) + public BindableFloat CircleThreshold { get; } = new BindableFloat(0.0015f) { MinValue = 0f, MaxValue = 0.005f, @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Edit }; // We map internal ranges to a more standard range of values for display to the user. - private readonly BindableInt displayTolerance = new BindableInt(100) + private readonly BindableInt displayTolerance = new BindableInt(90) { MinValue = 5, MaxValue = 100 @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Edit MaxValue = 100 }; - private readonly BindableInt displayCircleThreshold = new BindableInt(40) + private readonly BindableInt displayCircleThreshold = new BindableInt(30) { MinValue = 0, MaxValue = 100 From d6cb8b70bb653ae2bdf4a3a483818dc06bc1f7a3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 7 Dec 2023 12:25:23 +0900 Subject: [PATCH 1880/4092] Fix FP precision issue when converting mania beatmaps --- .../ManiaBeatmapConversionTest.cs | 4 +- .../ManiaBeatmapSampleConversionTest.cs | 2 +- .../ManiaDifficultyCalculatorTest.cs | 2 +- .../Beatmaps/100374-expected-conversion.json | 1 + .../Resources/Testing/Beatmaps/100374.osu | 449 ++++++++++++++++++ .../Beatmaps/20544-expected-conversion.json | 1 + .../Resources/Testing/Beatmaps/20544.osu | 126 +++++ .../Beatmaps/basic-expected-conversion.json | 0 .../Resources/Testing/Beatmaps/basic.osu | 54 +-- .../convert-samples-expected-conversion.json | 0 .../Testing/Beatmaps/convert-samples.osu | 0 .../Testing/Beatmaps/diffcalc-test.osu | 0 .../mania-samples-expected-conversion.json | 0 .../Testing/Beatmaps/mania-samples.osu | 0 ...r-convert-samples-expected-conversion.json | 0 .../Beatmaps/slider-convert-samples.osu | 0 ...ero-length-slider-expected-conversion.json | 0 .../Testing/Beatmaps/zero-length-slider.osu | 0 .../Patterns/Legacy/PatternGenerator.cs | 8 +- 19 files changed, 615 insertions(+), 32 deletions(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/100374-expected-conversion.json create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/100374.osu create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/20544-expected-conversion.json create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/20544.osu rename {osu.Game.Rulesets.Mania => osu.Game.Rulesets.Mania.Tests}/Resources/Testing/Beatmaps/basic-expected-conversion.json (100%) rename {osu.Game.Rulesets.Mania => osu.Game.Rulesets.Mania.Tests}/Resources/Testing/Beatmaps/basic.osu (96%) rename {osu.Game.Rulesets.Mania => osu.Game.Rulesets.Mania.Tests}/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json (100%) rename {osu.Game.Rulesets.Mania => osu.Game.Rulesets.Mania.Tests}/Resources/Testing/Beatmaps/convert-samples.osu (100%) rename {osu.Game.Rulesets.Mania => osu.Game.Rulesets.Mania.Tests}/Resources/Testing/Beatmaps/diffcalc-test.osu (100%) rename {osu.Game.Rulesets.Mania => osu.Game.Rulesets.Mania.Tests}/Resources/Testing/Beatmaps/mania-samples-expected-conversion.json (100%) rename {osu.Game.Rulesets.Mania => osu.Game.Rulesets.Mania.Tests}/Resources/Testing/Beatmaps/mania-samples.osu (100%) rename {osu.Game.Rulesets.Mania => osu.Game.Rulesets.Mania.Tests}/Resources/Testing/Beatmaps/slider-convert-samples-expected-conversion.json (100%) rename {osu.Game.Rulesets.Mania => osu.Game.Rulesets.Mania.Tests}/Resources/Testing/Beatmaps/slider-convert-samples.osu (100%) rename {osu.Game.Rulesets.Mania => osu.Game.Rulesets.Mania.Tests}/Resources/Testing/Beatmaps/zero-length-slider-expected-conversion.json (100%) rename {osu.Game.Rulesets.Mania => osu.Game.Rulesets.Mania.Tests}/Resources/Testing/Beatmaps/zero-length-slider.osu (100%) diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs index ef6dca620a..435d5e737e 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs @@ -18,10 +18,12 @@ namespace osu.Game.Rulesets.Mania.Tests [TestFixture] public class ManiaBeatmapConversionTest : BeatmapConversionTest { - protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; + protected override string ResourceAssembly => "osu.Game.Rulesets.Mania.Tests"; [TestCase("basic")] [TestCase("zero-length-slider")] + [TestCase("20544")] + [TestCase("100374")] public void Test(string name) => base.Test(name); protected override IEnumerable CreateConvertValue(HitObject hitObject) diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs index 51f35d3c3d..99598557a6 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Tests [TestFixture] public class ManiaBeatmapSampleConversionTest : BeatmapConversionTest, SampleConvertValue> { - protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; + protected override string ResourceAssembly => "osu.Game.Rulesets.Mania.Tests"; [TestCase("convert-samples")] [TestCase("mania-samples")] diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs index 7b0171a9ee..229df4b67b 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Mania.Tests { public class ManiaDifficultyCalculatorTest : DifficultyCalculatorTest { - protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; + protected override string ResourceAssembly => "osu.Game.Rulesets.Mania.Tests"; [TestCase(2.3493769750220914d, 242, "diffcalc-test")] public void Test(double expectedStarRating, int expectedMaxCombo, string name) diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/100374-expected-conversion.json b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/100374-expected-conversion.json new file mode 100644 index 0000000000..59f73f7ad4 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/100374-expected-conversion.json @@ -0,0 +1 @@ +{"Mappings":[{"RandomW":273084013,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":15562.0,"Objects":[{"StartTime":15562.0,"EndTime":17155.0,"Column":0}]},{"RandomW":2659258901,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273084013,"StartTime":17686.0,"Objects":[{"StartTime":17686.0,"EndTime":17686.0,"Column":0},{"StartTime":17686.0,"EndTime":17686.0,"Column":1}]},{"RandomW":3083655709,"RandomX":273326509,"RandomY":273084013,"RandomZ":2659258901,"StartTime":17951.0,"Objects":[{"StartTime":17951.0,"EndTime":17951.0,"Column":1}]},{"RandomW":3588026162,"RandomX":2659258901,"RandomY":3083655709,"RandomZ":4073603712,"StartTime":18217.0,"Objects":[{"StartTime":18217.0,"EndTime":18217.0,"Column":2},{"StartTime":18217.0,"EndTime":18217.0,"Column":4}]},{"RandomW":1130061350,"RandomX":3083655709,"RandomY":4073603712,"RandomZ":3588026162,"StartTime":18482.0,"Objects":[{"StartTime":18482.0,"EndTime":18482.0,"Column":2}]},{"RandomW":315421426,"RandomX":3588026162,"RandomY":1130061350,"RandomZ":2459334754,"StartTime":18748.0,"Objects":[{"StartTime":18748.0,"EndTime":19013.0,"Column":0}]},{"RandomW":3110660773,"RandomX":2459334754,"RandomY":315421426,"RandomZ":542845670,"StartTime":19279.0,"Objects":[{"StartTime":19279.0,"EndTime":19809.0,"Column":3},{"StartTime":19544.0,"EndTime":19544.0,"Column":1},{"StartTime":19809.0,"EndTime":19809.0,"Column":1}]},{"RandomW":3110660773,"RandomX":2459334754,"RandomY":315421426,"RandomZ":542845670,"StartTime":20075.0,"Objects":[{"StartTime":20075.0,"EndTime":20075.0,"Column":4},{"StartTime":20075.0,"EndTime":20075.0,"Column":2}]},{"RandomW":2552021122,"RandomX":315421426,"RandomY":542845670,"RandomZ":3110660773,"StartTime":20341.0,"Objects":[{"StartTime":20341.0,"EndTime":20341.0,"Column":3}]},{"RandomW":3979536913,"RandomX":542845670,"RandomY":3110660773,"RandomZ":2552021122,"StartTime":20606.0,"Objects":[{"StartTime":20606.0,"EndTime":20606.0,"Column":2},{"StartTime":20606.0,"EndTime":20606.0,"Column":3}]},{"RandomW":3926138036,"RandomX":2552021122,"RandomY":3979536913,"RandomZ":348643659,"StartTime":20871.0,"Objects":[{"StartTime":20871.0,"EndTime":21401.0,"Column":4}]},{"RandomW":4001028953,"RandomX":348643659,"RandomY":3926138036,"RandomZ":2489502118,"StartTime":21933.0,"Objects":[{"StartTime":21933.0,"EndTime":22198.0,"Column":5}]},{"RandomW":263714783,"RandomX":2489502118,"RandomY":4001028953,"RandomZ":3315380836,"StartTime":22464.0,"Objects":[{"StartTime":22464.0,"EndTime":22729.0,"Column":0}]},{"RandomW":3045229215,"RandomX":3315380836,"RandomY":263714783,"RandomZ":2367299702,"StartTime":22995.0,"Objects":[{"StartTime":22995.0,"EndTime":23791.0,"Column":2}]},{"RandomW":622075324,"RandomX":2367299702,"RandomY":3045229215,"RandomZ":2511145433,"StartTime":24057.0,"Objects":[{"StartTime":24057.0,"EndTime":24322.0,"Column":1}]},{"RandomW":1428674661,"RandomX":3630592823,"RandomY":628640291,"RandomZ":2684635853,"StartTime":24588.0,"Objects":[{"StartTime":24588.0,"EndTime":24853.0,"Column":4},{"StartTime":24588.0,"EndTime":24853.0,"Column":3}]},{"RandomW":2963472042,"RandomX":3191072317,"RandomY":1509788298,"RandomZ":3677221210,"StartTime":25119.0,"Objects":[{"StartTime":25119.0,"EndTime":25649.0,"Column":2}]},{"RandomW":2441208973,"RandomX":1509788298,"RandomY":3677221210,"RandomZ":2963472042,"StartTime":26181.0,"Objects":[{"StartTime":26181.0,"EndTime":26181.0,"Column":2},{"StartTime":26181.0,"EndTime":26181.0,"Column":3}]},{"RandomW":614303213,"RandomX":3677221210,"RandomY":2963472042,"RandomZ":2441208973,"StartTime":26447.0,"Objects":[{"StartTime":26447.0,"EndTime":26447.0,"Column":3}]},{"RandomW":931064848,"RandomX":2441208973,"RandomY":614303213,"RandomZ":2425227013,"StartTime":26712.0,"Objects":[{"StartTime":26712.0,"EndTime":26977.0,"Column":2}]},{"RandomW":1631554006,"RandomX":2425227013,"RandomY":931064848,"RandomZ":2839921662,"StartTime":27243.0,"Objects":[{"StartTime":27243.0,"EndTime":27508.0,"Column":4}]},{"RandomW":1102544522,"RandomX":2839921662,"RandomY":1631554006,"RandomZ":2171149531,"StartTime":27774.0,"Objects":[{"StartTime":27774.0,"EndTime":28039.0,"Column":3}]},{"RandomW":1535528787,"RandomX":2171149531,"RandomY":1102544522,"RandomZ":3328843633,"StartTime":28305.0,"Objects":[{"StartTime":28305.0,"EndTime":28835.0,"Column":4},{"StartTime":28305.0,"EndTime":28305.0,"Column":3},{"StartTime":28570.0,"EndTime":28570.0,"Column":3},{"StartTime":28835.0,"EndTime":28835.0,"Column":3}]},{"RandomW":2462060348,"RandomX":1102544522,"RandomY":3328843633,"RandomZ":1535528787,"StartTime":29102.0,"Objects":[{"StartTime":29102.0,"EndTime":29102.0,"Column":3}]},{"RandomW":2548780898,"RandomX":2462060348,"RandomY":1752789184,"RandomZ":4269701929,"StartTime":29367.0,"Objects":[{"StartTime":29367.0,"EndTime":29897.0,"Column":5},{"StartTime":29367.0,"EndTime":29897.0,"Column":1}]},{"RandomW":2872444045,"RandomX":2548780898,"RandomY":96471884,"RandomZ":2795275332,"StartTime":30429.0,"Objects":[{"StartTime":30429.0,"EndTime":30694.0,"Column":2}]},{"RandomW":554186146,"RandomX":2872444045,"RandomY":1718345430,"RandomZ":1676944188,"StartTime":30960.0,"Objects":[{"StartTime":30960.0,"EndTime":31225.0,"Column":4},{"StartTime":30960.0,"EndTime":31225.0,"Column":1}]},{"RandomW":44350362,"RandomX":1676944188,"RandomY":554186146,"RandomZ":973164386,"StartTime":31491.0,"Objects":[{"StartTime":31491.0,"EndTime":32287.0,"Column":0}]},{"RandomW":2689469863,"RandomX":973164386,"RandomY":44350362,"RandomZ":3230373169,"StartTime":32553.0,"Objects":[{"StartTime":32553.0,"EndTime":32818.0,"Column":1}]},{"RandomW":3076210018,"RandomX":3230373169,"RandomY":2689469863,"RandomZ":2416196755,"StartTime":33084.0,"Objects":[{"StartTime":33084.0,"EndTime":33349.0,"Column":2}]},{"RandomW":4212524875,"RandomX":2416196755,"RandomY":3076210018,"RandomZ":736433317,"StartTime":33615.0,"Objects":[{"StartTime":33615.0,"EndTime":34145.0,"Column":5}]},{"RandomW":668643347,"RandomX":4212524875,"RandomY":1246190622,"RandomZ":614058009,"StartTime":34677.0,"Objects":[{"StartTime":34677.0,"EndTime":34677.0,"Column":0},{"StartTime":34677.0,"EndTime":34677.0,"Column":5}]},{"RandomW":4133034829,"RandomX":668643347,"RandomY":1824376828,"RandomZ":476758489,"StartTime":34942.0,"Objects":[{"StartTime":34942.0,"EndTime":34942.0,"Column":1},{"StartTime":34942.0,"EndTime":34942.0,"Column":5}]},{"RandomW":82933693,"RandomX":1824376828,"RandomY":476758489,"RandomZ":4133034829,"StartTime":35208.0,"Objects":[{"StartTime":35208.0,"EndTime":35208.0,"Column":0},{"StartTime":35208.0,"EndTime":35208.0,"Column":1}]},{"RandomW":2263995128,"RandomX":476758489,"RandomY":4133034829,"RandomZ":82933693,"StartTime":35473.0,"Objects":[{"StartTime":35473.0,"EndTime":35473.0,"Column":1}]},{"RandomW":3437211638,"RandomX":4133034829,"RandomY":82933693,"RandomZ":2263995128,"StartTime":35739.0,"Objects":[{"StartTime":35739.0,"EndTime":35739.0,"Column":2}]},{"RandomW":2107738941,"RandomX":2263995128,"RandomY":3437211638,"RandomZ":4066526803,"StartTime":36004.0,"Objects":[{"StartTime":36004.0,"EndTime":36004.0,"Column":2},{"StartTime":36004.0,"EndTime":36004.0,"Column":5}]},{"RandomW":1976561763,"RandomX":3437211638,"RandomY":4066526803,"RandomZ":2107738941,"StartTime":36270.0,"Objects":[{"StartTime":36270.0,"EndTime":36270.0,"Column":3},{"StartTime":36270.0,"EndTime":36270.0,"Column":4}]},{"RandomW":1147027763,"RandomX":4066526803,"RandomY":2107738941,"RandomZ":1976561763,"StartTime":36535.0,"Objects":[{"StartTime":36535.0,"EndTime":36535.0,"Column":3}]},{"RandomW":3580315894,"RandomX":1976561763,"RandomY":1147027763,"RandomZ":2767111989,"StartTime":36801.0,"Objects":[{"StartTime":36801.0,"EndTime":37331.0,"Column":4}]},{"RandomW":3743545041,"RandomX":1147027763,"RandomY":2767111989,"RandomZ":3580315894,"StartTime":37597.0,"Objects":[{"StartTime":37597.0,"EndTime":37597.0,"Column":1}]},{"RandomW":1409948107,"RandomX":3743545041,"RandomY":1774216159,"RandomZ":3150304957,"StartTime":37863.0,"Objects":[{"StartTime":37863.0,"EndTime":38393.0,"Column":2},{"StartTime":37863.0,"EndTime":38393.0,"Column":3}]},{"RandomW":4009340712,"RandomX":3150304957,"RandomY":1409948107,"RandomZ":2219703013,"StartTime":38925.0,"Objects":[{"StartTime":38925.0,"EndTime":39190.0,"Column":5}]},{"RandomW":3071167491,"RandomX":2065497204,"RandomY":2145154717,"RandomZ":2494378321,"StartTime":39456.0,"Objects":[{"StartTime":39456.0,"EndTime":39721.0,"Column":0},{"StartTime":39456.0,"EndTime":39721.0,"Column":2}]},{"RandomW":1245938367,"RandomX":3071167491,"RandomY":728627658,"RandomZ":3080260260,"StartTime":39987.0,"Objects":[{"StartTime":39987.0,"EndTime":40783.0,"Column":3}]},{"RandomW":3032241617,"RandomX":1245938367,"RandomY":2414391712,"RandomZ":3406801470,"StartTime":41048.0,"Objects":[{"StartTime":41048.0,"EndTime":41313.0,"Column":2}]},{"RandomW":3367991920,"RandomX":3804000131,"RandomY":672376773,"RandomZ":2667292323,"StartTime":41579.0,"Objects":[{"StartTime":41579.0,"EndTime":41844.0,"Column":1},{"StartTime":41579.0,"EndTime":41844.0,"Column":3}]},{"RandomW":2095476726,"RandomX":2667292323,"RandomY":3367991920,"RandomZ":3380532371,"StartTime":42110.0,"Objects":[{"StartTime":42110.0,"EndTime":42640.0,"Column":5}]},{"RandomW":869340745,"RandomX":2095476726,"RandomY":1063981175,"RandomZ":204767504,"StartTime":43172.0,"Objects":[{"StartTime":43172.0,"EndTime":43172.0,"Column":1},{"StartTime":43172.0,"EndTime":43172.0,"Column":4}]},{"RandomW":461904197,"RandomX":204767504,"RandomY":869340745,"RandomZ":2080855578,"StartTime":43438.0,"Objects":[{"StartTime":43438.0,"EndTime":43438.0,"Column":2},{"StartTime":43438.0,"EndTime":43438.0,"Column":1}]},{"RandomW":3004966693,"RandomX":869340745,"RandomY":2080855578,"RandomZ":461904197,"StartTime":43703.0,"Objects":[{"StartTime":43703.0,"EndTime":43703.0,"Column":3},{"StartTime":43703.0,"EndTime":43703.0,"Column":4}]},{"RandomW":147065937,"RandomX":2080855578,"RandomY":461904197,"RandomZ":3004966693,"StartTime":43969.0,"Objects":[{"StartTime":43969.0,"EndTime":43969.0,"Column":4}]},{"RandomW":1312111829,"RandomX":461904197,"RandomY":3004966693,"RandomZ":147065937,"StartTime":44234.0,"Objects":[{"StartTime":44234.0,"EndTime":44234.0,"Column":4}]},{"RandomW":355223143,"RandomX":3004966693,"RandomY":147065937,"RandomZ":1312111829,"StartTime":44500.0,"Objects":[{"StartTime":44500.0,"EndTime":44500.0,"Column":3}]},{"RandomW":1197174504,"RandomX":147065937,"RandomY":1312111829,"RandomZ":355223143,"StartTime":44765.0,"Objects":[{"StartTime":44765.0,"EndTime":44765.0,"Column":2},{"StartTime":44765.0,"EndTime":44765.0,"Column":3}]},{"RandomW":2296450669,"RandomX":355223143,"RandomY":1197174504,"RandomZ":1876247766,"StartTime":45031.0,"Objects":[{"StartTime":45031.0,"EndTime":45031.0,"Column":1},{"StartTime":45031.0,"EndTime":45031.0,"Column":0}]},{"RandomW":1664705375,"RandomX":1876247766,"RandomY":2296450669,"RandomZ":4287200872,"StartTime":45296.0,"Objects":[{"StartTime":45296.0,"EndTime":45296.0,"Column":0},{"StartTime":45296.0,"EndTime":45296.0,"Column":4}]},{"RandomW":2786027546,"RandomX":2296450669,"RandomY":4287200872,"RandomZ":1664705375,"StartTime":45562.0,"Objects":[{"StartTime":45562.0,"EndTime":45562.0,"Column":1}]},{"RandomW":639469776,"RandomX":4287200872,"RandomY":1664705375,"RandomZ":2786027546,"StartTime":45827.0,"Objects":[{"StartTime":45827.0,"EndTime":45827.0,"Column":3},{"StartTime":45827.0,"EndTime":45827.0,"Column":4}]},{"RandomW":2463352901,"RandomX":1664705375,"RandomY":2786027546,"RandomZ":639469776,"StartTime":46093.0,"Objects":[{"StartTime":46093.0,"EndTime":46093.0,"Column":4}]},{"RandomW":760995091,"RandomX":2463352901,"RandomY":978871003,"RandomZ":3888812594,"StartTime":46358.0,"Objects":[{"StartTime":46358.0,"EndTime":46888.0,"Column":2}]},{"RandomW":3631307076,"RandomX":3888812594,"RandomY":760995091,"RandomZ":566667549,"StartTime":47420.0,"Objects":[{"StartTime":47420.0,"EndTime":47685.0,"Column":4}]},{"RandomW":2353216536,"RandomX":3631307076,"RandomY":1805196154,"RandomZ":2564415583,"StartTime":47951.0,"Objects":[{"StartTime":47951.0,"EndTime":48216.0,"Column":1},{"StartTime":47951.0,"EndTime":48216.0,"Column":0}]},{"RandomW":717730087,"RandomX":2353216536,"RandomY":3735744429,"RandomZ":2102099401,"StartTime":48482.0,"Objects":[{"StartTime":48482.0,"EndTime":49278.0,"Column":5},{"StartTime":48482.0,"EndTime":49278.0,"Column":2}]},{"RandomW":271333990,"RandomX":717730087,"RandomY":3220302747,"RandomZ":917482575,"StartTime":49544.0,"Objects":[{"StartTime":49544.0,"EndTime":49809.0,"Column":0}]},{"RandomW":937976203,"RandomX":917482575,"RandomY":271333990,"RandomZ":125173709,"StartTime":50075.0,"Objects":[{"StartTime":50075.0,"EndTime":50340.0,"Column":2}]},{"RandomW":2781059562,"RandomX":937976203,"RandomY":2087616237,"RandomZ":232817676,"StartTime":50606.0,"Objects":[{"StartTime":50606.0,"EndTime":51667.0,"Column":0},{"StartTime":50606.0,"EndTime":51667.0,"Column":1}]},{"RandomW":3511898336,"RandomX":2087616237,"RandomY":232817676,"RandomZ":2781059562,"StartTime":52730.0,"Objects":[{"StartTime":52730.0,"EndTime":52730.0,"Column":4}]},{"RandomW":623291556,"RandomX":3737503025,"RandomY":3607951873,"RandomZ":1857627587,"StartTime":53792.0,"Objects":[{"StartTime":53792.0,"EndTime":54322.0,"Column":5},{"StartTime":53792.0,"EndTime":54322.0,"Column":1}]},{"RandomW":3577350524,"RandomX":3607951873,"RandomY":1857627587,"RandomZ":623291556,"StartTime":54588.0,"Objects":[{"StartTime":54588.0,"EndTime":54588.0,"Column":2}]},{"RandomW":3611414219,"RandomX":1700150568,"RandomY":3261504380,"RandomZ":3526708248,"StartTime":54854.0,"Objects":[{"StartTime":54854.0,"EndTime":55384.0,"Column":3},{"StartTime":54854.0,"EndTime":55384.0,"Column":4}]},{"RandomW":4116828180,"RandomX":3526708248,"RandomY":3611414219,"RandomZ":53089910,"StartTime":55916.0,"Objects":[{"StartTime":55916.0,"EndTime":56446.0,"Column":5}]},{"RandomW":1419945944,"RandomX":53089910,"RandomY":4116828180,"RandomZ":2370574124,"StartTime":56978.0,"Objects":[{"StartTime":56978.0,"EndTime":57549.0,"Column":3}]},{"RandomW":4235330325,"RandomX":2370574124,"RandomY":1419945944,"RandomZ":124293788,"StartTime":58120.0,"Objects":[{"StartTime":58120.0,"EndTime":58405.0,"Column":5}]},{"RandomW":1354196818,"RandomX":124293788,"RandomY":4235330325,"RandomZ":292200128,"StartTime":58692.0,"Objects":[{"StartTime":58692.0,"EndTime":58973.0,"Column":3}]},{"RandomW":2131632245,"RandomX":292200128,"RandomY":1354196818,"RandomZ":319349674,"StartTime":59325.0,"Objects":[{"StartTime":59325.0,"EndTime":60170.0,"Column":5}]},{"RandomW":987180490,"RandomX":1354196818,"RandomY":319349674,"RandomZ":2131632245,"StartTime":60513.0,"Objects":[{"StartTime":60513.0,"EndTime":60513.0,"Column":3}]},{"RandomW":2247158810,"RandomX":2131632245,"RandomY":987180490,"RandomZ":3518058549,"StartTime":60778.0,"Objects":[{"StartTime":60778.0,"EndTime":61043.0,"Column":0}]},{"RandomW":2347989337,"RandomX":987180490,"RandomY":3518058549,"RandomZ":2247158810,"StartTime":61309.0,"Objects":[{"StartTime":61309.0,"EndTime":61309.0,"Column":3}]},{"RandomW":82954311,"RandomX":1403151684,"RandomY":1362150166,"RandomZ":1092174296,"StartTime":61840.0,"Objects":[{"StartTime":61840.0,"EndTime":62105.0,"Column":0}]},{"RandomW":408605211,"RandomX":82954311,"RandomY":1144587736,"RandomZ":2479248954,"StartTime":62371.0,"Objects":[{"StartTime":62371.0,"EndTime":62901.0,"Column":1}]},{"RandomW":2455999143,"RandomX":1144587736,"RandomY":2479248954,"RandomZ":408605211,"StartTime":63168.0,"Objects":[{"StartTime":63168.0,"EndTime":63168.0,"Column":2}]},{"RandomW":1898608481,"RandomX":2455999143,"RandomY":519590646,"RandomZ":3207504021,"StartTime":63433.0,"Objects":[{"StartTime":63433.0,"EndTime":63963.0,"Column":5}]},{"RandomW":601995191,"RandomX":3207504021,"RandomY":1898608481,"RandomZ":4283573577,"StartTime":64230.0,"Objects":[{"StartTime":64230.0,"EndTime":64230.0,"Column":5},{"StartTime":64230.0,"EndTime":64230.0,"Column":1}]},{"RandomW":3909194070,"RandomX":1898608481,"RandomY":4283573577,"RandomZ":601995191,"StartTime":64495.0,"Objects":[{"StartTime":64495.0,"EndTime":64495.0,"Column":3},{"StartTime":64495.0,"EndTime":64495.0,"Column":4}]},{"RandomW":3417465448,"RandomX":4283573577,"RandomY":601995191,"RandomZ":3909194070,"StartTime":64761.0,"Objects":[{"StartTime":64761.0,"EndTime":64761.0,"Column":4}]},{"RandomW":2779016762,"RandomX":601995191,"RandomY":3909194070,"RandomZ":3417465448,"StartTime":65026.0,"Objects":[{"StartTime":65026.0,"EndTime":65026.0,"Column":4},{"StartTime":65026.0,"EndTime":65026.0,"Column":5}]},{"RandomW":2346068278,"RandomX":3909194070,"RandomY":3417465448,"RandomZ":2779016762,"StartTime":65292.0,"Objects":[{"StartTime":65292.0,"EndTime":65292.0,"Column":3}]},{"RandomW":1857589819,"RandomX":3417465448,"RandomY":2779016762,"RandomZ":2346068278,"StartTime":65557.0,"Objects":[{"StartTime":65557.0,"EndTime":65557.0,"Column":4},{"StartTime":65557.0,"EndTime":65557.0,"Column":5}]},{"RandomW":910236838,"RandomX":2779016762,"RandomY":2346068278,"RandomZ":1857589819,"StartTime":66088.0,"Objects":[{"StartTime":66088.0,"EndTime":66088.0,"Column":2},{"StartTime":66088.0,"EndTime":66088.0,"Column":3}]},{"RandomW":910236838,"RandomX":2779016762,"RandomY":2346068278,"RandomZ":1857589819,"StartTime":66354.0,"Objects":[{"StartTime":66354.0,"EndTime":66354.0,"Column":3},{"StartTime":66354.0,"EndTime":66354.0,"Column":2}]},{"RandomW":2327273799,"RandomX":1857589819,"RandomY":910236838,"RandomZ":2953998826,"StartTime":66619.0,"Objects":[{"StartTime":66619.0,"EndTime":67149.0,"Column":0}]},{"RandomW":540283744,"RandomX":910236838,"RandomY":2953998826,"RandomZ":2327273799,"StartTime":67416.0,"Objects":[{"StartTime":67416.0,"EndTime":67416.0,"Column":0}]},{"RandomW":1024467186,"RandomX":2327273799,"RandomY":540283744,"RandomZ":514760684,"StartTime":67681.0,"Objects":[{"StartTime":67681.0,"EndTime":68211.0,"Column":2}]},{"RandomW":211600206,"RandomX":540283744,"RandomY":514760684,"RandomZ":1024467186,"StartTime":68478.0,"Objects":[{"StartTime":68478.0,"EndTime":68478.0,"Column":2}]},{"RandomW":2360573614,"RandomX":514760684,"RandomY":1024467186,"RandomZ":211600206,"StartTime":68743.0,"Objects":[{"StartTime":68743.0,"EndTime":68743.0,"Column":4},{"StartTime":68743.0,"EndTime":68743.0,"Column":5}]},{"RandomW":3867722027,"RandomX":1024467186,"RandomY":211600206,"RandomZ":2360573614,"StartTime":69009.0,"Objects":[{"StartTime":69009.0,"EndTime":69009.0,"Column":3}]},{"RandomW":1512274616,"RandomX":211600206,"RandomY":2360573614,"RandomZ":3867722027,"StartTime":69274.0,"Objects":[{"StartTime":69274.0,"EndTime":69274.0,"Column":4},{"StartTime":69274.0,"EndTime":69274.0,"Column":5}]},{"RandomW":2957984769,"RandomX":2360573614,"RandomY":3867722027,"RandomZ":1512274616,"StartTime":69540.0,"Objects":[{"StartTime":69540.0,"EndTime":69540.0,"Column":3}]},{"RandomW":2803767976,"RandomX":3867722027,"RandomY":1512274616,"RandomZ":2957984769,"StartTime":69805.0,"Objects":[{"StartTime":69805.0,"EndTime":69805.0,"Column":4},{"StartTime":69805.0,"EndTime":69805.0,"Column":5}]},{"RandomW":1183341084,"RandomX":2957984769,"RandomY":2803767976,"RandomZ":121575161,"StartTime":70336.0,"Objects":[{"StartTime":70336.0,"EndTime":70601.0,"Column":3}]},{"RandomW":3685872119,"RandomX":121575161,"RandomY":1183341084,"RandomZ":2351788416,"StartTime":70867.0,"Objects":[{"StartTime":70867.0,"EndTime":71397.0,"Column":4}]},{"RandomW":617004198,"RandomX":1183341084,"RandomY":2351788416,"RandomZ":3685872119,"StartTime":71663.0,"Objects":[{"StartTime":71663.0,"EndTime":71663.0,"Column":3}]},{"RandomW":2478235967,"RandomX":617004198,"RandomY":546986648,"RandomZ":3353120378,"StartTime":71929.0,"Objects":[{"StartTime":71929.0,"EndTime":72459.0,"Column":0}]},{"RandomW":2189712483,"RandomX":546986648,"RandomY":3353120378,"RandomZ":2478235967,"StartTime":72725.0,"Objects":[{"StartTime":72725.0,"EndTime":72725.0,"Column":2}]},{"RandomW":1882757169,"RandomX":3353120378,"RandomY":2478235967,"RandomZ":2189712483,"StartTime":72991.0,"Objects":[{"StartTime":72991.0,"EndTime":72991.0,"Column":3},{"StartTime":72991.0,"EndTime":72991.0,"Column":4}]},{"RandomW":1404331794,"RandomX":2478235967,"RandomY":2189712483,"RandomZ":1882757169,"StartTime":73256.0,"Objects":[{"StartTime":73256.0,"EndTime":73256.0,"Column":1}]},{"RandomW":1999620930,"RandomX":2189712483,"RandomY":1882757169,"RandomZ":1404331794,"StartTime":73522.0,"Objects":[{"StartTime":73522.0,"EndTime":73522.0,"Column":3},{"StartTime":73522.0,"EndTime":73522.0,"Column":4}]},{"RandomW":3622364800,"RandomX":1882757169,"RandomY":1404331794,"RandomZ":1999620930,"StartTime":73787.0,"Objects":[{"StartTime":73787.0,"EndTime":73787.0,"Column":2}]},{"RandomW":1671763292,"RandomX":1404331794,"RandomY":1999620930,"RandomZ":3622364800,"StartTime":74053.0,"Objects":[{"StartTime":74053.0,"EndTime":74053.0,"Column":3},{"StartTime":74053.0,"EndTime":74053.0,"Column":4}]},{"RandomW":2594561583,"RandomX":3622364800,"RandomY":1671763292,"RandomZ":2480497357,"StartTime":74584.0,"Objects":[{"StartTime":74584.0,"EndTime":74849.0,"Column":1}]},{"RandomW":1101860073,"RandomX":2480497357,"RandomY":2594561583,"RandomZ":183105309,"StartTime":75115.0,"Objects":[{"StartTime":75115.0,"EndTime":75645.0,"Column":3}]},{"RandomW":423280923,"RandomX":2594561583,"RandomY":183105309,"RandomZ":1101860073,"StartTime":75911.0,"Objects":[{"StartTime":75911.0,"EndTime":75911.0,"Column":2}]},{"RandomW":3905841932,"RandomX":1101860073,"RandomY":423280923,"RandomZ":2916757685,"StartTime":76177.0,"Objects":[{"StartTime":76177.0,"EndTime":76707.0,"Column":4}]},{"RandomW":3241015480,"RandomX":423280923,"RandomY":2916757685,"RandomZ":3905841932,"StartTime":76973.0,"Objects":[{"StartTime":76973.0,"EndTime":76973.0,"Column":3}]},{"RandomW":1928531304,"RandomX":3905841932,"RandomY":3241015480,"RandomZ":248564639,"StartTime":77239.0,"Objects":[{"StartTime":77239.0,"EndTime":77504.0,"Column":5}]},{"RandomW":634267655,"RandomX":3925777969,"RandomY":1203262350,"RandomZ":3485263061,"StartTime":77770.0,"Objects":[{"StartTime":77770.0,"EndTime":78035.0,"Column":3},{"StartTime":77770.0,"EndTime":78035.0,"Column":1}]},{"RandomW":953955737,"RandomX":1203262350,"RandomY":3485263061,"RandomZ":634267655,"StartTime":78301.0,"Objects":[{"StartTime":78301.0,"EndTime":78301.0,"Column":3}]},{"RandomW":3179099439,"RandomX":3485263061,"RandomY":634267655,"RandomZ":953955737,"StartTime":78566.0,"Objects":[{"StartTime":78566.0,"EndTime":78566.0,"Column":2},{"StartTime":78566.0,"EndTime":78566.0,"Column":3}]},{"RandomW":2513433625,"RandomX":634267655,"RandomY":953955737,"RandomZ":3179099439,"StartTime":78832.0,"Objects":[{"StartTime":78832.0,"EndTime":78832.0,"Column":3},{"StartTime":78832.0,"EndTime":78832.0,"Column":4}]},{"RandomW":3239409847,"RandomX":953955737,"RandomY":3179099439,"RandomZ":2513433625,"StartTime":79097.0,"Objects":[{"StartTime":79097.0,"EndTime":79097.0,"Column":5},{"StartTime":79097.0,"EndTime":79097.0,"Column":0}]},{"RandomW":1279031172,"RandomX":2513433625,"RandomY":3239409847,"RandomZ":415034865,"StartTime":79363.0,"Objects":[{"StartTime":79363.0,"EndTime":79893.0,"Column":3}]},{"RandomW":2797153574,"RandomX":3239409847,"RandomY":415034865,"RandomZ":1279031172,"StartTime":80159.0,"Objects":[{"StartTime":80159.0,"EndTime":80159.0,"Column":3}]},{"RandomW":858752658,"RandomX":1279031172,"RandomY":2797153574,"RandomZ":3422759302,"StartTime":80424.0,"Objects":[{"StartTime":80424.0,"EndTime":80954.0,"Column":2}]},{"RandomW":2617268004,"RandomX":2797153574,"RandomY":3422759302,"RandomZ":858752658,"StartTime":81221.0,"Objects":[{"StartTime":81221.0,"EndTime":81221.0,"Column":4}]},{"RandomW":4089416095,"RandomX":3422759302,"RandomY":858752658,"RandomZ":2617268004,"StartTime":81486.0,"Objects":[{"StartTime":81486.0,"EndTime":81486.0,"Column":4},{"StartTime":81486.0,"EndTime":81486.0,"Column":5}]},{"RandomW":640008567,"RandomX":858752658,"RandomY":2617268004,"RandomZ":4089416095,"StartTime":81752.0,"Objects":[{"StartTime":81752.0,"EndTime":81752.0,"Column":4}]},{"RandomW":1769064503,"RandomX":2617268004,"RandomY":4089416095,"RandomZ":640008567,"StartTime":82017.0,"Objects":[{"StartTime":82017.0,"EndTime":82017.0,"Column":5},{"StartTime":82017.0,"EndTime":82017.0,"Column":0}]},{"RandomW":4171929422,"RandomX":640008567,"RandomY":1769064503,"RandomZ":4149611338,"StartTime":82283.0,"Objects":[{"StartTime":82283.0,"EndTime":82283.0,"Column":3},{"StartTime":82283.0,"EndTime":82283.0,"Column":5}]},{"RandomW":4035764053,"RandomX":1769064503,"RandomY":4149611338,"RandomZ":4171929422,"StartTime":82548.0,"Objects":[{"StartTime":82548.0,"EndTime":82548.0,"Column":5},{"StartTime":82548.0,"EndTime":82548.0,"Column":0}]},{"RandomW":391872771,"RandomX":4149611338,"RandomY":4171929422,"RandomZ":4035764053,"StartTime":83079.0,"Objects":[{"StartTime":83079.0,"EndTime":83079.0,"Column":3},{"StartTime":83079.0,"EndTime":83079.0,"Column":4}]},{"RandomW":391872771,"RandomX":4149611338,"RandomY":4171929422,"RandomZ":4035764053,"StartTime":83345.0,"Objects":[{"StartTime":83345.0,"EndTime":83345.0,"Column":2},{"StartTime":83345.0,"EndTime":83345.0,"Column":1}]},{"RandomW":4239141202,"RandomX":4035764053,"RandomY":391872771,"RandomZ":1343280377,"StartTime":83610.0,"Objects":[{"StartTime":83610.0,"EndTime":84140.0,"Column":5}]},{"RandomW":2008371177,"RandomX":4239141202,"RandomY":1783379941,"RandomZ":2715086902,"StartTime":84407.0,"Objects":[{"StartTime":84407.0,"EndTime":84407.0,"Column":1},{"StartTime":84407.0,"EndTime":84407.0,"Column":5}]},{"RandomW":980563717,"RandomX":3939376884,"RandomY":3778473815,"RandomZ":3882214919,"StartTime":84672.0,"Objects":[{"StartTime":84672.0,"EndTime":85202.0,"Column":4},{"StartTime":84672.0,"EndTime":85202.0,"Column":2}]},{"RandomW":2698098433,"RandomX":3778473815,"RandomY":3882214919,"RandomZ":980563717,"StartTime":85469.0,"Objects":[{"StartTime":85469.0,"EndTime":85469.0,"Column":1}]},{"RandomW":4140546075,"RandomX":3882214919,"RandomY":980563717,"RandomZ":2698098433,"StartTime":85734.0,"Objects":[{"StartTime":85734.0,"EndTime":85734.0,"Column":3},{"StartTime":85734.0,"EndTime":85734.0,"Column":4}]},{"RandomW":1045835035,"RandomX":980563717,"RandomY":2698098433,"RandomZ":4140546075,"StartTime":86000.0,"Objects":[{"StartTime":86000.0,"EndTime":86000.0,"Column":1}]},{"RandomW":2503475147,"RandomX":2698098433,"RandomY":4140546075,"RandomZ":1045835035,"StartTime":86265.0,"Objects":[{"StartTime":86265.0,"EndTime":86265.0,"Column":1},{"StartTime":86265.0,"EndTime":86265.0,"Column":2}]},{"RandomW":3094559699,"RandomX":4140546075,"RandomY":1045835035,"RandomZ":2503475147,"StartTime":86531.0,"Objects":[{"StartTime":86531.0,"EndTime":86531.0,"Column":3}]},{"RandomW":332613542,"RandomX":1045835035,"RandomY":2503475147,"RandomZ":3094559699,"StartTime":86796.0,"Objects":[{"StartTime":86796.0,"EndTime":86796.0,"Column":2},{"StartTime":86796.0,"EndTime":86796.0,"Column":3}]},{"RandomW":2534271858,"RandomX":332613542,"RandomY":2623704626,"RandomZ":3061969874,"StartTime":87327.0,"Objects":[{"StartTime":87327.0,"EndTime":87592.0,"Column":1}]},{"RandomW":794230988,"RandomX":2534271858,"RandomY":510287938,"RandomZ":2532404899,"StartTime":87858.0,"Objects":[{"StartTime":87858.0,"EndTime":88388.0,"Column":2}]},{"RandomW":3623430191,"RandomX":510287938,"RandomY":2532404899,"RandomZ":794230988,"StartTime":88655.0,"Objects":[{"StartTime":88655.0,"EndTime":88655.0,"Column":2}]},{"RandomW":2269498220,"RandomX":794230988,"RandomY":3623430191,"RandomZ":2598120162,"StartTime":88920.0,"Objects":[{"StartTime":88920.0,"EndTime":89450.0,"Column":0}]},{"RandomW":277080616,"RandomX":3623430191,"RandomY":2598120162,"RandomZ":2269498220,"StartTime":89717.0,"Objects":[{"StartTime":89717.0,"EndTime":89717.0,"Column":2}]},{"RandomW":237305927,"RandomX":2598120162,"RandomY":2269498220,"RandomZ":277080616,"StartTime":89982.0,"Objects":[{"StartTime":89982.0,"EndTime":89982.0,"Column":1},{"StartTime":89982.0,"EndTime":89982.0,"Column":2}]},{"RandomW":3697412902,"RandomX":277080616,"RandomY":237305927,"RandomZ":1976938587,"StartTime":90247.0,"Objects":[{"StartTime":90247.0,"EndTime":90247.0,"Column":1},{"StartTime":90247.0,"EndTime":90247.0,"Column":4}]},{"RandomW":3552536616,"RandomX":237305927,"RandomY":1976938587,"RandomZ":3697412902,"StartTime":90513.0,"Objects":[{"StartTime":90513.0,"EndTime":90513.0,"Column":2},{"StartTime":90513.0,"EndTime":90513.0,"Column":3}]},{"RandomW":758205604,"RandomX":3697412902,"RandomY":3552536616,"RandomZ":4122897696,"StartTime":90778.0,"Objects":[{"StartTime":90778.0,"EndTime":90778.0,"Column":1},{"StartTime":90778.0,"EndTime":90778.0,"Column":2}]},{"RandomW":3787868447,"RandomX":3552536616,"RandomY":4122897696,"RandomZ":758205604,"StartTime":91044.0,"Objects":[{"StartTime":91044.0,"EndTime":91044.0,"Column":2},{"StartTime":91044.0,"EndTime":91044.0,"Column":3}]},{"RandomW":1748107640,"RandomX":3787868447,"RandomY":3373302567,"RandomZ":3485540424,"StartTime":91575.0,"Objects":[{"StartTime":91575.0,"EndTime":91840.0,"Column":4}]},{"RandomW":4130051617,"RandomX":3485540424,"RandomY":1748107640,"RandomZ":3144627152,"StartTime":92106.0,"Objects":[{"StartTime":92106.0,"EndTime":92636.0,"Column":5}]},{"RandomW":808332236,"RandomX":1748107640,"RandomY":3144627152,"RandomZ":4130051617,"StartTime":92902.0,"Objects":[{"StartTime":92902.0,"EndTime":92902.0,"Column":3}]},{"RandomW":182226446,"RandomX":4130051617,"RandomY":808332236,"RandomZ":3371160944,"StartTime":93168.0,"Objects":[{"StartTime":93168.0,"EndTime":93698.0,"Column":0}]},{"RandomW":2699856874,"RandomX":808332236,"RandomY":3371160944,"RandomZ":182226446,"StartTime":93964.0,"Objects":[{"StartTime":93964.0,"EndTime":93964.0,"Column":1}]},{"RandomW":3110990203,"RandomX":2699856874,"RandomY":3789399152,"RandomZ":1462741358,"StartTime":94230.0,"Objects":[{"StartTime":94230.0,"EndTime":94495.0,"Column":4},{"StartTime":94230.0,"EndTime":94495.0,"Column":2}]},{"RandomW":2375429180,"RandomX":2098892391,"RandomY":1911053200,"RandomZ":1537665050,"StartTime":94761.0,"Objects":[{"StartTime":94761.0,"EndTime":95026.0,"Column":5},{"StartTime":94761.0,"EndTime":95026.0,"Column":0}]},{"RandomW":391186846,"RandomX":1537665050,"RandomY":2375429180,"RandomZ":609673823,"StartTime":95292.0,"Objects":[{"StartTime":95292.0,"EndTime":96353.0,"Column":1}]},{"RandomW":2078004566,"RandomX":2375429180,"RandomY":609673823,"RandomZ":391186846,"StartTime":96486.0,"Objects":[{"StartTime":96486.0,"EndTime":98478.0,"Column":5}]},{"RandomW":2078004566,"RandomX":2375429180,"RandomY":609673823,"RandomZ":391186846,"StartTime":113345.0,"Objects":[{"StartTime":113345.0,"EndTime":113345.0,"Column":4}]},{"RandomW":2078004566,"RandomX":2375429180,"RandomY":609673823,"RandomZ":391186846,"StartTime":113876.0,"Objects":[{"StartTime":113876.0,"EndTime":113876.0,"Column":1}]},{"RandomW":2078004566,"RandomX":2375429180,"RandomY":609673823,"RandomZ":391186846,"StartTime":114407.0,"Objects":[{"StartTime":114407.0,"EndTime":114407.0,"Column":4}]},{"RandomW":1192288733,"RandomX":609673823,"RandomY":391186846,"RandomZ":2078004566,"StartTime":114672.0,"Objects":[{"StartTime":114672.0,"EndTime":114672.0,"Column":2},{"StartTime":114672.0,"EndTime":114672.0,"Column":3}]},{"RandomW":3569858426,"RandomX":391186846,"RandomY":2078004566,"RandomZ":1192288733,"StartTime":114938.0,"Objects":[{"StartTime":114938.0,"EndTime":114938.0,"Column":2}]},{"RandomW":1262832005,"RandomX":2078004566,"RandomY":1192288733,"RandomZ":3569858426,"StartTime":115203.0,"Objects":[{"StartTime":115203.0,"EndTime":115203.0,"Column":3},{"StartTime":115203.0,"EndTime":115203.0,"Column":4}]},{"RandomW":4002501854,"RandomX":1192288733,"RandomY":3569858426,"RandomZ":1262832005,"StartTime":115469.0,"Objects":[{"StartTime":115469.0,"EndTime":115469.0,"Column":3},{"StartTime":115469.0,"EndTime":115469.0,"Column":4}]},{"RandomW":776953560,"RandomX":3569858426,"RandomY":1262832005,"RandomZ":4002501854,"StartTime":116000.0,"Objects":[{"StartTime":116000.0,"EndTime":116000.0,"Column":3},{"StartTime":116000.0,"EndTime":116000.0,"Column":4}]},{"RandomW":776953560,"RandomX":3569858426,"RandomY":1262832005,"RandomZ":4002501854,"StartTime":116531.0,"Objects":[{"StartTime":116531.0,"EndTime":116531.0,"Column":2},{"StartTime":116531.0,"EndTime":116531.0,"Column":1}]},{"RandomW":3352969228,"RandomX":1262832005,"RandomY":4002501854,"RandomZ":776953560,"StartTime":117062.0,"Objects":[{"StartTime":117062.0,"EndTime":117062.0,"Column":3},{"StartTime":117062.0,"EndTime":117062.0,"Column":4}]},{"RandomW":2796695571,"RandomX":4002501854,"RandomY":776953560,"RandomZ":3352969228,"StartTime":117327.0,"Objects":[{"StartTime":117327.0,"EndTime":117327.0,"Column":2}]},{"RandomW":3269572543,"RandomX":776953560,"RandomY":3352969228,"RandomZ":2796695571,"StartTime":117593.0,"Objects":[{"StartTime":117593.0,"EndTime":117593.0,"Column":4},{"StartTime":117593.0,"EndTime":117593.0,"Column":5}]},{"RandomW":3269572543,"RandomX":776953560,"RandomY":3352969228,"RandomZ":2796695571,"StartTime":118124.0,"Objects":[{"StartTime":118124.0,"EndTime":118124.0,"Column":1},{"StartTime":118124.0,"EndTime":118124.0,"Column":0}]},{"RandomW":3269572543,"RandomX":776953560,"RandomY":3352969228,"RandomZ":2796695571,"StartTime":118655.0,"Objects":[{"StartTime":118655.0,"EndTime":118655.0,"Column":5},{"StartTime":118655.0,"EndTime":118655.0,"Column":4}]},{"RandomW":2517403813,"RandomX":3352969228,"RandomY":2796695571,"RandomZ":3269572543,"StartTime":118920.0,"Objects":[{"StartTime":118920.0,"EndTime":118920.0,"Column":2},{"StartTime":118920.0,"EndTime":118920.0,"Column":3}]},{"RandomW":2210619464,"RandomX":2796695571,"RandomY":3269572543,"RandomZ":2517403813,"StartTime":119186.0,"Objects":[{"StartTime":119186.0,"EndTime":119186.0,"Column":4}]},{"RandomW":3032935051,"RandomX":3269572543,"RandomY":2517403813,"RandomZ":2210619464,"StartTime":119451.0,"Objects":[{"StartTime":119451.0,"EndTime":119451.0,"Column":5},{"StartTime":119451.0,"EndTime":119451.0,"Column":0}]},{"RandomW":2069229539,"RandomX":2517403813,"RandomY":2210619464,"RandomZ":3032935051,"StartTime":119717.0,"Objects":[{"StartTime":119717.0,"EndTime":119717.0,"Column":4},{"StartTime":119717.0,"EndTime":119717.0,"Column":5}]},{"RandomW":2069229539,"RandomX":2517403813,"RandomY":2210619464,"RandomZ":3032935051,"StartTime":120247.0,"Objects":[{"StartTime":120247.0,"EndTime":120247.0,"Column":1},{"StartTime":120247.0,"EndTime":120247.0,"Column":0}]},{"RandomW":2069229539,"RandomX":2517403813,"RandomY":2210619464,"RandomZ":3032935051,"StartTime":120778.0,"Objects":[{"StartTime":120778.0,"EndTime":120778.0,"Column":5},{"StartTime":120778.0,"EndTime":120778.0,"Column":4}]},{"RandomW":2069229539,"RandomX":2517403813,"RandomY":2210619464,"RandomZ":3032935051,"StartTime":121309.0,"Objects":[{"StartTime":121309.0,"EndTime":121309.0,"Column":1},{"StartTime":121309.0,"EndTime":121309.0,"Column":0}]},{"RandomW":2314078604,"RandomX":2210619464,"RandomY":3032935051,"RandomZ":2069229539,"StartTime":121575.0,"Objects":[{"StartTime":121575.0,"EndTime":121575.0,"Column":3}]},{"RandomW":297269721,"RandomX":3032935051,"RandomY":2069229539,"RandomZ":2314078604,"StartTime":121840.0,"Objects":[{"StartTime":121840.0,"EndTime":121840.0,"Column":2},{"StartTime":121840.0,"EndTime":121840.0,"Column":3}]},{"RandomW":297269721,"RandomX":3032935051,"RandomY":2069229539,"RandomZ":2314078604,"StartTime":122371.0,"Objects":[{"StartTime":122371.0,"EndTime":122371.0,"Column":3},{"StartTime":122371.0,"EndTime":122371.0,"Column":2}]},{"RandomW":297269721,"RandomX":3032935051,"RandomY":2069229539,"RandomZ":2314078604,"StartTime":122902.0,"Objects":[{"StartTime":122902.0,"EndTime":122902.0,"Column":3},{"StartTime":122902.0,"EndTime":122902.0,"Column":2}]},{"RandomW":297269721,"RandomX":3032935051,"RandomY":2069229539,"RandomZ":2314078604,"StartTime":123433.0,"Objects":[{"StartTime":123433.0,"EndTime":123433.0,"Column":3},{"StartTime":123433.0,"EndTime":123433.0,"Column":2}]},{"RandomW":2460408790,"RandomX":2069229539,"RandomY":2314078604,"RandomZ":297269721,"StartTime":123699.0,"Objects":[{"StartTime":123699.0,"EndTime":123699.0,"Column":1}]},{"RandomW":1180177558,"RandomX":2314078604,"RandomY":297269721,"RandomZ":2460408790,"StartTime":123964.0,"Objects":[{"StartTime":123964.0,"EndTime":123964.0,"Column":3},{"StartTime":123964.0,"EndTime":123964.0,"Column":4}]},{"RandomW":1180177558,"RandomX":2314078604,"RandomY":297269721,"RandomZ":2460408790,"StartTime":124495.0,"Objects":[{"StartTime":124495.0,"EndTime":124495.0,"Column":2},{"StartTime":124495.0,"EndTime":124495.0,"Column":1}]},{"RandomW":1180177558,"RandomX":2314078604,"RandomY":297269721,"RandomZ":2460408790,"StartTime":125026.0,"Objects":[{"StartTime":125026.0,"EndTime":125026.0,"Column":4},{"StartTime":125026.0,"EndTime":125026.0,"Column":3}]},{"RandomW":1180177558,"RandomX":2314078604,"RandomY":297269721,"RandomZ":2460408790,"StartTime":125557.0,"Objects":[{"StartTime":125557.0,"EndTime":125557.0,"Column":2},{"StartTime":125557.0,"EndTime":125557.0,"Column":1}]},{"RandomW":3204700088,"RandomX":297269721,"RandomY":2460408790,"RandomZ":1180177558,"StartTime":125823.0,"Objects":[{"StartTime":125823.0,"EndTime":125823.0,"Column":2}]},{"RandomW":299141296,"RandomX":2460408790,"RandomY":1180177558,"RandomZ":3204700088,"StartTime":126088.0,"Objects":[{"StartTime":126088.0,"EndTime":126088.0,"Column":3},{"StartTime":126088.0,"EndTime":126088.0,"Column":4}]},{"RandomW":299141296,"RandomX":2460408790,"RandomY":1180177558,"RandomZ":3204700088,"StartTime":126619.0,"Objects":[{"StartTime":126619.0,"EndTime":126619.0,"Column":2},{"StartTime":126619.0,"EndTime":126619.0,"Column":1}]},{"RandomW":299141296,"RandomX":2460408790,"RandomY":1180177558,"RandomZ":3204700088,"StartTime":127150.0,"Objects":[{"StartTime":127150.0,"EndTime":127150.0,"Column":4},{"StartTime":127150.0,"EndTime":127150.0,"Column":3}]},{"RandomW":3037239607,"RandomX":1180177558,"RandomY":3204700088,"RandomZ":299141296,"StartTime":127416.0,"Objects":[{"StartTime":127416.0,"EndTime":127416.0,"Column":4},{"StartTime":127416.0,"EndTime":127416.0,"Column":5}]},{"RandomW":863164324,"RandomX":3204700088,"RandomY":299141296,"RandomZ":3037239607,"StartTime":127681.0,"Objects":[{"StartTime":127681.0,"EndTime":127681.0,"Column":5}]},{"RandomW":2456647781,"RandomX":299141296,"RandomY":3037239607,"RandomZ":863164324,"StartTime":127947.0,"Objects":[{"StartTime":127947.0,"EndTime":127947.0,"Column":4},{"StartTime":127947.0,"EndTime":127947.0,"Column":5}]},{"RandomW":659157904,"RandomX":3037239607,"RandomY":863164324,"RandomZ":2456647781,"StartTime":128212.0,"Objects":[{"StartTime":128212.0,"EndTime":128212.0,"Column":3},{"StartTime":128212.0,"EndTime":128212.0,"Column":4}]},{"RandomW":659157904,"RandomX":3037239607,"RandomY":863164324,"RandomZ":2456647781,"StartTime":128743.0,"Objects":[{"StartTime":128743.0,"EndTime":128743.0,"Column":2},{"StartTime":128743.0,"EndTime":128743.0,"Column":1}]},{"RandomW":659157904,"RandomX":3037239607,"RandomY":863164324,"RandomZ":2456647781,"StartTime":129274.0,"Objects":[{"StartTime":129274.0,"EndTime":129274.0,"Column":4},{"StartTime":129274.0,"EndTime":129274.0,"Column":3}]},{"RandomW":3598260079,"RandomX":863164324,"RandomY":2456647781,"RandomZ":659157904,"StartTime":129540.0,"Objects":[{"StartTime":129540.0,"EndTime":129540.0,"Column":3},{"StartTime":129540.0,"EndTime":129540.0,"Column":4}]},{"RandomW":1930638835,"RandomX":2456647781,"RandomY":659157904,"RandomZ":3598260079,"StartTime":129805.0,"Objects":[{"StartTime":129805.0,"EndTime":129805.0,"Column":1},{"StartTime":129805.0,"EndTime":129805.0,"Column":2}]},{"RandomW":4230333264,"RandomX":1930638835,"RandomY":2319762852,"RandomZ":3807998479,"StartTime":130071.0,"Objects":[{"StartTime":130071.0,"EndTime":130071.0,"Column":2},{"StartTime":130071.0,"EndTime":130071.0,"Column":3}]},{"RandomW":2482386774,"RandomX":4230333264,"RandomY":376688010,"RandomZ":3132506885,"StartTime":132460.0,"Objects":[{"StartTime":132460.0,"EndTime":132990.0,"Column":0}]},{"RandomW":3381449487,"RandomX":3132506885,"RandomY":2482386774,"RandomZ":1092311355,"StartTime":133522.0,"Objects":[{"StartTime":133522.0,"EndTime":134052.0,"Column":3}]},{"RandomW":3812940964,"RandomX":1092311355,"RandomY":3381449487,"RandomZ":3240759120,"StartTime":134318.0,"Objects":[{"StartTime":134318.0,"EndTime":134848.0,"Column":4}]},{"RandomW":2199106412,"RandomX":2014155638,"RandomY":3619038163,"RandomZ":1182263034,"StartTime":135115.0,"Objects":[{"StartTime":135115.0,"EndTime":135380.0,"Column":3},{"StartTime":135115.0,"EndTime":135380.0,"Column":0}]},{"RandomW":4049541057,"RandomX":1182263034,"RandomY":2199106412,"RandomZ":2542868059,"StartTime":135646.0,"Objects":[{"StartTime":135646.0,"EndTime":136176.0,"Column":5}]},{"RandomW":376448389,"RandomX":2542868059,"RandomY":4049541057,"RandomZ":149323558,"StartTime":136708.0,"Objects":[{"StartTime":136708.0,"EndTime":136973.0,"Column":1}]},{"RandomW":10761513,"RandomX":149323558,"RandomY":376448389,"RandomZ":156027614,"StartTime":137239.0,"Objects":[{"StartTime":137239.0,"EndTime":137504.0,"Column":0}]},{"RandomW":2890609580,"RandomX":156027614,"RandomY":10761513,"RandomZ":998270292,"StartTime":137770.0,"Objects":[{"StartTime":137770.0,"EndTime":138566.0,"Column":2}]},{"RandomW":3792858866,"RandomX":998270292,"RandomY":2890609580,"RandomZ":3275622081,"StartTime":138832.0,"Objects":[{"StartTime":138832.0,"EndTime":139097.0,"Column":4}]},{"RandomW":479756469,"RandomX":3792858866,"RandomY":3665829153,"RandomZ":799245198,"StartTime":139363.0,"Objects":[{"StartTime":139363.0,"EndTime":139628.0,"Column":2},{"StartTime":139363.0,"EndTime":139628.0,"Column":1}]},{"RandomW":1559664190,"RandomX":1837897770,"RandomY":3074386351,"RandomZ":2226336565,"StartTime":139894.0,"Objects":[{"StartTime":139894.0,"EndTime":140690.0,"Column":0},{"StartTime":139894.0,"EndTime":140690.0,"Column":4}]},{"RandomW":1370921154,"RandomX":3074386351,"RandomY":2226336565,"RandomZ":1559664190,"StartTime":140955.0,"Objects":[{"StartTime":140955.0,"EndTime":140955.0,"Column":4}]},{"RandomW":12534613,"RandomX":1559664190,"RandomY":1370921154,"RandomZ":495513930,"StartTime":141221.0,"Objects":[{"StartTime":141221.0,"EndTime":141751.0,"Column":3},{"StartTime":141486.0,"EndTime":141486.0,"Column":1},{"StartTime":141751.0,"EndTime":141751.0,"Column":1}]},{"RandomW":1474110729,"RandomX":12534613,"RandomY":3893387802,"RandomZ":226854738,"StartTime":142017.0,"Objects":[{"StartTime":142017.0,"EndTime":142017.0,"Column":2},{"StartTime":142017.0,"EndTime":142017.0,"Column":3}]},{"RandomW":3883366092,"RandomX":1474110729,"RandomY":2911002956,"RandomZ":3337209428,"StartTime":142283.0,"Objects":[{"StartTime":142283.0,"EndTime":142548.0,"Column":4}]},{"RandomW":1868157439,"RandomX":3883366092,"RandomY":1497166406,"RandomZ":3876220972,"StartTime":142814.0,"Objects":[{"StartTime":142814.0,"EndTime":143079.0,"Column":5}]},{"RandomW":868486094,"RandomX":1497166406,"RandomY":3876220972,"RandomZ":1868157439,"StartTime":143345.0,"Objects":[{"StartTime":143345.0,"EndTime":143345.0,"Column":2}]},{"RandomW":2379505970,"RandomX":3876220972,"RandomY":1868157439,"RandomZ":868486094,"StartTime":143610.0,"Objects":[{"StartTime":143610.0,"EndTime":143610.0,"Column":2}]},{"RandomW":971762612,"RandomX":1868157439,"RandomY":868486094,"RandomZ":2379505970,"StartTime":143876.0,"Objects":[{"StartTime":143876.0,"EndTime":143876.0,"Column":4}]},{"RandomW":2333467129,"RandomX":2379505970,"RandomY":971762612,"RandomZ":2560365407,"StartTime":144141.0,"Objects":[{"StartTime":144141.0,"EndTime":144671.0,"Column":0}]},{"RandomW":3275109659,"RandomX":2560365407,"RandomY":2333467129,"RandomZ":2783370328,"StartTime":145203.0,"Objects":[{"StartTime":145203.0,"EndTime":145468.0,"Column":3}]},{"RandomW":2675369072,"RandomX":2783370328,"RandomY":3275109659,"RandomZ":3142107337,"StartTime":145734.0,"Objects":[{"StartTime":145734.0,"EndTime":145999.0,"Column":1}]},{"RandomW":2114821552,"RandomX":3142107337,"RandomY":2675369072,"RandomZ":216133594,"StartTime":146265.0,"Objects":[{"StartTime":146265.0,"EndTime":146795.0,"Column":5}]},{"RandomW":2210288688,"RandomX":2675369072,"RandomY":216133594,"RandomZ":2114821552,"StartTime":147062.0,"Objects":[{"StartTime":147062.0,"EndTime":147062.0,"Column":3}]},{"RandomW":2824847566,"RandomX":2114821552,"RandomY":2210288688,"RandomZ":2881713491,"StartTime":147327.0,"Objects":[{"StartTime":147327.0,"EndTime":147592.0,"Column":1}]},{"RandomW":3418617049,"RandomX":2881713491,"RandomY":2824847566,"RandomZ":3131910248,"StartTime":147858.0,"Objects":[{"StartTime":147858.0,"EndTime":148123.0,"Column":3}]},{"RandomW":4264037536,"RandomX":3418617049,"RandomY":2065328415,"RandomZ":756387586,"StartTime":148389.0,"Objects":[{"StartTime":148389.0,"EndTime":149450.0,"Column":2},{"StartTime":148389.0,"EndTime":149450.0,"Column":5}]},{"RandomW":714689152,"RandomX":2065328415,"RandomY":756387586,"RandomZ":4264037536,"StartTime":149717.0,"Objects":[{"StartTime":149717.0,"EndTime":149717.0,"Column":2}]},{"RandomW":2187562077,"RandomX":756387586,"RandomY":4264037536,"RandomZ":714689152,"StartTime":149982.0,"Objects":[{"StartTime":149982.0,"EndTime":149982.0,"Column":1},{"StartTime":149982.0,"EndTime":149982.0,"Column":2}]},{"RandomW":59731596,"RandomX":4264037536,"RandomY":714689152,"RandomZ":2187562077,"StartTime":150247.0,"Objects":[{"StartTime":150247.0,"EndTime":150247.0,"Column":0}]},{"RandomW":3179032401,"RandomX":714689152,"RandomY":2187562077,"RandomZ":59731596,"StartTime":150513.0,"Objects":[{"StartTime":150513.0,"EndTime":150513.0,"Column":1}]},{"RandomW":1565638452,"RandomX":2187562077,"RandomY":59731596,"RandomZ":3179032401,"StartTime":150778.0,"Objects":[{"StartTime":150778.0,"EndTime":150778.0,"Column":2}]},{"RandomW":3285111207,"RandomX":59731596,"RandomY":3179032401,"RandomZ":1565638452,"StartTime":151044.0,"Objects":[{"StartTime":151044.0,"EndTime":151044.0,"Column":3},{"StartTime":151044.0,"EndTime":151044.0,"Column":4}]},{"RandomW":3142401116,"RandomX":3179032401,"RandomY":1565638452,"RandomZ":3285111207,"StartTime":151309.0,"Objects":[{"StartTime":151309.0,"EndTime":151309.0,"Column":4}]},{"RandomW":2191101353,"RandomX":3142401116,"RandomY":3877079747,"RandomZ":930029834,"StartTime":151575.0,"Objects":[{"StartTime":151575.0,"EndTime":152105.0,"Column":2},{"StartTime":151575.0,"EndTime":152105.0,"Column":0}]},{"RandomW":1171726387,"RandomX":2191101353,"RandomY":1357180538,"RandomZ":201209655,"StartTime":152637.0,"Objects":[{"StartTime":152637.0,"EndTime":152902.0,"Column":3}]},{"RandomW":2089660876,"RandomX":201209655,"RandomY":1171726387,"RandomZ":191699429,"StartTime":153168.0,"Objects":[{"StartTime":153168.0,"EndTime":153698.0,"Column":5}]},{"RandomW":2251323109,"RandomX":1171726387,"RandomY":191699429,"RandomZ":2089660876,"StartTime":153964.0,"Objects":[{"StartTime":153964.0,"EndTime":153964.0,"Column":2}]},{"RandomW":147408153,"RandomX":2251323109,"RandomY":2048526504,"RandomZ":433820735,"StartTime":154230.0,"Objects":[{"StartTime":154230.0,"EndTime":154230.0,"Column":0},{"StartTime":154230.0,"EndTime":154230.0,"Column":5}]},{"RandomW":223059387,"RandomX":2048526504,"RandomY":433820735,"RandomZ":147408153,"StartTime":154495.0,"Objects":[{"StartTime":154495.0,"EndTime":154495.0,"Column":3}]},{"RandomW":1644267862,"RandomX":147408153,"RandomY":223059387,"RandomZ":2814282738,"StartTime":154761.0,"Objects":[{"StartTime":154761.0,"EndTime":155026.0,"Column":4}]},{"RandomW":585628331,"RandomX":1644267862,"RandomY":547547522,"RandomZ":1901399656,"StartTime":155292.0,"Objects":[{"StartTime":155292.0,"EndTime":155292.0,"Column":0},{"StartTime":155292.0,"EndTime":155292.0,"Column":5}]},{"RandomW":1287818392,"RandomX":547547522,"RandomY":1901399656,"RandomZ":585628331,"StartTime":155557.0,"Objects":[{"StartTime":155557.0,"EndTime":155557.0,"Column":1}]},{"RandomW":3879046214,"RandomX":2065404539,"RandomY":2732913982,"RandomZ":3217781099,"StartTime":155823.0,"Objects":[{"StartTime":155823.0,"EndTime":156088.0,"Column":2},{"StartTime":155823.0,"EndTime":156088.0,"Column":4}]},{"RandomW":3318878889,"RandomX":3217781099,"RandomY":3879046214,"RandomZ":1075466897,"StartTime":156354.0,"Objects":[{"StartTime":156354.0,"EndTime":156619.0,"Column":3}]},{"RandomW":1785367685,"RandomX":1075466897,"RandomY":3318878889,"RandomZ":561406801,"StartTime":156885.0,"Objects":[{"StartTime":156885.0,"EndTime":157415.0,"Column":4}]},{"RandomW":2909067134,"RandomX":561406801,"RandomY":1785367685,"RandomZ":4168537475,"StartTime":157947.0,"Objects":[{"StartTime":157947.0,"EndTime":157947.0,"Column":5},{"StartTime":157947.0,"EndTime":157947.0,"Column":2}]},{"RandomW":1067074920,"RandomX":1785367685,"RandomY":4168537475,"RandomZ":2909067134,"StartTime":158212.0,"Objects":[{"StartTime":158212.0,"EndTime":158212.0,"Column":4}]},{"RandomW":27977914,"RandomX":4168537475,"RandomY":2909067134,"RandomZ":1067074920,"StartTime":158478.0,"Objects":[{"StartTime":158478.0,"EndTime":158478.0,"Column":5},{"StartTime":158478.0,"EndTime":158478.0,"Column":0}]},{"RandomW":1329528769,"RandomX":2909067134,"RandomY":1067074920,"RandomZ":27977914,"StartTime":158743.0,"Objects":[{"StartTime":158743.0,"EndTime":158743.0,"Column":4}]},{"RandomW":3295284863,"RandomX":1067074920,"RandomY":27977914,"RandomZ":1329528769,"StartTime":159009.0,"Objects":[{"StartTime":159009.0,"EndTime":159009.0,"Column":5}]},{"RandomW":691446431,"RandomX":27977914,"RandomY":1329528769,"RandomZ":3295284863,"StartTime":159540.0,"Objects":[{"StartTime":159540.0,"EndTime":159540.0,"Column":3},{"StartTime":159540.0,"EndTime":159540.0,"Column":4}]},{"RandomW":3354872060,"RandomX":3295284863,"RandomY":691446431,"RandomZ":2140106811,"StartTime":159805.0,"Objects":[{"StartTime":159805.0,"EndTime":159805.0,"Column":2},{"StartTime":159805.0,"EndTime":159805.0,"Column":3}]},{"RandomW":1400553355,"RandomX":691446431,"RandomY":2140106811,"RandomZ":3354872060,"StartTime":160071.0,"Objects":[{"StartTime":160071.0,"EndTime":160071.0,"Column":2}]},{"RandomW":1400553355,"RandomX":691446431,"RandomY":2140106811,"RandomZ":3354872060,"StartTime":160601.0,"Objects":[{"StartTime":160601.0,"EndTime":160601.0,"Column":3}]},{"RandomW":3485781281,"RandomX":2140106811,"RandomY":3354872060,"RandomZ":1400553355,"StartTime":160867.0,"Objects":[{"StartTime":160867.0,"EndTime":160867.0,"Column":3}]},{"RandomW":3053679463,"RandomX":1400553355,"RandomY":3485781281,"RandomZ":3419304522,"StartTime":161132.0,"Objects":[{"StartTime":161132.0,"EndTime":161397.0,"Column":2}]},{"RandomW":3645336111,"RandomX":3419304522,"RandomY":3053679463,"RandomZ":805504203,"StartTime":161663.0,"Objects":[{"StartTime":161663.0,"EndTime":162193.0,"Column":4}]},{"RandomW":1638076271,"RandomX":3053679463,"RandomY":805504203,"RandomZ":3645336111,"StartTime":162460.0,"Objects":[{"StartTime":162460.0,"EndTime":162460.0,"Column":3}]},{"RandomW":107981020,"RandomX":1638076271,"RandomY":3432435831,"RandomZ":3835408498,"StartTime":162725.0,"Objects":[{"StartTime":162725.0,"EndTime":162725.0,"Column":0},{"StartTime":162725.0,"EndTime":162725.0,"Column":5}]},{"RandomW":94467567,"RandomX":3835408498,"RandomY":107981020,"RandomZ":2144208649,"StartTime":163256.0,"Objects":[{"StartTime":163256.0,"EndTime":163256.0,"Column":4},{"StartTime":163256.0,"EndTime":163256.0,"Column":0}]},{"RandomW":1015041289,"RandomX":107981020,"RandomY":2144208649,"RandomZ":94467567,"StartTime":163522.0,"Objects":[{"StartTime":163522.0,"EndTime":163522.0,"Column":3}]},{"RandomW":2029876639,"RandomX":1204955917,"RandomY":1210817201,"RandomZ":1177260118,"StartTime":163787.0,"Objects":[{"StartTime":163787.0,"EndTime":164052.0,"Column":5}]},{"RandomW":3125496505,"RandomX":1177260118,"RandomY":2029876639,"RandomZ":2929832910,"StartTime":164318.0,"Objects":[{"StartTime":164318.0,"EndTime":164583.0,"Column":2}]},{"RandomW":2426857185,"RandomX":3125496505,"RandomY":2700661894,"RandomZ":859446411,"StartTime":164849.0,"Objects":[{"StartTime":164849.0,"EndTime":165114.0,"Column":0}]},{"RandomW":4116661924,"RandomX":2426857185,"RandomY":1884842190,"RandomZ":375578279,"StartTime":165380.0,"Objects":[{"StartTime":165380.0,"EndTime":165910.0,"Column":1},{"StartTime":165380.0,"EndTime":165910.0,"Column":5}]},{"RandomW":3787729819,"RandomX":375578279,"RandomY":4116661924,"RandomZ":1382622976,"StartTime":166442.0,"Objects":[{"StartTime":166442.0,"EndTime":166972.0,"Column":4}]},{"RandomW":3780331234,"RandomX":4116661924,"RandomY":1382622976,"RandomZ":3787729819,"StartTime":167239.0,"Objects":[{"StartTime":167239.0,"EndTime":167239.0,"Column":3}]},{"RandomW":891570220,"RandomX":3780331234,"RandomY":3996538378,"RandomZ":4118560235,"StartTime":167504.0,"Objects":[{"StartTime":167504.0,"EndTime":168034.0,"Column":5},{"StartTime":167504.0,"EndTime":168034.0,"Column":2}]},{"RandomW":1312521276,"RandomX":3996538378,"RandomY":4118560235,"RandomZ":891570220,"StartTime":168301.0,"Objects":[{"StartTime":168301.0,"EndTime":168301.0,"Column":0}]},{"RandomW":316798455,"RandomX":4118560235,"RandomY":891570220,"RandomZ":1312521276,"StartTime":168566.0,"Objects":[{"StartTime":168566.0,"EndTime":168566.0,"Column":2},{"StartTime":168566.0,"EndTime":168566.0,"Column":3}]},{"RandomW":107348261,"RandomX":891570220,"RandomY":1312521276,"RandomZ":316798455,"StartTime":168832.0,"Objects":[{"StartTime":168832.0,"EndTime":168832.0,"Column":1}]},{"RandomW":286543085,"RandomX":1312521276,"RandomY":316798455,"RandomZ":107348261,"StartTime":169097.0,"Objects":[{"StartTime":169097.0,"EndTime":169097.0,"Column":1},{"StartTime":169097.0,"EndTime":169097.0,"Column":2}]},{"RandomW":2220558447,"RandomX":316798455,"RandomY":107348261,"RandomZ":286543085,"StartTime":169363.0,"Objects":[{"StartTime":169363.0,"EndTime":169363.0,"Column":2}]},{"RandomW":2567445342,"RandomX":107348261,"RandomY":286543085,"RandomZ":2220558447,"StartTime":169628.0,"Objects":[{"StartTime":169628.0,"EndTime":169628.0,"Column":1},{"StartTime":169628.0,"EndTime":169628.0,"Column":2}]},{"RandomW":2941341299,"RandomX":286543085,"RandomY":2220558447,"RandomZ":2567445342,"StartTime":170159.0,"Objects":[{"StartTime":170159.0,"EndTime":170159.0,"Column":3},{"StartTime":170159.0,"EndTime":170159.0,"Column":4}]},{"RandomW":2941341299,"RandomX":286543085,"RandomY":2220558447,"RandomZ":2567445342,"StartTime":170424.0,"Objects":[{"StartTime":170424.0,"EndTime":170424.0,"Column":2},{"StartTime":170424.0,"EndTime":170424.0,"Column":1}]},{"RandomW":1087727581,"RandomX":2567445342,"RandomY":2941341299,"RandomZ":479267920,"StartTime":170690.0,"Objects":[{"StartTime":170690.0,"EndTime":171220.0,"Column":3}]},{"RandomW":2581485170,"RandomX":2941341299,"RandomY":479267920,"RandomZ":1087727581,"StartTime":171486.0,"Objects":[{"StartTime":171486.0,"EndTime":171486.0,"Column":5}]},{"RandomW":683596203,"RandomX":1087727581,"RandomY":2581485170,"RandomZ":3168383468,"StartTime":171752.0,"Objects":[{"StartTime":171752.0,"EndTime":172282.0,"Column":1}]},{"RandomW":3284056302,"RandomX":2581485170,"RandomY":3168383468,"RandomZ":683596203,"StartTime":172548.0,"Objects":[{"StartTime":172548.0,"EndTime":172548.0,"Column":2}]},{"RandomW":2830633773,"RandomX":3168383468,"RandomY":683596203,"RandomZ":3284056302,"StartTime":172814.0,"Objects":[{"StartTime":172814.0,"EndTime":172814.0,"Column":3},{"StartTime":172814.0,"EndTime":172814.0,"Column":4}]},{"RandomW":3651115271,"RandomX":683596203,"RandomY":3284056302,"RandomZ":2830633773,"StartTime":173079.0,"Objects":[{"StartTime":173079.0,"EndTime":173079.0,"Column":3}]},{"RandomW":120746014,"RandomX":3284056302,"RandomY":2830633773,"RandomZ":3651115271,"StartTime":173345.0,"Objects":[{"StartTime":173345.0,"EndTime":173345.0,"Column":3},{"StartTime":173345.0,"EndTime":173345.0,"Column":4}]},{"RandomW":830325214,"RandomX":2830633773,"RandomY":3651115271,"RandomZ":120746014,"StartTime":173610.0,"Objects":[{"StartTime":173610.0,"EndTime":173610.0,"Column":4}]},{"RandomW":1509180863,"RandomX":3651115271,"RandomY":120746014,"RandomZ":830325214,"StartTime":173876.0,"Objects":[{"StartTime":173876.0,"EndTime":173876.0,"Column":3},{"StartTime":173876.0,"EndTime":173876.0,"Column":4}]},{"RandomW":2233493011,"RandomX":3902833961,"RandomY":923589330,"RandomZ":3425613873,"StartTime":174407.0,"Objects":[{"StartTime":174407.0,"EndTime":174672.0,"Column":2},{"StartTime":174407.0,"EndTime":174672.0,"Column":0}]},{"RandomW":2517643905,"RandomX":1207989122,"RandomY":993303558,"RandomZ":3011821377,"StartTime":174938.0,"Objects":[{"StartTime":174938.0,"EndTime":175468.0,"Column":3},{"StartTime":174938.0,"EndTime":175468.0,"Column":1}]},{"RandomW":3720863650,"RandomX":993303558,"RandomY":3011821377,"RandomZ":2517643905,"StartTime":175734.0,"Objects":[{"StartTime":175734.0,"EndTime":175734.0,"Column":2}]},{"RandomW":3563355415,"RandomX":2517643905,"RandomY":3720863650,"RandomZ":1116519600,"StartTime":176000.0,"Objects":[{"StartTime":176000.0,"EndTime":176530.0,"Column":3}]},{"RandomW":3287800096,"RandomX":3720863650,"RandomY":1116519600,"RandomZ":3563355415,"StartTime":176796.0,"Objects":[{"StartTime":176796.0,"EndTime":176796.0,"Column":3}]},{"RandomW":539898931,"RandomX":1116519600,"RandomY":3563355415,"RandomZ":3287800096,"StartTime":177062.0,"Objects":[{"StartTime":177062.0,"EndTime":177062.0,"Column":2},{"StartTime":177062.0,"EndTime":177062.0,"Column":3}]},{"RandomW":123758010,"RandomX":3563355415,"RandomY":3287800096,"RandomZ":539898931,"StartTime":177327.0,"Objects":[{"StartTime":177327.0,"EndTime":177327.0,"Column":4}]},{"RandomW":4028312708,"RandomX":3287800096,"RandomY":539898931,"RandomZ":123758010,"StartTime":177593.0,"Objects":[{"StartTime":177593.0,"EndTime":177593.0,"Column":2},{"StartTime":177593.0,"EndTime":177593.0,"Column":3}]},{"RandomW":2371409278,"RandomX":539898931,"RandomY":123758010,"RandomZ":4028312708,"StartTime":177858.0,"Objects":[{"StartTime":177858.0,"EndTime":177858.0,"Column":3}]},{"RandomW":3699828554,"RandomX":123758010,"RandomY":4028312708,"RandomZ":2371409278,"StartTime":178124.0,"Objects":[{"StartTime":178124.0,"EndTime":178124.0,"Column":2},{"StartTime":178124.0,"EndTime":178124.0,"Column":3}]},{"RandomW":4053363780,"RandomX":2371409278,"RandomY":3699828554,"RandomZ":3637445845,"StartTime":178655.0,"Objects":[{"StartTime":178655.0,"EndTime":178920.0,"Column":5}]},{"RandomW":1366734997,"RandomX":3637445845,"RandomY":4053363780,"RandomZ":3122766892,"StartTime":179186.0,"Objects":[{"StartTime":179186.0,"EndTime":179716.0,"Column":3}]},{"RandomW":2085192570,"RandomX":1366734997,"RandomY":4047501250,"RandomZ":3422445293,"StartTime":179982.0,"Objects":[{"StartTime":179982.0,"EndTime":179982.0,"Column":3},{"StartTime":179982.0,"EndTime":179982.0,"Column":5}]},{"RandomW":2526042960,"RandomX":3422445293,"RandomY":2085192570,"RandomZ":2552180342,"StartTime":180247.0,"Objects":[{"StartTime":180247.0,"EndTime":180777.0,"Column":1}]},{"RandomW":2946528857,"RandomX":2085192570,"RandomY":2552180342,"RandomZ":2526042960,"StartTime":181044.0,"Objects":[{"StartTime":181044.0,"EndTime":181044.0,"Column":2}]},{"RandomW":4275012500,"RandomX":2526042960,"RandomY":2946528857,"RandomZ":2680316548,"StartTime":181309.0,"Objects":[{"StartTime":181309.0,"EndTime":181574.0,"Column":5}]},{"RandomW":716767862,"RandomX":1177533555,"RandomY":3396673648,"RandomZ":1210370441,"StartTime":181840.0,"Objects":[{"StartTime":181840.0,"EndTime":182105.0,"Column":3},{"StartTime":181840.0,"EndTime":182105.0,"Column":2}]},{"RandomW":1918581647,"RandomX":1210370441,"RandomY":716767862,"RandomZ":290385782,"StartTime":182371.0,"Objects":[{"StartTime":182371.0,"EndTime":182636.0,"Column":5}]},{"RandomW":2554770024,"RandomX":1918581647,"RandomY":475913420,"RandomZ":4262840195,"StartTime":182902.0,"Objects":[{"StartTime":182902.0,"EndTime":183432.0,"Column":1}]},{"RandomW":862610860,"RandomX":475913420,"RandomY":4262840195,"RandomZ":2554770024,"StartTime":183699.0,"Objects":[{"StartTime":183699.0,"EndTime":185557.0,"Column":2}]},{"RandomW":3240322225,"RandomX":4262840195,"RandomY":2554770024,"RandomZ":862610860,"StartTime":202017.0,"Objects":[{"StartTime":202017.0,"EndTime":202017.0,"Column":0}]},{"RandomW":2438630089,"RandomX":2554770024,"RandomY":862610860,"RandomZ":3240322225,"StartTime":202283.0,"Objects":[{"StartTime":202283.0,"EndTime":202283.0,"Column":1}]},{"RandomW":1543895637,"RandomX":3240322225,"RandomY":2438630089,"RandomZ":1008910200,"StartTime":202548.0,"Objects":[{"StartTime":202548.0,"EndTime":203078.0,"Column":4}]},{"RandomW":2262375304,"RandomX":2438630089,"RandomY":1008910200,"RandomZ":1543895637,"StartTime":203345.0,"Objects":[{"StartTime":203345.0,"EndTime":203345.0,"Column":2}]},{"RandomW":3932191533,"RandomX":1543895637,"RandomY":2262375304,"RandomZ":3281044824,"StartTime":203610.0,"Objects":[{"StartTime":203610.0,"EndTime":203875.0,"Column":4}]},{"RandomW":2456816417,"RandomX":3932191533,"RandomY":2579817318,"RandomZ":3616517773,"StartTime":204141.0,"Objects":[{"StartTime":204141.0,"EndTime":204406.0,"Column":0}]},{"RandomW":1863357795,"RandomX":2456816417,"RandomY":2065740625,"RandomZ":3309416576,"StartTime":204672.0,"Objects":[{"StartTime":204672.0,"EndTime":205202.0,"Column":3},{"StartTime":204672.0,"EndTime":205202.0,"Column":5}]},{"RandomW":66010220,"RandomX":3309416576,"RandomY":1863357795,"RandomZ":2100015779,"StartTime":205469.0,"Objects":[{"StartTime":205469.0,"EndTime":205469.0,"Column":4},{"StartTime":205469.0,"EndTime":205469.0,"Column":0}]},{"RandomW":548562611,"RandomX":2100015779,"RandomY":66010220,"RandomZ":3420604705,"StartTime":205734.0,"Objects":[{"StartTime":205734.0,"EndTime":205999.0,"Column":1}]},{"RandomW":2052728473,"RandomX":3420604705,"RandomY":548562611,"RandomZ":2913964,"StartTime":206265.0,"Objects":[{"StartTime":206265.0,"EndTime":206530.0,"Column":5}]},{"RandomW":1944462115,"RandomX":2052728473,"RandomY":2737357746,"RandomZ":270315162,"StartTime":206796.0,"Objects":[{"StartTime":206796.0,"EndTime":206796.0,"Column":2},{"StartTime":206796.0,"EndTime":206796.0,"Column":3}]},{"RandomW":3626216744,"RandomX":2737357746,"RandomY":270315162,"RandomZ":1944462115,"StartTime":207062.0,"Objects":[{"StartTime":207062.0,"EndTime":207062.0,"Column":5}]},{"RandomW":1039388877,"RandomX":270315162,"RandomY":1944462115,"RandomZ":3626216744,"StartTime":207327.0,"Objects":[{"StartTime":207327.0,"EndTime":207327.0,"Column":4}]},{"RandomW":3362701719,"RandomX":1944462115,"RandomY":3626216744,"RandomZ":1039388877,"StartTime":207593.0,"Objects":[{"StartTime":207593.0,"EndTime":207593.0,"Column":3}]},{"RandomW":3968495235,"RandomX":3362701719,"RandomY":2329091202,"RandomZ":1331472925,"StartTime":207858.0,"Objects":[{"StartTime":207858.0,"EndTime":208388.0,"Column":5}]},{"RandomW":1381394684,"RandomX":2329091202,"RandomY":1331472925,"RandomZ":3968495235,"StartTime":208655.0,"Objects":[{"StartTime":208655.0,"EndTime":208655.0,"Column":5}]},{"RandomW":1435798214,"RandomX":1381394684,"RandomY":1081301304,"RandomZ":3939835753,"StartTime":208920.0,"Objects":[{"StartTime":208920.0,"EndTime":209450.0,"Column":4}]},{"RandomW":3026458880,"RandomX":1081301304,"RandomY":3939835753,"RandomZ":1435798214,"StartTime":209717.0,"Objects":[{"StartTime":209717.0,"EndTime":209717.0,"Column":5}]},{"RandomW":3713738018,"RandomX":3026458880,"RandomY":1845767213,"RandomZ":745035987,"StartTime":209982.0,"Objects":[{"StartTime":209982.0,"EndTime":210512.0,"Column":2},{"StartTime":209982.0,"EndTime":210512.0,"Column":4}]},{"RandomW":1231260560,"RandomX":1845767213,"RandomY":745035987,"RandomZ":3713738018,"StartTime":210778.0,"Objects":[{"StartTime":210778.0,"EndTime":210778.0,"Column":4}]},{"RandomW":105489365,"RandomX":745035987,"RandomY":3713738018,"RandomZ":1231260560,"StartTime":211044.0,"Objects":[{"StartTime":211044.0,"EndTime":211044.0,"Column":4}]},{"RandomW":1753861391,"RandomX":3713738018,"RandomY":1231260560,"RandomZ":105489365,"StartTime":211309.0,"Objects":[{"StartTime":211309.0,"EndTime":211309.0,"Column":2}]},{"RandomW":966114829,"RandomX":105489365,"RandomY":1753861391,"RandomZ":1828685577,"StartTime":211575.0,"Objects":[{"StartTime":211575.0,"EndTime":211575.0,"Column":3},{"StartTime":211575.0,"EndTime":211575.0,"Column":2}]},{"RandomW":1431749195,"RandomX":1836275468,"RandomY":1290011463,"RandomZ":1159621643,"StartTime":211840.0,"Objects":[{"StartTime":211840.0,"EndTime":212370.0,"Column":5},{"StartTime":211840.0,"EndTime":212370.0,"Column":4}]},{"RandomW":3472418283,"RandomX":1159621643,"RandomY":1431749195,"RandomZ":2724869338,"StartTime":212637.0,"Objects":[{"StartTime":212637.0,"EndTime":212902.0,"Column":3}]},{"RandomW":1755864208,"RandomX":3472418283,"RandomY":2016458251,"RandomZ":2610391004,"StartTime":213168.0,"Objects":[{"StartTime":213168.0,"EndTime":213698.0,"Column":1},{"StartTime":213168.0,"EndTime":213698.0,"Column":4}]},{"RandomW":1635138515,"RandomX":2016458251,"RandomY":2610391004,"RandomZ":1755864208,"StartTime":213964.0,"Objects":[{"StartTime":213964.0,"EndTime":213964.0,"Column":3}]},{"RandomW":3162662082,"RandomX":1755864208,"RandomY":1635138515,"RandomZ":2617989400,"StartTime":214230.0,"Objects":[{"StartTime":214230.0,"EndTime":214495.0,"Column":2}]},{"RandomW":1184692914,"RandomX":2617989400,"RandomY":3162662082,"RandomZ":2531582750,"StartTime":214761.0,"Objects":[{"StartTime":214761.0,"EndTime":215026.0,"Column":3}]},{"RandomW":798124101,"RandomX":2531582750,"RandomY":1184692914,"RandomZ":2157553888,"StartTime":215292.0,"Objects":[{"StartTime":215292.0,"EndTime":215557.0,"Column":2}]},{"RandomW":1923400471,"RandomX":798124101,"RandomY":2665448122,"RandomZ":1060614841,"StartTime":215823.0,"Objects":[{"StartTime":215823.0,"EndTime":216088.0,"Column":5}]},{"RandomW":775950648,"RandomX":1923400471,"RandomY":3469237574,"RandomZ":2892029047,"StartTime":216354.0,"Objects":[{"StartTime":216354.0,"EndTime":216354.0,"Column":1},{"StartTime":216354.0,"EndTime":216354.0,"Column":4}]},{"RandomW":1321234603,"RandomX":4127626210,"RandomY":1546611249,"RandomZ":1925740893,"StartTime":216885.0,"Objects":[{"StartTime":216885.0,"EndTime":217150.0,"Column":5},{"StartTime":216885.0,"EndTime":217150.0,"Column":3}]},{"RandomW":2881678930,"RandomX":1925740893,"RandomY":1321234603,"RandomZ":2358993682,"StartTime":217416.0,"Objects":[{"StartTime":217416.0,"EndTime":217946.0,"Column":2}]},{"RandomW":2599512294,"RandomX":1321234603,"RandomY":2358993682,"RandomZ":2881678930,"StartTime":218212.0,"Objects":[{"StartTime":218212.0,"EndTime":218212.0,"Column":1}]},{"RandomW":2150464549,"RandomX":2881678930,"RandomY":2599512294,"RandomZ":3623425595,"StartTime":218478.0,"Objects":[{"StartTime":218478.0,"EndTime":219008.0,"Column":0}]},{"RandomW":763775798,"RandomX":3623425595,"RandomY":2150464549,"RandomZ":1008837132,"StartTime":219274.0,"Objects":[{"StartTime":219274.0,"EndTime":221132.0,"Column":2}]},{"RandomW":3656799832,"RandomX":1008837132,"RandomY":763775798,"RandomZ":852609139,"StartTime":221663.0,"Objects":[{"StartTime":221663.0,"EndTime":222193.0,"Column":4}]},{"RandomW":4147545979,"RandomX":852609139,"RandomY":3656799832,"RandomZ":3908484776,"StartTime":222460.0,"Objects":[{"StartTime":222460.0,"EndTime":222460.0,"Column":2},{"StartTime":222460.0,"EndTime":222460.0,"Column":5}]},{"RandomW":540508179,"RandomX":3908484776,"RandomY":4147545979,"RandomZ":1259887550,"StartTime":222725.0,"Objects":[{"StartTime":222725.0,"EndTime":223255.0,"Column":1}]},{"RandomW":1042752714,"RandomX":1259887550,"RandomY":540508179,"RandomZ":2104064323,"StartTime":223522.0,"Objects":[{"StartTime":223522.0,"EndTime":223522.0,"Column":5},{"StartTime":223522.0,"EndTime":223522.0,"Column":2}]},{"RandomW":3077262619,"RandomX":540508179,"RandomY":2104064323,"RandomZ":1042752714,"StartTime":223787.0,"Objects":[{"StartTime":223787.0,"EndTime":223787.0,"Column":3},{"StartTime":223787.0,"EndTime":223787.0,"Column":4}]},{"RandomW":734033149,"RandomX":2104064323,"RandomY":1042752714,"RandomZ":3077262619,"StartTime":224053.0,"Objects":[{"StartTime":224053.0,"EndTime":224053.0,"Column":4}]},{"RandomW":492155815,"RandomX":1042752714,"RandomY":3077262619,"RandomZ":734033149,"StartTime":224318.0,"Objects":[{"StartTime":224318.0,"EndTime":224318.0,"Column":4},{"StartTime":224318.0,"EndTime":224318.0,"Column":5}]},{"RandomW":441697715,"RandomX":3077262619,"RandomY":734033149,"RandomZ":492155815,"StartTime":224584.0,"Objects":[{"StartTime":224584.0,"EndTime":224584.0,"Column":3}]},{"RandomW":4156379255,"RandomX":734033149,"RandomY":492155815,"RandomZ":441697715,"StartTime":224849.0,"Objects":[{"StartTime":224849.0,"EndTime":224849.0,"Column":4},{"StartTime":224849.0,"EndTime":224849.0,"Column":5}]},{"RandomW":3757225441,"RandomX":492155815,"RandomY":441697715,"RandomZ":4156379255,"StartTime":225380.0,"Objects":[{"StartTime":225380.0,"EndTime":225380.0,"Column":2},{"StartTime":225380.0,"EndTime":225380.0,"Column":3}]},{"RandomW":3757225441,"RandomX":492155815,"RandomY":441697715,"RandomZ":4156379255,"StartTime":225646.0,"Objects":[{"StartTime":225646.0,"EndTime":225646.0,"Column":3},{"StartTime":225646.0,"EndTime":225646.0,"Column":2}]},{"RandomW":2225043333,"RandomX":3950035756,"RandomY":4132636893,"RandomZ":3158636107,"StartTime":225911.0,"Objects":[{"StartTime":225911.0,"EndTime":226441.0,"Column":5},{"StartTime":225911.0,"EndTime":226441.0,"Column":0}]},{"RandomW":479006094,"RandomX":2225043333,"RandomY":3919293849,"RandomZ":2279622039,"StartTime":226708.0,"Objects":[{"StartTime":226708.0,"EndTime":226708.0,"Column":0},{"StartTime":226708.0,"EndTime":226708.0,"Column":1}]},{"RandomW":3529234379,"RandomX":479006094,"RandomY":1674670789,"RandomZ":1460857923,"StartTime":226973.0,"Objects":[{"StartTime":226973.0,"EndTime":227503.0,"Column":4},{"StartTime":226973.0,"EndTime":227503.0,"Column":3}]},{"RandomW":2798539123,"RandomX":1674670789,"RandomY":1460857923,"RandomZ":3529234379,"StartTime":227770.0,"Objects":[{"StartTime":227770.0,"EndTime":227770.0,"Column":3}]},{"RandomW":1315002421,"RandomX":1460857923,"RandomY":3529234379,"RandomZ":2798539123,"StartTime":228035.0,"Objects":[{"StartTime":228035.0,"EndTime":228035.0,"Column":2},{"StartTime":228035.0,"EndTime":228035.0,"Column":3}]},{"RandomW":2396116302,"RandomX":3529234379,"RandomY":2798539123,"RandomZ":1315002421,"StartTime":228301.0,"Objects":[{"StartTime":228301.0,"EndTime":228301.0,"Column":1}]},{"RandomW":2184752848,"RandomX":2798539123,"RandomY":1315002421,"RandomZ":2396116302,"StartTime":228566.0,"Objects":[{"StartTime":228566.0,"EndTime":228566.0,"Column":2},{"StartTime":228566.0,"EndTime":228566.0,"Column":3}]},{"RandomW":1453929005,"RandomX":1315002421,"RandomY":2396116302,"RandomZ":2184752848,"StartTime":228832.0,"Objects":[{"StartTime":228832.0,"EndTime":228832.0,"Column":1}]},{"RandomW":307062845,"RandomX":2396116302,"RandomY":2184752848,"RandomZ":1453929005,"StartTime":229097.0,"Objects":[{"StartTime":229097.0,"EndTime":229097.0,"Column":2},{"StartTime":229097.0,"EndTime":229097.0,"Column":3}]},{"RandomW":2488853431,"RandomX":1430246951,"RandomY":1243135735,"RandomZ":862796553,"StartTime":229628.0,"Objects":[{"StartTime":229628.0,"EndTime":229893.0,"Column":0}]},{"RandomW":2954723307,"RandomX":862796553,"RandomY":2488853431,"RandomZ":1065193973,"StartTime":230159.0,"Objects":[{"StartTime":230159.0,"EndTime":230689.0,"Column":2}]},{"RandomW":3118771232,"RandomX":1065193973,"RandomY":2954723307,"RandomZ":3941773202,"StartTime":230955.0,"Objects":[{"StartTime":230955.0,"EndTime":230955.0,"Column":3},{"StartTime":230955.0,"EndTime":230955.0,"Column":2}]},{"RandomW":1630107201,"RandomX":3532926875,"RandomY":2476115689,"RandomZ":1207743047,"StartTime":231221.0,"Objects":[{"StartTime":231221.0,"EndTime":231751.0,"Column":0},{"StartTime":231221.0,"EndTime":231751.0,"Column":4}]},{"RandomW":313681160,"RandomX":2476115689,"RandomY":1207743047,"RandomZ":1630107201,"StartTime":232017.0,"Objects":[{"StartTime":232017.0,"EndTime":232017.0,"Column":2}]},{"RandomW":892602489,"RandomX":1207743047,"RandomY":1630107201,"RandomZ":313681160,"StartTime":232283.0,"Objects":[{"StartTime":232283.0,"EndTime":232283.0,"Column":3},{"StartTime":232283.0,"EndTime":232283.0,"Column":4}]},{"RandomW":2549672466,"RandomX":1630107201,"RandomY":313681160,"RandomZ":892602489,"StartTime":232548.0,"Objects":[{"StartTime":232548.0,"EndTime":232548.0,"Column":1}]},{"RandomW":3175685586,"RandomX":313681160,"RandomY":892602489,"RandomZ":2549672466,"StartTime":232814.0,"Objects":[{"StartTime":232814.0,"EndTime":232814.0,"Column":3},{"StartTime":232814.0,"EndTime":232814.0,"Column":4}]},{"RandomW":1012053334,"RandomX":892602489,"RandomY":2549672466,"RandomZ":3175685586,"StartTime":233079.0,"Objects":[{"StartTime":233079.0,"EndTime":233079.0,"Column":2}]},{"RandomW":2846885221,"RandomX":2549672466,"RandomY":3175685586,"RandomZ":1012053334,"StartTime":233345.0,"Objects":[{"StartTime":233345.0,"EndTime":233345.0,"Column":3},{"StartTime":233345.0,"EndTime":233345.0,"Column":4}]},{"RandomW":2773158813,"RandomX":2846885221,"RandomY":4182295099,"RandomZ":203093837,"StartTime":233876.0,"Objects":[{"StartTime":233876.0,"EndTime":234141.0,"Column":0},{"StartTime":233876.0,"EndTime":234141.0,"Column":1}]},{"RandomW":857734082,"RandomX":203093837,"RandomY":2773158813,"RandomZ":2365172092,"StartTime":234407.0,"Objects":[{"StartTime":234407.0,"EndTime":234937.0,"Column":2}]},{"RandomW":3898917491,"RandomX":2773158813,"RandomY":2365172092,"RandomZ":857734082,"StartTime":235203.0,"Objects":[{"StartTime":235203.0,"EndTime":235203.0,"Column":2}]},{"RandomW":1417532037,"RandomX":857734082,"RandomY":3898917491,"RandomZ":361638657,"StartTime":235469.0,"Objects":[{"StartTime":235469.0,"EndTime":235999.0,"Column":3}]},{"RandomW":2557538851,"RandomX":3898917491,"RandomY":361638657,"RandomZ":1417532037,"StartTime":236265.0,"Objects":[{"StartTime":236265.0,"EndTime":236265.0,"Column":3}]},{"RandomW":846935039,"RandomX":1417532037,"RandomY":2557538851,"RandomZ":1456065540,"StartTime":236531.0,"Objects":[{"StartTime":236531.0,"EndTime":236796.0,"Column":2}]},{"RandomW":2547399683,"RandomX":1456065540,"RandomY":846935039,"RandomZ":2284332751,"StartTime":237062.0,"Objects":[{"StartTime":237062.0,"EndTime":237327.0,"Column":1}]},{"RandomW":2405919505,"RandomX":846935039,"RandomY":2284332751,"RandomZ":2547399683,"StartTime":237593.0,"Objects":[{"StartTime":237593.0,"EndTime":237593.0,"Column":3},{"StartTime":237593.0,"EndTime":237593.0,"Column":4}]},{"RandomW":1684559305,"RandomX":2284332751,"RandomY":2547399683,"RandomZ":2405919505,"StartTime":237858.0,"Objects":[{"StartTime":237858.0,"EndTime":237858.0,"Column":5},{"StartTime":237858.0,"EndTime":237858.0,"Column":0}]},{"RandomW":2914982357,"RandomX":2547399683,"RandomY":2405919505,"RandomZ":1684559305,"StartTime":238124.0,"Objects":[{"StartTime":238124.0,"EndTime":238124.0,"Column":2},{"StartTime":238124.0,"EndTime":238124.0,"Column":3}]},{"RandomW":2343509573,"RandomX":2405919505,"RandomY":1684559305,"RandomZ":2914982357,"StartTime":238389.0,"Objects":[{"StartTime":238389.0,"EndTime":238389.0,"Column":5}]},{"RandomW":1059378114,"RandomX":1684559305,"RandomY":2914982357,"RandomZ":2343509573,"StartTime":238655.0,"Objects":[{"StartTime":238655.0,"EndTime":240778.0,"Column":2}]}]} \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/100374.osu b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/100374.osu new file mode 100644 index 0000000000..50f943b9e6 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/100374.osu @@ -0,0 +1,449 @@ +osu file format v9 + +[General] +StackLeniency: 0.4 +Mode: 0 + +[Difficulty] +HPDrainRate:5 +CircleSize:4 +OverallDifficulty:5 +ApproachRate:6 +SliderMultiplier:1.7 +SliderTickRate:2 + +[Events] +//Background and Video events +//Break Periods +2,98678,112295 +2,185757,200967 +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Sound Samples +//Background Colour Transformations +3,100,163,162,255 + +[TimingPoints] +695,530.973451327434,4,2,1,20,1,0 +33457,-100,4,2,1,25,0,0 +33988,-100,4,2,1,30,0,0 +34386,-100,4,1,0,30,0,0 +38649,-100,4,1,1,30,0,0 +42897,-100,4,1,0,30,0,0 +47144,-100,4,1,1,30,0,0 +51530,-100,4,2,1,20,0,0 +56978,571.428571428571,4,2,1,20,1,0 +58692,845.070422535211,4,2,1,20,1,0 +60248,530.973451327434,4,2,1,20,1,0 +60740,-100,4,1,1,30,0,0 +61555,-66.6666666666667,4,1,1,30,0,0 +62219,-100,4,1,0,40,0,0 +78148,-100,4,1,0,30,0,0 +78413,-100,4,1,0,35,0,0 +78679,-100,4,1,0,40,0,0 +78944,-100,4,1,0,45,0,0 +79210,-100,4,1,0,40,0,0 +96466,-100,4,2,1,30,0,0 +132285,-100,4,2,1,20,0,0 +149453,-100,4,1,1,35,0,0 +153790,-100,4,2,1,40,0,0 +157639,-100,4,1,1,35,0,0 +162020,-100,4,2,1,40,0,0 +166158,-100,4,1,0,40,0,0 +201733,-100,4,2,1,20,0,0 +219099,-133.333333333333,4,2,1,20,0,0 +221024,-100,4,1,1,30,0,0 +221290,-100,4,1,0,30,0,0 + +[HitObjects] +256,192,15562,12,0,17155 +72,120,17686,5,8 +128,224,17951,1,0 +185,119,18217,1,0 +246,220,18482,1,0 +128,224,18748,2,0,B|161:262|208:264,1,85,4|0 +309,213,19279,2,0,B|297:169|325:120,2,85,0|0|8 +309,213,20075,5,0 +309,332,20341,1,0 +206,272,20606,1,8 +309,213,20871,2,0,B|336:117|261:56,1,170,4|0 +205,272,21933,6,0,B|183:307|125:328,1,85,8|0 +149,256,22464,2,0,B|114:281|45:280,1,85,0|0 +101,216,22995,2,0,B|16:264|-56:176|16:72|104:128,1,255,4|0 +149,136,24057,6,0,B|170:100|229:80,1,85,8|0 +205,149,24588,2,0,B|239:123|309:125,1,85,0|8 +253,189,25119,2,0,B|349:144|413:221,1,170,4|8 +240,336,26181,5,8 +288,264,26447,1,0 +344,328,26712,2,0,B|391:339|440:328,1,85,0|0 +488,270,27243,2,0,B|424:256|392:200,1,85,4|0 +329,230,27774,2,0,B|328:176|386:142,1,85,0|0 +363,69,28305,2,0,B|328:40|280:56,2,85,8|0|0 +312,136,29102,1,0 +224,120,29367,2,0,B|192:168|256:240|224:296,1,170,4|8 +96,240,30429,6,0,B|83:195|56:160,1,85,8|0 +96,88,30960,2,0,B|83:132|56:168,1,85,0|0 +59,164,31491,2,0,B|129:182|187:167|254:149|323:168,1,255,4|0 +312,165,32553,6,0,B|302:210|256:237,1,85,8|0 +312,166,33084,2,0,B|321:120|368:94,1,85,8|0 +312,166,33615,2,0,B|318:204|374:193|426:183|450:247,1,170,8|8 +200,232,34677,5,4 +119,169,34942,1,0 +57,248,35208,1,8 +137,311,35473,1,0 +200,232,35739,5,0 +248,302,36004,1,0 +318,254,36270,1,8 +270,183,36535,1,0 +200,232,36801,6,0,B|120:272|120:272|40:224,1,170,0|8 +130,183,37597,1,0 +200,232,37863,2,0,B|280:192|280:192|368:240,1,170,0|8 +167,111,38925,6,0,B|134:71|98:65,1,85,8|0 +167,112,39456,2,0,B|115:116|90:142,1,85,4|0 +167,112,39987,2,0,B|120:192|176:248|240:312|152:368,1,255,8|0 +173,351,41048,6,0,B|142:305|80:288,1,85,8|0 +173,351,41579,2,0,B|194:299|175:238,1,85,4|0 +173,351,42110,2,0,B|237:351|253:303|269:255|341:263,1,170,8|8 +128,144,43172,5,4 +208,176,43438,1,0 +288,144,43703,1,8 +368,176,43969,1,0 +408,272,44234,5,0 +312,312,44500,1,0 +216,272,44765,1,8 +120,312,45031,1,0 +48,240,45296,5,0 +160,272,45562,1,0 +272,240,45827,1,8 +384,280,46093,1,0 +496,240,46358,2,0,B|448:208|448:208|496:176|504:128|442:127,1,170,0|8 +152,128,47420,6,0,B|122:167|120:224,1,85,8|0 +88,128,47951,2,0,B|95:177|133:218,1,85,4|0 +121,204,48482,2,0,B|140:296|264:280|308:368,1,255,8|0 +308,368,49544,6,0,B|293:318|324:264,1,85,8|0 +368,348,50075,2,0,B|322:323|305:263,1,85,4|0 +324,200,50606,2,0,B|274:214|203:224|142:108|131:56|243:32|243:120|211:160|107:136,1,340,8|2 +369,216,52730,5,2 +176,312,53792,2,0,B|166:217|64:144,1,170,0|0 +179,150,54588,1,0 +120,88,54854,2,0,B|107:176|38:232,1,170,2|0 +464,320,55916,6,0,B|392:252|288:280,1,170,0|0 +280,104,56978,6,0,B|312:192|416:208,1,170,2|0 +192,160,58120,2,0,B|182:224|112:240,1,85,2|0 +24,240,58692,6,0,B|72:240|88:272,1,56.6666666666667,6|0 +224,296,59325,2,0,B|240:200|200:120,1,170 +316,136,60513,5,0 +400,156,60778,2,0,B|408:100|364:56,1,85,10|0 +320,16,61309,1,2 +160,112,61840,6,0,B|95:104|28:135,1,127.499996200204,8|0 +160,112,62371,6,0,B|80:168|96:296,1,170,4|8 +176,280,63168,1,0 +224,208,63433,2,0,B|280:288|392:264,1,170,0|8 +456,184,64230,1,0 +328,144,64495,1,8 +416,248,64761,1,0 +408,112,65026,1,8 +336,232,65292,1,0 +388,182,65557,1,8 +256,288,66088,5,8 +256,288,66354,1,0 +256,288,66619,2,0,B|200:360|72:368,1,170,0|8 +44,308,67416,1,0 +87,234,67681,2,0,B|163:279|207:386,1,170,0|8 +256,288,68478,1,0 +400,120,68743,5,8 +328,256,69009,1,0 +400,120,69274,1,8 +264,184,69540,1,0 +400,120,69805,1,8 +400,120,70336,6,0,B|395:173|368:200,1,85,8|0 +213,255,70867,2,0,B|279:198|383:198,1,170,4|8 +329,125,71663,1,0 +248,104,71929,2,0,B|184:168|80:152,1,170,0|8 +200,224,72725,1,0 +272,339,72991,5,8 +151,276,73256,1,0 +267,204,73522,1,8 +204,322,73787,1,0 +287,272,74053,1,8 +287,272,74584,6,0,B|336:256|368:208,1,85,8|0 +372,140,75115,2,0,B|323:206|324:308,1,170,0|8 +240,288,75911,1,0 +160,248,76177,2,0,B|216:176|320:216,1,170,0|8 +272,136,76973,1,0 +200,88,77239,6,0,B|216:136|192:176,1,85,8|0 +160,248,77770,2,0,B|160:296|208:320,1,85,8|0 +328,232,78301,5,0 +233,133,78566,1,8 +297,15,78832,1,8 +432,40,79097,1,8 +453,176,79363,6,0,B|448:240|384:272|328:232,1,170,4|8 +286,306,80159,1,0 +203,288,80424,2,0,B|208:224|272:192|328:232,1,170,0|8 +404,231,81221,1,0 +408,160,81486,5,8 +360,288,81752,1,0 +472,216,82017,1,8 +336,208,82283,1,0 +440,296,82548,1,8 +288,320,83079,5,8 +288,320,83345,1,0 +288,320,83610,2,0,B|200:314|128:248,1,170,0|8 +88,320,84407,1,0 +56,240,84672,2,0,B|133:287|176:392,1,170,0|8 +163,274,85469,1,0 +296,216,85734,5,8 +165,75,86000,1,0 +99,178,86265,1,8 +282,97,86531,1,0 +184,264,86796,1,8 +184,264,87327,6,0,B|159:295|110:299,1,85,8|0 +23,247,87858,2,0,B|91:300|192:261,1,170,4|8 +245,326,88655,1,0 +293,254,88920,2,0,B|213:198|109:246,1,170,0|8 +181,302,89717,1,0 +165,166,89982,5,8 +141,302,90247,1,0 +205,182,90513,1,8 +109,278,90778,1,0 +229,214,91044,1,8 +376,132,91575,6,0,B|424:140|464:100,1,85,8|0 +464,192,92106,2,0,B|456:280|352:320,1,170,0|8 +300,256,92902,1,0 +228,212,93168,2,0,B|268:116|164:60,1,170,0|8 +100,32,93964,1,0 +84,116,94230,2,0,B|116:156|108:212,1,85,8|0 +188,160,94761,2,0,B|188:208|232:244,1,85,8|0 +296,196,95292,2,0,B|320:236|349:239|399:242|379:198|379:198|334:185|358:245|368:276|440:260|480:316|416:356,1,340,8|4 +256,192,96486,12,8,98478 +264,192,113345,5,8 +264,192,113876,1,8 +264,192,114407,5,0 +172,236,114672,1,8 +184,336,114938,1,0 +284,356,115203,1,8 +340,268,115469,1,8 +304,100,116000,1,8 +304,100,116531,1,0 +272,336,117062,5,8 +248,200,117327,1,0 +376,152,117593,1,8 +376,152,118124,1,8 +376,152,118655,5,0 +240,128,118920,1,8 +376,192,119186,1,0 +496,152,119451,1,8 +376,224,119717,1,8 +376,224,120247,1,8 +376,224,120778,1,0 +376,224,121309,5,8 +264,296,121575,1,0 +256,160,121840,1,8 +256,160,122371,1,8 +256,160,122902,1,0 +256,160,123433,5,8 +168,264,123699,1,0 +312,280,123964,1,8 +312,280,124495,1,8 +312,280,125026,1,0 +312,280,125557,5,8 +200,200,125823,1,0 +312,280,126088,1,8 +312,280,126619,1,8 +312,280,127150,5,0 +416,200,127416,1,8 +432,336,127681,1,0 +416,200,127947,1,8 +312,280,128212,1,8 +312,280,128743,1,8 +312,280,129274,5,8 +264,152,129540,1,8 +136,192,129805,1,8 +184,320,130071,1,12 +88,120,132460,6,0,B|127:224|104:304,1,170,2|0 +424,264,133522,2,0,B|384:159|408:80,1,170 +448,168,134318,2,0,B|369:240|297:240,1,170,4|0 +301,158,135115,2,0,B|277:206|309:262,1,85 +395,295,135646,2,0,B|323:263|227:287,1,170,0|2 +176,88,136708,6,0,B|134:57|80:64,1,85 +176,88,137239,2,0,B|221:64|264:64,1,85,8|0 +176,88,137770,2,0,B|137:175|196:220|272:272|208:344,1,255,4|0 +136,328,138832,6,0,B|83:306|40:328,1,85 +136,328,139363,2,0,B|184:312|224:328,1,85,2|0 +300,296,139894,2,0,B|300:198|388:200|468:200|452:104,1,255,4|0 +372,100,140955,1,0 +292,72,141221,6,0,B|250:102|244:152,2,85,0|8|0 +332,148,142017,1,4 +388,212,142283,2,0,B|414:243|465:241,1,85 +440,148,142814,2,0,B|400:172|388:213,1,85 +236,232,143345,1,0 +204,84,143610,1,0 +356,64,143876,1,0 +388,212,144141,2,0,B|350:295|228:308,1,170,4|0 +96,304,145203,6,0,B|96:208,1,85 +144,203,145734,2,0,B|144:288,1,85,8|0 +192,272,146265,2,0,B|192:176|192:176|192:120|256:112,1,170,4|0 +312,56,147062,1,0 +392,120,147327,6,0,B|392:208,1,85 +336,221,147858,2,0,B|336:136,1,85,8|0 +280,152,148389,2,0,B|280:256|280:256|264:272|280:288|280:288|296:304|280:320|280:320|248:336|280:352|280:352|312:368|312:368|280:376|224:384,1,340,4|4 +172,322,149717,5,0 +136,248,149982,1,8 +64,208,150247,1,0 +147,112,150513,5,0 +224,80,150778,1,0 +304,112,151044,1,8 +384,88,151309,1,0 +336,192,151575,6,0,B|280:272|176:264,1,170,0|8 +408,216,152637,2,0,B|429:173|464:152,1,85,0|0 +360,80,153168,2,0,B|376:168|304:264,1,170,8|0 +256,288,153964,5,2 +192,240,154230,1,4 +272,208,154495,1,0 +229,134,154761,2,0,B|276:214,1,85,0|2 +160,248,155292,1,4 +120,136,155557,1,0 +229,134,155823,6,0,B|331:134,1,85,0|2 +408,208,156354,2,0,B|312:208,1,85,4|0 +216,256,156885,2,0,B|272:280|264:352|208:344|192:296|256:272|328:312,1,170,0|4 +456,224,157947,5,0 +400,136,158212,1,0 +456,224,158478,1,8 +392,304,158743,1,0 +456,224,159009,1,0 +288,232,159540,5,8 +200,283,159805,1,0 +176,184,160071,1,0 +176,184,160601,5,8 +278,184,160867,1,0 +176,184,161132,2,0,B|88:184,1,85 +24,88,161663,2,0,B|192:88,1,170,8|0 +280,88,162460,1,2 +240,168,162725,1,4 +360,48,163256,5,0 +280,88,163522,1,2 +240,168,163787,2,0,B|344:168,1,85,4|0 +376,240,164318,2,0,B|320:312,1,85,2|0 +248,304,164849,2,0,B|200:232,1,85,6|0 +288,240,165380,2,0,B|288:136|288:136|286:82|344:72,1,170,6|8 +480,104,166442,6,0,B|416:168|416:296,1,170,4|8 +336,280,167239,1,0 +288,208,167504,2,0,B|232:288|120:264,1,170,0|8 +56,184,168301,1,0 +184,144,168566,1,8 +96,248,168832,1,0 +104,112,169097,1,8 +176,232,169363,1,0 +124,182,169628,1,8 +272,256,170159,5,8 +272,256,170424,1,0 +272,256,170690,2,0,B|310:339|428:329,1,170,0|8 +487,259,171486,1,0 +423,179,171752,2,0,B|340:241|340:329,1,170,0|8 +251,346,172548,1,0 +260,193,172814,5,8 +340,321,173079,1,0 +260,193,173345,1,8 +404,249,173610,1,0 +260,193,173876,1,8 +112,120,174407,6,0,B|117:173|144:200,1,85,8|0 +309,191,174938,2,0,B|225:225|117:191,1,170,0|8 +184,128,175734,1,0 +264,104,176000,2,0,B|328:168|432:152,1,170,0|8 +312,224,176796,1,0 +240,339,177062,5,8 +361,276,177327,1,0 +245,204,177593,1,8 +308,322,177858,1,0 +225,270,178124,1,8 +225,270,178655,6,0,B|176:256|144:208,1,85,8|0 +32,256,179186,2,0,B|120:256|192:312,1,170,0|8 +272,288,179982,1,0 +352,248,180247,2,0,B|296:176|192:216,1,170,0|8 +240,136,181044,1,0 +325,129,181309,6,0,B|322:176|285:217,1,85,8|0 +167,291,181840,2,0,B|170:244|207:203,1,85,8|0 +327,289,182371,2,0,B|280:286|239:249,1,85,8|0 +160,120,182902,2,0,B|216:112|248:152|272:192|336:192,1,170,8|4 +256,192,183699,12,4,185557 +80,104,202017,5,2 +152,219,202283,1,0 +16,224,202548,2,0,B|88:208|158:111,1,170,8|0 +226,87,203345,1,0 +304,120,203610,2,0,B|352:120|400:104,1,85,2|0 +304,120,204141,2,0,B|336:88|344:32,1,85,0|0 +341,45,204672,6,0,B|429:77|450:203,1,170,8|0 +360,184,205469,1,0 +304,120,205734,2,0,B|264:96|240:48,1,85,2|0 +304,120,206265,2,0,B|311:76|344:32,1,85,0|0 +408,88,206796,5,4 +472,168,207062,1,0 +392,224,207327,1,0 +304,280,207593,1,0 +224,208,207858,2,0,B|309:237|393:224,1,170 +472,168,208655,1,0 +408,88,208920,6,0,B|368:166|402:252,1,170,8|0 +504,280,209717,1,0 +403,319,209982,2,0,B|459:276|475:151,1,170,4|0 +408,88,210778,1,0 +384,200,211044,5,2 +240,160,211309,1,0 +264,304,211575,1,0 +296,224,211840,2,0,B|336:137|464:136,1,170,2|0 +296,224,212637,6,0,B|243:220|208:161,1,85,2|0 +163,324,213168,2,0,B|244:308|308:204,1,170,8|0 +296,136,213964,1,0 +264,56,214230,2,0,B|232:96|192:136,1,85,4|0 +208,120,214761,2,0,B|200:72|168:32,1,85 +175,42,215292,2,0,B|155:86|98:112,1,85,2|0 +50,53,215823,2,0,B|98:69|122:109,1,85,0|0 +117,102,216354,1,4 +168,344,216885,6,0,B|167:287|131:246,1,85 +88,160,217416,2,0,B|48:248|96:328,1,170,8|0 +144,264,218212,1,0 +224,296,218478,2,0,B|328:312|368:216,1,170,6|0 +363,110,219274,2,0,B|259:246|139:206|147:94|275:70|355:198|130:268,1,446.249986700714,2|8 +160,112,221663,6,0,B|80:168|96:296,1,170,4|8 +176,280,222460,1,0 +224,208,222725,2,0,B|280:288|392:264,1,170,0|8 +456,184,223522,1,0 +328,144,223787,5,8 +416,248,224053,1,0 +408,112,224318,1,8 +336,232,224584,1,0 +388,182,224849,1,8 +240,256,225380,5,8 +240,256,225646,1,0 +240,256,225911,2,0,B|184:328|76:314,1,170,0|8 +3,315,226708,1,0 +89,315,226973,2,0,B|184:302|240:374,1,170,0|8 +314,332,227770,1,0 +252,194,228035,5,8 +116,130,228301,1,0 +252,194,228566,1,8 +140,298,228832,1,0 +252,194,229097,1,8 +400,120,229628,6,0,B|352:112|288:144,1,85,8|0 +203,191,230159,2,0,B|287:225|395:191,1,170,0|8 +330,124,230955,1,0 +248,104,231221,2,0,B|152:96|80:152,1,170,0|8 +200,224,232017,1,0 +272,339,232283,5,8 +151,276,232548,1,0 +267,204,232814,1,8 +204,322,233079,1,0 +287,270,233345,1,8 +287,270,233876,6,0,B|335:254|367:206,1,85,8|0 +464,288,234407,2,0,B|368:272|304:344,1,170,0|8 +226,317,235203,1,0 +165,256,235469,2,0,B|224:192|336:208,1,170,0|8 +272,136,236265,1,0 +199,63,236531,2,0,B|152:80|120:128,1,85,8|0 +203,184,237062,2,0,B|167:218|165:267,1,85,8|0 +312,264,237593,5,8 +440,264,237858,1,8 +256,144,238124,1,8 +496,144,238389,1,0 +256,192,238655,12,4,240778 diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/20544-expected-conversion.json b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/20544-expected-conversion.json new file mode 100644 index 0000000000..2289a7243f --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/20544-expected-conversion.json @@ -0,0 +1 @@ +{"Mappings":[{"RandomW":273523780,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":7693.0,"Objects":[{"StartTime":7693.0,"EndTime":7693.0,"Column":0}]},{"RandomW":2659866685,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273523780,"StartTime":8043.0,"Objects":[{"StartTime":8043.0,"EndTime":8043.0,"Column":1}]},{"RandomW":3083309108,"RandomX":273326509,"RandomY":273523780,"RandomZ":2659866685,"StartTime":8393.0,"Objects":[{"StartTime":8393.0,"EndTime":8393.0,"Column":2}]},{"RandomW":2413296944,"RandomX":2659866685,"RandomY":3083309108,"RandomZ":4072999080,"StartTime":8626.0,"Objects":[{"StartTime":8626.0,"EndTime":8626.0,"Column":2},{"StartTime":8626.0,"EndTime":8626.0,"Column":0}]},{"RandomW":1129322311,"RandomX":3083309108,"RandomY":4072999080,"RandomZ":2413296944,"StartTime":8860.0,"Objects":[{"StartTime":8860.0,"EndTime":8860.0,"Column":2}]},{"RandomW":3365759273,"RandomX":4072999080,"RandomY":2413296944,"RandomZ":1129322311,"StartTime":9326.0,"Objects":[{"StartTime":9326.0,"EndTime":9326.0,"Column":3}]},{"RandomW":315078874,"RandomX":2413296944,"RandomY":1129322311,"RandomZ":3365759273,"StartTime":9560.0,"Objects":[{"StartTime":9560.0,"EndTime":9560.0,"Column":3}]},{"RandomW":583662031,"RandomX":1129322311,"RandomY":3365759273,"RandomZ":315078874,"StartTime":9793.0,"Objects":[{"StartTime":9793.0,"EndTime":9793.0,"Column":3}]},{"RandomW":3789568254,"RandomX":3365759273,"RandomY":315078874,"RandomZ":583662031,"StartTime":10260.0,"Objects":[{"StartTime":10260.0,"EndTime":10260.0,"Column":2}]},{"RandomW":3256340938,"RandomX":315078874,"RandomY":583662031,"RandomZ":3789568254,"StartTime":10493.0,"Objects":[{"StartTime":10493.0,"EndTime":10493.0,"Column":2}]},{"RandomW":2152938451,"RandomX":3789568254,"RandomY":3256340938,"RandomZ":3979976762,"StartTime":10727.0,"Objects":[{"StartTime":10727.0,"EndTime":10727.0,"Column":1},{"StartTime":10727.0,"EndTime":10727.0,"Column":0}]},{"RandomW":1620362479,"RandomX":3256340938,"RandomY":3979976762,"RandomZ":2152938451,"StartTime":11427.0,"Objects":[{"StartTime":11427.0,"EndTime":11427.0,"Column":1}]},{"RandomW":477221046,"RandomX":3979976762,"RandomY":2152938451,"RandomZ":1620362479,"StartTime":11777.0,"Objects":[{"StartTime":11777.0,"EndTime":11777.0,"Column":1}]},{"RandomW":1013554034,"RandomX":2152938451,"RandomY":1620362479,"RandomZ":477221046,"StartTime":12127.0,"Objects":[{"StartTime":12127.0,"EndTime":12127.0,"Column":2}]},{"RandomW":637383311,"RandomX":1620362479,"RandomY":477221046,"RandomZ":1013554034,"StartTime":12360.0,"Objects":[{"StartTime":12360.0,"EndTime":12360.0,"Column":2}]},{"RandomW":3817388387,"RandomX":477221046,"RandomY":1013554034,"RandomZ":637383311,"StartTime":12594.0,"Objects":[{"StartTime":12594.0,"EndTime":12594.0,"Column":3}]},{"RandomW":19695232,"RandomX":637383311,"RandomY":3817388387,"RandomZ":1911435716,"StartTime":13060.0,"Objects":[{"StartTime":13060.0,"EndTime":13060.0,"Column":3},{"StartTime":13060.0,"EndTime":13060.0,"Column":0}]},{"RandomW":3381470688,"RandomX":3817388387,"RandomY":1911435716,"RandomZ":19695232,"StartTime":13294.0,"Objects":[{"StartTime":13294.0,"EndTime":13294.0,"Column":3}]},{"RandomW":1862836779,"RandomX":19695232,"RandomY":3381470688,"RandomZ":1869143571,"StartTime":13527.0,"Objects":[{"StartTime":13527.0,"EndTime":13527.0,"Column":3},{"StartTime":13527.0,"EndTime":13527.0,"Column":5}]},{"RandomW":175452620,"RandomX":3381470688,"RandomY":1869143571,"RandomZ":1862836779,"StartTime":13994.0,"Objects":[{"StartTime":13994.0,"EndTime":13994.0,"Column":4}]},{"RandomW":2859972423,"RandomX":1869143571,"RandomY":1862836779,"RandomZ":175452620,"StartTime":14227.0,"Objects":[{"StartTime":14227.0,"EndTime":14227.0,"Column":4}]},{"RandomW":2210823260,"RandomX":1862836779,"RandomY":175452620,"RandomZ":2859972423,"StartTime":14461.0,"Objects":[{"StartTime":14461.0,"EndTime":14461.0,"Column":5}]},{"RandomW":2851442677,"RandomX":175452620,"RandomY":2859972423,"RandomZ":2210823260,"StartTime":14927.0,"Objects":[{"StartTime":14927.0,"EndTime":16561.0,"Column":1}]},{"RandomW":179122262,"RandomX":2859972423,"RandomY":2210823260,"RandomZ":2851442677,"StartTime":16794.0,"Objects":[{"StartTime":16794.0,"EndTime":18078.0,"Column":0}]},{"RandomW":2917386405,"RandomX":2851442677,"RandomY":179122262,"RandomZ":494367691,"StartTime":18661.0,"Objects":[{"StartTime":18661.0,"EndTime":19127.0,"Column":2}]},{"RandomW":3407923728,"RandomX":494367691,"RandomY":2917386405,"RandomZ":2825679051,"StartTime":19595.0,"Objects":[{"StartTime":19595.0,"EndTime":20061.0,"Column":3}]},{"RandomW":358318928,"RandomX":3407923728,"RandomY":1835995540,"RandomZ":3732560508,"StartTime":20528.0,"Objects":[{"StartTime":20528.0,"EndTime":20994.0,"Column":4},{"StartTime":20528.0,"EndTime":20994.0,"Column":1}]},{"RandomW":3440439960,"RandomX":3732560508,"RandomY":358318928,"RandomZ":3638999969,"StartTime":21462.0,"Objects":[{"StartTime":21462.0,"EndTime":21928.0,"Column":3}]},{"RandomW":3249928444,"RandomX":358318928,"RandomY":3638999969,"RandomZ":3440439960,"StartTime":22395.0,"Objects":[{"StartTime":22395.0,"EndTime":22395.0,"Column":1}]},{"RandomW":3857394572,"RandomX":3440439960,"RandomY":3249928444,"RandomZ":138257049,"StartTime":22628.0,"Objects":[{"StartTime":22628.0,"EndTime":24028.0,"Column":4}]},{"RandomW":2938470811,"RandomX":3249928444,"RandomY":138257049,"RandomZ":3857394572,"StartTime":24262.0,"Objects":[{"StartTime":24262.0,"EndTime":24262.0,"Column":3}]},{"RandomW":3241803419,"RandomX":138257049,"RandomY":3857394572,"RandomZ":2938470811,"StartTime":24495.0,"Objects":[{"StartTime":24495.0,"EndTime":24495.0,"Column":4}]},{"RandomW":620078415,"RandomX":3857394572,"RandomY":2938470811,"RandomZ":3241803419,"StartTime":25195.0,"Objects":[{"StartTime":25195.0,"EndTime":25195.0,"Column":4}]},{"RandomW":2566806806,"RandomX":2938470811,"RandomY":3241803419,"RandomZ":620078415,"StartTime":25429.0,"Objects":[{"StartTime":25429.0,"EndTime":25429.0,"Column":4}]},{"RandomW":458505931,"RandomX":3241803419,"RandomY":620078415,"RandomZ":2566806806,"StartTime":26129.0,"Objects":[{"StartTime":26129.0,"EndTime":26129.0,"Column":3}]},{"RandomW":2629948988,"RandomX":2566806806,"RandomY":458505931,"RandomZ":362272284,"StartTime":26362.0,"Objects":[{"StartTime":26362.0,"EndTime":27762.0,"Column":1}]},{"RandomW":1285940261,"RandomX":362272284,"RandomY":2629948988,"RandomZ":4139597407,"StartTime":27996.0,"Objects":[{"StartTime":27996.0,"EndTime":27996.0,"Column":1},{"StartTime":27996.0,"EndTime":27996.0,"Column":3}]},{"RandomW":3878288539,"RandomX":2629948988,"RandomY":4139597407,"RandomZ":1285940261,"StartTime":28229.0,"Objects":[{"StartTime":28229.0,"EndTime":28229.0,"Column":1}]},{"RandomW":1788551508,"RandomX":1285940261,"RandomY":3878288539,"RandomZ":1976280692,"StartTime":28929.0,"Objects":[{"StartTime":28929.0,"EndTime":28929.0,"Column":1},{"StartTime":28929.0,"EndTime":28929.0,"Column":4}]},{"RandomW":159147246,"RandomX":3878288539,"RandomY":1976280692,"RandomZ":1788551508,"StartTime":29163.0,"Objects":[{"StartTime":29163.0,"EndTime":29163.0,"Column":1}]},{"RandomW":2702806142,"RandomX":1976280692,"RandomY":1788551508,"RandomZ":159147246,"StartTime":29863.0,"Objects":[{"StartTime":29863.0,"EndTime":29863.0,"Column":2}]},{"RandomW":2311677487,"RandomX":1788551508,"RandomY":159147246,"RandomZ":2702806142,"StartTime":30213.0,"Objects":[{"StartTime":30213.0,"EndTime":30213.0,"Column":3}]},{"RandomW":3175953261,"RandomX":2311677487,"RandomY":988506051,"RandomZ":3495571300,"StartTime":30446.0,"Objects":[{"StartTime":30446.0,"EndTime":31146.0,"Column":2}]},{"RandomW":516122535,"RandomX":3495571300,"RandomY":3175953261,"RandomZ":2138555125,"StartTime":31730.0,"Objects":[{"StartTime":31730.0,"EndTime":31730.0,"Column":2},{"StartTime":31730.0,"EndTime":31730.0,"Column":1}]},{"RandomW":534989332,"RandomX":3175953261,"RandomY":2138555125,"RandomZ":516122535,"StartTime":32080.0,"Objects":[{"StartTime":32080.0,"EndTime":32080.0,"Column":2}]},{"RandomW":3420570846,"RandomX":2138555125,"RandomY":516122535,"RandomZ":534989332,"StartTime":32430.0,"Objects":[{"StartTime":32430.0,"EndTime":32430.0,"Column":2}]},{"RandomW":172021565,"RandomX":516122535,"RandomY":534989332,"RandomZ":3420570846,"StartTime":32663.0,"Objects":[{"StartTime":32663.0,"EndTime":32663.0,"Column":2}]},{"RandomW":168636292,"RandomX":3420570846,"RandomY":172021565,"RandomZ":263944077,"StartTime":32780.0,"Objects":[{"StartTime":32780.0,"EndTime":32780.0,"Column":0}]},{"RandomW":3473923375,"RandomX":172021565,"RandomY":263944077,"RandomZ":168636292,"StartTime":33597.0,"Objects":[{"StartTime":33597.0,"EndTime":33597.0,"Column":1}]},{"RandomW":3287941836,"RandomX":263944077,"RandomY":168636292,"RandomZ":3473923375,"StartTime":33947.0,"Objects":[{"StartTime":33947.0,"EndTime":33947.0,"Column":1}]},{"RandomW":1950056015,"RandomX":3473923375,"RandomY":3287941836,"RandomZ":388563489,"StartTime":34180.0,"Objects":[{"StartTime":34180.0,"EndTime":35230.0,"Column":5}]},{"RandomW":3600000321,"RandomX":388563489,"RandomY":1950056015,"RandomZ":3312202562,"StartTime":35464.0,"Objects":[{"StartTime":35464.0,"EndTime":36164.0,"Column":4}]},{"RandomW":647123919,"RandomX":3312202562,"RandomY":3600000321,"RandomZ":2314505656,"StartTime":36397.0,"Objects":[{"StartTime":36397.0,"EndTime":37097.0,"Column":1}]},{"RandomW":3375531720,"RandomX":2314505656,"RandomY":647123919,"RandomZ":2193654396,"StartTime":37564.0,"Objects":[{"StartTime":37564.0,"EndTime":37914.0,"Column":3}]},{"RandomW":2335314869,"RandomX":3834006299,"RandomY":1346269295,"RandomZ":3597388662,"StartTime":38264.0,"Objects":[{"StartTime":38264.0,"EndTime":38264.0,"Column":4},{"StartTime":38380.0,"EndTime":38380.0,"Column":3},{"StartTime":38496.0,"EndTime":38496.0,"Column":4}]},{"RandomW":1564102491,"RandomX":1346269295,"RandomY":3597388662,"RandomZ":2335314869,"StartTime":39197.0,"Objects":[{"StartTime":39197.0,"EndTime":39197.0,"Column":2}]},{"RandomW":1989977426,"RandomX":2335314869,"RandomY":1564102491,"RandomZ":4263834011,"StartTime":39431.0,"Objects":[{"StartTime":39431.0,"EndTime":39431.0,"Column":2},{"StartTime":39431.0,"EndTime":39431.0,"Column":5}]},{"RandomW":3806815718,"RandomX":4263834011,"RandomY":1989977426,"RandomZ":1831387023,"StartTime":39664.0,"Objects":[{"StartTime":39664.0,"EndTime":39664.0,"Column":1},{"StartTime":39664.0,"EndTime":39664.0,"Column":4}]},{"RandomW":999749640,"RandomX":1989977426,"RandomY":1831387023,"RandomZ":3806815718,"StartTime":39898.0,"Objects":[{"StartTime":39898.0,"EndTime":40831.0,"Column":1}]},{"RandomW":2830335005,"RandomX":1831387023,"RandomY":3806815718,"RandomZ":999749640,"StartTime":41298.0,"Objects":[{"StartTime":41298.0,"EndTime":41298.0,"Column":1}]},{"RandomW":2152692291,"RandomX":3806815718,"RandomY":999749640,"RandomZ":2830335005,"StartTime":41648.0,"Objects":[{"StartTime":41648.0,"EndTime":41648.0,"Column":1}]},{"RandomW":1499396089,"RandomX":999749640,"RandomY":2830335005,"RandomZ":2152692291,"StartTime":41998.0,"Objects":[{"StartTime":41998.0,"EndTime":41998.0,"Column":2}]},{"RandomW":3582202466,"RandomX":2830335005,"RandomY":2152692291,"RandomZ":1499396089,"StartTime":42231.0,"Objects":[{"StartTime":42231.0,"EndTime":42231.0,"Column":2}]},{"RandomW":3873754971,"RandomX":2152692291,"RandomY":1499396089,"RandomZ":3582202466,"StartTime":42931.0,"Objects":[{"StartTime":42931.0,"EndTime":42931.0,"Column":4}]},{"RandomW":495070374,"RandomX":1499396089,"RandomY":3582202466,"RandomZ":3873754971,"StartTime":43165.0,"Objects":[{"StartTime":43165.0,"EndTime":43165.0,"Column":4}]},{"RandomW":3016618448,"RandomX":3582202466,"RandomY":3873754971,"RandomZ":495070374,"StartTime":43398.0,"Objects":[{"StartTime":43398.0,"EndTime":43398.0,"Column":4}]},{"RandomW":1177547465,"RandomX":3873754971,"RandomY":495070374,"RandomZ":3016618448,"StartTime":43631.0,"Objects":[{"StartTime":43631.0,"EndTime":43631.0,"Column":3}]},{"RandomW":2255582016,"RandomX":495070374,"RandomY":3016618448,"RandomZ":1177547465,"StartTime":43865.0,"Objects":[{"StartTime":43865.0,"EndTime":43865.0,"Column":3}]},{"RandomW":2325387316,"RandomX":3016618448,"RandomY":1177547465,"RandomZ":2255582016,"StartTime":44098.0,"Objects":[{"StartTime":44098.0,"EndTime":44098.0,"Column":2}]},{"RandomW":1443216326,"RandomX":1177547465,"RandomY":2255582016,"RandomZ":2325387316,"StartTime":44332.0,"Objects":[{"StartTime":44332.0,"EndTime":44332.0,"Column":2}]},{"RandomW":1650665398,"RandomX":2325387316,"RandomY":1443216326,"RandomZ":1871032949,"StartTime":44565.0,"Objects":[{"StartTime":44565.0,"EndTime":44565.0,"Column":1},{"StartTime":44565.0,"EndTime":44565.0,"Column":4}]},{"RandomW":1204166455,"RandomX":1871032949,"RandomY":1650665398,"RandomZ":1013336310,"StartTime":44798.0,"Objects":[{"StartTime":44798.0,"EndTime":45498.0,"Column":3}]},{"RandomW":2125976115,"RandomX":1013336310,"RandomY":1204166455,"RandomZ":93461408,"StartTime":45732.0,"Objects":[{"StartTime":45732.0,"EndTime":46432.0,"Column":5}]},{"RandomW":1391245329,"RandomX":1889010923,"RandomY":131109480,"RandomZ":2450179625,"StartTime":46665.0,"Objects":[{"StartTime":46665.0,"EndTime":47365.0,"Column":0},{"StartTime":46665.0,"EndTime":47365.0,"Column":3}]},{"RandomW":1629740061,"RandomX":2450179625,"RandomY":1391245329,"RandomZ":3806548475,"StartTime":47599.0,"Objects":[{"StartTime":47599.0,"EndTime":47949.0,"Column":4}]},{"RandomW":2462543108,"RandomX":3806548475,"RandomY":1629740061,"RandomZ":2782684574,"StartTime":48532.0,"Objects":[{"StartTime":48532.0,"EndTime":49232.0,"Column":0}]},{"RandomW":1398343675,"RandomX":2462543108,"RandomY":1783863854,"RandomZ":368009293,"StartTime":49466.0,"Objects":[{"StartTime":49466.0,"EndTime":50166.0,"Column":1},{"StartTime":49466.0,"EndTime":50166.0,"Column":3}]},{"RandomW":1655209110,"RandomX":1398343675,"RandomY":4200591321,"RandomZ":204183638,"StartTime":50399.0,"Objects":[{"StartTime":50399.0,"EndTime":51099.0,"Column":0},{"StartTime":50399.0,"EndTime":51099.0,"Column":4}]},{"RandomW":2898792131,"RandomX":1655209110,"RandomY":4183149031,"RandomZ":4235317299,"StartTime":51333.0,"Objects":[{"StartTime":51333.0,"EndTime":52033.0,"Column":5},{"StartTime":51333.0,"EndTime":52033.0,"Column":2}]},{"RandomW":2376440576,"RandomX":4183149031,"RandomY":4235317299,"RandomZ":2898792131,"StartTime":52266.0,"Objects":[{"StartTime":52266.0,"EndTime":52266.0,"Column":0}]},{"RandomW":3672662434,"RandomX":4235317299,"RandomY":2898792131,"RandomZ":2376440576,"StartTime":52499.0,"Objects":[{"StartTime":52499.0,"EndTime":52499.0,"Column":1}]},{"RandomW":1144553308,"RandomX":2376440576,"RandomY":3672662434,"RandomZ":2825568900,"StartTime":52849.0,"Objects":[{"StartTime":52849.0,"EndTime":53199.0,"Column":3}]},{"RandomW":3856961856,"RandomX":3672662434,"RandomY":2825568900,"RandomZ":1144553308,"StartTime":54133.0,"Objects":[{"StartTime":54133.0,"EndTime":54133.0,"Column":3}]},{"RandomW":3856961856,"RandomX":3672662434,"RandomY":2825568900,"RandomZ":1144553308,"StartTime":54366.0,"Objects":[{"StartTime":54366.0,"EndTime":54366.0,"Column":2}]},{"RandomW":3856961856,"RandomX":3672662434,"RandomY":2825568900,"RandomZ":1144553308,"StartTime":54600.0,"Objects":[{"StartTime":54600.0,"EndTime":54600.0,"Column":3}]},{"RandomW":2182646490,"RandomX":1144553308,"RandomY":3856961856,"RandomZ":2090342703,"StartTime":55066.0,"Objects":[{"StartTime":55066.0,"EndTime":55066.0,"Column":2},{"StartTime":55066.0,"EndTime":55066.0,"Column":0}]},{"RandomW":2182646490,"RandomX":1144553308,"RandomY":3856961856,"RandomZ":2090342703,"StartTime":55300.0,"Objects":[{"StartTime":55300.0,"EndTime":55300.0,"Column":5},{"StartTime":55300.0,"EndTime":55300.0,"Column":3}]},{"RandomW":2182646490,"RandomX":1144553308,"RandomY":3856961856,"RandomZ":2090342703,"StartTime":55533.0,"Objects":[{"StartTime":55533.0,"EndTime":55533.0,"Column":2},{"StartTime":55533.0,"EndTime":55533.0,"Column":0}]},{"RandomW":3304208416,"RandomX":2090342703,"RandomY":2182646490,"RandomZ":90031962,"StartTime":56000.0,"Objects":[{"StartTime":56000.0,"EndTime":56233.0,"Column":3}]},{"RandomW":1041697651,"RandomX":90031962,"RandomY":3304208416,"RandomZ":2015301872,"StartTime":56583.0,"Objects":[{"StartTime":56583.0,"EndTime":56583.0,"Column":1},{"StartTime":56583.0,"EndTime":56583.0,"Column":2}]},{"RandomW":3818981880,"RandomX":15037736,"RandomY":2251270868,"RandomZ":2287819377,"StartTime":56700.0,"Objects":[{"StartTime":56700.0,"EndTime":56700.0,"Column":0},{"StartTime":56700.0,"EndTime":56700.0,"Column":4}]},{"RandomW":3368447121,"RandomX":2251270868,"RandomY":2287819377,"RandomZ":3818981880,"StartTime":56933.0,"Objects":[{"StartTime":56933.0,"EndTime":56933.0,"Column":1}]},{"RandomW":860096087,"RandomX":2287819377,"RandomY":3818981880,"RandomZ":3368447121,"StartTime":57867.0,"Objects":[{"StartTime":57867.0,"EndTime":57867.0,"Column":3}]},{"RandomW":860096087,"RandomX":2287819377,"RandomY":3818981880,"RandomZ":3368447121,"StartTime":58100.0,"Objects":[{"StartTime":58100.0,"EndTime":58100.0,"Column":2}]},{"RandomW":860096087,"RandomX":2287819377,"RandomY":3818981880,"RandomZ":3368447121,"StartTime":58334.0,"Objects":[{"StartTime":58334.0,"EndTime":58334.0,"Column":3}]},{"RandomW":1369988252,"RandomX":3818981880,"RandomY":3368447121,"RandomZ":860096087,"StartTime":58800.0,"Objects":[{"StartTime":58800.0,"EndTime":58800.0,"Column":4}]},{"RandomW":1369988252,"RandomX":3818981880,"RandomY":3368447121,"RandomZ":860096087,"StartTime":59034.0,"Objects":[{"StartTime":59034.0,"EndTime":59034.0,"Column":1}]},{"RandomW":1369988252,"RandomX":3818981880,"RandomY":3368447121,"RandomZ":860096087,"StartTime":59267.0,"Objects":[{"StartTime":59267.0,"EndTime":59267.0,"Column":4}]}]} \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/20544.osu b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/20544.osu new file mode 100644 index 0000000000..237a13ecd2 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/20544.osu @@ -0,0 +1,126 @@ +osu file format v5 + +[General] +StackLeniency: 0.7 +Mode: 0 + +[Difficulty] +HPDrainRate:2 +CircleSize:5 +OverallDifficulty:2 +SliderMultiplier:1 +SliderTickRate:2 + +[Events] +//Background and Video events +//Break Periods +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Failing) +//Storyboard Layer 2 (Passing) +//Storyboard Layer 3 (Foreground) +//Storyboard Sound Samples +//Background Colour Transformations +3,100,163,162,255 + +[TimingPoints] +7460,466.735154027506,4,1,0,100 + +[HitObjects] +80,56,7693,1,0 +120,96,8043,1,0 +176,104,8393,1,0 +216,104,8626,1,0 +256,104,8860,1,0 +296,168,9326,5,0 +296,208,9560,1,0 +296,248,9793,1,0 +216,256,10260,1,0 +176,256,10493,1,0 +136,256,10727,1,0 +136,136,11427,5,0 +136,72,11777,1,0 +192,72,12127,1,0 +232,72,12360,1,0 +272,72,12594,1,0 +280,152,13060,5,0 +280,192,13294,1,0 +280,232,13527,1,0 +360,240,13994,1,0 +400,240,14227,1,0 +440,240,14461,1,0 +256,192,14927,12,0,16561 +256,192,16794,12,0,18078 +192,96,18661,6,0,B|312:96,1,100 +288,176,19595,2,0,B|168:176,1,100 +192,256,20528,2,0,B|312:256,1,100 +304,176,21462,2,0,B|240:176|248:88,1,100 +168,104,22395,5,0 +128,104,22628,2,0,B|296:368,1,300 +328,352,24262,5,0 +368,352,24495,1,0 +368,232,25195,1,0 +368,192,25429,1,0 +280,104,26129,5,0 +240,104,26362,2,0,B|40:352,1,300 +88,336,27996,5,0 +128,336,28229,1,0 +136,216,28929,1,0 +136,176,29163,1,0 +256,176,29863,5,0 +312,176,30213,1,0 +352,176,30446,2,0,B|360:264|360:280|360:272|272:272,1,150 +208,232,31730,5,0 +208,168,32080,1,0 +208,104,32430,1,0 +248,104,32663,1,0 +248,104,32780,1,0 +120,160,33597,5,0 +120,216,33947,1,0 +120,256,34180,2,0,B|352:256,1,225 +344,216,35464,6,0,B|200:128,1,150 +176,136,36397,2,0,B|176:288,1,150 +296,288,37564,6,0,B|296:208,1,75 +296,152,38264,2,0,B|296:104,2,25 +248,32,39197,1,0 +208,32,39431,1,0 +168,32,39664,1,0 +168,72,39898,2,0,B|168:136,4,50 +104,128,41298,5,0 +168,136,41648,1,0 +208,184,41998,1,0 +232,216,42231,1,0 +344,248,42931,5,0 +344,208,43165,1,0 +344,168,43398,1,0 +304,168,43631,1,0 +264,168,43865,1,0 +224,168,44098,1,0 +184,168,44332,1,0 +144,168,44565,1,0 +104,176,44798,6,0,B|32:240|160:272,1,150 +192,272,45732,2,0,B|280:272|320:200,1,150 +320,160,46665,2,0,B|248:96|176:136,1,150 +144,144,47599,2,0,B|48:168,1,75 +112,256,48532,6,0,B|256:336,1,150 +280,320,49466,2,0,B|416:240,1,150 +408,200,50399,2,0,B|256:136,1,150 +232,144,51333,2,0,B|80:208,1,150 +56,216,52266,5,0 +96,216,52499,1,0 +152,216,52849,2,0,B|248:216,1,75 +328,88,54133,5,0 +328,88,54366,1,0 +328,88,54600,1,0 +248,88,55066,5,0 +248,88,55300,1,0 +248,88,55533,1,0 +256,168,56000,6,0,B|184:168,1,50 +144,168,56583,1,0 +144,168,56700,1,0 +104,168,56933,1,0 +264,168,57867,5,0 +264,168,58100,1,0 +264,168,58334,1,0 +344,168,58800,5,0 +344,168,59034,1,0 +344,168,59267,1,0 diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/basic-expected-conversion.json b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/basic-expected-conversion.json similarity index 100% rename from osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/basic-expected-conversion.json rename to osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/basic-expected-conversion.json diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/basic.osu b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/basic.osu similarity index 96% rename from osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/basic.osu rename to osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/basic.osu index 40b4409760..abd2ff2ee6 100644 --- a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/basic.osu +++ b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/basic.osu @@ -1,27 +1,27 @@ -osu file format v14 - -[Difficulty] -HPDrainRate:6 -CircleSize:4 -OverallDifficulty:7 -ApproachRate:8.3 -SliderMultiplier:1.6 -SliderTickRate:1 - -[TimingPoints] -500,500,4,2,1,50,1,0 -13426,-100,4,3,1,45,0,0 -14884,-100,4,2,1,50,0,0 - -[HitObjects] -96,192,500,6,0,L|416:192,2,320 -256,192,3000,12,0,4000,0:0:0:0: -256,192,4500,12,0,5500,0:0:0:0: -256,192,6000,12,0,6500,0:0:0:0: -256,128,7000,6,0,L|352:128,4,80 -32,192,8500,6,0,B|32:384|256:384|256:192|256:192|256:0|512:0|512:192,1,800 -256,192,11500,12,0,12000,0:0:0:0: -512,320,12500,6,0,B|0:256|0:256|512:96|512:96|256:32,1,1280 -256,256,17000,6,0,L|160:256,4,80 -256,192,18500,12,0,19450,0:0:0:0: -216,231,19875,6,0,B|216:135|280:135|344:135|344:199|344:263|248:327|248:327|120:327|120:327|56:39|408:39|408:39|472:150|408:342,1,1280 +osu file format v14 + +[Difficulty] +HPDrainRate:6 +CircleSize:4 +OverallDifficulty:7 +ApproachRate:8.3 +SliderMultiplier:1.6 +SliderTickRate:1 + +[TimingPoints] +500,500,4,2,1,50,1,0 +13426,-100,4,3,1,45,0,0 +14884,-100,4,2,1,50,0,0 + +[HitObjects] +96,192,500,6,0,L|416:192,2,320 +256,192,3000,12,0,4000,0:0:0:0: +256,192,4500,12,0,5500,0:0:0:0: +256,192,6000,12,0,6500,0:0:0:0: +256,128,7000,6,0,L|352:128,4,80 +32,192,8500,6,0,B|32:384|256:384|256:192|256:192|256:0|512:0|512:192,1,800 +256,192,11500,12,0,12000,0:0:0:0: +512,320,12500,6,0,B|0:256|0:256|512:96|512:96|256:32,1,1280 +256,256,17000,6,0,L|160:256,4,80 +256,192,18500,12,0,19450,0:0:0:0: +216,231,19875,6,0,B|216:135|280:135|344:135|344:199|344:263|248:327|248:327|120:327|120:327|56:39|408:39|408:39|472:150|408:342,1,1280 diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json similarity index 100% rename from osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json rename to osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples.osu b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/convert-samples.osu similarity index 100% rename from osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples.osu rename to osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/convert-samples.osu diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/diffcalc-test.osu b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/diffcalc-test.osu similarity index 100% rename from osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/diffcalc-test.osu rename to osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/diffcalc-test.osu diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples-expected-conversion.json b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/mania-samples-expected-conversion.json similarity index 100% rename from osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples-expected-conversion.json rename to osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/mania-samples-expected-conversion.json diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples.osu b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/mania-samples.osu similarity index 100% rename from osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples.osu rename to osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/mania-samples.osu diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/slider-convert-samples-expected-conversion.json b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/slider-convert-samples-expected-conversion.json similarity index 100% rename from osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/slider-convert-samples-expected-conversion.json rename to osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/slider-convert-samples-expected-conversion.json diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/slider-convert-samples.osu b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/slider-convert-samples.osu similarity index 100% rename from osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/slider-convert-samples.osu rename to osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/slider-convert-samples.osu diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/zero-length-slider-expected-conversion.json b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/zero-length-slider-expected-conversion.json similarity index 100% rename from osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/zero-length-slider-expected-conversion.json rename to osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/zero-length-slider-expected-conversion.json diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/zero-length-slider.osu b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/zero-length-slider.osu similarity index 100% rename from osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/zero-length-slider.osu rename to osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/zero-length-slider.osu diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs index 77f93b4ef9..e04b44311e 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs @@ -52,14 +52,18 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// The column. protected int GetColumn(float position, bool allowSpecial = false) { + // Casts to doubles are present here because, although code is originally written as float division, + // the division actually appears to occur on doubles in osu!stable. This is likely a result of + // differences in optimisations between .NET versions due to the presence of the double parameter type of Math.Floor(). + if (allowSpecial && TotalColumns == 8) { const float local_x_divisor = 512f / 7; - return Math.Clamp((int)MathF.Floor(position / local_x_divisor), 0, 6) + 1; + return Math.Clamp((int)Math.Floor((double)position / local_x_divisor), 0, 6) + 1; } float localXDivisor = 512f / TotalColumns; - return Math.Clamp((int)MathF.Floor(position / localXDivisor), 0, TotalColumns - 1); + return Math.Clamp((int)Math.Floor((double)position / localXDivisor), 0, TotalColumns - 1); } /// From 0553de768caaaaa9938a462953cc5e6960e26fcb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Dec 2023 15:26:02 +0900 Subject: [PATCH 1881/4092] Enforce namespace body style --- osu.sln.DotSettings | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 342bc8aa79..c8c5d6745c 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -15,6 +15,7 @@ HINT HINT WARNING + WARNING WARNING WARNING WARNING From 005fb789945497ea67cd12ef42ba735278fa0af0 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 7 Dec 2023 15:38:15 +0900 Subject: [PATCH 1882/4092] Fix last tick handling in osu beatmap conversion tests --- .../OsuBeatmapConversionTest.cs | 19 +- .../OsuDifficultyCalculatorTest.cs | 2 +- .../Beatmaps/1124896-expected-conversion.json | 1 + .../Resources/Testing/Beatmaps/1124896.osu | 1122 +++++++++++++++++ .../Beatmaps/basic-expected-conversion.json | 0 .../Resources/Testing/Beatmaps/basic.osu | 54 +- ...ear-perfect-curve-expected-conversion.json | 0 .../Beatmaps/colinear-perfect-curve.osu | 0 .../Testing/Beatmaps/diffcalc-test.osu | 0 ...ti-segment-slider-expected-conversion.json | 0 .../Testing/Beatmaps/multi-segment-slider.osu | 0 .../nan-slider-expected-conversion.json | 0 .../Resources/Testing/Beatmaps/nan-slider.osu | 0 .../old-stacking-expected-conversion.json | 0 .../Testing/Beatmaps/old-stacking.osu | 0 .../repeat-slider-expected-conversion.json | 0 .../Testing/Beatmaps/repeat-slider.osu | 0 ...r-paths-edge-case-expected-conversion.json | 0 .../Beatmaps/slider-paths-edge-case.osu | 0 ...r-ticks-edge-case-expected-conversion.json | 22 +- .../Beatmaps/slider-ticks-edge-case.osu | 0 .../slider-ticks-expected-conversion.json | 0 .../Testing/Beatmaps/slider-ticks.osu | 0 ...ven-repeat-slider-expected-conversion.json | 579 +++++++++ .../Testing/Beatmaps/uneven-repeat-slider.osu | 0 .../Testing/Beatmaps/very-fast-slider.osu | 0 .../Testing/Beatmaps/zero-length-sliders.osu | 0 ...ven-repeat-slider-expected-conversion.json | 348 ----- 28 files changed, 1744 insertions(+), 403 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/1124896-expected-conversion.json create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/1124896.osu rename {osu.Game.Rulesets.Osu => osu.Game.Rulesets.Osu.Tests}/Resources/Testing/Beatmaps/basic-expected-conversion.json (100%) rename {osu.Game.Rulesets.Osu => osu.Game.Rulesets.Osu.Tests}/Resources/Testing/Beatmaps/basic.osu (96%) rename {osu.Game.Rulesets.Osu => osu.Game.Rulesets.Osu.Tests}/Resources/Testing/Beatmaps/colinear-perfect-curve-expected-conversion.json (100%) rename {osu.Game.Rulesets.Osu => osu.Game.Rulesets.Osu.Tests}/Resources/Testing/Beatmaps/colinear-perfect-curve.osu (100%) rename {osu.Game.Rulesets.Osu => osu.Game.Rulesets.Osu.Tests}/Resources/Testing/Beatmaps/diffcalc-test.osu (100%) rename {osu.Game.Rulesets.Osu => osu.Game.Rulesets.Osu.Tests}/Resources/Testing/Beatmaps/multi-segment-slider-expected-conversion.json (100%) rename {osu.Game.Rulesets.Osu => osu.Game.Rulesets.Osu.Tests}/Resources/Testing/Beatmaps/multi-segment-slider.osu (100%) rename {osu.Game.Rulesets.Osu => osu.Game.Rulesets.Osu.Tests}/Resources/Testing/Beatmaps/nan-slider-expected-conversion.json (100%) rename {osu.Game.Rulesets.Osu => osu.Game.Rulesets.Osu.Tests}/Resources/Testing/Beatmaps/nan-slider.osu (100%) rename {osu.Game.Rulesets.Osu => osu.Game.Rulesets.Osu.Tests}/Resources/Testing/Beatmaps/old-stacking-expected-conversion.json (100%) rename {osu.Game.Rulesets.Osu => osu.Game.Rulesets.Osu.Tests}/Resources/Testing/Beatmaps/old-stacking.osu (100%) rename {osu.Game.Rulesets.Osu => osu.Game.Rulesets.Osu.Tests}/Resources/Testing/Beatmaps/repeat-slider-expected-conversion.json (100%) rename {osu.Game.Rulesets.Osu => osu.Game.Rulesets.Osu.Tests}/Resources/Testing/Beatmaps/repeat-slider.osu (100%) rename {osu.Game.Rulesets.Osu => osu.Game.Rulesets.Osu.Tests}/Resources/Testing/Beatmaps/slider-paths-edge-case-expected-conversion.json (100%) rename {osu.Game.Rulesets.Osu => osu.Game.Rulesets.Osu.Tests}/Resources/Testing/Beatmaps/slider-paths-edge-case.osu (100%) rename {osu.Game.Rulesets.Osu => osu.Game.Rulesets.Osu.Tests}/Resources/Testing/Beatmaps/slider-ticks-edge-case-expected-conversion.json (99%) rename {osu.Game.Rulesets.Osu => osu.Game.Rulesets.Osu.Tests}/Resources/Testing/Beatmaps/slider-ticks-edge-case.osu (100%) rename {osu.Game.Rulesets.Osu => osu.Game.Rulesets.Osu.Tests}/Resources/Testing/Beatmaps/slider-ticks-expected-conversion.json (100%) rename {osu.Game.Rulesets.Osu => osu.Game.Rulesets.Osu.Tests}/Resources/Testing/Beatmaps/slider-ticks.osu (100%) create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/uneven-repeat-slider-expected-conversion.json rename {osu.Game.Rulesets.Osu => osu.Game.Rulesets.Osu.Tests}/Resources/Testing/Beatmaps/uneven-repeat-slider.osu (100%) rename {osu.Game.Rulesets.Osu => osu.Game.Rulesets.Osu.Tests}/Resources/Testing/Beatmaps/very-fast-slider.osu (100%) rename {osu.Game.Rulesets.Osu => osu.Game.Rulesets.Osu.Tests}/Resources/Testing/Beatmaps/zero-length-sliders.osu (100%) delete mode 100644 osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/uneven-repeat-slider-expected-conversion.json diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs index 3e0a86d39c..4a217a19ea 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using NUnit.Framework; using osu.Framework.Utils; using osu.Game.Rulesets.Objects; @@ -15,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Tests [TestFixture] public class OsuBeatmapConversionTest : BeatmapConversionTest { - protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; + protected override string ResourceAssembly => "osu.Game.Rulesets.Osu.Tests"; [TestCase("basic")] [TestCase("colinear-perfect-curve")] @@ -27,6 +26,7 @@ namespace osu.Game.Rulesets.Osu.Tests [TestCase("old-stacking")] [TestCase("multi-segment-slider")] [TestCase("nan-slider")] + [TestCase("1124896")] public void Test(string name) => base.Test(name); protected override IEnumerable CreateConvertValue(HitObject hitObject) @@ -34,21 +34,8 @@ namespace osu.Game.Rulesets.Osu.Tests switch (hitObject) { case Slider slider: - var objects = new List(); - foreach (var nested in slider.NestedHitObjects) - objects.Add(createConvertValue((OsuHitObject)nested, slider)); - - // stable does slider tail leniency by offsetting the last tick 36ms back. - // based on player feedback, we're doing this a little different in lazer, - // and the lazer method does not require offsetting the last tick - // (see `DrawableSliderTail.CheckForResult()`). - // however, in conversion tests, just so the output matches, we're bringing - // the 36ms offset back locally. - // in particular, on some sliders, this may rearrange nested objects, - // so we sort them again by start time to prevent test failures. - foreach (var obj in objects.OrderBy(cv => cv.StartTime)) - yield return obj; + yield return createConvertValue((OsuHitObject)nested, slider); break; diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index fa7454b435..e35cf10d95 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Tests [TestFixture] public class OsuDifficultyCalculatorTest : DifficultyCalculatorTest { - protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; + protected override string ResourceAssembly => "osu.Game.Rulesets.Osu.Tests"; [TestCase(6.710442985146793d, 239, "diffcalc-test")] [TestCase(1.4386882251130073d, 54, "zero-length-sliders")] diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/1124896-expected-conversion.json b/osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/1124896-expected-conversion.json new file mode 100644 index 0000000000..68551d5d10 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/1124896-expected-conversion.json @@ -0,0 +1 @@ +{"Mappings":[{"StartTime":633.0,"Objects":[{"StartTime":633.0,"EndTime":633.0,"X":84.5217361,"Y":88.5217361}]},{"StartTime":844.0,"Objects":[{"StartTime":844.0,"EndTime":844.0,"X":88.2608643,"Y":92.2608643}]},{"StartTime":1055.0,"Objects":[{"StartTime":1055.0,"EndTime":1055.0,"X":92.0,"Y":96.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":1230.0,"EndTime":1230.0,"X":76.53984,"Y":161.705658,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":1477.0,"Objects":[{"StartTime":1477.0,"EndTime":1477.0,"X":200.0,"Y":100.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":1652.0,"EndTime":1652.0,"X":184.097,"Y":34.400116,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":1900.0,"Objects":[{"StartTime":1900.0,"EndTime":1900.0,"X":164.0,"Y":228.0}]},{"StartTime":2111.0,"Objects":[{"StartTime":2111.0,"EndTime":2111.0,"X":256.0,"Y":240.0}]},{"StartTime":2322.0,"Objects":[{"StartTime":2322.0,"EndTime":2322.0,"X":340.0,"Y":192.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":2497.0,"EndTime":2497.0,"X":350.197235,"Y":127.18325,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":2745.0,"Objects":[{"StartTime":2745.0,"EndTime":2745.0,"X":440.0,"Y":200.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":2920.0,"EndTime":2920.0,"X":450.363068,"Y":264.618042,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":3167.0,"Objects":[{"StartTime":3167.0,"EndTime":3167.0,"X":324.521729,"Y":308.521729}]},{"StartTime":3378.0,"Objects":[{"StartTime":3378.0,"EndTime":3378.0,"X":328.260864,"Y":312.260864,"StackOffset":{"X":-3.73913574,"Y":-3.73913574}},{"StartTime":3764.0,"EndTime":3764.0,"X":241.358566,"Y":327.7687,"StackOffset":{"X":-3.73913574,"Y":-3.73913574}}]},{"StartTime":4012.0,"Objects":[{"StartTime":4012.0,"EndTime":4012.0,"X":332.0,"Y":316.0}]},{"StartTime":4224.0,"Objects":[{"StartTime":4224.0,"EndTime":4224.0,"X":312.0,"Y":224.0}]},{"StartTime":4435.0,"Objects":[{"StartTime":4435.0,"EndTime":4435.0,"X":284.0,"Y":132.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":4610.0,"EndTime":4610.0,"X":218.719162,"Y":130.832062,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":4857.0,"Objects":[{"StartTime":4857.0,"EndTime":4857.0,"X":400.0,"Y":192.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":5032.0,"EndTime":5032.0,"X":465.280823,"Y":193.167923,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":5280.0,"Objects":[{"StartTime":5280.0,"EndTime":5280.0,"X":312.0,"Y":224.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":5455.0,"EndTime":5455.0,"X":310.832062,"Y":289.280823,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":5702.0,"Objects":[{"StartTime":5702.0,"EndTime":5702.0,"X":372.260864,"Y":104.260864}]},{"StartTime":5914.0,"Objects":[{"StartTime":5914.0,"EndTime":5914.0,"X":376.0,"Y":108.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":6300.0,"EndTime":6300.0,"X":249.910217,"Y":112.133125,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":6547.0,"Objects":[{"StartTime":6547.0,"EndTime":6547.0,"X":154.0,"Y":122.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":6722.0,"EndTime":6722.0,"X":171.671921,"Y":58.8828773,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":6970.0,"Objects":[{"StartTime":6970.0,"EndTime":6970.0,"X":107.0,"Y":195.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":7181.0,"EndTime":7181.0,"X":68.5987,"Y":143.051712,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":7356.0,"EndTime":7356.0,"X":107.0,"Y":195.0,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":7604.0,"Objects":[{"StartTime":7604.0,"EndTime":7604.0,"X":216.0,"Y":232.0}]},{"StartTime":7815.0,"Objects":[{"StartTime":7815.0,"EndTime":7815.0,"X":116.0,"Y":280.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":7990.0,"EndTime":7990.0,"X":53.6959572,"Y":265.658173,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":8238.0,"Objects":[{"StartTime":8238.0,"EndTime":8238.0,"X":176.0,"Y":160.0}]},{"StartTime":8449.0,"Objects":[{"StartTime":8449.0,"EndTime":8449.0,"X":248.0,"Y":291.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":8729.0,"EndTime":8729.0,"X":333.029968,"Y":327.610535,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":8871.0,"Objects":[{"StartTime":8871.0,"EndTime":8871.0,"X":334.0,"Y":328.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":9257.0,"EndTime":9257.0,"X":318.562378,"Y":193.885574,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":9505.0,"Objects":[{"StartTime":9505.0,"EndTime":9505.0,"X":428.0,"Y":184.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":9680.0,"EndTime":9680.0,"X":436.122375,"Y":251.009521,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":9928.0,"Objects":[{"StartTime":9928.0,"EndTime":9928.0,"X":328.0,"Y":128.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":10103.0,"EndTime":10103.0,"X":318.879852,"Y":194.881042,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":10350.0,"Objects":[{"StartTime":10350.0,"EndTime":10350.0,"X":320.0,"Y":108.0}]},{"StartTime":10773.0,"Objects":[{"StartTime":10773.0,"EndTime":10773.0,"X":308.0,"Y":88.0}]},{"StartTime":11195.0,"Objects":[{"StartTime":11195.0,"EndTime":11195.0,"X":296.0,"Y":68.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":11370.0,"EndTime":11370.0,"X":228.5764,"Y":64.78935,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":11618.0,"Objects":[{"StartTime":11618.0,"EndTime":11618.0,"X":318.0,"Y":194.0}]},{"StartTime":11829.0,"Objects":[{"StartTime":11829.0,"EndTime":11829.0,"X":288.0,"Y":52.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":12004.0,"EndTime":12004.0,"X":220.5764,"Y":48.7893524,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":12252.0,"Objects":[{"StartTime":12252.0,"EndTime":12252.0,"X":236.0,"Y":248.0}]},{"StartTime":12463.0,"Objects":[{"StartTime":12463.0,"EndTime":12463.0,"X":299.0,"Y":170.0}]},{"StartTime":12674.0,"Objects":[{"StartTime":12674.0,"EndTime":12674.0,"X":300.0,"Y":300.0}]},{"StartTime":12885.0,"Objects":[{"StartTime":12885.0,"EndTime":12885.0,"X":168.0,"Y":204.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":13096.0,"EndTime":13096.0,"X":100.5764,"Y":200.789352,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":13271.0,"EndTime":13271.0,"X":168.0,"Y":204.0,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":13519.0,"Objects":[{"StartTime":13519.0,"EndTime":13519.0,"X":227.0,"Y":332.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":13694.0,"EndTime":13694.0,"X":159.619965,"Y":336.022675,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":13942.0,"Objects":[{"StartTime":13942.0,"EndTime":13942.0,"X":299.260864,"Y":362.260864}]},{"StartTime":14153.0,"Objects":[{"StartTime":14153.0,"EndTime":14153.0,"X":302.0,"Y":365.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":14328.0,"EndTime":14328.0,"X":299.3276,"Y":299.703552,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":14576.0,"Objects":[{"StartTime":14576.0,"EndTime":14576.0,"X":469.0,"Y":258.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":14751.0,"EndTime":14751.0,"X":452.420563,"Y":331.144531,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":14998.0,"Objects":[{"StartTime":14998.0,"EndTime":14998.0,"X":376.0,"Y":256.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":15173.0,"EndTime":15173.0,"X":359.2077,"Y":182.904053,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":15421.0,"Objects":[{"StartTime":15421.0,"EndTime":15421.0,"X":384.0,"Y":80.0}]},{"StartTime":15632.0,"Objects":[{"StartTime":15632.0,"EndTime":15632.0,"X":282.0,"Y":102.0}]},{"StartTime":15843.0,"Objects":[{"StartTime":15843.0,"EndTime":15843.0,"X":436.0,"Y":148.0}]},{"StartTime":16055.0,"Objects":[{"StartTime":16055.0,"EndTime":16055.0,"X":266.521729,"Y":178.521729}]},{"StartTime":16160.0,"Objects":[{"StartTime":16160.0,"EndTime":16160.0,"X":270.260864,"Y":182.260864}]},{"StartTime":16266.0,"Objects":[{"StartTime":16266.0,"EndTime":16266.0,"X":274.0,"Y":186.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":16441.0,"EndTime":16441.0,"X":257.420563,"Y":259.144531,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":16688.0,"Objects":[{"StartTime":16688.0,"EndTime":16688.0,"X":160.0,"Y":202.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":16863.0,"EndTime":16863.0,"X":143.207687,"Y":128.904053,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":17111.0,"Objects":[{"StartTime":17111.0,"EndTime":17111.0,"X":79.0,"Y":35.0}]},{"StartTime":17322.0,"Objects":[{"StartTime":17322.0,"EndTime":17322.0,"X":23.0,"Y":123.0}]},{"StartTime":17533.0,"Objects":[{"StartTime":17533.0,"EndTime":17533.0,"X":161.0,"Y":42.0}]},{"StartTime":17745.0,"Objects":[{"StartTime":17745.0,"EndTime":17745.0,"X":76.0,"Y":188.0}]},{"StartTime":17956.0,"Objects":[{"StartTime":17956.0,"EndTime":17956.0,"X":79.0,"Y":35.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":18131.0,"EndTime":18131.0,"X":99.60409,"Y":107.114296,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":18378.0,"Objects":[{"StartTime":18378.0,"EndTime":18378.0,"X":211.0,"Y":104.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":18553.0,"EndTime":18553.0,"X":231.60408,"Y":176.114288,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":18801.0,"Objects":[{"StartTime":18801.0,"EndTime":18801.0,"X":344.0,"Y":170.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":18976.0,"EndTime":18976.0,"X":364.6041,"Y":242.114288,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":19224.0,"Objects":[{"StartTime":19224.0,"EndTime":19224.0,"X":433.0,"Y":132.0}]},{"StartTime":19435.0,"Objects":[{"StartTime":19435.0,"EndTime":19435.0,"X":364.521729,"Y":241.521729}]},{"StartTime":19540.0,"Objects":[{"StartTime":19540.0,"EndTime":19540.0,"X":368.260864,"Y":245.260864}]},{"StartTime":19646.0,"Objects":[{"StartTime":19646.0,"EndTime":19646.0,"X":372.0,"Y":249.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":19821.0,"EndTime":19821.0,"X":444.6992,"Y":253.148651,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":20069.0,"Objects":[{"StartTime":20069.0,"EndTime":20069.0,"X":468.0,"Y":104.0}]},{"StartTime":20280.0,"Objects":[{"StartTime":20280.0,"EndTime":20280.0,"X":413.0,"Y":180.0}]},{"StartTime":20491.0,"Objects":[{"StartTime":20491.0,"EndTime":20491.0,"X":324.0,"Y":58.0}]},{"StartTime":20702.0,"Objects":[{"StartTime":20702.0,"EndTime":20702.0,"X":414.0,"Y":31.0}]},{"StartTime":20914.0,"Objects":[{"StartTime":20914.0,"EndTime":20914.0,"X":324.0,"Y":151.0}]},{"StartTime":21125.0,"Objects":[{"StartTime":21125.0,"EndTime":21125.0,"X":244.0,"Y":40.0}]},{"StartTime":21336.0,"Objects":[{"StartTime":21336.0,"EndTime":21336.0,"X":301.0,"Y":186.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":21616.0,"EndTime":21616.0,"X":197.183792,"Y":187.195663,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":21759.0,"Objects":[{"StartTime":21759.0,"EndTime":21759.0,"X":197.0,"Y":187.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":21934.0,"EndTime":21934.0,"X":197.444717,"Y":260.028961,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":22181.0,"Objects":[{"StartTime":22181.0,"EndTime":22181.0,"X":287.0,"Y":362.0}]},{"StartTime":22393.0,"Objects":[{"StartTime":22393.0,"EndTime":22393.0,"X":330.0,"Y":234.0}]},{"StartTime":22604.0,"Objects":[{"StartTime":22604.0,"EndTime":22604.0,"X":197.0,"Y":260.0}]},{"StartTime":22815.0,"Objects":[{"StartTime":22815.0,"EndTime":22815.0,"X":356.260864,"Y":315.260864}]},{"StartTime":23026.0,"Objects":[{"StartTime":23026.0,"EndTime":23026.0,"X":360.0,"Y":319.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":23306.0,"EndTime":23306.0,"X":465.503235,"Y":323.503082,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":23449.0,"Objects":[{"StartTime":23449.0,"EndTime":23449.0,"X":468.739136,"Y":326.739136}]},{"StartTime":23660.0,"Objects":[{"StartTime":23660.0,"EndTime":23660.0,"X":398.260864,"Y":176.260864}]},{"StartTime":23871.0,"Objects":[{"StartTime":23871.0,"EndTime":23871.0,"X":402.0,"Y":180.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":24046.0,"EndTime":24046.0,"X":415.0339,"Y":253.858765,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":24294.0,"Objects":[{"StartTime":24294.0,"EndTime":24294.0,"X":314.0,"Y":145.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":24469.0,"EndTime":24469.0,"X":326.976959,"Y":71.13121,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":24716.0,"Objects":[{"StartTime":24716.0,"EndTime":24716.0,"X":472.0,"Y":72.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":24891.0,"EndTime":24891.0,"X":485.1493,"Y":145.838318,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":25139.0,"Objects":[{"StartTime":25139.0,"EndTime":25139.0,"X":320.0,"Y":222.0}]},{"StartTime":25350.0,"Objects":[{"StartTime":25350.0,"EndTime":25350.0,"X":235.0,"Y":116.0}]},{"StartTime":25562.0,"Objects":[{"StartTime":25562.0,"EndTime":25562.0,"X":276.0,"Y":295.0}]},{"StartTime":25667.0,"Objects":[{"StartTime":25667.0,"EndTime":25667.0,"X":304.0,"Y":305.0}]},{"StartTime":25773.0,"Objects":[{"StartTime":25773.0,"EndTime":25773.0,"X":333.0,"Y":306.0}]},{"StartTime":25878.0,"Objects":[{"StartTime":25878.0,"EndTime":25878.0,"X":362.0,"Y":299.0}]},{"StartTime":25984.0,"Objects":[{"StartTime":25984.0,"EndTime":25984.0,"X":392.0,"Y":280.0}]},{"StartTime":26090.0,"Objects":[{"StartTime":26090.0,"EndTime":26090.0,"X":425.0,"Y":239.0}]},{"StartTime":26195.0,"Objects":[{"StartTime":26195.0,"EndTime":26195.0,"X":447.0,"Y":193.0}]},{"StartTime":26301.0,"Objects":[{"StartTime":26301.0,"EndTime":26301.0,"X":454.0,"Y":143.0}]},{"StartTime":26407.0,"Objects":[{"StartTime":26407.0,"EndTime":26407.0,"X":452.0,"Y":88.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":26829.0,"EndTime":26829.0,"X":419.177216,"Y":32.9294777,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":27216.0,"EndTime":27216.0,"X":378.111816,"Y":82.11954,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":27463.0,"Objects":[{"StartTime":27463.0,"EndTime":27463.0,"X":368.0,"Y":160.0}]},{"StartTime":27674.0,"Objects":[{"StartTime":27674.0,"EndTime":27674.0,"X":487.0,"Y":58.0}]},{"StartTime":28097.0,"Objects":[{"StartTime":28097.0,"EndTime":28097.0,"X":300.0,"Y":200.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":28272.0,"EndTime":28272.0,"X":296.528,"Y":128.962769,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":28519.0,"Objects":[{"StartTime":28519.0,"EndTime":28519.0,"X":377.0,"Y":238.0}]},{"StartTime":28731.0,"Objects":[{"StartTime":28731.0,"EndTime":28731.0,"X":222.0,"Y":217.0}]},{"StartTime":28942.0,"Objects":[{"StartTime":28942.0,"EndTime":28942.0,"X":369.0,"Y":92.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":29117.0,"EndTime":29117.0,"X":365.6939,"Y":163.550735,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":29364.0,"Objects":[{"StartTime":29364.0,"EndTime":29364.0,"X":223.0,"Y":136.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":29539.0,"EndTime":29539.0,"X":224.683,"Y":64.56601,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":29787.0,"Objects":[{"StartTime":29787.0,"EndTime":29787.0,"X":251.0,"Y":276.0}]},{"StartTime":29998.0,"Objects":[{"StartTime":29998.0,"EndTime":29998.0,"X":135.0,"Y":240.0}]},{"StartTime":30209.0,"Objects":[{"StartTime":30209.0,"EndTime":30209.0,"X":244.0,"Y":356.0}]},{"StartTime":30421.0,"Objects":[{"StartTime":30421.0,"EndTime":30421.0,"X":137.0,"Y":161.0}]},{"StartTime":30632.0,"Objects":[{"StartTime":30632.0,"EndTime":30632.0,"X":166.0,"Y":327.0}]},{"StartTime":30843.0,"Objects":[{"StartTime":30843.0,"EndTime":30843.0,"X":219.0,"Y":187.0}]},{"StartTime":31055.0,"Objects":[{"StartTime":31055.0,"EndTime":31055.0,"X":68.0,"Y":322.0}]},{"StartTime":31266.0,"Objects":[{"StartTime":31266.0,"EndTime":31266.0,"X":311.0,"Y":192.0}]},{"StartTime":31477.0,"Objects":[{"StartTime":31477.0,"EndTime":31477.0,"X":140.0,"Y":89.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":31652.0,"EndTime":31652.0,"X":136.569946,"Y":160.058075,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":31899.0,"Objects":[{"StartTime":31899.0,"EndTime":31899.0,"X":217.0,"Y":51.0}]},{"StartTime":32111.0,"Objects":[{"StartTime":32111.0,"EndTime":32111.0,"X":62.0,"Y":72.0}]},{"StartTime":32322.0,"Objects":[{"StartTime":32322.0,"EndTime":32322.0,"X":209.0,"Y":197.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":32497.0,"EndTime":32497.0,"X":206.163559,"Y":125.298256,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":32744.0,"Objects":[{"StartTime":32744.0,"EndTime":32744.0,"X":64.0,"Y":168.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":32919.0,"EndTime":32919.0,"X":66.155014,"Y":239.272888,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":33167.0,"Objects":[{"StartTime":33167.0,"EndTime":33167.0,"X":209.0,"Y":197.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":33342.0,"EndTime":33342.0,"X":137.56601,"Y":198.683,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":33589.0,"Objects":[{"StartTime":33589.0,"EndTime":33589.0,"X":136.0,"Y":340.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":33764.0,"EndTime":33764.0,"X":207.453568,"Y":342.3376,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":34012.0,"Objects":[{"StartTime":34012.0,"EndTime":34012.0,"X":285.0,"Y":167.0}]},{"StartTime":34224.0,"Objects":[{"StartTime":34224.0,"EndTime":34224.0,"X":308.0,"Y":326.0}]},{"StartTime":34435.0,"Objects":[{"StartTime":34435.0,"EndTime":34435.0,"X":176.0,"Y":276.0}]},{"StartTime":34646.0,"Objects":[{"StartTime":34646.0,"EndTime":34646.0,"X":362.0,"Y":263.0}]},{"StartTime":34857.0,"Objects":[{"StartTime":34857.0,"EndTime":34857.0,"X":184.0,"Y":201.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":35032.0,"EndTime":35032.0,"X":175.4032,"Y":275.505676,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":35280.0,"Objects":[{"StartTime":35280.0,"EndTime":35280.0,"X":118.0,"Y":138.0}]},{"StartTime":35491.0,"Objects":[{"StartTime":35491.0,"EndTime":35491.0,"X":272.0,"Y":162.0}]},{"StartTime":35702.0,"Objects":[{"StartTime":35702.0,"EndTime":35702.0,"X":120.0,"Y":57.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":35877.0,"EndTime":35877.0,"X":164.450928,"Y":3.121443,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":36125.0,"Objects":[{"StartTime":36125.0,"EndTime":36125.0,"X":294.0,"Y":133.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":36300.0,"EndTime":36300.0,"X":247.996475,"Y":185.8328,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":36547.0,"Objects":[{"StartTime":36547.0,"EndTime":36547.0,"X":243.0,"Y":11.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":36722.0,"EndTime":36722.0,"X":296.045258,"Y":56.4152451,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":36970.0,"Objects":[{"StartTime":36970.0,"EndTime":36970.0,"X":171.0,"Y":183.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":37145.0,"EndTime":37145.0,"X":117.339569,"Y":137.949753,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":37393.0,"Objects":[{"StartTime":37393.0,"EndTime":37393.0,"X":368.0,"Y":94.0}]},{"StartTime":37604.0,"Objects":[{"StartTime":37604.0,"EndTime":37604.0,"X":228.0,"Y":243.0}]},{"StartTime":37815.0,"Objects":[{"StartTime":37815.0,"EndTime":37815.0,"X":222.0,"Y":94.0}]},{"StartTime":38026.0,"Objects":[{"StartTime":38026.0,"EndTime":38026.0,"X":374.0,"Y":238.0}]},{"StartTime":38238.0,"Objects":[{"StartTime":38238.0,"EndTime":38238.0,"X":368.0,"Y":94.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":38413.0,"EndTime":38413.0,"X":441.399017,"Y":109.413795,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":38660.0,"Objects":[{"StartTime":38660.0,"EndTime":38660.0,"X":240.0,"Y":170.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":38835.0,"EndTime":38835.0,"X":313.399017,"Y":185.413788,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":39083.0,"Objects":[{"StartTime":39083.0,"EndTime":39083.0,"X":110.0,"Y":240.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":39258.0,"EndTime":39258.0,"X":183.399017,"Y":255.413788,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":39505.0,"Objects":[{"StartTime":39505.0,"EndTime":39505.0,"X":106.0,"Y":321.0}]},{"StartTime":39716.0,"Objects":[{"StartTime":39716.0,"EndTime":39716.0,"X":148.0,"Y":159.0}]},{"StartTime":39928.0,"Objects":[{"StartTime":39928.0,"EndTime":39928.0,"X":35.0,"Y":279.0}]},{"StartTime":40139.0,"Objects":[{"StartTime":40139.0,"EndTime":40139.0,"X":213.0,"Y":325.0}]},{"StartTime":40350.0,"Objects":[{"StartTime":40350.0,"EndTime":40350.0,"X":61.0,"Y":312.0}]},{"StartTime":40561.0,"Objects":[{"StartTime":40561.0,"EndTime":40561.0,"X":237.0,"Y":299.0}]},{"StartTime":40773.0,"Objects":[{"StartTime":40773.0,"EndTime":40773.0,"X":120.0,"Y":92.0}]},{"StartTime":40878.0,"Objects":[{"StartTime":40878.0,"EndTime":40878.0,"X":124.0,"Y":129.0}]},{"StartTime":40984.0,"Objects":[{"StartTime":40984.0,"EndTime":40984.0,"X":128.0,"Y":166.0}]},{"StartTime":41089.0,"Objects":[{"StartTime":41089.0,"EndTime":41089.0,"X":132.0,"Y":203.0}]},{"StartTime":41195.0,"Objects":[{"StartTime":41195.0,"EndTime":41195.0,"X":136.0,"Y":241.0}]},{"StartTime":41407.0,"Objects":[{"StartTime":41407.0,"EndTime":41407.0,"X":273.521729,"Y":106.521736}]},{"StartTime":41512.0,"Objects":[{"StartTime":41512.0,"EndTime":41512.0,"X":277.260864,"Y":110.260864}]},{"StartTime":41618.0,"Objects":[{"StartTime":41618.0,"EndTime":41618.0,"X":281.0,"Y":114.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":41793.0,"EndTime":41793.0,"X":355.8014,"Y":108.545731,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":42040.0,"Objects":[{"StartTime":42040.0,"EndTime":42040.0,"X":292.0,"Y":34.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":42215.0,"EndTime":42215.0,"X":366.8014,"Y":28.54573,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":42463.0,"Objects":[{"StartTime":42463.0,"EndTime":42463.0,"X":400.0,"Y":177.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":42638.0,"EndTime":42638.0,"X":405.454285,"Y":251.8014,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":42885.0,"Objects":[{"StartTime":42885.0,"EndTime":42885.0,"X":480.0,"Y":188.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":43060.0,"EndTime":43060.0,"X":485.454285,"Y":262.8014,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":43308.0,"Objects":[{"StartTime":43308.0,"EndTime":43308.0,"X":330.0,"Y":317.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":43483.0,"EndTime":43483.0,"X":255.1986,"Y":311.545715,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":43730.0,"Objects":[{"StartTime":43730.0,"EndTime":43730.0,"X":319.0,"Y":237.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":43905.0,"EndTime":43905.0,"X":244.1986,"Y":231.545731,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":44153.0,"Objects":[{"StartTime":44153.0,"EndTime":44153.0,"X":129.0,"Y":357.0}]},{"StartTime":44364.0,"Objects":[{"StartTime":44364.0,"EndTime":44364.0,"X":43.0,"Y":239.0}]},{"StartTime":44576.0,"Objects":[{"StartTime":44576.0,"EndTime":44576.0,"X":181.0,"Y":284.0}]},{"StartTime":44787.0,"Objects":[{"StartTime":44787.0,"EndTime":44787.0,"X":43.0,"Y":329.0}]},{"StartTime":44998.0,"Objects":[{"StartTime":44998.0,"EndTime":44998.0,"X":129.0,"Y":211.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":45173.0,"EndTime":45173.0,"X":134.815765,"Y":136.22583,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":45421.0,"Objects":[{"StartTime":45421.0,"EndTime":45421.0,"X":224.0,"Y":157.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":45596.0,"EndTime":45596.0,"X":218.184235,"Y":82.22582,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":45843.0,"Objects":[{"StartTime":45843.0,"EndTime":45843.0,"X":312.0,"Y":60.0}]},{"StartTime":46055.0,"Objects":[{"StartTime":46055.0,"EndTime":46055.0,"X":414.0,"Y":106.0}]},{"StartTime":46266.0,"Objects":[{"StartTime":46266.0,"EndTime":46266.0,"X":401.0,"Y":1.0}]},{"StartTime":46477.0,"Objects":[{"StartTime":46477.0,"EndTime":46477.0,"X":302.521729,"Y":134.521729}]},{"StartTime":46583.0,"Objects":[{"StartTime":46583.0,"EndTime":46583.0,"X":306.260864,"Y":138.260864}]},{"StartTime":46688.0,"Objects":[{"StartTime":46688.0,"EndTime":46688.0,"X":310.0,"Y":142.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":46863.0,"EndTime":46863.0,"X":315.815765,"Y":216.77417,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":47111.0,"Objects":[{"StartTime":47111.0,"EndTime":47111.0,"X":405.0,"Y":196.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":47286.0,"EndTime":47286.0,"X":399.184235,"Y":270.77417,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":47533.0,"Objects":[{"StartTime":47533.0,"EndTime":47533.0,"X":280.0,"Y":288.0}]},{"StartTime":47745.0,"Objects":[{"StartTime":47745.0,"EndTime":47745.0,"X":388.0,"Y":352.0}]},{"StartTime":47956.0,"Objects":[{"StartTime":47956.0,"EndTime":47956.0,"X":492.0,"Y":176.0}]},{"StartTime":48167.0,"Objects":[{"StartTime":48167.0,"EndTime":48167.0,"X":465.0,"Y":312.0}]},{"StartTime":48378.0,"Objects":[{"StartTime":48378.0,"EndTime":48378.0,"X":315.0,"Y":216.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":48553.0,"EndTime":48553.0,"X":243.195923,"Y":215.908646,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":48801.0,"Objects":[{"StartTime":48801.0,"EndTime":48801.0,"X":280.0,"Y":288.0}]},{"StartTime":49012.0,"Objects":[{"StartTime":49012.0,"EndTime":49012.0,"X":392.0,"Y":188.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":49187.0,"EndTime":49187.0,"X":341.5537,"Y":136.966446,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":49435.0,"Objects":[{"StartTime":49435.0,"EndTime":49435.0,"X":472.0,"Y":212.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":49610.0,"EndTime":49610.0,"X":458.927246,"Y":141.03653,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":49857.0,"Objects":[{"StartTime":49857.0,"EndTime":49857.0,"X":399.0,"Y":270.0}]},{"StartTime":50069.0,"Objects":[{"StartTime":50069.0,"EndTime":50069.0,"X":341.0,"Y":136.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":50244.0,"EndTime":50244.0,"X":352.818542,"Y":61.9370422,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":50491.0,"Objects":[{"StartTime":50491.0,"EndTime":50491.0,"X":430.0,"Y":31.0}]},{"StartTime":50702.0,"Objects":[{"StartTime":50702.0,"EndTime":50702.0,"X":274.0,"Y":83.0}]},{"StartTime":50914.0,"Objects":[{"StartTime":50914.0,"EndTime":50914.0,"X":423.0,"Y":111.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":51089.0,"EndTime":51089.0,"X":497.184875,"Y":122.027481,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":51336.0,"Objects":[{"StartTime":51336.0,"EndTime":51336.0,"X":338.0,"Y":215.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":51511.0,"EndTime":51511.0,"X":407.975128,"Y":188.0096,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":51759.0,"Objects":[{"StartTime":51759.0,"EndTime":51759.0,"X":282.0,"Y":268.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":51934.0,"EndTime":51934.0,"X":262.7776,"Y":198.471313,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":52181.0,"Objects":[{"StartTime":52181.0,"EndTime":52181.0,"X":358.0,"Y":289.0}]},{"StartTime":52393.0,"Objects":[{"StartTime":52393.0,"EndTime":52393.0,"X":184.0,"Y":202.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":52568.0,"EndTime":52568.0,"X":218.515137,"Y":138.736755,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":52815.0,"Objects":[{"StartTime":52815.0,"EndTime":52815.0,"X":190.0,"Y":281.0}]},{"StartTime":53026.0,"Objects":[{"StartTime":53026.0,"EndTime":53026.0,"X":119.0,"Y":158.0}]},{"StartTime":53238.0,"Objects":[{"StartTime":53238.0,"EndTime":53238.0,"X":262.0,"Y":200.0}]},{"StartTime":53449.0,"Objects":[{"StartTime":53449.0,"EndTime":53449.0,"X":99.0,"Y":230.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":53624.0,"EndTime":53624.0,"X":118.7338,"Y":157.642715,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":53871.0,"Objects":[{"StartTime":53871.0,"EndTime":53871.0,"X":31.0,"Y":295.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":54046.0,"EndTime":54046.0,"X":11.2661953,"Y":222.642715,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":54294.0,"Objects":[{"StartTime":54294.0,"EndTime":54294.0,"X":131.0,"Y":316.0}]},{"StartTime":54505.0,"Objects":[{"StartTime":54505.0,"EndTime":54505.0,"X":222.0,"Y":242.0}]},{"StartTime":54716.0,"Objects":[{"StartTime":54716.0,"EndTime":54716.0,"X":110.521736,"Y":149.521729}]},{"StartTime":54822.0,"Objects":[{"StartTime":54822.0,"EndTime":54822.0,"X":114.260864,"Y":153.260864}]},{"StartTime":54928.0,"Objects":[{"StartTime":54928.0,"EndTime":54928.0,"X":118.0,"Y":157.0}]},{"StartTime":55139.0,"Objects":[{"StartTime":55139.0,"EndTime":55139.0,"X":226.0,"Y":332.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":55419.0,"EndTime":55419.0,"X":332.02774,"Y":333.580322,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":55562.0,"Objects":[{"StartTime":55562.0,"EndTime":55562.0,"X":332.0,"Y":333.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":55737.0,"EndTime":55737.0,"X":347.450775,"Y":259.608765,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":55984.0,"Objects":[{"StartTime":55984.0,"EndTime":55984.0,"X":289.0,"Y":191.0}]},{"StartTime":56195.0,"Objects":[{"StartTime":56195.0,"EndTime":56195.0,"X":338.0,"Y":116.0}]},{"StartTime":56407.0,"Objects":[{"StartTime":56407.0,"EndTime":56407.0,"X":427.0,"Y":103.0}]},{"StartTime":56618.0,"Objects":[{"StartTime":56618.0,"EndTime":56618.0,"X":502.0,"Y":151.0}]},{"StartTime":56829.0,"Objects":[{"StartTime":56829.0,"EndTime":56829.0,"X":371.0,"Y":38.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":57109.0,"EndTime":57109.0,"X":264.9723,"Y":36.41969,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":57252.0,"Objects":[{"StartTime":57252.0,"EndTime":57252.0,"X":265.0,"Y":37.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":57427.0,"EndTime":57427.0,"X":249.54921,"Y":110.391235,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":57674.0,"Objects":[{"StartTime":57674.0,"EndTime":57674.0,"X":132.0,"Y":25.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":57990.0,"EndTime":57990.0,"X":155.7147,"Y":134.790283,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":58271.0,"EndTime":58271.0,"X":132.0,"Y":25.0,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":58519.0,"Objects":[{"StartTime":58519.0,"EndTime":58519.0,"X":79.0,"Y":150.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":58799.0,"EndTime":58799.0,"X":158.959457,"Y":212.030838,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":58942.0,"Objects":[{"StartTime":58942.0,"EndTime":58942.0,"X":158.0,"Y":212.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":59117.0,"EndTime":59117.0,"X":231.232117,"Y":195.811844,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":59364.0,"Objects":[{"StartTime":59364.0,"EndTime":59364.0,"X":249.0,"Y":110.0}]},{"StartTime":59575.0,"Objects":[{"StartTime":59575.0,"EndTime":59575.0,"X":324.0,"Y":159.0}]},{"StartTime":59787.0,"Objects":[{"StartTime":59787.0,"EndTime":59787.0,"X":337.0,"Y":248.0}]},{"StartTime":59998.0,"Objects":[{"StartTime":59998.0,"EndTime":59998.0,"X":289.0,"Y":323.0}]},{"StartTime":60209.0,"Objects":[{"StartTime":60209.0,"EndTime":60209.0,"X":406.0,"Y":192.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":60489.0,"EndTime":60489.0,"X":468.030823,"Y":271.959473,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":60632.0,"Objects":[{"StartTime":60632.0,"EndTime":60632.0,"X":469.0,"Y":272.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":60807.0,"EndTime":60807.0,"X":451.908661,"Y":345.0266,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":61055.0,"Objects":[{"StartTime":61055.0,"EndTime":61055.0,"X":337.0,"Y":248.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":61371.0,"EndTime":61371.0,"X":359.946777,"Y":357.953369,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":61652.0,"EndTime":61652.0,"X":337.0,"Y":248.0,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":61900.0,"Objects":[{"StartTime":61900.0,"EndTime":61900.0,"X":232.0,"Y":195.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":62075.0,"EndTime":62075.0,"X":214.908661,"Y":268.0266,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":62322.0,"Objects":[{"StartTime":62322.0,"EndTime":62322.0,"X":129.0,"Y":122.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":62497.0,"EndTime":62497.0,"X":145.792313,"Y":195.095947,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":62745.0,"Objects":[{"StartTime":62745.0,"EndTime":62745.0,"X":177.0,"Y":358.0}]},{"StartTime":62956.0,"Objects":[{"StartTime":62956.0,"EndTime":62956.0,"X":108.0,"Y":282.0}]},{"StartTime":63167.0,"Objects":[{"StartTime":63167.0,"EndTime":63167.0,"X":286.0,"Y":341.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":63342.0,"EndTime":63342.0,"X":359.260956,"Y":357.0572,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":63590.0,"Objects":[{"StartTime":63590.0,"EndTime":63590.0,"X":410.0,"Y":231.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":63765.0,"EndTime":63765.0,"X":336.693939,"Y":246.84996,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":64012.0,"Objects":[{"StartTime":64012.0,"EndTime":64012.0,"X":465.0,"Y":158.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":64187.0,"EndTime":64187.0,"X":391.904053,"Y":141.207687,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":64435.0,"Objects":[{"StartTime":64435.0,"EndTime":64435.0,"X":226.0,"Y":111.0}]},{"StartTime":64646.0,"Objects":[{"StartTime":64646.0,"EndTime":64646.0,"X":320.0,"Y":175.0}]},{"StartTime":64857.0,"Objects":[{"StartTime":64857.0,"EndTime":64857.0,"X":222.0,"Y":34.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":65032.0,"EndTime":65032.0,"X":162.249863,"Y":68.4071,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":65280.0,"Objects":[{"StartTime":65280.0,"EndTime":65280.0,"X":218.0,"Y":189.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":65455.0,"EndTime":65455.0,"X":158.249863,"Y":154.592911,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":65702.0,"Objects":[{"StartTime":65702.0,"EndTime":65702.0,"X":296.0,"Y":70.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":65877.0,"EndTime":65877.0,"X":276.006042,"Y":142.285828,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":66125.0,"Objects":[{"StartTime":66125.0,"EndTime":66125.0,"X":236.0,"Y":337.0}]},{"StartTime":66336.0,"Objects":[{"StartTime":66336.0,"EndTime":66336.0,"X":325.0,"Y":219.0}]},{"StartTime":66547.0,"Objects":[{"StartTime":66547.0,"EndTime":66547.0,"X":152.0,"Y":247.0}]},{"StartTime":66758.0,"Objects":[{"StartTime":66758.0,"EndTime":66758.0,"X":316.0,"Y":312.0}]},{"StartTime":66970.0,"Objects":[{"StartTime":66970.0,"EndTime":66970.0,"X":88.0,"Y":184.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":67145.0,"EndTime":67145.0,"X":28.2498646,"Y":218.4071,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":67392.0,"Objects":[{"StartTime":67392.0,"EndTime":67392.0,"X":172.0,"Y":320.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":67567.0,"EndTime":67567.0,"X":152.006042,"Y":247.714172,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":67815.0,"Objects":[{"StartTime":67815.0,"EndTime":67815.0,"X":194.0,"Y":118.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":67990.0,"EndTime":67990.0,"X":127.445862,"Y":99.08952,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":68238.0,"Objects":[{"StartTime":68238.0,"EndTime":68238.0,"X":297.0,"Y":315.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":68413.0,"EndTime":68413.0,"X":277.006042,"Y":242.714172,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":68660.0,"Objects":[{"StartTime":68660.0,"EndTime":68660.0,"X":300.0,"Y":75.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":68835.0,"EndTime":68835.0,"X":277.048523,"Y":162.0243,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":68977.0,"Objects":[{"StartTime":68977.0,"EndTime":68977.0,"X":337.0,"Y":56.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":69152.0,"EndTime":69152.0,"X":314.048523,"Y":143.0243,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":69294.0,"Objects":[{"StartTime":69294.0,"EndTime":69294.0,"X":374.0,"Y":43.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":69363.0,"EndTime":69363.0,"X":353.9267,"Y":115.263847,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":69505.0,"Objects":[{"StartTime":69505.0,"EndTime":69505.0,"X":385.0,"Y":192.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":69680.0,"EndTime":69680.0,"X":470.1033,"Y":203.038986,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":69822.0,"Objects":[{"StartTime":69822.0,"EndTime":69822.0,"X":360.0,"Y":235.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":69997.0,"EndTime":69997.0,"X":444.7288,"Y":245.275024,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":70139.0,"Objects":[{"StartTime":70139.0,"EndTime":70139.0,"X":341.0,"Y":274.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":70208.0,"EndTime":70208.0,"X":412.045074,"Y":278.015778,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":70350.0,"Objects":[{"StartTime":70350.0,"EndTime":70350.0,"X":245.0,"Y":332.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":70525.0,"EndTime":70525.0,"X":238.370941,"Y":249.928986,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":70667.0,"Objects":[{"StartTime":70667.0,"EndTime":70667.0,"X":185.0,"Y":311.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":70842.0,"EndTime":70842.0,"X":238.16449,"Y":248.16507,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":70984.0,"Objects":[{"StartTime":70984.0,"EndTime":70984.0,"X":169.0,"Y":248.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":71053.0,"EndTime":71053.0,"X":237.883636,"Y":247.620834,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":71195.0,"Objects":[{"StartTime":71195.0,"EndTime":71195.0,"X":78.0,"Y":207.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":71370.0,"EndTime":71370.0,"X":63.43404,"Y":122.660629,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":71512.0,"Objects":[{"StartTime":71512.0,"EndTime":71512.0,"X":108.0,"Y":176.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":71687.0,"EndTime":71687.0,"X":93.43404,"Y":91.66063,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":71829.0,"Objects":[{"StartTime":71829.0,"EndTime":71829.0,"X":143.0,"Y":143.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":71898.0,"EndTime":71898.0,"X":131.188721,"Y":73.56615,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":72040.0,"Objects":[{"StartTime":72040.0,"EndTime":72040.0,"X":307.0,"Y":58.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":72215.0,"EndTime":72215.0,"X":225.182,"Y":43.19644,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":72357.0,"Objects":[{"StartTime":72357.0,"EndTime":72357.0,"X":388.0,"Y":72.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":72532.0,"EndTime":72532.0,"X":306.182,"Y":57.1964378,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":72674.0,"Objects":[{"StartTime":72674.0,"EndTime":72674.0,"X":454.0,"Y":91.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":72743.0,"EndTime":72743.0,"X":387.1621,"Y":71.76814,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":72885.0,"Objects":[{"StartTime":72885.0,"EndTime":72885.0,"X":338.0,"Y":180.0}]},{"StartTime":73097.0,"Objects":[{"StartTime":73097.0,"EndTime":73097.0,"X":269.0,"Y":308.0}]},{"StartTime":73202.0,"Objects":[{"StartTime":73202.0,"EndTime":73202.0,"X":304.0,"Y":334.0}]},{"StartTime":73308.0,"Objects":[{"StartTime":73308.0,"EndTime":73308.0,"X":348.0,"Y":344.0}]},{"StartTime":73414.0,"Objects":[{"StartTime":73414.0,"EndTime":73414.0,"X":391.0,"Y":335.0}]},{"StartTime":73519.0,"Objects":[{"StartTime":73519.0,"EndTime":73519.0,"X":428.0,"Y":309.0}]},{"StartTime":73625.0,"Objects":[{"StartTime":73625.0,"EndTime":73625.0,"X":450.0,"Y":271.0}]},{"StartTime":73730.0,"Objects":[{"StartTime":73730.0,"EndTime":73730.0,"X":453.0,"Y":227.0}]},{"StartTime":74576.0,"Objects":[{"StartTime":74576.0,"EndTime":74576.0,"X":453.0,"Y":227.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":74611.0,"EndTime":74611.0,"X":475.4206,"Y":227.605957,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":74646.0,"EndTime":74646.0,"X":453.142365,"Y":227.003845,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":74681.0,"EndTime":74681.0,"X":475.278259,"Y":227.602112,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":74716.0,"EndTime":74716.0,"X":453.2847,"Y":227.00769,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":74752.0,"EndTime":74752.0,"X":475.2071,"Y":227.600189,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":74787.0,"EndTime":74787.0,"X":453.213531,"Y":227.005768,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":74822.0,"EndTime":74822.0,"X":475.349426,"Y":227.604034,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":74857.0,"EndTime":74857.0,"X":453.071167,"Y":227.001923,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":74856.0,"EndTime":74856.0,"X":475.4918,"Y":227.60788,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":74998.0,"Objects":[{"StartTime":74998.0,"EndTime":74998.0,"X":506.0,"Y":152.0}]},{"StartTime":75421.0,"Objects":[{"StartTime":75421.0,"EndTime":75421.0,"X":222.0,"Y":89.0}]},{"StartTime":75632.0,"Objects":[{"StartTime":75632.0,"EndTime":75632.0,"X":194.0,"Y":259.0}]},{"StartTime":75843.0,"Objects":[{"StartTime":75843.0,"EndTime":75843.0,"X":320.0,"Y":218.0}]},{"StartTime":76054.0,"Objects":[{"StartTime":76054.0,"EndTime":76054.0,"X":150.0,"Y":190.0}]},{"StartTime":76266.0,"Objects":[{"StartTime":76266.0,"EndTime":76266.0,"X":339.0,"Y":335.0}]},{"StartTime":76477.0,"Objects":[{"StartTime":76477.0,"EndTime":76477.0,"X":372.0,"Y":130.0}]},{"StartTime":76688.0,"Objects":[{"StartTime":76688.0,"EndTime":76688.0,"X":221.0,"Y":180.0}]},{"StartTime":76899.0,"Objects":[{"StartTime":76899.0,"EndTime":76899.0,"X":425.0,"Y":212.0}]},{"StartTime":77111.0,"Objects":[{"StartTime":77111.0,"EndTime":77111.0,"X":285.0,"Y":121.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":77286.0,"EndTime":77286.0,"X":371.8806,"Y":129.901413,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":77533.0,"Objects":[{"StartTime":77533.0,"EndTime":77533.0,"X":194.0,"Y":259.0}]},{"StartTime":77745.0,"Objects":[{"StartTime":77745.0,"EndTime":77745.0,"X":323.0,"Y":182.0}]},{"StartTime":77956.0,"Objects":[{"StartTime":77956.0,"EndTime":77956.0,"X":244.0,"Y":316.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":78131.0,"EndTime":78131.0,"X":154.157745,"Y":324.1849,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":78378.0,"Objects":[{"StartTime":78378.0,"EndTime":78378.0,"X":245.0,"Y":179.0}]},{"StartTime":78590.0,"Objects":[{"StartTime":78590.0,"EndTime":78590.0,"X":350.0,"Y":277.0}]},{"StartTime":78801.0,"Objects":[{"StartTime":78801.0,"EndTime":78801.0,"X":160.0,"Y":228.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":79081.0,"EndTime":79081.0,"X":163.6551,"Y":81.7956848,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":79224.0,"Objects":[{"StartTime":79224.0,"EndTime":79224.0,"X":194.0,"Y":90.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":79399.0,"EndTime":79399.0,"X":283.264221,"Y":89.8079147,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":79646.0,"Objects":[{"StartTime":79646.0,"EndTime":79646.0,"X":129.0,"Y":0.0}]},{"StartTime":79857.0,"Objects":[{"StartTime":79857.0,"EndTime":79857.0,"X":22.0,"Y":146.0}]},{"StartTime":80069.0,"Objects":[{"StartTime":80069.0,"EndTime":80069.0,"X":194.0,"Y":90.0}]},{"StartTime":80280.0,"Objects":[{"StartTime":80280.0,"EndTime":80280.0,"X":22.0,"Y":33.0}]},{"StartTime":80491.0,"Objects":[{"StartTime":80491.0,"EndTime":80491.0,"X":129.0,"Y":180.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":80666.0,"EndTime":80666.0,"X":219.221863,"Y":178.1168,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":80913.0,"Objects":[{"StartTime":80913.0,"EndTime":80913.0,"X":308.0,"Y":80.0}]},{"StartTime":81125.0,"Objects":[{"StartTime":81125.0,"EndTime":81125.0,"X":280.0,"Y":252.0}]},{"StartTime":81336.0,"Objects":[{"StartTime":81336.0,"EndTime":81336.0,"X":446.0,"Y":206.0}]},{"StartTime":81547.0,"Objects":[{"StartTime":81547.0,"EndTime":81547.0,"X":339.0,"Y":60.0}]},{"StartTime":81759.0,"Objects":[{"StartTime":81759.0,"EndTime":81759.0,"X":511.0,"Y":116.0}]},{"StartTime":81970.0,"Objects":[{"StartTime":81970.0,"EndTime":81970.0,"X":339.0,"Y":173.0}]},{"StartTime":82181.0,"Objects":[{"StartTime":82181.0,"EndTime":82181.0,"X":446.0,"Y":26.0}]},{"StartTime":82393.0,"Objects":[{"StartTime":82393.0,"EndTime":82393.0,"X":280.0,"Y":118.0}]},{"StartTime":82604.0,"Objects":[{"StartTime":82604.0,"EndTime":82604.0,"X":435.0,"Y":118.0}]},{"StartTime":82816.0,"Objects":[{"StartTime":82816.0,"EndTime":82816.0,"X":259.0,"Y":26.0}]},{"StartTime":83026.0,"Objects":[{"StartTime":83026.0,"EndTime":83026.0,"X":339.0,"Y":173.0}]},{"StartTime":83238.0,"Objects":[{"StartTime":83238.0,"EndTime":83238.0,"X":154.0,"Y":128.0}]},{"StartTime":83449.0,"Objects":[{"StartTime":83449.0,"EndTime":83449.0,"X":304.0,"Y":88.0}]},{"StartTime":83661.0,"Objects":[{"StartTime":83661.0,"EndTime":83661.0,"X":157.0,"Y":222.0}]},{"StartTime":83871.0,"Objects":[{"StartTime":83871.0,"EndTime":83871.0,"X":352.0,"Y":280.0}]},{"StartTime":84083.0,"Objects":[{"StartTime":84083.0,"EndTime":84083.0,"X":160.0,"Y":173.0}]},{"StartTime":84294.0,"Objects":[{"StartTime":84294.0,"EndTime":84294.0,"X":339.0,"Y":173.0}]},{"StartTime":84506.0,"Objects":[{"StartTime":84506.0,"EndTime":84506.0,"X":135.0,"Y":280.0}]},{"StartTime":84716.0,"Objects":[{"StartTime":84716.0,"EndTime":84716.0,"X":259.0,"Y":130.0}]},{"StartTime":84928.0,"Objects":[{"StartTime":84928.0,"EndTime":84928.0,"X":65.0,"Y":235.0}]},{"StartTime":85139.0,"Objects":[{"StartTime":85139.0,"EndTime":85139.0,"X":244.0,"Y":235.0}]},{"StartTime":85351.0,"Objects":[{"StartTime":85351.0,"EndTime":85351.0,"X":40.0,"Y":129.0}]},{"StartTime":85562.0,"Objects":[{"StartTime":85562.0,"EndTime":85562.0,"X":300.0,"Y":92.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":85737.0,"EndTime":85737.0,"X":277.179749,"Y":186.7918,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":85984.0,"Objects":[{"StartTime":85984.0,"EndTime":85984.0,"X":192.0,"Y":43.0}]},{"StartTime":86195.0,"Objects":[{"StartTime":86195.0,"EndTime":86195.0,"X":361.0,"Y":34.0}]},{"StartTime":86407.0,"Objects":[{"StartTime":86407.0,"EndTime":86407.0,"X":327.0,"Y":233.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":86582.0,"EndTime":86582.0,"X":232.2082,"Y":210.179749,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":86829.0,"Objects":[{"StartTime":86829.0,"EndTime":86829.0,"X":376.0,"Y":125.0}]},{"StartTime":87040.0,"Objects":[{"StartTime":87040.0,"EndTime":87040.0,"X":385.0,"Y":294.0}]},{"StartTime":87252.0,"Objects":[{"StartTime":87252.0,"EndTime":87252.0,"X":195.0,"Y":265.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":87427.0,"EndTime":87427.0,"X":217.820251,"Y":170.2082,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":87674.0,"Objects":[{"StartTime":87674.0,"EndTime":87674.0,"X":303.0,"Y":314.0}]},{"StartTime":87885.0,"Objects":[{"StartTime":87885.0,"EndTime":87885.0,"X":134.0,"Y":323.0}]},{"StartTime":88097.0,"Objects":[{"StartTime":88097.0,"EndTime":88097.0,"X":177.0,"Y":108.0}]},{"StartTime":88202.0,"Objects":[{"StartTime":88202.0,"EndTime":88202.0,"X":223.0,"Y":95.0}]},{"StartTime":88308.0,"Objects":[{"StartTime":88308.0,"EndTime":88308.0,"X":267.0,"Y":114.0}]},{"StartTime":88413.0,"Objects":[{"StartTime":88413.0,"EndTime":88413.0,"X":291.0,"Y":155.0}]},{"StartTime":88519.0,"Objects":[{"StartTime":88519.0,"EndTime":88519.0,"X":284.0,"Y":203.0}]},{"StartTime":88731.0,"Objects":[{"StartTime":88731.0,"EndTime":88731.0,"X":102.0,"Y":204.0}]},{"StartTime":88942.0,"Objects":[{"StartTime":88942.0,"EndTime":88942.0,"X":224.0,"Y":16.0}]},{"StartTime":89153.0,"Objects":[{"StartTime":89153.0,"EndTime":89153.0,"X":207.0,"Y":200.0}]},{"StartTime":89364.0,"Objects":[{"StartTime":89364.0,"EndTime":89364.0,"X":96.0,"Y":112.0}]},{"StartTime":89575.0,"Objects":[{"StartTime":89575.0,"EndTime":89575.0,"X":113.0,"Y":296.0}]},{"StartTime":89787.0,"Objects":[{"StartTime":89787.0,"EndTime":89787.0,"X":0.0,"Y":152.0}]},{"StartTime":89998.0,"Objects":[{"StartTime":89998.0,"EndTime":89998.0,"X":184.0,"Y":169.0}]},{"StartTime":90209.0,"Objects":[{"StartTime":90209.0,"EndTime":90209.0,"X":16.0,"Y":296.0}]},{"StartTime":90420.0,"Objects":[{"StartTime":90420.0,"EndTime":90420.0,"X":211.0,"Y":242.0}]},{"StartTime":90632.0,"Objects":[{"StartTime":90632.0,"EndTime":90632.0,"X":88.0,"Y":52.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":90807.0,"EndTime":90807.0,"X":78.2983856,"Y":149.016129,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":91055.0,"Objects":[{"StartTime":91055.0,"EndTime":91055.0,"X":231.0,"Y":2.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":91230.0,"EndTime":91230.0,"X":173.4124,"Y":80.6760254,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":91477.0,"Objects":[{"StartTime":91477.0,"EndTime":91477.0,"X":383.0,"Y":22.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":91652.0,"EndTime":91652.0,"X":293.9368,"Y":61.67361,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":91900.0,"Objects":[{"StartTime":91900.0,"EndTime":91900.0,"X":491.0,"Y":110.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":92075.0,"EndTime":92075.0,"X":393.715942,"Y":103.5144,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":92322.0,"Objects":[{"StartTime":92322.0,"EndTime":92322.0,"X":436.0,"Y":284.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":92497.0,"EndTime":92497.0,"X":441.562347,"Y":186.658783,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":92745.0,"Objects":[{"StartTime":92745.0,"EndTime":92745.0,"X":300.260864,"Y":155.260864}]},{"StartTime":92956.0,"Objects":[{"StartTime":92956.0,"EndTime":92956.0,"X":304.0,"Y":159.0}]},{"StartTime":93167.0,"Objects":[{"StartTime":93167.0,"EndTime":93167.0,"X":412.0,"Y":328.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":93342.0,"EndTime":93342.0,"X":417.562347,"Y":230.658783,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":93590.0,"Objects":[{"StartTime":93590.0,"EndTime":93590.0,"X":288.260864,"Y":172.260864}]},{"StartTime":93801.0,"Objects":[{"StartTime":93801.0,"EndTime":93801.0,"X":292.0,"Y":176.0}]},{"StartTime":94012.0,"Objects":[{"StartTime":94012.0,"EndTime":94012.0,"X":392.0,"Y":364.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":94187.0,"EndTime":94187.0,"X":397.562347,"Y":266.658783,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":94435.0,"Objects":[{"StartTime":94435.0,"EndTime":94435.0,"X":276.260864,"Y":192.260864}]},{"StartTime":94646.0,"Objects":[{"StartTime":94646.0,"EndTime":94646.0,"X":280.0,"Y":196.0}]},{"StartTime":94857.0,"Objects":[{"StartTime":94857.0,"EndTime":94857.0,"X":160.0,"Y":155.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":95032.0,"EndTime":95032.0,"X":167.9152,"Y":243.954712,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":95280.0,"Objects":[{"StartTime":95280.0,"EndTime":95280.0,"X":424.0,"Y":112.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":95455.0,"EndTime":95455.0,"X":416.084778,"Y":23.0452919,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":95702.0,"Objects":[{"StartTime":95702.0,"EndTime":95702.0,"X":224.0,"Y":192.0}]},{"StartTime":95913.0,"Objects":[{"StartTime":95913.0,"EndTime":95913.0,"X":421.0,"Y":192.0}]},{"StartTime":96125.0,"Objects":[{"StartTime":96125.0,"EndTime":96125.0,"X":280.0,"Y":56.0}]},{"StartTime":96336.0,"Objects":[{"StartTime":96336.0,"EndTime":96336.0,"X":280.0,"Y":253.0}]},{"StartTime":96547.0,"Objects":[{"StartTime":96547.0,"EndTime":96547.0,"X":431.0,"Y":112.0}]},{"StartTime":96758.0,"Objects":[{"StartTime":96758.0,"EndTime":96758.0,"X":195.0,"Y":112.0}]},{"StartTime":96970.0,"Objects":[{"StartTime":96970.0,"EndTime":96970.0,"X":364.0,"Y":268.0}]},{"StartTime":97181.0,"Objects":[{"StartTime":97181.0,"EndTime":97181.0,"X":364.0,"Y":32.0}]},{"StartTime":97393.0,"Objects":[{"StartTime":97393.0,"EndTime":97393.0,"X":176.0,"Y":264.0}]},{"StartTime":97604.0,"Objects":[{"StartTime":97604.0,"EndTime":97604.0,"X":426.0,"Y":108.0}]},{"StartTime":97815.0,"Objects":[{"StartTime":97815.0,"EndTime":97815.0,"X":200.0,"Y":184.0}]},{"StartTime":98026.0,"Objects":[{"StartTime":98026.0,"EndTime":98026.0,"X":459.0,"Y":264.0}]},{"StartTime":98238.0,"Objects":[{"StartTime":98238.0,"EndTime":98238.0,"X":200.0,"Y":108.0}]},{"StartTime":98449.0,"Objects":[{"StartTime":98449.0,"EndTime":98449.0,"X":426.0,"Y":184.0}]},{"StartTime":98660.0,"Objects":[{"StartTime":98660.0,"EndTime":98660.0,"X":164.0,"Y":32.0}]},{"StartTime":98871.0,"Objects":[{"StartTime":98871.0,"EndTime":98871.0,"X":447.0,"Y":32.0}]},{"StartTime":99083.0,"Objects":[{"StartTime":99083.0,"EndTime":99083.0,"X":312.0,"Y":264.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":99258.0,"EndTime":99258.0,"X":305.2918,"Y":166.731049,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":99505.0,"Objects":[{"StartTime":99505.0,"EndTime":99505.0,"X":412.0,"Y":236.0}]},{"StartTime":99716.0,"Objects":[{"StartTime":99716.0,"EndTime":99716.0,"X":224.0,"Y":224.0}]},{"StartTime":99928.0,"Objects":[{"StartTime":99928.0,"EndTime":99928.0,"X":420.0,"Y":144.0}]},{"StartTime":100139.0,"Objects":[{"StartTime":100139.0,"EndTime":100139.0,"X":408.0,"Y":332.0}]},{"StartTime":100350.0,"Objects":[{"StartTime":100350.0,"EndTime":100350.0,"X":252.0,"Y":136.0}]},{"StartTime":100561.0,"Objects":[{"StartTime":100561.0,"EndTime":100561.0,"X":191.0,"Y":314.0}]},{"StartTime":100773.0,"Objects":[{"StartTime":100773.0,"EndTime":100773.0,"X":412.0,"Y":236.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":100948.0,"EndTime":100948.0,"X":487.0,"Y":236.0,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":101195.0,"Objects":[{"StartTime":101195.0,"EndTime":101195.0,"X":348.0,"Y":288.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":101370.0,"EndTime":101370.0,"X":273.0,"Y":288.0,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":101618.0,"Objects":[{"StartTime":101618.0,"EndTime":101618.0,"X":415.0,"Y":339.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":101898.0,"EndTime":101898.0,"X":411.2817,"Y":235.5634,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":102040.0,"Objects":[{"StartTime":102040.0,"EndTime":102040.0,"X":414.739136,"Y":238.739136}]},{"StartTime":102252.0,"Objects":[{"StartTime":102252.0,"EndTime":102252.0,"X":339.521729,"Y":119.521736}]},{"StartTime":102357.0,"Objects":[{"StartTime":102357.0,"EndTime":102357.0,"X":343.260864,"Y":123.260864}]},{"StartTime":102463.0,"Objects":[{"StartTime":102463.0,"EndTime":102463.0,"X":347.0,"Y":127.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":102638.0,"EndTime":102638.0,"X":432.363373,"Y":134.772491,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":102885.0,"Objects":[{"StartTime":102885.0,"EndTime":102885.0,"X":444.0,"Y":20.0}]},{"StartTime":103097.0,"Objects":[{"StartTime":103097.0,"EndTime":103097.0,"X":280.0,"Y":60.0}]},{"StartTime":103308.0,"Objects":[{"StartTime":103308.0,"EndTime":103308.0,"X":433.0,"Y":135.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":103483.0,"EndTime":103483.0,"X":423.061157,"Y":224.449539,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":103731.0,"Objects":[{"StartTime":103731.0,"EndTime":103731.0,"X":232.0,"Y":120.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":103906.0,"EndTime":103906.0,"X":222.061157,"Y":30.55046,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":104153.0,"Objects":[{"StartTime":104153.0,"EndTime":104153.0,"X":92.0,"Y":254.0}]},{"StartTime":104364.0,"Objects":[{"StartTime":104364.0,"EndTime":104364.0,"X":139.0,"Y":123.0}]},{"StartTime":104575.0,"Objects":[{"StartTime":104575.0,"EndTime":104575.0,"X":0.0,"Y":157.0}]},{"StartTime":104787.0,"Objects":[{"StartTime":104787.0,"EndTime":104787.0,"X":158.0,"Y":201.0}]},{"StartTime":104998.0,"Objects":[{"StartTime":104998.0,"EndTime":104998.0,"X":204.0,"Y":26.0}]},{"StartTime":105209.0,"Objects":[{"StartTime":105209.0,"EndTime":105209.0,"X":34.0,"Y":71.0}]},{"StartTime":105421.0,"Objects":[{"StartTime":105421.0,"EndTime":105421.0,"X":267.0,"Y":106.0}]},{"StartTime":105632.0,"Objects":[{"StartTime":105632.0,"EndTime":105632.0,"X":30.0,"Y":179.0}]},{"StartTime":105843.0,"Objects":[{"StartTime":105843.0,"EndTime":105843.0,"X":163.0,"Y":290.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":106018.0,"EndTime":106018.0,"X":157.2056,"Y":200.186722,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":106266.0,"Objects":[{"StartTime":106266.0,"EndTime":106266.0,"X":273.0,"Y":144.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":106441.0,"EndTime":106441.0,"X":354.2163,"Y":157.9499,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":106688.0,"Objects":[{"StartTime":106688.0,"EndTime":106688.0,"X":512.0,"Y":116.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":106863.0,"EndTime":106863.0,"X":430.2963,"Y":129.688965,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":107111.0,"Objects":[{"StartTime":107111.0,"EndTime":107111.0,"X":384.0,"Y":4.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":107286.0,"EndTime":107286.0,"X":368.694946,"Y":84.79979,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":107533.0,"Objects":[{"StartTime":107533.0,"EndTime":107533.0,"X":396.0,"Y":288.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":107708.0,"EndTime":107708.0,"X":410.385376,"Y":206.609482,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":107956.0,"Objects":[{"StartTime":107956.0,"EndTime":107956.0,"X":408.0,"Y":368.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":108131.0,"EndTime":108131.0,"X":475.4191,"Y":320.030762,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":108378.0,"Objects":[{"StartTime":108378.0,"EndTime":108378.0,"X":332.0,"Y":336.0}]},{"StartTime":108590.0,"Objects":[{"StartTime":108590.0,"EndTime":108590.0,"X":480.0,"Y":244.0}]},{"StartTime":108801.0,"Objects":[{"StartTime":108801.0,"EndTime":108801.0,"X":332.0,"Y":336.0}]},{"StartTime":109013.0,"Objects":[{"StartTime":109013.0,"EndTime":109013.0,"X":372.0,"Y":168.0}]},{"StartTime":109224.0,"Objects":[{"StartTime":109224.0,"EndTime":109224.0,"X":247.0,"Y":313.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":109399.0,"EndTime":109399.0,"X":267.7445,"Y":230.566544,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":109646.0,"Objects":[{"StartTime":109646.0,"EndTime":109646.0,"X":96.0,"Y":136.0}]},{"StartTime":109858.0,"Objects":[{"StartTime":109858.0,"EndTime":109858.0,"X":196.0,"Y":252.0}]},{"StartTime":110069.0,"Objects":[{"StartTime":110069.0,"EndTime":110069.0,"X":260.0,"Y":120.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":110244.0,"EndTime":110244.0,"X":170.550461,"Y":129.938843,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":110491.0,"Objects":[{"StartTime":110491.0,"EndTime":110491.0,"X":28.0,"Y":236.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":110666.0,"EndTime":110666.0,"X":117.449539,"Y":245.938843,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":110914.0,"Objects":[{"StartTime":110914.0,"EndTime":110914.0,"X":86.0,"Y":46.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":111089.0,"EndTime":111089.0,"X":95.05495,"Y":135.543335,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":111337.0,"Objects":[{"StartTime":111337.0,"EndTime":111337.0,"X":186.0,"Y":341.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":111512.0,"EndTime":111512.0,"X":195.938843,"Y":251.550461,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":111759.0,"Objects":[{"StartTime":111759.0,"EndTime":111759.0,"X":216.0,"Y":88.0}]},{"StartTime":111970.0,"Objects":[{"StartTime":111970.0,"EndTime":111970.0,"X":95.0,"Y":135.0}]},{"StartTime":112181.0,"Objects":[{"StartTime":112181.0,"EndTime":112181.0,"X":264.0,"Y":168.0}]},{"StartTime":112393.0,"Objects":[{"StartTime":112393.0,"EndTime":112393.0,"X":191.0,"Y":8.0}]},{"StartTime":112604.0,"Objects":[{"StartTime":112604.0,"EndTime":112604.0,"X":142.0,"Y":221.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":112779.0,"EndTime":112779.0,"X":132.061157,"Y":310.449524,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":113026.0,"Objects":[{"StartTime":113026.0,"EndTime":113026.0,"X":264.0,"Y":168.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":113201.0,"EndTime":113201.0,"X":254.061157,"Y":257.449524,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":113449.0,"Objects":[{"StartTime":113449.0,"EndTime":113449.0,"X":396.0,"Y":112.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":113624.0,"EndTime":113624.0,"X":386.061157,"Y":201.449539,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":113871.0,"Objects":[{"StartTime":113871.0,"EndTime":113871.0,"X":312.0,"Y":104.0}]},{"StartTime":114083.0,"Objects":[{"StartTime":114083.0,"EndTime":114083.0,"X":456.0,"Y":240.0}]},{"StartTime":114294.0,"Objects":[{"StartTime":114294.0,"EndTime":114294.0,"X":442.0,"Y":48.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":114469.0,"EndTime":114469.0,"X":360.0754,"Y":43.94542,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":114716.0,"Objects":[{"StartTime":114716.0,"EndTime":114716.0,"X":303.0,"Y":196.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":114891.0,"EndTime":114891.0,"X":386.2208,"Y":200.863846,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":115139.0,"Objects":[{"StartTime":115139.0,"EndTime":115139.0,"X":208.0,"Y":80.0}]},{"StartTime":115244.0,"Objects":[{"StartTime":115244.0,"EndTime":115244.0,"X":213.0,"Y":124.0}]},{"StartTime":115350.0,"Objects":[{"StartTime":115350.0,"EndTime":115350.0,"X":218.0,"Y":169.0}]},{"StartTime":115455.0,"Objects":[{"StartTime":115455.0,"EndTime":115455.0,"X":224.0,"Y":214.0}]},{"StartTime":115561.0,"Objects":[{"StartTime":115561.0,"EndTime":115561.0,"X":229.0,"Y":258.0}]},{"StartTime":115773.0,"Objects":[{"StartTime":115773.0,"EndTime":115773.0,"X":128.521729,"Y":184.521729}]},{"StartTime":115878.0,"Objects":[{"StartTime":115878.0,"EndTime":115878.0,"X":132.260864,"Y":188.260864}]},{"StartTime":115984.0,"Objects":[{"StartTime":115984.0,"EndTime":115984.0,"X":136.0,"Y":192.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":116159.0,"EndTime":116159.0,"X":61.1985931,"Y":186.545731,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":116407.0,"Objects":[{"StartTime":116407.0,"EndTime":116407.0,"X":60.0,"Y":104.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":116582.0,"EndTime":116582.0,"X":134.853943,"Y":108.678375,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":116829.0,"Objects":[{"StartTime":116829.0,"EndTime":116829.0,"X":202.0,"Y":5.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":117004.0,"EndTime":117004.0,"X":207.454269,"Y":79.80141,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":117251.0,"Objects":[{"StartTime":117251.0,"EndTime":117251.0,"X":288.0,"Y":104.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":117426.0,"EndTime":117426.0,"X":292.988922,"Y":29.1661148,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":117674.0,"Objects":[{"StartTime":117674.0,"EndTime":117674.0,"X":336.0,"Y":184.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":117849.0,"EndTime":117849.0,"X":261.1986,"Y":178.545731,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":118096.0,"Objects":[{"StartTime":118096.0,"EndTime":118096.0,"X":340.0,"Y":264.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":118271.0,"EndTime":118271.0,"X":414.754669,"Y":257.9388,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":118519.0,"Objects":[{"StartTime":118519.0,"EndTime":118519.0,"X":414.0,"Y":112.0}]},{"StartTime":118730.0,"Objects":[{"StartTime":118730.0,"EndTime":118730.0,"X":500.0,"Y":230.0}]},{"StartTime":118942.0,"Objects":[{"StartTime":118942.0,"EndTime":118942.0,"X":362.0,"Y":185.0}]},{"StartTime":119153.0,"Objects":[{"StartTime":119153.0,"EndTime":119153.0,"X":500.0,"Y":140.0}]},{"StartTime":119364.0,"Objects":[{"StartTime":119364.0,"EndTime":119364.0,"X":414.0,"Y":258.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":119539.0,"EndTime":119539.0,"X":339.245331,"Y":264.0612,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":119787.0,"Objects":[{"StartTime":119787.0,"EndTime":119787.0,"X":186.0,"Y":173.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":119962.0,"EndTime":119962.0,"X":260.829376,"Y":178.056046,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":120209.0,"Objects":[{"StartTime":120209.0,"EndTime":120209.0,"X":260.0,"Y":292.0}]},{"StartTime":120421.0,"Objects":[{"StartTime":120421.0,"EndTime":120421.0,"X":169.0,"Y":344.0}]},{"StartTime":120632.0,"Objects":[{"StartTime":120632.0,"EndTime":120632.0,"X":182.0,"Y":239.0}]},{"StartTime":120843.0,"Objects":[{"StartTime":120843.0,"EndTime":120843.0,"X":244.0,"Y":372.0}]},{"StartTime":121054.0,"Objects":[{"StartTime":121054.0,"EndTime":121054.0,"X":104.0,"Y":296.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":121229.0,"EndTime":121229.0,"X":29.2258224,"Y":301.815765,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":121477.0,"Objects":[{"StartTime":121477.0,"EndTime":121477.0,"X":186.0,"Y":173.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":121652.0,"EndTime":121652.0,"X":260.829376,"Y":178.056046,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":121899.0,"Objects":[{"StartTime":121899.0,"EndTime":121899.0,"X":104.0,"Y":208.0}]},{"StartTime":122111.0,"Objects":[{"StartTime":122111.0,"EndTime":122111.0,"X":78.0,"Y":106.0}]},{"StartTime":122322.0,"Objects":[{"StartTime":122322.0,"EndTime":122322.0,"X":104.0,"Y":248.0}]},{"StartTime":122534.0,"Objects":[{"StartTime":122534.0,"EndTime":122534.0,"X":177.0,"Y":144.0}]},{"StartTime":122744.0,"Objects":[{"StartTime":122744.0,"EndTime":122744.0,"X":288.0,"Y":256.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":122919.0,"EndTime":122919.0,"X":216.195923,"Y":256.09137,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":123167.0,"Objects":[{"StartTime":123167.0,"EndTime":123167.0,"X":216.0,"Y":144.0}]},{"StartTime":123378.0,"Objects":[{"StartTime":123378.0,"EndTime":123378.0,"X":367.0,"Y":280.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":123553.0,"EndTime":123553.0,"X":316.5537,"Y":331.033569,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":123801.0,"Objects":[{"StartTime":123801.0,"EndTime":123801.0,"X":450.0,"Y":260.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":123976.0,"EndTime":123976.0,"X":431.362823,"Y":329.464874,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":124223.0,"Objects":[{"StartTime":124223.0,"EndTime":124223.0,"X":277.0,"Y":260.0}]},{"StartTime":124435.0,"Objects":[{"StartTime":124435.0,"EndTime":124435.0,"X":332.0,"Y":128.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":124610.0,"EndTime":124610.0,"X":402.4845,"Y":153.630737,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":124857.0,"Objects":[{"StartTime":124857.0,"EndTime":124857.0,"X":367.0,"Y":280.0}]},{"StartTime":125069.0,"Objects":[{"StartTime":125069.0,"EndTime":125069.0,"X":272.0,"Y":180.0}]},{"StartTime":125280.0,"Objects":[{"StartTime":125280.0,"EndTime":125280.0,"X":470.0,"Y":129.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":125455.0,"EndTime":125455.0,"X":460.233978,"Y":199.678162,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":125702.0,"Objects":[{"StartTime":125702.0,"EndTime":125702.0,"X":356.0,"Y":52.0}]},{"StartTime":125914.0,"Objects":[{"StartTime":125914.0,"EndTime":125914.0,"X":402.0,"Y":153.0}]},{"StartTime":126125.0,"Objects":[{"StartTime":126125.0,"EndTime":126125.0,"X":232.0,"Y":72.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":126300.0,"EndTime":126300.0,"X":212.777573,"Y":141.528687,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":126547.0,"Objects":[{"StartTime":126547.0,"EndTime":126547.0,"X":288.0,"Y":124.0}]},{"StartTime":126759.0,"Objects":[{"StartTime":126759.0,"EndTime":126759.0,"X":134.0,"Y":138.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":126934.0,"EndTime":126934.0,"X":168.515137,"Y":201.263245,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":127181.0,"Objects":[{"StartTime":127181.0,"EndTime":127181.0,"X":335.0,"Y":212.0}]},{"StartTime":127393.0,"Objects":[{"StartTime":127393.0,"EndTime":127393.0,"X":212.0,"Y":141.0}]},{"StartTime":127604.0,"Objects":[{"StartTime":127604.0,"EndTime":127604.0,"X":254.0,"Y":284.0}]},{"StartTime":127815.0,"Objects":[{"StartTime":127815.0,"EndTime":127815.0,"X":286.0,"Y":130.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":127990.0,"EndTime":127990.0,"X":211.678345,"Y":140.064392,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":128237.0,"Objects":[{"StartTime":128237.0,"EndTime":128237.0,"X":384.0,"Y":51.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":128412.0,"EndTime":128412.0,"X":311.6427,"Y":31.2661953,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":128660.0,"Objects":[{"StartTime":128660.0,"EndTime":128660.0,"X":480.0,"Y":108.0}]},{"StartTime":128871.0,"Objects":[{"StartTime":128871.0,"EndTime":128871.0,"X":396.0,"Y":232.0}]},{"StartTime":129082.0,"Objects":[{"StartTime":129082.0,"EndTime":129082.0,"X":233.521729,"Y":217.521729}]},{"StartTime":129188.0,"Objects":[{"StartTime":129188.0,"EndTime":129188.0,"X":237.260864,"Y":221.260864}]},{"StartTime":129294.0,"Objects":[{"StartTime":129294.0,"EndTime":129294.0,"X":241.0,"Y":225.0}]},{"StartTime":129505.0,"Objects":[{"StartTime":129505.0,"EndTime":129505.0,"X":295.0,"Y":288.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":129785.0,"EndTime":129785.0,"X":191.701752,"Y":291.7883,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":129928.0,"Objects":[{"StartTime":129928.0,"EndTime":129928.0,"X":192.0,"Y":292.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":130103.0,"EndTime":130103.0,"X":175.94281,"Y":365.260956,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":130350.0,"Objects":[{"StartTime":130350.0,"EndTime":130350.0,"X":148.0,"Y":220.0}]},{"StartTime":130561.0,"Objects":[{"StartTime":130561.0,"EndTime":130561.0,"X":68.0,"Y":187.0}]},{"StartTime":130772.0,"Objects":[{"StartTime":130772.0,"EndTime":130772.0,"X":36.0,"Y":267.0}]},{"StartTime":130983.0,"Objects":[{"StartTime":130983.0,"EndTime":130983.0,"X":115.0,"Y":300.0}]},{"StartTime":131195.0,"Objects":[{"StartTime":131195.0,"EndTime":131195.0,"X":16.0,"Y":127.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":131475.0,"EndTime":131475.0,"X":119.044754,"Y":123.706215,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":131618.0,"Objects":[{"StartTime":131618.0,"EndTime":131618.0,"X":119.0,"Y":124.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":131793.0,"EndTime":131793.0,"X":192.260956,"Y":107.94281,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":132040.0,"Objects":[{"StartTime":132040.0,"EndTime":132040.0,"X":280.0,"Y":44.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":132356.0,"EndTime":132356.0,"X":170.209717,"Y":20.2853,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":132637.0,"EndTime":132637.0,"X":280.0,"Y":44.0,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":132885.0,"Objects":[{"StartTime":132885.0,"EndTime":132885.0,"X":96.0,"Y":56.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":133165.0,"EndTime":133165.0,"X":90.74685,"Y":156.698685,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":133308.0,"Objects":[{"StartTime":133308.0,"EndTime":133308.0,"X":91.0,"Y":157.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":133483.0,"EndTime":133483.0,"X":164.045471,"Y":139.98941,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":133731.0,"Objects":[{"StartTime":133731.0,"EndTime":133731.0,"X":44.0,"Y":216.0}]},{"StartTime":133942.0,"Objects":[{"StartTime":133942.0,"EndTime":133942.0,"X":123.0,"Y":249.0}]},{"StartTime":134153.0,"Objects":[{"StartTime":134153.0,"EndTime":134153.0,"X":91.0,"Y":329.0}]},{"StartTime":134364.0,"Objects":[{"StartTime":134364.0,"EndTime":134364.0,"X":11.0,"Y":296.0}]},{"StartTime":134576.0,"Objects":[{"StartTime":134576.0,"EndTime":134576.0,"X":200.0,"Y":268.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":134856.0,"EndTime":134856.0,"X":304.8808,"Y":260.356873,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":134998.0,"Objects":[{"StartTime":134998.0,"EndTime":134998.0,"X":304.0,"Y":260.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":135173.0,"EndTime":135173.0,"X":286.908661,"Y":333.0266,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":135421.0,"Objects":[{"StartTime":135421.0,"EndTime":135421.0,"X":436.0,"Y":348.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":135737.0,"EndTime":135737.0,"X":413.2101,"Y":238.014038,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":136018.0,"EndTime":136018.0,"X":436.0,"Y":348.0,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":136266.0,"Objects":[{"StartTime":136266.0,"EndTime":136266.0,"X":448.0,"Y":168.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":136441.0,"EndTime":136441.0,"X":377.865,"Y":166.693008,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":136688.0,"Objects":[{"StartTime":136688.0,"EndTime":136688.0,"X":232.0,"Y":260.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":136863.0,"EndTime":136863.0,"X":302.135,"Y":261.306976,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":137111.0,"Objects":[{"StartTime":137111.0,"EndTime":137111.0,"X":340.0,"Y":100.0}]},{"StartTime":137322.0,"Objects":[{"StartTime":137322.0,"EndTime":137322.0,"X":268.0,"Y":196.0}]},{"StartTime":137533.0,"Objects":[{"StartTime":137533.0,"EndTime":137533.0,"X":240.0,"Y":48.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":137708.0,"EndTime":137708.0,"X":250.133484,"Y":122.312263,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":137956.0,"Objects":[{"StartTime":137956.0,"EndTime":137956.0,"X":92.0,"Y":44.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":138131.0,"EndTime":138131.0,"X":163.568558,"Y":39.28212,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":138378.0,"Objects":[{"StartTime":138378.0,"EndTime":138378.0,"X":168.0,"Y":180.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":138553.0,"EndTime":138553.0,"X":98.2096,"Y":180.324524,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":138801.0,"Objects":[{"StartTime":138801.0,"EndTime":138801.0,"X":12.0,"Y":56.0}]},{"StartTime":139012.0,"Objects":[{"StartTime":139012.0,"EndTime":139012.0,"X":132.0,"Y":112.0}]},{"StartTime":139223.0,"Objects":[{"StartTime":139223.0,"EndTime":139223.0,"X":44.0,"Y":236.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":139398.0,"EndTime":139398.0,"X":19.9848156,"Y":171.056885,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":139646.0,"Objects":[{"StartTime":139646.0,"EndTime":139646.0,"X":244.0,"Y":172.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":139821.0,"EndTime":139821.0,"X":219.45665,"Y":236.357651,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":140069.0,"Objects":[{"StartTime":140069.0,"EndTime":140069.0,"X":216.0,"Y":104.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":140244.0,"EndTime":140244.0,"X":238.580536,"Y":39.2729034,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":140491.0,"Objects":[{"StartTime":140491.0,"EndTime":140491.0,"X":436.0,"Y":68.0}]},{"StartTime":140702.0,"Objects":[{"StartTime":140702.0,"EndTime":140702.0,"X":289.0,"Y":88.0}]},{"StartTime":140913.0,"Objects":[{"StartTime":140913.0,"EndTime":140913.0,"X":459.0,"Y":156.0}]},{"StartTime":141124.0,"Objects":[{"StartTime":141124.0,"EndTime":141124.0,"X":317.0,"Y":50.0}]},{"StartTime":141336.0,"Objects":[{"StartTime":141336.0,"EndTime":141336.0,"X":336.0,"Y":232.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":141511.0,"EndTime":141511.0,"X":325.956146,"Y":306.324432,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":141759.0,"Objects":[{"StartTime":141759.0,"EndTime":141759.0,"X":468.0,"Y":230.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":141934.0,"EndTime":141934.0,"X":458.0877,"Y":155.6579,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":142181.0,"Objects":[{"StartTime":142181.0,"EndTime":142181.0,"X":436.0,"Y":324.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":142356.0,"EndTime":142356.0,"X":510.4514,"Y":333.0549,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":142604.0,"Objects":[{"StartTime":142604.0,"EndTime":142604.0,"X":336.0,"Y":124.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":142779.0,"EndTime":142779.0,"X":261.534241,"Y":132.9359,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":143026.0,"Objects":[{"StartTime":143026.0,"EndTime":143026.0,"X":210.0,"Y":89.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":143201.0,"EndTime":143201.0,"X":184.922729,"Y":169.4724,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":143343.0,"Objects":[{"StartTime":143343.0,"EndTime":143343.0,"X":261.0,"Y":132.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":143518.0,"EndTime":143518.0,"X":185.715179,"Y":170.3263,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":143660.0,"Objects":[{"StartTime":143660.0,"EndTime":143660.0,"X":256.0,"Y":184.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":143729.0,"EndTime":143729.0,"X":184.960236,"Y":170.093552,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":143871.0,"Objects":[{"StartTime":143871.0,"EndTime":143871.0,"X":124.0,"Y":70.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":144046.0,"EndTime":144046.0,"X":110.185104,"Y":158.9334,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":144188.0,"Objects":[{"StartTime":144188.0,"EndTime":144188.0,"X":96.0,"Y":247.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":144363.0,"EndTime":144363.0,"X":109.814896,"Y":158.0666,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":144505.0,"Objects":[{"StartTime":144505.0,"EndTime":144505.0,"X":184.0,"Y":170.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":144574.0,"EndTime":144574.0,"X":109.964081,"Y":158.013229,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":144716.0,"Objects":[{"StartTime":144716.0,"EndTime":144716.0,"X":261.0,"Y":132.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":144891.0,"EndTime":144891.0,"X":349.75293,"Y":146.9304,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":145033.0,"Objects":[{"StartTime":145033.0,"EndTime":145033.0,"X":336.0,"Y":84.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":145208.0,"EndTime":145208.0,"X":387.835815,"Y":157.573425,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":145350.0,"Objects":[{"StartTime":145350.0,"EndTime":145350.0,"X":428.0,"Y":96.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":145419.0,"EndTime":145419.0,"X":415.2836,"Y":169.9141,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":145562.0,"Objects":[{"StartTime":145562.0,"EndTime":145562.0,"X":411.0,"Y":278.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":145737.0,"EndTime":145737.0,"X":491.462463,"Y":247.365463,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":145878.0,"Objects":[{"StartTime":145878.0,"EndTime":145878.0,"X":324.0,"Y":276.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":146053.0,"EndTime":146053.0,"X":409.8932,"Y":277.2359,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":146195.0,"Objects":[{"StartTime":146195.0,"EndTime":146195.0,"X":252.0,"Y":272.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":146264.0,"EndTime":146264.0,"X":324.1942,"Y":274.656555,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":146407.0,"Objects":[{"StartTime":146407.0,"EndTime":146407.0,"X":317.0,"Y":119.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":146582.0,"EndTime":146582.0,"X":292.912048,"Y":205.716614,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":146724.0,"Objects":[{"StartTime":146724.0,"EndTime":146724.0,"X":240.0,"Y":74.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":146899.0,"EndTime":146899.0,"X":262.5866,"Y":161.11972,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":147040.0,"Objects":[{"StartTime":147040.0,"EndTime":147040.0,"X":166.0,"Y":90.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":147109.0,"EndTime":147109.0,"X":219.407776,"Y":142.655563,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":147252.0,"Objects":[{"StartTime":147252.0,"EndTime":147252.0,"X":170.0,"Y":152.0}]},{"StartTime":147464.0,"Objects":[{"StartTime":147464.0,"EndTime":147464.0,"X":38.0,"Y":120.0}]},{"StartTime":147569.0,"Objects":[{"StartTime":147569.0,"EndTime":147569.0,"X":12.0,"Y":155.0}]},{"StartTime":147675.0,"Objects":[{"StartTime":147675.0,"EndTime":147675.0,"X":2.0,"Y":199.0}]},{"StartTime":147781.0,"Objects":[{"StartTime":147781.0,"EndTime":147781.0,"X":11.0,"Y":242.0}]},{"StartTime":147886.0,"Objects":[{"StartTime":147886.0,"EndTime":147886.0,"X":37.0,"Y":279.0}]},{"StartTime":147992.0,"Objects":[{"StartTime":147992.0,"EndTime":147992.0,"X":75.0,"Y":301.0}]},{"StartTime":148097.0,"Objects":[{"StartTime":148097.0,"EndTime":148097.0,"X":119.0,"Y":304.0}]},{"StartTime":148942.0,"Objects":[{"StartTime":148942.0,"EndTime":148942.0,"X":245.0,"Y":208.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":148977.0,"EndTime":148977.0,"X":264.88504,"Y":197.6252,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":149012.0,"EndTime":149012.0,"X":245.126251,"Y":207.934128,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":149047.0,"EndTime":149047.0,"X":264.7588,"Y":197.691071,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":149082.0,"EndTime":149082.0,"X":245.2525,"Y":207.868256,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":149118.0,"EndTime":149118.0,"X":264.695648,"Y":197.724014,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":149153.0,"EndTime":149153.0,"X":245.189377,"Y":207.901184,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":149188.0,"EndTime":149188.0,"X":264.8219,"Y":197.658142,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":149223.0,"EndTime":149223.0,"X":245.063126,"Y":207.967072,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":149222.0,"EndTime":149222.0,"X":264.948151,"Y":197.59227,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":149364.0,"Objects":[{"StartTime":149364.0,"EndTime":149364.0,"X":232.0,"Y":288.0}]},{"StartTime":149787.0,"Objects":[{"StartTime":149787.0,"EndTime":149787.0,"X":217.0,"Y":38.0}]},{"StartTime":149998.0,"Objects":[{"StartTime":149998.0,"EndTime":149998.0,"X":56.0,"Y":98.0}]},{"StartTime":150209.0,"Objects":[{"StartTime":150209.0,"EndTime":150209.0,"X":155.0,"Y":187.0}]},{"StartTime":150420.0,"Objects":[{"StartTime":150420.0,"EndTime":150420.0,"X":94.0,"Y":26.0}]},{"StartTime":150632.0,"Objects":[{"StartTime":150632.0,"EndTime":150632.0,"X":63.0,"Y":262.0}]},{"StartTime":150843.0,"Objects":[{"StartTime":150843.0,"EndTime":150843.0,"X":257.0,"Y":188.0}]},{"StartTime":151054.0,"Objects":[{"StartTime":151054.0,"EndTime":151054.0,"X":138.0,"Y":82.0}]},{"StartTime":151265.0,"Objects":[{"StartTime":151265.0,"EndTime":151265.0,"X":212.0,"Y":275.0}]},{"StartTime":151477.0,"Objects":[{"StartTime":151477.0,"EndTime":151477.0,"X":288.0,"Y":60.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":151652.0,"EndTime":151652.0,"X":266.524567,"Y":155.1055,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":151899.0,"Objects":[{"StartTime":151899.0,"EndTime":151899.0,"X":204.0,"Y":48.0}]},{"StartTime":152111.0,"Objects":[{"StartTime":152111.0,"EndTime":152111.0,"X":346.0,"Y":175.0}]},{"StartTime":152322.0,"Objects":[{"StartTime":152322.0,"EndTime":152322.0,"X":130.0,"Y":263.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":152497.0,"EndTime":152497.0,"X":151.311874,"Y":167.857727,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":152744.0,"Objects":[{"StartTime":152744.0,"EndTime":152744.0,"X":232.0,"Y":244.0}]},{"StartTime":152956.0,"Objects":[{"StartTime":152956.0,"EndTime":152956.0,"X":56.0,"Y":170.0}]},{"StartTime":153167.0,"Objects":[{"StartTime":153167.0,"EndTime":153167.0,"X":64.0,"Y":352.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":153447.0,"EndTime":153447.0,"X":194.861862,"Y":335.192657,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":153590.0,"Objects":[{"StartTime":153590.0,"EndTime":153590.0,"X":224.0,"Y":348.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":153765.0,"EndTime":153765.0,"X":313.264221,"Y":347.8079,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":154012.0,"Objects":[{"StartTime":154012.0,"EndTime":154012.0,"X":376.0,"Y":140.0}]},{"StartTime":154223.0,"Objects":[{"StartTime":154223.0,"EndTime":154223.0,"X":269.0,"Y":286.0}]},{"StartTime":154435.0,"Objects":[{"StartTime":154435.0,"EndTime":154435.0,"X":441.0,"Y":230.0}]},{"StartTime":154646.0,"Objects":[{"StartTime":154646.0,"EndTime":154646.0,"X":269.0,"Y":173.0}]},{"StartTime":154857.0,"Objects":[{"StartTime":154857.0,"EndTime":154857.0,"X":376.0,"Y":320.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":155032.0,"EndTime":155032.0,"X":465.264221,"Y":319.8079,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":155280.0,"Objects":[{"StartTime":155280.0,"EndTime":155280.0,"X":496.0,"Y":136.0}]},{"StartTime":155491.0,"Objects":[{"StartTime":155491.0,"EndTime":155491.0,"X":420.0,"Y":256.0}]},{"StartTime":155702.0,"Objects":[{"StartTime":155702.0,"EndTime":155702.0,"X":330.0,"Y":80.0}]},{"StartTime":155913.0,"Objects":[{"StartTime":155913.0,"EndTime":155913.0,"X":223.0,"Y":226.0}]},{"StartTime":156125.0,"Objects":[{"StartTime":156125.0,"EndTime":156125.0,"X":395.0,"Y":170.0}]},{"StartTime":156336.0,"Objects":[{"StartTime":156336.0,"EndTime":156336.0,"X":223.0,"Y":113.0}]},{"StartTime":156547.0,"Objects":[{"StartTime":156547.0,"EndTime":156547.0,"X":330.0,"Y":260.0}]},{"StartTime":156759.0,"Objects":[{"StartTime":156759.0,"EndTime":156759.0,"X":408.0,"Y":92.0}]},{"StartTime":156970.0,"Objects":[{"StartTime":156970.0,"EndTime":156970.0,"X":168.0,"Y":168.0}]},{"StartTime":157182.0,"Objects":[{"StartTime":157182.0,"EndTime":157182.0,"X":408.0,"Y":244.0}]},{"StartTime":157392.0,"Objects":[{"StartTime":157392.0,"EndTime":157392.0,"X":256.0,"Y":44.0}]},{"StartTime":157604.0,"Objects":[{"StartTime":157604.0,"EndTime":157604.0,"X":264.0,"Y":296.0}]},{"StartTime":157815.0,"Objects":[{"StartTime":157815.0,"EndTime":157815.0,"X":436.0,"Y":168.0}]},{"StartTime":158027.0,"Objects":[{"StartTime":158027.0,"EndTime":158027.0,"X":188.0,"Y":92.0}]},{"StartTime":158238.0,"Objects":[{"StartTime":158238.0,"EndTime":158238.0,"X":212.0,"Y":336.0}]},{"StartTime":158450.0,"Objects":[{"StartTime":158450.0,"EndTime":158450.0,"X":290.0,"Y":168.0}]},{"StartTime":158661.0,"Objects":[{"StartTime":158661.0,"EndTime":158661.0,"X":50.0,"Y":244.0}]},{"StartTime":158871.0,"Objects":[{"StartTime":158871.0,"EndTime":158871.0,"X":290.0,"Y":320.0}]},{"StartTime":159083.0,"Objects":[{"StartTime":159083.0,"EndTime":159083.0,"X":138.0,"Y":120.0}]},{"StartTime":159295.0,"Objects":[{"StartTime":159295.0,"EndTime":159295.0,"X":146.0,"Y":372.0}]},{"StartTime":159506.0,"Objects":[{"StartTime":159506.0,"EndTime":159506.0,"X":318.0,"Y":244.0}]},{"StartTime":159716.0,"Objects":[{"StartTime":159716.0,"EndTime":159716.0,"X":70.0,"Y":168.0}]},{"StartTime":159928.0,"Objects":[{"StartTime":159928.0,"EndTime":159928.0,"X":324.0,"Y":164.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":160103.0,"EndTime":160103.0,"X":396.4909,"Y":220.798523,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":160350.0,"Objects":[{"StartTime":160350.0,"EndTime":160350.0,"X":291.0,"Y":354.0}]},{"StartTime":160562.0,"Objects":[{"StartTime":160562.0,"EndTime":160562.0,"X":209.0,"Y":190.0}]},{"StartTime":160773.0,"Objects":[{"StartTime":160773.0,"EndTime":160773.0,"X":377.0,"Y":321.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":160948.0,"EndTime":160948.0,"X":290.7343,"Y":353.17215,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":161195.0,"Objects":[{"StartTime":161195.0,"EndTime":161195.0,"X":209.0,"Y":190.0}]},{"StartTime":161407.0,"Objects":[{"StartTime":161407.0,"EndTime":161407.0,"X":396.0,"Y":220.0}]},{"StartTime":161618.0,"Objects":[{"StartTime":161618.0,"EndTime":161618.0,"X":200.0,"Y":283.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":161793.0,"EndTime":161793.0,"X":209.6018,"Y":190.27742,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":162040.0,"Objects":[{"StartTime":162040.0,"EndTime":162040.0,"X":396.0,"Y":221.0}]},{"StartTime":162251.0,"Objects":[{"StartTime":162251.0,"EndTime":162251.0,"X":290.0,"Y":353.0}]},{"StartTime":162463.0,"Objects":[{"StartTime":162463.0,"EndTime":162463.0,"X":264.0,"Y":56.0}]},{"StartTime":162568.0,"Objects":[{"StartTime":162568.0,"EndTime":162568.0,"X":277.0,"Y":102.0}]},{"StartTime":162674.0,"Objects":[{"StartTime":162674.0,"EndTime":162674.0,"X":290.0,"Y":149.0}]},{"StartTime":162779.0,"Objects":[{"StartTime":162779.0,"EndTime":162779.0,"X":304.0,"Y":196.0}]},{"StartTime":162885.0,"Objects":[{"StartTime":162885.0,"EndTime":162885.0,"X":317.0,"Y":243.0}]},{"StartTime":163097.0,"Objects":[{"StartTime":163097.0,"EndTime":163097.0,"X":172.0,"Y":164.0}]},{"StartTime":163308.0,"Objects":[{"StartTime":163308.0,"EndTime":163308.0,"X":416.0,"Y":108.0}]},{"StartTime":163519.0,"Objects":[{"StartTime":163519.0,"EndTime":163519.0,"X":232.0,"Y":91.0}]},{"StartTime":163730.0,"Objects":[{"StartTime":163730.0,"EndTime":163730.0,"X":400.0,"Y":12.0}]},{"StartTime":163941.0,"Objects":[{"StartTime":163941.0,"EndTime":163941.0,"X":383.0,"Y":196.0}]},{"StartTime":164153.0,"Objects":[{"StartTime":164153.0,"EndTime":164153.0,"X":217.0,"Y":0.0}]},{"StartTime":164364.0,"Objects":[{"StartTime":164364.0,"EndTime":164364.0,"X":200.0,"Y":184.0}]},{"StartTime":164575.0,"Objects":[{"StartTime":164575.0,"EndTime":164575.0,"X":313.0,"Y":16.0}]},{"StartTime":164786.0,"Objects":[{"StartTime":164786.0,"EndTime":164786.0,"X":112.0,"Y":32.0}]},{"StartTime":164998.0,"Objects":[{"StartTime":164998.0,"EndTime":164998.0,"X":200.0,"Y":184.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":165173.0,"EndTime":165173.0,"X":205.788208,"Y":91.45287,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":165421.0,"Objects":[{"StartTime":165421.0,"EndTime":165421.0,"X":112.0,"Y":256.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":165596.0,"EndTime":165596.0,"X":106.211784,"Y":348.547119,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":165843.0,"Objects":[{"StartTime":165843.0,"EndTime":165843.0,"X":116.0,"Y":176.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":166018.0,"EndTime":166018.0,"X":23.4528751,"Y":170.211777,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":166266.0,"Objects":[{"StartTime":166266.0,"EndTime":166266.0,"X":196.0,"Y":264.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":166441.0,"EndTime":166441.0,"X":288.547119,"Y":269.7882,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":166688.0,"Objects":[{"StartTime":166688.0,"EndTime":166688.0,"X":248.0,"Y":60.0}]},{"StartTime":166899.0,"Objects":[{"StartTime":166899.0,"EndTime":166899.0,"X":248.0,"Y":201.0}]},{"StartTime":167111.0,"Objects":[{"StartTime":167111.0,"EndTime":167111.0,"X":333.0,"Y":55.0}]},{"StartTime":167322.0,"Objects":[{"StartTime":167322.0,"EndTime":167322.0,"X":248.0,"Y":201.0}]},{"StartTime":167533.0,"Objects":[{"StartTime":167533.0,"EndTime":167533.0,"X":424.0,"Y":101.0}]},{"StartTime":167744.0,"Objects":[{"StartTime":167744.0,"EndTime":167744.0,"X":248.0,"Y":201.0}]},{"StartTime":167956.0,"Objects":[{"StartTime":167956.0,"EndTime":167956.0,"X":468.0,"Y":224.0}]},{"StartTime":168167.0,"Objects":[{"StartTime":168167.0,"EndTime":168167.0,"X":292.0,"Y":124.0}]},{"StartTime":168378.0,"Objects":[{"StartTime":168378.0,"EndTime":168378.0,"X":364.0,"Y":328.0}]},{"StartTime":168589.0,"Objects":[{"StartTime":168589.0,"EndTime":168589.0,"X":364.0,"Y":158.0}]},{"StartTime":168801.0,"Objects":[{"StartTime":168801.0,"EndTime":168801.0,"X":244.0,"Y":304.0}]},{"StartTime":169013.0,"Objects":[{"StartTime":169013.0,"EndTime":169013.0,"X":464.0,"Y":327.0}]},{"StartTime":169224.0,"Objects":[{"StartTime":169224.0,"EndTime":169224.0,"X":192.0,"Y":248.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":169399.0,"EndTime":169399.0,"X":184.99115,"Y":345.247742,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":169646.0,"Objects":[{"StartTime":169646.0,"EndTime":169646.0,"X":508.0,"Y":272.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":169821.0,"EndTime":169821.0,"X":500.99115,"Y":174.752258,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":170068.0,"Objects":[{"StartTime":170068.0,"EndTime":170068.0,"X":268.0,"Y":60.0}]},{"StartTime":170279.0,"Objects":[{"StartTime":170279.0,"EndTime":170279.0,"X":268.0,"Y":257.0}]},{"StartTime":170491.0,"Objects":[{"StartTime":170491.0,"EndTime":170491.0,"X":404.0,"Y":116.0}]},{"StartTime":170702.0,"Objects":[{"StartTime":170702.0,"EndTime":170702.0,"X":207.0,"Y":116.0}]},{"StartTime":170913.0,"Objects":[{"StartTime":170913.0,"EndTime":170913.0,"X":348.0,"Y":267.0}]},{"StartTime":171124.0,"Objects":[{"StartTime":171124.0,"EndTime":171124.0,"X":348.0,"Y":31.0}]},{"StartTime":171336.0,"Objects":[{"StartTime":171336.0,"EndTime":171336.0,"X":192.0,"Y":200.0}]},{"StartTime":171547.0,"Objects":[{"StartTime":171547.0,"EndTime":171547.0,"X":428.0,"Y":200.0}]},{"StartTime":171759.0,"Objects":[{"StartTime":171759.0,"EndTime":171759.0,"X":268.0,"Y":60.0}]},{"StartTime":171970.0,"Objects":[{"StartTime":171970.0,"EndTime":171970.0,"X":386.0,"Y":236.0}]},{"StartTime":172181.0,"Objects":[{"StartTime":172181.0,"EndTime":172181.0,"X":386.0,"Y":11.0}]},{"StartTime":172393.0,"Objects":[{"StartTime":172393.0,"EndTime":172393.0,"X":268.0,"Y":187.0}]},{"StartTime":172604.0,"Objects":[{"StartTime":172604.0,"EndTime":172604.0,"X":149.0,"Y":55.0}]},{"StartTime":172815.0,"Objects":[{"StartTime":172815.0,"EndTime":172815.0,"X":30.0,"Y":231.0}]},{"StartTime":173026.0,"Objects":[{"StartTime":173026.0,"EndTime":173026.0,"X":30.0,"Y":7.0}]},{"StartTime":173238.0,"Objects":[{"StartTime":173238.0,"EndTime":173238.0,"X":149.0,"Y":183.0}]},{"StartTime":173449.0,"Objects":[{"StartTime":173449.0,"EndTime":173449.0,"X":30.0,"Y":7.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":173624.0,"EndTime":173624.0,"X":52.15489,"Y":101.949524,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":173871.0,"Objects":[{"StartTime":173871.0,"EndTime":173871.0,"X":240.0,"Y":64.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":174046.0,"EndTime":174046.0,"X":146.743469,"Y":35.54885,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":174294.0,"Objects":[{"StartTime":174294.0,"EndTime":174294.0,"X":80.0,"Y":216.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":174469.0,"EndTime":174469.0,"X":150.509186,"Y":148.659775,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":174716.0,"Objects":[{"StartTime":174716.0,"EndTime":174716.0,"X":124.0,"Y":280.0}]},{"StartTime":174928.0,"Objects":[{"StartTime":174928.0,"EndTime":174928.0,"X":56.0,"Y":128.0}]},{"StartTime":175139.0,"Objects":[{"StartTime":175139.0,"EndTime":175139.0,"X":216.0,"Y":212.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":175314.0,"EndTime":175314.0,"X":204.150711,"Y":286.058044,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":175562.0,"Objects":[{"StartTime":175562.0,"EndTime":175562.0,"X":296.0,"Y":216.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":175737.0,"EndTime":175737.0,"X":280.708374,"Y":304.6914,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":175984.0,"Objects":[{"StartTime":175984.0,"EndTime":175984.0,"X":376.0,"Y":208.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":176264.0,"EndTime":176264.0,"X":353.806122,"Y":341.1632,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":176406.0,"Objects":[{"StartTime":176406.0,"EndTime":176406.0,"X":356.739136,"Y":344.739136}]},{"StartTime":176618.0,"Objects":[{"StartTime":176618.0,"EndTime":176618.0,"X":320.521729,"Y":136.521729}]},{"StartTime":176723.0,"Objects":[{"StartTime":176723.0,"EndTime":176723.0,"X":324.260864,"Y":140.260864}]},{"StartTime":176829.0,"Objects":[{"StartTime":176829.0,"EndTime":176829.0,"X":328.0,"Y":144.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":177004.0,"EndTime":177004.0,"X":411.899,"Y":139.8616,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":177252.0,"Objects":[{"StartTime":177252.0,"EndTime":177252.0,"X":248.0,"Y":152.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":177427.0,"EndTime":177427.0,"X":164.101013,"Y":156.138382,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":177674.0,"Objects":[{"StartTime":177674.0,"EndTime":177674.0,"X":344.0,"Y":120.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":177849.0,"EndTime":177849.0,"X":427.899,"Y":115.8616,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":178097.0,"Objects":[{"StartTime":178097.0,"EndTime":178097.0,"X":236.0,"Y":168.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":178272.0,"EndTime":178272.0,"X":152.101013,"Y":172.138382,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":178519.0,"Objects":[{"StartTime":178519.0,"EndTime":178519.0,"X":192.0,"Y":272.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":178694.0,"EndTime":178694.0,"X":196.1384,"Y":355.899,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":178942.0,"Objects":[{"StartTime":178942.0,"EndTime":178942.0,"X":152.0,"Y":172.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":179117.0,"EndTime":179117.0,"X":147.8616,"Y":88.10101,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":179364.0,"Objects":[{"StartTime":179364.0,"EndTime":179364.0,"X":228.0,"Y":284.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":179539.0,"EndTime":179539.0,"X":232.1384,"Y":367.899,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":179787.0,"Objects":[{"StartTime":179787.0,"EndTime":179787.0,"X":116.0,"Y":152.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":179962.0,"EndTime":179962.0,"X":111.86161,"Y":68.10102,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":180209.0,"Objects":[{"StartTime":180209.0,"EndTime":180209.0,"X":100.0,"Y":256.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":180384.0,"EndTime":180384.0,"X":16.1010227,"Y":260.1384,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":180632.0,"Objects":[{"StartTime":180632.0,"EndTime":180632.0,"X":240.0,"Y":184.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":180807.0,"EndTime":180807.0,"X":323.899,"Y":179.8616,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":181055.0,"Objects":[{"StartTime":181055.0,"EndTime":181055.0,"X":288.0,"Y":336.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":181230.0,"EndTime":181230.0,"X":284.541016,"Y":246.0665,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":181477.0,"Objects":[{"StartTime":181477.0,"EndTime":181477.0,"X":432.0,"Y":84.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":181652.0,"EndTime":181652.0,"X":423.044678,"Y":173.553345,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":181900.0,"Objects":[{"StartTime":181900.0,"EndTime":181900.0,"X":368.0,"Y":352.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":182075.0,"EndTime":182075.0,"X":364.541016,"Y":262.0665,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":182322.0,"Objects":[{"StartTime":182322.0,"EndTime":182322.0,"X":512.0,"Y":100.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":182497.0,"EndTime":182497.0,"X":503.044678,"Y":189.553345,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":182745.0,"Objects":[{"StartTime":182745.0,"EndTime":182745.0,"X":272.0,"Y":104.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":182920.0,"EndTime":182920.0,"X":361.553345,"Y":112.955338,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":183062.0,"Objects":[{"StartTime":183062.0,"EndTime":183062.0,"X":356.0,"Y":132.0}]},{"StartTime":183167.0,"Objects":[{"StartTime":183167.0,"EndTime":183167.0,"X":352.0,"Y":156.0}]},{"StartTime":183378.0,"Objects":[{"StartTime":183378.0,"EndTime":183378.0,"X":276.0,"Y":20.0}]},{"StartTime":183590.0,"Objects":[{"StartTime":183590.0,"EndTime":183590.0,"X":304.0,"Y":240.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":183765.0,"EndTime":183765.0,"X":220.5027,"Y":243.341385,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":184012.0,"Objects":[{"StartTime":184012.0,"EndTime":184012.0,"X":392.0,"Y":272.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":184187.0,"EndTime":184187.0,"X":436.5039,"Y":342.962158,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":184435.0,"Objects":[{"StartTime":184435.0,"EndTime":184435.0,"X":376.0,"Y":184.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":184610.0,"EndTime":184610.0,"X":413.9991,"Y":109.324722,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":184857.0,"Objects":[{"StartTime":184857.0,"EndTime":184857.0,"X":320.0,"Y":336.0}]},{"StartTime":185069.0,"Objects":[{"StartTime":185069.0,"EndTime":185069.0,"X":260.0,"Y":180.0}]},{"StartTime":185280.0,"Objects":[{"StartTime":185280.0,"EndTime":185280.0,"X":176.0,"Y":304.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":185455.0,"EndTime":185455.0,"X":146.285233,"Y":347.999146,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":185702.0,"Objects":[{"StartTime":185702.0,"EndTime":185702.0,"X":207.0,"Y":176.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":185877.0,"EndTime":185877.0,"X":258.989227,"Y":179.51886,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":186125.0,"Objects":[{"StartTime":186125.0,"EndTime":186125.0,"X":84.0,"Y":224.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":186300.0,"EndTime":186300.0,"X":60.46429,"Y":176.0,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":186547.0,"Objects":[{"StartTime":186547.0,"EndTime":186547.0,"X":244.0,"Y":260.0}]},{"StartTime":186759.0,"Objects":[{"StartTime":186759.0,"EndTime":186759.0,"X":88.0,"Y":300.0}]},{"StartTime":186970.0,"Objects":[{"StartTime":186970.0,"EndTime":186970.0,"X":128.0,"Y":44.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":187145.0,"EndTime":187145.0,"X":133.824356,"Y":148.838348,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":187393.0,"Objects":[{"StartTime":187393.0,"EndTime":187393.0,"X":340.0,"Y":208.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":187568.0,"EndTime":187568.0,"X":345.824341,"Y":103.161659,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":187815.0,"Objects":[{"StartTime":187815.0,"EndTime":187815.0,"X":244.0,"Y":260.0}]},{"StartTime":188026.0,"Objects":[{"StartTime":188026.0,"EndTime":188026.0,"X":424.0,"Y":240.0}]},{"StartTime":188238.0,"Objects":[{"StartTime":188238.0,"EndTime":188238.0,"X":211.0,"Y":244.0}]},{"StartTime":188449.0,"Objects":[{"StartTime":188449.0,"EndTime":188449.0,"X":377.0,"Y":317.0}]},{"StartTime":188660.0,"Objects":[{"StartTime":188660.0,"EndTime":188660.0,"X":196.0,"Y":336.0}]},{"StartTime":188871.0,"Objects":[{"StartTime":188871.0,"EndTime":188871.0,"X":224.0,"Y":154.0}]},{"StartTime":189083.0,"Objects":[{"StartTime":189083.0,"EndTime":189083.0,"X":367.0,"Y":270.0}]},{"StartTime":189294.0,"Objects":[{"StartTime":189294.0,"EndTime":189294.0,"X":132.0,"Y":216.0}]},{"StartTime":189505.0,"Objects":[{"StartTime":189505.0,"EndTime":189505.0,"X":338.0,"Y":135.0}]},{"StartTime":189610.0,"Objects":[{"StartTime":189610.0,"EndTime":189610.0,"X":330.0,"Y":186.0}]},{"StartTime":189716.0,"Objects":[{"StartTime":189716.0,"EndTime":189716.0,"X":322.0,"Y":238.0}]},{"StartTime":189821.0,"Objects":[{"StartTime":189821.0,"EndTime":189821.0,"X":314.0,"Y":290.0}]},{"StartTime":189927.0,"Objects":[{"StartTime":189927.0,"EndTime":189927.0,"X":306.0,"Y":342.0}]},{"StartTime":190139.0,"Objects":[{"StartTime":190139.0,"EndTime":190139.0,"X":228.0,"Y":252.0}]},{"StartTime":190350.0,"Objects":[{"StartTime":190350.0,"EndTime":190350.0,"X":420.0,"Y":216.0}]},{"StartTime":190562.0,"Objects":[{"StartTime":190562.0,"EndTime":190562.0,"X":247.0,"Y":160.0}]},{"StartTime":190773.0,"Objects":[{"StartTime":190773.0,"EndTime":190773.0,"X":406.0,"Y":252.0}]},{"StartTime":190985.0,"Objects":[{"StartTime":190985.0,"EndTime":190985.0,"X":368.0,"Y":74.0}]},{"StartTime":191195.0,"Objects":[{"StartTime":191195.0,"EndTime":191195.0,"X":373.0,"Y":269.0}]},{"StartTime":191407.0,"Objects":[{"StartTime":191407.0,"EndTime":191407.0,"X":507.0,"Y":146.0}]},{"StartTime":191618.0,"Objects":[{"StartTime":191618.0,"EndTime":191618.0,"X":335.0,"Y":271.0}]},{"StartTime":191830.0,"Objects":[{"StartTime":191830.0,"EndTime":191830.0,"X":508.0,"Y":325.0}]},{"StartTime":192040.0,"Objects":[{"StartTime":192040.0,"EndTime":192040.0,"X":219.0,"Y":271.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":192215.0,"EndTime":192215.0,"X":205.632385,"Y":186.8185,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":192463.0,"Objects":[{"StartTime":192463.0,"EndTime":192463.0,"X":279.0,"Y":327.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":192638.0,"EndTime":192638.0,"X":197.296051,"Y":348.666077,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":192885.0,"Objects":[{"StartTime":192885.0,"EndTime":192885.0,"X":335.0,"Y":271.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":193060.0,"EndTime":193060.0,"X":356.590668,"Y":352.7418,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":193308.0,"Objects":[{"StartTime":193308.0,"EndTime":193308.0,"X":279.0,"Y":219.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":193483.0,"EndTime":193483.0,"X":360.7418,"Y":197.409332,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":193731.0,"Objects":[{"StartTime":193731.0,"EndTime":193731.0,"X":108.0,"Y":296.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":194011.0,"EndTime":194011.0,"X":111.138687,"Y":161.0365,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":194153.0,"Objects":[{"StartTime":194153.0,"EndTime":194153.0,"X":72.0,"Y":100.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":194328.0,"EndTime":194328.0,"X":155.1787,"Y":102.726517,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":194576.0,"Objects":[{"StartTime":194576.0,"EndTime":194576.0,"X":24.0,"Y":24.0}]},{"StartTime":194787.0,"Objects":[{"StartTime":194787.0,"EndTime":194787.0,"X":36.0,"Y":168.0}]},{"StartTime":194998.0,"Objects":[{"StartTime":194998.0,"EndTime":194998.0,"X":116.0,"Y":40.0}]},{"StartTime":195209.0,"Objects":[{"StartTime":195209.0,"EndTime":195209.0,"X":184.0,"Y":184.0}]},{"StartTime":195421.0,"Objects":[{"StartTime":195421.0,"EndTime":195421.0,"X":256.0,"Y":56.0}]},{"StartTime":195632.0,"Objects":[{"StartTime":195632.0,"EndTime":195632.0,"X":112.0,"Y":155.0}]},{"StartTime":195843.0,"Objects":[{"StartTime":195843.0,"EndTime":195843.0,"X":276.0,"Y":224.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":196018.0,"EndTime":196018.0,"X":268.203339,"Y":134.338348,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":196266.0,"Objects":[{"StartTime":196266.0,"EndTime":196266.0,"X":160.0,"Y":72.0}]},{"StartTime":196477.0,"Objects":[{"StartTime":196477.0,"EndTime":196477.0,"X":16.0,"Y":171.0}]},{"StartTime":196688.0,"Objects":[{"StartTime":196688.0,"EndTime":196688.0,"X":180.0,"Y":240.0}]},{"StartTime":196899.0,"Objects":[{"StartTime":196899.0,"EndTime":196899.0,"X":72.0,"Y":108.0}]},{"StartTime":197111.0,"Objects":[{"StartTime":197111.0,"EndTime":197111.0,"X":76.0,"Y":328.0}]},{"StartTime":197323.0,"Objects":[{"StartTime":197323.0,"EndTime":197323.0,"X":249.0,"Y":274.0}]},{"StartTime":197534.0,"Objects":[{"StartTime":197534.0,"EndTime":197534.0,"X":83.0,"Y":171.0}]},{"StartTime":197745.0,"Objects":[{"StartTime":197745.0,"EndTime":197745.0,"X":217.0,"Y":295.0}]},{"StartTime":197956.0,"Objects":[{"StartTime":197956.0,"EndTime":197956.0,"X":218.0,"Y":119.0}]},{"StartTime":198168.0,"Objects":[{"StartTime":198168.0,"EndTime":198168.0,"X":179.0,"Y":297.0}]},{"StartTime":198379.0,"Objects":[{"StartTime":198379.0,"EndTime":198379.0,"X":317.0,"Y":223.0}]},{"StartTime":198591.0,"Objects":[{"StartTime":198591.0,"EndTime":198591.0,"X":144.0,"Y":279.0}]},{"StartTime":198801.0,"Objects":[{"StartTime":198801.0,"EndTime":198801.0,"X":295.0,"Y":284.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":198976.0,"EndTime":198976.0,"X":277.349548,"Y":195.747742,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":199224.0,"Objects":[{"StartTime":199224.0,"EndTime":199224.0,"X":489.0,"Y":254.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":199399.0,"EndTime":199399.0,"X":471.349548,"Y":342.252258,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":199646.0,"Objects":[{"StartTime":199646.0,"EndTime":199646.0,"X":277.0,"Y":195.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":199821.0,"EndTime":199821.0,"X":259.349548,"Y":106.747734,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":200069.0,"Objects":[{"StartTime":200069.0,"EndTime":200069.0,"X":506.0,"Y":165.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":200244.0,"EndTime":200244.0,"X":488.349548,"Y":253.252258,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":200491.0,"Objects":[{"StartTime":200491.0,"EndTime":200491.0,"X":301.0,"Y":42.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":200771.0,"EndTime":200771.0,"X":419.8098,"Y":32.4704971,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":200914.0,"Objects":[{"StartTime":200914.0,"EndTime":200914.0,"X":432.0,"Y":52.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":201089.0,"EndTime":201089.0,"X":422.412018,"Y":141.487823,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":201336.0,"Objects":[{"StartTime":201336.0,"EndTime":201336.0,"X":262.0,"Y":226.0}]},{"StartTime":201547.0,"Objects":[{"StartTime":201547.0,"EndTime":201547.0,"X":352.0,"Y":103.0}]},{"StartTime":201759.0,"Objects":[{"StartTime":201759.0,"EndTime":201759.0,"X":352.0,"Y":256.0}]},{"StartTime":201970.0,"Objects":[{"StartTime":201970.0,"EndTime":201970.0,"X":262.0,"Y":132.0}]},{"StartTime":202181.0,"Objects":[{"StartTime":202181.0,"EndTime":202181.0,"X":407.0,"Y":179.0}]},{"StartTime":202393.0,"Objects":[{"StartTime":202393.0,"EndTime":202393.0,"X":240.0,"Y":253.0}]},{"StartTime":202604.0,"Objects":[{"StartTime":202604.0,"EndTime":202604.0,"X":418.0,"Y":291.0}]},{"StartTime":202815.0,"Objects":[{"StartTime":202815.0,"EndTime":202815.0,"X":296.0,"Y":155.0}]},{"StartTime":203026.0,"Objects":[{"StartTime":203026.0,"EndTime":203026.0,"X":315.0,"Y":338.0}]},{"StartTime":203131.0,"Objects":[{"StartTime":203131.0,"EndTime":203131.0,"X":281.0,"Y":308.0}]},{"StartTime":203237.0,"Objects":[{"StartTime":203237.0,"EndTime":203237.0,"X":239.0,"Y":292.0}]},{"StartTime":203342.0,"Objects":[{"StartTime":203342.0,"EndTime":203342.0,"X":195.0,"Y":291.0}]},{"StartTime":203448.0,"Objects":[{"StartTime":203448.0,"EndTime":203448.0,"X":152.0,"Y":306.0}]},{"StartTime":203660.0,"Objects":[{"StartTime":203660.0,"EndTime":203660.0,"X":328.0,"Y":380.0}]},{"StartTime":203871.0,"Objects":[{"StartTime":203871.0,"EndTime":203871.0,"X":312.0,"Y":204.0}]},{"StartTime":204083.0,"Objects":[{"StartTime":204083.0,"EndTime":204083.0,"X":120.0,"Y":266.0}]},{"StartTime":204294.0,"Objects":[{"StartTime":204294.0,"EndTime":204294.0,"X":284.0,"Y":136.0}]},{"StartTime":204506.0,"Objects":[{"StartTime":204506.0,"EndTime":204506.0,"X":241.0,"Y":334.0}]},{"StartTime":204716.0,"Objects":[{"StartTime":204716.0,"EndTime":204716.0,"X":210.0,"Y":130.0}]},{"StartTime":204928.0,"Objects":[{"StartTime":204928.0,"EndTime":204928.0,"X":359.0,"Y":267.0}]},{"StartTime":205139.0,"Objects":[{"StartTime":205139.0,"EndTime":205139.0,"X":152.0,"Y":180.0}]},{"StartTime":205351.0,"Objects":[{"StartTime":205351.0,"EndTime":205351.0,"X":345.0,"Y":120.0}]},{"StartTime":205562.0,"Objects":[{"StartTime":205562.0,"EndTime":205562.0,"X":84.0,"Y":136.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":205737.0,"EndTime":205737.0,"X":83.80006,"Y":221.6485,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":205984.0,"Objects":[{"StartTime":205984.0,"EndTime":205984.0,"X":284.0,"Y":136.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":206159.0,"EndTime":206159.0,"X":284.199921,"Y":50.3514977,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":206407.0,"Objects":[{"StartTime":206407.0,"EndTime":206407.0,"X":184.0,"Y":248.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":206582.0,"EndTime":206582.0,"X":269.6485,"Y":248.199936,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":206829.0,"Objects":[{"StartTime":206829.0,"EndTime":206829.0,"X":180.0,"Y":28.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":207004.0,"EndTime":207004.0,"X":94.3514938,"Y":27.80006,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":207252.0,"Objects":[{"StartTime":207252.0,"EndTime":207252.0,"X":153.0,"Y":305.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":207532.0,"EndTime":207532.0,"X":151.988937,"Y":179.081238,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":207674.0,"Objects":[{"StartTime":207674.0,"EndTime":207674.0,"X":140.0,"Y":160.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":207849.0,"EndTime":207849.0,"X":54.3514977,"Y":159.800079,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":208097.0,"Objects":[{"StartTime":208097.0,"EndTime":208097.0,"X":72.0,"Y":336.0}]},{"StartTime":208308.0,"Objects":[{"StartTime":208308.0,"EndTime":208308.0,"X":256.0,"Y":292.0}]},{"StartTime":208519.0,"Objects":[{"StartTime":208519.0,"EndTime":208519.0,"X":100.0,"Y":224.0}]},{"StartTime":208730.0,"Objects":[{"StartTime":208730.0,"EndTime":208730.0,"X":204.0,"Y":381.0}]},{"StartTime":208942.0,"Objects":[{"StartTime":208942.0,"EndTime":208942.0,"X":351.0,"Y":209.0}]},{"StartTime":209153.0,"Objects":[{"StartTime":209153.0,"EndTime":209153.0,"X":178.0,"Y":305.0}]},{"StartTime":209364.0,"Objects":[{"StartTime":209364.0,"EndTime":209364.0,"X":312.0,"Y":344.0}]},{"StartTime":209576.0,"Objects":[{"StartTime":209576.0,"EndTime":209576.0,"X":217.0,"Y":171.0}]},{"StartTime":209787.0,"Objects":[{"StartTime":209787.0,"EndTime":209787.0,"X":472.0,"Y":144.0}]},{"StartTime":209998.0,"Objects":[{"StartTime":209998.0,"EndTime":209998.0,"X":264.0,"Y":259.0}]},{"StartTime":210209.0,"Objects":[{"StartTime":210209.0,"EndTime":210209.0,"X":425.0,"Y":306.0}]},{"StartTime":210421.0,"Objects":[{"StartTime":210421.0,"EndTime":210421.0,"X":311.0,"Y":98.0}]},{"StartTime":210632.0,"Objects":[{"StartTime":210632.0,"EndTime":210632.0,"X":332.0,"Y":312.0}]},{"StartTime":210843.0,"Objects":[{"StartTime":210843.0,"EndTime":210843.0,"X":396.0,"Y":100.0}]},{"StartTime":211055.0,"Objects":[{"StartTime":211055.0,"EndTime":211055.0,"X":192.0,"Y":160.0}]},{"StartTime":211266.0,"Objects":[{"StartTime":211266.0,"EndTime":211266.0,"X":403.0,"Y":224.0}]},{"StartTime":211477.0,"Objects":[{"StartTime":211477.0,"EndTime":211477.0,"X":328.0,"Y":24.0}]},{"StartTime":211688.0,"Objects":[{"StartTime":211688.0,"EndTime":211688.0,"X":255.0,"Y":267.0}]},{"StartTime":211900.0,"Objects":[{"StartTime":211900.0,"EndTime":211900.0,"X":488.0,"Y":198.0}]},{"StartTime":212111.0,"Objects":[{"StartTime":212111.0,"EndTime":212111.0,"X":247.0,"Y":125.0}]},{"StartTime":212322.0,"Objects":[{"StartTime":212322.0,"EndTime":212322.0,"X":392.0,"Y":312.0}]},{"StartTime":212533.0,"Objects":[{"StartTime":212533.0,"EndTime":212533.0,"X":334.0,"Y":66.0}]},{"StartTime":212745.0,"Objects":[{"StartTime":212745.0,"EndTime":212745.0,"X":342.0,"Y":351.0}]},{"StartTime":212956.0,"Objects":[{"StartTime":212956.0,"EndTime":212956.0,"X":372.0,"Y":100.0}]},{"StartTime":213167.0,"Objects":[{"StartTime":213167.0,"EndTime":213167.0,"X":251.0,"Y":373.0}]},{"StartTime":213378.0,"Objects":[{"StartTime":213378.0,"EndTime":213378.0,"X":402.0,"Y":170.0}]},{"StartTime":213590.0,"Objects":[{"StartTime":213590.0,"EndTime":213590.0,"X":136.0,"Y":327.0}]},{"StartTime":213801.0,"Objects":[{"StartTime":213801.0,"EndTime":213801.0,"X":382.0,"Y":270.0}]},{"StartTime":214012.0,"Objects":[{"StartTime":214012.0,"EndTime":214012.0,"X":212.0,"Y":144.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":214187.0,"EndTime":214187.0,"X":220.116043,"Y":240.231522,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":214435.0,"Objects":[{"StartTime":214435.0,"EndTime":214435.0,"X":152.0,"Y":88.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":214610.0,"EndTime":214610.0,"X":65.05222,"Y":46.2239647,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":214857.0,"Objects":[{"StartTime":214857.0,"EndTime":214857.0,"X":232.0,"Y":64.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":215032.0,"EndTime":215032.0,"X":310.786377,"Y":7.698365,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":215280.0,"Objects":[{"StartTime":215280.0,"EndTime":215280.0,"X":80.0,"Y":120.0}]},{"StartTime":215491.0,"Objects":[{"StartTime":215491.0,"EndTime":215491.0,"X":272.0,"Y":188.0}]},{"StartTime":215702.0,"Objects":[{"StartTime":215702.0,"EndTime":215702.0,"X":192.0,"Y":8.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":215877.0,"EndTime":215877.0,"X":194.429779,"Y":88.99472,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":216125.0,"Objects":[{"StartTime":216125.0,"EndTime":216125.0,"X":384.0,"Y":64.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":216300.0,"EndTime":216300.0,"X":328.026855,"Y":123.368477,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":216547.0,"Objects":[{"StartTime":216547.0,"EndTime":216547.0,"X":432.0,"Y":244.0}]},{"StartTime":216759.0,"Objects":[{"StartTime":216759.0,"EndTime":216759.0,"X":260.0,"Y":264.0}]},{"StartTime":216970.0,"Objects":[{"StartTime":216970.0,"EndTime":216970.0,"X":328.0,"Y":123.0}]},{"StartTime":217075.0,"Objects":[{"StartTime":217075.0,"EndTime":217075.0,"X":333.0,"Y":175.0}]},{"StartTime":217181.0,"Objects":[{"StartTime":217181.0,"EndTime":217181.0,"X":338.0,"Y":227.0}]},{"StartTime":217286.0,"Objects":[{"StartTime":217286.0,"EndTime":217286.0,"X":344.0,"Y":279.0}]},{"StartTime":217392.0,"Objects":[{"StartTime":217392.0,"EndTime":217392.0,"X":349.0,"Y":331.0}]},{"StartTime":218238.0,"Objects":[{"StartTime":218238.0,"EndTime":218238.0,"X":349.0,"Y":331.0}]},{"StartTime":218343.0,"Objects":[{"StartTime":218343.0,"EndTime":218343.0,"X":310.0,"Y":323.0}]},{"StartTime":218449.0,"Objects":[{"StartTime":218449.0,"EndTime":218449.0,"X":273.0,"Y":317.0}]},{"StartTime":218554.0,"Objects":[{"StartTime":218554.0,"EndTime":218554.0,"X":236.0,"Y":312.0}]},{"StartTime":218660.0,"Objects":[{"StartTime":218660.0,"EndTime":218660.0,"X":198.0,"Y":306.0}]},{"StartTime":218765.0,"Objects":[{"StartTime":218765.0,"EndTime":218765.0,"X":253.0,"Y":296.0}]},{"StartTime":218871.0,"Objects":[{"StartTime":218871.0,"EndTime":218871.0,"X":309.0,"Y":287.0}]},{"StartTime":218976.0,"Objects":[{"StartTime":218976.0,"EndTime":218976.0,"X":365.0,"Y":278.0}]},{"StartTime":219082.0,"Objects":[{"StartTime":219082.0,"EndTime":219082.0,"X":421.0,"Y":268.0}]},{"StartTime":219294.0,"Objects":[{"StartTime":219294.0,"EndTime":219294.0,"X":348.0,"Y":92.0}]},{"StartTime":219505.0,"Objects":[{"StartTime":219505.0,"EndTime":219505.0,"X":205.0,"Y":236.0}]},{"StartTime":219717.0,"Objects":[{"StartTime":219717.0,"EndTime":219717.0,"X":381.0,"Y":163.0}]},{"StartTime":219928.0,"Objects":[{"StartTime":219928.0,"EndTime":219928.0,"X":237.0,"Y":24.0}]},{"StartTime":220140.0,"Objects":[{"StartTime":220140.0,"EndTime":220140.0,"X":310.0,"Y":200.0}]},{"StartTime":220350.0,"Objects":[{"StartTime":220350.0,"EndTime":220350.0,"X":449.0,"Y":52.0}]},{"StartTime":220562.0,"Objects":[{"StartTime":220562.0,"EndTime":220562.0,"X":273.0,"Y":125.0}]},{"StartTime":220773.0,"Objects":[{"StartTime":220773.0,"EndTime":220773.0,"X":392.0,"Y":272.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":220948.0,"EndTime":220948.0,"X":493.387451,"Y":282.365265,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":221195.0,"Objects":[{"StartTime":221195.0,"EndTime":221195.0,"X":257.0,"Y":249.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":221370.0,"EndTime":221370.0,"X":168.323166,"Y":298.312439,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":221618.0,"Objects":[{"StartTime":221618.0,"EndTime":221618.0,"X":380.0,"Y":189.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":221793.0,"EndTime":221793.0,"X":421.2337,"Y":95.80929,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":222040.0,"Objects":[{"StartTime":222040.0,"EndTime":222040.0,"X":317.0,"Y":308.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":222215.0,"EndTime":222215.0,"X":392.657227,"Y":376.100861,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":222463.0,"Objects":[{"StartTime":222463.0,"EndTime":222463.0,"X":297.0,"Y":175.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":222743.0,"EndTime":222743.0,"X":252.84137,"Y":29.1527958,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":222885.0,"Objects":[{"StartTime":222885.0,"EndTime":222885.0,"X":253.0,"Y":29.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":223060.0,"EndTime":223060.0,"X":343.9761,"Y":72.90899,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":223308.0,"Objects":[{"StartTime":223308.0,"EndTime":223308.0,"X":168.0,"Y":34.0}]},{"StartTime":223519.0,"Objects":[{"StartTime":223519.0,"EndTime":223519.0,"X":63.0,"Y":216.0}]},{"StartTime":223731.0,"Objects":[{"StartTime":223731.0,"EndTime":223731.0,"X":220.0,"Y":125.0}]},{"StartTime":223942.0,"Objects":[{"StartTime":223942.0,"EndTime":223942.0,"X":10.0,"Y":125.0}]},{"StartTime":224153.0,"Objects":[{"StartTime":224153.0,"EndTime":224153.0,"X":168.0,"Y":216.0}]},{"StartTime":224364.0,"Objects":[{"StartTime":224364.0,"EndTime":224364.0,"X":63.0,"Y":34.0}]},{"StartTime":224576.0,"Objects":[{"StartTime":224576.0,"EndTime":224576.0,"X":0.0,"Y":264.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":224751.0,"EndTime":224751.0,"X":93.40772,"Y":288.831,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":224998.0,"Objects":[{"StartTime":224998.0,"EndTime":224998.0,"X":144.0,"Y":140.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":225067.0,"EndTime":225067.0,"X":149.111465,"Y":87.74942,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":225209.0,"Objects":[{"StartTime":225209.0,"EndTime":225209.0,"X":208.0,"Y":304.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":225278.0,"EndTime":225278.0,"X":201.982239,"Y":356.153961,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":225421.0,"Objects":[{"StartTime":225421.0,"EndTime":225421.0,"X":256.0,"Y":144.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":225490.0,"EndTime":225490.0,"X":261.111481,"Y":91.74942,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":225632.0,"Objects":[{"StartTime":225632.0,"EndTime":225632.0,"X":320.0,"Y":308.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":225701.0,"EndTime":225701.0,"X":313.982239,"Y":360.153961,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":225843.0,"Objects":[{"StartTime":225843.0,"EndTime":225843.0,"X":425.0,"Y":265.0}]},{"StartTime":226055.0,"Objects":[{"StartTime":226055.0,"EndTime":226055.0,"X":256.0,"Y":188.0}]},{"StartTime":226266.0,"Objects":[{"StartTime":226266.0,"EndTime":226266.0,"X":425.0,"Y":102.0}]},{"StartTime":226477.0,"Objects":[{"StartTime":226477.0,"EndTime":226477.0,"X":299.0,"Y":248.0}]},{"StartTime":226688.0,"Objects":[{"StartTime":226688.0,"EndTime":226688.0,"X":271.0,"Y":53.0}]},{"StartTime":226900.0,"Objects":[{"StartTime":226900.0,"EndTime":226900.0,"X":369.0,"Y":225.0}]},{"StartTime":227111.0,"Objects":[{"StartTime":227111.0,"EndTime":227111.0,"X":176.0,"Y":183.0}]},{"StartTime":227322.0,"Objects":[{"StartTime":227322.0,"EndTime":227322.0,"X":369.0,"Y":151.0}]},{"StartTime":227533.0,"Objects":[{"StartTime":227533.0,"EndTime":227533.0,"X":274.0,"Y":339.0}]},{"StartTime":227745.0,"Objects":[{"StartTime":227745.0,"EndTime":227745.0,"X":307.0,"Y":116.0}]},{"StartTime":227956.0,"Objects":[{"StartTime":227956.0,"EndTime":227956.0,"X":458.0,"Y":279.0}]},{"StartTime":228168.0,"Objects":[{"StartTime":228168.0,"EndTime":228168.0,"X":256.0,"Y":187.0}]},{"StartTime":228379.0,"Objects":[{"StartTime":228379.0,"EndTime":228379.0,"X":458.0,"Y":83.0}]},{"StartTime":228590.0,"Objects":[{"StartTime":228590.0,"EndTime":228590.0,"X":308.0,"Y":256.0}]},{"StartTime":228801.0,"Objects":[{"StartTime":228801.0,"EndTime":228801.0,"X":274.0,"Y":25.0}]},{"StartTime":229013.0,"Objects":[{"StartTime":229013.0,"EndTime":229013.0,"X":391.0,"Y":231.0}]},{"StartTime":229224.0,"Objects":[{"StartTime":229224.0,"EndTime":229224.0,"X":160.0,"Y":181.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":229399.0,"EndTime":229399.0,"X":175.200348,"Y":84.64736,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":229646.0,"Objects":[{"StartTime":229646.0,"EndTime":229646.0,"X":257.0,"Y":263.0}]},{"StartTime":229858.0,"Objects":[{"StartTime":229858.0,"EndTime":229858.0,"X":288.0,"Y":39.0}]},{"StartTime":230069.0,"Objects":[{"StartTime":230069.0,"EndTime":230069.0,"X":348.0,"Y":227.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":230244.0,"EndTime":230244.0,"X":257.087128,"Y":263.065033,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":230491.0,"Objects":[{"StartTime":230491.0,"EndTime":230491.0,"X":366.0,"Y":100.0}]},{"StartTime":230703.0,"Objects":[{"StartTime":230703.0,"EndTime":230703.0,"X":160.0,"Y":181.0}]},{"StartTime":230914.0,"Objects":[{"StartTime":230914.0,"EndTime":230914.0,"X":288.0,"Y":39.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":231089.0,"EndTime":231089.0,"X":366.498749,"Y":100.621391,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":231336.0,"Objects":[{"StartTime":231336.0,"EndTime":231336.0,"X":175.0,"Y":84.0}]},{"StartTime":231547.0,"Objects":[{"StartTime":231547.0,"EndTime":231547.0,"X":348.0,"Y":227.0}]},{"StartTime":231759.0,"Objects":[{"StartTime":231759.0,"EndTime":231759.0,"X":184.0,"Y":336.0}]},{"StartTime":231864.0,"Objects":[{"StartTime":231864.0,"EndTime":231864.0,"X":181.0,"Y":283.0}]},{"StartTime":231970.0,"Objects":[{"StartTime":231970.0,"EndTime":231970.0,"X":179.0,"Y":231.0}]},{"StartTime":232075.0,"Objects":[{"StartTime":232075.0,"EndTime":232075.0,"X":176.0,"Y":178.0}]},{"StartTime":232181.0,"Objects":[{"StartTime":232181.0,"EndTime":232181.0,"X":174.0,"Y":126.0}]},{"StartTime":232393.0,"Objects":[{"StartTime":232393.0,"EndTime":232393.0,"X":366.0,"Y":100.0}]},{"StartTime":232604.0,"Objects":[{"StartTime":232604.0,"EndTime":232604.0,"X":268.0,"Y":228.0}]},{"StartTime":232815.0,"Objects":[{"StartTime":232815.0,"EndTime":232815.0,"X":412.0,"Y":280.0}]},{"StartTime":233026.0,"Objects":[{"StartTime":233026.0,"EndTime":233026.0,"X":268.0,"Y":188.0}]},{"StartTime":233237.0,"Objects":[{"StartTime":233237.0,"EndTime":233237.0,"X":451.0,"Y":187.0}]},{"StartTime":233449.0,"Objects":[{"StartTime":233449.0,"EndTime":233449.0,"X":256.0,"Y":152.0}]},{"StartTime":233660.0,"Objects":[{"StartTime":233660.0,"EndTime":233660.0,"X":473.0,"Y":113.0}]},{"StartTime":233871.0,"Objects":[{"StartTime":233871.0,"EndTime":233871.0,"X":328.0,"Y":248.0}]},{"StartTime":234082.0,"Objects":[{"StartTime":234082.0,"EndTime":234082.0,"X":289.0,"Y":31.0}]},{"StartTime":234294.0,"Objects":[{"StartTime":234294.0,"EndTime":234294.0,"X":192.0,"Y":204.0}]},{"StartTime":234505.0,"Objects":[{"StartTime":234505.0,"EndTime":234505.0,"X":410.0,"Y":241.0}]},{"StartTime":234716.0,"Objects":[{"StartTime":234716.0,"EndTime":234716.0,"X":112.0,"Y":188.0}]},{"StartTime":234927.0,"Objects":[{"StartTime":234927.0,"EndTime":234927.0,"X":305.0,"Y":297.0}]},{"StartTime":235139.0,"Objects":[{"StartTime":235139.0,"EndTime":235139.0,"X":36.0,"Y":176.0}]},{"StartTime":235350.0,"Objects":[{"StartTime":235350.0,"EndTime":235350.0,"X":181.0,"Y":344.0}]},{"StartTime":235562.0,"Objects":[{"StartTime":235562.0,"EndTime":235562.0,"X":252.0,"Y":136.0}]},{"StartTime":235773.0,"Objects":[{"StartTime":235773.0,"EndTime":235773.0,"X":84.0,"Y":281.0}]},{"StartTime":235984.0,"Objects":[{"StartTime":235984.0,"EndTime":235984.0,"X":316.0,"Y":188.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":236159.0,"EndTime":236159.0,"X":320.0774,"Y":88.93266,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":236407.0,"Objects":[{"StartTime":236407.0,"EndTime":236407.0,"X":328.0,"Y":268.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":236582.0,"EndTime":236582.0,"X":399.9171,"Y":200.393,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":236829.0,"Objects":[{"StartTime":236829.0,"EndTime":236829.0,"X":276.0,"Y":333.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":237004.0,"EndTime":237004.0,"X":374.878357,"Y":336.5995,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":237252.0,"Objects":[{"StartTime":237252.0,"EndTime":237252.0,"X":316.0,"Y":188.0}]},{"StartTime":237463.0,"Objects":[{"StartTime":237463.0,"EndTime":237463.0,"X":204.0,"Y":296.0}]},{"StartTime":237674.0,"Objects":[{"StartTime":237674.0,"EndTime":237674.0,"X":452.0,"Y":336.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":237849.0,"EndTime":237849.0,"X":469.90686,"Y":232.5382,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":238097.0,"Objects":[{"StartTime":238097.0,"EndTime":238097.0,"X":209.0,"Y":104.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":238272.0,"EndTime":238272.0,"X":227.870361,"Y":207.290421,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":238519.0,"Objects":[{"StartTime":238519.0,"EndTime":238519.0,"X":425.0,"Y":45.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":238588.0,"EndTime":238588.0,"X":477.25058,"Y":50.11147,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":238731.0,"Objects":[{"StartTime":238731.0,"EndTime":238731.0,"X":421.0,"Y":157.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":238800.0,"EndTime":238800.0,"X":473.25058,"Y":162.111465,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":238942.0,"Objects":[{"StartTime":238942.0,"EndTime":238942.0,"X":227.0,"Y":207.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":239011.0,"EndTime":239011.0,"X":174.833221,"Y":201.09433,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":239153.0,"Objects":[{"StartTime":239153.0,"EndTime":239153.0,"X":223.0,"Y":319.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":239222.0,"EndTime":239222.0,"X":170.833221,"Y":313.09433,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":239364.0,"Objects":[{"StartTime":239364.0,"EndTime":239364.0,"X":475.0,"Y":370.0}]},{"StartTime":239576.0,"Objects":[{"StartTime":239576.0,"EndTime":239576.0,"X":496.0,"Y":228.0}]},{"StartTime":239787.0,"Objects":[{"StartTime":239787.0,"EndTime":239787.0,"X":380.0,"Y":344.0}]},{"StartTime":239999.0,"Objects":[{"StartTime":239999.0,"EndTime":239999.0,"X":405.0,"Y":173.0}]},{"StartTime":240209.0,"Objects":[{"StartTime":240209.0,"EndTime":240209.0,"X":272.0,"Y":320.0}]},{"StartTime":240421.0,"Objects":[{"StartTime":240421.0,"EndTime":240421.0,"X":302.0,"Y":114.0}]},{"StartTime":240632.0,"Objects":[{"StartTime":240632.0,"EndTime":240632.0,"X":156.0,"Y":300.0}]},{"StartTime":240844.0,"Objects":[{"StartTime":240844.0,"EndTime":240844.0,"X":192.0,"Y":52.0}]},{"StartTime":241055.0,"Objects":[{"StartTime":241055.0,"EndTime":241055.0,"X":20.0,"Y":164.0}]},{"StartTime":241267.0,"Objects":[{"StartTime":241267.0,"EndTime":241267.0,"X":252.0,"Y":84.0}]},{"StartTime":241477.0,"Objects":[{"StartTime":241477.0,"EndTime":241477.0,"X":40.0,"Y":8.0}]},{"StartTime":241689.0,"Objects":[{"StartTime":241689.0,"EndTime":241689.0,"X":240.0,"Y":164.0}]},{"StartTime":241900.0,"Objects":[{"StartTime":241900.0,"EndTime":241900.0,"X":116.0,"Y":28.0}]},{"StartTime":242111.0,"Objects":[{"StartTime":242111.0,"EndTime":242111.0,"X":80.0,"Y":274.0}]},{"StartTime":242322.0,"Objects":[{"StartTime":242322.0,"EndTime":242322.0,"X":32.0,"Y":88.0}]},{"StartTime":242534.0,"Objects":[{"StartTime":242534.0,"EndTime":242534.0,"X":227.0,"Y":242.0}]},{"StartTime":242745.0,"Objects":[{"StartTime":242745.0,"EndTime":242745.0,"X":218.0,"Y":61.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":242920.0,"EndTime":242920.0,"X":239.304214,"Y":163.81601,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":243167.0,"Objects":[{"StartTime":243167.0,"EndTime":243167.0,"X":131.0,"Y":120.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":243342.0,"EndTime":243342.0,"X":31.3882523,"Y":86.79608,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":243590.0,"Objects":[{"StartTime":243590.0,"EndTime":243590.0,"X":292.0,"Y":32.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":243765.0,"EndTime":243765.0,"X":313.30423,"Y":134.81601,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":244012.0,"Objects":[{"StartTime":244012.0,"EndTime":244012.0,"X":132.0,"Y":204.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":244187.0,"EndTime":244187.0,"X":32.3882523,"Y":170.796082,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":244435.0,"Objects":[{"StartTime":244435.0,"EndTime":244435.0,"X":368.0,"Y":4.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":244821.0,"EndTime":244821.0,"X":393.857056,"Y":151.754578,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":245280.0,"Objects":[{"StartTime":245280.0,"EndTime":245280.0,"X":136.0,"Y":288.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":245560.0,"EndTime":245560.0,"X":28.55529,"Y":254.65509,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":245702.0,"Objects":[{"StartTime":245702.0,"EndTime":245702.0,"X":31.7391319,"Y":257.739136}]},{"StartTime":245914.0,"Objects":[{"StartTime":245914.0,"EndTime":245914.0,"X":196.521729,"Y":236.521729}]},{"StartTime":246020.0,"Objects":[{"StartTime":246020.0,"EndTime":246020.0,"X":200.260864,"Y":240.260864}]},{"StartTime":246125.0,"Objects":[{"StartTime":246125.0,"EndTime":246125.0,"X":204.0,"Y":244.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":246300.0,"EndTime":246300.0,"X":198.885376,"Y":339.263245,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":246547.0,"Objects":[{"StartTime":246547.0,"EndTime":246547.0,"X":100.0,"Y":188.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":246722.0,"EndTime":246722.0,"X":93.48614,"Y":92.7003,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":246970.0,"Objects":[{"StartTime":246970.0,"EndTime":246970.0,"X":120.0,"Y":272.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":247145.0,"EndTime":247145.0,"X":24.73676,"Y":266.885345,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":247393.0,"Objects":[{"StartTime":247393.0,"EndTime":247393.0,"X":176.0,"Y":160.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":247568.0,"EndTime":247568.0,"X":271.263245,"Y":165.114624,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":247815.0,"Objects":[{"StartTime":247815.0,"EndTime":247815.0,"X":277.0,"Y":260.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":247990.0,"EndTime":247990.0,"X":270.486145,"Y":164.7003,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":248238.0,"Objects":[{"StartTime":248238.0,"EndTime":248238.0,"X":357.0,"Y":288.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":248413.0,"EndTime":248413.0,"X":276.222839,"Y":340.022369,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":248660.0,"Objects":[{"StartTime":248660.0,"EndTime":248660.0,"X":341.0,"Y":208.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":248835.0,"EndTime":248835.0,"X":425.827118,"Y":250.5753,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":249083.0,"Objects":[{"StartTime":249083.0,"EndTime":249083.0,"X":276.0,"Y":340.0}]},{"StartTime":249294.0,"Objects":[{"StartTime":249294.0,"EndTime":249294.0,"X":341.0,"Y":208.0}]},{"StartTime":249505.0,"Objects":[{"StartTime":249505.0,"EndTime":249505.0,"X":200.0,"Y":120.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":249680.0,"EndTime":249680.0,"X":101.756859,"Y":119.9113,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":249928.0,"Objects":[{"StartTime":249928.0,"EndTime":249928.0,"X":64.0,"Y":300.0}]},{"StartTime":250139.0,"Objects":[{"StartTime":250139.0,"EndTime":250139.0,"X":152.0,"Y":176.0}]},{"StartTime":250350.0,"Objects":[{"StartTime":250350.0,"EndTime":250350.0,"X":12.0,"Y":196.0}]},{"StartTime":250561.0,"Objects":[{"StartTime":250561.0,"EndTime":250561.0,"X":164.0,"Y":210.0}]},{"StartTime":250773.0,"Objects":[{"StartTime":250773.0,"EndTime":250773.0,"X":32.0,"Y":88.0}]},{"StartTime":250984.0,"Objects":[{"StartTime":250984.0,"EndTime":250984.0,"X":49.0,"Y":269.0}]},{"StartTime":251195.0,"Objects":[{"StartTime":251195.0,"EndTime":251195.0,"X":218.0,"Y":129.0}]},{"StartTime":251406.0,"Objects":[{"StartTime":251406.0,"EndTime":251406.0,"X":293.0,"Y":294.0}]},{"StartTime":251618.0,"Objects":[{"StartTime":251618.0,"EndTime":251618.0,"X":341.0,"Y":84.0}]},{"StartTime":251829.0,"Objects":[{"StartTime":251829.0,"EndTime":251829.0,"X":164.0,"Y":210.0}]},{"StartTime":252040.0,"Objects":[{"StartTime":252040.0,"EndTime":252040.0,"X":400.0,"Y":176.0}]},{"StartTime":252251.0,"Objects":[{"StartTime":252251.0,"EndTime":252251.0,"X":232.0,"Y":80.0}]},{"StartTime":252463.0,"Objects":[{"StartTime":252463.0,"EndTime":252463.0,"X":340.0,"Y":272.0}]},{"StartTime":252674.0,"Objects":[{"StartTime":252674.0,"EndTime":252674.0,"X":456.0,"Y":80.0}]},{"StartTime":252885.0,"Objects":[{"StartTime":252885.0,"EndTime":252885.0,"X":452.0,"Y":316.0}]},{"StartTime":253307.0,"Objects":[{"StartTime":253307.0,"EndTime":253307.0,"X":452.0,"Y":316.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":253482.0,"EndTime":253482.0,"X":474.438171,"Y":213.4255,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":253730.0,"Objects":[{"StartTime":253730.0,"EndTime":253730.0,"X":284.0,"Y":220.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":253905.0,"EndTime":253905.0,"X":306.438171,"Y":117.4255,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":254153.0,"Objects":[{"StartTime":254153.0,"EndTime":254153.0,"X":116.0,"Y":132.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":254328.0,"EndTime":254328.0,"X":138.438171,"Y":29.425499,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":254576.0,"Objects":[{"StartTime":254576.0,"EndTime":254576.0,"X":36.0,"Y":236.0}]},{"StartTime":254998.0,"Objects":[{"StartTime":254998.0,"EndTime":254998.0,"X":36.0,"Y":236.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":255173.0,"EndTime":255173.0,"X":111.50975,"Y":251.103058,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":255421.0,"Objects":[{"StartTime":255421.0,"EndTime":255421.0,"X":204.0,"Y":152.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":255596.0,"EndTime":255596.0,"X":279.509766,"Y":167.103058,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":255843.0,"Objects":[{"StartTime":255843.0,"EndTime":255843.0,"X":356.0,"Y":56.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":256018.0,"EndTime":256018.0,"X":431.509766,"Y":71.10306,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":256266.0,"Objects":[{"StartTime":256266.0,"EndTime":256266.0,"X":356.0,"Y":204.0}]},{"StartTime":256688.0,"Objects":[{"StartTime":256688.0,"EndTime":256688.0,"X":356.0,"Y":204.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":256863.0,"EndTime":256863.0,"X":358.602356,"Y":299.339142,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":257111.0,"Objects":[{"StartTime":257111.0,"EndTime":257111.0,"X":252.0,"Y":184.0}]},{"StartTime":257322.0,"Objects":[{"StartTime":257322.0,"EndTime":257322.0,"X":296.0,"Y":340.0}]},{"StartTime":257533.0,"Objects":[{"StartTime":257533.0,"EndTime":257533.0,"X":192.0,"Y":272.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":257708.0,"EndTime":257708.0,"X":295.660339,"Y":255.2806,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":257956.0,"Objects":[{"StartTime":257956.0,"EndTime":257956.0,"X":117.0,"Y":119.0}]},{"StartTime":258167.0,"Objects":[{"StartTime":258167.0,"EndTime":258167.0,"X":285.0,"Y":31.0}]},{"StartTime":258378.0,"Objects":[{"StartTime":258378.0,"EndTime":258378.0,"X":137.0,"Y":31.0}]},{"StartTime":258589.0,"Objects":[{"StartTime":258589.0,"EndTime":258589.0,"X":305.0,"Y":119.0}]},{"StartTime":258801.0,"Objects":[{"StartTime":258801.0,"EndTime":258801.0,"X":49.0,"Y":55.0}]},{"StartTime":258906.0,"Objects":[{"StartTime":258906.0,"EndTime":258906.0,"X":26.0,"Y":101.0}]},{"StartTime":259012.0,"Objects":[{"StartTime":259012.0,"EndTime":259012.0,"X":32.0,"Y":153.0}]},{"StartTime":259117.0,"Objects":[{"StartTime":259117.0,"EndTime":259117.0,"X":64.0,"Y":194.0}]},{"StartTime":259223.0,"Objects":[{"StartTime":259223.0,"EndTime":259223.0,"X":112.0,"Y":212.0}]},{"StartTime":259435.0,"Objects":[{"StartTime":259435.0,"EndTime":259435.0,"X":255.0,"Y":75.0}]},{"StartTime":259646.0,"Objects":[{"StartTime":259646.0,"EndTime":259646.0,"X":240.0,"Y":252.0}]},{"StartTime":259857.0,"Objects":[{"StartTime":259857.0,"EndTime":259857.0,"X":112.0,"Y":212.0}]},{"StartTime":260068.0,"Objects":[{"StartTime":260068.0,"EndTime":260068.0,"X":236.0,"Y":330.0}]},{"StartTime":260280.0,"Objects":[{"StartTime":260280.0,"EndTime":260280.0,"X":114.0,"Y":133.0}]},{"StartTime":260491.0,"Objects":[{"StartTime":260491.0,"EndTime":260491.0,"X":146.0,"Y":308.0}]},{"StartTime":260702.0,"Objects":[{"StartTime":260702.0,"EndTime":260702.0,"X":204.0,"Y":154.0}]},{"StartTime":260914.0,"Objects":[{"StartTime":260914.0,"EndTime":260914.0,"X":51.0,"Y":304.0}]},{"StartTime":261125.0,"Objects":[{"StartTime":261125.0,"EndTime":261125.0,"X":298.0,"Y":156.0}]},{"StartTime":261336.0,"Objects":[{"StartTime":261336.0,"EndTime":261336.0,"X":28.0,"Y":232.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":261511.0,"EndTime":261511.0,"X":26.3694744,"Y":134.648315,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":261759.0,"Objects":[{"StartTime":261759.0,"EndTime":261759.0,"X":320.0,"Y":228.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":261934.0,"EndTime":261934.0,"X":321.6305,"Y":325.351685,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":262181.0,"Objects":[{"StartTime":262181.0,"EndTime":262181.0,"X":64.0,"Y":208.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":262356.0,"EndTime":262356.0,"X":59.4033928,"Y":109.17572,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":262604.0,"Objects":[{"StartTime":262604.0,"EndTime":262604.0,"X":364.0,"Y":248.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":262779.0,"EndTime":262779.0,"X":367.656372,"Y":346.437134,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":263026.0,"Objects":[{"StartTime":263026.0,"EndTime":263026.0,"X":484.0,"Y":148.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":263306.0,"EndTime":263306.0,"X":348.198273,"Y":146.574341,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":263449.0,"Objects":[{"StartTime":263449.0,"EndTime":263449.0,"X":315.0,"Y":131.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":263624.0,"EndTime":263624.0,"X":216.875748,"Y":124.6084,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":263871.0,"Objects":[{"StartTime":263871.0,"EndTime":263871.0,"X":192.0,"Y":300.0}]},{"StartTime":264083.0,"Objects":[{"StartTime":264083.0,"EndTime":264083.0,"X":264.0,"Y":188.0}]},{"StartTime":264294.0,"Objects":[{"StartTime":264294.0,"EndTime":264294.0,"X":172.0,"Y":208.0}]},{"StartTime":264506.0,"Objects":[{"StartTime":264506.0,"EndTime":264506.0,"X":284.0,"Y":280.0}]},{"StartTime":264716.0,"Objects":[{"StartTime":264716.0,"EndTime":264716.0,"X":160.0,"Y":44.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":264996.0,"EndTime":264996.0,"X":161.425659,"Y":179.801727,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":265139.0,"Objects":[{"StartTime":265139.0,"EndTime":265139.0,"X":172.0,"Y":208.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":265314.0,"EndTime":265314.0,"X":163.511826,"Y":305.6148,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":265562.0,"Objects":[{"StartTime":265562.0,"EndTime":265562.0,"X":104.0,"Y":252.0}]},{"StartTime":265773.0,"Objects":[{"StartTime":265773.0,"EndTime":265773.0,"X":264.0,"Y":352.0}]},{"StartTime":265984.0,"Objects":[{"StartTime":265984.0,"EndTime":265984.0,"X":76.0,"Y":352.0}]},{"StartTime":266195.0,"Objects":[{"StartTime":266195.0,"EndTime":266195.0,"X":248.0,"Y":252.0}]},{"StartTime":266407.0,"Objects":[{"StartTime":266407.0,"EndTime":266407.0,"X":132.0,"Y":112.0}]},{"StartTime":266618.0,"Objects":[{"StartTime":266618.0,"EndTime":266618.0,"X":22.0,"Y":288.0}]},{"StartTime":266829.0,"Objects":[{"StartTime":266829.0,"EndTime":266829.0,"X":22.0,"Y":81.0}]},{"StartTime":267040.0,"Objects":[{"StartTime":267040.0,"EndTime":267040.0,"X":132.0,"Y":270.0}]},{"StartTime":267252.0,"Objects":[{"StartTime":267252.0,"EndTime":267252.0,"X":240.0,"Y":112.0}]},{"StartTime":267463.0,"Objects":[{"StartTime":267463.0,"EndTime":267463.0,"X":350.0,"Y":288.0}]},{"StartTime":267674.0,"Objects":[{"StartTime":267674.0,"EndTime":267674.0,"X":350.0,"Y":81.0}]},{"StartTime":267885.0,"Objects":[{"StartTime":267885.0,"EndTime":267885.0,"X":240.0,"Y":270.0}]},{"StartTime":268097.0,"Objects":[{"StartTime":268097.0,"EndTime":268097.0,"X":512.0,"Y":212.0}]},{"StartTime":268308.0,"Objects":[{"StartTime":268308.0,"EndTime":268308.0,"X":290.0,"Y":94.0}]},{"StartTime":268519.0,"Objects":[{"StartTime":268519.0,"EndTime":268519.0,"X":415.0,"Y":310.0}]},{"StartTime":268730.0,"Objects":[{"StartTime":268730.0,"EndTime":268730.0,"X":417.0,"Y":47.0}]},{"StartTime":268942.0,"Objects":[{"StartTime":268942.0,"EndTime":268942.0,"X":168.0,"Y":180.0}]},{"StartTime":269153.0,"Objects":[{"StartTime":269153.0,"EndTime":269153.0,"X":416.0,"Y":214.0}]},{"StartTime":269364.0,"Objects":[{"StartTime":269364.0,"EndTime":269364.0,"X":225.0,"Y":54.0}]},{"StartTime":269576.0,"Objects":[{"StartTime":269576.0,"EndTime":269576.0,"X":313.0,"Y":302.0}]},{"StartTime":269787.0,"Objects":[{"StartTime":269787.0,"EndTime":269787.0,"X":376.0,"Y":172.0}]},{"StartTime":269998.0,"Objects":[{"StartTime":269998.0,"EndTime":269998.0,"X":177.0,"Y":242.0}]},{"StartTime":270209.0,"Objects":[{"StartTime":270209.0,"EndTime":270209.0,"X":345.0,"Y":147.0}]},{"StartTime":270420.0,"Objects":[{"StartTime":270420.0,"EndTime":270420.0,"X":215.0,"Y":254.0}]},{"StartTime":270632.0,"Objects":[{"StartTime":270632.0,"EndTime":270632.0,"X":325.0,"Y":146.0}]},{"StartTime":270843.0,"Objects":[{"StartTime":270843.0,"EndTime":270843.0,"X":237.0,"Y":249.0}]},{"StartTime":271055.0,"Objects":[{"StartTime":271055.0,"EndTime":271055.0,"X":333.0,"Y":238.0}]},{"StartTime":271266.0,"Objects":[{"StartTime":271266.0,"EndTime":271266.0,"X":230.0,"Y":151.0}]},{"StartTime":271477.0,"Objects":[{"StartTime":271477.0,"EndTime":271477.0,"X":292.0,"Y":312.0}]},{"StartTime":271583.0,"Objects":[{"StartTime":271583.0,"EndTime":272745.0,"X":256.0,"Y":192.0}]},{"StartTime":273167.0,"Objects":[{"StartTime":273167.0,"EndTime":273167.0,"X":163.0,"Y":256.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":273342.0,"EndTime":273342.0,"X":78.18209,"Y":248.905472,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":273590.0,"Objects":[{"StartTime":273590.0,"EndTime":273590.0,"X":68.0,"Y":364.0}]},{"StartTime":273801.0,"Objects":[{"StartTime":273801.0,"EndTime":273801.0,"X":236.0,"Y":324.0}]},{"StartTime":274012.0,"Objects":[{"StartTime":274012.0,"EndTime":274012.0,"X":79.0,"Y":249.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":274187.0,"EndTime":274187.0,"X":88.9388351,"Y":159.550461,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":274435.0,"Objects":[{"StartTime":274435.0,"EndTime":274435.0,"X":280.0,"Y":264.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":274610.0,"EndTime":274610.0,"X":289.938843,"Y":353.449524,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":274857.0,"Objects":[{"StartTime":274857.0,"EndTime":274857.0,"X":420.0,"Y":130.0}]},{"StartTime":275068.0,"Objects":[{"StartTime":275068.0,"EndTime":275068.0,"X":373.0,"Y":261.0}]},{"StartTime":275279.0,"Objects":[{"StartTime":275279.0,"EndTime":275279.0,"X":512.0,"Y":227.0}]},{"StartTime":275491.0,"Objects":[{"StartTime":275491.0,"EndTime":275491.0,"X":354.0,"Y":183.0}]},{"StartTime":275702.0,"Objects":[{"StartTime":275702.0,"EndTime":275702.0,"X":308.0,"Y":358.0}]},{"StartTime":275913.0,"Objects":[{"StartTime":275913.0,"EndTime":275913.0,"X":478.0,"Y":313.0}]},{"StartTime":276125.0,"Objects":[{"StartTime":276125.0,"EndTime":276125.0,"X":245.0,"Y":278.0}]},{"StartTime":276336.0,"Objects":[{"StartTime":276336.0,"EndTime":276336.0,"X":482.0,"Y":205.0}]},{"StartTime":276547.0,"Objects":[{"StartTime":276547.0,"EndTime":276547.0,"X":349.0,"Y":94.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":276722.0,"EndTime":276722.0,"X":354.7944,"Y":183.813278,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":276970.0,"Objects":[{"StartTime":276970.0,"EndTime":276970.0,"X":239.0,"Y":240.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":277145.0,"EndTime":277145.0,"X":157.7837,"Y":226.0501,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":277393.0,"Objects":[{"StartTime":277393.0,"EndTime":277393.0,"X":0.0,"Y":268.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":277568.0,"EndTime":277568.0,"X":81.70373,"Y":254.311035,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":277815.0,"Objects":[{"StartTime":277815.0,"EndTime":277815.0,"X":128.0,"Y":380.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":277990.0,"EndTime":277990.0,"X":143.305069,"Y":299.2002,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":278237.0,"Objects":[{"StartTime":278237.0,"EndTime":278237.0,"X":116.0,"Y":96.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":278412.0,"EndTime":278412.0,"X":101.614624,"Y":177.390518,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":278660.0,"Objects":[{"StartTime":278660.0,"EndTime":278660.0,"X":104.0,"Y":16.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":278835.0,"EndTime":278835.0,"X":36.5809135,"Y":63.969265,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":279082.0,"Objects":[{"StartTime":279082.0,"EndTime":279082.0,"X":180.0,"Y":48.0}]},{"StartTime":279294.0,"Objects":[{"StartTime":279294.0,"EndTime":279294.0,"X":32.0,"Y":140.0}]},{"StartTime":279505.0,"Objects":[{"StartTime":279505.0,"EndTime":279505.0,"X":180.0,"Y":48.0}]},{"StartTime":279717.0,"Objects":[{"StartTime":279717.0,"EndTime":279717.0,"X":140.0,"Y":216.0}]},{"StartTime":279928.0,"Objects":[{"StartTime":279928.0,"EndTime":279928.0,"X":265.0,"Y":71.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":280103.0,"EndTime":280103.0,"X":243.523376,"Y":153.8613,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":280350.0,"Objects":[{"StartTime":280350.0,"EndTime":280350.0,"X":416.0,"Y":248.0}]},{"StartTime":280562.0,"Objects":[{"StartTime":280562.0,"EndTime":280562.0,"X":316.0,"Y":132.0}]},{"StartTime":280773.0,"Objects":[{"StartTime":280773.0,"EndTime":280773.0,"X":252.0,"Y":264.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":280948.0,"EndTime":280948.0,"X":341.449524,"Y":254.061157,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":281196.0,"Objects":[{"StartTime":281196.0,"EndTime":281196.0,"X":484.0,"Y":148.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":281371.0,"EndTime":281371.0,"X":394.550476,"Y":138.061157,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":281618.0,"Objects":[{"StartTime":281618.0,"EndTime":281618.0,"X":426.0,"Y":338.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":281793.0,"EndTime":281793.0,"X":416.945068,"Y":248.456665,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":282041.0,"Objects":[{"StartTime":282041.0,"EndTime":282041.0,"X":326.0,"Y":43.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":282216.0,"EndTime":282216.0,"X":316.061157,"Y":132.449539,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":282463.0,"Objects":[{"StartTime":282463.0,"EndTime":282463.0,"X":296.0,"Y":296.0}]},{"StartTime":282674.0,"Objects":[{"StartTime":282674.0,"EndTime":282674.0,"X":417.0,"Y":249.0}]},{"StartTime":282885.0,"Objects":[{"StartTime":282885.0,"EndTime":282885.0,"X":248.0,"Y":216.0}]},{"StartTime":283097.0,"Objects":[{"StartTime":283097.0,"EndTime":283097.0,"X":321.0,"Y":376.0}]},{"StartTime":283308.0,"Objects":[{"StartTime":283308.0,"EndTime":283308.0,"X":370.0,"Y":163.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":283483.0,"EndTime":283483.0,"X":379.938843,"Y":73.55046,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":283730.0,"Objects":[{"StartTime":283730.0,"EndTime":283730.0,"X":248.0,"Y":216.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":283905.0,"EndTime":283905.0,"X":257.938843,"Y":126.550461,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":284153.0,"Objects":[{"StartTime":284153.0,"EndTime":284153.0,"X":122.0,"Y":266.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":284328.0,"EndTime":284328.0,"X":131.938843,"Y":176.550461,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":284575.0,"Objects":[{"StartTime":284575.0,"EndTime":284575.0,"X":200.0,"Y":280.0}]},{"StartTime":284787.0,"Objects":[{"StartTime":284787.0,"EndTime":284787.0,"X":56.0,"Y":144.0}]},{"StartTime":284998.0,"Objects":[{"StartTime":284998.0,"EndTime":284998.0,"X":69.0,"Y":335.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":285173.0,"EndTime":285173.0,"X":151.50708,"Y":340.3292,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":285420.0,"Objects":[{"StartTime":285420.0,"EndTime":285420.0,"X":213.0,"Y":180.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":285595.0,"EndTime":285595.0,"X":130.326477,"Y":176.450455,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":285843.0,"Objects":[{"StartTime":285843.0,"EndTime":285843.0,"X":304.0,"Y":272.0}]},{"StartTime":285948.0,"Objects":[{"StartTime":285948.0,"EndTime":285948.0,"X":299.0,"Y":228.0}]},{"StartTime":286054.0,"Objects":[{"StartTime":286054.0,"EndTime":286054.0,"X":294.0,"Y":183.0}]},{"StartTime":286159.0,"Objects":[{"StartTime":286159.0,"EndTime":286159.0,"X":288.0,"Y":138.0}]},{"StartTime":286265.0,"Objects":[{"StartTime":286265.0,"EndTime":286265.0,"X":283.0,"Y":94.0}]},{"StartTime":286477.0,"Objects":[{"StartTime":286477.0,"EndTime":286477.0,"X":156.521729,"Y":44.5217361}]},{"StartTime":286583.0,"Objects":[{"StartTime":286583.0,"EndTime":286583.0,"X":160.260864,"Y":48.2608681}]},{"StartTime":286688.0,"Objects":[{"StartTime":286688.0,"EndTime":286688.0,"X":164.0,"Y":52.0,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":287110.0,"EndTime":287110.0,"X":183.3807,"Y":124.354652,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":287533.0,"EndTime":287533.0,"X":172.208191,"Y":190.150177,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":287955.0,"EndTime":287955.0,"X":124.254967,"Y":247.694046,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":288378.0,"EndTime":288378.0,"X":173.0462,"Y":261.451965,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":288800.0,"EndTime":288800.0,"X":242.3152,"Y":273.1244,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":289223.0,"EndTime":289223.0,"X":282.0523,"Y":336.8299,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":289645.0,"EndTime":289645.0,"X":313.3097,"Y":323.5751,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":290068.0,"EndTime":290068.0,"X":338.3643,"Y":252.795883,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":290490.0,"EndTime":290490.0,"X":410.361755,"Y":235.620316,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":290913.0,"EndTime":290913.0,"X":431.88385,"Y":207.80217,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":291335.0,"EndTime":291335.0,"X":373.0279,"Y":161.46875,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":291758.0,"EndTime":291758.0,"X":367.150818,"Y":92.54223,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":292180.0,"EndTime":292180.0,"X":357.807159,"Y":45.76682,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":292603.0,"EndTime":292603.0,"X":294.6491,"Y":86.36842,"StackOffset":{"X":0.0,"Y":0.0}},{"StartTime":292990.0,"EndTime":292990.0,"X":228.255249,"Y":76.85775,"StackOffset":{"X":0.0,"Y":0.0}}]},{"StartTime":293238.0,"Objects":[{"StartTime":293238.0,"EndTime":293238.0,"X":231.739136,"Y":79.7391357}]},{"StartTime":293343.0,"Objects":[{"StartTime":293343.0,"EndTime":301900.0,"X":256.0,"Y":192.0}]}]} \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/1124896.osu b/osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/1124896.osu new file mode 100644 index 0000000000..8a9b18ae9c --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/1124896.osu @@ -0,0 +1,1122 @@ +osu file format v14 + +[General] +StackLeniency: 0.6 +Mode: 0 + +[Difficulty] +HPDrainRate:6 +CircleSize:3.8 +OverallDifficulty:7.5 +ApproachRate:8.7 +SliderMultiplier:1.5 +SliderTickRate:1 + +[Events] +//Background and Video events +//Break Periods +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Sound Samples + +[TimingPoints] +1055,422.535211267606,4,2,1,35,1,0 +1055,-111.111111111111,4,2,1,35,0,0 +8660,-111.111111111111,4,2,1,10,0,0 +8871,-111.111111111111,4,2,1,35,0,0 +13942,-111.111111111111,4,2,2,60,0,0 +14470,-111.111111111111,4,2,2,5,0,0 +14576,-100,4,2,2,45,0,0 +25562,-200,4,2,2,40,0,0 +28097,-100,4,2,2,40,0,0 +41618,-100,4,2,2,50,0,0 +55139,-100,4,2,3,45,0,0 +68660,-83.3333333333333,4,2,2,50,0,0 +69294,-50,4,2,2,50,0,0 +69505,-83.3333333333333,4,2,2,50,0,0 +70139,-50,4,2,2,50,0,0 +70350,-83.3333333333333,4,2,2,50,0,0 +70984,-50,4,2,2,50,0,0 +71195,-83.3333333333333,4,2,2,50,0,0 +71829,-50,4,2,2,50,0,0 +72040,-83.3333333333333,4,2,2,50,0,0 +72674,-50,4,2,2,50,0,0 +74576,-55.5555555555556,4,2,1,50,0,0 +75421,-76.9230769230769,4,2,2,60,0,1 +100773,-100,4,2,2,50,0,0 +102463,-83.3333333333333,4,2,2,50,0,0 +115984,-100,4,2,2,50,0,0 +129505,-100,4,2,3,45,0,0 +143026,-83.3333333333333,4,2,2,50,0,0 +143660,-50,4,2,2,50,0,0 +143871,-83.3333333333333,4,2,2,50,0,0 +144505,-50,4,2,2,50,0,0 +144716,-83.3333333333333,4,2,2,50,0,0 +145350,-50,4,2,2,50,0,0 +145562,-83.3333333333333,4,2,2,50,0,0 +146195,-50,4,2,2,50,0,0 +146407,-83.3333333333333,4,2,2,50,0,0 +147040,-50,4,2,2,50,0,0 +148942,-55.5555555555556,4,2,1,50,0,0 +149787,-76.9230769230769,4,2,2,60,0,1 +175139,-100,4,2,2,50,0,0 +175562,-83.3333333333333,4,2,3,50,0,0 +185280,-76.9230769230769,4,2,3,50,0,0 +186970,-71.4285714285714,4,2,3,50,0,0 +190350,-83.3333333333333,4,2,2,50,0,0 +214012,-71.4285714285714,4,2,2,50,0,0 +219083,-71.4285714285714,4,2,2,60,0,1 +244435,-100,4,2,2,50,0,0 +246125,-71.4285714285714,4,2,2,60,0,1 +273167,-83.3333333333333,4,2,2,50,0,0 +286688,-200,4,2,2,50,0,0 +293238,-200,4,2,0,30,0,0 +293343,-200,4,2,0,5,0,0 + +[HitObjects] +92,96,633,5,0,0:0:0:0: +92,96,844,1,0,0:0:0:0: +92,96,1055,6,0,L|76:164,1,67.4999979400635,2|0,3:0|0:0,0:0:0:0: +200,100,1477,2,0,L|184:34,1,67.4999979400635,0|2,0:0|0:3,0:0:0:0: +164,228,1900,1,0,0:0:0:0: +256,240,2111,1,0,0:0:0:0: +340,192,2322,2,0,P|352:160|348:120,1,67.4999979400635,2|0,0:3|0:0,0:0:0:0: +440,200,2745,6,0,P|438:233|450:264,1,67.4999979400635,2|0,0:3|0:0,0:0:0:0: +332,316,3167,1,0,0:0:0:0: +332,316,3378,2,0,B|280:296|224:320|224:320|268:344,1,134.999995880127,2|0,0:3|0:0,0:0:0:0: +332,316,4012,1,2,0:3:0:0: +312,224,4224,1,0,0:0:0:0: +284,132,4435,6,0,P|248:124|216:132,1,67.4999979400635,2|0,0:3|0:0,0:0:0:0: +400,192,4857,2,0,P|436:200|468:192,1,67.4999979400635,2|0,0:3|0:3,0:3:0:0: +312,224,5280,2,0,P|304:260|312:292,1,67.4999979400635,2|0,0:3|0:0,0:0:0:0: +376,108,5702,1,2,0:3:0:0: +376,108,5914,2,0,B|336:132|336:132|232:108,1,134.999995880127,2|0,0:3|0:0,0:0:0:0: +154,122,6547,6,0,P|159:80|174:56,1,67.4999979400635,2|0,0:3|0:0,0:0:0:0: +107,195,6970,2,0,P|73:160|68:132,2,67.4999979400635,2|0|2,0:3|0:0|0:3,0:0:0:0: +216,232,7604,1,0,0:0:0:0: +116,280,7815,6,0,P|76:280|51:263,1,67.4999979400635,2|0,0:3|0:0,0:0:0:0: +176,160,8238,1,0,0:0:0:0: +248,291,8449,2,0,P|292:291|336:335,1,101.249996910095,2|0,0:3|0:0,0:0:0:0: +334,328,8871,2,0,L|318:189,1,134.999995880127,2|0,0:3|0:0,0:0:0:0: +428,184,9505,6,0,L|436:250,1,67.4999979400635,2|0,0:3|0:0,0:0:0:0: +328,128,9928,2,0,L|319:194,1,67.4999979400635,0|2,0:0|0:3,0:0:0:0: +320,108,10350,1,0,0:0:0:0: +308,88,10773,1,2,0:3:0:0: +296,68,11195,6,0,L|212:64,1,67.4999979400635,2|0,0:3|0:0,0:0:0:0: +318,194,11618,1,0,0:0:0:0: +288,52,11829,2,0,L|204:48,1,67.4999979400635,2|0,0:3|0:0,0:0:0:0: +236,248,12252,1,0,0:0:0:0: +299,170,12463,1,2,0:3:0:0: +300,300,12674,1,0,0:0:0:0: +168,204,12885,6,0,L|84:200,2,67.4999979400635,2|0|0,0:3|0:0|0:0,0:0:0:0: +227,332,13519,2,0,L|160:336,1,67.4999979400635,2|0,0:3|0:0,0:0:0:0: +303,366,13942,1,2,0:0:0:0: +302,365,14153,2,0,P|308:332|299:299,1,67.4999979400635,2|0,0:0|0:0,0:0:0:0: +469,258,14576,6,0,L|452:333,1,75,4|0,0:0|0:0,0:0:0:0: +376,256,14998,2,0,L|359:182,1,75,0|2,0:0|0:0,0:0:0:0: +384,80,15421,1,0,0:0:0:0: +282,102,15632,1,2,0:0:0:0: +436,148,15843,1,0,0:0:0:0: +274,186,16055,5,2,0:0:0:0: +274,186,16160,1,0,0:0:0:0: +274,186,16266,2,0,L|257:261,1,75,0|2,0:0|0:0,0:0:0:0: +160,202,16688,2,0,L|143:128,1,75,0|2,0:0|0:0,0:0:0:0: +79,35,17111,1,0,0:0:0:0: +23,123,17322,1,2,0:0:0:0: +161,42,17533,1,0,0:0:0:0: +76,188,17745,1,2,0:0:0:0: +79,35,17956,6,0,L|105:126,1,75,0|2,0:0|0:0,0:0:0:0: +211,104,18378,2,0,L|237:195,1,75,0|2,0:0|0:0,0:0:0:0: +344,170,18801,2,0,L|370:261,1,75,0|2,0:0|0:0,0:0:0:0: +433,132,19224,1,0,0:0:0:0: +372,249,19435,5,2,0:0:0:0: +372,249,19540,1,0,0:0:0:0: +372,249,19646,2,0,P|414:259|452:250,1,75,0|2,0:0|0:0,0:0:0:0: +468,104,20069,1,0,0:0:0:0: +413,180,20280,1,2,0:0:0:0: +324,58,20491,1,2,0:0:0:0: +414,31,20702,1,2,0:0:0:0: +324,151,20914,1,10,0:0:0:0: +244,40,21125,1,2,0:0:0:0: +301,186,21336,6,0,P|242:205|184:174,1,112.5,6|0,0:0|0:0,0:0:0:0: +197,187,21759,2,0,P|190:229|215:287,1,75,0|2,0:0|0:0,0:0:0:0: +287,362,22181,1,0,0:0:0:0: +330,234,22393,1,2,0:0:0:0: +197,260,22604,1,8,0:0:0:0: +360,319,22815,1,2,0:0:0:0: +360,319,23026,6,0,P|419:338|479:313,1,112.5 +465,323,23449,1,0,0:0:0:0: +402,180,23660,1,2,0:0:0:0: +402,180,23871,2,0,L|417:265,1,75,0|2,0:0|0:0,0:0:0:0: +314,145,24294,6,0,L|327:71,1,75,8|2,0:0|0:0,0:0:0:0: +472,72,24716,2,0,L|485:145,1,75,4|2,0:0|0:0,0:0:0:0: +320,222,25139,1,0,0:0:0:0: +235,116,25350,1,2,0:0:0:0: +276,295,25562,5,8,0:0:0:0: +304,305,25667,1,8,0:0:0:0: +333,306,25773,1,8,0:0:0:0: +362,299,25878,1,8,0:0:0:0: +392,280,25984,5,8,0:0:0:0: +425,239,26090,1,8,0:0:0:0: +447,193,26195,1,8,0:0:0:0: +454,143,26301,1,8,0:0:0:0: +452,88,26407,6,0,P|426:34|384:95,1,150,4|0,0:0|0:0,0:0:0:0: +368,160,27463,1,0,0:0:0:0: +487,58,27674,1,12,0:0:0:0: +300,200,28097,6,0,P|288:158|305:117,1,75,12|0,0:0|0:0,0:0:0:0: +377,238,28519,1,10,0:0:0:0: +222,217,28731,1,2,0:0:0:0: +369,92,28942,2,0,P|377:128|366:163,1,75,10|0,0:0|0:0,0:0:0:0: +223,136,29364,2,0,P|214:99|225:64,1,75,10|0,0:0|0:0,0:0:0:0: +251,276,29787,5,10,0:0:0:0: +135,240,29998,1,0,0:0:0:0: +244,356,30209,1,10,0:0:0:0: +137,161,30421,1,0,0:0:0:0: +166,327,30632,1,10,0:0:0:0: +219,187,30843,1,0,0:0:0:0: +68,322,31055,1,10,0:0:0:0: +311,192,31266,1,0,0:0:0:0: +140,89,31477,6,0,P|128:130|145:172,1,75,10|0,0:0|0:0,0:0:0:0: +217,51,31899,1,10,0:0:0:0: +62,72,32111,1,2,0:0:0:0: +209,197,32322,2,0,P|217:160|206:125,1,75,8|0,0:0|0:0,0:0:0:0: +64,168,32744,2,0,P|55:204|66:239,1,75,10|0,0:0|0:0,0:0:0:0: +209,197,33167,6,0,P|172:188|137:199,1,75,8|0,0:0|0:0,0:0:0:0: +136,340,33589,2,0,P|171:351|206:343,1,75,8|0,0:0|0:0,0:0:0:0: +285,167,34012,1,10,0:0:0:0: +308,326,34224,1,2,0:0:0:0: +176,276,34435,1,10,0:0:0:0: +362,263,34646,1,2,0:0:0:0: +184,201,34857,6,0,L|172:305,1,75,14|0,0:0|0:0,0:0:0:0: +118,138,35280,1,10,0:0:0:0: +272,162,35491,1,2,0:0:0:0: +120,57,35702,2,0,P|146:11|197:5,1,75,10|0,0:0|0:0,0:0:0:0: +294,133,36125,2,0,P|267:178|216:184,1,75,10|0,0:0|0:0,0:0:0:0: +243,11,36547,6,0,P|288:37|294:88,1,75,10|0,0:0|0:0,0:0:0:0: +171,183,36970,2,0,P|125:156|119:105,1,75,10|0,0:0|0:0,0:0:0:0: +368,94,37393,1,10,0:0:0:0: +228,243,37604,1,0,0:0:0:0: +222,94,37815,1,10,0:0:0:0: +374,238,38026,1,0,0:0:0:0: +368,94,38238,6,0,L|468:115,1,75,10|0,0:0|0:0,0:0:0:0: +240,170,38660,2,0,L|340:191,1,75,10|0,0:0|0:0,0:0:0:0: +110,240,39083,2,0,L|210:261,1,75,10|0,0:0|0:0,0:0:0:0: +106,321,39505,1,10,0:0:0:0: +148,159,39716,1,2,0:0:0:0: +35,279,39928,5,10,0:0:0:0: +213,325,40139,1,2,0:0:0:0: +61,312,40350,1,8,0:0:0:0: +237,299,40561,1,2,0:0:0:0: +120,92,40773,1,8,0:0:0:0: +124,129,40878,1,8,0:0:0:0: +128,166,40984,1,8,0:0:0:0: +132,203,41089,1,8,0:0:0:0: +136,241,41195,1,12,0:0:0:0: +281,114,41407,5,8,0:0:0:0: +281,114,41512,1,8,0:0:0:0: +281,114,41618,2,0,L|377:107,1,75,12|2,0:0|0:0,0:0:0:0: +292,34,42040,2,0,L|388:27,1,75,0|2,0:0|0:0,0:0:0:0: +400,177,42463,2,0,L|407:273,1,75,0|2,0:0|0:0,0:0:0:0: +480,188,42885,2,0,L|487:284,1,75,0|2,0:0|0:0,0:0:0:0: +330,317,43308,6,0,L|234:310,1,75,0|2,0:0|0:0,0:0:0:0: +319,237,43730,2,0,L|223:230,1,75,0|2,0:0|0:0,0:0:0:0: +129,357,44153,1,0,0:0:0:0: +43,239,44364,1,2,0:0:0:0: +181,284,44576,1,2,0:0:0:0: +43,329,44787,1,2,0:0:0:0: +129,211,44998,6,0,L|136:121,1,75,0|2,0:0|0:0,0:0:0:0: +224,157,45421,2,0,L|217:67,1,75,0|2,0:0|0:0,0:0:0:0: +312,60,45843,1,0,0:0:0:0: +414,106,46055,1,2,0:0:0:0: +401,1,46266,1,0,0:0:0:0: +310,142,46477,5,2,0:0:0:0: +310,142,46583,1,0,0:0:0:0: +310,142,46688,2,0,L|317:232,1,75,0|2,0:0|0:0,0:0:0:0: +405,196,47111,2,0,L|398:286,1,75,0|2,0:0|0:0,0:0:0:0: +280,288,47533,1,0,0:0:0:0: +388,352,47745,1,2,0:0:0:0: +492,176,47956,1,10,0:0:0:0: +465,312,48167,1,2,0:0:0:0: +315,216,48378,6,0,P|271:207|228:227,1,75,4|2,0:0|0:0,0:0:0:0: +280,288,48801,1,0,0:0:0:0: +392,188,49012,2,0,P|367:150|322:134,1,75,2|0,0:0|0:0,0:0:0:0: +472,212,49435,2,0,P|472:166|445:127,1,75,2|0,0:0|0:0,0:0:0:0: +399,270,49857,1,2,0:0:0:0: +341,136,50069,6,0,L|356:42,1,75,0|2,0:0|0:0,0:0:0:0: +430,31,50491,1,0,0:0:0:0: +274,83,50702,1,2,0:0:0:0: +423,111,50914,2,0,L|497:122,1,75,0|2,0:0|0:0,0:0:0:0: +338,215,51336,2,0,L|408:188,1,75,8|2,0:0|0:0,0:0:0:0: +282,268,51759,6,0,P|261:214|276:169,1,75,4|2,0:0|0:0,0:0:0:0: +358,289,52181,1,0,0:0:0:0: +184,202,52393,2,0,P|207:148|249:127,1,75,2|0,0:0|0:0,0:0:0:0: +190,281,52815,1,2,0:0:0:0: +119,158,53026,1,10,0:0:0:0: +262,200,53238,1,0,0:0:0:0: +99,230,53449,6,0,L|123:142,1,75,4|2,0:0|0:0,0:0:0:0: +31,295,53871,2,0,L|7:207,1,75,8|2,0:0|0:0,0:0:0:0: +131,316,54294,1,8,0:0:0:0: +222,242,54505,1,8,0:0:0:0: +118,157,54716,1,8,0:0:0:0: +118,157,54822,1,8,0:0:0:0: +118,157,54928,1,8,0:0:0:0: +226,332,55139,6,0,P|281:349|357:310,1,112.5,4|0,0:0|0:0,0:0:0:0: +332,333,55562,2,0,L|352:238,1,75,2|0,0:0|0:0,0:0:0:0: +289,191,55984,1,2,0:0:0:0: +338,116,56195,1,0,0:0:0:0: +427,103,56407,1,10,0:0:0:0: +502,151,56618,1,0,0:0:0:0: +371,38,56829,6,0,P|316:21|240:60,1,112.5,2|0,0:0|0:0,0:0:0:0: +265,37,57252,2,0,L|245:132,1,75,2|0,0:0|0:0,0:0:0:0: +132,25,57674,2,0,L|159:150,2,112.5,2|0|0,0:0|0:0|0:0,0:0:0:0: +79,150,58519,6,0,P|160:212|192:199,1,112.5,4|0,0:0|0:0,0:0:0:0: +158,212,58942,2,0,L|253:191,1,75,2|0,0:0|0:0,0:0:0:0: +249,110,59364,1,2,0:0:0:0: +324,159,59575,1,0,0:0:0:0: +337,248,59787,1,10,0:0:0:0: +289,323,59998,1,0,0:0:0:0: +406,192,60209,6,0,P|468:273|455:305,1,112.5,2|0,0:0|0:0,0:0:0:0: +469,272,60632,2,0,L|447:366,1,75,2|0,0:0|0:0,0:0:0:0: +337,248,61055,2,0,L|361:363,2,112.5,2|0|0,0:0|0:0|0:0,0:0:0:0: +232,195,61900,6,0,L|210:289,1,75,4|0,0:0|0:0,0:0:0:0: +129,122,62322,2,0,L|146:196,1,75,10|0,0:0|0:0,0:0:0:0: +177,358,62745,1,2,0:0:0:0: +108,282,62956,1,0,0:0:0:0: +286,341,63167,2,0,L|359:357,1,75,10|0,0:0|0:0,0:0:0:0: +410,231,63590,6,0,L|336:247,1,75,2|0,0:0|0:0,0:0:0:0: +465,158,64012,2,0,L|391:141,1,75,10|0,0:0|0:0,0:0:0:0: +226,111,64435,1,2,0:0:0:0: +320,175,64646,1,0,0:0:0:0: +222,34,64857,2,0,P|180:44|159:87,1,75,8|2,0:0|0:0,0:0:0:0: +218,189,65280,6,0,P|176:179|155:136,1,75,4|2,0:0|0:0,0:0:0:0: +296,70,65702,2,0,L|270:164,1,75,0|2,0:0|0:0,0:0:0:0: +236,337,66125,1,0,0:0:0:0: +325,219,66336,1,2,0:0:0:0: +152,247,66547,1,0,0:0:0:0: +316,312,66758,1,2,0:0:0:0: +88,184,66970,6,0,P|46:194|25:237,1,75,0|2,0:0|0:0,0:0:0:0: +172,320,67392,2,0,L|146:226,1,75,0|2,0:0|0:0,0:0:0:0: +194,118,67815,2,0,P|157:95|111:110,1,75,0|2,0:0|0:0,0:0:0:0: +297,315,68238,2,0,L|271:221,1,75,8|2,0:0|0:0,0:0:0:0: +300,75,68660,6,0,L|276:166,1,89.9999972534181,12|0,0:0|0:0,0:0:0:0: +337,56,68977,2,0,L|313:147,1,89.9999972534181,8|0,0:0|0:0,0:0:0:0: +374,43,69294,2,0,L|354:115,1,75,8|0,0:0|0:0,0:0:0:0: +385,192,69505,6,0,B|417:183|417:183|470:203,1,89.9999972534181,8|0,0:0|0:0,0:0:0:0: +360,235,69822,2,0,B|391:225|391:225|444:245,1,89.9999972534181,8|0,0:0|0:0,0:0:0:0: +341,274,70139,2,0,B|372:264|372:264|412:278,1,75,8|0,0:0|0:0,0:0:0:0: +245,332,70350,6,0,P|226:291|239:249,1,89.9999972534181,12|0,0:0|0:0,0:0:0:0: +185,311,70667,2,0,P|200:269|239:248,1,89.9999972534181,8|0,0:0|0:0,0:0:0:0: +169,248,70984,2,0,P|202:235|237:247,1,75,8|0,0:0|0:0,0:0:0:0: +78,207,71195,6,0,B|66:171|66:171|74:157|74:157|62:118,1,89.9999972534181,8|0,0:0|0:0,0:0:0:0: +108,176,71512,2,0,B|96:140|96:140|104:126|104:126|92:87,1,89.9999972534181,8|0,0:0|0:0,0:0:0:0: +143,143,71829,2,0,B|130:108|130:108|138:94|138:94|131:73,1,75,8|0,0:0|0:0,0:0:0:0: +307,58,72040,6,0,P|254:35|207:58,1,89.9999972534181,12|0,0:0|0:0,0:0:0:0: +388,72,72357,2,0,P|335:49|288:72,1,89.9999972534181,12|0,0:0|0:0,0:0:0:0: +454,91,72674,2,0,P|401:68|364:89,1,75,12|0,0:0|0:0,0:0:0:0: +338,180,72885,5,8,0:0:0:0: +269,308,73097,1,8,0:0:0:0: +304,334,73202,1,8,0:0:0:0: +348,344,73308,1,8,0:0:0:0: +391,335,73414,1,8,0:0:0:0: +428,309,73519,1,8,0:0:0:0: +450,271,73625,1,8,0:0:0:0: +453,227,73730,1,8,0:0:0:0: +453,227,74576,6,0,L|490:228,9,22.4999993133545 +506,152,74998,1,12,0:0:0:0: +222,89,75421,5,12,0:0:0:0: +194,259,75632,1,0,0:0:0:0: +320,218,75843,1,8,0:0:0:0: +150,190,76054,1,2,0:0:0:0: +339,335,76266,1,8,0:0:0:0: +372,130,76477,1,2,0:0:0:0: +221,180,76688,1,10,0:0:0:0: +425,212,76899,1,2,0:0:0:0: +285,121,77111,6,0,P|341:109|385:165,1,97.4999955368044,8|0,0:0|0:0,0:0:0:0: +194,259,77533,1,8,0:0:0:0: +323,182,77745,1,2,0:0:0:0: +244,316,77956,2,0,P|200:336|140:312,1,97.4999955368044,8|2,0:0|0:0,0:0:0:0: +245,179,78378,1,10,0:0:0:0: +350,277,78590,1,2,0:0:0:0: +160,228,78801,6,0,L|164:68,1,146.249993305207,8|0,0:0|0:0,0:0:0:0: +194,90,79224,2,0,P|254:105|296:75,1,97.4999955368044,8|2,0:0|0:0,0:0:0:0: +129,0,79646,1,10,0:0:0:0: +22,146,79857,1,2,0:0:0:0: +194,90,80069,1,10,0:0:0:0: +22,33,80280,1,2,0:0:0:0: +129,180,80491,6,0,P|174:195|218:179,1,97.4999955368044,8|2,0:0|0:0,0:0:0:0: +308,80,80913,1,10,0:0:0:0: +280,252,81125,1,0,0:0:0:0: +446,206,81336,1,8,0:0:0:0: +339,60,81547,1,2,0:0:0:0: +511,116,81759,1,10,0:0:0:0: +339,173,81970,1,2,0:0:0:0: +446,26,82181,5,12,0:0:0:0: +280,118,82393,1,0,0:0:0:0: +435,118,82604,1,8,0:0:0:0: +259,26,82816,1,2,0:0:0:0: +339,173,83026,1,8,0:0:0:0: +154,128,83238,1,2,0:0:0:0: +304,88,83449,1,10,0:0:0:0: +157,222,83661,1,2,0:0:0:0: +352,280,83871,5,8,0:0:0:0: +160,173,84083,1,0,0:0:0:0: +339,173,84294,1,8,0:0:0:0: +135,280,84506,1,2,0:0:0:0: +259,130,84716,5,8,0:0:0:0: +65,235,84928,1,2,0:0:0:0: +244,235,85139,1,10,0:0:0:0: +40,129,85351,1,2,0:0:0:0: +300,92,85562,6,0,L|274:200,1,97.4999955368044,8|0,0:0|0:0,0:0:0:0: +192,43,85984,1,8,0:0:0:0: +361,34,86195,1,2,0:0:0:0: +327,233,86407,2,0,L|219:207,1,97.4999955368044,8|2,0:0|0:0,0:0:0:0: +376,125,86829,1,10,0:0:0:0: +385,294,87040,1,2,0:0:0:0: +195,265,87252,6,0,L|221:157,1,97.4999955368044,8|0,0:0|0:0,0:0:0:0: +303,314,87674,1,8,0:0:0:0: +134,323,87885,1,2,0:0:0:0: +177,108,88097,1,8,0:0:0:0: +223,95,88202,1,8,0:0:0:0: +267,114,88308,1,8,0:0:0:0: +291,155,88413,1,8,0:0:0:0: +284,203,88519,1,12,0:0:0:0: +102,204,88731,1,8,0:0:0:0: +224,16,88942,5,12,0:0:0:0: +207,200,89153,1,0,0:0:0:0: +96,112,89364,1,8,0:0:0:0: +113,296,89575,1,2,0:0:0:0: +0,152,89787,1,8,0:0:0:0: +184,169,89998,1,2,0:0:0:0: +16,296,90209,1,10,0:0:0:0: +211,242,90420,1,2,0:0:0:0: +88,52,90632,6,0,L|76:172,1,97.4999955368044,8|0,0:0|0:0,0:0:0:0: +231,2,91055,2,0,L|160:99,1,97.4999955368044,8|2,0:0|0:0,0:0:0:0: +383,22,91477,2,0,L|273:71,1,97.4999955368044,8|2,0:0|0:0,0:0:0:0: +491,110,91900,2,2,L|356:101,1,97.4999955368044,10|2,0:0|0:0,0:0:0:0: +436,284,92322,6,0,L|444:144,1,97.4999955368044,8|0,0:0|0:0,0:0:0:0: +304,159,92745,1,8,0:0:0:0: +304,159,92956,1,2,0:0:0:0: +412,328,93167,6,0,L|420:188,1,97.4999955368044,8|2,0:0|0:0,0:0:0:0: +292,176,93590,1,8,0:0:0:0: +292,176,93801,1,2,0:0:0:0: +392,364,94012,6,2,L|400:224,1,97.4999955368044,10|2,0:0|0:0,0:0:0:0: +280,196,94435,1,8,0:0:0:0: +280,196,94646,1,2,0:0:0:0: +160,155,94857,2,0,P|148:207|192:259,1,97.4999955368044,8|2,0:0|0:0,0:0:0:0: +424,112,95280,2,2,P|436:60|392:8,1,97.4999955368044,10|2,0:0|0:0,0:0:0:0: +224,192,95702,5,12,0:0:0:0: +421,192,95913,1,2,0:0:0:0: +280,56,96125,1,8,0:0:0:0: +280,253,96336,1,2,0:0:0:0: +431,112,96547,1,8,0:0:0:0: +195,112,96758,1,2,0:0:0:0: +364,268,96970,1,10,0:0:0:0: +364,32,97181,1,2,0:0:0:0: +176,264,97393,5,14,0:0:0:0: +426,108,97604,1,2,0:0:0:0: +200,184,97815,1,10,0:0:0:0: +459,264,98026,1,2,0:0:0:0: +200,108,98238,1,8,0:0:0:0: +426,184,98449,1,2,0:0:0:0: +164,32,98660,1,10,0:0:0:0: +447,32,98871,1,2,0:0:0:0: +312,264,99083,6,0,L|304:148,1,97.4999955368044,12|2,0:0|0:0,0:0:0:0: +412,236,99505,1,8,0:0:0:0: +224,224,99716,1,2,0:0:0:0: +420,144,99928,1,8,0:0:0:0: +408,332,100139,1,2,0:0:0:0: +252,136,100350,1,10,0:0:0:0: +191,314,100561,1,2,0:0:0:0: +412,236,100773,6,0,L|504:236,1,75,4|0,0:0|0:0,0:0:0:0: +348,288,101195,2,0,L|256:288,1,75,2|0,0:0|0:0,0:0:0:0: +415,339,101618,2,8,B|435:283|435:283|399:211,1,112.5 +411,235,102040,1,8,0:0:0:0: +347,127,102252,5,8,0:0:0:0: +347,127,102357,1,8,0:0:0:0: +347,127,102463,2,0,P|399:143|455:119,1,89.9999972534181,14|0,0:0|0:0,0:0:0:0: +444,20,102885,1,10,0:0:0:0: +280,60,103097,1,2,0:0:0:0: +433,135,103308,2,0,L|421:243,1,89.9999972534181,10|0,0:0|0:0,0:0:0:0: +232,120,103731,2,0,L|222:30,1,89.9999972534181,10|0,0:0|0:0,0:0:0:0: +92,254,104153,5,2,0:0:0:0: +139,123,104364,1,0,0:0:0:0: +0,157,104575,1,10,0:0:0:0: +158,201,104787,1,0,0:0:0:0: +204,26,104998,1,10,0:0:0:0: +34,71,105209,1,0,0:0:0:0: +267,106,105421,1,8,0:0:0:0: +30,179,105632,1,2,0:0:0:0: +163,290,105843,6,0,L|155:166,1,89.9999972534181,10|0,0:0|0:0,0:0:0:0: +273,144,106266,2,0,P|327:167|371:143,1,89.9999972534181,10|0,0:0|0:0,0:0:0:0: +512,116,106688,2,0,P|468:108|430:130,1,89.9999972534181,10|0,0:0|0:0,0:0:0:0: +384,4,107111,2,0,P|360:58|384:102,1,89.9999972534181,10|0,0:0|0:0,0:0:0:0: +396,288,107533,6,0,P|419:233|395:189,1,89.9999972534181,10|0,0:0|0:0,0:0:0:0: +408,368,107956,2,0,P|462:346|477:297,1,89.9999972534181,10|0,0:0|0:0,0:0:0:0: +332,336,108378,1,10,0:0:0:0: +480,244,108590,1,2,0:0:0:0: +332,336,108801,1,10,0:0:0:0: +372,168,109013,1,2,0:0:0:0: +247,313,109224,6,0,P|272:252|252:204,1,89.9999972534181,14|0,0:0|0:0,0:0:0:0: +96,136,109646,1,10,0:0:0:0: +196,252,109858,1,2,0:0:0:0: +260,120,110069,2,0,L|152:132,1,89.9999972534181,10|0,0:0|0:0,0:0:0:0: +28,236,110491,2,0,L|118:246,1,89.9999972534181,10|0,0:0|0:0,0:0:0:0: +86,46,110914,6,0,L|95:135,1,89.9999972534181,10|0,0:0|0:0,0:0:0:0: +186,341,111337,2,0,L|196:251,1,89.9999972534181,10|0,0:0|0:0,0:0:0:0: +216,88,111759,1,10,0:0:0:0: +95,135,111970,1,0,0:0:0:0: +264,168,112181,1,10,0:0:0:0: +191,8,112393,1,0,0:0:0:0: +142,221,112604,6,0,L|130:329,1,89.9999972534181,10|0,0:0|0:0,0:0:0:0: +264,168,113026,2,0,L|252:276,1,89.9999972534181,10|0,0:0|0:0,0:0:0:0: +396,112,113449,2,0,L|384:220,1,89.9999972534181,10|0,0:0|0:0,0:0:0:0: +312,104,113871,1,10,0:0:0:0: +456,240,114083,1,0,0:0:0:0: +442,48,114294,6,0,P|401:30|360:44,1,89.9999972534181,10|2,0:0|0:0,0:0:0:0: +303,196,114716,2,0,P|343:213|386:201,1,89.9999972534181,10|2,0:0|0:0,0:0:0:0: +208,80,115139,1,8,0:0:0:0: +213,124,115244,1,8,0:0:0:0: +218,169,115350,1,8,0:0:0:0: +224,214,115455,1,8,0:0:0:0: +229,258,115561,1,12,0:0:0:0: +136,192,115773,5,8,0:0:0:0: +136,192,115878,1,8,0:0:0:0: +136,192,115984,2,0,L|40:185,1,75,12|2,0:0|0:0,0:0:0:0: +60,104,116407,2,0,L|156:110,1,75,0|2,0:0|0:0,0:0:0:0: +202,5,116829,2,0,L|209:101,1,75,0|2,0:0|0:0,0:0:0:0: +288,104,117251,2,0,L|293:29,1,75,0|2,0:0|0:0,0:0:0:0: +336,184,117674,6,0,L|240:177,1,75,0|2,0:0|0:0,0:0:0:0: +340,264,118096,2,0,L|414:258,1,75,0|2,0:0|0:0,0:0:0:0: +414,112,118519,1,0,0:0:0:0: +500,230,118730,1,2,0:0:0:0: +362,185,118942,1,10,0:0:0:0: +500,140,119153,1,2,0:0:0:0: +414,258,119364,6,0,L|340:264,1,75,0|2,0:0|0:0,0:0:0:0: +186,173,119787,2,0,L|260:178,1,75,0|2,0:0|0:0,0:0:0:0: +260,292,120209,1,0,0:0:0:0: +169,344,120421,1,2,0:0:0:0: +182,239,120632,1,0,0:0:0:0: +244,372,120843,1,2,0:0:0:0: +104,296,121054,6,0,L|14:303,1,75,0|2,0:0|0:0,0:0:0:0: +186,173,121477,2,0,L|260:178,1,75,0|2,0:0|0:0,0:0:0:0: +104,208,121899,1,0,0:0:0:0: +78,106,122111,1,2,0:0:0:0: +104,248,122322,1,10,0:0:0:0: +177,144,122534,1,2,0:0:0:0: +288,256,122744,6,0,P|244:265|201:245,1,75,4|2,0:0|0:0,0:0:0:0: +216,144,123167,1,0,0:0:0:0: +367,280,123378,2,0,P|342:318|297:334,1,75,2|0,0:0|0:0,0:0:0:0: +450,260,123801,2,0,P|447:305|416:342,1,75,2|0,0:0|0:0,0:0:0:0: +277,260,124223,1,2,0:0:0:0: +332,128,124435,6,0,L|420:160,1,75,0|2,0:0|0:0,0:0:0:0: +367,280,124857,1,0,0:0:0:0: +272,180,125069,1,2,0:0:0:0: +470,129,125280,2,0,P|475:166|460:200,1,75,0|2,0:0|0:0,0:0:0:0: +356,52,125702,1,8,0:0:0:0: +402,153,125914,1,2,0:0:0:0: +232,72,126125,6,0,P|211:126|226:171,1,75,4|2,0:0|0:0,0:0:0:0: +288,124,126547,1,0,0:0:0:0: +134,138,126759,2,0,P|157:192|199:213,1,75,2|0,0:0|0:0,0:0:0:0: +335,212,127181,1,2,0:0:0:0: +212,141,127393,1,8,0:0:0:0: +254,284,127604,1,2,0:0:0:0: +286,130,127815,6,0,L|190:143,1,75,4|2,0:0|0:0,0:0:0:0: +384,51,128237,2,0,L|296:27,1,75,8|2,0:0|0:0,0:0:0:0: +480,108,128660,1,8,0:0:0:0: +396,232,128871,1,8,0:0:0:0: +241,225,129082,1,8,0:0:0:0: +241,225,129188,1,8,0:0:0:0: +241,225,129294,1,8,0:0:0:0: +295,288,129505,6,0,P|244:309|192:292,1,112.5,6|0,0:0|0:0,0:0:0:0: +192,292,129928,2,0,L|176:365,1,75,2|0,0:0|0:0,0:0:0:0: +148,220,130350,1,2,0:0:0:0: +68,187,130561,1,0,0:0:0:0: +36,267,130772,1,10,0:0:0:0: +115,300,130983,1,0,0:0:0:0: +16,127,131195,6,0,P|67:106|124:128,1,112.5,2|0,0:0|0:0,0:0:0:0: +119,124,131618,2,0,L|192:108,1,75,2|0,0:0|0:0,0:0:0:0: +280,44,132040,2,0,L|155:17,2,112.5,2|0|0,0:0|0:0|0:0,0:0:0:0: +96,56,132885,6,0,P|72:105|91:157,1,112.5,6|0,0:0|0:0,0:0:0:0: +91,157,133308,2,0,L|164:140,1,75 +44,216,133731,1,0,0:0:0:0: +123,249,133942,1,0,0:0:0:0: +91,329,134153,1,8,0:0:0:0: +11,296,134364,1,0,0:0:0:0: +200,268,134576,6,0,P|264:280|320:244,1,112.5 +304,260,134998,2,0,L|282:354,1,75 +436,348,135421,2,0,L|413:237,2,112.5,2|0|0,0:0|0:0|0:0,0:0:0:0: +448,168,136266,6,0,P|408:156|364:180,1,75,6|0,0:0|0:0,0:0:0:0: +232,260,136688,2,0,P|272:272|316:248,1,75,10|0,0:0|0:0,0:0:0:0: +340,100,137111,1,2,0:0:0:0: +268,196,137322,1,0,0:0:0:0: +240,48,137533,2,0,L|252:136,1,75,10|0,0:0|0:0,0:0:0:0: +92,44,137956,6,0,P|132:32|172:44,1,75,2|0,0:0|0:0,0:0:0:0: +168,180,138378,2,0,P|132:192|94:177,1,75,10|0,0:0|0:0,0:0:0:0: +12,56,138801,1,2,0:0:0:0: +132,112,139012,1,0,0:0:0:0: +44,236,139223,2,0,P|20:207|20:171,1,75,10|2,0:0|0:0,0:0:0:0: +244,172,139646,6,0,P|244:208|220:236,1,75,4|2,0:0|0:0,0:0:0:0: +216,104,140069,2,0,P|215:67|239:39,1,75,0|2,0:0|0:0,0:0:0:0: +436,68,140491,1,0,0:0:0:0: +289,88,140702,1,2,0:0:0:0: +459,156,140913,1,0,0:0:0:0: +317,50,141124,1,2,0:0:0:0: +336,232,141336,6,0,L|326:306,1,75,0|2,0:0|0:0,0:0:0:0: +468,230,141759,2,0,L|458:155,1,75,0|2,0:0|0:0,0:0:0:0: +436,324,142181,2,0,L|510:333,1,75,0|2,0:0|0:0,0:0:0:0: +336,124,142604,2,0,L|261:133,1,75,8|2,0:0|0:0,0:0:0:0: +210,89,143026,6,0,P|208:140|183:171,1,89.9999972534181,12|0,0:0|0:0,0:0:0:0: +261,132,143343,2,0,P|223:166|183:170,1,89.9999972534181,8|0,0:0|0:0,0:0:0:0: +256,184,143660,2,0,P|204:181|181:167,1,75,8|0,0:0|0:0,0:0:0:0: +124,70,143871,6,0,L|108:173,1,89.9999972534181,8|0,0:0|0:0,0:0:0:0: +96,247,144188,2,0,L|112:144,1,89.9999972534181,8|0,0:0|0:0,0:0:0:0: +184,170,144505,2,0,L|79:153,1,75,8|0,0:0|0:0,0:0:0:0: +261,132,144716,6,8,L|368:150,1,89.9999972534181,12|0,0:0|0:0,0:0:0:0: +336,84,145033,2,8,L|398:172,1,89.9999972534181,12|0,0:0|0:0,0:0:0:0: +428,96,145350,2,8,L|412:189,1,75,12|0,0:0|0:0,0:0:0:0: +411,278,145562,6,8,P|456:273|497:240,1,89.9999972534181,8|0,0:0|0:0,0:0:0:0: +324,276,145878,2,8,P|367:265|417:282,1,89.9999972534181,8|0,0:0|0:0,0:0:0:0: +252,272,146195,2,8,P|295:282|340:265,1,75,8|0,0:0|0:0,0:0:0:0: +317,119,146407,6,8,L|287:227,1,89.9999972534181,12|0,0:0|0:0,0:0:0:0: +240,74,146724,2,8,L|268:182,1,89.9999972534181,12|0,0:0|0:0,0:0:0:0: +166,90,147040,2,8,L|237:160,1,75,12|0,0:0|0:0,0:0:0:0: +170,152,147252,5,8,0:0:0:0: +38,120,147464,1,8,0:0:0:0: +12,155,147569,1,8,0:0:0:0: +2,199,147675,1,8,0:0:0:0: +11,242,147781,1,8,0:0:0:0: +37,279,147886,1,8,0:0:0:0: +75,301,147992,1,8,0:0:0:0: +119,304,148097,1,8,0:0:0:0: +245,208,148942,6,0,L|268:196,9,22.4999993133545 +232,288,149364,1,12,0:0:0:0: +217,38,149787,5,12,0:0:0:0: +56,98,149998,1,0,0:0:0:0: +155,187,150209,1,8,0:0:0:0: +94,26,150420,1,2,0:0:0:0: +63,262,150632,5,8,0:0:0:0: +257,188,150843,1,2,0:0:0:0: +138,82,151054,1,10,0:0:0:0: +212,275,151265,1,2,0:0:0:0: +288,60,151477,6,0,L|260:184,1,97.4999955368044,8|0,0:0|0:0,0:0:0:0: +204,48,151899,1,8,0:0:0:0: +346,175,152111,1,2,0:0:0:0: +130,263,152322,6,0,L|158:138,1,97.4999955368044,8|2,0:0|0:0,0:0:0:0: +232,244,152744,1,10,0:0:0:0: +56,170,152956,1,2,0:0:0:0: +64,352,153167,6,0,P|136:316|220:364,1,146.249993305207,8|0,0:0|0:0,0:0:0:0: +224,348,153590,2,0,P|284:363|326:333,1,97.4999955368044,8|2,0:0|0:0,0:0:0:0: +376,140,154012,1,10,0:0:0:0: +269,286,154223,1,2,0:0:0:0: +441,230,154435,1,10,0:0:0:0: +269,173,154646,1,2,0:0:0:0: +376,320,154857,6,0,P|436:335|478:305,1,97.4999955368044,8|2,0:0|0:0,0:0:0:0: +496,136,155280,1,10,0:0:0:0: +420,256,155491,1,0,0:0:0:0: +330,80,155702,1,10,0:0:0:0: +223,226,155913,1,2,0:0:0:0: +395,170,156125,1,10,0:0:0:0: +223,113,156336,1,2,0:0:0:0: +330,260,156547,5,12,0:0:0:0: +408,92,156759,1,0,0:0:0:0: +168,168,156970,1,8,0:0:0:0: +408,244,157182,1,2,0:0:0:0: +256,44,157392,5,8,0:0:0:0: +264,296,157604,1,2,0:0:0:0: +436,168,157815,1,10,0:0:0:0: +188,92,158027,1,2,0:0:0:0: +212,336,158238,5,8,0:0:0:0: +290,168,158450,1,0,0:0:0:0: +50,244,158661,1,8,0:0:0:0: +290,320,158871,1,2,0:0:0:0: +138,120,159083,5,8,0:0:0:0: +146,372,159295,1,2,0:0:0:0: +318,244,159506,1,10,0:0:0:0: +70,168,159716,1,2,0:0:0:0: +324,164,159928,6,0,P|384:197|399:266,1,97.4999955368044,8|0,0:0|0:0,0:0:0:0: +291,354,160350,1,8,0:0:0:0: +209,190,160562,1,2,0:0:0:0: +377,321,160773,6,0,P|317:355|255:335,1,97.4999955368044,8|0,0:0|0:0,0:0:0:0: +209,190,161195,1,10,0:0:0:0: +396,220,161407,1,2,0:0:0:0: +200,283,161618,6,0,P|198:212|240:163,1,97.4999955368044,8|2,0:0|0:0,0:0:0:0: +396,221,162040,1,8,0:0:0:0: +290,353,162251,1,2,0:0:0:0: +264,56,162463,5,8,0:0:0:0: +277,102,162568,1,8,0:0:0:0: +290,149,162674,1,8,0:0:0:0: +304,196,162779,1,8,0:0:0:0: +317,243,162885,1,12,0:0:0:0: +172,164,163097,1,8,0:0:0:0: +416,108,163308,5,12,0:0:0:0: +232,91,163519,1,0,0:0:0:0: +400,12,163730,1,8,0:0:0:0: +383,196,163941,1,2,0:0:0:0: +217,0,164153,5,8,0:0:0:0: +200,184,164364,1,2,0:0:0:0: +313,16,164575,1,10,0:0:0:0: +112,32,164786,1,2,0:0:0:0: +200,184,164998,6,0,P|216:136|204:88,1,97.4999955368044,8|0,0:0|0:0,0:0:0:0: +112,256,165421,2,0,P|96:304|108:352,1,97.4999955368044,8|2,0:0|0:0,0:0:0:0: +116,176,165843,2,0,P|68:160|20:172,1,97.4999955368044,8|2,0:0|0:0,0:0:0:0: +196,264,166266,2,2,P|244:280|292:268,1,97.4999955368044,10|2,0:0|0:0,0:0:0:0: +248,60,166688,5,8,0:0:0:0: +248,201,166899,1,0,0:0:0:0: +333,55,167111,1,8,0:0:0:0: +248,201,167322,1,2,0:0:0:0: +424,101,167533,5,8,0:0:0:0: +248,201,167744,1,2,0:0:0:0: +468,224,167956,1,10,0:0:0:0: +292,124,168167,1,2,0:0:0:0: +364,328,168378,5,8,0:0:0:0: +364,158,168589,1,0,0:0:0:0: +244,304,168801,1,8,0:0:0:0: +464,327,169013,1,2,0:0:0:0: +192,248,169224,6,0,L|184:359,1,97.4999955368044,8|2,0:0|0:0,0:0:0:0: +508,272,169646,2,2,L|500:161,1,97.4999955368044,10|2,0:0|0:0,0:0:0:0: +268,60,170068,5,12,0:0:0:0: +268,257,170279,1,2,0:0:0:0: +404,116,170491,1,8,0:0:0:0: +207,116,170702,1,2,0:0:0:0: +348,267,170913,5,8,0:0:0:0: +348,31,171124,1,2,0:0:0:0: +192,200,171336,1,8,0:0:0:0: +428,200,171547,1,2,0:0:0:0: +268,60,171759,5,12,0:0:0:0: +386,236,171970,1,2,0:0:0:0: +386,11,172181,1,8,0:0:0:0: +268,187,172393,1,2,0:0:0:0: +149,55,172604,5,10,0:0:0:0: +30,231,172815,1,2,0:0:0:0: +30,7,173026,1,10,0:0:0:0: +149,183,173238,1,2,0:0:0:0: +30,7,173449,6,0,L|58:127,1,97.4999955368044,12|0,0:0|0:0,0:0:0:0: +240,64,173871,2,0,L|122:28,1,97.4999955368044,8|2,0:0|0:0,0:0:0:0: +80,216,174294,2,0,L|169:131,1,97.4999955368044,8|2,0:0|0:0,0:0:0:0: +124,280,174716,1,10,0:0:0:0: +56,128,174928,1,2,0:0:0:0: +216,212,175139,6,0,L|200:312,1,75,4|0,0:0|0:0,0:0:0:0: +296,216,175562,6,0,L|276:332,1,89.9999972534181,2|0,0:0|0:0,0:0:0:0: +376,208,175984,6,8,L|352:352,1,134.999995880127 +353,341,176406,1,8,0:0:0:0: +328,144,176618,5,8,0:0:0:0: +328,144,176723,1,8,0:0:0:0: +328,144,176829,2,0,P|376:128|432:160,1,89.9999972534181,12|0,0:0|0:0,0:0:0:0: +248,152,177252,2,0,P|200:168|144:136,1,89.9999972534181,8|0,0:0|0:0,0:0:0:0: +344,120,177674,2,0,P|392:104|448:136,1,89.9999972534181,8|0,0:0|0:0,0:0:0:0: +236,168,178097,2,0,P|188:184|132:152,1,89.9999972534181,8|0,0:0|0:0,0:0:0:0: +192,272,178519,6,0,P|208:320|176:376,1,89.9999972534181,8|0,0:0|0:0,0:0:0:0: +152,172,178942,2,0,P|136:124|168:68,1,89.9999972534181,8|0,0:0|0:0,0:0:0:0: +228,284,179364,2,0,P|244:332|212:388,1,89.9999972534181,8|0,0:0|0:0,0:0:0:0: +116,152,179787,2,0,P|100:104|132:48,1,89.9999972534181,8|0,0:0|0:0,0:0:0:0: +100,256,180209,6,0,P|52:272|-4:240,1,89.9999972534181,8|0,0:0|0:0,0:0:0:0: +240,184,180632,2,0,P|288:168|344:200,1,89.9999972534181,8|0,0:0|0:0,0:0:0:0: +288,336,181055,2,0,L|284:232,1,89.9999972534181,8|0,0:0|0:0,0:0:0:0: +432,84,181477,2,0,L|420:204,1,89.9999972534181,8|0,0:0|0:0,0:0:0:0: +368,352,181900,6,0,L|364:248,1,89.9999972534181,8|0,0:0|0:0,0:0:0:0: +512,100,182322,2,0,L|500:220,1,89.9999972534181,8|0,0:0|0:0,0:0:0:0: +272,104,182745,2,0,L|392:116,1,89.9999972534181,8|0,0:0|0:0,0:0:0:0: +356,132,183062,1,0,0:0:0:0: +352,156,183167,1,8,0:0:0:0: +276,20,183378,1,0,0:0:0:0: +304,240,183590,6,0,P|264:256|216:240,1,89.9999972534181,12|0,0:0|0:0,0:0:0:0: +392,272,184012,2,0,P|425:298|436:348,1,89.9999972534181,8|0,0:0|0:0,0:0:0:0: +376,184,184435,2,0,P|382:141|419:107,1,89.9999972534181,8|0,0:0|0:0,0:0:0:0: +320,336,184857,1,8,0:0:0:0: +260,180,185069,1,0,0:0:0:0: +176,304,185280,6,0,B|160:372|160:372|144:344,1,97.4999955368044,8|0,0:0|0:0,0:0:0:0: +207,176,185702,2,0,B|273:155|273:155|257:183,1,97.4999955368044,8|0,0:0|0:0,0:0:0:0: +84,224,186125,2,0,B|33:176|33:176|65:176,1,97.4999955368044,8|0,0:0|0:0,0:0:0:0: +244,260,186547,1,8,0:0:0:0: +88,300,186759,1,0,0:0:0:0: +128,44,186970,6,0,L|136:188,1,104.999996795654,8|0,0:0|0:0,0:0:0:0: +340,208,187393,2,0,L|348:64,1,104.999996795654,8|0,0:0|0:0,0:0:0:0: +244,260,187815,1,8,0:0:0:0: +424,240,188026,1,0,0:0:0:0: +211,244,188238,1,8,0:0:0:0: +377,317,188449,1,0,0:0:0:0: +196,336,188660,5,8,0:0:0:0: +224,154,188871,1,0,0:0:0:0: +367,270,189083,1,8,0:0:0:0: +132,216,189294,1,0,0:0:0:0: +338,135,189505,1,8,0:0:0:0: +330,186,189610,1,8,0:0:0:0: +322,238,189716,1,8,0:0:0:0: +314,290,189821,1,8,0:0:0:0: +306,342,189927,1,12,0:0:0:0: +228,252,190139,1,8,0:0:0:0: +420,216,190350,5,12,0:0:0:0: +247,160,190562,1,0,0:0:0:0: +406,252,190773,1,8,0:0:0:0: +368,74,190985,1,2,0:0:0:0: +373,269,191195,1,8,0:0:0:0: +507,146,191407,1,2,0:0:0:0: +335,271,191618,1,10,0:0:0:0: +508,325,191830,1,2,0:0:0:0: +219,271,192040,6,0,P|199:219|231:155,1,89.9999972534181,8|0,0:0|0:0,0:0:0:0: +279,327,192463,2,0,P|217:353|163:323,1,89.9999972534181,8|2,0:0|0:0,0:0:0:0: +335,271,192885,2,0,P|361:332|331:387,1,89.9999972534181,8|2,0:0|0:0,0:0:0:0: +279,219,193308,2,2,P|340:193|395:223,1,89.9999972534181,10|2,0:0|0:0,0:0:0:0: +108,296,193731,6,0,L|112:124,1,134.999995880127,8|0,0:0|0:0,0:0:0:0: +72,100,194153,2,0,P|120:116|172:84,1,89.9999972534181,8|2,0:0|0:0,0:0:0:0: +24,24,194576,1,8,0:0:0:0: +36,168,194787,1,2,0:0:0:0: +116,40,194998,1,10,0:0:0:0: +184,184,195209,1,2,0:0:0:0: +256,56,195421,5,8,0:0:0:0: +112,155,195632,1,2,0:0:0:0: +276,224,195843,2,0,L|268:132,1,89.9999972534181 +160,72,196266,1,10,0:0:0:0: +16,171,196477,1,2,0:0:0:0: +180,240,196688,1,8,0:0:0:0: +72,108,196899,1,2,0:0:0:0: +76,328,197111,5,12,0:0:0:0: +249,274,197323,1,0,0:0:0:0: +83,171,197534,1,8,0:0:0:0: +217,295,197745,1,2,0:0:0:0: +218,119,197956,1,8,0:0:0:0: +179,297,198168,1,2,0:0:0:0: +317,223,198379,1,10,0:0:0:0: +144,279,198591,1,2,0:0:0:0: +295,284,198801,6,0,L|271:164,1,89.9999972534181,8|0,0:0|0:0,0:0:0:0: +489,254,199224,2,0,L|465:374,1,89.9999972534181,8|2,0:0|0:0,0:0:0:0: +277,195,199646,2,0,L|253:75,1,89.9999972534181,8|2,0:0|0:0,0:0:0:0: +506,165,200069,2,2,L|482:285,1,89.9999972534181,10|2,0:0|0:0,0:0:0:0: +301,42,200491,6,0,P|361:10|425:38,1,134.999995880127,8|0,0:0|0:0,0:0:0:0: +432,52,200914,2,0,L|420:164,1,89.9999972534181,8|2,0:0|0:0,0:0:0:0: +262,226,201336,1,8,0:0:0:0: +352,103,201547,1,2,0:0:0:0: +352,256,201759,1,10,0:0:0:0: +262,132,201970,1,2,0:0:0:0: +407,179,202181,5,8,0:0:0:0: +240,253,202393,1,2,0:0:0:0: +418,291,202604,1,8,0:0:0:0: +296,155,202815,1,2,0:0:0:0: +315,338,203026,1,8,0:0:0:0: +281,308,203131,1,8,0:0:0:0: +239,292,203237,1,8,0:0:0:0: +195,291,203342,1,8,0:0:0:0: +152,306,203448,1,12,0:0:0:0: +328,380,203660,1,8,0:0:0:0: +312,204,203871,5,12,0:0:0:0: +120,266,204083,1,0,0:0:0:0: +284,136,204294,1,8,0:0:0:0: +241,334,204506,1,2,0:0:0:0: +210,130,204716,5,8,0:0:0:0: +359,267,204928,1,2,0:0:0:0: +152,180,205139,1,10,0:0:0:0: +345,120,205351,1,2,0:0:0:0: +84,136,205562,6,0,P|72:176|88:228,1,89.9999972534181,8|0,0:0|0:0,0:0:0:0: +284,136,205984,2,0,P|296:96|280:44,1,89.9999972534181,8|2,0:0|0:0,0:0:0:0: +184,248,206407,2,0,P|224:260|276:244,1,89.9999972534181,8|2,0:0|0:0,0:0:0:0: +180,28,206829,2,2,P|140:16|88:32,1,89.9999972534181,10|2,0:0|0:0,0:0:0:0: +153,305,207252,6,0,P|173:233|137:163,1,134.999995880127,12|0,0:0|0:0,0:0:0:0: +140,160,207674,2,0,P|100:148|48:164,1,89.9999972534181,8|2,0:0|0:0,0:0:0:0: +72,336,208097,5,8,0:0:0:0: +256,292,208308,1,2,0:0:0:0: +100,224,208519,1,10,0:0:0:0: +204,381,208730,1,2,0:0:0:0: +351,209,208942,5,8,0:0:0:0: +178,305,209153,1,2,0:0:0:0: +312,344,209364,1,8,0:0:0:0: +217,171,209576,1,2,0:0:0:0: +472,144,209787,5,8,0:0:0:0: +264,259,209998,1,2,0:0:0:0: +425,306,210209,1,10,0:0:0:0: +311,98,210421,1,2,0:0:0:0: +332,312,210632,5,12,0:0:0:0: +396,100,210843,1,2,0:0:0:0: +192,160,211055,1,8,0:0:0:0: +403,224,211266,1,2,0:0:0:0: +328,24,211477,5,8,0:0:0:0: +255,267,211688,1,2,0:0:0:0: +488,198,211900,1,10,0:0:0:0: +247,125,212111,1,2,0:0:0:0: +392,312,212322,5,12,0:0:0:0: +334,66,212533,1,2,0:0:0:0: +342,351,212745,1,8,0:0:0:0: +372,100,212956,1,2,0:0:0:0: +251,373,213167,5,8,0:0:0:0: +402,170,213378,1,2,0:0:0:0: +136,327,213590,1,10,0:0:0:0: +382,270,213801,1,2,0:0:0:0: +212,144,214012,6,0,P|200:204|224:244,1,104.999996795654,12|2,0:0|0:0,0:0:0:0: +152,88,214435,2,0,P|106:47|59:48,1,104.999996795654,8|2,0:0|0:0,0:0:0:0: +232,64,214857,2,0,P|289:44|312:3,1,104.999996795654,8|2,0:0|0:0,0:0:0:0: +80,120,215280,1,10,0:0:0:0: +272,188,215491,1,2,0:0:0:0: +192,8,215702,6,0,B|183:98|183:98|216:72,1,104.999996795654,12|2,0:0|0:0,0:0:0:0: +384,64,216125,2,0,B|314:122|314:122|355:126,1,104.999996795654,8|2,0:0|0:0,0:0:0:0: +432,244,216547,1,8,0:0:0:0: +260,264,216759,1,8,0:0:0:0: +328,123,216970,1,8,0:0:0:0: +333,175,217075,1,8,0:0:0:0: +338,227,217181,1,8,0:0:0:0: +344,279,217286,1,8,0:0:0:0: +349,331,217392,1,8,0:0:0:0: +349,331,218238,5,8,0:0:0:0: +310,323,218343,1,8,0:0:0:0: +273,317,218449,1,8,0:0:0:0: +236,312,218554,1,8,0:0:0:0: +198,306,218660,5,8,0:0:0:0: +253,296,218765,1,8,0:0:0:0: +309,287,218871,1,8,0:0:0:0: +365,278,218976,1,8,0:0:0:0: +421,268,219082,5,12,0:0:0:0: +348,92,219294,1,0,0:0:0:0: +205,236,219505,5,8,0:0:0:0: +381,163,219717,1,2,0:0:0:0: +237,24,219928,5,8,0:0:0:0: +310,200,220140,1,2,0:0:0:0: +449,52,220350,5,10,0:0:0:0: +273,125,220562,1,2,0:0:0:0: +392,272,220773,6,0,P|441:288|509:276,1,104.999996795654,8|0,0:0|0:0,0:0:0:0: +257,249,221195,2,0,P|206:264|159:314,1,104.999996795654,8|2,0:0|0:0,0:0:0:0: +380,189,221618,2,0,P|411:146|420:79,1,104.999996795654,8|2,0:0|0:0,0:0:0:0: +317,308,222040,2,2,P|347:350|409:380,1,104.999996795654,10|2,0:0|0:0,0:0:0:0: +297,175,222463,6,0,P|297:122|248:24,1,157.499995193482,8|0,0:0|0:0,0:0:0:0: +253,29,222885,2,0,P|308:68|384:64,1,104.999996795654,8|2,0:0|0:0,0:0:0:0: +168,34,223308,1,10,0:0:0:0: +63,216,223519,1,2,0:0:0:0: +220,125,223731,1,10,0:0:0:0: +10,125,223942,1,2,0:0:0:0: +168,216,224153,5,10,0:0:0:0: +63,34,224364,1,2,0:0:0:0: +0,264,224576,2,0,P|60:296|120:268,1,104.999996795654,8|2,0:0|0:0,0:0:0:0: +144,140,224998,6,0,L|153:48,1,52.4999983978272,8|8,0:0|0:0,0:0:0:0: +208,304,225209,2,0,L|202:356,1,52.4999983978272,8|8,0:0|0:0,0:0:0:0: +256,144,225421,2,0,L|265:52,1,52.4999983978272,8|8,0:0|0:0,0:0:0:0: +320,308,225632,2,0,L|314:360,1,52.4999983978272,8|8,0:0|0:0,0:0:0:0: +425,265,225843,5,12,0:0:0:0: +256,188,226055,1,0,0:0:0:0: +425,102,226266,5,8,0:0:0:0: +299,248,226477,1,2,0:0:0:0: +271,53,226688,5,8,0:0:0:0: +369,225,226900,1,2,0:0:0:0: +176,183,227111,5,10,0:0:0:0: +369,151,227322,1,2,0:0:0:0: +274,339,227533,5,8,0:0:0:0: +307,116,227745,1,0,0:0:0:0: +458,279,227956,5,8,0:0:0:0: +256,187,228168,1,2,0:0:0:0: +458,83,228379,5,10,0:0:0:0: +308,256,228590,1,2,0:0:0:0: +274,25,228801,5,10,0:0:0:0: +391,231,229013,1,2,0:0:0:0: +160,181,229224,6,0,P|159:106|212:65,1,104.999996795654,8|0,0:0|0:0,0:0:0:0: +257,263,229646,1,8,0:0:0:0: +288,39,229858,1,2,0:0:0:0: +348,227,230069,6,0,P|282:266|220:241,1,104.999996795654,8|2,0:0|0:0,0:0:0:0: +366,100,230491,1,10,0:0:0:0: +160,181,230703,1,2,0:0:0:0: +288,39,230914,6,0,P|353:76|372:145,1,104.999996795654,8|2,0:0|0:0,0:0:0:0: +175,84,231336,1,8,0:0:0:0: +348,227,231547,1,2,0:0:0:0: +184,336,231759,5,8,0:0:0:0: +181,283,231864,1,8,0:0:0:0: +179,231,231970,1,8,0:0:0:0: +176,178,232075,1,8,0:0:0:0: +174,126,232181,1,12,0:0:0:0: +366,100,232393,1,8,0:0:0:0: +268,228,232604,5,12,0:0:0:0: +412,280,232815,1,0,0:0:0:0: +268,188,233026,5,8,0:0:0:0: +451,187,233237,1,2,0:0:0:0: +256,152,233449,5,8,0:0:0:0: +473,113,233660,1,2,0:0:0:0: +328,248,233871,5,10,0:0:0:0: +289,31,234082,1,2,0:0:0:0: +192,204,234294,5,8,0:0:0:0: +410,241,234505,1,0,0:0:0:0: +112,188,234716,5,8,0:0:0:0: +305,297,234927,1,2,0:0:0:0: +36,176,235139,5,10,0:0:0:0: +181,344,235350,1,2,0:0:0:0: +252,136,235562,5,10,0:0:0:0: +84,281,235773,1,2,0:0:0:0: +316,188,235984,6,0,P|333:134|317:84,1,104.999996795654,8|0,0:0|0:0,0:0:0:0: +328,268,236407,2,0,P|378:242|401:195,1,104.999996795654,8|2,0:0|0:0,0:0:0:0: +276,333,236829,2,0,P|329:350|379:334,1,104.999996795654,8|2,0:0|0:0,0:0:0:0: +316,188,237252,1,10,0:0:0:0: +204,296,237463,1,2,0:0:0:0: +452,336,237674,6,0,L|470:232,1,104.999996795654,8|2,0:0|0:0,0:0:0:0: +209,104,238097,2,0,L|228:208,1,104.999996795654,8|2,0:0|0:0,0:0:0:0: +425,45,238519,6,0,L|517:54,1,52.4999983978272,8|8,0:0|0:0,0:0:0:0: +421,157,238731,2,0,L|513:166,1,52.4999983978272,8|8,0:0|0:0,0:0:0:0: +227,207,238942,2,0,L|174:201,1,52.4999983978272,8|8,0:0|0:0,0:0:0:0: +223,319,239153,2,0,L|170:313,1,52.4999983978272,8|8,0:0|0:0,0:0:0:0: +475,370,239364,5,12,0:0:0:0: +496,228,239576,1,2,0:0:0:0: +380,344,239787,5,8,0:0:0:0: +405,173,239999,1,2,0:0:0:0: +272,320,240209,5,8,0:0:0:0: +302,114,240421,1,2,0:0:0:0: +156,300,240632,5,8,0:0:0:0: +192,52,240844,1,2,0:0:0:0: +20,164,241055,5,12,0:0:0:0: +252,84,241267,1,0,0:0:0:0: +40,8,241477,5,8,0:0:0:0: +240,164,241689,1,2,0:0:0:0: +116,28,241900,5,8,0:0:0:0: +80,274,242111,1,2,0:0:0:0: +32,88,242322,5,8,0:0:0:0: +227,242,242534,1,2,0:0:0:0: +218,61,242745,6,0,L|241:172,1,104.999996795654,12|0,0:0|0:0,0:0:0:0: +131,120,243167,2,0,L|23:84,1,104.999996795654,8|2,0:0|0:0,0:0:0:0: +292,32,243590,2,0,L|315:143,1,104.999996795654,8|2,0:0|0:0,0:0:0:0: +132,204,244012,2,2,L|24:168,1,104.999996795654,10|2,0:0|0:0,0:0:0:0: +368,4,244435,6,0,L|396:164,1,150,12|0,0:0|0:0,0:0:0:0: +136,288,245280,2,8,L|20:252,1,112.5 +28,254,245702,1,8,0:0:0:0: +204,244,245914,5,8,0:0:0:0: +204,244,246020,1,8,0:0:0:0: +204,244,246125,2,0,P|220:296|188:348,1,104.999996795654,12|2,0:0|0:0,0:0:0:0: +100,188,246547,2,0,P|78:141|94:92,1,104.999996795654,8|2,0:0|0:0,0:0:0:0: +120,272,246970,2,0,P|68:288|16:256,1,104.999996795654,8|2,0:0|0:0,0:0:0:0: +176,160,247393,2,2,P|228:144|280:176,1,104.999996795654,10|2,0:0|0:0,0:0:0:0: +277,260,247815,6,0,P|255:213|271:164,1,104.999996795654,8|2,0:0|0:0,0:0:0:0: +357,288,248238,2,0,P|327:329|276:340,1,104.999996795654,8|2,0:0|0:0,0:0:0:0: +341,208,248660,2,2,P|392:212|426:251,1,104.999996795654,10|2,0:0|0:0,0:0:0:0: +276,340,249083,1,10,0:0:0:0: +341,208,249294,1,2,0:0:0:0: +200,120,249505,6,0,P|152:104|92:128,1,104.999996795654,12|2,0:0|0:0,0:0:0:0: +64,300,249928,1,8,0:0:0:0: +152,176,250139,1,2,0:0:0:0: +12,196,250350,1,8,0:0:0:0: +164,210,250561,1,2,0:0:0:0: +32,88,250773,1,10,0:0:0:0: +49,269,250984,1,2,0:0:0:0: +218,129,251195,5,8,0:0:0:0: +293,294,251406,1,2,0:0:0:0: +341,84,251618,1,8,0:0:0:0: +164,210,251829,1,2,0:0:0:0: +400,176,252040,1,10,0:0:0:0: +232,80,252251,1,2,0:0:0:0: +340,272,252463,1,10,0:0:0:0: +456,80,252674,1,2,0:0:0:0: +452,316,252885,5,12,0:0:0:0: +452,316,253307,2,0,L|480:188,1,104.999996795654,8|2,0:0|0:0,0:0:0:0: +284,220,253730,2,0,L|312:92,1,104.999996795654,8|2,0:0|0:0,0:0:0:0: +116,132,254153,2,2,L|144:4,1,104.999996795654,10|2,0:0|0:0,0:0:0:0: +36,236,254576,5,12,0:0:0:0: +36,236,254998,2,0,B|120:232|120:232|104:268,1,104.999996795654,8|2,0:0|0:0,0:0:0:0: +204,152,255421,2,2,B|288:148|288:148|272:184,1,104.999996795654,10|2,0:0|0:0,0:0:0:0: +356,56,255843,2,2,B|440:52|440:52|424:88,1,104.999996795654,10|2,0:0|0:0,0:0:0:0: +356,204,256266,5,12,0:0:0:0: +356,204,256688,2,0,P|376:248|344:312,1,104.999996795654,8|2,0:0|0:0,0:0:0:0: +252,184,257111,1,8,0:0:0:0: +296,340,257322,1,2,0:0:0:0: +192,272,257533,2,2,L|316:252,1,104.999996795654,10|2,0:0|0:0,0:0:0:0: +117,119,257956,5,12,0:0:0:0: +285,31,258167,1,2,0:0:0:0: +137,31,258378,1,8,0:0:0:0: +305,119,258589,1,2,0:0:0:0: +49,55,258801,1,8,0:0:0:0: +26,101,258906,1,8,0:0:0:0: +32,153,259012,1,8,0:0:0:0: +64,194,259117,1,8,0:0:0:0: +112,212,259223,1,12,0:0:0:0: +255,75,259435,1,8,0:0:0:0: +240,252,259646,5,12,0:0:0:0: +112,212,259857,1,0,0:0:0:0: +236,330,260068,1,8,0:0:0:0: +114,133,260280,1,2,0:0:0:0: +146,308,260491,1,8,0:0:0:0: +204,154,260702,1,2,0:0:0:0: +51,304,260914,1,10,0:0:0:0: +298,156,261125,1,2,0:0:0:0: +28,232,261336,6,0,P|44:180|16:124,1,104.999996795654,12|0,0:0|0:0,0:0:0:0: +320,228,261759,2,0,P|304:280|332:336,1,104.999996795654,8|2,0:0|0:0,0:0:0:0: +64,208,262181,2,2,P|76:149|40:90,1,104.999996795654,10|2,0:0|0:0,0:0:0:0: +364,248,262604,2,2,P|351:307|387:365,1,104.999996795654,10|2,0:0|0:0,0:0:0:0: +484,148,263026,6,4,B|448:184|448:184|320:136,1,157.499995193482,12|0,0:0|0:0,0:0:0:0: +315,131,263449,2,0,P|268:112|218:124,1,104.999996795654,8|2,0:0|0:0,0:0:0:0: +192,300,263871,1,8,0:0:0:0: +264,188,264083,1,2,0:0:0:0: +172,208,264294,1,10,0:0:0:0: +284,280,264506,1,2,0:0:0:0: +160,44,264716,6,0,B|124:80|124:80|172:208,1,157.499995193482,12|0,0:0|0:0,0:0:0:0: +172,208,265139,2,0,P|184:258|164:305,1,104.999996795654,8|2,0:0|0:0,0:0:0:0: +104,252,265562,1,10,0:0:0:0: +264,352,265773,1,2,0:0:0:0: +76,352,265984,1,10,0:0:0:0: +248,252,266195,1,2,0:0:0:0: +132,112,266407,5,12,0:0:0:0: +22,288,266618,1,2,0:0:0:0: +22,81,266829,1,8,0:0:0:0: +132,270,267040,1,2,0:0:0:0: +240,112,267252,1,8,0:0:0:0: +350,288,267463,1,2,0:0:0:0: +350,81,267674,1,8,0:0:0:0: +240,270,267885,1,2,0:0:0:0: +512,212,268097,5,12,0:0:0:0: +290,94,268308,1,2,0:0:0:0: +415,310,268519,1,8,0:0:0:0: +417,47,268730,1,2,0:0:0:0: +168,180,268942,1,8,0:0:0:0: +416,214,269153,1,2,0:0:0:0: +225,54,269364,1,10,0:0:0:0: +313,302,269576,1,2,0:0:0:0: +376,172,269787,5,12,0:0:0:0: +177,242,269998,1,2,0:0:0:0: +345,147,270209,1,8,0:0:0:0: +215,254,270420,1,2,0:0:0:0: +325,146,270632,1,8,0:0:0:0: +237,249,270843,1,2,0:0:0:0: +333,238,271055,1,8,0:0:0:0: +230,151,271266,1,2,0:0:0:0: +292,312,271477,1,12,0:0:0:0: +256,192,271583,12,0,272745,0:0:0:0: +163,256,273167,6,0,P|123:240|67:256,1,89.9999972534181,14|0,0:0|0:0,0:0:0:0: +68,364,273590,1,10,0:0:0:0: +236,324,273801,1,2,0:0:0:0: +79,249,274012,2,0,L|91:141,1,89.9999972534181,10|0,0:0|0:0,0:0:0:0: +280,264,274435,2,0,L|290:354,1,89.9999972534181,10|0,0:0|0:0,0:0:0:0: +420,130,274857,5,2,0:0:0:0: +373,261,275068,1,0,0:0:0:0: +512,227,275279,1,10,0:0:0:0: +354,183,275491,1,0,0:0:0:0: +308,358,275702,1,10,0:0:0:0: +478,313,275913,1,0,0:0:0:0: +245,278,276125,1,8,0:0:0:0: +482,205,276336,1,2,0:0:0:0: +349,94,276547,6,0,L|357:218,1,89.9999972534181,10|0,0:0|0:0,0:0:0:0: +239,240,276970,2,0,P|185:217|141:241,1,89.9999972534181,10|0,0:0|0:0,0:0:0:0: +0,268,277393,2,0,P|44:276|82:254,1,89.9999972534181,10|0,0:0|0:0,0:0:0:0: +128,380,277815,2,0,P|152:326|128:282,1,89.9999972534181,10|0,0:0|0:0,0:0:0:0: +116,96,278237,6,0,P|93:151|117:195,1,89.9999972534181,10|0,0:0|0:0,0:0:0:0: +104,16,278660,2,0,P|50:38|35:87,1,89.9999972534181,10|0,0:0|0:0,0:0:0:0: +180,48,279082,1,10,0:0:0:0: +32,140,279294,1,2,0:0:0:0: +180,48,279505,1,10,0:0:0:0: +140,216,279717,1,2,0:0:0:0: +265,71,279928,6,0,P|240:132|260:184,1,89.9999972534181,14|0,0:0|0:0,0:0:0:0: +416,248,280350,1,10,0:0:0:0: +316,132,280562,1,2,0:0:0:0: +252,264,280773,2,0,L|360:252,1,89.9999972534181,10|0,0:0|0:0,0:0:0:0: +484,148,281196,2,0,L|394:138,1,89.9999972534181,10|0,0:0|0:0,0:0:0:0: +426,338,281618,6,0,L|417:249,1,89.9999972534181,10|0,0:0|0:0,0:0:0:0: +326,43,282041,2,0,L|316:133,1,89.9999972534181,10|0,0:0|0:0,0:0:0:0: +296,296,282463,1,10,0:0:0:0: +417,249,282674,1,0,0:0:0:0: +248,216,282885,1,10,0:0:0:0: +321,376,283097,1,0,0:0:0:0: +370,163,283308,6,0,L|382:55,1,89.9999972534181,10|0,0:0|0:0,0:0:0:0: +248,216,283730,2,0,L|260:108,1,89.9999972534181,10|0,0:0|0:0,0:0:0:0: +122,266,284153,2,0,L|134:158,1,89.9999972534181,10|0,0:0|0:0,0:0:0:0: +200,280,284575,1,10,0:0:0:0: +56,144,284787,1,0,0:0:0:0: +69,335,284998,6,0,P|110:353|152:340,1,89.9999972534181,10|0,0:0|0:0,0:0:0:0: +213,180,285420,2,0,P|173:163|131:176,1,89.9999972534181,10|0,0:0|0:0,0:0:0:0: +304,272,285843,1,8,0:0:0:0: +299,228,285948,1,8,0:0:0:0: +294,183,286054,1,8,0:0:0:0: +288,138,286159,1,8,0:0:0:0: +283,94,286265,1,12,0:0:0:0: +164,52,286477,5,8,0:0:0:0: +164,52,286583,1,8,0:0:0:0: +164,52,286688,2,0,B|194:164|194:164|114:260|114:260|236:263|236:263|299:364|299:364|339:251|339:251|455:226|455:226|361:152|361:152|373:36|373:36|275:99|275:99|218:72,1,1124.99994039536,4|0,0:0|0:0,0:1:0:0: +228,76,293238,5,0,0:0:0:0: +256,192,293343,12,0,301900,0:0:0:0: diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/basic-expected-conversion.json b/osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/basic-expected-conversion.json similarity index 100% rename from osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/basic-expected-conversion.json rename to osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/basic-expected-conversion.json diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/basic.osu b/osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/basic.osu similarity index 96% rename from osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/basic.osu rename to osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/basic.osu index 40b4409760..abd2ff2ee6 100644 --- a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/basic.osu +++ b/osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/basic.osu @@ -1,27 +1,27 @@ -osu file format v14 - -[Difficulty] -HPDrainRate:6 -CircleSize:4 -OverallDifficulty:7 -ApproachRate:8.3 -SliderMultiplier:1.6 -SliderTickRate:1 - -[TimingPoints] -500,500,4,2,1,50,1,0 -13426,-100,4,3,1,45,0,0 -14884,-100,4,2,1,50,0,0 - -[HitObjects] -96,192,500,6,0,L|416:192,2,320 -256,192,3000,12,0,4000,0:0:0:0: -256,192,4500,12,0,5500,0:0:0:0: -256,192,6000,12,0,6500,0:0:0:0: -256,128,7000,6,0,L|352:128,4,80 -32,192,8500,6,0,B|32:384|256:384|256:192|256:192|256:0|512:0|512:192,1,800 -256,192,11500,12,0,12000,0:0:0:0: -512,320,12500,6,0,B|0:256|0:256|512:96|512:96|256:32,1,1280 -256,256,17000,6,0,L|160:256,4,80 -256,192,18500,12,0,19450,0:0:0:0: -216,231,19875,6,0,B|216:135|280:135|344:135|344:199|344:263|248:327|248:327|120:327|120:327|56:39|408:39|408:39|472:150|408:342,1,1280 +osu file format v14 + +[Difficulty] +HPDrainRate:6 +CircleSize:4 +OverallDifficulty:7 +ApproachRate:8.3 +SliderMultiplier:1.6 +SliderTickRate:1 + +[TimingPoints] +500,500,4,2,1,50,1,0 +13426,-100,4,3,1,45,0,0 +14884,-100,4,2,1,50,0,0 + +[HitObjects] +96,192,500,6,0,L|416:192,2,320 +256,192,3000,12,0,4000,0:0:0:0: +256,192,4500,12,0,5500,0:0:0:0: +256,192,6000,12,0,6500,0:0:0:0: +256,128,7000,6,0,L|352:128,4,80 +32,192,8500,6,0,B|32:384|256:384|256:192|256:192|256:0|512:0|512:192,1,800 +256,192,11500,12,0,12000,0:0:0:0: +512,320,12500,6,0,B|0:256|0:256|512:96|512:96|256:32,1,1280 +256,256,17000,6,0,L|160:256,4,80 +256,192,18500,12,0,19450,0:0:0:0: +216,231,19875,6,0,B|216:135|280:135|344:135|344:199|344:263|248:327|248:327|120:327|120:327|56:39|408:39|408:39|472:150|408:342,1,1280 diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve-expected-conversion.json b/osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/colinear-perfect-curve-expected-conversion.json similarity index 100% rename from osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve-expected-conversion.json rename to osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/colinear-perfect-curve-expected-conversion.json diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve.osu b/osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/colinear-perfect-curve.osu similarity index 100% rename from osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve.osu rename to osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/colinear-perfect-curve.osu diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/diffcalc-test.osu b/osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/diffcalc-test.osu similarity index 100% rename from osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/diffcalc-test.osu rename to osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/diffcalc-test.osu diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/multi-segment-slider-expected-conversion.json b/osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/multi-segment-slider-expected-conversion.json similarity index 100% rename from osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/multi-segment-slider-expected-conversion.json rename to osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/multi-segment-slider-expected-conversion.json diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/multi-segment-slider.osu b/osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/multi-segment-slider.osu similarity index 100% rename from osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/multi-segment-slider.osu rename to osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/multi-segment-slider.osu diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/nan-slider-expected-conversion.json b/osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/nan-slider-expected-conversion.json similarity index 100% rename from osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/nan-slider-expected-conversion.json rename to osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/nan-slider-expected-conversion.json diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/nan-slider.osu b/osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/nan-slider.osu similarity index 100% rename from osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/nan-slider.osu rename to osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/nan-slider.osu diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/old-stacking-expected-conversion.json b/osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/old-stacking-expected-conversion.json similarity index 100% rename from osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/old-stacking-expected-conversion.json rename to osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/old-stacking-expected-conversion.json diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/old-stacking.osu b/osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/old-stacking.osu similarity index 100% rename from osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/old-stacking.osu rename to osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/old-stacking.osu diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/repeat-slider-expected-conversion.json b/osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/repeat-slider-expected-conversion.json similarity index 100% rename from osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/repeat-slider-expected-conversion.json rename to osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/repeat-slider-expected-conversion.json diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/repeat-slider.osu b/osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/repeat-slider.osu similarity index 100% rename from osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/repeat-slider.osu rename to osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/repeat-slider.osu diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/slider-paths-edge-case-expected-conversion.json b/osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/slider-paths-edge-case-expected-conversion.json similarity index 100% rename from osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/slider-paths-edge-case-expected-conversion.json rename to osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/slider-paths-edge-case-expected-conversion.json diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/slider-paths-edge-case.osu b/osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/slider-paths-edge-case.osu similarity index 100% rename from osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/slider-paths-edge-case.osu rename to osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/slider-paths-edge-case.osu diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/slider-ticks-edge-case-expected-conversion.json b/osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/slider-ticks-edge-case-expected-conversion.json similarity index 99% rename from osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/slider-ticks-edge-case-expected-conversion.json rename to osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/slider-ticks-edge-case-expected-conversion.json index 6d97b643b1..0bfe776dc7 100644 --- a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/slider-ticks-edge-case-expected-conversion.json +++ b/osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/slider-ticks-edge-case-expected-conversion.json @@ -13,16 +13,6 @@ "Y": 0.0 } }, - { - "StartTime": 7817.0, - "EndTime": 7817.0, - "X": 30.9946651, - "Y": 208.5157, - "StackOffset": { - "X": 0.0, - "Y": 0.0 - } - }, { "StartTime": 7843.0, "EndTime": 7843.0, @@ -32,8 +22,18 @@ "X": 0.0, "Y": 0.0 } + }, + { + "StartTime": 7817.0, + "EndTime": 7817.0, + "X": 30.9946651, + "Y": 208.5157, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } } ] } ] -} +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/slider-ticks-edge-case.osu b/osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/slider-ticks-edge-case.osu similarity index 100% rename from osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/slider-ticks-edge-case.osu rename to osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/slider-ticks-edge-case.osu diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/slider-ticks-expected-conversion.json b/osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/slider-ticks-expected-conversion.json similarity index 100% rename from osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/slider-ticks-expected-conversion.json rename to osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/slider-ticks-expected-conversion.json diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/slider-ticks.osu b/osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/slider-ticks.osu similarity index 100% rename from osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/slider-ticks.osu rename to osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/slider-ticks.osu diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/uneven-repeat-slider-expected-conversion.json b/osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/uneven-repeat-slider-expected-conversion.json new file mode 100644 index 0000000000..dda9078e57 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/uneven-repeat-slider-expected-conversion.json @@ -0,0 +1,579 @@ +{ + "Mappings": [ + { + "StartTime": 369.0, + "Objects": [ + { + "StartTime": 369.0, + "EndTime": 369.0, + "X": 127.0, + "Y": 194.0, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 450.0, + "EndTime": 450.0, + "X": 166.53389, + "Y": 193.8691, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 532.0, + "EndTime": 532.0, + "X": 206.555847, + "Y": 193.736572, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 614.0, + "EndTime": 614.0, + "X": 246.57782, + "Y": 193.60405, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 696.0, + "EndTime": 696.0, + "X": 286.5998, + "Y": 193.471527, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 778.0, + "EndTime": 778.0, + "X": 326.621765, + "Y": 193.339, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 860.0, + "EndTime": 860.0, + "X": 366.6437, + "Y": 193.206482, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 942.0, + "EndTime": 942.0, + "X": 406.66568, + "Y": 193.073959, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 970.0, + "EndTime": 970.0, + "X": 420.331726, + "Y": 193.0287, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 997.0, + "EndTime": 997.0, + "X": 407.153748, + "Y": 193.072342, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 1079.0, + "EndTime": 1079.0, + "X": 367.131775, + "Y": 193.204865, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 1161.0, + "EndTime": 1161.0, + "X": 327.1098, + "Y": 193.337387, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 1243.0, + "EndTime": 1243.0, + "X": 287.08783, + "Y": 193.46991, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 1325.0, + "EndTime": 1325.0, + "X": 247.0659, + "Y": 193.602432, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 1407.0, + "EndTime": 1407.0, + "X": 207.043915, + "Y": 193.734955, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 1489.0, + "EndTime": 1489.0, + "X": 167.021988, + "Y": 193.867477, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 1571.0, + "EndTime": 1571.0, + "X": 127.0, + "Y": 194.0, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 1653.0, + "EndTime": 1653.0, + "X": 167.021988, + "Y": 193.867477, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 1735.0, + "EndTime": 1735.0, + "X": 207.043976, + "Y": 193.734955, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 1817.0, + "EndTime": 1817.0, + "X": 247.065887, + "Y": 193.602432, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 1899.0, + "EndTime": 1899.0, + "X": 287.08783, + "Y": 193.46991, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 1981.0, + "EndTime": 1981.0, + "X": 327.1098, + "Y": 193.337387, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 2062.0, + "EndTime": 2062.0, + "X": 366.643738, + "Y": 193.206482, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 2144.0, + "EndTime": 2144.0, + "X": 406.665649, + "Y": 193.073959, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 2172.0, + "EndTime": 2172.0, + "X": 420.331726, + "Y": 193.0287, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 2199.0, + "EndTime": 2199.0, + "X": 407.153748, + "Y": 193.072342, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 2281.0, + "EndTime": 2281.0, + "X": 367.1318, + "Y": 193.204865, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 2363.0, + "EndTime": 2363.0, + "X": 327.1098, + "Y": 193.337387, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 2445.0, + "EndTime": 2445.0, + "X": 287.08783, + "Y": 193.46991, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 2527.0, + "EndTime": 2527.0, + "X": 247.065887, + "Y": 193.602432, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 2609.0, + "EndTime": 2609.0, + "X": 207.043976, + "Y": 193.734955, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 2691.0, + "EndTime": 2691.0, + "X": 167.021988, + "Y": 193.867477, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 2773.0, + "EndTime": 2773.0, + "X": 127.0, + "Y": 194.0, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 2855.0, + "EndTime": 2855.0, + "X": 167.021988, + "Y": 193.867477, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 2937.0, + "EndTime": 2937.0, + "X": 207.043976, + "Y": 193.734955, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 3019.0, + "EndTime": 3019.0, + "X": 247.065948, + "Y": 193.602432, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 3101.0, + "EndTime": 3101.0, + "X": 287.087952, + "Y": 193.46991, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 3183.0, + "EndTime": 3183.0, + "X": 327.109772, + "Y": 193.337387, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 3265.0, + "EndTime": 3265.0, + "X": 367.131775, + "Y": 193.204865, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 3347.0, + "EndTime": 3347.0, + "X": 407.153748, + "Y": 193.072342, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 3374.0, + "EndTime": 3374.0, + "X": 420.331726, + "Y": 193.0287, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 3401.0, + "EndTime": 3401.0, + "X": 407.153748, + "Y": 193.072342, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 3483.0, + "EndTime": 3483.0, + "X": 367.131775, + "Y": 193.204865, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 3565.0, + "EndTime": 3565.0, + "X": 327.109772, + "Y": 193.337387, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 3647.0, + "EndTime": 3647.0, + "X": 287.087952, + "Y": 193.46991, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 3729.0, + "EndTime": 3729.0, + "X": 247.065948, + "Y": 193.602432, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 3811.0, + "EndTime": 3811.0, + "X": 207.043976, + "Y": 193.734955, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 3893.0, + "EndTime": 3893.0, + "X": 167.021988, + "Y": 193.867477, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 3975.0, + "EndTime": 3975.0, + "X": 127.0, + "Y": 194.0, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 4057.0, + "EndTime": 4057.0, + "X": 167.021988, + "Y": 193.867477, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 4139.0, + "EndTime": 4139.0, + "X": 207.043976, + "Y": 193.734955, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 4221.0, + "EndTime": 4221.0, + "X": 247.065948, + "Y": 193.602432, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 4303.0, + "EndTime": 4303.0, + "X": 287.087952, + "Y": 193.46991, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 4385.0, + "EndTime": 4385.0, + "X": 327.109772, + "Y": 193.337387, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 4467.0, + "EndTime": 4467.0, + "X": 367.131775, + "Y": 193.204865, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 4549.0, + "EndTime": 4549.0, + "X": 407.153748, + "Y": 193.072342, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + }, + { + "StartTime": 4540.0, + "EndTime": 4540.0, + "X": 420.331726, + "Y": 193.0287, + "StackOffset": { + "X": 0.0, + "Y": 0.0 + } + } + ] + } + ] +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/uneven-repeat-slider.osu b/osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/uneven-repeat-slider.osu similarity index 100% rename from osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/uneven-repeat-slider.osu rename to osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/uneven-repeat-slider.osu diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/very-fast-slider.osu b/osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/very-fast-slider.osu similarity index 100% rename from osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/very-fast-slider.osu rename to osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/very-fast-slider.osu diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/zero-length-sliders.osu b/osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/zero-length-sliders.osu similarity index 100% rename from osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/zero-length-sliders.osu rename to osu.Game.Rulesets.Osu.Tests/Resources/Testing/Beatmaps/zero-length-sliders.osu diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/uneven-repeat-slider-expected-conversion.json b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/uneven-repeat-slider-expected-conversion.json deleted file mode 100644 index 12d1645c04..0000000000 --- a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/uneven-repeat-slider-expected-conversion.json +++ /dev/null @@ -1,348 +0,0 @@ -{ - "Mappings": [{ - "StartTime": 369, - "Objects": [{ - "StartTime": 369, - "EndTime": 369, - "X": 127, - "Y": 194 - }, - { - "StartTime": 450, - "EndTime": 450, - "X": 166.53389, - "Y": 193.8691 - }, - { - "StartTime": 532, - "EndTime": 532, - "X": 206.555847, - "Y": 193.736572 - }, - { - "StartTime": 614, - "EndTime": 614, - "X": 246.57782, - "Y": 193.60405 - }, - { - "StartTime": 696, - "EndTime": 696, - "X": 286.5998, - "Y": 193.471527 - }, - { - "StartTime": 778, - "EndTime": 778, - "X": 326.621765, - "Y": 193.339 - }, - { - "StartTime": 860, - "EndTime": 860, - "X": 366.6437, - "Y": 193.206482 - }, - { - "StartTime": 942, - "EndTime": 942, - "X": 406.66568, - "Y": 193.073959 - }, - { - "StartTime": 970, - "EndTime": 970, - "X": 420.331726, - "Y": 193.0287 - }, - { - "StartTime": 997, - "EndTime": 997, - "X": 407.153748, - "Y": 193.072342 - }, - { - "StartTime": 1079, - "EndTime": 1079, - "X": 367.131775, - "Y": 193.204865 - }, - { - "StartTime": 1161, - "EndTime": 1161, - "X": 327.1098, - "Y": 193.337387 - }, - { - "StartTime": 1243, - "EndTime": 1243, - "X": 287.08783, - "Y": 193.46991 - }, - { - "StartTime": 1325, - "EndTime": 1325, - "X": 247.0659, - "Y": 193.602432 - }, - { - "StartTime": 1407, - "EndTime": 1407, - "X": 207.043915, - "Y": 193.734955 - }, - { - "StartTime": 1489, - "EndTime": 1489, - "X": 167.021988, - "Y": 193.867477 - }, - { - "StartTime": 1571, - "EndTime": 1571, - "X": 127, - "Y": 194 - }, - { - "StartTime": 1653, - "EndTime": 1653, - "X": 167.021988, - "Y": 193.867477 - }, - { - "StartTime": 1735, - "EndTime": 1735, - "X": 207.043976, - "Y": 193.734955 - }, - { - "StartTime": 1817, - "EndTime": 1817, - "X": 247.065887, - "Y": 193.602432 - }, - { - "StartTime": 1899, - "EndTime": 1899, - "X": 287.08783, - "Y": 193.46991 - }, - { - "StartTime": 1981, - "EndTime": 1981, - "X": 327.1098, - "Y": 193.337387 - }, - { - "StartTime": 2062, - "EndTime": 2062, - "X": 366.643738, - "Y": 193.206482 - }, - { - "StartTime": 2144, - "EndTime": 2144, - "X": 406.665649, - "Y": 193.073959 - }, - { - "StartTime": 2172, - "EndTime": 2172, - "X": 420.331726, - "Y": 193.0287 - }, - { - "StartTime": 2199, - "EndTime": 2199, - "X": 407.153748, - "Y": 193.072342 - }, - { - "StartTime": 2281, - "EndTime": 2281, - "X": 367.1318, - "Y": 193.204865 - }, - { - "StartTime": 2363, - "EndTime": 2363, - "X": 327.1098, - "Y": 193.337387 - }, - { - "StartTime": 2445, - "EndTime": 2445, - "X": 287.08783, - "Y": 193.46991 - }, - { - "StartTime": 2527, - "EndTime": 2527, - "X": 247.065887, - "Y": 193.602432 - }, - { - "StartTime": 2609, - "EndTime": 2609, - "X": 207.043976, - "Y": 193.734955 - }, - { - "StartTime": 2691, - "EndTime": 2691, - "X": 167.021988, - "Y": 193.867477 - }, - { - "StartTime": 2773, - "EndTime": 2773, - "X": 127, - "Y": 194 - }, - { - "StartTime": 2855, - "EndTime": 2855, - "X": 167.021988, - "Y": 193.867477 - }, - { - "StartTime": 2937, - "EndTime": 2937, - "X": 207.043976, - "Y": 193.734955 - }, - { - "StartTime": 3019, - "EndTime": 3019, - "X": 247.065948, - "Y": 193.602432 - }, - { - "StartTime": 3101, - "EndTime": 3101, - "X": 287.087952, - "Y": 193.46991 - }, - { - "StartTime": 3183, - "EndTime": 3183, - "X": 327.109772, - "Y": 193.337387 - }, - { - "StartTime": 3265, - "EndTime": 3265, - "X": 367.131775, - "Y": 193.204865 - }, - { - "StartTime": 3347, - "EndTime": 3347, - "X": 407.153748, - "Y": 193.072342 - }, - { - "StartTime": 3374, - "EndTime": 3374, - "X": 420.331726, - "Y": 193.0287 - }, - { - "StartTime": 3401, - "EndTime": 3401, - "X": 407.153748, - "Y": 193.072342 - }, - { - "StartTime": 3483, - "EndTime": 3483, - "X": 367.131775, - "Y": 193.204865 - }, - { - "StartTime": 3565, - "EndTime": 3565, - "X": 327.109772, - "Y": 193.337387 - }, - { - "StartTime": 3647, - "EndTime": 3647, - "X": 287.087952, - "Y": 193.46991 - }, - { - "StartTime": 3729, - "EndTime": 3729, - "X": 247.065948, - "Y": 193.602432 - }, - { - "StartTime": 3811, - "EndTime": 3811, - "X": 207.043976, - "Y": 193.734955 - }, - { - "StartTime": 3893, - "EndTime": 3893, - "X": 167.021988, - "Y": 193.867477 - }, - { - "StartTime": 3975, - "EndTime": 3975, - "X": 127, - "Y": 194 - }, - { - "StartTime": 4057, - "EndTime": 4057, - "X": 167.021988, - "Y": 193.867477 - }, - { - "StartTime": 4139, - "EndTime": 4139, - "X": 207.043976, - "Y": 193.734955 - }, - { - "StartTime": 4221, - "EndTime": 4221, - "X": 247.065948, - "Y": 193.602432 - }, - { - "StartTime": 4303, - "EndTime": 4303, - "X": 287.087952, - "Y": 193.46991 - }, - { - "StartTime": 4385, - "EndTime": 4385, - "X": 327.109772, - "Y": 193.337387 - }, - { - "StartTime": 4467, - "EndTime": 4467, - "X": 367.131775, - "Y": 193.204865 - }, - { - "StartTime": 4540, - "EndTime": 4540, - "X": 420.331726, - "Y": 193.0287 - }, - { - "StartTime": 4549, - "EndTime": 4549, - "X": 407.153748, - "Y": 193.072342 - } - ] - }] -} \ No newline at end of file From 07dc44ccd769213a1078ac36bd867c7f6ee300f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Dec 2023 16:18:54 +0900 Subject: [PATCH 1883/4092] Make log export async and show notification on completion --- .../Sections/General/UpdateSettings.cs | 62 +++++++++++++------ 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 398e4b39af..a8f5b655d0 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -31,8 +31,11 @@ namespace osu.Game.Overlays.Settings.Sections.General [Resolved] private INotificationOverlay? notifications { get; set; } + [Resolved] + private Storage storage { get; set; } = null!; + [BackgroundDependencyLoader] - private void load(Storage storage, OsuConfigManager config, OsuGame game) + private void load(OsuConfigManager config, OsuGame game) { Add(new SettingsEnumDropdown { @@ -78,23 +81,7 @@ namespace osu.Game.Overlays.Settings.Sections.General { Text = GeneralSettingsStrings.ExportLogs, Keywords = new[] { @"bug", "report", "logs", "files" }, - Action = () => - { - var logStorage = Logger.Storage; - - const string archive_filename = "exports/compressed-logs.zip"; - - using (var outStream = storage.CreateFileSafely(archive_filename)) - using (var zip = ZipArchive.Create()) - { - foreach (string? f in logStorage.GetFiles(string.Empty, "*.log")) - zip.AddEntry(f, logStorage.GetStream(f), true); - - zip.SaveTo(outStream); - } - - storage.PresentFileExternally(archive_filename); - }, + Action = () => Task.Run(exportLogs), }); Add(new SettingsButton @@ -104,5 +91,44 @@ namespace osu.Game.Overlays.Settings.Sections.General }); } } + + private void exportLogs() + { + ProgressNotification notification = new ProgressNotification + { + State = ProgressNotificationState.Active, + Text = "Exporting logs...", + }; + + notifications?.Post(notification); + + const string archive_filename = "exports/compressed-logs.zip"; + + try + { + var logStorage = Logger.Storage; + + using (var outStream = storage.CreateFileSafely(archive_filename)) + using (var zip = ZipArchive.Create()) + { + foreach (string? f in logStorage.GetFiles(string.Empty, "*.log")) zip.AddEntry(f, logStorage.GetStream(f), true); + + zip.SaveTo(outStream); + } + } + catch + { + notification.State = ProgressNotificationState.Cancelled; + + // cleanup if export is failed or canceled. + storage.Delete(archive_filename); + throw; + } + + notification.CompletionText = "Exported logs! Click to view."; + notification.CompletionClickAction = () => storage.PresentFileExternally(archive_filename); + + notification.State = ProgressNotificationState.Completed; + } } } From 856310e954fd0a5b25ba20a26239a314bc2363ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 7 Dec 2023 08:22:01 +0100 Subject: [PATCH 1884/4092] Remove reference to removed comment from another comment --- osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs index 4a217a19ea..838bd35dd4 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs @@ -50,9 +50,8 @@ namespace osu.Game.Rulesets.Osu.Tests double startTime = obj.StartTime; double endTime = obj.GetEndTime(); - // as stated in the inline comment above, this is locally bringing back - // the stable treatment of the "legacy last tick" just to make sure - // that the conversion output matches. + // this is locally bringing back the stable treatment of the "legacy last tick" + // just to make sure that the conversion output matches. // compare: `SliderEventGenerator.Generate()`, and the calculation of `legacyLastTickTime`. if (obj is SliderTailCircle && parent is Slider slider) { From 323808ad1e915ef2b9f2aeb5b9846ba8f92cc9dc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Dec 2023 16:34:26 +0900 Subject: [PATCH 1885/4092] Add more inline commenting around `VELOCITY_MULTIPLIER` application to `TimeRange` --- osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 88085dfe97..49b0ad811d 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -71,14 +71,18 @@ namespace osu.Game.Rulesets.Taiko.UI protected virtual double ComputeTimeRange() { // Taiko scrolls at a constant 100px per 1000ms. More notes become visible as the playfield is lengthened. - const float scroll_rate = 10 / TaikoBeatmapConverter.VELOCITY_MULTIPLIER; + const float scroll_rate = 10; // Since the time range will depend on a positional value, it is referenced to the x480 pixel space. // Width is used because it defines how many notes fit on the playfield. // We clamp the ratio to the maximum aspect ratio to keep scroll speed consistent on widths lower than the default. float ratio = Math.Max(DrawSize.X / 768f, TaikoPlayfieldAdjustmentContainer.MAXIMUM_ASPECT); - return (Playfield.HitObjectContainer.DrawWidth / ratio) * scroll_rate; + // Stable internally increased the slider velocity of objects by a factor of `VELOCITY_MULTIPLIER`. + // To simulate this, we shrink the time range by that factor here. + // This, when combined with the rest of the scrolling ruleset machinery (see `MultiplierControlPoint` et al.), + // has the effect of increasing each multiplier control point's multiplier by `VELOCITY_MULTIPLIER`, ensuring parity with stable. + return (Playfield.HitObjectContainer.DrawWidth / ratio) * scroll_rate / TaikoBeatmapConverter.VELOCITY_MULTIPLIER; } protected override void UpdateAfterChildren() From 0fe2e1e8d60bde53ded276d1787d8404c6035570 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 8 Dec 2023 16:33:10 +0900 Subject: [PATCH 1886/4092] Re-fix mania conversion following new discoveries --- .../Beatmaps/100374-expected-conversion.json | 2 +- .../Beatmaps/20544-expected-conversion.json | 2 +- .../Beatmaps/basic-expected-conversion.json | 296 ++++++++++-------- ...ero-length-slider-expected-conversion.json | 28 +- .../Patterns/Legacy/PatternGenerator.cs | 8 +- 5 files changed, 186 insertions(+), 150 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/100374-expected-conversion.json b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/100374-expected-conversion.json index 59f73f7ad4..59cf6d2672 100644 --- a/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/100374-expected-conversion.json +++ b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/100374-expected-conversion.json @@ -1 +1 @@ -{"Mappings":[{"RandomW":273084013,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":15562.0,"Objects":[{"StartTime":15562.0,"EndTime":17155.0,"Column":0}]},{"RandomW":2659258901,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273084013,"StartTime":17686.0,"Objects":[{"StartTime":17686.0,"EndTime":17686.0,"Column":0},{"StartTime":17686.0,"EndTime":17686.0,"Column":1}]},{"RandomW":3083655709,"RandomX":273326509,"RandomY":273084013,"RandomZ":2659258901,"StartTime":17951.0,"Objects":[{"StartTime":17951.0,"EndTime":17951.0,"Column":1}]},{"RandomW":3588026162,"RandomX":2659258901,"RandomY":3083655709,"RandomZ":4073603712,"StartTime":18217.0,"Objects":[{"StartTime":18217.0,"EndTime":18217.0,"Column":2},{"StartTime":18217.0,"EndTime":18217.0,"Column":4}]},{"RandomW":1130061350,"RandomX":3083655709,"RandomY":4073603712,"RandomZ":3588026162,"StartTime":18482.0,"Objects":[{"StartTime":18482.0,"EndTime":18482.0,"Column":2}]},{"RandomW":315421426,"RandomX":3588026162,"RandomY":1130061350,"RandomZ":2459334754,"StartTime":18748.0,"Objects":[{"StartTime":18748.0,"EndTime":19013.0,"Column":0}]},{"RandomW":3110660773,"RandomX":2459334754,"RandomY":315421426,"RandomZ":542845670,"StartTime":19279.0,"Objects":[{"StartTime":19279.0,"EndTime":19809.0,"Column":3},{"StartTime":19544.0,"EndTime":19544.0,"Column":1},{"StartTime":19809.0,"EndTime":19809.0,"Column":1}]},{"RandomW":3110660773,"RandomX":2459334754,"RandomY":315421426,"RandomZ":542845670,"StartTime":20075.0,"Objects":[{"StartTime":20075.0,"EndTime":20075.0,"Column":4},{"StartTime":20075.0,"EndTime":20075.0,"Column":2}]},{"RandomW":2552021122,"RandomX":315421426,"RandomY":542845670,"RandomZ":3110660773,"StartTime":20341.0,"Objects":[{"StartTime":20341.0,"EndTime":20341.0,"Column":3}]},{"RandomW":3979536913,"RandomX":542845670,"RandomY":3110660773,"RandomZ":2552021122,"StartTime":20606.0,"Objects":[{"StartTime":20606.0,"EndTime":20606.0,"Column":2},{"StartTime":20606.0,"EndTime":20606.0,"Column":3}]},{"RandomW":3926138036,"RandomX":2552021122,"RandomY":3979536913,"RandomZ":348643659,"StartTime":20871.0,"Objects":[{"StartTime":20871.0,"EndTime":21401.0,"Column":4}]},{"RandomW":4001028953,"RandomX":348643659,"RandomY":3926138036,"RandomZ":2489502118,"StartTime":21933.0,"Objects":[{"StartTime":21933.0,"EndTime":22198.0,"Column":5}]},{"RandomW":263714783,"RandomX":2489502118,"RandomY":4001028953,"RandomZ":3315380836,"StartTime":22464.0,"Objects":[{"StartTime":22464.0,"EndTime":22729.0,"Column":0}]},{"RandomW":3045229215,"RandomX":3315380836,"RandomY":263714783,"RandomZ":2367299702,"StartTime":22995.0,"Objects":[{"StartTime":22995.0,"EndTime":23791.0,"Column":2}]},{"RandomW":622075324,"RandomX":2367299702,"RandomY":3045229215,"RandomZ":2511145433,"StartTime":24057.0,"Objects":[{"StartTime":24057.0,"EndTime":24322.0,"Column":1}]},{"RandomW":1428674661,"RandomX":3630592823,"RandomY":628640291,"RandomZ":2684635853,"StartTime":24588.0,"Objects":[{"StartTime":24588.0,"EndTime":24853.0,"Column":4},{"StartTime":24588.0,"EndTime":24853.0,"Column":3}]},{"RandomW":2963472042,"RandomX":3191072317,"RandomY":1509788298,"RandomZ":3677221210,"StartTime":25119.0,"Objects":[{"StartTime":25119.0,"EndTime":25649.0,"Column":2}]},{"RandomW":2441208973,"RandomX":1509788298,"RandomY":3677221210,"RandomZ":2963472042,"StartTime":26181.0,"Objects":[{"StartTime":26181.0,"EndTime":26181.0,"Column":2},{"StartTime":26181.0,"EndTime":26181.0,"Column":3}]},{"RandomW":614303213,"RandomX":3677221210,"RandomY":2963472042,"RandomZ":2441208973,"StartTime":26447.0,"Objects":[{"StartTime":26447.0,"EndTime":26447.0,"Column":3}]},{"RandomW":931064848,"RandomX":2441208973,"RandomY":614303213,"RandomZ":2425227013,"StartTime":26712.0,"Objects":[{"StartTime":26712.0,"EndTime":26977.0,"Column":2}]},{"RandomW":1631554006,"RandomX":2425227013,"RandomY":931064848,"RandomZ":2839921662,"StartTime":27243.0,"Objects":[{"StartTime":27243.0,"EndTime":27508.0,"Column":4}]},{"RandomW":1102544522,"RandomX":2839921662,"RandomY":1631554006,"RandomZ":2171149531,"StartTime":27774.0,"Objects":[{"StartTime":27774.0,"EndTime":28039.0,"Column":3}]},{"RandomW":1535528787,"RandomX":2171149531,"RandomY":1102544522,"RandomZ":3328843633,"StartTime":28305.0,"Objects":[{"StartTime":28305.0,"EndTime":28835.0,"Column":4},{"StartTime":28305.0,"EndTime":28305.0,"Column":3},{"StartTime":28570.0,"EndTime":28570.0,"Column":3},{"StartTime":28835.0,"EndTime":28835.0,"Column":3}]},{"RandomW":2462060348,"RandomX":1102544522,"RandomY":3328843633,"RandomZ":1535528787,"StartTime":29102.0,"Objects":[{"StartTime":29102.0,"EndTime":29102.0,"Column":3}]},{"RandomW":2548780898,"RandomX":2462060348,"RandomY":1752789184,"RandomZ":4269701929,"StartTime":29367.0,"Objects":[{"StartTime":29367.0,"EndTime":29897.0,"Column":5},{"StartTime":29367.0,"EndTime":29897.0,"Column":1}]},{"RandomW":2872444045,"RandomX":2548780898,"RandomY":96471884,"RandomZ":2795275332,"StartTime":30429.0,"Objects":[{"StartTime":30429.0,"EndTime":30694.0,"Column":2}]},{"RandomW":554186146,"RandomX":2872444045,"RandomY":1718345430,"RandomZ":1676944188,"StartTime":30960.0,"Objects":[{"StartTime":30960.0,"EndTime":31225.0,"Column":4},{"StartTime":30960.0,"EndTime":31225.0,"Column":1}]},{"RandomW":44350362,"RandomX":1676944188,"RandomY":554186146,"RandomZ":973164386,"StartTime":31491.0,"Objects":[{"StartTime":31491.0,"EndTime":32287.0,"Column":0}]},{"RandomW":2689469863,"RandomX":973164386,"RandomY":44350362,"RandomZ":3230373169,"StartTime":32553.0,"Objects":[{"StartTime":32553.0,"EndTime":32818.0,"Column":1}]},{"RandomW":3076210018,"RandomX":3230373169,"RandomY":2689469863,"RandomZ":2416196755,"StartTime":33084.0,"Objects":[{"StartTime":33084.0,"EndTime":33349.0,"Column":2}]},{"RandomW":4212524875,"RandomX":2416196755,"RandomY":3076210018,"RandomZ":736433317,"StartTime":33615.0,"Objects":[{"StartTime":33615.0,"EndTime":34145.0,"Column":5}]},{"RandomW":668643347,"RandomX":4212524875,"RandomY":1246190622,"RandomZ":614058009,"StartTime":34677.0,"Objects":[{"StartTime":34677.0,"EndTime":34677.0,"Column":0},{"StartTime":34677.0,"EndTime":34677.0,"Column":5}]},{"RandomW":4133034829,"RandomX":668643347,"RandomY":1824376828,"RandomZ":476758489,"StartTime":34942.0,"Objects":[{"StartTime":34942.0,"EndTime":34942.0,"Column":1},{"StartTime":34942.0,"EndTime":34942.0,"Column":5}]},{"RandomW":82933693,"RandomX":1824376828,"RandomY":476758489,"RandomZ":4133034829,"StartTime":35208.0,"Objects":[{"StartTime":35208.0,"EndTime":35208.0,"Column":0},{"StartTime":35208.0,"EndTime":35208.0,"Column":1}]},{"RandomW":2263995128,"RandomX":476758489,"RandomY":4133034829,"RandomZ":82933693,"StartTime":35473.0,"Objects":[{"StartTime":35473.0,"EndTime":35473.0,"Column":1}]},{"RandomW":3437211638,"RandomX":4133034829,"RandomY":82933693,"RandomZ":2263995128,"StartTime":35739.0,"Objects":[{"StartTime":35739.0,"EndTime":35739.0,"Column":2}]},{"RandomW":2107738941,"RandomX":2263995128,"RandomY":3437211638,"RandomZ":4066526803,"StartTime":36004.0,"Objects":[{"StartTime":36004.0,"EndTime":36004.0,"Column":2},{"StartTime":36004.0,"EndTime":36004.0,"Column":5}]},{"RandomW":1976561763,"RandomX":3437211638,"RandomY":4066526803,"RandomZ":2107738941,"StartTime":36270.0,"Objects":[{"StartTime":36270.0,"EndTime":36270.0,"Column":3},{"StartTime":36270.0,"EndTime":36270.0,"Column":4}]},{"RandomW":1147027763,"RandomX":4066526803,"RandomY":2107738941,"RandomZ":1976561763,"StartTime":36535.0,"Objects":[{"StartTime":36535.0,"EndTime":36535.0,"Column":3}]},{"RandomW":3580315894,"RandomX":1976561763,"RandomY":1147027763,"RandomZ":2767111989,"StartTime":36801.0,"Objects":[{"StartTime":36801.0,"EndTime":37331.0,"Column":4}]},{"RandomW":3743545041,"RandomX":1147027763,"RandomY":2767111989,"RandomZ":3580315894,"StartTime":37597.0,"Objects":[{"StartTime":37597.0,"EndTime":37597.0,"Column":1}]},{"RandomW":1409948107,"RandomX":3743545041,"RandomY":1774216159,"RandomZ":3150304957,"StartTime":37863.0,"Objects":[{"StartTime":37863.0,"EndTime":38393.0,"Column":2},{"StartTime":37863.0,"EndTime":38393.0,"Column":3}]},{"RandomW":4009340712,"RandomX":3150304957,"RandomY":1409948107,"RandomZ":2219703013,"StartTime":38925.0,"Objects":[{"StartTime":38925.0,"EndTime":39190.0,"Column":5}]},{"RandomW":3071167491,"RandomX":2065497204,"RandomY":2145154717,"RandomZ":2494378321,"StartTime":39456.0,"Objects":[{"StartTime":39456.0,"EndTime":39721.0,"Column":0},{"StartTime":39456.0,"EndTime":39721.0,"Column":2}]},{"RandomW":1245938367,"RandomX":3071167491,"RandomY":728627658,"RandomZ":3080260260,"StartTime":39987.0,"Objects":[{"StartTime":39987.0,"EndTime":40783.0,"Column":3}]},{"RandomW":3032241617,"RandomX":1245938367,"RandomY":2414391712,"RandomZ":3406801470,"StartTime":41048.0,"Objects":[{"StartTime":41048.0,"EndTime":41313.0,"Column":2}]},{"RandomW":3367991920,"RandomX":3804000131,"RandomY":672376773,"RandomZ":2667292323,"StartTime":41579.0,"Objects":[{"StartTime":41579.0,"EndTime":41844.0,"Column":1},{"StartTime":41579.0,"EndTime":41844.0,"Column":3}]},{"RandomW":2095476726,"RandomX":2667292323,"RandomY":3367991920,"RandomZ":3380532371,"StartTime":42110.0,"Objects":[{"StartTime":42110.0,"EndTime":42640.0,"Column":5}]},{"RandomW":869340745,"RandomX":2095476726,"RandomY":1063981175,"RandomZ":204767504,"StartTime":43172.0,"Objects":[{"StartTime":43172.0,"EndTime":43172.0,"Column":1},{"StartTime":43172.0,"EndTime":43172.0,"Column":4}]},{"RandomW":461904197,"RandomX":204767504,"RandomY":869340745,"RandomZ":2080855578,"StartTime":43438.0,"Objects":[{"StartTime":43438.0,"EndTime":43438.0,"Column":2},{"StartTime":43438.0,"EndTime":43438.0,"Column":1}]},{"RandomW":3004966693,"RandomX":869340745,"RandomY":2080855578,"RandomZ":461904197,"StartTime":43703.0,"Objects":[{"StartTime":43703.0,"EndTime":43703.0,"Column":3},{"StartTime":43703.0,"EndTime":43703.0,"Column":4}]},{"RandomW":147065937,"RandomX":2080855578,"RandomY":461904197,"RandomZ":3004966693,"StartTime":43969.0,"Objects":[{"StartTime":43969.0,"EndTime":43969.0,"Column":4}]},{"RandomW":1312111829,"RandomX":461904197,"RandomY":3004966693,"RandomZ":147065937,"StartTime":44234.0,"Objects":[{"StartTime":44234.0,"EndTime":44234.0,"Column":4}]},{"RandomW":355223143,"RandomX":3004966693,"RandomY":147065937,"RandomZ":1312111829,"StartTime":44500.0,"Objects":[{"StartTime":44500.0,"EndTime":44500.0,"Column":3}]},{"RandomW":1197174504,"RandomX":147065937,"RandomY":1312111829,"RandomZ":355223143,"StartTime":44765.0,"Objects":[{"StartTime":44765.0,"EndTime":44765.0,"Column":2},{"StartTime":44765.0,"EndTime":44765.0,"Column":3}]},{"RandomW":2296450669,"RandomX":355223143,"RandomY":1197174504,"RandomZ":1876247766,"StartTime":45031.0,"Objects":[{"StartTime":45031.0,"EndTime":45031.0,"Column":1},{"StartTime":45031.0,"EndTime":45031.0,"Column":0}]},{"RandomW":1664705375,"RandomX":1876247766,"RandomY":2296450669,"RandomZ":4287200872,"StartTime":45296.0,"Objects":[{"StartTime":45296.0,"EndTime":45296.0,"Column":0},{"StartTime":45296.0,"EndTime":45296.0,"Column":4}]},{"RandomW":2786027546,"RandomX":2296450669,"RandomY":4287200872,"RandomZ":1664705375,"StartTime":45562.0,"Objects":[{"StartTime":45562.0,"EndTime":45562.0,"Column":1}]},{"RandomW":639469776,"RandomX":4287200872,"RandomY":1664705375,"RandomZ":2786027546,"StartTime":45827.0,"Objects":[{"StartTime":45827.0,"EndTime":45827.0,"Column":3},{"StartTime":45827.0,"EndTime":45827.0,"Column":4}]},{"RandomW":2463352901,"RandomX":1664705375,"RandomY":2786027546,"RandomZ":639469776,"StartTime":46093.0,"Objects":[{"StartTime":46093.0,"EndTime":46093.0,"Column":4}]},{"RandomW":760995091,"RandomX":2463352901,"RandomY":978871003,"RandomZ":3888812594,"StartTime":46358.0,"Objects":[{"StartTime":46358.0,"EndTime":46888.0,"Column":2}]},{"RandomW":3631307076,"RandomX":3888812594,"RandomY":760995091,"RandomZ":566667549,"StartTime":47420.0,"Objects":[{"StartTime":47420.0,"EndTime":47685.0,"Column":4}]},{"RandomW":2353216536,"RandomX":3631307076,"RandomY":1805196154,"RandomZ":2564415583,"StartTime":47951.0,"Objects":[{"StartTime":47951.0,"EndTime":48216.0,"Column":1},{"StartTime":47951.0,"EndTime":48216.0,"Column":0}]},{"RandomW":717730087,"RandomX":2353216536,"RandomY":3735744429,"RandomZ":2102099401,"StartTime":48482.0,"Objects":[{"StartTime":48482.0,"EndTime":49278.0,"Column":5},{"StartTime":48482.0,"EndTime":49278.0,"Column":2}]},{"RandomW":271333990,"RandomX":717730087,"RandomY":3220302747,"RandomZ":917482575,"StartTime":49544.0,"Objects":[{"StartTime":49544.0,"EndTime":49809.0,"Column":0}]},{"RandomW":937976203,"RandomX":917482575,"RandomY":271333990,"RandomZ":125173709,"StartTime":50075.0,"Objects":[{"StartTime":50075.0,"EndTime":50340.0,"Column":2}]},{"RandomW":2781059562,"RandomX":937976203,"RandomY":2087616237,"RandomZ":232817676,"StartTime":50606.0,"Objects":[{"StartTime":50606.0,"EndTime":51667.0,"Column":0},{"StartTime":50606.0,"EndTime":51667.0,"Column":1}]},{"RandomW":3511898336,"RandomX":2087616237,"RandomY":232817676,"RandomZ":2781059562,"StartTime":52730.0,"Objects":[{"StartTime":52730.0,"EndTime":52730.0,"Column":4}]},{"RandomW":623291556,"RandomX":3737503025,"RandomY":3607951873,"RandomZ":1857627587,"StartTime":53792.0,"Objects":[{"StartTime":53792.0,"EndTime":54322.0,"Column":5},{"StartTime":53792.0,"EndTime":54322.0,"Column":1}]},{"RandomW":3577350524,"RandomX":3607951873,"RandomY":1857627587,"RandomZ":623291556,"StartTime":54588.0,"Objects":[{"StartTime":54588.0,"EndTime":54588.0,"Column":2}]},{"RandomW":3611414219,"RandomX":1700150568,"RandomY":3261504380,"RandomZ":3526708248,"StartTime":54854.0,"Objects":[{"StartTime":54854.0,"EndTime":55384.0,"Column":3},{"StartTime":54854.0,"EndTime":55384.0,"Column":4}]},{"RandomW":4116828180,"RandomX":3526708248,"RandomY":3611414219,"RandomZ":53089910,"StartTime":55916.0,"Objects":[{"StartTime":55916.0,"EndTime":56446.0,"Column":5}]},{"RandomW":1419945944,"RandomX":53089910,"RandomY":4116828180,"RandomZ":2370574124,"StartTime":56978.0,"Objects":[{"StartTime":56978.0,"EndTime":57549.0,"Column":3}]},{"RandomW":4235330325,"RandomX":2370574124,"RandomY":1419945944,"RandomZ":124293788,"StartTime":58120.0,"Objects":[{"StartTime":58120.0,"EndTime":58405.0,"Column":5}]},{"RandomW":1354196818,"RandomX":124293788,"RandomY":4235330325,"RandomZ":292200128,"StartTime":58692.0,"Objects":[{"StartTime":58692.0,"EndTime":58973.0,"Column":3}]},{"RandomW":2131632245,"RandomX":292200128,"RandomY":1354196818,"RandomZ":319349674,"StartTime":59325.0,"Objects":[{"StartTime":59325.0,"EndTime":60170.0,"Column":5}]},{"RandomW":987180490,"RandomX":1354196818,"RandomY":319349674,"RandomZ":2131632245,"StartTime":60513.0,"Objects":[{"StartTime":60513.0,"EndTime":60513.0,"Column":3}]},{"RandomW":2247158810,"RandomX":2131632245,"RandomY":987180490,"RandomZ":3518058549,"StartTime":60778.0,"Objects":[{"StartTime":60778.0,"EndTime":61043.0,"Column":0}]},{"RandomW":2347989337,"RandomX":987180490,"RandomY":3518058549,"RandomZ":2247158810,"StartTime":61309.0,"Objects":[{"StartTime":61309.0,"EndTime":61309.0,"Column":3}]},{"RandomW":82954311,"RandomX":1403151684,"RandomY":1362150166,"RandomZ":1092174296,"StartTime":61840.0,"Objects":[{"StartTime":61840.0,"EndTime":62105.0,"Column":0}]},{"RandomW":408605211,"RandomX":82954311,"RandomY":1144587736,"RandomZ":2479248954,"StartTime":62371.0,"Objects":[{"StartTime":62371.0,"EndTime":62901.0,"Column":1}]},{"RandomW":2455999143,"RandomX":1144587736,"RandomY":2479248954,"RandomZ":408605211,"StartTime":63168.0,"Objects":[{"StartTime":63168.0,"EndTime":63168.0,"Column":2}]},{"RandomW":1898608481,"RandomX":2455999143,"RandomY":519590646,"RandomZ":3207504021,"StartTime":63433.0,"Objects":[{"StartTime":63433.0,"EndTime":63963.0,"Column":5}]},{"RandomW":601995191,"RandomX":3207504021,"RandomY":1898608481,"RandomZ":4283573577,"StartTime":64230.0,"Objects":[{"StartTime":64230.0,"EndTime":64230.0,"Column":5},{"StartTime":64230.0,"EndTime":64230.0,"Column":1}]},{"RandomW":3909194070,"RandomX":1898608481,"RandomY":4283573577,"RandomZ":601995191,"StartTime":64495.0,"Objects":[{"StartTime":64495.0,"EndTime":64495.0,"Column":3},{"StartTime":64495.0,"EndTime":64495.0,"Column":4}]},{"RandomW":3417465448,"RandomX":4283573577,"RandomY":601995191,"RandomZ":3909194070,"StartTime":64761.0,"Objects":[{"StartTime":64761.0,"EndTime":64761.0,"Column":4}]},{"RandomW":2779016762,"RandomX":601995191,"RandomY":3909194070,"RandomZ":3417465448,"StartTime":65026.0,"Objects":[{"StartTime":65026.0,"EndTime":65026.0,"Column":4},{"StartTime":65026.0,"EndTime":65026.0,"Column":5}]},{"RandomW":2346068278,"RandomX":3909194070,"RandomY":3417465448,"RandomZ":2779016762,"StartTime":65292.0,"Objects":[{"StartTime":65292.0,"EndTime":65292.0,"Column":3}]},{"RandomW":1857589819,"RandomX":3417465448,"RandomY":2779016762,"RandomZ":2346068278,"StartTime":65557.0,"Objects":[{"StartTime":65557.0,"EndTime":65557.0,"Column":4},{"StartTime":65557.0,"EndTime":65557.0,"Column":5}]},{"RandomW":910236838,"RandomX":2779016762,"RandomY":2346068278,"RandomZ":1857589819,"StartTime":66088.0,"Objects":[{"StartTime":66088.0,"EndTime":66088.0,"Column":2},{"StartTime":66088.0,"EndTime":66088.0,"Column":3}]},{"RandomW":910236838,"RandomX":2779016762,"RandomY":2346068278,"RandomZ":1857589819,"StartTime":66354.0,"Objects":[{"StartTime":66354.0,"EndTime":66354.0,"Column":3},{"StartTime":66354.0,"EndTime":66354.0,"Column":2}]},{"RandomW":2327273799,"RandomX":1857589819,"RandomY":910236838,"RandomZ":2953998826,"StartTime":66619.0,"Objects":[{"StartTime":66619.0,"EndTime":67149.0,"Column":0}]},{"RandomW":540283744,"RandomX":910236838,"RandomY":2953998826,"RandomZ":2327273799,"StartTime":67416.0,"Objects":[{"StartTime":67416.0,"EndTime":67416.0,"Column":0}]},{"RandomW":1024467186,"RandomX":2327273799,"RandomY":540283744,"RandomZ":514760684,"StartTime":67681.0,"Objects":[{"StartTime":67681.0,"EndTime":68211.0,"Column":2}]},{"RandomW":211600206,"RandomX":540283744,"RandomY":514760684,"RandomZ":1024467186,"StartTime":68478.0,"Objects":[{"StartTime":68478.0,"EndTime":68478.0,"Column":2}]},{"RandomW":2360573614,"RandomX":514760684,"RandomY":1024467186,"RandomZ":211600206,"StartTime":68743.0,"Objects":[{"StartTime":68743.0,"EndTime":68743.0,"Column":4},{"StartTime":68743.0,"EndTime":68743.0,"Column":5}]},{"RandomW":3867722027,"RandomX":1024467186,"RandomY":211600206,"RandomZ":2360573614,"StartTime":69009.0,"Objects":[{"StartTime":69009.0,"EndTime":69009.0,"Column":3}]},{"RandomW":1512274616,"RandomX":211600206,"RandomY":2360573614,"RandomZ":3867722027,"StartTime":69274.0,"Objects":[{"StartTime":69274.0,"EndTime":69274.0,"Column":4},{"StartTime":69274.0,"EndTime":69274.0,"Column":5}]},{"RandomW":2957984769,"RandomX":2360573614,"RandomY":3867722027,"RandomZ":1512274616,"StartTime":69540.0,"Objects":[{"StartTime":69540.0,"EndTime":69540.0,"Column":3}]},{"RandomW":2803767976,"RandomX":3867722027,"RandomY":1512274616,"RandomZ":2957984769,"StartTime":69805.0,"Objects":[{"StartTime":69805.0,"EndTime":69805.0,"Column":4},{"StartTime":69805.0,"EndTime":69805.0,"Column":5}]},{"RandomW":1183341084,"RandomX":2957984769,"RandomY":2803767976,"RandomZ":121575161,"StartTime":70336.0,"Objects":[{"StartTime":70336.0,"EndTime":70601.0,"Column":3}]},{"RandomW":3685872119,"RandomX":121575161,"RandomY":1183341084,"RandomZ":2351788416,"StartTime":70867.0,"Objects":[{"StartTime":70867.0,"EndTime":71397.0,"Column":4}]},{"RandomW":617004198,"RandomX":1183341084,"RandomY":2351788416,"RandomZ":3685872119,"StartTime":71663.0,"Objects":[{"StartTime":71663.0,"EndTime":71663.0,"Column":3}]},{"RandomW":2478235967,"RandomX":617004198,"RandomY":546986648,"RandomZ":3353120378,"StartTime":71929.0,"Objects":[{"StartTime":71929.0,"EndTime":72459.0,"Column":0}]},{"RandomW":2189712483,"RandomX":546986648,"RandomY":3353120378,"RandomZ":2478235967,"StartTime":72725.0,"Objects":[{"StartTime":72725.0,"EndTime":72725.0,"Column":2}]},{"RandomW":1882757169,"RandomX":3353120378,"RandomY":2478235967,"RandomZ":2189712483,"StartTime":72991.0,"Objects":[{"StartTime":72991.0,"EndTime":72991.0,"Column":3},{"StartTime":72991.0,"EndTime":72991.0,"Column":4}]},{"RandomW":1404331794,"RandomX":2478235967,"RandomY":2189712483,"RandomZ":1882757169,"StartTime":73256.0,"Objects":[{"StartTime":73256.0,"EndTime":73256.0,"Column":1}]},{"RandomW":1999620930,"RandomX":2189712483,"RandomY":1882757169,"RandomZ":1404331794,"StartTime":73522.0,"Objects":[{"StartTime":73522.0,"EndTime":73522.0,"Column":3},{"StartTime":73522.0,"EndTime":73522.0,"Column":4}]},{"RandomW":3622364800,"RandomX":1882757169,"RandomY":1404331794,"RandomZ":1999620930,"StartTime":73787.0,"Objects":[{"StartTime":73787.0,"EndTime":73787.0,"Column":2}]},{"RandomW":1671763292,"RandomX":1404331794,"RandomY":1999620930,"RandomZ":3622364800,"StartTime":74053.0,"Objects":[{"StartTime":74053.0,"EndTime":74053.0,"Column":3},{"StartTime":74053.0,"EndTime":74053.0,"Column":4}]},{"RandomW":2594561583,"RandomX":3622364800,"RandomY":1671763292,"RandomZ":2480497357,"StartTime":74584.0,"Objects":[{"StartTime":74584.0,"EndTime":74849.0,"Column":1}]},{"RandomW":1101860073,"RandomX":2480497357,"RandomY":2594561583,"RandomZ":183105309,"StartTime":75115.0,"Objects":[{"StartTime":75115.0,"EndTime":75645.0,"Column":3}]},{"RandomW":423280923,"RandomX":2594561583,"RandomY":183105309,"RandomZ":1101860073,"StartTime":75911.0,"Objects":[{"StartTime":75911.0,"EndTime":75911.0,"Column":2}]},{"RandomW":3905841932,"RandomX":1101860073,"RandomY":423280923,"RandomZ":2916757685,"StartTime":76177.0,"Objects":[{"StartTime":76177.0,"EndTime":76707.0,"Column":4}]},{"RandomW":3241015480,"RandomX":423280923,"RandomY":2916757685,"RandomZ":3905841932,"StartTime":76973.0,"Objects":[{"StartTime":76973.0,"EndTime":76973.0,"Column":3}]},{"RandomW":1928531304,"RandomX":3905841932,"RandomY":3241015480,"RandomZ":248564639,"StartTime":77239.0,"Objects":[{"StartTime":77239.0,"EndTime":77504.0,"Column":5}]},{"RandomW":634267655,"RandomX":3925777969,"RandomY":1203262350,"RandomZ":3485263061,"StartTime":77770.0,"Objects":[{"StartTime":77770.0,"EndTime":78035.0,"Column":3},{"StartTime":77770.0,"EndTime":78035.0,"Column":1}]},{"RandomW":953955737,"RandomX":1203262350,"RandomY":3485263061,"RandomZ":634267655,"StartTime":78301.0,"Objects":[{"StartTime":78301.0,"EndTime":78301.0,"Column":3}]},{"RandomW":3179099439,"RandomX":3485263061,"RandomY":634267655,"RandomZ":953955737,"StartTime":78566.0,"Objects":[{"StartTime":78566.0,"EndTime":78566.0,"Column":2},{"StartTime":78566.0,"EndTime":78566.0,"Column":3}]},{"RandomW":2513433625,"RandomX":634267655,"RandomY":953955737,"RandomZ":3179099439,"StartTime":78832.0,"Objects":[{"StartTime":78832.0,"EndTime":78832.0,"Column":3},{"StartTime":78832.0,"EndTime":78832.0,"Column":4}]},{"RandomW":3239409847,"RandomX":953955737,"RandomY":3179099439,"RandomZ":2513433625,"StartTime":79097.0,"Objects":[{"StartTime":79097.0,"EndTime":79097.0,"Column":5},{"StartTime":79097.0,"EndTime":79097.0,"Column":0}]},{"RandomW":1279031172,"RandomX":2513433625,"RandomY":3239409847,"RandomZ":415034865,"StartTime":79363.0,"Objects":[{"StartTime":79363.0,"EndTime":79893.0,"Column":3}]},{"RandomW":2797153574,"RandomX":3239409847,"RandomY":415034865,"RandomZ":1279031172,"StartTime":80159.0,"Objects":[{"StartTime":80159.0,"EndTime":80159.0,"Column":3}]},{"RandomW":858752658,"RandomX":1279031172,"RandomY":2797153574,"RandomZ":3422759302,"StartTime":80424.0,"Objects":[{"StartTime":80424.0,"EndTime":80954.0,"Column":2}]},{"RandomW":2617268004,"RandomX":2797153574,"RandomY":3422759302,"RandomZ":858752658,"StartTime":81221.0,"Objects":[{"StartTime":81221.0,"EndTime":81221.0,"Column":4}]},{"RandomW":4089416095,"RandomX":3422759302,"RandomY":858752658,"RandomZ":2617268004,"StartTime":81486.0,"Objects":[{"StartTime":81486.0,"EndTime":81486.0,"Column":4},{"StartTime":81486.0,"EndTime":81486.0,"Column":5}]},{"RandomW":640008567,"RandomX":858752658,"RandomY":2617268004,"RandomZ":4089416095,"StartTime":81752.0,"Objects":[{"StartTime":81752.0,"EndTime":81752.0,"Column":4}]},{"RandomW":1769064503,"RandomX":2617268004,"RandomY":4089416095,"RandomZ":640008567,"StartTime":82017.0,"Objects":[{"StartTime":82017.0,"EndTime":82017.0,"Column":5},{"StartTime":82017.0,"EndTime":82017.0,"Column":0}]},{"RandomW":4171929422,"RandomX":640008567,"RandomY":1769064503,"RandomZ":4149611338,"StartTime":82283.0,"Objects":[{"StartTime":82283.0,"EndTime":82283.0,"Column":3},{"StartTime":82283.0,"EndTime":82283.0,"Column":5}]},{"RandomW":4035764053,"RandomX":1769064503,"RandomY":4149611338,"RandomZ":4171929422,"StartTime":82548.0,"Objects":[{"StartTime":82548.0,"EndTime":82548.0,"Column":5},{"StartTime":82548.0,"EndTime":82548.0,"Column":0}]},{"RandomW":391872771,"RandomX":4149611338,"RandomY":4171929422,"RandomZ":4035764053,"StartTime":83079.0,"Objects":[{"StartTime":83079.0,"EndTime":83079.0,"Column":3},{"StartTime":83079.0,"EndTime":83079.0,"Column":4}]},{"RandomW":391872771,"RandomX":4149611338,"RandomY":4171929422,"RandomZ":4035764053,"StartTime":83345.0,"Objects":[{"StartTime":83345.0,"EndTime":83345.0,"Column":2},{"StartTime":83345.0,"EndTime":83345.0,"Column":1}]},{"RandomW":4239141202,"RandomX":4035764053,"RandomY":391872771,"RandomZ":1343280377,"StartTime":83610.0,"Objects":[{"StartTime":83610.0,"EndTime":84140.0,"Column":5}]},{"RandomW":2008371177,"RandomX":4239141202,"RandomY":1783379941,"RandomZ":2715086902,"StartTime":84407.0,"Objects":[{"StartTime":84407.0,"EndTime":84407.0,"Column":1},{"StartTime":84407.0,"EndTime":84407.0,"Column":5}]},{"RandomW":980563717,"RandomX":3939376884,"RandomY":3778473815,"RandomZ":3882214919,"StartTime":84672.0,"Objects":[{"StartTime":84672.0,"EndTime":85202.0,"Column":4},{"StartTime":84672.0,"EndTime":85202.0,"Column":2}]},{"RandomW":2698098433,"RandomX":3778473815,"RandomY":3882214919,"RandomZ":980563717,"StartTime":85469.0,"Objects":[{"StartTime":85469.0,"EndTime":85469.0,"Column":1}]},{"RandomW":4140546075,"RandomX":3882214919,"RandomY":980563717,"RandomZ":2698098433,"StartTime":85734.0,"Objects":[{"StartTime":85734.0,"EndTime":85734.0,"Column":3},{"StartTime":85734.0,"EndTime":85734.0,"Column":4}]},{"RandomW":1045835035,"RandomX":980563717,"RandomY":2698098433,"RandomZ":4140546075,"StartTime":86000.0,"Objects":[{"StartTime":86000.0,"EndTime":86000.0,"Column":1}]},{"RandomW":2503475147,"RandomX":2698098433,"RandomY":4140546075,"RandomZ":1045835035,"StartTime":86265.0,"Objects":[{"StartTime":86265.0,"EndTime":86265.0,"Column":1},{"StartTime":86265.0,"EndTime":86265.0,"Column":2}]},{"RandomW":3094559699,"RandomX":4140546075,"RandomY":1045835035,"RandomZ":2503475147,"StartTime":86531.0,"Objects":[{"StartTime":86531.0,"EndTime":86531.0,"Column":3}]},{"RandomW":332613542,"RandomX":1045835035,"RandomY":2503475147,"RandomZ":3094559699,"StartTime":86796.0,"Objects":[{"StartTime":86796.0,"EndTime":86796.0,"Column":2},{"StartTime":86796.0,"EndTime":86796.0,"Column":3}]},{"RandomW":2534271858,"RandomX":332613542,"RandomY":2623704626,"RandomZ":3061969874,"StartTime":87327.0,"Objects":[{"StartTime":87327.0,"EndTime":87592.0,"Column":1}]},{"RandomW":794230988,"RandomX":2534271858,"RandomY":510287938,"RandomZ":2532404899,"StartTime":87858.0,"Objects":[{"StartTime":87858.0,"EndTime":88388.0,"Column":2}]},{"RandomW":3623430191,"RandomX":510287938,"RandomY":2532404899,"RandomZ":794230988,"StartTime":88655.0,"Objects":[{"StartTime":88655.0,"EndTime":88655.0,"Column":2}]},{"RandomW":2269498220,"RandomX":794230988,"RandomY":3623430191,"RandomZ":2598120162,"StartTime":88920.0,"Objects":[{"StartTime":88920.0,"EndTime":89450.0,"Column":0}]},{"RandomW":277080616,"RandomX":3623430191,"RandomY":2598120162,"RandomZ":2269498220,"StartTime":89717.0,"Objects":[{"StartTime":89717.0,"EndTime":89717.0,"Column":2}]},{"RandomW":237305927,"RandomX":2598120162,"RandomY":2269498220,"RandomZ":277080616,"StartTime":89982.0,"Objects":[{"StartTime":89982.0,"EndTime":89982.0,"Column":1},{"StartTime":89982.0,"EndTime":89982.0,"Column":2}]},{"RandomW":3697412902,"RandomX":277080616,"RandomY":237305927,"RandomZ":1976938587,"StartTime":90247.0,"Objects":[{"StartTime":90247.0,"EndTime":90247.0,"Column":1},{"StartTime":90247.0,"EndTime":90247.0,"Column":4}]},{"RandomW":3552536616,"RandomX":237305927,"RandomY":1976938587,"RandomZ":3697412902,"StartTime":90513.0,"Objects":[{"StartTime":90513.0,"EndTime":90513.0,"Column":2},{"StartTime":90513.0,"EndTime":90513.0,"Column":3}]},{"RandomW":758205604,"RandomX":3697412902,"RandomY":3552536616,"RandomZ":4122897696,"StartTime":90778.0,"Objects":[{"StartTime":90778.0,"EndTime":90778.0,"Column":1},{"StartTime":90778.0,"EndTime":90778.0,"Column":2}]},{"RandomW":3787868447,"RandomX":3552536616,"RandomY":4122897696,"RandomZ":758205604,"StartTime":91044.0,"Objects":[{"StartTime":91044.0,"EndTime":91044.0,"Column":2},{"StartTime":91044.0,"EndTime":91044.0,"Column":3}]},{"RandomW":1748107640,"RandomX":3787868447,"RandomY":3373302567,"RandomZ":3485540424,"StartTime":91575.0,"Objects":[{"StartTime":91575.0,"EndTime":91840.0,"Column":4}]},{"RandomW":4130051617,"RandomX":3485540424,"RandomY":1748107640,"RandomZ":3144627152,"StartTime":92106.0,"Objects":[{"StartTime":92106.0,"EndTime":92636.0,"Column":5}]},{"RandomW":808332236,"RandomX":1748107640,"RandomY":3144627152,"RandomZ":4130051617,"StartTime":92902.0,"Objects":[{"StartTime":92902.0,"EndTime":92902.0,"Column":3}]},{"RandomW":182226446,"RandomX":4130051617,"RandomY":808332236,"RandomZ":3371160944,"StartTime":93168.0,"Objects":[{"StartTime":93168.0,"EndTime":93698.0,"Column":0}]},{"RandomW":2699856874,"RandomX":808332236,"RandomY":3371160944,"RandomZ":182226446,"StartTime":93964.0,"Objects":[{"StartTime":93964.0,"EndTime":93964.0,"Column":1}]},{"RandomW":3110990203,"RandomX":2699856874,"RandomY":3789399152,"RandomZ":1462741358,"StartTime":94230.0,"Objects":[{"StartTime":94230.0,"EndTime":94495.0,"Column":4},{"StartTime":94230.0,"EndTime":94495.0,"Column":2}]},{"RandomW":2375429180,"RandomX":2098892391,"RandomY":1911053200,"RandomZ":1537665050,"StartTime":94761.0,"Objects":[{"StartTime":94761.0,"EndTime":95026.0,"Column":5},{"StartTime":94761.0,"EndTime":95026.0,"Column":0}]},{"RandomW":391186846,"RandomX":1537665050,"RandomY":2375429180,"RandomZ":609673823,"StartTime":95292.0,"Objects":[{"StartTime":95292.0,"EndTime":96353.0,"Column":1}]},{"RandomW":2078004566,"RandomX":2375429180,"RandomY":609673823,"RandomZ":391186846,"StartTime":96486.0,"Objects":[{"StartTime":96486.0,"EndTime":98478.0,"Column":5}]},{"RandomW":2078004566,"RandomX":2375429180,"RandomY":609673823,"RandomZ":391186846,"StartTime":113345.0,"Objects":[{"StartTime":113345.0,"EndTime":113345.0,"Column":4}]},{"RandomW":2078004566,"RandomX":2375429180,"RandomY":609673823,"RandomZ":391186846,"StartTime":113876.0,"Objects":[{"StartTime":113876.0,"EndTime":113876.0,"Column":1}]},{"RandomW":2078004566,"RandomX":2375429180,"RandomY":609673823,"RandomZ":391186846,"StartTime":114407.0,"Objects":[{"StartTime":114407.0,"EndTime":114407.0,"Column":4}]},{"RandomW":1192288733,"RandomX":609673823,"RandomY":391186846,"RandomZ":2078004566,"StartTime":114672.0,"Objects":[{"StartTime":114672.0,"EndTime":114672.0,"Column":2},{"StartTime":114672.0,"EndTime":114672.0,"Column":3}]},{"RandomW":3569858426,"RandomX":391186846,"RandomY":2078004566,"RandomZ":1192288733,"StartTime":114938.0,"Objects":[{"StartTime":114938.0,"EndTime":114938.0,"Column":2}]},{"RandomW":1262832005,"RandomX":2078004566,"RandomY":1192288733,"RandomZ":3569858426,"StartTime":115203.0,"Objects":[{"StartTime":115203.0,"EndTime":115203.0,"Column":3},{"StartTime":115203.0,"EndTime":115203.0,"Column":4}]},{"RandomW":4002501854,"RandomX":1192288733,"RandomY":3569858426,"RandomZ":1262832005,"StartTime":115469.0,"Objects":[{"StartTime":115469.0,"EndTime":115469.0,"Column":3},{"StartTime":115469.0,"EndTime":115469.0,"Column":4}]},{"RandomW":776953560,"RandomX":3569858426,"RandomY":1262832005,"RandomZ":4002501854,"StartTime":116000.0,"Objects":[{"StartTime":116000.0,"EndTime":116000.0,"Column":3},{"StartTime":116000.0,"EndTime":116000.0,"Column":4}]},{"RandomW":776953560,"RandomX":3569858426,"RandomY":1262832005,"RandomZ":4002501854,"StartTime":116531.0,"Objects":[{"StartTime":116531.0,"EndTime":116531.0,"Column":2},{"StartTime":116531.0,"EndTime":116531.0,"Column":1}]},{"RandomW":3352969228,"RandomX":1262832005,"RandomY":4002501854,"RandomZ":776953560,"StartTime":117062.0,"Objects":[{"StartTime":117062.0,"EndTime":117062.0,"Column":3},{"StartTime":117062.0,"EndTime":117062.0,"Column":4}]},{"RandomW":2796695571,"RandomX":4002501854,"RandomY":776953560,"RandomZ":3352969228,"StartTime":117327.0,"Objects":[{"StartTime":117327.0,"EndTime":117327.0,"Column":2}]},{"RandomW":3269572543,"RandomX":776953560,"RandomY":3352969228,"RandomZ":2796695571,"StartTime":117593.0,"Objects":[{"StartTime":117593.0,"EndTime":117593.0,"Column":4},{"StartTime":117593.0,"EndTime":117593.0,"Column":5}]},{"RandomW":3269572543,"RandomX":776953560,"RandomY":3352969228,"RandomZ":2796695571,"StartTime":118124.0,"Objects":[{"StartTime":118124.0,"EndTime":118124.0,"Column":1},{"StartTime":118124.0,"EndTime":118124.0,"Column":0}]},{"RandomW":3269572543,"RandomX":776953560,"RandomY":3352969228,"RandomZ":2796695571,"StartTime":118655.0,"Objects":[{"StartTime":118655.0,"EndTime":118655.0,"Column":5},{"StartTime":118655.0,"EndTime":118655.0,"Column":4}]},{"RandomW":2517403813,"RandomX":3352969228,"RandomY":2796695571,"RandomZ":3269572543,"StartTime":118920.0,"Objects":[{"StartTime":118920.0,"EndTime":118920.0,"Column":2},{"StartTime":118920.0,"EndTime":118920.0,"Column":3}]},{"RandomW":2210619464,"RandomX":2796695571,"RandomY":3269572543,"RandomZ":2517403813,"StartTime":119186.0,"Objects":[{"StartTime":119186.0,"EndTime":119186.0,"Column":4}]},{"RandomW":3032935051,"RandomX":3269572543,"RandomY":2517403813,"RandomZ":2210619464,"StartTime":119451.0,"Objects":[{"StartTime":119451.0,"EndTime":119451.0,"Column":5},{"StartTime":119451.0,"EndTime":119451.0,"Column":0}]},{"RandomW":2069229539,"RandomX":2517403813,"RandomY":2210619464,"RandomZ":3032935051,"StartTime":119717.0,"Objects":[{"StartTime":119717.0,"EndTime":119717.0,"Column":4},{"StartTime":119717.0,"EndTime":119717.0,"Column":5}]},{"RandomW":2069229539,"RandomX":2517403813,"RandomY":2210619464,"RandomZ":3032935051,"StartTime":120247.0,"Objects":[{"StartTime":120247.0,"EndTime":120247.0,"Column":1},{"StartTime":120247.0,"EndTime":120247.0,"Column":0}]},{"RandomW":2069229539,"RandomX":2517403813,"RandomY":2210619464,"RandomZ":3032935051,"StartTime":120778.0,"Objects":[{"StartTime":120778.0,"EndTime":120778.0,"Column":5},{"StartTime":120778.0,"EndTime":120778.0,"Column":4}]},{"RandomW":2069229539,"RandomX":2517403813,"RandomY":2210619464,"RandomZ":3032935051,"StartTime":121309.0,"Objects":[{"StartTime":121309.0,"EndTime":121309.0,"Column":1},{"StartTime":121309.0,"EndTime":121309.0,"Column":0}]},{"RandomW":2314078604,"RandomX":2210619464,"RandomY":3032935051,"RandomZ":2069229539,"StartTime":121575.0,"Objects":[{"StartTime":121575.0,"EndTime":121575.0,"Column":3}]},{"RandomW":297269721,"RandomX":3032935051,"RandomY":2069229539,"RandomZ":2314078604,"StartTime":121840.0,"Objects":[{"StartTime":121840.0,"EndTime":121840.0,"Column":2},{"StartTime":121840.0,"EndTime":121840.0,"Column":3}]},{"RandomW":297269721,"RandomX":3032935051,"RandomY":2069229539,"RandomZ":2314078604,"StartTime":122371.0,"Objects":[{"StartTime":122371.0,"EndTime":122371.0,"Column":3},{"StartTime":122371.0,"EndTime":122371.0,"Column":2}]},{"RandomW":297269721,"RandomX":3032935051,"RandomY":2069229539,"RandomZ":2314078604,"StartTime":122902.0,"Objects":[{"StartTime":122902.0,"EndTime":122902.0,"Column":3},{"StartTime":122902.0,"EndTime":122902.0,"Column":2}]},{"RandomW":297269721,"RandomX":3032935051,"RandomY":2069229539,"RandomZ":2314078604,"StartTime":123433.0,"Objects":[{"StartTime":123433.0,"EndTime":123433.0,"Column":3},{"StartTime":123433.0,"EndTime":123433.0,"Column":2}]},{"RandomW":2460408790,"RandomX":2069229539,"RandomY":2314078604,"RandomZ":297269721,"StartTime":123699.0,"Objects":[{"StartTime":123699.0,"EndTime":123699.0,"Column":1}]},{"RandomW":1180177558,"RandomX":2314078604,"RandomY":297269721,"RandomZ":2460408790,"StartTime":123964.0,"Objects":[{"StartTime":123964.0,"EndTime":123964.0,"Column":3},{"StartTime":123964.0,"EndTime":123964.0,"Column":4}]},{"RandomW":1180177558,"RandomX":2314078604,"RandomY":297269721,"RandomZ":2460408790,"StartTime":124495.0,"Objects":[{"StartTime":124495.0,"EndTime":124495.0,"Column":2},{"StartTime":124495.0,"EndTime":124495.0,"Column":1}]},{"RandomW":1180177558,"RandomX":2314078604,"RandomY":297269721,"RandomZ":2460408790,"StartTime":125026.0,"Objects":[{"StartTime":125026.0,"EndTime":125026.0,"Column":4},{"StartTime":125026.0,"EndTime":125026.0,"Column":3}]},{"RandomW":1180177558,"RandomX":2314078604,"RandomY":297269721,"RandomZ":2460408790,"StartTime":125557.0,"Objects":[{"StartTime":125557.0,"EndTime":125557.0,"Column":2},{"StartTime":125557.0,"EndTime":125557.0,"Column":1}]},{"RandomW":3204700088,"RandomX":297269721,"RandomY":2460408790,"RandomZ":1180177558,"StartTime":125823.0,"Objects":[{"StartTime":125823.0,"EndTime":125823.0,"Column":2}]},{"RandomW":299141296,"RandomX":2460408790,"RandomY":1180177558,"RandomZ":3204700088,"StartTime":126088.0,"Objects":[{"StartTime":126088.0,"EndTime":126088.0,"Column":3},{"StartTime":126088.0,"EndTime":126088.0,"Column":4}]},{"RandomW":299141296,"RandomX":2460408790,"RandomY":1180177558,"RandomZ":3204700088,"StartTime":126619.0,"Objects":[{"StartTime":126619.0,"EndTime":126619.0,"Column":2},{"StartTime":126619.0,"EndTime":126619.0,"Column":1}]},{"RandomW":299141296,"RandomX":2460408790,"RandomY":1180177558,"RandomZ":3204700088,"StartTime":127150.0,"Objects":[{"StartTime":127150.0,"EndTime":127150.0,"Column":4},{"StartTime":127150.0,"EndTime":127150.0,"Column":3}]},{"RandomW":3037239607,"RandomX":1180177558,"RandomY":3204700088,"RandomZ":299141296,"StartTime":127416.0,"Objects":[{"StartTime":127416.0,"EndTime":127416.0,"Column":4},{"StartTime":127416.0,"EndTime":127416.0,"Column":5}]},{"RandomW":863164324,"RandomX":3204700088,"RandomY":299141296,"RandomZ":3037239607,"StartTime":127681.0,"Objects":[{"StartTime":127681.0,"EndTime":127681.0,"Column":5}]},{"RandomW":2456647781,"RandomX":299141296,"RandomY":3037239607,"RandomZ":863164324,"StartTime":127947.0,"Objects":[{"StartTime":127947.0,"EndTime":127947.0,"Column":4},{"StartTime":127947.0,"EndTime":127947.0,"Column":5}]},{"RandomW":659157904,"RandomX":3037239607,"RandomY":863164324,"RandomZ":2456647781,"StartTime":128212.0,"Objects":[{"StartTime":128212.0,"EndTime":128212.0,"Column":3},{"StartTime":128212.0,"EndTime":128212.0,"Column":4}]},{"RandomW":659157904,"RandomX":3037239607,"RandomY":863164324,"RandomZ":2456647781,"StartTime":128743.0,"Objects":[{"StartTime":128743.0,"EndTime":128743.0,"Column":2},{"StartTime":128743.0,"EndTime":128743.0,"Column":1}]},{"RandomW":659157904,"RandomX":3037239607,"RandomY":863164324,"RandomZ":2456647781,"StartTime":129274.0,"Objects":[{"StartTime":129274.0,"EndTime":129274.0,"Column":4},{"StartTime":129274.0,"EndTime":129274.0,"Column":3}]},{"RandomW":3598260079,"RandomX":863164324,"RandomY":2456647781,"RandomZ":659157904,"StartTime":129540.0,"Objects":[{"StartTime":129540.0,"EndTime":129540.0,"Column":3},{"StartTime":129540.0,"EndTime":129540.0,"Column":4}]},{"RandomW":1930638835,"RandomX":2456647781,"RandomY":659157904,"RandomZ":3598260079,"StartTime":129805.0,"Objects":[{"StartTime":129805.0,"EndTime":129805.0,"Column":1},{"StartTime":129805.0,"EndTime":129805.0,"Column":2}]},{"RandomW":4230333264,"RandomX":1930638835,"RandomY":2319762852,"RandomZ":3807998479,"StartTime":130071.0,"Objects":[{"StartTime":130071.0,"EndTime":130071.0,"Column":2},{"StartTime":130071.0,"EndTime":130071.0,"Column":3}]},{"RandomW":2482386774,"RandomX":4230333264,"RandomY":376688010,"RandomZ":3132506885,"StartTime":132460.0,"Objects":[{"StartTime":132460.0,"EndTime":132990.0,"Column":0}]},{"RandomW":3381449487,"RandomX":3132506885,"RandomY":2482386774,"RandomZ":1092311355,"StartTime":133522.0,"Objects":[{"StartTime":133522.0,"EndTime":134052.0,"Column":3}]},{"RandomW":3812940964,"RandomX":1092311355,"RandomY":3381449487,"RandomZ":3240759120,"StartTime":134318.0,"Objects":[{"StartTime":134318.0,"EndTime":134848.0,"Column":4}]},{"RandomW":2199106412,"RandomX":2014155638,"RandomY":3619038163,"RandomZ":1182263034,"StartTime":135115.0,"Objects":[{"StartTime":135115.0,"EndTime":135380.0,"Column":3},{"StartTime":135115.0,"EndTime":135380.0,"Column":0}]},{"RandomW":4049541057,"RandomX":1182263034,"RandomY":2199106412,"RandomZ":2542868059,"StartTime":135646.0,"Objects":[{"StartTime":135646.0,"EndTime":136176.0,"Column":5}]},{"RandomW":376448389,"RandomX":2542868059,"RandomY":4049541057,"RandomZ":149323558,"StartTime":136708.0,"Objects":[{"StartTime":136708.0,"EndTime":136973.0,"Column":1}]},{"RandomW":10761513,"RandomX":149323558,"RandomY":376448389,"RandomZ":156027614,"StartTime":137239.0,"Objects":[{"StartTime":137239.0,"EndTime":137504.0,"Column":0}]},{"RandomW":2890609580,"RandomX":156027614,"RandomY":10761513,"RandomZ":998270292,"StartTime":137770.0,"Objects":[{"StartTime":137770.0,"EndTime":138566.0,"Column":2}]},{"RandomW":3792858866,"RandomX":998270292,"RandomY":2890609580,"RandomZ":3275622081,"StartTime":138832.0,"Objects":[{"StartTime":138832.0,"EndTime":139097.0,"Column":4}]},{"RandomW":479756469,"RandomX":3792858866,"RandomY":3665829153,"RandomZ":799245198,"StartTime":139363.0,"Objects":[{"StartTime":139363.0,"EndTime":139628.0,"Column":2},{"StartTime":139363.0,"EndTime":139628.0,"Column":1}]},{"RandomW":1559664190,"RandomX":1837897770,"RandomY":3074386351,"RandomZ":2226336565,"StartTime":139894.0,"Objects":[{"StartTime":139894.0,"EndTime":140690.0,"Column":0},{"StartTime":139894.0,"EndTime":140690.0,"Column":4}]},{"RandomW":1370921154,"RandomX":3074386351,"RandomY":2226336565,"RandomZ":1559664190,"StartTime":140955.0,"Objects":[{"StartTime":140955.0,"EndTime":140955.0,"Column":4}]},{"RandomW":12534613,"RandomX":1559664190,"RandomY":1370921154,"RandomZ":495513930,"StartTime":141221.0,"Objects":[{"StartTime":141221.0,"EndTime":141751.0,"Column":3},{"StartTime":141486.0,"EndTime":141486.0,"Column":1},{"StartTime":141751.0,"EndTime":141751.0,"Column":1}]},{"RandomW":1474110729,"RandomX":12534613,"RandomY":3893387802,"RandomZ":226854738,"StartTime":142017.0,"Objects":[{"StartTime":142017.0,"EndTime":142017.0,"Column":2},{"StartTime":142017.0,"EndTime":142017.0,"Column":3}]},{"RandomW":3883366092,"RandomX":1474110729,"RandomY":2911002956,"RandomZ":3337209428,"StartTime":142283.0,"Objects":[{"StartTime":142283.0,"EndTime":142548.0,"Column":4}]},{"RandomW":1868157439,"RandomX":3883366092,"RandomY":1497166406,"RandomZ":3876220972,"StartTime":142814.0,"Objects":[{"StartTime":142814.0,"EndTime":143079.0,"Column":5}]},{"RandomW":868486094,"RandomX":1497166406,"RandomY":3876220972,"RandomZ":1868157439,"StartTime":143345.0,"Objects":[{"StartTime":143345.0,"EndTime":143345.0,"Column":2}]},{"RandomW":2379505970,"RandomX":3876220972,"RandomY":1868157439,"RandomZ":868486094,"StartTime":143610.0,"Objects":[{"StartTime":143610.0,"EndTime":143610.0,"Column":2}]},{"RandomW":971762612,"RandomX":1868157439,"RandomY":868486094,"RandomZ":2379505970,"StartTime":143876.0,"Objects":[{"StartTime":143876.0,"EndTime":143876.0,"Column":4}]},{"RandomW":2333467129,"RandomX":2379505970,"RandomY":971762612,"RandomZ":2560365407,"StartTime":144141.0,"Objects":[{"StartTime":144141.0,"EndTime":144671.0,"Column":0}]},{"RandomW":3275109659,"RandomX":2560365407,"RandomY":2333467129,"RandomZ":2783370328,"StartTime":145203.0,"Objects":[{"StartTime":145203.0,"EndTime":145468.0,"Column":3}]},{"RandomW":2675369072,"RandomX":2783370328,"RandomY":3275109659,"RandomZ":3142107337,"StartTime":145734.0,"Objects":[{"StartTime":145734.0,"EndTime":145999.0,"Column":1}]},{"RandomW":2114821552,"RandomX":3142107337,"RandomY":2675369072,"RandomZ":216133594,"StartTime":146265.0,"Objects":[{"StartTime":146265.0,"EndTime":146795.0,"Column":5}]},{"RandomW":2210288688,"RandomX":2675369072,"RandomY":216133594,"RandomZ":2114821552,"StartTime":147062.0,"Objects":[{"StartTime":147062.0,"EndTime":147062.0,"Column":3}]},{"RandomW":2824847566,"RandomX":2114821552,"RandomY":2210288688,"RandomZ":2881713491,"StartTime":147327.0,"Objects":[{"StartTime":147327.0,"EndTime":147592.0,"Column":1}]},{"RandomW":3418617049,"RandomX":2881713491,"RandomY":2824847566,"RandomZ":3131910248,"StartTime":147858.0,"Objects":[{"StartTime":147858.0,"EndTime":148123.0,"Column":3}]},{"RandomW":4264037536,"RandomX":3418617049,"RandomY":2065328415,"RandomZ":756387586,"StartTime":148389.0,"Objects":[{"StartTime":148389.0,"EndTime":149450.0,"Column":2},{"StartTime":148389.0,"EndTime":149450.0,"Column":5}]},{"RandomW":714689152,"RandomX":2065328415,"RandomY":756387586,"RandomZ":4264037536,"StartTime":149717.0,"Objects":[{"StartTime":149717.0,"EndTime":149717.0,"Column":2}]},{"RandomW":2187562077,"RandomX":756387586,"RandomY":4264037536,"RandomZ":714689152,"StartTime":149982.0,"Objects":[{"StartTime":149982.0,"EndTime":149982.0,"Column":1},{"StartTime":149982.0,"EndTime":149982.0,"Column":2}]},{"RandomW":59731596,"RandomX":4264037536,"RandomY":714689152,"RandomZ":2187562077,"StartTime":150247.0,"Objects":[{"StartTime":150247.0,"EndTime":150247.0,"Column":0}]},{"RandomW":3179032401,"RandomX":714689152,"RandomY":2187562077,"RandomZ":59731596,"StartTime":150513.0,"Objects":[{"StartTime":150513.0,"EndTime":150513.0,"Column":1}]},{"RandomW":1565638452,"RandomX":2187562077,"RandomY":59731596,"RandomZ":3179032401,"StartTime":150778.0,"Objects":[{"StartTime":150778.0,"EndTime":150778.0,"Column":2}]},{"RandomW":3285111207,"RandomX":59731596,"RandomY":3179032401,"RandomZ":1565638452,"StartTime":151044.0,"Objects":[{"StartTime":151044.0,"EndTime":151044.0,"Column":3},{"StartTime":151044.0,"EndTime":151044.0,"Column":4}]},{"RandomW":3142401116,"RandomX":3179032401,"RandomY":1565638452,"RandomZ":3285111207,"StartTime":151309.0,"Objects":[{"StartTime":151309.0,"EndTime":151309.0,"Column":4}]},{"RandomW":2191101353,"RandomX":3142401116,"RandomY":3877079747,"RandomZ":930029834,"StartTime":151575.0,"Objects":[{"StartTime":151575.0,"EndTime":152105.0,"Column":2},{"StartTime":151575.0,"EndTime":152105.0,"Column":0}]},{"RandomW":1171726387,"RandomX":2191101353,"RandomY":1357180538,"RandomZ":201209655,"StartTime":152637.0,"Objects":[{"StartTime":152637.0,"EndTime":152902.0,"Column":3}]},{"RandomW":2089660876,"RandomX":201209655,"RandomY":1171726387,"RandomZ":191699429,"StartTime":153168.0,"Objects":[{"StartTime":153168.0,"EndTime":153698.0,"Column":5}]},{"RandomW":2251323109,"RandomX":1171726387,"RandomY":191699429,"RandomZ":2089660876,"StartTime":153964.0,"Objects":[{"StartTime":153964.0,"EndTime":153964.0,"Column":2}]},{"RandomW":147408153,"RandomX":2251323109,"RandomY":2048526504,"RandomZ":433820735,"StartTime":154230.0,"Objects":[{"StartTime":154230.0,"EndTime":154230.0,"Column":0},{"StartTime":154230.0,"EndTime":154230.0,"Column":5}]},{"RandomW":223059387,"RandomX":2048526504,"RandomY":433820735,"RandomZ":147408153,"StartTime":154495.0,"Objects":[{"StartTime":154495.0,"EndTime":154495.0,"Column":3}]},{"RandomW":1644267862,"RandomX":147408153,"RandomY":223059387,"RandomZ":2814282738,"StartTime":154761.0,"Objects":[{"StartTime":154761.0,"EndTime":155026.0,"Column":4}]},{"RandomW":585628331,"RandomX":1644267862,"RandomY":547547522,"RandomZ":1901399656,"StartTime":155292.0,"Objects":[{"StartTime":155292.0,"EndTime":155292.0,"Column":0},{"StartTime":155292.0,"EndTime":155292.0,"Column":5}]},{"RandomW":1287818392,"RandomX":547547522,"RandomY":1901399656,"RandomZ":585628331,"StartTime":155557.0,"Objects":[{"StartTime":155557.0,"EndTime":155557.0,"Column":1}]},{"RandomW":3879046214,"RandomX":2065404539,"RandomY":2732913982,"RandomZ":3217781099,"StartTime":155823.0,"Objects":[{"StartTime":155823.0,"EndTime":156088.0,"Column":2},{"StartTime":155823.0,"EndTime":156088.0,"Column":4}]},{"RandomW":3318878889,"RandomX":3217781099,"RandomY":3879046214,"RandomZ":1075466897,"StartTime":156354.0,"Objects":[{"StartTime":156354.0,"EndTime":156619.0,"Column":3}]},{"RandomW":1785367685,"RandomX":1075466897,"RandomY":3318878889,"RandomZ":561406801,"StartTime":156885.0,"Objects":[{"StartTime":156885.0,"EndTime":157415.0,"Column":4}]},{"RandomW":2909067134,"RandomX":561406801,"RandomY":1785367685,"RandomZ":4168537475,"StartTime":157947.0,"Objects":[{"StartTime":157947.0,"EndTime":157947.0,"Column":5},{"StartTime":157947.0,"EndTime":157947.0,"Column":2}]},{"RandomW":1067074920,"RandomX":1785367685,"RandomY":4168537475,"RandomZ":2909067134,"StartTime":158212.0,"Objects":[{"StartTime":158212.0,"EndTime":158212.0,"Column":4}]},{"RandomW":27977914,"RandomX":4168537475,"RandomY":2909067134,"RandomZ":1067074920,"StartTime":158478.0,"Objects":[{"StartTime":158478.0,"EndTime":158478.0,"Column":5},{"StartTime":158478.0,"EndTime":158478.0,"Column":0}]},{"RandomW":1329528769,"RandomX":2909067134,"RandomY":1067074920,"RandomZ":27977914,"StartTime":158743.0,"Objects":[{"StartTime":158743.0,"EndTime":158743.0,"Column":4}]},{"RandomW":3295284863,"RandomX":1067074920,"RandomY":27977914,"RandomZ":1329528769,"StartTime":159009.0,"Objects":[{"StartTime":159009.0,"EndTime":159009.0,"Column":5}]},{"RandomW":691446431,"RandomX":27977914,"RandomY":1329528769,"RandomZ":3295284863,"StartTime":159540.0,"Objects":[{"StartTime":159540.0,"EndTime":159540.0,"Column":3},{"StartTime":159540.0,"EndTime":159540.0,"Column":4}]},{"RandomW":3354872060,"RandomX":3295284863,"RandomY":691446431,"RandomZ":2140106811,"StartTime":159805.0,"Objects":[{"StartTime":159805.0,"EndTime":159805.0,"Column":2},{"StartTime":159805.0,"EndTime":159805.0,"Column":3}]},{"RandomW":1400553355,"RandomX":691446431,"RandomY":2140106811,"RandomZ":3354872060,"StartTime":160071.0,"Objects":[{"StartTime":160071.0,"EndTime":160071.0,"Column":2}]},{"RandomW":1400553355,"RandomX":691446431,"RandomY":2140106811,"RandomZ":3354872060,"StartTime":160601.0,"Objects":[{"StartTime":160601.0,"EndTime":160601.0,"Column":3}]},{"RandomW":3485781281,"RandomX":2140106811,"RandomY":3354872060,"RandomZ":1400553355,"StartTime":160867.0,"Objects":[{"StartTime":160867.0,"EndTime":160867.0,"Column":3}]},{"RandomW":3053679463,"RandomX":1400553355,"RandomY":3485781281,"RandomZ":3419304522,"StartTime":161132.0,"Objects":[{"StartTime":161132.0,"EndTime":161397.0,"Column":2}]},{"RandomW":3645336111,"RandomX":3419304522,"RandomY":3053679463,"RandomZ":805504203,"StartTime":161663.0,"Objects":[{"StartTime":161663.0,"EndTime":162193.0,"Column":4}]},{"RandomW":1638076271,"RandomX":3053679463,"RandomY":805504203,"RandomZ":3645336111,"StartTime":162460.0,"Objects":[{"StartTime":162460.0,"EndTime":162460.0,"Column":3}]},{"RandomW":107981020,"RandomX":1638076271,"RandomY":3432435831,"RandomZ":3835408498,"StartTime":162725.0,"Objects":[{"StartTime":162725.0,"EndTime":162725.0,"Column":0},{"StartTime":162725.0,"EndTime":162725.0,"Column":5}]},{"RandomW":94467567,"RandomX":3835408498,"RandomY":107981020,"RandomZ":2144208649,"StartTime":163256.0,"Objects":[{"StartTime":163256.0,"EndTime":163256.0,"Column":4},{"StartTime":163256.0,"EndTime":163256.0,"Column":0}]},{"RandomW":1015041289,"RandomX":107981020,"RandomY":2144208649,"RandomZ":94467567,"StartTime":163522.0,"Objects":[{"StartTime":163522.0,"EndTime":163522.0,"Column":3}]},{"RandomW":2029876639,"RandomX":1204955917,"RandomY":1210817201,"RandomZ":1177260118,"StartTime":163787.0,"Objects":[{"StartTime":163787.0,"EndTime":164052.0,"Column":5}]},{"RandomW":3125496505,"RandomX":1177260118,"RandomY":2029876639,"RandomZ":2929832910,"StartTime":164318.0,"Objects":[{"StartTime":164318.0,"EndTime":164583.0,"Column":2}]},{"RandomW":2426857185,"RandomX":3125496505,"RandomY":2700661894,"RandomZ":859446411,"StartTime":164849.0,"Objects":[{"StartTime":164849.0,"EndTime":165114.0,"Column":0}]},{"RandomW":4116661924,"RandomX":2426857185,"RandomY":1884842190,"RandomZ":375578279,"StartTime":165380.0,"Objects":[{"StartTime":165380.0,"EndTime":165910.0,"Column":1},{"StartTime":165380.0,"EndTime":165910.0,"Column":5}]},{"RandomW":3787729819,"RandomX":375578279,"RandomY":4116661924,"RandomZ":1382622976,"StartTime":166442.0,"Objects":[{"StartTime":166442.0,"EndTime":166972.0,"Column":4}]},{"RandomW":3780331234,"RandomX":4116661924,"RandomY":1382622976,"RandomZ":3787729819,"StartTime":167239.0,"Objects":[{"StartTime":167239.0,"EndTime":167239.0,"Column":3}]},{"RandomW":891570220,"RandomX":3780331234,"RandomY":3996538378,"RandomZ":4118560235,"StartTime":167504.0,"Objects":[{"StartTime":167504.0,"EndTime":168034.0,"Column":5},{"StartTime":167504.0,"EndTime":168034.0,"Column":2}]},{"RandomW":1312521276,"RandomX":3996538378,"RandomY":4118560235,"RandomZ":891570220,"StartTime":168301.0,"Objects":[{"StartTime":168301.0,"EndTime":168301.0,"Column":0}]},{"RandomW":316798455,"RandomX":4118560235,"RandomY":891570220,"RandomZ":1312521276,"StartTime":168566.0,"Objects":[{"StartTime":168566.0,"EndTime":168566.0,"Column":2},{"StartTime":168566.0,"EndTime":168566.0,"Column":3}]},{"RandomW":107348261,"RandomX":891570220,"RandomY":1312521276,"RandomZ":316798455,"StartTime":168832.0,"Objects":[{"StartTime":168832.0,"EndTime":168832.0,"Column":1}]},{"RandomW":286543085,"RandomX":1312521276,"RandomY":316798455,"RandomZ":107348261,"StartTime":169097.0,"Objects":[{"StartTime":169097.0,"EndTime":169097.0,"Column":1},{"StartTime":169097.0,"EndTime":169097.0,"Column":2}]},{"RandomW":2220558447,"RandomX":316798455,"RandomY":107348261,"RandomZ":286543085,"StartTime":169363.0,"Objects":[{"StartTime":169363.0,"EndTime":169363.0,"Column":2}]},{"RandomW":2567445342,"RandomX":107348261,"RandomY":286543085,"RandomZ":2220558447,"StartTime":169628.0,"Objects":[{"StartTime":169628.0,"EndTime":169628.0,"Column":1},{"StartTime":169628.0,"EndTime":169628.0,"Column":2}]},{"RandomW":2941341299,"RandomX":286543085,"RandomY":2220558447,"RandomZ":2567445342,"StartTime":170159.0,"Objects":[{"StartTime":170159.0,"EndTime":170159.0,"Column":3},{"StartTime":170159.0,"EndTime":170159.0,"Column":4}]},{"RandomW":2941341299,"RandomX":286543085,"RandomY":2220558447,"RandomZ":2567445342,"StartTime":170424.0,"Objects":[{"StartTime":170424.0,"EndTime":170424.0,"Column":2},{"StartTime":170424.0,"EndTime":170424.0,"Column":1}]},{"RandomW":1087727581,"RandomX":2567445342,"RandomY":2941341299,"RandomZ":479267920,"StartTime":170690.0,"Objects":[{"StartTime":170690.0,"EndTime":171220.0,"Column":3}]},{"RandomW":2581485170,"RandomX":2941341299,"RandomY":479267920,"RandomZ":1087727581,"StartTime":171486.0,"Objects":[{"StartTime":171486.0,"EndTime":171486.0,"Column":5}]},{"RandomW":683596203,"RandomX":1087727581,"RandomY":2581485170,"RandomZ":3168383468,"StartTime":171752.0,"Objects":[{"StartTime":171752.0,"EndTime":172282.0,"Column":1}]},{"RandomW":3284056302,"RandomX":2581485170,"RandomY":3168383468,"RandomZ":683596203,"StartTime":172548.0,"Objects":[{"StartTime":172548.0,"EndTime":172548.0,"Column":2}]},{"RandomW":2830633773,"RandomX":3168383468,"RandomY":683596203,"RandomZ":3284056302,"StartTime":172814.0,"Objects":[{"StartTime":172814.0,"EndTime":172814.0,"Column":3},{"StartTime":172814.0,"EndTime":172814.0,"Column":4}]},{"RandomW":3651115271,"RandomX":683596203,"RandomY":3284056302,"RandomZ":2830633773,"StartTime":173079.0,"Objects":[{"StartTime":173079.0,"EndTime":173079.0,"Column":3}]},{"RandomW":120746014,"RandomX":3284056302,"RandomY":2830633773,"RandomZ":3651115271,"StartTime":173345.0,"Objects":[{"StartTime":173345.0,"EndTime":173345.0,"Column":3},{"StartTime":173345.0,"EndTime":173345.0,"Column":4}]},{"RandomW":830325214,"RandomX":2830633773,"RandomY":3651115271,"RandomZ":120746014,"StartTime":173610.0,"Objects":[{"StartTime":173610.0,"EndTime":173610.0,"Column":4}]},{"RandomW":1509180863,"RandomX":3651115271,"RandomY":120746014,"RandomZ":830325214,"StartTime":173876.0,"Objects":[{"StartTime":173876.0,"EndTime":173876.0,"Column":3},{"StartTime":173876.0,"EndTime":173876.0,"Column":4}]},{"RandomW":2233493011,"RandomX":3902833961,"RandomY":923589330,"RandomZ":3425613873,"StartTime":174407.0,"Objects":[{"StartTime":174407.0,"EndTime":174672.0,"Column":2},{"StartTime":174407.0,"EndTime":174672.0,"Column":0}]},{"RandomW":2517643905,"RandomX":1207989122,"RandomY":993303558,"RandomZ":3011821377,"StartTime":174938.0,"Objects":[{"StartTime":174938.0,"EndTime":175468.0,"Column":3},{"StartTime":174938.0,"EndTime":175468.0,"Column":1}]},{"RandomW":3720863650,"RandomX":993303558,"RandomY":3011821377,"RandomZ":2517643905,"StartTime":175734.0,"Objects":[{"StartTime":175734.0,"EndTime":175734.0,"Column":2}]},{"RandomW":3563355415,"RandomX":2517643905,"RandomY":3720863650,"RandomZ":1116519600,"StartTime":176000.0,"Objects":[{"StartTime":176000.0,"EndTime":176530.0,"Column":3}]},{"RandomW":3287800096,"RandomX":3720863650,"RandomY":1116519600,"RandomZ":3563355415,"StartTime":176796.0,"Objects":[{"StartTime":176796.0,"EndTime":176796.0,"Column":3}]},{"RandomW":539898931,"RandomX":1116519600,"RandomY":3563355415,"RandomZ":3287800096,"StartTime":177062.0,"Objects":[{"StartTime":177062.0,"EndTime":177062.0,"Column":2},{"StartTime":177062.0,"EndTime":177062.0,"Column":3}]},{"RandomW":123758010,"RandomX":3563355415,"RandomY":3287800096,"RandomZ":539898931,"StartTime":177327.0,"Objects":[{"StartTime":177327.0,"EndTime":177327.0,"Column":4}]},{"RandomW":4028312708,"RandomX":3287800096,"RandomY":539898931,"RandomZ":123758010,"StartTime":177593.0,"Objects":[{"StartTime":177593.0,"EndTime":177593.0,"Column":2},{"StartTime":177593.0,"EndTime":177593.0,"Column":3}]},{"RandomW":2371409278,"RandomX":539898931,"RandomY":123758010,"RandomZ":4028312708,"StartTime":177858.0,"Objects":[{"StartTime":177858.0,"EndTime":177858.0,"Column":3}]},{"RandomW":3699828554,"RandomX":123758010,"RandomY":4028312708,"RandomZ":2371409278,"StartTime":178124.0,"Objects":[{"StartTime":178124.0,"EndTime":178124.0,"Column":2},{"StartTime":178124.0,"EndTime":178124.0,"Column":3}]},{"RandomW":4053363780,"RandomX":2371409278,"RandomY":3699828554,"RandomZ":3637445845,"StartTime":178655.0,"Objects":[{"StartTime":178655.0,"EndTime":178920.0,"Column":5}]},{"RandomW":1366734997,"RandomX":3637445845,"RandomY":4053363780,"RandomZ":3122766892,"StartTime":179186.0,"Objects":[{"StartTime":179186.0,"EndTime":179716.0,"Column":3}]},{"RandomW":2085192570,"RandomX":1366734997,"RandomY":4047501250,"RandomZ":3422445293,"StartTime":179982.0,"Objects":[{"StartTime":179982.0,"EndTime":179982.0,"Column":3},{"StartTime":179982.0,"EndTime":179982.0,"Column":5}]},{"RandomW":2526042960,"RandomX":3422445293,"RandomY":2085192570,"RandomZ":2552180342,"StartTime":180247.0,"Objects":[{"StartTime":180247.0,"EndTime":180777.0,"Column":1}]},{"RandomW":2946528857,"RandomX":2085192570,"RandomY":2552180342,"RandomZ":2526042960,"StartTime":181044.0,"Objects":[{"StartTime":181044.0,"EndTime":181044.0,"Column":2}]},{"RandomW":4275012500,"RandomX":2526042960,"RandomY":2946528857,"RandomZ":2680316548,"StartTime":181309.0,"Objects":[{"StartTime":181309.0,"EndTime":181574.0,"Column":5}]},{"RandomW":716767862,"RandomX":1177533555,"RandomY":3396673648,"RandomZ":1210370441,"StartTime":181840.0,"Objects":[{"StartTime":181840.0,"EndTime":182105.0,"Column":3},{"StartTime":181840.0,"EndTime":182105.0,"Column":2}]},{"RandomW":1918581647,"RandomX":1210370441,"RandomY":716767862,"RandomZ":290385782,"StartTime":182371.0,"Objects":[{"StartTime":182371.0,"EndTime":182636.0,"Column":5}]},{"RandomW":2554770024,"RandomX":1918581647,"RandomY":475913420,"RandomZ":4262840195,"StartTime":182902.0,"Objects":[{"StartTime":182902.0,"EndTime":183432.0,"Column":1}]},{"RandomW":862610860,"RandomX":475913420,"RandomY":4262840195,"RandomZ":2554770024,"StartTime":183699.0,"Objects":[{"StartTime":183699.0,"EndTime":185557.0,"Column":2}]},{"RandomW":3240322225,"RandomX":4262840195,"RandomY":2554770024,"RandomZ":862610860,"StartTime":202017.0,"Objects":[{"StartTime":202017.0,"EndTime":202017.0,"Column":0}]},{"RandomW":2438630089,"RandomX":2554770024,"RandomY":862610860,"RandomZ":3240322225,"StartTime":202283.0,"Objects":[{"StartTime":202283.0,"EndTime":202283.0,"Column":1}]},{"RandomW":1543895637,"RandomX":3240322225,"RandomY":2438630089,"RandomZ":1008910200,"StartTime":202548.0,"Objects":[{"StartTime":202548.0,"EndTime":203078.0,"Column":4}]},{"RandomW":2262375304,"RandomX":2438630089,"RandomY":1008910200,"RandomZ":1543895637,"StartTime":203345.0,"Objects":[{"StartTime":203345.0,"EndTime":203345.0,"Column":2}]},{"RandomW":3932191533,"RandomX":1543895637,"RandomY":2262375304,"RandomZ":3281044824,"StartTime":203610.0,"Objects":[{"StartTime":203610.0,"EndTime":203875.0,"Column":4}]},{"RandomW":2456816417,"RandomX":3932191533,"RandomY":2579817318,"RandomZ":3616517773,"StartTime":204141.0,"Objects":[{"StartTime":204141.0,"EndTime":204406.0,"Column":0}]},{"RandomW":1863357795,"RandomX":2456816417,"RandomY":2065740625,"RandomZ":3309416576,"StartTime":204672.0,"Objects":[{"StartTime":204672.0,"EndTime":205202.0,"Column":3},{"StartTime":204672.0,"EndTime":205202.0,"Column":5}]},{"RandomW":66010220,"RandomX":3309416576,"RandomY":1863357795,"RandomZ":2100015779,"StartTime":205469.0,"Objects":[{"StartTime":205469.0,"EndTime":205469.0,"Column":4},{"StartTime":205469.0,"EndTime":205469.0,"Column":0}]},{"RandomW":548562611,"RandomX":2100015779,"RandomY":66010220,"RandomZ":3420604705,"StartTime":205734.0,"Objects":[{"StartTime":205734.0,"EndTime":205999.0,"Column":1}]},{"RandomW":2052728473,"RandomX":3420604705,"RandomY":548562611,"RandomZ":2913964,"StartTime":206265.0,"Objects":[{"StartTime":206265.0,"EndTime":206530.0,"Column":5}]},{"RandomW":1944462115,"RandomX":2052728473,"RandomY":2737357746,"RandomZ":270315162,"StartTime":206796.0,"Objects":[{"StartTime":206796.0,"EndTime":206796.0,"Column":2},{"StartTime":206796.0,"EndTime":206796.0,"Column":3}]},{"RandomW":3626216744,"RandomX":2737357746,"RandomY":270315162,"RandomZ":1944462115,"StartTime":207062.0,"Objects":[{"StartTime":207062.0,"EndTime":207062.0,"Column":5}]},{"RandomW":1039388877,"RandomX":270315162,"RandomY":1944462115,"RandomZ":3626216744,"StartTime":207327.0,"Objects":[{"StartTime":207327.0,"EndTime":207327.0,"Column":4}]},{"RandomW":3362701719,"RandomX":1944462115,"RandomY":3626216744,"RandomZ":1039388877,"StartTime":207593.0,"Objects":[{"StartTime":207593.0,"EndTime":207593.0,"Column":3}]},{"RandomW":3968495235,"RandomX":3362701719,"RandomY":2329091202,"RandomZ":1331472925,"StartTime":207858.0,"Objects":[{"StartTime":207858.0,"EndTime":208388.0,"Column":5}]},{"RandomW":1381394684,"RandomX":2329091202,"RandomY":1331472925,"RandomZ":3968495235,"StartTime":208655.0,"Objects":[{"StartTime":208655.0,"EndTime":208655.0,"Column":5}]},{"RandomW":1435798214,"RandomX":1381394684,"RandomY":1081301304,"RandomZ":3939835753,"StartTime":208920.0,"Objects":[{"StartTime":208920.0,"EndTime":209450.0,"Column":4}]},{"RandomW":3026458880,"RandomX":1081301304,"RandomY":3939835753,"RandomZ":1435798214,"StartTime":209717.0,"Objects":[{"StartTime":209717.0,"EndTime":209717.0,"Column":5}]},{"RandomW":3713738018,"RandomX":3026458880,"RandomY":1845767213,"RandomZ":745035987,"StartTime":209982.0,"Objects":[{"StartTime":209982.0,"EndTime":210512.0,"Column":2},{"StartTime":209982.0,"EndTime":210512.0,"Column":4}]},{"RandomW":1231260560,"RandomX":1845767213,"RandomY":745035987,"RandomZ":3713738018,"StartTime":210778.0,"Objects":[{"StartTime":210778.0,"EndTime":210778.0,"Column":4}]},{"RandomW":105489365,"RandomX":745035987,"RandomY":3713738018,"RandomZ":1231260560,"StartTime":211044.0,"Objects":[{"StartTime":211044.0,"EndTime":211044.0,"Column":4}]},{"RandomW":1753861391,"RandomX":3713738018,"RandomY":1231260560,"RandomZ":105489365,"StartTime":211309.0,"Objects":[{"StartTime":211309.0,"EndTime":211309.0,"Column":2}]},{"RandomW":966114829,"RandomX":105489365,"RandomY":1753861391,"RandomZ":1828685577,"StartTime":211575.0,"Objects":[{"StartTime":211575.0,"EndTime":211575.0,"Column":3},{"StartTime":211575.0,"EndTime":211575.0,"Column":2}]},{"RandomW":1431749195,"RandomX":1836275468,"RandomY":1290011463,"RandomZ":1159621643,"StartTime":211840.0,"Objects":[{"StartTime":211840.0,"EndTime":212370.0,"Column":5},{"StartTime":211840.0,"EndTime":212370.0,"Column":4}]},{"RandomW":3472418283,"RandomX":1159621643,"RandomY":1431749195,"RandomZ":2724869338,"StartTime":212637.0,"Objects":[{"StartTime":212637.0,"EndTime":212902.0,"Column":3}]},{"RandomW":1755864208,"RandomX":3472418283,"RandomY":2016458251,"RandomZ":2610391004,"StartTime":213168.0,"Objects":[{"StartTime":213168.0,"EndTime":213698.0,"Column":1},{"StartTime":213168.0,"EndTime":213698.0,"Column":4}]},{"RandomW":1635138515,"RandomX":2016458251,"RandomY":2610391004,"RandomZ":1755864208,"StartTime":213964.0,"Objects":[{"StartTime":213964.0,"EndTime":213964.0,"Column":3}]},{"RandomW":3162662082,"RandomX":1755864208,"RandomY":1635138515,"RandomZ":2617989400,"StartTime":214230.0,"Objects":[{"StartTime":214230.0,"EndTime":214495.0,"Column":2}]},{"RandomW":1184692914,"RandomX":2617989400,"RandomY":3162662082,"RandomZ":2531582750,"StartTime":214761.0,"Objects":[{"StartTime":214761.0,"EndTime":215026.0,"Column":3}]},{"RandomW":798124101,"RandomX":2531582750,"RandomY":1184692914,"RandomZ":2157553888,"StartTime":215292.0,"Objects":[{"StartTime":215292.0,"EndTime":215557.0,"Column":2}]},{"RandomW":1923400471,"RandomX":798124101,"RandomY":2665448122,"RandomZ":1060614841,"StartTime":215823.0,"Objects":[{"StartTime":215823.0,"EndTime":216088.0,"Column":5}]},{"RandomW":775950648,"RandomX":1923400471,"RandomY":3469237574,"RandomZ":2892029047,"StartTime":216354.0,"Objects":[{"StartTime":216354.0,"EndTime":216354.0,"Column":1},{"StartTime":216354.0,"EndTime":216354.0,"Column":4}]},{"RandomW":1321234603,"RandomX":4127626210,"RandomY":1546611249,"RandomZ":1925740893,"StartTime":216885.0,"Objects":[{"StartTime":216885.0,"EndTime":217150.0,"Column":5},{"StartTime":216885.0,"EndTime":217150.0,"Column":3}]},{"RandomW":2881678930,"RandomX":1925740893,"RandomY":1321234603,"RandomZ":2358993682,"StartTime":217416.0,"Objects":[{"StartTime":217416.0,"EndTime":217946.0,"Column":2}]},{"RandomW":2599512294,"RandomX":1321234603,"RandomY":2358993682,"RandomZ":2881678930,"StartTime":218212.0,"Objects":[{"StartTime":218212.0,"EndTime":218212.0,"Column":1}]},{"RandomW":2150464549,"RandomX":2881678930,"RandomY":2599512294,"RandomZ":3623425595,"StartTime":218478.0,"Objects":[{"StartTime":218478.0,"EndTime":219008.0,"Column":0}]},{"RandomW":763775798,"RandomX":3623425595,"RandomY":2150464549,"RandomZ":1008837132,"StartTime":219274.0,"Objects":[{"StartTime":219274.0,"EndTime":221132.0,"Column":2}]},{"RandomW":3656799832,"RandomX":1008837132,"RandomY":763775798,"RandomZ":852609139,"StartTime":221663.0,"Objects":[{"StartTime":221663.0,"EndTime":222193.0,"Column":4}]},{"RandomW":4147545979,"RandomX":852609139,"RandomY":3656799832,"RandomZ":3908484776,"StartTime":222460.0,"Objects":[{"StartTime":222460.0,"EndTime":222460.0,"Column":2},{"StartTime":222460.0,"EndTime":222460.0,"Column":5}]},{"RandomW":540508179,"RandomX":3908484776,"RandomY":4147545979,"RandomZ":1259887550,"StartTime":222725.0,"Objects":[{"StartTime":222725.0,"EndTime":223255.0,"Column":1}]},{"RandomW":1042752714,"RandomX":1259887550,"RandomY":540508179,"RandomZ":2104064323,"StartTime":223522.0,"Objects":[{"StartTime":223522.0,"EndTime":223522.0,"Column":5},{"StartTime":223522.0,"EndTime":223522.0,"Column":2}]},{"RandomW":3077262619,"RandomX":540508179,"RandomY":2104064323,"RandomZ":1042752714,"StartTime":223787.0,"Objects":[{"StartTime":223787.0,"EndTime":223787.0,"Column":3},{"StartTime":223787.0,"EndTime":223787.0,"Column":4}]},{"RandomW":734033149,"RandomX":2104064323,"RandomY":1042752714,"RandomZ":3077262619,"StartTime":224053.0,"Objects":[{"StartTime":224053.0,"EndTime":224053.0,"Column":4}]},{"RandomW":492155815,"RandomX":1042752714,"RandomY":3077262619,"RandomZ":734033149,"StartTime":224318.0,"Objects":[{"StartTime":224318.0,"EndTime":224318.0,"Column":4},{"StartTime":224318.0,"EndTime":224318.0,"Column":5}]},{"RandomW":441697715,"RandomX":3077262619,"RandomY":734033149,"RandomZ":492155815,"StartTime":224584.0,"Objects":[{"StartTime":224584.0,"EndTime":224584.0,"Column":3}]},{"RandomW":4156379255,"RandomX":734033149,"RandomY":492155815,"RandomZ":441697715,"StartTime":224849.0,"Objects":[{"StartTime":224849.0,"EndTime":224849.0,"Column":4},{"StartTime":224849.0,"EndTime":224849.0,"Column":5}]},{"RandomW":3757225441,"RandomX":492155815,"RandomY":441697715,"RandomZ":4156379255,"StartTime":225380.0,"Objects":[{"StartTime":225380.0,"EndTime":225380.0,"Column":2},{"StartTime":225380.0,"EndTime":225380.0,"Column":3}]},{"RandomW":3757225441,"RandomX":492155815,"RandomY":441697715,"RandomZ":4156379255,"StartTime":225646.0,"Objects":[{"StartTime":225646.0,"EndTime":225646.0,"Column":3},{"StartTime":225646.0,"EndTime":225646.0,"Column":2}]},{"RandomW":2225043333,"RandomX":3950035756,"RandomY":4132636893,"RandomZ":3158636107,"StartTime":225911.0,"Objects":[{"StartTime":225911.0,"EndTime":226441.0,"Column":5},{"StartTime":225911.0,"EndTime":226441.0,"Column":0}]},{"RandomW":479006094,"RandomX":2225043333,"RandomY":3919293849,"RandomZ":2279622039,"StartTime":226708.0,"Objects":[{"StartTime":226708.0,"EndTime":226708.0,"Column":0},{"StartTime":226708.0,"EndTime":226708.0,"Column":1}]},{"RandomW":3529234379,"RandomX":479006094,"RandomY":1674670789,"RandomZ":1460857923,"StartTime":226973.0,"Objects":[{"StartTime":226973.0,"EndTime":227503.0,"Column":4},{"StartTime":226973.0,"EndTime":227503.0,"Column":3}]},{"RandomW":2798539123,"RandomX":1674670789,"RandomY":1460857923,"RandomZ":3529234379,"StartTime":227770.0,"Objects":[{"StartTime":227770.0,"EndTime":227770.0,"Column":3}]},{"RandomW":1315002421,"RandomX":1460857923,"RandomY":3529234379,"RandomZ":2798539123,"StartTime":228035.0,"Objects":[{"StartTime":228035.0,"EndTime":228035.0,"Column":2},{"StartTime":228035.0,"EndTime":228035.0,"Column":3}]},{"RandomW":2396116302,"RandomX":3529234379,"RandomY":2798539123,"RandomZ":1315002421,"StartTime":228301.0,"Objects":[{"StartTime":228301.0,"EndTime":228301.0,"Column":1}]},{"RandomW":2184752848,"RandomX":2798539123,"RandomY":1315002421,"RandomZ":2396116302,"StartTime":228566.0,"Objects":[{"StartTime":228566.0,"EndTime":228566.0,"Column":2},{"StartTime":228566.0,"EndTime":228566.0,"Column":3}]},{"RandomW":1453929005,"RandomX":1315002421,"RandomY":2396116302,"RandomZ":2184752848,"StartTime":228832.0,"Objects":[{"StartTime":228832.0,"EndTime":228832.0,"Column":1}]},{"RandomW":307062845,"RandomX":2396116302,"RandomY":2184752848,"RandomZ":1453929005,"StartTime":229097.0,"Objects":[{"StartTime":229097.0,"EndTime":229097.0,"Column":2},{"StartTime":229097.0,"EndTime":229097.0,"Column":3}]},{"RandomW":2488853431,"RandomX":1430246951,"RandomY":1243135735,"RandomZ":862796553,"StartTime":229628.0,"Objects":[{"StartTime":229628.0,"EndTime":229893.0,"Column":0}]},{"RandomW":2954723307,"RandomX":862796553,"RandomY":2488853431,"RandomZ":1065193973,"StartTime":230159.0,"Objects":[{"StartTime":230159.0,"EndTime":230689.0,"Column":2}]},{"RandomW":3118771232,"RandomX":1065193973,"RandomY":2954723307,"RandomZ":3941773202,"StartTime":230955.0,"Objects":[{"StartTime":230955.0,"EndTime":230955.0,"Column":3},{"StartTime":230955.0,"EndTime":230955.0,"Column":2}]},{"RandomW":1630107201,"RandomX":3532926875,"RandomY":2476115689,"RandomZ":1207743047,"StartTime":231221.0,"Objects":[{"StartTime":231221.0,"EndTime":231751.0,"Column":0},{"StartTime":231221.0,"EndTime":231751.0,"Column":4}]},{"RandomW":313681160,"RandomX":2476115689,"RandomY":1207743047,"RandomZ":1630107201,"StartTime":232017.0,"Objects":[{"StartTime":232017.0,"EndTime":232017.0,"Column":2}]},{"RandomW":892602489,"RandomX":1207743047,"RandomY":1630107201,"RandomZ":313681160,"StartTime":232283.0,"Objects":[{"StartTime":232283.0,"EndTime":232283.0,"Column":3},{"StartTime":232283.0,"EndTime":232283.0,"Column":4}]},{"RandomW":2549672466,"RandomX":1630107201,"RandomY":313681160,"RandomZ":892602489,"StartTime":232548.0,"Objects":[{"StartTime":232548.0,"EndTime":232548.0,"Column":1}]},{"RandomW":3175685586,"RandomX":313681160,"RandomY":892602489,"RandomZ":2549672466,"StartTime":232814.0,"Objects":[{"StartTime":232814.0,"EndTime":232814.0,"Column":3},{"StartTime":232814.0,"EndTime":232814.0,"Column":4}]},{"RandomW":1012053334,"RandomX":892602489,"RandomY":2549672466,"RandomZ":3175685586,"StartTime":233079.0,"Objects":[{"StartTime":233079.0,"EndTime":233079.0,"Column":2}]},{"RandomW":2846885221,"RandomX":2549672466,"RandomY":3175685586,"RandomZ":1012053334,"StartTime":233345.0,"Objects":[{"StartTime":233345.0,"EndTime":233345.0,"Column":3},{"StartTime":233345.0,"EndTime":233345.0,"Column":4}]},{"RandomW":2773158813,"RandomX":2846885221,"RandomY":4182295099,"RandomZ":203093837,"StartTime":233876.0,"Objects":[{"StartTime":233876.0,"EndTime":234141.0,"Column":0},{"StartTime":233876.0,"EndTime":234141.0,"Column":1}]},{"RandomW":857734082,"RandomX":203093837,"RandomY":2773158813,"RandomZ":2365172092,"StartTime":234407.0,"Objects":[{"StartTime":234407.0,"EndTime":234937.0,"Column":2}]},{"RandomW":3898917491,"RandomX":2773158813,"RandomY":2365172092,"RandomZ":857734082,"StartTime":235203.0,"Objects":[{"StartTime":235203.0,"EndTime":235203.0,"Column":2}]},{"RandomW":1417532037,"RandomX":857734082,"RandomY":3898917491,"RandomZ":361638657,"StartTime":235469.0,"Objects":[{"StartTime":235469.0,"EndTime":235999.0,"Column":3}]},{"RandomW":2557538851,"RandomX":3898917491,"RandomY":361638657,"RandomZ":1417532037,"StartTime":236265.0,"Objects":[{"StartTime":236265.0,"EndTime":236265.0,"Column":3}]},{"RandomW":846935039,"RandomX":1417532037,"RandomY":2557538851,"RandomZ":1456065540,"StartTime":236531.0,"Objects":[{"StartTime":236531.0,"EndTime":236796.0,"Column":2}]},{"RandomW":2547399683,"RandomX":1456065540,"RandomY":846935039,"RandomZ":2284332751,"StartTime":237062.0,"Objects":[{"StartTime":237062.0,"EndTime":237327.0,"Column":1}]},{"RandomW":2405919505,"RandomX":846935039,"RandomY":2284332751,"RandomZ":2547399683,"StartTime":237593.0,"Objects":[{"StartTime":237593.0,"EndTime":237593.0,"Column":3},{"StartTime":237593.0,"EndTime":237593.0,"Column":4}]},{"RandomW":1684559305,"RandomX":2284332751,"RandomY":2547399683,"RandomZ":2405919505,"StartTime":237858.0,"Objects":[{"StartTime":237858.0,"EndTime":237858.0,"Column":5},{"StartTime":237858.0,"EndTime":237858.0,"Column":0}]},{"RandomW":2914982357,"RandomX":2547399683,"RandomY":2405919505,"RandomZ":1684559305,"StartTime":238124.0,"Objects":[{"StartTime":238124.0,"EndTime":238124.0,"Column":2},{"StartTime":238124.0,"EndTime":238124.0,"Column":3}]},{"RandomW":2343509573,"RandomX":2405919505,"RandomY":1684559305,"RandomZ":2914982357,"StartTime":238389.0,"Objects":[{"StartTime":238389.0,"EndTime":238389.0,"Column":5}]},{"RandomW":1059378114,"RandomX":1684559305,"RandomY":2914982357,"RandomZ":2343509573,"StartTime":238655.0,"Objects":[{"StartTime":238655.0,"EndTime":240778.0,"Column":2}]}]} \ No newline at end of file +{"Mappings":[{"RandomW":273084013,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":15562.0,"Objects":[{"StartTime":15562.0,"EndTime":17155.0,"Column":0}]},{"RandomW":2659258901,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273084013,"StartTime":17686.0,"Objects":[{"StartTime":17686.0,"EndTime":17686.0,"Column":0},{"StartTime":17686.0,"EndTime":17686.0,"Column":1}]},{"RandomW":3083655709,"RandomX":273326509,"RandomY":273084013,"RandomZ":2659258901,"StartTime":17951.0,"Objects":[{"StartTime":17951.0,"EndTime":17951.0,"Column":1}]},{"RandomW":3588026162,"RandomX":2659258901,"RandomY":3083655709,"RandomZ":4073603712,"StartTime":18217.0,"Objects":[{"StartTime":18217.0,"EndTime":18217.0,"Column":2},{"StartTime":18217.0,"EndTime":18217.0,"Column":4}]},{"RandomW":1130061350,"RandomX":3083655709,"RandomY":4073603712,"RandomZ":3588026162,"StartTime":18482.0,"Objects":[{"StartTime":18482.0,"EndTime":18482.0,"Column":2}]},{"RandomW":315421426,"RandomX":3588026162,"RandomY":1130061350,"RandomZ":2459334754,"StartTime":18748.0,"Objects":[{"StartTime":18748.0,"EndTime":19013.0,"Column":0}]},{"RandomW":3110660773,"RandomX":2459334754,"RandomY":315421426,"RandomZ":542845670,"StartTime":19279.0,"Objects":[{"StartTime":19279.0,"EndTime":19809.0,"Column":3},{"StartTime":19544.0,"EndTime":19544.0,"Column":1},{"StartTime":19809.0,"EndTime":19809.0,"Column":1}]},{"RandomW":3110660773,"RandomX":2459334754,"RandomY":315421426,"RandomZ":542845670,"StartTime":20075.0,"Objects":[{"StartTime":20075.0,"EndTime":20075.0,"Column":4},{"StartTime":20075.0,"EndTime":20075.0,"Column":2}]},{"RandomW":2552021122,"RandomX":315421426,"RandomY":542845670,"RandomZ":3110660773,"StartTime":20341.0,"Objects":[{"StartTime":20341.0,"EndTime":20341.0,"Column":3}]},{"RandomW":3979536913,"RandomX":542845670,"RandomY":3110660773,"RandomZ":2552021122,"StartTime":20606.0,"Objects":[{"StartTime":20606.0,"EndTime":20606.0,"Column":2},{"StartTime":20606.0,"EndTime":20606.0,"Column":3}]},{"RandomW":3926138036,"RandomX":2552021122,"RandomY":3979536913,"RandomZ":348643659,"StartTime":20871.0,"Objects":[{"StartTime":20871.0,"EndTime":21401.0,"Column":4}]},{"RandomW":4001028953,"RandomX":348643659,"RandomY":3926138036,"RandomZ":2489502118,"StartTime":21933.0,"Objects":[{"StartTime":21933.0,"EndTime":22198.0,"Column":5}]},{"RandomW":263714783,"RandomX":2489502118,"RandomY":4001028953,"RandomZ":3315380836,"StartTime":22464.0,"Objects":[{"StartTime":22464.0,"EndTime":22729.0,"Column":0}]},{"RandomW":3045229215,"RandomX":3315380836,"RandomY":263714783,"RandomZ":2367299702,"StartTime":22995.0,"Objects":[{"StartTime":22995.0,"EndTime":23791.0,"Column":2}]},{"RandomW":622075324,"RandomX":2367299702,"RandomY":3045229215,"RandomZ":2511145433,"StartTime":24057.0,"Objects":[{"StartTime":24057.0,"EndTime":24322.0,"Column":1}]},{"RandomW":1428674661,"RandomX":3630592823,"RandomY":628640291,"RandomZ":2684635853,"StartTime":24588.0,"Objects":[{"StartTime":24588.0,"EndTime":24853.0,"Column":4},{"StartTime":24588.0,"EndTime":24853.0,"Column":3}]},{"RandomW":2963472042,"RandomX":3191072317,"RandomY":1509788298,"RandomZ":3677221210,"StartTime":25119.0,"Objects":[{"StartTime":25119.0,"EndTime":25649.0,"Column":2}]},{"RandomW":2441208973,"RandomX":1509788298,"RandomY":3677221210,"RandomZ":2963472042,"StartTime":26181.0,"Objects":[{"StartTime":26181.0,"EndTime":26181.0,"Column":2},{"StartTime":26181.0,"EndTime":26181.0,"Column":3}]},{"RandomW":614303213,"RandomX":3677221210,"RandomY":2963472042,"RandomZ":2441208973,"StartTime":26447.0,"Objects":[{"StartTime":26447.0,"EndTime":26447.0,"Column":3}]},{"RandomW":931064848,"RandomX":2441208973,"RandomY":614303213,"RandomZ":2425227013,"StartTime":26712.0,"Objects":[{"StartTime":26712.0,"EndTime":26977.0,"Column":2}]},{"RandomW":1631554006,"RandomX":2425227013,"RandomY":931064848,"RandomZ":2839921662,"StartTime":27243.0,"Objects":[{"StartTime":27243.0,"EndTime":27508.0,"Column":4}]},{"RandomW":1102544522,"RandomX":2839921662,"RandomY":1631554006,"RandomZ":2171149531,"StartTime":27774.0,"Objects":[{"StartTime":27774.0,"EndTime":28039.0,"Column":3}]},{"RandomW":1535528787,"RandomX":2171149531,"RandomY":1102544522,"RandomZ":3328843633,"StartTime":28305.0,"Objects":[{"StartTime":28305.0,"EndTime":28835.0,"Column":4},{"StartTime":28305.0,"EndTime":28305.0,"Column":3},{"StartTime":28570.0,"EndTime":28570.0,"Column":3},{"StartTime":28835.0,"EndTime":28835.0,"Column":3}]},{"RandomW":2462060348,"RandomX":1102544522,"RandomY":3328843633,"RandomZ":1535528787,"StartTime":29102.0,"Objects":[{"StartTime":29102.0,"EndTime":29102.0,"Column":3}]},{"RandomW":2548780898,"RandomX":2462060348,"RandomY":1752789184,"RandomZ":4269701929,"StartTime":29367.0,"Objects":[{"StartTime":29367.0,"EndTime":29897.0,"Column":5},{"StartTime":29367.0,"EndTime":29897.0,"Column":1}]},{"RandomW":2872444045,"RandomX":2548780898,"RandomY":96471884,"RandomZ":2795275332,"StartTime":30429.0,"Objects":[{"StartTime":30429.0,"EndTime":30694.0,"Column":2}]},{"RandomW":554186146,"RandomX":2872444045,"RandomY":1718345430,"RandomZ":1676944188,"StartTime":30960.0,"Objects":[{"StartTime":30960.0,"EndTime":31225.0,"Column":4},{"StartTime":30960.0,"EndTime":31225.0,"Column":1}]},{"RandomW":44350362,"RandomX":1676944188,"RandomY":554186146,"RandomZ":973164386,"StartTime":31491.0,"Objects":[{"StartTime":31491.0,"EndTime":32287.0,"Column":0}]},{"RandomW":2689469863,"RandomX":973164386,"RandomY":44350362,"RandomZ":3230373169,"StartTime":32553.0,"Objects":[{"StartTime":32553.0,"EndTime":32818.0,"Column":1}]},{"RandomW":3076210018,"RandomX":3230373169,"RandomY":2689469863,"RandomZ":2416196755,"StartTime":33084.0,"Objects":[{"StartTime":33084.0,"EndTime":33349.0,"Column":2}]},{"RandomW":4212524875,"RandomX":2416196755,"RandomY":3076210018,"RandomZ":736433317,"StartTime":33615.0,"Objects":[{"StartTime":33615.0,"EndTime":34145.0,"Column":5}]},{"RandomW":668643347,"RandomX":4212524875,"RandomY":1246190622,"RandomZ":614058009,"StartTime":34677.0,"Objects":[{"StartTime":34677.0,"EndTime":34677.0,"Column":0},{"StartTime":34677.0,"EndTime":34677.0,"Column":5}]},{"RandomW":4133034829,"RandomX":668643347,"RandomY":1824376828,"RandomZ":476758489,"StartTime":34942.0,"Objects":[{"StartTime":34942.0,"EndTime":34942.0,"Column":1},{"StartTime":34942.0,"EndTime":34942.0,"Column":5}]},{"RandomW":82933693,"RandomX":1824376828,"RandomY":476758489,"RandomZ":4133034829,"StartTime":35208.0,"Objects":[{"StartTime":35208.0,"EndTime":35208.0,"Column":0},{"StartTime":35208.0,"EndTime":35208.0,"Column":1}]},{"RandomW":2263995128,"RandomX":476758489,"RandomY":4133034829,"RandomZ":82933693,"StartTime":35473.0,"Objects":[{"StartTime":35473.0,"EndTime":35473.0,"Column":1}]},{"RandomW":3437211638,"RandomX":4133034829,"RandomY":82933693,"RandomZ":2263995128,"StartTime":35739.0,"Objects":[{"StartTime":35739.0,"EndTime":35739.0,"Column":2}]},{"RandomW":2107738941,"RandomX":2263995128,"RandomY":3437211638,"RandomZ":4066526803,"StartTime":36004.0,"Objects":[{"StartTime":36004.0,"EndTime":36004.0,"Column":2},{"StartTime":36004.0,"EndTime":36004.0,"Column":5}]},{"RandomW":1976561763,"RandomX":3437211638,"RandomY":4066526803,"RandomZ":2107738941,"StartTime":36270.0,"Objects":[{"StartTime":36270.0,"EndTime":36270.0,"Column":3},{"StartTime":36270.0,"EndTime":36270.0,"Column":4}]},{"RandomW":1147027763,"RandomX":4066526803,"RandomY":2107738941,"RandomZ":1976561763,"StartTime":36535.0,"Objects":[{"StartTime":36535.0,"EndTime":36535.0,"Column":3}]},{"RandomW":3580315894,"RandomX":1976561763,"RandomY":1147027763,"RandomZ":2767111989,"StartTime":36801.0,"Objects":[{"StartTime":36801.0,"EndTime":37331.0,"Column":4}]},{"RandomW":3743545041,"RandomX":1147027763,"RandomY":2767111989,"RandomZ":3580315894,"StartTime":37597.0,"Objects":[{"StartTime":37597.0,"EndTime":37597.0,"Column":1}]},{"RandomW":1409948107,"RandomX":3743545041,"RandomY":1774216159,"RandomZ":3150304957,"StartTime":37863.0,"Objects":[{"StartTime":37863.0,"EndTime":38393.0,"Column":2},{"StartTime":37863.0,"EndTime":38393.0,"Column":3}]},{"RandomW":4009340712,"RandomX":3150304957,"RandomY":1409948107,"RandomZ":2219703013,"StartTime":38925.0,"Objects":[{"StartTime":38925.0,"EndTime":39190.0,"Column":5}]},{"RandomW":3071167491,"RandomX":2065497204,"RandomY":2145154717,"RandomZ":2494378321,"StartTime":39456.0,"Objects":[{"StartTime":39456.0,"EndTime":39721.0,"Column":0},{"StartTime":39456.0,"EndTime":39721.0,"Column":2}]},{"RandomW":1245938367,"RandomX":3071167491,"RandomY":728627658,"RandomZ":3080260260,"StartTime":39987.0,"Objects":[{"StartTime":39987.0,"EndTime":40783.0,"Column":3}]},{"RandomW":3032241617,"RandomX":1245938367,"RandomY":2414391712,"RandomZ":3406801470,"StartTime":41048.0,"Objects":[{"StartTime":41048.0,"EndTime":41313.0,"Column":2}]},{"RandomW":3367991920,"RandomX":3804000131,"RandomY":672376773,"RandomZ":2667292323,"StartTime":41579.0,"Objects":[{"StartTime":41579.0,"EndTime":41844.0,"Column":1},{"StartTime":41579.0,"EndTime":41844.0,"Column":3}]},{"RandomW":2095476726,"RandomX":2667292323,"RandomY":3367991920,"RandomZ":3380532371,"StartTime":42110.0,"Objects":[{"StartTime":42110.0,"EndTime":42640.0,"Column":5}]},{"RandomW":869340745,"RandomX":2095476726,"RandomY":1063981175,"RandomZ":204767504,"StartTime":43172.0,"Objects":[{"StartTime":43172.0,"EndTime":43172.0,"Column":1},{"StartTime":43172.0,"EndTime":43172.0,"Column":4}]},{"RandomW":461904197,"RandomX":204767504,"RandomY":869340745,"RandomZ":2080855578,"StartTime":43438.0,"Objects":[{"StartTime":43438.0,"EndTime":43438.0,"Column":2},{"StartTime":43438.0,"EndTime":43438.0,"Column":1}]},{"RandomW":3004966693,"RandomX":869340745,"RandomY":2080855578,"RandomZ":461904197,"StartTime":43703.0,"Objects":[{"StartTime":43703.0,"EndTime":43703.0,"Column":3},{"StartTime":43703.0,"EndTime":43703.0,"Column":4}]},{"RandomW":147065937,"RandomX":2080855578,"RandomY":461904197,"RandomZ":3004966693,"StartTime":43969.0,"Objects":[{"StartTime":43969.0,"EndTime":43969.0,"Column":4}]},{"RandomW":1312111829,"RandomX":461904197,"RandomY":3004966693,"RandomZ":147065937,"StartTime":44234.0,"Objects":[{"StartTime":44234.0,"EndTime":44234.0,"Column":4}]},{"RandomW":355223143,"RandomX":3004966693,"RandomY":147065937,"RandomZ":1312111829,"StartTime":44500.0,"Objects":[{"StartTime":44500.0,"EndTime":44500.0,"Column":3}]},{"RandomW":1197174504,"RandomX":147065937,"RandomY":1312111829,"RandomZ":355223143,"StartTime":44765.0,"Objects":[{"StartTime":44765.0,"EndTime":44765.0,"Column":2},{"StartTime":44765.0,"EndTime":44765.0,"Column":3}]},{"RandomW":2296450669,"RandomX":355223143,"RandomY":1197174504,"RandomZ":1876247766,"StartTime":45031.0,"Objects":[{"StartTime":45031.0,"EndTime":45031.0,"Column":1},{"StartTime":45031.0,"EndTime":45031.0,"Column":0}]},{"RandomW":1664705375,"RandomX":1876247766,"RandomY":2296450669,"RandomZ":4287200872,"StartTime":45296.0,"Objects":[{"StartTime":45296.0,"EndTime":45296.0,"Column":0},{"StartTime":45296.0,"EndTime":45296.0,"Column":4}]},{"RandomW":2786027546,"RandomX":2296450669,"RandomY":4287200872,"RandomZ":1664705375,"StartTime":45562.0,"Objects":[{"StartTime":45562.0,"EndTime":45562.0,"Column":1}]},{"RandomW":639469776,"RandomX":4287200872,"RandomY":1664705375,"RandomZ":2786027546,"StartTime":45827.0,"Objects":[{"StartTime":45827.0,"EndTime":45827.0,"Column":3},{"StartTime":45827.0,"EndTime":45827.0,"Column":4}]},{"RandomW":2463352901,"RandomX":1664705375,"RandomY":2786027546,"RandomZ":639469776,"StartTime":46093.0,"Objects":[{"StartTime":46093.0,"EndTime":46093.0,"Column":4}]},{"RandomW":760995091,"RandomX":2463352901,"RandomY":978871003,"RandomZ":3888812594,"StartTime":46358.0,"Objects":[{"StartTime":46358.0,"EndTime":46888.0,"Column":2}]},{"RandomW":3631307076,"RandomX":3888812594,"RandomY":760995091,"RandomZ":566667549,"StartTime":47420.0,"Objects":[{"StartTime":47420.0,"EndTime":47685.0,"Column":4}]},{"RandomW":2353216536,"RandomX":3631307076,"RandomY":1805196154,"RandomZ":2564415583,"StartTime":47951.0,"Objects":[{"StartTime":47951.0,"EndTime":48216.0,"Column":1},{"StartTime":47951.0,"EndTime":48216.0,"Column":0}]},{"RandomW":717730087,"RandomX":2353216536,"RandomY":3735744429,"RandomZ":2102099401,"StartTime":48482.0,"Objects":[{"StartTime":48482.0,"EndTime":49278.0,"Column":5},{"StartTime":48482.0,"EndTime":49278.0,"Column":2}]},{"RandomW":271333990,"RandomX":717730087,"RandomY":3220302747,"RandomZ":917482575,"StartTime":49544.0,"Objects":[{"StartTime":49544.0,"EndTime":49809.0,"Column":0}]},{"RandomW":937976203,"RandomX":917482575,"RandomY":271333990,"RandomZ":125173709,"StartTime":50075.0,"Objects":[{"StartTime":50075.0,"EndTime":50340.0,"Column":2}]},{"RandomW":2781059562,"RandomX":937976203,"RandomY":2087616237,"RandomZ":232817676,"StartTime":50606.0,"Objects":[{"StartTime":50606.0,"EndTime":51667.0,"Column":0},{"StartTime":50606.0,"EndTime":51667.0,"Column":1}]},{"RandomW":3511898336,"RandomX":2087616237,"RandomY":232817676,"RandomZ":2781059562,"StartTime":52730.0,"Objects":[{"StartTime":52730.0,"EndTime":52730.0,"Column":4}]},{"RandomW":623291556,"RandomX":3737503025,"RandomY":3607951873,"RandomZ":1857627587,"StartTime":53792.0,"Objects":[{"StartTime":53792.0,"EndTime":54322.0,"Column":5},{"StartTime":53792.0,"EndTime":54322.0,"Column":1}]},{"RandomW":3577350524,"RandomX":3607951873,"RandomY":1857627587,"RandomZ":623291556,"StartTime":54588.0,"Objects":[{"StartTime":54588.0,"EndTime":54588.0,"Column":2}]},{"RandomW":3611414219,"RandomX":1700150568,"RandomY":3261504380,"RandomZ":3526708248,"StartTime":54854.0,"Objects":[{"StartTime":54854.0,"EndTime":55384.0,"Column":3},{"StartTime":54854.0,"EndTime":55384.0,"Column":4}]},{"RandomW":4116828180,"RandomX":3526708248,"RandomY":3611414219,"RandomZ":53089910,"StartTime":55916.0,"Objects":[{"StartTime":55916.0,"EndTime":56446.0,"Column":5}]},{"RandomW":1419945944,"RandomX":53089910,"RandomY":4116828180,"RandomZ":2370574124,"StartTime":56978.0,"Objects":[{"StartTime":56978.0,"EndTime":57549.0,"Column":3}]},{"RandomW":4235330325,"RandomX":2370574124,"RandomY":1419945944,"RandomZ":124293788,"StartTime":58120.0,"Objects":[{"StartTime":58120.0,"EndTime":58405.0,"Column":5}]},{"RandomW":1354196818,"RandomX":124293788,"RandomY":4235330325,"RandomZ":292200128,"StartTime":58692.0,"Objects":[{"StartTime":58692.0,"EndTime":58973.0,"Column":3}]},{"RandomW":2131632245,"RandomX":292200128,"RandomY":1354196818,"RandomZ":319349674,"StartTime":59325.0,"Objects":[{"StartTime":59325.0,"EndTime":60170.0,"Column":5}]},{"RandomW":987180490,"RandomX":1354196818,"RandomY":319349674,"RandomZ":2131632245,"StartTime":60513.0,"Objects":[{"StartTime":60513.0,"EndTime":60513.0,"Column":3}]},{"RandomW":2247158810,"RandomX":2131632245,"RandomY":987180490,"RandomZ":3518058549,"StartTime":60778.0,"Objects":[{"StartTime":60778.0,"EndTime":61043.0,"Column":0}]},{"RandomW":2347989337,"RandomX":987180490,"RandomY":3518058549,"RandomZ":2247158810,"StartTime":61309.0,"Objects":[{"StartTime":61309.0,"EndTime":61309.0,"Column":3}]},{"RandomW":82954311,"RandomX":1403151684,"RandomY":1362150166,"RandomZ":1092174296,"StartTime":61840.0,"Objects":[{"StartTime":61840.0,"EndTime":62105.0,"Column":0}]},{"RandomW":408605211,"RandomX":82954311,"RandomY":1144587736,"RandomZ":2479248954,"StartTime":62371.0,"Objects":[{"StartTime":62371.0,"EndTime":62901.0,"Column":1}]},{"RandomW":2455999143,"RandomX":1144587736,"RandomY":2479248954,"RandomZ":408605211,"StartTime":63168.0,"Objects":[{"StartTime":63168.0,"EndTime":63168.0,"Column":2}]},{"RandomW":1898608481,"RandomX":2455999143,"RandomY":519590646,"RandomZ":3207504021,"StartTime":63433.0,"Objects":[{"StartTime":63433.0,"EndTime":63963.0,"Column":5}]},{"RandomW":601995191,"RandomX":3207504021,"RandomY":1898608481,"RandomZ":4283573577,"StartTime":64230.0,"Objects":[{"StartTime":64230.0,"EndTime":64230.0,"Column":5},{"StartTime":64230.0,"EndTime":64230.0,"Column":1}]},{"RandomW":3909194070,"RandomX":1898608481,"RandomY":4283573577,"RandomZ":601995191,"StartTime":64495.0,"Objects":[{"StartTime":64495.0,"EndTime":64495.0,"Column":3},{"StartTime":64495.0,"EndTime":64495.0,"Column":4}]},{"RandomW":3417465448,"RandomX":4283573577,"RandomY":601995191,"RandomZ":3909194070,"StartTime":64761.0,"Objects":[{"StartTime":64761.0,"EndTime":64761.0,"Column":4}]},{"RandomW":2779016762,"RandomX":601995191,"RandomY":3909194070,"RandomZ":3417465448,"StartTime":65026.0,"Objects":[{"StartTime":65026.0,"EndTime":65026.0,"Column":4},{"StartTime":65026.0,"EndTime":65026.0,"Column":5}]},{"RandomW":2346068278,"RandomX":3909194070,"RandomY":3417465448,"RandomZ":2779016762,"StartTime":65292.0,"Objects":[{"StartTime":65292.0,"EndTime":65292.0,"Column":3}]},{"RandomW":1857589819,"RandomX":3417465448,"RandomY":2779016762,"RandomZ":2346068278,"StartTime":65557.0,"Objects":[{"StartTime":65557.0,"EndTime":65557.0,"Column":4},{"StartTime":65557.0,"EndTime":65557.0,"Column":5}]},{"RandomW":910236838,"RandomX":2779016762,"RandomY":2346068278,"RandomZ":1857589819,"StartTime":66088.0,"Objects":[{"StartTime":66088.0,"EndTime":66088.0,"Column":3},{"StartTime":66088.0,"EndTime":66088.0,"Column":4}]},{"RandomW":910236838,"RandomX":2779016762,"RandomY":2346068278,"RandomZ":1857589819,"StartTime":66354.0,"Objects":[{"StartTime":66354.0,"EndTime":66354.0,"Column":2},{"StartTime":66354.0,"EndTime":66354.0,"Column":1}]},{"RandomW":2327273799,"RandomX":1857589819,"RandomY":910236838,"RandomZ":2953998826,"StartTime":66619.0,"Objects":[{"StartTime":66619.0,"EndTime":67149.0,"Column":0}]},{"RandomW":540283744,"RandomX":910236838,"RandomY":2953998826,"RandomZ":2327273799,"StartTime":67416.0,"Objects":[{"StartTime":67416.0,"EndTime":67416.0,"Column":0}]},{"RandomW":1024467186,"RandomX":2327273799,"RandomY":540283744,"RandomZ":514760684,"StartTime":67681.0,"Objects":[{"StartTime":67681.0,"EndTime":68211.0,"Column":2}]},{"RandomW":211600206,"RandomX":540283744,"RandomY":514760684,"RandomZ":1024467186,"StartTime":68478.0,"Objects":[{"StartTime":68478.0,"EndTime":68478.0,"Column":3}]},{"RandomW":2360573614,"RandomX":514760684,"RandomY":1024467186,"RandomZ":211600206,"StartTime":68743.0,"Objects":[{"StartTime":68743.0,"EndTime":68743.0,"Column":4},{"StartTime":68743.0,"EndTime":68743.0,"Column":5}]},{"RandomW":3867722027,"RandomX":1024467186,"RandomY":211600206,"RandomZ":2360573614,"StartTime":69009.0,"Objects":[{"StartTime":69009.0,"EndTime":69009.0,"Column":3}]},{"RandomW":1512274616,"RandomX":211600206,"RandomY":2360573614,"RandomZ":3867722027,"StartTime":69274.0,"Objects":[{"StartTime":69274.0,"EndTime":69274.0,"Column":4},{"StartTime":69274.0,"EndTime":69274.0,"Column":5}]},{"RandomW":2957984769,"RandomX":2360573614,"RandomY":3867722027,"RandomZ":1512274616,"StartTime":69540.0,"Objects":[{"StartTime":69540.0,"EndTime":69540.0,"Column":3}]},{"RandomW":2803767976,"RandomX":3867722027,"RandomY":1512274616,"RandomZ":2957984769,"StartTime":69805.0,"Objects":[{"StartTime":69805.0,"EndTime":69805.0,"Column":4},{"StartTime":69805.0,"EndTime":69805.0,"Column":5}]},{"RandomW":1183341084,"RandomX":2957984769,"RandomY":2803767976,"RandomZ":121575161,"StartTime":70336.0,"Objects":[{"StartTime":70336.0,"EndTime":70601.0,"Column":3}]},{"RandomW":3685872119,"RandomX":121575161,"RandomY":1183341084,"RandomZ":2351788416,"StartTime":70867.0,"Objects":[{"StartTime":70867.0,"EndTime":71397.0,"Column":4}]},{"RandomW":617004198,"RandomX":1183341084,"RandomY":2351788416,"RandomZ":3685872119,"StartTime":71663.0,"Objects":[{"StartTime":71663.0,"EndTime":71663.0,"Column":3}]},{"RandomW":2478235967,"RandomX":617004198,"RandomY":546986648,"RandomZ":3353120378,"StartTime":71929.0,"Objects":[{"StartTime":71929.0,"EndTime":72459.0,"Column":0}]},{"RandomW":2189712483,"RandomX":546986648,"RandomY":3353120378,"RandomZ":2478235967,"StartTime":72725.0,"Objects":[{"StartTime":72725.0,"EndTime":72725.0,"Column":2}]},{"RandomW":1882757169,"RandomX":3353120378,"RandomY":2478235967,"RandomZ":2189712483,"StartTime":72991.0,"Objects":[{"StartTime":72991.0,"EndTime":72991.0,"Column":3},{"StartTime":72991.0,"EndTime":72991.0,"Column":4}]},{"RandomW":1404331794,"RandomX":2478235967,"RandomY":2189712483,"RandomZ":1882757169,"StartTime":73256.0,"Objects":[{"StartTime":73256.0,"EndTime":73256.0,"Column":1}]},{"RandomW":1999620930,"RandomX":2189712483,"RandomY":1882757169,"RandomZ":1404331794,"StartTime":73522.0,"Objects":[{"StartTime":73522.0,"EndTime":73522.0,"Column":3},{"StartTime":73522.0,"EndTime":73522.0,"Column":4}]},{"RandomW":3622364800,"RandomX":1882757169,"RandomY":1404331794,"RandomZ":1999620930,"StartTime":73787.0,"Objects":[{"StartTime":73787.0,"EndTime":73787.0,"Column":2}]},{"RandomW":1671763292,"RandomX":1404331794,"RandomY":1999620930,"RandomZ":3622364800,"StartTime":74053.0,"Objects":[{"StartTime":74053.0,"EndTime":74053.0,"Column":3},{"StartTime":74053.0,"EndTime":74053.0,"Column":4}]},{"RandomW":2594561583,"RandomX":3622364800,"RandomY":1671763292,"RandomZ":2480497357,"StartTime":74584.0,"Objects":[{"StartTime":74584.0,"EndTime":74849.0,"Column":1}]},{"RandomW":1101860073,"RandomX":2480497357,"RandomY":2594561583,"RandomZ":183105309,"StartTime":75115.0,"Objects":[{"StartTime":75115.0,"EndTime":75645.0,"Column":3}]},{"RandomW":423280923,"RandomX":2594561583,"RandomY":183105309,"RandomZ":1101860073,"StartTime":75911.0,"Objects":[{"StartTime":75911.0,"EndTime":75911.0,"Column":2}]},{"RandomW":3905841932,"RandomX":1101860073,"RandomY":423280923,"RandomZ":2916757685,"StartTime":76177.0,"Objects":[{"StartTime":76177.0,"EndTime":76707.0,"Column":4}]},{"RandomW":3241015480,"RandomX":423280923,"RandomY":2916757685,"RandomZ":3905841932,"StartTime":76973.0,"Objects":[{"StartTime":76973.0,"EndTime":76973.0,"Column":3}]},{"RandomW":1928531304,"RandomX":3905841932,"RandomY":3241015480,"RandomZ":248564639,"StartTime":77239.0,"Objects":[{"StartTime":77239.0,"EndTime":77504.0,"Column":5}]},{"RandomW":634267655,"RandomX":3925777969,"RandomY":1203262350,"RandomZ":3485263061,"StartTime":77770.0,"Objects":[{"StartTime":77770.0,"EndTime":78035.0,"Column":3},{"StartTime":77770.0,"EndTime":78035.0,"Column":1}]},{"RandomW":953955737,"RandomX":1203262350,"RandomY":3485263061,"RandomZ":634267655,"StartTime":78301.0,"Objects":[{"StartTime":78301.0,"EndTime":78301.0,"Column":3}]},{"RandomW":3179099439,"RandomX":3485263061,"RandomY":634267655,"RandomZ":953955737,"StartTime":78566.0,"Objects":[{"StartTime":78566.0,"EndTime":78566.0,"Column":2},{"StartTime":78566.0,"EndTime":78566.0,"Column":3}]},{"RandomW":2513433625,"RandomX":634267655,"RandomY":953955737,"RandomZ":3179099439,"StartTime":78832.0,"Objects":[{"StartTime":78832.0,"EndTime":78832.0,"Column":3},{"StartTime":78832.0,"EndTime":78832.0,"Column":4}]},{"RandomW":3239409847,"RandomX":953955737,"RandomY":3179099439,"RandomZ":2513433625,"StartTime":79097.0,"Objects":[{"StartTime":79097.0,"EndTime":79097.0,"Column":5},{"StartTime":79097.0,"EndTime":79097.0,"Column":0}]},{"RandomW":1279031172,"RandomX":2513433625,"RandomY":3239409847,"RandomZ":415034865,"StartTime":79363.0,"Objects":[{"StartTime":79363.0,"EndTime":79893.0,"Column":3}]},{"RandomW":2797153574,"RandomX":3239409847,"RandomY":415034865,"RandomZ":1279031172,"StartTime":80159.0,"Objects":[{"StartTime":80159.0,"EndTime":80159.0,"Column":3}]},{"RandomW":858752658,"RandomX":1279031172,"RandomY":2797153574,"RandomZ":3422759302,"StartTime":80424.0,"Objects":[{"StartTime":80424.0,"EndTime":80954.0,"Column":2}]},{"RandomW":2617268004,"RandomX":2797153574,"RandomY":3422759302,"RandomZ":858752658,"StartTime":81221.0,"Objects":[{"StartTime":81221.0,"EndTime":81221.0,"Column":4}]},{"RandomW":4089416095,"RandomX":3422759302,"RandomY":858752658,"RandomZ":2617268004,"StartTime":81486.0,"Objects":[{"StartTime":81486.0,"EndTime":81486.0,"Column":4},{"StartTime":81486.0,"EndTime":81486.0,"Column":5}]},{"RandomW":640008567,"RandomX":858752658,"RandomY":2617268004,"RandomZ":4089416095,"StartTime":81752.0,"Objects":[{"StartTime":81752.0,"EndTime":81752.0,"Column":4}]},{"RandomW":1769064503,"RandomX":2617268004,"RandomY":4089416095,"RandomZ":640008567,"StartTime":82017.0,"Objects":[{"StartTime":82017.0,"EndTime":82017.0,"Column":5},{"StartTime":82017.0,"EndTime":82017.0,"Column":0}]},{"RandomW":4171929422,"RandomX":640008567,"RandomY":1769064503,"RandomZ":4149611338,"StartTime":82283.0,"Objects":[{"StartTime":82283.0,"EndTime":82283.0,"Column":3},{"StartTime":82283.0,"EndTime":82283.0,"Column":5}]},{"RandomW":4035764053,"RandomX":1769064503,"RandomY":4149611338,"RandomZ":4171929422,"StartTime":82548.0,"Objects":[{"StartTime":82548.0,"EndTime":82548.0,"Column":5},{"StartTime":82548.0,"EndTime":82548.0,"Column":0}]},{"RandomW":391872771,"RandomX":4149611338,"RandomY":4171929422,"RandomZ":4035764053,"StartTime":83079.0,"Objects":[{"StartTime":83079.0,"EndTime":83079.0,"Column":3},{"StartTime":83079.0,"EndTime":83079.0,"Column":4}]},{"RandomW":391872771,"RandomX":4149611338,"RandomY":4171929422,"RandomZ":4035764053,"StartTime":83345.0,"Objects":[{"StartTime":83345.0,"EndTime":83345.0,"Column":2},{"StartTime":83345.0,"EndTime":83345.0,"Column":1}]},{"RandomW":4239141202,"RandomX":4035764053,"RandomY":391872771,"RandomZ":1343280377,"StartTime":83610.0,"Objects":[{"StartTime":83610.0,"EndTime":84140.0,"Column":5}]},{"RandomW":2008371177,"RandomX":4239141202,"RandomY":1783379941,"RandomZ":2715086902,"StartTime":84407.0,"Objects":[{"StartTime":84407.0,"EndTime":84407.0,"Column":1},{"StartTime":84407.0,"EndTime":84407.0,"Column":5}]},{"RandomW":980563717,"RandomX":3939376884,"RandomY":3778473815,"RandomZ":3882214919,"StartTime":84672.0,"Objects":[{"StartTime":84672.0,"EndTime":85202.0,"Column":4},{"StartTime":84672.0,"EndTime":85202.0,"Column":2}]},{"RandomW":2698098433,"RandomX":3778473815,"RandomY":3882214919,"RandomZ":980563717,"StartTime":85469.0,"Objects":[{"StartTime":85469.0,"EndTime":85469.0,"Column":1}]},{"RandomW":4140546075,"RandomX":3882214919,"RandomY":980563717,"RandomZ":2698098433,"StartTime":85734.0,"Objects":[{"StartTime":85734.0,"EndTime":85734.0,"Column":3},{"StartTime":85734.0,"EndTime":85734.0,"Column":4}]},{"RandomW":1045835035,"RandomX":980563717,"RandomY":2698098433,"RandomZ":4140546075,"StartTime":86000.0,"Objects":[{"StartTime":86000.0,"EndTime":86000.0,"Column":1}]},{"RandomW":2503475147,"RandomX":2698098433,"RandomY":4140546075,"RandomZ":1045835035,"StartTime":86265.0,"Objects":[{"StartTime":86265.0,"EndTime":86265.0,"Column":1},{"StartTime":86265.0,"EndTime":86265.0,"Column":2}]},{"RandomW":3094559699,"RandomX":4140546075,"RandomY":1045835035,"RandomZ":2503475147,"StartTime":86531.0,"Objects":[{"StartTime":86531.0,"EndTime":86531.0,"Column":3}]},{"RandomW":332613542,"RandomX":1045835035,"RandomY":2503475147,"RandomZ":3094559699,"StartTime":86796.0,"Objects":[{"StartTime":86796.0,"EndTime":86796.0,"Column":2},{"StartTime":86796.0,"EndTime":86796.0,"Column":3}]},{"RandomW":2534271858,"RandomX":332613542,"RandomY":2623704626,"RandomZ":3061969874,"StartTime":87327.0,"Objects":[{"StartTime":87327.0,"EndTime":87592.0,"Column":1}]},{"RandomW":794230988,"RandomX":2534271858,"RandomY":510287938,"RandomZ":2532404899,"StartTime":87858.0,"Objects":[{"StartTime":87858.0,"EndTime":88388.0,"Column":2}]},{"RandomW":3623430191,"RandomX":510287938,"RandomY":2532404899,"RandomZ":794230988,"StartTime":88655.0,"Objects":[{"StartTime":88655.0,"EndTime":88655.0,"Column":2}]},{"RandomW":2269498220,"RandomX":794230988,"RandomY":3623430191,"RandomZ":2598120162,"StartTime":88920.0,"Objects":[{"StartTime":88920.0,"EndTime":89450.0,"Column":0}]},{"RandomW":277080616,"RandomX":3623430191,"RandomY":2598120162,"RandomZ":2269498220,"StartTime":89717.0,"Objects":[{"StartTime":89717.0,"EndTime":89717.0,"Column":2}]},{"RandomW":237305927,"RandomX":2598120162,"RandomY":2269498220,"RandomZ":277080616,"StartTime":89982.0,"Objects":[{"StartTime":89982.0,"EndTime":89982.0,"Column":1},{"StartTime":89982.0,"EndTime":89982.0,"Column":2}]},{"RandomW":3697412902,"RandomX":277080616,"RandomY":237305927,"RandomZ":1976938587,"StartTime":90247.0,"Objects":[{"StartTime":90247.0,"EndTime":90247.0,"Column":1},{"StartTime":90247.0,"EndTime":90247.0,"Column":4}]},{"RandomW":3552536616,"RandomX":237305927,"RandomY":1976938587,"RandomZ":3697412902,"StartTime":90513.0,"Objects":[{"StartTime":90513.0,"EndTime":90513.0,"Column":2},{"StartTime":90513.0,"EndTime":90513.0,"Column":3}]},{"RandomW":758205604,"RandomX":3697412902,"RandomY":3552536616,"RandomZ":4122897696,"StartTime":90778.0,"Objects":[{"StartTime":90778.0,"EndTime":90778.0,"Column":1},{"StartTime":90778.0,"EndTime":90778.0,"Column":2}]},{"RandomW":3787868447,"RandomX":3552536616,"RandomY":4122897696,"RandomZ":758205604,"StartTime":91044.0,"Objects":[{"StartTime":91044.0,"EndTime":91044.0,"Column":2},{"StartTime":91044.0,"EndTime":91044.0,"Column":3}]},{"RandomW":1748107640,"RandomX":3787868447,"RandomY":3373302567,"RandomZ":3485540424,"StartTime":91575.0,"Objects":[{"StartTime":91575.0,"EndTime":91840.0,"Column":4}]},{"RandomW":4130051617,"RandomX":3485540424,"RandomY":1748107640,"RandomZ":3144627152,"StartTime":92106.0,"Objects":[{"StartTime":92106.0,"EndTime":92636.0,"Column":5}]},{"RandomW":808332236,"RandomX":1748107640,"RandomY":3144627152,"RandomZ":4130051617,"StartTime":92902.0,"Objects":[{"StartTime":92902.0,"EndTime":92902.0,"Column":3}]},{"RandomW":182226446,"RandomX":4130051617,"RandomY":808332236,"RandomZ":3371160944,"StartTime":93168.0,"Objects":[{"StartTime":93168.0,"EndTime":93698.0,"Column":0}]},{"RandomW":2699856874,"RandomX":808332236,"RandomY":3371160944,"RandomZ":182226446,"StartTime":93964.0,"Objects":[{"StartTime":93964.0,"EndTime":93964.0,"Column":1}]},{"RandomW":3110990203,"RandomX":2699856874,"RandomY":3789399152,"RandomZ":1462741358,"StartTime":94230.0,"Objects":[{"StartTime":94230.0,"EndTime":94495.0,"Column":4},{"StartTime":94230.0,"EndTime":94495.0,"Column":2}]},{"RandomW":2375429180,"RandomX":2098892391,"RandomY":1911053200,"RandomZ":1537665050,"StartTime":94761.0,"Objects":[{"StartTime":94761.0,"EndTime":95026.0,"Column":5},{"StartTime":94761.0,"EndTime":95026.0,"Column":0}]},{"RandomW":391186846,"RandomX":1537665050,"RandomY":2375429180,"RandomZ":609673823,"StartTime":95292.0,"Objects":[{"StartTime":95292.0,"EndTime":96353.0,"Column":1}]},{"RandomW":2078004566,"RandomX":2375429180,"RandomY":609673823,"RandomZ":391186846,"StartTime":96486.0,"Objects":[{"StartTime":96486.0,"EndTime":98478.0,"Column":5}]},{"RandomW":2078004566,"RandomX":2375429180,"RandomY":609673823,"RandomZ":391186846,"StartTime":113345.0,"Objects":[{"StartTime":113345.0,"EndTime":113345.0,"Column":4}]},{"RandomW":2078004566,"RandomX":2375429180,"RandomY":609673823,"RandomZ":391186846,"StartTime":113876.0,"Objects":[{"StartTime":113876.0,"EndTime":113876.0,"Column":1}]},{"RandomW":2078004566,"RandomX":2375429180,"RandomY":609673823,"RandomZ":391186846,"StartTime":114407.0,"Objects":[{"StartTime":114407.0,"EndTime":114407.0,"Column":4}]},{"RandomW":1192288733,"RandomX":609673823,"RandomY":391186846,"RandomZ":2078004566,"StartTime":114672.0,"Objects":[{"StartTime":114672.0,"EndTime":114672.0,"Column":2},{"StartTime":114672.0,"EndTime":114672.0,"Column":3}]},{"RandomW":3569858426,"RandomX":391186846,"RandomY":2078004566,"RandomZ":1192288733,"StartTime":114938.0,"Objects":[{"StartTime":114938.0,"EndTime":114938.0,"Column":2}]},{"RandomW":1262832005,"RandomX":2078004566,"RandomY":1192288733,"RandomZ":3569858426,"StartTime":115203.0,"Objects":[{"StartTime":115203.0,"EndTime":115203.0,"Column":3},{"StartTime":115203.0,"EndTime":115203.0,"Column":4}]},{"RandomW":4002501854,"RandomX":1192288733,"RandomY":3569858426,"RandomZ":1262832005,"StartTime":115469.0,"Objects":[{"StartTime":115469.0,"EndTime":115469.0,"Column":3},{"StartTime":115469.0,"EndTime":115469.0,"Column":4}]},{"RandomW":776953560,"RandomX":3569858426,"RandomY":1262832005,"RandomZ":4002501854,"StartTime":116000.0,"Objects":[{"StartTime":116000.0,"EndTime":116000.0,"Column":3},{"StartTime":116000.0,"EndTime":116000.0,"Column":4}]},{"RandomW":776953560,"RandomX":3569858426,"RandomY":1262832005,"RandomZ":4002501854,"StartTime":116531.0,"Objects":[{"StartTime":116531.0,"EndTime":116531.0,"Column":2},{"StartTime":116531.0,"EndTime":116531.0,"Column":1}]},{"RandomW":3352969228,"RandomX":1262832005,"RandomY":4002501854,"RandomZ":776953560,"StartTime":117062.0,"Objects":[{"StartTime":117062.0,"EndTime":117062.0,"Column":3},{"StartTime":117062.0,"EndTime":117062.0,"Column":4}]},{"RandomW":2796695571,"RandomX":4002501854,"RandomY":776953560,"RandomZ":3352969228,"StartTime":117327.0,"Objects":[{"StartTime":117327.0,"EndTime":117327.0,"Column":2}]},{"RandomW":3269572543,"RandomX":776953560,"RandomY":3352969228,"RandomZ":2796695571,"StartTime":117593.0,"Objects":[{"StartTime":117593.0,"EndTime":117593.0,"Column":4},{"StartTime":117593.0,"EndTime":117593.0,"Column":5}]},{"RandomW":3269572543,"RandomX":776953560,"RandomY":3352969228,"RandomZ":2796695571,"StartTime":118124.0,"Objects":[{"StartTime":118124.0,"EndTime":118124.0,"Column":1},{"StartTime":118124.0,"EndTime":118124.0,"Column":0}]},{"RandomW":3269572543,"RandomX":776953560,"RandomY":3352969228,"RandomZ":2796695571,"StartTime":118655.0,"Objects":[{"StartTime":118655.0,"EndTime":118655.0,"Column":5},{"StartTime":118655.0,"EndTime":118655.0,"Column":4}]},{"RandomW":2517403813,"RandomX":3352969228,"RandomY":2796695571,"RandomZ":3269572543,"StartTime":118920.0,"Objects":[{"StartTime":118920.0,"EndTime":118920.0,"Column":2},{"StartTime":118920.0,"EndTime":118920.0,"Column":3}]},{"RandomW":2210619464,"RandomX":2796695571,"RandomY":3269572543,"RandomZ":2517403813,"StartTime":119186.0,"Objects":[{"StartTime":119186.0,"EndTime":119186.0,"Column":4}]},{"RandomW":3032935051,"RandomX":3269572543,"RandomY":2517403813,"RandomZ":2210619464,"StartTime":119451.0,"Objects":[{"StartTime":119451.0,"EndTime":119451.0,"Column":5},{"StartTime":119451.0,"EndTime":119451.0,"Column":0}]},{"RandomW":2069229539,"RandomX":2517403813,"RandomY":2210619464,"RandomZ":3032935051,"StartTime":119717.0,"Objects":[{"StartTime":119717.0,"EndTime":119717.0,"Column":4},{"StartTime":119717.0,"EndTime":119717.0,"Column":5}]},{"RandomW":2069229539,"RandomX":2517403813,"RandomY":2210619464,"RandomZ":3032935051,"StartTime":120247.0,"Objects":[{"StartTime":120247.0,"EndTime":120247.0,"Column":1},{"StartTime":120247.0,"EndTime":120247.0,"Column":0}]},{"RandomW":2069229539,"RandomX":2517403813,"RandomY":2210619464,"RandomZ":3032935051,"StartTime":120778.0,"Objects":[{"StartTime":120778.0,"EndTime":120778.0,"Column":5},{"StartTime":120778.0,"EndTime":120778.0,"Column":4}]},{"RandomW":2069229539,"RandomX":2517403813,"RandomY":2210619464,"RandomZ":3032935051,"StartTime":121309.0,"Objects":[{"StartTime":121309.0,"EndTime":121309.0,"Column":1},{"StartTime":121309.0,"EndTime":121309.0,"Column":0}]},{"RandomW":2314078604,"RandomX":2210619464,"RandomY":3032935051,"RandomZ":2069229539,"StartTime":121575.0,"Objects":[{"StartTime":121575.0,"EndTime":121575.0,"Column":3}]},{"RandomW":297269721,"RandomX":3032935051,"RandomY":2069229539,"RandomZ":2314078604,"StartTime":121840.0,"Objects":[{"StartTime":121840.0,"EndTime":121840.0,"Column":3},{"StartTime":121840.0,"EndTime":121840.0,"Column":4}]},{"RandomW":297269721,"RandomX":3032935051,"RandomY":2069229539,"RandomZ":2314078604,"StartTime":122371.0,"Objects":[{"StartTime":122371.0,"EndTime":122371.0,"Column":2},{"StartTime":122371.0,"EndTime":122371.0,"Column":1}]},{"RandomW":297269721,"RandomX":3032935051,"RandomY":2069229539,"RandomZ":2314078604,"StartTime":122902.0,"Objects":[{"StartTime":122902.0,"EndTime":122902.0,"Column":4},{"StartTime":122902.0,"EndTime":122902.0,"Column":3}]},{"RandomW":297269721,"RandomX":3032935051,"RandomY":2069229539,"RandomZ":2314078604,"StartTime":123433.0,"Objects":[{"StartTime":123433.0,"EndTime":123433.0,"Column":2},{"StartTime":123433.0,"EndTime":123433.0,"Column":1}]},{"RandomW":2460408790,"RandomX":2069229539,"RandomY":2314078604,"RandomZ":297269721,"StartTime":123699.0,"Objects":[{"StartTime":123699.0,"EndTime":123699.0,"Column":1}]},{"RandomW":1180177558,"RandomX":2314078604,"RandomY":297269721,"RandomZ":2460408790,"StartTime":123964.0,"Objects":[{"StartTime":123964.0,"EndTime":123964.0,"Column":3},{"StartTime":123964.0,"EndTime":123964.0,"Column":4}]},{"RandomW":1180177558,"RandomX":2314078604,"RandomY":297269721,"RandomZ":2460408790,"StartTime":124495.0,"Objects":[{"StartTime":124495.0,"EndTime":124495.0,"Column":2},{"StartTime":124495.0,"EndTime":124495.0,"Column":1}]},{"RandomW":1180177558,"RandomX":2314078604,"RandomY":297269721,"RandomZ":2460408790,"StartTime":125026.0,"Objects":[{"StartTime":125026.0,"EndTime":125026.0,"Column":4},{"StartTime":125026.0,"EndTime":125026.0,"Column":3}]},{"RandomW":1180177558,"RandomX":2314078604,"RandomY":297269721,"RandomZ":2460408790,"StartTime":125557.0,"Objects":[{"StartTime":125557.0,"EndTime":125557.0,"Column":2},{"StartTime":125557.0,"EndTime":125557.0,"Column":1}]},{"RandomW":3204700088,"RandomX":297269721,"RandomY":2460408790,"RandomZ":1180177558,"StartTime":125823.0,"Objects":[{"StartTime":125823.0,"EndTime":125823.0,"Column":2}]},{"RandomW":299141296,"RandomX":2460408790,"RandomY":1180177558,"RandomZ":3204700088,"StartTime":126088.0,"Objects":[{"StartTime":126088.0,"EndTime":126088.0,"Column":3},{"StartTime":126088.0,"EndTime":126088.0,"Column":4}]},{"RandomW":299141296,"RandomX":2460408790,"RandomY":1180177558,"RandomZ":3204700088,"StartTime":126619.0,"Objects":[{"StartTime":126619.0,"EndTime":126619.0,"Column":2},{"StartTime":126619.0,"EndTime":126619.0,"Column":1}]},{"RandomW":299141296,"RandomX":2460408790,"RandomY":1180177558,"RandomZ":3204700088,"StartTime":127150.0,"Objects":[{"StartTime":127150.0,"EndTime":127150.0,"Column":4},{"StartTime":127150.0,"EndTime":127150.0,"Column":3}]},{"RandomW":3037239607,"RandomX":1180177558,"RandomY":3204700088,"RandomZ":299141296,"StartTime":127416.0,"Objects":[{"StartTime":127416.0,"EndTime":127416.0,"Column":4},{"StartTime":127416.0,"EndTime":127416.0,"Column":5}]},{"RandomW":863164324,"RandomX":3204700088,"RandomY":299141296,"RandomZ":3037239607,"StartTime":127681.0,"Objects":[{"StartTime":127681.0,"EndTime":127681.0,"Column":5}]},{"RandomW":2456647781,"RandomX":299141296,"RandomY":3037239607,"RandomZ":863164324,"StartTime":127947.0,"Objects":[{"StartTime":127947.0,"EndTime":127947.0,"Column":4},{"StartTime":127947.0,"EndTime":127947.0,"Column":5}]},{"RandomW":659157904,"RandomX":3037239607,"RandomY":863164324,"RandomZ":2456647781,"StartTime":128212.0,"Objects":[{"StartTime":128212.0,"EndTime":128212.0,"Column":3},{"StartTime":128212.0,"EndTime":128212.0,"Column":4}]},{"RandomW":659157904,"RandomX":3037239607,"RandomY":863164324,"RandomZ":2456647781,"StartTime":128743.0,"Objects":[{"StartTime":128743.0,"EndTime":128743.0,"Column":2},{"StartTime":128743.0,"EndTime":128743.0,"Column":1}]},{"RandomW":659157904,"RandomX":3037239607,"RandomY":863164324,"RandomZ":2456647781,"StartTime":129274.0,"Objects":[{"StartTime":129274.0,"EndTime":129274.0,"Column":4},{"StartTime":129274.0,"EndTime":129274.0,"Column":3}]},{"RandomW":3598260079,"RandomX":863164324,"RandomY":2456647781,"RandomZ":659157904,"StartTime":129540.0,"Objects":[{"StartTime":129540.0,"EndTime":129540.0,"Column":3},{"StartTime":129540.0,"EndTime":129540.0,"Column":4}]},{"RandomW":1930638835,"RandomX":2456647781,"RandomY":659157904,"RandomZ":3598260079,"StartTime":129805.0,"Objects":[{"StartTime":129805.0,"EndTime":129805.0,"Column":1},{"StartTime":129805.0,"EndTime":129805.0,"Column":2}]},{"RandomW":4230333264,"RandomX":1930638835,"RandomY":2319762852,"RandomZ":3807998479,"StartTime":130071.0,"Objects":[{"StartTime":130071.0,"EndTime":130071.0,"Column":2},{"StartTime":130071.0,"EndTime":130071.0,"Column":3}]},{"RandomW":2482386774,"RandomX":4230333264,"RandomY":376688010,"RandomZ":3132506885,"StartTime":132460.0,"Objects":[{"StartTime":132460.0,"EndTime":132990.0,"Column":0}]},{"RandomW":3381449487,"RandomX":3132506885,"RandomY":2482386774,"RandomZ":1092311355,"StartTime":133522.0,"Objects":[{"StartTime":133522.0,"EndTime":134052.0,"Column":3}]},{"RandomW":3812940964,"RandomX":1092311355,"RandomY":3381449487,"RandomZ":3240759120,"StartTime":134318.0,"Objects":[{"StartTime":134318.0,"EndTime":134848.0,"Column":4}]},{"RandomW":2199106412,"RandomX":2014155638,"RandomY":3619038163,"RandomZ":1182263034,"StartTime":135115.0,"Objects":[{"StartTime":135115.0,"EndTime":135380.0,"Column":3},{"StartTime":135115.0,"EndTime":135380.0,"Column":0}]},{"RandomW":4049541057,"RandomX":1182263034,"RandomY":2199106412,"RandomZ":2542868059,"StartTime":135646.0,"Objects":[{"StartTime":135646.0,"EndTime":136176.0,"Column":5}]},{"RandomW":376448389,"RandomX":2542868059,"RandomY":4049541057,"RandomZ":149323558,"StartTime":136708.0,"Objects":[{"StartTime":136708.0,"EndTime":136973.0,"Column":1}]},{"RandomW":10761513,"RandomX":149323558,"RandomY":376448389,"RandomZ":156027614,"StartTime":137239.0,"Objects":[{"StartTime":137239.0,"EndTime":137504.0,"Column":0}]},{"RandomW":2890609580,"RandomX":156027614,"RandomY":10761513,"RandomZ":998270292,"StartTime":137770.0,"Objects":[{"StartTime":137770.0,"EndTime":138566.0,"Column":2}]},{"RandomW":3792858866,"RandomX":998270292,"RandomY":2890609580,"RandomZ":3275622081,"StartTime":138832.0,"Objects":[{"StartTime":138832.0,"EndTime":139097.0,"Column":4}]},{"RandomW":479756469,"RandomX":3792858866,"RandomY":3665829153,"RandomZ":799245198,"StartTime":139363.0,"Objects":[{"StartTime":139363.0,"EndTime":139628.0,"Column":2},{"StartTime":139363.0,"EndTime":139628.0,"Column":1}]},{"RandomW":1559664190,"RandomX":1837897770,"RandomY":3074386351,"RandomZ":2226336565,"StartTime":139894.0,"Objects":[{"StartTime":139894.0,"EndTime":140690.0,"Column":0},{"StartTime":139894.0,"EndTime":140690.0,"Column":4}]},{"RandomW":1370921154,"RandomX":3074386351,"RandomY":2226336565,"RandomZ":1559664190,"StartTime":140955.0,"Objects":[{"StartTime":140955.0,"EndTime":140955.0,"Column":4}]},{"RandomW":12534613,"RandomX":1559664190,"RandomY":1370921154,"RandomZ":495513930,"StartTime":141221.0,"Objects":[{"StartTime":141221.0,"EndTime":141751.0,"Column":3},{"StartTime":141486.0,"EndTime":141486.0,"Column":1},{"StartTime":141751.0,"EndTime":141751.0,"Column":1}]},{"RandomW":1474110729,"RandomX":12534613,"RandomY":3893387802,"RandomZ":226854738,"StartTime":142017.0,"Objects":[{"StartTime":142017.0,"EndTime":142017.0,"Column":2},{"StartTime":142017.0,"EndTime":142017.0,"Column":3}]},{"RandomW":3883366092,"RandomX":1474110729,"RandomY":2911002956,"RandomZ":3337209428,"StartTime":142283.0,"Objects":[{"StartTime":142283.0,"EndTime":142548.0,"Column":4}]},{"RandomW":1868157439,"RandomX":3883366092,"RandomY":1497166406,"RandomZ":3876220972,"StartTime":142814.0,"Objects":[{"StartTime":142814.0,"EndTime":143079.0,"Column":5}]},{"RandomW":868486094,"RandomX":1497166406,"RandomY":3876220972,"RandomZ":1868157439,"StartTime":143345.0,"Objects":[{"StartTime":143345.0,"EndTime":143345.0,"Column":2}]},{"RandomW":2379505970,"RandomX":3876220972,"RandomY":1868157439,"RandomZ":868486094,"StartTime":143610.0,"Objects":[{"StartTime":143610.0,"EndTime":143610.0,"Column":2}]},{"RandomW":971762612,"RandomX":1868157439,"RandomY":868486094,"RandomZ":2379505970,"StartTime":143876.0,"Objects":[{"StartTime":143876.0,"EndTime":143876.0,"Column":4}]},{"RandomW":2333467129,"RandomX":2379505970,"RandomY":971762612,"RandomZ":2560365407,"StartTime":144141.0,"Objects":[{"StartTime":144141.0,"EndTime":144671.0,"Column":0}]},{"RandomW":3275109659,"RandomX":2560365407,"RandomY":2333467129,"RandomZ":2783370328,"StartTime":145203.0,"Objects":[{"StartTime":145203.0,"EndTime":145468.0,"Column":3}]},{"RandomW":2675369072,"RandomX":2783370328,"RandomY":3275109659,"RandomZ":3142107337,"StartTime":145734.0,"Objects":[{"StartTime":145734.0,"EndTime":145999.0,"Column":1}]},{"RandomW":2114821552,"RandomX":3142107337,"RandomY":2675369072,"RandomZ":216133594,"StartTime":146265.0,"Objects":[{"StartTime":146265.0,"EndTime":146795.0,"Column":5}]},{"RandomW":2210288688,"RandomX":2675369072,"RandomY":216133594,"RandomZ":2114821552,"StartTime":147062.0,"Objects":[{"StartTime":147062.0,"EndTime":147062.0,"Column":3}]},{"RandomW":2824847566,"RandomX":2114821552,"RandomY":2210288688,"RandomZ":2881713491,"StartTime":147327.0,"Objects":[{"StartTime":147327.0,"EndTime":147592.0,"Column":1}]},{"RandomW":3418617049,"RandomX":2881713491,"RandomY":2824847566,"RandomZ":3131910248,"StartTime":147858.0,"Objects":[{"StartTime":147858.0,"EndTime":148123.0,"Column":3}]},{"RandomW":4264037536,"RandomX":3418617049,"RandomY":2065328415,"RandomZ":756387586,"StartTime":148389.0,"Objects":[{"StartTime":148389.0,"EndTime":149450.0,"Column":2},{"StartTime":148389.0,"EndTime":149450.0,"Column":5}]},{"RandomW":714689152,"RandomX":2065328415,"RandomY":756387586,"RandomZ":4264037536,"StartTime":149717.0,"Objects":[{"StartTime":149717.0,"EndTime":149717.0,"Column":2}]},{"RandomW":2187562077,"RandomX":756387586,"RandomY":4264037536,"RandomZ":714689152,"StartTime":149982.0,"Objects":[{"StartTime":149982.0,"EndTime":149982.0,"Column":1},{"StartTime":149982.0,"EndTime":149982.0,"Column":2}]},{"RandomW":59731596,"RandomX":4264037536,"RandomY":714689152,"RandomZ":2187562077,"StartTime":150247.0,"Objects":[{"StartTime":150247.0,"EndTime":150247.0,"Column":0}]},{"RandomW":3179032401,"RandomX":714689152,"RandomY":2187562077,"RandomZ":59731596,"StartTime":150513.0,"Objects":[{"StartTime":150513.0,"EndTime":150513.0,"Column":1}]},{"RandomW":1565638452,"RandomX":2187562077,"RandomY":59731596,"RandomZ":3179032401,"StartTime":150778.0,"Objects":[{"StartTime":150778.0,"EndTime":150778.0,"Column":2}]},{"RandomW":3285111207,"RandomX":59731596,"RandomY":3179032401,"RandomZ":1565638452,"StartTime":151044.0,"Objects":[{"StartTime":151044.0,"EndTime":151044.0,"Column":3},{"StartTime":151044.0,"EndTime":151044.0,"Column":4}]},{"RandomW":3142401116,"RandomX":3179032401,"RandomY":1565638452,"RandomZ":3285111207,"StartTime":151309.0,"Objects":[{"StartTime":151309.0,"EndTime":151309.0,"Column":4}]},{"RandomW":2191101353,"RandomX":3142401116,"RandomY":3877079747,"RandomZ":930029834,"StartTime":151575.0,"Objects":[{"StartTime":151575.0,"EndTime":152105.0,"Column":2},{"StartTime":151575.0,"EndTime":152105.0,"Column":0}]},{"RandomW":1171726387,"RandomX":2191101353,"RandomY":1357180538,"RandomZ":201209655,"StartTime":152637.0,"Objects":[{"StartTime":152637.0,"EndTime":152902.0,"Column":3}]},{"RandomW":2089660876,"RandomX":201209655,"RandomY":1171726387,"RandomZ":191699429,"StartTime":153168.0,"Objects":[{"StartTime":153168.0,"EndTime":153698.0,"Column":5}]},{"RandomW":2251323109,"RandomX":1171726387,"RandomY":191699429,"RandomZ":2089660876,"StartTime":153964.0,"Objects":[{"StartTime":153964.0,"EndTime":153964.0,"Column":3}]},{"RandomW":147408153,"RandomX":2251323109,"RandomY":2048526504,"RandomZ":433820735,"StartTime":154230.0,"Objects":[{"StartTime":154230.0,"EndTime":154230.0,"Column":0},{"StartTime":154230.0,"EndTime":154230.0,"Column":5}]},{"RandomW":223059387,"RandomX":2048526504,"RandomY":433820735,"RandomZ":147408153,"StartTime":154495.0,"Objects":[{"StartTime":154495.0,"EndTime":154495.0,"Column":3}]},{"RandomW":1644267862,"RandomX":147408153,"RandomY":223059387,"RandomZ":2814282738,"StartTime":154761.0,"Objects":[{"StartTime":154761.0,"EndTime":155026.0,"Column":4}]},{"RandomW":585628331,"RandomX":1644267862,"RandomY":547547522,"RandomZ":1901399656,"StartTime":155292.0,"Objects":[{"StartTime":155292.0,"EndTime":155292.0,"Column":0},{"StartTime":155292.0,"EndTime":155292.0,"Column":5}]},{"RandomW":1287818392,"RandomX":547547522,"RandomY":1901399656,"RandomZ":585628331,"StartTime":155557.0,"Objects":[{"StartTime":155557.0,"EndTime":155557.0,"Column":1}]},{"RandomW":3879046214,"RandomX":2065404539,"RandomY":2732913982,"RandomZ":3217781099,"StartTime":155823.0,"Objects":[{"StartTime":155823.0,"EndTime":156088.0,"Column":2},{"StartTime":155823.0,"EndTime":156088.0,"Column":4}]},{"RandomW":3318878889,"RandomX":3217781099,"RandomY":3879046214,"RandomZ":1075466897,"StartTime":156354.0,"Objects":[{"StartTime":156354.0,"EndTime":156619.0,"Column":3}]},{"RandomW":1785367685,"RandomX":1075466897,"RandomY":3318878889,"RandomZ":561406801,"StartTime":156885.0,"Objects":[{"StartTime":156885.0,"EndTime":157415.0,"Column":4}]},{"RandomW":2909067134,"RandomX":561406801,"RandomY":1785367685,"RandomZ":4168537475,"StartTime":157947.0,"Objects":[{"StartTime":157947.0,"EndTime":157947.0,"Column":5},{"StartTime":157947.0,"EndTime":157947.0,"Column":2}]},{"RandomW":1067074920,"RandomX":1785367685,"RandomY":4168537475,"RandomZ":2909067134,"StartTime":158212.0,"Objects":[{"StartTime":158212.0,"EndTime":158212.0,"Column":4}]},{"RandomW":27977914,"RandomX":4168537475,"RandomY":2909067134,"RandomZ":1067074920,"StartTime":158478.0,"Objects":[{"StartTime":158478.0,"EndTime":158478.0,"Column":5},{"StartTime":158478.0,"EndTime":158478.0,"Column":0}]},{"RandomW":1329528769,"RandomX":2909067134,"RandomY":1067074920,"RandomZ":27977914,"StartTime":158743.0,"Objects":[{"StartTime":158743.0,"EndTime":158743.0,"Column":4}]},{"RandomW":3295284863,"RandomX":1067074920,"RandomY":27977914,"RandomZ":1329528769,"StartTime":159009.0,"Objects":[{"StartTime":159009.0,"EndTime":159009.0,"Column":5}]},{"RandomW":691446431,"RandomX":27977914,"RandomY":1329528769,"RandomZ":3295284863,"StartTime":159540.0,"Objects":[{"StartTime":159540.0,"EndTime":159540.0,"Column":3},{"StartTime":159540.0,"EndTime":159540.0,"Column":4}]},{"RandomW":3354872060,"RandomX":3295284863,"RandomY":691446431,"RandomZ":2140106811,"StartTime":159805.0,"Objects":[{"StartTime":159805.0,"EndTime":159805.0,"Column":2},{"StartTime":159805.0,"EndTime":159805.0,"Column":3}]},{"RandomW":1400553355,"RandomX":691446431,"RandomY":2140106811,"RandomZ":3354872060,"StartTime":160071.0,"Objects":[{"StartTime":160071.0,"EndTime":160071.0,"Column":2}]},{"RandomW":1400553355,"RandomX":691446431,"RandomY":2140106811,"RandomZ":3354872060,"StartTime":160601.0,"Objects":[{"StartTime":160601.0,"EndTime":160601.0,"Column":3}]},{"RandomW":3485781281,"RandomX":2140106811,"RandomY":3354872060,"RandomZ":1400553355,"StartTime":160867.0,"Objects":[{"StartTime":160867.0,"EndTime":160867.0,"Column":3}]},{"RandomW":3053679463,"RandomX":1400553355,"RandomY":3485781281,"RandomZ":3419304522,"StartTime":161132.0,"Objects":[{"StartTime":161132.0,"EndTime":161397.0,"Column":2}]},{"RandomW":3645336111,"RandomX":3419304522,"RandomY":3053679463,"RandomZ":805504203,"StartTime":161663.0,"Objects":[{"StartTime":161663.0,"EndTime":162193.0,"Column":4}]},{"RandomW":1638076271,"RandomX":3053679463,"RandomY":805504203,"RandomZ":3645336111,"StartTime":162460.0,"Objects":[{"StartTime":162460.0,"EndTime":162460.0,"Column":3}]},{"RandomW":107981020,"RandomX":1638076271,"RandomY":3432435831,"RandomZ":3835408498,"StartTime":162725.0,"Objects":[{"StartTime":162725.0,"EndTime":162725.0,"Column":0},{"StartTime":162725.0,"EndTime":162725.0,"Column":5}]},{"RandomW":94467567,"RandomX":3835408498,"RandomY":107981020,"RandomZ":2144208649,"StartTime":163256.0,"Objects":[{"StartTime":163256.0,"EndTime":163256.0,"Column":4},{"StartTime":163256.0,"EndTime":163256.0,"Column":0}]},{"RandomW":1015041289,"RandomX":107981020,"RandomY":2144208649,"RandomZ":94467567,"StartTime":163522.0,"Objects":[{"StartTime":163522.0,"EndTime":163522.0,"Column":3}]},{"RandomW":2029876639,"RandomX":1204955917,"RandomY":1210817201,"RandomZ":1177260118,"StartTime":163787.0,"Objects":[{"StartTime":163787.0,"EndTime":164052.0,"Column":5}]},{"RandomW":3125496505,"RandomX":1177260118,"RandomY":2029876639,"RandomZ":2929832910,"StartTime":164318.0,"Objects":[{"StartTime":164318.0,"EndTime":164583.0,"Column":2}]},{"RandomW":2426857185,"RandomX":3125496505,"RandomY":2700661894,"RandomZ":859446411,"StartTime":164849.0,"Objects":[{"StartTime":164849.0,"EndTime":165114.0,"Column":0}]},{"RandomW":4116661924,"RandomX":2426857185,"RandomY":1884842190,"RandomZ":375578279,"StartTime":165380.0,"Objects":[{"StartTime":165380.0,"EndTime":165910.0,"Column":1},{"StartTime":165380.0,"EndTime":165910.0,"Column":5}]},{"RandomW":3787729819,"RandomX":375578279,"RandomY":4116661924,"RandomZ":1382622976,"StartTime":166442.0,"Objects":[{"StartTime":166442.0,"EndTime":166972.0,"Column":4}]},{"RandomW":3780331234,"RandomX":4116661924,"RandomY":1382622976,"RandomZ":3787729819,"StartTime":167239.0,"Objects":[{"StartTime":167239.0,"EndTime":167239.0,"Column":3}]},{"RandomW":891570220,"RandomX":3780331234,"RandomY":3996538378,"RandomZ":4118560235,"StartTime":167504.0,"Objects":[{"StartTime":167504.0,"EndTime":168034.0,"Column":5},{"StartTime":167504.0,"EndTime":168034.0,"Column":2}]},{"RandomW":1312521276,"RandomX":3996538378,"RandomY":4118560235,"RandomZ":891570220,"StartTime":168301.0,"Objects":[{"StartTime":168301.0,"EndTime":168301.0,"Column":0}]},{"RandomW":316798455,"RandomX":4118560235,"RandomY":891570220,"RandomZ":1312521276,"StartTime":168566.0,"Objects":[{"StartTime":168566.0,"EndTime":168566.0,"Column":2},{"StartTime":168566.0,"EndTime":168566.0,"Column":3}]},{"RandomW":107348261,"RandomX":891570220,"RandomY":1312521276,"RandomZ":316798455,"StartTime":168832.0,"Objects":[{"StartTime":168832.0,"EndTime":168832.0,"Column":1}]},{"RandomW":286543085,"RandomX":1312521276,"RandomY":316798455,"RandomZ":107348261,"StartTime":169097.0,"Objects":[{"StartTime":169097.0,"EndTime":169097.0,"Column":1},{"StartTime":169097.0,"EndTime":169097.0,"Column":2}]},{"RandomW":2220558447,"RandomX":316798455,"RandomY":107348261,"RandomZ":286543085,"StartTime":169363.0,"Objects":[{"StartTime":169363.0,"EndTime":169363.0,"Column":2}]},{"RandomW":2567445342,"RandomX":107348261,"RandomY":286543085,"RandomZ":2220558447,"StartTime":169628.0,"Objects":[{"StartTime":169628.0,"EndTime":169628.0,"Column":1},{"StartTime":169628.0,"EndTime":169628.0,"Column":2}]},{"RandomW":2941341299,"RandomX":286543085,"RandomY":2220558447,"RandomZ":2567445342,"StartTime":170159.0,"Objects":[{"StartTime":170159.0,"EndTime":170159.0,"Column":3},{"StartTime":170159.0,"EndTime":170159.0,"Column":4}]},{"RandomW":2941341299,"RandomX":286543085,"RandomY":2220558447,"RandomZ":2567445342,"StartTime":170424.0,"Objects":[{"StartTime":170424.0,"EndTime":170424.0,"Column":2},{"StartTime":170424.0,"EndTime":170424.0,"Column":1}]},{"RandomW":1087727581,"RandomX":2567445342,"RandomY":2941341299,"RandomZ":479267920,"StartTime":170690.0,"Objects":[{"StartTime":170690.0,"EndTime":171220.0,"Column":3}]},{"RandomW":2581485170,"RandomX":2941341299,"RandomY":479267920,"RandomZ":1087727581,"StartTime":171486.0,"Objects":[{"StartTime":171486.0,"EndTime":171486.0,"Column":5}]},{"RandomW":683596203,"RandomX":1087727581,"RandomY":2581485170,"RandomZ":3168383468,"StartTime":171752.0,"Objects":[{"StartTime":171752.0,"EndTime":172282.0,"Column":1}]},{"RandomW":3284056302,"RandomX":2581485170,"RandomY":3168383468,"RandomZ":683596203,"StartTime":172548.0,"Objects":[{"StartTime":172548.0,"EndTime":172548.0,"Column":2}]},{"RandomW":2830633773,"RandomX":3168383468,"RandomY":683596203,"RandomZ":3284056302,"StartTime":172814.0,"Objects":[{"StartTime":172814.0,"EndTime":172814.0,"Column":3},{"StartTime":172814.0,"EndTime":172814.0,"Column":4}]},{"RandomW":3651115271,"RandomX":683596203,"RandomY":3284056302,"RandomZ":2830633773,"StartTime":173079.0,"Objects":[{"StartTime":173079.0,"EndTime":173079.0,"Column":3}]},{"RandomW":120746014,"RandomX":3284056302,"RandomY":2830633773,"RandomZ":3651115271,"StartTime":173345.0,"Objects":[{"StartTime":173345.0,"EndTime":173345.0,"Column":3},{"StartTime":173345.0,"EndTime":173345.0,"Column":4}]},{"RandomW":830325214,"RandomX":2830633773,"RandomY":3651115271,"RandomZ":120746014,"StartTime":173610.0,"Objects":[{"StartTime":173610.0,"EndTime":173610.0,"Column":4}]},{"RandomW":1509180863,"RandomX":3651115271,"RandomY":120746014,"RandomZ":830325214,"StartTime":173876.0,"Objects":[{"StartTime":173876.0,"EndTime":173876.0,"Column":3},{"StartTime":173876.0,"EndTime":173876.0,"Column":4}]},{"RandomW":2233493011,"RandomX":3902833961,"RandomY":923589330,"RandomZ":3425613873,"StartTime":174407.0,"Objects":[{"StartTime":174407.0,"EndTime":174672.0,"Column":2},{"StartTime":174407.0,"EndTime":174672.0,"Column":0}]},{"RandomW":2517643905,"RandomX":1207989122,"RandomY":993303558,"RandomZ":3011821377,"StartTime":174938.0,"Objects":[{"StartTime":174938.0,"EndTime":175468.0,"Column":3},{"StartTime":174938.0,"EndTime":175468.0,"Column":1}]},{"RandomW":3720863650,"RandomX":993303558,"RandomY":3011821377,"RandomZ":2517643905,"StartTime":175734.0,"Objects":[{"StartTime":175734.0,"EndTime":175734.0,"Column":2}]},{"RandomW":3563355415,"RandomX":2517643905,"RandomY":3720863650,"RandomZ":1116519600,"StartTime":176000.0,"Objects":[{"StartTime":176000.0,"EndTime":176530.0,"Column":3}]},{"RandomW":3287800096,"RandomX":3720863650,"RandomY":1116519600,"RandomZ":3563355415,"StartTime":176796.0,"Objects":[{"StartTime":176796.0,"EndTime":176796.0,"Column":3}]},{"RandomW":539898931,"RandomX":1116519600,"RandomY":3563355415,"RandomZ":3287800096,"StartTime":177062.0,"Objects":[{"StartTime":177062.0,"EndTime":177062.0,"Column":2},{"StartTime":177062.0,"EndTime":177062.0,"Column":3}]},{"RandomW":123758010,"RandomX":3563355415,"RandomY":3287800096,"RandomZ":539898931,"StartTime":177327.0,"Objects":[{"StartTime":177327.0,"EndTime":177327.0,"Column":4}]},{"RandomW":4028312708,"RandomX":3287800096,"RandomY":539898931,"RandomZ":123758010,"StartTime":177593.0,"Objects":[{"StartTime":177593.0,"EndTime":177593.0,"Column":2},{"StartTime":177593.0,"EndTime":177593.0,"Column":3}]},{"RandomW":2371409278,"RandomX":539898931,"RandomY":123758010,"RandomZ":4028312708,"StartTime":177858.0,"Objects":[{"StartTime":177858.0,"EndTime":177858.0,"Column":3}]},{"RandomW":3699828554,"RandomX":123758010,"RandomY":4028312708,"RandomZ":2371409278,"StartTime":178124.0,"Objects":[{"StartTime":178124.0,"EndTime":178124.0,"Column":2},{"StartTime":178124.0,"EndTime":178124.0,"Column":3}]},{"RandomW":4053363780,"RandomX":2371409278,"RandomY":3699828554,"RandomZ":3637445845,"StartTime":178655.0,"Objects":[{"StartTime":178655.0,"EndTime":178920.0,"Column":5}]},{"RandomW":1366734997,"RandomX":3637445845,"RandomY":4053363780,"RandomZ":3122766892,"StartTime":179186.0,"Objects":[{"StartTime":179186.0,"EndTime":179716.0,"Column":3}]},{"RandomW":2085192570,"RandomX":1366734997,"RandomY":4047501250,"RandomZ":3422445293,"StartTime":179982.0,"Objects":[{"StartTime":179982.0,"EndTime":179982.0,"Column":3},{"StartTime":179982.0,"EndTime":179982.0,"Column":5}]},{"RandomW":2526042960,"RandomX":3422445293,"RandomY":2085192570,"RandomZ":2552180342,"StartTime":180247.0,"Objects":[{"StartTime":180247.0,"EndTime":180777.0,"Column":1}]},{"RandomW":2946528857,"RandomX":2085192570,"RandomY":2552180342,"RandomZ":2526042960,"StartTime":181044.0,"Objects":[{"StartTime":181044.0,"EndTime":181044.0,"Column":2}]},{"RandomW":4275012500,"RandomX":2526042960,"RandomY":2946528857,"RandomZ":2680316548,"StartTime":181309.0,"Objects":[{"StartTime":181309.0,"EndTime":181574.0,"Column":5}]},{"RandomW":716767862,"RandomX":1177533555,"RandomY":3396673648,"RandomZ":1210370441,"StartTime":181840.0,"Objects":[{"StartTime":181840.0,"EndTime":182105.0,"Column":3},{"StartTime":181840.0,"EndTime":182105.0,"Column":2}]},{"RandomW":1918581647,"RandomX":1210370441,"RandomY":716767862,"RandomZ":290385782,"StartTime":182371.0,"Objects":[{"StartTime":182371.0,"EndTime":182636.0,"Column":5}]},{"RandomW":2554770024,"RandomX":1918581647,"RandomY":475913420,"RandomZ":4262840195,"StartTime":182902.0,"Objects":[{"StartTime":182902.0,"EndTime":183432.0,"Column":1}]},{"RandomW":862610860,"RandomX":475913420,"RandomY":4262840195,"RandomZ":2554770024,"StartTime":183699.0,"Objects":[{"StartTime":183699.0,"EndTime":185557.0,"Column":2}]},{"RandomW":3240322225,"RandomX":4262840195,"RandomY":2554770024,"RandomZ":862610860,"StartTime":202017.0,"Objects":[{"StartTime":202017.0,"EndTime":202017.0,"Column":0}]},{"RandomW":2438630089,"RandomX":2554770024,"RandomY":862610860,"RandomZ":3240322225,"StartTime":202283.0,"Objects":[{"StartTime":202283.0,"EndTime":202283.0,"Column":1}]},{"RandomW":1543895637,"RandomX":3240322225,"RandomY":2438630089,"RandomZ":1008910200,"StartTime":202548.0,"Objects":[{"StartTime":202548.0,"EndTime":203078.0,"Column":4}]},{"RandomW":2262375304,"RandomX":2438630089,"RandomY":1008910200,"RandomZ":1543895637,"StartTime":203345.0,"Objects":[{"StartTime":203345.0,"EndTime":203345.0,"Column":2}]},{"RandomW":3932191533,"RandomX":1543895637,"RandomY":2262375304,"RandomZ":3281044824,"StartTime":203610.0,"Objects":[{"StartTime":203610.0,"EndTime":203875.0,"Column":4}]},{"RandomW":2456816417,"RandomX":3932191533,"RandomY":2579817318,"RandomZ":3616517773,"StartTime":204141.0,"Objects":[{"StartTime":204141.0,"EndTime":204406.0,"Column":0}]},{"RandomW":1863357795,"RandomX":2456816417,"RandomY":2065740625,"RandomZ":3309416576,"StartTime":204672.0,"Objects":[{"StartTime":204672.0,"EndTime":205202.0,"Column":3},{"StartTime":204672.0,"EndTime":205202.0,"Column":5}]},{"RandomW":66010220,"RandomX":3309416576,"RandomY":1863357795,"RandomZ":2100015779,"StartTime":205469.0,"Objects":[{"StartTime":205469.0,"EndTime":205469.0,"Column":4},{"StartTime":205469.0,"EndTime":205469.0,"Column":0}]},{"RandomW":548562611,"RandomX":2100015779,"RandomY":66010220,"RandomZ":3420604705,"StartTime":205734.0,"Objects":[{"StartTime":205734.0,"EndTime":205999.0,"Column":1}]},{"RandomW":2052728473,"RandomX":3420604705,"RandomY":548562611,"RandomZ":2913964,"StartTime":206265.0,"Objects":[{"StartTime":206265.0,"EndTime":206530.0,"Column":5}]},{"RandomW":1944462115,"RandomX":2052728473,"RandomY":2737357746,"RandomZ":270315162,"StartTime":206796.0,"Objects":[{"StartTime":206796.0,"EndTime":206796.0,"Column":2},{"StartTime":206796.0,"EndTime":206796.0,"Column":3}]},{"RandomW":3626216744,"RandomX":2737357746,"RandomY":270315162,"RandomZ":1944462115,"StartTime":207062.0,"Objects":[{"StartTime":207062.0,"EndTime":207062.0,"Column":5}]},{"RandomW":1039388877,"RandomX":270315162,"RandomY":1944462115,"RandomZ":3626216744,"StartTime":207327.0,"Objects":[{"StartTime":207327.0,"EndTime":207327.0,"Column":4}]},{"RandomW":3362701719,"RandomX":1944462115,"RandomY":3626216744,"RandomZ":1039388877,"StartTime":207593.0,"Objects":[{"StartTime":207593.0,"EndTime":207593.0,"Column":3}]},{"RandomW":3968495235,"RandomX":3362701719,"RandomY":2329091202,"RandomZ":1331472925,"StartTime":207858.0,"Objects":[{"StartTime":207858.0,"EndTime":208388.0,"Column":5}]},{"RandomW":1381394684,"RandomX":2329091202,"RandomY":1331472925,"RandomZ":3968495235,"StartTime":208655.0,"Objects":[{"StartTime":208655.0,"EndTime":208655.0,"Column":5}]},{"RandomW":1435798214,"RandomX":1381394684,"RandomY":1081301304,"RandomZ":3939835753,"StartTime":208920.0,"Objects":[{"StartTime":208920.0,"EndTime":209450.0,"Column":4}]},{"RandomW":3026458880,"RandomX":1081301304,"RandomY":3939835753,"RandomZ":1435798214,"StartTime":209717.0,"Objects":[{"StartTime":209717.0,"EndTime":209717.0,"Column":5}]},{"RandomW":3713738018,"RandomX":3026458880,"RandomY":1845767213,"RandomZ":745035987,"StartTime":209982.0,"Objects":[{"StartTime":209982.0,"EndTime":210512.0,"Column":2},{"StartTime":209982.0,"EndTime":210512.0,"Column":4}]},{"RandomW":1231260560,"RandomX":1845767213,"RandomY":745035987,"RandomZ":3713738018,"StartTime":210778.0,"Objects":[{"StartTime":210778.0,"EndTime":210778.0,"Column":4}]},{"RandomW":105489365,"RandomX":745035987,"RandomY":3713738018,"RandomZ":1231260560,"StartTime":211044.0,"Objects":[{"StartTime":211044.0,"EndTime":211044.0,"Column":4}]},{"RandomW":1753861391,"RandomX":3713738018,"RandomY":1231260560,"RandomZ":105489365,"StartTime":211309.0,"Objects":[{"StartTime":211309.0,"EndTime":211309.0,"Column":2}]},{"RandomW":966114829,"RandomX":105489365,"RandomY":1753861391,"RandomZ":1828685577,"StartTime":211575.0,"Objects":[{"StartTime":211575.0,"EndTime":211575.0,"Column":3},{"StartTime":211575.0,"EndTime":211575.0,"Column":2}]},{"RandomW":1431749195,"RandomX":1836275468,"RandomY":1290011463,"RandomZ":1159621643,"StartTime":211840.0,"Objects":[{"StartTime":211840.0,"EndTime":212370.0,"Column":5},{"StartTime":211840.0,"EndTime":212370.0,"Column":4}]},{"RandomW":3472418283,"RandomX":1159621643,"RandomY":1431749195,"RandomZ":2724869338,"StartTime":212637.0,"Objects":[{"StartTime":212637.0,"EndTime":212902.0,"Column":3}]},{"RandomW":1755864208,"RandomX":3472418283,"RandomY":2016458251,"RandomZ":2610391004,"StartTime":213168.0,"Objects":[{"StartTime":213168.0,"EndTime":213698.0,"Column":1},{"StartTime":213168.0,"EndTime":213698.0,"Column":4}]},{"RandomW":1635138515,"RandomX":2016458251,"RandomY":2610391004,"RandomZ":1755864208,"StartTime":213964.0,"Objects":[{"StartTime":213964.0,"EndTime":213964.0,"Column":3}]},{"RandomW":3162662082,"RandomX":1755864208,"RandomY":1635138515,"RandomZ":2617989400,"StartTime":214230.0,"Objects":[{"StartTime":214230.0,"EndTime":214495.0,"Column":2}]},{"RandomW":1184692914,"RandomX":2617989400,"RandomY":3162662082,"RandomZ":2531582750,"StartTime":214761.0,"Objects":[{"StartTime":214761.0,"EndTime":215026.0,"Column":3}]},{"RandomW":798124101,"RandomX":2531582750,"RandomY":1184692914,"RandomZ":2157553888,"StartTime":215292.0,"Objects":[{"StartTime":215292.0,"EndTime":215557.0,"Column":2}]},{"RandomW":1923400471,"RandomX":798124101,"RandomY":2665448122,"RandomZ":1060614841,"StartTime":215823.0,"Objects":[{"StartTime":215823.0,"EndTime":216088.0,"Column":5}]},{"RandomW":775950648,"RandomX":1923400471,"RandomY":3469237574,"RandomZ":2892029047,"StartTime":216354.0,"Objects":[{"StartTime":216354.0,"EndTime":216354.0,"Column":1},{"StartTime":216354.0,"EndTime":216354.0,"Column":4}]},{"RandomW":1321234603,"RandomX":4127626210,"RandomY":1546611249,"RandomZ":1925740893,"StartTime":216885.0,"Objects":[{"StartTime":216885.0,"EndTime":217150.0,"Column":5},{"StartTime":216885.0,"EndTime":217150.0,"Column":3}]},{"RandomW":2881678930,"RandomX":1925740893,"RandomY":1321234603,"RandomZ":2358993682,"StartTime":217416.0,"Objects":[{"StartTime":217416.0,"EndTime":217946.0,"Column":2}]},{"RandomW":2599512294,"RandomX":1321234603,"RandomY":2358993682,"RandomZ":2881678930,"StartTime":218212.0,"Objects":[{"StartTime":218212.0,"EndTime":218212.0,"Column":1}]},{"RandomW":2150464549,"RandomX":2881678930,"RandomY":2599512294,"RandomZ":3623425595,"StartTime":218478.0,"Objects":[{"StartTime":218478.0,"EndTime":219008.0,"Column":0}]},{"RandomW":763775798,"RandomX":3623425595,"RandomY":2150464549,"RandomZ":1008837132,"StartTime":219274.0,"Objects":[{"StartTime":219274.0,"EndTime":221132.0,"Column":2}]},{"RandomW":3656799832,"RandomX":1008837132,"RandomY":763775798,"RandomZ":852609139,"StartTime":221663.0,"Objects":[{"StartTime":221663.0,"EndTime":222193.0,"Column":4}]},{"RandomW":4147545979,"RandomX":852609139,"RandomY":3656799832,"RandomZ":3908484776,"StartTime":222460.0,"Objects":[{"StartTime":222460.0,"EndTime":222460.0,"Column":2},{"StartTime":222460.0,"EndTime":222460.0,"Column":5}]},{"RandomW":540508179,"RandomX":3908484776,"RandomY":4147545979,"RandomZ":1259887550,"StartTime":222725.0,"Objects":[{"StartTime":222725.0,"EndTime":223255.0,"Column":1}]},{"RandomW":1042752714,"RandomX":1259887550,"RandomY":540508179,"RandomZ":2104064323,"StartTime":223522.0,"Objects":[{"StartTime":223522.0,"EndTime":223522.0,"Column":5},{"StartTime":223522.0,"EndTime":223522.0,"Column":2}]},{"RandomW":3077262619,"RandomX":540508179,"RandomY":2104064323,"RandomZ":1042752714,"StartTime":223787.0,"Objects":[{"StartTime":223787.0,"EndTime":223787.0,"Column":3},{"StartTime":223787.0,"EndTime":223787.0,"Column":4}]},{"RandomW":734033149,"RandomX":2104064323,"RandomY":1042752714,"RandomZ":3077262619,"StartTime":224053.0,"Objects":[{"StartTime":224053.0,"EndTime":224053.0,"Column":4}]},{"RandomW":492155815,"RandomX":1042752714,"RandomY":3077262619,"RandomZ":734033149,"StartTime":224318.0,"Objects":[{"StartTime":224318.0,"EndTime":224318.0,"Column":4},{"StartTime":224318.0,"EndTime":224318.0,"Column":5}]},{"RandomW":441697715,"RandomX":3077262619,"RandomY":734033149,"RandomZ":492155815,"StartTime":224584.0,"Objects":[{"StartTime":224584.0,"EndTime":224584.0,"Column":3}]},{"RandomW":4156379255,"RandomX":734033149,"RandomY":492155815,"RandomZ":441697715,"StartTime":224849.0,"Objects":[{"StartTime":224849.0,"EndTime":224849.0,"Column":4},{"StartTime":224849.0,"EndTime":224849.0,"Column":5}]},{"RandomW":3757225441,"RandomX":492155815,"RandomY":441697715,"RandomZ":4156379255,"StartTime":225380.0,"Objects":[{"StartTime":225380.0,"EndTime":225380.0,"Column":2},{"StartTime":225380.0,"EndTime":225380.0,"Column":3}]},{"RandomW":3757225441,"RandomX":492155815,"RandomY":441697715,"RandomZ":4156379255,"StartTime":225646.0,"Objects":[{"StartTime":225646.0,"EndTime":225646.0,"Column":3},{"StartTime":225646.0,"EndTime":225646.0,"Column":2}]},{"RandomW":2225043333,"RandomX":3950035756,"RandomY":4132636893,"RandomZ":3158636107,"StartTime":225911.0,"Objects":[{"StartTime":225911.0,"EndTime":226441.0,"Column":5},{"StartTime":225911.0,"EndTime":226441.0,"Column":0}]},{"RandomW":479006094,"RandomX":2225043333,"RandomY":3919293849,"RandomZ":2279622039,"StartTime":226708.0,"Objects":[{"StartTime":226708.0,"EndTime":226708.0,"Column":0},{"StartTime":226708.0,"EndTime":226708.0,"Column":1}]},{"RandomW":3529234379,"RandomX":479006094,"RandomY":1674670789,"RandomZ":1460857923,"StartTime":226973.0,"Objects":[{"StartTime":226973.0,"EndTime":227503.0,"Column":4},{"StartTime":226973.0,"EndTime":227503.0,"Column":3}]},{"RandomW":2798539123,"RandomX":1674670789,"RandomY":1460857923,"RandomZ":3529234379,"StartTime":227770.0,"Objects":[{"StartTime":227770.0,"EndTime":227770.0,"Column":3}]},{"RandomW":1315002421,"RandomX":1460857923,"RandomY":3529234379,"RandomZ":2798539123,"StartTime":228035.0,"Objects":[{"StartTime":228035.0,"EndTime":228035.0,"Column":2},{"StartTime":228035.0,"EndTime":228035.0,"Column":3}]},{"RandomW":2396116302,"RandomX":3529234379,"RandomY":2798539123,"RandomZ":1315002421,"StartTime":228301.0,"Objects":[{"StartTime":228301.0,"EndTime":228301.0,"Column":1}]},{"RandomW":2184752848,"RandomX":2798539123,"RandomY":1315002421,"RandomZ":2396116302,"StartTime":228566.0,"Objects":[{"StartTime":228566.0,"EndTime":228566.0,"Column":2},{"StartTime":228566.0,"EndTime":228566.0,"Column":3}]},{"RandomW":1453929005,"RandomX":1315002421,"RandomY":2396116302,"RandomZ":2184752848,"StartTime":228832.0,"Objects":[{"StartTime":228832.0,"EndTime":228832.0,"Column":1}]},{"RandomW":307062845,"RandomX":2396116302,"RandomY":2184752848,"RandomZ":1453929005,"StartTime":229097.0,"Objects":[{"StartTime":229097.0,"EndTime":229097.0,"Column":2},{"StartTime":229097.0,"EndTime":229097.0,"Column":3}]},{"RandomW":2488853431,"RandomX":1430246951,"RandomY":1243135735,"RandomZ":862796553,"StartTime":229628.0,"Objects":[{"StartTime":229628.0,"EndTime":229893.0,"Column":0}]},{"RandomW":2954723307,"RandomX":862796553,"RandomY":2488853431,"RandomZ":1065193973,"StartTime":230159.0,"Objects":[{"StartTime":230159.0,"EndTime":230689.0,"Column":2}]},{"RandomW":3118771232,"RandomX":1065193973,"RandomY":2954723307,"RandomZ":3941773202,"StartTime":230955.0,"Objects":[{"StartTime":230955.0,"EndTime":230955.0,"Column":3},{"StartTime":230955.0,"EndTime":230955.0,"Column":2}]},{"RandomW":1630107201,"RandomX":3532926875,"RandomY":2476115689,"RandomZ":1207743047,"StartTime":231221.0,"Objects":[{"StartTime":231221.0,"EndTime":231751.0,"Column":0},{"StartTime":231221.0,"EndTime":231751.0,"Column":4}]},{"RandomW":313681160,"RandomX":2476115689,"RandomY":1207743047,"RandomZ":1630107201,"StartTime":232017.0,"Objects":[{"StartTime":232017.0,"EndTime":232017.0,"Column":2}]},{"RandomW":892602489,"RandomX":1207743047,"RandomY":1630107201,"RandomZ":313681160,"StartTime":232283.0,"Objects":[{"StartTime":232283.0,"EndTime":232283.0,"Column":3},{"StartTime":232283.0,"EndTime":232283.0,"Column":4}]},{"RandomW":2549672466,"RandomX":1630107201,"RandomY":313681160,"RandomZ":892602489,"StartTime":232548.0,"Objects":[{"StartTime":232548.0,"EndTime":232548.0,"Column":1}]},{"RandomW":3175685586,"RandomX":313681160,"RandomY":892602489,"RandomZ":2549672466,"StartTime":232814.0,"Objects":[{"StartTime":232814.0,"EndTime":232814.0,"Column":3},{"StartTime":232814.0,"EndTime":232814.0,"Column":4}]},{"RandomW":1012053334,"RandomX":892602489,"RandomY":2549672466,"RandomZ":3175685586,"StartTime":233079.0,"Objects":[{"StartTime":233079.0,"EndTime":233079.0,"Column":2}]},{"RandomW":2846885221,"RandomX":2549672466,"RandomY":3175685586,"RandomZ":1012053334,"StartTime":233345.0,"Objects":[{"StartTime":233345.0,"EndTime":233345.0,"Column":3},{"StartTime":233345.0,"EndTime":233345.0,"Column":4}]},{"RandomW":2773158813,"RandomX":2846885221,"RandomY":4182295099,"RandomZ":203093837,"StartTime":233876.0,"Objects":[{"StartTime":233876.0,"EndTime":234141.0,"Column":0},{"StartTime":233876.0,"EndTime":234141.0,"Column":1}]},{"RandomW":857734082,"RandomX":203093837,"RandomY":2773158813,"RandomZ":2365172092,"StartTime":234407.0,"Objects":[{"StartTime":234407.0,"EndTime":234937.0,"Column":2}]},{"RandomW":3898917491,"RandomX":2773158813,"RandomY":2365172092,"RandomZ":857734082,"StartTime":235203.0,"Objects":[{"StartTime":235203.0,"EndTime":235203.0,"Column":2}]},{"RandomW":1417532037,"RandomX":857734082,"RandomY":3898917491,"RandomZ":361638657,"StartTime":235469.0,"Objects":[{"StartTime":235469.0,"EndTime":235999.0,"Column":3}]},{"RandomW":2557538851,"RandomX":3898917491,"RandomY":361638657,"RandomZ":1417532037,"StartTime":236265.0,"Objects":[{"StartTime":236265.0,"EndTime":236265.0,"Column":3}]},{"RandomW":846935039,"RandomX":1417532037,"RandomY":2557538851,"RandomZ":1456065540,"StartTime":236531.0,"Objects":[{"StartTime":236531.0,"EndTime":236796.0,"Column":2}]},{"RandomW":2547399683,"RandomX":1456065540,"RandomY":846935039,"RandomZ":2284332751,"StartTime":237062.0,"Objects":[{"StartTime":237062.0,"EndTime":237327.0,"Column":1}]},{"RandomW":2405919505,"RandomX":846935039,"RandomY":2284332751,"RandomZ":2547399683,"StartTime":237593.0,"Objects":[{"StartTime":237593.0,"EndTime":237593.0,"Column":3},{"StartTime":237593.0,"EndTime":237593.0,"Column":4}]},{"RandomW":1684559305,"RandomX":2284332751,"RandomY":2547399683,"RandomZ":2405919505,"StartTime":237858.0,"Objects":[{"StartTime":237858.0,"EndTime":237858.0,"Column":5},{"StartTime":237858.0,"EndTime":237858.0,"Column":0}]},{"RandomW":2914982357,"RandomX":2547399683,"RandomY":2405919505,"RandomZ":1684559305,"StartTime":238124.0,"Objects":[{"StartTime":238124.0,"EndTime":238124.0,"Column":3},{"StartTime":238124.0,"EndTime":238124.0,"Column":4}]},{"RandomW":2343509573,"RandomX":2405919505,"RandomY":1684559305,"RandomZ":2914982357,"StartTime":238389.0,"Objects":[{"StartTime":238389.0,"EndTime":238389.0,"Column":5}]},{"RandomW":1059378114,"RandomX":1684559305,"RandomY":2914982357,"RandomZ":2343509573,"StartTime":238655.0,"Objects":[{"StartTime":238655.0,"EndTime":240778.0,"Column":2}]}]} \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/20544-expected-conversion.json b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/20544-expected-conversion.json index 2289a7243f..ed4b550f01 100644 --- a/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/20544-expected-conversion.json +++ b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/20544-expected-conversion.json @@ -1 +1 @@ -{"Mappings":[{"RandomW":273523780,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":7693.0,"Objects":[{"StartTime":7693.0,"EndTime":7693.0,"Column":0}]},{"RandomW":2659866685,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273523780,"StartTime":8043.0,"Objects":[{"StartTime":8043.0,"EndTime":8043.0,"Column":1}]},{"RandomW":3083309108,"RandomX":273326509,"RandomY":273523780,"RandomZ":2659866685,"StartTime":8393.0,"Objects":[{"StartTime":8393.0,"EndTime":8393.0,"Column":2}]},{"RandomW":2413296944,"RandomX":2659866685,"RandomY":3083309108,"RandomZ":4072999080,"StartTime":8626.0,"Objects":[{"StartTime":8626.0,"EndTime":8626.0,"Column":2},{"StartTime":8626.0,"EndTime":8626.0,"Column":0}]},{"RandomW":1129322311,"RandomX":3083309108,"RandomY":4072999080,"RandomZ":2413296944,"StartTime":8860.0,"Objects":[{"StartTime":8860.0,"EndTime":8860.0,"Column":2}]},{"RandomW":3365759273,"RandomX":4072999080,"RandomY":2413296944,"RandomZ":1129322311,"StartTime":9326.0,"Objects":[{"StartTime":9326.0,"EndTime":9326.0,"Column":3}]},{"RandomW":315078874,"RandomX":2413296944,"RandomY":1129322311,"RandomZ":3365759273,"StartTime":9560.0,"Objects":[{"StartTime":9560.0,"EndTime":9560.0,"Column":3}]},{"RandomW":583662031,"RandomX":1129322311,"RandomY":3365759273,"RandomZ":315078874,"StartTime":9793.0,"Objects":[{"StartTime":9793.0,"EndTime":9793.0,"Column":3}]},{"RandomW":3789568254,"RandomX":3365759273,"RandomY":315078874,"RandomZ":583662031,"StartTime":10260.0,"Objects":[{"StartTime":10260.0,"EndTime":10260.0,"Column":2}]},{"RandomW":3256340938,"RandomX":315078874,"RandomY":583662031,"RandomZ":3789568254,"StartTime":10493.0,"Objects":[{"StartTime":10493.0,"EndTime":10493.0,"Column":2}]},{"RandomW":2152938451,"RandomX":3789568254,"RandomY":3256340938,"RandomZ":3979976762,"StartTime":10727.0,"Objects":[{"StartTime":10727.0,"EndTime":10727.0,"Column":1},{"StartTime":10727.0,"EndTime":10727.0,"Column":0}]},{"RandomW":1620362479,"RandomX":3256340938,"RandomY":3979976762,"RandomZ":2152938451,"StartTime":11427.0,"Objects":[{"StartTime":11427.0,"EndTime":11427.0,"Column":1}]},{"RandomW":477221046,"RandomX":3979976762,"RandomY":2152938451,"RandomZ":1620362479,"StartTime":11777.0,"Objects":[{"StartTime":11777.0,"EndTime":11777.0,"Column":1}]},{"RandomW":1013554034,"RandomX":2152938451,"RandomY":1620362479,"RandomZ":477221046,"StartTime":12127.0,"Objects":[{"StartTime":12127.0,"EndTime":12127.0,"Column":2}]},{"RandomW":637383311,"RandomX":1620362479,"RandomY":477221046,"RandomZ":1013554034,"StartTime":12360.0,"Objects":[{"StartTime":12360.0,"EndTime":12360.0,"Column":2}]},{"RandomW":3817388387,"RandomX":477221046,"RandomY":1013554034,"RandomZ":637383311,"StartTime":12594.0,"Objects":[{"StartTime":12594.0,"EndTime":12594.0,"Column":3}]},{"RandomW":19695232,"RandomX":637383311,"RandomY":3817388387,"RandomZ":1911435716,"StartTime":13060.0,"Objects":[{"StartTime":13060.0,"EndTime":13060.0,"Column":3},{"StartTime":13060.0,"EndTime":13060.0,"Column":0}]},{"RandomW":3381470688,"RandomX":3817388387,"RandomY":1911435716,"RandomZ":19695232,"StartTime":13294.0,"Objects":[{"StartTime":13294.0,"EndTime":13294.0,"Column":3}]},{"RandomW":1862836779,"RandomX":19695232,"RandomY":3381470688,"RandomZ":1869143571,"StartTime":13527.0,"Objects":[{"StartTime":13527.0,"EndTime":13527.0,"Column":3},{"StartTime":13527.0,"EndTime":13527.0,"Column":5}]},{"RandomW":175452620,"RandomX":3381470688,"RandomY":1869143571,"RandomZ":1862836779,"StartTime":13994.0,"Objects":[{"StartTime":13994.0,"EndTime":13994.0,"Column":4}]},{"RandomW":2859972423,"RandomX":1869143571,"RandomY":1862836779,"RandomZ":175452620,"StartTime":14227.0,"Objects":[{"StartTime":14227.0,"EndTime":14227.0,"Column":4}]},{"RandomW":2210823260,"RandomX":1862836779,"RandomY":175452620,"RandomZ":2859972423,"StartTime":14461.0,"Objects":[{"StartTime":14461.0,"EndTime":14461.0,"Column":5}]},{"RandomW":2851442677,"RandomX":175452620,"RandomY":2859972423,"RandomZ":2210823260,"StartTime":14927.0,"Objects":[{"StartTime":14927.0,"EndTime":16561.0,"Column":1}]},{"RandomW":179122262,"RandomX":2859972423,"RandomY":2210823260,"RandomZ":2851442677,"StartTime":16794.0,"Objects":[{"StartTime":16794.0,"EndTime":18078.0,"Column":0}]},{"RandomW":2917386405,"RandomX":2851442677,"RandomY":179122262,"RandomZ":494367691,"StartTime":18661.0,"Objects":[{"StartTime":18661.0,"EndTime":19127.0,"Column":2}]},{"RandomW":3407923728,"RandomX":494367691,"RandomY":2917386405,"RandomZ":2825679051,"StartTime":19595.0,"Objects":[{"StartTime":19595.0,"EndTime":20061.0,"Column":3}]},{"RandomW":358318928,"RandomX":3407923728,"RandomY":1835995540,"RandomZ":3732560508,"StartTime":20528.0,"Objects":[{"StartTime":20528.0,"EndTime":20994.0,"Column":4},{"StartTime":20528.0,"EndTime":20994.0,"Column":1}]},{"RandomW":3440439960,"RandomX":3732560508,"RandomY":358318928,"RandomZ":3638999969,"StartTime":21462.0,"Objects":[{"StartTime":21462.0,"EndTime":21928.0,"Column":3}]},{"RandomW":3249928444,"RandomX":358318928,"RandomY":3638999969,"RandomZ":3440439960,"StartTime":22395.0,"Objects":[{"StartTime":22395.0,"EndTime":22395.0,"Column":1}]},{"RandomW":3857394572,"RandomX":3440439960,"RandomY":3249928444,"RandomZ":138257049,"StartTime":22628.0,"Objects":[{"StartTime":22628.0,"EndTime":24028.0,"Column":4}]},{"RandomW":2938470811,"RandomX":3249928444,"RandomY":138257049,"RandomZ":3857394572,"StartTime":24262.0,"Objects":[{"StartTime":24262.0,"EndTime":24262.0,"Column":3}]},{"RandomW":3241803419,"RandomX":138257049,"RandomY":3857394572,"RandomZ":2938470811,"StartTime":24495.0,"Objects":[{"StartTime":24495.0,"EndTime":24495.0,"Column":4}]},{"RandomW":620078415,"RandomX":3857394572,"RandomY":2938470811,"RandomZ":3241803419,"StartTime":25195.0,"Objects":[{"StartTime":25195.0,"EndTime":25195.0,"Column":4}]},{"RandomW":2566806806,"RandomX":2938470811,"RandomY":3241803419,"RandomZ":620078415,"StartTime":25429.0,"Objects":[{"StartTime":25429.0,"EndTime":25429.0,"Column":4}]},{"RandomW":458505931,"RandomX":3241803419,"RandomY":620078415,"RandomZ":2566806806,"StartTime":26129.0,"Objects":[{"StartTime":26129.0,"EndTime":26129.0,"Column":3}]},{"RandomW":2629948988,"RandomX":2566806806,"RandomY":458505931,"RandomZ":362272284,"StartTime":26362.0,"Objects":[{"StartTime":26362.0,"EndTime":27762.0,"Column":1}]},{"RandomW":1285940261,"RandomX":362272284,"RandomY":2629948988,"RandomZ":4139597407,"StartTime":27996.0,"Objects":[{"StartTime":27996.0,"EndTime":27996.0,"Column":1},{"StartTime":27996.0,"EndTime":27996.0,"Column":3}]},{"RandomW":3878288539,"RandomX":2629948988,"RandomY":4139597407,"RandomZ":1285940261,"StartTime":28229.0,"Objects":[{"StartTime":28229.0,"EndTime":28229.0,"Column":1}]},{"RandomW":1788551508,"RandomX":1285940261,"RandomY":3878288539,"RandomZ":1976280692,"StartTime":28929.0,"Objects":[{"StartTime":28929.0,"EndTime":28929.0,"Column":1},{"StartTime":28929.0,"EndTime":28929.0,"Column":4}]},{"RandomW":159147246,"RandomX":3878288539,"RandomY":1976280692,"RandomZ":1788551508,"StartTime":29163.0,"Objects":[{"StartTime":29163.0,"EndTime":29163.0,"Column":1}]},{"RandomW":2702806142,"RandomX":1976280692,"RandomY":1788551508,"RandomZ":159147246,"StartTime":29863.0,"Objects":[{"StartTime":29863.0,"EndTime":29863.0,"Column":2}]},{"RandomW":2311677487,"RandomX":1788551508,"RandomY":159147246,"RandomZ":2702806142,"StartTime":30213.0,"Objects":[{"StartTime":30213.0,"EndTime":30213.0,"Column":3}]},{"RandomW":3175953261,"RandomX":2311677487,"RandomY":988506051,"RandomZ":3495571300,"StartTime":30446.0,"Objects":[{"StartTime":30446.0,"EndTime":31146.0,"Column":2}]},{"RandomW":516122535,"RandomX":3495571300,"RandomY":3175953261,"RandomZ":2138555125,"StartTime":31730.0,"Objects":[{"StartTime":31730.0,"EndTime":31730.0,"Column":2},{"StartTime":31730.0,"EndTime":31730.0,"Column":1}]},{"RandomW":534989332,"RandomX":3175953261,"RandomY":2138555125,"RandomZ":516122535,"StartTime":32080.0,"Objects":[{"StartTime":32080.0,"EndTime":32080.0,"Column":2}]},{"RandomW":3420570846,"RandomX":2138555125,"RandomY":516122535,"RandomZ":534989332,"StartTime":32430.0,"Objects":[{"StartTime":32430.0,"EndTime":32430.0,"Column":2}]},{"RandomW":172021565,"RandomX":516122535,"RandomY":534989332,"RandomZ":3420570846,"StartTime":32663.0,"Objects":[{"StartTime":32663.0,"EndTime":32663.0,"Column":2}]},{"RandomW":168636292,"RandomX":3420570846,"RandomY":172021565,"RandomZ":263944077,"StartTime":32780.0,"Objects":[{"StartTime":32780.0,"EndTime":32780.0,"Column":0}]},{"RandomW":3473923375,"RandomX":172021565,"RandomY":263944077,"RandomZ":168636292,"StartTime":33597.0,"Objects":[{"StartTime":33597.0,"EndTime":33597.0,"Column":1}]},{"RandomW":3287941836,"RandomX":263944077,"RandomY":168636292,"RandomZ":3473923375,"StartTime":33947.0,"Objects":[{"StartTime":33947.0,"EndTime":33947.0,"Column":1}]},{"RandomW":1950056015,"RandomX":3473923375,"RandomY":3287941836,"RandomZ":388563489,"StartTime":34180.0,"Objects":[{"StartTime":34180.0,"EndTime":35230.0,"Column":5}]},{"RandomW":3600000321,"RandomX":388563489,"RandomY":1950056015,"RandomZ":3312202562,"StartTime":35464.0,"Objects":[{"StartTime":35464.0,"EndTime":36164.0,"Column":4}]},{"RandomW":647123919,"RandomX":3312202562,"RandomY":3600000321,"RandomZ":2314505656,"StartTime":36397.0,"Objects":[{"StartTime":36397.0,"EndTime":37097.0,"Column":1}]},{"RandomW":3375531720,"RandomX":2314505656,"RandomY":647123919,"RandomZ":2193654396,"StartTime":37564.0,"Objects":[{"StartTime":37564.0,"EndTime":37914.0,"Column":3}]},{"RandomW":2335314869,"RandomX":3834006299,"RandomY":1346269295,"RandomZ":3597388662,"StartTime":38264.0,"Objects":[{"StartTime":38264.0,"EndTime":38264.0,"Column":4},{"StartTime":38380.0,"EndTime":38380.0,"Column":3},{"StartTime":38496.0,"EndTime":38496.0,"Column":4}]},{"RandomW":1564102491,"RandomX":1346269295,"RandomY":3597388662,"RandomZ":2335314869,"StartTime":39197.0,"Objects":[{"StartTime":39197.0,"EndTime":39197.0,"Column":2}]},{"RandomW":1989977426,"RandomX":2335314869,"RandomY":1564102491,"RandomZ":4263834011,"StartTime":39431.0,"Objects":[{"StartTime":39431.0,"EndTime":39431.0,"Column":2},{"StartTime":39431.0,"EndTime":39431.0,"Column":5}]},{"RandomW":3806815718,"RandomX":4263834011,"RandomY":1989977426,"RandomZ":1831387023,"StartTime":39664.0,"Objects":[{"StartTime":39664.0,"EndTime":39664.0,"Column":1},{"StartTime":39664.0,"EndTime":39664.0,"Column":4}]},{"RandomW":999749640,"RandomX":1989977426,"RandomY":1831387023,"RandomZ":3806815718,"StartTime":39898.0,"Objects":[{"StartTime":39898.0,"EndTime":40831.0,"Column":1}]},{"RandomW":2830335005,"RandomX":1831387023,"RandomY":3806815718,"RandomZ":999749640,"StartTime":41298.0,"Objects":[{"StartTime":41298.0,"EndTime":41298.0,"Column":1}]},{"RandomW":2152692291,"RandomX":3806815718,"RandomY":999749640,"RandomZ":2830335005,"StartTime":41648.0,"Objects":[{"StartTime":41648.0,"EndTime":41648.0,"Column":1}]},{"RandomW":1499396089,"RandomX":999749640,"RandomY":2830335005,"RandomZ":2152692291,"StartTime":41998.0,"Objects":[{"StartTime":41998.0,"EndTime":41998.0,"Column":2}]},{"RandomW":3582202466,"RandomX":2830335005,"RandomY":2152692291,"RandomZ":1499396089,"StartTime":42231.0,"Objects":[{"StartTime":42231.0,"EndTime":42231.0,"Column":2}]},{"RandomW":3873754971,"RandomX":2152692291,"RandomY":1499396089,"RandomZ":3582202466,"StartTime":42931.0,"Objects":[{"StartTime":42931.0,"EndTime":42931.0,"Column":4}]},{"RandomW":495070374,"RandomX":1499396089,"RandomY":3582202466,"RandomZ":3873754971,"StartTime":43165.0,"Objects":[{"StartTime":43165.0,"EndTime":43165.0,"Column":4}]},{"RandomW":3016618448,"RandomX":3582202466,"RandomY":3873754971,"RandomZ":495070374,"StartTime":43398.0,"Objects":[{"StartTime":43398.0,"EndTime":43398.0,"Column":4}]},{"RandomW":1177547465,"RandomX":3873754971,"RandomY":495070374,"RandomZ":3016618448,"StartTime":43631.0,"Objects":[{"StartTime":43631.0,"EndTime":43631.0,"Column":3}]},{"RandomW":2255582016,"RandomX":495070374,"RandomY":3016618448,"RandomZ":1177547465,"StartTime":43865.0,"Objects":[{"StartTime":43865.0,"EndTime":43865.0,"Column":3}]},{"RandomW":2325387316,"RandomX":3016618448,"RandomY":1177547465,"RandomZ":2255582016,"StartTime":44098.0,"Objects":[{"StartTime":44098.0,"EndTime":44098.0,"Column":2}]},{"RandomW":1443216326,"RandomX":1177547465,"RandomY":2255582016,"RandomZ":2325387316,"StartTime":44332.0,"Objects":[{"StartTime":44332.0,"EndTime":44332.0,"Column":2}]},{"RandomW":1650665398,"RandomX":2325387316,"RandomY":1443216326,"RandomZ":1871032949,"StartTime":44565.0,"Objects":[{"StartTime":44565.0,"EndTime":44565.0,"Column":1},{"StartTime":44565.0,"EndTime":44565.0,"Column":4}]},{"RandomW":1204166455,"RandomX":1871032949,"RandomY":1650665398,"RandomZ":1013336310,"StartTime":44798.0,"Objects":[{"StartTime":44798.0,"EndTime":45498.0,"Column":3}]},{"RandomW":2125976115,"RandomX":1013336310,"RandomY":1204166455,"RandomZ":93461408,"StartTime":45732.0,"Objects":[{"StartTime":45732.0,"EndTime":46432.0,"Column":5}]},{"RandomW":1391245329,"RandomX":1889010923,"RandomY":131109480,"RandomZ":2450179625,"StartTime":46665.0,"Objects":[{"StartTime":46665.0,"EndTime":47365.0,"Column":0},{"StartTime":46665.0,"EndTime":47365.0,"Column":3}]},{"RandomW":1629740061,"RandomX":2450179625,"RandomY":1391245329,"RandomZ":3806548475,"StartTime":47599.0,"Objects":[{"StartTime":47599.0,"EndTime":47949.0,"Column":4}]},{"RandomW":2462543108,"RandomX":3806548475,"RandomY":1629740061,"RandomZ":2782684574,"StartTime":48532.0,"Objects":[{"StartTime":48532.0,"EndTime":49232.0,"Column":0}]},{"RandomW":1398343675,"RandomX":2462543108,"RandomY":1783863854,"RandomZ":368009293,"StartTime":49466.0,"Objects":[{"StartTime":49466.0,"EndTime":50166.0,"Column":1},{"StartTime":49466.0,"EndTime":50166.0,"Column":3}]},{"RandomW":1655209110,"RandomX":1398343675,"RandomY":4200591321,"RandomZ":204183638,"StartTime":50399.0,"Objects":[{"StartTime":50399.0,"EndTime":51099.0,"Column":0},{"StartTime":50399.0,"EndTime":51099.0,"Column":4}]},{"RandomW":2898792131,"RandomX":1655209110,"RandomY":4183149031,"RandomZ":4235317299,"StartTime":51333.0,"Objects":[{"StartTime":51333.0,"EndTime":52033.0,"Column":5},{"StartTime":51333.0,"EndTime":52033.0,"Column":2}]},{"RandomW":2376440576,"RandomX":4183149031,"RandomY":4235317299,"RandomZ":2898792131,"StartTime":52266.0,"Objects":[{"StartTime":52266.0,"EndTime":52266.0,"Column":0}]},{"RandomW":3672662434,"RandomX":4235317299,"RandomY":2898792131,"RandomZ":2376440576,"StartTime":52499.0,"Objects":[{"StartTime":52499.0,"EndTime":52499.0,"Column":1}]},{"RandomW":1144553308,"RandomX":2376440576,"RandomY":3672662434,"RandomZ":2825568900,"StartTime":52849.0,"Objects":[{"StartTime":52849.0,"EndTime":53199.0,"Column":3}]},{"RandomW":3856961856,"RandomX":3672662434,"RandomY":2825568900,"RandomZ":1144553308,"StartTime":54133.0,"Objects":[{"StartTime":54133.0,"EndTime":54133.0,"Column":3}]},{"RandomW":3856961856,"RandomX":3672662434,"RandomY":2825568900,"RandomZ":1144553308,"StartTime":54366.0,"Objects":[{"StartTime":54366.0,"EndTime":54366.0,"Column":2}]},{"RandomW":3856961856,"RandomX":3672662434,"RandomY":2825568900,"RandomZ":1144553308,"StartTime":54600.0,"Objects":[{"StartTime":54600.0,"EndTime":54600.0,"Column":3}]},{"RandomW":2182646490,"RandomX":1144553308,"RandomY":3856961856,"RandomZ":2090342703,"StartTime":55066.0,"Objects":[{"StartTime":55066.0,"EndTime":55066.0,"Column":2},{"StartTime":55066.0,"EndTime":55066.0,"Column":0}]},{"RandomW":2182646490,"RandomX":1144553308,"RandomY":3856961856,"RandomZ":2090342703,"StartTime":55300.0,"Objects":[{"StartTime":55300.0,"EndTime":55300.0,"Column":5},{"StartTime":55300.0,"EndTime":55300.0,"Column":3}]},{"RandomW":2182646490,"RandomX":1144553308,"RandomY":3856961856,"RandomZ":2090342703,"StartTime":55533.0,"Objects":[{"StartTime":55533.0,"EndTime":55533.0,"Column":2},{"StartTime":55533.0,"EndTime":55533.0,"Column":0}]},{"RandomW":3304208416,"RandomX":2090342703,"RandomY":2182646490,"RandomZ":90031962,"StartTime":56000.0,"Objects":[{"StartTime":56000.0,"EndTime":56233.0,"Column":3}]},{"RandomW":1041697651,"RandomX":90031962,"RandomY":3304208416,"RandomZ":2015301872,"StartTime":56583.0,"Objects":[{"StartTime":56583.0,"EndTime":56583.0,"Column":1},{"StartTime":56583.0,"EndTime":56583.0,"Column":2}]},{"RandomW":3818981880,"RandomX":15037736,"RandomY":2251270868,"RandomZ":2287819377,"StartTime":56700.0,"Objects":[{"StartTime":56700.0,"EndTime":56700.0,"Column":0},{"StartTime":56700.0,"EndTime":56700.0,"Column":4}]},{"RandomW":3368447121,"RandomX":2251270868,"RandomY":2287819377,"RandomZ":3818981880,"StartTime":56933.0,"Objects":[{"StartTime":56933.0,"EndTime":56933.0,"Column":1}]},{"RandomW":860096087,"RandomX":2287819377,"RandomY":3818981880,"RandomZ":3368447121,"StartTime":57867.0,"Objects":[{"StartTime":57867.0,"EndTime":57867.0,"Column":3}]},{"RandomW":860096087,"RandomX":2287819377,"RandomY":3818981880,"RandomZ":3368447121,"StartTime":58100.0,"Objects":[{"StartTime":58100.0,"EndTime":58100.0,"Column":2}]},{"RandomW":860096087,"RandomX":2287819377,"RandomY":3818981880,"RandomZ":3368447121,"StartTime":58334.0,"Objects":[{"StartTime":58334.0,"EndTime":58334.0,"Column":3}]},{"RandomW":1369988252,"RandomX":3818981880,"RandomY":3368447121,"RandomZ":860096087,"StartTime":58800.0,"Objects":[{"StartTime":58800.0,"EndTime":58800.0,"Column":4}]},{"RandomW":1369988252,"RandomX":3818981880,"RandomY":3368447121,"RandomZ":860096087,"StartTime":59034.0,"Objects":[{"StartTime":59034.0,"EndTime":59034.0,"Column":1}]},{"RandomW":1369988252,"RandomX":3818981880,"RandomY":3368447121,"RandomZ":860096087,"StartTime":59267.0,"Objects":[{"StartTime":59267.0,"EndTime":59267.0,"Column":4}]}]} \ No newline at end of file +{"Mappings":[{"RandomW":273523780,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":7693.0,"Objects":[{"StartTime":7693.0,"EndTime":7693.0,"Column":0}]},{"RandomW":2659866685,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273523780,"StartTime":8043.0,"Objects":[{"StartTime":8043.0,"EndTime":8043.0,"Column":1}]},{"RandomW":3083309108,"RandomX":273326509,"RandomY":273523780,"RandomZ":2659866685,"StartTime":8393.0,"Objects":[{"StartTime":8393.0,"EndTime":8393.0,"Column":2}]},{"RandomW":2413296944,"RandomX":2659866685,"RandomY":3083309108,"RandomZ":4072999080,"StartTime":8626.0,"Objects":[{"StartTime":8626.0,"EndTime":8626.0,"Column":2},{"StartTime":8626.0,"EndTime":8626.0,"Column":0}]},{"RandomW":1129322311,"RandomX":3083309108,"RandomY":4072999080,"RandomZ":2413296944,"StartTime":8860.0,"Objects":[{"StartTime":8860.0,"EndTime":8860.0,"Column":3}]},{"RandomW":3365759273,"RandomX":4072999080,"RandomY":2413296944,"RandomZ":1129322311,"StartTime":9326.0,"Objects":[{"StartTime":9326.0,"EndTime":9326.0,"Column":3}]},{"RandomW":315078874,"RandomX":2413296944,"RandomY":1129322311,"RandomZ":3365759273,"StartTime":9560.0,"Objects":[{"StartTime":9560.0,"EndTime":9560.0,"Column":3}]},{"RandomW":583662031,"RandomX":1129322311,"RandomY":3365759273,"RandomZ":315078874,"StartTime":9793.0,"Objects":[{"StartTime":9793.0,"EndTime":9793.0,"Column":3}]},{"RandomW":3789568254,"RandomX":3365759273,"RandomY":315078874,"RandomZ":583662031,"StartTime":10260.0,"Objects":[{"StartTime":10260.0,"EndTime":10260.0,"Column":2}]},{"RandomW":3256340938,"RandomX":315078874,"RandomY":583662031,"RandomZ":3789568254,"StartTime":10493.0,"Objects":[{"StartTime":10493.0,"EndTime":10493.0,"Column":2}]},{"RandomW":2152938451,"RandomX":3789568254,"RandomY":3256340938,"RandomZ":3979976762,"StartTime":10727.0,"Objects":[{"StartTime":10727.0,"EndTime":10727.0,"Column":1},{"StartTime":10727.0,"EndTime":10727.0,"Column":0}]},{"RandomW":1620362479,"RandomX":3256340938,"RandomY":3979976762,"RandomZ":2152938451,"StartTime":11427.0,"Objects":[{"StartTime":11427.0,"EndTime":11427.0,"Column":1}]},{"RandomW":477221046,"RandomX":3979976762,"RandomY":2152938451,"RandomZ":1620362479,"StartTime":11777.0,"Objects":[{"StartTime":11777.0,"EndTime":11777.0,"Column":1}]},{"RandomW":1013554034,"RandomX":2152938451,"RandomY":1620362479,"RandomZ":477221046,"StartTime":12127.0,"Objects":[{"StartTime":12127.0,"EndTime":12127.0,"Column":2}]},{"RandomW":637383311,"RandomX":1620362479,"RandomY":477221046,"RandomZ":1013554034,"StartTime":12360.0,"Objects":[{"StartTime":12360.0,"EndTime":12360.0,"Column":2}]},{"RandomW":3817388387,"RandomX":477221046,"RandomY":1013554034,"RandomZ":637383311,"StartTime":12594.0,"Objects":[{"StartTime":12594.0,"EndTime":12594.0,"Column":3}]},{"RandomW":19695232,"RandomX":637383311,"RandomY":3817388387,"RandomZ":1911435716,"StartTime":13060.0,"Objects":[{"StartTime":13060.0,"EndTime":13060.0,"Column":3},{"StartTime":13060.0,"EndTime":13060.0,"Column":0}]},{"RandomW":3381470688,"RandomX":3817388387,"RandomY":1911435716,"RandomZ":19695232,"StartTime":13294.0,"Objects":[{"StartTime":13294.0,"EndTime":13294.0,"Column":3}]},{"RandomW":1862836779,"RandomX":19695232,"RandomY":3381470688,"RandomZ":1869143571,"StartTime":13527.0,"Objects":[{"StartTime":13527.0,"EndTime":13527.0,"Column":3},{"StartTime":13527.0,"EndTime":13527.0,"Column":5}]},{"RandomW":175452620,"RandomX":3381470688,"RandomY":1869143571,"RandomZ":1862836779,"StartTime":13994.0,"Objects":[{"StartTime":13994.0,"EndTime":13994.0,"Column":4}]},{"RandomW":2859972423,"RandomX":1869143571,"RandomY":1862836779,"RandomZ":175452620,"StartTime":14227.0,"Objects":[{"StartTime":14227.0,"EndTime":14227.0,"Column":4}]},{"RandomW":2210823260,"RandomX":1862836779,"RandomY":175452620,"RandomZ":2859972423,"StartTime":14461.0,"Objects":[{"StartTime":14461.0,"EndTime":14461.0,"Column":5}]},{"RandomW":2851442677,"RandomX":175452620,"RandomY":2859972423,"RandomZ":2210823260,"StartTime":14927.0,"Objects":[{"StartTime":14927.0,"EndTime":16561.0,"Column":1}]},{"RandomW":179122262,"RandomX":2859972423,"RandomY":2210823260,"RandomZ":2851442677,"StartTime":16794.0,"Objects":[{"StartTime":16794.0,"EndTime":18078.0,"Column":0}]},{"RandomW":2917386405,"RandomX":2851442677,"RandomY":179122262,"RandomZ":494367691,"StartTime":18661.0,"Objects":[{"StartTime":18661.0,"EndTime":19127.0,"Column":2}]},{"RandomW":3407923728,"RandomX":494367691,"RandomY":2917386405,"RandomZ":2825679051,"StartTime":19595.0,"Objects":[{"StartTime":19595.0,"EndTime":20061.0,"Column":3}]},{"RandomW":358318928,"RandomX":3407923728,"RandomY":1835995540,"RandomZ":3732560508,"StartTime":20528.0,"Objects":[{"StartTime":20528.0,"EndTime":20994.0,"Column":4},{"StartTime":20528.0,"EndTime":20994.0,"Column":1}]},{"RandomW":3440439960,"RandomX":3732560508,"RandomY":358318928,"RandomZ":3638999969,"StartTime":21462.0,"Objects":[{"StartTime":21462.0,"EndTime":21928.0,"Column":3}]},{"RandomW":3249928444,"RandomX":358318928,"RandomY":3638999969,"RandomZ":3440439960,"StartTime":22395.0,"Objects":[{"StartTime":22395.0,"EndTime":22395.0,"Column":1}]},{"RandomW":3857394572,"RandomX":3440439960,"RandomY":3249928444,"RandomZ":138257049,"StartTime":22628.0,"Objects":[{"StartTime":22628.0,"EndTime":24028.0,"Column":4}]},{"RandomW":2938470811,"RandomX":3249928444,"RandomY":138257049,"RandomZ":3857394572,"StartTime":24262.0,"Objects":[{"StartTime":24262.0,"EndTime":24262.0,"Column":3}]},{"RandomW":3241803419,"RandomX":138257049,"RandomY":3857394572,"RandomZ":2938470811,"StartTime":24495.0,"Objects":[{"StartTime":24495.0,"EndTime":24495.0,"Column":4}]},{"RandomW":620078415,"RandomX":3857394572,"RandomY":2938470811,"RandomZ":3241803419,"StartTime":25195.0,"Objects":[{"StartTime":25195.0,"EndTime":25195.0,"Column":4}]},{"RandomW":2566806806,"RandomX":2938470811,"RandomY":3241803419,"RandomZ":620078415,"StartTime":25429.0,"Objects":[{"StartTime":25429.0,"EndTime":25429.0,"Column":4}]},{"RandomW":458505931,"RandomX":3241803419,"RandomY":620078415,"RandomZ":2566806806,"StartTime":26129.0,"Objects":[{"StartTime":26129.0,"EndTime":26129.0,"Column":3}]},{"RandomW":2629948988,"RandomX":2566806806,"RandomY":458505931,"RandomZ":362272284,"StartTime":26362.0,"Objects":[{"StartTime":26362.0,"EndTime":27762.0,"Column":1}]},{"RandomW":1285940261,"RandomX":362272284,"RandomY":2629948988,"RandomZ":4139597407,"StartTime":27996.0,"Objects":[{"StartTime":27996.0,"EndTime":27996.0,"Column":1},{"StartTime":27996.0,"EndTime":27996.0,"Column":3}]},{"RandomW":3878288539,"RandomX":2629948988,"RandomY":4139597407,"RandomZ":1285940261,"StartTime":28229.0,"Objects":[{"StartTime":28229.0,"EndTime":28229.0,"Column":1}]},{"RandomW":1788551508,"RandomX":1285940261,"RandomY":3878288539,"RandomZ":1976280692,"StartTime":28929.0,"Objects":[{"StartTime":28929.0,"EndTime":28929.0,"Column":1},{"StartTime":28929.0,"EndTime":28929.0,"Column":4}]},{"RandomW":159147246,"RandomX":3878288539,"RandomY":1976280692,"RandomZ":1788551508,"StartTime":29163.0,"Objects":[{"StartTime":29163.0,"EndTime":29163.0,"Column":1}]},{"RandomW":2702806142,"RandomX":1976280692,"RandomY":1788551508,"RandomZ":159147246,"StartTime":29863.0,"Objects":[{"StartTime":29863.0,"EndTime":29863.0,"Column":3}]},{"RandomW":2311677487,"RandomX":1788551508,"RandomY":159147246,"RandomZ":2702806142,"StartTime":30213.0,"Objects":[{"StartTime":30213.0,"EndTime":30213.0,"Column":3}]},{"RandomW":3175953261,"RandomX":2311677487,"RandomY":988506051,"RandomZ":3495571300,"StartTime":30446.0,"Objects":[{"StartTime":30446.0,"EndTime":31146.0,"Column":2}]},{"RandomW":516122535,"RandomX":3495571300,"RandomY":3175953261,"RandomZ":2138555125,"StartTime":31730.0,"Objects":[{"StartTime":31730.0,"EndTime":31730.0,"Column":2},{"StartTime":31730.0,"EndTime":31730.0,"Column":1}]},{"RandomW":534989332,"RandomX":3175953261,"RandomY":2138555125,"RandomZ":516122535,"StartTime":32080.0,"Objects":[{"StartTime":32080.0,"EndTime":32080.0,"Column":2}]},{"RandomW":3420570846,"RandomX":2138555125,"RandomY":516122535,"RandomZ":534989332,"StartTime":32430.0,"Objects":[{"StartTime":32430.0,"EndTime":32430.0,"Column":2}]},{"RandomW":172021565,"RandomX":516122535,"RandomY":534989332,"RandomZ":3420570846,"StartTime":32663.0,"Objects":[{"StartTime":32663.0,"EndTime":32663.0,"Column":2}]},{"RandomW":168636292,"RandomX":3420570846,"RandomY":172021565,"RandomZ":263944077,"StartTime":32780.0,"Objects":[{"StartTime":32780.0,"EndTime":32780.0,"Column":0}]},{"RandomW":3473923375,"RandomX":172021565,"RandomY":263944077,"RandomZ":168636292,"StartTime":33597.0,"Objects":[{"StartTime":33597.0,"EndTime":33597.0,"Column":1}]},{"RandomW":3287941836,"RandomX":263944077,"RandomY":168636292,"RandomZ":3473923375,"StartTime":33947.0,"Objects":[{"StartTime":33947.0,"EndTime":33947.0,"Column":1}]},{"RandomW":1950056015,"RandomX":3473923375,"RandomY":3287941836,"RandomZ":388563489,"StartTime":34180.0,"Objects":[{"StartTime":34180.0,"EndTime":35230.0,"Column":5}]},{"RandomW":3600000321,"RandomX":388563489,"RandomY":1950056015,"RandomZ":3312202562,"StartTime":35464.0,"Objects":[{"StartTime":35464.0,"EndTime":36164.0,"Column":4}]},{"RandomW":647123919,"RandomX":3312202562,"RandomY":3600000321,"RandomZ":2314505656,"StartTime":36397.0,"Objects":[{"StartTime":36397.0,"EndTime":37097.0,"Column":1}]},{"RandomW":3375531720,"RandomX":2314505656,"RandomY":647123919,"RandomZ":2193654396,"StartTime":37564.0,"Objects":[{"StartTime":37564.0,"EndTime":37914.0,"Column":3}]},{"RandomW":2335314869,"RandomX":3834006299,"RandomY":1346269295,"RandomZ":3597388662,"StartTime":38264.0,"Objects":[{"StartTime":38264.0,"EndTime":38264.0,"Column":4},{"StartTime":38380.0,"EndTime":38380.0,"Column":3},{"StartTime":38496.0,"EndTime":38496.0,"Column":4}]},{"RandomW":1564102491,"RandomX":1346269295,"RandomY":3597388662,"RandomZ":2335314869,"StartTime":39197.0,"Objects":[{"StartTime":39197.0,"EndTime":39197.0,"Column":2}]},{"RandomW":1989977426,"RandomX":2335314869,"RandomY":1564102491,"RandomZ":4263834011,"StartTime":39431.0,"Objects":[{"StartTime":39431.0,"EndTime":39431.0,"Column":2},{"StartTime":39431.0,"EndTime":39431.0,"Column":5}]},{"RandomW":3806815718,"RandomX":4263834011,"RandomY":1989977426,"RandomZ":1831387023,"StartTime":39664.0,"Objects":[{"StartTime":39664.0,"EndTime":39664.0,"Column":1},{"StartTime":39664.0,"EndTime":39664.0,"Column":4}]},{"RandomW":999749640,"RandomX":1989977426,"RandomY":1831387023,"RandomZ":3806815718,"StartTime":39898.0,"Objects":[{"StartTime":39898.0,"EndTime":40831.0,"Column":1}]},{"RandomW":2830335005,"RandomX":1831387023,"RandomY":3806815718,"RandomZ":999749640,"StartTime":41298.0,"Objects":[{"StartTime":41298.0,"EndTime":41298.0,"Column":1}]},{"RandomW":2152692291,"RandomX":3806815718,"RandomY":999749640,"RandomZ":2830335005,"StartTime":41648.0,"Objects":[{"StartTime":41648.0,"EndTime":41648.0,"Column":1}]},{"RandomW":1499396089,"RandomX":999749640,"RandomY":2830335005,"RandomZ":2152692291,"StartTime":41998.0,"Objects":[{"StartTime":41998.0,"EndTime":41998.0,"Column":2}]},{"RandomW":3582202466,"RandomX":2830335005,"RandomY":2152692291,"RandomZ":1499396089,"StartTime":42231.0,"Objects":[{"StartTime":42231.0,"EndTime":42231.0,"Column":2}]},{"RandomW":3873754971,"RandomX":2152692291,"RandomY":1499396089,"RandomZ":3582202466,"StartTime":42931.0,"Objects":[{"StartTime":42931.0,"EndTime":42931.0,"Column":4}]},{"RandomW":495070374,"RandomX":1499396089,"RandomY":3582202466,"RandomZ":3873754971,"StartTime":43165.0,"Objects":[{"StartTime":43165.0,"EndTime":43165.0,"Column":4}]},{"RandomW":3016618448,"RandomX":3582202466,"RandomY":3873754971,"RandomZ":495070374,"StartTime":43398.0,"Objects":[{"StartTime":43398.0,"EndTime":43398.0,"Column":4}]},{"RandomW":1177547465,"RandomX":3873754971,"RandomY":495070374,"RandomZ":3016618448,"StartTime":43631.0,"Objects":[{"StartTime":43631.0,"EndTime":43631.0,"Column":3}]},{"RandomW":2255582016,"RandomX":495070374,"RandomY":3016618448,"RandomZ":1177547465,"StartTime":43865.0,"Objects":[{"StartTime":43865.0,"EndTime":43865.0,"Column":3}]},{"RandomW":2325387316,"RandomX":3016618448,"RandomY":1177547465,"RandomZ":2255582016,"StartTime":44098.0,"Objects":[{"StartTime":44098.0,"EndTime":44098.0,"Column":2}]},{"RandomW":1443216326,"RandomX":1177547465,"RandomY":2255582016,"RandomZ":2325387316,"StartTime":44332.0,"Objects":[{"StartTime":44332.0,"EndTime":44332.0,"Column":2}]},{"RandomW":1650665398,"RandomX":2325387316,"RandomY":1443216326,"RandomZ":1871032949,"StartTime":44565.0,"Objects":[{"StartTime":44565.0,"EndTime":44565.0,"Column":1},{"StartTime":44565.0,"EndTime":44565.0,"Column":4}]},{"RandomW":1204166455,"RandomX":1871032949,"RandomY":1650665398,"RandomZ":1013336310,"StartTime":44798.0,"Objects":[{"StartTime":44798.0,"EndTime":45498.0,"Column":3}]},{"RandomW":2125976115,"RandomX":1013336310,"RandomY":1204166455,"RandomZ":93461408,"StartTime":45732.0,"Objects":[{"StartTime":45732.0,"EndTime":46432.0,"Column":5}]},{"RandomW":1391245329,"RandomX":1889010923,"RandomY":131109480,"RandomZ":2450179625,"StartTime":46665.0,"Objects":[{"StartTime":46665.0,"EndTime":47365.0,"Column":0},{"StartTime":46665.0,"EndTime":47365.0,"Column":3}]},{"RandomW":1629740061,"RandomX":2450179625,"RandomY":1391245329,"RandomZ":3806548475,"StartTime":47599.0,"Objects":[{"StartTime":47599.0,"EndTime":47949.0,"Column":4}]},{"RandomW":2462543108,"RandomX":3806548475,"RandomY":1629740061,"RandomZ":2782684574,"StartTime":48532.0,"Objects":[{"StartTime":48532.0,"EndTime":49232.0,"Column":0}]},{"RandomW":1398343675,"RandomX":2462543108,"RandomY":1783863854,"RandomZ":368009293,"StartTime":49466.0,"Objects":[{"StartTime":49466.0,"EndTime":50166.0,"Column":1},{"StartTime":49466.0,"EndTime":50166.0,"Column":3}]},{"RandomW":1655209110,"RandomX":1398343675,"RandomY":4200591321,"RandomZ":204183638,"StartTime":50399.0,"Objects":[{"StartTime":50399.0,"EndTime":51099.0,"Column":0},{"StartTime":50399.0,"EndTime":51099.0,"Column":4}]},{"RandomW":2898792131,"RandomX":1655209110,"RandomY":4183149031,"RandomZ":4235317299,"StartTime":51333.0,"Objects":[{"StartTime":51333.0,"EndTime":52033.0,"Column":5},{"StartTime":51333.0,"EndTime":52033.0,"Column":2}]},{"RandomW":2376440576,"RandomX":4183149031,"RandomY":4235317299,"RandomZ":2898792131,"StartTime":52266.0,"Objects":[{"StartTime":52266.0,"EndTime":52266.0,"Column":0}]},{"RandomW":3672662434,"RandomX":4235317299,"RandomY":2898792131,"RandomZ":2376440576,"StartTime":52499.0,"Objects":[{"StartTime":52499.0,"EndTime":52499.0,"Column":1}]},{"RandomW":1144553308,"RandomX":2376440576,"RandomY":3672662434,"RandomZ":2825568900,"StartTime":52849.0,"Objects":[{"StartTime":52849.0,"EndTime":53199.0,"Column":3}]},{"RandomW":3856961856,"RandomX":3672662434,"RandomY":2825568900,"RandomZ":1144553308,"StartTime":54133.0,"Objects":[{"StartTime":54133.0,"EndTime":54133.0,"Column":3}]},{"RandomW":3856961856,"RandomX":3672662434,"RandomY":2825568900,"RandomZ":1144553308,"StartTime":54366.0,"Objects":[{"StartTime":54366.0,"EndTime":54366.0,"Column":2}]},{"RandomW":3856961856,"RandomX":3672662434,"RandomY":2825568900,"RandomZ":1144553308,"StartTime":54600.0,"Objects":[{"StartTime":54600.0,"EndTime":54600.0,"Column":3}]},{"RandomW":2182646490,"RandomX":1144553308,"RandomY":3856961856,"RandomZ":2090342703,"StartTime":55066.0,"Objects":[{"StartTime":55066.0,"EndTime":55066.0,"Column":2},{"StartTime":55066.0,"EndTime":55066.0,"Column":0}]},{"RandomW":2182646490,"RandomX":1144553308,"RandomY":3856961856,"RandomZ":2090342703,"StartTime":55300.0,"Objects":[{"StartTime":55300.0,"EndTime":55300.0,"Column":5},{"StartTime":55300.0,"EndTime":55300.0,"Column":3}]},{"RandomW":2182646490,"RandomX":1144553308,"RandomY":3856961856,"RandomZ":2090342703,"StartTime":55533.0,"Objects":[{"StartTime":55533.0,"EndTime":55533.0,"Column":2},{"StartTime":55533.0,"EndTime":55533.0,"Column":0}]},{"RandomW":3304208416,"RandomX":2090342703,"RandomY":2182646490,"RandomZ":90031962,"StartTime":56000.0,"Objects":[{"StartTime":56000.0,"EndTime":56233.0,"Column":3}]},{"RandomW":1041697651,"RandomX":90031962,"RandomY":3304208416,"RandomZ":2015301872,"StartTime":56583.0,"Objects":[{"StartTime":56583.0,"EndTime":56583.0,"Column":1},{"StartTime":56583.0,"EndTime":56583.0,"Column":2}]},{"RandomW":3818981880,"RandomX":15037736,"RandomY":2251270868,"RandomZ":2287819377,"StartTime":56700.0,"Objects":[{"StartTime":56700.0,"EndTime":56700.0,"Column":0},{"StartTime":56700.0,"EndTime":56700.0,"Column":4}]},{"RandomW":3368447121,"RandomX":2251270868,"RandomY":2287819377,"RandomZ":3818981880,"StartTime":56933.0,"Objects":[{"StartTime":56933.0,"EndTime":56933.0,"Column":1}]},{"RandomW":860096087,"RandomX":2287819377,"RandomY":3818981880,"RandomZ":3368447121,"StartTime":57867.0,"Objects":[{"StartTime":57867.0,"EndTime":57867.0,"Column":3}]},{"RandomW":860096087,"RandomX":2287819377,"RandomY":3818981880,"RandomZ":3368447121,"StartTime":58100.0,"Objects":[{"StartTime":58100.0,"EndTime":58100.0,"Column":2}]},{"RandomW":860096087,"RandomX":2287819377,"RandomY":3818981880,"RandomZ":3368447121,"StartTime":58334.0,"Objects":[{"StartTime":58334.0,"EndTime":58334.0,"Column":3}]},{"RandomW":1369988252,"RandomX":3818981880,"RandomY":3368447121,"RandomZ":860096087,"StartTime":58800.0,"Objects":[{"StartTime":58800.0,"EndTime":58800.0,"Column":4}]},{"RandomW":1369988252,"RandomX":3818981880,"RandomY":3368447121,"RandomZ":860096087,"StartTime":59034.0,"Objects":[{"StartTime":59034.0,"EndTime":59034.0,"Column":1}]},{"RandomW":1369988252,"RandomX":3818981880,"RandomY":3368447121,"RandomZ":860096087,"StartTime":59267.0,"Objects":[{"StartTime":59267.0,"EndTime":59267.0,"Column":4}]}]} \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/basic-expected-conversion.json b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/basic-expected-conversion.json index 753db99856..a25c8a12ab 100644 --- a/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/basic-expected-conversion.json +++ b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/basic-expected-conversion.json @@ -1,132 +1,168 @@ { - "Mappings": [{ - "RandomW": 2659373485, - "RandomX": 3579807591, - "RandomY": 273326509, - "RandomZ": 272969173, - "StartTime": 500.0, - "Objects": [{ - "StartTime": 500.0, - "EndTime": 2500.0, - "Column": 0 - }, { - "StartTime": 1500.0, - "EndTime": 2500.0, - "Column": 1 - }] - }, { - "RandomW": 3083803045, - "RandomX": 273326509, - "RandomY": 272969173, - "RandomZ": 2659373485, - "StartTime": 3000.0, - "Objects": [{ - "StartTime": 3000.0, - "EndTime": 4000.0, - "Column": 2 - }] - }, { - "RandomW": 4073554232, - "RandomX": 272969173, - "RandomY": 2659373485, - "RandomZ": 3083803045, - "StartTime": 4500.0, - "Objects": [{ - "StartTime": 4500.0, - "EndTime": 5500.0, - "Column": 4 - }] - }, { - "RandomW": 3420401969, - "RandomX": 2659373485, - "RandomY": 3083803045, - "RandomZ": 4073554232, - "StartTime": 6000.0, - "Objects": [{ - "StartTime": 6000.0, - "EndTime": 6500.0, - "Column": 2 - }] - }, { - "RandomW": 1129881182, - "RandomX": 3083803045, - "RandomY": 4073554232, - "RandomZ": 3420401969, - "StartTime": 7000.0, - "Objects": [{ - "StartTime": 7000.0, - "EndTime": 8000.0, - "Column": 2 - }] - }, { - "RandomW": 315568458, - "RandomX": 3420401969, - "RandomY": 1129881182, - "RandomZ": 2358617505, - "StartTime": 8500.0, - "Objects": [{ - "StartTime": 8500.0, - "EndTime": 11000.0, - "Column": 0 - }] - }, { - "RandomW": 548134043, - "RandomX": 1129881182, - "RandomY": 2358617505, - "RandomZ": 315568458, - "StartTime": 11500.0, - "Objects": [{ - "StartTime": 11500.0, - "EndTime": 12000.0, - "Column": 1 - }] - }, { - "RandomW": 3979422122, - "RandomX": 548134043, - "RandomY": 2810584254, - "RandomZ": 2250186050, - "StartTime": 12500.0, - "Objects": [{ - "StartTime": 12500.0, - "EndTime": 16500.0, - "Column": 4 - }] - }, { - "RandomW": 2466283411, - "RandomX": 2810584254, - "RandomY": 2250186050, - "RandomZ": 3979422122, - "StartTime": 17000.0, - "Objects": [{ - "StartTime": 17000.0, - "EndTime": 18000.0, - "Column": 2 - }] - }, { - "RandomW": 83157665, - "RandomX": 2250186050, - "RandomY": 3979422122, - "RandomZ": 2466283411, - "StartTime": 18500.0, - "Objects": [{ - "StartTime": 18500.0, - "EndTime": 19450.0, - "Column": 0 - }] - }, { - "RandomW": 2383087700, - "RandomX": 83157665, - "RandomY": 2055150192, - "RandomZ": 510071020, - "StartTime": 19875.0, - "Objects": [{ - "StartTime": 19875.0, - "EndTime": 23875.0, - "Column": 1 - }, { - "StartTime": 19875.0, - "EndTime": 23875.0, - "Column": 0 - }] - }] + "Mappings": [ + { + "RandomW": 2659373485, + "RandomX": 3579807591, + "RandomY": 273326509, + "RandomZ": 272969173, + "StartTime": 500.0, + "Objects": [ + { + "StartTime": 500.0, + "EndTime": 2500.0, + "Column": 0 + }, + { + "StartTime": 1500.0, + "EndTime": 2500.0, + "Column": 1 + } + ] + }, + { + "RandomW": 3083803045, + "RandomX": 273326509, + "RandomY": 272969173, + "RandomZ": 2659373485, + "StartTime": 3000.0, + "Objects": [ + { + "StartTime": 3000.0, + "EndTime": 4000.0, + "Column": 2 + } + ] + }, + { + "RandomW": 4073554232, + "RandomX": 272969173, + "RandomY": 2659373485, + "RandomZ": 3083803045, + "StartTime": 4500.0, + "Objects": [ + { + "StartTime": 4500.0, + "EndTime": 5500.0, + "Column": 4 + } + ] + }, + { + "RandomW": 3420401969, + "RandomX": 2659373485, + "RandomY": 3083803045, + "RandomZ": 4073554232, + "StartTime": 6000.0, + "Objects": [ + { + "StartTime": 6000.0, + "EndTime": 6500.0, + "Column": 2 + } + ] + }, + { + "RandomW": 1129881182, + "RandomX": 3083803045, + "RandomY": 4073554232, + "RandomZ": 3420401969, + "StartTime": 7000.0, + "Objects": [ + { + "StartTime": 7000.0, + "EndTime": 8000.0, + "Column": 2 + } + ] + }, + { + "RandomW": 315568458, + "RandomX": 3420401969, + "RandomY": 1129881182, + "RandomZ": 2358617505, + "StartTime": 8500.0, + "Objects": [ + { + "StartTime": 8500.0, + "EndTime": 11000.0, + "Column": 0 + } + ] + }, + { + "RandomW": 548134043, + "RandomX": 1129881182, + "RandomY": 2358617505, + "RandomZ": 315568458, + "StartTime": 11500.0, + "Objects": [ + { + "StartTime": 11500.0, + "EndTime": 12000.0, + "Column": 1 + } + ] + }, + { + "RandomW": 3979422122, + "RandomX": 548134043, + "RandomY": 2810584254, + "RandomZ": 2250186050, + "StartTime": 12500.0, + "Objects": [ + { + "StartTime": 12500.0, + "EndTime": 16500.0, + "Column": 4 + } + ] + }, + { + "RandomW": 2466283411, + "RandomX": 2810584254, + "RandomY": 2250186050, + "RandomZ": 3979422122, + "StartTime": 17000.0, + "Objects": [ + { + "StartTime": 17000.0, + "EndTime": 18000.0, + "Column": 2 + } + ] + }, + { + "RandomW": 83157665, + "RandomX": 2250186050, + "RandomY": 3979422122, + "RandomZ": 2466283411, + "StartTime": 18500.0, + "Objects": [ + { + "StartTime": 18500.0, + "EndTime": 19450.0, + "Column": 0 + } + ] + }, + { + "RandomW": 2383087700, + "RandomX": 83157665, + "RandomY": 2055150192, + "RandomZ": 510071020, + "StartTime": 19875.0, + "Objects": [ + { + "StartTime": 19875.0, + "EndTime": 23875.0, + "Column": 1 + }, + { + "StartTime": 19875.0, + "EndTime": 23875.0, + "Column": 0 + } + ] + } + ] } \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/zero-length-slider-expected-conversion.json b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/zero-length-slider-expected-conversion.json index 229760cd1c..400ce9cc1c 100644 --- a/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/zero-length-slider-expected-conversion.json +++ b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/zero-length-slider-expected-conversion.json @@ -1,14 +1,18 @@ { - "Mappings": [{ - "RandomW": 3083084786, - "RandomX": 273326509, - "RandomY": 273553282, - "RandomZ": 2659838971, - "StartTime": 4836, - "Objects": [{ - "StartTime": 4836, - "EndTime": 4836, - "Column": 0 - }] - }] + "Mappings": [ + { + "RandomW": 3083084786, + "RandomX": 273326509, + "RandomY": 273553282, + "RandomZ": 2659838971, + "StartTime": 4836.0, + "Objects": [ + { + "StartTime": 4836.0, + "EndTime": 4836.0, + "Column": 0 + } + ] + } + ] } \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs index e04b44311e..77f93b4ef9 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs @@ -52,18 +52,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// The column. protected int GetColumn(float position, bool allowSpecial = false) { - // Casts to doubles are present here because, although code is originally written as float division, - // the division actually appears to occur on doubles in osu!stable. This is likely a result of - // differences in optimisations between .NET versions due to the presence of the double parameter type of Math.Floor(). - if (allowSpecial && TotalColumns == 8) { const float local_x_divisor = 512f / 7; - return Math.Clamp((int)Math.Floor((double)position / local_x_divisor), 0, 6) + 1; + return Math.Clamp((int)MathF.Floor(position / local_x_divisor), 0, 6) + 1; } float localXDivisor = 512f / TotalColumns; - return Math.Clamp((int)Math.Floor((double)position / localXDivisor), 0, TotalColumns - 1); + return Math.Clamp((int)MathF.Floor(position / localXDivisor), 0, TotalColumns - 1); } /// From 9a83d7be81d7f2d2115ab76283be02c0806525f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 8 Dec 2023 09:27:12 +0100 Subject: [PATCH 1887/4092] Ensure that `SoloScoreInfo` serialisation result does not contain interface members --- .../TestSoloScoreInfoJsonSerialization.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/osu.Game.Tests/Online/TestSoloScoreInfoJsonSerialization.cs b/osu.Game.Tests/Online/TestSoloScoreInfoJsonSerialization.cs index 19bc96c677..509768530f 100644 --- a/osu.Game.Tests/Online/TestSoloScoreInfoJsonSerialization.cs +++ b/osu.Game.Tests/Online/TestSoloScoreInfoJsonSerialization.cs @@ -5,6 +5,7 @@ using Newtonsoft.Json; using NUnit.Framework; using osu.Game.IO.Serialization; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Scoring; using osu.Game.Tests.Resources; namespace osu.Game.Tests.Online @@ -36,5 +37,31 @@ namespace osu.Game.Tests.Online Assert.That(serialised, Contains.Substring("large_tick_hit")); Assert.That(serialised, Contains.Substring("\"rank\":\"S\"")); } + + /// + /// Ensures that the proxy implementations of by + /// do not get serialised to JSON. + /// + [Test] + public void TestScoreSerialisationSkipsInterfaceMembers() + { + var score = SoloScoreInfo.ForSubmission(TestResources.CreateTestScoreInfo()); + + string[] variants = + { + JsonConvert.SerializeObject(score), + score.Serialize() + }; + + foreach (string serialised in variants) + { + Assert.That(serialised, Does.Not.Contain("\"online_id\":")); + Assert.That(serialised, Does.Not.Contain("\"user\":")); + Assert.That(serialised, Does.Not.Contain("\"date\":")); + Assert.That(serialised, Does.Not.Contain("\"legacy_online_id\":")); + Assert.That(serialised, Does.Not.Contain("\"beatmap\":")); + Assert.That(serialised, Does.Not.Contain("\"ruleset\":")); + } + } } } From 81fe15f2882e54d0041538c1af5b16a1748b347f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 9 Dec 2023 15:39:54 +0900 Subject: [PATCH 1888/4092] Fix osu!mania converted key count edge cases --- .../ManiaBeatmapConversionTest.cs | 1 + .../Beatmaps/1450162-expected-conversion.json | 1 + .../Resources/Testing/Beatmaps/1450162.osu | 297 ++++++++++++++++++ .../Beatmaps/ManiaBeatmapConverter.cs | 5 +- 4 files changed, 303 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/1450162-expected-conversion.json create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/1450162.osu diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs index 435d5e737e..609c2e8953 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs @@ -24,6 +24,7 @@ namespace osu.Game.Rulesets.Mania.Tests [TestCase("zero-length-slider")] [TestCase("20544")] [TestCase("100374")] + [TestCase("1450162")] public void Test(string name) => base.Test(name); protected override IEnumerable CreateConvertValue(HitObject hitObject) diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/1450162-expected-conversion.json b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/1450162-expected-conversion.json new file mode 100644 index 0000000000..4981951267 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/1450162-expected-conversion.json @@ -0,0 +1 @@ +{"Mappings":[{"RandomW":2659430625,"RandomX":3579807591,"RandomY":273326509,"RandomZ":272911513,"StartTime":1107.0,"Objects":[{"StartTime":1107.0,"EndTime":1838.0,"Column":1}]},{"RandomW":4073513076,"RandomX":272911513,"RandomY":2659430625,"RandomZ":3083761897,"StartTime":2570.0,"Objects":[{"StartTime":2570.0,"EndTime":2935.0,"Column":6}]},{"RandomW":1129971314,"RandomX":3083761897,"RandomY":4073513076,"RandomZ":3235797552,"StartTime":3302.0,"Objects":[{"StartTime":3302.0,"EndTime":3667.0,"Column":3}]},{"RandomW":315510790,"RandomX":3235797552,"RandomY":1129971314,"RandomZ":2274676672,"StartTime":4033.0,"Objects":[{"StartTime":4033.0,"EndTime":4764.0,"Column":1}]},{"RandomW":2899658679,"RandomX":2274676672,"RandomY":315510790,"RandomZ":552830901,"StartTime":5497.0,"Objects":[{"StartTime":5497.0,"EndTime":5862.0,"Column":2}]},{"RandomW":3979364583,"RandomX":552830901,"RandomY":2899658679,"RandomZ":2367584034,"StartTime":6228.0,"Objects":[{"StartTime":6228.0,"EndTime":6593.0,"Column":5}]},{"RandomW":1470933435,"RandomX":2367584034,"RandomY":3979364583,"RandomZ":1363326171,"StartTime":6960.0,"Objects":[{"StartTime":6960.0,"EndTime":7142.0,"Column":4}]},{"RandomW":695558923,"RandomX":3979364583,"RandomY":1363326171,"RandomZ":1470933435,"StartTime":7326.0,"Objects":[{"StartTime":7326.0,"EndTime":7326.0,"Column":2},{"StartTime":7326.0,"EndTime":7326.0,"Column":3}]},{"RandomW":47047112,"RandomX":1470933435,"RandomY":695558923,"RandomZ":1181573554,"StartTime":7509.0,"Objects":[{"StartTime":7509.0,"EndTime":7691.0,"Column":0}]},{"RandomW":807301467,"RandomX":695558923,"RandomY":1181573554,"RandomZ":47047112,"StartTime":7875.0,"Objects":[{"StartTime":7875.0,"EndTime":7875.0,"Column":5}]},{"RandomW":2679940725,"RandomX":47047112,"RandomY":807301467,"RandomZ":3002147176,"StartTime":8058.0,"Objects":[{"StartTime":8058.0,"EndTime":8240.0,"Column":1}]},{"RandomW":176449914,"RandomX":2679940725,"RandomY":4061321195,"RandomZ":826668123,"StartTime":8424.0,"Objects":[{"StartTime":8424.0,"EndTime":8789.0,"Column":2},{"StartTime":8424.0,"EndTime":8789.0,"Column":0}]},{"RandomW":3697485076,"RandomX":347653435,"RandomY":172035291,"RandomZ":598178640,"StartTime":8972.0,"Objects":[{"StartTime":8972.0,"EndTime":9154.0,"Column":1},{"StartTime":8972.0,"EndTime":9154.0,"Column":5}]},{"RandomW":237023934,"RandomX":172035291,"RandomY":598178640,"RandomZ":3697485076,"StartTime":9338.0,"Objects":[{"StartTime":9338.0,"EndTime":9338.0,"Column":4},{"StartTime":9338.0,"EndTime":9338.0,"Column":5}]},{"RandomW":201670773,"RandomX":598178640,"RandomY":3697485076,"RandomZ":237023934,"StartTime":9521.0,"Objects":[{"StartTime":9521.0,"EndTime":9521.0,"Column":3}]},{"RandomW":3522038595,"RandomX":237023934,"RandomY":201670773,"RandomZ":341886814,"StartTime":9887.0,"Objects":[{"StartTime":9887.0,"EndTime":10069.0,"Column":4}]},{"RandomW":3662734978,"RandomX":201670773,"RandomY":341886814,"RandomZ":3522038595,"StartTime":10253.0,"Objects":[{"StartTime":10253.0,"EndTime":10253.0,"Column":3},{"StartTime":10253.0,"EndTime":10253.0,"Column":4}]},{"RandomW":4235203413,"RandomX":341886814,"RandomY":3522038595,"RandomZ":3662734978,"StartTime":10436.0,"Objects":[{"StartTime":10436.0,"EndTime":10436.0,"Column":2},{"StartTime":10436.0,"EndTime":10436.0,"Column":3}]},{"RandomW":3996672434,"RandomX":3522038595,"RandomY":3662734978,"RandomZ":4235203413,"StartTime":10619.0,"Objects":[{"StartTime":10619.0,"EndTime":10619.0,"Column":1},{"StartTime":10619.0,"EndTime":10619.0,"Column":2}]},{"RandomW":1328405285,"RandomX":3662734978,"RandomY":4235203413,"RandomZ":3996672434,"StartTime":10802.0,"Objects":[{"StartTime":10802.0,"EndTime":10802.0,"Column":0},{"StartTime":10802.0,"EndTime":10802.0,"Column":1}]},{"RandomW":303317172,"RandomX":4235203413,"RandomY":3996672434,"RandomZ":1328405285,"StartTime":10985.0,"Objects":[{"StartTime":10985.0,"EndTime":10985.0,"Column":1},{"StartTime":10985.0,"EndTime":10985.0,"Column":2}]},{"RandomW":1854018328,"RandomX":3996672434,"RandomY":1328405285,"RandomZ":303317172,"StartTime":11167.0,"Objects":[{"StartTime":11167.0,"EndTime":11167.0,"Column":2},{"StartTime":11167.0,"EndTime":11167.0,"Column":3}]},{"RandomW":1134221963,"RandomX":1328405285,"RandomY":303317172,"RandomZ":1854018328,"StartTime":12814.0,"Objects":[{"StartTime":12814.0,"EndTime":12814.0,"Column":1}]},{"RandomW":2894789541,"RandomX":1134221963,"RandomY":1649399086,"RandomZ":3538823219,"StartTime":13180.0,"Objects":[{"StartTime":13180.0,"EndTime":13362.0,"Column":4},{"StartTime":13180.0,"EndTime":13362.0,"Column":2}]},{"RandomW":2259123626,"RandomX":2894789541,"RandomY":961618493,"RandomZ":631989916,"StartTime":13546.0,"Objects":[{"StartTime":13546.0,"EndTime":13728.0,"Column":0}]},{"RandomW":3004853499,"RandomX":2259123626,"RandomY":2097932552,"RandomZ":3455806558,"StartTime":13911.0,"Objects":[{"StartTime":13911.0,"EndTime":14093.0,"Column":4},{"StartTime":13911.0,"EndTime":14093.0,"Column":2}]},{"RandomW":1511929919,"RandomX":250420511,"RandomY":747435619,"RandomZ":973338160,"StartTime":14277.0,"Objects":[{"StartTime":14277.0,"EndTime":14277.0,"Column":5},{"StartTime":14277.0,"EndTime":14277.0,"Column":6},{"StartTime":14459.0,"EndTime":14459.0,"Column":2},{"StartTime":14459.0,"EndTime":14459.0,"Column":3},{"StartTime":14641.0,"EndTime":14641.0,"Column":3},{"StartTime":14641.0,"EndTime":14641.0,"Column":4}]},{"RandomW":1997079940,"RandomX":973338160,"RandomY":1511929919,"RandomZ":1014879110,"StartTime":14826.0,"Objects":[{"StartTime":14826.0,"EndTime":15191.0,"Column":6}]},{"RandomW":735692759,"RandomX":1997079940,"RandomY":1386139427,"RandomZ":4192918159,"StartTime":15375.0,"Objects":[{"StartTime":15375.0,"EndTime":15557.0,"Column":2}]},{"RandomW":348373517,"RandomX":1386139427,"RandomY":4192918159,"RandomZ":735692759,"StartTime":15741.0,"Objects":[{"StartTime":15741.0,"EndTime":15741.0,"Column":5},{"StartTime":15741.0,"EndTime":15741.0,"Column":6}]},{"RandomW":521239132,"RandomX":735692759,"RandomY":348373517,"RandomZ":2961240161,"StartTime":16106.0,"Objects":[{"StartTime":16106.0,"EndTime":16288.0,"Column":1}]},{"RandomW":1199465075,"RandomX":521239132,"RandomY":4195606806,"RandomZ":4039804915,"StartTime":16472.0,"Objects":[{"StartTime":16472.0,"EndTime":16654.0,"Column":6},{"StartTime":16472.0,"EndTime":16654.0,"Column":3}]},{"RandomW":3059180408,"RandomX":4039804915,"RandomY":1199465075,"RandomZ":3542692698,"StartTime":16838.0,"Objects":[{"StartTime":16838.0,"EndTime":17020.0,"Column":2}]},{"RandomW":834119344,"RandomX":302423902,"RandomY":2799635095,"RandomZ":1022775029,"StartTime":17204.0,"Objects":[{"StartTime":17204.0,"EndTime":17204.0,"Column":4},{"StartTime":17204.0,"EndTime":17204.0,"Column":5},{"StartTime":17386.0,"EndTime":17386.0,"Column":2},{"StartTime":17386.0,"EndTime":17386.0,"Column":3},{"StartTime":17568.0,"EndTime":17568.0,"Column":3},{"StartTime":17568.0,"EndTime":17568.0,"Column":4}]},{"RandomW":1236797567,"RandomX":1022775029,"RandomY":834119344,"RandomZ":393032631,"StartTime":17753.0,"Objects":[{"StartTime":17753.0,"EndTime":18118.0,"Column":4}]},{"RandomW":892840048,"RandomX":1236797567,"RandomY":3350685275,"RandomZ":1270471227,"StartTime":18302.0,"Objects":[{"StartTime":18302.0,"EndTime":18484.0,"Column":2}]},{"RandomW":3233581364,"RandomX":1270471227,"RandomY":892840048,"RandomZ":3158680921,"StartTime":18667.0,"Objects":[{"StartTime":18667.0,"EndTime":19032.0,"Column":3}]},{"RandomW":1163000602,"RandomX":892840048,"RandomY":3158680921,"RandomZ":3233581364,"StartTime":19216.0,"Objects":[{"StartTime":19216.0,"EndTime":19216.0,"Column":2}]},{"RandomW":1548989545,"RandomX":3233581364,"RandomY":1163000602,"RandomZ":3450712040,"StartTime":19399.0,"Objects":[{"StartTime":19399.0,"EndTime":19581.0,"Column":5}]},{"RandomW":313779584,"RandomX":1548989545,"RandomY":2021811198,"RandomZ":2999045855,"StartTime":19765.0,"Objects":[{"StartTime":19765.0,"EndTime":19947.0,"Column":2},{"StartTime":19765.0,"EndTime":19947.0,"Column":1}]},{"RandomW":3548572483,"RandomX":2021811198,"RandomY":2999045855,"RandomZ":313779584,"StartTime":20131.0,"Objects":[{"StartTime":20131.0,"EndTime":20131.0,"Column":6}]},{"RandomW":75459001,"RandomX":313779584,"RandomY":3548572483,"RandomZ":3094675294,"StartTime":20314.0,"Objects":[{"StartTime":20314.0,"EndTime":20496.0,"Column":0}]},{"RandomW":1299261902,"RandomX":3094675294,"RandomY":75459001,"RandomZ":2305626963,"StartTime":20680.0,"Objects":[{"StartTime":20680.0,"EndTime":21045.0,"Column":4}]},{"RandomW":2905421941,"RandomX":2305626963,"RandomY":1299261902,"RandomZ":1390453041,"StartTime":21228.0,"Objects":[{"StartTime":21228.0,"EndTime":21410.0,"Column":2}]},{"RandomW":2294300184,"RandomX":1390453041,"RandomY":2905421941,"RandomZ":1278955784,"StartTime":21594.0,"Objects":[{"StartTime":21594.0,"EndTime":22325.0,"Column":6},{"StartTime":21594.0,"EndTime":21594.0,"Column":4},{"StartTime":21959.0,"EndTime":21959.0,"Column":4},{"StartTime":22324.0,"EndTime":22324.0,"Column":4}]},{"RandomW":3749637912,"RandomX":2905421941,"RandomY":1278955784,"RandomZ":2294300184,"StartTime":22509.0,"Objects":[{"StartTime":22509.0,"EndTime":22509.0,"Column":6},{"StartTime":22509.0,"EndTime":22509.0,"Column":0}]},{"RandomW":753327495,"RandomX":458525202,"RandomY":2373004129,"RandomZ":80278569,"StartTime":22692.0,"Objects":[{"StartTime":22692.0,"EndTime":22874.0,"Column":2}]},{"RandomW":3562609217,"RandomX":753327495,"RandomY":2472396307,"RandomZ":2540952890,"StartTime":23058.0,"Objects":[{"StartTime":23058.0,"EndTime":23058.0,"Column":1},{"StartTime":23058.0,"EndTime":23058.0,"Column":5}]},{"RandomW":3562609217,"RandomX":753327495,"RandomY":2472396307,"RandomZ":2540952890,"StartTime":23241.0,"Objects":[{"StartTime":23241.0,"EndTime":23241.0,"Column":5},{"StartTime":23241.0,"EndTime":23241.0,"Column":1}]},{"RandomW":3009004844,"RandomX":2540952890,"RandomY":3562609217,"RandomZ":3460951976,"StartTime":23606.0,"Objects":[{"StartTime":23606.0,"EndTime":23971.0,"Column":2}]},{"RandomW":1524995266,"RandomX":3133817968,"RandomY":2791164538,"RandomZ":669533622,"StartTime":24155.0,"Objects":[{"StartTime":24155.0,"EndTime":24337.0,"Column":4}]},{"RandomW":2667749121,"RandomX":1524995266,"RandomY":3001332266,"RandomZ":4204965910,"StartTime":24521.0,"Objects":[{"StartTime":24521.0,"EndTime":24521.0,"Column":0},{"StartTime":24521.0,"EndTime":24521.0,"Column":6}]},{"RandomW":1230014889,"RandomX":2127937865,"RandomY":2434329733,"RandomZ":443126576,"StartTime":24704.0,"Objects":[{"StartTime":24704.0,"EndTime":25069.0,"Column":1},{"StartTime":24704.0,"EndTime":25069.0,"Column":4}]},{"RandomW":1409501366,"RandomX":2573194819,"RandomY":3480583465,"RandomZ":2580776932,"StartTime":25253.0,"Objects":[{"StartTime":25253.0,"EndTime":25253.0,"Column":0},{"StartTime":25253.0,"EndTime":25253.0,"Column":1},{"StartTime":25435.0,"EndTime":25435.0,"Column":4},{"StartTime":25435.0,"EndTime":25435.0,"Column":5},{"StartTime":25617.0,"EndTime":25617.0,"Column":1},{"StartTime":25617.0,"EndTime":25617.0,"Column":2}]},{"RandomW":864641467,"RandomX":3480583465,"RandomY":2580776932,"RandomZ":1409501366,"StartTime":25802.0,"Objects":[{"StartTime":25802.0,"EndTime":25802.0,"Column":1}]},{"RandomW":1467076310,"RandomX":2580776932,"RandomY":1409501366,"RandomZ":864641467,"StartTime":25985.0,"Objects":[{"StartTime":25985.0,"EndTime":25985.0,"Column":2}]},{"RandomW":479214438,"RandomX":864641467,"RandomY":1467076310,"RandomZ":1385729915,"StartTime":26167.0,"Objects":[{"StartTime":26167.0,"EndTime":26532.0,"Column":1}]},{"RandomW":3054605916,"RandomX":1684801014,"RandomY":3182588115,"RandomZ":734516041,"StartTime":26716.0,"Objects":[{"StartTime":26716.0,"EndTime":26716.0,"Column":4},{"StartTime":26716.0,"EndTime":26716.0,"Column":2},{"StartTime":26898.0,"EndTime":26898.0,"Column":3},{"StartTime":26898.0,"EndTime":26898.0,"Column":1},{"StartTime":27080.0,"EndTime":27080.0,"Column":2},{"StartTime":27080.0,"EndTime":27080.0,"Column":6}]},{"RandomW":2992010973,"RandomX":3182588115,"RandomY":734516041,"RandomZ":3054605916,"StartTime":27265.0,"Objects":[{"StartTime":27265.0,"EndTime":27265.0,"Column":2}]},{"RandomW":2622274732,"RandomX":734516041,"RandomY":3054605916,"RandomZ":2992010973,"StartTime":27448.0,"Objects":[{"StartTime":27448.0,"EndTime":27448.0,"Column":3}]},{"RandomW":3013455357,"RandomX":2992010973,"RandomY":2622274732,"RandomZ":2298767863,"StartTime":27631.0,"Objects":[{"StartTime":27631.0,"EndTime":27996.0,"Column":2}]},{"RandomW":2994521549,"RandomX":2622274732,"RandomY":2298767863,"RandomZ":3013455357,"StartTime":28180.0,"Objects":[{"StartTime":28180.0,"EndTime":28180.0,"Column":5}]},{"RandomW":3949426364,"RandomX":1261217522,"RandomY":3788322225,"RandomZ":3210845744,"StartTime":28363.0,"Objects":[{"StartTime":28363.0,"EndTime":28363.0,"Column":5},{"StartTime":28363.0,"EndTime":28363.0,"Column":2},{"StartTime":28545.0,"EndTime":28545.0,"Column":5},{"StartTime":28545.0,"EndTime":28545.0,"Column":2},{"StartTime":28727.0,"EndTime":28727.0,"Column":3},{"StartTime":28727.0,"EndTime":28727.0,"Column":6}]},{"RandomW":1304069042,"RandomX":3210845744,"RandomY":3949426364,"RandomZ":3310503444,"StartTime":28911.0,"Objects":[{"StartTime":28911.0,"EndTime":29276.0,"Column":4}]},{"RandomW":781934546,"RandomX":3310503444,"RandomY":1304069042,"RandomZ":4271440939,"StartTime":29460.0,"Objects":[{"StartTime":29460.0,"EndTime":29460.0,"Column":6},{"StartTime":29460.0,"EndTime":29460.0,"Column":2}]},{"RandomW":3592330498,"RandomX":781934546,"RandomY":2041503475,"RandomZ":3767559527,"StartTime":29643.0,"Objects":[{"StartTime":29643.0,"EndTime":30008.0,"Column":5},{"StartTime":29643.0,"EndTime":30008.0,"Column":4}]},{"RandomW":579808732,"RandomX":2041503475,"RandomY":3767559527,"RandomZ":3592330498,"StartTime":30192.0,"Objects":[{"StartTime":30192.0,"EndTime":30192.0,"Column":4}]},{"RandomW":769209912,"RandomX":3767559527,"RandomY":3592330498,"RandomZ":579808732,"StartTime":30375.0,"Objects":[{"StartTime":30375.0,"EndTime":30375.0,"Column":4}]},{"RandomW":1825941494,"RandomX":579808732,"RandomY":769209912,"RandomZ":1308743097,"StartTime":30558.0,"Objects":[{"StartTime":30558.0,"EndTime":30923.0,"Column":5}]},{"RandomW":1378114054,"RandomX":930055805,"RandomY":3554877022,"RandomZ":2467280262,"StartTime":31106.0,"Objects":[{"StartTime":31106.0,"EndTime":31106.0,"Column":2},{"StartTime":31106.0,"EndTime":31106.0,"Column":5},{"StartTime":31288.0,"EndTime":31288.0,"Column":4},{"StartTime":31288.0,"EndTime":31288.0,"Column":1},{"StartTime":31470.0,"EndTime":31470.0,"Column":1},{"StartTime":31470.0,"EndTime":31470.0,"Column":4}]},{"RandomW":422797905,"RandomX":3554877022,"RandomY":2467280262,"RandomZ":1378114054,"StartTime":31655.0,"Objects":[{"StartTime":31655.0,"EndTime":31655.0,"Column":1}]},{"RandomW":3538525895,"RandomX":2467280262,"RandomY":1378114054,"RandomZ":422797905,"StartTime":31838.0,"Objects":[{"StartTime":31838.0,"EndTime":31838.0,"Column":0}]},{"RandomW":1277180769,"RandomX":422797905,"RandomY":3538525895,"RandomZ":1017422489,"StartTime":32021.0,"Objects":[{"StartTime":32021.0,"EndTime":32386.0,"Column":4}]},{"RandomW":69027963,"RandomX":3464755550,"RandomY":1342331375,"RandomZ":1235978524,"StartTime":32570.0,"Objects":[{"StartTime":32570.0,"EndTime":32752.0,"Column":0}]},{"RandomW":3582265519,"RandomX":1342331375,"RandomY":1235978524,"RandomZ":69027963,"StartTime":32936.0,"Objects":[{"StartTime":32936.0,"EndTime":32936.0,"Column":2},{"StartTime":32936.0,"EndTime":32936.0,"Column":3}]},{"RandomW":2197579333,"RandomX":69027963,"RandomY":3582265519,"RandomZ":2534080209,"StartTime":33302.0,"Objects":[{"StartTime":33302.0,"EndTime":33667.0,"Column":0}]},{"RandomW":820123404,"RandomX":1816967409,"RandomY":2440103335,"RandomZ":1364041006,"StartTime":33850.0,"Objects":[{"StartTime":33850.0,"EndTime":34215.0,"Column":4},{"StartTime":33850.0,"EndTime":34215.0,"Column":2}]},{"RandomW":962636497,"RandomX":2440103335,"RandomY":1364041006,"RandomZ":820123404,"StartTime":34399.0,"Objects":[{"StartTime":34399.0,"EndTime":34399.0,"Column":3},{"StartTime":34399.0,"EndTime":34399.0,"Column":4}]},{"RandomW":539348071,"RandomX":1364041006,"RandomY":820123404,"RandomZ":962636497,"StartTime":34582.0,"Objects":[{"StartTime":34582.0,"EndTime":34582.0,"Column":4}]},{"RandomW":1036431212,"RandomX":962636497,"RandomY":539348071,"RandomZ":498893216,"StartTime":34765.0,"Objects":[{"StartTime":34765.0,"EndTime":34947.0,"Column":3}]},{"RandomW":30194727,"RandomX":539348071,"RandomY":498893216,"RandomZ":1036431212,"StartTime":35131.0,"Objects":[{"StartTime":35131.0,"EndTime":35131.0,"Column":4},{"StartTime":35131.0,"EndTime":35131.0,"Column":5}]},{"RandomW":4140580700,"RandomX":1036431212,"RandomY":30194727,"RandomZ":260312717,"StartTime":35314.0,"Objects":[{"StartTime":35314.0,"EndTime":35496.0,"Column":6}]},{"RandomW":4269364006,"RandomX":30194727,"RandomY":260312717,"RandomZ":4140580700,"StartTime":35680.0,"Objects":[{"StartTime":35680.0,"EndTime":35680.0,"Column":5},{"StartTime":35680.0,"EndTime":35680.0,"Column":6}]},{"RandomW":3052364007,"RandomX":4140580700,"RandomY":4269364006,"RandomZ":2586895690,"StartTime":35863.0,"Objects":[{"StartTime":35863.0,"EndTime":36045.0,"Column":2}]},{"RandomW":575578073,"RandomX":4269364006,"RandomY":2586895690,"RandomZ":3052364007,"StartTime":36228.0,"Objects":[{"StartTime":36228.0,"EndTime":36228.0,"Column":4}]},{"RandomW":379197653,"RandomX":2586895690,"RandomY":3052364007,"RandomZ":575578073,"StartTime":36411.0,"Objects":[{"StartTime":36411.0,"EndTime":36411.0,"Column":3}]},{"RandomW":2472409868,"RandomX":379197653,"RandomY":194885113,"RandomZ":3317367861,"StartTime":36594.0,"Objects":[{"StartTime":36594.0,"EndTime":36776.0,"Column":1}]},{"RandomW":3530386304,"RandomX":1439106306,"RandomY":3004383294,"RandomZ":2928959685,"StartTime":36960.0,"Objects":[{"StartTime":36960.0,"EndTime":36960.0,"Column":1},{"StartTime":36960.0,"EndTime":36960.0,"Column":5},{"StartTime":37142.0,"EndTime":37142.0,"Column":2},{"StartTime":37142.0,"EndTime":37142.0,"Column":6},{"StartTime":37324.0,"EndTime":37324.0,"Column":2},{"StartTime":37324.0,"EndTime":37324.0,"Column":6}]},{"RandomW":3220147162,"RandomX":3004383294,"RandomY":2928959685,"RandomZ":3530386304,"StartTime":37509.0,"Objects":[{"StartTime":37509.0,"EndTime":37509.0,"Column":2}]},{"RandomW":2530492073,"RandomX":2928959685,"RandomY":3530386304,"RandomZ":3220147162,"StartTime":37692.0,"Objects":[{"StartTime":37692.0,"EndTime":37692.0,"Column":1}]},{"RandomW":2605446910,"RandomX":3530386304,"RandomY":3220147162,"RandomZ":2530492073,"StartTime":37875.0,"Objects":[{"StartTime":37875.0,"EndTime":37875.0,"Column":2}]},{"RandomW":3786494373,"RandomX":2530492073,"RandomY":2605446910,"RandomZ":583253884,"StartTime":38058.0,"Objects":[{"StartTime":38058.0,"EndTime":38240.0,"Column":5}]},{"RandomW":1188028287,"RandomX":3601275468,"RandomY":312474208,"RandomZ":764976912,"StartTime":38424.0,"Objects":[{"StartTime":38424.0,"EndTime":38424.0,"Column":4},{"StartTime":38424.0,"EndTime":38424.0,"Column":2},{"StartTime":38606.0,"EndTime":38606.0,"Column":1},{"StartTime":38606.0,"EndTime":38606.0,"Column":5},{"StartTime":38788.0,"EndTime":38788.0,"Column":2},{"StartTime":38788.0,"EndTime":38788.0,"Column":6}]},{"RandomW":2824132752,"RandomX":312474208,"RandomY":764976912,"RandomZ":1188028287,"StartTime":38972.0,"Objects":[{"StartTime":38972.0,"EndTime":38972.0,"Column":3}]},{"RandomW":1173715712,"RandomX":764976912,"RandomY":1188028287,"RandomZ":2824132752,"StartTime":39155.0,"Objects":[{"StartTime":39155.0,"EndTime":39155.0,"Column":4}]},{"RandomW":2490370662,"RandomX":2824132752,"RandomY":1173715712,"RandomZ":2893810865,"StartTime":39338.0,"Objects":[{"StartTime":39338.0,"EndTime":39520.0,"Column":1}]},{"RandomW":1949144326,"RandomX":2893810865,"RandomY":2490370662,"RandomZ":2599342112,"StartTime":39704.0,"Objects":[{"StartTime":39704.0,"EndTime":40069.0,"Column":6}]},{"RandomW":743381221,"RandomX":2599342112,"RandomY":1949144326,"RandomZ":947390134,"StartTime":40253.0,"Objects":[{"StartTime":40253.0,"EndTime":40435.0,"Column":2}]},{"RandomW":3629226534,"RandomX":947390134,"RandomY":743381221,"RandomZ":3234636444,"StartTime":40619.0,"Objects":[{"StartTime":40619.0,"EndTime":40984.0,"Column":4}]},{"RandomW":551844396,"RandomX":743381221,"RandomY":3234636444,"RandomZ":3629226534,"StartTime":41167.0,"Objects":[{"StartTime":41167.0,"EndTime":41167.0,"Column":3}]},{"RandomW":2240897560,"RandomX":551844396,"RandomY":1949877989,"RandomZ":3510981308,"StartTime":41350.0,"Objects":[{"StartTime":41350.0,"EndTime":41532.0,"Column":4},{"StartTime":41350.0,"EndTime":41532.0,"Column":0}]},{"RandomW":874163267,"RandomX":3510981308,"RandomY":2240897560,"RandomZ":2259115420,"StartTime":41716.0,"Objects":[{"StartTime":41716.0,"EndTime":41898.0,"Column":2}]},{"RandomW":3476146382,"RandomX":2240897560,"RandomY":2259115420,"RandomZ":874163267,"StartTime":42082.0,"Objects":[{"StartTime":42082.0,"EndTime":42082.0,"Column":4}]},{"RandomW":2101943428,"RandomX":874163267,"RandomY":3476146382,"RandomZ":3250516626,"StartTime":42265.0,"Objects":[{"StartTime":42265.0,"EndTime":42447.0,"Column":6}]},{"RandomW":2630934490,"RandomX":3476146382,"RandomY":3250516626,"RandomZ":2101943428,"StartTime":42631.0,"Objects":[{"StartTime":42631.0,"EndTime":42631.0,"Column":4}]},{"RandomW":3294029476,"RandomX":3722838838,"RandomY":3959050362,"RandomZ":3731020989,"StartTime":42814.0,"Objects":[{"StartTime":42814.0,"EndTime":42814.0,"Column":6},{"StartTime":42814.0,"EndTime":42814.0,"Column":4},{"StartTime":42996.0,"EndTime":42996.0,"Column":5},{"StartTime":42996.0,"EndTime":42996.0,"Column":3},{"StartTime":43178.0,"EndTime":43178.0,"Column":5},{"StartTime":43178.0,"EndTime":43178.0,"Column":3}]},{"RandomW":692368043,"RandomX":3959050362,"RandomY":3731020989,"RandomZ":3294029476,"StartTime":43363.0,"Objects":[{"StartTime":43363.0,"EndTime":43363.0,"Column":5}]},{"RandomW":268717689,"RandomX":3731020989,"RandomY":3294029476,"RandomZ":692368043,"StartTime":43546.0,"Objects":[{"StartTime":43546.0,"EndTime":43546.0,"Column":4}]},{"RandomW":3628859376,"RandomX":3294029476,"RandomY":692368043,"RandomZ":268717689,"StartTime":43728.0,"Objects":[{"StartTime":43728.0,"EndTime":43728.0,"Column":3}]},{"RandomW":2810605489,"RandomX":268717689,"RandomY":3628859376,"RandomZ":2874884507,"StartTime":43911.0,"Objects":[{"StartTime":43911.0,"EndTime":44093.0,"Column":2}]},{"RandomW":317739913,"RandomX":2874884507,"RandomY":2810605489,"RandomZ":2512620222,"StartTime":44277.0,"Objects":[{"StartTime":44277.0,"EndTime":44459.0,"Column":1}]},{"RandomW":967116709,"RandomX":4156133369,"RandomY":2124840394,"RandomZ":3998877068,"StartTime":44643.0,"Objects":[{"StartTime":44643.0,"EndTime":44825.0,"Column":6},{"StartTime":44643.0,"EndTime":44825.0,"Column":3}]},{"RandomW":1331553411,"RandomX":3998877068,"RandomY":967116709,"RandomZ":39354671,"StartTime":45009.0,"Objects":[{"StartTime":45009.0,"EndTime":45374.0,"Column":4}]},{"RandomW":2785797100,"RandomX":1331553411,"RandomY":1897266817,"RandomZ":1620854569,"StartTime":45558.0,"Objects":[{"StartTime":45558.0,"EndTime":45923.0,"Column":5},{"StartTime":45558.0,"EndTime":45923.0,"Column":2}]},{"RandomW":114455122,"RandomX":1897266817,"RandomY":1620854569,"RandomZ":2785797100,"StartTime":46106.0,"Objects":[{"StartTime":46106.0,"EndTime":46106.0,"Column":3},{"StartTime":46106.0,"EndTime":46106.0,"Column":4}]},{"RandomW":3639436799,"RandomX":1620854569,"RandomY":2785797100,"RandomZ":114455122,"StartTime":46289.0,"Objects":[{"StartTime":46289.0,"EndTime":46289.0,"Column":4}]},{"RandomW":2239997850,"RandomX":1523242180,"RandomY":2737260786,"RandomZ":921894438,"StartTime":46472.0,"Objects":[{"StartTime":46472.0,"EndTime":46472.0,"Column":5},{"StartTime":46472.0,"EndTime":46472.0,"Column":3},{"StartTime":46654.0,"EndTime":46654.0,"Column":1},{"StartTime":46654.0,"EndTime":46654.0,"Column":5},{"StartTime":46836.0,"EndTime":46836.0,"Column":3},{"StartTime":46836.0,"EndTime":46836.0,"Column":1}]},{"RandomW":270173708,"RandomX":921894438,"RandomY":2239997850,"RandomZ":2313367322,"StartTime":47021.0,"Objects":[{"StartTime":47021.0,"EndTime":47386.0,"Column":0}]},{"RandomW":2981644775,"RandomX":2239997850,"RandomY":2313367322,"RandomZ":270173708,"StartTime":47570.0,"Objects":[{"StartTime":47570.0,"EndTime":47570.0,"Column":5}]},{"RandomW":698324797,"RandomX":2313367322,"RandomY":270173708,"RandomZ":2981644775,"StartTime":47936.0,"Objects":[{"StartTime":47936.0,"EndTime":47936.0,"Column":2}]},{"RandomW":2105158963,"RandomX":2981644775,"RandomY":698324797,"RandomZ":3113547499,"StartTime":48119.0,"Objects":[{"StartTime":48119.0,"EndTime":48667.0,"Column":6}]},{"RandomW":3675126935,"RandomX":3113547499,"RandomY":2105158963,"RandomZ":251569162,"StartTime":48850.0,"Objects":[{"StartTime":48850.0,"EndTime":49398.0,"Column":4}]},{"RandomW":1771033747,"RandomX":251569162,"RandomY":3675126935,"RandomZ":3308284595,"StartTime":49582.0,"Objects":[{"StartTime":49582.0,"EndTime":50130.0,"Column":5}]},{"RandomW":653741274,"RandomX":3308284595,"RandomY":1771033747,"RandomZ":2460676956,"StartTime":50314.0,"Objects":[{"StartTime":50314.0,"EndTime":50862.0,"Column":2}]},{"RandomW":3908591175,"RandomX":2011739264,"RandomY":2988284210,"RandomZ":772833847,"StartTime":51046.0,"Objects":[{"StartTime":51046.0,"EndTime":51594.0,"Column":6},{"StartTime":51046.0,"EndTime":51594.0,"Column":5}]},{"RandomW":782718603,"RandomX":3908591175,"RandomY":3666262892,"RandomZ":2215410951,"StartTime":51777.0,"Objects":[{"StartTime":51777.0,"EndTime":51959.0,"Column":0},{"StartTime":51777.0,"EndTime":51959.0,"Column":2}]},{"RandomW":3946166617,"RandomX":2215410951,"RandomY":782718603,"RandomZ":75972478,"StartTime":52143.0,"Objects":[{"StartTime":52143.0,"EndTime":52508.0,"Column":5}]},{"RandomW":204866941,"RandomX":782718603,"RandomY":75972478,"RandomZ":3946166617,"StartTime":52692.0,"Objects":[{"StartTime":52692.0,"EndTime":52692.0,"Column":3},{"StartTime":52692.0,"EndTime":52692.0,"Column":4}]},{"RandomW":628140489,"RandomX":3946166617,"RandomY":204866941,"RandomZ":405870974,"StartTime":52875.0,"Objects":[{"StartTime":52875.0,"EndTime":53240.0,"Column":2}]},{"RandomW":1325586396,"RandomX":628140489,"RandomY":1674126159,"RandomZ":3748192166,"StartTime":53424.0,"Objects":[{"StartTime":53424.0,"EndTime":53606.0,"Column":5},{"StartTime":53424.0,"EndTime":53606.0,"Column":4}]},{"RandomW":3311768819,"RandomX":3748192166,"RandomY":1325586396,"RandomZ":4019978516,"StartTime":53789.0,"Objects":[{"StartTime":53789.0,"EndTime":53789.0,"Column":4},{"StartTime":53789.0,"EndTime":53789.0,"Column":3}]},{"RandomW":1550448150,"RandomX":1325586396,"RandomY":4019978516,"RandomZ":3311768819,"StartTime":53972.0,"Objects":[{"StartTime":53972.0,"EndTime":53972.0,"Column":5}]},{"RandomW":169296756,"RandomX":3311768819,"RandomY":1550448150,"RandomZ":93091440,"StartTime":54155.0,"Objects":[{"StartTime":54155.0,"EndTime":54337.0,"Column":0}]},{"RandomW":2528106598,"RandomX":169296756,"RandomY":3812396233,"RandomZ":4042657790,"StartTime":54521.0,"Objects":[{"StartTime":54521.0,"EndTime":54703.0,"Column":6},{"StartTime":54521.0,"EndTime":54703.0,"Column":1}]},{"RandomW":1636289987,"RandomX":2528106598,"RandomY":638788900,"RandomZ":558809067,"StartTime":54887.0,"Objects":[{"StartTime":54887.0,"EndTime":55069.0,"Column":5}]},{"RandomW":914779004,"RandomX":558809067,"RandomY":1636289987,"RandomZ":2298692989,"StartTime":55253.0,"Objects":[{"StartTime":55253.0,"EndTime":55618.0,"Column":2}]},{"RandomW":1650670496,"RandomX":1636289987,"RandomY":2298692989,"RandomZ":914779004,"StartTime":55802.0,"Objects":[{"StartTime":55802.0,"EndTime":55802.0,"Column":2}]},{"RandomW":3497220679,"RandomX":1037372410,"RandomY":2926479760,"RandomZ":2880883370,"StartTime":55985.0,"Objects":[{"StartTime":55985.0,"EndTime":56350.0,"Column":4}]},{"RandomW":1164710248,"RandomX":2926479760,"RandomY":2880883370,"RandomZ":3497220679,"StartTime":56533.0,"Objects":[{"StartTime":56533.0,"EndTime":56533.0,"Column":5}]},{"RandomW":2188007582,"RandomX":3497220679,"RandomY":1164710248,"RandomZ":2677289564,"StartTime":56716.0,"Objects":[{"StartTime":56716.0,"EndTime":57081.0,"Column":0}]},{"RandomW":3363933174,"RandomX":1164710248,"RandomY":2677289564,"RandomZ":2188007582,"StartTime":57265.0,"Objects":[{"StartTime":57265.0,"EndTime":57265.0,"Column":3}]},{"RandomW":50721184,"RandomX":3363933174,"RandomY":3980600543,"RandomZ":3548114425,"StartTime":57448.0,"Objects":[{"StartTime":57448.0,"EndTime":57630.0,"Column":4},{"StartTime":57448.0,"EndTime":57630.0,"Column":0}]},{"RandomW":864990701,"RandomX":3548114425,"RandomY":50721184,"RandomZ":3340702733,"StartTime":57814.0,"Objects":[{"StartTime":57814.0,"EndTime":57996.0,"Column":2}]},{"RandomW":322108643,"RandomX":3340702733,"RandomY":864990701,"RandomZ":1066828352,"StartTime":58180.0,"Objects":[{"StartTime":58180.0,"EndTime":58362.0,"Column":1}]},{"RandomW":1792394322,"RandomX":1066828352,"RandomY":322108643,"RandomZ":749878772,"StartTime":58546.0,"Objects":[{"StartTime":58546.0,"EndTime":58728.0,"Column":5}]},{"RandomW":475567653,"RandomX":3789213642,"RandomY":1703666422,"RandomZ":3630902830,"StartTime":58911.0,"Objects":[{"StartTime":58911.0,"EndTime":59093.0,"Column":4},{"StartTime":58911.0,"EndTime":59093.0,"Column":1}]},{"RandomW":292381990,"RandomX":3630902830,"RandomY":475567653,"RandomZ":734768891,"StartTime":59277.0,"Objects":[{"StartTime":59277.0,"EndTime":59459.0,"Column":0}]},{"RandomW":1221027582,"RandomX":734768891,"RandomY":292381990,"RandomZ":2432050043,"StartTime":59643.0,"Objects":[{"StartTime":59643.0,"EndTime":60008.0,"Column":3}]},{"RandomW":1041081707,"RandomX":292381990,"RandomY":2432050043,"RandomZ":1221027582,"StartTime":60192.0,"Objects":[{"StartTime":60192.0,"EndTime":60192.0,"Column":0}]},{"RandomW":1144239065,"RandomX":2432050043,"RandomY":1221027582,"RandomZ":1041081707,"StartTime":60375.0,"Objects":[{"StartTime":60375.0,"EndTime":60375.0,"Column":1}]},{"RandomW":1711255007,"RandomX":1221027582,"RandomY":1041081707,"RandomZ":1144239065,"StartTime":60558.0,"Objects":[{"StartTime":60558.0,"EndTime":60558.0,"Column":2},{"StartTime":60558.0,"EndTime":60558.0,"Column":3}]},{"RandomW":377276168,"RandomX":1041081707,"RandomY":1144239065,"RandomZ":1711255007,"StartTime":60649.0,"Objects":[{"StartTime":60649.0,"EndTime":60649.0,"Column":1}]},{"RandomW":377276168,"RandomX":1041081707,"RandomY":1144239065,"RandomZ":1711255007,"StartTime":60741.0,"Objects":[{"StartTime":60741.0,"EndTime":60741.0,"Column":2}]},{"RandomW":1158225489,"RandomX":1144239065,"RandomY":1711255007,"RandomZ":377276168,"StartTime":60924.0,"Objects":[{"StartTime":60924.0,"EndTime":60924.0,"Column":3}]},{"RandomW":74717015,"RandomX":377276168,"RandomY":1158225489,"RandomZ":2625486930,"StartTime":61106.0,"Objects":[{"StartTime":61106.0,"EndTime":61288.0,"Column":0}]},{"RandomW":4106277974,"RandomX":1158225489,"RandomY":2625486930,"RandomZ":74717015,"StartTime":61472.0,"Objects":[{"StartTime":61472.0,"EndTime":61472.0,"Column":2},{"StartTime":61472.0,"EndTime":61472.0,"Column":3}]},{"RandomW":3720471658,"RandomX":4181108489,"RandomY":2335938349,"RandomZ":793896882,"StartTime":61655.0,"Objects":[{"StartTime":61655.0,"EndTime":61655.0,"Column":6},{"StartTime":61746.0,"EndTime":61746.0,"Column":0},{"StartTime":61837.0,"EndTime":61837.0,"Column":2}]},{"RandomW":3031050452,"RandomX":2441289268,"RandomY":3327554006,"RandomZ":1721397977,"StartTime":62021.0,"Objects":[{"StartTime":62021.0,"EndTime":62021.0,"Column":0},{"StartTime":62112.0,"EndTime":62112.0,"Column":3},{"StartTime":62203.0,"EndTime":62203.0,"Column":5}]},{"RandomW":1028780747,"RandomX":3327554006,"RandomY":1721397977,"RandomZ":3031050452,"StartTime":62387.0,"Objects":[{"StartTime":62387.0,"EndTime":62387.0,"Column":1}]},{"RandomW":4249178890,"RandomX":3031050452,"RandomY":1028780747,"RandomZ":1224535158,"StartTime":62570.0,"Objects":[{"StartTime":62570.0,"EndTime":62935.0,"Column":6}]},{"RandomW":407644414,"RandomX":1028780747,"RandomY":1224535158,"RandomZ":4249178890,"StartTime":63119.0,"Objects":[{"StartTime":63119.0,"EndTime":63119.0,"Column":4}]},{"RandomW":84513019,"RandomX":4249178890,"RandomY":407644414,"RandomZ":2855880342,"StartTime":63302.0,"Objects":[{"StartTime":63302.0,"EndTime":63667.0,"Column":0}]},{"RandomW":2876344117,"RandomX":2855880342,"RandomY":84513019,"RandomZ":3523432019,"StartTime":63850.0,"Objects":[{"StartTime":63850.0,"EndTime":63850.0,"Column":5},{"StartTime":63850.0,"EndTime":63850.0,"Column":2}]},{"RandomW":1247936821,"RandomX":2876344117,"RandomY":3407636795,"RandomZ":2195437291,"StartTime":64033.0,"Objects":[{"StartTime":64033.0,"EndTime":64033.0,"Column":1},{"StartTime":64033.0,"EndTime":64033.0,"Column":5}]},{"RandomW":1165002312,"RandomX":2195437291,"RandomY":1247936821,"RandomZ":1829597027,"StartTime":64216.0,"Objects":[{"StartTime":64216.0,"EndTime":64216.0,"Column":6},{"StartTime":64216.0,"EndTime":64216.0,"Column":3}]},{"RandomW":440601827,"RandomX":1247936821,"RandomY":1829597027,"RandomZ":1165002312,"StartTime":64399.0,"Objects":[{"StartTime":64399.0,"EndTime":64399.0,"Column":6},{"StartTime":64399.0,"EndTime":64399.0,"Column":0}]},{"RandomW":1174586413,"RandomX":1165002312,"RandomY":440601827,"RandomZ":1081265463,"StartTime":64582.0,"Objects":[{"StartTime":64582.0,"EndTime":64947.0,"Column":3}]},{"RandomW":1399461522,"RandomX":1174586413,"RandomY":2273396835,"RandomZ":2242340964,"StartTime":65131.0,"Objects":[{"StartTime":65131.0,"EndTime":65313.0,"Column":0},{"StartTime":65131.0,"EndTime":65313.0,"Column":4}]},{"RandomW":806141128,"RandomX":1399461522,"RandomY":52007806,"RandomZ":2388001070,"StartTime":65497.0,"Objects":[{"StartTime":65497.0,"EndTime":65862.0,"Column":2}]},{"RandomW":869393117,"RandomX":52007806,"RandomY":2388001070,"RandomZ":806141128,"StartTime":66046.0,"Objects":[{"StartTime":66046.0,"EndTime":66046.0,"Column":2}]},{"RandomW":2114055664,"RandomX":932480042,"RandomY":484530218,"RandomZ":2599754617,"StartTime":66228.0,"Objects":[{"StartTime":66228.0,"EndTime":66593.0,"Column":3},{"StartTime":66228.0,"EndTime":66593.0,"Column":1},{"StartTime":66228.0,"EndTime":66593.0,"Column":6}]},{"RandomW":4212241992,"RandomX":2599754617,"RandomY":2114055664,"RandomZ":3978789838,"StartTime":66777.0,"Objects":[{"StartTime":66777.0,"EndTime":66777.0,"Column":0},{"StartTime":66777.0,"EndTime":66777.0,"Column":6}]},{"RandomW":1778029315,"RandomX":4212241992,"RandomY":3373094016,"RandomZ":3088207420,"StartTime":66960.0,"Objects":[{"StartTime":66960.0,"EndTime":67142.0,"Column":3},{"StartTime":66960.0,"EndTime":67142.0,"Column":5}]},{"RandomW":523225986,"RandomX":3373094016,"RandomY":3088207420,"RandomZ":1778029315,"StartTime":67326.0,"Objects":[{"StartTime":67326.0,"EndTime":67326.0,"Column":2},{"StartTime":67326.0,"EndTime":67326.0,"Column":3}]},{"RandomW":2523721637,"RandomX":1778029315,"RandomY":523225986,"RandomZ":3156555187,"StartTime":67509.0,"Objects":[{"StartTime":67509.0,"EndTime":67874.0,"Column":1}]},{"RandomW":3753678213,"RandomX":2523721637,"RandomY":733156576,"RandomZ":1252112847,"StartTime":68058.0,"Objects":[{"StartTime":68058.0,"EndTime":68240.0,"Column":4},{"StartTime":68058.0,"EndTime":68240.0,"Column":5}]},{"RandomW":765988363,"RandomX":2650496303,"RandomY":3671318686,"RandomZ":3791148796,"StartTime":68424.0,"Objects":[{"StartTime":68424.0,"EndTime":68789.0,"Column":1},{"StartTime":68424.0,"EndTime":68789.0,"Column":2}]},{"RandomW":1639351583,"RandomX":1794981044,"RandomY":795866725,"RandomZ":201525954,"StartTime":68972.0,"Objects":[{"StartTime":68972.0,"EndTime":69337.0,"Column":0},{"StartTime":68972.0,"EndTime":69337.0,"Column":5}]},{"RandomW":3794603265,"RandomX":795866725,"RandomY":201525954,"RandomZ":1639351583,"StartTime":69521.0,"Objects":[{"StartTime":69521.0,"EndTime":69521.0,"Column":4},{"StartTime":69521.0,"EndTime":69521.0,"Column":5}]},{"RandomW":2799716979,"RandomX":1639351583,"RandomY":3794603265,"RandomZ":2996900863,"StartTime":69704.0,"Objects":[{"StartTime":69704.0,"EndTime":69886.0,"Column":2}]},{"RandomW":1138768260,"RandomX":2799716979,"RandomY":1940635085,"RandomZ":4184142780,"StartTime":70070.0,"Objects":[{"StartTime":70070.0,"EndTime":70252.0,"Column":6},{"StartTime":70070.0,"EndTime":70252.0,"Column":3}]},{"RandomW":3382500543,"RandomX":4184142780,"RandomY":1138768260,"RandomZ":3891744857,"StartTime":70436.0,"Objects":[{"StartTime":70436.0,"EndTime":70436.0,"Column":3},{"StartTime":70436.0,"EndTime":70436.0,"Column":4}]},{"RandomW":665559990,"RandomX":398143267,"RandomY":1440028745,"RandomZ":150863666,"StartTime":70619.0,"Objects":[{"StartTime":70619.0,"EndTime":70984.0,"Column":0},{"StartTime":70619.0,"EndTime":70984.0,"Column":2}]},{"RandomW":340592762,"RandomX":150863666,"RandomY":665559990,"RandomZ":3920056919,"StartTime":71167.0,"Objects":[{"StartTime":71167.0,"EndTime":71167.0,"Column":5},{"StartTime":71167.0,"EndTime":71167.0,"Column":1}]},{"RandomW":1518605551,"RandomX":340592762,"RandomY":4088291758,"RandomZ":2304957054,"StartTime":71350.0,"Objects":[{"StartTime":71350.0,"EndTime":71532.0,"Column":0},{"StartTime":71350.0,"EndTime":71532.0,"Column":4}]},{"RandomW":972812530,"RandomX":1518605551,"RandomY":653707549,"RandomZ":2799009660,"StartTime":71716.0,"Objects":[{"StartTime":71716.0,"EndTime":71898.0,"Column":2},{"StartTime":71716.0,"EndTime":71898.0,"Column":3}]},{"RandomW":3736044692,"RandomX":972812530,"RandomY":1134737486,"RandomZ":3549179654,"StartTime":72082.0,"Objects":[{"StartTime":72082.0,"EndTime":72264.0,"Column":4},{"StartTime":72082.0,"EndTime":72264.0,"Column":5}]},{"RandomW":2646968586,"RandomX":3695561354,"RandomY":2121039538,"RandomZ":3939713463,"StartTime":72448.0,"Objects":[{"StartTime":72448.0,"EndTime":72630.0,"Column":6},{"StartTime":72448.0,"EndTime":72630.0,"Column":1}]},{"RandomW":34357760,"RandomX":2646968586,"RandomY":1864765858,"RandomZ":1923246874,"StartTime":72814.0,"Objects":[{"StartTime":72814.0,"EndTime":72814.0,"Column":0},{"StartTime":72814.0,"EndTime":72814.0,"Column":6}]},{"RandomW":34357760,"RandomX":2646968586,"RandomY":1864765858,"RandomZ":1923246874,"StartTime":72997.0,"Objects":[{"StartTime":72997.0,"EndTime":72997.0,"Column":6},{"StartTime":72997.0,"EndTime":72997.0,"Column":0}]},{"RandomW":3006273170,"RandomX":1864765858,"RandomY":1923246874,"RandomZ":34357760,"StartTime":73363.0,"Objects":[{"StartTime":73363.0,"EndTime":73363.0,"Column":4},{"StartTime":73363.0,"EndTime":73363.0,"Column":5}]},{"RandomW":3978541447,"RandomX":3006273170,"RandomY":3972311639,"RandomZ":2371876462,"StartTime":73728.0,"Objects":[{"StartTime":73728.0,"EndTime":73728.0,"Column":2},{"StartTime":73819.0,"EndTime":73819.0,"Column":5},{"StartTime":73910.0,"EndTime":73910.0,"Column":0}]},{"RandomW":399194528,"RandomX":2371876462,"RandomY":3978541447,"RandomZ":3734283831,"StartTime":74094.0,"Objects":[{"StartTime":74094.0,"EndTime":74094.0,"Column":3},{"StartTime":74094.0,"EndTime":74094.0,"Column":1}]},{"RandomW":2883430810,"RandomX":4002716317,"RandomY":2698819798,"RandomZ":1875619237,"StartTime":74277.0,"Objects":[{"StartTime":74277.0,"EndTime":74642.0,"Column":6},{"StartTime":74277.0,"EndTime":74642.0,"Column":2}]},{"RandomW":3502984571,"RandomX":3789000206,"RandomY":2760409322,"RandomZ":2518464347,"StartTime":74826.0,"Objects":[{"StartTime":74826.0,"EndTime":74826.0,"Column":1},{"StartTime":74826.0,"EndTime":74826.0,"Column":4}]},{"RandomW":2447473462,"RandomX":1834893326,"RandomY":512459921,"RandomZ":2493625006,"StartTime":75009.0,"Objects":[{"StartTime":75009.0,"EndTime":75374.0,"Column":5},{"StartTime":75009.0,"EndTime":75374.0,"Column":0}]},{"RandomW":236980020,"RandomX":512459921,"RandomY":2493625006,"RandomZ":2447473462,"StartTime":75558.0,"Objects":[{"StartTime":75558.0,"EndTime":75558.0,"Column":4}]},{"RandomW":1338160073,"RandomX":236980020,"RandomY":1288545645,"RandomZ":3579861656,"StartTime":75741.0,"Objects":[{"StartTime":75741.0,"EndTime":75741.0,"Column":1},{"StartTime":75741.0,"EndTime":75741.0,"Column":5}]},{"RandomW":1104479394,"RandomX":1288545645,"RandomY":3579861656,"RandomZ":1338160073,"StartTime":75924.0,"Objects":[{"StartTime":75924.0,"EndTime":75924.0,"Column":6}]},{"RandomW":1611802424,"RandomX":3579861656,"RandomY":1338160073,"RandomZ":1104479394,"StartTime":76106.0,"Objects":[{"StartTime":76106.0,"EndTime":76106.0,"Column":5},{"StartTime":76106.0,"EndTime":76106.0,"Column":6}]},{"RandomW":74337788,"RandomX":1611802424,"RandomY":3077637432,"RandomZ":3984045284,"StartTime":76289.0,"Objects":[{"StartTime":76289.0,"EndTime":76654.0,"Column":0}]},{"RandomW":2589155279,"RandomX":74337788,"RandomY":4122247598,"RandomZ":3402826469,"StartTime":76838.0,"Objects":[{"StartTime":76838.0,"EndTime":77020.0,"Column":4},{"StartTime":76838.0,"EndTime":77020.0,"Column":1}]},{"RandomW":4015672441,"RandomX":2589155279,"RandomY":3961839828,"RandomZ":3184309519,"StartTime":77204.0,"Objects":[{"StartTime":77204.0,"EndTime":77569.0,"Column":3},{"StartTime":77204.0,"EndTime":77569.0,"Column":6}]},{"RandomW":605987856,"RandomX":3184309519,"RandomY":4015672441,"RandomZ":4025998202,"StartTime":77753.0,"Objects":[{"StartTime":77753.0,"EndTime":77753.0,"Column":0},{"StartTime":77753.0,"EndTime":77753.0,"Column":1}]},{"RandomW":1497070673,"RandomX":2430309501,"RandomY":1093966930,"RandomZ":2905669028,"StartTime":77936.0,"Objects":[{"StartTime":77936.0,"EndTime":78301.0,"Column":3},{"StartTime":77936.0,"EndTime":78301.0,"Column":2},{"StartTime":77936.0,"EndTime":78301.0,"Column":4}]},{"RandomW":353334135,"RandomX":1093966930,"RandomY":2905669028,"RandomZ":1497070673,"StartTime":78485.0,"Objects":[{"StartTime":78485.0,"EndTime":78485.0,"Column":1}]},{"RandomW":912971684,"RandomX":4030507912,"RandomY":3670783478,"RandomZ":1485865738,"StartTime":78667.0,"Objects":[{"StartTime":78667.0,"EndTime":78849.0,"Column":4},{"StartTime":78667.0,"EndTime":78849.0,"Column":2}]},{"RandomW":589257226,"RandomX":3670783478,"RandomY":1485865738,"RandomZ":912971684,"StartTime":79033.0,"Objects":[{"StartTime":79033.0,"EndTime":79033.0,"Column":3},{"StartTime":79033.0,"EndTime":79033.0,"Column":4}]},{"RandomW":2024304860,"RandomX":912971684,"RandomY":589257226,"RandomZ":2767994778,"StartTime":79216.0,"Objects":[{"StartTime":79216.0,"EndTime":79581.0,"Column":6}]},{"RandomW":2219601613,"RandomX":2024304860,"RandomY":404709274,"RandomZ":3238631833,"StartTime":79765.0,"Objects":[{"StartTime":79765.0,"EndTime":79947.0,"Column":3},{"StartTime":79765.0,"EndTime":79947.0,"Column":0}]},{"RandomW":3490718869,"RandomX":2219601613,"RandomY":3210330120,"RandomZ":1566096374,"StartTime":80131.0,"Objects":[{"StartTime":80131.0,"EndTime":80496.0,"Column":5},{"StartTime":80131.0,"EndTime":80496.0,"Column":4}]},{"RandomW":1189469485,"RandomX":1566096374,"RandomY":3490718869,"RandomZ":936182364,"StartTime":80680.0,"Objects":[{"StartTime":80680.0,"EndTime":81045.0,"Column":3}]},{"RandomW":3740948748,"RandomX":3490718869,"RandomY":936182364,"RandomZ":1189469485,"StartTime":81228.0,"Objects":[{"StartTime":81228.0,"EndTime":81228.0,"Column":5},{"StartTime":81228.0,"EndTime":81228.0,"Column":6}]},{"RandomW":3491747463,"RandomX":1189469485,"RandomY":3740948748,"RandomZ":2409626314,"StartTime":81411.0,"Objects":[{"StartTime":81411.0,"EndTime":81776.0,"Column":4}]},{"RandomW":3095098652,"RandomX":3740948748,"RandomY":2409626314,"RandomZ":3491747463,"StartTime":81960.0,"Objects":[{"StartTime":81960.0,"EndTime":81960.0,"Column":3},{"StartTime":81960.0,"EndTime":81960.0,"Column":4}]},{"RandomW":3024447782,"RandomX":2409626314,"RandomY":3491747463,"RandomZ":3095098652,"StartTime":82143.0,"Objects":[{"StartTime":82143.0,"EndTime":82143.0,"Column":2},{"StartTime":82143.0,"EndTime":82143.0,"Column":3}]},{"RandomW":3942236456,"RandomX":3095098652,"RandomY":3024447782,"RandomZ":3296500942,"StartTime":82326.0,"Objects":[{"StartTime":82326.0,"EndTime":82508.0,"Column":5}]},{"RandomW":912304721,"RandomX":3942236456,"RandomY":2303302398,"RandomZ":383442600,"StartTime":82692.0,"Objects":[{"StartTime":82692.0,"EndTime":82874.0,"Column":1},{"StartTime":82692.0,"EndTime":82874.0,"Column":2}]},{"RandomW":2431170151,"RandomX":3622775798,"RandomY":385908797,"RandomZ":604082862,"StartTime":83058.0,"Objects":[{"StartTime":83058.0,"EndTime":83240.0,"Column":4},{"StartTime":83058.0,"EndTime":83240.0,"Column":0}]},{"RandomW":4088921973,"RandomX":1523770388,"RandomY":1345324755,"RandomZ":2436511051,"StartTime":83424.0,"Objects":[{"StartTime":83424.0,"EndTime":83606.0,"Column":2},{"StartTime":83424.0,"EndTime":83606.0,"Column":6}]},{"RandomW":2663434012,"RandomX":3999189199,"RandomY":2928551970,"RandomZ":3800966865,"StartTime":83789.0,"Objects":[{"StartTime":83789.0,"EndTime":83971.0,"Column":5},{"StartTime":83789.0,"EndTime":83971.0,"Column":1}]},{"RandomW":183339481,"RandomX":3405481532,"RandomY":1385906264,"RandomZ":3611020052,"StartTime":84155.0,"Objects":[{"StartTime":84155.0,"EndTime":84337.0,"Column":4},{"StartTime":84155.0,"EndTime":84337.0,"Column":0}]},{"RandomW":472982750,"RandomX":1385906264,"RandomY":3611020052,"RandomZ":183339481,"StartTime":84521.0,"Objects":[{"StartTime":84521.0,"EndTime":84521.0,"Column":4}]},{"RandomW":2485141120,"RandomX":3611020052,"RandomY":183339481,"RandomZ":472982750,"StartTime":84704.0,"Objects":[{"StartTime":84704.0,"EndTime":84704.0,"Column":5}]},{"RandomW":2638881915,"RandomX":183339481,"RandomY":472982750,"RandomZ":2485141120,"StartTime":84887.0,"Objects":[{"StartTime":84887.0,"EndTime":84887.0,"Column":6},{"StartTime":84887.0,"EndTime":84887.0,"Column":0}]},{"RandomW":2991178386,"RandomX":1846348081,"RandomY":4216122958,"RandomZ":938042528,"StartTime":85070.0,"Objects":[{"StartTime":85070.0,"EndTime":85070.0,"Column":5},{"StartTime":85161.0,"EndTime":85161.0,"Column":6},{"StartTime":85252.0,"EndTime":85252.0,"Column":3}]},{"RandomW":2830634920,"RandomX":3020624235,"RandomY":682207034,"RandomZ":1410927339,"StartTime":85436.0,"Objects":[{"StartTime":85436.0,"EndTime":85436.0,"Column":4},{"StartTime":85527.0,"EndTime":85527.0,"Column":2},{"StartTime":85618.0,"EndTime":85618.0,"Column":4}]},{"RandomW":1154798493,"RandomX":682207034,"RandomY":1410927339,"RandomZ":2830634920,"StartTime":85802.0,"Objects":[{"StartTime":85802.0,"EndTime":85802.0,"Column":3}]},{"RandomW":3579754894,"RandomX":1154798493,"RandomY":555826250,"RandomZ":3186828503,"StartTime":85985.0,"Objects":[{"StartTime":85985.0,"EndTime":86167.0,"Column":4}]},{"RandomW":522156379,"RandomX":3186828503,"RandomY":3579754894,"RandomZ":938791043,"StartTime":86350.0,"Objects":[{"StartTime":86350.0,"EndTime":86532.0,"Column":1}]},{"RandomW":2327696617,"RandomX":522156379,"RandomY":1005466611,"RandomZ":459042761,"StartTime":86716.0,"Objects":[{"StartTime":86716.0,"EndTime":86898.0,"Column":0}]},{"RandomW":3698157493,"RandomX":2327696617,"RandomY":1854714180,"RandomZ":615999181,"StartTime":87082.0,"Objects":[{"StartTime":87082.0,"EndTime":87264.0,"Column":2},{"StartTime":87082.0,"EndTime":87264.0,"Column":5}]},{"RandomW":2615638464,"RandomX":3088317005,"RandomY":3005119130,"RandomZ":738255674,"StartTime":87448.0,"Objects":[{"StartTime":87448.0,"EndTime":87448.0,"Column":4},{"StartTime":87448.0,"EndTime":87448.0,"Column":1},{"StartTime":87630.0,"EndTime":87630.0,"Column":2},{"StartTime":87630.0,"EndTime":87630.0,"Column":5},{"StartTime":87812.0,"EndTime":87812.0,"Column":2},{"StartTime":87812.0,"EndTime":87812.0,"Column":5}]},{"RandomW":4236988115,"RandomX":738255674,"RandomY":2615638464,"RandomZ":3154196835,"StartTime":87997.0,"Objects":[{"StartTime":87997.0,"EndTime":88362.0,"Column":6}]},{"RandomW":3260011681,"RandomX":4236988115,"RandomY":3619257163,"RandomZ":1999646981,"StartTime":88546.0,"Objects":[{"StartTime":88546.0,"EndTime":88728.0,"Column":3}]},{"RandomW":1679091693,"RandomX":3619257163,"RandomY":1999646981,"RandomZ":3260011681,"StartTime":88911.0,"Objects":[{"StartTime":88911.0,"EndTime":88911.0,"Column":1},{"StartTime":88911.0,"EndTime":88911.0,"Column":2}]},{"RandomW":4053500035,"RandomX":2020322055,"RandomY":2384790806,"RandomZ":846406319,"StartTime":89277.0,"Objects":[{"StartTime":89277.0,"EndTime":89459.0,"Column":0},{"StartTime":89277.0,"EndTime":89459.0,"Column":6}]},{"RandomW":3656101543,"RandomX":4053500035,"RandomY":3566026276,"RandomZ":1915132950,"StartTime":89643.0,"Objects":[{"StartTime":89643.0,"EndTime":89825.0,"Column":4}]},{"RandomW":3002483376,"RandomX":1234751024,"RandomY":253242681,"RandomZ":2332173547,"StartTime":90009.0,"Objects":[{"StartTime":90009.0,"EndTime":90191.0,"Column":0},{"StartTime":90009.0,"EndTime":90191.0,"Column":2}]},{"RandomW":1769147212,"RandomX":1032909712,"RandomY":4079968510,"RandomZ":1771054860,"StartTime":90375.0,"Objects":[{"StartTime":90375.0,"EndTime":90375.0,"Column":3},{"StartTime":90375.0,"EndTime":90375.0,"Column":6},{"StartTime":90557.0,"EndTime":90557.0,"Column":6},{"StartTime":90557.0,"EndTime":90557.0,"Column":3},{"StartTime":90739.0,"EndTime":90739.0,"Column":5},{"StartTime":90739.0,"EndTime":90739.0,"Column":2}]},{"RandomW":1533402007,"RandomX":1771054860,"RandomY":1769147212,"RandomZ":3552934273,"StartTime":90924.0,"Objects":[{"StartTime":90924.0,"EndTime":91289.0,"Column":4}]},{"RandomW":1123904499,"RandomX":3552934273,"RandomY":1533402007,"RandomZ":3005562800,"StartTime":91472.0,"Objects":[{"StartTime":91472.0,"EndTime":91654.0,"Column":3}]},{"RandomW":3485521641,"RandomX":3005562800,"RandomY":1123904499,"RandomZ":3121355612,"StartTime":91838.0,"Objects":[{"StartTime":91838.0,"EndTime":92203.0,"Column":4}]},{"RandomW":1434626078,"RandomX":1123904499,"RandomY":3121355612,"RandomZ":3485521641,"StartTime":92387.0,"Objects":[{"StartTime":92387.0,"EndTime":92387.0,"Column":4}]},{"RandomW":4013632575,"RandomX":1434626078,"RandomY":4236899246,"RandomZ":646300056,"StartTime":92570.0,"Objects":[{"StartTime":92570.0,"EndTime":92752.0,"Column":2},{"StartTime":92570.0,"EndTime":92752.0,"Column":6}]},{"RandomW":471738692,"RandomX":646300056,"RandomY":4013632575,"RandomZ":2948180894,"StartTime":92936.0,"Objects":[{"StartTime":92936.0,"EndTime":93118.0,"Column":1}]},{"RandomW":1081382077,"RandomX":471738692,"RandomY":346006110,"RandomZ":586362406,"StartTime":93302.0,"Objects":[{"StartTime":93302.0,"EndTime":93302.0,"Column":1},{"StartTime":93302.0,"EndTime":93302.0,"Column":5}]},{"RandomW":1151929163,"RandomX":586362406,"RandomY":1081382077,"RandomZ":2915942910,"StartTime":93485.0,"Objects":[{"StartTime":93485.0,"EndTime":93667.0,"Column":3}]},{"RandomW":3634683246,"RandomX":1151929163,"RandomY":4287668198,"RandomZ":463810005,"StartTime":93850.0,"Objects":[{"StartTime":93850.0,"EndTime":94215.0,"Column":1},{"StartTime":93850.0,"EndTime":94215.0,"Column":4}]},{"RandomW":2941238432,"RandomX":463810005,"RandomY":3634683246,"RandomZ":3562759778,"StartTime":94399.0,"Objects":[{"StartTime":94399.0,"EndTime":94581.0,"Column":2}]},{"RandomW":1661408876,"RandomX":3562759778,"RandomY":2941238432,"RandomZ":2646009625,"StartTime":94765.0,"Objects":[{"StartTime":94765.0,"EndTime":95130.0,"Column":5}]},{"RandomW":3189251976,"RandomX":2646009625,"RandomY":1661408876,"RandomZ":1818231832,"StartTime":95314.0,"Objects":[{"StartTime":95314.0,"EndTime":95314.0,"Column":5},{"StartTime":95314.0,"EndTime":95314.0,"Column":3}]},{"RandomW":2743067846,"RandomX":3189251976,"RandomY":2495392125,"RandomZ":3478354416,"StartTime":95497.0,"Objects":[{"StartTime":95497.0,"EndTime":95497.0,"Column":0},{"StartTime":95497.0,"EndTime":95497.0,"Column":6}]},{"RandomW":2762867836,"RandomX":3722791806,"RandomY":2892228350,"RandomZ":4171994747,"StartTime":95680.0,"Objects":[{"StartTime":95680.0,"EndTime":95680.0,"Column":5},{"StartTime":95680.0,"EndTime":95680.0,"Column":3},{"StartTime":95862.0,"EndTime":95862.0,"Column":2},{"StartTime":95862.0,"EndTime":95862.0,"Column":6},{"StartTime":96044.0,"EndTime":96044.0,"Column":6},{"StartTime":96044.0,"EndTime":96044.0,"Column":4}]},{"RandomW":1153177485,"RandomX":2762867836,"RandomY":1407653164,"RandomZ":3758120376,"StartTime":96228.0,"Objects":[{"StartTime":96228.0,"EndTime":96228.0,"Column":1},{"StartTime":96228.0,"EndTime":96228.0,"Column":5}]},{"RandomW":1153177485,"RandomX":2762867836,"RandomY":1407653164,"RandomZ":3758120376,"StartTime":96411.0,"Objects":[{"StartTime":96411.0,"EndTime":96411.0,"Column":5},{"StartTime":96411.0,"EndTime":96411.0,"Column":1}]},{"RandomW":2430957186,"RandomX":1407653164,"RandomY":3758120376,"RandomZ":1153177485,"StartTime":96777.0,"Objects":[{"StartTime":96777.0,"EndTime":96777.0,"Column":3},{"StartTime":96777.0,"EndTime":96777.0,"Column":4}]},{"RandomW":4223688647,"RandomX":3758120376,"RandomY":1153177485,"RandomZ":2430957186,"StartTime":97143.0,"Objects":[{"StartTime":97143.0,"EndTime":97143.0,"Column":4},{"StartTime":97143.0,"EndTime":97143.0,"Column":5}]},{"RandomW":433008794,"RandomX":1153177485,"RandomY":2430957186,"RandomZ":4223688647,"StartTime":97509.0,"Objects":[{"StartTime":97509.0,"EndTime":97509.0,"Column":5},{"StartTime":97509.0,"EndTime":97509.0,"Column":6}]},{"RandomW":3177925713,"RandomX":2430957186,"RandomY":4223688647,"RandomZ":433008794,"StartTime":97692.0,"Objects":[{"StartTime":97692.0,"EndTime":100619.0,"Column":3}]}]} \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/1450162.osu b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/1450162.osu new file mode 100644 index 0000000000..42669b1516 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/1450162.osu @@ -0,0 +1,297 @@ +osu file format v14 + +[General] +StackLeniency: 0.7 +Mode: 0 + +[Difficulty] +HPDrainRate:5 +CircleSize:4 +OverallDifficulty:7 +ApproachRate:7.5 +SliderMultiplier:1.4 +SliderTickRate:1 + +[Events] +//Background and Video events +//Break Periods +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Sound Samples + +[TimingPoints] +1107,365.853658536585,4,2,1,50,1,0 +1107,-166.666666666667,4,2,1,50,0,0 +6960,-111.111111111111,4,2,1,50,0,0 +8424,-100,4,2,1,50,0,0 +48119,-125,4,2,1,50,0,0 +52143,-100,4,2,1,50,0,0 +62570,-100,4,2,1,60,0,1 +85985,-100,4,2,1,50,0,0 +97692,-100,4,2,1,30,0,0 +99155,-100,4,2,1,20,0,0 +100619,-100,4,2,1,5,0,0 + +[HitObjects] +38,247,1107,6,0,P|96:269|170:192,1,167.999994873047,2|0,0:0|0:0,0:0:0:0: +201,128,2570,6,0,L|205:221,1,83.9999974365235,2|0,0:0|0:0,0:0:0:0: +242,230,3302,2,0,L|234:324,1,83.9999974365235,2|0,0:0|0:0,0:0:0:0: +205,343,4033,6,0,P|246:296|351:314,1,167.999994873047,2|0,0:0|0:0,0:0:0:0: +400,368,5497,6,0,L|412:269,1,83.9999974365235,6|0,0:0|0:0,0:0:0:0: +436,251,6228,2,0,P|425:203|408:153,1,83.9999974365235,2|0,0:0|0:0,0:0:0:0: +304,200,6960,6,0,P|262:186|234:181,1,62.9999980773926,6|0,0:0|0:0,0:0:0:0: +202,179,7326,1,8,0:0:0:0: +276,94,7509,2,0,P|313:92|353:87,1,62.9999980773926,2|0,0:0|0:0,0:0:0:0: +398,31,7875,1,2,0:0:0:0: +464,81,8058,2,0,L|450:150,1,62.9999980773926,2|0,0:0|0:0,0:0:0:0: +449,230,8424,6,0,P|347:206|306:217,1,140,2|8,0:0|0:0,0:0:0:0: +229,273,8972,2,0,P|225:339|235:361,1,70,2|0,0:0|0:0,0:0:0:0: +304,313,9338,1,8,0:0:0:0: +224,190,9521,1,2,0:0:0:0: +296,45,9887,6,0,P|297:97|288:125,1,70,6|0,0:0|0:0,0:0:0:0: +224,190,10253,1,8,0:0:0:0: +167,118,10436,1,8,0:0:0:0: +76,126,10619,1,8,0:0:0:0: +39,209,10802,1,8,0:0:0:0: +93,282,10985,1,10,0:0:0:0: +184,280,11167,1,10,0:0:0:0: +102,136,12814,5,2,0:0:0:0: +102,136,13180,2,0,L|199:130,1,70,8|0,0:0|0:0,0:0:0:0: +256,167,13546,2,0,L|339:161,1,70,8|2,0:0|0:0,0:0:0:0: +408,201,13911,2,0,P|454:176|471:143,1,70,8|2,0:0|0:0,0:0:0:0: +373,54,14277,6,0,L|396:137,2,70,6|0|8,0:0|0:0|0:0,0:0:0:0: +305,111,14826,2,0,L|287:274,1,140,0|2,0:0|0:0,0:0:0:0: +262,337,15375,2,0,L|349:327,1,70,8|2,0:0|0:0,0:0:0:0: +419,354,15741,1,8,0:0:0:0: +477,197,16106,6,0,P|423:197|385:209,1,70,8|0,0:0|0:0,0:0:0:0: +321,170,16472,2,0,P|278:190|253:219,1,70,8|2,0:0|0:0,0:0:0:0: +171,213,16838,2,0,P|152:259|158:304,1,70,8|2,0:0|0:0,0:0:0:0: +305,294,17204,6,0,L|224:278,2,70,6|0|8,0:0|0:0|0:0,0:0:0:0: +310,202,17753,2,0,L|149:214,1,140,0|2,0:0|0:0,0:0:0:0: +84,244,18302,2,0,L|92:152,1,70,8|2,0:0|0:0,0:0:0:0: +47,93,18667,6,0,P|78:53|176:80,1,140,6|8,0:0|0:0,0:0:0:0: +218,130,19216,1,0,0:0:0:0: +299,88,19399,2,0,L|387:91,1,70,8|0,0:0|0:0,0:0:0:0: +458,106,19765,2,0,P|447:139|444:205,1,70,8|0,0:0|0:0,0:0:0:0: +455,274,20131,5,2,0:0:0:0: +366,292,20314,2,0,L|353:211,1,70,0|8,0:0|0:0,0:0:0:0: +277,173,20680,2,0,L|253:342,1,140,0|2,0:0|0:0,0:0:0:0: +322,376,21228,2,0,P|368:368|416:370,1,70,8|2,0:0|0:0,0:0:0:0: +500,287,21594,6,0,P|427:273|362:293,2,140,6|8|8,0:0|0:0|0:0,0:0:0:0: +496,111,22509,1,8,0:0:0:0: +499,189,22692,2,0,L|418:191,1,70,8|2,0:0|0:0,0:0:0:0: +344,164,23058,5,6,0:0:0:0: +344,164,23241,1,12,0:0:0:0: +261,326,23606,2,0,L|246:178,1,140,8|2,0:0|0:0,0:0:0:0: +277,100,24155,2,0,P|225:99|196:109,1,70,8|2,0:0|0:0,0:0:0:0: +165,273,24521,5,6,0:0:0:0: +83,235,24704,2,0,L|93:81,1,140,0|0,0:0|0:0,0:0:0:0: +21,37,25253,2,0,L|1:120,2,70,2|0|8,0:0|0:0|0:0,0:0:0:0: +110,17,25802,1,0,0:0:0:0: +172,83,25985,5,2,0:0:0:0: +236,19,26167,2,0,P|223:70|227:170,1,140,0|0,0:0|0:0,0:0:0:0: +293,216,26716,2,0,P|316:165|314:134,2,70,2|0|8,0:0|0:0|0:0,0:0:0:0: +206,245,27265,1,0,0:0:0:0: +274,305,27448,5,2,0:0:0:0: +194,348,27631,2,0,L|363:332,1,140,0|0,0:0|0:0,0:0:0:0: +424,336,28180,1,2,0:0:0:0: +431,245,28363,2,0,P|381:252|354:276,2,70,0|8|0,0:0|0:0|0:0,0:0:0:0: +509,291,28911,6,0,L|496:128,1,140,2|8,0:0|0:0,0:0:0:0: +504,60,29460,1,0,0:0:0:0: +417,34,29643,2,0,L|402:183,1,140,2|8,0:0|0:0,0:0:0:0: +365,262,30192,1,0,0:0:0:0: +295,202,30375,5,2,0:0:0:0: +309,112,30558,2,0,P|282:172|196:176,1,140,0|0,0:0|0:0,0:0:0:0: +148,120,31106,2,0,P|189:99|225:99,2,70,2|0|8,0:0|0:0|0:0,0:0:0:0: +129,209,31655,1,0,0:0:0:0: +63,146,31838,5,2,0:0:0:0: +16,67,32021,2,0,L|27:220,1,140,0|0,0:0|0:0,0:0:0:0: +23,297,32570,2,0,P|81:286|111:290,1,70,2|0,0:0|0:0,0:0:0:0: +173,327,32936,1,8,0:0:0:0: +338,251,33302,6,0,P|268:254|227:199,1,140,2|8,0:0|0:0,0:0:0:0: +203,114,33850,2,0,L|185:262,1,140,0|0,0:0|0:0,0:0:0:0: +244,323,34399,1,8,0:0:0:0: +334,335,34582,1,0,0:0:0:0: +419,219,34765,6,0,L|410:304,1,70,2|0,0:0|0:0,0:0:0:0: +338,251,35131,1,8,0:0:0:0: +301,111,35314,2,0,L|301:190,1,70,6|0,0:0|0:0,0:0:0:0: +383,141,35680,1,8,0:0:0:0: +462,97,35863,2,0,P|427:64|393:54,1,70,2|0,0:0|0:0,0:0:0:0: +321,23,36228,5,2,0:0:0:0: +237,60,36411,1,0,0:0:0:0: +148,38,36594,2,0,P|107:33|56:43,1,70,8|0,0:0|0:0,0:0:0:0: +86,125,36960,2,0,P|51:125|17:117,2,70,2|0|8,0:0|0:0|0:0,0:0:0:0: +175,123,37509,1,0,0:0:0:0: +129,201,37692,5,2,0:0:0:0: +198,259,37875,1,0,0:0:0:0: +205,349,38058,2,0,P|251:330|284:326,1,70,8|0,0:0|0:0,0:0:0:0: +352,285,38424,2,0,P|361:318|357:353,2,70,2|0|8,0:0|0:0|0:0,0:0:0:0: +282,239,38972,1,0,0:0:0:0: +362,195,39155,5,2,0:0:0:0: +436,142,39338,2,0,P|398:115|354:112,1,70,0|8,0:0|0:0,0:0:0:0: +286,92,39704,2,0,L|451:74,1,140,0|0,0:0|0:0,0:0:0:0: +512,118,40253,2,0,L|494:198,1,70,8|0,0:0|0:0,0:0:0:0: +430,297,40619,6,0,P|423:236|336:195,1,140,2|8,0:0|0:0,0:0:0:0: +282,239,41167,1,0,0:0:0:0: +209,184,41350,2,0,L|222:112,1,70,2|2,0:0|0:0,0:0:0:0: +177,34,41716,2,0,P|230:26|269:38,1,70,8|0,0:0|0:0,0:0:0:0: +307,95,42082,5,2,0:0:0:0: +363,23,42265,2,0,L|359:114,1,70,0|8,0:0|0:0,0:0:0:0: +360,184,42631,1,0,0:0:0:0: +450,191,42814,2,0,P|443:145|424:119,2,70,2|0|8,0:0|0:0|0:0,0:0:0:0: +393,263,43363,1,0,0:0:0:0: +304,242,43546,5,2,0:0:0:0: +241,308,43728,1,0,0:0:0:0: +167,256,43911,2,0,P|205:228|245:226,1,70,8|0,0:0|0:0,0:0:0:0: +166,341,44277,2,0,P|118:325|90:289,1,70,2|0,0:0|0:0,0:0:0:0: +125,177,44643,2,0,P|168:152|201:153,1,70,8|0,0:0|0:0,0:0:0:0: +276,132,45009,6,0,L|119:105,1,140,2|8,0:0|0:0,0:0:0:0: +52,74,45558,2,0,L|210:57,1,140,2|0,0:0|0:0,0:0:0:0: +277,28,46106,1,8,0:0:0:0: +349,82,46289,1,0,0:0:0:0: +425,32,46472,6,0,L|451:110,2,70,6|2|8,0:0|0:0|0:0,0:0:0:0: +349,82,47021,2,0,L|344:235,1,140,2|8,0:0|0:0,0:0:0:0: +372,308,47570,1,2,0:0:0:0: +170,324,47936,5,2,0:0:0:0: +99,286,48119,2,0,L|112:112,1,168,2|2,0:0|0:0,0:0:0:0: +64,48,48850,2,0,P|125:36|195:111,1,168,2|2,0:0|0:0,0:0:0:0: +199,189,49582,6,0,L|369:166,1,168,2|2,0:0|0:0,0:0:0:0: +413,97,50314,2,0,P|390:180|377:274,1,168,2|2,0:0|0:0,0:0:0:0: +347,339,51046,6,0,P|424:333|463:251,1,168,2|2,0:0|0:0,0:0:0:0: +473,175,51777,2,0,L|477:105,1,56,2|2,0:0|0:0,0:0:0:0: +446,24,52143,6,0,P|363:22|308:82,1,140,12|2,0:0|0:0,0:0:0:0: +282,138,52692,1,8,0:0:0:0: +193,118,52875,2,0,L|213:281,1,140,2|8,0:0|0:0,0:0:0:0: +225,347,53424,2,0,P|268:328|286:301,1,70,2|0,0:0|0:0,0:0:0:0: +304,222,53789,5,2,0:0:0:0: +385,263,53972,1,0,0:0:0:0: +462,214,54155,2,0,P|421:185|383:179,1,70,8|0,0:0|0:0,0:0:0:0: +322,136,54521,2,0,P|360:105|400:93,1,70,2|0,0:0|0:0,0:0:0:0: +469,107,54887,2,0,L|483:24,1,70,8|0,0:0|0:0,0:0:0:0: +390,22,55253,6,0,L|223:30,1,140,2|8,0:0|0:0,0:0:0:0: +180,87,55802,1,0,0:0:0:0: +230,162,55985,2,0,L|391:154,1,140,2|8,0:0|0:0,0:0:0:0: +430,223,56533,1,0,0:0:0:0: +407,311,56716,6,0,P|356:347|285:307,1,140,2|8,0:0|0:0,0:0:0:0: +236,245,57265,1,0,0:0:0:0: +145,237,57448,2,0,L|162:316,1,70,2|0,0:0|0:0,0:0:0:0: +233,360,57814,6,0,P|185:349|142:350,1,70,8|0,0:0|0:0,0:0:0:0: +11,311,58180,2,0,P|64:302|104:306,1,70,2|0,0:0|0:0,0:0:0:0: +213,248,58546,2,0,P|162:237|130:237,1,70,8|0,0:0|0:0,0:0:0:0: +1,194,58911,2,0,P|47:183|74:185,1,70,2|0,0:0|0:0,0:0:0:0: +234,142,59277,2,0,P|175:129|152:128,1,70,8|0,0:0|0:0,0:0:0:0: +12,26,59643,6,0,P|66:38|71:140,1,140,2|8,0:0|0:0,0:0:0:0: +1,194,60192,1,0,0:0:0:0: +84,230,60375,1,2,0:0:0:0: +173,216,60558,1,8,0:0:0:0: +173,216,60649,1,8,0:0:0:0: +173,216,60741,1,8,0:0:0:0: +263,213,60924,1,2,0:0:0:0: +345,174,61106,6,0,P|320:144|286:130,1,70,2|0,0:0|0:0,0:0:0:0: +200,134,61472,1,8,0:0:0:0: +249,57,61655,2,0,L|263:12,2,35,12|8|8,0:0|0:0|0:0,0:0:0:0: +157,64,62021,2,0,L|153:13,2,35,12|8|8,0:0|0:0|0:0,0:0:0:0: +118,150,62387,1,2,0:0:0:0: +101,260,62570,6,0,P|207:236|257:243,1,140,2|8,0:0|0:0,0:0:0:0: +328,304,63119,1,0,0:0:0:0: +434,156,63302,2,0,P|373:157|329:217,1,140,2|8,0:0|0:0,0:0:0:0: +408,230,63850,1,2,0:0:0:0: +483,215,64033,5,6,0:0:0:0: +508,142,64216,1,0,0:0:0:0: +482,69,64399,1,8,0:0:0:0: +413,34,64582,2,0,P|336:30|256:49,1,140,0|2,0:0|0:0,0:0:0:0: +150,97,65131,2,0,P|190:97|243:107,1,70,8|2,0:0|0:0,0:0:0:0: +257,168,65497,6,0,L|225:323,1,140,2|8,0:0|0:0,0:0:0:0: +155,329,66046,1,0,0:0:0:0: +20,204,66228,2,0,P|92:202|133:271,1,140,8|8,0:0|0:0,0:0:0:0: +56,274,66777,1,2,0:0:0:0: +18,125,66960,6,0,L|93:119,1,70,6|0,0:0|0:0,0:0:0:0: +162,156,67326,1,8,0:0:0:0: +223,52,67509,2,0,L|227:219,1,140,0|2,0:0|0:0,0:0:0:0: +266,263,68058,2,0,P|300:229|308:199,1,70,8|2,0:0|0:0,0:0:0:0: +298,95,68424,6,0,L|458:75,1,140,6|8,0:0|0:0,0:0:0:0: +512,164,68972,2,0,L|358:154,1,140,0|2,0:0|0:0,0:0:0:0: +306,209,69521,1,8,0:0:0:0: +342,334,69704,6,0,P|361:289|369:244,1,70,2|6,0:0|0:0,0:0:0:0: +250,277,70070,2,0,P|223:228|219:186,1,70,0|8,0:0|0:0,0:0:0:0: +272,128,70436,1,0,0:0:0:0: +172,111,70619,2,0,L|343:97,1,140,8|8,0:0|0:0,0:0:0:0: +385,128,71167,1,2,0:0:0:0: +494,63,71350,6,0,L|413:54,1,70,6|0,0:0|0:0,0:0:0:0: +385,128,71716,2,0,L|475:140,1,70,8|0,0:0|0:0,0:0:0:0: +467,217,72082,2,0,L|386:208,1,70,8|2,0:0|0:0,0:0:0:0: +358,282,72448,2,0,L|448:294,1,70,8|2,0:0|0:0,0:0:0:0: +498,339,72814,5,12,0:0:0:0: +498,339,72997,1,12,0:0:0:0: +301,343,73363,1,8,0:0:0:0: +211,173,73728,2,0,L|221:216,2,35,2|2|8,0:0|0:0|0:0,0:0:0:0: +250,100,74094,1,2,0:0:0:0: +123,92,74277,6,0,P|129:156|129:236,1,140,2|8,0:0|0:0,0:0:0:0: +109,321,74826,1,0,0:0:0:0: +211,173,75009,2,0,P|266:165|333:237,1,140,8|8,0:0|0:0,0:0:0:0: +341,302,75558,1,2,0:0:0:0: +418,272,75741,5,6,0:0:0:0: +484,322,75924,1,0,0:0:0:0: +407,352,76106,1,8,0:0:0:0: +341,302,76289,2,0,L|364:147,1,140,0|2,0:0|0:0,0:0:0:0: +269,60,76838,2,0,P|315:69|349:94,1,70,8|0,0:0|0:0,0:0:0:0: +269,150,77204,6,0,P|228:160|114:139,1,140,2|8,0:0|0:0,0:0:0:0: +49,80,77753,1,0,0:0:0:0: +39,235,77936,2,0,P|103:222|160:277,1,140,8|8,0:0|0:0,0:0:0:0: +82,297,78485,1,2,0:0:0:0: +227,326,78667,6,0,L|233:241,1,70,4|0,0:0|0:0,0:0:0:0: +269,150,79033,1,8,0:0:0:0: +408,194,79216,2,0,P|359:172|271:187,1,140,0|2,0:0|0:0,0:0:0:0: +409,281,79765,2,0,P|447:272|478:250,1,70,8|2,0:0|0:0,0:0:0:0: +497,168,80131,6,0,L|481:332,1,140,6|8,0:0|0:0,0:0:0:0: +389,365,80680,2,0,L|376:198,1,140,0|2,0:0|0:0,0:0:0:0: +414,157,81228,1,8,0:0:0:0: +229,89,81411,6,0,P|304:91|338:167,1,140,2|0,0:0|0:0,0:0:0:0: +290,222,81960,1,8,0:0:0:0: +211,214,82143,1,8,0:0:0:0: +93,155,82326,2,0,P|137:143|172:150,1,70,2|2,0:0|0:0,0:0:0:0: +235,301,82692,2,0,P|177:296|141:279,1,70,8|2,0:0|0:0,0:0:0:0: +68,244,83058,6,0,L|72:328,1,70,6|0,0:0|0:0,0:0:0:0: +166,292,83424,2,0,L|157:372,1,70,8|0,0:0|0:0,0:0:0:0: +254,227,83789,2,0,L|258:310,1,70,8|2,0:0|0:0,0:0:0:0: +345,265,84155,2,0,L|336:349,1,70,8|0,0:0|0:0,0:0:0:0: +331,175,84521,5,2,0:0:0:0: +416,205,84704,1,2,0:0:0:0: +481,141,84887,1,8,0:0:0:0: +431,64,85070,2,0,L|444:26,2,35,8|8|2,0:0|0:0|0:0,0:0:0:0: +339,79,85436,2,0,L|341:39,2,35,8|8|8,0:0|0:0|0:0,0:0:0:0: +256,109,85802,1,2,0:0:0:0: +165,97,85985,6,0,P|167:150|164:187,1,70,2|0,0:0|0:0,0:0:0:0: +117,244,86350,2,0,P|163:241|204:235,1,70,8|0,0:0|0:0,0:0:0:0: +229,317,86716,2,0,P|273:305|300:294,1,70,8|2,0:0|0:0,0:0:0:0: +365,354,87082,2,0,P|404:334|430:310,1,70,8|0,0:0|0:0,0:0:0:0: +352,230,87448,6,0,L|271:216,2,70,6|0|8,0:0|0:0|0:0,0:0:0:0: +378,142,87997,2,0,L|222:144,1,140,0|2,0:0|0:0,0:0:0:0: +152,112,88546,2,0,L|166:214,1,70,8|2,0:0|0:0,0:0:0:0: +139,270,88911,5,8,0:0:0:0: +12,138,89277,2,0,L|29:55,1,70,8|0,0:0|0:0,0:0:0:0: +91,5,89643,2,0,L|104:97,1,70,8|2,0:0|0:0,0:0:0:0: +153,149,90009,2,0,L|175:78,1,70,8|0,0:0|0:0,0:0:0:0: +279,36,90375,6,0,L|357:27,2,70,6|0|8,0:0|0:0|0:0,0:0:0:0: +248,122,90924,2,0,L|398:125,1,140,0|2,0:0|0:0,0:0:0:0: +479,123,91472,2,0,P|468:170|445:195,1,70,8|2,0:0|0:0,0:0:0:0: +365,204,91838,6,0,P|414:220|409:320,1,140,6|8,0:0|0:0,0:0:0:0: +354,354,92387,1,0,0:0:0:0: +262,353,92570,2,0,L|271:273,1,70,8|2,0:0|0:0,0:0:0:0: +297,196,92936,2,0,P|243:198|216:215,1,70,8|0,0:0|0:0,0:0:0:0: +172,276,93302,5,6,0:0:0:0: +137,360,93485,2,0,L|127:265,1,70,0|8,0:0|0:0,0:0:0:0: +81,212,93850,2,0,P|93:138|118:67,1,140,0|2,0:0|0:0,0:0:0:0: +170,4,94399,2,0,P|195:37|204:74,1,70,8|2,0:0|0:0,0:0:0:0: +186,153,94765,6,0,L|340:139,1,140,6|8,0:0|0:0,0:0:0:0: +408,101,95314,1,2,0:0:0:0: +443,184,95497,1,6,0:0:0:0: +369,237,95680,2,0,L|300:224,2,70,8|8|2,0:0|0:0|0:0,0:0:0:0: +448,282,96228,5,12,0:0:0:0: +448,282,96411,1,12,0:0:0:0: +270,320,96777,1,8,0:0:0:0: +313,143,97143,1,8,0:0:0:0: +377,314,97509,1,8,0:0:0:0: +256,192,97692,12,0,100619,0:0:0:0: diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index ccfe1501bd..184c09f378 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -64,7 +64,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps double roundedOverallDifficulty = Math.Round(difficulty.OverallDifficulty); int countSliderOrSpinner = difficulty.TotalObjectCount - difficulty.CircleCount; - float percentSpecialObjects = (float)countSliderOrSpinner / difficulty.TotalObjectCount; + + // In osu!stable, this division appears as if it happens on floats, but due to release-mode + // optimisations, it actually ends up happening on doubles. + double percentSpecialObjects = (double)countSliderOrSpinner / difficulty.TotalObjectCount; if (percentSpecialObjects < 0.2) return 7; From 767d5c8018abb2a8aca3d8aaf8a3259862f976e2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 9 Dec 2023 21:57:34 +0900 Subject: [PATCH 1889/4092] Add object counts to IBeatmapDifficultyInfo --- osu.Game/BackgroundDataStoreProcessor.cs | 38 +++++++++++++++++++ osu.Game/Beatmaps/BeatmapDifficulty.cs | 9 +++++ osu.Game/Beatmaps/BeatmapImporter.cs | 3 ++ osu.Game/Beatmaps/BeatmapUpdater.cs | 20 +++++++++- osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs | 13 +++++++ osu.Game/Database/RealmAccess.cs | 3 +- .../API/Requests/Responses/APIBeatmap.cs | 2 + .../LegacyBeatmapConversionDifficultyInfo.cs | 1 + 8 files changed, 87 insertions(+), 2 deletions(-) diff --git a/osu.Game/BackgroundDataStoreProcessor.cs b/osu.Game/BackgroundDataStoreProcessor.cs index 90e55dea6d..8195856991 100644 --- a/osu.Game/BackgroundDataStoreProcessor.cs +++ b/osu.Game/BackgroundDataStoreProcessor.cs @@ -21,6 +21,7 @@ using osu.Game.Rulesets; using osu.Game.Scoring; using osu.Game.Scoring.Legacy; using osu.Game.Screens.Play; +using Realms; namespace osu.Game { @@ -68,6 +69,7 @@ namespace osu.Game checkForOutdatedStarRatings(); processBeatmapSetsWithMissingMetrics(); + processBeatmapsWithMissingObjectCounts(); processScoresWithMissingStatistics(); convertLegacyTotalScoreToStandardised(); }, TaskCreationOptions.LongRunning).ContinueWith(t => @@ -178,6 +180,42 @@ namespace osu.Game } } + private void processBeatmapsWithMissingObjectCounts() + { + Logger.Log("Querying for beatmaps with missing hitobject counts to reprocess..."); + + HashSet beatmapIds = realmAccess.Run(r => new HashSet(r.All() + .Filter($"{nameof(BeatmapInfo.Difficulty)}.{nameof(BeatmapDifficulty.TotalObjectCount)} == 0") + .AsEnumerable().Select(b => b.ID))); + + Logger.Log($"Found {beatmapIds.Count} beatmaps which require reprocessing."); + + int i = 0; + + foreach (var id in beatmapIds) + { + sleepIfRequired(); + + realmAccess.Run(r => + { + var beatmap = r.Find(id); + + if (beatmap != null) + { + try + { + Logger.Log($"Background processing {beatmap} ({++i} / {beatmapIds.Count})"); + beatmapUpdater.ProcessObjectCounts(beatmap); + } + catch (Exception e) + { + Logger.Log($"Background processing failed on {beatmap}: {e}"); + } + } + }); + } + } + private void processScoresWithMissingStatistics() { HashSet scoreIds = new HashSet(); diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs index ac2267380d..785728141e 100644 --- a/osu.Game/Beatmaps/BeatmapDifficulty.cs +++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs @@ -21,6 +21,9 @@ namespace osu.Game.Beatmaps public double SliderMultiplier { get; set; } = 1.4; public double SliderTickRate { get; set; } = 1; + public int EndTimeObjectCount { get; set; } + public int TotalObjectCount { get; set; } + public BeatmapDifficulty() { } @@ -44,6 +47,9 @@ namespace osu.Game.Beatmaps difficulty.SliderMultiplier = SliderMultiplier; difficulty.SliderTickRate = SliderTickRate; + + difficulty.EndTimeObjectCount = EndTimeObjectCount; + difficulty.TotalObjectCount = TotalObjectCount; } public virtual void CopyFrom(IBeatmapDifficultyInfo other) @@ -55,6 +61,9 @@ namespace osu.Game.Beatmaps SliderMultiplier = other.SliderMultiplier; SliderTickRate = other.SliderTickRate; + + EndTimeObjectCount = other.EndTimeObjectCount; + TotalObjectCount = other.TotalObjectCount; } } } diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index e89e5339e1..31d6b0108e 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -20,6 +20,7 @@ using osu.Game.IO; using osu.Game.IO.Archives; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects.Types; using Realms; namespace osu.Game.Beatmaps @@ -388,6 +389,8 @@ namespace osu.Game.Beatmaps ApproachRate = decodedDifficulty.ApproachRate, SliderMultiplier = decodedDifficulty.SliderMultiplier, SliderTickRate = decodedDifficulty.SliderTickRate, + EndTimeObjectCount = decoded.HitObjects.Count(h => h is IHasDuration), + TotalObjectCount = decoded.HitObjects.Count }; var metadata = new BeatmapMetadata diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index 56bfdc5001..472ac26ebe 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using System.Linq; using System.Threading.Tasks; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Logging; @@ -10,6 +11,7 @@ using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Database; using osu.Game.Online.API; +using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Beatmaps { @@ -44,7 +46,8 @@ namespace osu.Game.Beatmaps public void Queue(Live beatmapSet, MetadataLookupScope lookupScope = MetadataLookupScope.LocalCacheFirst) { Logger.Log($"Queueing change for local beatmap {beatmapSet}"); - Task.Factory.StartNew(() => beatmapSet.PerformRead(b => Process(b, lookupScope)), default, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler); + Task.Factory.StartNew(() => beatmapSet.PerformRead(b => Process(b, lookupScope)), default, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, + updateScheduler); } /// @@ -80,6 +83,21 @@ namespace osu.Game.Beatmaps workingBeatmapCache.Invalidate(beatmapSet); }); + public void ProcessObjectCounts(BeatmapInfo beatmapInfo, MetadataLookupScope lookupScope = MetadataLookupScope.LocalCacheFirst) => beatmapInfo.Realm!.Write(_ => + { + // Before we use below, we want to invalidate. + workingBeatmapCache.Invalidate(beatmapInfo); + + var working = workingBeatmapCache.GetWorkingBeatmap(beatmapInfo); + var beatmap = working.Beatmap; + + beatmapInfo.Difficulty.EndTimeObjectCount = beatmap.HitObjects.Count(h => h is IHasDuration); + beatmapInfo.Difficulty.TotalObjectCount = beatmap.HitObjects.Count; + + // And invalidate again afterwards as re-fetching the most up-to-date database metadata will be required. + workingBeatmapCache.Invalidate(beatmapInfo); + }); + #region Implementation of IDisposable public void Dispose() diff --git a/osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs b/osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs index e7a3d87d0a..47b261d1f6 100644 --- a/osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs +++ b/osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs @@ -44,6 +44,19 @@ namespace osu.Game.Beatmaps /// double SliderTickRate { get; } + /// + /// The number of hitobjects in the beatmap with a distinct end time. + /// + /// + /// Canonically, these are hitobjects are either sliders or spinners. + /// + int EndTimeObjectCount { get; } + + /// + /// The total number of hitobjects in the beatmap. + /// + int TotalObjectCount { get; } + /// /// Maps a difficulty value [0, 10] to a two-piece linear range of values. /// diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index e9f49ec662..9c7fe464dd 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -88,8 +88,9 @@ namespace osu.Game.Database /// 34 2023-08-21 Add BackgroundReprocessingFailed flag to ScoreInfo to track upgrade failures. /// 35 2023-10-16 Clear key combinations of keybindings that are assigned to more than one action in a given settings section. /// 36 2023-10-26 Add LegacyOnlineID to ScoreInfo. Move osu_scores_*_high IDs stored in OnlineID to LegacyOnlineID. Reset anomalous OnlineIDs. + /// 37 2023-12-10 Add EndTimeObjectCount and TotalObjectCount to BeatmapDifficulty. /// - private const int schema_version = 36; + private const int schema_version = 37; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs index 902b651be9..c1ceff7c43 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs @@ -109,6 +109,8 @@ namespace osu.Game.Online.API.Requests.Responses CircleSize = CircleSize, ApproachRate = ApproachRate, OverallDifficulty = OverallDifficulty, + EndTimeObjectCount = SliderCount + SpinnerCount, + TotalObjectCount = CircleCount + SliderCount + SpinnerCount }; IBeatmapSetInfo? IBeatmapInfo.BeatmapSet => BeatmapSet; diff --git a/osu.Game/Rulesets/Scoring/Legacy/LegacyBeatmapConversionDifficultyInfo.cs b/osu.Game/Rulesets/Scoring/Legacy/LegacyBeatmapConversionDifficultyInfo.cs index 97ccf787af..9c9294417f 100644 --- a/osu.Game/Rulesets/Scoring/Legacy/LegacyBeatmapConversionDifficultyInfo.cs +++ b/osu.Game/Rulesets/Scoring/Legacy/LegacyBeatmapConversionDifficultyInfo.cs @@ -45,6 +45,7 @@ namespace osu.Game.Rulesets.Scoring.Legacy float IBeatmapDifficultyInfo.ApproachRate => 0; double IBeatmapDifficultyInfo.SliderMultiplier => 0; double IBeatmapDifficultyInfo.SliderTickRate => 0; + int IBeatmapDifficultyInfo.EndTimeObjectCount => TotalObjectCount - CircleCount; public static LegacyBeatmapConversionDifficultyInfo FromAPIBeatmap(APIBeatmap apiBeatmap) => new LegacyBeatmapConversionDifficultyInfo { From b36db3518c79486a94fc97411fbf340fd3741a38 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 9 Dec 2023 22:09:49 +0900 Subject: [PATCH 1890/4092] Add keycount to song select details panel and carousel panels --- .../Beatmaps/ManiaBeatmapConverter.cs | 13 ++----- .../ManiaFilterCriteria.cs | 3 +- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 3 ++ osu.Game/Rulesets/ILegacyRuleset.cs | 7 ++++ .../LegacyBeatmapConversionDifficultyInfo.cs | 28 +++++--------- .../Carousel/DrawableCarouselBeatmap.cs | 37 +++++++++++++++++++ .../Screens/Select/Details/AdvancedStats.cs | 16 ++++++-- 7 files changed, 74 insertions(+), 33 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index ccfe1501bd..c4a8db92ed 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -57,10 +57,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps public static int GetColumnCount(LegacyBeatmapConversionDifficultyInfo difficulty) { - if (new ManiaRuleset().RulesetInfo.Equals(difficulty.SourceRuleset)) - return GetColumnCountForNonConvert(difficulty); - double roundedCircleSize = Math.Round(difficulty.CircleSize); + + if (new ManiaRuleset().RulesetInfo.Equals(difficulty.SourceRuleset)) + return (int)Math.Max(1, roundedCircleSize); + double roundedOverallDifficulty = Math.Round(difficulty.OverallDifficulty); int countSliderOrSpinner = difficulty.TotalObjectCount - difficulty.CircleCount; @@ -76,12 +77,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps return Math.Max(4, Math.Min((int)roundedOverallDifficulty + 1, 7)); } - public static int GetColumnCountForNonConvert(IBeatmapDifficultyInfo difficulty) - { - double roundedCircleSize = Math.Round(difficulty.CircleSize); - return (int)Math.Max(1, roundedCircleSize); - } - public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition); protected override Beatmap ConvertBeatmap(IBeatmap original, CancellationToken cancellationToken) diff --git a/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs b/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs index 7f8a00bf88..930ca217cd 100644 --- a/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs +++ b/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs @@ -4,6 +4,7 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Filter; using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Scoring.Legacy; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Filter; @@ -15,7 +16,7 @@ namespace osu.Game.Rulesets.Mania public bool Matches(BeatmapInfo beatmapInfo) { - return !keys.HasFilter || (beatmapInfo.Ruleset.OnlineID == new ManiaRuleset().LegacyID && keys.IsInRange(ManiaBeatmapConverter.GetColumnCountForNonConvert(beatmapInfo.Difficulty))); + return !keys.HasFilter || keys.IsInRange(ManiaBeatmapConverter.GetColumnCount(LegacyBeatmapConversionDifficultyInfo.FromBeatmapInfo(beatmapInfo))); } public bool TryParseCustomKeywordCriteria(string key, Operator op, string value) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 0c317e0f8a..c38d6519bd 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -420,6 +420,9 @@ namespace osu.Game.Rulesets.Mania public override RulesetSetupSection CreateEditorSetupSection() => new ManiaSetupSection(); public override DifficultySection CreateEditorDifficultySection() => new ManiaDifficultySection(); + + public int GetKeyCount(IBeatmapInfo beatmapInfo) + => ManiaBeatmapConverter.GetColumnCount(LegacyBeatmapConversionDifficultyInfo.FromBeatmapInfo(beatmapInfo)); } public enum PlayfieldType diff --git a/osu.Game/Rulesets/ILegacyRuleset.cs b/osu.Game/Rulesets/ILegacyRuleset.cs index 6900afa243..18d86f477a 100644 --- a/osu.Game/Rulesets/ILegacyRuleset.cs +++ b/osu.Game/Rulesets/ILegacyRuleset.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; using osu.Game.Rulesets.Scoring.Legacy; namespace osu.Game.Rulesets @@ -14,6 +15,12 @@ namespace osu.Game.Rulesets /// int LegacyID { get; } + /// + /// Retrieves the number of mania keys required to play the beatmap. + /// + /// + int GetKeyCount(IBeatmapInfo beatmapInfo) => 0; + ILegacyScoreSimulator CreateLegacyScoreSimulator(); } } diff --git a/osu.Game/Rulesets/Scoring/Legacy/LegacyBeatmapConversionDifficultyInfo.cs b/osu.Game/Rulesets/Scoring/Legacy/LegacyBeatmapConversionDifficultyInfo.cs index 9c9294417f..6f379e4ef1 100644 --- a/osu.Game/Rulesets/Scoring/Legacy/LegacyBeatmapConversionDifficultyInfo.cs +++ b/osu.Game/Rulesets/Scoring/Legacy/LegacyBeatmapConversionDifficultyInfo.cs @@ -1,10 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; using osu.Game.Beatmaps; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Scoring.Legacy { @@ -31,9 +29,6 @@ namespace osu.Game.Rulesets.Scoring.Legacy /// /// The count of hitcircles in the beatmap. /// - /// - /// When converting from osu! ruleset beatmaps, this is equivalent to the sum of sliders and spinners in the beatmap. - /// public int CircleCount { get; set; } /// @@ -47,22 +42,17 @@ namespace osu.Game.Rulesets.Scoring.Legacy double IBeatmapDifficultyInfo.SliderTickRate => 0; int IBeatmapDifficultyInfo.EndTimeObjectCount => TotalObjectCount - CircleCount; - public static LegacyBeatmapConversionDifficultyInfo FromAPIBeatmap(APIBeatmap apiBeatmap) => new LegacyBeatmapConversionDifficultyInfo - { - SourceRuleset = apiBeatmap.Ruleset, - CircleSize = apiBeatmap.CircleSize, - OverallDifficulty = apiBeatmap.OverallDifficulty, - CircleCount = apiBeatmap.CircleCount, - TotalObjectCount = apiBeatmap.SliderCount + apiBeatmap.SpinnerCount + apiBeatmap.CircleCount - }; + public static LegacyBeatmapConversionDifficultyInfo FromAPIBeatmap(APIBeatmap apiBeatmap) => FromBeatmapInfo(apiBeatmap); - public static LegacyBeatmapConversionDifficultyInfo FromBeatmap(IBeatmap beatmap) => new LegacyBeatmapConversionDifficultyInfo + public static LegacyBeatmapConversionDifficultyInfo FromBeatmap(IBeatmap beatmap) => FromBeatmapInfo(beatmap.BeatmapInfo); + + public static LegacyBeatmapConversionDifficultyInfo FromBeatmapInfo(IBeatmapInfo beatmapInfo) => new LegacyBeatmapConversionDifficultyInfo { - SourceRuleset = beatmap.BeatmapInfo.Ruleset, - CircleSize = beatmap.Difficulty.CircleSize, - OverallDifficulty = beatmap.Difficulty.OverallDifficulty, - CircleCount = beatmap.HitObjects.Count(h => h is not IHasDuration), - TotalObjectCount = beatmap.HitObjects.Count + SourceRuleset = beatmapInfo.Ruleset, + CircleSize = beatmapInfo.Difficulty.CircleSize, + OverallDifficulty = beatmapInfo.Difficulty.OverallDifficulty, + CircleCount = beatmapInfo.Difficulty.TotalObjectCount - beatmapInfo.Difficulty.EndTimeObjectCount, + TotalObjectCount = beatmapInfo.Difficulty.TotalObjectCount }; } } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 3dfd801f02..cda95a9d29 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -27,6 +27,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Resources.Localisation.Web; +using osu.Game.Rulesets; using osuTK; using osuTK.Graphics; @@ -57,6 +58,8 @@ namespace osu.Game.Screens.Select.Carousel private StarCounter starCounter = null!; private DifficultyIcon difficultyIcon = null!; + private OsuSpriteText keyCountText = null!; + [Resolved] private BeatmapSetOverlay? beatmapOverlay { get; set; } @@ -69,6 +72,9 @@ namespace osu.Game.Screens.Select.Carousel [Resolved] private RealmAccess realm { get; set; } = null!; + [Resolved] + private IBindable ruleset { get; set; } = null!; + private IBindable starDifficultyBindable = null!; private CancellationTokenSource? starDifficultyCancellationSource; @@ -133,6 +139,13 @@ namespace osu.Game.Screens.Select.Carousel AutoSizeAxes = Axes.Both, Children = new[] { + keyCountText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 20), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Alpha = 0, + }, new OsuSpriteText { Text = beatmapInfo.DifficultyName, @@ -167,6 +180,13 @@ namespace osu.Game.Screens.Select.Carousel }; } + protected override void LoadComplete() + { + base.LoadComplete(); + + ruleset.BindValueChanged(_ => updateKeyCount()); + } + protected override void Selected() { base.Selected(); @@ -216,11 +236,28 @@ namespace osu.Game.Screens.Select.Carousel if (d.NewValue != null) difficultyIcon.Current.Value = d.NewValue.Value; }, true); + + updateKeyCount(); } base.ApplyState(); } + private void updateKeyCount() + { + if (ruleset.Value.OnlineID == 3) + { + // Account for mania differences locally for now. + // Eventually this should be handled in a more modular way, allowing rulesets to add more information to the panel. + ILegacyRuleset legacyRuleset = (ILegacyRuleset)ruleset.Value.CreateInstance(); + + keyCountText.Alpha = 1; + keyCountText.Text = $"[{legacyRuleset.GetKeyCount(beatmapInfo)}K]"; + } + else + keyCountText.Alpha = 0; + } + public MenuItem[] ContextMenuItems { get diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index a383298faa..87185c351e 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -126,13 +126,21 @@ namespace osu.Game.Screens.Select.Details mod.ApplyToDifficulty(adjustedDifficulty); } - switch (BeatmapInfo?.Ruleset.OnlineID) + switch (gameRuleset.Value.OnlineID) { case 3: - // Account for mania differences locally for now - // Eventually this should be handled in a more modular way, allowing rulesets to return arbitrary difficulty attributes + // Account for mania differences locally for now. + // Eventually this should be handled in a more modular way, allowing rulesets to return arbitrary difficulty attributes. + ILegacyRuleset legacyRuleset = (ILegacyRuleset)gameRuleset.Value.CreateInstance(); + + // For the time being, the key count is static no matter what, because: + // a) The method doesn't have knowledge of the active keymods. Doing so may require considerations for filtering. + // b) Using the difficulty adjustment mod to adjust OD doesn't have an effect on conversion. + int keyCount = baseDifficulty == null ? 0 : legacyRuleset.GetKeyCount(BeatmapInfo); + FirstValue.Title = BeatmapsetsStrings.ShowStatsCsMania; - FirstValue.Value = (baseDifficulty?.CircleSize ?? 0, null); + FirstValue.Value = (keyCount, keyCount); + break; default: From 1d0c37e13875b57b4f8dfaf2f0b4ab299b222a15 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 10 Dec 2023 00:40:05 +0200 Subject: [PATCH 1891/4092] fixed test errors --- osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs | 10 ++++++---- osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs | 6 +++--- osu.Game/Screens/Select/Details/AdvancedStats.cs | 8 -------- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs b/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs index 3e800bfaf1..5314c6f4a9 100644 --- a/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs +++ b/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs @@ -17,11 +17,11 @@ namespace osu.Game.Overlays.Mods { public partial class AdjustedAttributesTooltip : CompositeDrawable, ITooltip { - private Dictionary> attributes = new Dictionary>(); + private readonly Dictionary> attributes = new Dictionary>(); - private Container content; + private readonly Container content; - private FillFlowContainer attributesFillFlow; + private readonly FillFlowContainer attributesFillFlow; [Resolved] private OsuColour colours { get; set; } = null!; @@ -83,8 +83,8 @@ namespace osu.Game.Overlays.Mods return; } } - content.Hide(); + content.Hide(); } public void AddAttribute(string name) @@ -97,6 +97,8 @@ namespace osu.Game.Overlays.Mods public void UpdateAttribute(string name, double oldValue, double newValue) { + if (!attributes.ContainsKey(name)) return; + Bindable attribute = attributes[name]; OldNewPair attributeValue = attribute.Value; diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index fa8f9cb7a4..d74f088ac2 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -104,6 +104,9 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); + rateAdjustTooltip.AddAttribute("AR"); + rateAdjustTooltip.AddAttribute("OD"); + mods.BindValueChanged(_ => { modSettingChangeTracker?.Dispose(); @@ -126,9 +129,6 @@ namespace osu.Game.Overlays.Mods BeatmapInfo.BindValueChanged(_ => updateValues(), true); - rateAdjustTooltip.AddAttribute("AR"); - rateAdjustTooltip.AddAttribute("OD"); - updateCollapsedState(); } diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index d3a6a88c68..30cb0601f3 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -214,14 +214,6 @@ namespace osu.Game.Screens.Select.Details starDifficultyCancellationSource?.Cancel(); } - private static bool hasRateAdjustedProperties(BeatmapDifficulty a, BeatmapDifficulty b) - { - if (!Precision.AlmostEquals(a.ApproachRate, b.ApproachRate)) return true; - if (!Precision.AlmostEquals(a.OverallDifficulty, b.OverallDifficulty)) return true; - - return false; - } - public partial class StatisticRow : Container, IHasAccentColour { private const float value_width = 25; From 78cdedf34d1f0c24b5b1f7d342c58fc96d27e18a Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 10 Dec 2023 00:42:02 +0200 Subject: [PATCH 1892/4092] Update BeatmapAttributesDisplay.cs --- osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index d74f088ac2..0c35e55df5 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -62,7 +62,6 @@ namespace osu.Game.Overlays.Mods public ITooltip GetCustomTooltip() => rateAdjustTooltip; public object TooltipContent => this; - private const float transition_duration = 250; [BackgroundDependencyLoader] From f5b93121f1194591aab5d538062e48e7315f6589 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 10 Dec 2023 00:51:50 +0200 Subject: [PATCH 1893/4092] Update AdjustedAttributesTooltip.cs --- osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs b/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs index 5314c6f4a9..e9b7ee5c54 100644 --- a/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs +++ b/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs @@ -111,6 +111,7 @@ namespace osu.Game.Overlays.Mods protected override void Update() { } + public void SetContent(object content) { } @@ -128,12 +129,13 @@ namespace osu.Game.Overlays.Mods private partial class AttributeDisplay : CompositeDrawable { public readonly Bindable AttributeValues; - public string AttributeName; + public readonly string AttributeName; - private OsuSpriteText text = new OsuSpriteText + private readonly OsuSpriteText text = new OsuSpriteText { Font = OsuFont.Default.With(weight: FontWeight.Bold) }; + public AttributeDisplay(string name, Bindable boundCopy) { AutoSizeAxes = Axes.Both; From 2d94841929aae312a09b81c8aee45d2befabcb27 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 10 Dec 2023 02:00:32 +0200 Subject: [PATCH 1894/4092] fixed one test --- osu.Game/Screens/Select/Details/AdvancedStats.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 30cb0601f3..819fe122fa 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -128,7 +128,8 @@ namespace osu.Game.Screens.Select.Details IBeatmapDifficultyInfo baseDifficulty = BeatmapInfo?.Difficulty; BeatmapDifficulty adjustedDifficulty = null; - if (baseDifficulty != null) + if (baseDifficulty != null && + (mods.Value.Any(m => m is IApplicableToDifficulty) || mods.Value.Any(m => m is IApplicableToRate))) { BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(baseDifficulty); From 6320194e19510bcd7cb26ad69b84a492fb0db6ab Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 11 Dec 2023 21:52:39 +0900 Subject: [PATCH 1895/4092] Fix catch applying positional clamping too early --- .../CatchBeatmapConversionTest.cs | 1 + .../Beatmaps/112643-expected-conversion.json | 1 + .../Resources/Testing/Beatmaps/112643.osu | 582 ++++++++++++++++++ .../Beatmaps/CatchBeatmapProcessor.cs | 12 + .../Objects/JuiceStream.cs | 12 +- 5 files changed, 600 insertions(+), 8 deletions(-) create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/112643-expected-conversion.json create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/112643.osu diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs index 7572c6670f..d0ecb828df 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs @@ -52,6 +52,7 @@ namespace osu.Game.Rulesets.Catch.Tests [TestCase("3644427", new[] { typeof(CatchModEasy), typeof(CatchModFlashlight) })] [TestCase("3689906", new[] { typeof(CatchModDoubleTime), typeof(CatchModEasy) })] [TestCase("3949367", new[] { typeof(CatchModDoubleTime), typeof(CatchModEasy) })] + [TestCase("112643")] public new void Test(string name, params Type[] mods) => base.Test(name, mods); protected override IEnumerable CreateConvertValue(HitObject hitObject) diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/112643-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/112643-expected-conversion.json new file mode 100644 index 0000000000..7d6e29b6c1 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/112643-expected-conversion.json @@ -0,0 +1 @@ +{"Mappings":[{"StartTime":2375.0,"Objects":[{"StartTime":2375.0,"Position":64.0,"HyperDash":false}]},{"StartTime":2625.0,"Objects":[{"StartTime":2625.0,"Position":172.0,"HyperDash":false}]},{"StartTime":2875.0,"Objects":[{"StartTime":2875.0,"Position":152.0,"HyperDash":false}]},{"StartTime":3125.0,"Objects":[{"StartTime":3125.0,"Position":80.0,"HyperDash":false}]},{"StartTime":3375.0,"Objects":[{"StartTime":3375.0,"Position":224.0,"HyperDash":false}]},{"StartTime":3625.0,"Objects":[{"StartTime":3625.0,"Position":192.0,"HyperDash":false}]},{"StartTime":3875.0,"Objects":[{"StartTime":3875.0,"Position":136.0,"HyperDash":false}]},{"StartTime":4125.0,"Objects":[{"StartTime":4125.0,"Position":272.0,"HyperDash":false},{"StartTime":4187.0,"Position":295.965057,"HyperDash":false},{"StartTime":4250.0,"Position":339.30658,"HyperDash":false},{"StartTime":4312.0,"Position":372.55603,"HyperDash":false},{"StartTime":4375.0,"Position":372.509583,"HyperDash":false},{"StartTime":4437.0,"Position":372.203644,"HyperDash":false},{"StartTime":4500.0,"Position":340.885864,"HyperDash":false},{"StartTime":4562.0,"Position":348.843384,"HyperDash":false},{"StartTime":4625.0,"Position":384.566772,"HyperDash":false},{"StartTime":4749.0,"Position":462.643433,"HyperDash":false}]},{"StartTime":4875.0,"Objects":[{"StartTime":4875.0,"Position":504.0,"HyperDash":false},{"StartTime":4937.0,"Position":456.809235,"HyperDash":false},{"StartTime":5000.0,"Position":413.577362,"HyperDash":false},{"StartTime":5062.0,"Position":384.032623,"HyperDash":false},{"StartTime":5125.0,"Position":351.76297,"HyperDash":false},{"StartTime":5178.0,"Position":327.56488,"HyperDash":false},{"StartTime":5232.0,"Position":288.905457,"HyperDash":false},{"StartTime":5285.0,"Position":281.458923,"HyperDash":false},{"StartTime":5375.0,"Position":249.3499,"HyperDash":false}]},{"StartTime":5625.0,"Objects":[{"StartTime":5625.0,"Position":384.0,"HyperDash":false}]},{"StartTime":5875.0,"Objects":[{"StartTime":5875.0,"Position":272.0,"HyperDash":false}]},{"StartTime":6000.0,"Objects":[{"StartTime":6000.0,"Position":272.0,"HyperDash":false}]},{"StartTime":6125.0,"Objects":[{"StartTime":6125.0,"Position":272.0,"HyperDash":false}]},{"StartTime":6375.0,"Objects":[{"StartTime":6375.0,"Position":92.0,"HyperDash":false}]},{"StartTime":6625.0,"Objects":[{"StartTime":6625.0,"Position":124.0,"HyperDash":false}]},{"StartTime":6875.0,"Objects":[{"StartTime":6875.0,"Position":256.0,"HyperDash":false}]},{"StartTime":7125.0,"Objects":[{"StartTime":7125.0,"Position":388.0,"HyperDash":false}]},{"StartTime":7375.0,"Objects":[{"StartTime":7375.0,"Position":420.0,"HyperDash":false}]},{"StartTime":7625.0,"Objects":[{"StartTime":7625.0,"Position":256.0,"HyperDash":false}]},{"StartTime":7875.0,"Objects":[{"StartTime":7875.0,"Position":256.0,"HyperDash":false}]},{"StartTime":8125.0,"Objects":[{"StartTime":8125.0,"Position":443.0,"HyperDash":false},{"StartTime":8187.0,"Position":392.598877,"HyperDash":false},{"StartTime":8250.0,"Position":365.1502,"HyperDash":false},{"StartTime":8312.0,"Position":352.954926,"HyperDash":false},{"StartTime":8375.0,"Position":294.614716,"HyperDash":false},{"StartTime":8437.0,"Position":268.171936,"HyperDash":false},{"StartTime":8500.0,"Position":207.09552,"HyperDash":false},{"StartTime":8562.0,"Position":158.395874,"HyperDash":false},{"StartTime":8625.0,"Position":135.590256,"HyperDash":false},{"StartTime":8749.0,"Position":67.66239,"HyperDash":false}]},{"StartTime":8875.0,"Objects":[{"StartTime":8875.0,"Position":24.0,"HyperDash":false},{"StartTime":8937.0,"Position":54.41505,"HyperDash":false},{"StartTime":9000.0,"Position":92.0854,"HyperDash":false},{"StartTime":9062.0,"Position":91.62684,"HyperDash":false},{"StartTime":9125.0,"Position":114.961037,"HyperDash":false},{"StartTime":9178.0,"Position":112.725426,"HyperDash":false},{"StartTime":9232.0,"Position":118.526962,"HyperDash":false},{"StartTime":9285.0,"Position":72.53759,"HyperDash":false},{"StartTime":9374.0,"Position":43.35332,"HyperDash":false}]},{"StartTime":9625.0,"Objects":[{"StartTime":9625.0,"Position":16.0,"HyperDash":false}]},{"StartTime":9875.0,"Objects":[{"StartTime":9875.0,"Position":136.0,"HyperDash":false}]},{"StartTime":10000.0,"Objects":[{"StartTime":10000.0,"Position":136.0,"HyperDash":false}]},{"StartTime":10125.0,"Objects":[{"StartTime":10125.0,"Position":136.0,"HyperDash":false}]},{"StartTime":10375.0,"Objects":[{"StartTime":10375.0,"Position":256.0,"HyperDash":false}]},{"StartTime":10625.0,"Objects":[{"StartTime":10625.0,"Position":368.0,"HyperDash":false}]},{"StartTime":10875.0,"Objects":[{"StartTime":10875.0,"Position":196.0,"HyperDash":false}]},{"StartTime":11125.0,"Objects":[{"StartTime":11125.0,"Position":316.0,"HyperDash":false}]},{"StartTime":11375.0,"Objects":[{"StartTime":11375.0,"Position":144.0,"HyperDash":false}]},{"StartTime":11625.0,"Objects":[{"StartTime":11625.0,"Position":256.0,"HyperDash":false}]},{"StartTime":11875.0,"Objects":[{"StartTime":11875.0,"Position":112.0,"HyperDash":false}]},{"StartTime":12125.0,"Objects":[{"StartTime":12125.0,"Position":164.0,"HyperDash":false},{"StartTime":12250.0,"Position":238.49942,"HyperDash":false}]},{"StartTime":12500.0,"Objects":[{"StartTime":12500.0,"Position":100.0,"HyperDash":false},{"StartTime":12625.0,"Position":25.50058,"HyperDash":false}]},{"StartTime":12875.0,"Objects":[{"StartTime":12875.0,"Position":144.0,"HyperDash":false},{"StartTime":13000.0,"Position":69.50058,"HyperDash":false}]},{"StartTime":13250.0,"Objects":[{"StartTime":13250.0,"Position":208.0,"HyperDash":false},{"StartTime":13375.0,"Position":282.49942,"HyperDash":false}]},{"StartTime":13625.0,"Objects":[{"StartTime":13625.0,"Position":332.0,"HyperDash":false}]},{"StartTime":13875.0,"Objects":[{"StartTime":13875.0,"Position":180.0,"HyperDash":false}]},{"StartTime":14125.0,"Objects":[{"StartTime":14125.0,"Position":256.0,"HyperDash":false}]},{"StartTime":14250.0,"Objects":[{"StartTime":14250.0,"Position":256.0,"HyperDash":false}]},{"StartTime":14500.0,"Objects":[{"StartTime":14500.0,"Position":324.0,"HyperDash":false}]},{"StartTime":14625.0,"Objects":[{"StartTime":14625.0,"Position":324.0,"HyperDash":false}]},{"StartTime":14875.0,"Objects":[{"StartTime":14875.0,"Position":192.0,"HyperDash":false}]},{"StartTime":15000.0,"Objects":[{"StartTime":15000.0,"Position":192.0,"HyperDash":false}]},{"StartTime":15250.0,"Objects":[{"StartTime":15250.0,"Position":256.0,"HyperDash":false}]},{"StartTime":15375.0,"Objects":[{"StartTime":15375.0,"Position":256.0,"HyperDash":false}]},{"StartTime":15625.0,"Objects":[{"StartTime":15625.0,"Position":256.0,"HyperDash":false}]},{"StartTime":15875.0,"Objects":[{"StartTime":15875.0,"Position":120.0,"HyperDash":false}]},{"StartTime":16125.0,"Objects":[{"StartTime":16125.0,"Position":256.0,"HyperDash":false}]},{"StartTime":18375.0,"Objects":[{"StartTime":18375.0,"Position":20.0,"HyperDash":false}]},{"StartTime":18625.0,"Objects":[{"StartTime":18625.0,"Position":180.0,"HyperDash":false}]},{"StartTime":18875.0,"Objects":[{"StartTime":18875.0,"Position":52.0,"HyperDash":false}]},{"StartTime":19125.0,"Objects":[{"StartTime":19125.0,"Position":120.0,"HyperDash":false}]},{"StartTime":19375.0,"Objects":[{"StartTime":19375.0,"Position":128.0,"HyperDash":false}]},{"StartTime":19625.0,"Objects":[{"StartTime":19625.0,"Position":48.0,"HyperDash":false}]},{"StartTime":19875.0,"Objects":[{"StartTime":19875.0,"Position":192.0,"HyperDash":false}]},{"StartTime":20125.0,"Objects":[{"StartTime":20125.0,"Position":300.0,"HyperDash":false},{"StartTime":20187.0,"Position":319.510284,"HyperDash":false},{"StartTime":20250.0,"Position":361.959717,"HyperDash":false},{"StartTime":20312.0,"Position":410.823639,"HyperDash":false},{"StartTime":20375.0,"Position":393.9937,"HyperDash":false},{"StartTime":20428.0,"Position":389.407,"HyperDash":false},{"StartTime":20482.0,"Position":394.563232,"HyperDash":false},{"StartTime":20535.0,"Position":430.098541,"HyperDash":false},{"StartTime":20624.0,"Position":486.9303,"HyperDash":false}]},{"StartTime":20875.0,"Objects":[{"StartTime":20875.0,"Position":472.0,"HyperDash":false},{"StartTime":20937.0,"Position":454.614349,"HyperDash":false},{"StartTime":21000.0,"Position":395.812744,"HyperDash":false},{"StartTime":21062.0,"Position":377.009979,"HyperDash":false},{"StartTime":21125.0,"Position":345.3677,"HyperDash":false},{"StartTime":21178.0,"Position":342.8652,"HyperDash":false},{"StartTime":21232.0,"Position":325.856567,"HyperDash":false},{"StartTime":21285.0,"Position":310.223846,"HyperDash":false},{"StartTime":21374.0,"Position":280.7244,"HyperDash":false}]},{"StartTime":21625.0,"Objects":[{"StartTime":21625.0,"Position":404.0,"HyperDash":false}]},{"StartTime":21875.0,"Objects":[{"StartTime":21875.0,"Position":432.0,"HyperDash":false}]},{"StartTime":22000.0,"Objects":[{"StartTime":22000.0,"Position":432.0,"HyperDash":false}]},{"StartTime":22125.0,"Objects":[{"StartTime":22125.0,"Position":432.0,"HyperDash":false}]},{"StartTime":22375.0,"Objects":[{"StartTime":22375.0,"Position":296.0,"HyperDash":false}]},{"StartTime":22625.0,"Objects":[{"StartTime":22625.0,"Position":168.0,"HyperDash":false},{"StartTime":22678.0,"Position":157.672318,"HyperDash":false},{"StartTime":22732.0,"Position":121.82901,"HyperDash":false},{"StartTime":22785.0,"Position":68.50134,"HyperDash":false},{"StartTime":22875.0,"Position":39.09584,"HyperDash":false}]},{"StartTime":23125.0,"Objects":[{"StartTime":23125.0,"Position":268.0,"HyperDash":false},{"StartTime":23178.0,"Position":252.906113,"HyperDash":false},{"StartTime":23232.0,"Position":215.4331,"HyperDash":false},{"StartTime":23285.0,"Position":192.339218,"HyperDash":false},{"StartTime":23375.0,"Position":173.217529,"HyperDash":false}]},{"StartTime":23625.0,"Objects":[{"StartTime":23625.0,"Position":252.0,"HyperDash":false},{"StartTime":23678.0,"Position":297.327667,"HyperDash":false},{"StartTime":23732.0,"Position":299.171,"HyperDash":false},{"StartTime":23785.0,"Position":350.498657,"HyperDash":false},{"StartTime":23875.0,"Position":380.904175,"HyperDash":false}]},{"StartTime":24125.0,"Objects":[{"StartTime":24125.0,"Position":484.0,"HyperDash":false},{"StartTime":24187.0,"Position":459.330444,"HyperDash":false},{"StartTime":24250.0,"Position":410.3108,"HyperDash":false},{"StartTime":24312.0,"Position":381.927948,"HyperDash":false},{"StartTime":24375.0,"Position":342.702942,"HyperDash":false},{"StartTime":24437.0,"Position":307.727,"HyperDash":false},{"StartTime":24500.0,"Position":254.618744,"HyperDash":false},{"StartTime":24562.0,"Position":219.823792,"HyperDash":false},{"StartTime":24625.0,"Position":195.842667,"HyperDash":false},{"StartTime":24750.0,"Position":124.114441,"HyperDash":false}]},{"StartTime":24875.0,"Objects":[{"StartTime":24875.0,"Position":72.0,"HyperDash":false},{"StartTime":24937.0,"Position":90.6446,"HyperDash":false},{"StartTime":25000.0,"Position":102.976662,"HyperDash":false},{"StartTime":25062.0,"Position":121.259918,"HyperDash":false},{"StartTime":25125.0,"Position":115.072632,"HyperDash":false},{"StartTime":25178.0,"Position":104.017952,"HyperDash":false},{"StartTime":25232.0,"Position":66.87554,"HyperDash":false},{"StartTime":25285.0,"Position":53.7148743,"HyperDash":false},{"StartTime":25374.0,"Position":0.0,"HyperDash":false}]},{"StartTime":25625.0,"Objects":[{"StartTime":25625.0,"Position":56.0,"HyperDash":false}]},{"StartTime":25875.0,"Objects":[{"StartTime":25875.0,"Position":176.0,"HyperDash":false}]},{"StartTime":26000.0,"Objects":[{"StartTime":26000.0,"Position":176.0,"HyperDash":false}]},{"StartTime":26125.0,"Objects":[{"StartTime":26125.0,"Position":176.0,"HyperDash":false}]},{"StartTime":26375.0,"Objects":[{"StartTime":26375.0,"Position":316.0,"HyperDash":false}]},{"StartTime":26625.0,"Objects":[{"StartTime":26625.0,"Position":464.0,"HyperDash":false},{"StartTime":26678.0,"Position":423.678864,"HyperDash":false},{"StartTime":26732.0,"Position":428.026764,"HyperDash":false},{"StartTime":26785.0,"Position":431.558746,"HyperDash":false},{"StartTime":26875.0,"Position":408.8022,"HyperDash":false}]},{"StartTime":27125.0,"Objects":[{"StartTime":27125.0,"Position":232.0,"HyperDash":false},{"StartTime":27178.0,"Position":266.0937,"HyperDash":false},{"StartTime":27232.0,"Position":284.472229,"HyperDash":false},{"StartTime":27285.0,"Position":289.223022,"HyperDash":false},{"StartTime":27374.0,"Position":288.2113,"HyperDash":false}]},{"StartTime":27625.0,"Objects":[{"StartTime":27625.0,"Position":136.0,"HyperDash":false}]},{"StartTime":27875.0,"Objects":[{"StartTime":27875.0,"Position":60.0,"HyperDash":false}]},{"StartTime":28125.0,"Objects":[{"StartTime":28125.0,"Position":212.0,"HyperDash":false},{"StartTime":28250.0,"Position":244.219086,"HyperDash":false}]},{"StartTime":28500.0,"Objects":[{"StartTime":28500.0,"Position":340.0,"HyperDash":false},{"StartTime":28625.0,"Position":372.2191,"HyperDash":false}]},{"StartTime":28875.0,"Objects":[{"StartTime":28875.0,"Position":256.0,"HyperDash":false},{"StartTime":29000.0,"Position":223.780914,"HyperDash":false}]},{"StartTime":29250.0,"Objects":[{"StartTime":29250.0,"Position":128.0,"HyperDash":false},{"StartTime":29375.0,"Position":95.7809143,"HyperDash":false}]},{"StartTime":29625.0,"Objects":[{"StartTime":29625.0,"Position":238.0,"HyperDash":false},{"StartTime":29678.0,"Position":279.04657,"HyperDash":false},{"StartTime":29731.0,"Position":322.09314,"HyperDash":false},{"StartTime":29784.0,"Position":325.1397,"HyperDash":false},{"StartTime":29874.0,"Position":397.954651,"HyperDash":false}]},{"StartTime":30125.0,"Objects":[{"StartTime":30125.0,"Position":512.0,"HyperDash":false}]},{"StartTime":30250.0,"Objects":[{"StartTime":30250.0,"Position":512.0,"HyperDash":false}]},{"StartTime":30500.0,"Objects":[{"StartTime":30500.0,"Position":416.0,"HyperDash":false}]},{"StartTime":30625.0,"Objects":[{"StartTime":30625.0,"Position":416.0,"HyperDash":false}]},{"StartTime":30875.0,"Objects":[{"StartTime":30875.0,"Position":300.0,"HyperDash":false}]},{"StartTime":31000.0,"Objects":[{"StartTime":31000.0,"Position":300.0,"HyperDash":false}]},{"StartTime":31250.0,"Objects":[{"StartTime":31250.0,"Position":236.0,"HyperDash":false}]},{"StartTime":31375.0,"Objects":[{"StartTime":31375.0,"Position":236.0,"HyperDash":false}]},{"StartTime":31625.0,"Objects":[{"StartTime":31625.0,"Position":152.0,"HyperDash":false}]},{"StartTime":31875.0,"Objects":[{"StartTime":31875.0,"Position":300.0,"HyperDash":false}]},{"StartTime":32125.0,"Objects":[{"StartTime":32125.0,"Position":256.0,"HyperDash":false}]},{"StartTime":34625.0,"Objects":[{"StartTime":34625.0,"Position":52.0,"HyperDash":false}]},{"StartTime":34875.0,"Objects":[{"StartTime":34875.0,"Position":152.0,"HyperDash":false}]},{"StartTime":35125.0,"Objects":[{"StartTime":35125.0,"Position":256.0,"HyperDash":false}]},{"StartTime":35625.0,"Objects":[{"StartTime":35625.0,"Position":256.0,"HyperDash":false}]},{"StartTime":36125.0,"Objects":[{"StartTime":36125.0,"Position":256.0,"HyperDash":false},{"StartTime":36178.0,"Position":285.74295,"HyperDash":false},{"StartTime":36232.0,"Position":306.695557,"HyperDash":false},{"StartTime":36285.0,"Position":338.9461,"HyperDash":false},{"StartTime":36375.0,"Position":338.0262,"HyperDash":false}]},{"StartTime":36625.0,"Objects":[{"StartTime":36625.0,"Position":320.0,"HyperDash":false}]},{"StartTime":36875.0,"Objects":[{"StartTime":36875.0,"Position":204.0,"HyperDash":false}]},{"StartTime":37125.0,"Objects":[{"StartTime":37125.0,"Position":104.0,"HyperDash":false},{"StartTime":37178.0,"Position":84.88513,"HyperDash":false},{"StartTime":37232.0,"Position":58.02897,"HyperDash":false},{"StartTime":37285.0,"Position":32.3897247,"HyperDash":false},{"StartTime":37375.0,"Position":42.93435,"HyperDash":false}]},{"StartTime":37625.0,"Objects":[{"StartTime":37625.0,"Position":92.0,"HyperDash":false}]},{"StartTime":37875.0,"Objects":[{"StartTime":37875.0,"Position":212.0,"HyperDash":false}]},{"StartTime":38000.0,"Objects":[{"StartTime":38000.0,"Position":268.0,"HyperDash":false}]},{"StartTime":38125.0,"Objects":[{"StartTime":38125.0,"Position":324.0,"HyperDash":false},{"StartTime":38178.0,"Position":338.3627,"HyperDash":false},{"StartTime":38232.0,"Position":380.1851,"HyperDash":false},{"StartTime":38285.0,"Position":411.5478,"HyperDash":false},{"StartTime":38375.0,"Position":438.918457,"HyperDash":false}]},{"StartTime":38625.0,"Objects":[{"StartTime":38625.0,"Position":504.0,"HyperDash":false}]},{"StartTime":38875.0,"Objects":[{"StartTime":38875.0,"Position":364.0,"HyperDash":false}]},{"StartTime":39125.0,"Objects":[{"StartTime":39125.0,"Position":232.0,"HyperDash":false},{"StartTime":39187.0,"Position":199.986359,"HyperDash":false},{"StartTime":39250.0,"Position":169.811844,"HyperDash":false},{"StartTime":39312.0,"Position":133.274048,"HyperDash":false},{"StartTime":39375.0,"Position":115.502953,"HyperDash":false},{"StartTime":39437.0,"Position":95.79658,"HyperDash":false},{"StartTime":39500.0,"Position":126.272606,"HyperDash":false},{"StartTime":39562.0,"Position":153.43367,"HyperDash":false},{"StartTime":39625.0,"Position":177.594223,"HyperDash":false},{"StartTime":39687.0,"Position":138.43367,"HyperDash":false},{"StartTime":39750.0,"Position":126.007256,"HyperDash":false},{"StartTime":39812.0,"Position":110.796577,"HyperDash":false},{"StartTime":39875.0,"Position":115.652954,"HyperDash":false},{"StartTime":39928.0,"Position":111.270706,"HyperDash":false},{"StartTime":39982.0,"Position":160.599289,"HyperDash":false},{"StartTime":40035.0,"Position":158.120911,"HyperDash":false},{"StartTime":40124.0,"Position":232.0,"HyperDash":false}]},{"StartTime":40375.0,"Objects":[{"StartTime":40375.0,"Position":280.0,"HyperDash":false}]},{"StartTime":40625.0,"Objects":[{"StartTime":40625.0,"Position":400.0,"HyperDash":false},{"StartTime":40678.0,"Position":429.074829,"HyperDash":false},{"StartTime":40732.0,"Position":455.5662,"HyperDash":false},{"StartTime":40785.0,"Position":457.641022,"HyperDash":false},{"StartTime":40875.0,"Position":504.126617,"HyperDash":false}]},{"StartTime":41125.0,"Objects":[{"StartTime":41125.0,"Position":480.0,"HyperDash":false}]},{"StartTime":41375.0,"Objects":[{"StartTime":41375.0,"Position":324.0,"HyperDash":false}]},{"StartTime":41625.0,"Objects":[{"StartTime":41625.0,"Position":168.0,"HyperDash":false}]},{"StartTime":41875.0,"Objects":[{"StartTime":41875.0,"Position":72.0,"HyperDash":false}]},{"StartTime":42000.0,"Objects":[{"StartTime":42000.0,"Position":48.0,"HyperDash":false}]},{"StartTime":42125.0,"Objects":[{"StartTime":42125.0,"Position":96.0,"HyperDash":false},{"StartTime":42178.0,"Position":114.931221,"HyperDash":false},{"StartTime":42232.0,"Position":153.604843,"HyperDash":false},{"StartTime":42285.0,"Position":193.4396,"HyperDash":false},{"StartTime":42374.0,"Position":240.778946,"HyperDash":false}]},{"StartTime":42625.0,"Objects":[{"StartTime":42625.0,"Position":400.0,"HyperDash":false}]},{"StartTime":42875.0,"Objects":[{"StartTime":42875.0,"Position":440.0,"HyperDash":false}]},{"StartTime":43000.0,"Objects":[{"StartTime":43000.0,"Position":464.0,"HyperDash":false}]},{"StartTime":43125.0,"Objects":[{"StartTime":43125.0,"Position":416.0,"HyperDash":false},{"StartTime":43178.0,"Position":375.182983,"HyperDash":false},{"StartTime":43232.0,"Position":366.663025,"HyperDash":false},{"StartTime":43285.0,"Position":335.968475,"HyperDash":false},{"StartTime":43375.0,"Position":271.221039,"HyperDash":false}]},{"StartTime":43625.0,"Objects":[{"StartTime":43625.0,"Position":112.0,"HyperDash":false}]},{"StartTime":43875.0,"Objects":[{"StartTime":43875.0,"Position":140.0,"HyperDash":false}]},{"StartTime":44125.0,"Objects":[{"StartTime":44125.0,"Position":52.0,"HyperDash":false}]},{"StartTime":44375.0,"Objects":[{"StartTime":44375.0,"Position":208.0,"HyperDash":false}]},{"StartTime":44625.0,"Objects":[{"StartTime":44625.0,"Position":344.0,"HyperDash":false}]},{"StartTime":44875.0,"Objects":[{"StartTime":44875.0,"Position":448.0,"HyperDash":false},{"StartTime":44937.0,"Position":411.344635,"HyperDash":false},{"StartTime":45000.0,"Position":386.572845,"HyperDash":false},{"StartTime":45062.0,"Position":355.1799,"HyperDash":false},{"StartTime":45125.0,"Position":304.139374,"HyperDash":false},{"StartTime":45187.0,"Position":271.8332,"HyperDash":false},{"StartTime":45250.0,"Position":232.840988,"HyperDash":false},{"StartTime":45312.0,"Position":235.629944,"HyperDash":false},{"StartTime":45375.0,"Position":232.882874,"HyperDash":false},{"StartTime":45437.0,"Position":251.629944,"HyperDash":false},{"StartTime":45500.0,"Position":243.152222,"HyperDash":false},{"StartTime":45562.0,"Position":270.8332,"HyperDash":false},{"StartTime":45625.0,"Position":304.729126,"HyperDash":false},{"StartTime":45678.0,"Position":323.441345,"HyperDash":false},{"StartTime":45732.0,"Position":370.914246,"HyperDash":false},{"StartTime":45785.0,"Position":421.2586,"HyperDash":false},{"StartTime":45874.0,"Position":448.0,"HyperDash":false}]},{"StartTime":46125.0,"Objects":[{"StartTime":46125.0,"Position":326.0,"HyperDash":false},{"StartTime":46187.0,"Position":309.377716,"HyperDash":false},{"StartTime":46250.0,"Position":271.650543,"HyperDash":false},{"StartTime":46312.0,"Position":219.299332,"HyperDash":false},{"StartTime":46375.0,"Position":182.286819,"HyperDash":false},{"StartTime":46428.0,"Position":144.357529,"HyperDash":false},{"StartTime":46482.0,"Position":145.0256,"HyperDash":false},{"StartTime":46535.0,"Position":101.934631,"HyperDash":false},{"StartTime":46625.0,"Position":110.882874,"HyperDash":false}]},{"StartTime":46875.0,"Objects":[{"StartTime":46875.0,"Position":230.0,"HyperDash":false},{"StartTime":46937.0,"Position":247.622284,"HyperDash":false},{"StartTime":47000.0,"Position":299.3495,"HyperDash":false},{"StartTime":47062.0,"Position":322.700653,"HyperDash":false},{"StartTime":47125.0,"Position":373.7132,"HyperDash":false},{"StartTime":47178.0,"Position":390.642456,"HyperDash":false},{"StartTime":47232.0,"Position":424.974426,"HyperDash":false},{"StartTime":47285.0,"Position":428.065369,"HyperDash":false},{"StartTime":47375.0,"Position":445.1171,"HyperDash":false}]},{"StartTime":47625.0,"Objects":[{"StartTime":47625.0,"Position":376.0,"HyperDash":false}]},{"StartTime":48125.0,"Objects":[{"StartTime":48125.0,"Position":376.0,"HyperDash":false},{"StartTime":48178.0,"Position":340.223816,"HyperDash":false},{"StartTime":48232.0,"Position":305.204224,"HyperDash":false},{"StartTime":48285.0,"Position":270.449249,"HyperDash":false},{"StartTime":48375.0,"Position":222.9901,"HyperDash":false}]},{"StartTime":48625.0,"Objects":[{"StartTime":48625.0,"Position":84.0,"HyperDash":false}]},{"StartTime":48875.0,"Objects":[{"StartTime":48875.0,"Position":152.0,"HyperDash":false}]},{"StartTime":49125.0,"Objects":[{"StartTime":49125.0,"Position":44.0,"HyperDash":false},{"StartTime":49178.0,"Position":69.96314,"HyperDash":false},{"StartTime":49232.0,"Position":103.8065,"HyperDash":false},{"StartTime":49285.0,"Position":156.7781,"HyperDash":false},{"StartTime":49374.0,"Position":197.1017,"HyperDash":false}]},{"StartTime":49625.0,"Objects":[{"StartTime":49625.0,"Position":336.0,"HyperDash":false}]},{"StartTime":49875.0,"Objects":[{"StartTime":49875.0,"Position":256.0,"HyperDash":false}]},{"StartTime":50125.0,"Objects":[{"StartTime":50125.0,"Position":176.0,"HyperDash":false}]},{"StartTime":50625.0,"Objects":[{"StartTime":50625.0,"Position":340.0,"HyperDash":false}]},{"StartTime":50875.0,"Objects":[{"StartTime":50875.0,"Position":420.0,"HyperDash":false}]},{"StartTime":51125.0,"Objects":[{"StartTime":51125.0,"Position":500.0,"HyperDash":false}]},{"StartTime":51625.0,"Objects":[{"StartTime":51625.0,"Position":172.0,"HyperDash":false}]},{"StartTime":51875.0,"Objects":[{"StartTime":51875.0,"Position":92.0,"HyperDash":false}]},{"StartTime":52125.0,"Objects":[{"StartTime":52125.0,"Position":12.0,"HyperDash":false},{"StartTime":52178.0,"Position":43.4575653,"HyperDash":false},{"StartTime":52232.0,"Position":57.4520721,"HyperDash":false},{"StartTime":52285.0,"Position":85.90964,"HyperDash":false},{"StartTime":52375.0,"Position":146.23381,"HyperDash":false}]},{"StartTime":52625.0,"Objects":[{"StartTime":52625.0,"Position":304.0,"HyperDash":false}]},{"StartTime":52875.0,"Objects":[{"StartTime":52875.0,"Position":256.0,"HyperDash":false}]},{"StartTime":53125.0,"Objects":[{"StartTime":53125.0,"Position":216.0,"HyperDash":false},{"StartTime":53178.0,"Position":229.457565,"HyperDash":false},{"StartTime":53232.0,"Position":269.452057,"HyperDash":false},{"StartTime":53285.0,"Position":304.909637,"HyperDash":false},{"StartTime":53375.0,"Position":350.233826,"HyperDash":false}]},{"StartTime":53625.0,"Objects":[{"StartTime":53625.0,"Position":508.0,"HyperDash":false}]},{"StartTime":53875.0,"Objects":[{"StartTime":53875.0,"Position":460.0,"HyperDash":false}]},{"StartTime":54125.0,"Objects":[{"StartTime":54125.0,"Position":344.0,"HyperDash":false}]},{"StartTime":54375.0,"Objects":[{"StartTime":54375.0,"Position":228.0,"HyperDash":false}]},{"StartTime":54625.0,"Objects":[{"StartTime":54625.0,"Position":153.0,"HyperDash":false}]},{"StartTime":54875.0,"Objects":[{"StartTime":54875.0,"Position":72.0,"HyperDash":false}]},{"StartTime":55125.0,"Objects":[{"StartTime":55125.0,"Position":180.0,"HyperDash":false}]},{"StartTime":55375.0,"Objects":[{"StartTime":55375.0,"Position":284.0,"HyperDash":false}]},{"StartTime":55625.0,"Objects":[{"StartTime":55625.0,"Position":359.0,"HyperDash":false}]},{"StartTime":55875.0,"Objects":[{"StartTime":55875.0,"Position":440.0,"HyperDash":false}]},{"StartTime":56125.0,"Objects":[{"StartTime":56125.0,"Position":352.0,"HyperDash":false},{"StartTime":56178.0,"Position":355.0677,"HyperDash":false},{"StartTime":56231.0,"Position":396.135376,"HyperDash":false},{"StartTime":56284.0,"Position":431.2031,"HyperDash":false},{"StartTime":56374.0,"Position":455.6765,"HyperDash":false}]},{"StartTime":56625.0,"Objects":[{"StartTime":56625.0,"Position":312.0,"HyperDash":false}]},{"StartTime":56875.0,"Objects":[{"StartTime":56875.0,"Position":200.0,"HyperDash":false}]},{"StartTime":57125.0,"Objects":[{"StartTime":57125.0,"Position":160.0,"HyperDash":false},{"StartTime":57178.0,"Position":134.932312,"HyperDash":false},{"StartTime":57231.0,"Position":131.864609,"HyperDash":false},{"StartTime":57284.0,"Position":84.7969055,"HyperDash":false},{"StartTime":57374.0,"Position":56.32347,"HyperDash":false}]},{"StartTime":57625.0,"Objects":[{"StartTime":57625.0,"Position":200.0,"HyperDash":false}]},{"StartTime":57875.0,"Objects":[{"StartTime":57875.0,"Position":312.0,"HyperDash":false}]},{"StartTime":58125.0,"Objects":[{"StartTime":58125.0,"Position":444.0,"HyperDash":false},{"StartTime":58178.0,"Position":405.081421,"HyperDash":false},{"StartTime":58232.0,"Position":380.062256,"HyperDash":false},{"StartTime":58285.0,"Position":399.193085,"HyperDash":false},{"StartTime":58374.0,"Position":377.6735,"HyperDash":false}]},{"StartTime":58500.0,"Objects":[{"StartTime":58500.0,"Position":344.0,"HyperDash":false}]},{"StartTime":58625.0,"Objects":[{"StartTime":58625.0,"Position":272.0,"HyperDash":false},{"StartTime":58678.0,"Position":263.870544,"HyperDash":false},{"StartTime":58732.0,"Position":246.779541,"HyperDash":false},{"StartTime":58785.0,"Position":179.497513,"HyperDash":false},{"StartTime":58875.0,"Position":139.25528,"HyperDash":false}]},{"StartTime":59125.0,"Objects":[{"StartTime":59125.0,"Position":68.0,"HyperDash":false},{"StartTime":59178.0,"Position":89.57149,"HyperDash":false},{"StartTime":59232.0,"Position":123.207489,"HyperDash":false},{"StartTime":59285.0,"Position":141.936157,"HyperDash":false},{"StartTime":59375.0,"Position":133.961975,"HyperDash":false}]},{"StartTime":59500.0,"Objects":[{"StartTime":59500.0,"Position":168.0,"HyperDash":false}]},{"StartTime":59625.0,"Objects":[{"StartTime":59625.0,"Position":240.0,"HyperDash":false},{"StartTime":59678.0,"Position":245.129486,"HyperDash":false},{"StartTime":59732.0,"Position":270.220459,"HyperDash":false},{"StartTime":59785.0,"Position":296.5025,"HyperDash":false},{"StartTime":59875.0,"Position":372.74472,"HyperDash":false}]},{"StartTime":60125.0,"Objects":[{"StartTime":60125.0,"Position":456.0,"HyperDash":false}]},{"StartTime":60375.0,"Objects":[{"StartTime":60375.0,"Position":328.0,"HyperDash":false}]},{"StartTime":60625.0,"Objects":[{"StartTime":60625.0,"Position":216.0,"HyperDash":false}]},{"StartTime":60875.0,"Objects":[{"StartTime":60875.0,"Position":72.0,"HyperDash":false},{"StartTime":60937.0,"Position":71.25553,"HyperDash":false},{"StartTime":61000.0,"Position":61.5583878,"HyperDash":false},{"StartTime":61062.0,"Position":98.84126,"HyperDash":false},{"StartTime":61125.0,"Position":119.510284,"HyperDash":false},{"StartTime":61187.0,"Position":142.845825,"HyperDash":false},{"StartTime":61250.0,"Position":184.319992,"HyperDash":false},{"StartTime":61312.0,"Position":240.90744,"HyperDash":false},{"StartTime":61375.0,"Position":269.728363,"HyperDash":false},{"StartTime":61437.0,"Position":239.90744,"HyperDash":false},{"StartTime":61500.0,"Position":197.687851,"HyperDash":false},{"StartTime":61562.0,"Position":150.845825,"HyperDash":false},{"StartTime":61625.0,"Position":119.024872,"HyperDash":false},{"StartTime":61678.0,"Position":90.12531,"HyperDash":false},{"StartTime":61732.0,"Position":72.3374557,"HyperDash":false},{"StartTime":61785.0,"Position":89.06496,"HyperDash":false},{"StartTime":61874.0,"Position":72.0,"HyperDash":false}]},{"StartTime":62125.0,"Objects":[{"StartTime":62125.0,"Position":200.0,"HyperDash":false},{"StartTime":62187.0,"Position":191.234039,"HyperDash":false},{"StartTime":62250.0,"Position":203.319962,"HyperDash":false},{"StartTime":62312.0,"Position":235.3192,"HyperDash":false},{"StartTime":62375.0,"Position":246.7092,"HyperDash":false},{"StartTime":62428.0,"Position":291.675018,"HyperDash":false},{"StartTime":62482.0,"Position":309.9024,"HyperDash":false},{"StartTime":62535.0,"Position":336.449463,"HyperDash":false},{"StartTime":62625.0,"Position":396.8608,"HyperDash":false}]},{"StartTime":62875.0,"Objects":[{"StartTime":62875.0,"Position":480.0,"HyperDash":false},{"StartTime":62937.0,"Position":492.1737,"HyperDash":false},{"StartTime":63000.0,"Position":476.1641,"HyperDash":false},{"StartTime":63062.0,"Position":475.045135,"HyperDash":false},{"StartTime":63125.0,"Position":433.461975,"HyperDash":false},{"StartTime":63178.0,"Position":389.354034,"HyperDash":false},{"StartTime":63232.0,"Position":366.034546,"HyperDash":false},{"StartTime":63285.0,"Position":321.454956,"HyperDash":false},{"StartTime":63375.0,"Position":283.111176,"HyperDash":false}]},{"StartTime":63625.0,"Objects":[{"StartTime":63625.0,"Position":136.0,"HyperDash":false},{"StartTime":63678.0,"Position":111.887825,"HyperDash":false},{"StartTime":63732.0,"Position":108.904541,"HyperDash":false},{"StartTime":63785.0,"Position":105.234535,"HyperDash":false},{"StartTime":63874.0,"Position":128.127991,"HyperDash":false}]},{"StartTime":64125.0,"Objects":[{"StartTime":64125.0,"Position":256.0,"HyperDash":false}]},{"StartTime":64375.0,"Objects":[{"StartTime":64375.0,"Position":284.0,"HyperDash":false}]},{"StartTime":64625.0,"Objects":[{"StartTime":64625.0,"Position":440.0,"HyperDash":false}]},{"StartTime":64875.0,"Objects":[{"StartTime":64875.0,"Position":420.0,"HyperDash":false}]},{"StartTime":65125.0,"Objects":[{"StartTime":65125.0,"Position":300.0,"HyperDash":false}]},{"StartTime":65375.0,"Objects":[{"StartTime":65375.0,"Position":272.0,"HyperDash":false}]},{"StartTime":65625.0,"Objects":[{"StartTime":65625.0,"Position":116.0,"HyperDash":false}]},{"StartTime":65875.0,"Objects":[{"StartTime":65875.0,"Position":136.0,"HyperDash":false}]},{"StartTime":66125.0,"Objects":[{"StartTime":66125.0,"Position":256.0,"HyperDash":false}]},{"StartTime":68125.0,"Objects":[{"StartTime":68125.0,"Position":256.0,"HyperDash":false},{"StartTime":68187.0,"Position":266.157,"HyperDash":false},{"StartTime":68250.0,"Position":280.344269,"HyperDash":false},{"StartTime":68312.0,"Position":243.4508,"HyperDash":false},{"StartTime":68375.0,"Position":216.9601,"HyperDash":false},{"StartTime":68428.0,"Position":173.102234,"HyperDash":false},{"StartTime":68482.0,"Position":150.915558,"HyperDash":false},{"StartTime":68535.0,"Position":106.794662,"HyperDash":false},{"StartTime":68625.0,"Position":73.61266,"HyperDash":false}]},{"StartTime":68875.0,"Objects":[{"StartTime":68875.0,"Position":132.0,"HyperDash":false},{"StartTime":68937.0,"Position":160.783325,"HyperDash":false},{"StartTime":69000.0,"Position":193.9825,"HyperDash":false},{"StartTime":69062.0,"Position":205.765823,"HyperDash":false},{"StartTime":69125.0,"Position":235.965,"HyperDash":false},{"StartTime":69178.0,"Position":262.005585,"HyperDash":false},{"StartTime":69232.0,"Position":285.462,"HyperDash":false},{"StartTime":69285.0,"Position":302.5026,"HyperDash":false},{"StartTime":69375.0,"Position":339.93,"HyperDash":false}]},{"StartTime":69625.0,"Objects":[{"StartTime":69625.0,"Position":456.0,"HyperDash":false}]},{"StartTime":69875.0,"Objects":[{"StartTime":69875.0,"Position":340.0,"HyperDash":false}]},{"StartTime":70000.0,"Objects":[{"StartTime":70000.0,"Position":340.0,"HyperDash":false}]},{"StartTime":70125.0,"Objects":[{"StartTime":70125.0,"Position":340.0,"HyperDash":false}]},{"StartTime":70375.0,"Objects":[{"StartTime":70375.0,"Position":228.0,"HyperDash":false}]},{"StartTime":70625.0,"Objects":[{"StartTime":70625.0,"Position":256.0,"HyperDash":false},{"StartTime":70678.0,"Position":210.6065,"HyperDash":false},{"StartTime":70732.0,"Position":177.325424,"HyperDash":false},{"StartTime":70785.0,"Position":151.573288,"HyperDash":false},{"StartTime":70875.0,"Position":107.425896,"HyperDash":false}]},{"StartTime":71125.0,"Objects":[{"StartTime":71125.0,"Position":148.0,"HyperDash":false},{"StartTime":71178.0,"Position":184.328445,"HyperDash":false},{"StartTime":71232.0,"Position":200.780228,"HyperDash":false},{"StartTime":71285.0,"Position":257.6842,"HyperDash":false},{"StartTime":71374.0,"Position":296.433563,"HyperDash":false}]},{"StartTime":71625.0,"Objects":[{"StartTime":71625.0,"Position":424.0,"HyperDash":false}]},{"StartTime":71875.0,"Objects":[{"StartTime":71875.0,"Position":336.0,"HyperDash":false}]},{"StartTime":72000.0,"Objects":[{"StartTime":72000.0,"Position":336.0,"HyperDash":false}]},{"StartTime":72125.0,"Objects":[{"StartTime":72125.0,"Position":336.0,"HyperDash":false}]},{"StartTime":72375.0,"Objects":[{"StartTime":72375.0,"Position":228.0,"HyperDash":false},{"StartTime":72428.0,"Position":211.104858,"HyperDash":false},{"StartTime":72482.0,"Position":163.608932,"HyperDash":false},{"StartTime":72535.0,"Position":134.045914,"HyperDash":false},{"StartTime":72625.0,"Position":143.764755,"HyperDash":false}]},{"StartTime":72875.0,"Objects":[{"StartTime":72875.0,"Position":268.0,"HyperDash":false},{"StartTime":72937.0,"Position":248.6492,"HyperDash":false},{"StartTime":73000.0,"Position":273.503021,"HyperDash":false},{"StartTime":73062.0,"Position":247.768143,"HyperDash":false},{"StartTime":73125.0,"Position":228.062622,"HyperDash":false},{"StartTime":73178.0,"Position":204.959824,"HyperDash":false},{"StartTime":73232.0,"Position":170.633987,"HyperDash":false},{"StartTime":73285.0,"Position":155.368179,"HyperDash":false},{"StartTime":73375.0,"Position":103.8164,"HyperDash":false}]},{"StartTime":73625.0,"Objects":[{"StartTime":73625.0,"Position":24.0,"HyperDash":false}]},{"StartTime":73875.0,"Objects":[{"StartTime":73875.0,"Position":92.0,"HyperDash":false}]},{"StartTime":74000.0,"Objects":[{"StartTime":74000.0,"Position":92.0,"HyperDash":false}]},{"StartTime":74125.0,"Objects":[{"StartTime":74125.0,"Position":92.0,"HyperDash":false}]},{"StartTime":74375.0,"Objects":[{"StartTime":74375.0,"Position":224.0,"HyperDash":false}]},{"StartTime":74625.0,"Objects":[{"StartTime":74625.0,"Position":340.0,"HyperDash":false},{"StartTime":74678.0,"Position":381.308228,"HyperDash":false},{"StartTime":74732.0,"Position":376.477844,"HyperDash":false},{"StartTime":74785.0,"Position":399.771942,"HyperDash":false},{"StartTime":74875.0,"Position":387.2963,"HyperDash":false}]},{"StartTime":75125.0,"Objects":[{"StartTime":75125.0,"Position":268.0,"HyperDash":false},{"StartTime":75178.0,"Position":219.691772,"HyperDash":false},{"StartTime":75232.0,"Position":224.522156,"HyperDash":false},{"StartTime":75285.0,"Position":185.228043,"HyperDash":false},{"StartTime":75375.0,"Position":220.70369,"HyperDash":false}]},{"StartTime":75625.0,"Objects":[{"StartTime":75625.0,"Position":268.0,"HyperDash":false},{"StartTime":75678.0,"Position":251.437485,"HyperDash":false},{"StartTime":75732.0,"Position":209.2417,"HyperDash":false},{"StartTime":75785.0,"Position":166.6792,"HyperDash":false},{"StartTime":75875.0,"Position":109.686234,"HyperDash":false}]},{"StartTime":76125.0,"Objects":[{"StartTime":76125.0,"Position":24.0,"HyperDash":false},{"StartTime":76250.0,"Position":103.510704,"HyperDash":false}]},{"StartTime":76375.0,"Objects":[{"StartTime":76375.0,"Position":176.0,"HyperDash":false}]},{"StartTime":76625.0,"Objects":[{"StartTime":76625.0,"Position":348.0,"HyperDash":false}]},{"StartTime":76875.0,"Objects":[{"StartTime":76875.0,"Position":248.0,"HyperDash":false}]},{"StartTime":77125.0,"Objects":[{"StartTime":77125.0,"Position":264.0,"HyperDash":false}]},{"StartTime":77375.0,"Objects":[{"StartTime":77375.0,"Position":324.0,"HyperDash":false}]},{"StartTime":77625.0,"Objects":[{"StartTime":77625.0,"Position":180.0,"HyperDash":false}]},{"StartTime":77875.0,"Objects":[{"StartTime":77875.0,"Position":240.0,"HyperDash":false}]},{"StartTime":78125.0,"Objects":[{"StartTime":78125.0,"Position":256.0,"HyperDash":false}]},{"StartTime":78375.0,"Objects":[{"StartTime":78375.0,"Position":100.0,"HyperDash":false}]},{"StartTime":78625.0,"Objects":[{"StartTime":78625.0,"Position":8.0,"HyperDash":false},{"StartTime":78678.0,"Position":30.0805969,"HyperDash":false},{"StartTime":78732.0,"Position":72.26928,"HyperDash":false},{"StartTime":78785.0,"Position":94.77067,"HyperDash":false},{"StartTime":78874.0,"Position":149.724487,"HyperDash":false}]},{"StartTime":79125.0,"Objects":[{"StartTime":79125.0,"Position":304.0,"HyperDash":false},{"StartTime":79178.0,"Position":282.0235,"HyperDash":false},{"StartTime":79232.0,"Position":238.981018,"HyperDash":false},{"StartTime":79285.0,"Position":222.634567,"HyperDash":false},{"StartTime":79375.0,"Position":162.2755,"HyperDash":false}]},{"StartTime":79625.0,"Objects":[{"StartTime":79625.0,"Position":304.0,"HyperDash":false}]},{"StartTime":79875.0,"Objects":[{"StartTime":79875.0,"Position":460.0,"HyperDash":false}]},{"StartTime":80125.0,"Objects":[{"StartTime":80125.0,"Position":420.0,"HyperDash":false},{"StartTime":80250.0,"Position":340.0,"HyperDash":false}]},{"StartTime":80375.0,"Objects":[{"StartTime":80375.0,"Position":256.0,"HyperDash":false}]},{"StartTime":80625.0,"Objects":[{"StartTime":80625.0,"Position":344.0,"HyperDash":false}]},{"StartTime":80875.0,"Objects":[{"StartTime":80875.0,"Position":168.0,"HyperDash":false}]},{"StartTime":81125.0,"Objects":[{"StartTime":81125.0,"Position":384.0,"HyperDash":false}]},{"StartTime":81375.0,"Objects":[{"StartTime":81375.0,"Position":256.0,"HyperDash":false}]},{"StartTime":81625.0,"Objects":[{"StartTime":81625.0,"Position":168.0,"HyperDash":false}]},{"StartTime":81875.0,"Objects":[{"StartTime":81875.0,"Position":344.0,"HyperDash":false}]},{"StartTime":82125.0,"Objects":[{"StartTime":82125.0,"Position":128.0,"HyperDash":false}]},{"StartTime":82250.0,"Objects":[{"StartTime":82250.0,"Position":48.0,"HyperDash":false},{"StartTime":82303.0,"Position":38.86482,"HyperDash":false},{"StartTime":82357.0,"Position":53.93512,"HyperDash":false},{"StartTime":82410.0,"Position":60.0134125,"HyperDash":false},{"StartTime":82500.0,"Position":124.821884,"HyperDash":false}]},{"StartTime":82625.0,"Objects":[{"StartTime":82625.0,"Position":204.0,"HyperDash":false},{"StartTime":82678.0,"Position":208.888657,"HyperDash":false},{"StartTime":82731.0,"Position":211.78508,"HyperDash":false},{"StartTime":82784.0,"Position":215.863892,"HyperDash":false},{"StartTime":82874.0,"Position":280.821869,"HyperDash":false}]},{"StartTime":83000.0,"Objects":[{"StartTime":83000.0,"Position":352.0,"HyperDash":false},{"StartTime":83053.0,"Position":303.246552,"HyperDash":false},{"StartTime":83107.0,"Position":291.2771,"HyperDash":false},{"StartTime":83160.0,"Position":254.710571,"HyperDash":false},{"StartTime":83250.0,"Position":222.496063,"HyperDash":false}]},{"StartTime":83375.0,"Objects":[{"StartTime":83375.0,"Position":192.0,"HyperDash":false},{"StartTime":83428.0,"Position":152.246567,"HyperDash":false},{"StartTime":83482.0,"Position":112.277092,"HyperDash":false},{"StartTime":83535.0,"Position":87.71058,"HyperDash":false},{"StartTime":83625.0,"Position":62.496067,"HyperDash":false}]},{"StartTime":83875.0,"Objects":[{"StartTime":83875.0,"Position":32.0,"HyperDash":false}]},{"StartTime":84125.0,"Objects":[{"StartTime":84125.0,"Position":172.0,"HyperDash":false}]},{"StartTime":84250.0,"Objects":[{"StartTime":84250.0,"Position":179.0,"HyperDash":false},{"StartTime":84308.0,"Position":278.0,"HyperDash":false},{"StartTime":84367.0,"Position":474.0,"HyperDash":false},{"StartTime":84425.0,"Position":50.0,"HyperDash":false},{"StartTime":84484.0,"Position":458.0,"HyperDash":false},{"StartTime":84542.0,"Position":425.0,"HyperDash":false},{"StartTime":84601.0,"Position":466.0,"HyperDash":false},{"StartTime":84660.0,"Position":56.0,"HyperDash":false},{"StartTime":84718.0,"Position":109.0,"HyperDash":false},{"StartTime":84777.0,"Position":482.0,"HyperDash":false},{"StartTime":84835.0,"Position":147.0,"HyperDash":false},{"StartTime":84894.0,"Position":285.0,"HyperDash":false},{"StartTime":84953.0,"Position":452.0,"HyperDash":false},{"StartTime":85011.0,"Position":419.0,"HyperDash":false},{"StartTime":85070.0,"Position":269.0,"HyperDash":false},{"StartTime":85128.0,"Position":249.0,"HyperDash":false},{"StartTime":85187.0,"Position":233.0,"HyperDash":false},{"StartTime":85246.0,"Position":449.0,"HyperDash":false},{"StartTime":85304.0,"Position":411.0,"HyperDash":false},{"StartTime":85363.0,"Position":75.0,"HyperDash":false},{"StartTime":85421.0,"Position":474.0,"HyperDash":false},{"StartTime":85480.0,"Position":176.0,"HyperDash":false},{"StartTime":85539.0,"Position":1.0,"HyperDash":false},{"StartTime":85597.0,"Position":37.0,"HyperDash":false},{"StartTime":85656.0,"Position":481.0,"HyperDash":false},{"StartTime":85714.0,"Position":375.0,"HyperDash":false},{"StartTime":85773.0,"Position":407.0,"HyperDash":false},{"StartTime":85832.0,"Position":231.0,"HyperDash":false},{"StartTime":85890.0,"Position":338.0,"HyperDash":false},{"StartTime":85949.0,"Position":322.0,"HyperDash":false},{"StartTime":86007.0,"Position":347.0,"HyperDash":false},{"StartTime":86066.0,"Position":365.0,"HyperDash":false},{"StartTime":86125.0,"Position":453.0,"HyperDash":false}]},{"StartTime":86250.0,"Objects":[{"StartTime":86250.0,"Position":486.0,"HyperDash":false},{"StartTime":86304.0,"Position":68.0,"HyperDash":false},{"StartTime":86359.0,"Position":498.0,"HyperDash":false},{"StartTime":86414.0,"Position":164.0,"HyperDash":false},{"StartTime":86468.0,"Position":1.0,"HyperDash":false},{"StartTime":86523.0,"Position":501.0,"HyperDash":false},{"StartTime":86578.0,"Position":82.0,"HyperDash":false},{"StartTime":86632.0,"Position":494.0,"HyperDash":false},{"StartTime":86687.0,"Position":479.0,"HyperDash":false},{"StartTime":86742.0,"Position":373.0,"HyperDash":false},{"StartTime":86796.0,"Position":450.0,"HyperDash":false},{"StartTime":86851.0,"Position":144.0,"HyperDash":false},{"StartTime":86906.0,"Position":365.0,"HyperDash":false},{"StartTime":86960.0,"Position":285.0,"HyperDash":false},{"StartTime":87015.0,"Position":45.0,"HyperDash":false},{"StartTime":87070.0,"Position":65.0,"HyperDash":false},{"StartTime":87125.0,"Position":337.0,"HyperDash":false}]},{"StartTime":88125.0,"Objects":[{"StartTime":88125.0,"Position":256.0,"HyperDash":false},{"StartTime":88178.0,"Position":292.30423,"HyperDash":false},{"StartTime":88232.0,"Position":341.450134,"HyperDash":false},{"StartTime":88285.0,"Position":358.591034,"HyperDash":false},{"StartTime":88375.0,"Position":390.822968,"HyperDash":false}]},{"StartTime":88625.0,"Objects":[{"StartTime":88625.0,"Position":256.0,"HyperDash":false}]},{"StartTime":88875.0,"Objects":[{"StartTime":88875.0,"Position":136.0,"HyperDash":false}]},{"StartTime":89125.0,"Objects":[{"StartTime":89125.0,"Position":8.0,"HyperDash":false},{"StartTime":89178.0,"Position":0.0,"HyperDash":false},{"StartTime":89232.0,"Position":12.7492714,"HyperDash":false},{"StartTime":89285.0,"Position":7.342363,"HyperDash":false},{"StartTime":89375.0,"Position":41.059124,"HyperDash":false}]},{"StartTime":89625.0,"Objects":[{"StartTime":89625.0,"Position":164.0,"HyperDash":false}]},{"StartTime":89875.0,"Objects":[{"StartTime":89875.0,"Position":288.0,"HyperDash":false}]},{"StartTime":90000.0,"Objects":[{"StartTime":90000.0,"Position":288.0,"HyperDash":false}]},{"StartTime":90125.0,"Objects":[{"StartTime":90125.0,"Position":288.0,"HyperDash":false},{"StartTime":90178.0,"Position":307.058655,"HyperDash":false},{"StartTime":90232.0,"Position":366.7033,"HyperDash":false},{"StartTime":90285.0,"Position":400.761932,"HyperDash":false},{"StartTime":90375.0,"Position":434.503052,"HyperDash":false}]},{"StartTime":90625.0,"Objects":[{"StartTime":90625.0,"Position":476.0,"HyperDash":false}]},{"StartTime":90875.0,"Objects":[{"StartTime":90875.0,"Position":332.0,"HyperDash":false}]},{"StartTime":91125.0,"Objects":[{"StartTime":91125.0,"Position":180.0,"HyperDash":false}]},{"StartTime":91375.0,"Objects":[{"StartTime":91375.0,"Position":36.0,"HyperDash":false}]},{"StartTime":91625.0,"Objects":[{"StartTime":91625.0,"Position":56.0,"HyperDash":false}]},{"StartTime":92125.0,"Objects":[{"StartTime":92125.0,"Position":56.0,"HyperDash":false},{"StartTime":92178.0,"Position":78.15752,"HyperDash":false},{"StartTime":92232.0,"Position":134.940643,"HyperDash":false},{"StartTime":92285.0,"Position":142.098145,"HyperDash":false},{"StartTime":92375.0,"Position":212.403366,"HyperDash":false}]},{"StartTime":92625.0,"Objects":[{"StartTime":92625.0,"Position":84.0,"HyperDash":false}]},{"StartTime":92875.0,"Objects":[{"StartTime":92875.0,"Position":220.0,"HyperDash":false}]},{"StartTime":93125.0,"Objects":[{"StartTime":93125.0,"Position":320.0,"HyperDash":false},{"StartTime":93178.0,"Position":369.1575,"HyperDash":false},{"StartTime":93232.0,"Position":398.940643,"HyperDash":false},{"StartTime":93285.0,"Position":408.098145,"HyperDash":false},{"StartTime":93375.0,"Position":476.403381,"HyperDash":false}]},{"StartTime":93625.0,"Objects":[{"StartTime":93625.0,"Position":432.0,"HyperDash":false}]},{"StartTime":93875.0,"Objects":[{"StartTime":93875.0,"Position":296.0,"HyperDash":false}]},{"StartTime":94000.0,"Objects":[{"StartTime":94000.0,"Position":296.0,"HyperDash":false}]},{"StartTime":94125.0,"Objects":[{"StartTime":94125.0,"Position":296.0,"HyperDash":false},{"StartTime":94178.0,"Position":273.1039,"HyperDash":false},{"StartTime":94232.0,"Position":244.445969,"HyperDash":false},{"StartTime":94285.0,"Position":219.02182,"HyperDash":false},{"StartTime":94374.0,"Position":170.471848,"HyperDash":false}]},{"StartTime":94625.0,"Objects":[{"StartTime":94625.0,"Position":216.0,"HyperDash":false},{"StartTime":94678.0,"Position":259.7602,"HyperDash":false},{"StartTime":94732.0,"Position":282.299927,"HyperDash":false},{"StartTime":94785.0,"Position":294.678436,"HyperDash":false},{"StartTime":94875.0,"Position":341.528168,"HyperDash":false}]},{"StartTime":95000.0,"Objects":[{"StartTime":95000.0,"Position":341.0,"HyperDash":false}]},{"StartTime":95125.0,"Objects":[{"StartTime":95125.0,"Position":341.0,"HyperDash":false},{"StartTime":95178.0,"Position":347.282532,"HyperDash":false},{"StartTime":95232.0,"Position":344.6459,"HyperDash":false},{"StartTime":95285.0,"Position":339.928436,"HyperDash":false},{"StartTime":95375.0,"Position":361.200684,"HyperDash":false}]},{"StartTime":95625.0,"Objects":[{"StartTime":95625.0,"Position":171.0,"HyperDash":false},{"StartTime":95678.0,"Position":158.717453,"HyperDash":false},{"StartTime":95732.0,"Position":169.354111,"HyperDash":false},{"StartTime":95785.0,"Position":172.071564,"HyperDash":false},{"StartTime":95875.0,"Position":150.799316,"HyperDash":false}]},{"StartTime":96125.0,"Objects":[{"StartTime":96125.0,"Position":43.0,"HyperDash":false}]},{"StartTime":96375.0,"Objects":[{"StartTime":96375.0,"Position":81.0,"HyperDash":false}]},{"StartTime":96625.0,"Objects":[{"StartTime":96625.0,"Position":169.0,"HyperDash":false}]},{"StartTime":96875.0,"Objects":[{"StartTime":96875.0,"Position":304.0,"HyperDash":false},{"StartTime":96937.0,"Position":333.433136,"HyperDash":false},{"StartTime":97000.0,"Position":385.325043,"HyperDash":false},{"StartTime":97062.0,"Position":379.667,"HyperDash":false},{"StartTime":97125.0,"Position":401.778076,"HyperDash":false},{"StartTime":97187.0,"Position":418.125366,"HyperDash":false},{"StartTime":97250.0,"Position":403.005768,"HyperDash":false},{"StartTime":97312.0,"Position":375.9013,"HyperDash":false},{"StartTime":97375.0,"Position":343.426239,"HyperDash":false},{"StartTime":97437.0,"Position":382.9013,"HyperDash":false},{"StartTime":97499.0,"Position":392.005768,"HyperDash":false},{"StartTime":97561.0,"Position":388.066345,"HyperDash":false},{"StartTime":97624.0,"Position":401.778076,"HyperDash":false},{"StartTime":97677.0,"Position":380.074066,"HyperDash":false},{"StartTime":97731.0,"Position":366.190063,"HyperDash":false},{"StartTime":97785.0,"Position":348.305481,"HyperDash":false},{"StartTime":97874.0,"Position":304.0,"HyperDash":false}]},{"StartTime":98125.0,"Objects":[{"StartTime":98125.0,"Position":240.0,"HyperDash":false},{"StartTime":98187.0,"Position":220.193451,"HyperDash":false},{"StartTime":98250.0,"Position":179.67662,"HyperDash":false},{"StartTime":98312.0,"Position":167.455551,"HyperDash":false},{"StartTime":98375.0,"Position":115.407051,"HyperDash":false},{"StartTime":98428.0,"Position":97.24337,"HyperDash":false},{"StartTime":98482.0,"Position":115.416969,"HyperDash":false},{"StartTime":98535.0,"Position":122.237556,"HyperDash":false},{"StartTime":98624.0,"Position":166.963364,"HyperDash":false}]},{"StartTime":98875.0,"Objects":[{"StartTime":98875.0,"Position":240.0,"HyperDash":false},{"StartTime":98937.0,"Position":273.329651,"HyperDash":false},{"StartTime":99000.0,"Position":306.601349,"HyperDash":false},{"StartTime":99062.0,"Position":324.816467,"HyperDash":false},{"StartTime":99125.0,"Position":363.818481,"HyperDash":false},{"StartTime":99178.0,"Position":391.8492,"HyperDash":false},{"StartTime":99232.0,"Position":363.507568,"HyperDash":false},{"StartTime":99285.0,"Position":349.543182,"HyperDash":false},{"StartTime":99374.0,"Position":311.711731,"HyperDash":false}]},{"StartTime":99625.0,"Objects":[{"StartTime":99625.0,"Position":180.0,"HyperDash":false},{"StartTime":99678.0,"Position":143.011124,"HyperDash":false},{"StartTime":99732.0,"Position":113.192444,"HyperDash":false},{"StartTime":99785.0,"Position":79.4256439,"HyperDash":false},{"StartTime":99874.0,"Position":45.3982735,"HyperDash":false}]},{"StartTime":100125.0,"Objects":[{"StartTime":100125.0,"Position":48.0,"HyperDash":false},{"StartTime":100178.0,"Position":75.85622,"HyperDash":false},{"StartTime":100231.0,"Position":116.712425,"HyperDash":false},{"StartTime":100284.0,"Position":156.568634,"HyperDash":false},{"StartTime":100374.0,"Position":202.3622,"HyperDash":false}]},{"StartTime":100625.0,"Objects":[{"StartTime":100625.0,"Position":348.0,"HyperDash":false},{"StartTime":100678.0,"Position":383.8562,"HyperDash":false},{"StartTime":100731.0,"Position":402.712433,"HyperDash":false},{"StartTime":100784.0,"Position":456.568634,"HyperDash":false},{"StartTime":100874.0,"Position":502.362183,"HyperDash":false}]},{"StartTime":101125.0,"Objects":[{"StartTime":101125.0,"Position":504.0,"HyperDash":false},{"StartTime":101178.0,"Position":488.1438,"HyperDash":false},{"StartTime":101231.0,"Position":446.287567,"HyperDash":false},{"StartTime":101284.0,"Position":423.431366,"HyperDash":false},{"StartTime":101374.0,"Position":349.637817,"HyperDash":false}]},{"StartTime":101625.0,"Objects":[{"StartTime":101625.0,"Position":204.0,"HyperDash":false},{"StartTime":101678.0,"Position":156.143784,"HyperDash":false},{"StartTime":101731.0,"Position":133.287567,"HyperDash":false},{"StartTime":101784.0,"Position":117.431358,"HyperDash":false},{"StartTime":101874.0,"Position":49.6378021,"HyperDash":false}]},{"StartTime":102000.0,"Objects":[{"StartTime":102000.0,"Position":49.0,"HyperDash":false}]},{"StartTime":102125.0,"Objects":[{"StartTime":102125.0,"Position":49.0,"HyperDash":false}]},{"StartTime":102625.0,"Objects":[{"StartTime":102625.0,"Position":256.0,"HyperDash":false}]},{"StartTime":102875.0,"Objects":[{"StartTime":102875.0,"Position":384.0,"HyperDash":false}]},{"StartTime":103125.0,"Objects":[{"StartTime":103125.0,"Position":256.0,"HyperDash":false}]},{"StartTime":103625.0,"Objects":[{"StartTime":103625.0,"Position":256.0,"HyperDash":false}]},{"StartTime":103875.0,"Objects":[{"StartTime":103875.0,"Position":128.0,"HyperDash":false}]},{"StartTime":104125.0,"Objects":[{"StartTime":104125.0,"Position":256.0,"HyperDash":false},{"StartTime":104178.0,"Position":272.994537,"HyperDash":false},{"StartTime":104232.0,"Position":332.6524,"HyperDash":false},{"StartTime":104285.0,"Position":355.331024,"HyperDash":false},{"StartTime":104375.0,"Position":402.7333,"HyperDash":false}]},{"StartTime":104625.0,"Objects":[{"StartTime":104625.0,"Position":492.0,"HyperDash":false}]},{"StartTime":104875.0,"Objects":[{"StartTime":104875.0,"Position":332.0,"HyperDash":false}]},{"StartTime":105125.0,"Objects":[{"StartTime":105125.0,"Position":256.0,"HyperDash":false},{"StartTime":105178.0,"Position":241.889923,"HyperDash":false},{"StartTime":105232.0,"Position":211.080078,"HyperDash":false},{"StartTime":105285.0,"Position":144.258759,"HyperDash":false},{"StartTime":105374.0,"Position":109.266708,"HyperDash":false}]},{"StartTime":105625.0,"Objects":[{"StartTime":105625.0,"Position":20.0,"HyperDash":false}]},{"StartTime":105875.0,"Objects":[{"StartTime":105875.0,"Position":180.0,"HyperDash":false}]},{"StartTime":106125.0,"Objects":[{"StartTime":106125.0,"Position":368.0,"HyperDash":false},{"StartTime":106178.0,"Position":376.172974,"HyperDash":false},{"StartTime":106231.0,"Position":410.418945,"HyperDash":false},{"StartTime":106284.0,"Position":421.151764,"HyperDash":false},{"StartTime":106374.0,"Position":416.485779,"HyperDash":false}]},{"StartTime":106625.0,"Objects":[{"StartTime":106625.0,"Position":220.0,"HyperDash":false},{"StartTime":106678.0,"Position":241.054184,"HyperDash":false},{"StartTime":106731.0,"Position":282.792328,"HyperDash":false},{"StartTime":106784.0,"Position":301.047577,"HyperDash":false},{"StartTime":106874.0,"Position":349.247284,"HyperDash":false}]},{"StartTime":107125.0,"Objects":[{"StartTime":107125.0,"Position":144.0,"HyperDash":false},{"StartTime":107178.0,"Position":125.932152,"HyperDash":false},{"StartTime":107232.0,"Position":88.3730545,"HyperDash":false},{"StartTime":107285.0,"Position":97.8173752,"HyperDash":false},{"StartTime":107375.0,"Position":95.51424,"HyperDash":false}]},{"StartTime":107625.0,"Objects":[{"StartTime":107625.0,"Position":292.0,"HyperDash":false},{"StartTime":107678.0,"Position":279.032471,"HyperDash":false},{"StartTime":107732.0,"Position":263.904663,"HyperDash":false},{"StartTime":107785.0,"Position":230.72316,"HyperDash":false},{"StartTime":107875.0,"Position":162.752716,"HyperDash":false}]},{"StartTime":108125.0,"Objects":[{"StartTime":108125.0,"Position":44.0,"HyperDash":false},{"StartTime":108178.0,"Position":95.98508,"HyperDash":false},{"StartTime":108232.0,"Position":110.3604,"HyperDash":false},{"StartTime":108285.0,"Position":123.706589,"HyperDash":false},{"StartTime":108374.0,"Position":169.6919,"HyperDash":false}]},{"StartTime":108625.0,"Objects":[{"StartTime":108625.0,"Position":304.0,"HyperDash":false}]},{"StartTime":108875.0,"Objects":[{"StartTime":108875.0,"Position":408.0,"HyperDash":false}]},{"StartTime":109125.0,"Objects":[{"StartTime":109125.0,"Position":468.0,"HyperDash":false},{"StartTime":109178.0,"Position":439.149963,"HyperDash":false},{"StartTime":109232.0,"Position":396.891418,"HyperDash":false},{"StartTime":109285.0,"Position":370.5935,"HyperDash":false},{"StartTime":109375.0,"Position":342.308075,"HyperDash":false}]},{"StartTime":109625.0,"Objects":[{"StartTime":109625.0,"Position":208.0,"HyperDash":false}]},{"StartTime":109875.0,"Objects":[{"StartTime":109875.0,"Position":104.0,"HyperDash":false}]},{"StartTime":110125.0,"Objects":[{"StartTime":110125.0,"Position":256.0,"HyperDash":false},{"StartTime":110178.0,"Position":239.263885,"HyperDash":false},{"StartTime":110232.0,"Position":204.098785,"HyperDash":false},{"StartTime":110285.0,"Position":187.362686,"HyperDash":false},{"StartTime":110375.0,"Position":148.7542,"HyperDash":false}]},{"StartTime":110625.0,"Objects":[{"StartTime":110625.0,"Position":256.0,"HyperDash":false},{"StartTime":110678.0,"Position":283.827423,"HyperDash":false},{"StartTime":110731.0,"Position":319.654846,"HyperDash":false},{"StartTime":110784.0,"Position":325.482239,"HyperDash":false},{"StartTime":110874.0,"Position":363.2458,"HyperDash":false}]},{"StartTime":111125.0,"Objects":[{"StartTime":111125.0,"Position":208.0,"HyperDash":false},{"StartTime":111178.0,"Position":185.263885,"HyperDash":false},{"StartTime":111232.0,"Position":170.098785,"HyperDash":false},{"StartTime":111285.0,"Position":123.362686,"HyperDash":false},{"StartTime":111375.0,"Position":100.754196,"HyperDash":false}]},{"StartTime":111625.0,"Objects":[{"StartTime":111625.0,"Position":304.0,"HyperDash":false},{"StartTime":111678.0,"Position":318.7361,"HyperDash":false},{"StartTime":111732.0,"Position":353.901184,"HyperDash":false},{"StartTime":111785.0,"Position":357.6373,"HyperDash":false},{"StartTime":111875.0,"Position":411.2458,"HyperDash":false}]},{"StartTime":112125.0,"Objects":[{"StartTime":112125.0,"Position":252.0,"HyperDash":false}]},{"StartTime":112375.0,"Objects":[{"StartTime":112375.0,"Position":112.0,"HyperDash":false}]},{"StartTime":112625.0,"Objects":[{"StartTime":112625.0,"Position":72.0,"HyperDash":false}]},{"StartTime":112875.0,"Objects":[{"StartTime":112875.0,"Position":158.0,"HyperDash":false},{"StartTime":112937.0,"Position":180.39856,"HyperDash":false},{"StartTime":113000.0,"Position":253.684036,"HyperDash":false},{"StartTime":113062.0,"Position":263.862976,"HyperDash":false},{"StartTime":113125.0,"Position":289.459473,"HyperDash":false},{"StartTime":113187.0,"Position":294.857574,"HyperDash":false},{"StartTime":113250.0,"Position":301.491974,"HyperDash":false},{"StartTime":113312.0,"Position":306.150818,"HyperDash":false},{"StartTime":113375.0,"Position":278.112,"HyperDash":false},{"StartTime":113437.0,"Position":308.150818,"HyperDash":false},{"StartTime":113500.0,"Position":291.538177,"HyperDash":false},{"StartTime":113562.0,"Position":288.857574,"HyperDash":false},{"StartTime":113625.0,"Position":289.160065,"HyperDash":false},{"StartTime":113678.0,"Position":275.785217,"HyperDash":false},{"StartTime":113732.0,"Position":261.88623,"HyperDash":false},{"StartTime":113785.0,"Position":219.895935,"HyperDash":false},{"StartTime":113874.0,"Position":158.0,"HyperDash":false}]},{"StartTime":114125.0,"Objects":[{"StartTime":114125.0,"Position":176.0,"HyperDash":false},{"StartTime":114187.0,"Position":215.46962,"HyperDash":false},{"StartTime":114250.0,"Position":243.459351,"HyperDash":false},{"StartTime":114312.0,"Position":280.9655,"HyperDash":false},{"StartTime":114375.0,"Position":311.184082,"HyperDash":false},{"StartTime":114428.0,"Position":345.321442,"HyperDash":false},{"StartTime":114482.0,"Position":372.3753,"HyperDash":false},{"StartTime":114535.0,"Position":414.472534,"HyperDash":false},{"StartTime":114624.0,"Position":431.115143,"HyperDash":false}]},{"StartTime":114875.0,"Objects":[{"StartTime":114875.0,"Position":328.0,"HyperDash":false},{"StartTime":114937.0,"Position":303.669556,"HyperDash":false},{"StartTime":115000.0,"Position":279.312225,"HyperDash":false},{"StartTime":115062.0,"Position":265.2286,"HyperDash":false},{"StartTime":115125.0,"Position":258.051422,"HyperDash":false},{"StartTime":115178.0,"Position":262.0706,"HyperDash":false},{"StartTime":115231.0,"Position":286.7301,"HyperDash":false},{"StartTime":115284.0,"Position":315.1607,"HyperDash":false},{"StartTime":115374.0,"Position":349.780029,"HyperDash":false}]},{"StartTime":115625.0,"Objects":[{"StartTime":115625.0,"Position":488.0,"HyperDash":false},{"StartTime":115678.0,"Position":480.653168,"HyperDash":false},{"StartTime":115732.0,"Position":483.186554,"HyperDash":false},{"StartTime":115785.0,"Position":463.839722,"HyperDash":false},{"StartTime":115875.0,"Position":458.062073,"HyperDash":false}]},{"StartTime":116125.0,"Objects":[{"StartTime":116125.0,"Position":416.0,"HyperDash":false}]},{"StartTime":116375.0,"Objects":[{"StartTime":116375.0,"Position":288.0,"HyperDash":false}]},{"StartTime":116625.0,"Objects":[{"StartTime":116625.0,"Position":164.0,"HyperDash":false}]},{"StartTime":116875.0,"Objects":[{"StartTime":116875.0,"Position":36.0,"HyperDash":false}]},{"StartTime":117125.0,"Objects":[{"StartTime":117125.0,"Position":104.0,"HyperDash":false}]},{"StartTime":117375.0,"Objects":[{"StartTime":117375.0,"Position":232.0,"HyperDash":false}]},{"StartTime":117625.0,"Objects":[{"StartTime":117625.0,"Position":356.0,"HyperDash":false}]},{"StartTime":117875.0,"Objects":[{"StartTime":117875.0,"Position":484.0,"HyperDash":false}]},{"StartTime":118125.0,"Objects":[{"StartTime":118125.0,"Position":356.0,"HyperDash":false}]},{"StartTime":128125.0,"Objects":[{"StartTime":128125.0,"Position":256.0,"HyperDash":false}]},{"StartTime":128250.0,"Objects":[{"StartTime":128250.0,"Position":256.0,"HyperDash":false}]},{"StartTime":128500.0,"Objects":[{"StartTime":128500.0,"Position":336.0,"HyperDash":false}]},{"StartTime":128625.0,"Objects":[{"StartTime":128625.0,"Position":336.0,"HyperDash":false}]},{"StartTime":128875.0,"Objects":[{"StartTime":128875.0,"Position":400.0,"HyperDash":false}]},{"StartTime":129000.0,"Objects":[{"StartTime":129000.0,"Position":400.0,"HyperDash":false}]},{"StartTime":129250.0,"Objects":[{"StartTime":129250.0,"Position":492.0,"HyperDash":false}]},{"StartTime":129375.0,"Objects":[{"StartTime":129375.0,"Position":492.0,"HyperDash":false}]},{"StartTime":129625.0,"Objects":[{"StartTime":129625.0,"Position":440.0,"HyperDash":false},{"StartTime":129678.0,"Position":420.699738,"HyperDash":false},{"StartTime":129731.0,"Position":376.399475,"HyperDash":false},{"StartTime":129784.0,"Position":327.099243,"HyperDash":false},{"StartTime":129874.0,"Position":283.551636,"HyperDash":false}]},{"StartTime":130125.0,"Objects":[{"StartTime":130125.0,"Position":256.0,"HyperDash":false}]},{"StartTime":130250.0,"Objects":[{"StartTime":130250.0,"Position":256.0,"HyperDash":false}]},{"StartTime":130500.0,"Objects":[{"StartTime":130500.0,"Position":176.0,"HyperDash":false}]},{"StartTime":130625.0,"Objects":[{"StartTime":130625.0,"Position":176.0,"HyperDash":false}]},{"StartTime":130875.0,"Objects":[{"StartTime":130875.0,"Position":112.0,"HyperDash":false}]},{"StartTime":131000.0,"Objects":[{"StartTime":131000.0,"Position":112.0,"HyperDash":false}]},{"StartTime":131250.0,"Objects":[{"StartTime":131250.0,"Position":20.0,"HyperDash":false}]},{"StartTime":131375.0,"Objects":[{"StartTime":131375.0,"Position":20.0,"HyperDash":false}]},{"StartTime":131625.0,"Objects":[{"StartTime":131625.0,"Position":72.0,"HyperDash":false},{"StartTime":131678.0,"Position":85.16705,"HyperDash":false},{"StartTime":131732.0,"Position":139.959915,"HyperDash":false},{"StartTime":131785.0,"Position":179.126953,"HyperDash":false},{"StartTime":131875.0,"Position":228.44838,"HyperDash":false}]},{"StartTime":132125.0,"Objects":[{"StartTime":132125.0,"Position":408.0,"HyperDash":false},{"StartTime":132187.0,"Position":432.7211,"HyperDash":false},{"StartTime":132250.0,"Position":463.48645,"HyperDash":false},{"StartTime":132312.0,"Position":484.605652,"HyperDash":false},{"StartTime":132375.0,"Position":511.913147,"HyperDash":false},{"StartTime":132437.0,"Position":511.3131,"HyperDash":false},{"StartTime":132500.0,"Position":512.0,"HyperDash":false},{"StartTime":132562.0,"Position":512.0,"HyperDash":false},{"StartTime":132625.0,"Position":491.9296,"HyperDash":false},{"StartTime":132687.0,"Position":477.671265,"HyperDash":false},{"StartTime":132750.0,"Position":455.869171,"HyperDash":false},{"StartTime":132812.0,"Position":413.826355,"HyperDash":false},{"StartTime":132875.0,"Position":366.962769,"HyperDash":false},{"StartTime":132937.0,"Position":340.888336,"HyperDash":false},{"StartTime":133000.0,"Position":273.617157,"HyperDash":false},{"StartTime":133062.0,"Position":263.5604,"HyperDash":false},{"StartTime":133125.0,"Position":210.586578,"HyperDash":false},{"StartTime":133187.0,"Position":176.064163,"HyperDash":false},{"StartTime":133250.0,"Position":127.187744,"HyperDash":false},{"StartTime":133312.0,"Position":131.32103,"HyperDash":false},{"StartTime":133375.0,"Position":102.106659,"HyperDash":false},{"StartTime":133437.0,"Position":101.403084,"HyperDash":false},{"StartTime":133500.0,"Position":84.85893,"HyperDash":false},{"StartTime":133562.0,"Position":83.863945,"HyperDash":false},{"StartTime":133625.0,"Position":119.323433,"HyperDash":false},{"StartTime":133687.0,"Position":159.490738,"HyperDash":false},{"StartTime":133750.0,"Position":179.476852,"HyperDash":false},{"StartTime":133812.0,"Position":207.3787,"HyperDash":false},{"StartTime":133875.0,"Position":256.6099,"HyperDash":false},{"StartTime":133937.0,"Position":289.899384,"HyperDash":false},{"StartTime":134000.0,"Position":322.431061,"HyperDash":false},{"StartTime":134062.0,"Position":371.9527,"HyperDash":false},{"StartTime":134125.0,"Position":392.617126,"HyperDash":false},{"StartTime":134187.0,"Position":422.877838,"HyperDash":false},{"StartTime":134250.0,"Position":425.129883,"HyperDash":false},{"StartTime":134312.0,"Position":404.693054,"HyperDash":false},{"StartTime":134375.0,"Position":409.929779,"HyperDash":false},{"StartTime":134437.0,"Position":384.0832,"HyperDash":false},{"StartTime":134500.0,"Position":354.885651,"HyperDash":false},{"StartTime":134562.0,"Position":326.547424,"HyperDash":false},{"StartTime":134625.0,"Position":301.508575,"HyperDash":false},{"StartTime":134687.0,"Position":255.1601,"HyperDash":false},{"StartTime":134750.0,"Position":222.486877,"HyperDash":false},{"StartTime":134812.0,"Position":183.853729,"HyperDash":false},{"StartTime":134875.0,"Position":145.138245,"HyperDash":false},{"StartTime":134937.0,"Position":107.848343,"HyperDash":false},{"StartTime":135000.0,"Position":58.21479,"HyperDash":false},{"StartTime":135062.0,"Position":57.82658,"HyperDash":false},{"StartTime":135125.0,"Position":20.1227779,"HyperDash":false},{"StartTime":135187.0,"Position":0.0,"HyperDash":false},{"StartTime":135250.0,"Position":0.0,"HyperDash":false},{"StartTime":135312.0,"Position":0.0,"HyperDash":false},{"StartTime":135375.0,"Position":0.05981236,"HyperDash":false},{"StartTime":135428.0,"Position":14.5409756,"HyperDash":false},{"StartTime":135482.0,"Position":36.1827965,"HyperDash":false},{"StartTime":135535.0,"Position":37.5372772,"HyperDash":false},{"StartTime":135625.0,"Position":103.892265,"HyperDash":false}]},{"StartTime":135875.0,"Objects":[{"StartTime":135875.0,"Position":256.0,"HyperDash":false}]},{"StartTime":136000.0,"Objects":[{"StartTime":136000.0,"Position":256.0,"HyperDash":false}]},{"StartTime":136125.0,"Objects":[{"StartTime":136125.0,"Position":256.0,"HyperDash":false}]},{"StartTime":136375.0,"Objects":[{"StartTime":136375.0,"Position":136.0,"HyperDash":false}]},{"StartTime":136625.0,"Objects":[{"StartTime":136625.0,"Position":132.0,"HyperDash":false}]},{"StartTime":136750.0,"Objects":[{"StartTime":136750.0,"Position":133.0,"HyperDash":false}]},{"StartTime":137000.0,"Objects":[{"StartTime":137000.0,"Position":256.0,"HyperDash":false}]},{"StartTime":137125.0,"Objects":[{"StartTime":137125.0,"Position":255.0,"HyperDash":false}]},{"StartTime":137250.0,"Objects":[{"StartTime":137250.0,"Position":256.0,"HyperDash":false}]},{"StartTime":137375.0,"Objects":[{"StartTime":137375.0,"Position":256.0,"HyperDash":false}]},{"StartTime":137625.0,"Objects":[{"StartTime":137625.0,"Position":380.0,"HyperDash":false}]},{"StartTime":137875.0,"Objects":[{"StartTime":137875.0,"Position":376.0,"HyperDash":false}]},{"StartTime":138125.0,"Objects":[{"StartTime":138125.0,"Position":256.0,"HyperDash":false}]},{"StartTime":138375.0,"Objects":[{"StartTime":138375.0,"Position":256.0,"HyperDash":false}]},{"StartTime":138625.0,"Objects":[{"StartTime":138625.0,"Position":144.0,"HyperDash":false}]},{"StartTime":138750.0,"Objects":[{"StartTime":138750.0,"Position":144.0,"HyperDash":false}]},{"StartTime":139000.0,"Objects":[{"StartTime":139000.0,"Position":256.0,"HyperDash":false}]},{"StartTime":139125.0,"Objects":[{"StartTime":139125.0,"Position":256.0,"HyperDash":false}]},{"StartTime":139250.0,"Objects":[{"StartTime":139250.0,"Position":256.0,"HyperDash":false}]},{"StartTime":139375.0,"Objects":[{"StartTime":139375.0,"Position":256.0,"HyperDash":false}]},{"StartTime":139625.0,"Objects":[{"StartTime":139625.0,"Position":368.0,"HyperDash":false}]},{"StartTime":139875.0,"Objects":[{"StartTime":139875.0,"Position":256.0,"HyperDash":false}]},{"StartTime":140000.0,"Objects":[{"StartTime":140000.0,"Position":256.0,"HyperDash":false}]},{"StartTime":140125.0,"Objects":[{"StartTime":140125.0,"Position":256.0,"HyperDash":false},{"StartTime":140178.0,"Position":227.121277,"HyperDash":false},{"StartTime":140232.0,"Position":201.278854,"HyperDash":false},{"StartTime":140285.0,"Position":210.432343,"HyperDash":false},{"StartTime":140374.0,"Position":256.095947,"HyperDash":false}]},{"StartTime":140625.0,"Objects":[{"StartTime":140625.0,"Position":332.0,"HyperDash":false}]},{"StartTime":140750.0,"Objects":[{"StartTime":140750.0,"Position":332.0,"HyperDash":false}]},{"StartTime":141000.0,"Objects":[{"StartTime":141000.0,"Position":332.0,"HyperDash":false}]},{"StartTime":141125.0,"Objects":[{"StartTime":141125.0,"Position":332.0,"HyperDash":false}]},{"StartTime":141250.0,"Objects":[{"StartTime":141250.0,"Position":332.0,"HyperDash":false}]},{"StartTime":141375.0,"Objects":[{"StartTime":141375.0,"Position":332.0,"HyperDash":false}]},{"StartTime":141625.0,"Objects":[{"StartTime":141625.0,"Position":180.0,"HyperDash":false}]},{"StartTime":141875.0,"Objects":[{"StartTime":141875.0,"Position":180.0,"HyperDash":false}]},{"StartTime":142125.0,"Objects":[{"StartTime":142125.0,"Position":256.0,"HyperDash":false}]},{"StartTime":142375.0,"Objects":[{"StartTime":142375.0,"Position":256.0,"HyperDash":false}]},{"StartTime":142625.0,"Objects":[{"StartTime":142625.0,"Position":256.0,"HyperDash":false}]},{"StartTime":142750.0,"Objects":[{"StartTime":142750.0,"Position":256.0,"HyperDash":false}]},{"StartTime":143000.0,"Objects":[{"StartTime":143000.0,"Position":256.0,"HyperDash":false}]},{"StartTime":143125.0,"Objects":[{"StartTime":143125.0,"Position":256.0,"HyperDash":false}]},{"StartTime":143250.0,"Objects":[{"StartTime":143250.0,"Position":256.0,"HyperDash":false}]},{"StartTime":143375.0,"Objects":[{"StartTime":143375.0,"Position":256.0,"HyperDash":false}]},{"StartTime":143625.0,"Objects":[{"StartTime":143625.0,"Position":188.0,"HyperDash":false}]},{"StartTime":143875.0,"Objects":[{"StartTime":143875.0,"Position":324.0,"HyperDash":false}]},{"StartTime":144000.0,"Objects":[{"StartTime":144000.0,"Position":324.0,"HyperDash":false}]},{"StartTime":144125.0,"Objects":[{"StartTime":144125.0,"Position":324.0,"HyperDash":false},{"StartTime":144178.0,"Position":375.919983,"HyperDash":false},{"StartTime":144232.0,"Position":388.48,"HyperDash":false},{"StartTime":144285.0,"Position":424.4,"HyperDash":false},{"StartTime":144375.0,"Position":484.0,"HyperDash":false}]},{"StartTime":144625.0,"Objects":[{"StartTime":144625.0,"Position":392.0,"HyperDash":false}]},{"StartTime":144750.0,"Objects":[{"StartTime":144750.0,"Position":392.0,"HyperDash":false}]},{"StartTime":145000.0,"Objects":[{"StartTime":145000.0,"Position":324.0,"HyperDash":false}]},{"StartTime":145125.0,"Objects":[{"StartTime":145125.0,"Position":324.0,"HyperDash":false}]},{"StartTime":145250.0,"Objects":[{"StartTime":145250.0,"Position":324.0,"HyperDash":false}]},{"StartTime":145375.0,"Objects":[{"StartTime":145375.0,"Position":324.0,"HyperDash":false}]},{"StartTime":145625.0,"Objects":[{"StartTime":145625.0,"Position":188.0,"HyperDash":false}]},{"StartTime":145875.0,"Objects":[{"StartTime":145875.0,"Position":120.0,"HyperDash":false}]},{"StartTime":146125.0,"Objects":[{"StartTime":146125.0,"Position":256.0,"HyperDash":false}]},{"StartTime":146375.0,"Objects":[{"StartTime":146375.0,"Position":256.0,"HyperDash":false}]},{"StartTime":146625.0,"Objects":[{"StartTime":146625.0,"Position":256.0,"HyperDash":false}]},{"StartTime":146750.0,"Objects":[{"StartTime":146750.0,"Position":256.0,"HyperDash":false}]},{"StartTime":147000.0,"Objects":[{"StartTime":147000.0,"Position":176.0,"HyperDash":false}]},{"StartTime":147125.0,"Objects":[{"StartTime":147125.0,"Position":176.0,"HyperDash":false}]},{"StartTime":147250.0,"Objects":[{"StartTime":147250.0,"Position":176.0,"HyperDash":false}]},{"StartTime":147375.0,"Objects":[{"StartTime":147375.0,"Position":176.0,"HyperDash":false}]},{"StartTime":147625.0,"Objects":[{"StartTime":147625.0,"Position":256.0,"HyperDash":false}]},{"StartTime":147875.0,"Objects":[{"StartTime":147875.0,"Position":336.0,"HyperDash":false}]},{"StartTime":148000.0,"Objects":[{"StartTime":148000.0,"Position":336.0,"HyperDash":false}]},{"StartTime":148125.0,"Objects":[{"StartTime":148125.0,"Position":336.0,"HyperDash":false},{"StartTime":148178.0,"Position":375.538025,"HyperDash":false},{"StartTime":148231.0,"Position":390.979462,"HyperDash":false},{"StartTime":148284.0,"Position":386.895447,"HyperDash":false},{"StartTime":148374.0,"Position":370.6822,"HyperDash":false}]},{"StartTime":148625.0,"Objects":[{"StartTime":148625.0,"Position":256.0,"HyperDash":false}]},{"StartTime":148750.0,"Objects":[{"StartTime":148750.0,"Position":240.0,"HyperDash":false}]},{"StartTime":149000.0,"Objects":[{"StartTime":149000.0,"Position":240.0,"HyperDash":false}]},{"StartTime":149125.0,"Objects":[{"StartTime":149125.0,"Position":256.0,"HyperDash":false}]},{"StartTime":149250.0,"Objects":[{"StartTime":149250.0,"Position":272.0,"HyperDash":false}]},{"StartTime":149375.0,"Objects":[{"StartTime":149375.0,"Position":288.0,"HyperDash":false}]},{"StartTime":149625.0,"Objects":[{"StartTime":149625.0,"Position":256.0,"HyperDash":false}]},{"StartTime":149875.0,"Objects":[{"StartTime":149875.0,"Position":256.0,"HyperDash":false}]},{"StartTime":150125.0,"Objects":[{"StartTime":150125.0,"Position":116.0,"HyperDash":false}]},{"StartTime":150250.0,"Objects":[{"StartTime":150250.0,"Position":120.0,"HyperDash":false}]},{"StartTime":150375.0,"Objects":[{"StartTime":150375.0,"Position":132.0,"HyperDash":false}]},{"StartTime":150500.0,"Objects":[{"StartTime":150500.0,"Position":152.0,"HyperDash":false}]},{"StartTime":150625.0,"Objects":[{"StartTime":150625.0,"Position":176.0,"HyperDash":false}]},{"StartTime":150750.0,"Objects":[{"StartTime":150750.0,"Position":208.0,"HyperDash":false}]},{"StartTime":150875.0,"Objects":[{"StartTime":150875.0,"Position":232.0,"HyperDash":false}]},{"StartTime":151000.0,"Objects":[{"StartTime":151000.0,"Position":248.0,"HyperDash":false}]},{"StartTime":151125.0,"Objects":[{"StartTime":151125.0,"Position":256.0,"HyperDash":false}]},{"StartTime":151250.0,"Objects":[{"StartTime":151250.0,"Position":260.0,"HyperDash":false}]},{"StartTime":151375.0,"Objects":[{"StartTime":151375.0,"Position":272.0,"HyperDash":false}]},{"StartTime":151500.0,"Objects":[{"StartTime":151500.0,"Position":292.0,"HyperDash":false}]},{"StartTime":151625.0,"Objects":[{"StartTime":151625.0,"Position":316.0,"HyperDash":false}]},{"StartTime":151750.0,"Objects":[{"StartTime":151750.0,"Position":348.0,"HyperDash":false}]},{"StartTime":151875.0,"Objects":[{"StartTime":151875.0,"Position":372.0,"HyperDash":false}]},{"StartTime":152000.0,"Objects":[{"StartTime":152000.0,"Position":388.0,"HyperDash":false}]},{"StartTime":152125.0,"Objects":[{"StartTime":152125.0,"Position":404.0,"HyperDash":false},{"StartTime":152178.0,"Position":429.642151,"HyperDash":false},{"StartTime":152232.0,"Position":425.184479,"HyperDash":false},{"StartTime":152285.0,"Position":392.507416,"HyperDash":false},{"StartTime":152375.0,"Position":342.072266,"HyperDash":false}]},{"StartTime":152625.0,"Objects":[{"StartTime":152625.0,"Position":108.0,"HyperDash":false},{"StartTime":152678.0,"Position":112.349617,"HyperDash":false},{"StartTime":152732.0,"Position":112.903786,"HyperDash":false},{"StartTime":152785.0,"Position":119.761673,"HyperDash":false},{"StartTime":152874.0,"Position":169.927719,"HyperDash":false}]},{"StartTime":153125.0,"Objects":[{"StartTime":153125.0,"Position":256.0,"HyperDash":false},{"StartTime":153250.0,"Position":256.0,"HyperDash":false}]},{"StartTime":153375.0,"Objects":[{"StartTime":153375.0,"Position":256.0,"HyperDash":false},{"StartTime":153437.0,"Position":269.0,"HyperDash":false},{"StartTime":153500.0,"Position":241.0,"HyperDash":false},{"StartTime":153562.0,"Position":247.0,"HyperDash":false},{"StartTime":153625.0,"Position":256.0,"HyperDash":false},{"StartTime":153678.0,"Position":244.0,"HyperDash":false},{"StartTime":153732.0,"Position":258.0,"HyperDash":false},{"StartTime":153785.0,"Position":240.0,"HyperDash":false},{"StartTime":153875.0,"Position":256.0,"HyperDash":false}]},{"StartTime":154125.0,"Objects":[{"StartTime":154125.0,"Position":360.0,"HyperDash":false}]},{"StartTime":154250.0,"Objects":[{"StartTime":154250.0,"Position":360.0,"HyperDash":false}]},{"StartTime":154375.0,"Objects":[{"StartTime":154375.0,"Position":360.0,"HyperDash":false}]},{"StartTime":154625.0,"Objects":[{"StartTime":154625.0,"Position":256.0,"HyperDash":false}]},{"StartTime":154750.0,"Objects":[{"StartTime":154750.0,"Position":256.0,"HyperDash":false}]},{"StartTime":154875.0,"Objects":[{"StartTime":154875.0,"Position":256.0,"HyperDash":false}]},{"StartTime":155125.0,"Objects":[{"StartTime":155125.0,"Position":154.0,"HyperDash":false}]},{"StartTime":155250.0,"Objects":[{"StartTime":155250.0,"Position":154.0,"HyperDash":false}]},{"StartTime":155375.0,"Objects":[{"StartTime":155375.0,"Position":155.0,"HyperDash":false},{"StartTime":155437.0,"Position":134.040146,"HyperDash":false},{"StartTime":155500.0,"Position":134.7992,"HyperDash":false},{"StartTime":155562.0,"Position":112.444061,"HyperDash":false},{"StartTime":155625.0,"Position":165.451813,"HyperDash":false},{"StartTime":155678.0,"Position":137.274612,"HyperDash":false},{"StartTime":155732.0,"Position":123.117592,"HyperDash":false},{"StartTime":155785.0,"Position":139.026031,"HyperDash":false},{"StartTime":155874.0,"Position":155.0,"HyperDash":false}]},{"StartTime":156000.0,"Objects":[{"StartTime":156000.0,"Position":163.0,"HyperDash":false}]},{"StartTime":156125.0,"Objects":[{"StartTime":156125.0,"Position":163.0,"HyperDash":false}]},{"StartTime":156250.0,"Objects":[{"StartTime":156250.0,"Position":163.0,"HyperDash":false},{"StartTime":156312.0,"Position":183.5915,"HyperDash":false},{"StartTime":156375.0,"Position":203.198776,"HyperDash":false},{"StartTime":156437.0,"Position":232.230286,"HyperDash":false},{"StartTime":156500.0,"Position":268.618439,"HyperDash":false},{"StartTime":156562.0,"Position":314.36087,"HyperDash":false},{"StartTime":156625.0,"Position":335.6506,"HyperDash":false},{"StartTime":156687.0,"Position":380.1404,"HyperDash":false},{"StartTime":156750.0,"Position":422.05127,"HyperDash":false},{"StartTime":156874.0,"Position":473.144562,"HyperDash":false}]},{"StartTime":157125.0,"Objects":[{"StartTime":157125.0,"Position":320.0,"HyperDash":false},{"StartTime":157187.0,"Position":278.6829,"HyperDash":false},{"StartTime":157250.0,"Position":245.222778,"HyperDash":false},{"StartTime":157312.0,"Position":218.502289,"HyperDash":false},{"StartTime":157375.0,"Position":221.008591,"HyperDash":false},{"StartTime":157428.0,"Position":240.596039,"HyperDash":false},{"StartTime":157482.0,"Position":248.418259,"HyperDash":false},{"StartTime":157535.0,"Position":260.110321,"HyperDash":false},{"StartTime":157624.0,"Position":317.0907,"HyperDash":false}]},{"StartTime":157750.0,"Objects":[{"StartTime":157750.0,"Position":348.0,"HyperDash":false}]},{"StartTime":157875.0,"Objects":[{"StartTime":157875.0,"Position":380.0,"HyperDash":false}]},{"StartTime":158000.0,"Objects":[{"StartTime":158000.0,"Position":404.0,"HyperDash":false}]},{"StartTime":158125.0,"Objects":[{"StartTime":158125.0,"Position":412.0,"HyperDash":false}]},{"StartTime":158250.0,"Objects":[{"StartTime":158250.0,"Position":412.0,"HyperDash":false}]},{"StartTime":158375.0,"Objects":[{"StartTime":158375.0,"Position":404.0,"HyperDash":false}]},{"StartTime":158625.0,"Objects":[{"StartTime":158625.0,"Position":264.0,"HyperDash":false},{"StartTime":158687.0,"Position":234.814957,"HyperDash":false},{"StartTime":158750.0,"Position":191.04628,"HyperDash":false},{"StartTime":158875.0,"Position":264.0,"HyperDash":false}]},{"StartTime":159125.0,"Objects":[{"StartTime":159125.0,"Position":164.0,"HyperDash":false},{"StartTime":159187.0,"Position":197.185043,"HyperDash":false},{"StartTime":159250.0,"Position":236.95372,"HyperDash":false},{"StartTime":159375.0,"Position":164.0,"HyperDash":false}]},{"StartTime":159625.0,"Objects":[{"StartTime":159625.0,"Position":56.0,"HyperDash":false}]},{"StartTime":159875.0,"Objects":[{"StartTime":159875.0,"Position":64.0,"HyperDash":false}]},{"StartTime":160000.0,"Objects":[{"StartTime":160000.0,"Position":64.0,"HyperDash":false}]},{"StartTime":160125.0,"Objects":[{"StartTime":160125.0,"Position":64.0,"HyperDash":false},{"StartTime":160187.0,"Position":64.51918,"HyperDash":false},{"StartTime":160250.0,"Position":26.7402878,"HyperDash":false},{"StartTime":160375.0,"Position":64.0,"HyperDash":false}]},{"StartTime":160500.0,"Objects":[{"StartTime":160500.0,"Position":128.0,"HyperDash":false},{"StartTime":160562.0,"Position":132.164291,"HyperDash":false},{"StartTime":160625.0,"Position":134.379623,"HyperDash":false},{"StartTime":160750.0,"Position":128.0,"HyperDash":false}]},{"StartTime":160875.0,"Objects":[{"StartTime":160875.0,"Position":192.0,"HyperDash":false},{"StartTime":160937.0,"Position":189.164291,"HyperDash":false},{"StartTime":161000.0,"Position":198.379623,"HyperDash":false},{"StartTime":161125.0,"Position":192.0,"HyperDash":false}]},{"StartTime":161250.0,"Objects":[{"StartTime":161250.0,"Position":240.0,"HyperDash":false},{"StartTime":161312.0,"Position":248.7879,"HyperDash":false},{"StartTime":161375.0,"Position":289.975616,"HyperDash":false},{"StartTime":161500.0,"Position":240.0,"HyperDash":false}]},{"StartTime":161625.0,"Objects":[{"StartTime":161625.0,"Position":284.0,"HyperDash":false},{"StartTime":161687.0,"Position":327.2897,"HyperDash":false},{"StartTime":161750.0,"Position":339.019562,"HyperDash":false},{"StartTime":161875.0,"Position":284.0,"HyperDash":false}]},{"StartTime":162000.0,"Objects":[{"StartTime":162000.0,"Position":328.0,"HyperDash":false},{"StartTime":162062.0,"Position":364.361755,"HyperDash":false},{"StartTime":162124.0,"Position":407.040955,"HyperDash":false},{"StartTime":162249.0,"Position":328.0,"HyperDash":false}]},{"StartTime":162375.0,"Objects":[{"StartTime":162375.0,"Position":308.0,"HyperDash":false},{"StartTime":162437.0,"Position":269.638245,"HyperDash":false},{"StartTime":162499.0,"Position":228.959045,"HyperDash":false},{"StartTime":162624.0,"Position":308.0,"HyperDash":false}]},{"StartTime":162750.0,"Objects":[{"StartTime":162750.0,"Position":340.0,"HyperDash":false},{"StartTime":162812.0,"Position":374.361755,"HyperDash":false},{"StartTime":162874.0,"Position":419.040955,"HyperDash":false},{"StartTime":162999.0,"Position":340.0,"HyperDash":false}]},{"StartTime":163125.0,"Objects":[{"StartTime":163125.0,"Position":284.0,"HyperDash":false},{"StartTime":163187.0,"Position":280.849731,"HyperDash":false},{"StartTime":163249.0,"Position":271.649841,"HyperDash":false},{"StartTime":163374.0,"Position":284.0,"HyperDash":false}]},{"StartTime":163500.0,"Objects":[{"StartTime":163500.0,"Position":224.0,"HyperDash":false},{"StartTime":163562.0,"Position":227.849731,"HyperDash":false},{"StartTime":163624.0,"Position":211.649857,"HyperDash":false},{"StartTime":163749.0,"Position":224.0,"HyperDash":false}]},{"StartTime":163875.0,"Objects":[{"StartTime":163875.0,"Position":180.0,"HyperDash":false},{"StartTime":163937.0,"Position":134.564423,"HyperDash":false},{"StartTime":163999.0,"Position":102.8189,"HyperDash":false},{"StartTime":164124.0,"Position":180.0,"HyperDash":false}]},{"StartTime":164250.0,"Objects":[{"StartTime":164250.0,"Position":144.0,"HyperDash":false},{"StartTime":164312.0,"Position":107.832245,"HyperDash":false},{"StartTime":164375.0,"Position":79.14566,"HyperDash":false},{"StartTime":164500.0,"Position":144.0,"HyperDash":false}]},{"StartTime":164625.0,"Objects":[{"StartTime":164625.0,"Position":168.0,"HyperDash":false},{"StartTime":164687.0,"Position":182.167755,"HyperDash":false},{"StartTime":164750.0,"Position":232.85434,"HyperDash":false},{"StartTime":164875.0,"Position":168.0,"HyperDash":false}]},{"StartTime":165000.0,"Objects":[{"StartTime":165000.0,"Position":136.0,"HyperDash":false},{"StartTime":165062.0,"Position":117.871719,"HyperDash":false},{"StartTime":165124.0,"Position":101.605316,"HyperDash":false},{"StartTime":165249.0,"Position":136.0,"HyperDash":false}]},{"StartTime":165375.0,"Objects":[{"StartTime":165375.0,"Position":188.0,"HyperDash":false},{"StartTime":165437.0,"Position":220.128281,"HyperDash":false},{"StartTime":165499.0,"Position":222.394684,"HyperDash":false},{"StartTime":165624.0,"Position":188.0,"HyperDash":false}]},{"StartTime":165750.0,"Objects":[{"StartTime":165750.0,"Position":236.0,"HyperDash":false}]},{"StartTime":165875.0,"Objects":[{"StartTime":165875.0,"Position":236.0,"HyperDash":false}]},{"StartTime":166125.0,"Objects":[{"StartTime":166125.0,"Position":364.0,"HyperDash":false},{"StartTime":166187.0,"Position":369.388123,"HyperDash":false},{"StartTime":166250.0,"Position":391.656616,"HyperDash":false},{"StartTime":166312.0,"Position":357.6028,"HyperDash":false},{"StartTime":166375.0,"Position":309.5534,"HyperDash":false},{"StartTime":166499.0,"Position":282.373474,"HyperDash":false}]},{"StartTime":166625.0,"Objects":[{"StartTime":166625.0,"Position":264.0,"HyperDash":false},{"StartTime":166687.0,"Position":284.388123,"HyperDash":false},{"StartTime":166750.0,"Position":283.656616,"HyperDash":false},{"StartTime":166812.0,"Position":260.602844,"HyperDash":false},{"StartTime":166875.0,"Position":209.5534,"HyperDash":false},{"StartTime":166999.0,"Position":182.373489,"HyperDash":false}]},{"StartTime":167125.0,"Objects":[{"StartTime":167125.0,"Position":192.0,"HyperDash":false}]},{"StartTime":167375.0,"Objects":[{"StartTime":167375.0,"Position":320.0,"HyperDash":false}]},{"StartTime":167625.0,"Objects":[{"StartTime":167625.0,"Position":192.0,"HyperDash":false}]},{"StartTime":167750.0,"Objects":[{"StartTime":167750.0,"Position":256.0,"HyperDash":false}]},{"StartTime":167875.0,"Objects":[{"StartTime":167875.0,"Position":320.0,"HyperDash":false}]},{"StartTime":168125.0,"Objects":[{"StartTime":168125.0,"Position":256.0,"HyperDash":false}]},{"StartTime":168250.0,"Objects":[{"StartTime":168250.0,"Position":193.0,"HyperDash":false},{"StartTime":168308.0,"Position":488.0,"HyperDash":false},{"StartTime":168367.0,"Position":314.0,"HyperDash":false},{"StartTime":168425.0,"Position":135.0,"HyperDash":false},{"StartTime":168484.0,"Position":399.0,"HyperDash":false},{"StartTime":168542.0,"Position":404.0,"HyperDash":false},{"StartTime":168601.0,"Position":152.0,"HyperDash":false},{"StartTime":168660.0,"Position":353.0,"HyperDash":false},{"StartTime":168718.0,"Position":358.0,"HyperDash":false},{"StartTime":168777.0,"Position":447.0,"HyperDash":false},{"StartTime":168835.0,"Position":222.0,"HyperDash":false},{"StartTime":168894.0,"Position":382.0,"HyperDash":false},{"StartTime":168953.0,"Position":433.0,"HyperDash":false},{"StartTime":169011.0,"Position":450.0,"HyperDash":false},{"StartTime":169070.0,"Position":326.0,"HyperDash":false},{"StartTime":169128.0,"Position":414.0,"HyperDash":false},{"StartTime":169187.0,"Position":285.0,"HyperDash":false},{"StartTime":169246.0,"Position":336.0,"HyperDash":false},{"StartTime":169304.0,"Position":509.0,"HyperDash":false},{"StartTime":169363.0,"Position":334.0,"HyperDash":false},{"StartTime":169421.0,"Position":72.0,"HyperDash":false},{"StartTime":169480.0,"Position":425.0,"HyperDash":false},{"StartTime":169539.0,"Position":451.0,"HyperDash":false},{"StartTime":169597.0,"Position":220.0,"HyperDash":false},{"StartTime":169656.0,"Position":25.0,"HyperDash":false},{"StartTime":169714.0,"Position":77.0,"HyperDash":false},{"StartTime":169773.0,"Position":509.0,"HyperDash":false},{"StartTime":169832.0,"Position":90.0,"HyperDash":false},{"StartTime":169890.0,"Position":118.0,"HyperDash":false},{"StartTime":169949.0,"Position":58.0,"HyperDash":false},{"StartTime":170007.0,"Position":12.0,"HyperDash":false},{"StartTime":170066.0,"Position":215.0,"HyperDash":false},{"StartTime":170125.0,"Position":487.0,"HyperDash":false}]},{"StartTime":171125.0,"Objects":[{"StartTime":171125.0,"Position":446.0,"HyperDash":false},{"StartTime":171187.0,"Position":491.0,"HyperDash":false},{"StartTime":171250.0,"Position":459.0,"HyperDash":false},{"StartTime":171312.0,"Position":37.0,"HyperDash":false},{"StartTime":171375.0,"Position":291.0,"HyperDash":false},{"StartTime":171437.0,"Position":315.0,"HyperDash":false},{"StartTime":171500.0,"Position":35.0,"HyperDash":false},{"StartTime":171562.0,"Position":208.0,"HyperDash":false},{"StartTime":171625.0,"Position":504.0,"HyperDash":false},{"StartTime":171687.0,"Position":296.0,"HyperDash":false},{"StartTime":171750.0,"Position":105.0,"HyperDash":false},{"StartTime":171812.0,"Position":488.0,"HyperDash":false},{"StartTime":171875.0,"Position":230.0,"HyperDash":false},{"StartTime":171937.0,"Position":446.0,"HyperDash":false},{"StartTime":172000.0,"Position":241.0,"HyperDash":false},{"StartTime":172062.0,"Position":413.0,"HyperDash":false},{"StartTime":172125.0,"Position":357.0,"HyperDash":false}]},{"StartTime":172375.0,"Objects":[{"StartTime":172375.0,"Position":48.0,"HyperDash":false}]},{"StartTime":172625.0,"Objects":[{"StartTime":172625.0,"Position":20.0,"HyperDash":false},{"StartTime":172678.0,"Position":23.1916313,"HyperDash":false},{"StartTime":172732.0,"Position":25.55497,"HyperDash":false},{"StartTime":172785.0,"Position":75.26404,"HyperDash":false},{"StartTime":172875.0,"Position":108.478035,"HyperDash":false}]},{"StartTime":173125.0,"Objects":[{"StartTime":173125.0,"Position":240.0,"HyperDash":false}]},{"StartTime":173375.0,"Objects":[{"StartTime":173375.0,"Position":200.0,"HyperDash":false}]},{"StartTime":173625.0,"Objects":[{"StartTime":173625.0,"Position":324.0,"HyperDash":false},{"StartTime":173678.0,"Position":349.476471,"HyperDash":false},{"StartTime":173732.0,"Position":378.649323,"HyperDash":false},{"StartTime":173785.0,"Position":384.945282,"HyperDash":false},{"StartTime":173875.0,"Position":412.1426,"HyperDash":false}]},{"StartTime":174000.0,"Objects":[{"StartTime":174000.0,"Position":412.0,"HyperDash":false}]},{"StartTime":174125.0,"Objects":[{"StartTime":174125.0,"Position":412.0,"HyperDash":false},{"StartTime":174178.0,"Position":426.1397,"HyperDash":false},{"StartTime":174232.0,"Position":445.433044,"HyperDash":false},{"StartTime":174285.0,"Position":425.572754,"HyperDash":false},{"StartTime":174375.0,"Position":450.394928,"HyperDash":false}]},{"StartTime":174625.0,"Objects":[{"StartTime":174625.0,"Position":398.0,"HyperDash":false},{"StartTime":174678.0,"Position":380.028442,"HyperDash":false},{"StartTime":174732.0,"Position":327.434753,"HyperDash":false},{"StartTime":174785.0,"Position":306.4632,"HyperDash":false},{"StartTime":174875.0,"Position":242.473724,"HyperDash":false}]},{"StartTime":175000.0,"Objects":[{"StartTime":175000.0,"Position":245.0,"HyperDash":false}]},{"StartTime":175125.0,"Objects":[{"StartTime":175125.0,"Position":245.0,"HyperDash":false},{"StartTime":175178.0,"Position":247.860275,"HyperDash":false},{"StartTime":175232.0,"Position":229.566971,"HyperDash":false},{"StartTime":175285.0,"Position":219.427246,"HyperDash":false},{"StartTime":175375.0,"Position":206.605072,"HyperDash":false}]},{"StartTime":175625.0,"Objects":[{"StartTime":175625.0,"Position":259.0,"HyperDash":false},{"StartTime":175678.0,"Position":271.971558,"HyperDash":false},{"StartTime":175732.0,"Position":338.565247,"HyperDash":false},{"StartTime":175785.0,"Position":339.5368,"HyperDash":false},{"StartTime":175875.0,"Position":414.526276,"HyperDash":false}]},{"StartTime":176125.0,"Objects":[{"StartTime":176125.0,"Position":424.0,"HyperDash":false}]},{"StartTime":176375.0,"Objects":[{"StartTime":176375.0,"Position":272.0,"HyperDash":false}]},{"StartTime":176625.0,"Objects":[{"StartTime":176625.0,"Position":116.0,"HyperDash":false}]},{"StartTime":176875.0,"Objects":[{"StartTime":176875.0,"Position":173.0,"HyperDash":false},{"StartTime":176937.0,"Position":220.433136,"HyperDash":false},{"StartTime":177000.0,"Position":248.325027,"HyperDash":false},{"StartTime":177062.0,"Position":256.667,"HyperDash":false},{"StartTime":177125.0,"Position":270.778076,"HyperDash":false},{"StartTime":177187.0,"Position":271.125366,"HyperDash":false},{"StartTime":177250.0,"Position":267.005768,"HyperDash":false},{"StartTime":177312.0,"Position":259.9013,"HyperDash":false},{"StartTime":177375.0,"Position":212.426208,"HyperDash":false},{"StartTime":177437.0,"Position":239.901321,"HyperDash":false},{"StartTime":177500.0,"Position":249.239349,"HyperDash":false},{"StartTime":177562.0,"Position":285.125366,"HyperDash":false},{"StartTime":177625.0,"Position":270.676758,"HyperDash":false},{"StartTime":177678.0,"Position":275.8453,"HyperDash":false},{"StartTime":177732.0,"Position":255.82901,"HyperDash":false},{"StartTime":177785.0,"Position":207.305466,"HyperDash":false},{"StartTime":177874.0,"Position":173.0,"HyperDash":false}]},{"StartTime":178125.0,"Objects":[{"StartTime":178125.0,"Position":28.0,"HyperDash":false},{"StartTime":178187.0,"Position":78.55116,"HyperDash":false},{"StartTime":178250.0,"Position":102.707985,"HyperDash":false},{"StartTime":178312.0,"Position":129.259155,"HyperDash":false},{"StartTime":178375.0,"Position":179.41597,"HyperDash":false},{"StartTime":178428.0,"Position":226.516159,"HyperDash":false},{"StartTime":178482.0,"Position":240.222,"HyperDash":false},{"StartTime":178535.0,"Position":283.3222,"HyperDash":false},{"StartTime":178625.0,"Position":330.83194,"HyperDash":false}]},{"StartTime":178875.0,"Objects":[{"StartTime":178875.0,"Position":172.0,"HyperDash":false},{"StartTime":178937.0,"Position":221.551163,"HyperDash":false},{"StartTime":179000.0,"Position":253.707977,"HyperDash":false},{"StartTime":179062.0,"Position":274.259155,"HyperDash":false},{"StartTime":179125.0,"Position":323.415955,"HyperDash":false},{"StartTime":179178.0,"Position":344.516174,"HyperDash":false},{"StartTime":179232.0,"Position":379.222,"HyperDash":false},{"StartTime":179285.0,"Position":429.3222,"HyperDash":false},{"StartTime":179375.0,"Position":474.83194,"HyperDash":false}]},{"StartTime":179625.0,"Objects":[{"StartTime":179625.0,"Position":384.0,"HyperDash":false},{"StartTime":179678.0,"Position":348.327026,"HyperDash":false},{"StartTime":179732.0,"Position":316.224579,"HyperDash":false},{"StartTime":179785.0,"Position":267.12973,"HyperDash":false},{"StartTime":179875.0,"Position":244.098541,"HyperDash":false}]},{"StartTime":180000.0,"Objects":[{"StartTime":180000.0,"Position":244.0,"HyperDash":false}]},{"StartTime":180125.0,"Objects":[{"StartTime":180125.0,"Position":244.0,"HyperDash":false},{"StartTime":180178.0,"Position":217.455292,"HyperDash":false},{"StartTime":180232.0,"Position":186.277634,"HyperDash":false},{"StartTime":180285.0,"Position":129.732925,"HyperDash":false},{"StartTime":180375.0,"Position":85.77019,"HyperDash":false}]},{"StartTime":180625.0,"Objects":[{"StartTime":180625.0,"Position":100.0,"HyperDash":false},{"StartTime":180678.0,"Position":146.386475,"HyperDash":false},{"StartTime":180732.0,"Position":185.4029,"HyperDash":false},{"StartTime":180785.0,"Position":189.789368,"HyperDash":false},{"StartTime":180875.0,"Position":257.4834,"HyperDash":false}]},{"StartTime":181000.0,"Objects":[{"StartTime":181000.0,"Position":257.0,"HyperDash":false}]},{"StartTime":181125.0,"Objects":[{"StartTime":181125.0,"Position":256.0,"HyperDash":false},{"StartTime":181178.0,"Position":273.4897,"HyperDash":false},{"StartTime":181231.0,"Position":332.9794,"HyperDash":false},{"StartTime":181284.0,"Position":358.4691,"HyperDash":false},{"StartTime":181374.0,"Position":413.338379,"HyperDash":false}]},{"StartTime":181625.0,"Objects":[{"StartTime":181625.0,"Position":426.0,"HyperDash":false},{"StartTime":181678.0,"Position":383.4294,"HyperDash":false},{"StartTime":181732.0,"Position":353.2254,"HyperDash":false},{"StartTime":181785.0,"Position":325.654816,"HyperDash":false},{"StartTime":181875.0,"Position":267.648163,"HyperDash":false}]},{"StartTime":182000.0,"Objects":[{"StartTime":182000.0,"Position":267.0,"HyperDash":false}]},{"StartTime":182125.0,"Objects":[{"StartTime":182125.0,"Position":267.0,"HyperDash":false},{"StartTime":182178.0,"Position":226.982559,"HyperDash":false},{"StartTime":182232.0,"Position":205.749466,"HyperDash":false},{"StartTime":182285.0,"Position":176.327576,"HyperDash":false},{"StartTime":182375.0,"Position":168.9247,"HyperDash":false}]},{"StartTime":182625.0,"Objects":[{"StartTime":182625.0,"Position":140.0,"HyperDash":false},{"StartTime":182678.0,"Position":155.139557,"HyperDash":false},{"StartTime":182731.0,"Position":203.985977,"HyperDash":false},{"StartTime":182784.0,"Position":216.5605,"HyperDash":false},{"StartTime":182874.0,"Position":238.0753,"HyperDash":false}]},{"StartTime":183125.0,"Objects":[{"StartTime":183125.0,"Position":62.0,"HyperDash":false},{"StartTime":183178.0,"Position":70.6348648,"HyperDash":false},{"StartTime":183232.0,"Position":74.16411,"HyperDash":false},{"StartTime":183285.0,"Position":122.076561,"HyperDash":false},{"StartTime":183375.0,"Position":173.7103,"HyperDash":false}]},{"StartTime":183625.0,"Objects":[{"StartTime":183625.0,"Position":348.0,"HyperDash":false},{"StartTime":183678.0,"Position":324.143158,"HyperDash":false},{"StartTime":183732.0,"Position":333.585327,"HyperDash":false},{"StartTime":183785.0,"Position":270.711,"HyperDash":false},{"StartTime":183874.0,"Position":236.603912,"HyperDash":false}]},{"StartTime":184125.0,"Objects":[{"StartTime":184125.0,"Position":64.0,"HyperDash":false}]},{"StartTime":184250.0,"Objects":[{"StartTime":184250.0,"Position":488.0,"HyperDash":false},{"StartTime":184335.0,"Position":482.0,"HyperDash":false},{"StartTime":184421.0,"Position":321.0,"HyperDash":false},{"StartTime":184507.0,"Position":474.0,"HyperDash":false},{"StartTime":184593.0,"Position":252.0,"HyperDash":false},{"StartTime":184679.0,"Position":247.0,"HyperDash":false},{"StartTime":184765.0,"Position":406.0,"HyperDash":false},{"StartTime":184851.0,"Position":319.0,"HyperDash":false},{"StartTime":184937.0,"Position":253.0,"HyperDash":false},{"StartTime":185023.0,"Position":411.0,"HyperDash":false},{"StartTime":185109.0,"Position":205.0,"HyperDash":false},{"StartTime":185195.0,"Position":54.0,"HyperDash":false},{"StartTime":185281.0,"Position":224.0,"HyperDash":false},{"StartTime":185367.0,"Position":465.0,"HyperDash":false},{"StartTime":185453.0,"Position":432.0,"HyperDash":false},{"StartTime":185539.0,"Position":108.0,"HyperDash":false},{"StartTime":185625.0,"Position":95.0,"HyperDash":false}]},{"StartTime":186125.0,"Objects":[{"StartTime":186125.0,"Position":48.0,"HyperDash":false},{"StartTime":186187.0,"Position":89.47744,"HyperDash":false},{"StartTime":186250.0,"Position":93.06244,"HyperDash":false},{"StartTime":186312.0,"Position":160.382751,"HyperDash":false},{"StartTime":186375.0,"Position":190.718857,"HyperDash":false},{"StartTime":186437.0,"Position":209.265518,"HyperDash":false},{"StartTime":186500.0,"Position":273.188416,"HyperDash":false},{"StartTime":186562.0,"Position":294.6259,"HyperDash":false},{"StartTime":186625.0,"Position":321.75354,"HyperDash":false},{"StartTime":186678.0,"Position":352.728241,"HyperDash":false},{"StartTime":186732.0,"Position":377.1885,"HyperDash":false},{"StartTime":186785.0,"Position":409.4063,"HyperDash":false},{"StartTime":186874.0,"Position":463.955,"HyperDash":false}]},{"StartTime":187125.0,"Objects":[{"StartTime":187125.0,"Position":328.0,"HyperDash":false},{"StartTime":187178.0,"Position":313.795776,"HyperDash":false},{"StartTime":187232.0,"Position":325.474457,"HyperDash":false},{"StartTime":187285.0,"Position":313.270233,"HyperDash":false},{"StartTime":187375.0,"Position":298.734741,"HyperDash":false}]},{"StartTime":187625.0,"Objects":[{"StartTime":187625.0,"Position":184.0,"HyperDash":false},{"StartTime":187678.0,"Position":198.204239,"HyperDash":false},{"StartTime":187732.0,"Position":213.525543,"HyperDash":false},{"StartTime":187785.0,"Position":188.729767,"HyperDash":false},{"StartTime":187875.0,"Position":213.265274,"HyperDash":false}]},{"StartTime":188125.0,"Objects":[{"StartTime":188125.0,"Position":256.0,"HyperDash":false}]},{"StartTime":188250.0,"Objects":[{"StartTime":188250.0,"Position":175.0,"HyperDash":false},{"StartTime":188335.0,"Position":48.0,"HyperDash":false},{"StartTime":188421.0,"Position":307.0,"HyperDash":false},{"StartTime":188507.0,"Position":375.0,"HyperDash":false},{"StartTime":188593.0,"Position":149.0,"HyperDash":false},{"StartTime":188679.0,"Position":250.0,"HyperDash":false},{"StartTime":188765.0,"Position":142.0,"HyperDash":false},{"StartTime":188851.0,"Position":170.0,"HyperDash":false},{"StartTime":188937.0,"Position":281.0,"HyperDash":false},{"StartTime":189023.0,"Position":444.0,"HyperDash":false},{"StartTime":189109.0,"Position":414.0,"HyperDash":false},{"StartTime":189195.0,"Position":321.0,"HyperDash":false},{"StartTime":189281.0,"Position":328.0,"HyperDash":false},{"StartTime":189367.0,"Position":32.0,"HyperDash":false},{"StartTime":189453.0,"Position":259.0,"HyperDash":false},{"StartTime":189539.0,"Position":169.0,"HyperDash":false},{"StartTime":189625.0,"Position":207.0,"HyperDash":false}]},{"StartTime":190125.0,"Objects":[{"StartTime":190125.0,"Position":464.0,"HyperDash":false},{"StartTime":190187.0,"Position":452.522552,"HyperDash":false},{"StartTime":190250.0,"Position":408.937561,"HyperDash":false},{"StartTime":190312.0,"Position":358.617249,"HyperDash":false},{"StartTime":190375.0,"Position":321.281128,"HyperDash":false},{"StartTime":190437.0,"Position":281.7345,"HyperDash":false},{"StartTime":190500.0,"Position":240.811569,"HyperDash":false},{"StartTime":190562.0,"Position":234.374115,"HyperDash":false},{"StartTime":190625.0,"Position":190.246445,"HyperDash":false},{"StartTime":190678.0,"Position":161.271759,"HyperDash":false},{"StartTime":190732.0,"Position":103.811485,"HyperDash":false},{"StartTime":190785.0,"Position":84.59368,"HyperDash":false},{"StartTime":190874.0,"Position":48.04496,"HyperDash":false}]},{"StartTime":191125.0,"Objects":[{"StartTime":191125.0,"Position":184.0,"HyperDash":false},{"StartTime":191178.0,"Position":177.204239,"HyperDash":false},{"StartTime":191232.0,"Position":205.525543,"HyperDash":false},{"StartTime":191285.0,"Position":211.729767,"HyperDash":false},{"StartTime":191375.0,"Position":213.265274,"HyperDash":false}]},{"StartTime":191625.0,"Objects":[{"StartTime":191625.0,"Position":328.0,"HyperDash":false},{"StartTime":191678.0,"Position":303.795776,"HyperDash":false},{"StartTime":191732.0,"Position":318.474457,"HyperDash":false},{"StartTime":191785.0,"Position":296.270233,"HyperDash":false},{"StartTime":191875.0,"Position":298.734741,"HyperDash":false}]},{"StartTime":192125.0,"Objects":[{"StartTime":192125.0,"Position":164.0,"HyperDash":false}]},{"StartTime":192375.0,"Objects":[{"StartTime":192375.0,"Position":28.0,"HyperDash":false}]},{"StartTime":192625.0,"Objects":[{"StartTime":192625.0,"Position":28.0,"HyperDash":false}]},{"StartTime":192875.0,"Objects":[{"StartTime":192875.0,"Position":128.0,"HyperDash":false},{"StartTime":192937.0,"Position":126.887405,"HyperDash":false},{"StartTime":193000.0,"Position":175.597244,"HyperDash":false},{"StartTime":193062.0,"Position":198.553162,"HyperDash":false},{"StartTime":193125.0,"Position":235.7683,"HyperDash":false},{"StartTime":193187.0,"Position":291.259583,"HyperDash":false},{"StartTime":193250.0,"Position":330.488678,"HyperDash":false},{"StartTime":193312.0,"Position":338.450653,"HyperDash":false},{"StartTime":193375.0,"Position":390.71225,"HyperDash":false},{"StartTime":193437.0,"Position":356.065,"HyperDash":false},{"StartTime":193500.0,"Position":315.488678,"HyperDash":false},{"StartTime":193562.0,"Position":279.894,"HyperDash":false},{"StartTime":193625.0,"Position":235.7683,"HyperDash":false},{"StartTime":193678.0,"Position":221.0309,"HyperDash":false},{"StartTime":193732.0,"Position":168.99295,"HyperDash":false},{"StartTime":193785.0,"Position":164.902176,"HyperDash":false},{"StartTime":193875.0,"Position":128.0,"HyperDash":false}]},{"StartTime":194125.0,"Objects":[{"StartTime":194125.0,"Position":276.0,"HyperDash":false},{"StartTime":194187.0,"Position":324.316467,"HyperDash":false},{"StartTime":194250.0,"Position":328.094818,"HyperDash":false},{"StartTime":194312.0,"Position":373.795776,"HyperDash":false},{"StartTime":194375.0,"Position":386.318756,"HyperDash":false},{"StartTime":194428.0,"Position":376.7576,"HyperDash":false},{"StartTime":194482.0,"Position":404.218842,"HyperDash":false},{"StartTime":194535.0,"Position":384.551483,"HyperDash":false},{"StartTime":194624.0,"Position":374.339844,"HyperDash":false}]},{"StartTime":194875.0,"Objects":[{"StartTime":194875.0,"Position":236.0,"HyperDash":false},{"StartTime":194937.0,"Position":201.752014,"HyperDash":false},{"StartTime":195000.0,"Position":162.019058,"HyperDash":false},{"StartTime":195062.0,"Position":146.331146,"HyperDash":false},{"StartTime":195125.0,"Position":125.789307,"HyperDash":false},{"StartTime":195178.0,"Position":127.304863,"HyperDash":false},{"StartTime":195232.0,"Position":133.772476,"HyperDash":false},{"StartTime":195285.0,"Position":111.34684,"HyperDash":false},{"StartTime":195375.0,"Position":137.660187,"HyperDash":false}]},{"StartTime":195625.0,"Objects":[{"StartTime":195625.0,"Position":280.0,"HyperDash":false},{"StartTime":195678.0,"Position":279.7856,"HyperDash":false},{"StartTime":195732.0,"Position":250.37854,"HyperDash":false},{"StartTime":195785.0,"Position":235.164154,"HyperDash":false},{"StartTime":195875.0,"Position":231.818985,"HyperDash":false}]},{"StartTime":196125.0,"Objects":[{"StartTime":196125.0,"Position":104.0,"HyperDash":false}]},{"StartTime":196375.0,"Objects":[{"StartTime":196375.0,"Position":136.0,"HyperDash":false}]},{"StartTime":196625.0,"Objects":[{"StartTime":196625.0,"Position":116.0,"HyperDash":false}]},{"StartTime":196875.0,"Objects":[{"StartTime":196875.0,"Position":256.0,"HyperDash":false}]},{"StartTime":197000.0,"Objects":[{"StartTime":197000.0,"Position":332.0,"HyperDash":false}]},{"StartTime":197125.0,"Objects":[{"StartTime":197125.0,"Position":408.0,"HyperDash":false}]},{"StartTime":197250.0,"Objects":[{"StartTime":197250.0,"Position":392.0,"HyperDash":false}]},{"StartTime":197375.0,"Objects":[{"StartTime":197375.0,"Position":376.0,"HyperDash":false}]},{"StartTime":197625.0,"Objects":[{"StartTime":197625.0,"Position":396.0,"HyperDash":false}]},{"StartTime":197875.0,"Objects":[{"StartTime":197875.0,"Position":256.0,"HyperDash":false}]},{"StartTime":198000.0,"Objects":[{"StartTime":198000.0,"Position":256.0,"HyperDash":false}]},{"StartTime":198125.0,"Objects":[{"StartTime":198125.0,"Position":256.0,"HyperDash":false}]},{"StartTime":198625.0,"Objects":[{"StartTime":198625.0,"Position":136.0,"HyperDash":false}]},{"StartTime":199125.0,"Objects":[{"StartTime":199125.0,"Position":256.0,"HyperDash":false}]},{"StartTime":199625.0,"Objects":[{"StartTime":199625.0,"Position":376.0,"HyperDash":false}]},{"StartTime":200125.0,"Objects":[{"StartTime":200125.0,"Position":256.0,"HyperDash":false}]}]} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/112643.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/112643.osu new file mode 100644 index 0000000000..35ef17ae34 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/112643.osu @@ -0,0 +1,582 @@ +osu file format v9 + +[General] +StackLeniency: 0.7 +Mode: 0 + +[Difficulty] +HPDrainRate:7 +CircleSize:5 +OverallDifficulty:8 +ApproachRate:8 +SliderMultiplier:3.2 +SliderTickRate:2 + +[Events] +//Background and Video events +//Break Periods +2,16325,17625 +2,32325,33875 +2,66325,67375 +2,120135,127375 +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Sound Samples +//Background Colour Transformations +3,100,163,162,255 + +[TimingPoints] +125,500,4,1,0,50,1,0 +36125,-100,4,1,0,50,0,1 +66125,-100,4,1,0,50,0,0 +88125,-100,4,1,0,50,0,1 +120125,-100,4,1,0,50,0,0 +170125,-100,4,2,0,5,0,0 +170250,-100,4,1,0,50,0,0 +172125,-100,4,1,0,50,0,1 +200125,-100,4,1,0,50,0,0 + +[HitObjects] +64,80,2375,5,0 +172,192,2625,1,2 +152,36,2875,1,0 +80,176,3125,1,2 +224,112,3375,1,0 +192,256,3625,1,8 +136,116,3875,1,0 +272,32,4125,2,2,B|376:0|408:56|412:125|320:144|304:176|328:216|368:272|496:208,1,400,6|0 +504,216,4875,2,2,B|376:232|288:280|248:384,1,320 +384,344,5625,1,8 +272,216,5875,1,0 +272,216,6000,1,0 +272,216,6125,1,4 +92,280,6375,5,0 +124,108,6625,1,8 +256,8,6875,1,0 +388,108,7125,1,2 +420,280,7375,1,8 +256,296,7625,1,8 +256,120,7875,1,0 +443,152,8125,2,2,B|397:202|305:219|256:192|203:163|114:181|68:231,1,400,2|0 +24,256,8875,2,2,B|112:227|141:134|122:36|37:1,1,320 +16,132,9625,1,8 +136,280,9875,1,0 +136,280,10000,1,0 +136,280,10125,1,4 +256,172,10375,5,0 +368,56,10625,1,8 +196,116,10875,1,0 +316,116,11125,1,2 +144,56,11375,1,0 +256,0,11625,1,8 +112,128,11875,1,0 +164,280,12125,6,0,B|256:316,1,80,4|2 +100,348,12500,2,0,B|8:312,1,80,0|2 +144,212,12875,2,0,B|52:176,1,80,0|2 +208,144,13250,2,0,B|300:180,1,80,0|2 +332,324,13625,1,8 +180,324,13875,1,0 +256,240,14125,5,4 +256,240,14250,1,2 +324,112,14500,1,0 +324,112,14625,1,2 +192,56,14875,1,4 +192,56,15000,1,2 +256,164,15250,1,0 +256,164,15375,1,2 +256,20,15625,1,8 +120,56,15875,1,0 +256,92,16125,1,6 +20,152,18375,5,0 +180,136,18625,1,8 +52,228,18875,1,0 +120,84,19125,1,2 +128,244,19375,1,0 +48,84,19625,1,8 +192,212,19875,1,0 +300,72,20125,2,4,B|396:36|444:84|396:144|352:184|372:224|416:260|532:224|528:164,1,320,4|0 +472,40,20875,2,2,B|376:72|304:164|272:260|280:320,1,320 +404,352,21625,1,8 +432,196,21875,1,0 +432,196,22000,1,0 +432,196,22125,1,4 +296,100,22375,5,0 +168,196,22625,2,0,B|32:296,1,160,8|0 +268,212,23125,2,0,B|168:76,1,160,2|8 +252,312,23625,2,0,B|388:212,1,160,8|0 +484,96,24125,2,2,B|412:0|320:36|288:120|240:136|200:132|156:116|132:96|80:44,1,400,2|0 +72,24,24875,2,2,B|158:66|148:177|67:253|-19:210,1,320 +56,108,25625,1,8 +176,200,25875,1,0 +176,200,26000,1,0 +176,200,26125,1,4 +316,92,26375,5,0 +464,164,26625,2,0,B|394:224|412:336,1,160,2|0 +232,316,27125,2,0,B|306:256|284:144,1,160,2|8 +136,88,27625,1,8 +60,224,27875,1,0 +212,132,28125,6,0,B|256:32,1,80,4|2 +340,228,28500,2,0,B|384:128,1,80,0|2 +256,284,28875,2,0,B|212:184,1,80,4|2 +128,380,29250,2,0,B|84:280,1,80,0|2 +238,383,29625,2,0,B|406:379,1,160,8|0 +512,267,30125,5,4 +512,267,30250,1,2 +416,152,30500,1,0 +416,152,30625,1,2 +300,264,30875,1,4 +300,264,31000,1,2 +236,100,31250,1,0 +236,100,31375,1,2 +152,256,31625,1,8 +300,160,31875,1,0 +256,332,32125,1,6 +52,52,34625,5,0 +152,164,34875,1,0 +256,56,35125,1,4 +256,56,35625,1,2 +256,56,36125,2,4,B|331:63|364:136|320:224,1,160,4|0 +320,312,36625,1,8 +204,228,36875,1,0 +104,328,37125,2,2,B|24:287|44:188,1,160 +92,60,37625,1,8 +212,148,37875,1,0 +268,104,38000,1,0 +324,60,38125,2,0,B|452:184,1,160,4|0 +504,300,38625,1,8 +364,340,38875,1,0 +232,280,39125,6,2,B|150:282|69:198|105:87|179:53,2,320,2|2|6 +280,148,40375,1,0 +400,228,40625,2,0,B|520:368,1,160,8|0 +480,192,41125,1,2 +324,220,41375,1,2 +168,256,41625,1,8 +72,148,41875,1,2 +48,84,42000,1,2 +96,36,42125,2,0,B|164:108|256:44,1,160,6|0 +400,72,42625,1,2 +440,236,42875,1,2 +464,300,43000,1,2 +416,348,43125,2,0,B|348:276|256:340,1,160,6|0 +112,312,43625,1,2 +140,188,43875,1,0 +52,64,44125,5,6 +208,48,44375,1,0 +344,132,44625,1,8 +448,256,44875,2,2,B|401:321|285:337|217:242|233:163,2,320,2|2|0 +326,211,46125,2,2,B|279:146|163:130|95:225|111:304,1,320,6|0 +230,287,46875,2,2,B|277:352|393:368|461:273|445:194,1,320,6|8 +376,80,47625,1,8 +376,80,48125,6,0,B|304:128|216:96,1,160,4|0 +84,56,48625,1,8 +152,200,48875,1,0 +44,320,49125,2,0,B|121:364|204:320,1,160,4|0 +336,240,49625,5,8 +256,148,49875,1,0 +176,240,50125,1,0 +340,144,50625,1,0 +420,236,50875,1,0 +500,144,51125,1,2 +172,144,51625,1,2 +92,236,51875,1,0 +12,144,52125,6,0,B|160:48,1,160,4|0 +304,76,52625,1,8 +256,228,52875,1,0 +216,112,53125,2,0,B|364:208,1,160,2|0 +508,180,53625,1,8 +460,28,53875,1,0 +344,96,54125,1,2 +228,8,54375,1,0 +153,116,54625,1,2 +72,220,54875,1,0 +180,295,55125,1,2 +284,376,55375,1,0 +359,268,55625,1,2 +440,164,55875,1,0 +352,160,56125,6,0,B|466:294,1,160,4|0 +312,228,56625,1,8 +200,300,56875,1,0 +160,160,57125,2,0,B|46:294,1,160,4|0 +200,228,57625,1,8 +312,300,57875,1,0 +444,208,58125,2,0,B|362:164|380:56,1,160,2|0 +344,12,58500,1,0 +272,4,58625,2,0,B|232:88|120:68,1,160,2|0 +68,176,59125,2,0,B|148:220|132:328,1,160,2|0 +168,372,59500,1,0 +240,380,59625,2,0,B|280:296|392:316,1,160,2|0 +456,176,60125,5,6 +328,80,60375,1,0 +216,196,60625,1,8 +72,136,60875,2,2,B|54:209|91:305|191:336|269:306,2,320,2|2|0 +200,224,62125,2,2,B|182:150|219:54|319:23|397:53,1,320,2|0 +480,179,62875,2,2,B|499:252|462:348|362:379|284:349,1,320,2|0 +136,296,63625,2,0,B|67:220|140:136,1,160,8|0 +256,56,64125,5,6 +284,212,64375,1,0 +440,180,64625,1,8 +420,24,64875,1,0 +300,132,65125,1,6 +272,288,65375,1,0 +116,256,65625,1,8 +136,100,65875,1,0 +256,8,66125,1,4 +256,56,68125,6,0,B|298:128|244:237|123:241|74:173,1,320 +132,80,68875,2,2,B|344:328,1,320 +456,224,69625,1,8 +340,116,69875,1,0 +340,116,70000,1,0 +340,116,70125,1,4 +228,4,70375,5,0 +256,160,70625,2,0,B|186:224|88:168,1,160,2|0 +148,332,71125,2,0,B|216:396|316:340,1,160,2|8 +424,248,71625,1,8 +336,112,71875,1,0 +336,112,72000,1,0 +336,112,72125,1,4 +228,208,72375,2,0,B|139:179|144:80,1,160,0|8 +268,56,72875,2,2,B|272:164|220:272|120:308|72:308,1,320 +24,192,73625,1,8 +92,64,73875,1,0 +92,64,74000,1,0 +92,64,74125,1,4 +224,140,74375,5,0 +340,224,74625,2,0,B|412:211|428:121|363:77,1,160,2|0 +268,192,75125,2,0,B|196:205|180:295|245:339,1,160,2|0 +268,192,75625,2,0,B|104:168,1,160,8|0 +24,52,76125,6,0,B|132:40,1,80 +176,32,76375,1,2 +348,60,76625,1,2 +248,164,76875,1,2 +264,20,77125,1,2 +324,140,77375,1,2 +180,116,77625,1,2 +240,240,77875,1,0 +256,92,78125,1,4 +100,124,78375,5,0 +8,256,78625,2,0,B|64:332|176:304,1,160,8|0 +304,260,79125,2,0,B|248:184|136:212,1,160,2|0 +304,260,79625,1,8 +460,284,79875,1,2 +420,128,80125,6,0,B|332:128,1,80,4|0 +256,124,80375,1,2 +344,260,80625,1,2 +168,260,80875,1,2 +384,192,81125,1,2 +256,260,81375,1,2 +168,124,81625,1,2 +344,124,81875,1,2 +128,192,82125,1,4 +48,192,82250,6,0,B|48:84|152:52,1,160,2|0 +204,44,82625,2,0,B|204:152|308:184,1,160,2|0 +352,160,83000,2,0,B|244:160|212:264,1,160,2|0 +192,316,83375,2,0,B|84:316|52:212,1,160,2|2 +32,88,83875,1,2 +172,8,84125,1,4 +256,192,84250,12,6,86125 +256,192,86250,12,4,87125 +256,100,88125,6,2,B|308:116|368:104|404:16,1,160,6|0 +256,100,88625,1,8 +136,180,88875,1,0 +8,96,89125,2,0,B|-28:168|16:232|68:256,1,160,2|0 +164,312,89625,1,8 +288,236,89875,1,2 +288,236,90000,1,2 +288,236,90125,2,2,B|452:164,1,160,6|0 +476,32,90625,1,8 +332,104,90875,1,0 +180,104,91125,5,6 +36,32,91375,1,8 +56,164,91625,1,8 +56,164,92125,2,0,B|260:208,1,160,6|0 +84,296,92625,1,8 +220,376,92875,1,0 +320,268,93125,2,0,B|524:224,1,160,6|0 +432,80,93625,1,8 +296,152,93875,1,2 +296,152,94000,1,2 +296,152,94125,2,2,B|232:164|176:132|164:52,1,160,6|0 +216,232,94625,2,2,B|280:220|336:252|348:332,1,160,2|0 +341,304,95000,1,0 +341,304,95125,2,0,B|369:84,1,160,2|0 +171,80,95625,2,0,B|143:300,1,160,2|0 +43,358,96125,5,6 +81,219,96375,1,0 +169,332,96625,1,8 +304,272,96875,2,2,B|388:252|426:161|418:63|344:19,2,320,2|2|0 +240,144,98125,2,2,B|219:244|50:229|65:60|168:58,1,320 +240,144,98875,2,2,B|260:43|429:58|414:227|311:229,1,320,2|0 +180,292,99625,2,0,B|80:304|36:208,1,160,2|0 +48,64,100125,6,0,B|224:112,1,160,4|0 +348,52,100625,2,0,B|524:4,1,160,2|0 +504,172,101125,2,0,B|328:124,1,160,2|0 +204,184,101625,2,0,B|28:232,1,160,2|0 +49,226,102000,1,0 +49,226,102125,1,2 +256,324,102625,5,8 +384,256,102875,1,0 +256,188,103125,1,6 +256,188,103625,1,2 +128,256,103875,1,0 +256,324,104125,6,0,B|324:252|432:316,1,160,6|0 +492,168,104625,1,8 +332,188,104875,1,0 +256,60,105125,2,0,B|188:132|80:68,1,160,6|0 +20,216,105625,1,8 +180,196,105875,1,0 +368,156,106125,2,0,B|418:184|462:234|408:296,1,160,2|0 +220,80,106625,2,0,B|248:30|298:-14|360:40,1,160,2|0 +144,228,107125,2,0,B|94:200|50:150|104:88,1,160,2|0 +292,304,107625,2,0,B|264:354|214:398|152:344,1,160,2|0 +44,216,108125,6,0,B|145:221|172:132,1,160,6|0 +304,224,108625,1,8 +408,104,108875,1,0 +468,216,109125,2,0,B|367:221|340:132,1,160,6|0 +208,224,109625,1,8 +104,104,109875,1,0 +256,56,110125,2,0,B|144:180,1,160,2|0 +256,328,110625,2,0,B|368:204,1,160,2|0 +208,244,111125,2,0,B|96:368,1,160,2|0 +304,140,111625,2,0,B|416:16,1,160,2|0 +252,20,112125,5,6 +112,60,112375,1,0 +72,200,112625,1,8 +158,316,112875,2,2,B|236:321|324:259|326:152|278:89,2,320,2|2|0 +176,168,114125,2,2,B|214:236|313:276|405:220|431:145,1,320,2|0 +328,64,114875,2,2,B|259:102|219:201|275:293|350:319,1,320,2|0 +488,340,115625,2,0,B|456:172,1,160,2|0 +416,72,116125,5,6 +288,140,116375,1,0 +164,68,116625,1,8 +36,136,116875,1,0 +104,264,117125,1,6 +232,332,117375,1,0 +356,260,117625,1,8 +484,328,117875,1,0 +356,384,118125,1,6 +256,12,128125,5,4 +256,12,128250,1,2 +336,128,128500,1,0 +336,128,128625,1,2 +400,0,128875,1,0 +400,0,129000,1,2 +492,112,129250,1,0 +492,112,129375,1,2 +440,248,129625,2,2,B|272:284,1,160 +256,108,130125,5,4 +256,108,130250,1,2 +176,224,130500,1,0 +176,224,130625,1,2 +112,96,130875,1,0 +112,96,131000,1,2 +20,208,131250,1,0 +20,208,131375,1,2 +72,344,131625,2,2,B|240:380,1,160 +408,376,132125,6,0,B|512:352|584:248|592:-32|416:-48|256:-80|96:-16|56:88|8:224|88:304|144:336|184:368|256:368|256:368|328:368|368:336|424:304|504:224|456:88|416:-16|256:-80|96:-48|-80:-32|-72:248|0:352|104:376,1,2240,6|0 +256,192,135875,5,2 +256,192,136000,1,0 +256,192,136125,1,4 +136,104,136375,1,0 +132,240,136625,1,8 +133,240,136750,1,0 +256,280,137000,1,0 +255,280,137125,1,8 +256,280,137250,1,0 +256,280,137375,1,0 +380,240,137625,1,8 +376,104,137875,1,0 +256,124,138125,5,4 +256,124,138375,1,0 +144,192,138625,1,8 +144,192,138750,1,0 +256,260,139000,1,0 +256,260,139125,1,8 +256,260,139250,1,0 +256,260,139375,1,0 +368,192,139625,1,8 +256,124,139875,1,0 +256,124,140000,1,0 +256,124,140125,2,2,B|188:112|212:76|188:36|256:20,1,160,6|2 +332,128,140625,5,8 +332,128,140750,1,0 +332,256,141000,1,0 +332,256,141125,1,8 +332,256,141250,1,0 +332,256,141375,1,0 +180,256,141625,1,8 +180,128,141875,1,0 +256,56,142125,5,4 +256,56,142375,1,0 +256,160,142625,1,8 +256,160,142750,1,0 +256,264,143000,1,0 +256,264,143125,1,8 +256,264,143250,1,0 +256,264,143375,1,0 +188,352,143625,1,8 +324,352,143875,1,0 +324,352,144000,1,0 +324,352,144125,2,0,B|492:352,1,160,6|2 +392,280,144625,5,8 +392,280,144750,1,0 +324,192,145000,1,0 +324,192,145125,1,8 +324,192,145250,1,0 +324,192,145375,1,0 +188,192,145625,1,8 +120,280,145875,1,0 +256,288,146125,5,4 +256,288,146375,1,0 +256,176,146625,1,8 +256,176,146750,1,0 +176,96,147000,1,0 +176,96,147125,1,8 +176,96,147250,1,0 +176,96,147375,1,0 +256,16,147625,1,8 +336,96,147875,1,0 +336,96,148000,1,0 +336,96,148125,2,6,B|400:156|388:224|364:248,1,160,6|2 +256,272,148625,5,8 +240,264,148750,1,0 +240,180,149000,1,0 +256,172,149125,1,8 +272,164,149250,1,0 +288,156,149375,1,0 +256,64,149625,1,8 +256,64,149875,1,0 +116,180,150125,5,0 +120,200,150250,1,0 +132,224,150375,1,0 +152,236,150500,1,0 +176,240,150625,1,8 +208,240,150750,1,0 +232,236,150875,1,0 +248,216,151000,1,0 +256,192,151125,1,8 +260,168,151250,1,0 +272,144,151375,1,8 +292,132,151500,1,0 +316,128,151625,1,8 +348,128,151750,1,8 +372,132,151875,1,8 +388,152,152000,1,0 +404,184,152125,6,0,B|436:250|377:334|292:300,1,160,6|0 +108,200,152625,2,0,B|76:134|135:50|220:84,1,160,6|0 +256,192,153125,2,0,B|256:100,1,80,2|0 +256,192,153375,2,0,B|256:368,2,160,2|8|0 +360,60,154125,5,0 +360,60,154250,1,0 +360,60,154375,1,2 +256,12,154625,1,0 +256,12,154750,1,0 +256,12,154875,1,2 +154,64,155125,1,0 +154,64,155250,1,2 +155,63,155375,2,0,B|87:119|115:191|179:211|227:179,2,160,0|8|0 +163,74,156000,5,0 +163,74,156125,1,0 +163,74,156250,2,2,B|174:151|299:265|445:180|473:106,1,400,2|0 +320,80,157125,2,2,B|224:88|184:188|224:288|320:295,1,320 +348,292,157750,1,0 +380,280,157875,1,0 +404,260,158000,1,0 +412,236,158125,1,0 +412,208,158250,1,0 +404,180,158375,1,0 +264,68,158625,2,0,B|184:104,2,80,2|0|2 +164,216,159125,2,0,B|244:180,2,80,2|0|2 +56,144,159625,5,8 +64,276,159875,1,8 +64,276,160000,1,8 +64,276,160125,2,0,B|24:352,2,80,2|0|0 +128,288,160500,2,0,B|136:188,2,80,2|0|0 +192,300,160875,2,0,B|200:400,2,80,2|0|0 +240,256,161250,2,0,B|304:176,2,80,2|0|0 +284,304,161625,2,0,B|356:380,2,80,2|0|0 +328,256,162000,6,0,B|456:236,2,80,0|2|0 +308,192,162375,2,0,B|180:172,2,80,0|2|0 +340,136,162750,2,0,B|468:116,2,80,0|2|0 +284,100,163125,2,0,B|264:-28,2,80,0|2|0 +224,128,163500,2,0,B|204:256,2,80,0|2|0 +180,76,163875,6,0,B|92:52,2,80,2|0|0 +144,132,164250,2,0,B|72:184,2,80,2|0|0 +168,196,164625,2,0,B|240:248,2,80,2|0|0 +136,256,165000,2,0,B|96:340,2,80,2|0|0 +188,296,165375,2,0,B|228:380,2,80,2|0|0 +236,252,165750,1,0 +236,252,165875,1,2 +364,276,166125,6,2,B|408:176|360:156|320:168|296:176|268:132|264:112|272:76|304:52|328:40,1,240,2|0 +264,24,166625,2,2,B|308:124|260:144|220:132|196:124|168:168|164:188|172:224|204:248|228:260,1,240,2|0 +192,280,167125,1,0 +320,376,167375,1,0 +192,376,167625,1,0 +256,328,167750,1,0 +320,280,167875,1,0 +256,124,168125,1,6 +256,192,168250,12,0,170125 +256,192,171125,12,6,172125 +48,56,172375,5,0 +20,184,172625,2,0,B|16:264|92:316|152:304,1,160,8|0 +240,300,173125,1,2 +200,176,173375,1,0 +324,220,173625,2,0,B|360:220|416:258|412:338,1,160,8|0 +412,334,174000,1,0 +412,334,174125,2,0,B|456:156,1,160,6|0 +398,35,174625,2,0,B|220:-8,1,160,2|0 +245,0,175000,1,0 +245,0,175125,2,0,B|201:178,1,160,6|0 +259,299,175625,2,0,B|437:342,1,160,2|0 +424,176,176125,5,6 +272,128,176375,1,0 +116,152,176625,1,8 +173,253,176875,2,2,B|257:233|295:142|287:44|213:0,2,320,2|2|0 +28,204,178125,2,2,B|356:316,1,320 +172,360,178875,2,2,B|500:248,1,320,2|0 +384,148,179625,2,0,B|292:168|224:96|232:44,1,160,2|0 +244,93,180000,1,0 +244,93,180125,6,0,B|64:120,1,160,6|0 +100,268,180625,2,0,B|256:296,1,160,8|0 +257,296,181000,1,0 +256,296,181125,2,0,B|413:267,1,160,6|0 +426,116,181625,2,0,B|267:93,1,160,8|2 +267,93,182000,5,2 +267,93,182125,2,2,B|180:112|168:212,1,160,2|0 +140,380,182625,2,0,B|227:361|239:261,1,160,8|0 +62,169,183125,2,2,B|80:256|180:268,1,160,2|0 +348,296,183625,2,0,B|329:208|229:196,1,160,8|0 +64,172,184125,1,6 +256,192,184250,12,2,185625 +48,188,186125,6,2,B|96:108|256:108|256:192|256:276|416:276|464:196,1,480,2|0 +328,144,187125,2,0,B|296:316,1,160,2|0 +184,240,187625,2,0,B|216:68,1,160,2|0 +256,192,188125,1,6 +256,192,188250,12,2,189625 +464,188,190125,6,2,B|416:108|256:108|256:192|256:276|96:276|48:196,1,480,2|0 +184,144,191125,2,0,B|216:316,1,160,2|0 +328,240,191625,2,0,B|296:68,1,160,2|0 +164,32,192125,5,6 +28,84,192375,1,0 +28,228,192625,1,8 +128,332,192875,2,2,B|160:224|300:172|408:244,2,320,2|2|0 +276,356,194125,2,2,B|384:324|436:184|364:76,1,320 +236,28,194875,2,2,B|128:60|76:200|148:308,1,320,2|0 +280,268,195625,2,0,B|232:116,1,160,2|0 +104,52,196125,5,6 +136,192,196375,1,0 +116,344,196625,1,8 +256,312,196875,1,0 +332,312,197000,1,0 +408,332,197125,1,6 +392,264,197250,1,0 +376,192,197375,1,0 +396,40,197625,1,8 +256,72,197875,5,0 +256,72,198000,1,0 +256,72,198125,1,6 +136,192,198625,1,6 +256,312,199125,1,6 +376,192,199625,1,6 +256,192,200125,1,6 diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index 02d4cdbb94..d5d4d3b694 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -44,6 +44,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps base.PostProcess(); ApplyPositionOffsets(Beatmap); + ApplyPositionClamping(Beatmap); int index = 0; @@ -114,6 +115,17 @@ namespace osu.Game.Rulesets.Catch.Beatmaps initialiseHyperDash(beatmap); } + public void ApplyPositionClamping(IBeatmap beatmap) + { + foreach (var obj in beatmap.HitObjects.OfType()) + { + if (obj.EffectiveX < 0) + obj.XOffset += Math.Abs(obj.EffectiveX); + else if (obj.EffectiveX > CatchPlayfield.WIDTH) + obj.XOffset += CatchPlayfield.WIDTH - obj.EffectiveX; + } + } + private static void applyHardRockOffset(CatchHitObject hitObject, ref float? lastPosition, ref double lastStartTime, LegacyRandom rng) { float offsetPosition = hitObject.OriginalX; diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index d691ceceb1..671291ef0e 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -10,7 +10,6 @@ using osu.Framework.Bindables; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -103,8 +102,7 @@ namespace osu.Game.Rulesets.Catch.Objects AddNested(new TinyDroplet { StartTime = t + lastEvent.Value.Time, - X = ClampToPlayfield(EffectiveX + Path.PositionAt( - lastEvent.Value.PathProgress + (t / sinceLastTick) * (e.PathProgress - lastEvent.Value.PathProgress)).X), + X = EffectiveX + Path.PositionAt(lastEvent.Value.PathProgress + (t / sinceLastTick) * (e.PathProgress - lastEvent.Value.PathProgress)).X, }); } } @@ -121,7 +119,7 @@ namespace osu.Game.Rulesets.Catch.Objects { Samples = dropletSamples, StartTime = e.Time, - X = ClampToPlayfield(EffectiveX + Path.PositionAt(e.PathProgress).X), + X = EffectiveX + Path.PositionAt(e.PathProgress).X, }); break; @@ -132,16 +130,14 @@ namespace osu.Game.Rulesets.Catch.Objects { Samples = this.GetNodeSamples(nodeIndex++), StartTime = e.Time, - X = ClampToPlayfield(EffectiveX + Path.PositionAt(e.PathProgress).X), + X = EffectiveX + Path.PositionAt(e.PathProgress).X, }); break; } } } - public float EndX => ClampToPlayfield(EffectiveX + this.CurvePositionAt(1).X); - - public float ClampToPlayfield(float value) => Math.Clamp(value, 0, CatchPlayfield.WIDTH); + public float EndX => EffectiveX + this.CurvePositionAt(1).X; [JsonIgnore] public double Duration From b20b2203ac92c3c261df43967fe6d6e140e58496 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Mon, 11 Dec 2023 21:06:46 +0800 Subject: [PATCH 1896/4092] Fix mania Autoplay mod missing 0ms hold notes --- .../Mods/TestSceneManiaModAutoplay.cs | 42 +++++++++++++++++++ .../Replays/ManiaAutoGenerator.cs | 17 +++++--- 2 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModAutoplay.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModAutoplay.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModAutoplay.cs new file mode 100644 index 0000000000..f653f209c1 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModAutoplay.cs @@ -0,0 +1,42 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Mania.Tests.Mods +{ + public partial class TestSceneManiaModAutoplay : ModTestScene + { + protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); + + [Test] + public void TestPerfectScoreOnShortHoldNote() + { + CreateModTest(new ModTestData + { + Autoplay = true, + Beatmap = new ManiaBeatmap(new StageDefinition(1)) + { + HitObjects = new List + { + new HoldNote + { + StartTime = 100, + EndTime = 100, + }, + new HoldNote + { + StartTime = 100.1, + EndTime = 150, + }, + } + }, + PassCondition = () => Player.ScoreProcessor.Combo.Value == 4 + }); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index 7c8afdff12..dd3208bd89 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -87,15 +87,22 @@ namespace osu.Game.Rulesets.Mania.Replays private double calculateReleaseTime(HitObject currentObject, HitObject? nextObject) { double endTime = currentObject.GetEndTime(); + double releaseDelay = RELEASE_DELAY; - if (currentObject is HoldNote) - // hold note releases must be timed exactly. - return endTime; + if (currentObject is HoldNote hold) + { + if (hold.Duration > 0) + // hold note releases must be timed exactly. + return endTime; + + // Special case for super short hold notes + releaseDelay = 1; + } bool canDelayKeyUpFully = nextObject == null || - nextObject.StartTime > endTime + RELEASE_DELAY; + nextObject.StartTime > endTime + releaseDelay; - return endTime + (canDelayKeyUpFully ? RELEASE_DELAY : (nextObject.AsNonNull().StartTime - endTime) * 0.9); + return endTime + (canDelayKeyUpFully ? releaseDelay : (nextObject.AsNonNull().StartTime - endTime) * 0.9); } protected override HitObject? GetNextObject(int currentIndex) From f0ddcb22c6e33a20ae07cafeae5360fc1e2173f8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 12 Dec 2023 21:21:04 +0300 Subject: [PATCH 1897/4092] Remove arbitrary margin --- osu.Game/Graphics/UserInterface/OsuDropdown.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs index 96604275ea..ea0da30911 100644 --- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs @@ -413,11 +413,6 @@ namespace osu.Game.Graphics.UserInterface private partial class DropdownSearchTextBox : SearchTextBox { - public DropdownSearchTextBox() - { - TextContainer.Margin = new MarginPadding { Top = 4f }; - } - public override bool OnPressed(KeyBindingPressEvent e) { if (e.Action == GlobalAction.Back) From 67a9eab741ab3a715ad0882fa8597529e380f844 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 12 Dec 2023 21:21:11 +0300 Subject: [PATCH 1898/4092] Update caret layout --- osu.Game/Graphics/UserInterface/OsuTextBox.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index 4742da6d0b..08d38837f6 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -314,18 +314,16 @@ namespace osu.Game.Graphics.UserInterface public OsuCaret() { - RelativeSizeAxes = Axes.Y; - Size = new Vector2(1, 0.9f); - Colour = Color4.Transparent; - Anchor = Anchor.CentreLeft; - Origin = Anchor.CentreLeft; - Masking = true; - CornerRadius = 1; InternalChild = beatSync = new CaretBeatSyncedContainer { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Masking = true, + CornerRadius = 1f, RelativeSizeAxes = Axes.Both, + Height = 0.9f, }; } From daaadf3fc38efb9631eb01a618d0a0033439ca03 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Dec 2023 11:51:45 +0900 Subject: [PATCH 1899/4092] Fix IgnoreMiss judgements not updating accuracy --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 21 ++++++++++++++++++ osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 22 +++++++++---------- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index fd041d3dd0..d883b91abc 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -356,6 +356,27 @@ namespace osu.Game.Tests.Rulesets.Scoring Assert.That(actual, Is.EqualTo(expected).Within(Precision.FLOAT_EPSILON)); } + [TestCase(HitResult.Great)] + [TestCase(HitResult.LargeTickHit)] + public void TestAccuracyUpdateFromIgnoreMiss(HitResult maxResult) + { + scoreProcessor.ApplyBeatmap(new Beatmap + { + HitObjects = + { + new TestHitObject(maxResult, HitResult.IgnoreMiss) + } + }); + + var judgementResult = new JudgementResult(beatmap.HitObjects.Single(), new TestJudgement(maxResult, HitResult.IgnoreMiss)) + { + Type = HitResult.IgnoreMiss + }; + scoreProcessor.ApplyResult(judgementResult); + + Assert.That(scoreProcessor.Accuracy.Value, Is.Not.EqualTo(1)); + } + private class TestJudgement : Judgement { public override HitResult MaxResult { get; } diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index baeddbd0e8..f110172988 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -218,9 +218,6 @@ namespace osu.Game.Rulesets.Scoring scoreResultCounts[result.Type] = scoreResultCounts.GetValueOrDefault(result.Type) + 1; - if (!result.Type.IsScorable()) - return; - if (result.Type.IncreasesCombo()) Combo.Value++; else if (result.Type.BreaksCombo()) @@ -228,16 +225,18 @@ namespace osu.Game.Rulesets.Scoring result.ComboAfterJudgement = Combo.Value; - if (result.Type.AffectsAccuracy()) + if (result.Judgement.MaxResult.AffectsAccuracy()) { currentMaximumBaseScore += Judgement.ToNumericResult(result.Judgement.MaxResult); - currentBaseScore += Judgement.ToNumericResult(result.Type); currentAccuracyJudgementCount++; } + if (result.Type.AffectsAccuracy()) + currentBaseScore += Judgement.ToNumericResult(result.Type); + if (result.Type.IsBonus()) currentBonusPortion += GetBonusScoreChange(result); - else + else if (result.Type.IsScorable()) currentComboPortion += GetComboScoreChange(result); ApplyScoreChange(result); @@ -275,19 +274,18 @@ namespace osu.Game.Rulesets.Scoring scoreResultCounts[result.Type] = scoreResultCounts.GetValueOrDefault(result.Type) - 1; - if (!result.Type.IsScorable()) - return; - - if (result.Type.AffectsAccuracy()) + if (result.Judgement.MaxResult.AffectsAccuracy()) { currentMaximumBaseScore -= Judgement.ToNumericResult(result.Judgement.MaxResult); - currentBaseScore -= Judgement.ToNumericResult(result.Type); currentAccuracyJudgementCount--; } + if (result.Type.AffectsAccuracy()) + currentBaseScore -= Judgement.ToNumericResult(result.Type); + if (result.Type.IsBonus()) currentBonusPortion -= GetBonusScoreChange(result); - else + else if (result.Type.IsScorable()) currentComboPortion -= GetComboScoreChange(result); RemoveScoreChange(result); From 8977e74171793d6321b2b8b23a7599ee6d6e490a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Dec 2023 13:15:49 +0900 Subject: [PATCH 1900/4092] Fix editor test not waiting for editor to load --- .../Visual/Editing/TestSceneOpenEditorTimestamp.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs index 1a754d5145..1f46a08831 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Editing { public partial class TestSceneOpenEditorTimestamp : OsuGameTestScene { - private Editor editor => (Editor)Game.ScreenStack.CurrentScreen; + private Editor? editor => Game.ScreenStack.CurrentScreen as Editor; private EditorBeatmap editorBeatmap => editor.ChildrenOfType().Single(); private EditorClock editorClock => editor.ChildrenOfType().Single(); @@ -111,18 +111,18 @@ namespace osu.Game.Tests.Visual.Editing } private void addStepScreenModeTo(EditorScreenMode screenMode) => - AddStep("change screen to " + screenMode, () => editor.Mode.Value = screenMode); + AddStep("change screen to " + screenMode, () => editor!.Mode.Value = screenMode); private void assertOnScreenAt(EditorScreenMode screen, double time) { AddAssert($"stayed on {screen} at {time}", () => - editor.Mode.Value == screen + editor!.Mode.Value == screen && editorClock.CurrentTime == time ); } private void assertMovedScreenTo(EditorScreenMode screen, string text = "moved to") => - AddAssert($"{text} {screen}", () => editor.Mode.Value == screen); + AddAssert($"{text} {screen}", () => editor!.Mode.Value == screen); private void setUpEditor(RulesetInfo ruleset) { @@ -145,7 +145,7 @@ namespace osu.Game.Tests.Visual.Editing ((PlaySongSelect)Game.ScreenStack.CurrentScreen) .Edit(beatmapSet.Beatmaps.Last(beatmap => beatmap.Ruleset.Name == ruleset.Name)) ); - AddUntilStep("Wait for editor open", () => editor.ReadyForUse); + AddUntilStep("Wait for editor open", () => editor?.ReadyForUse == true); } } } From c31ff84417b76fd6e6e47ec4a92f93e029bbbeb8 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Dec 2023 13:32:27 +0900 Subject: [PATCH 1901/4092] Fix possible nullref --- osu.Game/Screens/Select/Details/AdvancedStats.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 87185c351e..1d6b383dea 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -126,12 +126,14 @@ namespace osu.Game.Screens.Select.Details mod.ApplyToDifficulty(adjustedDifficulty); } - switch (gameRuleset.Value.OnlineID) + IRulesetInfo ruleset = gameRuleset?.Value ?? beatmapInfo.Ruleset; + + switch (ruleset.OnlineID) { case 3: // Account for mania differences locally for now. // Eventually this should be handled in a more modular way, allowing rulesets to return arbitrary difficulty attributes. - ILegacyRuleset legacyRuleset = (ILegacyRuleset)gameRuleset.Value.CreateInstance(); + ILegacyRuleset legacyRuleset = (ILegacyRuleset)ruleset.CreateInstance(); // For the time being, the key count is static no matter what, because: // a) The method doesn't have knowledge of the active keymods. Doing so may require considerations for filtering. From 8c760e511041ba5635f5c5987e89262905b74f32 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Dec 2023 13:41:01 +0900 Subject: [PATCH 1902/4092] Fix hitobject count when creating from an IBeatmap --- .../Legacy/LegacyBeatmapConversionDifficultyInfo.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/Legacy/LegacyBeatmapConversionDifficultyInfo.cs b/osu.Game/Rulesets/Scoring/Legacy/LegacyBeatmapConversionDifficultyInfo.cs index 6f379e4ef1..baf771adef 100644 --- a/osu.Game/Rulesets/Scoring/Legacy/LegacyBeatmapConversionDifficultyInfo.cs +++ b/osu.Game/Rulesets/Scoring/Legacy/LegacyBeatmapConversionDifficultyInfo.cs @@ -1,8 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Game.Beatmaps; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Scoring.Legacy { @@ -44,7 +46,14 @@ namespace osu.Game.Rulesets.Scoring.Legacy public static LegacyBeatmapConversionDifficultyInfo FromAPIBeatmap(APIBeatmap apiBeatmap) => FromBeatmapInfo(apiBeatmap); - public static LegacyBeatmapConversionDifficultyInfo FromBeatmap(IBeatmap beatmap) => FromBeatmapInfo(beatmap.BeatmapInfo); + public static LegacyBeatmapConversionDifficultyInfo FromBeatmap(IBeatmap beatmap) => new LegacyBeatmapConversionDifficultyInfo + { + SourceRuleset = beatmap.BeatmapInfo.Ruleset, + CircleSize = beatmap.Difficulty.CircleSize, + OverallDifficulty = beatmap.Difficulty.OverallDifficulty, + CircleCount = beatmap.HitObjects.Count - beatmap.HitObjects.Count(h => h is IHasDuration), + TotalObjectCount = beatmap.HitObjects.Count + }; public static LegacyBeatmapConversionDifficultyInfo FromBeatmapInfo(IBeatmapInfo beatmapInfo) => new LegacyBeatmapConversionDifficultyInfo { From 2930b53edd9aa4e007bd6db05e0072ae5ae906ce Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Dec 2023 13:43:14 +0900 Subject: [PATCH 1903/4092] Simplify implementation --- .../Beatmaps/ManiaBeatmapConverter.cs | 2 +- .../Legacy/LegacyBeatmapConversionDifficultyInfo.cs | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index c4a8db92ed..c3cd623c5d 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps double roundedOverallDifficulty = Math.Round(difficulty.OverallDifficulty); - int countSliderOrSpinner = difficulty.TotalObjectCount - difficulty.CircleCount; + int countSliderOrSpinner = difficulty.EndTimeObjectCount; float percentSpecialObjects = (float)countSliderOrSpinner / difficulty.TotalObjectCount; if (percentSpecialObjects < 0.2) diff --git a/osu.Game/Rulesets/Scoring/Legacy/LegacyBeatmapConversionDifficultyInfo.cs b/osu.Game/Rulesets/Scoring/Legacy/LegacyBeatmapConversionDifficultyInfo.cs index baf771adef..2021aa127d 100644 --- a/osu.Game/Rulesets/Scoring/Legacy/LegacyBeatmapConversionDifficultyInfo.cs +++ b/osu.Game/Rulesets/Scoring/Legacy/LegacyBeatmapConversionDifficultyInfo.cs @@ -29,9 +29,12 @@ namespace osu.Game.Rulesets.Scoring.Legacy public float OverallDifficulty { get; set; } /// - /// The count of hitcircles in the beatmap. + /// The number of hitobjects in the beatmap with a distinct end time. /// - public int CircleCount { get; set; } + /// + /// Canonically, these are hitobjects are either sliders or spinners. + /// + public int EndTimeObjectCount { get; set; } /// /// The total count of hitobjects in the beatmap. @@ -42,7 +45,6 @@ namespace osu.Game.Rulesets.Scoring.Legacy float IBeatmapDifficultyInfo.ApproachRate => 0; double IBeatmapDifficultyInfo.SliderMultiplier => 0; double IBeatmapDifficultyInfo.SliderTickRate => 0; - int IBeatmapDifficultyInfo.EndTimeObjectCount => TotalObjectCount - CircleCount; public static LegacyBeatmapConversionDifficultyInfo FromAPIBeatmap(APIBeatmap apiBeatmap) => FromBeatmapInfo(apiBeatmap); @@ -51,7 +53,7 @@ namespace osu.Game.Rulesets.Scoring.Legacy SourceRuleset = beatmap.BeatmapInfo.Ruleset, CircleSize = beatmap.Difficulty.CircleSize, OverallDifficulty = beatmap.Difficulty.OverallDifficulty, - CircleCount = beatmap.HitObjects.Count - beatmap.HitObjects.Count(h => h is IHasDuration), + EndTimeObjectCount = beatmap.HitObjects.Count(h => h is IHasDuration), TotalObjectCount = beatmap.HitObjects.Count }; @@ -60,7 +62,7 @@ namespace osu.Game.Rulesets.Scoring.Legacy SourceRuleset = beatmapInfo.Ruleset, CircleSize = beatmapInfo.Difficulty.CircleSize, OverallDifficulty = beatmapInfo.Difficulty.OverallDifficulty, - CircleCount = beatmapInfo.Difficulty.TotalObjectCount - beatmapInfo.Difficulty.EndTimeObjectCount, + EndTimeObjectCount = beatmapInfo.Difficulty.EndTimeObjectCount, TotalObjectCount = beatmapInfo.Difficulty.TotalObjectCount }; } From 2579b42ac5dc5d7e7f41f86628bc8503dec26b64 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Dec 2023 14:01:03 +0900 Subject: [PATCH 1904/4092] 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 6609db3027..bd35bb8d54 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 a6b3527466..84a4e61267 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From 0be6743e87b241b36b396c840e1d5acc1bb6b578 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Dec 2023 14:07:38 +0900 Subject: [PATCH 1905/4092] Apply `Bindable.Parse` refactorings --- osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs | 2 +- osu.Game/Rulesets/Configuration/RulesetConfigManager.cs | 2 +- osu.Game/Rulesets/Mods/Mod.cs | 3 ++- .../Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs | 2 +- osu.Game/Skinning/ISerialisableDrawable.cs | 3 ++- osu.Game/Skinning/LegacySkin.cs | 3 ++- 6 files changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs b/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs index 37ea2a3f96..e5ba7f61bf 100644 --- a/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs +++ b/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs @@ -121,7 +121,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 break; default: - slider.Current.Parse(textBox.Current.Value); + slider.Current.Parse(textBox.Current.Value, CultureInfo.CurrentCulture); break; } } diff --git a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs index 0eea1ff215..418dc3576f 100644 --- a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs +++ b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs @@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Configuration if (setting != null) { - bindable.Parse(setting.Value); + bindable.Parse(setting.Value, CultureInfo.InvariantCulture); } else { diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 775f6a0ed4..d635dccab1 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Reflection; using Newtonsoft.Json; @@ -284,7 +285,7 @@ namespace osu.Game.Rulesets.Mods if (!(target is IParseable parseable)) throw new InvalidOperationException($"Bindable type {target.GetType().ReadableName()} is not {nameof(IParseable)}."); - parseable.Parse(source); + parseable.Parse(source, CultureInfo.InvariantCulture); } } diff --git a/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs index eabe9b9f64..151d469415 100644 --- a/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs +++ b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs @@ -103,7 +103,7 @@ namespace osu.Game.Screens.Edit.Timing break; default: - slider.Current.Parse(t.Text); + slider.Current.Parse(t.Text, CultureInfo.CurrentCulture); break; } } diff --git a/osu.Game/Skinning/ISerialisableDrawable.cs b/osu.Game/Skinning/ISerialisableDrawable.cs index 503b44c2dd..c9dcaca6d1 100644 --- a/osu.Game/Skinning/ISerialisableDrawable.cs +++ b/osu.Game/Skinning/ISerialisableDrawable.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Globalization; using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; @@ -46,7 +47,7 @@ namespace osu.Game.Skinning if (!(target is IParseable parseable)) throw new InvalidOperationException($"Bindable type {target.GetType().ReadableName()} is not {nameof(IParseable)}."); - parseable.Parse(source); + parseable.Parse(source, CultureInfo.InvariantCulture); } } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 2e91770919..9102231913 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.IO; using System.Linq; using JetBrains.Annotations; @@ -330,7 +331,7 @@ namespace osu.Game.Skinning var bindable = new Bindable(); if (val != null) - bindable.Parse(val); + bindable.Parse(val, CultureInfo.InvariantCulture); return bindable; } } From 996bc659e4ed9806edf9d1f433afc6c03ad7b621 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Dec 2023 14:29:51 +0900 Subject: [PATCH 1906/4092] Adjust some mod multipliers for initial leaderboard sanity --- osu.Game/Rulesets/Mods/ModClassic.cs | 2 +- osu.Game/Rulesets/Mods/ModSynesthesia.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModClassic.cs b/osu.Game/Rulesets/Mods/ModClassic.cs index 55b16297e2..42fdee0402 100644 --- a/osu.Game/Rulesets/Mods/ModClassic.cs +++ b/osu.Game/Rulesets/Mods/ModClassic.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Mods public override string Acronym => "CL"; - public override double ScoreMultiplier => 1; + public override double ScoreMultiplier => 0.5; public override IconUsage? Icon => FontAwesome.Solid.History; diff --git a/osu.Game/Rulesets/Mods/ModSynesthesia.cs b/osu.Game/Rulesets/Mods/ModSynesthesia.cs index 23cb135c50..9084127f33 100644 --- a/osu.Game/Rulesets/Mods/ModSynesthesia.cs +++ b/osu.Game/Rulesets/Mods/ModSynesthesia.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Mods public override string Name => "Synesthesia"; public override string Acronym => "SY"; public override LocalisableString Description => "Colours hit objects based on the rhythm."; - public override double ScoreMultiplier => 1; + public override double ScoreMultiplier => 0.8; public override ModType Type => ModType.Fun; } } From eb30a603d97e878565c58504831fc149a81ccf3d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Nov 2023 17:54:15 +0900 Subject: [PATCH 1907/4092] Fix typo in argument name --- osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs index 9da032e489..28d664a48b 100644 --- a/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs @@ -23,11 +23,11 @@ namespace osu.Game.Screens.Play.HUD { base.LoadComplete(); - AccuracyDisplay.BindValueChanged(mod => + AccuracyDisplay.BindValueChanged(mode => { Current.UnbindBindings(); - switch (mod.NewValue) + switch (mode.NewValue) { case AccuracyDisplayMode.Standard: Current.BindTo(scoreProcessor.Accuracy); From 0e4e916388443d9429dee33fd0e3795706252a89 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Dec 2023 15:04:52 +0900 Subject: [PATCH 1908/4092] Sneaky comment fix --- osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs index ee274fc45e..07045b76ca 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs @@ -234,7 +234,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy break; default: - // this is where things get fucked up. + // this is where things get a bit messed up. // honestly there's three modes to handle here but they seem really pointless? // let's wait to see if anyone actually uses them in skins. if (bodySprite != null) From 5e10f9f899de50d4fe589798a486ad36fa881885 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Dec 2023 14:42:32 +0900 Subject: [PATCH 1909/4092] Fix failing tests due to more textboxes being present with searchable dropdowns --- .../Navigation/TestSceneScreenNavigation.cs | 10 +++++---- .../Visual/Online/TestSceneChatOverlay.cs | 2 +- .../Visual/Online/TestSceneCommentActions.cs | 2 +- .../Visual/Settings/TestSceneSettingsPanel.cs | 22 +++++++++---------- .../SongSelect/TestScenePlaySongSelect.cs | 4 ++-- osu.Game/Screens/Select/FilterControl.cs | 2 +- 6 files changed, 22 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index c6a668a714..d8dc512787 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -143,13 +143,13 @@ namespace osu.Game.Tests.Visual.Navigation PushAndConfirm(() => songSelect = new TestPlaySongSelect()); - AddStep("set filter", () => songSelect.ChildrenOfType().Single().Current.Value = "test"); + AddStep("set filter", () => filterControlTextBox().Current.Value = "test"); AddStep("press back", () => InputManager.Click(MouseButton.Button1)); AddAssert("still at song select", () => Game.ScreenStack.CurrentScreen == songSelect); - AddAssert("filter cleared", () => string.IsNullOrEmpty(songSelect.ChildrenOfType().Single().Current.Value)); + AddAssert("filter cleared", () => string.IsNullOrEmpty(filterControlTextBox().Current.Value)); - AddStep("set filter again", () => songSelect.ChildrenOfType().Single().Current.Value = "test"); + AddStep("set filter again", () => filterControlTextBox().Current.Value = "test"); AddStep("open collections dropdown", () => { InputManager.MoveMouseTo(songSelect.ChildrenOfType().Single()); @@ -163,10 +163,12 @@ namespace osu.Game.Tests.Visual.Navigation .ChildrenOfType.DropdownMenu>().Single().State == MenuState.Closed); AddStep("press back a second time", () => InputManager.Click(MouseButton.Button1)); - AddAssert("filter cleared", () => string.IsNullOrEmpty(songSelect.ChildrenOfType().Single().Current.Value)); + AddAssert("filter cleared", () => string.IsNullOrEmpty(filterControlTextBox().Current.Value)); AddStep("press back a third time", () => InputManager.Click(MouseButton.Button1)); ConfirmAtMainMenu(); + + TextBox filterControlTextBox() => songSelect.ChildrenOfType().Single(); } [Test] diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 8a2a66f60f..58feab4ebb 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -631,7 +631,7 @@ namespace osu.Game.Tests.Visual.Online AddAssert("Nothing happened", () => this.ChildrenOfType().Any()); AddStep("Set report data", () => { - var field = this.ChildrenOfType().Single().ChildrenOfType().Single(); + var field = this.ChildrenOfType().Single().ChildrenOfType().First(); field.Current.Value = "test other"; }); diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentActions.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentActions.cs index dbf3b52572..10fdffb8e1 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCommentActions.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentActions.cs @@ -262,7 +262,7 @@ namespace osu.Game.Tests.Visual.Online AddAssert("Nothing happened", () => this.ChildrenOfType().Any()); AddStep("Set report data", () => { - var field = this.ChildrenOfType().Single().ChildrenOfType().Single(); + var field = this.ChildrenOfType().Single().ChildrenOfType().First(); field.Current.Value = report_text; var reason = this.ChildrenOfType>().Single(); reason.Current.Value = CommentReportReason.Other; diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs index 69e489b247..5d9c2f890c 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs @@ -49,12 +49,12 @@ namespace osu.Game.Tests.Visual.Settings AddStep("reset mouse", () => InputManager.MoveMouseTo(settings)); if (beforeLoad) - AddStep("set filter", () => settings.SectionsContainer.ChildrenOfType().First().Current.Value = "scaling"); + AddStep("set filter", () => settings.SectionsContainer.ChildrenOfType().First().Current.Value = "scaling"); AddUntilStep("wait for items to load", () => settings.SectionsContainer.ChildrenOfType().Any()); if (!beforeLoad) - AddStep("set filter", () => settings.SectionsContainer.ChildrenOfType().First().Current.Value = "scaling"); + AddStep("set filter", () => settings.SectionsContainer.ChildrenOfType().First().Current.Value = "scaling"); AddAssert("ensure all items match filter", () => settings.SectionsContainer .ChildrenOfType().Where(f => f.IsPresent) @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Settings AddUntilStep("wait for items to load", () => settings.SectionsContainer.ChildrenOfType().Any()); - AddStep("set filter", () => settings.SectionsContainer.ChildrenOfType().First().Current.Value = "scaling"); + AddStep("set filter", () => settings.SectionsContainer.ChildrenOfType().First().Current.Value = "scaling"); } [Test] @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual.Settings AddStep("reset mouse", () => InputManager.MoveMouseTo(settings)); AddUntilStep("sections loaded", () => settings.SectionsContainer.Children.Count > 0); - AddUntilStep("top-level textbox focused", () => settings.SectionsContainer.ChildrenOfType().FirstOrDefault()?.HasFocus == true); + AddUntilStep("top-level textbox focused", () => settings.SectionsContainer.ChildrenOfType().FirstOrDefault()?.HasFocus == true); AddStep("open key binding subpanel", () => { @@ -106,13 +106,13 @@ namespace osu.Game.Tests.Visual.Settings AddUntilStep("binding panel textbox focused", () => settings .ChildrenOfType().FirstOrDefault()? - .ChildrenOfType().FirstOrDefault()?.HasFocus == true); + .ChildrenOfType().FirstOrDefault()?.HasFocus == true); AddStep("Press back", () => settings .ChildrenOfType().FirstOrDefault()? .ChildrenOfType().FirstOrDefault()?.TriggerClick()); - AddUntilStep("top-level textbox focused", () => settings.SectionsContainer.ChildrenOfType().FirstOrDefault()?.HasFocus == true); + AddUntilStep("top-level textbox focused", () => settings.SectionsContainer.ChildrenOfType().FirstOrDefault()?.HasFocus == true); } [Test] @@ -121,7 +121,7 @@ namespace osu.Game.Tests.Visual.Settings AddStep("reset mouse", () => InputManager.MoveMouseTo(settings)); AddUntilStep("sections loaded", () => settings.SectionsContainer.Children.Count > 0); - AddUntilStep("top-level textbox focused", () => settings.SectionsContainer.ChildrenOfType().FirstOrDefault()?.HasFocus == true); + AddUntilStep("top-level textbox focused", () => settings.SectionsContainer.ChildrenOfType().FirstOrDefault()?.HasFocus == true); AddStep("open key binding subpanel", () => { @@ -133,19 +133,19 @@ namespace osu.Game.Tests.Visual.Settings AddUntilStep("binding panel textbox focused", () => settings .ChildrenOfType().FirstOrDefault()? - .ChildrenOfType().FirstOrDefault()?.HasFocus == true); + .ChildrenOfType().FirstOrDefault()?.HasFocus == true); AddStep("Escape", () => InputManager.Key(Key.Escape)); - AddUntilStep("top-level textbox focused", () => settings.SectionsContainer.ChildrenOfType().FirstOrDefault()?.HasFocus == true); + AddUntilStep("top-level textbox focused", () => settings.SectionsContainer.ChildrenOfType().FirstOrDefault()?.HasFocus == true); } [Test] public void TestSearchTextBoxSelectedOnShow() { - SearchTextBox searchTextBox = null!; + SettingsSearchTextBox searchTextBox = null!; - AddStep("set text", () => (searchTextBox = settings.SectionsContainer.ChildrenOfType().First()).Current.Value = "some text"); + AddStep("set text", () => (searchTextBox = settings.SectionsContainer.ChildrenOfType().First()).Current.Value = "some text"); AddAssert("no text selected", () => searchTextBox.SelectedText == string.Empty); AddRepeatStep("toggle visibility", () => settings.ToggleVisibility(), 2); AddAssert("search text selected", () => searchTextBox.SelectedText == searchTextBox.Current.Value); diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 84750d4c16..64f10cb427 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -1135,7 +1135,7 @@ namespace osu.Game.Tests.Visual.SongSelect { createSongSelect(); - AddStep("set filter text", () => songSelect!.FilterControl.ChildrenOfType().First().Text = "nonono"); + AddStep("set filter text", () => songSelect!.FilterControl.ChildrenOfType().First().Text = "nonono"); AddStep("select all", () => InputManager.Keys(PlatformAction.SelectAll)); AddStep("press ctrl-x", () => { @@ -1144,7 +1144,7 @@ namespace osu.Game.Tests.Visual.SongSelect InputManager.ReleaseKey(Key.ControlLeft); }); - AddAssert("filter text cleared", () => songSelect!.FilterControl.ChildrenOfType().First().Text, () => Is.Empty); + AddAssert("filter text cleared", () => songSelect!.FilterControl.ChildrenOfType().First().Text, () => Is.Empty); } private void waitForInitialSelection() diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index c15bd76ef8..1827eb58ca 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -250,7 +250,7 @@ namespace osu.Game.Screens.Select protected override bool OnHover(HoverEvent e) => true; - private partial class FilterControlTextBox : SeekLimitedSearchTextBox + internal partial class FilterControlTextBox : SeekLimitedSearchTextBox { private const float filter_text_size = 12; From c2d3dcdd9c1c7bfc1a8df9756db557549a5f4e47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Dec 2023 15:15:22 +0900 Subject: [PATCH 1910/4092] Fix slider tests and incorrect nullability handling around `freehandToolboxGroup` --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 7 +++++-- .../Visual/SongSelect/TestScenePlaySongSelect.cs | 9 ++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index e5dada19bb..28e972bacd 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -42,12 +42,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private int currentSegmentLength; [Resolved(CanBeNull = true)] + [CanBeNull] private IPositionSnapProvider positionSnapProvider { get; set; } [Resolved(CanBeNull = true)] + [CanBeNull] private IDistanceSnapProvider distanceSnapProvider { get; set; } [Resolved(CanBeNull = true)] + [CanBeNull] private FreehandSliderToolboxGroup freehandToolboxGroup { get; set; } private readonly IncrementalBSplineBuilder bSplineBuilder = new IncrementalBSplineBuilder { Degree = 4 }; @@ -363,7 +366,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private Vector2[] tryCircleArc(List segment) { - if (segment.Count < 3 || freehandToolboxGroup.CircleThreshold.Value == 0) return null; + if (segment.Count < 3 || freehandToolboxGroup?.CircleThreshold.Value == 0) return null; // Assume the segment creates a reasonable circular arc and then check if it reasonable var points = PathApproximator.BSplineToPiecewiseLinear(segment.ToArray(), bSplineBuilder.Degree); @@ -436,7 +439,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders loss /= points.Count; - return loss > freehandToolboxGroup.CircleThreshold.Value || totalWinding > MathHelper.TwoPi ? null : circleArcControlPoints; + return loss > freehandToolboxGroup?.CircleThreshold.Value || totalWinding > MathHelper.TwoPi ? null : circleArcControlPoints; } private enum SliderPlacementState diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 64f10cb427..6b53277964 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -22,7 +22,6 @@ using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Extensions; -using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; using osu.Game.Overlays; @@ -614,7 +613,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo != null); - AddStep("set filter text", () => songSelect!.FilterControl.ChildrenOfType().First().Text = "nonono"); + AddStep("set filter text", () => songSelect!.FilterControl.ChildrenOfType().First().Text = "nonono"); AddUntilStep("dummy selected", () => Beatmap.Value is DummyWorkingBeatmap); @@ -648,7 +647,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("carousel has correct", () => songSelect!.Carousel.SelectedBeatmapInfo?.MatchesOnlineID(target) == true); AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.MatchesOnlineID(target)); - AddStep("reset filter text", () => songSelect!.FilterControl.ChildrenOfType().First().Text = string.Empty); + AddStep("reset filter text", () => songSelect!.FilterControl.ChildrenOfType().First().Text = string.Empty); AddAssert("game still correct", () => Beatmap.Value?.BeatmapInfo.MatchesOnlineID(target) == true); AddAssert("carousel still correct", () => songSelect!.Carousel.SelectedBeatmapInfo.MatchesOnlineID(target)); @@ -666,7 +665,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo != null); - AddStep("set filter text", () => songSelect!.FilterControl.ChildrenOfType().First().Text = "nonono"); + AddStep("set filter text", () => songSelect!.FilterControl.ChildrenOfType().First().Text = "nonono"); AddUntilStep("dummy selected", () => Beatmap.Value is DummyWorkingBeatmap); @@ -689,7 +688,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("carousel has correct", () => songSelect!.Carousel.SelectedBeatmapInfo?.MatchesOnlineID(target) == true); AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.MatchesOnlineID(target)); - AddStep("set filter text", () => songSelect!.FilterControl.ChildrenOfType().First().Text = "nononoo"); + AddStep("set filter text", () => songSelect!.FilterControl.ChildrenOfType().First().Text = "nononoo"); AddUntilStep("game lost selection", () => Beatmap.Value is DummyWorkingBeatmap); AddAssert("carousel lost selection", () => songSelect!.Carousel.SelectedBeatmapInfo == null); From 0611a1ddc98462af34e6f1820b58dcd231769af5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Dec 2023 15:28:54 +0900 Subject: [PATCH 1911/4092] Avoid background processing falling over is `BeatmapSet` is `null` See https://github.com/ppy/osu/issues/10458. --- osu.Game/BackgroundDataStoreProcessor.cs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/osu.Game/BackgroundDataStoreProcessor.cs b/osu.Game/BackgroundDataStoreProcessor.cs index 8195856991..c8a108a5f6 100644 --- a/osu.Game/BackgroundDataStoreProcessor.cs +++ b/osu.Game/BackgroundDataStoreProcessor.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -136,19 +135,13 @@ namespace osu.Game // of other possible ways), but for now avoid queueing if the user isn't logged in at startup. if (api.IsLoggedIn) { - foreach (var b in r.All().Where(b => b.StarRating < 0 || (b.OnlineID > 0 && b.LastOnlineUpdate == null))) - { - Debug.Assert(b.BeatmapSet != null); - beatmapSetIds.Add(b.BeatmapSet.ID); - } + foreach (var b in r.All().Where(b => (b.StarRating < 0 || (b.OnlineID > 0 && b.LastOnlineUpdate == null)) && b.BeatmapSet != null)) + beatmapSetIds.Add(b.BeatmapSet!.ID); } else { - foreach (var b in r.All().Where(b => b.StarRating < 0)) - { - Debug.Assert(b.BeatmapSet != null); - beatmapSetIds.Add(b.BeatmapSet.ID); - } + foreach (var b in r.All().Where(b => b.StarRating < 0 && b.BeatmapSet != null)) + beatmapSetIds.Add(b.BeatmapSet!.ID); } }); From 4983845041794eb5acff6037be4e163b50ae8418 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Dec 2023 15:51:26 +0900 Subject: [PATCH 1912/4092] Update HT mod multiplier to match stable better --- osu.Game/Rulesets/Mods/RateAdjustModHelper.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Mods/RateAdjustModHelper.cs b/osu.Game/Rulesets/Mods/RateAdjustModHelper.cs index ffd4de0e90..8bc481921f 100644 --- a/osu.Game/Rulesets/Mods/RateAdjustModHelper.cs +++ b/osu.Game/Rulesets/Mods/RateAdjustModHelper.cs @@ -32,9 +32,9 @@ namespace osu.Game.Rulesets.Mods value -= 1; if (SpeedChange.Value >= 1) - value /= 5; - - return 1 + value; + return 1 + value / 5; + else + return 0.6 + value; } } From eff81be6fdbfd16fdc9040c3eeb0cd0087d5d7d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Dec 2023 15:38:06 +0900 Subject: [PATCH 1913/4092] Fix failing test and add coverage of conversion case --- .../SongSelect/TestSceneAdvancedStats.cs | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs index 8650119dd4..4bb2b557ff 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs @@ -14,7 +14,9 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.Select.Details; using osuTK.Graphics; @@ -38,6 +40,12 @@ namespace osu.Game.Tests.Visual.SongSelect Width = 500 }); + [SetUpSteps] + public void SetUpSteps() + { + AddStep("reset game ruleset", () => Ruleset.Value = new OsuRuleset().RulesetInfo); + } + private BeatmapInfo exampleBeatmapInfo => new BeatmapInfo { Ruleset = rulesets.AvailableRulesets.First(), @@ -66,8 +74,10 @@ namespace osu.Game.Tests.Visual.SongSelect } [Test] - public void TestManiaFirstBarText() + public void TestManiaFirstBarTextManiaBeatmap() { + AddStep("set game ruleset to mania", () => Ruleset.Value = new ManiaRuleset().RulesetInfo); + AddStep("set beatmap", () => advancedStats.BeatmapInfo = new BeatmapInfo { Ruleset = rulesets.GetRuleset(3) ?? throw new InvalidOperationException("osu!mania ruleset not found"), @@ -84,6 +94,27 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("first bar text is correct", () => advancedStats.ChildrenOfType().First().Text == BeatmapsetsStrings.ShowStatsCsMania); } + [Test] + public void TestManiaFirstBarTextConvert() + { + AddStep("set game ruleset to mania", () => Ruleset.Value = new ManiaRuleset().RulesetInfo); + + AddStep("set beatmap", () => advancedStats.BeatmapInfo = new BeatmapInfo + { + Ruleset = new OsuRuleset().RulesetInfo, + Difficulty = new BeatmapDifficulty + { + CircleSize = 5, + DrainRate = 4.3f, + OverallDifficulty = 4.5f, + ApproachRate = 3.1f + }, + StarRating = 8 + }); + + AddAssert("first bar text is correct", () => advancedStats.ChildrenOfType().First().Text == BeatmapsetsStrings.ShowStatsCsMania); + } + [Test] public void TestEasyMod() { From 2abf3a55ae0bc214c484867ed9a4b67dd267a1f8 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Dec 2023 16:08:34 +0900 Subject: [PATCH 1914/4092] Add IsLegacyScore to SoloScoreInfo --- osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index ac2d8152b1..732da3d5da 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -150,6 +150,12 @@ namespace osu.Game.Online.API.Requests.Responses #endregion + /// + /// Whether this represents a legacy (osu!stable) score. + /// + [JsonIgnore] + public bool IsLegacyScore => LegacyScoreId != null; + public override string ToString() => $"score_id: {ID} user_id: {UserID}"; /// @@ -191,6 +197,7 @@ namespace osu.Game.Online.API.Requests.Responses { OnlineID = OnlineID, LegacyOnlineID = (long?)LegacyScoreId ?? -1, + IsLegacyScore = IsLegacyScore, User = User ?? new APIUser { Id = UserID }, BeatmapInfo = new BeatmapInfo { OnlineID = BeatmapID }, Ruleset = new RulesetInfo { OnlineID = RulesetID }, From 110749205d7366f05ca5d56dd2f037bc7770bff2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Dec 2023 16:13:08 +0900 Subject: [PATCH 1915/4092] Cache `GameplayClockContainer` to allow usage of `OnSeek` --- osu.Game/Screens/Play/GameplayClockContainer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 5a713fdae7..4def1d36bb 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -17,6 +17,7 @@ namespace osu.Game.Screens.Play /// Encapsulates gameplay timing logic and provides a via DI for gameplay components to use. /// [Cached(typeof(IGameplayClock))] + [Cached(typeof(GameplayClockContainer))] public partial class GameplayClockContainer : Container, IAdjustableClock, IGameplayClock { public IBindable IsPaused => isPaused; From f2c6c348be850bb07347bfb46a9c836c6304895f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Dec 2023 16:13:23 +0900 Subject: [PATCH 1916/4092] Fix `HitError` `Clear` methods not correctly returning pooled drawables --- .../Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs | 6 +++++- .../Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index eb5221aa45..a9141110c5 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -485,7 +485,11 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters } } - public override void Clear() => judgementsContainer.Clear(); + public override void Clear() + { + foreach (var j in judgementsContainer) + j.FadeOut().Expire(); + } public enum CentreMarkerStyles { diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs index 5793713fca..d95959bf9e 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs @@ -63,7 +63,11 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters judgementsFlow.Push(GetColourForHitResult(judgement.Type)); } - public override void Clear() => judgementsFlow.Clear(); + public override void Clear() + { + foreach (var j in judgementsFlow) + j.FadeOut().Expire(); + } private partial class JudgementFlow : FillFlowContainer { From fb44fb18e021a153d3d44d12190d53f15c288838 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Dec 2023 16:42:20 +0900 Subject: [PATCH 1917/4092] Update in line with upstream changes --- osu.Game/Screens/Select/Details/AdvancedStats.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 019c38679e..9433588b03 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -128,6 +128,8 @@ namespace osu.Game.Screens.Select.Details IBeatmapDifficultyInfo baseDifficulty = BeatmapInfo?.Difficulty; BeatmapDifficulty adjustedDifficulty = null; + IRulesetInfo ruleset = gameRuleset?.Value ?? beatmapInfo.Ruleset; + if (baseDifficulty != null && (mods.Value.Any(m => m is IApplicableToDifficulty) || mods.Value.Any(m => m is IApplicableToRate))) { @@ -140,21 +142,17 @@ namespace osu.Game.Screens.Select.Details if (gameRuleset != null) { - Ruleset ruleset = gameRuleset.Value.CreateInstance(); - double rate = 1; foreach (var mod in mods.Value.OfType()) rate = mod.ApplyToRate(0, rate); - adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(originalDifficulty, rate); + adjustedDifficulty = ruleset.CreateInstance().GetRateAdjustedDisplayDifficulty(originalDifficulty, rate); rateAdjustTooltip.UpdateAttribute("AR", originalDifficulty.ApproachRate, adjustedDifficulty.ApproachRate); rateAdjustTooltip.UpdateAttribute("OD", originalDifficulty.OverallDifficulty, adjustedDifficulty.OverallDifficulty); } } - IRulesetInfo ruleset = gameRuleset?.Value ?? beatmapInfo.Ruleset; - switch (ruleset.OnlineID) { case 3: From 9433180ffeacf239b4f688ad954387a12fcbbfb7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Dec 2023 16:57:31 +0900 Subject: [PATCH 1918/4092] Fix various code quality and visual issues with `AdjustedAttributesTooltip` --- .../Mods/AdjustedAttributesTooltip.cs | 135 +++++++++--------- 1 file changed, 68 insertions(+), 67 deletions(-) diff --git a/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs b/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs index e9b7ee5c54..a56c09931a 100644 --- a/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs +++ b/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -15,84 +16,72 @@ using osuTK; namespace osu.Game.Overlays.Mods { - public partial class AdjustedAttributesTooltip : CompositeDrawable, ITooltip + public partial class AdjustedAttributesTooltip : VisibilityContainer, ITooltip { private readonly Dictionary> attributes = new Dictionary>(); - private readonly Container content; + private FillFlowContainer? attributesFillFlow; - private readonly FillFlowContainer attributesFillFlow; + private Container content = null!; [Resolved] private OsuColour colours { get; set; } = null!; - public AdjustedAttributesTooltip() - { - // Need to be initialized in constructor to ensure accessability in AddAttribute function - InternalChild = content = new Container - { - AutoSizeAxes = Axes.Both - }; - attributesFillFlow = new FillFlowContainer - { - Direction = FillDirection.Vertical, - AutoSizeAxes = Axes.Both - }; - } - [BackgroundDependencyLoader] private void load() { AutoSizeAxes = Axes.Both; Masking = true; - CornerRadius = 15; + CornerRadius = 5; - content.AddRange(new Drawable[] + InternalChildren = new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colours.Gray1, - Alpha = 0.8f - }, - new FillFlowContainer + content = new Container { AutoSizeAxes = Axes.Both, - Padding = new MarginPadding { Vertical = 10, Horizontal = 15 }, - Direction = FillDirection.Vertical, Children = new Drawable[] { - new OsuSpriteText + new Box { - Text = "One or more values are being adjusted by mods that change speed.", + RelativeSizeAxes = Axes.Both, + Colour = colours.Gray3, }, - attributesFillFlow + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Padding = new MarginPadding { Vertical = 10, Horizontal = 15 }, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = "One or more values are being adjusted by mods that change speed.", + }, + attributesFillFlow = new FillFlowContainer + { + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both + } + } + } } - } - }); - } + }, + }; - private void checkAttributes() - { foreach (var attribute in attributes) - { - if (!Precision.AlmostEquals(attribute.Value.Value.Old, attribute.Value.Value.New)) - { - content.Show(); - return; - } - } + attributesFillFlow?.Add(new AttributeDisplay(attribute.Key, attribute.Value.GetBoundCopy())); - content.Hide(); + updateVisibility(); } public void AddAttribute(string name) { Bindable newBindable = new Bindable(); - newBindable.BindValueChanged(_ => checkAttributes()); + newBindable.BindValueChanged(_ => updateVisibility()); attributes.Add(name, newBindable); - attributesFillFlow.Add(new AttributeDisplay(name, newBindable.GetBoundCopy())); + + attributesFillFlow?.Add(new AttributeDisplay(name, newBindable.GetBoundCopy())); } public void UpdateAttribute(string name, double oldValue, double newValue) @@ -102,8 +91,8 @@ namespace osu.Game.Overlays.Mods Bindable attribute = attributes[name]; OldNewPair attributeValue = attribute.Value; - attributeValue.Old = oldValue; - attributeValue.New = newValue; + attributeValue.OldValue = oldValue; + attributeValue.NewValue = newValue; attribute.Value = attributeValue; } @@ -116,14 +105,20 @@ namespace osu.Game.Overlays.Mods { } - public void Move(Vector2 pos) - { - Position = pos; - } + protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); + protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); - private struct OldNewPair + public void Move(Vector2 pos) => Position = pos; + + private void updateVisibility() { - public double Old, New; + if (!IsLoaded) + return; + + if (attributes.Any(attribute => !Precision.AlmostEquals(attribute.Value.Value.OldValue, attribute.Value.Value.NewValue))) + content.Show(); + else + content.Hide(); } private partial class AttributeDisplay : CompositeDrawable @@ -131,33 +126,39 @@ namespace osu.Game.Overlays.Mods public readonly Bindable AttributeValues; public readonly string AttributeName; - private readonly OsuSpriteText text = new OsuSpriteText - { - Font = OsuFont.Default.With(weight: FontWeight.Bold) - }; + private readonly OsuSpriteText text; - public AttributeDisplay(string name, Bindable boundCopy) + public AttributeDisplay(string name, Bindable values) { AutoSizeAxes = Axes.Both; AttributeName = name; - AttributeValues = boundCopy; - InternalChild = text; + AttributeValues = values; + + InternalChild = text = new OsuSpriteText + { + Font = OsuFont.Default.With(weight: FontWeight.Bold) + }; + AttributeValues.BindValueChanged(_ => update(), true); } private void update() { - if (Precision.AlmostEquals(AttributeValues.Value.Old, AttributeValues.Value.New)) + if (Precision.AlmostEquals(AttributeValues.Value.OldValue, AttributeValues.Value.NewValue)) { Hide(); + return; } - else - { - Show(); - text.Text = $"{AttributeName}: {(AttributeValues.Value.Old):0.0#} → {(AttributeValues.Value.New):0.0#}"; - } + + Show(); + text.Text = $"{AttributeName}: {(AttributeValues.Value.OldValue):0.0#} → {(AttributeValues.Value.NewValue):0.0#}"; } } + + private struct OldNewPair + { + public double OldValue, NewValue; + } } } From c131f91736dc2d947fce9c0decea049358d93536 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Dec 2023 16:59:09 +0900 Subject: [PATCH 1919/4092] Add tests --- .../TestSceneSliderEarlyHitJudgement.cs | 187 ++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneSliderEarlyHitJudgement.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderEarlyHitJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderEarlyHitJudgement.cs new file mode 100644 index 0000000000..46278dfb12 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderEarlyHitJudgement.cs @@ -0,0 +1,187 @@ +// 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.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Screens; +using osu.Game.Beatmaps; +using osu.Game.Replays; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens.Play; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public partial class TestSceneSliderEarlyHitJudgement : RateAdjustedBeatmapTestScene + { + private const double time_slider_start = 1000; + private const double time_slider_tick = 2000; + private const double time_slider_end = 3000; + + private static readonly Vector2 slider_start_position = new Vector2(256 - slider_path_length / 2, 192); + private static readonly Vector2 slider_end_position = new Vector2(256 + slider_path_length / 2, 192); + + private ScoreAccessibleReplayPlayer currentPlayer = null!; + + private const float slider_path_length = 200; + + private readonly List judgementResults = new List(); + + [Test] + public void TestHitEarlyMoveIntoFollowRegion() + { + performTest(new List + { + new OsuReplayFrame(time_slider_start - 300, slider_start_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_start - 200, slider_start_position + new Vector2(32, 0), OsuAction.LeftButton), + new OsuReplayFrame(time_slider_end - 200, slider_end_position + new Vector2(32, 0), OsuAction.LeftButton), + }); + + assertHeadJudgement(HitResult.Miss); + assertTickJudgement(HitResult.LargeTickHit); + assertTailJudgement(HitResult.LargeTickHit); + assertSliderJudgement(HitResult.IgnoreHit); + } + + [Test] + public void TestHitEarlyMoveOutsideFollowRegion() + { + performTest(new List + { + new OsuReplayFrame(time_slider_start - 300, slider_start_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_start - 200, slider_start_position + new Vector2(96, 0), OsuAction.LeftButton), + new OsuReplayFrame(time_slider_end - 200, slider_end_position + new Vector2(96, 0), OsuAction.LeftButton), + }); + + assertHeadJudgement(HitResult.Miss); + assertTickJudgement(HitResult.LargeTickMiss); + assertTailJudgement(HitResult.IgnoreMiss); + assertSliderJudgement(HitResult.IgnoreMiss); + } + + private void assertHeadJudgement(HitResult result) + { + AddAssert( + "check head result", + () => judgementResults.SingleOrDefault(r => r.HitObject is SliderHeadCircle)?.Type, + () => Is.EqualTo(result)); + } + + private void assertTickJudgement(HitResult result) + { + AddAssert( + "check tick result", + () => judgementResults.SingleOrDefault(r => r.HitObject is SliderTick)?.Type, + () => Is.EqualTo(result)); + } + + private void assertRepeatJudgement(HitResult result) + { + AddAssert( + "check tick result", + () => judgementResults.SingleOrDefault(r => r.HitObject is SliderRepeat)?.Type, + () => Is.EqualTo(result)); + } + + private void assertTailJudgement(HitResult result) + { + AddAssert( + "check tail result", + () => judgementResults.SingleOrDefault(r => r.HitObject is SliderTailCircle)?.Type, + () => Is.EqualTo(result)); + } + + private void assertSliderJudgement(HitResult result) + { + AddAssert( + "check slider result", + () => judgementResults.SingleOrDefault(r => r.HitObject is Slider)?.Type, + () => Is.EqualTo(result)); + } + + private Vector2 computePositionFromTime(double time) + { + Vector2 dist = slider_end_position - slider_start_position; + double t = (time - time_slider_start) / (time_slider_end - time_slider_start); + return slider_start_position + dist * (float)t; + } + + private void performTest(List frames, Action? adjustSliderFunc = null, bool classic = false) + { + Slider slider = new Slider + { + StartTime = time_slider_start, + Position = new Vector2(256 - slider_path_length / 2, 192), + TickDistanceMultiplier = 3, + ClassicSliderBehaviour = classic, + Path = new SliderPath(PathType.LINEAR, new[] + { + Vector2.Zero, + new Vector2(slider_path_length, 0), + }, slider_path_length), + }; + + adjustSliderFunc?.Invoke(slider); + + AddStep("load player", () => + { + Beatmap.Value = CreateWorkingBeatmap(new Beatmap + { + HitObjects = { slider }, + BeatmapInfo = + { + Difficulty = new BeatmapDifficulty + { + SliderMultiplier = 1, + SliderTickRate = 3 + }, + Ruleset = new OsuRuleset().RulesetInfo, + } + }); + + var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } }); + + p.OnLoadComplete += _ => + { + p.ScoreProcessor.NewJudgement += result => + { + if (currentPlayer == p) judgementResults.Add(result); + }; + }; + + LoadScreen(currentPlayer = p); + judgementResults.Clear(); + }); + + AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0); + AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); + AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value); + } + + private partial class ScoreAccessibleReplayPlayer : ReplayPlayer + { + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + + protected override bool PauseOnFocusLost => false; + + public ScoreAccessibleReplayPlayer(Score score) + : base(score, new PlayerConfiguration + { + AllowPause = false, + ShowResults = false, + }) + { + } + } + } +} From 3f67538d6103f411bbbaf6f54dcd750f650f2387 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Dec 2023 16:59:41 +0900 Subject: [PATCH 1920/4092] Allow slider to be tracked before its start time --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs index 292f2ffd7d..a1724d6fdc 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs @@ -153,11 +153,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } Tracking = - // in valid time range - Time.Current >= drawableSlider.HitObject.StartTime // even in an edge case where current time has exceeded the slider's time, we may not have finished judging. // we don't want to potentially update from Tracking=true to Tracking=false at this point. - && (!drawableSlider.AllJudged || Time.Current <= drawableSlider.HitObject.GetEndTime()) + (!drawableSlider.AllJudged || Time.Current <= drawableSlider.HitObject.GetEndTime()) // in valid position range && lastScreenSpaceMousePosition.HasValue && followCircleReceptor.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) && // valid action From 3131d376218af0673a1b79190f52f7aaf576105e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Dec 2023 17:00:21 +0900 Subject: [PATCH 1921/4092] Clear transformations with more fire --- osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs | 5 ++++- .../Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index a9141110c5..443863fb2f 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -488,7 +488,10 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters public override void Clear() { foreach (var j in judgementsContainer) - j.FadeOut().Expire(); + { + j.ClearTransforms(); + j.Expire(); + } } public enum CentreMarkerStyles diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs index d95959bf9e..65f4b50dde 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs @@ -66,7 +66,10 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters public override void Clear() { foreach (var j in judgementsFlow) - j.FadeOut().Expire(); + { + j.ClearTransforms(); + j.Expire(); + } } private partial class JudgementFlow : FillFlowContainer From 9a982a9564e11252ba3a7e0dda77ffc6c0d86e0b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Dec 2023 17:13:15 +0900 Subject: [PATCH 1922/4092] Tidy up `GetRateAdjustedDisplayDifficulty` implemetations --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 7 +++++-- osu.Game.Rulesets.Osu/OsuRuleset.cs | 10 ++++++++-- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 8 +++++--- osu.Game/Rulesets/Ruleset.cs | 1 + 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 12d197109b..bcb28328f0 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -240,10 +240,13 @@ namespace osu.Game.Rulesets.Catch { BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty); - double preempt = adjustedDifficulty.ApproachRate < 6 ? (1200.0 + 600.0 * (5 - adjustedDifficulty.ApproachRate) / 5) : (1200.0 - 750.0 * (adjustedDifficulty.ApproachRate - 5) / 5); + double preempt = adjustedDifficulty.ApproachRate < 6 + ? 1200.0 + 600.0 * (5 - adjustedDifficulty.ApproachRate) / 5 + : 1200.0 - 750.0 * (adjustedDifficulty.ApproachRate - 5) / 5; preempt /= rate; - adjustedDifficulty.ApproachRate = (float)(preempt > 1200 ? ((1800 - preempt) / 120) : ((1200 - preempt) / 150 + 5)); + + adjustedDifficulty.ApproachRate = (float)(preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5); return adjustedDifficulty; } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 88489f2dc3..5355743a50 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -336,12 +336,18 @@ namespace osu.Game.Rulesets.Osu { BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty); - double preempt = adjustedDifficulty.ApproachRate < 5 ? (1200.0 + 600.0 * (5 - adjustedDifficulty.ApproachRate) / 5) : (1200.0 - 750.0 * (adjustedDifficulty.ApproachRate - 5) / 5); + double preempt = adjustedDifficulty.ApproachRate < 5 + ? 1200.0 + 600.0 * (5 - adjustedDifficulty.ApproachRate) / 5 + : 1200.0 - 750.0 * (adjustedDifficulty.ApproachRate - 5) / 5; + preempt /= rate; - adjustedDifficulty.ApproachRate = (float)(preempt > 1200 ? ((1800 - preempt) / 120) : ((1200 - preempt) / 150 + 5)); + + adjustedDifficulty.ApproachRate = (float)(preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5); double hitwindow = 80.0 - 6 * adjustedDifficulty.OverallDifficulty; + hitwindow /= rate; + adjustedDifficulty.OverallDifficulty = (float)(80.0 - hitwindow) / 6; return adjustedDifficulty; diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 6e5cdbf2d1..94136a11d3 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -269,9 +269,11 @@ namespace osu.Game.Rulesets.Taiko { BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty); - double hitwindow = 35.0 - 15.0 * (adjustedDifficulty.OverallDifficulty - 5) / 5; - hitwindow /= rate; - adjustedDifficulty.OverallDifficulty = (float)(5 * (35 - hitwindow) / 15 + 5); + double hitWindow = 35.0 - 15.0 * (adjustedDifficulty.OverallDifficulty - 5) / 5; + + hitWindow /= rate; + + adjustedDifficulty.OverallDifficulty = (float)(5 * (35 - hitWindow) / 15 + 5); return adjustedDifficulty; } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index c7c81ecb55..37a35fd3ae 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -380,6 +380,7 @@ namespace osu.Game.Rulesets /// /// Applies changes to difficulty attributes for presenting to a user a rough estimate of how rate adjust mods affect difficulty. /// Importantly, this should NOT BE USED FOR ANY CALCULATIONS. + /// /// It is also not always correct, and arguably is never correct depending on your frame of mind. /// /// >The that will be adjusted. From e865de7a6b8cdbef85211b1d74571d1c8ac9b8b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 13 Dec 2023 09:25:56 +0100 Subject: [PATCH 1923/4092] Update test assertions in line with half time mod multiplier changes --- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index f0822ce2a8..fed5f68449 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -787,7 +787,8 @@ namespace osu.Game.Tests.Visual.UserInterface InputManager.MoveMouseTo(this.ChildrenOfType().Single(preset => preset.Preset.Value.Name == "Half Time 0.5x")); InputManager.Click(MouseButton.Left); }); - AddAssert("difficulty multiplier display shows correct value", () => modSelectOverlay.ChildrenOfType().Single().Current.Value, () => Is.EqualTo(0.5)); + AddAssert("difficulty multiplier display shows correct value", + () => modSelectOverlay.ChildrenOfType().Single().Current.Value, () => Is.EqualTo(0.1).Within(Precision.DOUBLE_EPSILON)); // this is highly unorthodox in a test, but because the `ModSettingChangeTracker` machinery heavily leans on events and object disposal and re-creation, // it is instrumental in the reproduction of the failure scenario that this test is supposed to cover. @@ -796,7 +797,8 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("open customisation area", () => modSelectOverlay.CustomisationButton!.TriggerClick()); AddStep("reset half time speed to default", () => modSelectOverlay.ChildrenOfType().Single() .ChildrenOfType>().Single().TriggerClick()); - AddUntilStep("difficulty multiplier display shows correct value", () => modSelectOverlay.ChildrenOfType().Single().Current.Value, () => Is.EqualTo(0.7)); + AddUntilStep("difficulty multiplier display shows correct value", + () => modSelectOverlay.ChildrenOfType().Single().Current.Value, () => Is.EqualTo(0.3).Within(Precision.DOUBLE_EPSILON)); } private void waitForColumnLoad() => AddUntilStep("all column content loaded", () => From 01710780525bca3126764bb77805964968ed19d6 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Dec 2023 17:33:24 +0900 Subject: [PATCH 1924/4092] Move object counts to BeatmapInfo --- osu.Game.Tournament/Models/TournamentBeatmap.cs | 6 ++++++ osu.Game/BackgroundDataStoreProcessor.cs | 11 +++++++---- osu.Game/Beatmaps/BeatmapDifficulty.cs | 9 --------- osu.Game/Beatmaps/BeatmapImporter.cs | 6 +++--- osu.Game/Beatmaps/BeatmapInfo.cs | 4 ++++ osu.Game/Beatmaps/BeatmapUpdater.cs | 4 ++-- osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs | 13 ------------- osu.Game/Beatmaps/IBeatmapInfo.cs | 13 +++++++++++++ .../Online/API/Requests/Responses/APIBeatmap.cs | 8 +++++--- .../Legacy/LegacyBeatmapConversionDifficultyInfo.cs | 4 ++-- 10 files changed, 42 insertions(+), 36 deletions(-) diff --git a/osu.Game.Tournament/Models/TournamentBeatmap.cs b/osu.Game.Tournament/Models/TournamentBeatmap.cs index 7f57b6a151..a7ba5b7db1 100644 --- a/osu.Game.Tournament/Models/TournamentBeatmap.cs +++ b/osu.Game.Tournament/Models/TournamentBeatmap.cs @@ -21,6 +21,10 @@ namespace osu.Game.Tournament.Models public double StarRating { get; set; } + public int EndTimeObjectCount { get; set; } + + public int TotalObjectCount { get; set; } + public IBeatmapMetadataInfo Metadata { get; set; } = new BeatmapMetadata(); public IBeatmapDifficultyInfo Difficulty { get; set; } = new BeatmapDifficulty(); @@ -41,6 +45,8 @@ namespace osu.Game.Tournament.Models Metadata = beatmap.Metadata; Difficulty = beatmap.Difficulty; Covers = beatmap.BeatmapSet?.Covers ?? new BeatmapSetOnlineCovers(); + EndTimeObjectCount = beatmap.EndTimeObjectCount; + TotalObjectCount = beatmap.TotalObjectCount; } public bool Equals(IBeatmapInfo? other) => other is TournamentBeatmap b && this.MatchesOnlineID(b); diff --git a/osu.Game/BackgroundDataStoreProcessor.cs b/osu.Game/BackgroundDataStoreProcessor.cs index c8a108a5f6..10cc13dc29 100644 --- a/osu.Game/BackgroundDataStoreProcessor.cs +++ b/osu.Game/BackgroundDataStoreProcessor.cs @@ -20,7 +20,6 @@ using osu.Game.Rulesets; using osu.Game.Scoring; using osu.Game.Scoring.Legacy; using osu.Game.Screens.Play; -using Realms; namespace osu.Game { @@ -177,9 +176,13 @@ namespace osu.Game { Logger.Log("Querying for beatmaps with missing hitobject counts to reprocess..."); - HashSet beatmapIds = realmAccess.Run(r => new HashSet(r.All() - .Filter($"{nameof(BeatmapInfo.Difficulty)}.{nameof(BeatmapDifficulty.TotalObjectCount)} == 0") - .AsEnumerable().Select(b => b.ID))); + HashSet beatmapIds = new HashSet(); + + realmAccess.Run(r => + { + foreach (var b in r.All().Where(b => b.TotalObjectCount == 0)) + beatmapIds.Add(b.ID); + }); Logger.Log($"Found {beatmapIds.Count} beatmaps which require reprocessing."); diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs index 785728141e..ac2267380d 100644 --- a/osu.Game/Beatmaps/BeatmapDifficulty.cs +++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs @@ -21,9 +21,6 @@ namespace osu.Game.Beatmaps public double SliderMultiplier { get; set; } = 1.4; public double SliderTickRate { get; set; } = 1; - public int EndTimeObjectCount { get; set; } - public int TotalObjectCount { get; set; } - public BeatmapDifficulty() { } @@ -47,9 +44,6 @@ namespace osu.Game.Beatmaps difficulty.SliderMultiplier = SliderMultiplier; difficulty.SliderTickRate = SliderTickRate; - - difficulty.EndTimeObjectCount = EndTimeObjectCount; - difficulty.TotalObjectCount = TotalObjectCount; } public virtual void CopyFrom(IBeatmapDifficultyInfo other) @@ -61,9 +55,6 @@ namespace osu.Game.Beatmaps SliderMultiplier = other.SliderMultiplier; SliderTickRate = other.SliderTickRate; - - EndTimeObjectCount = other.EndTimeObjectCount; - TotalObjectCount = other.TotalObjectCount; } } } diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 31d6b0108e..7bb52eef52 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -388,9 +388,7 @@ namespace osu.Game.Beatmaps OverallDifficulty = decodedDifficulty.OverallDifficulty, ApproachRate = decodedDifficulty.ApproachRate, SliderMultiplier = decodedDifficulty.SliderMultiplier, - SliderTickRate = decodedDifficulty.SliderTickRate, - EndTimeObjectCount = decoded.HitObjects.Count(h => h is IHasDuration), - TotalObjectCount = decoded.HitObjects.Count + SliderTickRate = decodedDifficulty.SliderTickRate }; var metadata = new BeatmapMetadata @@ -428,6 +426,8 @@ namespace osu.Game.Beatmaps GridSize = decodedInfo.GridSize, TimelineZoom = decodedInfo.TimelineZoom, MD5Hash = memoryStream.ComputeMD5Hash(), + EndTimeObjectCount = decoded.HitObjects.Count(h => h is IHasDuration), + TotalObjectCount = decoded.HitObjects.Count }; beatmaps.Add(beatmap); diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index c1aeec1f71..2d04732f91 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -120,6 +120,10 @@ namespace osu.Game.Beatmaps [JsonIgnore] public bool Hidden { get; set; } + public int EndTimeObjectCount { get; set; } + + public int TotalObjectCount { get; set; } + /// /// Reset any fetched online linking information (and history). /// diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index 472ac26ebe..27492b8bac 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -91,8 +91,8 @@ namespace osu.Game.Beatmaps var working = workingBeatmapCache.GetWorkingBeatmap(beatmapInfo); var beatmap = working.Beatmap; - beatmapInfo.Difficulty.EndTimeObjectCount = beatmap.HitObjects.Count(h => h is IHasDuration); - beatmapInfo.Difficulty.TotalObjectCount = beatmap.HitObjects.Count; + beatmapInfo.EndTimeObjectCount = beatmap.HitObjects.Count(h => h is IHasDuration); + beatmapInfo.TotalObjectCount = beatmap.HitObjects.Count; // And invalidate again afterwards as re-fetching the most up-to-date database metadata will be required. workingBeatmapCache.Invalidate(beatmapInfo); diff --git a/osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs b/osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs index 47b261d1f6..e7a3d87d0a 100644 --- a/osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs +++ b/osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs @@ -44,19 +44,6 @@ namespace osu.Game.Beatmaps /// double SliderTickRate { get; } - /// - /// The number of hitobjects in the beatmap with a distinct end time. - /// - /// - /// Canonically, these are hitobjects are either sliders or spinners. - /// - int EndTimeObjectCount { get; } - - /// - /// The total number of hitobjects in the beatmap. - /// - int TotalObjectCount { get; } - /// /// Maps a difficulty value [0, 10] to a two-piece linear range of values. /// diff --git a/osu.Game/Beatmaps/IBeatmapInfo.cs b/osu.Game/Beatmaps/IBeatmapInfo.cs index b8c69cc525..9dcff5ce5e 100644 --- a/osu.Game/Beatmaps/IBeatmapInfo.cs +++ b/osu.Game/Beatmaps/IBeatmapInfo.cs @@ -61,5 +61,18 @@ namespace osu.Game.Beatmaps /// The basic star rating for this beatmap (with no mods applied). /// double StarRating { get; } + + /// + /// The number of hitobjects in the beatmap with a distinct end time. + /// + /// + /// Canonically, these are hitobjects are either sliders or spinners. + /// + int EndTimeObjectCount { get; } + + /// + /// The total number of hitobjects in the beatmap. + /// + int TotalObjectCount { get; } } } diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs index c1ceff7c43..e5ecfe2c99 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs @@ -41,6 +41,10 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"difficulty_rating")] public double StarRating { get; set; } + public int EndTimeObjectCount => SliderCount + SpinnerCount; + + public int TotalObjectCount => CircleCount + SliderCount + SpinnerCount; + [JsonProperty(@"drain")] public float DrainRate { get; set; } @@ -108,9 +112,7 @@ namespace osu.Game.Online.API.Requests.Responses DrainRate = DrainRate, CircleSize = CircleSize, ApproachRate = ApproachRate, - OverallDifficulty = OverallDifficulty, - EndTimeObjectCount = SliderCount + SpinnerCount, - TotalObjectCount = CircleCount + SliderCount + SpinnerCount + OverallDifficulty = OverallDifficulty }; IBeatmapSetInfo? IBeatmapInfo.BeatmapSet => BeatmapSet; diff --git a/osu.Game/Rulesets/Scoring/Legacy/LegacyBeatmapConversionDifficultyInfo.cs b/osu.Game/Rulesets/Scoring/Legacy/LegacyBeatmapConversionDifficultyInfo.cs index 2021aa127d..7d69069455 100644 --- a/osu.Game/Rulesets/Scoring/Legacy/LegacyBeatmapConversionDifficultyInfo.cs +++ b/osu.Game/Rulesets/Scoring/Legacy/LegacyBeatmapConversionDifficultyInfo.cs @@ -62,8 +62,8 @@ namespace osu.Game.Rulesets.Scoring.Legacy SourceRuleset = beatmapInfo.Ruleset, CircleSize = beatmapInfo.Difficulty.CircleSize, OverallDifficulty = beatmapInfo.Difficulty.OverallDifficulty, - EndTimeObjectCount = beatmapInfo.Difficulty.EndTimeObjectCount, - TotalObjectCount = beatmapInfo.Difficulty.TotalObjectCount + EndTimeObjectCount = beatmapInfo.EndTimeObjectCount, + TotalObjectCount = beatmapInfo.TotalObjectCount }; } } From 5062c53e36c1d4d68cd62bfedd14e1306553c8e7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Dec 2023 17:33:39 +0900 Subject: [PATCH 1925/4092] Refactor everything for sanity --- .../Mods/AdjustedAttributesTooltip.cs | 105 +++++++----------- .../Overlays/Mods/BeatmapAttributesDisplay.cs | 7 +- .../Screens/Select/Details/AdvancedStats.cs | 6 +- 3 files changed, 41 insertions(+), 77 deletions(-) diff --git a/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs b/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs index a56c09931a..ed12d917ad 100644 --- a/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs +++ b/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs @@ -1,29 +1,30 @@ // 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; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics; using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using osuTK; namespace osu.Game.Overlays.Mods { public partial class AdjustedAttributesTooltip : VisibilityContainer, ITooltip { - private readonly Dictionary> attributes = new Dictionary>(); - - private FillFlowContainer? attributesFillFlow; + private FillFlowContainer attributesFillFlow = null!; private Container content = null!; + private BeatmapDifficulty? originalDifficulty; + private BeatmapDifficulty? adjustedDifficulty; + [Resolved] private OsuColour colours { get; set; } = null!; @@ -69,36 +70,43 @@ namespace osu.Game.Overlays.Mods }, }; - foreach (var attribute in attributes) - attributesFillFlow?.Add(new AttributeDisplay(attribute.Key, attribute.Value.GetBoundCopy())); - - updateVisibility(); + updateDisplay(); } - public void AddAttribute(string name) + public void UpdateAttributes(BeatmapDifficulty original, BeatmapDifficulty adjusted) { - Bindable newBindable = new Bindable(); - newBindable.BindValueChanged(_ => updateVisibility()); - attributes.Add(name, newBindable); + originalDifficulty = original; + adjustedDifficulty = adjusted; - attributesFillFlow?.Add(new AttributeDisplay(name, newBindable.GetBoundCopy())); + if (IsLoaded) + updateDisplay(); } - public void UpdateAttribute(string name, double oldValue, double newValue) + private void updateDisplay() { - if (!attributes.ContainsKey(name)) return; + attributesFillFlow.Clear(); - Bindable attribute = attributes[name]; + if (originalDifficulty == null || adjustedDifficulty == null) + return; - OldNewPair attributeValue = attribute.Value; - attributeValue.OldValue = oldValue; - attributeValue.NewValue = newValue; + attemptAdd("AR", bd => bd.ApproachRate); + attemptAdd("OD", bd => bd.OverallDifficulty); + attemptAdd("CS", bd => bd.CircleSize); + attemptAdd("HP", bd => bd.DrainRate); - attribute.Value = attributeValue; - } + if (attributesFillFlow.Any()) + content.Show(); + else + content.Hide(); - protected override void Update() - { + void attemptAdd(string name, Func lookup) + { + double a = lookup(originalDifficulty); + double b = lookup(adjustedDifficulty); + + if (!Precision.AlmostEquals(a, b)) + attributesFillFlow.Add(new AttributeDisplay(name, a, b)); + } } public void SetContent(object content) @@ -110,55 +118,18 @@ namespace osu.Game.Overlays.Mods public void Move(Vector2 pos) => Position = pos; - private void updateVisibility() - { - if (!IsLoaded) - return; - - if (attributes.Any(attribute => !Precision.AlmostEquals(attribute.Value.Value.OldValue, attribute.Value.Value.NewValue))) - content.Show(); - else - content.Hide(); - } - private partial class AttributeDisplay : CompositeDrawable { - public readonly Bindable AttributeValues; - public readonly string AttributeName; - - private readonly OsuSpriteText text; - - public AttributeDisplay(string name, Bindable values) + public AttributeDisplay(string name, double original, double adjusted) { AutoSizeAxes = Axes.Both; - AttributeName = name; - AttributeValues = values; - - InternalChild = text = new OsuSpriteText + InternalChild = new OsuSpriteText { - Font = OsuFont.Default.With(weight: FontWeight.Bold) + Font = OsuFont.Default.With(weight: FontWeight.Bold), + Text = $"{name}: {original:0.0#} → {adjusted:0.0#}" }; - - AttributeValues.BindValueChanged(_ => update(), true); } - - private void update() - { - if (Precision.AlmostEquals(AttributeValues.Value.OldValue, AttributeValues.Value.NewValue)) - { - Hide(); - return; - } - - Show(); - text.Text = $"{AttributeName}: {(AttributeValues.Value.OldValue):0.0#} → {(AttributeValues.Value.NewValue):0.0#}"; - } - } - - private struct OldNewPair - { - public double OldValue, NewValue; } } } diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index 0c35e55df5..dd3daa2b7a 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -60,6 +60,7 @@ namespace osu.Game.Overlays.Mods private AdjustedAttributesTooltip rateAdjustTooltip = null!; public ITooltip GetCustomTooltip() => rateAdjustTooltip; + public object TooltipContent => this; private const float transition_duration = 250; @@ -103,9 +104,6 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); - rateAdjustTooltip.AddAttribute("AR"); - rateAdjustTooltip.AddAttribute("OD"); - mods.BindValueChanged(_ => { modSettingChangeTracker?.Dispose(); @@ -184,8 +182,7 @@ namespace osu.Game.Overlays.Mods Ruleset ruleset = gameRuleset.Value.CreateInstance(); BeatmapDifficulty adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(originalDifficulty, rate); - rateAdjustTooltip.UpdateAttribute("AR", originalDifficulty.ApproachRate, adjustedDifficulty.ApproachRate); - rateAdjustTooltip.UpdateAttribute("OD", originalDifficulty.OverallDifficulty, adjustedDifficulty.OverallDifficulty); + rateAdjustTooltip.UpdateAttributes(originalDifficulty, adjustedDifficulty); approachRateDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(originalDifficulty.ApproachRate, adjustedDifficulty.ApproachRate); overallDifficultyDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(originalDifficulty.OverallDifficulty, adjustedDifficulty.OverallDifficulty); diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 9433588b03..2a9e69f141 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -101,9 +101,6 @@ namespace osu.Game.Screens.Select.Details gameRuleset.BindValueChanged(_ => updateStatistics()); mods.BindValueChanged(modsChanged, true); - - rateAdjustTooltip.AddAttribute("AR"); - rateAdjustTooltip.AddAttribute("OD"); } private ModSettingChangeTracker modSettingChangeTracker; @@ -148,8 +145,7 @@ namespace osu.Game.Screens.Select.Details adjustedDifficulty = ruleset.CreateInstance().GetRateAdjustedDisplayDifficulty(originalDifficulty, rate); - rateAdjustTooltip.UpdateAttribute("AR", originalDifficulty.ApproachRate, adjustedDifficulty.ApproachRate); - rateAdjustTooltip.UpdateAttribute("OD", originalDifficulty.OverallDifficulty, adjustedDifficulty.OverallDifficulty); + rateAdjustTooltip.UpdateAttributes(originalDifficulty, adjustedDifficulty); } } From 812f52e793b4729548e1b452a063591fb28c546d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Dec 2023 17:38:16 +0900 Subject: [PATCH 1926/4092] Bump version number --- osu.Game/Database/RealmAccess.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 9c7fe464dd..191bb49b0c 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -88,9 +88,9 @@ namespace osu.Game.Database /// 34 2023-08-21 Add BackgroundReprocessingFailed flag to ScoreInfo to track upgrade failures. /// 35 2023-10-16 Clear key combinations of keybindings that are assigned to more than one action in a given settings section. /// 36 2023-10-26 Add LegacyOnlineID to ScoreInfo. Move osu_scores_*_high IDs stored in OnlineID to LegacyOnlineID. Reset anomalous OnlineIDs. - /// 37 2023-12-10 Add EndTimeObjectCount and TotalObjectCount to BeatmapDifficulty. + /// 38 2023-12-10 Add EndTimeObjectCount and TotalObjectCount to BeatmapInfo. /// - private const int schema_version = 37; + private const int schema_version = 38; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. From 7a4ea90bda6374b89a978e735c88731429e14fcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 13 Dec 2023 11:01:06 +0100 Subject: [PATCH 1927/4092] Fix test failures due to dependency becoming required --- .../Overlays/Settings/Sections/General/UpdateSettings.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index a8f5b655d0..3ff5556f4d 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -35,7 +35,7 @@ namespace osu.Game.Overlays.Settings.Sections.General private Storage storage { get; set; } = null!; [BackgroundDependencyLoader] - private void load(OsuConfigManager config, OsuGame game) + private void load(OsuConfigManager config, OsuGame? game) { Add(new SettingsEnumDropdown { @@ -57,7 +57,7 @@ namespace osu.Game.Overlays.Settings.Sections.General { notifications?.Post(new SimpleNotification { - Text = GeneralSettingsStrings.RunningLatestRelease(game.Version), + Text = GeneralSettingsStrings.RunningLatestRelease(game!.Version), Icon = FontAwesome.Solid.CheckCircle, }); } @@ -87,7 +87,7 @@ namespace osu.Game.Overlays.Settings.Sections.General Add(new SettingsButton { Text = GeneralSettingsStrings.ChangeFolderLocation, - Action = () => game.PerformFromScreen(menu => menu.Push(new MigrationSelectScreen())) + Action = () => game?.PerformFromScreen(menu => menu.Push(new MigrationSelectScreen())) }); } } From 683ac6f63a169351bf2bf16c9ddd24ec1493f551 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Dec 2023 19:25:41 +0900 Subject: [PATCH 1928/4092] Add some more tests --- .../TestSceneSliderEarlyHitJudgement.cs | 46 +++++++++++++++++-- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderEarlyHitJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderEarlyHitJudgement.cs index 46278dfb12..fb7d00b28c 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderEarlyHitJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderEarlyHitJudgement.cs @@ -25,11 +25,12 @@ namespace osu.Game.Rulesets.Osu.Tests public partial class TestSceneSliderEarlyHitJudgement : RateAdjustedBeatmapTestScene { private const double time_slider_start = 1000; - private const double time_slider_tick = 2000; private const double time_slider_end = 3000; private static readonly Vector2 slider_start_position = new Vector2(256 - slider_path_length / 2, 192); private static readonly Vector2 slider_end_position = new Vector2(256 + slider_path_length / 2, 192); + private static readonly Vector2 offset_inside_follow = new Vector2(35, 0); + private static readonly Vector2 offset_outside_follow = offset_inside_follow * 2; private ScoreAccessibleReplayPlayer currentPlayer = null!; @@ -43,8 +44,8 @@ namespace osu.Game.Rulesets.Osu.Tests performTest(new List { new OsuReplayFrame(time_slider_start - 300, slider_start_position, OsuAction.LeftButton), - new OsuReplayFrame(time_slider_start - 200, slider_start_position + new Vector2(32, 0), OsuAction.LeftButton), - new OsuReplayFrame(time_slider_end - 200, slider_end_position + new Vector2(32, 0), OsuAction.LeftButton), + new OsuReplayFrame(time_slider_start - 200, slider_start_position + offset_inside_follow, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_end - 200, slider_end_position + offset_inside_follow, OsuAction.LeftButton), }); assertHeadJudgement(HitResult.Miss); @@ -53,14 +54,49 @@ namespace osu.Game.Rulesets.Osu.Tests assertSliderJudgement(HitResult.IgnoreHit); } + [Test] + public void TestHitEarlyAndReleaseInFollowRegion() + { + performTest(new List + { + new OsuReplayFrame(time_slider_start - 300, slider_start_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_start - 200, slider_start_position + offset_inside_follow, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_start - 100, slider_start_position + offset_inside_follow), + new OsuReplayFrame(time_slider_end - 100, slider_end_position + offset_inside_follow, OsuAction.LeftButton), + }); + + assertHeadJudgement(HitResult.Miss); + assertTickJudgement(HitResult.LargeTickMiss); + assertTailJudgement(HitResult.IgnoreMiss); + assertSliderJudgement(HitResult.IgnoreMiss); + } + + [Test] + public void TestHitEarlyAndRepressInFollowRegion() + { + performTest(new List + { + new OsuReplayFrame(time_slider_start - 300, slider_start_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_start - 200, slider_start_position + offset_inside_follow, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_start - 100, slider_start_position + offset_inside_follow), + new OsuReplayFrame(time_slider_start - 50, slider_start_position + offset_inside_follow, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_end - 50, slider_end_position + offset_inside_follow, OsuAction.LeftButton), + }); + + assertHeadJudgement(HitResult.Miss); + assertTickJudgement(HitResult.LargeTickMiss); + assertTailJudgement(HitResult.IgnoreMiss); + assertSliderJudgement(HitResult.IgnoreMiss); + } + [Test] public void TestHitEarlyMoveOutsideFollowRegion() { performTest(new List { new OsuReplayFrame(time_slider_start - 300, slider_start_position, OsuAction.LeftButton), - new OsuReplayFrame(time_slider_start - 200, slider_start_position + new Vector2(96, 0), OsuAction.LeftButton), - new OsuReplayFrame(time_slider_end - 200, slider_end_position + new Vector2(96, 0), OsuAction.LeftButton), + new OsuReplayFrame(time_slider_start - 200, slider_start_position + offset_outside_follow, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_end - 200, slider_end_position + offset_outside_follow, OsuAction.LeftButton), }); assertHeadJudgement(HitResult.Miss); From 27e55def641cbf0eb233b4b094c2843b5648344b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Dec 2023 20:27:14 +0900 Subject: [PATCH 1929/4092] Make animation start at the slider's start time --- osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs index 355d3f9a2f..4fadb09948 100644 --- a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.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 System; using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -26,13 +27,17 @@ namespace osu.Game.Rulesets.Osu.Skinning ((DrawableSlider?)ParentObject)?.Tracking.BindValueChanged(tracking => { Debug.Assert(ParentObject != null); + if (ParentObject.Judged) return; - if (tracking.NewValue) - OnSliderPress(); - else - OnSliderRelease(); + using (BeginAbsoluteSequence(Math.Max(Time.Current, ParentObject.HitObject?.StartTime ?? 0))) + { + if (tracking.NewValue) + OnSliderPress(); + else + OnSliderRelease(); + } }, true); } From a6bf4cdb986b0d598cb74a863d2bab4de5998354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 13 Dec 2023 13:22:12 +0100 Subject: [PATCH 1930/4092] Remove dead clamping code `EffectiveX` is already defined as clamped to `[0, CatchPlayfield.WIDTH]`. --- .../Beatmaps/CatchBeatmapProcessor.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index d5d4d3b694..02d4cdbb94 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -44,7 +44,6 @@ namespace osu.Game.Rulesets.Catch.Beatmaps base.PostProcess(); ApplyPositionOffsets(Beatmap); - ApplyPositionClamping(Beatmap); int index = 0; @@ -115,17 +114,6 @@ namespace osu.Game.Rulesets.Catch.Beatmaps initialiseHyperDash(beatmap); } - public void ApplyPositionClamping(IBeatmap beatmap) - { - foreach (var obj in beatmap.HitObjects.OfType()) - { - if (obj.EffectiveX < 0) - obj.XOffset += Math.Abs(obj.EffectiveX); - else if (obj.EffectiveX > CatchPlayfield.WIDTH) - obj.XOffset += CatchPlayfield.WIDTH - obj.EffectiveX; - } - } - private static void applyHardRockOffset(CatchHitObject hitObject, ref float? lastPosition, ref double lastStartTime, LegacyRandom rng) { float offsetPosition = hitObject.OriginalX; From 31c9489cb92e6d365813f7faef4139cb9ae2ddcc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 13 Dec 2023 23:37:34 +0300 Subject: [PATCH 1931/4092] Fix failing tests --- .../Visual/SongSelect/TestSceneFilterControl.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 00a0d4a849..94c6130f15 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -192,7 +192,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select collection", () => { - InputManager.MoveMouseTo(getCollectionDropdownItems().ElementAt(1)); + InputManager.MoveMouseTo(getCollectionDropdownItemAt(1)); InputManager.Click(MouseButton.Left); }); @@ -206,7 +206,8 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("click manage collections filter", () => { - InputManager.MoveMouseTo(getCollectionDropdownItems().Last()); + int lastItemIndex = control.ChildrenOfType().Single().Items.Count() - 1; + InputManager.MoveMouseTo(getCollectionDropdownItemAt(lastItemIndex)); InputManager.Click(MouseButton.Left); }); @@ -232,10 +233,10 @@ namespace osu.Game.Tests.Visual.SongSelect private void assertCollectionDropdownContains(string collectionName, bool shouldContain = true) => AddUntilStep($"collection dropdown {(shouldContain ? "contains" : "does not contain")} '{collectionName}'", // A bit of a roundabout way of going about this, see: https://github.com/ppy/osu-framework/issues/3871 + https://github.com/ppy/osu-framework/issues/3872 - () => shouldContain == (getCollectionDropdownItems().Any(i => i.ChildrenOfType().OfType().First().Text == collectionName))); + () => shouldContain == control.ChildrenOfType().Any(i => i.ChildrenOfType().OfType().First().Text == collectionName)); private IconButton getAddOrRemoveButton(int index) - => getCollectionDropdownItems().ElementAt(index).ChildrenOfType().Single(); + => getCollectionDropdownItemAt(index).ChildrenOfType().Single(); private void addExpandHeaderStep() => AddStep("expand header", () => { @@ -249,7 +250,11 @@ namespace osu.Game.Tests.Visual.SongSelect InputManager.Click(MouseButton.Left); }); - private IEnumerable.DropdownMenu.DrawableDropdownMenuItem> getCollectionDropdownItems() - => control.ChildrenOfType().Single().ChildrenOfType.DropdownMenu.DrawableDropdownMenuItem>(); + private Menu.DrawableMenuItem getCollectionDropdownItemAt(int index) + { + // todo: we should be able to use Items, but apparently that's not guaranteed to be ordered... see: https://github.com/ppy/osu-framework/pull/6079 + CollectionFilterMenuItem item = control.ChildrenOfType().Single().ItemSource.ElementAt(index); + return control.ChildrenOfType().Single(i => i.Item.Text.Value == item.CollectionName); + } } } From 38e7c035008ba1e61ee364d53e6eb3bd0b1bc588 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 14 Dec 2023 12:00:01 +0900 Subject: [PATCH 1932/4092] Update tests to not miss the head --- .../TestSceneSliderEarlyHitJudgement.cs | 43 ++++++++++--------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderEarlyHitJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderEarlyHitJudgement.cs index fb7d00b28c..9caee86a32 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderEarlyHitJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderEarlyHitJudgement.cs @@ -43,12 +43,12 @@ namespace osu.Game.Rulesets.Osu.Tests { performTest(new List { - new OsuReplayFrame(time_slider_start - 300, slider_start_position, OsuAction.LeftButton), - new OsuReplayFrame(time_slider_start - 200, slider_start_position + offset_inside_follow, OsuAction.LeftButton), - new OsuReplayFrame(time_slider_end - 200, slider_end_position + offset_inside_follow, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_start - 150, slider_start_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_start - 100, slider_start_position + offset_inside_follow, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_end - 100, slider_end_position + offset_inside_follow, OsuAction.LeftButton), }); - assertHeadJudgement(HitResult.Miss); + assertHeadJudgement(HitResult.Meh); assertTickJudgement(HitResult.LargeTickHit); assertTailJudgement(HitResult.LargeTickHit); assertSliderJudgement(HitResult.IgnoreHit); @@ -59,16 +59,16 @@ namespace osu.Game.Rulesets.Osu.Tests { performTest(new List { - new OsuReplayFrame(time_slider_start - 300, slider_start_position, OsuAction.LeftButton), - new OsuReplayFrame(time_slider_start - 200, slider_start_position + offset_inside_follow, OsuAction.LeftButton), - new OsuReplayFrame(time_slider_start - 100, slider_start_position + offset_inside_follow), - new OsuReplayFrame(time_slider_end - 100, slider_end_position + offset_inside_follow, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_start - 150, slider_start_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_start - 100, slider_start_position + offset_inside_follow, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_start - 50, slider_start_position + offset_inside_follow), + new OsuReplayFrame(time_slider_end - 50, slider_end_position + offset_inside_follow, OsuAction.LeftButton), }); - assertHeadJudgement(HitResult.Miss); + assertHeadJudgement(HitResult.Meh); assertTickJudgement(HitResult.LargeTickMiss); assertTailJudgement(HitResult.IgnoreMiss); - assertSliderJudgement(HitResult.IgnoreMiss); + assertSliderJudgement(HitResult.IgnoreHit); } [Test] @@ -76,17 +76,17 @@ namespace osu.Game.Rulesets.Osu.Tests { performTest(new List { - new OsuReplayFrame(time_slider_start - 300, slider_start_position, OsuAction.LeftButton), - new OsuReplayFrame(time_slider_start - 200, slider_start_position + offset_inside_follow, OsuAction.LeftButton), - new OsuReplayFrame(time_slider_start - 100, slider_start_position + offset_inside_follow), + new OsuReplayFrame(time_slider_start - 150, slider_start_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_start - 100, slider_start_position + offset_inside_follow, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_start - 75, slider_start_position + offset_inside_follow), new OsuReplayFrame(time_slider_start - 50, slider_start_position + offset_inside_follow, OsuAction.LeftButton), new OsuReplayFrame(time_slider_end - 50, slider_end_position + offset_inside_follow, OsuAction.LeftButton), }); - assertHeadJudgement(HitResult.Miss); + assertHeadJudgement(HitResult.Meh); assertTickJudgement(HitResult.LargeTickMiss); assertTailJudgement(HitResult.IgnoreMiss); - assertSliderJudgement(HitResult.IgnoreMiss); + assertSliderJudgement(HitResult.IgnoreHit); } [Test] @@ -94,15 +94,15 @@ namespace osu.Game.Rulesets.Osu.Tests { performTest(new List { - new OsuReplayFrame(time_slider_start - 300, slider_start_position, OsuAction.LeftButton), - new OsuReplayFrame(time_slider_start - 200, slider_start_position + offset_outside_follow, OsuAction.LeftButton), - new OsuReplayFrame(time_slider_end - 200, slider_end_position + offset_outside_follow, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_start - 150, slider_start_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_start - 100, slider_start_position + offset_outside_follow, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_end - 100, slider_end_position + offset_outside_follow, OsuAction.LeftButton), }); - assertHeadJudgement(HitResult.Miss); + assertHeadJudgement(HitResult.Meh); assertTickJudgement(HitResult.LargeTickMiss); assertTailJudgement(HitResult.IgnoreMiss); - assertSliderJudgement(HitResult.IgnoreMiss); + assertSliderJudgement(HitResult.IgnoreHit); } private void assertHeadJudgement(HitResult result) @@ -179,7 +179,8 @@ namespace osu.Game.Rulesets.Osu.Tests Difficulty = new BeatmapDifficulty { SliderMultiplier = 1, - SliderTickRate = 3 + SliderTickRate = 3, + OverallDifficulty = 0 }, Ruleset = new OsuRuleset().RulesetInfo, } From 70a546b23cb3bb38f785f33dc3e18a1cbe3c129b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 14 Dec 2023 09:23:03 +0100 Subject: [PATCH 1933/4092] Add setting for adjusting whether text search is active by default --- osu.Game/Configuration/OsuConfigManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 6ef55ab919..ea526c6d54 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -49,6 +49,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.RandomSelectAlgorithm, RandomSelectAlgorithm.RandomPermutation); SetDefault(OsuSetting.ModSelectHotkeyStyle, ModSelectHotkeyStyle.Sequential); + SetDefault(OsuSetting.ModSelectTextSearchStartsActive, true); SetDefault(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2f, 1f); @@ -416,5 +417,6 @@ namespace osu.Game.Configuration AutomaticallyDownloadMissingBeatmaps, EditorShowSpeedChanges, TouchDisableGameplayTaps, + ModSelectTextSearchStartsActive, } } From 839a0802476deae907f773e781ab6b317434d332 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 14 Dec 2023 09:30:04 +0100 Subject: [PATCH 1934/4092] Add test coverage for desired mod overlay search box behaviour --- .../TestSceneModSelectOverlay.cs | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index fed5f68449..80be4412b3 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; using osu.Framework.Testing; using osu.Framework.Utils; +using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Overlays.Mods; @@ -38,6 +39,9 @@ namespace osu.Game.Tests.Visual.UserInterface private TestModSelectOverlay modSelectOverlay = null!; + [Resolved] + private OsuConfigManager configManager { get; set; } = null!; + [BackgroundDependencyLoader] private void load() { @@ -566,17 +570,33 @@ namespace osu.Game.Tests.Visual.UserInterface } [Test] - public void TestSearchFocusChangeViaKey() + public void TestTextSearchActiveByDefault() { + configManager.SetValue(OsuSetting.ModSelectTextSearchStartsActive, true); createScreen(); - const Key focus_switch_key = Key.Tab; + AddUntilStep("search text box focused", () => modSelectOverlay.SearchTextBox.HasFocus); - AddStep("press tab", () => InputManager.Key(focus_switch_key)); - AddAssert("focused", () => modSelectOverlay.SearchTextBox.HasFocus); + AddStep("press tab", () => InputManager.Key(Key.Tab)); + AddAssert("search text box unfocused", () => !modSelectOverlay.SearchTextBox.HasFocus); - AddStep("press tab", () => InputManager.Key(focus_switch_key)); - AddAssert("lost focus", () => !modSelectOverlay.SearchTextBox.HasFocus); + AddStep("press tab", () => InputManager.Key(Key.Tab)); + AddAssert("search text box focused", () => modSelectOverlay.SearchTextBox.HasFocus); + } + + [Test] + public void TestTextSearchNotActiveByDefault() + { + configManager.SetValue(OsuSetting.ModSelectTextSearchStartsActive, false); + createScreen(); + + AddUntilStep("search text box not focused", () => !modSelectOverlay.SearchTextBox.HasFocus); + + AddStep("press tab", () => InputManager.Key(Key.Tab)); + AddAssert("search text box focused", () => modSelectOverlay.SearchTextBox.HasFocus); + + AddStep("press tab", () => InputManager.Key(Key.Tab)); + AddAssert("search text box unfocused", () => !modSelectOverlay.SearchTextBox.HasFocus); } [Test] From 0ab6e1879276a75702ef7b0f2ffd435a3521ac28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 14 Dec 2023 09:49:48 +0100 Subject: [PATCH 1935/4092] Automatically focus search text box on open depending on setting --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index f2b3264a84..ce798ae752 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -115,6 +115,7 @@ namespace osu.Game.Overlays.Mods public IEnumerable AllAvailableMods => AvailableMods.Value.SelectMany(pair => pair.Value); private readonly BindableBool customisationVisible = new BindableBool(); + private Bindable textSearchStartsActive = null!; private ModSettingsArea modSettingsArea = null!; private ColumnScrollContainer columnScroll = null!; @@ -154,7 +155,7 @@ namespace osu.Game.Overlays.Mods } [BackgroundDependencyLoader] - private void load(OsuGameBase game, OsuColour colours, AudioManager audio) + private void load(OsuGameBase game, OsuColour colours, AudioManager audio, OsuConfigManager configManager) { Header.Title = ModSelectOverlayStrings.ModSelectTitle; Header.Description = ModSelectOverlayStrings.ModSelectDescription; @@ -282,6 +283,8 @@ namespace osu.Game.Overlays.Mods } globalAvailableMods.BindTo(game.AvailableMods); + + textSearchStartsActive = configManager.GetBindable(OsuSetting.ModSelectTextSearchStartsActive); } public override void Hide() @@ -617,6 +620,9 @@ namespace osu.Game.Overlays.Mods nonFilteredColumnCount += 1; } + + if (textSearchStartsActive.Value) + SearchTextBox.TakeFocus(); } protected override void PopOut() From b3a7c7a7c9bba70c1372a5f39875e8e3402c80fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 14 Dec 2023 10:04:37 +0100 Subject: [PATCH 1936/4092] Add control to adjust mod select search text box behaviour --- osu.Game/Localisation/UserInterfaceStrings.cs | 5 +++++ .../Settings/Sections/UserInterface/SongSelectSettings.cs | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/osu.Game/Localisation/UserInterfaceStrings.cs b/osu.Game/Localisation/UserInterfaceStrings.cs index 612668171c..68c5c3ccbc 100644 --- a/osu.Game/Localisation/UserInterfaceStrings.cs +++ b/osu.Game/Localisation/UserInterfaceStrings.cs @@ -104,6 +104,11 @@ namespace osu.Game.Localisation /// public static LocalisableString ModSelectHotkeyStyle => new TranslatableString(getKey(@"mod_select_hotkey_style"), @"Mod select hotkey style"); + /// + /// "Automatically focus search text box in mod select" + /// + public static LocalisableString ModSelectTextSearchStartsActive => new TranslatableString(getKey(@"mod_select_text_search_starts_active"), @"Automatically focus search text box in mod select"); + /// /// "no limit" /// diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs index addf5ce163..49bd17dfde 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs @@ -42,6 +42,12 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface ClassicDefault = ModSelectHotkeyStyle.Classic }, new SettingsCheckbox + { + LabelText = UserInterfaceStrings.ModSelectTextSearchStartsActive, + Current = config.GetBindable(OsuSetting.ModSelectTextSearchStartsActive), + ClassicDefault = false + }, + new SettingsCheckbox { LabelText = GameplaySettingsStrings.BackgroundBlur, Current = config.GetBindable(OsuSetting.SongSelectBackgroundBlur), From d77972a39b18f17e43f347fe3bf704e1fde8936b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Dec 2023 18:26:13 +0900 Subject: [PATCH 1937/4092] Show search bar by default in language and collection dropdowns --- osu.Game/Collections/CollectionDropdown.cs | 2 ++ osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs | 1 + 2 files changed, 3 insertions(+) diff --git a/osu.Game/Collections/CollectionDropdown.cs b/osu.Game/Collections/CollectionDropdown.cs index e435992381..db7b27d30c 100644 --- a/osu.Game/Collections/CollectionDropdown.cs +++ b/osu.Game/Collections/CollectionDropdown.cs @@ -48,6 +48,8 @@ namespace osu.Game.Collections ItemSource = filters; Current.Value = new AllBeatmapsCollectionFilterMenuItem(); + + AlwaysShowSearchBar = true; } protected override void LoadComplete() diff --git a/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs index cf7f63211e..2af6e36b7f 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs @@ -23,6 +23,7 @@ namespace osu.Game.Overlays.Settings.Sections.General { LabelText = GeneralSettingsStrings.LanguageDropdown, Current = game.CurrentLanguage, + AlwaysShowSearchBar = true, }, new SettingsCheckbox { From 8698835db2e1d0163b3cb1af22532d8a7d4b1554 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 14 Dec 2023 14:00:35 +0200 Subject: [PATCH 1938/4092] fixed bug fixed the bug where it's not updates tooltip when there are no mods --- osu.Game/Screens/Select/Details/AdvancedStats.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 2a9e69f141..6309b2b993 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -123,6 +123,8 @@ namespace osu.Game.Screens.Select.Details private void updateStatistics() { IBeatmapDifficultyInfo baseDifficulty = BeatmapInfo?.Difficulty; + + BeatmapDifficulty originalDifficulty = null; BeatmapDifficulty adjustedDifficulty = null; IRulesetInfo ruleset = gameRuleset?.Value ?? beatmapInfo.Ruleset; @@ -130,7 +132,7 @@ namespace osu.Game.Screens.Select.Details if (baseDifficulty != null && (mods.Value.Any(m => m is IApplicableToDifficulty) || mods.Value.Any(m => m is IApplicableToRate))) { - BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(baseDifficulty); + originalDifficulty = new BeatmapDifficulty(baseDifficulty); foreach (var mod in mods.Value.OfType()) mod.ApplyToDifficulty(originalDifficulty); @@ -149,6 +151,13 @@ namespace osu.Game.Screens.Select.Details } } + // update tooltip anyway + else if (baseDifficulty != null) + { + originalDifficulty = new BeatmapDifficulty(baseDifficulty); + rateAdjustTooltip.UpdateAttributes(originalDifficulty, originalDifficulty); + } + switch (ruleset.OnlineID) { case 3: From c2373bb37b6574bdae17a39a064098bf83737fe7 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 14 Dec 2023 14:31:19 +0200 Subject: [PATCH 1939/4092] change the order of attributes + simplifying the bug fix --- osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs | 2 +- osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs | 2 +- osu.Game/Screens/Select/Details/AdvancedStats.cs | 13 ++----------- 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs b/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs index ed12d917ad..7b991dbbfb 100644 --- a/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs +++ b/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs @@ -89,8 +89,8 @@ namespace osu.Game.Overlays.Mods if (originalDifficulty == null || adjustedDifficulty == null) return; - attemptAdd("AR", bd => bd.ApproachRate); attemptAdd("OD", bd => bd.OverallDifficulty); + attemptAdd("AR", bd => bd.ApproachRate); attemptAdd("CS", bd => bd.CircleSize); attemptAdd("HP", bd => bd.DrainRate); diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index dd3daa2b7a..8b50bfc791 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -95,8 +95,8 @@ namespace osu.Game.Overlays.Mods { circleSizeDisplay = new VerticalAttributeDisplay("CS") { Shear = new Vector2(-shear, 0), }, drainRateDisplay = new VerticalAttributeDisplay("HP") { Shear = new Vector2(-shear, 0), }, - approachRateDisplay = new VerticalAttributeDisplay("AR") { Shear = new Vector2(-shear, 0), }, overallDifficultyDisplay = new VerticalAttributeDisplay("OD") { Shear = new Vector2(-shear, 0), }, + approachRateDisplay = new VerticalAttributeDisplay("AR") { Shear = new Vector2(-shear, 0), }, }); } diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 6309b2b993..1c0667b9ee 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -124,15 +124,13 @@ namespace osu.Game.Screens.Select.Details { IBeatmapDifficultyInfo baseDifficulty = BeatmapInfo?.Difficulty; - BeatmapDifficulty originalDifficulty = null; BeatmapDifficulty adjustedDifficulty = null; IRulesetInfo ruleset = gameRuleset?.Value ?? beatmapInfo.Ruleset; - if (baseDifficulty != null && - (mods.Value.Any(m => m is IApplicableToDifficulty) || mods.Value.Any(m => m is IApplicableToRate))) + if (baseDifficulty != null) { - originalDifficulty = new BeatmapDifficulty(baseDifficulty); + BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(baseDifficulty); foreach (var mod in mods.Value.OfType()) mod.ApplyToDifficulty(originalDifficulty); @@ -151,13 +149,6 @@ namespace osu.Game.Screens.Select.Details } } - // update tooltip anyway - else if (baseDifficulty != null) - { - originalDifficulty = new BeatmapDifficulty(baseDifficulty); - rateAdjustTooltip.UpdateAttributes(originalDifficulty, originalDifficulty); - } - switch (ruleset.OnlineID) { case 3: From b22a7cf520b7dc0408b4199783ca898b47bb02f4 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 14 Dec 2023 14:31:58 +0200 Subject: [PATCH 1940/4092] Update AdvancedStats.cs --- osu.Game/Screens/Select/Details/AdvancedStats.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 1c0667b9ee..f56e84abce 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -123,7 +123,6 @@ namespace osu.Game.Screens.Select.Details private void updateStatistics() { IBeatmapDifficultyInfo baseDifficulty = BeatmapInfo?.Difficulty; - BeatmapDifficulty adjustedDifficulty = null; IRulesetInfo ruleset = gameRuleset?.Value ?? beatmapInfo.Ruleset; From 23c427cd3ee70781fbf226101af8d0156c1536fb Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 14 Dec 2023 14:38:01 +0200 Subject: [PATCH 1941/4092] Update AdjustedAttributesTooltip.cs --- osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs b/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs index 7b991dbbfb..10b84340ae 100644 --- a/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs +++ b/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs @@ -89,10 +89,10 @@ namespace osu.Game.Overlays.Mods if (originalDifficulty == null || adjustedDifficulty == null) return; - attemptAdd("OD", bd => bd.OverallDifficulty); - attemptAdd("AR", bd => bd.ApproachRate); attemptAdd("CS", bd => bd.CircleSize); attemptAdd("HP", bd => bd.DrainRate); + attemptAdd("OD", bd => bd.OverallDifficulty); + attemptAdd("AR", bd => bd.ApproachRate); if (attributesFillFlow.Any()) content.Show(); From 67a5e01c49fe9a36ab6a70f63129156aa1ed1d8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 14 Dec 2023 15:19:29 +0100 Subject: [PATCH 1942/4092] Update assertions in test --- osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index 60fb6b8c86..325cb9e0cb 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -220,7 +220,7 @@ namespace osu.Game.Tests.Visual.Online public void TestSelectedModsDontAffectStatistics() { AddStep("show map", () => overlay.ShowBeatmapSet(getBeatmapSet())); - AddAssert("AR displayed as 0", () => overlay.ChildrenOfType().Single(s => s.Title == BeatmapsetsStrings.ShowStatsAr).Value == (0, null)); + AddAssert("AR displayed as 0", () => overlay.ChildrenOfType().Single(s => s.Title == BeatmapsetsStrings.ShowStatsAr).Value, () => Is.EqualTo((0, 0))); AddStep("set AR10 diff adjust", () => SelectedMods.Value = new[] { new OsuModDifficultyAdjust @@ -228,7 +228,7 @@ namespace osu.Game.Tests.Visual.Online ApproachRate = { Value = 10 } } }); - AddAssert("AR still displayed as 0", () => overlay.ChildrenOfType().Single(s => s.Title == BeatmapsetsStrings.ShowStatsAr).Value == (0, null)); + AddAssert("AR still displayed as 0", () => overlay.ChildrenOfType().Single(s => s.Title == BeatmapsetsStrings.ShowStatsAr).Value, () => Is.EqualTo((0, 0))); } [Test] From 9e5b6b97ff52753140331e16aa1b001df493ce89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 14 Dec 2023 16:11:08 +0100 Subject: [PATCH 1943/4092] Fix `AdjustedAttributesTooltip` being broken by design Fixes issue described in the following comment: https://github.com/ppy/osu/pull/25759#issuecomment-1855954637 That is just not how the tooltip system is supposed to be used. To name the individual sins: - Caching and returning a tooltip instance like the classes that used tooltips is incorrect. The lifetime of tooltip instances is managed by the tooltip container. `GetCustomTooltip()` is called by it exclusively. It should return a fresh instance every time. - Not putting actual data in `IHasCustomTooltip.TooltipContent` is wrong. - Having `Tooltip.SetContent()` be a no-op is *grossly and flagrantly* wrong. I'm not even sure which particular combination of the above transgressions caused the issue as it presented itself, but at this time I frankly do not care. --- .../Mods/AdjustedAttributesTooltip.cs | 55 +++++++++++-------- .../Overlays/Mods/BeatmapAttributesDisplay.cs | 12 ++-- .../Screens/Select/Details/AdvancedStats.cs | 10 ++-- 3 files changed, 39 insertions(+), 38 deletions(-) diff --git a/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs b/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs index 10b84340ae..957ee23e3b 100644 --- a/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs +++ b/osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs @@ -16,14 +16,13 @@ using osuTK; namespace osu.Game.Overlays.Mods { - public partial class AdjustedAttributesTooltip : VisibilityContainer, ITooltip + public partial class AdjustedAttributesTooltip : VisibilityContainer, ITooltip { private FillFlowContainer attributesFillFlow = null!; private Container content = null!; - private BeatmapDifficulty? originalDifficulty; - private BeatmapDifficulty? adjustedDifficulty; + private Data? data; [Resolved] private OsuColour colours { get; set; } = null!; @@ -73,26 +72,17 @@ namespace osu.Game.Overlays.Mods updateDisplay(); } - public void UpdateAttributes(BeatmapDifficulty original, BeatmapDifficulty adjusted) - { - originalDifficulty = original; - adjustedDifficulty = adjusted; - - if (IsLoaded) - updateDisplay(); - } - private void updateDisplay() { attributesFillFlow.Clear(); - if (originalDifficulty == null || adjustedDifficulty == null) - return; - - attemptAdd("CS", bd => bd.CircleSize); - attemptAdd("HP", bd => bd.DrainRate); - attemptAdd("OD", bd => bd.OverallDifficulty); - attemptAdd("AR", bd => bd.ApproachRate); + if (data != null) + { + attemptAdd("CS", bd => bd.CircleSize); + attemptAdd("HP", bd => bd.DrainRate); + attemptAdd("OD", bd => bd.OverallDifficulty); + attemptAdd("AR", bd => bd.ApproachRate); + } if (attributesFillFlow.Any()) content.Show(); @@ -101,16 +91,21 @@ namespace osu.Game.Overlays.Mods void attemptAdd(string name, Func lookup) { - double a = lookup(originalDifficulty); - double b = lookup(adjustedDifficulty); + double originalValue = lookup(data.OriginalDifficulty); + double adjustedValue = lookup(data.AdjustedDifficulty); - if (!Precision.AlmostEquals(a, b)) - attributesFillFlow.Add(new AttributeDisplay(name, a, b)); + if (!Precision.AlmostEquals(originalValue, adjustedValue)) + attributesFillFlow.Add(new AttributeDisplay(name, originalValue, adjustedValue)); } } - public void SetContent(object content) + public void SetContent(Data? data) { + if (this.data == data) + return; + + this.data = data; + updateDisplay(); } protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); @@ -118,6 +113,18 @@ namespace osu.Game.Overlays.Mods public void Move(Vector2 pos) => Position = pos; + public class Data + { + public BeatmapDifficulty OriginalDifficulty { get; } + public BeatmapDifficulty AdjustedDifficulty { get; } + + public Data(BeatmapDifficulty originalDifficulty, BeatmapDifficulty adjustedDifficulty) + { + OriginalDifficulty = originalDifficulty; + AdjustedDifficulty = adjustedDifficulty; + } + } + private partial class AttributeDisplay : CompositeDrawable { public AttributeDisplay(string name, double original, double adjusted) diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index 8b50bfc791..d50b2c33d7 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Mods /// On the mod select overlay, this provides a local updating view of BPM, star rating and other /// difficulty attributes so the user can have a better insight into what mods are changing. /// - public partial class BeatmapAttributesDisplay : ModFooterInformationDisplay, IHasCustomTooltip + public partial class BeatmapAttributesDisplay : ModFooterInformationDisplay, IHasCustomTooltip { private StarRatingDisplay starRatingDisplay = null!; private BPMDisplay bpmDisplay = null!; @@ -57,11 +57,9 @@ namespace osu.Game.Overlays.Mods private CancellationTokenSource? cancellationSource; private IBindable starDifficulty = null!; - private AdjustedAttributesTooltip rateAdjustTooltip = null!; + public ITooltip GetCustomTooltip() => new AdjustedAttributesTooltip(); - public ITooltip GetCustomTooltip() => rateAdjustTooltip; - - public object TooltipContent => this; + public AdjustedAttributesTooltip.Data? TooltipContent { get; private set; } private const float transition_duration = 250; @@ -70,8 +68,6 @@ namespace osu.Game.Overlays.Mods { const float shear = ShearedOverlayContainer.SHEAR; - rateAdjustTooltip = new AdjustedAttributesTooltip(); - LeftContent.AddRange(new Drawable[] { starRatingDisplay = new StarRatingDisplay(default, animated: true) @@ -182,7 +178,7 @@ namespace osu.Game.Overlays.Mods Ruleset ruleset = gameRuleset.Value.CreateInstance(); BeatmapDifficulty adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(originalDifficulty, rate); - rateAdjustTooltip.UpdateAttributes(originalDifficulty, adjustedDifficulty); + TooltipContent = new AdjustedAttributesTooltip.Data(originalDifficulty, adjustedDifficulty); approachRateDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(originalDifficulty.ApproachRate, adjustedDifficulty.ApproachRate); overallDifficultyDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(originalDifficulty.OverallDifficulty, adjustedDifficulty.OverallDifficulty); diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index f56e84abce..ee805c2d12 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -30,7 +30,7 @@ using osu.Game.Overlays.Mods; namespace osu.Game.Screens.Select.Details { - public partial class AdvancedStats : Container, IHasCustomTooltip + public partial class AdvancedStats : Container, IHasCustomTooltip { [Resolved] private BeatmapDifficultyCache difficultyCache { get; set; } @@ -46,9 +46,8 @@ namespace osu.Game.Screens.Select.Details protected readonly StatisticRow FirstValue, HpDrain, Accuracy, ApproachRate; private readonly StatisticRow starDifficulty; - private AdjustedAttributesTooltip rateAdjustTooltip; - public ITooltip GetCustomTooltip() => rateAdjustTooltip; - public object TooltipContent => this; + public ITooltip GetCustomTooltip() => new AdjustedAttributesTooltip(); + public AdjustedAttributesTooltip.Data TooltipContent { get; private set; } private IBeatmapInfo beatmapInfo; @@ -86,7 +85,6 @@ namespace osu.Game.Screens.Select.Details private void load(OsuColour colours) { starDifficulty.AccentColour = colours.Yellow; - rateAdjustTooltip = new AdjustedAttributesTooltip(); } protected override void LoadComplete() @@ -144,7 +142,7 @@ namespace osu.Game.Screens.Select.Details adjustedDifficulty = ruleset.CreateInstance().GetRateAdjustedDisplayDifficulty(originalDifficulty, rate); - rateAdjustTooltip.UpdateAttributes(originalDifficulty, adjustedDifficulty); + TooltipContent = new AdjustedAttributesTooltip.Data(originalDifficulty, adjustedDifficulty); } } From 555559c5c12010122798086b6419b51442b86ba9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 14 Dec 2023 20:41:07 +0100 Subject: [PATCH 1944/4092] Add testing for `GetRateAdjustedDisplayDifficulty()` implementations --- .../CatchRateAdjustedDisplayDifficultyTest.cs | 52 +++++++++++++++ .../OsuRateAdjustedDisplayDifficultyTest.cs | 65 +++++++++++++++++++ .../TaikoRateAdjustedDisplayDifficultyTest.cs | 52 +++++++++++++++ 3 files changed, 169 insertions(+) create mode 100644 osu.Game.Rulesets.Catch.Tests/CatchRateAdjustedDisplayDifficultyTest.cs create mode 100644 osu.Game.Rulesets.Osu.Tests/OsuRateAdjustedDisplayDifficultyTest.cs create mode 100644 osu.Game.Rulesets.Taiko.Tests/TaikoRateAdjustedDisplayDifficultyTest.cs diff --git a/osu.Game.Rulesets.Catch.Tests/CatchRateAdjustedDisplayDifficultyTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchRateAdjustedDisplayDifficultyTest.cs new file mode 100644 index 0000000000..f77ec64df3 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/CatchRateAdjustedDisplayDifficultyTest.cs @@ -0,0 +1,52 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Beatmaps; + +namespace osu.Game.Rulesets.Catch.Tests +{ + [TestFixture] + public class CatchRateAdjustedDisplayDifficultyTest + { + private static IEnumerable difficultyValuesToTest() + { + for (float i = 0; i <= 10; i += 0.5f) + yield return i; + } + + [TestCaseSource(nameof(difficultyValuesToTest))] + public void TestApproachRateIsUnchangedWithRateEqualToOne(float originalApproachRate) + { + var ruleset = new CatchRuleset(); + var difficulty = new BeatmapDifficulty { ApproachRate = originalApproachRate }; + + var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 1); + + Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(originalApproachRate)); + } + + [Test] + public void TestRateBelowOne() + { + var ruleset = new CatchRuleset(); + var difficulty = new BeatmapDifficulty(); + + var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 0.75); + + Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(1.67).Within(0.01)); + } + + [Test] + public void TestRateAboveOne() + { + var ruleset = new CatchRuleset(); + var difficulty = new BeatmapDifficulty(); + + var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 1.5); + + Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(7.67).Within(0.01)); + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/OsuRateAdjustedDisplayDifficultyTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuRateAdjustedDisplayDifficultyTest.cs new file mode 100644 index 0000000000..aa903205c8 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/OsuRateAdjustedDisplayDifficultyTest.cs @@ -0,0 +1,65 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Beatmaps; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class OsuRateAdjustedDisplayDifficultyTest + { + private static IEnumerable difficultyValuesToTest() + { + for (float i = 0; i <= 10; i += 0.5f) + yield return i; + } + + [TestCaseSource(nameof(difficultyValuesToTest))] + public void TestApproachRateIsUnchangedWithRateEqualToOne(float originalApproachRate) + { + var ruleset = new OsuRuleset(); + var difficulty = new BeatmapDifficulty { ApproachRate = originalApproachRate }; + + var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 1); + + Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(originalApproachRate)); + } + + [TestCaseSource(nameof(difficultyValuesToTest))] + public void TestOverallDifficultyIsUnchangedWithRateEqualToOne(float originalOverallDifficulty) + { + var ruleset = new OsuRuleset(); + var difficulty = new BeatmapDifficulty { OverallDifficulty = originalOverallDifficulty }; + + var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 1); + + Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(originalOverallDifficulty)); + } + + [Test] + public void TestRateBelowOne() + { + var ruleset = new OsuRuleset(); + var difficulty = new BeatmapDifficulty(); + + var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 0.75); + + Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(1.67).Within(0.01)); + Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(2.22).Within(0.01)); + } + + [Test] + public void TestRateAboveOne() + { + var ruleset = new OsuRuleset(); + var difficulty = new BeatmapDifficulty(); + + var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 1.5); + + Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(7.67).Within(0.01)); + Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(7.77).Within(0.01)); + } + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoRateAdjustedDisplayDifficultyTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoRateAdjustedDisplayDifficultyTest.cs new file mode 100644 index 0000000000..4ab3f502ad --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoRateAdjustedDisplayDifficultyTest.cs @@ -0,0 +1,52 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Beatmaps; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + [TestFixture] + public class TaikoRateAdjustedDisplayDifficultyTest + { + private static IEnumerable difficultyValuesToTest() + { + for (float i = 0; i <= 10; i += 0.5f) + yield return i; + } + + [TestCaseSource(nameof(difficultyValuesToTest))] + public void TestOverallDifficultyIsUnchangedWithRateEqualToOne(float originalOverallDifficulty) + { + var ruleset = new TaikoRuleset(); + var difficulty = new BeatmapDifficulty { OverallDifficulty = originalOverallDifficulty }; + + var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 1); + + Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(originalOverallDifficulty)); + } + + [Test] + public void TestRateBelowOne() + { + var ruleset = new TaikoRuleset(); + var difficulty = new BeatmapDifficulty(); + + var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 0.75); + + Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(1.11).Within(0.01)); + } + + [Test] + public void TestRateAboveOne() + { + var ruleset = new TaikoRuleset(); + var difficulty = new BeatmapDifficulty(); + + var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 1.5); + + Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(8.89).Within(0.01)); + } + } +} From 24e31f7d9199ea89c422447c228579d7779d346a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 14 Dec 2023 19:00:00 +0100 Subject: [PATCH 1945/4092] Implement inverse of `IBeatmapDifficultyInfo.DifficultyRange()` --- osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs b/osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs index e7a3d87d0a..07b966ca44 100644 --- a/osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs +++ b/osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs @@ -92,5 +92,21 @@ namespace osu.Game.Beatmaps /// Value to which the difficulty value maps in the specified range. static double DifficultyRange(double difficulty, (double od0, double od5, double od10) range) => DifficultyRange(difficulty, range.od0, range.od5, range.od10); + + /// + /// Inverse function to . + /// Maps a value returned by the function above back to the difficulty that produced it. + /// + /// The difficulty-dependent value to be unmapped. + /// Minimum of the resulting range which will be achieved by a difficulty value of 0. + /// Midpoint of the resulting range which will be achieved by a difficulty value of 5. + /// Maximum of the resulting range which will be achieved by a difficulty value of 10. + /// Value to which the difficulty value maps in the specified range. + static double InverseDifficultyRange(double difficultyValue, double min, double mid, double max) + { + return difficultyValue >= mid + ? (difficultyValue - mid) / (max - mid) * 5 + 5 + : (difficultyValue - mid) / (mid - min) * 5 + 5; + } } } From fd1c72bf747cefdc9152008610550d28b24631d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 14 Dec 2023 19:16:08 +0100 Subject: [PATCH 1946/4092] Use `IBeatmapDifficultyInfo.(Inverse)DifficultyRange()` instead of local reimplementations Also adds explicit references to places from where the magic constants were lifted. --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 10 ++++------ osu.Game.Rulesets.Osu/OsuRuleset.cs | 18 +++++++----------- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 9 ++++----- 3 files changed, 15 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index bcb28328f0..9b9840f71b 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -15,6 +15,7 @@ using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Catch.Difficulty; using osu.Game.Rulesets.Catch.Edit; using osu.Game.Rulesets.Catch.Mods; +using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Catch.Scoring; using osu.Game.Rulesets.Catch.Skinning.Argon; @@ -236,17 +237,14 @@ namespace osu.Game.Rulesets.Catch }; } + /// public override BeatmapDifficulty GetRateAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, double rate) { BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty); - double preempt = adjustedDifficulty.ApproachRate < 6 - ? 1200.0 + 600.0 * (5 - adjustedDifficulty.ApproachRate) / 5 - : 1200.0 - 750.0 * (adjustedDifficulty.ApproachRate - 5) / 5; - + double preempt = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.ApproachRate, 1800, 1200, 450); preempt /= rate; - - adjustedDifficulty.ApproachRate = (float)(preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5); + adjustedDifficulty.ApproachRate = (float)IBeatmapDifficultyInfo.InverseDifficultyRange(preempt, 1800, 1200, 450); return adjustedDifficulty; } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 5355743a50..744f57eae9 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -332,23 +332,19 @@ namespace osu.Game.Rulesets.Osu public override RulesetSetupSection CreateEditorSetupSection() => new OsuSetupSection(); + /// + /// public override BeatmapDifficulty GetRateAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, double rate) { BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty); - double preempt = adjustedDifficulty.ApproachRate < 5 - ? 1200.0 + 600.0 * (5 - adjustedDifficulty.ApproachRate) / 5 - : 1200.0 - 750.0 * (adjustedDifficulty.ApproachRate - 5) / 5; - + double preempt = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.ApproachRate, 1800, 1200, 450); preempt /= rate; + adjustedDifficulty.ApproachRate = (float)IBeatmapDifficultyInfo.InverseDifficultyRange(preempt, 1800, 1200, 450); - adjustedDifficulty.ApproachRate = (float)(preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5); - - double hitwindow = 80.0 - 6 * adjustedDifficulty.OverallDifficulty; - - hitwindow /= rate; - - adjustedDifficulty.OverallDifficulty = (float)(80.0 - hitwindow) / 6; + double greatHitWindow = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.OverallDifficulty, 80, 50, 20); + greatHitWindow /= rate; + adjustedDifficulty.OverallDifficulty = (float)IBeatmapDifficultyInfo.InverseDifficultyRange(greatHitWindow, 80, 50, 20); return adjustedDifficulty; } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 94136a11d3..e97f132e7a 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -265,15 +265,14 @@ namespace osu.Game.Rulesets.Taiko }; } + /// public override BeatmapDifficulty GetRateAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, double rate) { BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty); - double hitWindow = 35.0 - 15.0 * (adjustedDifficulty.OverallDifficulty - 5) / 5; - - hitWindow /= rate; - - adjustedDifficulty.OverallDifficulty = (float)(5 * (35 - hitWindow) / 15 + 5); + double greatHitWindow = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.OverallDifficulty, 50, 35, 20); + greatHitWindow /= rate; + adjustedDifficulty.OverallDifficulty = (float)IBeatmapDifficultyInfo.InverseDifficultyRange(greatHitWindow, 50, 35, 20); return adjustedDifficulty; } From c0e68df20fb7d90bcd398e69a0a2f42dbfa72b02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 14 Dec 2023 19:30:56 +0100 Subject: [PATCH 1947/4092] Fix `InverseDifficultyRange()` not working correctly in both directions --- osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs b/osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs index 07b966ca44..48f6564084 100644 --- a/osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs +++ b/osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; + namespace osu.Game.Beatmaps { /// @@ -98,15 +100,15 @@ namespace osu.Game.Beatmaps /// Maps a value returned by the function above back to the difficulty that produced it. /// /// The difficulty-dependent value to be unmapped. - /// Minimum of the resulting range which will be achieved by a difficulty value of 0. - /// Midpoint of the resulting range which will be achieved by a difficulty value of 5. - /// Maximum of the resulting range which will be achieved by a difficulty value of 10. + /// Minimum of the resulting range which will be achieved by a difficulty value of 0. + /// Midpoint of the resulting range which will be achieved by a difficulty value of 5. + /// Maximum of the resulting range which will be achieved by a difficulty value of 10. /// Value to which the difficulty value maps in the specified range. - static double InverseDifficultyRange(double difficultyValue, double min, double mid, double max) + static double InverseDifficultyRange(double difficultyValue, double diff0, double diff5, double diff10) { - return difficultyValue >= mid - ? (difficultyValue - mid) / (max - mid) * 5 + 5 - : (difficultyValue - mid) / (mid - min) * 5 + 5; + return Math.Sign(difficultyValue - diff5) == Math.Sign(diff10 - diff5) + ? (difficultyValue - diff5) / (diff10 - diff5) * 5 + 5 + : (difficultyValue - diff5) / (diff5 - diff0) * 5 + 5; } } } From 605269f65feea2657e5d6fae48d0ae99b5469b0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 14 Dec 2023 19:54:23 +0100 Subject: [PATCH 1948/4092] Extract preempt durations to shared constants --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 4 ++-- .../Objects/CatchHitObject.cs | 17 ++++++++++++++++- osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 12 +++++++++++- osu.Game.Rulesets.Osu/OsuRuleset.cs | 4 ++-- 4 files changed, 31 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 9b9840f71b..72d1a161dd 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -242,9 +242,9 @@ namespace osu.Game.Rulesets.Catch { BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty); - double preempt = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.ApproachRate, 1800, 1200, 450); + double preempt = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.ApproachRate, CatchHitObject.PREEMPT_MAX, CatchHitObject.PREEMPT_MID, CatchHitObject.PREEMPT_MIN); preempt /= rate; - adjustedDifficulty.ApproachRate = (float)IBeatmapDifficultyInfo.InverseDifficultyRange(preempt, 1800, 1200, 450); + adjustedDifficulty.ApproachRate = (float)IBeatmapDifficultyInfo.InverseDifficultyRange(preempt, CatchHitObject.PREEMPT_MAX, CatchHitObject.PREEMPT_MID, CatchHitObject.PREEMPT_MIN); return adjustedDifficulty; } diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index 17ff8afb87..52c42dfddb 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -150,7 +150,7 @@ namespace osu.Game.Rulesets.Catch.Objects { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); - TimePreempt = (float)IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450); + TimePreempt = (float)IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, PREEMPT_MAX, PREEMPT_MID, PREEMPT_MIN); Scale = LegacyRulesetExtensions.CalculateScaleFromCircleSize(difficulty.CircleSize); } @@ -189,6 +189,21 @@ namespace osu.Game.Rulesets.Catch.Objects // The half of the height of the osu! playfield. public const float DEFAULT_LEGACY_CONVERT_Y = 192; + /// + /// Minimum preempt time at AR=10. + /// + public const double PREEMPT_MIN = 450; + + /// + /// Median preempt time at AR=5. + /// + public const double PREEMPT_MID = 1200; + + /// + /// Maximum preempt time at AR=0. + /// + public const double PREEMPT_MAX = 1800; + /// /// The Y position of the hit object is not used in the normal osu!catch gameplay. /// It is preserved to maximize the backward compatibility with the legacy editor, in which the mappers use the Y position to organize the patterns. diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 21f7b4b22d..74631400ca 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -37,6 +37,16 @@ namespace osu.Game.Rulesets.Osu.Objects /// public const double PREEMPT_MIN = 450; + /// + /// Median preempt time at AR=5. + /// + public const double PREEMPT_MID = 1200; + + /// + /// Maximum preempt time at AR=0. + /// + public const double PREEMPT_MAX = 1800; + public double TimePreempt = 600; public double TimeFadeIn = 400; @@ -148,7 +158,7 @@ namespace osu.Game.Rulesets.Osu.Objects { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); - TimePreempt = (float)IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, 1800, 1200, PREEMPT_MIN); + TimePreempt = (float)IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, PREEMPT_MAX, PREEMPT_MID, PREEMPT_MIN); // Preempt time can go below 450ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR. // This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear function above. diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 744f57eae9..5fe6e76e39 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -338,9 +338,9 @@ namespace osu.Game.Rulesets.Osu { BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty); - double preempt = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.ApproachRate, 1800, 1200, 450); + double preempt = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.ApproachRate, OsuHitObject.PREEMPT_MAX, OsuHitObject.PREEMPT_MID, OsuHitObject.PREEMPT_MIN); preempt /= rate; - adjustedDifficulty.ApproachRate = (float)IBeatmapDifficultyInfo.InverseDifficultyRange(preempt, 1800, 1200, 450); + adjustedDifficulty.ApproachRate = (float)IBeatmapDifficultyInfo.InverseDifficultyRange(preempt, OsuHitObject.PREEMPT_MAX, OsuHitObject.PREEMPT_MID, OsuHitObject.PREEMPT_MIN); double greatHitWindow = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.OverallDifficulty, 80, 50, 20); greatHitWindow /= rate; From 0f4d054bfef007601f13d5695749c37e1898ea53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 14 Dec 2023 20:02:02 +0100 Subject: [PATCH 1949/4092] Use `HitWindows` data directly for computing effective OD --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 5 +++-- osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs | 4 ++-- osu.Game.Rulesets.Taiko/Scoring/TaikoHitWindows.cs | 4 ++-- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 5 +++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 5fe6e76e39..35cbfa3790 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -342,9 +342,10 @@ namespace osu.Game.Rulesets.Osu preempt /= rate; adjustedDifficulty.ApproachRate = (float)IBeatmapDifficultyInfo.InverseDifficultyRange(preempt, OsuHitObject.PREEMPT_MAX, OsuHitObject.PREEMPT_MID, OsuHitObject.PREEMPT_MIN); - double greatHitWindow = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.OverallDifficulty, 80, 50, 20); + var greatHitWindowRange = OsuHitWindows.OSU_RANGES.Single(range => range.Result == HitResult.Great); + double greatHitWindow = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.OverallDifficulty, greatHitWindowRange.Min, greatHitWindowRange.Average, greatHitWindowRange.Max); greatHitWindow /= rate; - adjustedDifficulty.OverallDifficulty = (float)IBeatmapDifficultyInfo.InverseDifficultyRange(greatHitWindow, 80, 50, 20); + adjustedDifficulty.OverallDifficulty = (float)IBeatmapDifficultyInfo.InverseDifficultyRange(greatHitWindow, greatHitWindowRange.Min, greatHitWindowRange.Average, greatHitWindowRange.Max); return adjustedDifficulty; } diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs index 6f55e1790f..fd86e0eeda 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Scoring /// public const double MISS_WINDOW = 400; - private static readonly DifficultyRange[] osu_ranges = + internal static readonly DifficultyRange[] OSU_RANGES = { new DifficultyRange(HitResult.Great, 80, 50, 20), new DifficultyRange(HitResult.Ok, 140, 100, 60), @@ -34,6 +34,6 @@ namespace osu.Game.Rulesets.Osu.Scoring return false; } - protected override DifficultyRange[] GetRanges() => osu_ranges; + protected override DifficultyRange[] GetRanges() => OSU_RANGES; } } diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoHitWindows.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoHitWindows.cs index cf806c0c97..b44ef8ee93 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoHitWindows.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoHitWindows.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Taiko.Scoring { public class TaikoHitWindows : HitWindows { - private static readonly DifficultyRange[] taiko_ranges = + internal static readonly DifficultyRange[] TAIKO_RANGES = { new DifficultyRange(HitResult.Great, 50, 35, 20), new DifficultyRange(HitResult.Ok, 120, 80, 50), @@ -27,6 +27,6 @@ namespace osu.Game.Rulesets.Taiko.Scoring return false; } - protected override DifficultyRange[] GetRanges() => taiko_ranges; + protected override DifficultyRange[] GetRanges() => TAIKO_RANGES; } } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index e97f132e7a..9d34a34fce 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -270,9 +270,10 @@ namespace osu.Game.Rulesets.Taiko { BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty); - double greatHitWindow = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.OverallDifficulty, 50, 35, 20); + var greatHitWindowRange = TaikoHitWindows.TAIKO_RANGES.Single(range => range.Result == HitResult.Great); + double greatHitWindow = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.OverallDifficulty, greatHitWindowRange.Min, greatHitWindowRange.Average, greatHitWindowRange.Max); greatHitWindow /= rate; - adjustedDifficulty.OverallDifficulty = (float)IBeatmapDifficultyInfo.InverseDifficultyRange(greatHitWindow, 50, 35, 20); + adjustedDifficulty.OverallDifficulty = (float)IBeatmapDifficultyInfo.InverseDifficultyRange(greatHitWindow, greatHitWindowRange.Min, greatHitWindowRange.Average, greatHitWindowRange.Max); return adjustedDifficulty; } From fbef40bb1f083dad5146e8d32f8ca879b06f3f29 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 15 Dec 2023 11:01:13 +0900 Subject: [PATCH 1950/4092] Reduce some allocations in SongSelect in the mania ruleset --- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index cda95a9d29..3c64df656d 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -245,6 +245,9 @@ namespace osu.Game.Screens.Select.Carousel private void updateKeyCount() { + if (Item?.State.Value == CarouselItemState.Collapsed) + return; + if (ruleset.Value.OnlineID == 3) { // Account for mania differences locally for now. From 27296c59defef2897879ccfe2cee52c9e203e747 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Dec 2023 11:26:10 +0900 Subject: [PATCH 1951/4092] Show back button when spectating Avoids getting stuck at some screens. It's a bit ugly having the back button visible like this, but is the best approach we have for now. --- osu.Game/Screens/Play/SpectatorPlayer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index d1404ac184..2faead0ee1 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -25,6 +25,8 @@ namespace osu.Game.Screens.Play private readonly Score score; + public override bool AllowBackButton => true; + protected override bool CheckModsAllowFailure() { if (!allowFail) From 599fdb0128d88cbc44b8147cd5058b83528f2287 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 14 Dec 2023 11:51:50 +0900 Subject: [PATCH 1952/4092] Add lenience for late-hit of slider heads --- .../TestSceneSliderLateHitJudgement.cs | 419 ++++++++++++++++++ .../Objects/Drawables/DrawableSlider.cs | 2 + .../Objects/Drawables/DrawableSliderBall.cs | 57 +-- .../Objects/Drawables/DrawableSliderHead.cs | 48 ++ .../Objects/Drawables/DrawableSliderRepeat.cs | 26 +- .../Objects/Drawables/DrawableSliderTail.cs | 26 +- .../Objects/Drawables/DrawableSliderTick.cs | 28 +- 7 files changed, 565 insertions(+), 41 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs new file mode 100644 index 0000000000..4f32a6fe9f --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs @@ -0,0 +1,419 @@ +// 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.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Screens; +using osu.Game.Beatmaps; +using osu.Game.Replays; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens.Play; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public partial class TestSceneSliderLateHitJudgement : RateAdjustedBeatmapTestScene + { + // Note: In the following tests, the terminology "in range of the follow circle" is used as meaning + // the equivalent of "in range of the follow circle as if it were in its expanded state". + + private const double time_slider_start = 1000; + private const double time_slider_end = 1500; + + private static readonly Vector2 slider_start_position = new Vector2(256 - slider_path_length / 2, 192); + private static readonly Vector2 slider_end_position = new Vector2(256 + slider_path_length / 2, 192); + + private ScoreAccessibleReplayPlayer currentPlayer = null!; + + private const float slider_path_length = 200; + + private readonly List judgementResults = new List(); + + /// + /// If the head circle is hit and the mouse is in range of the follow circle, + /// then tracking should be enabled. + /// + [Test] + public void TestHitLateInRangeTracks() + { + performTest(new List + { + new OsuReplayFrame(time_slider_start + 100, slider_start_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_end + 100, slider_end_position, OsuAction.LeftButton), + }); + + assertHeadJudgement(HitResult.Ok); + assertTailJudgement(HitResult.LargeTickHit); + assertSliderJudgement(HitResult.IgnoreHit); + } + + /// + /// If the head circle is hit and the mouse is NOT in range of the follow circle, + /// then tracking should NOT be enabled. + /// + [Test] + public void TestHitLateOutOfRangeDoesNotTrack() + { + performTest(new List + { + new OsuReplayFrame(time_slider_start + 100, slider_start_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_end + 100, slider_end_position, OsuAction.LeftButton), + }, s => + { + s.SliderVelocityMultiplier = 2; + }); + + assertHeadJudgement(HitResult.Ok); + assertTailJudgement(HitResult.IgnoreMiss); + assertSliderJudgement(HitResult.IgnoreHit); + } + + /// + /// If the head circle is hit late and the mouse is in range of the follow circle, + /// then all ticks that the follow circle has passed through should be hit. + /// + [Test] + public void TestHitLateInRangeHitsTicks() + { + performTest(new List + { + new OsuReplayFrame(time_slider_start + 150, slider_start_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_end + 150, slider_end_position, OsuAction.LeftButton), + }, s => + { + s.TickDistanceMultiplier = 0.2f; + }); + + assertHeadJudgement(HitResult.Meh); + assertTickJudgement(0, HitResult.LargeTickHit); + assertTickJudgement(1, HitResult.LargeTickHit); + assertTickJudgement(2, HitResult.LargeTickHit); + assertTickJudgement(3, HitResult.LargeTickHit); + assertTailJudgement(HitResult.LargeTickHit); + assertSliderJudgement(HitResult.IgnoreHit); + } + + /// + /// If the head circle is hit late and the mouse is NOT in range of the follow circle, + /// then all ticks that the follow circle has passed through should NOT be hit. + /// + [Test] + public void TestHitLateOutOfRangeDoesNotHitTicks() + { + performTest(new List + { + new OsuReplayFrame(time_slider_start + 150, slider_start_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_end + 150, slider_end_position, OsuAction.LeftButton), + }, s => + { + s.SliderVelocityMultiplier = 2; + s.TickDistanceMultiplier = 0.2f; + }); + + assertHeadJudgement(HitResult.Meh); + assertTickJudgement(0, HitResult.LargeTickMiss); + assertTickJudgement(1, HitResult.LargeTickMiss); + assertTailJudgement(HitResult.IgnoreMiss); + assertSliderJudgement(HitResult.IgnoreHit); + } + + /// + /// If the head circle is pressed after it's missed and the mouse is in range of the follow circle, + /// then tracking should NOT be enabled. + /// + [Test] + public void TestMissHeadInRangeDoesNotTrack() + { + performTest(new List + { + new OsuReplayFrame(time_slider_start + 151, slider_start_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_end + 151, slider_end_position, OsuAction.LeftButton), + }, s => + { + s.TickDistanceMultiplier = 0.2f; + }); + + assertHeadJudgement(HitResult.Miss); + assertTickJudgement(0, HitResult.LargeTickMiss); + assertTickJudgement(1, HitResult.LargeTickMiss); + assertTickJudgement(2, HitResult.LargeTickMiss); + assertTickJudgement(3, HitResult.LargeTickMiss); + assertTailJudgement(HitResult.IgnoreMiss); + assertSliderJudgement(HitResult.IgnoreMiss); + } + + /// + /// If the head circle is hit late but after the completion of the slider and the mouse is in range of the follow circle, + /// then all nested objects (ticks/repeats/tail) should be hit. + /// + [Test] + public void TestHitLateShortSliderHitsAll() + { + performTest(new List + { + new OsuReplayFrame(time_slider_start + 150, slider_start_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_end + 150, slider_start_position, OsuAction.LeftButton), + }, s => + { + s.Path = new SliderPath(PathType.LINEAR, new[] + { + Vector2.Zero, + new Vector2(20, 0), + }, 20); + + s.TickDistanceMultiplier = 0.01f; + s.RepeatCount = 1; + }); + + assertHeadJudgement(HitResult.Meh); + assertAllTickJudgements(HitResult.LargeTickHit); + assertRepeatJudgement(HitResult.LargeTickHit); + assertTailJudgement(HitResult.LargeTickHit); + assertSliderJudgement(HitResult.IgnoreHit); + } + + /// + /// If the head circle is hit late and the mouse is in range of the follow circle, + /// then all the repeats that the mouse has passed through should be hit. + /// + [Test] + public void TestHitLateInRangeHitsRepeat() + { + performTest(new List + { + new OsuReplayFrame(time_slider_start + 150, slider_start_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_end + 150, slider_start_position, OsuAction.LeftButton), + }, s => + { + s.Path = new SliderPath(PathType.LINEAR, new[] + { + Vector2.Zero, + new Vector2(50, 0), + }, 50); + + s.RepeatCount = 1; + }); + + assertHeadJudgement(HitResult.Meh); + assertRepeatJudgement(HitResult.LargeTickHit); + assertTailJudgement(HitResult.LargeTickHit); + assertSliderJudgement(HitResult.IgnoreHit); + } + + /// + /// If the head circle is hit and the mouse is in range of the follow circle, + /// then only the ticks that were in range of the follow circle at the head should be hit. + /// If any hitobject was outside the follow range, ALL hitobjects after that point should be missed. + /// + [Test] + public void TestHitLateInRangeDoesNotHitAfterAnyOutOfRange() + { + performTest(new List + { + new OsuReplayFrame(time_slider_start + 150, slider_start_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_end + 150, slider_start_position, OsuAction.LeftButton), + }, s => + { + s.Path = new SliderPath(PathType.PERFECT_CURVE, new[] + { + Vector2.Zero, + new Vector2(70, 70), + new Vector2(20, 0), + }); + + s.TickDistanceMultiplier = 0.03f; + s.SliderVelocityMultiplier = 6f; + }); + + assertHeadJudgement(HitResult.Meh); + + // The first few ticks that are in the follow range of the head should be hit. + assertTickJudgement(0, HitResult.LargeTickHit); // This tick is hidden under the slider head :( + assertTickJudgement(1, HitResult.LargeTickHit); + assertTickJudgement(2, HitResult.LargeTickHit); + + // Every other tick should be missed + assertTickJudgement(3, HitResult.LargeTickMiss); + assertTickJudgement(4, HitResult.LargeTickMiss); + assertTickJudgement(5, HitResult.LargeTickMiss); + assertTickJudgement(6, HitResult.LargeTickMiss); + assertTickJudgement(7, HitResult.LargeTickMiss); + assertTickJudgement(8, HitResult.LargeTickMiss); + assertTickJudgement(9, HitResult.LargeTickMiss); + assertTickJudgement(10, HitResult.LargeTickMiss); + + // In particular, these three are in the follow range of the head, but should not be hit + // because the slider was at some point outside the follow range of the head. + assertTickJudgement(11, HitResult.LargeTickMiss); + assertTickJudgement(12, HitResult.LargeTickMiss); + + // And the tail should be hit because of its leniency. + assertTailJudgement(HitResult.LargeTickHit); + + assertSliderJudgement(HitResult.IgnoreHit); + } + + /// + /// If the head circle is hit and the mouse is in range of the follow circle, + /// then a tick outside the range of the follow circle from the head should not be hit. + /// + [Test] + public void TestHitLateInRangeDoesNotHitOutOfRange() + { + performTest(new List + { + new OsuReplayFrame(time_slider_start + 150, slider_start_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_end + 150, slider_start_position, OsuAction.LeftButton), + }, s => + { + s.Path = new SliderPath(PathType.PERFECT_CURVE, new[] + { + Vector2.Zero, + new Vector2(50, 50), + new Vector2(20, 0), + }); + + s.TickDistanceMultiplier = 0.3f; + s.SliderVelocityMultiplier = 3; + }); + + assertHeadJudgement(HitResult.Meh); + assertTickJudgement(0, HitResult.LargeTickMiss); + assertTailJudgement(HitResult.LargeTickHit); + assertSliderJudgement(HitResult.IgnoreHit); + } + + private void assertHeadJudgement(HitResult result) + { + AddAssert( + "check head result", + () => judgementResults.SingleOrDefault(r => r.HitObject is SliderHeadCircle)?.Type, + () => Is.EqualTo(result)); + } + + private void assertTickJudgement(int index, HitResult result) + { + AddAssert( + $"check tick({index}) result", + () => judgementResults.Where(r => r.HitObject is SliderTick).ElementAtOrDefault(index)?.Type, + () => Is.EqualTo(result)); + } + + private void assertAllTickJudgements(HitResult result) + { + AddAssert( + "check all tick results", + () => judgementResults.Where(r => r.HitObject is SliderTick).Select(t => t.Type), + () => Has.All.EqualTo(result)); + } + + private void assertRepeatJudgement(HitResult result) + { + AddAssert( + "check repeat result", + () => judgementResults.SingleOrDefault(r => r.HitObject is SliderRepeat)?.Type, + () => Is.EqualTo(result)); + } + + private void assertTailJudgement(HitResult result) + { + AddAssert( + "check tail result", + () => judgementResults.SingleOrDefault(r => r.HitObject is SliderTailCircle)?.Type, + () => Is.EqualTo(result)); + } + + private void assertSliderJudgement(HitResult result) + { + AddAssert( + "check slider result", + () => judgementResults.SingleOrDefault(r => r.HitObject is Slider)?.Type, + () => Is.EqualTo(result)); + } + + private Vector2 computePositionFromTime(double time) + { + Vector2 dist = slider_end_position - slider_start_position; + double t = (time - time_slider_start) / (time_slider_end - time_slider_start); + return slider_start_position + dist * (float)t; + } + + private void performTest(List frames, Action? adjustSliderFunc = null, bool classic = false) + { + Slider slider = new Slider + { + StartTime = time_slider_start, + Position = new Vector2(256 - slider_path_length / 2, 192), + TickDistanceMultiplier = 3, + ClassicSliderBehaviour = classic, + Path = new SliderPath(PathType.LINEAR, new[] + { + Vector2.Zero, + new Vector2(slider_path_length, 0), + }, slider_path_length), + }; + + adjustSliderFunc?.Invoke(slider); + + AddStep("load player", () => + { + Beatmap.Value = CreateWorkingBeatmap(new Beatmap + { + HitObjects = { slider }, + BeatmapInfo = + { + Difficulty = new BeatmapDifficulty + { + SliderMultiplier = 4, + SliderTickRate = 3 + }, + Ruleset = new OsuRuleset().RulesetInfo, + } + }); + + var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } }); + + p.OnLoadComplete += _ => + { + p.ScoreProcessor.NewJudgement += result => + { + if (currentPlayer == p) judgementResults.Add(result); + }; + }; + + LoadScreen(currentPlayer = p); + judgementResults.Clear(); + }); + + AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0); + AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); + AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value); + } + + private partial class ScoreAccessibleReplayPlayer : ReplayPlayer + { + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + + protected override bool PauseOnFocusLost => false; + + public ScoreAccessibleReplayPlayer(Score score) + : base(score, new PlayerConfiguration + { + AllowPause = false, + ShowResults = false, + }) + { + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index a053c99a53..1dd6f108f5 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -28,6 +28,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public DrawableSliderHead HeadCircle => headContainer.Child; public DrawableSliderTail TailCircle => tailContainer.Child; + public IEnumerable Ticks => tickContainer.Children; + public IEnumerable Repeats => repeatContainer.Children; [Cached] public DrawableSliderBall Ball { get; private set; } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs index a1724d6fdc..764dd43c30 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs @@ -27,7 +27,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public Func GetInitialHitAction; - private Drawable followCircleReceptor; private DrawableSlider drawableSlider; private Drawable ball; @@ -48,13 +47,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Anchor = Anchor.Centre, RelativeSizeAxes = Axes.Both, }, - followCircleReceptor = new CircularContainer - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Masking = true - }, ball = new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.SliderBall), _ => new DefaultSliderBall()) { Anchor = Anchor.Centre, @@ -86,21 +78,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.ApplyTransformsAt(time, false); } - private bool tracking; - - public bool Tracking - { - get => tracking; - private set - { - if (value == tracking) - return; - - tracking = value; - - followCircleReceptor.Scale = new Vector2(tracking ? FOLLOW_AREA : 1f); - } - } + public bool Tracking { get; private set; } /// /// If the cursor moves out of the ball's radius we still need to be able to receive positional updates to stop tracking. @@ -129,6 +107,30 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables /// private readonly List lastPressedActions = new List(); + public bool IsMouseInFollowCircleWithState(bool expanded) + { + if (lastScreenSpaceMousePosition is not Vector2 mousePos) + return false; + + float radius = GetFollowCircleRadius(expanded); + + double followProgress = Math.Clamp((Time.Current - drawableSlider.HitObject.StartTime) / drawableSlider.HitObject.Duration, 0, 1); + Vector2 followCirclePosition = drawableSlider.HitObject.CurvePositionAt(followProgress); + Vector2 mousePositionInSlider = drawableSlider.ToLocalSpace(mousePos) - drawableSlider.OriginPosition; + + return (mousePositionInSlider - followCirclePosition).LengthSquared <= radius * radius; + } + + public float GetFollowCircleRadius(bool expanded) + { + float radius = (float)drawableSlider.HitObject.Radius; + + if (expanded) + radius *= FOLLOW_AREA; + + return radius; + } + protected override void Update() { base.Update(); @@ -152,14 +154,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables timeToAcceptAnyKeyAfter = Time.Current; } + bool validInFollowArea = IsMouseInFollowCircleWithState(Tracking); + bool validInHeadCircle = drawableSlider.HeadCircle.IsHit + && IsMouseInFollowCircleWithState(true) + && drawableSlider.HeadCircle.Result.TimeAbsolute == Time.Current; + Tracking = // even in an edge case where current time has exceeded the slider's time, we may not have finished judging. // we don't want to potentially update from Tracking=true to Tracking=false at this point. (!drawableSlider.AllJudged || Time.Current <= drawableSlider.HitObject.GetEndTime()) // in valid position range - && lastScreenSpaceMousePosition.HasValue && followCircleReceptor.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) && + && (validInFollowArea || validInHeadCircle) // valid action - (actions?.Any(isValidTrackingAction) ?? false); + && (actions?.Any(isValidTrackingAction) ?? false); lastPressedActions.Clear(); if (actions != null) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index ff690417a8..0a104c123b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -3,10 +3,14 @@ #nullable disable +using System; using System.Diagnostics; +using System.Linq; using osu.Framework.Bindables; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Scoring; +using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -61,6 +65,50 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables CheckHittable = (d, t, r) => DrawableSlider.CheckHittable?.Invoke(d, t, r) ?? ClickAction.Hit; } + protected override void CheckForResult(bool userTriggered, double timeOffset) + { + base.CheckForResult(userTriggered, timeOffset); + + if (!Judged || !Result.IsHit) + return; + + // If the head is hit and in radius of the would-be-expanded follow circle, + // then hit every object that the follow circle has passed through up until the current time. + if (DrawableSlider.Ball.IsMouseInFollowCircleWithState(true)) + { + foreach (var nested in DrawableSlider.NestedHitObjects.OfType()) + { + if (nested.Judged) + continue; + + if (!check(nested.HitObject)) + break; + + if (nested is DrawableSliderTick tick) + tick.HitForcefully(); + + if (nested is DrawableSliderRepeat repeat) + repeat.HitForcefully(); + + if (nested is DrawableSliderTail tail) + tail.HitForcefully(); + } + } + + bool check(OsuHitObject h) + { + if (h.StartTime > Time.Current) + return false; + + float radius = DrawableSlider.Ball.GetFollowCircleRadius(true); + + double objectProgress = Math.Clamp((h.StartTime - DrawableSlider.HitObject.StartTime) / DrawableSlider.HitObject.Duration, 0, 1); + Vector2 objectPosition = DrawableSlider.HitObject.CurvePositionAt(objectProgress); + + return objectPosition.LengthSquared <= radius * radius; + } + } + protected override HitResult ResultFor(double timeOffset) { Debug.Assert(HitObject != null); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index cdfd96514e..a7979bde27 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -85,17 +85,33 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Position = HitObject.Position - DrawableSlider.Position; } + public void HitForcefully() + { + if (Judged) + return; + + ApplyResult(r => r.Type = r.Judgement.MaxResult); + } + protected override void CheckForResult(bool userTriggered, double timeOffset) { // shared implementation with DrawableSliderTick. if (timeOffset >= 0) { - // Attempt to preserve correct ordering of judgements as best we can by forcing - // an un-judged head to be missed when the user has clearly skipped it. - // // This check is applied to all nested slider objects apart from the head (ticks, repeats, tail). - if (Tracking && !DrawableSlider.HeadCircle.Judged) - DrawableSlider.HeadCircle.MissForcefully(); + if (!DrawableSlider.HeadCircle.Judged) + { + if (Tracking) + { + // Attempt to preserve correct ordering of judgements as best we can by forcing an un-judged head to be missed when the user has clearly skipped it. + DrawableSlider.HeadCircle.MissForcefully(); + } + else + { + // Don't judge this object as a miss before the head has been judged, to allow the head to be hit late. + return; + } + } ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : r.Judgement.MinResult); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index e3ed12a648..1ffbaf11c5 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -125,6 +125,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } + public void HitForcefully() + { + if (Judged) + return; + + ApplyResult(r => r.Type = r.Judgement.MaxResult); + } + protected override void CheckForResult(bool userTriggered, double timeOffset) { if (userTriggered) @@ -141,12 +149,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (timeOffset < SliderEventGenerator.TAIL_LENIENCY) return; - // Attempt to preserve correct ordering of judgements as best we can by forcing - // an un-judged head to be missed when the user has clearly skipped it. - // // This check is applied to all nested slider objects apart from the head (ticks, repeats, tail). - if (Tracking && !DrawableSlider.HeadCircle.Judged) - DrawableSlider.HeadCircle.MissForcefully(); + if (!DrawableSlider.HeadCircle.Judged) + { + if (Tracking) + { + // Attempt to preserve correct ordering of judgements as best we can by forcing an un-judged head to be missed when the user has clearly skipped it. + DrawableSlider.HeadCircle.MissForcefully(); + } + else + { + // Don't judge this object as a miss before the head has been judged, to allow the head to be hit late. + return; + } + } // The player needs to have engaged in tracking at any point after the tail leniency cutoff. // An actual tick miss should only occur if reaching the tick itself. diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index 172dca356e..b2ac8ecbda 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -73,17 +73,33 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Position = HitObject.Position - DrawableSlider.HitObject.Position; } + public void HitForcefully() + { + if (Judged) + return; + + ApplyResult(r => r.Type = r.Judgement.MaxResult); + } + protected override void CheckForResult(bool userTriggered, double timeOffset) { // shared implementation with DrawableSliderRepeat. if (timeOffset >= 0) { - // Attempt to preserve correct ordering of judgements as best we can by forcing - // an un-judged head to be missed when the user has clearly skipped it. - // // This check is applied to all nested slider objects apart from the head (ticks, repeats, tail). - if (Tracking && !DrawableSlider.HeadCircle.Judged) - DrawableSlider.HeadCircle.MissForcefully(); + if (!DrawableSlider.HeadCircle.Judged) + { + if (Tracking) + { + // Attempt to preserve correct ordering of judgements as best we can by forcing an un-judged head to be missed when the user has clearly skipped it. + DrawableSlider.HeadCircle.MissForcefully(); + } + else + { + // Don't judge this object as a miss before the head has been judged, to allow the head to be hit late. + return; + } + } ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : r.Judgement.MinResult); } @@ -107,7 +123,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables case ArmedState.Miss: this.FadeOut(ANIM_DURATION); - this.FadeColour(Color4.Red, ANIM_DURATION / 2); + this.TransformBindableTo(AccentColour, Color4.Red, 0); break; case ArmedState.Hit: From b86f387fd30acab999ad27d38dfb31bd9a993e8a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Dec 2023 15:04:20 +0900 Subject: [PATCH 1953/4092] Fix the width of vertical attribute display numbers Not necessarily required to fix the issue at hand, but probably good practice here. --- osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs b/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs index 62c08f8132..33b7eaae1c 100644 --- a/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs +++ b/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs @@ -82,7 +82,8 @@ namespace osu.Game.Overlays.Mods { Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, + AutoSizeAxes = Axes.Y, + Width = 50, Direction = FillDirection.Vertical, Children = new Drawable[] { From dc5c9837ed969d736645b9bf287099069774caa1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Dec 2023 16:00:03 +0900 Subject: [PATCH 1954/4092] Fix `StarRatingDisplay`'s display width to avoid text making slight autosize changes --- .../Beatmaps/Drawables/StarRatingDisplay.cs | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/StarRatingDisplay.cs b/osu.Game/Beatmaps/Drawables/StarRatingDisplay.cs index df8953d57c..55ef6f705e 100644 --- a/osu.Game/Beatmaps/Drawables/StarRatingDisplay.cs +++ b/osu.Game/Beatmaps/Drawables/StarRatingDisplay.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 System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -38,6 +39,8 @@ namespace osu.Game.Beatmaps.Drawables private readonly Bindable displayedStars = new BindableDouble(); + private readonly Container textContainer; + /// /// The currently displayed stars of this display wrapped in a bindable. /// This bindable gets transformed on change rather than instantaneous, if animation is enabled. @@ -116,15 +119,19 @@ namespace osu.Game.Beatmaps.Drawables Size = new Vector2(8f), }, Empty(), - starsText = new OsuSpriteText + textContainer = new Container { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Margin = new MarginPadding { Bottom = 1.5f }, - // todo: this should be size: 12f, but to match up with the design, it needs to be 14.4f - // see https://github.com/ppy/osu-framework/issues/3271. - Font = OsuFont.Torus.With(size: 14.4f, weight: FontWeight.Bold), - Shadow = false, + AutoSizeAxes = Axes.Y, + Child = starsText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Margin = new MarginPadding { Bottom = 1.5f }, + // todo: this should be size: 12f, but to match up with the design, it needs to be 14.4f + // see https://github.com/ppy/osu-framework/issues/3271. + Font = OsuFont.Torus.With(size: 14.4f, weight: FontWeight.Bold), + Shadow = false, + }, }, } } @@ -155,6 +162,11 @@ namespace osu.Game.Beatmaps.Drawables starIcon.Colour = s.NewValue >= 6.5 ? colours.Orange1 : colourProvider?.Background5 ?? Color4Extensions.FromHex("303d47"); starsText.Colour = s.NewValue >= 6.5 ? colours.Orange1 : colourProvider?.Background5 ?? Color4.Black.Opacity(0.75f); + + // In order to avoid autosize throwing the width of these displays all over the place, + // let's lock in some sane defaults for the text width based on how many digits we're + // displaying. + textContainer.Width = 24 + Math.Max(starsText.Text.ToString().Length - 4, 0) * 6; }, true); } } From 4357bb1040b6f7e64f7bc24eb98434e682d352cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Dec 2023 16:01:07 +0900 Subject: [PATCH 1955/4092] Change `LeftContent` autosize duration to match main content to reduce visual awkwards --- osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index d50b2c33d7..94dd96ec1c 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -144,8 +144,8 @@ namespace osu.Game.Overlays.Mods private void startAnimating() { - Content.AutoSizeEasing = Easing.OutQuint; - Content.AutoSizeDuration = transition_duration; + LeftContent.AutoSizeEasing = Content.AutoSizeEasing = Easing.OutQuint; + LeftContent.AutoSizeDuration = Content.AutoSizeDuration = transition_duration; } private void updateValues() => Scheduler.AddOnce(() => From 6e7e243e70492bc55dac69ce28ab7b8480c8702b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Dec 2023 16:05:29 +0900 Subject: [PATCH 1956/4092] Allow new common cases when a user is locating a stable osu! install directory for import --- .../Maintenance/StableDirectorySelectScreen.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs index 1b935b0cec..73ac6efcd8 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs @@ -15,7 +15,16 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled; - protected override bool IsValidDirectory(DirectoryInfo? info) => info?.GetFiles("osu!.*.cfg").Any() ?? false; + protected override bool IsValidDirectory(DirectoryInfo? info) => + // A full stable installation will have a configuration file present. + // This is the best case scenario, as it may contain a custom beatmap directory we need to traverse to. + info?.GetFiles("osu!.*.cfg").Any() == true || + // The user may only have their songs or skins folders left. + // We still want to allow them to import based on this. + info?.GetDirectories("Songs").Any() == true || + info?.GetDirectories("Skins").Any() == true || + // The user may have traverse *inside* their songs or skins folders. + shouldUseParentDirectory(info); public override LocalisableString HeaderText => "Please select your osu!stable install location"; @@ -26,7 +35,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance protected override void OnSelection(DirectoryInfo directory) { - taskCompletionSource.TrySetResult(directory.FullName); + taskCompletionSource.TrySetResult(shouldUseParentDirectory(directory) ? directory.Parent!.FullName : directory.FullName); this.Exit(); } @@ -35,5 +44,8 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance taskCompletionSource.TrySetCanceled(); return base.OnExiting(e); } + + private bool shouldUseParentDirectory(DirectoryInfo? info) + => info?.Parent != null && (info?.Name == "Songs" || info?.Name == "Skins"); } } From 6bd190c55d809ee6b69db19d5ce944bd6c887bfd Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 15 Dec 2023 16:13:32 +0900 Subject: [PATCH 1957/4092] Refactor all slider input into SliderInputManager --- .../Objects/Drawables/DrawableHitCircle.cs | 2 +- .../Objects/Drawables/DrawableOsuHitObject.cs | 5 + .../Objects/Drawables/DrawableSlider.cs | 14 +- .../Objects/Drawables/DrawableSliderBall.cs | 126 +-------- .../Objects/Drawables/DrawableSliderHead.cs | 44 +--- .../Objects/Drawables/DrawableSliderRepeat.cs | 36 +-- .../Objects/Drawables/DrawableSliderTail.cs | 53 +--- .../Objects/Drawables/DrawableSliderTick.cs | 36 +-- .../Objects/Drawables/IRequireTracking.cs | 13 - .../Objects/Drawables/SliderInputManager.cs | 248 ++++++++++++++++++ 10 files changed, 270 insertions(+), 307 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Objects/Drawables/IRequireTracking.cs create mode 100644 osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index e87a075a11..0d665cad0c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public partial class DrawableHitCircle : DrawableOsuHitObject, IHasApproachCircle { - public OsuAction? HitAction => HitArea.HitAction; + public OsuAction? HitAction => HitArea?.HitAction; protected virtual OsuSkinComponents CirclePieceComponent => OsuSkinComponents.HitCircle; public SkinnableDrawable ApproachCircle { get; private set; } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index bdd818cf18..5b379a0d90 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -97,6 +97,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables /// public virtual void Shake() { } + /// + /// Causes this to get hit, disregarding all conditions in implementations of . + /// + public void HitForcefully() => ApplyResult(r => r.Type = r.Judgement.MaxResult); + /// /// Causes this to get missed, disregarding all conditions in implementations of . /// diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 1dd6f108f5..1f9a028045 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -28,8 +28,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public DrawableSliderHead HeadCircle => headContainer.Child; public DrawableSliderTail TailCircle => tailContainer.Child; - public IEnumerable Ticks => tickContainer.Children; - public IEnumerable Repeats => repeatContainer.Children; [Cached] public DrawableSliderBall Ball { get; private set; } @@ -60,6 +58,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public IBindable PathVersion => pathVersion; private readonly Bindable pathVersion = new Bindable(); + public readonly SliderInputManager SliderInputManager; + private Container headContainer; private Container tailContainer; private Container tickContainer; @@ -74,9 +74,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public DrawableSlider([CanBeNull] Slider s = null) : base(s) { + SliderInputManager = new SliderInputManager(this); + Ball = new DrawableSliderBall { - GetInitialHitAction = () => HeadCircle.HitAction, BypassAutoSizeAxes = Axes.Both, AlwaysPresent = true, Alpha = 0 @@ -90,6 +91,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables AddRangeInternal(new Drawable[] { + SliderInputManager, shakeContainer = new ShakeContainer { ShakeDuration = 30, @@ -234,7 +236,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.Update(); - Tracking.Value = Ball.Tracking; + Tracking.Value = SliderInputManager.Tracking; if (Tracking.Value && slidingSample != null) // keep the sliding sample playing at the current tracking position @@ -247,8 +249,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables foreach (DrawableHitObject hitObject in NestedHitObjects) { - if (hitObject is ITrackSnaking s) s.UpdateSnakingPosition(HitObject.Path.PositionAt(SliderBody?.SnakedStart ?? 0), HitObject.Path.PositionAt(SliderBody?.SnakedEnd ?? 0)); - if (hitObject is IRequireTracking t) t.Tracking = Ball.Tracking; + if (hitObject is ITrackSnaking s) + s.UpdateSnakingPosition(HitObject.Path.PositionAt(SliderBody?.SnakedStart ?? 0), HitObject.Path.PositionAt(SliderBody?.SnakedEnd ?? 0)); } Size = SliderBody?.Size ?? Vector2.Zero; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs index 764dd43c30..46f0231981 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs @@ -4,14 +4,9 @@ #nullable disable using System; -using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Input; -using osu.Framework.Input.Events; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Skinning.Default; @@ -21,12 +16,10 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables { - public partial class DrawableSliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition + public partial class DrawableSliderBall : CircularContainer, ISliderProgress { public const float FOLLOW_AREA = 2.4f; - public Func GetInitialHitAction; - private DrawableSlider drawableSlider; private Drawable ball; @@ -55,14 +48,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }; } - private Vector2? lastScreenSpaceMousePosition; - - protected override bool OnMouseMove(MouseMoveEvent e) - { - lastScreenSpaceMousePosition = e.ScreenSpaceMousePosition; - return base.OnMouseMove(e); - } - public override void ClearTransformsAfter(double time, bool propagateChildren = false, string targetMember = null) { // Consider the case of rewinding - children's transforms are handled internally, so propagating down @@ -78,115 +63,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.ApplyTransformsAt(time, false); } - public bool Tracking { get; private set; } - - /// - /// If the cursor moves out of the ball's radius we still need to be able to receive positional updates to stop tracking. - /// - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; - - /// - /// The point in time after which we can accept any key for tracking. Before this time, we may need to restrict tracking to the key used to hit the head circle. - /// - /// This is a requirement to stop the case where a player holds down one key (from before the slider) and taps the second key while maintaining full scoring (tracking) of sliders. - /// Visually, this special case can be seen below (time increasing from left to right): - /// - /// Z Z+X Z - /// o========o - /// - /// Without this logic, tracking would continue through the entire slider even though no key hold action is directly attributing to it. - /// - /// In all other cases, no special handling is required (either key being pressed is allowable as valid tracking). - /// - /// The reason for storing this as a time value (rather than a bool) is to correctly handle rewind scenarios. - /// - private double? timeToAcceptAnyKeyAfter; - - /// - /// The actions that were pressed in the previous frame. - /// - private readonly List lastPressedActions = new List(); - - public bool IsMouseInFollowCircleWithState(bool expanded) - { - if (lastScreenSpaceMousePosition is not Vector2 mousePos) - return false; - - float radius = GetFollowCircleRadius(expanded); - - double followProgress = Math.Clamp((Time.Current - drawableSlider.HitObject.StartTime) / drawableSlider.HitObject.Duration, 0, 1); - Vector2 followCirclePosition = drawableSlider.HitObject.CurvePositionAt(followProgress); - Vector2 mousePositionInSlider = drawableSlider.ToLocalSpace(mousePos) - drawableSlider.OriginPosition; - - return (mousePositionInSlider - followCirclePosition).LengthSquared <= radius * radius; - } - - public float GetFollowCircleRadius(bool expanded) - { - float radius = (float)drawableSlider.HitObject.Radius; - - if (expanded) - radius *= FOLLOW_AREA; - - return radius; - } - - protected override void Update() - { - base.Update(); - - // from the point at which the head circle is hit, this will be non-null. - // it may be null if the head circle was missed. - var headCircleHitAction = GetInitialHitAction(); - - if (headCircleHitAction == null) - timeToAcceptAnyKeyAfter = null; - - var actions = drawableSlider.OsuActionInputManager?.PressedActions; - - // if the head circle was hit with a specific key, tracking should only occur while that key is pressed. - if (headCircleHitAction != null && timeToAcceptAnyKeyAfter == null) - { - var otherKey = headCircleHitAction == OsuAction.RightButton ? OsuAction.LeftButton : OsuAction.RightButton; - - // we can start accepting any key once all other keys have been released in the previous frame. - if (!lastPressedActions.Contains(otherKey)) - timeToAcceptAnyKeyAfter = Time.Current; - } - - bool validInFollowArea = IsMouseInFollowCircleWithState(Tracking); - bool validInHeadCircle = drawableSlider.HeadCircle.IsHit - && IsMouseInFollowCircleWithState(true) - && drawableSlider.HeadCircle.Result.TimeAbsolute == Time.Current; - - Tracking = - // even in an edge case where current time has exceeded the slider's time, we may not have finished judging. - // we don't want to potentially update from Tracking=true to Tracking=false at this point. - (!drawableSlider.AllJudged || Time.Current <= drawableSlider.HitObject.GetEndTime()) - // in valid position range - && (validInFollowArea || validInHeadCircle) - // valid action - && (actions?.Any(isValidTrackingAction) ?? false); - - lastPressedActions.Clear(); - if (actions != null) - lastPressedActions.AddRange(actions); - } - - /// - /// Check whether a given user input is a valid tracking action. - /// - private bool isValidTrackingAction(OsuAction action) - { - bool headCircleHit = GetInitialHitAction().HasValue; - - // if the head circle was hit, we may not yet be allowed to accept any key, so we must use the initial hit action. - if (headCircleHit && (!timeToAcceptAnyKeyAfter.HasValue || Time.Current <= timeToAcceptAnyKeyAfter.Value)) - return action == GetInitialHitAction(); - - return action == OsuAction.LeftButton || action == OsuAction.RightButton; - } - private Vector2? lastPosition; public void UpdateProgress(double completionProgress) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index 0a104c123b..76b9fdc3ce 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -3,14 +3,10 @@ #nullable disable -using System; using System.Diagnostics; -using System.Linq; using osu.Framework.Bindables; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Scoring; -using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -68,45 +64,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void CheckForResult(bool userTriggered, double timeOffset) { base.CheckForResult(userTriggered, timeOffset); - - if (!Judged || !Result.IsHit) - return; - - // If the head is hit and in radius of the would-be-expanded follow circle, - // then hit every object that the follow circle has passed through up until the current time. - if (DrawableSlider.Ball.IsMouseInFollowCircleWithState(true)) - { - foreach (var nested in DrawableSlider.NestedHitObjects.OfType()) - { - if (nested.Judged) - continue; - - if (!check(nested.HitObject)) - break; - - if (nested is DrawableSliderTick tick) - tick.HitForcefully(); - - if (nested is DrawableSliderRepeat repeat) - repeat.HitForcefully(); - - if (nested is DrawableSliderTail tail) - tail.HitForcefully(); - } - } - - bool check(OsuHitObject h) - { - if (h.StartTime > Time.Current) - return false; - - float radius = DrawableSlider.Ball.GetFollowCircleRadius(true); - - double objectProgress = Math.Clamp((h.StartTime - DrawableSlider.HitObject.StartTime) / DrawableSlider.HitObject.Duration, 0, 1); - Vector2 objectPosition = DrawableSlider.HitObject.CurvePositionAt(objectProgress); - - return objectPosition.LengthSquared <= radius * radius; - } + DrawableSlider.SliderInputManager.PostProcessHeadJudgement(this); } protected override HitResult ResultFor(double timeOffset) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index a7979bde27..0c8e5b765f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -17,7 +17,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables { - public partial class DrawableSliderRepeat : DrawableOsuHitObject, ITrackSnaking, IRequireTracking + public partial class DrawableSliderRepeat : DrawableOsuHitObject, ITrackSnaking { public new SliderRepeat HitObject => (SliderRepeat)base.HitObject; @@ -36,8 +36,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public override bool DisplayResult => false; - public bool Tracking { get; set; } - public DrawableSliderRepeat() : base(null) { @@ -85,37 +83,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Position = HitObject.Position - DrawableSlider.Position; } - public void HitForcefully() - { - if (Judged) - return; - - ApplyResult(r => r.Type = r.Judgement.MaxResult); - } - - protected override void CheckForResult(bool userTriggered, double timeOffset) - { - // shared implementation with DrawableSliderTick. - if (timeOffset >= 0) - { - // This check is applied to all nested slider objects apart from the head (ticks, repeats, tail). - if (!DrawableSlider.HeadCircle.Judged) - { - if (Tracking) - { - // Attempt to preserve correct ordering of judgements as best we can by forcing an un-judged head to be missed when the user has clearly skipped it. - DrawableSlider.HeadCircle.MissForcefully(); - } - else - { - // Don't judge this object as a miss before the head has been judged, to allow the head to be hit late. - return; - } - } - - ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : r.Judgement.MinResult); - } - } + protected override void CheckForResult(bool userTriggered, double timeOffset) => DrawableSlider.SliderInputManager.TryJudgeNestedObject(this, timeOffset); protected override void UpdateInitialTransforms() { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index 1ffbaf11c5..60bad5d4a7 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -4,12 +4,10 @@ #nullable disable using System.Diagnostics; -using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Skinning; @@ -17,7 +15,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables { - public partial class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking + public partial class DrawableSliderTail : DrawableOsuHitObject { public new SliderTailCircle HitObject => (SliderTailCircle)base.HitObject; @@ -37,8 +35,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables ///
public bool SamplePlaysOnlyOnHit { get; set; } = true; - public bool Tracking { get; set; } - public SkinnableDrawable CirclePiece { get; private set; } private Container scaleContainer; @@ -125,52 +121,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - public void HitForcefully() - { - if (Judged) - return; - - ApplyResult(r => r.Type = r.Judgement.MaxResult); - } - - protected override void CheckForResult(bool userTriggered, double timeOffset) - { - if (userTriggered) - return; - - // Ensure the tail can only activate after all previous ticks/repeats already have. - // - // This covers the edge case where the lenience may allow the tail to activate before - // the last tick, changing ordering of score/combo awarding. - var lastTick = DrawableSlider.NestedHitObjects.LastOrDefault(o => o.HitObject is SliderTick || o.HitObject is SliderRepeat); - if (lastTick?.Judged == false) - return; - - if (timeOffset < SliderEventGenerator.TAIL_LENIENCY) - return; - - // This check is applied to all nested slider objects apart from the head (ticks, repeats, tail). - if (!DrawableSlider.HeadCircle.Judged) - { - if (Tracking) - { - // Attempt to preserve correct ordering of judgements as best we can by forcing an un-judged head to be missed when the user has clearly skipped it. - DrawableSlider.HeadCircle.MissForcefully(); - } - else - { - // Don't judge this object as a miss before the head has been judged, to allow the head to be hit late. - return; - } - } - - // The player needs to have engaged in tracking at any point after the tail leniency cutoff. - // An actual tick miss should only occur if reaching the tick itself. - if (Tracking) - ApplyResult(r => r.Type = r.Judgement.MaxResult); - else if (timeOffset > 0) - ApplyResult(r => r.Type = r.Judgement.MinResult); - } + protected override void CheckForResult(bool userTriggered, double timeOffset) => DrawableSlider.SliderInputManager.TryJudgeNestedObject(this, timeOffset); protected override void OnApply() { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index b2ac8ecbda..cb323f4ac7 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -14,14 +14,12 @@ using osu.Framework.Graphics.Containers; namespace osu.Game.Rulesets.Osu.Objects.Drawables { - public partial class DrawableSliderTick : DrawableOsuHitObject, IRequireTracking + public partial class DrawableSliderTick : DrawableOsuHitObject { public const double ANIM_DURATION = 150; private const float default_tick_size = 16; - public bool Tracking { get; set; } - public override bool DisplayResult => false; protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject; @@ -73,37 +71,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Position = HitObject.Position - DrawableSlider.HitObject.Position; } - public void HitForcefully() - { - if (Judged) - return; - - ApplyResult(r => r.Type = r.Judgement.MaxResult); - } - - protected override void CheckForResult(bool userTriggered, double timeOffset) - { - // shared implementation with DrawableSliderRepeat. - if (timeOffset >= 0) - { - // This check is applied to all nested slider objects apart from the head (ticks, repeats, tail). - if (!DrawableSlider.HeadCircle.Judged) - { - if (Tracking) - { - // Attempt to preserve correct ordering of judgements as best we can by forcing an un-judged head to be missed when the user has clearly skipped it. - DrawableSlider.HeadCircle.MissForcefully(); - } - else - { - // Don't judge this object as a miss before the head has been judged, to allow the head to be hit late. - return; - } - } - - ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : r.Judgement.MinResult); - } - } + protected override void CheckForResult(bool userTriggered, double timeOffset) => DrawableSlider.SliderInputManager.TryJudgeNestedObject(this, timeOffset); protected override void UpdateInitialTransforms() { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/IRequireTracking.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/IRequireTracking.cs deleted file mode 100644 index b1815b23c9..0000000000 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/IRequireTracking.cs +++ /dev/null @@ -1,13 +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.Osu.Objects.Drawables -{ - public interface IRequireTracking - { - /// - /// Whether the is currently being tracked by the user. - /// - bool Tracking { get; set; } - } -} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs new file mode 100644 index 0000000000..a5d7bdc7db --- /dev/null +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs @@ -0,0 +1,248 @@ +// 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.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Input; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables +{ + public partial class SliderInputManager : Component, IRequireHighFrequencyMousePosition + { + /// + /// Whether the slider is currently being tracked. + /// + public bool Tracking { get; private set; } + + /// + /// The point in time after which we can accept any key for tracking. Before this time, we may need to restrict tracking to the key used to hit the head circle. + /// + /// This is a requirement to stop the case where a player holds down one key (from before the slider) and taps the second key while maintaining full scoring (tracking) of sliders. + /// Visually, this special case can be seen below (time increasing from left to right): + /// + /// Z Z+X Z + /// o========o + /// + /// Without this logic, tracking would continue through the entire slider even though no key hold action is directly attributing to it. + /// + /// In all other cases, no special handling is required (either key being pressed is allowable as valid tracking). + /// + /// The reason for storing this as a time value (rather than a bool) is to correctly handle rewind scenarios. + /// + private double? timeToAcceptAnyKeyAfter; + + /// + /// The actions that were pressed in the previous frame. + /// + private readonly List lastPressedActions = new List(); + + private Vector2? screenSpaceMousePosition; + private readonly DrawableSlider slider; + + public SliderInputManager(DrawableSlider slider) + { + this.slider = slider; + } + + /// + /// This component handles all input of the slider, so it should receive input no matter the position. + /// + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + + protected override bool OnMouseMove(MouseMoveEvent e) + { + screenSpaceMousePosition = e.ScreenSpaceMousePosition; + return base.OnMouseMove(e); + } + + protected override void Update() + { + base.Update(); + updateTracking(isMouseInFollowArea(Tracking)); + } + + public void PostProcessHeadJudgement(DrawableSliderHead head) + { + if (!head.Judged || !head.Result.IsHit) + return; + + if (!isMouseInFollowArea(true)) + return; + + // When the head is hit and the mouse is in the expanded follow area, force a hit on every nested hitobject + // from the start of the slider that is within follow-radius units from the head. + + bool forceMiss = false; + + foreach (var nested in slider.NestedHitObjects.OfType()) + { + // Skip nested objects that are already judged. + if (nested.Judged) + continue; + + // Stop the process when a nested object is reached that can't be hit before the current time. + if (nested.HitObject.StartTime > Time.Current) + break; + + float radius = getFollowRadius(true); + double objectProgress = Math.Clamp((nested.HitObject.StartTime - slider.HitObject.StartTime) / slider.HitObject.Duration, 0, 1); + Vector2 objectPosition = slider.HitObject.CurvePositionAt(objectProgress); + + // When the first nested object that is further than follow-radius units away from the start of the slider is reached, + // forcefully miss all other nested objects that would otherwise be valid to be hit by this process. + if (forceMiss || objectPosition.LengthSquared > radius * radius) + { + nested.MissForcefully(); + forceMiss = true; + } + else + nested.HitForcefully(); + } + + // Enable tracking, since the mouse is within the follow area (if it were expanded). + updateTracking(true); + } + + public void TryJudgeNestedObject(DrawableOsuHitObject nestedObject, double timeOffset) + { + switch (nestedObject) + { + case DrawableSliderRepeat: + case DrawableSliderTick: + if (timeOffset < 0) + return; + + break; + + case DrawableSliderTail: + if (timeOffset < SliderEventGenerator.TAIL_LENIENCY) + return; + + // Ensure the tail can only activate after all previous ticks/repeats already have. + // + // This covers the edge case where the lenience may allow the tail to activate before + // the last tick, changing ordering of score/combo awarding. + var lastTick = slider.NestedHitObjects.LastOrDefault(o => o.HitObject is SliderTick || o.HitObject is SliderRepeat); + if (lastTick?.Judged == false) + return; + + break; + + default: + return; + } + + if (!slider.HeadCircle.Judged) + { + if (slider.Tracking.Value) + { + // Attempt to preserve correct ordering of judgements as best we can by forcing an un-judged head to be missed when the user has clearly skipped it. + slider.HeadCircle.MissForcefully(); + } + else + { + // Don't judge this object as a miss before the head has been judged, to allow the head to be hit late. + return; + } + } + + if (slider.Tracking.Value) + nestedObject.HitForcefully(); + else + nestedObject.MissForcefully(); + } + + /// + /// Whether the mouse is currently in the follow area. + /// + /// Whether to test against the maximum area of the follow circle. + private bool isMouseInFollowArea(bool expanded) + { + if (screenSpaceMousePosition is not Vector2 pos) + return false; + + float radius = getFollowRadius(expanded); + + double followProgress = Math.Clamp((Time.Current - slider.HitObject.StartTime) / slider.HitObject.Duration, 0, 1); + Vector2 followCirclePosition = slider.HitObject.CurvePositionAt(followProgress); + Vector2 mousePositionInSlider = slider.ToLocalSpace(pos) - slider.OriginPosition; + + return (mousePositionInSlider - followCirclePosition).LengthSquared <= radius * radius; + } + + /// + /// Retrieves the radius of the follow area. + /// + /// Whether to return the maximum area of the follow circle. + private float getFollowRadius(bool expanded) + { + float radius = (float)slider.HitObject.Radius; + + if (expanded) + radius *= DrawableSliderBall.FOLLOW_AREA; + + return radius; + } + + /// + /// Updates the tracking state. + /// + /// Whether the current mouse position is valid to begin tracking. + private void updateTracking(bool isValidTrackingPosition) + { + // from the point at which the head circle is hit, this will be non-null. + // it may be null if the head circle was missed. + OsuAction? headCircleHitAction = getInitialHitAction(); + + if (headCircleHitAction == null) + timeToAcceptAnyKeyAfter = null; + + var actions = slider.OsuActionInputManager?.PressedActions; + + // if the head circle was hit with a specific key, tracking should only occur while that key is pressed. + if (headCircleHitAction != null && timeToAcceptAnyKeyAfter == null) + { + var otherKey = headCircleHitAction == OsuAction.RightButton ? OsuAction.LeftButton : OsuAction.RightButton; + + // we can start accepting any key once all other keys have been released in the previous frame. + if (!lastPressedActions.Contains(otherKey)) + timeToAcceptAnyKeyAfter = Time.Current; + } + + Tracking = + // even in an edge case where current time has exceeded the slider's time, we may not have finished judging. + // we don't want to potentially update from Tracking=true to Tracking=false at this point. + (!slider.AllJudged || Time.Current <= slider.HitObject.GetEndTime()) + // in valid position range + && isValidTrackingPosition + // valid action + && (actions?.Any(isValidTrackingAction) ?? false); + + lastPressedActions.Clear(); + if (actions != null) + lastPressedActions.AddRange(actions); + } + + private OsuAction? getInitialHitAction() => slider.HeadCircle?.HitAction; + + /// + /// Check whether a given user input is a valid tracking action. + /// + private bool isValidTrackingAction(OsuAction action) + { + OsuAction? hitAction = getInitialHitAction(); + + // if the head circle was hit, we may not yet be allowed to accept any key, so we must use the initial hit action. + if (hitAction.HasValue && (!timeToAcceptAnyKeyAfter.HasValue || Time.Current <= timeToAcceptAnyKeyAfter.Value)) + return action == hitAction; + + return action == OsuAction.LeftButton || action == OsuAction.RightButton; + } + } +} From 3b8a73bf2c5ac25224ae2fc88967b58f98cb07a7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 15 Dec 2023 16:13:34 +0900 Subject: [PATCH 1958/4092] Refactor test --- .../TestSceneSliderLateHitJudgement.cs | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs index 4f32a6fe9f..04bdd0094d 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs @@ -296,7 +296,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void assertHeadJudgement(HitResult result) { AddAssert( - "check head result", + $"head = {result}", () => judgementResults.SingleOrDefault(r => r.HitObject is SliderHeadCircle)?.Type, () => Is.EqualTo(result)); } @@ -304,7 +304,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void assertTickJudgement(int index, HitResult result) { AddAssert( - $"check tick({index}) result", + $"tick({index}) = {result}", () => judgementResults.Where(r => r.HitObject is SliderTick).ElementAtOrDefault(index)?.Type, () => Is.EqualTo(result)); } @@ -312,7 +312,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void assertAllTickJudgements(HitResult result) { AddAssert( - "check all tick results", + $"all ticks = {result}", () => judgementResults.Where(r => r.HitObject is SliderTick).Select(t => t.Type), () => Has.All.EqualTo(result)); } @@ -320,7 +320,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void assertRepeatJudgement(HitResult result) { AddAssert( - "check repeat result", + $"repeat = {result}", () => judgementResults.SingleOrDefault(r => r.HitObject is SliderRepeat)?.Type, () => Is.EqualTo(result)); } @@ -328,7 +328,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void assertTailJudgement(HitResult result) { AddAssert( - "check tail result", + $"tail = {result}", () => judgementResults.SingleOrDefault(r => r.HitObject is SliderTailCircle)?.Type, () => Is.EqualTo(result)); } @@ -336,18 +336,11 @@ namespace osu.Game.Rulesets.Osu.Tests private void assertSliderJudgement(HitResult result) { AddAssert( - "check slider result", + $"slider = {result}", () => judgementResults.SingleOrDefault(r => r.HitObject is Slider)?.Type, () => Is.EqualTo(result)); } - private Vector2 computePositionFromTime(double time) - { - Vector2 dist = slider_end_position - slider_start_position; - double t = (time - time_slider_start) / (time_slider_end - time_slider_start); - return slider_start_position + dist * (float)t; - } - private void performTest(List frames, Action? adjustSliderFunc = null, bool classic = false) { Slider slider = new Slider From 12210017e44341e5378edc2af86553addeae4eab Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 15 Dec 2023 17:05:14 +0900 Subject: [PATCH 1959/4092] Use the cursor position to test nested object validity --- .../TestSceneSliderLateHitJudgement.cs | 42 ++++++++++++++++--- .../Objects/Drawables/SliderInputManager.cs | 14 +++++-- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs index 04bdd0094d..80e4b04178 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs @@ -184,7 +184,7 @@ namespace osu.Game.Rulesets.Osu.Tests /// /// If the head circle is hit late and the mouse is in range of the follow circle, - /// then all the repeats that the mouse has passed through should be hit. + /// then all the repeats that the follow circle has passed through should be hit. /// [Test] public void TestHitLateInRangeHitsRepeat() @@ -212,8 +212,8 @@ namespace osu.Game.Rulesets.Osu.Tests /// /// If the head circle is hit and the mouse is in range of the follow circle, - /// then only the ticks that were in range of the follow circle at the head should be hit. - /// If any hitobject was outside the follow range, ALL hitobjects after that point should be missed. + /// then only the ticks that are in range of the cursor position should be hit. + /// If any hitobject does not meet this criteria, ALL hitobjects after that one should be missed. /// [Test] public void TestHitLateInRangeDoesNotHitAfterAnyOutOfRange() @@ -257,7 +257,7 @@ namespace osu.Game.Rulesets.Osu.Tests assertTickJudgement(11, HitResult.LargeTickMiss); assertTickJudgement(12, HitResult.LargeTickMiss); - // And the tail should be hit because of its leniency. + // This particular test actually starts tracking the slider just before the end, so the tail should be hit because of its leniency. assertTailJudgement(HitResult.LargeTickHit); assertSliderJudgement(HitResult.IgnoreHit); @@ -265,10 +265,10 @@ namespace osu.Game.Rulesets.Osu.Tests /// /// If the head circle is hit and the mouse is in range of the follow circle, - /// then a tick outside the range of the follow circle from the head should not be hit. + /// then a tick not within the follow radius from the cursor position should not be hit. /// [Test] - public void TestHitLateInRangeDoesNotHitOutOfRange() + public void TestHitLateInRangeDoesNotHitOutOfRangeTick() { performTest(new List { @@ -293,6 +293,36 @@ namespace osu.Game.Rulesets.Osu.Tests assertSliderJudgement(HitResult.IgnoreHit); } + /// + /// If the head circle is hit and the mouse is in range of the follow circle, + /// then a tick not within the follow radius from the cursor position should not be hit. + /// + [Test] + public void TestHitLateWithEdgeHit() + { + performTest(new List + { + new OsuReplayFrame(time_slider_start + 150, slider_start_position - new Vector2(20), OsuAction.LeftButton), + new OsuReplayFrame(time_slider_end + 150, slider_start_position - new Vector2(20), OsuAction.LeftButton), + }, s => + { + s.Path = new SliderPath(PathType.PERFECT_CURVE, new[] + { + Vector2.Zero, + new Vector2(50, 50), + new Vector2(20, 0), + }); + + s.TickDistanceMultiplier = 0.35f; + s.SliderVelocityMultiplier = 4; + }); + + assertHeadJudgement(HitResult.Meh); + assertTickJudgement(0, HitResult.LargeTickMiss); + assertTailJudgement(HitResult.IgnoreMiss); + assertSliderJudgement(HitResult.IgnoreHit); + } + private void assertHeadJudgement(HitResult result) { AddAssert( diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs index a5d7bdc7db..9feeb6ef14 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Input; @@ -75,8 +76,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (!isMouseInFollowArea(true)) return; + Debug.Assert(screenSpaceMousePosition != null); + + Vector2 mousePositionInSlider = slider.ToLocalSpace(screenSpaceMousePosition.Value) - slider.OriginPosition; + // When the head is hit and the mouse is in the expanded follow area, force a hit on every nested hitobject - // from the start of the slider that is within follow-radius units from the head. + // from the start of the slider that is within the follow area. bool forceMiss = false; @@ -94,9 +99,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables double objectProgress = Math.Clamp((nested.HitObject.StartTime - slider.HitObject.StartTime) / slider.HitObject.Duration, 0, 1); Vector2 objectPosition = slider.HitObject.CurvePositionAt(objectProgress); - // When the first nested object that is further than follow-radius units away from the start of the slider is reached, - // forcefully miss all other nested objects that would otherwise be valid to be hit by this process. - if (forceMiss || objectPosition.LengthSquared > radius * radius) + // When the first nested object that is further outside the follow area is reached, + // forcefully miss all other nested objects that would otherwise be valid to be hit. + // This covers a case of a slider overlapping itself that requires tracking to a tick on an outer edge. + if (forceMiss || (objectPosition - mousePositionInSlider).LengthSquared > radius * radius) { nested.MissForcefully(); forceMiss = true; From 9ae3be817fa77dd142b5292e3d361953319efe07 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 15 Dec 2023 17:40:36 +0900 Subject: [PATCH 1960/4092] Add some text to the test scene showing hits/misses --- .../TestSceneSliderLateHitJudgement.cs | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs index 80e4b04178..2a3655eccd 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs @@ -5,12 +5,17 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics; using osu.Framework.Screens; +using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Graphics.Sprites; using osu.Game.Replays; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Replays; @@ -410,7 +415,21 @@ namespace osu.Game.Rulesets.Osu.Tests { p.ScoreProcessor.NewJudgement += result => { - if (currentPlayer == p) judgementResults.Add(result); + if (currentPlayer == p) + judgementResults.Add(result); + + DrawableHitObject drawableObj = this.ChildrenOfType().Single(h => h.HitObject == result.HitObject); + + var text = new OsuSpriteText + { + Origin = Anchor.Centre, + Position = Content.ToLocalSpace(drawableObj.ToScreenSpace(drawableObj.OriginPosition)) - new Vector2(0, 20), + Text = result.IsHit ? "hit" : "miss" + }; + + Add(text); + + text.FadeOutFromOne(1000).Expire(); }; }; From 9e3b1dbb5926684b151066fed1df14e25cbb26b2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 15 Dec 2023 17:41:22 +0900 Subject: [PATCH 1961/4092] Fix CI inspection --- .../Sections/Maintenance/StableDirectorySelectScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs index 73ac6efcd8..17f09ade4e 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs @@ -46,6 +46,6 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance } private bool shouldUseParentDirectory(DirectoryInfo? info) - => info?.Parent != null && (info?.Name == "Songs" || info?.Name == "Skins"); + => info?.Parent != null && (info.Name == "Songs" || info.Name == "Skins"); } } From 456916f680c608acbd24ed3953e97e630a656551 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Dec 2023 18:02:27 +0900 Subject: [PATCH 1962/4092] Fix column sizing exceeding screen width on tablets --- .../Argon/ManiaArgonSkinTransformer.cs | 10 +---- osu.Game.Rulesets.Mania/UI/ColumnFlow.cs | 45 +++++++++++++++---- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index ca7f84cb4d..7f6540e7b5 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps; @@ -100,16 +99,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon return SkinUtils.As(new Bindable(30)); case LegacyManiaSkinConfigurationLookups.ColumnWidth: - - float width; - bool isSpecialColumn = stage.IsSpecialColumn(columnIndex); - // Best effort until we have better mobile support. - if (RuntimeInfo.IsMobile) - width = 170 * Math.Min(1, 7f / beatmap.TotalColumns) * (isSpecialColumn ? 1.8f : 1); - else - width = 60 * (isSpecialColumn ? 2 : 1); + float width = 60 * (isSpecialColumn ? 2 : 1); return SkinUtils.As(new Bindable(width)); diff --git a/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs b/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs index 0bc0bf4caf..44c318a2c5 100644 --- a/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs +++ b/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs @@ -3,14 +3,17 @@ #nullable disable +using System; using System.Collections.Generic; using System.Linq; +using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Skinning; using osu.Game.Skinning; +using osuTK; namespace osu.Game.Rulesets.Mania.UI { @@ -60,6 +63,12 @@ namespace osu.Game.Rulesets.Mania.UI onSkinChanged(); } + protected override void LoadComplete() + { + base.LoadComplete(); + updateMobileSizing(); + } + private void onSkinChanged() { for (int i = 0; i < stageDefinition.Columns; i++) @@ -77,12 +86,15 @@ namespace osu.Game.Rulesets.Mania.UI new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnWidth, i)) ?.Value; - if (width == null) - // only used by default skin (legacy skins get defaults set in LegacyManiaSkinConfiguration) - columns[i].Width = stageDefinition.IsSpecialColumn(i) ? Column.SPECIAL_COLUMN_WIDTH : Column.COLUMN_WIDTH; - else - columns[i].Width = width.Value; + bool isSpecialColumn = stageDefinition.IsSpecialColumn(i); + + // only used by default skin (legacy skins get defaults set in LegacyManiaSkinConfiguration) + width ??= isSpecialColumn ? Column.SPECIAL_COLUMN_WIDTH : Column.COLUMN_WIDTH; + + columns[i].Width = width.Value; } + + updateMobileSizing(); } /// @@ -92,10 +104,27 @@ namespace osu.Game.Rulesets.Mania.UI /// The content. public void SetContentForColumn(int column, TContent content) => columns[column].Child = content; - public new MarginPadding Padding + private void updateMobileSizing() { - get => base.Padding; - set => base.Padding = value; + if (!IsLoaded) + return; + + for (int i = 0; i < stageDefinition.Columns; i++) + { + // GridContainer+CellContainer containing this stage (gets split up for dual stages). + Vector2 containingCell = this.FindClosestParent().Parent!.DrawSize; + + // Best effort until we have better mobile support. + if (RuntimeInfo.IsMobile) + { + // These numbers are based on mobile phones, aspect ~1.92. + float mobileAdjust = 2.83f * Math.Min(1, 7f / stageDefinition.Columns); + // We should scale it back for cases like tablets which aren't so extreme. + mobileAdjust *= (containingCell.X / containingCell.Y) / 1.92f; + + columns[i].Width *= mobileAdjust; + } + } } protected override void Dispose(bool isDisposing) From 4ad312ef5b8bd4ce392f1a5cbb738da5cb991389 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Dec 2023 19:12:45 +0900 Subject: [PATCH 1963/4092] Update xmldoc for `LegacyComboIncrease` --- osu.Game/Rulesets/Scoring/HitResult.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index 6380b73558..9705421571 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -139,9 +139,12 @@ namespace osu.Game.Rulesets.Scoring /// /// A special result used as a padding value for legacy rulesets. It is a hit type and affects combo, but does not affect the base score (does not affect accuracy). + /// + /// DO NOT USE FOR ANYTHING EVER. /// /// - /// DO NOT USE. + /// This is used when dealing with legacy scores, which historically only have counts stored for 300/100/50/miss. + /// For these scores, we pad the hit statistics with `LegacyComboIncrease` to meet the correct max combo for the score. /// [EnumMember(Value = "legacy_combo_increase")] [Order(99)] From eb5a8284f1200e80a8e9b616e68b8a056c8a84e2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Dec 2023 19:15:25 +0900 Subject: [PATCH 1964/4092] Move mobile check earlier to avoid unnecessary looping --- osu.Game.Rulesets.Mania/UI/ColumnFlow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs b/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs index 44c318a2c5..f28619fb64 100644 --- a/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs +++ b/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs @@ -106,7 +106,7 @@ namespace osu.Game.Rulesets.Mania.UI private void updateMobileSizing() { - if (!IsLoaded) + if (!IsLoaded || !RuntimeInfo.IsMobile) return; for (int i = 0; i < stageDefinition.Columns; i++) From e8f3e52c9e0bccbe6f67f4904e4db0a45bd29f4b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Dec 2023 19:17:48 +0900 Subject: [PATCH 1965/4092] Fix nullref failure in tests --- osu.Game.Rulesets.Mania/UI/ColumnFlow.cs | 31 ++++++++++++------------ 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs b/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs index f28619fb64..3ecd14ce81 100644 --- a/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs +++ b/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs @@ -109,22 +109,23 @@ namespace osu.Game.Rulesets.Mania.UI if (!IsLoaded || !RuntimeInfo.IsMobile) return; + // GridContainer+CellContainer containing this stage (gets split up for dual stages). + Vector2? containingCell = this.FindClosestParent()?.Parent?.DrawSize; + + // Will be null in tests. + if (containingCell == null) + return; + + float aspectRatio = containingCell.Value.X / containingCell.Value.Y; + + // These numbers are based on mobile phones, aspect ~1.92. + float mobileAdjust = 2.83f * Math.Min(1, 7f / stageDefinition.Columns); + // We should scale it back for cases like tablets which aren't so extreme. + mobileAdjust *= aspectRatio / 1.92f; + + // Best effort until we have better mobile support. for (int i = 0; i < stageDefinition.Columns; i++) - { - // GridContainer+CellContainer containing this stage (gets split up for dual stages). - Vector2 containingCell = this.FindClosestParent().Parent!.DrawSize; - - // Best effort until we have better mobile support. - if (RuntimeInfo.IsMobile) - { - // These numbers are based on mobile phones, aspect ~1.92. - float mobileAdjust = 2.83f * Math.Min(1, 7f / stageDefinition.Columns); - // We should scale it back for cases like tablets which aren't so extreme. - mobileAdjust *= (containingCell.X / containingCell.Y) / 1.92f; - - columns[i].Width *= mobileAdjust; - } - } + columns[i].Width *= mobileAdjust; } protected override void Dispose(bool isDisposing) From 94f63d604401ad7b2a9153da1b944d6986045e51 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Dec 2023 19:18:53 +0900 Subject: [PATCH 1966/4092] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index b0ab8e445c..807e3ede2a 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + From b384c9f9388f10ca2447a47e5b0fa75000e75f16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 15 Dec 2023 15:42:19 +0100 Subject: [PATCH 1967/4092] Extract common method for determining stable import usability of directory --- osu.Game/Database/LegacyImportManager.cs | 46 +++++++++++++++++++ .../FirstRunSetup/ScreenImportFromStable.cs | 4 +- .../StableDirectorySelectScreen.cs | 26 +++++------ 3 files changed, 59 insertions(+), 17 deletions(-) diff --git a/osu.Game/Database/LegacyImportManager.cs b/osu.Game/Database/LegacyImportManager.cs index 20738f859e..7e1641d16f 100644 --- a/osu.Game/Database/LegacyImportManager.cs +++ b/osu.Game/Database/LegacyImportManager.cs @@ -3,6 +3,9 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; using osu.Framework; @@ -54,6 +57,49 @@ namespace osu.Game.Database public void UpdateStorage(string stablePath) => cachedStorage = new StableStorage(stablePath, gameHost as DesktopGameHost); + /// + /// Checks whether a valid location to run a stable import from can be determined starting from the supplied . + /// + /// The directory to check for stable import eligibility. + /// + /// If the return value is , + /// this parameter will contain the to use as the root directory for importing. + /// + public bool IsUsableForStableImport(DirectoryInfo? directory, [NotNullWhen(true)] out DirectoryInfo? stableRoot) + { + if (directory == null) + { + stableRoot = null; + return false; + } + + // A full stable installation will have a configuration file present. + // This is the best case scenario, as it may contain a custom beatmap directory we need to traverse to. + if (directory.GetFiles(@"osu!.*.cfg").Any()) + { + stableRoot = directory; + return true; + } + + // The user may only have their songs or skins folders left. + // We still want to allow them to import based on this. + if (directory.GetDirectories(@"Songs").Any() || directory.GetDirectories(@"Skins").Any()) + { + stableRoot = directory; + return true; + } + + // The user may have traversed *inside* their songs or skins folders. + if (directory.Parent != null && (directory.Name == @"Songs" || directory.Name == @"Skins")) + { + stableRoot = directory.Parent; + return true; + } + + stableRoot = null; + return false; + } + public bool CheckSongsFolderHardLinkAvailability() { var stableStorage = GetCurrentStableStorage(); diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 23f3b3e1af..185a47c371 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -269,11 +269,11 @@ namespace osu.Game.Overlays.FirstRunSetup if (directory.OldValue?.FullName == directory.NewValue.FullName) return; - if (directory.NewValue?.GetFiles(@"osu!.*.cfg").Any() ?? false) + if (legacyImportManager.IsUsableForStableImport(directory.NewValue, out var stableRoot)) { this.HidePopover(); - string path = directory.NewValue.FullName; + string path = stableRoot.FullName; legacyImportManager.UpdateStorage(path); Current.Value = path; diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs index 17f09ade4e..3f12b9c0df 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/StableDirectorySelectScreen.cs @@ -1,11 +1,13 @@ // 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 System.Linq; using System.Threading.Tasks; +using osu.Framework.Allocation; using osu.Framework.Localisation; using osu.Framework.Screens; +using osu.Game.Database; namespace osu.Game.Overlays.Settings.Sections.Maintenance { @@ -13,18 +15,12 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { private readonly TaskCompletionSource taskCompletionSource; + [Resolved] + private LegacyImportManager legacyImportManager { get; set; } = null!; + protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled; - protected override bool IsValidDirectory(DirectoryInfo? info) => - // A full stable installation will have a configuration file present. - // This is the best case scenario, as it may contain a custom beatmap directory we need to traverse to. - info?.GetFiles("osu!.*.cfg").Any() == true || - // The user may only have their songs or skins folders left. - // We still want to allow them to import based on this. - info?.GetDirectories("Songs").Any() == true || - info?.GetDirectories("Skins").Any() == true || - // The user may have traverse *inside* their songs or skins folders. - shouldUseParentDirectory(info); + protected override bool IsValidDirectory(DirectoryInfo? info) => legacyImportManager.IsUsableForStableImport(info, out _); public override LocalisableString HeaderText => "Please select your osu!stable install location"; @@ -35,7 +31,10 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance protected override void OnSelection(DirectoryInfo directory) { - taskCompletionSource.TrySetResult(shouldUseParentDirectory(directory) ? directory.Parent!.FullName : directory.FullName); + if (!legacyImportManager.IsUsableForStableImport(directory, out var stableRoot)) + throw new InvalidOperationException($@"{nameof(OnSelection)} was called on an invalid directory. This should never happen."); + + taskCompletionSource.TrySetResult(stableRoot.FullName); this.Exit(); } @@ -44,8 +43,5 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance taskCompletionSource.TrySetCanceled(); return base.OnExiting(e); } - - private bool shouldUseParentDirectory(DirectoryInfo? info) - => info?.Parent != null && (info.Name == "Songs" || info.Name == "Skins"); } } From acb7016156c7dd3411ed1d70154eafaab970e57e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 15 Dec 2023 23:53:26 +0900 Subject: [PATCH 1968/4092] Remove unused using --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs index 2a3655eccd..d12020db59 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs @@ -15,7 +15,6 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Replays; From 91f4123aa79b05c0a3e56a4025b54f993e001b98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 15 Dec 2023 16:00:29 +0100 Subject: [PATCH 1969/4092] Fix first run locator stable locator directory desyncing between display & popover --- .../FirstRunSetup/ScreenImportFromStable.cs | 45 ++++++++++++------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 185a47c371..24ac5e72e8 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -244,6 +244,8 @@ namespace osu.Game.Overlays.FirstRunSetup [Resolved(canBeNull: true)] // Can't really be null but required to handle potential of disposal before DI completes. private OsuGameBase? game { get; set; } + private bool changingDirectory; + protected override void LoadComplete() { base.LoadComplete(); @@ -259,24 +261,37 @@ namespace osu.Game.Overlays.FirstRunSetup private void onDirectorySelected(ValueChangedEvent directory) { - if (directory.NewValue == null) - { - Current.Value = string.Empty; + if (changingDirectory) return; + + try + { + changingDirectory = true; + + if (directory.NewValue == null) + { + Current.Value = string.Empty; + return; + } + + // DirectorySelectors can trigger a noop value changed, but `DirectoryInfo` equality doesn't catch this. + if (directory.OldValue?.FullName == directory.NewValue.FullName) + return; + + if (legacyImportManager.IsUsableForStableImport(directory.NewValue, out var stableRoot)) + { + this.HidePopover(); + + string path = stableRoot.FullName; + + legacyImportManager.UpdateStorage(path); + Current.Value = path; + currentDirectory.Value = stableRoot; + } } - - // DirectorySelectors can trigger a noop value changed, but `DirectoryInfo` equality doesn't catch this. - if (directory.OldValue?.FullName == directory.NewValue.FullName) - return; - - if (legacyImportManager.IsUsableForStableImport(directory.NewValue, out var stableRoot)) + finally { - this.HidePopover(); - - string path = stableRoot.FullName; - - legacyImportManager.UpdateStorage(path); - Current.Value = path; + changingDirectory = false; } } From 432ce275c4dad16cc1ad848bfbe4900445d0c8af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 15 Dec 2023 20:43:18 +0100 Subject: [PATCH 1970/4092] Explain magic constants better --- osu.Game.Rulesets.Mania/UI/ColumnFlow.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs b/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs index 3ecd14ce81..8734f8ac8a 100644 --- a/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs +++ b/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs @@ -118,8 +118,9 @@ namespace osu.Game.Rulesets.Mania.UI float aspectRatio = containingCell.Value.X / containingCell.Value.Y; - // These numbers are based on mobile phones, aspect ~1.92. + // 2.83 is a mostly arbitrary scale-up (170 / 60, based on original implementation for argon) float mobileAdjust = 2.83f * Math.Min(1, 7f / stageDefinition.Columns); + // 1.92 is a "reference" mobile screen aspect ratio for phones. // We should scale it back for cases like tablets which aren't so extreme. mobileAdjust *= aspectRatio / 1.92f; From f07771f59b19c5ac3c9d9709bb32d3baf6fe4138 Mon Sep 17 00:00:00 2001 From: clayton Date: Fri, 15 Dec 2023 22:41:55 -0800 Subject: [PATCH 1971/4092] Fix fallback column colors for legacy split stage mania skins --- .../Skinning/Legacy/LegacyManiaColumnElement.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaColumnElement.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaColumnElement.cs index 7e3fb0438c..3a69142b3c 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaColumnElement.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaColumnElement.cs @@ -34,7 +34,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy FallbackColumnIndex = "S"; else { - int distanceToEdge = Math.Min(Column.Index, (stage.Columns - 1) - Column.Index); + // Account for cases like dual-stage (assume that all stages have the same column count for now). + int columnInStage = Column.Index % stage.Columns; + int distanceToEdge = Math.Min(columnInStage, (stage.Columns - 1) - columnInStage); FallbackColumnIndex = distanceToEdge % 2 == 0 ? "1" : "2"; } } From d7aca2f64143d75d11fb09f2acfbb10709396206 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 17 Dec 2023 19:27:03 +0900 Subject: [PATCH 1972/4092] Add IApplicableHealthProcessor --- .../Mods/IApplicableHealthProcessor.cs | 18 ++++++++++++++++++ osu.Game/Screens/Play/Player.cs | 3 ++- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Rulesets/Mods/IApplicableHealthProcessor.cs diff --git a/osu.Game/Rulesets/Mods/IApplicableHealthProcessor.cs b/osu.Game/Rulesets/Mods/IApplicableHealthProcessor.cs new file mode 100644 index 0000000000..e16faa9595 --- /dev/null +++ b/osu.Game/Rulesets/Mods/IApplicableHealthProcessor.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.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// Interface for a that provides its own health processor. + /// + public interface IApplicableHealthProcessor + { + /// + /// Creates the . + /// + HealthProcessor CreateHealthProcessor(double drainStartTime); + } +} diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 1c97efcff7..cc08079d88 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -227,7 +227,8 @@ namespace osu.Game.Screens.Play dependencies.CacheAs(ScoreProcessor); - HealthProcessor = ruleset.CreateHealthProcessor(playableBeatmap.HitObjects[0].StartTime); + HealthProcessor = gameplayMods.OfType().FirstOrDefault()?.CreateHealthProcessor(playableBeatmap.HitObjects[0].StartTime); + HealthProcessor ??= ruleset.CreateHealthProcessor(playableBeatmap.HitObjects[0].StartTime); HealthProcessor.ApplyBeatmap(playableBeatmap); dependencies.CacheAs(HealthProcessor); From 4b9aefa6f2d7b7ad5a2fdc31685a6a900c55d444 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 17 Dec 2023 19:33:02 +0900 Subject: [PATCH 1973/4092] Change osu ruleset to use new HP algorithm by default --- .../TestSceneOsuHealthProcessor.cs | 8 ++++---- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 7 ++++++- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 -- ...{OsuHealthProcessor.cs => OsuLegacyHealthProcessor.cs} | 4 ++-- osu.Game/Rulesets/Mods/IApplicableHealthProcessor.cs | 4 ++-- 5 files changed, 14 insertions(+), 11 deletions(-) rename osu.Game.Rulesets.Osu/Scoring/{OsuHealthProcessor.cs => OsuLegacyHealthProcessor.cs} (95%) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuHealthProcessor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuHealthProcessor.cs index 16f28c0212..83c818c20d 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuHealthProcessor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuHealthProcessor.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestNoBreak() { - OsuHealthProcessor hp = new OsuHealthProcessor(-1000); + OsuLegacyHealthProcessor hp = new OsuLegacyHealthProcessor(-1000); hp.ApplyBeatmap(new Beatmap { HitObjects = @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestSingleBreak() { - OsuHealthProcessor hp = new OsuHealthProcessor(-1000); + OsuLegacyHealthProcessor hp = new OsuLegacyHealthProcessor(-1000); hp.ApplyBeatmap(new Beatmap { HitObjects = @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestOverlappingBreak() { - OsuHealthProcessor hp = new OsuHealthProcessor(-1000); + OsuLegacyHealthProcessor hp = new OsuLegacyHealthProcessor(-1000); hp.ApplyBeatmap(new Beatmap { HitObjects = @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestSequentialBreak() { - OsuHealthProcessor hp = new OsuHealthProcessor(-1000); + OsuLegacyHealthProcessor hp = new OsuLegacyHealthProcessor(-1000); hp.ApplyBeatmap(new Beatmap { HitObjects = diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index f20f95b384..10d7af5e58 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -18,7 +18,7 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModClassic : ModClassic, IApplicableToHitObject, IApplicableToDrawableHitObject, IApplicableToDrawableRuleset + public class OsuModClassic : ModClassic, IApplicableToHitObject, IApplicableToDrawableHitObject, IApplicableToDrawableRuleset, IApplicableHealthProcessor { public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModStrictTracking)).ToArray(); @@ -34,6 +34,9 @@ namespace osu.Game.Rulesets.Osu.Mods [SettingSource("Fade out hit circles earlier", "Make hit circles fade out into a miss, rather than after it.")] public Bindable FadeHitCircleEarly { get; } = new Bindable(true); + [SettingSource("Classic health", "More closely resembles the original HP drain mechanics.")] + public Bindable ClassicHealth { get; } = new Bindable(true); + private bool usingHiddenFading; public void ApplyToHitObject(HitObject hitObject) @@ -115,5 +118,7 @@ namespace osu.Game.Rulesets.Osu.Mods } }; } + + public HealthProcessor? CreateHealthProcessor(double drainStartTime) => ClassicHealth.Value ? new OsuLegacyHealthProcessor(drainStartTime) : null; } } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 35cbfa3790..e53f20277b 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -48,8 +48,6 @@ namespace osu.Game.Rulesets.Osu public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(); - public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new OsuHealthProcessor(drainStartTime); - public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap, this); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap); diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuLegacyHealthProcessor.cs similarity index 95% rename from osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs rename to osu.Game.Rulesets.Osu/Scoring/OsuLegacyHealthProcessor.cs index 7025a7be65..e383e82b86 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuLegacyHealthProcessor.cs @@ -10,9 +10,9 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Scoring { - public partial class OsuHealthProcessor : LegacyDrainingHealthProcessor + public partial class OsuLegacyHealthProcessor : LegacyDrainingHealthProcessor { - public OsuHealthProcessor(double drainStartTime) + public OsuLegacyHealthProcessor(double drainStartTime) : base(drainStartTime) { } diff --git a/osu.Game/Rulesets/Mods/IApplicableHealthProcessor.cs b/osu.Game/Rulesets/Mods/IApplicableHealthProcessor.cs index e16faa9595..be46828069 100644 --- a/osu.Game/Rulesets/Mods/IApplicableHealthProcessor.cs +++ b/osu.Game/Rulesets/Mods/IApplicableHealthProcessor.cs @@ -11,8 +11,8 @@ namespace osu.Game.Rulesets.Mods public interface IApplicableHealthProcessor { /// - /// Creates the . + /// Creates the . May be null to use the ruleset default. /// - HealthProcessor CreateHealthProcessor(double drainStartTime); + HealthProcessor? CreateHealthProcessor(double drainStartTime); } } From 10610d3387acd1863c406723d20e9557459def66 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 17 Dec 2023 19:33:38 +0900 Subject: [PATCH 1974/4092] Rename test scene --- ...uHealthProcessor.cs => TestSceneOsuLegacyHealthProcessor.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Rulesets.Osu.Tests/{TestSceneOsuHealthProcessor.cs => TestSceneOsuLegacyHealthProcessor.cs} (98%) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuHealthProcessor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuLegacyHealthProcessor.cs similarity index 98% rename from osu.Game.Rulesets.Osu.Tests/TestSceneOsuHealthProcessor.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneOsuLegacyHealthProcessor.cs index 83c818c20d..a7ae06a9ce 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuHealthProcessor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuLegacyHealthProcessor.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Osu.Scoring; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestSceneOsuHealthProcessor + public class TestSceneOsuLegacyHealthProcessor { [Test] public void TestNoBreak() From f77884b62f0a8c2b8b058c9e1180c21bd25cbdba Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 17 Dec 2023 19:57:48 +0900 Subject: [PATCH 1975/4092] Only hit passed-through ticks if none were missed --- .../TestSceneSliderLateHitJudgement.cs | 23 ++------------- .../Objects/Drawables/SliderInputManager.cs | 29 ++++++++++++++----- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs index d12020db59..54140e4d49 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs @@ -220,7 +220,7 @@ namespace osu.Game.Rulesets.Osu.Tests /// If any hitobject does not meet this criteria, ALL hitobjects after that one should be missed. /// [Test] - public void TestHitLateInRangeDoesNotHitAfterAnyOutOfRange() + public void TestHitLateDoesNotHitTicksIfAnyOutOfRange() { performTest(new List { @@ -241,25 +241,8 @@ namespace osu.Game.Rulesets.Osu.Tests assertHeadJudgement(HitResult.Meh); - // The first few ticks that are in the follow range of the head should be hit. - assertTickJudgement(0, HitResult.LargeTickHit); // This tick is hidden under the slider head :( - assertTickJudgement(1, HitResult.LargeTickHit); - assertTickJudgement(2, HitResult.LargeTickHit); - - // Every other tick should be missed - assertTickJudgement(3, HitResult.LargeTickMiss); - assertTickJudgement(4, HitResult.LargeTickMiss); - assertTickJudgement(5, HitResult.LargeTickMiss); - assertTickJudgement(6, HitResult.LargeTickMiss); - assertTickJudgement(7, HitResult.LargeTickMiss); - assertTickJudgement(8, HitResult.LargeTickMiss); - assertTickJudgement(9, HitResult.LargeTickMiss); - assertTickJudgement(10, HitResult.LargeTickMiss); - - // In particular, these three are in the follow range of the head, but should not be hit - // because the slider was at some point outside the follow range of the head. - assertTickJudgement(11, HitResult.LargeTickMiss); - assertTickJudgement(12, HitResult.LargeTickMiss); + // At least one tick was out of range, so they all should be missed. + assertAllTickJudgements(HitResult.LargeTickMiss); // This particular test actually starts tracking the slider just before the end, so the tail should be hit because of its leniency. assertTailJudgement(HitResult.LargeTickHit); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs index 9feeb6ef14..e71f4fcd48 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs @@ -80,10 +80,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Vector2 mousePositionInSlider = slider.ToLocalSpace(screenSpaceMousePosition.Value) - slider.OriginPosition; - // When the head is hit and the mouse is in the expanded follow area, force a hit on every nested hitobject - // from the start of the slider that is within the follow area. + // When the head is hit late: + // - If the cursor has at all times been within range of the expanded follow area, hit all nested objects that have been passed through. + // - If the cursor has at some point left the expanded follow area, miss those nested objects instead. - bool forceMiss = false; + bool allTicksInRange = true; foreach (var nested in slider.NestedHitObjects.OfType()) { @@ -102,13 +103,27 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // When the first nested object that is further outside the follow area is reached, // forcefully miss all other nested objects that would otherwise be valid to be hit. // This covers a case of a slider overlapping itself that requires tracking to a tick on an outer edge. - if (forceMiss || (objectPosition - mousePositionInSlider).LengthSquared > radius * radius) + if ((objectPosition - mousePositionInSlider).LengthSquared > radius * radius) { - nested.MissForcefully(); - forceMiss = true; + allTicksInRange = false; + break; } - else + } + + foreach (var nested in slider.NestedHitObjects.OfType()) + { + // Skip nested objects that are already judged. + if (nested.Judged) + continue; + + // Stop the process when a nested object is reached that can't be hit before the current time. + if (nested.HitObject.StartTime > Time.Current) + break; + + if (allTicksInRange) nested.HitForcefully(); + else + nested.MissForcefully(); } // Enable tracking, since the mouse is within the follow area (if it were expanded). From fbe48d7be84874d902d5559f723379bc5c9c395a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 17 Dec 2023 20:11:15 +0900 Subject: [PATCH 1976/4092] Fix tail being missed too early --- osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs index e71f4fcd48..996a477153 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs @@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (slider.Tracking.Value) nestedObject.HitForcefully(); - else + else if (timeOffset >= 0) nestedObject.MissForcefully(); } From 9b02bd712b19b34bd8d3813ce9a2c700063267c7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 17 Dec 2023 20:12:02 +0900 Subject: [PATCH 1977/4092] Only track if in slider ball after any ticks missed --- .../TestSceneSliderLateHitJudgement.cs | 31 +++++++++++++++++++ .../Objects/Drawables/SliderInputManager.cs | 6 ++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs index 54140e4d49..78633369c7 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs @@ -280,6 +280,37 @@ namespace osu.Game.Rulesets.Osu.Tests assertSliderJudgement(HitResult.IgnoreHit); } + /// + /// Same as except the tracking is limited to the ball + /// because the tick was missed. + /// + [Test] + public void TestHitLateInRangeDoesNotHitOutOfRangeTickAndTrackingLimitedToBall() + { + performTest(new List + { + new OsuReplayFrame(time_slider_start + 150, slider_start_position, OsuAction.LeftButton), + new OsuReplayFrame(time_slider_end + 150, slider_start_position, OsuAction.LeftButton), + }, s => + { + s.Path = new SliderPath(PathType.PERFECT_CURVE, new[] + { + Vector2.Zero, + new Vector2(50, 50), + new Vector2(20, 0), + }); + + s.TickDistanceMultiplier = 0.25f; + s.SliderVelocityMultiplier = 3; + }); + + assertHeadJudgement(HitResult.Meh); + assertTickJudgement(0, HitResult.LargeTickMiss); + assertTickJudgement(1, HitResult.LargeTickMiss); + assertTailJudgement(HitResult.LargeTickHit); + assertSliderJudgement(HitResult.IgnoreHit); + } + /// /// If the head circle is hit and the mouse is in range of the follow circle, /// then a tick not within the follow radius from the cursor position should not be hit. diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs index 996a477153..a497c04894 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs @@ -126,8 +126,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables nested.MissForcefully(); } - // Enable tracking, since the mouse is within the follow area (if it were expanded). - updateTracking(true); + // If all ticks were hit so far, enable tracking the full extent. + // If any ticks were missed, assume tracking would've broken at some point, and should only activate if the cursor is within the slider ball. + // For the second case, this may be the last chance we have to enable tracking before other objects get judged, otherwise the same would normally happen via Update(). + updateTracking(allTicksInRange || isMouseInFollowArea(false)); } public void TryJudgeNestedObject(DrawableOsuHitObject nestedObject, double timeOffset) From fddfa33e4982a87b2b27302c0dcd057526827412 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 17 Dec 2023 20:19:17 +0900 Subject: [PATCH 1978/4092] Fix 1-frame issues due to referencing external value --- osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs index a497c04894..d698ba56c4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs @@ -163,7 +163,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (!slider.HeadCircle.Judged) { - if (slider.Tracking.Value) + if (Tracking) { // Attempt to preserve correct ordering of judgements as best we can by forcing an un-judged head to be missed when the user has clearly skipped it. slider.HeadCircle.MissForcefully(); @@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - if (slider.Tracking.Value) + if (Tracking) nestedObject.HitForcefully(); else if (timeOffset >= 0) nestedObject.MissForcefully(); From 2b33aec12418e17f462730a4542f81e0d68cfe57 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 17 Dec 2023 21:26:48 +0900 Subject: [PATCH 1979/4092] Require slider head to be judged before ticks --- .../TestSceneSliderLateHitJudgement.cs | 80 ++++++++++++++++--- .../Objects/Drawables/SliderInputManager.cs | 13 +-- 2 files changed, 68 insertions(+), 25 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs index 78633369c7..e1797e877e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs @@ -341,6 +341,45 @@ namespace osu.Game.Rulesets.Osu.Tests assertSliderJudgement(HitResult.IgnoreHit); } + /// + /// Late hit and release on each slider head of a slider stream. + /// + [Test] + public void TestLateHitSliderStream() + { + var beatmap = new Beatmap(); + + for (int i = 0; i < 20; i++) + { + beatmap.HitObjects.Add(new Slider + { + StartTime = time_slider_start + 75 * i, // 200BPM @ 1/4 + Position = new Vector2(256 - slider_path_length / 2, 192), + TickDistanceMultiplier = 3, + Path = new SliderPath(PathType.LINEAR, new[] + { + Vector2.Zero, + new Vector2(20, 0), + }), + }); + } + + var replay = new List(); + + for (int i = 0; i < 20; i++) + { + replay.Add(new OsuReplayFrame(time_slider_start + 75 * i + 75, slider_start_position, i % 2 == 0 ? OsuAction.LeftButton : OsuAction.RightButton)); + replay.Add(new OsuReplayFrame(time_slider_start + 75 * i + 140, slider_start_position)); + } + + performTest(replay, beatmap); + + assertHeadJudgement(HitResult.Meh); + assertTickJudgement(0, HitResult.LargeTickMiss); + assertTailJudgement(HitResult.IgnoreMiss); + assertSliderJudgement(HitResult.IgnoreHit); + } + private void assertHeadJudgement(HitResult result) { AddAssert( @@ -406,21 +445,36 @@ namespace osu.Game.Rulesets.Osu.Tests adjustSliderFunc?.Invoke(slider); + var beatmap = new Beatmap + { + HitObjects = { slider }, + BeatmapInfo = + { + Difficulty = new BeatmapDifficulty + { + SliderMultiplier = 4, + SliderTickRate = 3 + }, + Ruleset = new OsuRuleset().RulesetInfo, + } + }; + + performTest(frames, beatmap); + } + + private void performTest(List frames, Beatmap beatmap) + { + beatmap.BeatmapInfo.Ruleset = new OsuRuleset().RulesetInfo; + beatmap.BeatmapInfo.StackLeniency = 0; + beatmap.BeatmapInfo.Difficulty = new BeatmapDifficulty + { + SliderMultiplier = 4, + SliderTickRate = 3, + }; + AddStep("load player", () => { - Beatmap.Value = CreateWorkingBeatmap(new Beatmap - { - HitObjects = { slider }, - BeatmapInfo = - { - Difficulty = new BeatmapDifficulty - { - SliderMultiplier = 4, - SliderTickRate = 3 - }, - Ruleset = new OsuRuleset().RulesetInfo, - } - }); + Beatmap.Value = CreateWorkingBeatmap(beatmap); var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } }); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs index d698ba56c4..8aa982783e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs @@ -162,18 +162,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } if (!slider.HeadCircle.Judged) - { - if (Tracking) - { - // Attempt to preserve correct ordering of judgements as best we can by forcing an un-judged head to be missed when the user has clearly skipped it. - slider.HeadCircle.MissForcefully(); - } - else - { - // Don't judge this object as a miss before the head has been judged, to allow the head to be hit late. - return; - } - } + return; if (Tracking) nestedObject.HitForcefully(); From 04d542105f1518119f18f7d3b160802d7d468e67 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 17 Dec 2023 21:52:21 +0900 Subject: [PATCH 1980/4092] Fix assertions --- .../TestSceneSliderLateHitJudgement.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs index e1797e877e..6ba9c723da 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs @@ -374,10 +374,10 @@ namespace osu.Game.Rulesets.Osu.Tests performTest(replay, beatmap); - assertHeadJudgement(HitResult.Meh); - assertTickJudgement(0, HitResult.LargeTickMiss); - assertTailJudgement(HitResult.IgnoreMiss); - assertSliderJudgement(HitResult.IgnoreHit); + AddAssert( + $"all heads = {HitResult.Ok}", + () => judgementResults.Where(r => r.HitObject is SliderHeadCircle).Select(r => r.Type), + () => Has.All.EqualTo(HitResult.Ok)); } private void assertHeadJudgement(HitResult result) From 30116512ca2dcd61a63813cab04cfc9d9bacb734 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 18 Dec 2023 12:01:51 +0900 Subject: [PATCH 1981/4092] Populate MaxCombo scoring attrib for non-osu rulesets --- .../Difficulty/CatchLegacyScoreSimulator.cs | 1 + .../Difficulty/ManiaLegacyScoreSimulator.cs | 6 +++++- .../Difficulty/TaikoLegacyScoreSimulator.cs | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyScoreSimulator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyScoreSimulator.cs index ed27e11208..7a84d9245d 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyScoreSimulator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyScoreSimulator.cs @@ -75,6 +75,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty attributes.BonusScoreRatio = legacyBonusScore == 0 ? 0 : (double)standardisedBonusScore / legacyBonusScore; attributes.BonusScore = legacyBonusScore; + attributes.MaxCombo = combo; return attributes; } diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaLegacyScoreSimulator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaLegacyScoreSimulator.cs index ddb4b868a3..d9fd96ac6a 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaLegacyScoreSimulator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaLegacyScoreSimulator.cs @@ -15,7 +15,11 @@ namespace osu.Game.Rulesets.Mania.Difficulty { public LegacyScoreAttributes Simulate(IWorkingBeatmap workingBeatmap, IBeatmap playableBeatmap) { - return new LegacyScoreAttributes { ComboScore = 1000000 }; + return new LegacyScoreAttributes + { + ComboScore = 1000000, + MaxCombo = 0 // Max combo is mod-dependent, so any value here is insufficient. + }; } public double GetLegacyScoreMultiplier(IReadOnlyList mods, LegacyBeatmapConversionDifficultyInfo difficulty) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs index 1db44592b8..a8ed056c89 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs @@ -76,6 +76,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty attributes.BonusScoreRatio = legacyBonusScore == 0 ? 0 : (double)standardisedBonusScore / legacyBonusScore; attributes.BonusScore = legacyBonusScore; + attributes.MaxCombo = combo; return attributes; } From f84c1815734d5aa7a921a0feacc73611f1568c51 Mon Sep 17 00:00:00 2001 From: clayton Date: Sun, 17 Dec 2023 23:47:50 -0800 Subject: [PATCH 1982/4092] Don't convert TaikoModRandom to/from legacy mods --- .../TaikoLegacyModConversionTest.cs | 1 - osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 13 ------------- 2 files changed, 14 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoLegacyModConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoLegacyModConversionTest.cs index 5f7a78ddf1..c15dc17ae4 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoLegacyModConversionTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoLegacyModConversionTest.cs @@ -25,7 +25,6 @@ namespace osu.Game.Rulesets.Taiko.Tests new object[] { LegacyMods.HalfTime, new[] { typeof(TaikoModHalfTime) } }, new object[] { LegacyMods.Flashlight, new[] { typeof(TaikoModFlashlight) } }, new object[] { LegacyMods.Autoplay, new[] { typeof(TaikoModAutoplay) } }, - new object[] { LegacyMods.Random, new[] { typeof(TaikoModRandom) } }, new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(TaikoModHardRock), typeof(TaikoModDoubleTime) } }, new object[] { LegacyMods.ScoreV2, new[] { typeof(ModScoreV2) } }, }; diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 9d34a34fce..ad3b7b09f7 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -115,23 +115,10 @@ namespace osu.Game.Rulesets.Taiko if (mods.HasFlagFast(LegacyMods.Relax)) yield return new TaikoModRelax(); - if (mods.HasFlagFast(LegacyMods.Random)) - yield return new TaikoModRandom(); - if (mods.HasFlagFast(LegacyMods.ScoreV2)) yield return new ModScoreV2(); } - public override LegacyMods ConvertToLegacyMods(Mod[] mods) - { - var value = base.ConvertToLegacyMods(mods); - - if (mods.OfType().Any()) - value |= LegacyMods.Random; - - return value; - } - public override IEnumerable GetModsFor(ModType type) { switch (type) From ef230884a87a51a25b9380193a13b94ff8144ac0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 18 Dec 2023 09:03:01 +0100 Subject: [PATCH 1983/4092] Fix collections dropdown crashing during storage migration Closes https://github.com/ppy/osu/issues/25815. `CollectionDropdown.collectionsChanged()` was assuming that if it received `null` changes, then it must mean that the change subscription is being initialised and the `filters` list will not contain any items. However, that is not the only circumstance wherein a realm subscription can fire with `null` changes; that can also happen after the main realm instance gets recycled via the notification registration flow: https://github.com/ppy/osu/blob/2f28a92f0a03c8aed6fac2ec7fb41f7ed865f798/osu.Game/Database/RealmAccess.cs#L545-L549 https://github.com/ppy/osu/blob/2f28a92f0a03c8aed6fac2ec7fb41f7ed865f798/osu.Game/Database/RealmAccess.cs#L1228-L1251 Therefore, to fix the crash, just ensure that the list is cleared every time. --- osu.Game/Collections/CollectionDropdown.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Collections/CollectionDropdown.cs b/osu.Game/Collections/CollectionDropdown.cs index e43d8f4b02..8d83ed3ec9 100644 --- a/osu.Game/Collections/CollectionDropdown.cs +++ b/osu.Game/Collections/CollectionDropdown.cs @@ -66,6 +66,7 @@ namespace osu.Game.Collections { if (changes == null) { + filters.Clear(); filters.Add(allBeatmapsItem); filters.AddRange(collections.Select(c => new CollectionFilterMenuItem(c.ToLive(realm)))); if (ShowManageCollectionsItem) From 7462a9f4ab84997dfd550f8b3025f5630c72beab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Dec 2023 18:17:47 +0900 Subject: [PATCH 1984/4092] Add helper method to handle progress notifications for background jobs --- osu.Game/BackgroundDataStoreProcessor.cs | 51 +++++++++++++++++++----- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/osu.Game/BackgroundDataStoreProcessor.cs b/osu.Game/BackgroundDataStoreProcessor.cs index 10cc13dc29..794d534f10 100644 --- a/osu.Game/BackgroundDataStoreProcessor.cs +++ b/osu.Game/BackgroundDataStoreProcessor.cs @@ -279,20 +279,17 @@ namespace osu.Game if (scoreIds.Count == 0) return; - ProgressNotification notification = new ProgressNotification { State = ProgressNotificationState.Active }; - - notificationOverlay?.Post(notification); + var notification = showProgressNotification("Upgrading scores to new scoring algorithm", "scores have been upgraded to the new scoring algorithm"); int processedCount = 0; int failedCount = 0; foreach (var id in scoreIds) { - if (notification.State == ProgressNotificationState.Cancelled) + if (notification?.State == ProgressNotificationState.Cancelled) break; - notification.Text = $"Upgrading scores to new scoring algorithm ({processedCount} of {scoreIds.Count})"; - notification.Progress = (float)processedCount / scoreIds.Count; + updateNotificationProgress(notification, processedCount, scoreIds.Count); sleepIfRequired(); @@ -325,24 +322,58 @@ namespace osu.Game } } - if (processedCount == scoreIds.Count) + completeNotification(notification, processedCount, scoreIds.Count, failedCount); + } + + private void updateNotificationProgress(ProgressNotification? notification, int processedCount, int totalCount) + { + if (notification == null) + return; + + notification.Text = notification.Text.ToString().Split('(').First().TrimEnd() + $" ({processedCount} of {totalCount})"; + notification.Progress = (float)processedCount / totalCount; + } + + private void completeNotification(ProgressNotification? notification, int processedCount, int totalCount, int? failedCount = null) + { + if (notification == null) + return; + + if (processedCount == totalCount) { - notification.CompletionText = $"{processedCount} score(s) have been upgraded to the new scoring algorithm"; + notification.CompletionText = $"{processedCount} {notification.CompletionText}"; notification.Progress = 1; notification.State = ProgressNotificationState.Completed; } else { - notification.Text = $"{processedCount} of {scoreIds.Count} score(s) have been upgraded to the new scoring algorithm."; + notification.Text = $"{processedCount} of {totalCount} {notification.CompletionText}"; // We may have arrived here due to user cancellation or completion with failures. if (failedCount > 0) - notification.Text += $" Check logs for issues with {failedCount} failed upgrades."; + notification.Text += $" Check logs for issues with {failedCount} failed items."; notification.State = ProgressNotificationState.Cancelled; } } + private ProgressNotification? showProgressNotification(string running, string completed) + { + if (notificationOverlay == null) + return null; + + ProgressNotification notification = new ProgressNotification + { + Text = running, + CompletionText = completed, + State = ProgressNotificationState.Active + }; + + notificationOverlay?.Post(notification); + + return notification; + } + private void sleepIfRequired() { while (localUserPlayInfo?.IsPlaying.Value == true) From e7d1cf7868663126479d2997b7d0da3062817072 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Dec 2023 18:22:40 +0900 Subject: [PATCH 1985/4092] Add progress notifications for background tasks which don't already have them --- osu.Game/BackgroundDataStoreProcessor.cs | 60 +++++++++++++++++++++--- 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/osu.Game/BackgroundDataStoreProcessor.cs b/osu.Game/BackgroundDataStoreProcessor.cs index 794d534f10..58b1c912c4 100644 --- a/osu.Game/BackgroundDataStoreProcessor.cs +++ b/osu.Game/BackgroundDataStoreProcessor.cs @@ -144,12 +144,24 @@ namespace osu.Game } }); + if (beatmapSetIds.Count == 0) + return; + Logger.Log($"Found {beatmapSetIds.Count} beatmap sets which require reprocessing."); - int i = 0; + // Technically this is doing more than just star ratings, but easier for the end user to understand. + var notification = showProgressNotification("Reprocessing star rating for beatmaps", "beatmaps' star ratings have been updated"); + + int processedCount = 0; + int failedCount = 0; foreach (var id in beatmapSetIds) { + if (notification?.State == ProgressNotificationState.Cancelled) + break; + + updateNotificationProgress(notification, processedCount, beatmapSetIds.Count); + sleepIfRequired(); realmAccess.Run(r => @@ -160,16 +172,19 @@ namespace osu.Game { try { - Logger.Log($"Background processing {set} ({++i} / {beatmapSetIds.Count})"); beatmapUpdater.Process(set); + ++processedCount; } catch (Exception e) { Logger.Log($"Background processing failed on {set}: {e}"); + ++failedCount; } } }); } + + completeNotification(notification, processedCount, beatmapSetIds.Count, failedCount); } private void processBeatmapsWithMissingObjectCounts() @@ -184,12 +199,23 @@ namespace osu.Game beatmapIds.Add(b.ID); }); - Logger.Log($"Found {beatmapIds.Count} beatmaps which require reprocessing."); + if (beatmapIds.Count == 0) + return; - int i = 0; + Logger.Log($"Found {beatmapIds.Count} beatmaps which require statistics population."); + + var notification = showProgressNotification("Populating missing statistics for beatmaps", "beatmaps have been populated with missing statistics"); + + int processedCount = 0; + int failedCount = 0; foreach (var id in beatmapIds) { + if (notification?.State == ProgressNotificationState.Cancelled) + break; + + updateNotificationProgress(notification, processedCount, beatmapIds.Count); + sleepIfRequired(); realmAccess.Run(r => @@ -200,16 +226,19 @@ namespace osu.Game { try { - Logger.Log($"Background processing {beatmap} ({++i} / {beatmapIds.Count})"); beatmapUpdater.ProcessObjectCounts(beatmap); + ++processedCount; } catch (Exception e) { Logger.Log($"Background processing failed on {beatmap}: {e}"); + ++failedCount; } } }); } + + completeNotification(notification, processedCount, beatmapIds.Count, failedCount); } private void processScoresWithMissingStatistics() @@ -231,10 +260,23 @@ namespace osu.Game } }); - Logger.Log($"Found {scoreIds.Count} scores which require reprocessing."); + if (scoreIds.Count == 0) + return; + + Logger.Log($"Found {scoreIds.Count} scores which require statistics population."); + + var notification = showProgressNotification("Populating missing statistics for scores", "scores have been populated with missing statistics"); + + int processedCount = 0; + int failedCount = 0; foreach (var id in scoreIds) { + if (notification?.State == ProgressNotificationState.Cancelled) + break; + + updateNotificationProgress(notification, processedCount, scoreIds.Count); + sleepIfRequired(); try @@ -251,6 +293,7 @@ namespace osu.Game }); Logger.Log($"Populated maximum statistics for score {id}"); + ++processedCount; } catch (ObjectDisposedException) { @@ -260,8 +303,11 @@ namespace osu.Game { Logger.Log(@$"Failed to populate maximum statistics for {id}: {e}"); realmAccess.Write(r => r.Find(id)!.BackgroundReprocessingFailed = true); + ++failedCount; } } + + completeNotification(notification, processedCount, scoreIds.Count, failedCount); } private void convertLegacyTotalScoreToStandardised() @@ -332,6 +378,8 @@ namespace osu.Game notification.Text = notification.Text.ToString().Split('(').First().TrimEnd() + $" ({processedCount} of {totalCount})"; notification.Progress = (float)processedCount / totalCount; + + // TODO add log output } private void completeNotification(ProgressNotification? notification, int processedCount, int totalCount, int? failedCount = null) From bfa90e9dcb73f0c8687a184e4b91b95d2d98d72f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Dec 2023 18:34:55 +0900 Subject: [PATCH 1986/4092] Also populate `ObjectCount`s when running a full beatmap process Saves running things twice on an old install --- osu.Game/BackgroundDataStoreProcessor.cs | 1 + osu.Game/Beatmaps/BeatmapUpdater.cs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/osu.Game/BackgroundDataStoreProcessor.cs b/osu.Game/BackgroundDataStoreProcessor.cs index 58b1c912c4..38dc170ccc 100644 --- a/osu.Game/BackgroundDataStoreProcessor.cs +++ b/osu.Game/BackgroundDataStoreProcessor.cs @@ -67,6 +67,7 @@ namespace osu.Game checkForOutdatedStarRatings(); processBeatmapSetsWithMissingMetrics(); + // Note that the previous method will also update these on a fresh run. processBeatmapsWithMissingObjectCounts(); processScoresWithMissingStatistics(); convertLegacyTotalScoreToStandardised(); diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index 27492b8bac..e897d28916 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -77,6 +77,8 @@ namespace osu.Game.Beatmaps beatmap.StarRating = calculator.Calculate().StarRating; beatmap.Length = working.Beatmap.CalculatePlayableLength(); beatmap.BPM = 60000 / working.Beatmap.GetMostCommonBeatLength(); + beatmap.EndTimeObjectCount = working.Beatmap.HitObjects.Count(h => h is IHasDuration); + beatmap.TotalObjectCount = working.Beatmap.HitObjects.Count; } // And invalidate again afterwards as re-fetching the most up-to-date database metadata will be required. From 5ab38151239d39d4feec8b2f84d92e651c992a60 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Dec 2023 18:41:36 +0900 Subject: [PATCH 1987/4092] Add logging of progress every so often --- osu.Game/BackgroundDataStoreProcessor.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/BackgroundDataStoreProcessor.cs b/osu.Game/BackgroundDataStoreProcessor.cs index 38dc170ccc..6a801ab5bf 100644 --- a/osu.Game/BackgroundDataStoreProcessor.cs +++ b/osu.Game/BackgroundDataStoreProcessor.cs @@ -380,7 +380,8 @@ namespace osu.Game notification.Text = notification.Text.ToString().Split('(').First().TrimEnd() + $" ({processedCount} of {totalCount})"; notification.Progress = (float)processedCount / totalCount; - // TODO add log output + if (processedCount % 100 == 0) + Logger.Log(notification.Text.ToString()); } private void completeNotification(ProgressNotification? notification, int processedCount, int totalCount, int? failedCount = null) From e3251b40b311e504c6bb22e75e1b1ab347704714 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Dec 2023 19:00:44 +0900 Subject: [PATCH 1988/4092] Fix progress notifications queueing up infinite text changes when not visible --- osu.Game/Overlays/Notifications/ProgressNotification.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 6ea032213e..2362cb11f6 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -54,7 +54,7 @@ namespace osu.Game.Overlays.Notifications set { text = value; - Schedule(() => textDrawable.Text = text); + Scheduler.AddOnce(t => textDrawable.Text = t, text); } } From 62444c3d0423284daf1f8511977cd8d8f93fab6b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Dec 2023 19:17:59 +0900 Subject: [PATCH 1989/4092] Fix song select's carousel scroll position getting reset on background processing This only happened for users using absolute right-click scroll. --- osu.Game/Graphics/Containers/OsuScrollContainer.cs | 10 +++++----- .../Graphics/Containers/UserTrackingScrollContainer.cs | 7 +++++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuScrollContainer.cs b/osu.Game/Graphics/Containers/OsuScrollContainer.cs index da6996c170..124becc35a 100644 --- a/osu.Game/Graphics/Containers/OsuScrollContainer.cs +++ b/osu.Game/Graphics/Containers/OsuScrollContainer.cs @@ -44,9 +44,6 @@ namespace osu.Game.Graphics.Containers private bool shouldPerformRightMouseScroll(MouseButtonEvent e) => RightMouseScrollbar && e.Button == MouseButton.Right; - private void scrollFromMouseEvent(MouseEvent e) => - ScrollTo(Clamp(ToLocalSpace(e.ScreenSpaceMousePosition)[ScrollDim] / DrawSize[ScrollDim]) * Content.DrawSize[ScrollDim], true, DistanceDecayOnRightMouseScrollbar); - private bool rightMouseDragging; protected override bool IsDragging => base.IsDragging || rightMouseDragging; @@ -80,7 +77,7 @@ namespace osu.Game.Graphics.Containers { if (shouldPerformRightMouseScroll(e)) { - scrollFromMouseEvent(e); + ScrollFromMouseEvent(e); return true; } @@ -91,7 +88,7 @@ namespace osu.Game.Graphics.Containers { if (rightMouseDragging) { - scrollFromMouseEvent(e); + ScrollFromMouseEvent(e); return; } @@ -129,6 +126,9 @@ namespace osu.Game.Graphics.Containers return base.OnScroll(e); } + protected virtual void ScrollFromMouseEvent(MouseEvent e) => + ScrollTo(Clamp(ToLocalSpace(e.ScreenSpaceMousePosition)[ScrollDim] / DrawSize[ScrollDim]) * Content.DrawSize[ScrollDim], true, DistanceDecayOnRightMouseScrollbar); + protected override ScrollbarContainer CreateScrollbar(Direction direction) => new OsuScrollbar(direction); protected partial class OsuScrollbar : ScrollbarContainer diff --git a/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs b/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs index 6934c95385..354a57b7d2 100644 --- a/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs +++ b/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; +using osu.Framework.Input.Events; namespace osu.Game.Graphics.Containers { @@ -46,6 +47,12 @@ namespace osu.Game.Graphics.Containers base.ScrollIntoView(target, animated); } + protected override void ScrollFromMouseEvent(MouseEvent e) + { + UserScrolling = true; + base.ScrollFromMouseEvent(e); + } + public new void ScrollTo(float value, bool animated = true, double? distanceDecay = null) { UserScrolling = false; From 03ac2c30949c66900e04a67250ff21e2f34f2dfe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Dec 2023 19:20:57 +0900 Subject: [PATCH 1990/4092] Remove some excessive logging --- osu.Game/BackgroundDataStoreProcessor.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/BackgroundDataStoreProcessor.cs b/osu.Game/BackgroundDataStoreProcessor.cs index 6a801ab5bf..55be7f2c9e 100644 --- a/osu.Game/BackgroundDataStoreProcessor.cs +++ b/osu.Game/BackgroundDataStoreProcessor.cs @@ -293,7 +293,6 @@ namespace osu.Game r.Find(id)!.MaximumStatisticsJson = JsonConvert.SerializeObject(score.MaximumStatistics); }); - Logger.Log($"Populated maximum statistics for score {id}"); ++processedCount; } catch (ObjectDisposedException) @@ -354,7 +353,6 @@ namespace osu.Game s.TotalScoreVersion = LegacyScoreEncoder.LATEST_VERSION; }); - Logger.Log($"Converted total score for score {id}"); ++processedCount; } catch (ObjectDisposedException) From 32cc3f9ef74594875f9049bbe7f5bdea901cb1e5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Dec 2023 20:00:57 +0900 Subject: [PATCH 1991/4092] Combine multiple similar invalidation logic into single event --- osu.Game/Screens/Select/BeatmapCarousel.cs | 33 ++++++++-------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index eb47a7201a..19ade780e1 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -168,7 +168,7 @@ namespace osu.Game.Screens.Select applyActiveCriteria(false); if (loadedTestBeatmaps) - signalBeatmapsLoaded(); + invalidateAfterChange(true); // Restore selection if (selectedBeatmapBefore != null && newRoot.BeatmapSetsByID.TryGetValue(selectedBeatmapBefore.BeatmapSet!.ID, out var newSelectionCandidates)) @@ -298,7 +298,8 @@ namespace osu.Game.Screens.Select removeBeatmapSet(id); } - signalBeatmapsLoaded(); + invalidateAfterChange(!BeatmapSetsLoaded); + BeatmapSetsLoaded = true; return; } @@ -393,12 +394,7 @@ namespace osu.Game.Screens.Select root.RemoveItem(set); } - itemsCache.Invalidate(); - - if (!Scroll.UserScrolling) - ScrollToSelected(true); - - BeatmapSetsChanged?.Invoke(); + invalidateAfterChange(true); }); public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() => @@ -465,12 +461,7 @@ namespace osu.Game.Screens.Select } } - itemsCache.Invalidate(); - - if (!Scroll.UserScrolling) - ScrollToSelected(true); - - BeatmapSetsChanged?.Invoke(); + invalidateAfterChange(true); }); /// @@ -748,15 +739,15 @@ namespace osu.Game.Screens.Select } } - private void signalBeatmapsLoaded() + private void invalidateAfterChange(bool invokeSetsChangedEvent) { - if (!BeatmapSetsLoaded) - { - BeatmapSetsChanged?.Invoke(); - BeatmapSetsLoaded = true; - } - itemsCache.Invalidate(); + + if (!Scroll.UserScrolling) + ScrollToSelected(true); + + if (invokeSetsChangedEvent) + BeatmapSetsChanged?.Invoke(); } private float? scrollTarget; From 87b7699fcc29ab7e70ec1f632a0341bbe08a23b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Dec 2023 20:09:09 +0900 Subject: [PATCH 1992/4092] Avoid calling invalidation logic per beatmap set updated Realm will batch the updates. We don't want to do expensive operations per set when we don't need to. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 33 +++++++++++++++------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 19ade780e1..63654fd36a 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -289,7 +289,7 @@ namespace osu.Game.Screens.Select foreach (var id in realmSets) { if (!root.BeatmapSetsByID.ContainsKey(id)) - UpdateBeatmapSet(realm.Realm.Find(id)!.Detach()); + updateBeatmapSet(realm.Realm.Find(id)!.Detach()); } foreach (var id in root.BeatmapSetsByID.Keys) @@ -304,10 +304,10 @@ namespace osu.Game.Screens.Select } foreach (int i in changes.NewModifiedIndices) - UpdateBeatmapSet(sender[i].Detach()); + updateBeatmapSet(sender[i].Detach()); foreach (int i in changes.InsertedIndices) - UpdateBeatmapSet(sender[i].Detach()); + updateBeatmapSet(sender[i].Detach()); if (changes.DeletedIndices.Length > 0 && SelectedBeatmapInfo != null) { @@ -348,6 +348,8 @@ namespace osu.Game.Screens.Select SelectBeatmap(sender[modifiedAndInserted.First()].Beatmaps.First()); } } + + invalidateAfterChange(!BeatmapSetsLoaded); } private void beatmapsChanged(IRealmCollection sender, ChangeSet? changes) @@ -356,6 +358,8 @@ namespace osu.Game.Screens.Select if (changes == null) return; + bool changed = false; + foreach (int i in changes.InsertedIndices) { var beatmapInfo = sender[i]; @@ -368,17 +372,24 @@ namespace osu.Game.Screens.Select if (root.BeatmapSetsByID.TryGetValue(beatmapSet.ID, out var existingSets) && existingSets.SelectMany(s => s.Beatmaps).All(b => b.BeatmapInfo.ID != beatmapInfo.ID)) { - UpdateBeatmapSet(beatmapSet.Detach()); + updateBeatmapSet(beatmapSet.Detach()); + changed = true; } } + + if (changed) + invalidateAfterChange(true); } private IQueryable getBeatmapSets(Realm realm) => realm.All().Where(s => !s.DeletePending && !s.Protected); - public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => + public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() => + { removeBeatmapSet(beatmapSet.ID); + invalidateAfterChange(true); + }); - private void removeBeatmapSet(Guid beatmapSetID) => Schedule(() => + private void removeBeatmapSet(Guid beatmapSetID) { if (!root.BeatmapSetsByID.TryGetValue(beatmapSetID, out var existingSets)) return; @@ -393,11 +404,15 @@ namespace osu.Game.Screens.Select root.RemoveItem(set); } + } + public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() => + { + updateBeatmapSet(beatmapSet); invalidateAfterChange(true); }); - public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() => + private void updateBeatmapSet(BeatmapSetInfo beatmapSet) { Guid? previouslySelectedID = null; @@ -460,9 +475,7 @@ namespace osu.Game.Screens.Select select((CarouselItem?)newSet.Beatmaps.FirstOrDefault(b => b.BeatmapInfo.ID == previouslySelectedID) ?? newSet); } } - - invalidateAfterChange(true); - }); + } /// /// Selects a given beatmap on the carousel. From 6fa1f5ef9bfaac577cad7b29d4840e9302c6a889 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Dec 2023 20:10:31 +0900 Subject: [PATCH 1993/4092] Simplify invalidation logic The only case where this was checking is guaranteed by realm to only be called once. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 63654fd36a..5178413ad6 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -168,7 +168,7 @@ namespace osu.Game.Screens.Select applyActiveCriteria(false); if (loadedTestBeatmaps) - invalidateAfterChange(true); + invalidateAfterChange(); // Restore selection if (selectedBeatmapBefore != null && newRoot.BeatmapSetsByID.TryGetValue(selectedBeatmapBefore.BeatmapSet!.ID, out var newSelectionCandidates)) @@ -298,7 +298,7 @@ namespace osu.Game.Screens.Select removeBeatmapSet(id); } - invalidateAfterChange(!BeatmapSetsLoaded); + invalidateAfterChange(); BeatmapSetsLoaded = true; return; } @@ -349,7 +349,7 @@ namespace osu.Game.Screens.Select } } - invalidateAfterChange(!BeatmapSetsLoaded); + invalidateAfterChange(); } private void beatmapsChanged(IRealmCollection sender, ChangeSet? changes) @@ -378,7 +378,7 @@ namespace osu.Game.Screens.Select } if (changed) - invalidateAfterChange(true); + invalidateAfterChange(); } private IQueryable getBeatmapSets(Realm realm) => realm.All().Where(s => !s.DeletePending && !s.Protected); @@ -386,7 +386,7 @@ namespace osu.Game.Screens.Select public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() => { removeBeatmapSet(beatmapSet.ID); - invalidateAfterChange(true); + invalidateAfterChange(); }); private void removeBeatmapSet(Guid beatmapSetID) @@ -409,7 +409,7 @@ namespace osu.Game.Screens.Select public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() => { updateBeatmapSet(beatmapSet); - invalidateAfterChange(true); + invalidateAfterChange(); }); private void updateBeatmapSet(BeatmapSetInfo beatmapSet) @@ -752,15 +752,14 @@ namespace osu.Game.Screens.Select } } - private void invalidateAfterChange(bool invokeSetsChangedEvent) + private void invalidateAfterChange() { itemsCache.Invalidate(); if (!Scroll.UserScrolling) ScrollToSelected(true); - if (invokeSetsChangedEvent) - BeatmapSetsChanged?.Invoke(); + BeatmapSetsChanged?.Invoke(); } private float? scrollTarget; From 034c5cd6541d622fe42ed1c301b647a3ac06afd8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Dec 2023 20:30:56 +0900 Subject: [PATCH 1994/4092] Debounce count updates for good measure --- osu.Game/Screens/Select/SongSelect.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index dfea4e3794..7c30fd5baa 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -162,7 +162,7 @@ namespace osu.Game.Screens.Select BleedBottom = Footer.HEIGHT, SelectionChanged = updateSelectedBeatmap, BeatmapSetsChanged = carouselBeatmapsLoaded, - FilterApplied = updateVisibleBeatmapCount, + FilterApplied = () => Scheduler.Add(updateVisibleBeatmapCount), GetRecommendedBeatmap = s => recommender?.GetRecommendedBeatmap(s), }, c => carouselContainer.Child = c); @@ -843,7 +843,7 @@ namespace osu.Game.Screens.Select private void carouselBeatmapsLoaded() { bindBindables(); - updateVisibleBeatmapCount(); + Scheduler.AddOnce(updateVisibleBeatmapCount); Carousel.AllowSelection = true; @@ -877,7 +877,8 @@ namespace osu.Game.Screens.Select { // Intentionally not localised until we have proper support for this (see https://github.com/ppy/osu-framework/pull/4918 // but also in this case we want support for formatting a number within a string). - FilterControl.InformationalText = Carousel.CountDisplayed != 1 ? $"{Carousel.CountDisplayed:#,0} matches" : $"{Carousel.CountDisplayed:#,0} match"; + int carouselCountDisplayed = Carousel.CountDisplayed; + FilterControl.InformationalText = carouselCountDisplayed != 1 ? $"{carouselCountDisplayed:#,0} matches" : $"{carouselCountDisplayed:#,0} match"; } private bool boundLocalBindables; From 5755fa214a450d1663babcad4fd1eb5bcafb12e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Dec 2023 20:44:08 +0900 Subject: [PATCH 1995/4092] Cache non-filtered beatmap counts to massively improve count performance --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- .../Screens/Select/Carousel/CarouselGroup.cs | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 5178413ad6..919a320bca 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -64,7 +64,7 @@ namespace osu.Game.Screens.Select /// /// The total count of non-filtered beatmaps displayed. /// - public int CountDisplayed => beatmapSets.Where(s => !s.Filtered.Value).Sum(s => s.Beatmaps.Count(b => !b.Filtered.Value)); + public int CountDisplayed => beatmapSets.Where(s => !s.Filtered.Value).Sum(s => s.TotalItemsNotFiltered); /// /// The currently selected beatmap set. diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index c353ee98ae..5aefdf5e28 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -14,6 +14,8 @@ namespace osu.Game.Screens.Select.Carousel public IReadOnlyList Items => items; + public int TotalItemsNotFiltered { get; private set; } + private readonly List items = new List(); /// @@ -31,6 +33,9 @@ namespace osu.Game.Screens.Select.Carousel { items.Remove(i); + if (!i.Filtered.Value) + TotalItemsNotFiltered--; + // it's important we do the deselection after removing, so any further actions based on // State.ValueChanged make decisions post-removal. i.State.Value = CarouselItemState.Collapsed; @@ -55,6 +60,9 @@ namespace osu.Game.Screens.Select.Carousel // criteria may be null for initial population. the filtering will be applied post-add. items.Add(i); } + + if (!i.Filtered.Value) + TotalItemsNotFiltered--; } public CarouselGroup(List? items = null) @@ -84,7 +92,14 @@ namespace osu.Game.Screens.Select.Carousel { base.Filter(criteria); - items.ForEach(c => c.Filter(criteria)); + TotalItemsNotFiltered = 0; + + foreach (var c in items) + { + c.Filter(criteria); + if (!c.Filtered.Value) + TotalItemsNotFiltered++; + } // Sorting is expensive, so only perform if it's actually changed. if (lastCriteria?.Sort != criteria.Sort) From 25df42630ecba979457a9dcd2a7678d154a85a5e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Dec 2023 20:47:08 +0900 Subject: [PATCH 1996/4092] Improve performance of `attemptSelection` using new cached count and `LastSelected` --- osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs index 7f90e05744..8d2ddc5812 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; namespace osu.Game.Screens.Select.Carousel { @@ -101,7 +100,7 @@ namespace osu.Game.Screens.Select.Carousel if (State.Value != CarouselItemState.Selected) return; // we only perform eager selection if none of our items are in a selected state already. - if (Items.Any(i => i.State.Value == CarouselItemState.Selected)) return; + if (LastSelected?.State.Value == CarouselItemState.Selected || TotalItemsNotFiltered == 0) return; PerformSelection(); } From be16e0e538465369aa9e33223f2814b34fdbddaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 18 Dec 2023 12:47:42 +0100 Subject: [PATCH 1997/4092] Add failing test for adding collection w/ name colliding w/\ default items --- .../TestSceneManageCollectionsDialog.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index cfa45ec6ef..747cf73baf 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -166,6 +166,29 @@ namespace osu.Game.Tests.Visual.Collections }))); } + [Test] + public void TestCollectionNameCollisionsWithBuiltInItems() + { + AddStep("add dropdown", () => + { + Add(new CollectionDropdown + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.X, + Width = 0.4f, + }); + }); + AddStep("add two collections which collide with default items", () => Realm.Write(r => r.Add(new[] + { + new BeatmapCollection(name: "All beatmaps"), + new BeatmapCollection(name: "Manage collections...") + { + BeatmapMD5Hashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash } + }, + }))); + } + [Test] public void TestRemoveCollectionViaButton() { From eeeb5aa3d4d0eeb8360b345751157eb52f09ad40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 18 Dec 2023 12:52:06 +0100 Subject: [PATCH 1998/4092] Fix crash when creating collections named "All beatmaps" or "Manage collections..." Closes https://github.com/ppy/osu/issues/25834 The name fallback that was there previously since https://github.com/ppy/osu/pull/11892 was half broken. This way should be a lot less prone to failure. --- .../Collections/CollectionFilterMenuItem.cs | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/osu.Game/Collections/CollectionFilterMenuItem.cs b/osu.Game/Collections/CollectionFilterMenuItem.cs index 2ac5784f09..49262ed917 100644 --- a/osu.Game/Collections/CollectionFilterMenuItem.cs +++ b/osu.Game/Collections/CollectionFilterMenuItem.cs @@ -37,22 +37,17 @@ namespace osu.Game.Collections CollectionName = name; } - public bool Equals(CollectionFilterMenuItem? other) + public virtual bool Equals(CollectionFilterMenuItem? other) { - if (other == null) - return false; + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; - // collections may have the same name, so compare first on reference equality. - // this relies on the assumption that only one instance of the BeatmapCollection exists game-wide, managed by CollectionManager. - if (Collection != null) - return Collection.ID == other.Collection?.ID; + if (Collection == null) return false; - // fallback to name-based comparison. - // this is required for special dropdown items which don't have a collection (all beatmaps / manage collections items below). - return CollectionName == other.CollectionName; + return Collection.ID == other.Collection?.ID; } - public override int GetHashCode() => CollectionName.GetHashCode(); + public override int GetHashCode() => Collection?.ID.GetHashCode() ?? 0; } public class AllBeatmapsCollectionFilterMenuItem : CollectionFilterMenuItem @@ -61,6 +56,10 @@ namespace osu.Game.Collections : base("All beatmaps") { } + + public override bool Equals(CollectionFilterMenuItem? other) => other is AllBeatmapsCollectionFilterMenuItem; + + public override int GetHashCode() => 1; } public class ManageCollectionsFilterMenuItem : CollectionFilterMenuItem @@ -69,5 +68,9 @@ namespace osu.Game.Collections : base("Manage collections...") { } + + public override bool Equals(CollectionFilterMenuItem? other) => other is ManageCollectionsFilterMenuItem; + + public override int GetHashCode() => 2; } } From 17f1f8bb43b7cadb1765338428870736a6f713bc Mon Sep 17 00:00:00 2001 From: 65-7a Date: Tue, 19 Dec 2023 00:28:23 +1100 Subject: [PATCH 1999/4092] Fix padding on dropdown search bar --- osu.Game/Graphics/UserInterface/OsuDropdown.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs index ea0da30911..632036fef9 100644 --- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs @@ -397,7 +397,7 @@ namespace osu.Game.Graphics.UserInterface protected override DropdownSearchBar CreateSearchBar() => new OsuDropdownSearchBar { - Padding = new MarginPadding { Right = 36 }, + Padding = new MarginPadding { Right = 26 }, }; private partial class OsuDropdownSearchBar : DropdownSearchBar From 25e3a8e82e712d18c904ccfcc29fbcf821e94163 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Dec 2023 22:24:57 +0900 Subject: [PATCH 2000/4092] Fix a few of silly issues --- osu.Game/Screens/Select/BeatmapCarousel.cs | 3 +++ osu.Game/Screens/Select/Carousel/CarouselGroup.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 919a320bca..607b891beb 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -168,7 +168,10 @@ namespace osu.Game.Screens.Select applyActiveCriteria(false); if (loadedTestBeatmaps) + { invalidateAfterChange(); + BeatmapSetsLoaded = true; + } // Restore selection if (selectedBeatmapBefore != null && newRoot.BeatmapSetsByID.TryGetValue(selectedBeatmapBefore.BeatmapSet!.ID, out var newSelectionCandidates)) diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index 5aefdf5e28..f5ea32a22a 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -62,7 +62,7 @@ namespace osu.Game.Screens.Select.Carousel } if (!i.Filtered.Value) - TotalItemsNotFiltered--; + TotalItemsNotFiltered++; } public CarouselGroup(List? items = null) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 7c30fd5baa..d23a660ff6 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -162,7 +162,7 @@ namespace osu.Game.Screens.Select BleedBottom = Footer.HEIGHT, SelectionChanged = updateSelectedBeatmap, BeatmapSetsChanged = carouselBeatmapsLoaded, - FilterApplied = () => Scheduler.Add(updateVisibleBeatmapCount), + FilterApplied = () => Scheduler.AddOnce(updateVisibleBeatmapCount), GetRecommendedBeatmap = s => recommender?.GetRecommendedBeatmap(s), }, c => carouselContainer.Child = c); From d81cabc06361ab9cfb20f7294c38166d4bdb03a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Dec 2023 22:35:36 +0900 Subject: [PATCH 2001/4092] Revert "Improve performance of `attemptSelection` using new cached count and `LastSelected`" This reverts commit 25df42630ecba979457a9dcd2a7678d154a85a5e. --- osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs index 8d2ddc5812..7f90e05744 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; namespace osu.Game.Screens.Select.Carousel { @@ -100,7 +101,7 @@ namespace osu.Game.Screens.Select.Carousel if (State.Value != CarouselItemState.Selected) return; // we only perform eager selection if none of our items are in a selected state already. - if (LastSelected?.State.Value == CarouselItemState.Selected || TotalItemsNotFiltered == 0) return; + if (Items.Any(i => i.State.Value == CarouselItemState.Selected)) return; PerformSelection(); } From 9aaaa128098af6a8e57e07d5131bb719779b428c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Dec 2023 00:01:09 +0900 Subject: [PATCH 2002/4092] Don't show progress notifications when there are too few items to be worthwhile --- osu.Game/BackgroundDataStoreProcessor.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game/BackgroundDataStoreProcessor.cs b/osu.Game/BackgroundDataStoreProcessor.cs index 55be7f2c9e..33b66ecfc7 100644 --- a/osu.Game/BackgroundDataStoreProcessor.cs +++ b/osu.Game/BackgroundDataStoreProcessor.cs @@ -151,7 +151,7 @@ namespace osu.Game Logger.Log($"Found {beatmapSetIds.Count} beatmap sets which require reprocessing."); // Technically this is doing more than just star ratings, but easier for the end user to understand. - var notification = showProgressNotification("Reprocessing star rating for beatmaps", "beatmaps' star ratings have been updated"); + var notification = showProgressNotification(beatmapSetIds.Count, "Reprocessing star rating for beatmaps", "beatmaps' star ratings have been updated"); int processedCount = 0; int failedCount = 0; @@ -205,7 +205,7 @@ namespace osu.Game Logger.Log($"Found {beatmapIds.Count} beatmaps which require statistics population."); - var notification = showProgressNotification("Populating missing statistics for beatmaps", "beatmaps have been populated with missing statistics"); + var notification = showProgressNotification(beatmapIds.Count, "Populating missing statistics for beatmaps", "beatmaps have been populated with missing statistics"); int processedCount = 0; int failedCount = 0; @@ -266,7 +266,7 @@ namespace osu.Game Logger.Log($"Found {scoreIds.Count} scores which require statistics population."); - var notification = showProgressNotification("Populating missing statistics for scores", "scores have been populated with missing statistics"); + var notification = showProgressNotification(scoreIds.Count, "Populating missing statistics for scores", "scores have been populated with missing statistics"); int processedCount = 0; int failedCount = 0; @@ -325,7 +325,7 @@ namespace osu.Game if (scoreIds.Count == 0) return; - var notification = showProgressNotification("Upgrading scores to new scoring algorithm", "scores have been upgraded to the new scoring algorithm"); + var notification = showProgressNotification(scoreIds.Count, "Upgrading scores to new scoring algorithm", "scores have been upgraded to the new scoring algorithm"); int processedCount = 0; int failedCount = 0; @@ -405,11 +405,14 @@ namespace osu.Game } } - private ProgressNotification? showProgressNotification(string running, string completed) + private ProgressNotification? showProgressNotification(int totalCount, string running, string completed) { if (notificationOverlay == null) return null; + if (totalCount < 10) + return null; + ProgressNotification notification = new ProgressNotification { Text = running, From 374425ea75ee36a853f170c3a3ae04deccdb9981 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Dec 2023 01:07:33 +0900 Subject: [PATCH 2003/4092] Fix keyboard precision of nightcore/daycore adjustments being incorrect Closes https://github.com/ppy/osu/issues/25854. --- osu.Game/Rulesets/Mods/ModDaycore.cs | 3 ++- osu.Game/Rulesets/Mods/ModNightcore.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs index 39ebd1fe4c..09b35c249e 100644 --- a/osu.Game/Rulesets/Mods/ModDaycore.cs +++ b/osu.Game/Rulesets/Mods/ModDaycore.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Configuration; +using osu.Game.Overlays.Settings; namespace osu.Game.Rulesets.Mods { @@ -17,7 +18,7 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.DifficultyReduction; public override LocalisableString Description => "Whoaaaaa..."; - [SettingSource("Speed decrease", "The actual decrease to apply")] + [SettingSource("Speed decrease", "The actual decrease to apply", SettingControlType = typeof(MultiplierSettingsSlider))] public override BindableNumber SpeedChange { get; } = new BindableDouble(0.75) { MinValue = 0.5, diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index b519ab4db7..b42927256c 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -14,6 +14,7 @@ using osu.Game.Beatmaps.Timing; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; using osu.Game.Skinning; @@ -28,7 +29,7 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.DifficultyIncrease; public override LocalisableString Description => "Uguuuuuuuu..."; - [SettingSource("Speed increase", "The actual increase to apply")] + [SettingSource("Speed increase", "The actual increase to apply", SettingControlType = typeof(MultiplierSettingsSlider))] public override BindableNumber SpeedChange { get; } = new BindableDouble(1.5) { MinValue = 1.01, From 51f4c7254c80bca74ea0ae9e772c630feb7f6a4e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Dec 2023 01:32:30 +0900 Subject: [PATCH 2004/4092] Fix mod search textbox having focus while settings are visible Stopped arrow key adjust on slider bars from working. Also just felt wrong that you could type into an off-screen textbox. --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 13 ++++++++----- osu.Game/Overlays/Mods/ModSettingsArea.cs | 2 ++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index ce798ae752..30d7b6191e 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -508,6 +508,11 @@ namespace osu.Game.Overlays.Mods modSettingsArea.ResizeHeightTo(modAreaHeight, transition_duration, Easing.InOutCubic); TopLevelContent.MoveToY(-modAreaHeight, transition_duration, Easing.InOutCubic); + + if (customisationVisible.Value) + GetContainingInputManager().ChangeFocus(modSettingsArea); + else + Scheduler.Add(() => GetContainingInputManager().ChangeFocus(null)); } /// @@ -622,7 +627,7 @@ namespace osu.Game.Overlays.Mods } if (textSearchStartsActive.Value) - SearchTextBox.TakeFocus(); + SearchTextBox.HoldFocus = true; } protected override void PopOut() @@ -761,11 +766,9 @@ namespace osu.Game.Overlays.Mods return false; // TODO: should probably eventually support typical platform search shortcuts (`Ctrl-F`, `/`) - if (SearchTextBox.HasFocus) - SearchTextBox.KillFocus(); - else + SearchTextBox.HoldFocus = !SearchTextBox.HoldFocus; + if (SearchTextBox.HoldFocus) SearchTextBox.TakeFocus(); - return true; } diff --git a/osu.Game/Overlays/Mods/ModSettingsArea.cs b/osu.Game/Overlays/Mods/ModSettingsArea.cs index 6158c2c70f..54bfcc7199 100644 --- a/osu.Game/Overlays/Mods/ModSettingsArea.cs +++ b/osu.Game/Overlays/Mods/ModSettingsArea.cs @@ -32,6 +32,8 @@ namespace osu.Game.Overlays.Mods [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; + public override bool AcceptsFocus => true; + public ModSettingsArea() { RelativeSizeAxes = Axes.X; From 41485c19cfe597a5144911fdaff34fe218f1d963 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Dec 2023 02:08:25 +0900 Subject: [PATCH 2005/4092] Add realm refresh steps in an attempt to stabilise failing test I think this is required because there is a higher chance of batched updates with the new structure (and less calls to `BeatmapSetsChanged` which causes re-selection). --- osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 6b53277964..28b4dae56d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -464,6 +464,8 @@ namespace osu.Game.Tests.Visual.SongSelect manager.Import(testBeatmapSetInfo); }, 10); + AddStep("Force realm refresh", () => Realm.Run(r => r.Refresh())); + AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.BeatmapSet?.OnlineID == originalOnlineSetID); Task?> updateTask = null!; @@ -476,6 +478,8 @@ namespace osu.Game.Tests.Visual.SongSelect }); AddUntilStep("wait for update completion", () => updateTask.IsCompleted); + AddStep("Force realm refresh", () => Realm.Run(r => r.Refresh())); + AddUntilStep("retained selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.BeatmapSet?.OnlineID == originalOnlineSetID); } From bf668174ecc743f5941035a57fbbbcfa8d2278fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 18 Dec 2023 19:02:23 +0100 Subject: [PATCH 2006/4092] Use nunit constraints in test for transparency --- osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 28b4dae56d..6af53df725 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -466,7 +466,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("Force realm refresh", () => Realm.Run(r => r.Refresh())); - AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.BeatmapSet?.OnlineID == originalOnlineSetID); + AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.BeatmapSet?.OnlineID, () => Is.EqualTo(originalOnlineSetID)); Task?> updateTask = null!; @@ -480,7 +480,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("Force realm refresh", () => Realm.Run(r => r.Refresh())); - AddUntilStep("retained selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.BeatmapSet?.OnlineID == originalOnlineSetID); + AddUntilStep("retained selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.BeatmapSet?.OnlineID, () => Is.EqualTo(originalOnlineSetID)); } [Test] From 9594ae7802fce0edea82d09d2bc2ecfe4bb50f85 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Dec 2023 03:22:22 +0900 Subject: [PATCH 2007/4092] 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 bd35bb8d54..cf01f2f99b 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 84a4e61267..c7b9d02b26 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From cc800a18b203e474fc9fe6d43f50c2bc4e63ea03 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Mon, 18 Dec 2023 21:11:00 +0100 Subject: [PATCH 2008/4092] Fix opening log files from notification not presenting the correct file --- osu.Game/OsuGame.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 8d9e029c9d..e7ff99ef01 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1190,7 +1190,7 @@ namespace osu.Game } else if (recentLogCount == short_term_display_limit) { - string logFile = $@"{entry.Target.Value.ToString().ToLowerInvariant()}.log"; + string logFile = Logger.GetLogger(entry.Target.Value).Filename; Schedule(() => Notifications.Post(new SimpleNotification { @@ -1198,7 +1198,7 @@ namespace osu.Game Text = NotificationsStrings.SubsequentMessagesLogged, Activated = () => { - Storage.GetStorageForDirectory(@"logs").PresentFileExternally(logFile); + Logger.Storage.PresentFileExternally(logFile); return true; } })); From 017003deea88739c244e88facd84252a940a6372 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 18 Dec 2023 21:56:56 +0100 Subject: [PATCH 2009/4092] Fix osu! standardised score conversion sometimes exceeding bounds Co-authored-by: Zyf Closes https://github.com/ppy/osu/issues/25860 Users reported that some stable scores would convert to large negative total scores in lazer after the introduction of combo exponent. Those large negative total scores were actually mangled NaNs. The root cause of this was the following calculation going below zero unexpectedly: https://github.com/ppy/osu/blob/8e8d9b2cd96be4c4b3d8f1f01dc013fc9d41f765/osu.Game/Database/StandardisedScoreMigrationTools.cs#L323 which then propagates negative numbers onward until https://github.com/ppy/osu/blob/8e8d9b2cd96be4c4b3d8f1f01dc013fc9d41f765/osu.Game/Database/StandardisedScoreMigrationTools.cs#L337 which yields a NaN due to attempting to take the square root of a negative number. To fix, clamp `comboPortionInScoreV1` to sane limits: to `comboPortionFromLongestComboInScoreV1` from below, and to `maximumAchievableComboPortionInScoreV1` from above. This is a less direct fix than perhaps imagined, but it seems like a better one as it will also affect the calculation of both the lower and the upper estimate of the score. --- .../Database/StandardisedScoreMigrationTools.cs | 15 +++++++++++++-- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 3 ++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index 6484500bcc..a30c40fdb0 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -293,13 +293,24 @@ namespace osu.Game.Database // Roughly corresponds to integrating f(combo) = combo ^ COMBO_EXPONENT (omitting constants) double maximumAchievableComboPortionInStandardisedScore = Math.Pow(maximumLegacyCombo, 1 + ScoreProcessor.COMBO_EXPONENT); - double comboPortionInScoreV1 = maximumAchievableComboPortionInScoreV1 * comboProportion / score.Accuracy; - // This is - roughly - how much score, in the combo portion, the longest combo on this particular play would gain in score V1. double comboPortionFromLongestComboInScoreV1 = Math.Pow(score.MaxCombo, 2); // Same for standardised score. double comboPortionFromLongestComboInStandardisedScore = Math.Pow(score.MaxCombo, 1 + ScoreProcessor.COMBO_EXPONENT); + // We estimate the combo portion of the score in score V1 terms. + // The division by accuracy is supposed to lessen the impact of accuracy on the combo portion, + // but in some edge cases it cannot sanely undo it. + // Therefore the resultant value is clamped from both sides for sanity. + // The clamp from below to `comboPortionFromLongestComboInScoreV1` targets near-FC scores wherein + // the player had bad accuracy at the end of their longest combo, which causes the division by accuracy + // to underestimate the combo portion. + // The clamp from above to `maximumAchievableComboPortionInScoreV1` targets FC scores wherein + // the player had bad accuracy at the start of the map, which causes the division by accuracy + // to overestimate the combo portion. + double comboPortionInScoreV1 = Math.Clamp(maximumAchievableComboPortionInScoreV1 * comboProportion / score.Accuracy, + comboPortionFromLongestComboInScoreV1, maximumAchievableComboPortionInScoreV1); + // Calculate how many times the longest combo the user has achieved in the play can repeat // without exceeding the combo portion in score V1 as achieved by the player. // This is a pessimistic estimate; it intentionally does not operate on object count and uses only score instead. diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 00b1f04782..5c51e68d9f 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -32,9 +32,10 @@ namespace osu.Game.Scoring.Legacy /// 30000003: First version after converting legacy total score to standardised. /// 30000004: Fixed mod multipliers during legacy score conversion. Reconvert all scores. /// 30000005: Introduce combo exponent in the osu! gamemode. Reconvert all scores. + /// 30000006: Fix edge cases in conversion after combo exponent introduction that lead to NaNs. Reconvert all scores. /// /// - public const int LATEST_VERSION = 30000005; + public const int LATEST_VERSION = 30000006; /// /// The first stable-compatible YYYYMMDD format version given to lazer usage of replays. From 9c8df4e6d1a041c0b2fc19873aa8ae8e1d0d9fb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 18 Dec 2023 22:23:58 +0100 Subject: [PATCH 2010/4092] Run score conversion for previously-imported scores --- osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs | 3 +++ osu.Game/BackgroundDataStoreProcessor.cs | 3 +-- osu.Game/Database/StandardisedScoreMigrationTools.cs | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs index e65088ca2e..43ce7200d2 100644 --- a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs +++ b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs @@ -127,8 +127,11 @@ namespace osu.Game.Tests.Database }); } + [TestCase(30000001)] [TestCase(30000002)] [TestCase(30000003)] + [TestCase(30000004)] + [TestCase(30000005)] public void TestScoreUpgradeSuccess(int scoreVersion) { ScoreInfo scoreInfo = null!; diff --git a/osu.Game/BackgroundDataStoreProcessor.cs b/osu.Game/BackgroundDataStoreProcessor.cs index 33b66ecfc7..0d5cb84359 100644 --- a/osu.Game/BackgroundDataStoreProcessor.cs +++ b/osu.Game/BackgroundDataStoreProcessor.cs @@ -316,8 +316,7 @@ namespace osu.Game HashSet scoreIds = realmAccess.Run(r => new HashSet(r.All() .Where(s => !s.BackgroundReprocessingFailed && s.BeatmapInfo != null - && (s.TotalScoreVersion == 30000002 - || s.TotalScoreVersion == 30000003)) + && s.TotalScoreVersion < LegacyScoreEncoder.LATEST_VERSION) .AsEnumerable().Select(s => s.ID))); Logger.Log($"Found {scoreIds.Count} scores which require total score conversion."); diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index a30c40fdb0..6980e81f58 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -26,7 +26,7 @@ namespace osu.Game.Database if (score.IsLegacyScore) return false; - if (score.TotalScoreVersion > 30000004) + if (score.TotalScoreVersion > 30000005) return false; // Recalculate the old-style standardised score to see if this was an old lazer score. From ee8a5d5a3077c5a1045ef74fc8b037296a26a005 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Dec 2023 13:33:45 +0900 Subject: [PATCH 2011/4092] Update bug-issue.yml with new workflow for exporting logs --- .github/ISSUE_TEMPLATE/bug-issue.yml | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-issue.yml b/.github/ISSUE_TEMPLATE/bug-issue.yml index ff6d869e72..17a3e1df41 100644 --- a/.github/ISSUE_TEMPLATE/bug-issue.yml +++ b/.github/ISSUE_TEMPLATE/bug-issue.yml @@ -46,22 +46,16 @@ body: value: | ## Logs - Attaching log files is required for every reported bug. See instructions below on how to find them. - - **Logs are reset when you reopen the game.** If the game crashed or has been closed since you found the bug, retrieve the logs using the file explorer instead. + Attaching log files is required for **every** issue, regardless of whether you deem them required or not. See instructions below on how to find them. ### Desktop platforms If the game has not yet been closed since you found the bug: - 1. Head on to game settings and click on "Open osu! folder" - 2. Then open the `logs` folder located there + 1. Head on to game settings and click on "Export logs" + 2. Click the notification to locate the file + 3. Drag the generated `.zip` files into the github issue window - The default places to find the logs on desktop platforms are as follows: - - `%AppData%/osu/logs` *on Windows* - - `~/.local/share/osu/logs` *on Linux* - - `~/Library/Application Support/osu/logs` *on macOS* - - If you have selected a custom location for the game files, you can find the `logs` folder there. + ![export logs button](https://github.com/ppy/osu/assets/191335/0866443f-0728-47bc-9dbd-f2b79ac802d5) ### Mobile platforms @@ -69,10 +63,6 @@ body: - *On Android*, navigate to `Android/data/sh.ppy.osulazer/files/logs` using a file browser app. - *On iOS*, connect your device to a PC and copy the `logs` directory from the app's document storage using iTunes. (https://support.apple.com/en-us/HT201301#copy-to-computer) - --- - - After locating the `logs` folder, select all log files inside and drag them into the "Logs" box below. - - type: textarea attributes: label: Logs From 469a659938dfb3c696c9a9fb51c7252214014a31 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Dec 2023 13:35:02 +0900 Subject: [PATCH 2012/4092] Fix arrow pointing to wrong place --- .github/ISSUE_TEMPLATE/bug-issue.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-issue.yml b/.github/ISSUE_TEMPLATE/bug-issue.yml index 17a3e1df41..00a873f9c8 100644 --- a/.github/ISSUE_TEMPLATE/bug-issue.yml +++ b/.github/ISSUE_TEMPLATE/bug-issue.yml @@ -53,9 +53,9 @@ body: If the game has not yet been closed since you found the bug: 1. Head on to game settings and click on "Export logs" 2. Click the notification to locate the file - 3. Drag the generated `.zip` files into the github issue window + 3. Drag the generated `.zip` files into the github issue window - ![export logs button](https://github.com/ppy/osu/assets/191335/0866443f-0728-47bc-9dbd-f2b79ac802d5) + ![export logs button](https://github.com/ppy/osu/assets/191335/cbfa5550-b7ed-4c5c-8dd0-8b87cc90ad9b) ### Mobile platforms From c1b55c7facaa675d62a68179657df8b9a1170846 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 19 Dec 2023 13:48:46 +0900 Subject: [PATCH 2013/4092] Add ScoreProcessor methods to override numeric result --- .../Scoring/CatchScoreProcessor.cs | 2 +- .../Scoring/ManiaScoreProcessor.cs | 2 +- .../Scoring/TaikoScoreProcessor.cs | 2 +- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 24 ++++++++++++++----- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index 66c76f9b17..503252df02 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.Scoring } protected override double GetComboScoreChange(JudgementResult result) - => Judgement.ToNumericResult(result.Type) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(combo_cap, combo_base)); + => GetNumericResultFor(result) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(combo_cap, combo_base)); public override ScoreRank RankFromAccuracy(double accuracy) { diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index c53f3c3e07..e5f9b33c6b 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Mania.Scoring } protected override double GetComboScoreChange(JudgementResult result) - => Judgement.ToNumericResult(result.Type) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(400, combo_base)); + => GetNumericResultFor(result) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(400, combo_base)); private class JudgementOrderComparer : IComparer { diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index a77e6db6f3..a34e977ee9 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Taiko.Scoring protected override double GetComboScoreChange(JudgementResult result) { - return Judgement.ToNumericResult(result.Type) + return GetNumericResultFor(result) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(400, combo_base)) * strongScaleValue(result); } diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index f110172988..aa8905c0b6 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -227,12 +227,12 @@ namespace osu.Game.Rulesets.Scoring if (result.Judgement.MaxResult.AffectsAccuracy()) { - currentMaximumBaseScore += Judgement.ToNumericResult(result.Judgement.MaxResult); + currentMaximumBaseScore += GetMaxNumericResultFor(result); currentAccuracyJudgementCount++; } if (result.Type.AffectsAccuracy()) - currentBaseScore += Judgement.ToNumericResult(result.Type); + currentBaseScore += GetNumericResultFor(result); if (result.Type.IsBonus()) currentBonusPortion += GetBonusScoreChange(result); @@ -276,12 +276,12 @@ namespace osu.Game.Rulesets.Scoring if (result.Judgement.MaxResult.AffectsAccuracy()) { - currentMaximumBaseScore -= Judgement.ToNumericResult(result.Judgement.MaxResult); + currentMaximumBaseScore -= GetMaxNumericResultFor(result); currentAccuracyJudgementCount--; } if (result.Type.AffectsAccuracy()) - currentBaseScore -= Judgement.ToNumericResult(result.Type); + currentBaseScore -= GetNumericResultFor(result); if (result.Type.IsBonus()) currentBonusPortion -= GetBonusScoreChange(result); @@ -297,9 +297,21 @@ namespace osu.Game.Rulesets.Scoring updateScore(); } - protected virtual double GetBonusScoreChange(JudgementResult result) => Judgement.ToNumericResult(result.Type); + protected virtual double GetBonusScoreChange(JudgementResult result) => GetNumericResultFor(result); - protected virtual double GetComboScoreChange(JudgementResult result) => Judgement.ToNumericResult(result.Judgement.MaxResult) * Math.Pow(result.ComboAfterJudgement, COMBO_EXPONENT); + protected virtual double GetComboScoreChange(JudgementResult result) => GetMaxNumericResultFor(result) * Math.Pow(result.ComboAfterJudgement, COMBO_EXPONENT); + + /// + /// Retrieves the numeric score representation for a . + /// + /// The . + protected virtual double GetNumericResultFor(JudgementResult result) => result.Judgement.NumericResultFor(result); + + /// + /// Retrieves the maximum numeric score representation for a . + /// + /// The . + protected virtual double GetMaxNumericResultFor(JudgementResult result) => result.Judgement.MaxNumericResult; protected virtual void ApplyScoreChange(JudgementResult result) { From 35c0eaee1c5ff6a55609e61aa40c14c6a3645fe9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 19 Dec 2023 13:50:46 +0900 Subject: [PATCH 2014/4092] Make taiko OKs worth 150 points --- .../Scoring/TaikoScoreProcessor.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index a34e977ee9..bc1e42c5d1 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -33,6 +33,17 @@ namespace osu.Game.Rulesets.Taiko.Scoring * strongScaleValue(result); } + protected override double GetNumericResultFor(JudgementResult result) + { + switch (result.Type) + { + case HitResult.Ok: + return 150; + } + + return base.GetNumericResultFor(result); + } + private double strongScaleValue(JudgementResult result) { if (result.HitObject is StrongNestedHitObject strong) From f2edb3ea54ef0bd5cbc181bd6b25a4ba20eaba8d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 19 Dec 2023 14:01:08 +0900 Subject: [PATCH 2015/4092] Add test --- .../TestSceneTaikoScoreProcessor.cs | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoScoreProcessor.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoScoreProcessor.cs new file mode 100644 index 0000000000..6f3b9f9748 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoScoreProcessor.cs @@ -0,0 +1,41 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Judgements; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Scoring; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + [TestFixture] + public class TestSceneTaikoScoreProcessor + { + [Test] + public void TestInaccurateHitScore() + { + var beatmap = new Beatmap + { + HitObjects = + { + new Hit(), + new Hit { StartTime = 1000 } + } + }; + + var scoreProcessor = new TaikoScoreProcessor(); + scoreProcessor.ApplyBeatmap(beatmap); + + // Apply a miss judgement + scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new TaikoJudgement()) { Type = HitResult.Great }); + scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], new TaikoJudgement()) { Type = HitResult.Ok }); + + Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(453745)); + Assert.That(scoreProcessor.Accuracy.Value, Is.EqualTo(0.75).Within(0.0001)); + } + } +} From 30957f847cdca20f1ee3a8619c406808289080a0 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 19 Dec 2023 14:27:29 +0900 Subject: [PATCH 2016/4092] Rename class/file (this is not a test scene) --- ...stSceneTaikoScoreProcessor.cs => TaikoScoreProcessorTest.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Rulesets.Taiko.Tests/{TestSceneTaikoScoreProcessor.cs => TaikoScoreProcessorTest.cs} (96%) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoScoreProcessorTest.cs similarity index 96% rename from osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoScoreProcessor.cs rename to osu.Game.Rulesets.Taiko.Tests/TaikoScoreProcessorTest.cs index 6f3b9f9748..d74fe99a9f 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoScoreProcessorTest.cs @@ -13,7 +13,7 @@ using osu.Game.Rulesets.Taiko.Scoring; namespace osu.Game.Rulesets.Taiko.Tests { [TestFixture] - public class TestSceneTaikoScoreProcessor + public class TaikoScoreProcessorTest { [Test] public void TestInaccurateHitScore() From 44efa2c540c392c998af7cd052a88d35d053aa5b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Dec 2023 15:09:03 +0900 Subject: [PATCH 2017/4092] Fix incorrect ordering of items at song select when certain sort modes are used --- .../Screens/Select/Carousel/CarouselGroup.cs | 2 +- osu.Game/Screens/Select/FilterCriteria.cs | 38 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index c353ee98ae..be841465bf 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -87,7 +87,7 @@ namespace osu.Game.Screens.Select.Carousel items.ForEach(c => c.Filter(criteria)); // Sorting is expensive, so only perform if it's actually changed. - if (lastCriteria?.Sort != criteria.Sort) + if (lastCriteria?.RequiresSorting(criteria) != false) { criteriaComparer = Comparer.Create((x, y) => { diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 811f623ee5..0bea2247ce 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -219,6 +219,44 @@ namespace osu.Game.Screens.Select public bool Equals(OptionalTextFilter other) => SearchTerm == other.SearchTerm; } + /// + /// Given a new filter criteria, decide whether a full sort needs to be performed. + /// + /// + /// + public bool RequiresSorting(FilterCriteria newCriteria) + { + if (Sort != newCriteria.Sort) + return true; + + switch (Sort) + { + // Some sorts are stable across all other changes. + // Running these sorts will sort all items, including currently hidden items. + case SortMode.Artist: + case SortMode.Author: + case SortMode.DateSubmitted: + case SortMode.DateAdded: + case SortMode.DateRanked: + case SortMode.Source: + case SortMode.Title: + return false; + + // Some sorts use aggregate max comparisons, which will change based on filtered items. + // These sorts generally ignore items hidden by filtered state, so we must force a sort under all circumstances here. + // + // This makes things very slow when typing a text search, and we probably want to consider a way to optimise things going forward. + case SortMode.LastPlayed: + case SortMode.BPM: + case SortMode.Length: + case SortMode.Difficulty: + return true; + + default: + throw new ArgumentOutOfRangeException(); + } + } + public enum MatchMode { /// From ddb67c87a8c20680c6ef77d8faa512b50d090be5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 19 Dec 2023 08:13:02 +0100 Subject: [PATCH 2018/4092] Roll back incorrect change in `ShouldMigrateToNewStandardised()` --- osu.Game/Database/StandardisedScoreMigrationTools.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index 6980e81f58..11eacd1c6b 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -26,7 +26,7 @@ namespace osu.Game.Database if (score.IsLegacyScore) return false; - if (score.TotalScoreVersion > 30000005) + if (score.TotalScoreVersion > 30000002) return false; // Recalculate the old-style standardised score to see if this was an old lazer score. From 011bd61e7d3f7301fd00bd5acd0d89120a92b61a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 19 Dec 2023 16:47:49 +0900 Subject: [PATCH 2019/4092] Give sliders in test scene a sample --- .../TestSceneSliderEarlyHitJudgement.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderEarlyHitJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderEarlyHitJudgement.cs index 9caee86a32..4ea21e51f6 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderEarlyHitJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderEarlyHitJudgement.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Screens; +using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Replays; using osu.Game.Rulesets.Judgements; @@ -160,6 +161,10 @@ namespace osu.Game.Rulesets.Osu.Tests Position = new Vector2(256 - slider_path_length / 2, 192), TickDistanceMultiplier = 3, ClassicSliderBehaviour = classic, + Samples = new[] + { + new HitSampleInfo(HitSampleInfo.HIT_NORMAL) + }, Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, From 7c05d66bd7913c3f7aa1a569af5fa1dd901c58dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 19 Dec 2023 08:57:18 +0100 Subject: [PATCH 2020/4092] Add more detail to exception --- osu.Game/Screens/Select/FilterCriteria.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 0bea2247ce..a7c8e7d093 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -253,7 +253,7 @@ namespace osu.Game.Screens.Select return true; default: - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(Sort), Sort, "Unknown sort mode"); } } From fe5e071e70cbd0a24f555fb9255dfa957246eb36 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 19 Dec 2023 17:01:52 +0900 Subject: [PATCH 2021/4092] Fix sliding sample playing before Slider's start time --- .../Objects/Drawables/DrawableSlider.cs | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 1f9a028045..b306fd38c1 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -128,8 +128,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables foreach (var drawableHitObject in NestedHitObjects) drawableHitObject.AccentColour.Value = colour.NewValue; }, true); - - Tracking.BindValueChanged(updateSlidingSample); } protected override void OnApply() @@ -166,14 +164,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables slidingSample?.Stop(); } - private void updateSlidingSample(ValueChangedEvent tracking) - { - if (tracking.NewValue) - slidingSample?.Play(); - else - slidingSample?.Stop(); - } - protected override void AddNestedHitObject(DrawableHitObject hitObject) { base.AddNestedHitObject(hitObject); @@ -238,9 +228,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Tracking.Value = SliderInputManager.Tracking; - if (Tracking.Value && slidingSample != null) - // keep the sliding sample playing at the current tracking position - slidingSample.Balance.Value = CalculateSamplePlaybackBalance(CalculateDrawableRelativePosition(Ball)); + if (slidingSample != null) + { + if (Tracking.Value && Time.Current >= HitObject.StartTime) + { + // keep the sliding sample playing at the current tracking position + if (!slidingSample.IsPlaying) + slidingSample.Play(); + slidingSample.Balance.Value = CalculateSamplePlaybackBalance(CalculateDrawableRelativePosition(Ball)); + } + else if (slidingSample.IsPlaying) + slidingSample.Stop(); + } double completionProgress = Math.Clamp((Time.Current - HitObject.StartTime) / HitObject.Duration, 0, 1); From 7e9c1b2acbaabb19564821d748deb2c97a964554 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Dec 2023 17:27:52 +0900 Subject: [PATCH 2022/4092] Use `sender`'s realm (because we can) --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 607b891beb..4c6c67c348 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -320,7 +320,7 @@ namespace osu.Game.Screens.Select // To handle the beatmap update flow, attempt to track selection changes across delete-insert transactions. // When an update occurs, the previous beatmap set is either soft or hard deleted. // Check if the current selection was potentially deleted by re-querying its validity. - bool selectedSetMarkedDeleted = realm.Run(r => r.Find(SelectedBeatmapSet.ID))?.DeletePending != false; + bool selectedSetMarkedDeleted = sender.Realm.Find(SelectedBeatmapSet.ID)?.DeletePending != false; int[] modifiedAndInserted = changes.NewModifiedIndices.Concat(changes.InsertedIndices).ToArray(); From 8f5d21dc703e3c1f3930e4b4e1c58c90acaba87a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Dec 2023 18:10:55 +0900 Subject: [PATCH 2023/4092] Actually fix realm selection retention regression --- .../SongSelect/TestScenePlaySongSelect.cs | 4 ---- osu.Game/Screens/Select/BeatmapCarousel.cs | 24 +++++++++++++++++-- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 6af53df725..518035fb82 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -464,8 +464,6 @@ namespace osu.Game.Tests.Visual.SongSelect manager.Import(testBeatmapSetInfo); }, 10); - AddStep("Force realm refresh", () => Realm.Run(r => r.Refresh())); - AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.BeatmapSet?.OnlineID, () => Is.EqualTo(originalOnlineSetID)); Task?> updateTask = null!; @@ -478,8 +476,6 @@ namespace osu.Game.Tests.Visual.SongSelect }); AddUntilStep("wait for update completion", () => updateTask.IsCompleted); - AddStep("Force realm refresh", () => Realm.Run(r => r.Refresh())); - AddUntilStep("retained selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.BeatmapSet?.OnlineID, () => Is.EqualTo(originalOnlineSetID)); } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 4c6c67c348..47cdbe34f4 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -269,8 +269,28 @@ namespace osu.Game.Screens.Select if (changes == null) return; - foreach (int i in changes.InsertedIndices) - removeBeatmapSet(sender[i].ID); + var removeableSets = changes.InsertedIndices.Select(i => sender[i].ID).ToHashSet(); + + // This schedule is required to retain selection of beatmaps over an ImportAsUpdate operation. + // This is covered by TestPlaySongSelect.TestSelectionRetainedOnBeatmapUpdate. + // + // In short, we have specialised logic in `beatmapSetsChanged` (directly below) to infer that an + // update operation has occurred. For this to work, we need to confirm the `DeletePending` flag + // of the current selection. + // + // If we don't schedule the following code, it is possible for the `deleteBeatmapSetsChanged` handler + // to be invoked before the `beatmapSetsChanged` handler (realm call order seems non-deterministic) + // which will lead to the currently selected beatmap changing via `CarouselGroupEagerSelect`. + // + // We need a better path forward here. A few ideas: + // - Avoid the necessity of having realm subscriptions on deleted/hidden items, maybe by storing all guids in realm + // to a local list so we can better look them up on receiving `DeletedIndices`. + // - Add a new property on `BeatmapSetInfo` to link to the pre-update set, and use that to handle the update case. + Schedule(() => + { + foreach (var set in removeableSets) + removeBeatmapSet(set); + }); } private void beatmapSetsChanged(IRealmCollection sender, ChangeSet? changes) From f09c6b8c1ba778d2a5a6f6f3c29de875bc3dc7a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Dec 2023 18:20:02 +0900 Subject: [PATCH 2024/4092] Change default values of new object counts to `-1` to identify non-processed values --- osu.Game/BackgroundDataStoreProcessor.cs | 2 +- osu.Game/Beatmaps/BeatmapInfo.cs | 4 ++-- osu.Game/Beatmaps/IBeatmapInfo.cs | 3 +++ osu.Game/Database/RealmAccess.cs | 17 ++++++++++++++++- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/osu.Game/BackgroundDataStoreProcessor.cs b/osu.Game/BackgroundDataStoreProcessor.cs index 33b66ecfc7..d4cd1ff6ea 100644 --- a/osu.Game/BackgroundDataStoreProcessor.cs +++ b/osu.Game/BackgroundDataStoreProcessor.cs @@ -196,7 +196,7 @@ namespace osu.Game realmAccess.Run(r => { - foreach (var b in r.All().Where(b => b.TotalObjectCount == 0)) + foreach (var b in r.All().Where(b => b.TotalObjectCount < 0 || b.EndTimeObjectCount < 0)) beatmapIds.Add(b.ID); }); diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 2d04732f91..425fd98d27 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -120,9 +120,9 @@ namespace osu.Game.Beatmaps [JsonIgnore] public bool Hidden { get; set; } - public int EndTimeObjectCount { get; set; } + public int EndTimeObjectCount { get; set; } = -1; - public int TotalObjectCount { get; set; } + public int TotalObjectCount { get; set; } = -1; /// /// Reset any fetched online linking information (and history). diff --git a/osu.Game/Beatmaps/IBeatmapInfo.cs b/osu.Game/Beatmaps/IBeatmapInfo.cs index 9dcff5ce5e..04c2017ded 100644 --- a/osu.Game/Beatmaps/IBeatmapInfo.cs +++ b/osu.Game/Beatmaps/IBeatmapInfo.cs @@ -59,11 +59,13 @@ namespace osu.Game.Beatmaps /// /// The basic star rating for this beatmap (with no mods applied). + /// Defaults to -1 (meaning not-yet-calculated). /// double StarRating { get; } /// /// The number of hitobjects in the beatmap with a distinct end time. + /// Defaults to -1 (meaning not-yet-calculated). /// /// /// Canonically, these are hitobjects are either sliders or spinners. @@ -72,6 +74,7 @@ namespace osu.Game.Beatmaps /// /// The total number of hitobjects in the beatmap. + /// Defaults to -1 (meaning not-yet-calculated). /// int TotalObjectCount { get; } } diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 191bb49b0c..ad61292c2e 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -89,8 +89,9 @@ namespace osu.Game.Database /// 35 2023-10-16 Clear key combinations of keybindings that are assigned to more than one action in a given settings section. /// 36 2023-10-26 Add LegacyOnlineID to ScoreInfo. Move osu_scores_*_high IDs stored in OnlineID to LegacyOnlineID. Reset anomalous OnlineIDs. /// 38 2023-12-10 Add EndTimeObjectCount and TotalObjectCount to BeatmapInfo. + /// 39 2023-12-19 Migrate any EndTimeObjectCount and TotalObjectCount values of 0 to -1 to better identify non-calculated values. /// - private const int schema_version = 38; + private const int schema_version = 39; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. @@ -1095,6 +1096,20 @@ namespace osu.Game.Database break; } + + case 39: + foreach (var b in migration.NewRealm.All()) + { + // Either actually no objects, or processing ran and failed. + // Reset to -1 so the next time they become zero we know that processing was attempted. + if (b.TotalObjectCount == 0 && b.EndTimeObjectCount == 0) + { + b.TotalObjectCount = -1; + b.EndTimeObjectCount = -1; + } + } + + break; } Logger.Log($"Migration completed in {stopwatch.ElapsedMilliseconds}ms"); From 372f930f8bd17b6ab4b928805fa4ee78a2100a48 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Dec 2023 18:27:44 +0900 Subject: [PATCH 2025/4092] Refactor usage of object counts for mania key count lookup to be a bit safer Protects against non-initialised values and also div-by-zero. --- .../Beatmaps/ManiaBeatmapConverter.cs | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index a496049729..def22608d6 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -59,23 +59,26 @@ namespace osu.Game.Rulesets.Mania.Beatmaps { double roundedCircleSize = Math.Round(difficulty.CircleSize); - if (new ManiaRuleset().RulesetInfo.Equals(difficulty.SourceRuleset)) + if (difficulty.SourceRuleset.ShortName == ManiaRuleset.SHORT_NAME) return (int)Math.Max(1, roundedCircleSize); double roundedOverallDifficulty = Math.Round(difficulty.OverallDifficulty); - int countSliderOrSpinner = difficulty.EndTimeObjectCount; + if (difficulty.TotalObjectCount > 0 && difficulty.EndTimeObjectCount >= 0) + { + int countSliderOrSpinner = difficulty.EndTimeObjectCount; - // In osu!stable, this division appears as if it happens on floats, but due to release-mode - // optimisations, it actually ends up happening on doubles. - double percentSpecialObjects = (double)countSliderOrSpinner / difficulty.TotalObjectCount; + // In osu!stable, this division appears as if it happens on floats, but due to release-mode + // optimisations, it actually ends up happening on doubles. + double percentSpecialObjects = (double)countSliderOrSpinner / difficulty.TotalObjectCount; - if (percentSpecialObjects < 0.2) - return 7; - if (percentSpecialObjects < 0.3 || roundedCircleSize >= 5) - return roundedOverallDifficulty > 5 ? 7 : 6; - if (percentSpecialObjects > 0.6) - return roundedOverallDifficulty > 4 ? 5 : 4; + if (percentSpecialObjects < 0.2) + return 7; + if (percentSpecialObjects < 0.3 || roundedCircleSize >= 5) + return roundedOverallDifficulty > 5 ? 7 : 6; + if (percentSpecialObjects > 0.6) + return roundedOverallDifficulty > 4 ? 5 : 4; + } return Math.Max(4, Math.Min((int)roundedOverallDifficulty + 1, 7)); } From 0a64d631e2af017dd042b8786201fd64de7b6e36 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Dec 2023 19:09:47 +0900 Subject: [PATCH 2026/4092] Add support for dual-column advanced stats --- .../Screens/Select/Details/AdvancedStats.cs | 48 ++++++++++++++----- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index ee805c2d12..3254ac4d99 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -64,21 +64,43 @@ namespace osu.Game.Screens.Select.Details } } - public AdvancedStats() + public AdvancedStats(int columns = 1) { - Child = new FillFlowContainer + switch (columns) { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new[] - { - FirstValue = new StatisticRow(), // circle size/key amount - HpDrain = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsDrain }, - Accuracy = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsAccuracy }, - ApproachRate = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsAr }, - starDifficulty = new StatisticRow(10, true) { Title = BeatmapsetsStrings.ShowStatsStars }, - }, - }; + case 1: + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new[] + { + FirstValue = new StatisticRow(), // circle size/key amount + HpDrain = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsDrain }, + Accuracy = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsAccuracy }, + ApproachRate = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsAr }, + starDifficulty = new StatisticRow(10, true) { Title = BeatmapsetsStrings.ShowStatsStars }, + }, + }; + break; + + case 2: + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Full, + Children = new[] + { + FirstValue = new StatisticRow { Width = 0.5f }, // circle size/key amount + HpDrain = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsDrain, Width = 0.5f }, + Accuracy = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsAccuracy, Width = 0.5f }, + ApproachRate = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsAr, Width = 0.5f }, + starDifficulty = new StatisticRow(10, true) { Title = BeatmapsetsStrings.ShowStatsStars, Width = 0.5f }, + }, + }; + break; + } } [BackgroundDependencyLoader] From 07bb0805e9b1980ed26212ab485193b28fa11baf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Dec 2023 19:09:56 +0900 Subject: [PATCH 2027/4092] Move advanced stats to a more permanent location on song select --- osu.Game/Screens/Select/BeatmapDetails.cs | 8 --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 55 ++++++++++++++++++++- 3 files changed, 54 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 179323176a..e1166b5abe 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -28,7 +28,6 @@ namespace osu.Game.Screens.Select private const float spacing = 10; private const float transition_duration = 250; - private readonly AdvancedStats advanced; private readonly UserRatings ratingsDisplay; private readonly MetadataSection description, source, tags; private readonly Container failRetryContainer; @@ -109,12 +108,6 @@ namespace osu.Game.Screens.Select Padding = new MarginPadding { Right = spacing / 2 }, Children = new[] { - new DetailBox().WithChild(advanced = new AdvancedStats - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Horizontal = spacing, Top = spacing * 2, Bottom = spacing }, - }), new DetailBox().WithChild(new OnlineViewContainer(string.Empty) { RelativeSizeAxes = Axes.X, @@ -180,7 +173,6 @@ namespace osu.Game.Screens.Select private void updateStatistics() { - advanced.BeatmapInfo = BeatmapInfo; description.Metadata = BeatmapInfo?.DifficultyName ?? string.Empty; source.Metadata = BeatmapInfo?.Metadata.Source ?? string.Empty; tags.Metadata = BeatmapInfo?.Metadata.Tags ?? string.Empty; diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 8bbf569566..a9ac30394f 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -305,7 +305,7 @@ namespace osu.Game.Screens.Select }, infoLabelContainer = new FillFlowContainer { - Margin = new MarginPadding { Top = 20 }, + Margin = new MarginPadding { Top = 8 }, Spacing = new Vector2(20, 0), AutoSizeAxes = Axes.Both, } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index dfea4e3794..0f2b49f9bf 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -13,6 +13,8 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Bindings; @@ -35,6 +37,7 @@ using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Edit; using osu.Game.Screens.Menu; using osu.Game.Screens.Play; +using osu.Game.Screens.Select.Details; using osu.Game.Screens.Select.Options; using osu.Game.Skinning; using osuTK; @@ -45,7 +48,7 @@ namespace osu.Game.Screens.Select { public abstract partial class SongSelect : ScreenWithBeatmapBackground, IKeyBindingHandler { - public static readonly float WEDGE_HEIGHT = 245; + public static readonly float WEDGE_HEIGHT = 200; protected const float BACKGROUND_BLUR = 20; private const float left_area_padding = 20; @@ -132,6 +135,8 @@ namespace osu.Game.Screens.Select private IDisposable? modSelectOverlayRegistration; + private AdvancedStats advancedStats = null!; + [Resolved] private MusicController music { get; set; } = null!; @@ -253,12 +258,56 @@ namespace osu.Game.Screens.Select }, }, new Container + { + RelativeSizeAxes = Axes.X, + Height = 90, + Padding = new MarginPadding(10) + { + Left = left_area_padding, + Right = left_area_padding * 2, + }, + Y = WEDGE_HEIGHT, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 10, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Hollow = true, + Colour = new Color4(130, 204, 255, 15), + Radius = 10, + }, + Children = new Drawable[] + { + new Box + { + Colour = new Color4(130, 204, 255, 40), + Blending = BlendingParameters.Additive, + RelativeSizeAxes = Axes.Both, + }, + advancedStats = new AdvancedStats(2) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Width = 0.8f, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + } + }, + } + }, + new Container { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Bottom = Footer.HEIGHT, - Top = WEDGE_HEIGHT, + Top = WEDGE_HEIGHT + 70, Left = left_area_padding, Right = left_area_padding * 2, }, @@ -797,6 +846,8 @@ namespace osu.Game.Screens.Select ModSelect.Beatmap = beatmap; + advancedStats.BeatmapInfo = beatmap.BeatmapInfo; + bool beatmapSelected = beatmap is not DummyWorkingBeatmap; if (beatmapSelected) From 3b848e85037a28afcc9fa43d99603af1c5cf456f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Dec 2023 19:18:36 +0900 Subject: [PATCH 2028/4092] Tidy up visual look --- osu.Game/Screens/Select/BeatmapDetails.cs | 24 +++++++---- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 2 +- .../Screens/Select/Details/AdvancedStats.cs | 43 ++++++++++++------- osu.Game/Screens/Select/SongSelect.cs | 4 +- 4 files changed, 47 insertions(+), 26 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index e1166b5abe..679ebfcf48 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -3,9 +3,9 @@ using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -67,12 +67,24 @@ namespace osu.Game.Screens.Select public BeatmapDetails() { + CornerRadius = 10; + Masking = true; + + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Hollow = true, + Colour = new Color4(130, 204, 255, 15), + Radius = 10, + }; + Children = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.5f), + Colour = new Color4(130, 204, 255, 40), + Blending = BlendingParameters.Additive, }, new Container { @@ -122,7 +134,8 @@ namespace osu.Game.Screens.Select }, new OsuScrollContainer { - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + Height = 250, Width = 0.5f, ScrollbarVisible = false, Padding = new MarginPadding { Left = spacing / 2 }, @@ -271,11 +284,6 @@ namespace osu.Game.Screens.Select InternalChildren = new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.5f), - }, content = new Container { RelativeSizeAxes = Axes.X, diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index a9ac30394f..2613857998 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -61,7 +61,7 @@ namespace osu.Game.Screens.Select { Type = EdgeEffectType.Glow, Colour = new Color4(130, 204, 255, 150), - Radius = 20, + Radius = 15, Roundness = 15, }; } diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 3254ac4d99..66a913fda1 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -311,23 +311,36 @@ namespace osu.Game.Screens.Select.Details Font = OsuFont.GetFont(size: 12) }, }, - bar = new Bar + new Container { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - RelativeSizeAxes = Axes.X, - Height = 5, - BackgroundColour = Color4.White.Opacity(0.5f), - Padding = new MarginPadding { Left = name_width + 10, Right = value_width + 10 }, - }, - ModBar = new Bar - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - RelativeSizeAxes = Axes.X, - Alpha = 0.5f, - Height = 5, + RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Left = name_width + 10, Right = value_width + 10 }, + Children = new Drawable[] + { + new Container + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Height = 5, + + CornerRadius = 2, + Masking = true, + Children = new Drawable[] + { + bar = new Bar + { + RelativeSizeAxes = Axes.Both, + BackgroundColour = Color4.White.Opacity(0.5f), + }, + ModBar = new Bar + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.5f, + }, + } + }, + } }, new Container { diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 0f2b49f9bf..208a8eb321 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -240,7 +240,7 @@ namespace osu.Game.Screens.Select Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = left_area_padding }, + Padding = new MarginPadding { Top = 5 }, Children = new Drawable[] { new LeftSideInteractionContainer(() => Carousel.ScrollToSelected()) @@ -264,7 +264,7 @@ namespace osu.Game.Screens.Select Padding = new MarginPadding(10) { Left = left_area_padding, - Right = left_area_padding * 2, + Right = left_area_padding * 2 + 5, }, Y = WEDGE_HEIGHT, Children = new Drawable[] From 2c5ca9c1c88b6c80cea70d47d5fdb121825e0146 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Dec 2023 19:31:05 +0900 Subject: [PATCH 2029/4092] Change default song select tab display to local leaderboard --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index ea526c6d54..0df870655f 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -37,7 +37,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.Ruleset, string.Empty); SetDefault(OsuSetting.Skin, SkinInfo.ARGON_SKIN.ToString()); - SetDefault(OsuSetting.BeatmapDetailTab, PlayBeatmapDetailArea.TabType.Details); + SetDefault(OsuSetting.BeatmapDetailTab, PlayBeatmapDetailArea.TabType.Local); SetDefault(OsuSetting.BeatmapDetailModsFilter, false); SetDefault(OsuSetting.ShowConvertedBeatmaps, true); From 502e3edac3ea2c5082718ca49d87064fdea4e92e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Dec 2023 19:39:48 +0900 Subject: [PATCH 2030/4092] Add missing invalidation call --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 47cdbe34f4..fe7eee701f 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -290,6 +290,8 @@ namespace osu.Game.Screens.Select { foreach (var set in removeableSets) removeBeatmapSet(set); + + invalidateAfterChange(); }); } From c556475c2c3522729cc1cf17b02b1497730d6e66 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Dec 2023 19:46:30 +0900 Subject: [PATCH 2031/4092] Revert to using a more manual approach to holding focus --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 23 +++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 30d7b6191e..baa7e594c1 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -132,6 +132,8 @@ namespace osu.Game.Overlays.Mods protected ShearedToggleButton? CustomisationButton { get; private set; } protected SelectAllModsButton? SelectAllModsButton { get; set; } + private bool textBoxShouldFocus; + private Sample? columnAppearSample; private WorkingBeatmap? beatmap; @@ -510,9 +512,9 @@ namespace osu.Game.Overlays.Mods TopLevelContent.MoveToY(-modAreaHeight, transition_duration, Easing.InOutCubic); if (customisationVisible.Value) - GetContainingInputManager().ChangeFocus(modSettingsArea); + SearchTextBox.KillFocus(); else - Scheduler.Add(() => GetContainingInputManager().ChangeFocus(null)); + setTextBoxFocus(textBoxShouldFocus); } /// @@ -626,8 +628,7 @@ namespace osu.Game.Overlays.Mods nonFilteredColumnCount += 1; } - if (textSearchStartsActive.Value) - SearchTextBox.HoldFocus = true; + setTextBoxFocus(textSearchStartsActive.Value); } protected override void PopOut() @@ -766,12 +767,20 @@ namespace osu.Game.Overlays.Mods return false; // TODO: should probably eventually support typical platform search shortcuts (`Ctrl-F`, `/`) - SearchTextBox.HoldFocus = !SearchTextBox.HoldFocus; - if (SearchTextBox.HoldFocus) - SearchTextBox.TakeFocus(); + setTextBoxFocus(!textBoxShouldFocus); return true; } + private void setTextBoxFocus(bool keepFocus) + { + textBoxShouldFocus = keepFocus; + + if (textBoxShouldFocus) + SearchTextBox.TakeFocus(); + else + SearchTextBox.KillFocus(); + } + #endregion #region Sample playback control From bbfdd6892ded1baf4e1d3da372e7678fe3e84d1e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Dec 2023 19:58:49 +0900 Subject: [PATCH 2032/4092] Allow choosing "Edit" from any beatmap carousel item --- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 2 +- .../Select/Carousel/CarouselGroupEagerSelect.cs | 2 +- .../Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 2 +- .../Select/Carousel/DrawableCarouselBeatmapSet.cs | 10 +++++++++- osu.Game/Screens/Select/PlaySongSelect.cs | 7 ++++--- osu.Game/Screens/Select/SongSelect.cs | 6 +++--- 6 files changed, 19 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 67822a27ee..6d2e938fb7 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -45,7 +45,7 @@ namespace osu.Game.Screens.Select.Carousel .ForEach(AddItem); } - protected override CarouselItem? GetNextToSelect() + public override CarouselItem? GetNextToSelect() { if (LastSelected == null || LastSelected.Filtered.Value) { diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs index 7f90e05744..b4313c1895 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs @@ -110,7 +110,7 @@ namespace osu.Game.Screens.Select.Carousel /// Finds the item this group would select next if it attempted selection /// /// An unfiltered item nearest to the last selected one or null if all items are filtered - protected virtual CarouselItem? GetNextToSelect() + public virtual CarouselItem? GetNextToSelect() { if (Items.Count == 0) return null; diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 3c64df656d..baf0a14062 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -91,7 +91,7 @@ namespace osu.Game.Screens.Select.Carousel if (songSelect != null) { - mainMenuItems = songSelect.CreateForwardNavigationMenuItemsForBeatmap(beatmapInfo); + mainMenuItems = songSelect.CreateForwardNavigationMenuItemsForBeatmap(() => beatmapInfo); selectRequested = b => songSelect.FinaliseSelection(b); } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index dd711b2513..f16e92a82a 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -44,6 +44,8 @@ namespace osu.Game.Screens.Select.Carousel private Task? beatmapsLoadTask; + private MenuItem[]? mainMenuItems; + [Resolved] private BeatmapManager manager { get; set; } = null!; @@ -57,8 +59,11 @@ namespace osu.Game.Screens.Select.Carousel } [BackgroundDependencyLoader] - private void load(BeatmapSetOverlay? beatmapOverlay) + private void load(BeatmapSetOverlay? beatmapOverlay, SongSelect? songSelect) { + if (songSelect != null) + mainMenuItems = songSelect.CreateForwardNavigationMenuItemsForBeatmap(() => (((CarouselBeatmapSet)Item!).GetNextToSelect() as CarouselBeatmap)!.BeatmapInfo); + restoreHiddenRequested = s => { foreach (var b in s.Beatmaps) @@ -222,6 +227,9 @@ namespace osu.Game.Screens.Select.Carousel if (Item?.State.Value == CarouselItemState.NotSelected) items.Add(new OsuMenuItem("Expand", MenuItemType.Highlighted, () => Item.State.Value = CarouselItemState.Selected)); + if (mainMenuItems != null) + items.AddRange(mainMenuItems); + if (beatmapSet.OnlineID > 0 && viewDetails != null) items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => viewDetails(beatmapSet.OnlineID))); diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 86bebdc2ff..7b7b8857f3 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.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 System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -34,10 +35,10 @@ namespace osu.Game.Screens.Select public override bool AllowExternalScreenChange => true; - public override MenuItem[] CreateForwardNavigationMenuItemsForBeatmap(BeatmapInfo beatmap) => new MenuItem[] + public override MenuItem[] CreateForwardNavigationMenuItemsForBeatmap(Func getBeatmap) => new MenuItem[] { - new OsuMenuItem(ButtonSystemStrings.Play.ToSentence(), MenuItemType.Highlighted, () => FinaliseSelection(beatmap)), - new OsuMenuItem(ButtonSystemStrings.Edit.ToSentence(), MenuItemType.Standard, () => Edit(beatmap)) + new OsuMenuItem(ButtonSystemStrings.Play.ToSentence(), MenuItemType.Highlighted, () => FinaliseSelection(getBeatmap())), + new OsuMenuItem(ButtonSystemStrings.Edit.ToSentence(), MenuItemType.Standard, () => Edit(getBeatmap())) }; protected override UserActivity InitialActivity => new UserActivity.ChoosingBeatmap(); diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index dfea4e3794..f4d1028747 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -89,11 +89,11 @@ namespace osu.Game.Screens.Select /// Creates any "action" menu items for the provided beatmap (ie. "Select", "Play", "Edit"). /// These will always be placed at the top of the context menu, with common items added below them. /// - /// The beatmap to create items for. + /// The beatmap to create items for. /// The menu items. - public virtual MenuItem[] CreateForwardNavigationMenuItemsForBeatmap(BeatmapInfo beatmap) => new MenuItem[] + public virtual MenuItem[] CreateForwardNavigationMenuItemsForBeatmap(Func getBeatmap) => new MenuItem[] { - new OsuMenuItem(@"Select", MenuItemType.Highlighted, () => FinaliseSelection(beatmap)) + new OsuMenuItem(@"Select", MenuItemType.Highlighted, () => FinaliseSelection(getBeatmap())) }; [Resolved] From d793d1cea16de4098c58fb5c50a3e20142a7a996 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 19 Dec 2023 14:52:16 +0100 Subject: [PATCH 2033/4092] Add test coverage of desired input handling behaviour --- .../TestSceneModSelectOverlay.cs | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 80be4412b3..4b101a52f4 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -572,7 +572,7 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestTextSearchActiveByDefault() { - configManager.SetValue(OsuSetting.ModSelectTextSearchStartsActive, true); + AddStep("text search starts active", () => configManager.SetValue(OsuSetting.ModSelectTextSearchStartsActive, true)); createScreen(); AddUntilStep("search text box focused", () => modSelectOverlay.SearchTextBox.HasFocus); @@ -587,7 +587,7 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestTextSearchNotActiveByDefault() { - configManager.SetValue(OsuSetting.ModSelectTextSearchStartsActive, false); + AddStep("text search does not start active", () => configManager.SetValue(OsuSetting.ModSelectTextSearchStartsActive, false)); createScreen(); AddUntilStep("search text box not focused", () => !modSelectOverlay.SearchTextBox.HasFocus); @@ -599,6 +599,31 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("search text box unfocused", () => !modSelectOverlay.SearchTextBox.HasFocus); } + [Test] + public void TestTextSearchDoesNotBlockCustomisationPanelKeyboardInteractions() + { + AddStep("text search starts active", () => configManager.SetValue(OsuSetting.ModSelectTextSearchStartsActive, true)); + createScreen(); + + AddUntilStep("search text box focused", () => modSelectOverlay.SearchTextBox.HasFocus); + + AddStep("select DT", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime() }); + AddAssert("DT selected", () => modSelectOverlay.ChildrenOfType().Count(panel => panel.Active.Value), () => Is.EqualTo(1)); + + AddStep("open customisation area", () => modSelectOverlay.CustomisationButton!.TriggerClick()); + assertCustomisationToggleState(false, true); + AddStep("hover over mod settings slider", () => + { + var slider = modSelectOverlay.ChildrenOfType().Single().ChildrenOfType>().First(); + InputManager.MoveMouseTo(slider); + }); + AddStep("press right arrow", () => InputManager.PressKey(Key.Right)); + AddAssert("DT speed changed", () => !SelectedMods.Value.OfType().Single().SpeedChange.IsDefault); + + AddStep("close customisation area", () => InputManager.PressKey(Key.Escape)); + AddUntilStep("search text box reacquired focus", () => modSelectOverlay.SearchTextBox.HasFocus); + } + [Test] public void TestDeselectAllViaKey() { From 3f41c20ac6badc09efb5cf61205ecc106c1cabc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 19 Dec 2023 17:25:15 +0100 Subject: [PATCH 2034/4092] Use safer fix for now --- osu.Game/Database/StandardisedScoreMigrationTools.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index 11eacd1c6b..d2321f4fc4 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -305,11 +305,10 @@ namespace osu.Game.Database // The clamp from below to `comboPortionFromLongestComboInScoreV1` targets near-FC scores wherein // the player had bad accuracy at the end of their longest combo, which causes the division by accuracy // to underestimate the combo portion. - // The clamp from above to `maximumAchievableComboPortionInScoreV1` targets FC scores wherein - // the player had bad accuracy at the start of the map, which causes the division by accuracy - // to overestimate the combo portion. - double comboPortionInScoreV1 = Math.Clamp(maximumAchievableComboPortionInScoreV1 * comboProportion / score.Accuracy, - comboPortionFromLongestComboInScoreV1, maximumAchievableComboPortionInScoreV1); + // Ideally, this would be clamped from above to `maximumAchievableComboPortionInScoreV1` too, + // but in practice this appears to fail for some scores (https://github.com/ppy/osu/pull/25876#issuecomment-1862248413). + // TODO: investigate the above more closely + double comboPortionInScoreV1 = Math.Max(maximumAchievableComboPortionInScoreV1 * comboProportion / score.Accuracy, comboPortionFromLongestComboInScoreV1); // Calculate how many times the longest combo the user has achieved in the play can repeat // without exceeding the combo portion in score V1 as achieved by the player. From ec578e1d9f62697eee1b2b0b9f53c6a7727c0ca7 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 19 Dec 2023 21:20:21 +0100 Subject: [PATCH 2035/4092] fix near-zero length sliders n stuff being placeable --- .../Edit/Blueprints/BananaShowerPlacementBlueprint.cs | 3 ++- .../Edit/Blueprints/JuiceStreamPlacementBlueprint.cs | 3 ++- .../Edit/Blueprints/HoldNotePlacementBlueprint.cs | 3 ++- .../Edit/Blueprints/TaikoSpanPlacementBlueprint.cs | 3 ++- osu.Game/Rulesets/Objects/SliderPath.cs | 2 +- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs index 1e63d32c41..6902f78172 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osu.Game.Rulesets.Catch.Edit.Blueprints.Components; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Edit; @@ -17,7 +18,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints private double placementStartTime; private double placementEndTime; - protected override bool IsValidForPlacement => HitObject.Duration > 0; + protected override bool IsValidForPlacement => Precision.DefinitelyBigger(HitObject.Duration, 0); public BananaShowerPlacementBlueprint() { diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamPlacementBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamPlacementBlueprint.cs index 9e50b5a80f..c8c8db1ebd 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamPlacementBlueprint.cs @@ -4,6 +4,7 @@ using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osu.Game.Rulesets.Catch.Edit.Blueprints.Components; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Edit; @@ -24,7 +25,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints private InputManager inputManager = null!; - protected override bool IsValidForPlacement => HitObject.Duration > 0; + protected override bool IsValidForPlacement => Precision.DefinitelyBigger(HitObject.Duration, 0); public JuiceStreamPlacementBlueprint() { diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs index 02ad1655b5..991b7f476c 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; @@ -23,7 +24,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints [Resolved] private IScrollingInfo scrollingInfo { get; set; } = null!; - protected override bool IsValidForPlacement => HitObject.Duration > 0; + protected override bool IsValidForPlacement => Precision.DefinitelyBigger(HitObject.Duration, 0); public HoldNotePlacementBlueprint() : base(new HoldNote()) diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs index bc4129c982..b0919417a4 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs @@ -6,6 +6,7 @@ using System; using osu.Framework.Graphics; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -25,7 +26,7 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints private readonly IHasDuration spanPlacementObject; - protected override bool IsValidForPlacement => spanPlacementObject.Duration > 0; + protected override bool IsValidForPlacement => Precision.DefinitelyBigger(spanPlacementObject.Duration, 0); public TaikoSpanPlacementBlueprint(HitObject hitObject) : base(hitObject) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index e9a192669f..dc71608132 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Objects /// public readonly Bindable ExpectedDistance = new Bindable(); - public bool HasValidLength => Distance > 0; + public bool HasValidLength => Precision.DefinitelyBigger(Distance, 0); /// /// The control points of the path. From c167f10ad5ff9b82b26ee5f3eb3b709fd7467f82 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 19 Dec 2023 21:20:45 +0100 Subject: [PATCH 2036/4092] fix crash from dragging near zero-length repeating object in timeline --- .../Compose/Components/Timeline/TimelineHitObjectBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 77afad2d4f..47dc3fb82e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -409,7 +409,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline double lengthOfOneRepeat = repeatHitObject.Duration / (repeatHitObject.RepeatCount + 1); int proposedCount = Math.Max(0, (int)Math.Round(proposedDuration / lengthOfOneRepeat) - 1); - if (proposedCount == repeatHitObject.RepeatCount || lengthOfOneRepeat == 0) + if (proposedCount == repeatHitObject.RepeatCount || Precision.AlmostEquals(lengthOfOneRepeat, 0)) return; repeatHitObject.RepeatCount = proposedCount; From 1b004dbebce337df0a38b6e3d4408758460d4399 Mon Sep 17 00:00:00 2001 From: rushiiMachine <33725716+rushiiMachine@users.noreply.github.com> Date: Tue, 19 Dec 2023 12:20:44 -0800 Subject: [PATCH 2037/4092] Allow Relax to fail and remove failable mod exclusions Allows the Relax mod to fail, and remove NF/PF/SD mod exclusion ref: https://github.com/ppy/osu/discussions/13229 --- osu.Game/Rulesets/Mods/ModFailCondition.cs | 2 +- osu.Game/Rulesets/Mods/ModNoFail.cs | 2 +- osu.Game/Rulesets/Mods/ModRelax.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModFailCondition.cs b/osu.Game/Rulesets/Mods/ModFailCondition.cs index e671c065cf..a116826e32 100644 --- a/osu.Game/Rulesets/Mods/ModFailCondition.cs +++ b/osu.Game/Rulesets/Mods/ModFailCondition.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mods { public abstract class ModFailCondition : Mod, IApplicableToHealthProcessor, IApplicableFailOverride { - public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModRelax) }; + public override Type[] IncompatibleMods => new[] { typeof(ModBlockFail) }; [SettingSource("Restart on fail", "Automatically restarts when failed.")] public BindableBool Restart { get; } = new BindableBool(); diff --git a/osu.Game/Rulesets/Mods/ModNoFail.cs b/osu.Game/Rulesets/Mods/ModNoFail.cs index 8c61d948a4..8fca98b018 100644 --- a/osu.Game/Rulesets/Mods/ModNoFail.cs +++ b/osu.Game/Rulesets/Mods/ModNoFail.cs @@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.DifficultyReduction; public override LocalisableString Description => "You can't fail, no matter what."; public override double ScoreMultiplier => 0.5; - public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModFailCondition) }; + public override Type[] IncompatibleMods => new[] { typeof(ModFailCondition) }; } } diff --git a/osu.Game/Rulesets/Mods/ModRelax.cs b/osu.Game/Rulesets/Mods/ModRelax.cs index 49c10339ee..3d672b5ef8 100644 --- a/osu.Game/Rulesets/Mods/ModRelax.cs +++ b/osu.Game/Rulesets/Mods/ModRelax.cs @@ -7,13 +7,13 @@ using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods { - public abstract class ModRelax : ModBlockFail + public abstract class ModRelax : Mod { public override string Name => "Relax"; public override string Acronym => "RX"; public override IconUsage? Icon => OsuIcon.ModRelax; public override ModType Type => ModType.Automation; public override double ScoreMultiplier => 0.1; - public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModNoFail), typeof(ModFailCondition) }; + public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay) }; } } From 85e5d74a16eead4bc1dc01049698be56d8669de1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Dec 2023 12:42:06 +0900 Subject: [PATCH 2038/4092] Apply proposed changes --- osu.Game/Screens/Select/BeatmapDetails.cs | 13 +------ .../Screens/Select/Details/AdvancedStats.cs | 34 ++++++++++++++++--- osu.Game/Screens/Select/SongSelect.cs | 13 ++----- 3 files changed, 32 insertions(+), 28 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 679ebfcf48..dec2c1c1de 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -5,7 +5,6 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -19,7 +18,6 @@ using osu.Game.Overlays.BeatmapSet; using osu.Game.Resources.Localisation.Web; using osu.Game.Screens.Select.Details; using osuTK; -using osuTK.Graphics; namespace osu.Game.Screens.Select { @@ -70,21 +68,12 @@ namespace osu.Game.Screens.Select CornerRadius = 10; Masking = true; - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Hollow = true, - Colour = new Color4(130, 204, 255, 15), - Radius = 10, - }; - Children = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, - Colour = new Color4(130, 204, 255, 40), - Blending = BlendingParameters.Additive, + Colour = Colour4.Black.Opacity(0.3f), }, new Container { diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 66a913fda1..0d68a0ec3c 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -92,11 +92,35 @@ namespace osu.Game.Screens.Select.Details Direction = FillDirection.Full, Children = new[] { - FirstValue = new StatisticRow { Width = 0.5f }, // circle size/key amount - HpDrain = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsDrain, Width = 0.5f }, - Accuracy = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsAccuracy, Width = 0.5f }, - ApproachRate = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsAr, Width = 0.5f }, - starDifficulty = new StatisticRow(10, true) { Title = BeatmapsetsStrings.ShowStatsStars, Width = 0.5f }, + FirstValue = new StatisticRow + { + Width = 0.5f, + Padding = new MarginPadding { Right = 5, Vertical = 2.5f }, + }, // circle size/key amount + HpDrain = new StatisticRow + { + Title = BeatmapsetsStrings.ShowStatsDrain, + Width = 0.5f, + Padding = new MarginPadding { Left = 5, Vertical = 2.5f }, + }, + Accuracy = new StatisticRow + { + Title = BeatmapsetsStrings.ShowStatsAccuracy, + Width = 0.5f, + Padding = new MarginPadding { Right = 5, Vertical = 2.5f }, + }, + ApproachRate = new StatisticRow + { + Title = BeatmapsetsStrings.ShowStatsAr, + Width = 0.5f, + Padding = new MarginPadding { Left = 5, Vertical = 2.5f }, + }, + starDifficulty = new StatisticRow(10, true) + { + Title = BeatmapsetsStrings.ShowStatsStars, + Width = 0.5f, + Padding = new MarginPadding { Right = 5, Vertical = 2.5f }, + }, }, }; break; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 208a8eb321..0d302dc561 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -13,7 +13,6 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; @@ -274,28 +273,20 @@ namespace osu.Game.Screens.Select RelativeSizeAxes = Axes.Both, Masking = true, CornerRadius = 10, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Hollow = true, - Colour = new Color4(130, 204, 255, 15), - Radius = 10, - }, Children = new Drawable[] { new Box { - Colour = new Color4(130, 204, 255, 40), - Blending = BlendingParameters.Additive, RelativeSizeAxes = Axes.Both, + Colour = Colour4.Black.Opacity(0.3f), }, advancedStats = new AdvancedStats(2) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Width = 0.8f, Anchor = Anchor.Centre, Origin = Anchor.Centre, + Padding = new MarginPadding(10) }, } }, From 856c59f7f75d9322e03d98b874ab04691c5bc968 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Dec 2023 14:08:12 +0900 Subject: [PATCH 2039/4092] Fix thread safety of `OnlineMetadataClient.UserStates` Closes https://github.com/ppy/osu-framework/issues/6081. --- osu.Game/Online/Metadata/OnlineMetadataClient.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/Metadata/OnlineMetadataClient.cs b/osu.Game/Online/Metadata/OnlineMetadataClient.cs index 27093d7961..b916386b06 100644 --- a/osu.Game/Online/Metadata/OnlineMetadataClient.cs +++ b/osu.Game/Online/Metadata/OnlineMetadataClient.cs @@ -97,8 +97,11 @@ namespace osu.Game.Online.Metadata { if (!connected.NewValue) { - isWatchingUserPresence.Value = false; - userStates.Clear(); + Schedule(() => + { + isWatchingUserPresence.Value = false; + userStates.Clear(); + }); return; } @@ -187,13 +190,13 @@ namespace osu.Game.Online.Metadata public override Task UserPresenceUpdated(int userId, UserPresence? presence) { - lock (userStates) + Schedule(() => { if (presence != null) userStates[userId] = presence.Value; else userStates.Remove(userId); - } + }); return Task.CompletedTask; } @@ -215,8 +218,8 @@ namespace osu.Game.Online.Metadata if (connector?.IsConnected.Value != true) throw new OperationCanceledException(); - // must happen synchronously before any remote calls to avoid misordering. - userStates.Clear(); + // must happen synchronously before any remote calls to avoid mis-ordering. + Schedule(() => userStates.Clear()); Debug.Assert(connection != null); await connection.InvokeAsync(nameof(IMetadataServer.EndWatchingUserPresence)).ConfigureAwait(false); } From d7603e8021a0a0ae3cd09ee73344c76054cbcd8a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Dec 2023 14:32:38 +0900 Subject: [PATCH 2040/4092] Adjust "classic" mod multiplier to 0.96x Following discussions on discord, this seems like the most agreed upon value. Increasing this is important so that imported legacy scores don't lose too much value. --- osu.Game/Rulesets/Mods/ModClassic.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModClassic.cs b/osu.Game/Rulesets/Mods/ModClassic.cs index 42fdee0402..16cb928bd4 100644 --- a/osu.Game/Rulesets/Mods/ModClassic.cs +++ b/osu.Game/Rulesets/Mods/ModClassic.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Mods public override string Acronym => "CL"; - public override double ScoreMultiplier => 0.5; + public override double ScoreMultiplier => 0.96; public override IconUsage? Icon => FontAwesome.Solid.History; From 14d2d0d21538e4240211fa839ab6a34256cf797b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Dec 2023 14:50:21 +0900 Subject: [PATCH 2041/4092] Remove `ModBlockFail` Was only being used by `NoFail` now. --- osu.Game/Rulesets/Mods/ModBlockFail.cs | 31 ---------------------- osu.Game/Rulesets/Mods/ModFailCondition.cs | 2 +- osu.Game/Rulesets/Mods/ModNoFail.cs | 24 ++++++++++++++++- 3 files changed, 24 insertions(+), 33 deletions(-) delete mode 100644 osu.Game/Rulesets/Mods/ModBlockFail.cs diff --git a/osu.Game/Rulesets/Mods/ModBlockFail.cs b/osu.Game/Rulesets/Mods/ModBlockFail.cs deleted file mode 100644 index cdfb36ebbc..0000000000 --- a/osu.Game/Rulesets/Mods/ModBlockFail.cs +++ /dev/null @@ -1,31 +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.Framework.Bindables; -using osu.Game.Configuration; -using osu.Game.Screens.Play; - -namespace osu.Game.Rulesets.Mods -{ - public abstract class ModBlockFail : Mod, IApplicableFailOverride, IApplicableToHUD, IReadFromConfig - { - private readonly Bindable showHealthBar = new Bindable(); - - /// - /// We never fail, 'yo. - /// - public bool PerformFail() => false; - - public bool RestartOnFail => false; - - public void ReadFromConfig(OsuConfigManager config) - { - config.BindWith(OsuSetting.ShowHealthDisplayWhenCantFail, showHealthBar); - } - - public void ApplyToHUD(HUDOverlay overlay) - { - overlay.ShowHealthBar.BindTo(showHealthBar); - } - } -} diff --git a/osu.Game/Rulesets/Mods/ModFailCondition.cs b/osu.Game/Rulesets/Mods/ModFailCondition.cs index a116826e32..471c3bfe8d 100644 --- a/osu.Game/Rulesets/Mods/ModFailCondition.cs +++ b/osu.Game/Rulesets/Mods/ModFailCondition.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mods { public abstract class ModFailCondition : Mod, IApplicableToHealthProcessor, IApplicableFailOverride { - public override Type[] IncompatibleMods => new[] { typeof(ModBlockFail) }; + public override Type[] IncompatibleMods => new[] { typeof(ModNoFail) }; [SettingSource("Restart on fail", "Automatically restarts when failed.")] public BindableBool Restart { get; } = new BindableBool(); diff --git a/osu.Game/Rulesets/Mods/ModNoFail.cs b/osu.Game/Rulesets/Mods/ModNoFail.cs index 8fca98b018..0cf81bf4c9 100644 --- a/osu.Game/Rulesets/Mods/ModNoFail.cs +++ b/osu.Game/Rulesets/Mods/ModNoFail.cs @@ -2,13 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Game.Configuration; using osu.Game.Graphics; +using osu.Game.Screens.Play; namespace osu.Game.Rulesets.Mods { - public abstract class ModNoFail : ModBlockFail + public abstract class ModNoFail : Mod, IApplicableFailOverride, IApplicableToHUD, IReadFromConfig { public override string Name => "No Fail"; public override string Acronym => "NF"; @@ -17,5 +20,24 @@ namespace osu.Game.Rulesets.Mods public override LocalisableString Description => "You can't fail, no matter what."; public override double ScoreMultiplier => 0.5; public override Type[] IncompatibleMods => new[] { typeof(ModFailCondition) }; + + private readonly Bindable showHealthBar = new Bindable(); + + /// + /// We never fail, 'yo. + /// + public bool PerformFail() => false; + + public bool RestartOnFail => false; + + public void ReadFromConfig(OsuConfigManager config) + { + config.BindWith(OsuSetting.ShowHealthDisplayWhenCantFail, showHealthBar); + } + + public void ApplyToHUD(HUDOverlay overlay) + { + overlay.ShowHealthBar.BindTo(showHealthBar); + } } } From b6f0c98a0907ad1869a19c4dc00c83cedbf4b03c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Dec 2023 14:56:52 +0900 Subject: [PATCH 2042/4092] Also apply to autopilot --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 56bf0e08e9..bf74b741d5 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -16,7 +16,7 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModAutopilot : Mod, IApplicableFailOverride, IUpdatableByPlayfield, IApplicableToDrawableRuleset + public class OsuModAutopilot : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset { public override string Name => "Autopilot"; public override string Acronym => "AP"; @@ -37,10 +37,6 @@ namespace osu.Game.Rulesets.Osu.Mods typeof(ModTouchDevice) }; - public bool PerformFail() => false; - - public bool RestartOnFail => false; - private OsuInputManager inputManager = null!; private List replayFrames = null!; From 64f62e7d904a31a31df6b0926aba6284ef4f7087 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Dec 2023 17:31:08 +0900 Subject: [PATCH 2043/4092] Fix song select running updates when screen is not active MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Who would have guessed that `Schedule` calls were there for a reason! I've tidied things up. Most of the changes I've made here are not required – the schedule is the main thing here. The reason the sound was playing is because one-too-many schedules was removed causing beatmap updates to update carousel specifics while still at the player loader screen. Note that the selection sound still plays on returning to song select, but this is not a regression. I'm looking at fixing this in a separate PR because I'm in a good place as far as understanding the logic right now and it would be a waste to leave it broken. Closes https://github.com/ppy/osu/issues/25875. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 100 ++++++++++++--------- 1 file changed, 58 insertions(+), 42 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index fe7eee701f..53529d592b 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -301,6 +301,9 @@ namespace osu.Game.Screens.Select if (loadedTestBeatmaps) return; + var setsRequiringUpdate = new HashSet(); + var setsRequiringRemoval = new HashSet(); + if (changes == null) { // During initial population, we must manually account for the fact that our original query was done on an async thread. @@ -314,67 +317,80 @@ namespace osu.Game.Screens.Select foreach (var id in realmSets) { if (!root.BeatmapSetsByID.ContainsKey(id)) - updateBeatmapSet(realm.Realm.Find(id)!.Detach()); + setsRequiringUpdate.Add(realm.Realm.Find(id)!.Detach()); } foreach (var id in root.BeatmapSetsByID.Keys) { if (!realmSets.Contains(id)) - removeBeatmapSet(id); + setsRequiringRemoval.Add(id); } + } + else + { + foreach (int i in changes.NewModifiedIndices) + setsRequiringUpdate.Add(sender[i].Detach()); - invalidateAfterChange(); - BeatmapSetsLoaded = true; - return; + foreach (int i in changes.InsertedIndices) + setsRequiringUpdate.Add(sender[i].Detach()); } - foreach (int i in changes.NewModifiedIndices) - updateBeatmapSet(sender[i].Detach()); - - foreach (int i in changes.InsertedIndices) - updateBeatmapSet(sender[i].Detach()); - - if (changes.DeletedIndices.Length > 0 && SelectedBeatmapInfo != null) + // All local operations must be scheduled. + // + // If we don't schedule, beatmaps getting changed while song select is suspended (ie. last played being updated) + // will cause unexpected sounds and operations to occur in the background. + Schedule(() => { - // If SelectedBeatmapInfo is non-null, the set should also be non-null. - Debug.Assert(SelectedBeatmapSet != null); - - // To handle the beatmap update flow, attempt to track selection changes across delete-insert transactions. - // When an update occurs, the previous beatmap set is either soft or hard deleted. - // Check if the current selection was potentially deleted by re-querying its validity. - bool selectedSetMarkedDeleted = sender.Realm.Find(SelectedBeatmapSet.ID)?.DeletePending != false; - - int[] modifiedAndInserted = changes.NewModifiedIndices.Concat(changes.InsertedIndices).ToArray(); - - if (selectedSetMarkedDeleted && modifiedAndInserted.Any()) + try { - // If it is no longer valid, make the bold assumption that an updated version will be available in the modified/inserted indices. - // This relies on the full update operation being in a single transaction, so please don't change that. - foreach (int i in modifiedAndInserted) + foreach (var set in setsRequiringRemoval) + removeBeatmapSet(set); + + foreach (var set in setsRequiringUpdate) + updateBeatmapSet(set); + + if (changes?.DeletedIndices.Length > 0 && SelectedBeatmapInfo != null) { - var beatmapSetInfo = sender[i]; + // If SelectedBeatmapInfo is non-null, the set should also be non-null. + Debug.Assert(SelectedBeatmapSet != null); - foreach (var beatmapInfo in beatmapSetInfo.Beatmaps) + // To handle the beatmap update flow, attempt to track selection changes across delete-insert transactions. + // When an update occurs, the previous beatmap set is either soft or hard deleted. + // Check if the current selection was potentially deleted by re-querying its validity. + bool selectedSetMarkedDeleted = realm.Run(r => r.Find(SelectedBeatmapSet.ID)?.DeletePending != false); + + if (selectedSetMarkedDeleted && setsRequiringUpdate.Any()) { - if (!((IBeatmapMetadataInfo)beatmapInfo.Metadata).Equals(SelectedBeatmapInfo.Metadata)) - continue; - - // Best effort matching. We can't use ID because in the update flow a new version will get its own GUID. - if (beatmapInfo.DifficultyName == SelectedBeatmapInfo.DifficultyName) + // If it is no longer valid, make the bold assumption that an updated version will be available in the modified/inserted indices. + // This relies on the full update operation being in a single transaction, so please don't change that. + foreach (var set in setsRequiringUpdate) { - SelectBeatmap(beatmapInfo); - return; + foreach (var beatmapInfo in set.Beatmaps) + { + if (!((IBeatmapMetadataInfo)beatmapInfo.Metadata).Equals(SelectedBeatmapInfo.Metadata)) + continue; + + // Best effort matching. We can't use ID because in the update flow a new version will get its own GUID. + if (beatmapInfo.DifficultyName == SelectedBeatmapInfo.DifficultyName) + { + SelectBeatmap(beatmapInfo); + return; + } + } } + + // If a direct selection couldn't be made, it's feasible that the difficulty name (or beatmap metadata) changed. + // Let's attempt to follow set-level selection anyway. + SelectBeatmap(setsRequiringUpdate.First().Beatmaps.First()); } } - - // If a direct selection couldn't be made, it's feasible that the difficulty name (or beatmap metadata) changed. - // Let's attempt to follow set-level selection anyway. - SelectBeatmap(sender[modifiedAndInserted.First()].Beatmaps.First()); } - } - - invalidateAfterChange(); + finally + { + BeatmapSetsLoaded = true; + invalidateAfterChange(); + } + }); } private void beatmapsChanged(IRealmCollection sender, ChangeSet? changes) From 104fbbde9460d42c10fe5d4d60db2dbfc4218421 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 20 Dec 2023 18:35:45 +0900 Subject: [PATCH 2044/4092] Change mania scoring to match ScoreV2 --- .../Objects/Drawables/DrawableNote.cs | 40 ----------------- .../Drawables/DrawableNotePerfectBonus.cs | 26 ----------- .../Scoring/ManiaScoreProcessor.cs | 43 +++++++++++++++++-- osu.Game.Rulesets.Mania/UI/Column.cs | 1 - 4 files changed, 40 insertions(+), 70 deletions(-) delete mode 100644 osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNotePerfectBonus.cs diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index c70dfcb761..680009bc4c 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -13,8 +13,6 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Skinning.Default; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit; @@ -40,8 +38,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private Drawable headPiece; - private DrawableNotePerfectBonus perfectBonus; - public DrawableNote() : this(null) { @@ -93,10 +89,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (!userTriggered) { if (!HitObject.HitWindows.CanBeHit(timeOffset)) - { - perfectBonus.TriggerResult(false); ApplyResult(r => r.Type = r.Judgement.MinResult); - } return; } @@ -107,16 +100,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables result = GetCappedResult(result); - perfectBonus.TriggerResult(result == HitResult.Perfect); ApplyResult(r => r.Type = result); } - public override void MissForcefully() - { - perfectBonus.TriggerResult(false); - base.MissForcefully(); - } - /// /// Some objects in mania may want to limit the max result. /// @@ -137,32 +123,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { } - protected override void AddNestedHitObject(DrawableHitObject hitObject) - { - switch (hitObject) - { - case DrawableNotePerfectBonus bonus: - AddInternal(perfectBonus = bonus); - break; - } - } - - protected override void ClearNestedHitObjects() - { - RemoveInternal(perfectBonus, false); - } - - protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject) - { - switch (hitObject) - { - case NotePerfectBonus bonus: - return new DrawableNotePerfectBonus(bonus); - } - - return base.CreateNestedHitObject(hitObject); - } - private void updateSnapColour() { if (beatmap == null || HitObject == null) return; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNotePerfectBonus.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNotePerfectBonus.cs deleted file mode 100644 index 70ddb60296..0000000000 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNotePerfectBonus.cs +++ /dev/null @@ -1,26 +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.Mania.Objects.Drawables -{ - public partial class DrawableNotePerfectBonus : DrawableManiaHitObject - { - public override bool DisplayResult => false; - - public DrawableNotePerfectBonus() - : this(null!) - { - } - - public DrawableNotePerfectBonus(NotePerfectBonus hitObject) - : base(hitObject) - { - } - - /// - /// Apply a judgement result. - /// - /// Whether this tick was reached. - internal void TriggerResult(bool hit) => ApplyResult(r => r.Type = hit ? r.Judgement.MaxResult : r.Judgement.MinResult); - } -} diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index e5f9b33c6b..31435ddaad 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -26,13 +26,50 @@ namespace osu.Game.Rulesets.Mania.Scoring protected override double ComputeTotalScore(double comboProgress, double accuracyProgress, double bonusPortion) { - return 10000 * comboProgress - + 990000 * Math.Pow(Accuracy.Value, 2 + 2 * Accuracy.Value) * accuracyProgress + return 200000 * comboProgress + + 800000 * Math.Pow(Accuracy.Value, 2 + 2 * Accuracy.Value) * accuracyProgress + bonusPortion; } + protected override double GetNumericResultFor(JudgementResult result) + { + switch (result.Type) + { + case HitResult.Perfect: + return 305; + } + + return base.GetNumericResultFor(result); + } + + protected override double GetMaxNumericResultFor(JudgementResult result) + { + switch (result.Judgement.MaxResult) + { + case HitResult.Perfect: + return 305; + } + + return base.GetMaxNumericResultFor(result); + } + protected override double GetComboScoreChange(JudgementResult result) - => GetNumericResultFor(result) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(400, combo_base)); + { + double numericResult; + + switch (result.Type) + { + case HitResult.Perfect: + numericResult = 300; + break; + + default: + numericResult = GetNumericResultFor(result); + break; + } + + return numericResult * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(400, combo_base)); + } private class JudgementOrderComparer : IComparer { diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 9489281176..6cd55bb099 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -109,7 +109,6 @@ namespace osu.Game.Rulesets.Mania.UI TopLevelContainer.Add(HitObjectArea.Explosions.CreateProxy()); RegisterPool(10, 50); - RegisterPool(10, 50); RegisterPool(10, 50); RegisterPool(10, 50); RegisterPool(10, 50); From 023bbda7dbb381255e66c84ac209c6510662380f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 20 Dec 2023 18:43:06 +0900 Subject: [PATCH 2045/4092] Change mania to 85% acc / 15% combo --- osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index 31435ddaad..d5191c880a 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -26,8 +26,8 @@ namespace osu.Game.Rulesets.Mania.Scoring protected override double ComputeTotalScore(double comboProgress, double accuracyProgress, double bonusPortion) { - return 200000 * comboProgress - + 800000 * Math.Pow(Accuracy.Value, 2 + 2 * Accuracy.Value) * accuracyProgress + return 150000 * comboProgress + + 850000 * Math.Pow(Accuracy.Value, 2 + 2 * Accuracy.Value) * accuracyProgress + bonusPortion; } From e003462f7d4077d43513d233ae2b4143270be3e2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Dec 2023 19:09:07 +0900 Subject: [PATCH 2046/4092] Fix beatmap updates causing one extra carousel selection --- osu.Game/Screens/Select/BeatmapCarousel.cs | 85 +++++++++++-------- .../Carousel/CarouselGroupEagerSelect.cs | 8 +- 2 files changed, 54 insertions(+), 39 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 53529d592b..a51b54b21d 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -455,30 +455,13 @@ namespace osu.Game.Screens.Select private void updateBeatmapSet(BeatmapSetInfo beatmapSet) { - Guid? previouslySelectedID = null; - originalBeatmapSetsDetached.RemoveAll(set => set.ID == beatmapSet.ID); originalBeatmapSetsDetached.Add(beatmapSet.Detach()); - // If the selected beatmap is about to be removed, store its ID so it can be re-selected if required - if (selectedBeatmapSet?.BeatmapSet.ID == beatmapSet.ID) - previouslySelectedID = selectedBeatmap?.BeatmapInfo.ID; - - var removedSets = root.RemoveItemsByID(beatmapSet.ID); - - foreach (var removedSet in removedSets) - { - // If we don't remove this here, it may remain in a hidden state until scrolled off screen. - // Doesn't really affect anything during actual user interaction, but makes testing annoying. - var removedDrawable = Scroll.FirstOrDefault(c => c.Item == removedSet); - if (removedDrawable != null) - expirePanelImmediately(removedDrawable); - } + var newSets = new List(); if (beatmapsSplitOut) { - var newSets = new List(); - foreach (var beatmap in beatmapSet.Beatmaps) { var newSet = createCarouselSet(new BeatmapSetInfo(new[] { beatmap }) @@ -489,18 +472,7 @@ namespace osu.Game.Screens.Select }); if (newSet != null) - { newSets.Add(newSet); - root.AddItem(newSet); - } - } - - // check if we can/need to maintain our current selection. - if (previouslySelectedID != null) - { - var toSelect = newSets.FirstOrDefault(s => s.Beatmaps.Any(b => b.BeatmapInfo.ID == previouslySelectedID)) - ?? newSets.FirstOrDefault(); - select(toSelect); } } else @@ -508,13 +480,18 @@ namespace osu.Game.Screens.Select var newSet = createCarouselSet(beatmapSet); if (newSet != null) - { - root.AddItem(newSet); + newSets.Add(newSet); + } - // check if we can/need to maintain our current selection. - if (previouslySelectedID != null) - select((CarouselItem?)newSet.Beatmaps.FirstOrDefault(b => b.BeatmapInfo.ID == previouslySelectedID) ?? newSet); - } + var removedSets = root.ReplaceItem(beatmapSet, newSets); + + // If we don't remove these here, it may remain in a hidden state until scrolled off screen. + // Doesn't really affect anything during actual user interaction, but makes testing annoying. + foreach (var removedSet in removedSets) + { + var removedDrawable = Scroll.FirstOrDefault(c => c.Item == removedSet); + if (removedDrawable != null) + expirePanelImmediately(removedDrawable); } } @@ -1207,6 +1184,44 @@ namespace osu.Game.Screens.Select base.AddItem(i); } + /// + /// A special method to handle replace operations (general for updating a beatmap). + /// Avoids event-driven selection flip-flopping during the remove/add process. + /// + /// The beatmap set to be replaced. + /// All new items to replace the removed beatmap set. + /// All removed items, for any further processing. + public IEnumerable ReplaceItem(BeatmapSetInfo oldItem, List newItems) + { + // Without doing this, the removal of the old beatmap will cause carousel's eager selection + // logic to invoke, causing one unnecessary selection. + DisableSelection = true; + var removedSets = RemoveItemsByID(oldItem.ID); + DisableSelection = false; + + foreach (var set in newItems) + AddItem(set); + + Guid? previouslySelectedID = null; + + var selectedBeatmap = (LastSelected as CarouselBeatmap)?.BeatmapInfo; + + // If the selected beatmap is about to be removed, store its ID so it can be re-selected if required + if (selectedBeatmap?.BeatmapSet?.ID == oldItem.ID) + previouslySelectedID = selectedBeatmap.ID; + + // check if we can/need to maintain our current selection. + if (previouslySelectedID != null) + { + var toSelect = newItems.FirstOrDefault(s => s.Beatmaps.Any(b => b.BeatmapInfo.ID == previouslySelectedID)) + ?? newItems.First(); + + toSelect.State.Value = CarouselItemState.Selected; + } + + return removedSets; + } + public IEnumerable RemoveItemsByID(Guid beatmapSetID) { if (BeatmapSetsByID.TryGetValue(beatmapSetID, out var carouselBeatmapSets)) diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs index b4313c1895..cf4ba5924f 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs @@ -36,13 +36,13 @@ namespace osu.Game.Screens.Select.Carousel /// items have been filtered. This bool will be true during the base /// operation. /// - private bool filteringItems; + protected bool DisableSelection; public override void Filter(FilterCriteria criteria) { - filteringItems = true; + DisableSelection = true; base.Filter(criteria); - filteringItems = false; + DisableSelection = false; attemptSelection(); } @@ -95,7 +95,7 @@ namespace osu.Game.Screens.Select.Carousel private void attemptSelection() { - if (filteringItems) return; + if (DisableSelection) return; // we only perform eager selection if we are a currently selected group. if (State.Value != CarouselItemState.Selected) return; From 7fa4dcf0fbd95c3f68de861a4985613c0ed29538 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Dec 2023 19:27:57 +0900 Subject: [PATCH 2047/4092] Fix selection retention logic copy paste failure --- osu.Game/Screens/Select/BeatmapCarousel.cs | 25 +++++++++++----------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index a51b54b21d..370b559897 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -1193,6 +1193,12 @@ namespace osu.Game.Screens.Select /// All removed items, for any further processing. public IEnumerable ReplaceItem(BeatmapSetInfo oldItem, List newItems) { + var previousSelection = (LastSelected as CarouselBeatmapSet)?.Beatmaps + .FirstOrDefault(s => s.State.Value == CarouselItemState.Selected) + ?.BeatmapInfo; + + bool wasSelected = previousSelection?.BeatmapSet?.ID == oldItem.ID; + // Without doing this, the removal of the old beatmap will cause carousel's eager selection // logic to invoke, causing one unnecessary selection. DisableSelection = true; @@ -1202,21 +1208,14 @@ namespace osu.Game.Screens.Select foreach (var set in newItems) AddItem(set); - Guid? previouslySelectedID = null; - - var selectedBeatmap = (LastSelected as CarouselBeatmap)?.BeatmapInfo; - - // If the selected beatmap is about to be removed, store its ID so it can be re-selected if required - if (selectedBeatmap?.BeatmapSet?.ID == oldItem.ID) - previouslySelectedID = selectedBeatmap.ID; - - // check if we can/need to maintain our current selection. - if (previouslySelectedID != null) + // Check if we can/need to maintain our current selection. + if (wasSelected) { - var toSelect = newItems.FirstOrDefault(s => s.Beatmaps.Any(b => b.BeatmapInfo.ID == previouslySelectedID)) - ?? newItems.First(); + CarouselBeatmap? matchingBeatmap = newItems.SelectMany(s => s.Beatmaps) + .FirstOrDefault(b => b.BeatmapInfo.ID == previousSelection?.ID); - toSelect.State.Value = CarouselItemState.Selected; + if (matchingBeatmap != null) + matchingBeatmap.State.Value = CarouselItemState.Selected; } return removedSets; From 5284b95bb8ffe943f8e53c52b33c0275ac59b529 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Dec 2023 19:42:05 +0900 Subject: [PATCH 2048/4092] Schedule even more --- osu.Game/Online/Metadata/OnlineMetadataClient.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Metadata/OnlineMetadataClient.cs b/osu.Game/Online/Metadata/OnlineMetadataClient.cs index b916386b06..6d00ce7551 100644 --- a/osu.Game/Online/Metadata/OnlineMetadataClient.cs +++ b/osu.Game/Online/Metadata/OnlineMetadataClient.cs @@ -208,7 +208,7 @@ namespace osu.Game.Online.Metadata Debug.Assert(connection != null); await connection.InvokeAsync(nameof(IMetadataServer.BeginWatchingUserPresence)).ConfigureAwait(false); - isWatchingUserPresence.Value = true; + Schedule(() => isWatchingUserPresence.Value = true); } public override async Task EndWatchingUserPresence() @@ -218,14 +218,14 @@ namespace osu.Game.Online.Metadata if (connector?.IsConnected.Value != true) throw new OperationCanceledException(); - // must happen synchronously before any remote calls to avoid mis-ordering. + // must be scheduled before any remote calls to avoid mis-ordering. Schedule(() => userStates.Clear()); Debug.Assert(connection != null); await connection.InvokeAsync(nameof(IMetadataServer.EndWatchingUserPresence)).ConfigureAwait(false); } finally { - isWatchingUserPresence.Value = false; + Schedule(() => isWatchingUserPresence.Value = false); } } From e0c27510f23b71df4d13b78d7b4800135a3d16ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Dec 2023 19:47:36 +0900 Subject: [PATCH 2049/4092] Remove remaining usage of `NotePerfectBonus` --- osu.Game.Rulesets.Mania/Objects/Note.cs | 8 -------- .../Objects/NotePerfectBonus.cs | 20 ------------------- 2 files changed, 28 deletions(-) delete mode 100644 osu.Game.Rulesets.Mania/Objects/NotePerfectBonus.cs diff --git a/osu.Game.Rulesets.Mania/Objects/Note.cs b/osu.Game.Rulesets.Mania/Objects/Note.cs index 5914132624..0035960c63 100644 --- a/osu.Game.Rulesets.Mania/Objects/Note.cs +++ b/osu.Game.Rulesets.Mania/Objects/Note.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.Threading; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Judgements; @@ -13,12 +12,5 @@ namespace osu.Game.Rulesets.Mania.Objects public class Note : ManiaHitObject { public override Judgement CreateJudgement() => new ManiaJudgement(); - - protected override void CreateNestedHitObjects(CancellationToken cancellationToken) - { - base.CreateNestedHitObjects(cancellationToken); - - AddNested(new NotePerfectBonus { StartTime = StartTime }); - } } } diff --git a/osu.Game.Rulesets.Mania/Objects/NotePerfectBonus.cs b/osu.Game.Rulesets.Mania/Objects/NotePerfectBonus.cs deleted file mode 100644 index def4c01268..0000000000 --- a/osu.Game.Rulesets.Mania/Objects/NotePerfectBonus.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.Judgements; -using osu.Game.Rulesets.Mania.Judgements; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Mania.Objects -{ - public class NotePerfectBonus : ManiaHitObject - { - public override Judgement CreateJudgement() => new NotePerfectBonusJudgement(); - protected override HitWindows CreateHitWindows() => HitWindows.Empty; - - public class NotePerfectBonusJudgement : ManiaJudgement - { - public override HitResult MaxResult => HitResult.SmallBonus; - } - } -} From d417b156b25b9541ae88d1acc1d93aa571abbaf0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Dec 2023 19:57:42 +0900 Subject: [PATCH 2050/4092] Update test expectations --- .../Mods/TestSceneManiaModDoubleTime.cs | 7 ++++--- osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs | 6 +++--- osu.Game.Rulesets.Mania.Tests/TestSceneMaximumScore.cs | 4 ++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs index c717f03f51..975e43ec08 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using NUnit.Framework; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mania.Objects; @@ -25,8 +26,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods public void TestHitWindowWithoutDoubleTime() => CreateModTest(new ModTestData { PassCondition = () => Player.ScoreProcessor.JudgedHits > 0 - && Player.ScoreProcessor.Accuracy.Value == 1 - && Player.ScoreProcessor.TotalScore.Value == 1_000_000, + && Precision.AlmostEquals(Player.ScoreProcessor.Accuracy.Value, 0.9836, 0.01) + && Player.ScoreProcessor.TotalScore.Value == 946_049, Autoplay = false, Beatmap = new Beatmap { @@ -53,7 +54,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods Mod = doubleTime, PassCondition = () => Player.ScoreProcessor.JudgedHits > 0 && Player.ScoreProcessor.Accuracy.Value == 1 - && Player.ScoreProcessor.TotalScore.Value == (long)(1_000_010 * doubleTime.ScoreMultiplier), + && Player.ScoreProcessor.TotalScore.Value == (long)(1_000_000 * doubleTime.ScoreMultiplier), Autoplay = false, Beatmap = new Beatmap { diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs index 044ce37832..d752c443cc 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs @@ -201,11 +201,11 @@ namespace osu.Game.Rulesets.Mania.Tests assertHeadJudgement(HitResult.Perfect); // judgement combo offset by perfect bonus judgement. see logic in DrawableNote.CheckForResult. - assertComboAtJudgement(1, 1); + assertComboAtJudgement(0, 1); assertTailJudgement(HitResult.Meh); - assertComboAtJudgement(2, 0); + assertComboAtJudgement(1, 0); // judgement combo offset by perfect bonus judgement. see logic in DrawableNote.CheckForResult. - assertComboAtJudgement(4, 1); + assertComboAtJudgement(3, 1); } /// diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneMaximumScore.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneMaximumScore.cs index edf866952b..ee6d999932 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneMaximumScore.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneMaximumScore.cs @@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Mania.Tests AddAssert("all objects perfectly judged", () => judgementResults.Select(result => result.Type), () => Is.EquivalentTo(judgementResults.Select(result => result.Judgement.MaxResult))); - AddAssert("score is correct", () => currentPlayer.ScoreProcessor.TotalScore.Value, () => Is.EqualTo(1_000_030)); + AddAssert("score is correct", () => currentPlayer.ScoreProcessor.TotalScore.Value, () => Is.EqualTo(1_000_000)); } [Test] @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Mania.Tests AddAssert("all objects perfectly judged", () => judgementResults.Select(result => result.Type), () => Is.EquivalentTo(judgementResults.Select(result => result.Judgement.MaxResult))); - AddAssert("score is correct", () => currentPlayer.ScoreProcessor.TotalScore.Value, () => Is.EqualTo(1_000_040)); + AddAssert("score is correct", () => currentPlayer.ScoreProcessor.TotalScore.Value, () => Is.EqualTo(1_000_000)); } private void performTest(List hitObjects, List frames) From 38d6b7f45b7d07bfb0b9e5bdac1163dda0dcc25b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 20 Dec 2023 20:03:13 +0900 Subject: [PATCH 2051/4092] Update total score conversion --- osu.Game/Database/StandardisedScoreMigrationTools.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index 6484500bcc..a9225050b7 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -367,8 +367,8 @@ namespace osu.Game.Database case 3: return (long)Math.Round(( - 990000 * comboProportion - + 10000 * Math.Pow(score.Accuracy, 2 + 2 * score.Accuracy) + 850000 * comboProportion + + 150000 * Math.Pow(score.Accuracy, 2 + 2 * score.Accuracy) + bonusProportion) * modMultiplier); default: From 9b383e3276c23ec9f070b3ab1ccd01df969c83a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Dec 2023 20:23:19 +0900 Subject: [PATCH 2052/4092] Add support for showing tick misses --- .../Objects/Drawables/DrawableSliderRepeat.cs | 2 - .../Objects/Drawables/DrawableSliderTail.cs | 5 -- .../Objects/Drawables/DrawableSliderTick.cs | 2 - .../Scoring/OsuHitWindows.cs | 2 + .../Skinning/Argon/ArgonJudgementPiece.cs | 30 +++++------ osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 5 +- osu.Game/Graphics/OsuColour.cs | 1 + .../Judgements/DefaultJudgementPiece.cs | 16 +++--- .../Rulesets/Judgements/DrawableJudgement.cs | 9 ++-- osu.Game/Rulesets/Scoring/HitResult.cs | 2 + osu.Game/Skinning/LegacyJudgementPieceNew.cs | 2 +- osu.Game/Skinning/LegacyJudgementPieceOld.cs | 52 +++++++++---------- osu.Game/Skinning/LegacySkin.cs | 6 +-- 13 files changed, 62 insertions(+), 72 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index 0c8e5b765f..c6d4f7c4ca 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -34,8 +34,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private Drawable scaleContainer; - public override bool DisplayResult => false; - public DrawableSliderRepeat() : base(null) { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index 60bad5d4a7..c4731118a1 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -24,11 +24,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject; - /// - /// The judgement text is provided by the . - /// - public override bool DisplayResult => false; - /// /// Whether the hit samples only play on successful hits. /// If false, the hit samples will also play on misses. diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index cb323f4ac7..d64fb0bcc6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -20,8 +20,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private const float default_tick_size = 16; - public override bool DisplayResult => false; - protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject; private SkinnableDrawable scaleContainer; diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs index fd86e0eeda..5b2c95b536 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs @@ -28,6 +28,8 @@ namespace osu.Game.Rulesets.Osu.Scoring case HitResult.Ok: case HitResult.Meh: case HitResult.Miss: + case HitResult.LargeTickMiss: + case HitResult.IgnoreMiss: return true; } diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs index 6f55d93eff..94766cb077 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs @@ -62,25 +62,23 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon /// public virtual void PlayAnimation() { - switch (Result) + if (Result.IsHit()) { - default: - JudgementText - .FadeInFromZero(300, Easing.OutQuint) - .ScaleTo(Vector2.One) - .ScaleTo(new Vector2(1.2f), 1800, Easing.OutQuint); - break; + JudgementText + .FadeInFromZero(300, Easing.OutQuint) + .ScaleTo(Vector2.One) + .ScaleTo(new Vector2(1.2f), 1800, Easing.OutQuint); + } + else + { + this.ScaleTo(1.6f); + this.ScaleTo(1, 100, Easing.In); - case HitResult.Miss: - this.ScaleTo(1.6f); - this.ScaleTo(1, 100, Easing.In); + this.MoveTo(Vector2.Zero); + this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint); - this.MoveTo(Vector2.Zero); - this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint); - - this.RotateTo(0); - this.RotateTo(40, 800, Easing.InQuint); - break; + this.RotateTo(0); + this.RotateTo(40, 800, Easing.InQuint); } this.FadeOutFromOne(800); diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 15ca0a90de..3f60ce3610 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -170,7 +170,10 @@ namespace osu.Game.Rulesets.Osu.UI if (!judgedObject.DisplayResult || !DisplayJudgements.Value) return; - DrawableOsuJudgement explosion = poolDictionary[result.Type].Get(doj => doj.Apply(result, judgedObject)); + if (!poolDictionary.TryGetValue(result.Type, out var pool)) + return; + + DrawableOsuJudgement explosion = pool.Get(doj => doj.Apply(result, judgedObject)); judgementLayer.Add(explosion); diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index a417164e27..caa2037691 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -77,6 +77,7 @@ namespace osu.Game.Graphics { case HitResult.SmallTickMiss: case HitResult.LargeTickMiss: + case HitResult.IgnoreMiss: case HitResult.Miss: case HitResult.ComboBreak: return Red; diff --git a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs index d5f586dc35..3c5e37f91c 100644 --- a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs +++ b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs @@ -38,18 +38,16 @@ namespace osu.Game.Rulesets.Judgements /// public virtual void PlayAnimation() { - switch (Result) + if (Result != HitResult.None && !Result.IsHit()) { - case HitResult.Miss: - this.ScaleTo(1.6f); - this.ScaleTo(1, 100, Easing.In); + this.ScaleTo(1.6f); + this.ScaleTo(1, 100, Easing.In); - this.MoveTo(Vector2.Zero); - this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint); + this.MoveTo(Vector2.Zero); + this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint); - this.RotateTo(0); - this.RotateTo(40, 800, Easing.InQuint); - break; + this.RotateTo(0); + this.RotateTo(40, 800, Easing.InQuint); } this.FadeOutFromOne(800); diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 15434fcc04..b4686c52f3 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -133,12 +133,11 @@ namespace osu.Game.Rulesets.Judgements case HitResult.None: break; - case HitResult.Miss: - ApplyMissAnimations(); - break; - default: - ApplyHitAnimations(); + if (Result.Type.IsHit()) + ApplyHitAnimations(); + else + ApplyMissAnimations(); break; } diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index 9705421571..b490501021 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -86,6 +86,7 @@ namespace osu.Game.Rulesets.Scoring /// Indicates a large tick miss. /// [EnumMember(Value = "large_tick_miss")] + [Description(@"x")] [Order(10)] LargeTickMiss, @@ -117,6 +118,7 @@ namespace osu.Game.Rulesets.Scoring /// Indicates a miss that should be ignored for scoring purposes. /// [EnumMember(Value = "ignore_miss")] + [Description("x")] [Order(13)] IgnoreMiss, diff --git a/osu.Game/Skinning/LegacyJudgementPieceNew.cs b/osu.Game/Skinning/LegacyJudgementPieceNew.cs index 9b1ff9b22f..a93c48ba63 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceNew.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceNew.cs @@ -50,7 +50,7 @@ namespace osu.Game.Skinning }); } - if (result != HitResult.Miss) + if (result.IsHit()) { //new judgement shows old as a temporary effect AddInternal(temporaryOldStyle = new LegacyJudgementPieceOld(result, createMainDrawable, 1.05f, true) diff --git a/osu.Game/Skinning/LegacyJudgementPieceOld.cs b/osu.Game/Skinning/LegacyJudgementPieceOld.cs index 082d0e4a67..5381cfc050 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceOld.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceOld.cs @@ -52,39 +52,35 @@ namespace osu.Game.Skinning if (animation?.FrameCount > 1 && !forceTransforms) return; - switch (result) + if (result.IsHit()) { - case HitResult.Miss: - this.ScaleTo(1.6f); - this.ScaleTo(1, 100, Easing.In); + this.ScaleTo(0.6f).Then() + .ScaleTo(1.1f, fade_in_length * 0.8f).Then() // t = 0.8 + .Delay(fade_in_length * 0.2f) // t = 1.0 + .ScaleTo(0.9f, fade_in_length * 0.2f).Then() // t = 1.2 + // stable dictates scale of 0.9->1 over time 1.0 to 1.4, but we are already at 1.2. + // so we need to force the current value to be correct at 1.2 (0.95) then complete the + // second half of the transform. + .ScaleTo(0.95f).ScaleTo(finalScale, fade_in_length * 0.2f); // t = 1.4 + } + else + { + this.ScaleTo(1.6f); + this.ScaleTo(1, 100, Easing.In); - decimal? legacyVersion = skin.GetConfig(SkinConfiguration.LegacySetting.Version)?.Value; + decimal? legacyVersion = skin.GetConfig(SkinConfiguration.LegacySetting.Version)?.Value; - if (legacyVersion >= 2.0m) - { - this.MoveTo(new Vector2(0, -5)); - this.MoveToOffset(new Vector2(0, 80), fade_out_delay + fade_out_length, Easing.In); - } + if (legacyVersion >= 2.0m) + { + this.MoveTo(new Vector2(0, -5)); + this.MoveToOffset(new Vector2(0, 80), fade_out_delay + fade_out_length, Easing.In); + } - float rotation = RNG.NextSingle(-8.6f, 8.6f); + float rotation = RNG.NextSingle(-8.6f, 8.6f); - this.RotateTo(0); - this.RotateTo(rotation, fade_in_length) - .Then().RotateTo(rotation * 2, fade_out_delay + fade_out_length - fade_in_length, Easing.In); - break; - - default: - - this.ScaleTo(0.6f).Then() - .ScaleTo(1.1f, fade_in_length * 0.8f).Then() // t = 0.8 - .Delay(fade_in_length * 0.2f) // t = 1.0 - .ScaleTo(0.9f, fade_in_length * 0.2f).Then() // t = 1.2 - - // stable dictates scale of 0.9->1 over time 1.0 to 1.4, but we are already at 1.2. - // so we need to force the current value to be correct at 1.2 (0.95) then complete the - // second half of the transform. - .ScaleTo(0.95f).ScaleTo(finalScale, fade_in_length * 0.2f); // t = 1.4 - break; + this.RotateTo(0); + this.RotateTo(rotation, fade_in_length) + .Then().RotateTo(rotation * 2, fade_out_delay + fade_out_length - fade_in_length, Easing.In); } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 9102231913..7516c73b68 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -453,11 +453,11 @@ namespace osu.Game.Skinning private Drawable? getJudgementAnimation(HitResult result) { + if (!result.IsHit()) + return this.GetAnimation("hit0", true, false); + switch (result) { - case HitResult.Miss: - return this.GetAnimation("hit0", true, false); - case HitResult.Meh: return this.GetAnimation("hit50", true, false); From d2716d855741c7461a33aa317965486a85ecd3d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Dec 2023 20:26:28 +0900 Subject: [PATCH 2053/4092] Improve animation for legacy skins --- osu.Game/Skinning/LegacyJudgementPieceOld.cs | 32 +++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/osu.Game/Skinning/LegacyJudgementPieceOld.cs b/osu.Game/Skinning/LegacyJudgementPieceOld.cs index 5381cfc050..6ad188bb47 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceOld.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceOld.cs @@ -65,22 +65,32 @@ namespace osu.Game.Skinning } else { - this.ScaleTo(1.6f); - this.ScaleTo(1, 100, Easing.In); + bool isTick = result != HitResult.Miss; - decimal? legacyVersion = skin.GetConfig(SkinConfiguration.LegacySetting.Version)?.Value; - - if (legacyVersion >= 2.0m) + if (isTick) { - this.MoveTo(new Vector2(0, -5)); - this.MoveToOffset(new Vector2(0, 80), fade_out_delay + fade_out_length, Easing.In); + this.ScaleTo(0.6f); + this.ScaleTo(0.3f, 100, Easing.In); } + else + { + this.ScaleTo(1.6f); + this.ScaleTo(1, 100, Easing.In); - float rotation = RNG.NextSingle(-8.6f, 8.6f); + decimal? legacyVersion = skin.GetConfig(SkinConfiguration.LegacySetting.Version)?.Value; - this.RotateTo(0); - this.RotateTo(rotation, fade_in_length) - .Then().RotateTo(rotation * 2, fade_out_delay + fade_out_length - fade_in_length, Easing.In); + if (legacyVersion >= 2.0m) + { + this.MoveTo(new Vector2(0, -5)); + this.MoveToOffset(new Vector2(0, 80), fade_out_delay + fade_out_length, Easing.In); + } + + float rotation = RNG.NextSingle(-8.6f, 8.6f); + + this.RotateTo(0); + this.RotateTo(rotation, fade_in_length) + .Then().RotateTo(rotation * 2, fade_out_delay + fade_out_length - fade_in_length, Easing.In); + } } } From d1ba2a4a64f7e5c51c5309d5366b9778b98fbdf4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Dec 2023 20:45:18 +0900 Subject: [PATCH 2054/4092] Use orange for non-combo-breaking miss --- osu.Game/Graphics/OsuColour.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index caa2037691..0d11d2d4ef 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -75,10 +75,12 @@ namespace osu.Game.Graphics { switch (result) { - case HitResult.SmallTickMiss: - case HitResult.LargeTickMiss: case HitResult.IgnoreMiss: + case HitResult.SmallTickMiss: + return Orange1; + case HitResult.Miss: + case HitResult.LargeTickMiss: case HitResult.ComboBreak: return Red; From bff08d124ba7d925566a1a2445036d3ace99c071 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 20 Dec 2023 22:28:46 +0900 Subject: [PATCH 2055/4092] Remove mania mod multiplier for DT/NC --- osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs | 5 +++++ osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs index a841a8ab37..bea1a14110 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs @@ -10,5 +10,10 @@ namespace osu.Game.Rulesets.Mania.Mods public class ManiaModDoubleTime : ModDoubleTime, IManiaRateAdjustmentMod { public HitWindows HitWindows { get; set; } = new ManiaHitWindows(); + + // For now, all rate-increasing mods should be given a 1x multiplier in mania because it doesn't always + // make the map harder and is more of a personal preference. + // In the future, we can consider adjusting this by experimenting with not applying the hitwindow leniency. + public override double ScoreMultiplier => 1; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs index f64f7ae31a..7e5e80db6c 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs @@ -11,5 +11,10 @@ namespace osu.Game.Rulesets.Mania.Mods public class ManiaModNightcore : ModNightcore, IManiaRateAdjustmentMod { public HitWindows HitWindows { get; set; } = new ManiaHitWindows(); + + // For now, all rate-increasing mods should be given a 1x multiplier in mania because it doesn't always + // make the map any harder and is more of a personal preference. + // In the future, we can consider adjusting this by experimenting with not applying the hitwindow leniency. + public override double ScoreMultiplier => 1; } } From b0da24176e5e2026173f25abb8a9e094f7d118ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 20 Dec 2023 15:44:02 +0100 Subject: [PATCH 2056/4092] Remove outdated inline comments --- osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs index d752c443cc..5f299f419d 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs @@ -200,11 +200,9 @@ namespace osu.Game.Rulesets.Mania.Tests }); assertHeadJudgement(HitResult.Perfect); - // judgement combo offset by perfect bonus judgement. see logic in DrawableNote.CheckForResult. assertComboAtJudgement(0, 1); assertTailJudgement(HitResult.Meh); assertComboAtJudgement(1, 0); - // judgement combo offset by perfect bonus judgement. see logic in DrawableNote.CheckForResult. assertComboAtJudgement(3, 1); } From 9515f7dbfa778a22eef7414e2ec68ff732befa7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 20 Dec 2023 15:58:26 +0100 Subject: [PATCH 2057/4092] Bump score version in order to recompute legacy scores --- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 5c51e68d9f..fa930d77d3 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -33,9 +33,10 @@ namespace osu.Game.Scoring.Legacy /// 30000004: Fixed mod multipliers during legacy score conversion. Reconvert all scores. /// 30000005: Introduce combo exponent in the osu! gamemode. Reconvert all scores. /// 30000006: Fix edge cases in conversion after combo exponent introduction that lead to NaNs. Reconvert all scores. + /// 30000007: Adjust osu!mania combo and accuracy portions and judgement scoring values. Reconvert all scores. /// /// - public const int LATEST_VERSION = 30000006; + public const int LATEST_VERSION = 30000007; /// /// The first stable-compatible YYYYMMDD format version given to lazer usage of replays. From fcf47267fd3a38e53794ca8dadd0d6222c327b64 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Dec 2023 00:48:46 +0900 Subject: [PATCH 2058/4092] Revert change to `OsuHitWindows` and move logic local to pooling initialisation --- osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs | 2 -- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 18 +++++++++++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs index 5b2c95b536..fd86e0eeda 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs @@ -28,8 +28,6 @@ namespace osu.Game.Rulesets.Osu.Scoring case HitResult.Ok: case HitResult.Meh: case HitResult.Miss: - case HitResult.LargeTickMiss: - case HitResult.IgnoreMiss: return true; } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 3f60ce3610..c94057cf6d 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -20,7 +20,6 @@ using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; -using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; @@ -66,8 +65,21 @@ namespace osu.Game.Rulesets.Osu.UI HitPolicy = new StartTimeOrderedHitPolicy(); - var hitWindows = new OsuHitWindows(); - foreach (var result in Enum.GetValues().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r))) + foreach (var result in Enum.GetValues().Where(r => + { + switch (r) + { + case HitResult.Great: + case HitResult.Ok: + case HitResult.Meh: + case HitResult.Miss: + case HitResult.LargeTickMiss: + case HitResult.IgnoreMiss: + return true; + } + + return false; + })) poolDictionary.Add(result, new DrawableJudgementPool(result, onJudgementLoaded)); AddRangeInternal(poolDictionary.Values); From eb8fb8092d2effd3c4251f8247f0dcc5d8f52a93 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Dec 2023 00:58:43 +0900 Subject: [PATCH 2059/4092] Attempt to standardise miss handling logic --- .../Skinning/Argon/ArgonJudgementPiece.cs | 16 ++++++------- osu.Game/Rulesets/Scoring/HitResult.cs | 19 +++++++++++++++ osu.Game/Skinning/LegacyJudgementPieceNew.cs | 2 +- osu.Game/Skinning/LegacyJudgementPieceOld.cs | 24 +++++++++---------- osu.Game/Skinning/LegacySkin.cs | 2 +- 5 files changed, 41 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs index 94766cb077..edeece0293 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs @@ -62,14 +62,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon /// public virtual void PlayAnimation() { - if (Result.IsHit()) - { - JudgementText - .FadeInFromZero(300, Easing.OutQuint) - .ScaleTo(Vector2.One) - .ScaleTo(new Vector2(1.2f), 1800, Easing.OutQuint); - } - else + if (Result.IsMiss()) { this.ScaleTo(1.6f); this.ScaleTo(1, 100, Easing.In); @@ -80,6 +73,13 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon this.RotateTo(0); this.RotateTo(40, 800, Easing.InQuint); } + else + { + JudgementText + .FadeInFromZero(300, Easing.OutQuint) + .ScaleTo(Vector2.One) + .ScaleTo(new Vector2(1.2f), 1800, Easing.OutQuint); + } this.FadeOutFromOne(800); diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index b490501021..bdb2a9db23 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -269,6 +269,25 @@ namespace osu.Game.Rulesets.Scoring } } + /// + /// Whether a represents a miss of any type. + /// + public static bool IsMiss(this HitResult result) + { + switch (result) + { + case HitResult.IgnoreMiss: + case HitResult.Miss: + case HitResult.SmallTickMiss: + case HitResult.LargeTickMiss: + case HitResult.ComboBreak: + return true; + + default: + return false; + } + } + /// /// Whether a represents a successful hit. /// diff --git a/osu.Game/Skinning/LegacyJudgementPieceNew.cs b/osu.Game/Skinning/LegacyJudgementPieceNew.cs index a93c48ba63..5ff28726c0 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceNew.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceNew.cs @@ -50,7 +50,7 @@ namespace osu.Game.Skinning }); } - if (result.IsHit()) + if (!result.IsMiss()) { //new judgement shows old as a temporary effect AddInternal(temporaryOldStyle = new LegacyJudgementPieceOld(result, createMainDrawable, 1.05f, true) diff --git a/osu.Game/Skinning/LegacyJudgementPieceOld.cs b/osu.Game/Skinning/LegacyJudgementPieceOld.cs index 6ad188bb47..39697090d1 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceOld.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceOld.cs @@ -52,18 +52,7 @@ namespace osu.Game.Skinning if (animation?.FrameCount > 1 && !forceTransforms) return; - if (result.IsHit()) - { - this.ScaleTo(0.6f).Then() - .ScaleTo(1.1f, fade_in_length * 0.8f).Then() // t = 0.8 - .Delay(fade_in_length * 0.2f) // t = 1.0 - .ScaleTo(0.9f, fade_in_length * 0.2f).Then() // t = 1.2 - // stable dictates scale of 0.9->1 over time 1.0 to 1.4, but we are already at 1.2. - // so we need to force the current value to be correct at 1.2 (0.95) then complete the - // second half of the transform. - .ScaleTo(0.95f).ScaleTo(finalScale, fade_in_length * 0.2f); // t = 1.4 - } - else + if (result.IsMiss()) { bool isTick = result != HitResult.Miss; @@ -92,6 +81,17 @@ namespace osu.Game.Skinning .Then().RotateTo(rotation * 2, fade_out_delay + fade_out_length - fade_in_length, Easing.In); } } + else + { + this.ScaleTo(0.6f).Then() + .ScaleTo(1.1f, fade_in_length * 0.8f).Then() // t = 0.8 + .Delay(fade_in_length * 0.2f) // t = 1.0 + .ScaleTo(0.9f, fade_in_length * 0.2f).Then() // t = 1.2 + // stable dictates scale of 0.9->1 over time 1.0 to 1.4, but we are already at 1.2. + // so we need to force the current value to be correct at 1.2 (0.95) then complete the + // second half of the transform. + .ScaleTo(0.95f).ScaleTo(finalScale, fade_in_length * 0.2f); // t = 1.4 + } } public Drawable GetAboveHitObjectsProxiedContent() => CreateProxy(); diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 7516c73b68..a37a386889 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -453,7 +453,7 @@ namespace osu.Game.Skinning private Drawable? getJudgementAnimation(HitResult result) { - if (!result.IsHit()) + if (result.IsMiss()) return this.GetAnimation("hit0", true, false); switch (result) From ebbc8333e84509d39c5ed4afada29f07cd8d2d04 Mon Sep 17 00:00:00 2001 From: rushiiMachine <33725716+rushiiMachine@users.noreply.github.com> Date: Tue, 19 Dec 2023 23:36:57 -0800 Subject: [PATCH 2060/4092] Prevent `ExportReplay` being spammed on fail by being held down This was already handled in ReplayDownloadButton (https://github.com/ppy/osu/blob/98efff0bd61ea6cc5d6a884aeda1614d81592b9d/osu.Game/Screens/Ranking/ReplayDownloadButton.cs#L114-L115) but seemingly missed for SaveFailedScoreButton --- osu.Game/Screens/Play/SaveFailedScoreButton.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Play/SaveFailedScoreButton.cs b/osu.Game/Screens/Play/SaveFailedScoreButton.cs index 0a2696339c..b97c140250 100644 --- a/osu.Game/Screens/Play/SaveFailedScoreButton.cs +++ b/osu.Game/Screens/Play/SaveFailedScoreButton.cs @@ -102,6 +102,9 @@ namespace osu.Game.Screens.Play public bool OnPressed(KeyBindingPressEvent e) { + if (e.Repeat) + return false; + switch (e.Action) { case GlobalAction.SaveReplay: From c5fb4d0f5c85714d207916d8035565c7a15a39c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Dec 2023 01:52:40 +0900 Subject: [PATCH 2061/4092] Mark flaky test temporarily --- osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 518035fb82..218bf495e3 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -420,6 +420,7 @@ namespace osu.Game.Tests.Visual.SongSelect } [Test] + [FlakyTest] // temporary while peppy investigates public void TestSelectionRetainedOnBeatmapUpdate() { createSongSelect(); From a763ad84730112774e89960d1c3afc115c3e1851 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 20 Dec 2023 19:07:18 +0100 Subject: [PATCH 2062/4092] Add remarks to `Is{Hit,Miss}()` to explain their simultaneous existence --- osu.Game/Rulesets/Scoring/HitResult.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index bdb2a9db23..e174ebd00f 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -272,6 +272,9 @@ namespace osu.Game.Rulesets.Scoring /// /// Whether a represents a miss of any type. /// + /// + /// Of note, both and return for . + /// public static bool IsMiss(this HitResult result) { switch (result) @@ -291,6 +294,9 @@ namespace osu.Game.Rulesets.Scoring /// /// Whether a represents a successful hit. /// + /// + /// Of note, both and return for . + /// public static bool IsHit(this HitResult result) { switch (result) From 975bacaeb750daef25afa0c7c117e23baeb42586 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 20 Dec 2023 19:07:26 +0100 Subject: [PATCH 2063/4092] Add back blank line to fix code quality inspection --- osu.Game/Skinning/LegacyJudgementPieceOld.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Skinning/LegacyJudgementPieceOld.cs b/osu.Game/Skinning/LegacyJudgementPieceOld.cs index 39697090d1..ca2c8ce6bc 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceOld.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceOld.cs @@ -87,6 +87,7 @@ namespace osu.Game.Skinning .ScaleTo(1.1f, fade_in_length * 0.8f).Then() // t = 0.8 .Delay(fade_in_length * 0.2f) // t = 1.0 .ScaleTo(0.9f, fade_in_length * 0.2f).Then() // t = 1.2 + // stable dictates scale of 0.9->1 over time 1.0 to 1.4, but we are already at 1.2. // so we need to force the current value to be correct at 1.2 (0.95) then complete the // second half of the transform. From b6a331b2f7c64100874d2eebe0ecff069a7b9693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 20 Dec 2023 20:52:26 +0100 Subject: [PATCH 2064/4092] Fix argon pro not showing slider tick judgements Addresses https://github.com/ppy/osu/discussions/25968. --- osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs index f98a47097d..0f9c97059c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon { case GameplaySkinComponentLookup resultComponent: // This should eventually be moved to a skin setting, when supported. - if (Skin is ArgonProSkin && resultComponent.Component >= HitResult.Great) + if (Skin is ArgonProSkin && (resultComponent.Component == HitResult.Great || resultComponent.Component == HitResult.Perfect)) return Drawable.Empty(); return new ArgonJudgementPiece(resultComponent.Component); From 88e36eb08c353e8a65108995ad32718831eded66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 20 Dec 2023 21:46:18 +0100 Subject: [PATCH 2065/4092] Fix autopilot mod still declaring incompatibility with fail-preventing mods Closes https://github.com/ppy/osu/issues/25974. --- osu.Game.Rulesets.Osu/Mods/OsuModAccuracyChallenge.cs | 3 --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModNoFail.cs | 3 --- osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs | 3 --- osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs | 1 - 5 files changed, 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAccuracyChallenge.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAccuracyChallenge.cs index 5b79753632..e6daa3846f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAccuracyChallenge.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAccuracyChallenge.cs @@ -1,14 +1,11 @@ // 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.Linq; using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Osu.Mods { public class OsuModAccuracyChallenge : ModAccuracyChallenge { - public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray(); } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index bf74b741d5..efcc728d55 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -29,8 +29,6 @@ namespace osu.Game.Rulesets.Osu.Mods { typeof(OsuModSpunOut), typeof(ModRelax), - typeof(ModFailCondition), - typeof(ModNoFail), typeof(ModAutoplay), typeof(OsuModMagnetised), typeof(OsuModRepel), diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNoFail.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNoFail.cs index 9f707a5aa6..53c67cd1c3 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModNoFail.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModNoFail.cs @@ -1,14 +1,11 @@ // 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.Linq; using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Osu.Mods { public class OsuModNoFail : ModNoFail { - public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray(); } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs b/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs index 33581405a6..da462eb6e8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs @@ -1,14 +1,11 @@ // 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.Linq; using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Osu.Mods { public class OsuModPerfect : ModPerfect { - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot) }).ToArray(); } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs index b4edb1581e..e661610fe7 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs @@ -11,7 +11,6 @@ namespace osu.Game.Rulesets.Osu.Mods { public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { - typeof(OsuModAutopilot), typeof(OsuModTargetPractice), }).ToArray(); } From 4e3b9941423591977dfe969d637661308dc7d9f6 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 20 Dec 2023 20:23:43 +0900 Subject: [PATCH 2066/4092] Relocate HitResult numeric score to ScoreProcessor --- .../Difficulty/CatchLegacyScoreSimulator.cs | 6 +- .../Scoring/CatchScoreProcessor.cs | 2 +- .../Scoring/ManiaScoreProcessor.cs | 31 ++--- .../TestSceneSpinnerRotation.cs | 11 +- .../Difficulty/OsuLegacyScoreSimulator.cs | 6 +- .../Objects/Drawables/DrawableSpinner.cs | 3 +- .../Difficulty/TaikoLegacyScoreSimulator.cs | 6 +- .../Scoring/TaikoScoreProcessor.cs | 19 +++- .../Gameplay/TestSceneScoreProcessor.cs | 2 +- .../StandardisedScoreMigrationTools.cs | 46 +++++++- osu.Game/Rulesets/Judgements/Judgement.cs | 59 +--------- .../Rulesets/Judgements/JudgementResult.cs | 2 +- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 107 +++++++++++++++--- osu.Game/Scoring/ScoreImporter.cs | 4 +- 14 files changed, 182 insertions(+), 122 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyScoreSimulator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyScoreSimulator.cs index 7a84d9245d..52605ec0cc 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyScoreSimulator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyScoreSimulator.cs @@ -7,7 +7,7 @@ using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Mods; using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Catch.Scoring; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty { internal class CatchLegacyScoreSimulator : ILegacyScoreSimulator { + private readonly ScoreProcessor scoreProcessor = new CatchScoreProcessor(); + private int legacyBonusScore; private int standardisedBonusScore; private int combo; @@ -134,7 +136,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty if (isBonus) { legacyBonusScore += scoreIncrease; - standardisedBonusScore += Judgement.ToNumericResult(bonusResult); + standardisedBonusScore += scoreProcessor.GetRawBonusScore(bonusResult); } else attributes.AccuracyScore += scoreIncrease; diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index 503252df02..023369badc 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.Scoring } protected override double GetComboScoreChange(JudgementResult result) - => GetNumericResultFor(result) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(combo_cap, combo_base)); + => GetRawComboScore(result.Type) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(combo_cap, combo_base)); public override ScoreRank RankFromAccuracy(double accuracy) { diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index d5191c880a..d059d99d9f 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -31,45 +31,30 @@ namespace osu.Game.Rulesets.Mania.Scoring + bonusPortion; } - protected override double GetNumericResultFor(JudgementResult result) + public override int GetRawAccuracyScore(HitResult result) { - switch (result.Type) + switch (result) { case HitResult.Perfect: return 305; } - return base.GetNumericResultFor(result); + return base.GetRawAccuracyScore(result); } - protected override double GetMaxNumericResultFor(JudgementResult result) + public override int GetRawComboScore(HitResult result) { - switch (result.Judgement.MaxResult) + switch (result) { case HitResult.Perfect: - return 305; + return 300; } - return base.GetMaxNumericResultFor(result); + return base.GetRawComboScore(result); } protected override double GetComboScoreChange(JudgementResult result) - { - double numericResult; - - switch (result.Type) - { - case HitResult.Perfect: - numericResult = 300; - break; - - default: - numericResult = GetNumericResultFor(result); - break; - } - - return numericResult * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(400, combo_base)); - } + => GetRawComboScore(result.Type) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(400, combo_base)); private class JudgementOrderComparer : IComparer { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index 8711aa9c09..fd445bc1ac 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -58,10 +58,7 @@ namespace osu.Game.Rulesets.Osu.Tests double trackerRotationTolerance = 0; addSeekStep(5000); - AddStep("calculate rotation tolerance", () => - { - trackerRotationTolerance = Math.Abs(drawableSpinner.RotationTracker.Rotation * 0.1f); - }); + AddStep("calculate rotation tolerance", () => { trackerRotationTolerance = Math.Abs(drawableSpinner.RotationTracker.Rotation * 0.1f); }); AddAssert("is disc rotation not almost 0", () => drawableSpinner.RotationTracker.Rotation, () => Is.Not.EqualTo(0).Within(100)); AddAssert("is disc rotation absolute not almost 0", () => drawableSpinner.Result.TotalRotation, () => Is.Not.EqualTo(0).Within(100)); @@ -133,9 +130,11 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert("player score matching expected bonus score", () => { + var scoreProcessor = ((ScoreExposedPlayer)Player).ScoreProcessor; + // multipled by 2 to nullify the score multiplier. (autoplay mod selected) - long totalScore = ((ScoreExposedPlayer)Player).ScoreProcessor.TotalScore.Value * 2; - return totalScore == (int)(drawableSpinner.Result.TotalRotation / 360) * new SpinnerTick().CreateJudgement().MaxNumericResult; + long totalScore = scoreProcessor.TotalScore.Value * 2; + return totalScore == (int)(drawableSpinner.Result.TotalRotation / 360) * scoreProcessor.GetRawBonusScore(new SpinnerTick().CreateJudgement().MaxResult); }); addSeekStep(0); diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuLegacyScoreSimulator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuLegacyScoreSimulator.cs index 28967cbf7e..6ac7c33275 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuLegacyScoreSimulator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuLegacyScoreSimulator.cs @@ -5,12 +5,12 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; -using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring.Legacy; @@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty { internal class OsuLegacyScoreSimulator : ILegacyScoreSimulator { + private readonly ScoreProcessor scoreProcessor = new OsuScoreProcessor(); + private int legacyBonusScore; private int standardisedBonusScore; private int combo; @@ -171,7 +173,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (isBonus) { legacyBonusScore += scoreIncrease; - standardisedBonusScore += Judgement.ToNumericResult(bonusResult); + standardisedBonusScore += scoreProcessor.GetRawBonusScore(bonusResult); } else attributes.AccuracyScore += scoreIncrease; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index c0c135d145..787d5e5937 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -17,6 +17,7 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Rulesets.Scoring; @@ -312,7 +313,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables updateBonusScore(); } - private static readonly int score_per_tick = new SpinnerBonusTick.OsuSpinnerBonusTickJudgement().MaxNumericResult; + private static readonly int score_per_tick = new OsuScoreProcessor().GetRawBonusScore(new SpinnerBonusTick.OsuSpinnerBonusTickJudgement().MaxResult); private void updateBonusScore() { diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs index a8ed056c89..8b34c8c808 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; -using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -13,11 +12,14 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring.Legacy; using osu.Game.Rulesets.Taiko.Mods; using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Scoring; namespace osu.Game.Rulesets.Taiko.Difficulty { internal class TaikoLegacyScoreSimulator : ILegacyScoreSimulator { + private readonly ScoreProcessor scoreProcessor = new TaikoScoreProcessor(); + private int legacyBonusScore; private int standardisedBonusScore; private int combo; @@ -191,7 +193,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (isBonus) { legacyBonusScore += scoreIncrease; - standardisedBonusScore += Judgement.ToNumericResult(bonusResult); + standardisedBonusScore += scoreProcessor.GetRawBonusScore(bonusResult); } else attributes.AccuracyScore += scoreIncrease; diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index bc1e42c5d1..e81ba324bd 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -28,20 +28,31 @@ namespace osu.Game.Rulesets.Taiko.Scoring protected override double GetComboScoreChange(JudgementResult result) { - return GetNumericResultFor(result) + return GetRawComboScore(result.Type) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(400, combo_base)) * strongScaleValue(result); } - protected override double GetNumericResultFor(JudgementResult result) + public override int GetRawAccuracyScore(HitResult result) { - switch (result.Type) + switch (result) { case HitResult.Ok: return 150; } - return base.GetNumericResultFor(result); + return base.GetRawAccuracyScore(result); + } + + public override int GetRawComboScore(HitResult result) + { + switch (result) + { + case HitResult.Ok: + return 150; + } + + return base.GetRawComboScore(result); } private double strongScaleValue(JudgementResult result) diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs index 1cf72cf937..5d2ad8b363 100644 --- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Gameplay // Apply a judgement scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new TestJudgement(HitResult.LargeBonus)) { Type = HitResult.LargeBonus }); - Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(Judgement.LARGE_BONUS_SCORE)); + Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(scoreProcessor.GetRawBonusScore(HitResult.LargeBonus))); } [Test] diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index 2e5ca390d7..5324d2569e 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -57,14 +57,14 @@ namespace osu.Game.Database // We are constructing a "best possible" score from the statistics provided because it's the best we can do. List sortedHits = score.Statistics .Where(kvp => kvp.Key.AffectsCombo()) - .OrderByDescending(kvp => Judgement.ToNumericResult(kvp.Key)) + .OrderByDescending(kvp => processor.GetRawComboScore(kvp.Key)) .SelectMany(kvp => Enumerable.Repeat(kvp.Key, kvp.Value)) .ToList(); // Attempt to use maximum statistics from the database. var maximumJudgements = score.MaximumStatistics .Where(kvp => kvp.Key.AffectsCombo()) - .OrderByDescending(kvp => Judgement.ToNumericResult(kvp.Key)) + .OrderByDescending(kvp => processor.GetRawComboScore(kvp.Key)) .SelectMany(kvp => Enumerable.Repeat(new FakeJudgement(kvp.Key), kvp.Value)) .ToList(); @@ -169,10 +169,10 @@ namespace osu.Game.Database public static long GetOldStandardised(ScoreInfo score) { double accuracyScore = - (double)score.Statistics.Where(kvp => kvp.Key.AffectsAccuracy()).Sum(kvp => Judgement.ToNumericResult(kvp.Key) * kvp.Value) - / score.MaximumStatistics.Where(kvp => kvp.Key.AffectsAccuracy()).Sum(kvp => Judgement.ToNumericResult(kvp.Key) * kvp.Value); + (double)score.Statistics.Where(kvp => kvp.Key.AffectsAccuracy()).Sum(kvp => numericScoreFor(kvp.Key) * kvp.Value) + / score.MaximumStatistics.Where(kvp => kvp.Key.AffectsAccuracy()).Sum(kvp => numericScoreFor(kvp.Key) * kvp.Value); double comboScore = (double)score.MaxCombo / score.MaximumStatistics.Where(kvp => kvp.Key.AffectsCombo()).Sum(kvp => kvp.Value); - double bonusScore = score.Statistics.Where(kvp => kvp.Key.IsBonus()).Sum(kvp => Judgement.ToNumericResult(kvp.Key) * kvp.Value); + double bonusScore = score.Statistics.Where(kvp => kvp.Key.IsBonus()).Sum(kvp => numericScoreFor(kvp.Key) * kvp.Value); double accuracyPortion = 0.3; @@ -193,6 +193,42 @@ namespace osu.Game.Database modMultiplier *= mod.ScoreMultiplier; return (long)Math.Round((1000000 * (accuracyPortion * accuracyScore + (1 - accuracyPortion) * comboScore) + bonusScore) * modMultiplier); + + static int numericScoreFor(HitResult result) + { + switch (result) + { + default: + return 0; + + case HitResult.SmallTickHit: + return 10; + + case HitResult.LargeTickHit: + return 30; + + case HitResult.Meh: + return 50; + + case HitResult.Ok: + return 100; + + case HitResult.Good: + return 200; + + case HitResult.Great: + return 300; + + case HitResult.Perfect: + return 315; + + case HitResult.SmallBonus: + return 10; + + case HitResult.LargeBonus: + return 50; + } + } } /// diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index cd1e81046d..27402d522c 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -11,16 +11,6 @@ namespace osu.Game.Rulesets.Judgements /// public class Judgement { - /// - /// The score awarded for a small bonus. - /// - public const int SMALL_BONUS_SCORE = 10; - - /// - /// The score awarded for a large bonus. - /// - public const int LARGE_BONUS_SCORE = 50; - /// /// The default health increase for a maximum judgement, as a proportion of total health. /// By default, each maximum judgement restores 5% of total health. @@ -91,23 +81,11 @@ namespace osu.Game.Rulesets.Judgements } } - /// - /// The numeric score representation for the maximum achievable result. - /// - public int MaxNumericResult => ToNumericResult(MaxResult); - /// /// The health increase for the maximum achievable result. /// public double MaxHealthIncrease => HealthIncreaseFor(MaxResult); - /// - /// Retrieves the numeric score representation of a . - /// - /// The to find the numeric score representation for. - /// The numeric score representation of . - public int NumericResultFor(JudgementResult result) => ToNumericResult(result.Type); - /// /// Retrieves the numeric health increase of a . /// @@ -165,41 +143,6 @@ namespace osu.Game.Rulesets.Judgements /// The numeric health increase of . public double HealthIncreaseFor(JudgementResult result) => HealthIncreaseFor(result.Type); - public override string ToString() => $"MaxResult:{MaxResult} MaxScore:{MaxNumericResult}"; - - public static int ToNumericResult(HitResult result) - { - switch (result) - { - default: - return 0; - - case HitResult.SmallTickHit: - return 10; - - case HitResult.LargeTickHit: - return 30; - - case HitResult.Meh: - return 50; - - case HitResult.Ok: - return 100; - - case HitResult.Good: - return 200; - - case HitResult.Great: - // Perfect doesn't actually give more score / accuracy directly. - case HitResult.Perfect: - return 300; - - case HitResult.SmallBonus: - return SMALL_BONUS_SCORE; - - case HitResult.LargeBonus: - return LARGE_BONUS_SCORE; - } - } + public override string ToString() => $"MaxResult:{MaxResult}"; } } diff --git a/osu.Game/Rulesets/Judgements/JudgementResult.cs b/osu.Game/Rulesets/Judgements/JudgementResult.cs index db621b4851..1b915d52b7 100644 --- a/osu.Game/Rulesets/Judgements/JudgementResult.cs +++ b/osu.Game/Rulesets/Judgements/JudgementResult.cs @@ -112,6 +112,6 @@ namespace osu.Game.Rulesets.Judgements RawTime = null; } - public override string ToString() => $"{Type} (Score:{Judgement.NumericResultFor(this)} HP:{Judgement.HealthIncreaseFor(this)} {Judgement})"; + public override string ToString() => $"{Type} ({Judgement})"; } } diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index aa8905c0b6..a80a118363 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -227,12 +227,12 @@ namespace osu.Game.Rulesets.Scoring if (result.Judgement.MaxResult.AffectsAccuracy()) { - currentMaximumBaseScore += GetMaxNumericResultFor(result); + currentMaximumBaseScore += GetRawAccuracyScore(result.Judgement.MaxResult); currentAccuracyJudgementCount++; } if (result.Type.AffectsAccuracy()) - currentBaseScore += GetNumericResultFor(result); + currentBaseScore += GetRawAccuracyScore(result.Type); if (result.Type.IsBonus()) currentBonusPortion += GetBonusScoreChange(result); @@ -276,12 +276,12 @@ namespace osu.Game.Rulesets.Scoring if (result.Judgement.MaxResult.AffectsAccuracy()) { - currentMaximumBaseScore -= GetMaxNumericResultFor(result); + currentMaximumBaseScore -= GetRawAccuracyScore(result.Judgement.MaxResult); currentAccuracyJudgementCount--; } if (result.Type.AffectsAccuracy()) - currentBaseScore -= GetNumericResultFor(result); + currentBaseScore -= GetRawAccuracyScore(result.Type); if (result.Type.IsBonus()) currentBonusPortion -= GetBonusScoreChange(result); @@ -297,21 +297,100 @@ namespace osu.Game.Rulesets.Scoring updateScore(); } - protected virtual double GetBonusScoreChange(JudgementResult result) => GetNumericResultFor(result); - - protected virtual double GetComboScoreChange(JudgementResult result) => GetMaxNumericResultFor(result) * Math.Pow(result.ComboAfterJudgement, COMBO_EXPONENT); + /// + /// Gets the final score change to be applied to the bonus portion of the score. + /// + /// The judgement result. + protected virtual double GetBonusScoreChange(JudgementResult result) => GetRawBonusScore(result.Type); /// - /// Retrieves the numeric score representation for a . + /// Gets the final score change to be applied to the combo portion of the score. /// - /// The . - protected virtual double GetNumericResultFor(JudgementResult result) => result.Judgement.NumericResultFor(result); + /// The judgement result. + protected virtual double GetComboScoreChange(JudgementResult result) => GetRawComboScore(result.Judgement.MaxResult) * Math.Pow(result.ComboAfterJudgement, COMBO_EXPONENT); /// - /// Retrieves the maximum numeric score representation for a . + /// Retrieves the raw score value for a hit result, in order to be applied to the combo portion. /// - /// The . - protected virtual double GetMaxNumericResultFor(JudgementResult result) => result.Judgement.MaxNumericResult; + /// The hit result. + public virtual int GetRawComboScore(HitResult result) + { + switch (result) + { + default: + return 0; + + case HitResult.SmallTickHit: + return 10; + + case HitResult.LargeTickHit: + return 30; + + case HitResult.Meh: + return 50; + + case HitResult.Ok: + return 100; + + case HitResult.Good: + return 200; + + case HitResult.Great: + case HitResult.Perfect: // Perfect doesn't actually give more score / accuracy directly. + return 300; + } + } + + /// + /// Retrieves the raw score value for a hit result, in order to be applied to the accuracy portion. + /// + /// The hit result. + public virtual int GetRawAccuracyScore(HitResult result) + { + switch (result) + { + default: + return 0; + + case HitResult.SmallTickHit: + return 10; + + case HitResult.LargeTickHit: + return 30; + + case HitResult.Meh: + return 50; + + case HitResult.Ok: + return 100; + + case HitResult.Good: + return 200; + + case HitResult.Great: + case HitResult.Perfect: // Perfect doesn't actually give more score / accuracy directly. + return 300; + } + } + + /// + /// Retrieves the raw score value for a hit result, in order to be applied to the bonus portion. + /// + /// The hit result. + public virtual int GetRawBonusScore(HitResult result) + { + switch (result) + { + default: + return 0; + + case HitResult.SmallBonus: + return 10; + + case HitResult.LargeBonus: + return 50; + } + } protected virtual void ApplyScoreChange(JudgementResult result) { @@ -540,7 +619,7 @@ namespace osu.Game.Rulesets.Scoring /// /// /// Used to compute accuracy. - /// See: and . + /// See: and . /// [Key(0)] public double BaseScore { get; set; } diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index b216c0897e..b1cba3756b 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -17,7 +17,6 @@ using osu.Game.Scoring.Legacy; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using Realms; @@ -125,13 +124,14 @@ namespace osu.Game.Scoring var beatmap = score.BeatmapInfo!.Detach(); var ruleset = score.Ruleset.Detach(); var rulesetInstance = ruleset.CreateInstance(); + var scoreProcessor = rulesetInstance.CreateScoreProcessor(); Debug.Assert(rulesetInstance != null); // Populate the maximum statistics. HitResult maxBasicResult = rulesetInstance.GetHitResults() .Select(h => h.result) - .Where(h => h.IsBasic()).MaxBy(Judgement.ToNumericResult); + .Where(h => h.IsBasic()).MaxBy(scoreProcessor.GetRawAccuracyScore); foreach ((HitResult result, int count) in score.Statistics) { From 6b4b2a57fc7d9652dd4726b388656175cec1e09b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 21 Dec 2023 14:58:23 +0900 Subject: [PATCH 2067/4092] Expose only as one method --- .../Difficulty/CatchLegacyScoreSimulator.cs | 2 +- .../Scoring/CatchScoreProcessor.cs | 2 +- .../Scoring/ManiaScoreProcessor.cs | 16 +++-- .../TestSceneSpinnerRotation.cs | 2 +- .../Difficulty/OsuLegacyScoreSimulator.cs | 2 +- .../Objects/Drawables/DrawableSpinner.cs | 2 +- .../Difficulty/TaikoLegacyScoreSimulator.cs | 2 +- .../Scoring/TaikoScoreProcessor.cs | 17 +---- .../Gameplay/TestSceneScoreProcessor.cs | 2 +- .../StandardisedScoreMigrationTools.cs | 4 +- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 65 +++---------------- osu.Game/Scoring/ScoreImporter.cs | 2 +- 12 files changed, 30 insertions(+), 88 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyScoreSimulator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyScoreSimulator.cs index 52605ec0cc..f65b6ef381 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyScoreSimulator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyScoreSimulator.cs @@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty if (isBonus) { legacyBonusScore += scoreIncrease; - standardisedBonusScore += scoreProcessor.GetRawBonusScore(bonusResult); + standardisedBonusScore += scoreProcessor.GetBaseScoreForResult(bonusResult); } else attributes.AccuracyScore += scoreIncrease; diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index 023369badc..4b3d378889 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.Scoring } protected override double GetComboScoreChange(JudgementResult result) - => GetRawComboScore(result.Type) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(combo_cap, combo_base)); + => GetBaseScoreForResult(result.Type) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(combo_cap, combo_base)); public override ScoreRank RankFromAccuracy(double accuracy) { diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index d059d99d9f..1947d86a97 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -31,7 +31,12 @@ namespace osu.Game.Rulesets.Mania.Scoring + bonusPortion; } - public override int GetRawAccuracyScore(HitResult result) + protected override double GetComboScoreChange(JudgementResult result) + { + return getBaseComboScoreForResult(result.Type) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(400, combo_base)); + } + + public override int GetBaseScoreForResult(HitResult result) { switch (result) { @@ -39,10 +44,10 @@ namespace osu.Game.Rulesets.Mania.Scoring return 305; } - return base.GetRawAccuracyScore(result); + return base.GetBaseScoreForResult(result); } - public override int GetRawComboScore(HitResult result) + private int getBaseComboScoreForResult(HitResult result) { switch (result) { @@ -50,12 +55,9 @@ namespace osu.Game.Rulesets.Mania.Scoring return 300; } - return base.GetRawComboScore(result); + return GetBaseScoreForResult(result); } - protected override double GetComboScoreChange(JudgementResult result) - => GetRawComboScore(result.Type) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(400, combo_base)); - private class JudgementOrderComparer : IComparer { public static readonly JudgementOrderComparer DEFAULT = new JudgementOrderComparer(); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index fd445bc1ac..6706d20080 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Osu.Tests // multipled by 2 to nullify the score multiplier. (autoplay mod selected) long totalScore = scoreProcessor.TotalScore.Value * 2; - return totalScore == (int)(drawableSpinner.Result.TotalRotation / 360) * scoreProcessor.GetRawBonusScore(new SpinnerTick().CreateJudgement().MaxResult); + return totalScore == (int)(drawableSpinner.Result.TotalRotation / 360) * scoreProcessor.GetBaseScoreForResult(new SpinnerTick().CreateJudgement().MaxResult); }); addSeekStep(0); diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuLegacyScoreSimulator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuLegacyScoreSimulator.cs index 6ac7c33275..a76054b42c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuLegacyScoreSimulator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuLegacyScoreSimulator.cs @@ -173,7 +173,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (isBonus) { legacyBonusScore += scoreIncrease; - standardisedBonusScore += scoreProcessor.GetRawBonusScore(bonusResult); + standardisedBonusScore += scoreProcessor.GetBaseScoreForResult(bonusResult); } else attributes.AccuracyScore += scoreIncrease; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 787d5e5937..f7c1437009 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -313,7 +313,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables updateBonusScore(); } - private static readonly int score_per_tick = new OsuScoreProcessor().GetRawBonusScore(new SpinnerBonusTick.OsuSpinnerBonusTickJudgement().MaxResult); + private static readonly int score_per_tick = new OsuScoreProcessor().GetBaseScoreForResult(new SpinnerBonusTick.OsuSpinnerBonusTickJudgement().MaxResult); private void updateBonusScore() { diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs index 8b34c8c808..b20aa4f2b6 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs @@ -193,7 +193,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (isBonus) { legacyBonusScore += scoreIncrease; - standardisedBonusScore += scoreProcessor.GetRawBonusScore(bonusResult); + standardisedBonusScore += scoreProcessor.GetBaseScoreForResult(bonusResult); } else attributes.AccuracyScore += scoreIncrease; diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index e81ba324bd..2fd9f070ec 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -28,12 +28,12 @@ namespace osu.Game.Rulesets.Taiko.Scoring protected override double GetComboScoreChange(JudgementResult result) { - return GetRawComboScore(result.Type) + return GetBaseScoreForResult(result.Type) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(400, combo_base)) * strongScaleValue(result); } - public override int GetRawAccuracyScore(HitResult result) + public override int GetBaseScoreForResult(HitResult result) { switch (result) { @@ -41,18 +41,7 @@ namespace osu.Game.Rulesets.Taiko.Scoring return 150; } - return base.GetRawAccuracyScore(result); - } - - public override int GetRawComboScore(HitResult result) - { - switch (result) - { - case HitResult.Ok: - return 150; - } - - return base.GetRawComboScore(result); + return base.GetBaseScoreForResult(result); } private double strongScaleValue(JudgementResult result) diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs index 5d2ad8b363..1a644ad600 100644 --- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Gameplay // Apply a judgement scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new TestJudgement(HitResult.LargeBonus)) { Type = HitResult.LargeBonus }); - Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(scoreProcessor.GetRawBonusScore(HitResult.LargeBonus))); + Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(scoreProcessor.GetBaseScoreForResult(HitResult.LargeBonus))); } [Test] diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index 5324d2569e..8a25a44b80 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -57,14 +57,14 @@ namespace osu.Game.Database // We are constructing a "best possible" score from the statistics provided because it's the best we can do. List sortedHits = score.Statistics .Where(kvp => kvp.Key.AffectsCombo()) - .OrderByDescending(kvp => processor.GetRawComboScore(kvp.Key)) + .OrderByDescending(kvp => processor.GetBaseScoreForResult(kvp.Key)) .SelectMany(kvp => Enumerable.Repeat(kvp.Key, kvp.Value)) .ToList(); // Attempt to use maximum statistics from the database. var maximumJudgements = score.MaximumStatistics .Where(kvp => kvp.Key.AffectsCombo()) - .OrderByDescending(kvp => processor.GetRawComboScore(kvp.Key)) + .OrderByDescending(kvp => processor.GetBaseScoreForResult(kvp.Key)) .SelectMany(kvp => Enumerable.Repeat(new FakeJudgement(kvp.Key), kvp.Value)) .ToList(); diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index a80a118363..5123668e54 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -227,12 +227,12 @@ namespace osu.Game.Rulesets.Scoring if (result.Judgement.MaxResult.AffectsAccuracy()) { - currentMaximumBaseScore += GetRawAccuracyScore(result.Judgement.MaxResult); + currentMaximumBaseScore += GetBaseScoreForResult(result.Judgement.MaxResult); currentAccuracyJudgementCount++; } if (result.Type.AffectsAccuracy()) - currentBaseScore += GetRawAccuracyScore(result.Type); + currentBaseScore += GetBaseScoreForResult(result.Type); if (result.Type.IsBonus()) currentBonusPortion += GetBonusScoreChange(result); @@ -276,12 +276,12 @@ namespace osu.Game.Rulesets.Scoring if (result.Judgement.MaxResult.AffectsAccuracy()) { - currentMaximumBaseScore -= GetRawAccuracyScore(result.Judgement.MaxResult); + currentMaximumBaseScore -= GetBaseScoreForResult(result.Judgement.MaxResult); currentAccuracyJudgementCount--; } if (result.Type.AffectsAccuracy()) - currentBaseScore -= GetRawAccuracyScore(result.Type); + currentBaseScore -= GetBaseScoreForResult(result.Type); if (result.Type.IsBonus()) currentBonusPortion -= GetBonusScoreChange(result); @@ -301,19 +301,15 @@ namespace osu.Game.Rulesets.Scoring /// Gets the final score change to be applied to the bonus portion of the score. /// /// The judgement result. - protected virtual double GetBonusScoreChange(JudgementResult result) => GetRawBonusScore(result.Type); + protected virtual double GetBonusScoreChange(JudgementResult result) => GetBaseScoreForResult(result.Type); /// /// Gets the final score change to be applied to the combo portion of the score. /// /// The judgement result. - protected virtual double GetComboScoreChange(JudgementResult result) => GetRawComboScore(result.Judgement.MaxResult) * Math.Pow(result.ComboAfterJudgement, COMBO_EXPONENT); + protected virtual double GetComboScoreChange(JudgementResult result) => GetBaseScoreForResult(result.Judgement.MaxResult) * Math.Pow(result.ComboAfterJudgement, COMBO_EXPONENT); - /// - /// Retrieves the raw score value for a hit result, in order to be applied to the combo portion. - /// - /// The hit result. - public virtual int GetRawComboScore(HitResult result) + public virtual int GetBaseScoreForResult(HitResult result) { switch (result) { @@ -338,51 +334,6 @@ namespace osu.Game.Rulesets.Scoring case HitResult.Great: case HitResult.Perfect: // Perfect doesn't actually give more score / accuracy directly. return 300; - } - } - - /// - /// Retrieves the raw score value for a hit result, in order to be applied to the accuracy portion. - /// - /// The hit result. - public virtual int GetRawAccuracyScore(HitResult result) - { - switch (result) - { - default: - return 0; - - case HitResult.SmallTickHit: - return 10; - - case HitResult.LargeTickHit: - return 30; - - case HitResult.Meh: - return 50; - - case HitResult.Ok: - return 100; - - case HitResult.Good: - return 200; - - case HitResult.Great: - case HitResult.Perfect: // Perfect doesn't actually give more score / accuracy directly. - return 300; - } - } - - /// - /// Retrieves the raw score value for a hit result, in order to be applied to the bonus portion. - /// - /// The hit result. - public virtual int GetRawBonusScore(HitResult result) - { - switch (result) - { - default: - return 0; case HitResult.SmallBonus: return 10; @@ -619,7 +570,7 @@ namespace osu.Game.Rulesets.Scoring /// /// /// Used to compute accuracy. - /// See: and . + /// See: and . /// [Key(0)] public double BaseScore { get; set; } diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index b1cba3756b..3c7ee5c86b 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -131,7 +131,7 @@ namespace osu.Game.Scoring // Populate the maximum statistics. HitResult maxBasicResult = rulesetInstance.GetHitResults() .Select(h => h.result) - .Where(h => h.IsBasic()).MaxBy(scoreProcessor.GetRawAccuracyScore); + .Where(h => h.IsBasic()).MaxBy(scoreProcessor.GetBaseScoreForResult); foreach ((HitResult result, int count) in score.Statistics) { From eb072a1d242f0f1680183200310742fa4d850371 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 21 Dec 2023 15:08:10 +0900 Subject: [PATCH 2068/4092] Add accuracy conversion, fix usages --- osu.Game/BackgroundDataStoreProcessor.cs | 5 +-- .../StandardisedScoreMigrationTools.cs | 42 +++++++++++++++++-- osu.Game/Scoring/ScoreImporter.cs | 2 +- 3 files changed, 41 insertions(+), 8 deletions(-) diff --git a/osu.Game/BackgroundDataStoreProcessor.cs b/osu.Game/BackgroundDataStoreProcessor.cs index 8f6dced350..a748a7422a 100644 --- a/osu.Game/BackgroundDataStoreProcessor.cs +++ b/osu.Game/BackgroundDataStoreProcessor.cs @@ -340,15 +340,12 @@ namespace osu.Game try { - var score = scoreManager.Query(s => s.ID == id); - long newTotalScore = StandardisedScoreMigrationTools.ConvertFromLegacyTotalScore(score, beatmapManager); - // Can't use async overload because we're not on the update thread. // ReSharper disable once MethodHasAsyncOverload realmAccess.Write(r => { ScoreInfo s = r.Find(id)!; - s.TotalScore = newTotalScore; + StandardisedScoreMigrationTools.UpdateFromLegacy(s, beatmapManager); s.TotalScoreVersion = LegacyScoreEncoder.LATEST_VERSION; }); diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index 8a25a44b80..380bf63e63 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -231,13 +231,36 @@ namespace osu.Game.Database } } + /// + /// Updates a legacy to standardised scoring. + /// + /// The score to update. + /// A used for lookups. + public static void UpdateFromLegacy(ScoreInfo score, BeatmapManager beatmaps) + { + score.TotalScore = convertFromLegacyTotalScore(score, beatmaps); + score.Accuracy = ComputeAccuracy(score); + } + + /// + /// Updates a legacy to standardised scoring. + /// + /// The score to update. + /// The beatmap difficulty. + /// The legacy scoring attributes for the beatmap which the score was set on. + public static void UpdateFromLegacy(ScoreInfo score, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes) + { + score.TotalScore = convertFromLegacyTotalScore(score, difficulty, attributes); + score.Accuracy = ComputeAccuracy(score); + } + /// /// Converts from to the new standardised scoring of . /// /// The score to convert the total score of. /// A used for lookups. /// The standardised total score. - public static long ConvertFromLegacyTotalScore(ScoreInfo score, BeatmapManager beatmaps) + private static long convertFromLegacyTotalScore(ScoreInfo score, BeatmapManager beatmaps) { if (!score.IsLegacyScore) return score.TotalScore; @@ -260,7 +283,7 @@ namespace osu.Game.Database ILegacyScoreSimulator sv1Simulator = legacyRuleset.CreateLegacyScoreSimulator(); LegacyScoreAttributes attributes = sv1Simulator.Simulate(beatmap, playableBeatmap); - return ConvertFromLegacyTotalScore(score, LegacyBeatmapConversionDifficultyInfo.FromBeatmap(beatmap.Beatmap), attributes); + return convertFromLegacyTotalScore(score, LegacyBeatmapConversionDifficultyInfo.FromBeatmap(beatmap.Beatmap), attributes); } /// @@ -270,7 +293,7 @@ namespace osu.Game.Database /// The beatmap difficulty. /// The legacy scoring attributes for the beatmap which the score was set on. /// The standardised total score. - public static long ConvertFromLegacyTotalScore(ScoreInfo score, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes) + private static long convertFromLegacyTotalScore(ScoreInfo score, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes) { if (!score.IsLegacyScore) return score.TotalScore; @@ -422,6 +445,19 @@ namespace osu.Game.Database } } + public static double ComputeAccuracy(ScoreInfo scoreInfo) + { + Ruleset ruleset = scoreInfo.Ruleset.CreateInstance(); + ScoreProcessor scoreProcessor = ruleset.CreateScoreProcessor(); + + int baseScore = scoreInfo.Statistics.Where(kvp => kvp.Key.AffectsAccuracy()) + .Sum(kvp => kvp.Value * scoreProcessor.GetBaseScoreForResult(kvp.Key)); + int maxBaseScore = scoreInfo.MaximumStatistics.Where(kvp => kvp.Key.AffectsAccuracy()) + .Sum(kvp => kvp.Value * scoreProcessor.GetBaseScoreForResult(kvp.Key)); + + return maxBaseScore == 0 ? 1 : baseScore / (double)maxBaseScore; + } + /// /// Used to populate the model using data parsed from its corresponding replay file. /// diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index 3c7ee5c86b..8e28707107 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -106,7 +106,7 @@ namespace osu.Game.Scoring else if (model.IsLegacyScore) { model.LegacyTotalScore = model.TotalScore; - model.TotalScore = StandardisedScoreMigrationTools.ConvertFromLegacyTotalScore(model, beatmaps()); + StandardisedScoreMigrationTools.UpdateFromLegacy(model, beatmaps()); } } From c3c1752a5a315ae2d72b8c4b6b6a9436d5e579eb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 21 Dec 2023 15:32:34 +0900 Subject: [PATCH 2069/4092] Reconvert all legacy scores --- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index fa930d77d3..dbd9a106df 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -34,9 +34,10 @@ namespace osu.Game.Scoring.Legacy /// 30000005: Introduce combo exponent in the osu! gamemode. Reconvert all scores. /// 30000006: Fix edge cases in conversion after combo exponent introduction that lead to NaNs. Reconvert all scores. /// 30000007: Adjust osu!mania combo and accuracy portions and judgement scoring values. Reconvert all scores. + /// 30000008: Add accuracy conversion. Reconvert all scores. /// /// - public const int LATEST_VERSION = 30000007; + public const int LATEST_VERSION = 30000008; /// /// The first stable-compatible YYYYMMDD format version given to lazer usage of replays. From 0648201844a962429a99a2754a2c10e439882680 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Dec 2023 18:17:03 +0900 Subject: [PATCH 2070/4092] Cancel test more --- osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 218bf495e3..ce241f3676 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -420,7 +420,7 @@ namespace osu.Game.Tests.Visual.SongSelect } [Test] - [FlakyTest] // temporary while peppy investigates + [Ignore("temporary while peppy investigates. probably realm batching related.")] public void TestSelectionRetainedOnBeatmapUpdate() { createSongSelect(); From a4baa0a716f05e6415e907824ff0bd06b66e2dfb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Dec 2023 18:14:24 +0900 Subject: [PATCH 2071/4092] Add versioning of local scores For any potential future usage --- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 2 ++ .../Visual/Gameplay/TestScenePlayerLocalScoreImport.cs | 4 ++++ osu.Game/Database/RealmAccess.cs | 3 ++- osu.Game/Scoring/ScoreInfo.cs | 6 ++++++ osu.Game/Screens/Play/Player.cs | 9 ++++++++- 5 files changed, 22 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index dd724d268e..85b4219792 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -196,6 +196,7 @@ namespace osu.Game.Tests.Scores.IO User = new APIUser { Username = "Test user" }, BeatmapInfo = beatmap.Beatmaps.First(), Ruleset = new OsuRuleset().RulesetInfo, + Version = "12345", Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, }; @@ -203,6 +204,7 @@ namespace osu.Game.Tests.Scores.IO Assert.IsTrue(imported.Mods.Any(m => m is OsuModHardRock)); Assert.IsTrue(imported.Mods.Any(m => m is OsuModDoubleTime)); + Assert.That(imported.Version, Is.EqualTo(toImport.Version)); } finally { diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index 0dd544bb30..f7a5ec7562 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -41,6 +41,9 @@ namespace osu.Game.Tests.Visual.Gameplay private BeatmapSetInfo? importedSet; + [Resolved] + private OsuGameBase osu { get; set; } = null!; + [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { @@ -153,6 +156,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); AddUntilStep("score in database", () => Realm.Run(r => r.Find(Player.Score.ScoreInfo.ID) != null)); + AddUntilStep("score has correct version", () => Realm.Run(r => r.Find(Player.Score.ScoreInfo.ID)!.Version), () => Is.EqualTo(osu.Version)); } [Test] diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index ad61292c2e..4bd7f36cdd 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -90,8 +90,9 @@ namespace osu.Game.Database /// 36 2023-10-26 Add LegacyOnlineID to ScoreInfo. Move osu_scores_*_high IDs stored in OnlineID to LegacyOnlineID. Reset anomalous OnlineIDs. /// 38 2023-12-10 Add EndTimeObjectCount and TotalObjectCount to BeatmapInfo. /// 39 2023-12-19 Migrate any EndTimeObjectCount and TotalObjectCount values of 0 to -1 to better identify non-calculated values. + /// 40 2023-12-21 Add ScoreInfo.Version to keep track of which build scores were set on. /// - private const int schema_version = 39; + private const int schema_version = 40; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 5545ba552e..4c00c73341 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -46,6 +46,12 @@ namespace osu.Game.Scoring /// public BeatmapInfo? BeatmapInfo { get; set; } + /// + /// The version of the client this score was set using. + /// Sourced from at the point of score submission. + /// + public string Version { get; set; } = string.Empty; + /// /// The at the point in time when the score was set. /// diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index cc08079d88..a432242046 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -109,6 +109,9 @@ namespace osu.Game.Screens.Play [Resolved] private MusicController musicController { get; set; } + [Resolved] + private OsuGameBase game { get; set; } + public GameplayState GameplayState { get; private set; } private Ruleset ruleset; @@ -1155,7 +1158,11 @@ namespace osu.Game.Screens.Play /// The . protected virtual Score CreateScore(IBeatmap beatmap) => new Score { - ScoreInfo = new ScoreInfo { User = api.LocalUser.Value }, + ScoreInfo = new ScoreInfo + { + User = api.LocalUser.Value, + Version = game.Version, + }, }; /// From 81bbdccee788daac7c7865859673eed1521191e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 Dec 2023 12:56:19 +0100 Subject: [PATCH 2072/4092] Rename `ScoreInfo.{ -> Client}Version` --- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 4 ++-- .../Visual/Gameplay/TestScenePlayerLocalScoreImport.cs | 2 +- osu.Game/Scoring/ScoreInfo.cs | 2 +- osu.Game/Screens/Play/Player.cs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index 85b4219792..ebbc329b9d 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -196,7 +196,7 @@ namespace osu.Game.Tests.Scores.IO User = new APIUser { Username = "Test user" }, BeatmapInfo = beatmap.Beatmaps.First(), Ruleset = new OsuRuleset().RulesetInfo, - Version = "12345", + ClientVersion = "12345", Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, }; @@ -204,7 +204,7 @@ namespace osu.Game.Tests.Scores.IO Assert.IsTrue(imported.Mods.Any(m => m is OsuModHardRock)); Assert.IsTrue(imported.Mods.Any(m => m is OsuModDoubleTime)); - Assert.That(imported.Version, Is.EqualTo(toImport.Version)); + Assert.That(imported.ClientVersion, Is.EqualTo(toImport.ClientVersion)); } finally { diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index f7a5ec7562..fafd1330cc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -156,7 +156,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); AddUntilStep("score in database", () => Realm.Run(r => r.Find(Player.Score.ScoreInfo.ID) != null)); - AddUntilStep("score has correct version", () => Realm.Run(r => r.Find(Player.Score.ScoreInfo.ID)!.Version), () => Is.EqualTo(osu.Version)); + AddUntilStep("score has correct version", () => Realm.Run(r => r.Find(Player.Score.ScoreInfo.ID)!.ClientVersion), () => Is.EqualTo(osu.Version)); } [Test] diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 4c00c73341..7071bd380e 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -50,7 +50,7 @@ namespace osu.Game.Scoring /// The version of the client this score was set using. /// Sourced from at the point of score submission. /// - public string Version { get; set; } = string.Empty; + public string ClientVersion { get; set; } = string.Empty; /// /// The at the point in time when the score was set. diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a432242046..c9251b0a78 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1161,7 +1161,7 @@ namespace osu.Game.Screens.Play ScoreInfo = new ScoreInfo { User = api.LocalUser.Value, - Version = game.Version, + ClientVersion = game.Version, }, }; From 2baf579f7ce62429e83aa4781ec3d55320b92d09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 Dec 2023 12:58:08 +0100 Subject: [PATCH 2073/4092] Serialise and deserialise `ClientVersion` to replays --- osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs | 4 ++++ osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 1 + 2 files changed, 5 insertions(+) diff --git a/osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs b/osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs index d34edf7bdf..2c5b91f10f 100644 --- a/osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs +++ b/osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs @@ -35,12 +35,16 @@ namespace osu.Game.Scoring.Legacy [JsonProperty("maximum_statistics")] public Dictionary MaximumStatistics { get; set; } = new Dictionary(); + [JsonProperty("client_version")] + public string ClientVersion = string.Empty; + public static LegacyReplaySoloScoreInfo FromScore(ScoreInfo score) => new LegacyReplaySoloScoreInfo { OnlineID = score.OnlineID, Mods = score.APIMods, Statistics = score.Statistics.Where(kvp => kvp.Value != 0).ToDictionary(kvp => kvp.Key, kvp => kvp.Value), MaximumStatistics = score.MaximumStatistics.Where(kvp => kvp.Value != 0).ToDictionary(kvp => kvp.Key, kvp => kvp.Value), + ClientVersion = score.ClientVersion, }; } } diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index c5e6e3bcce..ed11691674 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -125,6 +125,7 @@ namespace osu.Game.Scoring.Legacy score.ScoreInfo.Statistics = readScore.Statistics; score.ScoreInfo.MaximumStatistics = readScore.MaximumStatistics; score.ScoreInfo.Mods = readScore.Mods.Select(m => m.ToMod(currentRuleset)).ToArray(); + score.ScoreInfo.ClientVersion = readScore.ClientVersion; }); } } From 5ff95db02c2af9ce6e7b1d707ee88440e3df9e6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 Dec 2023 13:06:42 +0100 Subject: [PATCH 2074/4092] Add test coverage of `ClientVersion` serialisation --- osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index ab88be1511..4e281cf28e 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -219,6 +219,8 @@ namespace osu.Game.Tests.Beatmaps.Formats { new OsuModDoubleTime { SpeedChange = { Value = 1.1 } } }; + scoreInfo.OnlineID = 123123; + scoreInfo.ClientVersion = "2023.1221.0"; var beatmap = new TestBeatmap(ruleset); var score = new Score @@ -237,9 +239,11 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.Multiple(() => { + Assert.That(decodedAfterEncode.ScoreInfo.OnlineID, Is.EqualTo(123123)); Assert.That(decodedAfterEncode.ScoreInfo.Statistics, Is.EqualTo(scoreInfo.Statistics)); Assert.That(decodedAfterEncode.ScoreInfo.MaximumStatistics, Is.EqualTo(scoreInfo.MaximumStatistics)); Assert.That(decodedAfterEncode.ScoreInfo.Mods, Is.EqualTo(scoreInfo.Mods)); + Assert.That(decodedAfterEncode.ScoreInfo.ClientVersion, Is.EqualTo("2023.1221.0")); }); } From d4731e0830867868eec1050e51e90587bcfb330b Mon Sep 17 00:00:00 2001 From: Daniel Power Date: Thu, 21 Dec 2023 08:56:39 -0330 Subject: [PATCH 2075/4092] Fix scale of skin element bounding box --- osu.Game/Overlays/SkinEditor/SkinBlueprint.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs b/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs index 01cd3d97e0..8f8d899fad 100644 --- a/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs +++ b/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs @@ -136,9 +136,10 @@ namespace osu.Game.Overlays.SkinEditor { base.Update(); + Vector2 scale = drawable.DrawInfo.MatrixInverse.ExtractScale().Xy; drawableQuad = drawable.ToScreenSpace( drawable.DrawRectangle - .Inflate(SkinSelectionHandler.INFLATE_SIZE)); + .Inflate(SkinSelectionHandler.INFLATE_SIZE * scale)); var localSpaceQuad = ToLocalSpace(drawableQuad); From b4e71a0787df19714a5cbf0a166a81d139d7cd23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 Dec 2023 13:58:12 +0100 Subject: [PATCH 2076/4092] Fix slider tick / end misses displaying with full size on legacy skins with animated misses Resolves https://github.com/ppy/osu/issues/25987. Structure is a bit clumsy but I'm not sure how to do better... --- osu.Game/Skinning/LegacyJudgementPieceOld.cs | 21 +++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/osu.Game/Skinning/LegacyJudgementPieceOld.cs b/osu.Game/Skinning/LegacyJudgementPieceOld.cs index ca2c8ce6bc..68274ffa2d 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceOld.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceOld.cs @@ -50,17 +50,16 @@ namespace osu.Game.Skinning // legacy judgements don't play any transforms if they are an animation.... UNLESS they are the temporary displayed judgement from new piece. if (animation?.FrameCount > 1 && !forceTransforms) + { + if (isMissedTick()) + applyMissedTickScaling(); return; + } if (result.IsMiss()) { - bool isTick = result != HitResult.Miss; - - if (isTick) - { - this.ScaleTo(0.6f); - this.ScaleTo(0.3f, 100, Easing.In); - } + if (isMissedTick()) + applyMissedTickScaling(); else { this.ScaleTo(1.6f); @@ -95,6 +94,14 @@ namespace osu.Game.Skinning } } + private bool isMissedTick() => result.IsMiss() && result != HitResult.Miss; + + private void applyMissedTickScaling() + { + this.ScaleTo(0.6f); + this.ScaleTo(0.3f, 100, Easing.In); + } + public Drawable GetAboveHitObjectsProxiedContent() => CreateProxy(); } } From 3f6dad5502d6aec67d9e7522fdb69d21299d5058 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 22 Dec 2023 01:33:50 +0900 Subject: [PATCH 2077/4092] Use classic HP values for non-classic osu! HP drain --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 + .../Scoring/OsuHealthProcessor.cs | 68 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index e53f20277b..35cbfa3790 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -48,6 +48,8 @@ namespace osu.Game.Rulesets.Osu public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(); + public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new OsuHealthProcessor(drainStartTime); + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap, this); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap); diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs new file mode 100644 index 0000000000..784d3b934d --- /dev/null +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs @@ -0,0 +1,68 @@ +// 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; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Osu.Scoring +{ + public partial class OsuHealthProcessor : DrainingHealthProcessor + { + public OsuHealthProcessor(double drainStartTime, double drainLenience = 0) + : base(drainStartTime, drainLenience) + { + } + + protected override double GetHealthIncreaseFor(JudgementResult result) + { + switch (result.Type) + { + case HitResult.SmallTickMiss: + return IBeatmapDifficultyInfo.DifficultyRange(Beatmap.Difficulty.DrainRate, -0.02, -0.075, -0.14); + + case HitResult.LargeTickMiss: + return IBeatmapDifficultyInfo.DifficultyRange(Beatmap.Difficulty.DrainRate, -0.02, -0.075, -0.14); + + case HitResult.Miss: + return IBeatmapDifficultyInfo.DifficultyRange(Beatmap.Difficulty.DrainRate, -0.03, -0.125, -0.2); + + case HitResult.SmallTickHit: + // When classic slider mechanics are enabled, this result comes from the tail. + return 0.02; + + case HitResult.LargeTickHit: + switch (result.HitObject) + { + case SliderTick: + return 0.015; + + case SliderHeadCircle: + case SliderTailCircle: + case SliderRepeat: + return 0.02; + } + + break; + + case HitResult.Meh: + return 0.002; + + case HitResult.Ok: + return 0.011; + + case HitResult.Great: + return 0.03; + + case HitResult.SmallBonus: + return 0.0085; + + case HitResult.LargeBonus: + return 0.01; + } + + return base.GetHealthIncreaseFor(result); + } + } +} From b31b9e96d0cf016e4f7b892116fd6951977a7853 Mon Sep 17 00:00:00 2001 From: Simon G <45692977+jeenyuhs@users.noreply.github.com> Date: Fri, 22 Dec 2023 03:04:48 +0100 Subject: [PATCH 2078/4092] adjust beatmap length and drain based on rate changing mods --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 25 ++++++++++++++------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 2613857998..c69cd6ead6 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -161,6 +161,7 @@ namespace osu.Game.Screens.Select private ILocalisedBindableString artistBinding; private FillFlowContainer infoLabelContainer; private Container bpmLabelContainer; + private Container lengthLabelContainer; private readonly WorkingBeatmap working; private readonly RulesetInfo ruleset; @@ -341,10 +342,10 @@ namespace osu.Game.Screens.Select { settingChangeTracker?.Dispose(); - refreshBPMLabel(); + refreshBPMAndLengthLabel(); settingChangeTracker = new ModSettingChangeTracker(m.NewValue); - settingChangeTracker.SettingChanged += _ => refreshBPMLabel(); + settingChangeTracker.SettingChanged += _ => refreshBPMAndLengthLabel(); }, true); } @@ -370,12 +371,10 @@ namespace osu.Game.Screens.Select infoLabelContainer.Children = new Drawable[] { - new InfoLabel(new BeatmapStatistic + lengthLabelContainer = new Container { - Name = BeatmapsetsStrings.ShowStatsTotalLength(playableBeatmap.CalculateDrainLength().ToFormattedDuration()), - CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Length), - Content = working.BeatmapInfo.Length.ToFormattedDuration().ToString(), - }), + AutoSizeAxes = Axes.Both, + }, bpmLabelContainer = new Container { AutoSizeAxes = Axes.Both, @@ -394,7 +393,7 @@ namespace osu.Game.Screens.Select } } - private void refreshBPMLabel() + private void refreshBPMAndLengthLabel() { var beatmap = working.Beatmap; @@ -420,6 +419,16 @@ namespace osu.Game.Screens.Select CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Bpm), Content = labelText }); + + double drainLength = Math.Round(beatmap.CalculateDrainLength() / rate); + double hitLength = Math.Round(beatmap.BeatmapInfo.Length / rate); + + lengthLabelContainer.Child = new InfoLabel(new BeatmapStatistic + { + Name = BeatmapsetsStrings.ShowStatsTotalLength(drainLength.ToFormattedDuration()), + CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Length), + Content = hitLength.ToFormattedDuration().ToString(), + }); } private Drawable getMapper(BeatmapMetadata metadata) From 3b58f6a7e78d8244a6e60bf8b27078c84845fb3c Mon Sep 17 00:00:00 2001 From: smallketchup82 <69545310+smallketchup82@users.noreply.github.com> Date: Thu, 21 Dec 2023 21:07:12 -0500 Subject: [PATCH 2079/4092] Implement difficulty statistics --- global.json | 8 +- .../Drawables/DifficultyIconTooltip.cs | 100 +++++++++++++++++- 2 files changed, 103 insertions(+), 5 deletions(-) diff --git a/global.json b/global.json index 5dcd5f425a..7835999220 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,7 @@ { "sdk": { - "version": "6.0.100", - "rollForward": "latestFeature" + "version": "6.0.0", + "rollForward": "latestFeature", + "allowPrerelease": true } -} - +} \ No newline at end of file diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index 3fa24bcc3e..7ea2c6a0a2 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -19,6 +20,13 @@ namespace osu.Game.Beatmaps.Drawables { private OsuSpriteText difficultyName; private StarRatingDisplay starRating; + private OsuSpriteText overallDifficulty; + private OsuSpriteText drainRate; + private OsuSpriteText circleSize; + private OsuSpriteText approachRate; + private OsuSpriteText BPM; + private OsuSpriteText maxCombo; + private OsuSpriteText length; [BackgroundDependencyLoader] private void load(OsuColour colours) @@ -35,6 +43,7 @@ namespace osu.Game.Beatmaps.Drawables Colour = colours.Gray3, RelativeSizeAxes = Axes.Both }, + // Headers new FillFlowContainer { AutoSizeAxes = Axes.Both, @@ -55,6 +64,84 @@ namespace osu.Game.Beatmaps.Drawables { Anchor = Anchor.Centre, Origin = Anchor.Centre, + }, + // Difficulty stats + new FillFlowContainer() + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5), + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold), + }, + overallDifficulty = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 14), + }, + drainRate = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 14), + }, + circleSize = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 14), + }, + approachRate = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 14), + }, + } + }, + // Misc stats + new FillFlowContainer() + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5), + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold), + }, + length = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 14), + }, + BPM = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 14), + }, + maxCombo = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 14), + }, + } } } } @@ -68,10 +155,21 @@ namespace osu.Game.Beatmaps.Drawables if (displayedContent != null) starRating.Current.UnbindFrom(displayedContent.Difficulty); + // Header row displayedContent = content; - starRating.Current.BindTarget = displayedContent.Difficulty; difficultyName.Text = displayedContent.BeatmapInfo.DifficultyName; + + // Difficulty row + overallDifficulty.Text = "OD: " + displayedContent.BeatmapInfo.Difficulty.OverallDifficulty.ToString("0.##"); + drainRate.Text = "| HP: " + displayedContent.BeatmapInfo.Difficulty.DrainRate.ToString("0.##"); + circleSize.Text = "| CS: " + displayedContent.BeatmapInfo.Difficulty.CircleSize.ToString("0.##"); + approachRate.Text = "| AR: " + displayedContent.BeatmapInfo.Difficulty.ApproachRate.ToString("0.##"); + + // Misc row + length.Text = "Length: " + TimeSpan.FromMilliseconds(displayedContent.BeatmapInfo.Length).ToString("mm\\:ss"); + BPM.Text = "| BPM: " + displayedContent.BeatmapInfo.BPM; + maxCombo.Text = "| Max Combo: " + displayedContent.BeatmapInfo.TotalObjectCount; } public void Move(Vector2 pos) => Position = pos; From 9c35e250368ca3d777e1b2f4394310008839d3b3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 22 Dec 2023 13:38:52 +0900 Subject: [PATCH 2080/4092] Add failing tests --- .../Mods/TestSceneManiaModPerfect.cs | 29 +++++++++++++++++++ .../Mods/TestSceneOsuModPerfect.cs | 29 +++++++++++++++++++ osu.Game/Tests/Visual/ModPerfectTestScene.cs | 8 ++--- osu.Game/Tests/Visual/ModTestScene.cs | 18 ++++++------ 4 files changed, 71 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs index 97a6ee28f4..a734979bf6 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs @@ -1,9 +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.Collections.Generic; using NUnit.Framework; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Replays; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Replays; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests.Mods @@ -24,5 +29,29 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods [TestCase(false)] [TestCase(true)] public void TestHoldNote(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new HoldNote { StartTime = 1000, EndTime = 3000 }), shouldMiss); + + [Test] + public void TestBreakOnHoldNote() => CreateModTest(new ModTestData + { + Mod = new ManiaModPerfect(), + PassCondition = () => ((PerfectModTestPlayer)Player).CheckFailed(true) && Player.Results.Count == 2, + Autoplay = false, + Beatmap = new Beatmap + { + HitObjects = new List + { + new HoldNote + { + StartTime = 1000, + EndTime = 3000, + }, + }, + }, + ReplayFrames = new List + { + new ManiaReplayFrame(1000, ManiaAction.Key1), + new ManiaReplayFrame(2000) + } + }); } } diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs index 26c4133bc4..7030061e0e 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs @@ -1,11 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using NUnit.Framework; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Replays; using osu.Game.Tests.Visual; using osuTK; @@ -50,5 +54,30 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods CreateHitObjectTest(new HitObjectTestData(spinner), shouldMiss); } + + [Test] + public void TestMissSliderTail() => CreateModTest(new ModTestData + { + Mod = new OsuModPerfect(), + PassCondition = () => ((PerfectModTestPlayer)Player).CheckFailed(true), + Autoplay = false, + Beatmap = new Beatmap + { + HitObjects = new List + { + new Slider + { + Position = new Vector2(256, 192), + StartTime = 1000, + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(100, 0), }) + }, + }, + }, + ReplayFrames = new List + { + new OsuReplayFrame(1000, new Vector2(256, 192), OsuAction.LeftButton), + new OsuReplayFrame(1001, new Vector2(256, 192)), + } + }); } } diff --git a/osu.Game/Tests/Visual/ModPerfectTestScene.cs b/osu.Game/Tests/Visual/ModPerfectTestScene.cs index 164faa16aa..c6f4a27ec4 100644 --- a/osu.Game/Tests/Visual/ModPerfectTestScene.cs +++ b/osu.Game/Tests/Visual/ModPerfectTestScene.cs @@ -29,12 +29,12 @@ namespace osu.Game.Tests.Visual PassCondition = () => ((PerfectModTestPlayer)Player).CheckFailed(shouldMiss && testData.FailOnMiss) }); - protected override TestPlayer CreateModPlayer(Ruleset ruleset) => new PerfectModTestPlayer(); + protected override TestPlayer CreateModPlayer(Ruleset ruleset) => new PerfectModTestPlayer(CurrentTestData, AllowFail); - private partial class PerfectModTestPlayer : TestPlayer + protected partial class PerfectModTestPlayer : ModTestPlayer { - public PerfectModTestPlayer() - : base(showResults: false) + public PerfectModTestPlayer(ModTestData data, bool allowFail) + : base(data, allowFail) { } diff --git a/osu.Game/Tests/Visual/ModTestScene.cs b/osu.Game/Tests/Visual/ModTestScene.cs index aa5b506343..c2ebcdefac 100644 --- a/osu.Game/Tests/Visual/ModTestScene.cs +++ b/osu.Game/Tests/Visual/ModTestScene.cs @@ -20,35 +20,35 @@ namespace osu.Game.Tests.Visual { protected sealed override bool HasCustomSteps => true; - private ModTestData currentTestData; + protected ModTestData CurrentTestData { get; private set; } protected void CreateModTest(ModTestData testData) => CreateTest(() => { - AddStep("set test data", () => currentTestData = testData); + AddStep("set test data", () => CurrentTestData = testData); }); public override void TearDownSteps() { AddUntilStep("test passed", () => { - if (currentTestData == null) + if (CurrentTestData == null) return true; - return currentTestData.PassCondition?.Invoke() ?? false; + return CurrentTestData.PassCondition?.Invoke() ?? false; }); base.TearDownSteps(); } - protected sealed override IBeatmap CreateBeatmap(RulesetInfo ruleset) => currentTestData?.Beatmap ?? base.CreateBeatmap(ruleset); + protected sealed override IBeatmap CreateBeatmap(RulesetInfo ruleset) => CurrentTestData?.Beatmap ?? base.CreateBeatmap(ruleset); protected sealed override TestPlayer CreatePlayer(Ruleset ruleset) { var mods = new List(SelectedMods.Value); - if (currentTestData.Mods != null) - mods.AddRange(currentTestData.Mods); - if (currentTestData.Autoplay) + if (CurrentTestData.Mods != null) + mods.AddRange(CurrentTestData.Mods); + if (CurrentTestData.Autoplay) mods.Add(ruleset.GetAutoplayMod()); SelectedMods.Value = mods; @@ -56,7 +56,7 @@ namespace osu.Game.Tests.Visual return CreateModPlayer(ruleset); } - protected virtual TestPlayer CreateModPlayer(Ruleset ruleset) => new ModTestPlayer(currentTestData, AllowFail); + protected virtual TestPlayer CreateModPlayer(Ruleset ruleset) => new ModTestPlayer(CurrentTestData, AllowFail); protected partial class ModTestPlayer : TestPlayer { From ea778c6e0aa6bf52ae5e12f287b0fb7f131348ca Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 22 Dec 2023 13:39:41 +0900 Subject: [PATCH 2081/4092] Fix perfect/sudden death not working on slider tails --- osu.Game/Rulesets/Mods/ModPerfect.cs | 4 +++- osu.Game/Rulesets/Mods/ModSuddenDeath.cs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModPerfect.cs b/osu.Game/Rulesets/Mods/ModPerfect.cs index 6f0bb7ad3b..0ba40ba070 100644 --- a/osu.Game/Rulesets/Mods/ModPerfect.cs +++ b/osu.Game/Rulesets/Mods/ModPerfect.cs @@ -28,7 +28,9 @@ namespace osu.Game.Rulesets.Mods } protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) - => result.Type.AffectsAccuracy() + => (isRelevantResult(result.Judgement.MinResult) || isRelevantResult(result.Judgement.MaxResult) || isRelevantResult(result.Type)) && result.Type != result.Judgement.MaxResult; + + private bool isRelevantResult(HitResult result) => result.AffectsAccuracy() || result.AffectsCombo(); } } diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs index 4e4e8662e8..56420e5ff4 100644 --- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs +++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModPerfect)).ToArray(); protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) - => result.Type.AffectsCombo() + => (result.Judgement.MinResult.AffectsCombo() || result.Judgement.MaxResult.AffectsCombo() || result.Type.AffectsCombo()) && !result.IsHit; } } From 93efa98d9b8c1ce647a5baca6770f3c0c3e39755 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 22 Dec 2023 16:19:36 +0900 Subject: [PATCH 2082/4092] Fix mania "Great" hits failing with perfect mod --- .../Mods/TestSceneManiaModPerfect.cs | 23 +++++++++++++++++++ .../Mods/ManiaModPerfect.cs | 15 ++++++++++++ 2 files changed, 38 insertions(+) diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs index a734979bf6..73d6fe357d 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs @@ -30,6 +30,29 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods [TestCase(true)] public void TestHoldNote(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new HoldNote { StartTime = 1000, EndTime = 3000 }), shouldMiss); + [Test] + public void TestGreatHit() => CreateModTest(new ModTestData + { + Mod = new ManiaModPerfect(), + PassCondition = () => ((PerfectModTestPlayer)Player).CheckFailed(false), + Autoplay = false, + Beatmap = new Beatmap + { + HitObjects = new List + { + new Note + { + StartTime = 1000, + } + }, + }, + ReplayFrames = new List + { + new ManiaReplayFrame(1020, ManiaAction.Key1), + new ManiaReplayFrame(2000) + } + }); + [Test] public void TestBreakOnHoldNote() => CreateModTest(new ModTestData { diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModPerfect.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModPerfect.cs index 2e22e23dbd..b02a18c9f4 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModPerfect.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModPerfect.cs @@ -1,11 +1,26 @@ // 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.Judgements; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModPerfect : ModPerfect { + protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) + { + if (!isRelevantResult(result.Judgement.MinResult) && !isRelevantResult(result.Judgement.MaxResult) && !isRelevantResult(result.Type)) + return false; + + // Mania allows imperfect "Great" hits without failing. + if (result.Judgement.MaxResult == HitResult.Perfect) + return result.Type < HitResult.Great; + + return result.Type != result.Judgement.MaxResult; + } + + private bool isRelevantResult(HitResult result) => result.AffectsAccuracy() || result.AffectsCombo(); } } From 88a5ba8167facffe4c7c401ca32ddf9f003bf4df Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 22 Dec 2023 16:43:09 +0900 Subject: [PATCH 2083/4092] Add mania/osu sudden death mod tests --- .../Mods/TestSceneCatchModPerfect.cs | 2 +- .../Mods/TestSceneManiaModPerfect.cs | 6 +- .../Mods/TestSceneManiaModSuddenDeath.cs | 72 +++++++++++++++++ .../Mods/TestSceneOsuModPerfect.cs | 4 +- .../Mods/TestSceneOsuModSuddenDeath.cs | 77 +++++++++++++++++++ .../Mods/TestSceneTaikoModPerfect.cs | 2 +- ...tScene.cs => ModFailConditionTestScene.cs} | 14 ++-- 7 files changed, 163 insertions(+), 14 deletions(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModSuddenDeath.cs create mode 100644 osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSuddenDeath.cs rename osu.Game/Tests/Visual/{ModPerfectTestScene.cs => ModFailConditionTestScene.cs} (74%) diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs index 45e7d7aa28..7d539f91e4 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs @@ -11,7 +11,7 @@ using osuTK; namespace osu.Game.Rulesets.Catch.Tests.Mods { - public partial class TestSceneCatchModPerfect : ModPerfectTestScene + public partial class TestSceneCatchModPerfect : ModFailConditionTestScene { protected override Ruleset CreatePlayerRuleset() => new CatchRuleset(); diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs index 73d6fe357d..51730e2b43 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs @@ -13,7 +13,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests.Mods { - public partial class TestSceneManiaModPerfect : ModPerfectTestScene + public partial class TestSceneManiaModPerfect : ModFailConditionTestScene { protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods public void TestGreatHit() => CreateModTest(new ModTestData { Mod = new ManiaModPerfect(), - PassCondition = () => ((PerfectModTestPlayer)Player).CheckFailed(false), + PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(false), Autoplay = false, Beatmap = new Beatmap { @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods public void TestBreakOnHoldNote() => CreateModTest(new ModTestData { Mod = new ManiaModPerfect(), - PassCondition = () => ((PerfectModTestPlayer)Player).CheckFailed(true) && Player.Results.Count == 2, + PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true) && Player.Results.Count == 2, Autoplay = false, Beatmap = new Beatmap { diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModSuddenDeath.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModSuddenDeath.cs new file mode 100644 index 0000000000..619816a815 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModSuddenDeath.cs @@ -0,0 +1,72 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Replays; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Replays; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Mania.Tests.Mods +{ + public partial class TestSceneManiaModSuddenDeath : ModFailConditionTestScene + { + protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); + + public TestSceneManiaModSuddenDeath() + : base(new ManiaModSuddenDeath()) + { + } + + [Test] + public void TestGreatHit() => CreateModTest(new ModTestData + { + Mod = new ManiaModSuddenDeath(), + PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(false), + Autoplay = false, + Beatmap = new Beatmap + { + HitObjects = new List + { + new Note + { + StartTime = 1000, + } + }, + }, + ReplayFrames = new List + { + new ManiaReplayFrame(1020, ManiaAction.Key1), + new ManiaReplayFrame(2000) + } + }); + + [Test] + public void TestBreakOnHoldNote() => CreateModTest(new ModTestData + { + Mod = new ManiaModSuddenDeath(), + PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true) && Player.Results.Count == 2, + Autoplay = false, + Beatmap = new Beatmap + { + HitObjects = new List + { + new HoldNote + { + StartTime = 1000, + EndTime = 3000, + }, + }, + }, + ReplayFrames = new List + { + new ManiaReplayFrame(1000, ManiaAction.Key1), + new ManiaReplayFrame(2000) + } + }); + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs index 7030061e0e..b01bbbfca1 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs @@ -15,7 +15,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Tests.Mods { - public partial class TestSceneOsuModPerfect : ModPerfectTestScene + public partial class TestSceneOsuModPerfect : ModFailConditionTestScene { protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods public void TestMissSliderTail() => CreateModTest(new ModTestData { Mod = new OsuModPerfect(), - PassCondition = () => ((PerfectModTestPlayer)Player).CheckFailed(true), + PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true), Autoplay = false, Beatmap = new Beatmap { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSuddenDeath.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSuddenDeath.cs new file mode 100644 index 0000000000..ea048aaa6e --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSuddenDeath.cs @@ -0,0 +1,77 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Replays; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests.Mods +{ + public partial class TestSceneOsuModSuddenDeath : ModFailConditionTestScene + { + protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); + + public TestSceneOsuModSuddenDeath() + : base(new OsuModSuddenDeath()) + { + } + + [Test] + public void TestMissTail() => CreateModTest(new ModTestData + { + Mod = new OsuModSuddenDeath(), + PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(false), + Autoplay = false, + Beatmap = new Beatmap + { + HitObjects = new List + { + new Slider + { + Position = new Vector2(256, 192), + StartTime = 1000, + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(100, 0), }) + }, + }, + }, + ReplayFrames = new List + { + new OsuReplayFrame(1000, new Vector2(256, 192), OsuAction.LeftButton), + new OsuReplayFrame(1001, new Vector2(256, 192)), + } + }); + + [Test] + public void TestMissTick() => CreateModTest(new ModTestData + { + Mod = new OsuModSuddenDeath(), + PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true), + Autoplay = false, + Beatmap = new Beatmap + { + HitObjects = new List + { + new Slider + { + Position = new Vector2(256, 192), + StartTime = 1000, + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(200, 0), }) + }, + }, + }, + ReplayFrames = new List + { + new OsuReplayFrame(1000, new Vector2(256, 192), OsuAction.LeftButton), + new OsuReplayFrame(1001, new Vector2(256, 192)), + } + }); + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs index aed08f33e0..8a1157a7f8 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs @@ -10,7 +10,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Taiko.Tests.Mods { - public partial class TestSceneTaikoModPerfect : ModPerfectTestScene + public partial class TestSceneTaikoModPerfect : ModFailConditionTestScene { protected override Ruleset CreatePlayerRuleset() => new TestTaikoRuleset(); diff --git a/osu.Game/Tests/Visual/ModPerfectTestScene.cs b/osu.Game/Tests/Visual/ModFailConditionTestScene.cs similarity index 74% rename from osu.Game/Tests/Visual/ModPerfectTestScene.cs rename to osu.Game/Tests/Visual/ModFailConditionTestScene.cs index c6f4a27ec4..8f0dff055d 100644 --- a/osu.Game/Tests/Visual/ModPerfectTestScene.cs +++ b/osu.Game/Tests/Visual/ModFailConditionTestScene.cs @@ -8,11 +8,11 @@ using osu.Game.Rulesets.Objects; namespace osu.Game.Tests.Visual { - public abstract partial class ModPerfectTestScene : ModTestScene + public abstract partial class ModFailConditionTestScene : ModTestScene { - private readonly ModPerfect mod; + private readonly ModFailCondition mod; - protected ModPerfectTestScene(ModPerfect mod) + protected ModFailConditionTestScene(ModFailCondition mod) { this.mod = mod; } @@ -26,14 +26,14 @@ namespace osu.Game.Tests.Visual HitObjects = { testData.HitObject } }, Autoplay = !shouldMiss, - PassCondition = () => ((PerfectModTestPlayer)Player).CheckFailed(shouldMiss && testData.FailOnMiss) + PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(shouldMiss && testData.FailOnMiss) }); - protected override TestPlayer CreateModPlayer(Ruleset ruleset) => new PerfectModTestPlayer(CurrentTestData, AllowFail); + protected override TestPlayer CreateModPlayer(Ruleset ruleset) => new ModFailConditionTestPlayer(CurrentTestData, AllowFail); - protected partial class PerfectModTestPlayer : ModTestPlayer + protected partial class ModFailConditionTestPlayer : ModTestPlayer { - public PerfectModTestPlayer(ModTestData data, bool allowFail) + public ModFailConditionTestPlayer(ModTestData data, bool allowFail) : base(data, allowFail) { } From 5703546d715687d58b832c25082386d35abadb09 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 22 Dec 2023 16:43:17 +0900 Subject: [PATCH 2084/4092] Revert change to ModSuddenDeath --- osu.Game/Rulesets/Mods/ModSuddenDeath.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs index 56420e5ff4..4e4e8662e8 100644 --- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs +++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModPerfect)).ToArray(); protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) - => (result.Judgement.MinResult.AffectsCombo() || result.Judgement.MaxResult.AffectsCombo() || result.Type.AffectsCombo()) + => result.Type.AffectsCombo() && !result.IsHit; } } From a0185508b76af5f650534d575226d27fe3fc1f21 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 22 Dec 2023 17:55:06 +0900 Subject: [PATCH 2085/4092] Add basic consideration of density for HP drain --- .../Scoring/OsuHealthProcessor.cs | 4 ++ .../Scoring/DrainingHealthProcessor.cs | 55 +++++++++++++++++-- 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs index 784d3b934d..5ae5766cda 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs @@ -3,6 +3,8 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; @@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Osu.Scoring { } + protected override int? GetDensityGroup(HitObject hitObject) => (hitObject as IHasComboInformation)?.ComboIndex; + protected override double GetHealthIncreaseFor(JudgementResult result) { switch (result.Type) diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs index a2fc23ac2e..4f6f8598fb 100644 --- a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs @@ -61,7 +61,9 @@ namespace osu.Game.Rulesets.Scoring /// protected readonly double DrainLenience; - private readonly List<(double time, double health)> healthIncreases = new List<(double, double)>(); + private readonly List healthIncreases = new List(); + private readonly Dictionary densityMultiplierByGroup = new Dictionary(); + private double gameplayEndTime; private double targetMinimumHealth; @@ -133,14 +135,33 @@ namespace osu.Game.Rulesets.Scoring { base.ApplyResultInternal(result); - if (!result.Type.IsBonus()) - healthIncreases.Add((result.HitObject.GetEndTime() + result.TimeOffset, GetHealthIncreaseFor(result))); + if (IsSimulating && !result.Type.IsBonus()) + { + healthIncreases.Add(new HealthIncrease( + result.HitObject.GetEndTime() + result.TimeOffset, + GetHealthIncreaseFor(result), + GetDensityGroup(result.HitObject))); + } } + protected override double GetHealthIncreaseFor(JudgementResult result) => base.GetHealthIncreaseFor(result) * getDensityMultiplier(GetDensityGroup(result.HitObject)); + + private double getDensityMultiplier(int? group) + { + if (group == null) + return 1; + + return densityMultiplierByGroup.TryGetValue(group.Value, out double multiplier) ? multiplier : 1; + } + + protected virtual int? GetDensityGroup(HitObject hitObject) => null; + protected override void Reset(bool storeResults) { base.Reset(storeResults); + densityMultiplierByGroup.Clear(); + if (storeResults) DrainRate = ComputeDrainRate(); @@ -152,6 +173,24 @@ namespace osu.Game.Rulesets.Scoring if (healthIncreases.Count <= 1) return 0; + // Normalise the health gain during sections with higher densities. + (int group, double avgIncrease)[] avgIncreasesByGroup = healthIncreases + .Where(i => i.Group != null) + .GroupBy(i => i.Group) + .Select(g => ((int)g.Key!, g.Sum(i => i.Amount) / (g.Max(i => i.Time) - g.Min(i => i.Time) + 1))) + .ToArray(); + + if (avgIncreasesByGroup.Length > 1) + { + double overallAverageIncrease = avgIncreasesByGroup.Average(g => g.avgIncrease); + + foreach ((int group, double avgIncrease) in avgIncreasesByGroup) + { + // Reduce the health increase for groups that return more health than average. + densityMultiplierByGroup[group] = Math.Min(1, overallAverageIncrease / avgIncrease); + } + } + int adjustment = 1; double result = 1; @@ -165,8 +204,8 @@ namespace osu.Game.Rulesets.Scoring for (int i = 0; i < healthIncreases.Count; i++) { - double currentTime = healthIncreases[i].time; - double lastTime = i > 0 ? healthIncreases[i - 1].time : DrainStartTime; + double currentTime = healthIncreases[i].Time; + double lastTime = i > 0 ? healthIncreases[i - 1].Time : DrainStartTime; while (currentBreak < Beatmap.Breaks.Count && Beatmap.Breaks[currentBreak].EndTime <= currentTime) { @@ -177,10 +216,12 @@ namespace osu.Game.Rulesets.Scoring currentBreak++; } + double multiplier = getDensityMultiplier(healthIncreases[i].Group); + // Apply health adjustments currentHealth -= (currentTime - lastTime) * result; lowestHealth = Math.Min(lowestHealth, currentHealth); - currentHealth = Math.Min(1, currentHealth + healthIncreases[i].health); + currentHealth = Math.Min(1, currentHealth + healthIncreases[i].Amount * multiplier); // Common scenario for when the drain rate is definitely too harsh if (lowestHealth < 0) @@ -198,5 +239,7 @@ namespace osu.Game.Rulesets.Scoring return result; } + + private record struct HealthIncrease(double Time, double Amount, int? Group); } } From 7e557152fb430219ab484107d592311e983094f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 22 Dec 2023 12:40:24 +0100 Subject: [PATCH 2086/4092] Fix relax mod not considering full follow area radius when automatically holding sliders Closes https://github.com/ppy/osu/issues/25947. Regressed in https://github.com/ppy/osu/pull/25776 with the changes to `DrawableSliderBall`. I would have liked to include tests, but relax mod is a bit untestable, because it disengages completely in the presence of a replay: https://github.com/ppy/osu/blob/7e09164d7084265536570ac7036d7244934b651c/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs#L49-L58 Additionally, `RulesetInputManager` disengages completely from parent inputs when there is a replay active: https://github.com/ppy/osu/blob/7e09164d7084265536570ac7036d7244934b651c/osu.Game/Rulesets/UI/RulesetInputManager.cs#L116 which means there is really no easy way to control positional input while still having relax logic work. So I'm hoping the fix could be considered obvious enough to not require test coverage. --- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 2 +- .../Objects/Drawables/SliderInputManager.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index aaa7c70a8d..40fadfb77e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Osu.Mods if (!slider.HeadCircle.IsHit) handleHitCircle(slider.HeadCircle); - requiresHold |= slider.Ball.IsHovered || h.IsHovered; + requiresHold |= slider.SliderInputManager.IsMouseInFollowArea(true); break; case DrawableSpinner spinner: diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs index 8aa982783e..95896c7c91 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void Update() { base.Update(); - updateTracking(isMouseInFollowArea(Tracking)); + updateTracking(IsMouseInFollowArea(Tracking)); } public void PostProcessHeadJudgement(DrawableSliderHead head) @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (!head.Judged || !head.Result.IsHit) return; - if (!isMouseInFollowArea(true)) + if (!IsMouseInFollowArea(true)) return; Debug.Assert(screenSpaceMousePosition != null); @@ -129,7 +129,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // If all ticks were hit so far, enable tracking the full extent. // If any ticks were missed, assume tracking would've broken at some point, and should only activate if the cursor is within the slider ball. // For the second case, this may be the last chance we have to enable tracking before other objects get judged, otherwise the same would normally happen via Update(). - updateTracking(allTicksInRange || isMouseInFollowArea(false)); + updateTracking(allTicksInRange || IsMouseInFollowArea(false)); } public void TryJudgeNestedObject(DrawableOsuHitObject nestedObject, double timeOffset) @@ -174,7 +174,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables /// Whether the mouse is currently in the follow area. /// /// Whether to test against the maximum area of the follow circle. - private bool isMouseInFollowArea(bool expanded) + public bool IsMouseInFollowArea(bool expanded) { if (screenSpaceMousePosition is not Vector2 pos) return false; From 6cb82310549a73ca2657efe65c9528da8367f1ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 22 Dec 2023 13:22:26 +0100 Subject: [PATCH 2087/4092] Add test coverage for failure case --- .../Mods/TestSceneOsuModStrictTracking.cs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModStrictTracking.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModStrictTracking.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModStrictTracking.cs new file mode 100644 index 0000000000..726b415977 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModStrictTracking.cs @@ -0,0 +1,53 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Replays; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests.Mods +{ + public partial class TestSceneOsuModStrictTracking : OsuModTestScene + { + [Test] + public void TestSliderInput() => CreateModTest(new ModTestData + { + Mod = new OsuModStrictTracking(), + Autoplay = false, + Beatmap = new Beatmap + { + HitObjects = new List + { + new Slider + { + StartTime = 1000, + Path = new SliderPath + { + ControlPoints = + { + new PathControlPoint(), + new PathControlPoint(new Vector2(0, 100)) + } + } + } + } + }, + ReplayFrames = new List + { + new OsuReplayFrame(0, new Vector2(), OsuAction.LeftButton), + new OsuReplayFrame(500, new Vector2(200, 0), OsuAction.LeftButton), + new OsuReplayFrame(501, new Vector2(200, 0)), + new OsuReplayFrame(1000, new Vector2(), OsuAction.LeftButton), + new OsuReplayFrame(1750, new Vector2(0, 100), OsuAction.LeftButton), + new OsuReplayFrame(1751, new Vector2(0, 100)), + }, + PassCondition = () => Player.ScoreProcessor.Combo.Value == 2 + }); + } +} From 30553dc7b839600145ac57bd789b7e9992c34dfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 22 Dec 2023 13:38:07 +0100 Subject: [PATCH 2088/4092] Fix strict tracking mod forcefully missing tail before slider start time Closes https://github.com/ppy/osu/issues/25816. Regressed in https://github.com/ppy/osu/pull/25748. The reason this broke is that allowing the state of `Tracking` to change before the slider's start time to support the early hit scenario causes strict tracking to consider loss of tracking before the slider's start time as an actual miss, and thus forcefully miss the tail (see test case in 6cb82310549a73ca2657efe65c9528da8367f1ab). --- osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs index c465ab8732..2c9292c58b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs @@ -36,6 +36,9 @@ namespace osu.Game.Rulesets.Osu.Mods { if (e.NewValue || slider.Judged) return; + if (slider.Time.Current < slider.HitObject.StartTime) + return; + var tail = slider.NestedHitObjects.OfType().First(); if (!tail.Judged) From 01cf4ee15a8456ccc56381f138dfdf7f210de121 Mon Sep 17 00:00:00 2001 From: Simon G <45692977+jeenyuhs@users.noreply.github.com> Date: Fri, 22 Dec 2023 18:11:37 +0100 Subject: [PATCH 2089/4092] add test for length updates --- .../SongSelect/TestSceneBeatmapInfoWedge.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index 7cd4f06bce..4e100a37dc 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; @@ -194,6 +195,36 @@ namespace osu.Game.Tests.Visual.SongSelect }); } + [TestCase] + public void TestLengthUpdates() + { + IBeatmap beatmap = createTestBeatmap(new OsuRuleset().RulesetInfo); + double drain = beatmap.CalculateDrainLength(); + beatmap.BeatmapInfo.Length = drain; + + OsuModDoubleTime doubleTime = null; + + selectBeatmap(beatmap); + checkDisplayedLength(drain); + + AddStep("select DT", () => SelectedMods.Value = new[] { doubleTime = new OsuModDoubleTime() }); + checkDisplayedLength(Math.Round(drain / 1.5f)); + + AddStep("change DT rate", () => doubleTime.SpeedChange.Value = 2); + checkDisplayedLength(Math.Round(drain / 2)); + } + + private void checkDisplayedLength(double drain) + { + var displayedLength = drain.ToFormattedDuration(); + + AddUntilStep($"check map drain ({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; From 803329c5d8aa3a73ef9a58875adb8255f241a0ec Mon Sep 17 00:00:00 2001 From: smallketchup82 <69545310+smallketchup82@users.noreply.github.com> Date: Fri, 22 Dec 2023 15:58:41 -0500 Subject: [PATCH 2090/4092] rollback my accidental global.json change --- global.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/global.json b/global.json index 7835999220..d6c2c37f77 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,6 @@ { "sdk": { - "version": "6.0.0", - "rollForward": "latestFeature", - "allowPrerelease": true + "version": "6.0.100", + "rollForward": "latestFeature" } } \ No newline at end of file From f7c1e66165b5415a3779961f852677fca073c3d5 Mon Sep 17 00:00:00 2001 From: smallketchup82 <69545310+smallketchup82@users.noreply.github.com> Date: Fri, 22 Dec 2023 17:28:02 -0500 Subject: [PATCH 2091/4092] Make the difficulty stats change based on the currently applied mods --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 10 ++- .../Drawables/DifficultyIconTooltip.cs | 61 ++++++++++++++----- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 2 +- 3 files changed, 55 insertions(+), 18 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 1665ec52fa..44981003f2 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osuTK; using osuTK.Graphics; @@ -39,6 +40,8 @@ namespace osu.Game.Beatmaps.Drawables private readonly IRulesetInfo ruleset; + private readonly Mod[]? mods; + private Drawable background = null!; private readonly Container iconContainer; @@ -58,11 +61,14 @@ namespace osu.Game.Beatmaps.Drawables /// Creates a new . Will use provided beatmap's for initial value. /// /// The beatmap to be displayed in the tooltip, and to be used for the initial star rating value. + /// The mods type beat /// An optional ruleset to be used for the icon display, in place of the beatmap's ruleset. - public DifficultyIcon(IBeatmapInfo beatmap, IRulesetInfo? ruleset = null) + public DifficultyIcon(IBeatmapInfo beatmap, IRulesetInfo? ruleset = null, Mod[]? mods = null) : this(ruleset ?? beatmap.Ruleset) { this.beatmap = beatmap; + this.mods = mods; + Current.Value = new StarDifficulty(beatmap.StarRating, 0); } @@ -128,6 +134,6 @@ namespace osu.Game.Beatmaps.Drawables GetCustomTooltip() => new DifficultyIconTooltip(); DifficultyIconTooltipContent IHasCustomTooltip. - TooltipContent => (ShowTooltip && beatmap != null ? new DifficultyIconTooltipContent(beatmap, Current) : null)!; + TooltipContent => (ShowTooltip && beatmap != null ? new DifficultyIconTooltipContent(beatmap, Current, ruleset, mods) : null)!; } } diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index 7ea2c6a0a2..0f5c94ac1d 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -12,6 +13,8 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osuTK; namespace osu.Game.Beatmaps.Drawables @@ -24,7 +27,7 @@ namespace osu.Game.Beatmaps.Drawables private OsuSpriteText drainRate; private OsuSpriteText circleSize; private OsuSpriteText approachRate; - private OsuSpriteText BPM; + private OsuSpriteText bpm; private OsuSpriteText maxCombo; private OsuSpriteText length; @@ -66,7 +69,7 @@ namespace osu.Game.Beatmaps.Drawables Origin = Anchor.Centre, }, // Difficulty stats - new FillFlowContainer() + new FillFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -81,7 +84,7 @@ namespace osu.Game.Beatmaps.Drawables Origin = Anchor.Centre, Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold), }, - overallDifficulty = new OsuSpriteText + circleSize = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -93,13 +96,13 @@ namespace osu.Game.Beatmaps.Drawables Origin = Anchor.Centre, Font = OsuFont.GetFont(size: 14), }, - circleSize = new OsuSpriteText + approachRate = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, Font = OsuFont.GetFont(size: 14), }, - approachRate = new OsuSpriteText + overallDifficulty = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -108,7 +111,7 @@ namespace osu.Game.Beatmaps.Drawables } }, // Misc stats - new FillFlowContainer() + new FillFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -129,7 +132,7 @@ namespace osu.Game.Beatmaps.Drawables Origin = Anchor.Centre, Font = OsuFont.GetFont(size: 14), }, - BPM = new OsuSpriteText + bpm = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -155,20 +158,44 @@ namespace osu.Game.Beatmaps.Drawables if (displayedContent != null) starRating.Current.UnbindFrom(displayedContent.Difficulty); - // Header row displayedContent = content; + + // Header row starRating.Current.BindTarget = displayedContent.Difficulty; difficultyName.Text = displayedContent.BeatmapInfo.DifficultyName; + double rate = 1; + + if (displayedContent.Mods != null) + { + foreach (var mod in displayedContent.Mods.OfType()) + rate = mod.ApplyToRate(0, rate); + } + + double bpmAdjusted = displayedContent.BeatmapInfo.BPM * rate; + + BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(displayedContent.BeatmapInfo.Difficulty); + + if (displayedContent.Mods != null) + { + foreach (var mod in displayedContent.Mods.OfType()) + { + mod.ApplyToDifficulty(originalDifficulty); + } + } + + Ruleset ruleset = displayedContent.Ruleset.CreateInstance(); + BeatmapDifficulty adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(originalDifficulty, rate); + // Difficulty row - overallDifficulty.Text = "OD: " + displayedContent.BeatmapInfo.Difficulty.OverallDifficulty.ToString("0.##"); - drainRate.Text = "| HP: " + displayedContent.BeatmapInfo.Difficulty.DrainRate.ToString("0.##"); - circleSize.Text = "| CS: " + displayedContent.BeatmapInfo.Difficulty.CircleSize.ToString("0.##"); - approachRate.Text = "| AR: " + displayedContent.BeatmapInfo.Difficulty.ApproachRate.ToString("0.##"); + circleSize.Text = "CS: " + adjustedDifficulty.CircleSize.ToString("0.##"); + drainRate.Text = "| HP: " + adjustedDifficulty.DrainRate.ToString("0.##"); + approachRate.Text = "| AR: " + adjustedDifficulty.ApproachRate.ToString("0.##"); + overallDifficulty.Text = "| OD: " + adjustedDifficulty.OverallDifficulty.ToString("0.##"); // Misc row - length.Text = "Length: " + TimeSpan.FromMilliseconds(displayedContent.BeatmapInfo.Length).ToString("mm\\:ss"); - BPM.Text = "| BPM: " + displayedContent.BeatmapInfo.BPM; + length.Text = "Length: " + TimeSpan.FromMilliseconds(displayedContent.BeatmapInfo.Length / rate).ToString("mm\\:ss"); + bpm.Text = "| BPM: " + bpmAdjusted; maxCombo.Text = "| Max Combo: " + displayedContent.BeatmapInfo.TotalObjectCount; } @@ -183,11 +210,15 @@ namespace osu.Game.Beatmaps.Drawables { public readonly IBeatmapInfo BeatmapInfo; public readonly IBindable Difficulty; + public readonly IRulesetInfo Ruleset; + public readonly Mod[] Mods; - public DifficultyIconTooltipContent(IBeatmapInfo beatmapInfo, IBindable difficulty) + public DifficultyIconTooltipContent(IBeatmapInfo beatmapInfo, IBindable difficulty, IRulesetInfo rulesetInfo, Mod[] mods) { BeatmapInfo = beatmapInfo; Difficulty = difficulty; + Ruleset = rulesetInfo; + Mods = mods; } } } diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 8f405399a7..eb23ed6f8f 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -283,7 +283,7 @@ namespace osu.Game.Screens.OnlinePlay } if (beatmap != null) - difficultyIconContainer.Child = new DifficultyIcon(beatmap, ruleset) { Size = new Vector2(icon_height) }; + difficultyIconContainer.Child = new DifficultyIcon(beatmap, ruleset, requiredMods) { Size = new Vector2(icon_height) }; else difficultyIconContainer.Clear(); From c5893f245ce7a89d1900dbb620390823702481fe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Dec 2023 14:03:45 +0900 Subject: [PATCH 2092/4092] Change legacy version checks to account for users specifying incorrect versions --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs | 2 +- osu.Game/Skinning/LegacyJudgementPieceOld.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs index d8d86d1802..ef616ae964 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs @@ -160,7 +160,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { decimal? legacyVersion = skin.GetConfig(SkinConfiguration.LegacySetting.Version)?.Value; - if (legacyVersion >= 2.0m) + if (legacyVersion > 1.0m) // legacy skins of version 2.0 and newer only apply very short fade out to the number piece. hitCircleText.FadeOut(legacy_fade_duration / 4); else diff --git a/osu.Game/Skinning/LegacyJudgementPieceOld.cs b/osu.Game/Skinning/LegacyJudgementPieceOld.cs index 68274ffa2d..b8fe2f8d06 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceOld.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceOld.cs @@ -67,7 +67,7 @@ namespace osu.Game.Skinning decimal? legacyVersion = skin.GetConfig(SkinConfiguration.LegacySetting.Version)?.Value; - if (legacyVersion >= 2.0m) + if (legacyVersion > 1.0m) { this.MoveTo(new Vector2(0, -5)); this.MoveToOffset(new Vector2(0, 80), fade_out_delay + fade_out_length, Easing.In); From 9f34dfa2baa7e649d43ad74a444fa32df7b172e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Dec 2023 16:25:52 +0900 Subject: [PATCH 2093/4092] Add missing using statement --- osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index 4e100a37dc..fd102da026 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -15,6 +15,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Extensions; using osu.Game.Graphics.Sprites; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; From 8349cb7bbe4f30777f73cbf7c18f071997bd185c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Dec 2023 17:03:57 +0900 Subject: [PATCH 2094/4092] Fix hard crash when attempting to change folder location during a large import Closes https://github.com/ppy/osu/issues/26067. --- osu.Game/OsuGameBase.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 2d8024a45a..48548dc1ef 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -527,14 +527,21 @@ namespace osu.Game { ManualResetEventSlim readyToRun = new ManualResetEventSlim(); + bool success = false; + Scheduler.Add(() => { - realmBlocker = realm.BlockAllOperations("migration"); + try + { + realmBlocker = realm.BlockAllOperations("migration"); + success = true; + } + catch { } readyToRun.Set(); }, false); - if (!readyToRun.Wait(30000)) + if (!readyToRun.Wait(30000) || !success) throw new TimeoutException("Attempting to block for migration took too long."); bool? cleanupSucceded = (Storage as OsuStorage)?.Migrate(Host.GetStorage(path)); From 27a9dcc5a1a375e7d0591c90880ccb09fd8c0042 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Dec 2023 19:55:05 +0900 Subject: [PATCH 2095/4092] Add basic hotkey offset adjust support (via existing offset control) --- .../Input/Bindings/GlobalActionContainer.cs | 8 ++ .../GlobalActionKeyBindingStrings.cs | 10 +++ .../PlayerSettings/BeatmapOffsetControl.cs | 73 +++++++++++++------ 3 files changed, 68 insertions(+), 23 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 947cd5f54f..5a39c02185 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -160,6 +160,8 @@ namespace osu.Game.Input.Bindings new KeyBinding(InputKey.Enter, GlobalAction.ToggleChatFocus), new KeyBinding(InputKey.F1, GlobalAction.SaveReplay), new KeyBinding(InputKey.F2, GlobalAction.ExportReplay), + new KeyBinding(InputKey.Plus, GlobalAction.IncreaseOffset), + new KeyBinding(InputKey.Minus, GlobalAction.DecreaseOffset), }; private static IEnumerable replayKeyBindings => new[] @@ -404,6 +406,12 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorToggleRotateControl))] EditorToggleRotateControl, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.IncreaseOffset))] + IncreaseOffset, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.DecreaseOffset))] + DecreaseOffset } public enum GlobalActionCategory diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index 8356c480dd..ca27d0ff95 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -344,6 +344,16 @@ namespace osu.Game.Localisation /// public static LocalisableString ExportReplay => new TranslatableString(getKey(@"export_replay"), @"Export replay"); + /// + /// "Increase offset" + /// + public static LocalisableString IncreaseOffset => new TranslatableString(getKey(@"increase_offset"), @"Increase offset"); + + /// + /// "Decrease offset" + /// + public static LocalisableString DecreaseOffset => new TranslatableString(getKey(@"decrease_offset"), @"Decrease offset"); + /// /// "Toggle rotate control" /// diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 840077eb7f..b4bb35377d 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -9,6 +9,8 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Framework.Utils; using osu.Game.Beatmaps; @@ -16,6 +18,7 @@ using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Input.Bindings; using osu.Game.Localisation; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Mods; @@ -26,7 +29,7 @@ using osuTK; namespace osu.Game.Screens.Play.PlayerSettings { - public partial class BeatmapOffsetControl : CompositeDrawable + public partial class BeatmapOffsetControl : CompositeDrawable, IKeyBindingHandler { public Bindable ReferenceScore { get; } = new Bindable(); @@ -88,28 +91,6 @@ namespace osu.Game.Screens.Play.PlayerSettings }; } - public partial class OffsetSliderBar : PlayerSliderBar - { - protected override Drawable CreateControl() => new CustomSliderBar(); - - protected partial class CustomSliderBar : SliderBar - { - public override LocalisableString TooltipText => - Current.Value == 0 - ? LocalisableString.Interpolate($@"{base.TooltipText} ms") - : LocalisableString.Interpolate($@"{base.TooltipText} ms {getEarlyLateText(Current.Value)}"); - - private LocalisableString getEarlyLateText(double value) - { - Debug.Assert(value != 0); - - return value > 0 - ? BeatmapOffsetControlStrings.HitObjectsAppearEarlier - : BeatmapOffsetControlStrings.HitObjectsAppearLater; - } - } - } - protected override void LoadComplete() { base.LoadComplete(); @@ -243,5 +224,51 @@ namespace osu.Game.Screens.Play.PlayerSettings base.Dispose(isDisposing); beatmapOffsetSubscription?.Dispose(); } + + public bool OnPressed(KeyBindingPressEvent e) + { + double amount = e.AltPressed ? 1 : 5; + + switch (e.Action) + { + case GlobalAction.IncreaseOffset: + Current.Value += amount; + + return true; + + case GlobalAction.DecreaseOffset: + Current.Value -= amount; + + return true; + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + + public partial class OffsetSliderBar : PlayerSliderBar + { + protected override Drawable CreateControl() => new CustomSliderBar(); + + protected partial class CustomSliderBar : SliderBar + { + public override LocalisableString TooltipText => + Current.Value == 0 + ? LocalisableString.Interpolate($@"{base.TooltipText} ms") + : LocalisableString.Interpolate($@"{base.TooltipText} ms {getEarlyLateText(Current.Value)}"); + + private LocalisableString getEarlyLateText(double value) + { + Debug.Assert(value != 0); + + return value > 0 + ? BeatmapOffsetControlStrings.HitObjectsAppearEarlier + : BeatmapOffsetControlStrings.HitObjectsAppearLater; + } + } + } } } From 7e9522a722fd01ca872877e1e3b4bcfeb61c8a2b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Dec 2023 20:46:12 +0900 Subject: [PATCH 2096/4092] Allow external use of offset text explanation --- .../PlayerSettings/BeatmapOffsetControl.cs | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index b4bb35377d..80549343a5 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -233,12 +233,10 @@ namespace osu.Game.Screens.Play.PlayerSettings { case GlobalAction.IncreaseOffset: Current.Value += amount; - return true; case GlobalAction.DecreaseOffset: Current.Value -= amount; - return true; } @@ -249,25 +247,29 @@ namespace osu.Game.Screens.Play.PlayerSettings { } + public static LocalisableString GetOffsetExplanatoryText(double offset) + { + return offset == 0 + ? LocalisableString.Interpolate($@"{offset:0.0} ms") + : LocalisableString.Interpolate($@"{offset:0.0} ms {getEarlyLateText(offset)}"); + + LocalisableString getEarlyLateText(double value) + { + Debug.Assert(value != 0); + + return value > 0 + ? BeatmapOffsetControlStrings.HitObjectsAppearEarlier + : BeatmapOffsetControlStrings.HitObjectsAppearLater; + } + } + public partial class OffsetSliderBar : PlayerSliderBar { protected override Drawable CreateControl() => new CustomSliderBar(); protected partial class CustomSliderBar : SliderBar { - public override LocalisableString TooltipText => - Current.Value == 0 - ? LocalisableString.Interpolate($@"{base.TooltipText} ms") - : LocalisableString.Interpolate($@"{base.TooltipText} ms {getEarlyLateText(Current.Value)}"); - - private LocalisableString getEarlyLateText(double value) - { - Debug.Assert(value != 0); - - return value > 0 - ? BeatmapOffsetControlStrings.HitObjectsAppearEarlier - : BeatmapOffsetControlStrings.HitObjectsAppearLater; - } + public override LocalisableString TooltipText => GetOffsetExplanatoryText(Current.Value); } } } From 6f11885d4bedc303859964ab1415a2f1959e9bda Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Dec 2023 20:46:22 +0900 Subject: [PATCH 2097/4092] Add control to allow changing offset from gameplay --- .../Screens/Play/GameplayOffsetControl.cs | 104 ++++++++++++++++++ osu.Game/Screens/Play/Player.cs | 6 + 2 files changed, 110 insertions(+) create mode 100644 osu.Game/Screens/Play/GameplayOffsetControl.cs diff --git a/osu.Game/Screens/Play/GameplayOffsetControl.cs b/osu.Game/Screens/Play/GameplayOffsetControl.cs new file mode 100644 index 0000000000..3f5a5bef2a --- /dev/null +++ b/osu.Game/Screens/Play/GameplayOffsetControl.cs @@ -0,0 +1,104 @@ +// 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.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Threading; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Overlays; +using osu.Game.Screens.Play.PlayerSettings; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Play +{ + /// + /// This provides the ability to change the offset while in gameplay. + /// Eventually this should be replaced with all settings from PlayerLoader being accessible from the game. + /// + internal partial class GameplayOffsetControl : VisibilityContainer + { + protected override bool StartHidden => true; + + public override bool PropagateNonPositionalInputSubTree => true; + + private BeatmapOffsetControl offsetControl = null!; + + private OsuTextFlowContainer text = null!; + + private ScheduledDelegate? hideOp; + + public GameplayOffsetControl() + { + AutoSizeAxes = Axes.Y; + Width = SettingsToolboxGroup.CONTAINER_WIDTH; + + Masking = true; + CornerRadius = 5; + + // Allow BeatmapOffsetControl to handle keyboard input. + AlwaysPresent = true; + + Anchor = Anchor.CentreRight; + Origin = Anchor.CentreRight; + + X = 100; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider? colourProvider) + { + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.8f, + Colour = colourProvider?.Background4 ?? Color4.Black, + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(10), + Spacing = new Vector2(5), + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + offsetControl = new BeatmapOffsetControl(), + text = new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(weight: FontWeight.SemiBold)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + TextAnchor = Anchor.TopCentre, + } + } + }, + }; + + offsetControl.Current.BindValueChanged(val => + { + text.Text = BeatmapOffsetControl.GetOffsetExplanatoryText(val.NewValue); + Show(); + + hideOp?.Cancel(); + hideOp = Scheduler.AddDelayed(Hide, 500); + }); + } + + protected override void PopIn() + { + this.FadeIn(500, Easing.OutQuint) + .MoveToX(0, 500, Easing.OutQuint); + } + + protected override void PopOut() + { + this.FadeOut(500, Easing.InQuint) + .MoveToX(100, 500, Easing.InQuint); + } + } +} diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index c9251b0a78..c960ac357f 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -461,6 +461,12 @@ namespace osu.Game.Screens.Play OnRetry = () => Restart(), OnQuit = () => PerformExit(true), }, + new GameplayOffsetControl + { + Margin = new MarginPadding(20), + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + } }, }; From a2e5f624789d852a23d555e12b5dd962ad6f7712 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Dec 2023 21:07:17 +0900 Subject: [PATCH 2098/4092] Fix user profile cover showing 1px line when contracted Addresses https://github.com/ppy/osu/discussions/26068. --- osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index 36bd8a5af5..c9e5068b2a 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -232,6 +232,14 @@ namespace osu.Game.Overlays.Profile.Header bool expanded = coverToggle.CoverExpanded.Value; cover.ResizeHeightTo(expanded ? 250 : 0, transition_duration, Easing.OutQuint); + + // Without this a very tiny slither of the cover will be visible even with a size of zero. + // Integer masking woes, no doubt. + if (expanded) + cover.FadeIn(transition_duration, Easing.OutQuint); + else + cover.FadeOut(transition_duration, Easing.InQuint); + avatar.ResizeTo(new Vector2(expanded ? 120 : content_height), transition_duration, Easing.OutQuint); avatar.TransformTo(nameof(avatar.CornerRadius), expanded ? 40f : 20f, transition_duration, Easing.OutQuint); flow.TransformTo(nameof(flow.Spacing), new Vector2(expanded ? 20f : 10f), transition_duration, Easing.OutQuint); From 007ea51e202d546e813725991221436066c3db74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 23 Dec 2023 13:07:29 +0100 Subject: [PATCH 2099/4092] Add extra safety against returning negative total score in conversion operation --- .../StandardisedScoreMigrationTools.cs | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index 380bf63e63..5cd2a2f29c 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -321,6 +321,8 @@ namespace osu.Game.Database double modMultiplier = score.Mods.Select(m => m.ScoreMultiplier).Aggregate(1.0, (c, n) => c * n); + long convertedTotalScore; + switch (score.Ruleset.OnlineID) { case 0: @@ -417,32 +419,42 @@ namespace osu.Game.Database double newComboScoreProportion = estimatedComboPortionInStandardisedScore / maximumAchievableComboPortionInStandardisedScore; - return (long)Math.Round(( + convertedTotalScore = (long)Math.Round(( 500000 * newComboScoreProportion * score.Accuracy + 500000 * Math.Pow(score.Accuracy, 5) + bonusProportion) * modMultiplier); + break; case 1: - return (long)Math.Round(( + convertedTotalScore = (long)Math.Round(( 250000 * comboProportion + 750000 * Math.Pow(score.Accuracy, 3.6) + bonusProportion) * modMultiplier); + break; case 2: - return (long)Math.Round(( + convertedTotalScore = (long)Math.Round(( 600000 * comboProportion + 400000 * score.Accuracy + bonusProportion) * modMultiplier); + break; case 3: - return (long)Math.Round(( + convertedTotalScore = (long)Math.Round(( 850000 * comboProportion + 150000 * Math.Pow(score.Accuracy, 2 + 2 * score.Accuracy) + bonusProportion) * modMultiplier); + break; default: - return score.TotalScore; + convertedTotalScore = score.TotalScore; + break; } + + if (convertedTotalScore < 0) + throw new InvalidOperationException($"Total score conversion operation returned invalid total of {convertedTotalScore}"); + + return convertedTotalScore; } public static double ComputeAccuracy(ScoreInfo scoreInfo) From 15a9740eb65f39dbc3e58c55ad146e3103d483a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Dec 2023 21:12:47 +0900 Subject: [PATCH 2100/4092] Change "cinema" mod to never fail Addresses https://github.com/ppy/osu/discussions/26032. --- osu.Game/Rulesets/Mods/ModCinema.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index ae661c5f25..a942bcc678 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Mods } } - public class ModCinema : ModAutoplay, IApplicableToHUD, IApplicableToPlayer + public class ModCinema : ModAutoplay, IApplicableToHUD, IApplicableToPlayer, IApplicableFailOverride { public override string Name => "Cinema"; public override string Acronym => "CN"; @@ -45,5 +45,9 @@ namespace osu.Game.Rulesets.Mods player.BreakOverlay.Hide(); } + + public bool PerformFail() => false; + + public bool RestartOnFail => false; } } From 644c9816739b2aadfcdb9b5e14d438a0cae81313 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Dec 2023 21:21:34 +0900 Subject: [PATCH 2101/4092] Fix "spectate" button not always being clickable in online users list --- osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs index fe3151398f..37ea3f9c38 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; @@ -218,6 +219,7 @@ namespace osu.Game.Overlays.Dashboard { panel.Anchor = Anchor.TopCentre; panel.Origin = Anchor.TopCentre; + panel.CanSpectate.Value = playingUsers.Contains(user.Id); }); public partial class OnlineUserPanel : CompositeDrawable, IFilterable From 3a2ed3677b75bbbf8ec1683be7ecd8eb6e0a3e5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 23 Dec 2023 13:25:20 +0100 Subject: [PATCH 2102/4092] Fix standardised score conversion failing for scores set with 0.0x mod mutliplier Closes https://github.com/ppy/osu/issues/26073. --- osu.Game/Database/StandardisedScoreMigrationTools.cs | 8 ++++++-- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index 5cd2a2f29c..66c816e796 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -312,8 +312,12 @@ namespace osu.Game.Database double legacyAccScore = maximumLegacyAccuracyScore * score.Accuracy; // We can not separate the ComboScore from the BonusScore, so we keep the bonus in the ratio. - double comboProportion = - ((double)score.LegacyTotalScore - legacyAccScore) / (maximumLegacyComboScore + maximumLegacyBonusScore); + // Note that `maximumLegacyComboScore + maximumLegacyBonusScore` can actually be 0 + // when playing a beatmap with no bonus objects, with mods that have a 0.0x multiplier on stable (relax/autopilot). + // In such cases, just assume 0. + double comboProportion = maximumLegacyComboScore + maximumLegacyBonusScore > 0 + ? ((double)score.LegacyTotalScore - legacyAccScore) / (maximumLegacyComboScore + maximumLegacyBonusScore) + : 0; // We assume the bonus proportion only makes up the rest of the score that exceeds maximumLegacyBaseScore. long maximumLegacyBaseScore = maximumLegacyAccuracyScore + maximumLegacyComboScore; diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index dbd9a106df..cf0a7bd54f 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -35,9 +35,10 @@ namespace osu.Game.Scoring.Legacy /// 30000006: Fix edge cases in conversion after combo exponent introduction that lead to NaNs. Reconvert all scores. /// 30000007: Adjust osu!mania combo and accuracy portions and judgement scoring values. Reconvert all scores. /// 30000008: Add accuracy conversion. Reconvert all scores. + /// 30000009: Fix edge cases in conversion for scores which have 0.0x mod multiplier on stable. Reconvert all scores. /// /// - public const int LATEST_VERSION = 30000008; + public const int LATEST_VERSION = 30000009; /// /// The first stable-compatible YYYYMMDD format version given to lazer usage of replays. From 32d8ee2d0c30b4922ac96bb172959ea375cd2e0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 23 Dec 2023 13:42:19 +0100 Subject: [PATCH 2103/4092] Add test coverage --- .../Online/TestSceneCurrentlyOnlineDisplay.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneCurrentlyOnlineDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyOnlineDisplay.cs index 7687cd195d..b696c5d8ca 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCurrentlyOnlineDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyOnlineDisplay.cs @@ -81,6 +81,21 @@ namespace osu.Game.Tests.Visual.Online AddStep("End watching user presence", () => metadataClient.EndWatchingUserPresence()); } + [Test] + public void TestUserWasPlayingBeforeWatchingUserPresence() + { + AddStep("User began playing", () => spectatorClient.SendStartPlay(streamingUser.Id, 0)); + AddStep("Begin watching user presence", () => metadataClient.BeginWatchingUserPresence()); + AddStep("Add online user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.ChoosingBeatmap() })); + AddUntilStep("Panel loaded", () => currentlyOnline.ChildrenOfType().FirstOrDefault()?.User.Id == 2); + AddAssert("Spectate button enabled", () => currentlyOnline.ChildrenOfType().First().Enabled.Value, () => Is.True); + + AddStep("User finished playing", () => spectatorClient.SendEndPlay(streamingUser.Id)); + AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType().First().Enabled.Value, () => Is.False); + AddStep("Remove playing user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, null)); + AddStep("End watching user presence", () => metadataClient.EndWatchingUserPresence()); + } + internal partial class TestUserLookupCache : UserLookupCache { private static readonly string[] usernames = From 0cbf594a8c151e3699ca15431e29d3dac96f2016 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 23 Dec 2023 15:10:31 +0100 Subject: [PATCH 2104/4092] Make cinema mod incompatible with no fail --- osu.Game/Rulesets/Mods/ModCinema.cs | 2 +- osu.Game/Rulesets/Mods/ModNoFail.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index a942bcc678..dbb37e0af6 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => OsuIcon.ModCinema; public override LocalisableString Description => "Watch the video without visual distractions."; - public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModAutoplay)).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(ModAutoplay), typeof(ModNoFail) }).ToArray(); public void ApplyToHUD(HUDOverlay overlay) { diff --git a/osu.Game/Rulesets/Mods/ModNoFail.cs b/osu.Game/Rulesets/Mods/ModNoFail.cs index 0cf81bf4c9..cc451772b2 100644 --- a/osu.Game/Rulesets/Mods/ModNoFail.cs +++ b/osu.Game/Rulesets/Mods/ModNoFail.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.DifficultyReduction; public override LocalisableString Description => "You can't fail, no matter what."; public override double ScoreMultiplier => 0.5; - public override Type[] IncompatibleMods => new[] { typeof(ModFailCondition) }; + public override Type[] IncompatibleMods => new[] { typeof(ModFailCondition), typeof(ModCinema) }; private readonly Bindable showHealthBar = new Bindable(); From d1000b2e6c527f170e9fd1dd525d2ba296c31f17 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 23 Dec 2023 23:36:15 +0900 Subject: [PATCH 2105/4092] remove HP density --- .../Scoring/OsuHealthProcessor.cs | 2 - .../Scoring/DrainingHealthProcessor.cs | 42 ++----------------- 2 files changed, 3 insertions(+), 41 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs index 5ae5766cda..7463bb565c 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs @@ -17,8 +17,6 @@ namespace osu.Game.Rulesets.Osu.Scoring { } - protected override int? GetDensityGroup(HitObject hitObject) => (hitObject as IHasComboInformation)?.ComboIndex; - protected override double GetHealthIncreaseFor(JudgementResult result) { switch (result.Type) diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs index 4f6f8598fb..629a84ea62 100644 --- a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs @@ -62,7 +62,6 @@ namespace osu.Game.Rulesets.Scoring protected readonly double DrainLenience; private readonly List healthIncreases = new List(); - private readonly Dictionary densityMultiplierByGroup = new Dictionary(); private double gameplayEndTime; private double targetMinimumHealth; @@ -139,29 +138,14 @@ namespace osu.Game.Rulesets.Scoring { healthIncreases.Add(new HealthIncrease( result.HitObject.GetEndTime() + result.TimeOffset, - GetHealthIncreaseFor(result), - GetDensityGroup(result.HitObject))); + GetHealthIncreaseFor(result))); } } - protected override double GetHealthIncreaseFor(JudgementResult result) => base.GetHealthIncreaseFor(result) * getDensityMultiplier(GetDensityGroup(result.HitObject)); - - private double getDensityMultiplier(int? group) - { - if (group == null) - return 1; - - return densityMultiplierByGroup.TryGetValue(group.Value, out double multiplier) ? multiplier : 1; - } - - protected virtual int? GetDensityGroup(HitObject hitObject) => null; - protected override void Reset(bool storeResults) { base.Reset(storeResults); - densityMultiplierByGroup.Clear(); - if (storeResults) DrainRate = ComputeDrainRate(); @@ -173,24 +157,6 @@ namespace osu.Game.Rulesets.Scoring if (healthIncreases.Count <= 1) return 0; - // Normalise the health gain during sections with higher densities. - (int group, double avgIncrease)[] avgIncreasesByGroup = healthIncreases - .Where(i => i.Group != null) - .GroupBy(i => i.Group) - .Select(g => ((int)g.Key!, g.Sum(i => i.Amount) / (g.Max(i => i.Time) - g.Min(i => i.Time) + 1))) - .ToArray(); - - if (avgIncreasesByGroup.Length > 1) - { - double overallAverageIncrease = avgIncreasesByGroup.Average(g => g.avgIncrease); - - foreach ((int group, double avgIncrease) in avgIncreasesByGroup) - { - // Reduce the health increase for groups that return more health than average. - densityMultiplierByGroup[group] = Math.Min(1, overallAverageIncrease / avgIncrease); - } - } - int adjustment = 1; double result = 1; @@ -216,12 +182,10 @@ namespace osu.Game.Rulesets.Scoring currentBreak++; } - double multiplier = getDensityMultiplier(healthIncreases[i].Group); - // Apply health adjustments currentHealth -= (currentTime - lastTime) * result; lowestHealth = Math.Min(lowestHealth, currentHealth); - currentHealth = Math.Min(1, currentHealth + healthIncreases[i].Amount * multiplier); + currentHealth = Math.Min(1, currentHealth + healthIncreases[i].Amount); // Common scenario for when the drain rate is definitely too harsh if (lowestHealth < 0) @@ -240,6 +204,6 @@ namespace osu.Game.Rulesets.Scoring return result; } - private record struct HealthIncrease(double Time, double Amount, int? Group); + private record struct HealthIncrease(double Time, double Amount); } } From 00090bc527989b6caa094a40caf67023aeb352b3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 23 Dec 2023 23:36:24 +0900 Subject: [PATCH 2106/4092] Add combo end bonus to HP --- .../Scoring/OsuHealthProcessor.cs | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs index 7463bb565c..672a8c91ba 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.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 System; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; @@ -12,12 +14,68 @@ namespace osu.Game.Rulesets.Osu.Scoring { public partial class OsuHealthProcessor : DrainingHealthProcessor { + private ComboResult currentComboResult = ComboResult.Perfect; + public OsuHealthProcessor(double drainStartTime, double drainLenience = 0) : base(drainStartTime, drainLenience) { } protected override double GetHealthIncreaseFor(JudgementResult result) + { + if (IsSimulating) + return getHealthIncreaseFor(result); + + if (result.HitObject is not IHasComboInformation combo) + return getHealthIncreaseFor(result); + + if (combo.NewCombo) + currentComboResult = ComboResult.Perfect; + + switch (result.Type) + { + case HitResult.LargeTickMiss: + case HitResult.Ok: + setComboResult(ComboResult.Good); + break; + + case HitResult.Meh: + case HitResult.Miss: + setComboResult(ComboResult.None); + break; + } + + // The tail has a special IgnoreMiss judgement + if (result.HitObject is SliderTailCircle && !result.IsHit) + setComboResult(ComboResult.Good); + + if (combo.LastInCombo && result.Type.IsHit()) + { + switch (currentComboResult) + { + case ComboResult.Perfect: + return getHealthIncreaseFor(result) + 0.07; + + case ComboResult.Good: + return getHealthIncreaseFor(result) + 0.05; + + default: + return getHealthIncreaseFor(result) + 0.03; + } + } + + return getHealthIncreaseFor(result); + + void setComboResult(ComboResult comboResult) => currentComboResult = (ComboResult)Math.Min((int)currentComboResult, (int)comboResult); + } + + protected override void Reset(bool storeResults) + { + base.Reset(storeResults); + currentComboResult = ComboResult.Perfect; + } + + private double getHealthIncreaseFor(JudgementResult result) { switch (result.Type) { From 8b11bcc6ea617bbb689b793443a90b977ae2d5e9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 24 Dec 2023 00:08:15 +0900 Subject: [PATCH 2107/4092] Remove unused using --- osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs index 672a8c91ba..bd6281a7d3 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs @@ -4,7 +4,6 @@ using System; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; From 7437d21f498cca28084c7ed70341e714392e0690 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 24 Dec 2023 00:45:22 +0900 Subject: [PATCH 2108/4092] Adjust comment regarding slider tail --- osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs index bd6281a7d3..2eb257b3e6 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Scoring break; } - // The tail has a special IgnoreMiss judgement + // The slider tail has a special judgement that can't accurately be described above. if (result.HitObject is SliderTailCircle && !result.IsHit) setComboResult(ComboResult.Good); From 92b490f2e79d850980fa9e681f7c62c1ba1d5692 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 24 Dec 2023 01:59:48 +0900 Subject: [PATCH 2109/4092] Don't bother with alt support for now --- osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 80549343a5..80003aeaba 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -227,7 +227,10 @@ namespace osu.Game.Screens.Play.PlayerSettings public bool OnPressed(KeyBindingPressEvent e) { - double amount = e.AltPressed ? 1 : 5; + // To match stable, this should adjust by 5 ms, or 1 ms when holding alt. + // But that is hard to make work with global actions due to the operating mode. + // Let's use the more precise as a default for now. + const double amount = 1; switch (e.Action) { From 72bec527fd06714bd0b88f56ff1d7538bbf37710 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 24 Dec 2023 02:36:27 +0900 Subject: [PATCH 2110/4092] Add conditions to match stable offset adjust limitations --- .../PlayerSettings/BeatmapOffsetControl.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 80003aeaba..b0e7d08699 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -51,6 +51,12 @@ namespace osu.Game.Screens.Play.PlayerSettings [Resolved] private OsuColour colours { get; set; } = null!; + [Resolved] + private Player? player { get; set; } + + [Resolved] + private IGameplayClock? gameplayClock { get; set; } + private double lastPlayAverage; private double lastPlayBeatmapOffset; private HitEventTimingDistributionGraph? lastPlayGraph; @@ -227,6 +233,18 @@ namespace osu.Game.Screens.Play.PlayerSettings public bool OnPressed(KeyBindingPressEvent e) { + // General limitations to ensure players don't do anything too weird. + // These match stable for now. + if (player is SubmittingPlayer) + { + // TODO: the blocking conditions should probably display a message. + if (player?.IsBreakTime.Value == false && gameplayClock?.CurrentTime - gameplayClock?.StartTime > 10000) + return false; + + if (gameplayClock?.IsPaused.Value == true) + return false; + } + // To match stable, this should adjust by 5 ms, or 1 ms when holding alt. // But that is hard to make work with global actions due to the operating mode. // Let's use the more precise as a default for now. From 686b2a4394ac735ef309976910fed859f3f3b779 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 24 Dec 2023 03:00:51 +0900 Subject: [PATCH 2111/4092] Disable positional interaction for now --- osu.Game/Screens/Play/GameplayOffsetControl.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Play/GameplayOffsetControl.cs b/osu.Game/Screens/Play/GameplayOffsetControl.cs index 3f5a5bef2a..2f0cb821ec 100644 --- a/osu.Game/Screens/Play/GameplayOffsetControl.cs +++ b/osu.Game/Screens/Play/GameplayOffsetControl.cs @@ -25,6 +25,9 @@ namespace osu.Game.Screens.Play public override bool PropagateNonPositionalInputSubTree => true; + // Disable interaction for now to avoid any funny business with slider bar dragging. + public override bool PropagatePositionalInputSubTree => false; + private BeatmapOffsetControl offsetControl = null!; private OsuTextFlowContainer text = null!; From 5b03dc8d0bb1d189dc68103971fccf41bfc2a008 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 24 Dec 2023 03:20:42 +0900 Subject: [PATCH 2112/4092] Use a realm subscription to avoid overhead when hovering a toolbar button Addresses https://github.com/ppy/osu/discussions/26018. --- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 26 +++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 08bcb6bd8a..344f7b2018 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -157,6 +157,15 @@ namespace osu.Game.Overlays.Toolbar }; } + [BackgroundDependencyLoader] + private void load() + { + if (Hotkey != null) + { + realm.SubscribeToPropertyChanged(r => r.All().FirstOrDefault(rkb => rkb.RulesetName == null && rkb.ActionInt == (int)Hotkey.Value), kb => kb.KeyCombinationString, updateKeyBindingTooltip); + } + } + protected override bool OnMouseDown(MouseDownEvent e) => false; protected override bool OnClick(ClickEvent e) @@ -168,8 +177,6 @@ namespace osu.Game.Overlays.Toolbar protected override bool OnHover(HoverEvent e) { - updateKeyBindingTooltip(); - HoverBackground.FadeIn(200); tooltipContainer.FadeIn(100); @@ -197,19 +204,12 @@ namespace osu.Game.Overlays.Toolbar { } - private void updateKeyBindingTooltip() + private void updateKeyBindingTooltip(string keyCombination) { - if (Hotkey == null) return; + string keyBindingString = keyCombinationProvider.GetReadableString(keyCombination); - var realmKeyBinding = realm.Realm.All().FirstOrDefault(rkb => rkb.RulesetName == null && rkb.ActionInt == (int)Hotkey.Value); - - if (realmKeyBinding != null) - { - string keyBindingString = keyCombinationProvider.GetReadableString(realmKeyBinding.KeyCombination); - - if (!string.IsNullOrEmpty(keyBindingString)) - keyBindingTooltip.Text = $" ({keyBindingString})"; - } + if (!string.IsNullOrEmpty(keyBindingString)) + keyBindingTooltip.Text = $" ({keyBindingString})"; } } From 68430d6ecdbf7810285a818e4f762c07477f6de6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 23 Dec 2023 19:39:17 +0100 Subject: [PATCH 2113/4092] Fix toolbar keybinding hint not clearing after unbinding the keybinding --- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 344f7b2018..81d2de5acb 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -208,8 +208,9 @@ namespace osu.Game.Overlays.Toolbar { string keyBindingString = keyCombinationProvider.GetReadableString(keyCombination); - if (!string.IsNullOrEmpty(keyBindingString)) - keyBindingTooltip.Text = $" ({keyBindingString})"; + keyBindingTooltip.Text = !string.IsNullOrEmpty(keyBindingString) + ? $" ({keyBindingString})" + : string.Empty; } } From 19d02364185b72e97c157b42f1d668c2fa794dd8 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 23 Dec 2023 22:11:00 +0300 Subject: [PATCH 2114/4092] Change mod acronym --- osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs index f71acf95b8..b70d607ca1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModDepth : ModWithVisibilityAdjustment, IUpdatableByPlayfield, IApplicableToDrawableRuleset { public override string Name => "Depth"; - public override string Acronym => "DH"; + public override string Acronym => "DP"; public override IconUsage? Icon => FontAwesome.Solid.Cube; public override ModType Type => ModType.Fun; public override LocalisableString Description => "3D. Almost."; From b1d994b6ffb617f12a09aca20cf0482a3b5db2b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Dec 2023 17:17:23 +0900 Subject: [PATCH 2115/4092] Add classic skin sprites for slider tick and slider end misses --- osu.Game/Skinning/LegacyJudgementPieceOld.cs | 16 +++++++++++++--- osu.Game/Skinning/LegacySkin.cs | 13 ++++++++++--- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/osu.Game/Skinning/LegacyJudgementPieceOld.cs b/osu.Game/Skinning/LegacyJudgementPieceOld.cs index b8fe2f8d06..1834a17279 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceOld.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceOld.cs @@ -58,14 +58,24 @@ namespace osu.Game.Skinning if (result.IsMiss()) { + decimal? legacyVersion = skin.GetConfig(SkinConfiguration.LegacySetting.Version)?.Value; + + // missed ticks / slider end don't get the normal animation. if (isMissedTick()) - applyMissedTickScaling(); - else { this.ScaleTo(1.6f); this.ScaleTo(1, 100, Easing.In); - decimal? legacyVersion = skin.GetConfig(SkinConfiguration.LegacySetting.Version)?.Value; + if (legacyVersion > 1.0m) + { + this.MoveTo(new Vector2(0, -2f)); + this.MoveToOffset(new Vector2(0, 10), fade_out_delay + fade_out_length, Easing.In); + } + } + else + { + this.ScaleTo(1.6f); + this.ScaleTo(1, 100, Easing.In); if (legacyVersion > 1.0m) { diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index a37a386889..270abe2849 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -453,11 +453,18 @@ namespace osu.Game.Skinning private Drawable? getJudgementAnimation(HitResult result) { - if (result.IsMiss()) - return this.GetAnimation("hit0", true, false); - switch (result) { + case HitResult.Miss: + return this.GetAnimation("hit0", true, false); + + case HitResult.LargeTickMiss: + return this.GetAnimation("slidertickmiss", true, false); + + case HitResult.ComboBreak: + case HitResult.IgnoreMiss: + return this.GetAnimation("sliderendmiss", true, false); + case HitResult.Meh: return this.GetAnimation("hit50", true, false); From 02c771f540489eedaf56719632f500981b20da6a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Dec 2023 17:27:15 +0900 Subject: [PATCH 2116/4092] Add warning for linux users about to report a non-bug as a bug --- .github/ISSUE_TEMPLATE/bug-issue.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug-issue.yml b/.github/ISSUE_TEMPLATE/bug-issue.yml index 00a873f9c8..dfdcf8d320 100644 --- a/.github/ISSUE_TEMPLATE/bug-issue.yml +++ b/.github/ISSUE_TEMPLATE/bug-issue.yml @@ -11,6 +11,10 @@ body: - Current open `priority:0` issues, filterable [here](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Apriority%3A0). - And most importantly, search for your issue both in the [issue listing](https://github.com/ppy/osu/issues) and the [Q&A discussion listing](https://github.com/ppy/osu/discussions/categories/q-a). If you find that it already exists, respond with a reaction or add any further information that may be helpful. + # ATTENTION LINUX USERS + + If you are having an issue and it is hardware related, **please open a [q&a discussion](https://github.com/ppy/osu/discussions/categories/q-a)** instead of an issue. There's a high chance your issue is due to your system configuration, and not our software. + - type: dropdown attributes: label: Type From 8e6ea2dd9b098aa41f32d54a3c953218aee459fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Dec 2023 17:32:30 +0900 Subject: [PATCH 2117/4092] Update argon and triangles to match display style --- .../Skinning/Argon/ArgonJudgementPiece.cs | 11 ++++++++++- .../Rulesets/Judgements/DefaultJudgementPiece.cs | 15 ++++++++++++++- osu.Game/Rulesets/Scoring/HitResult.cs | 4 ++-- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs index edeece0293..bb61bd37c1 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs @@ -62,7 +62,16 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon /// public virtual void PlayAnimation() { - if (Result.IsMiss()) + if (Result == HitResult.IgnoreMiss || Result == HitResult.LargeTickMiss) + { + this.RotateTo(-45); + this.ScaleTo(1.8f); + this.ScaleTo(1.2f, 100, Easing.In); + + this.MoveTo(Vector2.Zero); + this.MoveToOffset(new Vector2(0, 10), 800, Easing.InQuint); + } + else if (Result.IsMiss()) { this.ScaleTo(1.6f); this.ScaleTo(1, 100, Easing.In); diff --git a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs index 3c5e37f91c..ada651b60e 100644 --- a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs +++ b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs @@ -38,7 +38,20 @@ namespace osu.Game.Rulesets.Judgements /// public virtual void PlayAnimation() { - if (Result != HitResult.None && !Result.IsHit()) + // TODO: make these better. currently they are using a text `-` and it's not centered properly. + // Should be an explicit drawable. + // + // When this is done, remove the [Description] attributes from HitResults which were added for this purpose. + if (Result == HitResult.IgnoreMiss || Result == HitResult.LargeTickMiss) + { + this.RotateTo(-45); + this.ScaleTo(1.8f); + this.ScaleTo(1.2f, 100, Easing.In); + + this.MoveTo(Vector2.Zero); + this.MoveToOffset(new Vector2(0, 10), 800, Easing.InQuint); + } + else if (Result.IsMiss()) { this.ScaleTo(1.6f); this.ScaleTo(1, 100, Easing.In); diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index e174ebd00f..2d55f1a649 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Scoring /// Indicates a large tick miss. /// [EnumMember(Value = "large_tick_miss")] - [Description(@"x")] + [Description("-")] [Order(10)] LargeTickMiss, @@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Scoring /// Indicates a miss that should be ignored for scoring purposes. /// [EnumMember(Value = "ignore_miss")] - [Description("x")] + [Description("-")] [Order(13)] IgnoreMiss, From 8142a7cb7e82d7053004cd6e4b2f62e74254ed57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 25 Dec 2023 14:00:32 +0100 Subject: [PATCH 2118/4092] Remove incorrect spec --- osu.Game/Skinning/LegacySkin.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 270abe2849..8f0cd59b68 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -461,7 +461,6 @@ namespace osu.Game.Skinning case HitResult.LargeTickMiss: return this.GetAnimation("slidertickmiss", true, false); - case HitResult.ComboBreak: case HitResult.IgnoreMiss: return this.GetAnimation("sliderendmiss", true, false); From 4fa35d709cb163a1a93bc17c6772f2b034614e91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 25 Dec 2023 14:56:40 +0100 Subject: [PATCH 2119/4092] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 2ec954b3a5..fbcdb00cdb 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + From f84b07e71a3298a5093eab2806a74c0ef2066679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 25 Dec 2023 19:01:06 +0100 Subject: [PATCH 2120/4092] Do not attempt to stop preview tracks when arriving from a "track completed" sync This fixes an issue identified with the WASAPI implementation in https://github.com/ppy/osu-framework/pull/6088. It has no real effect on current `master`, but fixes a deadlock that occurs with the aforementioned framework branch when one lets a preview track play out to the end - at this point all audio will stop and an attempt to perform any synchronous BASS operation (playing another track, seeking) will result in a deadlock. It isn't terribly clear as to why this is happening precisely, but there does not appear to be any need to stop and seek at that point, so this feels like a decent workaround even if the actual issue is upstream (and will unblock pushing out WASAPI support to users). --- osu.Game/Audio/PreviewTrack.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Audio/PreviewTrack.cs b/osu.Game/Audio/PreviewTrack.cs index d625566ee7..6184ff85dd 100644 --- a/osu.Game/Audio/PreviewTrack.cs +++ b/osu.Game/Audio/PreviewTrack.cs @@ -96,10 +96,13 @@ namespace osu.Game.Audio hasStarted = false; - Track.Stop(); + if (!Track.HasCompleted) + { + Track.Stop(); - // Ensure the track is reset immediately on stopping, so the next time it is started it has a correct time value. - Track.Seek(0); + // Ensure the track is reset immediately on stopping, so the next time it is started it has a correct time value. + Track.Seek(0); + } Stopped?.Invoke(); } From 060bf8beff4f58060e9d60f41dce5850a20748ba Mon Sep 17 00:00:00 2001 From: Nathan Tran Date: Mon, 25 Dec 2023 15:09:39 -0800 Subject: [PATCH 2121/4092] Fix rewind backtracking --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 370b559897..89911c9a69 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -643,7 +643,7 @@ namespace osu.Game.Screens.Select while (randomSelectedBeatmaps.Any()) { var beatmap = randomSelectedBeatmaps[^1]; - randomSelectedBeatmaps.Remove(beatmap); + randomSelectedBeatmaps.RemoveAt(randomSelectedBeatmaps.Count - 1); if (!beatmap.Filtered.Value && beatmap.BeatmapInfo.BeatmapSet?.DeletePending != true) { From b18b5b99773bffbd5cea8f2b536db763ae983f80 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Dec 2023 12:06:56 +0900 Subject: [PATCH 2122/4092] Add inline note about deadlock --- osu.Game/Audio/PreviewTrack.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Audio/PreviewTrack.cs b/osu.Game/Audio/PreviewTrack.cs index 6184ff85dd..961990a1bd 100644 --- a/osu.Game/Audio/PreviewTrack.cs +++ b/osu.Game/Audio/PreviewTrack.cs @@ -96,6 +96,7 @@ namespace osu.Game.Audio hasStarted = false; + // This pre-check is important, fixes a BASS deadlock in some scenarios. if (!Track.HasCompleted) { Track.Stop(); From 2ec6aa7fbb92aa286f1281c4043274f6d007e98b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 26 Dec 2023 12:46:21 +0900 Subject: [PATCH 2123/4092] Make mania scroll speed independent of hit position --- .../UI/DrawableManiaRuleset.cs | 20 ++++++++++++++++++- .../Skinning/LegacyManiaSkinConfiguration.cs | 4 +++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 9169599798..bea536e4af 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -19,12 +19,14 @@ using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Replays; +using osu.Game.Rulesets.Mania.Skinning; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Scoring; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Mania.UI { @@ -57,6 +59,9 @@ namespace osu.Game.Rulesets.Mania.UI // Stores the current speed adjustment active in gameplay. private readonly Track speedAdjustmentTrack = new TrackVirtual(0); + [Resolved] + private ISkinSource skin { get; set; } + public DrawableManiaRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) : base(ruleset, beatmap, mods) { @@ -104,7 +109,20 @@ namespace osu.Game.Rulesets.Mania.UI updateTimeRange(); } - private void updateTimeRange() => TimeRange.Value = smoothTimeRange * speedAdjustmentTrack.AggregateTempo.Value * speedAdjustmentTrack.AggregateFrequency.Value; + private void updateTimeRange() + { + float hitPosition = skin.GetConfig( + new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.HitPosition))?.Value + ?? Stage.HIT_TARGET_POSITION; + + const float length_to_default_hit_position = 768 - LegacyManiaSkinConfiguration.DEFAULT_HIT_POSITION; + float lengthToHitPosition = 768 - hitPosition; + + // This scaling factor preserves the scroll speed as the scroll length varies from changes to the hit position. + float scale = lengthToHitPosition / length_to_default_hit_position; + + TimeRange.Value = smoothTimeRange * speedAdjustmentTrack.AggregateTempo.Value * speedAdjustmentTrack.AggregateFrequency.Value * scale; + } /// /// Computes a scroll time (in milliseconds) from a scroll speed in the range of 1-40. diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs index 9acb29a793..042836984a 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs @@ -21,6 +21,8 @@ namespace osu.Game.Skinning /// public const float DEFAULT_COLUMN_SIZE = 30 * POSITION_SCALE_FACTOR; + public const float DEFAULT_HIT_POSITION = (480 - 402) * POSITION_SCALE_FACTOR; + public readonly int Keys; public Dictionary CustomColours { get; } = new Dictionary(); @@ -35,7 +37,7 @@ namespace osu.Game.Skinning public readonly float[] ExplosionWidth; public readonly float[] HoldNoteLightWidth; - public float HitPosition = (480 - 402) * POSITION_SCALE_FACTOR; + public float HitPosition = DEFAULT_HIT_POSITION; public float LightPosition = (480 - 413) * POSITION_SCALE_FACTOR; public float ScorePosition = 300 * POSITION_SCALE_FACTOR; public bool ShowJudgementLine = true; From f9e47242db508d596fe92afef5e15ab5f6583c1a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Dec 2023 17:44:49 +0900 Subject: [PATCH 2124/4092] Add visual offset to better align editor waveforms with expectations --- .../Edit/Compose/Components/Timeline/Timeline.cs | 7 ++++++- osu.Game/Screens/Edit/Editor.cs | 13 +++++++++++++ .../Edit/Timing/WaveformComparisonDisplay.cs | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 75de15fe56..83d34ab61a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -141,7 +141,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline waveformOpacity = config.GetBindable(OsuSetting.EditorWaveformOpacity); track.BindTo(editorClock.Track); - track.BindValueChanged(_ => waveform.Waveform = beatmap.Value.Waveform, true); + track.BindValueChanged(_ => + { + waveform.Waveform = beatmap.Value.Waveform; + waveform.RelativePositionAxes = Axes.X; + waveform.X = -(float)(Editor.WAVEFORM_VISUAL_OFFSET / beatmap.Value.Track.Length); + }, true); Zoom = (float)(defaultTimelineZoom * editorBeatmap.BeatmapInfo.TimelineZoom); } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index a6c18bdf0e..c1f6c02301 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -60,6 +60,19 @@ namespace osu.Game.Screens.Edit [Cached] public partial class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler, IKeyBindingHandler, IBeatSnapProvider, ISamplePlaybackDisabler, IBeatSyncProvider { + /// + /// An offset applied to waveform visuals to align them with expectations. + /// + /// + /// Historically, osu! beatmaps have an assumption of full system latency baked in. + /// This comes from a culmination of stable's platform offset, average hardware playback + /// latency, and users having their universal offsets tweaked to previous beatmaps. + /// + /// Coming to this value involved running various tests with existing users / beatmaps. + /// This included both visual and audible comparisons. Ballpark confidence is ≈2 ms. + /// + public const float WAVEFORM_VISUAL_OFFSET = 20; + public override float BackgroundParallaxAmount => 0.1f; public override bool AllowBackButton => false; diff --git a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs index 856bc7c303..b5315feccb 100644 --- a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs @@ -219,7 +219,7 @@ namespace osu.Game.Screens.Edit.Timing // offset to the required beat index. double time = selectedGroupStartTime + index * timingPoint.BeatLength; - float offset = (float)(time - visible_width / 2) / trackLength * scale; + float offset = (float)(time - visible_width / 2 + Editor.WAVEFORM_VISUAL_OFFSET) / trackLength * scale; row.Alpha = time < selectedGroupStartTime || time > selectedGroupEndTime ? 0.2f : 1; row.WaveformOffsetTo(-offset, animated); From 4e3bdb2b56bd0d8430ffeeb988498d42412d72ee Mon Sep 17 00:00:00 2001 From: Nathan Tran Date: Tue, 26 Dec 2023 00:57:06 -0800 Subject: [PATCH 2125/4092] Add test coverage --- .../SongSelect/TestSceneBeatmapCarousel.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index c509d40e07..41ea347ef3 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -454,6 +454,23 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false)); } + [Test] + public void TestRewind() + { + const int local_set_count = 3; + const int random_select_count = local_set_count * 3; + loadBeatmaps(setCount: local_set_count); + + for (int i = 0; i < random_select_count; i++) + nextRandom(); + + for (int i = 0; i < random_select_count; i++) + { + prevRandom(); + AddAssert("correct random last selected", () => selectedSets.Peek() == carousel.SelectedBeatmapSet); + } + } + [Test] public void TestRewindToDeletedBeatmap() { From 225528d519cf65420cf54055ff0602edc3c211d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Dec 2023 19:20:41 +0900 Subject: [PATCH 2126/4092] Bail from score submission if audio playback rate is too far from reality Closes https://github.com/ppy/osu/issues/23149. --- .../Play/MasterGameplayClockContainer.cs | 55 +++++++++++++++++++ osu.Game/Screens/Play/SubmittingPlayer.cs | 8 +++ 2 files changed, 63 insertions(+) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 54ed7ba626..2844d84f31 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -8,6 +8,7 @@ using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Logging; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -39,6 +40,14 @@ namespace osu.Game.Screens.Play Precision = 0.1, }; + /// + /// Whether the audio playback is within acceptable ranges. + /// Will become false if audio playback is not going as expected. + /// + public IBindable PlaybackRateValid => playbackRateValid; + + private readonly Bindable playbackRateValid = new Bindable(true); + private readonly WorkingBeatmap beatmap; private Track track; @@ -128,6 +137,7 @@ namespace osu.Game.Screens.Play { // Safety in case the clock is seeked while stopped. LastStopTime = null; + elapsedValidationTime = null; base.Seek(time); } @@ -197,6 +207,51 @@ namespace osu.Game.Screens.Play addAdjustmentsToTrack(); } + protected override void Update() + { + base.Update(); + checkPlaybackValidity(); + } + + #region Clock validation (ensure things are running correctly for local gameplay) + + private double elapsedGameplayClockTime; + private double? elapsedValidationTime; + private int playbackDiscrepancyCount; + + private const int allowed_playback_discrepancies = 5; + + private void checkPlaybackValidity() + { + if (GameplayClock.IsRunning) + { + elapsedGameplayClockTime += GameplayClock.ElapsedFrameTime; + + elapsedValidationTime ??= elapsedGameplayClockTime; + elapsedValidationTime += GameplayClock.Rate * Time.Elapsed; + + if (Math.Abs(elapsedGameplayClockTime - elapsedValidationTime!.Value) > 300) + { + if (playbackDiscrepancyCount++ > allowed_playback_discrepancies) + { + if (playbackRateValid.Value) + { + playbackRateValid.Value = false; + Logger.Log("System audio playback is not working as expected. Some online functionality will not work.\n\nPlease check your audio drivers.", level: LogLevel.Important); + } + } + else + { + Logger.Log($"Playback discrepancy detected ({playbackDiscrepancyCount} of allowed {allowed_playback_discrepancies}): {elapsedGameplayClockTime:N1} vs {elapsedValidationTime:N1}"); + } + + elapsedValidationTime = null; + } + } + } + + #endregion + private bool speedAdjustmentsApplied; private void addAdjustmentsToTrack() diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 785164178a..f88526b8f9 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -208,6 +208,14 @@ namespace osu.Game.Screens.Play private Task submitScore(Score score) { + var masterClock = GameplayClockContainer as MasterGameplayClockContainer; + + if (masterClock?.PlaybackRateValid.Value != true) + { + Logger.Log("Score submission cancelled due to audio playback rate discrepancy."); + return Task.CompletedTask; + } + // token may be null if the request failed but gameplay was still allowed (see HandleTokenRetrievalFailure). if (token == null) { From 30b5b36f1d72f9285971287d6d78c4628c3dde94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 26 Dec 2023 12:19:04 +0100 Subject: [PATCH 2127/4092] Fix code quality inspection --- osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 41ea347ef3..aa4c879468 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -467,7 +467,7 @@ namespace osu.Game.Tests.Visual.SongSelect for (int i = 0; i < random_select_count; i++) { prevRandom(); - AddAssert("correct random last selected", () => selectedSets.Peek() == carousel.SelectedBeatmapSet); + AddAssert("correct random last selected", () => selectedSets.Peek(), () => Is.EqualTo(carousel.SelectedBeatmapSet)); } } From f2c0e7cf2ecc04d5f3f01bd945623a61a8ab7f04 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Dec 2023 20:31:34 +0900 Subject: [PATCH 2128/4092] Fix editor's control point list refreshing multiple times for a single change --- osu.Game/Screens/Edit/Timing/ControlPointList.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointList.cs b/osu.Game/Screens/Edit/Timing/ControlPointList.cs index 22e37b9efb..7cd1dbc630 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointList.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointList.cs @@ -109,8 +109,13 @@ namespace osu.Game.Screens.Edit.Timing controlPointGroups.BindTo(Beatmap.ControlPointInfo.Groups); controlPointGroups.BindCollectionChanged((_, _) => { - table.ControlGroups = controlPointGroups; - changeHandler?.SaveState(); + // This callback can happen many times in a change operation. It gets expensive. + // We really should be handling the `CollectionChanged` event properly. + Scheduler.AddOnce(() => + { + table.ControlGroups = controlPointGroups; + changeHandler?.SaveState(); + }); }, true); table.OnRowSelected += drawable => scroll.ScrollIntoView(drawable); From 1f2f749db68fe3a541150abb7acdae7e0aabf79e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Dec 2023 20:42:04 +0900 Subject: [PATCH 2129/4092] Fix selection not being retained in control point list when undoing / redoing --- osu.Game/Screens/Edit/EditorTable.cs | 30 ++++++++++++++++++- .../Screens/Edit/Timing/ControlPointTable.cs | 14 +++++---- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorTable.cs b/osu.Game/Screens/Edit/EditorTable.cs index b79d71b42b..5ccb21cf59 100644 --- a/osu.Game/Screens/Edit/EditorTable.cs +++ b/osu.Game/Screens/Edit/EditorTable.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; @@ -46,15 +47,42 @@ namespace osu.Game.Screens.Edit }); } - protected void SetSelectedRow(object? item) + protected int GetIndexForObject(object? item) { + for (int i = 0; i < BackgroundFlow.Count; i++) + { + if (BackgroundFlow[i].Item == item) + return i; + } + + return -1; + } + + protected bool SetSelectedRow(object? item) + { + bool foundSelection = false; + foreach (var b in BackgroundFlow) { b.Selected = ReferenceEquals(b.Item, item); if (b.Selected) + { + Debug.Assert(!foundSelection); OnRowSelected?.Invoke(b); + foundSelection = true; + } } + + return foundSelection; + } + + protected object? GetObjectAtIndex(int index) + { + if (index < 0 || index > BackgroundFlow.Count - 1) + return null; + + return BackgroundFlow[index].Item; } protected override Drawable CreateHeader(int index, TableColumn? column) => new HeaderText(column?.Header ?? default); diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index b078e3fa44..335077c6f0 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Edit.Timing public partial class ControlPointTable : EditorTable { [Resolved] - private Bindable selectedGroup { get; set; } = null!; + private Bindable selectedGroup { get; set; } = null!; [Resolved] private EditorClock clock { get; set; } = null!; @@ -32,6 +32,8 @@ namespace osu.Game.Screens.Edit.Timing { set { + int selectedIndex = GetIndexForObject(selectedGroup.Value); + Content = null; BackgroundFlow.Clear(); @@ -53,7 +55,11 @@ namespace osu.Game.Screens.Edit.Timing Columns = createHeaders(); Content = value.Select(createContent).ToArray().ToRectangular(); - updateSelectedGroup(); + if (!SetSelectedRow(selectedGroup.Value)) + { + // Some operations completely obliterate references, so best-effort reselect based on index. + selectedGroup.Value = GetObjectAtIndex(selectedIndex) as ControlPointGroup; + } } } @@ -61,11 +67,9 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - selectedGroup.BindValueChanged(_ => updateSelectedGroup(), true); + selectedGroup.BindValueChanged(_ => SetSelectedRow(selectedGroup.Value), true); } - private void updateSelectedGroup() => SetSelectedRow(selectedGroup.Value); - private TableColumn[] createHeaders() { var columns = new List From 03e2463b06b14b66529627fc76941d901231001c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Dec 2023 21:20:18 +0900 Subject: [PATCH 2130/4092] Add test coverage and refactor to better handle equality edge case --- .../Visual/Editing/TestSceneTimingScreen.cs | 43 +++++++++++++++++++ osu.Game/Screens/Edit/EditorTable.cs | 2 +- .../Screens/Edit/Timing/ControlPointTable.cs | 29 ++++++++++--- 3 files changed, 66 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs index 216c35de65..40aadc8164 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs @@ -17,6 +17,7 @@ using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Timing; using osu.Game.Screens.Edit.Timing.RowAttributes; +using osuTK; using osuTK.Input; namespace osu.Game.Tests.Visual.Editing @@ -69,6 +70,48 @@ namespace osu.Game.Tests.Visual.Editing AddUntilStep("Wait for rows to load", () => Child.ChildrenOfType().Any()); } + [Test] + public void TestSelectedRetainedOverUndo() + { + AddStep("Select first timing point", () => + { + InputManager.MoveMouseTo(Child.ChildrenOfType().First()); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("Selection changed", () => timingScreen.SelectedGroup.Value.Time == 2170); + AddUntilStep("Ensure seeked to correct time", () => EditorClock.CurrentTimeAccurate == 2170); + + AddStep("Adjust offset", () => + { + InputManager.MoveMouseTo(timingScreen.ChildrenOfType().First().ScreenSpaceDrawQuad.Centre + new Vector2(20, 0)); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("wait for offset changed", () => + { + return timingScreen.SelectedGroup.Value.ControlPoints.Any(c => c is TimingControlPoint) && timingScreen.SelectedGroup.Value.Time > 2170; + }); + + AddStep("simulate undo", () => + { + var clone = editorBeatmap.ControlPointInfo.DeepClone(); + + editorBeatmap.ControlPointInfo.Clear(); + + foreach (var group in clone.Groups) + { + foreach (var cp in group.ControlPoints) + editorBeatmap.ControlPointInfo.Add(group.Time, cp); + } + }); + + AddUntilStep("selection retained", () => + { + return timingScreen.SelectedGroup.Value.ControlPoints.Any(c => c is TimingControlPoint) && timingScreen.SelectedGroup.Value.Time > 2170; + }); + } + [Test] public void TestTrackingCurrentTimeWhileRunning() { diff --git a/osu.Game/Screens/Edit/EditorTable.cs b/osu.Game/Screens/Edit/EditorTable.cs index 5ccb21cf59..e5dc540b06 100644 --- a/osu.Game/Screens/Edit/EditorTable.cs +++ b/osu.Game/Screens/Edit/EditorTable.cs @@ -58,7 +58,7 @@ namespace osu.Game.Screens.Edit return -1; } - protected bool SetSelectedRow(object? item) + protected virtual bool SetSelectedRow(object? item) { bool foundSelection = false; diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 335077c6f0..7a27056da3 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -46,7 +46,7 @@ namespace osu.Game.Screens.Edit.Timing { Action = () => { - selectedGroup.Value = group; + SetSelectedRow(group); clock.SeekSmoothlyTo(group.Time); } }); @@ -55,11 +55,16 @@ namespace osu.Game.Screens.Edit.Timing Columns = createHeaders(); Content = value.Select(createContent).ToArray().ToRectangular(); - if (!SetSelectedRow(selectedGroup.Value)) - { - // Some operations completely obliterate references, so best-effort reselect based on index. - selectedGroup.Value = GetObjectAtIndex(selectedIndex) as ControlPointGroup; - } + // Attempt to retain selection. + if (SetSelectedRow(selectedGroup.Value)) + return; + + // Some operations completely obliterate references, so best-effort reselect based on index. + if (SetSelectedRow(GetObjectAtIndex(selectedIndex))) + return; + + // Selection could not be retained. + selectedGroup.Value = null; } } @@ -67,7 +72,17 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - selectedGroup.BindValueChanged(_ => SetSelectedRow(selectedGroup.Value), true); + // Handle external selections. + selectedGroup.BindValueChanged(g => SetSelectedRow(g.NewValue), true); + } + + protected override bool SetSelectedRow(object? item) + { + if (!base.SetSelectedRow(item)) + return false; + + selectedGroup.Value = item as ControlPointGroup; + return true; } private TableColumn[] createHeaders() From d70fddb6fde18de0ef092c047509ee0a2efd2d16 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Dec 2023 00:11:22 +0900 Subject: [PATCH 2131/4092] Fix elapsed time being counted twice on first frame --- osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 2844d84f31..a475f4823f 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -227,8 +227,10 @@ namespace osu.Game.Screens.Play { elapsedGameplayClockTime += GameplayClock.ElapsedFrameTime; - elapsedValidationTime ??= elapsedGameplayClockTime; - elapsedValidationTime += GameplayClock.Rate * Time.Elapsed; + if (elapsedValidationTime == null) + elapsedValidationTime = elapsedGameplayClockTime; + else + elapsedValidationTime += GameplayClock.Rate * Time.Elapsed; if (Math.Abs(elapsedGameplayClockTime - elapsedValidationTime!.Value) > 300) { From c55458e49c6b5747a1a84e00ac05787829c81a2e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Dec 2023 02:32:47 +0900 Subject: [PATCH 2132/4092] Remove pointless intermediary class --- .../Containers/ExpandingButtonContainer.cs | 21 ------------------- osu.Game/Overlays/Settings/SettingsSidebar.cs | 2 +- 2 files changed, 1 insertion(+), 22 deletions(-) delete mode 100644 osu.Game/Graphics/Containers/ExpandingButtonContainer.cs diff --git a/osu.Game/Graphics/Containers/ExpandingButtonContainer.cs b/osu.Game/Graphics/Containers/ExpandingButtonContainer.cs deleted file mode 100644 index 5abb4096ac..0000000000 --- a/osu.Game/Graphics/Containers/ExpandingButtonContainer.cs +++ /dev/null @@ -1,21 +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.Graphics.Containers -{ - /// - /// An with a long hover expansion delay. - /// - /// - /// Mostly used for buttons with explanatory labels, in which the label would display after a "long hover". - /// - public partial class ExpandingButtonContainer : ExpandingContainer - { - protected ExpandingButtonContainer(float contractedWidth, float expandedWidth) - : base(contractedWidth, expandedWidth) - { - } - - protected override double HoverExpansionDelay => 400; - } -} diff --git a/osu.Game/Overlays/Settings/SettingsSidebar.cs b/osu.Game/Overlays/Settings/SettingsSidebar.cs index 06bc2fd788..7baf9a58ff 100644 --- a/osu.Game/Overlays/Settings/SettingsSidebar.cs +++ b/osu.Game/Overlays/Settings/SettingsSidebar.cs @@ -8,7 +8,7 @@ using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.Settings { - public partial class SettingsSidebar : ExpandingButtonContainer + public partial class SettingsSidebar : ExpandingContainer { public const float DEFAULT_WIDTH = 70; public const int EXPANDED_WIDTH = 200; From 58476d5429985109a13240c481da362449489167 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Dec 2023 02:33:02 +0900 Subject: [PATCH 2133/4092] Allow `ExpandingContainer` to not auto expand on hover --- osu.Game/Graphics/Containers/ExpandingContainer.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/Containers/ExpandingContainer.cs b/osu.Game/Graphics/Containers/ExpandingContainer.cs index 60b9e6a167..2abdb508ae 100644 --- a/osu.Game/Graphics/Containers/ExpandingContainer.cs +++ b/osu.Game/Graphics/Containers/ExpandingContainer.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.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -26,6 +24,8 @@ namespace osu.Game.Graphics.Containers /// protected virtual double HoverExpansionDelay => 0; + protected virtual bool ExpandOnHover => true; + protected override Container Content => FillFlow; protected FillFlowContainer FillFlow { get; } @@ -53,7 +53,7 @@ namespace osu.Game.Graphics.Containers }; } - private ScheduledDelegate hoverExpandEvent; + private ScheduledDelegate? hoverExpandEvent; protected override void LoadComplete() { @@ -93,6 +93,9 @@ namespace osu.Game.Graphics.Containers private void updateHoverExpansion() { + if (!ExpandOnHover) + return; + hoverExpandEvent?.Cancel(); if (IsHovered && !Expanded.Value) From 9a1a97180d7938bbc1dced8d887cc226f28deb46 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Dec 2023 02:33:17 +0900 Subject: [PATCH 2134/4092] Change settings overlay to always show expanded buttons --- osu.Game/Overlays/Settings/SettingsSidebar.cs | 3 +++ osu.Game/Overlays/SettingsOverlay.cs | 7 +++++-- osu.Game/Overlays/SettingsPanel.cs | 1 - 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsSidebar.cs b/osu.Game/Overlays/Settings/SettingsSidebar.cs index 7baf9a58ff..fc5c6b07bb 100644 --- a/osu.Game/Overlays/Settings/SettingsSidebar.cs +++ b/osu.Game/Overlays/Settings/SettingsSidebar.cs @@ -13,9 +13,12 @@ namespace osu.Game.Overlays.Settings public const float DEFAULT_WIDTH = 70; public const int EXPANDED_WIDTH = 200; + protected override bool ExpandOnHover => false; + public SettingsSidebar() : base(DEFAULT_WIDTH, EXPANDED_WIDTH) { + Expanded.Value = true; } [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs index 746d451343..a779c3c263 100644 --- a/osu.Game/Overlays/SettingsOverlay.cs +++ b/osu.Game/Overlays/SettingsOverlay.cs @@ -72,16 +72,19 @@ namespace osu.Game.Overlays switch (state.NewValue) { case Visibility.Visible: - Sidebar?.FadeColour(Color4.DarkGray, 300, Easing.OutQuint); + Sidebar.Expanded.Value = false; + Sidebar.FadeColour(Color4.DarkGray, 300, Easing.OutQuint); SectionsContainer.FadeOut(300, Easing.OutQuint); ContentContainer.MoveToX(-PANEL_WIDTH, 500, Easing.OutQuint); lastOpenedSubPanel = panel; + break; case Visibility.Hidden: - Sidebar?.FadeColour(Color4.White, 300, Easing.OutQuint); + Sidebar.Expanded.Value = true; + Sidebar.FadeColour(Color4.White, 300, Easing.OutQuint); SectionsContainer.FadeIn(500, Easing.OutQuint); ContentContainer.MoveToX(0, 500, Easing.OutQuint); diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index 3bac6c400f..339120fd84 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -285,7 +285,6 @@ namespace osu.Game.Overlays return; SectionsContainer.ScrollTo(section); - Sidebar.Expanded.Value = false; }, }; } From 5de8307918216ed5f18edb42bfc8eb28c6f310b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Dec 2023 02:38:23 +0900 Subject: [PATCH 2135/4092] Reduce width of sidebar buttons --- osu.Game/Overlays/Settings/SettingsSidebar.cs | 2 +- osu.Game/Overlays/Settings/SidebarIconButton.cs | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsSidebar.cs b/osu.Game/Overlays/Settings/SettingsSidebar.cs index fc5c6b07bb..c751f12003 100644 --- a/osu.Game/Overlays/Settings/SettingsSidebar.cs +++ b/osu.Game/Overlays/Settings/SettingsSidebar.cs @@ -11,7 +11,7 @@ namespace osu.Game.Overlays.Settings public partial class SettingsSidebar : ExpandingContainer { public const float DEFAULT_WIDTH = 70; - public const int EXPANDED_WIDTH = 200; + public const int EXPANDED_WIDTH = 170; protected override bool ExpandOnHover => false; diff --git a/osu.Game/Overlays/Settings/SidebarIconButton.cs b/osu.Game/Overlays/Settings/SidebarIconButton.cs index 4e5b361460..bd9ac3cf97 100644 --- a/osu.Game/Overlays/Settings/SidebarIconButton.cs +++ b/osu.Game/Overlays/Settings/SidebarIconButton.cs @@ -69,18 +69,18 @@ namespace osu.Game.Overlays.Settings Colour = OsuColour.Gray(0.6f), Children = new Drawable[] { - headerText = new OsuSpriteText - { - Position = new Vector2(SettingsSidebar.DEFAULT_WIDTH + 10, 0), - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, iconContainer = new ConstrainedIconContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(20), }, + headerText = new OsuSpriteText + { + Position = new Vector2(60, 0), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, } }, selectionIndicator = new CircularContainer From 5b1deb7c4b1139aa55543094dc652b6763ddfcd2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Dec 2023 02:40:50 +0900 Subject: [PATCH 2136/4092] Give buttons a touch of padding to make click effect feel better --- osu.Game/Overlays/Settings/SidebarIconButton.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Settings/SidebarIconButton.cs b/osu.Game/Overlays/Settings/SidebarIconButton.cs index bd9ac3cf97..a8d27eb792 100644 --- a/osu.Game/Overlays/Settings/SidebarIconButton.cs +++ b/osu.Game/Overlays/Settings/SidebarIconButton.cs @@ -60,6 +60,8 @@ namespace osu.Game.Overlays.Settings RelativeSizeAxes = Axes.X; Height = 46; + Padding = new MarginPadding(5); + AddRange(new Drawable[] { textIconContent = new Container From 8e13f65c5d388ba0494367dfedcb2cccdc066b84 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Dec 2023 02:46:21 +0900 Subject: [PATCH 2137/4092] Adjust hover effect slightly --- osu.Game/Overlays/Settings/SidebarIconButton.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/Settings/SidebarIconButton.cs b/osu.Game/Overlays/Settings/SidebarIconButton.cs index a8d27eb792..041d19e8bf 100644 --- a/osu.Game/Overlays/Settings/SidebarIconButton.cs +++ b/osu.Game/Overlays/Settings/SidebarIconButton.cs @@ -111,10 +111,13 @@ namespace osu.Game.Overlays.Settings private void load() { selectionIndicator.Colour = ColourProvider.Highlight1; + Hover.Colour = ColourProvider.Light4; } protected override void UpdateState() { + Hover.FadeTo(IsHovered ? 0.1f : 0, FADE_DURATION, Easing.OutQuint); + if (Selected) { textIconContent.FadeColour(ColourProvider.Content1, FADE_DURATION, Easing.OutQuint); From 5d0b5247946e758fd28f8bad58ec16cf7c15ee18 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Dec 2023 02:54:24 +0900 Subject: [PATCH 2138/4092] Adjust back button to match style better --- osu.Game/Overlays/Settings/SidebarButton.cs | 7 ++++++- osu.Game/Overlays/Settings/SidebarIconButton.cs | 3 +-- osu.Game/Overlays/SettingsSubPanel.cs | 13 +++++++++---- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Settings/SidebarButton.cs b/osu.Game/Overlays/Settings/SidebarButton.cs index a63688762d..f58c2f41ef 100644 --- a/osu.Game/Overlays/Settings/SidebarButton.cs +++ b/osu.Game/Overlays/Settings/SidebarButton.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; @@ -23,6 +24,7 @@ namespace osu.Game.Overlays.Settings private void load() { BackgroundColour = ColourProvider.Background5; + Hover.Colour = ColourProvider.Light4; } protected override void LoadComplete() @@ -40,6 +42,9 @@ namespace osu.Game.Overlays.Settings protected override void OnHoverLost(HoverLostEvent e) => UpdateState(); - protected abstract void UpdateState(); + protected virtual void UpdateState() + { + Hover.FadeTo(IsHovered ? 0.1f : 0, FADE_DURATION, Easing.OutQuint); + } } } diff --git a/osu.Game/Overlays/Settings/SidebarIconButton.cs b/osu.Game/Overlays/Settings/SidebarIconButton.cs index 041d19e8bf..1bb3aa2921 100644 --- a/osu.Game/Overlays/Settings/SidebarIconButton.cs +++ b/osu.Game/Overlays/Settings/SidebarIconButton.cs @@ -111,12 +111,11 @@ namespace osu.Game.Overlays.Settings private void load() { selectionIndicator.Colour = ColourProvider.Highlight1; - Hover.Colour = ColourProvider.Light4; } protected override void UpdateState() { - Hover.FadeTo(IsHovered ? 0.1f : 0, FADE_DURATION, Easing.OutQuint); + base.UpdateState(); if (Selected) { diff --git a/osu.Game/Overlays/SettingsSubPanel.cs b/osu.Game/Overlays/SettingsSubPanel.cs index 1651975a74..1b9edeebc2 100644 --- a/osu.Game/Overlays/SettingsSubPanel.cs +++ b/osu.Game/Overlays/SettingsSubPanel.cs @@ -47,7 +47,9 @@ namespace osu.Game.Overlays [BackgroundDependencyLoader] private void load() { - Size = new Vector2(SettingsSidebar.DEFAULT_WIDTH); + Size = new Vector2(SettingsSidebar.EXPANDED_WIDTH); + + Padding = new MarginPadding(5); AddRange(new Drawable[] { @@ -61,7 +63,8 @@ namespace osu.Game.Overlays { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(15), + Y = -5, + Size = new Vector2(30), Shadow = true, Icon = FontAwesome.Solid.ChevronLeft }, @@ -69,8 +72,8 @@ namespace osu.Game.Overlays { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Y = 15, - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Y = 30, + Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular), Text = @"back", }, } @@ -80,6 +83,8 @@ namespace osu.Game.Overlays protected override void UpdateState() { + base.UpdateState(); + content.FadeColour(IsHovered ? ColourProvider.Light1 : ColourProvider.Light3, FADE_DURATION, Easing.OutQuint); } } From c087578e011c6ade4411dbccfcfce80da407dba3 Mon Sep 17 00:00:00 2001 From: rushiiMachine <33725716+rushiiMachine@users.noreply.github.com> Date: Tue, 26 Dec 2023 10:07:21 -0800 Subject: [PATCH 2139/4092] Force minimum cursor size for `OsuResumeOverlay` On cursor sizes below 0.3x it becomes exceedingly difficult to quickly locate and then accurately click the resume cursor on the pause overlay as it could as big as a handful of pixels. This clamps the minimum cursor size to 1x for the resume overlay, which is way more comfortable and more closely resembles stable. --- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 10 +++++----- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 6 ++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index ba9fda25e4..18351c20ce 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -70,10 +70,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor }; userCursorScale = config.GetBindable(OsuSetting.GameplayCursorSize); - userCursorScale.ValueChanged += _ => calculateCursorScale(); + userCursorScale.ValueChanged += _ => cursorScale.Value = CalculateCursorScale(); autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize); - autoCursorScale.ValueChanged += _ => calculateCursorScale(); + autoCursorScale.ValueChanged += _ => cursorScale.Value = CalculateCursorScale(); cursorScale.BindValueChanged(e => cursorScaleContainer.Scale = new Vector2(e.NewValue), true); } @@ -81,10 +81,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor protected override void LoadComplete() { base.LoadComplete(); - calculateCursorScale(); + cursorScale.Value = CalculateCursorScale(); } - private void calculateCursorScale() + protected virtual float CalculateCursorScale() { float scale = userCursorScale.Value; @@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor scale *= GetScaleForCircleSize(state.Beatmap.Difficulty.CircleSize); } - cursorScale.Value = scale; + return scale; } protected override void SkinChanged(ISkinSource skin) diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index ea49836772..f5e83f46f2 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -71,6 +71,12 @@ namespace osu.Game.Rulesets.Osu.UI RelativePositionAxes = Axes.Both; } + protected override float CalculateCursorScale() + { + // Force minimum cursor size so it's easily clickable + return Math.Max(1f, base.CalculateCursorScale()); + } + protected override bool OnHover(HoverEvent e) { updateColour(); From c107bfcd3112cc8b1589d84f4634be1688cc684d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 26 Dec 2023 20:09:34 +0100 Subject: [PATCH 2140/4092] Fix `TestSceneSideOverlays` test failure --- osu.Game/Overlays/Settings/SettingsSidebar.cs | 4 ++-- osu.Game/Overlays/Settings/SidebarIconButton.cs | 2 +- osu.Game/Overlays/SettingsPanel.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsSidebar.cs b/osu.Game/Overlays/Settings/SettingsSidebar.cs index c751f12003..302c52fbd2 100644 --- a/osu.Game/Overlays/Settings/SettingsSidebar.cs +++ b/osu.Game/Overlays/Settings/SettingsSidebar.cs @@ -10,13 +10,13 @@ namespace osu.Game.Overlays.Settings { public partial class SettingsSidebar : ExpandingContainer { - public const float DEFAULT_WIDTH = 70; + public const float CONTRACTED_WIDTH = 70; public const int EXPANDED_WIDTH = 170; protected override bool ExpandOnHover => false; public SettingsSidebar() - : base(DEFAULT_WIDTH, EXPANDED_WIDTH) + : base(CONTRACTED_WIDTH, EXPANDED_WIDTH) { Expanded.Value = true; } diff --git a/osu.Game/Overlays/Settings/SidebarIconButton.cs b/osu.Game/Overlays/Settings/SidebarIconButton.cs index 1bb3aa2921..e7ae4cc81d 100644 --- a/osu.Game/Overlays/Settings/SidebarIconButton.cs +++ b/osu.Game/Overlays/Settings/SidebarIconButton.cs @@ -66,7 +66,7 @@ namespace osu.Game.Overlays.Settings { textIconContent = new Container { - Width = SettingsSidebar.DEFAULT_WIDTH, + Width = SettingsSidebar.CONTRACTED_WIDTH, RelativeSizeAxes = Axes.Y, Colour = OsuColour.Gray(0.6f), Children = new Drawable[] diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index 339120fd84..f4dfc7fa27 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -33,7 +33,7 @@ namespace osu.Game.Overlays public const float TRANSITION_LENGTH = 600; - private const float sidebar_width = SettingsSidebar.DEFAULT_WIDTH; + private const float sidebar_width = SettingsSidebar.EXPANDED_WIDTH; /// /// The width of the settings panel content, excluding the sidebar. From 9ac79782d25f3625d79d1d9096cb3eecacbdad21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 26 Dec 2023 20:21:15 +0100 Subject: [PATCH 2141/4092] Add visibility toggle step to settings panel test scene --- osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs index 5d9c2f890c..8c4ca47fc3 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs @@ -41,6 +41,7 @@ namespace osu.Game.Tests.Visual.Settings public void TestBasic() { AddStep("do nothing", () => { }); + AddToggleStep("toggle visibility", visible => settings.State.Value = visible ? Visibility.Visible : Visibility.Hidden); } [Test] From af47f9fd701897a3096c785a5c2e31c2b87ee6f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 26 Dec 2023 20:24:43 +0100 Subject: [PATCH 2142/4092] Fix sidebar button text becoming masked away during fadeout --- osu.Game/Overlays/Settings/SidebarIconButton.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Settings/SidebarIconButton.cs b/osu.Game/Overlays/Settings/SidebarIconButton.cs index e7ae4cc81d..f4b71207e3 100644 --- a/osu.Game/Overlays/Settings/SidebarIconButton.cs +++ b/osu.Game/Overlays/Settings/SidebarIconButton.cs @@ -66,16 +66,16 @@ namespace osu.Game.Overlays.Settings { textIconContent = new Container { - Width = SettingsSidebar.CONTRACTED_WIDTH, - RelativeSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.Both, Colour = OsuColour.Gray(0.6f), Children = new Drawable[] { iconContainer = new ConstrainedIconContainer { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, Size = new Vector2(20), + Margin = new MarginPadding { Left = 25 } }, headerText = new OsuSpriteText { From 6f672b8cb302fb7aa5034b07a2b6f0f351df54aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 26 Dec 2023 20:36:12 +0100 Subject: [PATCH 2143/4092] Fix `TestSceneKeyBindingPanel` failures --- .../Visual/Settings/TestSceneKeyBindingPanel.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs index 1c4e89e1a2..57c9770c9a 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs @@ -13,7 +13,6 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Localisation; using osu.Game.Overlays; -using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings.Sections.Input; using osu.Game.Rulesets.Taiko; using osuTK.Input; @@ -152,7 +151,7 @@ namespace osu.Game.Tests.Visual.Settings AddStep("click first row with two bindings", () => { multiBindingRow = panel.ChildrenOfType().First(row => row.Defaults.Count() > 1); - InputManager.MoveMouseTo(multiBindingRow); + InputManager.MoveMouseTo(multiBindingRow.ChildrenOfType().First()); InputManager.Click(MouseButton.Left); }); @@ -256,7 +255,7 @@ namespace osu.Game.Tests.Visual.Settings AddStep("click first row with two bindings", () => { multiBindingRow = panel.ChildrenOfType().First(row => row.Defaults.Count() > 1); - InputManager.MoveMouseTo(multiBindingRow); + InputManager.MoveMouseTo(multiBindingRow.ChildrenOfType().First()); InputManager.Click(MouseButton.Left); }); @@ -305,7 +304,6 @@ namespace osu.Game.Tests.Visual.Settings section.ChildrenOfType().Single().TriggerClick(); }); AddStep("move mouse to centre", () => InputManager.MoveMouseTo(panel.ScreenSpaceDrawQuad.Centre)); - AddUntilStep("wait for collapsed", () => panel.ChildrenOfType().Single().Expanded.Value, () => Is.False); scrollToAndStartBinding("Left (rim)"); AddStep("attempt to bind M1 to two keys", () => InputManager.Click(MouseButton.Left)); @@ -325,7 +323,6 @@ namespace osu.Game.Tests.Visual.Settings section.ChildrenOfType().Single().TriggerClick(); }); AddStep("move mouse to centre", () => InputManager.MoveMouseTo(panel.ScreenSpaceDrawQuad.Centre)); - AddUntilStep("wait for collapsed", () => panel.ChildrenOfType().Single().Expanded.Value, () => Is.False); scrollToAndStartBinding("Left (rim)"); AddStep("attempt to bind M1 to two keys", () => InputManager.Click(MouseButton.Left)); @@ -345,7 +342,6 @@ namespace osu.Game.Tests.Visual.Settings section.ChildrenOfType().Single().TriggerClick(); }); AddStep("move mouse to centre", () => InputManager.MoveMouseTo(panel.ScreenSpaceDrawQuad.Centre)); - AddUntilStep("wait for collapsed", () => panel.ChildrenOfType().Single().Expanded.Value, () => Is.False); scrollToAndStartBinding("Left (centre)"); AddStep("clear binding", () => { @@ -377,7 +373,6 @@ namespace osu.Game.Tests.Visual.Settings section.ChildrenOfType().Single().TriggerClick(); }); AddStep("move mouse to centre", () => InputManager.MoveMouseTo(panel.ScreenSpaceDrawQuad.Centre)); - AddUntilStep("wait for collapsed", () => panel.ChildrenOfType().Single().Expanded.Value, () => Is.False); scrollToAndStartBinding("Left (centre)"); AddStep("clear binding", () => { From 1d4db3b7a95b894604a0717920fc2771fd7ef352 Mon Sep 17 00:00:00 2001 From: rushiiMachine <33725716+rushiiMachine@users.noreply.github.com> Date: Tue, 26 Dec 2023 11:08:21 -0800 Subject: [PATCH 2144/4092] Fix `SkinEditorOverlay` freezing when `ReplayPlayer` screen exits early Originally when popping in, the ReplayPlayer was loaded first (if previous screen was MainMenu), and afterwards the SkinEditor component was loaded asynchronously. However, if the ReplayPlayer screen exits quickly (like in the event the beatmap has no objects), the skin editor component has not finished initializing (this is before it was even added to the component tree, so it's still not marked `Visible`), then the screen exiting will cause `OsuGame` to call SetTarget(newScreen) -> setTarget(...) which sees that the cached `skinEditor` is not visible yet, and hides/nulls the field. This is the point where LoadComponentAsync(editor, ...) finishes, and the callback sees that the cached skinEditor field is now different (null) than the one that was loaded, and never adds it to the component tree. This occurrence is unhandled and as such the SkinEditorOverlay never hides itself, consuming all input infinitely. This PR changes the loading to start loading the ReplayPlayer *after* the SkinEditor has been loaded and added to the component tree. Additionally, this lowers the exit delay for ReplayPlayer and changes the "no hit objects" notification to not be an error since it's a controlled exit. --- osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs | 8 ++++---- osu.Game/Screens/Play/Player.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs index bedaf12c9b..880921ca64 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs @@ -100,9 +100,6 @@ namespace osu.Game.Overlays.SkinEditor { globallyDisableBeatmapSkinSetting(); - if (lastTargetScreen is MainMenu) - PresentGameplay(); - if (skinEditor != null) { skinEditor.Show(); @@ -122,6 +119,9 @@ namespace osu.Game.Overlays.SkinEditor AddInternal(editor); + if (lastTargetScreen is MainMenu) + PresentGameplay(); + Debug.Assert(lastTargetScreen != null); SetTarget(lastTargetScreen); @@ -316,7 +316,7 @@ namespace osu.Game.Overlays.SkinEditor base.LoadComplete(); if (!LoadedBeatmapSuccessfully) - Scheduler.AddDelayed(this.Exit, 3000); + Scheduler.AddDelayed(this.Exit, RESULTS_DISPLAY_DELAY); } protected override void Update() diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index c960ac357f..08d77d60d8 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -547,7 +547,7 @@ namespace osu.Game.Screens.Play if (playable.HitObjects.Count == 0) { - Logger.Log("Beatmap contains no hit objects!", level: LogLevel.Error); + Logger.Log("Beatmap contains no hit objects!", level: LogLevel.Important); return null; } } From 85a768d0c806d5c4579b762ae98e938eee135fe7 Mon Sep 17 00:00:00 2001 From: rushiiMachine <33725716+rushiiMachine@users.noreply.github.com> Date: Tue, 26 Dec 2023 12:46:50 -0800 Subject: [PATCH 2145/4092] Don't reuse results delay const --- osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs index 880921ca64..10a032193f 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs @@ -316,7 +316,7 @@ namespace osu.Game.Overlays.SkinEditor base.LoadComplete(); if (!LoadedBeatmapSuccessfully) - Scheduler.AddDelayed(this.Exit, RESULTS_DISPLAY_DELAY); + Scheduler.AddDelayed(this.Exit, 1000); } protected override void Update() From 8cd240fbecebf7bd37ba2cc504fe751c5afe727a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Dec 2023 13:20:19 +0900 Subject: [PATCH 2146/4092] Reduce size and fix alignment of back button --- osu.Game/Overlays/SettingsSubPanel.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/SettingsSubPanel.cs b/osu.Game/Overlays/SettingsSubPanel.cs index 1b9edeebc2..4d1f8f45cc 100644 --- a/osu.Game/Overlays/SettingsSubPanel.cs +++ b/osu.Game/Overlays/SettingsSubPanel.cs @@ -37,7 +37,7 @@ namespace osu.Game.Overlays public partial class BackButton : SidebarButton { - private Container content; + private Drawable content; public BackButton() : base(HoverSampleSet.Default) @@ -49,30 +49,31 @@ namespace osu.Game.Overlays { Size = new Vector2(SettingsSidebar.EXPANDED_WIDTH); - Padding = new MarginPadding(5); + Padding = new MarginPadding(40); AddRange(new Drawable[] { - content = new Container + content = new FillFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(5), Children = new Drawable[] { new SpriteIcon { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Y = -5, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, Size = new Vector2(30), Shadow = true, Icon = FontAwesome.Solid.ChevronLeft }, new OsuSpriteText { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Y = 30, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular), Text = @"back", }, From 901674a1303a349185d5123f013a3d2e3d2d4fad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Dec 2023 13:39:13 +0900 Subject: [PATCH 2147/4092] Move back button inside sidebar to fix weird animation --- .../Visual/Settings/TestSceneSettingsPanel.cs | 2 +- osu.Game/Overlays/Settings/SettingsSidebar.cs | 79 ++++++++++++++++++- osu.Game/Overlays/SettingsOverlay.cs | 2 +- osu.Game/Overlays/SettingsPanel.cs | 13 +-- osu.Game/Overlays/SettingsSubPanel.cs | 71 ----------------- 5 files changed, 87 insertions(+), 80 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs index 8c4ca47fc3..df0fc8de57 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs @@ -111,7 +111,7 @@ namespace osu.Game.Tests.Visual.Settings AddStep("Press back", () => settings .ChildrenOfType().FirstOrDefault()? - .ChildrenOfType().FirstOrDefault()?.TriggerClick()); + .ChildrenOfType().FirstOrDefault()?.TriggerClick()); AddUntilStep("top-level textbox focused", () => settings.SectionsContainer.ChildrenOfType().FirstOrDefault()?.HasFocus == true); } diff --git a/osu.Game/Overlays/Settings/SettingsSidebar.cs b/osu.Game/Overlays/Settings/SettingsSidebar.cs index 302c52fbd2..ddbcd60ef6 100644 --- a/osu.Game/Overlays/Settings/SettingsSidebar.cs +++ b/osu.Game/Overlays/Settings/SettingsSidebar.cs @@ -1,10 +1,17 @@ // 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.Framework.Graphics.Sprites; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK; namespace osu.Game.Overlays.Settings { @@ -13,11 +20,16 @@ namespace osu.Game.Overlays.Settings public const float CONTRACTED_WIDTH = 70; public const int EXPANDED_WIDTH = 170; + public Action? BackButtonAction; + protected override bool ExpandOnHover => false; - public SettingsSidebar() + private readonly bool showBackButton; + + public SettingsSidebar(bool showBackButton) : base(CONTRACTED_WIDTH, EXPANDED_WIDTH) { + this.showBackButton = showBackButton; Expanded.Value = true; } @@ -30,6 +42,71 @@ namespace osu.Game.Overlays.Settings RelativeSizeAxes = Axes.Both, Depth = float.MaxValue }); + + if (showBackButton) + { + AddInternal(new BackButton + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Action = () => BackButtonAction?.Invoke(), + }); + } + } + + public partial class BackButton : SidebarButton + { + private Drawable content = null!; + + public BackButton() + : base(HoverSampleSet.Default) + { + } + + [BackgroundDependencyLoader] + private void load() + { + Size = new Vector2(SettingsSidebar.EXPANDED_WIDTH); + + Padding = new MarginPadding(40); + + AddRange(new[] + { + content = new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(5), + Children = new Drawable[] + { + new SpriteIcon + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Size = new Vector2(30), + Shadow = true, + Icon = FontAwesome.Solid.ChevronLeft + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular), + Text = @"back", + }, + } + } + }); + } + + protected override void UpdateState() + { + base.UpdateState(); + + content.FadeColour(IsHovered ? ColourProvider.Light1 : ColourProvider.Light3, FADE_DURATION, Easing.OutQuint); + } } } } diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs index a779c3c263..5735b1515a 100644 --- a/osu.Game/Overlays/SettingsOverlay.cs +++ b/osu.Game/Overlays/SettingsOverlay.cs @@ -49,7 +49,7 @@ namespace osu.Game.Overlays protected override Drawable CreateFooter() => new SettingsFooter(); public SettingsOverlay() - : base(true) + : base(false) { } diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index f4dfc7fa27..3861c5abc7 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -59,7 +59,7 @@ namespace osu.Game.Overlays protected override string PopInSampleName => "UI/settings-pop-in"; protected override double PopInOutSampleBalance => -OsuGameBase.SFX_STEREO_STRENGTH; - private readonly bool showSidebar; + private readonly bool showBackButton; private LoadingLayer loading; @@ -72,9 +72,9 @@ namespace osu.Game.Overlays [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); - protected SettingsPanel(bool showSidebar) + protected SettingsPanel(bool showBackButton) { - this.showSidebar = showSidebar; + this.showBackButton = showBackButton; RelativeSizeAxes = Axes.Y; AutoSizeAxes = Axes.X; } @@ -146,10 +146,11 @@ namespace osu.Game.Overlays } }); - if (showSidebar) + AddInternal(Sidebar = new SettingsSidebar(showBackButton) { - AddInternal(Sidebar = new SettingsSidebar { Width = sidebar_width }); - } + BackButtonAction = Hide, + Width = sidebar_width + }); CreateSections()?.ForEach(AddSection); } diff --git a/osu.Game/Overlays/SettingsSubPanel.cs b/osu.Game/Overlays/SettingsSubPanel.cs index 4d1f8f45cc..440639f06b 100644 --- a/osu.Game/Overlays/SettingsSubPanel.cs +++ b/osu.Game/Overlays/SettingsSubPanel.cs @@ -1,17 +1,7 @@ // 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.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays.Settings; -using osuTK; namespace osu.Game.Overlays { @@ -25,69 +15,8 @@ namespace osu.Game.Overlays [BackgroundDependencyLoader] private void load() { - AddInternal(new BackButton - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Action = Hide - }); } protected override bool DimMainContent => false; // dimming is handled by main overlay - - public partial class BackButton : SidebarButton - { - private Drawable content; - - public BackButton() - : base(HoverSampleSet.Default) - { - } - - [BackgroundDependencyLoader] - private void load() - { - Size = new Vector2(SettingsSidebar.EXPANDED_WIDTH); - - Padding = new MarginPadding(40); - - AddRange(new Drawable[] - { - content = new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Direction = FillDirection.Vertical, - AutoSizeAxes = Axes.Both, - Spacing = new Vector2(5), - Children = new Drawable[] - { - new SpriteIcon - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Size = new Vector2(30), - Shadow = true, - Icon = FontAwesome.Solid.ChevronLeft - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular), - Text = @"back", - }, - } - } - }); - } - - protected override void UpdateState() - { - base.UpdateState(); - - content.FadeColour(IsHovered ? ColourProvider.Light1 : ColourProvider.Light3, FADE_DURATION, Easing.OutQuint); - } - } } } From 81ba46216f0951e30329c3e2779a5380ee25e743 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Dec 2023 13:49:00 +0900 Subject: [PATCH 2148/4092] Speed up fades in transition to avoid ugliness --- osu.Game/Overlays/SettingsPanel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index 3861c5abc7..748673035b 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -181,7 +181,7 @@ namespace osu.Game.Overlays Scheduler.AddDelayed(loadSections, TRANSITION_LENGTH / 3); Sidebar?.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint); - this.FadeTo(1, TRANSITION_LENGTH, Easing.OutQuint); + this.FadeTo(1, TRANSITION_LENGTH / 2, Easing.OutQuint); searchTextBox.TakeFocus(); searchTextBox.HoldFocus = true; @@ -197,7 +197,7 @@ namespace osu.Game.Overlays ContentContainer.MoveToX(-WIDTH + ExpandedPosition, TRANSITION_LENGTH, Easing.OutQuint); Sidebar?.MoveToX(-sidebar_width, TRANSITION_LENGTH, Easing.OutQuint); - this.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint); + this.FadeTo(0, TRANSITION_LENGTH / 2, Easing.OutQuint); searchTextBox.HoldFocus = false; if (searchTextBox.HasFocus) From 768e10d55f5e3cc144856b47906b03187f162e13 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Dec 2023 13:50:47 +0900 Subject: [PATCH 2149/4092] Adjust `NotificationOverlay` fades to match --- osu.Game/Overlays/NotificationOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index c3ddb228ea..f56d09f2b2 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -227,7 +227,7 @@ namespace osu.Game.Overlays protected override void PopIn() { this.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint); - mainContent.FadeTo(1, TRANSITION_LENGTH, Easing.OutQuint); + mainContent.FadeTo(1, TRANSITION_LENGTH / 2, Easing.OutQuint); mainContent.FadeEdgeEffectTo(WaveContainer.SHADOW_OPACITY, WaveContainer.APPEAR_DURATION, Easing.Out); toastTray.FlushAllToasts(); @@ -240,7 +240,7 @@ namespace osu.Game.Overlays markAllRead(); this.MoveToX(WIDTH, TRANSITION_LENGTH, Easing.OutQuint); - mainContent.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint); + mainContent.FadeTo(0, TRANSITION_LENGTH / 2, Easing.OutQuint); mainContent.FadeEdgeEffectTo(0, WaveContainer.DISAPPEAR_DURATION, Easing.In); } From 8f7e0571f0693cfd8fefcc41f41a70239db1f21e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 10:51:05 +0100 Subject: [PATCH 2150/4092] Add failing test coverage of handling out-of-bounds catch objects --- .../TestSceneOutOfBoundsObjects.cs | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 osu.Game.Rulesets.Catch.Tests/TestSceneOutOfBoundsObjects.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneOutOfBoundsObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneOutOfBoundsObjects.cs new file mode 100644 index 0000000000..951f5d1ca1 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneOutOfBoundsObjects.cs @@ -0,0 +1,72 @@ +// 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.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.Objects.Drawables; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Tests +{ + public partial class TestSceneOutOfBoundsObjects : TestSceneCatchPlayer + { + protected override bool Autoplay => true; + + [Test] + public void TestNoOutOfBoundsObjects() + { + bool anyObjectOutOfBounds = false; + + AddStep("reset flag", () => anyObjectOutOfBounds = false); + + AddUntilStep("check for out-of-bounds objects", + () => + { + anyObjectOutOfBounds |= Player.ChildrenOfType().Any(dho => dho.X < 0 || dho.X > CatchPlayfield.WIDTH); + return Player.ScoreProcessor.HasCompleted.Value; + }); + + AddAssert("no out of bound objects found", () => !anyObjectOutOfBounds); + } + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + Ruleset = ruleset, + }, + HitObjects = new List + { + new Fruit { StartTime = 1000, X = -50 }, + new Fruit { StartTime = 1200, X = CatchPlayfield.WIDTH + 50 }, + new JuiceStream + { + StartTime = 1500, + X = 10, + Path = new SliderPath(PathType.LINEAR, new[] + { + Vector2.Zero, + new Vector2(-200, 0) + }) + }, + new JuiceStream + { + StartTime = 3000, + X = CatchPlayfield.WIDTH - 10, + Path = new SliderPath(PathType.LINEAR, new[] + { + Vector2.Zero, + new Vector2(200, 0) + }) + }, + } + }; + } +} From 2e8b49b93a7ec12ace3ea9e3f0713f8ba20545e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 10:55:29 +0100 Subject: [PATCH 2151/4092] Fix catch drawable objects not being clamped to playfield bounds --- .../Objects/Drawables/DrawablePalpableCatchHitObject.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs index 4a9661f108..ade00918ab 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.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 System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Catch.UI; using osuTK; using osuTK.Graphics; @@ -70,7 +72,10 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables private void updateXPosition(ValueChangedEvent _) { - X = OriginalXBindable.Value + XOffsetBindable.Value; + // same as `CatchHitObject.EffectiveX`. + // not using that property directly to support scenarios where `HitObject` may not necessarily be present + // for this pooled drawable. + X = Math.Clamp(OriginalXBindable.Value + XOffsetBindable.Value, 0, CatchPlayfield.WIDTH); } protected override void OnApply() From 1233533fb967390c2c433ea0f7feb0dadd682635 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Dec 2023 22:14:15 +0900 Subject: [PATCH 2152/4092] 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 cf01f2f99b..b179b8b837 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 c7b9d02b26..7e03ab50e2 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From 14b37db3dd17d3be07a9dce5b088b59205d33815 Mon Sep 17 00:00:00 2001 From: Gabriel Del Nero <43073074+Gabixel@users.noreply.github.com> Date: Wed, 27 Dec 2023 14:35:19 +0100 Subject: [PATCH 2153/4092] Make `ModDaycore` sequential for `ModHalfTime` --- osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs index 59a631a7b5..bf58efc339 100644 --- a/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs +++ b/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Mods.Input { [Key.Q] = new[] { typeof(ModEasy) }, [Key.W] = new[] { typeof(ModNoFail) }, - [Key.E] = new[] { typeof(ModHalfTime) }, + [Key.E] = new[] { typeof(ModHalfTime), typeof(ModDaycore) }, [Key.A] = new[] { typeof(ModHardRock) }, [Key.S] = new[] { typeof(ModSuddenDeath), typeof(ModPerfect) }, [Key.D] = new[] { typeof(ModDoubleTime), typeof(ModNightcore) }, From 13333f75756e587572411971524a65cda09eedab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 15:02:19 +0100 Subject: [PATCH 2154/4092] Make room for `OsuIcon` to accept new icons --- osu.Game/Graphics/OsuIcon.cs | 132 ++++++++++++++++++----------------- 1 file changed, 68 insertions(+), 64 deletions(-) diff --git a/osu.Game/Graphics/OsuIcon.cs b/osu.Game/Graphics/OsuIcon.cs index 15af8f000b..d1ad818300 100644 --- a/osu.Game/Graphics/OsuIcon.cs +++ b/osu.Game/Graphics/OsuIcon.cs @@ -7,90 +7,94 @@ namespace osu.Game.Graphics { public static class OsuIcon { - public static IconUsage Get(int icon) => new IconUsage((char)icon, "osuFont"); + #region Legacy spritesheet-based icons + + private static IconUsage get(int icon) => new IconUsage((char)icon, @"osuFont"); // ruleset icons in circles - public static IconUsage RulesetOsu => Get(0xe000); - public static IconUsage RulesetMania => Get(0xe001); - public static IconUsage RulesetCatch => Get(0xe002); - public static IconUsage RulesetTaiko => Get(0xe003); + public static IconUsage RulesetOsu => get(0xe000); + public static IconUsage RulesetMania => get(0xe001); + public static IconUsage RulesetCatch => get(0xe002); + public static IconUsage RulesetTaiko => get(0xe003); // ruleset icons without circles - public static IconUsage FilledCircle => Get(0xe004); - public static IconUsage CrossCircle => Get(0xe005); - public static IconUsage Logo => Get(0xe006); - public static IconUsage ChevronDownCircle => Get(0xe007); - public static IconUsage EditCircle => Get(0xe033); - public static IconUsage LeftCircle => Get(0xe034); - public static IconUsage RightCircle => Get(0xe035); - public static IconUsage Charts => Get(0xe036); - public static IconUsage Solo => Get(0xe037); - public static IconUsage Multi => Get(0xe038); - public static IconUsage Gear => Get(0xe039); + public static IconUsage FilledCircle => get(0xe004); + public static IconUsage CrossCircle => get(0xe005); + public static IconUsage Logo => get(0xe006); + public static IconUsage ChevronDownCircle => get(0xe007); + public static IconUsage EditCircle => get(0xe033); + public static IconUsage LeftCircle => get(0xe034); + public static IconUsage RightCircle => get(0xe035); + public static IconUsage Charts => get(0xe036); + public static IconUsage Solo => get(0xe037); + public static IconUsage Multi => get(0xe038); + public static IconUsage Gear => get(0xe039); // misc icons - public static IconUsage Bat => Get(0xe008); - public static IconUsage Bubble => Get(0xe009); - public static IconUsage BubblePop => Get(0xe02e); - public static IconUsage Dice => Get(0xe011); - public static IconUsage Heart => Get(0xe02f); - public static IconUsage HeartBreak => Get(0xe030); - public static IconUsage Hot => Get(0xe031); - public static IconUsage ListSearch => Get(0xe032); + public static IconUsage Bat => get(0xe008); + public static IconUsage Bubble => get(0xe009); + public static IconUsage BubblePop => get(0xe02e); + public static IconUsage Dice => get(0xe011); + public static IconUsage Heart => get(0xe02f); + public static IconUsage HeartBreak => get(0xe030); + public static IconUsage Hot => get(0xe031); + public static IconUsage ListSearch => get(0xe032); //osu! playstyles - public static IconUsage PlayStyleTablet => Get(0xe02a); - public static IconUsage PlayStyleMouse => Get(0xe029); - public static IconUsage PlayStyleKeyboard => Get(0xe02b); - public static IconUsage PlayStyleTouch => Get(0xe02c); + public static IconUsage PlayStyleTablet => get(0xe02a); + public static IconUsage PlayStyleMouse => get(0xe029); + public static IconUsage PlayStyleKeyboard => get(0xe02b); + public static IconUsage PlayStyleTouch => get(0xe02c); // osu! difficulties - public static IconUsage EasyOsu => Get(0xe015); - public static IconUsage NormalOsu => Get(0xe016); - public static IconUsage HardOsu => Get(0xe017); - public static IconUsage InsaneOsu => Get(0xe018); - public static IconUsage ExpertOsu => Get(0xe019); + public static IconUsage EasyOsu => get(0xe015); + public static IconUsage NormalOsu => get(0xe016); + public static IconUsage HardOsu => get(0xe017); + public static IconUsage InsaneOsu => get(0xe018); + public static IconUsage ExpertOsu => get(0xe019); // taiko difficulties - public static IconUsage EasyTaiko => Get(0xe01a); - public static IconUsage NormalTaiko => Get(0xe01b); - public static IconUsage HardTaiko => Get(0xe01c); - public static IconUsage InsaneTaiko => Get(0xe01d); - public static IconUsage ExpertTaiko => Get(0xe01e); + public static IconUsage EasyTaiko => get(0xe01a); + public static IconUsage NormalTaiko => get(0xe01b); + public static IconUsage HardTaiko => get(0xe01c); + public static IconUsage InsaneTaiko => get(0xe01d); + public static IconUsage ExpertTaiko => get(0xe01e); // fruits difficulties - public static IconUsage EasyFruits => Get(0xe01f); - public static IconUsage NormalFruits => Get(0xe020); - public static IconUsage HardFruits => Get(0xe021); - public static IconUsage InsaneFruits => Get(0xe022); - public static IconUsage ExpertFruits => Get(0xe023); + public static IconUsage EasyFruits => get(0xe01f); + public static IconUsage NormalFruits => get(0xe020); + public static IconUsage HardFruits => get(0xe021); + public static IconUsage InsaneFruits => get(0xe022); + public static IconUsage ExpertFruits => get(0xe023); // mania difficulties - public static IconUsage EasyMania => Get(0xe024); - public static IconUsage NormalMania => Get(0xe025); - public static IconUsage HardMania => Get(0xe026); - public static IconUsage InsaneMania => Get(0xe027); - public static IconUsage ExpertMania => Get(0xe028); + public static IconUsage EasyMania => get(0xe024); + public static IconUsage NormalMania => get(0xe025); + public static IconUsage HardMania => get(0xe026); + public static IconUsage InsaneMania => get(0xe027); + public static IconUsage ExpertMania => get(0xe028); // mod icons - public static IconUsage ModPerfect => Get(0xe049); - public static IconUsage ModAutopilot => Get(0xe03a); - public static IconUsage ModAuto => Get(0xe03b); - public static IconUsage ModCinema => Get(0xe03c); - public static IconUsage ModDoubleTime => Get(0xe03d); - public static IconUsage ModEasy => Get(0xe03e); - public static IconUsage ModFlashlight => Get(0xe03f); - public static IconUsage ModHalftime => Get(0xe040); - public static IconUsage ModHardRock => Get(0xe041); - public static IconUsage ModHidden => Get(0xe042); - public static IconUsage ModNightcore => Get(0xe043); - public static IconUsage ModNoFail => Get(0xe044); - public static IconUsage ModRelax => Get(0xe045); - public static IconUsage ModSpunOut => Get(0xe046); - public static IconUsage ModSuddenDeath => Get(0xe047); - public static IconUsage ModTarget => Get(0xe048); + public static IconUsage ModPerfect => get(0xe049); + public static IconUsage ModAutopilot => get(0xe03a); + public static IconUsage ModAuto => get(0xe03b); + public static IconUsage ModCinema => get(0xe03c); + public static IconUsage ModDoubleTime => get(0xe03d); + public static IconUsage ModEasy => get(0xe03e); + public static IconUsage ModFlashlight => get(0xe03f); + public static IconUsage ModHalftime => get(0xe040); + public static IconUsage ModHardRock => get(0xe041); + public static IconUsage ModHidden => get(0xe042); + public static IconUsage ModNightcore => get(0xe043); + public static IconUsage ModNoFail => get(0xe044); + public static IconUsage ModRelax => get(0xe045); + public static IconUsage ModSpunOut => get(0xe046); + public static IconUsage ModSuddenDeath => get(0xe047); + public static IconUsage ModTarget => get(0xe048); // Use "Icons/BeatmapDetails/mod-icon" instead // public static IconUsage ModBg => Get(0xe04a); + + #endregion } } From c9de6a383d71829eb3ec23c4a7571b72480459c1 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Wed, 27 Dec 2023 16:21:54 +0100 Subject: [PATCH 2155/4092] Add settings checkbox for `MinimiseOnFocusLossInFullscreen` Shown only on desktop platforms in fullscreen. "alt-tab" keyword also matches "alt" and "tab". --- osu.Game/Localisation/GraphicsSettingsStrings.cs | 5 +++++ .../Overlays/Settings/Sections/Graphics/LayoutSettings.cs | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/osu.Game/Localisation/GraphicsSettingsStrings.cs b/osu.Game/Localisation/GraphicsSettingsStrings.cs index 422704514f..fc29352ae2 100644 --- a/osu.Game/Localisation/GraphicsSettingsStrings.cs +++ b/osu.Game/Localisation/GraphicsSettingsStrings.cs @@ -155,6 +155,11 @@ namespace osu.Game.Localisation public static LocalisableString ChangeRendererConfirmation => new TranslatableString(getKey(@"change_renderer_configuration"), @"In order to change the renderer, the game will close. Please open it again."); + /// + /// "Minimise on focus loss" + /// + public static LocalisableString MinimiseOnFocusLoss => new TranslatableString(getKey(@"minimise_on_focus_loss"), @"Minimise on focus loss"); + private static string getKey(string key) => $"{prefix}:{key}"; } } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index a3290bc81c..adb572e245 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -51,6 +51,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics private SettingsDropdown resolutionDropdown = null!; private SettingsDropdown displayDropdown = null!; private SettingsDropdown windowModeDropdown = null!; + private SettingsCheckbox minimiseOnFocusLossCheckbox = null!; private SettingsCheckbox safeAreaConsiderationsCheckbox = null!; private Bindable scalingPositionX = null!; @@ -106,6 +107,12 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics ItemSource = resolutions, Current = sizeFullscreen }, + minimiseOnFocusLossCheckbox = new SettingsCheckbox + { + LabelText = GraphicsSettingsStrings.MinimiseOnFocusLoss, + Current = config.GetBindable(FrameworkSetting.MinimiseOnFocusLossInFullscreen), + Keywords = new[] { "alt-tab" }, + }, safeAreaConsiderationsCheckbox = new SettingsCheckbox { LabelText = "Shrink game to avoid cameras and notches", @@ -255,6 +262,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { resolutionDropdown.CanBeShown.Value = resolutions.Count > 1 && windowModeDropdown.Current.Value == WindowMode.Fullscreen; displayDropdown.CanBeShown.Value = displayDropdown.Items.Count() > 1; + minimiseOnFocusLossCheckbox.CanBeShown.Value = RuntimeInfo.IsDesktop && windowModeDropdown.Current.Value == WindowMode.Fullscreen; safeAreaConsiderationsCheckbox.CanBeShown.Value = host.Window?.SafeAreaPadding.Value.Total != Vector2.Zero; } From 0fde9cd6ae676c3bef39cd34182fbe0cfe338526 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Wed, 27 Dec 2023 16:40:52 +0100 Subject: [PATCH 2156/4092] Override confine mouse mode only when clicking outside the window would minimise it --- osu.Game/Input/ConfineMouseTracker.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Input/ConfineMouseTracker.cs b/osu.Game/Input/ConfineMouseTracker.cs index de8660dbce..926f68df45 100644 --- a/osu.Game/Input/ConfineMouseTracker.cs +++ b/osu.Game/Input/ConfineMouseTracker.cs @@ -22,6 +22,7 @@ namespace osu.Game.Input { private Bindable frameworkConfineMode; private Bindable frameworkWindowMode; + private Bindable frameworkMinimiseOnFocusLossInFullscreen; private Bindable osuConfineMode; private IBindable localUserPlaying; @@ -31,7 +32,9 @@ namespace osu.Game.Input { frameworkConfineMode = frameworkConfigManager.GetBindable(FrameworkSetting.ConfineMouseMode); frameworkWindowMode = frameworkConfigManager.GetBindable(FrameworkSetting.WindowMode); + frameworkMinimiseOnFocusLossInFullscreen = frameworkConfigManager.GetBindable(FrameworkSetting.MinimiseOnFocusLossInFullscreen); frameworkWindowMode.BindValueChanged(_ => updateConfineMode()); + frameworkMinimiseOnFocusLossInFullscreen.BindValueChanged(_ => updateConfineMode()); osuConfineMode = osuConfigManager.GetBindable(OsuSetting.ConfineMouseMode); localUserPlaying = localUserInfo.IsPlaying.GetBoundCopy(); @@ -46,7 +49,8 @@ namespace osu.Game.Input if (frameworkConfineMode.Disabled) return; - if (frameworkWindowMode.Value == WindowMode.Fullscreen) + // override confine mode only when clicking outside the window minimises it. + if (frameworkWindowMode.Value == WindowMode.Fullscreen && frameworkMinimiseOnFocusLossInFullscreen.Value) { frameworkConfineMode.Value = ConfineMouseMode.Fullscreen; return; From a6bed04e50b6bcc7845ca85157bb2c6672c991d5 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Wed, 27 Dec 2023 16:54:01 +0100 Subject: [PATCH 2157/4092] Update confine mode settings checkbox to match the new `ConfineMouseTracker` logic Not too happy with the duplicated logic, but settings can't depend on `ConfineMouseTracker` for testability reasons. --- .../Settings/Sections/Input/MouseSettings.cs | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index 6bf06f4f98..7805ed5834 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -28,6 +28,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input private Bindable localSensitivity; private Bindable windowMode; + private Bindable minimiseOnFocusLoss; private SettingsEnumDropdown confineMouseModeSetting; private Bindable relativeMode; @@ -47,6 +48,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input relativeMode = mouseHandler.UseRelativeMode.GetBoundCopy(); windowMode = config.GetBindable(FrameworkSetting.WindowMode); + minimiseOnFocusLoss = config.GetBindable(FrameworkSetting.MinimiseOnFocusLossInFullscreen); Children = new Drawable[] { @@ -98,21 +100,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input localSensitivity.BindValueChanged(val => handlerSensitivity.Value = val.NewValue); - windowMode.BindValueChanged(mode => - { - bool isFullscreen = mode.NewValue == WindowMode.Fullscreen; - - if (isFullscreen) - { - confineMouseModeSetting.Current.Disabled = true; - confineMouseModeSetting.TooltipText = MouseSettingsStrings.NotApplicableFullscreen; - } - else - { - confineMouseModeSetting.Current.Disabled = false; - confineMouseModeSetting.TooltipText = string.Empty; - } - }, true); + windowMode.BindValueChanged(_ => updateConfineMouseModeSettingVisibility()); + minimiseOnFocusLoss.BindValueChanged(_ => updateConfineMouseModeSettingVisibility(), true); highPrecisionMouse.Current.BindValueChanged(highPrecision => { @@ -126,6 +115,25 @@ namespace osu.Game.Overlays.Settings.Sections.Input }, true); } + /// + /// Updates disabled state and tooltip of to match when is overriding the confine mode. + /// + private void updateConfineMouseModeSettingVisibility() + { + bool confineModeOverriden = windowMode.Value == WindowMode.Fullscreen && minimiseOnFocusLoss.Value; + + if (confineModeOverriden) + { + confineMouseModeSetting.Current.Disabled = true; + confineMouseModeSetting.TooltipText = MouseSettingsStrings.NotApplicableFullscreen; + } + else + { + confineMouseModeSetting.Current.Disabled = false; + confineMouseModeSetting.TooltipText = string.Empty; + } + } + public partial class SensitivitySetting : SettingsSlider { public SensitivitySetting() From 45143a6c17e4574aab8a910a7be27e568f91c34a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 15:17:39 +0100 Subject: [PATCH 2158/4092] Implement new icon store --- osu.Game/Graphics/OsuIcon.cs | 348 ++++++++++++++++++++++++++++++++++- osu.Game/OsuGameBase.cs | 1 + 2 files changed, 347 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/OsuIcon.cs b/osu.Game/Graphics/OsuIcon.cs index d1ad818300..3cd10b1315 100644 --- a/osu.Game/Graphics/OsuIcon.cs +++ b/osu.Game/Graphics/OsuIcon.cs @@ -1,7 +1,16 @@ // 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.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using osu.Framework.Extensions; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Text; namespace osu.Game.Graphics { @@ -19,7 +28,6 @@ namespace osu.Game.Graphics // ruleset icons without circles public static IconUsage FilledCircle => get(0xe004); - public static IconUsage CrossCircle => get(0xe005); public static IconUsage Logo => get(0xe006); public static IconUsage ChevronDownCircle => get(0xe007); public static IconUsage EditCircle => get(0xe033); @@ -35,7 +43,6 @@ namespace osu.Game.Graphics public static IconUsage Bubble => get(0xe009); public static IconUsage BubblePop => get(0xe02e); public static IconUsage Dice => get(0xe011); - public static IconUsage Heart => get(0xe02f); public static IconUsage HeartBreak => get(0xe030); public static IconUsage Hot => get(0xe031); public static IconUsage ListSearch => get(0xe032); @@ -96,5 +103,342 @@ namespace osu.Game.Graphics // public static IconUsage ModBg => Get(0xe04a); #endregion + + #region New single-file-based icons + + public const string FONT_NAME = @"Icons"; + + public static IconUsage Audio => get(OsuIconMapping.Audio); + public static IconUsage Beatmap => get(OsuIconMapping.Beatmap); + public static IconUsage Calendar => get(OsuIconMapping.Calendar); + public static IconUsage ChangelogA => get(OsuIconMapping.ChangelogA); + public static IconUsage ChangelogB => get(OsuIconMapping.ChangelogB); + public static IconUsage Chat => get(OsuIconMapping.Chat); + public static IconUsage CheckCircle => get(OsuIconMapping.CheckCircle); + public static IconUsage CollapseA => get(OsuIconMapping.CollapseA); + public static IconUsage Collections => get(OsuIconMapping.Collections); + public static IconUsage Cross => get(OsuIconMapping.Cross); + public static IconUsage CrossCircle => get(OsuIconMapping.CrossCircle); + public static IconUsage Crown => get(OsuIconMapping.Crown); + public static IconUsage Debug => get(OsuIconMapping.Debug); + public static IconUsage Delete => get(OsuIconMapping.Delete); + public static IconUsage Details => get(OsuIconMapping.Details); + public static IconUsage Discord => get(OsuIconMapping.Discord); + public static IconUsage EllipsisHorizontal => get(OsuIconMapping.EllipsisHorizontal); + public static IconUsage EllipsisVertical => get(OsuIconMapping.EllipsisVertical); + public static IconUsage ExpandA => get(OsuIconMapping.ExpandA); + public static IconUsage ExpandB => get(OsuIconMapping.ExpandB); + public static IconUsage FeaturedArtist => get(OsuIconMapping.FeaturedArtist); + public static IconUsage FeaturedArtistCircle => get(OsuIconMapping.FeaturedArtistCircle); + public static IconUsage GameplayA => get(OsuIconMapping.GameplayA); + public static IconUsage GameplayB => get(OsuIconMapping.GameplayB); + public static IconUsage GameplayC => get(OsuIconMapping.GameplayC); + public static IconUsage Global => get(OsuIconMapping.Global); + public static IconUsage Graphics => get(OsuIconMapping.Graphics); + public static IconUsage Heart => get(OsuIconMapping.Heart); + public static IconUsage Home => get(OsuIconMapping.Home); + public static IconUsage Input => get(OsuIconMapping.Input); + public static IconUsage Maintenance => get(OsuIconMapping.Maintenance); + public static IconUsage Megaphone => get(OsuIconMapping.Megaphone); + public static IconUsage Music => get(OsuIconMapping.Music); + public static IconUsage News => get(OsuIconMapping.News); + public static IconUsage Next => get(OsuIconMapping.Next); + public static IconUsage NextCircle => get(OsuIconMapping.NextCircle); + public static IconUsage Notification => get(OsuIconMapping.Notification); + public static IconUsage Online => get(OsuIconMapping.Online); + public static IconUsage Play => get(OsuIconMapping.Play); + public static IconUsage Player => get(OsuIconMapping.Player); + public static IconUsage PlayerFollow => get(OsuIconMapping.PlayerFollow); + public static IconUsage Prev => get(OsuIconMapping.Prev); + public static IconUsage PrevCircle => get(OsuIconMapping.PrevCircle); + public static IconUsage Ranking => get(OsuIconMapping.Ranking); + public static IconUsage Rulesets => get(OsuIconMapping.Rulesets); + public static IconUsage Search => get(OsuIconMapping.Search); + public static IconUsage Settings => get(OsuIconMapping.Settings); + public static IconUsage SkinA => get(OsuIconMapping.SkinA); + public static IconUsage SkinB => get(OsuIconMapping.SkinB); + public static IconUsage Star => get(OsuIconMapping.Star); + public static IconUsage Storyboard => get(OsuIconMapping.Storyboard); + public static IconUsage Team => get(OsuIconMapping.Team); + public static IconUsage ThumbsUp => get(OsuIconMapping.ThumbsUp); + public static IconUsage Tournament => get(OsuIconMapping.Tournament); + public static IconUsage Twitter => get(OsuIconMapping.Twitter); + public static IconUsage UserInterface => get(OsuIconMapping.UserInterface); + public static IconUsage Wiki => get(OsuIconMapping.Wiki); + public static IconUsage EditorAddControlPoint => get(OsuIconMapping.EditorAddControlPoint); + public static IconUsage EditorConvertToStream => get(OsuIconMapping.EditorConvertToStream); + public static IconUsage EditorDistanceSnap => get(OsuIconMapping.EditorDistanceSnap); + public static IconUsage EditorFinish => get(OsuIconMapping.EditorFinish); + public static IconUsage EditorGridSnap => get(OsuIconMapping.EditorGridSnap); + public static IconUsage EditorNewComboA => get(OsuIconMapping.EditorNewComboA); + public static IconUsage EditorNewComboB => get(OsuIconMapping.EditorNewComboB); + public static IconUsage EditorSelect => get(OsuIconMapping.EditorSelect); + public static IconUsage EditorSound => get(OsuIconMapping.EditorSound); + public static IconUsage EditorWhistle => get(OsuIconMapping.EditorWhistle); + + private static IconUsage get(OsuIconMapping glyph) => new IconUsage((char)glyph, FONT_NAME); + + private enum OsuIconMapping + { + [Description(@"audio")] + Audio, + + [Description(@"beatmap")] + Beatmap, + + [Description(@"calendar")] + Calendar, + + [Description(@"changelog-a")] + ChangelogA, + + [Description(@"changelog-b")] + ChangelogB, + + [Description(@"chat")] + Chat, + + [Description(@"check-circle")] + CheckCircle, + + [Description(@"collapse-a")] + CollapseA, + + [Description(@"collections")] + Collections, + + [Description(@"cross")] + Cross, + + [Description(@"cross-circle")] + CrossCircle, + + [Description(@"crown")] + Crown, + + [Description(@"debug")] + Debug, + + [Description(@"delete")] + Delete, + + [Description(@"details")] + Details, + + [Description(@"discord")] + Discord, + + [Description(@"ellipsis-horizontal")] + EllipsisHorizontal, + + [Description(@"ellipsis-vertical")] + EllipsisVertical, + + [Description(@"expand-a")] + ExpandA, + + [Description(@"expand-b")] + ExpandB, + + [Description(@"featured-artist")] + FeaturedArtist, + + [Description(@"featured-artist-circle")] + FeaturedArtistCircle, + + [Description(@"gameplay-a")] + GameplayA, + + [Description(@"gameplay-b")] + GameplayB, + + [Description(@"gameplay-c")] + GameplayC, + + [Description(@"global")] + Global, + + [Description(@"graphics")] + Graphics, + + [Description(@"heart")] + Heart, + + [Description(@"home")] + Home, + + [Description(@"input")] + Input, + + [Description(@"maintenance")] + Maintenance, + + [Description(@"megaphone")] + Megaphone, + + [Description(@"music")] + Music, + + [Description(@"news")] + News, + + [Description(@"next")] + Next, + + [Description(@"next-circle")] + NextCircle, + + [Description(@"notification")] + Notification, + + [Description(@"online")] + Online, + + [Description(@"play")] + Play, + + [Description(@"player")] + Player, + + [Description(@"player-follow")] + PlayerFollow, + + [Description(@"prev")] + Prev, + + [Description(@"prev-circle")] + PrevCircle, + + [Description(@"ranking")] + Ranking, + + [Description(@"rulesets")] + Rulesets, + + [Description(@"search")] + Search, + + [Description(@"settings")] + Settings, + + [Description(@"skin-a")] + SkinA, + + [Description(@"skin-b")] + SkinB, + + [Description(@"star")] + Star, + + [Description(@"storyboard")] + Storyboard, + + [Description(@"team")] + Team, + + [Description(@"thumbs-up")] + ThumbsUp, + + [Description(@"tournament")] + Tournament, + + [Description(@"twitter")] + Twitter, + + [Description(@"user-interface")] + UserInterface, + + [Description(@"wiki")] + Wiki, + + [Description(@"Editor/add-control-point")] + EditorAddControlPoint = 1000, + + [Description(@"Editor/convert-to-stream")] + EditorConvertToStream, + + [Description(@"Editor/distance-snap")] + EditorDistanceSnap, + + [Description(@"Editor/finish")] + EditorFinish, + + [Description(@"Editor/grid-snap")] + EditorGridSnap, + + [Description(@"Editor/new-combo-a")] + EditorNewComboA, + + [Description(@"Editor/new-combo-b")] + EditorNewComboB, + + [Description(@"Editor/select")] + EditorSelect, + + [Description(@"Editor/sound")] + EditorSound, + + [Description(@"Editor/whistle")] + EditorWhistle, + } + + public class OsuIconStore : ITextureStore, ITexturedGlyphLookupStore + { + private readonly TextureStore textures; + + public OsuIconStore(TextureStore textures) + { + this.textures = textures; + } + + public ITexturedCharacterGlyph? Get(string? fontName, char character) + { + if (fontName == FONT_NAME) + return new Glyph(textures.Get($@"{fontName}/{((OsuIconMapping)character).GetDescription()}")); + + return null; + } + + public Task GetAsync(string fontName, char character) => Task.Run(() => Get(fontName, character)); + + public Texture? Get(string name, WrapMode wrapModeS, WrapMode wrapModeT) => null; + + public Texture Get(string name) => throw new NotImplementedException(); + + public Task GetAsync(string name, CancellationToken cancellationToken = default) => throw new NotImplementedException(); + + public Stream GetStream(string name) => throw new NotImplementedException(); + + public IEnumerable GetAvailableResources() => throw new NotImplementedException(); + + public Task GetAsync(string name, WrapMode wrapModeS, WrapMode wrapModeT, CancellationToken cancellationToken = default) => throw new NotImplementedException(); + + public class Glyph : ITexturedCharacterGlyph + { + public float XOffset => default; + public float YOffset => default; + public float XAdvance => default; + public float Baseline => default; + public char Character => default; + + public float GetKerning(T lastGlyph) where T : ICharacterGlyph => throw new NotImplementedException(); + + public Texture Texture { get; } + public float Width => Texture.Width; + public float Height => Texture.Height; + + public Glyph(Texture texture) + { + Texture = texture; + } + } + + public void Dispose() + { + textures.Dispose(); + } + } + + #endregion } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 48548dc1ef..b4ad21f045 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -479,6 +479,7 @@ namespace osu.Game AddFont(Resources, @"Fonts/Venera/Venera-Black"); Fonts.AddStore(new HexaconsIcons.HexaconsStore(Textures)); + Fonts.AddStore(new OsuIcon.OsuIconStore(Textures)); } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => From 69baabee627f55d87f6ba61f361e1b370bbfdd2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 15:35:03 +0100 Subject: [PATCH 2159/4092] Replace hexacons in toolbar with new icons --- osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs | 2 +- osu.Game/Overlays/Changelog/ChangelogHeader.cs | 2 +- osu.Game/Overlays/ChatOverlay.cs | 2 +- osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs | 2 +- osu.Game/Overlays/News/NewsHeader.cs | 2 +- osu.Game/Overlays/NotificationOverlay.cs | 2 +- osu.Game/Overlays/NowPlayingOverlay.cs | 2 +- osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs | 2 +- osu.Game/Overlays/SettingsOverlay.cs | 2 +- osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs | 2 +- osu.Game/Overlays/Wiki/WikiHeader.cs | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs index 27fab82bf3..075dfd02b0 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs @@ -24,7 +24,7 @@ namespace osu.Game.Overlays.BeatmapListing { Title = PageTitleStrings.MainBeatmapsetsControllerIndex; Description = NamedOverlayComponentStrings.BeatmapListingDescription; - Icon = HexaconsIcons.Beatmap; + Icon = OsuIcon.Beatmap; } } } diff --git a/osu.Game/Overlays/Changelog/ChangelogHeader.cs b/osu.Game/Overlays/Changelog/ChangelogHeader.cs index 61ea9dc4db..f738d70370 100644 --- a/osu.Game/Overlays/Changelog/ChangelogHeader.cs +++ b/osu.Game/Overlays/Changelog/ChangelogHeader.cs @@ -124,7 +124,7 @@ namespace osu.Game.Overlays.Changelog { Title = PageTitleStrings.MainChangelogControllerDefault; Description = NamedOverlayComponentStrings.ChangelogDescription; - Icon = HexaconsIcons.Devtools; + Icon = OsuIcon.ChangelogB; } } } diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 4aa4c59471..8f3b7031c2 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -31,7 +31,7 @@ namespace osu.Game.Overlays { public partial class ChatOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent, IKeyBindingHandler { - public IconUsage Icon => HexaconsIcons.Messaging; + public IconUsage Icon => OsuIcon.Chat; public LocalisableString Title => ChatStrings.HeaderTitle; public LocalisableString Description => ChatStrings.HeaderDescription; diff --git a/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs index 104f0943dc..8fd8f6b332 100644 --- a/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs +++ b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Dashboard { Title = PageTitleStrings.MainHomeControllerIndex; Description = NamedOverlayComponentStrings.DashboardDescription; - Icon = HexaconsIcons.Social; + Icon = OsuIcon.Global; } } } diff --git a/osu.Game/Overlays/News/NewsHeader.cs b/osu.Game/Overlays/News/NewsHeader.cs index f237ed66f2..92d71a21ef 100644 --- a/osu.Game/Overlays/News/NewsHeader.cs +++ b/osu.Game/Overlays/News/NewsHeader.cs @@ -69,7 +69,7 @@ namespace osu.Game.Overlays.News { Title = PageTitleStrings.MainNewsControllerDefault; Description = NamedOverlayComponentStrings.NewsDescription; - Icon = HexaconsIcons.News; + Icon = OsuIcon.News; } } } diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index f56d09f2b2..18a487a312 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -29,7 +29,7 @@ namespace osu.Game.Overlays { public partial class NotificationOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent, INotificationOverlay { - public IconUsage Icon => HexaconsIcons.Notification; + public IconUsage Icon => OsuIcon.Notification; public LocalisableString Title => NotificationsStrings.HeaderTitle; public LocalisableString Description => NotificationsStrings.HeaderDescription; diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 425ff0935d..7ec52364d4 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -29,7 +29,7 @@ namespace osu.Game.Overlays { public partial class NowPlayingOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent { - public IconUsage Icon => HexaconsIcons.Music; + public IconUsage Icon => OsuIcon.Music; public LocalisableString Title => NowPlayingStrings.HeaderTitle; public LocalisableString Description => NowPlayingStrings.HeaderDescription; diff --git a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs index 63128fb73d..a23ec18afe 100644 --- a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs +++ b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs @@ -36,7 +36,7 @@ namespace osu.Game.Overlays.Rankings { Title = PageTitleStrings.MainRankingControllerDefault; Description = NamedOverlayComponentStrings.RankingsDescription; - Icon = HexaconsIcons.Rankings; + Icon = OsuIcon.Ranking; } } } diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs index 5735b1515a..9efd848035 100644 --- a/osu.Game/Overlays/SettingsOverlay.cs +++ b/osu.Game/Overlays/SettingsOverlay.cs @@ -21,7 +21,7 @@ namespace osu.Game.Overlays { public partial class SettingsOverlay : SettingsPanel, INamedOverlayComponent { - public IconUsage Icon => HexaconsIcons.Settings; + public IconUsage Icon => OsuIcon.Settings; public LocalisableString Title => SettingsStrings.HeaderTitle; public LocalisableString Description => SettingsStrings.HeaderDescription; diff --git a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs index ded0229d67..70675c1b92 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs @@ -21,7 +21,7 @@ namespace osu.Game.Overlays.Toolbar { TooltipMain = ToolbarStrings.HomeHeaderTitle; TooltipSub = ToolbarStrings.HomeHeaderDescription; - SetIcon(HexaconsIcons.Home); + SetIcon(OsuIcon.Home); } } } diff --git a/osu.Game/Overlays/Wiki/WikiHeader.cs b/osu.Game/Overlays/Wiki/WikiHeader.cs index 9e9e565684..24eddeb0c2 100644 --- a/osu.Game/Overlays/Wiki/WikiHeader.cs +++ b/osu.Game/Overlays/Wiki/WikiHeader.cs @@ -82,7 +82,7 @@ namespace osu.Game.Overlays.Wiki { Title = PageTitleStrings.MainWikiControllerDefault; Description = NamedOverlayComponentStrings.WikiDescription; - Icon = HexaconsIcons.Wiki; + Icon = OsuIcon.Wiki; } } } From 28d9145a4c182ecb66029e0b1e52f24f7cc16acf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 15:47:29 +0100 Subject: [PATCH 2160/4092] Add more spacing to toolbar icons --- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 81d2de5acb..a547fda814 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -113,7 +113,7 @@ namespace osu.Game.Overlays.Toolbar { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Size = new Vector2(26), + Size = new Vector2(20), Alpha = 0, }, DrawableText = new OsuSpriteText From c45477bd1fff8447755f7afd54bf5156e1352833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 15:40:48 +0100 Subject: [PATCH 2161/4092] Use new icons in main menu wherever feasible --- osu.Game/Screens/Menu/ButtonSystem.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index ca5bef985e..b2b3fbd626 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -13,7 +13,6 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Logging; @@ -103,8 +102,8 @@ namespace osu.Game.Screens.Menu buttonArea.AddRange(new Drawable[] { - new MainMenuButton(ButtonSystemStrings.Settings, string.Empty, FontAwesome.Solid.Cog, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O), - backButton = new MainMenuButton(ButtonSystemStrings.Back, @"back-to-top", OsuIcon.LeftCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, + new MainMenuButton(ButtonSystemStrings.Settings, string.Empty, OsuIcon.Settings, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O), + backButton = new MainMenuButton(ButtonSystemStrings.Back, @"back-to-top", OsuIcon.PrevCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, -WEDGE_WIDTH) { VisibleStateMin = ButtonSystemState.Play, @@ -128,18 +127,18 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader] private void load(AudioManager audio, IdleTracker? idleTracker, GameHost host) { - buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Solo, @"button-default-select", FontAwesome.Solid.User, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P)); - buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Multi, @"button-default-select", FontAwesome.Solid.Users, new Color4(94, 63, 186, 255), onMultiplayer, 0, Key.M)); - buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Playlists, @"button-default-select", OsuIcon.Charts, new Color4(94, 63, 186, 255), onPlaylists, 0, Key.L)); + buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Solo, @"button-default-select", OsuIcon.Player, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P)); + buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Multi, @"button-default-select", OsuIcon.Online, new Color4(94, 63, 186, 255), onMultiplayer, 0, Key.M)); + buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Playlists, @"button-default-select", OsuIcon.Tournament, new Color4(94, 63, 186, 255), onPlaylists, 0, Key.L)); buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play); - buttonsEdit.Add(new MainMenuButton(EditorStrings.BeatmapEditor.ToLower(), @"button-default-select", HexaconsIcons.Beatmap, new Color4(238, 170, 0, 255), () => OnEditBeatmap?.Invoke(), WEDGE_WIDTH, Key.B)); - buttonsEdit.Add(new MainMenuButton(SkinEditorStrings.SkinEditor.ToLower(), @"button-default-select", HexaconsIcons.Editor, new Color4(220, 160, 0, 255), () => OnEditSkin?.Invoke(), 0, Key.S)); + buttonsEdit.Add(new MainMenuButton(EditorStrings.BeatmapEditor.ToLower(), @"button-default-select", OsuIcon.Beatmap, new Color4(238, 170, 0, 255), () => OnEditBeatmap?.Invoke(), WEDGE_WIDTH, Key.B)); + buttonsEdit.Add(new MainMenuButton(SkinEditorStrings.SkinEditor.ToLower(), @"button-default-select", OsuIcon.SkinB, new Color4(220, 160, 0, 255), () => OnEditSkin?.Invoke(), 0, Key.S)); buttonsEdit.ForEach(b => b.VisibleState = ButtonSystemState.Edit); buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Play, @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P)); buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Edit, @"button-play-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => State = ButtonSystemState.Edit, 0, Key.E)); - buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Browse, @"button-default-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.B, Key.D)); + buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Browse, @"button-default-select", OsuIcon.Beatmap, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.B, Key.D)); if (host.CanExit) buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Exit, string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), () => OnExit?.Invoke(), 0, Key.Q)); From 2857322a8bc3257ae26ea35567b8f1741b7a45ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 15:53:01 +0100 Subject: [PATCH 2162/4092] Use new icons in settings --- osu.Game/Overlays/Settings/Sections/AudioSection.cs | 3 ++- osu.Game/Overlays/Settings/Sections/DebugSection.cs | 3 ++- osu.Game/Overlays/Settings/Sections/GameplaySection.cs | 3 ++- osu.Game/Overlays/Settings/Sections/GeneralSection.cs | 2 +- osu.Game/Overlays/Settings/Sections/GraphicsSection.cs | 3 ++- osu.Game/Overlays/Settings/Sections/InputSection.cs | 3 ++- osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs | 3 ++- osu.Game/Overlays/Settings/Sections/OnlineSection.cs | 3 ++- osu.Game/Overlays/Settings/Sections/RulesetSection.cs | 3 ++- osu.Game/Overlays/Settings/Sections/SkinSection.cs | 3 ++- osu.Game/Overlays/Settings/Sections/UserInterfaceSection.cs | 3 ++- 11 files changed, 21 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/AudioSection.cs b/osu.Game/Overlays/Settings/Sections/AudioSection.cs index fb3d486776..1ab0d6c886 100644 --- a/osu.Game/Overlays/Settings/Sections/AudioSection.cs +++ b/osu.Game/Overlays/Settings/Sections/AudioSection.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Localisation; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; using osu.Game.Localisation; using osu.Game.Overlays.Settings.Sections.Audio; @@ -17,7 +18,7 @@ namespace osu.Game.Overlays.Settings.Sections public override Drawable CreateIcon() => new SpriteIcon { - Icon = FontAwesome.Solid.VolumeUp + Icon = OsuIcon.Audio }; public override IEnumerable FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { "sound" }); diff --git a/osu.Game/Overlays/Settings/Sections/DebugSection.cs b/osu.Game/Overlays/Settings/Sections/DebugSection.cs index 33a6f4c673..b84c441057 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSection.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSection.cs @@ -5,6 +5,7 @@ using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Game.Graphics; using osu.Game.Localisation; using osu.Game.Overlays.Settings.Sections.DebugSettings; @@ -16,7 +17,7 @@ namespace osu.Game.Overlays.Settings.Sections public override Drawable CreateIcon() => new SpriteIcon { - Icon = FontAwesome.Solid.Bug + Icon = OsuIcon.Debug }; public DebugSection() diff --git a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs index b60689b611..463b3d1d09 100644 --- a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs +++ b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs @@ -4,6 +4,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Game.Graphics; using osu.Game.Localisation; using osu.Game.Overlays.Settings.Sections.Gameplay; @@ -15,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections public override Drawable CreateIcon() => new SpriteIcon { - Icon = FontAwesome.Regular.DotCircle + Icon = OsuIcon.GameplayC }; public GameplaySection() diff --git a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs index 2b043d40bc..2aa1008b1d 100644 --- a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs +++ b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs @@ -23,7 +23,7 @@ namespace osu.Game.Overlays.Settings.Sections public override Drawable CreateIcon() => new SpriteIcon { - Icon = FontAwesome.Solid.Cog + Icon = OsuIcon.Settings }; [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs b/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs index 98f6908512..e1fa1eef9c 100644 --- a/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs +++ b/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs @@ -4,6 +4,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Game.Graphics; using osu.Game.Localisation; using osu.Game.Overlays.Settings.Sections.Graphics; @@ -15,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections public override Drawable CreateIcon() => new SpriteIcon { - Icon = FontAwesome.Solid.Laptop + Icon = OsuIcon.Graphics }; public GraphicsSection() diff --git a/osu.Game/Overlays/Settings/Sections/InputSection.cs b/osu.Game/Overlays/Settings/Sections/InputSection.cs index a8f19cc91d..0204aa5e64 100644 --- a/osu.Game/Overlays/Settings/Sections/InputSection.cs +++ b/osu.Game/Overlays/Settings/Sections/InputSection.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Handlers; using osu.Framework.Localisation; using osu.Framework.Platform; +using osu.Game.Graphics; using osu.Game.Localisation; using osu.Game.Overlays.Settings.Sections.Input; @@ -20,7 +21,7 @@ namespace osu.Game.Overlays.Settings.Sections public override Drawable CreateIcon() => new SpriteIcon { - Icon = FontAwesome.Solid.Keyboard + Icon = OsuIcon.Input }; public InputSection(KeyBindingPanel keyConfig) diff --git a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs index bb0a952164..bd90e4c35d 100644 --- a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs +++ b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs @@ -4,6 +4,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Game.Graphics; using osu.Game.Localisation; using osu.Game.Overlays.Settings.Sections.Maintenance; @@ -15,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections public override Drawable CreateIcon() => new SpriteIcon { - Icon = FontAwesome.Solid.Wrench + Icon = OsuIcon.Maintenance }; public MaintenanceSection() diff --git a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs index c8faa3b697..1484f2c756 100644 --- a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs +++ b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs @@ -4,6 +4,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Game.Graphics; using osu.Game.Localisation; using osu.Game.Overlays.Settings.Sections.Online; @@ -15,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections public override Drawable CreateIcon() => new SpriteIcon { - Icon = FontAwesome.Solid.GlobeAsia + Icon = OsuIcon.Online }; public OnlineSection() diff --git a/osu.Game/Overlays/Settings/Sections/RulesetSection.cs b/osu.Game/Overlays/Settings/Sections/RulesetSection.cs index aaad1ec4e2..626264151f 100644 --- a/osu.Game/Overlays/Settings/Sections/RulesetSection.cs +++ b/osu.Game/Overlays/Settings/Sections/RulesetSection.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Framework.Logging; +using osu.Game.Graphics; using osu.Game.Localisation; using osu.Game.Rulesets; @@ -18,7 +19,7 @@ namespace osu.Game.Overlays.Settings.Sections public override Drawable CreateIcon() => new SpriteIcon { - Icon = FontAwesome.Solid.Chess + Icon = OsuIcon.Rulesets }; [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 1d057f42c0..9b04f208a7 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Framework.Logging; using osu.Game.Database; +using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; using osu.Game.Overlays.SkinEditor; @@ -31,7 +32,7 @@ namespace osu.Game.Overlays.Settings.Sections public override Drawable CreateIcon() => new SpriteIcon { - Icon = FontAwesome.Solid.PaintBrush + Icon = OsuIcon.SkinB }; private static readonly Live random_skin_info = new SkinInfo diff --git a/osu.Game/Overlays/Settings/Sections/UserInterfaceSection.cs b/osu.Game/Overlays/Settings/Sections/UserInterfaceSection.cs index 2ec9e32ea9..953ede25e1 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterfaceSection.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterfaceSection.cs @@ -4,6 +4,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Game.Graphics; using osu.Game.Localisation; using osu.Game.Overlays.Settings.Sections.UserInterface; @@ -15,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections public override Drawable CreateIcon() => new SpriteIcon { - Icon = FontAwesome.Solid.LayerGroup + Icon = OsuIcon.UserInterface }; public UserInterfaceSection() From 288ac930e44bd8dc5b5ab8d5f5f342228625fc0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 16:03:57 +0100 Subject: [PATCH 2163/4092] Use new icons in editor Some that exist on figma are purposefully not used due to an editorial request from @peppy. --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 3 ++- osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs | 3 ++- osu.Game/Rulesets/Edit/Tools/SelectTool.cs | 3 ++- .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 6 +++--- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 8382317d70..448cfaf84c 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -17,6 +17,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; @@ -54,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Edit .Concat(DistanceSnapProvider.CreateTernaryButtons()) .Concat(new[] { - new TernaryButton(rectangularGridSnapToggle, "Grid Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Th }) + new TernaryButton(rectangularGridSnapToggle, "Grid Snap", () => new SpriteIcon { Icon = OsuIcon.EditorGridSnap }) }); private BindableList selectedHitObjects; diff --git a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs index ddf539771d..b3ca59a5b0 100644 --- a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs @@ -16,6 +16,7 @@ using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Framework.Utils; using osu.Game.Configuration; +using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Overlays; @@ -169,7 +170,7 @@ namespace osu.Game.Rulesets.Edit public IEnumerable CreateTernaryButtons() => new[] { - new TernaryButton(DistanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Ruler }) + new TernaryButton(DistanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = OsuIcon.EditorDistanceSnap }) }; protected override bool OnKeyDown(KeyDownEvent e) diff --git a/osu.Game/Rulesets/Edit/Tools/SelectTool.cs b/osu.Game/Rulesets/Edit/Tools/SelectTool.cs index 9640830a09..a272e9f480 100644 --- a/osu.Game/Rulesets/Edit/Tools/SelectTool.cs +++ b/osu.Game/Rulesets/Edit/Tools/SelectTool.cs @@ -5,6 +5,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; namespace osu.Game.Rulesets.Edit.Tools { @@ -15,7 +16,7 @@ namespace osu.Game.Rulesets.Edit.Tools { } - public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.MousePointer }; + public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorSelect }; public override PlacementBlueprint CreatePlacementBlueprint() => null; } diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index c7c7c4aa83..4fba798a26 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -225,7 +225,7 @@ namespace osu.Game.Screens.Edit.Compose.Components protected virtual IEnumerable CreateTernaryButtons() { //TODO: this should only be enabled (visible?) for rulesets that provide combo-supporting HitObjects. - yield return new TernaryButton(NewCombo, "New combo", () => new SpriteIcon { Icon = FontAwesome.Regular.DotCircle }); + yield return new TernaryButton(NewCombo, "New combo", () => new SpriteIcon { Icon = OsuIcon.EditorNewComboA }); foreach (var kvp in SelectionHandler.SelectionSampleStates) yield return new TernaryButton(kvp.Value, kvp.Key.Replace("hit", string.Empty).Titleize(), () => getIconForSample(kvp.Key)); @@ -272,10 +272,10 @@ namespace osu.Game.Screens.Edit.Compose.Components return new SpriteIcon { Icon = FontAwesome.Solid.Hands }; case HitSampleInfo.HIT_WHISTLE: - return new SpriteIcon { Icon = FontAwesome.Solid.Bullhorn }; + return new SpriteIcon { Icon = OsuIcon.EditorWhistle }; case HitSampleInfo.HIT_FINISH: - return new SpriteIcon { Icon = FontAwesome.Solid.DrumSteelpan }; + return new SpriteIcon { Icon = OsuIcon.EditorFinish }; } return null; From 53766285ce6cd5b227e933fe45da8f32bc2f6cf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 15:55:19 +0100 Subject: [PATCH 2164/4092] Remove remaining hexacons usages --- osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs | 2 +- osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs | 2 +- osu.Game/Overlays/Chat/ChatOverlayTopBar.cs | 2 +- osu.Game/Overlays/Profile/ProfileHeader.cs | 2 +- osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs | 2 +- osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs index 55a04b129c..5db7223bdf 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs @@ -154,7 +154,7 @@ namespace osu.Game.Tests.Visual.UserInterface public TestTitle() { Title = "title"; - Icon = HexaconsIcons.Devtools; + Icon = OsuIcon.ChangelogB; } } } diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs index eced27f35e..1df246ae77 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs @@ -60,7 +60,7 @@ namespace osu.Game.Overlays.BeatmapSet public BeatmapHeaderTitle() { Title = PageTitleStrings.MainBeatmapsetsControllerShow; - Icon = HexaconsIcons.Beatmap; + Icon = OsuIcon.Beatmap; } } } diff --git a/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs b/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs index 4fc9fbb6d5..3ecdb09976 100644 --- a/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs +++ b/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs @@ -46,7 +46,7 @@ namespace osu.Game.Overlays.Chat { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Icon = HexaconsIcons.Messaging, + Icon = OsuIcon.Chat, Size = new Vector2(24), }, // Placeholder text diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 78343d08f1..42bec50022 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -87,7 +87,7 @@ namespace osu.Game.Overlays.Profile public ProfileHeaderTitle() { Title = PageTitleStrings.MainUsersControllerDefault; - Icon = HexaconsIcons.Profile; + Icon = OsuIcon.Player; } } } diff --git a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs index 5c77672d90..0e125d0ec0 100644 --- a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs +++ b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Edit.Components.Menus Size = new Vector2(26), Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Icon = HexaconsIcons.Editor, + Icon = OsuIcon.EditCircle, }, text = new TextFlowContainer { diff --git a/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs b/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs index 93448c4394..022da36abc 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs @@ -80,7 +80,7 @@ namespace osu.Game.Screens.Edit.Setup { Title = EditorSetupStrings.BeatmapSetup.ToLower(); Description = EditorSetupStrings.BeatmapSetupDescription; - Icon = HexaconsIcons.Social; + Icon = OsuIcon.Beatmap; } } From 89e2b6358a2937ee46246f3b05b4122c7528ae95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 15:55:37 +0100 Subject: [PATCH 2165/4092] Remove hexacons --- osu.Game/Graphics/HexaconsIcons.cs | 131 ----------------------------- osu.Game/OsuGameBase.cs | 1 - 2 files changed, 132 deletions(-) delete mode 100644 osu.Game/Graphics/HexaconsIcons.cs diff --git a/osu.Game/Graphics/HexaconsIcons.cs b/osu.Game/Graphics/HexaconsIcons.cs deleted file mode 100644 index 3eee5d7197..0000000000 --- a/osu.Game/Graphics/HexaconsIcons.cs +++ /dev/null @@ -1,131 +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 System; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Framework.Text; - -namespace osu.Game.Graphics -{ - public static class HexaconsIcons - { - public const string FONT_NAME = "Icons/Hexacons"; - - public static IconUsage BeatmapPacks => get(HexaconsMapping.beatmap_packs); - public static IconUsage Beatmap => get(HexaconsMapping.beatmap); - public static IconUsage Calendar => get(HexaconsMapping.calendar); - public static IconUsage Chart => get(HexaconsMapping.chart); - public static IconUsage Community => get(HexaconsMapping.community); - public static IconUsage Contests => get(HexaconsMapping.contests); - public static IconUsage Devtools => get(HexaconsMapping.devtools); - public static IconUsage Download => get(HexaconsMapping.download); - public static IconUsage Editor => get(HexaconsMapping.editor); - public static IconUsage FeaturedArtist => get(HexaconsMapping.featured_artist); - public static IconUsage Home => get(HexaconsMapping.home); - public static IconUsage Messaging => get(HexaconsMapping.messaging); - public static IconUsage Music => get(HexaconsMapping.music); - public static IconUsage News => get(HexaconsMapping.news); - public static IconUsage Notification => get(HexaconsMapping.notification); - public static IconUsage Profile => get(HexaconsMapping.profile); - public static IconUsage Rankings => get(HexaconsMapping.rankings); - public static IconUsage Search => get(HexaconsMapping.search); - public static IconUsage Settings => get(HexaconsMapping.settings); - public static IconUsage Social => get(HexaconsMapping.social); - public static IconUsage Store => get(HexaconsMapping.store); - public static IconUsage Tournament => get(HexaconsMapping.tournament); - public static IconUsage Wiki => get(HexaconsMapping.wiki); - - private static IconUsage get(HexaconsMapping icon) => new IconUsage((char)icon, FONT_NAME); - - // Basically just converting to something we can use in a `char` lookup for FontStore/GlyphStore compatibility. - // Names should match filenames in resources. - private enum HexaconsMapping - { - beatmap_packs, - beatmap, - calendar, - chart, - community, - contests, - devtools, - download, - editor, - featured_artist, - home, - messaging, - music, - news, - notification, - profile, - rankings, - search, - settings, - social, - store, - tournament, - wiki, - } - - public class HexaconsStore : ITextureStore, ITexturedGlyphLookupStore - { - private readonly TextureStore textures; - - public HexaconsStore(TextureStore textures) - { - this.textures = textures; - } - - public void Dispose() - { - textures.Dispose(); - } - - public ITexturedCharacterGlyph? Get(string? fontName, char character) - { - if (fontName == FONT_NAME) - return new Glyph(textures.Get($"{fontName}/{((HexaconsMapping)character).ToString().Replace("_", "-")}")); - - return null; - } - - public Task GetAsync(string fontName, char character) => Task.Run(() => Get(fontName, character)); - - public Texture? Get(string name, WrapMode wrapModeS, WrapMode wrapModeT) => null; - - public Texture Get(string name) => throw new NotImplementedException(); - - public Task GetAsync(string name, CancellationToken cancellationToken = default) => throw new NotImplementedException(); - - public Stream GetStream(string name) => throw new NotImplementedException(); - - public IEnumerable GetAvailableResources() => throw new NotImplementedException(); - - public Task GetAsync(string name, WrapMode wrapModeS, WrapMode wrapModeT, CancellationToken cancellationToken = default) => throw new NotImplementedException(); - - public class Glyph : ITexturedCharacterGlyph - { - public float XOffset => default; - public float YOffset => default; - public float XAdvance => default; - public float Baseline => default; - public char Character => default; - - public float GetKerning(T lastGlyph) where T : ICharacterGlyph => throw new NotImplementedException(); - - public Texture Texture { get; } - public float Width => Texture.Width; - public float Height => Texture.Height; - - public Glyph(Texture texture) - { - Texture = texture; - } - } - } - } -} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index b4ad21f045..5b17dc13c2 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -478,7 +478,6 @@ namespace osu.Game AddFont(Resources, @"Fonts/Venera/Venera-Bold"); AddFont(Resources, @"Fonts/Venera/Venera-Black"); - Fonts.AddStore(new HexaconsIcons.HexaconsStore(Textures)); Fonts.AddStore(new OsuIcon.OsuIconStore(Textures)); } From 655528a5370fe83b05452e220d22103824bfecc0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 03:04:13 +0900 Subject: [PATCH 2166/4092] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index dd2393ff21..c7e0cc3808 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + From 5f7f1f771d2404e05a7d152d73e893e3d1c54cc0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 03:09:20 +0900 Subject: [PATCH 2167/4092] Reword tooltip text for dashboard --- osu.Game/Localisation/NamedOverlayComponentStrings.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Localisation/NamedOverlayComponentStrings.cs b/osu.Game/Localisation/NamedOverlayComponentStrings.cs index 475bea2a4a..72e63d699a 100644 --- a/osu.Game/Localisation/NamedOverlayComponentStrings.cs +++ b/osu.Game/Localisation/NamedOverlayComponentStrings.cs @@ -20,12 +20,12 @@ namespace osu.Game.Localisation public static LocalisableString ChangelogDescription => new TranslatableString(getKey(@"changelog_description"), @"track recent dev updates in the osu! ecosystem"); /// - /// "view your friends and other information" + /// "view your friends and spectate other players" /// - public static LocalisableString DashboardDescription => new TranslatableString(getKey(@"dashboard_description"), @"view your friends and other information"); + public static LocalisableString DashboardDescription => new TranslatableString(getKey(@"dashboard_description"), @"view your friends and spectate other players"); /// - /// "find out who's the best right now" + /// "find out who's the best right now" /// public static LocalisableString RankingsDescription => new TranslatableString(getKey(@"rankings_description"), @"find out who's the best right now"); @@ -39,6 +39,6 @@ namespace osu.Game.Localisation /// public static LocalisableString WikiDescription => new TranslatableString(getKey(@"wiki_description"), @"knowledge base"); - private static string getKey(string key) => $"{prefix}:{key}"; + private static string getKey(string key) => $@"{prefix}:{key}"; } } From 1f55ef211eda02b867327746313a32d599645d17 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 03:11:27 +0900 Subject: [PATCH 2168/4092] Rearrange buttons --- osu.Game/Overlays/Toolbar/Toolbar.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 93294a9d30..ec1238ad1f 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -164,11 +164,11 @@ namespace osu.Game.Overlays.Toolbar { new ToolbarNewsButton(), new ToolbarChangelogButton(), + new ToolbarWikiButton(), new ToolbarRankingsButton(), new ToolbarBeatmapListingButton(), new ToolbarChatButton(), new ToolbarSocialButton(), - new ToolbarWikiButton(), new ToolbarMusicButton(), //new ToolbarButton //{ From d4423d493364065baa7984acda63884e26e8a98d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 18:38:29 +0100 Subject: [PATCH 2169/4092] Store last set score to a `SessionStatic` --- osu.Game/Configuration/SessionStatics.cs | 7 +++++++ osu.Game/Scoring/ScoreInfo.cs | 1 + osu.Game/Screens/Play/SubmittingPlayer.cs | 5 +++++ 3 files changed, 13 insertions(+) diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs index 8f0a60b23d..1548b781a7 100644 --- a/osu.Game/Configuration/SessionStatics.cs +++ b/osu.Game/Configuration/SessionStatics.cs @@ -9,6 +9,7 @@ using osu.Game.Input; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.Mods; +using osu.Game.Scoring; namespace osu.Game.Configuration { @@ -27,6 +28,7 @@ namespace osu.Game.Configuration SetDefault(Static.LastModSelectPanelSamplePlaybackTime, (double?)null); SetDefault(Static.SeasonalBackgrounds, null); SetDefault(Static.TouchInputActive, RuntimeInfo.IsMobile); + SetDefault(Static.LastLocalUserScore, null); } /// @@ -73,5 +75,10 @@ namespace osu.Game.Configuration /// Used in touchscreen detection scenarios (). /// TouchInputActive, + + /// + /// Stores the local user's last score (can be completed or aborted). + /// + LastLocalUserScore, } } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 7071bd380e..44795c6fa7 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -207,6 +207,7 @@ namespace osu.Game.Scoring clone.Statistics = new Dictionary(clone.Statistics); clone.MaximumStatistics = new Dictionary(clone.MaximumStatistics); + clone.HitEvents = new List(clone.HitEvents); // Ensure we have fresh mods to avoid any references (ie. after gameplay). clone.clearAllMods(); diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index f88526b8f9..ff2c57bf35 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -11,6 +11,7 @@ using osu.Framework.Allocation; using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.Multiplayer; @@ -37,6 +38,9 @@ namespace osu.Game.Screens.Play [Resolved] private SpectatorClient spectatorClient { get; set; } + [Resolved] + private SessionStatics statics { get; set; } + private TaskCompletionSource scoreSubmissionSource; protected SubmittingPlayer(PlayerConfiguration configuration = null) @@ -176,6 +180,7 @@ namespace osu.Game.Screens.Play { bool exiting = base.OnExiting(e); submitFromFailOrQuit(); + statics.SetValue(Static.LastLocalUserScore, Score.ScoreInfo.DeepClone()); return exiting; } From 1b7af989ec68e06b10c1a014e37c4a56f47cd1c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 19:14:17 +0100 Subject: [PATCH 2170/4092] Migrate `BeatmapOffsetControl` to use session static directly --- .../Gameplay/TestSceneBeatmapOffsetControl.cs | 28 ++++++++++++++++--- osu.Game/Screens/Play/PlayerLoader.cs | 4 --- .../Play/PlayerSettings/AudioSettings.cs | 7 +++-- .../PlayerSettings/BeatmapOffsetControl.cs | 4 +++ 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs index f3701b664c..83fc5c2013 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Scoring; using osu.Game.Screens.Play.PlayerSettings; +using osu.Game.Tests.Resources; using osu.Game.Tests.Visual.Ranking; namespace osu.Game.Tests.Visual.Gameplay @@ -44,7 +45,23 @@ namespace osu.Game.Tests.Visual.Gameplay { offsetControl.ReferenceScore.Value = new ScoreInfo { - HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(0, 2) + HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(0, 2), + BeatmapInfo = Beatmap.Value.BeatmapInfo, + }; + }); + + AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); + } + + [Test] + public void TestScoreFromDifferentBeatmap() + { + AddStep("Set short reference score", () => + { + offsetControl.ReferenceScore.Value = new ScoreInfo + { + HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(10), + BeatmapInfo = TestResources.CreateTestBeatmapSetInfo().Beatmaps.First(), }; }); @@ -59,7 +76,8 @@ namespace osu.Game.Tests.Visual.Gameplay offsetControl.ReferenceScore.Value = new ScoreInfo { HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(10), - Mods = new Mod[] { new OsuModRelax() } + Mods = new Mod[] { new OsuModRelax() }, + BeatmapInfo = Beatmap.Value.BeatmapInfo, }; }); @@ -77,7 +95,8 @@ namespace osu.Game.Tests.Visual.Gameplay { offsetControl.ReferenceScore.Value = new ScoreInfo { - HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(average_error) + HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(average_error), + BeatmapInfo = Beatmap.Value.BeatmapInfo, }; }); @@ -105,7 +124,8 @@ namespace osu.Game.Tests.Visual.Gameplay { offsetControl.ReferenceScore.Value = new ScoreInfo { - HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(average_error) + HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(average_error), + BeatmapInfo = Beatmap.Value.BeatmapInfo, }; }); diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 681189d184..232de53ac3 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -263,10 +263,6 @@ namespace osu.Game.Screens.Play Debug.Assert(CurrentPlayer != null); - var lastScore = CurrentPlayer.Score; - - AudioSettings.ReferenceScore.Value = lastScore?.ScoreInfo; - // prepare for a retry. CurrentPlayer = null; playerConsumed = false; diff --git a/osu.Game/Screens/Play/PlayerSettings/AudioSettings.cs b/osu.Game/Screens/Play/PlayerSettings/AudioSettings.cs index 010d8115fa..3c79721590 100644 --- a/osu.Game/Screens/Play/PlayerSettings/AudioSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/AudioSettings.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Play.PlayerSettings { public partial class AudioSettings : PlayerSettingsGroup { - public Bindable ReferenceScore { get; } = new Bindable(); + private Bindable referenceScore { get; } = new Bindable(); private readonly PlayerCheckbox beatmapHitsoundsToggle; @@ -26,15 +26,16 @@ namespace osu.Game.Screens.Play.PlayerSettings beatmapHitsoundsToggle = new PlayerCheckbox { LabelText = SkinSettingsStrings.BeatmapHitsounds }, new BeatmapOffsetControl { - ReferenceScore = { BindTarget = ReferenceScore }, + ReferenceScore = { BindTarget = referenceScore }, }, }; } [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuConfigManager config, SessionStatics statics) { beatmapHitsoundsToggle.Current = config.GetBindable(OsuSetting.BeatmapHitsounds); + statics.BindWith(Static.LastLocalUserScore, referenceScore); } } } diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index b0e7d08699..3f0f0fd1df 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; @@ -174,6 +175,9 @@ namespace osu.Game.Screens.Play.PlayerSettings if (score.NewValue == null) return; + if (!score.NewValue.BeatmapInfo.AsNonNull().Equals(beatmap.Value.BeatmapInfo)) + return; + if (score.NewValue.Mods.Any(m => !m.UserPlayable || m is IHasNoTimedInputs)) return; From 70aa067eb166477f29b31495da6b1a8121a0bf8c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 03:23:04 +0900 Subject: [PATCH 2171/4092] Adjust gradient visibility and transition --- osu.Game/Overlays/Toolbar/Toolbar.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index ec1238ad1f..52fad2ba3b 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -224,9 +224,9 @@ namespace osu.Game.Overlays.Toolbar RelativeSizeAxes = Axes.X, Anchor = Anchor.BottomLeft, Alpha = 0, - Height = 100, + Height = 80, Colour = ColourInfo.GradientVertical( - OsuColour.Gray(0).Opacity(0.9f), OsuColour.Gray(0).Opacity(0)), + OsuColour.Gray(0f).Opacity(0.7f), OsuColour.Gray(0).Opacity(0)), }, }; } @@ -241,9 +241,9 @@ namespace osu.Game.Overlays.Toolbar private void updateState() { if (ShowGradient.Value) - gradientBackground.FadeIn(transition_time, Easing.OutQuint); + gradientBackground.FadeIn(2500, Easing.OutQuint); else - gradientBackground.FadeOut(transition_time, Easing.OutQuint); + gradientBackground.FadeOut(200, Easing.OutQuint); } } From 92c4c20a51e544a27b745932334e6442d5d5d994 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 03:43:38 +0900 Subject: [PATCH 2172/4092] Adjust paddings and fills of toolbar buttons --- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 45 ++++++++++--------- osu.Game/Overlays/Toolbar/ToolbarClock.cs | 34 +++++++++----- .../Toolbar/ToolbarOverlayToggleButton.cs | 2 +- .../Toolbar/ToolbarRulesetSelector.cs | 1 + .../Overlays/Toolbar/ToolbarUserButton.cs | 2 +- 5 files changed, 51 insertions(+), 33 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index a547fda814..bd5faf1588 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -7,7 +7,6 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input; @@ -74,6 +73,8 @@ namespace osu.Game.Overlays.Toolbar private readonly SpriteText keyBindingTooltip; protected FillFlowContainer Flow; + protected readonly Container BackgroundContent; + [Resolved] private RealmAccess realm { get; set; } = null!; @@ -82,21 +83,33 @@ namespace osu.Game.Overlays.Toolbar Width = Toolbar.HEIGHT; RelativeSizeAxes = Axes.Y; + Padding = new MarginPadding(3); + Children = new Drawable[] { - HoverBackground = new Box + BackgroundContent = new Container { RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(80).Opacity(180), - Blending = BlendingParameters.Additive, - Alpha = 0, - }, - flashBackground = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - Colour = Color4.White.Opacity(100), - Blending = BlendingParameters.Additive, + Masking = true, + CornerRadius = 6, + CornerExponent = 3f, + Children = new Drawable[] + { + HoverBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(80).Opacity(180), + Blending = BlendingParameters.Additive, + Alpha = 0, + }, + flashBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + Colour = Color4.White.Opacity(100), + Blending = BlendingParameters.Additive, + }, + } }, Flow = new FillFlowContainer { @@ -219,14 +232,6 @@ namespace osu.Game.Overlays.Toolbar public OpaqueBackground() { RelativeSizeAxes = Axes.Both; - Masking = true; - MaskingSmoothness = 0; - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(40), - Radius = 5, - }; Children = new Drawable[] { diff --git a/osu.Game/Overlays/Toolbar/ToolbarClock.cs b/osu.Game/Overlays/Toolbar/ToolbarClock.cs index f1310d8535..67688155ae 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarClock.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarClock.cs @@ -42,21 +42,33 @@ namespace osu.Game.Overlays.Toolbar clockDisplayMode = config.GetBindable(OsuSetting.ToolbarClockDisplayMode); prefer24HourTime = config.GetBindable(OsuSetting.Prefer24HourTime); + Padding = new MarginPadding(3); + Children = new Drawable[] { - hoverBackground = new Box + new Container { RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(80).Opacity(180), - Blending = BlendingParameters.Additive, - Alpha = 0, - }, - flashBackground = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - Colour = Color4.White.Opacity(100), - Blending = BlendingParameters.Additive, + Masking = true, + CornerRadius = 6, + CornerExponent = 3f, + Children = new Drawable[] + { + hoverBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(80).Opacity(180), + Blending = BlendingParameters.Additive, + Alpha = 0, + }, + flashBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + Colour = Color4.White.Opacity(100), + Blending = BlendingParameters.Additive, + }, + } }, new FillFlowContainer { diff --git a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs index 78c976111b..37038161e1 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs @@ -46,7 +46,7 @@ namespace osu.Game.Overlays.Toolbar public ToolbarOverlayToggleButton() { - Add(stateBackground = new Box + BackgroundContent.Add(stateBackground = new Box { RelativeSizeAxes = Axes.Both, Colour = OsuColour.Gray(150).Opacity(180), diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs index 715076b368..63d11c1b9e 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs @@ -41,6 +41,7 @@ namespace osu.Game.Overlays.Toolbar new OpaqueBackground { Depth = 1, + Masking = true, }, ModeButtonLine = new Container { diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs index 028decea1e..7d1b6c7404 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs @@ -40,7 +40,7 @@ namespace osu.Game.Overlays.Toolbar [BackgroundDependencyLoader] private void load(OsuColour colours, IAPIProvider api, LoginOverlay? login) { - Add(new OpaqueBackground { Depth = 1 }); + BackgroundContent.Add(new OpaqueBackground { Depth = 1 }); Flow.Add(new Container { From cf5e3e886386737385fe75cf1642e75279c7ff16 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 04:06:29 +0900 Subject: [PATCH 2173/4092] Breathe some colour and life into the toolbar --- .../Toolbar/ToolbarOverlayToggleButton.cs | 8 ++-- .../Toolbar/ToolbarRulesetSelector.cs | 43 +++++++++---------- .../Toolbar/ToolbarRulesetTabButton.cs | 30 +++++++------ 3 files changed, 43 insertions(+), 38 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs index 37038161e1..09b8df14a6 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs @@ -3,6 +3,7 @@ #nullable disable +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -14,7 +15,7 @@ namespace osu.Game.Overlays.Toolbar { public partial class ToolbarOverlayToggleButton : ToolbarButton { - private readonly Box stateBackground; + private Box stateBackground; private OverlayContainer stateContainer; @@ -44,12 +45,13 @@ namespace osu.Game.Overlays.Toolbar } } - public ToolbarOverlayToggleButton() + [BackgroundDependencyLoader] + private void load(OsuColour colours) { BackgroundContent.Add(stateBackground = new Box { RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(150).Opacity(180), + Colour = colours.Carmine.Opacity(180), Blending = BlendingParameters.Additive, Depth = 2, Alpha = 0, diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs index 63d11c1b9e..723c24597a 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs @@ -4,20 +4,19 @@ #nullable disable using System.Collections.Generic; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Effects; -using osuTK; -using osuTK.Graphics; -using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Events; -using osuTK.Input; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Game.Rulesets; +using osuTK; +using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Overlays.Toolbar { @@ -47,20 +46,18 @@ namespace osu.Game.Overlays.Toolbar { Size = new Vector2(Toolbar.HEIGHT, 3), Anchor = Anchor.BottomLeft, - Origin = Anchor.TopLeft, - Masking = true, - EdgeEffect = new EdgeEffectParameters + Origin = Anchor.BottomLeft, + Y = -1, + Children = new Drawable[] { - Type = EdgeEffectType.Glow, - Colour = new Color4(255, 194, 224, 100), - Radius = 15, - Roundness = 15, - }, - Child = new Box - { - RelativeSizeAxes = Axes.Both, + new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(18, 3), + } } - } + }, }); foreach (var ruleset in Rulesets.AvailableRulesets) @@ -90,7 +87,7 @@ namespace osu.Game.Overlays.Toolbar { if (SelectedTab != null) { - ModeButtonLine.MoveToX(SelectedTab.DrawPosition.X, !hasInitialPosition ? 0 : 200, Easing.OutQuint); + ModeButtonLine.MoveToX(SelectedTab.DrawPosition.X, !hasInitialPosition ? 0 : 500, Easing.OutElasticQuarter); if (hasInitialPosition) selectionSamples[SelectedTab.Value.ShortName]?.Play(); diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs index 74f76c7c89..5500f1c879 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs @@ -1,14 +1,16 @@ // 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.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; using osu.Game.Rulesets; -using osuTK.Graphics; namespace osu.Game.Overlays.Toolbar { @@ -41,27 +43,31 @@ namespace osu.Game.Overlays.Toolbar { protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(); + [Resolved] + private OsuColour colours { get; set; } = null!; + + public RulesetButton() + { + Padding = new MarginPadding(3) + { + Bottom = 5 + }; + } + public bool Active { - set + set => Scheduler.AddOnce(() => { if (value) { - IconContainer.Colour = Color4.White; - IconContainer.EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Colour = new Color4(255, 194, 224, 100), - Radius = 15, - Roundness = 15, - }; + IconContainer.Colour = Color4Extensions.FromHex("#00FFAA"); } else { - IconContainer.Colour = new Color4(255, 194, 224, 255); + IconContainer.Colour = colours.GrayF; IconContainer.EdgeEffect = new EdgeEffectParameters(); } - } + }); } protected override bool OnClick(ClickEvent e) From f51b5f5487e770b783cc12513865eb1d5a3462d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 19:43:14 +0100 Subject: [PATCH 2174/4092] Add components to track average hit errors across session --- osu.Game/OsuGameBase.cs | 2 + .../Audio/AudioOffsetAdjustControl.cs | 23 ++++++++ .../Audio/SessionAverageHitErrorTracker.cs | 55 +++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs create mode 100644 osu.Game/Overlays/Settings/Sections/Audio/SessionAverageHitErrorTracker.cs diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 48548dc1ef..64f15efb15 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -55,6 +55,7 @@ using osu.Game.Online.Spectator; using osu.Game.Overlays; using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings.Sections; +using osu.Game.Overlays.Settings.Sections.Audio; using osu.Game.Overlays.Settings.Sections.Input; using osu.Game.Resources; using osu.Game.Rulesets; @@ -349,6 +350,7 @@ namespace osu.Game dependencies.CacheAs(powerStatus); dependencies.Cache(SessionStatics = new SessionStatics()); + dependencies.Cache(new SessionAverageHitErrorTracker()); dependencies.Cache(Colours = new OsuColour()); RegisterImportHandler(BeatmapManager); diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs new file mode 100644 index 0000000000..045cd24fc0 --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs @@ -0,0 +1,23 @@ +// 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.Containers; + +namespace osu.Game.Overlays.Settings.Sections.Audio +{ + public partial class AudioOffsetAdjustControl : SettingsItem + { + [BackgroundDependencyLoader] + private void load() + { + } + + protected override Drawable CreateControl() => new AudioOffsetPreview(); + + private partial class AudioOffsetPreview : CompositeDrawable + { + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Audio/SessionAverageHitErrorTracker.cs b/osu.Game/Overlays/Settings/Sections/Audio/SessionAverageHitErrorTracker.cs new file mode 100644 index 0000000000..b714d49b89 --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Audio/SessionAverageHitErrorTracker.cs @@ -0,0 +1,55 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Configuration; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; + +namespace osu.Game.Overlays.Settings.Sections.Audio +{ + /// + /// Tracks the local user's average hit error during the ongoing play session. + /// + [Cached] + public partial class SessionAverageHitErrorTracker : Component + { + public IBindableList AverageHitErrorHistory => averageHitErrorHistory; + private readonly BindableList averageHitErrorHistory = new BindableList(); + + private readonly Bindable latestScore = new Bindable(); + + [BackgroundDependencyLoader] + private void load(SessionStatics statics) + { + statics.BindWith(Static.LastLocalUserScore, latestScore); + latestScore.BindValueChanged(score => calculateAverageHitError(score.NewValue), true); + } + + private void calculateAverageHitError(ScoreInfo? newScore) + { + if (newScore == null) + return; + + if (newScore.Mods.Any(m => !m.UserPlayable || m is IHasNoTimedInputs)) + return; + + if (newScore.HitEvents.Count < 10) + return; + + if (newScore.HitEvents.CalculateAverageHitError() is not double averageError) + return; + + // keep a sane maximum number of entries. + if (averageHitErrorHistory.Count >= 50) + averageHitErrorHistory.RemoveAt(0); + averageHitErrorHistory.Add(averageError); + } + + public void ClearHistory() => averageHitErrorHistory.Clear(); + } +} From 160342ceed4824a3214157c049ff981e2710568f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 21:14:37 +0100 Subject: [PATCH 2175/4092] Implement automatic suggestion of audio offset based on last plays --- .../TestSceneAudioOffsetAdjustControl.cs | 63 ++++++++ osu.Game/OsuGameBase.cs | 5 +- .../Audio/AudioOffsetAdjustControl.cs | 138 +++++++++++++++++- .../Settings/Sections/Audio/OffsetSettings.cs | 5 +- 4 files changed, 205 insertions(+), 6 deletions(-) create mode 100644 osu.Game.Tests/Visual/Settings/TestSceneAudioOffsetAdjustControl.cs diff --git a/osu.Game.Tests/Visual/Settings/TestSceneAudioOffsetAdjustControl.cs b/osu.Game.Tests/Visual/Settings/TestSceneAudioOffsetAdjustControl.cs new file mode 100644 index 0000000000..efb65bb0a8 --- /dev/null +++ b/osu.Game.Tests/Visual/Settings/TestSceneAudioOffsetAdjustControl.cs @@ -0,0 +1,63 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Utils; +using osu.Game.Configuration; +using osu.Game.Overlays.Settings.Sections.Audio; +using osu.Game.Scoring; +using osu.Game.Tests.Visual.Ranking; + +namespace osu.Game.Tests.Visual.Settings +{ + public partial class TestSceneAudioOffsetAdjustControl : OsuTestScene + { + [Resolved] + private SessionStatics statics { get; set; } = null!; + + [Cached] + private SessionAverageHitErrorTracker tracker = new SessionAverageHitErrorTracker(); + + private Container content = null!; + protected override Container Content => content; + + [BackgroundDependencyLoader] + private void load() + { + base.Content.AddRange(new Drawable[] + { + tracker, + content = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 400, + AutoSizeAxes = Axes.Y + } + }); + } + + [Test] + public void TestBehaviour() + { + AddStep("create control", () => Child = new AudioOffsetAdjustControl + { + Current = new BindableDouble + { + MinValue = -500, + MaxValue = 500 + } + }); + AddStep("set new score", () => statics.SetValue(Static.LastLocalUserScore, new ScoreInfo + { + HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(RNG.NextDouble(-100, 100)), + BeatmapInfo = Beatmap.Value.BeatmapInfo, + })); + AddStep("clear history", () => tracker.ClearHistory()); + } + } +} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 64f15efb15..0d8a2fbb97 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -201,6 +201,8 @@ namespace osu.Game private RulesetConfigCache rulesetConfigCache; + private SessionAverageHitErrorTracker hitErrorTracker; + protected SpectatorClient SpectatorClient { get; private set; } protected MultiplayerClient MultiplayerClient { get; private set; } @@ -350,7 +352,7 @@ namespace osu.Game dependencies.CacheAs(powerStatus); dependencies.Cache(SessionStatics = new SessionStatics()); - dependencies.Cache(new SessionAverageHitErrorTracker()); + dependencies.Cache(hitErrorTracker = new SessionAverageHitErrorTracker()); dependencies.Cache(Colours = new OsuColour()); RegisterImportHandler(BeatmapManager); @@ -410,6 +412,7 @@ namespace osu.Game }); base.Content.Add(new TouchInputInterceptor()); + base.Content.Add(hitErrorTracker); KeyBindingStore = new RealmKeyBindingStore(realm, keyCombinationProvider); KeyBindingStore.Register(globalBindings, RulesetStore.AvailableRulesets); diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs index 045cd24fc0..0023c60c61 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs @@ -1,9 +1,23 @@ // 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.Collections.Specialized; +using System.Diagnostics; +using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Localisation; +using osuTK; namespace osu.Game.Overlays.Settings.Sections.Audio { @@ -12,12 +26,134 @@ namespace osu.Game.Overlays.Settings.Sections.Audio [BackgroundDependencyLoader] private void load() { + LabelText = AudioSettingsStrings.AudioOffset; } protected override Drawable CreateControl() => new AudioOffsetPreview(); - private partial class AudioOffsetPreview : CompositeDrawable + private partial class AudioOffsetPreview : CompositeDrawable, IHasCurrentValue { + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + + private readonly BindableNumberWithCurrent current = new BindableNumberWithCurrent(); + + private readonly IBindableList averageHitErrorHistory = new BindableList(); + + private readonly Bindable suggestedOffset = new Bindable(); + + private Container notchContainer = null!; + private TextFlowContainer hintText = null!; + private RoundedButton applySuggestion = null!; + + [BackgroundDependencyLoader] + private void load(SessionAverageHitErrorTracker hitErrorTracker) + { + averageHitErrorHistory.BindTo(hitErrorTracker.AverageHitErrorHistory); + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + InternalChild = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(10), + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new TimeSlider + { + RelativeSizeAxes = Axes.X, + Current = { BindTarget = Current }, + KeyboardStep = 1, + }, + notchContainer = new Container + { + RelativeSizeAxes = Axes.X, + Height = 10, + Padding = new MarginPadding { Horizontal = Nub.DEFAULT_EXPANDED_SIZE / 2 }, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, + hintText = new OsuTextFlowContainer(t => t.Font = OsuFont.Default.With(size: 16)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, + applySuggestion = new RoundedButton + { + RelativeSizeAxes = Axes.X, + Text = "Apply suggested offset", + Action = () => + { + if (suggestedOffset.Value.HasValue) + current.Value = suggestedOffset.Value.Value; + hitErrorTracker.ClearHistory(); + } + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + averageHitErrorHistory.BindCollectionChanged(updateDisplay, true); + suggestedOffset.BindValueChanged(_ => updateHintText(), true); + } + + private void updateDisplay(object? _, NotifyCollectionChangedEventArgs e) + { + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + foreach (double average in e.NewItems!) + { + notchContainer.ForEach(n => n.Alpha *= 0.95f); + notchContainer.Add(new Box + { + RelativeSizeAxes = Axes.Y, + Width = 2, + RelativePositionAxes = Axes.X, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + X = getXPositionForAverage(average) + }); + } + + break; + + case NotifyCollectionChangedAction.Remove: + foreach (double average in e.OldItems!) + { + var notch = notchContainer.FirstOrDefault(n => n.X == getXPositionForAverage(average)); + Debug.Assert(notch != null); + notchContainer.Remove(notch, true); + } + + break; + + case NotifyCollectionChangedAction.Reset: + notchContainer.Clear(); + break; + } + + suggestedOffset.Value = averageHitErrorHistory.Count < 3 ? null : -averageHitErrorHistory.Average(); + } + + private float getXPositionForAverage(double average) => (float)(Math.Clamp(-average, current.MinValue, current.MaxValue) / (2 * current.MaxValue)); + + private void updateHintText() + { + hintText.Text = suggestedOffset.Value == null + ? @"Play a few beatmaps to receive a suggested offset!" + : $@"Based on the last {averageHitErrorHistory.Count} plays, the suggested offset is {suggestedOffset.Value:N0} ms."; + applySuggestion.Enabled.Value = suggestedOffset.Value != null; + } } } } diff --git a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs index 6b5c769853..af15d310fc 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs @@ -7,7 +7,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Localisation; using osu.Game.Configuration; -using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; namespace osu.Game.Overlays.Settings.Sections.Audio @@ -23,11 +22,9 @@ namespace osu.Game.Overlays.Settings.Sections.Audio { Children = new Drawable[] { - new SettingsSlider + new AudioOffsetAdjustControl { - LabelText = AudioSettingsStrings.AudioOffset, Current = config.GetBindable(OsuSetting.AudioOffset), - KeyboardStep = 1f }, new SettingsButton { From cf39bb7a18877055a58324d542b9c4feb83d1611 Mon Sep 17 00:00:00 2001 From: rushiiMachine <33725716+rushiiMachine@users.noreply.github.com> Date: Wed, 27 Dec 2023 12:55:20 -0800 Subject: [PATCH 2176/4092] Fix spinner max bonus not respecting ISamplePlaybackDisabler The spinner max bonus was loaded through SkinnableSound instead of PausableSkinnableSound, leading to it not respecting the case where sample playback is globally disabled through ISamplePlaybackDisabler, and can be easily heard in situations like during the catchup period after seeking using the ArgonSongProgressBar with song volume at 0 --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index f7c1437009..bf4b07eaab 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private const float spinning_sample_initial_frequency = 1.0f; private const float spinning_sample_modulated_base_frequency = 0.5f; - private SkinnableSound maxBonusSample; + private PausableSkinnableSound maxBonusSample; /// /// The amount of bonus score gained from spinning after the required number of spins, for display purposes. @@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Looping = true, Frequency = { Value = spinning_sample_initial_frequency } }, - maxBonusSample = new SkinnableSound + maxBonusSample = new PausableSkinnableSound { MinimumSampleVolume = MINIMUM_SAMPLE_VOLUME, } From d9299a8a55bff6757358db2d46b38806817f974a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 23:07:17 +0100 Subject: [PATCH 2177/4092] Implement visual appearance of "system title" message in main menu --- .../Visual/Menus/TestSceneMainMenu.cs | 30 ++++++ .../API/Requests/Responses/APISystemTitle.cs | 16 +++ osu.Game/OsuGame.cs | 5 +- osu.Game/Screens/Menu/MainMenu.cs | 15 +++ osu.Game/Screens/Menu/SystemTitle.cs | 101 ++++++++++++++++++ 5 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs create mode 100644 osu.Game/Online/API/Requests/Responses/APISystemTitle.cs create mode 100644 osu.Game/Screens/Menu/SystemTitle.cs diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs new file mode 100644 index 0000000000..85aa14f33e --- /dev/null +++ b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs @@ -0,0 +1,30 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Screens.Menu; + +namespace osu.Game.Tests.Visual.Menus +{ + public partial class TestSceneMainMenu : OsuGameTestScene + { + [Test] + public void TestSystemTitle() + { + AddStep("set system title", () => Game.ChildrenOfType().Single().Current.Value = new APISystemTitle + { + Image = @"https://assets.ppy.sh/main-menu/project-loved-2@2x.png", + Url = @"https://osu.ppy.sh/home/news/2023-12-21-project-loved-december-2023", + }); + AddStep("set another title", () => Game.ChildrenOfType().Single().Current.Value = new APISystemTitle + { + Image = @"https://assets.ppy.sh/main-menu/wf2023-vote@2x.png", + Url = @"https://osu.ppy.sh/community/contests/189", + }); + AddStep("unset system title", () => Game.ChildrenOfType().Single().Current.Value = null); + } + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APISystemTitle.cs b/osu.Game/Online/API/Requests/Responses/APISystemTitle.cs new file mode 100644 index 0000000000..3a2342cc4c --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APISystemTitle.cs @@ -0,0 +1,16 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APISystemTitle + { + [JsonProperty(@"image")] + public string Image { get; set; } = string.Empty; + + [JsonProperty(@"url")] + public string Url { get; set; } = string.Empty; + } +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index e7ff99ef01..37996e8832 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -995,7 +995,10 @@ namespace osu.Game }, topMostOverlayContent.Add); if (!args?.Any(a => a == @"--no-version-overlay") ?? true) - loadComponentSingleFile(versionManager = new VersionManager { Depth = int.MinValue }, ScreenContainer.Add); + { + dependencies.Cache(versionManager = new VersionManager { Depth = int.MinValue }); + loadComponentSingleFile(versionManager, ScreenContainer.Add); + } loadComponentSingleFile(osuLogo, _ => { diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index c25e62d69e..0739689daa 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -76,6 +76,9 @@ namespace osu.Game.Screens.Menu [Resolved(canBeNull: true)] private IDialogOverlay dialogOverlay { get; set; } + [Resolved(canBeNull: true)] + private VersionManager versionManager { get; set; } + protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault(); protected override bool PlayExitSound => false; @@ -91,6 +94,7 @@ namespace osu.Game.Screens.Menu private ParallaxContainer buttonsContainer; private SongTicker songTicker; private Container logoTarget; + private SystemTitle systemTitle; private Sample reappearSampleSwoosh; @@ -153,6 +157,7 @@ namespace osu.Game.Screens.Menu Margin = new MarginPadding { Right = 15, Top = 5 } }, new KiaiMenuFountains(), + systemTitle = new SystemTitle(), holdToExitGameOverlay?.CreateProxy() ?? Empty() }); @@ -263,6 +268,16 @@ namespace osu.Game.Screens.Menu } } + protected override void Update() + { + base.Update(); + + systemTitle.Margin = new MarginPadding + { + Bottom = (versionManager?.DrawHeight + 5) ?? 0 + }; + } + protected override void LogoSuspending(OsuLogo logo) { var seq = logo.FadeOut(300, Easing.InSine) diff --git a/osu.Game/Screens/Menu/SystemTitle.cs b/osu.Game/Screens/Menu/SystemTitle.cs new file mode 100644 index 0000000000..6ca9aab6c7 --- /dev/null +++ b/osu.Game/Screens/Menu/SystemTitle.cs @@ -0,0 +1,101 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Threading; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Input.Events; +using osu.Framework.Platform; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Screens.Menu +{ + public partial class SystemTitle : CompositeDrawable + { + internal Bindable Current { get; } = new Bindable(); + + private Container content = null!; + private CancellationTokenSource? cancellationTokenSource; + private SystemTitleImage? currentImage; + + [BackgroundDependencyLoader] + private void load(GameHost? gameHost) + { + Anchor = Anchor.BottomCentre; + Origin = Anchor.BottomCentre; + AutoSizeAxes = Axes.Both; + + InternalChild = content = new ClickableContainer + { + AutoSizeAxes = Axes.Both, + Action = () => + { + if (!string.IsNullOrEmpty(Current.Value?.Url)) + gameHost?.OpenUrlExternally(Current.Value.Url); + } + }; + } + + protected override bool OnHover(HoverEvent e) + { + content.ScaleTo(1.1f, 500, Easing.OutBounce); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + content.ScaleTo(1f, 500, Easing.OutBounce); + base.OnHoverLost(e); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(_ => loadNewImage(), true); + } + + private void loadNewImage() + { + cancellationTokenSource?.Cancel(); + cancellationTokenSource = null; + currentImage?.FadeOut(500, Easing.OutQuint).Expire(); + + if (string.IsNullOrEmpty(Current.Value?.Image)) + return; + + LoadComponentAsync(new SystemTitleImage(Current.Value), loaded => + { + if (loaded.SystemTitle != Current.Value) + loaded.Dispose(); + + loaded.FadeInFromZero(500, Easing.OutQuint); + content.Add(currentImage = loaded); + }, (cancellationTokenSource ??= new CancellationTokenSource()).Token); + } + + [LongRunningLoad] + private partial class SystemTitleImage : Sprite + { + public readonly APISystemTitle SystemTitle; + + public SystemTitleImage(APISystemTitle systemTitle) + { + SystemTitle = systemTitle; + } + + [BackgroundDependencyLoader] + private void load(LargeTextureStore textureStore) + { + var texture = textureStore.Get(SystemTitle.Image); + if (SystemTitle.Image.Contains(@"@2x")) + texture.ScaleAdjust *= 2; + Texture = texture; + } + } + } +} From a3f720bc627f27d6bcc9fe36c011190010930b0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 23:31:14 +0100 Subject: [PATCH 2178/4092] Retrieve system title from online source --- .../API/Requests/GetSystemTitleRequest.cs | 15 ++++++++++++ .../API/Requests/Responses/APISystemTitle.cs | 2 +- osu.Game/Screens/Menu/SystemTitle.cs | 23 +++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Online/API/Requests/GetSystemTitleRequest.cs diff --git a/osu.Game/Online/API/Requests/GetSystemTitleRequest.cs b/osu.Game/Online/API/Requests/GetSystemTitleRequest.cs new file mode 100644 index 0000000000..52ca0c11eb --- /dev/null +++ b/osu.Game/Online/API/Requests/GetSystemTitleRequest.cs @@ -0,0 +1,15 @@ +// 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.Online.API.Requests.Responses; + +namespace osu.Game.Online.API.Requests +{ + public class GetSystemTitleRequest : OsuJsonWebRequest + { + public GetSystemTitleRequest() + : base(@"https://assets.ppy.sh/lazer-status.json") + { + } + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APISystemTitle.cs b/osu.Game/Online/API/Requests/Responses/APISystemTitle.cs index 3a2342cc4c..97bdc8355a 100644 --- a/osu.Game/Online/API/Requests/Responses/APISystemTitle.cs +++ b/osu.Game/Online/API/Requests/Responses/APISystemTitle.cs @@ -5,7 +5,7 @@ using Newtonsoft.Json; namespace osu.Game.Online.API.Requests.Responses { - public class APISystemTitle + public record APISystemTitle { [JsonProperty(@"image")] public string Image { get; set; } = string.Empty; diff --git a/osu.Game/Screens/Menu/SystemTitle.cs b/osu.Game/Screens/Menu/SystemTitle.cs index 6ca9aab6c7..0867fc4748 100644 --- a/osu.Game/Screens/Menu/SystemTitle.cs +++ b/osu.Game/Screens/Menu/SystemTitle.cs @@ -1,7 +1,9 @@ // 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.Threading; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -10,6 +12,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; using osu.Framework.Platform; +using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Screens.Menu @@ -57,6 +60,26 @@ namespace osu.Game.Screens.Menu base.LoadComplete(); Current.BindValueChanged(_ => loadNewImage(), true); + + checkForUpdates(); + Scheduler.AddDelayed(checkForUpdates, TimeSpan.FromMinutes(15).TotalMilliseconds, true); + } + + private void checkForUpdates() + { + var request = new GetSystemTitleRequest(); + Task.Run(() => request.Perform()) + .ContinueWith(r => + { + if (r.IsCompletedSuccessfully) + Schedule(() => Current.Value = request.ResponseObject); + + // if the request failed, "observe" the exception. + // it isn't very important why this failed, as it's only for display. + // the inner error will be logged by framework mechanisms anyway. + if (r.IsFaulted) + _ = r.Exception; + }); } private void loadNewImage() From ac449131edcee95d9ca61fef538aeea7010c128d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 23:47:37 +0100 Subject: [PATCH 2179/4092] CodeFileSanity does not like records in standalone files --- .../API/Requests/Responses/APISystemTitle.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/APISystemTitle.cs b/osu.Game/Online/API/Requests/Responses/APISystemTitle.cs index 97bdc8355a..6d5d91f8f3 100644 --- a/osu.Game/Online/API/Requests/Responses/APISystemTitle.cs +++ b/osu.Game/Online/API/Requests/Responses/APISystemTitle.cs @@ -1,16 +1,29 @@ // 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 Newtonsoft.Json; namespace osu.Game.Online.API.Requests.Responses { - public record APISystemTitle + public class APISystemTitle : IEquatable { [JsonProperty(@"image")] public string Image { get; set; } = string.Empty; [JsonProperty(@"url")] public string Url { get; set; } = string.Empty; + + public bool Equals(APISystemTitle? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + + return Image == other.Image && Url == other.Url; + } + + public override bool Equals(object? obj) => obj is APISystemTitle other && Equals(other); + + public override int GetHashCode() => HashCode.Combine(Image, Url); } } From 0c8b551c6618668902ae1f92e0828f24fd1427f2 Mon Sep 17 00:00:00 2001 From: rushiiMachine <33725716+rushiiMachine@users.noreply.github.com> Date: Wed, 27 Dec 2023 14:45:56 -0800 Subject: [PATCH 2180/4092] SkinEditor lifetime fix & show gameplay If the SkinEditor was created already but not finished initializing, wait for it to initialize before handling a screen change, which could possibly null the skin editor. Additionally, in the case the skin editor is already loaded but hidden, make sure to show gameplay. --- osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs index 10a032193f..5f5323b584 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs @@ -103,6 +103,10 @@ namespace osu.Game.Overlays.SkinEditor if (skinEditor != null) { skinEditor.Show(); + + if (lastTargetScreen is MainMenu) + PresentGameplay(); + return; } @@ -252,7 +256,7 @@ namespace osu.Game.Overlays.SkinEditor Debug.Assert(skinEditor != null); - if (!target.IsLoaded) + if (!target.IsLoaded || !skinEditor.IsLoaded) { Scheduler.AddOnce(setTarget, target); return; From ef3975981376e7308f4156024929f404f9240504 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Dec 2023 00:09:51 +0100 Subject: [PATCH 2181/4092] More code quality inspections --- osu.Game/Online/API/Requests/Responses/APISystemTitle.cs | 1 + osu.Game/Screens/Menu/SystemTitle.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/APISystemTitle.cs b/osu.Game/Online/API/Requests/Responses/APISystemTitle.cs index 6d5d91f8f3..bfa5c1043b 100644 --- a/osu.Game/Online/API/Requests/Responses/APISystemTitle.cs +++ b/osu.Game/Online/API/Requests/Responses/APISystemTitle.cs @@ -24,6 +24,7 @@ namespace osu.Game.Online.API.Requests.Responses public override bool Equals(object? obj) => obj is APISystemTitle other && Equals(other); + // ReSharper disable NonReadonlyMemberInGetHashCode public override int GetHashCode() => HashCode.Combine(Image, Url); } } diff --git a/osu.Game/Screens/Menu/SystemTitle.cs b/osu.Game/Screens/Menu/SystemTitle.cs index 0867fc4748..6b976a8ed6 100644 --- a/osu.Game/Screens/Menu/SystemTitle.cs +++ b/osu.Game/Screens/Menu/SystemTitle.cs @@ -93,7 +93,7 @@ namespace osu.Game.Screens.Menu LoadComponentAsync(new SystemTitleImage(Current.Value), loaded => { - if (loaded.SystemTitle != Current.Value) + if (!loaded.SystemTitle.Equals(Current.Value)) loaded.Dispose(); loaded.FadeInFromZero(500, Easing.OutQuint); From cbfcda79299e2ed2d42e720b81dcf6cd6f55984f Mon Sep 17 00:00:00 2001 From: Felipe Marins Date: Thu, 28 Dec 2023 00:10:01 -0300 Subject: [PATCH 2182/4092] Expose `SelectAll()` method on `ShearedSearchTextBox` --- osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs b/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs index 131041e706..c3a9f8a586 100644 --- a/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs +++ b/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs @@ -48,6 +48,8 @@ namespace osu.Game.Graphics.UserInterface public void KillFocus() => textBox.KillFocus(); + public bool SelectAll() => textBox.SelectAll(); + public ShearedSearchTextBox() { Height = 42; From dce9204731972d81aaeedb42002de243b9c0742c Mon Sep 17 00:00:00 2001 From: Felipe Marins Date: Thu, 28 Dec 2023 00:10:44 -0300 Subject: [PATCH 2183/4092] Select search box text on ModSelectOverlay when mod selection changes --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index baa7e594c1..43f44d682d 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -565,6 +565,9 @@ namespace osu.Game.Overlays.Mods .ToArray(); SelectedMods.Value = ComputeNewModsFromSelection(SelectedMods.Value, candidateSelection); + + if (SearchTextBox.HasFocus) + SearchTextBox.SelectAll(); } #region Transition handling From f8d6b8e347c18e2bf0bec85cdf76aac6f0f5d7a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 14:10:52 +0900 Subject: [PATCH 2184/4092] Adjust toolbar animations / layering to feel better --- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 2 +- osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index bd5faf1588..03a1cfc005 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -183,7 +183,7 @@ namespace osu.Game.Overlays.Toolbar protected override bool OnClick(ClickEvent e) { - flashBackground.FadeOutFromOne(800, Easing.OutQuint); + flashBackground.FadeIn(50).Then().FadeOutFromOne(800, Easing.OutQuint); tooltipContainer.FadeOut(100); return base.OnClick(e); } diff --git a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs index 09b8df14a6..06755a9da9 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs @@ -53,7 +53,7 @@ namespace osu.Game.Overlays.Toolbar RelativeSizeAxes = Axes.Both, Colour = colours.Carmine.Opacity(180), Blending = BlendingParameters.Additive, - Depth = 2, + Depth = float.MaxValue, Alpha = 0, }); @@ -65,11 +65,11 @@ namespace osu.Game.Overlays.Toolbar switch (state.NewValue) { case Visibility.Hidden: - stateBackground.FadeOut(200); + stateBackground.FadeOut(200, Easing.OutQuint); break; case Visibility.Visible: - stateBackground.FadeIn(200); + stateBackground.FadeIn(200, Easing.OutQuint); break; } } From ffc8778d673dd54a650066b9dc47d6252367da15 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 14:13:35 +0900 Subject: [PATCH 2185/4092] Fix song select leaderboard tab ordering not matching stable --- .../Select/Leaderboards/BeatmapLeaderboardScope.cs | 6 +++--- osu.Game/Screens/Select/PlayBeatmapDetailArea.cs | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboardScope.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboardScope.cs index 5bcb4c27a7..e2e3404877 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboardScope.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboardScope.cs @@ -12,12 +12,12 @@ namespace osu.Game.Screens.Select.Leaderboards [Description("Local Ranking")] Local, - [LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowScoreboardCountry))] - Country, - [LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowScoreboardGlobal))] Global, + [LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowScoreboardCountry))] + Country, + [LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowScoreboardFriend))] Friend, } diff --git a/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs b/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs index 8a1b9ef3e1..deb1100dfc 100644 --- a/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs @@ -80,8 +80,8 @@ namespace osu.Game.Screens.Select protected override BeatmapDetailAreaTabItem[] CreateTabItems() => base.CreateTabItems().Concat(new BeatmapDetailAreaTabItem[] { new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Local), - new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Country), new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Global), + new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Country), new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Friend), }).ToArray(); @@ -95,12 +95,12 @@ namespace osu.Game.Screens.Select case TabType.Local: return new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Local); - case TabType.Country: - return new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Country); - case TabType.Global: return new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Global); + case TabType.Country: + return new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Country); + case TabType.Friends: return new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Friend); From 93a8afe96e101a63f65e6bcca92d9707c1fde767 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 14:40:10 +0900 Subject: [PATCH 2186/4092] Add very simple cache-busting (30 minutes) --- osu.Game/Online/API/Requests/GetSystemTitleRequest.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/GetSystemTitleRequest.cs b/osu.Game/Online/API/Requests/GetSystemTitleRequest.cs index 52ca0c11eb..659e46bb11 100644 --- a/osu.Game/Online/API/Requests/GetSystemTitleRequest.cs +++ b/osu.Game/Online/API/Requests/GetSystemTitleRequest.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 System; using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests @@ -8,7 +9,7 @@ namespace osu.Game.Online.API.Requests public class GetSystemTitleRequest : OsuJsonWebRequest { public GetSystemTitleRequest() - : base(@"https://assets.ppy.sh/lazer-status.json") + : base($@"https://assets.ppy.sh/lazer-status.json?{DateTimeOffset.UtcNow.ToUnixTimeSeconds() / 1800}") { } } From c70e7d340da905d081eb87863ea9588021816453 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 14:48:17 +0900 Subject: [PATCH 2187/4092] Adjust animation and add delay to URL open --- osu.Game/Screens/Menu/SystemTitle.cs | 37 ++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Menu/SystemTitle.cs b/osu.Game/Screens/Menu/SystemTitle.cs index 6b976a8ed6..2c8cb90b96 100644 --- a/osu.Game/Screens/Menu/SystemTitle.cs +++ b/osu.Game/Screens/Menu/SystemTitle.cs @@ -12,6 +12,8 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; using osu.Framework.Platform; +using osu.Framework.Threading; +using osu.Game.Graphics.Containers; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; @@ -25,6 +27,8 @@ namespace osu.Game.Screens.Menu private CancellationTokenSource? cancellationTokenSource; private SystemTitleImage? currentImage; + private ScheduledDelegate? openUrlAction; + [BackgroundDependencyLoader] private void load(GameHost? gameHost) { @@ -32,29 +36,52 @@ namespace osu.Game.Screens.Menu Origin = Anchor.BottomCentre; AutoSizeAxes = Axes.Both; - InternalChild = content = new ClickableContainer + InternalChild = content = new OsuClickableContainer { AutoSizeAxes = Axes.Both, Action = () => { - if (!string.IsNullOrEmpty(Current.Value?.Url)) - gameHost?.OpenUrlExternally(Current.Value.Url); + // Delay slightly to allow animation to play out. + openUrlAction?.Cancel(); + openUrlAction = Scheduler.AddDelayed(() => + { + if (!string.IsNullOrEmpty(Current.Value?.Url)) + gameHost?.OpenUrlExternally(Current.Value.Url); + }, 250); } }; } protected override bool OnHover(HoverEvent e) { - content.ScaleTo(1.1f, 500, Easing.OutBounce); + content.ScaleTo(1.05f, 2000, Easing.OutQuint); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - content.ScaleTo(1f, 500, Easing.OutBounce); + content.ScaleTo(1f, 500, Easing.OutQuint); base.OnHoverLost(e); } + protected override bool OnClick(ClickEvent e) + { + //hover.FlashColour(FlashColour, 800, Easing.OutQuint); + return base.OnClick(e); + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + content.ScaleTo(0.95f, 500, Easing.OutQuint); + return base.OnMouseDown(e); + } + + protected override void OnMouseUp(MouseUpEvent e) + { + content.ScaleTo(1, 500, Easing.OutElastic); + base.OnMouseUp(e); + } + protected override void LoadComplete() { base.LoadComplete(); From 289e0f00f98ea570fd0a3f6b644b4399c0e4b5ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 14:58:05 +0900 Subject: [PATCH 2188/4092] Add flash on click --- osu.Game/Screens/Menu/SystemTitle.cs | 37 +++++++++++++++++++++------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Menu/SystemTitle.cs b/osu.Game/Screens/Menu/SystemTitle.cs index 2c8cb90b96..667dc6e947 100644 --- a/osu.Game/Screens/Menu/SystemTitle.cs +++ b/osu.Game/Screens/Menu/SystemTitle.cs @@ -41,6 +41,8 @@ namespace osu.Game.Screens.Menu AutoSizeAxes = Axes.Both, Action = () => { + currentImage?.Flash(); + // Delay slightly to allow animation to play out. openUrlAction?.Cancel(); openUrlAction = Scheduler.AddDelayed(() => @@ -64,12 +66,6 @@ namespace osu.Game.Screens.Menu base.OnHoverLost(e); } - protected override bool OnClick(ClickEvent e) - { - //hover.FlashColour(FlashColour, 800, Easing.OutQuint); - return base.OnClick(e); - } - protected override bool OnMouseDown(MouseDownEvent e) { content.ScaleTo(0.95f, 500, Easing.OutQuint); @@ -78,7 +74,9 @@ namespace osu.Game.Screens.Menu protected override void OnMouseUp(MouseUpEvent e) { - content.ScaleTo(1, 500, Easing.OutElastic); + content + .ScaleTo(0.95f) + .ScaleTo(1, 500, Easing.OutElastic); base.OnMouseUp(e); } @@ -129,10 +127,12 @@ namespace osu.Game.Screens.Menu } [LongRunningLoad] - private partial class SystemTitleImage : Sprite + private partial class SystemTitleImage : CompositeDrawable { public readonly APISystemTitle SystemTitle; + private Sprite flash = null!; + public SystemTitleImage(APISystemTitle systemTitle) { SystemTitle = systemTitle; @@ -144,7 +144,26 @@ namespace osu.Game.Screens.Menu var texture = textureStore.Get(SystemTitle.Image); if (SystemTitle.Image.Contains(@"@2x")) texture.ScaleAdjust *= 2; - Texture = texture; + + AutoSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + new Sprite { Texture = texture }, + flash = new Sprite + { + Texture = texture, + Blending = BlendingParameters.Additive, + Alpha = 0, + }, + }; + } + + public void Flash() + { + flash.FadeInFromZero(50) + .Then() + .FadeOut(500, Easing.OutQuint); } } } From 481a25178658e80891a59bcdd7ad1720576854fe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 15:10:05 +0900 Subject: [PATCH 2189/4092] Use `HandleLink` to allow potentially opening wiki or otherwise --- osu.Game/Screens/Menu/SystemTitle.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/SystemTitle.cs b/osu.Game/Screens/Menu/SystemTitle.cs index 667dc6e947..967b928b43 100644 --- a/osu.Game/Screens/Menu/SystemTitle.cs +++ b/osu.Game/Screens/Menu/SystemTitle.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; -using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Graphics.Containers; using osu.Game.Online.API.Requests; @@ -30,7 +29,7 @@ namespace osu.Game.Screens.Menu private ScheduledDelegate? openUrlAction; [BackgroundDependencyLoader] - private void load(GameHost? gameHost) + private void load(OsuGame? game) { Anchor = Anchor.BottomCentre; Origin = Anchor.BottomCentre; @@ -48,7 +47,7 @@ namespace osu.Game.Screens.Menu openUrlAction = Scheduler.AddDelayed(() => { if (!string.IsNullOrEmpty(Current.Value?.Url)) - gameHost?.OpenUrlExternally(Current.Value.Url); + game?.HandleLink(Current.Value.Url); }, 250); } }; From 972234b1e5c1a505f23fe1491d509ab343636b4c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 15:12:44 +0900 Subject: [PATCH 2190/4092] Move re-schedule inside continuation --- osu.Game/Screens/Menu/SystemTitle.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/SystemTitle.cs b/osu.Game/Screens/Menu/SystemTitle.cs index 967b928b43..b8510dde87 100644 --- a/osu.Game/Screens/Menu/SystemTitle.cs +++ b/osu.Game/Screens/Menu/SystemTitle.cs @@ -86,7 +86,6 @@ namespace osu.Game.Screens.Menu Current.BindValueChanged(_ => loadNewImage(), true); checkForUpdates(); - Scheduler.AddDelayed(checkForUpdates, TimeSpan.FromMinutes(15).TotalMilliseconds, true); } private void checkForUpdates() @@ -103,6 +102,8 @@ namespace osu.Game.Screens.Menu // the inner error will be logged by framework mechanisms anyway. if (r.IsFaulted) _ = r.Exception; + + Scheduler.AddDelayed(checkForUpdates, TimeSpan.FromMinutes(15).TotalMilliseconds); }); } From 0ea62d0c5d52f98c19062e8baba5e225daea8c5c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 15:16:42 +0900 Subject: [PATCH 2191/4092] Add initial additive blending on fade in --- osu.Game/Screens/Menu/SystemTitle.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/SystemTitle.cs b/osu.Game/Screens/Menu/SystemTitle.cs index b8510dde87..8b243e8f7f 100644 --- a/osu.Game/Screens/Menu/SystemTitle.cs +++ b/osu.Game/Screens/Menu/SystemTitle.cs @@ -121,7 +121,6 @@ namespace osu.Game.Screens.Menu if (!loaded.SystemTitle.Equals(Current.Value)) loaded.Dispose(); - loaded.FadeInFromZero(500, Easing.OutQuint); content.Add(currentImage = loaded); }, (cancellationTokenSource ??= new CancellationTokenSource()).Token); } @@ -154,16 +153,25 @@ namespace osu.Game.Screens.Menu { Texture = texture, Blending = BlendingParameters.Additive, - Alpha = 0, }, }; } - public void Flash() + protected override void LoadComplete() + { + base.LoadComplete(); + + this.FadeInFromZero(500, Easing.OutQuint); + flash.FadeOutFromOne(4000, Easing.OutQuint); + } + + public Drawable Flash() { flash.FadeInFromZero(50) .Then() .FadeOut(500, Easing.OutQuint); + + return this; } } } From 6684987289a6dcbd0ab918cd9976909bead57dc1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 15:18:21 +0900 Subject: [PATCH 2192/4092] Reduce refresh interval slightly --- osu.Game/Screens/Menu/SystemTitle.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/SystemTitle.cs b/osu.Game/Screens/Menu/SystemTitle.cs index 8b243e8f7f..bb623cacdf 100644 --- a/osu.Game/Screens/Menu/SystemTitle.cs +++ b/osu.Game/Screens/Menu/SystemTitle.cs @@ -103,7 +103,7 @@ namespace osu.Game.Screens.Menu if (r.IsFaulted) _ = r.Exception; - Scheduler.AddDelayed(checkForUpdates, TimeSpan.FromMinutes(15).TotalMilliseconds); + Scheduler.AddDelayed(checkForUpdates, TimeSpan.FromMinutes(5).TotalMilliseconds); }); } From 75b9d0fe6666eba914fc87f5428414d8003c3edf Mon Sep 17 00:00:00 2001 From: rushiiMachine <33725716+rushiiMachine@users.noreply.github.com> Date: Wed, 27 Dec 2023 23:02:45 -0800 Subject: [PATCH 2193/4092] Make flashlight scale with playfield When the playfield is shrunk with mods such as BarrelRoll, flashlight does not account for this, making it significantly easier to play. This makes it scale along with the playfield. --- .../Mods/TestSceneOsuModFlashlight.cs | 26 +++++++++++++++++++ osu.Game/Rulesets/Mods/ModFlashlight.cs | 13 +++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs index a353914cd5..2438038951 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs @@ -1,8 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; +using osu.Framework.Utils; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osuTK; namespace osu.Game.Rulesets.Osu.Tests.Mods { @@ -21,5 +26,26 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods [Test] public void TestComboBasedSize([Values] bool comboBasedSize) => CreateModTest(new ModTestData { Mod = new OsuModFlashlight { ComboBasedSize = { Value = comboBasedSize } }, PassCondition = () => true }); + + [Test] + public void TestPlayfieldBasedSize() + { + ModFlashlight mod = new OsuModFlashlight(); + CreateModTest(new ModTestData + { + Mod = mod, + PassCondition = () => + { + var flashlightOverlay = Player.DrawableRuleset.Overlays + .OfType.Flashlight>() + .First(); + + return Precision.AlmostEquals(mod.DefaultFlashlightSize * .5f, flashlightOverlay.GetSize()); + } + }); + + AddStep("adjust playfield scale", () => + Player.DrawableRuleset.Playfield.Scale = new Vector2(.5f)); + } } } diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 95406cc9e6..d714cd3c85 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -86,6 +86,7 @@ namespace osu.Game.Rulesets.Mods flashlight.Depth = float.MinValue; flashlight.Combo.BindTo(Combo); + flashlight.GetPlayfieldScale = () => drawableRuleset.Playfield.Scale; drawableRuleset.Overlays.Add(flashlight); } @@ -102,6 +103,8 @@ namespace osu.Game.Rulesets.Mods public override bool RemoveCompletedTransforms => false; + internal Func? GetPlayfieldScale; + private readonly float defaultFlashlightSize; private readonly float sizeMultiplier; private readonly bool comboBasedSize; @@ -141,10 +144,18 @@ namespace osu.Game.Rulesets.Mods protected abstract string FragmentShader { get; } - protected float GetSize() + public float GetSize() { float size = defaultFlashlightSize * sizeMultiplier; + if (GetPlayfieldScale != null) + { + Vector2 playfieldScale = GetPlayfieldScale(); + float rulesetScaleAvg = (playfieldScale.X + playfieldScale.Y) / 2f; + + size *= rulesetScaleAvg; + } + if (isBreakTime.Value) size *= 2.5f; else if (comboBasedSize) From a1867afbb43307ca775e56586bb6c86af038cc81 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 16:05:20 +0900 Subject: [PATCH 2194/4092] Move menu tips to main menu In preparation for removing the disclaimer screen. --- osu.Game/Configuration/OsuConfigManager.cs | 2 + osu.Game/Localisation/UserInterfaceStrings.cs | 7 +- .../UserInterface/MainMenuSettings.cs | 5 ++ osu.Game/Screens/Menu/Disclaimer.cs | 30 ------- osu.Game/Screens/Menu/MainMenu.cs | 38 +++++++- osu.Game/Screens/Menu/MenuTip.cs | 89 +++++++++++++++++++ osu.Game/Screens/Menu/SystemTitle.cs | 2 - 7 files changed, 138 insertions(+), 35 deletions(-) create mode 100644 osu.Game/Screens/Menu/MenuTip.cs diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 0df870655f..d4162b76d6 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -96,6 +96,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.MenuVoice, true); SetDefault(OsuSetting.MenuMusic, true); + SetDefault(OsuSetting.MenuTips, true); SetDefault(OsuSetting.AudioOffset, 0, -500.0, 500.0, 1); @@ -350,6 +351,7 @@ namespace osu.Game.Configuration VolumeInactive, MenuMusic, MenuVoice, + MenuTips, CursorRotation, MenuParallax, Prefer24HourTime, diff --git a/osu.Game/Localisation/UserInterfaceStrings.cs b/osu.Game/Localisation/UserInterfaceStrings.cs index 68c5c3ccbc..dceedca05c 100644 --- a/osu.Game/Localisation/UserInterfaceStrings.cs +++ b/osu.Game/Localisation/UserInterfaceStrings.cs @@ -24,6 +24,11 @@ namespace osu.Game.Localisation /// public static LocalisableString MenuCursorSize => new TranslatableString(getKey(@"menu_cursor_size"), @"Menu cursor size"); + /// + /// "Menu tips" + /// + public static LocalisableString ShowMenuTips => new TranslatableString(getKey(@"show_menu_tips"), @"Menu tips"); + /// /// "Parallax" /// @@ -154,6 +159,6 @@ namespace osu.Game.Localisation /// public static LocalisableString TrueRandom => new TranslatableString(getKey(@"true_random"), @"True Random"); - private static string getKey(string key) => $"{prefix}:{key}"; + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs index 4577fadb01..5e42c3035c 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs @@ -29,6 +29,11 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface Children = new Drawable[] { + new SettingsCheckbox + { + LabelText = UserInterfaceStrings.ShowMenuTips, + Current = config.GetBindable(OsuSetting.MenuTips) + }, new SettingsCheckbox { LabelText = UserInterfaceStrings.InterfaceVoices, diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index 539d58d2d7..1afef66313 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Screens; -using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Online.API; @@ -122,10 +121,6 @@ namespace osu.Game.Screens.Menu textFlow.NewParagraph(); textFlow.NewParagraph(); - textFlow.AddParagraph("today's tip:", formatSemiBold); - textFlow.AddParagraph(getRandomTip(), formatRegular); - textFlow.NewParagraph(); - textFlow.NewParagraph(); iconColour = colours.Yellow; @@ -228,30 +223,5 @@ namespace osu.Game.Screens.Menu this.Push(nextScreen); }); } - - private string getRandomTip() - { - string[] tips = - { - "You can press Ctrl-T anywhere in the game to toggle the toolbar!", - "You can press Ctrl-O anywhere in the game to access options!", - "All settings are dynamic and take effect in real-time. Try pausing and changing the skin while playing!", - "New features are coming online every update. Make sure to stay up-to-date!", - "If you find the UI too large or small, try adjusting UI scale in settings!", - "Try adjusting the \"Screen Scaling\" mode to change your gameplay or UI area, even in fullscreen!", - "What used to be \"osu!direct\" is available to all users just like on the website. You can access it anywhere using Ctrl-B!", - "Seeking in replays is available by dragging on the difficulty bar at the bottom of the screen!", - "Multithreading support means that even with low \"FPS\" your input and judgements will be accurate!", - "Try scrolling down in the mod select panel to find a bunch of new fun mods!", - "Most of the web content (profiles, rankings, etc.) are available natively in-game from the icons on the toolbar!", - "Get more details, hide or delete a beatmap by right-clicking on its panel at song select!", - "All delete operations are temporary until exiting. Restore accidentally deleted content from the maintenance settings!", - "Check out the \"playlists\" system, which lets users create their own custom and permanent leaderboards!", - "Toggle advanced frame / thread statistics with Ctrl-F11!", - "Take a look under the hood at performance counters and enable verbose performance logging with Ctrl-F2!", - }; - - return tips[RNG.Next(0, tips.Length)]; - } } } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 0739689daa..cde7da53ce 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -95,6 +95,8 @@ namespace osu.Game.Screens.Menu private SongTicker songTicker; private Container logoTarget; private SystemTitle systemTitle; + private MenuTip menuTip; + private FillFlowContainer bottomElementsFlow; private Sample reappearSampleSwoosh; @@ -157,7 +159,27 @@ namespace osu.Game.Screens.Menu Margin = new MarginPadding { Right = 15, Top = 5 } }, new KiaiMenuFountains(), - systemTitle = new SystemTitle(), + bottomElementsFlow = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Spacing = new Vector2(15), + Children = new Drawable[] + { + menuTip = new MenuTip + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, + systemTitle = new SystemTitle + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + } + } + }, holdToExitGameOverlay?.CreateProxy() ?? Empty() }); @@ -220,6 +242,8 @@ namespace osu.Game.Screens.Menu if (storage is OsuStorage osuStorage && osuStorage.Error != OsuStorageError.None) dialogOverlay?.Push(new StorageErrorDialog(osuStorage, osuStorage.Error)); + + menuTip.ShowNextTip(); } [CanBeNull] @@ -272,7 +296,7 @@ namespace osu.Game.Screens.Menu { base.Update(); - systemTitle.Margin = new MarginPadding + bottomElementsFlow.Margin = new MarginPadding { Bottom = (versionManager?.DrawHeight + 5) ?? 0 }; @@ -314,6 +338,10 @@ namespace osu.Game.Screens.Menu buttonsContainer.MoveTo(new Vector2(-800, 0), FADE_OUT_DURATION, Easing.InSine); sideFlashes.FadeOut(64, Easing.OutQuint); + + bottomElementsFlow + .ScaleTo(0.9f, 1000, Easing.OutQuint) + .FadeOut(500, Easing.OutQuint); } public override void OnResuming(ScreenTransitionEvent e) @@ -330,6 +358,12 @@ namespace osu.Game.Screens.Menu preloadSongSelect(); musicController.EnsurePlayingSomething(); + + menuTip.ShowNextTip(); + + bottomElementsFlow + .ScaleTo(1, 1000, Easing.OutQuint) + .FadeIn(1000, Easing.OutQuint); } public override bool OnExiting(ScreenExitEvent e) diff --git a/osu.Game/Screens/Menu/MenuTip.cs b/osu.Game/Screens/Menu/MenuTip.cs new file mode 100644 index 0000000000..c9c8fea1c1 --- /dev/null +++ b/osu.Game/Screens/Menu/MenuTip.cs @@ -0,0 +1,89 @@ +// 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.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Utils; +using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osuTK; + +namespace osu.Game.Screens.Menu +{ + public partial class MenuTip : CompositeDrawable + { + [Resolved] + private OsuConfigManager config { get; set; } = null!; + + private LinkFlowContainer textFlow = null!; + + [BackgroundDependencyLoader] + private void load() + { + AutoSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + textFlow = new LinkFlowContainer + { + Width = 700, + AutoSizeAxes = Axes.Y, + TextAnchor = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Spacing = new Vector2(0, 2), + }, + }; + } + + public void ShowNextTip() + { + if (!config.Get(OsuSetting.MenuTips)) return; + + static void formatRegular(SpriteText t) => t.Font = OsuFont.GetFont(size: 20, weight: FontWeight.Regular); + static void formatSemiBold(SpriteText t) => t.Font = OsuFont.GetFont(size: 20, weight: FontWeight.SemiBold); + + string tip = getRandomTip(); + + AutoSizeAxes = Axes.Both; + + textFlow.Clear(); + textFlow.AddParagraph("a tip for you:", formatSemiBold); + textFlow.AddParagraph(tip, formatRegular); + + this.FadeInFromZero(200, Easing.OutQuint) + .Delay(1000 + 80 * tip.Length) + .Then() + .FadeOutFromOne(2000, Easing.OutQuint) + .Finally(_ => AutoSizeAxes = Axes.X); + } + + private string getRandomTip() + { + string[] tips = + { + "You can press Ctrl-T anywhere in the game to toggle the toolbar!", + "You can press Ctrl-O anywhere in the game to access options!", + "All settings are dynamic and take effect in real-time. Try changing the skin while watching autoplay!", + "New features are coming online every update. Make sure to stay up-to-date!", + "If you find the UI too large or small, try adjusting UI scale in settings!", + "Try adjusting the \"Screen Scaling\" mode to change your gameplay or UI area, even in fullscreen!", + "What used to be \"osu!direct\" is available to all users just like on the website. You can access it anywhere using Ctrl-B!", + "Seeking in replays is available by dragging on the difficulty bar at the bottom of the screen!", + "Multithreading support means that even with low \"FPS\" your input and judgements will be accurate!", + "Try scrolling down in the mod select panel to find a bunch of new fun mods!", + "Most of the web content (profiles, rankings, etc.) are available natively in-game from the icons on the toolbar!", + "Get more details, hide or delete a beatmap by right-clicking on its panel at song select!", + "All delete operations are temporary until exiting. Restore accidentally deleted content from the maintenance settings!", + "Check out the \"playlists\" system, which lets users create their own custom and permanent leaderboards!", + "Toggle advanced frame / thread statistics with Ctrl-F11!", + "Take a look under the hood at performance counters and enable verbose performance logging with Ctrl-F2!", + }; + + return tips[RNG.Next(0, tips.Length)]; + } + } +} diff --git a/osu.Game/Screens/Menu/SystemTitle.cs b/osu.Game/Screens/Menu/SystemTitle.cs index bb623cacdf..060e426f8c 100644 --- a/osu.Game/Screens/Menu/SystemTitle.cs +++ b/osu.Game/Screens/Menu/SystemTitle.cs @@ -31,8 +31,6 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader] private void load(OsuGame? game) { - Anchor = Anchor.BottomCentre; - Origin = Anchor.BottomCentre; AutoSizeAxes = Axes.Both; InternalChild = content = new OsuClickableContainer From 222459d921f886a7dd621057752a94442bc703d6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 16:16:27 +0900 Subject: [PATCH 2195/4092] Add background and improve layout --- osu.Game/Screens/Menu/MainMenu.cs | 2 +- osu.Game/Screens/Menu/MenuTip.cs | 32 ++++++++++++++++++++++--------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index cde7da53ce..e8554d077b 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -165,7 +165,7 @@ namespace osu.Game.Screens.Menu Direction = FillDirection.Vertical, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - Spacing = new Vector2(15), + Spacing = new Vector2(5), Children = new Drawable[] { menuTip = new MenuTip diff --git a/osu.Game/Screens/Menu/MenuTip.cs b/osu.Game/Screens/Menu/MenuTip.cs index c9c8fea1c1..ac23162bd0 100644 --- a/osu.Game/Screens/Menu/MenuTip.cs +++ b/osu.Game/Screens/Menu/MenuTip.cs @@ -4,12 +4,14 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Menu { @@ -27,14 +29,29 @@ namespace osu.Game.Screens.Menu InternalChildren = new Drawable[] { + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerExponent = 2.5f, + CornerRadius = 15, + Children = new Drawable[] + { + new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + Alpha = 0.4f, + }, + } + }, textFlow = new LinkFlowContainer { - Width = 700, + Width = 600, AutoSizeAxes = Axes.Y, TextAnchor = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, Spacing = new Vector2(0, 2), + Margin = new MarginPadding(10) }, }; } @@ -43,13 +60,11 @@ namespace osu.Game.Screens.Menu { if (!config.Get(OsuSetting.MenuTips)) return; - static void formatRegular(SpriteText t) => t.Font = OsuFont.GetFont(size: 20, weight: FontWeight.Regular); - static void formatSemiBold(SpriteText t) => t.Font = OsuFont.GetFont(size: 20, weight: FontWeight.SemiBold); + static void formatRegular(SpriteText t) => t.Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular); + static void formatSemiBold(SpriteText t) => t.Font = OsuFont.GetFont(size: 16, weight: FontWeight.SemiBold); string tip = getRandomTip(); - AutoSizeAxes = Axes.Both; - textFlow.Clear(); textFlow.AddParagraph("a tip for you:", formatSemiBold); textFlow.AddParagraph(tip, formatRegular); @@ -57,8 +72,7 @@ namespace osu.Game.Screens.Menu this.FadeInFromZero(200, Easing.OutQuint) .Delay(1000 + 80 * tip.Length) .Then() - .FadeOutFromOne(2000, Easing.OutQuint) - .Finally(_ => AutoSizeAxes = Axes.X); + .FadeOutFromOne(2000, Easing.OutQuint); } private string getRandomTip() From 932d03a4f8aebf8b76d43fad4c7b8ffa17e0ae95 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 16:19:12 +0900 Subject: [PATCH 2196/4092] Make toggle more immediately hide/show tips --- osu.Game/Screens/Menu/MainMenu.cs | 3 +-- osu.Game/Screens/Menu/MenuTip.cs | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index e8554d077b..e3fc69dc2f 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -242,8 +242,6 @@ namespace osu.Game.Screens.Menu if (storage is OsuStorage osuStorage && osuStorage.Error != OsuStorageError.None) dialogOverlay?.Push(new StorageErrorDialog(osuStorage, osuStorage.Error)); - - menuTip.ShowNextTip(); } [CanBeNull] @@ -359,6 +357,7 @@ namespace osu.Game.Screens.Menu musicController.EnsurePlayingSomething(); + // Cycle tip on resuming menuTip.ShowNextTip(); bottomElementsFlow diff --git a/osu.Game/Screens/Menu/MenuTip.cs b/osu.Game/Screens/Menu/MenuTip.cs index ac23162bd0..325fb07767 100644 --- a/osu.Game/Screens/Menu/MenuTip.cs +++ b/osu.Game/Screens/Menu/MenuTip.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.Shapes; @@ -22,6 +23,8 @@ namespace osu.Game.Screens.Menu private LinkFlowContainer textFlow = null!; + private Bindable showMenuTips = null!; + [BackgroundDependencyLoader] private void load() { @@ -56,9 +59,21 @@ namespace osu.Game.Screens.Menu }; } + protected override void LoadComplete() + { + base.LoadComplete(); + + showMenuTips = config.GetBindable(OsuSetting.MenuTips); + showMenuTips.BindValueChanged(_ => ShowNextTip(), true); + } + public void ShowNextTip() { - if (!config.Get(OsuSetting.MenuTips)) return; + if (!showMenuTips.Value) + { + this.FadeOut(100, Easing.OutQuint); + return; + } static void formatRegular(SpriteText t) => t.Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular); static void formatSemiBold(SpriteText t) => t.Font = OsuFont.GetFont(size: 16, weight: FontWeight.SemiBold); From 0ad6ac8b2a2866d5b26cb044110122cd4702cd8b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 16:48:17 +0900 Subject: [PATCH 2197/4092] Remove unused variable --- osu.Game/Screens/Menu/MainMenu.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index e3fc69dc2f..a90d9151b5 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -94,7 +94,6 @@ namespace osu.Game.Screens.Menu private ParallaxContainer buttonsContainer; private SongTicker songTicker; private Container logoTarget; - private SystemTitle systemTitle; private MenuTip menuTip; private FillFlowContainer bottomElementsFlow; @@ -173,7 +172,7 @@ namespace osu.Game.Screens.Menu Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, }, - systemTitle = new SystemTitle + new SystemTitle { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, From b19f72481b5d3cd1ad81458601aca0404a09d8fe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 17:19:41 +0900 Subject: [PATCH 2198/4092] Fade out quickly on game exit sequence --- osu.Game/Screens/Menu/MainMenu.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index a90d9151b5..bc19e9cb63 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -399,6 +399,10 @@ namespace osu.Game.Screens.Menu songTicker.Hide(); this.FadeOut(3000); + + bottomElementsFlow + .FadeOut(500, Easing.OutQuint); + return base.OnExiting(e); } From 1f2339244ef739341e3429d88678e512430a47d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 17:14:16 +0900 Subject: [PATCH 2199/4092] Add supporter display to main menu --- .../Visual/Menus/TestSceneSupporterDisplay.cs | 37 +++++ osu.Game/Screens/Menu/MainMenu.cs | 13 ++ osu.Game/Screens/Menu/SupporterDisplay.cs | 126 ++++++++++++++++++ 3 files changed, 176 insertions(+) create mode 100644 osu.Game.Tests/Visual/Menus/TestSceneSupporterDisplay.cs create mode 100644 osu.Game/Screens/Menu/SupporterDisplay.cs diff --git a/osu.Game.Tests/Visual/Menus/TestSceneSupporterDisplay.cs b/osu.Game.Tests/Visual/Menus/TestSceneSupporterDisplay.cs new file mode 100644 index 0000000000..8b18adbe0d --- /dev/null +++ b/osu.Game.Tests/Visual/Menus/TestSceneSupporterDisplay.cs @@ -0,0 +1,37 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Screens.Menu; + +namespace osu.Game.Tests.Visual.Menus +{ + public partial class TestSceneSupporterDisplay : OsuTestScene + { + [Test] + public void TestBasic() + { + AddStep("create display", () => + { + Child = new SupporterDisplay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + }); + + AddStep("toggle support", () => + { + ((DummyAPIAccess)API).LocalUser.Value = new APIUser + { + Username = API.LocalUser.Value.Username, + Id = API.LocalUser.Value.Id + 1, + IsSupporter = !API.LocalUser.Value.IsSupporter, + }; + }); + } + } +} diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index bc19e9cb63..401460a498 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -96,6 +96,7 @@ namespace osu.Game.Screens.Menu private Container logoTarget; private MenuTip menuTip; private FillFlowContainer bottomElementsFlow; + private SupporterDisplay supporterDisplay; private Sample reappearSampleSwoosh; @@ -179,6 +180,12 @@ namespace osu.Game.Screens.Menu } } }, + supporterDisplay = new SupporterDisplay + { + Margin = new MarginPadding(5), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + }, holdToExitGameOverlay?.CreateProxy() ?? Empty() }); @@ -339,6 +346,9 @@ namespace osu.Game.Screens.Menu bottomElementsFlow .ScaleTo(0.9f, 1000, Easing.OutQuint) .FadeOut(500, Easing.OutQuint); + + supporterDisplay + .FadeOut(500, Easing.OutQuint); } public override void OnResuming(ScreenTransitionEvent e) @@ -403,6 +413,9 @@ namespace osu.Game.Screens.Menu bottomElementsFlow .FadeOut(500, Easing.OutQuint); + supporterDisplay + .FadeOut(500, Easing.OutQuint); + return base.OnExiting(e); } diff --git a/osu.Game/Screens/Menu/SupporterDisplay.cs b/osu.Game/Screens/Menu/SupporterDisplay.cs new file mode 100644 index 0000000000..d8ba21cd88 --- /dev/null +++ b/osu.Game/Screens/Menu/SupporterDisplay.cs @@ -0,0 +1,126 @@ +// 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.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Menu +{ + public partial class SupporterDisplay : CompositeDrawable + { + private LinkFlowContainer supportFlow = null!; + + private Drawable heart = null!; + + private readonly IBindable currentUser = new Bindable(); + + private Box backgroundBox = null!; + + [Resolved] + private IAPIProvider api { get; set; } = null!; + + [Resolved] + private OsuColour colours { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load() + { + Height = 40; + + AutoSizeAxes = Axes.X; + AutoSizeDuration = 1000; + AutoSizeEasing = Easing.OutQuint; + + Masking = true; + CornerExponent = 2.5f; + CornerRadius = 15; + + InternalChildren = new Drawable[] + { + backgroundBox = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.4f, + }, + supportFlow = new LinkFlowContainer + { + AutoSizeAxes = Axes.Both, + Padding = new MarginPadding(10), + Spacing = new Vector2(0, 2), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + const float font_size = 14; + + static void formatSemiBold(SpriteText t) => t.Font = OsuFont.GetFont(size: font_size, weight: FontWeight.SemiBold); + + currentUser.BindTo(api.LocalUser); + currentUser.BindValueChanged(e => + { + supportFlow.Children.ForEach(d => d.FadeOut().Expire()); + + if (e.NewValue.IsSupporter) + { + supportFlow.AddText("Eternal thanks to you for supporting osu!", formatSemiBold); + + backgroundBox.FadeColour(colours.Pink, 250); + } + else + { + supportFlow.AddText("Consider becoming an ", formatSemiBold); + supportFlow.AddLink("osu!supporter", "https://osu.ppy.sh/home/support", formatSemiBold); + supportFlow.AddText(" to help support osu!'s development", formatSemiBold); + + backgroundBox.FadeColour(colours.Pink4, 250); + } + + supportFlow.AddIcon(FontAwesome.Solid.Heart, t => + { + heart = t; + + t.Padding = new MarginPadding { Left = 5, Top = 1 }; + t.Font = t.Font.With(size: font_size); + t.Origin = Anchor.Centre; + t.Colour = colours.Pink; + + Schedule(() => + { + heart?.FlashColour(Color4.White, 750, Easing.OutQuint).Loop(); + }); + }); + }, true); + + this + .FadeOut() + .Delay(1000) + .FadeInFromZero(800, Easing.OutQuint); + + Scheduler.AddDelayed(() => + { + AutoSizeEasing = Easing.In; + supportFlow.BypassAutoSizeAxes = Axes.X; + this + .Delay(200) + .FadeOut(750, Easing.Out); + }, 6000); + } + } +} From 7dc50b9baf5e6268c84b4177d3f6b14bbaa99514 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 17:18:47 +0900 Subject: [PATCH 2200/4092] Don't dismiss on hover, and allow dismissing via click --- osu.Game/Screens/Menu/SupporterDisplay.cs | 43 ++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/SupporterDisplay.cs b/osu.Game/Screens/Menu/SupporterDisplay.cs index d8ba21cd88..6639300f4a 100644 --- a/osu.Game/Screens/Menu/SupporterDisplay.cs +++ b/osu.Game/Screens/Menu/SupporterDisplay.cs @@ -8,6 +8,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Online.API; @@ -113,8 +115,47 @@ namespace osu.Game.Screens.Menu .Delay(1000) .FadeInFromZero(800, Easing.OutQuint); - Scheduler.AddDelayed(() => + scheduleDismissal(); + } + + protected override bool OnClick(ClickEvent e) + { + dismissalDelegate?.Cancel(); + + supportFlow.BypassAutoSizeAxes = Axes.X; + this.FadeOut(500, Easing.OutQuint); + return base.OnClick(e); + } + + protected override bool OnHover(HoverEvent e) + { + backgroundBox.FadeTo(0.6f, 500, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + backgroundBox.FadeTo(0.4f, 500, Easing.OutQuint); + base.OnHoverLost(e); + } + + private ScheduledDelegate? dismissalDelegate; + + private void scheduleDismissal() + { + dismissalDelegate?.Cancel(); + dismissalDelegate = Scheduler.AddDelayed(() => { + // If the user is hovering they may want to interact with the link. + // Give them more time. + if (IsHovered) + { + scheduleDismissal(); + return; + } + + dismissalDelegate?.Cancel(); + AutoSizeEasing = Easing.In; supportFlow.BypassAutoSizeAxes = Axes.X; this From bd0e2b4dde99cd20d990959ba52965cc73099977 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 17:21:29 +0900 Subject: [PATCH 2201/4092] Remove disclaimer screen completely --- .../Visual/Menus/TestSceneDisclaimer.cs | 29 --- osu.Game/Screens/Loader.cs | 13 +- osu.Game/Screens/Menu/Disclaimer.cs | 227 ------------------ 3 files changed, 2 insertions(+), 267 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs delete mode 100644 osu.Game/Screens/Menu/Disclaimer.cs diff --git a/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs b/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs deleted file mode 100644 index fb82b0df80..0000000000 --- a/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs +++ /dev/null @@ -1,29 +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.Framework.Allocation; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests.Responses; -using osu.Game.Screens.Menu; - -namespace osu.Game.Tests.Visual.Menus -{ - public partial class TestSceneDisclaimer : ScreenTestScene - { - [BackgroundDependencyLoader] - private void load() - { - AddStep("load disclaimer", () => LoadScreen(new Disclaimer())); - - AddStep("toggle support", () => - { - ((DummyAPIAccess)API).LocalUser.Value = new APIUser - { - Username = API.LocalUser.Value.Username, - Id = API.LocalUser.Value.Id + 1, - IsSupporter = !API.LocalUser.Value.IsSupporter, - }; - }); - } - } -} diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index 372cfe748e..4dba512cbd 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -21,8 +21,6 @@ namespace osu.Game.Screens { public partial class Loader : StartupScreen { - private bool showDisclaimer; - public Loader() { ValidForResume = false; @@ -35,13 +33,7 @@ namespace osu.Game.Screens private LoadingSpinner spinner; private ScheduledDelegate spinnerShow; - protected virtual OsuScreen CreateLoadableScreen() - { - if (showDisclaimer) - return new Disclaimer(getIntroSequence()); - - return getIntroSequence(); - } + protected virtual OsuScreen CreateLoadableScreen() => getIntroSequence(); private IntroScreen getIntroSequence() { @@ -107,9 +99,8 @@ namespace osu.Game.Screens } [BackgroundDependencyLoader] - private void load(OsuGameBase game, OsuConfigManager config) + private void load(OsuConfigManager config) { - showDisclaimer = game.IsDeployedBuild; introSequence = config.Get(OsuSetting.IntroSequence); } diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs deleted file mode 100644 index 1afef66313..0000000000 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ /dev/null @@ -1,227 +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 System.Collections.Generic; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Screens; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests.Responses; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Screens.Menu -{ - public partial class Disclaimer : StartupScreen - { - private SpriteIcon icon; - private Color4 iconColour; - private LinkFlowContainer textFlow; - private LinkFlowContainer supportFlow; - - private Drawable heart; - - private const float icon_y = -85; - private const float icon_size = 30; - - private readonly OsuScreen nextScreen; - - private readonly Bindable currentUser = new Bindable(); - private FillFlowContainer fill; - - private readonly List expendableText = new List(); - - public Disclaimer(OsuScreen nextScreen = null) - { - this.nextScreen = nextScreen; - ValidForResume = false; - } - - [Resolved] - private IAPIProvider api { get; set; } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - InternalChildren = new Drawable[] - { - icon = new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Icon = OsuIcon.Logo, - Size = new Vector2(icon_size), - Y = icon_y, - }, - fill = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Y = icon_y, - Anchor = Anchor.Centre, - Origin = Anchor.TopCentre, - Children = new Drawable[] - { - textFlow = new LinkFlowContainer - { - Width = 680, - AutoSizeAxes = Axes.Y, - TextAnchor = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Spacing = new Vector2(0, 2), - }, - } - }, - supportFlow = new LinkFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - TextAnchor = Anchor.BottomCentre, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Padding = new MarginPadding(20), - Alpha = 0, - Spacing = new Vector2(0, 2), - }, - }; - - textFlow.AddText("this is osu!", t => t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.Regular)); - - expendableText.Add(textFlow.AddText("lazer", t => - { - t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.Regular); - t.Colour = colours.PinkLight; - })); - - static void formatRegular(SpriteText t) => t.Font = OsuFont.GetFont(size: 20, weight: FontWeight.Regular); - static void formatSemiBold(SpriteText t) => t.Font = OsuFont.GetFont(size: 20, weight: FontWeight.SemiBold); - - textFlow.NewParagraph(); - - textFlow.AddText("the next ", formatRegular); - textFlow.AddText("major update", t => - { - t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.SemiBold); - t.Colour = colours.Pink; - }); - expendableText.Add(textFlow.AddText(" coming to osu!", formatRegular)); - textFlow.AddText(".", formatRegular); - - textFlow.NewParagraph(); - textFlow.NewParagraph(); - - textFlow.NewParagraph(); - - iconColour = colours.Yellow; - - // manually transfer the user once, but only do the final bind in LoadComplete to avoid thread woes (API scheduler could run while this screen is still loading). - // the manual transfer is here to ensure all text content is loaded ahead of time as this is very early in the game load process and we want to avoid stutters. - currentUser.Value = api.LocalUser.Value; - currentUser.BindValueChanged(e => - { - supportFlow.Children.ForEach(d => d.FadeOut().Expire()); - - if (e.NewValue.IsSupporter) - { - supportFlow.AddText("Eternal thanks to you for supporting osu!", formatSemiBold); - } - else - { - supportFlow.AddText("Consider becoming an ", formatSemiBold); - supportFlow.AddLink("osu!supporter", "https://osu.ppy.sh/home/support", formatSemiBold); - supportFlow.AddText(" to help support osu!'s development", formatSemiBold); - } - - supportFlow.AddIcon(FontAwesome.Solid.Heart, t => - { - heart = t; - - t.Padding = new MarginPadding { Left = 5, Top = 3 }; - t.Font = t.Font.With(size: 20); - t.Origin = Anchor.Centre; - t.Colour = colours.Pink; - - Schedule(() => heart?.FlashColour(Color4.White, 750, Easing.OutQuint).Loop()); - }); - - if (supportFlow.IsPresent) - supportFlow.FadeInFromZero(500); - }, true); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - if (nextScreen != null) - LoadComponentAsync(nextScreen); - - ((IBindable)currentUser).BindTo(api.LocalUser); - } - - public override void OnSuspending(ScreenTransitionEvent e) - { - base.OnSuspending(e); - - // Once this screen has finished being displayed, we don't want to unnecessarily handle user change events. - currentUser.UnbindAll(); - } - - public override void OnEntering(ScreenTransitionEvent e) - { - base.OnEntering(e); - - icon.RotateTo(10); - icon.FadeOut(); - icon.ScaleTo(0.5f); - - icon.Delay(500).FadeIn(500).ScaleTo(1, 500, Easing.OutQuint); - - using (BeginDelayedSequence(3000)) - { - icon.FadeColour(iconColour, 200, Easing.OutQuint); - icon.MoveToY(icon_y * 1.3f, 500, Easing.OutCirc) - .RotateTo(-360, 520, Easing.OutQuint) - .Then() - .MoveToY(icon_y, 160, Easing.InQuart) - .FadeColour(Color4.White, 160); - - using (BeginDelayedSequence(520 + 160)) - { - fill.MoveToOffset(new Vector2(0, 15), 160, Easing.OutQuart); - Schedule(() => expendableText.SelectMany(t => t.Drawables).ForEach(t => - { - t.FadeOut(100); - t.ScaleTo(new Vector2(0, 1), 100, Easing.OutQuart); - })); - } - } - - supportFlow.FadeOut().Delay(2000).FadeIn(500); - double delay = 500; - foreach (var c in textFlow.Children) - c.FadeTo(0.001f).Delay(delay += 20).FadeIn(500); - - this - .FadeInFromZero(500) - .Then(5500) - .FadeOut(250) - .ScaleTo(0.9f, 250, Easing.InQuint) - .Finally(_ => - { - if (nextScreen != null) - this.Push(nextScreen); - }); - } - } -} From 2ec9343868693f45f7650e2209271b157222d973 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 17:35:10 +0900 Subject: [PATCH 2202/4092] Add the ability to spectate a user by right clicking their user panel --- osu.Game/Localisation/ContextMenuStrings.cs | 9 +++-- osu.Game/Users/UserPanel.cs | 39 +++++++++++++-------- 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/osu.Game/Localisation/ContextMenuStrings.cs b/osu.Game/Localisation/ContextMenuStrings.cs index 029fba67d8..cb18a2159c 100644 --- a/osu.Game/Localisation/ContextMenuStrings.cs +++ b/osu.Game/Localisation/ContextMenuStrings.cs @@ -20,9 +20,14 @@ namespace osu.Game.Localisation public static LocalisableString ViewBeatmap => new TranslatableString(getKey(@"view_beatmap"), @"View beatmap"); /// - /// "Invite player" + /// "Invite to room" /// - public static LocalisableString InvitePlayer => new TranslatableString(getKey(@"invite_player"), @"Invite player"); + public static LocalisableString InvitePlayer => new TranslatableString(getKey(@"invite_player"), @"Invite to room"); + + /// + /// "Spectate" + /// + public static LocalisableString SpectatePlayer => new TranslatableString(getKey(@"spectate_player"), @"Spectate"); private static string getKey(string key) => $@"{prefix}:{key}"; } diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index 273faf9bd1..b6a77e754d 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -13,6 +13,7 @@ using osu.Game.Overlays; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; using osu.Framework.Graphics.Cursor; +using osu.Framework.Screens; using osu.Game.Graphics.Containers; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; @@ -20,6 +21,8 @@ using osu.Game.Online.Chat; using osu.Game.Resources.Localisation.Web; using osu.Game.Localisation; using osu.Game.Online.Multiplayer; +using osu.Game.Screens; +using osu.Game.Screens.Play; namespace osu.Game.Users { @@ -60,6 +63,9 @@ namespace osu.Game.Users [Resolved] protected OverlayColourProvider? ColourProvider { get; private set; } + [Resolved] + private IPerformFromScreenRunner? performer { get; set; } + [Resolved] protected OsuColour Colours { get; private set; } = null!; @@ -113,23 +119,26 @@ namespace osu.Game.Users new OsuMenuItem(ContextMenuStrings.ViewProfile, MenuItemType.Highlighted, ViewProfile) }; - if (!User.Equals(api.LocalUser.Value)) - { - items.Add(new OsuMenuItem(UsersStrings.CardSendMessage, MenuItemType.Standard, () => - { - channelManager?.OpenPrivateChannel(User); - chatOverlay?.Show(); - })); - } + if (User.Equals(api.LocalUser.Value)) + return items.ToArray(); - if ( - // TODO: uncomment this once lazer / osu-web is updating online states - // User.IsOnline && - multiplayerClient?.Room != null && - multiplayerClient.Room.Users.All(u => u.UserID != User.Id) - ) + items.Add(new OsuMenuItem(UsersStrings.CardSendMessage, MenuItemType.Standard, () => { - items.Add(new OsuMenuItem(ContextMenuStrings.InvitePlayer, MenuItemType.Standard, () => multiplayerClient.InvitePlayer(User.Id))); + channelManager?.OpenPrivateChannel(User); + chatOverlay?.Show(); + })); + + if (User.IsOnline) + { + items.Add(new OsuMenuItem(ContextMenuStrings.SpectatePlayer, MenuItemType.Standard, () => + { + performer?.PerformFromScreen(s => s.Push(new SoloSpectatorScreen(User))); + })); + + if (multiplayerClient?.Room?.Users.All(u => u.UserID != User.Id) == true) + { + items.Add(new OsuMenuItem(ContextMenuStrings.InvitePlayer, MenuItemType.Standard, () => multiplayerClient.InvitePlayer(User.Id))); + } } return items.ToArray(); From 22eced33006f959af2bc17198eafd46779a4923b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 17:40:07 +0900 Subject: [PATCH 2203/4092] Show local user in online users --- osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs index 37ea3f9c38..ee277ff538 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs @@ -131,9 +131,6 @@ namespace osu.Game.Overlays.Dashboard { int userId = kvp.Key; - if (userId == api.LocalUser.Value.Id) - continue; - users.GetUserAsync(userId).ContinueWith(task => { APIUser user = task.GetResultSafely(); From 28e220ca501592f854aab01b294981ca4d83154e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 19:04:19 +0900 Subject: [PATCH 2204/4092] Update popup dialog design Had to be done. I hated the old ones so much. As usual, disclaimer that this is an iterative design and will probably be replaced in the future. --- .../UserInterface/TestSceneDialogOverlay.cs | 19 ++--- .../Graphics/UserInterface/DialogButton.cs | 14 ++-- osu.Game/Overlays/Dialog/PopupDialog.cs | 77 +++++++++++-------- osu.Game/Overlays/DialogOverlay.cs | 12 +-- 4 files changed, 68 insertions(+), 54 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs index 81b692004b..3b38343f04 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs @@ -9,6 +9,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Testing; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; @@ -19,11 +20,15 @@ namespace osu.Game.Tests.Visual.UserInterface { private DialogOverlay overlay; + [SetUpSteps] + public void SetUpSteps() + { + AddStep("create dialog overlay", () => Child = overlay = new DialogOverlay()); + } + [Test] public void TestBasic() { - AddStep("create dialog overlay", () => Child = overlay = new DialogOverlay()); - TestPopupDialog firstDialog = null; TestPopupDialog secondDialog = null; @@ -84,7 +89,7 @@ namespace osu.Game.Tests.Visual.UserInterface })); AddAssert("second dialog displayed", () => overlay.CurrentDialog == secondDialog); - AddAssert("first dialog is not part of hierarchy", () => firstDialog.Parent == null); + AddUntilStep("first dialog is not part of hierarchy", () => firstDialog.Parent == null); } [Test] @@ -92,7 +97,7 @@ namespace osu.Game.Tests.Visual.UserInterface { PopupDialog dialog = null; - AddStep("create dialog overlay", () => overlay = new SlowLoadingDialogOverlay()); + AddStep("create slow loading dialog overlay", () => overlay = new SlowLoadingDialogOverlay()); AddStep("start loading overlay", () => LoadComponentAsync(overlay, Add)); @@ -128,8 +133,6 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestDismissBeforePush() { - AddStep("create dialog overlay", () => Child = overlay = new DialogOverlay()); - TestPopupDialog testDialog = null; AddStep("dismissed dialog push", () => { @@ -146,8 +149,6 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestDismissBeforePushViaButtonPress() { - AddStep("create dialog overlay", () => Child = overlay = new DialogOverlay()); - TestPopupDialog testDialog = null; AddStep("dismissed dialog push", () => { @@ -163,7 +164,7 @@ namespace osu.Game.Tests.Visual.UserInterface }); AddAssert("no dialog pushed", () => overlay.CurrentDialog == null); - AddAssert("dialog is not part of hierarchy", () => testDialog.Parent == null); + AddUntilStep("dialog is not part of hierarchy", () => testDialog.Parent == null); } private partial class TestPopupDialog : PopupDialog diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs index db81bc991d..c920597a95 100644 --- a/osu.Game/Graphics/UserInterface/DialogButton.cs +++ b/osu.Game/Graphics/UserInterface/DialogButton.cs @@ -25,7 +25,7 @@ namespace osu.Game.Graphics.UserInterface private const float idle_width = 0.8f; private const float hover_width = 0.9f; - private const float hover_duration = 500; + private const float hover_duration = 300; private const float click_duration = 200; public event Action? StateChanged; @@ -54,7 +54,7 @@ namespace osu.Game.Graphics.UserInterface private readonly Box rightGlow; private readonly Box background; private readonly SpriteText spriteText; - private Vector2 hoverSpacing => new Vector2(3f, 0f); + private Vector2 hoverSpacing => new Vector2(1.4f, 0f); public DialogButton(HoverSampleSet sampleSet = HoverSampleSet.Button) : base(sampleSet) @@ -279,15 +279,15 @@ namespace osu.Game.Graphics.UserInterface if (newState == SelectionState.Selected) { - spriteText.TransformSpacingTo(hoverSpacing, hover_duration, Easing.OutElastic); - ColourContainer.ResizeWidthTo(hover_width, hover_duration, Easing.OutElastic); + spriteText.TransformSpacingTo(hoverSpacing, hover_duration, Easing.OutQuint); + ColourContainer.ResizeWidthTo(hover_width, hover_duration, Easing.OutQuint); glowContainer.FadeIn(hover_duration, Easing.OutQuint); } else { - ColourContainer.ResizeWidthTo(idle_width, hover_duration, Easing.OutElastic); - spriteText.TransformSpacingTo(Vector2.Zero, hover_duration, Easing.OutElastic); - glowContainer.FadeOut(hover_duration, Easing.OutQuint); + ColourContainer.ResizeWidthTo(idle_width, hover_duration / 2, Easing.OutQuint); + spriteText.TransformSpacingTo(Vector2.Zero, hover_duration / 2, Easing.OutQuint); + glowContainer.FadeOut(hover_duration / 2, Easing.OutQuint); } } diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index 36a9baac67..663c2e78ce 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Localisation; +using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; using osuTK; @@ -25,11 +26,10 @@ namespace osu.Game.Overlays.Dialog public abstract partial class PopupDialog : VisibilityContainer { public const float ENTER_DURATION = 500; - public const float EXIT_DURATION = 200; + public const float EXIT_DURATION = 500; private readonly Vector2 ringSize = new Vector2(100f); private readonly Vector2 ringMinifiedSize = new Vector2(20f); - private readonly Vector2 buttonsEnterSpacing = new Vector2(0f, 50f); private readonly Box flashLayer; private Sample flashSample = null!; @@ -108,13 +108,20 @@ namespace osu.Game.Overlays.Dialog protected PopupDialog() { - RelativeSizeAxes = Axes.Both; + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; Children = new Drawable[] { content = new Container { - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Alpha = 0f, Children = new Drawable[] { @@ -122,11 +129,13 @@ namespace osu.Game.Overlays.Dialog { RelativeSizeAxes = Axes.Both, Masking = true, + CornerRadius = 20, + CornerExponent = 2.5f, EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.5f), - Radius = 8, + Colour = Color4.Black.Opacity(0.2f), + Radius = 14, }, Children = new Drawable[] { @@ -142,23 +151,29 @@ namespace osu.Game.Overlays.Dialog ColourDark = Color4Extensions.FromHex(@"1e171e"), TriangleScale = 4, }, + flashLayer = new Box + { + Alpha = 0, + RelativeSizeAxes = Axes.Both, + Blending = BlendingParameters.Additive, + Colour = Color4Extensions.FromHex(@"221a21"), + }, }, }, new FillFlowContainer { - Anchor = Anchor.Centre, - Origin = Anchor.BottomCentre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, Spacing = new Vector2(0f, 10f), - Padding = new MarginPadding { Bottom = 10 }, + Padding = new MarginPadding { Vertical = 60 }, Children = new Drawable[] { new Container { Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, + Padding = new MarginPadding { Bottom = 30 }, Size = ringSize, Children = new Drawable[] { @@ -181,6 +196,7 @@ namespace osu.Game.Overlays.Dialog Origin = Anchor.Centre, Anchor = Anchor.Centre, Icon = FontAwesome.Solid.TimesCircle, + Y = -2, Size = new Vector2(50), }, }, @@ -202,25 +218,18 @@ namespace osu.Game.Overlays.Dialog TextAnchor = Anchor.TopCentre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(5), + }, + buttonsContainer = new FillFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Top = 30 }, }, }, }, - buttonsContainer = new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - }, - flashLayer = new Box - { - Alpha = 0, - RelativeSizeAxes = Axes.Both, - Blending = BlendingParameters.Additive, - Colour = Color4Extensions.FromHex(@"221a21"), - }, }, }, }; @@ -231,7 +240,7 @@ namespace osu.Game.Overlays.Dialog } [BackgroundDependencyLoader] - private void load(AudioManager audio) + private void load(AudioManager audio, OsuColour colours) { flashSample = audio.Samples.Get(@"UI/default-select-disabled"); } @@ -288,15 +297,15 @@ namespace osu.Game.Overlays.Dialog // Reset various animations but only if the dialog animation fully completed if (content.Alpha == 0) { - buttonsContainer.TransformSpacingTo(buttonsEnterSpacing); - buttonsContainer.MoveToY(buttonsEnterSpacing.Y); + content.ScaleTo(0.7f); ring.ResizeTo(ringMinifiedSize); } - content.FadeIn(ENTER_DURATION, Easing.OutQuint); - ring.ResizeTo(ringSize, ENTER_DURATION, Easing.OutQuint); - buttonsContainer.TransformSpacingTo(Vector2.Zero, ENTER_DURATION, Easing.OutQuint); - buttonsContainer.MoveToY(0, ENTER_DURATION, Easing.OutQuint); + content + .ScaleTo(1, 750, Easing.OutElasticHalf) + .FadeIn(ENTER_DURATION, Easing.OutQuint); + + ring.ResizeTo(ringSize, ENTER_DURATION * 1.5f, Easing.OutQuint); } protected override void PopOut() @@ -306,7 +315,9 @@ namespace osu.Game.Overlays.Dialog // This is presumed to always be a sane default "cancel" action. buttonsContainer.Last().TriggerClick(); - content.FadeOut(EXIT_DURATION, Easing.InSine); + content + .ScaleTo(0.7f, EXIT_DURATION, Easing.Out) + .FadeOut(EXIT_DURATION, Easing.OutQuint); } private void pressButtonAtIndex(int index) diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index 005162bbcc..a85f1ecbcd 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -29,16 +29,18 @@ namespace osu.Game.Overlays public DialogOverlay() { - RelativeSizeAxes = Axes.Both; + AutoSizeAxes = Axes.Y; Child = dialogContainer = new Container { - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, }; - Width = 0.4f; - Anchor = Anchor.BottomCentre; - Origin = Anchor.BottomCentre; + Width = 500; + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; } [BackgroundDependencyLoader] From b7f3c83514deec96959b51bcbb227ec0ee4f8801 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 20:01:29 +0900 Subject: [PATCH 2205/4092] Expose the ability to update global offset from the player loader screen --- osu.Game/Overlays/SettingsOverlay.cs | 29 +++++++++++++++---- .../PlayerSettings/BeatmapOffsetControl.cs | 19 ++++++++++++ 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs index 5735b1515a..cf76ed8781 100644 --- a/osu.Game/Overlays/SettingsOverlay.cs +++ b/osu.Game/Overlays/SettingsOverlay.cs @@ -3,19 +3,21 @@ #nullable disable +using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Framework.Testing; +using osu.Game.Graphics; +using osu.Game.Localisation; using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings.Sections; using osu.Game.Overlays.Settings.Sections.Input; using osuTK.Graphics; -using System.Collections.Generic; -using osu.Framework.Bindables; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Localisation; -using osu.Game.Graphics; -using osu.Game.Localisation; namespace osu.Game.Overlays { @@ -55,6 +57,21 @@ namespace osu.Game.Overlays public override bool AcceptsFocus => lastOpenedSubPanel == null || lastOpenedSubPanel.State.Value == Visibility.Hidden; + public void ShowAtControl() + where T : Drawable + { + Show(); + + // wait for load of sections + if (!SectionsContainer.Any()) + { + Scheduler.Add(ShowAtControl); + return; + } + + SectionsContainer.ScrollTo(SectionsContainer.ChildrenOfType().Single()); + } + private T createSubPanel(T subPanel) where T : SettingsSubPanel { diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 3f0f0fd1df..8c6f1a8f7f 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -21,7 +21,9 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Input.Bindings; using osu.Game.Localisation; +using osu.Game.Overlays; using osu.Game.Overlays.Settings; +using osu.Game.Overlays.Settings.Sections.Audio; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -213,6 +215,8 @@ namespace osu.Game.Screens.Play.PlayerSettings lastPlayAverage = average; lastPlayBeatmapOffset = Current.Value; + LinkFlowContainer globalOffsetText; + referenceScoreContainer.AddRange(new Drawable[] { lastPlayGraph = new HitEventTimingDistributionGraph(hitEvents) @@ -226,9 +230,24 @@ namespace osu.Game.Screens.Play.PlayerSettings Text = BeatmapOffsetControlStrings.CalibrateUsingLastPlay, Action = () => Current.Value = lastPlayBeatmapOffset - lastPlayAverage }, + globalOffsetText = new LinkFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + } }); + + if (settings != null) + { + globalOffsetText.AddText("You can also "); + globalOffsetText.AddLink("adjust the global offset", () => settings.ShowAtControl()); + globalOffsetText.AddText(" based off this play."); + } } + [Resolved] + private SettingsOverlay? settings { get; set; } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From 91af94086c6ba2905779c07ebb35dc011979993f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 20:02:06 +0900 Subject: [PATCH 2206/4092] Remove offset wizard button for now --- osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs index af15d310fc..e05d20a5db 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs @@ -15,7 +15,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio { protected override LocalisableString Header => AudioSettingsStrings.OffsetHeader; - public override IEnumerable FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { "universal", "uo", "timing", "delay", "latency" }); + public override IEnumerable FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { "universal", "uo", "timing", "delay", "latency", "wizard" }); [BackgroundDependencyLoader] private void load(OsuConfigManager config) @@ -26,10 +26,6 @@ namespace osu.Game.Overlays.Settings.Sections.Audio { Current = config.GetBindable(OsuSetting.AudioOffset), }, - new SettingsButton - { - Text = AudioSettingsStrings.OffsetWizard - } }; } } From e1a376c0a742d958399734683c8eb79631f37add Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 20:13:19 +0900 Subject: [PATCH 2207/4092] Fix using "Back" binding at spectator fail screen not working --- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 10 +++++++++- osu.Game/Screens/Play/PauseOverlay.cs | 8 +++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 0680842891..440b8d37b9 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -44,7 +44,15 @@ namespace osu.Game.Screens.Play /// /// Action that is invoked when is triggered. /// - protected virtual Action BackAction => () => InternalButtons.LastOrDefault()?.TriggerClick(); + protected virtual Action BackAction => () => + { + // We prefer triggering the button click as it will animate... + // but sometimes buttons aren't present (see FailOverlay's constructor as an example). + if (Buttons.Any()) + Buttons.Last().TriggerClick(); + else + OnQuit?.Invoke(); + }; /// /// Action that is invoked when is triggered. diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs index 88561ada71..2aa2793fd4 100644 --- a/osu.Game/Screens/Play/PauseOverlay.cs +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -29,7 +29,13 @@ namespace osu.Game.Screens.Play private SkinnableSound pauseLoop; - protected override Action BackAction => () => InternalButtons.First().TriggerClick(); + protected override Action BackAction => () => + { + if (Buttons.Any()) + Buttons.First().TriggerClick(); + else + OnResume?.Invoke(); + }; [BackgroundDependencyLoader] private void load(OsuColour colours) From f8347288c1a5cdc5ab6587a49e3980075eec4872 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 20:29:56 +0900 Subject: [PATCH 2208/4092] Add padding around text in dialogs --- .../UserInterface/TestSceneDialogOverlay.cs | 24 +++++++++++++++++++ osu.Game/Overlays/Dialog/PopupDialog.cs | 2 ++ 2 files changed, 26 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs index 3b38343f04..f2313022ec 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs @@ -92,6 +92,30 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("first dialog is not part of hierarchy", () => firstDialog.Parent == null); } + [Test] + public void TestTooMuchText() + { + AddStep("dialog #1", () => overlay.Push(new TestPopupDialog + { + Icon = FontAwesome.Regular.TrashAlt, + HeaderText = @"Confirm deletion ofConfirm deletion ofConfirm deletion ofConfirm deletion ofConfirm deletion ofConfirm deletion of", + BodyText = @"Ayase Rie - Yuima-ru*World TVver.Ayase Rie - Yuima-ru*World TVver.Ayase Rie - Yuima-ru*World TVver.Ayase Rie - Yuima-ru*World TVver.Ayase Rie - Yuima-ru*World TVver.Ayase Rie - Yuima-ru*World TVver.Ayase Rie - Yuima-ru*World TVver.Ayase Rie - Yuima-ru*World TVver.Ayase Rie - Yuima-ru*World TVver. ", + Buttons = new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = @"I never want to see this again.", + Action = () => Console.WriteLine(@"OK"), + }, + new PopupDialogCancelButton + { + Text = @"Firetruck, I still want quick ranks!", + Action = () => Console.WriteLine(@"Cancel"), + }, + }, + })); + } + [Test] public void TestPushBeforeLoad() { diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index 663c2e78ce..4048b35e78 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -210,6 +210,7 @@ namespace osu.Game.Overlays.Dialog RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, TextAnchor = Anchor.TopCentre, + Padding = new MarginPadding { Horizontal = 5 }, }, body = new OsuTextFlowContainer(t => t.Font = t.Font.With(size: 18)) { @@ -218,6 +219,7 @@ namespace osu.Game.Overlays.Dialog TextAnchor = Anchor.TopCentre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = 5 }, }, buttonsContainer = new FillFlowContainer { From 6bc0f02e2d46296fe2838cf97d57a3905d739928 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 20:40:51 +0900 Subject: [PATCH 2209/4092] Fix test initialising dialogs without cleaning up --- .../Visual/UserInterface/TestScenePopupDialog.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs index 9537ab63be..37ff66aa35 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs @@ -1,10 +1,7 @@ // 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 NUnit.Framework; -using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; using osu.Game.Overlays.Dialog; @@ -15,18 +12,17 @@ namespace osu.Game.Tests.Visual.UserInterface { public partial class TestScenePopupDialog : OsuManualInputManagerTestScene { - private TestPopupDialog dialog; + private TestPopupDialog dialog = null!; [SetUpSteps] public void SetUpSteps() { AddStep("new popup", () => { - Add(dialog = new TestPopupDialog + Child = dialog = new TestPopupDialog { - RelativeSizeAxes = Axes.Both, State = { Value = Framework.Graphics.Containers.Visibility.Visible }, - }); + }; }); } From bb0737837b537fd7d97f276e48aaae871f0abc5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Dec 2023 13:58:00 +0100 Subject: [PATCH 2210/4092] Fix test failing due to querying button position during transform --- osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs index 37ff66aa35..96d19911bd 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs @@ -29,6 +29,8 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestDangerousButton([Values(false, true)] bool atEdge) { + AddStep("finish transforms", () => dialog.FinishTransforms(true)); + if (atEdge) { AddStep("move mouse to button edge", () => From e6f1d7db4446e9e0c2308b5d17c937c65a4f7a27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Dec 2023 14:10:03 +0100 Subject: [PATCH 2211/4092] Move `SessionAverageHitErrorTracker` to more general namespace --- .../Audio => Configuration}/SessionAverageHitErrorTracker.cs | 3 +-- .../Settings/Sections/Audio/AudioOffsetAdjustControl.cs | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) rename osu.Game/{Overlays/Settings/Sections/Audio => Configuration}/SessionAverageHitErrorTracker.cs (95%) diff --git a/osu.Game/Overlays/Settings/Sections/Audio/SessionAverageHitErrorTracker.cs b/osu.Game/Configuration/SessionAverageHitErrorTracker.cs similarity index 95% rename from osu.Game/Overlays/Settings/Sections/Audio/SessionAverageHitErrorTracker.cs rename to osu.Game/Configuration/SessionAverageHitErrorTracker.cs index b714d49b89..13b1ea7d37 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/SessionAverageHitErrorTracker.cs +++ b/osu.Game/Configuration/SessionAverageHitErrorTracker.cs @@ -5,12 +5,11 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; -namespace osu.Game.Overlays.Settings.Sections.Audio +namespace osu.Game.Configuration { /// /// Tracks the local user's average hit error during the ongoing play session. diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs index 0023c60c61..e10de58a6c 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; From 6718de01ecc7397f3804954f0c01aaa81d1fffdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Dec 2023 14:11:08 +0100 Subject: [PATCH 2212/4092] Suggest audio adjust after one play --- .../Settings/Sections/Audio/AudioOffsetAdjustControl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs index e10de58a6c..98acea1f2e 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs @@ -143,7 +143,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio break; } - suggestedOffset.Value = averageHitErrorHistory.Count < 3 ? null : -averageHitErrorHistory.Average(); + suggestedOffset.Value = averageHitErrorHistory.Any() ? -averageHitErrorHistory.Average() : null; } private float getXPositionForAverage(double average) => (float)(Math.Clamp(-average, current.MinValue, current.MaxValue) / (2 * current.MaxValue)); @@ -152,7 +152,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio { hintText.Text = suggestedOffset.Value == null ? @"Play a few beatmaps to receive a suggested offset!" - : $@"Based on the last {averageHitErrorHistory.Count} plays, the suggested offset is {suggestedOffset.Value:N0} ms."; + : $@"Based on the last {averageHitErrorHistory.Count} play(s), the suggested offset is {suggestedOffset.Value:N0} ms."; applySuggestion.Enabled.Value = suggestedOffset.Value != null; } } From 619b0cc69b1998fdc8b1b1fcb69bcddaefc08004 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Dec 2023 14:12:28 +0100 Subject: [PATCH 2213/4092] Fix code quality inspection --- .../Screens/Play/PlayerSettings/BeatmapOffsetControl.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 8c6f1a8f7f..8efb80e771 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -160,11 +160,11 @@ namespace osu.Game.Screens.Play.PlayerSettings // Apply to all difficulties in a beatmap set for now (they generally always share timing). foreach (var b in setInfo.Beatmaps) { - BeatmapUserSettings settings = b.UserSettings; + BeatmapUserSettings userSettings = b.UserSettings; double val = Current.Value; - if (settings.Offset != val) - settings.Offset = val; + if (userSettings.Offset != val) + userSettings.Offset = val; } }); } From 6d124513e7d88aba2849b5494783820eeb476777 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Dec 2023 14:15:15 +0100 Subject: [PATCH 2214/4092] Fix test failures due to player bailing early after failing to load beatmap --- osu.Game/Screens/Play/SubmittingPlayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index ff2c57bf35..83adf1f960 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -180,7 +180,7 @@ namespace osu.Game.Screens.Play { bool exiting = base.OnExiting(e); submitFromFailOrQuit(); - statics.SetValue(Static.LastLocalUserScore, Score.ScoreInfo.DeepClone()); + statics.SetValue(Static.LastLocalUserScore, Score?.ScoreInfo.DeepClone()); return exiting; } From 93c7ebdae3f61e95e36cc61c014d71fb67e8a3b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Dec 2023 14:30:11 +0100 Subject: [PATCH 2215/4092] Remove unused using --- osu.Game/OsuGameBase.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 90c88b2b33..4e465f59df 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -55,7 +55,6 @@ using osu.Game.Online.Spectator; using osu.Game.Overlays; using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings.Sections; -using osu.Game.Overlays.Settings.Sections.Audio; using osu.Game.Overlays.Settings.Sections.Input; using osu.Game.Resources; using osu.Game.Rulesets; From 68402bfd113b3c4051aead283fe3cf57d5b9ca78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Dec 2023 20:32:33 +0100 Subject: [PATCH 2216/4092] Add test coverage of failure scenario --- osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs index 85aa14f33e..46570e7cdf 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs @@ -24,6 +24,11 @@ namespace osu.Game.Tests.Visual.Menus Image = @"https://assets.ppy.sh/main-menu/wf2023-vote@2x.png", Url = @"https://osu.ppy.sh/community/contests/189", }); + AddStep("set title with nonexistent image", () => Game.ChildrenOfType().Single().Current.Value = new APISystemTitle + { + Image = @"https://test.invalid/@2x", // .invalid TLD reserved by https://datatracker.ietf.org/doc/html/rfc2606#section-2 + Url = @"https://osu.ppy.sh/community/contests/189", + }); AddStep("unset system title", () => Game.ChildrenOfType().Single().Current.Value = null); } } From 7a10e132eaea162febb65d03baa16067a1244e80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Dec 2023 20:33:56 +0100 Subject: [PATCH 2217/4092] Fix crash when retrieval of system title image fails Closes https://github.com/ppy/osu/issues/26194. --- osu.Game/Screens/Menu/SystemTitle.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/SystemTitle.cs b/osu.Game/Screens/Menu/SystemTitle.cs index 060e426f8c..7d49a1dd5f 100644 --- a/osu.Game/Screens/Menu/SystemTitle.cs +++ b/osu.Game/Screens/Menu/SystemTitle.cs @@ -138,8 +138,8 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader] private void load(LargeTextureStore textureStore) { - var texture = textureStore.Get(SystemTitle.Image); - if (SystemTitle.Image.Contains(@"@2x")) + Texture? texture = textureStore.Get(SystemTitle.Image); + if (texture != null && SystemTitle.Image.Contains(@"@2x")) texture.ScaleAdjust *= 2; AutoSizeAxes = Axes.Both; From 637119f7d4ae7142d9cef29e8a2cd22000c95fb1 Mon Sep 17 00:00:00 2001 From: iminlikewithyou Date: Thu, 28 Dec 2023 17:15:23 -0600 Subject: [PATCH 2218/4092] increase the base size of button icons --- osu.Game/Screens/Menu/MainMenuButton.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenuButton.cs b/osu.Game/Screens/Menu/MainMenuButton.cs index bc638b44ac..a9927541f1 100644 --- a/osu.Game/Screens/Menu/MainMenuButton.cs +++ b/osu.Game/Screens/Menu/MainMenuButton.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -125,7 +125,7 @@ namespace osu.Game.Screens.Menu Shadow = true, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(30), + Size = new Vector2(32), Position = new Vector2(0, 0), Icon = symbol }, From 4760c6aaee2bef3a47329158c612d9ea231d4e3b Mon Sep 17 00:00:00 2001 From: iminlikewithyou Date: Thu, 28 Dec 2023 17:17:24 -0600 Subject: [PATCH 2219/4092] move icon upwards to be visually centered --- osu.Game/Screens/Menu/MainMenuButton.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenuButton.cs b/osu.Game/Screens/Menu/MainMenuButton.cs index a9927541f1..87136675a2 100644 --- a/osu.Game/Screens/Menu/MainMenuButton.cs +++ b/osu.Game/Screens/Menu/MainMenuButton.cs @@ -126,7 +126,7 @@ namespace osu.Game.Screens.Menu Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(32), - Position = new Vector2(0, 0), + Position = new Vector2(0, -4), Icon = symbol }, new OsuSpriteText @@ -186,7 +186,7 @@ namespace osu.Game.Screens.Menu { icon.ClearTransforms(); icon.RotateTo(0, 500, Easing.Out); - icon.MoveTo(Vector2.Zero, 500, Easing.Out); + icon.MoveTo(new Vector2(0, -4), 500, Easing.Out); icon.ScaleTo(Vector2.One, 200, Easing.Out); if (State == ButtonState.Expanded) From f1f1221e0e4f5e83f89b74df1c6e1b1c3ce4e04d Mon Sep 17 00:00:00 2001 From: iminlikewithyou Date: Thu, 28 Dec 2023 17:18:41 -0600 Subject: [PATCH 2220/4092] move text left to be visually centered in the skewed rectangle --- osu.Game/Screens/Menu/MainMenuButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/MainMenuButton.cs b/osu.Game/Screens/Menu/MainMenuButton.cs index 87136675a2..69dbaa8ed8 100644 --- a/osu.Game/Screens/Menu/MainMenuButton.cs +++ b/osu.Game/Screens/Menu/MainMenuButton.cs @@ -135,7 +135,7 @@ namespace osu.Game.Screens.Menu AllowMultiline = false, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Position = new Vector2(0, 35), + Position = new Vector2(-3, 35), Text = text } } From 51d26d2d715f463a13b47d4692daac3d13a9319d Mon Sep 17 00:00:00 2001 From: iminlikewithyou Date: Thu, 28 Dec 2023 17:20:44 -0600 Subject: [PATCH 2221/4092] make the hover scale bigger as a consequence, the rotation needs to be tweaked to be lower --- osu.Game/Screens/Menu/MainMenuButton.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenuButton.cs b/osu.Game/Screens/Menu/MainMenuButton.cs index 69dbaa8ed8..3d56a75ca8 100644 --- a/osu.Game/Screens/Menu/MainMenuButton.cs +++ b/osu.Game/Screens/Menu/MainMenuButton.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -31,6 +31,9 @@ namespace osu.Game.Screens.Menu /// public partial class MainMenuButton : BeatSyncedContainer, IStateful { + public const float BOUNCE_COMPRESSION = 0.9f; + public const float HOVER_SCALE = 1.2f; + public const float BOUNCE_ROTATION = 8; public event Action? StateChanged; public readonly Key[] TriggerKeys; @@ -153,14 +156,14 @@ namespace osu.Game.Screens.Menu double duration = timingPoint.BeatLength / 2; - icon.RotateTo(rightward ? 10 : -10, duration * 2, Easing.InOutSine); + icon.RotateTo(rightward ? BOUNCE_ROTATION : -BOUNCE_ROTATION, duration * 2, Easing.InOutSine); icon.Animate( i => i.MoveToY(-10, duration, Easing.Out), - i => i.ScaleTo(1, duration, Easing.Out) + i => i.ScaleTo(HOVER_SCALE, duration, Easing.Out) ).Then( i => i.MoveToY(0, duration, Easing.In), - i => i.ScaleTo(new Vector2(1, 0.9f), duration, Easing.In) + i => i.ScaleTo(new Vector2(HOVER_SCALE, HOVER_SCALE * BOUNCE_COMPRESSION), duration, Easing.In) ); rightward = !rightward; @@ -177,8 +180,8 @@ namespace osu.Game.Screens.Menu double duration = TimeUntilNextBeat; icon.ClearTransforms(); - icon.RotateTo(rightward ? -10 : 10, duration, Easing.InOutSine); - icon.ScaleTo(new Vector2(1, 0.9f), duration, Easing.Out); + icon.RotateTo(rightward ? -BOUNCE_ROTATION : BOUNCE_ROTATION, duration, Easing.InOutSine); + icon.ScaleTo(new Vector2(HOVER_SCALE, HOVER_SCALE * BOUNCE_COMPRESSION), duration, Easing.Out); return true; } From c68a850325e36b8585b2d60f25be81deef52565a Mon Sep 17 00:00:00 2001 From: Nitrous Date: Fri, 29 Dec 2023 08:51:54 +0800 Subject: [PATCH 2222/4092] Make menu tip about mod select more up to date. --- osu.Game/Screens/Menu/MenuTip.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/MenuTip.cs b/osu.Game/Screens/Menu/MenuTip.cs index 325fb07767..ab09c07e64 100644 --- a/osu.Game/Screens/Menu/MenuTip.cs +++ b/osu.Game/Screens/Menu/MenuTip.cs @@ -103,7 +103,7 @@ namespace osu.Game.Screens.Menu "What used to be \"osu!direct\" is available to all users just like on the website. You can access it anywhere using Ctrl-B!", "Seeking in replays is available by dragging on the difficulty bar at the bottom of the screen!", "Multithreading support means that even with low \"FPS\" your input and judgements will be accurate!", - "Try scrolling down in the mod select panel to find a bunch of new fun mods!", + "Try scrolling right in mod select to find a bunch of new fun mods!", "Most of the web content (profiles, rankings, etc.) are available natively in-game from the icons on the toolbar!", "Get more details, hide or delete a beatmap by right-clicking on its panel at song select!", "All delete operations are temporary until exiting. Restore accidentally deleted content from the maintenance settings!", From 150bf670649e61ac071796fa4056aa372ff9023b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 29 Dec 2023 05:30:50 +0300 Subject: [PATCH 2223/4092] Fix dropdown colour not updating correctly on enabled state changes --- osu.Game/Graphics/UserInterface/OsuDropdown.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs index 632036fef9..c99a21ac28 100644 --- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs @@ -363,6 +363,7 @@ namespace osu.Game.Graphics.UserInterface base.LoadComplete(); SearchBar.State.ValueChanged += _ => updateColour(); + Enabled.BindValueChanged(_ => updateColour()); updateColour(); } From c147ec0a98b22bec0a4635e1fc65cc32c5b34ddf Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 29 Dec 2023 05:31:13 +0300 Subject: [PATCH 2224/4092] Update dropdown disabled state to match with other components --- osu.Game/Graphics/UserInterface/OsuDropdown.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs index c99a21ac28..2dc701dc9d 100644 --- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs @@ -384,6 +384,9 @@ namespace osu.Game.Graphics.UserInterface var hoveredColour = colourProvider?.Light4 ?? colours.PinkDarker; var unhoveredColour = colourProvider?.Background5 ?? Color4.Black.Opacity(0.5f); + Colour = Color4.White; + Alpha = Enabled.Value ? 1 : 0.3f; + if (SearchBar.State.Value == Visibility.Visible) { Icon.Colour = hovered ? hoveredColour.Lighten(0.5f) : Colour4.White; From a6313c4ee8b78361d052304a3469fc9bf229edfb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 29 Dec 2023 17:16:16 +0900 Subject: [PATCH 2225/4092] Expose `Mod.UsesDefaultConfiguration` --- osu.Game/Rulesets/Mods/Mod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index d635dccab1..0500b49513 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -195,7 +195,7 @@ namespace osu.Game.Rulesets.Mods /// /// Whether all settings in this mod are set to their default state. /// - protected virtual bool UsesDefaultConfiguration => SettingsBindables.All(s => s.IsDefault); + public virtual bool UsesDefaultConfiguration => SettingsBindables.All(s => s.IsDefault); /// /// Creates a copy of this initialised to a default state. From 9bb0663b3bb3b7f717a5cedbcd62a0051062eacc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 29 Dec 2023 10:36:52 +0100 Subject: [PATCH 2226/4092] Add test coverage of failure case --- .../Visual/Editing/TestSceneEditorBeatmapCreation.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 7a2ed23cae..db87987815 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -25,6 +25,7 @@ using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Taiko; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Screens.Edit.Setup; using osu.Game.Storyboards; using osu.Game.Tests.Resources; @@ -94,8 +95,11 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestAddAudioTrack() { - AddAssert("track is virtual", () => Beatmap.Value.Track is TrackVirtual); + AddStep("enter compose mode", () => InputManager.Key(Key.F1)); + AddUntilStep("wait for timeline load", () => Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddAssert("track is virtual", () => Beatmap.Value.Track is TrackVirtual); AddAssert("switch track to real track", () => { var setup = Editor.ChildrenOfType().First(); From cd1f6b46c40639443c49cb03bc84289fcdd47f68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 29 Dec 2023 10:24:48 +0100 Subject: [PATCH 2227/4092] Fix crash after changing audio track in editor Closes https://github.com/ppy/osu/issues/26213. Reproduction scenario: switch audio track in editor after timeline loads. Happens because `beatmap.Value.Track.Length` is 0 immediately after a track switch, until BASS computes the actual track length on the audio thread. Yes this is a hack. No I have no better immediate ideas how to address this otherwise. --- .../Screens/Edit/Compose/Components/Timeline/Timeline.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 83d34ab61a..03a868b0f3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -141,12 +141,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline waveformOpacity = config.GetBindable(OsuSetting.EditorWaveformOpacity); track.BindTo(editorClock.Track); - track.BindValueChanged(_ => + // schedule added as without it, `beatmap.Value.Track.Length` can be 0 immediately after a track switch. + track.BindValueChanged(_ => Schedule(() => { waveform.Waveform = beatmap.Value.Waveform; waveform.RelativePositionAxes = Axes.X; waveform.X = -(float)(Editor.WAVEFORM_VISUAL_OFFSET / beatmap.Value.Track.Length); - }, true); + }), true); Zoom = (float)(defaultTimelineZoom * editorBeatmap.BeatmapInfo.TimelineZoom); } From 99cddb631702f2aebe23056424ae86428369a5d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 29 Dec 2023 11:07:45 +0100 Subject: [PATCH 2228/4092] Use alternative workaround --- .../Compose/Components/Timeline/Timeline.cs | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 03a868b0f3..a2704e550c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -141,17 +141,29 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline waveformOpacity = config.GetBindable(OsuSetting.EditorWaveformOpacity); track.BindTo(editorClock.Track); - // schedule added as without it, `beatmap.Value.Track.Length` can be 0 immediately after a track switch. - track.BindValueChanged(_ => Schedule(() => + track.BindValueChanged(_ => { waveform.Waveform = beatmap.Value.Waveform; - waveform.RelativePositionAxes = Axes.X; - waveform.X = -(float)(Editor.WAVEFORM_VISUAL_OFFSET / beatmap.Value.Track.Length); - }), true); + Scheduler.AddOnce(applyVisualOffset, beatmap); + }, true); Zoom = (float)(defaultTimelineZoom * editorBeatmap.BeatmapInfo.TimelineZoom); } + private void applyVisualOffset(IBindable beatmap) + { + waveform.RelativePositionAxes = Axes.X; + + if (beatmap.Value.Track.Length > 0) + waveform.X = -(float)(Editor.WAVEFORM_VISUAL_OFFSET / beatmap.Value.Track.Length); + else + { + // sometimes this can be the case immediately after a track switch. + // reschedule with the hope that the track length eventually populates. + Scheduler.AddOnce(applyVisualOffset, beatmap); + } + } + protected override void LoadComplete() { base.LoadComplete(); From 25fa76a1b2bbb8d96794466e28024842aff9e71d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 29 Dec 2023 11:14:28 +0100 Subject: [PATCH 2229/4092] Do not show main menu version display on deployed builds See https://discord.com/channels/188630481301012481/188630652340404224/1190028102525530202. --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 37996e8832..0d6eb1ea44 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -994,7 +994,7 @@ namespace osu.Game Margin = new MarginPadding(5), }, topMostOverlayContent.Add); - if (!args?.Any(a => a == @"--no-version-overlay") ?? true) + if (!IsDeployedBuild) { dependencies.Cache(versionManager = new VersionManager { Depth = int.MinValue }); loadComponentSingleFile(versionManager, ScreenContainer.Add); From db78d73fa511b647e45286e805a6ae7784ee4fce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 29 Dec 2023 11:50:30 +0100 Subject: [PATCH 2230/4092] Do not display system title in inital menu state Addresses https://github.com/ppy/osu/discussions/26199. --- osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs | 15 +++++++++++---- osu.Game/Screens/Menu/MainMenu.cs | 5 ++++- osu.Game/Screens/Menu/SystemTitle.cs | 12 +++++++++++- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs index 46570e7cdf..7053a9d544 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs @@ -3,33 +3,40 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Online.API.Requests.Responses; using osu.Game.Screens.Menu; +using osuTK.Input; namespace osu.Game.Tests.Visual.Menus { public partial class TestSceneMainMenu : OsuGameTestScene { + private SystemTitle systemTitle => Game.ChildrenOfType().Single(); + [Test] public void TestSystemTitle() { - AddStep("set system title", () => Game.ChildrenOfType().Single().Current.Value = new APISystemTitle + AddStep("set system title", () => systemTitle.Current.Value = new APISystemTitle { Image = @"https://assets.ppy.sh/main-menu/project-loved-2@2x.png", Url = @"https://osu.ppy.sh/home/news/2023-12-21-project-loved-december-2023", }); - AddStep("set another title", () => Game.ChildrenOfType().Single().Current.Value = new APISystemTitle + AddAssert("system title not visible", () => systemTitle.State.Value, () => Is.EqualTo(Visibility.Hidden)); + AddStep("enter menu", () => InputManager.Key(Key.Enter)); + AddUntilStep("system title visible", () => systemTitle.State.Value, () => Is.EqualTo(Visibility.Visible)); + AddStep("set another title", () => systemTitle.Current.Value = new APISystemTitle { Image = @"https://assets.ppy.sh/main-menu/wf2023-vote@2x.png", Url = @"https://osu.ppy.sh/community/contests/189", }); - AddStep("set title with nonexistent image", () => Game.ChildrenOfType().Single().Current.Value = new APISystemTitle + AddStep("set title with nonexistent image", () => systemTitle.Current.Value = new APISystemTitle { Image = @"https://test.invalid/@2x", // .invalid TLD reserved by https://datatracker.ietf.org/doc/html/rfc2606#section-2 Url = @"https://osu.ppy.sh/community/contests/189", }); - AddStep("unset system title", () => Game.ChildrenOfType().Single().Current.Value = null); + AddStep("unset system title", () => systemTitle.Current.Value = null); } } } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 401460a498..516b090a16 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -94,6 +94,7 @@ namespace osu.Game.Screens.Menu private ParallaxContainer buttonsContainer; private SongTicker songTicker; private Container logoTarget; + private SystemTitle systemTitle; private MenuTip menuTip; private FillFlowContainer bottomElementsFlow; private SupporterDisplay supporterDisplay; @@ -173,7 +174,7 @@ namespace osu.Game.Screens.Menu Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, }, - new SystemTitle + systemTitle = new SystemTitle { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -196,10 +197,12 @@ namespace osu.Game.Screens.Menu case ButtonSystemState.Initial: case ButtonSystemState.Exit: ApplyToBackground(b => b.FadeColour(Color4.White, 500, Easing.OutSine)); + systemTitle.State.Value = Visibility.Hidden; break; default: ApplyToBackground(b => b.FadeColour(OsuColour.Gray(0.8f), 500, Easing.OutSine)); + systemTitle.State.Value = Visibility.Visible; break; } }; diff --git a/osu.Game/Screens/Menu/SystemTitle.cs b/osu.Game/Screens/Menu/SystemTitle.cs index 7d49a1dd5f..813a470ed6 100644 --- a/osu.Game/Screens/Menu/SystemTitle.cs +++ b/osu.Game/Screens/Menu/SystemTitle.cs @@ -18,10 +18,12 @@ using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Screens.Menu { - public partial class SystemTitle : CompositeDrawable + public partial class SystemTitle : VisibilityContainer { internal Bindable Current { get; } = new Bindable(); + private const float transition_duration = 500; + private Container content = null!; private CancellationTokenSource? cancellationTokenSource; private SystemTitleImage? currentImage; @@ -32,9 +34,13 @@ namespace osu.Game.Screens.Menu private void load(OsuGame? game) { AutoSizeAxes = Axes.Both; + AutoSizeDuration = transition_duration; + AutoSizeEasing = Easing.OutQuint; InternalChild = content = new OsuClickableContainer { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, AutoSizeAxes = Axes.Both, Action = () => { @@ -51,6 +57,10 @@ namespace osu.Game.Screens.Menu }; } + protected override void PopIn() => content.FadeInFromZero(transition_duration, Easing.OutQuint); + + protected override void PopOut() => content.FadeOut(transition_duration, Easing.OutQuint); + protected override bool OnHover(HoverEvent e) { content.ScaleTo(1.05f, 2000, Easing.OutQuint); From cdcb3ef4aa7401243d6a97e151d9cc41cd284060 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 29 Dec 2023 12:20:14 +0100 Subject: [PATCH 2231/4092] Fix global audio offset suggestion feature not taking previous offset value into account See https://osu.ppy.sh/comments/2989193 etc. --- .../SessionAverageHitErrorTracker.cs | 25 ++++++++++++++++--- .../Audio/AudioOffsetAdjustControl.cs | 14 +++++------ 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/osu.Game/Configuration/SessionAverageHitErrorTracker.cs b/osu.Game/Configuration/SessionAverageHitErrorTracker.cs index 13b1ea7d37..cd21eb6fa8 100644 --- a/osu.Game/Configuration/SessionAverageHitErrorTracker.cs +++ b/osu.Game/Configuration/SessionAverageHitErrorTracker.cs @@ -17,11 +17,14 @@ namespace osu.Game.Configuration [Cached] public partial class SessionAverageHitErrorTracker : Component { - public IBindableList AverageHitErrorHistory => averageHitErrorHistory; - private readonly BindableList averageHitErrorHistory = new BindableList(); + public IBindableList AverageHitErrorHistory => averageHitErrorHistory; + private readonly BindableList averageHitErrorHistory = new BindableList(); private readonly Bindable latestScore = new Bindable(); + [Resolved] + private OsuConfigManager configManager { get; set; } = null!; + [BackgroundDependencyLoader] private void load(SessionStatics statics) { @@ -46,9 +49,25 @@ namespace osu.Game.Configuration // keep a sane maximum number of entries. if (averageHitErrorHistory.Count >= 50) averageHitErrorHistory.RemoveAt(0); - averageHitErrorHistory.Add(averageError); + + double globalOffset = configManager.Get(OsuSetting.AudioOffset); + averageHitErrorHistory.Add(new DataPoint(averageError, globalOffset)); } public void ClearHistory() => averageHitErrorHistory.Clear(); + + public readonly struct DataPoint + { + public double AverageHitError { get; } + public double GlobalAudioOffset { get; } + + public double SuggestedGlobalAudioOffset => GlobalAudioOffset - AverageHitError; + + public DataPoint(double averageHitError, double globalOffset) + { + AverageHitError = averageHitError; + GlobalAudioOffset = globalOffset; + } + } } } diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs index 98acea1f2e..08bf4b0dad 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs @@ -42,7 +42,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio private readonly BindableNumberWithCurrent current = new BindableNumberWithCurrent(); - private readonly IBindableList averageHitErrorHistory = new BindableList(); + private readonly IBindableList averageHitErrorHistory = new BindableList(); private readonly Bindable suggestedOffset = new Bindable(); @@ -112,7 +112,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio switch (e.Action) { case NotifyCollectionChangedAction.Add: - foreach (double average in e.NewItems!) + foreach (SessionAverageHitErrorTracker.DataPoint dataPoint in e.NewItems!) { notchContainer.ForEach(n => n.Alpha *= 0.95f); notchContainer.Add(new Box @@ -122,16 +122,16 @@ namespace osu.Game.Overlays.Settings.Sections.Audio RelativePositionAxes = Axes.X, Anchor = Anchor.Centre, Origin = Anchor.Centre, - X = getXPositionForAverage(average) + X = getXPositionForOffset(dataPoint.SuggestedGlobalAudioOffset) }); } break; case NotifyCollectionChangedAction.Remove: - foreach (double average in e.OldItems!) + foreach (SessionAverageHitErrorTracker.DataPoint dataPoint in e.OldItems!) { - var notch = notchContainer.FirstOrDefault(n => n.X == getXPositionForAverage(average)); + var notch = notchContainer.FirstOrDefault(n => n.X == getXPositionForOffset(dataPoint.SuggestedGlobalAudioOffset)); Debug.Assert(notch != null); notchContainer.Remove(notch, true); } @@ -143,10 +143,10 @@ namespace osu.Game.Overlays.Settings.Sections.Audio break; } - suggestedOffset.Value = averageHitErrorHistory.Any() ? -averageHitErrorHistory.Average() : null; + suggestedOffset.Value = averageHitErrorHistory.Any() ? -averageHitErrorHistory.Average(dataPoint => dataPoint.SuggestedGlobalAudioOffset) : null; } - private float getXPositionForAverage(double average) => (float)(Math.Clamp(-average, current.MinValue, current.MaxValue) / (2 * current.MaxValue)); + private float getXPositionForOffset(double offset) => (float)(Math.Clamp(offset, current.MinValue, current.MaxValue) / (2 * current.MaxValue)); private void updateHintText() { From 17753b82356c4e4f51fb6e55dbff4e8b604c6ede Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 29 Dec 2023 13:48:49 +0100 Subject: [PATCH 2232/4092] Remove opaque background from toolbar user button Would close https://github.com/ppy/osu/issues/26223, and generally seems more consistent with the rest of toolbar anyhow? --- osu.Game/Overlays/Toolbar/ToolbarUserButton.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs index 7d1b6c7404..19b98628e6 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs @@ -40,8 +40,6 @@ namespace osu.Game.Overlays.Toolbar [BackgroundDependencyLoader] private void load(OsuColour colours, IAPIProvider api, LoginOverlay? login) { - BackgroundContent.Add(new OpaqueBackground { Depth = 1 }); - Flow.Add(new Container { Masking = true, From d00b7c9cdf23b204f4843523dbd7a5ae0883faaa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Dec 2023 22:14:51 +0900 Subject: [PATCH 2233/4092] Add some new menu tips (and reword some others) --- osu.Game/Screens/Menu/MenuTip.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/MenuTip.cs b/osu.Game/Screens/Menu/MenuTip.cs index ab09c07e64..e31281ea20 100644 --- a/osu.Game/Screens/Menu/MenuTip.cs +++ b/osu.Game/Screens/Menu/MenuTip.cs @@ -94,14 +94,14 @@ namespace osu.Game.Screens.Menu { string[] tips = { - "You can press Ctrl-T anywhere in the game to toggle the toolbar!", - "You can press Ctrl-O anywhere in the game to access options!", + "Press Ctrl-T anywhere in the game to toggle the toolbar!", + "Press Ctrl-O anywhere in the game to access options!", "All settings are dynamic and take effect in real-time. Try changing the skin while watching autoplay!", "New features are coming online every update. Make sure to stay up-to-date!", "If you find the UI too large or small, try adjusting UI scale in settings!", "Try adjusting the \"Screen Scaling\" mode to change your gameplay or UI area, even in fullscreen!", "What used to be \"osu!direct\" is available to all users just like on the website. You can access it anywhere using Ctrl-B!", - "Seeking in replays is available by dragging on the difficulty bar at the bottom of the screen!", + "Seeking in replays is available by dragging on the difficulty bar at the bottom of the screen or by using the left and right arrow keys!", "Multithreading support means that even with low \"FPS\" your input and judgements will be accurate!", "Try scrolling right in mod select to find a bunch of new fun mods!", "Most of the web content (profiles, rankings, etc.) are available natively in-game from the icons on the toolbar!", @@ -110,6 +110,18 @@ namespace osu.Game.Screens.Menu "Check out the \"playlists\" system, which lets users create their own custom and permanent leaderboards!", "Toggle advanced frame / thread statistics with Ctrl-F11!", "Take a look under the hood at performance counters and enable verbose performance logging with Ctrl-F2!", + "You can pause during a replay by pressing Space!", + "Most of the hotkeys in the game are configurable and can be changed to anything you want. Check the bindings panel under input settings!", + "When your gameplay HUD is hidden, you can press and hold Ctrl to view it temporarily!", + "Your gameplay HUD can be customized by using the skin layout editor. Open at any time via Ctrl-Shift-S!", + "Drag and drop any image into the skin editor to load it in quickly!", + "You can create mod presets to make toggling your favorite mod combinations easier!", + "Many mods have customisation settings that drastically change how they function. Click the Mod Customisation button in mod select to view settings!", + "Press Ctrl-Shift-R to switch to a random skin!", + "Press Ctrl-Shift-F to toggle the FPS Counter. But make sure not to pay too much attention to it!", + "While watching a reply replay, press Ctrl-H to toggle replay settings!", + "You can easily copy the mods from scores on a leaderboard by right-clicking on them!", + "Ctrl-Enter at song select will start a beatmap in autoplay mode!" }; return tips[RNG.Next(0, tips.Length)]; From 9f0fe6c6cab17dc1395e6874e57bf29a58b39a8c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Dec 2023 22:20:29 +0900 Subject: [PATCH 2234/4092] Fix common typos MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Screens/Menu/MenuTip.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/MenuTip.cs b/osu.Game/Screens/Menu/MenuTip.cs index e31281ea20..0f250768f1 100644 --- a/osu.Game/Screens/Menu/MenuTip.cs +++ b/osu.Game/Screens/Menu/MenuTip.cs @@ -113,13 +113,13 @@ namespace osu.Game.Screens.Menu "You can pause during a replay by pressing Space!", "Most of the hotkeys in the game are configurable and can be changed to anything you want. Check the bindings panel under input settings!", "When your gameplay HUD is hidden, you can press and hold Ctrl to view it temporarily!", - "Your gameplay HUD can be customized by using the skin layout editor. Open at any time via Ctrl-Shift-S!", + "Your gameplay HUD can be customized by using the skin layout editor. Open it at any time via Ctrl-Shift-S!", "Drag and drop any image into the skin editor to load it in quickly!", "You can create mod presets to make toggling your favorite mod combinations easier!", "Many mods have customisation settings that drastically change how they function. Click the Mod Customisation button in mod select to view settings!", "Press Ctrl-Shift-R to switch to a random skin!", "Press Ctrl-Shift-F to toggle the FPS Counter. But make sure not to pay too much attention to it!", - "While watching a reply replay, press Ctrl-H to toggle replay settings!", + "While watching a replay, press Ctrl-H to toggle replay settings!", "You can easily copy the mods from scores on a leaderboard by right-clicking on them!", "Ctrl-Enter at song select will start a beatmap in autoplay mode!" }; From 61c46b78bd380189944107782533915f9ec37995 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Dec 2023 22:24:16 +0900 Subject: [PATCH 2235/4092] Update MenuTip.cs Co-authored-by: Salman Ahmed --- osu.Game/Screens/Menu/MenuTip.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/MenuTip.cs b/osu.Game/Screens/Menu/MenuTip.cs index 0f250768f1..da349373c3 100644 --- a/osu.Game/Screens/Menu/MenuTip.cs +++ b/osu.Game/Screens/Menu/MenuTip.cs @@ -101,7 +101,7 @@ namespace osu.Game.Screens.Menu "If you find the UI too large or small, try adjusting UI scale in settings!", "Try adjusting the \"Screen Scaling\" mode to change your gameplay or UI area, even in fullscreen!", "What used to be \"osu!direct\" is available to all users just like on the website. You can access it anywhere using Ctrl-B!", - "Seeking in replays is available by dragging on the difficulty bar at the bottom of the screen or by using the left and right arrow keys!", + "Seeking in replays is available by dragging on the progress bar at the bottom of the screen or by using the left and right arrow keys!", "Multithreading support means that even with low \"FPS\" your input and judgements will be accurate!", "Try scrolling right in mod select to find a bunch of new fun mods!", "Most of the web content (profiles, rankings, etc.) are available natively in-game from the icons on the toolbar!", From 8df9a1ee1f8b8253e1438909f245aa709bd0be46 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 29 Dec 2023 20:43:00 +0300 Subject: [PATCH 2236/4092] Fix argon miss judgement on mania placed on a different position --- osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs index 4ce3c50f7c..6b1d8a7fcd 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs @@ -76,7 +76,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon this.ScaleTo(1.6f); this.ScaleTo(1, 100, Easing.In); - this.MoveTo(Vector2.Zero); this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint); this.RotateTo(0); From 08d88ec2fa94b8d6e855246e40d7eb23e632773a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 29 Dec 2023 20:47:18 +0300 Subject: [PATCH 2237/4092] Add back initial position transform to ensure correctness --- .../Skinning/Argon/ArgonJudgementPiece.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs index 6b1d8a7fcd..a191dee1ca 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs @@ -19,6 +19,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon { public partial class ArgonJudgementPiece : JudgementPiece, IAnimatableJudgement { + private const float judgement_y_position = 160; + private RingExplosion? ringExplosion; [Resolved] @@ -30,7 +32,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon AutoSizeAxes = Axes.Both; Origin = Anchor.Centre; - Y = 160; + Y = judgement_y_position; } [BackgroundDependencyLoader] @@ -76,6 +78,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon this.ScaleTo(1.6f); this.ScaleTo(1, 100, Easing.In); + this.MoveToY(judgement_y_position); this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint); this.RotateTo(0); From 807443b6481223fc14574b6c0829e4f197dee6cb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 30 Dec 2023 10:21:59 +0900 Subject: [PATCH 2238/4092] Add HitResult.SliderTailHit --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 8 ++++++++ osu.Game/Rulesets/Judgements/Judgement.cs | 2 ++ osu.Game/Rulesets/Scoring/HitResult.cs | 15 +++++++++++++++ osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 3 +++ 4 files changed, 28 insertions(+) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index d883b91abc..567bf6968f 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -84,6 +84,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, HitResult.SmallTickHit, 493_652)] [TestCase(ScoringMode.Standardised, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] [TestCase(ScoringMode.Standardised, HitResult.LargeTickHit, HitResult.LargeTickHit, 326_963)] + [TestCase(ScoringMode.Standardised, HitResult.SliderTailHit, HitResult.SliderTailHit, 326_963)] [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 1_000_030)] [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 1_000_150)] [TestCase(ScoringMode.Classic, HitResult.Miss, HitResult.Great, 0)] @@ -96,6 +97,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 49_365)] [TestCase(ScoringMode.Classic, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 32_696)] + [TestCase(ScoringMode.Classic, HitResult.SliderTailHit, HitResult.SliderTailHit, 32_696)] [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 100_003)] [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 100_015)] public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, int expectedScore) @@ -167,6 +169,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(HitResult.Perfect, HitResult.Miss)] [TestCase(HitResult.SmallTickHit, HitResult.SmallTickMiss)] [TestCase(HitResult.LargeTickHit, HitResult.LargeTickMiss)] + [TestCase(HitResult.SliderTailHit, HitResult.LargeTickMiss)] [TestCase(HitResult.SmallBonus, HitResult.IgnoreMiss)] [TestCase(HitResult.LargeBonus, HitResult.IgnoreMiss)] public void TestMinResults(HitResult hitResult, HitResult expectedMinResult) @@ -187,6 +190,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(HitResult.SmallTickHit, false)] [TestCase(HitResult.LargeTickMiss, true)] [TestCase(HitResult.LargeTickHit, true)] + [TestCase(HitResult.SliderTailHit, true)] [TestCase(HitResult.SmallBonus, false)] [TestCase(HitResult.LargeBonus, false)] public void TestAffectsCombo(HitResult hitResult, bool expectedReturnValue) @@ -207,6 +211,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(HitResult.SmallTickHit, true)] [TestCase(HitResult.LargeTickMiss, true)] [TestCase(HitResult.LargeTickHit, true)] + [TestCase(HitResult.SliderTailHit, true)] [TestCase(HitResult.SmallBonus, false)] [TestCase(HitResult.LargeBonus, false)] public void TestAffectsAccuracy(HitResult hitResult, bool expectedReturnValue) @@ -227,6 +232,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(HitResult.SmallTickHit, false)] [TestCase(HitResult.LargeTickMiss, false)] [TestCase(HitResult.LargeTickHit, false)] + [TestCase(HitResult.SliderTailHit, false)] [TestCase(HitResult.SmallBonus, true)] [TestCase(HitResult.LargeBonus, true)] public void TestIsBonus(HitResult hitResult, bool expectedReturnValue) @@ -247,6 +253,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(HitResult.SmallTickHit, true)] [TestCase(HitResult.LargeTickMiss, false)] [TestCase(HitResult.LargeTickHit, true)] + [TestCase(HitResult.SliderTailHit, true)] [TestCase(HitResult.SmallBonus, true)] [TestCase(HitResult.LargeBonus, true)] public void TestIsHit(HitResult hitResult, bool expectedReturnValue) @@ -267,6 +274,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(HitResult.SmallTickHit, true)] [TestCase(HitResult.LargeTickMiss, true)] [TestCase(HitResult.LargeTickHit, true)] + [TestCase(HitResult.SliderTailHit, true)] [TestCase(HitResult.SmallBonus, true)] [TestCase(HitResult.LargeBonus, true)] public void TestIsScorable(HitResult hitResult, bool expectedReturnValue) diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index 27402d522c..93386de483 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -73,6 +73,7 @@ namespace osu.Game.Rulesets.Judgements return HitResult.SmallTickMiss; case HitResult.LargeTickHit: + case HitResult.SliderTailHit: return HitResult.LargeTickMiss; default: @@ -104,6 +105,7 @@ namespace osu.Game.Rulesets.Judgements case HitResult.SmallTickMiss: return -DEFAULT_MAX_HEALTH_INCREASE * 0.5; + case HitResult.SliderTailHit: case HitResult.LargeTickHit: return DEFAULT_MAX_HEALTH_INCREASE; diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index 2d55f1a649..f307344347 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -139,6 +139,13 @@ namespace osu.Game.Rulesets.Scoring [Order(15)] ComboBreak, + /// + /// A special judgement similar to that's used to increase the valuation of the final tick of a slider. + /// + [EnumMember(Value = "slider_tail_hit")] + [Order(16)] + SliderTailHit, + /// /// A special result used as a padding value for legacy rulesets. It is a hit type and affects combo, but does not affect the base score (does not affect accuracy). /// @@ -188,6 +195,7 @@ namespace osu.Game.Rulesets.Scoring case HitResult.LargeTickMiss: case HitResult.LegacyComboIncrease: case HitResult.ComboBreak: + case HitResult.SliderTailHit: return true; default: @@ -246,6 +254,7 @@ namespace osu.Game.Rulesets.Scoring case HitResult.LargeTickMiss: case HitResult.SmallTickHit: case HitResult.SmallTickMiss: + case HitResult.SliderTailHit: return true; default: @@ -329,6 +338,9 @@ namespace osu.Game.Rulesets.Scoring case HitResult.ComboBreak: return true; + case HitResult.SliderTailHit: + return true; + default: // Note that IgnoreHit and IgnoreMiss are excluded as they do not affect score. return result >= HitResult.Miss && result < HitResult.IgnoreMiss; @@ -383,6 +395,9 @@ namespace osu.Game.Rulesets.Scoring if (minResult == HitResult.IgnoreMiss) return; + if (maxResult == HitResult.SliderTailHit && minResult != HitResult.LargeTickMiss) + throw new ArgumentOutOfRangeException(nameof(minResult), $"{HitResult.LargeTickMiss} is the only valid minimum result for a {maxResult} judgement."); + if (maxResult == HitResult.LargeTickHit && minResult != HitResult.LargeTickMiss) throw new ArgumentOutOfRangeException(nameof(minResult), $"{HitResult.LargeTickMiss} is the only valid minimum result for a {maxResult} judgement."); diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 5123668e54..837bb4080e 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -322,6 +322,9 @@ namespace osu.Game.Rulesets.Scoring case HitResult.LargeTickHit: return 30; + case HitResult.SliderTailHit: + return 150; + case HitResult.Meh: return 50; From 17a531209cf4ca6d2e2386f736779ad084a9fa44 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 30 Dec 2023 10:24:59 +0900 Subject: [PATCH 2239/4092] Use SliderTailHit result for slider tails (non-classic-mod) --- .../TestSceneSliderEarlyHitJudgement.cs | 2 +- .../TestSceneSliderInput.cs | 4 ++-- .../TestSceneSliderLateHitJudgement.cs | 14 +++++++------- osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs | 2 +- .../Scoring/OsuHealthProcessor.cs | 1 + .../Scoring/OsuLegacyHealthProcessor.cs | 1 + 6 files changed, 13 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderEarlyHitJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderEarlyHitJudgement.cs index 4ea21e51f6..19883060a0 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderEarlyHitJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderEarlyHitJudgement.cs @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Tests assertHeadJudgement(HitResult.Meh); assertTickJudgement(HitResult.LargeTickHit); - assertTailJudgement(HitResult.LargeTickHit); + assertTailJudgement(HitResult.SliderTailHit); assertSliderJudgement(HitResult.IgnoreHit); } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index 716d2e0756..12be74c4cc 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -467,13 +467,13 @@ namespace osu.Game.Rulesets.Osu.Tests private void assertHeadMissTailTracked() { - AddAssert("Tracking retained", () => judgementResults[^2].Type, () => Is.EqualTo(HitResult.LargeTickHit)); + AddAssert("Tracking retained", () => judgementResults[^2].Type, () => Is.EqualTo(HitResult.SliderTailHit)); AddAssert("Slider head missed", () => judgementResults.First().IsHit, () => Is.False); } private void assertMidSliderJudgements() { - AddAssert("Tracking acquired", () => judgementResults[^2].Type, () => Is.EqualTo(HitResult.LargeTickHit)); + AddAssert("Tracking acquired", () => judgementResults[^2].Type, () => Is.EqualTo(HitResult.SliderTailHit)); } private void assertMidSliderJudgementFail() diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs index 6ba9c723da..1ba4a60b75 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Tests }); assertHeadJudgement(HitResult.Ok); - assertTailJudgement(HitResult.LargeTickHit); + assertTailJudgement(HitResult.SliderTailHit); assertSliderJudgement(HitResult.IgnoreHit); } @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Tests assertTickJudgement(1, HitResult.LargeTickHit); assertTickJudgement(2, HitResult.LargeTickHit); assertTickJudgement(3, HitResult.LargeTickHit); - assertTailJudgement(HitResult.LargeTickHit); + assertTailJudgement(HitResult.SliderTailHit); assertSliderJudgement(HitResult.IgnoreHit); } @@ -182,7 +182,7 @@ namespace osu.Game.Rulesets.Osu.Tests assertHeadJudgement(HitResult.Meh); assertAllTickJudgements(HitResult.LargeTickHit); assertRepeatJudgement(HitResult.LargeTickHit); - assertTailJudgement(HitResult.LargeTickHit); + assertTailJudgement(HitResult.SliderTailHit); assertSliderJudgement(HitResult.IgnoreHit); } @@ -210,7 +210,7 @@ namespace osu.Game.Rulesets.Osu.Tests assertHeadJudgement(HitResult.Meh); assertRepeatJudgement(HitResult.LargeTickHit); - assertTailJudgement(HitResult.LargeTickHit); + assertTailJudgement(HitResult.SliderTailHit); assertSliderJudgement(HitResult.IgnoreHit); } @@ -245,7 +245,7 @@ namespace osu.Game.Rulesets.Osu.Tests assertAllTickJudgements(HitResult.LargeTickMiss); // This particular test actually starts tracking the slider just before the end, so the tail should be hit because of its leniency. - assertTailJudgement(HitResult.LargeTickHit); + assertTailJudgement(HitResult.SliderTailHit); assertSliderJudgement(HitResult.IgnoreHit); } @@ -276,7 +276,7 @@ namespace osu.Game.Rulesets.Osu.Tests assertHeadJudgement(HitResult.Meh); assertTickJudgement(0, HitResult.LargeTickMiss); - assertTailJudgement(HitResult.LargeTickHit); + assertTailJudgement(HitResult.SliderTailHit); assertSliderJudgement(HitResult.IgnoreHit); } @@ -307,7 +307,7 @@ namespace osu.Game.Rulesets.Osu.Tests assertHeadJudgement(HitResult.Meh); assertTickJudgement(0, HitResult.LargeTickMiss); assertTickJudgement(1, HitResult.LargeTickMiss); - assertTailJudgement(HitResult.LargeTickHit); + assertTailJudgement(HitResult.SliderTailHit); assertSliderJudgement(HitResult.IgnoreHit); } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index 357476ed30..ceee513412 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Objects public class TailJudgement : SliderEndJudgement { - public override HitResult MaxResult => HitResult.LargeTickHit; + public override HitResult MaxResult => HitResult.SliderTailHit; public override HitResult MinResult => HitResult.IgnoreMiss; } } diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs index 2eb257b3e6..fe6da9af35 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs @@ -91,6 +91,7 @@ namespace osu.Game.Rulesets.Osu.Scoring // When classic slider mechanics are enabled, this result comes from the tail. return 0.02; + case HitResult.SliderTailHit: case HitResult.LargeTickHit: switch (result.HitObject) { diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuLegacyHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuLegacyHealthProcessor.cs index e383e82b86..57d2f64e2c 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuLegacyHealthProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuLegacyHealthProcessor.cs @@ -57,6 +57,7 @@ namespace osu.Game.Rulesets.Osu.Scoring increase = 0.02; break; + case HitResult.SliderTailHit: case HitResult.LargeTickHit: // This result comes from either a slider tick or repeat. increase = hitObject is SliderTick ? 0.015 : 0.02; From bca060048266e1b124699def72b8f86e00b79c99 Mon Sep 17 00:00:00 2001 From: CaffeeLake Date: Wed, 27 Dec 2023 22:11:54 +0900 Subject: [PATCH 2240/4092] Use 0.99x or 1.01x Signed-off-by: CaffeeLake --- osu.Game/Overlays/Mods/ScoreMultiplierDisplay.cs | 10 ++++++++-- osu.Game/Screens/Select/FooterButtonMods.cs | 12 +++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Mods/ScoreMultiplierDisplay.cs b/osu.Game/Overlays/Mods/ScoreMultiplierDisplay.cs index c758632392..85bcf6f0b2 100644 --- a/osu.Game/Overlays/Mods/ScoreMultiplierDisplay.cs +++ b/osu.Game/Overlays/Mods/ScoreMultiplierDisplay.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; +using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -108,13 +109,13 @@ namespace osu.Game.Overlays.Mods Current.BindValueChanged(e => { - if (e.NewValue > Current.Default) + if (Precision.DefinitelyBigger(e.NewValue, Current.Default)) { MainBackground .FadeColour(colours.ForModType(ModType.DifficultyIncrease), transition_duration, Easing.OutQuint); counter.FadeColour(ColourProvider.Background5, transition_duration, Easing.OutQuint); } - else if (e.NewValue < Current.Default) + else if (Precision.DefinitelyBigger(Current.Default, e.NewValue)) { MainBackground .FadeColour(colours.ForModType(ModType.DifficultyReduction), transition_duration, Easing.OutQuint); @@ -131,6 +132,11 @@ namespace osu.Game.Overlays.Mods .FadeTo(0.15f, 60, Easing.OutQuint) .Then().FadeOut(500, Easing.OutQuint); + if (Precision.DefinitelyBigger(1.0, Current.Value) && Current.Value >= 0.995) + Current.Value = 0.99; + if (Precision.DefinitelyBigger(Current.Value, 1.0) && Current.Value < 1.005) + Current.Value = 1.01; + const float move_amount = 4; if (e.NewValue > e.OldValue) counter.MoveToY(Math.Max(-move_amount * 2, counter.Y - move_amount)).Then().MoveToY(0, transition_duration * 2, Easing.OutQuint); diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index 9a84f9a0aa..cfbd17be01 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -19,6 +19,7 @@ using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; using osu.Game.Input.Bindings; +using osu.Framework.Utils; namespace osu.Game.Screens.Select { @@ -88,11 +89,16 @@ namespace osu.Game.Screens.Select { double multiplier = Current.Value?.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier) ?? 1; - MultiplierText.Text = multiplier.Equals(1.0) ? string.Empty : $"{multiplier:N2}x"; + if (Precision.DefinitelyBigger(1.0, multiplier) && multiplier >= 0.995) + MultiplierText.Text = $"{0.99:N2}x"; + else if (Precision.DefinitelyBigger(multiplier, 1.0) && multiplier < 1.005) + MultiplierText.Text = $"{1.01:N2}x"; + else + MultiplierText.Text = multiplier.Equals(1.0) ? string.Empty : $"{multiplier:N2}x"; - if (multiplier > 1.0) + if (Precision.DefinitelyBigger(multiplier, 1.0)) MultiplierText.FadeColour(highMultiplierColour, 200); - else if (multiplier < 1.0) + else if (Precision.DefinitelyBigger(1.0, multiplier)) MultiplierText.FadeColour(lowMultiplierColour, 200); else MultiplierText.FadeColour(Color4.White, 200); From cc89390ea89e6b1f8127adef2717e4d6438b7b46 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 30 Dec 2023 21:33:03 +0300 Subject: [PATCH 2241/4092] Expose `SuggestedOffset` bindable for testing purposes --- .../Sections/Audio/AudioOffsetAdjustControl.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs index 08bf4b0dad..e46dc602eb 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs @@ -24,6 +24,8 @@ namespace osu.Game.Overlays.Settings.Sections.Audio { public partial class AudioOffsetAdjustControl : SettingsItem { + public IBindable SuggestedOffset => ((AudioOffsetPreview)Control).SuggestedOffset; + [BackgroundDependencyLoader] private void load() { @@ -44,7 +46,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio private readonly IBindableList averageHitErrorHistory = new BindableList(); - private readonly Bindable suggestedOffset = new Bindable(); + public readonly Bindable SuggestedOffset = new Bindable(); private Container notchContainer = null!; private TextFlowContainer hintText = null!; @@ -90,8 +92,8 @@ namespace osu.Game.Overlays.Settings.Sections.Audio Text = "Apply suggested offset", Action = () => { - if (suggestedOffset.Value.HasValue) - current.Value = suggestedOffset.Value.Value; + if (SuggestedOffset.Value.HasValue) + current.Value = SuggestedOffset.Value.Value; hitErrorTracker.ClearHistory(); } } @@ -104,7 +106,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio base.LoadComplete(); averageHitErrorHistory.BindCollectionChanged(updateDisplay, true); - suggestedOffset.BindValueChanged(_ => updateHintText(), true); + SuggestedOffset.BindValueChanged(_ => updateHintText(), true); } private void updateDisplay(object? _, NotifyCollectionChangedEventArgs e) @@ -143,17 +145,17 @@ namespace osu.Game.Overlays.Settings.Sections.Audio break; } - suggestedOffset.Value = averageHitErrorHistory.Any() ? -averageHitErrorHistory.Average(dataPoint => dataPoint.SuggestedGlobalAudioOffset) : null; + SuggestedOffset.Value = averageHitErrorHistory.Any() ? -averageHitErrorHistory.Average(dataPoint => dataPoint.SuggestedGlobalAudioOffset) : null; } private float getXPositionForOffset(double offset) => (float)(Math.Clamp(offset, current.MinValue, current.MaxValue) / (2 * current.MaxValue)); private void updateHintText() { - hintText.Text = suggestedOffset.Value == null + hintText.Text = SuggestedOffset.Value == null ? @"Play a few beatmaps to receive a suggested offset!" - : $@"Based on the last {averageHitErrorHistory.Count} play(s), the suggested offset is {suggestedOffset.Value:N0} ms."; - applySuggestion.Enabled.Value = suggestedOffset.Value != null; + : $@"Based on the last {averageHitErrorHistory.Count} play(s), the suggested offset is {SuggestedOffset.Value:N0} ms."; + applySuggestion.Enabled.Value = SuggestedOffset.Value != null; } } } From 68dd103c89e25619bf4aea6aea65e61d369396d6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 30 Dec 2023 21:33:22 +0300 Subject: [PATCH 2242/4092] Add failing test cases --- .../TestSceneAudioOffsetAdjustControl.cs | 88 ++++++++++++++++--- 1 file changed, 78 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneAudioOffsetAdjustControl.cs b/osu.Game.Tests/Visual/Settings/TestSceneAudioOffsetAdjustControl.cs index efb65bb0a8..85cde966b1 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneAudioOffsetAdjustControl.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneAudioOffsetAdjustControl.cs @@ -3,7 +3,7 @@ using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Utils; @@ -25,9 +25,15 @@ namespace osu.Game.Tests.Visual.Settings private Container content = null!; protected override Container Content => content; + private OsuConfigManager localConfig = null!; + private AudioOffsetAdjustControl adjustControl = null!; + [BackgroundDependencyLoader] private void load() { + localConfig = new OsuConfigManager(LocalStorage); + Dependencies.CacheAs(localConfig); + base.Content.AddRange(new Drawable[] { tracker, @@ -41,17 +47,21 @@ namespace osu.Game.Tests.Visual.Settings }); } - [Test] - public void TestBehaviour() + [SetUp] + public void SetUp() => Schedule(() => { - AddStep("create control", () => Child = new AudioOffsetAdjustControl + Child = adjustControl = new AudioOffsetAdjustControl { - Current = new BindableDouble - { - MinValue = -500, - MaxValue = 500 - } - }); + Current = localConfig.GetBindable(OsuSetting.AudioOffset), + }; + + localConfig.SetValue(OsuSetting.AudioOffset, 0.0); + tracker.ClearHistory(); + }); + + [Test] + public void TestDisplay() + { AddStep("set new score", () => statics.SetValue(Static.LastLocalUserScore, new ScoreInfo { HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(RNG.NextDouble(-100, 100)), @@ -59,5 +69,63 @@ namespace osu.Game.Tests.Visual.Settings })); AddStep("clear history", () => tracker.ClearHistory()); } + + [Test] + public void TestBehaviour() + { + AddStep("set score with -20ms", () => setScore(-20)); + AddAssert("suggested global offset is 20ms", () => adjustControl.SuggestedOffset.Value, () => Is.EqualTo(20)); + AddStep("clear history", () => tracker.ClearHistory()); + + AddStep("set score with 40ms", () => setScore(40)); + AddAssert("suggested global offset is -40ms", () => adjustControl.SuggestedOffset.Value, () => Is.EqualTo(-40)); + AddStep("clear history", () => tracker.ClearHistory()); + } + + [Test] + public void TestNonZeroGlobalOffset() + { + AddStep("set global offset to -20ms", () => localConfig.SetValue(OsuSetting.AudioOffset, -20.0)); + AddStep("set score with -20ms", () => setScore(-20)); + AddAssert("suggested global offset is 0ms", () => adjustControl.SuggestedOffset.Value, () => Is.EqualTo(0)); + AddStep("clear history", () => tracker.ClearHistory()); + + AddStep("set global offset to 20ms", () => localConfig.SetValue(OsuSetting.AudioOffset, 20.0)); + AddStep("set score with 40ms", () => setScore(40)); + AddAssert("suggested global offset is -20ms", () => adjustControl.SuggestedOffset.Value, () => Is.EqualTo(-20)); + AddStep("clear history", () => tracker.ClearHistory()); + } + + [Test] + public void TestMultiplePlays() + { + AddStep("set score with -20ms", () => setScore(-20)); + AddStep("set score with -10ms", () => setScore(-10)); + AddAssert("suggested global offset is 15ms", () => adjustControl.SuggestedOffset.Value, () => Is.EqualTo(15)); + AddStep("clear history", () => tracker.ClearHistory()); + + AddStep("set score with -20ms", () => setScore(-20)); + AddStep("set global offset to 30ms", () => localConfig.SetValue(OsuSetting.AudioOffset, 30.0)); + AddStep("set score with 10ms", () => setScore(10)); + AddAssert("suggested global offset is 20ms", () => adjustControl.SuggestedOffset.Value, () => Is.EqualTo(20)); + AddStep("clear history", () => tracker.ClearHistory()); + } + + private void setScore(double averageHitError) + { + statics.SetValue(Static.LastLocalUserScore, new ScoreInfo + { + HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(averageHitError), + BeatmapInfo = Beatmap.Value.BeatmapInfo, + }); + } + + protected override void Dispose(bool isDisposing) + { + if (localConfig.IsNotNull()) + localConfig.Dispose(); + + base.Dispose(isDisposing); + } } } From e6fe631625643a23b18641a132c6e67c1062b9ee Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 30 Dec 2023 21:34:37 +0300 Subject: [PATCH 2243/4092] Fix suggested value in audio offset adjust control being opposite in signs --- .../Settings/Sections/Audio/AudioOffsetAdjustControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs index e46dc602eb..90f5a59215 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs @@ -145,7 +145,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio break; } - SuggestedOffset.Value = averageHitErrorHistory.Any() ? -averageHitErrorHistory.Average(dataPoint => dataPoint.SuggestedGlobalAudioOffset) : null; + SuggestedOffset.Value = averageHitErrorHistory.Any() ? averageHitErrorHistory.Average(dataPoint => dataPoint.SuggestedGlobalAudioOffset) : null; } private float getXPositionForOffset(double offset) => (float)(Math.Clamp(offset, current.MinValue, current.MaxValue) / (2 * current.MaxValue)); From 452f201f0673426df7e88b78f720293863e8d660 Mon Sep 17 00:00:00 2001 From: iminlikewithyou Date: Sat, 30 Dec 2023 12:56:38 -0600 Subject: [PATCH 2244/4092] use margins isntead of moving the position of the sprite --- osu.Game/Screens/Menu/MainMenuButton.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenuButton.cs b/osu.Game/Screens/Menu/MainMenuButton.cs index 3d56a75ca8..422599a4a8 100644 --- a/osu.Game/Screens/Menu/MainMenuButton.cs +++ b/osu.Game/Screens/Menu/MainMenuButton.cs @@ -129,7 +129,8 @@ namespace osu.Game.Screens.Menu Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(32), - Position = new Vector2(0, -4), + Position = new Vector2(0, 0), + Margin = new MarginPadding { Top = -4 }, Icon = symbol }, new OsuSpriteText @@ -138,7 +139,8 @@ namespace osu.Game.Screens.Menu AllowMultiline = false, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Position = new Vector2(-3, 35), + Position = new Vector2(0, 35), + Margin = new MarginPadding { Left = -3 }, Text = text } } @@ -189,7 +191,7 @@ namespace osu.Game.Screens.Menu { icon.ClearTransforms(); icon.RotateTo(0, 500, Easing.Out); - icon.MoveTo(new Vector2(0, -4), 500, Easing.Out); + icon.MoveTo(Vector2.Zero, 500, Easing.Out); icon.ScaleTo(Vector2.One, 200, Easing.Out); if (State == ButtonState.Expanded) From 535177ab97c06c291aaba6860b0499096413c708 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 30 Dec 2023 11:40:40 -0800 Subject: [PATCH 2245/4092] Remove outdated main menu version reference on issue template --- .github/ISSUE_TEMPLATE/bug-issue.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug-issue.yml b/.github/ISSUE_TEMPLATE/bug-issue.yml index dfdcf8d320..a8a5d5e64b 100644 --- a/.github/ISSUE_TEMPLATE/bug-issue.yml +++ b/.github/ISSUE_TEMPLATE/bug-issue.yml @@ -42,7 +42,7 @@ body: - type: input attributes: label: Version - description: The version you encountered this bug on. This is shown at the bottom of the main menu and also at the end of the settings screen. + description: The version you encountered this bug on. This is shown at the end of the settings overlay. validations: required: true - type: markdown From 922b6ccb83b0e83d9b2be6f63ee7e8366e500c73 Mon Sep 17 00:00:00 2001 From: Gabriel Del Nero <43073074+Gabixel@users.noreply.github.com> Date: Sun, 31 Dec 2023 00:36:55 +0100 Subject: [PATCH 2246/4092] Use `FontAwesome` solid heart icon instead of `OsuIcon`'s --- osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs index 08a816930e..85751e7457 100644 --- a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs @@ -145,7 +145,7 @@ namespace osu.Game.Overlays.Profile.Header bool anyInfoAdded = false; anyInfoAdded |= tryAddInfo(FontAwesome.Solid.MapMarker, user.Location); - anyInfoAdded |= tryAddInfo(OsuIcon.Heart, user.Interests); + anyInfoAdded |= tryAddInfo(FontAwesome.Solid.Heart, user.Interests); anyInfoAdded |= tryAddInfo(FontAwesome.Solid.Suitcase, user.Occupation); if (anyInfoAdded) From 7cfb786b1a1dae5ef00c5d0359de336c4725c757 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 31 Dec 2023 04:48:06 +0300 Subject: [PATCH 2247/4092] Add helper method for properly formatting score multiplier in `ModUtils` --- osu.Game/Utils/ModUtils.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index 1bd60fcdde..dad8c72aa1 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -5,6 +5,8 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Localisation; using osu.Game.Online.API; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -226,5 +228,21 @@ namespace osu.Game.Utils return proposedWereValid; } + + /// + /// Given a value of a score multiplier, returns a string version with special handling for a value near 1.00x. + /// + /// The value of the score multiplier. + /// A formatted score multiplier with a trailing "x" symbol + public static LocalisableString FormatScoreMultiplier(double scoreMultiplier) + { + // Round multiplier values away from 1.00x to two significant digits. + if (scoreMultiplier > 1) + scoreMultiplier = Math.Ceiling(scoreMultiplier * 100) / 100; + else + scoreMultiplier = Math.Floor(scoreMultiplier * 100) / 100; + + return scoreMultiplier.ToLocalisableString("0.00x"); + } } } From 4dc11c4c48d49104a2d5e504a58441f1752e6d7b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 31 Dec 2023 05:18:07 +0300 Subject: [PATCH 2248/4092] Update existing code to use helper method --- osu.Game/Overlays/Mods/ScoreMultiplierDisplay.cs | 14 ++++---------- osu.Game/Screens/Select/FooterButtonMods.cs | 14 ++++---------- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/osu.Game/Overlays/Mods/ScoreMultiplierDisplay.cs b/osu.Game/Overlays/Mods/ScoreMultiplierDisplay.cs index 85bcf6f0b2..b599b53082 100644 --- a/osu.Game/Overlays/Mods/ScoreMultiplierDisplay.cs +++ b/osu.Game/Overlays/Mods/ScoreMultiplierDisplay.cs @@ -4,18 +4,17 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; -using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; using osu.Game.Rulesets.Mods; +using osu.Game.Utils; using osuTK; namespace osu.Game.Overlays.Mods @@ -109,13 +108,13 @@ namespace osu.Game.Overlays.Mods Current.BindValueChanged(e => { - if (Precision.DefinitelyBigger(e.NewValue, Current.Default)) + if (e.NewValue > Current.Default) { MainBackground .FadeColour(colours.ForModType(ModType.DifficultyIncrease), transition_duration, Easing.OutQuint); counter.FadeColour(ColourProvider.Background5, transition_duration, Easing.OutQuint); } - else if (Precision.DefinitelyBigger(Current.Default, e.NewValue)) + else if (e.NewValue < Current.Default) { MainBackground .FadeColour(colours.ForModType(ModType.DifficultyReduction), transition_duration, Easing.OutQuint); @@ -132,11 +131,6 @@ namespace osu.Game.Overlays.Mods .FadeTo(0.15f, 60, Easing.OutQuint) .Then().FadeOut(500, Easing.OutQuint); - if (Precision.DefinitelyBigger(1.0, Current.Value) && Current.Value >= 0.995) - Current.Value = 0.99; - if (Precision.DefinitelyBigger(Current.Value, 1.0) && Current.Value < 1.005) - Current.Value = 1.01; - const float move_amount = 4; if (e.NewValue > e.OldValue) counter.MoveToY(Math.Max(-move_amount * 2, counter.Y - move_amount)).Then().MoveToY(0, transition_duration * 2, Easing.OutQuint); @@ -153,7 +147,7 @@ namespace osu.Game.Overlays.Mods { protected override double RollingDuration => 500; - protected override LocalisableString FormatCount(double count) => count.ToLocalisableString(@"0.00x"); + protected override LocalisableString FormatCount(double count) => ModUtils.FormatScoreMultiplier(count); protected override OsuSpriteText CreateSpriteText() => new OsuSpriteText { diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index cfbd17be01..69782c25bb 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -19,7 +19,7 @@ using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; using osu.Game.Input.Bindings; -using osu.Framework.Utils; +using osu.Game.Utils; namespace osu.Game.Screens.Select { @@ -88,17 +88,11 @@ namespace osu.Game.Screens.Select private void updateMultiplierText() => Schedule(() => { double multiplier = Current.Value?.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier) ?? 1; + MultiplierText.Text = multiplier == 1 ? string.Empty : ModUtils.FormatScoreMultiplier(multiplier); - if (Precision.DefinitelyBigger(1.0, multiplier) && multiplier >= 0.995) - MultiplierText.Text = $"{0.99:N2}x"; - else if (Precision.DefinitelyBigger(multiplier, 1.0) && multiplier < 1.005) - MultiplierText.Text = $"{1.01:N2}x"; - else - MultiplierText.Text = multiplier.Equals(1.0) ? string.Empty : $"{multiplier:N2}x"; - - if (Precision.DefinitelyBigger(multiplier, 1.0)) + if (multiplier > 1) MultiplierText.FadeColour(highMultiplierColour, 200); - else if (Precision.DefinitelyBigger(1.0, multiplier)) + else if (multiplier < 1) MultiplierText.FadeColour(lowMultiplierColour, 200); else MultiplierText.FadeColour(Color4.White, 200); From cd9250d08ccc415b5d94bacf8e0c31eb0d273ffb Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 31 Dec 2023 05:18:10 +0300 Subject: [PATCH 2249/4092] Add test coverage --- osu.Game.Tests/Mods/ModUtilsTest.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game.Tests/Mods/ModUtilsTest.cs b/osu.Game.Tests/Mods/ModUtilsTest.cs index 9107ddd1ae..4f10a0ea54 100644 --- a/osu.Game.Tests/Mods/ModUtilsTest.cs +++ b/osu.Game.Tests/Mods/ModUtilsTest.cs @@ -310,6 +310,13 @@ namespace osu.Game.Tests.Mods Assert.That(invalid?.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid)); } + [Test] + public void TestFormatScoreMultiplier() + { + Assert.AreEqual(ModUtils.FormatScoreMultiplier(0.9999).ToString(), "0.99x"); + Assert.AreEqual(ModUtils.FormatScoreMultiplier(1.0001).ToString(), "1.01x"); + } + public abstract class CustomMod1 : Mod, IModCompatibilitySpecification { } @@ -339,6 +346,16 @@ namespace osu.Game.Tests.Mods public override bool ValidForMultiplayerAsFreeMod => false; } + public class EditableMod : Mod + { + public override string Name => string.Empty; + public override LocalisableString Description => string.Empty; + public override string Acronym => string.Empty; + public override double ScoreMultiplier => Multiplier; + + public double Multiplier = 1; + } + public interface IModCompatibilitySpecification { } From 5ae5d7f92dc7b002e5b685922707c9cef61edbf9 Mon Sep 17 00:00:00 2001 From: iminlikewithyou Date: Sat, 30 Dec 2023 23:59:47 -0600 Subject: [PATCH 2250/4092] change floor to round --- osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs index b5315feccb..45213b7bdb 100644 --- a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs @@ -224,7 +224,7 @@ namespace osu.Game.Screens.Edit.Timing row.Alpha = time < selectedGroupStartTime || time > selectedGroupEndTime ? 0.2f : 1; row.WaveformOffsetTo(-offset, animated); row.WaveformScale = new Vector2(scale, 1); - row.BeatIndex = (int)Math.Floor(index); + row.BeatIndex = (int)Math.Round(index); index++; } From 0c85fd496f9d555e1ec1c0b07fbebd2f9e8fd998 Mon Sep 17 00:00:00 2001 From: wooster0 Date: Sun, 31 Dec 2023 22:10:49 +0900 Subject: [PATCH 2251/4092] Don't leave scores screen empty if no scores are present yet Addresses https://github.com/ppy/osu/discussions/23787 I originally wanted to set `allowShowingResults` to false if there are no results but because this involves an API request to fetch the scores that would mean all the scores would have to be fetched all at once so that it knows whether to hide or show the "View results" button on a beatmap. Because that would slow things down and be very inefficient, this still allows the user to view the scores screen but if there aren't any scores, it shows a text, which I think is at least for now better than nothing. As for the testing of this, I wasn't sure how to not generate scores only for one specific test so I opted into not using `SetUpSteps` and doing it that way. --- .../TestScenePlaylistsResultsScreen.cs | 47 ++++++++++++++----- osu.Game/Screens/Ranking/ResultsScreen.cs | 35 ++++++++++---- osu.Game/Screens/Ranking/ScorePanelList.cs | 2 + 3 files changed, 63 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index cb422d8c06..e36e7f54ba 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -42,15 +42,15 @@ namespace osu.Game.Tests.Visual.Playlists private int totalCount; private ScoreInfo userScore; - [SetUpSteps] - public override void SetUpSteps() + // We don't use SetUpSteps for this because one of the tests shouldn't receive any scores. + public void InitialiseUserScoresAndBeatmap(bool noScores = false) { base.SetUpSteps(); // Previous test instances of the results screen may still exist at this point so wait for // those screens to be cleaned up by the base SetUpSteps before re-initialising test state. - // The the screen also holds a leased Beatmap bindable so reassigning it must happen after - // the screen as been exited. + // The screen also holds a leased Beatmap bindable so reassigning it must happen after + // the screen has been exited. AddStep("initialise user scores and beatmap", () => { lowestScoreId = 1; @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Playlists userScore.Statistics = new Dictionary(); userScore.MaximumStatistics = new Dictionary(); - bindHandler(); + bindHandler(noScores: noScores); // Beatmap is required to be an actual beatmap so the scores can get their scores correctly // calculated for standardised scoring, else the tests that rely on ordering will fall over. @@ -74,6 +74,8 @@ namespace osu.Game.Tests.Visual.Playlists [Test] public void TestShowWithUserScore() { + InitialiseUserScoresAndBeatmap(); + AddStep("bind user score info handler", () => bindHandler(userScore: userScore)); createResults(() => userScore); @@ -86,6 +88,8 @@ namespace osu.Game.Tests.Visual.Playlists [Test] public void TestShowNullUserScore() { + InitialiseUserScoresAndBeatmap(); + createResults(); AddAssert("top score selected", () => this.ChildrenOfType().OrderByDescending(p => p.Score.TotalScore).First().State == PanelState.Expanded); @@ -94,6 +98,8 @@ namespace osu.Game.Tests.Visual.Playlists [Test] public void TestShowUserScoreWithDelay() { + InitialiseUserScoresAndBeatmap(); + AddStep("bind user score info handler", () => bindHandler(true, userScore)); createResults(() => userScore); @@ -105,6 +111,8 @@ namespace osu.Game.Tests.Visual.Playlists [Test] public void TestShowNullUserScoreWithDelay() { + InitialiseUserScoresAndBeatmap(); + AddStep("bind delayed handler", () => bindHandler(true)); createResults(); @@ -115,6 +123,8 @@ namespace osu.Game.Tests.Visual.Playlists [Test] public void TestFetchWhenScrolledToTheRight() { + InitialiseUserScoresAndBeatmap(); + createResults(); AddStep("bind delayed handler", () => bindHandler(true)); @@ -137,6 +147,8 @@ namespace osu.Game.Tests.Visual.Playlists [Test] public void TestFetchWhenScrolledToTheLeft() { + InitialiseUserScoresAndBeatmap(); + AddStep("bind user score info handler", () => bindHandler(userScore: userScore)); createResults(() => userScore); @@ -158,7 +170,16 @@ namespace osu.Game.Tests.Visual.Playlists } } - private void createResults(Func getScore = null) + [Test] + public void TestShowWithNoScores() + { + InitialiseUserScoresAndBeatmap(noScores: true); + + createResults(noScores: true); + AddAssert("no scores visible", () => resultsScreen.ScorePanelList.GetScorePanels().Count() == 0); + } + + private void createResults(Func getScore = null, bool noScores = false) { AddStep("load results", () => { @@ -169,11 +190,13 @@ namespace osu.Game.Tests.Visual.Playlists }); AddUntilStep("wait for screen to load", () => resultsScreen.IsLoaded); - waitForDisplay(); + waitForDisplay(noScores); } - private void waitForDisplay() + private void waitForDisplay(bool noScores = false) { + if (noScores) return; + AddUntilStep("wait for scores loaded", () => requestComplete // request handler may need to fire more than once to get scores. @@ -183,7 +206,7 @@ namespace osu.Game.Tests.Visual.Playlists AddWaitStep("wait for display", 5); } - private void bindHandler(bool delayed = false, ScoreInfo userScore = null, bool failRequests = false) => ((DummyAPIAccess)API).HandleRequest = request => + private void bindHandler(bool delayed = false, ScoreInfo userScore = null, bool failRequests = false, bool noScores = false) => ((DummyAPIAccess)API).HandleRequest = request => { // pre-check for requests we should be handling (as they are scheduled below). switch (request) @@ -219,7 +242,7 @@ namespace osu.Game.Tests.Visual.Playlists break; case IndexPlaylistScoresRequest i: - triggerSuccess(i, createIndexResponse(i)); + triggerSuccess(i, createIndexResponse(i, noScores)); break; } }, delay); @@ -301,10 +324,12 @@ namespace osu.Game.Tests.Visual.Playlists return multiplayerUserScore; } - private IndexedMultiplayerScores createIndexResponse(IndexPlaylistScoresRequest req) + private IndexedMultiplayerScores createIndexResponse(IndexPlaylistScoresRequest req, bool noScores = false) { var result = new IndexedMultiplayerScores(); + if (noScores) return result; + string sort = req.IndexParams?.Properties["sort"].ToObject() ?? "score_desc"; for (int i = 1; i <= scores_per_result; i++) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index e3d19725da..e18ab63706 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -19,6 +19,7 @@ using osu.Framework.Input.Events; using osu.Framework.Screens; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Online.API; @@ -204,17 +205,31 @@ namespace osu.Game.Screens.Ranking if (lastFetchCompleted) { - APIRequest nextPageRequest = null; - - if (ScorePanelList.IsScrolledToStart) - nextPageRequest = FetchNextPage(-1, fetchScoresCallback); - else if (ScorePanelList.IsScrolledToEnd) - nextPageRequest = FetchNextPage(1, fetchScoresCallback); - - if (nextPageRequest != null) + if (ScorePanelList.IsEmpty) { - lastFetchCompleted = false; - api.Queue(nextPageRequest); + // This can happen if a beatmap part of a playlist hasn't been played yet. + VerticalScrollContent.Add(new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 32, weight: FontWeight.Regular), + Text = "no scores yet!", + }); + } + else + { + APIRequest nextPageRequest = null; + + if (ScorePanelList.IsScrolledToStart) + nextPageRequest = FetchNextPage(-1, fetchScoresCallback); + else if (ScorePanelList.IsScrolledToEnd) + nextPageRequest = FetchNextPage(1, fetchScoresCallback); + + if (nextPageRequest != null) + { + lastFetchCompleted = false; + api.Queue(nextPageRequest); + } } } } diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index b75f3d86ff..95c90e35a0 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -49,6 +49,8 @@ namespace osu.Game.Screens.Ranking public bool AllPanelsVisible => flow.All(p => p.IsPresent); + public bool IsEmpty => flow.Count == 0; + /// /// The current scroll position. /// From 4a94cfd35d74060daf691aabf6cf6235a1282344 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 31 Dec 2023 16:47:10 +0300 Subject: [PATCH 2252/4092] Fix failing test case --- .../Visual/UserInterface/TestSceneFooterButtonMods.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs index 4e1bf1390a..b98b91e8e5 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs @@ -5,10 +5,12 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Localisation; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.Select; +using osu.Game.Utils; namespace osu.Game.Tests.Visual.UserInterface { @@ -74,7 +76,7 @@ namespace osu.Game.Tests.Visual.UserInterface private bool assertModsMultiplier(IEnumerable mods) { double multiplier = mods.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier); - string expectedValue = multiplier.Equals(1.0) ? string.Empty : $"{multiplier:N2}x"; + string expectedValue = multiplier == 1 ? string.Empty : ModUtils.FormatScoreMultiplier(multiplier).ToString(); return expectedValue == footerButtonMods.MultiplierText.Current.Value; } From ca0f09733ab7741a1ee583d710bafead4fe86039 Mon Sep 17 00:00:00 2001 From: CaffeeLake Date: Sun, 31 Dec 2023 23:35:42 +0900 Subject: [PATCH 2253/4092] Remove unnecessary using directives Signed-off-by: CaffeeLake --- osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs index b98b91e8e5..a95bb2c9e3 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; -using osu.Framework.Localisation; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; From ad4b5f6deddc8cab855be638d7c77a358e6bf05a Mon Sep 17 00:00:00 2001 From: CaffeeLake Date: Mon, 1 Jan 2024 08:32:21 +0900 Subject: [PATCH 2254/4092] Fix: floating point errors Signed-off-by: CaffeeLake --- osu.Game/Utils/ModUtils.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index dad8c72aa1..252579a186 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -238,9 +238,9 @@ namespace osu.Game.Utils { // Round multiplier values away from 1.00x to two significant digits. if (scoreMultiplier > 1) - scoreMultiplier = Math.Ceiling(scoreMultiplier * 100) / 100; + scoreMultiplier = Math.Ceiling(Math.Round(scoreMultiplier * 100, 12)) / 100; else - scoreMultiplier = Math.Floor(scoreMultiplier * 100) / 100; + scoreMultiplier = Math.Floor(Math.Round(scoreMultiplier * 100, 12)) / 100; return scoreMultiplier.ToLocalisableString("0.00x"); } From f3b88c318b57ee222973779359611fdc18fe6eb6 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 28 Dec 2023 17:49:14 +0100 Subject: [PATCH 2255/4092] Add rotation to snap grid visual --- .../TestSceneRectangularPositionSnapGrid.cs | 16 +- .../Components/RectangularPositionSnapGrid.cs | 140 +++++++++++++++--- 2 files changed, 125 insertions(+), 31 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs index e73a45e154..210af09055 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs @@ -33,28 +33,30 @@ namespace osu.Game.Tests.Visual.Editing }, content = new Container { - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(10), } }); } private static readonly object[][] test_cases = { - new object[] { new Vector2(0, 0), new Vector2(10, 10) }, - new object[] { new Vector2(240, 180), new Vector2(10, 15) }, - new object[] { new Vector2(160, 120), new Vector2(30, 20) }, - new object[] { new Vector2(480, 360), new Vector2(100, 100) }, + new object[] { new Vector2(0, 0), new Vector2(10, 10), 0f }, + new object[] { new Vector2(240, 180), new Vector2(10, 15), 30f }, + new object[] { new Vector2(160, 120), new Vector2(30, 20), -30f }, + new object[] { new Vector2(480, 360), new Vector2(100, 100), 0f }, }; [TestCaseSource(nameof(test_cases))] - public void TestRectangularGrid(Vector2 position, Vector2 spacing) + public void TestRectangularGrid(Vector2 position, Vector2 spacing, float rotation) { RectangularPositionSnapGrid grid = null; AddStep("create grid", () => Child = grid = new RectangularPositionSnapGrid(position) { RelativeSizeAxes = Axes.Both, - Spacing = spacing + Spacing = spacing, + GridLineRotation = rotation }); AddStep("add snapping cursor", () => Add(new SnappingCursorContainer diff --git a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs index cfc01fe17b..160e7e026b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs @@ -15,10 +15,20 @@ namespace osu.Game.Screens.Edit.Compose.Components { public partial class RectangularPositionSnapGrid : CompositeDrawable { + private Vector2 startPosition; + /// /// The position of the origin of this in local coordinates. /// - public Vector2 StartPosition { get; } + public Vector2 StartPosition + { + get => startPosition; + set + { + startPosition = value; + gridCache.Invalidate(); + } + } private Vector2 spacing = Vector2.One; @@ -38,11 +48,27 @@ namespace osu.Game.Screens.Edit.Compose.Components } } + private float gridLineRotation; + + /// + /// The rotation in degrees of the grid lines of this . + /// + public float GridLineRotation + { + get => gridLineRotation; + set + { + gridLineRotation = value; + gridCache.Invalidate(); + } + } + private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); public RectangularPositionSnapGrid(Vector2 startPosition) { StartPosition = startPosition; + Masking = true; AddLayout(gridCache); } @@ -65,47 +91,43 @@ namespace osu.Game.Screens.Edit.Compose.Components private void createContent() { var drawSize = DrawSize; + var rot = Quaternion.FromAxisAngle(Vector3.UnitZ, MathHelper.DegreesToRadians(GridLineRotation)); - generateGridLines(Direction.Horizontal, StartPosition.Y, 0, -Spacing.Y); - generateGridLines(Direction.Horizontal, StartPosition.Y, drawSize.Y, Spacing.Y); + generateGridLines(Vector2.Transform(new Vector2(0, -Spacing.Y), rot), GridLineRotation + 90, drawSize); + generateGridLines(Vector2.Transform(new Vector2(0, Spacing.Y), rot), GridLineRotation + 90, drawSize); - generateGridLines(Direction.Vertical, StartPosition.X, 0, -Spacing.X); - generateGridLines(Direction.Vertical, StartPosition.X, drawSize.X, Spacing.X); + generateGridLines(Vector2.Transform(new Vector2(-Spacing.X, 0), rot), GridLineRotation, drawSize); + generateGridLines(Vector2.Transform(new Vector2(Spacing.X, 0), rot), GridLineRotation, drawSize); + + generateOutline(drawSize); } - private void generateGridLines(Direction direction, float startPosition, float endPosition, float step) + private void generateGridLines(Vector2 step, float rotation, Vector2 drawSize) { int index = 0; - float currentPosition = startPosition; + var currentPosition = startPosition; // Make lines the same width independent of display resolution. float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; + float lineLength = drawSize.Length * 2; List generatedLines = new List(); - while (Precision.AlmostBigger((endPosition - currentPosition) * Math.Sign(step), 0)) + while (lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize) || + isMovingTowardsBox(currentPosition, step, drawSize)) { var gridLine = new Box { Colour = Colour4.White, Alpha = 0.1f, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.None, + Width = lineWidth, + Height = lineLength, + Position = currentPosition, + Rotation = rotation, }; - if (direction == Direction.Horizontal) - { - gridLine.Origin = Anchor.CentreLeft; - gridLine.RelativeSizeAxes = Axes.X; - gridLine.Height = lineWidth; - gridLine.Y = currentPosition; - } - else - { - gridLine.Origin = Anchor.TopCentre; - gridLine.RelativeSizeAxes = Axes.Y; - gridLine.Width = lineWidth; - gridLine.X = currentPosition; - } - generatedLines.Add(gridLine); index += 1; @@ -116,11 +138,81 @@ namespace osu.Game.Screens.Edit.Compose.Components return; generatedLines.First().Alpha = 0.3f; - generatedLines.Last().Alpha = 0.3f; AddRangeInternal(generatedLines); } + private bool isMovingTowardsBox(Vector2 currentPosition, Vector2 step, Vector2 box) + { + return (currentPosition + step).LengthSquared < currentPosition.LengthSquared || + (currentPosition + step - box).LengthSquared < (currentPosition - box).LengthSquared; + } + + private bool lineDefinitelyIntersectsBox(Vector2 lineStart, Vector2 lineDir, Vector2 box) + { + var p2 = lineStart + lineDir; + + double d1 = det(Vector2.Zero); + double d2 = det(new Vector2(box.X, 0)); + double d3 = det(new Vector2(0, box.Y)); + double d4 = det(box); + + return definitelyDifferentSign(d1, d2) || definitelyDifferentSign(d3, d4) || + definitelyDifferentSign(d1, d3) || definitelyDifferentSign(d2, d4); + + double det(Vector2 p) => (p.X - lineStart.X) * (p2.Y - lineStart.Y) - (p.Y - lineStart.Y) * (p2.X - lineStart.X); + + bool definitelyDifferentSign(double a, double b) => !Precision.AlmostEquals(a, 0) && + !Precision.AlmostEquals(b, 0) && + Math.Sign(a) != Math.Sign(b); + } + + private void generateOutline(Vector2 drawSize) + { + // Make lines the same width independent of display resolution. + float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; + + AddRangeInternal(new[] + { + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Height = lineWidth, + Y = 0, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Height = lineWidth, + Y = drawSize.Y, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Width = lineWidth, + X = 0, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Width = lineWidth, + X = drawSize.X, + }, + }); + } + public Vector2 GetSnappedPosition(Vector2 original) { Vector2 relativeToStart = original - StartPosition; From f2edd705ea537774a662ca6121092b137bb2bb8e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 28 Dec 2023 18:56:49 +0100 Subject: [PATCH 2256/4092] add rotation to snapped position --- .../Components/RectangularPositionSnapGrid.cs | 5 +++-- osu.Game/Utils/GeometryUtils.cs | 18 +++++++++++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs index 160e7e026b..ea9eaf41bb 100644 --- a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Layout; using osu.Framework.Utils; +using osu.Game.Utils; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components @@ -215,11 +216,11 @@ namespace osu.Game.Screens.Edit.Compose.Components public Vector2 GetSnappedPosition(Vector2 original) { - Vector2 relativeToStart = original - StartPosition; + Vector2 relativeToStart = GeometryUtils.RotateVector(original - StartPosition, GridLineRotation); Vector2 offset = Vector2.Divide(relativeToStart, Spacing); Vector2 roundedOffset = new Vector2(MathF.Round(offset.X), MathF.Round(offset.Y)); - return StartPosition + Vector2.Multiply(roundedOffset, Spacing); + return StartPosition + GeometryUtils.RotateVector(Vector2.Multiply(roundedOffset, Spacing), -GridLineRotation); } } } diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index 725e93d098..fcc6b8ae2a 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -27,9 +27,8 @@ namespace osu.Game.Utils point.X -= origin.X; point.Y -= origin.Y; - Vector2 ret; - ret.X = point.X * MathF.Cos(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Sin(MathUtils.DegreesToRadians(angle)); - ret.Y = point.X * -MathF.Sin(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Cos(MathUtils.DegreesToRadians(angle)); + Vector2 ret = RotateVector(point, angle); + Matrix2 ret.X += origin.X; ret.Y += origin.Y; @@ -37,6 +36,19 @@ namespace osu.Game.Utils return ret; } + /// + /// Rotate a vector around the origin. + /// + /// The vector. + /// The angle to rotate (in degrees). + public static Vector2 RotateVector(Vector2 vector, float angle) + { + return new Vector2( + vector.X * MathF.Cos(MathUtils.DegreesToRadians(angle)) + vector.Y * MathF.Sin(MathUtils.DegreesToRadians(angle)), + vector.X * -MathF.Sin(MathUtils.DegreesToRadians(angle)) + vector.Y * MathF.Cos(MathUtils.DegreesToRadians(angle)) + ); + } + /// /// Given a flip direction, a surrounding quad for all selected objects, and a position, /// will return the flipped position in screen space coordinates. From 2193601f3a70150d2e777939b3e75c9a6dd248db Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 28 Dec 2023 20:00:24 +0100 Subject: [PATCH 2257/4092] fix typo --- osu.Game/Utils/GeometryUtils.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index fcc6b8ae2a..e0d217dd48 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -28,7 +28,6 @@ namespace osu.Game.Utils point.Y -= origin.Y; Vector2 ret = RotateVector(point, angle); - Matrix2 ret.X += origin.X; ret.Y += origin.Y; From 92c3b142a4941b14c83c479079adec3d6d6a9be5 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 28 Dec 2023 20:52:11 +0100 Subject: [PATCH 2258/4092] Added Triangular snap grid --- .../TestSceneTriangularPositionSnapGrid.cs | 108 ++++++++ .../Components/TriangularPositionSnapGrid.cs | 258 ++++++++++++++++++ 2 files changed, 366 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs create mode 100644 osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs new file mode 100644 index 0000000000..2f5ffd8423 --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs @@ -0,0 +1,108 @@ +// 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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Screens.Edit.Compose.Components; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Editing +{ + public partial class TestSceneTriangularPositionSnapGrid : OsuManualInputManagerTestScene + { + private Container content; + protected override Container Content => content; + + [BackgroundDependencyLoader] + private void load() + { + base.Content.AddRange(new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.Gray + }, + content = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(10), + } + }); + } + + private static readonly object[][] test_cases = + { + new object[] { new Vector2(0, 0), 10, 0f }, + new object[] { new Vector2(240, 180), 10, 10f }, + new object[] { new Vector2(160, 120), 30, -10f }, + new object[] { new Vector2(480, 360), 100, 0f }, + }; + + [TestCaseSource(nameof(test_cases))] + public void TestTriangularGrid(Vector2 position, float spacing, float rotation) + { + TriangularPositionSnapGrid grid = null; + + AddStep("create grid", () => Child = grid = new TriangularPositionSnapGrid(position) + { + RelativeSizeAxes = Axes.Both, + Spacing = spacing, + GridLineRotation = rotation + }); + + AddStep("add snapping cursor", () => Add(new SnappingCursorContainer + { + RelativeSizeAxes = Axes.Both, + GetSnapPosition = pos => grid.GetSnappedPosition(grid.ToLocalSpace(pos)) + })); + } + + private partial class SnappingCursorContainer : CompositeDrawable + { + public Func GetSnapPosition; + + private readonly Drawable cursor; + + public SnappingCursorContainer() + { + RelativeSizeAxes = Axes.Both; + + InternalChild = cursor = new Circle + { + Origin = Anchor.Centre, + Size = new Vector2(50), + Colour = Color4.Red + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + updatePosition(GetContainingInputManager().CurrentState.Mouse.Position); + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + base.OnMouseMove(e); + + updatePosition(e.ScreenSpaceMousePosition); + return true; + } + + private void updatePosition(Vector2 screenSpacePosition) + { + cursor.Position = GetSnapPosition.Invoke(screenSpacePosition); + } + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs new file mode 100644 index 0000000000..58889bd085 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs @@ -0,0 +1,258 @@ +// 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.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Layout; +using osu.Framework.Utils; +using osu.Game.Utils; +using osuTK; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + public partial class TriangularPositionSnapGrid : CompositeDrawable + { + private Vector2 startPosition; + + /// + /// The position of the origin of this in local coordinates. + /// + public Vector2 StartPosition + { + get => startPosition; + set + { + startPosition = value; + gridCache.Invalidate(); + } + } + + private float spacing = 1; + + /// + /// The spacing between grid lines of this . + /// + public float Spacing + { + get => spacing; + set + { + if (spacing <= 0) + throw new ArgumentException("Grid spacing must be positive."); + + spacing = value; + gridCache.Invalidate(); + } + } + + private float gridLineRotation; + + /// + /// The rotation in degrees of the grid lines of this . + /// + public float GridLineRotation + { + get => gridLineRotation; + set + { + gridLineRotation = value; + gridCache.Invalidate(); + } + } + + private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); + + public TriangularPositionSnapGrid(Vector2 startPosition) + { + StartPosition = startPosition; + Masking = true; + + AddLayout(gridCache); + } + + protected override void Update() + { + base.Update(); + + if (!gridCache.IsValid) + { + ClearInternal(); + + if (DrawWidth > 0 && DrawHeight > 0) + createContent(); + + gridCache.Validate(); + } + } + + private const float sqrt3 = 1.73205080757f; + private const float sqrt3_over2 = 0.86602540378f; + private const float one_over_sqrt3 = 0.57735026919f; + + private void createContent() + { + var drawSize = DrawSize; + float stepSpacing = Spacing * sqrt3_over2; + var step1 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation - 30); + var step2 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation - 90); + var step3 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation - 150); + + generateGridLines(step1, drawSize); + generateGridLines(-step1, drawSize); + + generateGridLines(step2, drawSize); + generateGridLines(-step2, drawSize); + + generateGridLines(step3, drawSize); + generateGridLines(-step3, drawSize); + + generateOutline(drawSize); + } + + private void generateGridLines(Vector2 step, Vector2 drawSize) + { + int index = 0; + var currentPosition = startPosition; + + // Make lines the same width independent of display resolution. + float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; + float lineLength = drawSize.Length * 2; + + List generatedLines = new List(); + + while (lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize) || + isMovingTowardsBox(currentPosition, step, drawSize)) + { + var gridLine = new Box + { + Colour = Colour4.White, + Alpha = 0.1f, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.None, + Width = lineWidth, + Height = lineLength, + Position = currentPosition, + Rotation = MathHelper.RadiansToDegrees(MathF.Atan2(step.Y, step.X)), + }; + + generatedLines.Add(gridLine); + + index += 1; + currentPosition = startPosition + index * step; + } + + if (generatedLines.Count == 0) + return; + + generatedLines.First().Alpha = 0.3f; + + AddRangeInternal(generatedLines); + } + + private bool isMovingTowardsBox(Vector2 currentPosition, Vector2 step, Vector2 box) + { + return (currentPosition + step).LengthSquared < currentPosition.LengthSquared || + (currentPosition + step - box).LengthSquared < (currentPosition - box).LengthSquared; + } + + private bool lineDefinitelyIntersectsBox(Vector2 lineStart, Vector2 lineDir, Vector2 box) + { + var p2 = lineStart + lineDir; + + double d1 = det(Vector2.Zero); + double d2 = det(new Vector2(box.X, 0)); + double d3 = det(new Vector2(0, box.Y)); + double d4 = det(box); + + return definitelyDifferentSign(d1, d2) || definitelyDifferentSign(d3, d4) || + definitelyDifferentSign(d1, d3) || definitelyDifferentSign(d2, d4); + + double det(Vector2 p) => (p.X - lineStart.X) * (p2.Y - lineStart.Y) - (p.Y - lineStart.Y) * (p2.X - lineStart.X); + + bool definitelyDifferentSign(double a, double b) => !Precision.AlmostEquals(a, 0) && + !Precision.AlmostEquals(b, 0) && + Math.Sign(a) != Math.Sign(b); + } + + private void generateOutline(Vector2 drawSize) + { + // Make lines the same width independent of display resolution. + float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; + + AddRangeInternal(new[] + { + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Height = lineWidth, + Y = 0, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Height = lineWidth, + Y = drawSize.Y, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Width = lineWidth, + X = 0, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Width = lineWidth, + X = drawSize.X, + }, + }); + } + + public Vector2 GetSnappedPosition(Vector2 original) + { + Vector2 relativeToStart = GeometryUtils.RotateVector(original - StartPosition, GridLineRotation); + Vector2 hex = pixelToHex(relativeToStart); + + return StartPosition + GeometryUtils.RotateVector(hexToPixel(hex), -GridLineRotation); + } + + private Vector2 pixelToHex(Vector2 pixel) + { + float x = pixel.X / Spacing; + float y = pixel.Y / Spacing; + // Algorithm from Charles Chambers + // with modifications and comments by Chris Cox 2023 + // + float t = sqrt3 * y + 1; // scaled y, plus phase + float temp1 = MathF.Floor(t + x); // (y+x) diagonal, this calc needs floor + float temp2 = t - x; // (y-x) diagonal, no floor needed + float temp3 = 2 * x + 1; // scaled horizontal, no floor needed, needs +1 to get correct phase + float qf = (temp1 + temp3) / 3.0f; // pseudo x with fraction + float rf = (temp1 + temp2) / 3.0f; // pseudo y with fraction + float q = MathF.Floor(qf); // pseudo x, quantized and thus requires floor + float r = MathF.Floor(rf); // pseudo y, quantized and thus requires floor + return new Vector2(q, r); + } + + private Vector2 hexToPixel(Vector2 hex) + { + return new Vector2(Spacing * (hex.X - hex.Y / 2), Spacing * one_over_sqrt3 * 1.5f * hex.Y); + } + } +} From d0c8b285cefc29025407597467357cd2940cb862 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 28 Dec 2023 21:00:47 +0100 Subject: [PATCH 2259/4092] clean up code duplication --- .../Components/LinedPositionSnapGrid.cs | 173 +++++++++++++++++ .../Components/RectangularPositionSnapGrid.cs | 167 +--------------- .../Components/TriangularPositionSnapGrid.cs | 179 ++---------------- 3 files changed, 195 insertions(+), 324 deletions(-) create mode 100644 osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs diff --git a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs new file mode 100644 index 0000000000..642a125265 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs @@ -0,0 +1,173 @@ +// 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.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Layout; +using osu.Framework.Utils; +using osuTK; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + public abstract partial class LinedPositionSnapGrid : CompositeDrawable + { + private Vector2 startPosition; + + /// + /// The position of the origin of this in local coordinates. + /// + public Vector2 StartPosition + { + get => startPosition; + set + { + startPosition = value; + GridCache.Invalidate(); + } + } + + protected readonly LayoutValue GridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); + + protected LinedPositionSnapGrid(Vector2 startPosition) + { + StartPosition = startPosition; + Masking = true; + + AddLayout(GridCache); + } + + protected override void Update() + { + base.Update(); + + if (!GridCache.IsValid) + { + ClearInternal(); + + if (DrawWidth > 0 && DrawHeight > 0) + CreateContent(); + + GridCache.Validate(); + } + } + + protected abstract void CreateContent(); + + protected void GenerateGridLines(Vector2 step, Vector2 drawSize) + { + int index = 0; + var currentPosition = startPosition; + + // Make lines the same width independent of display resolution. + float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; + float lineLength = drawSize.Length * 2; + + List generatedLines = new List(); + + while (lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize) || + isMovingTowardsBox(currentPosition, step, drawSize)) + { + var gridLine = new Box + { + Colour = Colour4.White, + Alpha = 0.1f, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.None, + Width = lineWidth, + Height = lineLength, + Position = currentPosition, + Rotation = MathHelper.RadiansToDegrees(MathF.Atan2(step.Y, step.X)), + }; + + generatedLines.Add(gridLine); + + index += 1; + currentPosition = startPosition + index * step; + } + + if (generatedLines.Count == 0) + return; + + generatedLines.First().Alpha = 0.3f; + + AddRangeInternal(generatedLines); + } + + private bool isMovingTowardsBox(Vector2 currentPosition, Vector2 step, Vector2 box) + { + return (currentPosition + step).LengthSquared < currentPosition.LengthSquared || + (currentPosition + step - box).LengthSquared < (currentPosition - box).LengthSquared; + } + + private bool lineDefinitelyIntersectsBox(Vector2 lineStart, Vector2 lineDir, Vector2 box) + { + var p2 = lineStart + lineDir; + + double d1 = det(Vector2.Zero); + double d2 = det(new Vector2(box.X, 0)); + double d3 = det(new Vector2(0, box.Y)); + double d4 = det(box); + + return definitelyDifferentSign(d1, d2) || definitelyDifferentSign(d3, d4) || + definitelyDifferentSign(d1, d3) || definitelyDifferentSign(d2, d4); + + double det(Vector2 p) => (p.X - lineStart.X) * (p2.Y - lineStart.Y) - (p.Y - lineStart.Y) * (p2.X - lineStart.X); + + bool definitelyDifferentSign(double a, double b) => !Precision.AlmostEquals(a, 0) && + !Precision.AlmostEquals(b, 0) && + Math.Sign(a) != Math.Sign(b); + } + + protected void GenerateOutline(Vector2 drawSize) + { + // Make lines the same width independent of display resolution. + float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; + + AddRangeInternal(new[] + { + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Height = lineWidth, + Y = 0, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Height = lineWidth, + Y = drawSize.Y, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Width = lineWidth, + X = 0, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Width = lineWidth, + X = drawSize.X, + }, + }); + } + + public abstract Vector2 GetSnappedPosition(Vector2 original); + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs index ea9eaf41bb..14a0e3625a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs @@ -2,35 +2,15 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; -using System.Linq; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Layout; -using osu.Framework.Utils; using osu.Game.Utils; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { - public partial class RectangularPositionSnapGrid : CompositeDrawable + public partial class RectangularPositionSnapGrid : LinedPositionSnapGrid { - private Vector2 startPosition; - - /// - /// The position of the origin of this in local coordinates. - /// - public Vector2 StartPosition - { - get => startPosition; - set - { - startPosition = value; - gridCache.Invalidate(); - } - } - private Vector2 spacing = Vector2.One; /// @@ -67,154 +47,25 @@ namespace osu.Game.Screens.Edit.Compose.Components private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); public RectangularPositionSnapGrid(Vector2 startPosition) + : base(startPosition) { - StartPosition = startPosition; - Masking = true; - - AddLayout(gridCache); } - protected override void Update() - { - base.Update(); - - if (!gridCache.IsValid) - { - ClearInternal(); - - if (DrawWidth > 0 && DrawHeight > 0) - createContent(); - - gridCache.Validate(); - } - } - - private void createContent() + protected override void CreateContent() { var drawSize = DrawSize; var rot = Quaternion.FromAxisAngle(Vector3.UnitZ, MathHelper.DegreesToRadians(GridLineRotation)); - generateGridLines(Vector2.Transform(new Vector2(0, -Spacing.Y), rot), GridLineRotation + 90, drawSize); - generateGridLines(Vector2.Transform(new Vector2(0, Spacing.Y), rot), GridLineRotation + 90, drawSize); + GenerateGridLines(Vector2.Transform(new Vector2(0, -Spacing.Y), rot), drawSize); + GenerateGridLines(Vector2.Transform(new Vector2(0, Spacing.Y), rot), drawSize); - generateGridLines(Vector2.Transform(new Vector2(-Spacing.X, 0), rot), GridLineRotation, drawSize); - generateGridLines(Vector2.Transform(new Vector2(Spacing.X, 0), rot), GridLineRotation, drawSize); + GenerateGridLines(Vector2.Transform(new Vector2(-Spacing.X, 0), rot), drawSize); + GenerateGridLines(Vector2.Transform(new Vector2(Spacing.X, 0), rot), drawSize); - generateOutline(drawSize); + GenerateOutline(drawSize); } - private void generateGridLines(Vector2 step, float rotation, Vector2 drawSize) - { - int index = 0; - var currentPosition = startPosition; - - // Make lines the same width independent of display resolution. - float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; - float lineLength = drawSize.Length * 2; - - List generatedLines = new List(); - - while (lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize) || - isMovingTowardsBox(currentPosition, step, drawSize)) - { - var gridLine = new Box - { - Colour = Colour4.White, - Alpha = 0.1f, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.None, - Width = lineWidth, - Height = lineLength, - Position = currentPosition, - Rotation = rotation, - }; - - generatedLines.Add(gridLine); - - index += 1; - currentPosition = startPosition + index * step; - } - - if (generatedLines.Count == 0) - return; - - generatedLines.First().Alpha = 0.3f; - - AddRangeInternal(generatedLines); - } - - private bool isMovingTowardsBox(Vector2 currentPosition, Vector2 step, Vector2 box) - { - return (currentPosition + step).LengthSquared < currentPosition.LengthSquared || - (currentPosition + step - box).LengthSquared < (currentPosition - box).LengthSquared; - } - - private bool lineDefinitelyIntersectsBox(Vector2 lineStart, Vector2 lineDir, Vector2 box) - { - var p2 = lineStart + lineDir; - - double d1 = det(Vector2.Zero); - double d2 = det(new Vector2(box.X, 0)); - double d3 = det(new Vector2(0, box.Y)); - double d4 = det(box); - - return definitelyDifferentSign(d1, d2) || definitelyDifferentSign(d3, d4) || - definitelyDifferentSign(d1, d3) || definitelyDifferentSign(d2, d4); - - double det(Vector2 p) => (p.X - lineStart.X) * (p2.Y - lineStart.Y) - (p.Y - lineStart.Y) * (p2.X - lineStart.X); - - bool definitelyDifferentSign(double a, double b) => !Precision.AlmostEquals(a, 0) && - !Precision.AlmostEquals(b, 0) && - Math.Sign(a) != Math.Sign(b); - } - - private void generateOutline(Vector2 drawSize) - { - // Make lines the same width independent of display resolution. - float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; - - AddRangeInternal(new[] - { - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.X, - Height = lineWidth, - Y = 0, - }, - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.X, - Height = lineWidth, - Y = drawSize.Y, - }, - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Y, - Width = lineWidth, - X = 0, - }, - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Y, - Width = lineWidth, - X = drawSize.X, - }, - }); - } - - public Vector2 GetSnappedPosition(Vector2 original) + public override Vector2 GetSnappedPosition(Vector2 original) { Vector2 relativeToStart = GeometryUtils.RotateVector(original - StartPosition, GridLineRotation); Vector2 offset = Vector2.Divide(relativeToStart, Spacing); diff --git a/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs index 58889bd085..4b6c5dcfe4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs @@ -2,35 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Layout; -using osu.Framework.Utils; using osu.Game.Utils; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { - public partial class TriangularPositionSnapGrid : CompositeDrawable + public partial class TriangularPositionSnapGrid : LinedPositionSnapGrid { - private Vector2 startPosition; - - /// - /// The position of the origin of this in local coordinates. - /// - public Vector2 StartPosition - { - get => startPosition; - set - { - startPosition = value; - gridCache.Invalidate(); - } - } - private float spacing = 1; /// @@ -45,7 +23,7 @@ namespace osu.Game.Screens.Edit.Compose.Components throw new ArgumentException("Grid spacing must be positive."); spacing = value; - gridCache.Invalidate(); + GridCache.Invalidate(); } } @@ -60,40 +38,20 @@ namespace osu.Game.Screens.Edit.Compose.Components set { gridLineRotation = value; - gridCache.Invalidate(); + GridCache.Invalidate(); } } - private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); - public TriangularPositionSnapGrid(Vector2 startPosition) + : base(startPosition) { - StartPosition = startPosition; - Masking = true; - - AddLayout(gridCache); - } - - protected override void Update() - { - base.Update(); - - if (!gridCache.IsValid) - { - ClearInternal(); - - if (DrawWidth > 0 && DrawHeight > 0) - createContent(); - - gridCache.Validate(); - } } private const float sqrt3 = 1.73205080757f; private const float sqrt3_over2 = 0.86602540378f; private const float one_over_sqrt3 = 0.57735026919f; - private void createContent() + protected override void CreateContent() { var drawSize = DrawSize; float stepSpacing = Spacing * sqrt3_over2; @@ -101,130 +59,19 @@ namespace osu.Game.Screens.Edit.Compose.Components var step2 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation - 90); var step3 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation - 150); - generateGridLines(step1, drawSize); - generateGridLines(-step1, drawSize); + GenerateGridLines(step1, drawSize); + GenerateGridLines(-step1, drawSize); - generateGridLines(step2, drawSize); - generateGridLines(-step2, drawSize); + GenerateGridLines(step2, drawSize); + GenerateGridLines(-step2, drawSize); - generateGridLines(step3, drawSize); - generateGridLines(-step3, drawSize); + GenerateGridLines(step3, drawSize); + GenerateGridLines(-step3, drawSize); - generateOutline(drawSize); + GenerateOutline(drawSize); } - private void generateGridLines(Vector2 step, Vector2 drawSize) - { - int index = 0; - var currentPosition = startPosition; - - // Make lines the same width independent of display resolution. - float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; - float lineLength = drawSize.Length * 2; - - List generatedLines = new List(); - - while (lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize) || - isMovingTowardsBox(currentPosition, step, drawSize)) - { - var gridLine = new Box - { - Colour = Colour4.White, - Alpha = 0.1f, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.None, - Width = lineWidth, - Height = lineLength, - Position = currentPosition, - Rotation = MathHelper.RadiansToDegrees(MathF.Atan2(step.Y, step.X)), - }; - - generatedLines.Add(gridLine); - - index += 1; - currentPosition = startPosition + index * step; - } - - if (generatedLines.Count == 0) - return; - - generatedLines.First().Alpha = 0.3f; - - AddRangeInternal(generatedLines); - } - - private bool isMovingTowardsBox(Vector2 currentPosition, Vector2 step, Vector2 box) - { - return (currentPosition + step).LengthSquared < currentPosition.LengthSquared || - (currentPosition + step - box).LengthSquared < (currentPosition - box).LengthSquared; - } - - private bool lineDefinitelyIntersectsBox(Vector2 lineStart, Vector2 lineDir, Vector2 box) - { - var p2 = lineStart + lineDir; - - double d1 = det(Vector2.Zero); - double d2 = det(new Vector2(box.X, 0)); - double d3 = det(new Vector2(0, box.Y)); - double d4 = det(box); - - return definitelyDifferentSign(d1, d2) || definitelyDifferentSign(d3, d4) || - definitelyDifferentSign(d1, d3) || definitelyDifferentSign(d2, d4); - - double det(Vector2 p) => (p.X - lineStart.X) * (p2.Y - lineStart.Y) - (p.Y - lineStart.Y) * (p2.X - lineStart.X); - - bool definitelyDifferentSign(double a, double b) => !Precision.AlmostEquals(a, 0) && - !Precision.AlmostEquals(b, 0) && - Math.Sign(a) != Math.Sign(b); - } - - private void generateOutline(Vector2 drawSize) - { - // Make lines the same width independent of display resolution. - float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; - - AddRangeInternal(new[] - { - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.X, - Height = lineWidth, - Y = 0, - }, - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.X, - Height = lineWidth, - Y = drawSize.Y, - }, - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Y, - Width = lineWidth, - X = 0, - }, - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Y, - Width = lineWidth, - X = drawSize.X, - }, - }); - } - - public Vector2 GetSnappedPosition(Vector2 original) + public override Vector2 GetSnappedPosition(Vector2 original) { Vector2 relativeToStart = GeometryUtils.RotateVector(original - StartPosition, GridLineRotation); Vector2 hex = pixelToHex(relativeToStart); From a20c430d6f1c4e8deeb2ca974d719ae032bd26fd Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 28 Dec 2023 22:35:00 +0100 Subject: [PATCH 2260/4092] fix wrong grid cache being used --- .../Compose/Components/RectangularPositionSnapGrid.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs index 14a0e3625a..930a592850 100644 --- a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs @@ -2,8 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Graphics; -using osu.Framework.Layout; using osu.Game.Utils; using osuTK; @@ -25,7 +23,7 @@ namespace osu.Game.Screens.Edit.Compose.Components throw new ArgumentException("Grid spacing must be positive."); spacing = value; - gridCache.Invalidate(); + GridCache.Invalidate(); } } @@ -40,12 +38,10 @@ namespace osu.Game.Screens.Edit.Compose.Components set { gridLineRotation = value; - gridCache.Invalidate(); + GridCache.Invalidate(); } } - private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); - public RectangularPositionSnapGrid(Vector2 startPosition) : base(startPosition) { From b16c232490a5353dbf3fd4e59390f8147b4e816b Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 28 Dec 2023 22:36:30 +0100 Subject: [PATCH 2261/4092] add basic control by grid tool box --- .../Edit/OsuHitObjectComposer.cs | 11 ++ osu.Game/Rulesets/Edit/GridToolboxGroup.cs | 108 ++++++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 osu.Game/Rulesets/Edit/GridToolboxGroup.cs diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 448cfaf84c..e487a5d490 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -65,6 +65,9 @@ namespace osu.Game.Rulesets.Osu.Edit [Cached(typeof(IDistanceSnapProvider))] protected readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider(); + [Cached] + protected readonly GridToolboxGroup GridToolboxGroup = new GridToolboxGroup(); + [Cached] protected readonly FreehandSliderToolboxGroup FreehandlSliderToolboxGroup = new FreehandSliderToolboxGroup(); @@ -99,8 +102,16 @@ namespace osu.Game.Rulesets.Osu.Edit // we may be entering the screen with a selection already active updateDistanceSnapGrid(); + GridToolboxGroup.StartPositionX.ValueChanged += x => + rectangularPositionSnapGrid.StartPosition = new Vector2(x.NewValue, rectangularPositionSnapGrid.StartPosition.Y); + GridToolboxGroup.StartPositionY.ValueChanged += y => + rectangularPositionSnapGrid.StartPosition = new Vector2(rectangularPositionSnapGrid.StartPosition.X, y.NewValue); + GridToolboxGroup.Spacing.ValueChanged += s => rectangularPositionSnapGrid.Spacing = new Vector2(s.NewValue); + GridToolboxGroup.GridLinesRotation.ValueChanged += r => rectangularPositionSnapGrid.GridLineRotation = r.NewValue; + RightToolbox.AddRange(new EditorToolboxGroup[] { + GridToolboxGroup, new TransformToolboxGroup { RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, }, FreehandlSliderToolboxGroup } diff --git a/osu.Game/Rulesets/Edit/GridToolboxGroup.cs b/osu.Game/Rulesets/Edit/GridToolboxGroup.cs new file mode 100644 index 0000000000..b6903c1369 --- /dev/null +++ b/osu.Game/Rulesets/Edit/GridToolboxGroup.cs @@ -0,0 +1,108 @@ +// 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.Bindables; +using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Edit; + +namespace osu.Game.Rulesets.Edit +{ + public partial class GridToolboxGroup : EditorToolboxGroup + { + [Resolved] + private EditorBeatmap editorBeatmap { get; set; } = null!; + + public GridToolboxGroup() + : base("grid") + { + } + + public BindableFloat StartPositionX { get; } = new BindableFloat(256f) + { + MinValue = 0f, + MaxValue = 512f, + Precision = 1f + }; + + public BindableFloat StartPositionY { get; } = new BindableFloat(192) + { + MinValue = 0f, + MaxValue = 384f, + Precision = 1f + }; + + public BindableFloat Spacing { get; } = new BindableFloat(4f) + { + MinValue = 4f, + MaxValue = 128f, + Precision = 1f + }; + + public BindableFloat GridLinesRotation { get; } = new BindableFloat(0f) + { + MinValue = -180f, + MaxValue = 180f, + Precision = 1f + }; + + private ExpandableSlider startPositionXSlider = null!; + private ExpandableSlider startPositionYSlider = null!; + private ExpandableSlider spacingSlider = null!; + private ExpandableSlider gridLinesRotationSlider = null!; + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + startPositionXSlider = new ExpandableSlider + { + Current = StartPositionX + }, + startPositionYSlider = new ExpandableSlider + { + Current = StartPositionY + }, + spacingSlider = new ExpandableSlider + { + Current = Spacing + }, + gridLinesRotationSlider = new ExpandableSlider + { + Current = GridLinesRotation + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + StartPositionX.BindValueChanged(x => + { + startPositionXSlider.ContractedLabelText = $"X: {x.NewValue:N0}"; + startPositionXSlider.ExpandedLabelText = $"X Offset: {x.NewValue:N0}"; + }, true); + + StartPositionY.BindValueChanged(y => + { + startPositionYSlider.ContractedLabelText = $"Y: {y.NewValue:N0}"; + startPositionYSlider.ExpandedLabelText = $"Y Offset: {y.NewValue:N0}"; + }, true); + + Spacing.BindValueChanged(spacing => + { + spacingSlider.ContractedLabelText = $"S: {spacing.NewValue:N0}"; + spacingSlider.ExpandedLabelText = $"Spacing: {spacing.NewValue:N0}"; + }, true); + + GridLinesRotation.BindValueChanged(rotation => + { + gridLinesRotationSlider.ContractedLabelText = $"R: {rotation.NewValue:N0}"; + gridLinesRotationSlider.ExpandedLabelText = $"Rotation: {rotation.NewValue:N0}"; + }, true); + } + } +} From 0ce1a48e68ea26d9295a611e6648ffafc8a8651f Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 28 Dec 2023 22:54:30 +0100 Subject: [PATCH 2262/4092] Add comment --- .../Edit/Compose/Components/TriangularPositionSnapGrid.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs index 4b6c5dcfe4..af44641f5e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs @@ -99,6 +99,8 @@ namespace osu.Game.Screens.Edit.Compose.Components private Vector2 hexToPixel(Vector2 hex) { + // Taken from + // with modifications for the different definition of size. return new Vector2(Spacing * (hex.X - hex.Y / 2), Spacing * one_over_sqrt3 * 1.5f * hex.Y); } } From f223487e1cd9cd2a391f79575a13222cfa19987e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 28 Dec 2023 23:10:06 +0100 Subject: [PATCH 2263/4092] improve code --- .../Editor/TestSceneOsuEditorGrids.cs | 7 +- .../Edit/OsuGridToolboxGroup.cs | 71 +++++++++++++++---- .../Edit/OsuHitObjectComposer.cs | 18 ++--- .../Edit/OsuRectangularPositionSnapGrid.cs | 69 ------------------ .../TestSceneRectangularPositionSnapGrid.cs | 3 +- .../Screens/Editors/LadderEditorScreen.cs | 2 +- .../Components/LinedPositionSnapGrid.cs | 3 +- .../Components/RectangularPositionSnapGrid.cs | 5 -- .../Components/TriangularPositionSnapGrid.cs | 5 -- 9 files changed, 75 insertions(+), 108 deletions(-) rename osu.Game/Rulesets/Edit/GridToolboxGroup.cs => osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs (65%) delete mode 100644 osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index d14e593587..299db23ccc 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -7,6 +7,7 @@ using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; +using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Tests.Visual; using osuTK; using osuTK.Input; @@ -69,7 +70,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("choose placement tool", () => InputManager.Key(Key.Number2)); AddStep("move cursor to (1, 1)", () => { - var composer = Editor.ChildrenOfType().Single(); + var composer = Editor.ChildrenOfType().Single(); InputManager.MoveMouseTo(composer.ToScreenSpace(new Vector2(1, 1))); }); @@ -83,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor public void TestGridSizeToggling() { AddStep("enable rectangular grid", () => InputManager.Key(Key.Y)); - AddUntilStep("rectangular grid visible", () => this.ChildrenOfType().Any()); + AddUntilStep("rectangular grid visible", () => this.ChildrenOfType().Any()); gridSizeIs(4); nextGridSizeIs(8); @@ -99,7 +100,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor } private void gridSizeIs(int size) - => AddAssert($"grid size is {size}", () => this.ChildrenOfType().Single().Spacing == new Vector2(size) + => AddAssert($"grid size is {size}", () => this.ChildrenOfType().Single().Spacing == new Vector2(size) && EditorBeatmap.BeatmapInfo.GridSize == size); } } diff --git a/osu.Game/Rulesets/Edit/GridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs similarity index 65% rename from osu.Game/Rulesets/Edit/GridToolboxGroup.cs rename to osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index b6903c1369..d93b6c27c0 100644 --- a/osu.Game/Rulesets/Edit/GridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -1,35 +1,40 @@ // 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.Bindables; using osu.Framework.Graphics; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; +using osu.Game.Input.Bindings; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit; -namespace osu.Game.Rulesets.Edit +namespace osu.Game.Rulesets.Osu.Edit { - public partial class GridToolboxGroup : EditorToolboxGroup + public partial class OsuGridToolboxGroup : EditorToolboxGroup, IKeyBindingHandler { + private static readonly int[] grid_sizes = { 4, 8, 16, 32 }; + + private int currentGridSizeIndex = grid_sizes.Length - 1; + [Resolved] private EditorBeatmap editorBeatmap { get; set; } = null!; - public GridToolboxGroup() - : base("grid") - { - } - - public BindableFloat StartPositionX { get; } = new BindableFloat(256f) + public BindableFloat StartPositionX { get; } = new BindableFloat(OsuPlayfield.BASE_SIZE.X / 2) { MinValue = 0f, - MaxValue = 512f, + MaxValue = OsuPlayfield.BASE_SIZE.X, Precision = 1f }; - public BindableFloat StartPositionY { get; } = new BindableFloat(192) + public BindableFloat StartPositionY { get; } = new BindableFloat(OsuPlayfield.BASE_SIZE.Y / 2) { MinValue = 0f, - MaxValue = 384f, + MaxValue = OsuPlayfield.BASE_SIZE.Y, Precision = 1f }; @@ -42,8 +47,8 @@ namespace osu.Game.Rulesets.Edit public BindableFloat GridLinesRotation { get; } = new BindableFloat(0f) { - MinValue = -180f, - MaxValue = 180f, + MinValue = -45f, + MaxValue = 45f, Precision = 1f }; @@ -52,6 +57,11 @@ namespace osu.Game.Rulesets.Edit private ExpandableSlider spacingSlider = null!; private ExpandableSlider gridLinesRotationSlider = null!; + public OsuGridToolboxGroup() + : base("grid") + { + } + [BackgroundDependencyLoader] private void load() { @@ -74,6 +84,11 @@ namespace osu.Game.Rulesets.Edit Current = GridLinesRotation } }; + + int gridSizeIndex = Array.IndexOf(grid_sizes, editorBeatmap.BeatmapInfo.GridSize); + if (gridSizeIndex >= 0) + currentGridSizeIndex = gridSizeIndex; + updateSpacing(); } protected override void LoadComplete() @@ -104,5 +119,35 @@ namespace osu.Game.Rulesets.Edit gridLinesRotationSlider.ExpandedLabelText = $"Rotation: {rotation.NewValue:N0}"; }, true); } + + private void nextGridSize() + { + currentGridSizeIndex = (currentGridSizeIndex + 1) % grid_sizes.Length; + updateSpacing(); + } + + private void updateSpacing() + { + int gridSize = grid_sizes[currentGridSizeIndex]; + + editorBeatmap.BeatmapInfo.GridSize = gridSize; + Spacing.Value = gridSize; + } + + public bool OnPressed(KeyBindingPressEvent e) + { + switch (e.Action) + { + case GlobalAction.EditorCycleGridDisplayMode: + nextGridSize(); + return true; + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index e487a5d490..12457fc88d 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Edit protected readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider(); [Cached] - protected readonly GridToolboxGroup GridToolboxGroup = new GridToolboxGroup(); + protected readonly OsuGridToolboxGroup OsuGridToolboxGroup = new OsuGridToolboxGroup(); [Cached] protected readonly FreehandSliderToolboxGroup FreehandlSliderToolboxGroup = new FreehandSliderToolboxGroup(); @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.Edit { RelativeSizeAxes = Axes.Both }, - rectangularPositionSnapGrid = new OsuRectangularPositionSnapGrid + rectangularPositionSnapGrid = new RectangularPositionSnapGrid { RelativeSizeAxes = Axes.Both } @@ -102,16 +102,16 @@ namespace osu.Game.Rulesets.Osu.Edit // we may be entering the screen with a selection already active updateDistanceSnapGrid(); - GridToolboxGroup.StartPositionX.ValueChanged += x => - rectangularPositionSnapGrid.StartPosition = new Vector2(x.NewValue, rectangularPositionSnapGrid.StartPosition.Y); - GridToolboxGroup.StartPositionY.ValueChanged += y => - rectangularPositionSnapGrid.StartPosition = new Vector2(rectangularPositionSnapGrid.StartPosition.X, y.NewValue); - GridToolboxGroup.Spacing.ValueChanged += s => rectangularPositionSnapGrid.Spacing = new Vector2(s.NewValue); - GridToolboxGroup.GridLinesRotation.ValueChanged += r => rectangularPositionSnapGrid.GridLineRotation = r.NewValue; + OsuGridToolboxGroup.StartPositionX.BindValueChanged(x => + rectangularPositionSnapGrid.StartPosition = new Vector2(x.NewValue, rectangularPositionSnapGrid.StartPosition.Y), true); + OsuGridToolboxGroup.StartPositionY.BindValueChanged(y => + rectangularPositionSnapGrid.StartPosition = new Vector2(rectangularPositionSnapGrid.StartPosition.X, y.NewValue), true); + OsuGridToolboxGroup.Spacing.BindValueChanged(s => rectangularPositionSnapGrid.Spacing = new Vector2(s.NewValue), true); + OsuGridToolboxGroup.GridLinesRotation.BindValueChanged(r => rectangularPositionSnapGrid.GridLineRotation = r.NewValue, true); RightToolbox.AddRange(new EditorToolboxGroup[] { - GridToolboxGroup, + OsuGridToolboxGroup, new TransformToolboxGroup { RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, }, FreehandlSliderToolboxGroup } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs b/osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs deleted file mode 100644 index efc6668ebf..0000000000 --- a/osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs +++ /dev/null @@ -1,69 +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 System; -using osu.Framework.Allocation; -using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; -using osu.Game.Input.Bindings; -using osu.Game.Rulesets.Osu.UI; -using osu.Game.Screens.Edit; -using osu.Game.Screens.Edit.Compose.Components; -using osuTK; - -namespace osu.Game.Rulesets.Osu.Edit -{ - public partial class OsuRectangularPositionSnapGrid : RectangularPositionSnapGrid, IKeyBindingHandler - { - private static readonly int[] grid_sizes = { 4, 8, 16, 32 }; - - private int currentGridSizeIndex = grid_sizes.Length - 1; - - [Resolved] - private EditorBeatmap editorBeatmap { get; set; } = null!; - - public OsuRectangularPositionSnapGrid() - : base(OsuPlayfield.BASE_SIZE / 2) - { - } - - [BackgroundDependencyLoader] - private void load() - { - int gridSizeIndex = Array.IndexOf(grid_sizes, editorBeatmap.BeatmapInfo.GridSize); - if (gridSizeIndex >= 0) - currentGridSizeIndex = gridSizeIndex; - updateSpacing(); - } - - private void nextGridSize() - { - currentGridSizeIndex = (currentGridSizeIndex + 1) % grid_sizes.Length; - updateSpacing(); - } - - private void updateSpacing() - { - int gridSize = grid_sizes[currentGridSizeIndex]; - - editorBeatmap.BeatmapInfo.GridSize = gridSize; - Spacing = new Vector2(gridSize); - } - - public bool OnPressed(KeyBindingPressEvent e) - { - switch (e.Action) - { - case GlobalAction.EditorCycleGridDisplayMode: - nextGridSize(); - return true; - } - - return false; - } - - public void OnReleased(KeyBindingReleaseEvent e) - { - } - } -} diff --git a/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs index 210af09055..a0042cf605 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs @@ -52,9 +52,10 @@ namespace osu.Game.Tests.Visual.Editing { RectangularPositionSnapGrid grid = null; - AddStep("create grid", () => Child = grid = new RectangularPositionSnapGrid(position) + AddStep("create grid", () => Child = grid = new RectangularPositionSnapGrid() { RelativeSizeAxes = Axes.Both, + StartPosition = position, Spacing = spacing, GridLineRotation = rotation }); diff --git a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs index 4074e681f9..ad00e8d47d 100644 --- a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs @@ -51,7 +51,7 @@ namespace osu.Game.Tournament.Screens.Editors AddInternal(rightClickMessage = new WarningBox("Right click to place and link matches")); - ScrollContent.Add(grid = new RectangularPositionSnapGrid(Vector2.Zero) + ScrollContent.Add(grid = new RectangularPositionSnapGrid { Spacing = new Vector2(GRID_SPACING), Anchor = Anchor.Centre, diff --git a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs index 642a125265..3616bc1ca1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs @@ -32,9 +32,8 @@ namespace osu.Game.Screens.Edit.Compose.Components protected readonly LayoutValue GridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); - protected LinedPositionSnapGrid(Vector2 startPosition) + protected LinedPositionSnapGrid() { - StartPosition = startPosition; Masking = true; AddLayout(GridCache); diff --git a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs index 930a592850..2392921203 100644 --- a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs @@ -42,11 +42,6 @@ namespace osu.Game.Screens.Edit.Compose.Components } } - public RectangularPositionSnapGrid(Vector2 startPosition) - : base(startPosition) - { - } - protected override void CreateContent() { var drawSize = DrawSize; diff --git a/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs index af44641f5e..c98890e294 100644 --- a/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs @@ -42,11 +42,6 @@ namespace osu.Game.Screens.Edit.Compose.Components } } - public TriangularPositionSnapGrid(Vector2 startPosition) - : base(startPosition) - { - } - private const float sqrt3 = 1.73205080757f; private const float sqrt3_over2 = 0.86602540378f; private const float one_over_sqrt3 = 0.57735026919f; From 351cfbff3e02f96b79eff3a020f0a519e1348052 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 28 Dec 2023 23:38:10 +0100 Subject: [PATCH 2264/4092] Fix snapping going out of bounds --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 12457fc88d..3e5cede1d3 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -24,6 +24,7 @@ using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Components.TernaryButtons; using osu.Game.Screens.Edit.Compose.Components; @@ -218,6 +219,10 @@ namespace osu.Game.Rulesets.Osu.Edit { Vector2 pos = rectangularPositionSnapGrid.GetSnappedPosition(rectangularPositionSnapGrid.ToLocalSpace(result.ScreenSpacePosition)); + // A rotated grid can produce a position that is outside of the playfield. + // We need to clamp the position to the playfield bounds to ensure that the snapped position is always in bounds. + pos = Vector2.Clamp(pos, Vector2.Zero, OsuPlayfield.BASE_SIZE); + result.ScreenSpacePosition = rectangularPositionSnapGrid.ToScreenSpace(pos); } } From 8ef9bdf861d40eafb12eedbfeb8c093e00c987a2 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 28 Dec 2023 23:39:24 +0100 Subject: [PATCH 2265/4092] clarify comment --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 3e5cede1d3..7b872eef38 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -219,7 +219,7 @@ namespace osu.Game.Rulesets.Osu.Edit { Vector2 pos = rectangularPositionSnapGrid.GetSnappedPosition(rectangularPositionSnapGrid.ToLocalSpace(result.ScreenSpacePosition)); - // A rotated grid can produce a position that is outside of the playfield. + // A grid which doesn't perfectly fit the playfield can produce a position that is outside of the playfield. // We need to clamp the position to the playfield bounds to ensure that the snapped position is always in bounds. pos = Vector2.Clamp(pos, Vector2.Zero, OsuPlayfield.BASE_SIZE); From 040fd5ef9c65ca22aeb5dd9d903d0ed1cf6f0951 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 29 Dec 2023 16:59:12 +0100 Subject: [PATCH 2266/4092] Add option to change grid type --- .../Edit/OsuGridToolboxGroup.cs | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index d93b6c27c0..892c89ebc2 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -2,9 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; @@ -12,6 +15,7 @@ using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Components.RadioButtons; namespace osu.Game.Rulesets.Osu.Edit { @@ -52,10 +56,13 @@ namespace osu.Game.Rulesets.Osu.Edit Precision = 1f }; + public Bindable GridType { get; } = new Bindable(); + private ExpandableSlider startPositionXSlider = null!; private ExpandableSlider startPositionYSlider = null!; private ExpandableSlider spacingSlider = null!; private ExpandableSlider gridLinesRotationSlider = null!; + private EditorRadioButtonCollection gridTypeButtons = null!; public OsuGridToolboxGroup() : base("grid") @@ -82,7 +89,20 @@ namespace osu.Game.Rulesets.Osu.Edit gridLinesRotationSlider = new ExpandableSlider { Current = GridLinesRotation - } + }, + gridTypeButtons = new EditorRadioButtonCollection + { + RelativeSizeAxes = Axes.X, + Items = new[] + { + new RadioButton("Square", + () => GridType.Value = PositionSnapGridType.Square, + () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), + new RadioButton("Triangle", + () => GridType.Value = PositionSnapGridType.Triangle, + () => new Triangle()) + } + }, }; int gridSizeIndex = Array.IndexOf(grid_sizes, editorBeatmap.BeatmapInfo.GridSize); @@ -95,6 +115,8 @@ namespace osu.Game.Rulesets.Osu.Edit { base.LoadComplete(); + gridTypeButtons.Items.First().Select(); + StartPositionX.BindValueChanged(x => { startPositionXSlider.ContractedLabelText = $"X: {x.NewValue:N0}"; @@ -150,4 +172,10 @@ namespace osu.Game.Rulesets.Osu.Edit { } } + + public enum PositionSnapGridType + { + Square, + Triangle, + } } From 8a331057b0bb76578608c7dd4afc79e98fab82b2 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 29 Dec 2023 17:25:17 +0100 Subject: [PATCH 2267/4092] Make it actually possible to change grid type --- .../Edit/OsuHitObjectComposer.cs | 60 ++++++++--- .../Components/LinedPositionSnapGrid.cs | 97 +---------------- .../Compose/Components/PositionSnapGrid.cs | 102 ++++++++++++++++++ 3 files changed, 152 insertions(+), 107 deletions(-) create mode 100644 osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 7b872eef38..b0ac8467c1 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -84,10 +84,6 @@ namespace osu.Game.Rulesets.Osu.Edit LayerBelowRuleset.AddRange(new Drawable[] { distanceSnapGridContainer = new Container - { - RelativeSizeAxes = Axes.Both - }, - rectangularPositionSnapGrid = new RectangularPositionSnapGrid { RelativeSizeAxes = Axes.Both } @@ -103,12 +99,7 @@ namespace osu.Game.Rulesets.Osu.Edit // we may be entering the screen with a selection already active updateDistanceSnapGrid(); - OsuGridToolboxGroup.StartPositionX.BindValueChanged(x => - rectangularPositionSnapGrid.StartPosition = new Vector2(x.NewValue, rectangularPositionSnapGrid.StartPosition.Y), true); - OsuGridToolboxGroup.StartPositionY.BindValueChanged(y => - rectangularPositionSnapGrid.StartPosition = new Vector2(rectangularPositionSnapGrid.StartPosition.X, y.NewValue), true); - OsuGridToolboxGroup.Spacing.BindValueChanged(s => rectangularPositionSnapGrid.Spacing = new Vector2(s.NewValue), true); - OsuGridToolboxGroup.GridLinesRotation.BindValueChanged(r => rectangularPositionSnapGrid.GridLineRotation = r.NewValue, true); + OsuGridToolboxGroup.GridType.BindValueChanged(updatePositionSnapGrid, true); RightToolbox.AddRange(new EditorToolboxGroup[] { @@ -119,6 +110,49 @@ namespace osu.Game.Rulesets.Osu.Edit ); } + private void updatePositionSnapGrid(ValueChangedEvent obj) + { + if (positionSnapGrid != null) + LayerBelowRuleset.Remove(positionSnapGrid, true); + + switch (obj.NewValue) + { + case PositionSnapGridType.Square: + var rectangularPositionSnapGrid = new RectangularPositionSnapGrid(); + + OsuGridToolboxGroup.Spacing.BindValueChanged(s => rectangularPositionSnapGrid.Spacing = new Vector2(s.NewValue), true); + OsuGridToolboxGroup.GridLinesRotation.BindValueChanged(r => rectangularPositionSnapGrid.GridLineRotation = r.NewValue, true); + + positionSnapGrid = rectangularPositionSnapGrid; + break; + + case PositionSnapGridType.Triangle: + var triangularPositionSnapGrid = new TriangularPositionSnapGrid(); + + OsuGridToolboxGroup.Spacing.BindValueChanged(s => triangularPositionSnapGrid.Spacing = s.NewValue, true); + OsuGridToolboxGroup.GridLinesRotation.BindValueChanged(r => triangularPositionSnapGrid.GridLineRotation = r.NewValue, true); + + positionSnapGrid = triangularPositionSnapGrid; + break; + + default: + throw new NotImplementedException($"{OsuGridToolboxGroup.GridType} has an incorrect value."); + } + + bindPositionSnapGridStartPosition(positionSnapGrid); + positionSnapGrid.RelativeSizeAxes = Axes.Both; + LayerBelowRuleset.Add(positionSnapGrid); + return; + + void bindPositionSnapGridStartPosition(PositionSnapGrid snapGrid) + { + OsuGridToolboxGroup.StartPositionX.BindValueChanged(x => + snapGrid.StartPosition = new Vector2(x.NewValue, snapGrid.StartPosition.Y), true); + OsuGridToolboxGroup.StartPositionY.BindValueChanged(y => + snapGrid.StartPosition = new Vector2(snapGrid.StartPosition.X, y.NewValue), true); + } + } + protected override ComposeBlueprintContainer CreateBlueprintContainer() => new OsuBlueprintContainer(this); @@ -159,7 +193,7 @@ namespace osu.Game.Rulesets.Osu.Edit private readonly Cached distanceSnapGridCache = new Cached(); private double? lastDistanceSnapGridTime; - private RectangularPositionSnapGrid rectangularPositionSnapGrid; + private PositionSnapGrid positionSnapGrid; protected override void Update() { @@ -217,13 +251,13 @@ namespace osu.Game.Rulesets.Osu.Edit { if (rectangularGridSnapToggle.Value == TernaryState.True) { - Vector2 pos = rectangularPositionSnapGrid.GetSnappedPosition(rectangularPositionSnapGrid.ToLocalSpace(result.ScreenSpacePosition)); + Vector2 pos = positionSnapGrid.GetSnappedPosition(positionSnapGrid.ToLocalSpace(result.ScreenSpacePosition)); // A grid which doesn't perfectly fit the playfield can produce a position that is outside of the playfield. // We need to clamp the position to the playfield bounds to ensure that the snapped position is always in bounds. pos = Vector2.Clamp(pos, Vector2.Zero, OsuPlayfield.BASE_SIZE); - result.ScreenSpacePosition = rectangularPositionSnapGrid.ToScreenSpace(pos); + result.ScreenSpacePosition = positionSnapGrid.ToScreenSpace(pos); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs index 3616bc1ca1..9ab12e4b71 100644 --- a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs @@ -5,61 +5,18 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Layout; using osu.Framework.Utils; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { - public abstract partial class LinedPositionSnapGrid : CompositeDrawable + public abstract partial class LinedPositionSnapGrid : PositionSnapGrid { - private Vector2 startPosition; - - /// - /// The position of the origin of this in local coordinates. - /// - public Vector2 StartPosition - { - get => startPosition; - set - { - startPosition = value; - GridCache.Invalidate(); - } - } - - protected readonly LayoutValue GridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); - - protected LinedPositionSnapGrid() - { - Masking = true; - - AddLayout(GridCache); - } - - protected override void Update() - { - base.Update(); - - if (!GridCache.IsValid) - { - ClearInternal(); - - if (DrawWidth > 0 && DrawHeight > 0) - CreateContent(); - - GridCache.Validate(); - } - } - - protected abstract void CreateContent(); - protected void GenerateGridLines(Vector2 step, Vector2 drawSize) { int index = 0; - var currentPosition = startPosition; + var currentPosition = StartPosition; // Make lines the same width independent of display resolution. float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; @@ -85,7 +42,7 @@ namespace osu.Game.Screens.Edit.Compose.Components generatedLines.Add(gridLine); index += 1; - currentPosition = startPosition + index * step; + currentPosition = StartPosition + index * step; } if (generatedLines.Count == 0) @@ -120,53 +77,5 @@ namespace osu.Game.Screens.Edit.Compose.Components !Precision.AlmostEquals(b, 0) && Math.Sign(a) != Math.Sign(b); } - - protected void GenerateOutline(Vector2 drawSize) - { - // Make lines the same width independent of display resolution. - float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; - - AddRangeInternal(new[] - { - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.X, - Height = lineWidth, - Y = 0, - }, - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.X, - Height = lineWidth, - Y = drawSize.Y, - }, - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Y, - Width = lineWidth, - X = 0, - }, - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Y, - Width = lineWidth, - X = drawSize.X, - }, - }); - } - - public abstract Vector2 GetSnappedPosition(Vector2 original); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs new file mode 100644 index 0000000000..dd412e3cd3 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs @@ -0,0 +1,102 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Layout; +using osuTK; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + public abstract partial class PositionSnapGrid : CompositeDrawable + { + private Vector2 startPosition; + + /// + /// The position of the origin of this in local coordinates. + /// + public Vector2 StartPosition + { + get => startPosition; + set + { + startPosition = value; + GridCache.Invalidate(); + } + } + + protected readonly LayoutValue GridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); + + protected PositionSnapGrid() + { + Masking = true; + + AddLayout(GridCache); + } + + protected override void Update() + { + base.Update(); + + if (GridCache.IsValid) return; + + ClearInternal(); + + if (DrawWidth > 0 && DrawHeight > 0) + CreateContent(); + + GridCache.Validate(); + } + + protected abstract void CreateContent(); + + protected void GenerateOutline(Vector2 drawSize) + { + // Make lines the same width independent of display resolution. + float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; + + AddRangeInternal(new[] + { + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Height = lineWidth, + Y = 0, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Height = lineWidth, + Y = drawSize.Y, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Width = lineWidth, + X = 0, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Width = lineWidth, + X = drawSize.X, + }, + }); + } + + public abstract Vector2 GetSnappedPosition(Vector2 original); + } +} From 847f04e63a0fb9b58bcea0ca35ea9d61880a5528 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 29 Dec 2023 17:32:09 +0100 Subject: [PATCH 2268/4092] reduce opacity of middle cardinal lines --- .../Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs index 9ab12e4b71..94da9c5b84 100644 --- a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (generatedLines.Count == 0) return; - generatedLines.First().Alpha = 0.3f; + generatedLines.First().Alpha = 0.2f; AddRangeInternal(generatedLines); } From d0ca3f2b2b086ddbfae4ed5f9d6e44b471804722 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 29 Dec 2023 18:24:05 +0100 Subject: [PATCH 2269/4092] Add circular grid --- .../Edit/OsuGridToolboxGroup.cs | 6 +- .../Edit/OsuHitObjectComposer.cs | 8 ++ .../Components/CircularPositionSnapGrid.cs | 106 ++++++++++++++++++ 3 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 892c89ebc2..0013f23057 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -100,7 +100,10 @@ namespace osu.Game.Rulesets.Osu.Edit () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), new RadioButton("Triangle", () => GridType.Value = PositionSnapGridType.Triangle, - () => new Triangle()) + () => new Triangle()), + new RadioButton("Circle", + () => GridType.Value = PositionSnapGridType.Circle, + () => new SpriteIcon { Icon = FontAwesome.Regular.Circle }), } }, }; @@ -177,5 +180,6 @@ namespace osu.Game.Rulesets.Osu.Edit { Square, Triangle, + Circle, } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index b0ac8467c1..1e7d07dbdd 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -135,6 +135,14 @@ namespace osu.Game.Rulesets.Osu.Edit positionSnapGrid = triangularPositionSnapGrid; break; + case PositionSnapGridType.Circle: + var circularPositionSnapGrid = new CircularPositionSnapGrid(); + + OsuGridToolboxGroup.Spacing.BindValueChanged(s => circularPositionSnapGrid.Spacing = s.NewValue, true); + + positionSnapGrid = circularPositionSnapGrid; + break; + default: throw new NotImplementedException($"{OsuGridToolboxGroup.GridType} has an incorrect value."); } diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs new file mode 100644 index 0000000000..b9b0cbe389 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.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.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Utils; +using osu.Game.Utils; +using osuTK; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + public partial class CircularPositionSnapGrid : PositionSnapGrid + { + private float spacing = 1; + + /// + /// The spacing between grid lines of this . + /// + public float Spacing + { + get => spacing; + set + { + if (spacing <= 0) + throw new ArgumentException("Grid spacing must be positive."); + + spacing = value; + GridCache.Invalidate(); + } + } + + protected override void CreateContent() + { + var drawSize = DrawSize; + + // Calculate the maximum distance from the origin to the edge of the grid. + float maxDist = MathF.Max( + MathF.Max(StartPosition.Length, (StartPosition - drawSize).Length), + MathF.Max((StartPosition - new Vector2(drawSize.X, 0)).Length, (StartPosition - new Vector2(0, drawSize.Y)).Length) + ); + + generateCircles((int)(maxDist / Spacing) + 1); + + GenerateOutline(drawSize); + } + + private void generateCircles(int count) + { + // Make lines the same width independent of display resolution. + float lineWidth = 2 * DrawWidth / ScreenSpaceDrawQuad.Width; + + List generatedCircles = new List(); + + for (int i = 0; i < count; i++) + { + // Add a minimum diameter so the center circle is clearly visible. + float diameter = MathF.Max(lineWidth * 1.5f, i * Spacing * 2); + + var gridCircle = new CircularContainer + { + BorderColour = Colour4.White, + BorderThickness = lineWidth, + Alpha = 0.2f, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.None, + Width = diameter, + Height = diameter, + Position = StartPosition, + Masking = true, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + AlwaysPresent = true, + Alpha = 0f, + } + }; + + generatedCircles.Add(gridCircle); + } + + if (generatedCircles.Count == 0) + return; + + generatedCircles.First().Alpha = 0.8f; + + AddRangeInternal(generatedCircles); + } + + public override Vector2 GetSnappedPosition(Vector2 original) + { + Vector2 relativeToStart = original - StartPosition; + + if (relativeToStart.LengthSquared < Precision.FLOAT_EPSILON) + return StartPosition; + + float length = relativeToStart.Length; + float wantedLength = MathF.Round(length / Spacing) * Spacing; + + return StartPosition + Vector2.Multiply(relativeToStart, wantedLength / length); + } + } +} From f649fa106f5de94592ff6d2ba55684625451bde3 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 30 Dec 2023 00:43:41 +0100 Subject: [PATCH 2270/4092] Added bindables and binding with BindTo --- .../Editor/TestSceneOsuEditorGrids.cs | 2 +- .../Edit/OsuGridToolboxGroup.cs | 28 +++++++++++ .../Edit/OsuHitObjectComposer.cs | 23 +++------ .../Components/CircularPositionSnapGrid.cs | 37 ++++++-------- .../Components/LinedPositionSnapGrid.cs | 4 +- .../Compose/Components/PositionSnapGrid.cs | 15 ++---- .../Components/RectangularPositionSnapGrid.cs | 46 ++++++----------- .../Components/TriangularPositionSnapGrid.cs | 49 +++++++------------ 8 files changed, 92 insertions(+), 112 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index 299db23ccc..ff406b1b88 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor } private void gridSizeIs(int size) - => AddAssert($"grid size is {size}", () => this.ChildrenOfType().Single().Spacing == new Vector2(size) + => AddAssert($"grid size is {size}", () => this.ChildrenOfType().Single().Spacing.Value == new Vector2(size) && EditorBeatmap.BeatmapInfo.GridSize == size); } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 0013f23057..46e43deaae 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -16,6 +16,7 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.RadioButtons; +using osuTK; namespace osu.Game.Rulesets.Osu.Edit { @@ -28,6 +29,9 @@ namespace osu.Game.Rulesets.Osu.Edit [Resolved] private EditorBeatmap editorBeatmap { get; set; } = null!; + /// + /// X position of the grid's origin. + /// public BindableFloat StartPositionX { get; } = new BindableFloat(OsuPlayfield.BASE_SIZE.X / 2) { MinValue = 0f, @@ -35,6 +39,9 @@ namespace osu.Game.Rulesets.Osu.Edit Precision = 1f }; + /// + /// Y position of the grid's origin. + /// public BindableFloat StartPositionY { get; } = new BindableFloat(OsuPlayfield.BASE_SIZE.Y / 2) { MinValue = 0f, @@ -42,6 +49,9 @@ namespace osu.Game.Rulesets.Osu.Edit Precision = 1f }; + /// + /// The spacing between grid lines. + /// public BindableFloat Spacing { get; } = new BindableFloat(4f) { MinValue = 4f, @@ -49,6 +59,9 @@ namespace osu.Game.Rulesets.Osu.Edit Precision = 1f }; + /// + /// Rotation of the grid lines in degrees. + /// public BindableFloat GridLinesRotation { get; } = new BindableFloat(0f) { MinValue = -45f, @@ -56,6 +69,18 @@ namespace osu.Game.Rulesets.Osu.Edit Precision = 1f }; + /// + /// Read-only bindable representing the grid's origin. + /// Equivalent to new Vector2(StartPositionX, StartPositionY) + /// + public Bindable StartPosition { get; } = new Bindable(); + + /// + /// Read-only bindable representing the grid's spacing in both the X and Y dimension. + /// Equivalent to new Vector2(Spacing) + /// + public Bindable SpacingVector { get; } = new Bindable(); + public Bindable GridType { get; } = new Bindable(); private ExpandableSlider startPositionXSlider = null!; @@ -124,18 +149,21 @@ namespace osu.Game.Rulesets.Osu.Edit { startPositionXSlider.ContractedLabelText = $"X: {x.NewValue:N0}"; startPositionXSlider.ExpandedLabelText = $"X Offset: {x.NewValue:N0}"; + StartPosition.Value = new Vector2(x.NewValue, StartPosition.Value.Y); }, true); StartPositionY.BindValueChanged(y => { startPositionYSlider.ContractedLabelText = $"Y: {y.NewValue:N0}"; startPositionYSlider.ExpandedLabelText = $"Y Offset: {y.NewValue:N0}"; + StartPosition.Value = new Vector2(StartPosition.Value.X, y.NewValue); }, true); Spacing.BindValueChanged(spacing => { spacingSlider.ContractedLabelText = $"S: {spacing.NewValue:N0}"; spacingSlider.ExpandedLabelText = $"Spacing: {spacing.NewValue:N0}"; + SpacingVector.Value = new Vector2(spacing.NewValue); }, true); GridLinesRotation.BindValueChanged(rotation => diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 1e7d07dbdd..84d5adbc52 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -120,8 +120,8 @@ namespace osu.Game.Rulesets.Osu.Edit case PositionSnapGridType.Square: var rectangularPositionSnapGrid = new RectangularPositionSnapGrid(); - OsuGridToolboxGroup.Spacing.BindValueChanged(s => rectangularPositionSnapGrid.Spacing = new Vector2(s.NewValue), true); - OsuGridToolboxGroup.GridLinesRotation.BindValueChanged(r => rectangularPositionSnapGrid.GridLineRotation = r.NewValue, true); + rectangularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.SpacingVector); + rectangularPositionSnapGrid.GridLineRotation.BindTo(OsuGridToolboxGroup.GridLinesRotation); positionSnapGrid = rectangularPositionSnapGrid; break; @@ -129,8 +129,8 @@ namespace osu.Game.Rulesets.Osu.Edit case PositionSnapGridType.Triangle: var triangularPositionSnapGrid = new TriangularPositionSnapGrid(); - OsuGridToolboxGroup.Spacing.BindValueChanged(s => triangularPositionSnapGrid.Spacing = s.NewValue, true); - OsuGridToolboxGroup.GridLinesRotation.BindValueChanged(r => triangularPositionSnapGrid.GridLineRotation = r.NewValue, true); + triangularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.Spacing); + triangularPositionSnapGrid.GridLineRotation.BindTo(OsuGridToolboxGroup.GridLinesRotation); positionSnapGrid = triangularPositionSnapGrid; break; @@ -138,7 +138,7 @@ namespace osu.Game.Rulesets.Osu.Edit case PositionSnapGridType.Circle: var circularPositionSnapGrid = new CircularPositionSnapGrid(); - OsuGridToolboxGroup.Spacing.BindValueChanged(s => circularPositionSnapGrid.Spacing = s.NewValue, true); + circularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.Spacing); positionSnapGrid = circularPositionSnapGrid; break; @@ -147,18 +147,11 @@ namespace osu.Game.Rulesets.Osu.Edit throw new NotImplementedException($"{OsuGridToolboxGroup.GridType} has an incorrect value."); } - bindPositionSnapGridStartPosition(positionSnapGrid); + // Bind the start position to the toolbox sliders. + positionSnapGrid.StartPosition.BindTo(OsuGridToolboxGroup.StartPosition); + positionSnapGrid.RelativeSizeAxes = Axes.Both; LayerBelowRuleset.Add(positionSnapGrid); - return; - - void bindPositionSnapGridStartPosition(PositionSnapGrid snapGrid) - { - OsuGridToolboxGroup.StartPositionX.BindValueChanged(x => - snapGrid.StartPosition = new Vector2(x.NewValue, snapGrid.StartPosition.Y), true); - OsuGridToolboxGroup.StartPositionY.BindValueChanged(y => - snapGrid.StartPosition = new Vector2(snapGrid.StartPosition.X, y.NewValue), true); - } } protected override ComposeBlueprintContainer CreateBlueprintContainer() diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs index b9b0cbe389..403a270359 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs @@ -4,33 +4,28 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Utils; -using osu.Game.Utils; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { public partial class CircularPositionSnapGrid : PositionSnapGrid { - private float spacing = 1; - /// /// The spacing between grid lines of this . /// - public float Spacing + public BindableFloat Spacing { get; } = new BindableFloat(1f) { - get => spacing; - set - { - if (spacing <= 0) - throw new ArgumentException("Grid spacing must be positive."); + MinValue = 0f, + }; - spacing = value; - GridCache.Invalidate(); - } + public CircularPositionSnapGrid() + { + Spacing.BindValueChanged(_ => GridCache.Invalidate()); } protected override void CreateContent() @@ -39,11 +34,11 @@ namespace osu.Game.Screens.Edit.Compose.Components // Calculate the maximum distance from the origin to the edge of the grid. float maxDist = MathF.Max( - MathF.Max(StartPosition.Length, (StartPosition - drawSize).Length), - MathF.Max((StartPosition - new Vector2(drawSize.X, 0)).Length, (StartPosition - new Vector2(0, drawSize.Y)).Length) + MathF.Max(StartPosition.Value.Length, (StartPosition.Value - drawSize).Length), + MathF.Max((StartPosition.Value - new Vector2(drawSize.X, 0)).Length, (StartPosition.Value - new Vector2(0, drawSize.Y)).Length) ); - generateCircles((int)(maxDist / Spacing) + 1); + generateCircles((int)(maxDist / Spacing.Value) + 1); GenerateOutline(drawSize); } @@ -58,7 +53,7 @@ namespace osu.Game.Screens.Edit.Compose.Components for (int i = 0; i < count; i++) { // Add a minimum diameter so the center circle is clearly visible. - float diameter = MathF.Max(lineWidth * 1.5f, i * Spacing * 2); + float diameter = MathF.Max(lineWidth * 1.5f, i * Spacing.Value * 2); var gridCircle = new CircularContainer { @@ -69,7 +64,7 @@ namespace osu.Game.Screens.Edit.Compose.Components RelativeSizeAxes = Axes.None, Width = diameter, Height = diameter, - Position = StartPosition, + Position = StartPosition.Value, Masking = true, Child = new Box { @@ -92,15 +87,15 @@ namespace osu.Game.Screens.Edit.Compose.Components public override Vector2 GetSnappedPosition(Vector2 original) { - Vector2 relativeToStart = original - StartPosition; + Vector2 relativeToStart = original - StartPosition.Value; if (relativeToStart.LengthSquared < Precision.FLOAT_EPSILON) - return StartPosition; + return StartPosition.Value; float length = relativeToStart.Length; - float wantedLength = MathF.Round(length / Spacing) * Spacing; + float wantedLength = MathF.Round(length / Spacing.Value) * Spacing.Value; - return StartPosition + Vector2.Multiply(relativeToStart, wantedLength / length); + return StartPosition.Value + Vector2.Multiply(relativeToStart, wantedLength / length); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs index 94da9c5b84..ebdd76a4e2 100644 --- a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs @@ -16,7 +16,7 @@ namespace osu.Game.Screens.Edit.Compose.Components protected void GenerateGridLines(Vector2 step, Vector2 drawSize) { int index = 0; - var currentPosition = StartPosition; + var currentPosition = StartPosition.Value; // Make lines the same width independent of display resolution. float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; @@ -42,7 +42,7 @@ namespace osu.Game.Screens.Edit.Compose.Components generatedLines.Add(gridLine); index += 1; - currentPosition = StartPosition + index * step; + currentPosition = StartPosition.Value + index * step; } if (generatedLines.Count == 0) diff --git a/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs index dd412e3cd3..36687ef73a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.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.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -11,20 +12,10 @@ namespace osu.Game.Screens.Edit.Compose.Components { public abstract partial class PositionSnapGrid : CompositeDrawable { - private Vector2 startPosition; - /// /// The position of the origin of this in local coordinates. /// - public Vector2 StartPosition - { - get => startPosition; - set - { - startPosition = value; - GridCache.Invalidate(); - } - } + public Bindable StartPosition { get; } = new Bindable(Vector2.Zero); protected readonly LayoutValue GridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); @@ -32,6 +23,8 @@ namespace osu.Game.Screens.Edit.Compose.Components { Masking = true; + StartPosition.BindValueChanged(_ => GridCache.Invalidate()); + AddLayout(GridCache); } diff --git a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs index 2392921203..3bf0ef8ac3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Bindables; using osu.Game.Utils; using osuTK; @@ -9,60 +10,43 @@ namespace osu.Game.Screens.Edit.Compose.Components { public partial class RectangularPositionSnapGrid : LinedPositionSnapGrid { - private Vector2 spacing = Vector2.One; - /// /// The spacing between grid lines of this . /// - public Vector2 Spacing - { - get => spacing; - set - { - if (spacing.X <= 0 || spacing.Y <= 0) - throw new ArgumentException("Grid spacing must be positive."); - - spacing = value; - GridCache.Invalidate(); - } - } - - private float gridLineRotation; + public Bindable Spacing { get; } = new Bindable(Vector2.One); /// /// The rotation in degrees of the grid lines of this . /// - public float GridLineRotation + public BindableFloat GridLineRotation { get; } = new BindableFloat(); + + public RectangularPositionSnapGrid() { - get => gridLineRotation; - set - { - gridLineRotation = value; - GridCache.Invalidate(); - } + Spacing.BindValueChanged(_ => GridCache.Invalidate()); + GridLineRotation.BindValueChanged(_ => GridCache.Invalidate()); } protected override void CreateContent() { var drawSize = DrawSize; - var rot = Quaternion.FromAxisAngle(Vector3.UnitZ, MathHelper.DegreesToRadians(GridLineRotation)); + var rot = Quaternion.FromAxisAngle(Vector3.UnitZ, MathHelper.DegreesToRadians(GridLineRotation.Value)); - GenerateGridLines(Vector2.Transform(new Vector2(0, -Spacing.Y), rot), drawSize); - GenerateGridLines(Vector2.Transform(new Vector2(0, Spacing.Y), rot), drawSize); + GenerateGridLines(Vector2.Transform(new Vector2(0, -Spacing.Value.Y), rot), drawSize); + GenerateGridLines(Vector2.Transform(new Vector2(0, Spacing.Value.Y), rot), drawSize); - GenerateGridLines(Vector2.Transform(new Vector2(-Spacing.X, 0), rot), drawSize); - GenerateGridLines(Vector2.Transform(new Vector2(Spacing.X, 0), rot), drawSize); + GenerateGridLines(Vector2.Transform(new Vector2(-Spacing.Value.X, 0), rot), drawSize); + GenerateGridLines(Vector2.Transform(new Vector2(Spacing.Value.X, 0), rot), drawSize); GenerateOutline(drawSize); } public override Vector2 GetSnappedPosition(Vector2 original) { - Vector2 relativeToStart = GeometryUtils.RotateVector(original - StartPosition, GridLineRotation); - Vector2 offset = Vector2.Divide(relativeToStart, Spacing); + Vector2 relativeToStart = GeometryUtils.RotateVector(original - StartPosition.Value, GridLineRotation.Value); + Vector2 offset = Vector2.Divide(relativeToStart, Spacing.Value); Vector2 roundedOffset = new Vector2(MathF.Round(offset.X), MathF.Round(offset.Y)); - return StartPosition + GeometryUtils.RotateVector(Vector2.Multiply(roundedOffset, Spacing), -GridLineRotation); + return StartPosition.Value + GeometryUtils.RotateVector(Vector2.Multiply(roundedOffset, Spacing.Value), -GridLineRotation.Value); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs index c98890e294..93d2c6a74a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Bindables; using osu.Game.Utils; using osuTK; @@ -9,37 +10,23 @@ namespace osu.Game.Screens.Edit.Compose.Components { public partial class TriangularPositionSnapGrid : LinedPositionSnapGrid { - private float spacing = 1; - /// /// The spacing between grid lines of this . /// - public float Spacing + public BindableFloat Spacing { get; } = new BindableFloat(1f) { - get => spacing; - set - { - if (spacing <= 0) - throw new ArgumentException("Grid spacing must be positive."); - - spacing = value; - GridCache.Invalidate(); - } - } - - private float gridLineRotation; + MinValue = 0f, + }; /// /// The rotation in degrees of the grid lines of this . /// - public float GridLineRotation + public BindableFloat GridLineRotation { get; } = new BindableFloat(); + + public TriangularPositionSnapGrid() { - get => gridLineRotation; - set - { - gridLineRotation = value; - GridCache.Invalidate(); - } + Spacing.BindValueChanged(_ => GridCache.Invalidate()); + GridLineRotation.BindValueChanged(_ => GridCache.Invalidate()); } private const float sqrt3 = 1.73205080757f; @@ -49,10 +36,10 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override void CreateContent() { var drawSize = DrawSize; - float stepSpacing = Spacing * sqrt3_over2; - var step1 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation - 30); - var step2 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation - 90); - var step3 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation - 150); + float stepSpacing = Spacing.Value * sqrt3_over2; + var step1 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation.Value - 30); + var step2 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation.Value - 90); + var step3 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation.Value - 150); GenerateGridLines(step1, drawSize); GenerateGridLines(-step1, drawSize); @@ -68,16 +55,16 @@ namespace osu.Game.Screens.Edit.Compose.Components public override Vector2 GetSnappedPosition(Vector2 original) { - Vector2 relativeToStart = GeometryUtils.RotateVector(original - StartPosition, GridLineRotation); + Vector2 relativeToStart = GeometryUtils.RotateVector(original - StartPosition.Value, GridLineRotation.Value); Vector2 hex = pixelToHex(relativeToStart); - return StartPosition + GeometryUtils.RotateVector(hexToPixel(hex), -GridLineRotation); + return StartPosition.Value + GeometryUtils.RotateVector(hexToPixel(hex), -GridLineRotation.Value); } private Vector2 pixelToHex(Vector2 pixel) { - float x = pixel.X / Spacing; - float y = pixel.Y / Spacing; + float x = pixel.X / Spacing.Value; + float y = pixel.Y / Spacing.Value; // Algorithm from Charles Chambers // with modifications and comments by Chris Cox 2023 // @@ -96,7 +83,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { // Taken from // with modifications for the different definition of size. - return new Vector2(Spacing * (hex.X - hex.Y / 2), Spacing * one_over_sqrt3 * 1.5f * hex.Y); + return new Vector2(Spacing.Value * (hex.X - hex.Y / 2), Spacing.Value * one_over_sqrt3 * 1.5f * hex.Y); } } } From 1c75357d77fba3723476d2bd1657e1d1fdd45efd Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 30 Dec 2023 01:12:23 +0100 Subject: [PATCH 2271/4092] fix compile --- osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs index ad00e8d47d..a7f0a52003 100644 --- a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs @@ -53,13 +53,14 @@ namespace osu.Game.Tournament.Screens.Editors ScrollContent.Add(grid = new RectangularPositionSnapGrid { - Spacing = new Vector2(GRID_SPACING), Anchor = Anchor.Centre, Origin = Anchor.Centre, BypassAutoSizeAxes = Axes.Both, Depth = float.MaxValue }); + grid.Spacing.Value = new Vector2(GRID_SPACING); + LadderInfo.Matches.CollectionChanged += (_, _) => updateMessage(); updateMessage(); } From 9a8c41f6ca8a4e1df4d3f98406126d40858ed419 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 30 Dec 2023 14:32:20 +0100 Subject: [PATCH 2272/4092] Saving exact grid spacing --- osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 46e43deaae..442575711a 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -133,10 +133,10 @@ namespace osu.Game.Rulesets.Osu.Edit }, }; + Spacing.Value = editorBeatmap.BeatmapInfo.GridSize; int gridSizeIndex = Array.IndexOf(grid_sizes, editorBeatmap.BeatmapInfo.GridSize); if (gridSizeIndex >= 0) currentGridSizeIndex = gridSizeIndex; - updateSpacing(); } protected override void LoadComplete() @@ -164,6 +164,7 @@ namespace osu.Game.Rulesets.Osu.Edit spacingSlider.ContractedLabelText = $"S: {spacing.NewValue:N0}"; spacingSlider.ExpandedLabelText = $"Spacing: {spacing.NewValue:N0}"; SpacingVector.Value = new Vector2(spacing.NewValue); + editorBeatmap.BeatmapInfo.GridSize = (int)spacing.NewValue; }, true); GridLinesRotation.BindValueChanged(rotation => @@ -176,15 +177,7 @@ namespace osu.Game.Rulesets.Osu.Edit private void nextGridSize() { currentGridSizeIndex = (currentGridSizeIndex + 1) % grid_sizes.Length; - updateSpacing(); - } - - private void updateSpacing() - { - int gridSize = grid_sizes[currentGridSizeIndex]; - - editorBeatmap.BeatmapInfo.GridSize = gridSize; - Spacing.Value = gridSize; + Spacing.Value = grid_sizes[currentGridSizeIndex]; } public bool OnPressed(KeyBindingPressEvent e) From 493e3a5f7a2cf9b355cf8319abe16b12c069f92d Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 02:55:47 +0100 Subject: [PATCH 2273/4092] use G to change grid type --- .../Edit/OsuGridToolboxGroup.cs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 442575711a..c07e8028b6 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -22,9 +22,9 @@ namespace osu.Game.Rulesets.Osu.Edit { public partial class OsuGridToolboxGroup : EditorToolboxGroup, IKeyBindingHandler { - private static readonly int[] grid_sizes = { 4, 8, 16, 32 }; + private static readonly PositionSnapGridType[] grid_types = Enum.GetValues(typeof(PositionSnapGridType)).Cast().ToArray(); - private int currentGridSizeIndex = grid_sizes.Length - 1; + private int currentGridTypeIndex; [Resolved] private EditorBeatmap editorBeatmap { get; set; } = null!; @@ -134,9 +134,6 @@ namespace osu.Game.Rulesets.Osu.Edit }; Spacing.Value = editorBeatmap.BeatmapInfo.GridSize; - int gridSizeIndex = Array.IndexOf(grid_sizes, editorBeatmap.BeatmapInfo.GridSize); - if (gridSizeIndex >= 0) - currentGridSizeIndex = gridSizeIndex; } protected override void LoadComplete() @@ -174,10 +171,11 @@ namespace osu.Game.Rulesets.Osu.Edit }, true); } - private void nextGridSize() + private void nextGridType() { - currentGridSizeIndex = (currentGridSizeIndex + 1) % grid_sizes.Length; - Spacing.Value = grid_sizes[currentGridSizeIndex]; + currentGridTypeIndex = (currentGridTypeIndex + 1) % grid_types.Length; + GridType.Value = grid_types[currentGridTypeIndex]; + gridTypeButtons.Items[currentGridTypeIndex].Select(); } public bool OnPressed(KeyBindingPressEvent e) @@ -185,7 +183,7 @@ namespace osu.Game.Rulesets.Osu.Edit switch (e.Action) { case GlobalAction.EditorCycleGridDisplayMode: - nextGridSize(); + nextGridType(); return true; } From e47d570e68d155375833f9a06f71820fabd7af47 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 03:53:42 +0100 Subject: [PATCH 2274/4092] improve UI --- .../Edit/OsuGridToolboxGroup.cs | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index c07e8028b6..da5849a77a 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -6,10 +6,12 @@ using System.Linq; using osu.Framework.Allocation; 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.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; @@ -17,6 +19,7 @@ using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.RadioButtons; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Edit { @@ -29,6 +32,9 @@ namespace osu.Game.Rulesets.Osu.Edit [Resolved] private EditorBeatmap editorBeatmap { get; set; } = null!; + [Resolved] + private IExpandingContainer? expandingContainer { get; set; } + /// /// X position of the grid's origin. /// @@ -125,7 +131,7 @@ namespace osu.Game.Rulesets.Osu.Edit () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), new RadioButton("Triangle", () => GridType.Value = PositionSnapGridType.Triangle, - () => new Triangle()), + () => new OutlineTriangle(true, 20)), new RadioButton("Circle", () => GridType.Value = PositionSnapGridType.Circle, () => new SpriteIcon { Icon = FontAwesome.Regular.Circle }), @@ -136,6 +142,36 @@ namespace osu.Game.Rulesets.Osu.Edit Spacing.Value = editorBeatmap.BeatmapInfo.GridSize; } + public partial class OutlineTriangle : BufferedContainer + { + public OutlineTriangle(bool outlineOnly, float size) + : base(cachedFrameBuffer: true) + { + Size = new Vector2(size); + + InternalChildren = new Drawable[] + { + new EquilateralTriangle { RelativeSizeAxes = Axes.Both }, + }; + + if (outlineOnly) + { + AddInternal(new EquilateralTriangle + { + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.Y, + Y = 0.48f, + Colour = Color4.Black, + Size = new Vector2(size - 7), + Blending = BlendingParameters.None, + }); + } + + Blending = BlendingParameters.Additive; + } + } + protected override void LoadComplete() { base.LoadComplete(); From 904ea2e436bf694f5e95f0bf02358bd6668304b1 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 15:48:09 +0100 Subject: [PATCH 2275/4092] move OutlineTriangle code down --- .../Edit/OsuGridToolboxGroup.cs | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index da5849a77a..72e60a5515 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -142,36 +142,6 @@ namespace osu.Game.Rulesets.Osu.Edit Spacing.Value = editorBeatmap.BeatmapInfo.GridSize; } - public partial class OutlineTriangle : BufferedContainer - { - public OutlineTriangle(bool outlineOnly, float size) - : base(cachedFrameBuffer: true) - { - Size = new Vector2(size); - - InternalChildren = new Drawable[] - { - new EquilateralTriangle { RelativeSizeAxes = Axes.Both }, - }; - - if (outlineOnly) - { - AddInternal(new EquilateralTriangle - { - Anchor = Anchor.TopCentre, - Origin = Anchor.Centre, - RelativePositionAxes = Axes.Y, - Y = 0.48f, - Colour = Color4.Black, - Size = new Vector2(size - 7), - Blending = BlendingParameters.None, - }); - } - - Blending = BlendingParameters.Additive; - } - } - protected override void LoadComplete() { base.LoadComplete(); @@ -229,6 +199,36 @@ namespace osu.Game.Rulesets.Osu.Edit public void OnReleased(KeyBindingReleaseEvent e) { } + + public partial class OutlineTriangle : BufferedContainer + { + public OutlineTriangle(bool outlineOnly, float size) + : base(cachedFrameBuffer: true) + { + Size = new Vector2(size); + + InternalChildren = new Drawable[] + { + new EquilateralTriangle { RelativeSizeAxes = Axes.Both }, + }; + + if (outlineOnly) + { + AddInternal(new EquilateralTriangle + { + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.Y, + Y = 0.48f, + Colour = Color4.Black, + Size = new Vector2(size - 7), + Blending = BlendingParameters.None, + }); + } + + Blending = BlendingParameters.Additive; + } + } } public enum PositionSnapGridType From 33e559f83502f84f93d328fea61d9e9fc7d83679 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 16:41:22 +0100 Subject: [PATCH 2276/4092] add integer keyboard step to sliders --- osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 72e60a5515..6ed7054159 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -107,19 +107,23 @@ namespace osu.Game.Rulesets.Osu.Edit { startPositionXSlider = new ExpandableSlider { - Current = StartPositionX + Current = StartPositionX, + KeyboardStep = 1, }, startPositionYSlider = new ExpandableSlider { - Current = StartPositionY + Current = StartPositionY, + KeyboardStep = 1, }, spacingSlider = new ExpandableSlider { - Current = Spacing + Current = Spacing, + KeyboardStep = 1, }, gridLinesRotationSlider = new ExpandableSlider { - Current = GridLinesRotation + Current = GridLinesRotation, + KeyboardStep = 1, }, gridTypeButtons = new EditorRadioButtonCollection { From 20e338b8920d33180434ce6c4a7f362118a6fdfe Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 16:45:21 +0100 Subject: [PATCH 2277/4092] also hide grid from points button when not hovered --- .../Edit/OsuGridToolboxGroup.cs | 41 +++++++++++++------ 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 6ed7054159..981148858d 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -125,20 +125,29 @@ namespace osu.Game.Rulesets.Osu.Edit Current = GridLinesRotation, KeyboardStep = 1, }, - gridTypeButtons = new EditorRadioButtonCollection + new FillFlowContainer { RelativeSizeAxes = Axes.X, - Items = new[] + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0f, 10f), + Children = new Drawable[] { - new RadioButton("Square", - () => GridType.Value = PositionSnapGridType.Square, - () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), - new RadioButton("Triangle", - () => GridType.Value = PositionSnapGridType.Triangle, - () => new OutlineTriangle(true, 20)), - new RadioButton("Circle", - () => GridType.Value = PositionSnapGridType.Circle, - () => new SpriteIcon { Icon = FontAwesome.Regular.Circle }), + gridTypeButtons = new EditorRadioButtonCollection + { + RelativeSizeAxes = Axes.X, + Items = new[] + { + new RadioButton("Square", + () => GridType.Value = PositionSnapGridType.Square, + () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), + new RadioButton("Triangle", + () => GridType.Value = PositionSnapGridType.Triangle, + () => new OutlineTriangle(true, 20)), + new RadioButton("Circle", + () => GridType.Value = PositionSnapGridType.Circle, + () => new SpriteIcon { Icon = FontAwesome.Regular.Circle }), + } + }, } }, }; @@ -176,8 +185,14 @@ namespace osu.Game.Rulesets.Osu.Edit GridLinesRotation.BindValueChanged(rotation => { - gridLinesRotationSlider.ContractedLabelText = $"R: {rotation.NewValue:N0}"; - gridLinesRotationSlider.ExpandedLabelText = $"Rotation: {rotation.NewValue:N0}"; + gridLinesRotationSlider.ContractedLabelText = $"R: {rotation.NewValue:#,0.##}"; + gridLinesRotationSlider.ExpandedLabelText = $"Rotation: {rotation.NewValue:#,0.##}"; + }, true); + + expandingContainer?.Expanded.BindValueChanged(v => + { + gridTypeButtons.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint); + gridTypeButtons.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None; }, true); } From 8425c7226c20fcafe961ca4e6c72b3d718dc7105 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 16:54:04 +0100 Subject: [PATCH 2278/4092] fix rectangular and triangular grid tests --- .../Editing/TestSceneRectangularPositionSnapGrid.cs | 13 ++++++++----- .../Editing/TestSceneTriangularPositionSnapGrid.cs | 12 ++++++++---- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs index a0042cf605..19903737f6 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs @@ -52,12 +52,15 @@ namespace osu.Game.Tests.Visual.Editing { RectangularPositionSnapGrid grid = null; - AddStep("create grid", () => Child = grid = new RectangularPositionSnapGrid() + AddStep("create grid", () => { - RelativeSizeAxes = Axes.Both, - StartPosition = position, - Spacing = spacing, - GridLineRotation = rotation + Child = grid = new RectangularPositionSnapGrid + { + RelativeSizeAxes = Axes.Both, + }; + grid.StartPosition.Value = position; + grid.Spacing.Value = spacing; + grid.GridLineRotation.Value = rotation; }); AddStep("add snapping cursor", () => Add(new SnappingCursorContainer diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs index 2f5ffd8423..b1f82fa114 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs @@ -52,11 +52,15 @@ namespace osu.Game.Tests.Visual.Editing { TriangularPositionSnapGrid grid = null; - AddStep("create grid", () => Child = grid = new TriangularPositionSnapGrid(position) + AddStep("create grid", () => { - RelativeSizeAxes = Axes.Both, - Spacing = spacing, - GridLineRotation = rotation + Child = grid = new TriangularPositionSnapGrid + { + RelativeSizeAxes = Axes.Both, + }; + grid.StartPosition.Value = position; + grid.Spacing.Value = spacing; + grid.GridLineRotation.Value = rotation; }); AddStep("add snapping cursor", () => Add(new SnappingCursorContainer From 31d17994807dd4fe36b2cc73b699753884f21d19 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 16:56:10 +0100 Subject: [PATCH 2279/4092] Create TestSceneCircularPositionSnapGrid.cs --- .../TestSceneCircularPositionSnapGrid.cs | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneCircularPositionSnapGrid.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneCircularPositionSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneCircularPositionSnapGrid.cs new file mode 100644 index 0000000000..4481199c94 --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneCircularPositionSnapGrid.cs @@ -0,0 +1,111 @@ +// 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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Screens.Edit.Compose.Components; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Editing +{ + public partial class TestSceneCircularPositionSnapGrid : OsuManualInputManagerTestScene + { + private Container content; + protected override Container Content => content; + + [BackgroundDependencyLoader] + private void load() + { + base.Content.AddRange(new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.Gray + }, + content = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(10), + } + }); + } + + private static readonly object[][] test_cases = + { + new object[] { new Vector2(0, 0), 10, 0f }, + new object[] { new Vector2(240, 180), 10, 10f }, + new object[] { new Vector2(160, 120), 30, -10f }, + new object[] { new Vector2(480, 360), 100, 0f }, + }; + + [TestCaseSource(nameof(test_cases))] + public void TestCircularGrid(Vector2 position, float spacing, float rotation) + { + CircularPositionSnapGrid grid = null; + + AddStep("create grid", () => + { + Child = grid = new CircularPositionSnapGrid + { + RelativeSizeAxes = Axes.Both, + }; + grid.StartPosition.Value = position; + grid.Spacing.Value = spacing; + }); + + AddStep("add snapping cursor", () => Add(new SnappingCursorContainer + { + RelativeSizeAxes = Axes.Both, + GetSnapPosition = pos => grid.GetSnappedPosition(grid.ToLocalSpace(pos)) + })); + } + + private partial class SnappingCursorContainer : CompositeDrawable + { + public Func GetSnapPosition; + + private readonly Drawable cursor; + + public SnappingCursorContainer() + { + RelativeSizeAxes = Axes.Both; + + InternalChild = cursor = new Circle + { + Origin = Anchor.Centre, + Size = new Vector2(50), + Colour = Color4.Red + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + updatePosition(GetContainingInputManager().CurrentState.Mouse.Position); + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + base.OnMouseMove(e); + + updatePosition(e.ScreenSpaceMousePosition); + return true; + } + + private void updatePosition(Vector2 screenSpacePosition) + { + cursor.Position = GetSnapPosition.Invoke(screenSpacePosition); + } + } + } +} From 9796fcff520b52a150cb469c046d13a42a0cc543 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 16:59:46 +0100 Subject: [PATCH 2280/4092] Merge position snap grid tests into single file --- .../TestSceneCircularPositionSnapGrid.cs | 111 ----------------- ...apGrid.cs => TestScenePositionSnapGrid.cs} | 51 +++++++- .../TestSceneTriangularPositionSnapGrid.cs | 112 ------------------ 3 files changed, 48 insertions(+), 226 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Editing/TestSceneCircularPositionSnapGrid.cs rename osu.Game.Tests/Visual/Editing/{TestSceneRectangularPositionSnapGrid.cs => TestScenePositionSnapGrid.cs} (66%) delete mode 100644 osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneCircularPositionSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneCircularPositionSnapGrid.cs deleted file mode 100644 index 4481199c94..0000000000 --- a/osu.Game.Tests/Visual/Editing/TestSceneCircularPositionSnapGrid.cs +++ /dev/null @@ -1,111 +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 System; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; -using osu.Game.Screens.Edit.Compose.Components; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Tests.Visual.Editing -{ - public partial class TestSceneCircularPositionSnapGrid : OsuManualInputManagerTestScene - { - private Container content; - protected override Container Content => content; - - [BackgroundDependencyLoader] - private void load() - { - base.Content.AddRange(new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Colour4.Gray - }, - content = new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(10), - } - }); - } - - private static readonly object[][] test_cases = - { - new object[] { new Vector2(0, 0), 10, 0f }, - new object[] { new Vector2(240, 180), 10, 10f }, - new object[] { new Vector2(160, 120), 30, -10f }, - new object[] { new Vector2(480, 360), 100, 0f }, - }; - - [TestCaseSource(nameof(test_cases))] - public void TestCircularGrid(Vector2 position, float spacing, float rotation) - { - CircularPositionSnapGrid grid = null; - - AddStep("create grid", () => - { - Child = grid = new CircularPositionSnapGrid - { - RelativeSizeAxes = Axes.Both, - }; - grid.StartPosition.Value = position; - grid.Spacing.Value = spacing; - }); - - AddStep("add snapping cursor", () => Add(new SnappingCursorContainer - { - RelativeSizeAxes = Axes.Both, - GetSnapPosition = pos => grid.GetSnappedPosition(grid.ToLocalSpace(pos)) - })); - } - - private partial class SnappingCursorContainer : CompositeDrawable - { - public Func GetSnapPosition; - - private readonly Drawable cursor; - - public SnappingCursorContainer() - { - RelativeSizeAxes = Axes.Both; - - InternalChild = cursor = new Circle - { - Origin = Anchor.Centre, - Size = new Vector2(50), - Colour = Color4.Red - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - updatePosition(GetContainingInputManager().CurrentState.Mouse.Position); - } - - protected override bool OnMouseMove(MouseMoveEvent e) - { - base.OnMouseMove(e); - - updatePosition(e.ScreenSpaceMousePosition); - return true; - } - - private void updatePosition(Vector2 screenSpacePosition) - { - cursor.Position = GetSnapPosition.Invoke(screenSpacePosition); - } - } - } -} diff --git a/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestScenePositionSnapGrid.cs similarity index 66% rename from osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs rename to osu.Game.Tests/Visual/Editing/TestScenePositionSnapGrid.cs index 19903737f6..7e66edc2dd 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestScenePositionSnapGrid.cs @@ -16,7 +16,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.Editing { - public partial class TestSceneRectangularPositionSnapGrid : OsuManualInputManagerTestScene + public partial class TestScenePositionSnapGrid : OsuManualInputManagerTestScene { private Container content; protected override Container Content => content; @@ -42,8 +42,8 @@ namespace osu.Game.Tests.Visual.Editing private static readonly object[][] test_cases = { new object[] { new Vector2(0, 0), new Vector2(10, 10), 0f }, - new object[] { new Vector2(240, 180), new Vector2(10, 15), 30f }, - new object[] { new Vector2(160, 120), new Vector2(30, 20), -30f }, + new object[] { new Vector2(240, 180), new Vector2(10, 15), 10f }, + new object[] { new Vector2(160, 120), new Vector2(30, 20), -10f }, new object[] { new Vector2(480, 360), new Vector2(100, 100), 0f }, }; @@ -70,6 +70,51 @@ namespace osu.Game.Tests.Visual.Editing })); } + [TestCaseSource(nameof(test_cases))] + public void TestTriangularGrid(Vector2 position, Vector2 spacing, float rotation) + { + TriangularPositionSnapGrid grid = null; + + AddStep("create grid", () => + { + Child = grid = new TriangularPositionSnapGrid + { + RelativeSizeAxes = Axes.Both, + }; + grid.StartPosition.Value = position; + grid.Spacing.Value = spacing.X; + grid.GridLineRotation.Value = rotation; + }); + + AddStep("add snapping cursor", () => Add(new SnappingCursorContainer + { + RelativeSizeAxes = Axes.Both, + GetSnapPosition = pos => grid.GetSnappedPosition(grid.ToLocalSpace(pos)) + })); + } + + [TestCaseSource(nameof(test_cases))] + public void TestCircularGrid(Vector2 position, Vector2 spacing, float rotation) + { + CircularPositionSnapGrid grid = null; + + AddStep("create grid", () => + { + Child = grid = new CircularPositionSnapGrid + { + RelativeSizeAxes = Axes.Both, + }; + grid.StartPosition.Value = position; + grid.Spacing.Value = spacing.X; + }); + + AddStep("add snapping cursor", () => Add(new SnappingCursorContainer + { + RelativeSizeAxes = Axes.Both, + GetSnapPosition = pos => grid.GetSnappedPosition(grid.ToLocalSpace(pos)) + })); + } + private partial class SnappingCursorContainer : CompositeDrawable { public Func GetSnapPosition; diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs deleted file mode 100644 index b1f82fa114..0000000000 --- a/osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs +++ /dev/null @@ -1,112 +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 System; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; -using osu.Game.Screens.Edit.Compose.Components; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Tests.Visual.Editing -{ - public partial class TestSceneTriangularPositionSnapGrid : OsuManualInputManagerTestScene - { - private Container content; - protected override Container Content => content; - - [BackgroundDependencyLoader] - private void load() - { - base.Content.AddRange(new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Colour4.Gray - }, - content = new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(10), - } - }); - } - - private static readonly object[][] test_cases = - { - new object[] { new Vector2(0, 0), 10, 0f }, - new object[] { new Vector2(240, 180), 10, 10f }, - new object[] { new Vector2(160, 120), 30, -10f }, - new object[] { new Vector2(480, 360), 100, 0f }, - }; - - [TestCaseSource(nameof(test_cases))] - public void TestTriangularGrid(Vector2 position, float spacing, float rotation) - { - TriangularPositionSnapGrid grid = null; - - AddStep("create grid", () => - { - Child = grid = new TriangularPositionSnapGrid - { - RelativeSizeAxes = Axes.Both, - }; - grid.StartPosition.Value = position; - grid.Spacing.Value = spacing; - grid.GridLineRotation.Value = rotation; - }); - - AddStep("add snapping cursor", () => Add(new SnappingCursorContainer - { - RelativeSizeAxes = Axes.Both, - GetSnapPosition = pos => grid.GetSnappedPosition(grid.ToLocalSpace(pos)) - })); - } - - private partial class SnappingCursorContainer : CompositeDrawable - { - public Func GetSnapPosition; - - private readonly Drawable cursor; - - public SnappingCursorContainer() - { - RelativeSizeAxes = Axes.Both; - - InternalChild = cursor = new Circle - { - Origin = Anchor.Centre, - Size = new Vector2(50), - Colour = Color4.Red - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - updatePosition(GetContainingInputManager().CurrentState.Mouse.Position); - } - - protected override bool OnMouseMove(MouseMoveEvent e) - { - base.OnMouseMove(e); - - updatePosition(e.ScreenSpaceMousePosition); - return true; - } - - private void updatePosition(Vector2 screenSpacePosition) - { - cursor.Position = GetSnapPosition.Invoke(screenSpacePosition); - } - } - } -} From c5edf4328338ac1ab2ef42f0b8ed004c674aed3a Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 19:53:32 +0100 Subject: [PATCH 2281/4092] fix grid test --- .../Editor/TestSceneOsuEditorGrids.cs | 74 ++++++++++++------- 1 file changed, 48 insertions(+), 26 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index ff406b1b88..baeb0639d5 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.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 System; using System.Linq; using NUnit.Framework; using osu.Framework.Testing; @@ -9,6 +10,7 @@ using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Tests.Visual; +using osu.Game.Utils; using osuTK; using osuTK.Input; @@ -25,22 +27,22 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1))); AddUntilStep("distance snap grid visible", () => this.ChildrenOfType().Any()); - rectangularGridActive(false); + gridActive(false); AddStep("enable rectangular grid", () => InputManager.Key(Key.Y)); AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1))); AddUntilStep("distance snap grid still visible", () => this.ChildrenOfType().Any()); - rectangularGridActive(true); + gridActive(true); AddStep("disable distance snap grid", () => InputManager.Key(Key.T)); AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType().Any()); AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1))); - rectangularGridActive(true); + gridActive(true); AddStep("disable rectangular grid", () => InputManager.Key(Key.Y)); AddUntilStep("distance snap grid still hidden", () => !this.ChildrenOfType().Any()); - rectangularGridActive(false); + gridActive(false); } [Test] @@ -58,49 +60,69 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor [Test] public void TestGridSnapMomentaryToggle() { - rectangularGridActive(false); + gridActive(false); AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft)); - rectangularGridActive(true); + gridActive(true); AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft)); - rectangularGridActive(false); + gridActive(false); } - private void rectangularGridActive(bool active) + private void gridActive(bool active) where T : PositionSnapGrid { AddStep("choose placement tool", () => InputManager.Key(Key.Number2)); - AddStep("move cursor to (1, 1)", () => + AddStep("move cursor to spacing + (1, 1)", () => { - var composer = Editor.ChildrenOfType().Single(); - InputManager.MoveMouseTo(composer.ToScreenSpace(new Vector2(1, 1))); + var composer = Editor.ChildrenOfType().Single(); + InputManager.MoveMouseTo(composer.ToScreenSpace(uniqueSnappingPosition(composer) + new Vector2(1, 1))); }); if (active) - AddAssert("placement blueprint at (0, 0)", () => Precision.AlmostEquals(Editor.ChildrenOfType().Single().HitObject.Position, new Vector2(0, 0))); + { + AddAssert("placement blueprint at spacing + (0, 0)", () => + { + var composer = Editor.ChildrenOfType().Single(); + return Precision.AlmostEquals(Editor.ChildrenOfType().Single().HitObject.Position, + uniqueSnappingPosition(composer)); + }); + } else - AddAssert("placement blueprint at (1, 1)", () => Precision.AlmostEquals(Editor.ChildrenOfType().Single().HitObject.Position, new Vector2(1, 1))); + { + AddAssert("placement blueprint at spacing + (1, 1)", () => + { + var composer = Editor.ChildrenOfType().Single(); + return Precision.AlmostEquals(Editor.ChildrenOfType().Single().HitObject.Position, + uniqueSnappingPosition(composer) + new Vector2(1, 1)); + }); + } + } + + private Vector2 uniqueSnappingPosition(PositionSnapGrid grid) + { + return grid switch + { + RectangularPositionSnapGrid rectangular => rectangular.StartPosition.Value + GeometryUtils.RotateVector(rectangular.Spacing.Value, -rectangular.GridLineRotation.Value), + TriangularPositionSnapGrid triangular => triangular.StartPosition.Value + GeometryUtils.RotateVector(new Vector2(triangular.Spacing.Value / 2, triangular.Spacing.Value / 2 * MathF.Sqrt(3)), -triangular.GridLineRotation.Value), + CircularPositionSnapGrid circular => circular.StartPosition.Value + GeometryUtils.RotateVector(new Vector2(circular.Spacing.Value, 0), -45), + _ => Vector2.Zero + }; } [Test] - public void TestGridSizeToggling() + public void TestGridTypeToggling() { AddStep("enable rectangular grid", () => InputManager.Key(Key.Y)); AddUntilStep("rectangular grid visible", () => this.ChildrenOfType().Any()); - gridSizeIs(4); + gridActive(true); - nextGridSizeIs(8); - nextGridSizeIs(16); - nextGridSizeIs(32); - nextGridSizeIs(4); + nextGridTypeIs(); + nextGridTypeIs(); + nextGridTypeIs(); } - private void nextGridSizeIs(int size) + private void nextGridTypeIs() where T : PositionSnapGrid { - AddStep("toggle to next grid size", () => InputManager.Key(Key.G)); - gridSizeIs(size); + AddStep("toggle to next grid type", () => InputManager.Key(Key.G)); + gridActive(true); } - - private void gridSizeIs(int size) - => AddAssert($"grid size is {size}", () => this.ChildrenOfType().Single().Spacing.Value == new Vector2(size) - && EditorBeatmap.BeatmapInfo.GridSize == size); } } From 594b6fe1672ddd4983e06986496519a6661c2352 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 21:57:11 +0100 Subject: [PATCH 2282/4092] Add back the old keybind for cycling grid spacing --- .../Editor/TestSceneOsuEditorGrids.cs | 25 ++++++++++++++++++- .../Edit/OsuGridToolboxGroup.cs | 10 ++++---- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index baeb0639d5..5636bb51b9 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -107,6 +107,29 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor }; } + [Test] + public void TestGridSizeToggling() + { + AddStep("enable rectangular grid", () => InputManager.Key(Key.Y)); + AddUntilStep("rectangular grid visible", () => this.ChildrenOfType().Any()); + gridSizeIs(4); + + nextGridSizeIs(8); + nextGridSizeIs(16); + nextGridSizeIs(32); + nextGridSizeIs(4); + } + + private void nextGridSizeIs(int size) + { + AddStep("toggle to next grid size", () => InputManager.Key(Key.G)); + gridSizeIs(size); + } + + private void gridSizeIs(int size) + => AddAssert($"grid size is {size}", () => this.ChildrenOfType().Single().Spacing.Value == new Vector2(size) + && EditorBeatmap.BeatmapInfo.GridSize == size); + [Test] public void TestGridTypeToggling() { @@ -121,7 +144,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private void nextGridTypeIs() where T : PositionSnapGrid { - AddStep("toggle to next grid type", () => InputManager.Key(Key.G)); + AddStep("toggle to next grid type", () => InputManager.Key(Key.H)); gridActive(true); } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 981148858d..237ccf3e58 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -100,6 +100,8 @@ namespace osu.Game.Rulesets.Osu.Edit { } + private const float max_automatic_spacing = 64; + [BackgroundDependencyLoader] private void load() { @@ -196,11 +198,9 @@ namespace osu.Game.Rulesets.Osu.Edit }, true); } - private void nextGridType() + private void nextGridSize() { - currentGridTypeIndex = (currentGridTypeIndex + 1) % grid_types.Length; - GridType.Value = grid_types[currentGridTypeIndex]; - gridTypeButtons.Items[currentGridTypeIndex].Select(); + Spacing.Value = Spacing.Value * 2 >= max_automatic_spacing ? Spacing.Value / 8 : Spacing.Value * 2; } public bool OnPressed(KeyBindingPressEvent e) @@ -208,7 +208,7 @@ namespace osu.Game.Rulesets.Osu.Edit switch (e.Action) { case GlobalAction.EditorCycleGridDisplayMode: - nextGridType(); + nextGridSize(); return true; } From 39f4a1aa8e19a6570c24bef2a71e0c078e7a1049 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 1 Jan 2024 15:34:05 +0100 Subject: [PATCH 2283/4092] conflict fixes --- .../Editor/TestSceneOsuEditorGrids.cs | 18 ------------------ .../Edit/OsuGridToolboxGroup.cs | 5 ----- 2 files changed, 23 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index 5636bb51b9..21427ba281 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -129,23 +129,5 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private void gridSizeIs(int size) => AddAssert($"grid size is {size}", () => this.ChildrenOfType().Single().Spacing.Value == new Vector2(size) && EditorBeatmap.BeatmapInfo.GridSize == size); - - [Test] - public void TestGridTypeToggling() - { - AddStep("enable rectangular grid", () => InputManager.Key(Key.Y)); - AddUntilStep("rectangular grid visible", () => this.ChildrenOfType().Any()); - gridActive(true); - - nextGridTypeIs(); - nextGridTypeIs(); - nextGridTypeIs(); - } - - private void nextGridTypeIs() where T : PositionSnapGrid - { - AddStep("toggle to next grid type", () => InputManager.Key(Key.H)); - gridActive(true); - } } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 237ccf3e58..76e735449a 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.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.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -25,10 +24,6 @@ namespace osu.Game.Rulesets.Osu.Edit { public partial class OsuGridToolboxGroup : EditorToolboxGroup, IKeyBindingHandler { - private static readonly PositionSnapGridType[] grid_types = Enum.GetValues(typeof(PositionSnapGridType)).Cast().ToArray(); - - private int currentGridTypeIndex; - [Resolved] private EditorBeatmap editorBeatmap { get; set; } = null!; From de14da95fa6d0230af1aeef7e9b0afd5caaa059e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 1 Jan 2024 15:44:20 +0100 Subject: [PATCH 2284/4092] Remove other grid types --- .../Editor/TestSceneOsuEditorGrids.cs | 3 - .../Edit/OsuGridToolboxGroup.cs | 79 -------------- .../Edit/OsuHitObjectComposer.cs | 41 ++----- .../Editing/TestScenePositionSnapGrid.cs | 45 -------- .../Components/CircularPositionSnapGrid.cs | 101 ------------------ .../Components/TriangularPositionSnapGrid.cs | 89 --------------- 6 files changed, 7 insertions(+), 351 deletions(-) delete mode 100644 osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs delete mode 100644 osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index 21427ba281..7cafd10454 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.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.Linq; using NUnit.Framework; using osu.Framework.Testing; @@ -101,8 +100,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor return grid switch { RectangularPositionSnapGrid rectangular => rectangular.StartPosition.Value + GeometryUtils.RotateVector(rectangular.Spacing.Value, -rectangular.GridLineRotation.Value), - TriangularPositionSnapGrid triangular => triangular.StartPosition.Value + GeometryUtils.RotateVector(new Vector2(triangular.Spacing.Value / 2, triangular.Spacing.Value / 2 * MathF.Sqrt(3)), -triangular.GridLineRotation.Value), - CircularPositionSnapGrid circular => circular.StartPosition.Value + GeometryUtils.RotateVector(new Vector2(circular.Spacing.Value, 0), -45), _ => Vector2.Zero }; } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 76e735449a..e82ca780ad 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -1,13 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; using osu.Framework.Allocation; 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.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; @@ -16,9 +12,7 @@ using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit; -using osu.Game.Screens.Edit.Components.RadioButtons; using osuTK; -using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Edit { @@ -82,13 +76,10 @@ namespace osu.Game.Rulesets.Osu.Edit /// public Bindable SpacingVector { get; } = new Bindable(); - public Bindable GridType { get; } = new Bindable(); - private ExpandableSlider startPositionXSlider = null!; private ExpandableSlider startPositionYSlider = null!; private ExpandableSlider spacingSlider = null!; private ExpandableSlider gridLinesRotationSlider = null!; - private EditorRadioButtonCollection gridTypeButtons = null!; public OsuGridToolboxGroup() : base("grid") @@ -122,31 +113,6 @@ namespace osu.Game.Rulesets.Osu.Edit Current = GridLinesRotation, KeyboardStep = 1, }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0f, 10f), - Children = new Drawable[] - { - gridTypeButtons = new EditorRadioButtonCollection - { - RelativeSizeAxes = Axes.X, - Items = new[] - { - new RadioButton("Square", - () => GridType.Value = PositionSnapGridType.Square, - () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), - new RadioButton("Triangle", - () => GridType.Value = PositionSnapGridType.Triangle, - () => new OutlineTriangle(true, 20)), - new RadioButton("Circle", - () => GridType.Value = PositionSnapGridType.Circle, - () => new SpriteIcon { Icon = FontAwesome.Regular.Circle }), - } - }, - } - }, }; Spacing.Value = editorBeatmap.BeatmapInfo.GridSize; @@ -156,8 +122,6 @@ namespace osu.Game.Rulesets.Osu.Edit { base.LoadComplete(); - gridTypeButtons.Items.First().Select(); - StartPositionX.BindValueChanged(x => { startPositionXSlider.ContractedLabelText = $"X: {x.NewValue:N0}"; @@ -185,12 +149,6 @@ namespace osu.Game.Rulesets.Osu.Edit gridLinesRotationSlider.ContractedLabelText = $"R: {rotation.NewValue:#,0.##}"; gridLinesRotationSlider.ExpandedLabelText = $"Rotation: {rotation.NewValue:#,0.##}"; }, true); - - expandingContainer?.Expanded.BindValueChanged(v => - { - gridTypeButtons.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint); - gridTypeButtons.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None; - }, true); } private void nextGridSize() @@ -213,42 +171,5 @@ namespace osu.Game.Rulesets.Osu.Edit public void OnReleased(KeyBindingReleaseEvent e) { } - - public partial class OutlineTriangle : BufferedContainer - { - public OutlineTriangle(bool outlineOnly, float size) - : base(cachedFrameBuffer: true) - { - Size = new Vector2(size); - - InternalChildren = new Drawable[] - { - new EquilateralTriangle { RelativeSizeAxes = Axes.Both }, - }; - - if (outlineOnly) - { - AddInternal(new EquilateralTriangle - { - Anchor = Anchor.TopCentre, - Origin = Anchor.Centre, - RelativePositionAxes = Axes.Y, - Y = 0.48f, - Colour = Color4.Black, - Size = new Vector2(size - 7), - Blending = BlendingParameters.None, - }); - } - - Blending = BlendingParameters.Additive; - } - } - } - - public enum PositionSnapGridType - { - Square, - Triangle, - Circle, } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 84d5adbc52..51bb74926f 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Edit // we may be entering the screen with a selection already active updateDistanceSnapGrid(); - OsuGridToolboxGroup.GridType.BindValueChanged(updatePositionSnapGrid, true); + updatePositionSnapGrid(); RightToolbox.AddRange(new EditorToolboxGroup[] { @@ -110,45 +110,18 @@ namespace osu.Game.Rulesets.Osu.Edit ); } - private void updatePositionSnapGrid(ValueChangedEvent obj) + private void updatePositionSnapGrid() { if (positionSnapGrid != null) LayerBelowRuleset.Remove(positionSnapGrid, true); - switch (obj.NewValue) - { - case PositionSnapGridType.Square: - var rectangularPositionSnapGrid = new RectangularPositionSnapGrid(); + var rectangularPositionSnapGrid = new RectangularPositionSnapGrid(); - rectangularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.SpacingVector); - rectangularPositionSnapGrid.GridLineRotation.BindTo(OsuGridToolboxGroup.GridLinesRotation); + rectangularPositionSnapGrid.StartPosition.BindTo(OsuGridToolboxGroup.StartPosition); + rectangularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.SpacingVector); + rectangularPositionSnapGrid.GridLineRotation.BindTo(OsuGridToolboxGroup.GridLinesRotation); - positionSnapGrid = rectangularPositionSnapGrid; - break; - - case PositionSnapGridType.Triangle: - var triangularPositionSnapGrid = new TriangularPositionSnapGrid(); - - triangularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.Spacing); - triangularPositionSnapGrid.GridLineRotation.BindTo(OsuGridToolboxGroup.GridLinesRotation); - - positionSnapGrid = triangularPositionSnapGrid; - break; - - case PositionSnapGridType.Circle: - var circularPositionSnapGrid = new CircularPositionSnapGrid(); - - circularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.Spacing); - - positionSnapGrid = circularPositionSnapGrid; - break; - - default: - throw new NotImplementedException($"{OsuGridToolboxGroup.GridType} has an incorrect value."); - } - - // Bind the start position to the toolbox sliders. - positionSnapGrid.StartPosition.BindTo(OsuGridToolboxGroup.StartPosition); + positionSnapGrid = rectangularPositionSnapGrid; positionSnapGrid.RelativeSizeAxes = Axes.Both; LayerBelowRuleset.Add(positionSnapGrid); diff --git a/osu.Game.Tests/Visual/Editing/TestScenePositionSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestScenePositionSnapGrid.cs index 7e66edc2dd..2721bc3602 100644 --- a/osu.Game.Tests/Visual/Editing/TestScenePositionSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestScenePositionSnapGrid.cs @@ -70,51 +70,6 @@ namespace osu.Game.Tests.Visual.Editing })); } - [TestCaseSource(nameof(test_cases))] - public void TestTriangularGrid(Vector2 position, Vector2 spacing, float rotation) - { - TriangularPositionSnapGrid grid = null; - - AddStep("create grid", () => - { - Child = grid = new TriangularPositionSnapGrid - { - RelativeSizeAxes = Axes.Both, - }; - grid.StartPosition.Value = position; - grid.Spacing.Value = spacing.X; - grid.GridLineRotation.Value = rotation; - }); - - AddStep("add snapping cursor", () => Add(new SnappingCursorContainer - { - RelativeSizeAxes = Axes.Both, - GetSnapPosition = pos => grid.GetSnappedPosition(grid.ToLocalSpace(pos)) - })); - } - - [TestCaseSource(nameof(test_cases))] - public void TestCircularGrid(Vector2 position, Vector2 spacing, float rotation) - { - CircularPositionSnapGrid grid = null; - - AddStep("create grid", () => - { - Child = grid = new CircularPositionSnapGrid - { - RelativeSizeAxes = Axes.Both, - }; - grid.StartPosition.Value = position; - grid.Spacing.Value = spacing.X; - }); - - AddStep("add snapping cursor", () => Add(new SnappingCursorContainer - { - RelativeSizeAxes = Axes.Both, - GetSnapPosition = pos => grid.GetSnappedPosition(grid.ToLocalSpace(pos)) - })); - } - private partial class SnappingCursorContainer : CompositeDrawable { public Func GetSnapPosition; diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs deleted file mode 100644 index 403a270359..0000000000 --- a/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs +++ /dev/null @@ -1,101 +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 System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Utils; -using osuTK; - -namespace osu.Game.Screens.Edit.Compose.Components -{ - public partial class CircularPositionSnapGrid : PositionSnapGrid - { - /// - /// The spacing between grid lines of this . - /// - public BindableFloat Spacing { get; } = new BindableFloat(1f) - { - MinValue = 0f, - }; - - public CircularPositionSnapGrid() - { - Spacing.BindValueChanged(_ => GridCache.Invalidate()); - } - - protected override void CreateContent() - { - var drawSize = DrawSize; - - // Calculate the maximum distance from the origin to the edge of the grid. - float maxDist = MathF.Max( - MathF.Max(StartPosition.Value.Length, (StartPosition.Value - drawSize).Length), - MathF.Max((StartPosition.Value - new Vector2(drawSize.X, 0)).Length, (StartPosition.Value - new Vector2(0, drawSize.Y)).Length) - ); - - generateCircles((int)(maxDist / Spacing.Value) + 1); - - GenerateOutline(drawSize); - } - - private void generateCircles(int count) - { - // Make lines the same width independent of display resolution. - float lineWidth = 2 * DrawWidth / ScreenSpaceDrawQuad.Width; - - List generatedCircles = new List(); - - for (int i = 0; i < count; i++) - { - // Add a minimum diameter so the center circle is clearly visible. - float diameter = MathF.Max(lineWidth * 1.5f, i * Spacing.Value * 2); - - var gridCircle = new CircularContainer - { - BorderColour = Colour4.White, - BorderThickness = lineWidth, - Alpha = 0.2f, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.None, - Width = diameter, - Height = diameter, - Position = StartPosition.Value, - Masking = true, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - AlwaysPresent = true, - Alpha = 0f, - } - }; - - generatedCircles.Add(gridCircle); - } - - if (generatedCircles.Count == 0) - return; - - generatedCircles.First().Alpha = 0.8f; - - AddRangeInternal(generatedCircles); - } - - public override Vector2 GetSnappedPosition(Vector2 original) - { - Vector2 relativeToStart = original - StartPosition.Value; - - if (relativeToStart.LengthSquared < Precision.FLOAT_EPSILON) - return StartPosition.Value; - - float length = relativeToStart.Length; - float wantedLength = MathF.Round(length / Spacing.Value) * Spacing.Value; - - return StartPosition.Value + Vector2.Multiply(relativeToStart, wantedLength / length); - } - } -} diff --git a/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs deleted file mode 100644 index 93d2c6a74a..0000000000 --- a/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs +++ /dev/null @@ -1,89 +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 System; -using osu.Framework.Bindables; -using osu.Game.Utils; -using osuTK; - -namespace osu.Game.Screens.Edit.Compose.Components -{ - public partial class TriangularPositionSnapGrid : LinedPositionSnapGrid - { - /// - /// The spacing between grid lines of this . - /// - public BindableFloat Spacing { get; } = new BindableFloat(1f) - { - MinValue = 0f, - }; - - /// - /// The rotation in degrees of the grid lines of this . - /// - public BindableFloat GridLineRotation { get; } = new BindableFloat(); - - public TriangularPositionSnapGrid() - { - Spacing.BindValueChanged(_ => GridCache.Invalidate()); - GridLineRotation.BindValueChanged(_ => GridCache.Invalidate()); - } - - private const float sqrt3 = 1.73205080757f; - private const float sqrt3_over2 = 0.86602540378f; - private const float one_over_sqrt3 = 0.57735026919f; - - protected override void CreateContent() - { - var drawSize = DrawSize; - float stepSpacing = Spacing.Value * sqrt3_over2; - var step1 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation.Value - 30); - var step2 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation.Value - 90); - var step3 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation.Value - 150); - - GenerateGridLines(step1, drawSize); - GenerateGridLines(-step1, drawSize); - - GenerateGridLines(step2, drawSize); - GenerateGridLines(-step2, drawSize); - - GenerateGridLines(step3, drawSize); - GenerateGridLines(-step3, drawSize); - - GenerateOutline(drawSize); - } - - public override Vector2 GetSnappedPosition(Vector2 original) - { - Vector2 relativeToStart = GeometryUtils.RotateVector(original - StartPosition.Value, GridLineRotation.Value); - Vector2 hex = pixelToHex(relativeToStart); - - return StartPosition.Value + GeometryUtils.RotateVector(hexToPixel(hex), -GridLineRotation.Value); - } - - private Vector2 pixelToHex(Vector2 pixel) - { - float x = pixel.X / Spacing.Value; - float y = pixel.Y / Spacing.Value; - // Algorithm from Charles Chambers - // with modifications and comments by Chris Cox 2023 - // - float t = sqrt3 * y + 1; // scaled y, plus phase - float temp1 = MathF.Floor(t + x); // (y+x) diagonal, this calc needs floor - float temp2 = t - x; // (y-x) diagonal, no floor needed - float temp3 = 2 * x + 1; // scaled horizontal, no floor needed, needs +1 to get correct phase - float qf = (temp1 + temp3) / 3.0f; // pseudo x with fraction - float rf = (temp1 + temp2) / 3.0f; // pseudo y with fraction - float q = MathF.Floor(qf); // pseudo x, quantized and thus requires floor - float r = MathF.Floor(rf); // pseudo y, quantized and thus requires floor - return new Vector2(q, r); - } - - private Vector2 hexToPixel(Vector2 hex) - { - // Taken from - // with modifications for the different definition of size. - return new Vector2(Spacing.Value * (hex.X - hex.Y / 2), Spacing.Value * one_over_sqrt3 * 1.5f * hex.Y); - } - } -} From 460c584dca79ec4dc40df0f49e6721edcb6e6fa9 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 1 Jan 2024 16:21:33 +0100 Subject: [PATCH 2285/4092] fix code quality --- osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index e82ca780ad..21cce553b1 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -6,7 +6,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; @@ -21,9 +20,6 @@ namespace osu.Game.Rulesets.Osu.Edit [Resolved] private EditorBeatmap editorBeatmap { get; set; } = null!; - [Resolved] - private IExpandingContainer? expandingContainer { get; set; } - /// /// X position of the grid's origin. /// From 94531807e476c774c42df3ef0ef745608eb80534 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Jan 2024 17:01:32 +0900 Subject: [PATCH 2286/4092] Make slider ends show on results screen again --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 ++ osu.Game/Scoring/ScoreInfo.cs | 1 + 2 files changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 5959576b9d..0496d1f680 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -277,6 +277,7 @@ namespace osu.Game.Rulesets.Osu HitResult.LargeTickHit, HitResult.SmallTickHit, + HitResult.SliderTailHit, HitResult.SmallBonus, HitResult.LargeBonus, }; @@ -289,6 +290,7 @@ namespace osu.Game.Rulesets.Osu case HitResult.LargeTickHit: return "slider tick"; + case HitResult.SliderTailHit: case HitResult.SmallTickHit: return "slider end"; diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 44795c6fa7..32e4bbbf29 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -350,6 +350,7 @@ namespace osu.Game.Scoring { case HitResult.SmallTickHit: case HitResult.LargeTickHit: + case HitResult.SliderTailHit: case HitResult.LargeBonus: case HitResult.SmallBonus: if (MaximumStatistics.TryGetValue(r.result, out int count) && count > 0) From b0cfea491675a012694576168396ba1c433133e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 2 Jan 2024 10:11:49 +0100 Subject: [PATCH 2287/4092] Fix standardised score conversion failing for some taiko scores due to overestimating accuracy portion Standardised score conversion would return a negative total score for https://osu.ppy.sh/scores/taiko/182230346. The underlying reason for this is that the estimation of the accuracy portion for a score can be above the actual accuracy portion in the taiko ruleset. When calculating the maximum accuracy portion achievable, `TaikoLegacyScoreSimulator` will include the extra 300 points from a double hit on a strong hit, per strong hit. However, this double hit is not factored into accuracy. Both of the aforementioned facts mean that in taiko maximumLegacyAccuracyScore * score.Accuracy - which normally in other rulesets can be used pretty reliably as the exact number of points gained from the accuracy portion - is an estimate in the case of taiko, and an _upper_ estimate at that, because it implicitly assumes that the user has also hit `score.Accuracy` percent of all double hits possible in the beatmap. If this assumption is not upheld, then the user will have earned _less_ points than that from the accuracy portion, which means that the combo proportion estimate will go below zero. It is possible that this has happened on other scores before, but did not result in the total score going negative as the accuracy portion gained would have counteracted the effect of that due to being larger in magnitude than the score loss incurred from the negative combo portion. In the case of the score in question this was not the case due to very low accuracy _and_ very low max combo. --- osu.Game/Database/StandardisedScoreMigrationTools.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index 66c816e796..9cfb9ea957 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -316,7 +316,7 @@ namespace osu.Game.Database // when playing a beatmap with no bonus objects, with mods that have a 0.0x multiplier on stable (relax/autopilot). // In such cases, just assume 0. double comboProportion = maximumLegacyComboScore + maximumLegacyBonusScore > 0 - ? ((double)score.LegacyTotalScore - legacyAccScore) / (maximumLegacyComboScore + maximumLegacyBonusScore) + ? Math.Max((double)score.LegacyTotalScore - legacyAccScore, 0) / (maximumLegacyComboScore + maximumLegacyBonusScore) : 0; // We assume the bonus proportion only makes up the rest of the score that exceeds maximumLegacyBaseScore. From 3c5e9ac9a9cfc224905f8f8c6da66ed143cc7e54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 2 Jan 2024 10:49:09 +0100 Subject: [PATCH 2288/4092] Fix possible double score submission when auto-retrying via perfect mod Closes https://github.com/ppy/osu/issues/26035. `submitOnFailOrQuit()`, as the name suggests, can be called both when the player has failed, or when the player screen is being exited from. Notably, when perfect mod with auto-retry is active, the two happen almost simultaneously. This double call exposes a data race in `submitScore()` concerning the handling of `scoreSubmissionSource`. The race could be experimentally confirmed by applying the following patch: diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 83adf1f960..76dd29bbdb 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -228,6 +228,7 @@ private Task submitScore(Score score) return Task.CompletedTask; } + Logger.Log($"{nameof(scoreSubmissionSource)} is {(scoreSubmissionSource == null ? "null" : "not null")}"); if (scoreSubmissionSource != null) return scoreSubmissionSource.Task; @@ -237,6 +238,7 @@ private Task submitScore(Score score) Logger.Log($"Beginning score submission (token:{token.Value})..."); + Logger.Log($"creating new {nameof(scoreSubmissionSource)}"); scoreSubmissionSource = new TaskCompletionSource(); var request = CreateSubmissionRequest(score, token.Value); which would result in the following log output: [runtime] 2024-01-02 09:54:13 [verbose]: scoreSubmissionSource is null [runtime] 2024-01-02 09:54:13 [verbose]: scoreSubmissionSource is null [runtime] 2024-01-02 09:54:13 [verbose]: Beginning score submission (token:36780)... [runtime] 2024-01-02 09:54:13 [verbose]: creating new scoreSubmissionSource [runtime] 2024-01-02 09:54:13 [verbose]: Beginning score submission (token:36780)... [runtime] 2024-01-02 09:54:13 [verbose]: creating new scoreSubmissionSource [network] 2024-01-02 09:54:13 [verbose]: Performing request osu.Game.Online.Solo.SubmitSoloScoreRequest [network] 2024-01-02 09:54:14 [verbose]: Request to https://dev.ppy.sh/api/v2/beatmaps/869310/solo/scores/36780 successfully completed! [network] 2024-01-02 09:54:14 [verbose]: SubmitSoloScoreRequest finished with response size of 639 bytes [network] 2024-01-02 09:54:14 [verbose]: Performing request osu.Game.Online.Solo.SubmitSoloScoreRequest [runtime] 2024-01-02 09:54:14 [verbose]: Score submission completed! (token:36780 id:20247) [network] 2024-01-02 09:54:14 [verbose]: Request to https://dev.ppy.sh/api/v2/beatmaps/869310/solo/scores/36780 successfully completed! [network] 2024-01-02 09:54:14 [verbose]: SubmitSoloScoreRequest finished with response size of 639 bytes [runtime] 2024-01-02 09:54:14 [error]: An unhandled error has occurred. [runtime] 2024-01-02 09:54:14 [error]: System.InvalidOperationException: An attempt was made to transition a task to a final state when it had already completed. [runtime] 2024-01-02 09:54:14 [error]: at osu.Game.Screens.Play.SubmittingPlayer.<>c__DisplayClass30_0.b__0(MultiplayerScore s) in /home/dachb/Documents/opensource/osu/osu.Game/Screens/Play/SubmittingPlayer.cs:line 250 The intention of the submission logic was to only ever create one `scoreSubmissionSource`, and then reuse this one if a redundant submission request was made. However, because of the temporal proximity of fail and quit in this particular case, combined with the fact that the calls to `submitScore()` are taking place on TPL threads, means that there is a read-write data race on `scoreSubmissionSource`, wherein the source can be actually created twice. This leads to two concurrent score submission requests, which, upon completion, attempt to transition only _the second_ `scoreSubmissionSource` to a final state (this is because the API success/failure request callbacks capture `this`, i.e. the entire `SubmittingPlayer` instance, rather than the `scoreSubmissionSource` reference specifically). To fix, ensure correct synchronisation on the read-write critical section, which should prevent the `scoreSubmissionSource` from being created multiple times. --- osu.Game/Screens/Play/SubmittingPlayer.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 83adf1f960..fb3481cbc4 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -41,6 +41,7 @@ namespace osu.Game.Screens.Play [Resolved] private SessionStatics statics { get; set; } + private readonly object scoreSubmissionLock = new object(); private TaskCompletionSource scoreSubmissionSource; protected SubmittingPlayer(PlayerConfiguration configuration = null) @@ -228,16 +229,19 @@ namespace osu.Game.Screens.Play return Task.CompletedTask; } - if (scoreSubmissionSource != null) - return scoreSubmissionSource.Task; + lock (scoreSubmissionLock) + { + if (scoreSubmissionSource != null) + return scoreSubmissionSource.Task; + + scoreSubmissionSource = new TaskCompletionSource(); + } // if the user never hit anything, this score should not be counted in any way. if (!score.ScoreInfo.Statistics.Any(s => s.Key.IsHit() && s.Value > 0)) return Task.CompletedTask; Logger.Log($"Beginning score submission (token:{token.Value})..."); - - scoreSubmissionSource = new TaskCompletionSource(); var request = CreateSubmissionRequest(score, token.Value); request.Success += s => From 72e502e6156b58474c8a6c9517b6f4c73ad420b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 2 Jan 2024 11:55:59 +0100 Subject: [PATCH 2289/4092] Fix backwards z-ordering of fruits in juice streams and banana showers Closes https://github.com/ppy/osu/issues/25827. The logic cannot be easily abstracted out (because `CompareReverseChildID()` is protected and non-static on `CompositeDrawable`), so a local copy was applied instead. No testing as any testing would have been purely visual anyways. --- .../Objects/Drawables/DrawableBananaShower.cs | 2 +- .../Objects/Drawables/DrawableJuiceStream.cs | 2 +- .../Objects/Drawables/NestedFruitContainer.cs | 26 +++++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Rulesets.Catch/Objects/Drawables/NestedFruitContainer.cs diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBananaShower.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBananaShower.cs index 03adbce885..9ee4a15182 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBananaShower.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables RelativeSizeAxes = Axes.X; Origin = Anchor.BottomLeft; - AddInternal(bananaContainer = new Container { RelativeSizeAxes = Axes.Both }); + AddInternal(bananaContainer = new NestedFruitContainer { RelativeSizeAxes = Axes.Both }); } protected override void AddNestedHitObject(DrawableHitObject hitObject) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs index 41ecf59276..677b61df47 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables RelativeSizeAxes = Axes.X; Origin = Anchor.BottomLeft; - AddInternal(dropletContainer = new Container { RelativeSizeAxes = Axes.Both, }); + AddInternal(dropletContainer = new NestedFruitContainer { RelativeSizeAxes = Axes.Both, }); } protected override void AddNestedHitObject(DrawableHitObject hitObject) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/NestedFruitContainer.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/NestedFruitContainer.cs new file mode 100644 index 0000000000..90bdb0237e --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/NestedFruitContainer.cs @@ -0,0 +1,26 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Catch.Objects.Drawables +{ + public partial class NestedFruitContainer : Container + { + /// + /// This comparison logic is a copy of comparison logic, + /// which can't be easily extracted to a more common place. + /// + /// + protected override int Compare(Drawable x, Drawable y) + { + if (x is not DrawableCatchHitObject xObj || y is not DrawableCatchHitObject yObj) + return base.Compare(x, y); + + int result = yObj.HitObject.StartTime.CompareTo(xObj.HitObject.StartTime); + return result == 0 ? CompareReverseChildID(x, y) : result; + } + } +} From f9f03ebc0f066dc8d7c23764fc3d88e4d6278bc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 2 Jan 2024 14:04:40 +0100 Subject: [PATCH 2290/4092] Store user online state in config for next launch Closes remainder of https://github.com/ppy/osu/issues/12635. --- osu.Game/Configuration/OsuConfigManager.cs | 3 +++ osu.Game/Online/API/APIAccess.cs | 15 +++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index d4162b76d6..23686db1f8 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -20,6 +20,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Filter; using osu.Game.Skinning; +using osu.Game.Users; namespace osu.Game.Configuration { @@ -193,6 +194,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.LastProcessedMetadataId, -1); SetDefault(OsuSetting.ComboColourNormalisationAmount, 0.2f, 0f, 1f, 0.01f); + SetDefault(OsuSetting.UserOnlineStatus, null); } protected override bool CheckLookupContainsPrivateInformation(OsuSetting lookup) @@ -420,5 +422,6 @@ namespace osu.Game.Configuration EditorShowSpeedChanges, TouchDisableGameplayTaps, ModSelectTextSearchStartsActive, + UserOnlineStatus, } } diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 21107d61fc..be5bdeca77 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -62,6 +62,9 @@ namespace osu.Game.Online.API private Bindable activity { get; } = new Bindable(); + private Bindable configStatus { get; } = new Bindable(); + private Bindable localUserStatus { get; } = new Bindable(); + protected bool HasLogin => authentication.Token.Value != null || (!string.IsNullOrEmpty(ProvidedUsername) && !string.IsNullOrEmpty(password)); private readonly CancellationTokenSource cancellationToken = new CancellationTokenSource(); @@ -85,12 +88,20 @@ namespace osu.Game.Online.API authentication.TokenString = config.Get(OsuSetting.Token); authentication.Token.ValueChanged += onTokenChanged; + config.BindWith(OsuSetting.UserOnlineStatus, configStatus); + localUser.BindValueChanged(u => { u.OldValue?.Activity.UnbindFrom(activity); u.NewValue.Activity.BindTo(activity); + + if (u.OldValue != null) + localUserStatus.UnbindFrom(u.OldValue.Status); + localUserStatus.BindTo(u.NewValue.Status); }, true); + localUserStatus.BindValueChanged(val => configStatus.Value = val.NewValue); + var thread = new Thread(run) { Name = "APIAccess", @@ -200,6 +211,7 @@ namespace osu.Game.Online.API setLocalUser(new APIUser { Username = ProvidedUsername, + Status = { Value = configStatus.Value ?? UserStatus.Online } }); } @@ -246,8 +258,7 @@ namespace osu.Game.Online.API }; userReq.Success += user => { - // todo: save/pull from settings - user.Status.Value = UserStatus.Online; + user.Status.Value = configStatus.Value ?? UserStatus.Online; setLocalUser(user); From d4e917448d98e6409982719231f8b53e4340e11a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 2 Jan 2024 14:07:04 +0100 Subject: [PATCH 2291/4092] Fix login panel dropdown forcing user online It was sort of assuming that the user can't be anything but online when opening, thus forcing the status to online via the immediately-run value change callback. --- osu.Game/Overlays/Login/LoginPanel.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/osu.Game/Overlays/Login/LoginPanel.cs b/osu.Game/Overlays/Login/LoginPanel.cs index 19af95459f..b0af2fa273 100644 --- a/osu.Game/Overlays/Login/LoginPanel.cs +++ b/osu.Game/Overlays/Login/LoginPanel.cs @@ -143,6 +143,8 @@ namespace osu.Game.Overlays.Login panel.Status.BindTo(api.LocalUser.Value.Status); panel.Activity.BindTo(api.LocalUser.Value.Activity); + panel.Status.BindValueChanged(_ => updateDropdownCurrent(), true); + dropdown.Current.BindValueChanged(action => { switch (action.NewValue) @@ -174,6 +176,24 @@ namespace osu.Game.Overlays.Login ScheduleAfterChildren(() => GetContainingInputManager()?.ChangeFocus(form)); }); + private void updateDropdownCurrent() + { + switch (panel.Status.Value) + { + case UserStatus.Online: + dropdown.Current.Value = UserAction.Online; + break; + + case UserStatus.DoNotDisturb: + dropdown.Current.Value = UserAction.DoNotDisturb; + break; + + case UserStatus.Offline: + dropdown.Current.Value = UserAction.AppearOffline; + break; + } + } + public override bool AcceptsFocus => true; protected override bool OnClick(ClickEvent e) => true; From 09b2a4e3b42defa1ead3a156c2f8c890ed175473 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 2 Jan 2024 14:07:59 +0100 Subject: [PATCH 2292/4092] Fix users blipping online briefly before their online status is known --- osu.Game/Online/Metadata/OnlineMetadataClient.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Online/Metadata/OnlineMetadataClient.cs b/osu.Game/Online/Metadata/OnlineMetadataClient.cs index 6d00ce7551..c42c3378b7 100644 --- a/osu.Game/Online/Metadata/OnlineMetadataClient.cs +++ b/osu.Game/Online/Metadata/OnlineMetadataClient.cs @@ -23,7 +23,6 @@ namespace osu.Game.Online.Metadata public override IBindable IsWatchingUserPresence => isWatchingUserPresence; private readonly BindableBool isWatchingUserPresence = new BindableBool(); - // ReSharper disable once InconsistentlySynchronizedField public override IBindableDictionary UserStates => userStates; private readonly BindableDictionary userStates = new BindableDictionary(); @@ -192,7 +191,7 @@ namespace osu.Game.Online.Metadata { Schedule(() => { - if (presence != null) + if (presence?.Status != null) userStates[userId] = presence.Value; else userStates.Remove(userId); From 5eaf5fca2ad8d162835212151b8c1c530f7ca0f1 Mon Sep 17 00:00:00 2001 From: StanR Date: Tue, 2 Jan 2024 20:33:36 +0600 Subject: [PATCH 2293/4092] Add user card with global/country ranks for login overlay --- osu.Game/Overlays/Login/LoginPanel.cs | 7 +- osu.Game/Users/ExtendedUserPanel.cs | 9 -- osu.Game/Users/UserPanel.cs | 41 +++-- osu.Game/Users/UserRankPanel.cs | 212 ++++++++++++++++++++++++++ 4 files changed, 241 insertions(+), 28 deletions(-) create mode 100644 osu.Game/Users/UserRankPanel.cs diff --git a/osu.Game/Overlays/Login/LoginPanel.cs b/osu.Game/Overlays/Login/LoginPanel.cs index 19af95459f..892d613ea4 100644 --- a/osu.Game/Overlays/Login/LoginPanel.cs +++ b/osu.Game/Overlays/Login/LoginPanel.cs @@ -30,7 +30,7 @@ namespace osu.Game.Overlays.Login [Resolved] private OsuColour colours { get; set; } = null!; - private UserGridPanel panel = null!; + private UserRankPanel panel = null!; private UserDropdown dropdown = null!; /// @@ -131,7 +131,7 @@ namespace osu.Game.Overlays.Login Text = LoginPanelStrings.SignedIn, Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold), }, - panel = new UserGridPanel(api.LocalUser.Value) + panel = new UserRankPanel(api.LocalUser.Value) { RelativeSizeAxes = Axes.X, Action = RequestHide @@ -140,9 +140,6 @@ namespace osu.Game.Overlays.Login }, }; - panel.Status.BindTo(api.LocalUser.Value.Status); - panel.Activity.BindTo(api.LocalUser.Value.Activity); - dropdown.Current.BindValueChanged(action => { switch (action.NewValue) diff --git a/osu.Game/Users/ExtendedUserPanel.cs b/osu.Game/Users/ExtendedUserPanel.cs index 1359f5d792..e33fb7a44e 100644 --- a/osu.Game/Users/ExtendedUserPanel.cs +++ b/osu.Game/Users/ExtendedUserPanel.cs @@ -3,7 +3,6 @@ #nullable disable -using osuTK; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; @@ -53,14 +52,6 @@ namespace osu.Game.Users statusIcon.FinishTransforms(); } - protected UpdateableAvatar CreateAvatar() => new UpdateableAvatar(User, false); - - protected UpdateableFlag CreateFlag() => new UpdateableFlag(User.CountryCode) - { - Size = new Vector2(36, 26), - Action = Action, - }; - protected Container CreateStatusIcon() => statusIcon = new StatusIcon(); protected FillFlowContainer CreateStatusMessage(bool rightAlignedChildren) diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index b6a77e754d..d38f5d8ccc 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -23,6 +23,8 @@ using osu.Game.Localisation; using osu.Game.Online.Multiplayer; using osu.Game.Screens; using osu.Game.Screens.Play; +using osu.Game.Users.Drawables; +using osuTK; namespace osu.Game.Users { @@ -77,23 +79,18 @@ namespace osu.Game.Users { Masking = true; - AddRange(new[] + Add(new Box { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourProvider?.Background5 ?? Colours.Gray1 - }, - Background = new UserCoverBackground - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - User = User, - }, - CreateLayout() + RelativeSizeAxes = Axes.Both, + Colour = ColourProvider?.Background5 ?? Colours.Gray1 }); + var background = CreateBackground(); + if (background != null) + Add(background); + + Add(CreateLayout()); + base.Action = ViewProfile = () => { Action?.Invoke(); @@ -110,6 +107,22 @@ namespace osu.Game.Users Text = User.Username, }; + protected virtual Drawable? CreateBackground() => Background = new UserCoverBackground + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + User = User + }; + + protected UpdateableAvatar CreateAvatar() => new UpdateableAvatar(User, false); + + protected UpdateableFlag CreateFlag() => new UpdateableFlag(User.CountryCode) + { + Size = new Vector2(36, 26), + Action = Action, + }; + public MenuItem[] ContextMenuItems { get diff --git a/osu.Game/Users/UserRankPanel.cs b/osu.Game/Users/UserRankPanel.cs new file mode 100644 index 0000000000..272d2eecb4 --- /dev/null +++ b/osu.Game/Users/UserRankPanel.cs @@ -0,0 +1,212 @@ +// 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.Extensions.LocalisationExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; +using osu.Framework.Localisation; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays.Profile.Header.Components; +using osu.Game.Resources.Localisation.Web; +using osuTK; + +namespace osu.Game.Users +{ + /// + /// User card that shows user's global and country ranks in the bottom. + /// Meant to be used in the toolbar login overlay. + /// + public partial class UserRankPanel : UserPanel + { + private const int padding = 10; + private const int main_content_height = 80; + + public UserRankPanel(APIUser user) + : base(user) + { + AutoSizeAxes = Axes.Y; + CornerRadius = 10; + } + + [BackgroundDependencyLoader] + private void load() + { + BorderColour = ColourProvider?.Light1 ?? Colours.GreyVioletLighter; + } + + protected override Drawable CreateLayout() + { + FillFlowContainer details; + + var layout = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new Container + { + Name = "Main content", + RelativeSizeAxes = Axes.X, + Height = main_content_height, + CornerRadius = 10, + Masking = true, + Children = new Drawable[] + { + new UserCoverBackground + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + User = User, + Alpha = 0.3f + }, + new GridContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, padding), + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + new Dimension(GridSizeMode.Absolute, padding), + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, padding), + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new[] + { + Empty(), + Empty(), + Empty(), + Empty() + }, + new[] + { + Empty(), + CreateAvatar().With(avatar => + { + avatar.Size = new Vector2(60); + avatar.Masking = true; + avatar.CornerRadius = 6; + }), + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = padding }, + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension() + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension() + }, + Content = new[] + { + new Drawable[] + { + details = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(6), + Children = new Drawable[] + { + CreateFlag(), + } + } + }, + new Drawable[] + { + CreateUsername().With(username => + { + username.Anchor = Anchor.CentreLeft; + username.Origin = Anchor.CentreLeft; + }) + } + } + } + }, + Empty() + } + } + } + } + }, + new Container + { + Name = "Bottom content", + Margin = new MarginPadding { Top = main_content_height }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Left = 80, Vertical = padding }, + Child = new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension() + }, + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, + Content = new[] + { + new Drawable[] + { + new ProfileValueDisplay(true) + { + Title = UsersStrings.ShowRankGlobalSimple, + Content = User.Statistics?.GlobalRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-" + }, + new ProfileValueDisplay(true) + { + Title = UsersStrings.ShowRankCountrySimple, + Content = User.Statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-" + } + } + } + } + } + } + }; + + if (User.IsSupporter) + { + details.Add(new SupporterIcon + { + Height = 26, + SupportLevel = User.SupportLevel + }); + } + + return layout; + } + + protected override bool OnHover(HoverEvent e) + { + BorderThickness = 2; + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + BorderThickness = 0; + base.OnHoverLost(e); + } + + protected override Drawable? CreateBackground() => null; + } +} From 17656e9b9cfe4e6ce3fa2627bb1b3e34694ad305 Mon Sep 17 00:00:00 2001 From: Lena Date: Tue, 2 Jan 2024 18:38:25 +0100 Subject: [PATCH 2294/4092] update the current activity when the multiplayer room updates --- .../Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 7c12e6eab5..56256bb15c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -349,6 +349,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer addItemButton.Alpha = localUserCanAddItem ? 1 : 0; Scheduler.AddOnce(UpdateMods); + + Activity.Value = new UserActivity.InLobby(Room); } private bool localUserCanAddItem => client.IsHost || Room.QueueMode.Value != QueueMode.HostOnly; From c4be6fa9741c5fdcf8c347bdec9bf5fe3641d3fb Mon Sep 17 00:00:00 2001 From: StanR Date: Wed, 3 Jan 2024 00:37:24 +0600 Subject: [PATCH 2295/4092] Fix code quality, add new cards to the test scene --- .../Visual/Online/TestSceneUserPanel.cs | 25 +++++++++++++++++-- osu.Game/Overlays/Login/LoginPanel.cs | 3 +-- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index b3b8fd78d3..81e17cd1b8 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Scoring; @@ -29,6 +30,9 @@ namespace osu.Game.Tests.Visual.Online private UserGridPanel boundPanel1; private TestUserListPanel boundPanel2; + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); + [Resolved] private IRulesetStore rulesetStore { get; set; } @@ -85,8 +89,25 @@ namespace osu.Game.Tests.Visual.Online CoverUrl = @"https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg", IsOnline = false, LastVisit = DateTimeOffset.Now - }) - }, + }), + new UserRankPanel(new APIUser + { + Username = @"flyte", + Id = 3103765, + CountryCode = CountryCode.JP, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg", + Statistics = new UserStatistics { GlobalRank = 12345, CountryRank = 1234 } + }) { Width = 300 }, + new UserRankPanel(new APIUser + { + Username = @"peppy", + Id = 2, + Colour = "99EB47", + CountryCode = CountryCode.AU, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + Statistics = new UserStatistics { GlobalRank = null, CountryRank = null } + }) { Width = 300 } + } }; boundPanel1.Status.BindTo(status); diff --git a/osu.Game/Overlays/Login/LoginPanel.cs b/osu.Game/Overlays/Login/LoginPanel.cs index 892d613ea4..57a9f4035c 100644 --- a/osu.Game/Overlays/Login/LoginPanel.cs +++ b/osu.Game/Overlays/Login/LoginPanel.cs @@ -30,7 +30,6 @@ namespace osu.Game.Overlays.Login [Resolved] private OsuColour colours { get; set; } = null!; - private UserRankPanel panel = null!; private UserDropdown dropdown = null!; /// @@ -131,7 +130,7 @@ namespace osu.Game.Overlays.Login Text = LoginPanelStrings.SignedIn, Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold), }, - panel = new UserRankPanel(api.LocalUser.Value) + new UserRankPanel(api.LocalUser.Value) { RelativeSizeAxes = Axes.X, Action = RequestHide From 88509f28ad66110f0b8b7fb79e69aeccce7e5929 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 2 Jan 2024 20:51:22 +0100 Subject: [PATCH 2296/4092] Adjust assertion to match new juice stream child ordering --- osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs index 419a846ec3..825e8c697c 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Catch.Tests Mod = new CatchModHidden(), PassCondition = () => Player.Results.Count > 0 && Player.ChildrenOfType().Single().Alpha > 0 - && Player.ChildrenOfType().Last().Alpha > 0 + && Player.ChildrenOfType().First().Alpha > 0 }); } From bdfaa4b583912ebe4bba70a0d48c13ef1e79b919 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Jan 2024 13:34:49 +0900 Subject: [PATCH 2297/4092] Fix crash when dragging rotation control in editor with both mouse buttons Closes https://github.com/ppy/osu/issues/26325. --- .../Edit/Compose/Components/SelectionBoxRotationHandle.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs index 024749a701..5270162189 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs @@ -62,6 +62,9 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override bool OnDragStart(DragStartEvent e) { + if (e.Button != MouseButton.Left) + return false; + if (rotationHandler == null) return false; rotationHandler.Begin(); From 9e8d07d3144bd4b072d28bd9bd0e255fee410de0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Dec 2023 15:47:05 +0900 Subject: [PATCH 2298/4092] Add NVAPI and force thread optimisations on This undoes what stable does to force this setting off. --- osu.Desktop/NVAPI.cs | 737 +++++++++++++++++++++++++++++++++++++++++ osu.Desktop/Program.cs | 2 + 2 files changed, 739 insertions(+) create mode 100644 osu.Desktop/NVAPI.cs diff --git a/osu.Desktop/NVAPI.cs b/osu.Desktop/NVAPI.cs new file mode 100644 index 0000000000..9648ea1072 --- /dev/null +++ b/osu.Desktop/NVAPI.cs @@ -0,0 +1,737 @@ +// 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.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using osu.Framework.Logging; + +namespace osu.Desktop +{ + [SuppressMessage("ReSharper", "InconsistentNaming")] + internal static class NVAPI + { + private const string osu_filename = "osu!.exe"; + + // This is a good reference: + // https://github.com/errollw/Warp-and-Blend-Quadros/blob/master/WarpBlend-Quadros/UnwarpAll-Quadros/include/nvapi.h + // Note our Stride == their VERSION (e.g. NVDRS_SETTING_VER) + + public const int MAX_PHYSICAL_GPUS = 64; + public const int UNICODE_STRING_MAX = 2048; + + public const string APPLICATION_NAME = @"osu!"; + public const string PROFILE_NAME = @"osu!"; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate NvStatus EnumPhysicalGPUsDelegate([Out] IntPtr[] gpuHandles, out int gpuCount); + + public static readonly EnumPhysicalGPUsDelegate EnumPhysicalGPUs; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate NvStatus EnumLogicalGPUsDelegate([Out] IntPtr[] gpuHandles, out int gpuCount); + + public static readonly EnumLogicalGPUsDelegate EnumLogicalGPUs; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate NvStatus GetSystemTypeDelegate(IntPtr gpuHandle, out NvSystemType systemType); + + public static readonly GetSystemTypeDelegate GetSystemType; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate NvStatus GetGPUTypeDelegate(IntPtr gpuHandle, out NvGpuType gpuType); + + public static readonly GetGPUTypeDelegate GetGPUType; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate NvStatus CreateSessionDelegate(out IntPtr sessionHandle); + + public static CreateSessionDelegate CreateSession; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate NvStatus LoadSettingsDelegate(IntPtr sessionHandle); + + public static LoadSettingsDelegate LoadSettings; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate NvStatus FindApplicationByNameDelegate(IntPtr sessionHandle, [MarshalAs(UnmanagedType.BStr)] string appName, out IntPtr profileHandle, ref NvApplication application); + + public static FindApplicationByNameDelegate FindApplicationByName; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate NvStatus GetCurrentGlobalProfileDelegate(IntPtr sessionHandle, out IntPtr profileHandle); + + public static GetCurrentGlobalProfileDelegate GetCurrentGlobalProfile; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate NvStatus GetProfileInfoDelegate(IntPtr sessionHandle, IntPtr profileHandle, ref NvProfile profile); + + public static GetProfileInfoDelegate GetProfileInfo; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate NvStatus GetSettingDelegate(IntPtr sessionHandle, IntPtr profileHandle, NvSettingID settingID, ref NvSetting setting); + + public static GetSettingDelegate GetSetting; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate NvStatus CreateProfileDelegate(IntPtr sessionHandle, ref NvProfile profile, out IntPtr profileHandle); + + private static readonly CreateProfileDelegate CreateProfile; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate NvStatus SetSettingDelegate(IntPtr sessionHandle, IntPtr profileHandle, ref NvSetting setting); + + private static readonly SetSettingDelegate SetSetting; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate NvStatus EnumApplicationsDelegate(IntPtr sessionHandle, IntPtr profileHandle, uint startIndex, ref uint appCount, [In, Out, MarshalAs(UnmanagedType.LPArray)] NvApplication[] applications); + + private static readonly EnumApplicationsDelegate EnumApplications; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate NvStatus CreateApplicationDelegate(IntPtr sessionHandle, IntPtr profileHandle, ref NvApplication application); + + private static readonly CreateApplicationDelegate CreateApplication; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate NvStatus SaveSettingsDelegate(IntPtr sessionHandle); + + private static readonly SaveSettingsDelegate SaveSettings; + + public static NvStatus Status { get; private set; } = NvStatus.OK; + public static bool Available { get; private set; } + + private static IntPtr sessionHandle; + + public static bool IsUsingOptimusDedicatedGpu + { + get + { + if (!Available) + return false; + + if (!IsLaptop) + return false; + + IntPtr profileHandle; + if (!getProfile(out profileHandle, out _, out bool _)) + return false; + + // Get the optimus setting + NvSetting setting; + if (!getSetting(NvSettingID.SHIM_RENDERING_MODE_ID, profileHandle, out setting)) + return false; + + return (setting.U32CurrentValue & (uint)NvShimSetting.SHIM_RENDERING_MODE_ENABLE) > 0; + } + } + + public static bool IsLaptop + { + get + { + if (!Available) + return false; + + // Make sure that this is a laptop. + var gpus = new IntPtr[64]; + if (checkError(EnumPhysicalGPUs(gpus, out int gpuCount))) + return false; + + for (int i = 0; i < gpuCount; i++) + { + if (checkError(GetSystemType(gpus[i], out var type))) + return false; + + if (type == NvSystemType.LAPTOP) + return true; + } + + return false; + } + } + + public static bool ThreadedOptimisations + { + get + { + if (!Available) + return false; + + IntPtr profileHandle; + if (!getProfile(out profileHandle, out _, out bool _)) + return false; + + // Get the threaded optimisations setting + NvSetting setting; + if (!getSetting(NvSettingID.OGL_THREAD_CONTROL_ID, profileHandle, out setting)) + return false; + + return setting.U32CurrentValue != (uint)NvThreadControlSetting.OGL_THREAD_CONTROL_DISABLE; + } + set + { + if (!Available) + return; + + bool success = setSetting(NvSettingID.OGL_THREAD_CONTROL_ID, (uint)(value ? NvThreadControlSetting.OGL_THREAD_CONTROL_ENABLE : NvThreadControlSetting.OGL_THREAD_CONTROL_DISABLE)); + + Logger.Log(success ? $"Threaded optimizations set to \"{value}\"!" : "Threaded optimizations set failed!"); + } + } + + /// + /// Checks if the profile contains the current application. + /// + /// If the profile contains the current application. + private static bool containsApplication(IntPtr profileHandle, NvProfile profile, out NvApplication application) + { + application = new NvApplication + { + Version = NvApplication.Stride + }; + + if (profile.NumOfApps == 0) + return false; + + NvApplication[] applications = new NvApplication[profile.NumOfApps]; + applications[0].Version = NvApplication.Stride; + + uint numApps = profile.NumOfApps; + + if (checkError(EnumApplications(sessionHandle, profileHandle, 0, ref numApps, applications))) + return false; + + for (uint i = 0; i < numApps; i++) + { + if (applications[i].AppName == osu_filename) + { + application = applications[i]; + return true; + } + } + + return false; + } + + /// + /// Retrieves the profile of the current application. + /// + /// The profile handle. + /// The current application description. + /// If this profile is not a global (default) profile. + /// If the operation succeeded. + private static bool getProfile(out IntPtr profileHandle, out NvApplication application, out bool isApplicationSpecific) + { + application = new NvApplication + { + Version = NvApplication.Stride + }; + + isApplicationSpecific = true; + + if (checkError(FindApplicationByName(sessionHandle, osu_filename, out profileHandle, ref application))) + { + isApplicationSpecific = false; + if (checkError(GetCurrentGlobalProfile(sessionHandle, out profileHandle))) + return false; + } + + return true; + } + + /// + /// Creates a profile. + /// + /// The profile handle. + /// If the operation succeeded. + private static bool createProfile(out IntPtr profileHandle) + { + NvProfile newProfile = new NvProfile + { + Version = NvProfile.Stride, + IsPredefined = 0, + ProfileName = PROFILE_NAME, + GPUSupport = new uint[32] + }; + + newProfile.GPUSupport[0] = 1; + + if (checkError(CreateProfile(sessionHandle, ref newProfile, out profileHandle))) + return false; + + return true; + } + + /// + /// Retrieves a setting from the profile. + /// + /// The setting to retrieve. + /// The profile handle to retrieve the setting from. + /// The setting. + /// If the operation succeeded. + private static bool getSetting(NvSettingID settingId, IntPtr profileHandle, out NvSetting setting) + { + setting = new NvSetting + { + Version = NvSetting.Stride, + SettingID = settingId + }; + + if (checkError(GetSetting(sessionHandle, profileHandle, settingId, ref setting))) + return false; + + return true; + } + + private static bool setSetting(NvSettingID settingId, uint settingValue) + { + NvApplication application; + IntPtr profileHandle; + bool isApplicationSpecific; + if (!getProfile(out profileHandle, out application, out isApplicationSpecific)) + return false; + + if (!isApplicationSpecific) + { + // We don't want to interfere with the user's other settings, so let's create a separate config for osu! + if (!createProfile(out profileHandle)) + return false; + } + + NvSetting newSetting = new NvSetting + { + Version = NvSetting.Stride, + SettingID = settingId, + U32CurrentValue = settingValue + }; + + // Set the thread state + if (checkError(SetSetting(sessionHandle, profileHandle, ref newSetting))) + return false; + + // Get the profile (needed to check app count) + NvProfile profile = new NvProfile + { + Version = NvProfile.Stride + }; + if (checkError(GetProfileInfo(sessionHandle, profileHandle, ref profile))) + return false; + + if (!containsApplication(profileHandle, profile, out application)) + { + // Need to add the current application to the profile + application.IsPredefined = 0; + + application.AppName = osu_filename; + application.UserFriendlyName = APPLICATION_NAME; + + if (checkError(CreateApplication(sessionHandle, profileHandle, ref application))) + return false; + } + + // Save! + return !checkError(SaveSettings(sessionHandle)); + } + + /// + /// Creates a session to access the driver configuration. + /// + /// If the operation succeeded. + private static bool createSession() + { + if (checkError(CreateSession(out sessionHandle))) + return false; + + // Load settings into session + if (checkError(LoadSettings(sessionHandle))) + return false; + + return true; + } + + private static bool checkError(NvStatus status) + { + Status = status; + return status != NvStatus.OK; + } + + static NVAPI() + { + // TODO: check whether gpu vendor contains NVIDIA before attempting load? + + try + { + // Try to load NVAPI + if ((IntPtr.Size == 4 && loadLibrary(@"nvapi.dll") == IntPtr.Zero) + || (IntPtr.Size == 8 && loadLibrary(@"nvapi64.dll") == IntPtr.Zero)) + { + return; + } + + InitializeDelegate initialize; + getDelegate(0x0150E828, out initialize); + + if (initialize?.Invoke() == NvStatus.OK) + { + // IDs can be found here: https://github.com/jNizM/AHK_NVIDIA_NvAPI/blob/master/info/NvAPI_IDs.txt + + getDelegate(0xE5AC921F, out EnumPhysicalGPUs); + getDelegate(0x48B3EA59, out EnumLogicalGPUs); + getDelegate(0xBAAABFCC, out GetSystemType); + getDelegate(0xC33BAEB1, out GetGPUType); + getDelegate(0x0694D52E, out CreateSession); + getDelegate(0x375DBD6B, out LoadSettings); + getDelegate(0xEEE566B2, out FindApplicationByName); + getDelegate(0x617BFF9F, out GetCurrentGlobalProfile); + getDelegate(0x577DD202, out SetSetting); + getDelegate(0x61CD6FD6, out GetProfileInfo); + getDelegate(0x73BF8338, out GetSetting); + getDelegate(0xCC176068, out CreateProfile); + getDelegate(0x7FA2173A, out EnumApplications); + getDelegate(0x4347A9DE, out CreateApplication); + getDelegate(0xFCBC7E14, out SaveSettings); + } + + if (createSession()) + Available = true; + } + catch { } + } + + private static void getDelegate(uint id, out T newDelegate) where T : class + { + IntPtr ptr = IntPtr.Size == 4 ? queryInterface32(id) : queryInterface64(id); + newDelegate = ptr == IntPtr.Zero ? null : Marshal.GetDelegateForFunctionPointer(ptr, typeof(T)) as T; + } + + [DllImport("kernel32.dll", EntryPoint = "LoadLibrary")] + private static extern IntPtr loadLibrary(string dllToLoad); + + [DllImport(@"nvapi.dll", EntryPoint = "nvapi_QueryInterface", CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr queryInterface32(uint id); + + [DllImport(@"nvapi64.dll", EntryPoint = "nvapi_QueryInterface", CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr queryInterface64(uint id); + + private delegate NvStatus InitializeDelegate(); + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct NvSetting + { + public uint Version; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = NVAPI.UNICODE_STRING_MAX)] + public string SettingName; + + public NvSettingID SettingID; + public uint SettingType; + public uint SettingLocation; + public uint IsCurrentPredefined; + public uint IsPredefinedValid; + + public uint U32PredefinedValue; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = NVAPI.UNICODE_STRING_MAX)] + public string StringPredefinedValue; + + public uint U32CurrentValue; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = NVAPI.UNICODE_STRING_MAX)] + public string StringCurrentValue; + + public static uint Stride => (uint)Marshal.SizeOf(typeof(NvSetting)) | (1 << 16); + } + + [StructLayout(LayoutKind.Sequential, Pack = 8, CharSet = CharSet.Unicode)] + internal struct NvProfile + { + public uint Version; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = NVAPI.UNICODE_STRING_MAX)] + public string ProfileName; + + [MarshalAs(UnmanagedType.ByValArray)] + public uint[] GPUSupport; + + public uint IsPredefined; + public uint NumOfApps; + public uint NumOfSettings; + + public static uint Stride => (uint)Marshal.SizeOf(typeof(NvProfile)) | (1 << 16); + } + + [StructLayout(LayoutKind.Sequential, Pack = 8, CharSet = CharSet.Unicode)] + internal struct NvApplication + { + public uint Version; + public uint IsPredefined; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = NVAPI.UNICODE_STRING_MAX)] + public string AppName; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = NVAPI.UNICODE_STRING_MAX)] + public string UserFriendlyName; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = NVAPI.UNICODE_STRING_MAX)] + public string Launcher; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = NVAPI.UNICODE_STRING_MAX)] + public string FileInFolder; + + public static uint Stride => (uint)Marshal.SizeOf(typeof(NvApplication)) | (2 << 16); + } + + internal enum NvStatus + { + OK = 0, // Success. Request is completed. + ERROR = -1, // Generic error + LIBRARY_NOT_FOUND = -2, // NVAPI support library cannot be loaded. + NO_IMPLEMENTATION = -3, // not implemented in current driver installation + API_NOT_INITIALIZED = -4, // Initialize has not been called (successfully) + INVALID_ARGUMENT = -5, // The argument/parameter value is not valid or NULL. + NVIDIA_DEVICE_NOT_FOUND = -6, // No NVIDIA display driver, or NVIDIA GPU driving a display, was found. + END_ENUMERATION = -7, // No more items to enumerate + INVALID_HANDLE = -8, // Invalid handle + INCOMPATIBLE_STRUCT_VERSION = -9, // An argument's structure version is not supported + HANDLE_INVALIDATED = -10, // The handle is no longer valid (likely due to GPU or display re-configuration) + OPENGL_CONTEXT_NOT_CURRENT = -11, // No NVIDIA OpenGL context is current (but needs to be) + INVALID_POINTER = -14, // An invalid pointer, usually NULL, was passed as a parameter + NO_GL_EXPERT = -12, // OpenGL Expert is not supported by the current drivers + INSTRUMENTATION_DISABLED = -13, // OpenGL Expert is supported, but driver instrumentation is currently disabled + NO_GL_NSIGHT = -15, // OpenGL does not support Nsight + + EXPECTED_LOGICAL_GPU_HANDLE = -100, // Expected a logical GPU handle for one or more parameters + EXPECTED_PHYSICAL_GPU_HANDLE = -101, // Expected a physical GPU handle for one or more parameters + EXPECTED_DISPLAY_HANDLE = -102, // Expected an NV display handle for one or more parameters + INVALID_COMBINATION = -103, // The combination of parameters is not valid. + NOT_SUPPORTED = -104, // Requested feature is not supported in the selected GPU + PORTID_NOT_FOUND = -105, // No port ID was found for the I2C transaction + EXPECTED_UNATTACHED_DISPLAY_HANDLE = -106, // Expected an unattached display handle as one of the input parameters. + INVALID_PERF_LEVEL = -107, // Invalid perf level + DEVICE_BUSY = -108, // Device is busy; request not fulfilled + NV_PERSIST_FILE_NOT_FOUND = -109, // NV persist file is not found + PERSIST_DATA_NOT_FOUND = -110, // NV persist data is not found + EXPECTED_TV_DISPLAY = -111, // Expected a TV output display + EXPECTED_TV_DISPLAY_ON_DCONNECTOR = -112, // Expected a TV output on the D Connector - HDTV_EIAJ4120. + NO_ACTIVE_SLI_TOPOLOGY = -113, // SLI is not active on this device. + SLI_RENDERING_MODE_NOTALLOWED = -114, // Setup of SLI rendering mode is not possible right now. + EXPECTED_DIGITAL_FLAT_PANEL = -115, // Expected a digital flat panel. + ARGUMENT_EXCEED_MAX_SIZE = -116, // Argument exceeds the expected size. + DEVICE_SWITCHING_NOT_ALLOWED = -117, // Inhibit is ON due to one of the flags in NV_GPU_DISPLAY_CHANGE_INHIBIT or SLI active. + TESTING_CLOCKS_NOT_SUPPORTED = -118, // Testing of clocks is not supported. + UNKNOWN_UNDERSCAN_CONFIG = -119, // The specified underscan config is from an unknown source (e.g. INF) + TIMEOUT_RECONFIGURING_GPU_TOPO = -120, // Timeout while reconfiguring GPUs + DATA_NOT_FOUND = -121, // Requested data was not found + EXPECTED_ANALOG_DISPLAY = -122, // Expected an analog display + NO_VIDLINK = -123, // No SLI video bridge is present + REQUIRES_REBOOT = -124, // NVAPI requires a reboot for the settings to take effect + INVALID_HYBRID_MODE = -125, // The function is not supported with the current Hybrid mode. + MIXED_TARGET_TYPES = -126, // The target types are not all the same + SYSWOW64_NOT_SUPPORTED = -127, // The function is not supported from 32-bit on a 64-bit system. + IMPLICIT_SET_GPU_TOPOLOGY_CHANGE_NOT_ALLOWED = -128, // There is no implicit GPU topology active. Use SetHybridMode to change topology. + REQUEST_USER_TO_CLOSE_NON_MIGRATABLE_APPS = -129, // Prompt the user to close all non-migratable applications. + OUT_OF_MEMORY = -130, // Could not allocate sufficient memory to complete the call. + WAS_STILL_DRAWING = -131, // The previous operation that is transferring information to or from this surface is incomplete. + FILE_NOT_FOUND = -132, // The file was not found. + TOO_MANY_UNIQUE_STATE_OBJECTS = -133, // There are too many unique instances of a particular type of state object. + INVALID_CALL = -134, // The method call is invalid. For example, a method's parameter may not be a valid pointer. + D3D10_1_LIBRARY_NOT_FOUND = -135, // d3d10_1.dll cannot be loaded. + FUNCTION_NOT_FOUND = -136, // Couldn't find the function in the loaded DLL. + INVALID_USER_PRIVILEGE = -137, // Current User is not Admin. + EXPECTED_NON_PRIMARY_DISPLAY_HANDLE = -138, // The handle corresponds to GDIPrimary. + EXPECTED_COMPUTE_GPU_HANDLE = -139, // Setting Physx GPU requires that the GPU is compute-capable. + STEREO_NOT_INITIALIZED = -140, // The Stereo part of NVAPI failed to initialize completely. Check if the stereo driver is installed. + STEREO_REGISTRY_ACCESS_FAILED = -141, // Access to stereo-related registry keys or values has failed. + STEREO_REGISTRY_PROFILE_TYPE_NOT_SUPPORTED = -142, // The given registry profile type is not supported. + STEREO_REGISTRY_VALUE_NOT_SUPPORTED = -143, // The given registry value is not supported. + STEREO_NOT_ENABLED = -144, // Stereo is not enabled and the function needed it to execute completely. + STEREO_NOT_TURNED_ON = -145, // Stereo is not turned on and the function needed it to execute completely. + STEREO_INVALID_DEVICE_INTERFACE = -146, // Invalid device interface. + STEREO_PARAMETER_OUT_OF_RANGE = -147, // Separation percentage or JPEG image capture quality is out of [0-100] range. + STEREO_FRUSTUM_ADJUST_MODE_NOT_SUPPORTED = -148, // The given frustum adjust mode is not supported. + TOPO_NOT_POSSIBLE = -149, // The mosaic topology is not possible given the current state of the hardware. + MODE_CHANGE_FAILED = -150, // An attempt to do a display resolution mode change has failed. + D3D11_LIBRARY_NOT_FOUND = -151, // d3d11.dll/d3d11_beta.dll cannot be loaded. + INVALID_ADDRESS = -152, // Address is outside of valid range. + STRING_TOO_SMALL = -153, // The pre-allocated string is too small to hold the result. + MATCHING_DEVICE_NOT_FOUND = -154, // The input does not match any of the available devices. + DRIVER_RUNNING = -155, // Driver is running. + DRIVER_NOTRUNNING = -156, // Driver is not running. + ERROR_DRIVER_RELOAD_REQUIRED = -157, // A driver reload is required to apply these settings. + SET_NOT_ALLOWED = -158, // Intended setting is not allowed. + ADVANCED_DISPLAY_TOPOLOGY_REQUIRED = -159, // Information can't be returned due to "advanced display topology". + SETTING_NOT_FOUND = -160, // Setting is not found. + SETTING_SIZE_TOO_LARGE = -161, // Setting size is too large. + TOO_MANY_SETTINGS_IN_PROFILE = -162, // There are too many settings for a profile. + PROFILE_NOT_FOUND = -163, // Profile is not found. + PROFILE_NAME_IN_USE = -164, // Profile name is duplicated. + PROFILE_NAME_EMPTY = -165, // Profile name is empty. + EXECUTABLE_NOT_FOUND = -166, // Application not found in the Profile. + EXECUTABLE_ALREADY_IN_USE = -167, // Application already exists in the other profile. + DATATYPE_MISMATCH = -168, // Data Type mismatch + PROFILE_REMOVED = -169, // The profile passed as parameter has been removed and is no longer valid. + UNREGISTERED_RESOURCE = -170, // An unregistered resource was passed as a parameter. + ID_OUT_OF_RANGE = -171, // The DisplayId corresponds to a display which is not within the normal outputId range. + DISPLAYCONFIG_VALIDATION_FAILED = -172, // Display topology is not valid so the driver cannot do a mode set on this configuration. + DPMST_CHANGED = -173, // Display Port Multi-Stream topology has been changed. + INSUFFICIENT_BUFFER = -174, // Input buffer is insufficient to hold the contents. + ACCESS_DENIED = -175, // No access to the caller. + MOSAIC_NOT_ACTIVE = -176, // The requested action cannot be performed without Mosaic being enabled. + SHARE_RESOURCE_RELOCATED = -177, // The surface is relocated away from video memory. + REQUEST_USER_TO_DISABLE_DWM = -178, // The user should disable DWM before calling NvAPI. + D3D_DEVICE_LOST = -179, // D3D device status is D3DERR_DEVICELOST or D3DERR_DEVICENOTRESET - the user has to reset the device. + INVALID_CONFIGURATION = -180, // The requested action cannot be performed in the current state. + STEREO_HANDSHAKE_NOT_DONE = -181, // Call failed as stereo handshake not completed. + EXECUTABLE_PATH_IS_AMBIGUOUS = -182, // The path provided was too short to determine the correct NVDRS_APPLICATION + DEFAULT_STEREO_PROFILE_IS_NOT_DEFINED = -183, // Default stereo profile is not currently defined + DEFAULT_STEREO_PROFILE_DOES_NOT_EXIST = -184, // Default stereo profile does not exist + CLUSTER_ALREADY_EXISTS = -185, // A cluster is already defined with the given configuration. + DPMST_DISPLAY_ID_EXPECTED = -186, // The input display id is not that of a multi stream enabled connector or a display device in a multi stream topology + INVALID_DISPLAY_ID = -187, // The input display id is not valid or the monitor associated to it does not support the current operation + STREAM_IS_OUT_OF_SYNC = -188, // While playing secure audio stream, stream goes out of sync + INCOMPATIBLE_AUDIO_DRIVER = -189, // Older audio driver version than required + VALUE_ALREADY_SET = -190, // Value already set, setting again not allowed. + TIMEOUT = -191, // Requested operation timed out + GPU_WORKSTATION_FEATURE_INCOMPLETE = -192, // The requested workstation feature set has incomplete driver internal allocation resources + STEREO_INIT_ACTIVATION_NOT_DONE = -193, // Call failed because InitActivation was not called. + SYNC_NOT_ACTIVE = -194, // The requested action cannot be performed without Sync being enabled. + SYNC_MASTER_NOT_FOUND = -195, // The requested action cannot be performed without Sync Master being enabled. + INVALID_SYNC_TOPOLOGY = -196, // Invalid displays passed in the NV_GSYNC_DISPLAY pointer. + ECID_SIGN_ALGO_UNSUPPORTED = -197, // The specified signing algorithm is not supported. Either an incorrect value was entered or the current installed driver/hardware does not support the input value. + ECID_KEY_VERIFICATION_FAILED = -198, // The encrypted public key verification has failed. + FIRMWARE_OUT_OF_DATE = -199, // The device's firmware is out of date. + FIRMWARE_REVISION_NOT_SUPPORTED = -200, // The device's firmware is not supported. + } + + internal enum NvSystemType + { + UNKNOWN = 0, + LAPTOP = 1, + DESKTOP = 2 + } + + internal enum NvGpuType + { + UNKNOWN = 0, + IGPU = 1, // Integrated + DGPU = 2, // Discrete + } + + internal enum NvSettingID : uint + { + OGL_AA_LINE_GAMMA_ID = 0x2089BF6C, + OGL_DEEP_COLOR_SCANOUT_ID = 0x2097C2F6, + OGL_DEFAULT_SWAP_INTERVAL_ID = 0x206A6582, + OGL_DEFAULT_SWAP_INTERVAL_FRACTIONAL_ID = 0x206C4581, + OGL_DEFAULT_SWAP_INTERVAL_SIGN_ID = 0x20655CFA, + OGL_EVENT_LOG_SEVERITY_THRESHOLD_ID = 0x209DF23E, + OGL_EXTENSION_STRING_VERSION_ID = 0x20FF7493, + OGL_FORCE_BLIT_ID = 0x201F619F, + OGL_FORCE_STEREO_ID = 0x204D9A0C, + OGL_IMPLICIT_GPU_AFFINITY_ID = 0x20D0F3E6, + OGL_MAX_FRAMES_ALLOWED_ID = 0x208E55E3, + OGL_MULTIMON_ID = 0x200AEBFC, + OGL_OVERLAY_PIXEL_TYPE_ID = 0x209AE66F, + OGL_OVERLAY_SUPPORT_ID = 0x206C28C4, + OGL_QUALITY_ENHANCEMENTS_ID = 0x20797D6C, + OGL_SINGLE_BACKDEPTH_BUFFER_ID = 0x20A29055, + OGL_THREAD_CONTROL_ID = 0x20C1221E, + OGL_TRIPLE_BUFFER_ID = 0x20FDD1F9, + OGL_VIDEO_EDITING_MODE_ID = 0x20EE02B4, + AA_BEHAVIOR_FLAGS_ID = 0x10ECDB82, + AA_MODE_ALPHATOCOVERAGE_ID = 0x10FC2D9C, + AA_MODE_GAMMACORRECTION_ID = 0x107D639D, + AA_MODE_METHOD_ID = 0x10D773D2, + AA_MODE_REPLAY_ID = 0x10D48A85, + AA_MODE_SELECTOR_ID = 0x107EFC5B, + AA_MODE_SELECTOR_SLIAA_ID = 0x107AFC5B, + ANISO_MODE_LEVEL_ID = 0x101E61A9, + ANISO_MODE_SELECTOR_ID = 0x10D2BB16, + APPLICATION_PROFILE_NOTIFICATION_TIMEOUT_ID = 0x104554B6, + APPLICATION_STEAM_ID_ID = 0x107CDDBC, + CPL_HIDDEN_PROFILE_ID = 0x106D5CFF, + CUDA_EXCLUDED_GPUS_ID = 0x10354FF8, + D3DOGL_GPU_MAX_POWER_ID = 0x10D1EF29, + EXPORT_PERF_COUNTERS_ID = 0x108F0841, + FXAA_ALLOW_ID = 0x1034CB89, + FXAA_ENABLE_ID = 0x1074C972, + FXAA_INDICATOR_ENABLE_ID = 0x1068FB9C, + MCSFRSHOWSPLIT_ID = 0x10287051, + OPTIMUS_MAXAA_ID = 0x10F9DC83, + PHYSXINDICATOR_ID = 0x1094F16F, + PREFERRED_PSTATE_ID = 0x1057EB71, + PREVENT_UI_AF_OVERRIDE_ID = 0x103BCCB5, + PS_FRAMERATE_LIMITER_ID = 0x10834FEE, + PS_FRAMERATE_LIMITER_GPS_CTRL_ID = 0x10834F01, + SHIM_MAXRES_ID = 0x10F9DC82, + SHIM_MCCOMPAT_ID = 0x10F9DC80, + SHIM_RENDERING_MODE_ID = 0x10F9DC81, + SHIM_RENDERING_OPTIONS_ID = 0x10F9DC84, + SLI_GPU_COUNT_ID = 0x1033DCD1, + SLI_PREDEFINED_GPU_COUNT_ID = 0x1033DCD2, + SLI_PREDEFINED_GPU_COUNT_DX10_ID = 0x1033DCD3, + SLI_PREDEFINED_MODE_ID = 0x1033CEC1, + SLI_PREDEFINED_MODE_DX10_ID = 0x1033CEC2, + SLI_RENDERING_MODE_ID = 0x1033CED1, + VRRFEATUREINDICATOR_ID = 0x1094F157, + VRROVERLAYINDICATOR_ID = 0x1095F16F, + VRRREQUESTSTATE_ID = 0x1094F1F7, + VSYNCSMOOTHAFR_ID = 0x101AE763, + VSYNCVRRCONTROL_ID = 0x10A879CE, + VSYNC_BEHAVIOR_FLAGS_ID = 0x10FDEC23, + WKS_API_STEREO_EYES_EXCHANGE_ID = 0x11AE435C, + WKS_API_STEREO_MODE_ID = 0x11E91A61, + WKS_MEMORY_ALLOCATION_POLICY_ID = 0x11112233, + WKS_STEREO_DONGLE_SUPPORT_ID = 0x112493BD, + WKS_STEREO_SUPPORT_ID = 0x11AA9E99, + WKS_STEREO_SWAP_MODE_ID = 0x11333333, + AO_MODE_ID = 0x00667329, + AO_MODE_ACTIVE_ID = 0x00664339, + AUTO_LODBIASADJUST_ID = 0x00638E8F, + ICAFE_LOGO_CONFIG_ID = 0x00DB1337, + LODBIASADJUST_ID = 0x00738E8F, + PRERENDERLIMIT_ID = 0x007BA09E, + PS_DYNAMIC_TILING_ID = 0x00E5C6C0, + PS_SHADERDISKCACHE_ID = 0x00198FFF, + PS_TEXFILTER_ANISO_OPTS2_ID = 0x00E73211, + PS_TEXFILTER_BILINEAR_IN_ANISO_ID = 0x0084CD70, + PS_TEXFILTER_DISABLE_TRILIN_SLOPE_ID = 0x002ECAF2, + PS_TEXFILTER_NO_NEG_LODBIAS_ID = 0x0019BB68, + QUALITY_ENHANCEMENTS_ID = 0x00CE2691, + REFRESH_RATE_OVERRIDE_ID = 0x0064B541, + SET_POWER_THROTTLE_FOR_PCIe_COMPLIANCE_ID = 0x00AE785C, + SET_VAB_DATA_ID = 0x00AB8687, + VSYNCMODE_ID = 0x00A879CF, + VSYNCTEARCONTROL_ID = 0x005A375C, + TOTAL_DWORD_SETTING_NUM = 80, + TOTAL_WSTRING_SETTING_NUM = 4, + TOTAL_SETTING_NUM = 84, + INVALID_SETTING_ID = 0xFFFFFFFF + } + + internal enum NvShimSetting : uint + { + SHIM_RENDERING_MODE_INTEGRATED = 0x00000000, + SHIM_RENDERING_MODE_ENABLE = 0x00000001, + SHIM_RENDERING_MODE_USER_EDITABLE = 0x00000002, + SHIM_RENDERING_MODE_MASK = 0x00000003, + SHIM_RENDERING_MODE_VIDEO_MASK = 0x00000004, + SHIM_RENDERING_MODE_VARYING_BIT = 0x00000008, + SHIM_RENDERING_MODE_AUTO_SELECT = 0x00000010, + SHIM_RENDERING_MODE_OVERRIDE_BIT = 0x80000000, + SHIM_RENDERING_MODE_NUM_VALUES = 8, + SHIM_RENDERING_MODE_DEFAULT = SHIM_RENDERING_MODE_AUTO_SELECT + } + + internal enum NvThreadControlSetting : uint + { + OGL_THREAD_CONTROL_ENABLE = 0x00000001, + OGL_THREAD_CONTROL_DISABLE = 0x00000002, + OGL_THREAD_CONTROL_NUM_VALUES = 2, + OGL_THREAD_CONTROL_DEFAULT = 0 + } +} diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index a33e845f5b..b37b5cf6ca 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -30,6 +30,8 @@ namespace osu.Desktop [STAThread] public static void Main(string[] args) { + NVAPI.ThreadedOptimisations = true; + // run Squirrel first, as the app may exit after these run if (OperatingSystem.IsWindows()) { From d38f8d9c783049f0c6a577383f3097a923ba635c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Jan 2024 15:37:24 +0900 Subject: [PATCH 2299/4092] Change threaded optimisations setting to "Auto" on startup --- osu.Desktop/NVAPI.cs | 12 ++++++------ osu.Desktop/Program.cs | 5 ++++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Desktop/NVAPI.cs b/osu.Desktop/NVAPI.cs index 9648ea1072..e1c5971e28 100644 --- a/osu.Desktop/NVAPI.cs +++ b/osu.Desktop/NVAPI.cs @@ -153,30 +153,30 @@ namespace osu.Desktop } } - public static bool ThreadedOptimisations + public static NvThreadControlSetting ThreadedOptimisations { get { if (!Available) - return false; + return NvThreadControlSetting.OGL_THREAD_CONTROL_DEFAULT; IntPtr profileHandle; if (!getProfile(out profileHandle, out _, out bool _)) - return false; + return NvThreadControlSetting.OGL_THREAD_CONTROL_DEFAULT; // Get the threaded optimisations setting NvSetting setting; if (!getSetting(NvSettingID.OGL_THREAD_CONTROL_ID, profileHandle, out setting)) - return false; + return NvThreadControlSetting.OGL_THREAD_CONTROL_DEFAULT; - return setting.U32CurrentValue != (uint)NvThreadControlSetting.OGL_THREAD_CONTROL_DISABLE; + return (NvThreadControlSetting)setting.U32CurrentValue; } set { if (!Available) return; - bool success = setSetting(NvSettingID.OGL_THREAD_CONTROL_ID, (uint)(value ? NvThreadControlSetting.OGL_THREAD_CONTROL_ENABLE : NvThreadControlSetting.OGL_THREAD_CONTROL_DISABLE)); + bool success = setSetting(NvSettingID.OGL_THREAD_CONTROL_ID, (uint)value); Logger.Log(success ? $"Threaded optimizations set to \"{value}\"!" : "Threaded optimizations set failed!"); } diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index b37b5cf6ca..a652e31f62 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -30,7 +30,10 @@ namespace osu.Desktop [STAThread] public static void Main(string[] args) { - NVAPI.ThreadedOptimisations = true; + // NVIDIA profiles are based on the executable name of a process. + // Lazer and stable share the same executable name. + // Stable sets this setting to "Off", which may not be what we want, so let's force it back to the default "Auto" on startup. + NVAPI.ThreadedOptimisations = NvThreadControlSetting.OGL_THREAD_CONTROL_DEFAULT; // run Squirrel first, as the app may exit after these run if (OperatingSystem.IsWindows()) From e686a6a1dddbe20c3c22dd1ec8cb572d7a9d8aef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 3 Jan 2024 09:17:01 +0100 Subject: [PATCH 2300/4092] Fix player submission test intermittent failures due to audio playback discrepancy logic kicking in See https://github.com/ppy/osu/actions/runs/7384457927/job/20087439457#step:5:133. --- .../Visual/Gameplay/TestScenePlayerScoreSubmission.cs | 6 ++++++ osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index f75a2656ef..96cfcd61c3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -21,6 +21,7 @@ using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko; using osu.Game.Scoring; +using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; using osu.Game.Tests.Beatmaps; @@ -359,6 +360,11 @@ namespace osu.Game.Tests.Visual.Gameplay AllowImportCompletion = new SemaphoreSlim(1); } + protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new MasterGameplayClockContainer(beatmap, gameplayStart) + { + ShouldValidatePlaybackRate = false, + }; + protected override async Task ImportScore(Score score) { ScoreImportStarted = true; diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index a475f4823f..0d60ec4713 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -40,6 +40,12 @@ namespace osu.Game.Screens.Play Precision = 0.1, }; + /// + /// Whether the audio playback rate should be validated. + /// Mostly disabled for tests. + /// + internal bool ShouldValidatePlaybackRate { get; init; } + /// /// Whether the audio playback is within acceptable ranges. /// Will become false if audio playback is not going as expected. @@ -223,6 +229,9 @@ namespace osu.Game.Screens.Play private void checkPlaybackValidity() { + if (!ShouldValidatePlaybackRate) + return; + if (GameplayClock.IsRunning) { elapsedGameplayClockTime += GameplayClock.ElapsedFrameTime; From 3df7430d2e278cbd3dacc173270053e6a415af07 Mon Sep 17 00:00:00 2001 From: StanR Date: Wed, 3 Jan 2024 14:32:32 +0600 Subject: [PATCH 2301/4092] Bind `UserRankPanel` values to `Statistics` bindable in `APIAccess` --- .../Visual/Online/TestSceneUserPanel.cs | 18 ++++++++++++++++++ osu.Game/Users/UserRankPanel.cs | 17 +++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index 81e17cd1b8..4df34e6244 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; @@ -157,6 +158,23 @@ namespace osu.Game.Tests.Visual.Online AddAssert("visit message is not visible", () => !boundPanel2.LastVisitMessage.IsPresent); } + [Test] + public void TestUserStatisticsChange() + { + AddStep("update statistics", () => + { + API.UpdateStatistics(new UserStatistics + { + GlobalRank = RNG.Next(100000), + CountryRank = RNG.Next(100000) + }); + }); + AddStep("set statistics to empty", () => + { + API.UpdateStatistics(new UserStatistics()); + }); + } + private UserActivity soloGameStatusForRuleset(int rulesetId) => new UserActivity.InSoloGame(new BeatmapInfo(), rulesetStore.GetRuleset(rulesetId)!); private ScoreInfo createScore(string name) => new ScoreInfo(new TestBeatmap(Ruleset.Value).BeatmapInfo) diff --git a/osu.Game/Users/UserRankPanel.cs b/osu.Game/Users/UserRankPanel.cs index 272d2eecb4..74ff0a06dd 100644 --- a/osu.Game/Users/UserRankPanel.cs +++ b/osu.Game/Users/UserRankPanel.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Framework.Localisation; +using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Resources.Localisation.Web; @@ -23,6 +24,12 @@ namespace osu.Game.Users private const int padding = 10; private const int main_content_height = 80; + [Resolved] + private IAPIProvider api { get; set; } = null!; + + private ProfileValueDisplay globalRankDisplay = null!; + private ProfileValueDisplay countryRankDisplay = null!; + public UserRankPanel(APIUser user) : base(user) { @@ -34,6 +41,12 @@ namespace osu.Game.Users private void load() { BorderColour = ColourProvider?.Light1 ?? Colours.GreyVioletLighter; + + api.Statistics.ValueChanged += e => + { + globalRankDisplay.Content = e.NewValue?.GlobalRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-"; + countryRankDisplay.Content = e.NewValue?.CountryRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-"; + }; } protected override Drawable CreateLayout() @@ -166,12 +179,12 @@ namespace osu.Game.Users { new Drawable[] { - new ProfileValueDisplay(true) + globalRankDisplay = new ProfileValueDisplay(true) { Title = UsersStrings.ShowRankGlobalSimple, Content = User.Statistics?.GlobalRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-" }, - new ProfileValueDisplay(true) + countryRankDisplay = new ProfileValueDisplay(true) { Title = UsersStrings.ShowRankCountrySimple, Content = User.Statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-" From d34f30f6ad1a2c5071eddb2ed1763465be3cc70b Mon Sep 17 00:00:00 2001 From: StanR Date: Wed, 3 Jan 2024 14:37:57 +0600 Subject: [PATCH 2302/4092] Add `Statistics` bindable to `IAPIProvider` and update it from `SoloStatisticsWatcher` --- osu.Game/Online/API/APIAccess.cs | 14 +++++++++++++- osu.Game/Online/API/DummyAPIAccess.cs | 14 ++++++++++++++ osu.Game/Online/API/IAPIProvider.cs | 10 ++++++++++ osu.Game/Online/Solo/SoloStatisticsWatcher.cs | 2 ++ 4 files changed, 39 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index be5bdeca77..3910f1efea 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -53,6 +53,7 @@ namespace osu.Game.Online.API public IBindable LocalUser => localUser; public IBindableList Friends => friends; public IBindable Activity => activity; + public IBindable Statistics => statistics; public Language Language => game.CurrentLanguage.Value; @@ -65,6 +66,8 @@ namespace osu.Game.Online.API private Bindable configStatus { get; } = new Bindable(); private Bindable localUserStatus { get; } = new Bindable(); + private Bindable statistics { get; } = new Bindable(); + protected bool HasLogin => authentication.Token.Value != null || (!string.IsNullOrEmpty(ProvidedUsername) && !string.IsNullOrEmpty(password)); private readonly CancellationTokenSource cancellationToken = new CancellationTokenSource(); @@ -517,9 +520,18 @@ namespace osu.Game.Online.API flushQueue(); } + public void UpdateStatistics(UserStatistics newStatistics) + { + statistics.Value = newStatistics; + } + private static APIUser createGuestUser() => new GuestUser(); - private void setLocalUser(APIUser user) => Scheduler.Add(() => localUser.Value = user, false); + private void setLocalUser(APIUser user) => Scheduler.Add(() => + { + localUser.Value = user; + statistics.Value = user.Statistics; + }, false); protected override void Dispose(bool isDisposing) { diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index d585124db6..aa8658cc83 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -28,6 +28,8 @@ namespace osu.Game.Online.API public Bindable Activity { get; } = new Bindable(); + public Bindable Statistics { get; } = new Bindable(); + public Language Language => Language.en; public string AccessToken => "token"; @@ -115,6 +117,12 @@ namespace osu.Game.Online.API Id = DUMMY_USER_ID, }; + Statistics.Value = new UserStatistics + { + GlobalRank = 1, + CountryRank = 1 + }; + state.Value = APIState.Online; } @@ -126,6 +134,11 @@ namespace osu.Game.Online.API LocalUser.Value = new GuestUser(); } + public void UpdateStatistics(UserStatistics newStatistics) + { + Statistics.Value = newStatistics; + } + public IHubClientConnector? GetHubConnector(string clientName, string endpoint, bool preferMessagePack) => null; public NotificationsClientConnector GetNotificationsConnector() => new PollingNotificationsClientConnector(this); @@ -141,6 +154,7 @@ namespace osu.Game.Online.API IBindable IAPIProvider.LocalUser => LocalUser; IBindableList IAPIProvider.Friends => Friends; IBindable IAPIProvider.Activity => Activity; + IBindable IAPIProvider.Statistics => Statistics; /// /// During the next simulated login, the process will fail immediately. diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index a1d7006c8c..b58d4a363a 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -28,6 +28,11 @@ namespace osu.Game.Online.API /// IBindable Activity { get; } + /// + /// The current user's online statistics. + /// + IBindable Statistics { get; } + /// /// The language supplied by this provider to API requests. /// @@ -111,6 +116,11 @@ namespace osu.Game.Online.API /// void Logout(); + /// + /// Sets Statistics bindable. + /// + void UpdateStatistics(UserStatistics newStatistics); + /// /// Constructs a new . May be null if not supported. /// diff --git a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs index 46449fea73..55b27fb364 100644 --- a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs +++ b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs @@ -127,6 +127,8 @@ namespace osu.Game.Online.Solo { string rulesetName = callback.Score.Ruleset.ShortName; + api.UpdateStatistics(updatedStatistics); + if (latestStatistics == null) return; From 474c416bd9985aab49f895054189ea5b61b40949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 3 Jan 2024 10:05:11 +0100 Subject: [PATCH 2303/4092] Make `dotnet format` shut up about naming --- osu.Desktop/NVAPI.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Desktop/NVAPI.cs b/osu.Desktop/NVAPI.cs index e1c5971e28..bb3a59cc7f 100644 --- a/osu.Desktop/NVAPI.cs +++ b/osu.Desktop/NVAPI.cs @@ -3,6 +3,8 @@ #nullable disable +#pragma warning disable IDE1006 // Naming rule violation + using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; From 04147eb68988e4ce7f94f5d36def2c8669821508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 3 Jan 2024 11:46:26 +0100 Subject: [PATCH 2304/4092] Fix lack of correct default value spec --- osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 0d60ec4713..8b8bf87436 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Play /// Whether the audio playback rate should be validated. /// Mostly disabled for tests. /// - internal bool ShouldValidatePlaybackRate { get; init; } + internal bool ShouldValidatePlaybackRate { get; init; } = true; /// /// Whether the audio playback is within acceptable ranges. From 4312e9e9a9c70bd7a139833108edca8dd4ff63e3 Mon Sep 17 00:00:00 2001 From: CaffeeLake Date: Wed, 3 Jan 2024 20:17:05 +0900 Subject: [PATCH 2305/4092] Add Test: floating point errors --- osu.Game.Tests/Mods/ModUtilsTest.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/Mods/ModUtilsTest.cs b/osu.Game.Tests/Mods/ModUtilsTest.cs index 4f10a0ea54..f1db146f3a 100644 --- a/osu.Game.Tests/Mods/ModUtilsTest.cs +++ b/osu.Game.Tests/Mods/ModUtilsTest.cs @@ -315,6 +315,10 @@ namespace osu.Game.Tests.Mods { Assert.AreEqual(ModUtils.FormatScoreMultiplier(0.9999).ToString(), "0.99x"); Assert.AreEqual(ModUtils.FormatScoreMultiplier(1.0001).ToString(), "1.01x"); + Assert.AreEqual(ModUtils.FormatScoreMultiplier(0.899999999999999).ToString(), "0.90x"); + Assert.AreEqual(ModUtils.FormatScoreMultiplier(1.099999999999999).ToString(), "1.10x"); + Assert.AreEqual(ModUtils.FormatScoreMultiplier(0.900000000000001).ToString(), "0.90x"); + Assert.AreEqual(ModUtils.FormatScoreMultiplier(1.100000000000001).ToString(), "1.10x"); } public abstract class CustomMod1 : Mod, IModCompatibilitySpecification From 7262fef67f5c0abc51b78f8aa643c3b68d9bbbe3 Mon Sep 17 00:00:00 2001 From: StanR Date: Wed, 3 Jan 2024 17:39:48 +0600 Subject: [PATCH 2306/4092] Add comments --- osu.Game/Users/UserGridPanel.cs | 2 ++ osu.Game/Users/UserPanel.cs | 17 ++++++++++------- osu.Game/Users/UserRankPanel.cs | 6 ++++-- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/osu.Game/Users/UserGridPanel.cs b/osu.Game/Users/UserGridPanel.cs index aac2315b2f..fe3435c248 100644 --- a/osu.Game/Users/UserGridPanel.cs +++ b/osu.Game/Users/UserGridPanel.cs @@ -91,6 +91,7 @@ namespace osu.Game.Users Children = new Drawable[] { CreateFlag(), + // supporter icon is being added later } } }, @@ -108,6 +109,7 @@ namespace osu.Game.Users }, new[] { + // padding Empty(), Empty() }, diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index d38f5d8ccc..ef5c95c422 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -100,13 +100,9 @@ namespace osu.Game.Users protected abstract Drawable CreateLayout(); - protected OsuSpriteText CreateUsername() => new OsuSpriteText - { - Font = OsuFont.GetFont(size: 16, weight: FontWeight.Bold), - Shadow = false, - Text = User.Username, - }; - + /// + /// Panel background container. Can be null if a panel doesn't want a background under it's layout + /// protected virtual Drawable? CreateBackground() => Background = new UserCoverBackground { RelativeSizeAxes = Axes.Both, @@ -115,6 +111,13 @@ namespace osu.Game.Users User = User }; + protected OsuSpriteText CreateUsername() => new OsuSpriteText + { + Font = OsuFont.GetFont(size: 16, weight: FontWeight.Bold), + Shadow = false, + Text = User.Username, + }; + protected UpdateableAvatar CreateAvatar() => new UpdateableAvatar(User, false); protected UpdateableFlag CreateFlag() => new UpdateableFlag(User.CountryCode) diff --git a/osu.Game/Users/UserRankPanel.cs b/osu.Game/Users/UserRankPanel.cs index 74ff0a06dd..a2e234cf82 100644 --- a/osu.Game/Users/UserRankPanel.cs +++ b/osu.Game/Users/UserRankPanel.cs @@ -96,6 +96,7 @@ namespace osu.Game.Users { new[] { + // padding Empty(), Empty(), Empty(), @@ -103,7 +104,7 @@ namespace osu.Game.Users }, new[] { - Empty(), + Empty(), // padding CreateAvatar().With(avatar => { avatar.Size = new Vector2(60); @@ -138,6 +139,7 @@ namespace osu.Game.Users Children = new Drawable[] { CreateFlag(), + // supporter icon is being added later } } }, @@ -152,7 +154,7 @@ namespace osu.Game.Users } } }, - Empty() + Empty() // padding } } } From e240443c467436a470c12fd77b3035b7720a8cdd Mon Sep 17 00:00:00 2001 From: StanR Date: Wed, 3 Jan 2024 18:15:32 +0600 Subject: [PATCH 2307/4092] Update `LocalUser` statistics, add test --- .../Online/TestSceneSoloStatisticsWatcher.cs | 20 +++++++++++++++++++ osu.Game/Online/API/APIAccess.cs | 3 +++ osu.Game/Online/API/DummyAPIAccess.cs | 3 +++ 3 files changed, 26 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs b/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs index e62e53bd02..be819afa3e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs @@ -268,6 +268,26 @@ namespace osu.Game.Tests.Visual.Online AddAssert("update not received", () => update == null); } + [Test] + public void TestGlobalStatisticsUpdatedAfterRegistrationAddedAndScoreProcessed() + { + int userId = getUserId(); + long scoreId = getScoreId(); + setUpUser(userId); + + var ruleset = new OsuRuleset().RulesetInfo; + + SoloStatisticsUpdate? update = null; + registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate); + + feignScoreProcessing(userId, ruleset, 5_000_000); + + AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId)); + AddUntilStep("update received", () => update != null); + AddAssert("local user values are correct", () => dummyAPI.LocalUser.Value.Statistics.TotalScore, () => Is.EqualTo(5_000_000)); + AddAssert("statistics values are correct", () => dummyAPI.Statistics.Value!.TotalScore, () => Is.EqualTo(5_000_000)); + } + private int nextUserId = 2000; private long nextScoreId = 50000; diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 3910f1efea..17bf8bcc37 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -523,6 +523,9 @@ namespace osu.Game.Online.API public void UpdateStatistics(UserStatistics newStatistics) { statistics.Value = newStatistics; + + if (IsLoggedIn) + localUser.Value.Statistics = newStatistics; } private static APIUser createGuestUser() => new GuestUser(); diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index aa8658cc83..4b4f8061e0 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -137,6 +137,9 @@ namespace osu.Game.Online.API public void UpdateStatistics(UserStatistics newStatistics) { Statistics.Value = newStatistics; + + if (IsLoggedIn) + LocalUser.Value.Statistics = newStatistics; } public IHubClientConnector? GetHubConnector(string clientName, string endpoint, bool preferMessagePack) => null; From 2c64db06287b40811e742f785664eafac09093c5 Mon Sep 17 00:00:00 2001 From: wooster0 Date: Thu, 4 Jan 2024 12:15:48 +0900 Subject: [PATCH 2308/4092] Use already existing message placeholder + localized string --- osu.Game/Screens/Ranking/ResultsScreen.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index e18ab63706..8823e4d320 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -19,10 +19,11 @@ using osu.Framework.Input.Events; using osu.Framework.Screens; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; +using osu.Game.Localisation; using osu.Game.Online.API; +using osu.Game.Online.Placeholders; using osu.Game.Scoring; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking.Statistics; @@ -208,13 +209,7 @@ namespace osu.Game.Screens.Ranking if (ScorePanelList.IsEmpty) { // This can happen if a beatmap part of a playlist hasn't been played yet. - VerticalScrollContent.Add(new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 32, weight: FontWeight.Regular), - Text = "no scores yet!", - }); + VerticalScrollContent.Add(new MessagePlaceholder(LeaderboardStrings.NoRecordsYet)); } else { From 92c45662874d2c4e3992124a8aeabcca90fa1862 Mon Sep 17 00:00:00 2001 From: wooster0 Date: Thu, 4 Jan 2024 12:20:05 +0900 Subject: [PATCH 2309/4092] Fix typo --- osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index d4b6bc2b91..6c87553971 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -275,7 +275,7 @@ Phasellus eu nunc nec ligula semper fringilla. Aliquam magna neque, placerat sed AddStep("set content", () => { markdownContainer.Text = @" -This is a paragraph containing `inline code` synatax. +This is a paragraph containing `inline code` syntax. Oh wow I do love the `WikiMarkdownContainer`, it is very cool! This is a line before the fenced code block: From 659118c04353bca73182a82e6d48c533779f6777 Mon Sep 17 00:00:00 2001 From: wooster0 Date: Thu, 4 Jan 2024 12:20:51 +0900 Subject: [PATCH 2310/4092] Fix wiki link path inconsistencies If I access https://osu.ppy.sh/wiki/en/MAIN_PAGE or use any other capitalization my browser always redirects me to https://osu.ppy.sh/wiki/en/Main_page so I think Main_page is the correct capitalization. This might slightly reduce loading time? No idea though. Probably negligible if so. --- osu.Game/Overlays/Wiki/WikiHeader.cs | 2 +- osu.Game/Overlays/WikiOverlay.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Wiki/WikiHeader.cs b/osu.Game/Overlays/Wiki/WikiHeader.cs index 24eddeb0c2..55be05ed7a 100644 --- a/osu.Game/Overlays/Wiki/WikiHeader.cs +++ b/osu.Game/Overlays/Wiki/WikiHeader.cs @@ -17,7 +17,7 @@ namespace osu.Game.Overlays.Wiki { public partial class WikiHeader : BreadcrumbControlOverlayHeader { - private const string index_path = "Main_Page"; + private const string index_path = "Main_page"; public static LocalisableString IndexPageString => LayoutStrings.HeaderHelpIndex; diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index c816eca776..440e451201 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays { public partial class WikiOverlay : OnlineOverlay { - private const string index_path = @"main_page"; + private const string index_path = "Main_page"; public string CurrentPath => path.Value; @@ -161,7 +161,7 @@ namespace osu.Game.Overlays path.Value = "error"; LoadDisplay(articlePage = new WikiArticlePage($@"{api.WebsiteRootUrl}/wiki/", - $"Something went wrong when trying to fetch page \"{originalPath}\".\n\n[Return to the main page](Main_Page).")); + $"Something went wrong when trying to fetch page \"{originalPath}\".\n\n[Return to the main page](Main_page).")); } private void showParentPage() From ddc8a647640ee1c6f4c2dcf005cb7dad0b9e4700 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Jan 2024 14:55:52 +0900 Subject: [PATCH 2311/4092] Reduce spinner glow It was horrible --- osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerProgressArc.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerProgressArc.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerProgressArc.cs index 88769442a1..76afeeb2c4 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerProgressArc.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerProgressArc.cs @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon RelativeSizeAxes = Axes.Both, InnerRadius = arc_radius, RoundedCaps = true, - GlowColour = new Color4(171, 255, 255, 255) + GlowColour = new Color4(171, 255, 255, 180) } }; } From cf5f0a2bdc79e50b178f9b41d08a7ab52b1b5a9f Mon Sep 17 00:00:00 2001 From: wooster0 Date: Thu, 4 Jan 2024 15:01:27 +0900 Subject: [PATCH 2312/4092] Make chat commands case-insensitive Would be nice if I accidentally have caps lock enabled and write "/HELP" it still works. --- .../Chat/TestSceneChannelManager.cs | 19 ++++++++++++++++++- osu.Game/Online/Chat/ChannelManager.cs | 2 +- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Chat/TestSceneChannelManager.cs b/osu.Game.Tests/Chat/TestSceneChannelManager.cs index 3a4c55c65c..eae12edebd 100644 --- a/osu.Game.Tests/Chat/TestSceneChannelManager.cs +++ b/osu.Game.Tests/Chat/TestSceneChannelManager.cs @@ -112,7 +112,7 @@ namespace osu.Game.Tests.Chat }); AddStep("post message", () => channelManager.PostMessage("Something interesting")); - AddUntilStep("message postesd", () => !channel.Messages.Any(m => m is LocalMessage)); + AddUntilStep("message posted", () => !channel.Messages.Any(m => m is LocalMessage)); AddStep("post /help command", () => channelManager.PostCommand("help", channel)); AddStep("post /me command with no action", () => channelManager.PostCommand("me", channel)); @@ -146,6 +146,23 @@ namespace osu.Game.Tests.Chat AddAssert("channel has no more messages", () => channel.Messages, () => Is.Empty); } + [Test] + public void TestCommandNameCaseInsensitivity() + { + Channel channel = null; + + AddStep("join channel and select it", () => + { + channelManager.JoinChannel(channel = createChannel(1, ChannelType.Public)); + channelManager.CurrentChannel.Value = channel; + }); + + AddStep("post /me command", () => channelManager.PostCommand("ME DANCES")); + AddUntilStep("/me command received", () => channel.Messages.Last().Content.Contains("DANCES")); + AddStep("post /help command", () => channelManager.PostCommand("HeLp")); + AddUntilStep("/help command received", () => channel.Messages.Last().Content.Contains("Supported commands")); + } + private void handlePostMessageRequest(PostMessageRequest request) { var message = new Message(++currentMessageId) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index e95bc128c8..23989caae2 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -247,7 +247,7 @@ namespace osu.Game.Online.Chat string command = parameters[0]; string content = parameters.Length == 2 ? parameters[1] : string.Empty; - switch (command) + switch (command.ToLowerInvariant()) { case "np": AddInternal(new NowPlayingCommand(target)); From cd9bf0c753c0d4e6ca86c4ccc17fe1a668654e8e Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 3 Jan 2024 21:30:46 -0800 Subject: [PATCH 2313/4092] Flash blocking ongoing operations dialog when trying to force quit --- osu.Game/Screens/Menu/MainMenu.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 516b090a16..14c950d726 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; +using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -25,6 +26,7 @@ using osu.Game.Input.Bindings; using osu.Game.IO; using osu.Game.Online.API; using osu.Game.Overlays; +using osu.Game.Overlays.Dialog; using osu.Game.Overlays.SkinEditor; using osu.Game.Rulesets; using osu.Game.Screens.Backgrounds; @@ -390,7 +392,12 @@ namespace osu.Game.Screens.Menu if (requiresConfirmation) { if (dialogOverlay.CurrentDialog is ConfirmExitDialog exitDialog) - exitDialog.PerformOkAction(); + { + if (exitDialog.Buttons.OfType().FirstOrDefault() != null) + exitDialog.PerformOkAction(); + else + exitDialog.Flash(); + } else { dialogOverlay.Push(new ConfirmExitDialog(() => From ea714c86d427cb6fa498b1d76a02e6ee389dae6f Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 3 Jan 2024 22:22:25 -0800 Subject: [PATCH 2314/4092] Fix potential null reference with flash sample when exiting rapidly Fixes `TestForceExitWithOperationInProgress()`. --- osu.Game/Overlays/Dialog/PopupDialog.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index 4048b35e78..4ac37a63e2 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Dialog private readonly Vector2 ringMinifiedSize = new Vector2(20f); private readonly Box flashLayer; - private Sample flashSample = null!; + private Sample? flashSample; private readonly Container content; private readonly Container ring; @@ -267,7 +267,7 @@ namespace osu.Game.Overlays.Dialog flashLayer.FadeInFromZero(80, Easing.OutQuint) .Then() .FadeOutFromOne(1500, Easing.OutQuint); - flashSample.Play(); + flashSample?.Play(); } protected override bool OnKeyDown(KeyDownEvent e) From 1beb3f5462329262b902472227a3b6c097714092 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Jan 2024 15:45:13 +0900 Subject: [PATCH 2315/4092] Add failing test coverage of fast streams not working in editor --- .../TestSceneEditorAutoplayFastStreams.cs | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Editor/TestSceneEditorAutoplayFastStreams.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneEditorAutoplayFastStreams.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneEditorAutoplayFastStreams.cs new file mode 100644 index 0000000000..cf5cd809ef --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneEditorAutoplayFastStreams.cs @@ -0,0 +1,51 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Osu.Tests.Editor +{ + /// + /// This test covers autoplay working correctly in the editor on fast streams. + /// Might seem like a weird test, but frame stability being toggled can cause autoplay to operation incorrectly. + /// This is clearly a bug with the autoplay algorithm, but is worked around at an editor level for now. + /// + public partial class TestSceneEditorAutoplayFastStreams : EditorTestScene + { + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + { + var testBeatmap = new TestBeatmap(ruleset, false); + testBeatmap.HitObjects.AddRange(new[] + { + new HitCircle { StartTime = 500 }, + new HitCircle { StartTime = 530 }, + new HitCircle { StartTime = 560 }, + new HitCircle { StartTime = 590 }, + new HitCircle { StartTime = 620 }, + }); + + return testBeatmap; + } + + protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + + [Test] + public void TestAllHit() + { + AddStep("start playback", () => EditorClock.Start()); + AddUntilStep("wait for all hit", () => + { + DrawableHitCircle[] hitCircles = Editor.ChildrenOfType().OrderBy(s => s.HitObject.StartTime).ToArray(); + + return hitCircles.Length == 5 && hitCircles.All(h => h.IsHit); + }); + } + } +} From 65c29b4f09d49d772cfe2c9934c4d1ee65e9b384 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Jan 2024 13:18:41 +0900 Subject: [PATCH 2316/4092] Make editor remain frame stable during normal playback --- .../Edit/DrawableEditorRulesetWrapper.cs | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs b/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs index 174b278d89..ebf06bcc4e 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.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 System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.ObjectExtensions; @@ -26,6 +27,9 @@ namespace osu.Game.Rulesets.Edit [Resolved] private EditorBeatmap beatmap { get; set; } = null!; + [Resolved] + private EditorClock editorClock { get; set; } = null!; + public DrawableEditorRulesetWrapper(DrawableRuleset drawableRuleset) { this.drawableRuleset = drawableRuleset; @@ -38,7 +42,6 @@ namespace osu.Game.Rulesets.Edit [BackgroundDependencyLoader] private void load() { - drawableRuleset.FrameStablePlayback = false; Playfield.DisplayJudgements.Value = false; } @@ -65,6 +68,22 @@ namespace osu.Game.Rulesets.Edit Scheduler.AddOnce(regenerateAutoplay); } + protected override void Update() + { + base.Update(); + + // Whenever possible, we want to stay in frame stability playback. + // Without doing so, we run into bugs with some gameplay elements not behaving as expected. + // + // Note that this is not using EditorClock.IsSeeking as that would exit frame stability + // on all seeks. The intention here is to retain frame stability for small seeks. + // + // I still think no gameplay elements should require frame stability in the first place, but maybe that ship has sailed already.. + bool shouldBypassFrameStability = Math.Abs(drawableRuleset.FrameStableClock.CurrentTime - editorClock.CurrentTime) > 1000; + + drawableRuleset.FrameStablePlayback = !shouldBypassFrameStability; + } + private void regenerateAutoplay() { var autoplayMod = drawableRuleset.Mods.OfType().Single(); From 3419c59b062ba0ac9670d9279029d6fbb062aac0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Jan 2024 15:33:18 +0900 Subject: [PATCH 2317/4092] Fix failing tests due to frame stable seeks taking longer --- .../Editor/TestSceneOsuComposerSelection.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs index 623cefff6b..366f17daee 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs @@ -46,10 +46,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor moveMouseToObject(() => slider); AddStep("seek after end", () => EditorClock.Seek(750)); + AddUntilStep("wait for seek", () => !EditorClock.IsSeeking); AddStep("left click", () => InputManager.Click(MouseButton.Left)); AddAssert("slider not selected", () => EditorBeatmap.SelectedHitObjects.Count == 0); AddStep("seek to visible", () => EditorClock.Seek(650)); + AddUntilStep("wait for seek", () => !EditorClock.IsSeeking); AddStep("left click", () => InputManager.Click(MouseButton.Left)); AddUntilStep("slider selected", () => EditorBeatmap.SelectedHitObjects.Single() == slider); } From b1813b17a2f84b5ee318a6db89fdff588356678b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Jan 2024 16:39:50 +0900 Subject: [PATCH 2318/4092] Few new rider inspection --- osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs index 69070b0b64..76ed5063b0 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs @@ -104,7 +104,7 @@ namespace osu.Game.Tests.Visual.Editing if (sameRuleset) { AddUntilStep("prompt for save dialog shown", () => DialogOverlay.CurrentDialog is PromptForSaveDialog); - AddStep("discard changes", () => ((PromptForSaveDialog)DialogOverlay.CurrentDialog).PerformOkAction()); + AddStep("discard changes", () => ((PromptForSaveDialog)DialogOverlay.CurrentDialog)?.PerformOkAction()); } // ensure editor loader didn't resume. From df99a37254b930b7369c754d09b3ad2ce9d115c3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Jan 2024 17:04:36 +0900 Subject: [PATCH 2319/4092] Fix another realm null inspection --- osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs index bb4e06654a..015c2c62ed 100644 --- a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs +++ b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs @@ -132,8 +132,8 @@ namespace osu.Game.Tests.Beatmaps public AudioManager AudioManager => Audio; public IResourceStore Files => userSkinResourceStore; public new IResourceStore Resources => base.Resources; - public IResourceStore CreateTextureLoaderStore(IResourceStore underlyingStore) => null; - RealmAccess IStorageResourceProvider.RealmAccess => null; + public IResourceStore CreateTextureLoaderStore(IResourceStore underlyingStore) => throw new NotImplementedException(); + RealmAccess IStorageResourceProvider.RealmAccess => throw new NotImplementedException(); #endregion From f0aeeeea966f06add12cf2bca3dd48dac8573e82 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Jan 2024 17:13:21 +0900 Subject: [PATCH 2320/4092] ...in a safer way --- osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs index 015c2c62ed..1f491be7e3 100644 --- a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs +++ b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs @@ -132,8 +132,8 @@ namespace osu.Game.Tests.Beatmaps public AudioManager AudioManager => Audio; public IResourceStore Files => userSkinResourceStore; public new IResourceStore Resources => base.Resources; - public IResourceStore CreateTextureLoaderStore(IResourceStore underlyingStore) => throw new NotImplementedException(); - RealmAccess IStorageResourceProvider.RealmAccess => throw new NotImplementedException(); + public IResourceStore CreateTextureLoaderStore(IResourceStore underlyingStore) => null!; + RealmAccess IStorageResourceProvider.RealmAccess => null!; #endregion From 0bbc27e380b7fb2d430322edba27e8b0122f583f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Jan 2024 16:41:52 +0900 Subject: [PATCH 2321/4092] Add a gameplay configuration flag to disable fail animation --- .../Multiplayer/MultiplayerPlayer.cs | 4 +- osu.Game/Screens/Play/GameplayState.cs | 2 +- osu.Game/Screens/Play/Player.cs | 57 +++++++++++-------- osu.Game/Screens/Play/PlayerConfiguration.cs | 6 ++ 4 files changed, 40 insertions(+), 29 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index e6d9dd4cd0..d9043df1d5 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -26,9 +26,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { protected override bool PauseOnFocusLost => false; - // Disallow fails in multiplayer for now. - protected override bool CheckModsAllowFailure() => false; - protected override UserActivity InitialActivity => new UserActivity.InMultiplayerGame(Beatmap.Value.BeatmapInfo, Ruleset.Value); [Resolved] @@ -55,6 +52,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { AllowPause = false, AllowRestart = false, + AllowFailAnimation = false, AllowSkipping = room.AutoSkip.Value, AutomaticallySkipIntro = room.AutoSkip.Value, AlwaysShowLeaderboard = true, diff --git a/osu.Game/Screens/Play/GameplayState.cs b/osu.Game/Screens/Play/GameplayState.cs index c2162d4df2..cc399a0fbe 100644 --- a/osu.Game/Screens/Play/GameplayState.cs +++ b/osu.Game/Screens/Play/GameplayState.cs @@ -46,7 +46,7 @@ namespace osu.Game.Screens.Play public bool HasPassed { get; set; } /// - /// Whether the user failed during gameplay. + /// Whether the user failed during gameplay. This is only set when the gameplay session has completed due to the fail. /// public bool HasFailed { get; set; } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index c960ac357f..df50e35986 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -735,7 +735,7 @@ namespace osu.Game.Screens.Play } // Only show the completion screen if the player hasn't failed - if (HealthProcessor.HasFailed) + if (GameplayState.HasFailed) return; GameplayState.HasPassed = true; @@ -924,37 +924,44 @@ namespace osu.Game.Screens.Play if (!CheckModsAllowFailure()) return false; - Debug.Assert(!GameplayState.HasFailed); - Debug.Assert(!GameplayState.HasPassed); - Debug.Assert(!GameplayState.HasQuit); + if (Configuration.AllowFailAnimation) + { + Debug.Assert(!GameplayState.HasFailed); + Debug.Assert(!GameplayState.HasPassed); + Debug.Assert(!GameplayState.HasQuit); - GameplayState.HasFailed = true; + GameplayState.HasFailed = true; - updateGameplayState(); + updateGameplayState(); - // There is a chance that we could be in a paused state as the ruleset's internal clock (see FrameStabilityContainer) - // could process an extra frame after the GameplayClock is stopped. - // In such cases we want the fail state to precede a user triggered pause. - if (PauseOverlay.State.Value == Visibility.Visible) - PauseOverlay.Hide(); + // There is a chance that we could be in a paused state as the ruleset's internal clock (see FrameStabilityContainer) + // could process an extra frame after the GameplayClock is stopped. + // In such cases we want the fail state to precede a user triggered pause. + if (PauseOverlay.State.Value == Visibility.Visible) + PauseOverlay.Hide(); - failAnimationContainer.Start(); + failAnimationContainer.Start(); - // Failures can be triggered either by a judgement, or by a mod. - // - // For the case of a judgement, due to ordering considerations, ScoreProcessor will not have received - // the final judgement which triggered the failure yet (see DrawableRuleset.NewResult handling above). - // - // A schedule here ensures that any lingering judgements from the current frame are applied before we - // finalise the score as "failed". - Schedule(() => + // Failures can be triggered either by a judgement, or by a mod. + // + // For the case of a judgement, due to ordering considerations, ScoreProcessor will not have received + // the final judgement which triggered the failure yet (see DrawableRuleset.NewResult handling above). + // + // A schedule here ensures that any lingering judgements from the current frame are applied before we + // finalise the score as "failed". + Schedule(() => + { + ScoreProcessor.FailScore(Score.ScoreInfo); + OnFail(); + + if (GameplayState.Mods.OfType().Any(m => m.RestartOnFail)) + Restart(true); + }); + } + else { ScoreProcessor.FailScore(Score.ScoreInfo); - OnFail(); - - if (GameplayState.Mods.OfType().Any(m => m.RestartOnFail)) - Restart(true); - }); + } return true; } diff --git a/osu.Game/Screens/Play/PlayerConfiguration.cs b/osu.Game/Screens/Play/PlayerConfiguration.cs index 122e25f406..466a691118 100644 --- a/osu.Game/Screens/Play/PlayerConfiguration.cs +++ b/osu.Game/Screens/Play/PlayerConfiguration.cs @@ -15,6 +15,12 @@ namespace osu.Game.Screens.Play /// public bool ShowResults { get; set; } = true; + /// + /// Whether the fail animation / screen should be triggered on failing. + /// If false, the score will still be marked as failed but gameplay will continue. + /// + public bool AllowFailAnimation { get; set; } = true; + /// /// Whether the player should be allowed to trigger a restart. /// From 705f25e4b990c0ab726c29d6541121f7eb47b034 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Jan 2024 16:42:05 +0900 Subject: [PATCH 2322/4092] Make `ScoreProcessor.Rank` read-only --- osu.Game/Rulesets/Mods/ModFlashlight.cs | 3 --- osu.Game/Rulesets/Mods/ModHidden.cs | 2 -- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 14 ++++++++------ osu.Game/Screens/Play/BreakOverlay.cs | 4 +++- osu.Game/Screens/Play/Player.cs | 2 -- 5 files changed, 11 insertions(+), 14 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 95406cc9e6..dc2ad6f47e 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -56,9 +56,6 @@ namespace osu.Game.Rulesets.Mods public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) { Combo.BindTo(scoreProcessor.Combo); - - // Default value of ScoreProcessor's Rank in Flashlight Mod should be SS+ - scoreProcessor.Rank.Value = ScoreRank.XH; } public ScoreRank AdjustRank(ScoreRank rank, double accuracy) diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index 5a8226115f..8b25768575 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -17,8 +17,6 @@ namespace osu.Game.Rulesets.Mods public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) { - // Default value of ScoreProcessor's Rank in Hidden Mod should be SS+ - scoreProcessor.Rank.Value = ScoreRank.XH; } public ScoreRank AdjustRank(ScoreRank rank, double accuracy) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 837bb4080e..4ef65c55ab 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -86,7 +86,9 @@ namespace osu.Game.Rulesets.Scoring /// /// The current rank. /// - public readonly Bindable Rank = new Bindable(ScoreRank.X); + public IBindable Rank => rank; + + private readonly Bindable rank = new Bindable(ScoreRank.X); /// /// The highest combo achieved by this score. @@ -186,9 +188,9 @@ namespace osu.Game.Rulesets.Scoring Combo.ValueChanged += combo => HighestCombo.Value = Math.Max(HighestCombo.Value, combo.NewValue); Accuracy.ValueChanged += accuracy => { - Rank.Value = RankFromAccuracy(accuracy.NewValue); + rank.Value = RankFromAccuracy(accuracy.NewValue); foreach (var mod in Mods.Value.OfType()) - Rank.Value = mod.AdjustRank(Rank.Value, accuracy.NewValue); + rank.Value = mod.AdjustRank(Rank.Value, accuracy.NewValue); }; Mods.ValueChanged += mods => @@ -411,8 +413,8 @@ namespace osu.Game.Rulesets.Scoring TotalScore.Value = 0; Accuracy.Value = 1; Combo.Value = 0; - Rank.Disabled = false; - Rank.Value = ScoreRank.X; + rank.Disabled = false; + rank.Value = ScoreRank.X; HighestCombo.Value = 0; } @@ -448,7 +450,7 @@ namespace osu.Game.Rulesets.Scoring return; score.Passed = false; - Rank.Value = ScoreRank.F; + rank.Value = ScoreRank.F; PopulateScore(score); } diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 3ca82ec00b..e18612c955 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -4,12 +4,14 @@ #nullable disable using System.Collections.Generic; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osu.Game.Screens.Play.Break; namespace osu.Game.Screens.Play @@ -113,7 +115,7 @@ namespace osu.Game.Screens.Play if (scoreProcessor != null) { info.AccuracyDisplay.Current.BindTo(scoreProcessor.Accuracy); - info.GradeDisplay.Current.BindTo(scoreProcessor.Rank); + ((IBindable)info.GradeDisplay.Current).BindTo(scoreProcessor.Rank); } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index df50e35986..b87306b9a2 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -801,8 +801,6 @@ namespace osu.Game.Screens.Play // This player instance may already be in the process of exiting. return; - Debug.Assert(ScoreProcessor.Rank.Value != ScoreRank.F); - this.Push(CreateResults(prepareScoreForDisplayTask.GetResultSafely())); }, Time.Current + delay, 50); From a4dee1a01af5c572f4fdd48c58caa754e542a32b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Jan 2024 16:51:25 +0900 Subject: [PATCH 2323/4092] Don't unset `Disabled` on rank (never actually disabled?) --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 4ef65c55ab..6d2b43a3e7 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -413,7 +413,6 @@ namespace osu.Game.Rulesets.Scoring TotalScore.Value = 0; Accuracy.Value = 1; Combo.Value = 0; - rank.Disabled = false; rank.Value = ScoreRank.X; HighestCombo.Value = 0; } From b12011d501ab28a34932176ab72854f4ed19fa52 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Jan 2024 17:04:45 +0900 Subject: [PATCH 2324/4092] Avoid rank updates after failing --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 6d2b43a3e7..13c5d523da 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -188,6 +188,10 @@ namespace osu.Game.Rulesets.Scoring Combo.ValueChanged += combo => HighestCombo.Value = Math.Max(HighestCombo.Value, combo.NewValue); Accuracy.ValueChanged += accuracy => { + // Once failed, we shouldn't update the rank anymore. + if (rank.Value == ScoreRank.F) + return; + rank.Value = RankFromAccuracy(accuracy.NewValue); foreach (var mod in Mods.Value.OfType()) rank.Value = mod.AdjustRank(Rank.Value, accuracy.NewValue); From 44d35020d16a1f445abd65758dd37a939de44b3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Jan 2024 17:54:52 +0900 Subject: [PATCH 2325/4092] Add test coverage of failed multiplayer score --- osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 16030d568b..462286335a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -29,6 +29,7 @@ using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Scoring; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.OnlinePlay.Lounge.Components; @@ -690,6 +691,13 @@ namespace osu.Game.Tests.Visual.Multiplayer } AddUntilStep("wait for results", () => multiplayerComponents.CurrentScreen is ResultsScreen); + + AddAssert("check is fail", () => + { + var scoreInfo = ((ResultsScreen)multiplayerComponents.CurrentScreen).Score; + + return !scoreInfo.Passed && scoreInfo.Rank == ScoreRank.F; + }); } [Test] From fc1a2c594cbf53322b8e10aea3ce173dd6d9d82d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Jan 2024 18:02:37 +0900 Subject: [PATCH 2326/4092] Add a bit more test coverage --- osu.Game.Tests/Mods/ModUtilsTest.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Mods/ModUtilsTest.cs b/osu.Game.Tests/Mods/ModUtilsTest.cs index f1db146f3a..13da69871e 100644 --- a/osu.Game.Tests/Mods/ModUtilsTest.cs +++ b/osu.Game.Tests/Mods/ModUtilsTest.cs @@ -314,11 +314,20 @@ namespace osu.Game.Tests.Mods public void TestFormatScoreMultiplier() { Assert.AreEqual(ModUtils.FormatScoreMultiplier(0.9999).ToString(), "0.99x"); + Assert.AreEqual(ModUtils.FormatScoreMultiplier(1.0).ToString(), "1.00x"); Assert.AreEqual(ModUtils.FormatScoreMultiplier(1.0001).ToString(), "1.01x"); + Assert.AreEqual(ModUtils.FormatScoreMultiplier(0.899999999999999).ToString(), "0.90x"); - Assert.AreEqual(ModUtils.FormatScoreMultiplier(1.099999999999999).ToString(), "1.10x"); + Assert.AreEqual(ModUtils.FormatScoreMultiplier(0.9).ToString(), "0.90x"); Assert.AreEqual(ModUtils.FormatScoreMultiplier(0.900000000000001).ToString(), "0.90x"); + + Assert.AreEqual(ModUtils.FormatScoreMultiplier(1.099999999999999).ToString(), "1.10x"); + Assert.AreEqual(ModUtils.FormatScoreMultiplier(1.1).ToString(), "1.10x"); Assert.AreEqual(ModUtils.FormatScoreMultiplier(1.100000000000001).ToString(), "1.10x"); + + Assert.AreEqual(ModUtils.FormatScoreMultiplier(1.045).ToString(), "1.05x"); + Assert.AreEqual(ModUtils.FormatScoreMultiplier(1.05).ToString(), "1.05x"); + Assert.AreEqual(ModUtils.FormatScoreMultiplier(1.055).ToString(), "1.06x"); } public abstract class CustomMod1 : Mod, IModCompatibilitySpecification From adac3b65cea7a896907ed5451abedc1e241e866f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Jan 2024 18:48:13 +0900 Subject: [PATCH 2327/4092] Fix beatmap carousel not preloading panels when off-screen --- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 +- .../Carousel/DrawableCarouselBeatmapSet.cs | 73 +++++++++++++------ 2 files changed, 51 insertions(+), 26 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 89911c9a69..3353aeed7d 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -96,12 +96,12 @@ namespace osu.Game.Screens.Select /// /// Extend the range to retain already loaded pooled drawables. /// - private const float distance_offscreen_before_unload = 1024; + private const float distance_offscreen_before_unload = 2048; /// /// Extend the range to update positions / retrieve pooled drawables outside of visible range. /// - private const float distance_offscreen_to_preload = 512; // todo: adjust this appropriately once we can make set panel contents load while off-screen. + private const float distance_offscreen_to_preload = 768; /// /// Whether carousel items have completed asynchronously loaded. diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index f16e92a82a..c24e09582e 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -46,6 +47,8 @@ namespace osu.Game.Screens.Select.Carousel private MenuItem[]? mainMenuItems; + private double timeSinceUnpool; + [Resolved] private BeatmapManager manager { get; set; } = null!; @@ -54,6 +57,7 @@ namespace osu.Game.Screens.Select.Carousel base.FreeAfterUse(); Item = null; + timeSinceUnpool = 0; ClearTransforms(); } @@ -92,13 +96,21 @@ namespace osu.Game.Screens.Select.Carousel // algorithm for this is taken from ScrollContainer. // while it doesn't necessarily need to match 1:1, as we are emulating scroll in some cases this feels most correct. Y = (float)Interpolation.Lerp(targetY, Y, Math.Exp(-0.01 * Time.Elapsed)); + + loadContentIfRequired(); } + private CancellationTokenSource? loadCancellation; + protected override void UpdateItem() { + loadCancellation?.Cancel(); + loadCancellation = null; + base.UpdateItem(); Content.Clear(); + Header.Clear(); beatmapContainer = null; beatmapsLoadTask = null; @@ -107,32 +119,8 @@ namespace osu.Game.Screens.Select.Carousel return; beatmapSet = ((CarouselBeatmapSet)Item).BeatmapSet; - - DelayedLoadWrapper background; - DelayedLoadWrapper mainFlow; - - Header.Children = new Drawable[] - { - // Choice of background image matches BSS implementation (always uses the lowest `beatmap_id` from the set). - background = new DelayedLoadWrapper(() => new SetPanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.MinBy(b => b.OnlineID))) - { - RelativeSizeAxes = Axes.Both, - }, 200) - { - RelativeSizeAxes = Axes.Both - }, - mainFlow = new DelayedLoadWrapper(() => new SetPanelContent((CarouselBeatmapSet)Item), 50) - { - RelativeSizeAxes = Axes.Both - }, - }; - - background.DelayedLoadComplete += fadeContentIn; - mainFlow.DelayedLoadComplete += fadeContentIn; } - private void fadeContentIn(Drawable d) => d.FadeInFromZero(150); - protected override void Deselected() { base.Deselected(); @@ -190,6 +178,43 @@ namespace osu.Game.Screens.Select.Carousel } } + private void loadContentIfRequired() + { + // Using DelayedLoadWrappers would only allow us to load content when on screen, but we want to preload while off-screen + // to provide a better user experience. + + // This is tracking time that this drawable is updating since the last pool. + // This is intended to provide a debounce so very fast scrolls (from one end to the other of the carousel) + // don't cause huge overheads. + const double time_updating_before_load = 150; + + Debug.Assert(Item != null); + + if (loadCancellation == null && (timeSinceUnpool += Time.Elapsed) > time_updating_before_load) + { + loadCancellation = new CancellationTokenSource(); + + LoadComponentAsync(new SetPanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.MinBy(b => b.OnlineID))) + { + RelativeSizeAxes = Axes.Both, + }, background => + { + Header.Add(background); + background.FadeInFromZero(150); + }, loadCancellation.Token); + + LoadComponentAsync(new SetPanelContent((CarouselBeatmapSet)Item) + { + Depth = float.MinValue, + RelativeSizeAxes = Axes.Both, + }, mainFlow => + { + Header.Add(mainFlow); + mainFlow.FadeInFromZero(150); + }, loadCancellation.Token); + } + } + private void updateBeatmapYPositions() { if (beatmapContainer == null) From 81c6fd5589751e125a8728ad57eb286dc4b146f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Jan 2024 19:13:36 +0900 Subject: [PATCH 2328/4092] Load items closer to the centre of the screen as a priority --- osu.Game/Screens/Select/BeatmapCarousel.cs | 3 +- .../Carousel/DrawableCarouselBeatmapSet.cs | 41 +++++++++++-------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 3353aeed7d..4408634787 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -108,6 +108,7 @@ namespace osu.Game.Screens.Select /// public bool BeatmapSetsLoaded { get; private set; } + [Cached] protected readonly CarouselScrollContainer Scroll; private readonly NoResultsPlaceholder noResultsPlaceholder; @@ -1251,7 +1252,7 @@ namespace osu.Game.Screens.Select } } - protected partial class CarouselScrollContainer : UserTrackingScrollContainer + public partial class CarouselScrollContainer : UserTrackingScrollContainer { private bool rightMouseScrollBlocked; diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index c24e09582e..61658526db 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -8,9 +8,11 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; using osu.Framework.Utils; using osu.Game.Beatmaps; @@ -178,39 +180,44 @@ namespace osu.Game.Screens.Select.Carousel } } + [Resolved] + private BeatmapCarousel.CarouselScrollContainer scrollContainer { get; set; } = null!; + private void loadContentIfRequired() { + Quad containingSsdq = scrollContainer.ScreenSpaceDrawQuad; + // Using DelayedLoadWrappers would only allow us to load content when on screen, but we want to preload while off-screen // to provide a better user experience. // This is tracking time that this drawable is updating since the last pool. // This is intended to provide a debounce so very fast scrolls (from one end to the other of the carousel) // don't cause huge overheads. - const double time_updating_before_load = 150; + // + // We increase the delay based on distance from centre, so the beatmaps the user is currently looking at load first. + float timeUpdatingBeforeLoad = 50 + Math.Abs(containingSsdq.Centre.Y - ScreenSpaceDrawQuad.Centre.Y) / containingSsdq.Height * 100; Debug.Assert(Item != null); - if (loadCancellation == null && (timeSinceUnpool += Time.Elapsed) > time_updating_before_load) + if (loadCancellation == null && (timeSinceUnpool += Time.Elapsed) > timeUpdatingBeforeLoad) { loadCancellation = new CancellationTokenSource(); - LoadComponentAsync(new SetPanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.MinBy(b => b.OnlineID))) + LoadComponentsAsync(new CompositeDrawable[] { - RelativeSizeAxes = Axes.Both, - }, background => + new SetPanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.MinBy(b => b.OnlineID))) + { + RelativeSizeAxes = Axes.Both, + }, + new SetPanelContent((CarouselBeatmapSet)Item) + { + Depth = float.MinValue, + RelativeSizeAxes = Axes.Both, + } + }, drawables => { - Header.Add(background); - background.FadeInFromZero(150); - }, loadCancellation.Token); - - LoadComponentAsync(new SetPanelContent((CarouselBeatmapSet)Item) - { - Depth = float.MinValue, - RelativeSizeAxes = Axes.Both, - }, mainFlow => - { - Header.Add(mainFlow); - mainFlow.FadeInFromZero(150); + Header.AddRange(drawables); + drawables.ForEach(d => d.FadeInFromZero(150)); }, loadCancellation.Token); } } From 085f5acd1abaf8cf309c5e2ccb12df367a19cc33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Jan 2024 19:26:28 +0900 Subject: [PATCH 2329/4092] Fix another rider inspection (why do these keep coming up at random) --- osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 8c32135cfd..8691f46605 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -368,7 +368,7 @@ namespace osu.Game.Tests.Visual.Online { var cardContainer = this.ChildrenOfType>().Single().Parent; var expandedContent = this.ChildrenOfType().Single(); - return expandedContent.ScreenSpaceDrawQuad.GetVertices().ToArray().All(v => cardContainer.ScreenSpaceDrawQuad.Contains(v)); + return expandedContent.ScreenSpaceDrawQuad.GetVertices().ToArray().All(v => cardContainer!.ScreenSpaceDrawQuad.Contains(v)); }); } From 9b734bac2515c5a8cf61ca3cc7576bf1e012a4b9 Mon Sep 17 00:00:00 2001 From: Zachary Date: Fri, 5 Jan 2024 01:14:34 +1000 Subject: [PATCH 2330/4092] Allow track control after intro screen finishes. --- osu.Game/Overlays/MusicController.cs | 2 +- osu.Game/Screens/Menu/MainMenu.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 0986c0513c..01086d3e33 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -43,7 +43,7 @@ namespace osu.Game.Overlays /// /// Whether user control of the global track should be allowed. /// - public readonly BindableBool AllowTrackControl = new BindableBool(true); + public readonly BindableBool AllowTrackControl = new BindableBool(false); /// /// Fired when the global has changed. diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 516b090a16..ffb68367c2 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -241,6 +241,8 @@ namespace osu.Game.Screens.Menu { var track = musicController.CurrentTrack; + musicController.AllowTrackControl.Value = true; + // presume the track is the current beatmap's track. not sure how correct this assumption is but it has worked until now. if (!track.IsRunning) { From 91bb3f6c57d7263876d5de57bab01c5d4b444b9c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Jan 2024 01:24:00 +0900 Subject: [PATCH 2331/4092] Cache argon character glyph lookups to reduce string allocations --- .../Play/HUD/ArgonCounterTextComponent.cs | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs index 2a3f4365cb..266dfb3301 100644 --- a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs +++ b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs @@ -2,6 +2,8 @@ // 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 System.Threading.Tasks; using osu.Framework.Allocation; @@ -137,33 +139,48 @@ namespace osu.Game.Screens.Play.HUD [BackgroundDependencyLoader] private void load(TextureStore textures) { + const string font_name = @"argon-counter"; + Spacing = new Vector2(-2f, 0f); - Font = new FontUsage(@"argon-counter", 1); - glyphStore = new GlyphStore(textures, getLookup); + Font = new FontUsage(font_name, 1); + glyphStore = new GlyphStore(font_name, textures, getLookup); } protected override TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) => base.CreateTextBuilder(glyphStore); private class GlyphStore : ITexturedGlyphLookupStore { + private readonly string fontName; private readonly TextureStore textures; private readonly Func getLookup; - public GlyphStore(TextureStore textures, Func getLookup) + private readonly Dictionary cache = new Dictionary(); + + public GlyphStore(string fontName, TextureStore textures, Func getLookup) { + this.fontName = fontName; this.textures = textures; this.getLookup = getLookup; } public ITexturedCharacterGlyph? Get(string? fontName, char character) { + // We only service one font. + Debug.Assert(fontName == this.fontName); + + if (cache.TryGetValue(character, out var cached)) + return cached; + string lookup = getLookup(character); var texture = textures.Get($"Gameplay/Fonts/{fontName}-{lookup}"); - if (texture == null) - return null; + TexturedCharacterGlyph? glyph = null; - return new TexturedCharacterGlyph(new CharacterGlyph(character, 0, 0, texture.Width, texture.Height, null), texture, 0.125f); + if (texture != null) + glyph = new TexturedCharacterGlyph(new CharacterGlyph(character, 0, 0, texture.Width, texture.Height, null), texture, 0.125f); + + cache[character] = glyph; + return glyph; } public Task GetAsync(string fontName, char character) => Task.Run(() => Get(fontName, character)); From b190333c17f3e0209e7f04ad1463b272b957c774 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 4 Jan 2024 09:00:24 -0800 Subject: [PATCH 2332/4092] Use repeat step for more delay between the two exits --- .../Visual/Navigation/TestSceneScreenNavigation.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index d8dc512787..a0069f55c7 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -799,11 +799,7 @@ namespace osu.Game.Tests.Visual.Navigation }); }); - AddStep("attempt exit", () => - { - for (int i = 0; i < 2; ++i) - Game.ScreenStack.CurrentScreen.Exit(); - }); + AddRepeatStep("attempt force exit", () => Game.ScreenStack.CurrentScreen.Exit(), 2); AddUntilStep("stopped at exit confirm", () => Game.ChildrenOfType().Single().CurrentDialog is ConfirmExitDialog); } From 5b55ca66920b40b394b0403f92b7f371c35cf6b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Jan 2024 02:26:26 +0900 Subject: [PATCH 2333/4092] Cache legacy skin character glyph lookups to reduce string allocations --- osu.Game/Skinning/LegacySpriteText.cs | 35 +++++++++++++++++++++------ 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/osu.Game/Skinning/LegacySpriteText.cs b/osu.Game/Skinning/LegacySpriteText.cs index 8aefa50252..81db5fdf36 100644 --- a/osu.Game/Skinning/LegacySpriteText.cs +++ b/osu.Game/Skinning/LegacySpriteText.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Diagnostics; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; @@ -44,10 +46,11 @@ namespace osu.Game.Skinning [BackgroundDependencyLoader] private void load(ISkinSource skin) { - base.Font = new FontUsage(skin.GetFontPrefix(font), 1, fixedWidth: FixedWidth); + string fontPrefix = skin.GetFontPrefix(font); + base.Font = new FontUsage(fontPrefix, 1, fixedWidth: FixedWidth); Spacing = new Vector2(-skin.GetFontOverlap(font), 0); - glyphStore = new LegacyGlyphStore(skin, MaxSizePerGlyph); + glyphStore = new LegacyGlyphStore(fontPrefix, skin, MaxSizePerGlyph); } protected override TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) => base.CreateTextBuilder(glyphStore); @@ -57,25 +60,41 @@ namespace osu.Game.Skinning private readonly ISkin skin; private readonly Vector2? maxSize; - public LegacyGlyphStore(ISkin skin, Vector2? maxSize) + private readonly string fontName; + + private readonly Dictionary cache = new Dictionary(); + + public LegacyGlyphStore(string fontName, ISkin skin, Vector2? maxSize) { + this.fontName = fontName; this.skin = skin; this.maxSize = maxSize; } public ITexturedCharacterGlyph? Get(string? fontName, char character) { + // We only service one font. + Debug.Assert(fontName == this.fontName); + + if (cache.TryGetValue(character, out var cached)) + return cached; + string lookup = getLookupName(character); var texture = skin.GetTexture($"{fontName}-{lookup}"); - if (texture == null) - return null; + TexturedCharacterGlyph? glyph = null; - if (maxSize != null) - texture = texture.WithMaximumSize(maxSize.Value); + if (texture != null) + { + if (maxSize != null) + texture = texture.WithMaximumSize(maxSize.Value); - return new TexturedCharacterGlyph(new CharacterGlyph(character, 0, 0, texture.Width, texture.Height, null), texture, 1f / texture.ScaleAdjust); + glyph = new TexturedCharacterGlyph(new CharacterGlyph(character, 0, 0, texture.Width, texture.Height, null), texture, 1f / texture.ScaleAdjust); + } + + cache[character] = glyph; + return glyph; } private static string getLookupName(char character) From e9289cfbe78f318d00c9b77065daca6fa824cb09 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Jan 2024 01:51:20 +0900 Subject: [PATCH 2334/4092] Reduce precision of audio balance adjustments during slider sliding --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 1daaa24d57..bce28361cb 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -599,7 +599,9 @@ namespace osu.Game.Rulesets.Objects.Drawables float balanceAdjustAmount = positionalHitsoundsLevel.Value * 2; double returnedValue = balanceAdjustAmount * (position - 0.5f); - return returnedValue; + // Rounded to reduce the overhead of audio adjustments (which are currently bindable heavy). + // Balance is very hard to perceive in small increments anyways. + return Math.Round(returnedValue, 2); } /// From 091241634c08aaea3b6d3e34762fb2f1cb459295 Mon Sep 17 00:00:00 2001 From: Zachary Date: Fri, 5 Jan 2024 23:55:17 +1000 Subject: [PATCH 2335/4092] Make IntroScreen set `AllowTrackControl` to false instead --- osu.Game/Overlays/MusicController.cs | 2 +- osu.Game/Screens/Menu/IntroScreen.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 01086d3e33..0986c0513c 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -43,7 +43,7 @@ namespace osu.Game.Overlays /// /// Whether user control of the global track should be allowed. /// - public readonly BindableBool AllowTrackControl = new BindableBool(false); + public readonly BindableBool AllowTrackControl = new BindableBool(true); /// /// Fired when the global has changed. diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index de7732dd5e..a81e24ee88 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -109,6 +109,8 @@ namespace osu.Game.Screens.Menu // prevent user from changing beatmap while the intro is still running. beatmap = Beatmap.BeginLease(false); + musicController.AllowTrackControl.Value = false; + MenuVoice = config.GetBindable(OsuSetting.MenuVoice); MenuMusic = config.GetBindable(OsuSetting.MenuMusic); From 8295ad1feb19c30ae944e2f8ea9bd3c5df6d04ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 2 Jan 2024 21:58:44 +0100 Subject: [PATCH 2336/4092] Change catch scoring to match score V2 --- .../Scoring/CatchScoreProcessor.cs | 60 ++++++++++++++++++- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 24 ++++---- 2 files changed, 69 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index 4b3d378889..161a59c5fd 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -20,20 +21,73 @@ namespace osu.Game.Rulesets.Catch.Scoring private const int combo_cap = 200; private const double combo_base = 4; + private double fruitTinyScale; + public CatchScoreProcessor() : base(new CatchRuleset()) { } + protected override void Reset(bool storeResults) + { + base.Reset(storeResults); + + // large ticks are *purposefully* not counted to match stable + int fruitTinyScaleDivisor = MaximumResultCounts.GetValueOrDefault(HitResult.SmallTickHit) + MaximumResultCounts.GetValueOrDefault(HitResult.Great); + fruitTinyScale = fruitTinyScaleDivisor == 0 + ? 0 + : (double)MaximumResultCounts.GetValueOrDefault(HitResult.SmallTickHit) / fruitTinyScaleDivisor; + } + protected override double ComputeTotalScore(double comboProgress, double accuracyProgress, double bonusPortion) { - return 600000 * comboProgress - + 400000 * Accuracy.Value * accuracyProgress + const int max_tiny_droplets_portion = 400000; + + double comboPortion = 1000000 - max_tiny_droplets_portion + max_tiny_droplets_portion * (1 - fruitTinyScale); + double dropletsPortion = max_tiny_droplets_portion * fruitTinyScale; + double dropletsHit = MaximumResultCounts.GetValueOrDefault(HitResult.SmallTickHit) == 0 + ? 0 + : (double)ScoreResultCounts.GetValueOrDefault(HitResult.SmallTickHit) / MaximumResultCounts.GetValueOrDefault(HitResult.SmallTickHit); + + return comboPortion * comboProgress + + dropletsPortion * dropletsHit + bonusPortion; } + public override int GetBaseScoreForResult(HitResult result) + { + switch (result) + { + // dirty hack to emulate accuracy on stable weighting every object equally in accuracy portion + case HitResult.Great: + case HitResult.LargeTickHit: + case HitResult.SmallTickHit: + return 300; + + case HitResult.LargeBonus: + return 200; + } + + return base.GetBaseScoreForResult(result); + } + protected override double GetComboScoreChange(JudgementResult result) - => GetBaseScoreForResult(result.Type) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(combo_cap, combo_base)); + { + double baseIncrease = 0; + + switch (result.Type) + { + case HitResult.Great: + baseIncrease = 300; + break; + + case HitResult.LargeTickHit: + baseIncrease = 100; + break; + } + + return baseIncrease * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(combo_cap, combo_base)); + } public override ScoreRank RankFromAccuracy(double accuracy) { diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 837bb4080e..869ad2c4ae 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -167,14 +167,14 @@ namespace osu.Game.Rulesets.Scoring if (!beatmapApplied) throw new InvalidOperationException($"Cannot access maximum statistics before calling {nameof(ApplyBeatmap)}."); - return new Dictionary(maximumResultCounts); + return new Dictionary(MaximumResultCounts); } } private bool beatmapApplied; - private readonly Dictionary scoreResultCounts = new Dictionary(); - private readonly Dictionary maximumResultCounts = new Dictionary(); + protected readonly Dictionary ScoreResultCounts = new Dictionary(); + protected readonly Dictionary MaximumResultCounts = new Dictionary(); private readonly List hitEvents = new List(); private HitObject? lastHitObject; @@ -216,7 +216,7 @@ namespace osu.Game.Rulesets.Scoring if (result.FailedAtJudgement) return; - scoreResultCounts[result.Type] = scoreResultCounts.GetValueOrDefault(result.Type) + 1; + ScoreResultCounts[result.Type] = ScoreResultCounts.GetValueOrDefault(result.Type) + 1; if (result.Type.IncreasesCombo()) Combo.Value++; @@ -272,7 +272,7 @@ namespace osu.Game.Rulesets.Scoring if (result.FailedAtJudgement) return; - scoreResultCounts[result.Type] = scoreResultCounts.GetValueOrDefault(result.Type) - 1; + ScoreResultCounts[result.Type] = ScoreResultCounts.GetValueOrDefault(result.Type) - 1; if (result.Judgement.MaxResult.AffectsAccuracy()) { @@ -394,13 +394,13 @@ namespace osu.Game.Rulesets.Scoring maximumComboPortion = currentComboPortion; maximumAccuracyJudgementCount = currentAccuracyJudgementCount; - maximumResultCounts.Clear(); - maximumResultCounts.AddRange(scoreResultCounts); + MaximumResultCounts.Clear(); + MaximumResultCounts.AddRange(ScoreResultCounts); MaximumTotalScore = TotalScore.Value; } - scoreResultCounts.Clear(); + ScoreResultCounts.Clear(); currentBaseScore = 0; currentMaximumBaseScore = 0; @@ -430,10 +430,10 @@ namespace osu.Game.Rulesets.Scoring score.MaximumStatistics.Clear(); foreach (var result in HitResultExtensions.ALL_TYPES) - score.Statistics[result] = scoreResultCounts.GetValueOrDefault(result); + score.Statistics[result] = ScoreResultCounts.GetValueOrDefault(result); foreach (var result in HitResultExtensions.ALL_TYPES) - score.MaximumStatistics[result] = maximumResultCounts.GetValueOrDefault(result); + score.MaximumStatistics[result] = MaximumResultCounts.GetValueOrDefault(result); // Populate total score after everything else. score.TotalScore = TotalScore.Value; @@ -464,8 +464,8 @@ namespace osu.Game.Rulesets.Scoring HighestCombo.Value = frame.Header.MaxCombo; TotalScore.Value = frame.Header.TotalScore; - scoreResultCounts.Clear(); - scoreResultCounts.AddRange(frame.Header.Statistics); + ScoreResultCounts.Clear(); + ScoreResultCounts.AddRange(frame.Header.Statistics); SetScoreProcessorStatistics(frame.Header.ScoreProcessorStatistics); From ea7078fab52bc1db38f4311c8822e99fa6e82e0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 3 Jan 2024 15:03:06 +0100 Subject: [PATCH 2337/4092] Implement approximate score conversion algorithm matching score V2 --- .../StandardisedScoreMigrationTools.cs | 113 +++++++++++++++++- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 3 +- 2 files changed, 113 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index 9cfb9ea957..f029d85aed 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -437,9 +437,30 @@ namespace osu.Game.Database break; case 2: + // compare logic in `CatchScoreProcessor`. + + // this could technically be slightly incorrect in the case of stable scores. + // because large droplet misses are counted as full misses in stable scores, + // `score.MaximumStatistics.GetValueOrDefault(Great)` will be equal to the count of fruits *and* large droplets + // rather than just fruits (which was the intent). + // this is not fixable without introducing an extra legacy score attribute dedicated for catch, + // and this is a ballpark conversion process anyway, so attempt to trudge on. + int fruitTinyScaleDivisor = score.MaximumStatistics.GetValueOrDefault(HitResult.SmallTickHit) + score.MaximumStatistics.GetValueOrDefault(HitResult.Great); + double fruitTinyScale = fruitTinyScaleDivisor == 0 + ? 0 + : (double)score.MaximumStatistics.GetValueOrDefault(HitResult.SmallTickHit) / fruitTinyScaleDivisor; + + const int max_tiny_droplets_portion = 400000; + + double comboPortion = 1000000 - max_tiny_droplets_portion + max_tiny_droplets_portion * (1 - fruitTinyScale); + double dropletsPortion = max_tiny_droplets_portion * fruitTinyScale; + double dropletsHit = score.MaximumStatistics.GetValueOrDefault(HitResult.SmallTickHit) == 0 + ? 0 + : (double)score.Statistics.GetValueOrDefault(HitResult.SmallTickHit) / score.MaximumStatistics.GetValueOrDefault(HitResult.SmallTickHit); + convertedTotalScore = (long)Math.Round(( - 600000 * comboProportion - + 400000 * score.Accuracy + comboPortion * estimateComboProportionForCatch(attributes.MaxCombo, score.MaxCombo, score.Statistics.GetValueOrDefault(HitResult.Miss)) + + dropletsPortion * dropletsHit + bonusProportion) * modMultiplier); break; @@ -461,6 +482,94 @@ namespace osu.Game.Database return convertedTotalScore; } + /// + /// + /// For catch, the general method of calculating the combo proportion used for other rulesets is generally useless. + /// This is because in stable score V1, catch has quadratic score progression, + /// while in stable score V2, score progression is logarithmic up to 200 combo and then linear. + /// + /// + /// This means that applying the naive rescale method to scores with lots of short combos (think 10x 100-long combos on a 1000-object map) + /// by linearly rescaling the combo portion as given by score V1 leads to horribly underestimating it. + /// Therefore this method attempts to counteract this by calculating the best case estimate for the combo proportion that takes all of the above into account. + /// + /// + /// The general idea is that aside from the which the player is known to have hit, + /// the remaining misses are evenly distributed across the rest of the objects that give combo. + /// This is therefore a worst-case estimate. + /// + /// + private static double estimateComboProportionForCatch(int beatmapMaxCombo, int scoreMaxCombo, int scoreMissCount) + { + if (beatmapMaxCombo == 0) + return 1; + + if (scoreMaxCombo == 0) + return 0; + + if (beatmapMaxCombo == scoreMaxCombo) + return 1; + + double estimatedBestCaseTotal = estimateBestCaseComboTotal(beatmapMaxCombo); + + int remainingCombo = beatmapMaxCombo - (scoreMaxCombo + scoreMissCount); + double totalDroppedScore = 0; + + int assumedLengthOfRemainingCombos = (int)Math.Floor((double)remainingCombo / scoreMissCount); + + if (assumedLengthOfRemainingCombos > 0) + { + while (remainingCombo > 0) + { + int comboLength = Math.Min(assumedLengthOfRemainingCombos, remainingCombo); + + remainingCombo -= comboLength; + totalDroppedScore += estimateDroppedComboScoreAfterMiss(comboLength); + } + } + else + { + // there are so many misses that attempting to evenly divide remaining combo results in 0 length per combo, + // i.e. all remaining judgements are combo breaks. + // in that case, presume every single remaining object is a miss and did not give any combo score. + totalDroppedScore = estimatedBestCaseTotal - estimateBestCaseComboTotal(scoreMaxCombo); + } + + return estimatedBestCaseTotal == 0 + ? 1 + : 1 - Math.Clamp(totalDroppedScore / estimatedBestCaseTotal, 0, 1); + + double estimateBestCaseComboTotal(int maxCombo) + { + if (maxCombo == 0) + return 1; + + double estimatedTotal = 0.5 * Math.Min(maxCombo, 2); + + if (maxCombo <= 2) + return estimatedTotal; + + // int_2^x log_4(t) dt + estimatedTotal += (Math.Min(maxCombo, 200) * (Math.Log(Math.Min(maxCombo, 200)) - 1) + 2 - Math.Log(4)) / Math.Log(4); + + if (maxCombo <= 200) + return estimatedTotal; + + estimatedTotal += (maxCombo - 200) * Math.Log(200) / Math.Log(4); + return estimatedTotal; + } + + double estimateDroppedComboScoreAfterMiss(int lengthOfComboAfterMiss) + { + if (lengthOfComboAfterMiss >= 200) + lengthOfComboAfterMiss = 200; + + // int_0^x (log_4(200) - log_4(t)) dt + // note that this is an pessimistic estimate, i.e. it may subtract too much if the miss happened before reaching 200 combo + return lengthOfComboAfterMiss * (1 + Math.Log(200) - Math.Log(lengthOfComboAfterMiss)) / Math.Log(4); + } + } + public static double ComputeAccuracy(ScoreInfo scoreInfo) { Ruleset ruleset = scoreInfo.Ruleset.CreateInstance(); diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index cf0a7bd54f..495edaf49c 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -36,9 +36,10 @@ namespace osu.Game.Scoring.Legacy /// 30000007: Adjust osu!mania combo and accuracy portions and judgement scoring values. Reconvert all scores. /// 30000008: Add accuracy conversion. Reconvert all scores. /// 30000009: Fix edge cases in conversion for scores which have 0.0x mod multiplier on stable. Reconvert all scores. + /// 30000010: Re-do catch scoring to mirror stable Score V2 as closely as feasible. Reconvert all scores. /// /// - public const int LATEST_VERSION = 30000009; + public const int LATEST_VERSION = 30000010; /// /// The first stable-compatible YYYYMMDD format version given to lazer usage of replays. From e2769dbda14b21e550c3bafa9446af1e406e2966 Mon Sep 17 00:00:00 2001 From: Zachary Date: Sat, 6 Jan 2024 19:29:41 +1000 Subject: [PATCH 2338/4092] Attempt at creating a test. --- .../TestSceneIntroMusicActionHandling.cs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 osu.Game.Tests/Visual/Menus/TestSceneIntroMusicActionHandling.cs diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroMusicActionHandling.cs new file mode 100644 index 0000000000..cc2b16a842 --- /dev/null +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroMusicActionHandling.cs @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Input.Bindings; +using osu.Game.Screens.Menu; + +namespace osu.Game.Tests.Visual.Menus +{ + public partial class TestSceneIntroMusicActionHandling : OsuTestScene + { + private OsuGameTestScene.TestOsuGame? game; + + private GlobalActionContainer globalActionContainer => game.ChildrenOfType().First(); + + [Test] + public void TestPauseDuringIntro() + { + AddStep("Create new game instance", () => + { + if (game?.Parent != null) + Remove(game, true); + + RecycleLocalStorage(false); + + AddGame(game = new OsuGameTestScene.TestOsuGame(LocalStorage, API)); + }); + + AddUntilStep("Wait for load", () => game?.IsLoaded ?? false); + AddUntilStep("Wait for intro", () => game?.ScreenStack.CurrentScreen is IntroScreen); + AddUntilStep("Wait for music", () => game?.MusicController.IsPlaying == true); + + // Check that pause dosesn't work during intro sequence. + AddStep("Toggle playback", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPlay)); + AddAssert("Still playing before menu", () => game?.MusicController.IsPlaying == true); + AddUntilStep("Wait for main menu", () => game?.ScreenStack.CurrentScreen is MainMenu menu && menu.IsLoaded); + + // Check that toggling after intro still works. + AddStep("Toggle playback", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPlay)); + AddUntilStep("Music paused", () => game?.MusicController.IsPlaying == false && game?.MusicController.UserPauseRequested == true); + AddStep("Toggle playback", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPlay)); + AddUntilStep("Music resumed", () => game?.MusicController.IsPlaying == true && game?.MusicController.UserPauseRequested == false); + } + } +} From 14a43375a70104094f7a9d23cf091c18d1223ad3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 6 Jan 2024 20:25:03 +0900 Subject: [PATCH 2339/4092] Fix overall ranking text overlapping at some aspect ratios Can't confirm on the actual ranking screen due to stuff not working. Maybe it'll work tomorrow. Closes https://github.com/ppy/osu/issues/26341. --- osu.Game/Screens/Ranking/Statistics/SoloStatisticsPanel.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Statistics/SoloStatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/SoloStatisticsPanel.cs index 73b9897096..762be61853 100644 --- a/osu.Game/Screens/Ranking/Statistics/SoloStatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/SoloStatisticsPanel.cs @@ -37,7 +37,6 @@ namespace osu.Game.Screens.Ranking.Statistics RelativeSizeAxes = Axes.X, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Width = 0.5f, StatisticsUpdate = { BindTarget = StatisticsUpdate } })).ToArray(); } From d3710f0bfd0e8fb457ffbde1fb14dce853ad4f14 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 6 Jan 2024 20:44:07 +0900 Subject: [PATCH 2340/4092] Remove scores from song select leaderboard when leaving the screen --- osu.Game/Online/Leaderboards/Leaderboard.cs | 13 ++++++++++--- osu.Game/Screens/Select/PlaySongSelect.cs | 8 ++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 93aa0b95a7..67f2590ad8 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -152,6 +152,15 @@ namespace osu.Game.Online.Leaderboards /// public void RefetchScores() => Scheduler.AddOnce(refetchScores); + /// + /// Clear all scores from the display. + /// + public void ClearScores() + { + cancelPendingWork(); + SetScores(null); + } + /// /// Call when a retrieval or display failure happened to show a relevant message to the user. /// @@ -220,9 +229,7 @@ namespace osu.Game.Online.Leaderboards { Debug.Assert(ThreadSafety.IsUpdateThread); - cancelPendingWork(); - - SetScores(null); + ClearScores(); setState(LeaderboardState.Retrieving); currentFetchCancellationSource = new CancellationTokenSource(); diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 7b7b8857f3..4951504ff5 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -146,6 +146,14 @@ namespace osu.Game.Screens.Select } } + public override void OnSuspending(ScreenTransitionEvent e) + { + // Scores will be refreshed on arriving at this screen. + // Clear them to avoid animation overload on returning to song select. + playBeatmapDetailArea.Leaderboard.ClearScores(); + base.OnSuspending(e); + } + public override void OnResuming(ScreenTransitionEvent e) { base.OnResuming(e); From eea87090fb613538e670611bb9e2ad830f23fd83 Mon Sep 17 00:00:00 2001 From: B3nn1 Date: Sat, 6 Jan 2024 19:25:49 +0100 Subject: [PATCH 2341/4092] Make `changeHandler` save changes to `PathTypes` --- .../Blueprints/Sliders/Components/PathControlPointVisualiser.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 24e2210b45..0cef93fbb5 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -410,8 +410,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components var item = new TernaryStateRadioMenuItem(type?.Description ?? "Inherit", MenuItemType.Standard, _ => { + changeHandler?.BeginChange(); foreach (var p in Pieces.Where(p => p.IsSelected.Value)) updatePathType(p, type); + changeHandler?.EndChange(); }); if (countOfState == totalCount) From b809d4c068f77558e30e80e0a9a8846cd41af5c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Jan 2024 02:14:03 +0900 Subject: [PATCH 2342/4092] Remove delegate overhead from argon health display's animation updates --- osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs | 16 ++++++++++++---- osu.Game/Screens/Play/HUD/HealthDisplay.cs | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 8acc43c091..7721f9c0c0 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Caching; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; @@ -68,11 +69,11 @@ namespace osu.Game.Screens.Play.HUD get => glowBarValue; set { - if (glowBarValue == value) + if (Precision.AlmostEquals(glowBarValue, value, 0.0001)) return; glowBarValue = value; - Scheduler.AddOnce(updatePathVertices); + pathVerticesCache.Invalidate(); } } @@ -83,11 +84,11 @@ namespace osu.Game.Screens.Play.HUD get => healthBarValue; set { - if (healthBarValue == value) + if (Precision.AlmostEquals(healthBarValue, value, 0.0001)) return; healthBarValue = value; - Scheduler.AddOnce(updatePathVertices); + pathVerticesCache.Invalidate(); } } @@ -100,6 +101,8 @@ namespace osu.Game.Screens.Play.HUD private readonly LayoutValue drawSizeLayout = new LayoutValue(Invalidation.DrawSize); + private readonly Cached pathVerticesCache = new Cached(); + public ArgonHealthDisplay() { AddLayout(drawSizeLayout); @@ -208,6 +211,9 @@ namespace osu.Game.Screens.Play.HUD drawSizeLayout.Validate(); } + if (!pathVerticesCache.IsValid) + updatePathVertices(); + mainBar.Alpha = (float)Interpolation.DampContinuously(mainBar.Alpha, Current.Value > 0 ? 1 : 0, 40, Time.Elapsed); glowBar.Alpha = (float)Interpolation.DampContinuously(glowBar.Alpha, GlowBarValue > 0 ? 1 : 0, 40, Time.Elapsed); } @@ -346,6 +352,8 @@ namespace osu.Game.Screens.Play.HUD mainBar.Vertices = healthBarVertices.Select(v => v - healthBarVertices[0]).ToList(); mainBar.Position = healthBarVertices[0]; + + pathVerticesCache.Validate(); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/Play/HUD/HealthDisplay.cs b/osu.Game/Screens/Play/HUD/HealthDisplay.cs index 7747036826..54aba6b8da 100644 --- a/osu.Game/Screens/Play/HUD/HealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HealthDisplay.cs @@ -30,7 +30,7 @@ namespace osu.Game.Screens.Play.HUD public Bindable Current { get; } = new BindableDouble { MinValue = 0, - MaxValue = 1 + MaxValue = 1, }; private BindableNumber health = null!; From 9d9e6fcfdbc3c21faa7e637375ac4a9e4cdf0c51 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Jan 2024 03:22:19 +0900 Subject: [PATCH 2343/4092] Remove LINQ calls in hot paths --- osu.Game/Skinning/SkinnableSound.cs | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index f866a4f8ec..f153f4f8d3 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -194,9 +194,33 @@ namespace osu.Game.Skinning /// /// Whether any samples are currently playing. /// - public bool IsPlaying => samplesContainer.Any(s => s.Playing); + public bool IsPlaying + { + get + { + foreach (PoolableSkinnableSample s in samplesContainer) + { + if (s.Playing) + return true; + } - public bool IsPlayed => samplesContainer.Any(s => s.Played); + return false; + } + } + + public bool IsPlayed + { + get + { + foreach (PoolableSkinnableSample s in samplesContainer) + { + if (s.Played) + return true; + } + + return false; + } + } public IBindable AggregateVolume => samplesContainer.AggregateVolume; From 35eff639cb936188f73c34dc71e67ea4d93a061d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Jan 2024 03:25:00 +0900 Subject: [PATCH 2344/4092] Remove unnecessary second iteration over `NestedHitObjects` --- .../Objects/Drawables/DrawableSlider.cs | 7 ++----- .../Objects/Drawables/DrawableSliderRepeat.cs | 2 +- .../Objects/Drawables/ITrackSnaking.cs | 15 --------------- 3 files changed, 3 insertions(+), 21 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Objects/Drawables/ITrackSnaking.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index b306fd38c1..0f8c9a4d36 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -246,11 +246,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Ball.UpdateProgress(completionProgress); SliderBody?.UpdateProgress(HeadCircle.IsHit ? completionProgress : 0); - foreach (DrawableHitObject hitObject in NestedHitObjects) - { - if (hitObject is ITrackSnaking s) - s.UpdateSnakingPosition(HitObject.Path.PositionAt(SliderBody?.SnakedStart ?? 0), HitObject.Path.PositionAt(SliderBody?.SnakedEnd ?? 0)); - } + foreach (DrawableSliderRepeat repeat in repeatContainer) + repeat.UpdateSnakingPosition(HitObject.Path.PositionAt(SliderBody?.SnakedStart ?? 0), HitObject.Path.PositionAt(SliderBody?.SnakedEnd ?? 0)); Size = SliderBody?.Size ?? Vector2.Zero; OriginPosition = SliderBody?.PathOffset ?? Vector2.Zero; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index c6d4f7c4ca..3239565528 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -17,7 +17,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables { - public partial class DrawableSliderRepeat : DrawableOsuHitObject, ITrackSnaking + public partial class DrawableSliderRepeat : DrawableOsuHitObject { public new SliderRepeat HitObject => (SliderRepeat)base.HitObject; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/ITrackSnaking.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/ITrackSnaking.cs deleted file mode 100644 index cae2a7c36d..0000000000 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/ITrackSnaking.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 osuTK; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables -{ - /// - /// A component which tracks the current end snaking position of a slider. - /// - public interface ITrackSnaking - { - void UpdateSnakingPosition(Vector2 start, Vector2 end); - } -} From 5cc4a586acf40fd3d9be3425f875e54406a38c4d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Jan 2024 03:26:58 +0900 Subject: [PATCH 2345/4092] Avoid iteration over `NestedHitObjects` in silder's `Update` unless necessary --- .../Objects/Drawables/DrawableSlider.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 0f8c9a4d36..c5194025c1 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -36,6 +36,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private ShakeContainer shakeContainer; + private Vector2? childAnchorPosition; + protected override IEnumerable DimmablePieces => new Drawable[] { HeadCircle, @@ -254,10 +256,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (DrawSize != Vector2.Zero) { - var childAnchorPosition = Vector2.Divide(OriginPosition, DrawSize); - foreach (var obj in NestedHitObjects) - obj.RelativeAnchorPosition = childAnchorPosition; - Ball.RelativeAnchorPosition = childAnchorPosition; + Vector2 pos = Vector2.Divide(OriginPosition, DrawSize); + + if (pos != childAnchorPosition) + { + childAnchorPosition = pos; + foreach (var obj in NestedHitObjects) + obj.RelativeAnchorPosition = pos; + Ball.RelativeAnchorPosition = pos; + } } } From 16ea7f9b7787462aa1506bac4ecb0c57090c630d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 7 Jan 2024 13:10:50 +0900 Subject: [PATCH 2346/4092] Avoid completely unnecessary string allocations in `ArgonCounterTextComponent` --- .../Screens/Play/HUD/ArgonCounterTextComponent.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs index 266dfb3301..9d364acd59 100644 --- a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs +++ b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -35,14 +34,7 @@ namespace osu.Game.Screens.Play.HUD public LocalisableString Text { get => textPart.Text; - set - { - int remainingCount = RequiredDisplayDigits.Value - value.ToString().Count(char.IsDigit); - string remainingText = remainingCount > 0 ? new string('#', remainingCount) : string.Empty; - - wireframesPart.Text = remainingText + value; - textPart.Text = value; - } + set => textPart.Text = value; } public ArgonCounterTextComponent(Anchor anchor, LocalisableString? label = null) @@ -83,6 +75,8 @@ namespace osu.Game.Screens.Play.HUD } } }; + + RequiredDisplayDigits.BindValueChanged(digits => wireframesPart.Text = new string('#', digits.NewValue)); } private string textLookup(char c) From dc31c66f6229445dd0ebd0d6a6a52c7241da59f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 7 Jan 2024 19:49:42 +0900 Subject: [PATCH 2347/4092] Return null on font lookup failure instead of asserting Fallback weirdness. --- osu.Game/Skinning/LegacySpriteText.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacySpriteText.cs b/osu.Game/Skinning/LegacySpriteText.cs index 81db5fdf36..581e7534e4 100644 --- a/osu.Game/Skinning/LegacySpriteText.cs +++ b/osu.Game/Skinning/LegacySpriteText.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; @@ -74,7 +73,8 @@ namespace osu.Game.Skinning public ITexturedCharacterGlyph? Get(string? fontName, char character) { // We only service one font. - Debug.Assert(fontName == this.fontName); + if (fontName != this.fontName) + return null; if (cache.TryGetValue(character, out var cached)) return cached; From 962c8ba4acfc8920633f99c022824d4d58564ef9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 7 Jan 2024 20:55:28 +0900 Subject: [PATCH 2348/4092] Reset child anchor position cache on hitobject position change --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index c5194025c1..6cc8b8e935 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -121,7 +121,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } }); - PositionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); + PositionBindable.BindValueChanged(_ => + { + Position = HitObject.StackedPosition; + childAnchorPosition = null; + }); StackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); ScaleBindable.BindValueChanged(scale => Ball.Scale = new Vector2(scale.NewValue)); From 7b663a27bd4d37334e428f1afc3179bc8f9da7d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jan 2024 10:47:22 +0100 Subject: [PATCH 2349/4092] Fix score conversion incorrectly assuming zero combo score in certain cases --- .../StandardisedScoreMigrationTools.cs | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index 9cfb9ea957..24fe147593 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -311,13 +311,22 @@ namespace osu.Game.Database long maximumLegacyBonusScore = attributes.BonusScore; double legacyAccScore = maximumLegacyAccuracyScore * score.Accuracy; - // We can not separate the ComboScore from the BonusScore, so we keep the bonus in the ratio. - // Note that `maximumLegacyComboScore + maximumLegacyBonusScore` can actually be 0 - // when playing a beatmap with no bonus objects, with mods that have a 0.0x multiplier on stable (relax/autopilot). - // In such cases, just assume 0. - double comboProportion = maximumLegacyComboScore + maximumLegacyBonusScore > 0 - ? Math.Max((double)score.LegacyTotalScore - legacyAccScore, 0) / (maximumLegacyComboScore + maximumLegacyBonusScore) - : 0; + + double comboProportion; + + if (maximumLegacyComboScore + maximumLegacyBonusScore > 0) + { + // We can not separate the ComboScore from the BonusScore, so we keep the bonus in the ratio. + comboProportion = Math.Max((double)score.LegacyTotalScore - legacyAccScore, 0) / (maximumLegacyComboScore + maximumLegacyBonusScore); + } + else + { + // Two possible causes: + // the beatmap has no bonus objects *AND* + // either the active mods have a zero mod multiplier, in which case assume 0, + // or the *beatmap* has a zero `difficultyPeppyStars` (or just no combo-giving objects), in which case assume 1. + comboProportion = legacyModMultiplier == 0 ? 0 : 1; + } // We assume the bonus proportion only makes up the rest of the score that exceeds maximumLegacyBaseScore. long maximumLegacyBaseScore = maximumLegacyAccuracyScore + maximumLegacyComboScore; From 50eba9ebdb92790c10fda1904219525029abe0a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jan 2024 12:52:14 +0100 Subject: [PATCH 2350/4092] Reduce code duplication in test --- .../TestSceneIntroMusicActionHandling.cs | 32 +++++++------------ osu.Game/Tests/Visual/OsuGameTestScene.cs | 8 +++-- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroMusicActionHandling.cs index cc2b16a842..00c14dc797 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneIntroMusicActionHandling.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroMusicActionHandling.cs @@ -9,39 +9,31 @@ using osu.Game.Screens.Menu; namespace osu.Game.Tests.Visual.Menus { - public partial class TestSceneIntroMusicActionHandling : OsuTestScene + public partial class TestSceneIntroMusicActionHandling : OsuGameTestScene { - private OsuGameTestScene.TestOsuGame? game; + private GlobalActionContainer globalActionContainer => Game.ChildrenOfType().First(); - private GlobalActionContainer globalActionContainer => game.ChildrenOfType().First(); + public override void SetUpSteps() + { + CreateNewGame(); + // we do not want to progress to main menu immediately, hence the override and lack of `ConfirmAtMainMenu()` call here. + } [Test] public void TestPauseDuringIntro() { - AddStep("Create new game instance", () => - { - if (game?.Parent != null) - Remove(game, true); - - RecycleLocalStorage(false); - - AddGame(game = new OsuGameTestScene.TestOsuGame(LocalStorage, API)); - }); - - AddUntilStep("Wait for load", () => game?.IsLoaded ?? false); - AddUntilStep("Wait for intro", () => game?.ScreenStack.CurrentScreen is IntroScreen); - AddUntilStep("Wait for music", () => game?.MusicController.IsPlaying == true); + AddUntilStep("Wait for music", () => Game?.MusicController.IsPlaying == true); // Check that pause dosesn't work during intro sequence. AddStep("Toggle playback", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPlay)); - AddAssert("Still playing before menu", () => game?.MusicController.IsPlaying == true); - AddUntilStep("Wait for main menu", () => game?.ScreenStack.CurrentScreen is MainMenu menu && menu.IsLoaded); + AddAssert("Still playing before menu", () => Game?.MusicController.IsPlaying == true); + AddUntilStep("Wait for main menu", () => Game?.ScreenStack.CurrentScreen is MainMenu menu && menu.IsLoaded); // Check that toggling after intro still works. AddStep("Toggle playback", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPlay)); - AddUntilStep("Music paused", () => game?.MusicController.IsPlaying == false && game?.MusicController.UserPauseRequested == true); + AddUntilStep("Music paused", () => Game?.MusicController.IsPlaying == false && Game?.MusicController.UserPauseRequested == true); AddStep("Toggle playback", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPlay)); - AddUntilStep("Music resumed", () => game?.MusicController.IsPlaying == true && game?.MusicController.UserPauseRequested == false); + AddUntilStep("Music resumed", () => Game?.MusicController.IsPlaying == true && Game?.MusicController.UserPauseRequested == false); } } } diff --git a/osu.Game/Tests/Visual/OsuGameTestScene.cs b/osu.Game/Tests/Visual/OsuGameTestScene.cs index 94be4a375d..947305439e 100644 --- a/osu.Game/Tests/Visual/OsuGameTestScene.cs +++ b/osu.Game/Tests/Visual/OsuGameTestScene.cs @@ -58,6 +58,12 @@ namespace osu.Game.Tests.Visual [SetUpSteps] public virtual void SetUpSteps() + { + CreateNewGame(); + ConfirmAtMainMenu(); + } + + protected void CreateNewGame() { AddStep("Create new game instance", () => { @@ -71,8 +77,6 @@ namespace osu.Game.Tests.Visual AddUntilStep("Wait for load", () => Game.IsLoaded); AddUntilStep("Wait for intro", () => Game.ScreenStack.CurrentScreen is IntroScreen); - - ConfirmAtMainMenu(); } [TearDownSteps] From b869be2f463b41781bfaefd2926c54925a33ac03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jan 2024 12:52:23 +0100 Subject: [PATCH 2351/4092] Fix typo --- .../Visual/Menus/TestSceneIntroMusicActionHandling.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroMusicActionHandling.cs index 00c14dc797..01aeaff1db 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneIntroMusicActionHandling.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroMusicActionHandling.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Menus { AddUntilStep("Wait for music", () => Game?.MusicController.IsPlaying == true); - // Check that pause dosesn't work during intro sequence. + // Check that pause doesn't work during intro sequence. AddStep("Toggle playback", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPlay)); AddAssert("Still playing before menu", () => Game?.MusicController.IsPlaying == true); AddUntilStep("Wait for main menu", () => Game?.ScreenStack.CurrentScreen is MainMenu menu && menu.IsLoaded); From b6ce57b777983af3808b9736d86d1aba4b6faf80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jan 2024 12:54:16 +0100 Subject: [PATCH 2352/4092] Use override that was intended to steer global track control rather than local sets --- osu.Game/Screens/Menu/IntroScreen.cs | 4 ++-- osu.Game/Screens/Menu/MainMenu.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index a81e24ee88..ac7dffc241 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -95,6 +95,8 @@ namespace osu.Game.Screens.Menu Colour = Color4.Black }; + public override bool? AllowGlobalTrackControl => false; + protected IntroScreen([CanBeNull] Func createNextScreen = null) { this.createNextScreen = createNextScreen; @@ -109,8 +111,6 @@ namespace osu.Game.Screens.Menu // prevent user from changing beatmap while the intro is still running. beatmap = Beatmap.BeginLease(false); - musicController.AllowTrackControl.Value = false; - MenuVoice = config.GetBindable(OsuSetting.MenuVoice); MenuMusic = config.GetBindable(OsuSetting.MenuMusic); diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index ffb68367c2..b264341cc5 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -49,6 +49,8 @@ namespace osu.Game.Screens.Menu public override bool AllowExternalScreenChange => true; + public override bool? AllowGlobalTrackControl => true; + private Screen songSelect; private MenuSideFlashes sideFlashes; @@ -241,8 +243,6 @@ namespace osu.Game.Screens.Menu { var track = musicController.CurrentTrack; - musicController.AllowTrackControl.Value = true; - // presume the track is the current beatmap's track. not sure how correct this assumption is but it has worked until now. if (!track.IsRunning) { From 8c82bb006cc5f842f943c513d70ab33d9d7234f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jan 2024 09:43:53 +0100 Subject: [PATCH 2353/4092] Fix mania score conversion using score V1 accuracy Partially addresses https://github.com/ppy/osu/discussions/26416 As pointed out in the discussion thread above, the total score conversion process for mania was using accuracy directly from the replay. In mania accuracy is calculated differently in score V1 than in score V2, which meant that scores coming from stable were treated more favourably (due to weighting GREAT and PERFECT equally). To fix, recompute accuracy locally and use that for the accuracy portion. Note that this will still not be (and cannot be made) 100% accurate, as in stable score V2, as well as in lazer, hold notes are *two* judgements, not one as in stable score V1, meaning that full and correct score statistics are not available without playing back the replay. The effects of the change can be previewed on the following spreadsheet: https://docs.google.com/spreadsheets/d/1wxD4UwLjwcr7n9y5Yq7EN0lgiLBN93kpd4gBnAlG-E0/edit#gid=1711190356 Top 5 changed scores with replays: | score | master | this PR | replay | | :------------------------------------------------------------------------------------------------------------------------------- | ------: | ------: | ------: | | [Outlasted on Uwa!! So Holiday by toby fox [[4K] easy] (0.71\*)](https://osu.ppy.sh/scores/mania/460404716) | 935,917 | 927,269 | 920,579 | | [ag0 on Emotional Uplifting Orchestral by bradbreeck [[4K] Rocket's Normal] (0.76\*)](https://osu.ppy.sh/scores/mania/453133066) | 921,636 | 913,535 | 875,549 | | [rlarkgus on Zen Zen Zense by Gom (HoneyWorks) [[5K] Normal] (1.68\*)](https://osu.ppy.sh/scores/mania/458368312) | 934,340 | 926,787 | 918,855 | | [YuJJun on Harumachi Clover by R3 Music Box [4K Catastrophe] (1.80\*)](https://osu.ppy.sh/scores/mania/548215786) | 918,606 | 911,111 | 885,454 | | [Fritte on 45-byou by respon feat. Hatsune Miku & Megpoid [[5K] Normal] (1.52\*)](https://osu.ppy.sh/scores/mania/516079410) | 900,024 | 892,569 | 907,456 | --- osu.Game/Database/StandardisedScoreMigrationTools.cs | 7 ++++++- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index 9cfb9ea957..1ef7355027 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -444,9 +444,14 @@ namespace osu.Game.Database break; case 3: + // in the mania case accuracy actually changes between score V1 and score V2 / standardised + // (PERFECT weighting changes from 300 to 305), + // so for better accuracy recompute accuracy locally based on hit statistics and use that instead, + double scoreV2Accuracy = ComputeAccuracy(score); + convertedTotalScore = (long)Math.Round(( 850000 * comboProportion - + 150000 * Math.Pow(score.Accuracy, 2 + 2 * score.Accuracy) + + 150000 * Math.Pow(scoreV2Accuracy, 2 + 2 * scoreV2Accuracy) + bonusProportion) * modMultiplier); break; diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index cf0a7bd54f..95f2ee0552 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -36,9 +36,10 @@ namespace osu.Game.Scoring.Legacy /// 30000007: Adjust osu!mania combo and accuracy portions and judgement scoring values. Reconvert all scores. /// 30000008: Add accuracy conversion. Reconvert all scores. /// 30000009: Fix edge cases in conversion for scores which have 0.0x mod multiplier on stable. Reconvert all scores. + /// 30000010: Fix mania score V1 conversion using score V1 accuracy rather than V2 accuracy. Reconvert all scores. /// /// - public const int LATEST_VERSION = 30000009; + public const int LATEST_VERSION = 30000010; /// /// The first stable-compatible YYYYMMDD format version given to lazer usage of replays. From e77d203a24088174a47810a70112e2a4cda46fc7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jan 2024 01:08:17 +0900 Subject: [PATCH 2354/4092] Refactor delayed load logic to hopefully read better --- .../Carousel/DrawableCarouselBeatmapSet.cs | 43 +++++++++++-------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 61658526db..b70278c9bb 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -199,27 +199,34 @@ namespace osu.Game.Screens.Select.Carousel Debug.Assert(Item != null); - if (loadCancellation == null && (timeSinceUnpool += Time.Elapsed) > timeUpdatingBeforeLoad) - { - loadCancellation = new CancellationTokenSource(); + // A load is already in progress if the cancellation token is non-null. + if (loadCancellation != null) + return; - LoadComponentsAsync(new CompositeDrawable[] + timeSinceUnpool += Time.Elapsed; + + // We only trigger a load after this set has been in an updating state for a set amount of time. + if (timeSinceUnpool <= timeUpdatingBeforeLoad) + return; + + loadCancellation = new CancellationTokenSource(); + + LoadComponentsAsync(new CompositeDrawable[] + { + new SetPanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.MinBy(b => b.OnlineID))) { - new SetPanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.MinBy(b => b.OnlineID))) - { - RelativeSizeAxes = Axes.Both, - }, - new SetPanelContent((CarouselBeatmapSet)Item) - { - Depth = float.MinValue, - RelativeSizeAxes = Axes.Both, - } - }, drawables => + RelativeSizeAxes = Axes.Both, + }, + new SetPanelContent((CarouselBeatmapSet)Item) { - Header.AddRange(drawables); - drawables.ForEach(d => d.FadeInFromZero(150)); - }, loadCancellation.Token); - } + Depth = float.MinValue, + RelativeSizeAxes = Axes.Both, + } + }, drawables => + { + Header.AddRange(drawables); + drawables.ForEach(d => d.FadeInFromZero(150)); + }, loadCancellation.Token); } private void updateBeatmapYPositions() From 51bd32bf7e94e74967d6852ead980b71bed6a8e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jan 2024 01:08:47 +0900 Subject: [PATCH 2355/4092] Restore comment regarding usage of `MinBy` --- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index b70278c9bb..369db37e63 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -213,6 +213,7 @@ namespace osu.Game.Screens.Select.Carousel LoadComponentsAsync(new CompositeDrawable[] { + // Choice of background image matches BSS implementation (always uses the lowest `beatmap_id` from the set). new SetPanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.MinBy(b => b.OnlineID))) { RelativeSizeAxes = Axes.Both, From c4ac53002c04567f29b9bd0efe9a7ac16e72a4e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jan 2024 19:49:22 +0100 Subject: [PATCH 2356/4092] Remove loop in combo score loss estimation calculation --- osu.Game/Database/StandardisedScoreMigrationTools.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index f029d85aed..07b4fe7f40 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -519,13 +519,13 @@ namespace osu.Game.Database if (assumedLengthOfRemainingCombos > 0) { - while (remainingCombo > 0) - { - int comboLength = Math.Min(assumedLengthOfRemainingCombos, remainingCombo); + int assumedCombosCount = (int)Math.Floor((double)remainingCombo / assumedLengthOfRemainingCombos); + totalDroppedScore += assumedCombosCount * estimateDroppedComboScoreAfterMiss(assumedLengthOfRemainingCombos); - remainingCombo -= comboLength; - totalDroppedScore += estimateDroppedComboScoreAfterMiss(comboLength); - } + remainingCombo -= assumedCombosCount * assumedLengthOfRemainingCombos; + + if (remainingCombo > 0) + totalDroppedScore += estimateDroppedComboScoreAfterMiss(remainingCombo); } else { From 8a87301c55262f888017cb2ce5ed23d04429ab05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jan 2024 21:33:25 +0100 Subject: [PATCH 2357/4092] Add test for crashing scenario --- .../Navigation/TestSceneScreenNavigation.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index a0069f55c7..8cb993eff2 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -938,6 +938,35 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("touch device mod still active", () => Game.SelectedMods.Value, () => Has.One.InstanceOf()); } + [Test] + public void TestExitSongSelectAndImmediatelyClickLogo() + { + Screens.Select.SongSelect songSelect = null; + PushAndConfirm(() => songSelect = new TestPlaySongSelect()); + AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); + + AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); + + AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + + AddStep("press escape and then click logo immediately", () => + { + InputManager.Key(Key.Escape); + clickLogoWhenNotCurrent(); + }); + + void clickLogoWhenNotCurrent() + { + if (songSelect.IsCurrentScreen()) + Scheduler.AddOnce(clickLogoWhenNotCurrent); + else + { + InputManager.MoveMouseTo(Game.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + } + } + } + private Func playToResults() { var player = playToCompletion(); From 58db39ec3206a34e1b8a0cbdbe196059dd4b8306 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jan 2024 21:37:25 +0100 Subject: [PATCH 2358/4092] Fix crash when clicking osu! logo in song select immediately after exiting Closes https://github.com/ppy/osu/issues/26415. The crash report with incomplete log was backwards, the exit comes first. Sentry events and the reproducing test in 8a87301c55262f888017cb2ce5ed23d04429ab05 confirm this. --- osu.Game/Screens/Select/PlaySongSelect.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 4951504ff5..3cf8de5267 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -92,6 +92,9 @@ namespace osu.Game.Screens.Select { if (playerLoader != null) return false; + if (!this.IsCurrentScreen()) + return false; + modsAtGameplayStart = Mods.Value; // Ctrl+Enter should start map with autoplay enabled. From 67df7b33fb6c355ad41382fae8323c5c9e3d0c86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jan 2024 21:51:01 +0100 Subject: [PATCH 2359/4092] Add failing test coverage for not attempting to upgrade custom ruleset scores --- .../BackgroundDataStoreProcessorTests.cs | 30 +++++++++++++++++++ osu.Game/BackgroundDataStoreProcessor.cs | 4 ++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs index 43ce7200d2..9dbfde7bce 100644 --- a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs +++ b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.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 System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -182,9 +183,38 @@ namespace osu.Game.Tests.Database AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(30000002)); } + [Test] + public void TestCustomRulesetScoreNotSubjectToUpgrades([Values] bool available) + { + RulesetInfo rulesetInfo = null!; + ScoreInfo scoreInfo = null!; + TestBackgroundDataStoreProcessor processor = null!; + + AddStep("Add unavailable ruleset", () => Realm.Write(r => r.Add(rulesetInfo = new RulesetInfo + { + ShortName = Guid.NewGuid().ToString(), + Available = available + }))); + + AddStep("Add score for unavailable ruleset", () => Realm.Write(r => r.Add(scoreInfo = new ScoreInfo( + ruleset: rulesetInfo, + beatmap: r.All().First()) + { + TotalScoreVersion = 30000001 + }))); + + AddStep("Run background processor", () => Add(processor = new TestBackgroundDataStoreProcessor())); + AddUntilStep("Wait for completion", () => processor.Completed); + + AddAssert("Score not marked as failed", () => Realm.Run(r => r.Find(scoreInfo.ID)!.BackgroundReprocessingFailed), () => Is.False); + AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(30000001)); + } + public partial class TestBackgroundDataStoreProcessor : BackgroundDataStoreProcessor { protected override int TimeToSleepDuringGameplay => 10; + + public bool Completed => ProcessingTask.IsCompleted; } } } diff --git a/osu.Game/BackgroundDataStoreProcessor.cs b/osu.Game/BackgroundDataStoreProcessor.cs index a748a7422a..969062be57 100644 --- a/osu.Game/BackgroundDataStoreProcessor.cs +++ b/osu.Game/BackgroundDataStoreProcessor.cs @@ -28,6 +28,8 @@ namespace osu.Game /// public partial class BackgroundDataStoreProcessor : Component { + protected Task ProcessingTask = null!; + [Resolved] private RulesetStore rulesetStore { get; set; } = null!; @@ -61,7 +63,7 @@ namespace osu.Game { base.LoadComplete(); - Task.Factory.StartNew(() => + ProcessingTask = Task.Factory.StartNew(() => { Logger.Log("Beginning background data store processing.."); From 388f6599e0264d640d4da1fda0e27441212ca389 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jan 2024 21:54:10 +0100 Subject: [PATCH 2360/4092] Add failing test for not attempting to upgrade non-legacy scores This was a source of confusion for users previously, wondering why their non-legacy (non-stable) scores weren't being converted in line with new scoring changes, when it was never actually our intention to support anything of the sort. --- .../BackgroundDataStoreProcessorTests.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs index 9dbfde7bce..8b066f860f 100644 --- a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs +++ b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs @@ -210,6 +210,31 @@ namespace osu.Game.Tests.Database AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(30000001)); } + [Test] + public void TestNonLegacyScoreNotSubjectToUpgrades() + { + ScoreInfo scoreInfo = null!; + TestBackgroundDataStoreProcessor processor = null!; + + AddStep("Add score which requires upgrade (and has beatmap)", () => + { + Realm.Write(r => + { + r.Add(scoreInfo = new ScoreInfo(ruleset: r.All().First(), beatmap: r.All().First()) + { + TotalScoreVersion = 30000005, + LegacyTotalScore = 123456, + }); + }); + }); + + AddStep("Run background processor", () => Add(processor = new TestBackgroundDataStoreProcessor())); + AddUntilStep("Wait for completion", () => processor.Completed); + + AddAssert("Score not marked as failed", () => Realm.Run(r => r.Find(scoreInfo.ID)!.BackgroundReprocessingFailed), () => Is.False); + AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(30000005)); + } + public partial class TestBackgroundDataStoreProcessor : BackgroundDataStoreProcessor { protected override int TimeToSleepDuringGameplay => 10; From aa83b84bb225781adccc15f4276a74901ed03397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jan 2024 22:34:41 +0100 Subject: [PATCH 2361/4092] Fix Cinema mod being compatible with mods that can force failure Addresses https://github.com/ppy/osu/pull/26080#issuecomment-1868833214. --- osu.Game/Rulesets/Mods/ModCinema.cs | 2 +- osu.Game/Rulesets/Mods/ModFailCondition.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index dbb37e0af6..7c88a8a588 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => OsuIcon.ModCinema; public override LocalisableString Description => "Watch the video without visual distractions."; - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(ModAutoplay), typeof(ModNoFail) }).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(ModAutoplay), typeof(ModNoFail), typeof(ModFailCondition) }).ToArray(); public void ApplyToHUD(HUDOverlay overlay) { diff --git a/osu.Game/Rulesets/Mods/ModFailCondition.cs b/osu.Game/Rulesets/Mods/ModFailCondition.cs index 471c3bfe8d..0b229766c1 100644 --- a/osu.Game/Rulesets/Mods/ModFailCondition.cs +++ b/osu.Game/Rulesets/Mods/ModFailCondition.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mods { public abstract class ModFailCondition : Mod, IApplicableToHealthProcessor, IApplicableFailOverride { - public override Type[] IncompatibleMods => new[] { typeof(ModNoFail) }; + public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModCinema) }; [SettingSource("Restart on fail", "Automatically restarts when failed.")] public BindableBool Restart { get; } = new BindableBool(); From 4f7dcb3a5022cb533cc467d974a7f382d1cf285e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jan 2024 22:07:33 +0100 Subject: [PATCH 2362/4092] Do not attempt to recalculate non-legacy scores or scores set on custom rulesets Addresses discussions such as https://github.com/ppy/osu/discussions/26407 or https://github.com/ppy/osu/discussions/25914 wherein: - the game would attempt to convert scores for custom rulesets, which makes no sense, especially so when they're not there, - the game would also "recalculate" lazer scores, but that was never the intention or was never supported; the game would just increment the score version on those but still include them in the converted tally. --- osu.Game/BackgroundDataStoreProcessor.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/osu.Game/BackgroundDataStoreProcessor.cs b/osu.Game/BackgroundDataStoreProcessor.cs index 969062be57..fc7db13d41 100644 --- a/osu.Game/BackgroundDataStoreProcessor.cs +++ b/osu.Game/BackgroundDataStoreProcessor.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Database; +using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; @@ -28,7 +29,7 @@ namespace osu.Game /// public partial class BackgroundDataStoreProcessor : Component { - protected Task ProcessingTask = null!; + protected Task ProcessingTask { get; private set; } = null!; [Resolved] private RulesetStore rulesetStore { get; set; } = null!; @@ -316,10 +317,17 @@ namespace osu.Game { Logger.Log("Querying for scores that need total score conversion..."); - HashSet scoreIds = realmAccess.Run(r => new HashSet(r.All() - .Where(s => !s.BackgroundReprocessingFailed && s.BeatmapInfo != null - && s.TotalScoreVersion < LegacyScoreEncoder.LATEST_VERSION) - .AsEnumerable().Select(s => s.ID))); + HashSet scoreIds = realmAccess.Run(r => new HashSet( + r.All() + .Where(s => !s.BackgroundReprocessingFailed + && s.BeatmapInfo != null + && s.IsLegacyScore + && s.TotalScoreVersion < LegacyScoreEncoder.LATEST_VERSION) + .AsEnumerable() + // must be done after materialisation, as realm doesn't want to support + // nested property predicates + .Where(s => s.Ruleset.IsLegacyRuleset()) + .Select(s => s.ID))); Logger.Log($"Found {scoreIds.Count} scores which require total score conversion."); From 58619f168458ead922914536a651951595f33875 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 8 Jan 2024 14:16:05 -0800 Subject: [PATCH 2363/4092] Fix wiki main page not displaying custom layout --- osu.Game/Overlays/WikiOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index 440e451201..a8d9cdcdb2 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -137,7 +137,7 @@ namespace osu.Game.Overlays wikiData.Value = response; path.Value = response.Path; - if (response.Layout == index_path) + if (response.Layout == index_path.ToLowerInvariant()) { LoadDisplay(new WikiMainPage { From d6ba7a9c6eeab7c2c4889249df4f7280f28515d2 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 8 Jan 2024 14:24:56 -0800 Subject: [PATCH 2364/4092] Centralise `INDEX_PATH` to `WikiOverlay` --- osu.Game/Overlays/Wiki/WikiHeader.cs | 4 +--- osu.Game/Overlays/WikiOverlay.cs | 10 +++++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Wiki/WikiHeader.cs b/osu.Game/Overlays/Wiki/WikiHeader.cs index 55be05ed7a..d64d6b934a 100644 --- a/osu.Game/Overlays/Wiki/WikiHeader.cs +++ b/osu.Game/Overlays/Wiki/WikiHeader.cs @@ -17,8 +17,6 @@ namespace osu.Game.Overlays.Wiki { public partial class WikiHeader : BreadcrumbControlOverlayHeader { - private const string index_path = "Main_page"; - public static LocalisableString IndexPageString => LayoutStrings.HeaderHelpIndex; public readonly Bindable WikiPageData = new Bindable(); @@ -45,7 +43,7 @@ namespace osu.Game.Overlays.Wiki TabControl.AddItem(IndexPageString); - if (e.NewValue.Path == index_path) + if (e.NewValue.Path == WikiOverlay.INDEX_PATH) { Current.Value = IndexPageString; return; diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index a8d9cdcdb2..3777e83cde 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -19,11 +19,11 @@ namespace osu.Game.Overlays { public partial class WikiOverlay : OnlineOverlay { - private const string index_path = "Main_page"; + public const string INDEX_PATH = @"Main_page"; public string CurrentPath => path.Value; - private readonly Bindable path = new Bindable(index_path); + private readonly Bindable path = new Bindable(INDEX_PATH); private readonly Bindable wikiData = new Bindable(); @@ -43,7 +43,7 @@ namespace osu.Game.Overlays { } - public void ShowPage(string pagePath = index_path) + public void ShowPage(string pagePath = INDEX_PATH) { path.Value = pagePath.Trim('/'); Show(); @@ -137,7 +137,7 @@ namespace osu.Game.Overlays wikiData.Value = response; path.Value = response.Path; - if (response.Layout == index_path.ToLowerInvariant()) + if (response.Layout == INDEX_PATH.ToLowerInvariant()) { LoadDisplay(new WikiMainPage { @@ -161,7 +161,7 @@ namespace osu.Game.Overlays path.Value = "error"; LoadDisplay(articlePage = new WikiArticlePage($@"{api.WebsiteRootUrl}/wiki/", - $"Something went wrong when trying to fetch page \"{originalPath}\".\n\n[Return to the main page](Main_page).")); + $"Something went wrong when trying to fetch page \"{originalPath}\".\n\n[Return to the main page]({INDEX_PATH}).")); } private void showParentPage() From b03813d3b40e0a279bb8ef6800cd587162adf0f2 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 8 Jan 2024 14:36:08 -0800 Subject: [PATCH 2365/4092] Update casing of hardcoded "Main_page" string in tests --- osu.Game.Tests/Visual/Online/TestSceneWikiHeader.cs | 4 ++-- osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs | 2 +- .../Visual/Online/TestSceneWikiMarkdownContainer.cs | 4 ++-- osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiHeader.cs index 4e71c5977e..40eda3f3dc 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiHeader.cs @@ -24,8 +24,8 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly Bindable wikiPageData = new Bindable(new APIWikiPage { - Title = "Main Page", - Path = "Main_Page", + Title = "Main page", + Path = "Main_page", }); private TestHeader header; diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs index 9967be73e8..7b4eadd46d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs @@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.Online }; } - // From https://osu.ppy.sh/api/v2/wiki/en/Main_Page + // From https://osu.ppy.sh/api/v2/wiki/en/Main_page private const string main_page_markdown = "---\nlayout: main_page\n---\n\n\n\n
\nWelcome to the osu! wiki, a project containing a wide range of osu! related information.\n
\n\n
\n
\n\n# Getting started\n\n[Welcome](/wiki/Welcome) • [Installation](/wiki/Installation) • [Registration](/wiki/Registration) • [Help Centre](/wiki/Help_Centre) • [FAQ](/wiki/FAQ)\n\n
\n
\n\n# Game client\n\n[Interface](/wiki/Interface) • [Options](/wiki/Options) • [Visual settings](/wiki/Visual_Settings) • [Shortcut key reference](/wiki/Shortcut_key_reference) • [Configuration file](/wiki/osu!_Program_Files/User_Configuration_File) • [Program files](/wiki/osu!_Program_Files)\n\n[File formats](/wiki/osu!_File_Formats): [.osz](/wiki/osu!_File_Formats/Osz_(file_format)) • [.osk](/wiki/osu!_File_Formats/Osk_(file_format)) • [.osr](/wiki/osu!_File_Formats/Osr_(file_format)) • [.osu](/wiki/osu!_File_Formats/Osu_(file_format)) • [.osb](/wiki/osu!_File_Formats/Osb_(file_format)) • [.db](/wiki/osu!_File_Formats/Db_(file_format))\n\n
\n
\n\n# Gameplay\n\n[Game modes](/wiki/Game_mode): [osu!](/wiki/Game_mode/osu!) • [osu!taiko](/wiki/Game_mode/osu!taiko) • [osu!catch](/wiki/Game_mode/osu!catch) • [osu!mania](/wiki/Game_mode/osu!mania)\n\n[Beatmap](/wiki/Beatmap) • [Hit object](/wiki/Hit_object) • [Mods](/wiki/Game_modifier) • [Score](/wiki/Score) • [Replay](/wiki/Replay) • [Multi](/wiki/Multi)\n\n
\n
\n\n# [Beatmap editor](/wiki/Beatmap_Editor)\n\nSections: [Compose](/wiki/Beatmap_Editor/Compose) • [Design](/wiki/Beatmap_Editor/Design) • [Timing](/wiki/Beatmap_Editor/Timing) • [Song setup](/wiki/Beatmap_Editor/Song_Setup)\n\nComponents: [AiMod](/wiki/Beatmap_Editor/AiMod) • [Beat snap divisor](/wiki/Beatmap_Editor/Beat_Snap_Divisor) • [Distance snap](/wiki/Beatmap_Editor/Distance_Snap) • [Menu](/wiki/Beatmap_Editor/Menu) • [SB load](/wiki/Beatmap_Editor/SB_Load) • [Timelines](/wiki/Beatmap_Editor/Timelines)\n\n[Beatmapping](/wiki/Beatmapping) • [Difficulty](/wiki/Beatmap/Difficulty) • [Mapping techniques](/wiki/Mapping_Techniques) • [Storyboarding](/wiki/Storyboarding)\n\n
\n
\n\n# Beatmap submission and ranking\n\n[Submission](/wiki/Submission) • [Modding](/wiki/Modding) • [Ranking procedure](/wiki/Beatmap_ranking_procedure) • [Mappers' Guild](/wiki/Mappers_Guild) • [Project Loved](/wiki/Project_Loved)\n\n[Ranking criteria](/wiki/Ranking_Criteria): [osu!](/wiki/Ranking_Criteria/osu!) • [osu!taiko](/wiki/Ranking_Criteria/osu!taiko) • [osu!catch](/wiki/Ranking_Criteria/osu!catch) • [osu!mania](/wiki/Ranking_Criteria/osu!mania)\n\n
\n
\n\n# Community\n\n[Tournaments](/wiki/Tournaments) • [Skinning](/wiki/Skinning) • [Projects](/wiki/Projects) • [Guides](/wiki/Guides) • [osu!dev Discord server](/wiki/osu!dev_Discord_server) • [How you can help](/wiki/How_You_Can_Help!) • [Glossary](/wiki/Glossary)\n\n
\n
\n\n# People\n\n[The Team](/wiki/People/The_Team): [Developers](/wiki/People/The_Team/Developers) • [Global Moderation Team](/wiki/People/The_Team/Global_Moderation_Team) • [Support Team](/wiki/People/The_Team/Support_Team) • [Nomination Assessment Team](/wiki/People/The_Team/Nomination_Assessment_Team) • [Beatmap Nominators](/wiki/People/The_Team/Beatmap_Nominators) • [osu! Alumni](/wiki/People/The_Team/osu!_Alumni) • [Project Loved Team](/wiki/People/The_Team/Project_Loved_Team)\n\nOrganisations: [osu! UCI](/wiki/Organisations/osu!_UCI)\n\n[Community Contributors](/wiki/People/Community_Contributors) • [Users with unique titles](/wiki/People/Users_with_unique_titles)\n\n
\n
\n\n# For developers\n\n[API](/wiki/osu!api) • [Bot account](/wiki/Bot_account) • [Brand identity guidelines](/wiki/Brand_identity_guidelines)\n\n
\n
\n\n# About the wiki\n\n[Sitemap](/wiki/Sitemap) • [Contribution guide](/wiki/osu!_wiki_Contribution_Guide) • [Article styling criteria](/wiki/Article_Styling_Criteria) • [News styling criteria](/wiki/News_Styling_Criteria)\n\n
\n
\n"; } diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index 6c87553971..8909305602 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -69,8 +69,8 @@ namespace osu.Game.Tests.Visual.Online { AddStep("set current path", () => markdownContainer.CurrentPath = $"{API.WebsiteRootUrl}/wiki/Article_styling_criteria/"); - AddStep("set '/wiki/Main_Page''", () => markdownContainer.Text = "[wiki main page](/wiki/Main_Page)"); - AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/Main_Page"); + AddStep("set '/wiki/Main_page''", () => markdownContainer.Text = "[wiki main page](/wiki/Main_page)"); + AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/Main_page"); AddStep("set '../FAQ''", () => markdownContainer.Text = "[FAQ](../FAQ)"); AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/FAQ"); diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs index 79c7e3a22e..352f100f3b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs @@ -107,12 +107,12 @@ namespace osu.Game.Tests.Visual.Online }; }); - // From https://osu.ppy.sh/api/v2/wiki/en/Main_Page + // From https://osu.ppy.sh/api/v2/wiki/en/Main_page private APIWikiPage responseMainPage => new APIWikiPage { - Title = "Main Page", + Title = "Main page", Layout = "main_page", - Path = "Main_Page", + Path = "Main_page", Locale = "en", Subtitle = null, Markdown = From 7d57a668aba2032be4c000561be60f17674c4732 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jan 2024 13:12:54 +0900 Subject: [PATCH 2366/4092] Use main page constant in more places --- osu.Game.Tests/Visual/Online/TestSceneWikiHeader.cs | 2 +- osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiHeader.cs index 40eda3f3dc..d259322d4a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiHeader.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Online private readonly Bindable wikiPageData = new Bindable(new APIWikiPage { Title = "Main page", - Path = "Main_page", + Path = WikiOverlay.INDEX_PATH, }); private TestHeader header; diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs index 352f100f3b..8765d8485a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs @@ -11,6 +11,7 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; +using osu.Game.Overlays.Wiki; namespace osu.Game.Tests.Visual.Online { @@ -111,8 +112,8 @@ namespace osu.Game.Tests.Visual.Online private APIWikiPage responseMainPage => new APIWikiPage { Title = "Main page", - Layout = "main_page", - Path = "Main_page", + Layout = WikiOverlay.INDEX_PATH.ToLowerInvariant(), // custom classes are always lower snake. + Path = WikiOverlay.INDEX_PATH, Locale = "en", Subtitle = null, Markdown = From 172fe53099bf4e2e26e7acc9dc0dc7708b0ea39d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jan 2024 13:13:32 +0900 Subject: [PATCH 2367/4092] Use better method of ignore case comparison --- osu.Game/Overlays/WikiOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index 3777e83cde..ffbc168fb7 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -137,7 +137,7 @@ namespace osu.Game.Overlays wikiData.Value = response; path.Value = response.Path; - if (response.Layout == INDEX_PATH.ToLowerInvariant()) + if (response.Layout.Equals(INDEX_PATH, StringComparison.OrdinalIgnoreCase)) { LoadDisplay(new WikiMainPage { From bb2b7d3c313ab8c2e78ca9a790de290f874c0b6d Mon Sep 17 00:00:00 2001 From: Nitrous Date: Tue, 9 Jan 2024 12:23:01 +0800 Subject: [PATCH 2368/4092] Add playback controls. --- .../Play/PlayerSettings/PlaybackSettings.cs | 117 ++++++++++++++++-- 1 file changed, 105 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs index 4753effdb0..69bfe666ee 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs @@ -1,11 +1,17 @@ // 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.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK; namespace osu.Game.Screens.Play.PlayerSettings { @@ -24,34 +30,115 @@ namespace osu.Game.Screens.Play.PlayerSettings private readonly OsuSpriteText multiplierText; + private readonly IconButton play; + + [Resolved] + private GameplayClockContainer gameplayClock { get; set; } = null!; + + [Resolved] + private GameplayState gameplayState { get; set; } = null!; + public PlaybackSettings() : base("playback") { + const double seek_amount = 5000; + const double seek_fast_amount = 10000; + Children = new Drawable[] { - new Container + new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Horizontal = padding }, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, padding), Children = new Drawable[] { - new OsuSpriteText + new FillFlowContainer { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Text = "Playback speed", + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5, 0), + Children = new Drawable[] + { + new IconButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = FontAwesome.Solid.FastBackward, + Action = () => seek(-1, seek_fast_amount), + }, + new IconButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = FontAwesome.Solid.Backward, + Action = () => seek(-1, seek_amount), + }, + play = new IconButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(1.4f), + IconScale = new Vector2(1.4f), + Icon = FontAwesome.Regular.PlayCircle, + Action = () => + { + if (gameplayClock != null) + { + if (gameplayClock.IsRunning) + gameplayClock.Stop(); + else + gameplayClock.Start(); + } + } + }, + new IconButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = FontAwesome.Solid.Forward, + Action = () => seek(1, seek_amount), + }, + new IconButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = FontAwesome.Solid.FastForward, + Action = () => seek(1, seek_fast_amount), + }, + }, }, - multiplierText = new OsuSpriteText + new Container { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Font = OsuFont.GetFont(weight: FontWeight.Bold), - } + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + rateSlider = new PlayerSliderBar + { + LabelText = "Playback speed", + Current = UserPlaybackRate, + }, + multiplierText = new OsuSpriteText + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Font = OsuFont.GetFont(weight: FontWeight.Bold), + Margin = new MarginPadding { Right = 20 }, + } + }, + }, }, }, - rateSlider = new PlayerSliderBar { Current = UserPlaybackRate } }; + + void seek(int direction, double amount) + { + double target = Math.Clamp((gameplayClock?.CurrentTime ?? 0) + (direction * amount), 0, gameplayState.Beatmap.GetLastObjectTime()); + gameplayClock?.Seek(target); + } } protected override void LoadComplete() @@ -59,5 +146,11 @@ namespace osu.Game.Screens.Play.PlayerSettings base.LoadComplete(); rateSlider.Current.BindValueChanged(multiplier => multiplierText.Text = $"{multiplier.NewValue:0.0}x", true); } + + protected override void Update() + { + base.Update(); + play.Icon = gameplayClock.IsRunning ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle; + } } } From 2e041823a15b2720c90c0f2fbcdf8afcee8417df Mon Sep 17 00:00:00 2001 From: Nitrous Date: Tue, 9 Jan 2024 12:24:09 +0800 Subject: [PATCH 2369/4092] Perform null check on gameplay state. --- osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs index 69bfe666ee..3662b7ddc8 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs @@ -136,7 +136,7 @@ namespace osu.Game.Screens.Play.PlayerSettings void seek(int direction, double amount) { - double target = Math.Clamp((gameplayClock?.CurrentTime ?? 0) + (direction * amount), 0, gameplayState.Beatmap.GetLastObjectTime()); + double target = Math.Clamp((gameplayClock?.CurrentTime ?? 0) + (direction * amount), 0, gameplayState?.Beatmap.GetLastObjectTime() ?? 0); gameplayClock?.Seek(target); } } From afa808695bf9c6060f2f706c15172861e1c56269 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Tue, 9 Jan 2024 12:48:11 +0800 Subject: [PATCH 2370/4092] Make resolved properties nullable. --- osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs index 3662b7ddc8..2a8701d4d5 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs @@ -33,10 +33,10 @@ namespace osu.Game.Screens.Play.PlayerSettings private readonly IconButton play; [Resolved] - private GameplayClockContainer gameplayClock { get; set; } = null!; + private GameplayClockContainer? gameplayClock { get; set; } [Resolved] - private GameplayState gameplayState { get; set; } = null!; + private GameplayState? gameplayState { get; set; } public PlaybackSettings() : base("playback") @@ -150,7 +150,7 @@ namespace osu.Game.Screens.Play.PlayerSettings protected override void Update() { base.Update(); - play.Icon = gameplayClock.IsRunning ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle; + play.Icon = gameplayClock?.IsRunning == true ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle; } } } From 3f5899dae041e66d8292acc757e25556a2172926 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jan 2024 14:05:59 +0900 Subject: [PATCH 2371/4092] Fix incorrect implementation of wireframe digits --- .../Play/HUD/ArgonCounterTextComponent.cs | 1 - .../Screens/Play/HUD/ArgonScoreCounter.cs | 36 +++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs index 9d364acd59..f88874f872 100644 --- a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs +++ b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; diff --git a/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs b/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs index 005f7e36a7..f7ca218767 100644 --- a/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonScoreCounter.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 System; using osu.Framework.Bindables; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; @@ -15,6 +16,8 @@ namespace osu.Game.Screens.Play.HUD { public partial class ArgonScoreCounter : GameplayScoreCounter, ISerialisableDrawable { + private ArgonScoreTextComponent scoreText = null!; + protected override double RollingDuration => 500; protected override Easing RollingEasing => Easing.OutQuint; @@ -33,13 +36,42 @@ namespace osu.Game.Screens.Play.HUD protected override LocalisableString FormatCount(long count) => count.ToLocalisableString(); - protected override IHasText CreateText() => new ArgonScoreTextComponent(Anchor.TopRight, BeatmapsetsStrings.ShowScoreboardHeadersScore.ToUpper()) + protected override IHasText CreateText() => scoreText = new ArgonScoreTextComponent(Anchor.TopRight, BeatmapsetsStrings.ShowScoreboardHeadersScore.ToUpper()) { - RequiredDisplayDigits = { BindTarget = RequiredDisplayDigits }, WireframeOpacity = { BindTarget = WireframeOpacity }, ShowLabel = { BindTarget = ShowLabel }, }; + public ArgonScoreCounter() + { + RequiredDisplayDigits.BindValueChanged(_ => updateWireframe()); + } + + public override long DisplayedCount + { + get => base.DisplayedCount; + set + { + base.DisplayedCount = value; + updateWireframe(); + } + } + + private void updateWireframe() + { + scoreText.RequiredDisplayDigits.Value = + Math.Max(RequiredDisplayDigits.Value, getDigitsRequiredForDisplayCount()); + } + + private int getDigitsRequiredForDisplayCount() + { + int digitsRequired = 1; + long c = DisplayedCount; + while ((c /= 10) > 0) + digitsRequired++; + return digitsRequired; + } + private partial class ArgonScoreTextComponent : ArgonCounterTextComponent { public ArgonScoreTextComponent(Anchor anchor, LocalisableString? label = null) From 765d41faa9940e8ae5878babe326cc76f39ba930 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jan 2024 14:06:59 +0900 Subject: [PATCH 2372/4092] Change second occurrence of debug.assert with early return for fallback safety --- osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs index f88874f872..a11f2f01cd 100644 --- a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs +++ b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs @@ -159,7 +159,8 @@ namespace osu.Game.Screens.Play.HUD public ITexturedCharacterGlyph? Get(string? fontName, char character) { // We only service one font. - Debug.Assert(fontName == this.fontName); + if (fontName != this.fontName) + return null; if (cache.TryGetValue(character, out var cached)) return cached; From 5970a68e2d12f4f8fc3a118cf68da7a69c1f1fcf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jan 2024 14:17:33 +0900 Subject: [PATCH 2373/4092] Use invalidation based logic for child anchor position updpates in `DrawableSlider` --- .../Objects/Drawables/DrawableSlider.cs | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 6cc8b8e935..4099d47d61 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -11,6 +11,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Layout; using osu.Game.Audio; using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Objects; @@ -36,8 +37,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private ShakeContainer shakeContainer; - private Vector2? childAnchorPosition; - protected override IEnumerable DimmablePieces => new Drawable[] { HeadCircle, @@ -68,6 +67,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private Container repeatContainer; private PausableSkinnableSound slidingSample; + private readonly LayoutValue drawSizeLayout; + public DrawableSlider() : this(null) { @@ -84,6 +85,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables AlwaysPresent = true, Alpha = 0 }; + AddLayout(drawSizeLayout = new LayoutValue(Invalidation.DrawSize | Invalidation.MiscGeometry)); } [BackgroundDependencyLoader] @@ -121,11 +123,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } }); - PositionBindable.BindValueChanged(_ => - { - Position = HitObject.StackedPosition; - childAnchorPosition = null; - }); + PositionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); StackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); ScaleBindable.BindValueChanged(scale => Ball.Scale = new Vector2(scale.NewValue)); @@ -258,17 +256,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Size = SliderBody?.Size ?? Vector2.Zero; OriginPosition = SliderBody?.PathOffset ?? Vector2.Zero; - if (DrawSize != Vector2.Zero) + if (!drawSizeLayout.IsValid) { Vector2 pos = Vector2.Divide(OriginPosition, DrawSize); + foreach (var obj in NestedHitObjects) + obj.RelativeAnchorPosition = pos; + Ball.RelativeAnchorPosition = pos; - if (pos != childAnchorPosition) - { - childAnchorPosition = pos; - foreach (var obj in NestedHitObjects) - obj.RelativeAnchorPosition = pos; - Ball.RelativeAnchorPosition = pos; - } + drawSizeLayout.Validate(); } } From 1f6e1cbe56d5f0dce5c3fe60f779bdd66c5fe655 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jan 2024 14:45:21 +0900 Subject: [PATCH 2374/4092] Allow interacting with playlist item buttons when not selected --- .../Screens/OnlinePlay/DrawableRoomPlaylist.cs | 6 +++++- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 16 +++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs index 8abdec9ade..5a1648c91f 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs @@ -165,7 +165,11 @@ namespace osu.Game.Screens.OnlinePlay { d.SelectedItem.BindTarget = SelectedItem; d.RequestDeletion = i => RequestDeletion?.Invoke(i); - d.RequestResults = i => RequestResults?.Invoke(i); + d.RequestResults = i => + { + SelectedItem.Value = i; + RequestResults?.Invoke(i); + }; d.RequestEdit = i => RequestEdit?.Invoke(i); d.AllowReordering = AllowReordering; d.AllowDeletion = AllowDeletion; diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 8f405399a7..823f6ea308 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -118,8 +118,6 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(CanBeNull = true)] private ManageCollectionsDialog manageCollectionsDialog { get; set; } - protected override bool ShouldBeConsideredForInput(Drawable child) => AllowReordering || AllowDeletion || !AllowSelection || SelectedItem.Value == Model; - public DrawableRoomPlaylistItem(PlaylistItem item) : base(item) { @@ -367,7 +365,7 @@ namespace osu.Game.Screens.OnlinePlay AutoSizeAxes = Axes.Both, Margin = new MarginPadding { Left = 8, Right = 8 }, }, - mainFillFlow = new FillFlowContainer + mainFillFlow = new MainFlow(() => SelectedItem.Value == Model) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -670,5 +668,17 @@ namespace osu.Game.Screens.OnlinePlay public LocalisableString TooltipText => avatar.TooltipText; } } + + public partial class MainFlow : FillFlowContainer + { + private readonly Func isSelected; + + public override bool PropagatePositionalInputSubTree => isSelected(); + + public MainFlow(Func isSelected) + { + this.isSelected = isSelected; + } + } } } From 79ff767eba341cba9e4c81e3410eacf75eb4cd07 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jan 2024 14:51:30 +0900 Subject: [PATCH 2375/4092] Remove unused using --- osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs index 8765d8485a..e70d35f74a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs @@ -11,7 +11,6 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; -using osu.Game.Overlays.Wiki; namespace osu.Game.Tests.Visual.Online { From 19d1fff5362b3652eb70c366f7ea361f89af4737 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jan 2024 15:37:29 +0900 Subject: [PATCH 2376/4092] Use native query to avoid huge overheads when cleaning up realm files --- osu.Game/Database/RealmFileStore.cs | 9 +++------ osu.Game/Models/RealmFile.cs | 4 ++++ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Database/RealmFileStore.cs b/osu.Game/Database/RealmFileStore.cs index 1da64d5be8..f1ed3f4b63 100644 --- a/osu.Game/Database/RealmFileStore.cs +++ b/osu.Game/Database/RealmFileStore.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.IO; using System.Linq; using osu.Framework.Extensions; @@ -98,15 +99,11 @@ namespace osu.Game.Database // can potentially be run asynchronously, although we will need to consider operation order for disk deletion vs realm removal. realm.Write(r => { - // TODO: consider using a realm native query to avoid iterating all files (https://github.com/realm/realm-dotnet/issues/2659#issuecomment-927823707) - var files = r.All().ToList(); - - foreach (var file in files) + foreach (var file in r.All().Filter("Usages.@count = 0")) { totalFiles++; - if (file.BacklinksCount > 0) - continue; + Debug.Assert(file.BacklinksCount == 0); try { diff --git a/osu.Game/Models/RealmFile.cs b/osu.Game/Models/RealmFile.cs index 2faa3f0ca6..4d1642fb5f 100644 --- a/osu.Game/Models/RealmFile.cs +++ b/osu.Game/Models/RealmFile.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 System.Linq; using osu.Game.IO; using Realms; @@ -11,5 +12,8 @@ namespace osu.Game.Models { [PrimaryKey] public string Hash { get; set; } = string.Empty; + + [Backlink(nameof(RealmNamedFileUsage.File))] + public IQueryable Usages { get; } = null!; } } From 99c76854956f1d6f3116a462fa6af4f0320500c6 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Tue, 9 Jan 2024 15:18:21 +0800 Subject: [PATCH 2377/4092] use `GameplayClock.IsPaused` bindable instead of polling in `Update` --- .../Screens/Play/PlayerSettings/PlaybackSettings.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs index 2a8701d4d5..a0fafb821c 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs @@ -32,6 +32,8 @@ namespace osu.Game.Screens.Play.PlayerSettings private readonly IconButton play; + private readonly BindableBool isPaused = new BindableBool(); + [Resolved] private GameplayClockContainer? gameplayClock { get; set; } @@ -134,6 +136,8 @@ namespace osu.Game.Screens.Play.PlayerSettings }, }; + isPaused.BindValueChanged(e => play.Icon = e.NewValue ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle, true); + void seek(int direction, double amount) { double target = Math.Clamp((gameplayClock?.CurrentTime ?? 0) + (direction * amount), 0, gameplayState?.Beatmap.GetLastObjectTime() ?? 0); @@ -145,12 +149,7 @@ namespace osu.Game.Screens.Play.PlayerSettings { base.LoadComplete(); rateSlider.Current.BindValueChanged(multiplier => multiplierText.Text = $"{multiplier.NewValue:0.0}x", true); - } - - protected override void Update() - { - base.Update(); - play.Icon = gameplayClock?.IsRunning == true ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle; + gameplayClock?.IsPaused.BindTo(isPaused); } } } From bdecac6d797661836279482a031ee97d3aae955a Mon Sep 17 00:00:00 2001 From: Nitrous Date: Tue, 9 Jan 2024 15:19:54 +0800 Subject: [PATCH 2378/4092] Inverse check. --- osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs index a0fafb821c..7974b07438 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs @@ -136,7 +136,7 @@ namespace osu.Game.Screens.Play.PlayerSettings }, }; - isPaused.BindValueChanged(e => play.Icon = e.NewValue ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle, true); + isPaused.BindValueChanged(e => play.Icon = !e.NewValue ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle, true); void seek(int direction, double amount) { From 1837b31f9b5e1d3a2b829fe97105a0abcc882e8b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jan 2024 16:38:02 +0900 Subject: [PATCH 2379/4092] Remove usage of `HealthDisplay.BindValueChanged` Health updates very often when using HP drain. Let's avoid bindable overheads. --- osu.Game/Screens/Play/HUD/HealthDisplay.cs | 29 ++++++++--- osu.Game/Skinning/LegacyHealthDisplay.cs | 56 ++++++++++------------ 2 files changed, 48 insertions(+), 37 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HealthDisplay.cs b/osu.Game/Screens/Play/HUD/HealthDisplay.cs index 7747036826..a20121b20b 100644 --- a/osu.Game/Screens/Play/HUD/HealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HealthDisplay.cs @@ -56,13 +56,6 @@ namespace osu.Game.Screens.Play.HUD // Don't bind directly so we can animate the startup procedure. health = HealthProcessor.Health.GetBoundCopy(); - health.BindValueChanged(h => - { - if (initialIncrease != null) - FinishInitialAnimation(h.OldValue); - - Current.Value = h.NewValue; - }); if (hudOverlay != null) showHealthBar.BindTo(hudOverlay.ShowHealthBar); @@ -76,6 +69,28 @@ namespace osu.Game.Screens.Play.HUD Current.Value = health.Value; } + protected override void Update() + { + base.Update(); + + // Health changes every frame in draining situations. + // Manually handle value changes to avoid bindable event flow overhead. + if (health.Value != Current.Value) + { + if (initialIncrease != null) + FinishInitialAnimation(Current.Value); + + Current.Value = health.Value; + + if (health.Value > Current.Value) + HealthIncreased(); + } + } + + protected virtual void HealthIncreased() + { + } + private void startInitialAnimation() { if (Current.Value >= health.Value) diff --git a/osu.Game/Skinning/LegacyHealthDisplay.cs b/osu.Game/Skinning/LegacyHealthDisplay.cs index 845fc77394..00e19c4f76 100644 --- a/osu.Game/Skinning/LegacyHealthDisplay.cs +++ b/osu.Game/Skinning/LegacyHealthDisplay.cs @@ -79,7 +79,13 @@ namespace osu.Game.Skinning marker.Position = fill.Position + new Vector2(fill.DrawWidth, isNewStyle ? fill.DrawHeight / 2 : 0); } - protected override void Flash() => marker.Flash(); + protected override void HealthIncreased() + { + marker.Bulge(); + base.HealthIncreased(); + } + + protected override void Flash() => marker.Flash(Current.Value >= epic_cutoff); private static Texture getTexture(ISkin skin, string name) => skin?.GetTexture($"scorebar-{name}"); @@ -113,19 +119,16 @@ namespace osu.Game.Skinning Origin = Anchor.Centre, }; - protected override void LoadComplete() + protected override void Update() { - base.LoadComplete(); + base.Update(); - Current.BindValueChanged(hp => - { - if (hp.NewValue < 0.2f) - Main.Texture = superDangerTexture; - else if (hp.NewValue < epic_cutoff) - Main.Texture = dangerTexture; - else - Main.Texture = normalTexture; - }); + if (Current.Value < 0.2f) + Main.Texture = superDangerTexture; + else if (Current.Value < epic_cutoff) + Main.Texture = dangerTexture; + else + Main.Texture = normalTexture; } } @@ -226,37 +229,30 @@ namespace osu.Game.Skinning public abstract Sprite CreateSprite(); - protected override void LoadComplete() + public override void Flash(bool isEpic) { - base.LoadComplete(); - - Current.BindValueChanged(val => - { - if (val.NewValue > val.OldValue) - bulgeMain(); - }); - } - - public override void Flash() - { - bulgeMain(); - - bool isEpic = Current.Value >= epic_cutoff; - + Bulge(); explode.Blending = isEpic ? BlendingParameters.Additive : BlendingParameters.Inherit; explode.ScaleTo(1).Then().ScaleTo(isEpic ? 2 : 1.6f, 120); explode.FadeOutFromOne(120); } - private void bulgeMain() => + public override void Bulge() + { + base.Bulge(); Main.ScaleTo(1.4f).Then().ScaleTo(1, 200, Easing.Out); + } } public partial class LegacyHealthPiece : CompositeDrawable { public Bindable Current { get; } = new Bindable(); - public virtual void Flash() + public virtual void Bulge() + { + } + + public virtual void Flash(bool isEpic) { } } From d83b8dbdaf489320b1ef2632e7b9fba5a3d7c26a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jan 2024 16:50:27 +0900 Subject: [PATCH 2380/4092] Refactor `ArgonHealthDisplay` to user interpolation and less bindable events --- .../Screens/Play/HUD/ArgonHealthDisplay.cs | 39 +++++++------------ 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 8acc43c091..f15789601c 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -158,7 +158,6 @@ namespace osu.Game.Screens.Play.HUD base.LoadComplete(); HealthProcessor.NewJudgement += onNewJudgement; - Current.BindValueChanged(onCurrentChanged, true); // we're about to set `RelativeSizeAxes` depending on the value of `UseRelativeSize`. // setting `RelativeSizeAxes` internally transforms absolute sizing to relative and back to keep the size the same, @@ -173,31 +172,6 @@ namespace osu.Game.Screens.Play.HUD private void onNewJudgement(JudgementResult result) => pendingMissAnimation |= !result.IsHit; - private void onCurrentChanged(ValueChangedEvent valueChangedEvent) - // schedule display updates one frame later to ensure we know the judgement result causing this change (if there is one). - => Scheduler.AddOnce(updateDisplay); - - private void updateDisplay() - { - double newHealth = Current.Value; - - if (newHealth >= GlowBarValue) - finishMissDisplay(); - - double time = newHealth > GlowBarValue ? 500 : 250; - - // TODO: this should probably use interpolation in update. - this.TransformTo(nameof(HealthBarValue), newHealth, time, Easing.OutQuint); - - if (pendingMissAnimation && newHealth < GlowBarValue) - triggerMissDisplay(); - - pendingMissAnimation = false; - - if (!displayingMiss) - this.TransformTo(nameof(GlowBarValue), newHealth, time, Easing.OutQuint); - } - protected override void Update() { base.Update(); @@ -210,6 +184,19 @@ namespace osu.Game.Screens.Play.HUD mainBar.Alpha = (float)Interpolation.DampContinuously(mainBar.Alpha, Current.Value > 0 ? 1 : 0, 40, Time.Elapsed); glowBar.Alpha = (float)Interpolation.DampContinuously(glowBar.Alpha, GlowBarValue > 0 ? 1 : 0, 40, Time.Elapsed); + + double newHealth = Current.Value; + + if (newHealth >= GlowBarValue) + finishMissDisplay(); + + if (pendingMissAnimation && newHealth < GlowBarValue) + triggerMissDisplay(); + pendingMissAnimation = false; + + HealthBarValue = Interpolation.DampContinuously(HealthBarValue, newHealth, 50, Time.Elapsed); + if (!displayingMiss) + GlowBarValue = Interpolation.DampContinuously(GlowBarValue, newHealth, 50, Time.Elapsed); } protected override void FinishInitialAnimation(double value) From 63961ea276a282f681e1b57866ad36dbb3d472e1 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Tue, 9 Jan 2024 16:08:29 +0800 Subject: [PATCH 2381/4092] use `RepeatingButtonBehavior` for seek buttons --- .../Play/PlayerSettings/PlaybackSettings.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs index 7974b07438..6e1ce39d9b 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs @@ -11,6 +11,7 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Edit.Timing; using osuTK; namespace osu.Game.Screens.Play.PlayerSettings @@ -64,14 +65,14 @@ namespace osu.Game.Screens.Play.PlayerSettings Spacing = new Vector2(5, 0), Children = new Drawable[] { - new IconButton + new SeekButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, Icon = FontAwesome.Solid.FastBackward, Action = () => seek(-1, seek_fast_amount), }, - new IconButton + new SeekButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -96,14 +97,14 @@ namespace osu.Game.Screens.Play.PlayerSettings } } }, - new IconButton + new SeekButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, Icon = FontAwesome.Solid.Forward, Action = () => seek(1, seek_amount), }, - new IconButton + new SeekButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -151,5 +152,13 @@ namespace osu.Game.Screens.Play.PlayerSettings rateSlider.Current.BindValueChanged(multiplier => multiplierText.Text = $"{multiplier.NewValue:0.0}x", true); gameplayClock?.IsPaused.BindTo(isPaused); } + + private partial class SeekButton : IconButton + { + public SeekButton() + { + AddInternal(new RepeatingButtonBehaviour(this)); + } + } } } From f1b4c305b02df1d0495f30f2390bd168cdbf8249 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jan 2024 17:09:19 +0900 Subject: [PATCH 2382/4092] Change skinnable health test scene to drain --- .../Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs index 15a7b48323..f215a5d528 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs @@ -35,6 +35,13 @@ namespace osu.Game.Tests.Visual.Gameplay }); } + protected override void Update() + { + base.Update(); + + healthProcessor.Health.Value -= 0.0001f * Time.Elapsed; + } + [Test] public void TestHealthDisplayIncrementing() { From b3533d270c0c9ad70cb89919a3c55d3899be2fd3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jan 2024 17:20:49 +0900 Subject: [PATCH 2383/4092] Remove delegate overhead of `HealthBarValue`/`GlowBarValue` --- .../Screens/Play/HUD/ArgonHealthDisplay.cs | 44 +++++-------------- 1 file changed, 10 insertions(+), 34 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index f15789601c..43de99b4b5 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -63,34 +63,8 @@ namespace osu.Game.Screens.Play.HUD private double glowBarValue; - public double GlowBarValue - { - get => glowBarValue; - set - { - if (glowBarValue == value) - return; - - glowBarValue = value; - Scheduler.AddOnce(updatePathVertices); - } - } - private double healthBarValue; - public double HealthBarValue - { - get => healthBarValue; - set - { - if (healthBarValue == value) - return; - - healthBarValue = value; - Scheduler.AddOnce(updatePathVertices); - } - } - public const float MAIN_PATH_RADIUS = 10f; private const float curve_start_offset = 70; @@ -183,27 +157,29 @@ namespace osu.Game.Screens.Play.HUD } mainBar.Alpha = (float)Interpolation.DampContinuously(mainBar.Alpha, Current.Value > 0 ? 1 : 0, 40, Time.Elapsed); - glowBar.Alpha = (float)Interpolation.DampContinuously(glowBar.Alpha, GlowBarValue > 0 ? 1 : 0, 40, Time.Elapsed); + glowBar.Alpha = (float)Interpolation.DampContinuously(glowBar.Alpha, glowBarValue > 0 ? 1 : 0, 40, Time.Elapsed); double newHealth = Current.Value; - if (newHealth >= GlowBarValue) + if (newHealth >= glowBarValue) finishMissDisplay(); - if (pendingMissAnimation && newHealth < GlowBarValue) + if (pendingMissAnimation && newHealth < glowBarValue) triggerMissDisplay(); pendingMissAnimation = false; - HealthBarValue = Interpolation.DampContinuously(HealthBarValue, newHealth, 50, Time.Elapsed); + healthBarValue = Interpolation.DampContinuously(healthBarValue, newHealth, 50, Time.Elapsed); if (!displayingMiss) - GlowBarValue = Interpolation.DampContinuously(GlowBarValue, newHealth, 50, Time.Elapsed); + glowBarValue = Interpolation.DampContinuously(glowBarValue, newHealth, 50, Time.Elapsed); + + updatePathVertices(); } protected override void FinishInitialAnimation(double value) { base.FinishInitialAnimation(value); - this.TransformTo(nameof(HealthBarValue), value, 500, Easing.OutQuint); - this.TransformTo(nameof(GlowBarValue), value, 250, Easing.OutQuint); + this.TransformTo(nameof(healthBarValue), value, 500, Easing.OutQuint); + this.TransformTo(nameof(glowBarValue), value, 250, Easing.OutQuint); } protected override void Flash() @@ -232,7 +208,7 @@ namespace osu.Game.Screens.Play.HUD this.Delay(500).Schedule(() => { - this.TransformTo(nameof(GlowBarValue), Current.Value, 300, Easing.OutQuint); + this.TransformTo(nameof(glowBarValue), Current.Value, 300, Easing.OutQuint); finishMissDisplay(); }, out resetMissBarDelegate); From c081ca21451baa684c343c8a019b90432a4c1336 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Tue, 9 Jan 2024 16:30:48 +0800 Subject: [PATCH 2384/4092] Make field to a local. --- osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs index 6e1ce39d9b..e208be6095 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs @@ -31,8 +31,6 @@ namespace osu.Game.Screens.Play.PlayerSettings private readonly OsuSpriteText multiplierText; - private readonly IconButton play; - private readonly BindableBool isPaused = new BindableBool(); [Resolved] @@ -47,6 +45,8 @@ namespace osu.Game.Screens.Play.PlayerSettings const double seek_amount = 5000; const double seek_fast_amount = 10000; + IconButton play; + Children = new Drawable[] { new FillFlowContainer From 12a59eb34cb44322315ec3608de8c112b2b217ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jan 2024 17:30:13 +0900 Subject: [PATCH 2385/4092] Remove vertex update overheads --- .../Screens/Play/HUD/ArgonHealthDisplay.cs | 50 ++++++++++--------- osu.Game/Screens/Play/HUD/HealthDisplay.cs | 9 ++-- osu.Game/Skinning/LegacyHealthDisplay.cs | 7 +-- 3 files changed, 35 insertions(+), 31 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 43de99b4b5..e22f45b40d 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -58,8 +57,7 @@ namespace osu.Game.Screens.Play.HUD private bool displayingMiss => resetMissBarDelegate != null; - private readonly List missBarVertices = new List(); - private readonly List healthBarVertices = new List(); + private readonly List vertices = new List(); private double glowBarValue; @@ -156,23 +154,27 @@ namespace osu.Game.Screens.Play.HUD drawSizeLayout.Validate(); } + healthBarValue = Interpolation.DampContinuously(healthBarValue, Current.Value, 50, Time.Elapsed); + if (!displayingMiss) + glowBarValue = Interpolation.DampContinuously(glowBarValue, Current.Value, 50, Time.Elapsed); + mainBar.Alpha = (float)Interpolation.DampContinuously(mainBar.Alpha, Current.Value > 0 ? 1 : 0, 40, Time.Elapsed); glowBar.Alpha = (float)Interpolation.DampContinuously(glowBar.Alpha, glowBarValue > 0 ? 1 : 0, 40, Time.Elapsed); - double newHealth = Current.Value; + updatePathVertices(); + } - if (newHealth >= glowBarValue) + protected override void HealthChanged(bool increase) + { + if (Current.Value >= glowBarValue) finishMissDisplay(); - if (pendingMissAnimation && newHealth < glowBarValue) + if (pendingMissAnimation && Current.Value < glowBarValue) triggerMissDisplay(); + pendingMissAnimation = false; - healthBarValue = Interpolation.DampContinuously(healthBarValue, newHealth, 50, Time.Elapsed); - if (!displayingMiss) - glowBarValue = Interpolation.DampContinuously(glowBarValue, newHealth, 50, Time.Elapsed); - - updatePathVertices(); + base.HealthChanged(increase); } protected override void FinishInitialAnimation(double value) @@ -265,7 +267,6 @@ namespace osu.Game.Screens.Play.HUD if (DrawWidth - padding < rescale_cutoff) rescalePathProportionally(); - List vertices = new List(); barPath.GetPathToProgress(vertices, 0.0, 1.0); background.Vertices = vertices; @@ -295,20 +296,23 @@ namespace osu.Game.Screens.Play.HUD private void updatePathVertices() { - barPath.GetPathToProgress(healthBarVertices, 0.0, healthBarValue); - barPath.GetPathToProgress(missBarVertices, healthBarValue, Math.Max(glowBarValue, healthBarValue)); + barPath.GetPathToProgress(vertices, 0.0, healthBarValue); + if (vertices.Count == 0) vertices.Add(Vector2.Zero); + Vector2 initialVertex = vertices[0]; + for (int i = 0; i < vertices.Count; i++) + vertices[i] -= initialVertex; - if (healthBarVertices.Count == 0) - healthBarVertices.Add(Vector2.Zero); + mainBar.Vertices = vertices; + mainBar.Position = initialVertex; - if (missBarVertices.Count == 0) - missBarVertices.Add(Vector2.Zero); + barPath.GetPathToProgress(vertices, healthBarValue, Math.Max(glowBarValue, healthBarValue)); + if (vertices.Count == 0) vertices.Add(Vector2.Zero); + initialVertex = vertices[0]; + for (int i = 0; i < vertices.Count; i++) + vertices[i] -= initialVertex; - glowBar.Vertices = missBarVertices.Select(v => v - missBarVertices[0]).ToList(); - glowBar.Position = missBarVertices[0]; - - mainBar.Vertices = healthBarVertices.Select(v => v - healthBarVertices[0]).ToList(); - mainBar.Position = healthBarVertices[0]; + glowBar.Vertices = vertices; + glowBar.Position = initialVertex; } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/Play/HUD/HealthDisplay.cs b/osu.Game/Screens/Play/HUD/HealthDisplay.cs index a20121b20b..d5b238cd50 100644 --- a/osu.Game/Screens/Play/HUD/HealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HealthDisplay.cs @@ -8,6 +8,7 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Threading; +using osu.Framework.Utils; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; @@ -75,19 +76,17 @@ namespace osu.Game.Screens.Play.HUD // Health changes every frame in draining situations. // Manually handle value changes to avoid bindable event flow overhead. - if (health.Value != Current.Value) + if (!Precision.AlmostEquals(health.Value, Current.Value, 0.001f)) { if (initialIncrease != null) FinishInitialAnimation(Current.Value); + HealthChanged(Current.Value > health.Value); Current.Value = health.Value; - - if (health.Value > Current.Value) - HealthIncreased(); } } - protected virtual void HealthIncreased() + protected virtual void HealthChanged(bool increase) { } diff --git a/osu.Game/Skinning/LegacyHealthDisplay.cs b/osu.Game/Skinning/LegacyHealthDisplay.cs index 00e19c4f76..9c06cbbfb5 100644 --- a/osu.Game/Skinning/LegacyHealthDisplay.cs +++ b/osu.Game/Skinning/LegacyHealthDisplay.cs @@ -79,10 +79,11 @@ namespace osu.Game.Skinning marker.Position = fill.Position + new Vector2(fill.DrawWidth, isNewStyle ? fill.DrawHeight / 2 : 0); } - protected override void HealthIncreased() + protected override void HealthChanged(bool increase) { - marker.Bulge(); - base.HealthIncreased(); + if (increase) + marker.Bulge(); + base.HealthChanged(increase); } protected override void Flash() => marker.Flash(Current.Value >= epic_cutoff); From b6505ba0634887d1d48db6aaa880a55622b3f715 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jan 2024 18:16:53 +0900 Subject: [PATCH 2386/4092] Reduce colour tween overhead and mark other calls of concern --- osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index e22f45b40d..f1653c8742 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -188,15 +188,9 @@ namespace osu.Game.Screens.Play.HUD { base.Flash(); - mainBar.TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour.Opacity(0.8f)) - .TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour, 300, Easing.OutQuint); - if (!displayingMiss) { - glowBar.TransformTo(nameof(BarPath.BarColour), Colour4.White, 30, Easing.OutQuint) - .Then() - .TransformTo(nameof(BarPath.BarColour), main_bar_colour, 1000, Easing.OutQuint); - + // TODO: REMOVE THIS. It's recreating textures. glowBar.TransformTo(nameof(BarPath.GlowColour), Colour4.White, 30, Easing.OutQuint) .Then() .TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour, 300, Easing.OutQuint); @@ -214,9 +208,11 @@ namespace osu.Game.Screens.Play.HUD finishMissDisplay(); }, out resetMissBarDelegate); + // TODO: REMOVE THIS. It's recreating textures. glowBar.TransformTo(nameof(BarPath.BarColour), new Colour4(255, 147, 147, 255), 100, Easing.OutQuint).Then() .TransformTo(nameof(BarPath.BarColour), new Colour4(255, 93, 93, 255), 800, Easing.OutQuint); + // TODO: REMOVE THIS. It's recreating textures. glowBar.TransformTo(nameof(BarPath.GlowColour), new Colour4(253, 0, 0, 255).Lighten(0.2f)) .TransformTo(nameof(BarPath.GlowColour), new Colour4(253, 0, 0, 255), 800, Easing.OutQuint); } @@ -228,6 +224,7 @@ namespace osu.Game.Screens.Play.HUD if (Current.Value > 0) { + // TODO: REMOVE THIS. It's recreating textures. glowBar.TransformTo(nameof(BarPath.BarColour), main_bar_colour, 300, Easing.In); glowBar.TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour, 300, Easing.In); } From 80892f316754c4004112f9be1dbe4dc4a8723191 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jan 2024 18:18:11 +0900 Subject: [PATCH 2387/4092] Fix misses not displaying properly --- osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index f1653c8742..78720ec087 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -169,10 +169,11 @@ namespace osu.Game.Screens.Play.HUD if (Current.Value >= glowBarValue) finishMissDisplay(); - if (pendingMissAnimation && Current.Value < glowBarValue) + if (pendingMissAnimation) + { triggerMissDisplay(); - - pendingMissAnimation = false; + pendingMissAnimation = false; + } base.HealthChanged(increase); } From 9c7e555237b29e241e22b0c40e14e94afdbec4bc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jan 2024 18:27:37 +0900 Subject: [PATCH 2388/4092] Fix initial animation not playing correctly --- osu.Game/Screens/Play/HUD/HealthDisplay.cs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HealthDisplay.cs b/osu.Game/Screens/Play/HUD/HealthDisplay.cs index d5b238cd50..cd4d050b52 100644 --- a/osu.Game/Screens/Play/HUD/HealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HealthDisplay.cs @@ -36,6 +36,8 @@ namespace osu.Game.Screens.Play.HUD private BindableNumber health = null!; + protected bool InitialAnimationPlaying => initialIncrease != null; + private ScheduledDelegate? initialIncrease; /// @@ -64,25 +66,35 @@ namespace osu.Game.Screens.Play.HUD // this probably shouldn't be operating on `this.` showHealthBar.BindValueChanged(healthBar => this.FadeTo(healthBar.NewValue ? 1 : 0, HUDOverlay.FADE_DURATION, HUDOverlay.FADE_EASING), true); + initialHealthValue = health.Value; + if (PlayInitialIncreaseAnimation) startInitialAnimation(); else Current.Value = health.Value; } + private double lastValue; + private double initialHealthValue; + protected override void Update() { base.Update(); - // Health changes every frame in draining situations. - // Manually handle value changes to avoid bindable event flow overhead. - if (!Precision.AlmostEquals(health.Value, Current.Value, 0.001f)) + if (!InitialAnimationPlaying || health.Value != initialHealthValue) { + Current.Value = health.Value; + if (initialIncrease != null) FinishInitialAnimation(Current.Value); + } - HealthChanged(Current.Value > health.Value); - Current.Value = health.Value; + // Health changes every frame in draining situations. + // Manually handle value changes to avoid bindable event flow overhead. + if (!Precision.AlmostEquals(lastValue, Current.Value, 0.001f)) + { + HealthChanged(Current.Value > lastValue); + lastValue = Current.Value; } } From 6ac1c799bde83a8fb49ad44e37de703421a4097c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jan 2024 18:34:20 +0900 Subject: [PATCH 2389/4092] Fix `SettingsToolboxGroup` allocating excessively due to missing cache validation --- osu.Game/Overlays/SettingsToolboxGroup.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/SettingsToolboxGroup.cs b/osu.Game/Overlays/SettingsToolboxGroup.cs index c0948c1eab..de13bd96d4 100644 --- a/osu.Game/Overlays/SettingsToolboxGroup.cs +++ b/osu.Game/Overlays/SettingsToolboxGroup.cs @@ -151,9 +151,12 @@ namespace osu.Game.Overlays base.Update(); if (!headerTextVisibilityCache.IsValid) + { // These toolbox grouped may be contracted to only show icons. // For now, let's hide the header to avoid text truncation weirdness in such cases. headerText.FadeTo(headerText.DrawWidth < DrawWidth ? 1 : 0, 150, Easing.OutQuint); + headerTextVisibilityCache.Validate(); + } } protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source) From 66b3945cd644abd7704adde5284211768981a36e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 9 Jan 2024 10:44:30 +0100 Subject: [PATCH 2390/4092] Move current screen check to better place --- osu.Game/Screens/Select/PlaySongSelect.cs | 3 --- osu.Game/Screens/Select/SongSelect.cs | 3 ++- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 3cf8de5267..4951504ff5 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -92,9 +92,6 @@ namespace osu.Game.Screens.Select { if (playerLoader != null) return false; - if (!this.IsCurrentScreen()) - return false; - modsAtGameplayStart = Mods.Value; // Ctrl+Enter should start map with autoplay enabled. diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 2d5c44e5a5..bf1724995a 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -660,7 +660,8 @@ namespace osu.Game.Screens.Select logo.Action = () => { - FinaliseSelection(); + if (this.IsCurrentScreen()) + FinaliseSelection(); return false; }; } From cac0b0de6dba025e99692d208cd56f545bb05660 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 9 Jan 2024 11:38:01 +0100 Subject: [PATCH 2391/4092] Remove unused using directive --- osu.Game/Database/RealmFileStore.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Database/RealmFileStore.cs b/osu.Game/Database/RealmFileStore.cs index f1ed3f4b63..1bd22af4c7 100644 --- a/osu.Game/Database/RealmFileStore.cs +++ b/osu.Game/Database/RealmFileStore.cs @@ -4,7 +4,6 @@ using System; using System.Diagnostics; using System.IO; -using System.Linq; using osu.Framework.Extensions; using osu.Framework.IO.Stores; using osu.Framework.Logging; From a8a70be04ab13f0ad4c565b86ee206c19ba01d02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 9 Jan 2024 11:49:42 +0100 Subject: [PATCH 2392/4092] Reference property via `nameof` rather than hardcoding --- osu.Game/Database/RealmFileStore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmFileStore.cs b/osu.Game/Database/RealmFileStore.cs index 1bd22af4c7..9683baec69 100644 --- a/osu.Game/Database/RealmFileStore.cs +++ b/osu.Game/Database/RealmFileStore.cs @@ -98,7 +98,7 @@ namespace osu.Game.Database // can potentially be run asynchronously, although we will need to consider operation order for disk deletion vs realm removal. realm.Write(r => { - foreach (var file in r.All().Filter("Usages.@count = 0")) + foreach (var file in r.All().Filter(@$"{nameof(RealmFile.Usages)}.@count = 0")) { totalFiles++; From 4110adc4c0a9fdc763ec73061169a5e4e8952a7c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jan 2024 20:16:27 +0900 Subject: [PATCH 2393/4092] Fix missing wireframe on argon combo counter --- .../Screens/Play/HUD/ArgonAccuracyCounter.cs | 1 + .../Screens/Play/HUD/ArgonComboCounter.cs | 24 +++++++++++++++++++ osu.Game/Screens/Play/HUD/ComboCounter.cs | 5 ---- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs index 521ad63426..5284e3167a 100644 --- a/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs @@ -83,6 +83,7 @@ namespace osu.Game.Screens.Play.HUD }, fractionPart = new ArgonCounterTextComponent(Anchor.TopLeft) { + RequiredDisplayDigits = { Value = 2 }, WireframeOpacity = { BindTarget = WireframeOpacity }, Scale = new Vector2(0.5f), }, diff --git a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs index 5ea7fd0b82..af884aa441 100644 --- a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs @@ -57,6 +57,30 @@ namespace osu.Game.Screens.Play.HUD }); } + public override int DisplayedCount + { + get => base.DisplayedCount; + set + { + base.DisplayedCount = value; + updateWireframe(); + } + } + + private void updateWireframe() + { + text.RequiredDisplayDigits.Value = getDigitsRequiredForDisplayCount(); + } + + private int getDigitsRequiredForDisplayCount() + { + int digitsRequired = 1; + long c = DisplayedCount; + while ((c /= 10) > 0) + digitsRequired++; + return digitsRequired; + } + protected override LocalisableString FormatCount(int count) => $@"{count}x"; protected override IHasText CreateText() => text = new ArgonCounterTextComponent(Anchor.TopLeft, MatchesStrings.MatchScoreStatsCombo.ToUpper()) diff --git a/osu.Game/Screens/Play/HUD/ComboCounter.cs b/osu.Game/Screens/Play/HUD/ComboCounter.cs index 17531281aa..93802e11c2 100644 --- a/osu.Game/Screens/Play/HUD/ComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/ComboCounter.cs @@ -11,11 +11,6 @@ namespace osu.Game.Screens.Play.HUD { public bool UsesFixedAnchor { get; set; } - protected ComboCounter() - { - Current.Value = DisplayedCount = 0; - } - protected override double GetProportionalDuration(int currentValue, int newValue) { return Math.Abs(currentValue - newValue) * RollingDuration * 100.0f; From 8ad697ff4cea857bec7f18a94723b098c6e036ef Mon Sep 17 00:00:00 2001 From: wooster0 Date: Tue, 9 Jan 2024 21:28:46 +0900 Subject: [PATCH 2394/4092] apply some suggestions/corrections --- .../TestScenePlaylistsResultsScreen.cs | 16 ++++++---- osu.Game/Screens/Ranking/ResultsScreen.cs | 32 +++++++++---------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index e36e7f54ba..98aeb66428 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -79,6 +79,7 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("bind user score info handler", () => bindHandler(userScore: userScore)); createResults(() => userScore); + waitForDisplay(); AddAssert("user score selected", () => this.ChildrenOfType().Single(p => p.Score.OnlineID == userScore.OnlineID).State == PanelState.Expanded); AddAssert($"score panel position is {real_user_position}", @@ -91,6 +92,7 @@ namespace osu.Game.Tests.Visual.Playlists InitialiseUserScoresAndBeatmap(); createResults(); + waitForDisplay(); AddAssert("top score selected", () => this.ChildrenOfType().OrderByDescending(p => p.Score.TotalScore).First().State == PanelState.Expanded); } @@ -103,6 +105,7 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("bind user score info handler", () => bindHandler(true, userScore)); createResults(() => userScore); + waitForDisplay(); AddAssert("more than 1 panel displayed", () => this.ChildrenOfType().Count() > 1); AddAssert("user score selected", () => this.ChildrenOfType().Single(p => p.Score.OnlineID == userScore.OnlineID).State == PanelState.Expanded); @@ -116,6 +119,7 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("bind delayed handler", () => bindHandler(true)); createResults(); + waitForDisplay(); AddAssert("top score selected", () => this.ChildrenOfType().OrderByDescending(p => p.Score.TotalScore).First().State == PanelState.Expanded); } @@ -126,6 +130,7 @@ namespace osu.Game.Tests.Visual.Playlists InitialiseUserScoresAndBeatmap(); createResults(); + waitForDisplay(); AddStep("bind delayed handler", () => bindHandler(true)); @@ -152,6 +157,7 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("bind user score info handler", () => bindHandler(userScore: userScore)); createResults(() => userScore); + waitForDisplay(); AddStep("bind delayed handler", () => bindHandler(true)); @@ -174,12 +180,11 @@ namespace osu.Game.Tests.Visual.Playlists public void TestShowWithNoScores() { InitialiseUserScoresAndBeatmap(noScores: true); - - createResults(noScores: true); + createResults(); AddAssert("no scores visible", () => resultsScreen.ScorePanelList.GetScorePanels().Count() == 0); } - private void createResults(Func getScore = null, bool noScores = false) + private void createResults(Func getScore = null) { AddStep("load results", () => { @@ -190,13 +195,10 @@ namespace osu.Game.Tests.Visual.Playlists }); AddUntilStep("wait for screen to load", () => resultsScreen.IsLoaded); - waitForDisplay(noScores); } - private void waitForDisplay(bool noScores = false) + private void waitForDisplay() { - if (noScores) return; - AddUntilStep("wait for scores loaded", () => requestComplete // request handler may need to fire more than once to get scores. diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 8823e4d320..697d62ad6e 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -206,25 +206,17 @@ namespace osu.Game.Screens.Ranking if (lastFetchCompleted) { - if (ScorePanelList.IsEmpty) - { - // This can happen if a beatmap part of a playlist hasn't been played yet. - VerticalScrollContent.Add(new MessagePlaceholder(LeaderboardStrings.NoRecordsYet)); - } - else - { - APIRequest nextPageRequest = null; + APIRequest nextPageRequest = null; - if (ScorePanelList.IsScrolledToStart) - nextPageRequest = FetchNextPage(-1, fetchScoresCallback); - else if (ScorePanelList.IsScrolledToEnd) - nextPageRequest = FetchNextPage(1, fetchScoresCallback); + if (ScorePanelList.IsScrolledToStart) + nextPageRequest = FetchNextPage(-1, fetchScoresCallback); + else if (ScorePanelList.IsScrolledToEnd) + nextPageRequest = FetchNextPage(1, fetchScoresCallback); - if (nextPageRequest != null) - { - lastFetchCompleted = false; - api.Queue(nextPageRequest); - } + if (nextPageRequest != null) + { + lastFetchCompleted = false; + api.Queue(nextPageRequest); } } } @@ -255,6 +247,12 @@ namespace osu.Game.Screens.Ranking addScore(s); lastFetchCompleted = true; + + if (ScorePanelList.IsEmpty) + { + // This can happen if for example a beatmap that is part of a playlist hasn't been played yet. + VerticalScrollContent.Add(new MessagePlaceholder(LeaderboardStrings.NoRecordsYet)); + } }); public override void OnEntering(ScreenTransitionEvent e) From ea37c70e0b0e032e84a9fa1ee7c902c1a7cb2c2e Mon Sep 17 00:00:00 2001 From: wooster0 Date: Tue, 9 Jan 2024 21:34:40 +0900 Subject: [PATCH 2395/4092] apply suggestion --- .../TestScenePlaylistsResultsScreen.cs | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index 98aeb66428..02bd7f950f 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -42,8 +42,8 @@ namespace osu.Game.Tests.Visual.Playlists private int totalCount; private ScoreInfo userScore; - // We don't use SetUpSteps for this because one of the tests shouldn't receive any scores. - public void InitialiseUserScoresAndBeatmap(bool noScores = false) + [SetUpSteps] + public override void SetUpSteps() { base.SetUpSteps(); @@ -63,18 +63,24 @@ namespace osu.Game.Tests.Visual.Playlists userScore.Statistics = new Dictionary(); userScore.MaximumStatistics = new Dictionary(); - bindHandler(noScores: noScores); - // Beatmap is required to be an actual beatmap so the scores can get their scores correctly // calculated for standardised scoring, else the tests that rely on ordering will fall over. Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value); }); } + public void SetUpRequestHandler(bool noScores = false) + { + AddStep("set up request handler", () => + { + bindHandler(noScores: noScores); + }); + } + [Test] public void TestShowWithUserScore() { - InitialiseUserScoresAndBeatmap(); + SetUpRequestHandler(); AddStep("bind user score info handler", () => bindHandler(userScore: userScore)); @@ -89,7 +95,7 @@ namespace osu.Game.Tests.Visual.Playlists [Test] public void TestShowNullUserScore() { - InitialiseUserScoresAndBeatmap(); + SetUpRequestHandler(); createResults(); waitForDisplay(); @@ -100,7 +106,7 @@ namespace osu.Game.Tests.Visual.Playlists [Test] public void TestShowUserScoreWithDelay() { - InitialiseUserScoresAndBeatmap(); + SetUpRequestHandler(); AddStep("bind user score info handler", () => bindHandler(true, userScore)); @@ -114,7 +120,7 @@ namespace osu.Game.Tests.Visual.Playlists [Test] public void TestShowNullUserScoreWithDelay() { - InitialiseUserScoresAndBeatmap(); + SetUpRequestHandler(); AddStep("bind delayed handler", () => bindHandler(true)); @@ -127,7 +133,7 @@ namespace osu.Game.Tests.Visual.Playlists [Test] public void TestFetchWhenScrolledToTheRight() { - InitialiseUserScoresAndBeatmap(); + SetUpRequestHandler(); createResults(); waitForDisplay(); @@ -152,7 +158,7 @@ namespace osu.Game.Tests.Visual.Playlists [Test] public void TestFetchWhenScrolledToTheLeft() { - InitialiseUserScoresAndBeatmap(); + SetUpRequestHandler(); AddStep("bind user score info handler", () => bindHandler(userScore: userScore)); @@ -179,7 +185,7 @@ namespace osu.Game.Tests.Visual.Playlists [Test] public void TestShowWithNoScores() { - InitialiseUserScoresAndBeatmap(noScores: true); + SetUpRequestHandler(true); createResults(); AddAssert("no scores visible", () => resultsScreen.ScorePanelList.GetScorePanels().Count() == 0); } From d5fdd0c0f9bb4a52630e9fead6b27ab44e472ca0 Mon Sep 17 00:00:00 2001 From: wooster0 Date: Tue, 9 Jan 2024 21:44:05 +0900 Subject: [PATCH 2396/4092] make each test bind the handler only once, depending on its need --- .../TestScenePlaylistsResultsScreen.cs | 24 +++---------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index 02bd7f950f..727d9da50b 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -69,19 +69,9 @@ namespace osu.Game.Tests.Visual.Playlists }); } - public void SetUpRequestHandler(bool noScores = false) - { - AddStep("set up request handler", () => - { - bindHandler(noScores: noScores); - }); - } - [Test] public void TestShowWithUserScore() { - SetUpRequestHandler(); - AddStep("bind user score info handler", () => bindHandler(userScore: userScore)); createResults(() => userScore); @@ -95,7 +85,7 @@ namespace osu.Game.Tests.Visual.Playlists [Test] public void TestShowNullUserScore() { - SetUpRequestHandler(); + AddStep("bind user score info handler", () => bindHandler()); createResults(); waitForDisplay(); @@ -106,8 +96,6 @@ namespace osu.Game.Tests.Visual.Playlists [Test] public void TestShowUserScoreWithDelay() { - SetUpRequestHandler(); - AddStep("bind user score info handler", () => bindHandler(true, userScore)); createResults(() => userScore); @@ -120,8 +108,6 @@ namespace osu.Game.Tests.Visual.Playlists [Test] public void TestShowNullUserScoreWithDelay() { - SetUpRequestHandler(); - AddStep("bind delayed handler", () => bindHandler(true)); createResults(); @@ -133,13 +119,11 @@ namespace osu.Game.Tests.Visual.Playlists [Test] public void TestFetchWhenScrolledToTheRight() { - SetUpRequestHandler(); + AddStep("bind delayed handler", () => bindHandler(true)); createResults(); waitForDisplay(); - AddStep("bind delayed handler", () => bindHandler(true)); - for (int i = 0; i < 2; i++) { int beforePanelCount = 0; @@ -158,8 +142,6 @@ namespace osu.Game.Tests.Visual.Playlists [Test] public void TestFetchWhenScrolledToTheLeft() { - SetUpRequestHandler(); - AddStep("bind user score info handler", () => bindHandler(userScore: userScore)); createResults(() => userScore); @@ -185,7 +167,7 @@ namespace osu.Game.Tests.Visual.Playlists [Test] public void TestShowWithNoScores() { - SetUpRequestHandler(true); + AddStep("bind user score info handler", () => bindHandler(noScores: true)); createResults(); AddAssert("no scores visible", () => resultsScreen.ScorePanelList.GetScorePanels().Count() == 0); } From 77bf6e3244111b026f930a75fbb35dd497d9c921 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 9 Jan 2024 13:59:27 +0100 Subject: [PATCH 2397/4092] Fix missing wireframe behind "x" sign on combo counter display --- osu.Game/Screens/Play/HUD/ArgonComboCounter.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs index af884aa441..1d6ca3c893 100644 --- a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs @@ -74,7 +74,8 @@ namespace osu.Game.Screens.Play.HUD private int getDigitsRequiredForDisplayCount() { - int digitsRequired = 1; + // one for the single presumed starting digit, one for the "x" at the end. + int digitsRequired = 2; long c = DisplayedCount; while ((c /= 10) > 0) digitsRequired++; From 92ba77031403632ad03b86efcbd2754a47b6c646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 9 Jan 2024 14:00:58 +0100 Subject: [PATCH 2398/4092] Fix missing wireframe behind percent sign on accuracy counter --- osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs index 5284e3167a..171aa3f44b 100644 --- a/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs @@ -90,6 +90,7 @@ namespace osu.Game.Screens.Play.HUD percentText = new ArgonCounterTextComponent(Anchor.TopLeft) { Text = @"%", + RequiredDisplayDigits = { Value = 1 }, WireframeOpacity = { BindTarget = WireframeOpacity } }, } From 484e9e8ee636d9d1ec4aaf549e7615fb18682226 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Tue, 9 Jan 2024 22:09:20 +0800 Subject: [PATCH 2399/4092] Fix binding order of `IsPaused` bindable and disable playback controls in spectator mode. --- .../Screens/Play/PlayerSettings/PlaybackSettings.cs | 13 +++++++++++-- osu.Game/Screens/Play/SpectatorPlayer.cs | 2 ++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs index e208be6095..e2859868bd 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs @@ -27,6 +27,8 @@ namespace osu.Game.Screens.Play.PlayerSettings Precision = 0.1, }; + public readonly Bindable AllowControls = new BindableBool(true); + private readonly PlayerSliderBar rateSlider; private readonly OsuSpriteText multiplierText; @@ -71,6 +73,7 @@ namespace osu.Game.Screens.Play.PlayerSettings Origin = Anchor.Centre, Icon = FontAwesome.Solid.FastBackward, Action = () => seek(-1, seek_fast_amount), + Enabled = { BindTarget = AllowControls }, }, new SeekButton { @@ -78,6 +81,7 @@ namespace osu.Game.Screens.Play.PlayerSettings Origin = Anchor.Centre, Icon = FontAwesome.Solid.Backward, Action = () => seek(-1, seek_amount), + Enabled = { BindTarget = AllowControls }, }, play = new IconButton { @@ -95,7 +99,8 @@ namespace osu.Game.Screens.Play.PlayerSettings else gameplayClock.Start(); } - } + }, + Enabled = { BindTarget = AllowControls }, }, new SeekButton { @@ -103,6 +108,7 @@ namespace osu.Game.Screens.Play.PlayerSettings Origin = Anchor.Centre, Icon = FontAwesome.Solid.Forward, Action = () => seek(1, seek_amount), + Enabled = { BindTarget = AllowControls }, }, new SeekButton { @@ -110,6 +116,7 @@ namespace osu.Game.Screens.Play.PlayerSettings Origin = Anchor.Centre, Icon = FontAwesome.Solid.FastForward, Action = () => seek(1, seek_fast_amount), + Enabled = { BindTarget = AllowControls }, }, }, }, @@ -150,7 +157,9 @@ namespace osu.Game.Screens.Play.PlayerSettings { base.LoadComplete(); rateSlider.Current.BindValueChanged(multiplier => multiplierText.Text = $"{multiplier.NewValue:0.0}x", true); - gameplayClock?.IsPaused.BindTo(isPaused); + + if (gameplayClock != null) + isPaused.BindTarget = gameplayClock.IsPaused; } private partial class SeekButton : IconButton diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index 2faead0ee1..dd7418d563 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -68,6 +68,8 @@ namespace osu.Game.Screens.Play master.UserPlaybackRate.Value = 1; } }, true); + + HUDOverlay.PlayerSettingsOverlay.PlaybackSettings.AllowControls.Value = false; } /// From 91677158a058e03bae2bace8d236c1ef12a5e66e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Jan 2024 22:33:00 +0900 Subject: [PATCH 2400/4092] 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 b179b8b837..56931bbcb4 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 7e03ab50e2..c180baeab7 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From 49d13cda6b80fcd439a7d40723e2635c7792eee2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Jan 2024 23:09:43 +0900 Subject: [PATCH 2401/4092] Fix failing test by setting health on source of truth --- .../Visual/Gameplay/TestSceneFailingLayer.cs | 18 ++++++++++-------- osu.Game/Screens/Play/HUD/FailingLayer.cs | 4 ++-- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs index 235ada2d63..684d263a58 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs @@ -22,6 +22,8 @@ namespace osu.Game.Tests.Visual.Gameplay private readonly Bindable showHealth = new Bindable(); + private HealthProcessor healthProcessor; + [Resolved] private OsuConfigManager config { get; set; } @@ -29,7 +31,7 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("create layer", () => { - Child = new HealthProcessorContainer(healthProcessor) + Child = new HealthProcessorContainer(this.healthProcessor = healthProcessor) { RelativeSizeAxes = Axes.Both, Child = layer = new FailingLayer() @@ -50,12 +52,12 @@ namespace osu.Game.Tests.Visual.Gameplay AddSliderStep("current health", 0.0, 1.0, 1.0, val => { if (layer != null) - layer.Current.Value = val; + healthProcessor.Health.Value = val; }); - AddStep("set health to 0.10", () => layer.Current.Value = 0.1); + AddStep("set health to 0.10", () => healthProcessor.Health.Value = 0.1); AddUntilStep("layer fade is visible", () => layer.ChildrenOfType().First().Alpha > 0.1f); - AddStep("set health to 1", () => layer.Current.Value = 1f); + AddStep("set health to 1", () => healthProcessor.Health.Value = 1f); AddUntilStep("layer fade is invisible", () => !layer.ChildrenOfType().First().IsPresent); } @@ -65,7 +67,7 @@ namespace osu.Game.Tests.Visual.Gameplay create(new DrainingHealthProcessor(0)); AddUntilStep("layer is visible", () => layer.IsPresent); AddStep("disable layer", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, false)); - AddStep("set health to 0.10", () => layer.Current.Value = 0.1); + AddStep("set health to 0.10", () => healthProcessor.Health.Value = 0.1); AddUntilStep("layer is not visible", () => !layer.IsPresent); } @@ -74,7 +76,7 @@ namespace osu.Game.Tests.Visual.Gameplay { create(new AccumulatingHealthProcessor(1)); AddUntilStep("layer is not visible", () => !layer.IsPresent); - AddStep("set health to 0.10", () => layer.Current.Value = 0.1); + AddStep("set health to 0.10", () => healthProcessor.Health.Value = 0.1); AddUntilStep("layer is not visible", () => !layer.IsPresent); } @@ -82,7 +84,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestLayerVisibilityWithDrainingProcessor() { create(new DrainingHealthProcessor(0)); - AddStep("set health to 0.10", () => layer.Current.Value = 0.1); + AddStep("set health to 0.10", () => healthProcessor.Health.Value = 0.1); AddWaitStep("wait for potential fade", 10); AddAssert("layer is still visible", () => layer.IsPresent); } @@ -92,7 +94,7 @@ namespace osu.Game.Tests.Visual.Gameplay { create(new DrainingHealthProcessor(0)); - AddStep("set health to 0.10", () => layer.Current.Value = 0.1); + AddStep("set health to 0.10", () => healthProcessor.Health.Value = 0.1); AddStep("don't show health", () => showHealth.Value = false); AddStep("disable FadePlayfieldWhenHealthLow", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, false)); diff --git a/osu.Game/Screens/Play/HUD/FailingLayer.cs b/osu.Game/Screens/Play/HUD/FailingLayer.cs index 3954e23cbe..2bac7660b3 100644 --- a/osu.Game/Screens/Play/HUD/FailingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FailingLayer.cs @@ -100,11 +100,11 @@ namespace osu.Game.Screens.Play.HUD protected override void Update() { + base.Update(); + double target = Math.Clamp(max_alpha * (1 - Current.Value / low_health_threshold), 0, max_alpha); boxes.Alpha = (float)Interpolation.Lerp(boxes.Alpha, target, Clock.ElapsedFrameTime * 0.01f); - - base.Update(); } } } From 84f704a6c2747360d89b5c241fa09ecb5b3a60e8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 10 Jan 2024 17:15:27 +0300 Subject: [PATCH 2402/4092] Add failing test case --- .../Resources/mania-skin-broken-array.ini | 3 +++ .../Skins/LegacyManiaSkinDecoderTest.cs | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 osu.Game.Tests/Resources/mania-skin-broken-array.ini diff --git a/osu.Game.Tests/Resources/mania-skin-broken-array.ini b/osu.Game.Tests/Resources/mania-skin-broken-array.ini new file mode 100644 index 0000000000..5a6d37eef6 --- /dev/null +++ b/osu.Game.Tests/Resources/mania-skin-broken-array.ini @@ -0,0 +1,3 @@ +[Mania] +Keys: 4 +ColumnLineWidth: 3,,3,3,3 \ No newline at end of file diff --git a/osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs index b96bf09255..c4117014db 100644 --- a/osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs +++ b/osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs @@ -114,5 +114,24 @@ namespace osu.Game.Tests.Skins Assert.That(configs[0].MinimumColumnWidth, Is.EqualTo(16)); } } + + [Test] + public void TestParseArrayWithSomeEmptyElements() + { + var decoder = new LegacyManiaSkinDecoder(); + + using (var resStream = TestResources.OpenResource("mania-skin-broken-array.ini")) + using (var stream = new LineBufferedReader(resStream)) + { + var configs = decoder.Decode(stream); + + Assert.That(configs.Count, Is.EqualTo(1)); + Assert.That(configs[0].ColumnLineWidth[0], Is.EqualTo(3)); + Assert.That(configs[0].ColumnLineWidth[1], Is.EqualTo(0)); // malformed entry, should be parsed as zero + Assert.That(configs[0].ColumnLineWidth[2], Is.EqualTo(3)); + Assert.That(configs[0].ColumnLineWidth[3], Is.EqualTo(3)); + Assert.That(configs[0].ColumnLineWidth[4], Is.EqualTo(3)); + } + } } } From 698ae66a494a899bfeb4159d72db665a7f514a8a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 10 Jan 2024 17:41:58 +0300 Subject: [PATCH 2403/4092] Fix mania skin array decoder not handling malformed entries rigorously --- osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index b472afb74f..720b05a0b1 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -155,7 +155,15 @@ namespace osu.Game.Skinning if (i >= output.Length) break; - output[i] = float.Parse(values[i], CultureInfo.InvariantCulture) * (applyScaleFactor ? LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR : 1); + if (!float.TryParse(values[i], NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out float parsedValue)) + // some skins may provide incorrect entries in array values. to match stable behaviour, read such entries as zero. + // see: https://github.com/ppy/osu/issues/26464, stable code: https://github.com/peppy/osu-stable-reference/blob/3ea48705eb67172c430371dcfc8a16a002ed0d3d/osu!/Graphics/Skinning/Components/Section.cs#L134-L137 + parsedValue = 0; + + if (applyScaleFactor) + parsedValue *= LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR; + + output[i] = parsedValue; } } } From 7ca4d854416ae663825bfc7d84c5df0288a7ce41 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 10 Jan 2024 17:48:47 +0300 Subject: [PATCH 2404/4092] Remove unnecessary `AllowThousands` flag The flag is there to match `float.Parse` behaviour, but it's too illogical and unnecessary to have it. --- osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index 720b05a0b1..ff6e7fc38e 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -155,7 +155,7 @@ namespace osu.Game.Skinning if (i >= output.Length) break; - if (!float.TryParse(values[i], NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out float parsedValue)) + if (!float.TryParse(values[i], NumberStyles.Float, CultureInfo.InvariantCulture, out float parsedValue)) // some skins may provide incorrect entries in array values. to match stable behaviour, read such entries as zero. // see: https://github.com/ppy/osu/issues/26464, stable code: https://github.com/peppy/osu-stable-reference/blob/3ea48705eb67172c430371dcfc8a16a002ed0d3d/osu!/Graphics/Skinning/Components/Section.cs#L134-L137 parsedValue = 0; From 7b848e1458c484cad520ce8ceee4d4fab64fa726 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 10 Jan 2024 17:51:27 +0300 Subject: [PATCH 2405/4092] Assert column line width length for extra safety --- osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs index c4117014db..d577e0fedf 100644 --- a/osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs +++ b/osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs @@ -126,6 +126,7 @@ namespace osu.Game.Tests.Skins var configs = decoder.Decode(stream); Assert.That(configs.Count, Is.EqualTo(1)); + Assert.That(configs[0].ColumnLineWidth.Length, Is.EqualTo(5)); Assert.That(configs[0].ColumnLineWidth[0], Is.EqualTo(3)); Assert.That(configs[0].ColumnLineWidth[1], Is.EqualTo(0)); // malformed entry, should be parsed as zero Assert.That(configs[0].ColumnLineWidth[2], Is.EqualTo(3)); From c2706ca91be7293a47142c0ae184774a17b263f6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Jan 2024 23:48:16 +0900 Subject: [PATCH 2406/4092] Also show drain on argon health display test --- .../Visual/Gameplay/TestSceneArgonHealthDisplay.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs index 30fb4412f4..06d199513c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs @@ -159,5 +159,11 @@ namespace osu.Game.Tests.Visual.Gameplay Type = HitResult.Perfect }); } + + protected override void Update() + { + base.Update(); + healthProcessor.Health.Value -= 0.0001f * Time.Elapsed; + } } } From 5d6f767dbdc613ca54bbfdb98880c33517769b1c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Jan 2024 23:52:39 +0900 Subject: [PATCH 2407/4092] Reduce excessive `Color4` allocations during path colour updates --- osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index afc64b0b84..236bd3366d 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -328,14 +328,17 @@ namespace osu.Game.Screens.Play.HUD private partial class BackgroundPath : SmoothPath { + private static readonly Color4 colour_white = Color4.White.Opacity(0.8f); + private static readonly Color4 colour_black = Color4.Black.Opacity(0.2f); + protected override Color4 ColourAt(float position) { if (position <= 0.16f) - return Color4.White.Opacity(0.8f); + return colour_white; return Interpolation.ValueAt(position, - Color4.White.Opacity(0.8f), - Color4.Black.Opacity(0.2f), + colour_white, + colour_black, -0.5f, 1f, Easing.OutQuint); } } @@ -374,12 +377,14 @@ namespace osu.Game.Screens.Play.HUD public float GlowPortion { get; init; } + private static readonly Colour4 transparent_black = Colour4.Black.Opacity(0.0f); + protected override Color4 ColourAt(float position) { if (position >= GlowPortion) return BarColour; - return Interpolation.ValueAt(position, Colour4.Black.Opacity(0.0f), GlowColour, 0.0, GlowPortion, Easing.InQuint); + return Interpolation.ValueAt(position, transparent_black, GlowColour, 0.0, GlowPortion, Easing.InQuint); } } } From 7c9adc7ad3c1d4c72620d92cb44bd665c6e8a28c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 10 Jan 2024 16:51:40 +0100 Subject: [PATCH 2408/4092] Fix incorrect score conversion on selected beatmaps due to incorrect `difficultyPeppyStars` rounding Fixes issue that occurs on *about* 246 beatmaps and was first described by me on discord: https://discord.com/channels/188630481301012481/188630652340404224/1154367700378865715 and then rediscovered again during work on https://github.com/ppy/osu/pull/26405: https://gist.github.com/bdach/414d5289f65b0399fa8f9732245a4f7c#venenog-on-ultmate-end-by-blacky-overdose-631 It so happens that in stable, due to .NET Framework internals, float math would be performed using x87 registers and opcodes. .NET (Core) however uses SSE instructions on 32- and 64-bit words. x87 registers are _80 bits_ wide. Which is notably wider than _both_ float and double. Therefore, on a significant number of beatmaps, the rounding would not produce correct values due to insufficient precision. See following gist for corroboration of the above: https://gist.github.com/bdach/dcde58d5a3607b0408faa3aa2b67bf10 Thus, to crudely - but, seemingly accurately, after checking across all ranked maps - emulate this, use `decimal`, which is slow, but has bigger precision than `double`. The single known exception beatmap in whose case this results in an incorrect result is https://osu.ppy.sh/beatmapsets/1156087#osu/2625853 which is considered an "acceptable casualty" of sorts. Doing this requires some fooling of the compiler / runtime (see second inline comment in new method). To corroborate that this is required, you can try the following code snippet: Console.WriteLine(string.Join(' ', BitConverter.GetBytes(1.3f).Select(x => x.ToString("X2")))); Console.WriteLine(string.Join(' ', BitConverter.GetBytes(1.3).Select(x => x.ToString("X2")))); Console.WriteLine(); decimal d1 = (decimal)1.3f; decimal d2 = (decimal)1.3; decimal d3 = (decimal)(double)1.3f; Console.WriteLine(string.Join(' ', decimal.GetBits(d1).SelectMany(BitConverter.GetBytes).Select(x => x.ToString("X2")))); Console.WriteLine(string.Join(' ', decimal.GetBits(d2).SelectMany(BitConverter.GetBytes).Select(x => x.ToString("X2")))); Console.WriteLine(string.Join(' ', decimal.GetBits(d3).SelectMany(BitConverter.GetBytes).Select(x => x.ToString("X2")))); which will print 66 66 A6 3F CD CC CC CC CC CC F4 3F 0D 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 0D 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 8C 5D 89 FB 3B 76 00 00 00 00 00 00 00 00 0E 00 Note that despite `d1` being converted from a less-precise floating- -point value than `d2`, it still is represented 100% accurately as a decimal number. After applying this change, recomputation of legacy scoring attributes for *all* rulesets will be required. --- .../Difficulty/CatchLegacyScoreSimulator.cs | 9 ++--- .../Difficulty/OsuLegacyScoreSimulator.cs | 9 ++--- .../Difficulty/TaikoLegacyScoreSimulator.cs | 7 ++-- .../Objects/Legacy/LegacyRulesetExtensions.cs | 35 +++++++++++++++++++ osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 7 +++- 5 files changed, 47 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyScoreSimulator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyScoreSimulator.cs index f65b6ef381..f931795ff2 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyScoreSimulator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyScoreSimulator.cs @@ -10,6 +10,7 @@ using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Scoring; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring.Legacy; @@ -62,13 +63,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty drainLength = ((int)Math.Round(baseBeatmap.HitObjects[^1].StartTime) - (int)Math.Round(baseBeatmap.HitObjects[0].StartTime) - breakLength) / 1000; } - int difficultyPeppyStars = (int)Math.Round( - (baseBeatmap.Difficulty.DrainRate - + baseBeatmap.Difficulty.OverallDifficulty - + baseBeatmap.Difficulty.CircleSize - + Math.Clamp((float)objectCount / drainLength * 8, 0, 16)) / 38 * 5); - - scoreMultiplier = difficultyPeppyStars; + scoreMultiplier = LegacyRulesetExtensions.CalculateDifficultyPeppyStars(baseBeatmap.Difficulty, objectCount, drainLength); LegacyScoreAttributes attributes = new LegacyScoreAttributes(); diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuLegacyScoreSimulator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuLegacyScoreSimulator.cs index a76054b42c..b808deab5c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuLegacyScoreSimulator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuLegacyScoreSimulator.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; @@ -62,13 +63,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty drainLength = ((int)Math.Round(baseBeatmap.HitObjects[^1].StartTime) - (int)Math.Round(baseBeatmap.HitObjects[0].StartTime) - breakLength) / 1000; } - int difficultyPeppyStars = (int)Math.Round( - (baseBeatmap.Difficulty.DrainRate - + baseBeatmap.Difficulty.OverallDifficulty - + baseBeatmap.Difficulty.CircleSize - + Math.Clamp((float)objectCount / drainLength * 8, 0, 16)) / 38 * 5); - - scoreMultiplier = difficultyPeppyStars; + scoreMultiplier = LegacyRulesetExtensions.CalculateDifficultyPeppyStars(baseBeatmap.Difficulty, objectCount, drainLength); LegacyScoreAttributes attributes = new LegacyScoreAttributes(); diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs index b20aa4f2b6..66ff0fc3d9 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring.Legacy; @@ -65,11 +66,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty drainLength = ((int)Math.Round(baseBeatmap.HitObjects[^1].StartTime) - (int)Math.Round(baseBeatmap.HitObjects[0].StartTime) - breakLength) / 1000; } - difficultyPeppyStars = (int)Math.Round( - (baseBeatmap.Difficulty.DrainRate - + baseBeatmap.Difficulty.OverallDifficulty - + baseBeatmap.Difficulty.CircleSize - + Math.Clamp((float)objectCount / drainLength * 8, 0, 16)) / 38 * 5); + difficultyPeppyStars = LegacyRulesetExtensions.CalculateDifficultyPeppyStars(baseBeatmap.Difficulty, objectCount, drainLength); LegacyScoreAttributes attributes = new LegacyScoreAttributes(); diff --git a/osu.Game/Rulesets/Objects/Legacy/LegacyRulesetExtensions.cs b/osu.Game/Rulesets/Objects/Legacy/LegacyRulesetExtensions.cs index 53cf835248..2a5a11161b 100644 --- a/osu.Game/Rulesets/Objects/Legacy/LegacyRulesetExtensions.cs +++ b/osu.Game/Rulesets/Objects/Legacy/LegacyRulesetExtensions.cs @@ -57,5 +57,40 @@ namespace osu.Game.Rulesets.Objects.Legacy return (float)(1.0f - 0.7f * IBeatmapDifficultyInfo.DifficultyRange(circleSize)) / 2 * (applyFudge ? broken_gamefield_rounding_allowance : 1); } + + public static int CalculateDifficultyPeppyStars(BeatmapDifficulty difficulty, int objectCount, int drainLength) + { + /* + * WARNING: DO NOT TOUCH IF YOU DO NOT KNOW WHAT YOU ARE DOING + * + * It so happens that in stable, due to .NET Framework internals, float math would be performed + * using x87 registers and opcodes. + * .NET (Core) however uses SSE instructions on 32- and 64-bit words. + * x87 registers are _80 bits_ wide. Which is notably wider than _both_ float and double. + * Therefore, on a significant number of beatmaps, the rounding would not produce correct values. + * + * Thus, to crudely - but, seemingly *mostly* accurately, after checking across all ranked maps - emulate this, + * use `decimal`, which is slow, but has bigger precision than `double`. + * At the time of writing, there is _one_ ranked exception to this - namely https://osu.ppy.sh/beatmapsets/1156087#osu/2625853 - + * but it is considered an "acceptable casualty", since in that case scores aren't inflated by _that_ much compared to others. + */ + + decimal objectToDrainRatio = drainLength != 0 + ? Math.Clamp((decimal)objectCount / drainLength * 8, 0, 16) + : 16; + + /* + * Notably, THE `double` CASTS BELOW ARE IMPORTANT AND MUST REMAIN. + * Their goal is to trick the compiler / runtime into NOT promoting from single-precision float, as doing so would prompt it + * to attempt to "silently" fix the single-precision values when converting to decimal, + * which is NOT what the x87 FPU does. + */ + + decimal drainRate = (decimal)(double)difficulty.DrainRate; + decimal overallDifficulty = (decimal)(double)difficulty.OverallDifficulty; + decimal circleSize = (decimal)(double)difficulty.CircleSize; + + return (int)Math.Round((drainRate + overallDifficulty + circleSize + objectToDrainRatio) / 38 * 5); + } } } diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 110cf63e5c..389b20b5c8 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -13,6 +13,7 @@ using osu.Game.Extensions; using osu.Game.IO.Legacy; using osu.Game.IO.Serialization; using osu.Game.Replays.Legacy; +using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; using SharpCompress.Compressors.LZMA; @@ -38,9 +39,13 @@ namespace osu.Game.Scoring.Legacy /// 30000009: Fix edge cases in conversion for scores which have 0.0x mod multiplier on stable. Reconvert all scores. /// 30000010: Fix mania score V1 conversion using score V1 accuracy rather than V2 accuracy. Reconvert all scores. /// 30000011: Re-do catch scoring to mirror stable Score V2 as closely as feasible. Reconvert all scores. + /// + /// 30000012: Fix incorrect total score conversion on selected beatmaps after implementing the more correct + /// method. Reconvert all scores. + /// /// /// - public const int LATEST_VERSION = 30000011; + public const int LATEST_VERSION = 30000012; /// /// The first stable-compatible YYYYMMDD format version given to lazer usage of replays. From 861080d3ae42de1b57e4471abc2f4f43a3026fc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 11 Jan 2024 10:04:37 +0100 Subject: [PATCH 2409/4092] Move simulated drain to separate test case Having it on at all times was causing other tests to fail. --- .../Visual/Gameplay/TestSceneArgonHealthDisplay.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs index 06d199513c..5d2921107e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs @@ -7,6 +7,7 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; +using osu.Framework.Threading; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Judgements; @@ -160,10 +161,14 @@ namespace osu.Game.Tests.Visual.Gameplay }); } - protected override void Update() + [Test] + public void TestSimulateDrain() { - base.Update(); - healthProcessor.Health.Value -= 0.0001f * Time.Elapsed; + ScheduledDelegate del = null!; + + AddStep("simulate drain", () => del = Scheduler.AddDelayed(() => healthProcessor.Health.Value -= 0.00025f * Time.Elapsed, 0, true)); + AddUntilStep("wait until zero", () => healthProcessor.Health.Value == 0); + AddStep("cancel drain", () => del.Cancel()); } } } From 600e4b6ef3c930ad8dc82f513c870582747e5bf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 11 Jan 2024 10:17:32 +0100 Subject: [PATCH 2410/4092] Adjust skinnable health display test scene for usability --- .../Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs index f215a5d528..1849e8abd0 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached(typeof(HealthProcessor))] private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); - protected override Drawable CreateArgonImplementation() => new ArgonHealthDisplay { Scale = new Vector2(0.6f), Width = 1f }; + protected override Drawable CreateArgonImplementation() => new ArgonHealthDisplay { Scale = new Vector2(0.6f), Width = 600, UseRelativeSize = { Value = false } }; protected override Drawable CreateDefaultImplementation() => new DefaultHealthDisplay { Scale = new Vector2(0.6f) }; protected override Drawable CreateLegacyImplementation() => new LegacyHealthDisplay { Scale = new Vector2(0.6f) }; From 90ab306a96e17b3d7e21011fb75acd9d753ba071 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 11 Jan 2024 14:55:04 +0300 Subject: [PATCH 2411/4092] Implement ArgonHealthDisplayBackground --- .../Screens/Play/HUD/ArgonHealthDisplay.cs | 11 +- .../ArgonHealthDisplayBackground.cs | 168 ++++++++++++++++++ 2 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBackground.cs diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 236bd3366d..868ef9283d 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -19,6 +19,7 @@ using osu.Game.Configuration; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -100,6 +101,14 @@ namespace osu.Game.Screens.Play.HUD background = new BackgroundPath { PathRadius = MAIN_PATH_RADIUS, + Alpha = 0, + AlwaysPresent = true + }, + new ArgonHealthDisplayBackground + { + RelativeSizeAxes = Axes.Both, + PathRadius = MAIN_PATH_RADIUS, + PathPadding = MAIN_PATH_RADIUS }, glowBar = new BarPath { @@ -121,7 +130,7 @@ namespace osu.Game.Screens.Play.HUD GlowColour = main_bar_glow_colour, PathRadius = MAIN_PATH_RADIUS, GlowPortion = 0.6f, - }, + } } }; } diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBackground.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBackground.cs new file mode 100644 index 0000000000..a819fb6f30 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBackground.cs @@ -0,0 +1,168 @@ +// 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.Runtime.InteropServices; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Rendering; +using osu.Framework.Graphics.Shaders; +using osu.Framework.Graphics.Shaders.Types; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts +{ + public partial class ArgonHealthDisplayBackground : Box + { + private float endProgress = 1f; + + public float EndProgress + { + get => endProgress; + set + { + if (endProgress == value) + return; + + endProgress = value; + Invalidate(Invalidation.DrawNode); + } + } + + private float startProgress = 0f; + + public float StartProgress + { + get => startProgress; + set + { + if (startProgress == value) + return; + + startProgress = value; + Invalidate(Invalidation.DrawNode); + } + } + + private float radius = 10f; + + public float PathRadius + { + get => radius; + set + { + if (radius == value) + return; + + radius = value; + Invalidate(Invalidation.DrawNode); + } + } + + private float padding = 10f; + + public float PathPadding + { + get => padding; + set + { + if (padding == value) + return; + + padding = value; + Invalidate(Invalidation.DrawNode); + } + } + + [BackgroundDependencyLoader] + private void load(ShaderManager shaders) + { + TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, "ArgonBarPathBackground"); + } + + protected override void Update() + { + base.Update(); + Invalidate(Invalidation.DrawNode); + } + + protected override DrawNode CreateDrawNode() => new ArgonBarPathDrawNode(this); + + private class ArgonBarPathDrawNode : SpriteDrawNode + { + protected new ArgonHealthDisplayBackground Source => (ArgonHealthDisplayBackground)base.Source; + + public ArgonBarPathDrawNode(ArgonHealthDisplayBackground source) + : base(source) + { + } + + private Vector2 size; + private float startProgress; + private float endProgress; + private float pathRadius; + private float padding; + + public override void ApplyState() + { + base.ApplyState(); + + size = Source.DrawSize; + endProgress = Source.endProgress; + startProgress = Math.Min(Source.startProgress, endProgress); + pathRadius = Source.PathRadius; + padding = Source.PathPadding; + } + + protected override void Draw(IRenderer renderer) + { + if (pathRadius == 0) + return; + + base.Draw(renderer); + } + + private IUniformBuffer parametersBuffer; + + protected override void BindUniformResources(IShader shader, IRenderer renderer) + { + base.BindUniformResources(shader, renderer); + + parametersBuffer ??= renderer.CreateUniformBuffer(); + parametersBuffer.Data = new ArgonBarPathBackgroundParameters + { + Size = size, + StartProgress = startProgress, + EndProgress = endProgress, + PathRadius = pathRadius, + Padding = padding + }; + + shader.BindUniformBlock("m_ArgonBarPathBackgroundParameters", parametersBuffer); + } + + protected override bool CanDrawOpaqueInterior => false; + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + parametersBuffer?.Dispose(); + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + private record struct ArgonBarPathBackgroundParameters + { + public UniformVector2 Size; + public UniformFloat StartProgress; + public UniformFloat EndProgress; + public UniformFloat PathRadius; + public UniformFloat Padding; + private UniformPadding8 pad; + } + } + } +} From f1db7db2592403d5783a63335362cf07a243bda7 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 11 Jan 2024 16:10:06 +0300 Subject: [PATCH 2412/4092] Implement ArgonHealthDisplayBar --- .../Screens/Play/HUD/ArgonHealthDisplay.cs | 211 +++------------- .../ArgonHealthDisplayBar.cs | 227 ++++++++++++++++++ 2 files changed, 265 insertions(+), 173 deletions(-) create mode 100644 osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBar.cs diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 868ef9283d..9b91252a06 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -2,23 +2,18 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Caching; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Lines; using osu.Framework.Layout; using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts; using osu.Game.Skinning; using osuTK; @@ -41,16 +36,14 @@ namespace osu.Game.Screens.Play.HUD [SettingSource("Use relative size")] public BindableBool UseRelativeSize { get; } = new BindableBool(true); - private BarPath mainBar = null!; + private ArgonHealthDisplayBar mainBar = null!; /// /// Used to show a glow at the end of the main bar, or red "damage" area when missing. /// - private BarPath glowBar = null!; + private ArgonHealthDisplayBar glowBar = null!; - private BackgroundPath background = null!; - - private SliderPath barPath = null!; + private Container content = null!; private static readonly Colour4 main_bar_colour = Colour4.White; private static readonly Colour4 main_bar_glow_colour = Color4Extensions.FromHex("#7ED7FD").Opacity(0.5f); @@ -59,23 +52,15 @@ namespace osu.Game.Screens.Play.HUD private bool displayingMiss => resetMissBarDelegate != null; - private readonly List vertices = new List(); - private double glowBarValue; private double healthBarValue; public const float MAIN_PATH_RADIUS = 10f; - - private const float curve_start_offset = 70; - private const float curve_end_offset = 40; private const float padding = MAIN_PATH_RADIUS * 2; - private const float curve_smoothness = 10; private readonly LayoutValue drawSizeLayout = new LayoutValue(Invalidation.DrawSize); - private readonly Cached pathVerticesCache = new Cached(); - public ArgonHealthDisplay() { AddLayout(drawSizeLayout); @@ -93,42 +78,40 @@ namespace osu.Game.Screens.Play.HUD { AutoSizeAxes = Axes.Y; - InternalChild = new Container + InternalChild = content = new Container { - AutoSizeAxes = Axes.Both, Children = new Drawable[] { - background = new BackgroundPath - { - PathRadius = MAIN_PATH_RADIUS, - Alpha = 0, - AlwaysPresent = true - }, new ArgonHealthDisplayBackground { RelativeSizeAxes = Axes.Both, PathRadius = MAIN_PATH_RADIUS, PathPadding = MAIN_PATH_RADIUS }, - glowBar = new BarPath + new Container { - BarColour = Color4.White, - GlowColour = main_bar_glow_colour, - Blending = BlendingParameters.Additive, - Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0.8f), Color4.White), - PathRadius = 40f, - // Kinda hacky, but results in correct positioning with increased path radius. - Margin = new MarginPadding(-30f), - GlowPortion = 0.9f, + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(-30f), + Child = glowBar = new ArgonHealthDisplayBar + { + RelativeSizeAxes = Axes.Both, + BarColour = Color4.White, + GlowColour = main_bar_glow_colour, + Blending = BlendingParameters.Additive, + Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0.8f), Color4.White), + PathRadius = 40f, + PathPadding = 40f, + GlowPortion = 0.9f, + } }, - mainBar = new BarPath + mainBar = new ArgonHealthDisplayBar { - AutoSizeAxes = Axes.None, RelativeSizeAxes = Axes.Both, Blending = BlendingParameters.Additive, BarColour = main_bar_colour, GlowColour = main_bar_glow_colour, PathRadius = MAIN_PATH_RADIUS, + PathPadding = MAIN_PATH_RADIUS, GlowPortion = 0.6f, } } @@ -151,7 +134,7 @@ namespace osu.Game.Screens.Play.HUD UseRelativeSize.BindValueChanged(v => RelativeSizeAxes = v.NewValue ? Axes.X : Axes.None, true); Width = previousWidth; - BarHeight.BindValueChanged(_ => updatePath(), true); + BarHeight.BindValueChanged(_ => updateContentSize(), true); } private void onNewJudgement(JudgementResult result) => pendingMissAnimation |= !result.IsHit; @@ -162,7 +145,7 @@ namespace osu.Game.Screens.Play.HUD if (!drawSizeLayout.IsValid) { - updatePath(); + updateContentSize(); drawSizeLayout.Validate(); } @@ -173,7 +156,7 @@ namespace osu.Game.Screens.Play.HUD mainBar.Alpha = (float)Interpolation.DampContinuously(mainBar.Alpha, Current.Value > 0 ? 1 : 0, 40, Time.Elapsed); glowBar.Alpha = (float)Interpolation.DampContinuously(glowBar.Alpha, glowBarValue > 0 ? 1 : 0, 40, Time.Elapsed); - updatePathVertices(); + updatePathProgress(); } protected override void HealthChanged(bool increase) @@ -203,10 +186,9 @@ namespace osu.Game.Screens.Play.HUD if (!displayingMiss) { - // TODO: REMOVE THIS. It's recreating textures. - glowBar.TransformTo(nameof(BarPath.GlowColour), Colour4.White, 30, Easing.OutQuint) + glowBar.TransformTo(nameof(ArgonHealthDisplayBar.GlowColour), Colour4.White, 30, Easing.OutQuint) .Then() - .TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour, 300, Easing.OutQuint); + .TransformTo(nameof(ArgonHealthDisplayBar.GlowColour), main_bar_glow_colour, 300, Easing.OutQuint); } } @@ -221,13 +203,11 @@ namespace osu.Game.Screens.Play.HUD finishMissDisplay(); }, out resetMissBarDelegate); - // TODO: REMOVE THIS. It's recreating textures. - glowBar.TransformTo(nameof(BarPath.BarColour), new Colour4(255, 147, 147, 255), 100, Easing.OutQuint).Then() - .TransformTo(nameof(BarPath.BarColour), new Colour4(255, 93, 93, 255), 800, Easing.OutQuint); + glowBar.TransformTo(nameof(ArgonHealthDisplayBar.BarColour), new Colour4(255, 147, 147, 255), 100, Easing.OutQuint).Then() + .TransformTo(nameof(ArgonHealthDisplayBar.BarColour), new Colour4(255, 93, 93, 255), 800, Easing.OutQuint); - // TODO: REMOVE THIS. It's recreating textures. - glowBar.TransformTo(nameof(BarPath.GlowColour), new Colour4(253, 0, 0, 255).Lighten(0.2f)) - .TransformTo(nameof(BarPath.GlowColour), new Colour4(253, 0, 0, 255), 800, Easing.OutQuint); + glowBar.TransformTo(nameof(ArgonHealthDisplayBar.GlowColour), new Colour4(253, 0, 0, 255).Lighten(0.2f)) + .TransformTo(nameof(ArgonHealthDisplayBar.GlowColour), new Colour4(253, 0, 0, 255), 800, Easing.OutQuint); } private void finishMissDisplay() @@ -237,53 +217,22 @@ namespace osu.Game.Screens.Play.HUD if (Current.Value > 0) { - // TODO: REMOVE THIS. It's recreating textures. - glowBar.TransformTo(nameof(BarPath.BarColour), main_bar_colour, 300, Easing.In); - glowBar.TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour, 300, Easing.In); + glowBar.TransformTo(nameof(ArgonHealthDisplayBar.BarColour), main_bar_colour, 300, Easing.In); + glowBar.TransformTo(nameof(ArgonHealthDisplayBar.GlowColour), main_bar_glow_colour, 300, Easing.In); } resetMissBarDelegate?.Cancel(); resetMissBarDelegate = null; } - private void updatePath() + private void updateContentSize() { float usableWidth = DrawWidth - padding; if (usableWidth < 0) enforceMinimumWidth(); - // the display starts curving at `curve_start_offset` units from the right and ends curving at `curve_end_offset`. - // to ensure that the curve is symmetric when it starts being narrow enough, add a `curve_end_offset` to the left side too. - const float rescale_cutoff = curve_start_offset + curve_end_offset; - - float barLength = Math.Max(DrawWidth - padding, rescale_cutoff); - float curveStart = barLength - curve_start_offset; - float curveEnd = barLength - curve_end_offset; - - Vector2 diagonalDir = (new Vector2(curveEnd, BarHeight.Value) - new Vector2(curveStart, 0)).Normalized(); - - barPath = new SliderPath(new[] - { - new PathControlPoint(new Vector2(0, 0), PathType.LINEAR), - new PathControlPoint(new Vector2(curveStart - curve_smoothness, 0), PathType.BEZIER), - new PathControlPoint(new Vector2(curveStart, 0)), - new PathControlPoint(new Vector2(curveStart, 0) + diagonalDir * curve_smoothness, PathType.LINEAR), - new PathControlPoint(new Vector2(curveEnd, BarHeight.Value) - diagonalDir * curve_smoothness, PathType.BEZIER), - new PathControlPoint(new Vector2(curveEnd, BarHeight.Value)), - new PathControlPoint(new Vector2(curveEnd + curve_smoothness, BarHeight.Value), PathType.LINEAR), - new PathControlPoint(new Vector2(barLength, BarHeight.Value)), - }); - - if (DrawWidth - padding < rescale_cutoff) - rescalePathProportionally(); - - barPath.GetPathToProgress(vertices, 0.0, 1.0); - - background.Vertices = vertices; - mainBar.Vertices = vertices; - glowBar.Vertices = vertices; - - updatePathVertices(); + content.Size = new Vector2(DrawWidth, BarHeight.Value + padding); + updatePathProgress(); void enforceMinimumWidth() { @@ -296,35 +245,13 @@ namespace osu.Game.Screens.Play.HUD RelativeSizeAxes = relativeAxes; } - - void rescalePathProportionally() - { - foreach (var point in barPath.ControlPoints) - point.Position = new Vector2(point.Position.X / barLength * (DrawWidth - padding), point.Position.Y); - } } - private void updatePathVertices() + private void updatePathProgress() { - barPath.GetPathToProgress(vertices, 0.0, healthBarValue); - if (vertices.Count == 0) vertices.Add(Vector2.Zero); - Vector2 initialVertex = vertices[0]; - for (int i = 0; i < vertices.Count; i++) - vertices[i] -= initialVertex; - - mainBar.Vertices = vertices; - mainBar.Position = initialVertex; - - barPath.GetPathToProgress(vertices, healthBarValue, Math.Max(glowBarValue, healthBarValue)); - if (vertices.Count == 0) vertices.Add(Vector2.Zero); - initialVertex = vertices[0]; - for (int i = 0; i < vertices.Count; i++) - vertices[i] -= initialVertex; - - glowBar.Vertices = vertices; - glowBar.Position = initialVertex; - - pathVerticesCache.Validate(); + mainBar.EndProgress = (float)healthBarValue; + glowBar.StartProgress = (float)healthBarValue; + glowBar.EndProgress = (float)Math.Max(glowBarValue, healthBarValue); } protected override void Dispose(bool isDisposing) @@ -334,67 +261,5 @@ namespace osu.Game.Screens.Play.HUD if (HealthProcessor.IsNotNull()) HealthProcessor.NewJudgement -= onNewJudgement; } - - private partial class BackgroundPath : SmoothPath - { - private static readonly Color4 colour_white = Color4.White.Opacity(0.8f); - private static readonly Color4 colour_black = Color4.Black.Opacity(0.2f); - - protected override Color4 ColourAt(float position) - { - if (position <= 0.16f) - return colour_white; - - return Interpolation.ValueAt(position, - colour_white, - colour_black, - -0.5f, 1f, Easing.OutQuint); - } - } - - private partial class BarPath : SmoothPath - { - private Colour4 barColour; - - public Colour4 BarColour - { - get => barColour; - set - { - if (barColour == value) - return; - - barColour = value; - InvalidateTexture(); - } - } - - private Colour4 glowColour; - - public Colour4 GlowColour - { - get => glowColour; - set - { - if (glowColour == value) - return; - - glowColour = value; - InvalidateTexture(); - } - } - - public float GlowPortion { get; init; } - - private static readonly Colour4 transparent_black = Colour4.Black.Opacity(0.0f); - - protected override Color4 ColourAt(float position) - { - if (position >= GlowPortion) - return BarColour; - - return Interpolation.ValueAt(position, transparent_black, GlowColour, 0.0, GlowPortion, Easing.InQuint); - } - } } } diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBar.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBar.cs new file mode 100644 index 0000000000..6bc2232776 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBar.cs @@ -0,0 +1,227 @@ +// 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.Runtime.InteropServices; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Rendering; +using osu.Framework.Graphics.Shaders; +using osu.Framework.Graphics.Shaders.Types; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts +{ + public partial class ArgonHealthDisplayBar : Box + { + private float endProgress = 1f; + + public float EndProgress + { + get => endProgress; + set + { + if (endProgress == value) + return; + + endProgress = value; + Invalidate(Invalidation.DrawNode); + } + } + + private float startProgress = 0f; + + public float StartProgress + { + get => startProgress; + set + { + if (startProgress == value) + return; + + startProgress = value; + Invalidate(Invalidation.DrawNode); + } + } + + private float radius = 10f; + + public float PathRadius + { + get => radius; + set + { + if (radius == value) + return; + + radius = value; + Invalidate(Invalidation.DrawNode); + } + } + + private float padding = 10f; + + public float PathPadding + { + get => padding; + set + { + if (padding == value) + return; + + padding = value; + Invalidate(Invalidation.DrawNode); + } + } + + private float glowPortion; + + public float GlowPortion + { + get => glowPortion; + set + { + if (glowPortion == value) + return; + + glowPortion = value; + Invalidate(Invalidation.DrawNode); + } + } + + private Colour4 barColour = Color4.White; + + public Colour4 BarColour + { + get => barColour; + set + { + if (barColour == value) + return; + + barColour = value; + Invalidate(Invalidation.DrawNode); + } + } + + private Colour4 glowColour = Color4.White.Opacity(0); + + public Colour4 GlowColour + { + get => glowColour; + set + { + if (glowColour == value) + return; + + glowColour = value; + Invalidate(Invalidation.DrawNode); + } + } + + [BackgroundDependencyLoader] + private void load(ShaderManager shaders) + { + TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, "ArgonBarPath"); + } + + protected override void Update() + { + base.Update(); + Invalidate(Invalidation.DrawNode); + } + + protected override DrawNode CreateDrawNode() => new ArgonBarPathDrawNode(this); + + private class ArgonBarPathDrawNode : SpriteDrawNode + { + protected new ArgonHealthDisplayBar Source => (ArgonHealthDisplayBar)base.Source; + + public ArgonBarPathDrawNode(ArgonHealthDisplayBar source) + : base(source) + { + } + + private Vector2 size; + private float startProgress; + private float endProgress; + private float pathRadius; + private float padding; + private float glowPortion; + private Color4 barColour; + private Color4 glowColour; + + public override void ApplyState() + { + base.ApplyState(); + + size = Source.DrawSize; + endProgress = Source.endProgress; + startProgress = Math.Min(Source.startProgress, endProgress); + pathRadius = Source.PathRadius; + padding = Source.PathPadding; + glowPortion = Source.GlowPortion; + barColour = Source.barColour; + glowColour = Source.glowColour; + } + + protected override void Draw(IRenderer renderer) + { + if (pathRadius == 0) + return; + + base.Draw(renderer); + } + + private IUniformBuffer parametersBuffer; + + protected override void BindUniformResources(IShader shader, IRenderer renderer) + { + base.BindUniformResources(shader, renderer); + + parametersBuffer ??= renderer.CreateUniformBuffer(); + parametersBuffer.Data = new ArgonBarPathParameters + { + BarColour = new Vector4(barColour.R, barColour.G, barColour.B, barColour.A), + GlowColour = new Vector4(glowColour.R, glowColour.G, glowColour.B, glowColour.A), + GlowPortion = glowPortion, + Size = size, + StartProgress = startProgress, + EndProgress = endProgress, + PathRadius = pathRadius, + Padding = padding + }; + + shader.BindUniformBlock("m_ArgonBarPathParameters", parametersBuffer); + } + + protected override bool CanDrawOpaqueInterior => false; + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + parametersBuffer?.Dispose(); + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + private record struct ArgonBarPathParameters + { + public UniformVector4 BarColour; + public UniformVector4 GlowColour; + public UniformVector2 Size; + public UniformFloat StartProgress; + public UniformFloat EndProgress; + public UniformFloat PathRadius; + public UniformFloat Padding; + public UniformFloat GlowPortion; + private UniformPadding4 pad; + } + } + } +} From d75bf55c586f72ec28ffc5ce228f6ec47338274f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 11 Jan 2024 18:28:00 +0300 Subject: [PATCH 2413/4092] CI fixes --- .../ArgonHealthDisplayParts/ArgonHealthDisplayBackground.cs | 4 ++-- .../Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBar.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBackground.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBackground.cs index a819fb6f30..781bd8e6e9 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBackground.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBackground.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts } } - private float startProgress = 0f; + private float startProgress; public float StartProgress { @@ -161,7 +161,7 @@ namespace osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts public UniformFloat EndProgress; public UniformFloat PathRadius; public UniformFloat Padding; - private UniformPadding8 pad; + private readonly UniformPadding8 pad; } } } diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBar.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBar.cs index 6bc2232776..ac73698f4c 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBar.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBar.cs @@ -35,7 +35,7 @@ namespace osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts } } - private float startProgress = 0f; + private float startProgress; public float StartProgress { @@ -220,7 +220,7 @@ namespace osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts public UniformFloat PathRadius; public UniformFloat Padding; public UniformFloat GlowPortion; - private UniformPadding4 pad; + private readonly UniformPadding4 pad; } } } From bbb36da32312064bc2d154ae2637b44c670ef76c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 11 Jan 2024 18:58:12 +0300 Subject: [PATCH 2414/4092] Don't pass start and end progress to the background --- .../ArgonHealthDisplayBackground.cs | 40 ------------------- 1 file changed, 40 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBackground.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBackground.cs index 781bd8e6e9..ccb224eca6 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBackground.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBackground.cs @@ -3,7 +3,6 @@ #nullable disable -using System; using System.Runtime.InteropServices; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -18,36 +17,6 @@ namespace osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts { public partial class ArgonHealthDisplayBackground : Box { - private float endProgress = 1f; - - public float EndProgress - { - get => endProgress; - set - { - if (endProgress == value) - return; - - endProgress = value; - Invalidate(Invalidation.DrawNode); - } - } - - private float startProgress; - - public float StartProgress - { - get => startProgress; - set - { - if (startProgress == value) - return; - - startProgress = value; - Invalidate(Invalidation.DrawNode); - } - } - private float radius = 10f; public float PathRadius @@ -102,8 +71,6 @@ namespace osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts } private Vector2 size; - private float startProgress; - private float endProgress; private float pathRadius; private float padding; @@ -112,8 +79,6 @@ namespace osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts base.ApplyState(); size = Source.DrawSize; - endProgress = Source.endProgress; - startProgress = Math.Min(Source.startProgress, endProgress); pathRadius = Source.PathRadius; padding = Source.PathPadding; } @@ -136,8 +101,6 @@ namespace osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts parametersBuffer.Data = new ArgonBarPathBackgroundParameters { Size = size, - StartProgress = startProgress, - EndProgress = endProgress, PathRadius = pathRadius, Padding = padding }; @@ -157,11 +120,8 @@ namespace osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts private record struct ArgonBarPathBackgroundParameters { public UniformVector2 Size; - public UniformFloat StartProgress; - public UniformFloat EndProgress; public UniformFloat PathRadius; public UniformFloat Padding; - private readonly UniformPadding8 pad; } } } From 101a26a53e3da3e16ec26a5c89891261a2b998c9 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 12 Jan 2024 02:54:04 +0300 Subject: [PATCH 2415/4092] Update start and end progress in one go --- .../Screens/Play/HUD/ArgonHealthDisplay.cs | 5 +-- .../ArgonHealthDisplayBar.cs | 37 +++++-------------- 2 files changed, 11 insertions(+), 31 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 9b91252a06..1f633c9e82 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -249,9 +249,8 @@ namespace osu.Game.Screens.Play.HUD private void updatePathProgress() { - mainBar.EndProgress = (float)healthBarValue; - glowBar.StartProgress = (float)healthBarValue; - glowBar.EndProgress = (float)Math.Max(glowBarValue, healthBarValue); + mainBar.ProgressRange = new Vector2(0f, (float)healthBarValue); + glowBar.ProgressRange = new Vector2((float)healthBarValue, (float)Math.Max(glowBarValue, healthBarValue)); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBar.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBar.cs index ac73698f4c..f92ab529ef 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBar.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBar.cs @@ -20,32 +20,17 @@ namespace osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts { public partial class ArgonHealthDisplayBar : Box { - private float endProgress = 1f; + private Vector2 progressRange = new Vector2(0f, 1f); - public float EndProgress + public Vector2 ProgressRange { - get => endProgress; + get => progressRange; set { - if (endProgress == value) + if (progressRange == value) return; - endProgress = value; - Invalidate(Invalidation.DrawNode); - } - } - - private float startProgress; - - public float StartProgress - { - get => startProgress; - set - { - if (startProgress == value) - return; - - startProgress = value; + progressRange = value; Invalidate(Invalidation.DrawNode); } } @@ -149,8 +134,7 @@ namespace osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts } private Vector2 size; - private float startProgress; - private float endProgress; + private Vector2 progressRange; private float pathRadius; private float padding; private float glowPortion; @@ -162,8 +146,7 @@ namespace osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts base.ApplyState(); size = Source.DrawSize; - endProgress = Source.endProgress; - startProgress = Math.Min(Source.startProgress, endProgress); + progressRange = new Vector2(Math.Min(Source.progressRange.X, Source.progressRange.Y), Source.progressRange.Y); pathRadius = Source.PathRadius; padding = Source.PathPadding; glowPortion = Source.GlowPortion; @@ -192,8 +175,7 @@ namespace osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts GlowColour = new Vector4(glowColour.R, glowColour.G, glowColour.B, glowColour.A), GlowPortion = glowPortion, Size = size, - StartProgress = startProgress, - EndProgress = endProgress, + ProgressRange = progressRange, PathRadius = pathRadius, Padding = padding }; @@ -215,8 +197,7 @@ namespace osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts public UniformVector4 BarColour; public UniformVector4 GlowColour; public UniformVector2 Size; - public UniformFloat StartProgress; - public UniformFloat EndProgress; + public UniformVector2 ProgressRange; public UniformFloat PathRadius; public UniformFloat Padding; public UniformFloat GlowPortion; From e861661037cce1079aee84e00b95efae50804873 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 12 Jan 2024 03:03:38 +0300 Subject: [PATCH 2416/4092] Remove invalidations in update oops --- .../ArgonHealthDisplayParts/ArgonHealthDisplayBackground.cs | 6 ------ .../HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBar.cs | 6 ------ 2 files changed, 12 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBackground.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBackground.cs index ccb224eca6..a98b3dc1f3 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBackground.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBackground.cs @@ -53,12 +53,6 @@ namespace osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, "ArgonBarPathBackground"); } - protected override void Update() - { - base.Update(); - Invalidate(Invalidation.DrawNode); - } - protected override DrawNode CreateDrawNode() => new ArgonBarPathDrawNode(this); private class ArgonBarPathDrawNode : SpriteDrawNode diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBar.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBar.cs index f92ab529ef..4f4af02d66 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBar.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBar.cs @@ -116,12 +116,6 @@ namespace osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, "ArgonBarPath"); } - protected override void Update() - { - base.Update(); - Invalidate(Invalidation.DrawNode); - } - protected override DrawNode CreateDrawNode() => new ArgonBarPathDrawNode(this); private class ArgonBarPathDrawNode : SpriteDrawNode From c40462811363c7432ede91c4475d7d6e7cd6d28b Mon Sep 17 00:00:00 2001 From: Nitrous Date: Fri, 12 Jan 2024 15:12:02 +0800 Subject: [PATCH 2417/4092] move creation of `PlaybackSettings` to `ReplayPlayer` --- osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs | 9 +++++---- osu.Game/Screens/Play/Player.cs | 3 --- .../Screens/Play/PlayerSettings/PlaybackSettings.cs | 6 ------ osu.Game/Screens/Play/ReplayPlayer.cs | 12 ++++++++++++ osu.Game/Screens/Play/SpectatorPlayer.cs | 2 -- 5 files changed, 17 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs index dbb0456cd0..b7f3dc36c3 100644 --- a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs +++ b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs @@ -12,17 +12,19 @@ namespace osu.Game.Screens.Play.HUD { private const int fade_duration = 200; - public readonly PlaybackSettings PlaybackSettings; - public readonly VisualSettings VisualSettings; + protected override Container Content => content; + + private readonly FillFlowContainer content; + public PlayerSettingsOverlay() { Anchor = Anchor.TopRight; Origin = Anchor.TopRight; AutoSizeAxes = Axes.Both; - Child = new FillFlowContainer + InternalChild = content = new FillFlowContainer { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, @@ -31,7 +33,6 @@ namespace osu.Game.Screens.Play.HUD Spacing = new Vector2(0, 20), Children = new PlayerSettingsGroup[] { - PlaybackSettings = new PlaybackSettings { Expanded = { Value = false } }, VisualSettings = new VisualSettings { Expanded = { Value = false } }, new AudioSettings { Expanded = { Value = false } } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index b87306b9a2..29c7849685 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -476,9 +476,6 @@ namespace osu.Game.Screens.Play skipOutroOverlay.Expire(); } - if (GameplayClockContainer is MasterGameplayClockContainer master) - HUDOverlay.PlayerSettingsOverlay.PlaybackSettings.UserPlaybackRate.BindTarget = master.UserPlaybackRate; - return container; } diff --git a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs index e2859868bd..0c9f5bb6ee 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs @@ -27,7 +27,6 @@ namespace osu.Game.Screens.Play.PlayerSettings Precision = 0.1, }; - public readonly Bindable AllowControls = new BindableBool(true); private readonly PlayerSliderBar rateSlider; @@ -73,7 +72,6 @@ namespace osu.Game.Screens.Play.PlayerSettings Origin = Anchor.Centre, Icon = FontAwesome.Solid.FastBackward, Action = () => seek(-1, seek_fast_amount), - Enabled = { BindTarget = AllowControls }, }, new SeekButton { @@ -81,7 +79,6 @@ namespace osu.Game.Screens.Play.PlayerSettings Origin = Anchor.Centre, Icon = FontAwesome.Solid.Backward, Action = () => seek(-1, seek_amount), - Enabled = { BindTarget = AllowControls }, }, play = new IconButton { @@ -100,7 +97,6 @@ namespace osu.Game.Screens.Play.PlayerSettings gameplayClock.Start(); } }, - Enabled = { BindTarget = AllowControls }, }, new SeekButton { @@ -108,7 +104,6 @@ namespace osu.Game.Screens.Play.PlayerSettings Origin = Anchor.Centre, Icon = FontAwesome.Solid.Forward, Action = () => seek(1, seek_amount), - Enabled = { BindTarget = AllowControls }, }, new SeekButton { @@ -116,7 +111,6 @@ namespace osu.Game.Screens.Play.PlayerSettings Origin = Anchor.Centre, Icon = FontAwesome.Solid.FastForward, Action = () => seek(1, seek_fast_amount), - Enabled = { BindTarget = AllowControls }, }, }, }, diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index ca71a89b48..788eb75283 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -15,6 +15,7 @@ using osu.Game.Input.Bindings; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens.Play.HUD; +using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Screens.Ranking; using osu.Game.Users; @@ -49,6 +50,17 @@ namespace osu.Game.Screens.Play this.createScore = createScore; } + protected override void LoadComplete() + { + base.LoadComplete(); + + var playerSettingsOverlay = new PlaybackSettings { Expanded = { Value = false } }; + HUDOverlay.PlayerSettingsOverlay.Add(playerSettingsOverlay); + + if (GameplayClockContainer is MasterGameplayClockContainer master) + playerSettingsOverlay.UserPlaybackRate.BindTarget = master.UserPlaybackRate; + } + protected override void PrepareReplay() { DrawableRuleset?.SetReplayScore(Score); diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index dd7418d563..2faead0ee1 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -68,8 +68,6 @@ namespace osu.Game.Screens.Play master.UserPlaybackRate.Value = 1; } }, true); - - HUDOverlay.PlayerSettingsOverlay.PlaybackSettings.AllowControls.Value = false; } /// From c545a9c242d56a499ddd6be46e69fd0730eed97e Mon Sep 17 00:00:00 2001 From: Nitrous Date: Fri, 12 Jan 2024 15:13:38 +0800 Subject: [PATCH 2418/4092] remove extra new line --- osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs index 0c9f5bb6ee..0fe3a08985 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs @@ -27,7 +27,6 @@ namespace osu.Game.Screens.Play.PlayerSettings Precision = 0.1, }; - private readonly PlayerSliderBar rateSlider; private readonly OsuSpriteText multiplierText; From ccbba8a00b7bcb7404cb31333742eafd21a5e62b Mon Sep 17 00:00:00 2001 From: Nitrous Date: Fri, 12 Jan 2024 17:19:59 +0800 Subject: [PATCH 2419/4092] Avoid NRE due to a beatmap loading with no hit objects. --- osu.Game/Screens/Play/ReplayPlayer.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 788eb75283..f6e4ac489a 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -54,11 +54,14 @@ namespace osu.Game.Screens.Play { base.LoadComplete(); - var playerSettingsOverlay = new PlaybackSettings { Expanded = { Value = false } }; - HUDOverlay.PlayerSettingsOverlay.Add(playerSettingsOverlay); + if (HUDOverlay != null) + { + var playerSettingsOverlay = new PlaybackSettings { Expanded = { Value = false } }; + HUDOverlay.PlayerSettingsOverlay.Add(playerSettingsOverlay); - if (GameplayClockContainer is MasterGameplayClockContainer master) - playerSettingsOverlay.UserPlaybackRate.BindTarget = master.UserPlaybackRate; + if (GameplayClockContainer is MasterGameplayClockContainer master) + playerSettingsOverlay.UserPlaybackRate.BindTarget = master.UserPlaybackRate; + } } protected override void PrepareReplay() From 8d4ba6d4664ead9e095b1b5145b92b2dc93ebb81 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 12 Jan 2024 15:30:16 +0300 Subject: [PATCH 2420/4092] Remove PathPadding property --- .../Screens/Play/HUD/ArgonHealthDisplay.cs | 6 +-- .../ArgonHealthDisplayBackground.cs | 49 +------------------ .../ArgonHealthDisplayBar.cs | 23 +-------- 3 files changed, 5 insertions(+), 73 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 1f633c9e82..0af514a4f9 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -84,9 +84,7 @@ namespace osu.Game.Screens.Play.HUD { new ArgonHealthDisplayBackground { - RelativeSizeAxes = Axes.Both, - PathRadius = MAIN_PATH_RADIUS, - PathPadding = MAIN_PATH_RADIUS + RelativeSizeAxes = Axes.Both }, new Container { @@ -100,7 +98,6 @@ namespace osu.Game.Screens.Play.HUD Blending = BlendingParameters.Additive, Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0.8f), Color4.White), PathRadius = 40f, - PathPadding = 40f, GlowPortion = 0.9f, } }, @@ -111,7 +108,6 @@ namespace osu.Game.Screens.Play.HUD BarColour = main_bar_colour, GlowColour = main_bar_glow_colour, PathRadius = MAIN_PATH_RADIUS, - PathPadding = MAIN_PATH_RADIUS, GlowPortion = 0.6f, } } diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBackground.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBackground.cs index a98b3dc1f3..a96c2f97bd 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBackground.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBackground.cs @@ -17,36 +17,6 @@ namespace osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts { public partial class ArgonHealthDisplayBackground : Box { - private float radius = 10f; - - public float PathRadius - { - get => radius; - set - { - if (radius == value) - return; - - radius = value; - Invalidate(Invalidation.DrawNode); - } - } - - private float padding = 10f; - - public float PathPadding - { - get => padding; - set - { - if (padding == value) - return; - - padding = value; - Invalidate(Invalidation.DrawNode); - } - } - [BackgroundDependencyLoader] private void load(ShaderManager shaders) { @@ -65,24 +35,12 @@ namespace osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts } private Vector2 size; - private float pathRadius; - private float padding; public override void ApplyState() { base.ApplyState(); size = Source.DrawSize; - pathRadius = Source.PathRadius; - padding = Source.PathPadding; - } - - protected override void Draw(IRenderer renderer) - { - if (pathRadius == 0) - return; - - base.Draw(renderer); } private IUniformBuffer parametersBuffer; @@ -94,9 +52,7 @@ namespace osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts parametersBuffer ??= renderer.CreateUniformBuffer(); parametersBuffer.Data = new ArgonBarPathBackgroundParameters { - Size = size, - PathRadius = pathRadius, - Padding = padding + Size = size }; shader.BindUniformBlock("m_ArgonBarPathBackgroundParameters", parametersBuffer); @@ -114,8 +70,7 @@ namespace osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts private record struct ArgonBarPathBackgroundParameters { public UniformVector2 Size; - public UniformFloat PathRadius; - public UniformFloat Padding; + private readonly UniformPadding8 pad; } } } diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBar.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBar.cs index 4f4af02d66..1938b97d5a 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBar.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBar.cs @@ -50,21 +50,6 @@ namespace osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts } } - private float padding = 10f; - - public float PathPadding - { - get => padding; - set - { - if (padding == value) - return; - - padding = value; - Invalidate(Invalidation.DrawNode); - } - } - private float glowPortion; public float GlowPortion @@ -130,7 +115,6 @@ namespace osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts private Vector2 size; private Vector2 progressRange; private float pathRadius; - private float padding; private float glowPortion; private Color4 barColour; private Color4 glowColour; @@ -142,7 +126,6 @@ namespace osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts size = Source.DrawSize; progressRange = new Vector2(Math.Min(Source.progressRange.X, Source.progressRange.Y), Source.progressRange.Y); pathRadius = Source.PathRadius; - padding = Source.PathPadding; glowPortion = Source.GlowPortion; barColour = Source.barColour; glowColour = Source.glowColour; @@ -170,8 +153,7 @@ namespace osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts GlowPortion = glowPortion, Size = size, ProgressRange = progressRange, - PathRadius = pathRadius, - Padding = padding + PathRadius = pathRadius }; shader.BindUniformBlock("m_ArgonBarPathParameters", parametersBuffer); @@ -193,9 +175,8 @@ namespace osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts public UniformVector2 Size; public UniformVector2 ProgressRange; public UniformFloat PathRadius; - public UniformFloat Padding; public UniformFloat GlowPortion; - private readonly UniformPadding4 pad; + private readonly UniformPadding8 pad; } } } From c1e4e51a5fe02cdff146cd9a45d85ba5c5847992 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 12 Jan 2024 15:34:02 +0300 Subject: [PATCH 2421/4092] Add comment explaining negative container padding --- osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 0af514a4f9..dcc8f4feec 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -89,6 +89,7 @@ namespace osu.Game.Screens.Play.HUD new Container { RelativeSizeAxes = Axes.Both, + // since we are using bigger path radius we need to expand the draw area outwards to preserve the curve placement Padding = new MarginPadding(-30f), Child = glowBar = new ArgonHealthDisplayBar { From 6572fa4378811663971527a80ea86b9db1c94103 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 12 Jan 2024 14:59:15 +0100 Subject: [PATCH 2422/4092] Only validate playback rate when in submission context Temporary workaround for https://github.com/ppy/osu/issues/26404. It appears that some audio files do not behave well with BASS, leading BASS to report a contradictory state of affairs (i.e. a track that is in playing state but also not progressing). This appears to be related to seeking specifically, therefore only enable the validation of playback rate in the most sensitive contexts, namely when any sort of score submission is involved. --- .../Visual/Gameplay/TestScenePlayerScoreSubmission.cs | 6 ------ osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 2 +- osu.Game/Screens/Play/SubmittingPlayer.cs | 5 +++++ 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index 96cfcd61c3..f75a2656ef 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -21,7 +21,6 @@ using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko; using osu.Game.Scoring; -using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; using osu.Game.Tests.Beatmaps; @@ -360,11 +359,6 @@ namespace osu.Game.Tests.Visual.Gameplay AllowImportCompletion = new SemaphoreSlim(1); } - protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new MasterGameplayClockContainer(beatmap, gameplayStart) - { - ShouldValidatePlaybackRate = false, - }; - protected override async Task ImportScore(Score score) { ScoreImportStarted = true; diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 8b8bf87436..0d60ec4713 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Play /// Whether the audio playback rate should be validated. /// Mostly disabled for tests. /// - internal bool ShouldValidatePlaybackRate { get; init; } = true; + internal bool ShouldValidatePlaybackRate { get; init; } /// /// Whether the audio playback is within acceptable ranges. diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index fb3481cbc4..04abb6162f 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -61,6 +61,11 @@ namespace osu.Game.Screens.Play AddInternal(new PlayerTouchInputDetector()); } + protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new MasterGameplayClockContainer(beatmap, gameplayStart) + { + ShouldValidatePlaybackRate = true, + }; + protected override void LoadAsyncComplete() { base.LoadAsyncComplete(); From 58ade18c06524365545aa9adb2a3264e8ad73f80 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 13 Jan 2024 04:49:50 +0900 Subject: [PATCH 2423/4092] Update framework --- osu.Android.props | 2 +- .../Overlays/Settings/Sections/Input/KeyBindingRow_KeyButton.cs | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 56931bbcb4..e39143ab93 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 c180baeab7..9afeaf7338 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From 882f49039029b7dc3e287ccc302d04de89de10df Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 13 Jan 2024 01:32:37 +0100 Subject: [PATCH 2424/4092] lazy load slider tail position --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 8 ++------ osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs | 8 ++++---- osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs | 8 ++++++++ 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 506145568e..2aca83ad03 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -98,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Objects set { repeatCount = value; - updateNestedPositions(); + endPositionCache.Invalidate(); } } @@ -165,7 +165,7 @@ namespace osu.Game.Rulesets.Osu.Objects public Slider() { SamplesBindable.CollectionChanged += (_, _) => UpdateNestedSamples(); - Path.Version.ValueChanged += _ => updateNestedPositions(); + Path.Version.ValueChanged += _ => endPositionCache.Invalidate(); } protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) @@ -218,7 +218,6 @@ namespace osu.Game.Rulesets.Osu.Objects { RepeatIndex = e.SpanIndex, StartTime = e.Time, - Position = EndPosition, StackHeight = StackHeight, ClassicSliderBehaviour = ClassicSliderBehaviour, }); @@ -245,9 +244,6 @@ namespace osu.Game.Rulesets.Osu.Objects if (HeadCircle != null) HeadCircle.Position = Position; - - if (TailCircle != null) - TailCircle.Position = EndPosition; } protected void UpdateNestedSamples() diff --git a/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs index 88a34fcb8f..2d5a5b7727 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs @@ -14,16 +14,16 @@ namespace osu.Game.Rulesets.Osu.Objects /// public abstract class SliderEndCircle : HitCircle { - private readonly Slider slider; + protected readonly Slider Slider; protected SliderEndCircle(Slider slider) { - this.slider = slider; + Slider = slider; } public int RepeatIndex { get; set; } - public double SpanDuration => slider.SpanDuration; + public double SpanDuration => Slider.SpanDuration; protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) { @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Objects else { // The first end circle should fade in with the slider. - TimePreempt += StartTime - slider.StartTime; + TimePreempt += StartTime - Slider.StartTime; } } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index ceee513412..abe2c7074b 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -1,9 +1,11 @@ // 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.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Scoring; +using osuTK; namespace osu.Game.Rulesets.Osu.Objects { @@ -15,6 +17,12 @@ namespace osu.Game.Rulesets.Osu.Objects /// public bool ClassicSliderBehaviour; + public override Vector2 Position + { + get => Slider.EndPosition; + set => throw new NotImplementedException(); + } + public SliderTailCircle(Slider slider) : base(slider) { From 5fa7f6ec53a4430b1db13f74e75e2e56b4a181fc Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 13 Jan 2024 02:21:32 +0100 Subject: [PATCH 2425/4092] make drawables that update from path version update once per frame --- .../PathControlPointConnectionPiece.cs | 16 ++++++++++++++-- .../Skinning/Default/PlaySliderBody.cs | 6 ++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs index 67685d21a7..004a091cae 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs @@ -51,14 +51,26 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components base.LoadComplete(); hitObjectPosition = hitObject.PositionBindable.GetBoundCopy(); - hitObjectPosition.BindValueChanged(_ => updateConnectingPath()); + hitObjectPosition.BindValueChanged(_ => pathRequiresUpdate = true); pathVersion = hitObject.Path.Version.GetBoundCopy(); - pathVersion.BindValueChanged(_ => updateConnectingPath()); + pathVersion.BindValueChanged(_ => pathRequiresUpdate = true); updateConnectingPath(); } + private bool pathRequiresUpdate; + + protected override void Update() + { + base.Update(); + + if (!pathRequiresUpdate) return; + + updateConnectingPath(); + pathRequiresUpdate = false; + } + /// /// Updates the path connecting this control point to the next one. /// diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs index aa507cbaf0..cbc62a1c7c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs @@ -18,8 +18,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default protected IBindable AccentColourBindable { get; private set; } = null!; - private IBindable pathVersion = null!; - [Resolved(CanBeNull = true)] private OsuRulesetConfigManager? config { get; set; } @@ -33,8 +31,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default ScaleBindable = drawableSlider.ScaleBindable.GetBoundCopy(); ScaleBindable.BindValueChanged(scale => PathRadius = OsuHitObject.OBJECT_RADIUS * scale.NewValue, true); - pathVersion = drawableSlider.PathVersion.GetBoundCopy(); - pathVersion.BindValueChanged(_ => Refresh()); + drawableObject.DefaultsApplied += _ => Refresh(); + drawableObject.HitObjectApplied += _ => Refresh(); AccentColourBindable = drawableObject.AccentColour.GetBoundCopy(); AccentColourBindable.BindValueChanged(accent => AccentColour = GetBodyAccentColour(skin, accent.NewValue), true); From e1186080b87436d78a591bb888ff8bfa93559dbf Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 13 Jan 2024 02:24:33 +0100 Subject: [PATCH 2426/4092] simplify scheduling logic --- .../PathControlPointConnectionPiece.cs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs index 004a091cae..7e7d653dbd 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs @@ -51,26 +51,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components base.LoadComplete(); hitObjectPosition = hitObject.PositionBindable.GetBoundCopy(); - hitObjectPosition.BindValueChanged(_ => pathRequiresUpdate = true); + hitObjectPosition.BindValueChanged(_ => Scheduler.AddOnce(updateConnectingPath)); pathVersion = hitObject.Path.Version.GetBoundCopy(); - pathVersion.BindValueChanged(_ => pathRequiresUpdate = true); + pathVersion.BindValueChanged(_ => Scheduler.AddOnce(updateConnectingPath)); updateConnectingPath(); } - private bool pathRequiresUpdate; - - protected override void Update() - { - base.Update(); - - if (!pathRequiresUpdate) return; - - updateConnectingPath(); - pathRequiresUpdate = false; - } - /// /// Updates the path connecting this control point to the next one. /// From 0934cff501bd580119e386bbc09e1073a12fb272 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 13 Jan 2024 05:22:41 +0900 Subject: [PATCH 2427/4092] Workaround implementation oversight See https://github.com/ppy/osu-framework/pull/6130. --- .../Settings/Sections/Input/KeyBindingRow_KeyButton.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow_KeyButton.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow_KeyButton.cs index 3493baa2ad..adf05a71b9 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow_KeyButton.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow_KeyButton.cs @@ -140,7 +140,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input /// A generated from the full input state. /// The key which triggered this update, and should be used as the binding. public void UpdateKeyCombination(KeyCombination fullState, InputKey triggerKey) => - UpdateKeyCombination(new KeyCombination(fullState.Keys.Where(KeyCombination.IsModifierKey).Append(triggerKey).ToArray())); + // TODO: Distinct() can be removed after https://github.com/ppy/osu-framework/pull/6130 is merged. + UpdateKeyCombination(new KeyCombination(fullState.Keys.Where(KeyCombination.IsModifierKey).Append(triggerKey).Distinct().ToArray())); public void UpdateKeyCombination(KeyCombination newCombination) { From 5303023e574c9e32239bd5afe4bc311241e07faa Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 13 Jan 2024 09:41:53 +0300 Subject: [PATCH 2428/4092] Add failing test case and fix selection assertion --- .../TestSceneFreeModSelectOverlay.cs | 47 +++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs index f1674401cd..a4feffddfb 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs @@ -10,12 +10,14 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Framework.Testing; using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Mods; using osu.Game.Screens.OnlinePlay; +using osu.Game.Utils; using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer @@ -23,6 +25,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public partial class TestSceneFreeModSelectOverlay : MultiplayerTestScene { private FreeModSelectOverlay freeModSelectOverlay; + private FooterButtonFreeMods footerButtonFreeMods; private readonly Bindable>> availableMods = new Bindable>>(); [BackgroundDependencyLoader] @@ -119,11 +122,46 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("select all button enabled", () => this.ChildrenOfType().Single().Enabled.Value); } + [Test] + public void TestSelectAllViaFooterButtonThenDeselectFromOverlay() + { + createFreeModSelect(); + + AddAssert("overlay select all button enabled", () => freeModSelectOverlay.ChildrenOfType().Single().Enabled.Value); + AddAssert("footer button displays off", () => footerButtonFreeMods.ChildrenOfType().Any(t => t.Text == "off")); + + AddStep("click footer select all button", () => + { + InputManager.MoveMouseTo(footerButtonFreeMods); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("all mods selected", assertAllAvailableModsSelected); + AddAssert("footer button displays all", () => footerButtonFreeMods.ChildrenOfType().Any(t => t.Text == "all")); + + AddStep("click deselect all button", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("all mods deselected", () => !freeModSelectOverlay.SelectedMods.Value.Any()); + AddAssert("footer button displays off", () => footerButtonFreeMods.ChildrenOfType().Any(t => t.Text == "off")); + } + private void createFreeModSelect() { - AddStep("create free mod select screen", () => Child = freeModSelectOverlay = new FreeModSelectOverlay + AddStep("create free mod select screen", () => Children = new Drawable[] { - State = { Value = Visibility.Visible } + freeModSelectOverlay = new FreeModSelectOverlay + { + State = { Value = Visibility.Visible } + }, + footerButtonFreeMods = new FooterButtonFreeMods(freeModSelectOverlay) + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Current = { BindTarget = freeModSelectOverlay.SelectedMods }, + }, }); AddUntilStep("all column content loaded", () => freeModSelectOverlay.ChildrenOfType().Any() @@ -134,10 +172,13 @@ namespace osu.Game.Tests.Visual.Multiplayer { var allAvailableMods = availableMods.Value .Where(pair => pair.Key != ModType.System) - .SelectMany(pair => pair.Value) + .SelectMany(pair => ModUtils.FlattenMods(pair.Value)) .Where(mod => mod.UserPlayable && mod.HasImplementation) .ToList(); + if (freeModSelectOverlay.SelectedMods.Value.Count != allAvailableMods.Count) + return false; + foreach (var availableMod in allAvailableMods) { if (freeModSelectOverlay.SelectedMods.Value.All(selectedMod => selectedMod.GetType() != availableMod.GetType())) From c476843a837882bd6d3eb7164a20203a9e6d1774 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 13 Jan 2024 09:43:20 +0300 Subject: [PATCH 2429/4092] Mark system mods as invalid for selection in mod select overlay --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index baa7e594c1..f9875c5547 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -447,7 +447,7 @@ namespace osu.Game.Overlays.Mods private void filterMods() { foreach (var modState in AllAvailableMods) - modState.ValidForSelection.Value = modState.Mod.HasImplementation && IsValidMod.Invoke(modState.Mod); + modState.ValidForSelection.Value = modState.Mod.Type != ModType.System && modState.Mod.HasImplementation && IsValidMod.Invoke(modState.Mod); } private void updateMultiplier() From aebf246f627b8658e94654d903c8d05bd23a190d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 13 Jan 2024 09:43:50 +0300 Subject: [PATCH 2430/4092] Change select all mod buttons to check `ValidForSelection` instead of directly checking system mods --- osu.Game/Overlays/Mods/SelectAllModsButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/SelectAllModsButton.cs b/osu.Game/Overlays/Mods/SelectAllModsButton.cs index b6b3051a0d..1da762d164 100644 --- a/osu.Game/Overlays/Mods/SelectAllModsButton.cs +++ b/osu.Game/Overlays/Mods/SelectAllModsButton.cs @@ -41,8 +41,8 @@ namespace osu.Game.Overlays.Mods private void updateEnabledState() { Enabled.Value = availableMods.Value - .Where(pair => pair.Key != ModType.System) .SelectMany(pair => pair.Value) + .Where(modState => modState.ValidForSelection.Value) .Any(modState => !modState.Active.Value && modState.Visible); } } From 4cde8685d340006824edf7a17ca2a67323c98279 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 13 Jan 2024 11:16:22 +0300 Subject: [PATCH 2431/4092] Add failing test case --- .../Multiplayer/TestSceneMultiplayer.cs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 462286335a..201fac37c8 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -29,7 +29,9 @@ using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Taiko; using osu.Game.Scoring; +using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.OnlinePlay.Lounge.Components; @@ -1009,6 +1011,54 @@ namespace osu.Game.Tests.Visual.Multiplayer } } + [Test] + public void TestGameplayStartsWhileInSongSelectWithDifferentRuleset() + { + createRoom(() => new Room + { + Name = { Value = "Test Room" }, + QueueMode = { Value = QueueMode.AllPlayers }, + Playlist = + { + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, + AllowedMods = new[] { new APIMod { Acronym = "HD" } }, + }, + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 1)).BeatmapInfo) + { + RulesetID = new TaikoRuleset().RulesetInfo.OnlineID, + AllowedMods = new[] { new APIMod { Acronym = "HD" } }, + }, + } + }); + + AddStep("join other user and make host", () => + { + multiplayerClient.AddUser(new APIUser { Id = 1234 }); + multiplayerClient.TransferHost(1234); + }); + + AddStep("select hidden", () => multiplayerClient.ChangeUserMods(new[] { new APIMod { Acronym = "HD" } })); + AddStep("make user ready", () => multiplayerClient.ChangeState(MultiplayerUserState.Ready)); + AddStep("press edit on second item", () => this.ChildrenOfType().Single(i => i.Item.RulesetID == 1) + .ChildrenOfType().Single().TriggerClick()); + + AddUntilStep("wait for song select", () => InputManager.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true); + AddAssert("ruleset is taiko", () => Ruleset.Value.OnlineID == 1); + + AddStep("start match by other user", () => + { + multiplayerClient.ChangeUserState(1234, MultiplayerUserState.Ready); + multiplayerClient.StartMatch().WaitSafely(); + }); + + AddUntilStep("wait for loading", () => multiplayerClient.ClientRoom?.State == MultiplayerRoomState.WaitingForLoad); + AddStep("set player loaded", () => multiplayerClient.ChangeUserState(1234, MultiplayerUserState.Loaded)); + AddUntilStep("wait for gameplay to start", () => multiplayerClient.ClientRoom?.State == MultiplayerRoomState.Playing); + AddAssert("hidden is selected", () => SelectedMods.Value, () => Has.One.TypeOf(typeof(OsuModHidden))); + } + private void enterGameplay() { pressReadyButton(); From c514550dfa0d3521b74d43aa2e0054a8b4325bdf Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 13 Jan 2024 11:17:08 +0300 Subject: [PATCH 2432/4092] Fix multiplayer potentially selecting mods of wrong ruleset when starting match --- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 8 ++++---- .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 2cd8e45d28..f35b205bc4 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -75,7 +75,7 @@ namespace osu.Game.Screens.OnlinePlay.Match private BeatmapManager beatmapManager { get; set; } [Resolved] - private RulesetStore rulesets { get; set; } + protected RulesetStore Rulesets { get; private set; } [Resolved] private IAPIProvider api { get; set; } = null!; @@ -422,7 +422,7 @@ namespace osu.Game.Screens.OnlinePlay.Match if (selected == null) return; - var rulesetInstance = rulesets.GetRuleset(SelectedItem.Value.RulesetID)?.CreateInstance(); + var rulesetInstance = Rulesets.GetRuleset(SelectedItem.Value.RulesetID)?.CreateInstance(); Debug.Assert(rulesetInstance != null); var allowedMods = SelectedItem.Value.AllowedMods.Select(m => m.ToMod(rulesetInstance)); @@ -463,7 +463,7 @@ namespace osu.Game.Screens.OnlinePlay.Match if (SelectedItem.Value == null || !this.IsCurrentScreen()) return; - var rulesetInstance = rulesets.GetRuleset(SelectedItem.Value.RulesetID)?.CreateInstance(); + var rulesetInstance = Rulesets.GetRuleset(SelectedItem.Value.RulesetID)?.CreateInstance(); Debug.Assert(rulesetInstance != null); Mods.Value = UserMods.Value.Concat(SelectedItem.Value.RequiredMods.Select(m => m.ToMod(rulesetInstance))).ToList(); } @@ -473,7 +473,7 @@ namespace osu.Game.Screens.OnlinePlay.Match if (SelectedItem.Value == null || !this.IsCurrentScreen()) return; - Ruleset.Value = rulesets.GetRuleset(SelectedItem.Value.RulesetID); + Ruleset.Value = Rulesets.GetRuleset(SelectedItem.Value.RulesetID); } private void beginHandlingTrack() diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 56256bb15c..8a85a46a2f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -241,8 +241,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer // update local mods based on room's reported status for the local user (omitting the base call implementation). // this makes the server authoritative, and avoids the local user potentially setting mods that the server is not aware of (ie. if the match was started during the selection being changed). - var ruleset = Ruleset.Value.CreateInstance(); - Mods.Value = client.LocalUser.Mods.Select(m => m.ToMod(ruleset)).Concat(SelectedItem.Value.RequiredMods.Select(m => m.ToMod(ruleset))).ToList(); + var rulesetInstance = Rulesets.GetRuleset(SelectedItem.Value.RulesetID)?.CreateInstance(); + Debug.Assert(rulesetInstance != null); + Mods.Value = client.LocalUser.Mods.Select(m => m.ToMod(rulesetInstance)).Concat(SelectedItem.Value.RequiredMods.Select(m => m.ToMod(rulesetInstance))).ToList(); } [Resolved(canBeNull: true)] From fca9b1f53630831c5ea3d56e09148c81b7ca9c57 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 13 Jan 2024 12:50:39 +0100 Subject: [PATCH 2433/4092] Fix so it reacts to PathVersion with Scheduler --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 6 ++++++ osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs | 6 ++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 4099d47d61..2f012e33da 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -244,7 +244,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables else if (slidingSample.IsPlaying) slidingSample.Stop(); } + } + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + // It's important that this is done in UpdateAfterChildren so that the SliderBody has a chance to update its Size and PathOffset on Update. double completionProgress = Math.Clamp((Time.Current - HitObject.StartTime) / HitObject.Duration, 0, 1); Ball.UpdateProgress(completionProgress); diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs index cbc62a1c7c..fb31f88d3c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs @@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default protected IBindable AccentColourBindable { get; private set; } = null!; + private IBindable pathVersion = null!; + [Resolved(CanBeNull = true)] private OsuRulesetConfigManager? config { get; set; } @@ -31,8 +33,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default ScaleBindable = drawableSlider.ScaleBindable.GetBoundCopy(); ScaleBindable.BindValueChanged(scale => PathRadius = OsuHitObject.OBJECT_RADIUS * scale.NewValue, true); - drawableObject.DefaultsApplied += _ => Refresh(); - drawableObject.HitObjectApplied += _ => Refresh(); + pathVersion = drawableSlider.PathVersion.GetBoundCopy(); + pathVersion.BindValueChanged(_ => Scheduler.AddOnce(Refresh)); AccentColourBindable = drawableObject.AccentColour.GetBoundCopy(); AccentColourBindable.BindValueChanged(accent => AccentColour = GetBodyAccentColour(skin, accent.NewValue), true); From ce643aa68f35369be1a975bb1ceb69fb54192cf2 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 13 Jan 2024 13:54:04 +0100 Subject: [PATCH 2434/4092] revert overwriting Position getter in SliderTailCircle It would have very weird implications when combined with the position bindable which would be all wrong and stuff --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 4 ++++ osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs | 8 -------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 2aca83ad03..032f105ded 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -218,6 +218,7 @@ namespace osu.Game.Rulesets.Osu.Objects { RepeatIndex = e.SpanIndex, StartTime = e.Time, + Position = EndPosition, StackHeight = StackHeight, ClassicSliderBehaviour = ClassicSliderBehaviour, }); @@ -244,6 +245,9 @@ namespace osu.Game.Rulesets.Osu.Objects if (HeadCircle != null) HeadCircle.Position = Position; + + if (TailCircle != null) + TailCircle.Position = EndPosition; } protected void UpdateNestedSamples() diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index abe2c7074b..ceee513412 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -1,11 +1,9 @@ // 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.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Scoring; -using osuTK; namespace osu.Game.Rulesets.Osu.Objects { @@ -17,12 +15,6 @@ namespace osu.Game.Rulesets.Osu.Objects /// public bool ClassicSliderBehaviour; - public override Vector2 Position - { - get => Slider.EndPosition; - set => throw new NotImplementedException(); - } - public SliderTailCircle(Slider slider) : base(slider) { From b1fae2bc6a766a6301b8ebdabd3573697ef3020f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 13 Jan 2024 16:24:48 +0300 Subject: [PATCH 2435/4092] Add failing test case --- .../TestScenePlayerScoreSubmission.cs | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index f75a2656ef..833cb24f41 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -15,11 +15,13 @@ using osu.Game.Online.Rooms; using osu.Game.Online.Solo; using osu.Game.Rulesets; using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko; +using osu.Game.Rulesets.Taiko.Mods; using osu.Game.Scoring; using osu.Game.Screens.Ranking; using osu.Game.Tests.Beatmaps; @@ -34,12 +36,19 @@ namespace osu.Game.Tests.Visual.Gameplay private Func createCustomBeatmap; private Func createCustomRuleset; + private Func createCustomMods; private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; protected override bool HasCustomSteps => true; - protected override TestPlayer CreatePlayer(Ruleset ruleset) => new FakeImportingPlayer(false); + protected override TestPlayer CreatePlayer(Ruleset ruleset) + { + if (createCustomMods != null) + SelectedMods.Value = SelectedMods.Value.Concat(createCustomMods()).ToList(); + + return new FakeImportingPlayer(false); + } protected new FakeImportingPlayer Player => (FakeImportingPlayer)base.Player; @@ -277,13 +286,27 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("ensure no submission", () => Player.SubmittedScore == null); } - private void createPlayerTest(bool allowFail = false, Func createBeatmap = null, Func createRuleset = null) + [Test] + public void TestNoSubmissionWithModsOfDifferentRuleset() + { + prepareTestAPI(true); + + createPlayerTest(createRuleset: () => new OsuRuleset(), createMods: () => new Mod[] { new TaikoModHidden() }); + + AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + + AddStep("exit", () => Player.Exit()); + AddAssert("ensure no submission", () => Player.SubmittedScore == null); + } + + private void createPlayerTest(bool allowFail = false, Func createBeatmap = null, Func createRuleset = null, Func createMods = null) { CreateTest(() => AddStep("set up requirements", () => { this.allowFail = allowFail; createCustomBeatmap = createBeatmap; createCustomRuleset = createRuleset; + createCustomMods = createMods; })); } From 0c02062780312b56dd2f79301e097b1b21de146a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 13 Jan 2024 15:52:57 +0300 Subject: [PATCH 2436/4092] Add guard against starting gameplay with invalid mod instances Move guard to `Player` instead --- osu.Game/Screens/Play/Player.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index b87306b9a2..ffd585148b 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -213,6 +213,12 @@ namespace osu.Game.Screens.Play if (playableBeatmap == null) return; + if (gameplayMods.Any(m => ruleset.AllMods.All(rulesetMod => m.GetType() != rulesetMod.GetType()))) + { + Logger.Log($@"Gameplay was started with a mod belonging to a ruleset different than '{ruleset.Description}'.", level: LogLevel.Important); + return; + } + mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); if (game != null) From f5d6d52d4c9160affc348e4dd81cbb9a1f5ed352 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 13 Jan 2024 14:47:40 +0100 Subject: [PATCH 2437/4092] Move logic for caching segments and updating path types to PathControlPointVisualiser --- .../Components/PathControlPointPiece.cs | 42 ---------------- .../Components/PathControlPointVisualiser.cs | 48 +++++++++++++++++++ 2 files changed, 48 insertions(+), 42 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 03792d8520..9b40b39a9d 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -5,19 +5,15 @@ using System; using System.Collections.Generic; -using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Localisation; -using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -56,27 +52,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private IBindable hitObjectPosition; private IBindable hitObjectScale; - [UsedImplicitly] - private readonly IBindable hitObjectVersion; - public PathControlPointPiece(T hitObject, PathControlPoint controlPoint) { this.hitObject = hitObject; ControlPoint = controlPoint; - // we don't want to run the path type update on construction as it may inadvertently change the hit object. - cachePoints(hitObject); - - hitObjectVersion = hitObject.Path.Version.GetBoundCopy(); - - // schedule ensure that updates are only applied after all operations from a single frame are applied. - // this avoids inadvertently changing the hit object path type for batch operations. - hitObjectVersion.BindValueChanged(_ => Scheduler.AddOnce(() => - { - cachePoints(hitObject); - updatePathType(); - })); - controlPoint.Changed += updateMarkerDisplay; Origin = Anchor.Centre; @@ -214,28 +194,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components protected override void OnDragEnd(DragEndEvent e) => DragEnded?.Invoke(); - private void cachePoints(T hitObject) => PointsInSegment = hitObject.Path.PointsInSegment(ControlPoint); - - /// - /// Handles correction of invalid path types. - /// - private void updatePathType() - { - if (ControlPoint.Type != PathType.PERFECT_CURVE) - return; - - if (PointsInSegment.Count > 3) - ControlPoint.Type = PathType.BEZIER; - - if (PointsInSegment.Count != 3) - return; - - ReadOnlySpan points = PointsInSegment.Select(p => p.Position).ToArray(); - RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(points); - if (boundingBox.Width >= 640 || boundingBox.Height >= 480) - ControlPoint.Type = PathType.BEZIER; - } - /// /// Updates the state of the circular control point marker. /// diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 24e2210b45..aae6275d45 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -9,15 +9,18 @@ using System.Collections.Specialized; using System.Diagnostics; using System.Linq; using Humanizer; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; @@ -52,6 +55,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components [Resolved(CanBeNull = true)] private IDistanceSnapProvider distanceSnapProvider { get; set; } + [UsedImplicitly] + private readonly IBindable hitObjectVersion; + public PathControlPointVisualiser(T hitObject, bool allowSelection) { this.hitObject = hitObject; @@ -64,6 +70,48 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components Connections = new Container> { RelativeSizeAxes = Axes.Both }, Pieces = new Container> { RelativeSizeAxes = Axes.Both } }; + + hitObjectVersion = hitObject.Path.Version.GetBoundCopy(); + + // schedule ensure that updates are only applied after all operations from a single frame are applied. + // this avoids inadvertently changing the hit object path type for batch operations. + hitObjectVersion.BindValueChanged(_ => Scheduler.AddOnce(cachePointsAndEnsureValidPathTypes)); + } + + /// + /// Caches the PointsInSegment of Pieces and handles correction of invalid path types. + /// + private void cachePointsAndEnsureValidPathTypes() + { + List pointsInCurrentSegment = new List(); + + foreach (var controlPoint in controlPoints) + { + if (controlPoint.Type != null) + pointsInCurrentSegment = new List(); + + pointsInCurrentSegment.Add(controlPoint); + + // Pieces might not be ordered so we need to find the piece corresponding to the current control point. + Pieces.Single(o => o.ControlPoint == controlPoint).PointsInSegment = pointsInCurrentSegment; + } + + foreach (var piece in Pieces) + { + if (piece.ControlPoint.Type != PathType.PERFECT_CURVE) + return; + + if (piece.PointsInSegment.Count > 3) + piece.ControlPoint.Type = PathType.BEZIER; + + if (piece.PointsInSegment.Count != 3) + return; + + ReadOnlySpan points = piece.PointsInSegment.Select(p => p.Position).ToArray(); + RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(points); + if (boundingBox.Width >= 640 || boundingBox.Height >= 480) + piece.ControlPoint.Type = PathType.BEZIER; + } } protected override void LoadComplete() From b4f9878b4656e83da770fcd00f1b70dd109e4a84 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 13 Jan 2024 20:39:49 +0100 Subject: [PATCH 2438/4092] working jank solution --- .../Components/PathControlPointVisualiser.cs | 36 ++++++++++++------- osu.Game/Rulesets/Objects/SliderPath.cs | 4 +++ 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index aae6275d45..a02a07f2b6 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -72,10 +72,27 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components }; hitObjectVersion = hitObject.Path.Version.GetBoundCopy(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + inputManager = GetContainingInputManager(); + + controlPoints.CollectionChanged += onControlPointsChanged; + controlPoints.BindTo(hitObject.Path.ControlPoints); // schedule ensure that updates are only applied after all operations from a single frame are applied. // this avoids inadvertently changing the hit object path type for batch operations. - hitObjectVersion.BindValueChanged(_ => Scheduler.AddOnce(cachePointsAndEnsureValidPathTypes)); + hitObject.Path.Validating += cachePointsAndEnsureValidPathTypes; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + hitObject.Path.Validating -= cachePointsAndEnsureValidPathTypes; } /// @@ -88,7 +105,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components foreach (var controlPoint in controlPoints) { if (controlPoint.Type != null) + { + pointsInCurrentSegment.Add(controlPoint); pointsInCurrentSegment = new List(); + } pointsInCurrentSegment.Add(controlPoint); @@ -99,13 +119,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components foreach (var piece in Pieces) { if (piece.ControlPoint.Type != PathType.PERFECT_CURVE) - return; + continue; if (piece.PointsInSegment.Count > 3) piece.ControlPoint.Type = PathType.BEZIER; if (piece.PointsInSegment.Count != 3) - return; + continue; ReadOnlySpan points = piece.PointsInSegment.Select(p => p.Position).ToArray(); RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(points); @@ -114,16 +134,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components } } - protected override void LoadComplete() - { - base.LoadComplete(); - - inputManager = GetContainingInputManager(); - - controlPoints.CollectionChanged += onControlPointsChanged; - controlPoints.BindTo(hitObject.Path.ControlPoints); - } - /// /// Selects the corresponding to the given , /// and deselects all other s. diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index dc71608132..bafc62ceda 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Objects private readonly Bindable version = new Bindable(); + public event Action? Validating; + /// /// The user-set distance of the path. If non-null, will match this value, /// and the path will be shortened/lengthened to match this length. @@ -233,6 +235,8 @@ namespace osu.Game.Rulesets.Objects if (pathCache.IsValid) return; + Validating?.Invoke(); + calculatePath(); calculateLength(); From da4d83f8ca2c7ccf5ff651c80df656393b817b7f Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 13 Jan 2024 20:54:44 +0100 Subject: [PATCH 2439/4092] remove the need for caching points in segment --- .../Components/PathControlPointPiece.cs | 2 - .../Components/PathControlPointVisualiser.cs | 57 +++++++++---------- 2 files changed, 27 insertions(+), 32 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 9b40b39a9d..8ddc38c38e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -37,8 +37,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public Action DragInProgress; public Action DragEnded; - public List PointsInSegment; - public readonly BindableBool IsSelected = new BindableBool(); public readonly PathControlPoint ControlPoint; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index a02a07f2b6..d057565c2b 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -9,7 +9,6 @@ using System.Collections.Specialized; using System.Diagnostics; using System.Linq; using Humanizer; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -55,9 +54,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components [Resolved(CanBeNull = true)] private IDistanceSnapProvider distanceSnapProvider { get; set; } - [UsedImplicitly] - private readonly IBindable hitObjectVersion; - public PathControlPointVisualiser(T hitObject, bool allowSelection) { this.hitObject = hitObject; @@ -70,8 +66,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components Connections = new Container> { RelativeSizeAxes = Axes.Both }, Pieces = new Container> { RelativeSizeAxes = Axes.Both } }; - - hitObjectVersion = hitObject.Path.Version.GetBoundCopy(); } protected override void LoadComplete() @@ -85,20 +79,20 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components // schedule ensure that updates are only applied after all operations from a single frame are applied. // this avoids inadvertently changing the hit object path type for batch operations. - hitObject.Path.Validating += cachePointsAndEnsureValidPathTypes; + hitObject.Path.Validating += ensureValidPathTypes; } protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - hitObject.Path.Validating -= cachePointsAndEnsureValidPathTypes; + hitObject.Path.Validating -= ensureValidPathTypes; } /// - /// Caches the PointsInSegment of Pieces and handles correction of invalid path types. + /// Handles correction of invalid path types. /// - private void cachePointsAndEnsureValidPathTypes() + private void ensureValidPathTypes() { List pointsInCurrentSegment = new List(); @@ -107,31 +101,33 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components if (controlPoint.Type != null) { pointsInCurrentSegment.Add(controlPoint); - pointsInCurrentSegment = new List(); + ensureValidPathType(pointsInCurrentSegment); + pointsInCurrentSegment.Clear(); } pointsInCurrentSegment.Add(controlPoint); - - // Pieces might not be ordered so we need to find the piece corresponding to the current control point. - Pieces.Single(o => o.ControlPoint == controlPoint).PointsInSegment = pointsInCurrentSegment; } - foreach (var piece in Pieces) - { - if (piece.ControlPoint.Type != PathType.PERFECT_CURVE) - continue; + ensureValidPathType(pointsInCurrentSegment); + } - if (piece.PointsInSegment.Count > 3) - piece.ControlPoint.Type = PathType.BEZIER; + private void ensureValidPathType(IReadOnlyList segment) + { + var first = segment[0]; - if (piece.PointsInSegment.Count != 3) - continue; + if (first.Type != PathType.PERFECT_CURVE) + return; - ReadOnlySpan points = piece.PointsInSegment.Select(p => p.Position).ToArray(); - RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(points); - if (boundingBox.Width >= 640 || boundingBox.Height >= 480) - piece.ControlPoint.Type = PathType.BEZIER; - } + if (segment.Count > 3) + first.Type = PathType.BEZIER; + + if (segment.Count != 3) + return; + + ReadOnlySpan points = segment.Select(p => p.Position).ToArray(); + RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(points); + if (boundingBox.Width >= 640 || boundingBox.Height >= 480) + first.Type = PathType.BEZIER; } /// @@ -298,7 +294,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components /// The path type we want to assign to the given control point piece. private void updatePathType(PathControlPointPiece piece, PathType? type) { - int indexInSegment = piece.PointsInSegment.IndexOf(piece.ControlPoint); + var pointsInSegment = hitObject.Path.PointsInSegment(piece.ControlPoint); + int indexInSegment = pointsInSegment.IndexOf(piece.ControlPoint); if (type?.Type == SplineType.PerfectCurve) { @@ -307,8 +304,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components // and one segment of the previous type. int thirdPointIndex = indexInSegment + 2; - if (piece.PointsInSegment.Count > thirdPointIndex + 1) - piece.PointsInSegment[thirdPointIndex].Type = piece.PointsInSegment[0].Type; + if (pointsInSegment.Count > thirdPointIndex + 1) + pointsInSegment[thirdPointIndex].Type = pointsInSegment[0].Type; } hitObject.Path.ExpectedDistance.Value = null; From 1e3c332658a320fb2dbf49e4a97e2927cafc4dcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Jan 2024 21:39:32 +0100 Subject: [PATCH 2440/4092] Fix broken installer Closes https://github.com/ppy/osu/issues/26510. Time for a rant. Technically, this "broke" with 9e8d07d3144bd4b072d28bd9bd0e255fee410de0, but it is actually an end result of upstream behaviours that I am failing to find a better description for than "utterly broken". Squirrel (the installer we use) has unit tests. Which is great, power to them. However, the method in which that testing is implemented leads to epic levels of WTF breakage. To determine whether Squirrel is being tested right now, it is checking all currently loaded assemblies, and determining that if any loaded assembly contains the magic string of "NUNIT" - among others - it must be being tested right now: https://github.com/clowd/Clowd.Squirrel/blob/24427217482deeeb9f2cacac555525edfc7bd9ac/src/Squirrel/SimpleSplat/PlatformModeDetector.cs#L17-L32 If one assumes that there is no conceivable way that an NUnit assembly *may* be loaded *without* it being a test context, this *may* seem sane. Foreshadowing. (Now, to avoid being hypocritical, we also do this, *but* we do this by checking if the *entry* assembly is an NUnit: https://github.com/ppy/osu-framework/blob/92db55a52742047b42c0b4b25327ce28bd991b44/osu.Framework/Development/DebugUtils.cs#L16-L34 which seems *much* saner, no?) Now, why did this break with 9e8d07d3144bd4b072d28bd9bd0e255fee410de0 *specifically*, you might wonder? Well the reason is this line: https://github.com/ppy/osu/blob/3d3f58c2523f519504d9156a684538e2aa0c094c/osu.Desktop/NVAPI.cs#L183 Yes you are reading this correctly, it's not NVAPI anything itself that breaks this, it is *a log statement*. To be precise, what the log statement *does* to provoke this, is calling into framework. That causes the framework assembly to load, *which* transitively loads the `nunit.framework` assembly. (If you ever find yourself wanting to find out this sort of cursed knowledge - I hope you never need to - you can run something along the lines of dotnet-trace collect --providers Microsoft-Windows-DotNETRuntime:4 -- .\osu!.exe then open the resulting trace in PerfView, and then search the `Microsoft-Windows-DotNETRuntime/AssemblyLoader/Start` log for the cursed assembly. In this case, the relevant entry said something along the lines of HasStack="True" ThreadID="23,924" ProcessorNumber="0" ClrInstanceID="6" AssemblyName="nunit.framework, Version=3.13.3.0, Culture=neutral, PublicKeyToken=2638cd05610744eb" AssemblyPath="" RequestingAssembly="osu.Framework, Version=2024.113.0.0, Culture=neutral, PublicKeyToken=null" AssemblyLoadContext="Default" RequestingAssemblyLoadContext="Default" ActivityID="/#21032/1/26/" Either that or just comment the log line for kicks. But the above is *much* faster.) Now, what *happens* if Squirrel "detects" that it is being "tested"? Well, it will refuse to close after executing the "hooks" defined via `SquirrelAwareApp`: https://github.com/clowd/Clowd.Squirrel/blob/24427217482deeeb9f2cacac555525edfc7bd9ac/src/Squirrel/SquirrelAwareApp.cs#L85-L88 and it will also refuse to create version shortcuts: https://github.com/clowd/Clowd.Squirrel/blob/24427217482deeeb9f2cacac555525edfc7bd9ac/src/Squirrel/UpdateManager.Shortcuts.cs#L63-L65 Sounds familiar, don't it? There are days on which I tire of computers. Today is one of them. --- osu.Desktop/Program.cs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index a652e31f62..6b95a82703 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -30,12 +30,19 @@ namespace osu.Desktop [STAThread] public static void Main(string[] args) { - // NVIDIA profiles are based on the executable name of a process. - // Lazer and stable share the same executable name. - // Stable sets this setting to "Off", which may not be what we want, so let's force it back to the default "Auto" on startup. - NVAPI.ThreadedOptimisations = NvThreadControlSetting.OGL_THREAD_CONTROL_DEFAULT; - - // run Squirrel first, as the app may exit after these run + /* + * WARNING: DO NOT PLACE **ANY** CODE ABOVE THE FOLLOWING BLOCK! + * + * Logic handling Squirrel MUST run before EVERYTHING if you do not want to break it. + * To be more precise: Squirrel is internally using a rather... crude method to determine whether it is running under NUnit, + * namely by checking loaded assemblies: + * https://github.com/clowd/Clowd.Squirrel/blob/24427217482deeeb9f2cacac555525edfc7bd9ac/src/Squirrel/SimpleSplat/PlatformModeDetector.cs#L17-L32 + * + * If it finds ANY assembly from the ones listed above - REGARDLESS of the reason why it is loaded - + * the app will then do completely broken things like: + * - not creating system shortcuts (as the logic is if'd out if "running tests") + * - not exiting after the install / first-update / uninstall hooks are ran (as the `Environment.Exit()` calls are if'd out if "running tests") + */ if (OperatingSystem.IsWindows()) { var windowsVersion = Environment.OSVersion.Version; @@ -59,6 +66,11 @@ namespace osu.Desktop setupSquirrel(); } + // NVIDIA profiles are based on the executable name of a process. + // Lazer and stable share the same executable name. + // Stable sets this setting to "Off", which may not be what we want, so let's force it back to the default "Auto" on startup. + NVAPI.ThreadedOptimisations = NvThreadControlSetting.OGL_THREAD_CONTROL_DEFAULT; + // Back up the cwd before DesktopGameHost changes it string cwd = Environment.CurrentDirectory; From 39908f5425d99d04e4c09b833c170193f3715205 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 13 Jan 2024 22:39:09 +0100 Subject: [PATCH 2441/4092] remove Validating event and instead call validation explicitly on edits --- .../Components/PathControlPointVisualiser.cs | 20 ++++++++----------- .../Sliders/SliderPlacementBlueprint.cs | 2 ++ .../Sliders/SliderSelectionBlueprint.cs | 4 ++++ osu.Game/Rulesets/Objects/SliderPath.cs | 4 ---- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index d057565c2b..b2d1709531 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -76,23 +76,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components controlPoints.CollectionChanged += onControlPointsChanged; controlPoints.BindTo(hitObject.Path.ControlPoints); - - // schedule ensure that updates are only applied after all operations from a single frame are applied. - // this avoids inadvertently changing the hit object path type for batch operations. - hitObject.Path.Validating += ensureValidPathTypes; - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - hitObject.Path.Validating -= ensureValidPathTypes; } /// /// Handles correction of invalid path types. /// - private void ensureValidPathTypes() + public void EnsureValidPathTypes() { List pointsInCurrentSegment = new List(); @@ -113,6 +102,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private void ensureValidPathType(IReadOnlyList segment) { + if (segment.Count == 0) + return; + var first = segment[0]; if (first.Type != PathType.PERFECT_CURVE) @@ -394,6 +386,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components // Maintain the path types in case they got defaulted to bezier at some point during the drag. for (int i = 0; i < hitObject.Path.ControlPoints.Count; i++) hitObject.Path.ControlPoints[i].Type = dragPathTypes[i]; + + EnsureValidPathTypes(); } public void DragEnded() => changeHandler?.EndChange(); @@ -467,6 +461,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { foreach (var p in Pieces.Where(p => p.IsSelected.Value)) updatePathType(p, type); + + EnsureValidPathTypes(); }); if (countOfState == totalCount) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 28e972bacd..75ed818693 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -316,6 +316,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void updateSlider() { + controlPointVisualiser.EnsureValidPathTypes(); + if (state == SliderPlacementState.Drawing) HitObject.Path.ExpectedDistance.Value = (float)HitObject.Path.CalculatedDistance; else diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index b3efe1c495..3575e15d1d 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -254,6 +254,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders // Move the control points from the insertion index onwards to make room for the insertion controlPoints.Insert(insertionIndex, pathControlPoint); + ControlPointVisualiser?.EnsureValidPathTypes(); + HitObject.SnapTo(distanceSnapProvider); return pathControlPoint; @@ -275,6 +277,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders controlPoints.Remove(c); } + ControlPointVisualiser?.EnsureValidPathTypes(); + // Snap the slider to the current beat divisor before checking length validity. HitObject.SnapTo(distanceSnapProvider); diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index bafc62ceda..dc71608132 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -25,8 +25,6 @@ namespace osu.Game.Rulesets.Objects private readonly Bindable version = new Bindable(); - public event Action? Validating; - /// /// The user-set distance of the path. If non-null, will match this value, /// and the path will be shortened/lengthened to match this length. @@ -235,8 +233,6 @@ namespace osu.Game.Rulesets.Objects if (pathCache.IsValid) return; - Validating?.Invoke(); - calculatePath(); calculateLength(); From 83e108071a123650991fded528f64ad80421d155 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 13 Jan 2024 22:51:33 +0100 Subject: [PATCH 2442/4092] fix wrong path type being displayed --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 75ed818693..0fa84c91fc 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -267,6 +267,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders segmentStart.Type = PathType.BEZIER; break; } + + controlPointVisualiser.EnsureValidPathTypes(); } private void updateCursor() @@ -316,8 +318,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void updateSlider() { - controlPointVisualiser.EnsureValidPathTypes(); - if (state == SliderPlacementState.Drawing) HitObject.Path.ExpectedDistance.Value = (float)HitObject.Path.CalculatedDistance; else From 243b7b6fdad6aa28df1df8a06ecf574ebeff1238 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 13 Jan 2024 23:17:38 +0100 Subject: [PATCH 2443/4092] fix code quality --- .../Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 8ddc38c38e..e741d67e3b 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -4,7 +4,6 @@ #nullable disable using System; -using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; From 68496f7a0e865b3d1d74415d482e78a4747bd454 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 14 Jan 2024 15:13:20 +0900 Subject: [PATCH 2444/4092] Fix scores not showing up on leaderboards during gameplay --- osu.Game/Screens/Select/PlaySongSelect.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 4951504ff5..7b7b8857f3 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -146,14 +146,6 @@ namespace osu.Game.Screens.Select } } - public override void OnSuspending(ScreenTransitionEvent e) - { - // Scores will be refreshed on arriving at this screen. - // Clear them to avoid animation overload on returning to song select. - playBeatmapDetailArea.Leaderboard.ClearScores(); - base.OnSuspending(e); - } - public override void OnResuming(ScreenTransitionEvent e) { base.OnResuming(e); From b7d74fda88b98cd8b78df7ae5e907cc8b8fb9c7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 14 Jan 2024 09:10:39 +0100 Subject: [PATCH 2445/4092] Revert "Keep editor in frame stable mode when possible" --- .../TestSceneEditorAutoplayFastStreams.cs | 51 ------------------- .../Editor/TestSceneOsuComposerSelection.cs | 2 - .../Edit/DrawableEditorRulesetWrapper.cs | 21 +------- 3 files changed, 1 insertion(+), 73 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu.Tests/Editor/TestSceneEditorAutoplayFastStreams.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneEditorAutoplayFastStreams.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneEditorAutoplayFastStreams.cs deleted file mode 100644 index cf5cd809ef..0000000000 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneEditorAutoplayFastStreams.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. - -using System.Linq; -using NUnit.Framework; -using osu.Framework.Testing; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Tests.Beatmaps; -using osu.Game.Tests.Visual; - -namespace osu.Game.Rulesets.Osu.Tests.Editor -{ - /// - /// This test covers autoplay working correctly in the editor on fast streams. - /// Might seem like a weird test, but frame stability being toggled can cause autoplay to operation incorrectly. - /// This is clearly a bug with the autoplay algorithm, but is worked around at an editor level for now. - /// - public partial class TestSceneEditorAutoplayFastStreams : EditorTestScene - { - protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) - { - var testBeatmap = new TestBeatmap(ruleset, false); - testBeatmap.HitObjects.AddRange(new[] - { - new HitCircle { StartTime = 500 }, - new HitCircle { StartTime = 530 }, - new HitCircle { StartTime = 560 }, - new HitCircle { StartTime = 590 }, - new HitCircle { StartTime = 620 }, - }); - - return testBeatmap; - } - - protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); - - [Test] - public void TestAllHit() - { - AddStep("start playback", () => EditorClock.Start()); - AddUntilStep("wait for all hit", () => - { - DrawableHitCircle[] hitCircles = Editor.ChildrenOfType().OrderBy(s => s.HitObject.StartTime).ToArray(); - - return hitCircles.Length == 5 && hitCircles.All(h => h.IsHit); - }); - } - } -} diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs index 366f17daee..623cefff6b 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs @@ -46,12 +46,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor moveMouseToObject(() => slider); AddStep("seek after end", () => EditorClock.Seek(750)); - AddUntilStep("wait for seek", () => !EditorClock.IsSeeking); AddStep("left click", () => InputManager.Click(MouseButton.Left)); AddAssert("slider not selected", () => EditorBeatmap.SelectedHitObjects.Count == 0); AddStep("seek to visible", () => EditorClock.Seek(650)); - AddUntilStep("wait for seek", () => !EditorClock.IsSeeking); AddStep("left click", () => InputManager.Click(MouseButton.Left)); AddUntilStep("slider selected", () => EditorBeatmap.SelectedHitObjects.Single() == slider); } diff --git a/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs b/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs index ebf06bcc4e..174b278d89 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.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.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.ObjectExtensions; @@ -27,9 +26,6 @@ namespace osu.Game.Rulesets.Edit [Resolved] private EditorBeatmap beatmap { get; set; } = null!; - [Resolved] - private EditorClock editorClock { get; set; } = null!; - public DrawableEditorRulesetWrapper(DrawableRuleset drawableRuleset) { this.drawableRuleset = drawableRuleset; @@ -42,6 +38,7 @@ namespace osu.Game.Rulesets.Edit [BackgroundDependencyLoader] private void load() { + drawableRuleset.FrameStablePlayback = false; Playfield.DisplayJudgements.Value = false; } @@ -68,22 +65,6 @@ namespace osu.Game.Rulesets.Edit Scheduler.AddOnce(regenerateAutoplay); } - protected override void Update() - { - base.Update(); - - // Whenever possible, we want to stay in frame stability playback. - // Without doing so, we run into bugs with some gameplay elements not behaving as expected. - // - // Note that this is not using EditorClock.IsSeeking as that would exit frame stability - // on all seeks. The intention here is to retain frame stability for small seeks. - // - // I still think no gameplay elements should require frame stability in the first place, but maybe that ship has sailed already.. - bool shouldBypassFrameStability = Math.Abs(drawableRuleset.FrameStableClock.CurrentTime - editorClock.CurrentTime) > 1000; - - drawableRuleset.FrameStablePlayback = !shouldBypassFrameStability; - } - private void regenerateAutoplay() { var autoplayMod = drawableRuleset.Mods.OfType().Single(); From eecd868d66a2fe928b76756996d25e588b383309 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 14 Jan 2024 09:24:50 +0100 Subject: [PATCH 2446/4092] Use darker blue for `SliderTailHit` result --- osu.Game/Graphics/OsuColour.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 0d11d2d4ef..1b5877b966 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -95,6 +95,7 @@ namespace osu.Game.Graphics case HitResult.SmallTickHit: case HitResult.LargeTickHit: + case HitResult.SliderTailHit: case HitResult.Great: return Blue; From 1cd7656f331172a281b12552c62b1f52ca2bad1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 14 Jan 2024 09:33:04 +0100 Subject: [PATCH 2447/4092] Reorder hit results so that `SliderTailHit` is next to `SmallTickHit` This addresses https://github.com/ppy/osu/discussions/26507. --- osu.Game/Rulesets/Scoring/HitResult.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index f307344347..7e58df3cfa 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Scoring /// [Description(@"")] [EnumMember(Value = "none")] - [Order(14)] + [Order(15)] None, /// @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Scoring /// Indicates small tick miss. /// [EnumMember(Value = "small_tick_miss")] - [Order(11)] + [Order(12)] SmallTickMiss, /// @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Scoring /// [EnumMember(Value = "large_tick_miss")] [Description("-")] - [Order(10)] + [Order(11)] LargeTickMiss, /// @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Scoring /// [Description("S Bonus")] [EnumMember(Value = "small_bonus")] - [Order(9)] + [Order(10)] SmallBonus, /// @@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Scoring /// [Description("L Bonus")] [EnumMember(Value = "large_bonus")] - [Order(8)] + [Order(9)] LargeBonus, /// @@ -119,14 +119,14 @@ namespace osu.Game.Rulesets.Scoring /// [EnumMember(Value = "ignore_miss")] [Description("-")] - [Order(13)] + [Order(14)] IgnoreMiss, /// /// Indicates a hit that should be ignored for scoring purposes. /// [EnumMember(Value = "ignore_hit")] - [Order(12)] + [Order(13)] IgnoreHit, /// @@ -136,14 +136,14 @@ namespace osu.Game.Rulesets.Scoring /// May be paired with . /// [EnumMember(Value = "combo_break")] - [Order(15)] + [Order(16)] ComboBreak, /// /// A special judgement similar to that's used to increase the valuation of the final tick of a slider. /// [EnumMember(Value = "slider_tail_hit")] - [Order(16)] + [Order(8)] SliderTailHit, /// From f85e6add8e7ba95463622904d26202e140ed4434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 14 Jan 2024 12:55:07 +0100 Subject: [PATCH 2448/4092] Add failing test case --- .../Mods/TestSceneOsuModFlashlight.cs | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs index a353914cd5..defbb97c8a 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs @@ -1,8 +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.Testing; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Replays; +using osuTK; namespace osu.Game.Rulesets.Osu.Tests.Mods { @@ -21,5 +31,51 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods [Test] public void TestComboBasedSize([Values] bool comboBasedSize) => CreateModTest(new ModTestData { Mod = new OsuModFlashlight { ComboBasedSize = { Value = comboBasedSize } }, PassCondition = () => true }); + + [Test] + public void TestSliderDimsOnlyAfterStartTime() + { + bool sliderDimmedBeforeStartTime = false; + + CreateModTest(new ModTestData + { + Mod = new OsuModFlashlight(), + PassCondition = () => + { + sliderDimmedBeforeStartTime |= + Player.GameplayClockContainer.CurrentTime < 1000 && Player.ChildrenOfType.Flashlight>().Single().FlashlightDim > 0; + return Player.GameplayState.HasPassed && !sliderDimmedBeforeStartTime; + }, + Beatmap = new OsuBeatmap + { + HitObjects = new List + { + new HitCircle { StartTime = 0, }, + new Slider + { + StartTime = 1000, + Path = new SliderPath(new[] + { + new PathControlPoint(), + new PathControlPoint(new Vector2(100)) + }) + } + }, + BeatmapInfo = + { + StackLeniency = 0, + } + }, + ReplayFrames = new List + { + new OsuReplayFrame(0, new Vector2(), OsuAction.LeftButton), + new OsuReplayFrame(990, new Vector2()), + new OsuReplayFrame(1000, new Vector2(), OsuAction.LeftButton), + new OsuReplayFrame(2000, new Vector2(100), OsuAction.LeftButton), + new OsuReplayFrame(2001, new Vector2(100)), + }, + Autoplay = false, + }); + } } } From 0b2b1fc58888b430c16e81119fe2a277567775de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 14 Jan 2024 13:05:02 +0100 Subject: [PATCH 2449/4092] Fix flashlight dim being applied before slider start time Closes https://github.com/ppy/osu/issues/26515. Compare https://github.com/ppy/osu/pull/26053. --- osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 252d7e2762..46c9bdd17f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToDrawableHitObject(DrawableHitObject drawable) { if (drawable is DrawableSlider s) - s.Tracking.ValueChanged += flashlight.OnSliderTrackingChange; + s.Tracking.ValueChanged += _ => flashlight.OnSliderTrackingChange(s); } private partial class OsuFlashlight : Flashlight, IRequireHighFrequencyMousePosition @@ -66,10 +66,10 @@ namespace osu.Game.Rulesets.Osu.Mods FlashlightSmoothness = 1.4f; } - public void OnSliderTrackingChange(ValueChangedEvent e) + public void OnSliderTrackingChange(DrawableSlider e) { // If a slider is in a tracking state, a further dim should be applied to the (remaining) visible portion of the playfield. - FlashlightDim = e.NewValue ? 0.8f : 0.0f; + FlashlightDim = Time.Current >= e.HitObject.StartTime && e.Tracking.Value ? 0.8f : 0.0f; } protected override bool OnMouseMove(MouseMoveEvent e) From baf3867e17c5d10e63433dcdf429095bce9deaa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 14 Jan 2024 13:48:47 +0100 Subject: [PATCH 2450/4092] Fix date failing to display on leaderboard for some scores with weird datetimes Addresses https://github.com/ppy/osu/discussions/26517. The score reported has a datetime of 0001/1/1 05:00:00 AM. Bit of a dodge fix but maybe fine? --- osu.Game/Extensions/TimeDisplayExtensions.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Extensions/TimeDisplayExtensions.cs b/osu.Game/Extensions/TimeDisplayExtensions.cs index 98633958ee..1b224cfeb7 100644 --- a/osu.Game/Extensions/TimeDisplayExtensions.cs +++ b/osu.Game/Extensions/TimeDisplayExtensions.cs @@ -59,7 +59,8 @@ namespace osu.Game.Extensions /// A short relative string representing the input time. public static string ToShortRelativeTime(this DateTimeOffset time, TimeSpan lowerCutoff) { - if (time == default) + // covers all `DateTimeOffset` instances with the date portion of 0001-01-01. + if (time.Date == default) return "-"; var now = DateTime.Now; From fb4f8d0834ee2dee64eda3e5ba8054faa26130eb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 14 Jan 2024 22:31:48 +0900 Subject: [PATCH 2451/4092] 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 e39143ab93..a7376aa5a7 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 9afeaf7338..98e8b136e5 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From 724b4c9507b886fa8af21c6384d01ae0752305d4 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Sun, 14 Jan 2024 21:09:49 +0100 Subject: [PATCH 2452/4092] Expand click target of toolbar buttons and clock --- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 92 ++++++++++--------- osu.Game/Overlays/Toolbar/ToolbarClock.cs | 77 +++++++++------- .../Overlays/Toolbar/ToolbarHomeButton.cs | 2 +- .../Overlays/Toolbar/ToolbarMusicButton.cs | 2 +- .../Toolbar/ToolbarRulesetTabButton.cs | 2 +- .../Overlays/Toolbar/ToolbarSettingsButton.cs | 2 +- .../Overlays/Toolbar/ToolbarUserButton.cs | 2 +- 7 files changed, 97 insertions(+), 82 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 03a1cfc005..36ff703c82 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -63,6 +63,7 @@ namespace osu.Game.Overlays.Toolbar protected virtual Anchor TooltipAnchor => Anchor.TopLeft; + protected readonly Container ButtonContent; protected ConstrainedIconContainer IconContainer; protected SpriteText DrawableText; protected Box HoverBackground; @@ -80,59 +81,66 @@ namespace osu.Game.Overlays.Toolbar protected ToolbarButton() { - Width = Toolbar.HEIGHT; + AutoSizeAxes = Axes.X; RelativeSizeAxes = Axes.Y; - Padding = new MarginPadding(3); - Children = new Drawable[] { - BackgroundContent = new Container + ButtonContent = new Container { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 6, - CornerExponent = 3f, - Children = new Drawable[] - { - HoverBackground = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(80).Opacity(180), - Blending = BlendingParameters.Additive, - Alpha = 0, - }, - flashBackground = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - Colour = Color4.White.Opacity(100), - Blending = BlendingParameters.Additive, - }, - } - }, - Flow = new FillFlowContainer - { - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5), - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Padding = new MarginPadding { Left = Toolbar.HEIGHT / 2, Right = Toolbar.HEIGHT / 2 }, + Width = Toolbar.HEIGHT, RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, + Padding = new MarginPadding(3), Children = new Drawable[] { - IconContainer = new ConstrainedIconContainer + BackgroundContent = new Container { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Size = new Vector2(20), - Alpha = 0, + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 6, + CornerExponent = 3f, + Children = new Drawable[] + { + HoverBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(80).Opacity(180), + Blending = BlendingParameters.Additive, + Alpha = 0, + }, + flashBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + Colour = Color4.White.Opacity(100), + Blending = BlendingParameters.Additive, + }, + } }, - DrawableText = new OsuSpriteText + Flow = new FillFlowContainer { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Padding = new MarginPadding { Left = Toolbar.HEIGHT / 2, Right = Toolbar.HEIGHT / 2 }, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Children = new Drawable[] + { + IconContainer = new ConstrainedIconContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Size = new Vector2(20), + Alpha = 0, + }, + DrawableText = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + }, }, }, }, diff --git a/osu.Game/Overlays/Toolbar/ToolbarClock.cs b/osu.Game/Overlays/Toolbar/ToolbarClock.cs index 67688155ae..e7959986ae 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarClock.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarClock.cs @@ -42,52 +42,59 @@ namespace osu.Game.Overlays.Toolbar clockDisplayMode = config.GetBindable(OsuSetting.ToolbarClockDisplayMode); prefer24HourTime = config.GetBindable(OsuSetting.Prefer24HourTime); - Padding = new MarginPadding(3); - Children = new Drawable[] { new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 6, - CornerExponent = 3f, - Children = new Drawable[] - { - hoverBackground = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(80).Opacity(180), - Blending = BlendingParameters.Additive, - Alpha = 0, - }, - flashBackground = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - Colour = Color4.White.Opacity(100), - Blending = BlendingParameters.Additive, - }, - } - }, - new FillFlowContainer { RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5), - Padding = new MarginPadding(10), + Padding = new MarginPadding(3), Children = new Drawable[] { - analog = new AnalogClockDisplay + new Container { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 6, + CornerExponent = 3f, + Children = new Drawable[] + { + hoverBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(80).Opacity(180), + Blending = BlendingParameters.Additive, + Alpha = 0, + }, + flashBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + Colour = Color4.White.Opacity(100), + Blending = BlendingParameters.Additive, + }, + } }, - digital = new DigitalClockDisplay + new FillFlowContainer { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5), + Padding = new MarginPadding(10), + Children = new Drawable[] + { + analog = new AnalogClockDisplay + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + digital = new DigitalClockDisplay + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + } + } } } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs index 70675c1b92..499ca804c9 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs @@ -12,7 +12,7 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarHomeButton() { - Width *= 1.4f; + ButtonContent.Width *= 1.4f; Hotkey = GlobalAction.Home; } diff --git a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs index 69597c6b46..5da0056787 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Toolbar public ToolbarMusicButton() { Hotkey = GlobalAction.ToggleNowPlaying; - AutoSizeAxes = Axes.X; + ButtonContent.AutoSizeAxes = Axes.X; } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs index 5500f1c879..98a0191e0a 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs @@ -48,7 +48,7 @@ namespace osu.Game.Overlays.Toolbar public RulesetButton() { - Padding = new MarginPadding(3) + ButtonContent.Padding = new MarginPadding(3) { Bottom = 5 }; diff --git a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs index 78df060252..899f58c9c0 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarSettingsButton() { - Width *= 1.4f; + ButtonContent.Width *= 1.4f; Hotkey = GlobalAction.ToggleSettings; } diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs index 19b98628e6..230974cd59 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs @@ -34,7 +34,7 @@ namespace osu.Game.Overlays.Toolbar public ToolbarUserButton() { - AutoSizeAxes = Axes.X; + ButtonContent.AutoSizeAxes = Axes.X; } [BackgroundDependencyLoader] From 47b385c552462c98705e4a8a8c578cf6e8a42419 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Sun, 14 Jan 2024 21:13:00 +0100 Subject: [PATCH 2453/4092] Move toolbar button padding to a const --- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 4 +++- osu.Game/Overlays/Toolbar/ToolbarClock.cs | 2 +- osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 36ff703c82..1da2e1b744 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -26,6 +26,8 @@ namespace osu.Game.Overlays.Toolbar { public abstract partial class ToolbarButton : OsuClickableContainer, IKeyBindingHandler { + public const float PADDING = 3; + protected GlobalAction? Hotkey { get; set; } public void SetIcon(Drawable icon) @@ -90,7 +92,7 @@ namespace osu.Game.Overlays.Toolbar { Width = Toolbar.HEIGHT, RelativeSizeAxes = Axes.Y, - Padding = new MarginPadding(3), + Padding = new MarginPadding(PADDING), Children = new Drawable[] { BackgroundContent = new Container diff --git a/osu.Game/Overlays/Toolbar/ToolbarClock.cs b/osu.Game/Overlays/Toolbar/ToolbarClock.cs index e7959986ae..e1d658b811 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarClock.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarClock.cs @@ -48,7 +48,7 @@ namespace osu.Game.Overlays.Toolbar { RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, - Padding = new MarginPadding(3), + Padding = new MarginPadding(ToolbarButton.PADDING), Children = new Drawable[] { new Container diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs index 98a0191e0a..c224f0f9c9 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs @@ -48,7 +48,7 @@ namespace osu.Game.Overlays.Toolbar public RulesetButton() { - ButtonContent.Padding = new MarginPadding(3) + ButtonContent.Padding = new MarginPadding(PADDING) { Bottom = 5 }; From d2efa2e56a34a28a2656ca30885046aa33cd06f7 Mon Sep 17 00:00:00 2001 From: smallketchup82 <69545310+smallketchup82@users.noreply.github.com> Date: Sun, 14 Jan 2024 15:47:50 -0500 Subject: [PATCH 2454/4092] peppy said he prefers the version without pipes so ill remove the pipes --- osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index 0f5c94ac1d..a4ba2a27f6 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -189,14 +189,14 @@ namespace osu.Game.Beatmaps.Drawables // Difficulty row circleSize.Text = "CS: " + adjustedDifficulty.CircleSize.ToString("0.##"); - drainRate.Text = "| HP: " + adjustedDifficulty.DrainRate.ToString("0.##"); - approachRate.Text = "| AR: " + adjustedDifficulty.ApproachRate.ToString("0.##"); - overallDifficulty.Text = "| OD: " + adjustedDifficulty.OverallDifficulty.ToString("0.##"); + drainRate.Text = " HP: " + adjustedDifficulty.DrainRate.ToString("0.##"); + approachRate.Text = " AR: " + adjustedDifficulty.ApproachRate.ToString("0.##"); + overallDifficulty.Text = " OD: " + adjustedDifficulty.OverallDifficulty.ToString("0.##"); // Misc row length.Text = "Length: " + TimeSpan.FromMilliseconds(displayedContent.BeatmapInfo.Length / rate).ToString("mm\\:ss"); - bpm.Text = "| BPM: " + bpmAdjusted; - maxCombo.Text = "| Max Combo: " + displayedContent.BeatmapInfo.TotalObjectCount; + bpm.Text = " BPM: " + bpmAdjusted; + maxCombo.Text = " Max Combo: " + displayedContent.BeatmapInfo.TotalObjectCount; } public void Move(Vector2 pos) => Position = pos; From 0237c9c6d712ee07a9f877b589e80bcf53591045 Mon Sep 17 00:00:00 2001 From: smallketchup82 <69545310+smallketchup82@users.noreply.github.com> Date: Sun, 14 Jan 2024 15:57:16 -0500 Subject: [PATCH 2455/4092] peppy said he prefers the version without pipes so ill remove the pipes --- global.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/global.json b/global.json index d6c2c37f77..5dcd5f425a 100644 --- a/global.json +++ b/global.json @@ -3,4 +3,5 @@ "version": "6.0.100", "rollForward": "latestFeature" } -} \ No newline at end of file +} + From 0b5cc8fb10c88fb12650762a8b4fef78f406bde8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jan 2024 14:01:03 +0900 Subject: [PATCH 2456/4092] Fix gameplay counter textures not being cached ahead of time Part of https://github.com/ppy/osu/issues/26535. --- osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs | 6 ++++++ osu.Game/Skinning/LegacySpriteText.cs | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs index a11f2f01cd..f16669f865 100644 --- a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs +++ b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs @@ -137,6 +137,12 @@ namespace osu.Game.Screens.Play.HUD Spacing = new Vector2(-2f, 0f); Font = new FontUsage(font_name, 1); glyphStore = new GlyphStore(font_name, textures, getLookup); + + // cache common lookups ahead of time. + foreach (char c in new[] { '.', '%', 'x' }) + glyphStore.Get(font_name, c); + for (int i = 0; i < 10; i++) + glyphStore.Get(font_name, (char)('0' + i)); } protected override TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) => base.CreateTextBuilder(glyphStore); diff --git a/osu.Game/Skinning/LegacySpriteText.cs b/osu.Game/Skinning/LegacySpriteText.cs index 581e7534e4..fdd8716d5a 100644 --- a/osu.Game/Skinning/LegacySpriteText.cs +++ b/osu.Game/Skinning/LegacySpriteText.cs @@ -50,6 +50,12 @@ namespace osu.Game.Skinning Spacing = new Vector2(-skin.GetFontOverlap(font), 0); glyphStore = new LegacyGlyphStore(fontPrefix, skin, MaxSizePerGlyph); + + // cache common lookups ahead of time. + foreach (char c in FixedWidthExcludeCharacters) + glyphStore.Get(fontPrefix, c); + for (int i = 0; i < 10; i++) + glyphStore.Get(fontPrefix, (char)('0' + i)); } protected override TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) => base.CreateTextBuilder(glyphStore); From cd20561843874df1eab24e179201eb1742e77520 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jan 2024 14:12:10 +0900 Subject: [PATCH 2457/4092] Adjust text slightly --- osu.Game/Localisation/GraphicsSettingsStrings.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Localisation/GraphicsSettingsStrings.cs b/osu.Game/Localisation/GraphicsSettingsStrings.cs index fc29352ae2..1d14b0a596 100644 --- a/osu.Game/Localisation/GraphicsSettingsStrings.cs +++ b/osu.Game/Localisation/GraphicsSettingsStrings.cs @@ -152,14 +152,13 @@ namespace osu.Game.Localisation /// /// "In order to change the renderer, the game will close. Please open it again." /// - public static LocalisableString ChangeRendererConfirmation => - new TranslatableString(getKey(@"change_renderer_configuration"), @"In order to change the renderer, the game will close. Please open it again."); + public static LocalisableString ChangeRendererConfirmation => new TranslatableString(getKey(@"change_renderer_configuration"), @"In order to change the renderer, the game will close. Please open it again."); /// - /// "Minimise on focus loss" + /// "Minimise osu! when switching to another app" /// - public static LocalisableString MinimiseOnFocusLoss => new TranslatableString(getKey(@"minimise_on_focus_loss"), @"Minimise on focus loss"); + public static LocalisableString MinimiseOnFocusLoss => new TranslatableString(getKey(@"minimise_on_focus_loss"), @"Minimise osu! when switching to another app"); - private static string getKey(string key) => $"{prefix}:{key}"; + private static string getKey(string key) => $@"{prefix}:{key}"; } } From a6c309b61a1edc109dfe0b3b2ff0dca57564628d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jan 2024 14:12:39 +0900 Subject: [PATCH 2458/4092] Add more keywords --- osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index adb572e245..71afec88d4 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -111,7 +111,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { LabelText = GraphicsSettingsStrings.MinimiseOnFocusLoss, Current = config.GetBindable(FrameworkSetting.MinimiseOnFocusLossInFullscreen), - Keywords = new[] { "alt-tab" }, + Keywords = new[] { "alt-tab", "minimize", "focus", "hide" }, }, safeAreaConsiderationsCheckbox = new SettingsCheckbox { From 6940579b9edb8c275de74c0f1ba4780f45577aaa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jan 2024 14:30:27 +0900 Subject: [PATCH 2459/4092] Remember multiplayer room filter mode As proposed in https://github.com/ppy/osu/discussions/26218. --- osu.Game/Configuration/OsuConfigManager.cs | 4 ++++ osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs | 6 +++++- .../OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs | 2 ++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 23686db1f8..c05831d043 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -17,6 +17,7 @@ using osu.Game.Localisation; using osu.Game.Overlays; using osu.Game.Overlays.Mods.Input; using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Filter; using osu.Game.Skinning; @@ -191,6 +192,8 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.EditorLimitedDistanceSnap, false); SetDefault(OsuSetting.EditorShowSpeedChanges, false); + SetDefault(OsuSetting.MultiplayerRoomFilter, RoomPermissionsFilter.All); + SetDefault(OsuSetting.LastProcessedMetadataId, -1); SetDefault(OsuSetting.ComboColourNormalisationAmount, 0.2f, 0f, 1f, 0.01f); @@ -423,5 +426,6 @@ namespace osu.Game.Configuration TouchDisableGameplayTaps, ModSelectTextSearchStartsActive, UserOnlineStatus, + MultiplayerRoomFilter } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index fc4a5357c6..6f5ff9c99c 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -4,8 +4,8 @@ #nullable disable using System; -using System.Diagnostics; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -19,6 +19,7 @@ using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Framework.Screens; using osu.Framework.Threading; +using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Input; @@ -77,6 +78,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge [CanBeNull] private LeasedBindable selectionLease; + [Resolved] + protected OsuConfigManager Config { get; private set; } + private readonly Bindable filter = new Bindable(new FilterCriteria()); private readonly IBindable operationInProgress = new Bindable(); private readonly IBindable isIdle = new BindableBool(); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs index 4478179726..a3a6fd2d8e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs @@ -12,6 +12,7 @@ using osu.Framework.Logging; using osu.Framework.Screens; using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; +using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.Multiplayer; @@ -51,6 +52,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer roomAccessTypeDropdown = new SlimEnumDropdown { RelativeSizeAxes = Axes.None, + Current = Config.GetBindable(OsuSetting.MultiplayerRoomFilter), Width = 160, }; From d8962ddff8e75a23114266db68de5f6e54eac264 Mon Sep 17 00:00:00 2001 From: Felipe Marins Date: Mon, 15 Jan 2024 03:22:52 -0300 Subject: [PATCH 2460/4092] Select all when pressing enter instead of every mod selection change --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 43f44d682d..8cb990757f 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -565,9 +565,6 @@ namespace osu.Game.Overlays.Mods .ToArray(); SelectedMods.Value = ComputeNewModsFromSelection(SelectedMods.Value, candidateSelection); - - if (SearchTextBox.HasFocus) - SearchTextBox.SelectAll(); } #region Transition handling @@ -724,6 +721,8 @@ namespace osu.Game.Overlays.Mods if (firstMod is not null) firstMod.Active.Value = !firstMod.Active.Value; + SearchTextBox.SelectAll(); + return true; } } From 2d5a39b234b85f9fe235bb746f50185c0e336b55 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jan 2024 16:01:34 +0900 Subject: [PATCH 2461/4092] Add failing test coverage of duplicates in judgement counter display --- .../Visual/Gameplay/TestSceneJudgementCounter.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneJudgementCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneJudgementCounter.cs index dae6dc7b4b..787c48fb26 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneJudgementCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneJudgementCounter.cs @@ -147,6 +147,16 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("Assert max judgement hidden", () => counterDisplay.CounterFlow.ChildrenOfType().First().Alpha == 0); } + [Test] + public void TestNoDuplicates() + { + AddStep("create counter", () => Child = counterDisplay = new TestJudgementCounterDisplay()); + AddStep("Show all judgements", () => counterDisplay.Mode.Value = JudgementCounterDisplay.DisplayMode.All); + AddAssert("Check no duplicates", + () => counterDisplay.CounterFlow.ChildrenOfType().Count(), + () => Is.EqualTo(counterDisplay.CounterFlow.ChildrenOfType().Select(c => c.ResultName.Text).Distinct().Count())); + } + [Test] public void TestCycleDisplayModes() { From e6453853c256245a2d600904b9df29a963268635 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jan 2024 15:54:00 +0900 Subject: [PATCH 2462/4092] De-dupe displayed hits in judgement counter --- .../Gameplay/TestSceneJudgementCounter.cs | 2 +- .../JudgementCountController.cs | 36 ++++++++++++++----- .../HUD/JudgementCounter/JudgementCounter.cs | 7 ++-- .../JudgementCounterDisplay.cs | 9 +++-- 4 files changed, 39 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneJudgementCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneJudgementCounter.cs index 787c48fb26..894f08e5b2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneJudgementCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneJudgementCounter.cs @@ -173,7 +173,7 @@ namespace osu.Game.Tests.Visual.Gameplay private int hiddenCount() { - var num = counterDisplay.CounterFlow.Children.First(child => child.Result.Type == HitResult.LargeTickHit); + var num = counterDisplay.CounterFlow.Children.First(child => child.Result.Types.Contains(HitResult.LargeTickHit)); return num.Result.ResultCount.Value; } diff --git a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCountController.cs b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCountController.cs index 43c2ae442a..8134c97bac 100644 --- a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCountController.cs +++ b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCountController.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Rulesets; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; @@ -21,18 +22,30 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter [Resolved] private ScoreProcessor scoreProcessor { get; set; } = null!; - public List Results = new List(); + private readonly Dictionary results = new Dictionary(); + + public IEnumerable Counters => counters; + + private readonly List counters = new List(); [BackgroundDependencyLoader] private void load(IBindable ruleset) { - foreach (var result in ruleset.Value.CreateInstance().GetHitResults()) + // Due to weirdness in judgements, some results have the same name and should be aggregated for display purposes. + // There's only one case of this right now ("slider end"). + foreach (var group in ruleset.Value.CreateInstance().GetHitResults().GroupBy(r => r.displayName)) { - Results.Add(new JudgementCount + var judgementCount = new JudgementCount { - Type = result.result, + DisplayName = group.Key, + Types = group.Select(r => r.result).ToArray(), ResultCount = new BindableInt() - }); + }; + + counters.Add(judgementCount); + + foreach (var r in group) + results[r.result] = judgementCount; } } @@ -46,13 +59,20 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter private void updateCount(JudgementResult judgement, bool revert) { - foreach (JudgementCount result in Results.Where(result => result.Type == judgement.Type)) - result.ResultCount.Value = revert ? result.ResultCount.Value - 1 : result.ResultCount.Value + 1; + if (!results.TryGetValue(judgement.Type, out var count)) + return; + + if (revert) + count.ResultCount.Value--; + else + count.ResultCount.Value++; } public struct JudgementCount { - public HitResult Type { get; set; } + public LocalisableString DisplayName { get; set; } + + public HitResult[] Types { get; set; } public BindableInt ResultCount { get; set; } } diff --git a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounter.cs b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounter.cs index 6c417faac2..45ed8d749b 100644 --- a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounter.cs +++ b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounter.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 System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -44,14 +45,14 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter { Alpha = 0, Font = OsuFont.Numeric.With(size: 8), - Text = ruleset.Value.CreateInstance().GetDisplayNameForHitResult(Result.Type) + Text = Result.DisplayName, } } }; - var result = Result.Type; + var result = Result.Types.First(); - Colour = result.IsBasic() ? colours.ForHitResult(Result.Type) : !result.IsBonus() ? colours.PurpleLight : colours.PurpleLighter; + Colour = result.IsBasic() ? colours.ForHitResult(result) : !result.IsBonus() ? colours.PurpleLight : colours.PurpleLighter; } protected override void LoadComplete() diff --git a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounterDisplay.cs b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounterDisplay.cs index 128897ddde..326be55222 100644 --- a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounterDisplay.cs +++ b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounterDisplay.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -49,7 +50,7 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter AutoSizeAxes = Axes.Both }; - foreach (var result in judgementCountController.Results) + foreach (var result in judgementCountController.Counters) CounterFlow.Add(createCounter(result)); } @@ -88,7 +89,9 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter if (index == 0 && !ShowMaxJudgement.Value) return false; - if (counter.Result.Type.IsBasic()) + var hitResult = counter.Result.Types.First(); + + if (hitResult.IsBasic()) return true; switch (Mode.Value) @@ -97,7 +100,7 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter return false; case DisplayMode.Normal: - return !counter.Result.Type.IsBonus(); + return !hitResult.IsBonus(); case DisplayMode.All: return true; From 2b45a9b7c629fd9f8df01fc35db37da7f8b511c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jan 2024 17:10:16 +0900 Subject: [PATCH 2463/4092] Add failing test coverage showing collection dropdown crash --- .../Visual/SongSelect/TestSceneFilterControl.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 94c6130f15..58183acb93 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -76,6 +77,20 @@ namespace osu.Game.Tests.Visual.SongSelect assertCollectionDropdownContains("2"); } + [Test] + public void TestCollectionsCleared() + { + AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1")))); + AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "2")))); + AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "3")))); + + AddAssert("check count 5", () => control.ChildrenOfType().Single().ChildrenOfType().Count(), () => Is.EqualTo(5)); + + AddStep("delete all collections", () => writeAndRefresh(r => r.RemoveAll())); + + AddAssert("check count 2", () => control.ChildrenOfType().Single().ChildrenOfType().Count(), () => Is.EqualTo(2)); + } + [Test] public void TestCollectionRemovedFromDropdown() { From 0a522d260becbbcc2e7d2835b763208ea76a6d4a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jan 2024 17:10:30 +0900 Subject: [PATCH 2464/4092] Fix collection dropdown crashing when all collections are deleted at once --- osu.Game/Collections/CollectionDropdown.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Collections/CollectionDropdown.cs b/osu.Game/Collections/CollectionDropdown.cs index 8d83ed3ec9..249a0c35e7 100644 --- a/osu.Game/Collections/CollectionDropdown.cs +++ b/osu.Game/Collections/CollectionDropdown.cs @@ -74,7 +74,7 @@ namespace osu.Game.Collections } else { - foreach (int i in changes.DeletedIndices) + foreach (int i in changes.DeletedIndices.OrderByDescending(i => i)) filters.RemoveAt(i + 1); foreach (int i in changes.InsertedIndices) From 52f8348ee31d1cecdc5a514565811588aaf31073 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jan 2024 17:48:37 +0900 Subject: [PATCH 2465/4092] Fade hold-for-menu button out completely on non-touch devices --- .../Screens/Play/HUD/HoldForMenuButton.cs | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 0921a9f18a..1cf3d25dad 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -16,6 +16,7 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Threading; using osu.Framework.Utils; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -44,6 +45,8 @@ namespace osu.Game.Screens.Play.HUD Direction = FillDirection.Horizontal; Spacing = new Vector2(20, 0); Margin = new MarginPadding(10); + + AlwaysPresent = true; } [BackgroundDependencyLoader(true)] @@ -66,9 +69,15 @@ namespace osu.Game.Screens.Play.HUD Action = () => Action(), } }; + AutoSizeAxes = Axes.Both; } + [Resolved] + private SessionStatics sessionStatics { get; set; } + + private Bindable touchActive; + protected override void LoadComplete() { button.HoldActivationDelay.BindValueChanged(v => @@ -78,7 +87,20 @@ namespace osu.Game.Screens.Play.HUD : "press for menu"; }, true); - text.FadeInFromZero(500, Easing.OutQuint).Delay(1500).FadeOut(500, Easing.OutQuint); + touchActive = sessionStatics.GetBindable(Static.TouchInputActive); + + if (touchActive.Value) + { + Alpha = 1f; + text.FadeInFromZero(500, Easing.OutQuint) + .Delay(1500) + .FadeOut(500, Easing.OutQuint); + } + else + { + Alpha = 0; + text.Alpha = 0f; + } base.LoadComplete(); } @@ -99,9 +121,11 @@ namespace osu.Game.Screens.Play.HUD Alpha = 1; else { + float minAlpha = touchActive.Value ? .08f : 0; + Alpha = Interpolation.ValueAt( Math.Clamp(Clock.ElapsedFrameTime, 0, 200), - Alpha, Math.Clamp(1 - positionalAdjust, 0.04f, 1), 0, 200, Easing.OutQuint); + Alpha, Math.Clamp(1 - positionalAdjust, minAlpha, 1), 0, 200, Easing.OutQuint); } } From 2a2a4c416eaf11ac2212ebbc88c19567e6ec22b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jan 2024 18:17:49 +0900 Subject: [PATCH 2466/4092] Only display offset toast when in local gameplay --- osu.Game/Screens/Play/Player.cs | 6 ------ osu.Game/Screens/Play/SubmittingPlayer.cs | 10 ++++++++++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index b87306b9a2..96530634fd 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -461,12 +461,6 @@ namespace osu.Game.Screens.Play OnRetry = () => Restart(), OnQuit = () => PerformExit(true), }, - new GameplayOffsetControl - { - Margin = new MarginPadding(20), - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - } }, }; diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 04abb6162f..171ceea84f 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Beatmaps; @@ -59,6 +60,15 @@ namespace osu.Game.Screens.Play } AddInternal(new PlayerTouchInputDetector()); + + // We probably want to move this display to something more global. + // Probably using the OSD somehow. + AddInternal(new GameplayOffsetControl + { + Margin = new MarginPadding(20), + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + }); } protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new MasterGameplayClockContainer(beatmap, gameplayStart) From 0aa8a20d57a9d50bc39d7ecf2d847314a51d317e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jan 2024 18:34:32 +0900 Subject: [PATCH 2467/4092] Fix regression in interaction when panels are not selectable --- .../Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 823f6ea308..800c73cceb 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -365,7 +365,7 @@ namespace osu.Game.Screens.OnlinePlay AutoSizeAxes = Axes.Both, Margin = new MarginPadding { Left = 8, Right = 8 }, }, - mainFillFlow = new MainFlow(() => SelectedItem.Value == Model) + mainFillFlow = new MainFlow(() => SelectedItem.Value == Model || !AllowSelection) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -671,13 +671,13 @@ namespace osu.Game.Screens.OnlinePlay public partial class MainFlow : FillFlowContainer { - private readonly Func isSelected; + private readonly Func allowInteraction; - public override bool PropagatePositionalInputSubTree => isSelected(); + public override bool PropagatePositionalInputSubTree => allowInteraction(); - public MainFlow(Func isSelected) + public MainFlow(Func allowInteraction) { - this.isSelected = isSelected; + this.allowInteraction = allowInteraction; } } } From 8e32780888cab9e3e035a8b9a3233c10e066f47e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jan 2024 19:21:19 +0900 Subject: [PATCH 2468/4092] Fix background dim occasionally getting in a bad state when exiting gameplay --- .../Screens/Backgrounds/BackgroundScreenBeatmap.cs | 2 +- osu.Game/Screens/Play/Player.cs | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 85ea881006..185e2cab99 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Backgrounds /// public readonly Bindable DimWhenUserSettingsIgnored = new Bindable(); - internal readonly IBindable IsBreakTime = new Bindable(); + internal readonly Bindable IsBreakTime = new Bindable(); private readonly DimmableBackground dimmable; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index b87306b9a2..f0f0a58368 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1078,7 +1078,7 @@ namespace osu.Game.Screens.Play b.FadeColour(Color4.White, 250); // bind component bindables. - b.IsBreakTime.BindTo(breakTracker.IsBreakTime); + ((IBindable)b.IsBreakTime).BindTo(breakTracker.IsBreakTime); b.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); @@ -1238,7 +1238,13 @@ namespace osu.Game.Screens.Play if (this.IsCurrentScreen()) { - ApplyToBackground(b => b.IgnoreUserSettings.Value = true); + ApplyToBackground(b => + { + b.IgnoreUserSettings.Value = true; + + b.IsBreakTime.UnbindFrom(breakTracker.IsBreakTime); + b.IsBreakTime.Value = true; + }); storyboardReplacesBackground.Value = false; } } From 494c1be6553ed9b891b8b05dc37bbe6d50d3f3a7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 15 Jan 2024 13:30:34 +0300 Subject: [PATCH 2469/4092] Fix blatant error in `TestSceneModAccuracyChallenge` --- osu.Game.Tests/Visual/Mods/TestSceneModAccuracyChallenge.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Mods/TestSceneModAccuracyChallenge.cs b/osu.Game.Tests/Visual/Mods/TestSceneModAccuracyChallenge.cs index 6bdb9132e1..c5e56c6453 100644 --- a/osu.Game.Tests/Visual/Mods/TestSceneModAccuracyChallenge.cs +++ b/osu.Game.Tests/Visual/Mods/TestSceneModAccuracyChallenge.cs @@ -8,6 +8,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; using osuTK; @@ -29,7 +30,7 @@ namespace osu.Game.Tests.Visual.Mods public void TestMaximumAchievableAccuracy() => CreateModTest(new ModTestData { - Mod = new ModAccuracyChallenge + Mod = new OsuModAccuracyChallenge { MinimumAccuracy = { Value = 0.6 } }, @@ -49,7 +50,7 @@ namespace osu.Game.Tests.Visual.Mods public void TestStandardAccuracy() => CreateModTest(new ModTestData { - Mod = new ModAccuracyChallenge + Mod = new OsuModAccuracyChallenge { MinimumAccuracy = { Value = 0.6 }, AccuracyJudgeMode = { Value = ModAccuracyChallenge.AccuracyMode.Standard } From e3989c854d39ec82d1ea27ada8f70a9dea159633 Mon Sep 17 00:00:00 2001 From: StanR Date: Mon, 15 Jan 2024 16:57:22 +0600 Subject: [PATCH 2470/4092] Change `LoginPanel` to use `LocalUser.Status` for the dropdown --- osu.Game/Overlays/Login/LoginPanel.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Login/LoginPanel.cs b/osu.Game/Overlays/Login/LoginPanel.cs index 513088ba0c..7db3644de6 100644 --- a/osu.Game/Overlays/Login/LoginPanel.cs +++ b/osu.Game/Overlays/Login/LoginPanel.cs @@ -139,7 +139,7 @@ namespace osu.Game.Overlays.Login }, }; - panel.Status.BindValueChanged(_ => updateDropdownCurrent(), true); + api.LocalUser.Value.Status.BindValueChanged(e => updateDropdownCurrent(e.NewValue), true); dropdown.Current.BindValueChanged(action => { @@ -172,9 +172,9 @@ namespace osu.Game.Overlays.Login ScheduleAfterChildren(() => GetContainingInputManager()?.ChangeFocus(form)); }); - private void updateDropdownCurrent() + private void updateDropdownCurrent(UserStatus? status) { - switch (panel.Status.Value) + switch (status) { case UserStatus.Online: dropdown.Current.Value = UserAction.Online; From 13517869f6bb373fbe6b44df5728d31bc696ef3d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 15 Jan 2024 14:03:23 +0300 Subject: [PATCH 2471/4092] Apply scale transitions in resume overlay cursor to local container --- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 24 +++++++++++--------- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 20 ++++++++++++---- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index 18351c20ce..710ca9ace7 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -57,17 +57,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor [BackgroundDependencyLoader] private void load() { - InternalChild = cursorScaleContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Child = cursorSprite = new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.Cursor), _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling) - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - } - }; + InternalChild = CreateCursorContent(); userCursorScale = config.GetBindable(OsuSetting.GameplayCursorSize); userCursorScale.ValueChanged += _ => cursorScale.Value = CalculateCursorScale(); @@ -84,6 +74,18 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor cursorScale.Value = CalculateCursorScale(); } + protected virtual Drawable CreateCursorContent() => cursorScaleContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Child = cursorSprite = new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.Cursor), _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling) + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + }, + }; + protected virtual float CalculateCursorScale() { float scale = userCursorScale.Value; diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index f5e83f46f2..8a84fe14e5 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -65,17 +65,24 @@ namespace osu.Game.Rulesets.Osu.UI public override bool HandlePositionalInput => true; public Action ResumeRequested; + private Container scaleTransitionContainer; public OsuClickToResumeCursor() { RelativePositionAxes = Axes.Both; } - protected override float CalculateCursorScale() + protected override Container CreateCursorContent() => scaleTransitionContainer = new Container { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Child = base.CreateCursorContent(), + }; + + protected override float CalculateCursorScale() => // Force minimum cursor size so it's easily clickable - return Math.Max(1f, base.CalculateCursorScale()); - } + Math.Max(1f, base.CalculateCursorScale()); protected override bool OnHover(HoverEvent e) { @@ -98,7 +105,7 @@ namespace osu.Game.Rulesets.Osu.UI if (!IsHovered) return false; - this.ScaleTo(2, TRANSITION_TIME, Easing.OutQuint); + scaleTransitionContainer.ScaleTo(2, TRANSITION_TIME, Easing.OutQuint); ResumeRequested?.Invoke(); return true; @@ -114,7 +121,10 @@ namespace osu.Game.Rulesets.Osu.UI public void Appear() => Schedule(() => { updateColour(); - this.ScaleTo(4).Then().ScaleTo(1, 1000, Easing.OutQuint); + + // importantly, we perform the scale transition on an underlying container rather than the whole cursor + // to prevent attempts of abuse by the scale change in the cursor's hitbox (see: https://github.com/ppy/osu/issues/26477). + scaleTransitionContainer.ScaleTo(4).Then().ScaleTo(1, 1000, Easing.OutQuint); }); private void updateColour() From 0a02050a8523c6c873b4fbf582f072d649efaec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 15 Jan 2024 12:12:27 +0100 Subject: [PATCH 2472/4092] Fix test crashing at the end in visual test browser --- .../Visual/Multiplayer/TestSceneMultiplayer.cs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 201fac37c8..747805edc8 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -1033,12 +1033,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddStep("join other user and make host", () => - { - multiplayerClient.AddUser(new APIUser { Id = 1234 }); - multiplayerClient.TransferHost(1234); - }); - AddStep("select hidden", () => multiplayerClient.ChangeUserMods(new[] { new APIMod { Acronym = "HD" } })); AddStep("make user ready", () => multiplayerClient.ChangeState(MultiplayerUserState.Ready)); AddStep("press edit on second item", () => this.ChildrenOfType().Single(i => i.Item.RulesetID == 1) @@ -1047,14 +1041,9 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for song select", () => InputManager.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true); AddAssert("ruleset is taiko", () => Ruleset.Value.OnlineID == 1); - AddStep("start match by other user", () => - { - multiplayerClient.ChangeUserState(1234, MultiplayerUserState.Ready); - multiplayerClient.StartMatch().WaitSafely(); - }); + AddStep("start match", () => multiplayerClient.StartMatch().WaitSafely()); AddUntilStep("wait for loading", () => multiplayerClient.ClientRoom?.State == MultiplayerRoomState.WaitingForLoad); - AddStep("set player loaded", () => multiplayerClient.ChangeUserState(1234, MultiplayerUserState.Loaded)); AddUntilStep("wait for gameplay to start", () => multiplayerClient.ClientRoom?.State == MultiplayerRoomState.Playing); AddAssert("hidden is selected", () => SelectedMods.Value, () => Has.One.TypeOf(typeof(OsuModHidden))); } From 96c472b870790825e6acbc692a425612f97efb64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 15 Jan 2024 12:33:25 +0100 Subject: [PATCH 2473/4092] Remove unused using directive --- osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 58183acb93..a639d50eee 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; From 86382f4408baa4bc292b2c510a9db49a8af0f6c9 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 15 Jan 2024 12:49:40 +0100 Subject: [PATCH 2474/4092] Clarify comment --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 2f012e33da..f5840248fb 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -250,7 +250,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.UpdateAfterChildren(); - // It's important that this is done in UpdateAfterChildren so that the SliderBody has a chance to update its Size and PathOffset on Update. + // During slider path editing, the PlaySliderBody is scheduled to refresh once in the Update phase. + // It is crucial to perform the code below in UpdateAfterChildren. This ensures that the SliderBody has the opportunity + // to update its Size and PathOffset beforehand, ensuring correct placement. + double completionProgress = Math.Clamp((Time.Current - HitObject.StartTime) / HitObject.Duration, 0, 1); Ball.UpdateProgress(completionProgress); From 6b844ed8b64db49554031bd397eaa310d4b54601 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jan 2024 20:29:08 +0900 Subject: [PATCH 2475/4092] Split out judgement pooling concepts from `OsuPlayfield` for reuse --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 64 +++++--------------- osu.Game/Rulesets/UI/JudgementPooler.cs | 77 ++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 50 deletions(-) create mode 100644 osu.Game/Rulesets/UI/JudgementPooler.cs diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index c94057cf6d..411a02c5af 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -4,13 +4,11 @@ #nullable disable using System; -using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Pooling; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; @@ -35,6 +33,8 @@ namespace osu.Game.Rulesets.Osu.UI private readonly ProxyContainer spinnerProxies; private readonly JudgementContainer judgementLayer; + private readonly JudgementPooler judgementPooler; + public SmokeContainer Smoke { get; } public FollowPointRenderer FollowPoints { get; } @@ -42,8 +42,6 @@ namespace osu.Game.Rulesets.Osu.UI protected override GameplayCursorContainer CreateCursor() => new OsuCursorContainer(); - private readonly IDictionary> poolDictionary = new Dictionary>(); - private readonly Container judgementAboveHitObjectLayer; public OsuPlayfield() @@ -65,24 +63,15 @@ namespace osu.Game.Rulesets.Osu.UI HitPolicy = new StartTimeOrderedHitPolicy(); - foreach (var result in Enum.GetValues().Where(r => - { - switch (r) - { - case HitResult.Great: - case HitResult.Ok: - case HitResult.Meh: - case HitResult.Miss: - case HitResult.LargeTickMiss: - case HitResult.IgnoreMiss: - return true; - } - - return false; - })) - poolDictionary.Add(result, new DrawableJudgementPool(result, onJudgementLoaded)); - - AddRangeInternal(poolDictionary.Values); + AddInternal(judgementPooler = new JudgementPooler(new[] + { + HitResult.Great, + HitResult.Ok, + HitResult.Meh, + HitResult.Miss, + HitResult.LargeTickMiss, + HitResult.IgnoreMiss, + }, onJudgementLoaded)); NewResult += onNewResult; } @@ -182,10 +171,10 @@ namespace osu.Game.Rulesets.Osu.UI if (!judgedObject.DisplayResult || !DisplayJudgements.Value) return; - if (!poolDictionary.TryGetValue(result.Type, out var pool)) - return; + var explosion = judgementPooler.Get(result.Type, doj => doj.Apply(result, judgedObject)); - DrawableOsuJudgement explosion = pool.Get(doj => doj.Apply(result, judgedObject)); + if (explosion == null) + return; judgementLayer.Add(explosion); @@ -201,31 +190,6 @@ namespace osu.Game.Rulesets.Osu.UI public void Add(Drawable proxy) => AddInternal(proxy); } - private partial class DrawableJudgementPool : DrawablePool - { - private readonly HitResult result; - private readonly Action onLoaded; - - public DrawableJudgementPool(HitResult result, Action onLoaded) - : base(20) - { - this.result = result; - this.onLoaded = onLoaded; - } - - protected override DrawableOsuJudgement CreateNewDrawable() - { - var judgement = base.CreateNewDrawable(); - - // just a placeholder to initialise the correct drawable hierarchy for this pool. - judgement.Apply(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null); - - onLoaded?.Invoke(judgement); - - return judgement; - } - } - private class OsuHitObjectLifetimeEntry : HitObjectLifetimeEntry { public OsuHitObjectLifetimeEntry(HitObject hitObject) diff --git a/osu.Game/Rulesets/UI/JudgementPooler.cs b/osu.Game/Rulesets/UI/JudgementPooler.cs new file mode 100644 index 0000000000..efec760f15 --- /dev/null +++ b/osu.Game/Rulesets/UI/JudgementPooler.cs @@ -0,0 +1,77 @@ +// 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.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Pooling; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.UI +{ + /// + /// Handles the task of preparing poolable drawable judgements for gameplay usage. + /// + /// The drawable judgement type. + public partial class JudgementPooler : CompositeComponent + where T : DrawableJudgement, new() + { + private readonly IDictionary> poolDictionary = new Dictionary>(); + + private readonly IEnumerable usableHitResults; + private readonly Action? onJudgementInitialLoad; + + public JudgementPooler(IEnumerable usableHitResults, Action? onJudgementInitialLoad = null) + { + this.usableHitResults = usableHitResults; + this.onJudgementInitialLoad = onJudgementInitialLoad; + } + + public T? Get(HitResult result, Action? setupAction) + { + if (!poolDictionary.TryGetValue(result, out var pool)) + return null; + + return pool.Get(setupAction); + } + + [BackgroundDependencyLoader] + private void load() + { + foreach (HitResult result in usableHitResults) + { + var pool = new DrawableJudgementPool(result, onJudgementInitialLoad); + poolDictionary.Add(result, pool); + AddInternal(pool); + } + } + + private partial class DrawableJudgementPool : DrawablePool + { + private readonly HitResult result; + private readonly Action? onLoaded; + + public DrawableJudgementPool(HitResult result, Action? onLoaded) + : base(20) + { + this.result = result; + this.onLoaded = onLoaded; + } + + protected override T CreateNewDrawable() + { + var judgement = base.CreateNewDrawable(); + + // just a placeholder to initialise the correct drawable hierarchy for this pool. + judgement.Apply(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null); + + onLoaded?.Invoke(judgement); + + return judgement; + } + } + } +} From e9812fac7ae09d4f33302bce80e20a353de9f8e2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jan 2024 20:37:33 +0900 Subject: [PATCH 2476/4092] Fix osu!mania judgments not being pooled correctly --- osu.Game.Rulesets.Mania/UI/Stage.cs | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs index fa9af6d157..36286940a8 100644 --- a/osu.Game.Rulesets.Mania/UI/Stage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -1,22 +1,23 @@ // 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 osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Pooling; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Mania.Skinning; using osu.Game.Rulesets.Mania.UI.Components; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; @@ -40,7 +41,7 @@ namespace osu.Game.Rulesets.Mania.UI private readonly ColumnFlow columnFlow; private readonly JudgementContainer judgements; - private readonly DrawablePool judgementPool; + private readonly JudgementPooler judgementPooler; private readonly Drawable barLineContainer; @@ -48,6 +49,8 @@ namespace osu.Game.Rulesets.Mania.UI private readonly int firstColumnIndex; + private ISkinSource currentSkin = null!; + public Stage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction) { this.firstColumnIndex = firstColumnIndex; @@ -65,7 +68,6 @@ namespace osu.Game.Rulesets.Mania.UI InternalChildren = new Drawable[] { - judgementPool = new DrawablePool(2), new Container { Anchor = Anchor.TopCentre, @@ -104,7 +106,7 @@ namespace osu.Game.Rulesets.Mania.UI { RelativeSizeAxes = Axes.Y, }, - new SkinnableDrawable(new ManiaSkinComponentLookup(ManiaSkinComponents.StageForeground), _ => null) + new SkinnableDrawable(new ManiaSkinComponentLookup(ManiaSkinComponents.StageForeground)) { RelativeSizeAxes = Axes.Both }, @@ -137,11 +139,13 @@ namespace osu.Game.Rulesets.Mania.UI AddNested(column); } + var hitWindows = new ManiaHitWindows(); + + AddInternal(judgementPooler = new JudgementPooler(Enum.GetValues().Where(r => hitWindows.IsHitResultAllowed(r)))); + RegisterPool(50, 200); } - private ISkinSource currentSkin; - [BackgroundDependencyLoader] private void load(ISkinSource skin) { @@ -170,7 +174,7 @@ namespace osu.Game.Rulesets.Mania.UI base.Dispose(isDisposing); - if (currentSkin != null) + if (currentSkin.IsNotNull()) currentSkin.SourceChanged -= onSkinChanged; } @@ -196,13 +200,13 @@ namespace osu.Game.Rulesets.Mania.UI return; judgements.Clear(false); - judgements.Add(judgementPool.Get(j => + judgements.Add(judgementPooler.Get(result.Type, j => { j.Apply(result, judgedObject); j.Anchor = Anchor.Centre; j.Origin = Anchor.Centre; - })); + })!); } protected override void Update() From 0fbca59523cea2bd7036581c5445c7173ff8123f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jan 2024 20:49:00 +0900 Subject: [PATCH 2477/4092] Fix osu!taiko judgments not being pooled correctly They weren't being initialised correctly on initial pool. --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 49 ++++++++++---------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 31f8171290..7e3ed7a4d4 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -10,7 +8,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Primitives; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; @@ -42,29 +39,29 @@ namespace osu.Game.Rulesets.Taiko.UI public Container UnderlayElements { get; private set; } = null!; - private Container hitExplosionContainer; - private Container kiaiExplosionContainer; - private JudgementContainer judgementContainer; - private ScrollingHitObjectContainer drumRollHitContainer; - internal Drawable HitTarget; - private SkinnableDrawable mascot; + private Container hitExplosionContainer = null!; + private Container kiaiExplosionContainer = null!; + private JudgementContainer judgementContainer = null!; + private ScrollingHitObjectContainer drumRollHitContainer = null!; + internal Drawable HitTarget = null!; + private SkinnableDrawable mascot = null!; - private readonly IDictionary> judgementPools = new Dictionary>(); + private JudgementPooler judgementPooler = null!; private readonly IDictionary explosionPools = new Dictionary(); - private ProxyContainer topLevelHitContainer; - private InputDrum inputDrum; - private Container rightArea; + private ProxyContainer topLevelHitContainer = null!; + private InputDrum inputDrum = null!; + private Container rightArea = null!; /// /// is purposefully not called on this to prevent i.e. being able to interact /// with bar lines in the editor. /// - private BarLinePlayfield barLinePlayfield; + private BarLinePlayfield barLinePlayfield = null!; - private Container barLineContent; - private Container hitObjectContent; - private Container overlayContent; + private Container barLineContent = null!; + private Container hitObjectContent = null!; + private Container overlayContent = null!; [BackgroundDependencyLoader] private void load(OsuColour colours) @@ -202,13 +199,12 @@ namespace osu.Game.Rulesets.Taiko.UI var hitWindows = new TaikoHitWindows(); - foreach (var result in Enum.GetValues().Where(r => hitWindows.IsHitResultAllowed(r))) - { - judgementPools.Add(result, new DrawablePool(15)); - explosionPools.Add(result, new HitExplosionPool(result)); - } + HitResult[] usableHitResults = Enum.GetValues().Where(r => hitWindows.IsHitResultAllowed(r)).ToArray(); - AddRangeInternal(judgementPools.Values); + AddInternal(judgementPooler = new JudgementPooler(usableHitResults)); + + foreach (var result in usableHitResults) + explosionPools.Add(result, new HitExplosionPool(result)); AddRangeInternal(explosionPools.Values); } @@ -339,7 +335,12 @@ namespace osu.Game.Rulesets.Taiko.UI if (!result.Type.IsScorable()) break; - judgementContainer.Add(judgementPools[result.Type].Get(j => j.Apply(result, judgedObject))); + var judgement = judgementPooler.Get(result.Type, j => j.Apply(result, judgedObject)); + + if (judgement == null) + return; + + judgementContainer.Add(judgement); var type = (judgedObject.HitObject as Hit)?.Type ?? HitType.Centre; addExplosion(judgedObject, result.Type, type); From 96ffe8e737e327517e4d585d1a5dd8dff754032b Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 15 Jan 2024 12:51:08 +0100 Subject: [PATCH 2478/4092] change wording --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index f5840248fb..ed4c02a4a9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -250,7 +250,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.UpdateAfterChildren(); - // During slider path editing, the PlaySliderBody is scheduled to refresh once in the Update phase. + // During slider path editing, the PlaySliderBody is scheduled to refresh once on Update. // It is crucial to perform the code below in UpdateAfterChildren. This ensures that the SliderBody has the opportunity // to update its Size and PathOffset beforehand, ensuring correct placement. From 9bc9db9138ee6d8e103c1f2a8dd87b671cb6a5b8 Mon Sep 17 00:00:00 2001 From: wooster0 Date: Mon, 15 Jan 2024 20:58:43 +0900 Subject: [PATCH 2479/4092] simplify the code even more --- osu.Game/Screens/Ranking/ResultsScreen.cs | 2 +- osu.Game/Screens/Ranking/ScorePanelList.cs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 697d62ad6e..da1609c2c4 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -248,7 +248,7 @@ namespace osu.Game.Screens.Ranking lastFetchCompleted = true; - if (ScorePanelList.IsEmpty) + if (!scores.Any()) { // This can happen if for example a beatmap that is part of a playlist hasn't been played yet. VerticalScrollContent.Add(new MessagePlaceholder(LeaderboardStrings.NoRecordsYet)); diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 95c90e35a0..b75f3d86ff 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -49,8 +49,6 @@ namespace osu.Game.Screens.Ranking public bool AllPanelsVisible => flow.All(p => p.IsPresent); - public bool IsEmpty => flow.Count == 0; - /// /// The current scroll position. /// From 1d7b63e204bfac6a5350b3221b3c951f2e4e5510 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 15 Jan 2024 14:57:17 +0300 Subject: [PATCH 2480/4092] Move checking logic inside `ModUtils` and somewhat optimise --- osu.Game/Screens/Play/Player.cs | 3 ++- osu.Game/Utils/ModUtils.cs | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ffd585148b..5f52f59ad2 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -38,6 +38,7 @@ using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Ranking; using osu.Game.Skinning; using osu.Game.Users; +using osu.Game.Utils; using osuTK.Graphics; namespace osu.Game.Screens.Play @@ -213,7 +214,7 @@ namespace osu.Game.Screens.Play if (playableBeatmap == null) return; - if (gameplayMods.Any(m => ruleset.AllMods.All(rulesetMod => m.GetType() != rulesetMod.GetType()))) + if (!ModUtils.CheckModsBelongToRuleset(ruleset, gameplayMods)) { Logger.Log($@"Gameplay was started with a mod belonging to a ruleset different than '{ruleset.Description}'.", level: LogLevel.Important); return; diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index 252579a186..827b199b16 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -229,6 +229,38 @@ namespace osu.Game.Utils return proposedWereValid; } + /// + /// Verifies all mods provided belong to the given ruleset. + /// + /// The ruleset to check the proposed mods against. + /// The mods proposed for checking. + /// Whether all belong to the given . + public static bool CheckModsBelongToRuleset(Ruleset ruleset, IEnumerable proposedMods) + { + var rulesetModsTypes = ruleset.AllMods.Select(m => m.GetType()).ToList(); + + foreach (var proposedMod in proposedMods) + { + bool found = false; + + var proposedModType = proposedMod.GetType(); + + foreach (var rulesetModType in rulesetModsTypes) + { + if (rulesetModType == proposedModType) + { + found = true; + break; + } + } + + if (!found) + return true; + } + + return true; + } + /// /// Given a value of a score multiplier, returns a string version with special handling for a value near 1.00x. /// From d346dd065027d08d374d76f045302c356d05fb9a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 15 Jan 2024 15:01:13 +0300 Subject: [PATCH 2481/4092] Fix `TestModReinstantiation` failing due to custom mod being used --- osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs | 8 +------- osu.Game/Utils/ModUtils.cs | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index dbd1ce1f6e..f97372e9b6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -13,7 +13,6 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Localisation; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Framework.Utils; @@ -487,13 +486,8 @@ namespace osu.Game.Tests.Visual.Gameplay } } - private class TestMod : Mod, IApplicableToScoreProcessor + private class TestMod : OsuModDoubleTime, IApplicableToScoreProcessor { - public override string Name => string.Empty; - public override string Acronym => string.Empty; - public override double ScoreMultiplier => 1; - public override LocalisableString Description => string.Empty; - public bool Applied { get; private set; } public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index 827b199b16..7a9f93e06f 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -247,7 +247,7 @@ namespace osu.Game.Utils foreach (var rulesetModType in rulesetModsTypes) { - if (rulesetModType == proposedModType) + if (rulesetModType.IsAssignableFrom(proposedModType)) { found = true; break; From 399fc8195a7441c250b528ffb6b8953aedba1aa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 15 Jan 2024 13:22:22 +0100 Subject: [PATCH 2482/4092] Fix cursor ripple pool not loading pooled drawables ahead of time Increment the counter over at https://github.com/ppy/osu-framework/pull/6136. --- osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs index 52486b701a..4cb91aa103 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs @@ -33,6 +33,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private void load(OsuRulesetConfigManager? rulesetConfig) { rulesetConfig?.BindWith(OsuRulesetSetting.ShowCursorRipples, showRipples); + + AddInternal(ripplePool); } public bool OnPressed(KeyBindingPressEvent e) From c5351bd14df70f61ab72c782b1dcab43c2d673b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 15 Jan 2024 14:20:07 +0100 Subject: [PATCH 2483/4092] Fix back-to-front set --- 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 f0f0a58368..4eb69a9893 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1243,7 +1243,7 @@ namespace osu.Game.Screens.Play b.IgnoreUserSettings.Value = true; b.IsBreakTime.UnbindFrom(breakTracker.IsBreakTime); - b.IsBreakTime.Value = true; + b.IsBreakTime.Value = false; }); storyboardReplacesBackground.Value = false; } From 91f8144f98adc6f79156d4514840d7b668ced376 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 15 Jan 2024 14:58:46 +0100 Subject: [PATCH 2484/4092] Add test coverage --- .../TestSceneDrawableRoomPlaylist.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 312135402f..d40283ac74 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -19,8 +19,10 @@ using osu.Game.Beatmaps.Drawables; using osu.Game.Database; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; +using osu.Game.Graphics.UserInterface; using osu.Game.Models; using osu.Game.Online.API; +using osu.Game.Online.Chat; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; @@ -302,6 +304,36 @@ namespace osu.Game.Tests.Visual.Multiplayer }); } + [Test] + public void TestSelectableMouseHandling() + { + bool resultsRequested = false; + + AddStep("reset flag", () => resultsRequested = false); + createPlaylist(p => + { + p.AllowSelection = true; + p.AllowShowingResults = true; + p.RequestResults = _ => resultsRequested = true; + }); + + AddStep("move mouse to first item title", () => + { + var drawQuad = playlist.ChildrenOfType().First().ScreenSpaceDrawQuad; + var location = (drawQuad.TopLeft + drawQuad.BottomLeft) / 2 + new Vector2(drawQuad.Width * 0.2f, 0); + InputManager.MoveMouseTo(location); + }); + AddAssert("first item title not hovered", () => playlist.ChildrenOfType().First().IsHovered, () => Is.False); + AddStep("click left mouse", () => InputManager.Click(MouseButton.Left)); + AddUntilStep("first item selected", () => playlist.ChildrenOfType().First().IsSelectedItem, () => Is.True); + // implies being clickable. + AddUntilStep("first item title hovered", () => playlist.ChildrenOfType().First().IsHovered, () => Is.True); + + AddStep("move mouse to second item results button", () => InputManager.MoveMouseTo(playlist.ChildrenOfType().ElementAt(5))); + AddStep("click left mouse", () => InputManager.Click(MouseButton.Left)); + AddUntilStep("results requested", () => resultsRequested); + } + private void moveToItem(int index, Vector2? offset = null) => AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType().ElementAt(index), offset)); From d8d1d9264c7bd1b933a21e67e26efebd2107707e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 15 Jan 2024 18:29:51 +0100 Subject: [PATCH 2485/4092] Fix test failure --- .../Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index d40283ac74..6446ebd35f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -323,6 +323,7 @@ namespace osu.Game.Tests.Visual.Multiplayer var location = (drawQuad.TopLeft + drawQuad.BottomLeft) / 2 + new Vector2(drawQuad.Width * 0.2f, 0); InputManager.MoveMouseTo(location); }); + AddUntilStep("wait for text load", () => playlist.ChildrenOfType().Any()); AddAssert("first item title not hovered", () => playlist.ChildrenOfType().First().IsHovered, () => Is.False); AddStep("click left mouse", () => InputManager.Click(MouseButton.Left)); AddUntilStep("first item selected", () => playlist.ChildrenOfType().First().IsSelectedItem, () => Is.True); From 8a839f64ed18cfabd6ca8ae98e7ba80441641803 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 15 Jan 2024 18:47:46 +0100 Subject: [PATCH 2486/4092] Add failing test coverage for placeholder shown when it shouldn't be --- .../TestScenePlaylistsResultsScreen.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index 727d9da50b..ff1b351b9f 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -17,6 +17,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Placeholders; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; @@ -139,6 +140,37 @@ namespace osu.Game.Tests.Visual.Playlists } } + [Test] + public void TestNoMoreScoresToTheRight() + { + AddStep("bind delayed handler with scores", () => bindHandler(delayed: true)); + + createResults(); + waitForDisplay(); + + int beforePanelCount = 0; + + AddStep("get panel count", () => beforePanelCount = this.ChildrenOfType().Count()); + AddStep("scroll to right", () => resultsScreen.ScorePanelList.ChildrenOfType().Single().ScrollToEnd(false)); + + AddAssert("right loading spinner shown", () => resultsScreen.RightSpinner.State.Value == Visibility.Visible); + waitForDisplay(); + + AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType().Count() >= beforePanelCount + scores_per_result); + AddAssert("right loading spinner hidden", () => resultsScreen.RightSpinner.State.Value == Visibility.Hidden); + + AddStep("get panel count", () => beforePanelCount = this.ChildrenOfType().Count()); + AddStep("bind delayed handler with no scores", () => bindHandler(delayed: true, noScores: true)); + AddStep("scroll to right", () => resultsScreen.ScorePanelList.ChildrenOfType().Single().ScrollToEnd(false)); + + AddAssert("right loading spinner shown", () => resultsScreen.RightSpinner.State.Value == Visibility.Visible); + waitForDisplay(); + + AddAssert("count not increased", () => this.ChildrenOfType().Count() == beforePanelCount); + AddAssert("right loading spinner hidden", () => resultsScreen.RightSpinner.State.Value == Visibility.Hidden); + AddAssert("no placeholders shown", () => this.ChildrenOfType().Count(), () => Is.Zero); + } + [Test] public void TestFetchWhenScrolledToTheLeft() { From 9d2c82452cfee1cfd7b23dc81c78d902938b5945 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 15 Jan 2024 18:47:49 +0100 Subject: [PATCH 2487/4092] Revert "simplify the code even more" This reverts commit 9bc9db9138ee6d8e103c1f2a8dd87b671cb6a5b8. --- osu.Game/Screens/Ranking/ResultsScreen.cs | 2 +- osu.Game/Screens/Ranking/ScorePanelList.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index da1609c2c4..697d62ad6e 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -248,7 +248,7 @@ namespace osu.Game.Screens.Ranking lastFetchCompleted = true; - if (!scores.Any()) + if (ScorePanelList.IsEmpty) { // This can happen if for example a beatmap that is part of a playlist hasn't been played yet. VerticalScrollContent.Add(new MessagePlaceholder(LeaderboardStrings.NoRecordsYet)); diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index b75f3d86ff..95c90e35a0 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -49,6 +49,8 @@ namespace osu.Game.Screens.Ranking public bool AllPanelsVisible => flow.All(p => p.IsPresent); + public bool IsEmpty => flow.Count == 0; + /// /// The current scroll position. /// From 988794cf90d8bbe803c6a24a1e8c0c8a3ffaf2c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 15 Jan 2024 18:49:41 +0100 Subject: [PATCH 2488/4092] Improve test coverage for empty results --- .../Visual/Playlists/TestScenePlaylistsResultsScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index ff1b351b9f..e805b03542 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -201,7 +201,8 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("bind user score info handler", () => bindHandler(noScores: true)); createResults(); - AddAssert("no scores visible", () => resultsScreen.ScorePanelList.GetScorePanels().Count() == 0); + AddAssert("no scores visible", () => !resultsScreen.ScorePanelList.GetScorePanels().Any()); + AddAssert("placeholder shown", () => this.ChildrenOfType().Count(), () => Is.EqualTo(1)); } private void createResults(Func getScore = null) From e8394e6f74351f7b136fe16d2a53033814d1bd56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 15 Jan 2024 19:05:33 +0100 Subject: [PATCH 2489/4092] Add test coverage --- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 4b101a52f4..7958b68fb5 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -542,6 +542,11 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("press enter", () => InputManager.Key(Key.Enter)); AddAssert("hidden selected", () => getPanelForMod(typeof(OsuModHidden)).Active.Value); + AddAssert("all text selected in textbox", () => + { + var textBox = modSelectOverlay.ChildrenOfType().Single(); + return textBox.SelectedText == textBox.Text; + }); AddStep("press enter again", () => InputManager.Key(Key.Enter)); AddAssert("hidden deselected", () => !getPanelForMod(typeof(OsuModHidden)).Active.Value); From 8661edfc2fbc3cb9a5df62f8f31a7e1c928eb941 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 15 Jan 2024 21:07:09 +0300 Subject: [PATCH 2490/4092] Organize consts better --- osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index dcc8f4feec..6fc957874a 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -58,6 +58,8 @@ namespace osu.Game.Screens.Play.HUD public const float MAIN_PATH_RADIUS = 10f; private const float padding = MAIN_PATH_RADIUS * 2; + private const float glow_path_radius = 40f; + private const float main_path_glow_portion = 0.6f; private readonly LayoutValue drawSizeLayout = new LayoutValue(Invalidation.DrawSize); @@ -90,7 +92,7 @@ namespace osu.Game.Screens.Play.HUD { RelativeSizeAxes = Axes.Both, // since we are using bigger path radius we need to expand the draw area outwards to preserve the curve placement - Padding = new MarginPadding(-30f), + Padding = new MarginPadding(MAIN_PATH_RADIUS - glow_path_radius), Child = glowBar = new ArgonHealthDisplayBar { RelativeSizeAxes = Axes.Both, @@ -98,8 +100,8 @@ namespace osu.Game.Screens.Play.HUD GlowColour = main_bar_glow_colour, Blending = BlendingParameters.Additive, Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0.8f), Color4.White), - PathRadius = 40f, - GlowPortion = 0.9f, + PathRadius = glow_path_radius, + GlowPortion = (glow_path_radius - MAIN_PATH_RADIUS * (1f - main_path_glow_portion)) / glow_path_radius, } }, mainBar = new ArgonHealthDisplayBar @@ -109,7 +111,7 @@ namespace osu.Game.Screens.Play.HUD BarColour = main_bar_colour, GlowColour = main_bar_glow_colour, PathRadius = MAIN_PATH_RADIUS, - GlowPortion = 0.6f, + GlowPortion = main_path_glow_portion } } }; From c0d4ed4789fbd7de96c131e2e7882ac673d03e97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 15 Jan 2024 19:07:52 +0100 Subject: [PATCH 2491/4092] Add test coverage of select-all-text-in-search when nothing matched --- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 7958b68fb5..046954db47 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -551,6 +551,14 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("press enter again", () => InputManager.Key(Key.Enter)); AddAssert("hidden deselected", () => !getPanelForMod(typeof(OsuModHidden)).Active.Value); + AddStep("apply search matching nothing", () => modSelectOverlay.SearchTerm = "ZZZ"); + AddStep("press enter", () => InputManager.Key(Key.Enter)); + AddAssert("all text not selected in textbox", () => + { + var textBox = modSelectOverlay.ChildrenOfType().Single(); + return textBox.SelectedText != textBox.Text; + }); + AddStep("clear search", () => modSelectOverlay.SearchTerm = string.Empty); AddStep("press enter", () => InputManager.Key(Key.Enter)); AddAssert("mod select hidden", () => modSelectOverlay.State.Value == Visibility.Hidden); From c46615839dcc40bca80f401d8559422492c43cfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 15 Jan 2024 19:09:50 +0100 Subject: [PATCH 2492/4092] Only select all text in mod search text box if enter press selected anything --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 8cb990757f..a774bb9167 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -719,9 +719,10 @@ namespace osu.Game.Overlays.Mods ModState? firstMod = columnFlow.Columns.OfType().FirstOrDefault(m => m.IsPresent)?.AvailableMods.FirstOrDefault(x => x.Visible); if (firstMod is not null) + { firstMod.Active.Value = !firstMod.Active.Value; - - SearchTextBox.SelectAll(); + SearchTextBox.SelectAll(); + } return true; } From 97e08f50715e980d4a987d9f8ca52f7d6d178df1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 15 Jan 2024 21:39:33 +0100 Subject: [PATCH 2493/4092] Fix `ColourHitErrorMeter` not loading pooled drawables ahead of time --- .../HUD/HitErrorMeters/ColourHitErrorMeter.cs | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs index 65f4b50dde..0f2f9dc323 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs @@ -15,7 +15,6 @@ using osu.Game.Localisation.HUD; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osuTK; -using osuTK.Graphics; namespace osu.Game.Screens.Play.HUD.HitErrorMeters { @@ -42,16 +41,21 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters [SettingSource(typeof(ColourHitErrorMeterStrings), nameof(ColourHitErrorMeterStrings.JudgementShape), nameof(ColourHitErrorMeterStrings.JudgementShapeDescription))] public Bindable JudgementShape { get; } = new Bindable(); + private readonly DrawablePool judgementShapePool; private readonly JudgementFlow judgementsFlow; public ColourHitErrorMeter() { AutoSizeAxes = Axes.Both; - InternalChild = judgementsFlow = new JudgementFlow + InternalChildren = new Drawable[] { - JudgementShape = { BindTarget = JudgementShape }, - JudgementSpacing = { BindTarget = JudgementSpacing }, - JudgementCount = { BindTarget = JudgementCount } + judgementShapePool = new DrawablePool(50), + judgementsFlow = new JudgementFlow + { + JudgementShape = { BindTarget = JudgementShape }, + JudgementSpacing = { BindTarget = JudgementSpacing }, + JudgementCount = { BindTarget = JudgementCount } + } }; } @@ -60,7 +64,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters if (!judgement.Type.IsScorable() || judgement.Type.IsBonus()) return; - judgementsFlow.Push(GetColourForHitResult(judgement.Type)); + judgementsFlow.Push(judgementShapePool.Get(shape => shape.Colour = GetColourForHitResult(judgement.Type))); } public override void Clear() @@ -105,15 +109,10 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters private readonly DrawablePool judgementLinePool = new DrawablePool(50); - public void Push(Color4 colour) + public void Push(HitErrorShape shape) { - judgementLinePool.Get(shape => - { - shape.Colour = colour; - Add(shape); - - removeExtraJudgements(); - }); + Add(shape); + removeExtraJudgements(); } private void removeExtraJudgements() From 09e5b2fb46748d7c03ac7764e9170d2c860ac7ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Jan 2024 13:18:43 +0900 Subject: [PATCH 2494/4092] Add test coverage of incorrect default state for `Rank` when hidden is applied --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 567bf6968f..73465fae08 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -18,10 +18,12 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; using osu.Game.Scoring.Legacy; using osu.Game.Tests.Beatmaps; @@ -385,6 +387,42 @@ namespace osu.Game.Tests.Rulesets.Scoring Assert.That(scoreProcessor.Accuracy.Value, Is.Not.EqualTo(1)); } + [Test] + public void TestNormalGrades() + { + scoreProcessor.ApplyBeatmap(new Beatmap()); + + Assert.That(scoreProcessor.Rank.Value, Is.EqualTo(ScoreRank.X)); + + scoreProcessor.Accuracy.Value = 0.99f; + Assert.That(scoreProcessor.Rank.Value, Is.EqualTo(ScoreRank.S)); + } + + [Test] + public void TestSilverGrades() + { + scoreProcessor.ApplyBeatmap(new Beatmap()); + Assert.That(scoreProcessor.Rank.Value, Is.EqualTo(ScoreRank.X)); + + scoreProcessor.Mods.Value = new[] { new OsuModHidden() }; + Assert.That(scoreProcessor.Rank.Value, Is.EqualTo(ScoreRank.XH)); + + scoreProcessor.Accuracy.Value = 0.99f; + Assert.That(scoreProcessor.Rank.Value, Is.EqualTo(ScoreRank.SH)); + } + + [Test] + public void TestSilverGradesModsAppliedFirst() + { + scoreProcessor.Mods.Value = new[] { new OsuModHidden() }; + scoreProcessor.ApplyBeatmap(new Beatmap()); + + Assert.That(scoreProcessor.Rank.Value, Is.EqualTo(ScoreRank.XH)); + + scoreProcessor.Accuracy.Value = 0.99f; + Assert.That(scoreProcessor.Rank.Value, Is.EqualTo(ScoreRank.SH)); + } + private class TestJudgement : Judgement { public override HitResult MaxResult { get; } From 902a5436f3e2c2f330bcaae176d07f252f26e14a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Jan 2024 13:12:22 +0900 Subject: [PATCH 2495/4092] Fix silver S/SS not being awarded correctly --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 25 ++++++++++++--------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 14fa928224..80e751422e 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -186,16 +186,7 @@ namespace osu.Game.Rulesets.Scoring Ruleset = ruleset; Combo.ValueChanged += combo => HighestCombo.Value = Math.Max(HighestCombo.Value, combo.NewValue); - Accuracy.ValueChanged += accuracy => - { - // Once failed, we shouldn't update the rank anymore. - if (rank.Value == ScoreRank.F) - return; - - rank.Value = RankFromAccuracy(accuracy.NewValue); - foreach (var mod in Mods.Value.OfType()) - rank.Value = mod.AdjustRank(Rank.Value, accuracy.NewValue); - }; + Accuracy.ValueChanged += _ => updateRank(); Mods.ValueChanged += mods => { @@ -205,6 +196,7 @@ namespace osu.Game.Rulesets.Scoring scoreMultiplier *= m.ScoreMultiplier; updateScore(); + updateRank(); }; } @@ -372,6 +364,17 @@ namespace osu.Game.Rulesets.Scoring TotalScore.Value = (long)Math.Round(ComputeTotalScore(comboProgress, accuracyProcess, currentBonusPortion) * scoreMultiplier); } + private void updateRank() + { + // Once failed, we shouldn't update the rank anymore. + if (rank.Value == ScoreRank.F) + return; + + rank.Value = RankFromAccuracy(Accuracy.Value); + foreach (var mod in Mods.Value.OfType()) + rank.Value = mod.AdjustRank(Rank.Value, Accuracy.Value); + } + protected virtual double ComputeTotalScore(double comboProgress, double accuracyProgress, double bonusPortion) { return 500000 * Accuracy.Value * comboProgress + @@ -417,8 +420,8 @@ namespace osu.Game.Rulesets.Scoring TotalScore.Value = 0; Accuracy.Value = 1; Combo.Value = 0; - rank.Value = ScoreRank.X; HighestCombo.Value = 0; + updateRank(); } /// From e75f113a06c4c4d9ec229857ed580d5747c1f143 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Jan 2024 14:14:04 +0900 Subject: [PATCH 2496/4092] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 07a638f04a..f33ddac66f 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + From 34905b20527bf7adf618506975feab634f7632b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Jan 2024 14:17:21 +0900 Subject: [PATCH 2497/4092] Apply NRT to new classes --- .../ArgonHealthDisplayBackground.cs | 12 +++--------- .../ArgonHealthDisplayParts/ArgonHealthDisplayBar.cs | 6 ++---- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBackground.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBackground.cs index a96c2f97bd..b486465cb0 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBackground.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBackground.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Runtime.InteropServices; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -29,6 +27,8 @@ namespace osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts { protected new ArgonHealthDisplayBackground Source => (ArgonHealthDisplayBackground)base.Source; + private IUniformBuffer? parametersBuffer; + public ArgonBarPathDrawNode(ArgonHealthDisplayBackground source) : base(source) { @@ -39,21 +39,15 @@ namespace osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts public override void ApplyState() { base.ApplyState(); - size = Source.DrawSize; } - private IUniformBuffer parametersBuffer; - protected override void BindUniformResources(IShader shader, IRenderer renderer) { base.BindUniformResources(shader, renderer); parametersBuffer ??= renderer.CreateUniformBuffer(); - parametersBuffer.Data = new ArgonBarPathBackgroundParameters - { - Size = size - }; + parametersBuffer.Data = new ArgonBarPathBackgroundParameters { Size = size }; shader.BindUniformBlock("m_ArgonBarPathBackgroundParameters", parametersBuffer); } diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBar.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBar.cs index 1938b97d5a..28e56183bf 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBar.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplayParts/ArgonHealthDisplayBar.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Runtime.InteropServices; using osu.Framework.Allocation; @@ -107,6 +105,8 @@ namespace osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts { protected new ArgonHealthDisplayBar Source => (ArgonHealthDisplayBar)base.Source; + private IUniformBuffer? parametersBuffer; + public ArgonBarPathDrawNode(ArgonHealthDisplayBar source) : base(source) { @@ -139,8 +139,6 @@ namespace osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts base.Draw(renderer); } - private IUniformBuffer parametersBuffer; - protected override void BindUniformResources(IShader shader, IRenderer renderer) { base.BindUniformResources(shader, renderer); From 451ba1c86131e61245d4d3a9eb232cc47c32ff7e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Jan 2024 15:12:23 +0900 Subject: [PATCH 2498/4092] Ensure `PresentGameplay` doesn't get stuck in loop if no beatmaps available --- osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs index bedaf12c9b..40cd31934f 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs @@ -136,10 +136,15 @@ namespace osu.Game.Overlays.SkinEditor globallyReenableBeatmapSkinSetting(); } - public void PresentGameplay() + public void PresentGameplay() => presentGameplay(false); + + private void presentGameplay(bool attemptedBeatmapSwitch) { performer?.PerformFromScreen(screen => { + if (State.Value != Visibility.Visible) + return; + if (beatmap.Value is DummyWorkingBeatmap) { // presume we don't have anything good to play and just bail. @@ -149,8 +154,12 @@ namespace osu.Game.Overlays.SkinEditor // If we're playing the intro, switch away to another beatmap. if (beatmap.Value.BeatmapSetInfo.Protected) { - music.NextTrack(); - Schedule(PresentGameplay); + if (!attemptedBeatmapSwitch) + { + music.NextTrack(); + Schedule(() => presentGameplay(true)); + } + return; } From e3ab7b1e9fd60ac15ae3e3e838425290512cf517 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 16 Jan 2024 17:22:22 +1100 Subject: [PATCH 2499/4092] Update disctop to use turnratio like stable --- .../Skinning/Legacy/LegacyNewStyleSpinner.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs index 67a6d5e41a..d4a0f243e4 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs @@ -135,7 +135,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy protected override void Update() { base.Update(); - spinningMiddle.Rotation = discTop.Rotation = DrawableSpinner.RotationTracker.Rotation; + + float turnRatio = spinningMiddle.Texture != null ? 0.5f : 1; + discTop.Rotation = DrawableSpinner.RotationTracker.Rotation * turnRatio; + spinningMiddle.Rotation = DrawableSpinner.RotationTracker.Rotation; + discBottom.Rotation = discTop.Rotation / 3; glow.Alpha = DrawableSpinner.Progress; From 0e41d0c9cf1cc46da66bd1c0d11dbc4091ea644a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Jan 2024 15:21:43 +0900 Subject: [PATCH 2500/4092] Add failing test coverage of skin getting nuked This doesn't fail in headless unfortunately. Run a few times manually to confirm. --- .../Navigation/TestSceneScreenNavigation.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 8cb993eff2..f59fbc75ac 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -35,6 +35,7 @@ using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Ranking; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Carousel; @@ -834,6 +835,24 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert("exit dialog is shown", () => Game.Dependencies.Get().CurrentDialog is ConfirmExitDialog); } + [Test] + public void TestQuickSkinEditorDoesntNukeSkin() + { + AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); + + AddStep("open", () => InputManager.Key(Key.Space)); + AddStep("skin", () => InputManager.Key(Key.E)); + AddStep("editor", () => InputManager.Key(Key.S)); + AddStep("and close immediately", () => InputManager.Key(Key.Escape)); + + AddStep("open again", () => InputManager.Key(Key.S)); + + Player player = null; + + AddUntilStep("wait for player", () => (player = Game.ScreenStack.CurrentScreen as Player) != null); + AddUntilStep("wait for gameplay still has health bar", () => player.ChildrenOfType().Any()); + } + [Test] public void TestTouchScreenDetectionAtSongSelect() { From cd02d00c0352eed86d4f2e5ec9aaf3bcd61a0102 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Jan 2024 15:23:07 +0900 Subject: [PATCH 2501/4092] Fix skin potentially being lost when opening and closing skin editor rapidly --- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index f972186333..d3af928907 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -47,7 +47,7 @@ namespace osu.Game.Overlays.SkinEditor protected override bool StartHidden => true; - private Drawable targetScreen = null!; + private Drawable? targetScreen; private OsuTextFlowContainer headerText = null!; @@ -541,8 +541,14 @@ namespace osu.Game.Overlays.SkinEditor if (!hasBegunMutating) return; + if (targetScreen?.IsLoaded != true) + return; + SkinComponentsContainer[] targetContainers = availableTargets.ToArray(); + if (!targetContainers.All(c => c.ComponentsLoaded)) + return; + foreach (var t in targetContainers) currentSkin.Value.UpdateDrawableTarget(t); From 57a6025a2c492c897531cbb56ae0bdaf136dd03c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Jan 2024 15:52:05 +0900 Subject: [PATCH 2502/4092] Add helper method to bypass judgement woes --- osu.Game/Rulesets/Judgements/JudgementResult.cs | 5 +++++ osu.Game/Rulesets/Scoring/HealthProcessor.cs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Judgements/JudgementResult.cs b/osu.Game/Rulesets/Judgements/JudgementResult.cs index 1b915d52b7..b781a13929 100644 --- a/osu.Game/Rulesets/Judgements/JudgementResult.cs +++ b/osu.Game/Rulesets/Judgements/JudgementResult.cs @@ -94,6 +94,11 @@ namespace osu.Game.Rulesets.Judgements /// public bool IsHit => Type.IsHit(); + /// + /// The increase in health resulting from this judgement result. + /// + public double HealthIncrease => Judgement.HealthIncreaseFor(this); + /// /// Creates a new . /// diff --git a/osu.Game/Rulesets/Scoring/HealthProcessor.cs b/osu.Game/Rulesets/Scoring/HealthProcessor.cs index 3e0b6433c2..b5eb755650 100644 --- a/osu.Game/Rulesets/Scoring/HealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/HealthProcessor.cs @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Scoring /// /// The . /// The health increase. - protected virtual double GetHealthIncreaseFor(JudgementResult result) => result.Judgement.HealthIncreaseFor(result); + protected virtual double GetHealthIncreaseFor(JudgementResult result) => result.HealthIncrease; /// /// The default conditions for failing. From 2be8d66d4c75cee98d9aa0e5b60c99f475bb555c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Jan 2024 15:52:18 +0900 Subject: [PATCH 2503/4092] Fix argon health bar showing "miss" bar for bananas --- osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 6fc957874a..71996718d9 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -136,7 +136,12 @@ namespace osu.Game.Screens.Play.HUD BarHeight.BindValueChanged(_ => updateContentSize(), true); } - private void onNewJudgement(JudgementResult result) => pendingMissAnimation |= !result.IsHit; + private void onNewJudgement(JudgementResult result) + { + // Check the health increase because cases like osu!catch bananas fire `IgnoreMiss`, + // which counts as a miss but doesn't actually subtract any health. + pendingMissAnimation |= !result.IsHit && result.HealthIncrease < 0; + } protected override void Update() { From cdc6621f33f1365cb212e8663e0f948e7dc34bf5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Jan 2024 16:37:05 +0900 Subject: [PATCH 2504/4092] Allow adjusting volume controls via a drag --- osu.Game/Overlays/Volume/VolumeMeter.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index d366f0bddb..6ad314c601 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -309,10 +309,24 @@ namespace osu.Game.Overlays.Volume private const double max_acceleration = 5; private const double acceleration_multiplier = 1.8; + private const float mouse_drag_divisor = 20; + private ScheduledDelegate accelerationDebounce; private void resetAcceleration() => accelerationModifier = 1; + protected override bool OnDragStart(DragStartEvent e) + { + adjust(-e.Delta.Y / mouse_drag_divisor, true); + return true; + } + + protected override void OnDrag(DragEvent e) + { + adjust(-e.Delta.Y / mouse_drag_divisor, true); + base.OnDrag(e); + } + private void adjust(double delta, bool isPrecise) { if (delta == 0) From ee26329353ec41e9a9859aad8fc7000760bfac66 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Jan 2024 16:44:50 +0900 Subject: [PATCH 2505/4092] Fix some elements not showing on leaderboard scores when almost off-screen --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index a76f4ae955..1c2a26eafb 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -164,7 +164,8 @@ namespace osu.Game.Online.Leaderboards { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - AutoSizeAxes = Axes.Both, + AutoSizeAxes = Axes.X, + Height = 28, Direction = FillDirection.Horizontal, Spacing = new Vector2(10f, 0f), Children = new Drawable[] From c45daa373e24070c78379c71c57d6c1ede6c6c31 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Jan 2024 17:15:25 +0900 Subject: [PATCH 2506/4092] Fix cursor scale animation not matching stable on classic skins --- .../Skinning/Argon/ArgonCursor.cs | 2 +- .../Skinning/Legacy/LegacyCursor.cs | 16 +++++++++- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 11 +++---- .../UI/Cursor/OsuCursorSprite.cs | 19 ------------ .../UI/Cursor/SkinnableCursor.cs | 31 +++++++++++++++++++ 5 files changed, 51 insertions(+), 28 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs create mode 100644 osu.Game.Rulesets.Osu/UI/Cursor/SkinnableCursor.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonCursor.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonCursor.cs index 4ca6abfdf7..15838f3e1b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonCursor.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonCursor.cs @@ -13,7 +13,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Argon { - public partial class ArgonCursor : OsuCursorSprite + public partial class ArgonCursor : SkinnableCursor { public ArgonCursor() { diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursor.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursor.cs index b0c01d2925..375d81049d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursor.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursor.cs @@ -9,8 +9,11 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { - public partial class LegacyCursor : OsuCursorSprite + public partial class LegacyCursor : SkinnableCursor { + private const float pressed_scale = 1.3f; + private const float released_scale = 1f; + private readonly ISkin skin; private bool spin; @@ -51,5 +54,16 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy if (spin) ExpandTarget.Spin(10000, RotationDirection.Clockwise); } + + public override void Expand() + { + ExpandTarget?.ScaleTo(released_scale) + .ScaleTo(pressed_scale, 100, Easing.Out); + } + + public override void Contract() + { + ExpandTarget?.ScaleTo(released_scale, 100, Easing.Out); + } } } diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index 710ca9ace7..d8f50c1f5d 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -24,15 +24,12 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { public const float SIZE = 28; - private const float pressed_scale = 1.2f; - private const float released_scale = 1f; - private bool cursorExpand; private SkinnableDrawable cursorSprite; private Container cursorScaleContainer = null!; - private Drawable expandTarget => (cursorSprite.Drawable as OsuCursorSprite)?.ExpandTarget ?? cursorSprite; + private SkinnableCursor skinnableCursor => (SkinnableCursor)cursorSprite.Drawable; public IBindable CursorScale => cursorScale; @@ -108,10 +105,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { if (!cursorExpand) return; - expandTarget.ScaleTo(released_scale).ScaleTo(pressed_scale, 400, Easing.OutElasticHalf); + skinnableCursor.Expand(); } - public void Contract() => expandTarget.ScaleTo(released_scale, 400, Easing.OutQuad); + public void Contract() => skinnableCursor.Contract(); /// /// Get the scale applicable to the ActiveCursor based on a beatmap's circle size. @@ -119,7 +116,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor public static float GetScaleForCircleSize(float circleSize) => 1f - 0.7f * (1f + circleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY; - private partial class DefaultCursor : OsuCursorSprite + private partial class DefaultCursor : SkinnableCursor { public DefaultCursor() { diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs deleted file mode 100644 index aaf8949084..0000000000 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.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. - -#nullable disable - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; - -namespace osu.Game.Rulesets.Osu.UI.Cursor -{ - public abstract partial class OsuCursorSprite : CompositeDrawable - { - /// - /// The an optional piece of the cursor to expand when in a clicked state. - /// If null, the whole cursor will be affected by expansion. - /// - public Drawable ExpandTarget { get; protected set; } - } -} diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/SkinnableCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/SkinnableCursor.cs new file mode 100644 index 0000000000..09e6f989a4 --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/Cursor/SkinnableCursor.cs @@ -0,0 +1,31 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Rulesets.Osu.UI.Cursor +{ + public abstract partial class SkinnableCursor : CompositeDrawable + { + private const float pressed_scale = 1.2f; + private const float released_scale = 1f; + + public virtual void Expand() + { + ExpandTarget?.ScaleTo(released_scale) + .ScaleTo(pressed_scale, 400, Easing.OutElasticHalf); + } + + public virtual void Contract() + { + ExpandTarget?.ScaleTo(released_scale, 400, Easing.OutQuad); + } + + /// + /// The an optional piece of the cursor to expand when in a clicked state. + /// If null, the whole cursor will be affected by expansion. + /// + public Drawable? ExpandTarget { get; protected set; } + } +} From 2935bd4809d1e071d8feb93604629fa94fb2cbbc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 16 Jan 2024 14:27:24 +0300 Subject: [PATCH 2507/4092] Add test cases for helper method --- osu.Game.Tests/Mods/ModUtilsTest.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Tests/Mods/ModUtilsTest.cs b/osu.Game.Tests/Mods/ModUtilsTest.cs index 13da69871e..decb0a31ac 100644 --- a/osu.Game.Tests/Mods/ModUtilsTest.cs +++ b/osu.Game.Tests/Mods/ModUtilsTest.cs @@ -7,7 +7,9 @@ using Moq; using NUnit.Framework; using osu.Framework.Localisation; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Taiko.Mods; using osu.Game.Utils; namespace osu.Game.Tests.Mods @@ -310,6 +312,16 @@ namespace osu.Game.Tests.Mods Assert.That(invalid?.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid)); } + [Test] + public void TestModBelongsToRuleset() + { + Assert.That(ModUtils.CheckModsBelongToRuleset(new OsuRuleset(), Array.Empty())); + Assert.That(ModUtils.CheckModsBelongToRuleset(new OsuRuleset(), new Mod[] { new OsuModDoubleTime() })); + Assert.That(ModUtils.CheckModsBelongToRuleset(new OsuRuleset(), new Mod[] { new OsuModDoubleTime(), new OsuModAccuracyChallenge() })); + Assert.That(ModUtils.CheckModsBelongToRuleset(new OsuRuleset(), new Mod[] { new OsuModDoubleTime(), new ModAccuracyChallenge() }), Is.False); + Assert.That(ModUtils.CheckModsBelongToRuleset(new OsuRuleset(), new Mod[] { new OsuModDoubleTime(), new TaikoModFlashlight() }), Is.False); + } + [Test] public void TestFormatScoreMultiplier() { From 90da39c65d252e157b8a550429e18c944cd65380 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 16 Jan 2024 14:27:30 +0300 Subject: [PATCH 2508/4092] Fix helper method returning incorrect result --- osu.Game/Utils/ModUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index 7a9f93e06f..2c9eef41e3 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -255,7 +255,7 @@ namespace osu.Game.Utils } if (!found) - return true; + return false; } return true; From 485195d4c25d3b8f13f52f6da7faf72472a21b0e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 16 Jan 2024 14:27:40 +0300 Subject: [PATCH 2509/4092] Fix submission test not asserting properly --- osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index 833cb24f41..e2ce3a014c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -294,6 +294,7 @@ namespace osu.Game.Tests.Visual.Gameplay createPlayerTest(createRuleset: () => new OsuRuleset(), createMods: () => new Mod[] { new TaikoModHidden() }); AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + AddAssert("gameplay not loaded", () => Player.DrawableRuleset == null); AddStep("exit", () => Player.Exit()); AddAssert("ensure no submission", () => Player.SubmittedScore == null); From 7920e93fa91dbe14a610591cb4446a3c08d63819 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 16 Jan 2024 17:36:46 +0300 Subject: [PATCH 2510/4092] Rework GlowingSpriteText --- .../Graphics/Sprites/GlowingSpriteText.cs | 85 +++++++++---------- 1 file changed, 40 insertions(+), 45 deletions(-) diff --git a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs index 1355bfc272..669c5da01e 100644 --- a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs +++ b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs @@ -5,95 +5,90 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Framework.Utils; using osuTK; namespace osu.Game.Graphics.Sprites { - public partial class GlowingSpriteText : Container, IHasText + public partial class GlowingSpriteText : BufferedContainer, IHasText { - private readonly OsuSpriteText spriteText, blurredText; + private const float blur_sigma = 3f; + + // Inflate draw quad to prevent glow from trimming at the edges. + // Padding won't suffice since it will affect text position in cases when it's not centered. + protected override Quad ComputeScreenSpaceDrawQuad() => base.ComputeScreenSpaceDrawQuad().AABBFloat.Inflate(Blur.KernelSize(blur_sigma)); + + private readonly OsuSpriteText text; public LocalisableString Text { - get => spriteText.Text; - set => blurredText.Text = spriteText.Text = value; + get => text.Text; + set => text.Text = value; } public FontUsage Font { - get => spriteText.Font; - set => blurredText.Font = spriteText.Font = value.With(fixedWidth: true); + get => text.Font; + set => text.Font = value.With(fixedWidth: true); } public Vector2 TextSize { - get => spriteText.Size; - set => blurredText.Size = spriteText.Size = value; + get => text.Size; + set => text.Size = value; } public ColourInfo TextColour { - get => spriteText.Colour; - set => spriteText.Colour = value; + get => text.Colour; + set => text.Colour = value; } public ColourInfo GlowColour { - get => blurredText.Colour; - set => blurredText.Colour = value; + get => EffectColour; + set + { + EffectColour = value; + BackgroundColour = value.MultiplyAlpha(0f); + } } public Vector2 Spacing { - get => spriteText.Spacing; - set => spriteText.Spacing = blurredText.Spacing = value; + get => text.Spacing; + set => text.Spacing = value; } public bool UseFullGlyphHeight { - get => spriteText.UseFullGlyphHeight; - set => spriteText.UseFullGlyphHeight = blurredText.UseFullGlyphHeight = value; + get => text.UseFullGlyphHeight; + set => text.UseFullGlyphHeight = value; } public Bindable Current { - get => spriteText.Current; - set => spriteText.Current = value; + get => text.Current; + set => text.Current = value; } public GlowingSpriteText() + : base(cachedFrameBuffer: true) { AutoSizeAxes = Axes.Both; - - Children = new Drawable[] + BlurSigma = new Vector2(blur_sigma); + RedrawOnScale = false; + DrawOriginal = true; + EffectBlending = BlendingParameters.Additive; + EffectPlacement = EffectPlacement.InFront; + Child = text = new OsuSpriteText { - new BufferedContainer(cachedFrameBuffer: true) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - BlurSigma = new Vector2(4), - RedrawOnScale = false, - RelativeSizeAxes = Axes.Both, - Blending = BlendingParameters.Additive, - Size = new Vector2(3f), - Children = new[] - { - blurredText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Shadow = false, - }, - }, - }, - spriteText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Shadow = false, - }, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Shadow = false, }; } } From dfdf700560726d0f8bd87408b3cecd9b0e373f4c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 16 Jan 2024 17:39:37 +0300 Subject: [PATCH 2511/4092] Don't use glowing text in ScoreComponentLabel --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 1c2a26eafb..964f065813 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -358,14 +358,12 @@ namespace osu.Game.Online.Leaderboards }, }, }, - new GlowingSpriteText + new OsuSpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - TextColour = Color4.White, - GlowColour = Color4Extensions.FromHex(@"83ccfa"), Text = statistic.Value, - Font = OsuFont.GetFont(size: 17, weight: FontWeight.Bold), + Font = OsuFont.GetFont(size: 17, weight: FontWeight.Bold, fixedWidth: true) }, }, }; From 7a7548e89d91849992461c759a01a7e4f5cf97fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 16 Jan 2024 17:09:04 +0100 Subject: [PATCH 2512/4092] Add failing test coverage --- .../Mods/TestSceneOsuModTouchDevice.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs index cd51ccd751..5b5ad07fde 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs @@ -51,7 +51,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods public void TestUserAlreadyHasTouchDeviceActive() { loadPlayer(); - // it is presumed that a previous screen (i.e. song select) will set this up AddStep("set up touchscreen user", () => { currentPlayer.Score.ScoreInfo.Mods = currentPlayer.Score.ScoreInfo.Mods.Append(new OsuModTouchDevice()).ToArray(); @@ -69,6 +68,14 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods AddAssert("touch device mod activated", () => currentPlayer.Score.ScoreInfo.Mods, () => Has.One.InstanceOf()); } + [Test] + public void TestTouchActivePriorToPlayerLoad() + { + AddStep("set touch input active", () => statics.SetValue(Static.TouchInputActive, true)); + loadPlayer(); + AddAssert("touch device mod activated", () => currentPlayer.Score.ScoreInfo.Mods, () => Has.One.InstanceOf()); + } + [Test] public void TestTouchDuringBreak() { From a9d086c11954c618e12301eb762edf2df8931187 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 16 Jan 2024 17:12:40 +0100 Subject: [PATCH 2513/4092] Fix touch device not always activating when it should See https://github.com/ppy/osu/discussions/26574 --- osu.Game/Screens/Play/PlayerTouchInputDetector.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerTouchInputDetector.cs b/osu.Game/Screens/Play/PlayerTouchInputDetector.cs index 69c3cd0ded..12fb748e7d 100644 --- a/osu.Game/Screens/Play/PlayerTouchInputDetector.cs +++ b/osu.Game/Screens/Play/PlayerTouchInputDetector.cs @@ -20,12 +20,16 @@ namespace osu.Game.Screens.Play private GameplayState gameplayState { get; set; } = null!; private IBindable touchActive = new BindableBool(); + private IBindable isBreakTime = null!; [BackgroundDependencyLoader] private void load(SessionStatics statics) { touchActive = statics.GetBindable(Static.TouchInputActive); touchActive.BindValueChanged(_ => updateState()); + + isBreakTime = player.IsBreakTime.GetBoundCopy(); + isBreakTime.BindValueChanged(_ => updateState(), true); } private void updateState() @@ -39,7 +43,7 @@ namespace osu.Game.Screens.Play if (gameplayState.Score.ScoreInfo.Mods.OfType().Any()) return; - if (player.IsBreakTime.Value) + if (isBreakTime.Value) return; var touchDeviceMod = gameplayState.Ruleset.GetTouchDeviceMod(); From b16152811d0266905db40187cfbaefe6ebe8c434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 16 Jan 2024 17:54:49 +0100 Subject: [PATCH 2514/4092] Use until step instead --- osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs index 5b5ad07fde..fdb1cac3e5 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { AddStep("set touch input active", () => statics.SetValue(Static.TouchInputActive, true)); loadPlayer(); - AddAssert("touch device mod activated", () => currentPlayer.Score.ScoreInfo.Mods, () => Has.One.InstanceOf()); + AddUntilStep("touch device mod activated", () => currentPlayer.Score.ScoreInfo.Mods, () => Has.One.InstanceOf()); } [Test] From e9e5a0b08cfd0fca03443cd0d25e00b14382934d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 16 Jan 2024 18:41:49 +0100 Subject: [PATCH 2515/4092] Add failing test case --- .../Mods/TestSceneOsuModFlashlight.cs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs index defbb97c8a..1a786b99e8 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs @@ -77,5 +77,44 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Autoplay = false, }); } + + [Test] + public void TestSliderDoesDimAfterStartTimeIfHitEarly() + { + bool sliderDimmed = false; + + CreateModTest(new ModTestData + { + Mod = new OsuModFlashlight(), + PassCondition = () => + { + sliderDimmed |= + Player.GameplayClockContainer.CurrentTime >= 1000 && Player.ChildrenOfType.Flashlight>().Single().FlashlightDim > 0; + return Player.GameplayState.HasPassed && sliderDimmed; + }, + Beatmap = new OsuBeatmap + { + HitObjects = new List + { + new Slider + { + StartTime = 1000, + Path = new SliderPath(new[] + { + new PathControlPoint(), + new PathControlPoint(new Vector2(100)) + }) + } + }, + }, + ReplayFrames = new List + { + new OsuReplayFrame(990, new Vector2(), OsuAction.LeftButton), + new OsuReplayFrame(2000, new Vector2(100), OsuAction.LeftButton), + new OsuReplayFrame(2001, new Vector2(100)), + }, + Autoplay = false, + }); + } } } From d0e9402761a37a393d3048689ef72bb287f26237 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 16 Jan 2024 18:47:00 +0100 Subject: [PATCH 2516/4092] Fix flashlight not dimming if slider head is hit early Closes https://github.com/ppy/osu/issues/26551 Fix is a bit nuclear (`OnUpdate` should be considered last resort), but I don't see many better alternatives here as `ApplyCustomUpdateState` does not work... --- osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 46c9bdd17f..5a6cc50082 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToDrawableHitObject(DrawableHitObject drawable) { if (drawable is DrawableSlider s) - s.Tracking.ValueChanged += _ => flashlight.OnSliderTrackingChange(s); + s.OnUpdate += _ => flashlight.OnSliderTrackingChange(s); } private partial class OsuFlashlight : Flashlight, IRequireHighFrequencyMousePosition From 24741e96156ebaed5e1abd4f26c090b254d869ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 16 Jan 2024 18:54:22 +0100 Subject: [PATCH 2517/4092] Add some more test coverage for good measure --- .../Mods/TestSceneOsuModFlashlight.cs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs index 1a786b99e8..776d5854d1 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs @@ -116,5 +116,44 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Autoplay = false, }); } + + [Test] + public void TestSliderDoesDimAfterStartTimeIfHitLate() + { + bool sliderDimmed = false; + + CreateModTest(new ModTestData + { + Mod = new OsuModFlashlight(), + PassCondition = () => + { + sliderDimmed |= + Player.GameplayClockContainer.CurrentTime >= 1000 && Player.ChildrenOfType.Flashlight>().Single().FlashlightDim > 0; + return Player.GameplayState.HasPassed && sliderDimmed; + }, + Beatmap = new OsuBeatmap + { + HitObjects = new List + { + new Slider + { + StartTime = 1000, + Path = new SliderPath(new[] + { + new PathControlPoint(), + new PathControlPoint(new Vector2(100)) + }) + } + }, + }, + ReplayFrames = new List + { + new OsuReplayFrame(1100, new Vector2(), OsuAction.LeftButton), + new OsuReplayFrame(2000, new Vector2(100), OsuAction.LeftButton), + new OsuReplayFrame(2001, new Vector2(100)), + }, + Autoplay = false, + }); + } } } From 265a56bede4c66e0632e6a6dad6cf6ed0d7bedd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 16 Jan 2024 19:40:01 +0100 Subject: [PATCH 2518/4092] Add basic test coverage of taiko health processor --- .../TaikoHealthProcessorTest.cs | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko.Tests/TaikoHealthProcessorTest.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoHealthProcessorTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoHealthProcessorTest.cs new file mode 100644 index 0000000000..51dcf1ba91 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoHealthProcessorTest.cs @@ -0,0 +1,110 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Beatmaps; +using osu.Game.Rulesets.Taiko.Judgements; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Scoring; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + [TestFixture] + public class TaikoHealthProcessorTest + { + [Test] + public void TestHitsOnlyGreat() + { + var beatmap = new TaikoBeatmap + { + HitObjects = + { + new Hit(), + new Hit { StartTime = 1000 }, + new Hit { StartTime = 2000 }, + new Hit { StartTime = 3000 }, + new Hit { StartTime = 4000 }, + } + }; + + var healthProcessor = new TaikoHealthProcessor(); + healthProcessor.ApplyBeatmap(beatmap); + + healthProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new TaikoJudgement()) { Type = HitResult.Great }); + healthProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], new TaikoJudgement()) { Type = HitResult.Great }); + healthProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[2], new TaikoJudgement()) { Type = HitResult.Great }); + healthProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[3], new TaikoJudgement()) { Type = HitResult.Great }); + healthProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[4], new TaikoJudgement()) { Type = HitResult.Great }); + + Assert.Multiple(() => + { + Assert.That(healthProcessor.Health.Value, Is.EqualTo(1)); + Assert.That(healthProcessor.HasFailed, Is.False); + }); + } + + [Test] + public void TestHitsAboveThreshold() + { + var beatmap = new TaikoBeatmap + { + HitObjects = + { + new Hit(), + new Hit { StartTime = 1000 }, + new Hit { StartTime = 2000 }, + new Hit { StartTime = 3000 }, + new Hit { StartTime = 4000 }, + } + }; + + var healthProcessor = new TaikoHealthProcessor(); + healthProcessor.ApplyBeatmap(beatmap); + + healthProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new TaikoJudgement()) { Type = HitResult.Great }); + healthProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], new TaikoJudgement()) { Type = HitResult.Ok }); + healthProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[2], new TaikoJudgement()) { Type = HitResult.Ok }); + healthProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[3], new TaikoJudgement()) { Type = HitResult.Ok }); + healthProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[4], new TaikoJudgement()) { Type = HitResult.Miss }); + + Assert.Multiple(() => + { + Assert.That(healthProcessor.Health.Value, Is.GreaterThan(0.5)); + Assert.That(healthProcessor.HasFailed, Is.False); + }); + } + + [Test] + public void TestHitsBelowThreshold() + { + var beatmap = new TaikoBeatmap + { + HitObjects = + { + new Hit(), + new Hit { StartTime = 1000 }, + new Hit { StartTime = 2000 }, + new Hit { StartTime = 3000 }, + new Hit { StartTime = 4000 }, + } + }; + + var healthProcessor = new TaikoHealthProcessor(); + healthProcessor.ApplyBeatmap(beatmap); + + healthProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new TaikoJudgement()) { Type = HitResult.Miss }); + healthProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], new TaikoJudgement()) { Type = HitResult.Ok }); + healthProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[2], new TaikoJudgement()) { Type = HitResult.Ok }); + healthProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[3], new TaikoJudgement()) { Type = HitResult.Ok }); + healthProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[4], new TaikoJudgement()) { Type = HitResult.Miss }); + + Assert.Multiple(() => + { + Assert.That(healthProcessor.Health.Value, Is.LessThan(0.5)); + Assert.That(healthProcessor.HasFailed, Is.True); + }); + } + } +} From 190f30a4b3c56927c312f6163b5f7920fd014adc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 16 Jan 2024 19:46:00 +0100 Subject: [PATCH 2519/4092] Add failing test cases --- .../TaikoHealthProcessorTest.cs | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoHealthProcessorTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoHealthProcessorTest.cs index 51dcf1ba91..f4a1e888c9 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoHealthProcessorTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoHealthProcessorTest.cs @@ -106,5 +106,71 @@ namespace osu.Game.Rulesets.Taiko.Tests Assert.That(healthProcessor.HasFailed, Is.True); }); } + + [Test] + public void TestDrumRollOnly() + { + var beatmap = new TaikoBeatmap + { + HitObjects = + { + new DrumRoll { Duration = 2000 } + } + }; + + foreach (var ho in beatmap.HitObjects) + ho.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty); + + var healthProcessor = new TaikoHealthProcessor(); + healthProcessor.ApplyBeatmap(beatmap); + + foreach (var nested in beatmap.HitObjects[0].NestedHitObjects) + { + var nestedJudgement = nested.CreateJudgement(); + healthProcessor.ApplyResult(new JudgementResult(nested, nestedJudgement) { Type = nestedJudgement.MaxResult }); + } + + var judgement = beatmap.HitObjects[0].CreateJudgement(); + healthProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], judgement) { Type = judgement.MaxResult }); + + Assert.Multiple(() => + { + Assert.That(healthProcessor.Health.Value, Is.EqualTo(1)); + Assert.That(healthProcessor.HasFailed, Is.False); + }); + } + + [Test] + public void TestSwellOnly() + { + var beatmap = new TaikoBeatmap + { + HitObjects = + { + new DrumRoll { Duration = 2000 } + } + }; + + foreach (var ho in beatmap.HitObjects) + ho.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty); + + var healthProcessor = new TaikoHealthProcessor(); + healthProcessor.ApplyBeatmap(beatmap); + + foreach (var nested in beatmap.HitObjects[0].NestedHitObjects) + { + var nestedJudgement = nested.CreateJudgement(); + healthProcessor.ApplyResult(new JudgementResult(nested, nestedJudgement) { Type = nestedJudgement.MaxResult }); + } + + var judgement = beatmap.HitObjects[0].CreateJudgement(); + healthProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], judgement) { Type = judgement.MaxResult }); + + Assert.Multiple(() => + { + Assert.That(healthProcessor.Health.Value, Is.EqualTo(1)); + Assert.That(healthProcessor.HasFailed, Is.False); + }); + } } } From fc37c5e4c2bb4daa7a61714555a68b43930fc211 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 16 Jan 2024 20:18:32 +0100 Subject: [PATCH 2520/4092] Fix taiko maps containing only drum rolls / swells not being passable without mods Closes https://github.com/ppy/osu/issues/26370. As was noted in the issue thread stable does not attempt to account for such maps, and the maps are impassable in stable without No Fail. Nevertheless that seems like a pretty anti-player behaviour and I honestly believe that it is fine to change this in lazer. --- .../Scoring/TaikoHealthProcessor.cs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs index d75906f3aa..dfca4ac8c7 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs @@ -31,11 +31,39 @@ namespace osu.Game.Rulesets.Taiko.Scoring /// private double hpMissMultiplier; + /// + /// Sum of all achievable health increases throughout the map. + /// Used to determine if there are any objects that give health. + /// If there are none, health will be forcibly pulled up to 1 to avoid cases of impassable maps. + /// + private double sumOfMaxHealthIncreases; + public TaikoHealthProcessor() : base(0.5) { } + protected override void ApplyResultInternal(JudgementResult result) + { + base.ApplyResultInternal(result); + sumOfMaxHealthIncreases += result.Judgement.MaxHealthIncrease; + } + + protected override void RevertResultInternal(JudgementResult result) + { + base.RevertResultInternal(result); + sumOfMaxHealthIncreases -= result.Judgement.MaxHealthIncrease; + } + + protected override void Reset(bool storeResults) + { + base.Reset(storeResults); + + if (storeResults && sumOfMaxHealthIncreases == 0) + Health.Value = 1; + sumOfMaxHealthIncreases = 0; + } + public override void ApplyBeatmap(IBeatmap beatmap) { base.ApplyBeatmap(beatmap); From 1075b87fb8bfb84ce3404bf93253940992e86671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 16 Jan 2024 22:19:20 +0100 Subject: [PATCH 2521/4092] Add failing test coverage --- .../Formats/LegacyScoreDecoderTest.cs | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index 4e281cf28e..6e7c8c3631 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -3,14 +3,18 @@ #nullable disable +using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using NUnit.Framework; +using osu.Framework.Extensions; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; +using osu.Game.Beatmaps.Legacy; +using osu.Game.IO.Legacy; using osu.Game.Replays; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; @@ -247,6 +251,123 @@ namespace osu.Game.Tests.Beatmaps.Formats }); } + [Test] + public void AccuracyAndRankOfStableScorePreserved() + { + var memoryStream = new MemoryStream(); + + // local partial implementation of legacy score encoder + // this is done half for readability, half because `LegacyScoreEncoder` forces `LATEST_VERSION` + // and we want to emulate a stable score here + using (var sw = new SerializationWriter(memoryStream, true)) + { + sw.Write((byte)0); // ruleset id (osu!) + sw.Write(20240116); // version (anything below `LegacyScoreEncoder.FIRST_LAZER_VERSION` is stable) + sw.Write(string.Empty.ComputeMD5Hash()); // beatmap hash, irrelevant to this test + sw.Write("username"); // irrelevant to this test + sw.Write(string.Empty.ComputeMD5Hash()); // score hash, irrelevant to this test + sw.Write((ushort)198); // count300 + sw.Write((ushort)1); // count100 + sw.Write((ushort)0); // count50 + sw.Write((ushort)0); // countGeki + sw.Write((ushort)0); // countKatu + sw.Write((ushort)1); // countMiss + sw.Write(12345678); // total score, irrelevant to this test + sw.Write((ushort)1000); // max combo, irrelevant to this test + sw.Write(false); // full combo, irrelevant to this test + sw.Write((int)LegacyMods.Hidden); // mods + sw.Write(string.Empty); // hp graph, irrelevant + sw.Write(DateTime.Now); // date, irrelevant + sw.Write(Array.Empty()); // replay data, irrelevant + sw.Write((long)1234); // legacy online ID, irrelevant + } + + memoryStream.Seek(0, SeekOrigin.Begin); + var decoded = new TestLegacyScoreDecoder().Parse(memoryStream); + + Assert.Multiple(() => + { + Assert.That(decoded.ScoreInfo.Accuracy, Is.EqualTo((double)(198 * 300 + 100) / (200 * 300))); + Assert.That(decoded.ScoreInfo.Rank, Is.EqualTo(ScoreRank.A)); + }); + } + + [Test] + public void AccuracyAndRankOfLazerScorePreserved() + { + var ruleset = new OsuRuleset().RulesetInfo; + + var scoreInfo = TestResources.CreateTestScoreInfo(ruleset); + scoreInfo.Mods = new Mod[] { new OsuModFlashlight() }; + scoreInfo.Statistics = new Dictionary + { + [HitResult.Great] = 199, + [HitResult.Miss] = 1, + [HitResult.LargeTickHit] = 1, + }; + scoreInfo.MaximumStatistics = new Dictionary + { + [HitResult.Great] = 200, + [HitResult.LargeTickHit] = 1, + }; + + var beatmap = new TestBeatmap(ruleset); + var score = new Score + { + ScoreInfo = scoreInfo, + }; + + var decodedAfterEncode = encodeThenDecode(LegacyBeatmapDecoder.LATEST_VERSION, score, beatmap); + + Assert.Multiple(() => + { + Assert.That(decodedAfterEncode.ScoreInfo.Accuracy, Is.EqualTo((double)(199 * 300 + 30) / (200 * 300 + 30))); + Assert.That(decodedAfterEncode.ScoreInfo.Rank, Is.EqualTo(ScoreRank.SH)); + }); + } + + [Test] + public void AccuracyAndRankOfLazerScoreWithoutLegacyReplaySoloScoreInfoUsesBestEffortFallbackToLegacy() + { + var memoryStream = new MemoryStream(); + + // local partial implementation of legacy score encoder + // this is done half for readability, half because we want to emulate an old lazer score here + // that does not have everything that `LegacyScoreEncoder` now writes to the replay + using (var sw = new SerializationWriter(memoryStream, true)) + { + sw.Write((byte)0); // ruleset id (osu!) + sw.Write(LegacyScoreEncoder.FIRST_LAZER_VERSION); // version + sw.Write(string.Empty.ComputeMD5Hash()); // beatmap hash, irrelevant to this test + sw.Write("username"); // irrelevant to this test + sw.Write(string.Empty.ComputeMD5Hash()); // score hash, irrelevant to this test + sw.Write((ushort)198); // count300 + sw.Write((ushort)0); // count100 + sw.Write((ushort)1); // count50 + sw.Write((ushort)0); // countGeki + sw.Write((ushort)0); // countKatu + sw.Write((ushort)1); // countMiss + sw.Write(12345678); // total score, irrelevant to this test + sw.Write((ushort)1000); // max combo, irrelevant to this test + sw.Write(false); // full combo, irrelevant to this test + sw.Write((int)LegacyMods.Hidden); // mods + sw.Write(string.Empty); // hp graph, irrelevant + sw.Write(DateTime.Now); // date, irrelevant + sw.Write(Array.Empty()); // replay data, irrelevant + sw.Write((long)1234); // legacy online ID, irrelevant + // importantly, no compressed `LegacyReplaySoloScoreInfo` here + } + + memoryStream.Seek(0, SeekOrigin.Begin); + var decoded = new TestLegacyScoreDecoder().Parse(memoryStream); + + Assert.Multiple(() => + { + Assert.That(decoded.ScoreInfo.Accuracy, Is.EqualTo((double)(198 * 300 + 50) / (200 * 300))); + Assert.That(decoded.ScoreInfo.Rank, Is.EqualTo(ScoreRank.A)); + }); + } + private static Score encodeThenDecode(int beatmapVersion, Score score, TestBeatmap beatmap) { var encodeStream = new MemoryStream(); From 17b9d842ab9a051ff6bc43d4ac5eb3d90dedfd93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 16 Jan 2024 21:08:02 +0100 Subject: [PATCH 2522/4092] Fix incorrect accuracy and rank population when decoding lazer replays Closes https://github.com/ppy/osu/issues/24061. The gist of this change is that if the `LegacyReplaySoloScoreInfo` bolt-on is present in the replay, then it can (and is) used to recompute the accuracy, and rank is computed based on that. This was the missing part of https://github.com/ppy/osu/issues/24061#issuecomment-1888438151. The accuracy would change on import before that because the encode process is _lossy_ if the `LegacyReplaySoloScoreInfo` bolt-on is not used, as the legacy format only has 6 fields for encoding judgement counts, and some judgements that affect accuracy in lazer do not fit into that. Note that this _only_ fixes _relatively_ new lazer scores looking wrong after reimport. - Very old lazer scores, i.e. ones that don't have the `LegacyReplaySoloScoreInfo` bolt-on, obviously can't use it to repopulate. There's really not much good that can be done there, so the stable pathways are used as a fallback that always works. - For stable replays, `ScoreImporter` recalculates the accuracy of the score _again_ in https://github.com/ppy/osu/blob/15a5fd7e4c8e8e3c38382cfd3d5d676d107d7908/osu.Game/Scoring/ScoreImporter.cs#L106-L110 as `StandardisedScoreMigrationTools.UpdateFromLegacy()` recomputes _both_ total score and accuracy. This makes a _semblance_ of sense as it attempts to make the accuracy of stable and lazer replays comparable. In most cases it also won't matter, as the only ruleset where accuracy changed between the legacy implementation and current lazer accuracy is mania. But it is also an inaccurate process (as, again, some of the required data is not in the replay, namely judgement counts of ticks and so on). For whatever's worth, a similar thing happens server-side in https://github.com/ppy/osu-queue-score-statistics/blob/106c2948dbe695efcad5972d32cd46f4b36005cc/osu.Server.Queues.ScoreStatisticsProcessor/Commands/Queue/BatchInserter.cs#L319 - However, _ranks_ of stable scores will still use the local stable reimplementation of ranks, i.e. a 1-miss stable score in osu! ruleset will be an A rather than an S. See importer: https://github.com/ppy/osu-queue-score-statistics/blob/106c2948dbe695efcad5972d32cd46f4b36005cc/osu.Server.Queues.ScoreStatisticsProcessor/Commands/Queue/BatchInserter.cs#L237 (it's the same method which is renamed to `PopulateLegacyAccuracyAndRank()` in this commit). That is all a bit of a mess honestly, but I'm not sure where to even begin there... --- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index ed11691674..b30fc7aee1 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -11,6 +11,7 @@ using Newtonsoft.Json; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Legacy; +using osu.Game.Database; using osu.Game.IO.Legacy; using osu.Game.Online.API.Requests.Responses; using osu.Game.Replays; @@ -37,6 +38,7 @@ namespace osu.Game.Scoring.Legacy }; WorkingBeatmap workingBeatmap; + byte[] compressedScoreInfo = null; using (SerializationReader sr = new SerializationReader(stream)) { @@ -105,8 +107,6 @@ namespace osu.Game.Scoring.Legacy else if (version >= 20121008) scoreInfo.LegacyOnlineID = sr.ReadInt32(); - byte[] compressedScoreInfo = null; - if (version >= 30000001) compressedScoreInfo = sr.ReadByteArray(); @@ -130,7 +130,10 @@ namespace osu.Game.Scoring.Legacy } } - PopulateAccuracy(score.ScoreInfo); + if (score.ScoreInfo.IsLegacyScore || compressedScoreInfo == null) + PopulateLegacyAccuracyAndRank(score.ScoreInfo); + else + populateLazerAccuracyAndRank(score.ScoreInfo); // before returning for database import, we must restore the database-sourced BeatmapInfo. // if not, the clone operation in GetPlayableBeatmap will cause a dereference and subsequent database exception. @@ -174,7 +177,7 @@ namespace osu.Game.Scoring.Legacy /// Legacy use only. /// /// The to populate. - public static void PopulateAccuracy(ScoreInfo score) + public static void PopulateLegacyAccuracyAndRank(ScoreInfo score) { int countMiss = score.GetCountMiss() ?? 0; int count50 = score.GetCount50() ?? 0; @@ -273,6 +276,18 @@ namespace osu.Game.Scoring.Legacy } } + private void populateLazerAccuracyAndRank(ScoreInfo scoreInfo) + { + scoreInfo.Accuracy = StandardisedScoreMigrationTools.ComputeAccuracy(scoreInfo); + + var rank = currentRuleset.CreateScoreProcessor().RankFromAccuracy(scoreInfo.Accuracy); + + foreach (var mod in scoreInfo.Mods.OfType()) + rank = mod.AdjustRank(rank, scoreInfo.Accuracy); + + scoreInfo.Rank = rank; + } + private void readLegacyReplay(Replay replay, StreamReader reader) { float lastTime = beatmapOffset; From 9cde04c30c964a62d7e1e92c0c05f3726b8acf17 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Jan 2024 14:12:12 +0900 Subject: [PATCH 2523/4092] Change drag adjustments to be linear (and account for partial deltas) --- osu.Game/Overlays/Volume/VolumeMeter.cs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 6ad314c601..a5b496b7d8 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -309,24 +309,37 @@ namespace osu.Game.Overlays.Volume private const double max_acceleration = 5; private const double acceleration_multiplier = 1.8; - private const float mouse_drag_divisor = 20; - private ScheduledDelegate accelerationDebounce; private void resetAcceleration() => accelerationModifier = 1; + private float dragDelta = 0; + protected override bool OnDragStart(DragStartEvent e) { - adjust(-e.Delta.Y / mouse_drag_divisor, true); + dragDelta = 0; + adjustFromDrag(e.Delta); return true; } protected override void OnDrag(DragEvent e) { - adjust(-e.Delta.Y / mouse_drag_divisor, true); + adjustFromDrag(e.Delta); base.OnDrag(e); } + private void adjustFromDrag(Vector2 delta) + { + const float mouse_drag_divisor = 200; + + dragDelta += delta.Y / mouse_drag_divisor; + + if (Math.Abs(dragDelta) < 0.01) return; + + Volume -= dragDelta; + dragDelta = 0; + } + private void adjust(double delta, bool isPrecise) { if (delta == 0) From f11682d44f769fe480ae1bfcfc7f324208fd49a7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Jan 2024 08:10:02 +0300 Subject: [PATCH 2524/4092] Add failing test case --- .../Visual/Editing/TestSceneTimingScreen.cs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs index 40aadc8164..56c8c05fe6 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs @@ -4,11 +4,13 @@ #nullable disable using System; +using System.Diagnostics; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Testing; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Containers; @@ -177,6 +179,43 @@ namespace osu.Game.Tests.Visual.Editing AddUntilStep("Scrolled to end", () => timingScreen.ChildrenOfType().First().IsScrolledToEnd()); } + [Test] + public void TestEditThenClickAway() + { + AddStep("Add two control points", () => + { + editorBeatmap.ControlPointInfo.Clear(); + editorBeatmap.ControlPointInfo.Add(1000, new TimingControlPoint()); + editorBeatmap.ControlPointInfo.Add(2000, new TimingControlPoint()); + }); + + AddStep("Select second timing point", () => + { + InputManager.MoveMouseTo(Child.ChildrenOfType().Last()); + InputManager.Click(MouseButton.Left); + }); + + AddStep("Scroll to end", () => timingScreen.ChildrenOfType().Single().ChildrenOfType().Single().ScrollToEnd(false)); + AddStep("Modify time signature", () => + { + var timeSignatureTextBox = Child.ChildrenOfType().Single().ChildrenOfType().Single(); + InputManager.MoveMouseTo(timeSignatureTextBox); + InputManager.Click(MouseButton.Left); + + Debug.Assert(!timeSignatureTextBox.Current.Value.Equals("1")); + timeSignatureTextBox.Current.Value = "1"; + }); + + AddStep("Select first timing point", () => + { + InputManager.MoveMouseTo(Child.ChildrenOfType().First()); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("Second timing point changed time signature", () => editorBeatmap.ControlPointInfo.TimingPoints.Last().TimeSignature.Numerator == 1); + AddAssert("First timing point preserved time signature", () => editorBeatmap.ControlPointInfo.TimingPoints.First().TimeSignature.Numerator == 4); + } + protected override void Dispose(bool isDisposing) { Beatmap.Disabled = false; From 46429c5074b05447f8934dd25bd26fcce5e46dec Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Jan 2024 08:10:37 +0300 Subject: [PATCH 2525/4092] Schedule control point switch for settings modifications to apply first --- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 7a27056da3..219575a380 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -44,11 +44,12 @@ namespace osu.Game.Screens.Edit.Timing { BackgroundFlow.Add(new RowBackground(group) { - Action = () => + // schedule to give time for any modified focused text box to lose focus and commit changes (e.g. BPM / time signature textboxes) before switching to new point. + Action = () => Schedule(() => { SetSelectedRow(group); clock.SeekSmoothlyTo(group.Time); - } + }) }); } From a34d2a3424eec99dec0efb44ae91844348f46f22 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Jan 2024 08:28:47 +0300 Subject: [PATCH 2526/4092] Add small volume meter to test feel --- .../Visual/UserInterface/TestSceneVolumePieces.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneVolumePieces.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneVolumePieces.cs index 311bae0d50..b85e4c19d1 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneVolumePieces.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneVolumePieces.cs @@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.UserInterface { VolumeMeter meter; MuteButton mute; - Add(meter = new VolumeMeter("MASTER", 125, Color4.Blue) { Position = new Vector2(10) }); + Add(meter = new VolumeMeter("MASTER", 125, Color4.Green) { Position = new Vector2(10) }); AddSliderStep("master volume", 0, 10, 0, i => meter.Bindable.Value = i * 0.1); Add(new VolumeMeter("BIG", 250, Color4.Red) @@ -22,6 +22,15 @@ namespace osu.Game.Tests.Visual.UserInterface Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(10), + Margin = new MarginPadding { Left = 250 }, + }); + + Add(new VolumeMeter("SML", 125, Color4.Blue) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Position = new Vector2(10), + Margin = new MarginPadding { Right = 500 }, }); Add(mute = new MuteButton From 1790a5df03719e0483d4abede98460f4874bb65f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Jan 2024 08:29:55 +0300 Subject: [PATCH 2527/4092] Remove redundant value to appease CI --- osu.Game/Overlays/Volume/VolumeMeter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index a5b496b7d8..9ca4c25ab9 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -313,7 +313,7 @@ namespace osu.Game.Overlays.Volume private void resetAcceleration() => accelerationModifier = 1; - private float dragDelta = 0; + private float dragDelta; protected override bool OnDragStart(DragStartEvent e) { From 05729706eb2633b74dbb97d3e2615f85cd2721e5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Jan 2024 08:53:24 +0300 Subject: [PATCH 2528/4092] Fix android build CI failing No idea why this is only in android, but okay. --- osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs index 56c8c05fe6..f3d2b5d518 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs @@ -202,7 +202,7 @@ namespace osu.Game.Tests.Visual.Editing InputManager.MoveMouseTo(timeSignatureTextBox); InputManager.Click(MouseButton.Left); - Debug.Assert(!timeSignatureTextBox.Current.Value.Equals("1")); + Debug.Assert(!timeSignatureTextBox.Current.Value.Equals("1", StringComparison.Ordinal)); timeSignatureTextBox.Current.Value = "1"; }); From 42f64c2c44f34bceb9fbbadb8a5cb6d2f8ed04aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Jan 2024 14:48:33 +0900 Subject: [PATCH 2529/4092] Move load procedure to async method and simplify code --- .../Screens/Play/HUD/PlayerSettingsOverlay.cs | 2 ++ osu.Game/Screens/Play/ReplayPlayer.cs | 20 ++++++++++--------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs index b7f3dc36c3..8c74b0254d 100644 --- a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs +++ b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs @@ -41,5 +41,7 @@ namespace osu.Game.Screens.Play.HUD protected override void PopIn() => this.FadeIn(fade_duration); protected override void PopOut() => this.FadeOut(fade_duration); + + public void Insert(int i, PlayerSettingsGroup drawable) => content.Insert(i, drawable); } } diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index f6e4ac489a..39df223a9d 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; @@ -50,18 +51,19 @@ namespace osu.Game.Screens.Play this.createScore = createScore; } - protected override void LoadComplete() + [BackgroundDependencyLoader] + private void load() { - base.LoadComplete(); - - if (HUDOverlay != null) + var playbackSettings = new PlaybackSettings { - var playerSettingsOverlay = new PlaybackSettings { Expanded = { Value = false } }; - HUDOverlay.PlayerSettingsOverlay.Add(playerSettingsOverlay); + Depth = float.MaxValue, + Expanded = { Value = false } + }; - if (GameplayClockContainer is MasterGameplayClockContainer master) - playerSettingsOverlay.UserPlaybackRate.BindTarget = master.UserPlaybackRate; - } + if (GameplayClockContainer is MasterGameplayClockContainer master) + playbackSettings.UserPlaybackRate.BindTo(master.UserPlaybackRate); + + HUDOverlay.PlayerSettingsOverlay.Insert(-1, playbackSettings); } protected override void PrepareReplay() From 042e852a3ec027721a80817a7d3a9538df5904ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Jan 2024 14:53:49 +0900 Subject: [PATCH 2530/4092] Adjust playback speed range to allow slower minimum speed --- osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 4 ++-- osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 8b8bf87436..1b7588d81a 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -35,9 +35,9 @@ namespace osu.Game.Screens.Play public readonly BindableNumber UserPlaybackRate = new BindableDouble(1) { - MinValue = 0.5, + MinValue = 0.05, MaxValue = 2, - Precision = 0.1, + Precision = 0.01, }; /// diff --git a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs index 0fe3a08985..2f37b8877f 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs @@ -22,9 +22,9 @@ namespace osu.Game.Screens.Play.PlayerSettings public readonly Bindable UserPlaybackRate = new BindableDouble(1) { - MinValue = 0.5, + MinValue = 0.05, MaxValue = 2, - Precision = 0.1, + Precision = 0.01, }; private readonly PlayerSliderBar rateSlider; @@ -149,7 +149,7 @@ namespace osu.Game.Screens.Play.PlayerSettings protected override void LoadComplete() { base.LoadComplete(); - rateSlider.Current.BindValueChanged(multiplier => multiplierText.Text = $"{multiplier.NewValue:0.0}x", true); + rateSlider.Current.BindValueChanged(multiplier => multiplierText.Text = $"{multiplier.NewValue:0.00}x", true); if (gameplayClock != null) isPaused.BindTarget = gameplayClock.IsPaused; From e53989faebebb0bf323cac4a0f76e6bebd042939 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Jan 2024 14:54:14 +0900 Subject: [PATCH 2531/4092] Change replay playback adjustment to skew frequency, not tempo --- osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 1b7588d81a..1159e5f6d9 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -274,7 +274,7 @@ namespace osu.Game.Screens.Play track.BindAdjustments(AdjustmentsFromMods); track.AddAdjustment(AdjustableProperty.Frequency, GameplayClock.ExternalPauseFrequencyAdjust); - track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); + track.AddAdjustment(AdjustableProperty.Frequency, UserPlaybackRate); speedAdjustmentsApplied = true; } @@ -286,7 +286,7 @@ namespace osu.Game.Screens.Play track.UnbindAdjustments(AdjustmentsFromMods); track.RemoveAdjustment(AdjustableProperty.Frequency, GameplayClock.ExternalPauseFrequencyAdjust); - track.RemoveAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); + track.RemoveAdjustment(AdjustableProperty.Frequency, UserPlaybackRate); speedAdjustmentsApplied = false; } From 2788bd912e87068c370b27ff40333bda3c85139a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Jan 2024 15:12:33 +0900 Subject: [PATCH 2532/4092] Add tooltips and localisation --- .../PlayerSettingsOverlayStrings.cs | 24 +++++++++++++++++++ .../Play/PlayerSettings/PlaybackSettings.cs | 19 ++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Localisation/PlayerSettingsOverlayStrings.cs diff --git a/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs b/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs new file mode 100644 index 0000000000..1aedd9fc5b --- /dev/null +++ b/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs @@ -0,0 +1,24 @@ +// 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.Localisation; + +namespace osu.Game.Localisation +{ + public static class PlayerSettingsOverlayStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.PlaybackSettings"; + + /// + /// "Seek backward {0} seconds" + /// + public static LocalisableString SeekBackwardSeconds(double arg0) => new TranslatableString(getKey(@"seek_backward_seconds"), @"Seek backward {0} seconds", arg0); + + /// + /// "Seek forward {0} seconds" + /// + public static LocalisableString SeekForwardSeconds(double arg0) => new TranslatableString(getKey(@"seek_forward_seconds"), @"Seek forward {0} seconds", arg0); + + private static string getKey(string key) => $@"{prefix}:{key}"; + } +} diff --git a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs index 2f37b8877f..44cfa8d811 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Edit.Timing; using osuTK; +using osu.Game.Localisation; namespace osu.Game.Screens.Play.PlayerSettings { @@ -71,6 +72,7 @@ namespace osu.Game.Screens.Play.PlayerSettings Origin = Anchor.Centre, Icon = FontAwesome.Solid.FastBackward, Action = () => seek(-1, seek_fast_amount), + TooltipText = PlayerSettingsOverlayStrings.SeekBackwardSeconds(seek_fast_amount / 1000), }, new SeekButton { @@ -78,6 +80,7 @@ namespace osu.Game.Screens.Play.PlayerSettings Origin = Anchor.Centre, Icon = FontAwesome.Solid.Backward, Action = () => seek(-1, seek_amount), + TooltipText = PlayerSettingsOverlayStrings.SeekBackwardSeconds(seek_amount / 1000), }, play = new IconButton { @@ -103,6 +106,7 @@ namespace osu.Game.Screens.Play.PlayerSettings Origin = Anchor.Centre, Icon = FontAwesome.Solid.Forward, Action = () => seek(1, seek_amount), + TooltipText = PlayerSettingsOverlayStrings.SeekForwardSeconds(seek_amount / 1000), }, new SeekButton { @@ -110,6 +114,7 @@ namespace osu.Game.Screens.Play.PlayerSettings Origin = Anchor.Centre, Icon = FontAwesome.Solid.FastForward, Action = () => seek(1, seek_fast_amount), + TooltipText = PlayerSettingsOverlayStrings.SeekForwardSeconds(seek_fast_amount / 1000), }, }, }, @@ -137,7 +142,19 @@ namespace osu.Game.Screens.Play.PlayerSettings }, }; - isPaused.BindValueChanged(e => play.Icon = !e.NewValue ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle, true); + isPaused.BindValueChanged(paused => + { + if (!paused.NewValue) + { + play.TooltipText = ToastStrings.PauseTrack; + play.Icon = FontAwesome.Regular.PauseCircle; + } + else + { + play.TooltipText = ToastStrings.PlayTrack; + play.Icon = FontAwesome.Regular.PlayCircle; + } + }, true); void seek(int direction, double amount) { From e7732caaf79a90c15354502ab307e8927373a46e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Jan 2024 15:13:59 +0900 Subject: [PATCH 2533/4092] Make `PlayerSettingsOverlay`'s api more stringent --- osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs | 2 +- osu.Game/Screens/Play/ReplayPlayer.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs index 8c74b0254d..a2b49f6302 100644 --- a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs +++ b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs @@ -42,6 +42,6 @@ namespace osu.Game.Screens.Play.HUD protected override void PopIn() => this.FadeIn(fade_duration); protected override void PopOut() => this.FadeOut(fade_duration); - public void Insert(int i, PlayerSettingsGroup drawable) => content.Insert(i, drawable); + public void AddAtStart(PlayerSettingsGroup drawable) => content.Insert(-1, drawable); } } diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 39df223a9d..69913c6555 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -63,7 +63,7 @@ namespace osu.Game.Screens.Play if (GameplayClockContainer is MasterGameplayClockContainer master) playbackSettings.UserPlaybackRate.BindTo(master.UserPlaybackRate); - HUDOverlay.PlayerSettingsOverlay.Insert(-1, playbackSettings); + HUDOverlay.PlayerSettingsOverlay.AddAtStart(playbackSettings); } protected override void PrepareReplay() From 266c7b28e807978ad2e71e664afd454ce5c4dc3d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Jan 2024 15:38:51 +0900 Subject: [PATCH 2534/4092] Show an empty baetmap instead of nothing --- osu.Game.Tournament/Components/SongBar.cs | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tournament/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs index cc1d00f62f..ae59e92e33 100644 --- a/osu.Game.Tournament/Components/SongBar.cs +++ b/osu.Game.Tournament/Components/SongBar.cs @@ -12,6 +12,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Legacy; using osu.Game.Extensions; using osu.Game.Graphics; +using osu.Game.Models; using osu.Game.Rulesets; using osu.Game.Screens.Menu; using osuTK; @@ -101,11 +102,25 @@ namespace osu.Game.Tournament.Components private void refreshContent() { - if (beatmap == null) + beatmap ??= new BeatmapInfo { - flow.Clear(); - return; - } + Metadata = new BeatmapMetadata + { + Artist = "unknown", + Title = "no beatmap selected", + Author = new RealmUser { Username = "unknown" }, + }, + DifficultyName = "unknown", + BeatmapSet = new BeatmapSetInfo(), + StarRating = 0, + Difficulty = new BeatmapDifficulty + { + CircleSize = 0, + DrainRate = 0, + OverallDifficulty = 0, + ApproachRate = 0, + }, + }; double bpm = beatmap.BPM; double length = beatmap.Length; From 06aa35a10e106997521389f26d59aadd1047c7d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Jan 2024 16:26:01 +0900 Subject: [PATCH 2535/4092] Fix tournament beatmap backgrounds occasionally not loading --- osu.Game.Tournament/Components/TournamentBeatmapPanel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs index 4e0adb30ac..514ba482c4 100644 --- a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs +++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs @@ -194,7 +194,7 @@ namespace osu.Game.Tournament.Components // Use DelayedLoadWrapper to avoid content unloading when switching away to another screen. protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Func createContentFunc, double timeBeforeLoad) - => new DelayedLoadWrapper(createContentFunc, timeBeforeLoad); + => new DelayedLoadWrapper(createContentFunc(), timeBeforeLoad); } } } From e97b31d82e9aa6c9dace49742ff3c246d27c3368 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Jan 2024 16:40:45 +0900 Subject: [PATCH 2536/4092] Fix test failures --- osu.Game/Screens/Play/ReplayPlayer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 69913c6555..805f907466 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -54,6 +54,9 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load() { + if (!LoadedBeatmapSuccessfully) + return; + var playbackSettings = new PlaybackSettings { Depth = float.MaxValue, From fe06402951ada9980fe5c02308cab9423bacfa20 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Jan 2024 16:50:48 +0900 Subject: [PATCH 2537/4092] Fix incorrect bindable usage --- osu.Game/Overlays/Login/LoginPanel.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Login/LoginPanel.cs b/osu.Game/Overlays/Login/LoginPanel.cs index 7db3644de6..ea65656eb1 100644 --- a/osu.Game/Overlays/Login/LoginPanel.cs +++ b/osu.Game/Overlays/Login/LoginPanel.cs @@ -38,6 +38,7 @@ namespace osu.Game.Overlays.Login public Action? RequestHide; private readonly IBindable apiState = new Bindable(); + private readonly Bindable userStatus = new Bindable(); [Resolved] private IAPIProvider api { get; set; } = null!; @@ -139,7 +140,8 @@ namespace osu.Game.Overlays.Login }, }; - api.LocalUser.Value.Status.BindValueChanged(e => updateDropdownCurrent(e.NewValue), true); + userStatus.BindTo(api.LocalUser.Value.Status); + userStatus.BindValueChanged(e => updateDropdownCurrent(e.NewValue), true); dropdown.Current.BindValueChanged(action => { From 66c7a29e794197478a597922bbc6c7028515b304 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Jan 2024 16:57:22 +0900 Subject: [PATCH 2538/4092] Add comment about messy methods --- osu.Game/Users/UserPanel.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index ef5c95c422..b88619c8b7 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -98,6 +98,8 @@ namespace osu.Game.Users }; } + // TODO: this whole api is messy. half these Create methods are expected to by the implementation and half are implictly called. + protected abstract Drawable CreateLayout(); /// From 353df2f31227a71da752e40dbdcb650e288e889d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Jan 2024 17:00:30 +0900 Subject: [PATCH 2539/4092] Fix one more incorrect bindable flow and simplify string setters --- osu.Game/Users/UserRankPanel.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/Users/UserRankPanel.cs b/osu.Game/Users/UserRankPanel.cs index a2e234cf82..a38962dfc7 100644 --- a/osu.Game/Users/UserRankPanel.cs +++ b/osu.Game/Users/UserRankPanel.cs @@ -2,11 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; -using osu.Framework.Localisation; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Profile.Header.Components; @@ -30,6 +30,8 @@ namespace osu.Game.Users private ProfileValueDisplay globalRankDisplay = null!; private ProfileValueDisplay countryRankDisplay = null!; + private readonly IBindable statistics = new Bindable(); + public UserRankPanel(APIUser user) : base(user) { @@ -42,11 +44,12 @@ namespace osu.Game.Users { BorderColour = ColourProvider?.Light1 ?? Colours.GreyVioletLighter; - api.Statistics.ValueChanged += e => + statistics.BindTo(api.Statistics); + statistics.BindValueChanged(stats => { - globalRankDisplay.Content = e.NewValue?.GlobalRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-"; - countryRankDisplay.Content = e.NewValue?.CountryRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-"; - }; + globalRankDisplay.Content = stats.NewValue?.GlobalRank?.ToLocalisableString("\\##,##0") ?? "-"; + countryRankDisplay.Content = stats.NewValue?.CountryRank?.ToLocalisableString("\\##,##0") ?? "-"; + }, true); } protected override Drawable CreateLayout() @@ -184,12 +187,10 @@ namespace osu.Game.Users globalRankDisplay = new ProfileValueDisplay(true) { Title = UsersStrings.ShowRankGlobalSimple, - Content = User.Statistics?.GlobalRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-" }, countryRankDisplay = new ProfileValueDisplay(true) { Title = UsersStrings.ShowRankCountrySimple, - Content = User.Statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-" } } } From 45e52854ca50d31182845f3de3ca9b6bd87cf86d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Jan 2024 17:37:35 +0900 Subject: [PATCH 2540/4092] Change key overlay to use the ordering provided by rulesets osu!mania already goes out of its way to order things correctly. Arguably, osu!taiko just did it wrong. --- osu.Game/Rulesets/UI/RulesetInputManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 35d05b87c0..041c7a13ae 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -167,7 +167,6 @@ namespace osu.Game.Rulesets.UI var triggers = KeyBindingContainer.DefaultKeyBindings .Select(b => b.GetAction()) .Distinct() - .OrderBy(action => action) .Select(action => new KeyCounterActionTrigger(action)) .ToArray(); From a4c0bfd099bb6a5140e1513cb5ac640c085082bd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Jan 2024 17:37:20 +0900 Subject: [PATCH 2541/4092] Change taiko's default key ordering to better match display order Fixes the issue originally fixed via https://github.com/ppy/osu/pull/10553 in a slightly different way, allowing more flexibility. --- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index ad3b7b09f7..b80d8218f5 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -69,11 +69,11 @@ namespace osu.Game.Rulesets.Taiko public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] { - new KeyBinding(InputKey.MouseLeft, TaikoAction.LeftCentre), - new KeyBinding(InputKey.MouseRight, TaikoAction.LeftRim), new KeyBinding(InputKey.D, TaikoAction.LeftRim), new KeyBinding(InputKey.F, TaikoAction.LeftCentre), + new KeyBinding(InputKey.MouseLeft, TaikoAction.LeftCentre), new KeyBinding(InputKey.J, TaikoAction.RightCentre), + new KeyBinding(InputKey.MouseRight, TaikoAction.LeftRim), new KeyBinding(InputKey.K, TaikoAction.RightRim), }; From 123e36a999e90a2698c52b03bb55c0054235d1a4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Jan 2024 17:51:46 +0900 Subject: [PATCH 2542/4092] Change display text for beatmap audio offset adjust to make more sense --- osu.Game/Localisation/BeatmapOffsetControlStrings.cs | 4 ++-- osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Localisation/BeatmapOffsetControlStrings.cs b/osu.Game/Localisation/BeatmapOffsetControlStrings.cs index 632a1ad0ea..b905b7ae1c 100644 --- a/osu.Game/Localisation/BeatmapOffsetControlStrings.cs +++ b/osu.Game/Localisation/BeatmapOffsetControlStrings.cs @@ -10,9 +10,9 @@ namespace osu.Game.Localisation private const string prefix = @"osu.Game.Resources.Localisation.BeatmapOffsetControl"; /// - /// "Beatmap offset" + /// "Audio offset (this beatmap)" /// - public static LocalisableString BeatmapOffset => new TranslatableString(getKey(@"beatmap_offset"), @"Beatmap offset"); + public static LocalisableString AudioOffsetThisBeatmap => new TranslatableString(getKey(@"beatmap_offset"), @"Audio offset (this beatmap)"); /// /// "Previous play:" diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 8efb80e771..7ac9c7186e 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -86,7 +86,7 @@ namespace osu.Game.Screens.Play.PlayerSettings new OffsetSliderBar { KeyboardStep = 5, - LabelText = BeatmapOffsetControlStrings.BeatmapOffset, + LabelText = BeatmapOffsetControlStrings.AudioOffsetThisBeatmap, Current = Current, }, referenceScoreContainer = new FillFlowContainer From cc7be137bc22415dbc3637189a638fb70910f014 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Jan 2024 17:57:49 +0900 Subject: [PATCH 2543/4092] Show tooltip on global offset adjust slider bar --- .../Settings/Sections/Audio/AudioOffsetAdjustControl.cs | 9 ++++++++- .../Screens/Play/PlayerSettings/BeatmapOffsetControl.cs | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs index 90f5a59215..ef1691534f 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs @@ -12,12 +12,14 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Localisation; +using osu.Game.Screens.Play.PlayerSettings; using osuTK; namespace osu.Game.Overlays.Settings.Sections.Audio @@ -67,7 +69,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio Direction = FillDirection.Vertical, Children = new Drawable[] { - new TimeSlider + new OffsetSliderBar { RelativeSizeAxes = Axes.X, Current = { BindTarget = Current }, @@ -157,6 +159,11 @@ namespace osu.Game.Overlays.Settings.Sections.Audio : $@"Based on the last {averageHitErrorHistory.Count} play(s), the suggested offset is {SuggestedOffset.Value:N0} ms."; applySuggestion.Enabled.Value = SuggestedOffset.Value != null; } + + private partial class OffsetSliderBar : RoundedSliderBar + { + public override LocalisableString TooltipText => BeatmapOffsetControl.GetOffsetExplanatoryText(Current.Value); + } } } } diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 7ac9c7186e..9039604471 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -307,7 +307,7 @@ namespace osu.Game.Screens.Play.PlayerSettings } } - public partial class OffsetSliderBar : PlayerSliderBar + private partial class OffsetSliderBar : PlayerSliderBar { protected override Drawable CreateControl() => new CustomSliderBar(); From a66ddc78136a877a697fec6d5232206db328439a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Jan 2024 18:18:53 +0900 Subject: [PATCH 2544/4092] Change rolling counters to use quicker easing types --- osu.Game/Graphics/UserInterface/PercentageCounter.cs | 2 +- osu.Game/Graphics/UserInterface/RollingCounter.cs | 2 +- osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs | 2 +- osu.Game/Overlays/Mods/ScoreMultiplierDisplay.cs | 2 +- osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs | 2 +- osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs | 3 +-- osu.Game/Screens/Play/HUD/ArgonComboCounter.cs | 3 +-- osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs | 3 +-- osu.Game/Screens/Play/HUD/BPMCounter.cs | 2 +- .../Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs | 2 +- osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs | 2 +- osu.Game/Screens/Play/HUD/UnstableRateCounter.cs | 2 +- .../Ranking/Expanded/Statistics/AccuracyStatistic.cs | 7 ++++--- 13 files changed, 16 insertions(+), 18 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PercentageCounter.cs b/osu.Game/Graphics/UserInterface/PercentageCounter.cs index ed06211e8f..1f9103b3bd 100644 --- a/osu.Game/Graphics/UserInterface/PercentageCounter.cs +++ b/osu.Game/Graphics/UserInterface/PercentageCounter.cs @@ -14,7 +14,7 @@ namespace osu.Game.Graphics.UserInterface /// public partial class PercentageCounter : RollingCounter { - protected override double RollingDuration => 750; + protected override double RollingDuration => 375; private float epsilon => 1e-10f; diff --git a/osu.Game/Graphics/UserInterface/RollingCounter.cs b/osu.Game/Graphics/UserInterface/RollingCounter.cs index b80c0e3b58..e69727e047 100644 --- a/osu.Game/Graphics/UserInterface/RollingCounter.cs +++ b/osu.Game/Graphics/UserInterface/RollingCounter.cs @@ -45,7 +45,7 @@ namespace osu.Game.Graphics.UserInterface /// /// Easing for the counter rollover animation. /// - protected virtual Easing RollingEasing => Easing.OutQuint; + protected virtual Easing RollingEasing => Easing.OutQuad; private T displayedCount; diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index 94dd96ec1c..b9e4896b21 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -196,7 +196,7 @@ namespace osu.Game.Overlays.Mods private partial class BPMDisplay : RollingCounter { - protected override double RollingDuration => 500; + protected override double RollingDuration => 250; protected override LocalisableString FormatCount(double count) => count.ToLocalisableString("0 BPM"); diff --git a/osu.Game/Overlays/Mods/ScoreMultiplierDisplay.cs b/osu.Game/Overlays/Mods/ScoreMultiplierDisplay.cs index b599b53082..a86eba81e4 100644 --- a/osu.Game/Overlays/Mods/ScoreMultiplierDisplay.cs +++ b/osu.Game/Overlays/Mods/ScoreMultiplierDisplay.cs @@ -145,7 +145,7 @@ namespace osu.Game.Overlays.Mods private partial class EffectCounter : RollingCounter { - protected override double RollingDuration => 500; + protected override double RollingDuration => 250; protected override LocalisableString FormatCount(double count) => ModUtils.FormatScoreMultiplier(count); diff --git a/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs b/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs index 33b7eaae1c..a3e24b486f 100644 --- a/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs +++ b/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs @@ -124,7 +124,7 @@ namespace osu.Game.Overlays.Mods private partial class EffectCounter : RollingCounter { - protected override double RollingDuration => 500; + protected override double RollingDuration => 250; protected override LocalisableString FormatCount(double count) => count.ToLocalisableString("0.0#"); diff --git a/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs index 171aa3f44b..ca00ab12c7 100644 --- a/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs @@ -17,8 +17,7 @@ namespace osu.Game.Screens.Play.HUD { public partial class ArgonAccuracyCounter : GameplayAccuracyCounter, ISerialisableDrawable { - protected override double RollingDuration => 500; - protected override Easing RollingEasing => Easing.OutQuint; + protected override double RollingDuration => 250; [SettingSource("Wireframe opacity", "Controls the opacity of the wire frames behind the digits.")] public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.25f) diff --git a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs index 1d6ca3c893..369c753cb0 100644 --- a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs @@ -21,8 +21,7 @@ namespace osu.Game.Screens.Play.HUD { private ArgonCounterTextComponent text = null!; - protected override double RollingDuration => 500; - protected override Easing RollingEasing => Easing.OutQuint; + protected override double RollingDuration => 250; [SettingSource("Wireframe opacity", "Controls the opacity of the wire frames behind the digits.")] public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.25f) diff --git a/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs b/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs index f7ca218767..348327d710 100644 --- a/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs @@ -18,8 +18,7 @@ namespace osu.Game.Screens.Play.HUD { private ArgonScoreTextComponent scoreText = null!; - protected override double RollingDuration => 500; - protected override Easing RollingEasing => Easing.OutQuint; + protected override double RollingDuration => 250; [SettingSource("Wireframe opacity", "Controls the opacity of the wire frames behind the digits.")] public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.25f) diff --git a/osu.Game/Screens/Play/HUD/BPMCounter.cs b/osu.Game/Screens/Play/HUD/BPMCounter.cs index cd24237493..9cd285db4c 100644 --- a/osu.Game/Screens/Play/HUD/BPMCounter.cs +++ b/osu.Game/Screens/Play/HUD/BPMCounter.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Play.HUD { public partial class BPMCounter : RollingCounter, ISerialisableDrawable { - protected override double RollingDuration => 750; + protected override double RollingDuration => 375; [Resolved] private IBindable beatmap { get; set; } = null!; diff --git a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs index 9b5ea309b0..a1cccdef0a 100644 --- a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs +++ b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs @@ -19,7 +19,7 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond [Resolved] private ClicksPerSecondController controller { get; set; } = null!; - protected override double RollingDuration => 350; + protected override double RollingDuration => 175; public bool UsesFixedAnchor { get; set; } diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index 82f116b4ae..f041e120f6 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Play.HUD protected override bool IsRollingProportional => true; - protected override double RollingDuration => 1000; + protected override double RollingDuration => 500; private const float alpha_when_invalid = 0.3f; diff --git a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs index 701b8a8732..ab7ab6b3a0 100644 --- a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs +++ b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs @@ -23,7 +23,7 @@ namespace osu.Game.Screens.Play.HUD { public bool UsesFixedAnchor { get; set; } - protected override double RollingDuration => 750; + protected override double RollingDuration => 375; private const float alpha_when_invalid = 0.3f; private readonly Bindable valid = new Bindable(); diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/AccuracyStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/AccuracyStatistic.cs index 4b8c057235..f1f2c47e20 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/AccuracyStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/AccuracyStatistic.cs @@ -44,9 +44,10 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics private partial class Counter : RollingCounter { - protected override double RollingDuration => AccuracyCircle.ACCURACY_TRANSFORM_DURATION; - - protected override Easing RollingEasing => AccuracyCircle.ACCURACY_TRANSFORM_EASING; + // FormatAccuracy doesn't round, which means if we use the OutPow10 easing the number will stick 0.01% short for some time. + // To avoid that let's use a shorter easing which looks roughly the same. + protected override double RollingDuration => AccuracyCircle.ACCURACY_TRANSFORM_DURATION / 2; + protected override Easing RollingEasing => Easing.OutQuad; protected override LocalisableString FormatCount(double count) => count.FormatAccuracy(); From 5ad2918a75d59c24ecc1d8a66cf6d4292b259bbd Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Jan 2024 11:21:44 +0300 Subject: [PATCH 2545/4092] Allow disabling high resolution texture lookups in `LegacySkin` --- .../Skinning/LegacySkinTextureFallbackTest.cs | 21 +++++++++++++++ osu.Game/Skinning/LegacySkin.cs | 26 ++++++++++++++----- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs b/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs index ca0d4d3cf3..3a7b9af0cb 100644 --- a/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs +++ b/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs @@ -127,8 +127,29 @@ namespace osu.Game.Tests.NonVisual.Skinning Assert.IsNull(texture); } + [Test] + public void TestDisallowHighResolutionSprites() + { + var textureStore = new TestTextureStore("hitcircle", "hitcircle@2x"); + var legacySkin = new TestLegacySkin(textureStore) { HighResolutionSprites = false }; + + var texture = legacySkin.GetTexture("hitcircle"); + + Assert.IsNotNull(texture); + Assert.That(texture.ScaleAdjust, Is.EqualTo(1)); + + texture = legacySkin.GetTexture("hitcircle@2x"); + + Assert.IsNotNull(texture); + Assert.That(texture.ScaleAdjust, Is.EqualTo(1)); + } + private class TestLegacySkin : LegacySkin { + public bool HighResolutionSprites { get; set; } = true; + + protected override bool AllowHighResolutionSprites => HighResolutionSprites; + public TestLegacySkin(IResourceStore textureStore) : base(new SkinInfo(), new TestResourceProvider(textureStore), null, string.Empty) { diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 8f0cd59b68..10913e7369 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -477,6 +477,11 @@ namespace osu.Game.Skinning return null; } + /// + /// Whether high-resolution textures ("@2x"-suffixed) are allowed to be used by when available. + /// + protected virtual bool AllowHighResolutionSprites => true; + public override Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) { switch (componentName) @@ -488,15 +493,22 @@ namespace osu.Game.Skinning foreach (string name in getFallbackNames(componentName)) { - // some component names (especially user-controlled ones, like `HitX` in mania) - // may contain `@2x` scale specifications. - // stable happens to check for that and strip them, so do the same to match stable behaviour. - string lookupName = name.Replace(@"@2x", string.Empty); + string lookupName = name; + Texture? texture = null; + float ratio = 1; - float ratio = 2; - string twoTimesFilename = $"{Path.ChangeExtension(lookupName, null)}@2x{Path.GetExtension(lookupName)}"; + if (AllowHighResolutionSprites) + { + // some component names (especially user-controlled ones, like `HitX` in mania) + // may contain `@2x` scale specifications. + // stable happens to check for that and strip them, so do the same to match stable behaviour. + lookupName = name.Replace(@"@2x", string.Empty); - var texture = Textures?.Get(twoTimesFilename, wrapModeS, wrapModeT); + string twoTimesFilename = $"{Path.ChangeExtension(lookupName, null)}@2x{Path.GetExtension(lookupName)}"; + + texture = Textures?.Get(twoTimesFilename, wrapModeS, wrapModeT); + ratio = 2; + } if (texture == null) { From 98c65f36c9b81b352f4d2eb575eba0cc7fdf1ed3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Jan 2024 11:21:56 +0300 Subject: [PATCH 2546/4092] Disable high resolution texture lookup for legacy beatmap skins to match stable --- osu.Game/Skinning/LegacyBeatmapSkin.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index d6ba6ea332..9cd072b607 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -22,6 +22,11 @@ namespace osu.Game.Skinning protected override bool AllowManiaConfigLookups => false; protected override bool UseCustomSampleBanks => true; + // matches stable. references: + // 1. https://github.com/peppy/osu-stable-reference/blob/dc0994645801010d4b628fff5ff79cd3c286ca83/osu!/Graphics/Textures/TextureManager.cs#L115-L137 (beatmap skin textures lookup) + // 2. https://github.com/peppy/osu-stable-reference/blob/dc0994645801010d4b628fff5ff79cd3c286ca83/osu!/Graphics/Textures/TextureManager.cs#L158-L196 (user skin textures lookup) + protected override bool AllowHighResolutionSprites => false; + /// /// Construct a new legacy beatmap skin instance. /// From 1af5d1434c0abc615984fb64ef72d9d83d803740 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Jan 2024 19:53:23 +0900 Subject: [PATCH 2547/4092] Rename test method slightly --- osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs index f3d2b5d518..6181024230 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs @@ -180,7 +180,7 @@ namespace osu.Game.Tests.Visual.Editing } [Test] - public void TestEditThenClickAway() + public void TestEditThenClickAwayAppliesChanges() { AddStep("Add two control points", () => { From a8970d7642a6a2aa1834f4008ddc900997629dae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 17 Jan 2024 12:24:48 +0100 Subject: [PATCH 2548/4092] Jiggle ordering a bit further to fix revert-to-default buttons showing up --- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index b80d8218f5..24b0ec5d57 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -69,11 +69,11 @@ namespace osu.Game.Rulesets.Taiko public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] { - new KeyBinding(InputKey.D, TaikoAction.LeftRim), - new KeyBinding(InputKey.F, TaikoAction.LeftCentre), - new KeyBinding(InputKey.MouseLeft, TaikoAction.LeftCentre), - new KeyBinding(InputKey.J, TaikoAction.RightCentre), new KeyBinding(InputKey.MouseRight, TaikoAction.LeftRim), + new KeyBinding(InputKey.D, TaikoAction.LeftRim), + new KeyBinding(InputKey.MouseLeft, TaikoAction.LeftCentre), + new KeyBinding(InputKey.F, TaikoAction.LeftCentre), + new KeyBinding(InputKey.J, TaikoAction.RightCentre), new KeyBinding(InputKey.K, TaikoAction.RightRim), }; From bcc7dd6af574ecbfb4004f5aaa6265f149578d86 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Jan 2024 15:58:27 +0300 Subject: [PATCH 2549/4092] Add extended test coverage --- .../Skinning/LegacySkinTextureFallbackTest.cs | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs b/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs index 3a7b9af0cb..ff954211eb 100644 --- a/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs +++ b/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs @@ -138,10 +138,31 @@ namespace osu.Game.Tests.NonVisual.Skinning Assert.IsNotNull(texture); Assert.That(texture.ScaleAdjust, Is.EqualTo(1)); - texture = legacySkin.GetTexture("hitcircle@2x"); + var twoTimesTexture = legacySkin.GetTexture("hitcircle@2x"); + + Assert.IsNotNull(twoTimesTexture); + Assert.That(twoTimesTexture.ScaleAdjust, Is.EqualTo(1)); + + Assert.AreNotEqual(texture, twoTimesTexture); + } + + [Test] + public void TestAllowHighResolutionSprites() + { + var textureStore = new TestTextureStore("hitcircle", "hitcircle@2x"); + var legacySkin = new TestLegacySkin(textureStore) { HighResolutionSprites = true }; + + var texture = legacySkin.GetTexture("hitcircle"); Assert.IsNotNull(texture); - Assert.That(texture.ScaleAdjust, Is.EqualTo(1)); + Assert.That(texture.ScaleAdjust, Is.EqualTo(2)); + + var twoTimesTexture = legacySkin.GetTexture("hitcircle@2x"); + + Assert.IsNotNull(twoTimesTexture); + Assert.That(twoTimesTexture.ScaleAdjust, Is.EqualTo(2)); + + Assert.AreEqual(texture, twoTimesTexture); } private class TestLegacySkin : LegacySkin From e54d20ea93514a017889ee560f92e06d7a28d37f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Jan 2024 13:27:44 +0300 Subject: [PATCH 2550/4092] Remove ancient osu-resources lookup path from legacy skin textures --- .../Skinning/Legacy/LegacyApproachCircle.cs | 2 +- .../Skinning/Legacy/LegacyReverseArrow.cs | 8 +-- .../Skinning/GameplaySkinComponentLookup.cs | 4 -- osu.Game/Skinning/LegacySkin.cs | 57 ++++++++----------- 4 files changed, 29 insertions(+), 42 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyApproachCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyApproachCircle.cs index eea6606233..d31c763c2c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyApproachCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyApproachCircle.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private DrawableHitObject drawableObject { get; set; } = null!; public LegacyApproachCircle() - : base("Gameplay/osu/approachcircle", OsuHitObject.OBJECT_DIMENSIONS * 2) + : base(@"approachcircle", OsuHitObject.OBJECT_DIMENSIONS * 2) { } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs index 780084115d..ad1fb98aef 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs @@ -34,19 +34,19 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy [BackgroundDependencyLoader] private void load(DrawableHitObject drawableObject, ISkinSource skinSource) { + const string lookup_name = @"reversearrow"; + drawableRepeat = (DrawableSliderRepeat)drawableObject; AutoSizeAxes = Axes.Both; - string lookupName = new OsuSkinComponentLookup(OsuSkinComponents.ReverseArrow).LookupName; - - var skin = skinSource.FindProvider(s => s.GetTexture(lookupName) != null); + var skin = skinSource.FindProvider(s => s.GetTexture(lookup_name) != null); InternalChild = arrow = new Sprite { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Texture = skin?.GetTexture(lookupName)?.WithMaximumSize(maxSize: OsuHitObject.OBJECT_DIMENSIONS * 2), + Texture = skin?.GetTexture(lookup_name)?.WithMaximumSize(maxSize: OsuHitObject.OBJECT_DIMENSIONS * 2), }; textureIsDefaultSkin = skin is ISkinTransformer transformer && transformer.Skin is DefaultLegacySkin; diff --git a/osu.Game/Skinning/GameplaySkinComponentLookup.cs b/osu.Game/Skinning/GameplaySkinComponentLookup.cs index a44bf3a43d..ec159873f8 100644 --- a/osu.Game/Skinning/GameplaySkinComponentLookup.cs +++ b/osu.Game/Skinning/GameplaySkinComponentLookup.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; @@ -28,8 +27,5 @@ namespace osu.Game.Skinning protected virtual string RulesetPrefix => string.Empty; protected virtual string ComponentName => Component.ToString(); - - public string LookupName => - string.Join('/', new[] { "Gameplay", RulesetPrefix, ComponentName }.Where(s => !string.IsNullOrEmpty(s))); } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 10913e7369..cfa5f972d2 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -491,39 +491,30 @@ namespace osu.Game.Skinning break; } - foreach (string name in getFallbackNames(componentName)) + Texture? texture = null; + float ratio = 1; + + if (AllowHighResolutionSprites) { - string lookupName = name; - Texture? texture = null; - float ratio = 1; + // some component names (especially user-controlled ones, like `HitX` in mania) + // may contain `@2x` scale specifications. + // stable happens to check for that and strip them, so do the same to match stable behaviour. + componentName = componentName.Replace(@"@2x", string.Empty); - if (AllowHighResolutionSprites) - { - // some component names (especially user-controlled ones, like `HitX` in mania) - // may contain `@2x` scale specifications. - // stable happens to check for that and strip them, so do the same to match stable behaviour. - lookupName = name.Replace(@"@2x", string.Empty); + string twoTimesFilename = $"{Path.ChangeExtension(componentName, null)}@2x{Path.GetExtension(componentName)}"; - string twoTimesFilename = $"{Path.ChangeExtension(lookupName, null)}@2x{Path.GetExtension(lookupName)}"; + texture = Textures?.Get(twoTimesFilename, wrapModeS, wrapModeT); - texture = Textures?.Get(twoTimesFilename, wrapModeS, wrapModeT); + if (texture != null) ratio = 2; - } - - if (texture == null) - { - ratio = 1; - texture = Textures?.Get(lookupName, wrapModeS, wrapModeT); - } - - if (texture == null) - continue; - - texture.ScaleAdjust = ratio; - return texture; } - return null; + texture ??= Textures?.Get(componentName, wrapModeS, wrapModeT); + + if (texture != null) + texture.ScaleAdjust = ratio; + + return texture; } public override ISample? GetSample(ISampleInfo sampleInfo) @@ -534,7 +525,7 @@ namespace osu.Game.Skinning lookupNames = getLegacyLookupNames(hitSample); else { - lookupNames = sampleInfo.LookupNames.SelectMany(getFallbackNames); + lookupNames = sampleInfo.LookupNames.SelectMany(getFallbackSampleNames); } foreach (string lookup in lookupNames) @@ -552,7 +543,7 @@ namespace osu.Game.Skinning private IEnumerable getLegacyLookupNames(HitSampleInfo hitSample) { - var lookupNames = hitSample.LookupNames.SelectMany(getFallbackNames); + var lookupNames = hitSample.LookupNames.SelectMany(getFallbackSampleNames); if (!UseCustomSampleBanks && !string.IsNullOrEmpty(hitSample.Suffix)) { @@ -571,13 +562,13 @@ namespace osu.Game.Skinning yield return hitSample.Name; } - private IEnumerable getFallbackNames(string componentName) + private IEnumerable getFallbackSampleNames(string name) { - // May be something like "Gameplay/osu/approachcircle" from lazer, or "Arrows/note1" from a user skin. - yield return componentName; + // May be something like "Gameplay/normal-hitnormal" from lazer. + yield return name; - // Fall back to using the last piece for components coming from lazer (e.g. "Gameplay/osu/approachcircle" -> "approachcircle"). - yield return componentName.Split('/').Last(); + // Fall back to using the last piece for components coming from lazer (e.g. "Gameplay/normal-hitnormal" -> "normal-hitnormal"). + yield return name.Split('/').Last(); } } } From ed1e66b8f99032828b3e52a02b1baf5b10accaeb Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Jan 2024 16:35:39 +0300 Subject: [PATCH 2551/4092] Fix enabling beatmap skin cause hitobjects to use `LegacyApproachCircle` regardless of selected skin --- .../Skinning/Legacy/OsuLegacySkinTransformer.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index c01d28c8e1..86ca5fff8c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using NUnit.Framework; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Rulesets.Osu.Objects; @@ -163,7 +164,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return null; case OsuSkinComponents.ApproachCircle: - return new LegacyApproachCircle(); + if (IsProvidingLegacyResources) + return new LegacyApproachCircle(); + + return null; default: throw new UnsupportedSkinComponentException(lookup); From ede0a2269329cde6afbbc82d185b664f17591327 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Jan 2024 16:58:47 +0300 Subject: [PATCH 2552/4092] Remove existing test coverage --- .../Skinning/LegacySkinTextureFallbackTest.cs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs b/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs index 3a7b9af0cb..89103cd4d9 100644 --- a/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs +++ b/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs @@ -56,24 +56,6 @@ namespace osu.Game.Tests.NonVisual.Skinning "Gameplay/osu/followpoint", 1 }, new object[] - { - new[] { "followpoint@2x", "followpoint" }, - "Gameplay/osu/followpoint", - "followpoint@2x", 2 - }, - new object[] - { - new[] { "followpoint@2x" }, - "Gameplay/osu/followpoint", - "followpoint@2x", 2 - }, - new object[] - { - new[] { "followpoint" }, - "Gameplay/osu/followpoint", - "followpoint", 1 - }, - new object[] { // Looking up a filename with extension specified should work. new[] { "followpoint.png" }, From b6422bc8bdb88a5b19b41992fb90bec6a966368d Mon Sep 17 00:00:00 2001 From: smallketchup82 <69545310+smallketchup82@users.noreply.github.com> Date: Wed, 17 Jan 2024 09:07:17 -0500 Subject: [PATCH 2553/4092] Apply suggested changes - Change difficultyicon mods parameter docstring to be more professional - Add a parameter for controlling whether the difficulty statistics show or not. Defaults to false - Round the BPM in the tooltip to make sure it displays correctly --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 10 ++++-- .../Drawables/DifficultyIconTooltip.cs | 34 ++++++++++--------- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 2 +- 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 44981003f2..9c2a435cb0 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -42,6 +42,8 @@ namespace osu.Game.Beatmaps.Drawables private readonly Mod[]? mods; + private readonly bool showTooltip; + private Drawable background = null!; private readonly Container iconContainer; @@ -61,13 +63,15 @@ namespace osu.Game.Beatmaps.Drawables /// Creates a new . Will use provided beatmap's for initial value. /// /// The beatmap to be displayed in the tooltip, and to be used for the initial star rating value. - /// The mods type beat + /// An array of mods to account for in the calculations /// An optional ruleset to be used for the icon display, in place of the beatmap's ruleset. - public DifficultyIcon(IBeatmapInfo beatmap, IRulesetInfo? ruleset = null, Mod[]? mods = null) + /// Whether to display a tooltip on hover. Defaults to false. + public DifficultyIcon(IBeatmapInfo beatmap, IRulesetInfo? ruleset = null, Mod[]? mods = null, bool showTooltip = false) : this(ruleset ?? beatmap.Ruleset) { this.beatmap = beatmap; this.mods = mods; + this.showTooltip = showTooltip; Current.Value = new StarDifficulty(beatmap.StarRating, 0); } @@ -134,6 +138,6 @@ namespace osu.Game.Beatmaps.Drawables GetCustomTooltip() => new DifficultyIconTooltip(); DifficultyIconTooltipContent IHasCustomTooltip. - TooltipContent => (ShowTooltip && beatmap != null ? new DifficultyIconTooltipContent(beatmap, Current, ruleset, mods) : null)!; + TooltipContent => (ShowTooltip && beatmap != null ? new DifficultyIconTooltipContent(beatmap, Current, ruleset, mods, showTooltip) : null)!; } } diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index a4ba2a27f6..c5e276e6b4 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -31,6 +31,9 @@ namespace osu.Game.Beatmaps.Drawables private OsuSpriteText maxCombo; private OsuSpriteText length; + private FillFlowContainer difficultyFillFlowContainer; + private FillFlowContainer miscFillFlowContainer; + [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -69,21 +72,16 @@ namespace osu.Game.Beatmaps.Drawables Origin = Anchor.Centre, }, // Difficulty stats - new FillFlowContainer + difficultyFillFlowContainer = new FillFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, + Alpha = 0, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Spacing = new Vector2(5), Children = new Drawable[] { - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold), - }, circleSize = new OsuSpriteText { Anchor = Anchor.Centre, @@ -111,21 +109,16 @@ namespace osu.Game.Beatmaps.Drawables } }, // Misc stats - new FillFlowContainer + miscFillFlowContainer = new FillFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, + Alpha = 0, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Spacing = new Vector2(5), Children = new Drawable[] { - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold), - }, length = new OsuSpriteText { Anchor = Anchor.Centre, @@ -164,6 +157,13 @@ namespace osu.Game.Beatmaps.Drawables starRating.Current.BindTarget = displayedContent.Difficulty; difficultyName.Text = displayedContent.BeatmapInfo.DifficultyName; + // Don't show difficulty stats if showTooltip is false + if (!displayedContent.ShowTooltip) return; + + // Show the difficulty stats if showTooltip is true + difficultyFillFlowContainer.Show(); + miscFillFlowContainer.Show(); + double rate = 1; if (displayedContent.Mods != null) @@ -195,7 +195,7 @@ namespace osu.Game.Beatmaps.Drawables // Misc row length.Text = "Length: " + TimeSpan.FromMilliseconds(displayedContent.BeatmapInfo.Length / rate).ToString("mm\\:ss"); - bpm.Text = " BPM: " + bpmAdjusted; + bpm.Text = " BPM: " + Math.Round(bpmAdjusted, 0); maxCombo.Text = " Max Combo: " + displayedContent.BeatmapInfo.TotalObjectCount; } @@ -212,13 +212,15 @@ namespace osu.Game.Beatmaps.Drawables public readonly IBindable Difficulty; public readonly IRulesetInfo Ruleset; public readonly Mod[] Mods; + public readonly bool ShowTooltip; - public DifficultyIconTooltipContent(IBeatmapInfo beatmapInfo, IBindable difficulty, IRulesetInfo rulesetInfo, Mod[] mods) + public DifficultyIconTooltipContent(IBeatmapInfo beatmapInfo, IBindable difficulty, IRulesetInfo rulesetInfo, Mod[] mods, bool showTooltip = false) { BeatmapInfo = beatmapInfo; Difficulty = difficulty; Ruleset = rulesetInfo; Mods = mods; + ShowTooltip = showTooltip; } } } diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index eb23ed6f8f..2a6387871f 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -283,7 +283,7 @@ namespace osu.Game.Screens.OnlinePlay } if (beatmap != null) - difficultyIconContainer.Child = new DifficultyIcon(beatmap, ruleset, requiredMods) { Size = new Vector2(icon_height) }; + difficultyIconContainer.Child = new DifficultyIcon(beatmap, ruleset, requiredMods, true) { Size = new Vector2(icon_height) }; else difficultyIconContainer.Clear(); From eaa748f075948e0162db1b38503df2a4ed203ada Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Jan 2024 18:35:34 +0300 Subject: [PATCH 2554/4092] Remove unused using directive --- .../Skinning/Legacy/OsuLegacySkinTransformer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 86ca5fff8c..b01c18031c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using NUnit.Framework; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Rulesets.Osu.Objects; From 5597b48882b4ae4a00e3869b9bb42133db036ae2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Jan 2024 16:38:36 +0300 Subject: [PATCH 2555/4092] Fix storyboard animations stripping path directory on skin lookup --- osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index cefd51b2aa..fae9ec7f2e 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -129,7 +129,7 @@ namespace osu.Game.Storyboards.Drawables // When reading from a skin, we match stables weird behaviour where `FrameCount` is ignored // and resources are retrieved until the end of the animation. - var skinTextures = skin.GetTextures(Path.GetFileNameWithoutExtension(Animation.Path)!, default, default, true, string.Empty, null, out _); + var skinTextures = skin.GetTextures(Path.ChangeExtension(Animation.Path, null), default, default, true, string.Empty, null, out _); if (skinTextures.Length > 0) { From 9a6541356eb32899ffc54bd1db2a151e3db5b42e Mon Sep 17 00:00:00 2001 From: smallketchup82 <69545310+smallketchup82@users.noreply.github.com> Date: Wed, 17 Jan 2024 13:31:23 -0500 Subject: [PATCH 2556/4092] Refactor the extended tooltip variables to be more descriptive --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 10 +++++----- osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 9c2a435cb0..7a9b2fe389 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -42,7 +42,7 @@ namespace osu.Game.Beatmaps.Drawables private readonly Mod[]? mods; - private readonly bool showTooltip; + private readonly bool showExtendedTooltip; private Drawable background = null!; @@ -65,13 +65,13 @@ namespace osu.Game.Beatmaps.Drawables /// The beatmap to be displayed in the tooltip, and to be used for the initial star rating value. /// An array of mods to account for in the calculations /// An optional ruleset to be used for the icon display, in place of the beatmap's ruleset. - /// Whether to display a tooltip on hover. Defaults to false. - public DifficultyIcon(IBeatmapInfo beatmap, IRulesetInfo? ruleset = null, Mod[]? mods = null, bool showTooltip = false) + /// Whether to include the difficulty stats in the tooltip or not. Defaults to false + public DifficultyIcon(IBeatmapInfo beatmap, IRulesetInfo? ruleset = null, Mod[]? mods = null, bool showExtendedTooltip = false) : this(ruleset ?? beatmap.Ruleset) { this.beatmap = beatmap; this.mods = mods; - this.showTooltip = showTooltip; + this.showExtendedTooltip = showExtendedTooltip; Current.Value = new StarDifficulty(beatmap.StarRating, 0); } @@ -138,6 +138,6 @@ namespace osu.Game.Beatmaps.Drawables GetCustomTooltip() => new DifficultyIconTooltip(); DifficultyIconTooltipContent IHasCustomTooltip. - TooltipContent => (ShowTooltip && beatmap != null ? new DifficultyIconTooltipContent(beatmap, Current, ruleset, mods, showTooltip) : null)!; + TooltipContent => (ShowTooltip && beatmap != null ? new DifficultyIconTooltipContent(beatmap, Current, ruleset, mods, showExtendedTooltip) : null)!; } } diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index c5e276e6b4..fae4100473 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -157,10 +157,10 @@ namespace osu.Game.Beatmaps.Drawables starRating.Current.BindTarget = displayedContent.Difficulty; difficultyName.Text = displayedContent.BeatmapInfo.DifficultyName; - // Don't show difficulty stats if showTooltip is false - if (!displayedContent.ShowTooltip) return; + // Don't show difficulty stats if showExtendedTooltip is false + if (!displayedContent.ShowExtendedTooltip) return; - // Show the difficulty stats if showTooltip is true + // Show the difficulty stats if showExtendedTooltip is true difficultyFillFlowContainer.Show(); miscFillFlowContainer.Show(); @@ -212,15 +212,15 @@ namespace osu.Game.Beatmaps.Drawables public readonly IBindable Difficulty; public readonly IRulesetInfo Ruleset; public readonly Mod[] Mods; - public readonly bool ShowTooltip; + public readonly bool ShowExtendedTooltip; - public DifficultyIconTooltipContent(IBeatmapInfo beatmapInfo, IBindable difficulty, IRulesetInfo rulesetInfo, Mod[] mods, bool showTooltip = false) + public DifficultyIconTooltipContent(IBeatmapInfo beatmapInfo, IBindable difficulty, IRulesetInfo rulesetInfo, Mod[] mods, bool showExtendedTooltip = false) { BeatmapInfo = beatmapInfo; Difficulty = difficulty; Ruleset = rulesetInfo; Mods = mods; - ShowTooltip = showTooltip; + ShowExtendedTooltip = showExtendedTooltip; } } } From 060ea1d4fd82899991eae7b9cdd8468c18dc551c Mon Sep 17 00:00:00 2001 From: smallketchup82 <69545310+smallketchup82@users.noreply.github.com> Date: Wed, 17 Jan 2024 14:04:13 -0500 Subject: [PATCH 2557/4092] Switch from using a constructor argument to using a public field for ShowExtendedTooltip --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 13 +++++++------ .../Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 8 +++++++- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 7a9b2fe389..fc78d6f322 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -36,14 +36,17 @@ namespace osu.Game.Beatmaps.Drawables /// public bool ShowTooltip { get; set; } = true; + /// + /// Whether to include the difficulty stats in the tooltip or not. Defaults to false. Has no effect if is false. + /// + public bool ShowExtendedTooltip { get; set; } + private readonly IBeatmapInfo? beatmap; private readonly IRulesetInfo ruleset; private readonly Mod[]? mods; - private readonly bool showExtendedTooltip; - private Drawable background = null!; private readonly Container iconContainer; @@ -65,13 +68,11 @@ namespace osu.Game.Beatmaps.Drawables /// The beatmap to be displayed in the tooltip, and to be used for the initial star rating value. /// An array of mods to account for in the calculations /// An optional ruleset to be used for the icon display, in place of the beatmap's ruleset. - /// Whether to include the difficulty stats in the tooltip or not. Defaults to false - public DifficultyIcon(IBeatmapInfo beatmap, IRulesetInfo? ruleset = null, Mod[]? mods = null, bool showExtendedTooltip = false) + public DifficultyIcon(IBeatmapInfo beatmap, IRulesetInfo? ruleset = null, Mod[]? mods = null) : this(ruleset ?? beatmap.Ruleset) { this.beatmap = beatmap; this.mods = mods; - this.showExtendedTooltip = showExtendedTooltip; Current.Value = new StarDifficulty(beatmap.StarRating, 0); } @@ -138,6 +139,6 @@ namespace osu.Game.Beatmaps.Drawables GetCustomTooltip() => new DifficultyIconTooltip(); DifficultyIconTooltipContent IHasCustomTooltip. - TooltipContent => (ShowTooltip && beatmap != null ? new DifficultyIconTooltipContent(beatmap, Current, ruleset, mods, showExtendedTooltip) : null)!; + TooltipContent => (ShowTooltip && beatmap != null ? new DifficultyIconTooltipContent(beatmap, Current, ruleset, mods, ShowExtendedTooltip) : null)!; } } diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 2a6387871f..8cfdc2e0e2 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -283,7 +283,13 @@ namespace osu.Game.Screens.OnlinePlay } if (beatmap != null) - difficultyIconContainer.Child = new DifficultyIcon(beatmap, ruleset, requiredMods, true) { Size = new Vector2(icon_height) }; + { + difficultyIconContainer.Child = new DifficultyIcon(beatmap, ruleset, requiredMods) + { + Size = new Vector2(icon_height), + ShowExtendedTooltip = true + }; + } else difficultyIconContainer.Clear(); From 42e4c933d374bc3ebc863ec38bc7951dd0498054 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 18 Jan 2024 04:06:02 +0300 Subject: [PATCH 2558/4092] Fix ConstrainedIconContainer always using masking --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 1 - .../Containers/ConstrainedIconContainer.cs | 18 ------------------ .../Toolbar/ToolbarRulesetTabButton.cs | 11 +---------- 3 files changed, 1 insertion(+), 29 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 1665ec52fa..eecf79aa34 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -92,7 +92,6 @@ namespace osu.Game.Beatmaps.Drawables EdgeEffect = new EdgeEffectParameters { Colour = Color4.Black.Opacity(0.06f), - Type = EdgeEffectType.Shadow, Radius = 3, }, diff --git a/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs b/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs index 7722374c69..63ac84fcf7 100644 --- a/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs +++ b/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs @@ -4,7 +4,6 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Effects; using osuTK; namespace osu.Game.Graphics.Containers @@ -17,21 +16,9 @@ namespace osu.Game.Graphics.Containers public Drawable Icon { get => InternalChild; - set => InternalChild = value; } - /// - /// Determines an edge effect of this . - /// Edge effects are e.g. glow or a shadow. - /// Only has an effect when is true. - /// - public new EdgeEffectParameters EdgeEffect - { - get => base.EdgeEffect; - set => base.EdgeEffect = value; - } - protected override void Update() { base.Update(); @@ -49,10 +36,5 @@ namespace osu.Game.Graphics.Containers InternalChild.Origin = Anchor.Centre; } } - - public ConstrainedIconContainer() - { - Masking = true; - } } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs index c224f0f9c9..3287ac6eaa 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Graphics; @@ -58,15 +57,7 @@ namespace osu.Game.Overlays.Toolbar { set => Scheduler.AddOnce(() => { - if (value) - { - IconContainer.Colour = Color4Extensions.FromHex("#00FFAA"); - } - else - { - IconContainer.Colour = colours.GrayF; - IconContainer.EdgeEffect = new EdgeEffectParameters(); - } + IconContainer.Colour = value ? Color4Extensions.FromHex("#00FFAA") : colours.GrayF; }); } From c362a93a3688cd13b8befc11ca1a75a59d8172cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jan 2024 02:18:20 +0900 Subject: [PATCH 2559/4092] Change frame stable catch-up method to allow for much faster sync --- .../Gameplay/TestSceneFrameStabilityContainer.cs | 4 +--- .../Visual/Gameplay/TestSceneSongProgress.cs | 5 +---- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 10 ++++++---- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFrameStabilityContainer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFrameStabilityContainer.cs index 534348bed3..98a97e1d23 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFrameStabilityContainer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFrameStabilityContainer.cs @@ -129,10 +129,8 @@ namespace osu.Game.Tests.Visual.Gameplay checkRate(1); } - private const int max_frames_catchup = 50; - private void createStabilityContainer(double gameplayStartTime = double.MinValue) => AddStep("create container", () => - mainContainer.Child = new FrameStabilityContainer(gameplayStartTime) { MaxCatchUpFrames = max_frames_catchup } + mainContainer.Child = new FrameStabilityContainer(gameplayStartTime) .WithChild(consumer = new ClockConsumingChild())); private void seekManualTo(double time) => AddStep($"seek manual clock to {time}", () => manualClock.CurrentTime = time); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs index e975a85401..19bb5cdde1 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs @@ -45,10 +45,7 @@ namespace osu.Game.Tests.Visual.Gameplay }, gameplayClockContainer = new MasterGameplayClockContainer(Beatmap.Value, skip_target_time) { - Child = frameStabilityContainer = new FrameStabilityContainer - { - MaxCatchUpFrames = 1 - } + Child = frameStabilityContainer = new FrameStabilityContainer() } }); diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 2af9916a6b..c07bd3aef5 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -25,9 +25,9 @@ namespace osu.Game.Rulesets.UI public ReplayInputHandler? ReplayInputHandler { get; set; } /// - /// The number of frames (per parent frame) which can be run in an attempt to catch-up to real-time. + /// The number of CPU milliseconds to spend at most during seek catch-up. /// - public int MaxCatchUpFrames { get; set; } = 5; + private const double max_catchup_milliseconds = 10; /// /// Whether to enable frame-stable playback. @@ -61,6 +61,8 @@ namespace osu.Game.Rulesets.UI /// private readonly FramedClock framedClock; + private readonly Stopwatch stopwatch = new Stopwatch(); + /// /// The current direction of playback to be exposed to frame stable children. /// @@ -99,7 +101,7 @@ namespace osu.Game.Rulesets.UI public override bool UpdateSubTree() { - int loops = MaxCatchUpFrames; + stopwatch.Restart(); do { @@ -112,7 +114,7 @@ namespace osu.Game.Rulesets.UI base.UpdateSubTree(); UpdateSubTreeMasking(this, ScreenSpaceDrawQuad.AABBFloat); - } while (state == PlaybackState.RequiresCatchUp && loops-- > 0); + } while (state == PlaybackState.RequiresCatchUp && stopwatch.ElapsedMilliseconds < max_catchup_milliseconds); return true; } From fb4efd992de542ae5ad57ce192d9626de692e8ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jan 2024 11:52:28 +0900 Subject: [PATCH 2560/4092] Add a fake load to visual test to restore previous testing behaviour --- .../Visual/Gameplay/TestSceneSongProgress.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs index 19bb5cdde1..99f0ffb9d0 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using System.Threading; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; @@ -45,7 +46,10 @@ namespace osu.Game.Tests.Visual.Gameplay }, gameplayClockContainer = new MasterGameplayClockContainer(Beatmap.Value, skip_target_time) { - Child = frameStabilityContainer = new FrameStabilityContainer() + Child = frameStabilityContainer = new FrameStabilityContainer + { + Child = new FakeLoad() + } } }); @@ -53,6 +57,15 @@ namespace osu.Game.Tests.Visual.Gameplay Dependencies.CacheAs(frameStabilityContainer); } + private partial class FakeLoad : Drawable + { + protected override void Update() + { + base.Update(); + Thread.Sleep(1); + } + } + [SetUpSteps] public void SetupSteps() { From 799c74cfe533da28509699dd2feea84179a2b8f8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jan 2024 02:40:34 +0900 Subject: [PATCH 2561/4092] Simplify gameplay pause sequence --- .../Visual/Gameplay/TestScenePause.cs | 5 +- .../Play/MasterGameplayClockContainer.cs | 66 ------------------- 2 files changed, 1 insertion(+), 70 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index ec3b3e0822..d55af2777f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -93,15 +93,12 @@ namespace osu.Game.Tests.Visual.Gameplay double currentTime = masterClock.CurrentTime; - bool goingForward = currentTime >= (masterClock.LastStopTime ?? lastStopTime); + bool goingForward = currentTime >= lastStopTime; alwaysGoingForward &= goingForward; if (!goingForward) Logger.Log($"Went too far backwards (last stop: {lastStopTime:N1} current: {currentTime:N1})"); - - if (masterClock.LastStopTime != null) - lastStopTime = masterClock.LastStopTime.Value; }; }); diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 12f541167f..10451963a1 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -7,7 +7,6 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; -using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Framework.Timing; using osu.Game.Beatmaps; @@ -60,17 +59,6 @@ namespace osu.Game.Screens.Play private readonly double skipTargetTime; - /// - /// Stores the time at which the last call was triggered. - /// This is used to ensure we resume from that precise point in time, ignoring the proceeding frequency ramp. - /// - /// Optimally, we'd have gameplay ramp down with the frequency, but I believe this was intentionally disabled - /// to avoid fails occurring after the pause screen has been shown. - /// - /// In the future I want to change this. - /// - internal double? LastStopTime; - [Resolved] private MusicController musicController { get; set; } = null!; @@ -113,71 +101,17 @@ namespace osu.Game.Screens.Play return time; } - protected override void StopGameplayClock() - { - LastStopTime = GameplayClock.CurrentTime; - - if (IsLoaded) - { - // During normal operation, the source is stopped after performing a frequency ramp. - this.TransformBindableTo(GameplayClock.ExternalPauseFrequencyAdjust, 0, 200, Easing.Out).OnComplete(_ => - { - if (IsPaused.Value) - base.StopGameplayClock(); - }); - } - else - { - base.StopGameplayClock(); - - // If not yet loaded, we still want to ensure relevant state is correct, as it is used for offset calculations. - GameplayClock.ExternalPauseFrequencyAdjust.Value = 0; - - // We must also process underlying gameplay clocks to update rate-adjusted offsets with the new frequency adjustment. - // Without doing this, an initial seek may be performed with the wrong offset. - GameplayClock.ProcessFrame(); - } - } - public override void Seek(double time) { - // Safety in case the clock is seeked while stopped. - LastStopTime = null; elapsedValidationTime = null; base.Seek(time); } - protected override void PrepareStart() - { - if (LastStopTime != null) - { - Seek(LastStopTime.Value); - LastStopTime = null; - } - else - base.PrepareStart(); - } - protected override void StartGameplayClock() { addAdjustmentsToTrack(); - base.StartGameplayClock(); - - if (IsLoaded) - { - this.TransformBindableTo(GameplayClock.ExternalPauseFrequencyAdjust, 1, 200, Easing.In); - } - else - { - // If not yet loaded, we still want to ensure relevant state is correct, as it is used for offset calculations. - GameplayClock.ExternalPauseFrequencyAdjust.Value = 1; - - // We must also process underlying gameplay clocks to update rate-adjusted offsets with the new frequency adjustment. - // Without doing this, an initial seek may be performed with the wrong offset. - GameplayClock.ProcessFrame(); - } } /// From 8ab8c90e338f00c784e05fc406540b872cefa5a0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jan 2024 14:21:02 +0900 Subject: [PATCH 2562/4092] Remove "pause rate adjust" flow --- osu.Game/Beatmaps/FramedBeatmapClock.cs | 11 +++-------- .../Screens/Play/MasterGameplayClockContainer.cs | 2 -- osu.Game/Screens/Play/OffsetCorrectionClock.cs | 14 +++----------- 3 files changed, 6 insertions(+), 21 deletions(-) diff --git a/osu.Game/Beatmaps/FramedBeatmapClock.cs b/osu.Game/Beatmaps/FramedBeatmapClock.cs index 587e6bbeed..d0ffbdd459 100644 --- a/osu.Game/Beatmaps/FramedBeatmapClock.cs +++ b/osu.Game/Beatmaps/FramedBeatmapClock.cs @@ -27,11 +27,6 @@ namespace osu.Game.Beatmaps { private readonly bool applyOffsets; - /// - /// The total frequency adjustment from pause transforms. Should eventually be handled in a better way. - /// - public readonly BindableDouble ExternalPauseFrequencyAdjust = new BindableDouble(1); - private readonly OffsetCorrectionClock? userGlobalOffsetClock; private readonly OffsetCorrectionClock? platformOffsetClock; private readonly OffsetCorrectionClock? userBeatmapOffsetClock; @@ -69,13 +64,13 @@ namespace osu.Game.Beatmaps { // Audio timings in general with newer BASS versions don't match stable. // This only seems to be required on windows. We need to eventually figure out why, with a bit of luck. - platformOffsetClock = new OffsetCorrectionClock(interpolatedTrack, ExternalPauseFrequencyAdjust) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 15 : 0 }; + platformOffsetClock = new OffsetCorrectionClock(interpolatedTrack) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 15 : 0 }; // User global offset (set in settings) should also be applied. - userGlobalOffsetClock = new OffsetCorrectionClock(platformOffsetClock, ExternalPauseFrequencyAdjust); + userGlobalOffsetClock = new OffsetCorrectionClock(platformOffsetClock); // User per-beatmap offset will be applied to this final clock. - finalClockSource = userBeatmapOffsetClock = new OffsetCorrectionClock(userGlobalOffsetClock, ExternalPauseFrequencyAdjust); + finalClockSource = userBeatmapOffsetClock = new OffsetCorrectionClock(userGlobalOffsetClock); } else { diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 10451963a1..93bdcb1cab 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -207,7 +207,6 @@ namespace osu.Game.Screens.Play musicController.ResetTrackAdjustments(); track.BindAdjustments(AdjustmentsFromMods); - track.AddAdjustment(AdjustableProperty.Frequency, GameplayClock.ExternalPauseFrequencyAdjust); track.AddAdjustment(AdjustableProperty.Frequency, UserPlaybackRate); speedAdjustmentsApplied = true; @@ -219,7 +218,6 @@ namespace osu.Game.Screens.Play return; track.UnbindAdjustments(AdjustmentsFromMods); - track.RemoveAdjustment(AdjustableProperty.Frequency, GameplayClock.ExternalPauseFrequencyAdjust); track.RemoveAdjustment(AdjustableProperty.Frequency, UserPlaybackRate); speedAdjustmentsApplied = false; diff --git a/osu.Game/Screens/Play/OffsetCorrectionClock.cs b/osu.Game/Screens/Play/OffsetCorrectionClock.cs index 207980f45c..e83ed7e464 100644 --- a/osu.Game/Screens/Play/OffsetCorrectionClock.cs +++ b/osu.Game/Screens/Play/OffsetCorrectionClock.cs @@ -1,15 +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.Timing; namespace osu.Game.Screens.Play { public class OffsetCorrectionClock : FramedOffsetClock { - private readonly BindableDouble pauseRateAdjust; - private double offset; public new double Offset @@ -28,10 +25,9 @@ namespace osu.Game.Screens.Play public double RateAdjustedOffset => base.Offset; - public OffsetCorrectionClock(IClock source, BindableDouble pauseRateAdjust) + public OffsetCorrectionClock(IClock source) : base(source) { - this.pauseRateAdjust = pauseRateAdjust; } public override void ProcessFrame() @@ -42,12 +38,8 @@ namespace osu.Game.Screens.Play private void updateOffset() { - // changing this during the pause transform effect will cause a potentially large offset to be suddenly applied as we approach zero rate. - if (pauseRateAdjust.Value == 1) - { - // we always want to apply the same real-time offset, so it should be adjusted by the difference in playback rate (from realtime) to achieve this. - base.Offset = Offset * Rate; - } + // we always want to apply the same real-time offset, so it should be adjusted by the difference in playback rate (from realtime) to achieve this. + base.Offset = Offset * Rate; } } } From a870f99877fa3758307d33fae01e46f5f067af3b Mon Sep 17 00:00:00 2001 From: mouzedrift <43358824+mouzedrift@users.noreply.github.com> Date: Thu, 18 Jan 2024 07:19:29 +0100 Subject: [PATCH 2563/4092] ease out input drum animation and add delay before fading out --- osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index 8ad419f8bd..d9e94ede4a 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -159,8 +159,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy const float up_time = 50; target.Animate( - t => t.FadeTo(Math.Min(target.Alpha + alpha_amount, 1), down_time) - ).Then( + t => t.FadeTo(Math.Min(target.Alpha + alpha_amount, 1), down_time, Easing.Out) + ).Delay(100).Then( t => t.FadeOut(up_time) ); } From ee3de9c522685b5f85eeb9c021f9c6eab8b9d9a5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jan 2024 15:44:27 +0900 Subject: [PATCH 2564/4092] Fix gameplay elements not correcly offsetting to avoid per-ruleset skin layout Closes https://github.com/ppy/osu/issues/26601. --- osu.Game/Screens/Play/HUDOverlay.cs | 49 ++++++++++++++++++----------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 128f8d5ffd..a9fe393395 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -99,6 +99,9 @@ namespace osu.Game.Screens.Play private readonly SkinComponentsContainer mainComponents; + [CanBeNull] + private readonly SkinComponentsContainer rulesetComponents; + /// /// A flow which sits at the left side of the screen to house leaderboard (and related) components. /// Will automatically be positioned to avoid colliding with top scoring elements. @@ -111,7 +114,6 @@ namespace osu.Game.Screens.Play public HUDOverlay([CanBeNull] DrawableRuleset drawableRuleset, IReadOnlyList mods, bool alwaysShowLeaderboard = true) { - Drawable rulesetComponents; this.drawableRuleset = drawableRuleset; this.mods = mods; @@ -125,8 +127,8 @@ namespace osu.Game.Screens.Play clicksPerSecondController = new ClicksPerSecondController(), InputCountController = new InputCountController(), mainComponents = new HUDComponentsContainer { AlwaysPresent = true, }, - rulesetComponents = drawableRuleset != null - ? new HUDComponentsContainer(drawableRuleset.Ruleset.RulesetInfo) { AlwaysPresent = true, } + drawableRuleset != null + ? (rulesetComponents = new HUDComponentsContainer(drawableRuleset.Ruleset.RulesetInfo) { AlwaysPresent = true, }) : Empty(), playfieldComponents = drawableRuleset != null ? new SkinComponentsContainer(new SkinComponentsContainerLookup(SkinComponentsContainerLookup.TargetArea.Playfield, drawableRuleset.Ruleset.RulesetInfo)) { AlwaysPresent = true, } @@ -256,13 +258,37 @@ namespace osu.Game.Screens.Play // LINQ cast can be removed when IDrawable interface includes Anchor / RelativeSizeAxes. foreach (var element in mainComponents.Components.Cast()) + processDrawable(element); + + if (rulesetComponents != null) + { + foreach (var element in rulesetComponents.Components.Cast()) + processDrawable(element); + } + + if (lowestTopScreenSpaceRight.HasValue) + topRightElements.Y = MathHelper.Clamp(ToLocalSpace(new Vector2(0, lowestTopScreenSpaceRight.Value)).Y, 0, DrawHeight - topRightElements.DrawHeight); + else + topRightElements.Y = 0; + + if (lowestTopScreenSpaceLeft.HasValue) + LeaderboardFlow.Y = MathHelper.Clamp(ToLocalSpace(new Vector2(0, lowestTopScreenSpaceLeft.Value)).Y, 0, DrawHeight - LeaderboardFlow.DrawHeight); + else + LeaderboardFlow.Y = 0; + + if (highestBottomScreenSpace.HasValue) + bottomRightElements.Y = BottomScoringElementsHeight = -MathHelper.Clamp(DrawHeight - ToLocalSpace(highestBottomScreenSpace.Value).Y, 0, DrawHeight - bottomRightElements.DrawHeight); + else + bottomRightElements.Y = 0; + + void processDrawable(Drawable element) { // for now align some top components with the bottom-edge of the lowest top-anchored hud element. if (element.Anchor.HasFlagFast(Anchor.y0)) { // health bars are excluded for the sake of hacky legacy skins which extend the health bar to take up the full screen area. if (element is LegacyHealthDisplay) - continue; + return; float bottom = element.ScreenSpaceDrawQuad.BottomRight.Y; @@ -288,21 +314,6 @@ namespace osu.Game.Screens.Play highestBottomScreenSpace = topLeft; } } - - if (lowestTopScreenSpaceRight.HasValue) - topRightElements.Y = MathHelper.Clamp(ToLocalSpace(new Vector2(0, lowestTopScreenSpaceRight.Value)).Y, 0, DrawHeight - topRightElements.DrawHeight); - else - topRightElements.Y = 0; - - if (lowestTopScreenSpaceLeft.HasValue) - LeaderboardFlow.Y = MathHelper.Clamp(ToLocalSpace(new Vector2(0, lowestTopScreenSpaceLeft.Value)).Y, 0, DrawHeight - LeaderboardFlow.DrawHeight); - else - LeaderboardFlow.Y = 0; - - if (highestBottomScreenSpace.HasValue) - bottomRightElements.Y = BottomScoringElementsHeight = -MathHelper.Clamp(DrawHeight - ToLocalSpace(highestBottomScreenSpace.Value).Y, 0, DrawHeight - bottomRightElements.DrawHeight); - else - bottomRightElements.Y = 0; } private void updateVisibility() From cbfc6091af4265462e89016c3dc6210c58cff2d8 Mon Sep 17 00:00:00 2001 From: mouzedrift <43358824+mouzedrift@users.noreply.github.com> Date: Thu, 18 Jan 2024 08:22:26 +0100 Subject: [PATCH 2565/4092] apply proposed changes --- .../Skinning/Legacy/LegacyInputDrum.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index d9e94ede4a..28415bb72a 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.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; @@ -153,16 +152,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy if (target != null) { - const float alpha_amount = 1; - const float down_time = 80; const float up_time = 50; - target.Animate( - t => t.FadeTo(Math.Min(target.Alpha + alpha_amount, 1), down_time, Easing.Out) - ).Delay(100).Then( - t => t.FadeOut(up_time) - ); + target + .FadeTo(1, down_time * (1 - target.Alpha), Easing.Out) + .Delay(100).FadeOut(up_time); } return false; From ef0b5c94064654f798727ad0dbc12f919e6284d3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jan 2024 16:31:20 +0900 Subject: [PATCH 2566/4092] Stop playing back samples for nested screens in multiplayer I'd argue this sounds better. --- osu.Game/Screens/OnlinePlay/OnlinePlaySubScreen.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySubScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySubScreen.cs index b527bf98a2..ea855c0474 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySubScreen.cs @@ -13,6 +13,8 @@ namespace osu.Game.Screens.OnlinePlay public virtual string ShortTitle => Title; + protected override bool PlayExitSound => false; + [Resolved] protected IRoomManager? RoomManager { get; private set; } From fd9c7184e449ffeeadeb3768e80bdb059bcaa92c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jan 2024 16:31:33 +0900 Subject: [PATCH 2567/4092] Only play back sample once when traversing up multiple screens in stack --- osu.Game/Screens/OsuScreen.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 490a1ae6b8..f719ef67c9 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -223,7 +223,12 @@ namespace osu.Game.Screens public override bool OnExiting(ScreenExitEvent e) { - if (ValidForResume && PlayExitSound) + // Only play the exit sound if we are the last screen in the exit sequence. + // This stops many sample playbacks from stacking when a huge screen purge happens (ie. returning to menu via the home button + // from a deeply nested screen). + bool arrivingAtFinalDestination = e.Next == e.Destination; + + if (ValidForResume && PlayExitSound && arrivingAtFinalDestination) sampleExit?.Play(); if (ValidForResume && logo != null) From 1527ab89ef1dee1994f4629a5ebbcbcf2c9edc8a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 18 Jan 2024 10:34:23 +0300 Subject: [PATCH 2568/4092] Refactor `DefaultApproachCircle`/`LegacyApproachCircle` to make sense --- .../Skinning/Default/DefaultApproachCircle.cs | 34 +++++++----------- .../Skinning/Legacy/LegacyApproachCircle.cs | 36 ++++++++----------- .../Legacy/OsuLegacySkinTransformer.cs | 2 +- 3 files changed, 28 insertions(+), 44 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultApproachCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultApproachCircle.cs index b65f46c414..272f4b5658 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultApproachCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultApproachCircle.cs @@ -3,47 +3,39 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Default { - public partial class DefaultApproachCircle : SkinnableSprite + public partial class DefaultApproachCircle : Sprite { - private readonly IBindable accentColour = new Bindable(); - [Resolved] private DrawableHitObject drawableObject { get; set; } = null!; - public DefaultApproachCircle() - : base("Gameplay/osu/approachcircle") - { - } + private IBindable accentColour = null!; [BackgroundDependencyLoader] - private void load() + private void load(TextureStore textures) { - accentColour.BindTo(drawableObject.AccentColour); + Texture = textures.Get(@"Gameplay/osu/approachcircle").WithMaximumSize(OsuHitObject.OBJECT_DIMENSIONS * 2); + + // account for the sprite being used for the default approach circle being taken from stable, + // when hitcircles have 5px padding on each size. this should be removed if we update the sprite. + Scale = new Vector2(128 / 118f); } protected override void LoadComplete() { base.LoadComplete(); - accentColour.BindValueChanged(colour => Colour = colour.NewValue, true); - } - protected override Drawable CreateDefault(ISkinComponentLookup lookup) - { - var drawable = base.CreateDefault(lookup); - - // Although this is a non-legacy component, osu-resources currently stores approach circle as a legacy-like texture. - // See LegacyApproachCircle for documentation as to why this is required. - drawable.Scale = new Vector2(128 / 118f); - - return drawable; + accentColour = drawableObject.AccentColour.GetBoundCopy(); + accentColour.BindValueChanged(colour => Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue), true); } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyApproachCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyApproachCircle.cs index d31c763c2c..0bdea0cab1 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyApproachCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyApproachCircle.cs @@ -1,9 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Skinning; @@ -12,40 +13,31 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { - // todo: this should probably not be a SkinnableSprite, as this is always created for legacy skins and is recreated on skin change. - public partial class LegacyApproachCircle : SkinnableSprite + public partial class LegacyApproachCircle : Sprite { - private readonly IBindable accentColour = new Bindable(); - [Resolved] private DrawableHitObject drawableObject { get; set; } = null!; - public LegacyApproachCircle() - : base(@"approachcircle", OsuHitObject.OBJECT_DIMENSIONS * 2) - { - } + private IBindable accentColour = null!; [BackgroundDependencyLoader] - private void load() + private void load(ISkinSource skin) { - accentColour.BindTo(drawableObject.AccentColour); + var texture = skin.GetTexture(@"approachcircle"); + Debug.Assert(texture != null); + Texture = texture.WithMaximumSize(OsuHitObject.OBJECT_DIMENSIONS * 2); + + // account for the sprite being used for the default approach circle being taken from stable, + // when hitcircles have 5px padding on each size. this should be removed if we update the sprite. + Scale = new Vector2(128 / 118f); } protected override void LoadComplete() { base.LoadComplete(); + + accentColour = drawableObject.AccentColour.GetBoundCopy(); accentColour.BindValueChanged(colour => Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue), true); } - - protected override Drawable CreateDefault(ISkinComponentLookup lookup) - { - var drawable = base.CreateDefault(lookup); - - // account for the sprite being used for the default approach circle being taken from stable, - // when hitcircles have 5px padding on each size. this should be removed if we update the sprite. - drawable.Scale = new Vector2(128 / 118f); - - return drawable; - } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index b01c18031c..d2ebc68c52 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -163,7 +163,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return null; case OsuSkinComponents.ApproachCircle: - if (IsProvidingLegacyResources) + if (GetTexture(@"approachcircle") != null) return new LegacyApproachCircle(); return null; From d80a5d44eedca57e8be969d771152edb4fe4b229 Mon Sep 17 00:00:00 2001 From: smallketchup82 <69545310+smallketchup82@users.noreply.github.com> Date: Thu, 18 Jan 2024 03:17:37 -0500 Subject: [PATCH 2569/4092] Add tests for DifficultyIcon --- .../Beatmaps/TestSceneDifficultyIcon.cs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultyIcon.cs diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultyIcon.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultyIcon.cs new file mode 100644 index 0000000000..aa2543eea1 --- /dev/null +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultyIcon.cs @@ -0,0 +1,42 @@ +// 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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Platform; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Tests.Visual.Beatmaps +{ + public partial class TestSceneDifficultyIcon : OsuTestScene + { + [Test] + public void createDifficultyIcon() + { + DifficultyIcon difficultyIcon = null; + + AddStep("create difficulty icon", () => + { + Child = difficultyIcon = new DifficultyIcon(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, new OsuRuleset().RulesetInfo) + { + ShowTooltip = true, + ShowExtendedTooltip = true + }; + }); + + AddStep("hide extended tooltip", () => difficultyIcon.ShowExtendedTooltip = false); + + AddStep("hide tooltip", () => difficultyIcon.ShowTooltip = false); + + AddStep("show tooltip", () => difficultyIcon.ShowTooltip = true); + + AddStep("show extended tooltip", () => difficultyIcon.ShowExtendedTooltip = true); + } + } +} From 5c70c786b4581d91c5b2d069e04b9d774cf79351 Mon Sep 17 00:00:00 2001 From: smallketchup82 <69545310+smallketchup82@users.noreply.github.com> Date: Thu, 18 Jan 2024 03:18:44 -0500 Subject: [PATCH 2570/4092] Fix extended tooltip content still being shown despite ShowExtendedTooltip being false --- osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index fae4100473..fe23b49346 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -158,7 +158,12 @@ namespace osu.Game.Beatmaps.Drawables difficultyName.Text = displayedContent.BeatmapInfo.DifficultyName; // Don't show difficulty stats if showExtendedTooltip is false - if (!displayedContent.ShowExtendedTooltip) return; + if (!displayedContent.ShowExtendedTooltip) + { + difficultyFillFlowContainer.Hide(); + miscFillFlowContainer.Hide(); + return; + } // Show the difficulty stats if showExtendedTooltip is true difficultyFillFlowContainer.Show(); From c9945b41c1007ab2ff18900d1d70686598e57b9d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jan 2024 18:36:25 +0900 Subject: [PATCH 2571/4092] Remove rounding of slider velocity (when applied as scroll speed) This affects both osu!taiko and osu!mania. Closes https://github.com/ppy/osu/issues/25862. --- osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs index 7edf892f35..0138ac7569 100644 --- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs @@ -21,7 +21,6 @@ namespace osu.Game.Beatmaps.ControlPoints /// public readonly BindableDouble ScrollSpeedBindable = new BindableDouble(1) { - Precision = 0.01, MinValue = 0.01, MaxValue = 10 }; From 82e7643df55323a7e66fb9dc3351becf3357c469 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jan 2024 19:45:35 +0900 Subject: [PATCH 2572/4092] Update IPC usages Of note, I've disabled IPC on visual test runners as we generally don't use IPC in these cases. Having it set means that the game will not open while visual tests are open, which has been a complaint from devs in the past. --- .../osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs | 2 +- .../osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs | 2 +- .../osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs | 2 +- .../osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs | 2 +- osu.Desktop/Program.cs | 2 +- .../Visual/Navigation/TestSceneInterProcessCommunication.cs | 2 +- osu.Game.Tournament.Tests/TournamentTestRunner.cs | 2 +- osu.Game/OsuGame.cs | 2 ++ osu.Game/Tests/CleanRunHeadlessGameHost.cs | 2 +- osu.Game/Tests/VisualTestRunner.cs | 2 +- 10 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs index 03ee7c9204..63c481a623 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.EmptyFreeform.Tests [STAThread] public static int Main(string[] args) { - using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true })) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu")) { host.Run(new OsuTestBrowser()); return 0; diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs index 55c0cf6a3b..c44cbb845b 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Pippidon.Tests [STAThread] public static int Main(string[] args) { - using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true })) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu")) { host.Run(new OsuTestBrowser()); return 0; diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs index b45505678c..5beb6616a7 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.EmptyScrolling.Tests [STAThread] public static int Main(string[] args) { - using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true })) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu")) { host.Run(new OsuTestBrowser()); return 0; diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs index 55c0cf6a3b..c44cbb845b 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Pippidon.Tests [STAThread] public static int Main(string[] args) { - using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true })) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu")) { host.Run(new OsuTestBrowser()); return 0; diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 6b95a82703..a7453dc0e0 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -102,7 +102,7 @@ namespace osu.Desktop } } - using (DesktopGameHost host = Host.GetSuitableDesktopHost(gameName, new HostOptions { BindIPC = !tournamentClient })) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(gameName, new HostOptions { IPCPort = !tournamentClient ? OsuGame.IPC_PORT : null })) { if (!host.IsPrimaryInstance) { diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs b/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs index 1ecd38e1d3..83430b5665 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Navigation }); AddStep("create IPC sender channels", () => { - ipcSenderHost = new HeadlessGameHost(gameHost.Name, new HostOptions { BindIPC = true }); + ipcSenderHost = new HeadlessGameHost(gameHost.Name, new HostOptions { IPCPort = OsuGame.IPC_PORT }); osuSchemeLinkIPCSender = new OsuSchemeLinkIPCChannel(ipcSenderHost); archiveImportIPCSender = new ArchiveImportIPCChannel(ipcSenderHost); }); diff --git a/osu.Game.Tournament.Tests/TournamentTestRunner.cs b/osu.Game.Tournament.Tests/TournamentTestRunner.cs index 5f642b14f5..e09d1be22c 100644 --- a/osu.Game.Tournament.Tests/TournamentTestRunner.cs +++ b/osu.Game.Tournament.Tests/TournamentTestRunner.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tournament.Tests [STAThread] public static int Main(string[] args) { - using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu-development", new HostOptions { BindIPC = true })) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu-development")) { host.Run(new TournamentTestBrowser()); return 0; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 0d6eb1ea44..7138bf2175 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -80,6 +80,8 @@ namespace osu.Game [Cached(typeof(OsuGame))] public partial class OsuGame : OsuGameBase, IKeyBindingHandler, ILocalUserPlayInfo, IPerformFromScreenRunner, IOverlayManager, ILinkHandler { + public const int IPC_PORT = 44823; + /// /// The amount of global offset to apply when a left/right anchored overlay is displayed (ie. settings or notifications). /// diff --git a/osu.Game/Tests/CleanRunHeadlessGameHost.cs b/osu.Game/Tests/CleanRunHeadlessGameHost.cs index f3c69201e2..00e5b38b1a 100644 --- a/osu.Game/Tests/CleanRunHeadlessGameHost.cs +++ b/osu.Game/Tests/CleanRunHeadlessGameHost.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests [CallerMemberName] string callingMethodName = @"") : base($"{callingMethodName}-{Guid.NewGuid()}", new HostOptions { - BindIPC = bindIPC, + IPCPort = bindIPC ? OsuGame.IPC_PORT : null, }, bypassCleanup: bypassCleanupOnDispose, realtime: realtime) { this.bypassCleanupOnSetup = bypassCleanupOnSetup; diff --git a/osu.Game/Tests/VisualTestRunner.cs b/osu.Game/Tests/VisualTestRunner.cs index e04c71d193..1a9e03b2a4 100644 --- a/osu.Game/Tests/VisualTestRunner.cs +++ b/osu.Game/Tests/VisualTestRunner.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tests [STAThread] public static int Main(string[] args) { - using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu-development", new HostOptions { BindIPC = true, })) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu-development")) { host.Run(new OsuTestBrowser()); return 0; From c4e9bcd140bbb59a094c282fc7b21ac04e8ec5d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jan 2024 20:06:53 +0900 Subject: [PATCH 2573/4092] Remove test guarantee of audio time not advancing --- osu.Game.Tests/Visual/Gameplay/TestScenePause.cs | 2 +- osu.Game/Screens/Play/GameplayClockContainer.cs | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index d55af2777f..73aa3be73d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -122,7 +122,7 @@ namespace osu.Game.Tests.Visual.Gameplay resumeAndConfirm(); - AddAssert("Resumed without seeking forward", () => Player.LastResumeTime, () => Is.LessThanOrEqualTo(Player.LastPauseTime)); + AddAssert("continued playing forward", () => Player.LastResumeTime, () => Is.GreaterThanOrEqualTo(Player.LastPauseTime)); AddUntilStep("player playing", () => Player.LocalUserPlaying.Value); } diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 4def1d36bb..c039d1e535 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -78,8 +78,6 @@ namespace osu.Game.Screens.Play isPaused.Value = false; - PrepareStart(); - // The case which caused this to be added is FrameStabilityContainer, which manages its own current and elapsed time. // Because we generally update our own current time quicker than children can query it (via Start/Seek/Update), // this means that the first frame ever exposed to children may have a non-zero current time. @@ -99,14 +97,6 @@ namespace osu.Game.Screens.Play }); } - /// - /// When is called, this will be run to give an opportunity to prepare the clock at the correct - /// start location. - /// - protected virtual void PrepareStart() - { - } - /// /// Seek to a specific time in gameplay. /// From 4532a409d23bc0cb2fffbecda32991c5278c4ea8 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 18 Jan 2024 04:06:02 +0300 Subject: [PATCH 2574/4092] Fix ConstrainedIconContainer always using masking --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 1 - .../Containers/ConstrainedIconContainer.cs | 18 ------------------ .../Toolbar/ToolbarRulesetTabButton.cs | 11 +---------- 3 files changed, 1 insertion(+), 29 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 1665ec52fa..eecf79aa34 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -92,7 +92,6 @@ namespace osu.Game.Beatmaps.Drawables EdgeEffect = new EdgeEffectParameters { Colour = Color4.Black.Opacity(0.06f), - Type = EdgeEffectType.Shadow, Radius = 3, }, diff --git a/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs b/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs index 7722374c69..63ac84fcf7 100644 --- a/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs +++ b/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs @@ -4,7 +4,6 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Effects; using osuTK; namespace osu.Game.Graphics.Containers @@ -17,21 +16,9 @@ namespace osu.Game.Graphics.Containers public Drawable Icon { get => InternalChild; - set => InternalChild = value; } - /// - /// Determines an edge effect of this . - /// Edge effects are e.g. glow or a shadow. - /// Only has an effect when is true. - /// - public new EdgeEffectParameters EdgeEffect - { - get => base.EdgeEffect; - set => base.EdgeEffect = value; - } - protected override void Update() { base.Update(); @@ -49,10 +36,5 @@ namespace osu.Game.Graphics.Containers InternalChild.Origin = Anchor.Centre; } } - - public ConstrainedIconContainer() - { - Masking = true; - } } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs index c224f0f9c9..3287ac6eaa 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Graphics; @@ -58,15 +57,7 @@ namespace osu.Game.Overlays.Toolbar { set => Scheduler.AddOnce(() => { - if (value) - { - IconContainer.Colour = Color4Extensions.FromHex("#00FFAA"); - } - else - { - IconContainer.Colour = colours.GrayF; - IconContainer.EdgeEffect = new EdgeEffectParameters(); - } + IconContainer.Colour = value ? Color4Extensions.FromHex("#00FFAA") : colours.GrayF; }); } From 2afa4c7e1c761ce998199e39e86cd5d1e9228f49 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jan 2024 14:10:16 +0900 Subject: [PATCH 2575/4092] Remove redundant `RequiresChildrenUpdate` usage We are already manually calling `base.UpdateSubTree` when we need to. Changing this flag is doing nothing and just adds to the complexity of the implementation. --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 2af9916a6b..b6bf5ff53a 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -34,8 +34,6 @@ namespace osu.Game.Rulesets.UI /// internal bool FrameStablePlayback { get; set; } = true; - protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate && state != PlaybackState.NotValid; - private readonly Bindable isCatchingUp = new Bindable(); private readonly Bindable waitingOnFrames = new Bindable(); From e73910571f35e69e409cec18cc597d232829bfa5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jan 2024 14:15:22 +0900 Subject: [PATCH 2576/4092] Allow `FrameStabilityContainer` to continue updating while paused during replay playback --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index b6bf5ff53a..ee19baaf65 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -122,7 +122,7 @@ namespace osu.Game.Rulesets.UI // if waiting on frames, run one update loop to determine if frames have arrived. state = PlaybackState.Valid; } - else if (IsPaused.Value) + else if (IsPaused.Value && !hasReplayAttached) { // time should not advance while paused, nor should anything run. state = PlaybackState.NotValid; From dafff26d0ecf6de7f27bb5a329455729ee3ce084 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jan 2024 14:44:31 +0900 Subject: [PATCH 2577/4092] Tidy up implementation of `PlaybackSettings` --- .../Play/PlayerSettings/PlaybackSettings.cs | 76 +++++++++---------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs index 44cfa8d811..34f086da33 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs @@ -11,9 +11,9 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Localisation; using osu.Game.Screens.Edit.Timing; using osuTK; -using osu.Game.Localisation; namespace osu.Game.Screens.Play.PlayerSettings { @@ -28,26 +28,31 @@ namespace osu.Game.Screens.Play.PlayerSettings Precision = 0.01, }; - private readonly PlayerSliderBar rateSlider; + private PlayerSliderBar rateSlider = null!; - private readonly OsuSpriteText multiplierText; + private OsuSpriteText multiplierText = null!; - private readonly BindableBool isPaused = new BindableBool(); + private readonly IBindable isPaused = new BindableBool(); [Resolved] - private GameplayClockContainer? gameplayClock { get; set; } + private GameplayClockContainer gameplayClock { get; set; } = null!; [Resolved] - private GameplayState? gameplayState { get; set; } + private GameplayState gameplayState { get; set; } = null!; + + private IconButton pausePlay = null!; public PlaybackSettings() : base("playback") + { + } + + [BackgroundDependencyLoader] + private void load() { const double seek_amount = 5000; const double seek_fast_amount = 10000; - IconButton play; - Children = new Drawable[] { new FillFlowContainer @@ -82,7 +87,7 @@ namespace osu.Game.Screens.Play.PlayerSettings Action = () => seek(-1, seek_amount), TooltipText = PlayerSettingsOverlayStrings.SeekBackwardSeconds(seek_amount / 1000), }, - play = new IconButton + pausePlay = new IconButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -91,13 +96,10 @@ namespace osu.Game.Screens.Play.PlayerSettings Icon = FontAwesome.Regular.PlayCircle, Action = () => { - if (gameplayClock != null) - { - if (gameplayClock.IsRunning) - gameplayClock.Stop(); - else - gameplayClock.Start(); - } + if (gameplayClock.IsRunning) + gameplayClock.Stop(); + else + gameplayClock.Start(); }, }, new SeekButton @@ -141,26 +143,6 @@ namespace osu.Game.Screens.Play.PlayerSettings }, }, }; - - isPaused.BindValueChanged(paused => - { - if (!paused.NewValue) - { - play.TooltipText = ToastStrings.PauseTrack; - play.Icon = FontAwesome.Regular.PauseCircle; - } - else - { - play.TooltipText = ToastStrings.PlayTrack; - play.Icon = FontAwesome.Regular.PlayCircle; - } - }, true); - - void seek(int direction, double amount) - { - double target = Math.Clamp((gameplayClock?.CurrentTime ?? 0) + (direction * amount), 0, gameplayState?.Beatmap.GetLastObjectTime() ?? 0); - gameplayClock?.Seek(target); - } } protected override void LoadComplete() @@ -168,8 +150,26 @@ namespace osu.Game.Screens.Play.PlayerSettings base.LoadComplete(); rateSlider.Current.BindValueChanged(multiplier => multiplierText.Text = $"{multiplier.NewValue:0.00}x", true); - if (gameplayClock != null) - isPaused.BindTarget = gameplayClock.IsPaused; + isPaused.BindTo(gameplayClock.IsPaused); + isPaused.BindValueChanged(paused => + { + if (!paused.NewValue) + { + pausePlay.TooltipText = ToastStrings.PauseTrack; + pausePlay.Icon = FontAwesome.Regular.PauseCircle; + } + else + { + pausePlay.TooltipText = ToastStrings.PlayTrack; + pausePlay.Icon = FontAwesome.Regular.PlayCircle; + } + }, true); + } + + private void seek(int direction, double amount) + { + double target = Math.Clamp(gameplayClock.CurrentTime + (direction * amount), 0, gameplayState.Beatmap.GetLastObjectTime()); + gameplayClock.Seek(target); } private partial class SeekButton : IconButton From 60e972cd159dcfe653ed19cc2d96ed179701205d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jan 2024 14:59:35 +0900 Subject: [PATCH 2578/4092] Add ability to step forward/backwards single frames --- .../PlayerSettingsOverlayStrings.cs | 10 ++++++ .../Play/PlayerSettings/PlaybackSettings.cs | 33 ++++++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs b/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs index 1aedd9fc5b..60874da561 100644 --- a/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs +++ b/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs @@ -9,6 +9,16 @@ namespace osu.Game.Localisation { private const string prefix = @"osu.Game.Resources.Localisation.PlaybackSettings"; + /// + /// "Step backward one frame" + /// + public static LocalisableString StepBackward => new TranslatableString(getKey(@"step_backward_frame"), @"Step backward one frame"); + + /// + /// "Step forward one frame" + /// + public static LocalisableString StepForward => new TranslatableString(getKey(@"step_forward_frame"), @"Step forward one frame"); + /// /// "Seek backward {0} seconds" /// diff --git a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs index 34f086da33..b946805be8 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -87,13 +88,20 @@ namespace osu.Game.Screens.Play.PlayerSettings Action = () => seek(-1, seek_amount), TooltipText = PlayerSettingsOverlayStrings.SeekBackwardSeconds(seek_amount / 1000), }, + new SeekButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = FontAwesome.Solid.StepBackward, + Action = () => seekFrame(-1), + TooltipText = PlayerSettingsOverlayStrings.StepBackward, + }, pausePlay = new IconButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, Scale = new Vector2(1.4f), IconScale = new Vector2(1.4f), - Icon = FontAwesome.Regular.PlayCircle, Action = () => { if (gameplayClock.IsRunning) @@ -103,6 +111,14 @@ namespace osu.Game.Screens.Play.PlayerSettings }, }, new SeekButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = FontAwesome.Solid.StepForward, + Action = () => seekFrame(1), + TooltipText = PlayerSettingsOverlayStrings.StepForward, + }, + new SeekButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -166,6 +182,21 @@ namespace osu.Game.Screens.Play.PlayerSettings }, true); } + private void seekFrame(int direction) + { + gameplayClock.Stop(); + + var frames = gameplayState.Score.Replay.Frames; + + if (frames.Count == 0) + return; + + gameplayClock.Seek(direction < 0 + ? (frames.LastOrDefault(f => f.Time < gameplayClock.CurrentTime) ?? frames.First()).Time + : (frames.FirstOrDefault(f => f.Time > gameplayClock.CurrentTime) ?? frames.Last()).Time + ); + } + private void seek(int direction, double amount) { double target = Math.Clamp(gameplayClock.CurrentTime + (direction * amount), 0, gameplayState.Beatmap.GetLastObjectTime()); From 8c4af58109b202e4505f62634c8e9001397c5d3f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jan 2024 15:08:24 +0900 Subject: [PATCH 2579/4092] Keep replay controls expanded by default --- osu.Game/Configuration/OsuConfigManager.cs | 2 ++ osu.Game/Screens/Play/ReplayPlayer.cs | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index c05831d043..6b2cb4ee74 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -142,6 +142,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.FadePlayfieldWhenHealthLow, true); SetDefault(OsuSetting.KeyOverlay, false); SetDefault(OsuSetting.ReplaySettingsOverlay, true); + SetDefault(OsuSetting.ReplayPlaybackControlsExpanded, true); SetDefault(OsuSetting.GameplayLeaderboard, true); SetDefault(OsuSetting.AlwaysPlayFirstComboBreak, true); @@ -421,6 +422,7 @@ namespace osu.Game.Configuration ProfileCoverExpanded, EditorLimitedDistanceSnap, ReplaySettingsOverlay, + ReplayPlaybackControlsExpanded, AutomaticallyDownloadMissingBeatmaps, EditorShowSpeedChanges, TouchDisableGameplayTaps, diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 805f907466..d4d6e7ecd0 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -12,6 +12,7 @@ using osu.Framework.Bindables; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; @@ -52,7 +53,7 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader] - private void load() + private void load(OsuConfigManager config) { if (!LoadedBeatmapSuccessfully) return; @@ -60,7 +61,7 @@ namespace osu.Game.Screens.Play var playbackSettings = new PlaybackSettings { Depth = float.MaxValue, - Expanded = { Value = false } + Expanded = { BindTarget = config.GetBindable(OsuSetting.ReplayPlaybackControlsExpanded) } }; if (GameplayClockContainer is MasterGameplayClockContainer master) From 22cfc8605094aebb6809333582543d6443756899 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 18 Jan 2024 14:24:48 +0300 Subject: [PATCH 2580/4092] Add failing test case --- osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs index 2926b11067..ee0c64aa3f 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs @@ -40,8 +40,15 @@ namespace osu.Game.Tests.Visual.Settings AddStep("change value from default", () => textBox.Current.Value = "non-default"); AddUntilStep("restore button shown", () => revertToDefaultButton.Alpha > 0); + AddStep("disable setting", () => textBox.Current.Disabled = true); + AddUntilStep("restore button still shown", () => revertToDefaultButton.Alpha > 0); + + AddStep("enable setting", () => textBox.Current.Disabled = false); AddStep("restore default", () => textBox.Current.SetDefault()); AddUntilStep("restore button hidden", () => revertToDefaultButton.Alpha == 0); + + AddStep("disable setting", () => textBox.Current.Disabled = true); + AddUntilStep("restore button still hidden", () => revertToDefaultButton.Alpha == 0); } [Test] From b97c3ac9ee1c3a0e34afb296dc0e99543d002011 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 18 Jan 2024 14:20:07 +0300 Subject: [PATCH 2581/4092] Fix revert-to-default button appearing on disabled settings regardless of value --- osu.Game/Overlays/RevertToDefaultButton.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/RevertToDefaultButton.cs b/osu.Game/Overlays/RevertToDefaultButton.cs index 582138b0b4..6fa5209f64 100644 --- a/osu.Game/Overlays/RevertToDefaultButton.cs +++ b/osu.Game/Overlays/RevertToDefaultButton.cs @@ -115,7 +115,12 @@ namespace osu.Game.Overlays Enabled.Value = !current.Disabled; - this.FadeTo(current.Disabled ? 0.2f : (current.IsDefault ? 0 : 1), fade_duration, Easing.OutQuint); + if (current.IsDefault) + this.FadeTo(0, fade_duration, Easing.OutQuint); + else if (current.Disabled) + this.FadeTo(0.2f, fade_duration, Easing.OutQuint); + else + this.FadeTo(1, fade_duration, Easing.OutQuint); if (IsHovered && Enabled.Value) { From c50534c8191e3fe408fe9e04dcbaf41c2b9c4577 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jan 2024 20:38:15 +0900 Subject: [PATCH 2582/4092] Move seek/step logic into `ReplayPlayer` --- .../Play/PlayerSettings/PlaybackSettings.cs | 51 +++++-------------- osu.Game/Screens/Play/ReplayPlayer.cs | 42 +++++++++++---- 2 files changed, 45 insertions(+), 48 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs index b946805be8..b3d07421ed 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs @@ -1,14 +1,11 @@ // 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.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -36,10 +33,10 @@ namespace osu.Game.Screens.Play.PlayerSettings private readonly IBindable isPaused = new BindableBool(); [Resolved] - private GameplayClockContainer gameplayClock { get; set; } = null!; + private ReplayPlayer replayPlayer { get; set; } = null!; [Resolved] - private GameplayState gameplayState { get; set; } = null!; + private GameplayClockContainer gameplayClock { get; set; } = null!; private IconButton pausePlay = null!; @@ -51,9 +48,6 @@ namespace osu.Game.Screens.Play.PlayerSettings [BackgroundDependencyLoader] private void load() { - const double seek_amount = 5000; - const double seek_fast_amount = 10000; - Children = new Drawable[] { new FillFlowContainer @@ -77,23 +71,23 @@ namespace osu.Game.Screens.Play.PlayerSettings Anchor = Anchor.Centre, Origin = Anchor.Centre, Icon = FontAwesome.Solid.FastBackward, - Action = () => seek(-1, seek_fast_amount), - TooltipText = PlayerSettingsOverlayStrings.SeekBackwardSeconds(seek_fast_amount / 1000), + Action = () => replayPlayer.SeekInDirection(-10), + TooltipText = PlayerSettingsOverlayStrings.SeekBackwardSeconds(10 * ReplayPlayer.BASE_SEEK_AMOUNT / 1000), }, new SeekButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, Icon = FontAwesome.Solid.Backward, - Action = () => seek(-1, seek_amount), - TooltipText = PlayerSettingsOverlayStrings.SeekBackwardSeconds(seek_amount / 1000), + Action = () => replayPlayer.SeekInDirection(-1), + TooltipText = PlayerSettingsOverlayStrings.SeekBackwardSeconds(ReplayPlayer.BASE_SEEK_AMOUNT / 1000), }, new SeekButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, Icon = FontAwesome.Solid.StepBackward, - Action = () => seekFrame(-1), + Action = () => replayPlayer.StepFrame(-1), TooltipText = PlayerSettingsOverlayStrings.StepBackward, }, pausePlay = new IconButton @@ -115,7 +109,7 @@ namespace osu.Game.Screens.Play.PlayerSettings Anchor = Anchor.Centre, Origin = Anchor.Centre, Icon = FontAwesome.Solid.StepForward, - Action = () => seekFrame(1), + Action = () => replayPlayer.StepFrame(1), TooltipText = PlayerSettingsOverlayStrings.StepForward, }, new SeekButton @@ -123,16 +117,16 @@ namespace osu.Game.Screens.Play.PlayerSettings Anchor = Anchor.Centre, Origin = Anchor.Centre, Icon = FontAwesome.Solid.Forward, - Action = () => seek(1, seek_amount), - TooltipText = PlayerSettingsOverlayStrings.SeekForwardSeconds(seek_amount / 1000), + Action = () => replayPlayer.SeekInDirection(1), + TooltipText = PlayerSettingsOverlayStrings.SeekForwardSeconds(ReplayPlayer.BASE_SEEK_AMOUNT / 1000), }, new SeekButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, Icon = FontAwesome.Solid.FastForward, - Action = () => seek(1, seek_fast_amount), - TooltipText = PlayerSettingsOverlayStrings.SeekForwardSeconds(seek_fast_amount / 1000), + Action = () => replayPlayer.SeekInDirection(10), + TooltipText = PlayerSettingsOverlayStrings.SeekForwardSeconds(10 * ReplayPlayer.BASE_SEEK_AMOUNT / 1000), }, }, }, @@ -182,27 +176,6 @@ namespace osu.Game.Screens.Play.PlayerSettings }, true); } - private void seekFrame(int direction) - { - gameplayClock.Stop(); - - var frames = gameplayState.Score.Replay.Frames; - - if (frames.Count == 0) - return; - - gameplayClock.Seek(direction < 0 - ? (frames.LastOrDefault(f => f.Time < gameplayClock.CurrentTime) ?? frames.First()).Time - : (frames.FirstOrDefault(f => f.Time > gameplayClock.CurrentTime) ?? frames.Last()).Time - ); - } - - private void seek(int direction, double amount) - { - double target = Math.Clamp(gameplayClock.CurrentTime + (direction * amount), 0, gameplayState.Beatmap.GetLastObjectTime()); - gameplayClock.Seek(target); - } - private partial class SeekButton : IconButton { public SeekButton() diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index d4d6e7ecd0..a26a2b9904 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -23,8 +23,11 @@ using osu.Game.Users; namespace osu.Game.Screens.Play { + [Cached] public partial class ReplayPlayer : Player, IKeyBindingHandler { + public const double BASE_SEEK_AMOUNT = 1000; + private readonly Func, Score> createScore; private readonly bool replayIsFailedScore; @@ -93,16 +96,22 @@ namespace osu.Game.Screens.Play public bool OnPressed(KeyBindingPressEvent e) { - const double keyboard_seek_amount = 5000; - switch (e.Action) { + case GlobalAction.StepReplayBackward: + StepFrame(-1); + return true; + + case GlobalAction.StepReplayForward: + StepFrame(1); + return true; + case GlobalAction.SeekReplayBackward: - keyboardSeek(-1); + SeekInDirection(-1); return true; case GlobalAction.SeekReplayForward: - keyboardSeek(1); + SeekInDirection(1); return true; case GlobalAction.TogglePauseReplay: @@ -114,13 +123,28 @@ namespace osu.Game.Screens.Play } return false; + } - void keyboardSeek(int direction) - { - double target = Math.Clamp(GameplayClockContainer.CurrentTime + direction * keyboard_seek_amount, 0, GameplayState.Beatmap.GetLastObjectTime()); + public void StepFrame(int direction) + { + GameplayClockContainer.Stop(); - Seek(target); - } + var frames = GameplayState.Score.Replay.Frames; + + if (frames.Count == 0) + return; + + GameplayClockContainer.Seek(direction < 0 + ? (frames.LastOrDefault(f => f.Time < GameplayClockContainer.CurrentTime) ?? frames.First()).Time + : (frames.FirstOrDefault(f => f.Time > GameplayClockContainer.CurrentTime) ?? frames.Last()).Time + ); + } + + public void SeekInDirection(float amount) + { + double target = Math.Clamp(GameplayClockContainer.CurrentTime + amount * BASE_SEEK_AMOUNT, 0, GameplayState.Beatmap.GetLastObjectTime()); + + Seek(target); } public void OnReleased(KeyBindingReleaseEvent e) From 0383bdf6a15d57a0a499b6b88ac53d4eb57b4f2b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jan 2024 20:38:25 +0900 Subject: [PATCH 2583/4092] Add bindings for stepping backward/forward --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 10 +++++++++- osu.Game/Localisation/GlobalActionKeyBindingStrings.cs | 10 ++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 5a39c02185..436334cfe1 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -170,6 +170,8 @@ namespace osu.Game.Input.Bindings new KeyBinding(InputKey.MouseMiddle, GlobalAction.TogglePauseReplay), new KeyBinding(InputKey.Left, GlobalAction.SeekReplayBackward), new KeyBinding(InputKey.Right, GlobalAction.SeekReplayForward), + new KeyBinding(InputKey.Comma, GlobalAction.StepReplayBackward), + new KeyBinding(InputKey.Period, GlobalAction.StepReplayForward), new KeyBinding(new[] { InputKey.Control, InputKey.H }, GlobalAction.ToggleReplaySettings), }; @@ -411,7 +413,13 @@ namespace osu.Game.Input.Bindings IncreaseOffset, [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.DecreaseOffset))] - DecreaseOffset + DecreaseOffset, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.StepReplayForward))] + StepReplayForward, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.StepReplayBackward))] + StepReplayBackward, } public enum GlobalActionCategory diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index ca27d0ff95..703e0ff1ca 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -324,6 +324,16 @@ namespace osu.Game.Localisation /// public static LocalisableString SeekReplayBackward => new TranslatableString(getKey(@"seek_replay_backward"), @"Seek replay backward"); + /// + /// "Seek replay forward one frame" + /// + public static LocalisableString StepReplayForward => new TranslatableString(getKey(@"step_replay_forward"), @"Seek replay forward one frame"); + + /// + /// "Step replay backward one frame" + /// + public static LocalisableString StepReplayBackward => new TranslatableString(getKey(@"step_replay_backward"), @"Step replay backward one frame"); + /// /// "Toggle chat focus" /// From 87369f8a808b29e3da0bde82027a439e763532dc Mon Sep 17 00:00:00 2001 From: smallketchup82 <69545310+smallketchup82@users.noreply.github.com> Date: Thu, 18 Jan 2024 11:16:03 -0500 Subject: [PATCH 2584/4092] Conform to code style & remove unused imports --- osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultyIcon.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultyIcon.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultyIcon.cs index aa2543eea1..79f9aec2e3 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultyIcon.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultyIcon.cs @@ -4,11 +4,7 @@ #nullable disable using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Platform; using osu.Game.Beatmaps.Drawables; -using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Tests.Beatmaps; @@ -17,7 +13,7 @@ namespace osu.Game.Tests.Visual.Beatmaps public partial class TestSceneDifficultyIcon : OsuTestScene { [Test] - public void createDifficultyIcon() + public void CreateDifficultyIcon() { DifficultyIcon difficultyIcon = null; From 3eeefd5b7e8708894c26474cf3868fc54e662f61 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 18 Jan 2024 16:13:48 -0800 Subject: [PATCH 2585/4092] Fix changelog stream user count only accounting for latest build --- osu.Game/Online/API/Requests/Responses/APIUpdateStream.cs | 3 +++ osu.Game/Overlays/Changelog/ChangelogUpdateStreamItem.cs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIUpdateStream.cs b/osu.Game/Online/API/Requests/Responses/APIUpdateStream.cs index 76d1941d9d..dac72f2488 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUpdateStream.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUpdateStream.cs @@ -28,6 +28,9 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty("latest_build")] public APIChangelogBuild LatestBuild { get; set; } + [JsonProperty("user_count")] + public int UserCount { get; set; } + public bool Equals(APIUpdateStream other) => Id == other?.Id; internal static readonly Dictionary KNOWN_STREAMS = new Dictionary diff --git a/osu.Game/Overlays/Changelog/ChangelogUpdateStreamItem.cs b/osu.Game/Overlays/Changelog/ChangelogUpdateStreamItem.cs index 08ea373fb1..30273d2405 100644 --- a/osu.Game/Overlays/Changelog/ChangelogUpdateStreamItem.cs +++ b/osu.Game/Overlays/Changelog/ChangelogUpdateStreamItem.cs @@ -24,7 +24,7 @@ namespace osu.Game.Overlays.Changelog protected override LocalisableString AdditionalText => Value.LatestBuild.DisplayVersion; - protected override LocalisableString InfoText => Value.LatestBuild.Users > 0 ? $"{"user".ToQuantity(Value.LatestBuild.Users, "N0")} online" : null; + protected override LocalisableString InfoText => Value.UserCount > 0 ? $"{"user".ToQuantity(Value.UserCount, "N0")} online" : null; protected override Color4 GetBarColour(OsuColour colours) => Value.Colour; } From ce4fd6aca51690455f9a1f02ada35d95269883ed Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Jan 2024 23:48:05 +0300 Subject: [PATCH 2586/4092] Rename `DEFAULT_HEIGHT` to `BASE_HEIGHT` --- .../Edit/Blueprints/HitPlacementBlueprint.cs | 2 +- .../Edit/Blueprints/TaikoSpanPlacementBlueprint.cs | 6 +++--- osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs | 2 +- .../Skinning/Legacy/TaikoLegacyHitTarget.cs | 2 +- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs index 8b1a4f688c..329fff5b42 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints { InternalChild = piece = new HitPiece { - Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT) + Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.BASE_HEIGHT) }; } diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs index b0919417a4..cd52398086 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs @@ -39,15 +39,15 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints { headPiece = new HitPiece { - Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT) + Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.BASE_HEIGHT) }, lengthPiece = new LengthPiece { - Height = TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT + Height = TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.BASE_HEIGHT }, tailPiece = new HitPiece { - Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT) + Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.BASE_HEIGHT) } }; } diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 733772e21f..26bbb6d71b 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Taiko.Mods /// private Vector2 adjustSizeForPlayfieldAspectRatio(float size) { - return new Vector2(0, size * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT); + return new Vector2(0, size * taikoPlayfield.DrawHeight / TaikoPlayfield.BASE_HEIGHT); } protected override void UpdateFlashlightSize(float size) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyHitTarget.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyHitTarget.cs index 492782f0d1..2f845adcd8 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyHitTarget.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyHitTarget.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy // Relying on RelativeSizeAxes.Both + FillMode.Fit doesn't work due to the precise pixel layout requirements. // This is a bit ugly but makes the non-legacy implementations a lot cleaner to implement. - content.Scale = new Vector2(DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT); + content.Scale = new Vector2(DrawHeight / TaikoPlayfield.BASE_HEIGHT); } } } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 7e3ed7a4d4..af4ed0557a 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -28,9 +28,9 @@ namespace osu.Game.Rulesets.Taiko.UI public partial class TaikoPlayfield : ScrollingPlayfield { /// - /// Default height of a when inside a . + /// Base height of a when inside a . /// - public const float DEFAULT_HEIGHT = 200; + public const float BASE_HEIGHT = 200; /// /// Whether the hit target should be nudged further towards the left area, matching the stable "classic" position. From 3e17d01cebc88c56615f67c0deea7351168a46b1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Jan 2024 23:49:14 +0300 Subject: [PATCH 2587/4092] Rewrite taiko playfield adjustment container to keep height constant --- .../UI/TaikoPlayfieldAdjustmentContainer.cs | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs index 54608b77de..1bc9f7d89c 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs @@ -5,23 +5,31 @@ using System; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Rulesets.UI; +using osuTK; namespace osu.Game.Rulesets.Taiko.UI { public partial class TaikoPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer { - private const float default_relative_height = TaikoPlayfield.DEFAULT_HEIGHT / 768; - public const float MAXIMUM_ASPECT = 16f / 9f; public const float MINIMUM_ASPECT = 5f / 4f; public readonly IBindable LockPlayfieldAspectRange = new BindableBool(true); + public TaikoPlayfieldAdjustmentContainer() + { + RelativeSizeAxes = Axes.X; + RelativePositionAxes = Axes.Y; + Height = TaikoPlayfield.BASE_HEIGHT; + } + protected override void Update() { base.Update(); - float height = default_relative_height; + const float base_relative_height = TaikoPlayfield.BASE_HEIGHT / 768; + + float relativeHeight = base_relative_height; // Players coming from stable expect to be able to change the aspect ratio regardless of the window size. // We originally wanted to limit this more, but there was considerable pushback from the community. @@ -33,19 +41,20 @@ namespace osu.Game.Rulesets.Taiko.UI float currentAspect = Parent!.ChildSize.X / Parent!.ChildSize.Y; if (currentAspect > MAXIMUM_ASPECT) - height *= currentAspect / MAXIMUM_ASPECT; + relativeHeight *= currentAspect / MAXIMUM_ASPECT; else if (currentAspect < MINIMUM_ASPECT) - height *= currentAspect / MINIMUM_ASPECT; + relativeHeight *= currentAspect / MINIMUM_ASPECT; } // Limit the maximum relative height of the playfield to one-third of available area to avoid it masking out on extreme resolutions. - height = Math.Min(height, 1f / 3f); - Height = height; + relativeHeight = Math.Min(relativeHeight, 1f / 3f); // Position the taiko playfield exactly one playfield from the top of the screen, if there is enough space for it. // Note that the relative height cannot exceed one-third - if that limit is hit, the playfield will be exactly centered. - RelativePositionAxes = Axes.Y; - Y = height; + Y = relativeHeight; + + Scale = new Vector2(Math.Max(relativeHeight / base_relative_height, 1f)); + Width = 1 / Scale.X; } } } From f5ce9eaf7512b27c9df3b03adb8d8a76b7462969 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Jan 2024 23:58:55 +0300 Subject: [PATCH 2588/4092] Refactor taiko playfield layout to use constant values --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 38 ++++++++------------ 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index af4ed0557a..4e1691ffe0 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -21,7 +21,6 @@ using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Scoring; using osu.Game.Skinning; -using osuTK; namespace osu.Game.Rulesets.Taiko.UI { @@ -44,7 +43,6 @@ namespace osu.Game.Rulesets.Taiko.UI private JudgementContainer judgementContainer = null!; private ScrollingHitObjectContainer drumRollHitContainer = null!; internal Drawable HitTarget = null!; - private SkinnableDrawable mascot = null!; private JudgementPooler judgementPooler = null!; private readonly IDictionary explosionPools = new Dictionary(); @@ -59,13 +57,11 @@ namespace osu.Game.Rulesets.Taiko.UI /// private BarLinePlayfield barLinePlayfield = null!; - private Container barLineContent = null!; - private Container hitObjectContent = null!; - private Container overlayContent = null!; - [BackgroundDependencyLoader] private void load(OsuColour colours) { + const float hit_target_width = BASE_HEIGHT; + inputDrum = new InputDrum { Anchor = Anchor.CentreLeft, @@ -89,7 +85,7 @@ namespace osu.Game.Rulesets.Taiko.UI inputDrum.CreateProxy(), } }, - mascot = new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.Mascot), _ => Empty()) + new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.Mascot), _ => Empty()) { Origin = Anchor.BottomLeft, Anchor = Anchor.TopLeft, @@ -101,14 +97,13 @@ namespace osu.Game.Rulesets.Taiko.UI { Name = "Right area", RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, Children = new Drawable[] { new Container { - Name = "Elements before hit objects", - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, + Name = "Elements behind hit objects", + RelativeSizeAxes = Axes.Y, + Width = hit_target_width, Children = new[] { new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.KiaiGlow), _ => Empty()) @@ -125,10 +120,11 @@ namespace osu.Game.Rulesets.Taiko.UI } } }, - barLineContent = new Container + new Container { Name = "Bar line content", RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = hit_target_width / 2 }, Children = new Drawable[] { UnderlayElements = new Container @@ -138,17 +134,19 @@ namespace osu.Game.Rulesets.Taiko.UI barLinePlayfield = new BarLinePlayfield(), } }, - hitObjectContent = new Container + new Container { Name = "Masked hit objects content", RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = hit_target_width / 2 }, Masking = true, Child = HitObjectContainer, }, - overlayContent = new Container + new Container { - Name = "Elements after hit objects", + Name = "Overlay content", RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = hit_target_width / 2 }, Children = new Drawable[] { drumRollHitContainer = new DrumRollHitContainer(), @@ -226,14 +224,8 @@ namespace osu.Game.Rulesets.Taiko.UI { base.Update(); - // Padding is required to be updated for elements which are based on "absolute" X sized elements. - // This is basically allowing for correct alignment as relative pieces move around them. - rightArea.Padding = new MarginPadding { Left = inputDrum.Width }; - barLineContent.Padding = new MarginPadding { Left = HitTarget.DrawWidth / 2 }; - hitObjectContent.Padding = new MarginPadding { Left = HitTarget.DrawWidth / 2 }; - overlayContent.Padding = new MarginPadding { Left = HitTarget.DrawWidth / 2 }; - - mascot.Scale = new Vector2(DrawHeight / DEFAULT_HEIGHT); + // todo: input drum width should be constant. + rightArea.Padding = new MarginPadding { Left = inputDrum.DrawWidth }; } #region Pooling support From fa2c33c64173d51d5ad14654e12e3f956f17cac8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 20 Jan 2024 00:20:43 +0300 Subject: [PATCH 2589/4092] Upscale playfield by difference in game height as well I honestly don't have much clue about this one but doing so matches master. --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs index 1bc9f7d89c..57722b8065 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Taiko.UI // Note that the relative height cannot exceed one-third - if that limit is hit, the playfield will be exactly centered. Y = relativeHeight; - Scale = new Vector2(Math.Max(relativeHeight / base_relative_height, 1f)); + Scale = new Vector2(Math.Max((Parent!.ChildSize.Y / 768f) * (relativeHeight / base_relative_height), 1f)); Width = 1 / Scale.X; } } From 0c03326eaf1bb5954b118c8f58ba721b8058fc8b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 20 Jan 2024 01:25:04 +0300 Subject: [PATCH 2590/4092] Update remaining usages of playfield height --- osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs | 2 +- .../Skinning/Legacy/TaikoLegacyHitTarget.cs | 14 +------------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 26bbb6d71b..64f2f4c18a 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Taiko.Mods /// private Vector2 adjustSizeForPlayfieldAspectRatio(float size) { - return new Vector2(0, size * taikoPlayfield.DrawHeight / TaikoPlayfield.BASE_HEIGHT); + return new Vector2(0, size * taikoPlayfield.Parent!.Scale.Y); } protected override void UpdateFlashlightSize(float size) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyHitTarget.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyHitTarget.cs index 2f845adcd8..cf9d8dd52e 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyHitTarget.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyHitTarget.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Game.Rulesets.Taiko.UI; using osu.Game.Skinning; using osuTK; @@ -13,14 +12,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { public partial class TaikoLegacyHitTarget : CompositeDrawable { - private Container content = null!; - [BackgroundDependencyLoader] private void load(ISkinSource skin) { RelativeSizeAxes = Axes.Both; - InternalChild = content = new Container + InternalChild = new Container { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, @@ -46,14 +43,5 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy } }; } - - protected override void Update() - { - base.Update(); - - // Relying on RelativeSizeAxes.Both + FillMode.Fit doesn't work due to the precise pixel layout requirements. - // This is a bit ugly but makes the non-legacy implementations a lot cleaner to implement. - content.Scale = new Vector2(DrawHeight / TaikoPlayfield.BASE_HEIGHT); - } } } From d4fef99e1f677fa3af1e1822ff9d9f2116a4be59 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Jan 2024 23:26:35 +0300 Subject: [PATCH 2591/4092] Define constant input drum width for osu!taiko --- .../Skinning/TestSceneInputDrum.cs | 7 +++-- .../Skinning/Argon/ArgonInputDrum.cs | 2 +- .../Skinning/Default/DefaultInputDrum.cs | 2 +- .../Skinning/Legacy/LegacyInputDrum.cs | 28 ++++++------------- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 9 +----- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 20 +++++-------- 6 files changed, 24 insertions(+), 44 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs index 3c6319ddf9..900d6523cf 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs @@ -30,8 +30,11 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(200), - Child = new InputDrum() + Size = new Vector2(180f, 200f), + Child = new InputDrum + { + RelativeSizeAxes = Axes.Both, + } } }); } diff --git a/osu.Game.Rulesets.Taiko/Skinning/Argon/ArgonInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Argon/ArgonInputDrum.cs index f7b7105bdc..f22c7bf017 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Argon/ArgonInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Argon/ArgonInputDrum.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon public ArgonInputDrum() { - RelativeSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.X; } [BackgroundDependencyLoader] diff --git a/osu.Game.Rulesets.Taiko/Skinning/Default/DefaultInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Default/DefaultInputDrum.cs index 60bacf6413..3eb4f6b8a6 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Default/DefaultInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Default/DefaultInputDrum.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default { public DefaultInputDrum() { - RelativeSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.X; } [BackgroundDependencyLoader] diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index 8ad419f8bd..07a7f237ba 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Rulesets.Taiko.UI; using osu.Game.Skinning; using osuTK; @@ -18,22 +19,20 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy /// internal partial class LegacyInputDrum : Container { - private Container content = null!; private LegacyHalfDrum left = null!; private LegacyHalfDrum right = null!; public LegacyInputDrum() { - RelativeSizeAxes = Axes.Y; - AutoSizeAxes = Axes.X; + RelativeSizeAxes = Axes.Both; } [BackgroundDependencyLoader] private void load(ISkinSource skin) { - Child = content = new Container + Child = new Container { - Size = new Vector2(180, 200), + RelativeSizeAxes = Axes.Both, Children = new Drawable[] { new Sprite @@ -66,33 +65,24 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy const float ratio = 1.6f; // because the right half is flipped, we need to position using width - position to get the true "topleft" origin position - float negativeScaleAdjust = content.Width / ratio; + const float negative_scale_adjust = TaikoPlayfield.INPUT_DRUM_WIDTH / ratio; if (skin.GetConfig(SkinConfiguration.LegacySetting.Version)?.Value >= 2.1m) { left.Centre.Position = new Vector2(0, taiko_bar_y) * ratio; - right.Centre.Position = new Vector2(negativeScaleAdjust - 56, taiko_bar_y) * ratio; + right.Centre.Position = new Vector2(negative_scale_adjust - 56, taiko_bar_y) * ratio; left.Rim.Position = new Vector2(0, taiko_bar_y) * ratio; - right.Rim.Position = new Vector2(negativeScaleAdjust - 56, taiko_bar_y) * ratio; + right.Rim.Position = new Vector2(negative_scale_adjust - 56, taiko_bar_y) * ratio; } else { left.Centre.Position = new Vector2(18, taiko_bar_y + 31) * ratio; - right.Centre.Position = new Vector2(negativeScaleAdjust - 54, taiko_bar_y + 31) * ratio; + right.Centre.Position = new Vector2(negative_scale_adjust - 54, taiko_bar_y + 31) * ratio; left.Rim.Position = new Vector2(8, taiko_bar_y + 23) * ratio; - right.Rim.Position = new Vector2(negativeScaleAdjust - 53, taiko_bar_y + 23) * ratio; + right.Rim.Position = new Vector2(negative_scale_adjust - 53, taiko_bar_y + 23) * ratio; } } - protected override void Update() - { - base.Update(); - - // Relying on RelativeSizeAxes.Both + FillMode.Fit doesn't work due to the precise pixel layout requirements. - // This is a bit ugly but makes the non-legacy implementations a lot cleaner to implement. - content.Scale = new Vector2(DrawHeight / content.Size.Y); - } - /// /// A half-drum. Contains one centre and one rim hit. /// diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 725857ed34..d0a8cf647d 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -14,12 +14,6 @@ namespace osu.Game.Rulesets.Taiko.UI /// internal partial class InputDrum : Container { - public InputDrum() - { - AutoSizeAxes = Axes.X; - RelativeSizeAxes = Axes.Y; - } - [BackgroundDependencyLoader] private void load() { @@ -27,8 +21,7 @@ namespace osu.Game.Rulesets.Taiko.UI { new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.InputDrum), _ => new DefaultInputDrum()) { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Both, }, }; } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 4e1691ffe0..95907bcb8b 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -31,6 +31,8 @@ namespace osu.Game.Rulesets.Taiko.UI /// public const float BASE_HEIGHT = 200; + public const float INPUT_DRUM_WIDTH = 180f; + /// /// Whether the hit target should be nudged further towards the left area, matching the stable "classic" position. /// @@ -49,7 +51,6 @@ namespace osu.Game.Rulesets.Taiko.UI private ProxyContainer topLevelHitContainer = null!; private InputDrum inputDrum = null!; - private Container rightArea = null!; /// /// is purposefully not called on this to prevent i.e. being able to interact @@ -66,8 +67,8 @@ namespace osu.Game.Rulesets.Taiko.UI { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.X, RelativeSizeAxes = Axes.Y, + Width = INPUT_DRUM_WIDTH, }; InternalChildren = new[] @@ -76,8 +77,8 @@ namespace osu.Game.Rulesets.Taiko.UI new Container { Name = "Left overlay", - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, + RelativeSizeAxes = Axes.Y, + Width = INPUT_DRUM_WIDTH, BorderColour = colours.Gray0, Children = new[] { @@ -93,10 +94,11 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.None, Y = 0.2f }, - rightArea = new Container + new Container { Name = "Right area", RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = INPUT_DRUM_WIDTH }, Children = new Drawable[] { new Container @@ -220,14 +222,6 @@ namespace osu.Game.Rulesets.Taiko.UI topLevelHitContainer.Add(taikoObject.CreateProxiedContent()); } - protected override void Update() - { - base.Update(); - - // todo: input drum width should be constant. - rightArea.Padding = new MarginPadding { Left = inputDrum.DrawWidth }; - } - #region Pooling support public override void Add(HitObject h) From 2f618b7f35514f1025d4750cb3726e670f4aa2d8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 20 Jan 2024 00:28:26 +0300 Subject: [PATCH 2592/4092] Change taiko hit position to always match stable --- osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs | 3 --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 14 +++++--------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs index cdeaafde10..f63d6c2673 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs @@ -16,9 +16,6 @@ namespace osu.Game.Rulesets.Taiko.Mods { var drawableTaikoRuleset = (DrawableTaikoRuleset)drawableRuleset; drawableTaikoRuleset.LockPlayfieldAspectRange.Value = false; - - var playfield = (TaikoPlayfield)drawableRuleset.Playfield; - playfield.ClassicHitTargetPosition.Value = true; } public void ApplyToDrawableHitObject(DrawableHitObject drawable) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 95907bcb8b..0510f08068 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; @@ -33,11 +32,6 @@ namespace osu.Game.Rulesets.Taiko.UI public const float INPUT_DRUM_WIDTH = 180f; - /// - /// Whether the hit target should be nudged further towards the left area, matching the stable "classic" position. - /// - public Bindable ClassicHitTargetPosition = new BindableBool(); - public Container UnderlayElements { get; private set; } = null!; private Container hitExplosionContainer = null!; @@ -62,6 +56,7 @@ namespace osu.Game.Rulesets.Taiko.UI private void load(OsuColour colours) { const float hit_target_width = BASE_HEIGHT; + const float hit_target_offset = -24f; inputDrum = new InputDrum { @@ -106,6 +101,7 @@ namespace osu.Game.Rulesets.Taiko.UI Name = "Elements behind hit objects", RelativeSizeAxes = Axes.Y, Width = hit_target_width, + X = hit_target_offset, Children = new[] { new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.KiaiGlow), _ => Empty()) @@ -126,7 +122,7 @@ namespace osu.Game.Rulesets.Taiko.UI { Name = "Bar line content", RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = hit_target_width / 2 }, + Padding = new MarginPadding { Left = hit_target_width / 2 + hit_target_offset }, Children = new Drawable[] { UnderlayElements = new Container @@ -140,7 +136,7 @@ namespace osu.Game.Rulesets.Taiko.UI { Name = "Masked hit objects content", RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = hit_target_width / 2 }, + Padding = new MarginPadding { Left = hit_target_width / 2 + hit_target_offset }, Masking = true, Child = HitObjectContainer, }, @@ -148,7 +144,7 @@ namespace osu.Game.Rulesets.Taiko.UI { Name = "Overlay content", RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = hit_target_width / 2 }, + Padding = new MarginPadding { Left = hit_target_width / 2 + hit_target_offset }, Children = new Drawable[] { drumRollHitContainer = new DrumRollHitContainer(), From d8cba1cbfdee1b39b1403e90bcad33444d4e1acb Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 20 Jan 2024 00:22:32 +0300 Subject: [PATCH 2593/4092] Move taiko playfield down a nudge to visually match stable --- .../UI/TaikoPlayfieldAdjustmentContainer.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs index 57722b8065..9a5fc90a05 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs @@ -51,7 +51,12 @@ namespace osu.Game.Rulesets.Taiko.UI // Position the taiko playfield exactly one playfield from the top of the screen, if there is enough space for it. // Note that the relative height cannot exceed one-third - if that limit is hit, the playfield will be exactly centered. - Y = relativeHeight; + float playfieldPosition = relativeHeight; + + // arbitrary offset to make playfield position match stable. + playfieldPosition += 0.022f; + + Y = playfieldPosition; Scale = new Vector2(Math.Max((Parent!.ChildSize.Y / 768f) * (relativeHeight / base_relative_height), 1f)); Width = 1 / Scale.X; From 26c0d1077a7a4d00fb9ae22bcb16dde08365b987 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 20 Jan 2024 00:22:53 +0100 Subject: [PATCH 2594/4092] Refactor scale handling in editor to facilitate reuse --- .../Edit/OsuSelectionHandler.cs | 142 +----------- .../Edit/OsuSelectionScaleHandler.cs | 205 ++++++++++++++++++ .../Edit/Compose/Components/SelectionBox.cs | 2 - .../Components/SelectionBoxScaleHandle.cs | 94 +++++++- .../Compose/Components/SelectionHandler.cs | 10 +- .../Components/SelectionScaleHandler.cs | 88 ++++++++ osu.Game/Utils/GeometryUtils.cs | 9 + 7 files changed, 402 insertions(+), 148 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs create mode 100644 osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index cea2adc6e2..c36b535bfa 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; @@ -25,15 +24,6 @@ namespace osu.Game.Rulesets.Osu.Edit { public partial class OsuSelectionHandler : EditorSelectionHandler { - [Resolved(CanBeNull = true)] - private IDistanceSnapProvider? snapProvider { get; set; } - - /// - /// During a transform, the initial path types of a single selected slider are stored so they - /// can be maintained throughout the operation. - /// - private List? referencePathTypes; - protected override void OnSelectionChanged() { base.OnSelectionChanged(); @@ -46,12 +36,6 @@ namespace osu.Game.Rulesets.Osu.Edit SelectionBox.CanReverse = EditorBeatmap.SelectedHitObjects.Count > 1 || EditorBeatmap.SelectedHitObjects.Any(s => s is Slider); } - protected override void OnOperationEnded() - { - base.OnOperationEnded(); - referencePathTypes = null; - } - protected override bool OnKeyDown(KeyDownEvent e) { if (e.Key == Key.M && e.ControlPressed && e.ShiftPressed) @@ -135,96 +119,9 @@ namespace osu.Game.Rulesets.Osu.Edit return didFlip; } - public override bool HandleScale(Vector2 scale, Anchor reference) - { - adjustScaleFromAnchor(ref scale, reference); - - var hitObjects = selectedMovableObjects; - - // for the time being, allow resizing of slider paths only if the slider is - // the only hit object selected. with a group selection, it's likely the user - // is not looking to change the duration of the slider but expand the whole pattern. - if (hitObjects.Length == 1 && hitObjects.First() is Slider slider) - scaleSlider(slider, scale); - else - scaleHitObjects(hitObjects, reference, scale); - - moveSelectionInBounds(); - return true; - } - - private static void adjustScaleFromAnchor(ref Vector2 scale, Anchor reference) - { - // cancel out scale in axes we don't care about (based on which drag handle was used). - if ((reference & Anchor.x1) > 0) scale.X = 0; - if ((reference & Anchor.y1) > 0) scale.Y = 0; - - // reverse the scale direction if dragging from top or left. - if ((reference & Anchor.x0) > 0) scale.X = -scale.X; - if ((reference & Anchor.y0) > 0) scale.Y = -scale.Y; - } - public override SelectionRotationHandler CreateRotationHandler() => new OsuSelectionRotationHandler(); - private void scaleSlider(Slider slider, Vector2 scale) - { - referencePathTypes ??= slider.Path.ControlPoints.Select(p => p.Type).ToList(); - - Quad sliderQuad = GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position)); - - // Limit minimum distance between control points after scaling to almost 0. Less than 0 causes the slider to flip, exactly 0 causes a crash through division by 0. - scale = Vector2.ComponentMax(new Vector2(Precision.FLOAT_EPSILON), sliderQuad.Size + scale) - sliderQuad.Size; - - Vector2 pathRelativeDeltaScale = new Vector2( - sliderQuad.Width == 0 ? 0 : 1 + scale.X / sliderQuad.Width, - sliderQuad.Height == 0 ? 0 : 1 + scale.Y / sliderQuad.Height); - - Queue oldControlPoints = new Queue(); - - foreach (var point in slider.Path.ControlPoints) - { - oldControlPoints.Enqueue(point.Position); - point.Position *= pathRelativeDeltaScale; - } - - // Maintain the path types in case they were defaulted to bezier at some point during scaling - for (int i = 0; i < slider.Path.ControlPoints.Count; ++i) - slider.Path.ControlPoints[i].Type = referencePathTypes[i]; - - // Snap the slider's length to the current beat divisor - // to calculate the final resulting duration / bounding box before the final checks. - slider.SnapTo(snapProvider); - - //if sliderhead or sliderend end up outside playfield, revert scaling. - Quad scaledQuad = GeometryUtils.GetSurroundingQuad(new OsuHitObject[] { slider }); - (bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad); - - if (xInBounds && yInBounds && slider.Path.HasValidLength) - return; - - foreach (var point in slider.Path.ControlPoints) - point.Position = oldControlPoints.Dequeue(); - - // Snap the slider's length again to undo the potentially-invalid length applied by the previous snap. - slider.SnapTo(snapProvider); - } - - private void scaleHitObjects(OsuHitObject[] hitObjects, Anchor reference, Vector2 scale) - { - scale = getClampedScale(hitObjects, reference, scale); - Quad selectionQuad = GeometryUtils.GetSurroundingQuad(hitObjects); - - foreach (var h in hitObjects) - h.Position = GeometryUtils.GetScaledPosition(reference, scale, selectionQuad, h.Position); - } - - private (bool X, bool Y) isQuadInBounds(Quad quad) - { - bool xInBounds = (quad.TopLeft.X >= 0) && (quad.BottomRight.X <= DrawWidth); - bool yInBounds = (quad.TopLeft.Y >= 0) && (quad.BottomRight.Y <= DrawHeight); - - return (xInBounds, yInBounds); - } + public override SelectionScaleHandler CreateScaleHandler() => new OsuSelectionScaleHandler(); private void moveSelectionInBounds() { @@ -248,43 +145,6 @@ namespace osu.Game.Rulesets.Osu.Edit h.Position += delta; } - /// - /// Clamp scale for multi-object-scaling where selection does not exceed playfield bounds or flip. - /// - /// The hitobjects to be scaled - /// The anchor from which the scale operation is performed - /// The scale to be clamped - /// The clamped scale vector - private Vector2 getClampedScale(OsuHitObject[] hitObjects, Anchor reference, Vector2 scale) - { - float xOffset = ((reference & Anchor.x0) > 0) ? -scale.X : 0; - float yOffset = ((reference & Anchor.y0) > 0) ? -scale.Y : 0; - - Quad selectionQuad = GeometryUtils.GetSurroundingQuad(hitObjects); - - //todo: this is not always correct for selections involving sliders. This approximation assumes each point is scaled independently, but sliderends move with the sliderhead. - Quad scaledQuad = new Quad(selectionQuad.TopLeft.X + xOffset, selectionQuad.TopLeft.Y + yOffset, selectionQuad.Width + scale.X, selectionQuad.Height + scale.Y); - - //max Size -> playfield bounds - if (scaledQuad.TopLeft.X < 0) - scale.X += scaledQuad.TopLeft.X; - if (scaledQuad.TopLeft.Y < 0) - scale.Y += scaledQuad.TopLeft.Y; - - if (scaledQuad.BottomRight.X > DrawWidth) - scale.X -= scaledQuad.BottomRight.X - DrawWidth; - if (scaledQuad.BottomRight.Y > DrawHeight) - scale.Y -= scaledQuad.BottomRight.Y - DrawHeight; - - //min Size -> almost 0. Less than 0 causes the quad to flip, exactly 0 causes scaling to get stuck at minimum scale. - Vector2 scaledSize = selectionQuad.Size + scale; - Vector2 minSize = new Vector2(Precision.FLOAT_EPSILON); - - scale = Vector2.ComponentMax(minSize, scaledSize) - selectionQuad.Size; - - return scale; - } - /// /// All osu! hitobjects which can be moved/rotated/scaled. /// diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs new file mode 100644 index 0000000000..8068c73131 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -0,0 +1,205 @@ +// 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.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Primitives; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Compose.Components; +using osu.Game.Utils; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public partial class OsuSelectionScaleHandler : SelectionScaleHandler + { + [Resolved] + private IEditorChangeHandler? changeHandler { get; set; } + + [Resolved(CanBeNull = true)] + private IDistanceSnapProvider? snapProvider { get; set; } + + private BindableList selectedItems { get; } = new BindableList(); + + [BackgroundDependencyLoader] + private void load(EditorBeatmap editorBeatmap) + { + selectedItems.BindTo(editorBeatmap.SelectedHitObjects); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + selectedItems.CollectionChanged += (_, __) => updateState(); + updateState(); + } + + private void updateState() + { + var quad = GeometryUtils.GetSurroundingQuad(selectedMovableObjects); + CanScale.Value = quad.Width > 0 || quad.Height > 0; + } + + private OsuHitObject[]? objectsInScale; + + private Vector2? defaultOrigin; + private Dictionary? originalPositions; + private Dictionary? originalPathControlPointPositions; + private Dictionary? originalPathControlPointTypes; + + public override void Begin() + { + if (objectsInScale != null) + throw new InvalidOperationException($"Cannot {nameof(Begin)} a scale operation while another is in progress!"); + + changeHandler?.BeginChange(); + + objectsInScale = selectedMovableObjects.ToArray(); + OriginalSurroundingQuad = objectsInScale.Length == 1 && objectsInScale.First() is Slider slider + ? GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position)) + : GeometryUtils.GetSurroundingQuad(objectsInScale); + defaultOrigin = OriginalSurroundingQuad.Value.Centre; + originalPositions = objectsInScale.ToDictionary(obj => obj, obj => obj.Position); + originalPathControlPointPositions = objectsInScale.OfType().ToDictionary( + obj => obj, + obj => obj.Path.ControlPoints.Select(point => point.Position).ToArray()); + originalPathControlPointTypes = objectsInScale.OfType().ToDictionary( + obj => obj, + obj => obj.Path.ControlPoints.Select(p => p.Type).ToArray()); + } + + public override void Update(Vector2 scale, Vector2? origin = null) + { + if (objectsInScale == null) + throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!"); + + Debug.Assert(originalPositions != null && originalPathControlPointPositions != null && defaultOrigin != null && originalPathControlPointTypes != null && OriginalSurroundingQuad != null); + + Vector2 actualOrigin = origin ?? defaultOrigin.Value; + + // for the time being, allow resizing of slider paths only if the slider is + // the only hit object selected. with a group selection, it's likely the user + // is not looking to change the duration of the slider but expand the whole pattern. + if (objectsInScale.Length == 1 && objectsInScale.First() is Slider slider) + scaleSlider(slider, scale, originalPathControlPointPositions[slider], originalPathControlPointTypes[slider]); + else + { + scale = getClampedScale(OriginalSurroundingQuad.Value, actualOrigin, scale); + + foreach (var ho in objectsInScale) + { + ho.Position = GeometryUtils.GetScaledPositionMultiply(scale, actualOrigin, originalPositions[ho]); + } + } + + moveSelectionInBounds(); + } + + public override void Commit() + { + if (objectsInScale == null) + throw new InvalidOperationException($"Cannot {nameof(Commit)} a rotate operation without calling {nameof(Begin)} first!"); + + changeHandler?.EndChange(); + + objectsInScale = null; + OriginalSurroundingQuad = null; + originalPositions = null; + originalPathControlPointPositions = null; + originalPathControlPointTypes = null; + defaultOrigin = null; + } + + private IEnumerable selectedMovableObjects => selectedItems.Cast() + .Where(h => h is not Spinner); + + private void scaleSlider(Slider slider, Vector2 scale, Vector2[] originalPathPositions, PathType?[] originalPathTypes) + { + // Maintain the path types in case they were defaulted to bezier at some point during scaling + for (int i = 0; i < slider.Path.ControlPoints.Count; i++) + { + slider.Path.ControlPoints[i].Position = originalPathPositions[i] * scale; + slider.Path.ControlPoints[i].Type = originalPathTypes[i]; + } + + // Snap the slider's length to the current beat divisor + // to calculate the final resulting duration / bounding box before the final checks. + slider.SnapTo(snapProvider); + + //if sliderhead or sliderend end up outside playfield, revert scaling. + Quad scaledQuad = GeometryUtils.GetSurroundingQuad(new OsuHitObject[] { slider }); + (bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad); + + if (xInBounds && yInBounds && slider.Path.HasValidLength) + return; + + for (int i = 0; i < slider.Path.ControlPoints.Count; i++) + slider.Path.ControlPoints[i].Position = originalPathPositions[i]; + + // Snap the slider's length again to undo the potentially-invalid length applied by the previous snap. + slider.SnapTo(snapProvider); + } + + private (bool X, bool Y) isQuadInBounds(Quad quad) + { + bool xInBounds = (quad.TopLeft.X >= 0) && (quad.BottomRight.X <= OsuPlayfield.BASE_SIZE.X); + bool yInBounds = (quad.TopLeft.Y >= 0) && (quad.BottomRight.Y <= OsuPlayfield.BASE_SIZE.Y); + + return (xInBounds, yInBounds); + } + + /// + /// Clamp scale for multi-object-scaling where selection does not exceed playfield bounds or flip. + /// + /// The quad surrounding the hitobjects + /// The origin from which the scale operation is performed + /// The scale to be clamped + /// The clamped scale vector + private Vector2 getClampedScale(Quad selectionQuad, Vector2 origin, Vector2 scale) + { + //todo: this is not always correct for selections involving sliders. This approximation assumes each point is scaled independently, but sliderends move with the sliderhead. + + var tl1 = Vector2.Divide(-origin, selectionQuad.TopLeft - origin); + var tl2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - origin, selectionQuad.TopLeft - origin); + var br1 = Vector2.Divide(-origin, selectionQuad.BottomRight - origin); + var br2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - origin, selectionQuad.BottomRight - origin); + + scale.X = selectionQuad.TopLeft.X - origin.X < 0 ? MathHelper.Clamp(scale.X, tl2.X, tl1.X) : MathHelper.Clamp(scale.X, tl1.X, tl2.X); + scale.Y = selectionQuad.TopLeft.Y - origin.Y < 0 ? MathHelper.Clamp(scale.Y, tl2.Y, tl1.Y) : MathHelper.Clamp(scale.Y, tl1.Y, tl2.Y); + scale.X = selectionQuad.BottomRight.X - origin.X < 0 ? MathHelper.Clamp(scale.X, br2.X, br1.X) : MathHelper.Clamp(scale.X, br1.X, br2.X); + scale.Y = selectionQuad.BottomRight.Y - origin.Y < 0 ? MathHelper.Clamp(scale.Y, br2.Y, br1.Y) : MathHelper.Clamp(scale.Y, br1.Y, br2.Y); + + return scale; + } + + private void moveSelectionInBounds() + { + Quad quad = GeometryUtils.GetSurroundingQuad(objectsInScale!); + + Vector2 delta = Vector2.Zero; + + if (quad.TopLeft.X < 0) + delta.X -= quad.TopLeft.X; + if (quad.TopLeft.Y < 0) + delta.Y -= quad.TopLeft.Y; + + if (quad.BottomRight.X > OsuPlayfield.BASE_SIZE.X) + delta.X -= quad.BottomRight.X - OsuPlayfield.BASE_SIZE.X; + if (quad.BottomRight.Y > OsuPlayfield.BASE_SIZE.Y) + delta.Y -= quad.BottomRight.Y - OsuPlayfield.BASE_SIZE.Y; + + foreach (var h in objectsInScale!) + h.Position += delta; + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index 0b16941bc4..e8b3e430eb 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -27,7 +27,6 @@ namespace osu.Game.Screens.Edit.Compose.Components [Resolved] private SelectionRotationHandler? rotationHandler { get; set; } - public Func? OnScale; public Func? OnFlip; public Func? OnReverse; @@ -353,7 +352,6 @@ namespace osu.Game.Screens.Edit.Compose.Components var handle = new SelectionBoxScaleHandle { Anchor = anchor, - HandleScale = (delta, a) => OnScale?.Invoke(delta, a) }; handle.OperationStarted += operationStarted; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs index 7943065c82..56c5585ae7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs @@ -1,19 +1,22 @@ // 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 osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input.Events; using osuTK; +using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components { public partial class SelectionBoxScaleHandle : SelectionBoxDragHandle { - public Action HandleScale { get; set; } + [Resolved] + private SelectionBox selectionBox { get; set; } = null!; + + [Resolved] + private SelectionScaleHandler? scaleHandler { get; set; } [BackgroundDependencyLoader] private void load() @@ -21,10 +24,93 @@ namespace osu.Game.Screens.Edit.Compose.Components Size = new Vector2(10); } + protected override bool OnDragStart(DragStartEvent e) + { + if (e.Button != MouseButton.Left) + return false; + + if (scaleHandler == null) return false; + + scaleHandler.Begin(); + return true; + } + + private Vector2 getOriginPosition() + { + var quad = scaleHandler!.OriginalSurroundingQuad!.Value; + Vector2 origin = quad.TopLeft; + + if ((Anchor & Anchor.x0) > 0) + origin.X += quad.Width; + + if ((Anchor & Anchor.y0) > 0) + origin.Y += quad.Height; + + return origin; + } + + private Vector2 rawScale; + protected override void OnDrag(DragEvent e) { - HandleScale?.Invoke(e.Delta, Anchor); base.OnDrag(e); + + if (scaleHandler == null) return; + + rawScale = convertDragEventToScaleMultiplier(e); + + applyScale(shouldKeepAspectRatio: e.ShiftPressed); + } + + protected override bool OnKeyDown(KeyDownEvent e) + { + if (IsDragged && (e.Key == Key.ShiftLeft || e.Key == Key.ShiftRight)) + { + applyScale(shouldKeepAspectRatio: true); + return true; + } + + return base.OnKeyDown(e); + } + + protected override void OnKeyUp(KeyUpEvent e) + { + base.OnKeyUp(e); + + if (IsDragged && (e.Key == Key.ShiftLeft || e.Key == Key.ShiftRight)) + applyScale(shouldKeepAspectRatio: false); + } + + protected override void OnDragEnd(DragEndEvent e) + { + scaleHandler?.Commit(); + } + + private Vector2 convertDragEventToScaleMultiplier(DragEvent e) + { + Vector2 scale = e.MousePosition - e.MouseDownPosition; + adjustScaleFromAnchor(ref scale); + return Vector2.Divide(scale, scaleHandler!.OriginalSurroundingQuad!.Value.Size) + Vector2.One; + } + + private void adjustScaleFromAnchor(ref Vector2 scale) + { + // cancel out scale in axes we don't care about (based on which drag handle was used). + if ((Anchor & Anchor.x1) > 0) scale.X = 1; + if ((Anchor & Anchor.y1) > 0) scale.Y = 1; + + // reverse the scale direction if dragging from top or left. + if ((Anchor & Anchor.x0) > 0) scale.X = -scale.X; + if ((Anchor & Anchor.y0) > 0) scale.Y = -scale.Y; + } + + private void applyScale(bool shouldKeepAspectRatio) + { + var newScale = shouldKeepAspectRatio + ? new Vector2(MathF.Max(rawScale.X, rawScale.Y)) + : rawScale; + + scaleHandler!.Update(newScale, getOriginPosition()); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 3c859c65ff..dd6bd43f4d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -57,6 +57,8 @@ namespace osu.Game.Screens.Edit.Compose.Components public SelectionRotationHandler RotationHandler { get; private set; } + public SelectionScaleHandler ScaleHandler { get; private set; } + protected SelectionHandler() { selectedBlueprints = new List>(); @@ -69,6 +71,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); dependencies.CacheAs(RotationHandler = CreateRotationHandler()); + dependencies.CacheAs(ScaleHandler = CreateScaleHandler()); return dependencies; } @@ -78,6 +81,7 @@ namespace osu.Game.Screens.Edit.Compose.Components AddRangeInternal(new Drawable[] { RotationHandler, + ScaleHandler, SelectionBox = CreateSelectionBox(), }); @@ -93,7 +97,6 @@ namespace osu.Game.Screens.Edit.Compose.Components OperationStarted = OnOperationBegan, OperationEnded = OnOperationEnded, - OnScale = HandleScale, OnFlip = HandleFlip, OnReverse = HandleReverse, }; @@ -157,6 +160,11 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Whether any items could be scaled. public virtual bool HandleScale(Vector2 scale, Anchor anchor) => false; + /// + /// Creates the handler to use for scale operations. + /// + public virtual SelectionScaleHandler CreateScaleHandler() => new SelectionScaleHandler(); + /// /// Handles the selected items being flipped. /// diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs new file mode 100644 index 0000000000..b7c8f16a02 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs @@ -0,0 +1,88 @@ +// 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.Framework.Graphics.Primitives; +using osuTK; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + /// + /// Base handler for editor scale operations. + /// + public partial class SelectionScaleHandler : Component + { + /// + /// Whether the scale can currently be performed. + /// + public Bindable CanScale { get; private set; } = new BindableBool(); + + public Quad? OriginalSurroundingQuad { get; protected set; } + + /// + /// Performs a single, instant, atomic scale operation. + /// + /// + /// This method is intended to be used in atomic contexts (such as when pressing a single button). + /// For continuous operations, see the -- flow. + /// + /// The scale to apply, as multiplier. + /// + /// The origin point to scale from. + /// If the default value is supplied, a sane implementation-defined default will be used. + /// + public void ScaleSelection(Vector2 scale, Vector2? origin = null) + { + Begin(); + Update(scale, origin); + Commit(); + } + + /// + /// Begins a continuous scale operation. + /// + /// + /// This flow is intended to be used when a scale operation is made incrementally (such as when dragging a scale handle or slider). + /// For instantaneous, atomic operations, use the convenience method. + /// + public virtual void Begin() + { + } + + /// + /// Updates a continuous scale operation. + /// Must be preceded by a call. + /// + /// + /// + /// This flow is intended to be used when a scale operation is made incrementally (such as when dragging a scale handle or slider). + /// As such, the values of and supplied should be relative to the state of the objects being scaled + /// when was called, rather than instantaneous deltas. + /// + /// + /// For instantaneous, atomic operations, use the convenience method. + /// + /// + /// The Scale to apply, as multiplier. + /// + /// The origin point to scale from. + /// If the default value is supplied, a sane implementation-defined default will be used. + /// + public virtual void Update(Vector2 scale, Vector2? origin = null) + { + } + + /// + /// Ends a continuous scale operation. + /// Must be preceded by a call. + /// + /// + /// This flow is intended to be used when a scale operation is made incrementally (such as when dragging a scale handle or slider). + /// For instantaneous, atomic operations, use the convenience method. + /// + public virtual void Commit() + { + } + } +} diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index 725e93d098..ef362d8223 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -79,6 +79,15 @@ namespace osu.Game.Utils return position; } + /// + /// Given a scale multiplier, an origin, and a position, + /// will return the scaled position in screen space coordinates. + /// + public static Vector2 GetScaledPositionMultiply(Vector2 scale, Vector2 origin, Vector2 position) + { + return origin + (position - origin) * scale; + } + /// /// Returns a quad surrounding the provided points. /// From a4f771ec089baff91ddea3d4714355e64a8237dd Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 20 Jan 2024 01:13:01 +0100 Subject: [PATCH 2595/4092] refactor CanScale properties --- .../Edit/OsuSelectionHandler.cs | 5 +- .../Edit/OsuSelectionScaleHandler.cs | 7 +- .../SkinEditor/SkinSelectionHandler.cs | 3 - .../Edit/Compose/Components/SelectionBox.cs | 76 +++++-------------- .../Components/SelectionScaleHandler.cs | 18 ++++- osu.Game/Utils/GeometryUtils.cs | 2 +- 6 files changed, 44 insertions(+), 67 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index c36b535bfa..00c90cdbd6 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -30,9 +30,8 @@ namespace osu.Game.Rulesets.Osu.Edit Quad quad = selectedMovableObjects.Length > 0 ? GeometryUtils.GetSurroundingQuad(selectedMovableObjects) : new Quad(); - SelectionBox.CanFlipX = SelectionBox.CanScaleX = quad.Width > 0; - SelectionBox.CanFlipY = SelectionBox.CanScaleY = quad.Height > 0; - SelectionBox.CanScaleDiagonally = SelectionBox.CanScaleX && SelectionBox.CanScaleY; + SelectionBox.CanFlipX = quad.Width > 0; + SelectionBox.CanFlipY = quad.Height > 0; SelectionBox.CanReverse = EditorBeatmap.SelectedHitObjects.Count > 1 || EditorBeatmap.SelectedHitObjects.Any(s => s is Slider); } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 8068c73131..7b0ae947e7 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -47,7 +47,10 @@ namespace osu.Game.Rulesets.Osu.Edit private void updateState() { var quad = GeometryUtils.GetSurroundingQuad(selectedMovableObjects); - CanScale.Value = quad.Width > 0 || quad.Height > 0; + + CanScaleX.Value = quad.Width > 0; + CanScaleY.Value = quad.Height > 0; + CanScaleDiagonally.Value = CanScaleX.Value && CanScaleY.Value; } private OsuHitObject[]? objectsInScale; @@ -98,7 +101,7 @@ namespace osu.Game.Rulesets.Osu.Edit foreach (var ho in objectsInScale) { - ho.Position = GeometryUtils.GetScaledPositionMultiply(scale, actualOrigin, originalPositions[ho]); + ho.Position = GeometryUtils.GetScaledPosition(scale, actualOrigin, originalPositions[ho]); } } diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index cf6fb60636..efca6f0080 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -218,9 +218,6 @@ namespace osu.Game.Overlays.SkinEditor { base.OnSelectionChanged(); - SelectionBox.CanScaleX = allSelectedSupportManualSizing(Axes.X); - SelectionBox.CanScaleY = allSelectedSupportManualSizing(Axes.Y); - SelectionBox.CanScaleDiagonally = true; SelectionBox.CanFlipX = true; SelectionBox.CanFlipY = true; SelectionBox.CanReverse = false; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index e8b3e430eb..2329a466fe 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -27,6 +27,9 @@ namespace osu.Game.Screens.Edit.Compose.Components [Resolved] private SelectionRotationHandler? rotationHandler { get; set; } + [Resolved] + private SelectionScaleHandler? scaleHandler { get; set; } + public Func? OnFlip; public Func? OnReverse; @@ -56,60 +59,11 @@ namespace osu.Game.Screens.Edit.Compose.Components private readonly IBindable canRotate = new BindableBool(); - private bool canScaleX; + private readonly IBindable canScaleX = new BindableBool(); - /// - /// Whether horizontal scaling (from the left or right edge) support should be enabled. - /// - public bool CanScaleX - { - get => canScaleX; - set - { - if (canScaleX == value) return; + private readonly IBindable canScaleY = new BindableBool(); - canScaleX = value; - recreate(); - } - } - - private bool canScaleY; - - /// - /// Whether vertical scaling (from the top or bottom edge) support should be enabled. - /// - public bool CanScaleY - { - get => canScaleY; - set - { - if (canScaleY == value) return; - - canScaleY = value; - recreate(); - } - } - - private bool canScaleDiagonally; - - /// - /// Whether diagonal scaling (from a corner) support should be enabled. - /// - /// - /// There are some cases where we only want to allow proportional resizing, and not allow - /// one or both explicit directions of scale. - /// - public bool CanScaleDiagonally - { - get => canScaleDiagonally; - set - { - if (canScaleDiagonally == value) return; - - canScaleDiagonally = value; - recreate(); - } - } + private readonly IBindable canScaleDiagonally = new BindableBool(); private bool canFlipX; @@ -175,7 +129,17 @@ namespace osu.Game.Screens.Edit.Compose.Components if (rotationHandler != null) canRotate.BindTo(rotationHandler.CanRotate); - canRotate.BindValueChanged(_ => recreate(), true); + if (scaleHandler != null) + { + canScaleX.BindTo(scaleHandler.CanScaleX); + canScaleY.BindTo(scaleHandler.CanScaleY); + canScaleDiagonally.BindTo(scaleHandler.CanScaleDiagonally); + } + + canRotate.BindValueChanged(_ => recreate()); + canScaleX.BindValueChanged(_ => recreate()); + canScaleY.BindValueChanged(_ => recreate()); + canScaleDiagonally.BindValueChanged(_ => recreate(), true); } protected override bool OnKeyDown(KeyDownEvent e) @@ -264,9 +228,9 @@ namespace osu.Game.Screens.Edit.Compose.Components } }; - if (CanScaleX) addXScaleComponents(); - if (CanScaleDiagonally) addFullScaleComponents(); - if (CanScaleY) addYScaleComponents(); + if (canScaleX.Value) addXScaleComponents(); + if (canScaleDiagonally.Value) addFullScaleComponents(); + if (canScaleY.Value) addYScaleComponents(); if (CanFlipX) addXFlipComponents(); if (CanFlipY) addYFlipComponents(); if (canRotate.Value) addRotationComponents(); diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs index b7c8f16a02..59406b3184 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs @@ -14,9 +14,23 @@ namespace osu.Game.Screens.Edit.Compose.Components public partial class SelectionScaleHandler : Component { /// - /// Whether the scale can currently be performed. + /// Whether horizontal scaling (from the left or right edge) support should be enabled. /// - public Bindable CanScale { get; private set; } = new BindableBool(); + public Bindable CanScaleX { get; private set; } = new BindableBool(); + + /// + /// Whether vertical scaling (from the top or bottom edge) support should be enabled. + /// + public Bindable CanScaleY { get; private set; } = new BindableBool(); + + /// + /// Whether diagonal scaling (from a corner) support should be enabled. + /// + /// + /// There are some cases where we only want to allow proportional resizing, and not allow + /// one or both explicit directions of scale. + /// + public Bindable CanScaleDiagonally { get; private set; } = new BindableBool(); public Quad? OriginalSurroundingQuad { get; protected set; } diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index ef362d8223..6d8237ea34 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -83,7 +83,7 @@ namespace osu.Game.Utils /// Given a scale multiplier, an origin, and a position, /// will return the scaled position in screen space coordinates. /// - public static Vector2 GetScaledPositionMultiply(Vector2 scale, Vector2 origin, Vector2 position) + public static Vector2 GetScaledPosition(Vector2 scale, Vector2 origin, Vector2 position) { return origin + (position - origin) * scale; } From bc0e6baba70cd9c69dbf9f87180c24c8a47dcff9 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 20 Jan 2024 01:13:05 +0100 Subject: [PATCH 2596/4092] fix test --- .../Editing/TestSceneComposeSelectBox.cs | 77 ++++++++++++------- 1 file changed, 51 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs index f6637d0e80..680a76f9b8 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs @@ -10,9 +10,11 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; using osu.Framework.Testing; using osu.Framework.Threading; using osu.Game.Screens.Edit.Compose.Components; +using osu.Game.Utils; using osuTK; using osuTK.Input; @@ -26,9 +28,13 @@ namespace osu.Game.Tests.Visual.Editing [Cached(typeof(SelectionRotationHandler))] private TestSelectionRotationHandler rotationHandler; + [Cached(typeof(SelectionScaleHandler))] + private TestSelectionScaleHandler scaleHandler; + public TestSceneComposeSelectBox() { rotationHandler = new TestSelectionRotationHandler(() => selectionArea); + scaleHandler = new TestSelectionScaleHandler(() => selectionArea); } [SetUp] @@ -45,13 +51,8 @@ namespace osu.Game.Tests.Visual.Editing { RelativeSizeAxes = Axes.Both, - CanScaleX = true, - CanScaleY = true, - CanScaleDiagonally = true, CanFlipX = true, CanFlipY = true, - - OnScale = handleScale } } }; @@ -60,27 +61,6 @@ namespace osu.Game.Tests.Visual.Editing InputManager.ReleaseButton(MouseButton.Left); }); - private bool handleScale(Vector2 amount, Anchor reference) - { - if ((reference & Anchor.y1) == 0) - { - int directionY = (reference & Anchor.y0) > 0 ? -1 : 1; - if (directionY < 0) - selectionArea.Y += amount.Y; - selectionArea.Height += directionY * amount.Y; - } - - if ((reference & Anchor.x1) == 0) - { - int directionX = (reference & Anchor.x0) > 0 ? -1 : 1; - if (directionX < 0) - selectionArea.X += amount.X; - selectionArea.Width += directionX * amount.X; - } - - return true; - } - private partial class TestSelectionRotationHandler : SelectionRotationHandler { private readonly Func getTargetContainer; @@ -125,6 +105,51 @@ namespace osu.Game.Tests.Visual.Editing } } + private partial class TestSelectionScaleHandler : SelectionScaleHandler + { + private readonly Func getTargetContainer; + + public TestSelectionScaleHandler(Func getTargetContainer) + { + this.getTargetContainer = getTargetContainer; + + CanScaleX.Value = true; + CanScaleY.Value = true; + CanScaleDiagonally.Value = true; + } + + [CanBeNull] + private Container targetContainer; + + public override void Begin() + { + if (targetContainer != null) + throw new InvalidOperationException($"Cannot {nameof(Begin)} a scale operation while another is in progress!"); + + targetContainer = getTargetContainer(); + OriginalSurroundingQuad = new Quad(targetContainer!.X, targetContainer.Y, targetContainer.Width, targetContainer.Height); + } + + public override void Update(Vector2 scale, Vector2? origin = null) + { + if (targetContainer == null) + throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!"); + + Vector2 actualOrigin = origin ?? Vector2.Zero; + + targetContainer.Position = GeometryUtils.GetScaledPosition(scale, actualOrigin, OriginalSurroundingQuad!.Value.TopLeft); + targetContainer.Size = OriginalSurroundingQuad!.Value.Size * scale; + } + + public override void Commit() + { + if (targetContainer == null) + throw new InvalidOperationException($"Cannot {nameof(Commit)} a scale operation without calling {nameof(Begin)} first!"); + + targetContainer = null; + } + } + [Test] public void TestRotationHandleShownOnHover() { From ed430a3df4bbcacf5860db8f21ac625d2a176bbc Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 20 Jan 2024 02:49:56 +0100 Subject: [PATCH 2597/4092] refactor skin editor scale --- .../SkinEditor/SkinSelectionHandler.cs | 157 +------------- .../SkinEditor/SkinSelectionScaleHandler.cs | 198 ++++++++++++++++++ 2 files changed, 204 insertions(+), 151 deletions(-) create mode 100644 osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index efca6f0080..2d8db61ee7 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -7,10 +7,8 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Utils; using osu.Game.Extensions; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; @@ -31,148 +29,16 @@ namespace osu.Game.Overlays.SkinEditor UpdatePosition = updateDrawablePosition }; - private bool allSelectedSupportManualSizing(Axes axis) => SelectedItems.All(b => (b as CompositeDrawable)?.AutoSizeAxes.HasFlagFast(axis) == false); - - public override bool HandleScale(Vector2 scale, Anchor anchor) + public override SelectionScaleHandler CreateScaleHandler() { - Axes adjustAxis; - - switch (anchor) + var scaleHandler = new SkinSelectionScaleHandler { - // for corners, adjust scale. - case Anchor.TopLeft: - case Anchor.TopRight: - case Anchor.BottomLeft: - case Anchor.BottomRight: - adjustAxis = Axes.Both; - break; + UpdatePosition = updateDrawablePosition + }; - // for edges, adjust size. - // autosize elements can't be easily handled so just disable sizing for now. - case Anchor.TopCentre: - case Anchor.BottomCentre: - if (!allSelectedSupportManualSizing(Axes.Y)) - return false; + scaleHandler.PerformFlipFromScaleHandles += a => SelectionBox.PerformFlipFromScaleHandles(a); - adjustAxis = Axes.Y; - break; - - case Anchor.CentreLeft: - case Anchor.CentreRight: - if (!allSelectedSupportManualSizing(Axes.X)) - return false; - - adjustAxis = Axes.X; - break; - - default: - throw new ArgumentOutOfRangeException(nameof(anchor), anchor, null); - } - - // convert scale to screen space - scale = ToScreenSpace(scale) - ToScreenSpace(Vector2.Zero); - - adjustScaleFromAnchor(ref scale, anchor); - - // the selection quad is always upright, so use an AABB rect to make mutating the values easier. - var selectionRect = getSelectionQuad().AABBFloat; - - // If the selection has no area we cannot scale it - if (selectionRect.Area == 0) - return false; - - // copy to mutate, as we will need to compare to the original later on. - var adjustedRect = selectionRect; - bool isRotated = false; - - // for now aspect lock scale adjustments that occur at corners.. - if (!anchor.HasFlagFast(Anchor.x1) && !anchor.HasFlagFast(Anchor.y1)) - { - // project scale vector along diagonal - Vector2 diag = (selectionRect.TopLeft - selectionRect.BottomRight).Normalized(); - scale = Vector2.Dot(scale, diag) * diag; - } - // ..or if any of the selection have been rotated. - // this is to avoid requiring skew logic (which would likely not be the user's expected transform anyway). - else if (SelectedBlueprints.Any(b => !Precision.AlmostEquals(((Drawable)b.Item).Rotation % 90, 0))) - { - isRotated = true; - if (anchor.HasFlagFast(Anchor.x1)) - // if dragging from the horizontal centre, only a vertical component is available. - scale.X = scale.Y / selectionRect.Height * selectionRect.Width; - else - // in all other cases (arbitrarily) use the horizontal component for aspect lock. - scale.Y = scale.X / selectionRect.Width * selectionRect.Height; - } - - if (anchor.HasFlagFast(Anchor.x0)) adjustedRect.X -= scale.X; - if (anchor.HasFlagFast(Anchor.y0)) adjustedRect.Y -= scale.Y; - - // Maintain the selection's centre position if dragging from the centre anchors and selection is rotated. - if (isRotated && anchor.HasFlagFast(Anchor.x1)) adjustedRect.X -= scale.X / 2; - if (isRotated && anchor.HasFlagFast(Anchor.y1)) adjustedRect.Y -= scale.Y / 2; - - adjustedRect.Width += scale.X; - adjustedRect.Height += scale.Y; - - if (adjustedRect.Width <= 0 || adjustedRect.Height <= 0) - { - Axes toFlip = Axes.None; - - if (adjustedRect.Width <= 0) toFlip |= Axes.X; - if (adjustedRect.Height <= 0) toFlip |= Axes.Y; - - SelectionBox.PerformFlipFromScaleHandles(toFlip); - return true; - } - - // scale adjust applied to each individual item should match that of the quad itself. - var scaledDelta = new Vector2( - adjustedRect.Width / selectionRect.Width, - adjustedRect.Height / selectionRect.Height - ); - - foreach (var b in SelectedBlueprints) - { - var drawableItem = (Drawable)b.Item; - - // each drawable's relative position should be maintained in the scaled quad. - var screenPosition = b.ScreenSpaceSelectionPoint; - - var relativePositionInOriginal = - new Vector2( - (screenPosition.X - selectionRect.TopLeft.X) / selectionRect.Width, - (screenPosition.Y - selectionRect.TopLeft.Y) / selectionRect.Height - ); - - var newPositionInAdjusted = new Vector2( - adjustedRect.TopLeft.X + adjustedRect.Width * relativePositionInOriginal.X, - adjustedRect.TopLeft.Y + adjustedRect.Height * relativePositionInOriginal.Y - ); - - updateDrawablePosition(drawableItem, newPositionInAdjusted); - - var currentScaledDelta = scaledDelta; - if (Precision.AlmostEquals(MathF.Abs(drawableItem.Rotation) % 180, 90)) - currentScaledDelta = new Vector2(scaledDelta.Y, scaledDelta.X); - - switch (adjustAxis) - { - case Axes.X: - drawableItem.Width *= currentScaledDelta.X; - break; - - case Axes.Y: - drawableItem.Height *= currentScaledDelta.Y; - break; - - case Axes.Both: - drawableItem.Scale *= currentScaledDelta; - break; - } - } - - return true; + return scaleHandler; } public override bool HandleFlip(Direction direction, bool flipOverOrigin) @@ -410,16 +276,5 @@ namespace osu.Game.Overlays.SkinEditor drawable.Anchor = anchor; drawable.Position -= drawable.AnchorPosition - previousAnchor; } - - private static void adjustScaleFromAnchor(ref Vector2 scale, Anchor reference) - { - // cancel out scale in axes we don't care about (based on which drag handle was used). - if ((reference & Anchor.x1) > 0) scale.X = 0; - if ((reference & Anchor.y1) > 0) scale.Y = 0; - - // reverse the scale direction if dragging from top or left. - if ((reference & Anchor.x0) > 0) scale.X = -scale.X; - if ((reference & Anchor.y0) > 0) scale.Y = -scale.Y; - } } } diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs new file mode 100644 index 0000000000..46b39645b2 --- /dev/null +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs @@ -0,0 +1,198 @@ +// 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.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.EnumExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Utils; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Compose.Components; +using osu.Game.Skinning; +using osu.Game.Utils; +using osuTK; + +namespace osu.Game.Overlays.SkinEditor +{ + public partial class SkinSelectionScaleHandler : SelectionScaleHandler + { + public Action UpdatePosition { get; init; } = null!; + + public event Action? PerformFlipFromScaleHandles; + + [Resolved] + private IEditorChangeHandler? changeHandler { get; set; } + + private BindableList selectedItems { get; } = new BindableList(); + + [BackgroundDependencyLoader] + private void load(SkinEditor skinEditor) + { + selectedItems.BindTo(skinEditor.SelectedComponents); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + selectedItems.CollectionChanged += (_, __) => updateState(); + updateState(); + } + + private void updateState() + { + CanScaleX.Value = allSelectedSupportManualSizing(Axes.X); + CanScaleY.Value = allSelectedSupportManualSizing(Axes.Y); + CanScaleDiagonally.Value = true; + } + + private bool allSelectedSupportManualSizing(Axes axis) => selectedItems.All(b => (b as CompositeDrawable)?.AutoSizeAxes.HasFlagFast(axis) == false); + + private Drawable[]? objectsInScale; + + private Vector2? defaultOrigin; + private Dictionary? originalWidths; + private Dictionary? originalHeights; + private Dictionary? originalScales; + private Dictionary? originalPositions; + + public override void Begin() + { + if (objectsInScale != null) + throw new InvalidOperationException($"Cannot {nameof(Begin)} a scale operation while another is in progress!"); + + changeHandler?.BeginChange(); + + objectsInScale = selectedItems.Cast().ToArray(); + originalWidths = objectsInScale.ToDictionary(d => d, d => d.Width); + originalHeights = objectsInScale.ToDictionary(d => d, d => d.Height); + originalScales = objectsInScale.ToDictionary(d => d, d => d.Scale); + originalPositions = objectsInScale.ToDictionary(d => d, d => d.ToScreenSpace(d.OriginPosition)); + OriginalSurroundingQuad = GeometryUtils.GetSurroundingQuad(objectsInScale.SelectMany(d => d.ScreenSpaceDrawQuad.GetVertices().ToArray())); + defaultOrigin = OriginalSurroundingQuad.Value.Centre; + } + + public override void Update(Vector2 scale, Vector2? origin = null) + { + if (objectsInScale == null) + throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!"); + + Debug.Assert(originalWidths != null && originalHeights != null && originalScales != null && originalPositions != null && defaultOrigin != null && OriginalSurroundingQuad != null); + + var actualOrigin = origin ?? defaultOrigin.Value; + + Axes adjustAxis = scale.X == 0 ? Axes.Y : scale.Y == 0 ? Axes.X : Axes.Both; + + if ((adjustAxis == Axes.Y && !allSelectedSupportManualSizing(Axes.Y)) || + (adjustAxis == Axes.X && !allSelectedSupportManualSizing(Axes.X))) + return; + + // the selection quad is always upright, so use an AABB rect to make mutating the values easier. + var selectionRect = OriginalSurroundingQuad.Value.AABBFloat; + + // If the selection has no area we cannot scale it + if (selectionRect.Area == 0) + return; + + // copy to mutate, as we will need to compare to the original later on. + var adjustedRect = selectionRect; + + // for now aspect lock scale adjustments that occur at corners.. + if (adjustAxis == Axes.Both) + { + // project scale vector along diagonal + Vector2 diag = new Vector2(1, 1).Normalized(); + scale = Vector2.Dot(scale, diag) * diag; + } + // ..or if any of the selection have been rotated. + // this is to avoid requiring skew logic (which would likely not be the user's expected transform anyway). + else if (objectsInScale.Any(b => !Precision.AlmostEquals(b.Rotation % 90, 0))) + { + if (adjustAxis == Axes.Y) + // if dragging from the horizontal centre, only a vertical component is available. + scale.X = scale.Y / selectionRect.Height * selectionRect.Width; + else + // in all other cases (arbitrarily) use the horizontal component for aspect lock. + scale.Y = scale.X / selectionRect.Width * selectionRect.Height; + } + + adjustedRect.Location = GeometryUtils.GetScaledPosition(scale, actualOrigin, OriginalSurroundingQuad!.Value.TopLeft); + adjustedRect.Size = OriginalSurroundingQuad!.Value.Size * scale; + + if (adjustedRect.Width <= 0 || adjustedRect.Height <= 0) + { + Axes toFlip = Axes.None; + + if (adjustedRect.Width <= 0) toFlip |= Axes.X; + if (adjustedRect.Height <= 0) toFlip |= Axes.Y; + + PerformFlipFromScaleHandles?.Invoke(toFlip); + return; + } + + // scale adjust applied to each individual item should match that of the quad itself. + var scaledDelta = new Vector2( + adjustedRect.Width / selectionRect.Width, + adjustedRect.Height / selectionRect.Height + ); + + foreach (var b in objectsInScale) + { + // each drawable's relative position should be maintained in the scaled quad. + var screenPosition = originalPositions[b]; + + var relativePositionInOriginal = + new Vector2( + (screenPosition.X - selectionRect.TopLeft.X) / selectionRect.Width, + (screenPosition.Y - selectionRect.TopLeft.Y) / selectionRect.Height + ); + + var newPositionInAdjusted = new Vector2( + adjustedRect.TopLeft.X + adjustedRect.Width * relativePositionInOriginal.X, + adjustedRect.TopLeft.Y + adjustedRect.Height * relativePositionInOriginal.Y + ); + + UpdatePosition(b, newPositionInAdjusted); + + var currentScaledDelta = scaledDelta; + if (Precision.AlmostEquals(MathF.Abs(b.Rotation) % 180, 90)) + currentScaledDelta = new Vector2(scaledDelta.Y, scaledDelta.X); + + switch (adjustAxis) + { + case Axes.X: + b.Width = originalWidths[b] * currentScaledDelta.X; + break; + + case Axes.Y: + b.Height = originalHeights[b] * currentScaledDelta.Y; + break; + + case Axes.Both: + b.Scale = originalScales[b] * currentScaledDelta; + break; + } + } + } + + public override void Commit() + { + if (objectsInScale == null) + throw new InvalidOperationException($"Cannot {nameof(Commit)} a scale operation without calling {nameof(Begin)} first!"); + + changeHandler?.EndChange(); + + objectsInScale = null; + originalPositions = null; + originalWidths = null; + originalHeights = null; + originalScales = null; + defaultOrigin = null; + } + } +} From 45effdb6dfaddf3e6fc5ed0aa882629e8ff99470 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 20 Jan 2024 08:41:34 +0300 Subject: [PATCH 2598/4092] Remove local vertex batching from triangles backgrounds --- osu.Game/Graphics/Backgrounds/Triangles.cs | 11 +---------- osu.Game/Graphics/Backgrounds/TrianglesV2.cs | 12 +----------- 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index 1a78c1ec5e..8db7f3a1c3 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -15,7 +15,6 @@ using osu.Framework.Graphics.Primitives; using osu.Framework.Allocation; using System.Collections.Generic; using osu.Framework.Graphics.Rendering; -using osu.Framework.Graphics.Rendering.Vertices; using osu.Framework.Lists; using osu.Framework.Bindables; @@ -264,7 +263,6 @@ namespace osu.Game.Graphics.Backgrounds private readonly Vector2 triangleSize = new Vector2(1f, equilateral_triangle_ratio) * triangle_size; private Vector2 size; - private IVertexBatch vertexBatch; public TrianglesDrawNode(Triangles source) : base(source) @@ -290,12 +288,6 @@ namespace osu.Game.Graphics.Backgrounds { base.Draw(renderer); - if (Source.AimCount > 0 && (vertexBatch == null || vertexBatch.Size != Source.AimCount)) - { - vertexBatch?.Dispose(); - vertexBatch = renderer.CreateQuadBatch(Source.AimCount, 1); - } - borderDataBuffer ??= renderer.CreateUniformBuffer(); borderDataBuffer.Data = borderDataBuffer.Data with { @@ -333,7 +325,7 @@ namespace osu.Game.Graphics.Backgrounds triangleQuad.Height ) / relativeSize; - renderer.DrawQuad(texture, drawQuad, colourInfo, new RectangleF(0, 0, 1, 1), vertexBatch.AddAction, textureCoords: textureCoords); + renderer.DrawQuad(texture, drawQuad, colourInfo, new RectangleF(0, 0, 1, 1), textureCoords: textureCoords); } shader.Unbind(); @@ -356,7 +348,6 @@ namespace osu.Game.Graphics.Backgrounds { base.Dispose(isDisposing); - vertexBatch?.Dispose(); borderDataBuffer?.Dispose(); } } diff --git a/osu.Game/Graphics/Backgrounds/TrianglesV2.cs b/osu.Game/Graphics/Backgrounds/TrianglesV2.cs index a20fd78569..4f611d9def 100644 --- a/osu.Game/Graphics/Backgrounds/TrianglesV2.cs +++ b/osu.Game/Graphics/Backgrounds/TrianglesV2.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Primitives; using osu.Framework.Allocation; using System.Collections.Generic; using osu.Framework.Graphics.Rendering; -using osu.Framework.Graphics.Rendering.Vertices; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -196,8 +195,6 @@ namespace osu.Game.Graphics.Backgrounds private float texelSize; private bool masking; - private IVertexBatch? vertexBatch; - public TrianglesDrawNode(TrianglesV2 source) : base(source) { @@ -235,12 +232,6 @@ namespace osu.Game.Graphics.Backgrounds if (Source.AimCount == 0 || thickness == 0) return; - if (vertexBatch == null || vertexBatch.Size != Source.AimCount) - { - vertexBatch?.Dispose(); - vertexBatch = renderer.CreateQuadBatch(Source.AimCount, 1); - } - borderDataBuffer ??= renderer.CreateUniformBuffer(); borderDataBuffer.Data = borderDataBuffer.Data with { @@ -273,7 +264,7 @@ namespace osu.Game.Graphics.Backgrounds triangleQuad.Height ) / relativeSize; - renderer.DrawQuad(texture, drawQuad, DrawColourInfo.Colour.Interpolate(triangleQuad), new RectangleF(0, 0, 1, 1), vertexBatch.AddAction, textureCoords: textureCoords); + renderer.DrawQuad(texture, drawQuad, DrawColourInfo.Colour.Interpolate(triangleQuad), new RectangleF(0, 0, 1, 1), textureCoords: textureCoords); } shader.Unbind(); @@ -296,7 +287,6 @@ namespace osu.Game.Graphics.Backgrounds { base.Dispose(isDisposing); - vertexBatch?.Dispose(); borderDataBuffer?.Dispose(); } } From 6a57be0a50c8ddc20356f237dd80bc219226ba59 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 20 Jan 2024 13:04:05 +0100 Subject: [PATCH 2599/4092] clean up code and fix flipping --- .../SkinEditor/SkinSelectionScaleHandler.cs | 74 ++++++++----------- .../Components/SelectionBoxScaleHandle.cs | 19 +++-- 2 files changed, 43 insertions(+), 50 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs index 46b39645b2..c2f788a9e8 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs @@ -61,6 +61,9 @@ namespace osu.Game.Overlays.SkinEditor private Dictionary? originalScales; private Dictionary? originalPositions; + private bool isFlippedX; + private bool isFlippedY; + public override void Begin() { if (objectsInScale != null) @@ -75,6 +78,9 @@ namespace osu.Game.Overlays.SkinEditor originalPositions = objectsInScale.ToDictionary(d => d, d => d.ToScreenSpace(d.OriginPosition)); OriginalSurroundingQuad = GeometryUtils.GetSurroundingQuad(objectsInScale.SelectMany(d => d.ScreenSpaceDrawQuad.GetVertices().ToArray())); defaultOrigin = OriginalSurroundingQuad.Value.Centre; + + isFlippedX = false; + isFlippedY = false; } public override void Update(Vector2 scale, Vector2? origin = null) @@ -85,29 +91,21 @@ namespace osu.Game.Overlays.SkinEditor Debug.Assert(originalWidths != null && originalHeights != null && originalScales != null && originalPositions != null && defaultOrigin != null && OriginalSurroundingQuad != null); var actualOrigin = origin ?? defaultOrigin.Value; - Axes adjustAxis = scale.X == 0 ? Axes.Y : scale.Y == 0 ? Axes.X : Axes.Both; if ((adjustAxis == Axes.Y && !allSelectedSupportManualSizing(Axes.Y)) || (adjustAxis == Axes.X && !allSelectedSupportManualSizing(Axes.X))) return; - // the selection quad is always upright, so use an AABB rect to make mutating the values easier. - var selectionRect = OriginalSurroundingQuad.Value.AABBFloat; - // If the selection has no area we cannot scale it - if (selectionRect.Area == 0) + if (OriginalSurroundingQuad.Value.Width == 0 || OriginalSurroundingQuad.Value.Height == 0) return; - // copy to mutate, as we will need to compare to the original later on. - var adjustedRect = selectionRect; - // for now aspect lock scale adjustments that occur at corners.. if (adjustAxis == Axes.Both) { // project scale vector along diagonal - Vector2 diag = new Vector2(1, 1).Normalized(); - scale = Vector2.Dot(scale, diag) * diag; + scale = new Vector2((scale.X + scale.Y) * 0.5f); } // ..or if any of the selection have been rotated. // this is to avoid requiring skew logic (which would likely not be the user's expected transform anyway). @@ -115,66 +113,54 @@ namespace osu.Game.Overlays.SkinEditor { if (adjustAxis == Axes.Y) // if dragging from the horizontal centre, only a vertical component is available. - scale.X = scale.Y / selectionRect.Height * selectionRect.Width; + scale.X = scale.Y; else // in all other cases (arbitrarily) use the horizontal component for aspect lock. - scale.Y = scale.X / selectionRect.Width * selectionRect.Height; + scale.Y = scale.X; } - adjustedRect.Location = GeometryUtils.GetScaledPosition(scale, actualOrigin, OriginalSurroundingQuad!.Value.TopLeft); - adjustedRect.Size = OriginalSurroundingQuad!.Value.Size * scale; + bool flippedX = scale.X < 0; + bool flippedY = scale.Y < 0; + Axes toFlip = Axes.None; - if (adjustedRect.Width <= 0 || adjustedRect.Height <= 0) + if (flippedX != isFlippedX) { - Axes toFlip = Axes.None; + isFlippedX = flippedX; + toFlip |= Axes.X; + } - if (adjustedRect.Width <= 0) toFlip |= Axes.X; - if (adjustedRect.Height <= 0) toFlip |= Axes.Y; + if (flippedY != isFlippedY) + { + isFlippedY = flippedY; + toFlip |= Axes.Y; + } + if (toFlip != Axes.None) + { PerformFlipFromScaleHandles?.Invoke(toFlip); return; } - // scale adjust applied to each individual item should match that of the quad itself. - var scaledDelta = new Vector2( - adjustedRect.Width / selectionRect.Width, - adjustedRect.Height / selectionRect.Height - ); - foreach (var b in objectsInScale) { - // each drawable's relative position should be maintained in the scaled quad. - var screenPosition = originalPositions[b]; + UpdatePosition(b, GeometryUtils.GetScaledPosition(scale, actualOrigin, originalPositions[b])); - var relativePositionInOriginal = - new Vector2( - (screenPosition.X - selectionRect.TopLeft.X) / selectionRect.Width, - (screenPosition.Y - selectionRect.TopLeft.Y) / selectionRect.Height - ); - - var newPositionInAdjusted = new Vector2( - adjustedRect.TopLeft.X + adjustedRect.Width * relativePositionInOriginal.X, - adjustedRect.TopLeft.Y + adjustedRect.Height * relativePositionInOriginal.Y - ); - - UpdatePosition(b, newPositionInAdjusted); - - var currentScaledDelta = scaledDelta; + var currentScale = scale; if (Precision.AlmostEquals(MathF.Abs(b.Rotation) % 180, 90)) - currentScaledDelta = new Vector2(scaledDelta.Y, scaledDelta.X); + currentScale = new Vector2(scale.Y, scale.X); switch (adjustAxis) { case Axes.X: - b.Width = originalWidths[b] * currentScaledDelta.X; + b.Width = originalWidths[b] * currentScale.X; break; case Axes.Y: - b.Height = originalHeights[b] * currentScaledDelta.Y; + b.Height = originalHeights[b] * currentScale.Y; break; case Axes.Both: - b.Scale = originalScales[b] * currentScaledDelta; + b.Scale = originalScales[b] * currentScale; break; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs index 56c5585ae7..6179be1d4f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input.Events; +using osu.Framework.Logging; using osuTK; using osuTK.Input; @@ -24,6 +25,8 @@ namespace osu.Game.Screens.Edit.Compose.Components Size = new Vector2(10); } + private Anchor originalAnchor; + protected override bool OnDragStart(DragStartEvent e) { if (e.Button != MouseButton.Left) @@ -31,6 +34,8 @@ namespace osu.Game.Screens.Edit.Compose.Components if (scaleHandler == null) return false; + originalAnchor = Anchor; + scaleHandler.Begin(); return true; } @@ -40,10 +45,10 @@ namespace osu.Game.Screens.Edit.Compose.Components var quad = scaleHandler!.OriginalSurroundingQuad!.Value; Vector2 origin = quad.TopLeft; - if ((Anchor & Anchor.x0) > 0) + if ((originalAnchor & Anchor.x0) > 0) origin.X += quad.Width; - if ((Anchor & Anchor.y0) > 0) + if ((originalAnchor & Anchor.y0) > 0) origin.Y += quad.Height; return origin; @@ -89,6 +94,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private Vector2 convertDragEventToScaleMultiplier(DragEvent e) { Vector2 scale = e.MousePosition - e.MouseDownPosition; + Logger.Log($"Raw scale {scale}"); adjustScaleFromAnchor(ref scale); return Vector2.Divide(scale, scaleHandler!.OriginalSurroundingQuad!.Value.Size) + Vector2.One; } @@ -96,12 +102,12 @@ namespace osu.Game.Screens.Edit.Compose.Components private void adjustScaleFromAnchor(ref Vector2 scale) { // cancel out scale in axes we don't care about (based on which drag handle was used). - if ((Anchor & Anchor.x1) > 0) scale.X = 1; - if ((Anchor & Anchor.y1) > 0) scale.Y = 1; + if ((originalAnchor & Anchor.x1) > 0) scale.X = 1; + if ((originalAnchor & Anchor.y1) > 0) scale.Y = 1; // reverse the scale direction if dragging from top or left. - if ((Anchor & Anchor.x0) > 0) scale.X = -scale.X; - if ((Anchor & Anchor.y0) > 0) scale.Y = -scale.Y; + if ((originalAnchor & Anchor.x0) > 0) scale.X = -scale.X; + if ((originalAnchor & Anchor.y0) > 0) scale.Y = -scale.Y; } private void applyScale(bool shouldKeepAspectRatio) @@ -110,6 +116,7 @@ namespace osu.Game.Screens.Edit.Compose.Components ? new Vector2(MathF.Max(rawScale.X, rawScale.Y)) : rawScale; + Logger.Log($"Raw scale adjusted {newScale}, origin {getOriginPosition()}"); scaleHandler!.Update(newScale, getOriginPosition()); } } From fcaa5ec20e3fe43948bb1bd9d898d45dcf9b50cf Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 20 Jan 2024 13:26:08 +0100 Subject: [PATCH 2600/4092] remove debug logs --- .../Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs index 6179be1d4f..e0b41fd8e2 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs @@ -5,7 +5,6 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input.Events; -using osu.Framework.Logging; using osuTK; using osuTK.Input; @@ -94,7 +93,6 @@ namespace osu.Game.Screens.Edit.Compose.Components private Vector2 convertDragEventToScaleMultiplier(DragEvent e) { Vector2 scale = e.MousePosition - e.MouseDownPosition; - Logger.Log($"Raw scale {scale}"); adjustScaleFromAnchor(ref scale); return Vector2.Divide(scale, scaleHandler!.OriginalSurroundingQuad!.Value.Size) + Vector2.One; } @@ -116,7 +114,6 @@ namespace osu.Game.Screens.Edit.Compose.Components ? new Vector2(MathF.Max(rawScale.X, rawScale.Y)) : rawScale; - Logger.Log($"Raw scale adjusted {newScale}, origin {getOriginPosition()}"); scaleHandler!.Update(newScale, getOriginPosition()); } } From e1f3f7d988194e2f48df3aba184f095f59d2623b Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 20 Jan 2024 14:49:47 +0100 Subject: [PATCH 2601/4092] fix possible NaN in clamped scale --- .../Edit/OsuSelectionScaleHandler.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 7b0ae947e7..3c4818a533 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -177,10 +177,14 @@ namespace osu.Game.Rulesets.Osu.Edit var br1 = Vector2.Divide(-origin, selectionQuad.BottomRight - origin); var br2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - origin, selectionQuad.BottomRight - origin); - scale.X = selectionQuad.TopLeft.X - origin.X < 0 ? MathHelper.Clamp(scale.X, tl2.X, tl1.X) : MathHelper.Clamp(scale.X, tl1.X, tl2.X); - scale.Y = selectionQuad.TopLeft.Y - origin.Y < 0 ? MathHelper.Clamp(scale.Y, tl2.Y, tl1.Y) : MathHelper.Clamp(scale.Y, tl1.Y, tl2.Y); - scale.X = selectionQuad.BottomRight.X - origin.X < 0 ? MathHelper.Clamp(scale.X, br2.X, br1.X) : MathHelper.Clamp(scale.X, br1.X, br2.X); - scale.Y = selectionQuad.BottomRight.Y - origin.Y < 0 ? MathHelper.Clamp(scale.Y, br2.Y, br1.Y) : MathHelper.Clamp(scale.Y, br1.Y, br2.Y); + if (!Precision.AlmostEquals(selectionQuad.TopLeft.X - origin.X, 0)) + scale.X = selectionQuad.TopLeft.X - origin.X < 0 ? MathHelper.Clamp(scale.X, tl2.X, tl1.X) : MathHelper.Clamp(scale.X, tl1.X, tl2.X); + if (!Precision.AlmostEquals(selectionQuad.TopLeft.Y - origin.Y, 0)) + scale.Y = selectionQuad.TopLeft.Y - origin.Y < 0 ? MathHelper.Clamp(scale.Y, tl2.Y, tl1.Y) : MathHelper.Clamp(scale.Y, tl1.Y, tl2.Y); + if (!Precision.AlmostEquals(selectionQuad.BottomRight.X - origin.X, 0)) + scale.X = selectionQuad.BottomRight.X - origin.X < 0 ? MathHelper.Clamp(scale.X, br2.X, br1.X) : MathHelper.Clamp(scale.X, br1.X, br2.X); + if (!Precision.AlmostEquals(selectionQuad.BottomRight.Y - origin.Y, 0)) + scale.Y = selectionQuad.BottomRight.Y - origin.Y < 0 ? MathHelper.Clamp(scale.Y, br2.Y, br1.Y) : MathHelper.Clamp(scale.Y, br1.Y, br2.Y); return scale; } From 6a4129dad880e839b033d77ac2bdb00f22dc1c0d Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 20 Jan 2024 15:11:35 +0100 Subject: [PATCH 2602/4092] fix aspect ratio transform --- .../Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs index e0b41fd8e2..ea98ac573c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs @@ -111,7 +111,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void applyScale(bool shouldKeepAspectRatio) { var newScale = shouldKeepAspectRatio - ? new Vector2(MathF.Max(rawScale.X, rawScale.Y)) + ? new Vector2((rawScale.X + rawScale.Y) * 0.5f) : rawScale; scaleHandler!.Update(newScale, getOriginPosition()); From 0fc448f4f3a31643903017a9881b81749561e0eb Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 20 Jan 2024 15:12:48 +0100 Subject: [PATCH 2603/4092] fix adjusting scale from anchor --- osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs | 2 +- .../Edit/Compose/Components/SelectionBoxScaleHandle.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs index c2f788a9e8..bf75469d7a 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs @@ -91,7 +91,7 @@ namespace osu.Game.Overlays.SkinEditor Debug.Assert(originalWidths != null && originalHeights != null && originalScales != null && originalPositions != null && defaultOrigin != null && OriginalSurroundingQuad != null); var actualOrigin = origin ?? defaultOrigin.Value; - Axes adjustAxis = scale.X == 0 ? Axes.Y : scale.Y == 0 ? Axes.X : Axes.Both; + Axes adjustAxis = scale.X == 1 ? Axes.Y : scale.Y == 1 ? Axes.X : Axes.Both; if ((adjustAxis == Axes.Y && !allSelectedSupportManualSizing(Axes.Y)) || (adjustAxis == Axes.X && !allSelectedSupportManualSizing(Axes.X))) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs index ea98ac573c..60fbeb9fff 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs @@ -100,8 +100,8 @@ namespace osu.Game.Screens.Edit.Compose.Components private void adjustScaleFromAnchor(ref Vector2 scale) { // cancel out scale in axes we don't care about (based on which drag handle was used). - if ((originalAnchor & Anchor.x1) > 0) scale.X = 1; - if ((originalAnchor & Anchor.y1) > 0) scale.Y = 1; + if ((originalAnchor & Anchor.x1) > 0) scale.X = 0; + if ((originalAnchor & Anchor.y1) > 0) scale.Y = 0; // reverse the scale direction if dragging from top or left. if ((originalAnchor & Anchor.x0) > 0) scale.X = -scale.X; From 1596776a81b91db1850bf3325b6d2992ed5eaf6c Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 20 Jan 2024 15:15:49 +0100 Subject: [PATCH 2604/4092] fix imports --- osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs | 1 + .../Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 3c4818a533..1e3e22e34a 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -8,6 +8,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Primitives; +using osu.Framework.Utils; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs index 60fbeb9fff..3dde97657f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.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.Input.Events; From 9b9485f656807570afd91bd3b25923147a2075f2 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 20 Jan 2024 15:39:38 +0100 Subject: [PATCH 2605/4092] fix adjust axes detection --- .../Edit/OsuSelectionScaleHandler.cs | 3 +- .../SkinEditor/SkinSelectionScaleHandler.cs | 3 +- .../Components/SelectionBoxScaleHandle.cs | 47 +++++++++++++------ .../Components/SelectionScaleHandler.cs | 8 ++-- 4 files changed, 40 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 1e3e22e34a..7d5240fb69 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Utils; using osu.Game.Rulesets.Edit; @@ -82,7 +83,7 @@ namespace osu.Game.Rulesets.Osu.Edit obj => obj.Path.ControlPoints.Select(p => p.Type).ToArray()); } - public override void Update(Vector2 scale, Vector2? origin = null) + public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both) { if (objectsInScale == null) throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!"); diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs index bf75469d7a..0bd146a0a1 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs @@ -83,7 +83,7 @@ namespace osu.Game.Overlays.SkinEditor isFlippedY = false; } - public override void Update(Vector2 scale, Vector2? origin = null) + public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both) { if (objectsInScale == null) throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!"); @@ -91,7 +91,6 @@ namespace osu.Game.Overlays.SkinEditor Debug.Assert(originalWidths != null && originalHeights != null && originalScales != null && originalPositions != null && defaultOrigin != null && OriginalSurroundingQuad != null); var actualOrigin = origin ?? defaultOrigin.Value; - Axes adjustAxis = scale.X == 1 ? Axes.Y : scale.Y == 1 ? Axes.X : Axes.Both; if ((adjustAxis == Axes.Y && !allSelectedSupportManualSizing(Axes.Y)) || (adjustAxis == Axes.X && !allSelectedSupportManualSizing(Axes.X))) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs index 3dde97657f..d433e4e860 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs @@ -38,20 +38,6 @@ namespace osu.Game.Screens.Edit.Compose.Components return true; } - private Vector2 getOriginPosition() - { - var quad = scaleHandler!.OriginalSurroundingQuad!.Value; - Vector2 origin = quad.TopLeft; - - if ((originalAnchor & Anchor.x0) > 0) - origin.X += quad.Width; - - if ((originalAnchor & Anchor.y0) > 0) - origin.Y += quad.Height; - - return origin; - } - private Vector2 rawScale; protected override void OnDrag(DragEvent e) @@ -113,7 +99,38 @@ namespace osu.Game.Screens.Edit.Compose.Components ? new Vector2((rawScale.X + rawScale.Y) * 0.5f) : rawScale; - scaleHandler!.Update(newScale, getOriginPosition()); + scaleHandler!.Update(newScale, getOriginPosition(), getAdjustAxis()); + } + + private Vector2 getOriginPosition() + { + var quad = scaleHandler!.OriginalSurroundingQuad!.Value; + Vector2 origin = quad.TopLeft; + + if ((originalAnchor & Anchor.x0) > 0) + origin.X += quad.Width; + + if ((originalAnchor & Anchor.y0) > 0) + origin.Y += quad.Height; + + return origin; + } + + private Axes getAdjustAxis() + { + switch (originalAnchor) + { + case Anchor.TopCentre: + case Anchor.BottomCentre: + return Axes.Y; + + case Anchor.CentreLeft: + case Anchor.CentreRight: + return Axes.X; + + default: + return Axes.Both; + } } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs index 59406b3184..a96f627e56 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs @@ -46,10 +46,11 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The origin point to scale from. /// If the default value is supplied, a sane implementation-defined default will be used. /// - public void ScaleSelection(Vector2 scale, Vector2? origin = null) + /// The axes to adjust the scale in. + public void ScaleSelection(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both) { Begin(); - Update(scale, origin); + Update(scale, origin, adjustAxis); Commit(); } @@ -83,7 +84,8 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The origin point to scale from. /// If the default value is supplied, a sane implementation-defined default will be used. /// - public virtual void Update(Vector2 scale, Vector2? origin = null) + /// The axes to adjust the scale in. + public virtual void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both) { } From ac76af5cc8f894dfb87ae4d4987172b9f5a85934 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 20 Jan 2024 15:43:47 +0100 Subject: [PATCH 2606/4092] fix skin scale coordinate system --- osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs index 0bd146a0a1..e87952efa0 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs @@ -76,7 +76,7 @@ namespace osu.Game.Overlays.SkinEditor originalHeights = objectsInScale.ToDictionary(d => d, d => d.Height); originalScales = objectsInScale.ToDictionary(d => d, d => d.Scale); originalPositions = objectsInScale.ToDictionary(d => d, d => d.ToScreenSpace(d.OriginPosition)); - OriginalSurroundingQuad = GeometryUtils.GetSurroundingQuad(objectsInScale.SelectMany(d => d.ScreenSpaceDrawQuad.GetVertices().ToArray())); + OriginalSurroundingQuad = ToLocalSpace(GeometryUtils.GetSurroundingQuad(objectsInScale.SelectMany(d => d.ScreenSpaceDrawQuad.GetVertices().ToArray()))); defaultOrigin = OriginalSurroundingQuad.Value.Centre; isFlippedX = false; @@ -90,7 +90,7 @@ namespace osu.Game.Overlays.SkinEditor Debug.Assert(originalWidths != null && originalHeights != null && originalScales != null && originalPositions != null && defaultOrigin != null && OriginalSurroundingQuad != null); - var actualOrigin = origin ?? defaultOrigin.Value; + var actualOrigin = ToScreenSpace(origin ?? defaultOrigin.Value); if ((adjustAxis == Axes.Y && !allSelectedSupportManualSizing(Axes.Y)) || (adjustAxis == Axes.X && !allSelectedSupportManualSizing(Axes.X))) From 9459c66981a022905283b603f9bfb0d7e3cf6e77 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 20 Jan 2024 15:53:08 +0100 Subject: [PATCH 2607/4092] fix test --- osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs index 680a76f9b8..4c60ecf5db 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs @@ -130,7 +130,7 @@ namespace osu.Game.Tests.Visual.Editing OriginalSurroundingQuad = new Quad(targetContainer!.X, targetContainer.Y, targetContainer.Width, targetContainer.Height); } - public override void Update(Vector2 scale, Vector2? origin = null) + public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both) { if (targetContainer == null) throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!"); From a155b315bf8ad9060ae214ccc8763e4deebdae6c Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 20 Jan 2024 16:10:17 +0100 Subject: [PATCH 2608/4092] Fix negative width or height skin drawables --- osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs index e87952efa0..8daf0043da 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs @@ -151,11 +151,11 @@ namespace osu.Game.Overlays.SkinEditor switch (adjustAxis) { case Axes.X: - b.Width = originalWidths[b] * currentScale.X; + b.Width = MathF.Abs(originalWidths[b] * currentScale.X); break; case Axes.Y: - b.Height = originalHeights[b] * currentScale.Y; + b.Height = MathF.Abs(originalHeights[b] * currentScale.Y); break; case Axes.Both: From 5f40d3aed9ca859535c75d8f9e927d5cc7ad1581 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 20 Jan 2024 16:29:26 +0100 Subject: [PATCH 2609/4092] rename variable --- .../Edit/Compose/Components/SelectionBoxScaleHandle.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs index d433e4e860..74629a5384 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs @@ -48,14 +48,14 @@ namespace osu.Game.Screens.Edit.Compose.Components rawScale = convertDragEventToScaleMultiplier(e); - applyScale(shouldKeepAspectRatio: e.ShiftPressed); + applyScale(shouldLockAspectRatio: e.ShiftPressed); } protected override bool OnKeyDown(KeyDownEvent e) { if (IsDragged && (e.Key == Key.ShiftLeft || e.Key == Key.ShiftRight)) { - applyScale(shouldKeepAspectRatio: true); + applyScale(shouldLockAspectRatio: true); return true; } @@ -67,7 +67,7 @@ namespace osu.Game.Screens.Edit.Compose.Components base.OnKeyUp(e); if (IsDragged && (e.Key == Key.ShiftLeft || e.Key == Key.ShiftRight)) - applyScale(shouldKeepAspectRatio: false); + applyScale(shouldLockAspectRatio: false); } protected override void OnDragEnd(DragEndEvent e) @@ -93,9 +93,9 @@ namespace osu.Game.Screens.Edit.Compose.Components if ((originalAnchor & Anchor.y0) > 0) scale.Y = -scale.Y; } - private void applyScale(bool shouldKeepAspectRatio) + private void applyScale(bool shouldLockAspectRatio) { - var newScale = shouldKeepAspectRatio + var newScale = shouldLockAspectRatio ? new Vector2((rawScale.X + rawScale.Y) * 0.5f) : rawScale; From 2f924b33686ff7d7cb3080c2cc16f891d27cbc2e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 20 Jan 2024 16:33:03 +0100 Subject: [PATCH 2610/4092] fix skewed single axis scale --- .../SkinEditor/SkinSelectionScaleHandler.cs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs index 8daf0043da..0c2ee6aae3 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs @@ -100,23 +100,15 @@ namespace osu.Game.Overlays.SkinEditor if (OriginalSurroundingQuad.Value.Width == 0 || OriginalSurroundingQuad.Value.Height == 0) return; - // for now aspect lock scale adjustments that occur at corners.. + // for now aspect lock scale adjustments that occur at corners. if (adjustAxis == Axes.Both) { // project scale vector along diagonal scale = new Vector2((scale.X + scale.Y) * 0.5f); } - // ..or if any of the selection have been rotated. - // this is to avoid requiring skew logic (which would likely not be the user's expected transform anyway). - else if (objectsInScale.Any(b => !Precision.AlmostEquals(b.Rotation % 90, 0))) - { - if (adjustAxis == Axes.Y) - // if dragging from the horizontal centre, only a vertical component is available. - scale.X = scale.Y; - else - // in all other cases (arbitrarily) use the horizontal component for aspect lock. - scale.Y = scale.X; - } + // If any of the selection have been rotated and the adjust axis is not both, + // we would require skew logic to achieve a correct image editor-like scale. + // For now we just ignore, because it would likely not be the user's expected transform anyway. bool flippedX = scale.X < 0; bool flippedY = scale.Y < 0; From 78e87d379b760b9ebd5d567610423b607013b16d Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 20 Jan 2024 16:49:10 +0100 Subject: [PATCH 2611/4092] fix divide by zero --- .../Edit/Compose/Components/SelectionBoxScaleHandle.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs index 74629a5384..a1f6a1732a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osuTK; using osuTK.Input; @@ -79,7 +80,12 @@ namespace osu.Game.Screens.Edit.Compose.Components { Vector2 scale = e.MousePosition - e.MouseDownPosition; adjustScaleFromAnchor(ref scale); - return Vector2.Divide(scale, scaleHandler!.OriginalSurroundingQuad!.Value.Size) + Vector2.One; + + var surroundingQuad = scaleHandler!.OriginalSurroundingQuad!.Value; + scale.X = Precision.AlmostEquals(surroundingQuad.Width, 0) ? 0 : scale.X / surroundingQuad.Width; + scale.Y = Precision.AlmostEquals(surroundingQuad.Height, 0) ? 0 : scale.Y / surroundingQuad.Height; + + return scale + Vector2.One; } private void adjustScaleFromAnchor(ref Vector2 scale) From 3ad3b052dffe4a1434868ad3cd2fe37c67784d24 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 21 Jan 2024 02:10:36 +0300 Subject: [PATCH 2612/4092] Enable masking is osu logo --- osu.Game/Screens/Menu/OsuLogo.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 75ef8be02e..243612eee1 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -192,6 +192,7 @@ namespace osu.Game.Screens.Menu ColourLight = Color4Extensions.FromHex(@"ff7db7"), ColourDark = Color4Extensions.FromHex(@"de5b95"), RelativeSizeAxes = Axes.Both, + Masking = true }, } }, From a3703d657ab5ba2a93dbd89cad0adb6a21dfbd17 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 21 Jan 2024 02:12:49 +0300 Subject: [PATCH 2613/4092] Enable masking in popup dialog --- osu.Game/Overlays/Dialog/PopupDialog.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index 4ac37a63e2..1c1196a1c5 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -150,6 +150,7 @@ namespace osu.Game.Overlays.Dialog ColourLight = Color4Extensions.FromHex(@"271e26"), ColourDark = Color4Extensions.FromHex(@"1e171e"), TriangleScale = 4, + Masking = true }, flashLayer = new Box { From 087d0f03a4595d5eac66bd2f8ffb2ca6ea85264a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 21 Jan 2024 02:15:48 +0300 Subject: [PATCH 2614/4092] Enable masking in toolbar --- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 1da2e1b744..bdd16b347d 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -255,6 +255,7 @@ namespace osu.Game.Overlays.Toolbar RelativeSizeAxes = Axes.Both, ColourLight = OsuColour.Gray(40), ColourDark = OsuColour.Gray(20), + Masking = true }, }; } From 421ae73715ad9f1d3f5dcc331c8ac81304830187 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 21 Jan 2024 02:21:09 +0300 Subject: [PATCH 2615/4092] Enable masking in DrawableCarouselBeatmap --- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index baf0a14062..ecf65fe55c 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -109,7 +109,8 @@ namespace osu.Game.Screens.Select.Carousel TriangleScale = 2, RelativeSizeAxes = Axes.Both, ColourLight = Color4Extensions.FromHex(@"3a7285"), - ColourDark = Color4Extensions.FromHex(@"123744") + ColourDark = Color4Extensions.FromHex(@"123744"), + Masking = true }, new FillFlowContainer { From 60f7b4ea2f582b8fa2f1f15dd70c0febe0ea711d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 21 Jan 2024 02:25:29 +0300 Subject: [PATCH 2616/4092] Enable masking in DrawableRank --- osu.Game/Online/Leaderboards/DrawableRank.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Leaderboards/DrawableRank.cs b/osu.Game/Online/Leaderboards/DrawableRank.cs index 5177f35478..bbf981af09 100644 --- a/osu.Game/Online/Leaderboards/DrawableRank.cs +++ b/osu.Game/Online/Leaderboards/DrawableRank.cs @@ -50,6 +50,7 @@ namespace osu.Game.Online.Leaderboards ColourDark = rankColour.Darken(0.1f), ColourLight = rankColour.Lighten(0.1f), Velocity = 0.25f, + Masking = true }, new OsuSpriteText { From 6ba3546be5c2ef8f514e6652f2965bfb70d1b59c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 21 Jan 2024 02:28:14 +0300 Subject: [PATCH 2617/4092] Enable masking in RoundedButton --- osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs index 6aded3fe32..65bd5f49c2 100644 --- a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs +++ b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs @@ -68,6 +68,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 SpawnRatio = 0.6f, RelativeSizeAxes = Axes.Both, Depth = float.MaxValue, + Masking = true }); updateColours(); From 1999e772f6df41e35dafb2e185f6ff49a31089bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 21 Jan 2024 10:31:27 +0900 Subject: [PATCH 2618/4092] Fix `FollowPointConnection` pool filling up when follow points are hidden Closes https://github.com/ppy/osu/issues/26642. I think this applied to all pooling cases here. --- .../Objects/Pooling/PooledDrawableWithLifetimeContainer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs b/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs index 3b45acc7bb..1b0176cae5 100644 --- a/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs @@ -153,6 +153,9 @@ namespace osu.Game.Rulesets.Objects.Pooling protected override bool CheckChildrenLife() { + if (!IsPresent) + return false; + bool aliveChanged = base.CheckChildrenLife(); aliveChanged |= lifetimeManager.Update(Time.Current - PastLifetimeExtension, Time.Current + FutureLifetimeExtension); return aliveChanged; From 2dedead1d12c39fdb2f26385664b9f37105877fa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 21 Jan 2024 10:55:52 +0900 Subject: [PATCH 2619/4092] Allow debug instances to coexist alongside release instances --- osu.Game/OsuGame.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 7138bf2175..c244708385 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -80,7 +80,12 @@ namespace osu.Game [Cached(typeof(OsuGame))] public partial class OsuGame : OsuGameBase, IKeyBindingHandler, ILocalUserPlayInfo, IPerformFromScreenRunner, IOverlayManager, ILinkHandler { +#if DEBUG + // Different port allows runnning release and debug builds alongside each other. + public const int IPC_PORT = 44824; +#else public const int IPC_PORT = 44823; +#endif /// /// The amount of global offset to apply when a left/right anchored overlay is displayed (ie. settings or notifications). From e003ecb5937f5d1239b64250e8968908bf41c165 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 21 Jan 2024 04:57:48 +0300 Subject: [PATCH 2620/4092] Change default masking value to true --- osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs | 1 + osu.Game/Graphics/Backgrounds/Triangles.cs | 4 ++-- osu.Game/Graphics/Backgrounds/TrianglesV2.cs | 4 ++-- osu.Game/Graphics/UserInterface/DialogButton.cs | 1 + osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs | 1 - osu.Game/Online/Leaderboards/DrawableRank.cs | 1 - osu.Game/Overlays/Dialog/PopupDialog.cs | 1 - osu.Game/Overlays/Mods/ModSelectColumn.cs | 1 + osu.Game/Overlays/Toolbar/ToolbarButton.cs | 1 - osu.Game/Screens/Menu/OsuLogo.cs | 1 - osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 1 - 11 files changed, 7 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs index f1143cf14d..16cd302b88 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs @@ -15,6 +15,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { TriangleScale = 1.2f; HideAlphaDiscrepancies = false; + Masking = false; } protected override void Update() diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index 8db7f3a1c3..cf74d6ef62 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -78,9 +78,9 @@ namespace osu.Game.Graphics.Backgrounds /// /// If enabled, only the portion of triangles that falls within this 's - /// shape is drawn to the screen. + /// shape is drawn to the screen. Default is true. /// - public bool Masking { get; set; } + public bool Masking { get; set; } = true; /// /// Whether we should drop-off alpha values of triangles more quickly to improve diff --git a/osu.Game/Graphics/Backgrounds/TrianglesV2.cs b/osu.Game/Graphics/Backgrounds/TrianglesV2.cs index 4f611d9def..40b076ebc8 100644 --- a/osu.Game/Graphics/Backgrounds/TrianglesV2.cs +++ b/osu.Game/Graphics/Backgrounds/TrianglesV2.cs @@ -34,9 +34,9 @@ namespace osu.Game.Graphics.Backgrounds /// /// If enabled, only the portion of triangles that falls within this 's - /// shape is drawn to the screen. + /// shape is drawn to the screen. Default is true. /// - public bool Masking { get; set; } + public bool Masking { get; set; } = true; private readonly BindableFloat spawnRatio = new BindableFloat(1f); diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs index c920597a95..59acbecbbf 100644 --- a/osu.Game/Graphics/UserInterface/DialogButton.cs +++ b/osu.Game/Graphics/UserInterface/DialogButton.cs @@ -150,6 +150,7 @@ namespace osu.Game.Graphics.UserInterface TriangleScale = 4, ColourDark = OsuColour.Gray(0.88f), Shear = new Vector2(-0.2f, 0), + Masking = false }, }, }, diff --git a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs index 65bd5f49c2..6aded3fe32 100644 --- a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs +++ b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs @@ -68,7 +68,6 @@ namespace osu.Game.Graphics.UserInterfaceV2 SpawnRatio = 0.6f, RelativeSizeAxes = Axes.Both, Depth = float.MaxValue, - Masking = true }); updateColours(); diff --git a/osu.Game/Online/Leaderboards/DrawableRank.cs b/osu.Game/Online/Leaderboards/DrawableRank.cs index bbf981af09..5177f35478 100644 --- a/osu.Game/Online/Leaderboards/DrawableRank.cs +++ b/osu.Game/Online/Leaderboards/DrawableRank.cs @@ -50,7 +50,6 @@ namespace osu.Game.Online.Leaderboards ColourDark = rankColour.Darken(0.1f), ColourLight = rankColour.Lighten(0.1f), Velocity = 0.25f, - Masking = true }, new OsuSpriteText { diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index 1c1196a1c5..4ac37a63e2 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -150,7 +150,6 @@ namespace osu.Game.Overlays.Dialog ColourLight = Color4Extensions.FromHex(@"271e26"), ColourDark = Color4Extensions.FromHex(@"1e171e"), TriangleScale = 4, - Masking = true }, flashLayer = new Box { diff --git a/osu.Game/Overlays/Mods/ModSelectColumn.cs b/osu.Game/Overlays/Mods/ModSelectColumn.cs index 1c56763bd9..631bb72ced 100644 --- a/osu.Game/Overlays/Mods/ModSelectColumn.cs +++ b/osu.Game/Overlays/Mods/ModSelectColumn.cs @@ -95,6 +95,7 @@ namespace osu.Game.Overlays.Mods Height = header_height, Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), Velocity = 0.7f, + Masking = false }, headerText = new OsuTextFlowContainer(t => { diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index bdd16b347d..1da2e1b744 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -255,7 +255,6 @@ namespace osu.Game.Overlays.Toolbar RelativeSizeAxes = Axes.Both, ColourLight = OsuColour.Gray(40), ColourDark = OsuColour.Gray(20), - Masking = true }, }; } diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 243612eee1..75ef8be02e 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -192,7 +192,6 @@ namespace osu.Game.Screens.Menu ColourLight = Color4Extensions.FromHex(@"ff7db7"), ColourDark = Color4Extensions.FromHex(@"de5b95"), RelativeSizeAxes = Axes.Both, - Masking = true }, } }, diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index ecf65fe55c..4a28b3212e 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -110,7 +110,6 @@ namespace osu.Game.Screens.Select.Carousel RelativeSizeAxes = Axes.Both, ColourLight = Color4Extensions.FromHex(@"3a7285"), ColourDark = Color4Extensions.FromHex(@"123744"), - Masking = true }, new FillFlowContainer { From a69fd8198db3901fffa43a323ddb15a6df029713 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 21 Jan 2024 10:57:23 +0900 Subject: [PATCH 2621/4092] Update framework and other nuget packages - Moq held back because dicks - NUnit held back because large API changes (trivial but effort) - SignalR held back due to api deprecations --- ...u.Game.Rulesets.EmptyFreeform.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- ....Game.Rulesets.EmptyScrolling.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- osu.Desktop/osu.Desktop.csproj | 4 ++-- .../osu.Game.Benchmarks.csproj | 2 +- .../osu.Game.Rulesets.Catch.Tests.csproj | 2 +- .../osu.Game.Rulesets.Mania.Tests.csproj | 2 +- .../osu.Game.Rulesets.Osu.Tests.csproj | 2 +- .../osu.Game.Rulesets.Taiko.Tests.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- .../osu.Game.Tournament.Tests.csproj | 2 +- osu.Game/osu.Game.csproj | 20 +++++++++---------- 13 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj index 2baa7ee0e0..5babdb47ff 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index a2308e6dfc..5d64ca832a 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj index e839d2657c..6796a8962b 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index a2308e6dfc..5d64ca832a 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index f37cfdc5f1..d6a11fa924 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -23,9 +23,9 @@ - + - + diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj index 5de21a68d0..47c93fbd02 100644 --- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj +++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj @@ -8,7 +8,7 @@ - + diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index c45c85833c..0a77845343 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index b991db408c..877b9c3ea4 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index ea033cda45..9c248abd66 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -3,7 +3,7 @@ - + diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index 48465bb119..4eb6b0ab3d 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index ef6c16f2c4..7b08524240 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index 2cc07dd9ed..3b00f103c4 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -5,7 +5,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index f33ddac66f..1b1abe3971 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -21,12 +21,12 @@ - + - - - - + + + + @@ -36,13 +36,13 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + - + - - - + + + From 1393f52b2bf537bacf2248db501068f5f2b165b8 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 21 Jan 2024 05:20:42 +0300 Subject: [PATCH 2622/4092] Rename Masking to ClampToDrawable --- osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs | 2 +- .../Visual/Background/TestSceneTrianglesBackground.cs | 2 +- .../Visual/Background/TestSceneTrianglesV2Background.cs | 2 +- osu.Game/Graphics/Backgrounds/Triangles.cs | 8 ++++---- osu.Game/Graphics/Backgrounds/TrianglesV2.cs | 8 ++++---- osu.Game/Graphics/UserInterface/DialogButton.cs | 2 +- osu.Game/Overlays/Mods/ModSelectColumn.cs | 2 +- .../Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 2 +- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs index 16cd302b88..566176505d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { TriangleScale = 1.2f; HideAlphaDiscrepancies = false; - Masking = false; + ClampToDrawable = false; } protected override void Update() diff --git a/osu.Game.Tests/Visual/Background/TestSceneTrianglesBackground.cs b/osu.Game.Tests/Visual/Background/TestSceneTrianglesBackground.cs index 378dd99664..4733b7f92f 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneTrianglesBackground.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneTrianglesBackground.cs @@ -40,7 +40,7 @@ namespace osu.Game.Tests.Visual.Background AddSliderStep("Triangle scale", 0f, 10f, 1f, s => triangles.TriangleScale = s); AddSliderStep("Seed", 0, 1000, 0, s => triangles.Reset(s)); - AddToggleStep("Masking", m => triangles.Masking = m); + AddToggleStep("ClampToDrawable", c => triangles.ClampToDrawable = c); } } } diff --git a/osu.Game.Tests/Visual/Background/TestSceneTrianglesV2Background.cs b/osu.Game.Tests/Visual/Background/TestSceneTrianglesV2Background.cs index 01a2464b8e..71d1baddc8 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneTrianglesV2Background.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneTrianglesV2Background.cs @@ -128,7 +128,7 @@ namespace osu.Game.Tests.Visual.Background AddStep("White colour", () => box.Colour = triangles.Colour = maskedTriangles.Colour = Color4.White); AddStep("Vertical gradient", () => box.Colour = triangles.Colour = maskedTriangles.Colour = ColourInfo.GradientVertical(Color4.White, Color4.Red)); AddStep("Horizontal gradient", () => box.Colour = triangles.Colour = maskedTriangles.Colour = ColourInfo.GradientHorizontal(Color4.White, Color4.Red)); - AddToggleStep("Masking", m => maskedTriangles.Masking = m); + AddToggleStep("ClampToDrawable", c => maskedTriangles.ClampToDrawable = c); } } } diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index cf74d6ef62..d7bfeb7731 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -80,7 +80,7 @@ namespace osu.Game.Graphics.Backgrounds /// If enabled, only the portion of triangles that falls within this 's /// shape is drawn to the screen. Default is true. /// - public bool Masking { get; set; } = true; + public bool ClampToDrawable { get; set; } = true; /// /// Whether we should drop-off alpha values of triangles more quickly to improve @@ -257,7 +257,7 @@ namespace osu.Game.Graphics.Backgrounds private IShader shader; private Texture texture; - private bool masking; + private bool clamp; private readonly List parts = new List(); private readonly Vector2 triangleSize = new Vector2(1f, equilateral_triangle_ratio) * triangle_size; @@ -276,7 +276,7 @@ namespace osu.Game.Graphics.Backgrounds shader = Source.shader; texture = Source.texture; size = Source.DrawSize; - masking = Source.Masking; + clamp = Source.ClampToDrawable; parts.Clear(); parts.AddRange(Source.parts); @@ -306,7 +306,7 @@ namespace osu.Game.Graphics.Backgrounds Vector2 topLeft = particle.Position - new Vector2(relativeSize.X * 0.5f, 0f); - Quad triangleQuad = masking ? clampToDrawable(topLeft, relativeSize) : new Quad(topLeft.X, topLeft.Y, relativeSize.X, relativeSize.Y); + Quad triangleQuad = clamp ? clampToDrawable(topLeft, relativeSize) : new Quad(topLeft.X, topLeft.Y, relativeSize.X, relativeSize.Y); var drawQuad = new Quad( Vector2Extensions.Transform(triangleQuad.TopLeft * size, DrawInfo.Matrix), diff --git a/osu.Game/Graphics/Backgrounds/TrianglesV2.cs b/osu.Game/Graphics/Backgrounds/TrianglesV2.cs index 40b076ebc8..f723b1b358 100644 --- a/osu.Game/Graphics/Backgrounds/TrianglesV2.cs +++ b/osu.Game/Graphics/Backgrounds/TrianglesV2.cs @@ -36,7 +36,7 @@ namespace osu.Game.Graphics.Backgrounds /// If enabled, only the portion of triangles that falls within this 's /// shape is drawn to the screen. Default is true. /// - public bool Masking { get; set; } = true; + public bool ClampToDrawable { get; set; } = true; private readonly BindableFloat spawnRatio = new BindableFloat(1f); @@ -193,7 +193,7 @@ namespace osu.Game.Graphics.Backgrounds private Vector2 size; private float thickness; private float texelSize; - private bool masking; + private bool clamp; public TrianglesDrawNode(TrianglesV2 source) : base(source) @@ -208,7 +208,7 @@ namespace osu.Game.Graphics.Backgrounds texture = Source.texture; size = Source.DrawSize; thickness = Source.Thickness; - masking = Source.Masking; + clamp = Source.ClampToDrawable; Quad triangleQuad = new Quad( Vector2Extensions.Transform(Vector2.Zero, DrawInfo.Matrix), @@ -248,7 +248,7 @@ namespace osu.Game.Graphics.Backgrounds { Vector2 topLeft = particle.Position - new Vector2(relativeSize.X * 0.5f, 0f); - Quad triangleQuad = masking ? clampToDrawable(topLeft, relativeSize) : new Quad(topLeft.X, topLeft.Y, relativeSize.X, relativeSize.Y); + Quad triangleQuad = clamp ? clampToDrawable(topLeft, relativeSize) : new Quad(topLeft.X, topLeft.Y, relativeSize.X, relativeSize.Y); var drawQuad = new Quad( Vector2Extensions.Transform(triangleQuad.TopLeft * size, DrawInfo.Matrix), diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs index 59acbecbbf..10aca017f1 100644 --- a/osu.Game/Graphics/UserInterface/DialogButton.cs +++ b/osu.Game/Graphics/UserInterface/DialogButton.cs @@ -150,7 +150,7 @@ namespace osu.Game.Graphics.UserInterface TriangleScale = 4, ColourDark = OsuColour.Gray(0.88f), Shear = new Vector2(-0.2f, 0), - Masking = false + ClampToDrawable = false }, }, }, diff --git a/osu.Game/Overlays/Mods/ModSelectColumn.cs b/osu.Game/Overlays/Mods/ModSelectColumn.cs index 631bb72ced..05454159c7 100644 --- a/osu.Game/Overlays/Mods/ModSelectColumn.cs +++ b/osu.Game/Overlays/Mods/ModSelectColumn.cs @@ -95,7 +95,7 @@ namespace osu.Game.Overlays.Mods Height = header_height, Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), Velocity = 0.7f, - Masking = false + ClampToDrawable = false }, headerText = new OsuTextFlowContainer(t => { diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 4a28b3212e..baf0a14062 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -109,7 +109,7 @@ namespace osu.Game.Screens.Select.Carousel TriangleScale = 2, RelativeSizeAxes = Axes.Both, ColourLight = Color4Extensions.FromHex(@"3a7285"), - ColourDark = Color4Extensions.FromHex(@"123744"), + ColourDark = Color4Extensions.FromHex(@"123744") }, new FillFlowContainer { From 18d16018d340b38729b2ddbcb46e3bcb5b3fe3b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 21 Jan 2024 12:17:16 +0900 Subject: [PATCH 2623/4092] Fix failing tests due to pooling safety changes --- .../Skinning/TestSceneBarLine.cs | 40 +++++++--------- .../TestSceneDrawableJudgement.cs | 47 ++++++++++--------- 2 files changed, 42 insertions(+), 45 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneBarLine.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneBarLine.cs index ab9f57ecc3..a5c18babe2 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneBarLine.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneBarLine.cs @@ -1,10 +1,8 @@ // 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.Collections.Generic; using NUnit.Framework; -using osu.Framework.Graphics; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.UI; @@ -16,37 +14,35 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning [Test] public void TestMinor() { - AddStep("Create barlines", () => recreate()); + AddStep("Create barlines", recreate); } - private void recreate(Func>? createBarLines = null) + private void recreate() { var stageDefinitions = new List { new StageDefinition(4), }; - SetContents(_ => new ManiaPlayfield(stageDefinitions).With(s => + SetContents(_ => { - if (createBarLines != null) + var maniaPlayfield = new ManiaPlayfield(stageDefinitions); + + // Must be scheduled so the pool is loaded before we try and retrieve from it. + Schedule(() => { - var barLines = createBarLines(); - - foreach (var b in barLines) - s.Add(b); - - return; - } - - for (int i = 0; i < 64; i++) - { - s.Add(new BarLine + for (int i = 0; i < 64; i++) { - StartTime = Time.Current + i * 500, - Major = i % 4 == 0, - }); - } - })); + maniaPlayfield.Add(new BarLine + { + StartTime = Time.Current + i * 500, + Major = i % 4 == 0, + }); + } + }); + + return maniaPlayfield; + }); } } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs index 874130233a..5f5596cbb3 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs @@ -25,16 +25,16 @@ namespace osu.Game.Rulesets.Osu.Tests [Resolved] private OsuConfigManager config { get; set; } = null!; - private readonly List> pools; + private readonly List> pools = new List>(); - public TestSceneDrawableJudgement() + [TestCaseSource(nameof(validResults))] + public void Test(HitResult result) { - pools = new List>(); - - foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType().Skip(1)) - showResult(result); + showResult(result); } + private static IEnumerable validResults => Enum.GetValues().Skip(1); + [Test] public void TestHitLightingDisabled() { @@ -72,32 +72,33 @@ namespace osu.Game.Rulesets.Osu.Tests pools.Add(pool = new DrawablePool(1)); else { - pool = pools[poolIndex]; - // We need to make sure neither the pool nor the judgement get disposed when new content is set, and they both share the same parent. + pool = pools[poolIndex]; ((Container)pool.Parent!).Clear(false); } var container = new Container { RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - pool, - pool.Get(j => j.Apply(new JudgementResult(new HitObject - { - StartTime = Time.Current - }, new Judgement()) - { - Type = result, - }, null)).With(j => - { - j.Anchor = Anchor.Centre; - j.Origin = Anchor.Centre; - }) - } + Child = pool, }; + // Must be scheduled so the pool is loaded before we try and retrieve from it. + Schedule(() => + { + container.Add(pool.Get(j => j.Apply(new JudgementResult(new HitObject + { + StartTime = Time.Current + }, new Judgement()) + { + Type = result, + }, null)).With(j => + { + j.Anchor = Anchor.Centre; + j.Origin = Anchor.Centre; + })); + }); + poolIndex++; return container; }); From 02369139b3054213e6598337a66ea868a5ff56c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 21 Jan 2024 13:11:41 +0900 Subject: [PATCH 2624/4092] Remove `FillFlow` overhead of argon counters --- .../Play/HUD/ArgonCounterTextComponent.cs | 51 +++++++++---------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs index f16669f865..f8c82feddd 100644 --- a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs +++ b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs @@ -42,35 +42,30 @@ namespace osu.Game.Screens.Play.HUD Origin = anchor; AutoSizeAxes = Axes.Both; - InternalChild = new FillFlowContainer + InternalChildren = new Drawable[] { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] + labelText = new OsuSpriteText { - labelText = new OsuSpriteText + Alpha = 0, + Text = label.GetValueOrDefault(), + Font = OsuFont.Torus.With(size: 12, weight: FontWeight.Bold), + Margin = new MarginPadding { Left = 2.5f }, + }, + NumberContainer = new Container + { + AutoSizeAxes = Axes.Both, + Children = new[] { - Alpha = 0, - Text = label.GetValueOrDefault(), - Font = OsuFont.Torus.With(size: 12, weight: FontWeight.Bold), - Margin = new MarginPadding { Left = 2.5f }, - }, - NumberContainer = new Container - { - AutoSizeAxes = Axes.Both, - Children = new[] + wireframesPart = new ArgonCounterSpriteText(wireframesLookup) { - wireframesPart = new ArgonCounterSpriteText(wireframesLookup) - { - Anchor = anchor, - Origin = anchor, - }, - textPart = new ArgonCounterSpriteText(textLookup) - { - Anchor = anchor, - Origin = anchor, - }, - } + Anchor = anchor, + Origin = anchor, + }, + textPart = new ArgonCounterSpriteText(textLookup) + { + Anchor = anchor, + Origin = anchor, + }, } } }; @@ -110,7 +105,11 @@ namespace osu.Game.Screens.Play.HUD { base.LoadComplete(); WireframeOpacity.BindValueChanged(v => wireframesPart.Alpha = v.NewValue, true); - ShowLabel.BindValueChanged(s => labelText.Alpha = s.NewValue ? 1 : 0, true); + ShowLabel.BindValueChanged(s => + { + labelText.Alpha = s.NewValue ? 1 : 0; + NumberContainer.Y = s.NewValue ? 12 : 0; + }, true); } private partial class ArgonCounterSpriteText : OsuSpriteText From bab14dce31dcc408a600ebe4d0a94648b2cbaae6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 22 Jan 2024 02:07:09 +0300 Subject: [PATCH 2625/4092] Add failing test case --- .../TestSceneSliderApplication.cs | 39 +++++++++++++++++-- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs index f41dd913ab..380a2087ac 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Tests { DrawableSlider dho = null; - AddStep("create slider", () => Child = dho = new DrawableSlider(prepareObject(new Slider + AddStep("create slider", () => Child = dho = new DrawableSlider(applyDefaults(new Slider { Position = new Vector2(256, 192), IndexInCurrentCombo = 0, @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Tests AddWaitStep("wait for progression", 1); - AddStep("apply new slider", () => dho.Apply(prepareObject(new Slider + AddStep("apply new slider", () => dho.Apply(applyDefaults(new Slider { Position = new Vector2(256, 192), ComboIndex = 1, @@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Osu.Tests Child = new SkinProvidingContainer(provider) { RelativeSizeAxes = Axes.Both, - Child = dho = new DrawableSlider(prepareObject(new Slider + Child = dho = new DrawableSlider(applyDefaults(new Slider { Position = new Vector2(256, 192), IndexInCurrentCombo = 0, @@ -97,7 +97,38 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert("ball is red", () => dho.ChildrenOfType().Single().BallColour == Color4.Red); } - private Slider prepareObject(Slider slider) + [Test] + public void TestIncreaseRepeatCount() + { + DrawableSlider dho = null; + + AddStep("create slider", () => + { + Child = dho = new DrawableSlider(applyDefaults(new Slider + { + Position = new Vector2(256, 192), + IndexInCurrentCombo = 0, + StartTime = Time.Current, + Path = new SliderPath(PathType.LINEAR, new[] + { + Vector2.Zero, + new Vector2(150, 100), + new Vector2(300, 0), + }) + })); + }); + + AddStep("increase repeat count", () => + { + dho.HitObject.RepeatCount++; + applyDefaults(dho.HitObject); + }); + + AddAssert("repeat got custom anchor", () => + dho.ChildrenOfType().Single().RelativeAnchorPosition == Vector2.Divide(dho.SliderBody!.PathOffset, dho.DrawSize)); + } + + private Slider applyDefaults(Slider slider) { slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); return slider; From 57b2d018a99a7307d09984687299b03c027040cf Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 22 Jan 2024 02:07:29 +0300 Subject: [PATCH 2626/4092] Fix slider sometimes not updating relative anchor position --- .../Objects/Drawables/DrawableSlider.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index ed4c02a4a9..baec200107 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private Container repeatContainer; private PausableSkinnableSound slidingSample; - private readonly LayoutValue drawSizeLayout; + private readonly LayoutValue relativeAnchorPositionLayout; public DrawableSlider() : this(null) @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables AlwaysPresent = true, Alpha = 0 }; - AddLayout(drawSizeLayout = new LayoutValue(Invalidation.DrawSize | Invalidation.MiscGeometry)); + AddLayout(relativeAnchorPositionLayout = new LayoutValue(Invalidation.DrawSize | Invalidation.MiscGeometry)); } [BackgroundDependencyLoader] @@ -190,6 +190,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables repeatContainer.Add(repeat); break; } + + relativeAnchorPositionLayout.Invalidate(); } protected override void ClearNestedHitObjects() @@ -265,14 +267,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Size = SliderBody?.Size ?? Vector2.Zero; OriginPosition = SliderBody?.PathOffset ?? Vector2.Zero; - if (!drawSizeLayout.IsValid) + if (!relativeAnchorPositionLayout.IsValid) { Vector2 pos = Vector2.Divide(OriginPosition, DrawSize); foreach (var obj in NestedHitObjects) obj.RelativeAnchorPosition = pos; Ball.RelativeAnchorPosition = pos; - drawSizeLayout.Validate(); + relativeAnchorPositionLayout.Validate(); } } From cec4f670d1f84e39b4fa918b13e57fbed087f65b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 22 Jan 2024 03:12:23 +0300 Subject: [PATCH 2627/4092] Reduce allocation overhead in BeatmapCarousel --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 4408634787..1f2103ff61 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -899,7 +899,7 @@ namespace osu.Game.Screens.Select // Update externally controlled state of currently visible items (e.g. x-offset and opacity). // This is a per-frame update on all drawable panels. - foreach (DrawableCarouselItem item in Scroll.Children) + foreach (DrawableCarouselItem item in Scroll.ScrollContent) { updateItem(item); From 2bd9cd5d3410a281cff18f285a01c355a9c9b8e3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 22 Jan 2024 04:39:08 +0300 Subject: [PATCH 2628/4092] Fix blueprint container not handling right clicks correctly while moveing an element --- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 110beb0fa6..2d6e234e57 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -114,7 +114,7 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override bool OnMouseDown(MouseDownEvent e) { bool selectionPerformed = performMouseDownActions(e); - bool movementPossible = prepareSelectionMovement(); + bool movementPossible = prepareSelectionMovement(e); // check if selection has occurred if (selectionPerformed) @@ -536,9 +536,13 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Attempts to begin the movement of any selected blueprints. /// + /// The defining the beginning of a movement. /// Whether a movement is possible. - private bool prepareSelectionMovement() + private bool prepareSelectionMovement(MouseDownEvent e) { + if (e.Button == MouseButton.Right) + return false; + if (!SelectionHandler.SelectedBlueprints.Any()) return false; From 74f05a5c4b28d1493e7556588f0589cb20821650 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Jan 2024 15:54:24 +0900 Subject: [PATCH 2629/4092] Use container itself rather than `ScrollContent` --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 1f2103ff61..35d534cf68 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -899,7 +899,7 @@ namespace osu.Game.Screens.Select // Update externally controlled state of currently visible items (e.g. x-offset and opacity). // This is a per-frame update on all drawable panels. - foreach (DrawableCarouselItem item in Scroll.ScrollContent) + foreach (DrawableCarouselItem item in Scroll) { updateItem(item); From 1f0ad5cff24f4c445d4dd8d6b5c4be1e3de98472 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Jan 2024 15:56:16 +0900 Subject: [PATCH 2630/4092] Apply same fix in more places --- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 35d534cf68..70ecde3858 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -868,7 +868,7 @@ namespace osu.Game.Screens.Select { var toDisplay = visibleItems.GetRange(displayedRange.first, displayedRange.last - displayedRange.first + 1); - foreach (var panel in Scroll.Children) + foreach (var panel in Scroll) { Debug.Assert(panel.Item != null); @@ -1094,7 +1094,7 @@ namespace osu.Game.Screens.Select // to enter clamp-special-case mode where it animates completely differently to normal. float scrollChange = scrollTarget.Value - Scroll.Current; Scroll.ScrollTo(scrollTarget.Value, false); - foreach (var i in Scroll.Children) + foreach (var i in Scroll) i.Y += scrollChange; break; } From 3e5fe66e58b3bf3e36761511883b8c0170f7c0e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Jan 2024 16:37:56 +0900 Subject: [PATCH 2631/4092] Fix potential null reference in player screen transition handling See https://github.com/ppy/osu/actions/runs/7607677219/job/20715418536?pr=26660. --- 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 db39d06c54..ad1f9ec897 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1240,9 +1240,14 @@ namespace osu.Game.Screens.Play { b.IgnoreUserSettings.Value = true; - b.IsBreakTime.UnbindFrom(breakTracker.IsBreakTime); - b.IsBreakTime.Value = false; + // May be null if the load never completed. + if (breakTracker != null) + { + b.IsBreakTime.UnbindFrom(breakTracker.IsBreakTime); + b.IsBreakTime.Value = false; + } }); + storyboardReplacesBackground.Value = false; } } From 3dd18c777a578acbc8ded48cb3ea92683391fc5e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Jan 2024 17:00:35 +0900 Subject: [PATCH 2632/4092] Remove a couple more overrides --- osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs | 2 -- .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 7 +------ osu.Game/Screens/OnlinePlay/OnlinePlaySubScreen.cs | 2 +- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index 6f5ff9c99c..3792a67896 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -40,8 +40,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge { public override string Title => "Lounge"; - protected override bool PlayExitSound => false; - protected override BackgroundScreen CreateBackground() => new LoungeBackgroundScreen { SelectedRoom = { BindTarget = SelectedRoom } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 8a85a46a2f..aab7ac3280 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -44,8 +44,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer public override string ShortTitle => "room"; - protected override bool PlayExitSound => !exitConfirmed; - [Resolved] private MultiplayerClient client { get; set; } @@ -249,15 +247,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [Resolved(canBeNull: true)] private IDialogOverlay dialogOverlay { get; set; } - private bool exitConfirmed; - public override bool OnExiting(ScreenExitEvent e) { // room has not been created yet or we're offline; exit immediately. if (client.Room == null || !IsConnected) return base.OnExiting(e); - if (!exitConfirmed && dialogOverlay != null) + if (dialogOverlay != null) { if (dialogOverlay.CurrentDialog is ConfirmDialog confirmDialog) confirmDialog.PerformOkAction(); @@ -265,7 +261,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { dialogOverlay.Push(new ConfirmDialog("Are you sure you want to leave this multiplayer match?", () => { - exitConfirmed = true; if (this.IsCurrentScreen()) this.Exit(); })); diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySubScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySubScreen.cs index ea855c0474..fa1ee004c9 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySubScreen.cs @@ -13,7 +13,7 @@ namespace osu.Game.Screens.OnlinePlay public virtual string ShortTitle => Title; - protected override bool PlayExitSound => false; + protected sealed override bool PlayExitSound => false; [Resolved] protected IRoomManager? RoomManager { get; private set; } From f12be60d8d33471fc8acb4ee54c369f4f4e928e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Jan 2024 17:18:22 +0900 Subject: [PATCH 2633/4092] Make test actually test multiple icons --- .../Beatmaps/TestSceneDifficultyIcon.cs | 39 +++++++++++++++++-- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultyIcon.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultyIcon.cs index 79f9aec2e3..80320c138b 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultyIcon.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultyIcon.cs @@ -4,27 +4,58 @@ #nullable disable using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Utils; using osu.Game.Beatmaps.Drawables; using osu.Game.Rulesets.Osu; using osu.Game.Tests.Beatmaps; +using osuTK; namespace osu.Game.Tests.Visual.Beatmaps { public partial class TestSceneDifficultyIcon : OsuTestScene { + private FillFlowContainer fill; + + protected override void LoadComplete() + { + base.LoadComplete(); + + Child = fill = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + Width = 300, + Direction = FillDirection.Full, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + } + [Test] public void CreateDifficultyIcon() { DifficultyIcon difficultyIcon = null; - AddStep("create difficulty icon", () => + AddRepeatStep("create difficulty icon", () => { - Child = difficultyIcon = new DifficultyIcon(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, new OsuRuleset().RulesetInfo) + var rulesetInfo = new OsuRuleset().RulesetInfo; + var beatmapInfo = new TestBeatmap(rulesetInfo).BeatmapInfo; + + beatmapInfo.Difficulty.ApproachRate = RNG.Next(0, 10); + beatmapInfo.Difficulty.CircleSize = RNG.Next(0, 10); + beatmapInfo.Difficulty.OverallDifficulty = RNG.Next(0, 10); + beatmapInfo.Difficulty.DrainRate = RNG.Next(0, 10); + beatmapInfo.StarRating = RNG.NextSingle(0, 10); + beatmapInfo.BPM = RNG.Next(60, 300); + + fill.Add(difficultyIcon = new DifficultyIcon(beatmapInfo, rulesetInfo) { + Scale = new Vector2(2), ShowTooltip = true, ShowExtendedTooltip = true - }; - }); + }); + }, 10); AddStep("hide extended tooltip", () => difficultyIcon.ShowExtendedTooltip = false); From cd551b1abd4912b7d93fedeabd0b21fcffa9f1bd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Jan 2024 18:01:21 +0900 Subject: [PATCH 2634/4092] Fix star fountains sometimes resetting visually Addresses https://github.com/ppy/osu/discussions/26622. --- osu.Game/Screens/Menu/StarFountain.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/StarFountain.cs b/osu.Game/Screens/Menu/StarFountain.cs index fd59ec3573..dd5171c6be 100644 --- a/osu.Game/Screens/Menu/StarFountain.cs +++ b/osu.Game/Screens/Menu/StarFountain.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Textures; +using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Skinning; @@ -43,8 +44,6 @@ namespace osu.Game.Screens.Menu private const double shoot_duration = 800; - protected override bool CanSpawnParticles => lastShootTime != null && Time.Current - lastShootTime < shoot_duration; - [Resolved] private ISkinSource skin { get; set; } = null!; @@ -57,7 +56,6 @@ namespace osu.Game.Screens.Menu private void load(TextureStore textures) { Texture = skin.GetTexture("Menu/fountain-star") ?? textures.Get("Menu/fountain-star"); - Active.Value = true; } protected override FallingParticle CreateParticle() @@ -81,8 +79,15 @@ namespace osu.Game.Screens.Menu return lastShootDirection * x_velocity_from_direction * (float)(1 - 2 * (Clock.CurrentTime - lastShootTime!.Value) / shoot_duration) + getRandomVariance(x_velocity_random_variance); } + private ScheduledDelegate? deactivateDelegate; + public void Shoot(int direction) { + Active.Value = true; + + deactivateDelegate?.Cancel(); + deactivateDelegate = Scheduler.AddDelayed(() => Active.Value = false, shoot_duration); + lastShootTime = Clock.CurrentTime; lastShootDirection = direction; } From 47e9846315244e9f4824a8fedcb8e6c4dee3cd44 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Jan 2024 18:48:48 +0900 Subject: [PATCH 2635/4092] Adjust slider tick / end miss animations to be less busy --- .../Skinning/Argon/ArgonJudgementPiece.cs | 11 ++++++----- osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs | 9 ++++----- osu.Game/Skinning/LegacyJudgementPieceOld.cs | 10 +++------- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs index bb61bd37c1..9a5abba4fb 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs @@ -65,14 +65,15 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon if (Result == HitResult.IgnoreMiss || Result == HitResult.LargeTickMiss) { this.RotateTo(-45); - this.ScaleTo(1.8f); + this.ScaleTo(1.6f); this.ScaleTo(1.2f, 100, Easing.In); - this.MoveTo(Vector2.Zero); - this.MoveToOffset(new Vector2(0, 10), 800, Easing.InQuint); + this.FadeOutFromOne(400); } else if (Result.IsMiss()) { + this.FadeOutFromOne(800); + this.ScaleTo(1.6f); this.ScaleTo(1, 100, Easing.In); @@ -84,14 +85,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon } else { + this.FadeOutFromOne(800); + JudgementText .FadeInFromZero(300, Easing.OutQuint) .ScaleTo(Vector2.One) .ScaleTo(new Vector2(1.2f), 1800, Easing.OutQuint); } - this.FadeOutFromOne(800); - ringExplosion?.PlayAnimation(); } diff --git a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs index ada651b60e..c7103cd9fd 100644 --- a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs +++ b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs @@ -45,11 +45,10 @@ namespace osu.Game.Rulesets.Judgements if (Result == HitResult.IgnoreMiss || Result == HitResult.LargeTickMiss) { this.RotateTo(-45); - this.ScaleTo(1.8f); + this.ScaleTo(1.6f); this.ScaleTo(1.2f, 100, Easing.In); - this.MoveTo(Vector2.Zero); - this.MoveToOffset(new Vector2(0, 10), 800, Easing.InQuint); + this.FadeOutFromOne(400); } else if (Result.IsMiss()) { @@ -61,9 +60,9 @@ namespace osu.Game.Rulesets.Judgements this.RotateTo(0); this.RotateTo(40, 800, Easing.InQuint); - } - this.FadeOutFromOne(800); + this.FadeOutFromOne(800); + } } public Drawable? GetAboveHitObjectsProxiedContent() => null; diff --git a/osu.Game/Skinning/LegacyJudgementPieceOld.cs b/osu.Game/Skinning/LegacyJudgementPieceOld.cs index 1834a17279..a9f68bd378 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceOld.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceOld.cs @@ -63,14 +63,10 @@ namespace osu.Game.Skinning // missed ticks / slider end don't get the normal animation. if (isMissedTick()) { - this.ScaleTo(1.6f); - this.ScaleTo(1, 100, Easing.In); + this.ScaleTo(1.2f); + this.ScaleTo(1f, 100, Easing.In); - if (legacyVersion > 1.0m) - { - this.MoveTo(new Vector2(0, -2f)); - this.MoveToOffset(new Vector2(0, 10), fade_out_delay + fade_out_length, Easing.In); - } + this.FadeOutFromOne(400); } else { From 9b2740f8a4032dfa568f2bbcc1ee0609a56c2bb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jan 2024 11:11:15 +0100 Subject: [PATCH 2636/4092] Fix score submission test failures due to checking audio playback validity (again) As seen in https://github.com/ppy/osu/actions/runs/7607899979/job/20716013982?pr=26662#step:5:75 In https://github.com/ppy/osu/pull/26484, I went "well if I'm moving the enabling of validation of playback rate to `SubmittingPlayer` context, then surely I can remove the local override in the test scene, right?" Reader: Apparently I did not notice that `FakeImportingPlayer : TestPlayer : SoloPlayer : SubmittingPlayer`. So no, I could not remove the local override in the test scene. You could probably attempt to conjure up some excuse about deep inheritance hierarchies here but nah. Really just a failure to read on my behalf as usual. --- .../Visual/Gameplay/TestScenePlayerScoreSubmission.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index e2ce3a014c..5e22e47572 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -23,6 +23,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko; using osu.Game.Rulesets.Taiko.Mods; using osu.Game.Scoring; +using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; using osu.Game.Tests.Beatmaps; @@ -383,6 +384,11 @@ namespace osu.Game.Tests.Visual.Gameplay AllowImportCompletion = new SemaphoreSlim(1); } + protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new MasterGameplayClockContainer(beatmap, gameplayStart) + { + ShouldValidatePlaybackRate = false, + }; + protected override async Task ImportScore(Score score) { ScoreImportStarted = true; From c8521b49cd6bcfa3aee3c437da357d49a196844d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Jan 2024 18:43:15 +0900 Subject: [PATCH 2637/4092] Change S rank to require no miss --- .../Scoring/CatchScoreProcessor.cs | 2 +- .../Scoring/OsuScoreProcessor.cs | 18 ++++++++++++++++++ .../Scoring/TaikoScoreProcessor.cs | 18 ++++++++++++++++++ .../Visual/Ranking/TestSceneAccuracyCircle.cs | 19 +++++++++++-------- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 4 ++-- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 2 +- 6 files changed, 51 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index 161a59c5fd..a12fe7f3e1 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -89,7 +89,7 @@ namespace osu.Game.Rulesets.Catch.Scoring return baseIncrease * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(combo_cap, combo_base)); } - public override ScoreRank RankFromAccuracy(double accuracy) + public override ScoreRank RankFromScore(double accuracy, Dictionary results) { if (accuracy == accuracy_cutoff_x) return ScoreRank.X; diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 97980c6d18..0d62a6e718 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -1,9 +1,11 @@ // 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 osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Osu.Scoring { @@ -14,6 +16,22 @@ namespace osu.Game.Rulesets.Osu.Scoring { } + public override ScoreRank RankFromScore(double accuracy, Dictionary results) + { + ScoreRank rank = base.RankFromScore(accuracy, results); + + switch (rank) + { + case ScoreRank.S: + case ScoreRank.X: + if (results.GetValueOrDefault(HitResult.Miss) > 0) + rank = ScoreRank.A; + break; + } + + return rank; + } + protected override HitEvent CreateHitEvent(JudgementResult result) => base.CreateHitEvent(result).With((result as OsuHitCircleJudgementResult)?.CursorPositionAtHit); } diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index 2fd9f070ec..023cd1f902 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -2,9 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Taiko.Scoring { @@ -33,6 +35,22 @@ namespace osu.Game.Rulesets.Taiko.Scoring * strongScaleValue(result); } + public override ScoreRank RankFromScore(double accuracy, Dictionary results) + { + ScoreRank rank = base.RankFromScore(accuracy, results); + + switch (rank) + { + case ScoreRank.S: + case ScoreRank.X: + if (results.GetValueOrDefault(HitResult.Miss) == 0) + rank = ScoreRank.A; + break; + } + + return rank; + } + public override int GetBaseScoreForResult(HitResult result) { switch (result) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs index 435dd77120..7aa36429a7 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -96,6 +97,14 @@ namespace osu.Game.Tests.Visual.Ranking { var scoreProcessor = ruleset.CreateScoreProcessor(); + var statistics = new Dictionary + { + { HitResult.Miss, 1 }, + { HitResult.Meh, 50 }, + { HitResult.Good, 100 }, + { HitResult.Great, 300 }, + }; + return new ScoreInfo { User = new APIUser @@ -109,15 +118,9 @@ namespace osu.Game.Tests.Visual.Ranking TotalScore = 2845370, Accuracy = accuracy, MaxCombo = 999, - Rank = scoreProcessor.RankFromAccuracy(accuracy), + Rank = scoreProcessor.RankFromScore(accuracy, statistics), Date = DateTimeOffset.Now, - Statistics = - { - { HitResult.Miss, 1 }, - { HitResult.Meh, 50 }, - { HitResult.Good, 100 }, - { HitResult.Great, 300 }, - } + Statistics = statistics, }; } } diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 80e751422e..95f672f6e6 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -370,7 +370,7 @@ namespace osu.Game.Rulesets.Scoring if (rank.Value == ScoreRank.F) return; - rank.Value = RankFromAccuracy(Accuracy.Value); + rank.Value = RankFromScore(Accuracy.Value, ScoreResultCounts); foreach (var mod in Mods.Value.OfType()) rank.Value = mod.AdjustRank(Rank.Value, Accuracy.Value); } @@ -505,7 +505,7 @@ namespace osu.Game.Rulesets.Scoring /// /// Given an accuracy (0..1), return the correct . /// - public virtual ScoreRank RankFromAccuracy(double accuracy) + public virtual ScoreRank RankFromScore(double accuracy, Dictionary results) { if (accuracy == accuracy_cutoff_x) return ScoreRank.X; diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index b30fc7aee1..427b1cb7cf 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -280,7 +280,7 @@ namespace osu.Game.Scoring.Legacy { scoreInfo.Accuracy = StandardisedScoreMigrationTools.ComputeAccuracy(scoreInfo); - var rank = currentRuleset.CreateScoreProcessor().RankFromAccuracy(scoreInfo.Accuracy); + var rank = currentRuleset.CreateScoreProcessor().RankFromScore(scoreInfo.Accuracy, scoreInfo.Statistics); foreach (var mod in scoreInfo.Mods.OfType()) rank = mod.AdjustRank(rank, scoreInfo.Accuracy); From bb1eab58440ab36c0c5197335a20b72a23961219 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Jan 2024 20:25:36 +0900 Subject: [PATCH 2638/4092] Update test in line with new rank definitions always being applied --- osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index 6e7c8c3631..e56dff74c3 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -293,7 +293,7 @@ namespace osu.Game.Tests.Beatmaps.Formats } [Test] - public void AccuracyAndRankOfLazerScorePreserved() + public void AccuracyOfLazerScorePreserved() { var ruleset = new OsuRuleset().RulesetInfo; @@ -322,7 +322,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.Multiple(() => { Assert.That(decodedAfterEncode.ScoreInfo.Accuracy, Is.EqualTo((double)(199 * 300 + 30) / (200 * 300 + 30))); - Assert.That(decodedAfterEncode.ScoreInfo.Rank, Is.EqualTo(ScoreRank.SH)); + Assert.That(decodedAfterEncode.ScoreInfo.Rank, Is.EqualTo(ScoreRank.A)); }); } From 4eba3b5d7093f89f186a343c8f316054edd5552c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Jan 2024 20:48:29 +0900 Subject: [PATCH 2639/4092] Move `BackgroundDataStoreProcessor` to better namespace --- osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs | 1 + osu.Game/{ => Database}/BackgroundDataStoreProcessor.cs | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) rename osu.Game/{ => Database}/BackgroundDataStoreProcessor.cs (99%) diff --git a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs index 8b066f860f..e22cf2398b 100644 --- a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs +++ b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs @@ -9,6 +9,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Rulesets; using osu.Game.Scoring; using osu.Game.Scoring.Legacy; diff --git a/osu.Game/BackgroundDataStoreProcessor.cs b/osu.Game/Database/BackgroundDataStoreProcessor.cs similarity index 99% rename from osu.Game/BackgroundDataStoreProcessor.cs rename to osu.Game/Database/BackgroundDataStoreProcessor.cs index fc7db13d41..e462e0ccf8 100644 --- a/osu.Game/BackgroundDataStoreProcessor.cs +++ b/osu.Game/Database/BackgroundDataStoreProcessor.cs @@ -12,7 +12,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game.Beatmaps; -using osu.Game.Database; using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Overlays; @@ -22,7 +21,7 @@ using osu.Game.Scoring; using osu.Game.Scoring.Legacy; using osu.Game.Screens.Play; -namespace osu.Game +namespace osu.Game.Database { /// /// Performs background updating of data stores at startup. From 644e7d6fe6f845ccf0e3d919229d2e130dd76d1e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Jan 2024 20:55:17 +0900 Subject: [PATCH 2640/4092] Add migration --- .../Database/BackgroundDataStoreProcessor.cs | 58 +++++++++++++++++++ osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 3 +- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/BackgroundDataStoreProcessor.cs b/osu.Game/Database/BackgroundDataStoreProcessor.cs index e462e0ccf8..324d699e7c 100644 --- a/osu.Game/Database/BackgroundDataStoreProcessor.cs +++ b/osu.Game/Database/BackgroundDataStoreProcessor.cs @@ -73,6 +73,7 @@ namespace osu.Game.Database processBeatmapsWithMissingObjectCounts(); processScoresWithMissingStatistics(); convertLegacyTotalScoreToStandardised(); + upgradeScoreRanks(); }, TaskCreationOptions.LongRunning).ContinueWith(t => { if (t.Exception?.InnerException is ObjectDisposedException) @@ -354,6 +355,7 @@ namespace osu.Game.Database realmAccess.Write(r => { ScoreInfo s = r.Find(id)!; + // TODO: ensure that this is also updating rank (as it will set TotalScoreVersion to latest). StandardisedScoreMigrationTools.UpdateFromLegacy(s, beatmapManager); s.TotalScoreVersion = LegacyScoreEncoder.LATEST_VERSION; }); @@ -375,6 +377,62 @@ namespace osu.Game.Database completeNotification(notification, processedCount, scoreIds.Count, failedCount); } + private void upgradeScoreRanks() + { + Logger.Log("Querying for scores that need rank upgrades..."); + + HashSet scoreIds = realmAccess.Run(r => new HashSet( + r.All() + .Where(s => s.TotalScoreVersion < LegacyScoreEncoder.LATEST_VERSION) + .Select(s => s.ID))); + + Logger.Log($"Found {scoreIds.Count} scores which require rank upgrades."); + + if (scoreIds.Count == 0) + return; + + var notification = showProgressNotification(scoreIds.Count, "Adjusting ranks of scores", "scores now have more correct ranks"); + + int processedCount = 0; + int failedCount = 0; + + foreach (var id in scoreIds) + { + if (notification?.State == ProgressNotificationState.Cancelled) + break; + + updateNotificationProgress(notification, processedCount, scoreIds.Count); + + sleepIfRequired(); + + try + { + // Can't use async overload because we're not on the update thread. + // ReSharper disable once MethodHasAsyncOverload + realmAccess.Write(r => + { + ScoreInfo s = r.Find(id)!; + // TODO: uncomment when ready + // s.Rank = StandardisedScoreMigrationTools.ComputeRank(s, beatmapManager); + }); + + ++processedCount; + } + catch (ObjectDisposedException) + { + throw; + } + catch (Exception e) + { + Logger.Log($"Failed to update rank score {id}: {e}"); + realmAccess.Write(r => r.Find(id)!.BackgroundReprocessingFailed = true); + ++failedCount; + } + } + + completeNotification(notification, processedCount, scoreIds.Count, failedCount); + } + private void updateNotificationProgress(ProgressNotification? notification, int processedCount, int totalCount) { if (notification == null) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 389b20b5c8..c74980abb6 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -43,9 +43,10 @@ namespace osu.Game.Scoring.Legacy /// 30000012: Fix incorrect total score conversion on selected beatmaps after implementing the more correct /// method. Reconvert all scores. /// + /// 30000013: All local scores will use lazer definitions of ranks for consistency. Recalculates the rank of all scores. /// /// - public const int LATEST_VERSION = 30000012; + public const int LATEST_VERSION = 30000013; /// /// The first stable-compatible YYYYMMDD format version given to lazer usage of replays. From 83f9118b22d64e1b52f213d895cb6e959d88b842 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Jan 2024 21:31:17 +0900 Subject: [PATCH 2641/4092] Adjust results screen to handle S->A rank adjustment when misses are present --- .../Visual/Ranking/TestSceneResultsScreen.cs | 21 +++++----- .../Expanded/Accuracy/AccuracyCircle.cs | 39 +++++++++++++++++++ .../Ranking/Expanded/Accuracy/RankBadge.cs | 8 ++-- 3 files changed, 55 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index ab2e867255..866e20d063 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -21,6 +21,7 @@ using osu.Game.Online.API; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens; using osu.Game.Screens.Play; @@ -71,15 +72,16 @@ namespace osu.Game.Tests.Visual.Ranking private int onlineScoreID = 1; - [TestCase(1, ScoreRank.X)] - [TestCase(0.9999, ScoreRank.S)] - [TestCase(0.975, ScoreRank.S)] - [TestCase(0.925, ScoreRank.A)] - [TestCase(0.85, ScoreRank.B)] - [TestCase(0.75, ScoreRank.C)] - [TestCase(0.5, ScoreRank.D)] - [TestCase(0.2, ScoreRank.D)] - public void TestResultsWithPlayer(double accuracy, ScoreRank rank) + [TestCase(1, ScoreRank.X, 0)] + [TestCase(0.9999, ScoreRank.S, 0)] + [TestCase(0.975, ScoreRank.S, 0)] + [TestCase(0.975, ScoreRank.A, 1)] + [TestCase(0.925, ScoreRank.A, 5)] + [TestCase(0.85, ScoreRank.B, 9)] + [TestCase(0.75, ScoreRank.C, 11)] + [TestCase(0.5, ScoreRank.D, 21)] + [TestCase(0.2, ScoreRank.D, 51)] + public void TestResultsWithPlayer(double accuracy, ScoreRank rank, int missCount) { TestResultsScreen screen = null; @@ -91,6 +93,7 @@ namespace osu.Game.Tests.Visual.Ranking score.HitEvents = TestSceneStatisticsPanel.CreatePositionDistributedHitEvents(); score.Accuracy = accuracy; score.Rank = rank; + score.Statistics[HitResult.Miss] = missCount; return screen = createResultsScreen(score); }); diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 8cbca74466..d2632c70c9 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -21,6 +21,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Skinning; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Ranking.Expanded.Accuracy { @@ -111,6 +112,9 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy private readonly double accuracyD; private readonly bool withFlair; + private readonly bool isFailedSDueToMisses; + private RankText failedSRankText; + public AccuracyCircle(ScoreInfo score, bool withFlair = false) { this.score = score; @@ -119,10 +123,17 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy ScoreProcessor scoreProcessor = score.Ruleset.CreateInstance().CreateScoreProcessor(); accuracyX = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.X); accuracyS = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.S); + + // Some rulesets require no misses to get an S rank. + // if (score.Accuracy >= accuracyS && score.Rank == ScoreRank.A) + // accuracyS = score.Accuracy + 0.0001; + accuracyA = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.A); accuracyB = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.B); accuracyC = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.C); accuracyD = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.D); + + isFailedSDueToMisses = score.Accuracy >= accuracyS && score.Rank == ScoreRank.A; } [BackgroundDependencyLoader] @@ -249,6 +260,9 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy if (withFlair) { + if (isFailedSDueToMisses) + AddInternal(failedSRankText = new RankText(ScoreRank.S)); + AddRangeInternal(new Drawable[] { rankImpactSound = new PoolableSkinnableSample(new SampleInfo(impactSampleName)), @@ -387,6 +401,31 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy }); } } + + if (isFailedSDueToMisses) + { + const double adjust_duration = 200; + + using (BeginDelayedSequence(TEXT_APPEAR_DELAY - adjust_duration)) + { + failedSRankText.FadeIn(adjust_duration); + + using (BeginDelayedSequence(adjust_duration)) + { + failedSRankText + .FadeColour(Color4.Red, 800, Easing.Out) + .RotateTo(10, 1000, Easing.Out) + .MoveToY(100, 1000, Easing.In) + .FadeOut(800, Easing.Out); + + accuracyCircle + .FillTo(accuracyS - 0.0001, 70, Easing.OutQuint); + + badges.Single(b => b.Rank == ScoreRank.S) + .FadeOut(70, Easing.OutQuint); + } + } + } } } diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/RankBadge.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/RankBadge.cs index 7af327828e..8aea6045eb 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/RankBadge.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/RankBadge.cs @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy /// private readonly double displayPosition; - private readonly ScoreRank rank; + public readonly ScoreRank Rank; private Drawable rankContainer; private Drawable overlay; @@ -47,7 +47,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy { Accuracy = accuracy; displayPosition = position; - this.rank = rank; + Rank = rank; RelativeSizeAxes = Axes.Both; Alpha = 0; @@ -62,7 +62,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy Size = new Vector2(28, 14), Children = new[] { - new DrawableRank(rank), + new DrawableRank(Rank), overlay = new CircularContainer { RelativeSizeAxes = Axes.Both, @@ -71,7 +71,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, - Colour = OsuColour.ForRank(rank).Opacity(0.2f), + Colour = OsuColour.ForRank(Rank).Opacity(0.2f), Radius = 10, }, Child = new Box From 99d3fcc3261a5f1ec9f8270d734e5e5f93318ba9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jan 2024 13:45:55 +0100 Subject: [PATCH 2642/4092] Fix crash if ruleset components container is not present --- osu.Game/Screens/Play/HUDOverlay.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index a9fe393395..b5482f2a5b 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -172,7 +172,10 @@ namespace osu.Game.Screens.Play }, }; - hideTargets = new List { mainComponents, rulesetComponents, playfieldComponents, topRightElements }; + hideTargets = new List { mainComponents, playfieldComponents, topRightElements }; + + if (rulesetComponents != null) + hideTargets.Add(rulesetComponents); if (!alwaysShowLeaderboard) hideTargets.Add(LeaderboardFlow); From 15df6b1da16cd745e45e60dc150bed29be2f2775 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Jan 2024 21:47:38 +0900 Subject: [PATCH 2643/4092] Revert keyboard seek speed change --- osu.Game/Screens/Play/ReplayPlayer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index a26a2b9904..3c5b85662a 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -107,11 +107,11 @@ namespace osu.Game.Screens.Play return true; case GlobalAction.SeekReplayBackward: - SeekInDirection(-1); + SeekInDirection(-5); return true; case GlobalAction.SeekReplayForward: - SeekInDirection(1); + SeekInDirection(5); return true; case GlobalAction.TogglePauseReplay: From 17d05b0fdfd392423c708bd368cd02a77116d0bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jan 2024 14:23:46 +0100 Subject: [PATCH 2644/4092] Fix non-miss drawable judgements fading out instantly on triangles skin `else if` proves to be insidious once again. --- osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs index c7103cd9fd..7330f138ce 100644 --- a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs +++ b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs @@ -49,8 +49,10 @@ namespace osu.Game.Rulesets.Judgements this.ScaleTo(1.2f, 100, Easing.In); this.FadeOutFromOne(400); + return; } - else if (Result.IsMiss()) + + if (Result.IsMiss()) { this.ScaleTo(1.6f); this.ScaleTo(1, 100, Easing.In); @@ -60,9 +62,9 @@ namespace osu.Game.Rulesets.Judgements this.RotateTo(0); this.RotateTo(40, 800, Easing.InQuint); - - this.FadeOutFromOne(800); } + + this.FadeOutFromOne(800); } public Drawable? GetAboveHitObjectsProxiedContent() => null; From 2305a53a02b23185d83a9740ce876350adc711cc Mon Sep 17 00:00:00 2001 From: smallketchup82 <69545310+smallketchup82@users.noreply.github.com> Date: Mon, 22 Jan 2024 08:59:37 -0500 Subject: [PATCH 2645/4092] Remove max combo reading & remove unnecessary commas --- .../Drawables/DifficultyIconTooltip.cs | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index fe23b49346..7fe0080e89 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -28,7 +28,6 @@ namespace osu.Game.Beatmaps.Drawables private OsuSpriteText circleSize; private OsuSpriteText approachRate; private OsuSpriteText bpm; - private OsuSpriteText maxCombo; private OsuSpriteText length; private FillFlowContainer difficultyFillFlowContainer; @@ -64,12 +63,12 @@ namespace osu.Game.Beatmaps.Drawables { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 16, weight: FontWeight.Bold), + Font = OsuFont.GetFont(size: 16, weight: FontWeight.Bold) }, starRating = new StarRatingDisplay(default, StarRatingDisplaySize.Small) { Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Origin = Anchor.Centre }, // Difficulty stats difficultyFillFlowContainer = new FillFlowContainer @@ -86,26 +85,26 @@ namespace osu.Game.Beatmaps.Drawables { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 14), + Font = OsuFont.GetFont(size: 14) }, drainRate = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 14), + Font = OsuFont.GetFont(size: 14) }, approachRate = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 14), + Font = OsuFont.GetFont(size: 14) }, overallDifficulty = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 14), - }, + Font = OsuFont.GetFont(size: 14) + } } }, // Misc stats @@ -123,19 +122,13 @@ namespace osu.Game.Beatmaps.Drawables { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 14), + Font = OsuFont.GetFont(size: 14) }, bpm = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 14), - }, - maxCombo = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 14), + Font = OsuFont.GetFont(size: 14) }, } } @@ -201,7 +194,6 @@ namespace osu.Game.Beatmaps.Drawables // Misc row length.Text = "Length: " + TimeSpan.FromMilliseconds(displayedContent.BeatmapInfo.Length / rate).ToString("mm\\:ss"); bpm.Text = " BPM: " + Math.Round(bpmAdjusted, 0); - maxCombo.Text = " Max Combo: " + displayedContent.BeatmapInfo.TotalObjectCount; } public void Move(Vector2 pos) => Position = pos; From 9aa7c7f59197b9754e98e1ba5ce5e4730f4d5f4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jan 2024 15:35:03 +0100 Subject: [PATCH 2646/4092] Revert incorrect removal of `exitConfirmed` flag Removing it meant that it was _literally impossible_ to exit multiplayer. --- .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index aab7ac3280..a37314de0e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -247,13 +247,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [Resolved(canBeNull: true)] private IDialogOverlay dialogOverlay { get; set; } + private bool exitConfirmed; + public override bool OnExiting(ScreenExitEvent e) { // room has not been created yet or we're offline; exit immediately. if (client.Room == null || !IsConnected) return base.OnExiting(e); - if (dialogOverlay != null) + if (!exitConfirmed && dialogOverlay != null) { if (dialogOverlay.CurrentDialog is ConfirmDialog confirmDialog) confirmDialog.PerformOkAction(); @@ -261,6 +263,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { dialogOverlay.Push(new ConfirmDialog("Are you sure you want to leave this multiplayer match?", () => { + exitConfirmed = true; if (this.IsCurrentScreen()) this.Exit(); })); From cb8ec48717b49110bb784ae69028612d546a7a40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jan 2024 19:56:30 +0100 Subject: [PATCH 2647/4092] Make `RankFromScore()`'s dictionary param readonly Just to make sure nobody tries any "funny" business. --- osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs | 2 +- osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs | 2 +- osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs | 2 +- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index a12fe7f3e1..12a4182bf1 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -89,7 +89,7 @@ namespace osu.Game.Rulesets.Catch.Scoring return baseIncrease * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(combo_cap, combo_base)); } - public override ScoreRank RankFromScore(double accuracy, Dictionary results) + public override ScoreRank RankFromScore(double accuracy, IReadOnlyDictionary results) { if (accuracy == accuracy_cutoff_x) return ScoreRank.X; diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 0d62a6e718..4d8381cf42 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Scoring { } - public override ScoreRank RankFromScore(double accuracy, Dictionary results) + public override ScoreRank RankFromScore(double accuracy, IReadOnlyDictionary results) { ScoreRank rank = base.RankFromScore(accuracy, results); diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index 023cd1f902..441a53c05a 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Taiko.Scoring * strongScaleValue(result); } - public override ScoreRank RankFromScore(double accuracy, Dictionary results) + public override ScoreRank RankFromScore(double accuracy, IReadOnlyDictionary results) { ScoreRank rank = base.RankFromScore(accuracy, results); diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 95f672f6e6..a092829317 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -505,7 +505,7 @@ namespace osu.Game.Rulesets.Scoring /// /// Given an accuracy (0..1), return the correct . /// - public virtual ScoreRank RankFromScore(double accuracy, Dictionary results) + public virtual ScoreRank RankFromScore(double accuracy, IReadOnlyDictionary results) { if (accuracy == accuracy_cutoff_x) return ScoreRank.X; From 20ed7e13e31c4da412e1cffab581e4b7054492cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jan 2024 19:57:12 +0100 Subject: [PATCH 2648/4092] Fix back-to-front conditional in taiko processor Would be weird to degrade a score due to *no* misses wouldn't it? --- osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index 441a53c05a..7e40d575bc 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Taiko.Scoring { case ScoreRank.S: case ScoreRank.X: - if (results.GetValueOrDefault(HitResult.Miss) == 0) + if (results.GetValueOrDefault(HitResult.Miss) > 0) rank = ScoreRank.A; break; } From 45dc9de1e0d22f83ea2851bf8116c180724193da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jan 2024 19:58:11 +0100 Subject: [PATCH 2649/4092] Remove remnant of old implementation of showing "A due to misses" --- osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index d2632c70c9..be9a3a8a78 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -124,10 +124,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy accuracyX = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.X); accuracyS = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.S); - // Some rulesets require no misses to get an S rank. - // if (score.Accuracy >= accuracyS && score.Rank == ScoreRank.A) - // accuracyS = score.Accuracy + 0.0001; - accuracyA = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.A); accuracyB = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.B); accuracyC = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.C); From 81a02665b67a4f68dda204be6207acd6d9488cc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 17 Jan 2024 18:24:39 +0100 Subject: [PATCH 2650/4092] Adjust existing test to fail --- .../Beatmaps/Formats/LegacyScoreDecoderTest.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index e56dff74c3..234067ccda 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -252,7 +252,7 @@ namespace osu.Game.Tests.Beatmaps.Formats } [Test] - public void AccuracyAndRankOfStableScorePreserved() + public void AccuracyOfStableScoreRecomputed() { var memoryStream = new MemoryStream(); @@ -261,15 +261,16 @@ namespace osu.Game.Tests.Beatmaps.Formats // and we want to emulate a stable score here using (var sw = new SerializationWriter(memoryStream, true)) { - sw.Write((byte)0); // ruleset id (osu!) + sw.Write((byte)3); // ruleset id (mania). + // mania is used intentionally as it is the only ruleset wherein default accuracy calculation is changed in lazer sw.Write(20240116); // version (anything below `LegacyScoreEncoder.FIRST_LAZER_VERSION` is stable) sw.Write(string.Empty.ComputeMD5Hash()); // beatmap hash, irrelevant to this test sw.Write("username"); // irrelevant to this test sw.Write(string.Empty.ComputeMD5Hash()); // score hash, irrelevant to this test - sw.Write((ushort)198); // count300 - sw.Write((ushort)1); // count100 + sw.Write((ushort)1); // count300 + sw.Write((ushort)0); // count100 sw.Write((ushort)0); // count50 - sw.Write((ushort)0); // countGeki + sw.Write((ushort)198); // countGeki (perfects / "rainbow 300s" in mania) sw.Write((ushort)0); // countKatu sw.Write((ushort)1); // countMiss sw.Write(12345678); // total score, irrelevant to this test @@ -287,8 +288,8 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.Multiple(() => { - Assert.That(decoded.ScoreInfo.Accuracy, Is.EqualTo((double)(198 * 300 + 100) / (200 * 300))); - Assert.That(decoded.ScoreInfo.Rank, Is.EqualTo(ScoreRank.A)); + Assert.That(decoded.ScoreInfo.Accuracy, Is.EqualTo((double)(198 * 305 + 300) / (200 * 305))); + Assert.That(decoded.ScoreInfo.Rank, Is.EqualTo(ScoreRank.S)); }); } From 3f31593a196a5bb7ce93ca4fafb91f45309043be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jan 2024 20:01:18 +0100 Subject: [PATCH 2651/4092] Add another failing tests covering recomputation of ranks --- .../Formats/LegacyScoreDecoderTest.cs | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index 234067ccda..fd99b51a2f 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -293,6 +293,47 @@ namespace osu.Game.Tests.Beatmaps.Formats }); } + [Test] + public void RankOfStableScoreUsesLazerDefinitions() + { + var memoryStream = new MemoryStream(); + + // local partial implementation of legacy score encoder + // this is done half for readability, half because `LegacyScoreEncoder` forces `LATEST_VERSION` + // and we want to emulate a stable score here + using (var sw = new SerializationWriter(memoryStream, true)) + { + sw.Write((byte)0); // ruleset id (osu!) + sw.Write(20240116); // version (anything below `LegacyScoreEncoder.FIRST_LAZER_VERSION` is stable) + sw.Write(string.Empty.ComputeMD5Hash()); // beatmap hash, irrelevant to this test + sw.Write("username"); // irrelevant to this test + sw.Write(string.Empty.ComputeMD5Hash()); // score hash, irrelevant to this test + sw.Write((ushort)195); // count300 + sw.Write((ushort)1); // count100 + sw.Write((ushort)4); // count50 + sw.Write((ushort)0); // countGeki + sw.Write((ushort)0); // countKatu + sw.Write((ushort)0); // countMiss + sw.Write(12345678); // total score, irrelevant to this test + sw.Write((ushort)1000); // max combo, irrelevant to this test + sw.Write(false); // full combo, irrelevant to this test + sw.Write((int)LegacyMods.Hidden); // mods + sw.Write(string.Empty); // hp graph, irrelevant + sw.Write(DateTime.Now); // date, irrelevant + sw.Write(Array.Empty()); // replay data, irrelevant + sw.Write((long)1234); // legacy online ID, irrelevant + } + + memoryStream.Seek(0, SeekOrigin.Begin); + var decoded = new TestLegacyScoreDecoder().Parse(memoryStream); + + Assert.Multiple(() => + { + // In stable this would be an A because there are over 1% 50s. But that's not a thing in lazer. + Assert.That(decoded.ScoreInfo.Rank, Is.EqualTo(ScoreRank.S)); + }); + } + [Test] public void AccuracyOfLazerScorePreserved() { From aa8eee0796ffcc6e6a244f623f0a98ab69fb2c1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 17 Jan 2024 19:00:21 +0100 Subject: [PATCH 2652/4092] Move maximum statistics population to `LegacyScoreDecoder` --- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 68 +++++++++++++++++++ osu.Game/Scoring/ScoreImporter.cs | 67 ------------------ osu.Game/Scoring/ScoreManager.cs | 9 ++- 3 files changed, 76 insertions(+), 68 deletions(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index 427b1cb7cf..3d671d80c3 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; @@ -19,6 +20,7 @@ using osu.Game.Replays.Legacy; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Scoring; using SharpCompress.Compressors.LZMA; namespace osu.Game.Scoring.Legacy @@ -130,6 +132,8 @@ namespace osu.Game.Scoring.Legacy } } + PopulateMaximumStatistics(score.ScoreInfo, workingBeatmap); + if (score.ScoreInfo.IsLegacyScore || compressedScoreInfo == null) PopulateLegacyAccuracyAndRank(score.ScoreInfo); else @@ -170,6 +174,70 @@ namespace osu.Game.Scoring.Legacy } } + /// + /// Populates the for a given . + /// + /// The score to populate the statistics of. + /// The corresponding . + internal static void PopulateMaximumStatistics(ScoreInfo score, WorkingBeatmap workingBeatmap) + { + Debug.Assert(score.BeatmapInfo != null); + + if (score.MaximumStatistics.Select(kvp => kvp.Value).Sum() > 0) + return; + + var ruleset = score.Ruleset.Detach(); + var rulesetInstance = ruleset.CreateInstance(); + var scoreProcessor = rulesetInstance.CreateScoreProcessor(); + + Debug.Assert(rulesetInstance != null); + + // Populate the maximum statistics. + HitResult maxBasicResult = rulesetInstance.GetHitResults() + .Select(h => h.result) + .Where(h => h.IsBasic()).MaxBy(scoreProcessor.GetBaseScoreForResult); + + foreach ((HitResult result, int count) in score.Statistics) + { + switch (result) + { + case HitResult.LargeTickHit: + case HitResult.LargeTickMiss: + score.MaximumStatistics[HitResult.LargeTickHit] = score.MaximumStatistics.GetValueOrDefault(HitResult.LargeTickHit) + count; + break; + + case HitResult.SmallTickHit: + case HitResult.SmallTickMiss: + score.MaximumStatistics[HitResult.SmallTickHit] = score.MaximumStatistics.GetValueOrDefault(HitResult.SmallTickHit) + count; + break; + + case HitResult.IgnoreHit: + case HitResult.IgnoreMiss: + case HitResult.SmallBonus: + case HitResult.LargeBonus: + break; + + default: + score.MaximumStatistics[maxBasicResult] = score.MaximumStatistics.GetValueOrDefault(maxBasicResult) + count; + break; + } + } + + if (!score.IsLegacyScore) + return; + +#pragma warning disable CS0618 + // In osu! and osu!mania, some judgements affect combo but aren't stored to scores. + // A special hit result is used to pad out the combo value to match, based on the max combo from the difficulty attributes. + var calculator = rulesetInstance.CreateDifficultyCalculator(workingBeatmap); + var attributes = calculator.Calculate(score.Mods); + + int maxComboFromStatistics = score.MaximumStatistics.Where(kvp => kvp.Key.AffectsCombo()).Select(kvp => kvp.Value).DefaultIfEmpty(0).Sum(); + if (attributes.MaxCombo > maxComboFromStatistics) + score.MaximumStatistics[HitResult.LegacyComboIncrease] = attributes.MaxCombo - maxComboFromStatistics; +#pragma warning restore CS0618 + } + /// /// Populates the accuracy of a given from its contained statistics. /// diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index 8e28707107..afa522b253 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -17,7 +17,6 @@ using osu.Game.Scoring.Legacy; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Rulesets.Scoring; using Realms; namespace osu.Game.Scoring @@ -91,8 +90,6 @@ namespace osu.Game.Scoring ArgumentNullException.ThrowIfNull(model.BeatmapInfo); ArgumentNullException.ThrowIfNull(model.Ruleset); - PopulateMaximumStatistics(model); - if (string.IsNullOrEmpty(model.StatisticsJson)) model.StatisticsJson = JsonConvert.SerializeObject(model.Statistics); @@ -110,70 +107,6 @@ namespace osu.Game.Scoring } } - /// - /// Populates the for a given . - /// - /// The score to populate the statistics of. - public void PopulateMaximumStatistics(ScoreInfo score) - { - Debug.Assert(score.BeatmapInfo != null); - - if (score.MaximumStatistics.Select(kvp => kvp.Value).Sum() > 0) - return; - - var beatmap = score.BeatmapInfo!.Detach(); - var ruleset = score.Ruleset.Detach(); - var rulesetInstance = ruleset.CreateInstance(); - var scoreProcessor = rulesetInstance.CreateScoreProcessor(); - - Debug.Assert(rulesetInstance != null); - - // Populate the maximum statistics. - HitResult maxBasicResult = rulesetInstance.GetHitResults() - .Select(h => h.result) - .Where(h => h.IsBasic()).MaxBy(scoreProcessor.GetBaseScoreForResult); - - foreach ((HitResult result, int count) in score.Statistics) - { - switch (result) - { - case HitResult.LargeTickHit: - case HitResult.LargeTickMiss: - score.MaximumStatistics[HitResult.LargeTickHit] = score.MaximumStatistics.GetValueOrDefault(HitResult.LargeTickHit) + count; - break; - - case HitResult.SmallTickHit: - case HitResult.SmallTickMiss: - score.MaximumStatistics[HitResult.SmallTickHit] = score.MaximumStatistics.GetValueOrDefault(HitResult.SmallTickHit) + count; - break; - - case HitResult.IgnoreHit: - case HitResult.IgnoreMiss: - case HitResult.SmallBonus: - case HitResult.LargeBonus: - break; - - default: - score.MaximumStatistics[maxBasicResult] = score.MaximumStatistics.GetValueOrDefault(maxBasicResult) + count; - break; - } - } - - if (!score.IsLegacyScore) - return; - -#pragma warning disable CS0618 - // In osu! and osu!mania, some judgements affect combo but aren't stored to scores. - // A special hit result is used to pad out the combo value to match, based on the max combo from the difficulty attributes. - var calculator = rulesetInstance.CreateDifficultyCalculator(beatmaps().GetWorkingBeatmap(beatmap)); - var attributes = calculator.Calculate(score.Mods); - - int maxComboFromStatistics = score.MaximumStatistics.Where(kvp => kvp.Key.AffectsCombo()).Select(kvp => kvp.Value).DefaultIfEmpty(0).Sum(); - if (attributes.MaxCombo > maxComboFromStatistics) - score.MaximumStatistics[HitResult.LegacyComboIncrease] = attributes.MaxCombo - maxComboFromStatistics; -#pragma warning restore CS0618 - } - // Very naive local caching to improve performance of large score imports (where the username is usually the same for most or all scores). private readonly Dictionary usernameLookupCache = new Dictionary(); diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 02d9e0a280..1ee99e9e93 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Threading; @@ -26,6 +27,7 @@ namespace osu.Game.Scoring { public class ScoreManager : ModelManager, IModelImporter { + private readonly Func beatmaps; private readonly OsuConfigManager configManager; private readonly ScoreImporter scoreImporter; private readonly LegacyScoreExporter scoreExporter; @@ -44,6 +46,7 @@ namespace osu.Game.Scoring OsuConfigManager configManager = null) : base(storage, realm) { + this.beatmaps = beatmaps; this.configManager = configManager; scoreImporter = new ScoreImporter(rulesets, beatmaps, storage, realm, api) @@ -171,7 +174,11 @@ namespace osu.Game.Scoring /// Populates the for a given . /// /// The score to populate the statistics of. - public void PopulateMaximumStatistics(ScoreInfo score) => scoreImporter.PopulateMaximumStatistics(score); + public void PopulateMaximumStatistics(ScoreInfo score) + { + Debug.Assert(score.BeatmapInfo != null); + LegacyScoreDecoder.PopulateMaximumStatistics(score, beatmaps().GetWorkingBeatmap(score.BeatmapInfo.Detach())); + } #region Implementation of IPresentImports From 2958631c5d69fdd3af915d04f26825077213069e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 17 Jan 2024 19:14:53 +0100 Subject: [PATCH 2653/4092] Use lazer accuracy & rank implementations across the board --- osu.Game/Database/StandardisedScoreMigrationTools.cs | 11 +++++++++++ osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 10 ++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index 8c73806cb5..4221ab93f1 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -597,6 +597,17 @@ namespace osu.Game.Database return maxBaseScore == 0 ? 1 : baseScore / (double)maxBaseScore; } + public static ScoreRank ComputeRank(ScoreInfo scoreInfo) + { + Ruleset ruleset = scoreInfo.Ruleset.CreateInstance(); + var rank = ruleset.CreateScoreProcessor().RankFromScore(scoreInfo.Accuracy, scoreInfo.Statistics); + + foreach (var mod in scoreInfo.Mods.OfType()) + rank = mod.AdjustRank(rank, scoreInfo.Accuracy); + + return rank; + } + /// /// Used to populate the model using data parsed from its corresponding replay file. /// diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index 3d671d80c3..be704ca18b 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -40,7 +40,6 @@ namespace osu.Game.Scoring.Legacy }; WorkingBeatmap workingBeatmap; - byte[] compressedScoreInfo = null; using (SerializationReader sr = new SerializationReader(stream)) { @@ -109,6 +108,8 @@ namespace osu.Game.Scoring.Legacy else if (version >= 20121008) scoreInfo.LegacyOnlineID = sr.ReadInt32(); + byte[] compressedScoreInfo = null; + if (version >= 30000001) compressedScoreInfo = sr.ReadByteArray(); @@ -133,11 +134,8 @@ namespace osu.Game.Scoring.Legacy } PopulateMaximumStatistics(score.ScoreInfo, workingBeatmap); - - if (score.ScoreInfo.IsLegacyScore || compressedScoreInfo == null) - PopulateLegacyAccuracyAndRank(score.ScoreInfo); - else - populateLazerAccuracyAndRank(score.ScoreInfo); + score.ScoreInfo.Accuracy = StandardisedScoreMigrationTools.ComputeAccuracy(score.ScoreInfo); + score.ScoreInfo.Rank = StandardisedScoreMigrationTools.ComputeRank(score.ScoreInfo); // before returning for database import, we must restore the database-sourced BeatmapInfo. // if not, the clone operation in GetPlayableBeatmap will cause a dereference and subsequent database exception. From db4849442ec7b93312afc39bbc009d4b1da62d22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jan 2024 20:23:37 +0100 Subject: [PATCH 2654/4092] Unify legacy total score / accuracy / rank recomputation flows --- .../Database/BackgroundDataStoreProcessor.cs | 6 +- .../StandardisedScoreMigrationTools.cs | 60 +++++++++++-------- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 15 +---- osu.Game/Scoring/ScoreImporter.cs | 5 -- 4 files changed, 38 insertions(+), 48 deletions(-) diff --git a/osu.Game/Database/BackgroundDataStoreProcessor.cs b/osu.Game/Database/BackgroundDataStoreProcessor.cs index 324d699e7c..1e44141819 100644 --- a/osu.Game/Database/BackgroundDataStoreProcessor.cs +++ b/osu.Game/Database/BackgroundDataStoreProcessor.cs @@ -355,8 +355,7 @@ namespace osu.Game.Database realmAccess.Write(r => { ScoreInfo s = r.Find(id)!; - // TODO: ensure that this is also updating rank (as it will set TotalScoreVersion to latest). - StandardisedScoreMigrationTools.UpdateFromLegacy(s, beatmapManager); + StandardisedScoreMigrationTools.UpdateFromLegacy(s, beatmapManager.GetWorkingBeatmap(s.BeatmapInfo)); s.TotalScoreVersion = LegacyScoreEncoder.LATEST_VERSION; }); @@ -412,8 +411,7 @@ namespace osu.Game.Database realmAccess.Write(r => { ScoreInfo s = r.Find(id)!; - // TODO: uncomment when ready - // s.Rank = StandardisedScoreMigrationTools.ComputeRank(s, beatmapManager); + s.Rank = StandardisedScoreMigrationTools.ComputeRank(s); }); ++processedCount; diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index 4221ab93f1..d62d4a905a 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using osu.Framework.Logging; using osu.Game.Beatmaps; @@ -235,39 +234,49 @@ namespace osu.Game.Database /// Updates a legacy to standardised scoring. /// /// The score to update. - /// A used for lookups. - public static void UpdateFromLegacy(ScoreInfo score, BeatmapManager beatmaps) + /// The applicable for this score. + public static void UpdateFromLegacy(ScoreInfo score, WorkingBeatmap beatmap) { - score.TotalScore = convertFromLegacyTotalScore(score, beatmaps); - score.Accuracy = ComputeAccuracy(score); + var ruleset = score.Ruleset.CreateInstance(); + var scoreProcessor = ruleset.CreateScoreProcessor(); + + score.TotalScore = convertFromLegacyTotalScore(score, ruleset, beatmap); + score.Accuracy = computeAccuracy(score, scoreProcessor); + score.Rank = computeRank(score, scoreProcessor); } /// /// Updates a legacy to standardised scoring. /// + /// + /// This overload is intended for server-side flows. + /// See: https://github.com/ppy/osu-queue-score-statistics/blob/3681e92ac91c6c61922094bdbc7e92e6217dd0fc/osu.Server.Queues.ScoreStatisticsProcessor/Commands/Queue/BatchInserter.cs + /// /// The score to update. + /// The in which the score was set. /// The beatmap difficulty. /// The legacy scoring attributes for the beatmap which the score was set on. - public static void UpdateFromLegacy(ScoreInfo score, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes) + public static void UpdateFromLegacy(ScoreInfo score, Ruleset ruleset, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes) { - score.TotalScore = convertFromLegacyTotalScore(score, difficulty, attributes); - score.Accuracy = ComputeAccuracy(score); + var scoreProcessor = ruleset.CreateScoreProcessor(); + + score.TotalScore = convertFromLegacyTotalScore(score, ruleset, difficulty, attributes); + score.Accuracy = computeAccuracy(score, scoreProcessor); + score.Rank = computeRank(score, scoreProcessor); } /// /// Converts from to the new standardised scoring of . /// /// The score to convert the total score of. - /// A used for lookups. + /// The in which the score was set. + /// The applicable for this score. /// The standardised total score. - private static long convertFromLegacyTotalScore(ScoreInfo score, BeatmapManager beatmaps) + private static long convertFromLegacyTotalScore(ScoreInfo score, Ruleset ruleset, WorkingBeatmap beatmap) { if (!score.IsLegacyScore) return score.TotalScore; - WorkingBeatmap beatmap = beatmaps.GetWorkingBeatmap(score.BeatmapInfo); - Ruleset ruleset = score.Ruleset.CreateInstance(); - if (ruleset is not ILegacyRuleset legacyRuleset) return score.TotalScore; @@ -283,27 +292,28 @@ namespace osu.Game.Database ILegacyScoreSimulator sv1Simulator = legacyRuleset.CreateLegacyScoreSimulator(); LegacyScoreAttributes attributes = sv1Simulator.Simulate(beatmap, playableBeatmap); - return convertFromLegacyTotalScore(score, LegacyBeatmapConversionDifficultyInfo.FromBeatmap(beatmap.Beatmap), attributes); + return convertFromLegacyTotalScore(score, ruleset, LegacyBeatmapConversionDifficultyInfo.FromBeatmap(beatmap.Beatmap), attributes); } /// /// Converts from to the new standardised scoring of . /// /// The score to convert the total score of. + /// The in which the score was set. /// The beatmap difficulty. /// The legacy scoring attributes for the beatmap which the score was set on. /// The standardised total score. - private static long convertFromLegacyTotalScore(ScoreInfo score, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes) + private static long convertFromLegacyTotalScore(ScoreInfo score, Ruleset ruleset, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes) { if (!score.IsLegacyScore) return score.TotalScore; - Debug.Assert(score.LegacyTotalScore != null); - - Ruleset ruleset = score.Ruleset.CreateInstance(); if (ruleset is not ILegacyRuleset legacyRuleset) return score.TotalScore; + // ensure legacy total score is saved for later. + score.LegacyTotalScore = score.TotalScore; + double legacyModMultiplier = legacyRuleset.CreateLegacyScoreSimulator().GetLegacyScoreMultiplier(score.Mods, difficulty); int maximumLegacyAccuracyScore = attributes.AccuracyScore; long maximumLegacyComboScore = (long)Math.Round(attributes.ComboScore * legacyModMultiplier); @@ -584,11 +594,10 @@ namespace osu.Game.Database } } - public static double ComputeAccuracy(ScoreInfo scoreInfo) - { - Ruleset ruleset = scoreInfo.Ruleset.CreateInstance(); - ScoreProcessor scoreProcessor = ruleset.CreateScoreProcessor(); + public static double ComputeAccuracy(ScoreInfo scoreInfo) => computeAccuracy(scoreInfo, scoreInfo.Ruleset.CreateInstance().CreateScoreProcessor()); + private static double computeAccuracy(ScoreInfo scoreInfo, ScoreProcessor scoreProcessor) + { int baseScore = scoreInfo.Statistics.Where(kvp => kvp.Key.AffectsAccuracy()) .Sum(kvp => kvp.Value * scoreProcessor.GetBaseScoreForResult(kvp.Key)); int maxBaseScore = scoreInfo.MaximumStatistics.Where(kvp => kvp.Key.AffectsAccuracy()) @@ -597,10 +606,11 @@ namespace osu.Game.Database return maxBaseScore == 0 ? 1 : baseScore / (double)maxBaseScore; } - public static ScoreRank ComputeRank(ScoreInfo scoreInfo) + public static ScoreRank ComputeRank(ScoreInfo scoreInfo) => computeRank(scoreInfo, scoreInfo.Ruleset.CreateInstance().CreateScoreProcessor()); + + private static ScoreRank computeRank(ScoreInfo scoreInfo, ScoreProcessor scoreProcessor) { - Ruleset ruleset = scoreInfo.Ruleset.CreateInstance(); - var rank = ruleset.CreateScoreProcessor().RankFromScore(scoreInfo.Accuracy, scoreInfo.Statistics); + var rank = scoreProcessor.RankFromScore(scoreInfo.Accuracy, scoreInfo.Statistics); foreach (var mod in scoreInfo.Mods.OfType()) rank = mod.AdjustRank(rank, scoreInfo.Accuracy); diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index be704ca18b..c12e8b8474 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -134,8 +134,7 @@ namespace osu.Game.Scoring.Legacy } PopulateMaximumStatistics(score.ScoreInfo, workingBeatmap); - score.ScoreInfo.Accuracy = StandardisedScoreMigrationTools.ComputeAccuracy(score.ScoreInfo); - score.ScoreInfo.Rank = StandardisedScoreMigrationTools.ComputeRank(score.ScoreInfo); + StandardisedScoreMigrationTools.UpdateFromLegacy(score.ScoreInfo, workingBeatmap); // before returning for database import, we must restore the database-sourced BeatmapInfo. // if not, the clone operation in GetPlayableBeatmap will cause a dereference and subsequent database exception. @@ -342,18 +341,6 @@ namespace osu.Game.Scoring.Legacy } } - private void populateLazerAccuracyAndRank(ScoreInfo scoreInfo) - { - scoreInfo.Accuracy = StandardisedScoreMigrationTools.ComputeAccuracy(scoreInfo); - - var rank = currentRuleset.CreateScoreProcessor().RankFromScore(scoreInfo.Accuracy, scoreInfo.Statistics); - - foreach (var mod in scoreInfo.Mods.OfType()) - rank = mod.AdjustRank(rank, scoreInfo.Accuracy); - - scoreInfo.Rank = rank; - } - private void readLegacyReplay(Replay replay, StreamReader reader) { float lastTime = beatmapOffset; diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index afa522b253..768c28cc38 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -100,11 +100,6 @@ namespace osu.Game.Scoring // this requires: max combo, statistics, max statistics (where available), and mods to already be populated on the score. if (StandardisedScoreMigrationTools.ShouldMigrateToNewStandardised(model)) model.TotalScore = StandardisedScoreMigrationTools.GetNewStandardised(model); - else if (model.IsLegacyScore) - { - model.LegacyTotalScore = model.TotalScore; - StandardisedScoreMigrationTools.UpdateFromLegacy(model, beatmaps()); - } } // Very naive local caching to improve performance of large score imports (where the username is usually the same for most or all scores). From 217cbf684b249c220b1c867bca92f0fd4e4a0361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jan 2024 20:25:29 +0100 Subject: [PATCH 2655/4092] Remove superfluous recomputation of accuracy --- .../Database/StandardisedScoreMigrationTools.cs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index d62d4a905a..5fcf35b690 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -240,9 +240,10 @@ namespace osu.Game.Database var ruleset = score.Ruleset.CreateInstance(); var scoreProcessor = ruleset.CreateScoreProcessor(); - score.TotalScore = convertFromLegacyTotalScore(score, ruleset, beatmap); + // warning: ordering is important here - both total score and ranks are dependent on accuracy! score.Accuracy = computeAccuracy(score, scoreProcessor); score.Rank = computeRank(score, scoreProcessor); + score.TotalScore = convertFromLegacyTotalScore(score, ruleset, beatmap); } /// @@ -260,9 +261,10 @@ namespace osu.Game.Database { var scoreProcessor = ruleset.CreateScoreProcessor(); - score.TotalScore = convertFromLegacyTotalScore(score, ruleset, difficulty, attributes); + // warning: ordering is important here - both total score and ranks are dependent on accuracy! score.Accuracy = computeAccuracy(score, scoreProcessor); score.Rank = computeRank(score, scoreProcessor); + score.TotalScore = convertFromLegacyTotalScore(score, ruleset, difficulty, attributes); } /// @@ -484,14 +486,9 @@ namespace osu.Game.Database break; case 3: - // in the mania case accuracy actually changes between score V1 and score V2 / standardised - // (PERFECT weighting changes from 300 to 305), - // so for better accuracy recompute accuracy locally based on hit statistics and use that instead, - double scoreV2Accuracy = ComputeAccuracy(score); - convertedTotalScore = (long)Math.Round(( 850000 * comboProportion - + 150000 * Math.Pow(scoreV2Accuracy, 2 + 2 * scoreV2Accuracy) + + 150000 * Math.Pow(score.Accuracy, 2 + 2 * score.Accuracy) + bonusProportion) * modMultiplier); break; @@ -594,8 +591,6 @@ namespace osu.Game.Database } } - public static double ComputeAccuracy(ScoreInfo scoreInfo) => computeAccuracy(scoreInfo, scoreInfo.Ruleset.CreateInstance().CreateScoreProcessor()); - private static double computeAccuracy(ScoreInfo scoreInfo, ScoreProcessor scoreProcessor) { int baseScore = scoreInfo.Statistics.Where(kvp => kvp.Key.AffectsAccuracy()) From cdd6e71d0129bce737995ae888d015589f7e43f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jan 2024 20:26:30 +0100 Subject: [PATCH 2656/4092] Remove legacy computation of accuracy & ranks --- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 106 ------------------ 1 file changed, 106 deletions(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index c12e8b8474..a7374d4d4b 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -235,112 +235,6 @@ namespace osu.Game.Scoring.Legacy #pragma warning restore CS0618 } - /// - /// Populates the accuracy of a given from its contained statistics. - /// - /// - /// Legacy use only. - /// - /// The to populate. - public static void PopulateLegacyAccuracyAndRank(ScoreInfo score) - { - int countMiss = score.GetCountMiss() ?? 0; - int count50 = score.GetCount50() ?? 0; - int count100 = score.GetCount100() ?? 0; - int count300 = score.GetCount300() ?? 0; - int countGeki = score.GetCountGeki() ?? 0; - int countKatu = score.GetCountKatu() ?? 0; - - switch (score.Ruleset.OnlineID) - { - case 0: - { - int totalHits = count50 + count100 + count300 + countMiss; - score.Accuracy = totalHits > 0 ? (double)(count50 * 50 + count100 * 100 + count300 * 300) / (totalHits * 300) : 1; - - float ratio300 = (float)count300 / totalHits; - float ratio50 = (float)count50 / totalHits; - - if (ratio300 == 1) - score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.XH : ScoreRank.X; - else if (ratio300 > 0.9 && ratio50 <= 0.01 && countMiss == 0) - score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.SH : ScoreRank.S; - else if ((ratio300 > 0.8 && countMiss == 0) || ratio300 > 0.9) - score.Rank = ScoreRank.A; - else if ((ratio300 > 0.7 && countMiss == 0) || ratio300 > 0.8) - score.Rank = ScoreRank.B; - else if (ratio300 > 0.6) - score.Rank = ScoreRank.C; - else - score.Rank = ScoreRank.D; - break; - } - - case 1: - { - int totalHits = count50 + count100 + count300 + countMiss; - score.Accuracy = totalHits > 0 ? (double)(count100 * 150 + count300 * 300) / (totalHits * 300) : 1; - - float ratio300 = (float)count300 / totalHits; - float ratio50 = (float)count50 / totalHits; - - if (ratio300 == 1) - score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.XH : ScoreRank.X; - else if (ratio300 > 0.9 && ratio50 <= 0.01 && countMiss == 0) - score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.SH : ScoreRank.S; - else if ((ratio300 > 0.8 && countMiss == 0) || ratio300 > 0.9) - score.Rank = ScoreRank.A; - else if ((ratio300 > 0.7 && countMiss == 0) || ratio300 > 0.8) - score.Rank = ScoreRank.B; - else if (ratio300 > 0.6) - score.Rank = ScoreRank.C; - else - score.Rank = ScoreRank.D; - break; - } - - case 2: - { - int totalHits = count50 + count100 + count300 + countMiss + countKatu; - score.Accuracy = totalHits > 0 ? (double)(count50 + count100 + count300) / totalHits : 1; - - if (score.Accuracy == 1) - score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.XH : ScoreRank.X; - else if (score.Accuracy > 0.98) - score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.SH : ScoreRank.S; - else if (score.Accuracy > 0.94) - score.Rank = ScoreRank.A; - else if (score.Accuracy > 0.9) - score.Rank = ScoreRank.B; - else if (score.Accuracy > 0.85) - score.Rank = ScoreRank.C; - else - score.Rank = ScoreRank.D; - break; - } - - case 3: - { - int totalHits = count50 + count100 + count300 + countMiss + countGeki + countKatu; - score.Accuracy = totalHits > 0 ? (double)(count50 * 50 + count100 * 100 + countKatu * 200 + (count300 + countGeki) * 300) / (totalHits * 300) : 1; - - if (score.Accuracy == 1) - score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.XH : ScoreRank.X; - else if (score.Accuracy > 0.95) - score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.SH : ScoreRank.S; - else if (score.Accuracy > 0.9) - score.Rank = ScoreRank.A; - else if (score.Accuracy > 0.8) - score.Rank = ScoreRank.B; - else if (score.Accuracy > 0.7) - score.Rank = ScoreRank.C; - else - score.Rank = ScoreRank.D; - break; - } - } - } - private void readLegacyReplay(Replay replay, StreamReader reader) { float lastTime = beatmapOffset; From d53fb5a1652a3ae50cb89a2e8609da752a319df7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jan 2024 20:52:45 +0100 Subject: [PATCH 2657/4092] Update assertions to match expected behaviour --- .../Formats/LegacyScoreDecoderTest.cs | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index fd99b51a2f..7e3967dc95 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -10,7 +10,6 @@ using System.IO; using System.Linq; using NUnit.Framework; using osu.Framework.Extensions; -using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Legacy; @@ -23,6 +22,7 @@ using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Replays; @@ -59,14 +59,14 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(2, score.ScoreInfo.Statistics[HitResult.Great]); Assert.AreEqual(1, score.ScoreInfo.Statistics[HitResult.Good]); - Assert.AreEqual(829_931, score.ScoreInfo.TotalScore); + Assert.AreEqual(829_931, score.ScoreInfo.LegacyTotalScore); Assert.AreEqual(3, score.ScoreInfo.MaxCombo); Assert.IsTrue(score.ScoreInfo.Mods.Any(m => m is ManiaModClassic)); Assert.IsTrue(score.ScoreInfo.APIMods.Any(m => m.Acronym == "CL")); Assert.IsTrue(score.ScoreInfo.ModsJson.Contains("CL")); - Assert.IsTrue(Precision.AlmostEquals(0.8889, score.ScoreInfo.Accuracy, 0.0001)); + Assert.That((2 * 300d + 1 * 200) / (3 * 305d), Is.EqualTo(score.ScoreInfo.Accuracy).Within(0.0001)); Assert.AreEqual(ScoreRank.B, score.ScoreInfo.Rank); Assert.That(score.Replay.Frames, Is.Not.Empty); @@ -289,7 +289,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.Multiple(() => { Assert.That(decoded.ScoreInfo.Accuracy, Is.EqualTo((double)(198 * 305 + 300) / (200 * 305))); - Assert.That(decoded.ScoreInfo.Rank, Is.EqualTo(ScoreRank.S)); + Assert.That(decoded.ScoreInfo.Rank, Is.EqualTo(ScoreRank.SH)); }); } @@ -330,12 +330,12 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.Multiple(() => { // In stable this would be an A because there are over 1% 50s. But that's not a thing in lazer. - Assert.That(decoded.ScoreInfo.Rank, Is.EqualTo(ScoreRank.S)); + Assert.That(decoded.ScoreInfo.Rank, Is.EqualTo(ScoreRank.SH)); }); } [Test] - public void AccuracyOfLazerScorePreserved() + public void AccuracyRankAndTotalScoreOfLazerScorePreserved() { var ruleset = new OsuRuleset().RulesetInfo; @@ -363,6 +363,8 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.Multiple(() => { + Assert.That(decodedAfterEncode.ScoreInfo.TotalScore, Is.EqualTo(284_537)); + Assert.That(decodedAfterEncode.ScoreInfo.LegacyTotalScore, Is.Null); Assert.That(decodedAfterEncode.ScoreInfo.Accuracy, Is.EqualTo((double)(199 * 300 + 30) / (200 * 300 + 30))); Assert.That(decodedAfterEncode.ScoreInfo.Rank, Is.EqualTo(ScoreRank.A)); }); @@ -457,6 +459,12 @@ namespace osu.Game.Tests.Beatmaps.Formats Ruleset = new OsuRuleset().RulesetInfo, Difficulty = new BeatmapDifficulty(), BeatmapVersion = beatmapVersion, + }, + // needs to have at least one objects so that `StandardisedScoreMigrationTools` doesn't die + // when trying to recompute total score. + HitObjects = + { + new HitCircle() } }); } From 24be6d92ceebf8950bb7bddd44710eeaac92f10a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jan 2024 21:13:06 +0100 Subject: [PATCH 2658/4092] Expand xmldoc --- osu.Game/Database/StandardisedScoreMigrationTools.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index 5fcf35b690..bf92dbc8f5 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -231,7 +231,10 @@ namespace osu.Game.Database } /// - /// Updates a legacy to standardised scoring. + /// Updates a to standardised scoring. + /// This will recompite the score's (always), (always), + /// and (if the score comes from stable). + /// The total score from stable - if any applicable - will be stored to . /// /// The score to update. /// The applicable for this score. @@ -247,7 +250,10 @@ namespace osu.Game.Database } /// - /// Updates a legacy to standardised scoring. + /// Updates a to standardised scoring. + /// This will recompute the score's (always), (always), + /// and (if the score comes from stable). + /// The total score from stable - if any applicable - will be stored to . /// /// /// This overload is intended for server-side flows. From 069af13aaf1d1c716ce207f9c9d2eab48f013eaa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Jan 2024 05:26:16 +0900 Subject: [PATCH 2659/4092] Reduce enumerator overhead in `GameplayLeaderboard` --- osu.Game/Online/Leaderboards/Leaderboard.cs | 4 ++-- osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 67f2590ad8..0fd9597ac0 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -287,7 +287,7 @@ namespace osu.Game.Online.Leaderboards double delay = 0; - foreach (var s in scoreFlowContainer.Children) + foreach (var s in scoreFlowContainer) { using (s.BeginDelayedSequence(delay)) s.Show(); @@ -384,7 +384,7 @@ namespace osu.Game.Online.Leaderboards if (scoreFlowContainer == null) return; - foreach (var c in scoreFlowContainer.Children) + foreach (var c in scoreFlowContainer) { float topY = c.ToSpaceOfOtherDrawable(Vector2.Zero, scoreFlowContainer).Y; float bottomY = topY + LeaderboardScore.HEIGHT; diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs index d990af32e7..d2b6b834f8 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs @@ -128,7 +128,7 @@ namespace osu.Game.Screens.Play.HUD if (!scroll.IsScrolledToEnd()) fadeBottom -= panel_height; // logic is mostly shared with Leaderboard, copied here for simplicity. - foreach (var c in Flow.Children) + foreach (var c in Flow) { float topY = c.ToSpaceOfOtherDrawable(Vector2.Zero, Flow).Y; float bottomY = topY + panel_height; From 02bb506cce02df332ff4f884462bc5e24dfac949 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Jan 2024 05:32:11 +0900 Subject: [PATCH 2660/4092] Avoid using `.Children` for enumeration in other locations --- osu.Desktop.slnf | 13 ++++++------- osu.Game/Graphics/Containers/WaveContainer.cs | 4 ++-- .../Graphics/UserInterface/BreadcrumbControl.cs | 2 +- osu.Game/Graphics/UserInterface/OsuTabControl.cs | 4 ++-- osu.Game/Graphics/UserInterface/StarCounter.cs | 2 +- osu.Game/Overlays/Chat/Listing/ChannelListing.cs | 2 +- osu.Game/Overlays/OverlayStreamControl.cs | 4 ++-- osu.Game/Screens/Menu/ButtonSystem.cs | 2 +- .../HUD/JudgementCounter/JudgementCounterDisplay.cs | 2 +- .../Select/Carousel/DrawableCarouselBeatmapSet.cs | 2 +- 10 files changed, 18 insertions(+), 19 deletions(-) diff --git a/osu.Desktop.slnf b/osu.Desktop.slnf index 503e5935f5..606988ccdf 100644 --- a/osu.Desktop.slnf +++ b/osu.Desktop.slnf @@ -16,15 +16,14 @@ "osu.Game.Tournament.Tests\\osu.Game.Tournament.Tests.csproj", "osu.Game.Tournament\\osu.Game.Tournament.csproj", "osu.Game\\osu.Game.csproj", - - "Templates\\Rulesets\\ruleset-empty\\osu.Game.Rulesets.EmptyFreeform\\osu.Game.Rulesets.EmptyFreeform.csproj", "Templates\\Rulesets\\ruleset-empty\\osu.Game.Rulesets.EmptyFreeform.Tests\\osu.Game.Rulesets.EmptyFreeform.Tests.csproj", - "Templates\\Rulesets\\ruleset-example\\osu.Game.Rulesets.Pippidon\\osu.Game.Rulesets.Pippidon.csproj", + "Templates\\Rulesets\\ruleset-empty\\osu.Game.Rulesets.EmptyFreeform\\osu.Game.Rulesets.EmptyFreeform.csproj", "Templates\\Rulesets\\ruleset-example\\osu.Game.Rulesets.Pippidon.Tests\\osu.Game.Rulesets.Pippidon.Tests.csproj", - "Templates\\Rulesets\\ruleset-scrolling-empty\\osu.Game.Rulesets.EmptyScrolling\\osu.Game.Rulesets.EmptyScrolling.csproj", + "Templates\\Rulesets\\ruleset-example\\osu.Game.Rulesets.Pippidon\\osu.Game.Rulesets.Pippidon.csproj", "Templates\\Rulesets\\ruleset-scrolling-empty\\osu.Game.Rulesets.EmptyScrolling.Tests\\osu.Game.Rulesets.EmptyScrolling.Tests.csproj", - "Templates\\Rulesets\\ruleset-scrolling-example\\osu.Game.Rulesets.Pippidon\\osu.Game.Rulesets.Pippidon.csproj", - "Templates\\Rulesets\\ruleset-scrolling-example\\osu.Game.Rulesets.Pippidon.Tests\\osu.Game.Rulesets.Pippidon.Tests.csproj" + "Templates\\Rulesets\\ruleset-scrolling-empty\\osu.Game.Rulesets.EmptyScrolling\\osu.Game.Rulesets.EmptyScrolling.csproj", + "Templates\\Rulesets\\ruleset-scrolling-example\\osu.Game.Rulesets.Pippidon.Tests\\osu.Game.Rulesets.Pippidon.Tests.csproj", + "Templates\\Rulesets\\ruleset-scrolling-example\\osu.Game.Rulesets.Pippidon\\osu.Game.Rulesets.Pippidon.csproj" ] } -} +} \ No newline at end of file diff --git a/osu.Game/Graphics/Containers/WaveContainer.cs b/osu.Game/Graphics/Containers/WaveContainer.cs index 5abc66d2ac..2ae4dc5a76 100644 --- a/osu.Game/Graphics/Containers/WaveContainer.cs +++ b/osu.Game/Graphics/Containers/WaveContainer.cs @@ -122,7 +122,7 @@ namespace osu.Game.Graphics.Containers protected override void PopIn() { - foreach (var w in wavesContainer.Children) + foreach (var w in wavesContainer) w.Show(); contentContainer.MoveToY(0, APPEAR_DURATION, Easing.OutQuint); @@ -132,7 +132,7 @@ namespace osu.Game.Graphics.Containers protected override void PopOut() { - foreach (var w in wavesContainer.Children) + foreach (var w in wavesContainer) w.Hide(); contentContainer.MoveToY(2, DISAPPEAR_DURATION, Easing.In); diff --git a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs index fc0770d896..af4b3849af 100644 --- a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs +++ b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs @@ -33,7 +33,7 @@ namespace osu.Game.Graphics.UserInterface Current.ValueChanged += index => { - foreach (var t in TabContainer.Children.OfType()) + foreach (var t in TabContainer.OfType()) { int tIndex = TabContainer.IndexOf(t); int tabIndex = TabContainer.IndexOf(TabMap[index.NewValue]); diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index 05309760e7..c260c92b43 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -37,7 +37,7 @@ namespace osu.Game.Graphics.UserInterface if (Dropdown is IHasAccentColour dropdown) dropdown.AccentColour = value; - foreach (var i in TabContainer.Children.OfType()) + foreach (var i in TabContainer.OfType()) i.AccentColour = value; } } @@ -48,7 +48,7 @@ namespace osu.Game.Graphics.UserInterface protected override TabItem CreateTabItem(T value) => new OsuTabItem(value); - protected virtual float StripWidth => TabContainer.Children.Sum(c => c.IsPresent ? c.DrawWidth + TabContainer.Spacing.X : 0) - TabContainer.Spacing.X; + protected virtual float StripWidth => TabContainer.Sum(c => c.IsPresent ? c.DrawWidth + TabContainer.Spacing.X : 0) - TabContainer.Spacing.X; /// /// Whether entries should be automatically populated if is an type. diff --git a/osu.Game/Graphics/UserInterface/StarCounter.cs b/osu.Game/Graphics/UserInterface/StarCounter.cs index fe986b275e..720f479216 100644 --- a/osu.Game/Graphics/UserInterface/StarCounter.cs +++ b/osu.Game/Graphics/UserInterface/StarCounter.cs @@ -101,7 +101,7 @@ namespace osu.Game.Graphics.UserInterface public void StopAnimation() { animate(current); - foreach (var star in stars.Children) + foreach (var star in stars) star.FinishTransforms(true); } diff --git a/osu.Game/Overlays/Chat/Listing/ChannelListing.cs b/osu.Game/Overlays/Chat/Listing/ChannelListing.cs index 809ea2f11d..1699dcceb0 100644 --- a/osu.Game/Overlays/Chat/Listing/ChannelListing.cs +++ b/osu.Game/Overlays/Chat/Listing/ChannelListing.cs @@ -63,7 +63,7 @@ namespace osu.Game.Overlays.Chat.Listing flow.ChildrenEnumerable = newChannels.Where(c => c.Type == ChannelType.Public) .Select(c => new ChannelListingItem(c)); - foreach (var item in flow.Children) + foreach (var item in flow) { item.OnRequestJoin += channel => OnRequestJoin?.Invoke(channel); item.OnRequestLeave += channel => OnRequestLeave?.Invoke(channel); diff --git a/osu.Game/Overlays/OverlayStreamControl.cs b/osu.Game/Overlays/OverlayStreamControl.cs index 84de384fb5..bc37a57cab 100644 --- a/osu.Game/Overlays/OverlayStreamControl.cs +++ b/osu.Game/Overlays/OverlayStreamControl.cs @@ -41,7 +41,7 @@ namespace osu.Game.Overlays protected override bool OnHover(HoverEvent e) { - foreach (var streamBadge in TabContainer.Children.OfType>()) + foreach (var streamBadge in TabContainer.OfType>()) streamBadge.UserHoveringArea = true; return base.OnHover(e); @@ -49,7 +49,7 @@ namespace osu.Game.Overlays protected override void OnHoverLost(HoverLostEvent e) { - foreach (var streamBadge in TabContainer.Children.OfType>()) + foreach (var streamBadge in TabContainer.OfType>()) streamBadge.UserHoveringArea = false; base.OnHoverLost(e); diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index b2b3fbd626..d742d2377f 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -343,7 +343,7 @@ namespace osu.Game.Screens.Menu { buttonArea.ButtonSystemState = state; - foreach (var b in buttonArea.Children.OfType()) + foreach (var b in buttonArea.OfType()) b.ButtonSystemState = state; } diff --git a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounterDisplay.cs b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounterDisplay.cs index 326be55222..25e5464205 100644 --- a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounterDisplay.cs +++ b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounterDisplay.cs @@ -64,7 +64,7 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter CounterFlow.Direction = convertedDirection; - foreach (var counter in CounterFlow.Children) + foreach (var counter in CounterFlow) counter.Direction.Value = convertedDirection; }, true); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 369db37e63..bd659d7423 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -242,7 +242,7 @@ namespace osu.Game.Screens.Select.Carousel bool isSelected = Item?.State.Value == CarouselItemState.Selected; - foreach (var panel in beatmapContainer.Children) + foreach (var panel in beatmapContainer) { Debug.Assert(panel.Item != null); From fdd499a4859bcd682dc09156b3dcef1a8a32bcb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jan 2024 21:34:28 +0100 Subject: [PATCH 2661/4092] Fix rank background processing not working (and not bumping score version) --- osu.Game/Database/BackgroundDataStoreProcessor.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Database/BackgroundDataStoreProcessor.cs b/osu.Game/Database/BackgroundDataStoreProcessor.cs index 1e44141819..27d19f86c8 100644 --- a/osu.Game/Database/BackgroundDataStoreProcessor.cs +++ b/osu.Game/Database/BackgroundDataStoreProcessor.cs @@ -383,6 +383,7 @@ namespace osu.Game.Database HashSet scoreIds = realmAccess.Run(r => new HashSet( r.All() .Where(s => s.TotalScoreVersion < LegacyScoreEncoder.LATEST_VERSION) + .AsEnumerable() // need to materialise here as realm cannot support `.Select()`. .Select(s => s.ID))); Logger.Log($"Found {scoreIds.Count} scores which require rank upgrades."); @@ -412,6 +413,7 @@ namespace osu.Game.Database { ScoreInfo s = r.Find(id)!; s.Rank = StandardisedScoreMigrationTools.ComputeRank(s); + s.TotalScoreVersion = LegacyScoreEncoder.LATEST_VERSION; }); ++processedCount; From 4d253ebf3c98cc8b923695c7a94b4360143d7d34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jan 2024 22:01:03 +0100 Subject: [PATCH 2662/4092] Remove redundant assertion It does look pretty dumb to be checking if something is `null` *after calling an instance method on it*... --- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index a7374d4d4b..63bd822205 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -187,8 +187,6 @@ namespace osu.Game.Scoring.Legacy var rulesetInstance = ruleset.CreateInstance(); var scoreProcessor = rulesetInstance.CreateScoreProcessor(); - Debug.Assert(rulesetInstance != null); - // Populate the maximum statistics. HitResult maxBasicResult = rulesetInstance.GetHitResults() .Select(h => h.result) From da992ccc55e6712b18b3b91b8864e7c44238f9ca Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 23 Jan 2024 04:54:27 +0300 Subject: [PATCH 2663/4092] Implement per-axis triangles clamping --- .../Skinning/Default/TrianglesPiece.cs | 3 +- .../TestSceneTrianglesBackground.cs | 8 +++-- .../TestSceneTrianglesV2Background.cs | 8 +++-- osu.Game/Graphics/Backgrounds/Triangles.cs | 36 +++++++++++-------- osu.Game/Graphics/Backgrounds/TrianglesV2.cs | 36 +++++++++++-------- .../Graphics/UserInterface/DialogButton.cs | 2 +- osu.Game/Overlays/Mods/ModSelectColumn.cs | 4 +-- 7 files changed, 59 insertions(+), 38 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs index 566176505d..512ac8ee3e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.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.Framework.Graphics; using osu.Game.Graphics.Backgrounds; namespace osu.Game.Rulesets.Osu.Skinning.Default @@ -15,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { TriangleScale = 1.2f; HideAlphaDiscrepancies = false; - ClampToDrawable = false; + ClampAxes = Axes.None; } protected override void Update() diff --git a/osu.Game.Tests/Visual/Background/TestSceneTrianglesBackground.cs b/osu.Game.Tests/Visual/Background/TestSceneTrianglesBackground.cs index 4733b7f92f..dd4c372193 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneTrianglesBackground.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneTrianglesBackground.cs @@ -29,7 +29,8 @@ namespace osu.Game.Tests.Visual.Background ColourDark = Color4.Gray, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(0.9f) + Size = new Vector2(0.9f), + ClampAxes = Axes.None } }; } @@ -40,7 +41,10 @@ namespace osu.Game.Tests.Visual.Background AddSliderStep("Triangle scale", 0f, 10f, 1f, s => triangles.TriangleScale = s); AddSliderStep("Seed", 0, 1000, 0, s => triangles.Reset(s)); - AddToggleStep("ClampToDrawable", c => triangles.ClampToDrawable = c); + AddStep("ClampAxes X", () => triangles.ClampAxes = Axes.X); + AddStep("ClampAxes Y", () => triangles.ClampAxes = Axes.Y); + AddStep("ClampAxes Both", () => triangles.ClampAxes = Axes.Both); + AddStep("ClampAxes None", () => triangles.ClampAxes = Axes.None); } } } diff --git a/osu.Game.Tests/Visual/Background/TestSceneTrianglesV2Background.cs b/osu.Game.Tests/Visual/Background/TestSceneTrianglesV2Background.cs index 71d1baddc8..4713852c0b 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneTrianglesV2Background.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneTrianglesV2Background.cs @@ -86,7 +86,8 @@ namespace osu.Game.Tests.Visual.Background { Anchor = Anchor.Centre, Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, + ClampAxes = Axes.None } } }, @@ -128,7 +129,10 @@ namespace osu.Game.Tests.Visual.Background AddStep("White colour", () => box.Colour = triangles.Colour = maskedTriangles.Colour = Color4.White); AddStep("Vertical gradient", () => box.Colour = triangles.Colour = maskedTriangles.Colour = ColourInfo.GradientVertical(Color4.White, Color4.Red)); AddStep("Horizontal gradient", () => box.Colour = triangles.Colour = maskedTriangles.Colour = ColourInfo.GradientHorizontal(Color4.White, Color4.Red)); - AddToggleStep("ClampToDrawable", c => maskedTriangles.ClampToDrawable = c); + AddStep("ClampAxes X", () => maskedTriangles.ClampAxes = Axes.X); + AddStep("ClampAxes Y", () => maskedTriangles.ClampAxes = Axes.Y); + AddStep("ClampAxes Both", () => maskedTriangles.ClampAxes = Axes.Both); + AddStep("ClampAxes None", () => maskedTriangles.ClampAxes = Axes.None); } } } diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index d7bfeb7731..e877915fac 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -77,10 +77,10 @@ namespace osu.Game.Graphics.Backgrounds } /// - /// If enabled, only the portion of triangles that falls within this 's - /// shape is drawn to the screen. Default is true. + /// Controls on which the portion of triangles that falls within this 's + /// shape is drawn to the screen. Default is Axes.Both. /// - public bool ClampToDrawable { get; set; } = true; + public Axes ClampAxes { get; set; } = Axes.Both; /// /// Whether we should drop-off alpha values of triangles more quickly to improve @@ -257,7 +257,7 @@ namespace osu.Game.Graphics.Backgrounds private IShader shader; private Texture texture; - private bool clamp; + private Axes clampAxes; private readonly List parts = new List(); private readonly Vector2 triangleSize = new Vector2(1f, equilateral_triangle_ratio) * triangle_size; @@ -276,7 +276,7 @@ namespace osu.Game.Graphics.Backgrounds shader = Source.shader; texture = Source.texture; size = Source.DrawSize; - clamp = Source.ClampToDrawable; + clampAxes = Source.ClampAxes; parts.Clear(); parts.AddRange(Source.parts); @@ -306,7 +306,7 @@ namespace osu.Game.Graphics.Backgrounds Vector2 topLeft = particle.Position - new Vector2(relativeSize.X * 0.5f, 0f); - Quad triangleQuad = clamp ? clampToDrawable(topLeft, relativeSize) : new Quad(topLeft.X, topLeft.Y, relativeSize.X, relativeSize.Y); + Quad triangleQuad = getClampedQuad(clampAxes, topLeft, relativeSize); var drawQuad = new Quad( Vector2Extensions.Transform(triangleQuad.TopLeft * size, DrawInfo.Matrix), @@ -331,17 +331,23 @@ namespace osu.Game.Graphics.Backgrounds shader.Unbind(); } - private static Quad clampToDrawable(Vector2 topLeft, Vector2 size) + private static Quad getClampedQuad(Axes clampAxes, Vector2 topLeft, Vector2 size) { - float leftClamped = Math.Clamp(topLeft.X, 0f, 1f); - float topClamped = Math.Clamp(topLeft.Y, 0f, 1f); + Vector2 clampedTopLeft = topLeft; - return new Quad( - leftClamped, - topClamped, - Math.Clamp(topLeft.X + size.X, 0f, 1f) - leftClamped, - Math.Clamp(topLeft.Y + size.Y, 0f, 1f) - topClamped - ); + if (clampAxes == Axes.X || clampAxes == Axes.Both) + { + clampedTopLeft.X = Math.Clamp(topLeft.X, 0f, 1f); + size.X = Math.Clamp(topLeft.X + size.X, 0f, 1f) - clampedTopLeft.X; + } + + if (clampAxes == Axes.Y || clampAxes == Axes.Both) + { + clampedTopLeft.Y = Math.Clamp(topLeft.Y, 0f, 1f); + size.Y = Math.Clamp(topLeft.Y + size.Y, 0f, 1f) - clampedTopLeft.Y; + } + + return new Quad(clampedTopLeft.X, clampedTopLeft.Y, size.X, size.Y); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Graphics/Backgrounds/TrianglesV2.cs b/osu.Game/Graphics/Backgrounds/TrianglesV2.cs index f723b1b358..706b05f5ad 100644 --- a/osu.Game/Graphics/Backgrounds/TrianglesV2.cs +++ b/osu.Game/Graphics/Backgrounds/TrianglesV2.cs @@ -33,10 +33,10 @@ namespace osu.Game.Graphics.Backgrounds protected virtual bool CreateNewTriangles => true; /// - /// If enabled, only the portion of triangles that falls within this 's - /// shape is drawn to the screen. Default is true. + /// Controls on which the portion of triangles that falls within this 's + /// shape is drawn to the screen. Default is Axes.Both. /// - public bool ClampToDrawable { get; set; } = true; + public Axes ClampAxes { get; set; } = Axes.Both; private readonly BindableFloat spawnRatio = new BindableFloat(1f); @@ -193,7 +193,7 @@ namespace osu.Game.Graphics.Backgrounds private Vector2 size; private float thickness; private float texelSize; - private bool clamp; + private Axes clampAxes; public TrianglesDrawNode(TrianglesV2 source) : base(source) @@ -208,7 +208,7 @@ namespace osu.Game.Graphics.Backgrounds texture = Source.texture; size = Source.DrawSize; thickness = Source.Thickness; - clamp = Source.ClampToDrawable; + clampAxes = Source.ClampAxes; Quad triangleQuad = new Quad( Vector2Extensions.Transform(Vector2.Zero, DrawInfo.Matrix), @@ -248,7 +248,7 @@ namespace osu.Game.Graphics.Backgrounds { Vector2 topLeft = particle.Position - new Vector2(relativeSize.X * 0.5f, 0f); - Quad triangleQuad = clamp ? clampToDrawable(topLeft, relativeSize) : new Quad(topLeft.X, topLeft.Y, relativeSize.X, relativeSize.Y); + Quad triangleQuad = getClampedQuad(clampAxes, topLeft, relativeSize); var drawQuad = new Quad( Vector2Extensions.Transform(triangleQuad.TopLeft * size, DrawInfo.Matrix), @@ -270,17 +270,23 @@ namespace osu.Game.Graphics.Backgrounds shader.Unbind(); } - private static Quad clampToDrawable(Vector2 topLeft, Vector2 size) + private static Quad getClampedQuad(Axes clampAxes, Vector2 topLeft, Vector2 size) { - float leftClamped = Math.Clamp(topLeft.X, 0f, 1f); - float topClamped = Math.Clamp(topLeft.Y, 0f, 1f); + Vector2 clampedTopLeft = topLeft; - return new Quad( - leftClamped, - topClamped, - Math.Clamp(topLeft.X + size.X, 0f, 1f) - leftClamped, - Math.Clamp(topLeft.Y + size.Y, 0f, 1f) - topClamped - ); + if (clampAxes == Axes.X || clampAxes == Axes.Both) + { + clampedTopLeft.X = Math.Clamp(topLeft.X, 0f, 1f); + size.X = Math.Clamp(topLeft.X + size.X, 0f, 1f) - clampedTopLeft.X; + } + + if (clampAxes == Axes.Y || clampAxes == Axes.Both) + { + clampedTopLeft.Y = Math.Clamp(topLeft.Y, 0f, 1f); + size.Y = Math.Clamp(topLeft.Y + size.Y, 0f, 1f) - clampedTopLeft.Y; + } + + return new Quad(clampedTopLeft.X, clampedTopLeft.Y, size.X, size.Y); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs index 10aca017f1..c39f41bf72 100644 --- a/osu.Game/Graphics/UserInterface/DialogButton.cs +++ b/osu.Game/Graphics/UserInterface/DialogButton.cs @@ -150,7 +150,7 @@ namespace osu.Game.Graphics.UserInterface TriangleScale = 4, ColourDark = OsuColour.Gray(0.88f), Shear = new Vector2(-0.2f, 0), - ClampToDrawable = false + ClampAxes = Axes.Y }, }, }, diff --git a/osu.Game/Overlays/Mods/ModSelectColumn.cs b/osu.Game/Overlays/Mods/ModSelectColumn.cs index 05454159c7..b2c5a054e1 100644 --- a/osu.Game/Overlays/Mods/ModSelectColumn.cs +++ b/osu.Game/Overlays/Mods/ModSelectColumn.cs @@ -34,7 +34,7 @@ namespace osu.Game.Overlays.Mods var hsv = new Colour4(value.R, value.G, value.B, 1f).ToHSV(); var trianglesColour = Colour4.FromHSV(hsv.X, hsv.Y + 0.2f, hsv.Z - 0.1f); - triangles.Colour = ColourInfo.GradientVertical(trianglesColour, trianglesColour.MultiplyAlpha(0f)); + triangles.Colour = ColourInfo.GradientVertical(trianglesColour, value); } } @@ -95,7 +95,7 @@ namespace osu.Game.Overlays.Mods Height = header_height, Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), Velocity = 0.7f, - ClampToDrawable = false + ClampAxes = Axes.Y }, headerText = new OsuTextFlowContainer(t => { From c8cd7ebe6c66b57cbcfef2d841deacb60fdbd755 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Jan 2024 17:29:31 +0900 Subject: [PATCH 2664/4092] Fix now playing beatmap backgrounds not being correctly centred https://github.com/ppy/osu/discussions/26679 --- osu.Game/Overlays/NowPlayingOverlay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 7ec52364d4..ab99370603 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -405,6 +405,8 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Both, Colour = OsuColour.Gray(150), FillMode = FillMode.Fill, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, }, new Box { From 0cf90677e616793ca3ea7e7da1c1a74b7b80e7b2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Jan 2024 19:16:55 +0900 Subject: [PATCH 2665/4092] Apply more correct visual offset adjustment Co-authored-by: Walavouchey <36758269+Walavouchey@users.noreply.github.com> --- osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index be9a3a8a78..e7e54d0fae 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -415,7 +415,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy .FadeOut(800, Easing.Out); accuracyCircle - .FillTo(accuracyS - 0.0001, 70, Easing.OutQuint); + .FillTo(accuracyS - NOTCH_WIDTH_PERCENTAGE / 2 - visual_alignment_offset, 70, Easing.OutQuint); badges.Single(b => b.Rank == ScoreRank.S) .FadeOut(70, Easing.OutQuint); From cb87d6ce500bd8070b1a0ad69537a6f643e1aa67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 23 Jan 2024 11:05:29 +0100 Subject: [PATCH 2666/4092] Move transferal of `LegacyTotalScore` back to original spot This subtle detail was messing with server-side score import flows. Server-side, legacy total score will *already* be in `LegacyTotalScore` from the start, and `TotalScore` will be zero until recomputed via `StandardisedScoreMigrationTools.UpdateFromLegacy()` - so in that context, attempting to move it across is incorrect. --- osu.Game/Database/StandardisedScoreMigrationTools.cs | 6 +++--- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index bf92dbc8f5..403e73ab77 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Logging; using osu.Game.Beatmaps; @@ -316,12 +317,11 @@ namespace osu.Game.Database if (!score.IsLegacyScore) return score.TotalScore; + Debug.Assert(score.LegacyTotalScore != null); + if (ruleset is not ILegacyRuleset legacyRuleset) return score.TotalScore; - // ensure legacy total score is saved for later. - score.LegacyTotalScore = score.TotalScore; - double legacyModMultiplier = legacyRuleset.CreateLegacyScoreSimulator().GetLegacyScoreMultiplier(score.Mods, difficulty); int maximumLegacyAccuracyScore = attributes.AccuracyScore; long maximumLegacyComboScore = (long)Math.Round(attributes.ComboScore * legacyModMultiplier); diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index 63bd822205..e51a95798b 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -134,6 +134,10 @@ namespace osu.Game.Scoring.Legacy } PopulateMaximumStatistics(score.ScoreInfo, workingBeatmap); + + if (score.ScoreInfo.IsLegacyScore) + score.ScoreInfo.LegacyTotalScore = score.ScoreInfo.TotalScore; + StandardisedScoreMigrationTools.UpdateFromLegacy(score.ScoreInfo, workingBeatmap); // before returning for database import, we must restore the database-sourced BeatmapInfo. From 0b5be3c40cd0ccb2c1ab190c719d44d6d4521268 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 23 Jan 2024 12:53:16 +0100 Subject: [PATCH 2667/4092] Remove outdated test Non-legacy scores *are* subject to upgrades again - albeit it is *rank* upgrades that they are subject to. --- .../BackgroundDataStoreProcessorTests.cs | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs index e22cf2398b..e960995c45 100644 --- a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs +++ b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs @@ -211,31 +211,6 @@ namespace osu.Game.Tests.Database AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(30000001)); } - [Test] - public void TestNonLegacyScoreNotSubjectToUpgrades() - { - ScoreInfo scoreInfo = null!; - TestBackgroundDataStoreProcessor processor = null!; - - AddStep("Add score which requires upgrade (and has beatmap)", () => - { - Realm.Write(r => - { - r.Add(scoreInfo = new ScoreInfo(ruleset: r.All().First(), beatmap: r.All().First()) - { - TotalScoreVersion = 30000005, - LegacyTotalScore = 123456, - }); - }); - }); - - AddStep("Run background processor", () => Add(processor = new TestBackgroundDataStoreProcessor())); - AddUntilStep("Wait for completion", () => processor.Completed); - - AddAssert("Score not marked as failed", () => Realm.Run(r => r.Find(scoreInfo.ID)!.BackgroundReprocessingFailed), () => Is.False); - AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(30000005)); - } - public partial class TestBackgroundDataStoreProcessor : BackgroundDataStoreProcessor { protected override int TimeToSleepDuringGameplay => 10; From 6c169e3156af665059df1a526495eee5cec6aa6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 23 Jan 2024 12:59:35 +0100 Subject: [PATCH 2668/4092] Do not reprocess ranks for custom rulesets Chances are that because we've broken rank API, it would utterly fail for all custom rulesets anyhow. --- osu.Game/Database/BackgroundDataStoreProcessor.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/BackgroundDataStoreProcessor.cs b/osu.Game/Database/BackgroundDataStoreProcessor.cs index 27d19f86c8..be0c83bdb3 100644 --- a/osu.Game/Database/BackgroundDataStoreProcessor.cs +++ b/osu.Game/Database/BackgroundDataStoreProcessor.cs @@ -383,7 +383,10 @@ namespace osu.Game.Database HashSet scoreIds = realmAccess.Run(r => new HashSet( r.All() .Where(s => s.TotalScoreVersion < LegacyScoreEncoder.LATEST_VERSION) - .AsEnumerable() // need to materialise here as realm cannot support `.Select()`. + .AsEnumerable() + // must be done after materialisation, as realm doesn't support + // filtering on nested property predicates or projection via `.Select()` + .Where(s => s.Ruleset.IsLegacyRuleset()) .Select(s => s.ID))); Logger.Log($"Found {scoreIds.Count} scores which require rank upgrades."); From d0d09a86578854c30e802083dd4d977a967db1e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 23 Jan 2024 16:55:00 +0100 Subject: [PATCH 2669/4092] Fix login overlay test not clearing second auth factor --- osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs b/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs index 0c5f5a321c..0c0edca995 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs @@ -89,6 +89,12 @@ namespace osu.Game.Tests.Visual.Menus AddStep("enter password", () => loginOverlay.ChildrenOfType().First().Text = "password"); AddStep("submit", () => loginOverlay.ChildrenOfType().First(b => b.Text.ToString() == "Sign in").TriggerClick()); + assertAPIState(APIState.RequiresSecondFactorAuth); + AddUntilStep("wait for second factor auth form", () => loginOverlay.ChildrenOfType().SingleOrDefault(), () => Is.Not.Null); + + AddStep("enter code", () => loginOverlay.ChildrenOfType().First().Text = "88800088"); + assertAPIState(APIState.Online); + AddStep("click on flag", () => { InputManager.MoveMouseTo(loginOverlay.ChildrenOfType().First()); From d7e957087ebb348df86692c7ab525f27df8bf4b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 23 Jan 2024 16:56:39 +0100 Subject: [PATCH 2670/4092] Fix account creation overlay test not clearing second auth factor --- .../Visual/Online/TestSceneAccountCreationOverlay.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs index 0f920643f0..79fb063ea9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs @@ -10,6 +10,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.AccountCreation; @@ -59,7 +60,11 @@ namespace osu.Game.Tests.Visual.Online AddStep("click button", () => accountCreation.ChildrenOfType().Single().TriggerClick()); AddUntilStep("warning screen is present", () => accountCreation.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddStep("log back in", () => API.Login("dummy", "password")); + AddStep("log back in", () => + { + API.Login("dummy", "password"); + ((DummyAPIAccess)API).AuthenticateSecondFactor("abcdefgh"); + }); AddUntilStep("overlay is hidden", () => accountCreation.State.Value == Visibility.Hidden); } } From 5d26afc53175678fc241379dcde7d286b72e91c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 23 Jan 2024 17:00:07 +0100 Subject: [PATCH 2671/4092] Fix `OsuGameTestScene` not clearing second auth factor --- osu.Game/Tests/Visual/OsuGameTestScene.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Tests/Visual/OsuGameTestScene.cs b/osu.Game/Tests/Visual/OsuGameTestScene.cs index 947305439e..6069fe4fb0 100644 --- a/osu.Game/Tests/Visual/OsuGameTestScene.cs +++ b/osu.Game/Tests/Visual/OsuGameTestScene.cs @@ -178,6 +178,7 @@ namespace osu.Game.Tests.Visual LocalConfig.SetValue(OsuSetting.ShowFirstRunSetup, false); API.Login("Rhythm Champion", "osu!"); + ((DummyAPIAccess)API).AuthenticateSecondFactor("abcdefgh"); Dependencies.Get().SetValue(Static.MutedAudioNotificationShownOnce, true); From 0d7834af5fc5f4f8dc7f67903cced787b39416df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 23 Jan 2024 18:04:41 +0100 Subject: [PATCH 2672/4092] Ensure all remaining test usages of `IAPIAccess.Login()` also authenticate with second factor --- .../Gameplay/TestScenePlayerLocalScoreImport.cs | 6 +++++- .../Visual/Menus/TestSceneToolbarUserButton.cs | 12 ++++++++---- .../Visual/Online/TestSceneCommentActions.cs | 1 + .../Visual/Online/TestSceneFavouriteButton.cs | 13 +++++++++++-- .../Visual/Online/TestSceneSoloStatisticsWatcher.cs | 6 +++++- .../Visual/Online/TestSceneUserProfileOverlay.cs | 12 ++++++++++-- osu.Game.Tests/Visual/Online/TestSceneVotePill.cs | 7 ++++++- .../Visual/UserInterface/TestSceneCommentEditor.cs | 6 +++++- 8 files changed, 51 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index fafd1330cc..22422a685e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -184,7 +184,11 @@ namespace osu.Game.Tests.Visual.Gameplay CreateTest(); AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); - AddStep("log back in", () => API.Login("username", "password")); + AddStep("log back in", () => + { + API.Login("username", "password"); + ((DummyAPIAccess)API).AuthenticateSecondFactor("abcdefgh"); + }); AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime())); diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs index 2bdfc8959d..f0506ed35c 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs @@ -16,6 +16,8 @@ namespace osu.Game.Tests.Visual.Menus [TestFixture] public partial class TestSceneToolbarUserButton : OsuManualInputManagerTestScene { + private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; + public TestSceneToolbarUserButton() { Container mainContainer; @@ -69,18 +71,20 @@ namespace osu.Game.Tests.Visual.Menus [Test] public void TestLoginLogout() { - AddStep("Log out", () => ((DummyAPIAccess)API).Logout()); - AddStep("Log in", () => ((DummyAPIAccess)API).Login("wang", "jang")); + AddStep("Log out", () => dummyAPI.Logout()); + AddStep("Log in", () => dummyAPI.Login("wang", "jang")); + AddStep("Authenticate via second factor", () => dummyAPI.AuthenticateSecondFactor("abcdefgh")); } [Test] public void TestStates() { - AddStep("Log in", () => ((DummyAPIAccess)API).Login("wang", "jang")); + AddStep("Log in", () => dummyAPI.Login("wang", "jang")); + AddStep("Authenticate via second factor", () => dummyAPI.AuthenticateSecondFactor("abcdefgh")); foreach (var state in Enum.GetValues()) { - AddStep($"Change state to {state}", () => ((DummyAPIAccess)API).SetState(state)); + AddStep($"Change state to {state}", () => dummyAPI.SetState(state)); } } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentActions.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentActions.cs index 10fdffb8e1..f47322b9e0 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCommentActions.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentActions.cs @@ -67,6 +67,7 @@ namespace osu.Game.Tests.Visual.Online Schedule(() => { API.Login("test", "test"); + dummyAPI.AuthenticateSecondFactor("abcdefgh"); Child = commentsContainer = new CommentsContainer(); }); } diff --git a/osu.Game.Tests/Visual/Online/TestSceneFavouriteButton.cs b/osu.Game.Tests/Visual/Online/TestSceneFavouriteButton.cs index 3954fd5cff..0acf8336e3 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneFavouriteButton.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneFavouriteButton.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Testing; +using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapSet.Buttons; using osuTK; @@ -34,14 +35,22 @@ namespace osu.Game.Tests.Visual.Online AddStep("set valid beatmap", () => favourite.BeatmapSet.Value = new APIBeatmapSet { OnlineID = 88 }); AddStep("log out", () => API.Logout()); checkEnabled(false); - AddStep("log in", () => API.Login("test", "test")); + AddStep("log in", () => + { + API.Login("test", "test"); + ((DummyAPIAccess)API).AuthenticateSecondFactor("abcdefgh"); + }); checkEnabled(true); } [Test] public void TestBeatmapChange() { - AddStep("log in", () => API.Login("test", "test")); + AddStep("log in", () => + { + API.Login("test", "test"); + ((DummyAPIAccess)API).AuthenticateSecondFactor("abcdefgh"); + }); AddStep("set valid beatmap", () => favourite.BeatmapSet.Value = new APIBeatmapSet { OnlineID = 88 }); checkEnabled(true); AddStep("set invalid beatmap", () => favourite.BeatmapSet.Value = new APIBeatmapSet()); diff --git a/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs b/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs index be819afa3e..bb78cf9fd4 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs @@ -177,7 +177,11 @@ namespace osu.Game.Tests.Visual.Online AddWaitStep("wait a bit", 5); AddAssert("update not received", () => update == null); - AddStep("log in user", () => dummyAPI.Login("user", "password")); + AddStep("log in user", () => + { + dummyAPI.Login("user", "password"); + dummyAPI.AuthenticateSecondFactor("abcdefgh"); + }); } [Test] diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index 1375689075..bc8f75d4ce 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -52,7 +52,11 @@ namespace osu.Game.Tests.Visual.Online AddStep("show user", () => profile.ShowUser(new APIUser { Id = 1 })); AddToggleStep("toggle visibility", visible => profile.State.Value = visible ? Visibility.Visible : Visibility.Hidden); AddStep("log out", () => dummyAPI.Logout()); - AddStep("log back in", () => dummyAPI.Login("username", "password")); + AddStep("log back in", () => + { + dummyAPI.Login("username", "password"); + dummyAPI.AuthenticateSecondFactor("abcdefgh"); + }); } [Test] @@ -98,7 +102,11 @@ namespace osu.Game.Tests.Visual.Online }); AddStep("logout", () => dummyAPI.Logout()); AddStep("show user", () => profile.ShowUser(new APIUser { Id = 1 })); - AddStep("login", () => dummyAPI.Login("username", "password")); + AddStep("login", () => + { + dummyAPI.Login("username", "password"); + dummyAPI.AuthenticateSecondFactor("abcdefgh"); + }); AddWaitStep("wait some", 3); AddStep("complete request", () => pendingRequest.TriggerSuccess(TEST_USER)); } diff --git a/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs b/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs index ce1a9ac6a7..488902c417 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs @@ -11,6 +11,7 @@ using osu.Framework.Allocation; using osu.Game.Overlays; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Containers; +using osu.Game.Online.API; namespace osu.Game.Tests.Visual.Online { @@ -72,7 +73,11 @@ namespace osu.Game.Tests.Visual.Online AddAssert("Login overlay is visible", () => login.State.Value == Visibility.Visible); } - private void logIn() => API.Login("localUser", "password"); + private void logIn() + { + API.Login("localUser", "password"); + ((DummyAPIAccess)API).AuthenticateSecondFactor("abcdefgh"); + } private Comment getUserComment() => new Comment { diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneCommentEditor.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneCommentEditor.cs index b17024ae8f..e1d40882be 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneCommentEditor.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneCommentEditor.cs @@ -125,7 +125,11 @@ namespace osu.Game.Tests.Visual.UserInterface assertLoggedOutState(); // moving from logged out -> logged in - AddStep("log back in", () => dummyAPI.Login("username", "password")); + AddStep("log back in", () => + { + dummyAPI.Login("username", "password"); + dummyAPI.AuthenticateSecondFactor("abcdefgh"); + }); assertLoggedInState(); } From 7c140408ea20fdc8122fabe92fdff93501d16d2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 24 Jan 2024 13:53:40 +0100 Subject: [PATCH 2673/4092] Add request structures for verification endpoints --- .../ReissueVerificationCodeRequest.cs | 22 ++++++++++++++ .../API/Requests/VerifySessionRequest.cs | 30 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 osu.Game/Online/API/Requests/ReissueVerificationCodeRequest.cs create mode 100644 osu.Game/Online/API/Requests/VerifySessionRequest.cs diff --git a/osu.Game/Online/API/Requests/ReissueVerificationCodeRequest.cs b/osu.Game/Online/API/Requests/ReissueVerificationCodeRequest.cs new file mode 100644 index 0000000000..f2a3cf0a16 --- /dev/null +++ b/osu.Game/Online/API/Requests/ReissueVerificationCodeRequest.cs @@ -0,0 +1,22 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Net.Http; +using osu.Framework.IO.Network; + +namespace osu.Game.Online.API.Requests +{ + public class ReissueVerificationCodeRequest : APIRequest + { + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + + req.Method = HttpMethod.Post; + + return req; + } + + protected override string Target => @"session/verify/reissue"; + } +} diff --git a/osu.Game/Online/API/Requests/VerifySessionRequest.cs b/osu.Game/Online/API/Requests/VerifySessionRequest.cs new file mode 100644 index 0000000000..b39ec5b79a --- /dev/null +++ b/osu.Game/Online/API/Requests/VerifySessionRequest.cs @@ -0,0 +1,30 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Net.Http; +using osu.Framework.IO.Network; + +namespace osu.Game.Online.API.Requests +{ + public class VerifySessionRequest : APIRequest + { + public readonly string VerificationKey; + + public VerifySessionRequest(string verificationKey) + { + VerificationKey = verificationKey; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + + req.Method = HttpMethod.Post; + req.AddParameter(@"verification_key", VerificationKey); + + return req; + } + + protected override string Target => @"session/verify"; + } +} From 7b4721565702a71aa35e4ede372b3468bd72fb52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 24 Jan 2024 14:22:57 +0100 Subject: [PATCH 2674/4092] Split `/me` request from `/users` requests Them being together always bothered me and led to the abject failure that is `APIUser` and its sprawl. Now that I'm about to add a flag that is unique to `/me` for verification purposes, I'm not repeating the errors of the past by adding yet another flag to `APIUser` that is never present outside of a single usage context. --- osu.Game/Online/API/APIAccess.cs | 2 +- osu.Game/Online/API/Requests/GetMeRequest.cs | 24 +++++++++++++++++++ .../Online/API/Requests/GetUserRequest.cs | 19 ++++----------- .../Online/API/Requests/Responses/APIMe.cs | 9 +++++++ 4 files changed, 39 insertions(+), 15 deletions(-) create mode 100644 osu.Game/Online/API/Requests/GetMeRequest.cs create mode 100644 osu.Game/Online/API/Requests/Responses/APIMe.cs diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 51472dab30..42cf39414e 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -266,7 +266,7 @@ namespace osu.Game.Online.API } } - var userReq = new GetUserRequest(); + var userReq = new GetMeRequest(); userReq.Failure += ex => { if (ex is APIException) diff --git a/osu.Game/Online/API/Requests/GetMeRequest.cs b/osu.Game/Online/API/Requests/GetMeRequest.cs new file mode 100644 index 0000000000..aab7d7b2f1 --- /dev/null +++ b/osu.Game/Online/API/Requests/GetMeRequest.cs @@ -0,0 +1,24 @@ +// 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.Online.API.Requests.Responses; +using osu.Game.Rulesets; + +namespace osu.Game.Online.API.Requests +{ + public class GetMeRequest : APIRequest + { + public readonly IRulesetInfo? Ruleset; + + /// + /// Gets the currently logged-in user. + /// + /// The ruleset to get the user's info for. + public GetMeRequest(IRulesetInfo? ruleset = null) + { + Ruleset = ruleset; + } + + protected override string Target => $@"me/{Ruleset?.ShortName}"; + } +} diff --git a/osu.Game/Online/API/Requests/GetUserRequest.cs b/osu.Game/Online/API/Requests/GetUserRequest.cs index 7dcf75950e..90d3268e75 100644 --- a/osu.Game/Online/API/Requests/GetUserRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRequest.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.Online.API.Requests.Responses; using osu.Game.Rulesets; @@ -11,24 +9,17 @@ namespace osu.Game.Online.API.Requests public class GetUserRequest : APIRequest { public readonly string Lookup; - public readonly IRulesetInfo Ruleset; + public readonly IRulesetInfo? Ruleset; private readonly LookupType lookupType; - /// - /// Gets the currently logged-in user. - /// - public GetUserRequest() - { - } - /// /// Gets a user from their ID. /// /// The user to get. /// The ruleset to get the user's info for. - public GetUserRequest(long? userId = null, IRulesetInfo ruleset = null) + public GetUserRequest(long? userId = null, IRulesetInfo? ruleset = null) { - Lookup = userId.ToString(); + Lookup = userId.ToString()!; lookupType = LookupType.Id; Ruleset = ruleset; } @@ -38,14 +29,14 @@ namespace osu.Game.Online.API.Requests /// /// The user to get. /// The ruleset to get the user's info for. - public GetUserRequest(string username = null, IRulesetInfo ruleset = null) + public GetUserRequest(string username, IRulesetInfo? ruleset = null) { Lookup = username; lookupType = LookupType.Username; Ruleset = ruleset; } - protected override string Target => Lookup != null ? $@"users/{Lookup}/{Ruleset?.ShortName}?key={lookupType.ToString().ToLowerInvariant()}" : $@"me/{Ruleset?.ShortName}"; + protected override string Target => $@"users/{Lookup}/{Ruleset?.ShortName}?key={lookupType.ToString().ToLowerInvariant()}"; private enum LookupType { diff --git a/osu.Game/Online/API/Requests/Responses/APIMe.cs b/osu.Game/Online/API/Requests/Responses/APIMe.cs new file mode 100644 index 0000000000..e4c4e8ce4f --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIMe.cs @@ -0,0 +1,9 @@ +// 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.Online.API.Requests.Responses +{ + public class APIMe : APIUser + { + } +} From ddc2bbeb9bd6ca5fef3050ff690b53e2ac01cd9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 24 Jan 2024 14:24:33 +0100 Subject: [PATCH 2675/4092] Add `session_verified` attribute to `/me` response --- osu.Game/Online/API/Requests/Responses/APIMe.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Online/API/Requests/Responses/APIMe.cs b/osu.Game/Online/API/Requests/Responses/APIMe.cs index e4c4e8ce4f..3cbddbe5e7 100644 --- a/osu.Game/Online/API/Requests/Responses/APIMe.cs +++ b/osu.Game/Online/API/Requests/Responses/APIMe.cs @@ -1,9 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using Newtonsoft.Json; + namespace osu.Game.Online.API.Requests.Responses { public class APIMe : APIUser { + [JsonProperty("session_verified")] + public bool SessionVerified { get; set; } } } From d2775680e66129c34297246ce047914d2b94fd90 Mon Sep 17 00:00:00 2001 From: Chandler Stowell Date: Wed, 24 Jan 2024 13:13:45 -0500 Subject: [PATCH 2676/4092] use stack to pass action state when applying hit results this removes closure allocations --- .../Drawables/DrawableEmptyFreeformHitObject.cs | 2 +- .../Drawables/DrawablePippidonHitObject.cs | 7 ++++++- .../Drawables/DrawableEmptyScrollingHitObject.cs | 2 +- .../Drawables/DrawablePippidonHitObject.cs | 7 ++++++- .../Objects/Drawables/DrawableCatchHitObject.cs | 7 ++++++- .../Objects/Drawables/DrawableHoldNote.cs | 2 +- .../Objects/Drawables/DrawableHoldNoteBody.cs | 2 +- .../Objects/Drawables/DrawableManiaHitObject.cs | 2 +- .../Objects/Drawables/DrawableNote.cs | 4 ++-- .../Objects/Drawables/DrawableHitCircle.cs | 15 ++++++++------- .../Objects/Drawables/DrawableOsuHitObject.cs | 4 ++-- .../Objects/Drawables/DrawableSlider.cs | 13 ++++++++----- .../Objects/Drawables/DrawableSpinner.cs | 12 ++++++------ .../Objects/Drawables/DrawableSpinnerTick.cs | 2 +- .../Objects/Drawables/DrawableDrumRoll.cs | 7 +++++-- .../Objects/Drawables/DrawableDrumRollTick.cs | 11 +++++++---- .../Objects/Drawables/DrawableFlyingHit.cs | 2 +- .../Objects/Drawables/DrawableHit.cs | 12 ++++++------ .../Objects/Drawables/DrawableStrongNestedHit.cs | 2 +- .../Objects/Drawables/DrawableSwell.cs | 8 ++++++-- .../Objects/Drawables/DrawableSwellTick.cs | 5 ++++- .../Objects/Drawables/DrawableHitObject.cs | 13 +++++++++++-- 22 files changed, 91 insertions(+), 50 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/Drawables/DrawableEmptyFreeformHitObject.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/Drawables/DrawableEmptyFreeformHitObject.cs index 744e207b57..e8f511bc4b 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/Drawables/DrawableEmptyFreeformHitObject.cs +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/Drawables/DrawableEmptyFreeformHitObject.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.EmptyFreeform.Objects.Drawables { if (timeOffset >= 0) // todo: implement judgement logic - ApplyResult(r => r.Type = HitResult.Perfect); + ApplyResult(static r => r.Type = HitResult.Perfect); } protected override void UpdateHitStateTransforms(ArmedState state) diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs index c5ada4288d..a8bb57ba18 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs @@ -49,7 +49,12 @@ namespace osu.Game.Rulesets.Pippidon.Objects.Drawables protected override void CheckForResult(bool userTriggered, double timeOffset) { if (timeOffset >= 0) - ApplyResult(r => r.Type = IsHovered ? HitResult.Perfect : HitResult.Miss); + { + ApplyResult(static (r, isHovered) => + { + r.Type = isHovered ? HitResult.Perfect : HitResult.Miss; + }, IsHovered); + } } protected override double InitialLifetimeOffset => time_preempt; diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/Drawables/DrawableEmptyScrollingHitObject.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/Drawables/DrawableEmptyScrollingHitObject.cs index a3c3b89105..070a802aea 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/Drawables/DrawableEmptyScrollingHitObject.cs +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/Drawables/DrawableEmptyScrollingHitObject.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.EmptyScrolling.Objects.Drawables { if (timeOffset >= 0) // todo: implement judgement logic - ApplyResult(r => r.Type = HitResult.Perfect); + ApplyResult(static r => r.Type = HitResult.Perfect); } protected override void UpdateHitStateTransforms(ArmedState state) diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs index d198fa81cb..9983ec20b0 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs @@ -49,7 +49,12 @@ namespace osu.Game.Rulesets.Pippidon.Objects.Drawables protected override void CheckForResult(bool userTriggered, double timeOffset) { if (timeOffset >= 0) - ApplyResult(r => r.Type = currentLane.Value == HitObject.Lane ? HitResult.Perfect : HitResult.Miss); + { + ApplyResult(static (r, pippidonHitObject) => + { + r.Type = pippidonHitObject.currentLane.Value == pippidonHitObject.HitObject.Lane ? HitResult.Perfect : HitResult.Miss; + }, this); + } } protected override void UpdateHitStateTransforms(ArmedState state) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs index 7f8c17861d..5a921f36f5 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs @@ -63,7 +63,12 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables if (CheckPosition == null) return; if (timeOffset >= 0 && Result != null) - ApplyResult(r => r.Type = CheckPosition.Invoke(HitObject) ? r.Judgement.MaxResult : r.Judgement.MinResult); + { + ApplyResult(static (r, state) => + { + r.Type = state.CheckPosition.Invoke(state.HitObject) ? r.Judgement.MaxResult : r.Judgement.MinResult; + }, this); + } } protected override void UpdateHitStateTransforms(ArmedState state) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 3490d50871..e5056d5167 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -265,7 +265,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (Tail.AllJudged) { if (Tail.IsHit) - ApplyResult(r => r.Type = r.Judgement.MaxResult); + ApplyResult(static r => r.Type = r.Judgement.MaxResult); else MissForcefully(); } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteBody.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteBody.cs index 1b2efbafdf..317da0580c 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteBody.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteBody.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { if (AllJudged) return; - ApplyResult(r => r.Type = hit ? r.Judgement.MaxResult : r.Judgement.MinResult); + ApplyResult(static (r, hit) => r.Type = hit ? r.Judgement.MaxResult : r.Judgement.MinResult, hit); } } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index 8498fd36de..dea0817869 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// /// Causes this to get missed, disregarding all conditions in implementations of . /// - public virtual void MissForcefully() => ApplyResult(r => r.Type = r.Judgement.MinResult); + public virtual void MissForcefully() => ApplyResult(static r => r.Type = r.Judgement.MinResult); } public abstract partial class DrawableManiaHitObject : DrawableManiaHitObject diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 680009bc4c..985007f905 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -89,7 +89,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (!userTriggered) { if (!HitObject.HitWindows.CanBeHit(timeOffset)) - ApplyResult(r => r.Type = r.Judgement.MinResult); + ApplyResult(static r => r.Type = r.Judgement.MinResult); return; } @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables result = GetCappedResult(result); - ApplyResult(r => r.Type = result); + ApplyResult(static (r, result) => r.Type = result, result); } /// diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 0d665cad0c..8284229d82 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -155,7 +155,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (!userTriggered) { if (!HitObject.HitWindows.CanBeHit(timeOffset)) - ApplyResult(r => r.Type = r.Judgement.MinResult); + ApplyResult(static r => r.Type = r.Judgement.MinResult); return; } @@ -169,19 +169,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (result == HitResult.None || clickAction != ClickAction.Hit) return; - ApplyResult(r => + ApplyResult(static (r, state) => { + var (hitCircle, hitResult) = state; var circleResult = (OsuHitCircleJudgementResult)r; // Todo: This should also consider misses, but they're a little more interesting to handle, since we don't necessarily know the position at the time of a miss. - if (result.IsHit()) + if (hitResult.IsHit()) { - var localMousePosition = ToLocalSpace(inputManager.CurrentState.Mouse.Position); - circleResult.CursorPositionAtHit = HitObject.StackedPosition + (localMousePosition - DrawSize / 2); + var localMousePosition = hitCircle.ToLocalSpace(hitCircle.inputManager.CurrentState.Mouse.Position); + circleResult.CursorPositionAtHit = hitCircle.HitObject.StackedPosition + (localMousePosition - hitCircle.DrawSize / 2); } - circleResult.Type = result; - }); + circleResult.Type = hitResult; + }, (this, result)); } /// diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 5b379a0d90..cc06d009c9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -100,12 +100,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables /// /// Causes this to get hit, disregarding all conditions in implementations of . /// - public void HitForcefully() => ApplyResult(r => r.Type = r.Judgement.MaxResult); + public void HitForcefully() => ApplyResult(static r => r.Type = r.Judgement.MaxResult); /// /// Causes this to get missed, disregarding all conditions in implementations of . /// - public void MissForcefully() => ApplyResult(r => r.Type = r.Judgement.MinResult); + public void MissForcefully() => ApplyResult(static r => r.Type = r.Judgement.MinResult); private RectangleF parentScreenSpaceRectangle => ((DrawableOsuHitObject)ParentHitObject)?.parentScreenSpaceRectangle ?? Parent!.ScreenSpaceDrawQuad.AABBFloat; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index baec200107..3c298cc6af 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -292,10 +292,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (HitObject.ClassicSliderBehaviour) { // Classic behaviour means a slider is judged proportionally to the number of nested hitobjects hit. This is the classic osu!stable scoring. - ApplyResult(r => + ApplyResult(static (r, nestedHitObjects) => { - int totalTicks = NestedHitObjects.Count; - int hitTicks = NestedHitObjects.Count(h => h.IsHit); + int totalTicks = nestedHitObjects.Count; + int hitTicks = nestedHitObjects.Count(h => h.IsHit); if (hitTicks == totalTicks) r.Type = HitResult.Great; @@ -306,13 +306,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables double hitFraction = (double)hitTicks / totalTicks; r.Type = hitFraction >= 0.5 ? HitResult.Ok : HitResult.Meh; } - }); + }, NestedHitObjects); } else { // If only the nested hitobjects are judged, then the slider's own judgement is ignored for scoring purposes. // But the slider needs to still be judged with a reasonable hit/miss result for visual purposes (hit/miss transforms, etc). - ApplyResult(r => r.Type = NestedHitObjects.Any(h => h.Result.IsHit) ? r.Judgement.MaxResult : r.Judgement.MinResult); + ApplyResult(static (r, nestedHitObjects) => + { + r.Type = nestedHitObjects.Any(h => h.Result.IsHit) ? r.Judgement.MaxResult : r.Judgement.MinResult; + }, NestedHitObjects); } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index bf4b07eaab..d21d02c8ce 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -258,17 +258,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables foreach (var tick in ticks.Where(t => !t.Result.HasResult)) tick.TriggerResult(false); - ApplyResult(r => + ApplyResult(static (r, spinner) => { - if (Progress >= 1) + if (spinner.Progress >= 1) r.Type = HitResult.Great; - else if (Progress > .9) + else if (spinner.Progress > .9) r.Type = HitResult.Ok; - else if (Progress > .75) + else if (spinner.Progress > .75) r.Type = HitResult.Meh; - else if (Time.Current >= HitObject.EndTime) + else if (spinner.Time.Current >= spinner.HitObject.EndTime) r.Type = r.Judgement.MinResult; - }); + }, this); } protected override void Update() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs index 5b55533edd..1c3ff29118 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs @@ -35,6 +35,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables /// Apply a judgement result. /// /// Whether this tick was reached. - internal void TriggerResult(bool hit) => ApplyResult(r => r.Type = hit ? r.Judgement.MaxResult : r.Judgement.MinResult); + internal void TriggerResult(bool hit) => ApplyResult(static (r, hit) => r.Type = hit ? r.Judgement.MaxResult : r.Judgement.MinResult, hit); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 2bf0c04adf..d3fe363857 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (timeOffset < 0) return; - ApplyResult(r => r.Type = r.Judgement.MaxResult); + ApplyResult(static r => r.Type = r.Judgement.MaxResult); } protected override void UpdateHitStateTransforms(ArmedState state) @@ -192,7 +192,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!ParentHitObject.Judged) return; - ApplyResult(r => r.Type = ParentHitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult); + ApplyResult(static (r, parentHitObject) => + { + r.Type = parentHitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult; + }, ParentHitObject); } public override bool OnPressed(KeyBindingPressEvent e) => false; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs index c900165d34..de9a3a31c5 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs @@ -49,14 +49,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!userTriggered) { if (timeOffset > HitObject.HitWindow) - ApplyResult(r => r.Type = r.Judgement.MinResult); + ApplyResult(static r => r.Type = r.Judgement.MinResult); return; } if (Math.Abs(timeOffset) > HitObject.HitWindow) return; - ApplyResult(r => r.Type = r.Judgement.MaxResult); + ApplyResult(static r => r.Type = r.Judgement.MaxResult); } public override void OnKilled() @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables base.OnKilled(); if (Time.Current > HitObject.GetEndTime() && !Judged) - ApplyResult(r => r.Type = r.Judgement.MinResult); + ApplyResult(static r => r.Type = r.Judgement.MinResult); } protected override void UpdateHitStateTransforms(ArmedState state) @@ -105,7 +105,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!ParentHitObject.Judged) return; - ApplyResult(r => r.Type = ParentHitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult); + ApplyResult(static (r, parentHitObject) => + { + r.Type = parentHitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult; + }, ParentHitObject); } public override bool OnPressed(KeyBindingPressEvent e) => false; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingHit.cs index a039ce3407..1332b9e950 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingHit.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected override void LoadComplete() { base.LoadComplete(); - ApplyResult(r => r.Type = r.Judgement.MaxResult); + ApplyResult(static r => r.Type = r.Judgement.MaxResult); } protected override void LoadSamples() diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 1ef426854e..c3bd76bf81 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!userTriggered) { if (!HitObject.HitWindows.CanBeHit(timeOffset)) - ApplyResult(r => r.Type = r.Judgement.MinResult); + ApplyResult(static r => r.Type = r.Judgement.MinResult); return; } @@ -108,9 +108,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables return; if (!validActionPressed) - ApplyResult(r => r.Type = r.Judgement.MinResult); + ApplyResult(static r => r.Type = r.Judgement.MinResult); else - ApplyResult(r => r.Type = result); + ApplyResult(static (r, result) => r.Type = result, result); } public override bool OnPressed(KeyBindingPressEvent e) @@ -209,19 +209,19 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!ParentHitObject.Result.IsHit) { - ApplyResult(r => r.Type = r.Judgement.MinResult); + ApplyResult(static r => r.Type = r.Judgement.MinResult); return; } if (!userTriggered) { if (timeOffset - ParentHitObject.Result.TimeOffset > SECOND_HIT_WINDOW) - ApplyResult(r => r.Type = r.Judgement.MinResult); + ApplyResult(static r => r.Type = r.Judgement.MinResult); return; } if (Math.Abs(timeOffset - ParentHitObject.Result.TimeOffset) <= SECOND_HIT_WINDOW) - ApplyResult(r => r.Type = r.Judgement.MaxResult); + ApplyResult(static r => r.Type = r.Judgement.MaxResult); } public override bool OnPressed(KeyBindingPressEvent e) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs index 724d59edcd..4080c14066 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables // it can happen that the hit window of the nested strong hit extends past the lifetime of the parent object. // this is a safety to prevent such cases from causing the nested hit to never be judged and as such prevent gameplay from completing. if (!Judged && Time.Current > ParentHitObject?.HitObject.GetEndTime()) - ApplyResult(r => r.Type = r.Judgement.MinResult); + ApplyResult(static r => r.Type = r.Judgement.MinResult); } } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index e4a083f218..d48b78283b 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -206,7 +206,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables expandingRing.ScaleTo(1f + Math.Min(target_ring_scale - 1f, (target_ring_scale - 1f) * completion * 1.3f), 260, Easing.OutQuint); if (numHits == HitObject.RequiredHits) - ApplyResult(r => r.Type = r.Judgement.MaxResult); + ApplyResult(static r => r.Type = r.Judgement.MaxResult); } else { @@ -227,7 +227,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables tick.TriggerResult(false); } - ApplyResult(r => r.Type = numHits == HitObject.RequiredHits ? r.Judgement.MaxResult : r.Judgement.MinResult); + ApplyResult(static (r, state) => + { + var (numHits, hitObject) = state; + r.Type = numHits == hitObject.RequiredHits ? r.Judgement.MaxResult : r.Judgement.MinResult; + }, (numHits, HitObject)); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs index 3a5c006962..ad1d09bc7b 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs @@ -30,7 +30,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public void TriggerResult(bool hit) { HitObject.StartTime = Time.Current; - ApplyResult(r => r.Type = hit ? r.Judgement.MaxResult : r.Judgement.MinResult); + ApplyResult(static (r, hit) => + { + r.Type = hit ? r.Judgement.MaxResult : r.Judgement.MinResult; + }, hit); } protected override void CheckForResult(bool userTriggered, double timeOffset) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index bce28361cb..9acd7b3c0f 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -687,12 +687,14 @@ namespace osu.Game.Rulesets.Objects.Drawables /// the of the . /// /// The callback that applies changes to the . - protected void ApplyResult(Action application) + /// The state passed to the callback. + /// The type of the state information that is passed to the callback method. + protected void ApplyResult(Action application, TState state) { if (Result.HasResult) throw new InvalidOperationException("Cannot apply result on a hitobject that already has a result."); - application?.Invoke(Result); + application?.Invoke(Result, state); if (!Result.HasResult) throw new InvalidOperationException($"{GetType().ReadableName()} applied a {nameof(JudgementResult)} but did not update {nameof(JudgementResult.Type)}."); @@ -714,6 +716,13 @@ namespace osu.Game.Rulesets.Objects.Drawables OnNewResult?.Invoke(this, Result); } + /// + /// Applies the of this , notifying responders such as + /// the of the . + /// + /// The callback that applies changes to the . + protected void ApplyResult(Action application) => ApplyResult((r, _) => application?.Invoke(r), null); + /// /// Processes this , checking if a scoring result has occurred. /// From 445a7450e015458aaf071643ba024ca55da57ebe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 24 Jan 2024 15:57:40 +0100 Subject: [PATCH 2677/4092] Implement verification from within client --- osu.Game/Online/API/APIAccess.cs | 61 +++++++++++++++++--------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 42cf39414e..389816fcf8 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -230,8 +230,6 @@ namespace osu.Game.Online.API try { authentication.AuthenticateWithLogin(ProvidedUsername, password); - state.Value = APIState.RequiresSecondFactorAuth; - return; } catch (Exception e) { @@ -244,28 +242,6 @@ namespace osu.Game.Online.API } } - if (state.Value == APIState.RequiresSecondFactorAuth) - { - if (string.IsNullOrEmpty(SecondFactorCode)) - return; - - state.Value = APIState.Connecting; - LastLoginError = null; - - // TODO: use code to ensure second factor authentication completed. - Thread.Sleep(1000); - bool success = SecondFactorCode == "00000000"; - SecondFactorCode = null; - - if (!success) - { - state.Value = APIState.RequiresSecondFactorAuth; - LastLoginError = new InvalidOperationException("Second factor auth failed"); - SecondFactorCode = null; - return; - } - } - var userReq = new GetMeRequest(); userReq.Failure += ex => { @@ -285,14 +261,13 @@ namespace osu.Game.Online.API state.Value = APIState.Failing; } }; - userReq.Success += user => + userReq.Success += me => { - user.Status.Value = configStatus.Value ?? UserStatus.Online; + me.Status.Value = configStatus.Value ?? UserStatus.Online; - setLocalUser(user); + setLocalUser(me); - // we're connected! - state.Value = APIState.Online; + state.Value = me.SessionVerified ? APIState.Online : APIState.RequiresSecondFactorAuth; failureCount = 0; }; @@ -302,6 +277,34 @@ namespace osu.Game.Online.API return; } + if (state.Value == APIState.RequiresSecondFactorAuth) + { + if (string.IsNullOrEmpty(SecondFactorCode)) + return; + + state.Value = APIState.Connecting; + LastLoginError = null; + + var verificationRequest = new VerifySessionRequest(SecondFactorCode); + + verificationRequest.Success += () => state.Value = APIState.Online; + verificationRequest.Failure += ex => + { + state.Value = APIState.RequiresSecondFactorAuth; + LastLoginError = ex; + SecondFactorCode = null; + }; + + if (!handleRequest(verificationRequest)) + { + state.Value = APIState.Failing; + return; + } + + if (state.Value != APIState.Online) + return; + } + var friendsReq = new GetFriendsRequest(); friendsReq.Failure += _ => state.Value = APIState.Failing; friendsReq.Success += res => From 602c3bc2d90e57f84c6cf40e64eb6df1e168d945 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 24 Jan 2024 16:17:13 +0100 Subject: [PATCH 2678/4092] Hook up reissue request --- osu.Game/Overlays/Login/SecondFactorAuthForm.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Login/SecondFactorAuthForm.cs b/osu.Game/Overlays/Login/SecondFactorAuthForm.cs index 60c04eedee..566587a541 100644 --- a/osu.Game/Overlays/Login/SecondFactorAuthForm.cs +++ b/osu.Game/Overlays/Login/SecondFactorAuthForm.cs @@ -5,10 +5,12 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; +using osu.Framework.Logging; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; +using osu.Game.Online.API.Requests; using osu.Game.Overlays.Settings; using osu.Game.Resources.Localisation.Web; using osuTK; @@ -83,7 +85,9 @@ namespace osu.Game.Overlays.Login explainText.AddText(". You can also "); explainText.AddLink(UserVerificationStrings.BoxInfoReissueLink, () => { - // TODO: request another code. + var reissueRequest = new ReissueVerificationCodeRequest(); + reissueRequest.Failure += ex => Logger.Error(ex, @"Failed to retrieve new verification code."); + api.Perform(reissueRequest); }); explainText.AddText(" or "); explainText.AddLink(UserVerificationStrings.BoxInfoLogoutLink, () => { api.Logout(); }); From 62a0c236bc2eb706bb1f2840e80f7ed7df9c3b78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 24 Jan 2024 20:58:23 +0100 Subject: [PATCH 2679/4092] Split out raw websocket logic from conjoined notifications client contrivance --- .../WebSocket/OsuClientWebSocket.cs | 154 ++++++++++++++++++ .../WebSocket/WebSocketNotificationsClient.cs | 94 +---------- .../WebSocketNotificationsClientConnector.cs | 11 +- 3 files changed, 163 insertions(+), 96 deletions(-) create mode 100644 osu.Game/Online/Notifications/WebSocket/OsuClientWebSocket.cs diff --git a/osu.Game/Online/Notifications/WebSocket/OsuClientWebSocket.cs b/osu.Game/Online/Notifications/WebSocket/OsuClientWebSocket.cs new file mode 100644 index 0000000000..965f606bdc --- /dev/null +++ b/osu.Game/Online/Notifications/WebSocket/OsuClientWebSocket.cs @@ -0,0 +1,154 @@ +// 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 System.Net; +using System.Net.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Newtonsoft.Json; +using osu.Framework.Extensions.TypeExtensions; +using osu.Framework.Logging; +using osu.Game.Online.API; + +namespace osu.Game.Online.Notifications.WebSocket +{ + public class OsuClientWebSocket : IAsyncDisposable + { + public event Func? MessageReceived; + public event Func? Closed; + + private readonly string endpoint; + private readonly ClientWebSocket socket; + + private CancellationTokenSource? linkedTokenSource = null; + + public OsuClientWebSocket(IAPIProvider api, string endpoint) + { + socket = new ClientWebSocket(); + socket.Options.SetRequestHeader(@"Authorization", @$"Bearer {api.AccessToken}"); + socket.Options.Proxy = WebRequest.DefaultWebProxy; + if (socket.Options.Proxy != null) + socket.Options.Proxy.Credentials = CredentialCache.DefaultCredentials; + + this.endpoint = endpoint; + } + + public async Task ConnectAsync(CancellationToken cancellationToken) + { + if (socket.State == WebSocketState.Connecting || socket.State == WebSocketState.Open) + throw new InvalidOperationException("Connection is already opened"); + + Debug.Assert(linkedTokenSource == null); + linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + + await socket.ConnectAsync(new Uri(endpoint), linkedTokenSource.Token).ConfigureAwait(false); + runReadLoop(linkedTokenSource.Token); + } + + private void runReadLoop(CancellationToken cancellationToken) => Task.Factory.StartNew(async () => + { + byte[] buffer = new byte[1024]; + StringBuilder messageResult = new StringBuilder(); + + while (!cancellationToken.IsCancellationRequested) + { + try + { + WebSocketReceiveResult result = await socket.ReceiveAsync(buffer, cancellationToken).ConfigureAwait(false); + + switch (result.MessageType) + { + case WebSocketMessageType.Text: + messageResult.Append(Encoding.UTF8.GetString(buffer[..result.Count])); + + if (result.EndOfMessage) + { + SocketMessage? message = JsonConvert.DeserializeObject(messageResult.ToString()); + messageResult.Clear(); + + Debug.Assert(message != null); + + if (message.Error != null) + { + Logger.Log($"{GetType().ReadableName()} error: {message.Error}", LoggingTarget.Network); + break; + } + + await invokeMessageReceived(message).ConfigureAwait(false); + } + + break; + + case WebSocketMessageType.Binary: + throw new NotImplementedException("Binary message type not supported."); + + case WebSocketMessageType.Close: + throw new WebException("Connection closed by remote host."); + } + } + catch (Exception ex) + { + await invokeClosed(ex).ConfigureAwait(false); + return; + } + } + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default); + + private async Task invokeMessageReceived(SocketMessage message) + { + if (MessageReceived == null) + return; + + var invocationList = MessageReceived.GetInvocationList(); + + // ReSharper disable once PossibleInvalidCastExceptionInForeachLoop + foreach (Func handler in invocationList) + await handler.Invoke(message).ConfigureAwait(false); + } + + private async Task invokeClosed(Exception ex) + { + if (Closed == null) + return; + + var invocationList = Closed.GetInvocationList(); + + // ReSharper disable once PossibleInvalidCastExceptionInForeachLoop + foreach (Func handler in invocationList) + await handler.Invoke(ex).ConfigureAwait(false); + } + + public Task SendMessage(SocketMessage message, CancellationToken cancellationToken) + { + if (socket.State != WebSocketState.Open) + return Task.CompletedTask; + + return socket.SendAsync(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message)), WebSocketMessageType.Text, true, cancellationToken); + } + + public async Task DisconnectAsync() + { + linkedTokenSource?.Cancel(); + await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, @"Disconnecting", CancellationToken.None).ConfigureAwait(false); + linkedTokenSource?.Dispose(); + linkedTokenSource = null; + } + + public async ValueTask DisposeAsync() + { + try + { + await DisconnectAsync().ConfigureAwait(false); + } + catch + { + // Closure can fail if the connection is aborted. Don't really care since it's disposed anyway. + } + + socket.Dispose(); + } + } +} diff --git a/osu.Game/Online/Notifications/WebSocket/WebSocketNotificationsClient.cs b/osu.Game/Online/Notifications/WebSocket/WebSocketNotificationsClient.cs index 73e5dcec6f..b85a6ca3fe 100644 --- a/osu.Game/Online/Notifications/WebSocket/WebSocketNotificationsClient.cs +++ b/osu.Game/Online/Notifications/WebSocket/WebSocketNotificationsClient.cs @@ -1,17 +1,11 @@ // 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.Collections.Concurrent; using System.Diagnostics; -using System.Net; -using System.Net.WebSockets; -using System.Text; using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; -using osu.Framework.Extensions.TypeExtensions; -using osu.Framework.Logging; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.Chat; @@ -23,96 +17,25 @@ namespace osu.Game.Online.Notifications.WebSocket /// public class WebSocketNotificationsClient : NotificationsClient { - private readonly ClientWebSocket socket; - private readonly string endpoint; + private readonly OsuClientWebSocket socket; private readonly ConcurrentDictionary channelsMap = new ConcurrentDictionary(); - public WebSocketNotificationsClient(ClientWebSocket socket, string endpoint, IAPIProvider api) + public WebSocketNotificationsClient(IAPIProvider api, string endpoint) : base(api) { - this.socket = socket; - this.endpoint = endpoint; + socket = new OsuClientWebSocket(api, endpoint); + socket.MessageReceived += onMessageReceivedAsync; + socket.Closed += InvokeClosed; } public override async Task ConnectAsync(CancellationToken cancellationToken) { - await socket.ConnectAsync(new Uri(endpoint), cancellationToken).ConfigureAwait(false); - await sendMessage(new StartChatRequest(), CancellationToken.None).ConfigureAwait(false); - - runReadLoop(cancellationToken); + await socket.ConnectAsync(cancellationToken).ConfigureAwait(false); + await socket.SendMessage(new StartChatRequest(), CancellationToken.None).ConfigureAwait(false); await base.ConnectAsync(cancellationToken).ConfigureAwait(false); } - private void runReadLoop(CancellationToken cancellationToken) => Task.Run(async () => - { - byte[] buffer = new byte[1024]; - StringBuilder messageResult = new StringBuilder(); - - while (!cancellationToken.IsCancellationRequested) - { - try - { - WebSocketReceiveResult result = await socket.ReceiveAsync(buffer, cancellationToken).ConfigureAwait(false); - - switch (result.MessageType) - { - case WebSocketMessageType.Text: - messageResult.Append(Encoding.UTF8.GetString(buffer[..result.Count])); - - if (result.EndOfMessage) - { - SocketMessage? message = JsonConvert.DeserializeObject(messageResult.ToString()); - messageResult.Clear(); - - Debug.Assert(message != null); - - if (message.Error != null) - { - Logger.Log($"{GetType().ReadableName()} error: {message.Error}", LoggingTarget.Network); - break; - } - - await onMessageReceivedAsync(message).ConfigureAwait(false); - } - - break; - - case WebSocketMessageType.Binary: - throw new NotImplementedException("Binary message type not supported."); - - case WebSocketMessageType.Close: - throw new WebException("Connection closed by remote host."); - } - } - catch (Exception ex) - { - await InvokeClosed(ex).ConfigureAwait(false); - return; - } - } - }, cancellationToken); - - private async Task closeAsync() - { - try - { - await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, @"Disconnecting", CancellationToken.None).ConfigureAwait(false); - } - catch - { - // Closure can fail if the connection is aborted. Don't really care since it's disposed anyway. - } - } - - private async Task sendMessage(SocketMessage message, CancellationToken cancellationToken) - { - if (socket.State != WebSocketState.Open) - return; - - await socket.SendAsync(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message)), WebSocketMessageType.Text, true, cancellationToken).ConfigureAwait(false); - } - private async Task onMessageReceivedAsync(SocketMessage message) { switch (message.Event) @@ -173,8 +96,7 @@ namespace osu.Game.Online.Notifications.WebSocket public override async ValueTask DisposeAsync() { await base.DisposeAsync().ConfigureAwait(false); - await closeAsync().ConfigureAwait(false); - socket.Dispose(); + await socket.DisposeAsync().ConfigureAwait(false); } } } diff --git a/osu.Game/Online/Notifications/WebSocket/WebSocketNotificationsClientConnector.cs b/osu.Game/Online/Notifications/WebSocket/WebSocketNotificationsClientConnector.cs index f50369a06c..6c61281e90 100644 --- a/osu.Game/Online/Notifications/WebSocket/WebSocketNotificationsClientConnector.cs +++ b/osu.Game/Online/Notifications/WebSocket/WebSocketNotificationsClientConnector.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.Net; -using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; using osu.Game.Online.API; @@ -33,14 +31,7 @@ namespace osu.Game.Online.Notifications.WebSocket api.Queue(req); string endpoint = await tcs.Task.ConfigureAwait(false); - - ClientWebSocket socket = new ClientWebSocket(); - socket.Options.SetRequestHeader(@"Authorization", @$"Bearer {api.AccessToken}"); - socket.Options.Proxy = WebRequest.DefaultWebProxy; - if (socket.Options.Proxy != null) - socket.Options.Proxy.Credentials = CredentialCache.DefaultCredentials; - - return new WebSocketNotificationsClient(socket, endpoint, api); + return new WebSocketNotificationsClient(api, endpoint); } } } From e3eb7a8b4281c7d3b2170b10b2681cde8ee7f066 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 24 Jan 2024 21:33:34 +0100 Subject: [PATCH 2680/4092] Support verification via clicking link from e-mail --- osu.Game/Online/API/APIAccess.cs | 50 ++++++++++++++++++- .../DevelopmentEndpointConfiguration.cs | 1 + osu.Game/Online/EndpointConfiguration.cs | 5 ++ .../ExperimentalEndpointConfiguration.cs | 1 + .../Online/ProductionEndpointConfiguration.cs | 1 + 5 files changed, 57 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 389816fcf8..dabb2cc94c 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -11,8 +11,10 @@ using System.Net.Http; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; +using JetBrains.Annotations; using Newtonsoft.Json.Linq; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Extensions.ExceptionExtensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; @@ -76,6 +78,11 @@ namespace osu.Game.Online.API private readonly Logger log; + private string webSocketEndpointUrl; + + [CanBeNull] + private OsuClientWebSocket webSocket; + public APIAccess(OsuGameBase game, OsuConfigManager config, EndpointConfiguration endpointConfiguration, string versionHash) { this.game = game; @@ -84,6 +91,7 @@ namespace osu.Game.Online.API APIEndpointUrl = endpointConfiguration.APIEndpointUrl; WebsiteRootUrl = endpointConfiguration.WebsiteRootUrl; + webSocketEndpointUrl = endpointConfiguration.NotificationsWebSocketEndpointUrl; authentication = new OAuth(endpointConfiguration.APIClientID, endpointConfiguration.APIClientSecret, APIEndpointUrl); log = Logger.GetLogger(LoggingTarget.Network); @@ -267,7 +275,10 @@ namespace osu.Game.Online.API setLocalUser(me); - state.Value = me.SessionVerified ? APIState.Online : APIState.RequiresSecondFactorAuth; + if (me.SessionVerified) + state.Value = APIState.Online; + else + setUpSecondFactorAuthentication(); failureCount = 0; }; @@ -350,6 +361,42 @@ namespace osu.Game.Online.API this.password = password; } + private void setUpSecondFactorAuthentication() + { + if (state.Value == APIState.RequiresSecondFactorAuth) + return; + + state.Value = APIState.RequiresSecondFactorAuth; + + try + { + webSocket?.DisposeAsync().AsTask().WaitSafely(); + var newSocket = new OsuClientWebSocket(this, webSocketEndpointUrl); + newSocket.MessageReceived += async msg => + { + if (msg.Event == @"verified") + { + state.Value = APIState.Online; + await newSocket.DisposeAsync().ConfigureAwait(false); + if (webSocket == newSocket) + webSocket = null; + } + }; + newSocket.Closed += ex => + { + Logger.Error(ex, "Connection with account verification endpoint closed unexpectedly. Please supply account verification code manually.", LoggingTarget.Network); + return Task.CompletedTask; + }; + webSocket = newSocket; + + webSocket.ConnectAsync(cancellationToken.Token).WaitSafely(); + } + catch (Exception ex) + { + Logger.Error(ex, "Failed to set up connection with account verification endpoint. Please supply account verification code manually.", LoggingTarget.Network); + } + } + public void AuthenticateSecondFactor(string code) { Debug.Assert(State.Value == APIState.RequiresSecondFactorAuth); @@ -579,6 +626,7 @@ namespace osu.Game.Online.API flushQueue(); cancellationToken.Cancel(); + webSocket?.DisposeAsync().AsTask().WaitSafely(); } } diff --git a/osu.Game/Online/DevelopmentEndpointConfiguration.cs b/osu.Game/Online/DevelopmentEndpointConfiguration.cs index 5f3c353f4d..1c78c3c147 100644 --- a/osu.Game/Online/DevelopmentEndpointConfiguration.cs +++ b/osu.Game/Online/DevelopmentEndpointConfiguration.cs @@ -13,6 +13,7 @@ namespace osu.Game.Online SpectatorEndpointUrl = $@"{APIEndpointUrl}/signalr/spectator"; MultiplayerEndpointUrl = $@"{APIEndpointUrl}/signalr/multiplayer"; MetadataEndpointUrl = $@"{APIEndpointUrl}/signalr/metadata"; + NotificationsWebSocketEndpointUrl = "wss://dev.ppy.sh/home/notifications/feed"; } } } diff --git a/osu.Game/Online/EndpointConfiguration.cs b/osu.Game/Online/EndpointConfiguration.cs index f3bcced630..6187471b65 100644 --- a/osu.Game/Online/EndpointConfiguration.cs +++ b/osu.Game/Online/EndpointConfiguration.cs @@ -44,5 +44,10 @@ namespace osu.Game.Online /// The endpoint for the SignalR metadata server. /// public string MetadataEndpointUrl { get; set; } + + /// + /// The endpoint for the notifications websocket. + /// + public string NotificationsWebSocketEndpointUrl { get; set; } } } diff --git a/osu.Game/Online/ExperimentalEndpointConfiguration.cs b/osu.Game/Online/ExperimentalEndpointConfiguration.cs index c3d0014c8b..bc65fd63f3 100644 --- a/osu.Game/Online/ExperimentalEndpointConfiguration.cs +++ b/osu.Game/Online/ExperimentalEndpointConfiguration.cs @@ -14,6 +14,7 @@ namespace osu.Game.Online SpectatorEndpointUrl = "https://spectator.ppy.sh/spectator"; MultiplayerEndpointUrl = "https://spectator.ppy.sh/multiplayer"; MetadataEndpointUrl = "https://spectator.ppy.sh/metadata"; + NotificationsWebSocketEndpointUrl = "wss://notify.ppy.sh"; } } } diff --git a/osu.Game/Online/ProductionEndpointConfiguration.cs b/osu.Game/Online/ProductionEndpointConfiguration.cs index 0244761b65..a26a25bce5 100644 --- a/osu.Game/Online/ProductionEndpointConfiguration.cs +++ b/osu.Game/Online/ProductionEndpointConfiguration.cs @@ -13,6 +13,7 @@ namespace osu.Game.Online SpectatorEndpointUrl = "https://spectator.ppy.sh/spectator"; MultiplayerEndpointUrl = "https://spectator.ppy.sh/multiplayer"; MetadataEndpointUrl = "https://spectator.ppy.sh/metadata"; + NotificationsWebSocketEndpointUrl = "wss://notify.ppy.sh"; } } } From 67010fcd03b1546ef0bbf24fdf864db69ad5bb58 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 24 Jan 2024 23:45:07 +0300 Subject: [PATCH 2681/4092] Reduce allocation overhead in ScoreCounter --- osu.Game/Graphics/UserInterface/ScoreCounter.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ScoreCounter.cs b/osu.Game/Graphics/UserInterface/ScoreCounter.cs index 255b2149f0..62cdefda43 100644 --- a/osu.Game/Graphics/UserInterface/ScoreCounter.cs +++ b/osu.Game/Graphics/UserInterface/ScoreCounter.cs @@ -4,7 +4,6 @@ #nullable disable using osu.Framework.Bindables; -using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Localisation; using osu.Game.Graphics.Sprites; @@ -39,7 +38,7 @@ namespace osu.Game.Graphics.UserInterface protected override double GetProportionalDuration(long currentValue, long newValue) => currentValue > newValue ? currentValue - newValue : newValue - currentValue; - protected override LocalisableString FormatCount(long count) => count.ToLocalisableString(formatString); + protected override LocalisableString FormatCount(long count) => count.ToString(formatString); protected override OsuSpriteText CreateSpriteText() => base.CreateSpriteText().With(s => s.Font = s.Font.With(fixedWidth: true)); From 2f8747776efa654e6d939eac867139249a75b6ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 24 Jan 2024 22:01:59 +0100 Subject: [PATCH 2682/4092] Fix nullability inspection --- osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs b/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs index bb78cf9fd4..3607b37c7e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs @@ -90,7 +90,7 @@ namespace osu.Game.Tests.Visual.Online else { int userId = int.Parse(getUserRequest.Lookup); - string rulesetName = getUserRequest.Ruleset.ShortName; + string rulesetName = getUserRequest.Ruleset!.ShortName; var response = new APIUser { Id = userId, From 9e1c24271394770313e5b06e6b89ed1e73da5056 Mon Sep 17 00:00:00 2001 From: Mike Will Date: Wed, 24 Jan 2024 08:01:41 -0500 Subject: [PATCH 2683/4092] Prevent custom divisor ranges from halting preset cycling A custom divisor like 24 or 32 will result in a range containing many divisors that are already in the `Common` and `Triplets` presets. When this happens, it can become impossible to cycle between presets, because the preset can only be changed if the new divisor isn't already contained within the current preset's range. --- .../Visual/Editing/TestSceneBeatDivisorControl.cs | 7 +++++++ osu.Game/Screens/Edit/BindableBeatDivisor.cs | 5 +++-- .../Screens/Edit/Compose/Components/BeatDivisorControl.cs | 4 ++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs b/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs index f2b3351533..6c36e6729e 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs @@ -210,6 +210,13 @@ namespace osu.Game.Tests.Visual.Editing switchPresets(-1); assertPreset(BeatDivisorType.Custom, 15); assertBeatSnap(15); + + setDivisorViaInput(24); + assertPreset(BeatDivisorType.Custom, 24); + switchPresets(1); + assertPreset(BeatDivisorType.Common); + switchPresets(-2); + assertPreset(BeatDivisorType.Triplets); } private void switchBeatSnap(int direction) => AddRepeatStep($"move snap {(direction > 0 ? "forward" : "backward")}", () => diff --git a/osu.Game/Screens/Edit/BindableBeatDivisor.cs b/osu.Game/Screens/Edit/BindableBeatDivisor.cs index ffa4f01e75..87cb191a82 100644 --- a/osu.Game/Screens/Edit/BindableBeatDivisor.cs +++ b/osu.Game/Screens/Edit/BindableBeatDivisor.cs @@ -29,10 +29,11 @@ namespace osu.Game.Screens.Edit /// Set a divisor, updating the valid divisor range appropriately. /// /// The intended divisor. - public void SetArbitraryDivisor(int divisor) + /// Ignores the current valid divisor range when true. + public void SetArbitraryDivisor(int divisor, bool force = false) { // If the current valid divisor range doesn't contain the proposed value, attempt to find one which does. - if (!ValidDivisors.Value.Presets.Contains(divisor)) + if (force || !ValidDivisors.Value.Presets.Contains(divisor)) { if (BeatDivisorPresetCollection.COMMON.Presets.Contains(divisor)) ValidDivisors.Value = BeatDivisorPresetCollection.COMMON; diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index b33edb9edb..da1a37d57f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -208,11 +208,11 @@ namespace osu.Game.Screens.Edit.Compose.Components switch (currentType) { case BeatDivisorType.Common: - beatDivisor.SetArbitraryDivisor(4); + beatDivisor.SetArbitraryDivisor(4, true); break; case BeatDivisorType.Triplets: - beatDivisor.SetArbitraryDivisor(6); + beatDivisor.SetArbitraryDivisor(6, true); break; case BeatDivisorType.Custom: From 781196858209dcc6804182b9d8dfac2896e6fd1d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 25 Jan 2024 03:28:02 +0300 Subject: [PATCH 2684/4092] Fix ArgonScoreCounter is still using localisation --- osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs b/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs index 348327d710..44b9fb3123 100644 --- a/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Play.HUD public bool UsesFixedAnchor { get; set; } - protected override LocalisableString FormatCount(long count) => count.ToLocalisableString(); + protected override LocalisableString FormatCount(long count) => count.ToString(); protected override IHasText CreateText() => scoreText = new ArgonScoreTextComponent(Anchor.TopRight, BeatmapsetsStrings.ShowScoreboardHeadersScore.ToUpper()) { From 21b11092d61cb6b633b2b7ab248881504aeb98b7 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 25 Jan 2024 04:06:15 +0300 Subject: [PATCH 2685/4092] Fix slider sliding samples allocation --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index baec200107..080d07be49 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -239,11 +239,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (Tracking.Value && Time.Current >= HitObject.StartTime) { // keep the sliding sample playing at the current tracking position - if (!slidingSample.IsPlaying) + if (!slidingSample.RequestedPlaying) slidingSample.Play(); slidingSample.Balance.Value = CalculateSamplePlaybackBalance(CalculateDrawableRelativePosition(Ball)); } - else if (slidingSample.IsPlaying) + else if (slidingSample.IsPlaying || slidingSample.RequestedPlaying) slidingSample.Stop(); } } From 30e335233da019c7e72203a2589fbe8fd9484476 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Thu, 25 Jan 2024 09:27:10 +0800 Subject: [PATCH 2686/4092] Crop screenshot image to scaling container. --- osu.Game/Graphics/ScreenshotManager.cs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs index 26e499ae9a..47ce0d31eb 100644 --- a/osu.Game/Graphics/ScreenshotManager.cs +++ b/osu.Game/Graphics/ScreenshotManager.cs @@ -24,6 +24,7 @@ using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Processing; namespace osu.Game.Graphics { @@ -52,6 +53,9 @@ namespace osu.Game.Graphics private INotificationOverlay notificationOverlay { get; set; } private Sample shutter; + private Bindable sizeX; + private Bindable sizeY; + private Bindable scalingMode; [BackgroundDependencyLoader] private void load(OsuConfigManager config, Storage storage, AudioManager audio) @@ -62,6 +66,10 @@ namespace osu.Game.Graphics captureMenuCursor = config.GetBindable(OsuSetting.ScreenshotCaptureMenuCursor); shutter = audio.Samples.Get("UI/shutter"); + + sizeX = config.GetBindable(OsuSetting.ScalingSizeX); + sizeY = config.GetBindable(OsuSetting.ScalingSizeY); + scalingMode = config.GetBindable(OsuSetting.Scaling); } public bool OnPressed(KeyBindingPressEvent e) @@ -119,6 +127,19 @@ namespace osu.Game.Graphics using (var image = await host.TakeScreenshotAsync().ConfigureAwait(false)) { + if (scalingMode.Value == ScalingMode.Everything) + { + image.Mutate(m => + { + var size = m.GetCurrentSize(); + var rect = new Rectangle(Point.Empty, size); + int sx = (size.Width - (int)(size.Width * sizeX.Value)) / 2; + int sy = (size.Height - (int)(size.Height * sizeY.Value)) / 2; + rect.Inflate(-sx, -sy); + m.Crop(rect); + }); + } + clipboard.SetImage(image); (string filename, var stream) = getWritableStream(); From b20051fd55b9977152b22e1d3a86dba86d9b0839 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Thu, 25 Jan 2024 10:35:41 +0800 Subject: [PATCH 2687/4092] offset crop rectangle to scaling container --- osu.Game/Graphics/ScreenshotManager.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs index 47ce0d31eb..a60d55cbef 100644 --- a/osu.Game/Graphics/ScreenshotManager.cs +++ b/osu.Game/Graphics/ScreenshotManager.cs @@ -53,6 +53,8 @@ namespace osu.Game.Graphics private INotificationOverlay notificationOverlay { get; set; } private Sample shutter; + private Bindable posX; + private Bindable posY; private Bindable sizeX; private Bindable sizeY; private Bindable scalingMode; @@ -67,6 +69,8 @@ namespace osu.Game.Graphics shutter = audio.Samples.Get("UI/shutter"); + posX = config.GetBindable(OsuSetting.ScalingPositionX); + posY = config.GetBindable(OsuSetting.ScalingPositionY); sizeX = config.GetBindable(OsuSetting.ScalingSizeX); sizeY = config.GetBindable(OsuSetting.ScalingSizeY); scalingMode = config.GetBindable(OsuSetting.Scaling); @@ -136,6 +140,8 @@ namespace osu.Game.Graphics int sx = (size.Width - (int)(size.Width * sizeX.Value)) / 2; int sy = (size.Height - (int)(size.Height * sizeY.Value)) / 2; rect.Inflate(-sx, -sy); + rect.X = (int)(rect.X * posX.Value) * 2; + rect.Y = (int)(rect.Y * posY.Value) * 2; m.Crop(rect); }); } From d2990170d021f2caee2c6e66368f07792c7ac53c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jan 2024 13:53:31 +0900 Subject: [PATCH 2688/4092] Add retry loop to avoid log export failing occasionally on windows Closes https://github.com/ppy/osu/issues/26693. --- osu.Game/IO/MigratableStorage.cs | 40 ++--------- .../Sections/General/UpdateSettings.cs | 4 +- osu.Game/Utils/FileUtils.cs | 72 +++++++++++++++++++ 3 files changed, 80 insertions(+), 36 deletions(-) create mode 100644 osu.Game/Utils/FileUtils.cs diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs index 14a3c5a43c..d03d259f71 100644 --- a/osu.Game/IO/MigratableStorage.cs +++ b/osu.Game/IO/MigratableStorage.cs @@ -6,8 +6,8 @@ using System; using System.IO; using System.Linq; -using System.Threading; using osu.Framework.Platform; +using osu.Game.Utils; namespace osu.Game.IO { @@ -81,7 +81,7 @@ namespace osu.Game.IO if (IgnoreSuffixes.Any(suffix => fi.Name.EndsWith(suffix, StringComparison.Ordinal))) continue; - allFilesDeleted &= AttemptOperation(() => fi.Delete(), throwOnFailure: false); + allFilesDeleted &= FileUtils.AttemptOperation(() => fi.Delete(), throwOnFailure: false); } foreach (DirectoryInfo dir in target.GetDirectories()) @@ -92,11 +92,11 @@ namespace osu.Game.IO if (IgnoreSuffixes.Any(suffix => dir.Name.EndsWith(suffix, StringComparison.Ordinal))) continue; - allFilesDeleted &= AttemptOperation(() => dir.Delete(true), throwOnFailure: false); + allFilesDeleted &= FileUtils.AttemptOperation(() => dir.Delete(true), throwOnFailure: false); } if (target.GetFiles().Length == 0 && target.GetDirectories().Length == 0) - allFilesDeleted &= AttemptOperation(target.Delete, throwOnFailure: false); + allFilesDeleted &= FileUtils.AttemptOperation(target.Delete, throwOnFailure: false); return allFilesDeleted; } @@ -115,7 +115,7 @@ namespace osu.Game.IO if (IgnoreSuffixes.Any(suffix => fileInfo.Name.EndsWith(suffix, StringComparison.Ordinal))) continue; - AttemptOperation(() => + FileUtils.AttemptOperation(() => { fileInfo.Refresh(); @@ -139,35 +139,5 @@ namespace osu.Game.IO CopyRecursive(dir, destination.CreateSubdirectory(dir.Name), false); } } - - /// - /// Attempt an IO operation multiple times and only throw if none of the attempts succeed. - /// - /// The action to perform. - /// The number of attempts (250ms wait between each). - /// Whether to throw an exception on failure. If false, will silently fail. - protected static bool AttemptOperation(Action action, int attempts = 10, bool throwOnFailure = true) - { - while (true) - { - try - { - action(); - return true; - } - catch (Exception) - { - if (attempts-- == 0) - { - if (throwOnFailure) - throw; - - return false; - } - } - - Thread.Sleep(250); - } - } } } diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 3ff5556f4d..fe88413e6a 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -15,6 +15,7 @@ using osu.Game.Localisation; using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Settings.Sections.Maintenance; using osu.Game.Updater; +using osu.Game.Utils; using SharpCompress.Archives.Zip; namespace osu.Game.Overlays.Settings.Sections.General @@ -111,7 +112,8 @@ namespace osu.Game.Overlays.Settings.Sections.General using (var outStream = storage.CreateFileSafely(archive_filename)) using (var zip = ZipArchive.Create()) { - foreach (string? f in logStorage.GetFiles(string.Empty, "*.log")) zip.AddEntry(f, logStorage.GetStream(f), true); + foreach (string? f in logStorage.GetFiles(string.Empty, "*.log")) + FileUtils.AttemptOperation(z => z.AddEntry(f, logStorage.GetStream(f), true), zip); zip.SaveTo(outStream); } diff --git a/osu.Game/Utils/FileUtils.cs b/osu.Game/Utils/FileUtils.cs new file mode 100644 index 0000000000..063ab178f7 --- /dev/null +++ b/osu.Game/Utils/FileUtils.cs @@ -0,0 +1,72 @@ +// 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.Threading; + +namespace osu.Game.Utils +{ + public static class FileUtils + { + /// + /// Attempt an IO operation multiple times and only throw if none of the attempts succeed. + /// + /// The action to perform. + /// The provided state. + /// The number of attempts (250ms wait between each). + /// Whether to throw an exception on failure. If false, will silently fail. + public static bool AttemptOperation(Action action, T state, int attempts = 10, bool throwOnFailure = true) + { + while (true) + { + try + { + action(state); + return true; + } + catch (Exception) + { + if (attempts-- == 0) + { + if (throwOnFailure) + throw; + + return false; + } + } + + Thread.Sleep(250); + } + } + + /// + /// Attempt an IO operation multiple times and only throw if none of the attempts succeed. + /// + /// The action to perform. + /// The number of attempts (250ms wait between each). + /// Whether to throw an exception on failure. If false, will silently fail. + public static bool AttemptOperation(Action action, int attempts = 10, bool throwOnFailure = true) + { + while (true) + { + try + { + action(); + return true; + } + catch (Exception) + { + if (attempts-- == 0) + { + if (throwOnFailure) + throw; + + return false; + } + } + + Thread.Sleep(250); + } + } + } +} From e1f8bc96924b104665782aacfd223eb2ba9dafed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=C3=AAn=20Minh=20H=E1=BB=93?= Date: Thu, 25 Jan 2024 12:09:39 +0700 Subject: [PATCH 2689/4092] Rename CanRotate property of SelectionRotationHandler to a more descriptive name --- osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs | 4 +++- osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs | 2 +- osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs | 2 +- osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs | 2 +- osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs | 2 +- .../Edit/Compose/Components/SelectionRotationHandler.cs | 5 +++-- 6 files changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs index 21fb8a67de..0ce78e4f61 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs @@ -19,6 +19,7 @@ namespace osu.Game.Rulesets.Osu.Edit { public partial class OsuSelectionRotationHandler : SelectionRotationHandler { + public BindableBool CanRotatePlayfieldOrigin { get; private set; } = new(); [Resolved] private IEditorChangeHandler? changeHandler { get; set; } @@ -41,7 +42,8 @@ namespace osu.Game.Rulesets.Osu.Edit private void updateState() { var quad = GeometryUtils.GetSurroundingQuad(selectedMovableObjects); - CanRotate.Value = quad.Width > 0 || quad.Height > 0; + CanRotateSelectionOrigin.Value = quad.Width > 0 || quad.Height > 0; + CanRotatePlayfieldOrigin.Value = selectedItems.Any(); } private OsuHitObject[]? objectsInRotation; diff --git a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs index 3da9f5b69b..291c79e613 100644 --- a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.Edit // bindings to `Enabled` on the buttons are decoupled on purpose // due to the weird `OsuButton` behaviour of resetting `Enabled` to `false` when `Action` is set. - canRotate.BindTo(RotationHandler.CanRotate); + canRotate.BindTo(RotationHandler.CanRotateSelectionOrigin); canRotate.BindValueChanged(_ => rotateButton.Enabled.Value = canRotate.Value, true); } diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs index f6637d0e80..8e4f4a1cfd 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.Editing { this.getTargetContainer = getTargetContainer; - CanRotate.Value = true; + CanRotateSelectionOrigin.Value = true; } [CanBeNull] diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs index 60f69000a2..7ecf116b68 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs @@ -41,7 +41,7 @@ namespace osu.Game.Overlays.SkinEditor private void updateState() { - CanRotate.Value = selectedItems.Count > 0; + CanRotateSelectionOrigin.Value = selectedItems.Count > 0; } private Drawable[]? objectsInRotation; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index 0b16941bc4..85ea7364e8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -174,7 +174,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void load() { if (rotationHandler != null) - canRotate.BindTo(rotationHandler.CanRotate); + canRotate.BindTo(rotationHandler.CanRotateSelectionOrigin); canRotate.BindValueChanged(_ => recreate(), true); } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs index 5faa4a108d..749e1aab17 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs @@ -13,9 +13,10 @@ namespace osu.Game.Screens.Edit.Compose.Components public partial class SelectionRotationHandler : Component { /// - /// Whether the rotation can currently be performed. + /// Whether rotation anchored by the selection origin can currently be performed. + /// This is in constrast to rotation anchored by the entire field. /// - public Bindable CanRotate { get; private set; } = new BindableBool(); + public Bindable CanRotateSelectionOrigin { get; private set; } = new BindableBool(); /// /// Performs a single, instant, atomic rotation operation. From 601ba9f194ee029714d85a73f4e0078a7401f910 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=C3=AAn=20Minh=20H=E1=BB=93?= Date: Thu, 25 Jan 2024 12:16:35 +0700 Subject: [PATCH 2690/4092] Change rotate tool button to be enabled on single circle. Inject osu ruleset specific rotate handler instead of generic handler. --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 5 ++++- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 2 +- osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs | 5 ++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 448cfaf84c..a0fb0b06c3 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -101,7 +101,10 @@ namespace osu.Game.Rulesets.Osu.Edit RightToolbox.AddRange(new EditorToolboxGroup[] { - new TransformToolboxGroup { RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, }, + new TransformToolboxGroup + { + RotationHandler = (OsuSelectionRotationHandler)BlueprintContainer.SelectionHandler.RotationHandler, + }, FreehandlSliderToolboxGroup } ); diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index cea2adc6e2..7e645bc670 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -164,7 +164,7 @@ namespace osu.Game.Rulesets.Osu.Edit if ((reference & Anchor.y0) > 0) scale.Y = -scale.Y; } - public override SelectionRotationHandler CreateRotationHandler() => new OsuSelectionRotationHandler(); + public override OsuSelectionRotationHandler CreateRotationHandler() => new OsuSelectionRotationHandler(); private void scaleSlider(Slider slider, Vector2 scale) { diff --git a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs index 291c79e613..c70f35c6fb 100644 --- a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs @@ -11,7 +11,6 @@ using osu.Framework.Input.Events; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Components; -using osu.Game.Screens.Edit.Compose.Components; using osuTK; namespace osu.Game.Rulesets.Osu.Edit @@ -22,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Edit private EditorToolButton rotateButton = null!; - public SelectionRotationHandler RotationHandler { get; init; } = null!; + public OsuSelectionRotationHandler RotationHandler { get; init; } = null!; public TransformToolboxGroup() : base("transform") @@ -53,7 +52,7 @@ namespace osu.Game.Rulesets.Osu.Edit // bindings to `Enabled` on the buttons are decoupled on purpose // due to the weird `OsuButton` behaviour of resetting `Enabled` to `false` when `Action` is set. - canRotate.BindTo(RotationHandler.CanRotateSelectionOrigin); + canRotate.BindTo(RotationHandler.CanRotatePlayfieldOrigin); canRotate.BindValueChanged(_ => rotateButton.Enabled.Value = canRotate.Value, true); } From 0cbba7e011bd91d4d9883adc4d5c772c6789670a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jan 2024 14:18:20 +0900 Subject: [PATCH 2691/4092] Apply NRT to `ScreenshotManager` --- osu.Game/Graphics/ScreenshotManager.cs | 39 ++++++++++++-------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs index a60d55cbef..d952460c47 100644 --- a/osu.Game/Graphics/ScreenshotManager.cs +++ b/osu.Game/Graphics/ScreenshotManager.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.IO; using System.Threading; @@ -38,37 +36,36 @@ namespace osu.Game.Graphics /// public IBindable CursorVisibility => cursorVisibility; - private Bindable screenshotFormat; - private Bindable captureMenuCursor; + [Resolved] + private GameHost host { get; set; } = null!; [Resolved] - private GameHost host { get; set; } + private Clipboard clipboard { get; set; } = null!; [Resolved] - private Clipboard clipboard { get; set; } + private INotificationOverlay notificationOverlay { get; set; } = null!; - private Storage storage; + private Storage storage = null!; - [Resolved] - private INotificationOverlay notificationOverlay { get; set; } + private Sample? shutter; - private Sample shutter; - private Bindable posX; - private Bindable posY; - private Bindable sizeX; - private Bindable sizeY; - private Bindable scalingMode; + private Bindable screenshotFormat = null!; + private Bindable captureMenuCursor = null!; + private Bindable posX = null!; + private Bindable posY = null!; + private Bindable sizeX = null!; + private Bindable sizeY = null!; + private Bindable scalingMode = null!; [BackgroundDependencyLoader] private void load(OsuConfigManager config, Storage storage, AudioManager audio) { this.storage = storage.GetStorageForDirectory(@"screenshots"); - screenshotFormat = config.GetBindable(OsuSetting.ScreenshotFormat); - captureMenuCursor = config.GetBindable(OsuSetting.ScreenshotCaptureMenuCursor); - shutter = audio.Samples.Get("UI/shutter"); + screenshotFormat = config.GetBindable(OsuSetting.ScreenshotFormat); + captureMenuCursor = config.GetBindable(OsuSetting.ScreenshotCaptureMenuCursor); posX = config.GetBindable(OsuSetting.ScalingPositionX); posY = config.GetBindable(OsuSetting.ScalingPositionY); sizeX = config.GetBindable(OsuSetting.ScalingSizeX); @@ -84,7 +81,7 @@ namespace osu.Game.Graphics switch (e.Action) { case GlobalAction.TakeScreenshot: - shutter.Play(); + shutter?.Play(); TakeScreenshotAsync().FireAndForget(); return true; } @@ -148,7 +145,7 @@ namespace osu.Game.Graphics clipboard.SetImage(image); - (string filename, var stream) = getWritableStream(); + (string? filename, Stream? stream) = getWritableStream(); if (filename == null) return; @@ -191,7 +188,7 @@ namespace osu.Game.Graphics private static readonly object filename_reservation_lock = new object(); - private (string filename, Stream stream) getWritableStream() + private (string? filename, Stream? stream) getWritableStream() { lock (filename_reservation_lock) { From 85927e06827eaa3660f00191eac6b4251e8db7b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jan 2024 14:22:27 +0900 Subject: [PATCH 2692/4092] Move configuration retrieval to non-bindable inline for readability --- osu.Game/Graphics/ScreenshotManager.cs | 65 ++++++++++++-------------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs index d952460c47..ca6a3fe84f 100644 --- a/osu.Game/Graphics/ScreenshotManager.cs +++ b/osu.Game/Graphics/ScreenshotManager.cs @@ -22,6 +22,7 @@ using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; namespace osu.Game.Graphics @@ -45,32 +46,18 @@ namespace osu.Game.Graphics [Resolved] private INotificationOverlay notificationOverlay { get; set; } = null!; + [Resolved] + private OsuConfigManager config { get; set; } = null!; + private Storage storage = null!; private Sample? shutter; - private Bindable screenshotFormat = null!; - private Bindable captureMenuCursor = null!; - private Bindable posX = null!; - private Bindable posY = null!; - private Bindable sizeX = null!; - private Bindable sizeY = null!; - private Bindable scalingMode = null!; - [BackgroundDependencyLoader] - private void load(OsuConfigManager config, Storage storage, AudioManager audio) + private void load(Storage storage, AudioManager audio) { this.storage = storage.GetStorageForDirectory(@"screenshots"); - shutter = audio.Samples.Get("UI/shutter"); - - screenshotFormat = config.GetBindable(OsuSetting.ScreenshotFormat); - captureMenuCursor = config.GetBindable(OsuSetting.ScreenshotCaptureMenuCursor); - posX = config.GetBindable(OsuSetting.ScalingPositionX); - posY = config.GetBindable(OsuSetting.ScalingPositionY); - sizeX = config.GetBindable(OsuSetting.ScalingSizeX); - sizeY = config.GetBindable(OsuSetting.ScalingSizeY); - scalingMode = config.GetBindable(OsuSetting.Scaling); } public bool OnPressed(KeyBindingPressEvent e) @@ -99,9 +86,19 @@ namespace osu.Game.Graphics { Interlocked.Increment(ref screenShotTasks); + ScreenshotFormat screenshotFormat = config.Get(OsuSetting.ScreenshotFormat); + bool captureMenuCursor = config.Get(OsuSetting.ScreenshotCaptureMenuCursor); + + float posX = config.Get(OsuSetting.ScalingPositionX); + float posY = config.Get(OsuSetting.ScalingPositionY); + float sizeX = config.Get(OsuSetting.ScalingSizeX); + float sizeY = config.Get(OsuSetting.ScalingSizeY); + + ScalingMode scalingMode = config.Get(OsuSetting.Scaling); + try { - if (!captureMenuCursor.Value) + if (!captureMenuCursor) { cursorVisibility.Value = false; @@ -110,7 +107,7 @@ namespace osu.Game.Graphics int framesWaited = 0; - using (var framesWaitedEvent = new ManualResetEventSlim(false)) + using (ManualResetEventSlim framesWaitedEvent = new ManualResetEventSlim(false)) { ScheduledDelegate waitDelegate = host.DrawThread.Scheduler.AddDelayed(() => { @@ -126,32 +123,32 @@ namespace osu.Game.Graphics } } - using (var image = await host.TakeScreenshotAsync().ConfigureAwait(false)) + using (Image? image = await host.TakeScreenshotAsync().ConfigureAwait(false)) { - if (scalingMode.Value == ScalingMode.Everything) + if (scalingMode == ScalingMode.Everything) { image.Mutate(m => { - var size = m.GetCurrentSize(); - var rect = new Rectangle(Point.Empty, size); - int sx = (size.Width - (int)(size.Width * sizeX.Value)) / 2; - int sy = (size.Height - (int)(size.Height * sizeY.Value)) / 2; + Size size = m.GetCurrentSize(); + Rectangle rect = new Rectangle(Point.Empty, size); + int sx = (size.Width - (int)(size.Width * sizeX)) / 2; + int sy = (size.Height - (int)(size.Height * sizeY)) / 2; rect.Inflate(-sx, -sy); - rect.X = (int)(rect.X * posX.Value) * 2; - rect.Y = (int)(rect.Y * posY.Value) * 2; + rect.X = (int)(rect.X * posX) * 2; + rect.Y = (int)(rect.Y * posY) * 2; m.Crop(rect); }); } clipboard.SetImage(image); - (string? filename, Stream? stream) = getWritableStream(); + (string? filename, Stream? stream) = getWritableStream(screenshotFormat); if (filename == null) return; using (stream) { - switch (screenshotFormat.Value) + switch (screenshotFormat) { case ScreenshotFormat.Png: await image.SaveAsPngAsync(stream).ConfigureAwait(false); @@ -164,7 +161,7 @@ namespace osu.Game.Graphics break; default: - throw new InvalidOperationException($"Unknown enum member {nameof(ScreenshotFormat)} {screenshotFormat.Value}."); + throw new InvalidOperationException($"Unknown enum member {nameof(ScreenshotFormat)} {screenshotFormat}."); } } @@ -188,12 +185,12 @@ namespace osu.Game.Graphics private static readonly object filename_reservation_lock = new object(); - private (string? filename, Stream? stream) getWritableStream() + private (string? filename, Stream? stream) getWritableStream(ScreenshotFormat format) { lock (filename_reservation_lock) { - var dt = DateTime.Now; - string fileExt = screenshotFormat.ToString().ToLowerInvariant(); + DateTime dt = DateTime.Now; + string fileExt = format.ToString().ToLowerInvariant(); string withoutIndex = $"osu_{dt:yyyy-MM-dd_HH-mm-ss}.{fileExt}"; if (!storage.Exists(withoutIndex)) From 69e822f3c53186c61299c6b80bcb17a834f547eb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jan 2024 14:30:26 +0900 Subject: [PATCH 2693/4092] Refactor crop logic slightly --- osu.Game/Graphics/ScreenshotManager.cs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs index ca6a3fe84f..a085558b3a 100644 --- a/osu.Game/Graphics/ScreenshotManager.cs +++ b/osu.Game/Graphics/ScreenshotManager.cs @@ -89,13 +89,6 @@ namespace osu.Game.Graphics ScreenshotFormat screenshotFormat = config.Get(OsuSetting.ScreenshotFormat); bool captureMenuCursor = config.Get(OsuSetting.ScreenshotCaptureMenuCursor); - float posX = config.Get(OsuSetting.ScalingPositionX); - float posY = config.Get(OsuSetting.ScalingPositionY); - float sizeX = config.Get(OsuSetting.ScalingSizeX); - float sizeY = config.Get(OsuSetting.ScalingSizeY); - - ScalingMode scalingMode = config.Get(OsuSetting.Scaling); - try { if (!captureMenuCursor) @@ -125,17 +118,26 @@ namespace osu.Game.Graphics using (Image? image = await host.TakeScreenshotAsync().ConfigureAwait(false)) { - if (scalingMode == ScalingMode.Everything) + if (config.Get(OsuSetting.Scaling) == ScalingMode.Everything) { + float posX = config.Get(OsuSetting.ScalingPositionX); + float posY = config.Get(OsuSetting.ScalingPositionY); + float sizeX = config.Get(OsuSetting.ScalingSizeX); + float sizeY = config.Get(OsuSetting.ScalingSizeY); + image.Mutate(m => { - Size size = m.GetCurrentSize(); - Rectangle rect = new Rectangle(Point.Empty, size); - int sx = (size.Width - (int)(size.Width * sizeX)) / 2; - int sy = (size.Height - (int)(size.Height * sizeY)) / 2; + Rectangle rect = new Rectangle(Point.Empty, m.GetCurrentSize()); + + // Reduce size by user scale settings... + int sx = (rect.Width - (int)(rect.Width * sizeX)) / 2; + int sy = (rect.Height - (int)(rect.Height * sizeY)) / 2; rect.Inflate(-sx, -sy); + + // ...then adjust the region based on their positional offset. rect.X = (int)(rect.X * posX) * 2; rect.Y = (int)(rect.Y * posY) * 2; + m.Crop(rect); }); } From f22bfa350a2b95ff3fa16a5528d7ef95c0e0d3fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jan 2024 15:02:43 +0900 Subject: [PATCH 2694/4092] Add test coverage of hidden scores on accuracy circle --- .../Visual/Ranking/TestSceneAccuracyCircle.cs | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs index 7aa36429a7..41a5603060 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs @@ -46,6 +46,16 @@ namespace osu.Game.Tests.Visual.Ranking addCircleStep(createScore(1, new OsuRuleset())); } + [Test] + public void TestOsuRankHidden() + { + addCircleStep(createScore(0, new OsuRuleset(), 20, true)); + addCircleStep(createScore(0.8, new OsuRuleset(), 5, true)); + addCircleStep(createScore(0.95, new OsuRuleset(), 0, true)); + addCircleStep(createScore(0.97, new OsuRuleset(), 1, true)); + addCircleStep(createScore(1, new OsuRuleset(), 0, true)); + } + [Test] public void TestCatchRank() { @@ -66,7 +76,7 @@ namespace osu.Game.Tests.Visual.Ranking addCircleStep(createScore(1, new CatchRuleset())); } - private void addCircleStep(ScoreInfo score) => AddStep($"add panel ({score.DisplayAccuracy})", () => + private void addCircleStep(ScoreInfo score) => AddStep($"add panel ({score.DisplayAccuracy}, {score.Statistics.GetValueOrDefault(HitResult.Miss)} miss)", () => { Children = new Drawable[] { @@ -93,18 +103,22 @@ namespace osu.Game.Tests.Visual.Ranking }; }); - private ScoreInfo createScore(double accuracy, Ruleset ruleset) + private ScoreInfo createScore(double accuracy, Ruleset ruleset, int missCount = 0, bool hidden = false) { var scoreProcessor = ruleset.CreateScoreProcessor(); var statistics = new Dictionary { - { HitResult.Miss, 1 }, + { HitResult.Miss, missCount }, { HitResult.Meh, 50 }, { HitResult.Good, 100 }, { HitResult.Great, 300 }, }; + var mods = hidden + ? new[] { new OsuModHidden() } + : new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }; + return new ScoreInfo { User = new APIUser @@ -114,7 +128,7 @@ namespace osu.Game.Tests.Visual.Ranking }, BeatmapInfo = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, Ruleset = ruleset.RulesetInfo, - Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, + Mods = mods, TotalScore = 2845370, Accuracy = accuracy, MaxCombo = 999, From 37e370e654d743ca11b78074cd9a60f66d5590c7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jan 2024 14:57:42 +0900 Subject: [PATCH 2695/4092] Fix crash at results screen when hidden is enabled and S rank becomes A due to miss Closes https://github.com/ppy/osu/issues/26692. --- osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index e7e54d0fae..0aff98df2b 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -417,7 +417,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy accuracyCircle .FillTo(accuracyS - NOTCH_WIDTH_PERCENTAGE / 2 - visual_alignment_offset, 70, Easing.OutQuint); - badges.Single(b => b.Rank == ScoreRank.S) + badges.Single(b => b.Rank == getRank(ScoreRank.S)) .FadeOut(70, Easing.OutQuint); } } From cc341b4119bd9a1aa8cb5aafe936088ff1e0f857 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jan 2024 15:21:19 +0900 Subject: [PATCH 2696/4092] Adjust beatmap carousel padding to avoid scrollbar disappearing underneath logo --- osu.Game/Screens/Select/BeatmapCarousel.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 70ecde3858..32a1b5cb58 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -215,6 +215,12 @@ namespace osu.Game.Screens.Select InternalChild = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding + { + // Avoid clash between scrollbar and osu! logo. + Top = 10, + Bottom = 100, + }, Children = new Drawable[] { setPool, From 500bed01215f6b99eeb3a6a717563b243c18ccd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=C3=AAn=20Minh=20H=E1=BB=93?= Date: Thu, 25 Jan 2024 14:24:35 +0700 Subject: [PATCH 2697/4092] Split editor toolbox radio button disabling logic from EditorRadioButton, then add disabling logic for rotate popover --- osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs | 8 +++++++- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 8 ++++++++ .../Edit/Components/RadioButtons/EditorRadioButton.cs | 2 -- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs index f09d6b78e6..fdab84f38d 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs @@ -24,6 +24,7 @@ namespace osu.Game.Rulesets.Osu.Edit private SliderWithTextBoxInput angleInput = null!; private EditorRadioButtonCollection rotationOrigin = null!; + private RadioButton selectionCentreButton = null!; public PreciseRotationPopover(SelectionRotationHandler rotationHandler) { this.rotationHandler = rotationHandler; @@ -59,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Edit new RadioButton("Playfield centre", () => rotationInfo.Value = rotationInfo.Value with { Origin = RotationOrigin.PlayfieldCentre }, () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), - new RadioButton("Selection centre", + selectionCentreButton = new RadioButton("Selection centre", () => rotationInfo.Value = rotationInfo.Value with { Origin = RotationOrigin.SelectionCentre }, () => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare }) } @@ -76,6 +77,11 @@ namespace osu.Game.Rulesets.Osu.Edit angleInput.Current.BindValueChanged(angle => rotationInfo.Value = rotationInfo.Value with { Degrees = angle.NewValue }); rotationOrigin.Items.First().Select(); + rotationHandler.CanRotateSelectionOrigin.BindValueChanged(e => + { + selectionCentreButton.Selected.Disabled = !e.NewValue; + }, true); + rotationInfo.BindValueChanged(rotation => { rotationHandler.Update(rotation.NewValue.Degrees, rotation.NewValue.Origin == RotationOrigin.PlayfieldCentre ? OsuPlayfield.BASE_SIZE / 2 : null); diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 50e6393895..6abc6cb95b 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -244,6 +244,14 @@ namespace osu.Game.Rulesets.Edit if (!timing.NewValue) setSelectTool(); }); + + EditorBeatmap.HasTiming.BindValueChanged(hasTiming => + { + foreach (var item in toolboxCollection.Items) + { + item.Selected.Disabled = !hasTiming.NewValue; + } + }, true); } protected override void Update() diff --git a/osu.Game/Screens/Edit/Components/RadioButtons/EditorRadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/EditorRadioButton.cs index 65f3e41c13..5549095639 100644 --- a/osu.Game/Screens/Edit/Components/RadioButtons/EditorRadioButton.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/EditorRadioButton.cs @@ -76,8 +76,6 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons Selected?.Invoke(Button); }; - editorBeatmap?.HasTiming.BindValueChanged(hasTiming => Button.Selected.Disabled = !hasTiming.NewValue, true); - Button.Selected.BindDisabledChanged(disabled => Enabled.Value = !disabled, true); updateSelectionState(); } From 94ada87cbad0828fcb669d0e4133f850b12a07b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=C3=AAn=20Minh=20H=E1=BB=93?= Date: Thu, 25 Jan 2024 14:24:45 +0700 Subject: [PATCH 2698/4092] Un-hardcode tooltip from EditorRadioButton and add disabled tooltip for rotation popover --- osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs | 2 ++ osu.Game/Rulesets/Edit/HitObjectComposer.cs | 5 +++++ .../Edit/Components/RadioButtons/EditorRadioButton.cs | 2 +- .../Edit/Components/RadioButtons/RadioButton.cs | 11 +++++++++++ 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs index fdab84f38d..2cf6799279 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs @@ -25,6 +25,7 @@ namespace osu.Game.Rulesets.Osu.Edit private EditorRadioButtonCollection rotationOrigin = null!; private RadioButton selectionCentreButton = null!; + public PreciseRotationPopover(SelectionRotationHandler rotationHandler) { this.rotationHandler = rotationHandler; @@ -67,6 +68,7 @@ namespace osu.Game.Rulesets.Osu.Edit } } }; + selectionCentreButton.TooltipTextWhenDisabled = "We can't rotate a circle around itself! Can we?"; } protected override void LoadComplete() diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 6abc6cb95b..bc8de7f4b2 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -212,6 +212,11 @@ namespace osu.Game.Rulesets.Edit .Select(t => new RadioButton(t.Name, () => toolSelected(t), t.CreateIcon)) .ToList(); + foreach (var item in toolboxCollection.Items) + { + item.TooltipTextWhenDisabled = "Add at least one timing point first!"; + } + TernaryStates = CreateTernaryButtons().ToArray(); togglesCollection.AddRange(TernaryStates.Select(b => new DrawableTernaryButton(b))); diff --git a/osu.Game/Screens/Edit/Components/RadioButtons/EditorRadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/EditorRadioButton.cs index 5549095639..601548fadd 100644 --- a/osu.Game/Screens/Edit/Components/RadioButtons/EditorRadioButton.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/EditorRadioButton.cs @@ -97,6 +97,6 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons X = 40f }; - public LocalisableString TooltipText => Enabled.Value ? string.Empty : "Add at least one timing point first!"; + public LocalisableString TooltipText => Enabled.Value ? Button.TooltipTextWhenEnabled : Button.TooltipTextWhenDisabled; } } diff --git a/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs index 9dcd29bf83..1b47c028ab 100644 --- a/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Localisation; namespace osu.Game.Screens.Edit.Components.RadioButtons { @@ -11,9 +12,19 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons { /// /// Whether this is selected. + /// Disable this bindable to disable the button. /// public readonly BindableBool Selected; + /// + /// Tooltip text that will be shown on hover if button is enabled. + /// + public LocalisableString TooltipTextWhenEnabled { get; set; } = string.Empty; + /// + /// Tooltip text that will be shown on hover if button is disabled. + /// + public LocalisableString TooltipTextWhenDisabled { get; set; } = string.Empty; + /// /// The item related to this button. /// From b87ff4db0d5d69e7f8e787eb1135745c491c074e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=C3=AAn=20Minh=20H=E1=BB=93?= Date: Thu, 25 Jan 2024 15:33:48 +0700 Subject: [PATCH 2699/4092] Edit test for precise rotation popover --- .../Editor/TestScenePreciseRotation.cs | 41 +++++++++++++++++-- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePreciseRotation.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePreciseRotation.cs index d7dd30d608..67283f40da 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePreciseRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePreciseRotation.cs @@ -24,14 +24,38 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor [Test] public void TestHotkeyHandling() { - AddStep("select single circle", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.OfType().First())); + AddStep("deselect everything", () => EditorBeatmap.SelectedHitObjects.Clear()); AddStep("press rotate hotkey", () => { InputManager.PressKey(Key.ControlLeft); InputManager.Key(Key.R); InputManager.ReleaseKey(Key.ControlLeft); }); - AddUntilStep("no popover present", () => this.ChildrenOfType().Count(), () => Is.Zero); + AddUntilStep("no popover present", getPopover, () => Is.Null); + + AddStep("select single circle", + () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.OfType().First())); + AddStep("press rotate hotkey", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.R); + InputManager.ReleaseKey(Key.ControlLeft); + }); + AddUntilStep("popover present", getPopover, () => Is.Not.Null); + AddAssert("only playfield centre origin rotation available", () => + { + var popover = getPopover(); + var buttons = popover.ChildrenOfType(); + return buttons.Any(btn => btn.Text == "Selection centre" && btn.Enabled.Value is false) && + buttons.Any(btn => btn.Text == "Playfield centre" && btn.Enabled.Value is true); + }); + AddStep("press rotate hotkey", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.R); + InputManager.ReleaseKey(Key.ControlLeft); + }); + AddUntilStep("no popover present", getPopover, () => Is.Null); AddStep("select first three objects", () => { @@ -44,14 +68,23 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor InputManager.Key(Key.R); InputManager.ReleaseKey(Key.ControlLeft); }); - AddUntilStep("popover present", () => this.ChildrenOfType().Count(), () => Is.EqualTo(1)); + AddUntilStep("popover present", getPopover, () => Is.Not.Null); + AddAssert("both origin rotation available", () => + { + var popover = getPopover(); + var buttons = popover.ChildrenOfType(); + return buttons.Any(btn => btn.Text == "Selection centre" && btn.Enabled.Value is true) && + buttons.Any(btn => btn.Text == "Playfield centre" && btn.Enabled.Value is true); + }); AddStep("press rotate hotkey", () => { InputManager.PressKey(Key.ControlLeft); InputManager.Key(Key.R); InputManager.ReleaseKey(Key.ControlLeft); }); - AddUntilStep("no popover present", () => this.ChildrenOfType().Count(), () => Is.Zero); + AddUntilStep("no popover present", getPopover, () => Is.Null); + + PreciseRotationPopover? getPopover() => this.ChildrenOfType().SingleOrDefault(); } [Test] From 2fa52de87a07a7bbdbbde850ccd47d5722d1cc55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=C3=AAn=20Minh=20H=E1=BB=93?= Date: Thu, 25 Jan 2024 15:52:57 +0700 Subject: [PATCH 2700/4092] Fix formatting --- .../Editor/TestScenePreciseRotation.cs | 8 ++++---- osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs | 3 ++- .../Edit/Components/RadioButtons/EditorRadioButton.cs | 3 --- .../Screens/Edit/Components/RadioButtons/RadioButton.cs | 1 + 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePreciseRotation.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePreciseRotation.cs index 67283f40da..30e0dbbf2e 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePreciseRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePreciseRotation.cs @@ -46,8 +46,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { var popover = getPopover(); var buttons = popover.ChildrenOfType(); - return buttons.Any(btn => btn.Text == "Selection centre" && btn.Enabled.Value is false) && - buttons.Any(btn => btn.Text == "Playfield centre" && btn.Enabled.Value is true); + return buttons.Any(btn => btn.Text == "Selection centre" && !btn.Enabled.Value) + && buttons.Any(btn => btn.Text == "Playfield centre" && btn.Enabled.Value); }); AddStep("press rotate hotkey", () => { @@ -73,8 +73,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { var popover = getPopover(); var buttons = popover.ChildrenOfType(); - return buttons.Any(btn => btn.Text == "Selection centre" && btn.Enabled.Value is true) && - buttons.Any(btn => btn.Text == "Playfield centre" && btn.Enabled.Value is true); + return buttons.Any(btn => btn.Text == "Selection centre" && btn.Enabled.Value) + && buttons.Any(btn => btn.Text == "Playfield centre" && btn.Enabled.Value); }); AddStep("press rotate hotkey", () => { diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs index 0ce78e4f61..cd01fc9f4d 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs @@ -19,7 +19,8 @@ namespace osu.Game.Rulesets.Osu.Edit { public partial class OsuSelectionRotationHandler : SelectionRotationHandler { - public BindableBool CanRotatePlayfieldOrigin { get; private set; } = new(); + public BindableBool CanRotatePlayfieldOrigin { get; private set; } = new BindableBool(); + [Resolved] private IEditorChangeHandler? changeHandler { get; set; } diff --git a/osu.Game/Screens/Edit/Components/RadioButtons/EditorRadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/EditorRadioButton.cs index 601548fadd..9d1f87e1e0 100644 --- a/osu.Game/Screens/Edit/Components/RadioButtons/EditorRadioButton.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/EditorRadioButton.cs @@ -33,9 +33,6 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons private Drawable icon = null!; - [Resolved] - private EditorBeatmap? editorBeatmap { get; set; } - public EditorRadioButton(RadioButton button) { Button = button; diff --git a/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs index 1b47c028ab..2d1416c9c6 100644 --- a/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs @@ -20,6 +20,7 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons /// Tooltip text that will be shown on hover if button is enabled. /// public LocalisableString TooltipTextWhenEnabled { get; set; } = string.Empty; + /// /// Tooltip text that will be shown on hover if button is disabled. /// From d5b70ed09a68c8c99484df97dd4fbd245c233e8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=C3=AAn=20Minh=20H=E1=BB=93?= Date: Thu, 25 Jan 2024 16:56:59 +0700 Subject: [PATCH 2701/4092] Move CanRotatePlayfieldOrigin bindable to generic rotation handler --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 5 +---- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 2 +- osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs | 2 -- osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs | 3 ++- .../Edit/Compose/Components/SelectionRotationHandler.cs | 6 +++++- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index a0fb0b06c3..448cfaf84c 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -101,10 +101,7 @@ namespace osu.Game.Rulesets.Osu.Edit RightToolbox.AddRange(new EditorToolboxGroup[] { - new TransformToolboxGroup - { - RotationHandler = (OsuSelectionRotationHandler)BlueprintContainer.SelectionHandler.RotationHandler, - }, + new TransformToolboxGroup { RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, }, FreehandlSliderToolboxGroup } ); diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 7e645bc670..cea2adc6e2 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -164,7 +164,7 @@ namespace osu.Game.Rulesets.Osu.Edit if ((reference & Anchor.y0) > 0) scale.Y = -scale.Y; } - public override OsuSelectionRotationHandler CreateRotationHandler() => new OsuSelectionRotationHandler(); + public override SelectionRotationHandler CreateRotationHandler() => new OsuSelectionRotationHandler(); private void scaleSlider(Slider slider, Vector2 scale) { diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs index cd01fc9f4d..1998e02a5c 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs @@ -19,8 +19,6 @@ namespace osu.Game.Rulesets.Osu.Edit { public partial class OsuSelectionRotationHandler : SelectionRotationHandler { - public BindableBool CanRotatePlayfieldOrigin { get; private set; } = new BindableBool(); - [Resolved] private IEditorChangeHandler? changeHandler { get; set; } diff --git a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs index c70f35c6fb..19590e9b6e 100644 --- a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs @@ -11,6 +11,7 @@ using osu.Framework.Input.Events; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Components; +using osu.Game.Screens.Edit.Compose.Components; using osuTK; namespace osu.Game.Rulesets.Osu.Edit @@ -21,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Edit private EditorToolButton rotateButton = null!; - public OsuSelectionRotationHandler RotationHandler { get; init; } = null!; + public SelectionRotationHandler RotationHandler { get; init; } = null!; public TransformToolboxGroup() : base("transform") diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs index 749e1aab17..459e4b0c41 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs @@ -14,10 +14,14 @@ namespace osu.Game.Screens.Edit.Compose.Components { /// /// Whether rotation anchored by the selection origin can currently be performed. - /// This is in constrast to rotation anchored by the entire field. /// public Bindable CanRotateSelectionOrigin { get; private set; } = new BindableBool(); + /// + /// Whether rotation anchored by the center of the playfield can currently be performed. + /// + public Bindable CanRotatePlayfieldOrigin { get; private set; } = new BindableBool(); + /// /// Performs a single, instant, atomic rotation operation. /// From a264ac9f381dc71d8b8d79af70c61d168a9ed91f Mon Sep 17 00:00:00 2001 From: Mike Will Date: Thu, 25 Jan 2024 05:12:54 -0500 Subject: [PATCH 2702/4092] Change name and description of `force` parameter in `SetArbitraryDivisor` --- osu.Game/Screens/Edit/BindableBeatDivisor.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/BindableBeatDivisor.cs b/osu.Game/Screens/Edit/BindableBeatDivisor.cs index 87cb191a82..4b0726658f 100644 --- a/osu.Game/Screens/Edit/BindableBeatDivisor.cs +++ b/osu.Game/Screens/Edit/BindableBeatDivisor.cs @@ -29,11 +29,11 @@ namespace osu.Game.Screens.Edit /// Set a divisor, updating the valid divisor range appropriately. /// /// The intended divisor. - /// Ignores the current valid divisor range when true. - public void SetArbitraryDivisor(int divisor, bool force = false) + /// Forces changing the valid divisors to a known preset. + public void SetArbitraryDivisor(int divisor, bool preferKnownPresets = false) { // If the current valid divisor range doesn't contain the proposed value, attempt to find one which does. - if (force || !ValidDivisors.Value.Presets.Contains(divisor)) + if (preferKnownPresets || !ValidDivisors.Value.Presets.Contains(divisor)) { if (BeatDivisorPresetCollection.COMMON.Presets.Contains(divisor)) ValidDivisors.Value = BeatDivisorPresetCollection.COMMON; From 8aea6e07c31127f9b93540d9b99c295f9ca3463c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jan 2024 16:31:33 +0900 Subject: [PATCH 2703/4092] Change slider end miss colour to gray --- osu.Game/Graphics/OsuColour.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 1b5877b966..985898958c 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -77,7 +77,7 @@ namespace osu.Game.Graphics { case HitResult.IgnoreMiss: case HitResult.SmallTickMiss: - return Orange1; + return Color4.Gray; case HitResult.Miss: case HitResult.LargeTickMiss: From dda96d71063796af8a3d4344ba080005447283e2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jan 2024 16:33:52 +0900 Subject: [PATCH 2704/4092] Rename `JudgementPiece` to `TextJudgementPiece` --- osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs | 2 +- osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs | 2 +- osu.Game.Rulesets.Taiko/Skinning/Argon/ArgonJudgementPiece.cs | 2 +- osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs | 2 +- .../Judgements/{JudgementPiece.cs => TextJudgementPiece.cs} | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) rename osu.Game/Rulesets/Judgements/{JudgementPiece.cs => TextJudgementPiece.cs} (88%) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs index a191dee1ca..0052fd8b78 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs @@ -17,7 +17,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Skinning.Argon { - public partial class ArgonJudgementPiece : JudgementPiece, IAnimatableJudgement + public partial class ArgonJudgementPiece : TextJudgementPiece, IAnimatableJudgement { private const float judgement_y_position = 160; diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs index 9a5abba4fb..83992fc785 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs @@ -16,7 +16,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Skinning.Argon { - public partial class ArgonJudgementPiece : JudgementPiece, IAnimatableJudgement + public partial class ArgonJudgementPiece : TextJudgementPiece, IAnimatableJudgement { private RingExplosion? ringExplosion; diff --git a/osu.Game.Rulesets.Taiko/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Taiko/Skinning/Argon/ArgonJudgementPiece.cs index bbd62ff85b..724e387cc7 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Argon/ArgonJudgementPiece.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Argon/ArgonJudgementPiece.cs @@ -17,7 +17,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Taiko.Skinning.Argon { - public partial class ArgonJudgementPiece : JudgementPiece, IAnimatableJudgement + public partial class ArgonJudgementPiece : TextJudgementPiece, IAnimatableJudgement { private RingExplosion? ringExplosion; diff --git a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs index 7330f138ce..458d79cc00 100644 --- a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs +++ b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs @@ -10,7 +10,7 @@ using osuTK; namespace osu.Game.Rulesets.Judgements { - public partial class DefaultJudgementPiece : JudgementPiece, IAnimatableJudgement + public partial class DefaultJudgementPiece : TextJudgementPiece, IAnimatableJudgement { public DefaultJudgementPiece(HitResult result) : base(result) diff --git a/osu.Game/Rulesets/Judgements/JudgementPiece.cs b/osu.Game/Rulesets/Judgements/TextJudgementPiece.cs similarity index 88% rename from osu.Game/Rulesets/Judgements/JudgementPiece.cs rename to osu.Game/Rulesets/Judgements/TextJudgementPiece.cs index 03f211c318..42527705eb 100644 --- a/osu.Game/Rulesets/Judgements/JudgementPiece.cs +++ b/osu.Game/Rulesets/Judgements/TextJudgementPiece.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Judgements { - public abstract partial class JudgementPiece : CompositeDrawable + public abstract partial class TextJudgementPiece : CompositeDrawable { protected readonly HitResult Result; @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Judgements [Resolved] private OsuColour colours { get; set; } = null!; - protected JudgementPiece(HitResult result) + protected TextJudgementPiece(HitResult result) { Result = result; } From 6070eac6eec64852a8aa27a2c283cf9388c6ad5c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jan 2024 18:48:14 +0900 Subject: [PATCH 2705/4092] Remove dead code --- osu.Game/Skinning/LegacyJudgementPieceNew.cs | 2 +- osu.Game/Skinning/LegacyJudgementPieceOld.cs | 12 +----------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/osu.Game/Skinning/LegacyJudgementPieceNew.cs b/osu.Game/Skinning/LegacyJudgementPieceNew.cs index 5ff28726c0..bd1508b4a6 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceNew.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceNew.cs @@ -53,7 +53,7 @@ namespace osu.Game.Skinning if (!result.IsMiss()) { //new judgement shows old as a temporary effect - AddInternal(temporaryOldStyle = new LegacyJudgementPieceOld(result, createMainDrawable, 1.05f, true) + AddInternal(temporaryOldStyle = new LegacyJudgementPieceOld(result, createMainDrawable, 1.05f) { Blending = BlendingParameters.Additive, Anchor = Anchor.Centre, diff --git a/osu.Game/Skinning/LegacyJudgementPieceOld.cs b/osu.Game/Skinning/LegacyJudgementPieceOld.cs index a9f68bd378..55c81e4e41 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceOld.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceOld.cs @@ -18,16 +18,14 @@ namespace osu.Game.Skinning private readonly HitResult result; private readonly float finalScale; - private readonly bool forceTransforms; [Resolved] private ISkinSource skin { get; set; } = null!; - public LegacyJudgementPieceOld(HitResult result, Func createMainDrawable, float finalScale = 1f, bool forceTransforms = false) + public LegacyJudgementPieceOld(HitResult result, Func createMainDrawable, float finalScale = 1f) { this.result = result; this.finalScale = finalScale; - this.forceTransforms = forceTransforms; AutoSizeAxes = Axes.Both; Origin = Anchor.Centre; @@ -48,14 +46,6 @@ namespace osu.Game.Skinning this.FadeInFromZero(fade_in_length); this.Delay(fade_out_delay).FadeOut(fade_out_length); - // legacy judgements don't play any transforms if they are an animation.... UNLESS they are the temporary displayed judgement from new piece. - if (animation?.FrameCount > 1 && !forceTransforms) - { - if (isMissedTick()) - applyMissedTickScaling(); - return; - } - if (result.IsMiss()) { decimal? legacyVersion = skin.GetConfig(SkinConfiguration.LegacySetting.Version)?.Value; From d0421fe20667530bf1bca1a5c8e3f387dde0cf6a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jan 2024 18:50:45 +0900 Subject: [PATCH 2706/4092] Move fade more local to avoid fading twice --- osu.Game/Skinning/LegacyJudgementPieceOld.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyJudgementPieceOld.cs b/osu.Game/Skinning/LegacyJudgementPieceOld.cs index 55c81e4e41..c15ff041d1 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceOld.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceOld.cs @@ -44,7 +44,6 @@ namespace osu.Game.Skinning const double fade_out_length = 600; this.FadeInFromZero(fade_in_length); - this.Delay(fade_out_delay).FadeOut(fade_out_length); if (result.IsMiss()) { @@ -74,6 +73,8 @@ namespace osu.Game.Skinning this.RotateTo(0); this.RotateTo(rotation, fade_in_length) .Then().RotateTo(rotation * 2, fade_out_delay + fade_out_length - fade_in_length, Easing.In); + + this.Delay(fade_out_delay).FadeOut(fade_out_length); } } else @@ -87,6 +88,8 @@ namespace osu.Game.Skinning // so we need to force the current value to be correct at 1.2 (0.95) then complete the // second half of the transform. .ScaleTo(0.95f).ScaleTo(finalScale, fade_in_length * 0.2f); // t = 1.4 + + this.Delay(fade_out_delay).FadeOut(fade_out_length); } } From 0175f6e0b8f5357a161211474e07af840dcf1852 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jan 2024 18:23:25 +0900 Subject: [PATCH 2707/4092] Add new judgement drawable for argon slider misses --- .../ArgonJudgementPieceSliderTickMiss.cs | 51 +++++++++++++++++++ .../Skinning/Argon/ArgonSliderScorePoint.cs | 4 +- .../Skinning/Argon/OsuArgonSkinTransformer.cs | 14 ++++- 3 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPieceSliderTickMiss.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPieceSliderTickMiss.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPieceSliderTickMiss.cs new file mode 100644 index 0000000000..878e8dbfc2 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPieceSliderTickMiss.cs @@ -0,0 +1,51 @@ +// 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.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Skinning.Argon +{ + public partial class ArgonJudgementPieceSliderTickMiss : CompositeDrawable, IAnimatableJudgement + { + private readonly HitResult result; + private Circle piece = null!; + + [Resolved] + private OsuColour colours { get; set; } = null!; + + public ArgonJudgementPieceSliderTickMiss(HitResult result) + { + this.result = result; + } + + [BackgroundDependencyLoader] + private void load() + { + AddInternal(piece = new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Blending = BlendingParameters.Additive, + Colour = colours.ForHitResult(result), + Size = new Vector2(ArgonSliderScorePoint.SIZE) + }); + } + + public void PlayAnimation() + { + this.ScaleTo(1.4f); + this.ScaleTo(1f, 150, Easing.Out); + + this.FadeOutFromOne(400); + } + + public Drawable? GetAboveHitObjectsProxiedContent() => piece.CreateProxy(); + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderScorePoint.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderScorePoint.cs index 7479c2aced..e9ee432bac 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderScorePoint.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderScorePoint.cs @@ -16,14 +16,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon { private Bindable accentColour = null!; - private const float size = 12; + public const float SIZE = 12; [BackgroundDependencyLoader] private void load(DrawableHitObject hitObject) { Masking = true; Origin = Anchor.Centre; - Size = new Vector2(size); + Size = new Vector2(SIZE); BorderThickness = 3; BorderColour = Color4.White; Child = new Box diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs index 0f9c97059c..ec63e1194d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs @@ -19,11 +19,21 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon switch (lookup) { case GameplaySkinComponentLookup resultComponent: + HitResult result = resultComponent.Component; + // This should eventually be moved to a skin setting, when supported. - if (Skin is ArgonProSkin && (resultComponent.Component == HitResult.Great || resultComponent.Component == HitResult.Perfect)) + if (Skin is ArgonProSkin && (result == HitResult.Great || result == HitResult.Perfect)) return Drawable.Empty(); - return new ArgonJudgementPiece(resultComponent.Component); + switch (result) + { + case HitResult.IgnoreMiss: + case HitResult.LargeTickMiss: + return new ArgonJudgementPieceSliderTickMiss(result); + + default: + return new ArgonJudgementPiece(result); + } case OsuSkinComponentLookup osuComponent: // TODO: Once everything is finalised, consider throwing UnsupportedSkinComponentException on missing entries. From 107b37494ede1ca66a1b2cf4d669102d4c9804d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jan 2024 19:14:04 +0900 Subject: [PATCH 2708/4092] Update triangles skin judgment display --- .../Objects/Drawables/DrawableSliderTick.cs | 6 +-- osu.Game.Rulesets.Osu/OsuRuleset.cs | 4 ++ .../DefaultJudgementPieceSliderTickMiss.cs | 52 +++++++++++++++++++ .../Default/OsuTrianglesSkinTransformer.cs | 38 ++++++++++++++ .../Judgements/DefaultJudgementPiece.cs | 14 ----- osu.Game/Rulesets/Scoring/HitResult.cs | 2 - 6 files changed, 97 insertions(+), 19 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Skinning/Default/DefaultJudgementPieceSliderTickMiss.cs create mode 100644 osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index d64fb0bcc6..e457a50128 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public const double ANIM_DURATION = 150; - private const float default_tick_size = 16; + public const float DEFAULT_TICK_SIZE = 16; protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject; @@ -44,8 +44,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { Masking = true, Origin = Anchor.Centre, - Size = new Vector2(default_tick_size), - BorderThickness = default_tick_size / 4, + Size = new Vector2(DEFAULT_TICK_SIZE), + BorderThickness = DEFAULT_TICK_SIZE / 4, BorderColour = Color4.White, Child = new Box { diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 0496d1f680..6752712be1 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -28,6 +28,7 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Osu.Skinning.Argon; +using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Rulesets.Osu.Skinning.Legacy; using osu.Game.Rulesets.Osu.Statistics; using osu.Game.Rulesets.Osu.UI; @@ -254,6 +255,9 @@ namespace osu.Game.Rulesets.Osu case ArgonSkin: return new OsuArgonSkinTransformer(skin); + + case TrianglesSkin: + return new OsuTrianglesSkinTransformer(skin); } return null; diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultJudgementPieceSliderTickMiss.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultJudgementPieceSliderTickMiss.cs new file mode 100644 index 0000000000..9fc71852ba --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultJudgementPieceSliderTickMiss.cs @@ -0,0 +1,52 @@ +// 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.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Scoring; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Skinning.Default +{ + public partial class DefaultJudgementPieceSliderTickMiss : CompositeDrawable, IAnimatableJudgement + { + private readonly HitResult result; + private Circle piece = null!; + + [Resolved] + private OsuColour colours { get; set; } = null!; + + public DefaultJudgementPieceSliderTickMiss(HitResult result) + { + this.result = result; + } + + [BackgroundDependencyLoader] + private void load() + { + AddInternal(piece = new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Blending = BlendingParameters.Additive, + Colour = colours.ForHitResult(result), + Size = new Vector2(DrawableSliderTick.DEFAULT_TICK_SIZE) + }); + } + + public void PlayAnimation() + { + this.ScaleTo(1.4f); + this.ScaleTo(1f, 150, Easing.Out); + + this.FadeOutFromOne(400); + } + + public Drawable? GetAboveHitObjectsProxiedContent() => piece.CreateProxy(); + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs new file mode 100644 index 0000000000..7a4c768aa2 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs @@ -0,0 +1,38 @@ +// 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.Graphics; +using osu.Game.Rulesets.Scoring; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Osu.Skinning.Default +{ + public class OsuTrianglesSkinTransformer : SkinTransformer + { + public OsuTrianglesSkinTransformer(ISkin skin) + : base(skin) + { + } + + public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) + { + switch (lookup) + { + case GameplaySkinComponentLookup resultComponent: + HitResult result = resultComponent.Component; + + switch (result) + { + case HitResult.IgnoreMiss: + case HitResult.LargeTickMiss: + // use argon judgement piece for new tick misses because i don't want to design another one for triangles. + return new DefaultJudgementPieceSliderTickMiss(result); + } + + break; + } + + return base.GetDrawableComponent(lookup); + } + } +} diff --git a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs index 458d79cc00..61b72a6066 100644 --- a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs +++ b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs @@ -38,20 +38,6 @@ namespace osu.Game.Rulesets.Judgements /// public virtual void PlayAnimation() { - // TODO: make these better. currently they are using a text `-` and it's not centered properly. - // Should be an explicit drawable. - // - // When this is done, remove the [Description] attributes from HitResults which were added for this purpose. - if (Result == HitResult.IgnoreMiss || Result == HitResult.LargeTickMiss) - { - this.RotateTo(-45); - this.ScaleTo(1.6f); - this.ScaleTo(1.2f, 100, Easing.In); - - this.FadeOutFromOne(400); - return; - } - if (Result.IsMiss()) { this.ScaleTo(1.6f); diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index 7e58df3cfa..20ec3c4946 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -86,7 +86,6 @@ namespace osu.Game.Rulesets.Scoring /// Indicates a large tick miss. /// [EnumMember(Value = "large_tick_miss")] - [Description("-")] [Order(11)] LargeTickMiss, @@ -118,7 +117,6 @@ namespace osu.Game.Rulesets.Scoring /// Indicates a miss that should be ignored for scoring purposes. /// [EnumMember(Value = "ignore_miss")] - [Description("-")] [Order(14)] IgnoreMiss, From fd9527d5233fd6254d4b8a627fd4b07588dbc496 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jan 2024 19:25:42 +0900 Subject: [PATCH 2709/4092] Remove weird red fade that didn't work --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index e457a50128..73c061afbd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -88,8 +88,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables break; case ArmedState.Miss: - this.FadeOut(ANIM_DURATION); - this.TransformBindableTo(AccentColour, Color4.Red, 0); + this.FadeOut(ANIM_DURATION, Easing.OutQuint); break; case ArmedState.Hit: From 56a9b059e9ff32b1be5288cd7f4a92921cc49542 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jan 2024 19:42:38 +0900 Subject: [PATCH 2710/4092] Add back `ScaleAdjust` to triangles --- osu.Game/Graphics/Backgrounds/TrianglesV2.cs | 24 ++++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/TrianglesV2.cs b/osu.Game/Graphics/Backgrounds/TrianglesV2.cs index 706b05f5ad..bcb73f71cc 100644 --- a/osu.Game/Graphics/Backgrounds/TrianglesV2.cs +++ b/osu.Game/Graphics/Backgrounds/TrianglesV2.cs @@ -1,17 +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.Framework.Utils; -using osuTK; using System; -using osu.Framework.Graphics.Shaders; -using osu.Framework.Graphics.Textures; -using osu.Framework.Graphics.Primitives; -using osu.Framework.Allocation; using System.Collections.Generic; -using osu.Framework.Graphics.Rendering; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Rendering; +using osu.Framework.Graphics.Shaders; +using osu.Framework.Graphics.Textures; +using osu.Framework.Utils; +using osuTK; namespace osu.Game.Graphics.Backgrounds { @@ -27,6 +28,8 @@ namespace osu.Game.Graphics.Backgrounds public float Thickness { get; set; } = 0.02f; // No need for invalidation since it's happening in Update() + public float ScaleAdjust { get; set; } = 1; + /// /// Whether we should create new triangles as others expire. /// @@ -106,7 +109,7 @@ namespace osu.Game.Graphics.Backgrounds parts[i] = newParticle; - float bottomPos = parts[i].Position.Y + triangle_size * equilateral_triangle_ratio / DrawHeight; + float bottomPos = parts[i].Position.Y + triangle_size * ScaleAdjust * equilateral_triangle_ratio / DrawHeight; if (bottomPos < 0) parts.RemoveAt(i); } @@ -149,7 +152,7 @@ namespace osu.Game.Graphics.Backgrounds if (randomY) { // since triangles are drawn from the top - allow them to be positioned a bit above the screen - float maxOffset = triangle_size * equilateral_triangle_ratio / DrawHeight; + float maxOffset = triangle_size * ScaleAdjust * equilateral_triangle_ratio / DrawHeight; y = Interpolation.ValueAt(nextRandom(), -maxOffset, 1f, 0f, 1f); } @@ -188,7 +191,7 @@ namespace osu.Game.Graphics.Backgrounds private readonly List parts = new List(); - private readonly Vector2 triangleSize = new Vector2(1f, equilateral_triangle_ratio) * triangle_size; + private Vector2 triangleSize; private Vector2 size; private float thickness; @@ -209,6 +212,7 @@ namespace osu.Game.Graphics.Backgrounds size = Source.DrawSize; thickness = Source.Thickness; clampAxes = Source.ClampAxes; + triangleSize = new Vector2(1f, equilateral_triangle_ratio) * triangle_size * Source.ScaleAdjust; Quad triangleQuad = new Quad( Vector2Extensions.Transform(Vector2.Zero, DrawInfo.Matrix), From 4a629fbc799093c96ec824f979c7258cdf5370ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jan 2024 19:43:08 +0900 Subject: [PATCH 2711/4092] Update logo to new version --- osu.Game/Screens/Menu/OsuLogo.cs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 75ef8be02e..624c78bdb8 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -10,6 +10,7 @@ using osu.Framework.Audio.Sample; using osu.Framework.Audio.Track; 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.Framework.Graphics.Sprites; @@ -31,15 +32,13 @@ namespace osu.Game.Screens.Menu /// public partial class OsuLogo : BeatSyncedContainer { - public readonly Color4 OsuPink = Color4Extensions.FromHex(@"e967a1"); - private const double transition_length = 300; /// /// The osu! logo sprite has a shadow included in its texture. /// This adjustment vector is used to match the precise edge of the border of the logo. /// - public static readonly Vector2 SCALE_ADJUST = new Vector2(0.96f); + public static readonly Vector2 SCALE_ADJUST = new Vector2(0.94f); private readonly Sprite logo; private readonly CircularContainer logoContainer; @@ -58,7 +57,7 @@ namespace osu.Game.Screens.Menu private Sample sampleDownbeat; private readonly Container colourAndTriangles; - private readonly Triangles triangles; + private readonly TrianglesV2 triangles; /// /// Return value decides whether the logo should play its own sample for the click action. @@ -184,13 +183,17 @@ namespace osu.Game.Screens.Menu new Box { RelativeSizeAxes = Axes.Both, - Colour = OsuPink, + Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex(@"ff66ab"), Color4Extensions.FromHex(@"cc5289")), }, - triangles = new Triangles + triangles = new TrianglesV2 { - TriangleScale = 4, - ColourLight = Color4Extensions.FromHex(@"ff7db7"), - ColourDark = Color4Extensions.FromHex(@"de5b95"), + ClampAxes = Axes.X, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Thickness = 0.009f, + ScaleAdjust = 3, + SpawnRatio = 1.2f, + Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex(@"ff66ab"), Color4Extensions.FromHex(@"b6346f")), RelativeSizeAxes = Axes.Both, }, } From 7476d307d3b71f46aec187b2c16b60a51458d959 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jan 2024 19:49:52 +0900 Subject: [PATCH 2712/4092] Update repository resources --- assets/lazer-nuget.png | Bin 12471 -> 12770 bytes assets/lazer.png | Bin 191397 -> 326320 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/assets/lazer-nuget.png b/assets/lazer-nuget.png index c2a587fdc26734c1f263023d7eefa8fd644e978b..98a152a5a05b40b857780b2682be6a126151ba72 100644 GIT binary patch literal 12770 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 literal 12471 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 BjmH20 diff --git a/assets/lazer.png b/assets/lazer.png index 1e40e844ccb4f6065bbdb28a591af35ed78ada35..484bc0a449bb2ee813fad5180a1f74d368d75e2e 100644 GIT binary patch literal 326320 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⩔30V^aN^)*_D&yQ2xLHAa-!JJ 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 z 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 literal 191397 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

grX5kAnI7=yXit`@W>Pbv5O3$W*%9No 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%0D!HvI2srO7R>s0u0X(o9pX;k-Xz_y?7-o4VgT%# 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.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 VCItYF2In1!ZDDF{Qek{8`d_IH0SW*B From 7e8b3d03fa583a86ffafbdff573e9ed51a9178df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jan 2024 19:56:40 +0900 Subject: [PATCH 2713/4092] Update copyright years --- Directory.Build.props | 2 +- LICENCE | 2 +- Templates/osu.Game.Templates.csproj | 4 ++-- osu.Desktop/osu.nuspec | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 734374c840..b08283f071 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -35,7 +35,7 @@ https://github.com/ppy/osu Automated release. ppy Pty Ltd - Copyright (c) 2022 ppy Pty Ltd + Copyright (c) 2024 ppy Pty Ltd osu game diff --git a/LICENCE b/LICENCE index d3e7537cef..3bb8b62d5d 100644 --- a/LICENCE +++ b/LICENCE @@ -1,4 +1,4 @@ -Copyright (c) 2022 ppy Pty Ltd . +Copyright (c) 2024 ppy Pty Ltd . Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Templates/osu.Game.Templates.csproj b/Templates/osu.Game.Templates.csproj index b8c3ad373a..186a6093f5 100644 --- a/Templates/osu.Game.Templates.csproj +++ b/Templates/osu.Game.Templates.csproj @@ -1,4 +1,4 @@ - + Template ppy.osu.Game.Templates @@ -8,7 +8,7 @@ https://github.com/ppy/osu/blob/master/Templates https://github.com/ppy/osu Automated release. - Copyright (c) 2022 ppy Pty Ltd + Copyright (c) 2024 ppy Pty Ltd Templates to use when creating a ruleset for consumption in osu!. dotnet-new;templates;osu netstandard2.1 diff --git a/osu.Desktop/osu.nuspec b/osu.Desktop/osu.nuspec index db58c325bd..3dadc4e002 100644 --- a/osu.Desktop/osu.nuspec +++ b/osu.Desktop/osu.nuspec @@ -11,7 +11,7 @@ false A free-to-win rhythm game. Rhythm is just a *click* away! testing - Copyright (c) 2022 ppy Pty Ltd + Copyright (c) 2024 ppy Pty Ltd en-AU From 2941787f5eec8543f68bbc9be89ca69b118ad9c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jan 2024 20:35:33 +0900 Subject: [PATCH 2714/4092] Update windows logo --- osu.Desktop/lazer.ico | Bin 86650 -> 67263 bytes 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 osu.Desktop/lazer.ico diff --git a/osu.Desktop/lazer.ico b/osu.Desktop/lazer.ico old mode 100755 new mode 100644 index a6aa8abb9f3f10a2aac07c1168d9a2adb25ce1ea..5051c00ffc79eceac173442cd0466750d8e3aaa6 GIT binary patch literal 67263 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 literal 86650 zcmeFZby!qg^gp_(p@boaF6j~wL6H~`QA+8Q?(T*ekPr}&5*1KEq)|dZ#Q;n!8YKil zDFLM$X6Ej}*Y|zD@r&o)`^SBr`&)3%KKtzTS-aNRXMOe&0U!Z%fS;cMrsUuUDFB}V z01OPj^I{|bw2%QHD+^#b6{w&GV4562{lD^509i^DrPynFF4Glmc0sO!;6@Xg; z0H~|~&WoJ@V5RDhJ$_Ij3;^dZ0K~+8mwRvm@PY{<`G4mtpj`=R62Q*>JD)-gK(8tZ zU}5>4e}n+Q`ZUR3_45M{*fyrKBmkO&wZnShLF^Y%;$YwY2sHaIiGzhO8yELGfoFIL zk2uiQ)&>g*`@eudAYQ=({t#~wPyd}DGg4AAmi{h5rtHg~8!(%mJQ$IZ^4E=&l<@TA zl!4@wjC}+$B^{HTJTQpCq{I<#5K>Yw=@H@S5fOt#1>_bi3x_HZgW*IANL)mC#6U7E z9~c~j0782500gHeLyhYx0Bpg+hykb)F))yx0^x%Z7!1+Gz(B-c$`UdKo-rxJb2|Kq zL#C%>5K=O>(uqwb=2Hf@GPY7+hhQ#wzkF+OYY;YS5dIGCmlHJxiTPxhPfvk4sJ``= zy<~WqoC5WU0?W4sp(;^(FnQnpRzyU2czAL;{Dt`?u$8_q$?0%RQegWDDX=sGb|N`_ ze*lOEVE@_jgQ03b9XIUROt3yv320V?kg-|qn@Knvj!;e#oE%^s52 z8Q3Jk-xEaGN=YI7orNagO#drOwE9PgC4_NG0wIosfCnIU4*)c*mXA0y;{Z?+k2V0v zh({cB#s8h-kN)^yc%m($O`>gL9mKl!>x5Mk{hR?F{P6hW=WX!%j}XiLkvQnoFbDm5 zKY?d>`M>Mugx~&2oJVMbDg`Wo$?s!d z<6oyg_lY`0T_T2vC61FUJRIObT*rtKN3);&K0$kb6%x4Ldd#!;A?^RIYw+z!Y zQ0cP{s{OXWUH>gm6}SZMhAxBZfDLdrXdTpqz$0=Q)CBH;+Mqp97q$WFqEBk|xt47`q8294)ez=Na}&=kK8(>3tu!UlMhx&oS0 z*T9oYYvAeS4bU8q2Tzmn;4yq4o~3Vr)=PNsEN2 zXuGlnUSt!%%j?_VRsI%ua}yqUd*FQ`9<g15yR;C*V+kH~ zRBeH-yW60YFvmG$l zj0ZzcabTpC0EXLkz{rbTF#382d}-T**EsO?H9X$#fblnYF#c{AeD8qQ?{F~1fvGMW znC#sJQ~mJh!GY;dd*H_>JeVEY1M{PM;OEypu<#uR7AJ9FX$l8ce&E2`JOQl!#DVn% z9N1XGgRK=j*x7)`77py~-~et92Mjd;2sj5oW(WXvmjURigue6=I%6**APmX@!l)4- zOa=nNToxcK)dRvt&)@F@8Tf88JP7g7;WE`A{tU!JTT7l0AIS&^7v%uqrV$|21_HwK zENHVH5T<(m!9(9QB!_tTZp(!4yE^EnT?Zf@zIzG$On{&!2M9JsfDjc32-mUz;Xyqh z^!NOO2lT)Yuz>&|05X9jr~`_i3%&`LfDRxqS_6Vy03aA$0EECBfRJ?`5bECoLeJR0 z@ca<32=V$5?*#D?5PuosOCY`h;#(lT9pYh~StAfX1@TLNXBai?#vj||jo&a`xi0{4_vOG7*w;*BBR8REkrJ_X|Q z7zv%z(_p-%=i3*r-FFm2(Ust7sLxdya>cgLA)9x0a(frK%fx; zTndD?vIwBH9{OF+KlnfE!2iuOIy^WKN^p40|JndH+~3~T5(@ETYwsV9`JZ88g6yG^ zyO+DGv$HEqAk;qSe~bvSv$XeeF;qV!$j!yYEhr$T>+Eh15&wM^5$=}u?k3XQtf=2V zj1*)P%!kd~?JV6R{~JW8t);uUB!Ue2TN}lSAR|N2(NRj7yIb19hvq*CHqhSvBrB4P zjDiKVUjgxwj*bpNN`Bnc*7iTQ35&&eDT#`*l97?op@_0?o}Qkbf{cvxfQ_AP;D4|m zi^Y2K@QU)%k-O%EE#mp|P^F4gMDZi^cl#kTHtFEg^YxR#?IC@>>mIY8!AOdK|yQ-5rIT8L3P+!2tc6_Bv2$>EM5NA$6{mD z=;;u|wvfUySOJn~9zrM>QB1@t$q=e8mi~VMU~3#0pfPBg0@h6myNsZK=3#9RN<0%U z^lWV-egiPra4Dz`m7qQ;$zSEa4Uqk%PD)B^Z|SqI56xR3;mG}N5g9a36k_Z5d%vHf zU`6p7+uH5VGX@(ejU-MHJ?!!y6{0%gFDMxTNsrCTvgM*q1+Mwj;=R}E$ zp0Txs>Toa*Lu0H$7D3@54w4jr8*z}Hc;=xt3&CKq-W_#;-I$O_s2M3mM1>3J!EQu29b&eyuoOaST3D$f4J{mG=}1WtmbOHFtPd|A zpQslWtHU54g$;ro#~8`-Gc$SkTFJ6%+1hC_SXek5rXVFHw;*~1>@T;7sK`kSHp(*$ zi#1_zgBDzabeMfH!ADX0&d%CU-&r0`2$vnvMPcOvC{bR1s~9YN%G@~Uj)cd?#$r9# z{jf2ztokmF+RRoK4#!wnScP3I;X@XS#VDghSW#S>&fbn_IJA_~79JiJ%FH?r<~)qY z%#02*L49>r7S>}f`_C8_Yeam$QLJpNlyD-TFtKv7vLNYU6OgP-NP1>#3r7w39P8WL z5&Z{?_2>S*L?M5x)3XqT32H+#5X5X_;dqLVPt2Y;oJ9TTWBc}*i0TLg9n-!(TqBSu zUWkJFP9CSk#ZS0f?k`)gmo}Wl3if>xy6SIrxNJcuN5Pwv{D*|OMa3=c?4pVGVUHt@ z!a|}c_>j@jL&I?WL9(zxK`}8hLhnbR_*C3~KQ2UltSjunzImt*m!5s0gXUS;L`6jR zMcmfTE^6OA5fGz|V%#@RPtQaI?DvHD3^D$tE_&LX=*)iu!eoiV2gi?%l@WnJ@i;atPIl)hC1#A(L3CYOBb<8FbYRCN->-#rgC$KRAX3E0c z%nZz&+(PPR0Wnbfzcc>ZKpcEfu(6oPkU&r0z>rAT5U73rzc>E-?)f;VLe$zn{jaqD zV4ymbxc{l;|5>g7DkIWaVi@+DV8V2taKe;GK#BJuHA529Hb`iRX&mHJ#FS_Qo}fCM z3&^>MDH^gkV#@bVN@R9`$m579VO0b%Z?C^oi>Ln0QGHkb=BU5<=`y$% zy7CV{4c!1W5zC-9a_w)9S|9zJpVmiifcr7)e{oYBXpDl~G-d}hMBqVFBmq3Y;6OwC z5@?Kv{FKO1&;P?wn=h__$Cp+?bIJyIa``t;eUb|K>1D`IGxosK>=n?Oy8@nNt%KGp zYoP7gDrn1r{4{rcpR2yg-vF;}!t1MB;CU7fyvp1C%~OjuAV1v%?;t<@P_Y3zsy9LB zJ;+x}AWtpd0`G6{fzEO~=&ag?{1x)mO8h=w?XJQ@&I+&VHbLJ5B4^zKeN9{76XdM@ zkRN>d!&@KiLC(4j1|Q?V@IGgS>GK^h`tmnt9chDH^%V|`y@MRJ9S6RB+yUR-L$3O9 z4@^R?I@R}^r~dei2Xn(XFgNm>qyGA~2RZ5)#w;_L3_j#sas;fL90AqI z$?5F&iBXI_j41XEzezA1V8i3qMygik-1;Vb^sL6P0WrFilom0k=xt+E4p;|ATdG^D zvRRt2sh?p(AkGr;M$9ZW7#%9-7@XXHcCo*uEWwO6hDgPAUDd&_As-69h@Re)Cpo>OixLVpk!cXh767v{-JE0j|yoz z5<}4#7j`)9Fsg*17%V3`2@DU9oDdN^2djq$A`~GU3akJMk*n_ukEY{qW*pZBsU6)pl4*_R&$Mr!TxUBKWA)AbcmO|Ioyc}js8#Izm%|;n0>+g7wkV){>#;V zxG?_zmG2I}_tVu${?*KXUEdSW1rg(q6;Knj4QfNTKrP${uM6LR`{T=?0kaMo&i~#6Z%kYQ&1tLPX$IW;CdL%$ zYv5VNI%rPV1dQ=-B~NaPNAi9|vZKcfi~i9Ne?U z!98oZUp)c$tKpvY`oiAdF~H8o@BL|-Isj<90H9xju_A#9s1oGBIf4<0Bm{!%ge=fR zs0aOofA)h}WB@p52)}>8WlJ>^0R1`utRNnSf*^vx2rfeW4T!IS_%?{|h4{&T;YA=` z-4K3f4FDh{6MpZl1E3P(Un~L81M!0p{}tjVAbu9&7a@M*U-MDE2M>xWp8D&K1`pgWq#~!_RNKFy%W;5|lA4^7h7v)%VaQDhU;6MBOi4z{t_6T5 zlA0WDL{h>RKNZ7KU~-67?l`BMETgCdrzESCzOe|`NqLn++A^}z;u7=%I>wTMrTysXprWIrpE#~WIVq*BRLfVxi+Z~m9bM@Z;tEiYJ-$sfBCrRtcK#5A%ikk)prKZuew|; zxek~d07({jtsg)lg^CQm+#uh;9DTeWvx$CKRbE#9WZpbE&(5K%ed^<($JKLRAJ3q- zxi#2k589RtNT-%!~xB(zuT7nl?H#kkupF?}f zN%rbilrTi%zMvs=-?P_^lIz-w=aUx$}x#s(hyRN)l?fa=6op)wJBe_C1%-)-6 z3Qf_>R$(oSui;OX8~5LCy~3|0hb!a3<(!XC45%7O96;6FZ$+FAN3NkxlPzTj1#?szCo&~*AckmKAw)S^L^%TWww>}ol?jG z%}sW}amy2v5xg_h>9M3#!8mP3rybo?6R1vjXtL9DiaP(bR{HHioeFA`<6$j-yFx6e5B7a^{ja1&<%Tz)+W%9MpJ8)i(@k5y+)=}no}G~wvn*-RrUwI0Bs{<3u4_{wT#^Rr$jL+P2=5Fpo3n{O zw56Ge!b#Nh4R0!Tm?Z3!mXuDUY+vGa*or)G`T}D@>oql#jphm=UBW{KpOZt2W`$jQ zA!%BrXylo1QsVU%u@U8l3uh0`Q23T`4>-tTdVDNn9#es|leZFT45o6gE-jqVZTA0s zAh6W!axVFWYln+oQ0BdJ7Mdd1ogccE|6b#{{b>dv(>&^LUh0NN?htmxaWj#)%Efui z_W6`{`%i`J&nkGN=%G`gn5s8&a>!WpelZxgocYp$E=4vYz+A5Ns|LMJcXoXIX6&I{ zxo9%um`X7!7d)ry_?WKu`VsPb2Y(QN{c~R2=Hh#HjEPdGA07G3p?HmXINC?@#@kFL zCNNiKhXrgx%O7C;Ke@*i;0;EHFI-y{np6>4Vkew%2|Kr<@tjg^wvrle7g01bcc!oP z(me91MAFuSpwV`*qtDKTglE`=2q|7WM5><}uyg3^?cpK?MnLNAqGCFIP>&``oakBGi+==42#V1KD$ZW}0k?ikr zi1;BBYQ-Sx4r_mC5(q<>A6CcNi+{C4e3IY0ZAUk#sWR!UxJ zV6s~ea<4}oXN|V2dE&u4*sBHV_&c_Ej+}d{(L5(O*{!_x{G9lNT(kNx+vv9A$l%lCkUBaNazCV1x!V_G4c<*=93s^Sg+9reVuthih7kT?Z|<^}oxgaf^P5aYCFR5{?cpWT z)e?wRy zOIYESaCpp{J+)lH7qKthgQhdm3bp*{c9`Biy{6U|@lo>nd7;OMOzl_NLe3{EkELEF zPpx5jw{RqNfrW7Bc#hE0*izp08%Uce&kouZKI;krQv|8Sonb~ymqBXD>Lw>Sz1;!6 zmQIp5@1LR-h2OV#*JiiON=GDbUT2n6%CqUJpLsA|(n%ZKb3?vfg_WIArsL6(n}XpX z`6Iz5lehBtH-C~>uJSgx(Pp_sOA-&CHHA?PlfeZkwD}7p6l9GEA1Y*qcSy3bVvJ zel|$;+)=ys{X5sS*h4Ww&-uHzhEwwBhJiQDhr>b^>)8!u8Rtp(NqldU*QDi&P3=-~ z%OU$modT^16U_OW1k)SGH!$^q&JQAwJ`4B}N(l@%8-{*fNaWgO&0c8jKIiZ=rd-Po zS4Ayt8YEO@lU1t3{w!jPn>FK-mkwG-R}Uk>!iBi))jL+MZLMC?G>?|U_}-&?i$5Fm zc|EJ4rEqf2j!Bi1;rrRP^QZ5fDU#5lQ?b8*?egQjd?Ga8W?M(#W-CkVUYp}`8(FPn z!+0nK;Qst!daN>W?~L(6Q~t8E@H?>HJot)C^aFeAtF<>JhVqf-6KTmuUM`}m^9U+m z7CRia_%-I((wfp{^UEj_vnZ*S1mBvePWo^NxhEhJN$v@hOx(J1l|xW>6ujk3373-#CoZU$H1)Av2%;*)B%F3b7TkI;NtCF;a3G$gd@o739=ex6n zl=?O6eLpDhOC6mi)lYJ`#J77~=H-2*O~uQ4i^DqyJc0vG+V9FAti|ice4p60M-I1n z^MVn<)4l!ZHG1KTt<~G()}l39nS3iNH)GOyDS!xe~rtZwLh-&+Sys! zeW!O=)0D8S$8Z2h%Tf5eB_ne`BlG$Y-(iQky9sv~SDCRDk$8@-+g7?V7yS9{r92wO zIdD<McQY3)vQ_w? zvPAL*%{6?WuT4o`cQ3u@MPZu$^rW|f@lrlHSa>C$)2bmk8e2pND!A0nBwV>D=b>qu zwY;vdn!ns$R=MMv2srQqruu0~(D#Uux+S0*PpEU=+hr|m~Uq-|amP%^# zzxp98slZAV-$TmaO_p@y>m`3`VZ64>cXXt5n21--(NPtr(_+ z?2IEl-x0WOy&wCKeIiAva!fni2A%lo=t@obuju2d8jZW;mDdcIZ+2PpsU3OzZYn5L z&Xb*$lN?2Ux;URyni3R_OLkkU|GX%0(e}yJ$@jcu@HWi^YX4rt`02QfoQvRf8}6 z$dy0I+>lj5&n|qeEBV6AoPewvC}JeNLrU($p?GOKVZb*(^1SzY$=l|bl<)q7MdypY z0F8H*Y#(PUR(a2YHAkk&MxCO`fkZ{=*M&lMOh}up%2#YE;70VoZVv2~Cd!JF+jb*uo$uYL2Ss-SP!Ns(`(z)sEUz6wD`fj+#`* zH6p0`AG<%h{N`#FHnwxl?&4@;Y6lrT-<$W~SGv~)z;ny1|8|n!Hl#>zjs87cq z^OLmQylz=@@_YDtZL844SoPBLBSGi$n_fuf+ri&y`v0s-M{oN zTcmp_k#jZMSFh3;UYWGHpZ(qctA3C#AEN}bbMRN(<0cYx0)y$e;Wr^=O8MoCd4-$4 zv&Gi+26{MP^zLTZfP~pr#?Bh9(R@4SlvA|+Ippdp?fQ)vt-d2cJ*g=3?@7{-eSF_}sDt`6E)`1_?meAx?FJ_4c(;nje6$2l zcG${pXZO#Cc4aSa2Opq1R#NGb&6Ijlvumu=u~oKelELQ<4kbO$fc=(EcIDAJ6BE}p zMhdR${qtPoFUph}`ff^lkfZFGwKnf?deEnQo{m&VKSTw_8^30aOi9$JX`Fa=Mr7iw z^T$PjV|;+QE4;nTb7Ob6sUQ9JjADG^%R-%UuH$uvxGxGn*;;H@dE(P`3FDz`lJg9J zw{IBOKDqE}-8(g^Q89C=k!0*=+G7zu@;$2OJ}uh1_#FQAf?~<-m849y5%?+aBf2ur zzPm6hXiUN+@SV&(JLQ_mAM(_^b?dad$>O^m*D3-W9#!SYR5k$*(shPp96#<{9eNw_ zB30u8{pf=@RJXpb-~m&dJ1r?i3o}LH(nxB=hz;2y7Q>fXIQ$W<6wcqd-oCCxm4FxY zARSWaeng~ z+tAfxC7JkphCN(#yQNL$Wchj2G7+myAofg0v8;5PePQe3mQ>r|y1qogyylMblzLyz z!N3VbvA22f?tSA=j;is(9%m2gU+=h0C_<2Y+Tibg01UIXpFd4HelhaBse;Q&bGhi? z=dNFBoz`(3w=b~hc$TH!_x#1VK_(v_60)>tVq#+McCwl#$xDkPW_(SObz+An?solx zk0KY%T}#og=d@0oF8*MV9q~b=_&ojU<|+LT*K4V5ju9&wKl{<{w#~Y`(5D-dwdL8h zll8)PR=QA?b(=?A`J%Hfwx61bJpb}Kbuyo^yq%z;nm?WNfn9}zxTNxqt;=V$duL~7 zyJvh{OC6M`Oy^w4rwvMtOS?!L-Q3;1Yvd~jEc-sTMVW7A!e_Gh42~oxZZ3`K)%znP z771~Nts+lMWSU$iI7=Ro$K}=6OH=3V&|(hwUGp?pVaqj38dX_weqg6T8TE52oVRR9 z)J}1I+tuY|MW$E))vzondv!NyyZs$UG+cH0f5312{l=B&MkR|zSSn0A4=hP%yaAxf zQwAJ3S0~bVJlTG{%53LU-~|8Li!akbPRJs9}69|epOOg73kANr8ZmpmW?mse-* z6KdVQhQA9L5m(wPJ{&9St*3`kP}6J}!{$!xdUTkj`=|qBF%Bk_&p;jDb@r;*NFTm| zL~+B}$7!FW-%?)J?JYci6j-Z$3kK&`FmIZcE+15te~~b7PdKpe6dje$9lHJkTTa?z z2=~Sg@kzlGZ(`4cy>*rzeU;L04%#@d5_rN7d$gE|-3_$FDy3ey?aMunmOR`W+AFc& zB7QX8Rr$UXQ2LGG?m@;c&BMs?DP_WY>Q42+PgAB&cp%^OeSFJWJyIqid)DmrRmyIJ z!oje;*{;u@ez43;-)9x_8}-^F6`pY*Y;{qDWPV4F*MSd_#>Q;41_^#6G?-^tuIgk78?B?|@_LDW-=3g>)aZM*^vPsur5D zWa}QqYE&M~jn_N6Xinpk-kw$Ar$h`FhZRW!V21H>+87hRjWrN)y7@ERH*wpj7U@2Q zm%oC0?#<)wSmh2dUwT>W7F(viZE^PgWO&!v&$~9a3mxB;G{qIU#7pG+8@0p=_VRVn z%=(*rRaR2+bahKKqdiqBhB;8JB_ z(n)^9_RpS;jjG(GmJ$aQ=snF!wH;-D6WzV0OYi7@CVD1r3v6d~weGUJwr;4DZz`_E zC^~A;ffnE4PU!ftBiVAmNtOa-LR-h-VBAs`)Gn0W#AN+4t0T0-~XFDZxY9l4Xb z$)V5gCfj92cU}LQ^aY_b40^{@#FBPg=kzh&;=$xsS-?nY>Mv`Ot#de3ZO-hY@$yur zXgZWj+_wA5mFNwz4z-OFuVw8`?>m8e`DM?JpEpm!afa^YCgXfQ^Po^`;$FLT*4~%A z6mz{vGWR9RBYVfLx>vi&glaqO zzU1}^Z{FiKt{wm*I^o)^ZiHlPe6)+mAr65%uWS+&V>_Mk<;aGIe40lJv>rHWfb(P1 zw8^qnC#$v6FW`2?X;j_S1im?>-a;e>$6gug`Wb_hiL@n6s-91ex?9||OSQO)05uYR~z*7GA9#6|c1GMpVE6l4nIUKEWG zq-A_5Rln^OyO1Yn{cG%u*Zs(k$orvGq~OY?vZ*iHs@y2JD3|y)MWLk1Pc_9wox6T* z;a4P%&9y~9eSAYN<$*VYcDp`7>u!Vv)8EeQr3;{(`TT=!A4kM?oKjd&k zYZIb;L*kH)fNR#K7G<~ya`>Om^PxsPB%X7}IWOI374gp0OstW!m(;-ry_h+=5q#x* z!I>ORlS4S=@}uj>G}RavBa%4Um^bcz{b{(d{OyOv@AJ4iJ)__7$UGZx#(w-#u)R`q zfd`A~;@!zRIi#@$JBZwJO2oZ*t0?uufc-M5;vTBy4#U!9? zt*YiZx;u>E|MtLe!&{HAR{KxMtHqzHzh@ccM=p6v@baCzE8Oq$=45WdTbi>xp3l>d z;fpWeUR3nd@h8taEFyexZpU;;9?YtTj%PDuCw;{v8O>-I;v7i_Pu_0BNUm)vZZ;sE zoU6tc!GA!J@WfH^-bbP zzf%RZURt{8Oef-vhLg=C*zb=f*UcwvpS!wdg8X*lYhC9W%TnJ^Mg}tvN4gvXV>y+ycbo{Tt015_GU_dep@m$qJ+wdAYD8@-pn-M5?EG?=NZTa{(yjpayZ zD^Q~l{r>pl^ljIO!~}K}%E!E*)_mwy<_NJ?y^pEIbY=%DaNUoGPwY^NbI1?H58Tc3 zMu#tP_sqUqy8Lo$Z`iI_;o07k`6@;nXKYebuf0#Hc(*lRFBmH6`iS=WL_nD1a6KK0 zAC`Y%D{jbJ3x5s15QIdZexJ53b4vMV)%=Q`o62{M1GKb?4ILKMZy#XT!zU`83Xc|3 zY~fMv;ykfUR7#|ak6*mn4wQ;1ACIIhJQRK6C{|$VlV# zE_@cGRL3|zdajtpez~@gVgKGMH6rRp5K>KVp?;f}V2*rMp>^!3V&I}KnSun*-l;D! z-;|8g9GJfxnOIZa{5)I`l~WQhTIZ*)clL)|L{~1ih|7tSfZsvhjy{s)+JO}@d0(2z zQiejq(Rb+n=%Eo7iwx@^>&&G)jEla*9|@FsA?BR5vE-k|9`1fyn^nvX_pedu22v`bvnh$)FAr`x~TM*DIw^^sxRN+p?EQYH&#l4w|*>nJX)k3 zvz^H!O`&19$-=Um@*_=Eykfgd-5WT>#I#Aj!Q0v6{D!iQm|-J7u<1-nqGsLZuXT$|9Zcb=6;re#l5T2C;cst&LGvZ zyX%q+WR%!gm#Pr0A}(_?t{Ex=KihqdgSO@JQi6=<`c&(?5S5p&nY8q3(bqPkUbD(u zAODHgYfcW!{fU>@^rlUAkgocq=3QCXnrF)Sf>D6nbDqJcx~@=Kz;HjkM`0}2m?z_LZYN{v{ztyqXMWa9`X(f8JOz*KSo_&rC0cHkw*C_0Yi&TP9nTTm z{M2`4w>LP;YgguE^0>Fo&HxQuS)`NsH>!KyebhU9liHbP>$b*?d5aUe7c{!r2R5$n z0TcaD?c?9EJzthX8hGz{f^pu!zAE|Mv9VOHO7<|%3rpiN@7{s?G>t~8T746tn3*YdCS^ZZ;7jmNCdn(DADHmLX!RDqlC;W zvC<;Rv6?mK2ChuAb&$nn&7NB`R+G%B&)w^OFx%2Hmmx-Cj3GqIu(mvr**TzY9p(S{ z*L7dnkMq($*-qs)#?gxWbk}4EI_iJKBzdoY;~B~<0sESt=EBF_@9Rl0Yo zvxHhkRcFt>?Wl2!eB|;r zo{k~vpse6nXkO}i!fARBlg*DOxAORY3Jt5Y@zgiQC`(@1;!wJdE&k^2wMKesr^{=$ zwsFm1SdWlBVG#JjV0q}ymE9fMu-R;Z{sqV912mwYd4pDPBi#cSNNW|;qKrKs99+Q} zDPG$6K7L;O7mpN}QLvdP5n&Okw(g4lsQKpTC<_(IRW%q|g+xZ_Qd`X{*W^%;^$Nr3ocob8o z=}nSw3suyqD~#qx^o{xb=;7Qx{BT=$lhFJC7kl0})}}*uhcasS%w)yVGt3pj#;wQP zv1Wz$=y>95S*Ix#m^P}lR7Qs6*4OTh)V@9}t;>@YznP%9)WMbNXK8;C8+c*dRGU)Z zz(=hn2LsNe6Nki?9|rN`{5d`OKC^=Z2G5MBHwE%^Yn*2N3s&ac_s-0`RmI*4DvZwW zZ{Bg+`cxbiOx0ozia21%7>{TNUyI=%9l6me7JzKljuKan;>`tF@m9-;cY!8SEL9W{ zEnw|H(f7dHlbE2QiOR>Dx4+8Fz2qJMOYPP>gvVF$y$5FNbas}%M6or8TjL9A4ZoeH zJlFem{zL#3B6`|ZjHL+|uG;UR7y2c%k?B=T(cMQDj~3~?Q@b^&rk8wrJV+eh`7cof z+&1i;q@Y@-j;xs9e6_yrO8NO&)TYk{lQ3<@TbCFvcihl0UJ94`iD&aiMHc?d^$Cmg z;ZcFN=Xw)|qSCa|s4a~ODj$5Y=HK?t*YTjmfBkh*QojZ5O_D6t7?*2X#;W8^vR0L% zwE6Z{|Edy>4yZ^{u7o zuR{48^=j&e!S$Eqq%5B-@i+{PLe5fQjjiU|XX(;_W8uv7eWVV4f;R@A51o$`o(;d@ zdNbDApwz+EzgJbE{)EzZfxMK_SGI<}1(<;MGJ%6#sw%~$gw6}uSRc>IrZ@-W^QvNX`X$^dwK_ZwTBli;Qp9cFyD#|ULt`BCgoxIo&ryMwK1ZCc#`yVqg1C?1#%PUaqUgN2wNXaBjybCN@Fvx_6o2+^;ck^`b^y@ z_-#9)?Q~9QY*VF%hF8cNCw2v{!M#1I>Sr(9Yx=C)qzlsH)&xoW1VmKHWg0;vyllQ%wvDk9HL~6&~*e{Y>RxVFs>YL)cl6Ow@ky@9Eq}?aavtBIw z%8{1#>`j$p>+n(0s6Iq-fg<~xQ)-|wuI{d7BGbdIRE-5j#yjC3Ty38SjvjxXJhXjO zWz|S)q1`21;!P*Ipu2Ev#$Uu>uK$=`?56%aWt4dYUMrN!T)CSqAJr|6~R9=N(1TlFTc)>=eNE7jA3fRl(y2t~@NQQ9MfdB(Cn+ zCr=S{A}3mSl+X!X3d4uw)r<$5lxRi6mM0@67Sv7!3eR_4e)UY&bh659`W99C9b?%` z2IiX#_hy~Xe!klFQ0xAZ+Rzter|2)*4u>IX9DLjKnS;Kvm8!Mvc&^h1uALy{nCGfVJn74B z_58Ibcoo0!q1U*Dc(HpYz)SJ54=Pi3$?*lszO9EY1qXShMP|=4~wB}a3vTt~GpC-AzQ}ZD`7#Tt{``o!H zN$Qs42VlT_IPd)T*0;|)33})|x%u9h7Yw`M6sq4#oQ@iv?me_PCbLap=#eWzSZAwT z`q}eFu~MgBw>C%~6xkBDl4A>)oNV~+yk*m*r6P^Xe`i86?AfC3qt%cnziEZFy@l5E z2!3w$#hsnuCYEo6$L^!Erg9^7!0zYlu1k7JFK?WGD=^qSlg?&=X7YSIpeV{u14eF_ zY(4#{zqV4sSK!y$vYL^GeIr)>N$Aq1ALUA)e!&?dc;$d zFkUP?Dp_ucqmMoX)P26t2#%kP1hS)vGfB)#80AaJW8sJkbjx`GT>$3LH*fA;mxP;dx30K|!9R2#+Acq~OsRv`##>3u*b7%p1yo(jrv{29NvIr7 zUnkDeME1?|sP4JCd+>wwakUi@rs1pR@=EVpW3q3?ObNUXasq>#ATAkx8I+aw`^4Gy zsk&qAbeTTavh}bcxwHhig2y8ox46~9DTnDz-;Y2Uuv?J%h0QtUh4@`8!+R?u1#+@H zc^9l+d6Xy~L_aUN`61*B*o=&D%ZxpXRsAX>zcimikYx zcV&FI)o8*Q!hWh{Cl>oTWG^zPtJobsl*xzknL&-}-J@tc{L?*p(R|83kwjfV+JP5v za|o}O&ZIy1a^MB->;1e}T%YsDz8(E?)aKm$#vPI}cLyV?ZDl7VQHoD4kGqe4zojyC z+hxYxJ&QiBJqC6XQBKDD7>DEV{i^X6-jSIBJp zmuZjY{5+f0%X9++8dg93((U3U2a`>87rqoB7)G3%1LLw6TR%^V4(5~mPPZ>S(%Fo_@Es0BtW>-L!{M%t@&m^P*4m4lNFyo0t3s0ys-sUmLtz2+5b<@d+Z} z`o~tS{5Eq5(;uWgw?FBxAT5-Y$=S})dK~#VK()=7xw5XvRlxzUasbpGB8UG!Mm&Gq zqEZ3EQcGq*RdYz@XJcW!?I~6q7pG-*xSdWamm=pOid8alubT$P=z5ME$?1llD!iu% zfxtv) zJ9V)cZe6BFKmh#1<+Enw0Ph1X8ETF%)fv!%=(FA+qHKX7TtPj}R`F!zZjD@$j=WuA zoK5;_YVdjOIQxTlC)=kCXZZSQC_fWFa0fh;C~Fa8?&MLa8Wh=M)5aUMw|hKD&yIcZ z!^tw>GTatbIMLE`6G1sHhJA(d`g+IeJ z*>N%2zHKXKO-$O(nDfh?iYBG+OWV zOoqwAx6$cnfy8uQ$~R9Kxa059(<;7P9-uPI!9SnzzS@kKv7dGyJK4G7hXk;L6$`a1 zO>vQ+s5J-Y1pn8QbpGv}#b@~nB-2kOW;#s2S@-A3VgU=Kc&N5^Xt~W-lO{(Gb}%yW z_&eXtg%FW!98#L3nSlMU$gsjVHeLFFQ|u6VvO{%KT-a6ob9Wzv;b{tBPn(%wJ9$?= z`$66Au@1Ern^l~RSP7<2M0X^tvgfW5{HqfBwdCX?N3otOU(1nilA~ITCs4Vm1+h17 zY;C^3P3dAUl8?)9EOZw=GrO3z)a!ET{EPF`H8KJ+tjVorIs$4N;;N3ef#RM$2ZqMh;5&rpGhY3*5U#z9volC zo@0T)>%}xyTfW-QLM6umx2gD11w8UFV{2hlcGz<47``xHcyp;@cK4{!7q{nb2hrTf z7m5+-*Ek!W&taDzA)c1%NW1o0+v>i)lq1X^ctz2QCW`L$tdwYSKty@E7kizRF`2Ja z&SBTPI{_&!cTI0uA8d`L%eN>ZSFSy;RFbSIL|L$e?^^y!`xRZ~kucTB7(wcO+pVIn zweU!SViSA!XV>#xr4qRdr=FDm&^B&A?I+}JCbMXPUGxkR^)9pud*jt+c>pk2YwBQ@ z5ZT)6uQ^80+zXVJLp*OO1JE+=r?6*zMl6^0%t+c)<&{b}z8MW=J-ukc?8_aeu^YQ7 z_g?kBvZ=TBmCy?$&ND0D>;|>1;}rE69_DI}3R!GspWGG3g)i)Ve~6)}x}nv#K>aac zZR&fo4*cSB=X<@81ib;f%(7EKJE+l*=a!Ci^1c>`;QDEpI(z_F`(Sq^z6w5-cWH8z z2IrThn(y@Oj9&I$JzMJ>a)H)iZ{)q47$Pop$$Y(@RW%sY>5;V!Sd)Q>+IO2au6uyLgr|_R7RPgd zTccK8NL)Pp+Vz{y&iPql?|_*Kgk| zS8qs>o?}dYJhe@?Bm6>>XMhq+m1n9*ttWT~yc#(k|BBS8!_!}@HtF1RGIHFli=v(0 zzQ5o<3Cl2Y^tsqn=*80N#9BHQD6H4fi&6Fbm{^GC5dU?%S+ucYq=ER&`#tOJT<+LM z2H)}CjcfC_3dC5a_tqNe;|z@o3j2@?F48repJ+j+q0H;12^UUx#Y0cC~T&vYzNL}7-!Vz@l$@yEiDcM~k zF1o3;=Fmh}w-cUs&ze?AICr@;2qW5`SCnv?uMTRKhJW5PS)t?7nJX+7^m}|y{ls>H zPQt;bF)~_1GHT2k_cexpytGk~*HF5*Gu2c!arWa8dqLpunR@L}6pwI?wRa=xuE4i@ zA=5OAaJ07z>NJ;jY-llUXu0)Dk>2z};x07!jI>DlNSMJAEyrbzpM!uuFA z-Yxy9tzmilNIw!_8oWpCTPTVSBBM@x9$!N0EypGnO}`l9VOX%!kF371z9qR%8mGwN zRILiOzLC6FUDo}Oc)lT2NqF(e=j$~d$K%Z%n+|#dccAXhB+_>2>p;d^)={LHO>AlG zBxw;dbL$HR%Rv`?ADUy;7o*piMSl`gR{pR)j-{>CRsUv? zPVGes|Lx*CuA_2&>Y<70UHUJ_j$XREm!M2Oh6g`&+GQ#w4aD+`=>*cL9NpvY zi+rz!KiGi}h<&Y>3yRRr^c?+*RnZ=o&*;eaxH+EHd#?)&rGFkpf&iK8Qr$KITNt{I zFEUfq{CQ^sf{^7uhC!U7=wb9KVu9}eqUbEcqWIb{KFiYG-QAti(v6ZLDbguO$1YOR z{OOXA?pB%wRJyxC>F!>5_x-RR_nJA^oSAc;=f3aX`5Lfw`_1Z-A(oT~k?=3aTeTb$ zLY`Pl>|yBjM8>USn%H*j;c9=@DDKT40?vA|Fn+qNS_~FF`He9075I*pUd_CT%l+MH z=kU;|^QJyp2!&6wkR--r&7A-GY6P2Z!9?kL7*5< z=L|NZ0U&YgFw>d;`J9WEGjh-;{rE5RPl;>T6)o$J+8wWfXWT~I&z4_?hb zv6xrh3hWixrqyrx!Tdq8wPM#|ztgBXj)^Xh5~r;z}F- zoo>x0M|WFnhEJ;4;3Z7Sj=TnCKAV!1XpH27SqxA2A)ZUM7knUuLmgfdekpiCbshq{ z^s_H9BKPV#L?5+R)Ad;g2Wi>!mM`<=SqJkc911{2!G?Ap@R2`{U^|EJqi*mX)vmM@ zlK{G0M`S2~+zOFiz1E*fkG&*n>k?gz{~Bn3Oeie zX?i7W|L_Z!*DFy|t$vjEh2d@F3r+;RVkVG}i*+v&O!y3zM<7PfJ>?@_NUSMPgxv1;*SSxRGXkcdume!S2v{oXSvx>Tk+MFvL<-J}=fH&B_1Swa07t zr9dA{zZdcreF7EupezzXDRG|}o*kcP(V)D*7mH7*Xg^W9@DMz-0+Ck3R+9UGDFr9U zR92NBI}xfZwd7tFUt_!@2+Wh4WLD_OjtA4(Vlvm5YVF^eY9MVkhgr{Af#q`1x__w# z`!*8OmHKULK+3jse?gL1e_(G!?Ci{mKm7*+ZU;zJx3mOxUDFp6GU}{TexL$D&x!fJ zf&BTDfSo!H!q?pqXJTZT8!MXdCYe*!cYp5XnDgfr_@$7)qYg`UW&!t)AMCBPV6PE) zMxhg27x<~*$rEQ~;SeLzM&uv${pTLGFipL;_-(=!NI@wD-}Hl`+X1e^SrC^$iUFZh{BN!ssTm9 zAV*{Swe^BSNDK;KOf(nK$xSV7Bn0`r{-_REnnRqL0DH&5$C}L|`kg_ALR(uop4aT| zsbrY?2Yg!PnzYZnBR^*eAt)Ce+KA?XjV_5C-cejfOSIe}-ghLFXFd`m;#7+q35dR2&h6+GQ_kP8eBK=<=jG36~+eQofzMs^5*5s=yo* zgjNjp>S}xi16*JKj%eZN3+cYx&YY~IEYwF#owf}Lf0HXJrUZOPELJ>~Pi>+z!`JBq z+314)3u8&+r36-*LkZ_=#Q^ufu-k?ktqL$;@pfqEv0&oe{mF4ki}yE)ZZ_`MA;5uG z@eVy+^!_wm+VYQ4EFA*?y6O5~l=OGJXP%Xh6UsDRNDMHyPR+&Bk!B!)5TV`Nq1awX zM6Z7;-%mxwQ8Hfo-qnVMG%fdj5>4i%R{%Gl-iJa00nFiv`j3Ki9xazf`YSIp;~hC5 zTvuIyvgf1|bi!fC!__t4g?sQs=EfM+o20p-4ZjUJ3QU_B{4^xv1@+*g&NEu*lS$W% zfOzE4$qY9GNX*wo+@ zkjcq@4(~d`9a!G&F?~+c%L*j`PA2<7qi!A;?>hUJg|1Gn}?NCpT-@)i>KpdePY_HZ1 z$Ny@kj&C@x2yiG67E}^K_-Pt1?{XJ>nouc~F!0@ZpBkftuYT}!7RQ`l zA)j3@U=y}`Bq=(!YR}qz-Z5!8#Ga|@K9#C7k-w1eC1r69`}h2fo^x5ctqA%KUgS>~ z9cMGO{r9{KOQNu)NhE;XI@sKTRjoc3cUr=3o#G}<1q>Cd1IL0kVD92T{s+QH+Vj)o zU55v{O1q+mQ^dFbl?9l*84c)++`vPHHq?m$`HMj$Q*JAoX1At7M#RKvSWs%(SZtxe zk2;kZoK0TfW>iR1TC_K6(PukO%FSSv8 zkvhB!uW$ru9*QxyTraPAjv;h_aHE#YkM{2=Sjy&4NR@t(g!5KH`S#mBWf%ZSM&7MX zHbeah_Dg)w0NlP?8rTEpbiBO!N6o{@>4^9NBf;8v{Zab|#s3C0JK=)rYb}|r1->4! zm~0A5uI~V7EsAjCCoPXh%7bSZmBtbs4R$8Q*F=Vh@L%`vZpPv^y()9>k82mh-T!DM zAyG+JS}yI?n^+5~%(AH|v#WW!o3MACFP@zC)97(*uJdT_{f@Y9DQ|-m%C@ek^0pO_ zQ&*Z@^eej4+Z?cp*Ss^;YGXAtIF8vC?Bo7Tuxm+QV3Tm_FZRgKeY!PZhR$?yNCL3! zPv}j5igDxXkjFdyr-U2WCGpQXVQ8V>%NJ2gj}1|voFn74RRqAqNKW7*NeMdYI$b6< z%995$5cEvJscloqmI?ya{4rS8J7xh``6bEvv{VBEjb|P-ZhMoi>1-x1P1iOE6%C8h z62u39K{-X0@bAmpS@&=dnIq7zTa7+3F_u^JGksVX7gFvs|!54#e?ROpn zGTxAxws$ALI-LUQyr7jw&I*})W|%>8FE4|qrO9nYcE*y2d%>u|>*+otgfbU-8{ zZ+@N~Sowq^2JZ2R-Yr#tOJf7}bLHbjc)*Efd}jUMheHI+p7&fUbVVCG2(5%JFbk7} zx@^T_D%)iNJG%C;G&e6uNKL$Kv+*?qW-=(}e#J<^{(BK>r#RB8P;;~-?RLMtK1m7F zQlBsq0j_!`t;NtL1Zb~`b{#JCWSI-JLNt@9A(!@L*}nq3$@zqF^PBrd0=lM&s-nrb zABfXx`9kadH6JdX+b`+7t2mIFwMlV8E_p-u#tKPue$^=Xmt{Z6H)U|$by5INON(-# zuv1R<*ZqWXUHRpDlW^8nySG_valoC#G)I{U z(WF1f@{(Rfd_n}%>eLZMf2Hxe8g9>mF5dNX@B6#192|6D;N3^C?cWJNxe4`*w;J^1 z{{Q(}7_YTbV{sVTz_&Vz{vjJRmK*lZ`v2Q3U1vdy`e~g)=2~*b-5M@UY5$Rr>=8@G zGJq!edumE`>F`&BPIK9P-6f@MXdP3X^|C701^(N@TGBU9Ql(6??V=oQCl@BNwa2;Z zm9U7XB=c<%?#!V}RZ(p<)PL5iCSJDu6Orv@Jq2rBD3^d-Wh%9QRnJpCJ%FeyOO9@D zO=@*)lTw2Fy=#&I{s=Gbzo+)V*K2?6{!O*4`%{eY&6c0Y%7l@|xcn2tOpu4x`p=;> z*}Rp)-P5^%Yl+2#S5R~Ss+_N)LIF5elrttr&fN9k$_f?x5);ilysX~yxfrI zgt)D1BZ(U5Ait-%l@(Gup~aC&km;XlZJIChnMjzsPa9^(IQ}p#_}2X1P5MzN9hKVN z1nC%oRafQ#LWOoYARFA0ZN0t0+xkd=OAVmtmuRc{NgE&V2+)}vxjh=?o4M(e{R@^0 z7G6OtpkHsj{70%py882KkHwLJmAa_6KBgxje-wYeO1qDV1_0tc!PR^P{^pMKw&0Ud z&wUtTtWIFS`9SPjh%O#};q9?nCA5%6<2!)03B) z^rJOZ7dv(-lj{iNarjuCV@JpzUzV>$T%moXmo+rZ|3x)-Fny`!r3jo^v&wq-<7znS zmj~{=O-`@*9{~J>x3;6^N=HwDIPYf2X*SkFNSJ@kuHxfP9!PC%DRYwR^%V* z1^Y_Pm^uUi^o6U%uAO0?($Us>1(d)A=ih9jx~MNsedVUu0$fx$SiPBRjgE0097 z2P3pRg)>cb07f_U8{n_j`h(|4wDF&x6UR%u;Xm23usg}cNWk?G0OnoRA=1BVO>LO# zyrj&tuw}#8|Emwj?DRJW{QFnENF>s$=qV2bQ6xnTp~3-$H+j}X&|;0nEgI^xllEFx zG=sRxcCCO)+f<=|;EW_$RO})UbZpd}t#Bn*Px$dkfUA+cUJDG%{+YSoIBfzJmnFdk zG66(anF#Qg0_wgZ@dMmg0L+>caNnj>@BAcGTUj=5KwfQz1q14AP<#&LFJ_57a9&4u zwQL9R8u=LG6Ew$f2QiFd`2k579Q0ao#F&k(BZeW;jM zSUAEJc5V4s3<8Ed-i;0PQI2U(&ZOrSJjL|$oEUDtqJ(Z#`|!>Jb$`|j?NnAjhcJQk zbUFq)!I}4=OIS&7Y8K@(#atOo<`2WceXoGoM)YpBe10_5P~cnU_osr!6!`hG-;Np! zCkTovB9=XkC|WUqlJZi72E>E}n*{+Gb;!^f#DH;8pkZ^csWMKbp(@+Gr#t;)!@mgB zZkc%`Jttdxd;8#|9Oqj5H*|xqD0o20G6?vgbJLPg^wWoM%sHzrb z;jHk9x_}+lB(PnBHg2*~-Rt!i*XCqF`actgyYVQXM|Z=g3FBhwJQ|mQ`etmo zW+M~@+Vgey?Z5F7H&*lP^sXRtjpyWl*qZJWts=r8RA?S10Lx$ju8@I^b^_4GH(G$0 z2Son{6<9}*YSx?1K^uWiK)!IG=xbwq=<4KT4@L99Fu|>>Sg7n%G|c39_W%jXLCmRy{HYztO#ZUx(by_}-K0BCBZLB#?X9Z@aNQUYC^hEeYK( zCE)Bv3yb8})*BzFlTPqMzm)Uu2-DiAY95Bj!ph2F<{1INRIP~-0Q8yRU)t_+TMMy!gWwW<;x z!BW-mIE%QnLsJo(c?Bh!n;oFLB1H<|yI}(hO_??OmbSpet(-C~VF)YTl3I-tl|Ww0 zVs*Fz{uEsWgrh+FG}5;g6&gH_fKY~I0x<~FGEy(v1QHuP9cl)kdU+JTfsAN^QcG@< z1KiQ(oaSi2nYRm)sd*%VTn{BW8%$?^?5&?T9jFm5t~F5^)QHezz?K6@qCRz(RkT{d z0>WA@HWBIrWJLUcGb0!qa61PqS&@J%RA56A@i5J75c1gNs8Vr>OsqDieAF`lNKxVf z=cB_Mmvh6KbjqJC|6u?A5YC>w4pqwYxua}xIQ$&X+T9k@pSYOh@(nn!M??X3sax3f zBJnIUcaf2SFbNP77za|R zaK;NF4r&(u%MLrJvDcP6eNIj|Em`a=Jvu2p9$BccNm=VMd^utQ0kFI=MN8GMQTOSz zh30Z+$x(^5`FJEnwk%P9wqYzlMl&)L0fo_?0|AH>sbvQv08_lk98_}z$oDu{u8l#k z2$1@^cQ{9OyecY?8;1t3kJ_-b{E zHDSoa`*l)a0_}8{Ea zu#2^zzbyH>eNdsbkzjyRFj>r1AkOkClkC!#Ck-d=>V^~vJxafx?RW?#0{nY{%oM}b3+!&Xexz#6jAxv6igZssLsRYzva@ZLqfewbR3;3W63Tq*nyl+qjWTZYo)%w2$5jxM%awuo|~7G(ljaw@=> z;-9{T^&aI7p)g;R zlU~mxYp8MBPKw!jy+JZe)%sl$laQ$bEcUW_mvru?klQ3%p892sOq@hqljuXjy`0#K zVtmild|u;0`?wP-umT47ZKm!wFA>C9{;{X#L1rL-91*|+V4f~mKu1JF9lg|Z;xoy* zNQKpUP;sUo#_|iy-35d*?Vh}jC-;qj(L2(V${aT^inGFPD0ha^ki!K8jr^iHLHy^- ze5Nyz@`^MX8vffBK9|}EFoufOCAN6DE(KZm8T{%!-@n$Nrmn~oix|2GB^+Qv1~;#C z(GGWVhKNr|9Vru1F36LA)SVhUKa$zypt8T1 zcf7oBUNWdC&DlBKUVTbN9h)*W#;*_l^;CH?XiR&C8x5BlzP&IAsjE^i~t}|>_Q~( zHQ&6^uui#R48>JbtpdgB&jA}x8tdK?{iE_IL3=UDn~wwWa?HE=lGR;1BR|ctYg^e6 zr7;cwdj|%#)WHper7f8&YLBjUCUs+WJi;XjbV#VggyLLie37g%VAs=5cH0 za-`}?!*50QtJc3Yu;vpC2!1bA3avNDgyiBhCEI9BubcA+ijmrRohIA5dJu=68rdt# z70)II9QwyCt`vA~&~#*3SeN}#l=*tUz`5$0FZCYNuEeTH`z16rl*pw=lHWVWN*HOcwdEOKry@G8#Mi!ts|EW`v@?5XOc-|3?l$OT z~WJN}gW#?Xm%2-s~d)^$E$%!JTQtaLRu1OENem;64mvQ~zGdw!7GENxeQUv_vlQ z8-xHrJ9t54&F*{5v!2hN55~*sxSE4#oW!cq60|(sx;w;*QHy`}C>Ncf=sW+YV3A$2 zVyRt)2?Us|7Rn3C&@l(?tOwYd3*o6OzX+sEI=;4=XiA$(1q6RsK}WGvwU1~tY+^$3 zb|U%Ye1({!3#V74DTA*6?OgQ=Np>EFg8W#7xSoKHRkK?3)T?Q--$!REuhBy7BcfX)$KjcpzaXh>F$+jg z;m#pH!ZC$$;bJti=78e$rtqYBH(^`Ac3;U#y~sB=b88VO!$$UJ%IrIh?{mn_ux+%Z zkep^l4Xrx*=*3C6aJMPzQmU2DbhdwBkJE3Kj@o?pBh_|caK==FZ(%brC43WOY|Pqr z5>5Ys5QjlNXpez~07b4PsT9~;Eg1DZ%d8YRo0*U9dy}qqVVB4yfR*}V>LvgfxrNeM z`sPf3>AU>}rhw;c(R~i8IWxOrgtqqFboM`;S;J`4pZqpKurf42BpzXTf=TCa_No47 znkuhn=sN_mw;OvelGPLRAyoR$Jik=ANQs#U7e^%>Nzat*XAe^9=*7jzA9zy8fvC6(Ax4m}x z5oSecKgJ+%V_!FojoLgTo+02zYo1oQaksGvcnC%cVu-1t%2$y%{FHkm9{zm|%+uO| zDS7iepVj))D)H4M<;w8#&Mv#JSs;D!IZ*{4IdWmghMx~S00UKC^<%KMluv@#plT~t zX1C_WrHHOVLSO6(W>d=rRl5F4;Na%OFPqA{+8R;tL|GpxIm<1Y>ZNocK0xo|AC}of zY5)d0lGT&T-xc~jrm|{EWKUC$wyfHVcCMdqcXIOsQ+jv^j*U(MVSx{84x|9*^Bat> z`1BE*fA4YQW_VHlZ1X#pboWsqM1wBlc_JBV;w@SVxKhx`RA%&Y9NIs->(YHpy*DU37idmjip0o?)pX5SGxlekmMiAlyF?>f zRQ}HU)W5Pwy}Evblr!~1r21F{LtA}RD{+#hFJ+8+ExF0S?BBoPuqN+^E(k7WPYr(cH>0vN_ANTMSh&oBp^X2(lG!V z-~uDg9xK&AP#p86q6ydAUS|s%`4+<({dqPQ9r!p$ggddKSTH%k?S@_MaTw z+%?!4aPGbtk%xrcLfnhIDf!0)#*$IryS$JIUp+UhEZq8F&Ar)gKk4!GaT>VGp&t0G ztd6E^zjY_B6|~90#plNKOO0c15)HA!v$LDTc5Y3at2k9obw5IOAyNKXvZc>=2#bj4 z*OKn{l3OUAg^$wn|25i3)LQ4x;7aMJz1B}AUh^T%;9wYW(_cK9GWNXV@))P9-d|A2 zzS>iJ3N7yi%D}#-SK)o8ns=u!=;kqtU`0hdvwsyH_sk5X;XMZ zjpvUHz1WRxn{sorHSxlvdsCU41ESjxWI?zJXK2iYAK zVGfF~3FkR(eZ8B}eLp7xNhZ@UHaczHz~#lUocyB|@XZ&}O9}dGGxY3kq0zQ565z`d zwLshXp?N<)w59Yt8@liVcktz8qn^}%sKx!QL8Y&6>4W-g|G^^_lKbOD?4mWxi7J}@7Iu7^0Rhl>eE@3G?4x*;9yk+a9h7?mBytvHUJspTdSX({fMIs`gEd?N$ z${T&Yn4&hlntfT5%_$PmpcmDI!!kZkUYOvpDIiOXs1R4iLWSWAF_Sa9U$-hf)ip+& z3^zZh`a=7sb!OgisL26iq#vH>LJ+O z(Id5ZE}BBJ0q~wDlhj{vx1iSss8BL<)t3E&?xg*NaTQ#Y+^qawuF?foM;ijv zURD5VDx>pS)_SKpJqesmY~2S9?%J|v!Sxle@=nT~9+9nT&a=av8qq_m`p}F1;2&@T{aSrLdI{kcg1T9<~$p((*L+Ir29~?<);nkS@;BtfSrd@X_iJImh0o?H8CI9u!Y z_Zj*YF{(gmmv&#&Krod5@uzt&mfRe%XycxN-lk2yjhC}oHwDeyacW-$tJZ%2R=$j*hZyHNVunO9nyW{vs!qb1O0Z z5<7IFo27q-^M4g{ad`bBbxj%!lL)2+qKV$QaT^6WRivGEp9PWg#O@+_`%0rLxd4_p zZ-p4fu#)0heo<&?YtN5?6U{k)sEU`cBEbsArG$ZOZR6*r9n~-ypbT^d|D=jkAwqs8F%-k@s6ArFxtx2fzRfzyCfp8* ziT6^{p2vlzbbM+=Z5}c8Ve~y~lt`qAk(WtJfIItM{lgt{H@wI+6!2|*Q1vmOY zP@$2*Z$;i<*r7|~>bmQ5ev&yRqaF=s)cCn0?CcqI(m&yLKowmk;rr3ZJ}*?&s++!g zU*sKBD!kni$WQZ&y2UOPWCUum2-JfVT`7Farc^4CU`K00^M5{!6*llS=6rJ!{SUCm zRE0{sQWjcY)Dy|BBIw;o-PJ72;@oXEdaX=$)V3AxWvgDbu7M{<-_h#VW9n9-jzv^V zcj+urg_r41)L1lTw>TN;ZqXwcOwD}taM*Thl6)LdR2Vxy+3%)S%qi^@zWW!)ff0wjjCBcjZ$9HZURzNNxtEo zkzn0!NIZ~OMGo4KF7w8@oycS`NK8PK^#;@*8bLt!@2jX!L#HxB48_Wr%$%M4{E*@a zj4bXnN-mf-f@k&CuK-8f{`=P{Re*w=`y|zmckg~j&$2+N7MFS;=1#_g=1OCLj;3I7 zH^C!Gr&D6XlCbGT+g?)mhks_UZS6W;?thRh1)1Bb)Lf&?%{*~ppj17cC4nKD?N7@* zUBlBskZrbze07A+>Lev@Z7HRn1XhLf@a|ej26)2+SBTECsRU74Kb;c<%ic+k)P}ID zl3nm$HO^^-lj-Q51O${PR<7;ObFO#)B`YomOivBCdaH0XKoR8t5%dvg8CTD2d+&Ty zwGj8?pr#^J{jjJoZzl2_E}U5JiR}LsC%dKPlv|4dqcBPcX+yj`O$c%JGU-NI+{$Z7 z4(v}*{V!p!C()Rh>>lq&ScZRqhPm537kv^jGXx;PSwuM=Xk+fb%^Nem9^$pVt&0DS z8uAFY!X}=Q{kPWm>ch#q(D4`_L4o!OMFnx;_m=&{(FT_BRu|Pn&1K4kfm)Qmbk!){ zua6)02GG02f4$gEP|wvt3&5+DHmNGG#8i_d4~@V4V-n%=msP#gZOt(vsAME~d^+n3 zQ95`vUBcwccLpAO%=Y+$%ixI73B-01WSRIhhj>{W3Vz?nYI1cPPLtxwp*xS@DV{2; z<)x2pjYq{%alK+km}(r6aO0nIpI>C7kI=*X13&67D~(@lI-#P_KxWai@$k#;>@lWA zH5~Ef?uO(Qv_6OYF8I7tC|KBHOZ+jS$I!c(S^g{92y(zwIOcK%KAW5douWQW-gDC& zVy_R{=mmuz9HwXxBy(b4(`ts;8J5ke2qdBM@E*Fn@v>!?_tN$cNRTxVN>+6m^*vPk zqn-&}Q{Nd7S@nS(VZ+vz5$XS;2^h<=yMWw=GYP->dE_kTUhHxK=OS2wG9Up3;Q@KnwX10Ow{pn9H|Qpia)I}& zI26kfP^YW)a=V2MNI$GR6`HR7SMQ2{;H$tIABR}xx_j%lZSk2Q#(PEINp)>zgMX4e zQ*e0>`JyFAwTlc$wM35Ur8mjJ(FY>X)=J>#dBN?ZWQZuwy(HY z52f1sGtEd}sm}&{*@z-+_;(@`68z#74p+CBgVdNR{eAF?sAstEdlFq3E}*T&qu1Jh zgy@=!-ldxSEK@(&Sk#D7I>#yHYg~K9G;s^I+}OJ8Z+(m>ai5{@pa!5gUmmP_{QLPM z2{O73=3QA0jwvQew!nvPkE`iOx!L$#XM(UDLAsB62v$&WcHi^5Bej3NryeUt8EMb1 z;8r-BnV0IoljN3-lX-R9iOKxm6FGr%rE1F6o_a6WUdMeja)EmwISqqwmhUG&B7X`% zUl-hK^QxBW9)H&waAPz{Uus3(^5_|#I&qP(S5AGF`PA+f^c=Wv@lzC4Z+~o9(~eFx zDD0*82%;#IR@hD;2*e~{MX%P1Kr4bCoSc9>qxb;$0AwQXfu^-VBhf;`A|_B4I5&mU5GRd-$PROmpu8Q89N5PFcrUu^9%oo-@c&rI_CSuTGUZ5dqZ~ z*633=9RAEp+bI8?@>)yC-bHGJ#w5y}rs@-&Ts^`fMWUs_Aj8c|^W;&z90rDI*!|#O zpiLc~$`)@NYd!ty-jw8Wi&4E<+3JCo`)~Dm57xIsx{EsSgC^+#LvczZVmEQ5;el2SlCjj?$ zYYph#VePN}K~d*T z%Ps4VnmhTuESFrBPC=QC=%c;{AwMDetZX=!NzcIJLRI{lHJ^q1n+p)YkTiBR_@Z~R!g7r$aBfIk_ zVM$3UwIqvy|35b;n7vS$C569>%Ko?v8xQ>bwdo$xN1LgKRR}MZ?~BVUr21RCsj}M3 zJ6D>gpw96Ev<1p9v(KB$tKT2KP#^7Wj}?hd0^OoiYVwielUt$9$zPtnM;WR{lpz2Y zSy#8^+a_`h9VhuT#Sl}CXIrdX`g;r)LFZLz4k4gOOj-=YUIizsdDwqQ6!i!)y`&%bB;k+k>VPy5(9|`zwN*C=j<#%))(VlJYE* zTI3c(j!zI`qCM}O`aI|#fHqAx8`)kJ#w3Y|j_q;^H{ zV``9HEeM9a;B#W_s`->=O3;IS<^&$o_PC|EL*m5w=KOzavU48-cY(m8IJPSATeS0b ztq!23tWfTPcIQ~v*O>?BTJYK^S8H4#pT8nGjAqh6o8uYpoDAgP=_c@X=hElL@sC<3 zYmGk-?C!rGoti3}rI~$sS^^Lm-1n%RNL<^Ag)*Lj;+YBh3`<^2s>y@Gn-X zeDL7BIa|G&F$lS?X8!AWysq{+5$Cm_~Zf5+=cb<&sLXDZ=LGOX!7{Vi%CTIIFtD7K#?cDJ>p?}U9y9mE`MrgU__ zG{rERj{N@`t&<`s;b>0=iO*V-rjL@UD_)s~+nt+rXHSv8pRFQlkN-7U<-V|OpGLpH z?eg&Pq%4EJGIbLmR#yW`EV}Ll@5KMNZA^lZ+n;qyjirA9BY79~GnDU;&{OfOM}AVC>QK38Ot z9B&Y~H6s`gc|&iL@Bpnr%3s0{hoZViJ3N1kBEIA?6>eri+@BdtpdqZ$tVP7KT%tEY zizg+Qt`_~MIXT8PwikGVnoqVSY4JPQ$ltPeXm8F?7Bux7M4APUj79^n9m#m|Q*nFd zKd3g7mZ`ABIvn0-QgQrfn12E<1s9xJRP*%x7xK?-qVkF9HlSClOUHFTdd07_X1bpy z6fX|mmUKcl95N=J&b=fK4&ni#f-=`hA?q*ezrlw;Eme=+g2<1#oQDUWClN_^j|*tubxW(jWlyWQl%fI zo4|KcsGqmFIS}E-&B~Mdua>Ymj&!030gF805j@dt3DxXVevlH5IS@hN&xvq>Xp*Di z=q#y0?3c~=bilArF0T;z{X@7~=lJ?W_8gW@E1Jqn&WEcQ55IKw)R(1|*>S9i5;+7p zj{R{}TI(AVx$-e1f|*RklaFE5W2p9Wk+iyU!KD)zoBuatp87$=1VoQVcB!N+?e#q?;zT=g-LRfvwu$MZ^qGF zg)a%=@v>G*vh=DiBDq&=x3R%0bcu&)a8IW3HG~)2!JhXzeQP3eTdve{koGqjbW&&6 zmi;Fp@)h_mJWZ?m+wDlT$CIBI>a}_M8$J4+R6PYj(ZBf>^9;sF-K0k@&E`RVE~vtd z@BnFIlq&(O`oq94XHd;^3i|MJqSgdNb8fgm)?+IMJTh_d`sAKU`U(ffCB{t1& zu(O!8sH2nm!PllxXAp2G`ON8cV312%H?yf+=~Met$ZOl=pl#PgNR&qEi)%L%sTB4; z-Zez~;U(5|`SxSs_$oEP?f`o21fsby0oYkblw)nn>t2pt*4LVWRa$8H%>v9dDxSolQI8r6n)TMXRSDsz3UOI3$3oZ!>sTb4x6xzD0RivRC-Y!Prj2?icey)N5SzfR@tN_Lyyuodr^m{UdoY46#Ee^;yw2-{2uZ$Z1|8D}Rd;=3C( z{u(^A1retQ$c{j_UtOoEzx)nhFGTVEgdUN3j_CSWh(ye@lgk4oDAm~AcDp+ROH(Rl z^Jbb2TyMDV%IA)V@Qd7HYb4@DP$*m1XepmkqmwvSxBif*2gU4wmLP6+-SMnZ{H6$@v6HEf)PKeuC$BZ%_KWQ5^-~t?*b+X(6dfI*X`~Y8MpD>PmvE;-G!+ z{+aMj$X*NSXM_KaEce$-15g-u^YZRvS7t8fpJ+lpwZepSjzWp-*a_EDq6T6<_u<31 z2;QH_=i2IR>=9=pbr**h)IZcZgsVY99ZjHv(Ur*3L?KbHiOSP=(uc#&IUPX$W{0NB zy@Fj7(kAT@+MSl(TEqCC-$VMCg%aOCHeZpQ#cU=eo+ilC%|2Py}h{eS~+F6lxAOn>B$|4km6#Vv|5EpK+*OF(~*%z_&cT#@`5v@-8)1m7m}6s>6+OeqG^cz&Ph$AUd99T;cv|GX4fgM%I{9Q*;0H<)8AMG z6JUZTh{7#0T1}&RAwwUXb=lDT5UgH)6nC4(%c6Lj&k^~w>~Fy#{y2{XwUQZ1jg3Q6 zJPG3%KWx<-j3Q>Sd4TnFi-F5`KD~C=9~J*T^Z=Ca*r3uOb02yu=f-e|gRU1}UFzg5 z7uL*oJHBYf-V4UdhQW`l3iez+c%F_cqH}l@804K<#YFW@=hd;m!;iETy=pH}=p4Sg zXKZvPU+Ro622lp3MGfJ4DOEa=%JryHSJ0HLuub%7t_6~7paZbnU z;Nt6BRSoLUrPD~cr-~OANK4fpeA`EUAz40l)Z|fHov+X^l%pt&$EX@)uMXQ%@8|tX z+<=$XPi`2p8K@o2I${_o#_#UpQYK|dL!)+k+ir9zpbvNbEM|~PDwmip=Hy8knmJg( z#gd|YnWQIeA*J`N9#>W9RM>vZjZouiMumjkl*WzL=gGV{ZEWe?*HbpG}7K9w)b zTv|NQ6qE}TkmVn~0;N7+*TQfb*sL7TRC>^e05I&sSOc*&YoZcb!MfCYfu=nX0TeWf z&ee3=EO`$(B6|^EjK@?YG+7HP?Y#N%Ga}L|lDiK6l1s)1f*;DIEV9{EQ5xe#Tid%y za?dfxO9%jE=Ql6=fyC#%3#Uqyk`X@j*=wu%H(~zxR8t|n!NDGpNq=!E8hPiwg~l4q z9Ja=`M7p4A_q@w`?4cc!4)$FPzb3i~UBW|kpSo`Ag4&8@vnwGcgZ$IAqhOpn`d?3a zcppEH`|%k{SjFVIU;OEk=Dw4KqTq3s-PsM@75@tOYpXfr*6lv&lmz9`#Y1Df>x_3v zUV$7cisIUWIG!)-=dfZRu!*D9VNMk0J^@2M6H_I8C0GmxVJQ@{2@-_CrREB$`Yj|< ziM%h55c?-_WSt@2ODKI2WZ&-1S1b7=d1LEMP#Afml@0pEq++z&?javz<+{Uk^>NSw zeD>iH^lG!T-{JhY?76V&+=6e;YmK7hz{2A`F2t42`{RLiV6xcY=ML*nNELh#sATxp zOu5YsahtBkx_Er@JB&vLtHLQ`HfmCPP&Ga&w&c<7CO92lMc)L?r-5wGfwAdhUxf^m zm>>B6`%S#K;e8W6jW;M`zGJqLhBk!@mg`Mu@a(97ur==M%ueqN<54b!jnRHv&BqLf zSbgYH_hyR@0(W@W{v!@E`K<}S!8^~9nXrXezJ9tQoFa%wDv=^GPk!boD<#QIWkkcE zyq72e&eFM&gH|FH{+79kQ+wIrHDAfzzSB20O^cU*6mB~_wvPo<3E>6VXP5uu?mfV= zIJ&=Kz;0sK7-JM`iZRi|M2(5vSYnDX#w429V(eY9E7*JQ6&p4blwy}^7Znfz5u~W} z-tM})@9($Ez%36NlYgH7`+gtSHO$_-voq(MIWu$Sl-W`ZpIv+wkvl2zwYUTOU+?s8 z`Fb~h8|Lh1|4Y`5%75g{tX#=v>#p~ghYk%W-RJYRy30LA#cn^Vzg#Cd(e6Zz-3!*& zzhkp=uD(v^pQeVp8q1Zd|HhT*1=gP)uhVQyqf67i&bD!G^VJ^vnGNlBfA&jUmgBUy z?FWTE4wv3}I`V$2M$MFNx$kC-U7S2IFtfGC+?F4{wPW_hZ?khgsg;J0{&g^0I}EkS zsa)em`{~O^WX#^2dNaB5k5y~D=@8a6EZ|g3@JHtc_4z*B^{s_CIp}L+-Efz#ryk$x zGc7;qQohZ~w8*Dbz0=m_mg0DS`WX z+VA$3y6;%~xoq`zbJt^j4i%~_tPl{8Qtgi&8Let;f4{}a$$j=4O?GWR`=I$bkJlzV zu&Wf9^>gLU4=OB9(|=H|N!bSngKpk!>*RXgA+Gkg$#)x+mv4C7e{^qE)lC!DbewG( zn3!=S-tePWr?J6~PwmF(8;%{&c(y4qpn>h7NBW9#r|C@(pX;<+p5N}V>lfy^J-*KBaQdV6!I@Y0y@R-c`g!?^_jH*#n+*+HEm%?G$ED6oq~rVd5A8hMyd`Pj zz{=-bX5;gDjqQh>uWvQq!(l`(`%))<3>~@0Dqip8^7yOMQ3JngGJm*n*2#WLE?2o2 zT_^dbZG-gMZyvuNZJ6mEdf?dWof68K4R^M=$9P92FCX{7VBKIuK*h`pZ4U3*e!KMg zkB`o?X<2SR{b+!I7o!PYr9_@6A1-H9+u#4{&vxaz7{i|EUpr8$TA$4D4zKU`${o8yzvk@V@3VJgy=z!=-TV5@ zFHZMbU9oSQ6$?VbW}V!*b3(+lvQ|eMUcL4pwcNPQ_D!Dzd89X5ye2E6+m*-_Lv7x> z81VExZ{@u^QF-rOeP?P&wY>UYJ1;)vzi__){LXQU^M`(RVCNfs);+zR=R3X1O6#AS z%x~g2A@$(hu*=I2JPK`_a4RIw)Np=3{hrVSkNkJ4dhe{(dfwN=`zql_hdX{fszO<- z@IU^*;Z749?thGPfVY42R(9R`rF}{__b;72{GbwXPq(3M+iI(ymjAf<(HtpOKQ``A zOoOH47Dseg9oBx@$mYF`YtvGG$<8=@V`+zFU#~uEdnIF5;+ii4rb0>4du4?|k?-`2 zt1)HG?#jA%F75yGO{*bQzIUy))qLW^2Lp#Ud*k=iW~sX>oSS8nS)oCfNpshRw9wUP zKkw7^ANe2sXi>)oqx6+bOBSHHPu=GI5?Hm`s$myf|CoF{XVrPBfxcRsyBS;b4VSmJ zt8p@8O1atloL$X3Z`AJ4q|LN7OV6~OpKLjhr;lEli+5X2ft(0 zd199ZTj#8L?_IA9tDuR4Geb9@4!*vu&%1qkhqq`xYT1xIU0eH2v^`T*-`pl7=iNcm z%QkKC#V&p7=ihgE&||6XA#ca()4Q6t?u}oR{WxRgH~FQeWlnSiB;ZeuESwDM}jcar8 z(!S%rWKU?FA%BzGWN)SMoyT z^2>YspYRNIoIE7*;|i7Mym_ZzzVpJdw()20{M>lR#UE#+RCvv}Sn`_mjAMfA&r1@og>Fs0h z{@VD)gAvtz>TEgY{;Xds|M?I4O#J0kwQswPy_q=hlGobuS3e5xa_ncv9-*B^20a|P zbJp;=kr#h3mAhEaGrFqx_{)P|zp^)O%c%K(taui>5ALrOD%aTFp~qO8AvQOE33~ff z@9fges(D+7v^)A|;`tHpRLz<*HL;a`)yUml4!m{d=;PQ1^B>Lqta`iVHwJVfwOdzf zdNyN0MqZDfW_`c1y6cy_s@HbE%b+mDvi z{X^RGed|_1*Zuo?wEk{{&A7_ldIwmS9~^GpvOL-<*nWIOY{=xWM|T}J*uHh}yluzM zhLJtG*6BScf6ACqIS)p4x_vY3*FlkA5AA#?tNU43r;rTCgey9`0WCfsS?+F!-Ay|l zORU=bosc8r=P%ROyJ>6RZ1o_y^8vf>2UU1uhl7W_;QQcOpHy4Z{1fX*sqe1q|E?VT zMfVK5w&m=cr+m48+QqEY_NGztP>gYP&EIu|v3*Nz$)wE}ID`+5#+(tLSPv99V|{A({(E;tw%TFWPabWX-@z)utylKsr8ApnDjlnrnYAf#q^I|irry3QhgexP zdpG1tWKzK3j=lR{?tjg$%jZEqjIf$>-TZK@RdB1J)g7kxi1feNY{&%Vhg))Pg_E@w zAFJfi7i;X0u?hVr{nDqmbU&=m#_>{OzTJgIu4Tu3w>F?{Gw;D2t0wn9b0O(+mq+GV zXJ(`icS`SiGBK^ive1t6dJnmGcS7~g4e!UFnHb--*8Kri!{n~l&9lZ?Z!i7cy~u#o z4Qfs8Un+gcvCmId7@iUJeSWv`UJJfH>U8I4_XVd$ez2^}(6YOqI!_ue>3g3Yd$YyG zjjQIYK0Woq_Uqp-zbAb&?Au|dSGl@W>ieslP`7zT$7bdFepo9(H|2{DD(7#$-2GvW zZM@^-#=Y7l4M_A^nbrGEznGC-7jz4$5WTOJ^M=aJrP`I(-j8hIc=*dkWpaF*N*=?2 zYX1Sh^r@26c4VohgJx~5obzG3`LT(0?VP-}xmc zZ1bo6<#liDwRL^eoJcxbmG1xZ=hK$|xWsCEvv1bdd>HxtifxX)PVcGvS>wQ#eQnoz zW&LU&bp&70o@toe;lSE={&+NI^~7gWL+UB-dbw`Bk)~T!>$a7Z$6G(UpKo();e{nL z{`g?x!oboiSAIBc^v%$ib2cLp6rVQj%S(4_CzWlSQ>A^699^X!h6eN)kXXIJ)7sZc z@9y4UeY*vx{1#rybL)8W`s9OuRC#M=^XVUSANuC*t2YvKALLoLtmcv4z4XmqnLjiM znR@Guv%hRwG4V{7p9Y`&CFpglP93dx1`HbUn(@;b&sH`4Zd+Dr{mdOV|Frt4N;R9@ z@vUxlwaSd{^j;8Z9M7%JhhC^=glA zI`-Rt1FLzhyR*vgM(I1zw^t9j z*Kp*p)8!UgWw|asHTbJ@0!Y{Iaqu-+0hKszF?B;N3AxZrcEEj8jh_o&b~#(kLk8|&vx*}lWK z^MILh+hgO_K9J+7xmh#H_HeX+@>$Pg>)K79c;LpbDS3U?epT1@)3v4RA2m$M?40Hr z^trRH+Tr>e{oAeHX4}l$e7aBErUM@sI~`m${Zkv~jn-D}=C$2ar{8NEbQ}7Gxu5v; z;9wu?1#ehI`wU+bVKwAU>jPzmlv{rXjY(Im(Nb!-B<1C z7tMSA`AO+t)0XbE{rr!UTlE`0ocEpY+F5lE%?WKfclg54uKI}BU6bC)n^wD?*NN

@r7l@_>dLwGP1inIvgp>RYo9-;{?Vr` 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 Date: Thu, 25 Jan 2024 21:02:22 +0900 Subject: [PATCH 2715/4092] Simplify `TaikoLegacyHitTarget` container hierarchy --- .../Skinning/Legacy/TaikoLegacyHitTarget.cs | 38 ++++++++----------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyHitTarget.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyHitTarget.cs index cf9d8dd52e..0b43f1c845 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyHitTarget.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyHitTarget.cs @@ -17,30 +17,24 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { RelativeSizeAxes = Axes.Both; - InternalChild = new Container + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Children = new Drawable[] + new Sprite { - new Sprite - { - Texture = skin.GetTexture("approachcircle"), - Scale = new Vector2(0.83f), - Alpha = 0.47f, // eyeballed to match stable - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new Sprite - { - Texture = skin.GetTexture("taikobigcircle"), - Scale = new Vector2(0.8f), - Alpha = 0.22f, // eyeballed to match stable - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - } + Texture = skin.GetTexture("approachcircle"), + Scale = new Vector2(0.83f), + Alpha = 0.47f, // eyeballed to match stable + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new Sprite + { + Texture = skin.GetTexture("taikobigcircle"), + Scale = new Vector2(0.8f), + Alpha = 0.22f, // eyeballed to match stable + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, }; } } From 64ba95bbd664964409224a8f589b473af7d6ca1e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jan 2024 21:11:33 +0900 Subject: [PATCH 2716/4092] Remove pointless comments --- osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index 7fe0080e89..6caaab1508 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -48,7 +48,6 @@ namespace osu.Game.Beatmaps.Drawables Colour = colours.Gray3, RelativeSizeAxes = Axes.Both }, - // Headers new FillFlowContainer { AutoSizeAxes = Axes.Both, @@ -70,7 +69,6 @@ namespace osu.Game.Beatmaps.Drawables Anchor = Anchor.Centre, Origin = Anchor.Centre }, - // Difficulty stats difficultyFillFlowContainer = new FillFlowContainer { Anchor = Anchor.Centre, @@ -107,7 +105,6 @@ namespace osu.Game.Beatmaps.Drawables } } }, - // Misc stats miscFillFlowContainer = new FillFlowContainer { Anchor = Anchor.Centre, @@ -146,11 +143,9 @@ namespace osu.Game.Beatmaps.Drawables displayedContent = content; - // Header row starRating.Current.BindTarget = displayedContent.Difficulty; difficultyName.Text = displayedContent.BeatmapInfo.DifficultyName; - // Don't show difficulty stats if showExtendedTooltip is false if (!displayedContent.ShowExtendedTooltip) { difficultyFillFlowContainer.Hide(); @@ -158,7 +153,6 @@ namespace osu.Game.Beatmaps.Drawables return; } - // Show the difficulty stats if showExtendedTooltip is true difficultyFillFlowContainer.Show(); miscFillFlowContainer.Show(); @@ -185,13 +179,11 @@ namespace osu.Game.Beatmaps.Drawables Ruleset ruleset = displayedContent.Ruleset.CreateInstance(); BeatmapDifficulty adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(originalDifficulty, rate); - // Difficulty row circleSize.Text = "CS: " + adjustedDifficulty.CircleSize.ToString("0.##"); drainRate.Text = " HP: " + adjustedDifficulty.DrainRate.ToString("0.##"); approachRate.Text = " AR: " + adjustedDifficulty.ApproachRate.ToString("0.##"); overallDifficulty.Text = " OD: " + adjustedDifficulty.OverallDifficulty.ToString("0.##"); - // Misc row length.Text = "Length: " + TimeSpan.FromMilliseconds(displayedContent.BeatmapInfo.Length / rate).ToString("mm\\:ss"); bpm.Text = " BPM: " + Math.Round(bpmAdjusted, 0); } From 3c18efed0530c362ae363c84f5a5321c7e60eb3e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jan 2024 21:13:25 +0900 Subject: [PATCH 2717/4092] Remove transparency --- osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index 6caaab1508..fa07b150d5 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -44,7 +44,6 @@ namespace osu.Game.Beatmaps.Drawables { new Box { - Alpha = 0.9f, Colour = colours.Gray3, RelativeSizeAxes = Axes.Both }, From aeac0a2a9d83c741b65c81e04ab445061d94324d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jan 2024 21:16:12 +0900 Subject: [PATCH 2718/4092] Nullability --- .../Drawables/DifficultyIconTooltip.cs | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index fa07b150d5..803618f15e 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Framework.Allocation; @@ -21,17 +19,17 @@ namespace osu.Game.Beatmaps.Drawables { internal partial class DifficultyIconTooltip : VisibilityContainer, ITooltip { - private OsuSpriteText difficultyName; - private StarRatingDisplay starRating; - private OsuSpriteText overallDifficulty; - private OsuSpriteText drainRate; - private OsuSpriteText circleSize; - private OsuSpriteText approachRate; - private OsuSpriteText bpm; - private OsuSpriteText length; + private OsuSpriteText difficultyName = null!; + private StarRatingDisplay starRating = null!; + private OsuSpriteText overallDifficulty = null!; + private OsuSpriteText drainRate = null!; + private OsuSpriteText circleSize = null!; + private OsuSpriteText approachRate = null!; + private OsuSpriteText bpm = null!; + private OsuSpriteText length = null!; - private FillFlowContainer difficultyFillFlowContainer; - private FillFlowContainer miscFillFlowContainer; + private FillFlowContainer difficultyFillFlowContainer = null!; + private FillFlowContainer miscFillFlowContainer = null!; [BackgroundDependencyLoader] private void load(OsuColour colours) @@ -133,7 +131,7 @@ namespace osu.Game.Beatmaps.Drawables }; } - private DifficultyIconTooltipContent displayedContent; + private DifficultyIconTooltipContent? displayedContent; public void SetContent(DifficultyIconTooltipContent content) { @@ -178,12 +176,12 @@ namespace osu.Game.Beatmaps.Drawables Ruleset ruleset = displayedContent.Ruleset.CreateInstance(); BeatmapDifficulty adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(originalDifficulty, rate); - circleSize.Text = "CS: " + adjustedDifficulty.CircleSize.ToString("0.##"); - drainRate.Text = " HP: " + adjustedDifficulty.DrainRate.ToString("0.##"); - approachRate.Text = " AR: " + adjustedDifficulty.ApproachRate.ToString("0.##"); - overallDifficulty.Text = " OD: " + adjustedDifficulty.OverallDifficulty.ToString("0.##"); + circleSize.Text = @"CS: " + adjustedDifficulty.CircleSize.ToString(@"0.##"); + drainRate.Text = @" HP: " + adjustedDifficulty.DrainRate.ToString(@"0.##"); + 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(@"mm\:ss"); bpm.Text = " BPM: " + Math.Round(bpmAdjusted, 0); } @@ -199,10 +197,10 @@ namespace osu.Game.Beatmaps.Drawables public readonly IBeatmapInfo BeatmapInfo; public readonly IBindable Difficulty; public readonly IRulesetInfo Ruleset; - public readonly Mod[] Mods; + public readonly Mod[]? Mods; public readonly bool ShowExtendedTooltip; - public DifficultyIconTooltipContent(IBeatmapInfo beatmapInfo, IBindable difficulty, IRulesetInfo rulesetInfo, Mod[] mods, bool showExtendedTooltip = false) + public DifficultyIconTooltipContent(IBeatmapInfo beatmapInfo, IBindable difficulty, IRulesetInfo rulesetInfo, Mod[]? mods, bool showExtendedTooltip = false) { BeatmapInfo = beatmapInfo; Difficulty = difficulty; From 50300adef86222ed1554f512dafe857cf25d2cf3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jan 2024 21:17:29 +0900 Subject: [PATCH 2719/4092] Tidy things up --- .../Drawables/DifficultyIconTooltip.cs | 44 +++---------------- 1 file changed, 6 insertions(+), 38 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index 803618f15e..71366de654 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -76,30 +76,10 @@ namespace osu.Game.Beatmaps.Drawables Spacing = new Vector2(5), Children = new Drawable[] { - circleSize = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 14) - }, - drainRate = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 14) - }, - approachRate = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 14) - }, - overallDifficulty = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 14) - } + circleSize = new OsuSpriteText { Font = OsuFont.GetFont(size: 14) }, + drainRate = new OsuSpriteText { Font = OsuFont.GetFont(size: 14) }, + approachRate = new OsuSpriteText { Font = OsuFont.GetFont(size: 14) }, + overallDifficulty = new OsuSpriteText { Font = OsuFont.GetFont(size: 14) } } }, miscFillFlowContainer = new FillFlowContainer @@ -112,18 +92,8 @@ namespace osu.Game.Beatmaps.Drawables Spacing = new Vector2(5), Children = new Drawable[] { - length = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 14) - }, - bpm = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 14) - }, + length = new OsuSpriteText { Font = OsuFont.GetFont(size: 14) }, + bpm = new OsuSpriteText { Font = OsuFont.GetFont(size: 14) }, } } } @@ -168,9 +138,7 @@ namespace osu.Game.Beatmaps.Drawables if (displayedContent.Mods != null) { foreach (var mod in displayedContent.Mods.OfType()) - { mod.ApplyToDifficulty(originalDifficulty); - } } Ruleset ruleset = displayedContent.Ruleset.CreateInstance(); From 3f9c2b41f7080639d3f4d94097cfeb3238ee503a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jan 2024 21:28:08 +0900 Subject: [PATCH 2720/4092] Adjust `BeatSyncContainer`'s early animate offset based on source's rate --- osu.Game/Graphics/Containers/BeatSyncedContainer.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index f911311a09..a14dfd4d64 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -91,7 +91,17 @@ namespace osu.Game.Graphics.Containers if (IsBeatSyncedWithTrack) { - currentTrackTime = BeatSyncSource.Clock.CurrentTime + EarlyActivationMilliseconds; + double early = EarlyActivationMilliseconds; + + // In the case of gameplay, we are usually within a hierarchy with the correct rate applied to our `Drawable.Clock`. + // This means that the amount of early adjustment is adjusted in line with audio track rate changes. + // But other cases like the osu! logo at the main menu won't correctly have this rate information. + // + // So for cases where the rate of the source isn't in sync with our hierarchy, let's assume we need to account for it locally. + if (Clock.Rate == 1 && BeatSyncSource.Clock.Rate != Clock.Rate) + early *= BeatSyncSource.Clock.Rate; + + currentTrackTime = BeatSyncSource.Clock.CurrentTime + early; timingPoint = BeatSyncSource.ControlPoints?.TimingPointAt(currentTrackTime) ?? TimingControlPoint.DEFAULT; effectPoint = BeatSyncSource.ControlPoints?.EffectPointAt(currentTrackTime) ?? EffectControlPoint.DEFAULT; From 6a338ce5551c9ce8de5af42a06886c7c951e2cbf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jan 2024 21:59:38 +0900 Subject: [PATCH 2721/4092] Adjust spawn ratio up slightly --- osu.Game/Screens/Menu/OsuLogo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 624c78bdb8..a39d552a90 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -192,7 +192,7 @@ namespace osu.Game.Screens.Menu Origin = Anchor.Centre, Thickness = 0.009f, ScaleAdjust = 3, - SpawnRatio = 1.2f, + SpawnRatio = 1.4f, Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex(@"ff66ab"), Color4Extensions.FromHex(@"b6346f")), RelativeSizeAxes = Axes.Both, }, From c35df0313f758c4747cbfe5f43e983511d005205 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 25 Jan 2024 16:31:12 +0300 Subject: [PATCH 2722/4092] Fix taiko playfield test scene --- .../Skinning/TestSceneTaikoPlayfield.cs | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs index c89e2b727b..d1a8a048ed 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs @@ -1,17 +1,19 @@ // 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 NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Taiko.Beatmaps; using osu.Game.Rulesets.Taiko.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; +using osuTK; namespace osu.Game.Rulesets.Taiko.Tests.Skinning { @@ -37,11 +39,14 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning Beatmap.Value.Track.Start(); }); - AddStep("Load playfield", () => SetContents(_ => new TaikoPlayfield + AddStep("Load playfield", () => SetContents(_ => new Container { - Height = 0.2f, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(2f, 1f), + Scale = new Vector2(0.5f), + Child = new TaikoPlayfieldAdjustmentContainer { Child = new TaikoPlayfield() }, })); } @@ -54,7 +59,20 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning [Test] public void TestHeightChanges() { - AddRepeatStep("change height", () => this.ChildrenOfType().ForEach(p => p.Height = Math.Max(0.2f, (p.Height + 0.2f) % 1f)), 50); + int value = 0; + + AddRepeatStep("change height", () => + { + value = (value + 1) % 5; + + this.ChildrenOfType().ForEach(p => + { + var parent = (Container)p.Parent.AsNonNull(); + parent.Scale = new Vector2(0.5f + 0.1f * value); + parent.Width = 1f / parent.Scale.X; + parent.Height = 0.5f / parent.Scale.Y; + }); + }, 50); } [Test] From de52f0a80c088d6f6fb4bc8008a1f525d6783a9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 25 Jan 2024 12:25:27 +0100 Subject: [PATCH 2723/4092] Decouple notifications websocket handling from chat operations This is a prerequisite for https://github.com/ppy/osu/pull/25480. The `WebSocketNotificationsClient` was tightly coupled to chat specifics making it difficult to use in the second factor verification flow. This commit's goal is to separate the websocket connection and message handling concerns from specific chat logic concerns. --- .../Chat/TestSceneChannelManager.cs | 2 - osu.Game/Online/API/APIAccess.cs | 8 +- osu.Game/Online/API/DummyAPIAccess.cs | 8 +- osu.Game/Online/API/IAPIProvider.cs | 10 +- osu.Game/Online/Chat/ChannelManager.cs | 26 +-- osu.Game/Online/Chat/IChatClient.cs | 18 +++ osu.Game/Online/Chat/WebSocketChatClient.cs | 148 ++++++++++++++++++ .../NotificationsClientConnector.cs | 42 ----- .../WebSocket/DummyNotificationsClient.cs | 29 ++++ .../WebSocket/INotificationsClient.cs | 17 ++ .../WebSocket/WebSocketNotificationsClient.cs | 79 +--------- .../WebSocketNotificationsClientConnector.cs | 20 ++- .../PollingChatClient.cs} | 41 ++--- osu.Game/Tests/PollingChatClientConnector.cs | 48 ++++++ osu.Game/Tests/PollingNotificationsClient.cs | 35 ----- .../PollingNotificationsClientConnector.cs | 24 --- 16 files changed, 330 insertions(+), 225 deletions(-) create mode 100644 osu.Game/Online/Chat/IChatClient.cs create mode 100644 osu.Game/Online/Chat/WebSocketChatClient.cs delete mode 100644 osu.Game/Online/Notifications/NotificationsClientConnector.cs create mode 100644 osu.Game/Online/Notifications/WebSocket/DummyNotificationsClient.cs create mode 100644 osu.Game/Online/Notifications/WebSocket/INotificationsClient.cs rename osu.Game/{Online/Notifications/NotificationsClient.cs => Tests/PollingChatClient.cs} (59%) create mode 100644 osu.Game/Tests/PollingChatClientConnector.cs delete mode 100644 osu.Game/Tests/PollingNotificationsClient.cs delete mode 100644 osu.Game/Tests/PollingNotificationsClientConnector.cs diff --git a/osu.Game.Tests/Chat/TestSceneChannelManager.cs b/osu.Game.Tests/Chat/TestSceneChannelManager.cs index eae12edebd..95fd2669e5 100644 --- a/osu.Game.Tests/Chat/TestSceneChannelManager.cs +++ b/osu.Game.Tests/Chat/TestSceneChannelManager.cs @@ -75,8 +75,6 @@ namespace osu.Game.Tests.Chat return false; }; }); - - AddUntilStep("wait for notifications client", () => channelManager.NotificationsConnected); } [Test] diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 17bf8bcc37..359c52553d 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -21,7 +21,7 @@ using osu.Game.Configuration; using osu.Game.Localisation; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Online.Notifications; +using osu.Game.Online.Chat; using osu.Game.Online.Notifications.WebSocket; using osu.Game.Users; @@ -55,6 +55,8 @@ namespace osu.Game.Online.API public IBindable Activity => activity; public IBindable Statistics => statistics; + public INotificationsClient NotificationsClient { get; } + public Language Language => game.CurrentLanguage.Value; private Bindable localUser { get; } = new Bindable(createGuestUser()); @@ -82,6 +84,7 @@ namespace osu.Game.Online.API APIEndpointUrl = endpointConfiguration.APIEndpointUrl; WebsiteRootUrl = endpointConfiguration.WebsiteRootUrl; + NotificationsClient = new WebSocketNotificationsClientConnector(this); authentication = new OAuth(endpointConfiguration.APIClientID, endpointConfiguration.APIClientSecret, APIEndpointUrl); log = Logger.GetLogger(LoggingTarget.Network); @@ -324,8 +327,7 @@ namespace osu.Game.Online.API public IHubClientConnector GetHubConnector(string clientName, string endpoint, bool preferMessagePack) => new HubClientConnector(clientName, endpoint, this, versionHash, preferMessagePack); - public NotificationsClientConnector GetNotificationsConnector() => - new WebSocketNotificationsClientConnector(this); + public IChatClient GetChatClient() => new WebSocketChatClient(this); public RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password) { diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 4b4f8061e0..2d5852b209 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -8,7 +8,8 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Localisation; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Online.Notifications; +using osu.Game.Online.Chat; +using osu.Game.Online.Notifications.WebSocket; using osu.Game.Tests; using osu.Game.Users; @@ -30,6 +31,9 @@ namespace osu.Game.Online.API public Bindable Statistics { get; } = new Bindable(); + public DummyNotificationsClient NotificationsClient { get; } = new DummyNotificationsClient(); + INotificationsClient IAPIProvider.NotificationsClient => NotificationsClient; + public Language Language => Language.en; public string AccessToken => "token"; @@ -144,7 +148,7 @@ namespace osu.Game.Online.API public IHubClientConnector? GetHubConnector(string clientName, string endpoint, bool preferMessagePack) => null; - public NotificationsClientConnector GetNotificationsConnector() => new PollingNotificationsClientConnector(this); + public IChatClient GetChatClient() => new PollingChatClientConnector(this); public RegistrationRequest.RegistrationRequestErrors? CreateAccount(string email, string username, string password) { diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index b58d4a363a..ea4eb97ccb 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -6,7 +6,8 @@ using System.Threading.Tasks; using osu.Framework.Bindables; using osu.Game.Localisation; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Online.Notifications; +using osu.Game.Online.Chat; +using osu.Game.Online.Notifications.WebSocket; using osu.Game.Users; namespace osu.Game.Online.API @@ -129,10 +130,9 @@ namespace osu.Game.Online.API /// Whether to use MessagePack for serialisation if available on this platform. IHubClientConnector? GetHubConnector(string clientName, string endpoint, bool preferMessagePack = true); - ///

- /// Constructs a new . - /// - NotificationsClientConnector GetNotificationsConnector(); + INotificationsClient NotificationsClient { get; } + + IChatClient GetChatClient(); /// /// Create a new user account. This is a blocking operation. diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 23989caae2..d0c686a666 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -16,7 +16,6 @@ using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Online.Notifications; using osu.Game.Overlays.Chat.Listing; namespace osu.Game.Online.Chat @@ -64,13 +63,8 @@ namespace osu.Game.Online.Chat /// public IBindableList AvailableChannels => availableChannels; - /// - /// Whether the client responsible for channel notifications is connected. - /// - public bool NotificationsConnected => connector.IsConnected.Value; - private readonly IAPIProvider api; - private readonly NotificationsClientConnector connector; + private readonly IChatClient chatClient; [Resolved] private UserLookupCache users { get; set; } @@ -85,7 +79,7 @@ namespace osu.Game.Online.Chat { this.api = api; - connector = api.GetNotificationsConnector(); + chatClient = api.GetChatClient(); CurrentChannel.ValueChanged += currentChannelChanged; } @@ -93,15 +87,11 @@ namespace osu.Game.Online.Chat [BackgroundDependencyLoader] private void load() { - connector.ChannelJoined += ch => Schedule(() => joinChannel(ch)); - - connector.ChannelParted += ch => Schedule(() => leaveChannel(getChannel(ch), false)); - - connector.NewMessages += msgs => Schedule(() => addMessages(msgs)); - - connector.PresenceReceived += () => Schedule(initializeChannels); - - connector.Start(); + chatClient.ChannelJoined += ch => Schedule(() => joinChannel(ch)); + chatClient.ChannelParted += ch => Schedule(() => leaveChannel(getChannel(ch), false)); + chatClient.NewMessages += msgs => Schedule(() => addMessages(msgs)); + chatClient.PresenceReceived += () => Schedule(initializeChannels); + chatClient.FetchInitialMessages(); apiState.BindTo(api.State); apiState.BindValueChanged(_ => SendAck(), true); @@ -655,7 +645,7 @@ namespace osu.Game.Online.Chat protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - connector?.Dispose(); + chatClient?.Dispose(); } } diff --git a/osu.Game/Online/Chat/IChatClient.cs b/osu.Game/Online/Chat/IChatClient.cs new file mode 100644 index 0000000000..94977b8acd --- /dev/null +++ b/osu.Game/Online/Chat/IChatClient.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 System; +using System.Collections.Generic; + +namespace osu.Game.Online.Chat +{ + public interface IChatClient : IDisposable + { + event Action? ChannelJoined; + event Action? ChannelParted; + event Action>? NewMessages; + event Action? PresenceReceived; + + void FetchInitialMessages(); + } +} diff --git a/osu.Game/Online/Chat/WebSocketChatClient.cs b/osu.Game/Online/Chat/WebSocketChatClient.cs new file mode 100644 index 0000000000..fb67c205dc --- /dev/null +++ b/osu.Game/Online/Chat/WebSocketChatClient.cs @@ -0,0 +1,148 @@ +// 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.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using Newtonsoft.Json; +using osu.Framework.Bindables; +using osu.Framework.Extensions; +using osu.Framework.Logging; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.Notifications.WebSocket; + +namespace osu.Game.Online.Chat +{ + public class WebSocketChatClient : IChatClient + { + public event Action? ChannelJoined; + public event Action? ChannelParted; + public event Action>? NewMessages; + public event Action? PresenceReceived; + + private readonly IAPIProvider api; + private readonly INotificationsClient client; + private readonly ConcurrentDictionary channelsMap = new ConcurrentDictionary(); + + public WebSocketChatClient(IAPIProvider api) + { + this.api = api; + client = api.NotificationsClient; + client.IsConnected.BindValueChanged(start, true); + } + + private void start(ValueChangedEvent connected) + { + if (!connected.NewValue) + return; + + client.MessageReceived += onMessageReceived; + client.SendAsync(new StartChatRequest()).WaitSafely(); + } + + public void FetchInitialMessages() + { + api.Queue(createInitialFetchRequest()); + } + + private APIRequest createInitialFetchRequest() + { + var fetchReq = new GetUpdatesRequest(0); + + fetchReq.Success += updates => + { + if (updates?.Presence != null) + { + foreach (var channel in updates.Presence) + joinChannel(channel); + + handleMessages(updates.Messages); + } + + PresenceReceived?.Invoke(); + }; + + return fetchReq; + } + + private void onMessageReceived(SocketMessage message) + { + switch (message.Event) + { + case @"chat.channel.join": + Debug.Assert(message.Data != null); + + Channel? joinedChannel = JsonConvert.DeserializeObject(message.Data.ToString()); + Debug.Assert(joinedChannel != null); + + joinChannel(joinedChannel); + break; + + case @"chat.channel.part": + Debug.Assert(message.Data != null); + + Channel? partedChannel = JsonConvert.DeserializeObject(message.Data.ToString()); + Debug.Assert(partedChannel != null); + + partChannel(partedChannel); + break; + + case @"chat.message.new": + Debug.Assert(message.Data != null); + + NewChatMessageData? messageData = JsonConvert.DeserializeObject(message.Data.ToString()); + Debug.Assert(messageData != null); + + foreach (var msg in messageData.Messages) + postToChannel(msg); + + break; + } + } + + private void postToChannel(Message message) + { + if (channelsMap.TryGetValue(message.ChannelId, out Channel? channel)) + { + joinChannel(channel); + NewMessages?.Invoke(new List { message }); + return; + } + + var req = new GetChannelRequest(message.ChannelId); + + req.Success += response => + { + joinChannel(channelsMap[message.ChannelId] = response.Channel); + NewMessages?.Invoke(new List { message }); + }; + req.Failure += ex => Logger.Error(ex, "Failed to join channel"); + + api.Queue(req); + } + + private void joinChannel(Channel ch) + { + ch.Joined.Value = true; + ChannelJoined?.Invoke(ch); + } + + private void partChannel(Channel channel) => ChannelParted?.Invoke(channel); + + private void handleMessages(List? messages) + { + if (messages == null) + return; + + NewMessages?.Invoke(messages); + } + + public void Dispose() + { + client.IsConnected.ValueChanged -= start; + client.MessageReceived -= onMessageReceived; + } + } +} diff --git a/osu.Game/Online/Notifications/NotificationsClientConnector.cs b/osu.Game/Online/Notifications/NotificationsClientConnector.cs deleted file mode 100644 index 34ce186cb8..0000000000 --- a/osu.Game/Online/Notifications/NotificationsClientConnector.cs +++ /dev/null @@ -1,42 +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 System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using osu.Game.Online.API; -using osu.Game.Online.Chat; - -namespace osu.Game.Online.Notifications -{ - /// - /// An abstract connector or s. - /// - public abstract class NotificationsClientConnector : PersistentEndpointClientConnector - { - public event Action? ChannelJoined; - public event Action? ChannelParted; - public event Action>? NewMessages; - public event Action? PresenceReceived; - - protected NotificationsClientConnector(IAPIProvider api) - : base(api) - { - } - - protected sealed override async Task BuildConnectionAsync(CancellationToken cancellationToken) - { - var client = await BuildNotificationClientAsync(cancellationToken).ConfigureAwait(false); - - client.ChannelJoined = c => ChannelJoined?.Invoke(c); - client.ChannelParted = c => ChannelParted?.Invoke(c); - client.NewMessages = m => NewMessages?.Invoke(m); - client.PresenceReceived = () => PresenceReceived?.Invoke(); - - return client; - } - - protected abstract Task BuildNotificationClientAsync(CancellationToken cancellationToken); - } -} diff --git a/osu.Game/Online/Notifications/WebSocket/DummyNotificationsClient.cs b/osu.Game/Online/Notifications/WebSocket/DummyNotificationsClient.cs new file mode 100644 index 0000000000..c1f3d25be7 --- /dev/null +++ b/osu.Game/Online/Notifications/WebSocket/DummyNotificationsClient.cs @@ -0,0 +1,29 @@ +// 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.Threading; +using System.Threading.Tasks; +using osu.Framework.Bindables; + +namespace osu.Game.Online.Notifications.WebSocket +{ + public class DummyNotificationsClient : INotificationsClient + { + public IBindable IsConnected => new BindableBool(true); + + public event Action? MessageReceived; + + public Func? HandleMessage; + + public Task SendAsync(SocketMessage message, CancellationToken? cancellationToken = default) + { + if (HandleMessage?.Invoke(message) != true) + throw new InvalidOperationException($@"{nameof(DummyNotificationsClient)} cannot process this message."); + + return Task.CompletedTask; + } + + public void Receive(SocketMessage message) => MessageReceived?.Invoke(message); + } +} diff --git a/osu.Game/Online/Notifications/WebSocket/INotificationsClient.cs b/osu.Game/Online/Notifications/WebSocket/INotificationsClient.cs new file mode 100644 index 0000000000..f687752047 --- /dev/null +++ b/osu.Game/Online/Notifications/WebSocket/INotificationsClient.cs @@ -0,0 +1,17 @@ +// 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.Threading; +using System.Threading.Tasks; +using osu.Framework.Bindables; + +namespace osu.Game.Online.Notifications.WebSocket +{ + public interface INotificationsClient + { + IBindable IsConnected { get; } + event Action? MessageReceived; + Task SendAsync(SocketMessage message, CancellationToken? cancellationToken = default); + } +} diff --git a/osu.Game/Online/Notifications/WebSocket/WebSocketNotificationsClient.cs b/osu.Game/Online/Notifications/WebSocket/WebSocketNotificationsClient.cs index 73e5dcec6f..854f46880f 100644 --- a/osu.Game/Online/Notifications/WebSocket/WebSocketNotificationsClient.cs +++ b/osu.Game/Online/Notifications/WebSocket/WebSocketNotificationsClient.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Concurrent; using System.Diagnostics; using System.Net; using System.Net.WebSockets; @@ -12,23 +11,20 @@ using System.Threading.Tasks; using Newtonsoft.Json; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Logging; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests; -using osu.Game.Online.Chat; namespace osu.Game.Online.Notifications.WebSocket { /// /// A notifications client which receives events via a websocket. /// - public class WebSocketNotificationsClient : NotificationsClient + public class WebSocketNotificationsClient : PersistentEndpointClient { + public event Action? MessageReceived; + private readonly ClientWebSocket socket; private readonly string endpoint; - private readonly ConcurrentDictionary channelsMap = new ConcurrentDictionary(); - public WebSocketNotificationsClient(ClientWebSocket socket, string endpoint, IAPIProvider api) - : base(api) + public WebSocketNotificationsClient(ClientWebSocket socket, string endpoint) { this.socket = socket; this.endpoint = endpoint; @@ -37,11 +33,7 @@ namespace osu.Game.Online.Notifications.WebSocket public override async Task ConnectAsync(CancellationToken cancellationToken) { await socket.ConnectAsync(new Uri(endpoint), cancellationToken).ConfigureAwait(false); - await sendMessage(new StartChatRequest(), CancellationToken.None).ConfigureAwait(false); - runReadLoop(cancellationToken); - - await base.ConnectAsync(cancellationToken).ConfigureAwait(false); } private void runReadLoop(CancellationToken cancellationToken) => Task.Run(async () => @@ -73,7 +65,7 @@ namespace osu.Game.Online.Notifications.WebSocket break; } - await onMessageReceivedAsync(message).ConfigureAwait(false); + MessageReceived?.Invoke(message); } break; @@ -105,69 +97,12 @@ namespace osu.Game.Online.Notifications.WebSocket } } - private async Task sendMessage(SocketMessage message, CancellationToken cancellationToken) + public async Task SendAsync(SocketMessage message, CancellationToken? cancellationToken = default) { if (socket.State != WebSocketState.Open) return; - await socket.SendAsync(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message)), WebSocketMessageType.Text, true, cancellationToken).ConfigureAwait(false); - } - - private async Task onMessageReceivedAsync(SocketMessage message) - { - switch (message.Event) - { - case @"chat.channel.join": - Debug.Assert(message.Data != null); - - Channel? joinedChannel = JsonConvert.DeserializeObject(message.Data.ToString()); - Debug.Assert(joinedChannel != null); - - HandleChannelJoined(joinedChannel); - break; - - case @"chat.channel.part": - Debug.Assert(message.Data != null); - - Channel? partedChannel = JsonConvert.DeserializeObject(message.Data.ToString()); - Debug.Assert(partedChannel != null); - - HandleChannelParted(partedChannel); - break; - - case @"chat.message.new": - Debug.Assert(message.Data != null); - - NewChatMessageData? messageData = JsonConvert.DeserializeObject(message.Data.ToString()); - Debug.Assert(messageData != null); - - foreach (var msg in messageData.Messages) - HandleChannelJoined(await getChannel(msg.ChannelId).ConfigureAwait(false)); - - HandleMessages(messageData.Messages); - break; - } - } - - private async Task getChannel(long channelId) - { - if (channelsMap.TryGetValue(channelId, out Channel? channel)) - return channel; - - var tsc = new TaskCompletionSource(); - var req = new GetChannelRequest(channelId); - - req.Success += response => - { - channelsMap[channelId] = response.Channel; - tsc.SetResult(response.Channel); - }; - - req.Failure += ex => tsc.SetException(ex); - - API.Queue(req); - - return await tsc.Task.ConfigureAwait(false); + await socket.SendAsync(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message)), WebSocketMessageType.Text, true, cancellationToken ?? CancellationToken.None).ConfigureAwait(false); } public override async ValueTask DisposeAsync() diff --git a/osu.Game/Online/Notifications/WebSocket/WebSocketNotificationsClientConnector.cs b/osu.Game/Online/Notifications/WebSocket/WebSocketNotificationsClientConnector.cs index f50369a06c..73fe29d441 100644 --- a/osu.Game/Online/Notifications/WebSocket/WebSocketNotificationsClientConnector.cs +++ b/osu.Game/Online/Notifications/WebSocket/WebSocketNotificationsClientConnector.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 System; using System.Net; using System.Net.WebSockets; using System.Threading; @@ -13,17 +14,20 @@ namespace osu.Game.Online.Notifications.WebSocket /// /// A connector for s that receive events via a websocket. /// - public class WebSocketNotificationsClientConnector : NotificationsClientConnector + public class WebSocketNotificationsClientConnector : PersistentEndpointClientConnector, INotificationsClient { + public event Action? MessageReceived; + private readonly IAPIProvider api; public WebSocketNotificationsClientConnector(IAPIProvider api) : base(api) { this.api = api; + Start(); } - protected override async Task BuildNotificationClientAsync(CancellationToken cancellationToken) + protected override async Task BuildConnectionAsync(CancellationToken cancellationToken) { var tcs = new TaskCompletionSource(); @@ -40,7 +44,17 @@ namespace osu.Game.Online.Notifications.WebSocket if (socket.Options.Proxy != null) socket.Options.Proxy.Credentials = CredentialCache.DefaultCredentials; - return new WebSocketNotificationsClient(socket, endpoint, api); + var client = new WebSocketNotificationsClient(socket, endpoint); + client.MessageReceived += msg => MessageReceived?.Invoke(msg); + return client; + } + + public Task SendAsync(SocketMessage message, CancellationToken? cancellationToken = default) + { + if (CurrentConnection is not WebSocketNotificationsClient webSocketClient) + return Task.CompletedTask; + + return webSocketClient.SendAsync(message, cancellationToken); } } } diff --git a/osu.Game/Online/Notifications/NotificationsClient.cs b/osu.Game/Tests/PollingChatClient.cs similarity index 59% rename from osu.Game/Online/Notifications/NotificationsClient.cs rename to osu.Game/Tests/PollingChatClient.cs index 5762e0e588..eb29b35c1d 100644 --- a/osu.Game/Online/Notifications/NotificationsClient.cs +++ b/osu.Game/Tests/PollingChatClient.cs @@ -6,34 +6,39 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using osu.Game.Online; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.Chat; -namespace osu.Game.Online.Notifications +namespace osu.Game.Tests { - /// - /// An abstract client which receives notification-related events (chat/notifications). - /// - public abstract class NotificationsClient : PersistentEndpointClient + public class PollingChatClient : PersistentEndpointClient { - public Action? ChannelJoined; - public Action? ChannelParted; - public Action>? NewMessages; - public Action? PresenceReceived; + public event Action? ChannelJoined; + public event Action>? NewMessages; + public event Action? PresenceReceived; - protected readonly IAPIProvider API; + private readonly IAPIProvider api; private long lastMessageId; - protected NotificationsClient(IAPIProvider api) + public PollingChatClient(IAPIProvider api) { - API = api; + this.api = api; } public override Task ConnectAsync(CancellationToken cancellationToken) { - API.Queue(CreateInitialFetchRequest(0)); + Task.Run(async () => + { + while (!cancellationToken.IsCancellationRequested) + { + await api.PerformAsync(CreateInitialFetchRequest()).ConfigureAwait(true); + await Task.Delay(1000, cancellationToken).ConfigureAwait(true); + } + }, cancellationToken); + return Task.CompletedTask; } @@ -46,11 +51,11 @@ namespace osu.Game.Online.Notifications if (updates?.Presence != null) { foreach (var channel in updates.Presence) - HandleChannelJoined(channel); + handleChannelJoined(channel); //todo: handle left channels - HandleMessages(updates.Messages); + handleMessages(updates.Messages); } PresenceReceived?.Invoke(); @@ -59,15 +64,13 @@ namespace osu.Game.Online.Notifications return fetchReq; } - protected void HandleChannelJoined(Channel channel) + private void handleChannelJoined(Channel channel) { channel.Joined.Value = true; ChannelJoined?.Invoke(channel); } - protected void HandleChannelParted(Channel channel) => ChannelParted?.Invoke(channel); - - protected void HandleMessages(List? messages) + private void handleMessages(List? messages) { if (messages == null) return; diff --git a/osu.Game/Tests/PollingChatClientConnector.cs b/osu.Game/Tests/PollingChatClientConnector.cs new file mode 100644 index 0000000000..3e96a8cde7 --- /dev/null +++ b/osu.Game/Tests/PollingChatClientConnector.cs @@ -0,0 +1,48 @@ +// 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.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using osu.Game.Online; +using osu.Game.Online.API; +using osu.Game.Online.Chat; + +namespace osu.Game.Tests +{ + public class PollingChatClientConnector : PersistentEndpointClientConnector, IChatClient + { + public event Action? ChannelJoined; + + public event Action? ChannelParted + { + add { } + remove { } + } + + public event Action>? NewMessages; + public event Action? PresenceReceived; + + public void FetchInitialMessages() + { + // don't really need to do anything special if we poll every second anyway. + } + + public PollingChatClientConnector(IAPIProvider api) + : base(api) + { + } + + protected sealed override Task BuildConnectionAsync(CancellationToken cancellationToken) + { + var client = new PollingChatClient(API); + + client.ChannelJoined += c => ChannelJoined?.Invoke(c); + client.NewMessages += m => NewMessages?.Invoke(m); + client.PresenceReceived += () => PresenceReceived?.Invoke(); + + return Task.FromResult(client); + } + } +} diff --git a/osu.Game/Tests/PollingNotificationsClient.cs b/osu.Game/Tests/PollingNotificationsClient.cs deleted file mode 100644 index 450c763170..0000000000 --- a/osu.Game/Tests/PollingNotificationsClient.cs +++ /dev/null @@ -1,35 +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 System.Threading; -using System.Threading.Tasks; -using osu.Game.Online.API; -using osu.Game.Online.Notifications; - -namespace osu.Game.Tests -{ - /// - /// A notifications client which polls for new messages every second. - /// - public class PollingNotificationsClient : NotificationsClient - { - public PollingNotificationsClient(IAPIProvider api) - : base(api) - { - } - - public override Task ConnectAsync(CancellationToken cancellationToken) - { - Task.Run(async () => - { - while (!cancellationToken.IsCancellationRequested) - { - await API.PerformAsync(CreateInitialFetchRequest()).ConfigureAwait(true); - await Task.Delay(1000, cancellationToken).ConfigureAwait(true); - } - }, cancellationToken); - - return Task.CompletedTask; - } - } -} diff --git a/osu.Game/Tests/PollingNotificationsClientConnector.cs b/osu.Game/Tests/PollingNotificationsClientConnector.cs deleted file mode 100644 index 823fc9d157..0000000000 --- a/osu.Game/Tests/PollingNotificationsClientConnector.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 System.Threading; -using System.Threading.Tasks; -using osu.Game.Online.API; -using osu.Game.Online.Notifications; - -namespace osu.Game.Tests -{ - /// - /// A connector for s that poll for new messages. - /// - public class PollingNotificationsClientConnector : NotificationsClientConnector - { - public PollingNotificationsClientConnector(IAPIProvider api) - : base(api) - { - } - - protected override Task BuildNotificationClientAsync(CancellationToken cancellationToken) - => Task.FromResult((NotificationsClient)new PollingNotificationsClient(API)); - } -} From c463aa5ba1e67d0fc0fca5b5db5230a3875cf52a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 25 Jan 2024 14:46:39 +0100 Subject: [PATCH 2724/4092] xmldoc everything --- osu.Game/Online/API/IAPIProvider.cs | 6 ++++ osu.Game/Online/Chat/ChannelManager.cs | 2 +- osu.Game/Online/Chat/IChatClient.cs | 29 ++++++++++++++++--- osu.Game/Online/Chat/WebSocketChatClient.cs | 10 ++----- .../WebSocket/INotificationsClient.cs | 14 +++++++++ osu.Game/Tests/PollingChatClientConnector.cs | 2 +- 6 files changed, 50 insertions(+), 13 deletions(-) diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index ea4eb97ccb..a050b2dfae 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -130,8 +130,14 @@ namespace osu.Game.Online.API /// Whether to use MessagePack for serialisation if available on this platform. IHubClientConnector? GetHubConnector(string clientName, string endpoint, bool preferMessagePack = true); + /// + /// Accesses the used to receive asynchronous notifications from web. + /// INotificationsClient NotificationsClient { get; } + /// + /// Creates a instance to use in order to chat. + /// IChatClient GetChatClient(); /// diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index d0c686a666..74e85c595c 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -91,7 +91,7 @@ namespace osu.Game.Online.Chat chatClient.ChannelParted += ch => Schedule(() => leaveChannel(getChannel(ch), false)); chatClient.NewMessages += msgs => Schedule(() => addMessages(msgs)); chatClient.PresenceReceived += () => Schedule(initializeChannels); - chatClient.FetchInitialMessages(); + chatClient.RequestPresence(); apiState.BindTo(api.State); apiState.BindValueChanged(_ => SendAck(), true); diff --git a/osu.Game/Online/Chat/IChatClient.cs b/osu.Game/Online/Chat/IChatClient.cs index 94977b8acd..290ee22710 100644 --- a/osu.Game/Online/Chat/IChatClient.cs +++ b/osu.Game/Online/Chat/IChatClient.cs @@ -6,13 +6,34 @@ using System.Collections.Generic; namespace osu.Game.Online.Chat { + /// + /// Interface for consuming online chat. + /// public interface IChatClient : IDisposable { + /// + /// Fired when a has been joined. + /// event Action? ChannelJoined; - event Action? ChannelParted; - event Action>? NewMessages; - event Action? PresenceReceived; - void FetchInitialMessages(); + /// + /// Fired when a has been parted. + /// + event Action? ChannelParted; + + /// + /// Fired when new s have arrived from the server. + /// + event Action>? NewMessages; + + /// + /// Requests presence information from the server. + /// + void RequestPresence(); + + /// + /// Fired when the initial user presence information has been received. + /// + event Action? PresenceReceived; } } diff --git a/osu.Game/Online/Chat/WebSocketChatClient.cs b/osu.Game/Online/Chat/WebSocketChatClient.cs index fb67c205dc..05d3b7b3ce 100644 --- a/osu.Game/Online/Chat/WebSocketChatClient.cs +++ b/osu.Game/Online/Chat/WebSocketChatClient.cs @@ -40,14 +40,10 @@ namespace osu.Game.Online.Chat client.MessageReceived += onMessageReceived; client.SendAsync(new StartChatRequest()).WaitSafely(); + RequestPresence(); } - public void FetchInitialMessages() - { - api.Queue(createInitialFetchRequest()); - } - - private APIRequest createInitialFetchRequest() + public void RequestPresence() { var fetchReq = new GetUpdatesRequest(0); @@ -64,7 +60,7 @@ namespace osu.Game.Online.Chat PresenceReceived?.Invoke(); }; - return fetchReq; + api.Queue(fetchReq); } private void onMessageReceived(SocketMessage message) diff --git a/osu.Game/Online/Notifications/WebSocket/INotificationsClient.cs b/osu.Game/Online/Notifications/WebSocket/INotificationsClient.cs index f687752047..9a222d0fdd 100644 --- a/osu.Game/Online/Notifications/WebSocket/INotificationsClient.cs +++ b/osu.Game/Online/Notifications/WebSocket/INotificationsClient.cs @@ -8,10 +8,24 @@ using osu.Framework.Bindables; namespace osu.Game.Online.Notifications.WebSocket { + /// + /// A client for asynchronous notifications sent by osu-web. + /// public interface INotificationsClient { + /// + /// Whether this is currently connected to a server. + /// IBindable IsConnected { get; } + + /// + /// Invoked when a new arrives for this client. + /// event Action? MessageReceived; + + /// + /// Sends a to the notification server. + /// Task SendAsync(SocketMessage message, CancellationToken? cancellationToken = default); } } diff --git a/osu.Game/Tests/PollingChatClientConnector.cs b/osu.Game/Tests/PollingChatClientConnector.cs index 3e96a8cde7..1cab24dae6 100644 --- a/osu.Game/Tests/PollingChatClientConnector.cs +++ b/osu.Game/Tests/PollingChatClientConnector.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests public event Action>? NewMessages; public event Action? PresenceReceived; - public void FetchInitialMessages() + public void RequestPresence() { // don't really need to do anything special if we poll every second anyway. } From 14067c2e573e00719b849bd37c01e37f96dbc712 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jan 2024 00:39:29 +0900 Subject: [PATCH 2725/4092] Remove unused using statements --- osu.Game/Graphics/Backgrounds/TrianglesV2.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Graphics/Backgrounds/TrianglesV2.cs b/osu.Game/Graphics/Backgrounds/TrianglesV2.cs index bcb73f71cc..4143a6d76d 100644 --- a/osu.Game/Graphics/Backgrounds/TrianglesV2.cs +++ b/osu.Game/Graphics/Backgrounds/TrianglesV2.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Rendering; using osu.Framework.Graphics.Shaders; From 93bd3ce5ae73eb6f00649e09ace1acd2224ecc95 Mon Sep 17 00:00:00 2001 From: Chandler Stowell Date: Thu, 25 Jan 2024 11:25:41 -0500 Subject: [PATCH 2726/4092] update `DrawableHitCircle.ApplyResult` to pass `this` to its callback --- .../DrawableEmptyFreeformHitObject.cs | 2 +- .../Drawables/DrawablePippidonHitObject.cs | 6 ++--- .../DrawableEmptyScrollingHitObject.cs | 2 +- .../Drawables/DrawablePippidonHitObject.cs | 5 ++-- .../Drawables/DrawableCatchHitObject.cs | 7 +++--- .../Objects/Drawables/DrawableHoldNote.cs | 2 +- .../Objects/Drawables/DrawableHoldNoteBody.cs | 9 ++++++- .../Drawables/DrawableManiaHitObject.cs | 2 +- .../Objects/Drawables/DrawableNote.cs | 16 +++++++++---- .../TestSceneHitCircle.cs | 2 +- .../TestSceneHitCircleLateFade.cs | 2 +- .../Objects/Drawables/DrawableHitCircle.cs | 19 ++++++++------- .../Objects/Drawables/DrawableOsuHitObject.cs | 4 ++-- .../Objects/Drawables/DrawableSlider.cs | 14 +++++------ .../Objects/Drawables/DrawableSpinner.cs | 5 ++-- .../Objects/Drawables/DrawableSpinnerTick.cs | 12 +++++++++- .../Objects/Drawables/DrawableDrumRoll.cs | 8 +++---- .../Objects/Drawables/DrawableDrumRollTick.cs | 13 +++++----- .../Objects/Drawables/DrawableFlyingHit.cs | 2 +- .../Objects/Drawables/DrawableHit.cs | 24 ++++++++++++------- .../Drawables/DrawableStrongNestedHit.cs | 2 +- .../Objects/Drawables/DrawableSwell.cs | 16 +++++++------ .../Objects/Drawables/DrawableSwellTick.cs | 10 +++++--- .../Gameplay/TestSceneDrawableHitObject.cs | 2 +- .../Gameplay/TestScenePoolingRuleset.cs | 8 +++---- .../Objects/Drawables/DrawableHitObject.cs | 13 ++-------- 26 files changed, 120 insertions(+), 87 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/Drawables/DrawableEmptyFreeformHitObject.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/Drawables/DrawableEmptyFreeformHitObject.cs index e8f511bc4b..3ad8f06fb4 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/Drawables/DrawableEmptyFreeformHitObject.cs +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/Drawables/DrawableEmptyFreeformHitObject.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.EmptyFreeform.Objects.Drawables { if (timeOffset >= 0) // todo: implement judgement logic - ApplyResult(static r => r.Type = HitResult.Perfect); + ApplyResult(static (r, hitObject) => r.Type = HitResult.Perfect); } protected override void UpdateHitStateTransforms(ArmedState state) diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs index a8bb57ba18..925f2d04bf 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs @@ -50,10 +50,10 @@ namespace osu.Game.Rulesets.Pippidon.Objects.Drawables { if (timeOffset >= 0) { - ApplyResult(static (r, isHovered) => + ApplyResult(static (r, hitObject) => { - r.Type = isHovered ? HitResult.Perfect : HitResult.Miss; - }, IsHovered); + r.Type = hitObject.IsHovered ? HitResult.Perfect : HitResult.Miss; + }); } } diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/Drawables/DrawableEmptyScrollingHitObject.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/Drawables/DrawableEmptyScrollingHitObject.cs index 070a802aea..408bbea717 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/Drawables/DrawableEmptyScrollingHitObject.cs +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/Drawables/DrawableEmptyScrollingHitObject.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.EmptyScrolling.Objects.Drawables { if (timeOffset >= 0) // todo: implement judgement logic - ApplyResult(static r => r.Type = HitResult.Perfect); + ApplyResult(static (r, hitObject) => r.Type = HitResult.Perfect); } protected override void UpdateHitStateTransforms(ArmedState state) diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs index 9983ec20b0..2c9eac7f65 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs @@ -50,10 +50,11 @@ namespace osu.Game.Rulesets.Pippidon.Objects.Drawables { if (timeOffset >= 0) { - ApplyResult(static (r, pippidonHitObject) => + ApplyResult(static (r, hitObject) => { + var pippidonHitObject = (DrawablePippidonHitObject)hitObject; r.Type = pippidonHitObject.currentLane.Value == pippidonHitObject.HitObject.Lane ? HitResult.Perfect : HitResult.Miss; - }, this); + }); } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs index 5a921f36f5..721c6aaa59 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs @@ -64,10 +64,11 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables if (timeOffset >= 0 && Result != null) { - ApplyResult(static (r, state) => + ApplyResult(static (r, hitObject) => { - r.Type = state.CheckPosition.Invoke(state.HitObject) ? r.Judgement.MaxResult : r.Judgement.MinResult; - }, this); + var catchHitObject = (DrawableCatchHitObject)hitObject; + r.Type = catchHitObject.CheckPosition!.Invoke(catchHitObject.HitObject) ? r.Judgement.MaxResult : r.Judgement.MinResult; + }); } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index e5056d5167..6c70ab3526 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -265,7 +265,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (Tail.AllJudged) { if (Tail.IsHit) - ApplyResult(static r => r.Type = r.Judgement.MaxResult); + ApplyResult(static (r, _) => r.Type = r.Judgement.MaxResult); else MissForcefully(); } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteBody.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteBody.cs index 317da0580c..731b1b6298 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteBody.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteBody.cs @@ -11,6 +11,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables public override bool DisplayResult => false; + private bool hit; + public DrawableHoldNoteBody() : this(null) { @@ -25,7 +27,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { if (AllJudged) return; - ApplyResult(static (r, hit) => r.Type = hit ? r.Judgement.MaxResult : r.Judgement.MinResult, hit); + this.hit = hit; + ApplyResult(static (r, hitObject) => + { + var holdNoteBody = (DrawableHoldNoteBody)hitObject; + r.Type = holdNoteBody.hit ? r.Judgement.MaxResult : r.Judgement.MinResult; + }); } } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index dea0817869..2d10fa27cd 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// /// Causes this to get missed, disregarding all conditions in implementations of . /// - public virtual void MissForcefully() => ApplyResult(static r => r.Type = r.Judgement.MinResult); + public virtual void MissForcefully() => ApplyResult(static (r, _) => r.Type = r.Judgement.MinResult); } public abstract partial class DrawableManiaHitObject : DrawableManiaHitObject diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 985007f905..a70253798a 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -38,6 +38,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private Drawable headPiece; + private HitResult hitResult; + public DrawableNote() : this(null) { @@ -89,18 +91,22 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (!userTriggered) { if (!HitObject.HitWindows.CanBeHit(timeOffset)) - ApplyResult(static r => r.Type = r.Judgement.MinResult); + ApplyResult(static (r, _) => r.Type = r.Judgement.MinResult); return; } - var result = HitObject.HitWindows.ResultFor(timeOffset); - if (result == HitResult.None) + hitResult = HitObject.HitWindows.ResultFor(timeOffset); + if (hitResult == HitResult.None) return; - result = GetCappedResult(result); + hitResult = GetCappedResult(hitResult); - ApplyResult(static (r, result) => r.Type = result, result); + ApplyResult(static (r, hitObject) => + { + var note = (DrawableNote)hitObject; + r.Type = note.hitResult; + }); } /// diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs index 30b0451a3b..8d4145f2c1 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs @@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Osu.Tests if (auto && !userTriggered && timeOffset > hitOffset && CheckHittable?.Invoke(this, Time.Current, HitResult.Great) == ClickAction.Hit) { // force success - ApplyResult(r => r.Type = HitResult.Great); + ApplyResult(static (r, _) => r.Type = HitResult.Great); } else base.CheckForResult(userTriggered, timeOffset); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs index 7824f26251..2d1e9c1270 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs @@ -208,7 +208,7 @@ namespace osu.Game.Rulesets.Osu.Tests if (shouldHit && !userTriggered && timeOffset >= 0) { // force success - ApplyResult(r => r.Type = HitResult.Great); + ApplyResult(static (r, _) => r.Type = HitResult.Great); } else base.CheckForResult(userTriggered, timeOffset); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 8284229d82..ce5422b180 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -44,6 +44,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private Container scaleContainer; private InputManager inputManager; + private HitResult hitResult; public DrawableHitCircle() : this(null) @@ -155,34 +156,34 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (!userTriggered) { if (!HitObject.HitWindows.CanBeHit(timeOffset)) - ApplyResult(static r => r.Type = r.Judgement.MinResult); + ApplyResult(static (r, _) => r.Type = r.Judgement.MinResult); return; } - var result = ResultFor(timeOffset); - var clickAction = CheckHittable?.Invoke(this, Time.Current, result); + hitResult = ResultFor(timeOffset); + var clickAction = CheckHittable?.Invoke(this, Time.Current, hitResult); if (clickAction == ClickAction.Shake) Shake(); - if (result == HitResult.None || clickAction != ClickAction.Hit) + if (hitResult == HitResult.None || clickAction != ClickAction.Hit) return; - ApplyResult(static (r, state) => + ApplyResult(static (r, hitObject) => { - var (hitCircle, hitResult) = state; + var hitCircle = (DrawableHitCircle)hitObject; var circleResult = (OsuHitCircleJudgementResult)r; // Todo: This should also consider misses, but they're a little more interesting to handle, since we don't necessarily know the position at the time of a miss. - if (hitResult.IsHit()) + if (hitCircle.hitResult.IsHit()) { var localMousePosition = hitCircle.ToLocalSpace(hitCircle.inputManager.CurrentState.Mouse.Position); circleResult.CursorPositionAtHit = hitCircle.HitObject.StackedPosition + (localMousePosition - hitCircle.DrawSize / 2); } - circleResult.Type = hitResult; - }, (this, result)); + circleResult.Type = hitCircle.hitResult; + }); } /// diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index cc06d009c9..6de60a9d51 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -100,12 +100,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables /// /// Causes this to get hit, disregarding all conditions in implementations of . /// - public void HitForcefully() => ApplyResult(static r => r.Type = r.Judgement.MaxResult); + public void HitForcefully() => ApplyResult(static (r, _) => r.Type = r.Judgement.MaxResult); /// /// Causes this to get missed, disregarding all conditions in implementations of . /// - public void MissForcefully() => ApplyResult(static r => r.Type = r.Judgement.MinResult); + public void MissForcefully() => ApplyResult(static (r, _) => r.Type = r.Judgement.MinResult); private RectangleF parentScreenSpaceRectangle => ((DrawableOsuHitObject)ParentHitObject)?.parentScreenSpaceRectangle ?? Parent!.ScreenSpaceDrawQuad.AABBFloat; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 3c298cc6af..c0ff258352 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -292,10 +292,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (HitObject.ClassicSliderBehaviour) { // Classic behaviour means a slider is judged proportionally to the number of nested hitobjects hit. This is the classic osu!stable scoring. - ApplyResult(static (r, nestedHitObjects) => + ApplyResult(static (r, hitObject) => { - int totalTicks = nestedHitObjects.Count; - int hitTicks = nestedHitObjects.Count(h => h.IsHit); + int totalTicks = hitObject.NestedHitObjects.Count; + int hitTicks = hitObject.NestedHitObjects.Count(h => h.IsHit); if (hitTicks == totalTicks) r.Type = HitResult.Great; @@ -306,16 +306,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables double hitFraction = (double)hitTicks / totalTicks; r.Type = hitFraction >= 0.5 ? HitResult.Ok : HitResult.Meh; } - }, NestedHitObjects); + }); } else { // If only the nested hitobjects are judged, then the slider's own judgement is ignored for scoring purposes. // But the slider needs to still be judged with a reasonable hit/miss result for visual purposes (hit/miss transforms, etc). - ApplyResult(static (r, nestedHitObjects) => + ApplyResult(static (r, hitObject) => { - r.Type = nestedHitObjects.Any(h => h.Result.IsHit) ? r.Judgement.MaxResult : r.Judgement.MinResult; - }, NestedHitObjects); + r.Type = hitObject.NestedHitObjects.Any(h => h.Result.IsHit) ? r.Judgement.MaxResult : r.Judgement.MinResult; + }); } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index d21d02c8ce..3679bc9775 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -258,8 +258,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables foreach (var tick in ticks.Where(t => !t.Result.HasResult)) tick.TriggerResult(false); - ApplyResult(static (r, spinner) => + ApplyResult(static (r, hitObject) => { + var spinner = (DrawableSpinner)hitObject; if (spinner.Progress >= 1) r.Type = HitResult.Great; else if (spinner.Progress > .9) @@ -268,7 +269,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables r.Type = HitResult.Meh; else if (spinner.Time.Current >= spinner.HitObject.EndTime) r.Type = r.Judgement.MinResult; - }, this); + }); } protected override void Update() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs index 1c3ff29118..628f07a281 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs @@ -11,6 +11,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public override bool DisplayResult => false; + private bool hit; + public DrawableSpinnerTick() : this(null) { @@ -35,6 +37,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables /// Apply a judgement result. /// /// Whether this tick was reached. - internal void TriggerResult(bool hit) => ApplyResult(static (r, hit) => r.Type = hit ? r.Judgement.MaxResult : r.Judgement.MinResult, hit); + internal void TriggerResult(bool hit) + { + this.hit = hit; + ApplyResult(static (r, hitObject) => + { + var spinnerTick = (DrawableSpinnerTick)hitObject; + r.Type = spinnerTick.hit ? r.Judgement.MaxResult : r.Judgement.MinResult; + }); + } } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index d3fe363857..2e40875af1 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (timeOffset < 0) return; - ApplyResult(static r => r.Type = r.Judgement.MaxResult); + ApplyResult(static (r, _) => r.Type = r.Judgement.MaxResult); } protected override void UpdateHitStateTransforms(ArmedState state) @@ -192,10 +192,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!ParentHitObject.Judged) return; - ApplyResult(static (r, parentHitObject) => + ApplyResult(static (r, hitObject) => { - r.Type = parentHitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult; - }, ParentHitObject); + r.Type = hitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult; + }); } public override bool OnPressed(KeyBindingPressEvent e) => false; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs index de9a3a31c5..aa678d7043 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs @@ -49,14 +49,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!userTriggered) { if (timeOffset > HitObject.HitWindow) - ApplyResult(static r => r.Type = r.Judgement.MinResult); + ApplyResult(static (r, _) => r.Type = r.Judgement.MinResult); return; } if (Math.Abs(timeOffset) > HitObject.HitWindow) return; - ApplyResult(static r => r.Type = r.Judgement.MaxResult); + ApplyResult(static (r, _) => r.Type = r.Judgement.MaxResult); } public override void OnKilled() @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables base.OnKilled(); if (Time.Current > HitObject.GetEndTime() && !Judged) - ApplyResult(static r => r.Type = r.Judgement.MinResult); + ApplyResult(static (r, _) => r.Type = r.Judgement.MinResult); } protected override void UpdateHitStateTransforms(ArmedState state) @@ -105,10 +105,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!ParentHitObject.Judged) return; - ApplyResult(static (r, parentHitObject) => + ApplyResult(static (r, hitObject) => { - r.Type = parentHitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult; - }, ParentHitObject); + var nestedHit = (StrongNestedHit)hitObject; + r.Type = nestedHit.ParentHitObject!.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult; + }); } public override bool OnPressed(KeyBindingPressEvent e) => false; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingHit.cs index 1332b9e950..4349dff9f9 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingHit.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected override void LoadComplete() { base.LoadComplete(); - ApplyResult(static r => r.Type = r.Judgement.MaxResult); + ApplyResult(static (r, _) => r.Type = r.Judgement.MaxResult); } protected override void LoadSamples() diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index c3bd76bf81..cf8e4050ee 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -37,6 +37,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables private double? lastPressHandleTime; + private HitResult hitResult; + private readonly Bindable type = new Bindable(); public DrawableHit() @@ -99,18 +101,24 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!userTriggered) { if (!HitObject.HitWindows.CanBeHit(timeOffset)) - ApplyResult(static r => r.Type = r.Judgement.MinResult); + ApplyResult(static (r, _) => r.Type = r.Judgement.MinResult); return; } - var result = HitObject.HitWindows.ResultFor(timeOffset); - if (result == HitResult.None) + hitResult = HitObject.HitWindows.ResultFor(timeOffset); + if (hitResult == HitResult.None) return; if (!validActionPressed) - ApplyResult(static r => r.Type = r.Judgement.MinResult); + ApplyResult(static (r, _) => r.Type = r.Judgement.MinResult); else - ApplyResult(static (r, result) => r.Type = result, result); + { + ApplyResult(static (r, hitObject) => + { + var drawableHit = (DrawableHit)hitObject; + r.Type = drawableHit.hitResult; + }); + } } public override bool OnPressed(KeyBindingPressEvent e) @@ -209,19 +217,19 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!ParentHitObject.Result.IsHit) { - ApplyResult(static r => r.Type = r.Judgement.MinResult); + ApplyResult(static (r, _) => r.Type = r.Judgement.MinResult); return; } if (!userTriggered) { if (timeOffset - ParentHitObject.Result.TimeOffset > SECOND_HIT_WINDOW) - ApplyResult(static r => r.Type = r.Judgement.MinResult); + ApplyResult(static (r, _) => r.Type = r.Judgement.MinResult); return; } if (Math.Abs(timeOffset - ParentHitObject.Result.TimeOffset) <= SECOND_HIT_WINDOW) - ApplyResult(static r => r.Type = r.Judgement.MaxResult); + ApplyResult(static (r, _) => r.Type = r.Judgement.MaxResult); } public override bool OnPressed(KeyBindingPressEvent e) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs index 4080c14066..8f99538448 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables // it can happen that the hit window of the nested strong hit extends past the lifetime of the parent object. // this is a safety to prevent such cases from causing the nested hit to never be judged and as such prevent gameplay from completing. if (!Judged && Time.Current > ParentHitObject?.HitObject.GetEndTime()) - ApplyResult(static r => r.Type = r.Judgement.MinResult); + ApplyResult(static (r, _) => r.Type = r.Judgement.MinResult); } } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index d48b78283b..0781ea5e2a 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -41,6 +41,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables private double? lastPressHandleTime; + private int numHits; + public override bool DisplayResult => false; public DrawableSwell() @@ -192,7 +194,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables nextTick?.TriggerResult(true); - int numHits = ticks.Count(r => r.IsHit); + numHits = ticks.Count(r => r.IsHit); float completion = (float)numHits / HitObject.RequiredHits; @@ -206,14 +208,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables expandingRing.ScaleTo(1f + Math.Min(target_ring_scale - 1f, (target_ring_scale - 1f) * completion * 1.3f), 260, Easing.OutQuint); if (numHits == HitObject.RequiredHits) - ApplyResult(static r => r.Type = r.Judgement.MaxResult); + ApplyResult(static (r, _) => r.Type = r.Judgement.MaxResult); } else { if (timeOffset < 0) return; - int numHits = 0; + numHits = 0; foreach (var tick in ticks) { @@ -227,11 +229,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables tick.TriggerResult(false); } - ApplyResult(static (r, state) => + ApplyResult(static (r, hitObject) => { - var (numHits, hitObject) = state; - r.Type = numHits == hitObject.RequiredHits ? r.Judgement.MaxResult : r.Judgement.MinResult; - }, (numHits, HitObject)); + var swell = (DrawableSwell)hitObject; + r.Type = swell.numHits == swell.HitObject.RequiredHits ? r.Judgement.MaxResult : r.Judgement.MinResult; + }); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs index ad1d09bc7b..557438e5e5 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs @@ -15,6 +15,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { public override bool DisplayResult => false; + private bool hit; + public DrawableSwellTick() : this(null) { @@ -29,11 +31,13 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public void TriggerResult(bool hit) { + this.hit = hit; HitObject.StartTime = Time.Current; - ApplyResult(static (r, hit) => + ApplyResult(static (r, hitObject) => { - r.Type = hit ? r.Judgement.MaxResult : r.Judgement.MinResult; - }, hit); + var swellTick = (DrawableSwellTick)hitObject; + r.Type = swellTick.hit ? r.Judgement.MaxResult : r.Judgement.MinResult; + }); } protected override void CheckForResult(bool userTriggered, double timeOffset) diff --git a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs index 10dbede2e0..bf1e52aab5 100644 --- a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs +++ b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs @@ -216,7 +216,7 @@ namespace osu.Game.Tests.Gameplay LifetimeStart = LIFETIME_ON_APPLY; } - public void MissForcefully() => ApplyResult(r => r.Type = HitResult.Miss); + public void MissForcefully() => ApplyResult(static (r, _) => r.Type = HitResult.Miss); protected override void UpdateHitStateTransforms(ArmedState state) { diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs index fea7456472..00bd58e303 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs @@ -431,7 +431,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected override void CheckForResult(bool userTriggered, double timeOffset) { if (timeOffset > HitObject.Duration) - ApplyResult(r => r.Type = r.Judgement.MaxResult); + ApplyResult(static (r, _) => r.Type = r.Judgement.MaxResult); } protected override void UpdateHitStateTransforms(ArmedState state) @@ -468,7 +468,7 @@ namespace osu.Game.Tests.Visual.Gameplay public override void OnKilled() { base.OnKilled(); - ApplyResult(r => r.Type = r.Judgement.MinResult); + ApplyResult(static (r, _) => r.Type = r.Judgement.MinResult); } } @@ -547,7 +547,7 @@ namespace osu.Game.Tests.Visual.Gameplay { base.CheckForResult(userTriggered, timeOffset); if (timeOffset >= 0) - ApplyResult(r => r.Type = r.Judgement.MaxResult); + ApplyResult(static (r, _) => r.Type = r.Judgement.MaxResult); } } @@ -596,7 +596,7 @@ namespace osu.Game.Tests.Visual.Gameplay { base.CheckForResult(userTriggered, timeOffset); if (timeOffset >= 0) - ApplyResult(r => r.Type = r.Judgement.MaxResult); + ApplyResult(static (r, _) => r.Type = r.Judgement.MaxResult); } } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 9acd7b3c0f..bffe174be1 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -687,14 +687,12 @@ namespace osu.Game.Rulesets.Objects.Drawables /// the of the . /// /// The callback that applies changes to the . - /// The state passed to the callback. - /// The type of the state information that is passed to the callback method. - protected void ApplyResult(Action application, TState state) + protected void ApplyResult(Action application) { if (Result.HasResult) throw new InvalidOperationException("Cannot apply result on a hitobject that already has a result."); - application?.Invoke(Result, state); + application?.Invoke(Result, this); if (!Result.HasResult) throw new InvalidOperationException($"{GetType().ReadableName()} applied a {nameof(JudgementResult)} but did not update {nameof(JudgementResult.Type)}."); @@ -716,13 +714,6 @@ namespace osu.Game.Rulesets.Objects.Drawables OnNewResult?.Invoke(this, Result); } - /// - /// Applies the of this , notifying responders such as - /// the of the . - /// - /// The callback that applies changes to the . - protected void ApplyResult(Action application) => ApplyResult((r, _) => application?.Invoke(r), null); - /// /// Processes this , checking if a scoring result has occurred. /// From 682dab5d8327e24b38914f0599d8822eddf05e93 Mon Sep 17 00:00:00 2001 From: Chandler Stowell Date: Thu, 25 Jan 2024 11:30:52 -0500 Subject: [PATCH 2727/4092] check if parent was hit in taiko's `DrawableDrumRoll.CheckForResult` --- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 2e40875af1..f68198b967 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -194,7 +194,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables ApplyResult(static (r, hitObject) => { - r.Type = hitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult; + var drumRoll = (DrawableDrumRoll)hitObject; + r.Type = drumRoll.ParentHitObject!.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult; }); } From 542f571deecbcc1a1a86cfd960687e5bf1dd546e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 26 Jan 2024 01:03:22 +0300 Subject: [PATCH 2728/4092] Remove LINQ cast in HUDOverlay --- osu.Game/Screens/Play/HUDOverlay.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index b5482f2a5b..4fc37b8bb5 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; -using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -259,14 +258,14 @@ namespace osu.Game.Screens.Play Vector2? highestBottomScreenSpace = null; - // LINQ cast can be removed when IDrawable interface includes Anchor / RelativeSizeAxes. - foreach (var element in mainComponents.Components.Cast()) - processDrawable(element); + // cast can be removed when IDrawable interface includes Anchor / RelativeSizeAxes. + foreach (var element in mainComponents.Components) + processDrawable(element as Drawable); if (rulesetComponents != null) { - foreach (var element in rulesetComponents.Components.Cast()) - processDrawable(element); + foreach (var element in rulesetComponents.Components) + processDrawable(element as Drawable); } if (lowestTopScreenSpaceRight.HasValue) From d2af05b30e93f729ba40326bdaf51867817f65f2 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 26 Jan 2024 04:09:00 +0300 Subject: [PATCH 2729/4092] Remove useless bindable from ArgonSongProgressBar --- .../Screens/Play/HUD/ArgonSongProgressBar.cs | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs index beaee0e9ee..40311da646 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs @@ -3,7 +3,6 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -52,23 +51,14 @@ namespace osu.Game.Screens.Play.HUD set => CurrentNumber.Value = value; } - public double TrackTime - { - private get => currentTrackTime.Value; - set => currentTrackTime.Value = value; - } + public double TrackTime { private get; set; } private double length => EndTime - StartTime; - private readonly BindableNumber currentTrackTime; - public bool Interactive { get; set; } public ArgonSongProgressBar(float barHeight) { - currentTrackTime = new BindableDouble(); - setupAlternateValue(); - StartTime = 0; EndTime = 1; @@ -107,13 +97,6 @@ namespace osu.Game.Screens.Play.HUD }; } - private void setupAlternateValue() - { - CurrentNumber.MaxValueChanged += v => currentTrackTime.MaxValue = v; - CurrentNumber.MinValueChanged += v => currentTrackTime.MinValue = v; - CurrentNumber.PrecisionChanged += v => currentTrackTime.Precision = v; - } - private float normalizedReference { get From 9b5b313193d61c603c6e42773a8fc6ea7e05fb94 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 26 Jan 2024 04:49:23 +0300 Subject: [PATCH 2730/4092] Move common logic into own SongProgressBar class --- .../Screens/Play/HUD/ArgonSongProgressBar.cs | 46 +----------- .../Play/HUD/DefaultSongProgressBar.cs | 64 ++--------------- osu.Game/Screens/Play/HUD/SongProgressBar.cs | 70 +++++++++++++++++++ 3 files changed, 76 insertions(+), 104 deletions(-) create mode 100644 osu.Game/Screens/Play/HUD/SongProgressBar.cs diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs index 40311da646..5fe3b97f15 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs @@ -7,19 +7,15 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; -using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Graphics; using osuTK; namespace osu.Game.Screens.Play.HUD { - public partial class ArgonSongProgressBar : SliderBar + public partial class ArgonSongProgressBar : SongProgressBar { - public Action? OnSeek { get; set; } - // Parent will handle restricting the area of valid input. public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; @@ -33,35 +29,12 @@ namespace osu.Game.Screens.Play.HUD private readonly ColourInfo mainColour; private ColourInfo catchUpColour; - public double StartTime - { - private get => CurrentNumber.MinValue; - set => CurrentNumber.MinValue = value; - } - - public double EndTime - { - private get => CurrentNumber.MaxValue; - set => CurrentNumber.MaxValue = value; - } - - public double CurrentTime - { - private get => CurrentNumber.Value; - set => CurrentNumber.Value = value; - } - public double TrackTime { private get; set; } private double length => EndTime - StartTime; - public bool Interactive { get; set; } - public ArgonSongProgressBar(float barHeight) { - StartTime = 0; - EndTime = 1; - RelativeSizeAxes = Axes.X; Height = this.barHeight = barHeight; @@ -136,11 +109,6 @@ namespace osu.Game.Screens.Play.HUD base.OnHoverLost(e); } - protected override void UpdateValue(float value) - { - // Handled in Update - } - protected override void Update() { base.Update(); @@ -167,18 +135,6 @@ namespace osu.Game.Screens.Play.HUD catchupBar.Alpha = Math.Max(1, catchupBar.Length); } - private ScheduledDelegate? scheduledSeek; - - protected override void OnUserChange(double value) - { - scheduledSeek?.Cancel(); - scheduledSeek = Schedule(() => - { - if (Interactive) - OnSeek?.Invoke(value); - }); - } - private partial class RoundedBar : Container { private readonly Box fill; diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgressBar.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgressBar.cs index 0e16067dcc..4079351baf 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgressBar.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgressBar.cs @@ -7,67 +7,23 @@ using osuTK.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Utils; -using osu.Framework.Threading; namespace osu.Game.Screens.Play.HUD { - public partial class DefaultSongProgressBar : SliderBar + public partial class DefaultSongProgressBar : SongProgressBar { - /// - /// Action which is invoked when a seek is requested, with the proposed millisecond value for the seek operation. - /// - public Action? OnSeek { get; set; } - - /// - /// Whether the progress bar should allow interaction, ie. to perform seek operations. - /// - public bool Interactive - { - get => showHandle; - set - { - if (value == showHandle) - return; - - showHandle = value; - - handleBase.FadeTo(showHandle ? 1 : 0, 200); - } - } - public Color4 FillColour { set => fill.Colour = value; } - public double StartTime - { - set => CurrentNumber.MinValue = value; - } - - public double EndTime - { - set => CurrentNumber.MaxValue = value; - } - - public double CurrentTime - { - set => CurrentNumber.Value = value; - } - private readonly Box fill; private readonly Container handleBase; private readonly Container handleContainer; - private bool showHandle; - public DefaultSongProgressBar(float barHeight, float handleBarHeight, Vector2 handleSize) { - CurrentNumber.MinValue = 0; - CurrentNumber.MaxValue = 1; - RelativeSizeAxes = Axes.X; Height = barHeight + handleBarHeight + handleSize.Y; @@ -130,9 +86,11 @@ namespace osu.Game.Screens.Play.HUD }; } - protected override void UpdateValue(float value) + protected override void LoadComplete() { - // handled in update + base.LoadComplete(); + + InteractiveBindable.BindValueChanged(i => handleBase.FadeTo(i.NewValue ? 1 : 0, 200), true); } protected override void Update() @@ -145,17 +103,5 @@ namespace osu.Game.Screens.Play.HUD fill.Width = newX; handleBase.X = newX; } - - private ScheduledDelegate? scheduledSeek; - - protected override void OnUserChange(double value) - { - scheduledSeek?.Cancel(); - scheduledSeek = Schedule(() => - { - if (showHandle) - OnSeek?.Invoke(value); - }); - } } } diff --git a/osu.Game/Screens/Play/HUD/SongProgressBar.cs b/osu.Game/Screens/Play/HUD/SongProgressBar.cs new file mode 100644 index 0000000000..db9c8901b4 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/SongProgressBar.cs @@ -0,0 +1,70 @@ +// 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.Bindables; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Threading; + +namespace osu.Game.Screens.Play.HUD +{ + public abstract partial class SongProgressBar : SliderBar + { + /// + /// Action which is invoked when a seek is requested, with the proposed millisecond value for the seek operation. + /// + public Action? OnSeek { get; set; } + + /// + /// Whether the progress bar should allow interaction, ie. to perform seek operations. + /// + public bool Interactive + { + get => InteractiveBindable.Value; + set => InteractiveBindable.Value = value; + } + + protected readonly BindableBool InteractiveBindable = new BindableBool(); + + public double StartTime + { + get => CurrentNumber.MinValue; + set => CurrentNumber.MinValue = value; + } + + public double EndTime + { + get => CurrentNumber.MaxValue; + set => CurrentNumber.MaxValue = value; + } + + public double CurrentTime + { + get => CurrentNumber.Value; + set => CurrentNumber.Value = value; + } + + protected SongProgressBar() + { + StartTime = 0; + EndTime = 1; + } + + protected override void UpdateValue(float value) + { + // handled in update + } + + private ScheduledDelegate? scheduledSeek; + + protected override void OnUserChange(double value) + { + scheduledSeek?.Cancel(); + scheduledSeek = Schedule(() => + { + if (Interactive) + OnSeek?.Invoke(value); + }); + } + } +} From 84a0291c85550ab70db9a9aea0a69b9e8edfd428 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jan 2024 12:32:18 +0900 Subject: [PATCH 2731/4092] Update icon in nuspec file --- osu.Desktop/osu.nuspec | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Desktop/osu.nuspec b/osu.Desktop/osu.nuspec index 3dadc4e002..3b7d6cbe79 100644 --- a/osu.Desktop/osu.nuspec +++ b/osu.Desktop/osu.nuspec @@ -7,7 +7,8 @@ ppy Pty Ltd Dean Herbert https://osu.ppy.sh/ - https://puu.sh/tYyXZ/9a01a5d1b0.ico + https://github.com/ppy/osu/blob/master/assets/lazer-nuget.png?raw=true + icon.png false A free-to-win rhythm game. Rhythm is just a *click* away! testing From 6e3eb674f61d6865d5c39cc25a4241921796f989 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jan 2024 12:55:36 +0900 Subject: [PATCH 2732/4092] Move cast to local function and make direct cast --- osu.Game/Screens/Play/HUDOverlay.cs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 4fc37b8bb5..32ebb82f15 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -258,14 +258,13 @@ namespace osu.Game.Screens.Play Vector2? highestBottomScreenSpace = null; - // cast can be removed when IDrawable interface includes Anchor / RelativeSizeAxes. foreach (var element in mainComponents.Components) - processDrawable(element as Drawable); + processDrawable(element); if (rulesetComponents != null) { foreach (var element in rulesetComponents.Components) - processDrawable(element as Drawable); + processDrawable(element); } if (lowestTopScreenSpaceRight.HasValue) @@ -283,33 +282,36 @@ namespace osu.Game.Screens.Play else bottomRightElements.Y = 0; - void processDrawable(Drawable element) + void processDrawable(ISerialisableDrawable element) { + // Cast can be removed when IDrawable interface includes Anchor / RelativeSizeAxes. + Drawable drawable = (Drawable)element; + // for now align some top components with the bottom-edge of the lowest top-anchored hud element. - if (element.Anchor.HasFlagFast(Anchor.y0)) + if (drawable.Anchor.HasFlagFast(Anchor.y0)) { // health bars are excluded for the sake of hacky legacy skins which extend the health bar to take up the full screen area. if (element is LegacyHealthDisplay) return; - float bottom = element.ScreenSpaceDrawQuad.BottomRight.Y; + float bottom = drawable.ScreenSpaceDrawQuad.BottomRight.Y; - bool isRelativeX = element.RelativeSizeAxes == Axes.X; + bool isRelativeX = drawable.RelativeSizeAxes == Axes.X; - if (element.Anchor.HasFlagFast(Anchor.TopRight) || isRelativeX) + if (drawable.Anchor.HasFlagFast(Anchor.TopRight) || isRelativeX) { if (lowestTopScreenSpaceRight == null || bottom > lowestTopScreenSpaceRight.Value) lowestTopScreenSpaceRight = bottom; } - if (element.Anchor.HasFlagFast(Anchor.TopLeft) || isRelativeX) + if (drawable.Anchor.HasFlagFast(Anchor.TopLeft) || isRelativeX) { if (lowestTopScreenSpaceLeft == null || bottom > lowestTopScreenSpaceLeft.Value) lowestTopScreenSpaceLeft = bottom; } } // and align bottom-right components with the top-edge of the highest bottom-anchored hud element. - else if (element.Anchor.HasFlagFast(Anchor.BottomRight) || (element.Anchor.HasFlagFast(Anchor.y2) && element.RelativeSizeAxes == Axes.X)) + else if (drawable.Anchor.HasFlagFast(Anchor.BottomRight) || (drawable.Anchor.HasFlagFast(Anchor.y2) && drawable.RelativeSizeAxes == Axes.X)) { var topLeft = element.ScreenSpaceDrawQuad.TopLeft; if (highestBottomScreenSpace == null || topLeft.Y < highestBottomScreenSpace.Value.Y) From a77db5d837bb41f57b533c7dff1b893eaa5148a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jan 2024 13:36:50 +0900 Subject: [PATCH 2733/4092] Add failing test coverage of editor metadata not saving --- .../Editing/TestSceneMetadataSection.cs | 105 ++++++++++++++++-- 1 file changed, 94 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs b/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs index a9f8e19e30..f767d9f7a3 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs @@ -3,17 +3,22 @@ #nullable disable +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Setup; +using osuTK.Input; namespace osu.Game.Tests.Visual.Editing { - public partial class TestSceneMetadataSection : OsuTestScene + public partial class TestSceneMetadataSection : OsuManualInputManagerTestScene { [Cached] private EditorBeatmap editorBeatmap = new EditorBeatmap(new Beatmap @@ -26,6 +31,81 @@ namespace osu.Game.Tests.Visual.Editing private TestMetadataSection metadataSection; + [Test] + public void TestUpdateViaTextBoxOnFocusLoss() + { + AddStep("set metadata", () => + { + editorBeatmap.Metadata.Artist = "Example Artist"; + editorBeatmap.Metadata.ArtistUnicode = string.Empty; + }); + + createSection(); + + TextBox textbox = null!; + + AddStep("focus first textbox", () => + { + textbox = metadataSection.ChildrenOfType().First(); + InputManager.MoveMouseTo(textbox); + InputManager.Click(MouseButton.Left); + }); + + AddStep("simulate changing textbox", () => + { + // Can't simulate text input but this should work. + InputManager.Keys(PlatformAction.SelectAll); + InputManager.Keys(PlatformAction.Copy); + InputManager.Keys(PlatformAction.Paste); + InputManager.Keys(PlatformAction.Paste); + }); + + assertArtistMetadata("Example Artist"); + + // It's important values are committed immediately on focus loss so the editor exit sequence detects them. + AddAssert("value immediately changed on focus loss", () => + { + InputManager.TriggerFocusContention(metadataSection); + return editorBeatmap.Metadata.Artist; + }, () => Is.EqualTo("Example ArtistExample Artist")); + } + + [Test] + public void TestUpdateViaTextBoxOnCommit() + { + AddStep("set metadata", () => + { + editorBeatmap.Metadata.Artist = "Example Artist"; + editorBeatmap.Metadata.ArtistUnicode = string.Empty; + }); + + createSection(); + + TextBox textbox = null!; + + AddStep("focus first textbox", () => + { + textbox = metadataSection.ChildrenOfType().First(); + InputManager.MoveMouseTo(textbox); + InputManager.Click(MouseButton.Left); + }); + + AddStep("simulate changing textbox", () => + { + // Can't simulate text input but this should work. + InputManager.Keys(PlatformAction.SelectAll); + InputManager.Keys(PlatformAction.Copy); + InputManager.Keys(PlatformAction.Paste); + InputManager.Keys(PlatformAction.Paste); + }); + + assertArtistMetadata("Example Artist"); + + AddStep("commit", () => InputManager.Key(Key.Enter)); + + assertArtistMetadata("Example ArtistExample Artist"); + } + [Test] public void TestMinimalMetadata() { @@ -40,7 +120,7 @@ namespace osu.Game.Tests.Visual.Editing createSection(); - assertArtist("Example Artist"); + assertArtistTextBox("Example Artist"); assertRomanisedArtist("Example Artist", false); assertTitle("Example Title"); @@ -61,7 +141,7 @@ namespace osu.Game.Tests.Visual.Editing createSection(); - assertArtist("*なみりん"); + assertArtistTextBox("*なみりん"); assertRomanisedArtist(string.Empty, true); assertTitle("コイシテイク・プラネット"); @@ -82,7 +162,7 @@ namespace osu.Game.Tests.Visual.Editing createSection(); - assertArtist("*なみりん"); + assertArtistTextBox("*なみりん"); assertRomanisedArtist("*namirin", true); assertTitle("コイシテイク・プラネット"); @@ -104,11 +184,11 @@ namespace osu.Game.Tests.Visual.Editing createSection(); AddStep("set romanised artist name", () => metadataSection.ArtistTextBox.Current.Value = "*namirin"); - assertArtist("*namirin"); + assertArtistTextBox("*namirin"); assertRomanisedArtist("*namirin", false); AddStep("set native artist name", () => metadataSection.ArtistTextBox.Current.Value = "*なみりん"); - assertArtist("*なみりん"); + assertArtistTextBox("*なみりん"); assertRomanisedArtist("*namirin", true); AddStep("set romanised title", () => metadataSection.TitleTextBox.Current.Value = "Hitokoto no kyori"); @@ -123,21 +203,24 @@ namespace osu.Game.Tests.Visual.Editing private void createSection() => AddStep("create metadata section", () => Child = metadataSection = new TestMetadataSection()); - private void assertArtist(string expected) - => AddAssert($"artist is {expected}", () => metadataSection.ArtistTextBox.Current.Value == expected); + private void assertArtistMetadata(string expected) + => AddAssert($"artist metadata is {expected}", () => editorBeatmap.Metadata.Artist, () => Is.EqualTo(expected)); + + private void assertArtistTextBox(string expected) + => AddAssert($"artist textbox is {expected}", () => metadataSection.ArtistTextBox.Current.Value, () => Is.EqualTo(expected)); private void assertRomanisedArtist(string expected, bool editable) { - AddAssert($"romanised artist is {expected}", () => metadataSection.RomanisedArtistTextBox.Current.Value == expected); + AddAssert($"romanised artist is {expected}", () => metadataSection.RomanisedArtistTextBox.Current.Value, () => Is.EqualTo(expected)); AddAssert($"romanised artist is {(editable ? "" : "not ")}editable", () => metadataSection.RomanisedArtistTextBox.ReadOnly == !editable); } private void assertTitle(string expected) - => AddAssert($"title is {expected}", () => metadataSection.TitleTextBox.Current.Value == expected); + => AddAssert($"title is {expected}", () => metadataSection.TitleTextBox.Current.Value, () => Is.EqualTo(expected)); private void assertRomanisedTitle(string expected, bool editable) { - AddAssert($"romanised title is {expected}", () => metadataSection.RomanisedTitleTextBox.Current.Value == expected); + AddAssert($"romanised title is {expected}", () => metadataSection.RomanisedTitleTextBox.Current.Value, () => Is.EqualTo(expected)); AddAssert($"romanised title is {(editable ? "" : "not ")}editable", () => metadataSection.RomanisedTitleTextBox.ReadOnly == !editable); } From e54502eef1bd0d99e3a3b5c29476b0addc7a5c13 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jan 2024 13:59:28 +0900 Subject: [PATCH 2734/4092] Add full editor save test coverage --- .../TestSceneBeatmapEditorNavigation.cs | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs index 9930349b1b..370c40222e 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs @@ -7,6 +7,7 @@ using osu.Framework.Extensions; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -17,6 +18,7 @@ using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.GameplayTest; +using osu.Game.Screens.Edit.Setup; using osu.Game.Screens.Menu; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Filter; @@ -27,6 +29,59 @@ namespace osu.Game.Tests.Visual.Navigation { public partial class TestSceneBeatmapEditorNavigation : OsuGameTestScene { + [Test] + public void TestChangeMetadataExitWhileTextboxFocusedPromptsSave() + { + BeatmapSetInfo beatmapSet = null!; + + AddStep("import test beatmap", () => Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely()); + AddStep("retrieve beatmap", () => beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach()); + + AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet)); + AddUntilStep("wait for song select", + () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) + && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect + && songSelect.IsLoaded); + AddStep("switch ruleset", () => Game.Ruleset.Value = new ManiaRuleset().RulesetInfo); + + AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); + AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse); + + AddStep("change to song setup", () => InputManager.Key(Key.F4)); + + TextBox textbox = null!; + + AddUntilStep("wait for metadata section", () => + { + var t = Game.ChildrenOfType().SingleOrDefault().ChildrenOfType().FirstOrDefault(); + + if (t == null) + return false; + + textbox = t; + return true; + }); + + AddStep("focus textbox", () => + { + InputManager.MoveMouseTo(textbox); + InputManager.Click(MouseButton.Left); + }); + + AddStep("simulate changing textbox", () => + { + // Can't simulate text input but this should work. + InputManager.Keys(PlatformAction.SelectAll); + InputManager.Keys(PlatformAction.Copy); + InputManager.Keys(PlatformAction.Paste); + InputManager.Keys(PlatformAction.Paste); + }); + + AddStep("exit", () => Game.ChildrenOfType().Single().Exit()); + + AddAssert("save dialog displayed", () => Game.ChildrenOfType().Single().CurrentDialog is PromptForSaveDialog); + } + [Test] public void TestEditorGameplayTestAlwaysUsesOriginalRuleset() { From 45f2980dc099ba8c421395dd4258cdf3df4a2e55 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jan 2024 13:23:02 +0900 Subject: [PATCH 2735/4092] Fix potential editor data loss if exiting while a textbox is focused --- osu.Game/Screens/Edit/Editor.cs | 6 ++++++ osu.Game/Screens/Edit/Setup/MetadataSection.cs | 10 +++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index c1f6c02301..bc376f6165 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -719,6 +719,12 @@ namespace osu.Game.Screens.Edit public override bool OnExiting(ScreenExitEvent e) { + // Before exiting, trigger a focus loss. + // + // This is important to ensure that if the user is still editing a textbox, it will commit + // (and potentially block the exit procedure for save). + GetContainingInputManager().TriggerFocusContention(this); + if (!ExitConfirmed) { // dialog overlay may not be available in visual tests. diff --git a/osu.Game/Screens/Edit/Setup/MetadataSection.cs b/osu.Game/Screens/Edit/Setup/MetadataSection.cs index 752f590308..b51c03b796 100644 --- a/osu.Game/Screens/Edit/Setup/MetadataSection.cs +++ b/osu.Game/Screens/Edit/Setup/MetadataSection.cs @@ -53,9 +53,6 @@ namespace osu.Game.Screens.Edit.Setup sourceTextBox = createTextBox(BeatmapsetsStrings.ShowInfoSource, metadata.Source), tagsTextBox = createTextBox(BeatmapsetsStrings.ShowInfoTags, metadata.Tags) }; - - foreach (var item in Children.OfType()) - item.OnCommit += onCommit; } private TTextBox createTextBox(LocalisableString label, string initialValue) @@ -77,6 +74,10 @@ namespace osu.Game.Screens.Edit.Setup ArtistTextBox.Current.BindValueChanged(artist => transferIfRomanised(artist.NewValue, RomanisedArtistTextBox)); TitleTextBox.Current.BindValueChanged(title => transferIfRomanised(title.NewValue, RomanisedTitleTextBox)); + + foreach (var item in Children.OfType()) + item.OnCommit += onCommit; + updateReadOnlyState(); } @@ -86,7 +87,6 @@ namespace osu.Game.Screens.Edit.Setup target.Current.Value = value; updateReadOnlyState(); - Scheduler.AddOnce(updateMetadata); } private void updateReadOnlyState() @@ -101,7 +101,7 @@ namespace osu.Game.Screens.Edit.Setup // for now, update on commit rather than making BeatmapMetadata bindables. // after switching database engines we can reconsider if switching to bindables is a good direction. - Scheduler.AddOnce(updateMetadata); + updateMetadata(); } private void updateMetadata() From 347e88f59772f0dc0045f46dc1c8e060ed8b51b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jan 2024 16:21:48 +0900 Subject: [PATCH 2736/4092] Add note about using `static` callback --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index bffe174be1..e30ce13f08 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -686,7 +686,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// Applies the of this , notifying responders such as /// the of the . /// - /// The callback that applies changes to the . + /// The callback that applies changes to the . Using a `static` delegate is recommended to avoid allocation overhead. protected void ApplyResult(Action application) { if (Result.HasResult) From 6cfd2813ede27126f002ace78e3def0e5f7b03f5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jan 2024 16:52:03 +0900 Subject: [PATCH 2737/4092] Fix incorrect cast --- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index f68198b967..e15298f3ca 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -194,7 +194,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables ApplyResult(static (r, hitObject) => { - var drumRoll = (DrawableDrumRoll)hitObject; + var drumRoll = (StrongNestedHit)hitObject; r.Type = drumRoll.ParentHitObject!.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult; }); } From b6fa50c312c6b47b37cc6037688c303e5c039b24 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jan 2024 17:48:36 +0900 Subject: [PATCH 2738/4092] Reduce allocation overheads in `SliderInputManager` --- .../Objects/Drawables/SliderInputManager.cs | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs index 95896c7c91..148cf79337 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs @@ -215,8 +215,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (headCircleHitAction == null) timeToAcceptAnyKeyAfter = null; - var actions = slider.OsuActionInputManager?.PressedActions; - // if the head circle was hit with a specific key, tracking should only occur while that key is pressed. if (headCircleHitAction != null && timeToAcceptAnyKeyAfter == null) { @@ -227,6 +225,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables timeToAcceptAnyKeyAfter = Time.Current; } + if (slider.OsuActionInputManager == null) + return; + + lastPressedActions.Clear(); + bool validTrackingAction = false; + + foreach (OsuAction action in slider.OsuActionInputManager.PressedActions) + { + if (isValidTrackingAction(action)) + validTrackingAction = true; + + lastPressedActions.Add(action); + } + Tracking = // even in an edge case where current time has exceeded the slider's time, we may not have finished judging. // we don't want to potentially update from Tracking=true to Tracking=false at this point. @@ -234,11 +246,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // in valid position range && isValidTrackingPosition // valid action - && (actions?.Any(isValidTrackingAction) ?? false); - - lastPressedActions.Clear(); - if (actions != null) - lastPressedActions.AddRange(actions); + && validTrackingAction; } private OsuAction? getInitialHitAction() => slider.HeadCircle?.HitAction; From 68d5e8affc69c6d219c85e220913a27749551035 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jan 2024 18:50:40 +0900 Subject: [PATCH 2739/4092] Use a better constant for playfield positioning --- .../UI/TaikoPlayfieldAdjustmentContainer.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs index 9a5fc90a05..c10e505f50 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs @@ -28,6 +28,8 @@ namespace osu.Game.Rulesets.Taiko.UI base.Update(); const float base_relative_height = TaikoPlayfield.BASE_HEIGHT / 768; + // Matches stable, see https://github.com/peppy/osu-stable-reference/blob/7519cafd1823f1879c0d9c991ba0e5c7fd3bfa02/osu!/GameModes/Play/Rulesets/Taiko/RulesetTaiko.cs#L514 + const float base_position = 135f / 480f; float relativeHeight = base_relative_height; @@ -49,14 +51,7 @@ namespace osu.Game.Rulesets.Taiko.UI // Limit the maximum relative height of the playfield to one-third of available area to avoid it masking out on extreme resolutions. relativeHeight = Math.Min(relativeHeight, 1f / 3f); - // Position the taiko playfield exactly one playfield from the top of the screen, if there is enough space for it. - // Note that the relative height cannot exceed one-third - if that limit is hit, the playfield will be exactly centered. - float playfieldPosition = relativeHeight; - - // arbitrary offset to make playfield position match stable. - playfieldPosition += 0.022f; - - Y = playfieldPosition; + Y = base_position; Scale = new Vector2(Math.Max((Parent!.ChildSize.Y / 768f) * (relativeHeight / base_relative_height), 1f)); Width = 1 / Scale.X; From 04cae874b0e0a6f98ba40aa048c6b6f08120c667 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 26 Jan 2024 10:51:52 +0100 Subject: [PATCH 2740/4092] Handle forced logouts due to password change too --- osu.Game/Online/API/APIAccess.cs | 45 ++++++++++++++----------- osu.Game/Online/OnlineStatusNotifier.cs | 22 ++++++++++++ 2 files changed, 48 insertions(+), 19 deletions(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index ebebdbbfc2..8b369d0f3f 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -86,7 +86,7 @@ namespace osu.Game.Online.API APIEndpointUrl = endpointConfiguration.APIEndpointUrl; WebsiteRootUrl = endpointConfiguration.WebsiteRootUrl; - NotificationsClient = new WebSocketNotificationsClientConnector(this); + NotificationsClient = setUpNotificationsClient(); authentication = new OAuth(endpointConfiguration.APIClientID, endpointConfiguration.APIClientSecret, APIEndpointUrl); log = Logger.GetLogger(LoggingTarget.Network); @@ -119,6 +119,30 @@ namespace osu.Game.Online.API thread.Start(); } + private WebSocketNotificationsClientConnector setUpNotificationsClient() + { + var connector = new WebSocketNotificationsClientConnector(this); + + connector.MessageReceived += msg => + { + switch (msg.Event) + { + case @"verified": + if (state.Value == APIState.RequiresSecondFactorAuth) + state.Value = APIState.Online; + break; + + case @"logout": + if (state.Value == APIState.Online) + Logout(); + + break; + } + }; + + return connector; + } + private void onTokenChanged(ValueChangedEvent e) => config.SetValue(OsuSetting.Token, config.Get(OsuSetting.SavePassword) ? authentication.TokenString : string.Empty); internal new void Schedule(Action action) => base.Schedule(action); @@ -270,10 +294,7 @@ namespace osu.Game.Online.API setLocalUser(me); - if (me.SessionVerified) - state.Value = APIState.Online; - else - setUpSecondFactorAuthentication(); + state.Value = me.SessionVerified ? APIState.Online : APIState.RequiresSecondFactorAuth; failureCount = 0; }; @@ -356,20 +377,6 @@ namespace osu.Game.Online.API this.password = password; } - private void setUpSecondFactorAuthentication() - { - if (state.Value == APIState.RequiresSecondFactorAuth) - return; - - state.Value = APIState.RequiresSecondFactorAuth; - - NotificationsClient.MessageReceived += msg => - { - if (msg.Event == @"verified") - state.Value = APIState.Online; - }; - } - public void AuthenticateSecondFactor(string code) { Debug.Assert(State.Value == APIState.RequiresSecondFactorAuth); diff --git a/osu.Game/Online/OnlineStatusNotifier.cs b/osu.Game/Online/OnlineStatusNotifier.cs index c36e4ab894..dda430ce6f 100644 --- a/osu.Game/Online/OnlineStatusNotifier.cs +++ b/osu.Game/Online/OnlineStatusNotifier.cs @@ -11,6 +11,7 @@ using osu.Framework.Screens; using osu.Game.Online.API; using osu.Game.Online.Metadata; using osu.Game.Online.Multiplayer; +using osu.Game.Online.Notifications.WebSocket; using osu.Game.Online.Spectator; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; @@ -25,6 +26,8 @@ namespace osu.Game.Online { private readonly Func getCurrentScreen; + private INotificationsClient notificationsClient = null!; + [Resolved] private MultiplayerClient multiplayerClient { get; set; } = null!; @@ -55,9 +58,11 @@ namespace osu.Game.Online private void load(IAPIProvider api) { apiState = api.State.GetBoundCopy(); + notificationsClient = api.NotificationsClient; multiplayerState = multiplayerClient.IsConnected.GetBoundCopy(); spectatorState = spectatorClient.IsConnected.GetBoundCopy(); + notificationsClient.MessageReceived += notifyAboutForcedDisconnection; multiplayerClient.Disconnecting += notifyAboutForcedDisconnection; spectatorClient.Disconnecting += notifyAboutForcedDisconnection; metadataClient.Disconnecting += notifyAboutForcedDisconnection; @@ -127,10 +132,27 @@ namespace osu.Game.Online }); } + private void notifyAboutForcedDisconnection(SocketMessage obj) + { + if (obj.Event != @"logout") return; + + if (userNotified) return; + + userNotified = true; + notificationOverlay?.Post(new SimpleErrorNotification + { + Icon = FontAwesome.Solid.ExclamationCircle, + Text = "You have been logged out due to a change to your account. Please log in again." + }); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); + if (notificationsClient.IsNotNull()) + notificationsClient.MessageReceived += notifyAboutForcedDisconnection; + if (spectatorClient.IsNotNull()) spectatorClient.Disconnecting -= notifyAboutForcedDisconnection; From a2e69d37e8444a75763e9e974bf32e4eb2ce1dca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 26 Jan 2024 11:17:32 +0100 Subject: [PATCH 2741/4092] Add basic testing of failure flow --- .../Visual/Menus/TestSceneLoginOverlay.cs | 54 +++++++++++++++++++ osu.Game/Online/API/DummyAPIAccess.cs | 22 +++++++- 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs b/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs index 0c0edca995..5fc075ed99 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; +using System.Net; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -9,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; +using osu.Game.Online.API.Requests; using osu.Game.Overlays; using osu.Game.Overlays.Login; using osu.Game.Users.Drawables; @@ -19,6 +21,8 @@ namespace osu.Game.Tests.Visual.Menus [TestFixture] public partial class TestSceneLoginOverlay : OsuManualInputManagerTestScene { + private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; + private LoginOverlay loginOverlay = null!; [BackgroundDependencyLoader] @@ -49,13 +53,63 @@ namespace osu.Game.Tests.Visual.Menus assertAPIState(APIState.RequiresSecondFactorAuth); AddUntilStep("wait for second factor auth form", () => loginOverlay.ChildrenOfType().SingleOrDefault(), () => Is.Not.Null); + AddStep("set up verification handling", () => dummyAPI.HandleRequest = req => + { + switch (req) + { + case VerifySessionRequest verifySessionRequest: + if (verifySessionRequest.VerificationKey == "88800088") + verifySessionRequest.TriggerSuccess(); + else + verifySessionRequest.TriggerFailure(new WebException()); + return true; + } + + return false; + }); AddStep("enter code", () => loginOverlay.ChildrenOfType().First().Text = "88800088"); assertAPIState(APIState.Online); + AddStep("clear handler", () => dummyAPI.HandleRequest = null); } private void assertAPIState(APIState expected) => AddUntilStep($"login state is {expected}", () => API.State.Value, () => Is.EqualTo(expected)); + [Test] + public void TestVerificationFailure() + { + bool verificationHandled = false; + AddStep("reset flag", () => verificationHandled = false); + AddStep("logout", () => API.Logout()); + assertAPIState(APIState.Offline); + + AddStep("enter password", () => loginOverlay.ChildrenOfType().First().Text = "password"); + AddStep("submit", () => loginOverlay.ChildrenOfType().First(b => b.Text.ToString() == "Sign in").TriggerClick()); + + assertAPIState(APIState.RequiresSecondFactorAuth); + AddUntilStep("wait for second factor auth form", () => loginOverlay.ChildrenOfType().SingleOrDefault(), () => Is.Not.Null); + + AddStep("set up verification handling", () => dummyAPI.HandleRequest = req => + { + switch (req) + { + case VerifySessionRequest verifySessionRequest: + if (verifySessionRequest.VerificationKey == "88800088") + verifySessionRequest.TriggerSuccess(); + else + verifySessionRequest.TriggerFailure(new WebException()); + verificationHandled = true; + return true; + } + + return false; + }); + AddStep("enter code", () => loginOverlay.ChildrenOfType().First().Text = "abcdefgh"); + AddUntilStep("wait for verification handled", () => verificationHandled); + assertAPIState(APIState.RequiresSecondFactorAuth); + AddStep("clear handler", () => dummyAPI.HandleRequest = null); + } + [Test] public void TestLoginFailure() { diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 08b1733aed..435c100c9a 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Localisation; +using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; using osu.Game.Online.Notifications.WebSocket; @@ -133,7 +134,26 @@ namespace osu.Game.Online.API } } - public void AuthenticateSecondFactor(string code) => onSuccessfulLogin(); + public void AuthenticateSecondFactor(string code) + { + var request = new VerifySessionRequest(code); + request.Failure += e => + { + state.Value = APIState.RequiresSecondFactorAuth; + LastLoginError = e; + }; + + state.Value = APIState.Connecting; + LastLoginError = null; + + // if no handler installed / handler can't handle verification, just assume that the server would verify for simplicity. + if (HandleRequest?.Invoke(request) != true) + onSuccessfulLogin(); + + // if a handler did handle this, make sure the verification actually passed. + if (request.CompletionState == APIRequestCompletionState.Completed) + onSuccessfulLogin(); + } private void onSuccessfulLogin() { From 243c2bc9b420b264e5a48b34fb4d7f3bd78d155d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 26 Jan 2024 11:21:23 +0100 Subject: [PATCH 2742/4092] Make sure the polling connector actually auto-starts itself --- osu.Game/Tests/PollingChatClientConnector.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Tests/PollingChatClientConnector.cs b/osu.Game/Tests/PollingChatClientConnector.cs index 1cab24dae6..f1b0d9dd7d 100644 --- a/osu.Game/Tests/PollingChatClientConnector.cs +++ b/osu.Game/Tests/PollingChatClientConnector.cs @@ -32,6 +32,7 @@ namespace osu.Game.Tests public PollingChatClientConnector(IAPIProvider api) : base(api) { + Start(); } protected sealed override Task BuildConnectionAsync(CancellationToken cancellationToken) From 9dc2a1d6f8892d7a548df60b1a21f2c6eb66dd7a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 27 Jan 2024 01:02:16 +0900 Subject: [PATCH 2743/4092] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 1b1abe3971..f2623ac2a0 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + From f54e1418ae8308202f963faabd5acf545ef7ba1c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 27 Jan 2024 01:25:48 +0900 Subject: [PATCH 2744/4092] Update to v4 --- assets/lazer-nuget.png | Bin 12770 -> 11930 bytes assets/lazer.png | Bin 326320 -> 328536 bytes osu.Desktop/lazer.ico | Bin 67263 -> 67391 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/assets/lazer-nuget.png b/assets/lazer-nuget.png index 98a152a5a05b40b857780b2682be6a126151ba72..fed2f45149d74aee14421e74fa75dc117469d01a 100644 GIT binary patch literal 11930 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 b!4O3$rBAdmT`k@`=Kuv673nHTP~iUodp;H% literal 12770 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 diff --git a/assets/lazer.png b/assets/lazer.png index 484bc0a449bb2ee813fad5180a1f74d368d75e2e..2ee44225bf02b4bcd19b0f6590f0695027f3f1ee 100644 GIT binary patch literal 328536 zcmc$_by(B=_dmXk(H#;}qXeY8+2{tb=#mnVPKj-lppuGUPy&i52$GT;0s_(@-I5Zc z2W(?seBbx|`TzIV?_AgR+OBJ_v-3RXc^>ESJZF1y!`y(D>M9ig0H8H8)V&D+fG=Ny z0Tg7H7X-Ih+~tMR-_Rxq0ALmR`v4l<6x;)ll294xYTXW>-foMz`~(15KqzG^1^s>b z|D$*;%m*@)-4j8w{`@`(#<{GQiVKZz7~g$~AI=l7IZS$OwAf4X|Jgp5KsA+xI-j>e zYk0ewD;fi@#yQrsD{>>Ed7+oO5c4;|p2KrN69N-k%1s9AEejZ2Ldzjf;6<;?->>53 zqxw?{a9sZopvyKFLU1HJZCS`TeBESF!C1l3)qtIvz+e&Exr1^Vk!kB2TqwcIdlsLb z32(R8|Cz#AKU;Wr!;yMxpdzUmzO!|F{smqei7Gsw<9T%JVjeU5^f)3#AS~G)y1O;e z{-m!53#rg^+s`N9%ea^>72$Pk*zeXfj{C5@uu1ped;8UKa5+-sV^ev!W`5dJ_X~Eb z8A9V-LQ%iQAOHiqG14PX^}Szl1zpRxfJp5~~@~wv@I3;YcnaP7^~IEpOAt; zrt_O6l`|L`W{ek83hKS-fOUt39=^kBhjLV?O|isMtdl3cHVyR*&GH$@ZK}3s6T^(0 z&OHA-Cn>i6@ag+09#PZ+wDab`>#I*^-fxFaUE9*k_j_aV*?ecT&@a+HVE5x!cdz`x z)o+1~PDNF0x@r?BH*1>JS9<83_M66YML;d!rR{&^)XR-Z*`(B4>FWz(b@1kmc>%`&(ltO z>XE(LyYt_6I)%R&yl-`EbKns8t8a6@oXunZmV;Sh_O;uuZhIXFC#Yvl153Xu{M2YRbz^%!Ii(kdXcddCZ zdM|lje`(xw76li}HLvLUz5h#>|7%CkVgop?jU#VHdJFwKkoQ^+i2MOrT&7)U!qv*J z#hTRPehU5t>aLA|=b1C({26D~;&rcj+!U!2zK7P@&iTn-HF#J^+x|`7>@e?Wa_czHa4@cI@%;j( z#IWM_;EVbDaE0qBSo)IzVfioDseYT5j9>g>DTO4(U-h>nio4HF6f(&>1oOG^X%B5^ zOMj5mEp8H*Z@u#YZco0v)p~;#h+R0=e@jgNRV|Ry5+hOI?iK~T3w_=sVmRNdVnR>XBec3T_37 zRYXE2^$Ju?zHcc44*p{2U$J|=c&g@J={vr$o`;&ycdB2EB(ZVcHMytXnVFiF#``Ka zH`?FJYm;&wh`Gm%3w*GVXkC)DA-`2tTbJ?XG~V4hly=3%=J8AA^$T}}qz#%awegZ4 zujt=X^QHc_vN3qisoh9hd_!7YJ*u9?K^v2NXQ0*2fqkzN5PeMLuEvs}Ip3#j$gL=f z75NMBe??IvLia7|ai)^y0OowXc4s|d)2q}Ws^zo&?(XiqoV9FkC+84ODyOnAJ#hDB=pvr1dd&hSCs-hU<)axAqQKqU? z9v&XMyquigTbr9x+{*AuvsLDmE96wvBo~_1p>aiY)bZl~rXt!yk+4)u@P|1COvA?? zZte%GxI{WmulQjwm<6lyyBHt&tBH5--c9w#ol!Sei#K5f1{x^GF?e;hvvX)4;lq#j z82>x-qTaDdnrmT%)aD>bI`y6@D*8jl6q4xZ=*W@d=kK|w#E-Tls_UC~2^kp~b++Xv3~%x6mMksZKQQq! zYsEpk;#(l7K_CC=ibU^~^;tv|;cW%<=k%g0m+;6kno-3O(xq|z=nyN-lPCmoR%1RfI zG%Lp>;+cGgytQp%0^o+~JPRRpWSi9vDCj(on;5yYh9GF<_^fba>g?>SOW(i%+p9}* z8TwJG-~d>099hee?$bgppp*j_tp4A%E`~Cg{+aN(I9CrwAGy&Y6kqjoiujzkvxQ`X zG5fpQE{f+G_kJeb|IQf=-i|p&tFV1t6UrH4KlJ75lIjVF-Q3(f85kIttGY`v2U0s| zBw4}UnRrHZRlJXJXhAQT?_aQ(z1ry^z%^ZxlB|g1cRvcDCWc<9h4uIMYka4~88k@X zlo@ja2MBMj{o$OvQ3+HdyZAbuCr~)}Tu^1e8>?c|-kgQ~R0!({quNOSS%}oK{0J#P zrBPh@Pkp-j3(S?PsO+#%r`)NDr6~8T5_M_mO22>r%RV?;{wU&YQfuJAuTzBLl33d{ z4C}Y(+AOrpWCQKGdEl@t?~YJy*JGWtIPF zVvBrcW+v>9IXfa4c3VCGwo*bHU)&Tf?f#Eyq{P_IiuYlsA<{M(Q&wvD)v~3EyPvV* zQc_UKop5!ejR1NIv_dXuy}b6;k5_4h8`mSlIPH$_ko(WhhR(s}#&p>zL>@-F zn6lfA=`r&%IX)Ep@J9SW6yNJN?*)cCD%n|o|28oV*1aJwW65kJc;Dk`P*wM}U_CMx zk8&!p=z>}5th(&1$VfjwB5`!#m@CLagRtXI9k0#Xn!mC#KR?`LMI1fcixi1+4-P(R zk389aRHDjopMnltXbDo4h4HSGN^vRt zYC{jJO6?_~uE}T7{{2eUaB;WP|($2UH z=-OwpYs1OIGryhJV#poyVebYk?5aN|`m+`>qQ5TAh1230#owHBj`2M`rp>1#rTcui zHM?BNoi8~#F*SAO*tCRbu3I8LBkXFOc%ZpuHuAKv;*Fjo+!Py`KrBtuJAUvzJ<^ay zWGYPo{I7ZbO{Ju8>*FTh$ksiNg_eqxNeP7ODN#9mZ}HP5WLS*b?zf2WlWaF36AW}` zW6)aIvPRF7jo~7U<>6!act|!7DrIlMs72J|qD%Zsm(fu*>!Z-Egr=XwU-g9HO85i< z;q1J)bnojo1*7NDb<$Iu@kb=jQmu)KKenY_8kb)Zg`&v1keN1Xtu|?Hk!|rCxMts8 zExNzfA>ZB>&UczS=u#rUwJ?4cx4kt{HwJT5f;@74dMBO>_E@CI2W}cJ`^UA9I(u|)l6w52^VgZ6qo9|IBLNitC2y6%4hCNy z>enuhxYSioADx{z?#iJy{k*(vyr@rGP&Z2-nvK@3KrmfE$;tm zt?#~x7FLD)QVqabB!M9N6;4nsS)hG6iqQEb`&(muNyz9xj*-LdEx@@nC@5FPqy8;!mTQAv%TkCbsg&$URDYLDrtoH1Ex0_eDGUm;JypHcyLvF8yK)ET@IrhOxcDQ< zu#-LL+YJ%*UzgQZ@l{3p*e7W{NKTP!0*RN7T0lq1GC@Hy&mqvSgJFi{6@0^CqZDbmae^9{CP3Z^*hCV{@U%<2e`9~aI zD1qmX#(B=2;BJhJmRM9szBIPrbWKg_W-$o?yi$63xntO#b(Y|8xP_7;(5$!Mbx0_~ zB0b4R=1UEgx0Q(J5ZINJpWtVm8!0Ha&yFUI;$g{hmP0 zOQlLNdqo#~%34O7!4lJ_K;2@?^_T05o1c3KUtQ)E+g@jT?qZ1|;IIpxkyeO#_CxYO z9MD-ygx%G#Z~ZX8Ht47+I07kobz?meieCU-;nx4VW087=z8E++*&n~h_w)!+F6g|x zJn|8F!s8@-Zml9f+?4+y;{DA>ADWtQZPFSr1JaFM;L?q^h>^d0m9@~njdlKL0zS}j zezL`YxYLgky~O7?f<0+F*fncg67({Jzd_IlzP#oSE!k29YRaNp(?V(YYF(247IN!X zve(C;wml2hkBEr#dkyv%F*v(L`uLsuZ?+(?+O!Ze%thR*Td+C` zgVNK|Mi<{K#cS&xh{ph(r2lq8A5;8cXa0JG1kC$azO{{DW~?2m;D_|J1zz>Tyd!5^=zo^~!XOGrq_ zkpoq)qTy?H6ay@@G(?YK%}W?Apli7?QcqZ-@O6s1L$u)}a;t^Tdb0UBN&nzt&G5>q zu};3zE2Qviv$W>H&s)EO3vZfz$@=uN*Y(#yLdJuy>-R#MBP&%yKh?j1Ug(Fc>_I=b zSKy0Ok485CARLu#T7nnD6`F{@9_%8Dir&mkIBF0->kxTS@P>1zlP_zvPasc;loxhS z#*4l1pJI8`lMIg2b)?F@&RxVLk~ZE^+|)2+q~&?@;;vDsl2`fdqF>f{5wkKuaWni! zvpU>iOV^utcSehm@3^+KK2wjime&lS2)|bxg~bGW6z=x4A+ACEdK%Tp`Wl{T24Yv? z{KBlR<&J$9wpQwNe_!mZu2b`A#lQ(2PSVvs%z>bF1$O|Qd{E0OlEg(%1;#h1pb>YG zVb+ja^t87{E~|(3yS--@1pwHdCUH^YV(vRM!;q`er)eQ%E~mBi`pNV3Ngq}5r+N_L z?8kTeUuYt|Xlu7Q8Fx78O{4N0(1~w3GQVhcJa|CY8Dmps|ElevVar-wBSo7icp!Yu znVT~)B%Yl&?w1mkm?EfJiB!N?z0dUfh7o_*o@@XGndQh5pPNpc&zkn_fz8Tn`bc+| zPmv$ZcAuSdWx>s=+F0H`R5MpsmDF5Kvq(Zb^Zc;CjHfNL&uQC=QL{Fi*YUb;=K8io zMpNsoHKxB$_S4`~S@M*6_dMp;oSscaLGQIlI`d+;l+8a}o?eH7x_Bwl2fqN2j=Udl z{BP;|QaJo&I3GYq5ostyC(5xReS*x}ATl<)Hq>dj=S!e069r8@yOLKVdlQH=z4 zOvwm8(#f$gVcoOho&SU9@dsB{jO5=67N&GpCT*7qcj#5#nm^L5H>xQVrHC|`?vr!X zHA>TF$Ei`rob4X5<3q&jh9^qBABCzas%gHqEIc(nGoJTg~^gM?d(?U`=y+y#iwG>XbTj&ky{q(wMM32ZkgxwIQ^ybPBq63p z27#Y0z*X5!@F_;3s4jkD`giFRg#ASy^^SuLuI-v}!-9owuw7_;;c-euYxYjWirrKl zF58XMQ|jlq8T|TZ#+w%LR)w38L*tbvm80xT;rvtD-=83R4FM{=hxbW~Vrc1_Qw+Yl z`no*~?u}0wR+VCQF(|c)^@YfGXsg zT{hHF5}R)hLTsTK+@vd}Hkm%SpCe@#OZ8`d0V|O!f(0Y-m2 z03n$iqy~VWLmfuBHT0Qx;{t4uneFSNB>K&>7VK5k$A_Q9NzJNZXV_(ah{Aq#$@Dn-uWoWLx(n)gIRZ%eeCr*z-c zmhqj{cj^N=Ci;=x7v9`j-CxCVO2ZqK8C_k~{jOq@x&T)qxpxGVlHHd>5>%)4PWMvnT_uZ=Dn8Zq=zs4~vE zJvqTf!kuXwzCBYG;=fJKSv|Uibz8rh+K8%s$^MEqQ(!SV5IlP&i>#QNgoX4qU?W_| z!v=WSFnXB)M?Gx@ov&SDcq z7&us*y z(pSq}^ZV*jZ>h%&$DB-zsPaXp_;$&jq=mVHRK{=3?~_*L5B8?~KH_@Bu_gaK20}-r zEFPY(EEB%seZ^Bix|xQ;K8&Zx6eRJw0L5KJ@o^hTtlPS#p6B!GELoM@darV`eMesS zs!d1w5&1T6XBjjVNpGI3+L;PyvC>OLc5oD&D@Irx=?8)Ti!-&tg;$4D5uud?B6RVv z{$}PX{RZDw)A+z(*{6&RM{JMW)axB`uHkiC{CNfHrB4H<^I>~UNv=Ql#I&3Yo~vZN zDIz3Q()+=A1Zm+ofd2?mUPHq#=9UMzXX-qX93r#^8M|`O>Vvy@$-L08ig8-!st5AFo$%#C7U?L~YA@ye$Ttyr(Nsm&AJ!zoOI1+0(oklf9O2s%si|5WXbv>HYtIaf4U?}IblNxp@Ql1t0|PdJw>5d~NG(?$q>!ClIiBn1)Lkv~Fc+Bq8$ zmt~DBstcdZ+TEM^Ts>#;E$QNOnXZMdQ^Ii#U4GAks_sF8IFnK4s^A?5eylt{ZQeNJ zYx00OW58?rq%TZv>2BA4K`@~70Md-zMoQ|c#(gwhz2*|feCrFe!0*>o*AA?aq_CO_ zcp<3ErMPQlaN@AgGArL>dod^&jcp5C#?TIVpT4QAUNj2ek#^(YpkdE&2N(dWuCse> z0+A?{4!XG<|M#Rt{9S0}2tqRHY|1;AwuJ_jAMe3CIE$Ng$TR-_z)nz8L`1}01x3Y+ zym7+13g~|1mm3AMYG$11zW>R?Q@fmHsjWww7f-(BYyTnosb97}P@8}LL?p~FxTIxz z7@y40=M=YMBdg-X5ZdJj%AYKLo?8THD3|Qq;#QWB8#efFW{@e3RgzR^cTUr@kd}6@=Rp!H|ft|0K8uq`UXeBXuAai$fFK6mn$WB7q@61r1oH!L~Rt_;ZDdIX&|j z=BZBKAgWgmq2L?=12iWsEiFo*zWeO~7mzORMu93Y>K*L=GK1DaZ0k4X367Kz!GK^Q z2+TADt@|w>Lep3QEI~U;nvRWbFVg z?@t>!wv5bwy2>Y%uMZj-OK_uVvnV`frhptnPIN;_?qLette?mNs`|liDV2dM%`rb7 zN9OgPm`9}6E9g!}rnLvz#G%U(rUGp~*{kc1I!?YbVqSCk{tin2-FwDx-*m7iOngta zRe+0}Z&BV^aEem?yH>#j_W&?cVT0Yx8Kwk$L#lF4LS>uA4FX>cftR;Xn*-lGQx~2v z`|z8%->Ys#-$IbfYvN9PTh19n$f^wu3xj?Q!^{tUys_x1dH8y!Cc0pr{@D?brN;OD ze=ymea%7cuNaBNtzF)wDz>*fkLO*Pp%tkWXwRQD!|rRoCiH>-B`ic z^pH+XZediqg82u_p^pVS$)f6Hlh89gP)ehELV@cyr#hD-r-@heIfEMU&S#{t7=Lt? zGVS7RLoRZ_7e4(E7Jz*o@_Et|^+FopH0jfVW@J`|3`B?&kfXQJhn@m4K>C8H!s$NI zj_w)9>sRah9HWg5FQZr{3fXtcn%xSJ55h~%$uFPQOI z{m&ITJ}wXV#|4(@m6t0R5h?I*BrS&nYQ&E%Eri1(|C%ly#tPmoUtfWYR>D*Urfdm* zZr8fUj~y1SN}E4(adVpPco&ja(?eZ!1|i7wSjDKOw^4qqwlLBxJW-crWrP{3sPZyJ zrrYuDBAMJ6!eCF7D9&Rj$fcxEncOT~Z;S=nQqa9qeADax3|s0vYwnMm$#6#@%8wtm zYjS0$^%pJb@mwLlRVv zu!COj+YlzOKp-X;$Pks>#A5I3CDI5g(hE+{v3@R!4vMPIq~p36zk0!2KqSR!eQV_c zs)_dQP-9TU3#o2`ycI93U=av5j{h-mDktS&gn@I z%CdhG^vSkBv=cg>2HfxM#l{>>v2`ZMYZ)~*RW$DCy`ezMTRdt~F8tis zx$3FNH#WTqcLtYh1CPLt1`NlpJSxzE-e>Nd<+$Eawo%ozD*Lu4n-#;VY4Oe-vg5yZ ztjAr=5ihg%Ijn!2t;7uSrc(0k=dRjzq|&*G3!2BW0SB@Sjjt@GTN;?TG_>)Doam~8 zOK&XEZP3Q=&~+u!0hLKdm||&*S{}v>-#7af%EAS@eXx9Cb&6OU`jqccJ|nKPc(vYS zY<11&K@iU7$-#o41eiJjq|iRlv~H{a9|2#~#edBiKks5h#PaTk^+M|de*ZuW4-bFs ze!@Q$v{38LHW1?OiRI}}ZLOC4T%!4h<=4u_wHE|6n=&cZ4U-~V(l+jU)bp(V8{c_9 zc1#Qh!cGKYS~Os$2c${!7@de#CbQH5fn&#_V;)P=cUH1*pFaYCZL?lB$3?{<^NaUa zXucmYn8`Vo?5JN;R&X_HHV=1*+1d0SaqY-w%zL)mRmXCwY3;e#By0|D3GH@6*RJkM z8#7Bk2u(;9f!^psa=x=bZpY}xK}NV>SxF?aWE43zOv+@>nLwRuy|Hmxk=nnQky*%f zkx`l$hugCLD^1?j)wBj?I*Zfd)XFCAKx)nNjUvRCb3ptID8oM1jdVo?DE@y{91XrO zHbPZwTey~OWA=h9poAkS=6&MN&Sz|#JC>L?(svax>Jh5m=ldsRE=$hoqq{H=RCun5chloiVZD1oo;>%#f1y{*|^-j zo4f`;4*9nshw<0aJ#DWc3xtbXrX&UhhbFhBnU(t&cfjJKBG!LMX87eJdnFpxb+E%S z?&=b~2ahJD&<)G@CHrozMO{dy2Orw&yE&*|oFZ4s_F9{Or?cs!sRa!7w1P|U2`Cr8klRAOEQ!S>_d4)gcH_-45ILVjKra?;zWoQJ2rcCL`(>ecaNk) zvm4ZKB2{2<@v>N79L3RVo?da{Eie?RED%Iyn~4nllW=9I(u=P{PG8XZQU}7K$x2MV zUS3`eZ$}6dGevU>@5Tm2d&sj1$}{_HejZzxMIby9oXqgAwbl0MT9D)(%@7ag$wXkHc>)J#*K z`=evHQn&8W0F8L>aoaJKEImcAPiF>`^4*%1m;h65FqIn}S_YPmLiLgV;)Z-1p?j^w zZ!99gn4fqH35bqSwu2>*7mWaYEH)skB!5n)O<_HNM%w$20rFrTMoTS8w#B+vY8Qbv zq4!rUs_V{mrq1fWhzf z*CYN5hST1PLsVzW#pP@arBj=-@BwSbyv_K~L3q&33_4}`i1=U?03K>4gxQSM?C(3BWj zxAjX32j|t{X=T)vDet<_vYJ-&QtL&(-!`V`*VEN3tGT#!=`HG}d%sYnSrlmtc`Qdd z1s3MW3j(CZu%Cw|x+48wF({KqbpdinrVkhn=|S-fyfh)C;pS|clxWdCx1s0KJ~~OP z@!%Ssu`1CSQw$b3O|Fii;W#_$aD&U~YC7i~jkLTaSI> zMsPm(r*b*!TIp|uxKCHfG0yZPhywNW$gBAFkEriNE?23Wr<5|o*at_4h3BIbrl<0% zWsHOgcE~x1?}b^i5LhVB2ov}h{>j`F?Fqx#0>9Deh?_ge-cEY$veaD57Avns{s0V|MKRWIf|(5u3E0~fGTe& zWN%3cR8ic|+y-W{+B-@H(!uWUQ;@8J6T#$h4`Z_3E?Ur&CDS1ACTp9GXyk6p2ec%4 zD4FxTiHY__KI}0vi$?uU09o=XP0UjPq*f{*Rux2XfG}DG9iSSg-N{WORcFo?KW$TN z2G(GpMh{2itqkrfqBk#p%Rr9Ui{r(A$|61G0J_)!j7gTi0Bd|X{^M#5As2cYgm=5V z`go}lk-W<}DwRgrcBMgGhC5qRL{)WUsvG%7+Nrt<@zSH;M1e0V9xnsUH9QLW;+>43 zFQSc{PG;J^M@3Ut z&A^$gkjTaGo5q*CR<%Ms7Z^g-tc$Ee!?lZQq=M%JF4>)PA`-?0N{&dBHi5;^0Q>1+ zj{!#{Tt67nxsh}vVH$m}fk&u5;^+*jQC_VQ-z6?+NIX1gSNfhTA6z%05qi+~TFx&l zH9Q0~6j(?P=!$-)|9=GL%a$!7IT@dK0lDBgLrs}riFq1@DO^oMQ)U`noYg-%F`jlr zw}88HyOsv04IiA0P)zVneGoGoNI|>>Tv}*bq=0Hs5aWAmap2-ZohMME-)x1vdM70< zIR-}@_aoU14qk6-<)rHCVt$`Th}U2D@E+4NDkb+K9cGKSJPEb&#TM-WEy)?XK3ZG0 z*~fkvo!lIk(iz{k)i1f1>ip&2`)1ymI6?#sN6X;I;dk7=YP0Z}`FnFxvhjeO=i49< zksDxRKpCsEB@UFQ+Edbh4Gbn9=>rDKykhjCu_qhUdh^2a44Fj7<6IDr%mvYHGlV;G zfgB$LI!L*Pe2|sgs-TzK$o*{!=M5si3RsYnoCCBqz8x2s{A z4~>k?qB=$h26UUymH@w#eG^2!4c3 zPaMTG`EX3D>zNy53>Qyf3oQDErwN9XrO)dk8Zrp4w&OOaU#$om-Z~l_5;@)imKr?J zD4g^KHwZ&OJWMmvpLSn_F|64ia6dniyy~7itdIr>l-de6!_MAm{<85S5X5DHbl=9t z1TmGN8y4*YT4}g?!;EVVYYP}B;uw`R?|A~-gBbeS7RdIvhYN%|Z$h9UJwd7!+ z5=-W_SAA+%W!Mz#U~*)$c=!8ffPTN2VBUfNC%yYOz-0M{GmKT0&V*u6A{wj=HoPG{ zMhAJQ{TQjdx5!Ws2o${%UQo!nr>B~hjd+#oh)RSDh|4+0XOh+owP2U9^O2Z|)jarH z*2jXOW|TS zu9>y+)vW4Kloz(0iQOMVRkSV%C$k?)7yXydzb zhCBV8hNvznS*N{)vU83j8Tq86<3U!D#tAsQDrou0r8tMVrX1`@}9 z3;)4d_G_e>o(RMa4tpiX3%2k$%bL1|nJ$MoP)3_hzs!j>Cg}4Vuw{7OKia14@Ck61v$tZT7!^~0ieEc z4Ndx!7?hNLCi^!g5ZRi^5S?YVP9lI$l$)Z1OuzwHb1gm#&0=A339%!sHydft976yC zvY;K11014T>_dK4835cTE9fr}V}_J*zkdxpte%#V8lY<-RBz9Abyb;MgDB_hFi~P8 zRWAFGs5P9bE{MbjFbGWFAQ@8ZCHtpi*~Bw)aqAtbX549%fN8?CBEnC$r=*@U3^PW~ zU(I?~PJ6XyMI(f7ub{heV&ek)Kl)>(xG^PR$q-+fN zS-sm#zSQbMR%(06tX~RYo=ztS>(OXHOv7EaD0nLc(4%BSRRAb6qzmZm0Qz_HGMdCA z0!ImqLWU9N`IjwYtWo$z;z^PMzN}=`K6IH-K4m^pg~=1n?Pi-enwS+T<|UkQydC;t zp@*kPI$&0Q|LPWwRNP##M*#0)r)joDzJ#6GONi&)#K*q?-*NKe_Yclz)IYOhhX?Z- z<8;s4?`uv^2yHIz1Qp%aESDQ*6WJ#(?F!WWL)a0GF%Y!RjzOYJwji_eAgwXSE^4g4 zTTA+42`(})JBHeyqC07@B!o*Y^eP}+YCuktwP+|nIy)vgj%pDMpyCFLbfP0NTNqUl z_gu8a$p-`nSdq7o!*3o?frB{A%Y{}}p4u`QhXS-NoE8}vN?@iY|Dg_S!0LJXq4X?HNA6^Z9X_ zgW$U_SrWXSwAAxGiUaoDKCR=1y{&JC=?~l0lAsh&LnwBAg4NaK0hG^hN7(-XO8=BH z1z>i8P}mG#g{SPvwP_hcN6x&)vLZ{gPJYY7YbFJxm(pyvV#*|Re3T0W>D{Ca z+~||MXA^>DM?@6fx5hCr$0n(zKO~p=kwg*MOoy~SgnzedPRYi+bZ@?kUFQ~7X9eaz z`z<#n`fjV|Y>5DH0G@yju#MphqSP-ABn|M_ z%E#>O+}vdv$chKq!~I;~O@07S>nGJWZ_NXgd&px?KfIYcW7-r37cT%sFYFRdOOEl>x@Zgs>LHm@_$d>py z`FA`sTun2!80H=CWXO;<>x8;fgfr^b1PY2>!@*d7SyfeRj0~qStYjQwq?vBR za`vs0BY-^SLgJkPvZ25{_6qFA%>pz&cCnx)^!ELFiq{pTLy*CcPzn)q>KlrHL}Zea zHWY@Q=2-UsUB>C7B$BIeVx*F)3=)(>qRYv0i~M39kY%cnKp-C@fCqL(1AuYXhDdnb zYFZE9*JWXIDKz|JqLmH$!wX@HdA{WqB{c)2PBE>2(97q5|0V#mR=xYPDJ)-&UHiXjQHawWA|!2i=9Cvw6QXD*5e{HuoPMb2|lTJ8=3(Zc7#?ifQ+F=x4>g8>f9#+VyCnpV3pc|veA&pQdTrAd$GGot>`;@>ity_ zsSKi$1FjtZ2Mmg!ZUn>rAoJM^LzL4N;;{5q$&VWyXj`0vk3dTK-&0sh1uEaC6?^vZ zI5%f`Fod9>eX5k~Ap<@7V-Nx5C%_|!y5^T843nO8`t=?WK6WT)_>L+aCvfa6& zEHn;jT3z78+-imrqkzhQg8@DbYGI_f@)RQAWA*XyDhEjY4>6`hU{C+K5Wq#NpDw(gjYYQs9ejoP z_8d}LtstPP=J%K^5cL_|k#Jw;;}r0BCkI3VHf)Aeqbx8*hmp-HED-Jn*o=Hoc~%uI zMU?iQ2~lne9*!qT)KWy#g{5JufFJ`{j3Vq01*AIppI$=sXSYYga8y@?KE7xiZo7_X z7G?*g@Pj>h&nx;^is#4Mce_4c}eu zlPml*z?8r;1K{4UVQ#hjK<6YM2PnSs z5SD%x>&r^VaD^tGlux943fPU#paCXP8jlYwmEutj0o(gT^`GD|j!N?PpjJ7+>q|^! z2xb=X2bVQ*N{~9R#q1#VJwlgTB(kpfAD1WY9#@P_OdxD|1VAw`TPtDMkSL_Y1nK_o zc-(>Zu!!)eoWLJKRG!Pnf$D2&tjCCdu1@qTww}Q2Hs{x?6eOoDhUGq6G?be*v=-63jajL7M&` z{ph;L02qzym7|Y9%uhPzS>C{7kJim3#s?E&q(>nzzcnFhZ;J7wdU z9xsbPvnWYBctu$G#lw<|XK-P;U!i{%ku=P1)badr8Uc?1{UJP*8Qsns6hi@uRV4Qa zfDN+d9#|=wNxzyT9&CBNz=72MHCjG|tl%?pod$_?-ll_}i~&(=VGVL$vX(6u#zW>2dQt>P?3DZ- zTusv8oYMH*Ap&Y}o-x0`e)-O*7;3uXNwH)#QUNB3&xd&Zl>ZPLZ5S~ACR!=tDPAw0 zxA386##2Mh2ThST4@7VUKb(wDV3WASz_wO-GoD`9)FWqJKE5^=a-fZr;5&g#|IWyuMxyxHf6=;!3gUkk z?eJ3ans+x}<*~_1`l+MY_@m}dUk0lZcjw&5YeNr_7KroFdXs|1Dgg+U`0RbUe^HRH z;*9*eDx&7N=h->xc-#(Ue2kE?9Yb{IS5{rlRI1bRiCT35|-XV`l|f%^Vz zkL2o7He*sDPX5DB{Ki?{bA{r@?2I+#)u`?arnOAKdPPZZw65(fC+>y`YRiiQ!)T|$ z5@Nh&C!{~2byK7`=!!W&+&hf2FVZnl2S!S!phLz1LRN6Yl5&B5Ff{;w&PsQj021;& zw<6&UFxg5Q*w*t^4rvTNRU(f7KJkaNQ<6Uwa4u5?@dTyI#&p%>7KqV18*Dt@q^^pt zXnvbJ7u~VTp@RW$Y;J6XujPi#pB0Bv?e&80`2fJYv2`)y68|PGK*D!3TZF*;)D}0l z?6@#Hi(e4^gX-i7dEBR5dwld*HY*47j^r3iBfn1WJ#_^04*S5Xm{0s(ot7i9&|Z) zLU|bWlXdcVZ)U2r_BgW$KJP22^O?5tYia4GIos-lq?WHBxDlYK?E_HkrT8}w@4rL< zlMwJRVkkE6a*jrneRRV69-CL&_4eJ@Lm%#`eO}*z2bWFFI{oQ;zlJ^@6SfR+!|;+7 zdP~#}yarKdH6c}6e{${y2|FOKnyP=36aIq8@@~j!dY+yqvZ}}+?E-hWK8SEjlh?6YK=_}f{cX2p zoI5{zL!5iFcInFguOAn_mE^p+{F~+<3dv4X-W|Nk$l(3OB)gA`2tWt{LZGw#-K#)I zI(T9mo_=*!mARNV`i%PtV9F}E&!J7=HZIN)ueDN@h#dEVq{x4OnIQ~2mX9-~vtHci zDtU;gDl>edJ|6kA8nAbjix*&nkuavz_@bk`_SIaZL2(~mVEPLhtc^aqu|d<;@LOTC zfwtRTWExSs21gdg#%atiCh1&4upq=qM{g*t zAcXLIc~GN8sNbmr|IG?&u-(cZWiLIBO2`yw&M^p$K9V+je~Odma<+({MPKRaQox z<1%1_;Cbcw`5#1F=HfksZYsHtB%nQ7(%BR!45%Uj^iwgw{vS=>9Z&V+{r|cHSC?!z z`$nQ{QWWkrLS#i!b`r8P5^=9px6BX~A|o=gXXRcJ%HCOBvuk8;?)cr_pYQMW2M_;v zxW`$~=XsvBDUiKOpJLcI z(Wu9?@Q~U>KzraEVbxp@E5Yg72>eG|-2W<1yU)jk*(;ISOjx0)8YPr8HBm2}Tc*i` z`1AQYIpUu(5IY~7RkQoJjyHUc(yIU~(`2IV@a`7t9M`#f(sXo=AH~#<{FWTY)5k_P z$jmoJQJxh;>qk*p^4ji4gF9hoDLInwh(+EEkSJ8C-r7jY!fX?Hp`VnZ4;p6X`kmf{ z60g46F8ggVqgT%FJU5rVAiT3*b?_ zNNl>qsOVn)ZsMjx{<5F`UOWt@n40@_I-@?9ZdlW^=)^88*W(^w^oFS+^n<7rJ>prH zSzzPL8o4due_4zw!EO4M1Xq0A^pm5~P|#jEYb?bRy=1unIG|GV?y z>ut(6Z>~**BdX}OtsL7&fxEK_uJ|M>?w~jcHQIN1z$6INnq^M5AK=DZ+a^8p7N_u+ z=Jto{qi}d)uE^am(>10G18gl>L)+S=BJ;T?KukA8p)GlYz%OUBBn!$`jr>1*T+zU9 z_-X!5;yAvMk{p1f9jCnfelVFE7`PNb*N5>Z&)kT}e21{R)aMs25A}OrAV>dx!S6IJ z#6QiMsjE1Ui8pq&9g@jBUWvcC>+3Y^Chveh^urTt5Nm9C4UMA^G&Lox729Mv@MNiy88`%A`~RZ~NEFf4?%7 z$~lA6N8ou2`F}74ujENeETeyfD1fiFA|}Fz`Xi+yhfV`F$$sHJ0e`d(iTEpbESZ1{ z5LlRXvp&6Fi5NGd_rCph<*#&mPQa8PI!@3xp!Pg9pe0;RQ^iyWl$_y{y$JQO1e#SL zrose1kgFJO3s{8W#HQJ#;<2{d#95;2QE9Ef$n-0ye{$i*ps|frE6zJ%f}w2;*t#sm z{J-jKzU}oCcciyU5!{qpUd8&k2rcJo4g;NOa;!a58xdxW_=?Kzc&Wgn>+K{|$a5AIN>N?s2=l zT<6=cJ88CmD|jO!oz>OVixWgp)Z1ZRn^16#Ay(_1y=e>1Mab{}BJ?MnYq2lfV0(|Z zhSj}O582y3yK_k;a7mPd#;-@lzbtJCX*z!-)-BR%FG?+3*IN(snvv)atKM*$#jtT; z+-A~h*XN#*&WvLo+_xA-U0EB($7Z*Rd~8zd;hkmsPA#Y3L=f0#j$Si#$9HzPGOM;y zaX9c*H{ycZuVMP!{0u_($ne3mISB;w|9I!gX2V=a8auUtx>EchrU2GtzqvWE-YhAt zN;vkAWhrX_{GGa1&SHsB-61>nJ|b^Fn=jVY0UlT2HqUux_yqe50V(D1pophjWEDDg zZaljbN{Pj8@?>IHb9ewfz=Z)C4;;n)AubQ+HoY6B@=?O5K_P)3NfA?hj0I<=xz!^l z+k3^J`yR6~kRv|OI|$lw?0>PSOU>;-MNtQSQ!jnGUG*o6u85VZ(MxPbQqx`^c;A|( zsp^q!TSay$>6&EmZV?WQ<@Tdi3?~j1bHu^_{Gcic^Y)LNXKh{p9iE24j^2ODIttcP zraN``JmfQLVCvaw4{CQwK{hiZM zsKPQe%En1r?B_50)XNDA%N(;78M2kkBD1Rb7%Tk@ z9C1T5+ZjAAJmkkH*`u!uoL&^|gSue-9w^4YS3~|hfo2N-nl{1Q!eQD@XGEE&iIaC! zz{pdJ29UO(cOU4J&)1p zru$aTAB-suFS3b}HK3l9a1zH;97)_#2s@iA`?6hE`-CMH>zbX3LBv(9r{c~Hw^o2AIrI|xv zG2YHvs#$|9?dMYX-$RyM(9CM=yN1kG0kcmRQs{I~j)4WQ{@@_o&RUqy_y_;{Ms}Ou zzMY~&3nVLQXiN`}V4HoEKf_6AZ{Ppt6EtBgGS)Ai=~jDxSA4JT5o?b7iZ zU_|JD9`&|HWn3f~6a870^X_}R03Fz8QTg-9c&}ar#GSs=ac`IH(?bqSJZI^g1xA#X zt2`@LMCIHZrBbe(8Mj9{I)XMab~t>@-ZM8Ul%s=l7A2I2k3Jm{rj+Z7hxyhF7AiNg z??Prq3o$0LGxoRjM+UeT9yV8{5W>}VE}yugJ70uHiH8oGe;gc5tLu`I$Z2TtvFath zJHJ>EaCr5Mu;eeslq_%6nXdy4*xea51a`$$Hlr+u=jAd(NrWBtn;=**b{gi>7Ylew zbl~mnEP)m1{3|A;)O6u8^!nxm?1eG<27fv3bJ36yRu^5G${1^8NS~r7R(@`~F^bh| zdX+imOYn+{lC)gu_4EP_w#{$C!qRpRBKwRGSm-f|U^*TKxOL?#djt&hbbbmZCNfrm|slJJ<% zr8;~dmWN>Zi)Z2iFOiOTZ0(fZ-!>JjuJenP#j&fOp_8(`7c?(o5eGi?W)8vsOWLEB z^F*fA_~)o(Ov|d`?}6XHHQFtxOg)!RDm#ZDC`6B`sCKEZx$Cnyym{h%k68=Mi#_;e z!O;M^AOEgA-JN`Hob4(Gu_|$9mx83eoh@q>`C6?>);wa!xFc}&So7d<%7RyRQKCp{ znTi2CA~%c8BGmqnw5yS0Ykb%6xua^Zd0J)(B3T(3W@P_Ko5*aZ%IGMYM{@IXyw?>y zqyU<(6YpWc5+osSKz3J}>GZ_u0LBY3Yu5(-fBeJLDNN;Jz-3Se44Da}Fw)_*4lj$& zC{~glLI8A)AH5GWA~w_=iL?8a@sWX_g$P@XALx1v)$1GLRtLR0ny;BT{(VW<#PX9A*(&dTF3Fho$`HvgLcR>D!dT&8t5H8w#!0c4e#?KOJ1T zDLxdbiukE9`lqGi9lV2)Knr?dJbVJ{<8X!_cQIlXhBXG>zcJ-t;qFJ{To@l;wx3D-D`BqVir)n(z8>G` z*tn2ad=0*2J4Wj`{~JY+e8C@?a`zb78@-(O_*4%=ba!-RL*jjiPCTkkIwBmM&BLAw zbG4*fBT5WQIC*19kU-Bj;45HLVfpCKOo?2){GgX&=qpS(U4nT-JeC#u$lgJZQ22uV z?}vuo`+7}PXRi=P94`%opJP67n5gx{M<)UIz}z(_Qd}hdFTcc1cHl*JAK@&h-hRi{ zckbvWx0AEgWo;fC;*&+ZdTYa$cr3&0LP(=hvtj(3649>o%5^A0qZ6b<2kU1qT)%VX zBei$%n|Q$?f*qrCY%=gG7Kh5^$-{F}w5mnisD9rpFlUcBufl&Q3s1gGome?qT$be8 zjM0$uJYB0rSUIozI!O-4p?iGH%co8>*?Ki%R{(&Pm3}59tlnJdF;8J1)M{=x{TA+) z6{dBxzfQ9O7@x>?)im^dc7JslWGc*l1#~Mv9Ub0<%QJm2bVa{(6o92W>( z(Oa(S8X>QH7$Ul(!+N5tyIh>#IGO$glVh>Yl58CTP_zWSu1m6Izs&gLdK%VDkf+no zwva>)`j=@Kdngtuh7|(+RkQOWX^+L@@1$mqH4(kaZ+WTjlx&lBZ0NG%5Q|~vY;n+8 zcGrKr%Ec1tMg@jl>cH07#nm!5+FIKlU7<$_rr(vg^tWN!fPrf49Kx$LmSug!y!osW zRk!UF?dr8exik`kmWz3GAzF;p#c*TGgL%NCtC5$Q) zm>CQ%5-u+Bmpj`GT~3^C{5BLDreZB71ld!gd@b45ubT2H>zhaY5ZKnjRe_PqIkJ&cr)lN(_%h1+I%^mF|3ay*pMZ8pH|Eie* zmkPOpXz`p;j&0Y>ghSKew=lAKfZ9`0y%K>F7(WkU+vnV`~lx#TZxHf86D^L{O;MqG`@uhmFeM_wZr^tv8^B}!yh6Y`dYC$ zu5Mb=-#WxU83s=!xhHE6lnb7?_<<-R-hCVhvg$aMO_PuMSVG@>#Y_1pLeAgw-~q{)KxCaV%_ zPw1p!MSamKkn8^&2zwO%z)XjxE@V_=bSTke;=^{KN0WhTlMQ8ezG7&lrBt8VZFi}{ z9@={kBgu_+v-Iw$*9UY1=jf6V5eMxYw7fR^4t(B`dn^8?7&ZRD@GYvK>_lEI&kL%b z@@$z8hXQ-3+9cf3v3}vgvgJG8cz*{Erdg&?Wa;Qyj~=MGo%9FKNaACM#)8;4=XX=e z<#X2GoOl`jWF|=AIZHd)xujrX{mKKv zP4s&|@ka~wN6V-CX!A3`$u^%V5Jft1fDt(7r*$7appl3%gjAq?VHTC(JJr$IwQOfH zGB|44)C^#XU>i3AcMohs5Co@FT!j~~R~Ta>HLxU@gC}-4PsyCV)KTKO@Az3^pUB*I zwGr4K+~#r@Rh0l5p#V3(s39=PsacHta@xJpVXjAS{K)}}rb>uL< z{rg@vWV_lxvXd;2Ig~0~szdpj8fM+5w3%i{E&AI#pCz(a-$o~QexR<#a{8jcC=1bH zdogO%MR6*WcQ58SZSq1lU6O=GGd>aJ;bcmTnQSEPST(ZTNa55JyhPf4VLB-#PV?hb zY>mQab7R{Tck-b5b@wiyWYolC&S{FB85OrP#tFlC#GcryRv1;CFBg{Ge=O^7e`teD z>19YXQmHtBQ|OTz^Y(Mp>0waU-EBx*)}Sl1-%_IdEF|0tfFocQ#*VW>Ua{{Dx=i0s z=ws3s1nmg)YK1cuXi0B4^@qAmR{{Z0X}Tqg*f#1fgD2IWX^OjKu$n5ZRg`Sj>`$uy z^ctd6(@&p{PAVmZSl*BTWZI3fXSlGnZ)E=y$c<%>kR@8}S15BSn7AYNue7zP&ouGj zFQKk=hfNdt+lb0J)MVG!H>y*_-EEO=+{gi!%51O0tVyAHIBMvCX*TRQnk@46MIk;! zUk&7oYCCtDPL`$NP;sOSml+0nok%u!&gyt>0(pb8@9Sf)=Mkuh9S+i>6WbehdGaAE zzfu!%TZ@?Bp3I;)**PoU>gvnyJ1H7SPv(Z7a=yUq&`LyyUw=)nxa}~`!tgQEF0>o^3~!+^01kM66KYcl(pWU)MQfVM63CC>NPwF78oV_N8q1Tv?S<)=uTWN}i``;<4@^_-hb(~QL@TeX5 z)C=bCxC z)fQqQHXUWpkBrAgn%7IC8yQ{Ou|pi?wwD<_Zt4L$AQb`h1mj37=7M`(h}EOc+n?X> z+ajjVJck(b_!1^sJOCK9Z2?gHKipM9d&;k*L!GOpMFi8V-G~zO)2*}t-`k%v_kYtD zH&c`;_w;I)wtM|N#q%v6pfFm$dPM@LCb{`poHVOx@1oJptRJ+yZc7pz7%)om;k+d8 zzL-8OES~IFEkZfgJjx&D(SveyZ*ddMcjBK&1ta)Y*={ICo8|g6=roZ-0$#mhoi7k@ zl7{>iu*3T|NOM{nFK+x^p8q&bH7)dt>WSXrzl(Wgu|B&Cdi$?2q0i5=Cr_(}A&^G^ zlz)P&0-eI>^M}{Px0%DoKnOHA17m-WZjqXWC60tc@lNRT+z0nWx$DEK{o(b;jIP3KhJs`~v|<+qw)F$`aa;tauE%>Qfbz8lcq|3n=f7muSr^I0Ql z(zmx_&bV$rox&gAUu81Bzq?KRIr3Ybcuv}Ih$ulF@1hHJTZ@!ZLG_#0 z814iFIF>wA1H=LJt=C?YA>o9kq%5ysH%1XKtlh(f$=mK)I>5-2sD2o)dQ$LIVl#84fcip zJ%!IsVwKnEV={e@J_irNT^acse6U&)O1t*Cf4_gSYAAvM9@ejJ?G~7S&-!=H0wC-) z-WJu05#XYgmlB0>f)8DxouE!mE^~FP3Dr zZn9HL;~06Via3hdYQV0T1}O2!9rp zd|L>TTaq6n$}QP*PX+w$RXiw0W!r=4CwNhKH|0KrJ^ z>2sv6AG3*4PmHJHC>$0`G-J+Bst@Xxe*art=G8BHb5PH9efC*yfNv%Wv`k$j$puga zpKGFMX|t(ef}5!KGL-wlty$T#VV+eMlP1?t56Crb+)LE)I{w)(G>0moNwe93ohi>@ z``d-`h<>79i^5MO=HRDqcV0pA1#cbQ7W0xtGK9dWy5E9jfs2Sw<2JZY2t`nNtOxeh zK)f>hiF>60{hv4zJ_|!uXG^0M!RPZq?UZ%g~4?K-OrRA!dF+c;6bk} zC8s6O-Sv70!a?u9b_&CS-PZVG-qld`uR!NxAT7+<^8Zu#-yXKaL=9ZTEUx#~=jZ1; zU;esZJ@qccrL-n)Y(x%kV1_Z+f7W4NvL;tUHQgtQn_)z$q8s~Ph7OOx7+(}kykUvz zS@M_m;R8-<81rdo^H${xZj9|y$n^Nrq#ZRTGzRMweVZY2`pYTTf#gTz6#?~c7Zufi zVWiUG-&|#NWGrue&j%H8AwBOn#nB-eM80}K>JAg9u`y`?;ElT(iq$sHb0REMVoCMZ z1mSMR?Bkn4&PATL^srg1(?7vhBGVirjfZ@%zIcA7?UoEzf{tdPt~oBB-pIm|17TPQ zQjQCq+Wo)O$Xk09`4iPjC3oN>?ITAq-G3Y=!~sWpwwDs zjn`C~C&j+Zv{lSwwQlKQDJ{7qPkM>|IqhKqn6vb~ab`J#AymmW>fYKk^LrMF7Ke7B!J*phuw~wbSCOMtdvD7Tm)$rG0Ci%qmM!$d7 z&5#ahIUrcm?YapxL-xRk_lm0}-)D(0IL6>?qGGsDD3c)Tpcl;X$= zn~&5f>a%zg9qNT;%&7osQzd?fKGUZsEnREm7UIRRrpkFF75RD8@r9h|E{m;j;he-t zKm{S4Iqo3O5;UT1#V`L%D1*Me{2t9nl*r~_r_bYgv!}rEoQrnzce-<|3G`T$g%>VD z>i@;$Q2cfksNNY#H+GZ<-3RK9 zJ!Rzy613J6lVXf?B!tJyeAGZcqmy>v>XRPuO8Rh|VT+^DdCSi4wto zr1{^Y8%In}D)8m)s2fx|iZAz^s3nSSd3#ceejQe2ayJJhtnk;2U_!GuImnx%`|x2@~;wT!wp&c~SZ7hh0vw>jZml zaa~!=4^{mmG=^8CPV6v)XK7PjvHbUnd+a_h{u6mURJ|v<`LeG4pIP?qXxVi5=notH z{>Qh)u+96-^uPHRGZHt>Si}W*LJF?c0XXQxk8fW<*LYr{3!KeOkS>?5fPXO)#6i-W zRva*YAz#?q?}k)G(jI0`dxg8N^I|w%;6(fRGGxE&k18nQArS5SUx~F9;oXlM-i;!E z7GT^zU^>LVJnE%NaK8IkH{sDh*Vw8|?7b2fY_A>?(4&lbY8W z7eSTlI^fO&7LSB>(=q;u;~fW^lY&a>sY#nB6KS)4tyY;nOZ%F?p!MeXIBF zt>`WuS~vFb)#F>XB7T|*62DGzbcMf#FOZa8`+>?EeAK%b$e*5#o)mYep5%A|)e>+x z8QxZVdl}Z}0rdKX!5xud-YYDeORoxu(47;<@cUE&bni>D5T2t zV$WX5FWkZJ^wr%t%PjbhUG!)3#b%tggYnSC#MFuVTsqcIYe2Ah7?#U{(HVl%0Mej+ z>VN4fxNK|mH}&34b??!<=!`>4CVHZTA_W|#7WHmla@i#n#(I&R#%8?BdSnB$>X_mm zN)XQNceORY>tiM?0Fv8L$n4ADF1~*_AaM0%*!Q@oGpqJDzNe7g6KDd)87c~?qO_4Y zeCxk4CjG2bNzMk!fSamGeRWXWg3{9nG;)Z>yuhh3&6b(bJ~|HB^H7z?BWlCE9~7Z- zs7Xy@Jq}uYiI|-&6A_JK)R(au!xY6K1F38HN8uz6 zX6!ut^|Hyh5@ea|7fKqn{D*YLBim^%>(-I;^`ExS^D-0i?tYdYG-$|pR(v-t+2G^( z1(9=~XUPWnFH&JiNEocuT_MKmX8inOg3;Iuu5C~v65xcQInL;S&pbl#{NOg~tdx#P zjZL)qA_8STVTFalut6MFYP?ESK^1;$V}AQ9O?Ah6U4OgQy7p9W+V6UpJTmE>&R%~U zCV_0buT-1Z{0w+Hbc%_`H!;d}xdb9dZ3MGambUqaw(*%ulojV=~E+cFdY` zfr({vvT4~XR=^(&e31)U4HgtclCKoX&a1kX4oCl8&ui>kYXtmLW%UiQtPU1@rvxN< z%&^uR1REu8D1ZWPK{65&SkAN-fuhd%WV-n#*J53AKvxv|*rEJh05kM&W<%2RhmRr! z2qhI?M!f9n&tImng>($w6gY!iFievTH4>zLjB0(2t|Df#qbK@DOiRJ0W@ct@?)gzy zA;kMs^9-6b{eXOyz{A_N+Jg(rhwojdKaS>7mlAZX*Y1FGP~SG_c95SvSkE&0N^RL# ze@X6kB4r()jh;_XlFYIS#QZqm%GeB39FglM$?2viZ|dp()C_%dMdWdt>uYtC2lwa< z;b5_NDyY^t=#nNFbdUUW4c>wG#;3h4n<8<5!RqAo)H^LUCT_kPR>RRV;~HFxUhie+ z)1pW7;oqEwqf2U8hNF3x!HzlpAa|WFMl8_qe7i8)y-1*t5h;HS3E}~96Bw@{$H95x zScegsj-9wX zrwOES5eROeJ|y68Da`kzIj-~N`;rv~?Sm=I4tr^DsOXOf z^$hvh2c53bLi2>pfJA#xnEm-!KabB$o%~W+npQVLA||SRPjcHF@i#cA3N6P zWfq9VZt(E>aP$*ff;(R1>u@7`VBt-KqW?_MvX+#;<_Oy}1UdqA@)~lk(wY@n3aWnU zi=38b0QcxC6Rv@E86(d0mBiKZ1Igfb20jq02!dzD1FX8eEy8wkJDmUQ*h42**F{V^ zuStuA08}0`VStTAVrwCV^yfMwi72WuN0f!#SID08Dz-S^8SwRbTDs28BacCvU)yo` zOqLRD|K+xvAEjXwb72LadL*1bYF|l}Xg$27+t24xoh489OIWzdPFDEDp14^@zb-!B zf=wQkS96zhDs9*Ap$cS8tge6Coma+syw)3E$V%AXinDog7I)yWgn4Yn7EG)9^t|K? zoOrugLz7ho1`w?`_IES$pGXu;1%QsVS^ge2n)a@gb6pB+WM2#MhS(wtWw|vGPsop& zZawnI=7J?Yzz%fA#>AX~At#kx;Rm&>V5|VT2z2iT77DWbUJtT-`!c+cvEKWQTiqNI zNPuS?2KkrlXW@{hF}zCi=9R1OY| z!kapkOLCuwg*v!{vxv81K<_ZI-WCP-IY57l58~_8MMfR;7)Egz5%R?Y8$mW?jAT2+ zRoiMP(h|DCu7Y>BFQt5cc(Mn6B{0;p1Rgr~9=_%sJJ)WFC=Ynw*yq-WUbL`9Bl$G3 z4=$qsFbI05SYENOn2|jK9m_J0RAB%O6oalC0*%qnOgH-K=LN`0(x{ zwm?sZB$Nu(VZZJxl_z%3?=^+5X*eS3Tv-MDycX&K*`rZ`ew%Vkj-Izb+&DAE6kGcek4}10p=`SmUX*=sOI9kdd zM9jU}i1g`Am$cnjXLc=;l?U&CXvC5<-v_*3Cc=wAFivRsD*y@w_3_d#0-DqhvxWnA zxE_0w_0KX0zQ;EBKwg_QTxai?1n%2RU-x3xpJSX>4rVGHuD)M9lXSXm#JbUciKGHU zt^$lpuK=`kL*6QIp8j}>HXl+M`P6CVU0P^(i~UmNVrTb^d;d!WMsSx{)tY$H@rwu- zPQFMO%{5x zQyr|%-7O16Qrdy9`80y)Z$`nWq287Nvp~3t zZVjSuql}3pf;Q9|R|k0=>lg;iJn(D;u4l{Iur5aU8SVdiJ`_!8FdAf_ef43SdOQ{0 zNGP;M@Q2D`heZhc8<;GGm6qW@E8ySlDFC4u#-4<(E_pA5y#_X8MGk^FCxKe=X)!M7 zYpG#Ci(TKnn7Q1KecINIIq@8*g%t@6f5f36$FQ8>ap3Ep8+?=X4Y$HY;&_d_5&N`d zErZi5!1pd1eyJfZZrrigf5*D^35fSf{$iFYye;A{({Rl!m zq<$p%+=^Hb=|MS5-1|h+AE9Zd?`BEGp&|atUIU~1)*Y&Qda2(h$h`BgkI|{Acg=hk8^0z6WyGv6{`EulU~XrGJ&r@3 zh&RI1)(HFbWB2d3oXU8R;eFnjYo-hV;r-Vd(XN%U0N>HY@J7=zC0u9`{CgimThuc6 z6LSmtqk)u@Vq7)U6rBzMqBx5{cay<+GT4Z@0Zs?-pDtE;L^61G5;%TrfGuTAOvN74 z9cdCU3|b9a@3p_^-oSt1oK`KdxL19Dvg|by`w=RS7#+QzIrYTC}kXqrvqhn@TAk(iJ|-Zxi$e~-dEf6$BDJ=*rb46d-{pi;<&Wtf)3 zf`_!|iV~WZJh@=^D6M%z{M|f&z{O>84(0^27`zyuyXi%^b08d1VH3y3s&%dU)Io=h_Sh{2+mL>Za#>Y^)Hsa ze$v*V+6M87k!qwMgB~xw&JM_cdO8ScXiiuJdXQ6M80dvxn}%H?m`T8p!vHr7cN5a} zP~R+? zR8kjrQJ#F%nFGTznl3#j9q6^$n~Dd0rONYGRZfOiT9WEF6b`=l&(#TweX)cHw@0y> zpBi$6yy56APn%*1sZ0LR^$1MA%|iC(rZ%~npaZ~>yU1^gw`>c|5!q~R@%|Hw;Axj? zi5rNG%(Ka|(w4S`hKN(fQg3~RHfp&=%qG5_5`|sDf{H-A28Mt>Sk<*>81VZjLxBEE z)_xEyG7$p0!HTv~Qg;@EIO{foC9Y-xzrll+|!CSUy@@Z^d=?Y1Q zMCbP7Deo%ksr5AJL(J1--tUJbmNvK8{m5;R^<4>@+IYO?mtNG@QKE~$dab_lczL&i z3Z=ux7yfPsi>y2yDf&*7Q4xI|x`2+|d>}^d275UIb4+tA#+bFZvAR z8K0QmJvog;j~XJG=U;|fusS$2fTEBa^={0B6u>To$wL`1VcuwG7~q#R2d8d8vnUwN zmF17{2OBC6DMz~^p1;2wUYgYe-1wB~zovwfAjDdD^S^X6H%)for1Is(9tw1my{8&? zFD+@}wbMT5+58Gn8LtTn7&2i5hX@j;K-N#;aA$C_wV2XB@+QIW2TGOJam2NSqsGTpl2KP~Y@xDl zQ-3(jl%RqsV~2I|C_Z(l3Kbi0Bo(29$#?NzNUJ$o=uU6Zy_`tgenC0Z&BqYYR8^qT zBz$Y~?}~w?`pMtVR^~6us>rHYmW))A94tTG(ON3;!*dnD(JsqcDj^f8jX)3&#A%oz zd|As10S3K)!qlkk4?4BNKVvUe!;iiLe&U6NZz2ic6JRXTGzcWgj$Zi`3r|4;FmMAu zHj-Yf98|Z#?5fDJO-l91;M-nz)EMx9>3i!hoCPyM69C@=|Dv0bhrkH(SIefuil6mR z09r9tlKTeZ=oJt`^?tnFeF+mI{MXw5aiq;>i};ge6wM)8{<8)(pX1o;BzoD7`etwE z-hsPUORKx9gYn45ZOiFkqMD-x6ejSUr;k?F7WjVpP>-<}Tyg|DE?%dwSpD*0Y^xM=qaheB~{Tc_~nb)hd=dlM>`LPJ`Z4>=_R8N4A(_)NA zpL&%nmYSrAE!y$lT8aJbl)={b_}2-CLgz0Dl$~*+rBbXD&98FW&VG85ZO{4Cw#v!% zZH*l&r0liF<#@+~uayT>vW6W~++3o0^1=n|)d>m0$^TZu$JejBW)I~QI2vlKa z@UI8h(tSlaU;;|2V=D}wuZEC!3Q&}~3M9e8@Z_m7XgOD5#&dul`j6^xd%K*t8Qx8v5<=bRR#i-Dpzr*+*Iol*%(&l@5>+NYqf)6VL<3OC^z50PnYa+ovftB;o z%+arBD^VT7i6>Ts)?K?exN>ROf%CM$7ORsXfBFCv zG#N-|j_%b!7X1Mp9-ddadX(=XMY0q4aB;FyqU0*~n{C>D5aTN*8C>g?y>RnB8MG`7x0yaLO+*Kdj?RGC zYh#uU&}W$kI=3Gn&4pz5_j;NCZpT_^mTR3}xr1I5B*!^bvRY~xT$3|cWVoS~5c0EQ zd2XC9-k@AZgF+>*Pd^D;Cdq%X#7fmhgGfwaz4uqZ(`gSA>SR5N%n;zCNrguCpLugv z{OLXPY7rbd99^*ju2ua4_&y}0C4B*)CkUN|gk3RBAR?1Nv zATCuZi@r+e^-CaHV>a0LkG<IQBBl0JR)hb?szSHo4G^*KM2dazD6_z$CcjMY>^Zw{Z_b_yYYt%(yJu6tC>m_G z$F@NgB(+>hS$Sj?o~V%d2Ud^f!NMCXUj{gu$2GDa{Ek}$J8Aic&!oky_|d(@la5wp z1fc*skX`zX6`hF2g34;Z0#N;GXvS+pbMT+ixterq@ELZWzAdm1JAoch=Rh7}UyN#7 znwT!>4l5ysu$&4P^DAVG1kh?Q2PxCJWKcEde=VR#JZ3oq6D&7)Xit9!Eoo7cQY3pz z?4d7_%K7BkP)q`*_B^`I)LHf!rws5iEHaY__8xeKrNDdmG!FrCv$(mna@Yh>KySI^mE0aDOSW@mkZg7=C-MvpODB8|S8Kjj_RTJ|Z?zXt;4g9%-zpA@rM zSH720kDppSc<$DL`+ls_rZ5|^(A^Xsx7}=(IKe+nv_A^Sd^j~^H{Q3-)zes!+uMU8 zfIz+Uqw`X1BrfAmmpLSr;8Vzt zJg;+70>p1qJ9#fotnVH}nlup|vGshpUHj#`RsWBqD-VbA?ZWSju@0FiLzJ2E4j%0(r?2!mh?TrH{_r3`^hk(NSkc{QX-c;oZT)LZe}_76p^=>JH*Q<^;X_6+D( z8g%2!+<%E;_m$TzG#%J}yZtp$flhE^Ga0p4UVTfbS31?7@afx3-^;G8#lqSr7I&zNSk>}MpYG9&)vsPEQutsx*r$Eqv8xt@5lwC}3H zWo75sMx81PpNoDP?VpeSY3WVnBu0F#n;4pU{HgteekQdpkNxwb0IUmzE!c(BlPXA8 z>V-Ze@b+=)>&}4}`CRR6?Bl1s|MdZwV*J-)G$nE#=>QmY1aI|S90m&4bGN3R66YyL zg+*=_Q&Nt~0pyzf=dCRdQX8>cbxm})vk)ri84em2a=plZbgf=$*X0v&twULD>xVUp zyLfT>q)kUi0QyJnMXJG>u5bsleVAR8Ihbbhhu0=?zv9`*W3&)@+RjRX?s!foyE_=( z?ZE~L2sF``6+nioN?7b1tx1;NYRqfG2Vb_BU-$V?Nf#>ZiRpm~<PIm?w-M1pP!X{yHjBh# zZUbGR-dAeVM)wld61L`<2TdbLnK$bpC9aI4*fglx7u<7RQQ0hiE2K!q`LKC89{YQU zrrm-_%ng001T`dJ3ng<1s5seV#RPdRG5# zMD@6&-qd)yd8bGfdx9x`rVi}+h4biqgzELbAB6j}%Sz+6T*7MPU|oQ0uVld+@pQsy zI&*ZGJ+nbD>;9my5KG8j>~)9+N6}3HwDHNv*Ts>?VUw)kA@zNlM=TWgXmHKjXBe)# zy5{to58s~2D$mP;S$#5+{)w|kXyT=ET_SqRO?$TdC7$9_e=5e{t^VZJL#VAXg*ZHl zeC5rs`!~*SQ5yZ6NW))hLcd8o$W}|FIYmROTWH?( zaN1}vxy8Jxxou}fa0j~*`KFz;pBABiZE`qsiVE7y+)=mTp|v}mQM2J8U@fpC7TD-Z z*h*i7Rncw|WME^#A$t!YdQ=W{e#fs-E+XIMCs`q<0QDB^cn>k9q6{f;?Ql%ubCjNN zQ{M-^J_E}7y@LfGe#RU@>S@e?{mGgM1CmB7B`*vHKv3{01&$GH>i>J>g_E}96YiBpeffv6O0*F^0M~6N4*2YWM4D za1ZZGGAe@aJ{D3YZq2?0i`mQejHWKrYfgPL<0=n>?o3-cV*r9EjD2)zi>3`%RDwSA z9FWStSTte-Kh)h**ID^lo6mlKKhMA3lP%d2OkCYAO5Bv0&S^)U7sDAc#in!Yvtasn zhC+ZG;0fN#u(Mv1-JfqcoM(xSh%^uF4DVH0 zeQ3uo(<7v0j_jSb&<>=BDJh;06oXNIM_nFeN=mBv)y_2NJ+JO!Bx%->m@MG+OV|D3 z*}pFR5zvVuvu96SwwP!0Kh1-Gc9a6M9z3rk~hnTiG^Ft>byxcP8nqdm81d+bOD~M?^Ku z&a%2}bVAw~hxeHUz~ zG_9{|m7{!=t)a{Hh+~Y~OV1%r!jctIbM}E!n+9vSMX*TR>QXpl zt4q44EP^t>0I+k~jdZr~uR=JB@%Z0`Ztr4qb8)fJ$LxDgai-6oNSmQnUb!dRTN5?kmAbZmHqih#m>BdU3-ILm4WUGJ|yRwuoFA$C)st8M!O!)TbeSeV-wKP6V$cvds?gU-*VzmiuqKDf}_YQhA55fq@l z)dlQQ=c_FUFr9sD6;$r-+!H=|E0clr%2eU!|Dm5^-HzOP(w*Nn+;_@*(Uf_O4uq;A z<|0zVgGxw{Sk72R26r?-cVv0WEjti7hijryh;(mcxa-bL%(BPoQqJA@mqlC!(p_uw zgWOXpq{a_7;s51`S^P83F%$OkdMK$>1STOzbcs}>sSTW^- zb|`*iyM6v6gBXwfVY9$|+D+(S@2B^oj&pct%-U_S8X4>bFua5I$Tns7)6TUavxo;6 z*aCyLZA9A0{rqOGOma?tY{lWD=Z7mS1@s&fmNlv3X*)b5Vqxm)-#ctgwDuJSyo2I# z!!EbbzS#$H2|5vqv9Fvl(DAUI8Cn_S48a%U0Y^jH@g-`^v!4_3{RZ!+r*7B2FCNUm zfF_{=2Pq=<^DZN6k=eG8mtLjVe$W4SOp-+MLB!u3_XYg9<>bXq7EnHZqX~92nOnN08$J_8(t#zi+GNJ3^inqr; z#I{Ew-QOMOoxN@wSe}oh25*GWf+ZdkUD?N|+v|m)gC@B5hKAYGOm7+?lc5G-$0Qkaa19M z15#6^=b9}kipojFQI5VuWpt%MovL9~H_uRCE1U(E34|68P8hk>d3ks(&1uN*K&9yD z+OSg&>yks)3vPrtugV?>It+oVrzZA{xXQE|AUtK%%2>?2v)i|CANy{<6Vp8hcKk{l zl=(_i?t%F|*)&N{_J-9o-1X`sL*WG|0^XhQ>8@2W#*r8ir?>w25o6k|F0v9FlFCtu zh}I*|kr9b#-?R1Rvqx<@e6(bZV}hKqHkj(4 z-k`pZm+!A9g`EA)37V-_z8~J8l4(~p_FwbTvp`xusO3-sh$%dombyy72z($6~PhW8vYP?#;)Q#Fo|GOt|hh zb2Jpb_lp@Zn}glbFIpI8CoOz z*`8nYLgu*4a%Ncft`F}^y&L?Sh-EHZjdp+UcG9vs`FR7#JNbWg^Gh$OVwPQGY$!AY z2~(l&`vDAZ4|++Y`+bcX;|<2I{yByf_1H)E&C9Rv%fKt$eTY{oy@K5u0ht!)F1X)s z^#gVH>b=}7y=qIk+r_*50EKWpLfR|rtc`F4=e18b)(>(wfF5%2sZwn#&-#6a6rf?e zYYYFQLl+%YYhG8l@Dd~^QTC;;^bV5DxB!(m>r+EaX74l%Vhdczf=78SD%&<}D!fQg zR4mS7bv8NAzrNX@{(fH+{TzCJ-zw8|D(4nchVC94G{hj_SHcl@*&&W$N1bs$ae-EU!rd7&KT@}x~DIS#Bu)2Kd|}a zHM;TbEV8typ3-ys{hYygz&x^`q#jxh?6E?`BE*VO$vBF3Jg;LD?zZ^vlgQy+La2aS z{{>M`hF&Sk*bC{J$=*Mz<}@~BH8TN4L_`qf^`U)&`^$W&t54$B9|?i*vKQdy`<& z&v2l(GIBN=R=4Bm<2SchA3;sZG5^&~2xLEG6dg+GSQ%F_zo32$5pl3UN%Zx{ceZc1 z&Nh9(sx=QJ{CWMFuV#q*xzpj69Pxm0ZH?9AA66OL!|*rAKVoH6fbuvOAtU5#h^QgcpnrU_T;t*1w;~-U$I^_qh<2KOAy(05AEC?gKf;gW7d&R5k2Aj$&qkEO+zHAJQkG+uDvSmTB-L z9~Xlr5pVhaA@Pf?I;{B3_=}$G#wKDQ&9C{;Hh7rh+$!nl-rW|7Yi@~S8h^YtTC<4A zG-egJNC%b636qsXF}jl)>}{k5EAZS3J1(crKD(N|&B8vK6#S=DA*FbPrl_QfN-cYd zD0SV5;~P2x880~`2yOh2iSE}6@TA)_4Tq66PXwl#;uqk9Z*K{#Waf2WYML0auqi%# z1-qb^-jiHkXn%%Eqe?D#H^{s@NnKq(h zvxFbp2}gI&fBtml6~%CmFEX(27CMi= zTuZgCZ@WdcL=@$FY`9Fp_&3F2V4J1D(}C=XAq{(q&i34) zvv<91fK^NQg{)5vKJp-^1$HhJZ(#v?R{2`I*8F>QdCVn9Mi}t8fW3;_g;Gg_GBC>( z@w|r{P8c=dD2h{@Lz>=l@+f0n5-$?4yFCvD+)rwj?XLRrWvJe&_Acw}Mb3j7%e}&C z?Hqgd9{}2<$x~feuY;jbPIqr-^*4N#H2NrP3kjAnnle|;^}rXt(0kE;?)tG+r+Qb1 z_GUFc>0%QwphIIm1o5G&+);F9n4tfOJ=sI}ps<}{OLSyCy9V#Oe(BAhlu{`U+&gn( z-R+Eyq~uNA)SJ9G=ho;dmFvBSyeHI@_?qB=<;`+HhBaUUbt77+(TTu?K4v{ zIKg!2whN!l5xJnkN}&@^W)Z8Fvsc@h9~c`>Ox*%@mMZawIM3w%cGYf86S{!9=;5_J z*R{xEhUG~k0cd8(a64D!xuuO|Q+>Dfah;0Cymw~_^dpD2bk!te8c{8EDZUdXSMqn0 zLf=2e9Cj(iL%=Wm`mfWssJ3jh+e`F617BkTa6Hg(4{aCIP*DR>O;3O!vfS<^{DT)! zjEdz(pelF~u0#)vUI60xUeLqeG9U>ckKQ*SUByt}C?DS?@OsnMC&D#Pns2!2SQ}U3 zS*{F`GGzJ1D(6?vNx+1*sBpJ!{4-NrG@KB=cf(00`y$?^eii%Chh27x`!O*o3yvB3 z2iGh`Q!}?gu9ma4!>6W`O#M;FveHPwWB5l zPpx$*lDq`P_k4{iZ+TUZdDlw$QJMs<|O!e}y^NGc;B^S{Tw3cX7%Ws|S8F#9ek9D74 z-a9}e2TlET9EM#dL4ZhV!;cu;!;(Yns9DLIaOn4(XQ75V^PcyU-EmVlk9s`Y3e*p~ zV1wX=$LLGio5T7X8skHs4_+$Oj{BYTRwl+y;@a5XA#$6*h2Xnvvi|gZa~ht_T5val zr0lt84LS~7un{FsBXjF>>s9vE>lI(a-e!j4P^h8f$$kJLC;@H{HKYof`7Eidd|A|6 z={5yaKpM22DbaU^2SiBvupeX=`vW&xOuEs=;V1rdXsE5tP~*NDknFdGQxL8YSTqhA z+Yza4TF(}st0gzMr-}CVRPv1pOfcTw?8AMMIIF5+NILQZ`!nyWn`V^WRdPMmSO&~pYn0L zgKaviL&d;HiZ=X(=8#QYH<{5Ze0-tgMc=&P%=X~*z!3cJ|4cuNK6x+e%?B&WdAExB zAfkf-#Ek?AF!v0Pmg#zXCPUf7Aly}W0!7Kt)8r}dMIk!IDjblt1wy&y`cP$5?_M3B z8M2tuxBzl94Z?9+c>QB7cpoY?6B4h6uO>Ysa#*d8kB`Hnpo0%4=k^ekeX6M|%?j#l z;1hEhr2`%37PBjZ>|RAzR%h4!hHpIfWF8tNsKIJW?F!qb2sNEIr`~=WbY(ehMLWbb z_4Y~6ziSRVMI*Mf$(chodb>ZGdV1Iw%J@hH+kA`S4a8eq!nTn>>Er#*$!0DG?W~$~QUL;ri1#FZiCtUWu_vsuF180o_^~Auwc#!;K6auU zGer#MXk%#Jgi-tG3O$6!EN?P_2kt?u3Ocds7RH6x!=Xfj&(ZdA@oOGsp)DJiKg01% z2l2Y;Hn1PFQC|1WH%y%0Lmvc9CKjnucf)GlK=dX0^S|C31tBTva|RBBT-tea1}W^2 zPmv%?p{hBSeE_{4km&ZfKnSI!?2O!$@)JSwefH@t?G~0J%@gESXY(~cxG1U6s&Tpi;xu|279pU79`KW zXiZ}>Y}maC;`06v?=nI-^rybk;H;YKoTs$qbtMfZBLp#>-!mT`P%^d>G;@vSC2<)( zJ|`}C>7)%Ll`Cm};4N^N1^P-cpIyQ}VFN`8u*$_R@rUiOBtsvBn-gAvWFL!bfU-Q^ z*=r+2yud~r`Cb3a!E`q{ijE666-TP@%rYURIikaab7vMIM;WqS58JKnmi&)fuFfO! z;ZVUF2*>r+6^MJvv;`6aKFvuC^)R3f=lAF7J0O7xm%C$XnG{#1J29B-M^9~J4Hb{k$g_9&C>F`A;0sPMO^zj+*oX< z1~;$g?B6DWl)1q4p30E`3}SD{UB@<}G`-h>t+zg1!O|TLm0v6AEichfHbtgd-Y`ZM zPDxlj<2g~5no`2$42yQ1P%y!oxaFcJ#zm&zrs01VysYmF|EG$!&l#Nd&C9Ge=W;w_ zuc(R}mA$J=DY-X)?+h*+vf$NQ9zi^6C0X)fPJNqfPwF+6;jBKb(Z#K26^-IJ%ma9h zfvo}Jb-A0Zr(ua{__DMHW3RN9_O<<&2j?~OBM#b$AJ`^y--Lr)!bY7wv7J1JK~5;8 z6Ox|6{grT){g#up#Iu#&hXJNrP7)`jm?yrIv&fW5-v|BYcSuO;ea`v0xfbU2xvXV> z3{4rf-q^e#Lr!rl!DbdUZgFH+D9r379It;43o`KaH0X2~HN7|w|53X|IUlbJ7a&N`B>eU4 zQw^4>G;7rI@HE^vNBdGtNM+HpVALqASkCIb)^gs{k3IbcbPs-PfniBCxS{;7beB@Y zt^S*pgtpyrI)Uu2BVrmMr4M72s>#aZ%e>-ypyO514!mI-9Md|3JF+9*1l#s)HaC8| z)BQIXHnok`dAg|_+;BEk>!N+y}C^5lWh8IoPKgjePAng5X!6JOJ6J zG3hCaZ}>t|`Poa=;MP8~3t9VSr(iWQ^7RaV#%~TiT~gw|0T36LSu%4mVY{jjW+qFY z$vMu-0g`WavAx&tkD6L^^4h+Zb+Xck%P00TZJ2wAVRvHEGj?%&o!Kkg3<$Alh(wsQ z%U`;%F4!*FL!?j;9%t&$hYwQ}$1Uce^^MN^UJuR&L2>pQr-86< z-qAXru)&q9M=n^+7-;dRgma?$tq2 z64Tm7a;4|E(bW|v77W?*%Pg+=!eALWf>ky#RLk3JRF{f7D%~SBHRwd~8udYRN1~M>Lg5!CPY5(efZgS92 zmw%Cb#r$>T*akzxww@Y6>-`0SlADwc)Se6zQt4F;#RA#k1DOPPe3OyW8xk?V*k1}4 zwlBhPfacrnl|#tWg8WYKqzU_RMG0^1`$edYOCg>M`p+p-ctGeNqt*&p3D-y-NM}qp z!C-63n9*}$TCtwjud=bkEn4W|5wDE_=b(m#oFI12uc$@J!nc}fkmydXJ9V+onGO?y z1#*e0?DdQoel7J&eDz3{y=&M=CHAdTeKYObMO6sZROW6y$qJ@iMJh7J55Qx{jgB@z zR&=sAo{RcOweQ$Qt<56MU*Vy>1!$EUe`?bYSt9lfa2j6pky=0jdIk1Ikp3M;G5&Bd zdNbH%dt^TX8o>{BAj0Z>!}?5sX&B-<){Vy1+(&T$z?@wXrXc$4v=_2`@q`BE1xNRK zEMho%MB5?r4S>udqn4a0d=HUh3eQOrsN_ya0u@_}6y3Jw_-Bnio3o)TC<^o+Q`q4- z@gjgyOiA}2?>zIb_7I6*2{u`95Gxzra1w7OU+Q3j z(g!>*O6ep97bQ%|*#VhBm4zU;3Lmth*?y||-d^T*V9x<6uY=Y5NkqcuLzkeO>so2h zYAqKMz_PiN0Lh11@XWS<7SYtaGH`89x76nSdI|`asRaLgc2IgUogDd$_~0@<{H_0s zX8_$0gxMb$NolOEEZ{7wvrfzcwNY582i_gAbaF~E&R1)6m&Q`oN|=hXQ2V=^;S8~Y zJqbSW)j=vE>rTPd@SDmz=yp=8|r+(2LIug=)%i0NMH;O>sPa(gdCozGe7VIpZX*hU05-1P?+NL z!Rz6&H&LcmdgadWr+@)|VD&`#yt)fcGZ($P;usUCZ9EOBqC5AeLFk`;q_z8gB*DVH zRNNKh>&h(1)pRirg03Kc+zE#q-OnGP4CgGwJ&V~(~-!&+iox>YJ{FLB)+3{u1QX2OzkA3`#Oi-@aW3Q- zgWgC_2={9b)@GR_aI?1E7j*Z9exQ(99S1~M`0wD%o{%bEg!8O--d!6^q;J^7rBDRV z$s0#YUZKOeeDfmzhS*_68TF*u-GBE8-+yfn2cHCs|bK-th44oXbu{16>K(0Ek|9fD&J>O06buQ=3dCOR4CA`5??cupgJQLO$ zh3S4@B{x1k1JeD!L3ATB@JKE-z>!je!u#porW(g&KB`)@8ZY;>hjNuZyenE4UHZdm zVdUC0gVLz8qtex;s4g+HBUGpo?I zLflfCY(Hr+@EZuH)dbU zjiC){yC3xpBarE!mCfVV)NlQE+Xkm~&4f8xjIVC-Mg$I+)k9z3pc{ot(P^r)$btgj ziAs23I%M_|B7XL@Cz$|7?dB&ke*yd9u&h#Evnxwk)P|MC9?8fftC`yiFBAx~h z077%33(=;ybwm}f)25l|ejg8cz4i_@$10}SU`6ZdN%J38#74alW-nho1t_&I>eE%< zu$sLu(V?S*!DD|d194F7B7X6t##hU` zd0;B%%9SM<9&o|!M{gZ9(->~)AA{?T?>X*WVH^yJcWP+XyaP&W&28)@h|2N*G5^PF zwJJU2FB^m?sEv4lMF?777~~W7Y`l=L&YEqTStJBxWWnuPTirc`q;0DY=r7wOP3+TX3|;@!J6K$IKg)xaFYZRp$--i)Ibn0ZuWv1Exlk zcW%DD$^Kbv=ymUU@!8J)sBFJ4oLQ5}$iK0A7!H%&P&o8rk5C%a%)3mQKm{0vL*2Z3 z8tH(BafJAg-GHu0I>mB)y13nDSn~}8Uj*?|8#H#;)w=|_Y>W`NvHQQ7ivDhL4ny!E z;342^&8dw+8*Hb!ZRL~i*4mce&QSl-&ZmM`B*YIL)&S3YrUMob-gU_Q=v=STRUGr_ zoh*2|kFK0Ze7%rEP28ST3R9mZ$i;=bqT&m!3n+5@UI`K#I(Y*o;{kjv^FbfK?`7=r ziqVeFx8heyP+(Q_<&7rbO*_Vgi@fa)Xkgof*A}&#Jj!r z&xP!{haDZCo)4eOo1s)*8NPKe{71t*`Y(XxLhT;;bV6J@B7pml>)c9G z%je3}Xg^bFd+Y&CPn;_*ebF#Ml{kNhjuYNG8S~h zp`W9p+hv^^)3P>T=-?GJbF7w6?_cXM$CQDn>#U+Q_S3;{x8G_i#!A3TR%g_z7PAX) zh3y3T6Dx0lsPTz?zcC&Adh)_Laln!7qF+#2@Q4QJ z-t!3)SYn{Y^7(RI>LJHezE}8oV5;2{h~ISuOX?Yn8qk_GsD&4d?E42fOAMP=w7ALo z;`7KRO=>O5{v^&Dsg&vmA?40w2Be2(X!kcTpK7>wF6AtI?s}0|o3+=JOQH+a(49BetTor1d?&cq zDEC%(J=CI=Q;jaHd5K1B4mvZy*oo8?Y~Up<>WWFJIvFtx``9F_RwzGp670$B)#s4V zEUoXedWGi+dt-n$U4Am3d?RPWY1RcQRnh%HnIOB^~m zOVEXd&SzCR^=6M3G{N-qh~Ldig86P6%5gPjcHn%p;A(0{lo4=Oj)Q;N!4!^MPBIihX{a5E&(z316mYitg#{j-2okiNti|M|KF@VFH-6CprggrzIJ>Xvb@h-L-eX zZ@Pc&tcPUkL5BHLPw$-ZvpfQ@RG6&?;1)}(zRB89g0y+E3xFtN9)XXQQ8pt(z3g)Q zkOl-eIFN`OeLMvbyP;k7X}`H+5vqHE7-a^y1*I-t6~N~Up)%HFM{fo3v|S!OKIvKW zvm@HUs+8x3D}8aa7S1jiIim4icIkyD21-FR;7E@UFN=2>31!0Z0|2z2MV$xEn9FZ0 z*Y3?7nM>lsJr>N4t?9KAbIWAUbi5;P^UnfJYNKq|wB#}PH?fIL(^-d5P{NRa0{Oxu6?Ie{|*Bnmq z@0Ed8rMOO?rv5y5JSWVxVZ$cO7T%z&efgyQ5bNnd}D-N7F*#H zWF~9?9AEh9&38@%uoG>bfR!)6r^*-Np+ny}@8#&X5<94q_bvcC2%kbHSM46&_QTh( zrPX@aQmXdT3=f}cNW^0kNAVRO(&M4aI<aOkqk z(*$8n*Oo&8YJ2uTKc3s@-#=Z%ZytU-?`m32EI)aov74ed3N#WvsSf67U9KpD zyX`*H0ocN0g$BSe|RBuW=sWQwWeqwgLBd37j z<~9&zFQ&=pLihv^q!O?aeSVU(p-aj4YSB_whX!X|ig)Xis3%!x-S`JEs}R z3TfO_(od912>EH>yWW)KEs#aCGdeD57(7R#St}pop>T~xoz%D&5zjJZ36v9+>dk@V zSAbt8_kUy*58;-Z>U|Mq9ORF(15rz$JukRd55@nf1M(8?unm?6v_KmT-c>$%wZ}je zRJ^Z|`>0sMCi%{C-O6{>9sHExp8lw%``Q6)C4YJ3 zjI-YEwETFcOmLfl8vJ9!eV^#+NIHAm>+kPQ?lT^q163vIF$G}C_0jc*f1)DVS%4x} zXVZ}<&zM`P)U$46^)eaKBxi%!uuiOhNgx74wVyH%cqOz#~0J8?5CS}UbN9|?(f z-%0-D>ts{F`|l__zHw5%aE3Yl)M!lzVAtL`iEVDHnrlYQAlEt8j$Vv*k~FS{#k5&$lxGl&7nQ=9^^LeBm)@l9c1poo}2LLR6jL zpIua0MDsX;yuCSx?D3UHw!V^5-r3Lzp)&a?8sFRLJ5gE&>)3N#8C3w3o_mUeYVRzi z`H>dY_9eUOR<5w+>+i7)4-ZGR`ACieXa#0b8FW*Ed3if9&E|-9=Lu+OAW|_0<)4*W}E)J zg?DFSUcK*5?=3l|Bl%$Aec62Pu8AK%=w7D$VSROMjd7JuIMJc&Q?!7Gnk)4vz|y)& zP1ga3tY`l3Jr48mI2^>!*h4-y)ah)Et$+FgtyOAV4*{C&pE^$>q9?PwzhNn@M1dKk zVkhQ|pFFQmG+wWEb~`mo53)pvVu7Wr;S877d*JR;IzaUXvz!O0>IM3wjY1*&@$R&m zy(D2BE84wyw}zxZ_y9;|je%=#b!iQB?1Hmi$-=X_M@XUL4jd3t07x}V;(F6D!_xT% zK^4V$7j3T}FAz_f`lL@;aUdUzsP<_b`fIe)G+l0`&ic4t`du{2@RzjN#ph=+R{19% zKHC2%|KUuNBv+2i(cFxh{pY-vijWT{>n#S`Qn>w#Q{wZQAV{m_y<7mN; zrE}7-3R|!@BpaLRQ0PaTADUNcBLo%fWW=lxj((fBvt-@hr5D-~u4B9*ednZWqli6y z&pYnz;C(%u!Cak^q1(U zvAGI!rE<02M~d7Y&%3`TTmHOul@v+hjO}f(r(tm-@>XG-#zr^vKQ%e_#HfrX35tMM z)0`?B!ZGC&S4oHWp*rE<)(ROkRh8fKB4e1t@L!6$45QEvtg6IdD3NU$HW|*O}@vf0IUXbO%`MFKuub z2RHjzAv;MfEUBW!Da8hz{p?2Ew>E+_+>k8slpHMv`;NfcZcOz_xa-rV6RjGO%w#gh5!-f&s`did{y__zD>G5*yp^kYEiWCqLy+sgiel)x?Qd^d{SxXd70AOF?V`9 z^8Sa@NU)w&<_HHmS^h@e5ZVmthSRW+gbokJhPIb$O+|m+h3;J6X z#jLP@EE3OCfy=I|Du)axt3f?Tl1wi5A7}x!|0WWIL;5pl{*k|l8Q2fgQnF5J<;9ti zjwMwIlYE;R?QEY0d#uczaTeI53y5nlAIYy@&V=d@l6sKFPDtb5g#W@K^(*lt`{9pjMlelc?_QgIM<os{x?xOvM?ja?{Gth5F%3|A$cpM&4# zioy3e*B!+s$!hE?vfEvv@Re8W%7)~uYq_JW*=kte$K&*dKI_MRmj=FI7)~vACxdFZ zyHnxdQ!TrPSFoC2Dq#Z}G23#dY~|IhR}I3ugy8nR6G8vv3!v@ zIzLOOC}QHra5uTGQ*r5*OVZWlBR~FDPzFZsN!_?^c;k8l_m1YBC#S_9M4fg-q{caB zob|(rP_B^fT}CY;u5GhoMxlHoiVM7dr7 z`b!Y=<>tDG?{Y=E4p5J#?7}=l?1x&PA@HI8MnYS-pKQwaH&$@iz}o3g^+;z0Js${s zVtZfMyo+HpLf*@&6@`Ds6%bxk5-r|_w7NkW+Y$mM+Nb7=K1&y~l*cu1#_6l}lj8K_mQ7|F+-ScSVp^vyqoTG{tE#OZcyQr_bB(ef zSr-LLP>C1GyRZjRuEhIWuON~`jh>UbS606(Npl8Uk8p(E*U|fIn{6%kQ)N|ETf5Gd zn{{%7Y-)^1D238F{{Nj8yR`|nVC-75BtiZa-CuJDQT8W zukyVx{Vh!+W7y)iWIWuB57M7wcXoB71unp62gspc%T^BZKe*9oa_(RvcS``vCE|0s zB~QW=nVRddGekz{PV}=xPF+rasY=MJba#b{`V_dadKFZSHqEDP0ac-z9*>6Pxc4ST zUHOvZ62fN&?Rb}ajEZ@TRFQ+}WrpB{gI z9z0E*Yy)GeQ^r@IYOY-u^lmEfNb33g3)rGl@Zu^_1vc#^?qv;@!2TN>8?dm6ppMAV z*>Tw7Iq_U^H8FeB1z~?f^cFJ*-A25m3pypV?XRfczYK0Mr`OeGn>PUvI0*6{)*CdYUOd+gxzgSP0#UDS*LM%vOjuN--lg`fTtO!8Q}7fI{a zOmPQ+eVQS8C1_QT=~To9uU@<%I3rU9-4%*#nTVZDz_1s*5}8xFtuR1n*}UQlfQ<4X zbGsFgRxP`6(m<&Hw&$)E@k(nf;4ZngLIz0rQ~M$Y2q%hT?8a4K@%J#VO*S` z-UwJS;bCozI6eL4IrwnNcZ-b?g;V$SJWN1p6LB+}> z`iajH`+VleiR>a7c=jvNgxiw2GeMT>IiWlH$jvS|{50(MI37-`hCIl`w>T9mrFhtV zpg9aIbYpvuX1>C)CM~N~w*`vc(8I~gtR{O7n?2^lRf3X$5h7k}Ev*j_6 z-*3MY9cKTbbUVu9r~4G$P$@E=vo`VpXUCaVWGpQ535E@_w|9oQ9~bcu%!ttA8Opm_ z?s?R{n){44B9Mv_Rny_HPF*J{KBfC$wGmTg-^`%GZQcV^6A-b{94AwGDLM{R8^qo0 z;~{}}>v!QkbJ{y1gU`6e%|PIDYKRZB*r$Ac+ug5b9xP;&0k;Y>!PZ!(N2K-{_Lq~( zf8(VCuQy|#%-m}xXg!sN4YvHPjbSr?&)+%M9KZO&5n|7Y4##BenbW8ESnQD7Z0?5j zr_}@}myAbMU(a>=6OY?l%fQwFV|zB=oIZ^H%s; zlmDY+?pb#IE^4-q2ubLsW=1AtB8>&us=AJYRFf7@T!hTWj*WmC*jRKp_w#AWWW2@a6o+*-|Pt+Us$T{-R# z7c$WI*wgm>e*4cac~h(Ac0WKSrrgmk4I?Z3`jmAtDrXB3UWaFkE1IOhw|;h?+jxb*&BqcS!yg%QI?sIeM|i^BKwl; z2`S7_BKs~SDim#uJ*gSlC0nu=QQ7x>nR&m*_m7LeT=UHHoO7T1zRx+mdIW*%r!Zn0 z&WKJ*lk5%;vr@cIYlXx4>O7I=tBnDdz&UhFV5tbPYlv-4X8Zem7Tb`48XDh@IAbJD z{W`(uV2SVFFuS2e3h6!SSLK3a5hY2){@VLn*|zaj(y-Op$;m0d0#(6+_-+juS!Oq- zaEc|WUDZlXbgra*e+$p|lwX2z?|X^UkDk=uQS}`)`bE#TO7rfU416U)dxDy@{}Db@ z3#VCb5nnzLgg>~MfFNKtKr{YByezLKae1rjBC*)q*!W5<_8hW?f1~|$i&+1P#jMI! zwaww-k#AAw)&%1=Pae)FdXbdslOsA7C?ES|-q-%?xh$KWs!P=9v%8VI#vcZ1OYuD9 z?oC4JcT)5P*>f>rV53<<7#Z?7gBfLV{^*Rb#9_pqJ4GDwNe?~y5PsdP-NTIbGZ09e z_KMaeua$}od|6USyh$?Af_yk@9-T!Ls6g9LjSA@|@|K4TNg5TJ8L0c4!Qtmht0+U&>5(}BfPGonJUqQ#-X8-29zPJU*Vu%ZHTM${6nHdxOq$O3sZm7bJId|cFQH)<`t ze51d@bt#Fm(8LqDhGClbd@`V1?RrA2YS8Fq2uIx&-v0KY(h(9$fBDg zf?{94Ywvj+BmGhY%^>c4Uh2ES;R(%sisWt*4U~2_&$Vm*{#+y_&WL?9#@w2P9y&-5 z<5;M7A%+v5g}i0x4w^yt^mCra&V1DO6+8}%bn*gOl#a$ao>@u(0eW?UZ+ zGU1#dELQQ*6aEft#|vhuA8&o7T%Ls=OO2mg|Ci#j=WNbBGj{3Ew`=bW;Zu{iyP-*4 zHVsD|zHP%ks+RD7!-K9T>CxB4dpJlv+94F!-+KNK=V^?!O-iZe#89k$kd%u@f8cS2 zKQnV~*T!xywK^$nc0FXrd+D%8dJb7SYpBh1qlysLN~xQkpXyO54$ z@y_p(z#MrP^^k*#{N#R5lt5b5{0YuV7|kGG|3&w%L;Yu~QJY3>cc*%d2bbuFbcA|< zUs#)r#60->ORQCIBCLUm6ow`k#eSL;h=xK}+({e4)8bE)Y&7#8!!IUCJeJh|-byaI zTT+WnNFoI~$FJR-p3>J@Vp}fryPr`8rBC;hq=YYIb=y>0=@D zgJk{b8qaz&mn9DAyKHaLL@saT`D_In#KZY~H1p~`^;d#$;Xhp3dXm(CTb#|rtoqok zWTSTH2uZd?6om~{=pf7QgX+z0zpDG~*`5!n)W?cA^mujydrj1vJVF z`JSA`4;V_<^CpJPPVo)$ZYjPKb?`GS#V>I*%ncTLYC3@E%ZP}bd8UVKJG z8N+z1?Nbz~6Y5~b*Zx`j^`otF%}RTb&2O?bMUqoa=((jFzV-wjivn2*lUTuk&w&nK zf}=LzT+m4WrVqYn_-wL)(?=>A9>AB;e#;zNAQqbKxi2Xc(gM%|H&6|{@CTp7eWBBE z%s%H_D+RFfE9S^mib+>k{neuHa->Y2wvC23RdtQIe|z)OC13zv>m(Q*BY%Orhp#&I z@09|v>~@((Zr0y-Z^=a_%0{Rhakk5Mux(lKx>CWcTQ`Fq@jZ90Dod5n1wRP@X*^?I? zf%7l+4}1<=Wv*Yttut8X-a{Z>bux+>t|1!xN&bx|6Y{Ye(%$=DaZ@5QxvqeV|^a*JoK!UQojwyu1cjD4C7c8l~ZqCxh3GP zcj)Ql5~6Q@Nbdf**VBY#c|{G(_2}L-GE06xhtWG*oXY8ba;rL@YR(9~Lq~Wf&3eSk zN%pR&)m5qR@?%Bid>4{4w9l5vVCLs4j-sWw(F)*Ju`@iEQsSxt*d`HWRWW>~3Qz~k zXxY884_d>A(5*-%-S|KmOzhJKQH77u5aYLeDc1thIK#L@*RkFm*|kdsh=Gi=h+ml| zP$6#d$*0J#2Em$Up^*Ldotv8(c~as@zkmP!TgyjcG=n@26~z-V{zzTsgOsjD!)5Gt zHRB!{?gtO#YTl)b6z;^!IC8x289tx3-l?CbaFSlWf@5yR72fI5<(!1;e74*L;By@Ulm8LaiDsx~>>?MCj`clZCj@^jOF zGrE-+pq@lFOq?HU!tALOgp4rRQRbKKq4fKTnh1}sGlJ~oP`|S0{NHjfM53~udk~|~V1x@<&O1azv70zAjx`d$Zw56Wa}J-8VGTRVg2{9% z>2j`>A~C4hezCrB2pBoGp-xa*Umm#H&zn@nJC0IAxV;;H)~oQFK`gO2*%%QE<|^z4 zy6QX%g>q10wvd|@Y4C^=$nX;oe%<~RU5G|sDh$&KT>JM;HF9B#ICG<{x>R~-r&U=Q z5G`E`^nwAH)peiuH+pNAf6{)I{$H5Dz(ewixsw2FKvTWb@+t_8&Gm0p@Fx1PNAg_0y2 zKEvmmGk`{K1}6^ua|XTuq@fnZR<7!449cs-khC|Cg}#_IYJYT!DELjF&UNJQi;2Xe zTBjvmoqsx~wA%6ozd@h|lsaqw6L-Ph@^lY$AP}w|N8zRMibe=M6HDraO9dqU4BlsZ z$9@WuB6a5(l?)Jh@+%0X>dQ+<`p@c!xSZJ69b#_};u4xT%45pXdJR#rQlgG*e1DMg zT^h!2K?cV=#|-u1j8_WaN;MKeYq>q^hcP&%1TKEvqdZL0jIJdZy9G75OtVl z?1z`9tQyu#Qtcc5?baz}ya>*SH~g4eI#zgl*ltS$)%Hp4o-0<0$X+{5N56*bi$kf^&YVH$s^O=s1Buts*ytT{@?y+I3#mT9z>qwWof*+3g%J zR0#P297sj=GPki(>z@aM4O5{|JL@wWhEBV-`eV{4scUNsIC@ZbDCDBDpI9c7&VXM7 zvkj8H51FzM^w%<9z=K@N{ zwG>Pj#g`j+|&6S9p2S^^R>qP{;}#*RN(5|2AFFXPmic@_KGc;Fk2lGYrJB+s&!AT#3rA|(Vn5e z$m-+DUb3H2p$x{$_RpAS0)e#c6miU{-ZA-H{`JT&6Uid}?8gxcWGa@- z2daxB8pp|WPiB%C?UmITI*vADt}+J$It9a1+!B4hUoOE!YL8N;HMDZ!b=wQWkg>@V z7F#&qjK?)m>*gQ;%@S3G;2U26>m^MakqCrs@Vm^+r0DbixKtiP`Kj?{sjx~)8$8*fUDEsQ%ZsMuBy)Q>V3WId(i>ghNj#iEp%?QX|uL` zSE7li>`$!r=1f9VXv3Q@GHLA!(QA!taa{{D3ZnIUf`Fnxp@v`gae5 zQ|~bgA9|9@1<~z$=1c9-ZHTFuwz~KF1Xv3?ZgWhFbg5Ag1$Lu$ykU zONCi>yDfbKDumkJv@Y7QSM;SG8r|^J%-BU~+GHOf0@2@TbZ(mHdxPD9CGw-M6LZ^7 z;`L5$i)(i~`!IVDHe%fa(Rb?OFJDm(owG9YQ}8NZ`W{#pJ#qQl$*9rwfRUI0U0Aa; z8Kz=_0K)l@2L@&&dcV+2P>NaBWRuX;A8!wCQ9lpNa) z!2e2hoIlg}a4PriqSbvaB?=#2>VWUh_fLU4s)!X&J>kHgfLs|fUtM|l@S%GuzU6P; zym=LFpK-ux0qk|{15H#-zlM3IxAI=^*>m>dbm0sJyaepDExEfi^-0m?(}X6*G1W(x z_qurhh`89JH;K?Unc9Z3ANCK|r`Y^X2vz=yv+-YzQSgeINT)s7aY78N+^+HfMbr*K zwn7!-XPaOv6vU<=YyfD)D^7@B3+%1&I>-amWgtxnd{dsv-}mg1{`Ko2fuy2SAXLbX zaHyYw9JEMBdD6kNcy^T`wGQ-s9MDi}g?tWLD~OItP=7A02hFlC|FT~))fUOm&p&ZE zPr&dYm(mZ+GRLv&XclJtp94ZlS`EVQM^6)Dx0e>dQh~6{O;KZ{aN5*+sk>QkugJqv zyNZEu?-D$yvA&m;Nu2*tIE||g|IpD4OJCdSauR~eZeM<*#K%t7(w7=4lFUL0ylZGr zKVp*3#c_x*xdvp4i*DqNy3b zRoVr$xY56z#5>U_3Gha=(Uj2;FmxJPzC3qumG`9O!qGQ##s zD1?4>l_A$E6e_^wF78UtrO8;Zk@`uNY^6FgFO2OB-an8 z$W4yqxKG5oAsV@($MCxpZklQWao9>9#;ttl3R|Sl4V1wDC3h4I_)6;+{~g-MW@+}57{EKZ*ut#)uo276tg54R`c@<&L=P$?b~*qLd{ z8WYO1*~8B!g>Em+UCvoO*dQt-M z^vcbjIMc?C-aA;Zpz-Qe`qKe<7*yadE>qxRwSPs^=w#A{Jtm>!YW3qBNNn=FNJ!AG z$bCur>WA!O$xB{ECx3Q-O4eE33b1YYM6qE@zOzNncl@x`F{U1l?j!a;j0!Crq5Zso zJ%vWow{nuAw1ae0sxDyfM?|S9GDe`(=1wk2?v6QOw}}I&xgi6rw$1!lB0;t<8dbq0 zb>0(?=|pnLkZz$Mxo5{w0BRJt4~Ifu)1JOQEw+NN-CoO+iAYR};VRYVM&1C9aM7=2 zP>tpXUAnU%K=*w!Q1XD~y8Xat!6mq9n-V{5ldPoBq#RK3 zmF5~2$UXaW{g7-SWPdetKBrn)?kMHaeKnu4&tZL1jC?*&C(|B^JD>Jh2ZBRiHeO{DckacWF56g$HV}{yogFryWMCFp#$ZB%1dbY z`7$Qh$OuS(_>5Oa_di*sa>{5KXbXeBCUd!b`ypWuZM>B}G<@{``05=8>DDanlN%3g zI70ad8Y$>!CFWRCx0imt^Y#09xOk6w4krlfH*HV#5o7oB7W8Iu<#cqz955Cn8OaOG z!ej$6v{=%@nE-Ojhnt?nZzGSe+1?gm*fj8Elh9Zu8eL*Do#a@3viE` zZw5!K{odbnAIz-Gt4s=n2ggD&bGF+ZSGLQCQlpIl_%pCZz`g$8Im&kFqnz zJ_^<0a%<;Ap=(5Aa(JyFDapcql1=J~aRx3K$Q3bI2npXRt3@Wyr&^L!o`efy#m52u z=FrCfMq0dxOm5feF@ttL{(;0E7J?e)qWm1L&fWIXrP-a5Mh6?(d}HG8Rjh>DZ`SO(^6NYLX`9DVZ+*qB&aB&wfEo_1B}7WPk`bdV+8m&G0Hx*|0{_ur`RiQERlv`UwU ztfktkV!!OIYYQ}O;jV_%ckDw0M}rGk+|oUlmwrc}!kfa*9tL21_Q&u0&wK4OHeh9RNq*sf8Oss+tGi^W?tbkvc(2GES78gQG9co1C_S?Grd$#|+dnp=a zz*|b4c!+XAunX3*o>l)-8{tsTK@gkc|{ zdDa>|)2zNDN{u^1+U)mnLq1+|7c zV99*o@EmBq(pbK$_fcDQT;%aD+8{(xnXo0_QUjwmf=-rH*E=FQeX{Ri929syKi_Q7 z_SGqMK!w%FJ3QvHD1(>`Wn&m+pqb2c$98MLc|xN2+SAX{k;2;xhu$_YscC71to{)e zXROQnV@EbzY`=jnI`)6;w%Yo3Pc<9hFtNilitkXFE5)Rd=z9#2SpI~V>P4Jp-Fz*v zk(U76S4Z<&GX0QHJS;(wvTf(7Qn&j?+Y32=tX?9{KsoV^A81V0 zVZzS}KTs9fVmXP?n|#cn!}CI8Z-|M#^IF@*)TgP_b`{s;-qgGoiRiq0W>00Lo?$Tx zn|fwecCa~p ztj;3mJVDk?t6BzgXNVqn1h3Z9-dqb@BKFm-+W*Ef;Hn3%vn?mpM8@m z>9n7hI!#$^|Lj@0Kl1hx%)Mhiu%AXa>Y=||T-km3YI^(kIo&gIzrB4>RGv-=fve9t z5zjlI5y{-_7mq$HPyA=>=ovozLNctcs6ODtP*GHny>VRr=bqwZwHLgbnv#=|6lWG9 z{EMJO6n*1XW`xBH_vF*78nwv{r49pZszG+t%`!gQHR8t&f~+pqgcswb71_RqG;Gvv zC?(yM%2(CgnzQ^Q|DPj5`6pp;HUiaBuG5`c(NL;nhsIDDs3nr~j1CtC^Lr12rSk=R zq;{j8Bc*#!ghQ3LAvK*08-|-!q*tjT<(cIQLK9*mlbq?^PCUrR-iS+2p%;MRQd53F zpCYYr_7k~a!!F{r;I)JH5cG|2D?Wa|nqC;u#)>TAq3tpK$Ud0=_Lskz#ME74t~G4$ z#!THiw_7qt{}v8w-Y63l`)#F88zXp)o({{-BTpn-N)e<|y^eEn>GP#0p6-&lPwKB+ z5q`h+Z4@G)a*JjIOF|~ymzF@T8R%AF*X)olfs^MC4iNv*AY{ZpkfI9N2i^3 zs!u5!IfuoYV8<&}N*!p|HtL{a1lpkfSj0+wqS0LN78SX8+Ua=$*}?w%X~lIt>?uC9 zU*q|L%Zj(KgYv#9i1S}|WBtEzm6F`gGt1sWJ-P`!JN?WBVGHa5!9vS$W zp4hMl^hA6@!aKC`2Aq+1a~3y1i(4Tk@6kN_iLvy#K6o*!U=h#}Rt0~@N{wjtk<*mN z<2}r0Yy`1KMy7o;=O3%8r$I)aL=4Ro(+SkIUd@mQg(Nd9$$vIRsWzQs&${feVCZ5H zG@j8^pO>u9aev{q)+?SHqQ1tIH=kAaRBfmS^!nh~v8uMaoLCoq#S7;SqbC8!B^!Mx zhu_bX0qypZ%h|$C67%v9dSAVQ?I7&0?Onfw6xDC>)_OJJFCw&tt_HA0ctveNUzf!(-05Y1AOear= z!%TBHsk`I_Rg5yWpUaD(VJovEs?YAx>ZPsa&y>&zJJKmlVfQ=z{z*R0`dX?P=5@iz zW4{A1bB}3)T?Qk<11c{>GBo$5XP(fWd^j8`yEwy=oDmB+9rajeS+#Hi^{&m?*FxwE z_CS;H!mbA~VM@Q%y788d2MP3Q@Q{^&jt=1a@)YVUZgHqtyWZMpKnjc3AUg=q{F}a{W#4eG9Gr<}9=n2E#q_MU+S|P@CObTq zkL}eS@wZj()!w{zT9 zl!fi$=!DY4=L`1ZwjJt$r@KCE$GJWflVS^mtRv``%45$$>hJjQqFa|ExRKgalc@gf zaBu87ut`Z^M%T3CN}+oCf8b3^*vnuHX1^fmOG;BubaT9uqMX7A)Nt9TQVOTIwwFD& zv@Z(9c<)xSK{aYLRRT2{-%px=#D25)eb@7(HWXWu!n9dxu4N!Y19fhyP~Sr?aiR8= zsLNFkt=XQ5Kt>&jP!Bhk{waFXwrW1CO%HxmLHpiFtX&wxx+=;hcC?_Mj zCd_n1tm^QweYZIF!suV$&j?RNpkgv!&qSb%x!Ysw9&MxtIzA`JW4v5zXr*{d6lU4l zc?uQtq?HpMk&@=-@0^Q9rN%^UwnS))e&WlXtFv>2eR{pur9<4Ij%IdoYL znyGTH-ap0aZ6UpC>VdwURd!LzBBXjyK~(0v4gTXXhg0;)M2Ua|CmSs2~^@pvFSH@Hm^lZX3sL#_4U zo>k69q8_k%kXZzs6!kJTGYrZkGY0CelvIxYwWlznPl1ho4ODXSFq-no;Ymvk|782o zo%yD$CJp_bK&?>yo!EqpyZ_V`4J_Ux^3mRyPh^NJnNLgj5@~(u#?n+0D)WJ3L;-K9 zgXq1v)1qhw;XvWn>A$}Vku8`pVhf3=xvTzm*Lg6HbhlzncaI2Q-iD15@T~bOpA4?( zjyOj&`r45g@P-i=*7U%^uxpXB%8e2K-v5jLp;Kr{I&8_de7P*(SFFqaV3btF`7>>c z+RvARGZM7FSVQv=7kJu;($IJ*;J!Rro&aN4aX;7W2Y{SP6m6qP1>1oE6Ty+ryr29} zoL!|BsJ~~{q#y+?7s0jG4B1&lvT9*DEFsI z4F4sm#|}SuZeH#pv;3FXIAXVG6yH?P_{`q_hm-!N)x)~joDY$xkqz3p49Ts}K7~IL zy@p!GRc#z9ajQ+pMt|I)hR8iz5aO0b01!LHgVq}0_RzpQ;J=v8Y7MRDUhpg9VXh zwru$GvOttJ14IKKo(0i!*uk~TDhsKxuX}TPt6HkMWQ<*bdwilP&ia&*}9&{gGRz^>#Rb<#BvzL++D_rfPhMs6{4>|mtjeK)|o_J!PWd_H&_>tKT} zg^F*0bi#nniMZ)n&mPHGPV^M3 z<%#WDy$iezn_>$f5Vy~bLIV>RS$eYjLtBJvxJWY#-ZNA zA0E_Y^{7gF>{z8sAo6n#nIxgQ$eigX)r8|wwO5+Xbg=~Im1HWjjZ)0hpAG0dcWTqh z+IQ1qyv{%@u_G+QbRex!>>DE^P@MjS>1PDc1QdN%JtB<^EC!hj`B2$@51?41P(fgD|pYEcvuq65};Vdv^1I&zM;vj&`+d^0~}yCn-fa^YrMcw~viz{1dp-&vO^TAIcCO zU;kB>``1LQ z!}>oQ|EA}&%SP8uOX7cCYXA1*!jpaSc&3|EeGr!;B?iTh8SNT9f?bb{iG!;JpJeI) z{SifAxnoDF5X3Nn0#j|y&|&cA2ql?JwUj^T0r{l&PckU=d)NaK>aHEWN|xg0hQx-T z%pf_`?L5XpNU_iuaSH>vSA0HJ2z@N>2pr+hHAvLlm?~f(@?AN*v@pU?pYNj2tDFn4 z9#X26#MjC{YJQv-Pw`>9YX@4~jE50Qj}=he z*AQxG2g$iY{$uFzx9v#6vcy?Lza`U&U{tp?l=It2{(biRoxOZ>GKcW6EGAA7Or&ZL zx@ZQOvKVB48^u^W>}2eAAsqm#`XuZGZs8DbD!VS+v$>S3`s?=O8Xry65P3vZUpke+ z-a2o-;8u}l$1d!3(}EW!uTZ4V4jn6oUH0>sSMtng`hW5oUBFL@giyuZi~dV3%5^dM z%MyuKwe9^+u+?a>qx#5OySmNF)qAqIg=4oBLfyFv&VX<-)ErG*=i?z;3@rVa3-%!j zmj1)kdo*gJOUIAx=-KD+zqA@x;!o)SoG828;~c5&MLdhZd>zD0s$lBlfC}Bq*t2Sp z(^%*@Dj5s0T`7K+k&b-(+mcibV0O8c(%6qg{alkxT6JLCx1P4{VI&7Ut&iJPwFcx@ z@Z}q5gAr$GyzuYDQ>}r!#N#5(UVE?d;VnA=!AI&h_QzH)InngHV1|I)Z__)V9z0AV zCV~AL>-Or5q?> zLCyQKyf&g?a~pMCcDkf?-PQz+(s6OqFOfD97>$BTf&k$043Z;-$lXXRexk1XB!NmR z%pZ!OEB^U_bE@|bd+f_D!hE0fybE+gU7z7qfVhyLhCMdThQIS#g7MF@!>AHMlBQPD zM`Tg&m2_nCua9v#kS1O$(9r$EhwJ)m!LqbcUceFi9rV5MAe*_zxh&ktF!;Pu zZtL)Y0z^9((kC5h#C_AcHP z4iI%G{?p34Yri%+)Uv#Fv+&LQM{UMdX0%Us5O&<|8bJnVU60nZ0gCfRsN$s>qPJ#5 zVhGpI0P>rQ33F%nw^($kzYq_m`3NvIzt@>_8(c$vmN{0-{_GFqG%qvtvK8rT@q1|M zKRK$lG^2~=Ww94X)p*4=g_2I0XFz=DEyMK zE0n^elqi_K%lQw!HA1<(fZL)Gm+P5{jyG?vs-I7}KU$X|Oxw+~T+h1!yT2R1Rm4!9 zCpig+IGGd<6U}JElp94w2NFu^ZpjGU``^PX8K(*wZxbQT`FRz+(>}TTO^}#Msz#rD z4{@?~dxY!|f}O5;;5aB@%BktS!XDBU+m*wXXH(xA-kE+ged}#!nBP$W?H%r<-)~tg zwxo5HLratIE`5)Q3YC1N0z?5WOwO9NjiV#G$5#(W?3xNF^?3nKVS&(BhOBBYzXvp3 ztODZgfXl4<__vw95OJJZud!1aVwN3Bm9LcqMpeJ$*@+h$^NeaI3ZYj8C#V6@FQ{P(NL-^LZy=lz!&RAupjL8{#)b`Q~WqRN`k@W`XMX<+z z1qObZ6_bwvcgMU7>(D7K!dcuA!GhJ5vf>Xe`x#Jc0_AR23jDQhulRmxN>t z!r$i6uJezs>yk>7m@&&Ajsb~aFB}w80or?x&-rz2`{Ak+wTF*m#2lULiFPca=;Tmr z>+qwKTXE%DV!*VKgi^cM;ZL53fl>M#17!jQX#4>XiH6$AMP zb|*^54eUh$&;RLHOw?<#!|I{)_kCqcLl91`i1^Jf2&GuiKZ&BgD2Egt0l|MHyp@A(grJT0`yqJ=PEt|Ns4#Zyib53B;d!_Cj#G zPNo^niYTfcL}3cX zhv5-!^oB!3;X^{qnl_C+5Tn(!wAmD=q5e8*GwF(!%qVOf7-1ur#-+>vP zgJi%Yps&QxL(Jc_r!g3;Q#ugKJ6%NjrGXNAP|@bpOUbBDy-n^TR?zbMh`C(m9s0BI z*#giy(ar%0zXq1Rfo~^~Kw9tLrU;XtdG>oGoH5=w zQ~UMvEbw<65EY7;`P2Wci2=Vx^6Dhr9LPw~m0O1D^yUP_T-oU-M?J?8B=W@E z8hXv=z+r@l;YwC@wRW9^i7Dgos<9{b#wYL z1B-PPS7ADwBWi~~8S=4VL$B{m)#SpFk0?KbE|;o?`_+Gy8opSs=PS?cC41&}CGMU? zI+1wv{sjfW;8K*3OTzyCy(0L}QVRq6DCIw6^7yY;a)2e($JR_mnSDVz2CL9Ze!w*X zr;idyy4dj=vpoJg%%Gol9GiCfYCoRGKhu;S(|oTHS<5j4wLW`}t~`u+d`j_|BqR(0 zTxQ8eEqpt57EO8z$)b|qLt=~tRiq@%r(QDGLY93wq{R=Fd29v+2GUa5Zd$%9&p zPnxY=YSa!p2ri$%L*Yd)&P8l@`R(8Q&~XabPq_)C3TDimc42I@2BY26e#hrKYmp1f zvp`eKOy?y%e3M`a<)5J5XeV93Kr_w4QX?x(GHEmb%14Etm}nmXLw6Q_SVKT#_Ig$Z zn$+~2nenmAk#>!&ki@LQn+?du!T#geZG9CQMKu-U;OKHPLxT0? z3Qj?;21xo1z}7Mh6pM@cFWD(mxf|T*Ku7T~iq$cpnb`ig0wK%oHKa0@%0Mz9y%}Z| zvxaQhfB+`Os3BwYx*g3x`VJrN{*TjSCMqP!XHTXF9umI*<8J$^4V5qNxC~_F_7}rEw5&dO2c+b} zKf#N` zqqdNvqedqhG}4UR*4q?1_=m3yW}E8`$UO(P9hqah@Etr2mwM^V=dAhUD&h?%buc62_y!zx zxeMS~_oe9zD!WO2#8UcN0a^yXcH`~Y)c7JIznwm};bxlj56pz-i}4!Y09NLz!QV zrO;v*SZ-h8MFze5uU;t#DLjD%2*pS8=gyzmC9;Bu1%~SL6qW3QHYmaHlY*fC--fRU zhiY2dH0}}n?K9kWBH)Ta4@_>DO1;)M?7~?|8&zYXAG3Vc#ym#wQ&yK%C9T{^DGwJ*l`0$)ac?oFP z;Att_|k4sGS?<>{0U%s0bjQk zC-RFMjlpQV|NTcOGF<#VDSYck#M+mlY!t}^;;=h3be}ZA*nOYmc}SZ&^$HM*^5fsz z60N_c^)R?=e??BG6ybp4DM>P9l68f85M26uoCw*tZ~IC8#EF&TKj?6??|!o)$f3Te z_~wNt)v1!i?HgrjttNn5j2Jq;A=*P+4WG@0Pl>-+5Pe0Dn_7RJy=yM<0I5-ONK*XE zi5ILzdHPZxdY$^Fl;4VL-WJ)qTH~oC{T~Lu^??1}?SM(l*O$t}I#gMWaS6}H00n%% zj%j-d?n4F+!}hI7d+zY7&u)(O44w=3qWtZ#Vht^ppFMuX5~g(zJS#$IF?s_Wsn&SV zMuk$3OBBJ*Ky9w!uA0RQQ&GF92t;~8Bv-NsAXl|n40hkm`k=J&$HO##o(4C;gaz@C z6R1e{(c_Lc(A)ZBW?Hpj>a4C{*0po6xp~)^RaH`1#3_DK`URrxkRgaE5q+(|bfI5K zdm}*O?;6l#+ve0*rpg>%2F44rSiv;>Lp8}_gyNdMOR6acFQGyc;JeS8NpCjVMA@@3OLP#EK)jUU0>v{$u{RCR9b1y56aje>d#D$N1A zK8uTi?R3`xC0Vi3rL3zwddUvPNh=RaMKS1=7ZS?-XYPjEL`*;F>@zEvd1(9IAa)>Y zi~XGq>E{>kT&AfkO-USZH3-LwZq6uS2UBDW1PFe{1_b;API`3$-)Iaf8hp#5Xe=-z zy;;Ti2?pSVo=t+L^2n&3T={XM$Y=XbZXM_uu?#iHg2^w+CHVu?jak;HMui#%);npv zoz!M2igexkATO4M^s}KdP(F$8EDvu;rJon<@;&}b#g#!0tT5E=Ks|d3NeezpJ@|f~ zyn-9Rc39ZD60Wc>-h`h9|C)K>9ND(?VLeMdb-{j{34HJ_Vw2wuF8G~sXW^XEw8oPB}eSK43GD7#Hu4 zIoYU&?IZ$q%9I+ZF66<&82|mOB7XKU|L%=1$O6t#=m8kzf>aV8FXZ8;Rs*_xcje?a zltnudFGnq0S~5W%sU8d8ThLEGq-)uZv@nByVv?(}gn{;h7xos~QT^j=%>iXBI&IR3 z^W>Jx3owj+S>=9RV`$6XuAqu&e(dg{35xTYm5fZ_2r&2cY?{Zzmv<>WyXFIQP8U~5 z?JGkm(f+ERFzYbg+enBm))}BE_2Wdrpj+^%(k_q1ypq20Pu$`IT~Y3nN7{0)ri<@$ zY%f~uf$82e0oS?~0-3Gzcf9C8!#`UUC=7&jN#mygEXrZ#z15^0uiiDTU#nN(TH?8R|=W&>|-p?3}vrr=ZRvr4+9+Fl45|N zI*BuyRH8yvCDG$W6{rGBr~-9au+n%yTJ?Q=R61(8;efc{@3n>Gn4w@srKkT7iy!sq z*1eT58}-TprGj>8DT8@FKS`@Lg;BZFhi+Il33>r{6)WL@yeZa&Z4F(}r}@1Hm_g*- zCi76u#MiXE4=heR4;&(!3w%PGd+&uQvv`{@L;BzU3@bEs{bC;ZHd*SMG@92SWWPel z`V!IAD}PAd)x(9X@#J0KAI%e+_p-)*@ozn*6ph5a{{)Q)GhcCID93H}#YB0zN_;Jv z_rlhE*Hq_6cZP_2VY~NT_I)K$)hgs}AmbUJ4yt$vD-eLYA8(N(L%`yPnbCYMr9{r$ zEQ2JKQv5^`Ds-%0t2x9*O)*};8qzT?kO$rlC4Q0%qV9w>R3Q3>$GSfQ*a;IMS8HhG z^Rn*E#UHAvq~>o7%p9Oepb167l0feyu!ZI??9qYOUZ4jyKVE8XX7)LgQThEf0i(@Z zxR!!rSJsKSv2;qShnX->Itj9mVurl522yrw)x>Aie4 zzua7IsmKJOqLfl7Fcg<6SeP|QnBQx^+FkFRT#45yo<7+nIaQQ?W^lw;Hn<;|7;p|_ zQrA@%Uf47!J0N!cjh;Pra6nm$6G9Jm?R)!)Qgz)O>Q5`0h=N)^8a4kUDeC!!?J+Zd z2VHE37&gAj$Ief{&XkH2l{72anL7zfPK+Y4YQn;sS_|Tv#rHycD3}%o!-g8!>s^^B-}zqnX09;`H-fKFs##HEO&oe6!sC)ihk`@B)4k z-cbpky68XL8*%pdF6B5N0*2`i8Stj}@AYoMY?iRcDhR`IkVYw&z6&sG);Y1 z(ZUZ9S(Fqs)KQASpxxV>b6|;qkRJrJiA8W6Dcr6ysFACZSzwvuiWaDpAcv|76Fs_U>M$ zv!CXh0t8c%B4#;TvKRPSO8Y>zx>0K3=OnHk>#=Y4fM*(bZH-IEPbww@17B(sDpSu1 z0YX;%iyha1M+LgU9GE|kVo)-ebJy*}R+=LhY7BVc^Nefl+kXNsU~5Y8y~0>hnu{b& z`end_GU!Mmuv+32*MB@jWhIgeM_H4PqiQ;AA)%K!&}&J^P!3J{3SCL##RniX&_K{B z{dwB=g0lZEO)CB5$&)LwYLJ$p&5Vxyt{!6fCj3O0rawkZ+o8N*fZ|Nev$FI*$DKjNk?j^* zEk9mEb@8hOCWq<6yXnD;te@>^PY$Ja-wjvT?GQ!R1ZyZ-9&Z2-&_iGGnV2dUnr7M< z*;2=)fSG5{+e!U>Z%zkraOb|0EvC7!`1z3MI%FvR3Ddvb;*Aqd^_=Lc9ve=~__LEP z0vwO^I?+{$46o7^F$STfR42_zmbTJ=$ZipYL5(Ezu>JlgNCD!=1Otb-d-FfYkH5`G zMJ=0@J-xM%rWmTo>Y6Nn>V=mCYwAtXWnpAtCTaciA1KBQoS)0!AL2yu@i#Ops#yyB zQ}fK3%}}0J0om1i|Pwl!|@{SQZYC)I5=ZPu>)SpU&5Kk0ITItoMlqX*BY`6 z%_xrV1#7Q%r-iJAam?NNH)NJ6tP~-6exb%ZG;_hRxqNVM@Xz_pFZTnMgX3GxIOxbcF8M&ybY6($^P1?b1!BYnB=gyV1CuUh74gSZ`bq7NE|MBOJGdg5- z2sx5bSERC6zOq+ZC$bBPtdMnwWMq?-l2vKhvv6c5tFl+N?0GnM&+pUEAO7Nxd+zgm z-tYHoy*nx_PLCXLJm3G8a(zD`F%cj8Vd}Kx-A|&@p@L&)TN88Sqj1hKTq7dQtfH6W zvEFXdM5nVU*+g+EqL90KK*LweWefjD{?>(HlX&5R|%No%= zcDfgj>t>#P9Mg-AXIMg|xB(GgsX0;)L-K79p|_|=(6={!ANvm^GK20MU5bU2TUWnO z#~lY6DUPcuym#*^_^giJTY<3Y+A?FD@s^%^L^s5Y8MNQ0+fqM)c%1`xyK_ zBX8ckX~~JkX|GYtV`=1x;;3WEz|q0Vr46?u+-xI1Qz!CrVJwYn+Df+?48^W41K}=l zA7`s8uC3bsY@AxEWY@i!bFw||OK5c#w^&`v;6+Yh*JM%MsHgaGcOOE@53Wz@=IYg} z&3bub!mCc3Pg;^>#m7C!W{Q+8UT9-$`&4I>lZDXjBGt|J`JL9Z@c zTaONPtV`1ta}`lY46 z-VcAo}csuk-{OZ?x*+z(d>b(uk9Seml0V;{i>HPQJ_=gfrdF}HB(zjosY=mih z5!dYnfu7Th^7lG!Z%5;4k;!`mTGY{@BS-v?PD68TnjL=dl_BoQQdn1`#(4~SqlpmW zl)GZ(U2L*xo$cAm#P>liX)>L8!bBdU++9GogVYbZ;#1zoMs#t5c%Kme)iNA0HxY$> zLQF)tsrify?>BtO)q^)A&U_y#b$~@*ZINBrnU_{*aJ}5%Ba)BaLCZ^Q8ox; zt~;`J{<4S4LjP-qVsfxf`ZaytTa;5%e(i!AhPLY`oV?N`_MA7@k53icNLkrqL1F?e z>Yu-1bian6_Ud#uH5BB>8l${a_Sp-_+V+>*jxRMK|3;2Ih_uIXUyDI~^(IfGdR<$ zbA;KU;{pPQ6IZoTIy_r*#(YN4^b$T(9Z)+V>~L+F{ikn-#(GB4fQWlp2X|pn8+m?z z9&JvZDt}^osL}JeMc1YDeuF^w<3mzRVAGXii-)y3%KI_mhCiZw{c>20u7|J&-9bv% zX!7-E8XZ0P%aicue5-7#LHjV{XSvIx5^Z_O&e)OyK}>Bnp2#hjn6+%%FjlV?Q}(lH zasa*3tWo?D?{*=;Kzrz2p1zzQx>*4Isj+ciYzd`9DRLW^_{|?imlpHAs{yGSi~3rr zBy0UDUr83cpz0z^nKT%(10l00!dM4^E$5+z? zi#m*ZAeO|kK9TW{(#O(aeduTOKcvHE-0UulL}ZZ>JTOD}HyiR7TGrMmaqTGH`h114 zzUbsImFE9$H;Y31;sU5yLb@jCwxr*r!wCn4f>5cXnZ$4vw^ZD~iBQJK7c^HTh?L^O z(XL*s#QBh^C2+viZh@s~PE~~qX>~RewzPI@uH`m8#e3u{gYfGm7vmFdN#fz`%i_C3 zHZK$sNY(dCTHY5+n|~6P4q5A##O-CB{KQqP-p7f%XdqQY4`Laz9G}_+=egoue_<9I z+k5!UCPfUV-uDcZ2>gwvQtFMx#6*l{d^S83(WKB%3^R>IsdXFE6%kzoHiVizL$}`a z`UO||2w)6E)H#yS)-RI3sTws6E-(SeI0Mj5_ock_G3u#qh|%mSXf?`m@4J-4Q#A2G zUR_+k%*lrUH2h&@mCCm~jOAG9T<-m^GcQ1zTSafTC>QXxq z;av;6<`eN8D{^VD!huv1A%f<4pf*fP00{}+$WGhC{{&`L%wIlyBzeGfMrWvT9T;Qz ztg{zB@Pu=WP)83eJ4rEQ=7P}G^U8sx)A!l(KXI;xvGllRyz&ofn0j&1cCy*qK*TN9 zk-`&;BG*jfGXBGpW+s+?IjUj4`ela!qJRb6d>g3tnMKaYBP231?1WF^+v+T#r>NHB z-xM&4{OG+v8zrR^D-QD2CE{UzHrgQ0j8CLRZKJr#0-Aw%Po1xZjQGqT111Rzi1LIz zUdAmFJ^9MfgWTKcg;MripG4lh5J_kK3MBGXJnuqE-~MdO4=V_%*Ol3(8^LkUXlIO{ zHy;v8^&G0b7W;mhMbd=!ex8V;P5f}uv7pqmcY<6hOMV`zLo+y2;-tg4ZNiy59h}CB z_b**_QcA>Wi57C`J`EvrHv2L-VG7@lXIU=U^pTCQiR1o3XULiijvQqgUU4qkP0nfR zHk=J1F0_fh$CHl}u*hHLjF_*z?-YOwtJC+Dvr_TD*X^4|!DA38Y!jT5m$Qc5wv-9* zp21JrJ+b){kWmz$IX8A0k4?aY*o!L0TD6;eFOFx!F^TIQeI*ofPB5DX`=dEzRLLHS zH*gdu>4Egncmq!a%W*Sk6yuhVjP&|67i|s5%VgFo z70LF9CQn2UWfT!Fb+#RHyA4ZX?=upA9D-}SkkT6$wC&GLt&fjffYk&eya~jAWCFO= zZD;^UjHI2JBdk&Y3rxt^2f*u>-Fn;nJJnNn-|h@}WnaOFeTxufWIer}DgLX|kJq0{ zSE05tu2xX6q9~CQ(!jT~b7IaVWky$9B-_qoWo%C-61uz>Kvz7e$~8ae$&IE(ur+b% z-an2x_=-3fwlogmUsj%K)zG#P9zeTxl&z#O#5IpLAoaDa{{+Yb z?OjP5U+a9bW&GoFwQIAN9FW`qKwwAqS)_;F`|ta8N#Yz*-JJBF7Dl}u%*N-4o2xvz z(YRa5Kk^=k7iZ4_>``Zs+1i`F?J=~Of0qr~P^5M~=q22tNKMu*&Zb(8CxQnu`=K}3 z_#uE*ZmKhyPzt%Z&z$~osFvpp-_lzFaUSxJ9s{NRR%<}l0g^}_f$4ELal-L;PP?rar3R4)BD4Zf@r$*B893KE5 za<#eP=&kIufyZ78muyQLY4hV0um`Ej7E)e`mJ&~`gXxW&m@@l*eA77Js&TTIxN=i$ z!j@_Atcf+$?Jj;`r~R&sb-lBHn@k#RPu2>Ib<}d zdPlFIn#DQ=NBqiywYoh)wi7AoKX25if> z#{%VAHIy-5?L4S>iQh3OIesUOs(`ttmP8VQGz(24@sbwM_b{}zq$i>Z*lvY5#fb^1 z9#SzLY_PG|mNX5v39|F2Fn=>)@a@S6@9@29@tsV{LSC zAXuW6nYVMA<%&F)@}-3K0w;EXx5B-2&n_CQ9xc?ZdLUx0&Kk#(VqK=w@mf_#lh{~1 zd1Tcg7G>R_Ja8ax_Xp3U;!P=rSW0OHz@&0eEZrhm#V^RXuwNaGs{gP0!j8uITE7Ou z(N87EtZ&p-uz-Rd53tGWVC)|QL`UCn??Mi#Tb z;~9=a8F*j5-QLyWhFb?|ZS02c7>3?4BT4~4d9Z^lRJtOyS6IqPMES7TI;g~5)?w~0 zR$uyM147>TSNdYvYvWaML`LES8d2fZlqf^gh?9q0*^z8UKJDJvk!kipR&F?bf?DXw03v2|~t6TZFNPw2RIcYr3O;9;*omEBpD7m<#R z9pBn}Ol%F1Zv?y}16aw&KH zn3~R!jR}$I^s^~UCQjP&n9+fF0rcR*&Bp^z!%TTGMaFTK3l=zO%N3@3`L4Klfbd4$ zU1KJZ|D z=k5W?JjUclYA*-r8)_ZtfcO}@Yv_si80B5J_IaTPz6L~Txh#W%*R9AJEYX(2UTFj*GNID?n6uq}pZ$5rFCpY1@zd0+U$%pR}5{U4YsybQ@ z-+8|a+i1dX?X#t0Si%gD?G^#8?zoSPgn$@vm8Hhj;>_63zU7vP~=nLH* zVt#JwVExBp3&gu-SvvAyutBvZ;Eyla1jmp#qJJNGiw*q2ggLE%*;5#)aeq*t?9d&Q zw5^dZhKs+*fj%aLu|JWY&%#JdMr^N-rUSC{)MO06-+(a5)gB@6SL7Vzj@;JAkc>Kz z@0L|K?%HJUE)uj`R4eI4(|b94KriqGH;-&feR}Q<9n=Y~vFRMR5dH_JS+#}k=p8(R zt)9i2Y-jy_mK>6>0N?+x2HaB%ly_8pC|?^5 zn|{uNeMXA{@2RV`GDiKrv+I(1erpM{yeD4tXhSC|*GYk13}2)qY<)_lQk6cs5wk$@_Ctcad$>f5pK08~N;g7jq69|P{Vy#ez0F~Mr`!^f+yf@gmV z!F#RnjiKv`X!T0^i|vr%IVe#X4US=H+R7==6xP8tJ6U{G@#@H7{Ksc9MPCCPv472d z3EQJ9*D3YuTH7$umS&Zg1_K_Z6Q;{NFfNCqny+iaH~y+sQh%~R5_Rtxq$g&2FOc+p zd~j;oTsrThWG6U58CHuIf9rg6s{fVq=Hsl5I}c;-9zMK({NktfK@nMQN0~Pa3xai9 z7Y6puiLR=Oc+_NOrGr47aP>g^`Wx0c{JKp84|Sa&2P$T5PXpfi58M`E?W5V`re0!s z|1>5l2$$U4qd_0rouF`aP6Tt>tRLMhyNJ?xj)yjcSTwv1X7THzWH8d?@8h8Xy6xPL zkNuVJ-G0ZTmyIVW@}Xn7NPm#L&tsuqhWb^Itj9le%8fl%&;2EiSa;<^&aK!O`}|Xr zig&M#X*_F3S*IYtB>3e1H3u^b_$oR%sTDSjpuQuV?ZTMxROp$>9p6&%;0)-nE z7vNky4&4dD*lwJNRRpb6?wS%bxCer2`c(@{9EjVzm!++pOoEHTtNqseh}xO?Eyo`` z*kbK#m%Lw9*`CBmxQKA-JSn68{dl0iN^j%+T>e%1C;CTcKJ*p=ehqKpzdAt1n?}zU>p_eG@M8th@C{Q(&U0RB+Ewu*oWvzY% z>gffM%UH%^+ORQL3m<9lYl6j4v-_;Mpb90MlA{6=m80R4?{}?ss8%(x*I>q!)@`bk zvMVQ?O-D?p&1iw}7PA7yF8ZHm#pGYo-{FhQ~}OYp>;r zDTMV7r{*m-?7h9DCy}@&-1VCPt7p8)Iu?EE2b;xv4z%Stq|O@<@yUxhOln}TX~@dq zw@dS8!w~<>nxFk9)R@*nU?PuuKG500zq=lZ`gvLL2&0n(1}g~cLcj46<)%^~^YXcU zZBGF1_4V$<`gP|6sabrR45n$}N=x{HL3?6j5GWAVhu>>~E`-ZfU;UN&QC)4Q9Rvpe zz7sF_PZ}mvL)OBOHUP}R!p0WV*oiceO)aUt&RpSv#G)ZK=sa!9fEeIpu$?7)#)gp? zOM9^hAIV(vZB+`2Y=eV1?^-KV?@)TrZHngXS|z}rymR62$s6@UV(_zv)Nq|gUmbWR zip4(PIDTT`5_(!F!Cqp!iutRL$>OE%A98I&6F%a#vidHpC+sr|W%o8t6uxAp?^pHS z=XKCfPz5?a#SL;}tQqHi6ihCOkvDLIYc2>>W8K^dCQ9q!}D{ttC=(uXoGR zE~lA+uOeRnE#GQ_+m_`=)2w5XsnulZ9sIhIj$W-S9~ONZ=_;#;{P?!d+#Q)_0kCD$ zBG+SF-fIDCFMHct>xJ&xfc-fg#t-YMyK;%tW;SSyl{mv=$VlWY{^`Y_{t6W@mCXjp z9TW;cePp$BRQM4vk@(3az;_}|>nu(bXj0Ggn{30DdS^7Ytt2x}0^_c%sgd^as$Mb> zT)Y7iwJk!N>3#OQ*YD3RU!aX$J|CpSU5wF86fUhS{Jva&T&X)Xdi3 zyw!g-@PzmL1t0aoaWw1O=ts+(B3sgyZfY+Br{&m}RKGtmCkt?3x8#2vVV?(`*qSwe z2ns0i5Pj}=B7R^(9Y?^)(!VC>Z4Kq#WNJS+ z;u8y2^g!;p20M1yl75IjQQYgm^n4$pjd)Uv*w_0fFoy#j^YgFd9L8txZLclm2PFM2 z&n!R4_X`^rKzlNxCq>=edvLYH32zJrA8kN1`wkhMH7{hUs^%syh|JRC|MT<`qnmj> zR+@;CULR14Eh+kXxcPmg3E}o^gtO-?-RrfPyU;%8iG6YcoW2Y+i&^!Lx8^45f~W_M z$(yr(_iyC}3XQPuNFEhYfOgMp)}K2@<(VTmQMHdmr6#WAp4E_Pm8Z6Jya|Vbz z(b7=}WATlHe2y1gI>9CR`&(6rEBEJEubrE*sKf`>2s0uHIp^9%``5FCTQ}g&uuo?_ znePFte6j!pc0*Nlg4y)zO&|J6>{w7c5}dIwYHmJdYlH_8pYQly(xYfp{MLE+aP8p> zd*XIRJM>6j9c+kU{r~B!0k31xgH5%A({1p=C5{`1?#aP-m(x`MnDgvs#ID`}sKgvp zwdO!Le@CbfcF=@J)%|(}FisRoCd6Pv2K6 z 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 literal 326320 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⩔30V^aN^)*_D&yQ2xLHAa-!JJ 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 z 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 diff --git a/osu.Desktop/lazer.ico b/osu.Desktop/lazer.ico index 5051c00ffc79eceac173442cd0466750d8e3aaa6..f84866b8e93b4c7a449371b71a99b1a8c5567df1 100644 GIT binary patch literal 67391 zcmb??byOV9w(sCB0fK7?5Q4i6oguXrR|5cO08GF?8ybKafHnvKC_U2r{Qu6|u>%0g zk344Pf9GF30RXB_0RU3c|HvOrXVK9BpWkXK6XDb1KbjJ$ym_VbkMy4d7yB_Wb}zAc zOfYTbHRJ(+UkLIEp#gaLNaF&~z@UHC`5zy&j|TvNk%tDrcudhAcR(Kc|CXWw@-Y6J{zuVv zk5=VTZ4mTM-&0>hUDDFUiPyr)<)byPuaoOP3IL$5*VO{A?Yjq>|Yv^ zkMuumzGuw;Qt@<mC}FAA9K>r>^wbP zCHeS#e0+F)gm_)tZTSQwBqaFw1^EO8c^)-*Jp7zJEqr;LJy`!;$p0?qm9>YZJIK`& z(SL3KuBWvx=)XNVd;BM?#{l{Mk?;xd^7H*y*^j2cf4Gv0 zE{-nlx~>+M)-nRXe<}V?)_;5XxAt3*ueGE8E0B}5v&UnNWCTS-|6}xjO8&2w2LDgX z{~`I0C6Mo*aQ{d6|Gu{W;yzwn8GIn$f88h<{4LssDF8qYpz=!ooiEx!J5HOKp4WD( zOVwVDRlkNi29;wqXM@`(7IxvuZ(TsLwC8;3gt-*x304Y%Nvmzo6?-Br-q=L{jZz;d zBqaUG@QL#?-Y_G>+gh1gTnwm8I|B+_N&E~G`YWR`gbyX@hx>%v zTPt`b?E1U`9ezjM3=Iu=H#6mAWi7p2t48gN7qp7HtOV-2S%+-~RnYT}v7K8@hl&j( zeZQZSF%d}coyo|L7HDvRci0~;)Mxe{q}o02G>#1}eYp=rX;J|TZcFXrOM$H-8*?x3 zS~Db;D&~9I6J+mi8q|23|H^Sf8N%^is%bQ<@vE(n8)K-UuVU)CN}th8&Uzv%j2bK9 z)fUj+k5^^C4@RjhyH0F(eSRpN`x{6-u^@kWO#I}&(uM&3^=*^WY^90Oi1hW6H0a~y z-t#y!_uMpEeKG(?v8hAJL^IwS<<{2Yj({EH`DkYo1hQ=?fFgN`!0*k)qppU-u)7-- zdscaeDV(!2G7tlbB+_-8XM=o9Dgy^zZ&{xw-LP#xn-0~xc$R4NENLoq)@kK4Q^3Km z>9P+M{-$PT@Im2$&R2vXiK_wRS&#w`9rQC9)V)E^YD#;+b?|w4?Z`kXftpYR`663e zQqJ7F9HpsRVztk4r^hY(mEWmMJjbVLcg+eek(c*0wW*0D{o zxzLGp39gB9l0Mx3Nn{lR`TRX4^Z{|?$ElX%>@hT%F6rn;(7v^$2r-)OzR9M|&yfNG zk(3&zW_%AcL}Mx8RQcHNt`i(=PVk)g?k~s2#*yOYz9mqzIduT^QN7N&h?QCYJ@@T4ahVOUN{JINXOI&cyA(fQ4NYk!9Udyiol;w>2#aNO>r z&u~0U8`a_Oes||qEGi`>CM7K-CL|#uCM_c&CM}r{_q|FoviOZA#4!i?rX3LYUG#Q9 zucLX{j1icOY337rk=4|FI|1dgCCX*2abOYgQNzB z4e_KhtY<1ek>Be?KTSB``OphT#GHPbHgagrDlIz;&%&R5Xyi{$I>F1_&6N}(6NdbP zGA_?6&nRM)B#yu0thw0^BO~teAhyEj&-}4g_cvFrJBiEfj{Ke^s=ZHA6|}s0^bs#d zv&k?YP;~H{K3HY@mEn=#&4f9~U@R{x1ye5jiwey{ai%kE;QXNJRl!H34{$zKR$7pe z{hE>3Qi>${l;I9Kw{ZvkOAe0PwT+Jl&!3j^b6_8*6?Mks-*RCe?)U?(LWK51dVoZ(@t0WGB@Q^Gk-p00R9_HNS zsgnk-rWiTj&Xt*=_OyS7xtH*!V9J*wTGj3$^?KQb8;v1%jni6^HdU=hEzVwfdZ34U z**_t}{gu?zehNb}Er*}^&&DeGYrM45NH??`Ji+4@XCv_=%)OR72YZC=bIa}d1+9u} ziSwI+EM2)n*68`#2G4xDF4EbDvBF5rnRy6*NK4S!T-!JNy0fG1ympHkjh4Wp!BlLt z!3m<%i|QWUhn`^3^{f_qk>PVC^YJp}&=*{_=p0_nR{2ktt-Q2e3xFfH#Ng%p8UYHB zf$lnI6;dk=ZAKhTO@eIyQct_=QIa@!bGQNR7JLlUPWM+D^*Fu+!Chm4$4D_<=Z&JukbK7L6OO?9`3OHxo4VdPs^+bs9t3$<=1+A6+M za4bp$jWj*il^mVTh!%sK5Em_2NC;VcN#4mq5q+m{9R1$nO^^9)(e|4cj)Xz`AM|NC zc1eICNzR-dw!L4THYT!5GN3-XY3Z6iyFQlHy}Rjr$$m#8Kr(Bu zRg#{aF^=2GL4QBe;QIH-$wIvVb+9HN5J+0<19qln?6dWx(uLmeH^m{FECMa>BiE*X zq>o}M^xc0T4jcH*zRgEZ8bkvnkL#y~XZ4-@wta|9#>cS1I^{siqb5gZC*0~8Dv#_w zP){3oRC4ebCVN-;6eHiPHheSH1)qy|v5=<6e^N@nh2tb4vg=diMD9L)cgT1vp0TET z{zDxl=E^-s^`4f-#2%^Waw;GV^+#D|qO5*hYfm7~OwU;?_Yb{Lz3kP=k?T$-Ni@_) znyim$8Nwl6EL;OUK?w9L?u_78nQcX1dPLh|=Y`yW|GnM~A)}yoV5*Y)m(B;?7qiMoYUM>XuS*o3oSF-5hW+A+Yysw>! zd^##2Rvx&5WpUFssdb|xe|Y~X1?xD`dAn-mrcdlFuYc0tfHXjD=0uB%U<(Yg+9GC< z^ej?B!4n^za?~I#rLLkd8fB@8SAs*R7ZI;^($XlQ*O80@(vt{hC4T{LG4)&l8P;~s zGsA7VR@;IgD_?2~v{05AIzcWyN=D1!$r&ap0KX*reH?G|#siQTO^*3P7S``{Ku@>< z;FO*4Icf0|e0J*RxWZCtIT^&dlm4Gds-y@VO{qSZdRgeB!U>Ar8LPQ-4B3gE%v-|Z zQCkDwxg^xBYQuaw@0PWsP+gO%csm6hX;vTbJM#A{TaQvNYeuSmVw3})NXH#ELw%fx z?m9iS#QjGtEi^Oym2SHkIR~TfdJjGZrpZ_aTL#{4ygBx^a`9d6yN#X9-poGz%tJlB z{Ms?rLzZyA&q7!B2v^ZD@}x5ZwlOrHTZAXm4SU%7V3H}lXu$pCM+Y-LfE*Jy)Hzat z0$Z0v`iXati|rihR~2|oZIsx-%u_|*uh*}ELFG#f=((pEOLo_Ei|2)DHq*@pRp(7_ zo9Ewc=?Q$pAuBBj!%N19- z&VCs2+>4dTMLPeqt(bX-v=wCR7C*3{Qz+1eDX~qZ~uZ?8V$VRXk+Owrx>~>^&6QpvB1SF->|elQ%9sC0@RgOOqC+t$X@3Bs=jm zU}Ahl38nrkRUpWCvP|V5#4+P15oYcCunPR`_a<13)P%c`6GlyX^e#vFv=Qc71L9R> zz-ms4T-tyQW~7FuhAOps)X4h?i_K}OXfIi07JuM9c0qkwNOGql8D{7CXRW~X&H_5~ zE?-G*ypF0=0DmWK?RfzcYG)`%I$$K}>N<$_(yTR&0mo3e0Z8i&4}9$0wxRi!o-Z%1 zE~9c-veU?uuBp#!8xmMUDyM{sQ};BDEniC0e|F0idN-gRT6Bt+Q2}My^c8VShk+!B z!lj4Sq=^?d295-CAiJWcn&5DyKpQD-W>@>pK^xp!QK1@!NKYkZ`LQyC$#=)FC@QB2Y$IRi)>eT-_4z4xPpL%=Z zOH#hgF|xbyn@2YZPEGM(bLvZ0_rf?b@{H6*?P>IFthx7s4ek&jdwuo`p5V0oUBbc2 zC3?}eqnCK$wG+wlixq)DK~d2wZys`44e#j$$qqAH47iU_XymQ!rFYOn!oy9%Lrd}c zG1)oa!ZPxS)5an4x6|5*6UmoT+a5Vt*5F3X4T*Dyfl<#~k@mwihoCBF7sETp=XE(G zmPJZgXykMHrA@#E2J6;HBW9~7cx@8PTAo{?(Y?9u)?^8j>R*gFxFL$hT>7w7hD~&} zd}77@Z-o;UmgJbCtYi7Om_K)M)1H*X2h;}$iU>Vbat;s&=2c8R9qnXxL?)I&zcxIf zg7Uv5Bqk1i6~0Ase{_6*BrS`YlpSs)>)^A+_;pWJFr$e^%ipPWdr5I=kkN-a{it~h z%N3{a4m!K4e#kw>%PvUDuc|W3eo}nR#47uxt*MAgIcRQP->{v5*u_}8SY`BwP;6fp z93~*DoA@gKCn>rNmz-=@e0Q=ZhHl1F^&hNXnbYW*NIY87>JV(H(L_~2@;hdozIzM| z=7?ls64Wy=xP$2u9WEvmntUrUP=+HZ^E(p})6F5Ykz|pO91tMm6_7R`z`fJ;cBfuM zra(qFh*~xt;kemb8Z6o%uzq)9C(3G)jGoxP40ilfAX^*iXg8$%2N!FYu+LGI3Ytf) z`+O+>#~`}>M@!!?x_?VVg`+XFoMYNrMkhVX4W;7ZgnwXOoaoy?jyi}{00jjtmZG5} zdAb(fnhG$yCpFTw$h0S(Q7SyG?4Bv+4;ntvie{a%>s$mQR)yF^WQ{xT?o%f!WD!jxQ$`?tSeuTFiC zhp9bts8)sChO?VX&rhed^&J)GK|lE@`$cXIg5GvxkxR7NB!0%4i0 zwf`A;GBsw9r|eXtm5_k^ftUfm?rBIifz`=+l->fG*Ehlm1zTPaGotX>`fF;?wOSr&s z9hA@we9lX-E?{X!tGY^=2pjxSNUH%>Li9-=nd z^1z}I>Hb!>RnFV^UGA&DkQ%^AVlE05HrsyWb^N{Nn2M@^_d3W=ZSGX8F?u%}T)0Zd zUqsH@!F8K<27z3yU-+(Z){ey4-o|X1Q-+%Blc8Vke{ac;F5!?ebhO1SB$wk0C6yCn z4&#>*rEC$aB(!b2ZK=M(;jPpvAAXxnb+eHWg52n251JHLSS@W+e0$L(%C)9~J(`~D zva_3H81if@WaD%Tbk;R~GWXE&K4ADd+BEJUs68mZstiJXX%VP<_KVl#=2PxgQetdn z>`E6Ai*RfXL#;KM8U~3+n!oqcG=JasP)*^Wv&7=WmPugoT2l-ST55vlS6sBN6c!8DcDTZM6@*)GwwB+$C*;3BBS z2B$^WJ{nu(cM1_c4XHm0OE|~AFMx<`Gu%lj;^_M@v}KLYvGDCXM7oXa`;(&Gw~3xg zVxZn)hLjr$V)kuq67aQn0mquT8d|TI;HIZ;Q;V=}=x>u~wwFcAf3m-p?3XJy@|sho z6>qpQZh1>Ap>Dmbrb~}0)vX56lOs1Q3-a%Vrq>Q~n`O%Vk8a}|%#DP?Mvv!yZE%P= zE!6FozQk+`qBO?iAQ!N{S7!9HN|Ir@^jX+yMfjfaO9?#n4*bit_E{@vat49ncnF#T zp9T#aPohc+9rQ%>{_=hh-FUyHquhJa1kQBl(6U7%uH|3!Az6U&ZkGn(5AzSK{|YsE z)3RIBbxd9ToL|1nQ4aj6xMi!SWmbkS>$cB9(H4GsNKv8{d>Dqq=zA`ddpo3DNAEkp zz!W^-h*3OLm7tOnay4%oqR+bh^tBHNF4Ax=H+R)N8lC9k(pb?REFC!y{-%ZA)o>-e zSG)PUgvlU9I55^ZB}Z|#r j`R8xN_kGCM>Yh(ca?853aVZpj7!15NmWM*$R7sPA zDD*XJ9mNXpxr_72P`hx1oAz6>?cE~r%bsV?w|T_+%a&-}(*+$|=0g@l^BXmdqGaZ~IM4S(M};(H zQ5OlH+<2vO0_n#h=o9YydHffT^r^>^J9F!UW%fM$AakQG`YJ18R9~OujC@p*#~Jo3 zkXOJ3v@~?jsO^iNX(t#%tCe z1R3ZT;i$yJO5!?d1Fnz6>{a|9bU_khd4mjE@40!M2avHyL?sgTTb#f)*y|I_E7oTg z9%O){BB{_>j6}_s89+{vG@rRf15xnt%R5Q`JYL3*v^~?26mkTt)A>i;0Z}Z z2+?}kg@>OCFS%HZF>8hltO@)`R>Dpo&Fh$C>;8L?%tO$}-Um%y;hsC$n2i=Pt(vyV z@zZe}eUW|axZHB7oQ?;4AKAs(jv0E}JOy>?I1V}v$h4LT1P+ZZ-Ym*q+*UuF!|^ce zZtnb@{w93s@U>1Y{b(g55?op!vPZl^v%5-NUha|!f6%YHuXgHvSI3BojV$KGB$RrlfKLX+)_i zET|J@-oMT;G^ntAT0}>J7&o=N2^HU=(BO5poXO}Wy z|7YiXg{On!D|mxz2e+)|_Vcxb@AttecmN6NVOKqYxU;udKEEu0e9n-I{ir8!O-f+anh zwo7wY?G?xIud6G2ouIae_WS0lphJhU+{`+0DCk8jWzsF()2Cb3h^iW4bP%m3IIcF-`)RmFzf8rmQYf)XXwz@C3t;5#)2&~y zE3l%E3lWqm+uBUQ@Xh8M+$^sJ5X9u_i(cD06Z|ZtF?KTb64k?OQBCU3L1AkleTr%G zIgyEVizEaA36XS#5{U)AxkFZ&+I>?#3K9l#Z{;or%_-fZ#UWoH$F3GX%jjMiH0s-t z(U^)33Fm(-klnxe!s?q&;al9Vr$bPrT|A%D=|!_x(D-&|V-RPWDdZPV+V%Vdz(~Pd z0oHY`rULZ5-zB(@4vXyalg#rzO1I7NJFh|xRH>J>OFFk-&O}k(M6PAKqsv|XejOA4 zILE6zCwVv_fkU=igYEN+wp~YOWRX@o;)03%Auhp*FkzIvr&v^3S}>WCk4F+>zgvpl zn>K;$i%6-Y68^VV-In-FZx0{n%@F#{lAb+1+zGPA5268b*E5Vd!LB{`=dG!co1S@k zqQptXoBWI^3%@1eq0NV`rSRqW4gR@OfnQY!ZX1krxiLZ~Lz1SWS%MLiZ7wDDMSpD+ zb^RqnU{yc9_Pbi4?lTL zkP0G0w{Pz{{mteO9a>RG} zeP`Dpr=_jxP?RQg*P@lQ1bpnvn7SUIFHI+IOOa5e9MdGcv^;m@9oRV8M8WPQ#qFJu zKp__8=93-`eH<$ZGvtovZZg%%Ba}K3ctO^0eB@e`Ar&Nt4+}xWD-9Y<#e4*AN<*Wo z(VBS;8}%Aqr=*%RY|Kv;v3A9RR=dK)b|s4Gs0`Ij|1RIdWy$?*Bzy9lPkT8#iLd8~ zIu5Q@6h89-NVZ^IO)(bYX@!<0{_ADU3e`iH6%)>w@E2`QrPpnd+G6#}$z!_;6E--&JR9VrL4L3fo2mcGlA#ZMwNm+L;M2**k@ zz1)@6fv*%tD40ooZ@|g*MEfcr%Xd9uzyFs$`|M6AE}1|wv%fCnjwys)FvLaltajK| z*`Mat66%lfEE=@q-}lhuY3Sh; zpei@|2QdS+0&+G>Sv zN8a|st_RXm@GCB_4(xgUVhxbLzXUK5|2aMMQg46GYiAT1_u~9slIIyc%n8T4b8*<5 z0p{eoAQGQ6^O}{Gc=1KBrq|!g!Xq1V>lRKT^BXFyDCV}P@UCgwA>yUmx71@W4XPj1 z6w8wr<#T>5zpqMVZz5#VDG>Ev}#Qhx~68Dh#wrK0{e)vlK-{`|ZSIFY~BvIR!3fDYEM{ z&dnk|Y^Vq(jXme|i)%wA!X^MpXr4N#rgv7Ra1-+~`A|C9h|Vb;&B~p*w;d7bl2&AI+m=4Q{gKjU z=;etypMy|o+*mp-W5#Gg}jEV;o+$?Fe}Q^>49J4R{(lD|{)YIBIM z#uMAY_u#-5jS^Q5>6hh|!yPNW&t1dwflW+?r`YJ-jZis-RGE@Y3 zo#c8Hze`GyimpoVx}cX!^T`w8-u|6F^jG?!SvK>ByG3j9nX|8>&7uV|1QH7ELHcJ~ zwa7#mu#>Z6&Bo2zhbZewd>ycjuqsgS4BE3}Ucy?&!KeIX`{=n@bm}2#p7O^JpG`~m z=ibdB%w!pM0tf{BtFQFDkCtxz*EFFRze2bpCr&qjoak~`4a(B*Zqi*s?iQ&kO33%^Kcs-xe zM)_wX*s=@|kW-PP+bes2^<}^>5|mQ2fPcD_My#T%=U$*4!)vSuu}@Q}Usz|aX@B=`BD?YZEM$4h$u z7|vOLo$^c_CK}UTTmEvJM^5lvZir0m9t(-|q~kYTW4cckoR*eqV zR3o-H9Cneco6KoO>FnXt&}$}(;j>LwXGk3$)HiE!%(B747Nf>ji_SL|Dj6rt?Io23 zwrD+V^7qEyaBd8{MvC*gnUZYsuj8s|?7_0v(e>h$aSNDsr@rqC=XhIucpL3Q-6K8X zwEh;)Jt7IJa8ZA+IgB}Vt#rKLET+DjbV$qK)+rA$kvO<2Sju5?YA{~ zapt8t-uX#Q_5=t<#6Y+F&OZ{+2g=;0xY6a4jqBNqN{t!AkM=&1RIvb25uSp4(}U*| zx-9HYbtBj@r;f34aLW0PCX_8HKNgbQB&!3g2GDTFIH@Ls@e>)Avz|x7v=K|Y_&CEBX}10?e-1B#O1!k! zSn5Cd%8niy|6o-ZA~>MW7H5!V#Oki+gR>1yp7d~1uJo&tBY@(^aVb{rnckMEdu z1l|gHf_63*XtN@VBjvw;MiJC60q%1Cb7&_?nrp?B(b6|niv^kXC(z+y!O-gH;tbLPy zH@DnfmirrG1?hc7N8ukU2&lmh){rz#Q`(h*L(mYoAUB<~E&{lD^9WS?|5UEhUJ-^( z5%LA246bqB5?<{%XiIu)!V41`9DWnb?y)wwkgpH|e;m3gB_s)kXHlT7>OM#vw|N+* z2ASsZbow+HskE)Mny`k(+%r`Cybv2=H1we-?=eDa!Rd*M_0mI;a73cz_t2TK;!!LpEpUJ zyL=NWYplj$bSCi|CpkWi1o%&wLR2}|VQ)$J78AWCHlIzcF`#$WV}y@UROe*kKJg%Z zn3>Tn{0(2HG7;$0eX&zLS9pA>I(Cxsd@(})Sj{j&`E;Ap;N>(ge zC}yPwO3I7RUv583b~?v#G1qSXG^B%`IOKGN|9Njr_A%A@UC{l>NdqFvU{l_u@ zT235O(d4Mhqd<_mbGhHqC4p#LhRI&!lMKr}SaD-%-Vg#*io?H-T(quSR)&=E-L%!c zHW`YM@!W=59CLJ*R-^`-9DJ_2?m?b@Oz=rXkJB{YcdO{1_Ib_pxpcwu5^klPZRNoSd?Xa;}n_FB3ykP>WT$(z=BmY|Ai>!jk~WfSp+H&@1Q z3@KI?M-qtAh~pRRw&^6tDo8P|*E1Slb>`SX4kOoU@A_DZO7q-`tu zyiT5ppi_75+sDQff*0D%7q~{SU1?aqXX3P}*_DKo5CyyF&0*wJ_JfVez=m0vL5H*5SzC6W{-i8!+IgPg9@2`Jbx>2ZE8=cz zEq6OW7%J)lA~VdQ$dVHO`H75Tn!b8BxNRTXg)`9uoP2cGWW1 zXjb|{!3#y>#DP%@_F!Y8avK2#hDLaBKx#+6=yf+q@QF{|c1RG>E3Sl>0e87Uys<%d zzYeOkfV;LXH@~tAZrZ$E|7a<9#ZSs<{^`{oYlx(9R;u3PYF>=Lwlz)AqQQA&Fckuf)^@r35!^ww6{p+kV3qCaRx>s-q?L* zhLqx~%<{%|1X*LY$*2=cg(a6PVIMbX3Z2jXRm^GQ{j0fO8U5T9|Ief_5O2*9@5AH> z-p2*T91cetLT_HV&r+4J?HyItsy1#?>ex+15q#Xfp4T46qhB=W8Su)?PGS*H8N0Vp zPY28O8=Xx?k5MbfK_9?zl4}f+-N3Hb_~I5qfagC2S2&CMVxP~8ND7}O9d4!Xw}bDk zW3pSm-~=HxJN0}D?dl6;8?Y&$wJ1gEI7>3dpwKhLG}O%oyBp-RSt_EyqZqsMkZrJA z$~}#i-p?NS1)>{~3f}nIt*REeXntXvQvoQz_(6D@Hsvk0m`JZuYziE*IEBde2uK)j zogj*;m!jcyuK=eNSe1EFSb4sXu$)<6d*DrN{bc z?r^WZ8U{FC%u3m1Bjgm0NAC|t?|&)%X`l#6!YR}adeF!ZaAfaT<11pK)tfQ4GcRzH zLaZACO_9~4uRUJaYAT?yzZlt?jO29YlAYRu=G|CMz1W$FIOlgH+)V))yoxxX5cPtH zH?;$?Vrk`vX!yEbz1Dj|^=t6nM%ZEUMPHkeIOED6cBj~FDa6l*;j>EbA_n|kvs#4L zUj62k|3~(>Qb``-je|F@#f3>RsdMWJjKq;+Bh`o~s`|T%u$8A=AF}Ry`GQX^8<*S( zs{RTR@Gr!mwj+lCffsrrT~yxYYX0(`=@D_q2IGeY2zf8PvRdZuo8f8;svf z3rlmM<-N78ziSDF!o`O-$^!`pEnePkZ=^ZPcmINt9?R-@HZDdsvk-2$m74GYiPVIA!#0hFjw}A9pPdIsx?977$wldlBX%e`g zkYIprNI3_B2UYUvF(L*S%9+c!saa>N0jG#$Y-aVK61wJgXkPEIO0)dL_xnk4no91A zsMQ~%fOnt0CyyV2Q^Iu9N@W?D7wi(X+4$nfL*<^z&X#GV;D=D0fXdSnmmpPyX{$g% z#fB=gc28@~(M^oHa{9+%&Tbv8CkCqbDwl@y-y=$>rSEHGOQSg1;@>KV%}!4GFIGiL zj;cvp#6xoORz35q6?71xtVTXj-2`Hh_mg^0jQc-nf>VaKKl$9t3+-~Yvr&5nRFgSN z$?=LVc7yN-H98*`kSX~53uQ`B`6k`Tsle%O6DSD0Z+xI_xm@lrH93CWYOfa%6#Tu5 zH9cF-nc-@-aGk+vw!g`oKKNc3YrQx7T?b1^w7x?wbcjsMcT``qatKyVpQfTgzG;N!?gbbqcp})of*qg-sV6oD7@m09V)NV{TzN+0m2gr>>Aw&e&X> zllymlYHrIWBz?&}hW<4ocC(XSs$XR^;Lb2RgLCxLu>^cu|KpYRV9RO-G9C-6_W*uG ztryGY>#?1x*@PLS&LDj<;41iN(PBMp`D$GAC4W3py|M=D1NJcyY(d5wGY-8-9NaA$ zJ^hKuk@l#c{KLIp*5YrWy%Wpf-4n*+25{@)+TT%VwSaT$;)brQ^W%V2eV!@*i?9xT zB)}Tdu_T^NF#^q)CqCvcg@Yg#oW||0>(W4vnW>}=h^V@%wt6)|DI*bN*Z1gJcu8|3 zecFJjf3JTdEBMRCg%0&Xn)S=t_fniCGUch2J-<<{Q1ZRSL)ti`%UyviqM+Uoo7$z@ z@^HcqyR*bYLBIe~%$dD-@p~35i(G5mX@HLktF_UK{knGbO{NNB6KLLncG#1s%4$5& zea|>#Qg18{s~{YVm~*S)$^C%qwGOGDGA}&Qt|$J(N%NbhsRT!R{`&)@PJXA1fwbo` zB@m`7iv6Xxgd?-sMkitrhLl45IjY{vu#G%ok$#~w{z7*DvZCARA3WD(_R4(r>Toe4 zQr>{^eW56mqkt`dJ^$-FG5d>Ix*b_r+2$>lR9T94$bIh;=7D`bvdjktL@O`Xw+x#tKPGj&S)0I0&6(vk*NWWEx!fKinU(M zeMywfwtFr1MO+EcxnYuNTJhDhLZlXNM^nqg0t%Y>sAQJB(448QygM(2Z1(jDYz(W+ zbuQGsr#t<)GSF1-i9aL^T%S#WLh%Vl$-S^9-?~J7zGw^n;%a<3;f|{XNnFP5RIkmW zasb9%H}%*kM;Mr~Tnz3)c8lE-mA<=ANex z&!xjUR^qh{a@kC}9HNA>cV`#mUGQ7=AJK|?2H8tp*?I+shmcv}Y01P^*g*a& zh*hvicp(RNMSyn2lX+!9$#7W9Ap=#&6Ux(*TH})lCM7eA)xrndgdVUsrjk{Lco1)i ze@Cd+WSsNI<;}0-@gM_F?2YQ2n6!eM#x9LttkIKv%$6BB3Opv5)VjudRs5%TLXd2o zvn5|cMk7wDm16x2%3)E{PCP&1bw2e#|A8(c2}q{Kxva8M>O-|qr|3+`i6FDh$@c?oePYXcZc<RcP_xI0QZCzJYo0Zm62F_ZQQ9s@W-D*O?E7uAzv8*S{HRsi2clnCQy*~wA#082 z16)-H@Fq9bFP#IPyO9AhZUH+PS;I;XHwQXIqXBV)>`O|P66zQS_1Rq%(uQ3i@$W3M z+2NYWYQCS)!77J@DMHw8h;rSiqMNMX-|;tHH&yCFDm`xpF1YzI+-c(+%^&;TI>}F>1s!nRd=&4BGh9Q2DU>=;F=kyovq87cH6D!^iMx5`)CYDE$1R^xG7Gi)wdCs`yoomX+CMA?T_ux zjnlR1HU(%U{MTT@2qx4FtE9-Zi;f`tJauYF$~gRwWr7>goREvn z*UudM7U z;u>^ZH0KHT<%+YTs^gH)G0#TO(8=_D)wS z%BvCD+DVv;;KWgIvbUcogZ;>kh6=X0k;fxElF}BUlU=pYib7B8OnQ?_7UX|=h_1PJkmSyml zLMDu)Sx)&TiC(dSQQ^raK;C+{JFO8~vHO}ZUi@y5im#GCp@s8~=NkhpK8-;Opmd{j z6=UuKv0_^5@>Mm3j2Fgs-}0s7OuzA$kF66UZ(b-8gzXxE49N z-O`l!e;0Wrz=_Z^SkS@_J%J;;-%*T5Cz^{WP~cv4{9QJCP^1+4loJhnZTZR@*hP&7)7~E#)--1YjE^~!2FuP)DE^|K1R1H;eZ(AHn6G79^|(iN^aU%IW^-{lBwA+xp6avu38eHmHWzfs1qGd3x~ z@~43rSgqAI79Tcy@j9O?QfQES)px3lI5Rs zUOIGRj6ZbSe1x8qUgSqX4h0x(a}FL~T~e1A-w=b&U@a&EN8GtDHnX)-P^50*>l`4X zvHF)x1dmRLqY4XJ7dL=W{Do;K$4^>cr}M4hKojxUgajY@1VLAMbU8BE_Yej$%>4_$ zf>ga>pzG3g?t&Zh#ZNa0mfrWdb%~P#IU?n8j(c;F0oIW3S3;6Vvquc>Lq| z`?O73E+m!&?Ca=c@MY{7JW|5Rsbem7mKdjWn4L+Alo*bB^JQj&C+co+O`76lsq3l2 z!fT^-sjhHLz@PLp7bP>{X4N5&4su6O8RZq+Uu*2;^z%FoRBzz!1)K{%Q@d-X)=Ri+ zc*31_*nC2K)lVhW=3@Y1&?{9|FNiNBJiy*v&^d0Vlyv~jqcLOsPLj^BV(4=v0l8i5 zJz3A;#h$mh(f424K&dClav3_bdR$1=B(DkL*BusL7r6*SB^FWr?5&I6piw2+oEr}Y z<#RxqJH1RG^^#`LHu60g;QM}X;PySg`!R7QqWTI#Rub!vT|{Zb7wZ^flW}3Xc%{3+ zzt6l33IFW%yG;CY|I@nQ7eP7Ki{|2DH7^UWsX9%@3b~yQ0UMJ2>u`apXIj)P@oUbX zoY~&ynPm1)`nkLI+p+ARb{Bw&zu+J44*NqJu^}c=ydg=zU})EB$p4bR8hh*7rigT0=-JYaaY$= z|9NhoIU>oQ=xN3*xX~^_@^LDD;X}lqZXq!F2$Vo!HPKW{^imlxmoW;GieHqlCp6!p z?}w!;%7pw`bNiD9|Gb6v;bl@mKLv1mhGOZHoN^?EOyab*l{*%|0c3UiCxJ=0c=IF{+=l1F<)s)Jrg3(y3 zIL`8`%dy@4>-p#-%02%$*o4~l3As6Pd*+JLrAh2Q zk`T+Z+nyd&OBLm}<*9CMSvh|a3_6ZyAc>(K!{Y};L#3;8Jv?BAY^(32&gdW&V zpEp(Ans`1ecd}25nVl%Y6g$fx5}<<=^i?=(;vQOk73#g(j&)}LiKc@sb%RPcjAi*# zLPd!0sy0dNuX#A(%#Oc|6-E{RRuFDJO%UA0TaM#Ow1POBs+@pfKP8H$0gYok$*y^h zybi35<_&&J&uHzZYTN8t=;V zUG2ep;$_Il~9eVL@lw*#Q%h)H9Ej+;P%PS7~ZUW?zwW zf|yi{9d$0c5yv|QPN$LZp-UfdK9is3PA8&mtl37~(^G{sUMs2;d=+ge24^Vlyz|{T zm>$0@%*psAUjU?N0XX4(nJMwLd#OC(si}Y?fuG=p^v6g)H_0 z_ncE?(ZS$>c`q`(!BC^wTw04^p0M;|CH4DOiSa&DZ*qFBRy!jp;V`427YIu9MrA31 z;WUuIq&g^32BX_CL{I-Th7V7%DnN6`uoLLH^Oy5-Ng!`~H`hx^|8j(hxSbEEC4_cK zut*vEWiR=|`^G=21f<`?PF}$xZT`RmOJ25g@0RuOk?~db)qY(LZNLP4c0G}sSz27V zYKWQA=4CGb^7z{go^m}3JXHtj5WXy(9cws*>~a)z_4BKHsnjYvjL>3uaFX!Lw}^be zIDhWDP7CMI3K3w9gDM&ZpMjjqN z{mDff1-%P!7}HoI(lBjg6C?jR!Koux1!d8@Hi|@Z;_c{5r!W~h+{Hhl)(Lzrm zl=~WFEt=lncSUSx`>aequm7qj?}P^^ref+Ttt!CJ&Z4%U1SuRM~D3nRY`n zkK))yu)KHtJx&`-teOTM1oc6ymxT#Ds1F3`cW*a|F+=HkbfItlSX0ColIay=IJWac z4vX z8sjHZ!IjgB)?ZuhI|ydMG!<{P2`55!m^KZ*Z$q+zX^Gty$=(Ib3CaQ8Z;qV2CLJkC zW*bA~+)(SIBi^ZKhq!?FdvCikmYwXg$?4BI%P$l+U6nFeeOzYew|zIM*yy6-hiS?% zs3p;LNYU1Yl`xuHQ=Dm6{tziSu7@iv{r#m47J*1MZ$FQU(?3npc2ul+t)G^*oor+2 zlL%g@DfEly*DN614nELV*7;%o_cMi#WNB-P4bS19B~SgkuF|L6y>ZTyG$kHF1aHn) z>j-%sbo+91gVV7uhm){7iT#;bdFv=^FqCc7cdrP z#~UTkY}$N)RmLLJf&QhE0b0}o@VX1&^Esm<8Jpc3!f#9} zZK1P@^o*D3*)XwME&{+O8_0uA{dGuU@u%-q_nJpiWb6&n7T>LKLcX!Hdk8miWl_6X zvds*78~R3>BjF zJ+o=-P3ot&ue(1mQHnc|)VEiS6Cj$p&dr?Ezq>1j42%f8*(u}6`cP1#ebrzmG>hE8~tlxGT&a8Mo;_7a>@O7E(T214^R9 zXeLfuH-jK9QBV};CEKUO|PjB8PwZfdGcxAHi*fpRxC zCiUYcV`&^PZam!mHu|&F$fwb@^c*ftAmtV!002M$Nklikg=!S<)5`1tiuL_Huj`grcE!KS=sW_)?cH;L4!aWW&jE9xOV{H6uadL7j znbDF?@5ajM01CwE^@`hn1}{&IKZ!?yMf;k7G2kRJa}rG2HgnQ?9a_18wiCylM?^;+ zmBqQ=MsxKA`b7;+=BWu2sVOP`tPg3~GAFe?N<;E)!pm{I0!n$n>-4ZKzZMu7IEL9@ zLMt1`b{j-=8KyK=zxKXExKqbAlRKi<_(Ju;5*eKnJOl4kHSpxG{o1eDd4P8G0F?v) z)BkL3G54C+yyhHb_Kd0-o7j6KOzk>kjTf0;0zieNMra5|6&lvwd!tgkVp7}PX`&7* z+oIj?FquDZY)H$4bY?m)Q5z8*&u#GCE!*+2c^1kfyekBOqm{8SsqJnLujePs!`)rB z1;SL<*X()Tpo>Z1Ew70`r=7y3Zo$$PbXpjR6PPt7p=Igo;ET-ylJFXMr|otbE+fxr zJEqDe(6ai=^Pb|$O*Ds|rnB84qA@TX?Z~nuKdhCe*UTMBWOQn(Fo|j9R1UH*!`G@z-$YvXk(bTrIvI=ZMs(REsT2b>)=d})x0GJ0; zjXY17+18j#XQ$(KnM_T%JT?9@aLKPaLhH>#sOT%K!<9fjP>d^#^ zcLt-*fK!O|pt7)XnkCR&pmCC}8LZ->?js6q@|b5|aV$&Fi6GSH39nL`^Sm|b;;9Lk z2i`P5sN5wm8gko?hJ>j-98#~I>Ln#OcmsyC9Uk$_Xq)E&;7y^=wsRh!iU6=Rg{FTL zr9=Dwm&&eeMjqOgt^XtQdwnpCL#f8pAErd2$3~cMH;QW%9y3R5{6}YnX>HU&@n@Nc z={OCCos%f8`!5Se!ZSrf!Z*rq5soV@PxO0Ob6c?L?J!G^4#+U`aBS@IFQ-L5o0UOj z2R6b}Q_vD~t6msMSywwmng;M3cwy8fPvk9{+PoNDrJv$@8o--CQ(KT;Ogh5G{O~uy zRbE+|ke1Eb6jc+iaC8`Oj;*`1cCCR)>E+m-COI;qIMQrJe~Z5{w^@E6gH!ZOFsWZN z;GpY>gd?2itp+#CBiV-+PzH7gjb%VPCgaUc&gxXv1GE$XRDQPqPyX~OasS2bWNP%0 zCv5tPa)ZLT@dk^BaIEICYDlXqy1|LnS8J!??v8e>X54OIShoB+^`lHiPuao6s5Z+; zs~cea=%gHeVVlQ(xOEF`TAguvT-%2#2kX2^6oPUWw95G_%0)h+FXkw9dmOcshR4ny zM-+OO$6?#;HS(_?qa+7eV*Zpz@=2bxx6SsJ*~;%UWz(e=j=Ta7Uc3dzwqV>6g}L2W zI$55`8+quKFp_0U26CIjke3XR(U>~5dd|Xukk1_F1J4SkdO9bDqvpQB1^3lq)ybVZ(^vN$H_zS=A3oEr2+}w(&$Ipz6Yb|ro zu6K$I#j}50w`4TtYpI0N5sue51-G3ZDcZ=UGzW$TE!aAq)|9iFTI-Sa(79?TbZ=Y{ zy3be@x=veWr@=aP`NJ-P2xD$W7-Q;ShepHf-lJi9=b_n@XDa`vuLxahheM~rIv5?%b-;`hy7O>! zBFyYR7N&O}4%3ex3^RL!0MGTKPuPk7#DVwHu1`);Tn#Cd4xp4Bp$@$D4hcy%al6 zz@+JRH%+L2!9!g`)xNc(b~)#lfBBbJeBc8g*!$6sew4wT=|@x3QUKhj z`~M>}Z5hOCg z%tk-q!t8_3J}dNJc2?*)XHDRIcRL2X23MOW?|D2--Mu4BKYBo&a5i-6o_IX#AZlsr zwnJR7Fc=-1^?BO)>8!JwjfB4L(0kb#q5pZCL*K<4L)SXpToaR3HFFYvGism7`*lV8 zj%{J`K53mt)h-eQK3k%`%4MKPwsk1|4RCz}{U(UW&)~~15B)DVH*~LEW`bw-@K_kX zYkL^^+`VCH-_bCxb{LQj#YX=o?M||c<_PZo!3OQ3j8HE#x;TTdTYbaTq3^kyLJw(+ z+h-1rSh+?%e{YyRDkf67Oh8*tZcs}LY%eQH?AkSiNt1p~Jq-*rC`S!O^uQ}9#o--z zXt1IHP`$3d?z-zvmjHM|gLcSX32IuZmI8n*sQ^}On>TMhONqo`v`)s$LF1BO+@tQ$ zs6c13hSf;S*5AOO$TZDbnc4#U$~EgSQ#5MY$+Woll8s^L`in!K7@tvop{8@FFAQFC zrv2t#e`T21vNMc)VN00!&SRlN9k)}$jSg3*U$jgz>MN)tbs7<%gVEV-#8)NuzhHA1 zdc|d-?-`X3W6n#Hk+FNjO8X67_slT$sGe&3;(cNC%Ma)jnl#Yeli3Vy9;)SuY4_;J zXdI!J_lH%#@Ixj{Q|QiR{b5+MlKv~s3A_H_<}ml@xYY|wk7o5kd3}b#?nBwoZDM_B zfLk>fR=w?|p;u?~Q`4zDA=vvQ)%0K7B4>p&RXw5}DJ#S3@n+;h)8Tb{c!rG3kN#Fj$_%7jb-7}l;`yQxx6 zGNADt`w9i1^<*`yrge%-b# zzab2~Ko30}2lJ=t;O2^G|Fh2yYkuYBVa>0;B6L4eyN|K}gHyWhjSDSRLuy zghmh>@zqON*LSQL3M+r^XZ)Sozb}hPD6c)nVw0&0*~5h;`b! zc~l^?6Z9#gBO_t>N1r>7`E$Z@Fxy)5?_V5xmJLW4OOP4+&Tmi0@zZpEHpIh=yoiSC zWJa2@760~{dCc$d*uCei3(MbpjbMetQYiaJoYTV{4Iv9N^Jzso^m86Jwe3?&u}C*f zEp^+#p(PYE%41q;wqr6Lor7mHMPO6(Rsz6;WhMYRG>zHZlv*irV&}oA(X`P&JNl_c zpkdPjd*#)J(Ntbp(e31G;XKq~vE#q7W20eCYt)rL_I!2h*Mx!RpBvZ21wn|bVa4mOu#qcX7V_MCo5&Ue+RaYs90p%_LCY*LTS7=qYZ(A;q-2wc8eA9~ zW??H0=7}lCwgnJ8V}1$5u5;Ihfh#xF;Mem2)3tf6mJ6AZ(C9?Sd^S)tN<9*F!wM94 z#Zr~?Oj&4CJN%SWa-6hmd88?tnemN2y0-|d;L3P3kp7SHnHw z^k2Inta#H4Lg(PZr~9Pdj_HsdVA&1N)@~rbY&!@laM-OCB10Y4{!!BTKmO53FJTV^oh(H`vt zbXGYL*i--%g$v>Q^Uv4wcwHM?<{$%@kwR%&iWsCg1)}i2KN<^NqU3c_3U0>>2^tut znOQp+&it((3B%7nu_I=){Pk%maGC^AH=U0yM&&IQbADkFlB|0%-5j_PEK7)>M=Dmo z^(CQ8I~6C}oc5+`!oW+OY4vFvYs$8=74w#L(9V^WRYIXluqHUH9=tA!QZd`$MPQi4 z6h=i(&ra~PG?zi~l5 zx^Z}e(;xZ(f{?Qo@C;`erVeQtP|?7n7hZTFd|K&=u$KE0z6H2p!-iFCGgQonGCg8i zOikA8!wP+ajQ+J%?v+!IX1%y_SoG7OB+;3YivC(@viqd@J=IK)_UYHW^~K@w|MwHx z$LAbG+?SSo)LKtnTKPvnvQ^1Q#fiM-*IwB6ZpFyXePQJB{b5p_h7VeFYY%zgv=w1M z2Zj1pRvs8?s*k;lvGK7mpgnE%0d>-NLXxemb{^%R^^N(3uM_XaGb+A~#Bt|CFCD2~ z5ES(c!NkEN*?goPSluvi?4J>j?z(`B6zD~ud~QfuYxJO_x;7KA(}E5Nkm}ZSAOhqN z(d4Yod+5Bzl$O#}4LrJe^X7OOssd6|0g!GZ=xaa_t*l9#K0K)XbaiWubR+s`T}K`l13ju!eoq_(bedTuHBOO9I9cR$`r9nr|DKa|NP3y z9Qf`d;o-l(H5|VGu`sPeN|K1myC#&Ht67T&^=I1aW^Ac zlX|FfPR!riuTe=S)Tr<7ImE5%M&~QqF_94?6SLHTLF_V*%019G*pT^)G-?^S2ygAm zg+QqzLh(eIGG$Xps;y1nmFkXV7PCPfJYnf)2#!<5u)y+6Jh>0i|_ZDGrY8)QuDsN}fjtxBO0990_g5l4bJ_3zYC-c{OWRalOG0E^Q`4cL*kxi~ z8Oh6CyfHN(joFaenW5}-dPYWeK8h${yafQxu`^djprYm2W+$X^ViqO@9J`=V+sBiH zB8U}(={?81SsKIaRv;C1LQ6u3 zz>qQlFsDmEmB0T7;*@3pTxMA|yiA=@!;tDC3W1mxue!8}V@&b4fB`D{Vh2&jQ$ej` zs{^qmwB|MXki&}V8fjAD{Q6^``9|3KkuT}QmF_HP3U0cJ?EF1x#ALIntKcW}y!j)# z;kZM1=e+KPVe^k(YhQ^ea^2k3sUx#1f8=>#&wKxoxxcNe`A(v?O0spW1e(J8`6z{$ zu9cw-+y3Dzx;i!)^eT*I6*@bwA3HD10H`(UeYQ)JW(ngNjochzzURFk4ST+FZ+OPf zy&?>1c~H+B_~wJ*@sEDhj&k?vWaHG>gdNW{U5?Sss;3@ulQ7Q{ATU3jma`$uEQ*$f z{UcivhH*S+*4QIrT&uqbniM|v#h99hJKa{aJf7S}9R7-m)p-ml@;v{n2fG1%>SGhC zPZXYZ>{qi-4nWxnS?_lug0meZAbC?is2MpVtX0K!MbopM+f93zzXBxa4oPSZSywh= z8dX`(#nW6=ajX)rY$xO~<#3O8EQ6D0Li_uWV!BM{M{(4pBcID&_zXv>WKZj&%9j7} z>9G3?cNxr_>s`J zx_b2tUVcvKzx2#-KkzgY>{sXd26zefGu&K})uDqdbrJ7nBC z)9gZ9Z9V$*754tGZV7klQ&boH%*(?1YcH`fQ+d?uZq10cf9Ug}dqVdcdi `fTbt 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 From 33738b5285bbf8d78182c2b33d908b1928607d87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 26 Jan 2024 19:02:44 +0100 Subject: [PATCH 2745/4092] Update android icons --- osu.Android/AndroidManifest.xml | 6 +- .../drawable/ic_launcher_background.xml | 618 ++++++++++++++++++ osu.Android/Resources/drawable/lazer.png | Bin 39318 -> 0 bytes osu.Android/Resources/drawable/monochrome.xml | 24 + .../mipmap-anydpi-v26/ic_launcher.xml | 6 + .../Resources/mipmap-hdpi/ic_launcher.png | Bin 0 -> 6403 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 0 -> 7232 bytes .../Resources/mipmap-mdpi/ic_launcher.png | Bin 0 -> 3582 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 0 -> 4040 bytes .../Resources/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 9537 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 0 -> 11248 bytes .../Resources/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 16409 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 0 -> 21398 bytes .../Resources/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 25006 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 0 -> 35444 bytes 15 files changed, 653 insertions(+), 1 deletion(-) create mode 100644 osu.Android/Resources/drawable/ic_launcher_background.xml delete mode 100644 osu.Android/Resources/drawable/lazer.png create mode 100644 osu.Android/Resources/drawable/monochrome.xml create mode 100644 osu.Android/Resources/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 osu.Android/Resources/mipmap-hdpi/ic_launcher.png create mode 100644 osu.Android/Resources/mipmap-hdpi/ic_launcher_foreground.png create mode 100644 osu.Android/Resources/mipmap-mdpi/ic_launcher.png create mode 100644 osu.Android/Resources/mipmap-mdpi/ic_launcher_foreground.png create mode 100644 osu.Android/Resources/mipmap-xhdpi/ic_launcher.png create mode 100644 osu.Android/Resources/mipmap-xhdpi/ic_launcher_foreground.png create mode 100644 osu.Android/Resources/mipmap-xxhdpi/ic_launcher.png create mode 100644 osu.Android/Resources/mipmap-xxhdpi/ic_launcher_foreground.png create mode 100644 osu.Android/Resources/mipmap-xxxhdpi/ic_launcher.png create mode 100644 osu.Android/Resources/mipmap-xxxhdpi/ic_launcher_foreground.png diff --git a/osu.Android/AndroidManifest.xml b/osu.Android/AndroidManifest.xml index 492d48542e..a2b55257cb 100644 --- a/osu.Android/AndroidManifest.xml +++ b/osu.Android/AndroidManifest.xml @@ -1,7 +1,11 @@  - + diff --git a/osu.Android/Resources/drawable/ic_launcher_background.xml b/osu.Android/Resources/drawable/ic_launcher_background.xml new file mode 100644 index 0000000000..1af30228ec --- /dev/null +++ b/osu.Android/Resources/drawable/ic_launcher_background.xml @@ -0,0 +1,618 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/osu.Android/Resources/drawable/lazer.png b/osu.Android/Resources/drawable/lazer.png deleted file mode 100644 index fc7aa8a092cb616233bc83a3328413740bb57953..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39318 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 + + + + + + + diff --git a/osu.Android/Resources/mipmap-anydpi-v26/ic_launcher.xml b/osu.Android/Resources/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000000..7133c9c861 --- /dev/null +++ b/osu.Android/Resources/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/osu.Android/Resources/mipmap-hdpi/ic_launcher.png b/osu.Android/Resources/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..7870430484b7a5d9433afea0af2fe479ac0e967c GIT binary patch literal 6403 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 Rc+vm>002ovPDHLkV1m2~j8Omp literal 0 HcmV?d00001 diff --git a/osu.Android/Resources/mipmap-hdpi/ic_launcher_foreground.png b/osu.Android/Resources/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..b2ec3e49da191d5840d72a7ae2e5421dd468fecf GIT binary patch literal 7232 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 Ef^`evu>b%7 literal 0 HcmV?d00001 diff --git a/osu.Android/Resources/mipmap-mdpi/ic_launcher_foreground.png b/osu.Android/Resources/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..e22f2565625261d8ad104f2d09c70ffccb5d2ee8 GIT binary patch literal 4040 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 literal 0 HcmV?d00001 diff --git a/osu.Android/Resources/mipmap-xhdpi/ic_launcher.png b/osu.Android/Resources/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..b5e1a9e3798869f72105dec0643dfe18e8634a54 GIT binary patch literal 9537 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�>IX}``GPV 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 literal 0 HcmV?d00001 diff --git a/osu.Android/Resources/mipmap-xhdpi/ic_launcher_foreground.png b/osu.Android/Resources/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..1cc3fa9072d0bddb5511ec97d47bd80a7513c78a GIT binary patch literal 11248 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 JIpTig{{TBU2Ppsm literal 0 HcmV?d00001 diff --git a/osu.Android/Resources/mipmap-xxxhdpi/ic_launcher.png b/osu.Android/Resources/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..65751e15c9432447c18a4af6a03ec970ad82c945 GIT binary patch literal 25006 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 E0Sm8>N&o-= literal 0 HcmV?d00001 diff --git a/osu.Android/Resources/mipmap-xxxhdpi/ic_launcher_foreground.png b/osu.Android/Resources/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..05c6829a47625e8c647bcd3141594d1d5cea7b3a GIT binary patch literal 35444 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 Wm9}oz-ruXr00f?{elF{r5}E)w6xqZ8 literal 0 HcmV?d00001 From 2ccd0e3692d66b9b1dcf29daa666a09a459d2fde Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 27 Jan 2024 01:45:22 +0300 Subject: [PATCH 2746/4092] Add visual and failing test cases --- .../Visual/Ranking/TestSceneResultsScreen.cs | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 866e20d063..dbbfac75f7 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -26,8 +26,10 @@ using osu.Game.Scoring; using osu.Game.Screens; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; +using osu.Game.Screens.Ranking.Expanded.Accuracy; using osu.Game.Screens.Ranking.Expanded.Statistics; using osu.Game.Screens.Ranking.Statistics; +using osu.Game.Skinning; using osu.Game.Tests.Resources; using osuTK; using osuTK.Input; @@ -44,6 +46,9 @@ namespace osu.Game.Tests.Visual.Ranking [Resolved] private RealmAccess realm { get; set; } + [Resolved] + private SkinManager skins { get; set; } + protected override void LoadComplete() { base.LoadComplete(); @@ -59,6 +64,9 @@ namespace osu.Game.Tests.Visual.Ranking }); } + [SetUp] + public void SetUp() => Schedule(() => skins.CurrentSkinInfo.SetDefault()); + [Test] public void TestScaling() { @@ -132,6 +140,46 @@ namespace osu.Game.Tests.Visual.Ranking AddAssert("retry overlay present", () => screen.RetryOverlay != null); } + [Test] + public void TestResultsWithFailingRank() + { + TestResultsScreen screen = null; + + loadResultsScreen(() => + { + var score = TestResources.CreateTestScoreInfo(); + + score.OnlineID = onlineScoreID++; + score.HitEvents = TestSceneStatisticsPanel.CreatePositionDistributedHitEvents(); + score.Rank = ScoreRank.F; + return screen = createResultsScreen(score); + }); + AddUntilStep("wait for loaded", () => screen.IsLoaded); + AddAssert("retry overlay present", () => screen.RetryOverlay != null); + AddAssert("no badges displayed", () => this.ChildrenOfType().All(b => !b.IsPresent)); + } + + [Test] + public void TestResultsWithFailingRankOnLegacySkin() + { + TestResultsScreen screen = null; + + AddStep("set legacy skin", () => skins.CurrentSkinInfo.Value = skins.DefaultClassicSkin.SkinInfo); + + loadResultsScreen(() => + { + var score = TestResources.CreateTestScoreInfo(); + + score.OnlineID = onlineScoreID++; + score.HitEvents = TestSceneStatisticsPanel.CreatePositionDistributedHitEvents(); + score.Rank = ScoreRank.F; + return screen = createResultsScreen(score); + }); + AddUntilStep("wait for loaded", () => screen.IsLoaded); + AddAssert("retry overlay present", () => screen.RetryOverlay != null); + AddAssert("no badges displayed", () => this.ChildrenOfType().All(b => !b.IsPresent)); + } + [Test] public void TestShowHideStatisticsViaOutsideClick() { From 47f0b860186c720619b24df1ab0581bafb80b1a1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 27 Jan 2024 01:46:12 +0300 Subject: [PATCH 2747/4092] Fix results screen showing other rank badges on F rank --- .../Expanded/Accuracy/AccuracyCircle.cs | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 0aff98df2b..8cec79ad90 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -351,24 +351,28 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy int badgeNum = 0; - foreach (var badge in badges) + if (score.Rank != ScoreRank.F) { - if (badge.Accuracy > score.Accuracy) - continue; - - using (BeginDelayedSequence(inverseEasing(ACCURACY_TRANSFORM_EASING, Math.Min(accuracyX - virtual_ss_percentage, badge.Accuracy) / targetAccuracy) * ACCURACY_TRANSFORM_DURATION)) + foreach (var badge in badges) { - badge.Appear(); + if (badge.Accuracy > score.Accuracy) + continue; - if (withFlair) + using (BeginDelayedSequence( + inverseEasing(ACCURACY_TRANSFORM_EASING, Math.Min(accuracyX - virtual_ss_percentage, badge.Accuracy) / targetAccuracy) * ACCURACY_TRANSFORM_DURATION)) { - Schedule(() => - { - var dink = badgeNum < badges.Count - 1 ? badgeTickSound : badgeMaxSound; + badge.Appear(); - dink.FrequencyTo(1 + badgeNum++ * 0.05); - dink.Play(); - }); + if (withFlair) + { + Schedule(() => + { + var dink = badgeNum < badges.Count - 1 ? badgeTickSound : badgeMaxSound; + + dink.FrequencyTo(1 + badgeNum++ * 0.05); + dink.Play(); + }); + } } } } From d25262944ecd3a6a2d595b7ffc65c9cbb058e1f6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 27 Jan 2024 01:46:43 +0300 Subject: [PATCH 2748/4092] Force results screen to play default D rank applause sound on fail (regardless of skin) --- .../Expanded/Accuracy/AccuracyCircle.cs | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 8cec79ad90..60c35e6203 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -10,6 +10,7 @@ using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; @@ -98,6 +99,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy private PoolableSkinnableSample swooshUpSound; private PoolableSkinnableSample rankImpactSound; private PoolableSkinnableSample rankApplauseSound; + private DrawableSample rankFailSound; private readonly Bindable tickPlaybackRate = new Bindable(); @@ -133,7 +135,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy } [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio) { InternalChildren = new Drawable[] { @@ -267,6 +269,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy badgeTickSound = new PoolableSkinnableSample(new SampleInfo(@"Results/badge-dink")), badgeMaxSound = new PoolableSkinnableSample(new SampleInfo(@"Results/badge-dink-max")), swooshUpSound = new PoolableSkinnableSample(new SampleInfo(@"Results/swoosh-up")), + rankFailSound = new DrawableSample(audio.Samples.Get(results_applause_d_sound)), }); } } @@ -396,8 +399,16 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy { Schedule(() => { - rankApplauseSound.VolumeTo(applause_volume); - rankApplauseSound.Play(); + if (score.Rank != ScoreRank.F) + { + rankApplauseSound.VolumeTo(applause_volume); + rankApplauseSound.Play(); + } + else + { + rankFailSound.VolumeTo(applause_volume); + rankFailSound.Play(); + } }); } } @@ -440,6 +451,8 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy } } + private const string results_applause_d_sound = @"Results/applause-d"; + private string applauseSampleName { get @@ -448,7 +461,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy { default: case ScoreRank.D: - return @"Results/applause-d"; + return results_applause_d_sound; case ScoreRank.C: return @"Results/applause-c"; From bb6c7a0a82f7a19da7445706698c67360968301d Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 26 Jan 2024 15:26:22 -0800 Subject: [PATCH 2749/4092] Fix edit mod preset popover buttons overflowing on some languages --- osu.Game/Overlays/Mods/EditPresetPopover.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/EditPresetPopover.cs b/osu.Game/Overlays/Mods/EditPresetPopover.cs index 8bce57c96a..9554ba8ce2 100644 --- a/osu.Game/Overlays/Mods/EditPresetPopover.cs +++ b/osu.Game/Overlays/Mods/EditPresetPopover.cs @@ -92,7 +92,7 @@ namespace osu.Game.Overlays.Mods { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Direction = FillDirection.Horizontal, + RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Spacing = new Vector2(7), Children = new Drawable[] From 567d2bedbf928ab4260fd7fa90b48ac906e8d109 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 27 Jan 2024 03:03:01 +0300 Subject: [PATCH 2750/4092] Refactor ArgonSongProgress for same CurrentTime meaning --- .../Screens/Play/HUD/ArgonSongProgress.cs | 8 ++--- .../Screens/Play/HUD/ArgonSongProgressBar.cs | 36 ++++++------------- .../Screens/Play/HUD/DefaultSongProgress.cs | 6 +--- 3 files changed, 14 insertions(+), 36 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs index cb38854bca..a0e74dd6cd 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs @@ -114,12 +114,8 @@ namespace osu.Game.Screens.Play.HUD protected override void UpdateProgress(double progress, bool isIntro) { - bar.TrackTime = GameplayClock.CurrentTime; - - if (isIntro) - bar.CurrentTime = 0; - else - bar.CurrentTime = FrameStableClock.CurrentTime; + bar.CurrentTime = GameplayClock.CurrentTime; + bar.Progress = isIntro ? 0 : progress; } } } diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs index 5fe3b97f15..196d8ca278 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs @@ -22,16 +22,16 @@ namespace osu.Game.Screens.Play.HUD private readonly float barHeight; private readonly RoundedBar playfieldBar; - private readonly RoundedBar catchupBar; + private readonly RoundedBar audioBar; private readonly Box background; private readonly ColourInfo mainColour; private ColourInfo catchUpColour; - public double TrackTime { private get; set; } + public double Progress { get; set; } - private double length => EndTime - StartTime; + private double trackTime => (EndTime - StartTime) * Progress; public ArgonSongProgressBar(float barHeight) { @@ -49,13 +49,12 @@ namespace osu.Game.Screens.Play.HUD Alpha = 0, Colour = OsuColour.Gray(0.2f), }, - catchupBar = new RoundedBar + audioBar = new RoundedBar { Name = "Audio bar", Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, CornerRadius = 5, - AlwaysPresent = true, RelativeSizeAxes = Axes.Both }, playfieldBar = new RoundedBar @@ -70,17 +69,6 @@ namespace osu.Game.Screens.Play.HUD }; } - private float normalizedReference - { - get - { - if (EndTime - StartTime == 0) - return 1; - - return (float)((TrackTime - StartTime) / length); - } - } - [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -113,26 +101,24 @@ namespace osu.Game.Screens.Play.HUD { base.Update(); - playfieldBar.Length = (float)Interpolation.Lerp(playfieldBar.Length, NormalizedValue, Math.Clamp(Time.Elapsed / 40, 0, 1)); - catchupBar.Length = (float)Interpolation.Lerp(catchupBar.Length, normalizedReference, Math.Clamp(Time.Elapsed / 40, 0, 1)); + playfieldBar.Length = (float)Interpolation.Lerp(playfieldBar.Length, Progress, Math.Clamp(Time.Elapsed / 40, 0, 1)); + audioBar.Length = (float)Interpolation.Lerp(audioBar.Length, NormalizedValue, Math.Clamp(Time.Elapsed / 40, 0, 1)); - if (TrackTime < CurrentTime) - ChangeChildDepth(catchupBar, -1); + if (trackTime > CurrentTime) + ChangeChildDepth(audioBar, -1); else - ChangeChildDepth(catchupBar, 0); + ChangeChildDepth(audioBar, 0); - float timeDelta = (float)(Math.Abs(CurrentTime - TrackTime)); + float timeDelta = (float)Math.Abs(CurrentTime - trackTime); const float colour_transition_threshold = 20000; - catchupBar.AccentColour = Interpolation.ValueAt( + audioBar.AccentColour = Interpolation.ValueAt( Math.Min(timeDelta, colour_transition_threshold), mainColour, catchUpColour, 0, colour_transition_threshold, Easing.OutQuint); - - catchupBar.Alpha = Math.Max(1, catchupBar.Length); } private partial class RoundedBar : Container diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs index 48809796f3..be310df3a9 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs @@ -99,11 +99,7 @@ namespace osu.Game.Screens.Play.HUD protected override void UpdateProgress(double progress, bool isIntro) { bar.CurrentTime = GameplayClock.CurrentTime; - - if (isIntro) - graph.Progress = 0; - else - graph.Progress = (int)(graph.ColumnCount * progress); + graph.Progress = isIntro ? 0 : (int)(graph.ColumnCount * progress); } protected override void Update() From 3387565ba934acf70c6276c675c7c506e0b08d9a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 27 Jan 2024 03:11:12 +0300 Subject: [PATCH 2751/4092] Fix song progress value may go beyond 1 --- osu.Game/Screens/Play/HUD/SongProgress.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/SongProgress.cs b/osu.Game/Screens/Play/HUD/SongProgress.cs index 4391193df8..962752ba5a 100644 --- a/osu.Game/Screens/Play/HUD/SongProgress.cs +++ b/osu.Game/Screens/Play/HUD/SongProgress.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 System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -96,7 +97,7 @@ namespace osu.Game.Screens.Play.HUD if (objects == null) return; - double currentTime = FrameStableClock.CurrentTime; + double currentTime = Math.Min(FrameStableClock.CurrentTime, LastHitTime); bool isInIntro = currentTime < FirstHitTime; From 96dba035134cae64d86a602236a36c7d60cfaade Mon Sep 17 00:00:00 2001 From: aychar <58487401+hrfarmer@users.noreply.github.com> Date: Sat, 27 Jan 2024 00:06:01 -0600 Subject: [PATCH 2752/4092] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d5dc0723af..d7e710f392 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ A few resources are available as starting points to getting involved and underst - Detailed release changelogs are available on the [official osu! site](https://osu.ppy.sh/home/changelog/lazer). - You can learn more about our approach to [project management](https://github.com/ppy/osu/wiki/Project-management). -- Track our current efforts [towards full "ranked play" support](https://github.com/orgs/ppy/projects/13?query=is%3Aopen+sort%3Aupdated-desc). +- Track our current efforts [towards improving the game](https://github.com/orgs/ppy/projects/7/views/6). ## Running osu! From 71dd8e2729a6e0f3d44606cf1d0d5622e030c71d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 27 Jan 2024 19:16:54 +0900 Subject: [PATCH 2753/4092] Update iOS icons --- ...80-5cbe0121-ed68-414f-9ddc-dd993ac97e62.png | Bin 0 -> 278451 bytes .../AppIcon.appiconset/Contents.json | 15 ++++++++++++++- .../AppIcon.appiconset/iOSAppStore.png | Bin 350640 -> 0 bytes .../AppIcon.appiconset/iPadApp1x.png | Bin 10106 -> 0 bytes .../AppIcon.appiconset/iPadApp2x.png | Bin 24966 -> 0 bytes .../AppIcon.appiconset/iPadNotification1x.png | Bin 2227 -> 0 bytes .../AppIcon.appiconset/iPadNotification2x.png | Bin 4485 -> 0 bytes .../AppIcon.appiconset/iPadProApp2x.png | Bin 28089 -> 0 bytes .../AppIcon.appiconset/iPadSettings1x.png | Bin 3192 -> 0 bytes .../AppIcon.appiconset/iPadSettings2x.png | Bin 7156 -> 0 bytes .../AppIcon.appiconset/iPadSpotlight1x.png | Bin 4485 -> 0 bytes .../AppIcon.appiconset/iPadSpotlight2x.png | Bin 10768 -> 0 bytes .../AppIcon.appiconset/iPhoneApp2x.png | Bin 18204 -> 0 bytes .../AppIcon.appiconset/iPhoneApp3x.png | Bin 30946 -> 0 bytes .../iPhoneNotification2x.png | Bin 4485 -> 0 bytes .../iPhoneNotification3x.png | Bin 7458 -> 0 bytes .../AppIcon.appiconset/iPhoneSettings2x.png | Bin 7156 -> 0 bytes .../AppIcon.appiconset/iPhoneSettings3x.png | Bin 12085 -> 0 bytes .../AppIcon.appiconset/iPhoneSpotlight2x.png | Bin 10768 -> 0 bytes .../AppIcon.appiconset/iPhoneSpotlight3x.png | Bin 18204 -> 0 bytes osu.iOS/iTunesArtwork | Bin 142214 -> 0 bytes osu.iOS/iTunesArtwork@2x | Bin 350640 -> 0 bytes 22 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/300076680-5cbe0121-ed68-414f-9ddc-dd993ac97e62.png delete mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iOSAppStore.png delete mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadApp1x.png delete mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadApp2x.png delete mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadNotification1x.png delete mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadNotification2x.png delete mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadProApp2x.png delete mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadSettings1x.png delete mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadSettings2x.png delete mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadSpotlight1x.png delete mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadSpotlight2x.png delete mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneApp2x.png delete mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneApp3x.png delete mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneNotification2x.png delete mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneNotification3x.png delete mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneSettings2x.png delete mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneSettings3x.png delete mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneSpotlight2x.png delete mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneSpotlight3x.png delete mode 100644 osu.iOS/iTunesArtwork delete mode 100644 osu.iOS/iTunesArtwork@2x diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/300076680-5cbe0121-ed68-414f-9ddc-dd993ac97e62.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/300076680-5cbe0121-ed68-414f-9ddc-dd993ac97e62.png new file mode 100644 index 0000000000000000000000000000000000000000..21f5f0f3a0901ed465ec468164bce1817fb4ea22 GIT binary patch literal 278451 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

b;UQhmR-aO6N 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�k+(fIDq7qxZKK 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�IQvA; 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

-2ku4wGs_zr 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 z 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

?+l7 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�Kv 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 z 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 VRQY{WNd^D_002ovPDHLkV1gW!zV844 diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadNotification1x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadNotification1x.png deleted file mode 100644 index 8c483a0a7a92ab5a46e6cfcfa49f8fdf5ddb173b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2227 zcmV;k2u$~hP)Hg1+lHrgWSWcKdPn90sKGrRqvPeo9CG3uKX#J{(IASm?@+di}}l?o-=)F3E6 zwD^Ni=!>T7nL9I?X}YoAW$t|Qo$sD|?zw001?ah|SeB6#0T!CBEf+H4bBB+JJu8re zhoBb*p;u8ID_yBf0ya+zcePvJL&AGs+11_tpRKn>9TgyPA7ZoSs0)aX0r00)%XR^J z`jH<$>RKN5V(7OqK*TS4xZz{h!*f1C3ECFkK$#7nA@pGN!$;%jYv zwjAKwmYb0gKL(K8-kPtb5${A?tlI~wzMrJ6wTdBr=Y%%%EaEMQ&o}4FQ^DA)s*}Z> z!FI&AHCpoWI|RUqx?7s@$8!5^Q=anY%X@i5{QA6kNcMelpE>R6eCYFpmMsVT zrI(b06~u#xf1yS}_UGdMvD``!0~u->P=lA4?YN`hilQ z|3tHka)7T{2CGqwjZfMwx$5irQN_*|e4l)UHmiYuz74Yp1t^#>hrJ3-SOXDcC_o0^ z7T9R1gAN8V6s;5)ieI5-7aQlmJn}lUna#nz!j%5V$X|o`xX!dHWQRV27P1=rj;t2b zW$~+pTw@bIek?ZvKPDL<64`^#UNTAck#RBsB6*5DP4<%UA_FqU$I>2EH_cM;u)Q~SI+rg`Rn{L z_AC5qq~L$#SMj%U$6Cz0vP{G5Y*=%5RT^yu;}-DInZ=349rJPVM6C3K^oO)8y(fJr{l>k`ead~!ea?NsT>_Ci%bnxC;Vy6= zb6>{xYV#Ue-+LB$7`JEXmTRm^AtP)R9u{)KHsMiWGV&)32xCG~*nyU<>-!d;FP=Re z4r3qYr~6#KE>;1F`>_J_P5xC?ROxV(DIHdCO*p$HRQI@7^PwV@Pvuf+ z5K}u-6REM(K@W$srgorh0{i?O)v0c>QtHxU-hBdD(>iYJ4b2sIOVX2K8m~4gmYVA5 zh^QEb$V`rCQ-|7ZS{nuL-t>?3n=-o(6I(7vocj#GzCZEo`!3>+v;dYIfPu#&ZWzzX z2i^rZ^Mu;6+rb@?NPG+6)c5T6zxpzGe*M(x+{AON=PiJ>H#?ob-|uwRK0yDg0B4PV z0id6JRRdfL?*ISMZUrf*k6*ChPpT+|w%)~?&LZ-nb17Y3hFmq)L#-QuEuI;_O zzdYwVMi9SEyY}95Kj(ZtpL5PcPfu@12SNxudH9;=dGLK-UFP3>q#+@7`M-E|lkan2 zys@ zorE0Ap?WEYT45b7G0=ntUDA7d4Dr5w(B4hq`>|1+J^LjBdXArGR(fk|>o5blu7^*C zhH&QS2^2s53C@*y$^wvzu%Z^E89^XI1a%LXdjKQ5g5HyFA|iE6{544lg_2B(W|JOA z`VM1o;60Q+y9jS_6`mpB2fnJBvxh-IMri7xNZdkrw}jw}-*CF?b@cbXjDWM#(Kwj( z`S625gcoiAOC@Ngp(xZnR|)U>9z3FD5G8)ssDP>-z+NpwpIyP3L+`*eO$DiOU?!bb z22?N4K@bIsMM%^_a#nbx4X83o>eOsiv0bSm=uSZI>x46V2dTz3?0c?*l6sIn^;(Jm zH_ z3cR%qB%7KMl6D%2MNLb#`vEp=qC%iSp+X=o{8AMgKl}l?zZJ5j5#BvAB4&`&F0kPs zBu0ibsGTAVqmIUoEZqCu5ogS#E@u@E> z>r`o56Rxe@#&rH~NCxCGTmty!yK^v(zX*NTGtfwIp%#Up&7vb>utu93iTlm6O!%au zo@j*3wBg&!7nGsA7xZK@In2TTEG)p--hjiWjv|eGe5%Z?o1@8a=002ovPDHLkV1i)_ BI!^!q diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadNotification2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadNotification2x.png deleted file mode 100644 index a45b01b91c65e3a8d8feefe5122bef85c77a78fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4485 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 X5L}ax?^OsQ00000NkvXXu0mjf-pQ8F diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadProApp2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadProApp2x.png deleted file mode 100644 index d2ba8f3a7ee97837b87d619cb0f7c1f3243d7591..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28089 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 Y0}pcCLt@!TD*ylh07*qoM6N<$f{85HlmGw# diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadSettings1x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadSettings1x.png deleted file mode 100644 index 43d577040eeca37d88ca15030a9994678c44f14a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3192 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 X5L}ax?^OsQ00000NkvXXu0mjf-pQ8F diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadSpotlight2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadSpotlight2x.png deleted file mode 100644 index 717603dd684e9a71f55243678b954a70b89f0a13..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10768 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

rD-NlRi2>YR3M-xKh z9c4Kh`=aD%&%Ndl%vr+n}RYro+f%j>+#f{sNr+KALrZ~C8bYLjxa@X>BCtfCeq)hRhhs? z-~Ze8kq>^v>i6|nL7#UsKJXM&7f7IQ(oXjL*|Yt8=K*~{qg%%f_dd1Bk3Ou|TxhF~ z2R;?RLiDUj3Ce<@%1B4+NMiZeWYZ-&PXE^PtU-h`2#bZxI%d(;2}@#BvKTk7g^9y+ z+U;{+_?&&}Z$B#>X-AnK$A@;mOJC9E1dwPl($bZjwyd%li%*|(_C-NEjWm9lkd#unpyJ7=zK)B?Fr-tiZ9*AMTJ!78ttRC177RC3a{L)=iQ ziNBvx0*C4bl^qiqLU&7=r|EkGzy7Q5w)gz{do+<;#bVEI`ukM=gjLIj!l=b9RwR4E%ntJ5Nj4jgyMV-8EAae!#FgIqyHd;Ln%J#0GK3n2Lw@H zPOxDKl7?(WCIgPBKjzp3S;`I}e)5!*Pkzh*=?iYABQ3gyyY0?zf6Ko34}WhPHg5Fw z%Pf+kp9L2IAED0;S)Ite8lKa6O&Eb8T<~F|dU&Q1G%5_S2m`Pnd6&2*6C;dSET#$1 zSiZvE^~>+Hx4h*oHhbD>a&4-gdu@LrK_DAM$HY!DEFoMEs5|;&JZ3CH;4UU`=SfKC zeXQz&M3W~MV_BZ5B6*|pQNW2oEqIP|o-hK3X-0@iC1^yb3=IoJrf|Z;l@L0SW+c0>FCp}T zdGqbfx7}vH@C$FV^UlI zm&P;iCWOEgL;*C6DMuuk`Cb4*QYqY>G3OE<-UoGqt2C%@^62f=7dF*@tz5a%ZWFPu zx#k*Mdio-{Je5-YqG|Yq)5JdkAfro#C4_?Lz*xlNy;nhh{LXXfH|dd5so$LXW1Fkt z5MSYz=HI-1tNrwW2kh><@3!Zjd(I9YIqYq1(Q+fS71{|zC+$s$OHpy#c|H*ccqV`F z9r6U1xH&Og(|Dc`0z-_@$52=x7!^QrA#iw~ZrBK+QQILcgdQ1Q^(j3cyO=72?ELf3 zvsJ6Ew5zVVQr7n}ecwsQhu}yxYr{B2R(+|TMTPGhwHs6~1Oy-;#22FY>;T6y<~UQD z)B89wP^SmUf$jw5sNM~mHrk_)K5CCW`k1}2<^|igZ@*7=z|eT1KsfCHxvY9*+|RqD zDCaW&eX@TL9&ieAacAei|!XHzExT4OpkF`aOI1+H0FO*{kc<+nP0NY~9**wr9^CKeYf=$UJQzCddS- zTrf2T6`6zhKwxs8C}5*m!a$|tCQE`v{z&&JBQOYu2q1k#MN0zIScHaE4{6m=t7&t@u~Y|Q<0DTDG|KIji;*;3ty(+W(J|SkPoM4=Gc;4COtJP!?WH|E$)b3s2>W__ z{iO6GI&1mxp+k01*Wts5?TAj;?a?Rg1T{nTY5 zCkz6vLia^E{00JZHF9E}97$meJU|`*_sVO8=!8RL z`X1CI4W!}D7`)SSvfQ-P&6YGY5}eeIsZmLw(lV=rM6w2pIo@P}d3;a$(?lI7e)fP& z+8Pi|dy(W5T|+ezrb%0a=gSP%fKLzBFD~X zK|*L2AB;#ijT;)O(?!3Bkt2nx#uB%#o&GV=BA!9UpM(z+0tG_F_k_@}LmY>-QpxEy zw2zB2xv)7-6>+IPngJ#Pz%u}oSHts!5g4S!SOts>9 z!U!B1L!$#{g|Lad2LvW}2$vuf1cP*xaft`Y#2&x=K-?r|D1)a!^sGFHmz0_H_SA;e zisuO@@TqN9I`>M#ByW>2BsnHjI%WF#c;cqh{=fYE|2cuHqxRn`!K$}7`_2Ca1L!4r T1Z_6R00000NkvXXu0mjfx&MOR diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneApp3x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneApp3x.png deleted file mode 100644 index 78ef8d12b73234c4a0d842db591fddf8646b5efa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30946 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 Va~q+uY%u@;002ovPDHLkV1i*114sY> diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneNotification2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneNotification2x.png deleted file mode 100644 index a45b01b91c65e3a8d8feefe5122bef85c77a78fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4485 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 X5L}ax?^OsQ00000NkvXXu0mjf-pQ8F diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneNotification3x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneNotification3x.png deleted file mode 100644 index 46ddf1179de6fe5a5d0ea2907a1b2823ad0d14b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7458 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

#ey>)?|3_`yyL|7a`H`_C`|$RPW{X#!{8F#Ruy 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 diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneSettings2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneSettings2x.png deleted file mode 100644 index 1ebec1390b7d9dd55bda6e79f655d2ddbf5114dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7156 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 diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneSpotlight2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneSpotlight2x.png deleted file mode 100644 index 717603dd684e9a71f55243678b954a70b89f0a13..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10768 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

rD-NlRi2>YR3M-xKh z9c4Kh`=aD%&%Ndl%vr+n}RYro+f%j>+#f{sNr+KALrZ~C8bYLjxa@X>BCtfCeq)hRhhs? z-~Ze8kq>^v>i6|nL7#UsKJXM&7f7IQ(oXjL*|Yt8=K*~{qg%%f_dd1Bk3Ou|TxhF~ z2R;?RLiDUj3Ce<@%1B4+NMiZeWYZ-&PXE^PtU-h`2#bZxI%d(;2}@#BvKTk7g^9y+ z+U;{+_?&&}Z$B#>X-AnK$A@;mOJC9E1dwPl($bZjwyd%li%*|(_C-NEjWm9lkd#unpyJ7=zK)B?Fr-tiZ9*AMTJ!78ttRC177RC3a{L)=iQ ziNBvx0*C4bl^qiqLU&7=r|EkGzy7Q5w)gz{do+<;#bVEI`ukM=gjLIj!l=b9RwR4E%ntJ5Nj4jgyMV-8EAae!#FgIqyHd;Ln%J#0GK3n2Lw@H zPOxDKl7?(WCIgPBKjzp3S;`I}e)5!*Pkzh*=?iYABQ3gyyY0?zf6Ko34}WhPHg5Fw z%Pf+kp9L2IAED0;S)Ite8lKa6O&Eb8T<~F|dU&Q1G%5_S2m`Pnd6&2*6C;dSET#$1 zSiZvE^~>+Hx4h*oHhbD>a&4-gdu@LrK_DAM$HY!DEFoMEs5|;&JZ3CH;4UU`=SfKC zeXQz&M3W~MV_BZ5B6*|pQNW2oEqIP|o-hK3X-0@iC1^yb3=IoJrf|Z;l@L0SW+c0>FCp}T zdGqbfx7}vH@C$FV^UlI zm&P;iCWOEgL;*C6DMuuk`Cb4*QYqY>G3OE<-UoGqt2C%@^62f=7dF*@tz5a%ZWFPu zx#k*Mdio-{Je5-YqG|Yq)5JdkAfro#C4_?Lz*xlNy;nhh{LXXfH|dd5so$LXW1Fkt z5MSYz=HI-1tNrwW2kh><@3!Zjd(I9YIqYq1(Q+fS71{|zC+$s$OHpy#c|H*ccqV`F z9r6U1xH&Og(|Dc`0z-_@$52=x7!^QrA#iw~ZrBK+QQILcgdQ1Q^(j3cyO=72?ELf3 zvsJ6Ew5zVVQr7n}ecwsQhu}yxYr{B2R(+|TMTPGhwHs6~1Oy-;#22FY>;T6y<~UQD z)B89wP^SmUf$jw5sNM~mHrk_)K5CCW`k1}2<^|igZ@*7=z|eT1KsfCHxvY9*+|RqD zDCaW&eX@TL9&ieAacAei|!XHzExT4OpkF`aOI1+H0FO*{kc<+nP0NY~9**wr9^CKeYf=$UJQzCddS- zTrf2T6`6zhKwxs8C}5*m!a$|tCQE`v{z&&JBQOYu2q1k#MN0zIScHaE4{6m=t7&t@u~Y|Q<0DTDG|KIji;*;3ty(+W(J|SkPoM4=Gc;4COtJP!?WH|E$)b3s2>W__ z{iO6GI&1mxp+k01*Wts5?TAj;?a?Rg1T{nTY5 zCkz6vLia^E{00JZHF9E}97$meJU|`*_sVO8=!8RL z`X1CI4W!}D7`)SSvfQ-P&6YGY5}eeIsZmLw(lV=rM6w2pIo@P}d3;a$(?lI7e)fP& z+8Pi|dy(W5T|+ezrb%0a=gSP%fKLzBFD~X zK|*L2AB;#ijT;)O(?!3Bkt2nx#uB%#o&GV=BA!9UpM(z+0tG_F_k_@}LmY>-QpxEy zw2zB2xv)7-6>+IPngJ#Pz%u}oSHts!5g4S!SOts>9 z!U!B1L!$#{g|Lad2LvW}2$vuf1cP*xaft`Y#2&x=K-?r|D1)a!^sGFHmz0_H_SA;e zisuO@@TqN9I`>M#ByW>2BsnHjI%WF#c;cqh{=fYE|2cuHqxRn`!K$}7`_2Ca1L!4r T1Z_6R00000NkvXXu0mjfx&MOR diff --git a/osu.iOS/iTunesArtwork b/osu.iOS/iTunesArtwork deleted file mode 100644 index 1939459992eba68e1067c5af904fa25624cef86d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 142214 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 Z{y%t>pLdzx&T#+$002ovPDHLkV1j&!dny0` diff --git a/osu.iOS/iTunesArtwork@2x b/osu.iOS/iTunesArtwork@2x deleted file mode 100644 index 0e8bb029bc964d313c07fea891a1fcfa1b10e139..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 350640 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 z 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

?+l7 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�Kv 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 Date: Sat, 27 Jan 2024 20:21:27 +0900 Subject: [PATCH 2754/4092] 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 a7376aa5a7..969fd52340 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 98e8b136e5..bbcabc6360 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From 9d73d0e04d08a235587a874f56d1492aadcfba51 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 27 Jan 2024 19:06:04 +0300 Subject: [PATCH 2755/4092] Clamp logo triangles along both axes --- osu.Game/Screens/Menu/OsuLogo.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index a39d552a90..25101730e7 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -187,7 +187,6 @@ namespace osu.Game.Screens.Menu }, triangles = new TrianglesV2 { - ClampAxes = Axes.X, Anchor = Anchor.Centre, Origin = Anchor.Centre, Thickness = 0.009f, From d38779f9620deae86c6ce0be2d0d916411c3b359 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 27 Jan 2024 23:22:29 +0300 Subject: [PATCH 2756/4092] Add failing test case --- .../Multiplayer/TestSceneMultiplayerPlayer.cs | 40 ++++++++++++++----- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs index cbeff770c9..aaf85dab7c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs @@ -3,13 +3,18 @@ #nullable disable +using System; +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Scoring; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.Play; @@ -19,14 +24,37 @@ namespace osu.Game.Tests.Visual.Multiplayer { private MultiplayerPlayer player; - [SetUpSteps] - public override void SetUpSteps() + [Test] + public void TestGameplay() { - base.SetUpSteps(); + setup(); + AddUntilStep("wait for gameplay start", () => player.LocalUserPlaying.Value); + } + + [Test] + public void TestFail() + { + setup(() => new[] { new OsuModAutopilot() }); + + AddUntilStep("wait for gameplay start", () => player.LocalUserPlaying.Value); + AddStep("set health zero", () => player.ChildrenOfType().Single().Health.Value = 0); + AddUntilStep("wait for fail", () => player.ChildrenOfType().Single().HasFailed); + AddAssert("fail animation not shown", () => !player.GameplayState.HasFailed); + + // ensure that even after reaching a failed state, score processor keeps accounting for new hit results. + // the testing method used here (autopilot + hold key) is sort-of dodgy, but works enough. + AddAssert("score is zero", () => player.GameplayState.ScoreProcessor.TotalScore.Value == 0); + AddStep("hold key", () => player.ChildrenOfType().First().TriggerPressed(OsuAction.LeftButton)); + AddUntilStep("score changed", () => player.GameplayState.ScoreProcessor.TotalScore.Value > 0); + } + + private void setup(Func> mods = null) + { AddStep("set beatmap", () => { Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); + SelectedMods.Value = mods?.Invoke() ?? Array.Empty(); }); AddStep("Start track playing", () => @@ -52,11 +80,5 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("gameplay clock is not paused", () => !player.ChildrenOfType().Single().IsPaused.Value); AddAssert("gameplay clock is running", () => player.ChildrenOfType().Single().IsRunning); } - - [Test] - public void TestGameplay() - { - AddUntilStep("wait for gameplay start", () => player.LocalUserPlaying.Value); - } } } From ea641bb8d2cf7d307d508c0f1d8fb81e3ef773d7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 27 Jan 2024 19:09:46 +0300 Subject: [PATCH 2757/4092] Rename `GameplayState.HasFailed` to properly clarify its meaning --- .../TestSceneTaikoSuddenDeath.cs | 2 +- .../Visual/Gameplay/TestSceneFailAnimation.cs | 2 +- .../Visual/Gameplay/TestSceneFailJudgement.cs | 2 +- osu.Game.Tests/Visual/Gameplay/TestScenePause.cs | 12 ++++++------ .../Gameplay/TestScenePlayerScoreSubmission.cs | 4 ++-- .../Visual/Gameplay/TestSceneSpectator.cs | 2 +- .../Gameplay/TestSceneStoryboardWithOutro.cs | 4 ++-- .../Visual/Mods/TestSceneModAccuracyChallenge.cs | 4 ++-- .../Multiplayer/TestSceneMultiplayerPlayer.cs | 2 +- .../Navigation/TestSceneScreenNavigation.cs | 2 +- osu.Game/Online/Spectator/SpectatorClient.cs | 2 +- osu.Game/Screens/Play/GameplayState.cs | 7 +++++-- osu.Game/Screens/Play/Player.cs | 16 ++++++++-------- .../Screens/Play/PlayerTouchInputDetector.cs | 2 +- 14 files changed, 33 insertions(+), 30 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs index 9e45197b04..4b78e0a005 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Taiko.Tests Player.ScoreProcessor.NewJudgement += _ => judged = true; }); AddUntilStep("swell judged", () => judged); - AddAssert("not failed", () => !Player.GameplayState.HasFailed); + AddAssert("not failed", () => !Player.GameplayState.ShownFailAnimation); } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs index b251253b7c..369bcfac4e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs @@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected override void AddCheckSteps() { - AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); + AddUntilStep("wait for fail", () => Player.GameplayState.ShownFailAnimation); AddUntilStep("wait for fail overlay", () => ((FailPlayer)Player).FailOverlay.State.Value == Visibility.Visible); // The pause screen and fail animation both ramp frequency. diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs index 6297b062dd..4fb6e9783c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs @@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected override void AddCheckSteps() { AddUntilStep("player is playing", () => Player.LocalUserPlaying.Value); - AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); + AddUntilStep("wait for fail", () => Player.GameplayState.ShownFailAnimation); AddAssert("player is not playing", () => !Player.LocalUserPlaying.Value); AddUntilStep("wait for multiple judgements", () => ((FailPlayer)Player).ScoreProcessor.JudgedHits > 1); AddAssert("total number of results == 1", () => diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 73aa3be73d..32f5d7cc55 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -224,7 +224,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestPauseAfterFail() { - AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); + AddUntilStep("wait for fail", () => Player.GameplayState.ShownFailAnimation); AddUntilStep("fail overlay shown", () => Player.FailOverlayVisible); confirmClockRunning(false); @@ -240,7 +240,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestExitFromFailedGameplayAfterFailAnimation() { - AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); + AddUntilStep("wait for fail", () => Player.GameplayState.ShownFailAnimation); AddUntilStep("wait for fail overlay shown", () => Player.FailOverlayVisible); confirmClockRunning(false); @@ -252,7 +252,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestExitFromFailedGameplayDuringFailAnimation() { - AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); + AddUntilStep("wait for fail", () => Player.GameplayState.ShownFailAnimation); // will finish the fail animation and show the fail/pause screen. pauseViaBackAction(); @@ -266,7 +266,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestQuickRetryFromFailedGameplay() { - AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); + AddUntilStep("wait for fail", () => Player.GameplayState.ShownFailAnimation); AddStep("quick retry", () => Player.GameplayClockContainer.ChildrenOfType().First().Action?.Invoke()); confirmExited(); @@ -275,7 +275,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestQuickExitFromFailedGameplay() { - AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); + AddUntilStep("wait for fail", () => Player.GameplayState.ShownFailAnimation); exitViaQuickExitAction(); confirmExited(); @@ -380,7 +380,7 @@ namespace osu.Game.Tests.Visual.Gameplay { confirmClockRunning(false); confirmNotExited(); - AddAssert("player not failed", () => !Player.GameplayState.HasFailed); + AddAssert("player not failed", () => !Player.GameplayState.ShownFailAnimation); AddAssert("pause overlay shown", () => Player.PauseOverlayVisible); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index 5e22e47572..a4f71bcaad 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -171,7 +171,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for token request", () => Player.TokenCreationRequested); - AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); + AddUntilStep("wait for fail", () => Player.GameplayState.ShownFailAnimation); AddStep("exit", () => Player.Exit()); AddAssert("ensure no submission", () => Player.SubmittedScore == null); @@ -188,7 +188,7 @@ namespace osu.Game.Tests.Visual.Gameplay addFakeHit(); - AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); + AddUntilStep("wait for fail", () => Player.GameplayState.ShownFailAnimation); AddUntilStep("wait for submission", () => Player.SubmittedScore != null); AddAssert("ensure failing submission", () => Player.SubmittedScore.ScoreInfo.Passed == false); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 1c7ede2b19..b0bb42921d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -353,7 +353,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("send failed", () => spectatorClient.SendEndPlay(streamingUser.Id, SpectatedUserState.Failed)); AddUntilStep("state is failed", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Failed); - AddUntilStep("wait for player to fail", () => player.GameplayState.HasFailed); + AddUntilStep("wait for player to fail", () => player.GameplayState.ShownFailAnimation); start(); sendFrames(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs index 98825b27d4..e2e5aac734 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs @@ -102,7 +102,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("set storyboard duration to 0.6s", () => currentStoryboardDuration = 600); }); - AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); + AddUntilStep("wait for fail", () => Player.GameplayState.ShownFailAnimation); AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration); AddUntilStep("wait for fail overlay", () => Player.FailOverlay.State.Value == Visibility.Visible); } @@ -116,7 +116,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("set storyboard duration to 0s", () => currentStoryboardDuration = 0); }); AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration); - AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); + AddUntilStep("wait for fail", () => Player.GameplayState.ShownFailAnimation); AddUntilStep("wait for fail overlay", () => Player.FailOverlay.State.Value == Visibility.Visible); AddUntilStep("wait for button clickable", () => Player.ChildrenOfType().First().ChildrenOfType().First().Enabled.Value); diff --git a/osu.Game.Tests/Visual/Mods/TestSceneModAccuracyChallenge.cs b/osu.Game.Tests/Visual/Mods/TestSceneModAccuracyChallenge.cs index c5e56c6453..4fc6fe1b77 100644 --- a/osu.Game.Tests/Visual/Mods/TestSceneModAccuracyChallenge.cs +++ b/osu.Game.Tests/Visual/Mods/TestSceneModAccuracyChallenge.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.Mods Position = new Vector2(i * 50) }).Cast().ToList() }, - PassCondition = () => Player.GameplayState.HasFailed && Player.ScoreProcessor.JudgedHits >= 3 + PassCondition = () => Player.GameplayState.ShownFailAnimation && Player.ScoreProcessor.JudgedHits >= 3 }); [Test] @@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Mods Position = new Vector2(i * 50) }).Cast().ToList() }, - PassCondition = () => Player.GameplayState.HasFailed && Player.ScoreProcessor.JudgedHits >= 1 + PassCondition = () => Player.GameplayState.ShownFailAnimation && Player.ScoreProcessor.JudgedHits >= 1 }); } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs index aaf85dab7c..175fd3127d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs @@ -40,7 +40,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for gameplay start", () => player.LocalUserPlaying.Value); AddStep("set health zero", () => player.ChildrenOfType().Single().Health.Value = 0); AddUntilStep("wait for fail", () => player.ChildrenOfType().Single().HasFailed); - AddAssert("fail animation not shown", () => !player.GameplayState.HasFailed); + AddAssert("fail animation not shown", () => !player.GameplayState.ShownFailAnimation); // ensure that even after reaching a failed state, score processor keeps accounting for new hit results. // the testing method used here (autopilot + hold key) is sort-of dodgy, but works enough. diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index f59fbc75ac..083a5dc833 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -392,7 +392,7 @@ namespace osu.Game.Tests.Visual.Navigation return (player = Game.ScreenStack.CurrentScreen as Player) != null; }); - AddUntilStep("wait for fail", () => player.GameplayState.HasFailed); + AddUntilStep("wait for fail", () => player.GameplayState.ShownFailAnimation); AddUntilStep("wait for track stop", () => !Game.MusicController.IsPlaying); AddAssert("Ensure time before preview point", () => Game.MusicController.CurrentTrack.CurrentTime < beatmap().BeatmapInfo.Metadata.PreviewTime); diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 7911701853..a9a666a360 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -251,7 +251,7 @@ namespace osu.Game.Online.Spectator if (state.HasPassed) currentState.State = SpectatedUserState.Passed; - else if (state.HasFailed) + else if (state.ShownFailAnimation) currentState.State = SpectatedUserState.Failed; else currentState.State = SpectatedUserState.Quit; diff --git a/osu.Game/Screens/Play/GameplayState.cs b/osu.Game/Screens/Play/GameplayState.cs index cc399a0fbe..f64bcc9a3c 100644 --- a/osu.Game/Screens/Play/GameplayState.cs +++ b/osu.Game/Screens/Play/GameplayState.cs @@ -46,9 +46,12 @@ namespace osu.Game.Screens.Play public bool HasPassed { get; set; } ///

- /// Whether the user failed during gameplay. This is only set when the gameplay session has completed due to the fail. + /// Whether the user failed during gameplay and the fail animation has been displayed. /// - public bool HasFailed { get; set; } + /// + /// In multiplayer, this is never set to true even if the player reached zero health, due to being turned off. + /// + public bool ShownFailAnimation { get; set; } /// /// Whether the user quit gameplay without having either passed or failed. diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ad1f9ec897..ff1deecc3d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -489,7 +489,7 @@ namespace osu.Game.Screens.Play private void updateGameplayState() { - bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value && !DrawableRuleset.IsPaused.Value && !breakTracker.IsBreakTime.Value && !GameplayState.HasFailed; + bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value && !DrawableRuleset.IsPaused.Value && !breakTracker.IsBreakTime.Value && !GameplayState.ShownFailAnimation; OverlayActivationMode.Value = inGameplay ? OverlayActivation.Disabled : OverlayActivation.UserTriggered; localUserPlaying.Value = inGameplay; } @@ -586,7 +586,7 @@ namespace osu.Game.Screens.Play if (showDialogFirst && !pauseOrFailDialogVisible) { // if the fail animation is currently in progress, accelerate it (it will show the pause dialog on completion). - if (ValidForResume && GameplayState.HasFailed) + if (ValidForResume && GameplayState.ShownFailAnimation) { failAnimationContainer.FinishTransforms(true); return false; @@ -733,7 +733,7 @@ namespace osu.Game.Screens.Play } // Only show the completion screen if the player hasn't failed - if (GameplayState.HasFailed) + if (GameplayState.ShownFailAnimation) return; GameplayState.HasPassed = true; @@ -922,11 +922,11 @@ namespace osu.Game.Screens.Play if (Configuration.AllowFailAnimation) { - Debug.Assert(!GameplayState.HasFailed); + Debug.Assert(!GameplayState.ShownFailAnimation); Debug.Assert(!GameplayState.HasPassed); Debug.Assert(!GameplayState.HasQuit); - GameplayState.HasFailed = true; + GameplayState.ShownFailAnimation = true; updateGameplayState(); @@ -1002,13 +1002,13 @@ namespace osu.Game.Screens.Play // replays cannot be paused and exit immediately && !DrawableRuleset.HasReplayLoaded.Value // cannot pause if we are already in a fail state - && !GameplayState.HasFailed; + && !GameplayState.ShownFailAnimation; private bool canResume => // cannot resume from a non-paused state GameplayClockContainer.IsPaused.Value // cannot resume if we are already in a fail state - && !GameplayState.HasFailed + && !GameplayState.ShownFailAnimation // already resuming && !IsResuming; @@ -1142,7 +1142,7 @@ namespace osu.Game.Screens.Play { Debug.Assert(resultsDisplayDelegate == null); - if (!GameplayState.HasFailed) + if (!GameplayState.ShownFailAnimation) GameplayState.HasQuit = true; if (DrawableRuleset.ReplayScore == null) diff --git a/osu.Game/Screens/Play/PlayerTouchInputDetector.cs b/osu.Game/Screens/Play/PlayerTouchInputDetector.cs index 12fb748e7d..13c94f7b32 100644 --- a/osu.Game/Screens/Play/PlayerTouchInputDetector.cs +++ b/osu.Game/Screens/Play/PlayerTouchInputDetector.cs @@ -37,7 +37,7 @@ namespace osu.Game.Screens.Play if (!touchActive.Value) return; - if (gameplayState.HasPassed || gameplayState.HasFailed || gameplayState.HasQuit) + if (gameplayState.HasPassed || gameplayState.ShownFailAnimation || gameplayState.HasQuit) return; if (gameplayState.Score.ScoreInfo.Mods.OfType().Any()) From a25be9927df56d845f1a95f8a74bd535c9273fa0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 27 Jan 2024 19:12:03 +0300 Subject: [PATCH 2758/4092] Fix score processor no longer applying results when failing in multiplayer match --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 6 ------ osu.Game/Screens/Play/Player.cs | 6 ++++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index a092829317..7d50dd4665 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -211,9 +211,6 @@ namespace osu.Game.Rulesets.Scoring result.ComboAtJudgement = Combo.Value; result.HighestComboAtJudgement = HighestCombo.Value; - if (result.FailedAtJudgement) - return; - ScoreResultCounts[result.Type] = ScoreResultCounts.GetValueOrDefault(result.Type) + 1; if (result.Type.IncreasesCombo()) @@ -267,9 +264,6 @@ namespace osu.Game.Rulesets.Scoring Combo.Value = result.ComboAtJudgement; HighestCombo.Value = result.HighestComboAtJudgement; - if (result.FailedAtJudgement) - return; - ScoreResultCounts[result.Type] = ScoreResultCounts.GetValueOrDefault(result.Type) - 1; if (result.Judgement.MaxResult.AffectsAccuracy()) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ff1deecc3d..e2470285ba 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -357,6 +357,9 @@ namespace osu.Game.Screens.Play DrawableRuleset.NewResult += r => { + if (GameplayState.ShownFailAnimation) + return; + HealthProcessor.ApplyResult(r); ScoreProcessor.ApplyResult(r); GameplayState.ApplyResult(r); @@ -364,6 +367,9 @@ namespace osu.Game.Screens.Play DrawableRuleset.RevertResult += r => { + if (GameplayState.ShownFailAnimation) + return; + HealthProcessor.RevertResult(r); ScoreProcessor.RevertResult(r); }; From 5f689998930a5b37021a2ace9f6c76d69d07239d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 27 Jan 2024 20:44:34 +0300 Subject: [PATCH 2759/4092] Fix `TestSceneFailJudgement` asserts no longer being correct --- .../Visual/Gameplay/TestSceneFailJudgement.cs | 2 +- osu.Game/Rulesets/UI/Playfield.cs | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs index 4fb6e9783c..339746ac8b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("player is playing", () => Player.LocalUserPlaying.Value); AddUntilStep("wait for fail", () => Player.GameplayState.ShownFailAnimation); AddAssert("player is not playing", () => !Player.LocalUserPlaying.Value); - AddUntilStep("wait for multiple judgements", () => ((FailPlayer)Player).ScoreProcessor.JudgedHits > 1); + AddUntilStep("wait for multiple judgements", () => ((FailPlayer)Player).DrawableRuleset.Playfield.AllEntries.Count(e => e.Judged) > 1); AddAssert("total number of results == 1", () => { var score = new ScoreInfo { Ruleset = Ruleset.Value }; diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index e9c35555c8..176223729a 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -78,6 +78,25 @@ namespace osu.Game.Rulesets.UI } } + /// + /// All the s contained in this and all . + /// + public IEnumerable AllEntries + { + get + { + if (HitObjectContainer == null) + return Enumerable.Empty(); + + var enumerable = HitObjectContainer.Entries; + + if (nestedPlayfields.Count != 0) + enumerable = enumerable.Concat(NestedPlayfields.SelectMany(p => p.AllEntries)); + + return enumerable; + } + } + /// /// All s nested inside this . /// From 64b61108ad5b389ce136385393eb3eba9a4f9bc8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 28 Jan 2024 00:34:23 +0300 Subject: [PATCH 2760/4092] Move solution to multiplayer flow instead --- .../TestSceneTaikoSuddenDeath.cs | 2 +- .../Visual/Gameplay/TestSceneFailAnimation.cs | 2 +- .../Visual/Gameplay/TestSceneFailJudgement.cs | 4 ++-- .../Visual/Gameplay/TestScenePause.cs | 12 +++++----- .../TestScenePlayerScoreSubmission.cs | 4 ++-- .../Visual/Gameplay/TestSceneSpectator.cs | 2 +- .../Gameplay/TestSceneStoryboardWithOutro.cs | 4 ++-- .../Mods/TestSceneModAccuracyChallenge.cs | 4 ++-- .../Multiplayer/TestSceneMultiplayerPlayer.cs | 2 +- .../Navigation/TestSceneScreenNavigation.cs | 2 +- osu.Game/Online/Spectator/SpectatorClient.cs | 2 +- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 8 +++++++ osu.Game/Rulesets/UI/Playfield.cs | 19 ---------------- .../Multiplayer/MultiplayerPlayer.cs | 2 ++ osu.Game/Screens/Play/GameplayState.cs | 7 ++---- osu.Game/Screens/Play/Player.cs | 22 +++++++------------ .../Screens/Play/PlayerTouchInputDetector.cs | 2 +- 17 files changed, 41 insertions(+), 59 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs index 4b78e0a005..9e45197b04 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Taiko.Tests Player.ScoreProcessor.NewJudgement += _ => judged = true; }); AddUntilStep("swell judged", () => judged); - AddAssert("not failed", () => !Player.GameplayState.ShownFailAnimation); + AddAssert("not failed", () => !Player.GameplayState.HasFailed); } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs index 369bcfac4e..b251253b7c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs @@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected override void AddCheckSteps() { - AddUntilStep("wait for fail", () => Player.GameplayState.ShownFailAnimation); + AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); AddUntilStep("wait for fail overlay", () => ((FailPlayer)Player).FailOverlay.State.Value == Visibility.Visible); // The pause screen and fail animation both ramp frequency. diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs index 339746ac8b..6297b062dd 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs @@ -22,9 +22,9 @@ namespace osu.Game.Tests.Visual.Gameplay protected override void AddCheckSteps() { AddUntilStep("player is playing", () => Player.LocalUserPlaying.Value); - AddUntilStep("wait for fail", () => Player.GameplayState.ShownFailAnimation); + AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); AddAssert("player is not playing", () => !Player.LocalUserPlaying.Value); - AddUntilStep("wait for multiple judgements", () => ((FailPlayer)Player).DrawableRuleset.Playfield.AllEntries.Count(e => e.Judged) > 1); + AddUntilStep("wait for multiple judgements", () => ((FailPlayer)Player).ScoreProcessor.JudgedHits > 1); AddAssert("total number of results == 1", () => { var score = new ScoreInfo { Ruleset = Ruleset.Value }; diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 32f5d7cc55..73aa3be73d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -224,7 +224,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestPauseAfterFail() { - AddUntilStep("wait for fail", () => Player.GameplayState.ShownFailAnimation); + AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); AddUntilStep("fail overlay shown", () => Player.FailOverlayVisible); confirmClockRunning(false); @@ -240,7 +240,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestExitFromFailedGameplayAfterFailAnimation() { - AddUntilStep("wait for fail", () => Player.GameplayState.ShownFailAnimation); + AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); AddUntilStep("wait for fail overlay shown", () => Player.FailOverlayVisible); confirmClockRunning(false); @@ -252,7 +252,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestExitFromFailedGameplayDuringFailAnimation() { - AddUntilStep("wait for fail", () => Player.GameplayState.ShownFailAnimation); + AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); // will finish the fail animation and show the fail/pause screen. pauseViaBackAction(); @@ -266,7 +266,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestQuickRetryFromFailedGameplay() { - AddUntilStep("wait for fail", () => Player.GameplayState.ShownFailAnimation); + AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); AddStep("quick retry", () => Player.GameplayClockContainer.ChildrenOfType().First().Action?.Invoke()); confirmExited(); @@ -275,7 +275,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestQuickExitFromFailedGameplay() { - AddUntilStep("wait for fail", () => Player.GameplayState.ShownFailAnimation); + AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); exitViaQuickExitAction(); confirmExited(); @@ -380,7 +380,7 @@ namespace osu.Game.Tests.Visual.Gameplay { confirmClockRunning(false); confirmNotExited(); - AddAssert("player not failed", () => !Player.GameplayState.ShownFailAnimation); + AddAssert("player not failed", () => !Player.GameplayState.HasFailed); AddAssert("pause overlay shown", () => Player.PauseOverlayVisible); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index a4f71bcaad..5e22e47572 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -171,7 +171,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for token request", () => Player.TokenCreationRequested); - AddUntilStep("wait for fail", () => Player.GameplayState.ShownFailAnimation); + AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); AddStep("exit", () => Player.Exit()); AddAssert("ensure no submission", () => Player.SubmittedScore == null); @@ -188,7 +188,7 @@ namespace osu.Game.Tests.Visual.Gameplay addFakeHit(); - AddUntilStep("wait for fail", () => Player.GameplayState.ShownFailAnimation); + AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); AddUntilStep("wait for submission", () => Player.SubmittedScore != null); AddAssert("ensure failing submission", () => Player.SubmittedScore.ScoreInfo.Passed == false); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index b0bb42921d..1c7ede2b19 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -353,7 +353,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("send failed", () => spectatorClient.SendEndPlay(streamingUser.Id, SpectatedUserState.Failed)); AddUntilStep("state is failed", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Failed); - AddUntilStep("wait for player to fail", () => player.GameplayState.ShownFailAnimation); + AddUntilStep("wait for player to fail", () => player.GameplayState.HasFailed); start(); sendFrames(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs index e2e5aac734..98825b27d4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs @@ -102,7 +102,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("set storyboard duration to 0.6s", () => currentStoryboardDuration = 600); }); - AddUntilStep("wait for fail", () => Player.GameplayState.ShownFailAnimation); + AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration); AddUntilStep("wait for fail overlay", () => Player.FailOverlay.State.Value == Visibility.Visible); } @@ -116,7 +116,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("set storyboard duration to 0s", () => currentStoryboardDuration = 0); }); AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration); - AddUntilStep("wait for fail", () => Player.GameplayState.ShownFailAnimation); + AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); AddUntilStep("wait for fail overlay", () => Player.FailOverlay.State.Value == Visibility.Visible); AddUntilStep("wait for button clickable", () => Player.ChildrenOfType().First().ChildrenOfType().First().Enabled.Value); diff --git a/osu.Game.Tests/Visual/Mods/TestSceneModAccuracyChallenge.cs b/osu.Game.Tests/Visual/Mods/TestSceneModAccuracyChallenge.cs index 4fc6fe1b77..c5e56c6453 100644 --- a/osu.Game.Tests/Visual/Mods/TestSceneModAccuracyChallenge.cs +++ b/osu.Game.Tests/Visual/Mods/TestSceneModAccuracyChallenge.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.Mods Position = new Vector2(i * 50) }).Cast().ToList() }, - PassCondition = () => Player.GameplayState.ShownFailAnimation && Player.ScoreProcessor.JudgedHits >= 3 + PassCondition = () => Player.GameplayState.HasFailed && Player.ScoreProcessor.JudgedHits >= 3 }); [Test] @@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Mods Position = new Vector2(i * 50) }).Cast().ToList() }, - PassCondition = () => Player.GameplayState.ShownFailAnimation && Player.ScoreProcessor.JudgedHits >= 1 + PassCondition = () => Player.GameplayState.HasFailed && Player.ScoreProcessor.JudgedHits >= 1 }); } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs index 175fd3127d..aaf85dab7c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs @@ -40,7 +40,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for gameplay start", () => player.LocalUserPlaying.Value); AddStep("set health zero", () => player.ChildrenOfType().Single().Health.Value = 0); AddUntilStep("wait for fail", () => player.ChildrenOfType().Single().HasFailed); - AddAssert("fail animation not shown", () => !player.GameplayState.ShownFailAnimation); + AddAssert("fail animation not shown", () => !player.GameplayState.HasFailed); // ensure that even after reaching a failed state, score processor keeps accounting for new hit results. // the testing method used here (autopilot + hold key) is sort-of dodgy, but works enough. diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 083a5dc833..f59fbc75ac 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -392,7 +392,7 @@ namespace osu.Game.Tests.Visual.Navigation return (player = Game.ScreenStack.CurrentScreen as Player) != null; }); - AddUntilStep("wait for fail", () => player.GameplayState.ShownFailAnimation); + AddUntilStep("wait for fail", () => player.GameplayState.HasFailed); AddUntilStep("wait for track stop", () => !Game.MusicController.IsPlaying); AddAssert("Ensure time before preview point", () => Game.MusicController.CurrentTrack.CurrentTime < beatmap().BeatmapInfo.Metadata.PreviewTime); diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index a9a666a360..7911701853 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -251,7 +251,7 @@ namespace osu.Game.Online.Spectator if (state.HasPassed) currentState.State = SpectatedUserState.Passed; - else if (state.ShownFailAnimation) + else if (state.HasFailed) currentState.State = SpectatedUserState.Failed; else currentState.State = SpectatedUserState.Quit; diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 7d50dd4665..9d12daad04 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -181,6 +181,8 @@ namespace osu.Game.Rulesets.Scoring private readonly List hitEvents = new List(); private HitObject? lastHitObject; + public bool ApplyNewJudgementsWhenFailed { get; set; } + public ScoreProcessor(Ruleset ruleset) { Ruleset = ruleset; @@ -211,6 +213,9 @@ namespace osu.Game.Rulesets.Scoring result.ComboAtJudgement = Combo.Value; result.HighestComboAtJudgement = HighestCombo.Value; + if (result.FailedAtJudgement && !ApplyNewJudgementsWhenFailed) + return; + ScoreResultCounts[result.Type] = ScoreResultCounts.GetValueOrDefault(result.Type) + 1; if (result.Type.IncreasesCombo()) @@ -264,6 +269,9 @@ namespace osu.Game.Rulesets.Scoring Combo.Value = result.ComboAtJudgement; HighestCombo.Value = result.HighestComboAtJudgement; + if (result.FailedAtJudgement && !ApplyNewJudgementsWhenFailed) + return; + ScoreResultCounts[result.Type] = ScoreResultCounts.GetValueOrDefault(result.Type) - 1; if (result.Judgement.MaxResult.AffectsAccuracy()) diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 176223729a..e9c35555c8 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -78,25 +78,6 @@ namespace osu.Game.Rulesets.UI } } - /// - /// All the s contained in this and all . - /// - public IEnumerable AllEntries - { - get - { - if (HitObjectContainer == null) - return Enumerable.Empty(); - - var enumerable = HitObjectContainer.Entries; - - if (nestedPlayfields.Count != 0) - enumerable = enumerable.Concat(NestedPlayfields.SelectMany(p => p.AllEntries)); - - return enumerable; - } - } - /// /// All s nested inside this . /// diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index d9043df1d5..c5c536eae6 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -67,6 +67,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (!LoadedBeatmapSuccessfully) return; + ScoreProcessor.ApplyNewJudgementsWhenFailed = true; + LoadComponentAsync(new GameplayChatDisplay(Room) { Expanded = { BindTarget = LeaderboardExpandedState }, diff --git a/osu.Game/Screens/Play/GameplayState.cs b/osu.Game/Screens/Play/GameplayState.cs index f64bcc9a3c..cc399a0fbe 100644 --- a/osu.Game/Screens/Play/GameplayState.cs +++ b/osu.Game/Screens/Play/GameplayState.cs @@ -46,12 +46,9 @@ namespace osu.Game.Screens.Play public bool HasPassed { get; set; } /// - /// Whether the user failed during gameplay and the fail animation has been displayed. + /// Whether the user failed during gameplay. This is only set when the gameplay session has completed due to the fail. /// - /// - /// In multiplayer, this is never set to true even if the player reached zero health, due to being turned off. - /// - public bool ShownFailAnimation { get; set; } + public bool HasFailed { get; set; } /// /// Whether the user quit gameplay without having either passed or failed. diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index e2470285ba..ad1f9ec897 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -357,9 +357,6 @@ namespace osu.Game.Screens.Play DrawableRuleset.NewResult += r => { - if (GameplayState.ShownFailAnimation) - return; - HealthProcessor.ApplyResult(r); ScoreProcessor.ApplyResult(r); GameplayState.ApplyResult(r); @@ -367,9 +364,6 @@ namespace osu.Game.Screens.Play DrawableRuleset.RevertResult += r => { - if (GameplayState.ShownFailAnimation) - return; - HealthProcessor.RevertResult(r); ScoreProcessor.RevertResult(r); }; @@ -495,7 +489,7 @@ namespace osu.Game.Screens.Play private void updateGameplayState() { - bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value && !DrawableRuleset.IsPaused.Value && !breakTracker.IsBreakTime.Value && !GameplayState.ShownFailAnimation; + bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value && !DrawableRuleset.IsPaused.Value && !breakTracker.IsBreakTime.Value && !GameplayState.HasFailed; OverlayActivationMode.Value = inGameplay ? OverlayActivation.Disabled : OverlayActivation.UserTriggered; localUserPlaying.Value = inGameplay; } @@ -592,7 +586,7 @@ namespace osu.Game.Screens.Play if (showDialogFirst && !pauseOrFailDialogVisible) { // if the fail animation is currently in progress, accelerate it (it will show the pause dialog on completion). - if (ValidForResume && GameplayState.ShownFailAnimation) + if (ValidForResume && GameplayState.HasFailed) { failAnimationContainer.FinishTransforms(true); return false; @@ -739,7 +733,7 @@ namespace osu.Game.Screens.Play } // Only show the completion screen if the player hasn't failed - if (GameplayState.ShownFailAnimation) + if (GameplayState.HasFailed) return; GameplayState.HasPassed = true; @@ -928,11 +922,11 @@ namespace osu.Game.Screens.Play if (Configuration.AllowFailAnimation) { - Debug.Assert(!GameplayState.ShownFailAnimation); + Debug.Assert(!GameplayState.HasFailed); Debug.Assert(!GameplayState.HasPassed); Debug.Assert(!GameplayState.HasQuit); - GameplayState.ShownFailAnimation = true; + GameplayState.HasFailed = true; updateGameplayState(); @@ -1008,13 +1002,13 @@ namespace osu.Game.Screens.Play // replays cannot be paused and exit immediately && !DrawableRuleset.HasReplayLoaded.Value // cannot pause if we are already in a fail state - && !GameplayState.ShownFailAnimation; + && !GameplayState.HasFailed; private bool canResume => // cannot resume from a non-paused state GameplayClockContainer.IsPaused.Value // cannot resume if we are already in a fail state - && !GameplayState.ShownFailAnimation + && !GameplayState.HasFailed // already resuming && !IsResuming; @@ -1148,7 +1142,7 @@ namespace osu.Game.Screens.Play { Debug.Assert(resultsDisplayDelegate == null); - if (!GameplayState.ShownFailAnimation) + if (!GameplayState.HasFailed) GameplayState.HasQuit = true; if (DrawableRuleset.ReplayScore == null) diff --git a/osu.Game/Screens/Play/PlayerTouchInputDetector.cs b/osu.Game/Screens/Play/PlayerTouchInputDetector.cs index 13c94f7b32..12fb748e7d 100644 --- a/osu.Game/Screens/Play/PlayerTouchInputDetector.cs +++ b/osu.Game/Screens/Play/PlayerTouchInputDetector.cs @@ -37,7 +37,7 @@ namespace osu.Game.Screens.Play if (!touchActive.Value) return; - if (gameplayState.HasPassed || gameplayState.ShownFailAnimation || gameplayState.HasQuit) + if (gameplayState.HasPassed || gameplayState.HasFailed || gameplayState.HasQuit) return; if (gameplayState.Score.ScoreInfo.Mods.OfType().Any()) From b48f99ba4bd4f16ebff1dc99accb7a1a5e96abf5 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 28 Jan 2024 18:06:09 +0300 Subject: [PATCH 2761/4092] Stop using SliderBar as a base --- .../Play/HUD/DefaultSongProgressBar.cs | 2 +- osu.Game/Screens/Play/HUD/SongProgressBar.cs | 67 +++++++++++++------ 2 files changed, 46 insertions(+), 23 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgressBar.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgressBar.cs index 4079351baf..46867e7622 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgressBar.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgressBar.cs @@ -98,7 +98,7 @@ namespace osu.Game.Screens.Play.HUD base.Update(); handleBase.Height = Height - handleContainer.Height; - float newX = (float)Interpolation.Lerp(handleBase.X, NormalizedValue * UsableWidth, Math.Clamp(Time.Elapsed / 40, 0, 1)); + float newX = (float)Interpolation.Lerp(handleBase.X, NormalizedValue * DrawWidth, Math.Clamp(Time.Elapsed / 40, 0, 1)); fill.Width = newX; handleBase.X = newX; diff --git a/osu.Game/Screens/Play/HUD/SongProgressBar.cs b/osu.Game/Screens/Play/HUD/SongProgressBar.cs index db9c8901b4..df4df2a783 100644 --- a/osu.Game/Screens/Play/HUD/SongProgressBar.cs +++ b/osu.Game/Screens/Play/HUD/SongProgressBar.cs @@ -3,12 +3,14 @@ using System; using osu.Framework.Bindables; -using osu.Framework.Graphics.UserInterface; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; using osu.Framework.Threading; +using osuTK; namespace osu.Game.Screens.Play.HUD { - public abstract partial class SongProgressBar : SliderBar + public abstract partial class SongProgressBar : Container { /// /// Action which is invoked when a seek is requested, with the proposed millisecond value for the seek operation. @@ -26,45 +28,66 @@ namespace osu.Game.Screens.Play.HUD protected readonly BindableBool InteractiveBindable = new BindableBool(); - public double StartTime + public double StartTime { get; set; } + + public double EndTime { get; set; } = 1.0; + + public double CurrentTime { get; set; } + + private double length => EndTime - StartTime; + + protected double NormalizedValue => length == 0 ? 1 : Math.Clamp(CurrentTime - StartTime, 0.0, length) / length; + + private bool handleClick; + + protected override bool OnMouseDown(MouseDownEvent e) { - get => CurrentNumber.MinValue; - set => CurrentNumber.MinValue = value; + handleClick = true; + return base.OnMouseDown(e); } - public double EndTime + protected override bool OnClick(ClickEvent e) { - get => CurrentNumber.MaxValue; - set => CurrentNumber.MaxValue = value; + if (handleClick) + handleMouseInput(e); + + return true; } - public double CurrentTime + protected override void OnDrag(DragEvent e) { - get => CurrentNumber.Value; - set => CurrentNumber.Value = value; + handleMouseInput(e); } - protected SongProgressBar() + protected override bool OnDragStart(DragStartEvent e) { - StartTime = 0; - EndTime = 1; + Vector2 posDiff = e.MouseDownPosition - e.MousePosition; + + if (Math.Abs(posDiff.X) < Math.Abs(posDiff.Y)) + { + handleClick = false; + return false; + } + + handleMouseInput(e); + return true; } - protected override void UpdateValue(float value) + private void handleMouseInput(UIEvent e) { - // handled in update + if (!Interactive) + return; + + double relativeX = Math.Clamp(ToLocalSpace(e.ScreenSpaceMousePosition).X / DrawWidth, 0, 1); + onUserChange(StartTime + (EndTime - StartTime) * relativeX); } private ScheduledDelegate? scheduledSeek; - protected override void OnUserChange(double value) + private void onUserChange(double value) { scheduledSeek?.Cancel(); - scheduledSeek = Schedule(() => - { - if (Interactive) - OnSeek?.Invoke(value); - }); + scheduledSeek = Schedule(() => OnSeek?.Invoke(value)); } } } From 23f12e1ea36195ed29fa19955d41290cfbc92328 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jan 2024 01:49:34 +0900 Subject: [PATCH 2762/4092] Should not be a container --- osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs | 6 +++--- osu.Game/Screens/Play/HUD/DefaultSongProgressBar.cs | 2 +- osu.Game/Screens/Play/HUD/SongProgressBar.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs index 196d8ca278..42f638c386 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Play.HUD CornerRadius = 5; Masking = true; - Children = new Drawable[] + InternalChildren = new Drawable[] { background = new Box { @@ -105,9 +105,9 @@ namespace osu.Game.Screens.Play.HUD audioBar.Length = (float)Interpolation.Lerp(audioBar.Length, NormalizedValue, Math.Clamp(Time.Elapsed / 40, 0, 1)); if (trackTime > CurrentTime) - ChangeChildDepth(audioBar, -1); + ChangeInternalChildDepth(audioBar, -1); else - ChangeChildDepth(audioBar, 0); + ChangeInternalChildDepth(audioBar, 0); float timeDelta = (float)Math.Abs(CurrentTime - trackTime); diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgressBar.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgressBar.cs index 46867e7622..222d6c17bf 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgressBar.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgressBar.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Play.HUD RelativeSizeAxes = Axes.X; Height = barHeight + handleBarHeight + handleSize.Y; - Children = new Drawable[] + InternalChildren = new Drawable[] { new Box { diff --git a/osu.Game/Screens/Play/HUD/SongProgressBar.cs b/osu.Game/Screens/Play/HUD/SongProgressBar.cs index df4df2a783..140621986c 100644 --- a/osu.Game/Screens/Play/HUD/SongProgressBar.cs +++ b/osu.Game/Screens/Play/HUD/SongProgressBar.cs @@ -10,7 +10,7 @@ using osuTK; namespace osu.Game.Screens.Play.HUD { - public abstract partial class SongProgressBar : Container + public abstract partial class SongProgressBar : CompositeDrawable { /// /// Action which is invoked when a seek is requested, with the proposed millisecond value for the seek operation. From f4dd84fa77fe41edc57120063a3df9a4bbb834a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jan 2024 02:01:37 +0900 Subject: [PATCH 2763/4092] Fix various inconsistencies and document better --- .../Screens/Play/HUD/ArgonSongProgress.cs | 1 - .../Screens/Play/HUD/ArgonSongProgressBar.cs | 9 +++++---- .../Screens/Play/HUD/DefaultSongProgress.cs | 1 - .../Play/HUD/DefaultSongProgressBar.cs | 2 +- osu.Game/Screens/Play/HUD/SongProgress.cs | 6 ++++++ osu.Game/Screens/Play/HUD/SongProgressBar.cs | 19 +++++++++++++++---- 6 files changed, 27 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs index a0e74dd6cd..7db3f9fd3c 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs @@ -114,7 +114,6 @@ namespace osu.Game.Screens.Play.HUD protected override void UpdateProgress(double progress, bool isIntro) { - bar.CurrentTime = GameplayClock.CurrentTime; bar.Progress = isIntro ? 0 : progress; } } diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs index 42f638c386..7a7870a775 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs @@ -48,6 +48,7 @@ namespace osu.Game.Screens.Play.HUD RelativeSizeAxes = Axes.Both, Alpha = 0, Colour = OsuColour.Gray(0.2f), + Depth = float.MaxValue, }, audioBar = new RoundedBar { @@ -102,14 +103,14 @@ namespace osu.Game.Screens.Play.HUD base.Update(); playfieldBar.Length = (float)Interpolation.Lerp(playfieldBar.Length, Progress, Math.Clamp(Time.Elapsed / 40, 0, 1)); - audioBar.Length = (float)Interpolation.Lerp(audioBar.Length, NormalizedValue, Math.Clamp(Time.Elapsed / 40, 0, 1)); + audioBar.Length = (float)Interpolation.Lerp(audioBar.Length, AudioProgress, Math.Clamp(Time.Elapsed / 40, 0, 1)); - if (trackTime > CurrentTime) + if (trackTime > AudioTime) ChangeInternalChildDepth(audioBar, -1); else - ChangeInternalChildDepth(audioBar, 0); + ChangeInternalChildDepth(audioBar, 1); - float timeDelta = (float)Math.Abs(CurrentTime - trackTime); + float timeDelta = (float)Math.Abs(AudioTime - trackTime); const float colour_transition_threshold = 20000; diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs index be310df3a9..f01c11855c 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs @@ -98,7 +98,6 @@ namespace osu.Game.Screens.Play.HUD protected override void UpdateProgress(double progress, bool isIntro) { - bar.CurrentTime = GameplayClock.CurrentTime; graph.Progress = isIntro ? 0 : (int)(graph.ColumnCount * progress); } diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgressBar.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgressBar.cs index 222d6c17bf..05c8c6c46b 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgressBar.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgressBar.cs @@ -98,7 +98,7 @@ namespace osu.Game.Screens.Play.HUD base.Update(); handleBase.Height = Height - handleContainer.Height; - float newX = (float)Interpolation.Lerp(handleBase.X, NormalizedValue * DrawWidth, Math.Clamp(Time.Elapsed / 40, 0, 1)); + float newX = (float)Interpolation.Lerp(handleBase.X, AudioProgress * DrawWidth, Math.Clamp(Time.Elapsed / 40, 0, 1)); fill.Width = newX; handleBase.X = newX; diff --git a/osu.Game/Screens/Play/HUD/SongProgress.cs b/osu.Game/Screens/Play/HUD/SongProgress.cs index 962752ba5a..296306ec89 100644 --- a/osu.Game/Screens/Play/HUD/SongProgress.cs +++ b/osu.Game/Screens/Play/HUD/SongProgress.cs @@ -71,7 +71,13 @@ namespace osu.Game.Screens.Play.HUD protected double LastHitTime { get; private set; } + /// + /// Called every update frame with current progress information. + /// + /// Current (visual) progress through the beatmap (0..1). + /// If true, progress is (0..1) through the intro. protected abstract void UpdateProgress(double progress, bool isIntro); + protected virtual void UpdateObjects(IEnumerable objects) { } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Play/HUD/SongProgressBar.cs b/osu.Game/Screens/Play/HUD/SongProgressBar.cs index 140621986c..ea2e5b2891 100644 --- a/osu.Game/Screens/Play/HUD/SongProgressBar.cs +++ b/osu.Game/Screens/Play/HUD/SongProgressBar.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; @@ -12,6 +13,20 @@ namespace osu.Game.Screens.Play.HUD { public abstract partial class SongProgressBar : CompositeDrawable { + /// + /// The current seek position of the audio, on a (0..1) range. + /// This is generally the seek target, which will eventually match the gameplay clock when it catches up. + /// + protected double AudioProgress => length == 0 ? 1 : AudioTime / length; + + /// + /// The current (non-frame-stable) audio time. + /// + protected double AudioTime => Math.Clamp(GameplayClock.CurrentTime - StartTime, 0.0, length); + + [Resolved] + protected IGameplayClock GameplayClock { get; private set; } = null!; + /// /// Action which is invoked when a seek is requested, with the proposed millisecond value for the seek operation. /// @@ -32,12 +47,8 @@ namespace osu.Game.Screens.Play.HUD public double EndTime { get; set; } = 1.0; - public double CurrentTime { get; set; } - private double length => EndTime - StartTime; - protected double NormalizedValue => length == 0 ? 1 : Math.Clamp(CurrentTime - StartTime, 0.0, length) / length; - private bool handleClick; protected override bool OnMouseDown(MouseDownEvent e) From 462dab3c90d762de0c9c0918d93fe90472956a06 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jan 2024 02:13:39 +0900 Subject: [PATCH 2764/4092] Remove unnecessary bindable --- osu.Game/Screens/Play/HUD/DefaultSongProgressBar.cs | 11 +++++++---- osu.Game/Screens/Play/HUD/SongProgressBar.cs | 9 +-------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgressBar.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgressBar.cs index 05c8c6c46b..d5a6a75793 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgressBar.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgressBar.cs @@ -86,11 +86,14 @@ namespace osu.Game.Screens.Play.HUD }; } - protected override void LoadComplete() + public override bool Interactive { - base.LoadComplete(); - - InteractiveBindable.BindValueChanged(i => handleBase.FadeTo(i.NewValue ? 1 : 0, 200), true); + get => base.Interactive; + set + { + base.Interactive = value; + handleBase.FadeTo(value ? 1 : 0, 200); + } } protected override void Update() diff --git a/osu.Game/Screens/Play/HUD/SongProgressBar.cs b/osu.Game/Screens/Play/HUD/SongProgressBar.cs index ea2e5b2891..40c4e587b9 100644 --- a/osu.Game/Screens/Play/HUD/SongProgressBar.cs +++ b/osu.Game/Screens/Play/HUD/SongProgressBar.cs @@ -3,7 +3,6 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Framework.Threading; @@ -35,13 +34,7 @@ namespace osu.Game.Screens.Play.HUD /// /// Whether the progress bar should allow interaction, ie. to perform seek operations. /// - public bool Interactive - { - get => InteractiveBindable.Value; - set => InteractiveBindable.Value = value; - } - - protected readonly BindableBool InteractiveBindable = new BindableBool(); + public virtual bool Interactive { get; set; } public double StartTime { get; set; } From ee4fe1c0683a26bcb8559ac1918d52a88e6a1276 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 28 Jan 2024 23:11:42 +0300 Subject: [PATCH 2765/4092] Fix relax mod not handling objects close to a previous slider's follow area --- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index 40fadfb77e..3679425389 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Osu.Mods if (!slider.HeadCircle.IsHit) handleHitCircle(slider.HeadCircle); - requiresHold |= slider.SliderInputManager.IsMouseInFollowArea(true); + requiresHold |= slider.SliderInputManager.IsMouseInFollowArea(slider.Tracking.Value); break; case DrawableSpinner spinner: From 5d456c8d68fe61fa6fc97f8db235b0c6d429a55d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 29 Jan 2024 04:55:21 +0300 Subject: [PATCH 2766/4092] Rework drawing of graded circles --- .../Expanded/Accuracy/AccuracyCircle.cs | 87 +++++++++++-------- 1 file changed, 52 insertions(+), 35 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 0aff98df2b..8d32989110 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -169,46 +169,63 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy { new CircularProgress { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.ForRank(ScoreRank.X), - InnerRadius = RANK_CIRCLE_RADIUS, - Current = { Value = accuracyX } - }, - new CircularProgress - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.ForRank(ScoreRank.S), - InnerRadius = RANK_CIRCLE_RADIUS, - Current = { Value = accuracyX - virtual_ss_percentage } - }, - new CircularProgress - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.ForRank(ScoreRank.A), - InnerRadius = RANK_CIRCLE_RADIUS, - Current = { Value = accuracyS } - }, - new CircularProgress - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.ForRank(ScoreRank.B), - InnerRadius = RANK_CIRCLE_RADIUS, - Current = { Value = accuracyA } - }, - new CircularProgress - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.ForRank(ScoreRank.C), - InnerRadius = RANK_CIRCLE_RADIUS, - Current = { Value = accuracyB } - }, - new CircularProgress - { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, Colour = OsuColour.ForRank(ScoreRank.D), InnerRadius = RANK_CIRCLE_RADIUS, Current = { Value = accuracyC } }, + new CircularProgress + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.ForRank(ScoreRank.C), + InnerRadius = RANK_CIRCLE_RADIUS, + Current = { Value = accuracyB - accuracyC }, + Rotation = (float)accuracyC * 360 + }, + new CircularProgress + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.ForRank(ScoreRank.B), + InnerRadius = RANK_CIRCLE_RADIUS, + Current = { Value = accuracyA - accuracyB }, + Rotation = (float)accuracyB * 360 + }, + new CircularProgress + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.ForRank(ScoreRank.A), + InnerRadius = RANK_CIRCLE_RADIUS, + Current = { Value = accuracyS - accuracyA }, + Rotation = (float)accuracyA * 360 + }, + new CircularProgress + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.ForRank(ScoreRank.S), + InnerRadius = RANK_CIRCLE_RADIUS, + Current = { Value = accuracyX - accuracyS - virtual_ss_percentage }, + Rotation = (float)accuracyS * 360 + }, + new CircularProgress + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.ForRank(ScoreRank.X), + InnerRadius = RANK_CIRCLE_RADIUS, + Current = { Value = 1f - (accuracyX - virtual_ss_percentage) }, + Rotation = (float)(accuracyX - virtual_ss_percentage) * 360 + }, new RankNotch((float)accuracyX), new RankNotch((float)(accuracyX - virtual_ss_percentage)), new RankNotch((float)accuracyS), From 32b0e0b7380fcb54219fea66b4f46f2a83ece1e1 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 29 Jan 2024 05:05:18 +0300 Subject: [PATCH 2767/4092] Remove RankNotch --- .../Expanded/Accuracy/AccuracyCircle.cs | 33 ++++++------- .../Ranking/Expanded/Accuracy/RankNotch.cs | 49 ------------------- 2 files changed, 14 insertions(+), 68 deletions(-) delete mode 100644 osu.Game/Screens/Ranking/Expanded/Accuracy/RankNotch.cs diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 8d32989110..7bd586ebb7 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -76,9 +76,9 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy private const double virtual_ss_percentage = 0.01; /// - /// The width of a in terms of accuracy. + /// The width of a solid "notch" in terms of accuracy that appears at the ends of the rank circles to add separation. /// - public const double NOTCH_WIDTH_PERCENTAGE = 1.0 / 360; + public const float NOTCH_WIDTH_PERCENTAGE = 2f / 360; /// /// The easing for the circle filling transforms. @@ -174,7 +174,8 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy RelativeSizeAxes = Axes.Both, Colour = OsuColour.ForRank(ScoreRank.D), InnerRadius = RANK_CIRCLE_RADIUS, - Current = { Value = accuracyC } + Current = { Value = accuracyC - NOTCH_WIDTH_PERCENTAGE }, + Rotation = NOTCH_WIDTH_PERCENTAGE * 0.5f * 360 }, new CircularProgress { @@ -183,8 +184,8 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy RelativeSizeAxes = Axes.Both, Colour = OsuColour.ForRank(ScoreRank.C), InnerRadius = RANK_CIRCLE_RADIUS, - Current = { Value = accuracyB - accuracyC }, - Rotation = (float)accuracyC * 360 + Current = { Value = accuracyB - accuracyC - NOTCH_WIDTH_PERCENTAGE }, + Rotation = (float)accuracyC * 360 + NOTCH_WIDTH_PERCENTAGE * 0.5f * 360 }, new CircularProgress { @@ -193,8 +194,8 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy RelativeSizeAxes = Axes.Both, Colour = OsuColour.ForRank(ScoreRank.B), InnerRadius = RANK_CIRCLE_RADIUS, - Current = { Value = accuracyA - accuracyB }, - Rotation = (float)accuracyB * 360 + Current = { Value = accuracyA - accuracyB - NOTCH_WIDTH_PERCENTAGE }, + Rotation = (float)accuracyB * 360 + NOTCH_WIDTH_PERCENTAGE * 0.5f * 360 }, new CircularProgress { @@ -203,8 +204,8 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy RelativeSizeAxes = Axes.Both, Colour = OsuColour.ForRank(ScoreRank.A), InnerRadius = RANK_CIRCLE_RADIUS, - Current = { Value = accuracyS - accuracyA }, - Rotation = (float)accuracyA * 360 + Current = { Value = accuracyS - accuracyA - NOTCH_WIDTH_PERCENTAGE }, + Rotation = (float)accuracyA * 360 + NOTCH_WIDTH_PERCENTAGE * 0.5f * 360 }, new CircularProgress { @@ -213,8 +214,8 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy RelativeSizeAxes = Axes.Both, Colour = OsuColour.ForRank(ScoreRank.S), InnerRadius = RANK_CIRCLE_RADIUS, - Current = { Value = accuracyX - accuracyS - virtual_ss_percentage }, - Rotation = (float)accuracyS * 360 + Current = { Value = accuracyX - accuracyS - virtual_ss_percentage - NOTCH_WIDTH_PERCENTAGE }, + Rotation = (float)accuracyS * 360 + NOTCH_WIDTH_PERCENTAGE * 0.5f * 360 }, new CircularProgress { @@ -223,15 +224,9 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy RelativeSizeAxes = Axes.Both, Colour = OsuColour.ForRank(ScoreRank.X), InnerRadius = RANK_CIRCLE_RADIUS, - Current = { Value = 1f - (accuracyX - virtual_ss_percentage) }, - Rotation = (float)(accuracyX - virtual_ss_percentage) * 360 + Current = { Value = 1f - (accuracyX - virtual_ss_percentage) - NOTCH_WIDTH_PERCENTAGE }, + Rotation = (float)(accuracyX - virtual_ss_percentage) * 360 + NOTCH_WIDTH_PERCENTAGE * 0.5f * 360 }, - new RankNotch((float)accuracyX), - new RankNotch((float)(accuracyX - virtual_ss_percentage)), - new RankNotch((float)accuracyS), - new RankNotch((float)accuracyA), - new RankNotch((float)accuracyB), - new RankNotch((float)accuracyC), new BufferedContainer { Name = "Graded circle mask", diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/RankNotch.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/RankNotch.cs deleted file mode 100644 index 244acbe8b1..0000000000 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/RankNotch.cs +++ /dev/null @@ -1,49 +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.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osuTK; - -namespace osu.Game.Screens.Ranking.Expanded.Accuracy -{ - /// - /// A solid "notch" of the that appears at the ends of the rank circles to add separation. - /// - public partial class RankNotch : CompositeDrawable - { - private readonly float position; - - public RankNotch(float position) - { - this.position = position; - - RelativeSizeAxes = Axes.Both; - } - - [BackgroundDependencyLoader] - private void load() - { - InternalChild = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Rotation = position * 360f, - Child = new Box - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Y, - Height = AccuracyCircle.RANK_CIRCLE_RADIUS, - Width = (float)AccuracyCircle.NOTCH_WIDTH_PERCENTAGE * 360f, - Colour = OsuColour.Gray(0.3f), - EdgeSmoothness = new Vector2(1f) - } - }; - } - } -} From 5783838b07df8420916904ab307f91b23fb8a6ad Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 29 Jan 2024 05:14:24 +0300 Subject: [PATCH 2768/4092] Move graded circles into a separate class --- .../Expanded/Accuracy/AccuracyCircle.cs | 105 ++-------------- .../Expanded/Accuracy/GradedCircles.cs | 113 ++++++++++++++++++ 2 files changed, 120 insertions(+), 98 deletions(-) create mode 100644 osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 7bd586ebb7..3141810894 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -20,7 +20,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Skinning; -using osuTK; using osuTK.Graphics; namespace osu.Game.Screens.Ranking.Expanded.Accuracy @@ -73,7 +72,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy /// /// SS is displayed as a 1% region, otherwise it would be invisible. /// - private const double virtual_ss_percentage = 0.01; + public const double VIRTUAL_SS_PERCENTAGE = 0.01; /// /// The width of a solid "notch" in terms of accuracy that appears at the ends of the rank circles to add separation. @@ -88,7 +87,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy private readonly ScoreInfo score; private CircularProgress accuracyCircle; - private CircularProgress innerMask; + private GradedCircles gradedCircles; private Container badges; private RankText rankText; @@ -157,96 +156,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex("#7CF6FF"), Color4Extensions.FromHex("#BAFFA9")), InnerRadius = accuracy_circle_radius, }, - new BufferedContainer - { - Name = "Graded circles", - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.8f), - Padding = new MarginPadding(2), - Children = new Drawable[] - { - new CircularProgress - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.ForRank(ScoreRank.D), - InnerRadius = RANK_CIRCLE_RADIUS, - Current = { Value = accuracyC - NOTCH_WIDTH_PERCENTAGE }, - Rotation = NOTCH_WIDTH_PERCENTAGE * 0.5f * 360 - }, - new CircularProgress - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.ForRank(ScoreRank.C), - InnerRadius = RANK_CIRCLE_RADIUS, - Current = { Value = accuracyB - accuracyC - NOTCH_WIDTH_PERCENTAGE }, - Rotation = (float)accuracyC * 360 + NOTCH_WIDTH_PERCENTAGE * 0.5f * 360 - }, - new CircularProgress - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.ForRank(ScoreRank.B), - InnerRadius = RANK_CIRCLE_RADIUS, - Current = { Value = accuracyA - accuracyB - NOTCH_WIDTH_PERCENTAGE }, - Rotation = (float)accuracyB * 360 + NOTCH_WIDTH_PERCENTAGE * 0.5f * 360 - }, - new CircularProgress - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.ForRank(ScoreRank.A), - InnerRadius = RANK_CIRCLE_RADIUS, - Current = { Value = accuracyS - accuracyA - NOTCH_WIDTH_PERCENTAGE }, - Rotation = (float)accuracyA * 360 + NOTCH_WIDTH_PERCENTAGE * 0.5f * 360 - }, - new CircularProgress - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.ForRank(ScoreRank.S), - InnerRadius = RANK_CIRCLE_RADIUS, - Current = { Value = accuracyX - accuracyS - virtual_ss_percentage - NOTCH_WIDTH_PERCENTAGE }, - Rotation = (float)accuracyS * 360 + NOTCH_WIDTH_PERCENTAGE * 0.5f * 360 - }, - new CircularProgress - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.ForRank(ScoreRank.X), - InnerRadius = RANK_CIRCLE_RADIUS, - Current = { Value = 1f - (accuracyX - virtual_ss_percentage) - NOTCH_WIDTH_PERCENTAGE }, - Rotation = (float)(accuracyX - virtual_ss_percentage) * 360 + NOTCH_WIDTH_PERCENTAGE * 0.5f * 360 - }, - new BufferedContainer - { - Name = "Graded circle mask", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(1), - Blending = new BlendingParameters - { - Source = BlendingType.DstColor, - Destination = BlendingType.OneMinusSrcColor, - SourceAlpha = BlendingType.One, - DestinationAlpha = BlendingType.SrcAlpha - }, - Child = innerMask = new CircularProgress - { - RelativeSizeAxes = Axes.Both, - InnerRadius = RANK_CIRCLE_RADIUS - 0.02f, - } - } - } - }, + gradedCircles = new GradedCircles(accuracyC, accuracyB, accuracyA, accuracyS, accuracyX), badges = new Container { Name = "Rank badges", @@ -259,7 +169,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy new RankBadge(accuracyB, Interpolation.Lerp(accuracyB, accuracyA, 0.5), getRank(ScoreRank.B)), // The S and A badges are moved down slightly to prevent collision with the SS badge. new RankBadge(accuracyA, Interpolation.Lerp(accuracyA, accuracyS, 0.25), getRank(ScoreRank.A)), - new RankBadge(accuracyS, Interpolation.Lerp(accuracyS, (accuracyX - virtual_ss_percentage), 0.25), getRank(ScoreRank.S)), + new RankBadge(accuracyS, Interpolation.Lerp(accuracyS, (accuracyX - VIRTUAL_SS_PERCENTAGE), 0.25), getRank(ScoreRank.S)), new RankBadge(accuracyX, accuracyX, getRank(ScoreRank.X)), } }, @@ -301,8 +211,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy }); } - using (BeginDelayedSequence(RANK_CIRCLE_TRANSFORM_DELAY)) - innerMask.FillTo(1f, RANK_CIRCLE_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING); + gradedCircles.Transform(); using (BeginDelayedSequence(ACCURACY_TRANSFORM_DELAY)) { @@ -331,7 +240,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy if (score.Rank == ScoreRank.X || score.Rank == ScoreRank.XH) targetAccuracy = 1; else - targetAccuracy = Math.Min(accuracyX - virtual_ss_percentage - NOTCH_WIDTH_PERCENTAGE / 2, targetAccuracy); + targetAccuracy = Math.Min(accuracyX - VIRTUAL_SS_PERCENTAGE - NOTCH_WIDTH_PERCENTAGE / 2, targetAccuracy); // The accuracy circle gauge visually fills up a bit too much. // This wouldn't normally matter but we want it to align properly with the inner graded circle in the above cases. @@ -368,7 +277,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy if (badge.Accuracy > score.Accuracy) continue; - using (BeginDelayedSequence(inverseEasing(ACCURACY_TRANSFORM_EASING, Math.Min(accuracyX - virtual_ss_percentage, badge.Accuracy) / targetAccuracy) * ACCURACY_TRANSFORM_DURATION)) + using (BeginDelayedSequence(inverseEasing(ACCURACY_TRANSFORM_EASING, Math.Min(accuracyX - VIRTUAL_SS_PERCENTAGE, badge.Accuracy) / targetAccuracy) * ACCURACY_TRANSFORM_DURATION)) { badge.Appear(); diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs new file mode 100644 index 0000000000..51c4237528 --- /dev/null +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs @@ -0,0 +1,113 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics; +using osu.Game.Scoring; +using osuTK; + +namespace osu.Game.Screens.Ranking.Expanded.Accuracy +{ + public partial class GradedCircles : BufferedContainer + { + private readonly CircularProgress innerMask; + + public GradedCircles(double accuracyC, double accuracyB, double accuracyA, double accuracyS, double accuracyX) + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + RelativeSizeAxes = Axes.Both; + Size = new Vector2(0.8f); + Padding = new MarginPadding(2); + Children = new Drawable[] + { + new CircularProgress + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.ForRank(ScoreRank.D), + InnerRadius = AccuracyCircle.RANK_CIRCLE_RADIUS, + Current = { Value = accuracyC - AccuracyCircle.NOTCH_WIDTH_PERCENTAGE }, + Rotation = AccuracyCircle.NOTCH_WIDTH_PERCENTAGE * 0.5f * 360 + }, + new CircularProgress + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.ForRank(ScoreRank.C), + InnerRadius = AccuracyCircle.RANK_CIRCLE_RADIUS, + Current = { Value = accuracyB - accuracyC - AccuracyCircle.NOTCH_WIDTH_PERCENTAGE }, + Rotation = (float)accuracyC * 360 + AccuracyCircle.NOTCH_WIDTH_PERCENTAGE * 0.5f * 360 + }, + new CircularProgress + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.ForRank(ScoreRank.B), + InnerRadius = AccuracyCircle.RANK_CIRCLE_RADIUS, + Current = { Value = accuracyA - accuracyB - AccuracyCircle.NOTCH_WIDTH_PERCENTAGE }, + Rotation = (float)accuracyB * 360 + AccuracyCircle.NOTCH_WIDTH_PERCENTAGE * 0.5f * 360 + }, + new CircularProgress + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.ForRank(ScoreRank.A), + InnerRadius = AccuracyCircle.RANK_CIRCLE_RADIUS, + Current = { Value = accuracyS - accuracyA - AccuracyCircle.NOTCH_WIDTH_PERCENTAGE }, + Rotation = (float)accuracyA * 360 + AccuracyCircle.NOTCH_WIDTH_PERCENTAGE * 0.5f * 360 + }, + new CircularProgress + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.ForRank(ScoreRank.S), + InnerRadius = AccuracyCircle.RANK_CIRCLE_RADIUS, + Current = { Value = accuracyX - accuracyS - AccuracyCircle.VIRTUAL_SS_PERCENTAGE - AccuracyCircle.NOTCH_WIDTH_PERCENTAGE }, + Rotation = (float)accuracyS * 360 + AccuracyCircle.NOTCH_WIDTH_PERCENTAGE * 0.5f * 360 + }, + new CircularProgress + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.ForRank(ScoreRank.X), + InnerRadius = AccuracyCircle.RANK_CIRCLE_RADIUS, + Current = { Value = 1f - (accuracyX - AccuracyCircle.VIRTUAL_SS_PERCENTAGE) - AccuracyCircle.NOTCH_WIDTH_PERCENTAGE }, + Rotation = (float)(accuracyX - AccuracyCircle.VIRTUAL_SS_PERCENTAGE) * 360 + AccuracyCircle.NOTCH_WIDTH_PERCENTAGE * 0.5f * 360 + }, + new BufferedContainer + { + Name = "Graded circle mask", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(1), + Blending = new BlendingParameters + { + Source = BlendingType.DstColor, + Destination = BlendingType.OneMinusSrcColor, + SourceAlpha = BlendingType.One, + DestinationAlpha = BlendingType.SrcAlpha + }, + Child = innerMask = new CircularProgress + { + RelativeSizeAxes = Axes.Both, + InnerRadius = AccuracyCircle.RANK_CIRCLE_RADIUS - 0.02f, + } + } + }; + } + + public void Transform() + { + using (BeginDelayedSequence(AccuracyCircle.RANK_CIRCLE_TRANSFORM_DELAY)) + innerMask.FillTo(1f, AccuracyCircle.RANK_CIRCLE_TRANSFORM_DURATION, AccuracyCircle.ACCURACY_TRANSFORM_EASING); + } + } +} From 9c411ad48d81fa384619a6eb8c49c3e56f760fbc Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 29 Jan 2024 05:19:28 +0300 Subject: [PATCH 2769/4092] Simplify notch math --- .../Ranking/Expanded/Accuracy/GradedCircles.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs index 51c4237528..5241a4d983 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs @@ -31,7 +31,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy Colour = OsuColour.ForRank(ScoreRank.D), InnerRadius = AccuracyCircle.RANK_CIRCLE_RADIUS, Current = { Value = accuracyC - AccuracyCircle.NOTCH_WIDTH_PERCENTAGE }, - Rotation = AccuracyCircle.NOTCH_WIDTH_PERCENTAGE * 0.5f * 360 + Rotation = AccuracyCircle.NOTCH_WIDTH_PERCENTAGE * 180 }, new CircularProgress { @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy Colour = OsuColour.ForRank(ScoreRank.C), InnerRadius = AccuracyCircle.RANK_CIRCLE_RADIUS, Current = { Value = accuracyB - accuracyC - AccuracyCircle.NOTCH_WIDTH_PERCENTAGE }, - Rotation = (float)accuracyC * 360 + AccuracyCircle.NOTCH_WIDTH_PERCENTAGE * 0.5f * 360 + Rotation = (float)(accuracyC + AccuracyCircle.NOTCH_WIDTH_PERCENTAGE * 0.5f) * 360 }, new CircularProgress { @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy Colour = OsuColour.ForRank(ScoreRank.B), InnerRadius = AccuracyCircle.RANK_CIRCLE_RADIUS, Current = { Value = accuracyA - accuracyB - AccuracyCircle.NOTCH_WIDTH_PERCENTAGE }, - Rotation = (float)accuracyB * 360 + AccuracyCircle.NOTCH_WIDTH_PERCENTAGE * 0.5f * 360 + Rotation = (float)(accuracyB + AccuracyCircle.NOTCH_WIDTH_PERCENTAGE * 0.5f) * 360 }, new CircularProgress { @@ -61,7 +61,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy Colour = OsuColour.ForRank(ScoreRank.A), InnerRadius = AccuracyCircle.RANK_CIRCLE_RADIUS, Current = { Value = accuracyS - accuracyA - AccuracyCircle.NOTCH_WIDTH_PERCENTAGE }, - Rotation = (float)accuracyA * 360 + AccuracyCircle.NOTCH_WIDTH_PERCENTAGE * 0.5f * 360 + Rotation = (float)(accuracyA + AccuracyCircle.NOTCH_WIDTH_PERCENTAGE * 0.5f) * 360 }, new CircularProgress { @@ -71,7 +71,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy Colour = OsuColour.ForRank(ScoreRank.S), InnerRadius = AccuracyCircle.RANK_CIRCLE_RADIUS, Current = { Value = accuracyX - accuracyS - AccuracyCircle.VIRTUAL_SS_PERCENTAGE - AccuracyCircle.NOTCH_WIDTH_PERCENTAGE }, - Rotation = (float)accuracyS * 360 + AccuracyCircle.NOTCH_WIDTH_PERCENTAGE * 0.5f * 360 + Rotation = (float)(accuracyS + AccuracyCircle.NOTCH_WIDTH_PERCENTAGE * 0.5f) * 360 }, new CircularProgress { @@ -81,7 +81,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy Colour = OsuColour.ForRank(ScoreRank.X), InnerRadius = AccuracyCircle.RANK_CIRCLE_RADIUS, Current = { Value = 1f - (accuracyX - AccuracyCircle.VIRTUAL_SS_PERCENTAGE) - AccuracyCircle.NOTCH_WIDTH_PERCENTAGE }, - Rotation = (float)(accuracyX - AccuracyCircle.VIRTUAL_SS_PERCENTAGE) * 360 + AccuracyCircle.NOTCH_WIDTH_PERCENTAGE * 0.5f * 360 + Rotation = (float)(accuracyX - AccuracyCircle.VIRTUAL_SS_PERCENTAGE + AccuracyCircle.NOTCH_WIDTH_PERCENTAGE * 0.5f) * 360 }, new BufferedContainer { From 809ca81b9ccba45aa1ea29fe4645805b8e32e2e7 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 29 Jan 2024 05:29:29 +0300 Subject: [PATCH 2770/4092] Add TestSceneGradedCircles --- .../Visual/Ranking/TestSceneGradedCircles.cs | 44 +++++++++++++++++++ .../Expanded/Accuracy/GradedCircles.cs | 6 +++ 2 files changed, 50 insertions(+) create mode 100644 osu.Game.Tests/Visual/Ranking/TestSceneGradedCircles.cs diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneGradedCircles.cs b/osu.Game.Tests/Visual/Ranking/TestSceneGradedCircles.cs new file mode 100644 index 0000000000..87fbca5c44 --- /dev/null +++ b/osu.Game.Tests/Visual/Ranking/TestSceneGradedCircles.cs @@ -0,0 +1,44 @@ +// 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.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens.Ranking.Expanded.Accuracy; +using osuTK; + +namespace osu.Game.Tests.Visual.Ranking +{ + public partial class TestSceneGradedCircles : OsuTestScene + { + private readonly GradedCircles ring; + + public TestSceneGradedCircles() + { + ScoreProcessor scoreProcessor = new OsuRuleset().CreateScoreProcessor(); + double accuracyX = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.X); + double accuracyS = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.S); + + double accuracyA = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.A); + double accuracyB = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.B); + double accuracyC = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.C); + + Add(new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(400), + Child = ring = new GradedCircles(accuracyC, accuracyB, accuracyA, accuracyS, accuracyX) + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + AddSliderStep("Progress", 0.0, 1.0, 1.0, p => ring.Progress = p); + } + } +} diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs index 5241a4d983..2ce8c511e0 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs @@ -12,6 +12,12 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy { public partial class GradedCircles : BufferedContainer { + public double Progress + { + get => innerMask.Current.Value; + set => innerMask.Current.Value = value; + } + private readonly CircularProgress innerMask; public GradedCircles(double accuracyC, double accuracyB, double accuracyA, double accuracyS, double accuracyX) From 3987faa21cc80c39b42f465d1ef3f3046e235843 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 29 Jan 2024 06:13:52 +0300 Subject: [PATCH 2771/4092] Rework GradedCircles to not use BufferedContainer --- .../Expanded/Accuracy/AccuracyCircle.cs | 5 +- .../Expanded/Accuracy/GradedCircles.cs | 118 ++++++++---------- 2 files changed, 52 insertions(+), 71 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 3141810894..5b929554ff 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy /// /// The width of a solid "notch" in terms of accuracy that appears at the ends of the rank circles to add separation. /// - public const float NOTCH_WIDTH_PERCENTAGE = 2f / 360; + public const double NOTCH_WIDTH_PERCENTAGE = 2.0 / 360; /// /// The easing for the circle filling transforms. @@ -211,7 +211,8 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy }); } - gradedCircles.Transform(); + using (BeginDelayedSequence(RANK_CIRCLE_TRANSFORM_DELAY)) + gradedCircles.TransformTo(nameof(GradedCircles.Progress), 1.0, RANK_CIRCLE_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING); using (BeginDelayedSequence(ACCURACY_TRANSFORM_DELAY)) { diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs index 2ce8c511e0..19c7a9b606 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.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 System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; @@ -10,15 +11,31 @@ using osuTK; namespace osu.Game.Screens.Ranking.Expanded.Accuracy { - public partial class GradedCircles : BufferedContainer + public partial class GradedCircles : CompositeDrawable { + private double progress; + public double Progress { - get => innerMask.Current.Value; - set => innerMask.Current.Value = value; + get => progress; + set + { + progress = value; + dProgress.RevealProgress = value; + cProgress.RevealProgress = value; + bProgress.RevealProgress = value; + aProgress.RevealProgress = value; + sProgress.RevealProgress = value; + xProgress.RevealProgress = value; + } } - private readonly CircularProgress innerMask; + private readonly GradedCircle dProgress; + private readonly GradedCircle cProgress; + private readonly GradedCircle bProgress; + private readonly GradedCircle aProgress; + private readonly GradedCircle sProgress; + private readonly GradedCircle xProgress; public GradedCircles(double accuracyC, double accuracyB, double accuracyA, double accuracyS, double accuracyX) { @@ -27,93 +44,56 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy RelativeSizeAxes = Axes.Both; Size = new Vector2(0.8f); Padding = new MarginPadding(2); - Children = new Drawable[] + InternalChildren = new Drawable[] { - new CircularProgress + dProgress = new GradedCircle(0.0, accuracyC) { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, Colour = OsuColour.ForRank(ScoreRank.D), - InnerRadius = AccuracyCircle.RANK_CIRCLE_RADIUS, - Current = { Value = accuracyC - AccuracyCircle.NOTCH_WIDTH_PERCENTAGE }, - Rotation = AccuracyCircle.NOTCH_WIDTH_PERCENTAGE * 180 }, - new CircularProgress + cProgress = new GradedCircle(accuracyC, accuracyB) { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, Colour = OsuColour.ForRank(ScoreRank.C), - InnerRadius = AccuracyCircle.RANK_CIRCLE_RADIUS, - Current = { Value = accuracyB - accuracyC - AccuracyCircle.NOTCH_WIDTH_PERCENTAGE }, - Rotation = (float)(accuracyC + AccuracyCircle.NOTCH_WIDTH_PERCENTAGE * 0.5f) * 360 }, - new CircularProgress + bProgress = new GradedCircle(accuracyB, accuracyA) { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, Colour = OsuColour.ForRank(ScoreRank.B), - InnerRadius = AccuracyCircle.RANK_CIRCLE_RADIUS, - Current = { Value = accuracyA - accuracyB - AccuracyCircle.NOTCH_WIDTH_PERCENTAGE }, - Rotation = (float)(accuracyB + AccuracyCircle.NOTCH_WIDTH_PERCENTAGE * 0.5f) * 360 }, - new CircularProgress + aProgress = new GradedCircle(accuracyA, accuracyS) { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, Colour = OsuColour.ForRank(ScoreRank.A), - InnerRadius = AccuracyCircle.RANK_CIRCLE_RADIUS, - Current = { Value = accuracyS - accuracyA - AccuracyCircle.NOTCH_WIDTH_PERCENTAGE }, - Rotation = (float)(accuracyA + AccuracyCircle.NOTCH_WIDTH_PERCENTAGE * 0.5f) * 360 }, - new CircularProgress + sProgress = new GradedCircle(accuracyS, accuracyX - AccuracyCircle.VIRTUAL_SS_PERCENTAGE) { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, Colour = OsuColour.ForRank(ScoreRank.S), - InnerRadius = AccuracyCircle.RANK_CIRCLE_RADIUS, - Current = { Value = accuracyX - accuracyS - AccuracyCircle.VIRTUAL_SS_PERCENTAGE - AccuracyCircle.NOTCH_WIDTH_PERCENTAGE }, - Rotation = (float)(accuracyS + AccuracyCircle.NOTCH_WIDTH_PERCENTAGE * 0.5f) * 360 }, - new CircularProgress + xProgress = new GradedCircle(accuracyX - AccuracyCircle.VIRTUAL_SS_PERCENTAGE, 1.0) { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.ForRank(ScoreRank.X), - InnerRadius = AccuracyCircle.RANK_CIRCLE_RADIUS, - Current = { Value = 1f - (accuracyX - AccuracyCircle.VIRTUAL_SS_PERCENTAGE) - AccuracyCircle.NOTCH_WIDTH_PERCENTAGE }, - Rotation = (float)(accuracyX - AccuracyCircle.VIRTUAL_SS_PERCENTAGE + AccuracyCircle.NOTCH_WIDTH_PERCENTAGE * 0.5f) * 360 - }, - new BufferedContainer - { - Name = "Graded circle mask", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(1), - Blending = new BlendingParameters - { - Source = BlendingType.DstColor, - Destination = BlendingType.OneMinusSrcColor, - SourceAlpha = BlendingType.One, - DestinationAlpha = BlendingType.SrcAlpha - }, - Child = innerMask = new CircularProgress - { - RelativeSizeAxes = Axes.Both, - InnerRadius = AccuracyCircle.RANK_CIRCLE_RADIUS - 0.02f, - } + Colour = OsuColour.ForRank(ScoreRank.X) } }; } - public void Transform() + private partial class GradedCircle : CircularProgress { - using (BeginDelayedSequence(AccuracyCircle.RANK_CIRCLE_TRANSFORM_DELAY)) - innerMask.FillTo(1f, AccuracyCircle.RANK_CIRCLE_TRANSFORM_DURATION, AccuracyCircle.ACCURACY_TRANSFORM_EASING); + public double RevealProgress + { + set => Current.Value = Math.Clamp(value, startProgress, endProgress) - startProgress; + } + + private readonly double startProgress; + private readonly double endProgress; + + public GradedCircle(double startProgress, double endProgress) + { + this.startProgress = startProgress + AccuracyCircle.NOTCH_WIDTH_PERCENTAGE * 0.5; + this.endProgress = endProgress - AccuracyCircle.NOTCH_WIDTH_PERCENTAGE * 0.5; + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + RelativeSizeAxes = Axes.Both; + InnerRadius = AccuracyCircle.RANK_CIRCLE_RADIUS; + Rotation = (float)this.startProgress * 360; + } } } } From 0c0ba7abefe04e16f3d153964f7fe8f139ba2373 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 29 Jan 2024 06:28:38 +0300 Subject: [PATCH 2772/4092] Adjust values to visually match previous --- osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs | 2 +- osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 5b929554ff..8304e7a542 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -62,7 +62,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy /// /// Relative width of the rank circles. /// - public const float RANK_CIRCLE_RADIUS = 0.06f; + public const float RANK_CIRCLE_RADIUS = 0.05f; /// /// Relative width of the circle showing the accuracy. diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs index 19c7a9b606..efcb848530 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy Origin = Anchor.Centre; RelativeSizeAxes = Axes.Both; Size = new Vector2(0.8f); - Padding = new MarginPadding(2); + Padding = new MarginPadding(2.5f); InternalChildren = new Drawable[] { dProgress = new GradedCircle(0.0, accuracyC) From 9c55498058abbb9b5563319abbc942a186b556cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jan 2024 15:28:14 +0900 Subject: [PATCH 2773/4092] r# says hi --- .../NonVisual/Skinning/LegacySkinTextureFallbackTest.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs b/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs index fbe5a0e4d7..98cb66a234 100644 --- a/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs +++ b/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs @@ -169,9 +169,9 @@ namespace osu.Game.Tests.NonVisual.Skinning public IRenderer Renderer => new DummyRenderer(); public AudioManager AudioManager => null; - public IResourceStore Files => null; - public IResourceStore Resources => null; - public RealmAccess RealmAccess => null; + public IResourceStore Files => null!; + public IResourceStore Resources => null!; + public RealmAccess RealmAccess => null!; public IResourceStore CreateTextureLoaderStore(IResourceStore underlyingStore) => textureStore; } } From de32e7815b19d8ea5a19114eebc67d3f85ecaa27 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jan 2024 14:57:19 +0900 Subject: [PATCH 2774/4092] Clean up `DrawableHitObject` events on `Dispose` This is just general safeties to avoid cases where components don't correctly unbind events. --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 7 +++++++ osu.Game/Rulesets/Objects/HitObject.cs | 2 ++ 2 files changed, 9 insertions(+) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index bce28361cb..5662fb2293 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -768,6 +768,13 @@ namespace osu.Game.Rulesets.Objects.Drawables if (CurrentSkin != null) CurrentSkin.SourceChanged -= skinSourceChanged; + + // Safeties against shooting in foot in cases where these are bound by external entities (like playfield) that don't clean up. + OnNestedDrawableCreated = null; + OnNewResult = null; + OnRevertResult = null; + DefaultsApplied = null; + HitObjectApplied = null; } public Bindable AnimationStartTime { get; } = new BindableDouble(); diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index ec2a4a31f6..ef8bd08bf4 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -38,6 +38,8 @@ namespace osu.Game.Rulesets.Objects /// /// Invoked after has completed on this . /// + // TODO: This has no implicit unbind flow. Currently, if a Playfield manages HitObjects it will leave a bound event on this and cause the + // playfield to remain in memory. public event Action DefaultsApplied; public readonly Bindable StartTimeBindable = new BindableDouble(); From 76832a1495d2d94fc433e46d7413af69cd9e3463 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jan 2024 15:15:10 +0900 Subject: [PATCH 2775/4092] Remove `ScorePerformanceCache` This class was only used in two places, both on the results screen, but was holding references to `OsuPlayfield` game-wide (due to unrelated issues, but still). Because I can't really think of future use cases for this, and running the calculation twice at results screen isn't a huge overhead, let's just do that for now to keep things simple. --- osu.Game/OsuGameBase.cs | 4 -- .../PerformanceBreakdownCalculator.cs | 24 +++++-- .../Difficulty/PerformanceCalculator.cs | 5 ++ osu.Game/Scoring/ScorePerformanceCache.cs | 68 ------------------- .../Statistics/PerformanceStatistic.cs | 20 ++++-- .../Statistics/PerformanceBreakdownChart.cs | 5 +- 6 files changed, 40 insertions(+), 86 deletions(-) delete mode 100644 osu.Game/Scoring/ScorePerformanceCache.cs diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 4e465f59df..2208f7d7ca 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -340,10 +340,6 @@ namespace osu.Game dependencies.Cache(beatmapCache = new BeatmapLookupCache()); base.Content.Add(beatmapCache); - var scorePerformanceManager = new ScorePerformanceCache(); - dependencies.Cache(scorePerformanceManager); - base.Content.Add(scorePerformanceManager); - dependencies.CacheAs(rulesetConfigCache = new RulesetConfigCache(realm, RulesetStore)); var powerStatus = CreateBatteryInfo(); diff --git a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs index ad9257d4f3..4563c264f7 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs @@ -21,21 +21,29 @@ namespace osu.Game.Rulesets.Difficulty { private readonly IBeatmap playableBeatmap; private readonly BeatmapDifficultyCache difficultyCache; - private readonly ScorePerformanceCache performanceCache; - public PerformanceBreakdownCalculator(IBeatmap playableBeatmap, BeatmapDifficultyCache difficultyCache, ScorePerformanceCache performanceCache) + public PerformanceBreakdownCalculator(IBeatmap playableBeatmap, BeatmapDifficultyCache difficultyCache) { this.playableBeatmap = playableBeatmap; this.difficultyCache = difficultyCache; - this.performanceCache = performanceCache; } [ItemCanBeNull] public async Task CalculateAsync(ScoreInfo score, CancellationToken cancellationToken = default) { + var attributes = await difficultyCache.GetDifficultyAsync(score.BeatmapInfo!, score.Ruleset, score.Mods, cancellationToken).ConfigureAwait(false); + + var performanceCalculator = score.Ruleset.CreateInstance().CreatePerformanceCalculator(); + + // Performance calculation requires the beatmap and ruleset to be locally available. If not, return a default value. + if (attributes?.Attributes == null || performanceCalculator == null) + return null; + + cancellationToken.ThrowIfCancellationRequested(); + PerformanceAttributes[] performanceArray = await Task.WhenAll( // compute actual performance - performanceCache.CalculatePerformanceAsync(score, cancellationToken), + performanceCalculator.CalculateAsync(score, attributes.Value.Attributes, cancellationToken), // compute performance for perfect play getPerfectPerformance(score, cancellationToken) ).ConfigureAwait(false); @@ -88,8 +96,12 @@ namespace osu.Game.Rulesets.Difficulty cancellationToken ).ConfigureAwait(false); - // ScorePerformanceCache is not used to avoid caching multiple copies of essentially identical perfect performance attributes - return difficulty == null ? null : ruleset.CreatePerformanceCalculator()?.Calculate(perfectPlay, difficulty.Value.Attributes.AsNonNull()); + var performanceCalculator = ruleset.CreatePerformanceCalculator(); + + if (performanceCalculator == null || difficulty == null) + return null; + + return await performanceCalculator.CalculateAsync(perfectPlay, difficulty.Value.Attributes.AsNonNull(), cancellationToken).ConfigureAwait(false); }, cancellationToken); } diff --git a/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs index f5e826f8c7..966da0ff12 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Threading; +using System.Threading.Tasks; using osu.Game.Beatmaps; using osu.Game.Scoring; @@ -15,6 +17,9 @@ namespace osu.Game.Rulesets.Difficulty Ruleset = ruleset; } + public Task CalculateAsync(ScoreInfo score, DifficultyAttributes attributes, CancellationToken cancellationToken) + => Task.Run(() => CreatePerformanceAttributes(score, attributes), cancellationToken); + public PerformanceAttributes Calculate(ScoreInfo score, DifficultyAttributes attributes) => CreatePerformanceAttributes(score, attributes); diff --git a/osu.Game/Scoring/ScorePerformanceCache.cs b/osu.Game/Scoring/ScorePerformanceCache.cs deleted file mode 100644 index 1f2b1aeb95..0000000000 --- a/osu.Game/Scoring/ScorePerformanceCache.cs +++ /dev/null @@ -1,68 +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 System; -using System.Threading; -using System.Threading.Tasks; -using osu.Framework.Allocation; -using osu.Game.Beatmaps; -using osu.Game.Database; -using osu.Game.Rulesets.Difficulty; - -namespace osu.Game.Scoring -{ - /// - /// A component which performs and acts as a central cache for performance calculations of locally databased scores. - /// Currently not persisted between game sessions. - /// - public partial class ScorePerformanceCache : MemoryCachingComponent - { - [Resolved] - private BeatmapDifficultyCache difficultyCache { get; set; } = null!; - - protected override bool CacheNullValues => false; - - /// - /// Calculates performance for the given . - /// - /// The score to do the calculation on. - /// An optional to cancel the operation. - public Task CalculatePerformanceAsync(ScoreInfo score, CancellationToken token = default) => - GetAsync(new PerformanceCacheLookup(score), token); - - protected override async Task ComputeValueAsync(PerformanceCacheLookup lookup, CancellationToken token = default) - { - var score = lookup.ScoreInfo; - - var attributes = await difficultyCache.GetDifficultyAsync(score.BeatmapInfo!, score.Ruleset, score.Mods, token).ConfigureAwait(false); - - // Performance calculation requires the beatmap and ruleset to be locally available. If not, return a default value. - if (attributes?.Attributes == null) - return null; - - token.ThrowIfCancellationRequested(); - - return score.Ruleset.CreateInstance().CreatePerformanceCalculator()?.Calculate(score, attributes.Value.Attributes); - } - - public readonly struct PerformanceCacheLookup - { - public readonly ScoreInfo ScoreInfo; - - public PerformanceCacheLookup(ScoreInfo info) - { - ScoreInfo = info; - } - - public override int GetHashCode() - { - var hash = new HashCode(); - - hash.Add(ScoreInfo.Hash); - hash.Add(ScoreInfo.ID); - - return hash.ToHashCode(); - } - } - } -} diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs index 22509b2cea..22c1e26d43 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs @@ -5,10 +5,11 @@ using System; using System.Threading; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions; using osu.Framework.Graphics; +using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Resources.Localisation.Web; using osu.Game.Scoring; @@ -32,7 +33,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics } [BackgroundDependencyLoader] - private void load(ScorePerformanceCache performanceCache) + private void load(BeatmapDifficultyCache difficultyCache, CancellationToken? cancellationToken) { if (score.PP.HasValue) { @@ -40,8 +41,19 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics } else { - performanceCache.CalculatePerformanceAsync(score, cancellationTokenSource.Token) - .ContinueWith(t => Schedule(() => setPerformanceValue(t.GetResultSafely()?.Total)), cancellationTokenSource.Token); + Task.Run(async () => + { + var attributes = await difficultyCache.GetDifficultyAsync(score.BeatmapInfo!, score.Ruleset, score.Mods, cancellationToken ?? default).ConfigureAwait(false); + var performanceCalculator = score.Ruleset.CreateInstance().CreatePerformanceCalculator(); + + // Performance calculation requires the beatmap and ruleset to be locally available. If not, return a default value. + if (attributes?.Attributes == null || performanceCalculator == null) + return; + + var result = await performanceCalculator.CalculateAsync(score, attributes.Value.Attributes, cancellationToken ?? default).ConfigureAwait(false); + + Schedule(() => setPerformanceValue(result.Total)); + }, cancellationToken ?? default); } } diff --git a/osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs b/osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs index ee0ce6183d..8b13f0951c 100644 --- a/osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs +++ b/osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs @@ -39,9 +39,6 @@ namespace osu.Game.Screens.Ranking.Statistics private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - [Resolved] - private ScorePerformanceCache performanceCache { get; set; } - [Resolved] private BeatmapDifficultyCache difficultyCache { get; set; } @@ -148,7 +145,7 @@ namespace osu.Game.Screens.Ranking.Statistics spinner.Show(); - new PerformanceBreakdownCalculator(playableBeatmap, difficultyCache, performanceCache) + new PerformanceBreakdownCalculator(playableBeatmap, difficultyCache) .CalculateAsync(score, cancellationTokenSource.Token) .ContinueWith(t => Schedule(() => setPerformanceValue(t.GetResultSafely()))); } From eb90ee5415b05dd827464fcd3c715f731da2df1f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jan 2024 15:39:31 +0900 Subject: [PATCH 2776/4092] Add safety in `ResultsScreen.Exit` to ensure `HitEvents` are not holding references This is a catch-all safety disconnecting `ScoreInfo` from `HitObject`s. --- osu.Game/Screens/Ranking/ResultsScreen.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 697d62ad6e..410fdf7090 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -275,6 +275,11 @@ namespace osu.Game.Screens.Ranking if (base.OnExiting(e)) return true; + // This is a stop-gap safety against components holding references to gameplay after exiting the gameplay flow. + // Right now, HitEvents are only used up to the results screen. If this changes in the future we need to remove + // HitObject references from HitEvent. + Score.HitEvents.Clear(); + this.FadeOut(100); return false; } From 760368709a4855d0c64a89b03d455a8dd514b987 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jan 2024 16:12:00 +0900 Subject: [PATCH 2777/4092] Mark some delegates as static because we can --- osu.Game.Rulesets.Osu/OsuInputManager.cs | 2 +- osu.Game/Rulesets/UI/RulesetInputManager.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs index ccd388192e..e472de1dfe 100644 --- a/osu.Game.Rulesets.Osu/OsuInputManager.cs +++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs @@ -98,7 +98,7 @@ namespace osu.Game.Rulesets.Osu base.ReloadMappings(realmKeyBindings); if (!AllowGameplayInputs) - KeyBindings = KeyBindings.Where(b => b.GetAction() == OsuAction.Smoke).ToList(); + KeyBindings = KeyBindings.Where(static b => b.GetAction() == OsuAction.Smoke).ToList(); } } } diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 041c7a13ae..a08c3bab08 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -218,7 +218,7 @@ namespace osu.Game.Rulesets.UI { base.ReloadMappings(realmKeyBindings); - KeyBindings = KeyBindings.Where(b => RealmKeyBindingStore.CheckValidForGameplay(b.KeyCombination)).ToList(); + KeyBindings = KeyBindings.Where(static b => RealmKeyBindingStore.CheckValidForGameplay(b.KeyCombination)).ToList(); RealmKeyBindingStore.ClearDuplicateBindings(KeyBindings); } } From fb24c663425648481a0e6f2006801cc0165e34dc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jan 2024 16:13:30 +0900 Subject: [PATCH 2778/4092] Mark `ResultsScreen.Score` as nullable Is nullable in playlist results at very least. --- .../Visual/Gameplay/TestScenePlayerLocalScoreImport.cs | 4 ++-- osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs | 2 +- .../Visual/Playlists/TestScenePlaylistsResultsScreen.cs | 2 +- .../Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs | 2 +- osu.Game/Screens/Play/SpectatorResultsScreen.cs | 2 +- osu.Game/Screens/Ranking/ResultsScreen.cs | 6 ++++-- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 7 +++++++ 7 files changed, 17 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index fafd1330cc..c645b1b01c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -138,8 +138,8 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); // Player creates new instance of mods after gameplay to ensure any runtime references to drawables etc. are not retained. - AddAssert("results screen score has copied mods", () => (Player.GetChildScreen() as ResultsScreen)?.Score.Mods.First(), () => Is.Not.SameAs(playerMods.First())); - AddAssert("results screen score has matching", () => (Player.GetChildScreen() as ResultsScreen)?.Score.Mods.First(), () => Is.EqualTo(playerMods.First())); + AddAssert("results screen score has copied mods", () => (Player.GetChildScreen() as ResultsScreen)?.Score?.Mods.First(), () => Is.Not.SameAs(playerMods.First())); + AddAssert("results screen score has matching", () => (Player.GetChildScreen() as ResultsScreen)?.Score?.Mods.First(), () => Is.EqualTo(playerMods.First())); AddUntilStep("score in database", () => Realm.Run(r => r.Find(Player.Score.ScoreInfo.ID) != null)); AddUntilStep("databased score has correct mods", () => Realm.Run(r => r.Find(Player.Score.ScoreInfo.ID))!.Mods.First(), () => Is.EqualTo(playerMods.First())); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 747805edc8..8c7576ff52 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -698,7 +698,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { var scoreInfo = ((ResultsScreen)multiplayerComponents.CurrentScreen).Score; - return !scoreInfo.Passed && scoreInfo.Rank == ScoreRank.F; + return scoreInfo?.Passed == false && scoreInfo.Rank == ScoreRank.F; }); } diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index e805b03542..25ee20b089 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -420,7 +420,7 @@ namespace osu.Game.Tests.Visual.Playlists public new LoadingSpinner RightSpinner => base.RightSpinner; public new ScorePanelList ScorePanelList => base.ScorePanelList; - public TestResultsScreen(ScoreInfo score, int roomId, PlaylistItem playlistItem, bool allowRetry = true) + public TestResultsScreen([CanBeNull] ScoreInfo score, int roomId, PlaylistItem playlistItem, bool allowRetry = true) : base(score, roomId, playlistItem, allowRetry) { } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs index aa72394ac9..6a1924dea2 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists [Resolved] private RulesetStore rulesets { get; set; } - public PlaylistsResultsScreen(ScoreInfo score, long roomId, PlaylistItem playlistItem, bool allowRetry, bool allowWatchingReplay = true) + public PlaylistsResultsScreen([CanBeNull] ScoreInfo score, long roomId, PlaylistItem playlistItem, bool allowRetry, bool allowWatchingReplay = true) : base(score, allowRetry, allowWatchingReplay) { this.roomId = roomId; diff --git a/osu.Game/Screens/Play/SpectatorResultsScreen.cs b/osu.Game/Screens/Play/SpectatorResultsScreen.cs index 001d3b4bbc..393cbddb34 100644 --- a/osu.Game/Screens/Play/SpectatorResultsScreen.cs +++ b/osu.Game/Screens/Play/SpectatorResultsScreen.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Play private void userBeganPlaying(int userId, SpectatorState state) { - if (userId == Score.UserID) + if (userId == Score?.UserID) { Schedule(() => { diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 410fdf7090..82dade40eb 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -45,6 +46,7 @@ namespace osu.Game.Screens.Ranking public readonly Bindable SelectedScore = new Bindable(); + [CanBeNull] public readonly ScoreInfo Score; protected ScorePanelList ScorePanelList { get; private set; } @@ -69,7 +71,7 @@ namespace osu.Game.Screens.Ranking private Sample popInSample; - protected ResultsScreen(ScoreInfo score, bool allowRetry, bool allowWatchingReplay = true) + protected ResultsScreen([CanBeNull] ScoreInfo score, bool allowRetry, bool allowWatchingReplay = true) { Score = score; this.allowRetry = allowRetry; @@ -278,7 +280,7 @@ namespace osu.Game.Screens.Ranking // This is a stop-gap safety against components holding references to gameplay after exiting the gameplay flow. // Right now, HitEvents are only used up to the results screen. If this changes in the future we need to remove // HitObject references from HitEvent. - Score.HitEvents.Clear(); + Score?.HitEvents.Clear(); this.FadeOut(100); return false; diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index da08a26a58..22d631e137 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -45,12 +46,16 @@ namespace osu.Game.Screens.Ranking { base.LoadComplete(); + Debug.Assert(Score != null); + if (ShowUserStatistics) statisticsSubscription = soloStatisticsWatcher.RegisterForStatisticsUpdateAfter(Score, update => statisticsUpdate.Value = update); } protected override StatisticsPanel CreateStatisticsPanel() { + Debug.Assert(Score != null); + if (ShowUserStatistics) { return new SoloStatisticsPanel(Score) @@ -64,6 +69,8 @@ namespace osu.Game.Screens.Ranking protected override APIRequest? FetchScores(Action>? scoresCallback) { + Debug.Assert(Score != null); + if (Score.BeatmapInfo!.OnlineID <= 0 || Score.BeatmapInfo.Status <= BeatmapOnlineStatus.Pending) return null; From a41ba7c381ba66ca432a34dd4ecc9cd655119772 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 29 Jan 2024 08:43:16 +0100 Subject: [PATCH 2779/4092] Fix more nullable inspections --- osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs | 4 ++-- osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs | 2 +- osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index 3ac4d25028..636cd78d9c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -47,8 +47,8 @@ namespace osu.Game.Tests.Visual.Gameplay seekTo(referenceBeatmap.HitObjects[^1].GetEndTime()); AddUntilStep("results displayed", () => getResultsScreen()?.IsLoaded == true); - AddAssert("score has combo", () => getResultsScreen().Score.Combo > 100); - AddAssert("score has no misses", () => getResultsScreen().Score.Statistics[HitResult.Miss] == 0); + AddAssert("score has combo", () => getResultsScreen().Score!.Combo > 100); + AddAssert("score has no misses", () => getResultsScreen().Score!.Statistics[HitResult.Miss] == 0); AddUntilStep("avatar displayed", () => getAvatar() != null); AddAssert("avatar not clickable", () => getAvatar().ChildrenOfType().First().Action == null); diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs index 459a8b0df5..6590339311 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs @@ -193,7 +193,7 @@ namespace osu.Game.Tests.Visual.Navigation case ScorePresentType.Results: AddUntilStep("wait for results", () => lastWaitedScreen != Game.ScreenStack.CurrentScreen && Game.ScreenStack.CurrentScreen is ResultsScreen); AddStep("store last waited screen", () => lastWaitedScreen = Game.ScreenStack.CurrentScreen); - AddUntilStep("correct score displayed", () => ((ResultsScreen)Game.ScreenStack.CurrentScreen).Score.Equals(getImport())); + AddUntilStep("correct score displayed", () => ((ResultsScreen)Game.ScreenStack.CurrentScreen).Score!.Equals(getImport())); AddAssert("correct ruleset selected", () => Game.Ruleset.Value.Equals(getImport().Ruleset)); break; diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 866e20d063..5671cbebd7 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -418,7 +418,7 @@ namespace osu.Game.Tests.Visual.Ranking public UnrankedSoloResultsScreen(ScoreInfo score) : base(score, true) { - Score.BeatmapInfo!.OnlineID = 0; + Score!.BeatmapInfo!.OnlineID = 0; Score.BeatmapInfo.Status = BeatmapOnlineStatus.Pending; } From ef94eff5745e577cec54615d6c69522100b20b87 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jan 2024 16:56:28 +0900 Subject: [PATCH 2780/4092] Rename `PollingChatClientConnector` to better describe usage --- osu.Game/Online/API/DummyAPIAccess.cs | 2 +- ...llingChatClientConnector.cs => TestChatClientConnector.cs} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename osu.Game/Tests/{PollingChatClientConnector.cs => TestChatClientConnector.cs} (89%) diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 2d5852b209..3c60513495 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -148,7 +148,7 @@ namespace osu.Game.Online.API public IHubClientConnector? GetHubConnector(string clientName, string endpoint, bool preferMessagePack) => null; - public IChatClient GetChatClient() => new PollingChatClientConnector(this); + public IChatClient GetChatClient() => new TestChatClientConnector(this); public RegistrationRequest.RegistrationRequestErrors? CreateAccount(string email, string username, string password) { diff --git a/osu.Game/Tests/PollingChatClientConnector.cs b/osu.Game/Tests/TestChatClientConnector.cs similarity index 89% rename from osu.Game/Tests/PollingChatClientConnector.cs rename to osu.Game/Tests/TestChatClientConnector.cs index f1b0d9dd7d..40e15b5ef5 100644 --- a/osu.Game/Tests/PollingChatClientConnector.cs +++ b/osu.Game/Tests/TestChatClientConnector.cs @@ -11,7 +11,7 @@ using osu.Game.Online.Chat; namespace osu.Game.Tests { - public class PollingChatClientConnector : PersistentEndpointClientConnector, IChatClient + public class TestChatClientConnector : PersistentEndpointClientConnector, IChatClient { public event Action? ChannelJoined; @@ -29,7 +29,7 @@ namespace osu.Game.Tests // don't really need to do anything special if we poll every second anyway. } - public PollingChatClientConnector(IAPIProvider api) + public TestChatClientConnector(IAPIProvider api) : base(api) { Start(); From 363fd1d54f76c82e639d9394aaa05e1a0cbfa28b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 29 Jan 2024 09:05:03 +0100 Subject: [PATCH 2781/4092] Remove no longer relevant changes --- .../DevelopmentEndpointConfiguration.cs | 1 - osu.Game/Online/EndpointConfiguration.cs | 5 - .../ExperimentalEndpointConfiguration.cs | 1 - .../WebSocket/OsuClientWebSocket.cs | 154 ------------------ .../Online/ProductionEndpointConfiguration.cs | 1 - 5 files changed, 162 deletions(-) delete mode 100644 osu.Game/Online/Notifications/WebSocket/OsuClientWebSocket.cs diff --git a/osu.Game/Online/DevelopmentEndpointConfiguration.cs b/osu.Game/Online/DevelopmentEndpointConfiguration.cs index 1c78c3c147..5f3c353f4d 100644 --- a/osu.Game/Online/DevelopmentEndpointConfiguration.cs +++ b/osu.Game/Online/DevelopmentEndpointConfiguration.cs @@ -13,7 +13,6 @@ namespace osu.Game.Online SpectatorEndpointUrl = $@"{APIEndpointUrl}/signalr/spectator"; MultiplayerEndpointUrl = $@"{APIEndpointUrl}/signalr/multiplayer"; MetadataEndpointUrl = $@"{APIEndpointUrl}/signalr/metadata"; - NotificationsWebSocketEndpointUrl = "wss://dev.ppy.sh/home/notifications/feed"; } } } diff --git a/osu.Game/Online/EndpointConfiguration.cs b/osu.Game/Online/EndpointConfiguration.cs index 6187471b65..f3bcced630 100644 --- a/osu.Game/Online/EndpointConfiguration.cs +++ b/osu.Game/Online/EndpointConfiguration.cs @@ -44,10 +44,5 @@ namespace osu.Game.Online /// The endpoint for the SignalR metadata server. /// public string MetadataEndpointUrl { get; set; } - - /// - /// The endpoint for the notifications websocket. - /// - public string NotificationsWebSocketEndpointUrl { get; set; } } } diff --git a/osu.Game/Online/ExperimentalEndpointConfiguration.cs b/osu.Game/Online/ExperimentalEndpointConfiguration.cs index bc65fd63f3..c3d0014c8b 100644 --- a/osu.Game/Online/ExperimentalEndpointConfiguration.cs +++ b/osu.Game/Online/ExperimentalEndpointConfiguration.cs @@ -14,7 +14,6 @@ namespace osu.Game.Online SpectatorEndpointUrl = "https://spectator.ppy.sh/spectator"; MultiplayerEndpointUrl = "https://spectator.ppy.sh/multiplayer"; MetadataEndpointUrl = "https://spectator.ppy.sh/metadata"; - NotificationsWebSocketEndpointUrl = "wss://notify.ppy.sh"; } } } diff --git a/osu.Game/Online/Notifications/WebSocket/OsuClientWebSocket.cs b/osu.Game/Online/Notifications/WebSocket/OsuClientWebSocket.cs deleted file mode 100644 index 965f606bdc..0000000000 --- a/osu.Game/Online/Notifications/WebSocket/OsuClientWebSocket.cs +++ /dev/null @@ -1,154 +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 System; -using System.Diagnostics; -using System.Net; -using System.Net.WebSockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Newtonsoft.Json; -using osu.Framework.Extensions.TypeExtensions; -using osu.Framework.Logging; -using osu.Game.Online.API; - -namespace osu.Game.Online.Notifications.WebSocket -{ - public class OsuClientWebSocket : IAsyncDisposable - { - public event Func? MessageReceived; - public event Func? Closed; - - private readonly string endpoint; - private readonly ClientWebSocket socket; - - private CancellationTokenSource? linkedTokenSource = null; - - public OsuClientWebSocket(IAPIProvider api, string endpoint) - { - socket = new ClientWebSocket(); - socket.Options.SetRequestHeader(@"Authorization", @$"Bearer {api.AccessToken}"); - socket.Options.Proxy = WebRequest.DefaultWebProxy; - if (socket.Options.Proxy != null) - socket.Options.Proxy.Credentials = CredentialCache.DefaultCredentials; - - this.endpoint = endpoint; - } - - public async Task ConnectAsync(CancellationToken cancellationToken) - { - if (socket.State == WebSocketState.Connecting || socket.State == WebSocketState.Open) - throw new InvalidOperationException("Connection is already opened"); - - Debug.Assert(linkedTokenSource == null); - linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - - await socket.ConnectAsync(new Uri(endpoint), linkedTokenSource.Token).ConfigureAwait(false); - runReadLoop(linkedTokenSource.Token); - } - - private void runReadLoop(CancellationToken cancellationToken) => Task.Factory.StartNew(async () => - { - byte[] buffer = new byte[1024]; - StringBuilder messageResult = new StringBuilder(); - - while (!cancellationToken.IsCancellationRequested) - { - try - { - WebSocketReceiveResult result = await socket.ReceiveAsync(buffer, cancellationToken).ConfigureAwait(false); - - switch (result.MessageType) - { - case WebSocketMessageType.Text: - messageResult.Append(Encoding.UTF8.GetString(buffer[..result.Count])); - - if (result.EndOfMessage) - { - SocketMessage? message = JsonConvert.DeserializeObject(messageResult.ToString()); - messageResult.Clear(); - - Debug.Assert(message != null); - - if (message.Error != null) - { - Logger.Log($"{GetType().ReadableName()} error: {message.Error}", LoggingTarget.Network); - break; - } - - await invokeMessageReceived(message).ConfigureAwait(false); - } - - break; - - case WebSocketMessageType.Binary: - throw new NotImplementedException("Binary message type not supported."); - - case WebSocketMessageType.Close: - throw new WebException("Connection closed by remote host."); - } - } - catch (Exception ex) - { - await invokeClosed(ex).ConfigureAwait(false); - return; - } - } - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default); - - private async Task invokeMessageReceived(SocketMessage message) - { - if (MessageReceived == null) - return; - - var invocationList = MessageReceived.GetInvocationList(); - - // ReSharper disable once PossibleInvalidCastExceptionInForeachLoop - foreach (Func handler in invocationList) - await handler.Invoke(message).ConfigureAwait(false); - } - - private async Task invokeClosed(Exception ex) - { - if (Closed == null) - return; - - var invocationList = Closed.GetInvocationList(); - - // ReSharper disable once PossibleInvalidCastExceptionInForeachLoop - foreach (Func handler in invocationList) - await handler.Invoke(ex).ConfigureAwait(false); - } - - public Task SendMessage(SocketMessage message, CancellationToken cancellationToken) - { - if (socket.State != WebSocketState.Open) - return Task.CompletedTask; - - return socket.SendAsync(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message)), WebSocketMessageType.Text, true, cancellationToken); - } - - public async Task DisconnectAsync() - { - linkedTokenSource?.Cancel(); - await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, @"Disconnecting", CancellationToken.None).ConfigureAwait(false); - linkedTokenSource?.Dispose(); - linkedTokenSource = null; - } - - public async ValueTask DisposeAsync() - { - try - { - await DisconnectAsync().ConfigureAwait(false); - } - catch - { - // Closure can fail if the connection is aborted. Don't really care since it's disposed anyway. - } - - socket.Dispose(); - } - } -} diff --git a/osu.Game/Online/ProductionEndpointConfiguration.cs b/osu.Game/Online/ProductionEndpointConfiguration.cs index a26a25bce5..0244761b65 100644 --- a/osu.Game/Online/ProductionEndpointConfiguration.cs +++ b/osu.Game/Online/ProductionEndpointConfiguration.cs @@ -13,7 +13,6 @@ namespace osu.Game.Online SpectatorEndpointUrl = "https://spectator.ppy.sh/spectator"; MultiplayerEndpointUrl = "https://spectator.ppy.sh/multiplayer"; MetadataEndpointUrl = "https://spectator.ppy.sh/metadata"; - NotificationsWebSocketEndpointUrl = "wss://notify.ppy.sh"; } } } From 96811a88749b7cc763f721ecebca20006ed08289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 29 Jan 2024 09:13:44 +0100 Subject: [PATCH 2782/4092] Fix `APIAccess` spamming requests while waiting for second factor --- osu.Game/Online/API/APIAccess.cs | 60 +++++++++++++++++--------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 8b369d0f3f..52506e4b12 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -269,42 +269,44 @@ namespace osu.Game.Online.API } } - var userReq = new GetMeRequest(); - userReq.Failure += ex => + if (state.Value != APIState.RequiresSecondFactorAuth) { - if (ex is APIException) + var userReq = new GetMeRequest(); + userReq.Failure += ex => { - LastLoginError = ex; - log.Add($@"Login failed for username {ProvidedUsername} on user retrieval ({LastLoginError.Message})!"); - Logout(); - } - else if (ex is WebException webException && webException.Message == @"Unauthorized") + if (ex is APIException) + { + LastLoginError = ex; + log.Add($@"Login failed for username {ProvidedUsername} on user retrieval ({LastLoginError.Message})!"); + Logout(); + } + else if (ex is WebException webException && webException.Message == @"Unauthorized") + { + log.Add(@"Login no longer valid"); + Logout(); + } + else + { + state.Value = APIState.Failing; + } + }; + userReq.Success += me => { - log.Add(@"Login no longer valid"); - Logout(); - } - else + me.Status.Value = configStatus.Value ?? UserStatus.Online; + + setLocalUser(me); + + state.Value = me.SessionVerified ? APIState.Online : APIState.RequiresSecondFactorAuth; + failureCount = 0; + }; + + if (!handleRequest(userReq)) { state.Value = APIState.Failing; + return; } - }; - userReq.Success += me => - { - me.Status.Value = configStatus.Value ?? UserStatus.Online; - - setLocalUser(me); - - state.Value = me.SessionVerified ? APIState.Online : APIState.RequiresSecondFactorAuth; - failureCount = 0; - }; - - if (!handleRequest(userReq)) - { - state.Value = APIState.Failing; - return; } - - if (state.Value == APIState.RequiresSecondFactorAuth) + else { if (string.IsNullOrEmpty(SecondFactorCode)) return; From 6a469f2cb6e45ec46c435682dc99e6e0d09b3a82 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jan 2024 17:18:17 +0900 Subject: [PATCH 2783/4092] Use `switch` instead of `if-else` --- osu.Game/Online/API/APIAccess.cs | 114 +++++++++++++++++-------------- 1 file changed, 62 insertions(+), 52 deletions(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 52506e4b12..d3707fe74d 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -269,69 +269,79 @@ namespace osu.Game.Online.API } } - if (state.Value != APIState.RequiresSecondFactorAuth) + switch (state.Value) { - var userReq = new GetMeRequest(); - userReq.Failure += ex => + case APIState.RequiresSecondFactorAuth: { - if (ex is APIException) + if (string.IsNullOrEmpty(SecondFactorCode)) + return; + + state.Value = APIState.Connecting; + LastLoginError = null; + + var verificationRequest = new VerifySessionRequest(SecondFactorCode); + + verificationRequest.Success += () => state.Value = APIState.Online; + verificationRequest.Failure += ex => { + state.Value = APIState.RequiresSecondFactorAuth; LastLoginError = ex; - log.Add($@"Login failed for username {ProvidedUsername} on user retrieval ({LastLoginError.Message})!"); - Logout(); - } - else if (ex is WebException webException && webException.Message == @"Unauthorized") - { - log.Add(@"Login no longer valid"); - Logout(); - } - else + SecondFactorCode = null; + }; + + if (!handleRequest(verificationRequest)) { state.Value = APIState.Failing; + return; } - }; - userReq.Success += me => - { - me.Status.Value = configStatus.Value ?? UserStatus.Online; - setLocalUser(me); + if (state.Value != APIState.Online) + return; - state.Value = me.SessionVerified ? APIState.Online : APIState.RequiresSecondFactorAuth; - failureCount = 0; - }; - - if (!handleRequest(userReq)) - { - state.Value = APIState.Failing; - return; - } - } - else - { - if (string.IsNullOrEmpty(SecondFactorCode)) - return; - - state.Value = APIState.Connecting; - LastLoginError = null; - - var verificationRequest = new VerifySessionRequest(SecondFactorCode); - - verificationRequest.Success += () => state.Value = APIState.Online; - verificationRequest.Failure += ex => - { - state.Value = APIState.RequiresSecondFactorAuth; - LastLoginError = ex; - SecondFactorCode = null; - }; - - if (!handleRequest(verificationRequest)) - { - state.Value = APIState.Failing; - return; + break; } - if (state.Value != APIState.Online) - return; + default: + { + var userReq = new GetMeRequest(); + + userReq.Failure += ex => + { + if (ex is APIException) + { + LastLoginError = ex; + log.Add($@"Login failed for username {ProvidedUsername} on user retrieval ({LastLoginError.Message})!"); + Logout(); + } + else if (ex is WebException webException && webException.Message == @"Unauthorized") + { + log.Add(@"Login no longer valid"); + Logout(); + } + else + { + state.Value = APIState.Failing; + } + }; + + userReq.Success += me => + { + me.Status.Value = configStatus.Value ?? UserStatus.Online; + + setLocalUser(me); + + state.Value = me.SessionVerified ? APIState.Online : APIState.RequiresSecondFactorAuth; + failureCount = 0; + }; + + if (!handleRequest(userReq)) + { + state.Value = APIState.Failing; + return; + } + + break; + } } var friendsReq = new GetFriendsRequest(); From 540ff0da5b1192a9f442952973ddb4ae02bdb80c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jan 2024 17:54:34 +0900 Subject: [PATCH 2784/4092] Add loading layer when requesting a code reissue --- .../Overlays/Login/SecondFactorAuthForm.cs | 82 ++++++++++++------- 1 file changed, 54 insertions(+), 28 deletions(-) diff --git a/osu.Game/Overlays/Login/SecondFactorAuthForm.cs b/osu.Game/Overlays/Login/SecondFactorAuthForm.cs index 566587a541..dcd3119f33 100644 --- a/osu.Game/Overlays/Login/SecondFactorAuthForm.cs +++ b/osu.Game/Overlays/Login/SecondFactorAuthForm.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 System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -17,12 +18,14 @@ using osuTK; namespace osu.Game.Overlays.Login { - public partial class SecondFactorAuthForm : FillFlowContainer + public partial class SecondFactorAuthForm : Container { private OsuTextBox codeTextBox = null!; private LinkFlowContainer explainText = null!; private ErrorTextFlowContainer errorText = null!; + private LoadingLayer loading = null!; + [Resolved] private IAPIProvider api { get; set; } = null!; @@ -31,8 +34,6 @@ namespace osu.Game.Overlays.Login { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - Direction = FillDirection.Vertical; - Spacing = new Vector2(0, SettingsSection.ITEM_SPACING); Children = new Drawable[] { @@ -40,42 +41,56 @@ namespace osu.Game.Overlays.Login { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Horizontal = SettingsPanel.CONTENT_MARGINS }, Direction = FillDirection.Vertical, - Spacing = new Vector2(0f, SettingsSection.ITEM_SPACING), + Spacing = new Vector2(0, SettingsSection.ITEM_SPACING), Children = new Drawable[] { - new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(weight: FontWeight.Regular)) + new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Text = "An email has been sent to you with a verification code. Enter the code.", + Padding = new MarginPadding { Horizontal = SettingsPanel.CONTENT_MARGINS }, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0f, SettingsSection.ITEM_SPACING), + Children = new Drawable[] + { + new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(weight: FontWeight.Regular)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Text = "An email has been sent to you with a verification code. Enter the code.", + }, + codeTextBox = new OsuTextBox + { + PlaceholderText = "Enter code", + RelativeSizeAxes = Axes.X, + TabbableContentContainer = this, + }, + explainText = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(weight: FontWeight.Regular)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, + errorText = new ErrorTextFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Alpha = 0, + }, + }, }, - codeTextBox = new OsuTextBox - { - PlaceholderText = "Enter code", - RelativeSizeAxes = Axes.X, - TabbableContentContainer = this, - }, - explainText = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(weight: FontWeight.Regular)) + new LinkFlowContainer { + Padding = new MarginPadding { Horizontal = SettingsPanel.CONTENT_MARGINS }, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, }, - errorText = new ErrorTextFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Alpha = 0, - }, - }, + } }, - new LinkFlowContainer + loading = new LoadingLayer(true) { - Padding = new MarginPadding { Horizontal = SettingsPanel.CONTENT_MARGINS }, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - }, + Padding = new MarginPadding { Vertical = -SettingsSection.ITEM_SPACING }, + } }; explainText.AddParagraph(UserVerificationStrings.BoxInfoCheckSpam); @@ -85,9 +100,20 @@ namespace osu.Game.Overlays.Login explainText.AddText(". You can also "); explainText.AddLink(UserVerificationStrings.BoxInfoReissueLink, () => { + loading.Show(); + var reissueRequest = new ReissueVerificationCodeRequest(); - reissueRequest.Failure += ex => Logger.Error(ex, @"Failed to retrieve new verification code."); - api.Perform(reissueRequest); + reissueRequest.Failure += ex => + { + Logger.Error(ex, @"Failed to retrieve new verification code."); + loading.Hide(); + }; + reissueRequest.Success += () => + { + loading.Hide(); + }; + + Task.Run(() => api.Perform(reissueRequest)); }); explainText.AddText(" or "); explainText.AddLink(UserVerificationStrings.BoxInfoLogoutLink, () => { api.Logout(); }); From dc28a6b873f0af9835f991acb805e647123e8446 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jan 2024 19:33:07 +0900 Subject: [PATCH 2785/4092] Update resources --- osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs | 2 +- osu.Game/osu.Game.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index e46b92795a..a552b22c11 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Backgrounds private Background background; private int currentDisplay; - private const int background_count = 7; + private const int background_count = 8; private IBindable user; private Bindable skin; private Bindable source; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 6f71424130..48ba7beb7d 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + From 8341da7586126de557904f4db124113d33ef9e4d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jan 2024 19:41:03 +0900 Subject: [PATCH 2786/4092] Revert "Remove dead code" (mostly) This reverts commit 6070eac6eec64852a8aa27a2c283cf9388c6ad5c. --- osu.Game/Skinning/LegacyJudgementPieceNew.cs | 2 +- osu.Game/Skinning/LegacyJudgementPieceOld.cs | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Skinning/LegacyJudgementPieceNew.cs b/osu.Game/Skinning/LegacyJudgementPieceNew.cs index bd1508b4a6..5ff28726c0 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceNew.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceNew.cs @@ -53,7 +53,7 @@ namespace osu.Game.Skinning if (!result.IsMiss()) { //new judgement shows old as a temporary effect - AddInternal(temporaryOldStyle = new LegacyJudgementPieceOld(result, createMainDrawable, 1.05f) + AddInternal(temporaryOldStyle = new LegacyJudgementPieceOld(result, createMainDrawable, 1.05f, true) { Blending = BlendingParameters.Additive, Anchor = Anchor.Centre, diff --git a/osu.Game/Skinning/LegacyJudgementPieceOld.cs b/osu.Game/Skinning/LegacyJudgementPieceOld.cs index c15ff041d1..edfb5a23ec 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceOld.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceOld.cs @@ -18,14 +18,16 @@ namespace osu.Game.Skinning private readonly HitResult result; private readonly float finalScale; + private readonly bool forceTransforms; [Resolved] private ISkinSource skin { get; set; } = null!; - public LegacyJudgementPieceOld(HitResult result, Func createMainDrawable, float finalScale = 1f) + public LegacyJudgementPieceOld(HitResult result, Func createMainDrawable, float finalScale = 1f, bool forceTransforms = false) { this.result = result; this.finalScale = finalScale; + this.forceTransforms = forceTransforms; AutoSizeAxes = Axes.Both; Origin = Anchor.Centre; @@ -45,6 +47,10 @@ namespace osu.Game.Skinning this.FadeInFromZero(fade_in_length); + // legacy judgements don't play any transforms if they are an animation.... UNLESS they are the temporary displayed judgement from new piece. + if (animation?.FrameCount > 1 && !forceTransforms) + return; + if (result.IsMiss()) { decimal? legacyVersion = skin.GetConfig(SkinConfiguration.LegacySetting.Version)?.Value; @@ -95,12 +101,6 @@ namespace osu.Game.Skinning private bool isMissedTick() => result.IsMiss() && result != HitResult.Miss; - private void applyMissedTickScaling() - { - this.ScaleTo(0.6f); - this.ScaleTo(0.3f, 100, Easing.In); - } - public Drawable GetAboveHitObjectsProxiedContent() => CreateProxy(); } } From 1efdf2ae253a9844dc437c6f26767efb17bfb06a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jan 2024 19:45:56 +0900 Subject: [PATCH 2787/4092] 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 969fd52340..da404599ef 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 bbcabc6360..0855c0c6d6 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From 8a11ff122712a15a2f466f01434af76e00ac98f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 29 Jan 2024 11:46:45 +0100 Subject: [PATCH 2788/4092] Apply local precision workaround to editor effect section --- osu.Game/Screens/Edit/Timing/EffectSection.cs | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/EffectSection.cs b/osu.Game/Screens/Edit/Timing/EffectSection.cs index 7e484433f7..f321f7eeb0 100644 --- a/osu.Game/Screens/Edit/Timing/EffectSection.cs +++ b/osu.Game/Screens/Edit/Timing/EffectSection.cs @@ -52,17 +52,38 @@ namespace osu.Game.Screens.Edit.Timing protected override void OnControlPointChanged(ValueChangedEvent point) { - if (point.NewValue != null) + scrollSpeedSlider.Current.ValueChanged -= updateControlPointFromSlider; + + if (point.NewValue is EffectControlPoint newEffectPoint) { isRebinding = true; - kiai.Current = point.NewValue.KiaiModeBindable; - scrollSpeedSlider.Current = point.NewValue.ScrollSpeedBindable; + kiai.Current = newEffectPoint.KiaiModeBindable; + scrollSpeedSlider.Current = new BindableDouble + { + MinValue = 0.01, + MaxValue = 10, + Precision = 0.01, + Value = newEffectPoint.ScrollSpeedBindable.Value + }; + scrollSpeedSlider.Current.ValueChanged += updateControlPointFromSlider; + // at this point in time the above is enough to keep the slider control in sync with reality, + // since undo/redo causes `OnControlPointChanged()` to fire. + // whenever that stops being the case, or there is a possibility that the scroll speed could be changed + // by something else other than this control, this code should probably be revisited to have a binding in the other direction, too. isRebinding = false; } } + private void updateControlPointFromSlider(ValueChangedEvent scrollSpeed) + { + if (ControlPoint.Value is not EffectControlPoint effectPoint || isRebinding) + return; + + effectPoint.ScrollSpeedBindable.Value = scrollSpeed.NewValue; + } + protected override EffectControlPoint CreatePoint() { var reference = Beatmap.ControlPointInfo.EffectPointAt(SelectedGroup.Value.Time); From 034f8c0388c84333961d5ad6702c044dc83e519f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jan 2024 20:36:59 +0900 Subject: [PATCH 2789/4092] Also fix spinner case --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index bf4b07eaab..9a98286738 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -153,7 +153,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { if (tracking.NewValue) { - if (!spinningSample.IsPlaying) + if (!spinningSample.RequestedPlaying) spinningSample.Play(); spinningSample.VolumeTo(1, 300); From f2546d72c2616f4d3d746d27b9fea061998591d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jan 2024 20:45:51 +0900 Subject: [PATCH 2790/4092] Fix osu! logo being mispositioned in song select on very wide resolutions --- osu.Game/Screens/Select/SongSelect.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index bf1724995a..50467bc089 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -642,7 +642,11 @@ namespace osu.Game.Screens.Select { base.LogoArriving(logo, resuming); - Vector2 position = new Vector2(0.95f, 0.96f); + logo.Anchor = Anchor.BottomRight; + logo.Origin = Anchor.Centre; + logo.RelativePositionAxes = Axes.None; + + Vector2 position = new Vector2(-76, -36); if (logo.Alpha > 0.8f) { From 16c06169ed077bc493e1856094b76528de1665d6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jan 2024 01:06:32 +0900 Subject: [PATCH 2791/4092] 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 da404599ef..55ef55ab7d 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 0855c0c6d6..5b99319499 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From 996ae0ecc1f970ebc60f17653b3cf6b698794dbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 29 Jan 2024 19:17:39 +0100 Subject: [PATCH 2792/4092] Fix incorrect handling of `StartChatRequest` in websocket chat client Fixes this happening on staging: [network] 2024-01-29 17:48:24 [verbose]: WebSocketNotificationsClientConnector connected! [network] 2024-01-29 17:48:24 [verbose]: WebSocketNotificationsClientConnector connect attempt failed: Can't use WaitSafely from inside an async operation. I'm not sure how I ever allowed that `.WaitSafely()` to be there. It did feel rather dangerous but then I must have forgotten and never noticed it failing. Which is weird because you'd think that would be caught by testing that chat isn't working but I'm pretty sure that I tested that chat *was* indeed working. Anyway now that entire flow is replaced by something that should hopefully be somewhat more sane? It has a whole retry flow with logging now which should be more robust than what was there previously (failing to start to listen to chat events killing the entire websocket connection for very little good reason). --- osu.Game/Online/Chat/WebSocketChatClient.cs | 47 +++++++++++++++++---- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/osu.Game/Online/Chat/WebSocketChatClient.cs b/osu.Game/Online/Chat/WebSocketChatClient.cs index 05d3b7b3ce..b74f8bec4b 100644 --- a/osu.Game/Online/Chat/WebSocketChatClient.cs +++ b/osu.Game/Online/Chat/WebSocketChatClient.cs @@ -5,9 +5,10 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; using Newtonsoft.Json; using osu.Framework.Bindables; -using osu.Framework.Extensions; using osu.Framework.Logging; using osu.Game.Online.API; using osu.Game.Online.API.Requests; @@ -26,21 +27,49 @@ namespace osu.Game.Online.Chat private readonly INotificationsClient client; private readonly ConcurrentDictionary channelsMap = new ConcurrentDictionary(); + private CancellationTokenSource? chatStartCancellationSource; + public WebSocketChatClient(IAPIProvider api) { this.api = api; client = api.NotificationsClient; - client.IsConnected.BindValueChanged(start, true); + client.IsConnected.BindValueChanged(onConnectedChanged, true); } - private void start(ValueChangedEvent connected) + private void onConnectedChanged(ValueChangedEvent connected) { - if (!connected.NewValue) - return; + if (connected.NewValue) + { + client.MessageReceived += onMessageReceived; + attemptToStartChat(); + RequestPresence(); + } + else + chatStartCancellationSource?.Cancel(); + } - client.MessageReceived += onMessageReceived; - client.SendAsync(new StartChatRequest()).WaitSafely(); - RequestPresence(); + private void attemptToStartChat() + { + chatStartCancellationSource?.Cancel(); + chatStartCancellationSource = new CancellationTokenSource(); + + Task.Factory.StartNew(async () => + { + while (!chatStartCancellationSource.IsCancellationRequested) + { + try + { + await client.SendAsync(new StartChatRequest()).ConfigureAwait(false); + Logger.Log(@"Now listening to websocket chat messages.", LoggingTarget.Network); + chatStartCancellationSource.Cancel(); + } + catch (Exception ex) + { + Logger.Log($@"Could not start listening to websocket chat messages: {ex}", LoggingTarget.Network); + await Task.Delay(5000).ConfigureAwait(false); + } + } + }, chatStartCancellationSource.Token); } public void RequestPresence() @@ -137,7 +166,7 @@ namespace osu.Game.Online.Chat public void Dispose() { - client.IsConnected.ValueChanged -= start; + client.IsConnected.ValueChanged -= onConnectedChanged; client.MessageReceived -= onMessageReceived; } } From 959cc7c7d91fa288bb98777b73286bc30285a21f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 29 Jan 2024 21:26:36 +0300 Subject: [PATCH 2793/4092] Rewrite time range computation logic to be completely based on stable code --- .../UI/DrawableTaikoRuleset.cs | 20 ++-------- .../UI/TaikoPlayfieldAdjustmentContainer.cs | 40 +++++++++++++++++-- 2 files changed, 39 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 49b0ad811d..77b2b06c0e 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -16,7 +16,6 @@ using osu.Game.Replays; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Taiko.Beatmaps; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Replays; using osu.Game.Rulesets.Timing; @@ -36,6 +35,8 @@ namespace osu.Game.Rulesets.Taiko.UI public new TaikoInputManager KeyBindingInputManager => (TaikoInputManager)base.KeyBindingInputManager; + protected new TaikoPlayfieldAdjustmentContainer PlayfieldAdjustmentContainer => (TaikoPlayfieldAdjustmentContainer)base.PlayfieldAdjustmentContainer; + protected override bool UserScrollSpeedAdjustment => false; private SkinnableDrawable scroller; @@ -68,22 +69,7 @@ namespace osu.Game.Rulesets.Taiko.UI TimeRange.Value = ComputeTimeRange(); } - protected virtual double ComputeTimeRange() - { - // Taiko scrolls at a constant 100px per 1000ms. More notes become visible as the playfield is lengthened. - const float scroll_rate = 10; - - // Since the time range will depend on a positional value, it is referenced to the x480 pixel space. - // Width is used because it defines how many notes fit on the playfield. - // We clamp the ratio to the maximum aspect ratio to keep scroll speed consistent on widths lower than the default. - float ratio = Math.Max(DrawSize.X / 768f, TaikoPlayfieldAdjustmentContainer.MAXIMUM_ASPECT); - - // Stable internally increased the slider velocity of objects by a factor of `VELOCITY_MULTIPLIER`. - // To simulate this, we shrink the time range by that factor here. - // This, when combined with the rest of the scrolling ruleset machinery (see `MultiplierControlPoint` et al.), - // has the effect of increasing each multiplier control point's multiplier by `VELOCITY_MULTIPLIER`, ensuring parity with stable. - return (Playfield.HitObjectContainer.DrawWidth / ratio) * scroll_rate / TaikoBeatmapConverter.VELOCITY_MULTIPLIER; - } + protected virtual double ComputeTimeRange() => PlayfieldAdjustmentContainer.ComputeTimeRange(); protected override void UpdateAfterChildren() { diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs index c10e505f50..c67f61052c 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Game.Rulesets.Taiko.Beatmaps; using osu.Game.Rulesets.UI; using osuTK; @@ -14,6 +15,8 @@ namespace osu.Game.Rulesets.Taiko.UI public const float MAXIMUM_ASPECT = 16f / 9f; public const float MINIMUM_ASPECT = 5f / 4f; + private const float stable_gamefield_height = 480f; + public readonly IBindable LockPlayfieldAspectRange = new BindableBool(true); public TaikoPlayfieldAdjustmentContainer() @@ -21,6 +24,9 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.X; RelativePositionAxes = Axes.Y; Height = TaikoPlayfield.BASE_HEIGHT; + + // Matches stable, see https://github.com/peppy/osu-stable-reference/blob/7519cafd1823f1879c0d9c991ba0e5c7fd3bfa02/osu!/GameModes/Play/Rulesets/Taiko/RulesetTaiko.cs#L514 + Y = 135f / stable_gamefield_height; } protected override void Update() @@ -28,8 +34,6 @@ namespace osu.Game.Rulesets.Taiko.UI base.Update(); const float base_relative_height = TaikoPlayfield.BASE_HEIGHT / 768; - // Matches stable, see https://github.com/peppy/osu-stable-reference/blob/7519cafd1823f1879c0d9c991ba0e5c7fd3bfa02/osu!/GameModes/Play/Rulesets/Taiko/RulesetTaiko.cs#L514 - const float base_position = 135f / 480f; float relativeHeight = base_relative_height; @@ -51,10 +55,38 @@ namespace osu.Game.Rulesets.Taiko.UI // Limit the maximum relative height of the playfield to one-third of available area to avoid it masking out on extreme resolutions. relativeHeight = Math.Min(relativeHeight, 1f / 3f); - Y = base_position; - Scale = new Vector2(Math.Max((Parent!.ChildSize.Y / 768f) * (relativeHeight / base_relative_height), 1f)); Width = 1 / Scale.X; } + + public double ComputeTimeRange() + { + float currentAspect = Parent!.ChildSize.X / Parent!.ChildSize.Y; + + if (LockPlayfieldAspectRange.Value) + currentAspect = Math.Clamp(currentAspect, MINIMUM_ASPECT, MAXIMUM_ASPECT); + + // in a game resolution of 1024x768, stable's scrolling system consists of objects being placed 600px (widthScaled - 40) away from their hit location. + // however, the point at which the object renders at the end of the screen is exactly x=640, but stable makes the object start moving from beyond the screen instead of the boundary point. + // therefore, in lazer we have to adjust the "in length" so that it's in a 640px->160px fashion before passing it down as a "time range". + // see stable's "in length": https://github.com/peppy/osu-stable-reference/blob/013c3010a9d495e3471a9c59518de17006f9ad89/osu!/GameplayElements/HitObjectManagerTaiko.cs#L168 + const float stable_hit_location = 160f; + float widthScaled = currentAspect * stable_gamefield_height; + float inLength = widthScaled - stable_hit_location; + + // also in a game resolution of 1024x768, stable makes hit objects scroll from 760px->160px at a duration of 6000ms, divided by slider velocity (i.e. at a rate of 0.1px/ms) + // compare: https://github.com/peppy/osu-stable-reference/blob/013c3010a9d495e3471a9c59518de17006f9ad89/osu!/GameplayElements/HitObjectManagerTaiko.cs#L218 + // note: the variable "sv", in the linked reference, is equivalent to MultiplierControlPoint.Multiplier * 100, but since time range is agnostic of velocity, we replace "sv" with 100 below. + float inMsLength = inLength / 100 * 1000; + + // stable multiplies the slider velocity by 1.4x for certain reasons, divide the time range by that factor to achieve similar result. + // for references on how the factor is applied to the time range, see: + // 1. https://github.com/peppy/osu-stable-reference/blob/013c3010a9d495e3471a9c59518de17006f9ad89/osu!/GameplayElements/HitObjectManagerTaiko.cs#L79 (DifficultySliderMultiplier multiplied by 1.4x) + // 2. https://github.com/peppy/osu-stable-reference/blob/013c3010a9d495e3471a9c59518de17006f9ad89/osu!/GameplayElements/HitObjectManager.cs#L468-L470 (DifficultySliderMultiplier used to calculate SliderScoringPointDistance) + // 3. https://github.com/peppy/osu-stable-reference/blob/013c3010a9d495e3471a9c59518de17006f9ad89/osu!/GameplayElements/HitObjectManager.cs#L248-L250 (SliderScoringPointDistance used to calculate slider velocity, i.e. the "sv" variable from above) + inMsLength /= TaikoBeatmapConverter.VELOCITY_MULTIPLIER; + + return inMsLength; + } } } From acc2614090739ce546bead00deef3ddcfbbeadf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 29 Jan 2024 20:04:18 +0100 Subject: [PATCH 2794/4092] Apply alternative solution Fixes the logo flying out of the wrong corner when transitioning from song select to gameplay. Co-authored-by: Dean Herbert --- osu.Game/Screens/Menu/OsuLogo.cs | 7 +++++++ osu.Game/Screens/OsuScreen.cs | 5 ++++- osu.Game/Screens/Select/SongSelect.cs | 3 +-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 25101730e7..f2e2e25fa6 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -486,5 +486,12 @@ namespace osu.Game.Screens.Menu defaultProxyTarget.Add(this); defaultProxyTarget.Add(proxy = CreateProxy()); } + + public void ChangeAnchor(Anchor anchor) + { + var previousAnchor = AnchorPosition; + Anchor = anchor; + Position -= AnchorPosition - previousAnchor; + } } } diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index f719ef67c9..2e8f85423d 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -250,9 +250,12 @@ namespace osu.Game.Screens { logo.Action = null; logo.FadeOut(300, Easing.OutQuint); - logo.Anchor = Anchor.TopLeft; + logo.Origin = Anchor.Centre; + + logo.ChangeAnchor(Anchor.TopLeft); logo.RelativePositionAxes = Axes.Both; + logo.Triangles = true; logo.Ripple = true; } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 50467bc089..a603934a9d 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -642,9 +642,8 @@ namespace osu.Game.Screens.Select { base.LogoArriving(logo, resuming); - logo.Anchor = Anchor.BottomRight; - logo.Origin = Anchor.Centre; logo.RelativePositionAxes = Axes.None; + logo.ChangeAnchor(Anchor.BottomRight); Vector2 position = new Vector2(-76, -36); From 6888cda02cd127ff4baf6159d3c6a7e533184970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 29 Jan 2024 21:42:38 +0100 Subject: [PATCH 2795/4092] Make `LegacyScoreDecoder.PopulateMaximumStatistics()` public For `osu-tools` consumption. --- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index e51a95798b..65e2c02655 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -180,7 +180,7 @@ namespace osu.Game.Scoring.Legacy /// /// The score to populate the statistics of. /// The corresponding . - internal static void PopulateMaximumStatistics(ScoreInfo score, WorkingBeatmap workingBeatmap) + public static void PopulateMaximumStatistics(ScoreInfo score, WorkingBeatmap workingBeatmap) { Debug.Assert(score.BeatmapInfo != null); From 87f853fcd270c9fce2f3f5a7368bfb506d060a23 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 30 Jan 2024 00:23:32 +0300 Subject: [PATCH 2796/4092] Reduce overhead in ScrollingHitObjectContainer --- .../Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 129918da14..39ddb5c753 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -184,9 +184,12 @@ namespace osu.Game.Rulesets.UI.Scrolling // We need to calculate hit object positions (including nested hit objects) as soon as possible after lifetimes // to prevent hit objects displayed in a wrong position for one frame. - // Only AliveObjects need to be considered for layout (reduces overhead in the case of scroll speed changes). - foreach (var obj in AliveObjects) + // Only AliveEntries need to be considered for layout (reduces overhead in the case of scroll speed changes). + // We are not using AliveObjects directly to avoid selection/sorting overhead since we don't care about the order at which positions will be updated. + foreach (var entry in AliveEntries) { + var obj = entry.Drawable; + updatePosition(obj, Time.Current); if (layoutComputed.Contains(obj)) From 7dba870518857bb832dbf7b4a034471a3ed45f94 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 30 Jan 2024 03:07:37 +0300 Subject: [PATCH 2797/4092] Rework Content storage in ColumnFlow --- osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs | 4 ++-- osu.Game.Rulesets.Mania/UI/ColumnFlow.cs | 16 +++++++++------- osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs | 10 +++++----- osu.Game.Rulesets.Mania/UI/Stage.cs | 11 +++++------ 4 files changed, 21 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs index fee3ba3e39..db04142915 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs @@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Mania.Tests { foreach (var stage in stages) { - for (int i = 0; i < stage.Columns.Count; i++) + for (int i = 0; i < stage.Columns.Length; i++) { var obj = new Note { Column = i, StartTime = Time.Current + 2000 }; obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Mania.Tests { foreach (var stage in stages) { - for (int i = 0; i < stage.Columns.Count; i++) + for (int i = 0; i < stage.Columns.Length; i++) { var obj = new HoldNote { Column = i, StartTime = Time.Current + 2000, Duration = 500 }; obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); diff --git a/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs b/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs index 8734f8ac8a..1593e8e76f 100644 --- a/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs +++ b/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs @@ -4,8 +4,6 @@ #nullable disable using System; -using System.Collections.Generic; -using System.Linq; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -28,20 +26,21 @@ namespace osu.Game.Rulesets.Mania.UI /// /// All contents added to this . /// - public IReadOnlyList Content => columns.Children.Select(c => c.Count == 0 ? null : (TContent)c.Child).ToList(); + public TContent[] Content { get; } - private readonly FillFlowContainer columns; + private readonly FillFlowContainer> columns; private readonly StageDefinition stageDefinition; public ColumnFlow(StageDefinition stageDefinition) { this.stageDefinition = stageDefinition; + Content = new TContent[stageDefinition.Columns]; AutoSizeAxes = Axes.X; Masking = true; - InternalChild = columns = new FillFlowContainer + InternalChild = columns = new FillFlowContainer> { RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, @@ -49,7 +48,7 @@ namespace osu.Game.Rulesets.Mania.UI }; for (int i = 0; i < stageDefinition.Columns; i++) - columns.Add(new Container { RelativeSizeAxes = Axes.Y }); + columns.Add(new Container { RelativeSizeAxes = Axes.Y }); } private ISkinSource currentSkin; @@ -102,7 +101,10 @@ namespace osu.Game.Rulesets.Mania.UI /// /// The index of the column to set the content of. /// The content. - public void SetContentForColumn(int column, TContent content) => columns[column].Child = content; + public void SetContentForColumn(int column, TContent content) + { + Content[column] = columns[column].Child = content; + } private void updateMobileSizing() { diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 314d199944..c8874c8ab3 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Mania.UI stages.Add(newStage); AddNested(newStage); - firstColumnIndex += newStage.Columns.Count; + firstColumnIndex += newStage.Columns.Length; } } @@ -125,9 +125,9 @@ namespace osu.Game.Rulesets.Mania.UI foreach (var stage in stages) { - if (index >= stage.Columns.Count) + if (index >= stage.Columns.Length) { - index -= stage.Columns.Count; + index -= stage.Columns.Length; continue; } @@ -140,7 +140,7 @@ namespace osu.Game.Rulesets.Mania.UI /// /// Retrieves the total amount of columns across all stages in this playfield. /// - public int TotalColumns => stages.Sum(s => s.Columns.Count); + public int TotalColumns => stages.Sum(s => s.Columns.Length); private Stage getStageByColumn(int column) { @@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Mania.UI foreach (var stage in stages) { - sum += stage.Columns.Count; + sum += stage.Columns.Length; if (sum > column) return stage; } diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs index 36286940a8..db06f9dbde 100644 --- a/osu.Game.Rulesets.Mania/UI/Stage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.ObjectExtensions; @@ -37,7 +36,7 @@ namespace osu.Game.Rulesets.Mania.UI public const float HIT_TARGET_POSITION = 110; - public IReadOnlyList Columns => columnFlow.Content; + public Column[] Columns => columnFlow.Content; private readonly ColumnFlow columnFlow; private readonly JudgementContainer judgements; @@ -184,13 +183,13 @@ namespace osu.Game.Rulesets.Mania.UI NewResult += OnNewResult; } - public override void Add(HitObject hitObject) => Columns.ElementAt(((ManiaHitObject)hitObject).Column - firstColumnIndex).Add(hitObject); + public override void Add(HitObject hitObject) => Columns[((ManiaHitObject)hitObject).Column - firstColumnIndex].Add(hitObject); - public override bool Remove(HitObject hitObject) => Columns.ElementAt(((ManiaHitObject)hitObject).Column - firstColumnIndex).Remove(hitObject); + public override bool Remove(HitObject hitObject) => Columns[((ManiaHitObject)hitObject).Column - firstColumnIndex].Remove(hitObject); - public override void Add(DrawableHitObject h) => Columns.ElementAt(((ManiaHitObject)h.HitObject).Column - firstColumnIndex).Add(h); + public override void Add(DrawableHitObject h) => Columns[((ManiaHitObject)h.HitObject).Column - firstColumnIndex].Add(h); - public override bool Remove(DrawableHitObject h) => Columns.ElementAt(((ManiaHitObject)h.HitObject).Column - firstColumnIndex).Remove(h); + public override bool Remove(DrawableHitObject h) => Columns[((ManiaHitObject)h.HitObject).Column - firstColumnIndex].Remove(h); public void Add(BarLine barLine) => base.Add(barLine); From 8e20eed4ef2783234e54101fd0fa8bd92d5e11fe Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 30 Jan 2024 03:19:27 +0300 Subject: [PATCH 2798/4092] Don't use LINQ in ReceivePositionalInputAt --- osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs | 11 ++++++++++- osu.Game.Rulesets.Mania/UI/Stage.cs | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index c8874c8ab3..0d36f51943 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -42,7 +42,16 @@ namespace osu.Game.Rulesets.Mania.UI } } - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => stages.Any(s => s.ReceivePositionalInputAt(screenSpacePos)); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) + { + foreach (var s in stages) + { + if (s.ReceivePositionalInputAt(screenSpacePos)) + return true; + } + + return false; + } public ManiaPlayfield(List stageDefinitions) { diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs index db06f9dbde..a4a09c9a82 100644 --- a/osu.Game.Rulesets.Mania/UI/Stage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -44,7 +44,16 @@ namespace osu.Game.Rulesets.Mania.UI private readonly Drawable barLineContainer; - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Columns.Any(c => c.ReceivePositionalInputAt(screenSpacePos)); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) + { + foreach (var c in Columns) + { + if (c.ReceivePositionalInputAt(screenSpacePos)) + return true; + } + + return false; + } private readonly int firstColumnIndex; From d895a91cd535328e327752d98576e5d5db9b20ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jan 2024 18:40:17 +0900 Subject: [PATCH 2799/4092] Update endpoints to final production endpoints --- osu.Game/Online/EndpointConfiguration.cs | 16 +++++++--------- .../ExperimentalEndpointConfiguration.cs | 19 ------------------- osu.Game/OsuGameBase.cs | 2 +- 3 files changed, 8 insertions(+), 29 deletions(-) delete mode 100644 osu.Game/Online/ExperimentalEndpointConfiguration.cs diff --git a/osu.Game/Online/EndpointConfiguration.cs b/osu.Game/Online/EndpointConfiguration.cs index f3bcced630..bd3c945124 100644 --- a/osu.Game/Online/EndpointConfiguration.cs +++ b/osu.Game/Online/EndpointConfiguration.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 - namespace osu.Game.Online { /// @@ -13,36 +11,36 @@ namespace osu.Game.Online /// /// The base URL for the website. /// - public string WebsiteRootUrl { get; set; } + public string WebsiteRootUrl { get; set; } = string.Empty; /// /// The endpoint for the main (osu-web) API. /// - public string APIEndpointUrl { get; set; } + public string APIEndpointUrl { get; set; } = string.Empty; /// /// The OAuth client secret. /// - public string APIClientSecret { get; set; } + public string APIClientSecret { get; set; } = string.Empty; /// /// The OAuth client ID. /// - public string APIClientID { get; set; } + public string APIClientID { get; set; } = string.Empty; /// /// The endpoint for the SignalR spectator server. /// - public string SpectatorEndpointUrl { get; set; } + public string SpectatorEndpointUrl { get; set; } = string.Empty; /// /// The endpoint for the SignalR multiplayer server. /// - public string MultiplayerEndpointUrl { get; set; } + public string MultiplayerEndpointUrl { get; set; } = string.Empty; /// /// The endpoint for the SignalR metadata server. /// - public string MetadataEndpointUrl { get; set; } + public string MetadataEndpointUrl { get; set; } = string.Empty; } } diff --git a/osu.Game/Online/ExperimentalEndpointConfiguration.cs b/osu.Game/Online/ExperimentalEndpointConfiguration.cs deleted file mode 100644 index c3d0014c8b..0000000000 --- a/osu.Game/Online/ExperimentalEndpointConfiguration.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. - -namespace osu.Game.Online -{ - public class ExperimentalEndpointConfiguration : EndpointConfiguration - { - public ExperimentalEndpointConfiguration() - { - WebsiteRootUrl = @"https://osu.ppy.sh"; - APIEndpointUrl = @"https://lazer.ppy.sh"; - APIClientSecret = @"FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk"; - APIClientID = "5"; - SpectatorEndpointUrl = "https://spectator.ppy.sh/spectator"; - MultiplayerEndpointUrl = "https://spectator.ppy.sh/multiplayer"; - MetadataEndpointUrl = "https://spectator.ppy.sh/metadata"; - } - } -} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 2208f7d7ca..a2a6322665 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -102,7 +102,7 @@ namespace osu.Game public virtual bool UseDevelopmentServer => DebugUtils.IsDebugBuild; public virtual EndpointConfiguration CreateEndpoints() => - UseDevelopmentServer ? new DevelopmentEndpointConfiguration() : new ExperimentalEndpointConfiguration(); + UseDevelopmentServer ? new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration(); public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version(); From 6931af664ed3c9a1550a9e2751cfe436b1dc542d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 30 Jan 2024 12:05:31 +0100 Subject: [PATCH 2800/4092] Add missing `icon.png` entry to nuspec This is only half of the deployment fix, the other half will be in `osu-deploy` (making sure the icon is actually in the staging directory). --- osu.Desktop/osu.nuspec | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Desktop/osu.nuspec b/osu.Desktop/osu.nuspec index 3b7d6cbe79..f85698680e 100644 --- a/osu.Desktop/osu.nuspec +++ b/osu.Desktop/osu.nuspec @@ -20,5 +20,6 @@ + From 000ddc14acadf104af4724731c0aa41e27952ab0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 30 Jan 2024 20:50:00 +0100 Subject: [PATCH 2801/4092] Fix broken locking in `OAuth` Closes https://github.com/ppy/osu/issues/26824... I think? Can be reproduced via something like diff --git a/osu.Game/Online/API/OAuth.cs b/osu.Game/Online/API/OAuth.cs index 485274f349..e6e93ab4c7 100644 --- a/osu.Game/Online/API/OAuth.cs +++ b/osu.Game/Online/API/OAuth.cs @@ -151,6 +151,11 @@ internal string RequestAccessToken() { if (!ensureAccessToken()) return null; + for (int i = 0; i < 10000; ++i) + { + _ = Token.Value.AccessToken; + } + return Token.Value.AccessToken; } The cause is `SecondFactorAuthForm` calling `Logout()`, which calls `OAuth.Clear()`, _while_ the `APIAccess` connect loop is checking if `authentication.HasValidAccessToken` is true, which happens to internally check `Token.Value.AccessToken`, which the clearing of tokens can brutally interrupt. --- osu.Game/Online/API/OAuth.cs | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/osu.Game/Online/API/OAuth.cs b/osu.Game/Online/API/OAuth.cs index 485274f349..4829310870 100644 --- a/osu.Game/Online/API/OAuth.cs +++ b/osu.Game/Online/API/OAuth.cs @@ -128,19 +128,12 @@ namespace osu.Game.Online.API // if we already have a valid access token, let's use it. if (accessTokenValid) return true; - // we want to ensure only a single authentication update is happening at once. - lock (access_token_retrieval_lock) - { - // re-check if valid, in case another request completed and revalidated our access. - if (accessTokenValid) return true; + // if not, let's try using our refresh token to request a new access token. + if (!string.IsNullOrEmpty(Token.Value?.RefreshToken)) + // ReSharper disable once PossibleNullReferenceException + AuthenticateWithRefresh(Token.Value.RefreshToken); - // if not, let's try using our refresh token to request a new access token. - if (!string.IsNullOrEmpty(Token.Value?.RefreshToken)) - // ReSharper disable once PossibleNullReferenceException - AuthenticateWithRefresh(Token.Value.RefreshToken); - - return accessTokenValid; - } + return accessTokenValid; } private bool accessTokenValid => Token.Value?.IsValid ?? false; @@ -149,14 +142,18 @@ namespace osu.Game.Online.API internal string RequestAccessToken() { - if (!ensureAccessToken()) return null; + lock (access_token_retrieval_lock) + { + if (!ensureAccessToken()) return null; - return Token.Value.AccessToken; + return Token.Value.AccessToken; + } } internal void Clear() { - Token.Value = null; + lock (access_token_retrieval_lock) + Token.Value = null; } private class AccessTokenRequestRefresh : AccessTokenRequest From 4126dcbe281c2f6590b999af14e27a09748bbde6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 30 Jan 2024 21:40:23 +0100 Subject: [PATCH 2802/4092] Fix 2FA verification via link not working correctly Closes https://github.com/ppy/osu/issues/26835. I must have not re-tested this correctly after all the refactors... Basically the issue is that the websocket connection would only come online when the API state changed to full `Online`. In particular the connector would not attempt to connect when the API state was `RequiresSecondFactorAuth`, giving the link-based flow no chance to actually work. The change in `WebSocketNotificationsClientConnector` is relevant in that queueing requests does nothing before the API state changes to full `Online`. It also cleans up things a bit code-wise so... win? And yes, this means that the _other_ `PersistentEndpointClientConnector` implementations (i.e. SignalR connectors) will also come online earlier after this. Based on previous discussions (https://github.com/ppy/osu/pull/25480#discussion_r1395566545) I think this is fine, but if it is _not_ fine, then it can be fixed by exposing a virtual that lets a connector to decide when to come alive, I guess. --- .../WebSocketNotificationsClientConnector.cs | 11 ++++------- osu.Game/Online/PersistentEndpointClientConnector.cs | 3 ++- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game/Online/Notifications/WebSocket/WebSocketNotificationsClientConnector.cs b/osu.Game/Online/Notifications/WebSocket/WebSocketNotificationsClientConnector.cs index 73fe29d441..596322d377 100644 --- a/osu.Game/Online/Notifications/WebSocket/WebSocketNotificationsClientConnector.cs +++ b/osu.Game/Online/Notifications/WebSocket/WebSocketNotificationsClientConnector.cs @@ -29,14 +29,11 @@ namespace osu.Game.Online.Notifications.WebSocket protected override async Task BuildConnectionAsync(CancellationToken cancellationToken) { - var tcs = new TaskCompletionSource(); - var req = new GetNotificationsRequest(); - req.Success += bundle => tcs.SetResult(bundle.Endpoint); - req.Failure += ex => tcs.SetException(ex); - api.Queue(req); - - string endpoint = await tcs.Task.ConfigureAwait(false); + // must use `PerformAsync()`, since we may not be fully online yet + // (see `APIState.RequiresSecondFactorAuth` - in this state queued requests will not execute). + await api.PerformAsync(req).ConfigureAwait(false); + string endpoint = req.Response!.Endpoint; ClientWebSocket socket = new ClientWebSocket(); socket.Options.SetRequestHeader(@"Authorization", @$"Bearer {api.AccessToken}"); diff --git a/osu.Game/Online/PersistentEndpointClientConnector.cs b/osu.Game/Online/PersistentEndpointClientConnector.cs index 8c1b58a750..024a0fea73 100644 --- a/osu.Game/Online/PersistentEndpointClientConnector.cs +++ b/osu.Game/Online/PersistentEndpointClientConnector.cs @@ -69,6 +69,7 @@ namespace osu.Game.Online break; case APIState.Online: + case APIState.RequiresSecondFactorAuth: await connect().ConfigureAwait(true); break; } @@ -83,7 +84,7 @@ namespace osu.Game.Online try { - while (apiState.Value == APIState.Online) + while (apiState.Value == APIState.RequiresSecondFactorAuth || apiState.Value == APIState.Online) { // ensure any previous connection was disposed. // this will also create a new cancellation token source. From c5e118bd1011a3a2f9b5f1affa832dc2e0735c4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 30 Jan 2024 22:15:35 +0100 Subject: [PATCH 2803/4092] Revert "Move fade more local to avoid fading twice" This reverts commit d0421fe20667530bf1bca1a5c8e3f387dde0cf6a and fixes https://github.com/ppy/osu/issues/26801. https://github.com/ppy/osu/pull/26703#discussion_r1469409667 was correct in saying that the early fade-out needs to be restored, and that's because of the early-return. Legacy judgements that are the temporary displayed judgement from new piece should also receive the fade-out, and d0421fe20667530bf1bca1a5c8e3f387dde0cf6a broke that. --- osu.Game/Skinning/LegacyJudgementPieceOld.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Skinning/LegacyJudgementPieceOld.cs b/osu.Game/Skinning/LegacyJudgementPieceOld.cs index edfb5a23ec..068707ba4f 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceOld.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceOld.cs @@ -46,6 +46,7 @@ namespace osu.Game.Skinning const double fade_out_length = 600; this.FadeInFromZero(fade_in_length); + this.Delay(fade_out_delay).FadeOut(fade_out_length); // legacy judgements don't play any transforms if they are an animation.... UNLESS they are the temporary displayed judgement from new piece. if (animation?.FrameCount > 1 && !forceTransforms) @@ -79,8 +80,6 @@ namespace osu.Game.Skinning this.RotateTo(0); this.RotateTo(rotation, fade_in_length) .Then().RotateTo(rotation * 2, fade_out_delay + fade_out_length - fade_in_length, Easing.In); - - this.Delay(fade_out_delay).FadeOut(fade_out_length); } } else @@ -94,8 +93,6 @@ namespace osu.Game.Skinning // so we need to force the current value to be correct at 1.2 (0.95) then complete the // second half of the transform. .ScaleTo(0.95f).ScaleTo(finalScale, fade_in_length * 0.2f); // t = 1.4 - - this.Delay(fade_out_delay).FadeOut(fade_out_length); } } From 3aefc919675a855f6d13385d4d69ed326db2bc4f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 31 Jan 2024 07:54:07 +0300 Subject: [PATCH 2804/4092] Make AliveDrawableMap public --- .../PooledDrawableWithLifetimeContainer.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs b/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs index 1b0176cae5..1ebdf48ae8 100644 --- a/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Objects.Pooling /// /// The enumeration order is undefined. /// - public IEnumerable<(TEntry Entry, TDrawable Drawable)> AliveEntries => aliveDrawableMap.Select(x => (x.Key, x.Value)); + public IEnumerable<(TEntry Entry, TDrawable Drawable)> AliveEntries => AliveDrawableMap.Select(x => (x.Key, x.Value)); /// /// Whether to remove an entry when clock goes backward and crossed its . @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Objects.Pooling /// internal double FutureLifetimeExtension { get; set; } - private readonly Dictionary aliveDrawableMap = new Dictionary(); + public readonly Dictionary AliveDrawableMap = new Dictionary(); private readonly HashSet allEntries = new HashSet(); private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); @@ -101,10 +101,10 @@ namespace osu.Game.Rulesets.Objects.Pooling private void entryBecameAlive(LifetimeEntry lifetimeEntry) { var entry = (TEntry)lifetimeEntry; - Debug.Assert(!aliveDrawableMap.ContainsKey(entry)); + Debug.Assert(!AliveDrawableMap.ContainsKey(entry)); TDrawable drawable = GetDrawable(entry); - aliveDrawableMap[entry] = drawable; + AliveDrawableMap[entry] = drawable; AddDrawable(entry, drawable); } @@ -119,10 +119,10 @@ namespace osu.Game.Rulesets.Objects.Pooling private void entryBecameDead(LifetimeEntry lifetimeEntry) { var entry = (TEntry)lifetimeEntry; - Debug.Assert(aliveDrawableMap.ContainsKey(entry)); + Debug.Assert(AliveDrawableMap.ContainsKey(entry)); - TDrawable drawable = aliveDrawableMap[entry]; - aliveDrawableMap.Remove(entry); + TDrawable drawable = AliveDrawableMap[entry]; + AliveDrawableMap.Remove(entry); RemoveDrawable(entry, drawable); } @@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Objects.Pooling foreach (var entry in Entries.ToArray()) Remove(entry); - Debug.Assert(aliveDrawableMap.Count == 0); + Debug.Assert(AliveDrawableMap.Count == 0); } protected override bool CheckChildrenLife() From 6b1de5446ad5dfd21a481ee8f484b92b42851be5 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 31 Jan 2024 07:54:28 +0300 Subject: [PATCH 2805/4092] Reduce allocaations in ScrollingHitObjectContainer --- osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 39ddb5c753..23ac4378e4 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -186,9 +186,9 @@ namespace osu.Game.Rulesets.UI.Scrolling // to prevent hit objects displayed in a wrong position for one frame. // Only AliveEntries need to be considered for layout (reduces overhead in the case of scroll speed changes). // We are not using AliveObjects directly to avoid selection/sorting overhead since we don't care about the order at which positions will be updated. - foreach (var entry in AliveEntries) + foreach (var entry in AliveDrawableMap) { - var obj = entry.Drawable; + var obj = entry.Value; updatePosition(obj, Time.Current); From 9b1bbe5f48eaaf6854c6055d45d6c4601ff3226e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 31 Jan 2024 15:54:43 +0900 Subject: [PATCH 2806/4092] Adjust default min result of SliderTailHit, remove override --- osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs | 1 - osu.Game/Rulesets/Judgements/Judgement.cs | 4 +++- osu.Game/Rulesets/Scoring/HitResult.cs | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index ceee513412..ee2490439f 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -30,7 +30,6 @@ namespace osu.Game.Rulesets.Osu.Objects public class TailJudgement : SliderEndJudgement { public override HitResult MaxResult => HitResult.SliderTailHit; - public override HitResult MinResult => HitResult.IgnoreMiss; } } } diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index 93386de483..d4d06167f1 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -73,9 +73,11 @@ namespace osu.Game.Rulesets.Judgements return HitResult.SmallTickMiss; case HitResult.LargeTickHit: - case HitResult.SliderTailHit: return HitResult.LargeTickMiss; + case HitResult.SliderTailHit: + return HitResult.IgnoreMiss; + default: return HitResult.Miss; } diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index 20ec3c4946..b6cfca58db 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -138,7 +138,8 @@ namespace osu.Game.Rulesets.Scoring ComboBreak, /// - /// A special judgement similar to that's used to increase the valuation of the final tick of a slider. + /// A special tick judgement to increase the valuation of the final tick of a slider. + /// The default minimum result is , but may be overridden to . /// [EnumMember(Value = "slider_tail_hit")] [Order(8)] From 3f527081bd663064cf098b5b8af003ba4d41b2ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Jan 2024 16:12:46 +0900 Subject: [PATCH 2807/4092] 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 55ef55ab7d..d944e2ce8e 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 5b99319499..bd6891f448 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From a7f9f50ce5c3924733c82934fc0402af41d83802 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Jan 2024 16:52:50 +0900 Subject: [PATCH 2808/4092] Show a better message when score submission fails due to system clock being set wrong --- osu.Game/Screens/Play/SubmittingPlayer.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 171ceea84f..c8e84f1961 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -132,7 +132,18 @@ namespace osu.Game.Screens.Play if (string.IsNullOrEmpty(exception.Message)) Logger.Error(exception, "Failed to retrieve a score submission token."); else - Logger.Log($"You are not able to submit a score: {exception.Message}", level: LogLevel.Important); + { + switch (exception.Message) + { + case "expired token": + Logger.Log("Score submission failed because your system clock is set incorrectly. Please check your system time, date and timezone.", level: LogLevel.Important); + break; + + default: + Logger.Log($"You are not able to submit a score: {exception.Message}", level: LogLevel.Important); + break; + } + } Schedule(() => { From ebf637bd3c33f1c886f6bfc81aa9ea2132c9e0d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Jan 2024 17:41:26 +0900 Subject: [PATCH 2809/4092] Adjust slider tick / end misses to show slightly longer --- .../Skinning/Argon/ArgonJudgementPieceSliderTickMiss.cs | 2 +- .../Skinning/Default/DefaultJudgementPieceSliderTickMiss.cs | 2 +- osu.Game/Skinning/LegacyJudgementPieceOld.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPieceSliderTickMiss.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPieceSliderTickMiss.cs index 878e8dbfc2..bd883d6e4c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPieceSliderTickMiss.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPieceSliderTickMiss.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon this.ScaleTo(1.4f); this.ScaleTo(1f, 150, Easing.Out); - this.FadeOutFromOne(400); + this.FadeOutFromOne(600); } public Drawable? GetAboveHitObjectsProxiedContent() => piece.CreateProxy(); diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultJudgementPieceSliderTickMiss.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultJudgementPieceSliderTickMiss.cs index 9fc71852ba..04c15a1433 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultJudgementPieceSliderTickMiss.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultJudgementPieceSliderTickMiss.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default this.ScaleTo(1.4f); this.ScaleTo(1f, 150, Easing.Out); - this.FadeOutFromOne(400); + this.FadeOutFromOne(600); } public Drawable? GetAboveHitObjectsProxiedContent() => piece.CreateProxy(); diff --git a/osu.Game/Skinning/LegacyJudgementPieceOld.cs b/osu.Game/Skinning/LegacyJudgementPieceOld.cs index 068707ba4f..c8630b54a6 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceOld.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceOld.cs @@ -62,7 +62,7 @@ namespace osu.Game.Skinning this.ScaleTo(1.2f); this.ScaleTo(1f, 100, Easing.In); - this.FadeOutFromOne(400); + this.Delay(fade_out_delay / 2).FadeOut(fade_out_length); } else { From fbc923b47ed5e00ac94b5fa20b7310208e650e8c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Jan 2024 17:51:38 +0900 Subject: [PATCH 2810/4092] Revert "Merge pull request #26870 from smoogipoo/adjust-default-minresult" This reverts commit 1acff746ee65020689b873c279aefb9c6c3d8124, reversing changes made to 696ecda398b22da06066bb4d5fc32861758829a8. --- osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs | 1 + osu.Game/Rulesets/Judgements/Judgement.cs | 4 +--- osu.Game/Rulesets/Scoring/HitResult.cs | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index ee2490439f..ceee513412 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -30,6 +30,7 @@ namespace osu.Game.Rulesets.Osu.Objects public class TailJudgement : SliderEndJudgement { public override HitResult MaxResult => HitResult.SliderTailHit; + public override HitResult MinResult => HitResult.IgnoreMiss; } } } diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index d4d06167f1..93386de483 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -73,10 +73,8 @@ namespace osu.Game.Rulesets.Judgements return HitResult.SmallTickMiss; case HitResult.LargeTickHit: - return HitResult.LargeTickMiss; - case HitResult.SliderTailHit: - return HitResult.IgnoreMiss; + return HitResult.LargeTickMiss; default: return HitResult.Miss; diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index b6cfca58db..20ec3c4946 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -138,8 +138,7 @@ namespace osu.Game.Rulesets.Scoring ComboBreak, /// - /// A special tick judgement to increase the valuation of the final tick of a slider. - /// The default minimum result is , but may be overridden to . + /// A special judgement similar to that's used to increase the valuation of the final tick of a slider. /// [EnumMember(Value = "slider_tail_hit")] [Order(8)] From 0642a0ee11f492345d373cd488479449df01bb28 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 31 Jan 2024 15:54:43 +0900 Subject: [PATCH 2811/4092] Adjust default min result of SliderTailHit, remove override --- osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs | 1 - osu.Game/Rulesets/Judgements/Judgement.cs | 4 +++- osu.Game/Rulesets/Scoring/HitResult.cs | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index ceee513412..ee2490439f 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -30,7 +30,6 @@ namespace osu.Game.Rulesets.Osu.Objects public class TailJudgement : SliderEndJudgement { public override HitResult MaxResult => HitResult.SliderTailHit; - public override HitResult MinResult => HitResult.IgnoreMiss; } } } diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index 93386de483..d4d06167f1 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -73,9 +73,11 @@ namespace osu.Game.Rulesets.Judgements return HitResult.SmallTickMiss; case HitResult.LargeTickHit: - case HitResult.SliderTailHit: return HitResult.LargeTickMiss; + case HitResult.SliderTailHit: + return HitResult.IgnoreMiss; + default: return HitResult.Miss; } diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index 20ec3c4946..b6cfca58db 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -138,7 +138,8 @@ namespace osu.Game.Rulesets.Scoring ComboBreak, /// - /// A special judgement similar to that's used to increase the valuation of the final tick of a slider. + /// A special tick judgement to increase the valuation of the final tick of a slider. + /// The default minimum result is , but may be overridden to . /// [EnumMember(Value = "slider_tail_hit")] [Order(8)] From 66aa41e00162bcc2c32b48407a9d8dd999831f7c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 31 Jan 2024 18:04:32 +0900 Subject: [PATCH 2812/4092] Fix tests --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 73465fae08..a3f91fffba 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -31,8 +29,8 @@ namespace osu.Game.Tests.Rulesets.Scoring { public partial class ScoreProcessorTest { - private ScoreProcessor scoreProcessor; - private IBeatmap beatmap; + private ScoreProcessor scoreProcessor = null!; + private IBeatmap beatmap = null!; [SetUp] public void SetUp() @@ -86,7 +84,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, HitResult.SmallTickHit, 493_652)] [TestCase(ScoringMode.Standardised, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] [TestCase(ScoringMode.Standardised, HitResult.LargeTickHit, HitResult.LargeTickHit, 326_963)] - [TestCase(ScoringMode.Standardised, HitResult.SliderTailHit, HitResult.SliderTailHit, 326_963)] + [TestCase(ScoringMode.Standardised, HitResult.SliderTailHit, HitResult.SliderTailHit, 371_627)] [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 1_000_030)] [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 1_000_150)] [TestCase(ScoringMode.Classic, HitResult.Miss, HitResult.Great, 0)] @@ -99,7 +97,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 49_365)] [TestCase(ScoringMode.Classic, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 32_696)] - [TestCase(ScoringMode.Classic, HitResult.SliderTailHit, HitResult.SliderTailHit, 32_696)] + [TestCase(ScoringMode.Classic, HitResult.SliderTailHit, HitResult.SliderTailHit, 37_163)] [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 100_003)] [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 100_015)] public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, int expectedScore) @@ -171,7 +169,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(HitResult.Perfect, HitResult.Miss)] [TestCase(HitResult.SmallTickHit, HitResult.SmallTickMiss)] [TestCase(HitResult.LargeTickHit, HitResult.LargeTickMiss)] - [TestCase(HitResult.SliderTailHit, HitResult.LargeTickMiss)] + [TestCase(HitResult.SliderTailHit, HitResult.IgnoreMiss)] [TestCase(HitResult.SmallBonus, HitResult.IgnoreMiss)] [TestCase(HitResult.LargeBonus, HitResult.IgnoreMiss)] public void TestMinResults(HitResult hitResult, HitResult expectedMinResult) @@ -476,7 +474,7 @@ namespace osu.Game.Tests.Rulesets.Scoring public override IEnumerable GetModsFor(ModType type) => throw new NotImplementedException(); - public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => throw new NotImplementedException(); + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList? mods = null) => throw new NotImplementedException(); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException(); From f927cb59285ff730f7c8ad7426775f992c0f1352 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 31 Jan 2024 12:29:18 +0100 Subject: [PATCH 2813/4092] Increase repeat count for better coverage of flip operations --- .../OsuHitObjectGenerationUtilsTest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/OsuHitObjectGenerationUtilsTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuHitObjectGenerationUtilsTest.cs index d78c32aa6a..ffb9ad1784 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuHitObjectGenerationUtilsTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuHitObjectGenerationUtilsTest.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Tests new PathControlPoint(new Vector2(-128, 0), PathType.LINEAR) // absolute position: (0, 128) } }, - RepeatCount = 1 + RepeatCount = 2 }; slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); return slider; @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Tests OsuHitObjectGenerationUtils.ReflectHorizontallyAlongPlayfield(slider); Assert.That(slider.Position, Is.EqualTo(new Vector2(OsuPlayfield.BASE_SIZE.X - 128, 128))); - Assert.That(slider.NestedHitObjects.OfType().Single().Position, Is.EqualTo(new Vector2(OsuPlayfield.BASE_SIZE.X - 0, 128))); + Assert.That(slider.NestedHitObjects.OfType().First().Position, Is.EqualTo(new Vector2(OsuPlayfield.BASE_SIZE.X - 0, 128))); Assert.That(slider.Path.ControlPoints.Select(point => point.Position), Is.EquivalentTo(new[] { new Vector2(), @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.Tests OsuHitObjectGenerationUtils.ReflectVerticallyAlongPlayfield(slider); Assert.That(slider.Position, Is.EqualTo(new Vector2(128, OsuPlayfield.BASE_SIZE.Y - 128))); - Assert.That(slider.NestedHitObjects.OfType().Single().Position, Is.EqualTo(new Vector2(0, OsuPlayfield.BASE_SIZE.Y - 128))); + Assert.That(slider.NestedHitObjects.OfType().First().Position, Is.EqualTo(new Vector2(0, OsuPlayfield.BASE_SIZE.Y - 128))); Assert.That(slider.Path.ControlPoints.Select(point => point.Position), Is.EquivalentTo(new[] { new Vector2(), @@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Osu.Tests OsuHitObjectGenerationUtils.FlipSliderInPlaceHorizontally(slider); Assert.That(slider.Position, Is.EqualTo(new Vector2(128, 128))); - Assert.That(slider.NestedHitObjects.OfType().Single().Position, Is.EqualTo(new Vector2(256, 128))); + Assert.That(slider.NestedHitObjects.OfType().First().Position, Is.EqualTo(new Vector2(256, 128))); Assert.That(slider.Path.ControlPoints.Select(point => point.Position), Is.EquivalentTo(new[] { new Vector2(), From a934556bb8e4d266b67c35bbc8c328d540ee35cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 31 Jan 2024 12:34:38 +0100 Subject: [PATCH 2814/4092] Add failing assertions for head/tail positions after flip --- .../OsuHitObjectGenerationUtilsTest.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/OsuHitObjectGenerationUtilsTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuHitObjectGenerationUtilsTest.cs index ffb9ad1784..77ef4627cb 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuHitObjectGenerationUtilsTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuHitObjectGenerationUtilsTest.cs @@ -45,7 +45,9 @@ namespace osu.Game.Rulesets.Osu.Tests OsuHitObjectGenerationUtils.ReflectHorizontallyAlongPlayfield(slider); Assert.That(slider.Position, Is.EqualTo(new Vector2(OsuPlayfield.BASE_SIZE.X - 128, 128))); + Assert.That(slider.NestedHitObjects.OfType().Single().Position, Is.EqualTo(new Vector2(OsuPlayfield.BASE_SIZE.X - 128, 128))); Assert.That(slider.NestedHitObjects.OfType().First().Position, Is.EqualTo(new Vector2(OsuPlayfield.BASE_SIZE.X - 0, 128))); + Assert.That(slider.NestedHitObjects.OfType().Single().Position, Is.EqualTo(new Vector2(OsuPlayfield.BASE_SIZE.X, 128))); Assert.That(slider.Path.ControlPoints.Select(point => point.Position), Is.EquivalentTo(new[] { new Vector2(), @@ -62,7 +64,9 @@ namespace osu.Game.Rulesets.Osu.Tests OsuHitObjectGenerationUtils.ReflectVerticallyAlongPlayfield(slider); Assert.That(slider.Position, Is.EqualTo(new Vector2(128, OsuPlayfield.BASE_SIZE.Y - 128))); + Assert.That(slider.NestedHitObjects.OfType().Single().Position, Is.EqualTo(new Vector2(128, OsuPlayfield.BASE_SIZE.Y - 128))); Assert.That(slider.NestedHitObjects.OfType().First().Position, Is.EqualTo(new Vector2(0, OsuPlayfield.BASE_SIZE.Y - 128))); + Assert.That(slider.NestedHitObjects.OfType().Single().Position, Is.EqualTo(new Vector2(0, OsuPlayfield.BASE_SIZE.Y - 128))); Assert.That(slider.Path.ControlPoints.Select(point => point.Position), Is.EquivalentTo(new[] { new Vector2(), @@ -79,7 +83,9 @@ namespace osu.Game.Rulesets.Osu.Tests OsuHitObjectGenerationUtils.FlipSliderInPlaceHorizontally(slider); Assert.That(slider.Position, Is.EqualTo(new Vector2(128, 128))); + Assert.That(slider.NestedHitObjects.OfType().Single().Position, Is.EqualTo(new Vector2(128, 128))); Assert.That(slider.NestedHitObjects.OfType().First().Position, Is.EqualTo(new Vector2(256, 128))); + Assert.That(slider.NestedHitObjects.OfType().Single().Position, Is.EqualTo(new Vector2(256, 128))); Assert.That(slider.Path.ControlPoints.Select(point => point.Position), Is.EquivalentTo(new[] { new Vector2(), From dfea2ade6d7d1dda0b080fca826cec767a480199 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 31 Jan 2024 12:43:24 +0100 Subject: [PATCH 2815/4092] Revert incorrect end position optimisation Closes https://github.com/ppy/osu/issues/26867. Reverts 882f49039029b7dc3e287ccc302d04de89de10df and ce643aa68f35369be1a975bb1ceb69fb54192cf2. The applied optimisation may have been valid as long as it was constrained to `Slider`. But it is not, as `SliderTailCircle` stores a local copy of the object position. And as the commit message of ce643aa68f35369be1a975bb1ceb69fb54192cf2 states, this could be bypassed by some pretty hacky delegation from `SliderTailCircle.Position` to the slider, but it'd also be pretty hacky because it would make flows like `PositionBindable` break down. Long-term solution is to probably remove bindables from hitobjects. --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 032f105ded..506145568e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -98,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Objects set { repeatCount = value; - endPositionCache.Invalidate(); + updateNestedPositions(); } } @@ -165,7 +165,7 @@ namespace osu.Game.Rulesets.Osu.Objects public Slider() { SamplesBindable.CollectionChanged += (_, _) => UpdateNestedSamples(); - Path.Version.ValueChanged += _ => endPositionCache.Invalidate(); + Path.Version.ValueChanged += _ => updateNestedPositions(); } protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) From 424859328938f86d6520bf784530c3b33d2a1ffb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Jan 2024 22:41:44 +0900 Subject: [PATCH 2816/4092] Fix menu banner not updating as often as we want it to --- osu.Game/Online/API/Requests/GetSystemTitleRequest.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetSystemTitleRequest.cs b/osu.Game/Online/API/Requests/GetSystemTitleRequest.cs index 659e46bb11..52ca0c11eb 100644 --- a/osu.Game/Online/API/Requests/GetSystemTitleRequest.cs +++ b/osu.Game/Online/API/Requests/GetSystemTitleRequest.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.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests @@ -9,7 +8,7 @@ namespace osu.Game.Online.API.Requests public class GetSystemTitleRequest : OsuJsonWebRequest { public GetSystemTitleRequest() - : base($@"https://assets.ppy.sh/lazer-status.json?{DateTimeOffset.UtcNow.ToUnixTimeSeconds() / 1800}") + : base(@"https://assets.ppy.sh/lazer-status.json") { } } From e71c95f1fed41cf194d4c004c0d1b5a9e3620f32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 31 Jan 2024 14:48:27 +0100 Subject: [PATCH 2817/4092] Reintroduce `IMod.Ranked` What goes around, comes around. --- osu.Game/Rulesets/Mods/IMod.cs | 5 +++++ osu.Game/Rulesets/Mods/Mod.cs | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/osu.Game/Rulesets/Mods/IMod.cs b/osu.Game/Rulesets/Mods/IMod.cs index 744d02a4fa..3a33d14835 100644 --- a/osu.Game/Rulesets/Mods/IMod.cs +++ b/osu.Game/Rulesets/Mods/IMod.cs @@ -66,6 +66,11 @@ namespace osu.Game.Rulesets.Mods /// bool AlwaysValidForSubmission { get; } + /// + /// Whether scores with this mod active can give performance points. + /// + bool Ranked { get; } + /// /// Create a fresh instance based on this mod. /// diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 0500b49513..50c867f41b 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -167,6 +167,12 @@ namespace osu.Game.Rulesets.Mods [JsonIgnore] public virtual bool RequiresConfiguration => false; + /// + /// Whether scores with this mod active can give performance points. + /// + [JsonIgnore] + public virtual bool Ranked => false; + /// /// The mods this mod cannot be enabled with. /// From 0642d740149b2daf8876d76b3b55c15709cd6144 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 31 Jan 2024 22:52:57 +0900 Subject: [PATCH 2818/4092] Expose as ReadOnlyDictionary --- .../PooledDrawableWithLifetimeContainer.cs | 19 +++++++++++-------- .../UI/GameplaySampleTriggerSource.cs | 2 +- osu.Game/Rulesets/UI/HitObjectContainer.cs | 2 +- .../Scrolling/ScrollingHitObjectContainer.cs | 2 +- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs b/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs index 1ebdf48ae8..aed608cf8f 100644 --- a/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Diagnostics; using System.Linq; using osu.Framework.Graphics; @@ -35,7 +36,7 @@ namespace osu.Game.Rulesets.Objects.Pooling /// /// The enumeration order is undefined. /// - public IEnumerable<(TEntry Entry, TDrawable Drawable)> AliveEntries => AliveDrawableMap.Select(x => (x.Key, x.Value)); + public readonly ReadOnlyDictionary AliveEntries; /// /// Whether to remove an entry when clock goes backward and crossed its . @@ -53,7 +54,7 @@ namespace osu.Game.Rulesets.Objects.Pooling /// internal double FutureLifetimeExtension { get; set; } - public readonly Dictionary AliveDrawableMap = new Dictionary(); + private readonly Dictionary aliveDrawableMap = new Dictionary(); private readonly HashSet allEntries = new HashSet(); private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); @@ -63,6 +64,8 @@ namespace osu.Game.Rulesets.Objects.Pooling lifetimeManager.EntryBecameAlive += entryBecameAlive; lifetimeManager.EntryBecameDead += entryBecameDead; lifetimeManager.EntryCrossedBoundary += entryCrossedBoundary; + + AliveEntries = new ReadOnlyDictionary(aliveDrawableMap); } /// @@ -101,10 +104,10 @@ namespace osu.Game.Rulesets.Objects.Pooling private void entryBecameAlive(LifetimeEntry lifetimeEntry) { var entry = (TEntry)lifetimeEntry; - Debug.Assert(!AliveDrawableMap.ContainsKey(entry)); + Debug.Assert(!aliveDrawableMap.ContainsKey(entry)); TDrawable drawable = GetDrawable(entry); - AliveDrawableMap[entry] = drawable; + aliveDrawableMap[entry] = drawable; AddDrawable(entry, drawable); } @@ -119,10 +122,10 @@ namespace osu.Game.Rulesets.Objects.Pooling private void entryBecameDead(LifetimeEntry lifetimeEntry) { var entry = (TEntry)lifetimeEntry; - Debug.Assert(AliveDrawableMap.ContainsKey(entry)); + Debug.Assert(aliveDrawableMap.ContainsKey(entry)); - TDrawable drawable = AliveDrawableMap[entry]; - AliveDrawableMap.Remove(entry); + TDrawable drawable = aliveDrawableMap[entry]; + aliveDrawableMap.Remove(entry); RemoveDrawable(entry, drawable); } @@ -148,7 +151,7 @@ namespace osu.Game.Rulesets.Objects.Pooling foreach (var entry in Entries.ToArray()) Remove(entry); - Debug.Assert(AliveDrawableMap.Count == 0); + Debug.Assert(aliveDrawableMap.Count == 0); } protected override bool CheckChildrenLife() diff --git a/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs b/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs index b61e8d9674..177520f28f 100644 --- a/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs +++ b/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.UI // If required, we can make this lookup more efficient by adding support to get next-future-entry in LifetimeEntryManager. var candidate = // Use alive entries first as an optimisation. - hitObjectContainer.AliveEntries.Select(tuple => tuple.Entry).Where(e => !isAlreadyHit(e)).MinBy(e => e.HitObject.StartTime) + hitObjectContainer.AliveEntries.Keys.Where(e => !isAlreadyHit(e)).MinBy(e => e.HitObject.StartTime) ?? hitObjectContainer.Entries.Where(e => !isAlreadyHit(e)).MinBy(e => e.HitObject.StartTime); // In the case there are no non-judged objects, the last hit object should be used instead. diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 099be486b3..c2879e6d87 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.UI { public IEnumerable Objects => InternalChildren.Cast().OrderBy(h => h.HitObject.StartTime); - public IEnumerable AliveObjects => AliveEntries.Select(pair => pair.Drawable).OrderBy(h => h.HitObject.StartTime); + public IEnumerable AliveObjects => AliveEntries.Values.OrderBy(h => h.HitObject.StartTime); /// /// Invoked when a is judged. diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 23ac4378e4..4e72291b9c 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -186,7 +186,7 @@ namespace osu.Game.Rulesets.UI.Scrolling // to prevent hit objects displayed in a wrong position for one frame. // Only AliveEntries need to be considered for layout (reduces overhead in the case of scroll speed changes). // We are not using AliveObjects directly to avoid selection/sorting overhead since we don't care about the order at which positions will be updated. - foreach (var entry in AliveDrawableMap) + foreach (var entry in AliveEntries) { var obj = entry.Value; From f89923aeaee5785aeb4b91b6aaa3dfdf8b8ad791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 31 Jan 2024 14:59:35 +0100 Subject: [PATCH 2819/4092] Annotate mods that give pp --- osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs | 1 + osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs | 1 + osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs | 1 + osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs | 1 + osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs | 1 + osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs | 1 + osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs | 1 + osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 1 + osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs | 1 + osu.Game/Rulesets/Mods/ModDaycore.cs | 1 + osu.Game/Rulesets/Mods/ModDoubleTime.cs | 1 + osu.Game/Rulesets/Mods/ModEasy.cs | 1 + osu.Game/Rulesets/Mods/ModFlashlight.cs | 1 + osu.Game/Rulesets/Mods/ModHalfTime.cs | 1 + osu.Game/Rulesets/Mods/ModHardRock.cs | 1 + osu.Game/Rulesets/Mods/ModHidden.cs | 1 + osu.Game/Rulesets/Mods/ModMuted.cs | 1 + osu.Game/Rulesets/Mods/ModNightcore.cs | 1 + osu.Game/Rulesets/Mods/ModNoFail.cs | 1 + osu.Game/Rulesets/Mods/ModPerfect.cs | 1 + osu.Game/Rulesets/Mods/ModSuddenDeath.cs | 1 + 21 files changed, 21 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs index 050b302bd8..88d6a19822 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs @@ -15,6 +15,7 @@ namespace osu.Game.Rulesets.Mania.Mods public abstract int KeyCount { get; } public override ModType Type => ModType.Conversion; public override double ScoreMultiplier => 1; // TODO: Implement the mania key mod score multiplier + public override bool Ranked => UsesDefaultConfiguration; public void ApplyToBeatmapConverter(IBeatmapConverter beatmapConverter) { diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs index d9de06a811..189c4b3a5f 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs @@ -8,5 +8,6 @@ namespace osu.Game.Rulesets.Mania.Mods public class ManiaModHardRock : ModHardRock { public override double ScoreMultiplier => 1; + public override bool Ranked => false; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs index 31f52610e9..7dd0c499da 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs @@ -11,5 +11,6 @@ namespace osu.Game.Rulesets.Mania.Mods public override string Name => "One Key"; public override string Acronym => "1K"; public override LocalisableString Description => @"Play with one key."; + public override bool Ranked => false; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs index 67e65b887a..a6c57d4597 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs @@ -11,5 +11,6 @@ namespace osu.Game.Rulesets.Mania.Mods public override string Name => "Ten Keys"; public override string Acronym => "10K"; public override LocalisableString Description => @"Play with ten keys."; + public override bool Ranked => false; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs index 0f8148d252..0d04395a52 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs @@ -11,5 +11,6 @@ namespace osu.Game.Rulesets.Mania.Mods public override string Name => "Two Keys"; public override string Acronym => "2K"; public override LocalisableString Description => @"Play with two keys."; + public override bool Ranked => false; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs index 0f8af7940c..c83b0979ee 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs @@ -11,5 +11,6 @@ namespace osu.Game.Rulesets.Mania.Mods public override string Name => "Three Keys"; public override string Acronym => "3K"; public override LocalisableString Description => @"Play with three keys."; + public override bool Ranked => false; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs index f9690b4298..cc7e270dda 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs @@ -14,6 +14,7 @@ namespace osu.Game.Rulesets.Mania.Mods public class ManiaModMirror : ModMirror, IApplicableToBeatmap { public override LocalisableString Description => "Notes are flipped horizontally."; + public override bool Ranked => UsesDefaultConfiguration; public void ApplyToBeatmap(IBeatmap beatmap) { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index f691731afe..df9544b71e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override LocalisableString Description => @"Spinners will be automatically completed."; public override double ScoreMultiplier => 0.9; public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(OsuModAutopilot), typeof(OsuModTargetPractice) }; + public override bool Ranked => UsesDefaultConfiguration; public void ApplyToDrawableHitObject(DrawableHitObject hitObject) { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs index f1468d414e..917685cdad 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs @@ -10,5 +10,6 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModTouchDevice : ModTouchDevice { public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot) }).ToArray(); + public override bool Ranked => UsesDefaultConfiguration; } } diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs index 09b35c249e..359f8a950c 100644 --- a/osu.Game/Rulesets/Mods/ModDaycore.cs +++ b/osu.Game/Rulesets/Mods/ModDaycore.cs @@ -17,6 +17,7 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => null; public override ModType Type => ModType.DifficultyReduction; public override LocalisableString Description => "Whoaaaaa..."; + public override bool Ranked => UsesDefaultConfiguration; [SettingSource("Speed decrease", "The actual decrease to apply", SettingControlType = typeof(MultiplierSettingsSlider))] public override BindableNumber SpeedChange { get; } = new BindableDouble(0.75) diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index 789291772d..8e430da368 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -18,6 +18,7 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => OsuIcon.ModDoubleTime; public override ModType Type => ModType.DifficultyIncrease; public override LocalisableString Description => "Zoooooooooom..."; + public override bool Ranked => UsesDefaultConfiguration; [SettingSource("Speed increase", "The actual increase to apply", SettingControlType = typeof(MultiplierSettingsSlider))] public override BindableNumber SpeedChange { get; } = new BindableDouble(1.5) diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index 0f51e2a6d5..da43a6b294 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -16,6 +16,7 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.DifficultyReduction; public override double ScoreMultiplier => 0.5; public override Type[] IncompatibleMods => new[] { typeof(ModHardRock), typeof(ModDifficultyAdjust) }; + public override bool Ranked => UsesDefaultConfiguration; public virtual void ReadFromDifficulty(BeatmapDifficulty difficulty) { diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index dc2ad6f47e..9227af64b8 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -33,6 +33,7 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => OsuIcon.ModFlashlight; public override ModType Type => ModType.DifficultyIncrease; public override LocalisableString Description => "Restricted view area."; + public override bool Ranked => UsesDefaultConfiguration; [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] public abstract BindableFloat SizeMultiplier { get; } diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index 8b5dd39584..59e40ee9cc 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -18,6 +18,7 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => OsuIcon.ModHalftime; public override ModType Type => ModType.DifficultyReduction; public override LocalisableString Description => "Less zoom..."; + public override bool Ranked => UsesDefaultConfiguration; [SettingSource("Speed decrease", "The actual decrease to apply", SettingControlType = typeof(MultiplierSettingsSlider))] public override BindableNumber SpeedChange { get; } = new BindableDouble(0.75) diff --git a/osu.Game/Rulesets/Mods/ModHardRock.cs b/osu.Game/Rulesets/Mods/ModHardRock.cs index 4b2d1d050e..1e99891b99 100644 --- a/osu.Game/Rulesets/Mods/ModHardRock.cs +++ b/osu.Game/Rulesets/Mods/ModHardRock.cs @@ -17,6 +17,7 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.DifficultyIncrease; public override LocalisableString Description => "Everything just got a bit harder..."; public override Type[] IncompatibleMods => new[] { typeof(ModEasy), typeof(ModDifficultyAdjust) }; + public override bool Ranked => UsesDefaultConfiguration; protected const float ADJUST_RATIO = 1.4f; diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index 8b25768575..5a1abf115f 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -14,6 +14,7 @@ namespace osu.Game.Rulesets.Mods public override string Acronym => "HD"; public override IconUsage? Icon => OsuIcon.ModHidden; public override ModType Type => ModType.DifficultyIncrease; + public override bool Ranked => UsesDefaultConfiguration; public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) { diff --git a/osu.Game/Rulesets/Mods/ModMuted.cs b/osu.Game/Rulesets/Mods/ModMuted.cs index 131f501630..3ecd9aa6a1 100644 --- a/osu.Game/Rulesets/Mods/ModMuted.cs +++ b/osu.Game/Rulesets/Mods/ModMuted.cs @@ -25,6 +25,7 @@ namespace osu.Game.Rulesets.Mods public override LocalisableString Description => "Can you still feel the rhythm without music?"; public override ModType Type => ModType.Fun; public override double ScoreMultiplier => 1; + public override bool Ranked => UsesDefaultConfiguration; } public abstract class ModMuted : ModMuted, IApplicableToDrawableRuleset, IApplicableToTrack, IApplicableToScoreProcessor diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index b42927256c..bb18940f8c 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -28,6 +28,7 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => OsuIcon.ModNightcore; public override ModType Type => ModType.DifficultyIncrease; public override LocalisableString Description => "Uguuuuuuuu..."; + public override bool Ranked => UsesDefaultConfiguration; [SettingSource("Speed increase", "The actual increase to apply", SettingControlType = typeof(MultiplierSettingsSlider))] public override BindableNumber SpeedChange { get; } = new BindableDouble(1.5) diff --git a/osu.Game/Rulesets/Mods/ModNoFail.cs b/osu.Game/Rulesets/Mods/ModNoFail.cs index cc451772b2..1aaef8eac4 100644 --- a/osu.Game/Rulesets/Mods/ModNoFail.cs +++ b/osu.Game/Rulesets/Mods/ModNoFail.cs @@ -20,6 +20,7 @@ namespace osu.Game.Rulesets.Mods public override LocalisableString Description => "You can't fail, no matter what."; public override double ScoreMultiplier => 0.5; public override Type[] IncompatibleMods => new[] { typeof(ModFailCondition), typeof(ModCinema) }; + public override bool Ranked => UsesDefaultConfiguration; private readonly Bindable showHealthBar = new Bindable(); diff --git a/osu.Game/Rulesets/Mods/ModPerfect.cs b/osu.Game/Rulesets/Mods/ModPerfect.cs index 0ba40ba070..f8f498ceb5 100644 --- a/osu.Game/Rulesets/Mods/ModPerfect.cs +++ b/osu.Game/Rulesets/Mods/ModPerfect.cs @@ -19,6 +19,7 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.DifficultyIncrease; public override double ScoreMultiplier => 1; public override LocalisableString Description => "SS or quit."; + public override bool Ranked => UsesDefaultConfiguration; public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(ModSuddenDeath), typeof(ModAccuracyChallenge) }).ToArray(); diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs index 4e4e8662e8..62579a168c 100644 --- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs +++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs @@ -19,6 +19,7 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.DifficultyIncrease; public override LocalisableString Description => "Miss and fail."; public override double ScoreMultiplier => 1; + public override bool Ranked => UsesDefaultConfiguration; public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModPerfect)).ToArray(); From 7165511754ad437f035e87fdd96323d3c75e7a67 Mon Sep 17 00:00:00 2001 From: syscats Date: Wed, 31 Jan 2024 15:49:31 +0100 Subject: [PATCH 2820/4092] Use ordinal sorting for artist string comparison --- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 6d2e938fb7..857f7efb8e 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -67,7 +67,7 @@ namespace osu.Game.Screens.Select.Carousel { default: case SortMode.Artist: - comparison = string.Compare(BeatmapSet.Metadata.Artist, otherSet.BeatmapSet.Metadata.Artist, StringComparison.OrdinalIgnoreCase); + comparison = string.Compare(BeatmapSet.Metadata.Artist, otherSet.BeatmapSet.Metadata.Artist, StringComparison.Ordinal); break; case SortMode.Title: From 563f4a26b139be4b4c9354af20b5275527ee85d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Feb 2024 21:10:37 +0900 Subject: [PATCH 2821/4092] Show "failing" icon on user panel when 2FA prompt is present This gives the user a chance to know it's required. --- osu.Game/Overlays/Toolbar/ToolbarUserButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs index 28521e3331..1e812d7238 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs @@ -95,7 +95,7 @@ namespace osu.Game.Overlays.Toolbar private void onlineStateChanged(ValueChangedEvent state) => Schedule(() => { - failingIcon.FadeTo(state.NewValue == APIState.Failing ? 1 : 0, 200, Easing.OutQuint); + failingIcon.FadeTo(state.NewValue == APIState.Failing || state.NewValue == APIState.RequiresSecondFactorAuth ? 1 : 0, 200, Easing.OutQuint); switch (state.NewValue) { From bf3746daa8e8117a23978b3982185af7145d37fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Feb 2024 21:10:53 +0900 Subject: [PATCH 2822/4092] Show login overlay at main menu when 2FA is required --- osu.Game/Screens/Menu/MainMenu.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index a75edd1cff..decb901c32 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -280,7 +280,7 @@ namespace osu.Game.Screens.Menu sideFlashes.Delay(FADE_IN_DURATION).FadeIn(64, Easing.InQuint); } - else if (!api.IsLoggedIn) + else if (!api.IsLoggedIn || api.State.Value == APIState.RequiresSecondFactorAuth) { // copy out old action to avoid accidentally capturing logo.Action in closure, causing a self-reference loop. var previousAction = logo.Action; From 0407b5d84ac0bc2d872ffc3bb173f622900ffd45 Mon Sep 17 00:00:00 2001 From: syscats Date: Thu, 1 Feb 2024 14:03:59 +0100 Subject: [PATCH 2823/4092] use ordinal sorting on each method --- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 857f7efb8e..f7b715862a 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -71,15 +71,15 @@ namespace osu.Game.Screens.Select.Carousel break; case SortMode.Title: - comparison = string.Compare(BeatmapSet.Metadata.Title, otherSet.BeatmapSet.Metadata.Title, StringComparison.OrdinalIgnoreCase); + comparison = string.Compare(BeatmapSet.Metadata.Title, otherSet.BeatmapSet.Metadata.Title, StringComparison.Ordinal); break; case SortMode.Author: - comparison = string.Compare(BeatmapSet.Metadata.Author.Username, otherSet.BeatmapSet.Metadata.Author.Username, StringComparison.OrdinalIgnoreCase); + comparison = string.Compare(BeatmapSet.Metadata.Author.Username, otherSet.BeatmapSet.Metadata.Author.Username, StringComparison.Ordinal); break; case SortMode.Source: - comparison = string.Compare(BeatmapSet.Metadata.Source, otherSet.BeatmapSet.Metadata.Source, StringComparison.OrdinalIgnoreCase); + comparison = string.Compare(BeatmapSet.Metadata.Source, otherSet.BeatmapSet.Metadata.Source, StringComparison.Ordinal); break; case SortMode.DateAdded: From 5bbaeb6836cd9280f7e09d946192ba9e3ad69ddf Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 1 Feb 2024 16:08:33 +0300 Subject: [PATCH 2824/4092] Play legacy applause sound only when rank is B or higher --- .../Expanded/Accuracy/AccuracyCircle.cs | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 60c35e6203..5de5ceb2a8 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -10,7 +10,6 @@ using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; @@ -86,6 +85,9 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy /// public static readonly Easing ACCURACY_TRANSFORM_EASING = Easing.OutPow10; + [Resolved] + private SkinManager skins { get; set; } + private readonly ScoreInfo score; private CircularProgress accuracyCircle; @@ -99,7 +101,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy private PoolableSkinnableSample swooshUpSound; private PoolableSkinnableSample rankImpactSound; private PoolableSkinnableSample rankApplauseSound; - private DrawableSample rankFailSound; + private PoolableSkinnableSample rankLegacyApplauseSound; private readonly Bindable tickPlaybackRate = new Bindable(); @@ -264,12 +266,12 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy AddRangeInternal(new Drawable[] { rankImpactSound = new PoolableSkinnableSample(new SampleInfo(impactSampleName)), - rankApplauseSound = new PoolableSkinnableSample(new SampleInfo(@"applause", applauseSampleName)), + rankApplauseSound = new PoolableSkinnableSample(new SampleInfo(applauseSampleName)), + rankLegacyApplauseSound = new PoolableSkinnableSample(new SampleInfo("applause")), scoreTickSound = new PoolableSkinnableSample(new SampleInfo(@"Results/score-tick")), badgeTickSound = new PoolableSkinnableSample(new SampleInfo(@"Results/badge-dink")), badgeMaxSound = new PoolableSkinnableSample(new SampleInfo(@"Results/badge-dink-max")), swooshUpSound = new PoolableSkinnableSample(new SampleInfo(@"Results/swoosh-up")), - rankFailSound = new DrawableSample(audio.Samples.Get(results_applause_d_sound)), }); } } @@ -399,15 +401,19 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy { Schedule(() => { - if (score.Rank != ScoreRank.F) + if (skins.CurrentSkin.Value is LegacySkin) { - rankApplauseSound.VolumeTo(applause_volume); - rankApplauseSound.Play(); + // only play legacy "applause" sound if score rank is B or higher. + if (score.Rank >= ScoreRank.B) + { + rankLegacyApplauseSound.VolumeTo(applause_volume); + rankLegacyApplauseSound.Play(); + } } else { - rankFailSound.VolumeTo(applause_volume); - rankFailSound.Play(); + rankApplauseSound.VolumeTo(applause_volume); + rankApplauseSound.Play(); } }); } @@ -451,8 +457,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy } } - private const string results_applause_d_sound = @"Results/applause-d"; - private string applauseSampleName { get @@ -461,7 +465,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy { default: case ScoreRank.D: - return results_applause_d_sound; + return @"Results/applause-d"; case ScoreRank.C: return @"Results/applause-c"; From 6ab8960fdc630d4110734d206bd15c417b76d0f5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 1 Feb 2024 16:08:48 +0300 Subject: [PATCH 2825/4092] Add step for toggling skins in results screen test scene --- osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 122149be44..2bda242de2 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -62,6 +62,12 @@ namespace osu.Game.Tests.Visual.Ranking if (beatmapInfo != null) Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo); }); + + AddToggleStep("toggle skin", v => + { + if (skins != null) + skins.CurrentSkinInfo.Value = v ? skins.DefaultClassicSkin.SkinInfo : skins.CurrentSkinInfo.Default; + }); } [SetUp] From b0f6a87a2b6119f82c6219f73363e6c6578d0d2d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 1 Feb 2024 15:35:23 +0300 Subject: [PATCH 2826/4092] Add stress test in TestSceneDashboardOverlay --- .../Online/TestSceneDashboardOverlay.cs | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneDashboardOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneDashboardOverlay.cs index 9407941da6..b6a300322f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDashboardOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDashboardOverlay.cs @@ -2,14 +2,15 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; namespace osu.Game.Tests.Visual.Online { public partial class TestSceneDashboardOverlay : OsuTestScene { - protected override bool UseOnlineAPI => true; - private readonly DashboardOverlay overlay; public TestSceneDashboardOverlay() @@ -17,6 +18,30 @@ namespace osu.Game.Tests.Visual.Online Add(overlay = new DashboardOverlay()); } + [BackgroundDependencyLoader] + private void load() + { + int supportLevel = 0; + + for (int i = 0; i < 1000; i++) + { + supportLevel++; + + if (supportLevel > 3) + supportLevel = 0; + + ((DummyAPIAccess)API).Friends.Add(new APIUser + { + Username = @"peppy", + Id = 2, + Colour = "99EB47", + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + IsSupporter = supportLevel > 0, + SupportLevel = supportLevel + }); + } + } + [Test] public void TestShow() { From 2bd9dcf646127261c62e1a8c913768a9a59d0221 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 1 Feb 2024 16:13:36 +0300 Subject: [PATCH 2827/4092] Rework UserGridPanel to reduce containers nesting --- osu.Game/Users/UserGridPanel.cs | 138 ++++++++++++++------------------ 1 file changed, 62 insertions(+), 76 deletions(-) diff --git a/osu.Game/Users/UserGridPanel.cs b/osu.Game/Users/UserGridPanel.cs index fe3435c248..fce543415d 100644 --- a/osu.Game/Users/UserGridPanel.cs +++ b/osu.Game/Users/UserGridPanel.cs @@ -35,98 +35,84 @@ namespace osu.Game.Users { FillFlowContainer details; - var layout = new Container + var layout = new GridContainer { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(margin), - Child = new GridContainer + ColumnDimensions = new[] { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] + new Dimension(GridSizeMode.AutoSize), + new Dimension() + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension() + }, + Content = new[] + { + new Drawable[] { - new Dimension(GridSizeMode.AutoSize), - new Dimension() - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.Absolute, margin), - new Dimension() - }, - Content = new[] - { - new Drawable[] + CreateAvatar().With(avatar => { - CreateAvatar().With(avatar => + avatar.Size = new Vector2(60); + avatar.Masking = true; + avatar.CornerRadius = 6; + avatar.Margin = new MarginPadding { Bottom = margin }; + }), + new GridContainer + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = margin, Bottom = margin }, + ColumnDimensions = new[] { - avatar.Size = new Vector2(60); - avatar.Masking = true; - avatar.CornerRadius = 6; - }), - new Container + new Dimension() + }, + RowDimensions = new[] { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = margin }, - Child = new GridContainer + new Dimension(GridSizeMode.AutoSize), + new Dimension() + }, + Content = new[] + { + new Drawable[] { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] + details = new FillFlowContainer { - new Dimension() - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension() - }, - Content = new[] - { - new Drawable[] + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(6), + Children = new Drawable[] { - details = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(6), - Children = new Drawable[] - { - CreateFlag(), - // supporter icon is being added later - } - } - }, - new Drawable[] - { - CreateUsername().With(username => - { - username.Anchor = Anchor.CentreLeft; - username.Origin = Anchor.CentreLeft; - }) + CreateFlag(), + // supporter icon is being added later } } + }, + new Drawable[] + { + CreateUsername().With(username => + { + username.Anchor = Anchor.CentreLeft; + username.Origin = Anchor.CentreLeft; + }) } } - }, - new[] - { - // padding - Empty(), - Empty() - }, - new Drawable[] - { - CreateStatusIcon().With(icon => - { - icon.Anchor = Anchor.Centre; - icon.Origin = Anchor.Centre; - }), - CreateStatusMessage(false).With(message => - { - message.Anchor = Anchor.CentreLeft; - message.Origin = Anchor.CentreLeft; - message.Margin = new MarginPadding { Left = margin }; - }) } + }, + new Drawable[] + { + CreateStatusIcon().With(icon => + { + icon.Anchor = Anchor.Centre; + icon.Origin = Anchor.Centre; + }), + CreateStatusMessage(false).With(message => + { + message.Anchor = Anchor.CentreLeft; + message.Origin = Anchor.CentreLeft; + message.Margin = new MarginPadding { Left = margin }; + }) } } }; From 66350fd148f7189535b1c070af9d3321cc9704d3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 1 Feb 2024 16:33:54 +0300 Subject: [PATCH 2828/4092] Refactor UserRankPanel layout --- osu.Game/Users/UserRankPanel.cs | 116 +++++++++++++------------------- 1 file changed, 47 insertions(+), 69 deletions(-) diff --git a/osu.Game/Users/UserRankPanel.cs b/osu.Game/Users/UserRankPanel.cs index a38962dfc7..84ff3114fc 100644 --- a/osu.Game/Users/UserRankPanel.cs +++ b/osu.Game/Users/UserRankPanel.cs @@ -81,117 +81,95 @@ namespace osu.Game.Users }, new GridContainer { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(padding), ColumnDimensions = new[] { - new Dimension(GridSizeMode.Absolute, padding), new Dimension(GridSizeMode.AutoSize), new Dimension(), - new Dimension(GridSizeMode.Absolute, padding), }, RowDimensions = new[] { - new Dimension(GridSizeMode.Absolute, padding), - new Dimension(GridSizeMode.AutoSize), + new Dimension() }, Content = new[] { - new[] + new Drawable[] { - // padding - Empty(), - Empty(), - Empty(), - Empty() - }, - new[] - { - Empty(), // padding CreateAvatar().With(avatar => { avatar.Size = new Vector2(60); avatar.Masking = true; avatar.CornerRadius = 6; }), - new Container + new GridContainer { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Left = padding }, - Child = new GridContainer + ColumnDimensions = new[] { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] + new Dimension() + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension() + }, + Content = new[] + { + new Drawable[] { - new Dimension() - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension() - }, - Content = new[] - { - new Drawable[] + details = new FillFlowContainer { - details = new FillFlowContainer + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(6), + Children = new Drawable[] { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(6), - Children = new Drawable[] - { - CreateFlag(), - // supporter icon is being added later - } + CreateFlag(), + // supporter icon is being added later } - }, - new Drawable[] - { - CreateUsername().With(username => - { - username.Anchor = Anchor.CentreLeft; - username.Origin = Anchor.CentreLeft; - }) } + }, + new Drawable[] + { + CreateUsername().With(username => + { + username.Anchor = Anchor.CentreLeft; + username.Origin = Anchor.CentreLeft; + }) } } - }, - Empty() // padding + } } } } } }, - new Container + new GridContainer { Name = "Bottom content", Margin = new MarginPadding { Top = main_content_height }, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Padding = new MarginPadding { Left = 80, Vertical = padding }, - Child = new GridContainer + ColumnDimensions = new[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - ColumnDimensions = new[] + new Dimension(), + new Dimension() + }, + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, + Content = new[] + { + new Drawable[] { - new Dimension(), - new Dimension() - }, - RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, - Content = new[] - { - new Drawable[] + globalRankDisplay = new ProfileValueDisplay(true) { - globalRankDisplay = new ProfileValueDisplay(true) - { - Title = UsersStrings.ShowRankGlobalSimple, - }, - countryRankDisplay = new ProfileValueDisplay(true) - { - Title = UsersStrings.ShowRankCountrySimple, - } + Title = UsersStrings.ShowRankGlobalSimple, + }, + countryRankDisplay = new ProfileValueDisplay(true) + { + Title = UsersStrings.ShowRankCountrySimple, } } } From 1428cbfbc34f29eb869d8867a5d4f76453fd1cfd Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 1 Feb 2024 16:56:57 +0100 Subject: [PATCH 2829/4092] Remove Masking from PositionSnapGrid This caused issues in rendering the outline of the grid because the outline was getting masked at some resolutions. --- .../Components/LinedPositionSnapGrid.cs | 128 +++++++++++++++--- .../Compose/Components/PositionSnapGrid.cs | 2 - 2 files changed, 106 insertions(+), 24 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs index ebdd76a4e2..8a7f6b5344 100644 --- a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs @@ -15,18 +15,29 @@ namespace osu.Game.Screens.Edit.Compose.Components { protected void GenerateGridLines(Vector2 step, Vector2 drawSize) { + if (Precision.AlmostEquals(step, Vector2.Zero)) + return; + int index = 0; - var currentPosition = StartPosition.Value; // Make lines the same width independent of display resolution. float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; - float lineLength = drawSize.Length * 2; + float rotation = MathHelper.RadiansToDegrees(MathF.Atan2(step.Y, step.X)); List generatedLines = new List(); - while (lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize) || - isMovingTowardsBox(currentPosition, step, drawSize)) + while (true) { + Vector2 currentPosition = StartPosition.Value + index++ * step; + + if (!lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize, out var p1, out var p2)) + { + if (!isMovingTowardsBox(currentPosition, step, drawSize)) + break; + + continue; + } + var gridLine = new Box { Colour = Colour4.White, @@ -34,15 +45,12 @@ namespace osu.Game.Screens.Edit.Compose.Components Origin = Anchor.Centre, RelativeSizeAxes = Axes.None, Width = lineWidth, - Height = lineLength, - Position = currentPosition, - Rotation = MathHelper.RadiansToDegrees(MathF.Atan2(step.Y, step.X)), + Height = Vector2.Distance(p1, p2), + Position = (p1 + p2) / 2, + Rotation = rotation, }; generatedLines.Add(gridLine); - - index += 1; - currentPosition = StartPosition.Value + index * step; } if (generatedLines.Count == 0) @@ -59,23 +67,99 @@ namespace osu.Game.Screens.Edit.Compose.Components (currentPosition + step - box).LengthSquared < (currentPosition - box).LengthSquared; } - private bool lineDefinitelyIntersectsBox(Vector2 lineStart, Vector2 lineDir, Vector2 box) + /// + /// Determines if the line starting at and going in the direction of + /// definitely intersects the box on (0, 0) with the given width and height and returns the intersection points if it does. + /// + /// The start point of the line. + /// The direction of the line. + /// The width and height of the box. + /// The first intersection point. + /// The second intersection point. + /// Whether the line definitely intersects the box. + private bool lineDefinitelyIntersectsBox(Vector2 lineStart, Vector2 lineDir, Vector2 box, out Vector2 p1, out Vector2 p2) { - var p2 = lineStart + lineDir; + p1 = Vector2.Zero; + p2 = Vector2.Zero; - double d1 = det(Vector2.Zero); - double d2 = det(new Vector2(box.X, 0)); - double d3 = det(new Vector2(0, box.Y)); - double d4 = det(box); + if (Precision.AlmostEquals(lineDir.X, 0)) + { + // If the line is vertical, we only need to check if the X coordinate of the line is within the box. + if (!Precision.DefinitelyBigger(lineStart.X, 0) || !Precision.DefinitelyBigger(box.X, lineStart.X)) + return false; - return definitelyDifferentSign(d1, d2) || definitelyDifferentSign(d3, d4) || - definitelyDifferentSign(d1, d3) || definitelyDifferentSign(d2, d4); + p1 = new Vector2(lineStart.X, 0); + p2 = new Vector2(lineStart.X, box.Y); + return true; + } - double det(Vector2 p) => (p.X - lineStart.X) * (p2.Y - lineStart.Y) - (p.Y - lineStart.Y) * (p2.X - lineStart.X); + if (Precision.AlmostEquals(lineDir.Y, 0)) + { + // If the line is horizontal, we only need to check if the Y coordinate of the line is within the box. + if (!Precision.DefinitelyBigger(lineStart.Y, 0) || !Precision.DefinitelyBigger(box.Y, lineStart.Y)) + return false; - bool definitelyDifferentSign(double a, double b) => !Precision.AlmostEquals(a, 0) && - !Precision.AlmostEquals(b, 0) && - Math.Sign(a) != Math.Sign(b); + p1 = new Vector2(0, lineStart.Y); + p2 = new Vector2(box.X, lineStart.Y); + return true; + } + + float m = lineDir.Y / lineDir.X; + float mInv = lineDir.X / lineDir.Y; // Use this to improve numerical stability if X is close to zero. + float b = lineStart.Y - m * lineStart.X; + + // Calculate intersection points with the sides of the box. + var p = new List(4); + + if (0 <= b && b <= box.Y) + p.Add(new Vector2(0, b)); + if (0 <= (box.Y - b) * mInv && (box.Y - b) * mInv <= box.X) + p.Add(new Vector2((box.Y - b) * mInv, box.Y)); + if (0 <= m * box.X + b && m * box.X + b <= box.Y) + p.Add(new Vector2(box.X, m * box.X + b)); + if (0 <= -b * mInv && -b * mInv <= box.X) + p.Add(new Vector2(-b * mInv, 0)); + + switch (p.Count) + { + case 4: + // If there are 4 intersection points, the line is a diagonal of the box. + if (m > 0) + { + p1 = Vector2.Zero; + p2 = box; + } + else + { + p1 = new Vector2(0, box.Y); + p2 = new Vector2(box.X, 0); + } + + break; + + case 3: + // If there are 3 intersection points, the line goes through a corner of the box. + if (p[0] == p[1]) + { + p1 = p[0]; + p2 = p[2]; + } + else + { + p1 = p[0]; + p2 = p[1]; + } + + break; + + case 2: + p1 = p[0]; + p2 = p[1]; + + break; + } + + return !Precision.AlmostEquals(p1, p2); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs index 36687ef73a..e576ac1e49 100644 --- a/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs @@ -21,8 +21,6 @@ namespace osu.Game.Screens.Edit.Compose.Components protected PositionSnapGrid() { - Masking = true; - StartPosition.BindValueChanged(_ => GridCache.Invalidate()); AddLayout(GridCache); From b0095ee54832dc8e00ab4b7491bf482bdfd7225b Mon Sep 17 00:00:00 2001 From: Susko3 Date: Thu, 1 Feb 2024 18:38:18 +0100 Subject: [PATCH 2830/4092] Apply NRT --- osu.Game/Updater/SimpleUpdateManager.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Updater/SimpleUpdateManager.cs b/osu.Game/Updater/SimpleUpdateManager.cs index bc1b0919b8..77de68d63e 100644 --- a/osu.Game/Updater/SimpleUpdateManager.cs +++ b/osu.Game/Updater/SimpleUpdateManager.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using System.Runtime.InteropServices; @@ -22,10 +20,10 @@ namespace osu.Game.Updater /// public partial class SimpleUpdateManager : UpdateManager { - private string version; + private string version = null!; [Resolved] - private GameHost host { get; set; } + private GameHost host { get; set; } = null!; [BackgroundDependencyLoader] private void load(OsuGameBase game) @@ -76,7 +74,7 @@ namespace osu.Game.Updater private string getBestUrl(GitHubRelease release) { - GitHubAsset bestAsset = null; + GitHubAsset? bestAsset = null; switch (RuntimeInfo.OS) { From b9d750cfee9c18a6226c74b0ca5d452279234f5f Mon Sep 17 00:00:00 2001 From: Susko3 Date: Thu, 1 Feb 2024 18:55:34 +0100 Subject: [PATCH 2831/4092] Convert to try-get and don't fall back to release URL --- osu.Game/Updater/SimpleUpdateManager.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game/Updater/SimpleUpdateManager.cs b/osu.Game/Updater/SimpleUpdateManager.cs index 77de68d63e..a7671551ef 100644 --- a/osu.Game/Updater/SimpleUpdateManager.cs +++ b/osu.Game/Updater/SimpleUpdateManager.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.InteropServices; using System.Threading.Tasks; @@ -46,7 +47,7 @@ namespace osu.Game.Updater version = version.Split('-').First(); string latestTagName = latest.TagName.Split('-').First(); - if (latestTagName != version) + if (latestTagName != version && tryGetBestUrl(latest, out string? url)) { Notifications.Post(new SimpleNotification { @@ -55,7 +56,7 @@ namespace osu.Game.Updater Icon = FontAwesome.Solid.Download, Activated = () => { - host.OpenUrlExternally(getBestUrl(latest)); + host.OpenUrlExternally(url); return true; } }); @@ -72,8 +73,9 @@ namespace osu.Game.Updater return false; } - private string getBestUrl(GitHubRelease release) + private bool tryGetBestUrl(GitHubRelease release, [NotNullWhen(true)] out string? url) { + url = null; GitHubAsset? bestAsset = null; switch (RuntimeInfo.OS) @@ -94,15 +96,18 @@ namespace osu.Game.Updater case RuntimeInfo.Platform.iOS: // iOS releases are available via testflight. this link seems to work well enough for now. // see https://stackoverflow.com/a/32960501 - return "itms-beta://beta.itunes.apple.com/v1/app/1447765923"; + url = "itms-beta://beta.itunes.apple.com/v1/app/1447765923"; + break; case RuntimeInfo.Platform.Android: - // on our testing device this causes the download to magically disappear. + // on our testing device using the .apk URL causes the download to magically disappear. //bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".apk")); + url = release.HtmlUrl; break; } - return bestAsset?.BrowserDownloadUrl ?? release.HtmlUrl; + url ??= bestAsset?.BrowserDownloadUrl; + return url != null; } } } From f92751a7d29baf4bdc81d60725e00b8b7bb7db02 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Thu, 1 Feb 2024 18:56:38 +0100 Subject: [PATCH 2832/4092] Don't suggest an update on mobile if it isn't available --- osu.Game/Updater/SimpleUpdateManager.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/Updater/SimpleUpdateManager.cs b/osu.Game/Updater/SimpleUpdateManager.cs index a7671551ef..0f9d5b929f 100644 --- a/osu.Game/Updater/SimpleUpdateManager.cs +++ b/osu.Game/Updater/SimpleUpdateManager.cs @@ -94,15 +94,18 @@ namespace osu.Game.Updater break; case RuntimeInfo.Platform.iOS: - // iOS releases are available via testflight. this link seems to work well enough for now. - // see https://stackoverflow.com/a/32960501 - url = "itms-beta://beta.itunes.apple.com/v1/app/1447765923"; + if (release.Assets?.Exists(f => f.Name.EndsWith(".ipa", StringComparison.Ordinal)) == true) + // iOS releases are available via testflight. this link seems to work well enough for now. + // see https://stackoverflow.com/a/32960501 + url = "itms-beta://beta.itunes.apple.com/v1/app/1447765923"; + break; case RuntimeInfo.Platform.Android: - // on our testing device using the .apk URL causes the download to magically disappear. - //bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".apk")); - url = release.HtmlUrl; + if (release.Assets?.Exists(f => f.Name.EndsWith(".apk", StringComparison.Ordinal)) == true) + // on our testing device using the .apk URL causes the download to magically disappear. + url = release.HtmlUrl; + break; } From 966093f7cea7097e9095d5595cc8401e317b3fde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 31 Jan 2024 15:50:43 +0100 Subject: [PATCH 2833/4092] Transform score multiplier display to also show ranked state --- .../TestSceneModSelectOverlay.cs | 8 +- ... => TestSceneRankingInformationDisplay.cs} | 18 ++-- .../Localisation/ModSelectOverlayStrings.cs | 20 ++++ osu.Game/Overlays/Mods/ModSelectOverlay.cs | 19 ++-- ...isplay.cs => RankingInformationDisplay.cs} | 95 ++++++++++++------- 5 files changed, 105 insertions(+), 55 deletions(-) rename osu.Game.Tests/Visual/UserInterface/{TestSceneScoreMultiplierDisplay.cs => TestSceneRankingInformationDisplay.cs} (50%) rename osu.Game/Overlays/Mods/{ScoreMultiplierDisplay.cs => RankingInformationDisplay.cs} (60%) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 046954db47..99a5897dff 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -119,7 +119,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("mod multiplier correct", () => { double multiplier = SelectedMods.Value.Aggregate(1d, (m, mod) => m * mod.ScoreMultiplier); - return Precision.AlmostEquals(multiplier, modSelectOverlay.ChildrenOfType().Single().Current.Value); + return Precision.AlmostEquals(multiplier, modSelectOverlay.ChildrenOfType().Single().ModMultiplier.Value); }); assertCustomisationToggleState(disabled: false, active: false); AddAssert("setting items created", () => modSelectOverlay.ChildrenOfType().Any()); @@ -134,7 +134,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("mod multiplier correct", () => { double multiplier = SelectedMods.Value.Aggregate(1d, (m, mod) => m * mod.ScoreMultiplier); - return Precision.AlmostEquals(multiplier, modSelectOverlay.ChildrenOfType().Single().Current.Value); + return Precision.AlmostEquals(multiplier, modSelectOverlay.ChildrenOfType().Single().ModMultiplier.Value); }); assertCustomisationToggleState(disabled: false, active: false); AddAssert("setting items created", () => modSelectOverlay.ChildrenOfType().Any()); @@ -846,7 +846,7 @@ namespace osu.Game.Tests.Visual.UserInterface InputManager.Click(MouseButton.Left); }); AddAssert("difficulty multiplier display shows correct value", - () => modSelectOverlay.ChildrenOfType().Single().Current.Value, () => Is.EqualTo(0.1).Within(Precision.DOUBLE_EPSILON)); + () => modSelectOverlay.ChildrenOfType().Single().ModMultiplier.Value, () => Is.EqualTo(0.1).Within(Precision.DOUBLE_EPSILON)); // this is highly unorthodox in a test, but because the `ModSettingChangeTracker` machinery heavily leans on events and object disposal and re-creation, // it is instrumental in the reproduction of the failure scenario that this test is supposed to cover. @@ -856,7 +856,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("reset half time speed to default", () => modSelectOverlay.ChildrenOfType().Single() .ChildrenOfType>().Single().TriggerClick()); AddUntilStep("difficulty multiplier display shows correct value", - () => modSelectOverlay.ChildrenOfType().Single().Current.Value, () => Is.EqualTo(0.3).Within(Precision.DOUBLE_EPSILON)); + () => modSelectOverlay.ChildrenOfType().Single().ModMultiplier.Value, () => Is.EqualTo(0.3).Within(Precision.DOUBLE_EPSILON)); } private void waitForColumnLoad() => AddUntilStep("all column content loaded", () => diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneScoreMultiplierDisplay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneRankingInformationDisplay.cs similarity index 50% rename from osu.Game.Tests/Visual/UserInterface/TestSceneScoreMultiplierDisplay.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneRankingInformationDisplay.cs index c2ddd814b7..42f243cc21 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneScoreMultiplierDisplay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneRankingInformationDisplay.cs @@ -11,7 +11,7 @@ using osu.Game.Overlays.Mods; namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public partial class TestSceneScoreMultiplierDisplay : OsuTestScene + public partial class TestSceneRankingInformationDisplay : OsuTestScene { [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); @@ -19,22 +19,24 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestBasic() { - ScoreMultiplierDisplay multiplierDisplay = null!; + RankingInformationDisplay onlinePropertiesDisplay = null!; - AddStep("create content", () => Child = multiplierDisplay = new ScoreMultiplierDisplay + AddStep("create content", () => Child = onlinePropertiesDisplay = new RankingInformationDisplay { Anchor = Anchor.Centre, Origin = Anchor.Centre }); - AddStep("set multiplier below 1", () => multiplierDisplay.Current.Value = 0.5); - AddStep("set multiplier to 1", () => multiplierDisplay.Current.Value = 1); - AddStep("set multiplier above 1", () => multiplierDisplay.Current.Value = 1.5); + AddToggleStep("toggle ranked", ranked => onlinePropertiesDisplay.Ranked.Value = ranked); + + AddStep("set multiplier below 1", () => onlinePropertiesDisplay.ModMultiplier.Value = 0.5); + AddStep("set multiplier to 1", () => onlinePropertiesDisplay.ModMultiplier.Value = 1); + AddStep("set multiplier above 1", () => onlinePropertiesDisplay.ModMultiplier.Value = 1.5); AddSliderStep("set multiplier", 0, 2, 1d, multiplier => { - if (multiplierDisplay.IsNotNull()) - multiplierDisplay.Current.Value = multiplier; + if (onlinePropertiesDisplay.IsNotNull()) + onlinePropertiesDisplay.ModMultiplier.Value = multiplier; }); } } diff --git a/osu.Game/Localisation/ModSelectOverlayStrings.cs b/osu.Game/Localisation/ModSelectOverlayStrings.cs index 86ebebd293..9513eacf02 100644 --- a/osu.Game/Localisation/ModSelectOverlayStrings.cs +++ b/osu.Game/Localisation/ModSelectOverlayStrings.cs @@ -49,6 +49,26 @@ namespace osu.Game.Localisation /// public static LocalisableString ScoreMultiplier => new TranslatableString(getKey(@"score_multiplier"), @"Score Multiplier"); + /// + /// "Ranked" + /// + public static LocalisableString Ranked => new TranslatableString(getKey(@"ranked"), @"Ranked"); + + /// + /// "Performance points can be granted for the active mods." + /// + public static LocalisableString RankedExplanation => new TranslatableString(getKey(@"ranked_explanation"), @"Performance points can be granted for the active mods."); + + /// + /// "Unranked" + /// + public static LocalisableString Unranked => new TranslatableString(getKey(@"unranked"), @"Unranked"); + + /// + /// "Performance points will not be granted due to active mods." + /// + public static LocalisableString UnrankedExplanation => new TranslatableString(getKey(@"ranked_explanation"), @"Performance points will not be granted due to active mods."); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 7271c53e7a..ddf96c1cb3 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -125,7 +125,7 @@ namespace osu.Game.Overlays.Mods private DeselectAllModsButton deselectAllModsButton = null!; private Container aboveColumnsContent = null!; - private ScoreMultiplierDisplay? multiplierDisplay; + private RankingInformationDisplay? rankingInformationDisplay; private BeatmapAttributesDisplay? beatmapAttributesDisplay; protected ShearedButton BackButton { get; private set; } = null!; @@ -185,7 +185,7 @@ namespace osu.Game.Overlays.Mods aboveColumnsContent = new Container { RelativeSizeAxes = Axes.X, - Height = ScoreMultiplierDisplay.HEIGHT, + Height = RankingInformationDisplay.HEIGHT, Padding = new MarginPadding { Horizontal = 100 }, Child = SearchTextBox = new ShearedSearchTextBox { @@ -200,7 +200,7 @@ namespace osu.Game.Overlays.Mods { Padding = new MarginPadding { - Top = ScoreMultiplierDisplay.HEIGHT + PADDING, + Top = RankingInformationDisplay.HEIGHT + PADDING, Bottom = PADDING }, RelativeSizeAxes = Axes.Both, @@ -269,7 +269,7 @@ namespace osu.Game.Overlays.Mods }, Children = new Drawable[] { - multiplierDisplay = new ScoreMultiplierDisplay + rankingInformationDisplay = new RankingInformationDisplay { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight @@ -315,7 +315,7 @@ namespace osu.Game.Overlays.Mods SelectedMods.BindValueChanged(_ => { - updateMultiplier(); + updateRankingInformation(); updateFromExternalSelection(); updateCustomisation(); @@ -328,7 +328,7 @@ namespace osu.Game.Overlays.Mods // // See https://github.com/ppy/osu/pull/23284#issuecomment-1529056988 modSettingChangeTracker = new ModSettingChangeTracker(SelectedMods.Value); - modSettingChangeTracker.SettingChanged += _ => updateMultiplier(); + modSettingChangeTracker.SettingChanged += _ => updateRankingInformation(); } }, true); @@ -450,9 +450,9 @@ namespace osu.Game.Overlays.Mods modState.ValidForSelection.Value = modState.Mod.Type != ModType.System && modState.Mod.HasImplementation && IsValidMod.Invoke(modState.Mod); } - private void updateMultiplier() + private void updateRankingInformation() { - if (multiplierDisplay == null) + if (rankingInformationDisplay == null) return; double multiplier = 1.0; @@ -460,7 +460,8 @@ namespace osu.Game.Overlays.Mods foreach (var mod in SelectedMods.Value) multiplier *= mod.ScoreMultiplier; - multiplierDisplay.Current.Value = multiplier; + rankingInformationDisplay.ModMultiplier.Value = multiplier; + rankingInformationDisplay.Ranked.Value = SelectedMods.Value.All(m => m.Ranked); } private void updateCustomisation() diff --git a/osu.Game/Overlays/Mods/ScoreMultiplierDisplay.cs b/osu.Game/Overlays/Mods/RankingInformationDisplay.cs similarity index 60% rename from osu.Game/Overlays/Mods/ScoreMultiplierDisplay.cs rename to osu.Game/Overlays/Mods/RankingInformationDisplay.cs index a86eba81e4..494f8a377f 100644 --- a/osu.Game/Overlays/Mods/ScoreMultiplierDisplay.cs +++ b/osu.Game/Overlays/Mods/RankingInformationDisplay.cs @@ -6,8 +6,8 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -22,15 +22,13 @@ namespace osu.Game.Overlays.Mods /// /// On the mod select overlay, this provides a local updating view of the aggregate score multiplier coming from mods. /// - public partial class ScoreMultiplierDisplay : ModFooterInformationDisplay, IHasCurrentValue + public partial class RankingInformationDisplay : ModFooterInformationDisplay { public const float HEIGHT = 42; - public Bindable Current - { - get => current.Current; - set => current.Current = value; - } + public Bindable ModMultiplier = new BindableDouble(1); + + public Bindable Ranked { get; } = new BindableBool(true); private readonly BindableWithCurrent current = new BindableWithCurrent(); @@ -39,16 +37,11 @@ namespace osu.Game.Overlays.Mods private RollingCounter counter = null!; private Box flashLayer = null!; + private TextWithTooltip rankedText = null!; [Resolved] private OsuColour colours { get; set; } = null!; - public ScoreMultiplierDisplay() - { - Current.Default = 1d; - Current.Value = 1d; - } - [BackgroundDependencyLoader] private void load() { @@ -75,13 +68,20 @@ namespace osu.Game.Overlays.Mods LeftContent.AddRange(new Drawable[] { - new OsuSpriteText + new Container { + Width = 50, + RelativeSizeAxes = Axes.Y, + Margin = new MarginPadding(10), Anchor = Anchor.Centre, Origin = Anchor.Centre, - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), - Text = ModSelectOverlayStrings.ScoreMultiplier, - Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold) + Child = rankedText = new TextWithTooltip + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), + Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold) + } } }); @@ -97,7 +97,7 @@ namespace osu.Game.Overlays.Mods Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), Anchor = Anchor.Centre, Origin = Anchor.Centre, - Current = { BindTarget = Current } + Current = { BindTarget = ModMultiplier } } }); } @@ -106,30 +106,22 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); - Current.BindValueChanged(e => + ModMultiplier.BindValueChanged(e => { - if (e.NewValue > Current.Default) + if (e.NewValue > ModMultiplier.Default) { - MainBackground - .FadeColour(colours.ForModType(ModType.DifficultyIncrease), transition_duration, Easing.OutQuint); - counter.FadeColour(ColourProvider.Background5, transition_duration, Easing.OutQuint); + counter.FadeColour(colours.ForModType(ModType.DifficultyIncrease), transition_duration, Easing.OutQuint); } - else if (e.NewValue < Current.Default) + else if (e.NewValue < ModMultiplier.Default) { - MainBackground - .FadeColour(colours.ForModType(ModType.DifficultyReduction), transition_duration, Easing.OutQuint); - counter.FadeColour(ColourProvider.Background5, transition_duration, Easing.OutQuint); + counter.FadeColour(colours.ForModType(ModType.DifficultyReduction), transition_duration, Easing.OutQuint); } else { - MainBackground.FadeColour(ColourProvider.Background4, transition_duration, Easing.OutQuint); counter.FadeColour(Colour4.White, transition_duration, Easing.OutQuint); } - flashLayer - .FadeOutFromOne() - .FadeTo(0.15f, 60, Easing.OutQuint) - .Then().FadeOut(500, Easing.OutQuint); + flash(); const float move_amount = 4; if (e.NewValue > e.OldValue) @@ -140,10 +132,43 @@ namespace osu.Game.Overlays.Mods // required to prevent the counter initially rolling up from 0 to 1 // due to `Current.Value` having a nonstandard default value of 1. - counter.SetCountWithoutRolling(Current.Value); + counter.SetCountWithoutRolling(ModMultiplier.Value); + + Ranked.BindValueChanged(e => + { + flash(); + + if (e.NewValue) + { + rankedText.Text = ModSelectOverlayStrings.Ranked; + rankedText.TooltipText = ModSelectOverlayStrings.RankedExplanation; + rankedText.FadeColour(Colour4.White, transition_duration, Easing.OutQuint); + FrontBackground.FadeColour(ColourProvider.Background3, transition_duration, Easing.OutQuint); + } + else + { + rankedText.Text = ModSelectOverlayStrings.Unranked; + rankedText.TooltipText = ModSelectOverlayStrings.UnrankedExplanation; + rankedText.FadeColour(ColourProvider.Background5, transition_duration, Easing.OutQuint); + FrontBackground.FadeColour(colours.Orange1, transition_duration, Easing.OutQuint); + } + }, true); } - private partial class EffectCounter : RollingCounter + private void flash() + { + flashLayer + .FadeOutFromOne() + .FadeTo(0.15f, 60, Easing.OutQuint) + .Then().FadeOut(500, Easing.OutQuint); + } + + private partial class TextWithTooltip : OsuSpriteText, IHasTooltip + { + public LocalisableString TooltipText { get; set; } + } + + private partial class EffectCounter : RollingCounter, IHasTooltip { protected override double RollingDuration => 250; @@ -155,6 +180,8 @@ namespace osu.Game.Overlays.Mods Origin = Anchor.Centre, Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold) }; + + public LocalisableString TooltipText => ModSelectOverlayStrings.ScoreMultiplier; } } } From f87ab197318b08e91757b82ea5898de19487b495 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 1 Feb 2024 21:22:36 +0100 Subject: [PATCH 2834/4092] Apply NRT to `FooterButtonMods` --- osu.Game/Screens/Select/FooterButtonMods.cs | 47 ++++++++++----------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index 69782c25bb..57784dd6ca 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -1,15 +1,12 @@ // 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.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Screens.Play.HUD; using osu.Game.Rulesets.Mods; using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics.UserInterface; @@ -31,28 +28,14 @@ namespace osu.Game.Screens.Select set => modDisplay.Current = value; } - protected readonly OsuSpriteText MultiplierText; - private readonly ModDisplay modDisplay; + protected OsuSpriteText MultiplierText { get; private set; } = null!; + private ModDisplay modDisplay = null!; + + private ModSettingChangeTracker? modSettingChangeTracker; + private Color4 lowMultiplierColour; private Color4 highMultiplierColour; - public FooterButtonMods() - { - ButtonContentContainer.Add(modDisplay = new ModDisplay - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(0.8f), - ExpansionMode = ExpansionMode.AlwaysContracted, - }); - ButtonContentContainer.Add(MultiplierText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.GetFont(weight: FontWeight.Bold), - }); - } - [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -62,10 +45,24 @@ namespace osu.Game.Screens.Select highMultiplierColour = colours.Green; Text = @"mods"; Hotkey = GlobalAction.ToggleModSelection; - } - [CanBeNull] - private ModSettingChangeTracker modSettingChangeTracker; + ButtonContentContainer.AddRange(new Drawable[] + { + modDisplay = new ModDisplay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(0.8f), + ExpansionMode = ExpansionMode.AlwaysContracted, + }, + MultiplierText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(weight: FontWeight.Bold), + } + }); + } protected override void LoadComplete() { From 865f4d76fff5fe0cff3d7c2d6cf2aae95c99ffac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 1 Feb 2024 21:51:41 +0100 Subject: [PATCH 2835/4092] Add unranked indicator to song select footer too --- .../TestSceneFooterButtonMods.cs | 11 ++++ osu.Game/Screens/Select/FooterButtonMods.cs | 58 ++++++++++++++++--- 2 files changed, 60 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs index a95bb2c9e3..b79ce6c75f 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; @@ -67,6 +68,15 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert(@"Check empty multiplier", () => assertModsMultiplier(Array.Empty())); } + [Test] + public void TestUnrankedBadge() + { + AddStep(@"Add unranked mod", () => changeMods(new[] { new OsuModDeflate() })); + AddAssert("Unranked badge shown", () => footerButtonMods.UnrankedBadge.Alpha == 1); + AddStep(@"Clear selected mod", () => changeMods(Array.Empty())); + AddAssert("Unranked badge not shown", () => footerButtonMods.UnrankedBadge.Alpha == 0); + } + private void changeMods(IReadOnlyList mods) { footerButtonMods.Current.Value = mods; @@ -83,6 +93,7 @@ namespace osu.Game.Tests.Visual.UserInterface private partial class TestFooterButtonMods : FooterButtonMods { public new OsuSpriteText MultiplierText => base.MultiplierText; + public new Drawable UnrankedBadge => base.UnrankedBadge; } } } diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index 57784dd6ca..5685910c0a 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -9,6 +9,9 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Game.Configuration; using osu.Game.Graphics; @@ -16,6 +19,7 @@ using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; using osu.Game.Input.Bindings; +using osu.Game.Localisation; using osu.Game.Utils; namespace osu.Game.Screens.Select @@ -29,13 +33,27 @@ namespace osu.Game.Screens.Select } protected OsuSpriteText MultiplierText { get; private set; } = null!; - private ModDisplay modDisplay = null!; + protected Container UnrankedBadge { get; private set; } = null!; + + private readonly ModDisplay modDisplay; private ModSettingChangeTracker? modSettingChangeTracker; private Color4 lowMultiplierColour; private Color4 highMultiplierColour; + public FooterButtonMods() + { + // must be created in ctor for correct operation of `Current`. + modDisplay = new ModDisplay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(0.8f), + ExpansionMode = ExpansionMode.AlwaysContracted, + }; + } + [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -48,19 +66,38 @@ namespace osu.Game.Screens.Select ButtonContentContainer.AddRange(new Drawable[] { - modDisplay = new ModDisplay - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(0.8f), - ExpansionMode = ExpansionMode.AlwaysContracted, - }, + modDisplay, MultiplierText = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, Font = OsuFont.GetFont(weight: FontWeight.Bold), - } + }, + UnrankedBadge = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = colours.Yellow, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = colours.Gray2, + Padding = new MarginPadding(5), + UseFullGlyphHeight = false, + Text = ModSelectOverlayStrings.Unranked.ToLower() + } + } + }, }); } @@ -98,6 +135,9 @@ namespace osu.Game.Screens.Select modDisplay.FadeIn(); else modDisplay.FadeOut(); + + bool anyUnrankedMods = Current.Value?.Any(m => !m.Ranked) == true; + UnrankedBadge.FadeTo(anyUnrankedMods ? 1 : 0); }); } } From c114fd8f8963f28b06df951145486689ad0084db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 1 Feb 2024 22:28:49 +0100 Subject: [PATCH 2836/4092] Allow pp for Sudden Death and Perfect regardless of "restart on fail" setting Closes https://github.com/ppy/osu/issues/26844. --- osu.Game/Rulesets/Mods/ModPerfect.cs | 2 +- osu.Game/Rulesets/Mods/ModSuddenDeath.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModPerfect.cs b/osu.Game/Rulesets/Mods/ModPerfect.cs index f8f498ceb5..5bedf443da 100644 --- a/osu.Game/Rulesets/Mods/ModPerfect.cs +++ b/osu.Game/Rulesets/Mods/ModPerfect.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.DifficultyIncrease; public override double ScoreMultiplier => 1; public override LocalisableString Description => "SS or quit."; - public override bool Ranked => UsesDefaultConfiguration; + public override bool Ranked => true; public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(ModSuddenDeath), typeof(ModAccuracyChallenge) }).ToArray(); diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs index 62579a168c..d07ff6ce87 100644 --- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs +++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.DifficultyIncrease; public override LocalisableString Description => "Miss and fail."; public override double ScoreMultiplier => 1; - public override bool Ranked => UsesDefaultConfiguration; + public override bool Ranked => true; public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModPerfect)).ToArray(); From 96f66aaa2e4eaa44c167e32af51fde84ad1e6a27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 1 Feb 2024 22:30:26 +0100 Subject: [PATCH 2837/4092] Allow pp for Accuracy Challenge Addresses https://github.com/ppy/osu/discussions/26919. --- osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs b/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs index 0072c21053..9570cddb0a 100644 --- a/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs +++ b/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs @@ -31,6 +31,8 @@ namespace osu.Game.Rulesets.Mods public override bool RequiresConfiguration => false; + public override bool Ranked => true; + public override string SettingDescription => base.SettingDescription.Replace(MinimumAccuracy.ToString(), MinimumAccuracy.Value.ToString("##%", NumberFormatInfo.InvariantInfo)); [SettingSource("Minimum accuracy", "Trigger a failure if your accuracy goes below this value.", SettingControlType = typeof(SettingsPercentageSlider))] From ea76f7a5d88a3060369db0529330c3c28ea35752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 1 Feb 2024 22:33:49 +0100 Subject: [PATCH 2838/4092] Allow pp for Double/Half Time's "adjust pitch" setting --- osu.Game/Rulesets/Mods/ModDoubleTime.cs | 2 +- osu.Game/Rulesets/Mods/ModHalfTime.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index 8e430da368..fd5120a767 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => OsuIcon.ModDoubleTime; public override ModType Type => ModType.DifficultyIncrease; public override LocalisableString Description => "Zoooooooooom..."; - public override bool Ranked => UsesDefaultConfiguration; + public override bool Ranked => SpeedChange.IsDefault; [SettingSource("Speed increase", "The actual increase to apply", SettingControlType = typeof(MultiplierSettingsSlider))] public override BindableNumber SpeedChange { get; } = new BindableDouble(1.5) diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index 59e40ee9cc..efdf0d6358 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => OsuIcon.ModHalftime; public override ModType Type => ModType.DifficultyReduction; public override LocalisableString Description => "Less zoom..."; - public override bool Ranked => UsesDefaultConfiguration; + public override bool Ranked => SpeedChange.IsDefault; [SettingSource("Speed decrease", "The actual decrease to apply", SettingControlType = typeof(MultiplierSettingsSlider))] public override BindableNumber SpeedChange { get; } = new BindableDouble(0.75) From 4dbcb41dd1e8d46e2bdee4acc9c6f858f979dd79 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 2 Feb 2024 00:44:51 +0300 Subject: [PATCH 2839/4092] Remove unused parameter --- osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 5de5ceb2a8..e5ba9500ee 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -137,7 +137,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy } [BackgroundDependencyLoader] - private void load(AudioManager audio) + private void load() { InternalChildren = new Drawable[] { From 5a1b0004db37148191ccbc349b8bd6fe4ca9e502 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 2 Feb 2024 01:17:28 +0300 Subject: [PATCH 2840/4092] Add failing test case --- .../Visual/Gameplay/TestScenePlayerLoader.cs | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index f97372e9b6..b2e607d9a3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -24,9 +24,11 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using osu.Game.Screens.Menu; using osu.Game.Screens.Play; using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Utils; +using osuTK; using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay @@ -53,6 +55,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached] private readonly VolumeOverlay volumeOverlay; + [Cached] + private readonly OsuLogo logo; + [Cached(typeof(BatteryInfo))] private readonly LocalBatteryInfo batteryInfo = new LocalBatteryInfo(); @@ -76,7 +81,14 @@ namespace osu.Game.Tests.Visual.Gameplay Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, }, - changelogOverlay = new ChangelogOverlay() + changelogOverlay = new ChangelogOverlay(), + logo = new OsuLogo + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Scale = new Vector2(0.5f), + Position = new Vector2(128f), + }, }); } @@ -204,6 +216,36 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("loads after idle", () => !loader.IsCurrentScreen()); } + [Test] + public void TestLoadNotBlockedOnOsuLogo() + { + AddStep("load dummy beatmap", () => resetPlayer(false)); + AddUntilStep("wait for current", () => loader.IsCurrentScreen()); + + AddUntilStep("wait for load ready", () => + { + moveMouse(); + return player?.LoadState == LoadState.Ready; + }); + + // move mouse in logo while waiting for load to still proceed (it shouldn't be blocked when hovering logo). + AddUntilStep("move mouse in logo", () => + { + moveMouse(); + return !loader.IsCurrentScreen(); + }); + + void moveMouse() + { + notificationOverlay.State.Value = Visibility.Hidden; + + InputManager.MoveMouseTo( + logo.ScreenSpaceDrawQuad.TopLeft + + (logo.ScreenSpaceDrawQuad.BottomRight - logo.ScreenSpaceDrawQuad.TopLeft) + * RNG.NextSingle(0.3f, 0.7f)); + } + } + [Test] public void TestLoadContinuation() { From 0502997ae958e0697fcdbcad2555e80c52b6a22d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 2 Feb 2024 01:02:13 +0300 Subject: [PATCH 2841/4092] Stop blocking player loader when hovering over osu! logo --- osu.Game/Screens/Play/PlayerLoader.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 232de53ac3..201511529e 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -106,8 +106,8 @@ namespace osu.Game.Screens.Play && ReadyForGameplay; protected virtual bool ReadyForGameplay => - // not ready if the user is hovering one of the panes, unless they are idle. - (IsHovered || idleTracker.IsIdle.Value) + // not ready if the user is hovering one of the panes (logo is excluded), unless they are idle. + (IsHovered || osuLogo?.IsHovered == true || idleTracker.IsIdle.Value) // not ready if the user is dragging a slider or otherwise. && inputManager.DraggedDrawable == null // not ready if a focused overlay is visible, like settings. @@ -306,10 +306,14 @@ namespace osu.Game.Screens.Play return base.OnExiting(e); } + private OsuLogo? osuLogo; + protected override void LogoArriving(OsuLogo logo, bool resuming) { base.LogoArriving(logo, resuming); + osuLogo = logo; + const double duration = 300; if (!resuming) logo.MoveTo(new Vector2(0.5f), duration, Easing.OutQuint); @@ -328,6 +332,7 @@ namespace osu.Game.Screens.Play { base.LogoExiting(logo); content.StopTracking(); + osuLogo = null; } protected override void LogoSuspending(OsuLogo logo) @@ -338,6 +343,8 @@ namespace osu.Game.Screens.Play logo .FadeOut(CONTENT_OUT_DURATION / 2, Easing.OutQuint) .ScaleTo(logo.Scale * 0.8f, CONTENT_OUT_DURATION * 2, Easing.OutQuint); + + osuLogo = null; } #endregion From e00e583bb4fbe58c8c26c847d5c2435d86f6a75d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 2 Feb 2024 03:56:38 +0300 Subject: [PATCH 2842/4092] Fix DrawableManiaRuleset performing skin lookup every frame --- .../UI/DrawableManiaRuleset.cs | 43 ++++++++++++++++--- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index bea536e4af..070021ef74 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -9,8 +9,10 @@ using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Input; +using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Input.Handlers; @@ -59,8 +61,7 @@ namespace osu.Game.Rulesets.Mania.UI // Stores the current speed adjustment active in gameplay. private readonly Track speedAdjustmentTrack = new TrackVirtual(0); - [Resolved] - private ISkinSource skin { get; set; } + private ISkinSource currentSkin; public DrawableManiaRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) : base(ruleset, beatmap, mods) @@ -72,8 +73,12 @@ namespace osu.Game.Rulesets.Mania.UI } [BackgroundDependencyLoader] - private void load() + private void load(ISkinSource source) { + currentSkin = source; + currentSkin.SourceChanged += onSkinChange; + skinChanged(); + foreach (var mod in Mods.OfType()) mod.ApplyToTrack(speedAdjustmentTrack); @@ -109,12 +114,28 @@ namespace osu.Game.Rulesets.Mania.UI updateTimeRange(); } + private ScheduledDelegate pendingSkinChange; + private float hitPosition; + + private void onSkinChange() + { + // schedule required to avoid calls after disposed. + // note that this has the side-effect of components only performing a skin change when they are alive. + pendingSkinChange?.Cancel(); + pendingSkinChange = Scheduler.Add(skinChanged); + } + + private void skinChanged() + { + hitPosition = currentSkin.GetConfig( + new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.HitPosition))?.Value + ?? Stage.HIT_TARGET_POSITION; + + pendingSkinChange = null; + } + private void updateTimeRange() { - float hitPosition = skin.GetConfig( - new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.HitPosition))?.Value - ?? Stage.HIT_TARGET_POSITION; - const float length_to_default_hit_position = 768 - LegacyManiaSkinConfiguration.DEFAULT_HIT_POSITION; float lengthToHitPosition = 768 - hitPosition; @@ -144,5 +165,13 @@ namespace osu.Game.Rulesets.Mania.UI protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay); protected override ReplayRecorder CreateReplayRecorder(Score score) => new ManiaReplayRecorder(score); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (currentSkin.IsNotNull()) + currentSkin.SourceChanged -= onSkinChange; + } } } From 7884e5808ad0407449d4895b6b06594755cc7b65 Mon Sep 17 00:00:00 2001 From: Loreos7 Date: Fri, 2 Feb 2024 04:47:13 +0300 Subject: [PATCH 2843/4092] localise remaining strings --- ...RunOverlayImportFromStableScreenStrings.cs | 30 +++++++++++++++++++ .../FirstRunSetup/ScreenImportFromStable.cs | 21 ++++++++----- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs b/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs index f0620245c3..bbd83d2395 100644 --- a/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs +++ b/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs @@ -51,6 +51,36 @@ namespace osu.Game.Localisation /// public static LocalisableString Items(int arg0) => new TranslatableString(getKey(@"items"), @"{0} item(s)", arg0); + /// + /// "Data migration will use "hard links". No extra disk space will be used, and you can delete either data folder at any point without affecting the other installation." + /// + public static LocalisableString DataMigrationNoExtraSpace => new TranslatableString(getKey(@"data_migration_no_extra_space"), @"Data migration will use ""hard links"". No extra disk space will be used, and you can delete either data folder at any point without affecting the other installation."); + + /// + /// "Learn more about how "hard links" work" + /// + public static LocalisableString LearnAboutHardLinks => new TranslatableString(getKey(@"learn_about_hard_links"), @"Learn more about how ""hard links"" work"); + + /// + /// "Lightweight linking of files is not supported on your operating system yet, so a copy of all files will be made during import." + /// + public static LocalisableString LightweightLinkingNotSupported => new TranslatableString(getKey(@"lightweight_linking_not_supported"), @"Lightweight linking of files is not supported on your operating system yet, so a copy of all files will be made during import."); + + /// + /// "A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install {0}." + /// + public static LocalisableString SecondCopyWillBeMade(LocalisableString extra) => new TranslatableString(getKey(@"second_copy_will_be_made"), @"A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install {0}.", extra); + + /// + /// "(and the file system is NTFS)" + /// + public static LocalisableString ToAvoidEnsureNtfs => new TranslatableString(getKey(@"to_avoid_ensure_ntfs"), @"(and the file system is NTFS)"); + + /// + /// "(and the file system supports hard links)" + /// + public static LocalisableString ToAvoidEnsureHardLinksSupport => new TranslatableString(getKey(@"to_avoid_ensure_hard_links_support"), @"(and the file system supports hard links)"); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 24ac5e72e8..0b2b750136 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -127,18 +127,16 @@ namespace osu.Game.Overlays.FirstRunSetup if (available) { - copyInformation.Text = - "Data migration will use \"hard links\". No extra disk space will be used, and you can delete either data folder at any point without affecting the other installation. "; - - copyInformation.AddLink("Learn more about how \"hard links\" work", LinkAction.OpenWiki, @"Client/Release_stream/Lazer/File_storage#via-hard-links"); + copyInformation.Text = FirstRunOverlayImportFromStableScreenStrings.DataMigrationNoExtraSpace; + copyInformation.AddLink(FirstRunOverlayImportFromStableScreenStrings.LearnAboutHardLinks, LinkAction.OpenWiki, @"Client/Release_stream/Lazer/File_storage#via-hard-links"); } else if (!RuntimeInfo.IsDesktop) - copyInformation.Text = "Lightweight linking of files is not supported on your operating system yet, so a copy of all files will be made during import."; + copyInformation.Text = FirstRunOverlayImportFromStableScreenStrings.LightweightLinkingNotSupported; else { copyInformation.Text = RuntimeInfo.OS == RuntimeInfo.Platform.Windows - ? "A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install (and the file system is NTFS). " - : "A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install (and the file system supports hard links). "; + ? FirstRunOverlayImportFromStableScreenStrings.SecondCopyWillBeMade(FirstRunOverlayImportFromStableScreenStrings.ToAvoidEnsureNtfs) + : FirstRunOverlayImportFromStableScreenStrings.SecondCopyWillBeMade(FirstRunOverlayImportFromStableScreenStrings.ToAvoidEnsureHardLinksSupport); copyInformation.AddLink(GeneralSettingsStrings.ChangeFolderLocation, () => { game?.PerformFromScreen(menu => menu.Push(new MigrationSelectScreen())); @@ -328,4 +326,13 @@ namespace osu.Game.Overlays.FirstRunSetup } } } + + public enum FileSystemAddition + { + [LocalisableDescription(typeof(FirstRunOverlayImportFromStableScreenStrings), nameof(FirstRunOverlayImportFromStableScreenStrings.ToAvoidEnsureNtfs))] + ToAvoidEnsureNtfs, + + [LocalisableDescription(typeof(FirstRunOverlayImportFromStableScreenStrings), nameof(FirstRunOverlayImportFromStableScreenStrings.ToAvoidEnsureHardLinksSupport))] + ToAvoidEnsureHardLinksSupport, + } } From 8dce158a13e05911273c5fb07f77555dc3d0b6ae Mon Sep 17 00:00:00 2001 From: Loreos7 Date: Fri, 2 Feb 2024 04:49:22 +0300 Subject: [PATCH 2844/4092] localise update-related strings --- osu.Game/Localisation/NotificationsStrings.cs | 22 +++++++++++++++++++ osu.Game/Updater/UpdateManager.cs | 9 ++++---- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/osu.Game/Localisation/NotificationsStrings.cs b/osu.Game/Localisation/NotificationsStrings.cs index fb3dab032d..f4965e4ebe 100644 --- a/osu.Game/Localisation/NotificationsStrings.cs +++ b/osu.Game/Localisation/NotificationsStrings.cs @@ -113,6 +113,28 @@ Please try changing your audio device to a working setting."); /// public static LocalisableString MismatchingBeatmapForReplay => new TranslatableString(getKey(@"mismatching_beatmap_for_replay"), @"Your local copy of the beatmap for this replay appears to be different than expected. You may need to update or re-download it."); + /// + /// "You are now running osu! {version}. + /// Click to see what's new!" + /// + public static LocalisableString GameVersionAfterUpdate(string version) => new TranslatableString(getKey(@"game_version_after_update"), @"You are now running osu! {0}. +Click to see what's new!", version); + + /// + /// "Update ready to install. Click to restart!" + /// + public static LocalisableString UpdateReadyToInstall => new TranslatableString(getKey(@"update_ready_to_install"), @"Update ready to install. Click to restart!"); + + /// + /// "Downloading update..." + /// + public static LocalisableString DownloadingUpdate => new TranslatableString(getKey(@"downloading_update"), @"Downloading update..."); + + /// + /// "Installing update..." + /// + public static LocalisableString InstallingUpdate => new TranslatableString(getKey(@"installing_update"), @"Installing update..."); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index 190748137a..8f13e0f42a 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; using osu.Game.Graphics; +using osu.Game.Localisation; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osuTK; @@ -92,7 +93,7 @@ namespace osu.Game.Updater public UpdateCompleteNotification(string version) { this.version = version; - Text = $"You are now running osu! {version}.\nClick to see what's new!"; + Text = NotificationsStrings.GameVersionAfterUpdate(version); } [BackgroundDependencyLoader] @@ -114,7 +115,7 @@ namespace osu.Game.Updater { public UpdateApplicationCompleteNotification() { - Text = @"Update ready to install. Click to restart!"; + Text = NotificationsStrings.UpdateReadyToInstall; } } @@ -166,13 +167,13 @@ namespace osu.Game.Updater { State = ProgressNotificationState.Active; Progress = 0; - Text = @"Downloading update..."; + Text = NotificationsStrings.DownloadingUpdate; } public void StartInstall() { Progress = 0; - Text = @"Installing update..."; + Text = NotificationsStrings.InstallingUpdate; } public void FailDownload() From 53c5483eba108acb4c8782764ae4840d414428dd Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 2 Feb 2024 04:53:48 +0300 Subject: [PATCH 2845/4092] Reduce allocation in Playfield --- osu.Game/Rulesets/UI/Playfield.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index e9c35555c8..90a2f63faa 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -247,10 +247,14 @@ namespace osu.Game.Rulesets.UI nestedPlayfields.Add(otherPlayfield); } + private Mod[] mods; + protected override void LoadComplete() { base.LoadComplete(); + mods = Mods?.ToArray(); + // in the case a consumer forgets to add the HitObjectContainer, we will add it here. if (HitObjectContainer.Parent == null) AddInternal(HitObjectContainer); @@ -260,9 +264,9 @@ namespace osu.Game.Rulesets.UI { base.Update(); - if (!IsNested && Mods != null) + if (!IsNested && mods != null) { - foreach (var mod in Mods) + foreach (Mod mod in mods) { if (mod is IUpdatableByPlayfield updatable) updatable.Update(this); @@ -403,10 +407,13 @@ namespace osu.Game.Rulesets.UI // If this is the first time this DHO is being used, then apply the DHO mods. // This is done before Apply() so that the state is updated once when the hitobject is applied. - if (Mods != null) + if (mods != null) { - foreach (var m in Mods.OfType()) - m.ApplyToDrawableHitObject(dho); + foreach (Mod mod in mods) + { + if (mod is IApplicableToDrawableHitObject applicable) + applicable.ApplyToDrawableHitObject(dho); + } } } From 2ff46daf5ede3204da4263fa265ee417601b667a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 2 Feb 2024 06:34:47 +0100 Subject: [PATCH 2846/4092] Also change icon and tooltip text when pending 2FA --- osu.Game/Localisation/ToolbarStrings.cs | 5 +++++ osu.Game/Overlays/Toolbar/ToolbarUserButton.cs | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/Localisation/ToolbarStrings.cs b/osu.Game/Localisation/ToolbarStrings.cs index e71a3fff9b..5822f76e02 100644 --- a/osu.Game/Localisation/ToolbarStrings.cs +++ b/osu.Game/Localisation/ToolbarStrings.cs @@ -19,6 +19,11 @@ namespace osu.Game.Localisation /// public static LocalisableString Connecting => new TranslatableString(getKey(@"connecting"), @"Connecting..."); + /// + /// "Verification required" + /// + public static LocalisableString VerificationRequired => new TranslatableString(getKey(@"verification_required"), @"Verification required"); + /// /// "home" /// diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs index 1e812d7238..2620e850c8 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs @@ -99,7 +99,6 @@ namespace osu.Game.Overlays.Toolbar switch (state.NewValue) { - case APIState.RequiresSecondFactorAuth: case APIState.Connecting: TooltipText = ToolbarStrings.Connecting; spinner.Show(); @@ -108,6 +107,13 @@ namespace osu.Game.Overlays.Toolbar case APIState.Failing: TooltipText = ToolbarStrings.AttemptingToReconnect; spinner.Show(); + failingIcon.Icon = FontAwesome.Solid.ExclamationTriangle; + break; + + case APIState.RequiresSecondFactorAuth: + TooltipText = ToolbarStrings.VerificationRequired; + spinner.Show(); + failingIcon.Icon = FontAwesome.Solid.Key; break; case APIState.Offline: From dfd966e039e49a3582b519146440b866ae00705c Mon Sep 17 00:00:00 2001 From: Mike Will Date: Wed, 31 Jan 2024 18:47:47 -0500 Subject: [PATCH 2847/4092] Improve silence detection in `MutedNotification` Instead of checking the master and music volumes separately to see if they're <= 1%, check the aggregate of the two volumes to see if it's <= -60 dB. When muted notification is activated, restore the aggregate volume level to -30 dB. --- .../Visual/Gameplay/TestScenePlayerLoader.cs | 16 +++++++++---- osu.Game/Screens/Play/PlayerLoader.cs | 23 +++++++++++++------ 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index f97372e9b6..2798d77373 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -264,15 +264,23 @@ namespace osu.Game.Tests.Visual.Gameplay } [Test] - public void TestMutedNotificationMasterVolume() + public void TestMutedNotificationHighMasterVolume() { - addVolumeSteps("master volume", () => audioManager.Volume.Value = 0, () => audioManager.Volume.Value == 0.5); + addVolumeSteps("high master volume", () => + { + audioManager.Volume.Value = 0.1; + audioManager.VolumeTrack.Value = 0.01; + }, () => audioManager.Volume.Value == 0.1 && audioManager.VolumeTrack.Value == 0.32); } [Test] - public void TestMutedNotificationTrackVolume() + public void TestMutedNotificationLowMasterVolume() { - addVolumeSteps("music volume", () => audioManager.VolumeTrack.Value = 0, () => audioManager.VolumeTrack.Value == 0.5); + addVolumeSteps("low master volume", () => + { + audioManager.Volume.Value = 0.01; + audioManager.VolumeTrack.Value = 0.1; + }, () => audioManager.Volume.Value == 0.03 && audioManager.VolumeTrack.Value == 1); } [Test] diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 232de53ac3..6282041f2c 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -544,14 +544,14 @@ namespace osu.Game.Screens.Play private int restartCount; - private const double volume_requirement = 0.01; + private const double volume_requirement = 1e-3; // -60 dB private void showMuteWarningIfNeeded() { if (!muteWarningShownOnce.Value) { // Checks if the notification has not been shown yet and also if master volume is muted, track/music volume is muted or if the whole game is muted. - if (volumeOverlay?.IsMuted.Value == true || audioManager.Volume.Value <= volume_requirement || audioManager.VolumeTrack.Value <= volume_requirement) + if (volumeOverlay?.IsMuted.Value == true || audioManager.Volume.Value * audioManager.VolumeTrack.Value <= volume_requirement) { notificationOverlay?.Post(new MutedNotification()); muteWarningShownOnce.Value = true; @@ -581,11 +581,20 @@ namespace osu.Game.Screens.Play volumeOverlay.IsMuted.Value = false; // Check values before resetting, as the user may have only had mute enabled, in which case we might not need to adjust volumes. - // Note that we only restore halfway to ensure the user isn't suddenly overloaded by unexpectedly high volume. - if (audioManager.Volume.Value <= volume_requirement) - audioManager.Volume.Value = 0.5f; - if (audioManager.VolumeTrack.Value <= volume_requirement) - audioManager.VolumeTrack.Value = 0.5f; + // Note that we only restore to -30 dB to ensure the user isn't suddenly overloaded by unexpectedly high volume. + if (audioManager.Volume.Value * audioManager.VolumeTrack.Value <= volume_requirement) + { + // Prioritize increasing music over master volume as to avoid also increasing effects volume. + const double target = 0.031622776601684; // 10 ^ (-30 / 20) + double result = target / Math.Max(0.01, audioManager.Volume.Value); + if (result > 1) + { + audioManager.Volume.Value = target; + audioManager.VolumeTrack.Value = 1; + } + else + audioManager.VolumeTrack.Value = result; + } return true; }; From bb6387bea01ebac9e5c3e6c2ffa043892824319b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 2 Feb 2024 06:53:00 +0100 Subject: [PATCH 2848/4092] Enable NRT in `ScreenEntry` --- .../Overlays/AccountCreation/ScreenEntry.cs | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs index 9ad507d82a..64d0fa94ab 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using System.Threading.Tasks; @@ -28,28 +26,28 @@ namespace osu.Game.Overlays.AccountCreation { public partial class ScreenEntry : AccountCreationScreen { - private ErrorTextFlowContainer usernameDescription; - private ErrorTextFlowContainer emailAddressDescription; - private ErrorTextFlowContainer passwordDescription; + private ErrorTextFlowContainer usernameDescription = null!; + private ErrorTextFlowContainer emailAddressDescription = null!; + private ErrorTextFlowContainer passwordDescription = null!; - private OsuTextBox usernameTextBox; - private OsuTextBox emailTextBox; - private OsuPasswordTextBox passwordTextBox; + private OsuTextBox usernameTextBox = null!; + private OsuTextBox emailTextBox = null!; + private OsuPasswordTextBox passwordTextBox = null!; [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; - private ShakeContainer registerShake; - private ITextPart characterCheckText; + private ShakeContainer registerShake = null!; + private ITextPart characterCheckText = null!; - private OsuTextBox[] textboxes; - private LoadingLayer loadingLayer; + private OsuTextBox[] textboxes = null!; + private LoadingLayer loadingLayer = null!; [Resolved] - private GameHost host { get; set; } + private GameHost? host { get; set; } [Resolved] - private OsuGame game { get; set; } + private OsuGame game { get; set; } = null!; [BackgroundDependencyLoader] private void load() @@ -180,7 +178,7 @@ namespace osu.Game.Overlays.AccountCreation Task.Run(() => { bool success; - RegistrationRequest.RegistrationRequestErrors errors = null; + RegistrationRequest.RegistrationRequestErrors? errors = null; try { @@ -241,6 +239,6 @@ namespace osu.Game.Overlays.AccountCreation return false; } - private OsuTextBox nextUnfilledTextBox() => textboxes.FirstOrDefault(t => string.IsNullOrEmpty(t.Text)); + private OsuTextBox? nextUnfilledTextBox() => textboxes.FirstOrDefault(t => string.IsNullOrEmpty(t.Text)); } } From b58ac7950bd69d383cd26e6c3142b4ea568e9a1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 2 Feb 2024 06:53:17 +0100 Subject: [PATCH 2849/4092] Make game dependency in `ScreenEntry` optional to unbreak tests --- osu.Game/Overlays/AccountCreation/ScreenEntry.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs index 64d0fa94ab..ab462357c0 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs @@ -47,7 +47,7 @@ namespace osu.Game.Overlays.AccountCreation private GameHost? host { get; set; } [Resolved] - private OsuGame game { get; set; } = null!; + private OsuGame? game { get; set; } [BackgroundDependencyLoader] private void load() @@ -208,7 +208,7 @@ namespace osu.Game.Overlays.AccountCreation if (!string.IsNullOrEmpty(errors.Message)) passwordDescription.AddErrors(new[] { errors.Message }); - game.OpenUrlExternally($"{errors.Redirect}?username={usernameTextBox.Text}&email={emailTextBox.Text}", true); + game?.OpenUrlExternally($"{errors.Redirect}?username={usernameTextBox.Text}&email={emailTextBox.Text}", true); } } else From 93d34e411535f2a47442146f92f62a3cab150821 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 2 Feb 2024 06:54:18 +0100 Subject: [PATCH 2850/4092] Enable NRT in `ScreenWarning` --- osu.Game/Overlays/AccountCreation/ScreenWarning.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/AccountCreation/ScreenWarning.cs b/osu.Game/Overlays/AccountCreation/ScreenWarning.cs index 0fbf6ba59e..c24bd32bb4 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenWarning.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenWarning.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.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -23,14 +21,14 @@ namespace osu.Game.Overlays.AccountCreation { public partial class ScreenWarning : AccountCreationScreen { - private OsuTextFlowContainer multiAccountExplanationText; - private LinkFlowContainer furtherAssistance; + private OsuTextFlowContainer multiAccountExplanationText = null!; + private LinkFlowContainer furtherAssistance = null!; - [Resolved(canBeNull: true)] - private IAPIProvider api { get; set; } + [Resolved] + private IAPIProvider? api { get; set; } - [Resolved(canBeNull: true)] - private OsuGame game { get; set; } + [Resolved] + private OsuGame? game { get; set; } private const string help_centre_url = "/help/wiki/Help_Centre#login"; From 45f60b035e549e24a9497bf0f11de2eb3d6a4b04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 2 Feb 2024 07:05:27 +0100 Subject: [PATCH 2851/4092] Enable NRT in `AccountCreationOverlay` --- osu.Game/Overlays/AccountCreationOverlay.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/AccountCreationOverlay.cs b/osu.Game/Overlays/AccountCreationOverlay.cs index 576ee92b48..82fc5508f1 100644 --- a/osu.Game/Overlays/AccountCreationOverlay.cs +++ b/osu.Game/Overlays/AccountCreationOverlay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -25,7 +23,9 @@ namespace osu.Game.Overlays { private const float transition_time = 400; - private ScreenWelcome welcomeScreen; + private ScreenWelcome welcomeScreen = null!; + + private ScheduledDelegate? scheduledHide; public AccountCreationOverlay() { @@ -108,8 +108,6 @@ namespace osu.Game.Overlays this.FadeOut(100); } - private ScheduledDelegate scheduledHide; - private void apiStateChanged(ValueChangedEvent state) { switch (state.NewValue) From a00cf87925a06b485d039ba03dcf72fc06127789 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 2 Feb 2024 06:50:27 +0100 Subject: [PATCH 2852/4092] Add 2FA verification screen to registration flow --- .../ScreenEmailVerification.cs | 24 +++++++++++++++++++ .../Overlays/AccountCreation/ScreenEntry.cs | 11 +++++++++ 2 files changed, 35 insertions(+) create mode 100644 osu.Game/Overlays/AccountCreation/ScreenEmailVerification.cs diff --git a/osu.Game/Overlays/AccountCreation/ScreenEmailVerification.cs b/osu.Game/Overlays/AccountCreation/ScreenEmailVerification.cs new file mode 100644 index 0000000000..f3b42117ea --- /dev/null +++ b/osu.Game/Overlays/AccountCreation/ScreenEmailVerification.cs @@ -0,0 +1,24 @@ +// 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.Game.Overlays.Login; + +namespace osu.Game.Overlays.AccountCreation +{ + public partial class ScreenEmailVerification : AccountCreationScreen + { + [BackgroundDependencyLoader] + private void load() + { + InternalChild = new SecondFactorAuthForm + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(20), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + } + } +} diff --git a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs index ab462357c0..f57c7d22a2 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -37,6 +38,8 @@ namespace osu.Game.Overlays.AccountCreation [Resolved] private IAPIProvider api { get; set; } = null!; + private IBindable apiState = null!; + private ShakeContainer registerShake = null!; private ITextPart characterCheckText = null!; @@ -142,6 +145,8 @@ namespace osu.Game.Overlays.AccountCreation passwordTextBox.Current.BindValueChanged(_ => updateCharacterCheckTextColour(), true); characterCheckText.DrawablePartsRecreated += _ => updateCharacterCheckTextColour(); + + apiState = api.State.GetBoundCopy(); } private void updateCharacterCheckTextColour() @@ -221,6 +226,12 @@ namespace osu.Game.Overlays.AccountCreation return; } + apiState.BindValueChanged(state => + { + if (state.NewValue == APIState.RequiresSecondFactorAuth) + this.Push(new ScreenEmailVerification()); + }); + api.Login(usernameTextBox.Text, passwordTextBox.Text); }); }); From 091618db1a81ec017ef0903f2844b95e048465e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 2 Feb 2024 07:04:31 +0100 Subject: [PATCH 2853/4092] Add test coverage of full account creation flow --- .../Online/TestSceneAccountCreationOverlay.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs index 79fb063ea9..b9d7312233 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs @@ -10,6 +10,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; @@ -67,5 +68,34 @@ namespace osu.Game.Tests.Visual.Online }); AddUntilStep("overlay is hidden", () => accountCreation.State.Value == Visibility.Hidden); } + + [Test] + public void TestFullFlow() + { + AddStep("log out", () => API.Logout()); + + AddStep("show manually", () => accountCreation.Show()); + AddUntilStep("overlay is visible", () => accountCreation.State.Value == Visibility.Visible); + + AddStep("click button", () => accountCreation.ChildrenOfType().Single().TriggerClick()); + AddUntilStep("warning screen is present", () => accountCreation.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + + AddStep("proceed", () => accountCreation.ChildrenOfType().Single().TriggerClick()); + AddUntilStep("entry screen is present", () => accountCreation.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + + AddStep("input details", () => + { + var entryScreen = accountCreation.ChildrenOfType().Single(); + entryScreen.ChildrenOfType().ElementAt(0).Text = "new_user"; + entryScreen.ChildrenOfType().ElementAt(1).Text = "new.user@fake.mail"; + entryScreen.ChildrenOfType().ElementAt(2).Text = "password"; + }); + AddStep("click button", () => accountCreation.ChildrenOfType().Single() + .ChildrenOfType().Single().TriggerClick()); + AddUntilStep("verification screen is present", () => accountCreation.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + + AddStep("verify", () => ((DummyAPIAccess)API).AuthenticateSecondFactor("abcdefgh")); + AddUntilStep("overlay is hidden", () => accountCreation.State.Value == Visibility.Hidden); + } } } From 3122211268346b9c57e3ea8b96e77090251f06e5 Mon Sep 17 00:00:00 2001 From: Mike Will Date: Fri, 2 Feb 2024 02:12:46 -0500 Subject: [PATCH 2854/4092] Change silence threshold and target restore volume level --- osu.Game/Screens/Play/PlayerLoader.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 6282041f2c..9ec7197429 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -544,7 +544,7 @@ namespace osu.Game.Screens.Play private int restartCount; - private const double volume_requirement = 1e-3; // -60 dB + private const double volume_requirement = 0.01; private void showMuteWarningIfNeeded() { @@ -581,11 +581,11 @@ namespace osu.Game.Screens.Play volumeOverlay.IsMuted.Value = false; // Check values before resetting, as the user may have only had mute enabled, in which case we might not need to adjust volumes. - // Note that we only restore to -30 dB to ensure the user isn't suddenly overloaded by unexpectedly high volume. + // Note that we only restore to -20 dB to ensure the user isn't suddenly overloaded by unexpectedly high volume. if (audioManager.Volume.Value * audioManager.VolumeTrack.Value <= volume_requirement) { // Prioritize increasing music over master volume as to avoid also increasing effects volume. - const double target = 0.031622776601684; // 10 ^ (-30 / 20) + const double target = 0.1; double result = target / Math.Max(0.01, audioManager.Volume.Value); if (result > 1) { From f4a2d5f3f489c1332f7e200747dd9b665337db7a Mon Sep 17 00:00:00 2001 From: Mike Will Date: Fri, 2 Feb 2024 02:41:58 -0500 Subject: [PATCH 2855/4092] Round off inaccuracies in the aggregate volume before evaluating --- osu.Game/Screens/Play/PlayerLoader.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 9ec7197429..50c95ce525 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -551,7 +551,8 @@ namespace osu.Game.Screens.Play if (!muteWarningShownOnce.Value) { // Checks if the notification has not been shown yet and also if master volume is muted, track/music volume is muted or if the whole game is muted. - if (volumeOverlay?.IsMuted.Value == true || audioManager.Volume.Value * audioManager.VolumeTrack.Value <= volume_requirement) + double aggregate = Math.Floor(audioManager.Volume.Value * audioManager.VolumeTrack.Value * 100) / 100; + if (volumeOverlay?.IsMuted.Value == true || aggregate <= volume_requirement) { notificationOverlay?.Post(new MutedNotification()); muteWarningShownOnce.Value = true; @@ -582,7 +583,8 @@ namespace osu.Game.Screens.Play // Check values before resetting, as the user may have only had mute enabled, in which case we might not need to adjust volumes. // Note that we only restore to -20 dB to ensure the user isn't suddenly overloaded by unexpectedly high volume. - if (audioManager.Volume.Value * audioManager.VolumeTrack.Value <= volume_requirement) + double aggregate = Math.Floor(audioManager.Volume.Value * audioManager.VolumeTrack.Value * 100) / 100; + if (aggregate <= volume_requirement) { // Prioritize increasing music over master volume as to avoid also increasing effects volume. const double target = 0.1; From 5d9200b4fe02c4ed0f287fe0f43c5f2433b52ec9 Mon Sep 17 00:00:00 2001 From: Mike Will Date: Fri, 2 Feb 2024 02:59:02 -0500 Subject: [PATCH 2856/4092] Update tests to reflect new restore target volume --- osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 2798d77373..99bb006071 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -268,9 +268,9 @@ namespace osu.Game.Tests.Visual.Gameplay { addVolumeSteps("high master volume", () => { - audioManager.Volume.Value = 0.1; + audioManager.Volume.Value = 0.15; audioManager.VolumeTrack.Value = 0.01; - }, () => audioManager.Volume.Value == 0.1 && audioManager.VolumeTrack.Value == 0.32); + }, () => audioManager.Volume.Value == 0.15 && audioManager.VolumeTrack.Value == 0.67); } [Test] @@ -279,8 +279,8 @@ namespace osu.Game.Tests.Visual.Gameplay addVolumeSteps("low master volume", () => { audioManager.Volume.Value = 0.01; - audioManager.VolumeTrack.Value = 0.1; - }, () => audioManager.Volume.Value == 0.03 && audioManager.VolumeTrack.Value == 1); + audioManager.VolumeTrack.Value = 0.15; + }, () => audioManager.Volume.Value == 0.1 && audioManager.VolumeTrack.Value == 1); } [Test] From c60e110976ca7214c200d90da45bdd165cb0b39c Mon Sep 17 00:00:00 2001 From: Mike Will Date: Fri, 2 Feb 2024 03:09:19 -0500 Subject: [PATCH 2857/4092] Try to fix code quality issues raised by workflow --- osu.Game/Screens/Play/PlayerLoader.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 50c95ce525..1d0ecfc12d 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -551,8 +551,7 @@ namespace osu.Game.Screens.Play if (!muteWarningShownOnce.Value) { // Checks if the notification has not been shown yet and also if master volume is muted, track/music volume is muted or if the whole game is muted. - double aggregate = Math.Floor(audioManager.Volume.Value * audioManager.VolumeTrack.Value * 100) / 100; - if (volumeOverlay?.IsMuted.Value == true || aggregate <= volume_requirement) + if (volumeOverlay?.IsMuted.Value == true || Math.Floor(audioManager.Volume.Value * audioManager.VolumeTrack.Value * 100) / 100 <= volume_requirement) { notificationOverlay?.Post(new MutedNotification()); muteWarningShownOnce.Value = true; @@ -583,11 +582,11 @@ namespace osu.Game.Screens.Play // Check values before resetting, as the user may have only had mute enabled, in which case we might not need to adjust volumes. // Note that we only restore to -20 dB to ensure the user isn't suddenly overloaded by unexpectedly high volume. - double aggregate = Math.Floor(audioManager.Volume.Value * audioManager.VolumeTrack.Value * 100) / 100; - if (aggregate <= volume_requirement) + if (Math.Floor(audioManager.Volume.Value * audioManager.VolumeTrack.Value * 100) / 100 <= volume_requirement) { - // Prioritize increasing music over master volume as to avoid also increasing effects volume. const double target = 0.1; + + // Prioritize increasing music over master volume as to avoid also increasing effects volume. double result = target / Math.Max(0.01, audioManager.Volume.Value); if (result > 1) { From 29a28905820bb1242cda326a2e4610e058bef1c2 Mon Sep 17 00:00:00 2001 From: Mike Will Date: Fri, 2 Feb 2024 03:46:05 -0500 Subject: [PATCH 2858/4092] fix code quality error #2 --- osu.Game/Screens/Play/PlayerLoader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 1d0ecfc12d..40ffa844da 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -584,10 +584,10 @@ namespace osu.Game.Screens.Play // Note that we only restore to -20 dB to ensure the user isn't suddenly overloaded by unexpectedly high volume. if (Math.Floor(audioManager.Volume.Value * audioManager.VolumeTrack.Value * 100) / 100 <= volume_requirement) { - const double target = 0.1; - // Prioritize increasing music over master volume as to avoid also increasing effects volume. + const double target = 0.1; double result = target / Math.Max(0.01, audioManager.Volume.Value); + if (result > 1) { audioManager.Volume.Value = target; From 906560f66d971778ab8ec00866b3aa3d0afb8caa Mon Sep 17 00:00:00 2001 From: Mike Will Date: Fri, 2 Feb 2024 04:06:22 -0500 Subject: [PATCH 2859/4092] Fix mute button test Lowering the master volume in the previous test meant that the volume levels would end up being modified. --- osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 99bb006071..04b681989f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -289,9 +289,10 @@ namespace osu.Game.Tests.Visual.Gameplay addVolumeSteps("mute button", () => { // Importantly, in the case the volume is muted but the user has a volume level set, it should be retained. + audioManager.Volume.Value = 0.5f; audioManager.VolumeTrack.Value = 0.5f; volumeOverlay.IsMuted.Value = true; - }, () => !volumeOverlay.IsMuted.Value && audioManager.VolumeTrack.Value == 0.5f); + }, () => !volumeOverlay.IsMuted.Value && audioManager.Volume.Value == 0.5f && audioManager.VolumeTrack.Value == 0.5f); } /// From e4ec8c111b90542c8705e73eaabca6bf9557f63f Mon Sep 17 00:00:00 2001 From: Mike Will Date: Fri, 2 Feb 2024 05:48:57 -0500 Subject: [PATCH 2860/4092] give better volume names to `addVolumeSteps` --- osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 04b681989f..67e94a2960 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -266,7 +266,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestMutedNotificationHighMasterVolume() { - addVolumeSteps("high master volume", () => + addVolumeSteps("master and music volumes", () => { audioManager.Volume.Value = 0.15; audioManager.VolumeTrack.Value = 0.01; @@ -276,7 +276,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestMutedNotificationLowMasterVolume() { - addVolumeSteps("low master volume", () => + addVolumeSteps("master and music volumes", () => { audioManager.Volume.Value = 0.01; audioManager.VolumeTrack.Value = 0.15; From b44f77cee1b6adb6568f9b5764382a6cd6ffe2ed Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 2 Feb 2024 19:48:13 +0900 Subject: [PATCH 2861/4092] Update R# + fix inspections --- .config/dotnet-tools.json | 4 +- .globalconfig | 4 +- .../TestScenePathControlPointVisualiser.cs | 2 +- .../TestSceneFollowPoints.cs | 2 +- .../TestSceneSkinFallbacks.cs | 2 + .../UI/DrumTouchInputArea.cs | 3 +- osu.Game.Tests/Beatmaps/WorkingBeatmapTest.cs | 6 ++- osu.Game.Tests/Database/RulesetStoreTests.cs | 6 +-- .../Gameplay/TestSceneStoryboardSamples.cs | 4 +- .../TestSceneBrokenRulesetHandling.cs | 12 +++--- .../Editing/TestSceneEditorTestGameplay.cs | 4 +- .../Gameplay/TestSceneReplayRecorder.cs | 2 +- .../Gameplay/TestSceneSpectatorPlayback.cs | 2 +- .../Visual/Menus/TestSceneToolbar.cs | 2 +- .../TestSceneLoungeRoomsContainer.cs | 2 +- .../TestScenePlaylistsMatchSettingsOverlay.cs | 2 +- .../TestScenePlaylistsRoomCreation.cs | 8 ++-- .../Visual/Ranking/TestSceneResultsScreen.cs | 2 +- .../TestSceneFirstRunSetupOverlay.cs | 2 +- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 3 +- osu.Game/Beatmaps/WorkingBeatmapCache.cs | 2 +- .../SelectionCycleFillFlowContainer.cs | 4 +- .../UserInterface/BreadcrumbControl.cs | 2 + osu.Game/IO/IStorageResourceProvider.cs | 2 +- osu.Game/Online/Spectator/SpectatorClient.cs | 3 +- osu.Game/Overlays/Comments/VotePill.cs | 2 +- .../Overlays/MedalSplash/DrawableMedal.cs | 2 + osu.Game/Overlays/Volume/VolumeMeter.cs | 2 + .../Objects/Drawables/DrawableHitObject.cs | 3 +- .../Objects/Pooling/HitObjectEntryManager.cs | 5 +-- .../Edit/Compose/Components/DragBox.cs | 2 + osu.Game/Screens/Menu/ButtonArea.cs | 3 ++ .../OnlinePlay/Components/RoomManager.cs | 2 + .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 2 +- .../Screens/Play/HUD/HoldForMenuButton.cs | 2 +- .../Multiplayer/MultiplayerTestScene.cs | 4 +- .../IOnlinePlayTestSceneDependencies.cs | 12 +++--- .../Visual/OnlinePlay/OnlinePlayTestScene.cs | 42 +++++++++++-------- .../OnlinePlayTestSceneDependencies.cs | 4 +- osu.Game/Tests/Visual/SkinnableTestScene.cs | 4 +- osu.sln.DotSettings | 5 +++ 41 files changed, 102 insertions(+), 81 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index b8dc201559..99906f0895 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,13 +3,13 @@ "isRoot": true, "tools": { "jetbrains.resharper.globaltools": { - "version": "2022.2.3", + "version": "2023.3.3", "commands": [ "jb" ] }, "nvika": { - "version": "2.2.0", + "version": "3.0.0", "commands": [ "nvika" ] diff --git a/.globalconfig b/.globalconfig index a7b652c454..a4d4707f9b 100644 --- a/.globalconfig +++ b/.globalconfig @@ -1,5 +1,3 @@ -is_global = true - # .NET Code Style # IDE styles reference: https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ @@ -56,4 +54,4 @@ dotnet_diagnostic.RS0030.severity = error # Temporarily disable analysing CanBeNull = true in NRT contexts due to mobile issues. # See: https://github.com/ppy/osu/pull/19677 -dotnet_diagnostic.OSUF001.severity = none \ No newline at end of file +dotnet_diagnostic.OSUF001.severity = none diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs index 8234381283..2b53554ed1 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs @@ -206,7 +206,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { AddStep($"click context menu item \"{contextMenuText}\"", () => { - MenuItem item = visualiser.ContextMenuItems.FirstOrDefault(menuItem => menuItem.Text.Value == "Curve type")?.Items.FirstOrDefault(menuItem => menuItem.Text.Value == contextMenuText); + MenuItem item = visualiser.ContextMenuItems!.FirstOrDefault(menuItem => menuItem.Text.Value == "Curve type")?.Items.FirstOrDefault(menuItem => menuItem.Text.Value == contextMenuText); item?.Action.Value?.Invoke(); }); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index eefaa3cae3..28c9d71139 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -183,7 +183,7 @@ namespace osu.Game.Rulesets.Osu.Tests break; } - hitObjectContainer.Add(drawableObject); + hitObjectContainer.Add(drawableObject!); followPointRenderer.AddFollowPoints(objects[i]); } }); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs index 09b906cb10..c624fbbe73 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -173,6 +174,7 @@ namespace osu.Game.Rulesets.Osu.Tests public IEnumerable AllSources => new[] { this }; + [CanBeNull] public event Action SourceChanged; private bool enabled = true; diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 29ccd96675..0b7f6f621a 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -179,10 +179,9 @@ namespace osu.Game.Rulesets.Taiko.UI TaikoAction taikoAction = getTaikoActionFromPosition(position); // Not too sure how this can happen, but let's avoid throwing. - if (trackedActions.ContainsKey(source)) + if (!trackedActions.TryAdd(source, taikoAction)) return; - trackedActions.Add(source, taikoAction); keyBindingContainer.TriggerPressed(taikoAction); } diff --git a/osu.Game.Tests/Beatmaps/WorkingBeatmapTest.cs b/osu.Game.Tests/Beatmaps/WorkingBeatmapTest.cs index f4b1028c0e..3c26f8e39a 100644 --- a/osu.Game.Tests/Beatmaps/WorkingBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/WorkingBeatmapTest.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using JetBrains.Annotations; using Moq; using NUnit.Framework; using osu.Game.Beatmaps; @@ -98,9 +99,10 @@ namespace osu.Game.Tests.Beatmaps Beatmap = beatmap; } +#pragma warning disable CS0067 + [CanBeNull] public event Action> ObjectConverted; - - protected virtual void OnObjectConverted(HitObject arg1, IEnumerable arg2) => ObjectConverted?.Invoke(arg1, arg2); +#pragma warning restore CS0067 public IBeatmap Beatmap { get; } diff --git a/osu.Game.Tests/Database/RulesetStoreTests.cs b/osu.Game.Tests/Database/RulesetStoreTests.cs index 8b4c6e2411..ddf207342a 100644 --- a/osu.Game.Tests/Database/RulesetStoreTests.cs +++ b/osu.Game.Tests/Database/RulesetStoreTests.cs @@ -79,7 +79,7 @@ namespace osu.Game.Tests.Database Assert.That(realm.Run(r => r.Find(rulesetShortName)!.Available), Is.True); // Availability is updated on construction of a RealmRulesetStore - var _ = new RealmRulesetStore(realm, storage); + _ = new RealmRulesetStore(realm, storage); Assert.That(realm.Run(r => r.Find(rulesetShortName)!.Available), Is.False); }); @@ -104,13 +104,13 @@ namespace osu.Game.Tests.Database Assert.That(realm.Run(r => r.Find(rulesetShortName)!.Available), Is.True); // Availability is updated on construction of a RealmRulesetStore - var _ = new RealmRulesetStore(realm, storage); + _ = new RealmRulesetStore(realm, storage); Assert.That(realm.Run(r => r.Find(rulesetShortName)!.Available), Is.False); // Simulate the ruleset getting updated LoadTestRuleset.Version = Ruleset.CURRENT_RULESET_API_VERSION; - var __ = new RealmRulesetStore(realm, storage); + _ = new RealmRulesetStore(realm, storage); Assert.That(realm.Run(r => r.Find(rulesetShortName)!.Available), Is.True); }); diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index 4fb9db845b..61161f3206 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -203,9 +203,9 @@ namespace osu.Game.Tests.Gameplay public IRenderer Renderer => host.Renderer; public AudioManager AudioManager => Audio; - public IResourceStore Files => null; + public IResourceStore Files => null!; public new IResourceStore Resources => base.Resources; - public RealmAccess RealmAccess => null; + public RealmAccess RealmAccess => null!; public IResourceStore CreateTextureLoaderStore(IResourceStore underlyingStore) => null; #endregion diff --git a/osu.Game.Tests/Rulesets/TestSceneBrokenRulesetHandling.cs b/osu.Game.Tests/Rulesets/TestSceneBrokenRulesetHandling.cs index dac6beea65..b378704e80 100644 --- a/osu.Game.Tests/Rulesets/TestSceneBrokenRulesetHandling.cs +++ b/osu.Game.Tests/Rulesets/TestSceneBrokenRulesetHandling.cs @@ -56,9 +56,9 @@ namespace osu.Game.Tests.Rulesets public override IEnumerable GetModsFor(ModType type) => new Mod[] { null }; - public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => null; - public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => null; - public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => null; + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => null!; + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => null!; + public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => null!; } private class TestAPIIncompatibleRuleset : Ruleset @@ -69,9 +69,9 @@ namespace osu.Game.Tests.Rulesets // simulate API incompatibility by throwing similar exceptions. public override IEnumerable GetModsFor(ModType type) => throw new MissingMethodException(); - public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => null; - public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => null; - public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => null; + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => null!; + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => null!; + public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => null!; } } } diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs index bbd7123f20..ca5e89c8ed 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs @@ -142,7 +142,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("dismiss prompt", () => { - var button = DialogOverlay.CurrentDialog.Buttons.Last(); + var button = DialogOverlay.CurrentDialog!.Buttons.Last(); InputManager.MoveMouseTo(button); InputManager.Click(MouseButton.Left); }); @@ -167,7 +167,7 @@ namespace osu.Game.Tests.Visual.Editing }); AddUntilStep("save prompt shown", () => DialogOverlay.CurrentDialog is SaveRequiredPopupDialog); - AddStep("save changes", () => DialogOverlay.CurrentDialog.PerformOkAction()); + AddStep("save changes", () => DialogOverlay.CurrentDialog!.PerformOkAction()); EditorPlayer editorPlayer = null; AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index c51883b221..a7ab021884 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected override void Update() { base.Update(); - playbackManager?.ReplayInputHandler.SetFrameFromTime(Time.Current - 100); + playbackManager?.ReplayInputHandler?.SetFrameFromTime(Time.Current - 100); } [TearDownSteps] diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index 3a5b3864af..dd5bbf70b4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -198,7 +198,7 @@ namespace osu.Game.Tests.Visual.Gameplay foreach (var legacyFrame in frames.Frames) { var frame = new TestReplayFrame(); - frame.FromLegacy(legacyFrame, null); + frame.FromLegacy(legacyFrame, null!); playbackReplay.Frames.Add(frame); } diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs index ce9f80a84f..12d7dde11b 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs @@ -250,7 +250,7 @@ namespace osu.Game.Tests.Visual.Menus { } - public virtual IBindable UnreadCount => null; + public virtual IBindable UnreadCount { get; } = new Bindable(); public IEnumerable AllNotifications => Enumerable.Empty(); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index d99d764449..b938e59d63 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.Multiplayer .SkipWhile(r => r.Room.Category.Value == RoomCategory.Spotlight) .All(r => r.Room.Category.Value == RoomCategory.Normal)); - AddStep("remove first room", () => RoomManager.RemoveRoom(RoomManager.Rooms.FirstOrDefault(r => r.RoomID.Value == 0))); + AddStep("remove first room", () => RoomManager.RemoveRoom(RoomManager.Rooms.First(r => r.RoomID.Value == 0))); AddAssert("has 4 rooms", () => container.Rooms.Count == 4); AddAssert("first room removed", () => container.Rooms.All(r => r.Room.RoomID.Value != 0)); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs index 1053789b27..9f7b20ad43 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs @@ -179,7 +179,7 @@ namespace osu.Game.Tests.Visual.Playlists public IBindable InitialRoomsReceived { get; } = new Bindable(true); - public IBindableList Rooms => null; + public IBindableList Rooms => null!; public void AddOrUpdateRoom(Room room) => throw new NotImplementedException(); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index 2f66309f04..1636a3d4b8 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -52,11 +52,11 @@ namespace osu.Game.Tests.Visual.Playlists [SetUpSteps] public void SetupSteps() { - AddStep("set room", () => SelectedRoom.Value = new Room()); + AddStep("set room", () => SelectedRoom!.Value = new Room()); importBeatmap(); - AddStep("load match", () => LoadScreen(match = new TestPlaylistsRoomSubScreen(SelectedRoom.Value))); + AddStep("load match", () => LoadScreen(match = new TestPlaylistsRoomSubScreen(SelectedRoom!.Value))); AddUntilStep("wait for load", () => match.IsCurrentScreen()); } @@ -115,7 +115,7 @@ namespace osu.Game.Tests.Visual.Playlists }); }); - AddAssert("first playlist item selected", () => match.SelectedItem.Value == SelectedRoom.Value.Playlist[0]); + AddAssert("first playlist item selected", () => match.SelectedItem.Value == SelectedRoom!.Value.Playlist[0]); } [Test] @@ -201,7 +201,7 @@ namespace osu.Game.Tests.Visual.Playlists private void setupAndCreateRoom(Action room) { - AddStep("setup room", () => room(SelectedRoom.Value)); + AddStep("setup room", () => room(SelectedRoom!.Value)); AddStep("click create button", () => { diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 5671cbebd7..685a685896 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -432,7 +432,7 @@ namespace osu.Game.Tests.Visual.Ranking private class RulesetWithNoPerformanceCalculator : OsuRuleset { - public override PerformanceCalculator CreatePerformanceCalculator() => null; + public override PerformanceCalculator CreatePerformanceCalculator() => null!; } } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs index 9275f9755f..51da4d8755 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs @@ -213,7 +213,7 @@ namespace osu.Game.Tests.Visual.UserInterface { } - public virtual IBindable UnreadCount => null; + public virtual IBindable UnreadCount { get; } = new Bindable(); public IEnumerable AllNotifications => Enumerable.Empty(); } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 1ee4670ae2..386dada328 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -567,10 +567,9 @@ namespace osu.Game.Beatmaps.Formats for (int i = pendingControlPoints.Count - 1; i >= 0; i--) { var type = pendingControlPoints[i].GetType(); - if (pendingControlPointTypes.Contains(type)) + if (!pendingControlPointTypes.Add(type)) continue; - pendingControlPointTypes.Add(type); beatmap.ControlPointInfo.Add(pendingControlPointsTime, pendingControlPoints[i]); } diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index 2c500146c5..74a85cde7c 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -116,7 +116,7 @@ namespace osu.Game.Beatmaps ITrackStore IBeatmapResourceProvider.Tracks => trackStore; IRenderer IStorageResourceProvider.Renderer => host?.Renderer ?? new DummyRenderer(); AudioManager IStorageResourceProvider.AudioManager => audioManager; - RealmAccess IStorageResourceProvider.RealmAccess => null; + RealmAccess IStorageResourceProvider.RealmAccess => null!; IResourceStore IStorageResourceProvider.Files => files; IResourceStore IStorageResourceProvider.Resources => resources; IResourceStore IStorageResourceProvider.CreateTextureLoaderStore(IResourceStore underlyingStore) => host?.CreateTextureLoaderStore(underlyingStore); diff --git a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs index 62544c6111..098fd7b1ab 100644 --- a/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs +++ b/osu.Game/Graphics/Containers/SelectionCycleFillFlowContainer.cs @@ -52,10 +52,10 @@ namespace osu.Game.Graphics.Containers public override void Add(T drawable) { - base.Add(drawable); - Debug.Assert(drawable != null); + base.Add(drawable); + drawable.StateChanged += state => selectionChanged(drawable, state); } diff --git a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs index af4b3849af..4af6ce7498 100644 --- a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs +++ b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Graphics.Sprites; namespace osu.Game.Graphics.UserInterface @@ -48,6 +49,7 @@ namespace osu.Game.Graphics.UserInterface { protected virtual float ChevronSize => 10; + [CanBeNull] public event Action StateChanged; public readonly SpriteIcon Chevron; diff --git a/osu.Game/IO/IStorageResourceProvider.cs b/osu.Game/IO/IStorageResourceProvider.cs index 08982a8b5f..91760971e8 100644 --- a/osu.Game/IO/IStorageResourceProvider.cs +++ b/osu.Game/IO/IStorageResourceProvider.cs @@ -41,6 +41,6 @@ namespace osu.Game.IO /// /// The underlying provider of texture data (in arbitrary image formats). /// A texture loader store. - IResourceStore CreateTextureLoaderStore(IResourceStore underlyingStore); + IResourceStore? CreateTextureLoaderStore(IResourceStore underlyingStore); } } diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 7911701853..07ee9115d6 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -264,13 +264,12 @@ namespace osu.Game.Online.Spectator { Debug.Assert(ThreadSafety.IsUpdateThread); - if (watchedUsersRefCounts.ContainsKey(userId)) + if (!watchedUsersRefCounts.TryAdd(userId, 1)) { watchedUsersRefCounts[userId]++; return; } - watchedUsersRefCounts.Add(userId, 1); WatchUserInternal(userId); } diff --git a/osu.Game/Overlays/Comments/VotePill.cs b/osu.Game/Overlays/Comments/VotePill.cs index dd418a9e58..8c5aaa062f 100644 --- a/osu.Game/Overlays/Comments/VotePill.cs +++ b/osu.Game/Overlays/Comments/VotePill.cs @@ -30,7 +30,7 @@ namespace osu.Game.Overlays.Comments public Color4 AccentColour { get; set; } - protected override IEnumerable EffectTargets => null; + protected override IEnumerable EffectTargets => Enumerable.Empty(); [Resolved] private IAPIProvider api { get; set; } diff --git a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs index a25147b69f..f4f6fd2bc1 100644 --- a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs +++ b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using JetBrains.Annotations; using osu.Framework; using osuTK; using osu.Framework.Allocation; @@ -24,6 +25,7 @@ namespace osu.Game.Overlays.MedalSplash private const float scale_when_unlocked = 0.76f; private const float scale_when_full = 0.6f; + [CanBeNull] public event Action StateChanged; private readonly Medal medal; diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 9ca4c25ab9..6ec4971f06 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -5,6 +5,7 @@ using System; using System.Globalization; +using JetBrains.Annotations; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -48,6 +49,7 @@ namespace osu.Game.Overlays.Volume private Sample notchSample; private double sampleLastPlaybackTime; + [CanBeNull] public event Action StateChanged; private SelectionState state; diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 5662fb2293..161537200a 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -11,6 +11,7 @@ using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; @@ -139,7 +140,7 @@ namespace osu.Game.Rulesets.Objects.Drawables protected override bool RequiresChildrenUpdate => true; - public override bool IsPresent => base.IsPresent || (State.Value == ArmedState.Idle && Clock?.CurrentTime >= LifetimeStart); + public override bool IsPresent => base.IsPresent || (State.Value == ArmedState.Idle && Clock.IsNotNull() && Clock.CurrentTime >= LifetimeStart); private readonly Bindable state = new Bindable(); diff --git a/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs b/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs index fabf4fc444..7977166cb2 100644 --- a/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs +++ b/osu.Game/Rulesets/Objects/Pooling/HitObjectEntryManager.cs @@ -47,12 +47,9 @@ namespace osu.Game.Rulesets.Objects.Pooling { HitObject hitObject = entry.HitObject; - if (entryMap.ContainsKey(hitObject)) + if (!entryMap.TryAdd(hitObject, entry)) throw new InvalidOperationException($@"The {nameof(HitObjectLifetimeEntry)} is already added to this {nameof(HitObjectEntryManager)}."); - // Add the entry. - entryMap[hitObject] = entry; - // If the entry has a parent, set it and add the entry to the parent's children. if (parent != null) { diff --git a/osu.Game/Screens/Edit/Compose/Components/DragBox.cs b/osu.Game/Screens/Edit/Compose/Components/DragBox.cs index 4d1f81228e..b83e565e89 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DragBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DragBox.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using JetBrains.Annotations; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -69,6 +70,7 @@ namespace osu.Game.Screens.Edit.Compose.Components public override void Show() => State = Visibility.Visible; + [CanBeNull] public event Action StateChanged; public partial class BoxWithBorders : CompositeDrawable diff --git a/osu.Game/Screens/Menu/ButtonArea.cs b/osu.Game/Screens/Menu/ButtonArea.cs index 69ba68442f..4eb91c526f 100644 --- a/osu.Game/Screens/Menu/ButtonArea.cs +++ b/osu.Game/Screens/Menu/ButtonArea.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using JetBrains.Annotations; using osu.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -88,6 +89,7 @@ namespace osu.Game.Screens.Menu public override void Show() => State = Visibility.Visible; + [CanBeNull] public event Action StateChanged; private partial class ButtonAreaBackground : Box, IStateful @@ -146,6 +148,7 @@ namespace osu.Game.Screens.Menu } } + [CanBeNull] public event Action StateChanged; } diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs index e892f9280f..cb27d1ee61 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Development; @@ -19,6 +20,7 @@ namespace osu.Game.Screens.OnlinePlay.Components { public partial class RoomManager : Component, IRoomManager { + [CanBeNull] public event Action RoomsUpdated; private readonly BindableList rooms = new BindableList(); diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index f35b205bc4..4c0219eff5 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -509,7 +509,7 @@ namespace osu.Game.Screens.OnlinePlay.Match private void cancelTrackLooping() { - var track = Beatmap?.Value?.Track; + var track = Beatmap.Value?.Track; if (track != null) track.Looping = false; diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 1cf3d25dad..a260156595 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -109,7 +109,7 @@ namespace osu.Game.Screens.Play.HUD protected override bool OnMouseMove(MouseMoveEvent e) { - positionalAdjust = Vector2.Distance(e.MousePosition, button.ToSpaceOfOtherDrawable(button.DrawRectangle.Centre, Parent)) / 100; + positionalAdjust = Vector2.Distance(e.MousePosition, button.ToSpaceOfOtherDrawable(button.DrawRectangle.Centre, Parent!)) / 100; return base.OnMouseMove(e); } diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index 93c6e72aa2..54c5b578e6 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public TestMultiplayerClient MultiplayerClient => OnlinePlayDependencies.MultiplayerClient; public new TestMultiplayerRoomManager RoomManager => OnlinePlayDependencies.RoomManager; - public TestSpectatorClient SpectatorClient => OnlinePlayDependencies?.SpectatorClient; + public TestSpectatorClient SpectatorClient => OnlinePlayDependencies.SpectatorClient; protected new MultiplayerTestSceneDependencies OnlinePlayDependencies => (MultiplayerTestSceneDependencies)base.OnlinePlayDependencies; @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("join room", () => { - SelectedRoom.Value = CreateRoom(); + SelectedRoom!.Value = CreateRoom(); RoomManager.CreateRoom(SelectedRoom.Value); }); diff --git a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs index 3509519113..eb5184353a 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs @@ -16,31 +16,31 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// The cached . /// - Bindable SelectedRoom { get; } + Bindable? SelectedRoom { get; } /// /// The cached /// - IRoomManager RoomManager { get; } + IRoomManager? RoomManager { get; } /// /// The cached . /// - OngoingOperationTracker OngoingOperationTracker { get; } + OngoingOperationTracker? OngoingOperationTracker { get; } /// /// The cached . /// - OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker { get; } + OnlinePlayBeatmapAvailabilityTracker? AvailabilityTracker { get; } /// /// The cached . /// - TestUserLookupCache UserLookupCache { get; } + TestUserLookupCache? UserLookupCache { get; } /// /// The cached . /// - BeatmapLookupCache BeatmapLookupCache { get; } + BeatmapLookupCache? BeatmapLookupCache { get; } } } diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs index 87488710a7..0118d60dca 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -22,23 +20,23 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// public abstract partial class OnlinePlayTestScene : ScreenTestScene, IOnlinePlayTestSceneDependencies { - public Bindable SelectedRoom => OnlinePlayDependencies?.SelectedRoom; - public IRoomManager RoomManager => OnlinePlayDependencies?.RoomManager; - public OngoingOperationTracker OngoingOperationTracker => OnlinePlayDependencies?.OngoingOperationTracker; - public OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker => OnlinePlayDependencies?.AvailabilityTracker; - public TestUserLookupCache UserLookupCache => OnlinePlayDependencies?.UserLookupCache; - public BeatmapLookupCache BeatmapLookupCache => OnlinePlayDependencies?.BeatmapLookupCache; + public Bindable SelectedRoom => OnlinePlayDependencies.SelectedRoom; + public IRoomManager RoomManager => OnlinePlayDependencies.RoomManager; + public OngoingOperationTracker OngoingOperationTracker => OnlinePlayDependencies.OngoingOperationTracker; + public OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker => OnlinePlayDependencies.AvailabilityTracker; + public TestUserLookupCache UserLookupCache => OnlinePlayDependencies.UserLookupCache; + public BeatmapLookupCache BeatmapLookupCache => OnlinePlayDependencies.BeatmapLookupCache; /// /// All dependencies required for online play components and screens. /// - protected OnlinePlayTestSceneDependencies OnlinePlayDependencies => dependencies?.OnlinePlayDependencies; + protected OnlinePlayTestSceneDependencies OnlinePlayDependencies => dependencies.OnlinePlayDependencies; protected override Container Content => content; private readonly Container content; private readonly Container drawableDependenciesContainer; - private DelegatedDependencyContainer dependencies; + private DelegatedDependencyContainer dependencies = null!; protected OnlinePlayTestScene() { @@ -51,8 +49,10 @@ namespace osu.Game.Tests.Visual.OnlinePlay protected sealed override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - dependencies = new DelegatedDependencyContainer(base.CreateChildDependencies(parent)); - return dependencies; + return dependencies = new DelegatedDependencyContainer(base.CreateChildDependencies(parent)) + { + OnlinePlayDependencies = initDependencies() + }; } public override void SetUpSteps() @@ -62,9 +62,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay AddStep("setup dependencies", () => { // Reset the room dependencies to a fresh state. - drawableDependenciesContainer.Clear(); - dependencies.OnlinePlayDependencies = CreateOnlinePlayDependencies(); - drawableDependenciesContainer.AddRange(OnlinePlayDependencies.DrawableComponents); + dependencies.OnlinePlayDependencies = initDependencies(); var handler = OnlinePlayDependencies.RequestsHandler; @@ -90,6 +88,14 @@ namespace osu.Game.Tests.Visual.OnlinePlay }); } + private OnlinePlayTestSceneDependencies initDependencies() + { + var newDependencies = CreateOnlinePlayDependencies(); + drawableDependenciesContainer.Clear(); + drawableDependenciesContainer.AddRange(newDependencies.DrawableComponents); + return newDependencies; + } + /// /// Creates the room dependencies. Called every . /// @@ -106,7 +112,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// The online play dependencies. /// - public OnlinePlayTestSceneDependencies OnlinePlayDependencies { get; set; } + public OnlinePlayTestSceneDependencies OnlinePlayDependencies { get; set; } = null!; private readonly IReadOnlyDependencyContainer parent; private readonly DependencyContainer injectableDependencies; @@ -122,10 +128,10 @@ namespace osu.Game.Tests.Visual.OnlinePlay } public object Get(Type type) - => OnlinePlayDependencies?.Get(type) ?? parent.Get(type); + => OnlinePlayDependencies.Get(type) ?? parent.Get(type); public object Get(Type type, CacheInfo info) - => OnlinePlayDependencies?.Get(type, info) ?? parent.Get(type, info); + => OnlinePlayDependencies.Get(type, info) ?? parent.Get(type, info); public void Inject(T instance) where T : class, IDependencyInjectionCandidate diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs index 975423d19b..64bd27b871 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs @@ -56,10 +56,10 @@ namespace osu.Game.Tests.Visual.OnlinePlay CacheAs(BeatmapLookupCache); } - public object Get(Type type) + public object? Get(Type type) => dependencies.Get(type); - public object Get(Type type, CacheInfo info) + public object? Get(Type type, CacheInfo info) => dependencies.Get(type, info); public void Inject(T instance) diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index f371cf721f..c9acfa0ee5 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -171,10 +171,10 @@ namespace osu.Game.Tests.Visual public IRenderer Renderer => host.Renderer; public AudioManager AudioManager => Audio; - public IResourceStore Files => null; + public IResourceStore Files => null!; public new IResourceStore Resources => base.Resources; public IResourceStore CreateTextureLoaderStore(IResourceStore underlyingStore) => host.CreateTextureLoaderStore(underlyingStore); - RealmAccess IStorageResourceProvider.RealmAccess => null; + RealmAccess IStorageResourceProvider.RealmAccess => null!; #endregion diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index c8c5d6745c..1bf8aa7b0b 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -66,6 +66,7 @@ HINT WARNING DO_NOT_SHOW + HINT WARNING WARNING WARNING @@ -81,6 +82,7 @@ WARNING WARNING HINT + HINT WARNING HINT DO_NOT_SHOW @@ -165,6 +167,7 @@ WARNING WARNING WARNING + HINT WARNING WARNING WARNING @@ -251,6 +254,7 @@ HINT DO_NOT_SHOW WARNING + HINT WARNING WARNING WARNING @@ -263,6 +267,7 @@ WARNING WARNING WARNING + HINT WARNING HINT HINT From 20ae88b0a0dd9a23dfbb7982d292e475f3a959c7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 2 Feb 2024 21:05:12 +0900 Subject: [PATCH 2862/4092] Revert unnecessary changes --- .../Tests/Visual/Multiplayer/MultiplayerTestScene.cs | 2 +- .../OnlinePlay/IOnlinePlayTestSceneDependencies.cs | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index 54c5b578e6..80c69db8b1 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("join room", () => { - SelectedRoom!.Value = CreateRoom(); + SelectedRoom.Value = CreateRoom(); RoomManager.CreateRoom(SelectedRoom.Value); }); diff --git a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs index eb5184353a..3509519113 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs @@ -16,31 +16,31 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// The cached . /// - Bindable? SelectedRoom { get; } + Bindable SelectedRoom { get; } /// /// The cached /// - IRoomManager? RoomManager { get; } + IRoomManager RoomManager { get; } /// /// The cached . /// - OngoingOperationTracker? OngoingOperationTracker { get; } + OngoingOperationTracker OngoingOperationTracker { get; } /// /// The cached . /// - OnlinePlayBeatmapAvailabilityTracker? AvailabilityTracker { get; } + OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker { get; } /// /// The cached . /// - TestUserLookupCache? UserLookupCache { get; } + TestUserLookupCache UserLookupCache { get; } /// /// The cached . /// - BeatmapLookupCache? BeatmapLookupCache { get; } + BeatmapLookupCache BeatmapLookupCache { get; } } } From fe0433e6ecd1a69132fee41626562590c4b688ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 2 Feb 2024 13:24:59 +0100 Subject: [PATCH 2863/4092] Remove redundant default value assignments --- osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs b/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs index f767d9f7a3..5930c077a4 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Editing createSection(); - TextBox textbox = null!; + TextBox textbox; AddStep("focus first textbox", () => { @@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual.Editing createSection(); - TextBox textbox = null!; + TextBox textbox; AddStep("focus first textbox", () => { From 6e4d52863c81e28a6b4389e844081d016a4cdee7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 2 Feb 2024 00:50:26 +0900 Subject: [PATCH 2864/4092] Upgrade to .NET 8 SDK --- .../osu.Game.Rulesets.EmptyFreeform.Tests.csproj | 2 +- .../osu.Game.Rulesets.EmptyFreeform.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.csproj | 2 +- .../osu.Game.Rulesets.EmptyScrolling.Tests.csproj | 2 +- .../osu.Game.Rulesets.EmptyScrolling.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.csproj | 2 +- global.json | 8 ++++---- osu.Android/osu.Android.csproj | 2 +- osu.Desktop/osu.Desktop.csproj | 2 +- osu.Game.Benchmarks/osu.Game.Benchmarks.csproj | 2 +- .../osu.Game.Rulesets.Catch.Tests.Android.csproj | 4 ++-- .../osu.Game.Rulesets.Catch.Tests.iOS.csproj | 2 +- .../osu.Game.Rulesets.Catch.Tests.csproj | 2 +- osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj | 2 +- .../osu.Game.Rulesets.Mania.Tests.Android.csproj | 4 ++-- .../osu.Game.Rulesets.Mania.Tests.iOS.csproj | 2 +- .../osu.Game.Rulesets.Mania.Tests.csproj | 2 +- osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj | 2 +- .../osu.Game.Rulesets.Osu.Tests.Android.csproj | 4 ++-- .../osu.Game.Rulesets.Osu.Tests.iOS.csproj | 2 +- .../osu.Game.Rulesets.Osu.Tests.csproj | 2 +- osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj | 2 +- .../osu.Game.Rulesets.Taiko.Tests.Android.csproj | 4 ++-- .../osu.Game.Rulesets.Taiko.Tests.iOS.csproj | 2 +- .../osu.Game.Rulesets.Taiko.Tests.csproj | 2 +- osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj | 2 +- osu.Game.Tests.Android/osu.Game.Tests.Android.csproj | 2 +- osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- .../osu.Game.Tournament.Tests.csproj | 2 +- osu.Game.Tournament/osu.Game.Tournament.csproj | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS/osu.iOS.csproj | 2 +- 35 files changed, 42 insertions(+), 42 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj index 5babdb47ff..7d43eb2b05 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj @@ -18,7 +18,7 @@ WinExe - net6.0 + net8.0 osu.Game.Rulesets.EmptyFreeform.Tests diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj index d09e7647e0..89abd5665c 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj @@ -1,6 +1,6 @@  - net6.0 + net8.0 osu.Game.Rulesets.EmptyFreeform Library AnyCPU diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index 5d64ca832a..7dc8a1336b 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -18,7 +18,7 @@ WinExe - net6.0 + net8.0 osu.Game.Rulesets.Pippidon.Tests diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj index 9c8867f4ef..165b6b6c6b 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj @@ -1,6 +1,6 @@  - net6.0 + net8.0 osu.Game.Rulesets.Pippidon Library AnyCPU diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj index 6796a8962b..9c4c8217f0 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj @@ -18,7 +18,7 @@ WinExe - net6.0 + net8.0 osu.Game.Rulesets.EmptyScrolling.Tests diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj index 5bf3884f53..6d9565a6f2 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj @@ -1,6 +1,6 @@  - net6.0 + net8.0 osu.Game.Rulesets.EmptyScrolling Library AnyCPU diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index 5d64ca832a..7dc8a1336b 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -18,7 +18,7 @@ WinExe - net6.0 + net8.0 osu.Game.Rulesets.Pippidon.Tests diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj index 9c8867f4ef..165b6b6c6b 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj @@ -1,6 +1,6 @@  - net6.0 + net8.0 osu.Game.Rulesets.Pippidon Library AnyCPU diff --git a/global.json b/global.json index 5dcd5f425a..da113e4cbd 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,7 @@ { "sdk": { - "version": "6.0.100", - "rollForward": "latestFeature" + "version": "8.0.0", + "rollForward": "latestFeature", + "allowPrerelease": false } -} - +} \ No newline at end of file diff --git a/osu.Android/osu.Android.csproj b/osu.Android/osu.Android.csproj index 1507bfaa29..17e049a569 100644 --- a/osu.Android/osu.Android.csproj +++ b/osu.Android/osu.Android.csproj @@ -1,7 +1,7 @@  - net6.0-android + net8.0-android Exe osu.Android osu.Android diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index d6a11fa924..cf2ec6e681 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -1,6 +1,6 @@  - net6.0 + net8.0 WinExe true A free-to-win rhythm game. Rhythm is just a *click* away! diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj index 47c93fbd02..64da5412a8 100644 --- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj +++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 Exe false diff --git a/osu.Game.Rulesets.Catch.Tests.Android/osu.Game.Rulesets.Catch.Tests.Android.csproj b/osu.Game.Rulesets.Catch.Tests.Android/osu.Game.Rulesets.Catch.Tests.Android.csproj index 4ee3219442..4b2e54be67 100644 --- a/osu.Game.Rulesets.Catch.Tests.Android/osu.Game.Rulesets.Catch.Tests.Android.csproj +++ b/osu.Game.Rulesets.Catch.Tests.Android/osu.Game.Rulesets.Catch.Tests.Android.csproj @@ -1,7 +1,7 @@  - net6.0-android + net8.0-android Exe osu.Game.Rulesets.Catch.Tests osu.Game.Rulesets.Catch.Tests.Android @@ -21,4 +21,4 @@ - \ No newline at end of file + diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj b/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj index acf12bb0ac..9c262a752a 100644 --- a/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj +++ b/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj @@ -1,7 +1,7 @@  Exe - net6.0-ios + net8.0-ios 13.4 osu.Game.Rulesets.Catch.Tests osu.Game.Rulesets.Catch.Tests.iOS diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index 0a77845343..619081c754 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -7,7 +7,7 @@ WinExe - net6.0 + net8.0 diff --git a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj index ecce7c1b3f..a5138ffb39 100644 --- a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj +++ b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj @@ -1,6 +1,6 @@  - net6.0 + net8.0 Library true catch the fruit. to the beat. diff --git a/osu.Game.Rulesets.Mania.Tests.Android/osu.Game.Rulesets.Mania.Tests.Android.csproj b/osu.Game.Rulesets.Mania.Tests.Android/osu.Game.Rulesets.Mania.Tests.Android.csproj index 25335754d2..2866508a02 100644 --- a/osu.Game.Rulesets.Mania.Tests.Android/osu.Game.Rulesets.Mania.Tests.Android.csproj +++ b/osu.Game.Rulesets.Mania.Tests.Android/osu.Game.Rulesets.Mania.Tests.Android.csproj @@ -1,7 +1,7 @@  - net6.0-android + net8.0-android Exe osu.Game.Rulesets.Mania.Tests osu.Game.Rulesets.Mania.Tests.Android @@ -21,4 +21,4 @@ - \ No newline at end of file + diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj b/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj index 51e07dd6c1..d51e541e95 100644 --- a/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj +++ b/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj @@ -1,7 +1,7 @@  Exe - net6.0-ios + net8.0-ios 13.4 osu.Game.Rulesets.Mania.Tests osu.Game.Rulesets.Mania.Tests.iOS diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index 877b9c3ea4..eee06acdb8 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -7,7 +7,7 @@ WinExe - net6.0 + net8.0 diff --git a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj index 72f172188e..3bca938450 100644 --- a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj +++ b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj @@ -1,6 +1,6 @@  - net6.0 + net8.0 Library true smash the keys. to the beat. diff --git a/osu.Game.Rulesets.Osu.Tests.Android/osu.Game.Rulesets.Osu.Tests.Android.csproj b/osu.Game.Rulesets.Osu.Tests.Android/osu.Game.Rulesets.Osu.Tests.Android.csproj index e8a46a9828..b79de6d40b 100644 --- a/osu.Game.Rulesets.Osu.Tests.Android/osu.Game.Rulesets.Osu.Tests.Android.csproj +++ b/osu.Game.Rulesets.Osu.Tests.Android/osu.Game.Rulesets.Osu.Tests.Android.csproj @@ -1,7 +1,7 @@  - net6.0-android + net8.0-android Exe osu.Game.Rulesets.Osu.Tests osu.Game.Rulesets.Osu.Tests.Android @@ -24,4 +24,4 @@ - \ No newline at end of file + diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj b/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj index 7d50deb8ba..cc0233d7fd 100644 --- a/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj +++ b/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj @@ -1,7 +1,7 @@  Exe - net6.0-ios + net8.0-ios 13.4 Exe osu.Game.Rulesets.Osu.Tests diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index 9c248abd66..ea54c8d313 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -8,7 +8,7 @@ WinExe - net6.0 + net8.0 diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj index 75656e2976..518ab362ca 100644 --- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj +++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj @@ -1,6 +1,6 @@  - net6.0 + net8.0 Library true click the circles. to the beat. diff --git a/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj b/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj index a639326ebd..ee973e8544 100644 --- a/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj +++ b/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj @@ -1,7 +1,7 @@  - net6.0-android + net8.0-android Exe osu.Game.Rulesets.Taiko.Tests osu.Game.Rulesets.Taiko.Tests.Android @@ -21,4 +21,4 @@ - \ No newline at end of file + diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj b/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj index e648a11299..ee2d4d703e 100644 --- a/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj +++ b/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj @@ -1,7 +1,7 @@  Exe - net6.0-ios + net8.0-ios 13.4 osu.Game.Rulesets.Taiko.Tests osu.Game.Rulesets.Taiko.Tests.iOS diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index 4eb6b0ab3d..26afd42445 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -7,7 +7,7 @@ WinExe - net6.0 + net8.0 diff --git a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj index f0e1cb8e8f..cacba55c2a 100644 --- a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj +++ b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj @@ -1,6 +1,6 @@  - net6.0 + net8.0 Library true bash the drum. to the beat. diff --git a/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj b/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj index b745d91980..889f0a3583 100644 --- a/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj +++ b/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj @@ -1,7 +1,7 @@  - net6.0-android + net8.0-android Exe osu.Game.Tests osu.Game.Tests.Android diff --git a/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj index 79771fcd50..e4b9d2ba94 100644 --- a/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj +++ b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj @@ -1,7 +1,7 @@  Exe - net6.0-ios + net8.0-ios 13.4 osu.Game.Tests osu.Game.Tests.iOS diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 7b08524240..c0bbdfb4ed 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -10,7 +10,7 @@ WinExe - net6.0 + net8.0 tests.ruleset diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index 3b00f103c4..8f1d7114b1 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -10,7 +10,7 @@ WinExe - net6.0 + net8.0 diff --git a/osu.Game.Tournament/osu.Game.Tournament.csproj b/osu.Game.Tournament/osu.Game.Tournament.csproj index ab67e490cd..c8578ac464 100644 --- a/osu.Game.Tournament/osu.Game.Tournament.csproj +++ b/osu.Game.Tournament/osu.Game.Tournament.csproj @@ -1,6 +1,6 @@  - net6.0 + net8.0 Library true tools for tournaments. diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index a5d212bffe..caffebd063 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -1,6 +1,6 @@  - net6.0 + net8.0 Library true 10 diff --git a/osu.iOS/osu.iOS.csproj b/osu.iOS/osu.iOS.csproj index 2d61b73125..6e9449de95 100644 --- a/osu.iOS/osu.iOS.csproj +++ b/osu.iOS/osu.iOS.csproj @@ -1,6 +1,6 @@  - net6.0-ios + net8.0-ios 13.4 Exe true From f06642c315eb7b35298fd4f1f8e3ff9e9d075766 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 2 Feb 2024 00:51:43 +0900 Subject: [PATCH 2865/4092] Upgrade to C# 12 --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index b08283f071..2d289d0f22 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@  - 10.0 + 12.0 true enable From c8b4ff2b47d53f0a1397f05f481f3d47d70aca0e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 2 Feb 2024 00:51:55 +0900 Subject: [PATCH 2866/4092] Fix compile error (reserved name) --- osu.Game/IO/HardLinkHelper.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/IO/HardLinkHelper.cs b/osu.Game/IO/HardLinkHelper.cs index 619bfdad6e..ad57f87d10 100644 --- a/osu.Game/IO/HardLinkHelper.cs +++ b/osu.Game/IO/HardLinkHelper.cs @@ -153,12 +153,12 @@ namespace osu.Game.IO public static extern int link(string oldpath, string newpath); [DllImport("libc", SetLastError = true)] - private static extern int stat(string pathname, out struct_stat statbuf); + private static extern int stat(string pathname, out Stat statbuf); // ReSharper disable once InconsistentNaming // Struct layout is likely non-portable across unices. Tread with caution. [StructLayout(LayoutKind.Sequential)] - private struct struct_stat + private struct Stat { public readonly long st_dev; public readonly long st_ino; @@ -170,14 +170,14 @@ namespace osu.Game.IO public readonly long st_size; public readonly long st_blksize; public readonly long st_blocks; - public readonly timespec st_atim; - public readonly timespec st_mtim; - public readonly timespec st_ctim; + public readonly Timespec st_atim; + public readonly Timespec st_mtim; + public readonly Timespec st_ctim; } // ReSharper disable once InconsistentNaming [StructLayout(LayoutKind.Sequential)] - private struct timespec + private struct Timespec { public readonly long tv_sec; public readonly long tv_nsec; From 630278f6e727ce1b7d3cc727e60087db4fd61316 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 2 Feb 2024 01:08:15 +0900 Subject: [PATCH 2867/4092] Replace UseMauiEssentials with PackageReference --- osu.Android/osu.Android.csproj | 4 +++- osu.iOS/osu.iOS.csproj | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Android/osu.Android.csproj b/osu.Android/osu.Android.csproj index 17e049a569..0e79e0ab3e 100644 --- a/osu.Android/osu.Android.csproj +++ b/osu.Android/osu.Android.csproj @@ -5,7 +5,6 @@ Exe osu.Android osu.Android - true false 0.0.0 @@ -19,4 +18,7 @@ + + + diff --git a/osu.iOS/osu.iOS.csproj b/osu.iOS/osu.iOS.csproj index 6e9449de95..19c0c610b5 100644 --- a/osu.iOS/osu.iOS.csproj +++ b/osu.iOS/osu.iOS.csproj @@ -3,7 +3,6 @@ net8.0-ios 13.4 Exe - true 0.1.0 $(Version) $(Version) @@ -16,4 +15,7 @@ + + + From a217a7f8cf74de72a416cda75deb18fdcb6d0712 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 2 Feb 2024 01:11:58 +0900 Subject: [PATCH 2868/4092] Adjust CI workflows --- .github/workflows/ci.yml | 23 +++++++------------ .../workflows/update-web-mod-definitions.yml | 4 ++-- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 103e4dbc30..722e8910d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,17 +15,10 @@ jobs: - name: Checkout uses: actions/checkout@v3 - # FIXME: Tools won't run in .NET 6.0 unless you install 3.1.x LTS side by side. - # https://itnext.io/how-to-support-multiple-net-sdks-in-github-actions-workflows-b988daa884e - - name: Install .NET 3.1.x LTS + - name: Install .NET 8.0.x uses: actions/setup-dotnet@v3 with: - dotnet-version: "3.1.x" - - - name: Install .NET 6.0.x - uses: actions/setup-dotnet@v3 - with: - dotnet-version: "6.0.x" + dotnet-version: "8.0.x" - name: Restore Tools run: dotnet tool restore @@ -79,10 +72,10 @@ jobs: - name: Checkout uses: actions/checkout@v3 - - name: Install .NET 6.0.x + - name: Install .NET 8.0.x uses: actions/setup-dotnet@v3 with: - dotnet-version: "6.0.x" + dotnet-version: "8.0.x" - name: Compile run: dotnet build -c Debug -warnaserror osu.Desktop.slnf @@ -114,10 +107,10 @@ jobs: distribution: microsoft java-version: 11 - - name: Install .NET 6.0.x + - name: Install .NET 8.0.x uses: actions/setup-dotnet@v3 with: - dotnet-version: "6.0.x" + dotnet-version: "8.0.x" - name: Install .NET workloads run: dotnet workload install maui-android @@ -135,10 +128,10 @@ jobs: - name: Checkout uses: actions/checkout@v3 - - name: Install .NET 6.0.x + - name: Install .NET 8.0.x uses: actions/setup-dotnet@v3 with: - dotnet-version: "6.0.x" + dotnet-version: "8.0.x" - name: Install .NET Workloads run: dotnet workload install maui-ios diff --git a/.github/workflows/update-web-mod-definitions.yml b/.github/workflows/update-web-mod-definitions.yml index 32d3d37ffe..5827a6cdbf 100644 --- a/.github/workflows/update-web-mod-definitions.yml +++ b/.github/workflows/update-web-mod-definitions.yml @@ -12,10 +12,10 @@ jobs: name: Update osu-web mod definitions runs-on: ubuntu-latest steps: - - name: Install .NET 6.0.x + - name: Install .NET 8.0.x uses: actions/setup-dotnet@v3 with: - dotnet-version: "6.0.x" + dotnet-version: "8.0.x" - name: Checkout ppy/osu uses: actions/checkout@v3 From dda691b8c219f104a938d5a8c0bc29a88acfc19e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 2 Feb 2024 01:14:55 +0900 Subject: [PATCH 2869/4092] Fix NVAPI compile error --- osu.Desktop/NVAPI.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/NVAPI.cs b/osu.Desktop/NVAPI.cs index bb3a59cc7f..a34b762738 100644 --- a/osu.Desktop/NVAPI.cs +++ b/osu.Desktop/NVAPI.cs @@ -456,7 +456,7 @@ namespace osu.Desktop [MarshalAs(UnmanagedType.ByValTStr, SizeConst = NVAPI.UNICODE_STRING_MAX)] public string ProfileName; - [MarshalAs(UnmanagedType.ByValArray)] + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public uint[] GPUSupport; public uint IsPredefined; From 38ed426f8fa90c2bfcd816317349767f1502082d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 2 Feb 2024 02:03:37 +0900 Subject: [PATCH 2870/4092] Fix more compiler warnings/errors --- .../ruleset-empty/Directory.Build.props | 10 ++++ Templates/Rulesets/ruleset-empty/app.manifest | 46 +++++++++++++++++++ .../ruleset-example/Directory.Build.props | 10 ++++ .../Rulesets/ruleset-example/app.manifest | 46 +++++++++++++++++++ .../Directory.Build.props | 10 ++++ .../ruleset-scrolling-empty/app.manifest | 46 +++++++++++++++++++ .../Directory.Build.props | 10 ++++ .../ruleset-scrolling-example/app.manifest | 46 +++++++++++++++++++ 8 files changed, 224 insertions(+) create mode 100644 Templates/Rulesets/ruleset-empty/Directory.Build.props create mode 100644 Templates/Rulesets/ruleset-empty/app.manifest create mode 100644 Templates/Rulesets/ruleset-example/Directory.Build.props create mode 100644 Templates/Rulesets/ruleset-example/app.manifest create mode 100644 Templates/Rulesets/ruleset-scrolling-empty/Directory.Build.props create mode 100644 Templates/Rulesets/ruleset-scrolling-empty/app.manifest create mode 100644 Templates/Rulesets/ruleset-scrolling-example/Directory.Build.props create mode 100644 Templates/Rulesets/ruleset-scrolling-example/app.manifest diff --git a/Templates/Rulesets/ruleset-empty/Directory.Build.props b/Templates/Rulesets/ruleset-empty/Directory.Build.props new file mode 100644 index 0000000000..74d05ff690 --- /dev/null +++ b/Templates/Rulesets/ruleset-empty/Directory.Build.props @@ -0,0 +1,10 @@ + + + + $(MSBuildThisFileDirectory)app.manifest + + + true + $(NoWarn);CS1591 + + diff --git a/Templates/Rulesets/ruleset-empty/app.manifest b/Templates/Rulesets/ruleset-empty/app.manifest new file mode 100644 index 0000000000..1c1e5f540c --- /dev/null +++ b/Templates/Rulesets/ruleset-empty/app.manifest @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + diff --git a/Templates/Rulesets/ruleset-example/Directory.Build.props b/Templates/Rulesets/ruleset-example/Directory.Build.props new file mode 100644 index 0000000000..74d05ff690 --- /dev/null +++ b/Templates/Rulesets/ruleset-example/Directory.Build.props @@ -0,0 +1,10 @@ + + + + $(MSBuildThisFileDirectory)app.manifest + + + true + $(NoWarn);CS1591 + + diff --git a/Templates/Rulesets/ruleset-example/app.manifest b/Templates/Rulesets/ruleset-example/app.manifest new file mode 100644 index 0000000000..1c1e5f540c --- /dev/null +++ b/Templates/Rulesets/ruleset-example/app.manifest @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + diff --git a/Templates/Rulesets/ruleset-scrolling-empty/Directory.Build.props b/Templates/Rulesets/ruleset-scrolling-empty/Directory.Build.props new file mode 100644 index 0000000000..74d05ff690 --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-empty/Directory.Build.props @@ -0,0 +1,10 @@ + + + + $(MSBuildThisFileDirectory)app.manifest + + + true + $(NoWarn);CS1591 + + diff --git a/Templates/Rulesets/ruleset-scrolling-empty/app.manifest b/Templates/Rulesets/ruleset-scrolling-empty/app.manifest new file mode 100644 index 0000000000..1c1e5f540c --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-empty/app.manifest @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + diff --git a/Templates/Rulesets/ruleset-scrolling-example/Directory.Build.props b/Templates/Rulesets/ruleset-scrolling-example/Directory.Build.props new file mode 100644 index 0000000000..74d05ff690 --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-example/Directory.Build.props @@ -0,0 +1,10 @@ + + + + $(MSBuildThisFileDirectory)app.manifest + + + true + $(NoWarn);CS1591 + + diff --git a/Templates/Rulesets/ruleset-scrolling-example/app.manifest b/Templates/Rulesets/ruleset-scrolling-example/app.manifest new file mode 100644 index 0000000000..1c1e5f540c --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-example/app.manifest @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + From ec4f3577c0659709a26383af1c3818593aef1ebc Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 2 Feb 2024 02:09:00 +0900 Subject: [PATCH 2871/4092] Use Xcode 15.2 --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 722e8910d5..de902df93f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -136,5 +136,8 @@ jobs: - name: Install .NET Workloads run: dotnet workload install maui-ios + - name: Select Xcode 15.2 + run: sudo xcode-select -s /Applications/Xcode_15.2.app/Contents/Developer + - name: Build run: dotnet build -c Debug osu.iOS From 19ea4842400f45cb78cf55b00cee69669c5b1369 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 2 Feb 2024 02:18:22 +0900 Subject: [PATCH 2872/4092] Adjust pragma disable to fix Android compile --- osu.Android/OsuGameActivity.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index 33ffed432e..bbee491d90 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -72,9 +72,9 @@ namespace osu.Android Debug.Assert(Resources?.DisplayMetrics != null); Point displaySize = new Point(); -#pragma warning disable 618 // GetSize is deprecated +#pragma warning disable CA1422 // GetSize is deprecated WindowManager.DefaultDisplay.GetSize(displaySize); -#pragma warning restore 618 +#pragma warning restore CA1422 float smallestWidthDp = Math.Min(displaySize.X, displaySize.Y) / Resources.DisplayMetrics.Density; bool isTablet = smallestWidthDp >= 600f; From 497213d529f57e1f057f69720d8fe7e3e22c806d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 2 Feb 2024 02:25:52 +0900 Subject: [PATCH 2873/4092] Re-enable LLVM for now --- osu.Android/osu.Android.csproj | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Android/osu.Android.csproj b/osu.Android/osu.Android.csproj index 0e79e0ab3e..be2e669728 100644 --- a/osu.Android/osu.Android.csproj +++ b/osu.Android/osu.Android.csproj @@ -5,8 +5,6 @@ Exe osu.Android osu.Android - - false 0.0.0 1 $(Version) From dbd4397bef2419ea4d9b1b8fdc44068358d59159 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 2 Feb 2024 13:32:16 +0100 Subject: [PATCH 2874/4092] Attempt to salvage test by using until step --- .../Visual/Navigation/TestSceneBeatmapEditorNavigation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs index 370c40222e..e3a8e575f8 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs @@ -79,7 +79,7 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("exit", () => Game.ChildrenOfType().Single().Exit()); - AddAssert("save dialog displayed", () => Game.ChildrenOfType().Single().CurrentDialog is PromptForSaveDialog); + AddUntilStep("save dialog displayed", () => Game.ChildrenOfType().SingleOrDefault()?.CurrentDialog is PromptForSaveDialog); } [Test] From 20504c55ef02b83ea9c575d3b435148a4eff8931 Mon Sep 17 00:00:00 2001 From: Loreos7 Date: Fri, 2 Feb 2024 15:32:55 +0300 Subject: [PATCH 2875/4092] localise storage error popup dialog --- .../Localisation/StorageErrorDialogStrings.cs | 49 +++++++++++++++++++ osu.Game/Screens/Menu/StorageErrorDialog.cs | 17 ++++--- 2 files changed, 58 insertions(+), 8 deletions(-) create mode 100644 osu.Game/Localisation/StorageErrorDialogStrings.cs diff --git a/osu.Game/Localisation/StorageErrorDialogStrings.cs b/osu.Game/Localisation/StorageErrorDialogStrings.cs new file mode 100644 index 0000000000..6ad388dd1f --- /dev/null +++ b/osu.Game/Localisation/StorageErrorDialogStrings.cs @@ -0,0 +1,49 @@ +// 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.Localisation; + +namespace osu.Game.Localisation +{ + public static class StorageErrorDialogStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.StorageErrorDialog"; + + /// + /// "osu! storage error" + /// + public static LocalisableString StorageError => new TranslatableString(getKey(@"storage_error"), @"osu! storage error"); + + /// + /// "The specified osu! data location ("{0}") is not accessible. If it is on external storage, please reconnect the device and try again." + /// + public static LocalisableString LocationIsNotAccessible(string? loc) => new TranslatableString(getKey(@"location_is_not_accessible"), @"The specified osu! data location (""{0}"") is not accessible. If it is on external storage, please reconnect the device and try again.", loc); + + /// + /// "The specified osu! data location ("{0}") is empty. If you have moved the files, please close osu! and move them back." + /// + public static LocalisableString LocationIsEmpty(string? loc2) => new TranslatableString(getKey(@"location_is_empty"), @"The specified osu! data location (""{0}"") is empty. If you have moved the files, please close osu! and move them back.", loc2); + + /// + /// "Try again" + /// + public static LocalisableString TryAgain => new TranslatableString(getKey(@"try_again"), @"Try again"); + + /// + /// "Use default location until restart" + /// + public static LocalisableString UseDefaultLocation => new TranslatableString(getKey(@"use_default_location"), @"Use default location until restart"); + + /// + /// "Reset to default location" + /// + public static LocalisableString ResetToDefaultLocation => new TranslatableString(getKey(@"reset_to_default_location"), @"Reset to default location"); + + /// + /// "Start fresh at specified location" + /// + public static LocalisableString StartFresh => new TranslatableString(getKey(@"start_fresh"), @"Start fresh at specified location"); + + private static string getKey(string key) => $@"{prefix}:{key}"; + } +} diff --git a/osu.Game/Screens/Menu/StorageErrorDialog.cs b/osu.Game/Screens/Menu/StorageErrorDialog.cs index dd43289873..b48046d190 100644 --- a/osu.Game/Screens/Menu/StorageErrorDialog.cs +++ b/osu.Game/Screens/Menu/StorageErrorDialog.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using osu.Game.IO; +using osu.Game.Localisation; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; @@ -17,7 +18,7 @@ namespace osu.Game.Screens.Menu public StorageErrorDialog(OsuStorage storage, OsuStorageError error) { - HeaderText = "osu! storage error"; + HeaderText = StorageErrorDialogStrings.StorageError; Icon = FontAwesome.Solid.ExclamationTriangle; var buttons = new List(); @@ -25,13 +26,13 @@ namespace osu.Game.Screens.Menu switch (error) { case OsuStorageError.NotAccessible: - BodyText = $"The specified osu! data location (\"{storage.CustomStoragePath}\") is not accessible. If it is on external storage, please reconnect the device and try again."; + BodyText = StorageErrorDialogStrings.LocationIsNotAccessible(storage.CustomStoragePath); buttons.AddRange(new PopupDialogButton[] { new PopupDialogCancelButton { - Text = "Try again", + Text = StorageErrorDialogStrings.TryAgain, Action = () => { if (!storage.TryChangeToCustomStorage(out var nextError)) @@ -40,29 +41,29 @@ namespace osu.Game.Screens.Menu }, new PopupDialogCancelButton { - Text = "Use default location until restart", + Text = StorageErrorDialogStrings.UseDefaultLocation, }, new PopupDialogOkButton { - Text = "Reset to default location", + Text = StorageErrorDialogStrings.ResetToDefaultLocation, Action = storage.ResetCustomStoragePath }, }); break; case OsuStorageError.AccessibleButEmpty: - BodyText = $"The specified osu! data location (\"{storage.CustomStoragePath}\") is empty. If you have moved the files, please close osu! and move them back."; + BodyText = StorageErrorDialogStrings.LocationIsEmpty(storage.CustomStoragePath); // Todo: Provide the option to search for the files similar to migration. buttons.AddRange(new PopupDialogButton[] { new PopupDialogCancelButton { - Text = "Start fresh at specified location" + Text = StorageErrorDialogStrings.StartFresh }, new PopupDialogOkButton { - Text = "Reset to default location", + Text = StorageErrorDialogStrings.ResetToDefaultLocation, Action = storage.ResetCustomStoragePath }, }); From 8e8ba9e77f94a33f06a05190b86c76da90b3c3a6 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 2 Feb 2024 21:36:41 +0900 Subject: [PATCH 2876/4092] Fix more CI inspections --- osu.Desktop/NVAPI.cs | 2 +- osu.Game.Tests/NonVisual/TaskChainTest.cs | 2 +- osu.Game/Online/Chat/WebSocketChatClient.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Desktop/NVAPI.cs b/osu.Desktop/NVAPI.cs index a34b762738..78a814c585 100644 --- a/osu.Desktop/NVAPI.cs +++ b/osu.Desktop/NVAPI.cs @@ -138,7 +138,7 @@ namespace osu.Desktop return false; // Make sure that this is a laptop. - var gpus = new IntPtr[64]; + IntPtr[] gpus = new IntPtr[64]; if (checkError(EnumPhysicalGPUs(gpus, out int gpuCount))) return false; diff --git a/osu.Game.Tests/NonVisual/TaskChainTest.cs b/osu.Game.Tests/NonVisual/TaskChainTest.cs index ad1a3fd63f..2ac89efb69 100644 --- a/osu.Game.Tests/NonVisual/TaskChainTest.cs +++ b/osu.Game.Tests/NonVisual/TaskChainTest.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.NonVisual var task3 = addTask(); // Cancel task2, allow task3 to complete. - task2.cancellation.Cancel(); + await task2.cancellation.CancelAsync(); task2.mutex.Set(); task3.mutex.Set(); diff --git a/osu.Game/Online/Chat/WebSocketChatClient.cs b/osu.Game/Online/Chat/WebSocketChatClient.cs index b74f8bec4b..8e1b501b25 100644 --- a/osu.Game/Online/Chat/WebSocketChatClient.cs +++ b/osu.Game/Online/Chat/WebSocketChatClient.cs @@ -61,7 +61,7 @@ namespace osu.Game.Online.Chat { await client.SendAsync(new StartChatRequest()).ConfigureAwait(false); Logger.Log(@"Now listening to websocket chat messages.", LoggingTarget.Network); - chatStartCancellationSource.Cancel(); + await chatStartCancellationSource.CancelAsync().ConfigureAwait(false); } catch (Exception ex) { From 50f9c6102975a6fac1c2ddba4bdd1f2d203e4862 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 2 Feb 2024 22:34:48 +0900 Subject: [PATCH 2877/4092] Fix multiplayer tests --- .../Visual/OnlinePlay/OnlinePlayTestScene.cs | 27 ++++++------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs index 0118d60dca..eebc3503bc 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// All dependencies required for online play components and screens. /// - protected OnlinePlayTestSceneDependencies OnlinePlayDependencies => dependencies.OnlinePlayDependencies; + protected OnlinePlayTestSceneDependencies OnlinePlayDependencies => dependencies.OnlinePlayDependencies!; protected override Container Content => content; @@ -48,12 +48,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay } protected sealed override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - return dependencies = new DelegatedDependencyContainer(base.CreateChildDependencies(parent)) - { - OnlinePlayDependencies = initDependencies() - }; - } + => dependencies = new DelegatedDependencyContainer(base.CreateChildDependencies(parent)); public override void SetUpSteps() { @@ -62,7 +57,9 @@ namespace osu.Game.Tests.Visual.OnlinePlay AddStep("setup dependencies", () => { // Reset the room dependencies to a fresh state. - dependencies.OnlinePlayDependencies = initDependencies(); + dependencies.OnlinePlayDependencies = CreateOnlinePlayDependencies(); + drawableDependenciesContainer.Clear(); + drawableDependenciesContainer.AddRange(dependencies.OnlinePlayDependencies.DrawableComponents); var handler = OnlinePlayDependencies.RequestsHandler; @@ -88,14 +85,6 @@ namespace osu.Game.Tests.Visual.OnlinePlay }); } - private OnlinePlayTestSceneDependencies initDependencies() - { - var newDependencies = CreateOnlinePlayDependencies(); - drawableDependenciesContainer.Clear(); - drawableDependenciesContainer.AddRange(newDependencies.DrawableComponents); - return newDependencies; - } - /// /// Creates the room dependencies. Called every . /// @@ -112,7 +101,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// The online play dependencies. /// - public OnlinePlayTestSceneDependencies OnlinePlayDependencies { get; set; } = null!; + public OnlinePlayTestSceneDependencies? OnlinePlayDependencies { get; set; } private readonly IReadOnlyDependencyContainer parent; private readonly DependencyContainer injectableDependencies; @@ -128,10 +117,10 @@ namespace osu.Game.Tests.Visual.OnlinePlay } public object Get(Type type) - => OnlinePlayDependencies.Get(type) ?? parent.Get(type); + => OnlinePlayDependencies?.Get(type) ?? parent.Get(type); public object Get(Type type, CacheInfo info) - => OnlinePlayDependencies.Get(type, info) ?? parent.Get(type, info); + => OnlinePlayDependencies?.Get(type, info) ?? parent.Get(type, info); public void Inject(T instance) where T : class, IDependencyInjectionCandidate From 2ab967f783fd25d95e9142bffb79eb78e03e7cbc Mon Sep 17 00:00:00 2001 From: Mike Will Date: Fri, 2 Feb 2024 13:27:26 -0500 Subject: [PATCH 2878/4092] Increase precision of aggregate volume rounding It should be large enough to account for true accuracy but small enough to disregard any hair-thin inaccuracy found at the very end of the float value. --- osu.Game/Screens/Play/PlayerLoader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 40ffa844da..aafa93c122 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -551,7 +551,7 @@ namespace osu.Game.Screens.Play if (!muteWarningShownOnce.Value) { // Checks if the notification has not been shown yet and also if master volume is muted, track/music volume is muted or if the whole game is muted. - if (volumeOverlay?.IsMuted.Value == true || Math.Floor(audioManager.Volume.Value * audioManager.VolumeTrack.Value * 100) / 100 <= volume_requirement) + if (volumeOverlay?.IsMuted.Value == true || Math.Floor(audioManager.Volume.Value * audioManager.VolumeTrack.Value * 1e6) / 1e6 <= volume_requirement) { notificationOverlay?.Post(new MutedNotification()); muteWarningShownOnce.Value = true; @@ -582,7 +582,7 @@ namespace osu.Game.Screens.Play // Check values before resetting, as the user may have only had mute enabled, in which case we might not need to adjust volumes. // Note that we only restore to -20 dB to ensure the user isn't suddenly overloaded by unexpectedly high volume. - if (Math.Floor(audioManager.Volume.Value * audioManager.VolumeTrack.Value * 100) / 100 <= volume_requirement) + if (Math.Floor(audioManager.Volume.Value * audioManager.VolumeTrack.Value * 1e6) / 1e6 <= volume_requirement) { // Prioritize increasing music over master volume as to avoid also increasing effects volume. const double target = 0.1; From 9a5348598af58008e0c22897964f81bf27d34ee0 Mon Sep 17 00:00:00 2001 From: Mike Will Date: Fri, 2 Feb 2024 14:22:24 -0500 Subject: [PATCH 2879/4092] Replace aggregate rounding method with a float cast --- osu.Game/Screens/Play/PlayerLoader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index aafa93c122..6154e443ef 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -551,7 +551,7 @@ namespace osu.Game.Screens.Play if (!muteWarningShownOnce.Value) { // Checks if the notification has not been shown yet and also if master volume is muted, track/music volume is muted or if the whole game is muted. - if (volumeOverlay?.IsMuted.Value == true || Math.Floor(audioManager.Volume.Value * audioManager.VolumeTrack.Value * 1e6) / 1e6 <= volume_requirement) + if (volumeOverlay?.IsMuted.Value == true || (float)(audioManager.Volume.Value * audioManager.VolumeTrack.Value) <= volume_requirement) { notificationOverlay?.Post(new MutedNotification()); muteWarningShownOnce.Value = true; @@ -582,7 +582,7 @@ namespace osu.Game.Screens.Play // Check values before resetting, as the user may have only had mute enabled, in which case we might not need to adjust volumes. // Note that we only restore to -20 dB to ensure the user isn't suddenly overloaded by unexpectedly high volume. - if (Math.Floor(audioManager.Volume.Value * audioManager.VolumeTrack.Value * 1e6) / 1e6 <= volume_requirement) + if ((float)(audioManager.Volume.Value * audioManager.VolumeTrack.Value) <= volume_requirement) { // Prioritize increasing music over master volume as to avoid also increasing effects volume. const double target = 0.1; From dde7e068a4ae490ade2c49650996da50c60e939b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 2 Feb 2024 22:28:47 +0300 Subject: [PATCH 2880/4092] Incorporate new unstable rate algo --- .../Rulesets/Scoring/HitEventExtensions.cs | 39 ++++++++++++------- osu.sln.DotSettings | 1 + 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs index 9fb61c6cd9..c3ad8980fe 100644 --- a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs +++ b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Scoring public static class HitEventExtensions { /// - /// Calculates the "unstable rate" for a sequence of s. + /// Calculates the "unstable rate" for a sequence of s using Welford's online algorithm. /// /// /// A non-null value if unstable rate could be calculated, @@ -21,9 +21,30 @@ namespace osu.Game.Rulesets.Scoring { Debug.Assert(hitEvents.All(ev => ev.GameplayRate != null)); - // Division by gameplay rate is to account for TimeOffset scaling with gameplay rate. - double[] timeOffsets = hitEvents.Where(affectsUnstableRate).Select(ev => ev.TimeOffset / ev.GameplayRate!.Value).ToArray(); - return 10 * standardDeviation(timeOffsets); + double currentValue; + int k = 0; + double m = 0; + double s = 0; + double mNext; + + foreach (var e in hitEvents) + { + if (!affectsUnstableRate(e)) + continue; + + // Division by gameplay rate is to account for TimeOffset scaling with gameplay rate. + currentValue = e.TimeOffset / e.GameplayRate!.Value; + + k++; + mNext = m + (currentValue - m) / k; + s += (currentValue - m) * (currentValue - mNext); + m = mNext; + } + + if (k == 0) + return null; + + return 10.0 * Math.Sqrt(s / k); } /// @@ -44,15 +65,5 @@ namespace osu.Game.Rulesets.Scoring } private static bool affectsUnstableRate(HitEvent e) => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit(); - - private static double? standardDeviation(double[] timeOffsets) - { - if (timeOffsets.Length == 0) - return null; - - double mean = timeOffsets.Average(); - double squares = timeOffsets.Select(offset => Math.Pow(offset - mean, 2)).Sum(); - return Math.Sqrt(squares / timeOffsets.Length); - } } } diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 1bf8aa7b0b..ef557cbbfc 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -1041,4 +1041,5 @@ private void load() True True True + True True From 4d324a30573f9cd6c4dcff2e0c336e26e66a574a Mon Sep 17 00:00:00 2001 From: Loreos7 Date: Sat, 3 Feb 2024 00:08:36 +0300 Subject: [PATCH 2881/4092] localise remaining parts of the game settings --- .../TaikoSettingsSubsection.cs | 5 +++-- osu.Game/Localisation/AudioSettingsStrings.cs | 15 +++++++++++++++ osu.Game/Localisation/DebugSettingsStrings.cs | 5 +++++ osu.Game/Localisation/GraphicsSettingsStrings.cs | 5 +++++ osu.Game/Localisation/RulesetSettingsStrings.cs | 5 +++++ .../Sections/Audio/AudioOffsetAdjustControl.cs | 6 +++--- .../Sections/DebugSettings/GeneralSettings.cs | 2 +- .../Settings/Sections/Graphics/LayoutSettings.cs | 2 +- 8 files changed, 38 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/TaikoSettingsSubsection.cs b/osu.Game.Rulesets.Taiko/TaikoSettingsSubsection.cs index 9fe861c08c..84dea474c5 100644 --- a/osu.Game.Rulesets.Taiko/TaikoSettingsSubsection.cs +++ b/osu.Game.Rulesets.Taiko/TaikoSettingsSubsection.cs @@ -1,9 +1,10 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// 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.Localisation; +using osu.Game.Localisation; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Taiko.Configuration; @@ -27,7 +28,7 @@ namespace osu.Game.Rulesets.Taiko { new SettingsEnumDropdown { - LabelText = "Touch control scheme", + LabelText = RulesetSettingsStrings.TouchControlScheme, Current = config.GetBindable(TaikoRulesetSetting.TouchControlScheme) } }; diff --git a/osu.Game/Localisation/AudioSettingsStrings.cs b/osu.Game/Localisation/AudioSettingsStrings.cs index 0f0f560df9..d93de59cd2 100644 --- a/osu.Game/Localisation/AudioSettingsStrings.cs +++ b/osu.Game/Localisation/AudioSettingsStrings.cs @@ -64,6 +64,21 @@ namespace osu.Game.Localisation /// public static LocalisableString AudioOffset => new TranslatableString(getKey(@"audio_offset"), @"Audio offset"); + /// + /// "Play a few beatmaps to receive a suggested offset!" + /// + public static LocalisableString SuggestedOffsetNote => new TranslatableString(getKey(@"suggested_offset_note"), @"Play a few beatmaps to receive a suggested offset!"); + + /// + /// "Based on the last {0} play(s), the suggested offset is {1} ms" + /// + public static LocalisableString SuggestedOffsetValueReceived(int plays, double? value) => new TranslatableString(getKey(@"suggested_offset_value_received"), @"Based on the last {0} play(s), the suggested offset is {1} ms", plays, value); + + /// + /// "Apply suggested offset" + /// + public static LocalisableString ApplySuggestedOffset => new TranslatableString(getKey(@"apply_suggested_offset"), @"Apply suggested offset"); + /// /// "Offset wizard" /// diff --git a/osu.Game/Localisation/DebugSettingsStrings.cs b/osu.Game/Localisation/DebugSettingsStrings.cs index 18fd3e83da..066c07858c 100644 --- a/osu.Game/Localisation/DebugSettingsStrings.cs +++ b/osu.Game/Localisation/DebugSettingsStrings.cs @@ -29,6 +29,11 @@ namespace osu.Game.Localisation /// public static LocalisableString ImportFiles => new TranslatableString(getKey(@"import_files"), @"Import files"); + /// + /// "Run latency certifier" + /// + public static LocalisableString RunLatencyCertifier => new TranslatableString(getKey(@"run_latency_certifier"), @"Run latency certifier"); + /// /// "Memory" /// diff --git a/osu.Game/Localisation/GraphicsSettingsStrings.cs b/osu.Game/Localisation/GraphicsSettingsStrings.cs index 1d14b0a596..d3952d0b4c 100644 --- a/osu.Game/Localisation/GraphicsSettingsStrings.cs +++ b/osu.Game/Localisation/GraphicsSettingsStrings.cs @@ -159,6 +159,11 @@ namespace osu.Game.Localisation /// public static LocalisableString MinimiseOnFocusLoss => new TranslatableString(getKey(@"minimise_on_focus_loss"), @"Minimise osu! when switching to another app"); + /// + /// "Shrink game to avoid cameras and notches" + /// + public static LocalisableString ShrinkGameOnMobile => new TranslatableString(getKey(@"shrink_game_on_mobile"), @"Shrink game to avoid cameras and notches"); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Localisation/RulesetSettingsStrings.cs b/osu.Game/Localisation/RulesetSettingsStrings.cs index 3fa7656cbb..e3d51f1124 100644 --- a/osu.Game/Localisation/RulesetSettingsStrings.cs +++ b/osu.Game/Localisation/RulesetSettingsStrings.cs @@ -84,6 +84,11 @@ namespace osu.Game.Localisation /// public static LocalisableString ScrollSpeedTooltip(int scrollTime, int scrollSpeed) => new TranslatableString(getKey(@"ruleset"), @"{0}ms (speed {1})", scrollTime, scrollSpeed); + /// + /// "Touch control scheme" + /// + public static LocalisableString TouchControlScheme => new TranslatableString(getKey(@"touch_control_scheme"), @"Touch control scheme"); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs index ef1691534f..71c357c065 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs @@ -91,7 +91,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio applySuggestion = new RoundedButton { RelativeSizeAxes = Axes.X, - Text = "Apply suggested offset", + Text = AudioSettingsStrings.ApplySuggestedOffset, Action = () => { if (SuggestedOffset.Value.HasValue) @@ -155,8 +155,8 @@ namespace osu.Game.Overlays.Settings.Sections.Audio private void updateHintText() { hintText.Text = SuggestedOffset.Value == null - ? @"Play a few beatmaps to receive a suggested offset!" - : $@"Based on the last {averageHitErrorHistory.Count} play(s), the suggested offset is {SuggestedOffset.Value:N0} ms."; + ? AudioSettingsStrings.SuggestedOffsetNote + : AudioSettingsStrings.SuggestedOffsetValueReceived(averageHitErrorHistory.Count, SuggestedOffset.Value); applySuggestion.Enabled.Value = SuggestedOffset.Value != null; } diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs index 049ccedf37..df46e38491 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs @@ -39,7 +39,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings }, new SettingsButton { - Text = @"Run latency certifier", + Text = DebugSettingsStrings.RunLatencyCertifier, Action = () => performer?.PerformFromScreen(menu => menu.Push(new LatencyCertifierScreen())) } }; diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 71afec88d4..4eb3e54708 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -115,7 +115,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics }, safeAreaConsiderationsCheckbox = new SettingsCheckbox { - LabelText = "Shrink game to avoid cameras and notches", + LabelText = GraphicsSettingsStrings.ShrinkGameOnMobile, Current = osuConfig.GetBindable(OsuSetting.SafeAreaConsiderations), }, new SettingsSlider From 9706836778a8fb3e83c663dc9a957f2265001a64 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 3 Feb 2024 06:16:24 +0900 Subject: [PATCH 2882/4092] Update system requirements --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d7e710f392..dc5809d46b 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,8 @@ If you are just looking to give the game a whirl, you can grab the latest releas ### Latest release: -| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | macOS 10.15+ ([Intel](https://github.com/ppy/osu/releases/latest/download/osu.app.Intel.zip), [Apple Silicon](https://github.com/ppy/osu/releases/latest/download/osu.app.Apple.Silicon.zip)) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 13.4+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) | -| ------------- | ------------- | ------------- | ------------- | ------------- | +| [Windows 10+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | macOS 12+ ([Intel](https://github.com/ppy/osu/releases/latest/download/osu.app.Intel.zip), [Apple Silicon](https://github.com/ppy/osu/releases/latest/download/osu.app.Apple.Silicon.zip)) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 13.4+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) | +|--------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| ------------- | ------------- | ------------- | You can also generally download a version for your current device from the [osu! site](https://osu.ppy.sh/home/download). From 57bc5ee04fd68fc317dea18c51d6d473f1eb80dd Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 3 Feb 2024 00:19:04 +0300 Subject: [PATCH 2883/4092] Improve readability --- osu.Game/Rulesets/Scoring/HitEventExtensions.cs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs index c3ad8980fe..ab36a54d3d 100644 --- a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs +++ b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs @@ -21,30 +21,28 @@ namespace osu.Game.Rulesets.Scoring { Debug.Assert(hitEvents.All(ev => ev.GameplayRate != null)); - double currentValue; - int k = 0; + int count = 0; double m = 0; double s = 0; - double mNext; foreach (var e in hitEvents) { if (!affectsUnstableRate(e)) continue; - // Division by gameplay rate is to account for TimeOffset scaling with gameplay rate. - currentValue = e.TimeOffset / e.GameplayRate!.Value; + count++; - k++; - mNext = m + (currentValue - m) / k; + // Division by gameplay rate is to account for TimeOffset scaling with gameplay rate. + double currentValue = e.TimeOffset / e.GameplayRate!.Value; + double mNext = m + (currentValue - m) / count; s += (currentValue - m) * (currentValue - mNext); m = mNext; } - if (k == 0) + if (count == 0) return null; - return 10.0 * Math.Sqrt(s / k); + return 10.0 * Math.Sqrt(s / count); } /// From 24e5c7ed42a58f7a787a965a84488a02e00e8e3f Mon Sep 17 00:00:00 2001 From: Loreos7 Date: Sat, 3 Feb 2024 02:02:21 +0300 Subject: [PATCH 2884/4092] fix formatting --- osu.Game/Localisation/AudioSettingsStrings.cs | 2 +- .../Settings/Sections/Audio/AudioOffsetAdjustControl.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Localisation/AudioSettingsStrings.cs b/osu.Game/Localisation/AudioSettingsStrings.cs index d93de59cd2..f4537a4c1c 100644 --- a/osu.Game/Localisation/AudioSettingsStrings.cs +++ b/osu.Game/Localisation/AudioSettingsStrings.cs @@ -72,7 +72,7 @@ namespace osu.Game.Localisation /// /// "Based on the last {0} play(s), the suggested offset is {1} ms" /// - public static LocalisableString SuggestedOffsetValueReceived(int plays, double? value) => new TranslatableString(getKey(@"suggested_offset_value_received"), @"Based on the last {0} play(s), the suggested offset is {1} ms", plays, value); + public static LocalisableString SuggestedOffsetValueReceived(int plays, string value) => new TranslatableString(getKey(@"suggested_offset_value_received"), @"Based on the last {0} play(s), the suggested offset is {1} ms", plays, value); /// /// "Apply suggested offset" diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs index 71c357c065..ad33434848 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs @@ -156,7 +156,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio { hintText.Text = SuggestedOffset.Value == null ? AudioSettingsStrings.SuggestedOffsetNote - : AudioSettingsStrings.SuggestedOffsetValueReceived(averageHitErrorHistory.Count, SuggestedOffset.Value); + : AudioSettingsStrings.SuggestedOffsetValueReceived(averageHitErrorHistory.Count, $"{SuggestedOffset.Value:N0}"); applySuggestion.Enabled.Value = SuggestedOffset.Value != null; } From 498d93be614cfd9057ca9c3af0ff63f4c581b3f2 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Sat, 3 Feb 2024 16:59:48 +0100 Subject: [PATCH 2885/4092] Add way to associate with files and URIs on Windows --- .../TestSceneWindowsAssociationManager.cs | 106 +++++++ .../WindowsAssociationManagerStrings.cs | 39 +++ osu.Game/Updater/WindowsAssociationManager.cs | 268 ++++++++++++++++++ osu.sln.DotSettings | 1 + 4 files changed, 414 insertions(+) create mode 100644 osu.Game.Tests/Visual/Updater/TestSceneWindowsAssociationManager.cs create mode 100644 osu.Game/Localisation/WindowsAssociationManagerStrings.cs create mode 100644 osu.Game/Updater/WindowsAssociationManager.cs diff --git a/osu.Game.Tests/Visual/Updater/TestSceneWindowsAssociationManager.cs b/osu.Game.Tests/Visual/Updater/TestSceneWindowsAssociationManager.cs new file mode 100644 index 0000000000..72256860fd --- /dev/null +++ b/osu.Game.Tests/Visual/Updater/TestSceneWindowsAssociationManager.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 System.IO; +using System.Runtime.Versioning; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Platform; +using osu.Game.Graphics.Sprites; +using osu.Game.Tests.Resources; +using osu.Game.Updater; + +namespace osu.Game.Tests.Visual.Updater +{ + [SupportedOSPlatform("windows")] + [Ignore("These tests modify the windows registry and open programs")] + public partial class TestSceneWindowsAssociationManager : OsuTestScene + { + private static readonly string exe_path = Path.ChangeExtension(typeof(TestSceneWindowsAssociationManager).Assembly.Location, ".exe"); + + [Resolved] + private GameHost host { get; set; } = null!; + + private readonly WindowsAssociationManager associationManager; + + public TestSceneWindowsAssociationManager() + { + Children = new Drawable[] + { + new OsuSpriteText { Text = Environment.CommandLine }, + associationManager = new WindowsAssociationManager(exe_path, "osu.Test"), + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (Environment.CommandLine.Contains(".osz", StringComparison.Ordinal)) + ChangeBackgroundColour(ColourInfo.SingleColour(Colour4.DarkOliveGreen)); + + if (Environment.CommandLine.Contains("osu://", StringComparison.Ordinal)) + ChangeBackgroundColour(ColourInfo.SingleColour(Colour4.DarkBlue)); + + if (Environment.CommandLine.Contains("osump://", StringComparison.Ordinal)) + ChangeBackgroundColour(ColourInfo.SingleColour(Colour4.DarkRed)); + } + + [Test] + public void TestInstall() + { + AddStep("install", () => associationManager.InstallAssociations()); + } + + [Test] + public void TestOpenBeatmap() + { + string beatmapPath = null!; + AddStep("create temp beatmap", () => beatmapPath = TestResources.GetTestBeatmapForImport()); + AddAssert("beatmap path ends with .osz", () => beatmapPath, () => Does.EndWith(".osz")); + AddStep("open beatmap", () => host.OpenFileExternally(beatmapPath)); + AddUntilStep("wait for focus", () => host.IsActive.Value); + AddStep("delete temp beatmap", () => File.Delete(beatmapPath)); + } + + /// + /// To check that the icon is correct + /// + [Test] + public void TestPresentBeatmap() + { + string beatmapPath = null!; + AddStep("create temp beatmap", () => beatmapPath = TestResources.GetTestBeatmapForImport()); + AddAssert("beatmap path ends with .osz", () => beatmapPath, () => Does.EndWith(".osz")); + AddStep("show beatmap in explorer", () => host.PresentFileExternally(beatmapPath)); + AddUntilStep("wait for focus", () => host.IsActive.Value); + AddStep("delete temp beatmap", () => File.Delete(beatmapPath)); + } + + [TestCase("osu://s/1")] + [TestCase("osump://123")] + public void TestUrl(string url) + { + AddStep($"open {url}", () => Process.Start(new ProcessStartInfo(url) { UseShellExecute = true })); + } + + [Test] + public void TestUninstall() + { + AddStep("uninstall", () => associationManager.UninstallAssociations()); + } + + /// + /// Useful when testing things out and manually changing the registry. + /// + [Test] + public void TestNotifyShell() + { + AddStep("notify shell of changes", () => associationManager.NotifyShellUpdate()); + } + } +} diff --git a/osu.Game/Localisation/WindowsAssociationManagerStrings.cs b/osu.Game/Localisation/WindowsAssociationManagerStrings.cs new file mode 100644 index 0000000000..95a6decdd6 --- /dev/null +++ b/osu.Game/Localisation/WindowsAssociationManagerStrings.cs @@ -0,0 +1,39 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Localisation; + +namespace osu.Game.Localisation +{ + public static class WindowsAssociationManagerStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.WindowsAssociationManager"; + + /// + /// "osu! Beatmap" + /// + public static LocalisableString OsuBeatmap => new TranslatableString(getKey(@"osu_beatmap"), @"osu! Beatmap"); + + /// + /// "osu! Replay" + /// + public static LocalisableString OsuReplay => new TranslatableString(getKey(@"osu_replay"), @"osu! Replay"); + + /// + /// "osu! Skin" + /// + public static LocalisableString OsuSkin => new TranslatableString(getKey(@"osu_skin"), @"osu! Skin"); + + /// + /// "osu!" + /// + public static LocalisableString OsuProtocol => new TranslatableString(getKey(@"osu_protocol"), @"osu!"); + + /// + /// "osu! Multiplayer" + /// + public static LocalisableString OsuMultiplayer => new TranslatableString(getKey(@"osu_multiplayer"), @"osu! Multiplayer"); + + private static string getKey(string key) => $@"{prefix}:{key}"; + } +} \ No newline at end of file diff --git a/osu.Game/Updater/WindowsAssociationManager.cs b/osu.Game/Updater/WindowsAssociationManager.cs new file mode 100644 index 0000000000..8949d88362 --- /dev/null +++ b/osu.Game/Updater/WindowsAssociationManager.cs @@ -0,0 +1,268 @@ +// 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.Runtime.InteropServices; +using System.Runtime.Versioning; +using Microsoft.Win32; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Localisation; +using osu.Framework.Logging; +using osu.Game.Resources.Icons; +using osu.Game.Localisation; + +namespace osu.Game.Updater +{ + [SupportedOSPlatform("windows")] + public partial class WindowsAssociationManager : Component + { + public const string SOFTWARE_CLASSES = @"Software\Classes"; + + /// + /// Sub key for setting the icon. + /// https://learn.microsoft.com/en-us/windows/win32/com/defaulticon + /// + public const string DEFAULT_ICON = @"DefaultIcon"; + + /// + /// Sub key for setting the command line that the shell invokes. + /// https://learn.microsoft.com/en-us/windows/win32/com/shell + /// + public const string SHELL_OPEN_COMMAND = @"Shell\Open\Command"; + + private static readonly FileAssociation[] file_associations = + { + new FileAssociation(@".osz", WindowsAssociationManagerStrings.OsuBeatmap, Icons.Lazer), + new FileAssociation(@".olz", WindowsAssociationManagerStrings.OsuBeatmap, Icons.Lazer), + new FileAssociation(@".osr", WindowsAssociationManagerStrings.OsuReplay, Icons.Lazer), + new FileAssociation(@".osk", WindowsAssociationManagerStrings.OsuSkin, Icons.Lazer), + }; + + private static readonly UriAssociation[] uri_associations = + { + new UriAssociation(@"osu", WindowsAssociationManagerStrings.OsuProtocol, Icons.Lazer), + new UriAssociation(@"osump", WindowsAssociationManagerStrings.OsuMultiplayer, Icons.Lazer), + }; + + [Resolved] + private LocalisationManager localisation { get; set; } = null!; + + private IBindable localisationParameters = null!; + + private readonly string exePath; + private readonly string programIdPrefix; + + /// Path to the executable to register. + /// + /// Program ID prefix used for file associations. Should be relatively short since the full program ID has a 39 character limit, + /// see https://learn.microsoft.com/en-us/windows/win32/com/-progid--key. + /// + public WindowsAssociationManager(string exePath, string programIdPrefix) + { + this.exePath = exePath; + this.programIdPrefix = programIdPrefix; + } + + [BackgroundDependencyLoader] + private void load() + { + localisationParameters = localisation.CurrentParameters.GetBoundCopy(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + localisationParameters.ValueChanged += _ => updateDescriptions(); + } + + internal void InstallAssociations() + { + try + { + using (var classes = Registry.CurrentUser.OpenSubKey(SOFTWARE_CLASSES, writable: true)) + { + if (classes == null) + return; + + foreach (var association in file_associations) + association.Install(classes, exePath, programIdPrefix); + + foreach (var association in uri_associations) + association.Install(classes, exePath); + } + + updateDescriptions(); + } + catch (Exception e) + { + Logger.Log(@$"Failed to install file and URI associations: {e.Message}"); + } + } + + private void updateDescriptions() + { + try + { + using var classes = Registry.CurrentUser.OpenSubKey(SOFTWARE_CLASSES, true); + if (classes == null) + return; + + foreach (var association in file_associations) + { + var b = localisation.GetLocalisedBindableString(association.Description); + association.UpdateDescription(classes, programIdPrefix, b.Value); + b.UnbindAll(); + } + + foreach (var association in uri_associations) + { + var b = localisation.GetLocalisedBindableString(association.Description); + association.UpdateDescription(classes, b.Value); + b.UnbindAll(); + } + + NotifyShellUpdate(); + } + catch (Exception e) + { + Logger.Log($@"Failed to update file and URI associations: {e.Message}"); + } + } + + internal void UninstallAssociations() + { + try + { + using var classes = Registry.CurrentUser.OpenSubKey(SOFTWARE_CLASSES, true); + if (classes == null) + return; + + foreach (var association in file_associations) + association.Uninstall(classes, programIdPrefix); + + foreach (var association in uri_associations) + association.Uninstall(classes); + + NotifyShellUpdate(); + } + catch (Exception e) + { + Logger.Log($@"Failed to uninstall file and URI associations: {e.Message}"); + } + } + + internal void NotifyShellUpdate() => SHChangeNotify(EventId.SHCNE_ASSOCCHANGED, Flags.SHCNF_IDLIST, IntPtr.Zero, IntPtr.Zero); + + #region Native interop + + [DllImport("Shell32.dll")] + private static extern void SHChangeNotify(EventId wEventId, Flags uFlags, IntPtr dwItem1, IntPtr dwItem2); + + private enum EventId + { + /// + /// A file type association has changed. must be specified in the uFlags parameter. + /// dwItem1 and dwItem2 are not used and must be . This event should also be sent for registered protocols. + /// + SHCNE_ASSOCCHANGED = 0x08000000 + } + + private enum Flags : uint + { + SHCNF_IDLIST = 0x0000 + } + + #endregion + + private record FileAssociation(string Extension, LocalisableString Description, Win32Icon Icon) + { + private string getProgramId(string prefix) => $@"{prefix}.File{Extension}"; + + /// + /// Installs a file extenstion association in accordance with https://learn.microsoft.com/en-us/windows/win32/com/-progid--key + /// + public void Install(RegistryKey classes, string exePath, string programIdPrefix) + { + string programId = getProgramId(programIdPrefix); + + // register a program id for the given extension + using (var programKey = classes.CreateSubKey(programId)) + { + using (var defaultIconKey = programKey.CreateSubKey(DEFAULT_ICON)) + defaultIconKey.SetValue(null, Icon.RegistryString); + + using (var openCommandKey = programKey.CreateSubKey(SHELL_OPEN_COMMAND)) + openCommandKey.SetValue(null, $@"""{exePath}"" ""%1"""); + } + + using (var extensionKey = classes.CreateSubKey(Extension)) + { + // set ourselves as the default program + extensionKey.SetValue(null, programId); + + // add to the open with dialog + // https://learn.microsoft.com/en-us/windows/win32/shell/how-to-include-an-application-on-the-open-with-dialog-box + using (var openWithKey = extensionKey.CreateSubKey(@"OpenWithProgIds")) + openWithKey.SetValue(programId, string.Empty); + } + } + + public void UpdateDescription(RegistryKey classes, string programIdPrefix, string description) + { + using (var programKey = classes.OpenSubKey(getProgramId(programIdPrefix), true)) + programKey?.SetValue(null, description); + } + + public void Uninstall(RegistryKey classes, string programIdPrefix) + { + string programId = getProgramId(programIdPrefix); + + // importantly, we don't delete the default program entry because some other program could have taken it. + + using (var extensionKey = classes.OpenSubKey($@"{Extension}\OpenWithProgIds", true)) + extensionKey?.DeleteValue(programId, throwOnMissingValue: false); + + classes.DeleteSubKeyTree(programId, throwOnMissingSubKey: false); + } + } + + private record UriAssociation(string Protocol, LocalisableString Description, Win32Icon Icon) + { + /// + /// "The URL Protocol string value indicates that this key declares a custom pluggable protocol handler." + /// See https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa767914(v=vs.85). + /// + public const string URL_PROTOCOL = @"URL Protocol"; + + /// + /// Registers an URI protocol handler in accordance with https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa767914(v=vs.85). + /// + public void Install(RegistryKey classes, string exePath) + { + using (var protocolKey = classes.CreateSubKey(Protocol)) + { + protocolKey.SetValue(URL_PROTOCOL, string.Empty); + + using (var defaultIconKey = protocolKey.CreateSubKey(DEFAULT_ICON)) + defaultIconKey.SetValue(null, Icon.RegistryString); + + using (var openCommandKey = protocolKey.CreateSubKey(SHELL_OPEN_COMMAND)) + openCommandKey.SetValue(null, $@"""{exePath}"" ""%1"""); + } + } + + public void UpdateDescription(RegistryKey classes, string description) + { + using (var protocolKey = classes.OpenSubKey(Protocol, true)) + protocolKey?.SetValue(null, $@"URL:{description}"); + } + + public void Uninstall(RegistryKey classes) + { + classes.DeleteSubKeyTree(Protocol, throwOnMissingSubKey: false); + } + } + } +} diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 1bf8aa7b0b..e2e28c38ec 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -1005,6 +1005,7 @@ private void load() True True True + True True True True From 03578821c0c2c9fc4b4832208e7c10751f8b28af Mon Sep 17 00:00:00 2001 From: Susko3 Date: Sat, 3 Feb 2024 17:04:06 +0100 Subject: [PATCH 2886/4092] Associate on startup --- osu.Desktop/OsuGameDesktop.cs | 7 ++++++- osu.Game/Updater/WindowsAssociationManager.cs | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index a0db896f46..a048deddb3 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -134,9 +134,14 @@ namespace osu.Desktop LoadComponentAsync(new DiscordRichPresence(), Add); - if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) + if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows && OperatingSystem.IsWindows()) + { LoadComponentAsync(new GameplayWinKeyBlocker(), Add); + string? executableLocation = Path.GetDirectoryName(typeof(OsuGameDesktop).Assembly.Location); + LoadComponentAsync(new WindowsAssociationManager(Path.Join(executableLocation, @"osu!.exe"), "osu"), Add); + } + LoadComponentAsync(new ElevatedPrivilegesChecker(), Add); osuSchemeLinkIPCChannel = new OsuSchemeLinkIPCChannel(Host, this); diff --git a/osu.Game/Updater/WindowsAssociationManager.cs b/osu.Game/Updater/WindowsAssociationManager.cs index 8949d88362..104406c81b 100644 --- a/osu.Game/Updater/WindowsAssociationManager.cs +++ b/osu.Game/Updater/WindowsAssociationManager.cs @@ -69,6 +69,7 @@ namespace osu.Game.Updater private void load() { localisationParameters = localisation.CurrentParameters.GetBoundCopy(); + InstallAssociations(); } protected override void LoadComplete() From 2bac09ee00d1e809e38aca97350e4853b2ebf09f Mon Sep 17 00:00:00 2001 From: Susko3 Date: Sat, 3 Feb 2024 17:23:59 +0100 Subject: [PATCH 2887/4092] Only associate on a deployed build Helps to reduce clutter when developing --- osu.Desktop/OsuGameDesktop.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index a048deddb3..c5175fd549 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -134,10 +134,11 @@ namespace osu.Desktop LoadComponentAsync(new DiscordRichPresence(), Add); - if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows && OperatingSystem.IsWindows()) - { + if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) LoadComponentAsync(new GameplayWinKeyBlocker(), Add); + if (OperatingSystem.IsWindows() && IsDeployedBuild) + { string? executableLocation = Path.GetDirectoryName(typeof(OsuGameDesktop).Assembly.Location); LoadComponentAsync(new WindowsAssociationManager(Path.Join(executableLocation, @"osu!.exe"), "osu"), Add); } From 4aa27482a9b95ce8049d2a6cac1b1ded0945bc8f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 3 Feb 2024 19:52:40 +0300 Subject: [PATCH 2888/4092] Use SlimReadOnlyDictionaryWrapper for AliveEntries --- .../Objects/Pooling/PooledDrawableWithLifetimeContainer.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs b/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs index aed608cf8f..efc10f26e1 100644 --- a/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs @@ -2,12 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Diagnostics; using System.Linq; +using osu.Framework.Extensions.ListExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Performance; +using osu.Framework.Lists; namespace osu.Game.Rulesets.Objects.Pooling { @@ -36,7 +37,7 @@ namespace osu.Game.Rulesets.Objects.Pooling /// /// The enumeration order is undefined. /// - public readonly ReadOnlyDictionary AliveEntries; + public readonly SlimReadOnlyDictionaryWrapper AliveEntries; /// /// Whether to remove an entry when clock goes backward and crossed its . @@ -65,7 +66,7 @@ namespace osu.Game.Rulesets.Objects.Pooling lifetimeManager.EntryBecameDead += entryBecameDead; lifetimeManager.EntryCrossedBoundary += entryCrossedBoundary; - AliveEntries = new ReadOnlyDictionary(aliveDrawableMap); + AliveEntries = aliveDrawableMap.AsSlimReadOnly(); } /// From e2e3c61c9c9e1a13434a6db6d919299ee459c78e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 3 Feb 2024 19:54:04 +0300 Subject: [PATCH 2889/4092] Use AliveEntries where we don't need startTime order --- osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs | 4 +++- osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs | 4 +++- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 4 +++- osu.Game/Screens/Play/FailAnimationContainer.cs | 4 +++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs index b70d607ca1..a9111eec1f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs @@ -75,8 +75,10 @@ namespace osu.Game.Rulesets.Osu.Mods { double time = playfield.Time.Current; - foreach (var drawable in playfield.HitObjectContainer.AliveObjects) + foreach (var entry in playfield.HitObjectContainer.AliveEntries) { + var drawable = entry.Value; + switch (drawable) { case DrawableHitCircle circle: diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs index befee4af5a..b49fb931d1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs @@ -49,8 +49,10 @@ namespace osu.Game.Rulesets.Osu.Mods { var cursorPos = playfield.Cursor.AsNonNull().ActiveCursor.DrawPosition; - foreach (var drawable in playfield.HitObjectContainer.AliveObjects) + foreach (var entry in playfield.HitObjectContainer.AliveEntries) { + var drawable = entry.Value; + switch (drawable) { case DrawableHitCircle circle: diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index 91feb33931..ced98f0cd5 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -48,8 +48,10 @@ namespace osu.Game.Rulesets.Osu.Mods { var cursorPos = playfield.Cursor.AsNonNull().ActiveCursor.DrawPosition; - foreach (var drawable in playfield.HitObjectContainer.AliveObjects) + foreach (var entry in playfield.HitObjectContainer.AliveEntries) { + var drawable = entry.Value; + var destination = Vector2.Clamp(2 * drawable.Position - cursorPos, Vector2.Zero, OsuPlayfield.BASE_SIZE); if (drawable.HitObject is Slider thisSlider) diff --git a/osu.Game/Screens/Play/FailAnimationContainer.cs b/osu.Game/Screens/Play/FailAnimationContainer.cs index 821c67e3cb..ebb0d77726 100644 --- a/osu.Game/Screens/Play/FailAnimationContainer.cs +++ b/osu.Game/Screens/Play/FailAnimationContainer.cs @@ -198,8 +198,10 @@ namespace osu.Game.Screens.Play foreach (var nested in playfield.NestedPlayfields) applyToPlayfield(nested); - foreach (DrawableHitObject obj in playfield.HitObjectContainer.AliveObjects) + foreach (var entry in playfield.HitObjectContainer.AliveEntries) { + var obj = entry.Value; + if (appliedObjects.Contains(obj)) continue; From cdcf5bddda5c15c518a810af6b68b06ab214ee52 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Sat, 3 Feb 2024 17:56:14 +0100 Subject: [PATCH 2890/4092] Uninstall associations when uninstalling from squirrel --- osu.Desktop/Program.cs | 2 ++ .../Visual/Updater/TestSceneWindowsAssociationManager.cs | 2 +- osu.Game/Updater/WindowsAssociationManager.cs | 8 +++++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index a7453dc0e0..c9ce5ebf1b 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -12,6 +12,7 @@ using osu.Framework.Platform; using osu.Game; using osu.Game.IPC; using osu.Game.Tournament; +using osu.Game.Updater; using SDL2; using Squirrel; @@ -180,6 +181,7 @@ namespace osu.Desktop { tools.RemoveShortcutForThisExe(); tools.RemoveUninstallerRegistryEntry(); + WindowsAssociationManager.UninstallAssociations(@"osu"); }, onEveryRun: (_, _, _) => { // While setting the `ProcessAppUserModelId` fixes duplicate icons/shortcuts on the taskbar, it currently diff --git a/osu.Game.Tests/Visual/Updater/TestSceneWindowsAssociationManager.cs b/osu.Game.Tests/Visual/Updater/TestSceneWindowsAssociationManager.cs index 72256860fd..f3eb468334 100644 --- a/osu.Game.Tests/Visual/Updater/TestSceneWindowsAssociationManager.cs +++ b/osu.Game.Tests/Visual/Updater/TestSceneWindowsAssociationManager.cs @@ -100,7 +100,7 @@ namespace osu.Game.Tests.Visual.Updater [Test] public void TestNotifyShell() { - AddStep("notify shell of changes", () => associationManager.NotifyShellUpdate()); + AddStep("notify shell of changes", WindowsAssociationManager.NotifyShellUpdate); } } } diff --git a/osu.Game/Updater/WindowsAssociationManager.cs b/osu.Game/Updater/WindowsAssociationManager.cs index 104406c81b..bb0e37a2f4 100644 --- a/osu.Game/Updater/WindowsAssociationManager.cs +++ b/osu.Game/Updater/WindowsAssociationManager.cs @@ -78,7 +78,7 @@ namespace osu.Game.Updater localisationParameters.ValueChanged += _ => updateDescriptions(); } - internal void InstallAssociations() + public void InstallAssociations() { try { @@ -132,7 +132,9 @@ namespace osu.Game.Updater } } - internal void UninstallAssociations() + public void UninstallAssociations() => UninstallAssociations(programIdPrefix); + + public static void UninstallAssociations(string programIdPrefix) { try { @@ -154,7 +156,7 @@ namespace osu.Game.Updater } } - internal void NotifyShellUpdate() => SHChangeNotify(EventId.SHCNE_ASSOCCHANGED, Flags.SHCNF_IDLIST, IntPtr.Zero, IntPtr.Zero); + internal static void NotifyShellUpdate() => SHChangeNotify(EventId.SHCNE_ASSOCCHANGED, Flags.SHCNF_IDLIST, IntPtr.Zero, IntPtr.Zero); #region Native interop From 397def9ceb543ca7e6d0ada77bfde9558dae286d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 4 Feb 2024 02:58:15 +0300 Subject: [PATCH 2891/4092] Move layout specification outside the GradedCircles class --- .../Visual/Ranking/TestSceneGradedCircles.cs | 6 ++---- .../Ranking/Expanded/Accuracy/AccuracyCircle.cs | 14 +++++++++++++- .../Ranking/Expanded/Accuracy/GradedCircles.cs | 6 ------ 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneGradedCircles.cs b/osu.Game.Tests/Visual/Ranking/TestSceneGradedCircles.cs index 87fbca5c44..116386b4b5 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneGradedCircles.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneGradedCircles.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 osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; @@ -25,12 +24,11 @@ namespace osu.Game.Tests.Visual.Ranking double accuracyB = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.B); double accuracyC = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.C); - Add(new Container + Add(ring = new GradedCircles(accuracyC, accuracyB, accuracyA, accuracyS, accuracyX) { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(400), - Child = ring = new GradedCircles(accuracyC, accuracyB, accuracyA, accuracyS, accuracyX) + Size = new Vector2(400) }); } diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 8304e7a542..8dc1a48f40 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -20,6 +20,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Skinning; +using osuTK; using osuTK.Graphics; namespace osu.Game.Screens.Ranking.Expanded.Accuracy @@ -156,7 +157,18 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex("#7CF6FF"), Color4Extensions.FromHex("#BAFFA9")), InnerRadius = accuracy_circle_radius, }, - gradedCircles = new GradedCircles(accuracyC, accuracyB, accuracyA, accuracyS, accuracyX), + new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.8f), + Padding = new MarginPadding(2.5f), + Child = gradedCircles = new GradedCircles(accuracyC, accuracyB, accuracyA, accuracyS, accuracyX) + { + RelativeSizeAxes = Axes.Both + } + }, badges = new Container { Name = "Rank badges", diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs index efcb848530..e60a24a310 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osu.Game.Scoring; -using osuTK; namespace osu.Game.Screens.Ranking.Expanded.Accuracy { @@ -39,11 +38,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy public GradedCircles(double accuracyC, double accuracyB, double accuracyA, double accuracyS, double accuracyX) { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - RelativeSizeAxes = Axes.Both; - Size = new Vector2(0.8f); - Padding = new MarginPadding(2.5f); InternalChildren = new Drawable[] { dProgress = new GradedCircle(0.0, accuracyC) From 4e5c9ddbfe6afad4713e9705cf4b28cd32cfd764 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 4 Feb 2024 03:02:39 +0300 Subject: [PATCH 2892/4092] Rename notch const to spacing --- .../Ranking/Expanded/Accuracy/AccuracyCircle.cs | 12 ++++++------ .../Ranking/Expanded/Accuracy/GradedCircles.cs | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 8dc1a48f40..7edfc00760 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -76,9 +76,9 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy public const double VIRTUAL_SS_PERCENTAGE = 0.01; /// - /// The width of a solid "notch" in terms of accuracy that appears at the ends of the rank circles to add separation. + /// The width of spacing in terms of accuracy between the grade circles. /// - public const double NOTCH_WIDTH_PERCENTAGE = 2.0 / 360; + public const double GRADE_SPACING_PERCENTAGE = 2.0 / 360; /// /// The easing for the circle filling transforms. @@ -241,10 +241,10 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy // to prevent ambiguity on what grade it's pointing at. foreach (double p in notchPercentages) { - if (Precision.AlmostEquals(p, targetAccuracy, NOTCH_WIDTH_PERCENTAGE / 2)) + if (Precision.AlmostEquals(p, targetAccuracy, GRADE_SPACING_PERCENTAGE / 2)) { int tippingDirection = targetAccuracy - p >= 0 ? 1 : -1; // We "round up" here to match rank criteria - targetAccuracy = p + tippingDirection * (NOTCH_WIDTH_PERCENTAGE / 2); + targetAccuracy = p + tippingDirection * (GRADE_SPACING_PERCENTAGE / 2); break; } } @@ -253,7 +253,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy if (score.Rank == ScoreRank.X || score.Rank == ScoreRank.XH) targetAccuracy = 1; else - targetAccuracy = Math.Min(accuracyX - VIRTUAL_SS_PERCENTAGE - NOTCH_WIDTH_PERCENTAGE / 2, targetAccuracy); + targetAccuracy = Math.Min(accuracyX - VIRTUAL_SS_PERCENTAGE - GRADE_SPACING_PERCENTAGE / 2, targetAccuracy); // The accuracy circle gauge visually fills up a bit too much. // This wouldn't normally matter but we want it to align properly with the inner graded circle in the above cases. @@ -349,7 +349,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy .FadeOut(800, Easing.Out); accuracyCircle - .FillTo(accuracyS - NOTCH_WIDTH_PERCENTAGE / 2 - visual_alignment_offset, 70, Easing.OutQuint); + .FillTo(accuracyS - GRADE_SPACING_PERCENTAGE / 2 - visual_alignment_offset, 70, Easing.OutQuint); badges.Single(b => b.Rank == getRank(ScoreRank.S)) .FadeOut(70, Easing.OutQuint); diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs index e60a24a310..57b6d8e4ac 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs @@ -79,8 +79,8 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy public GradedCircle(double startProgress, double endProgress) { - this.startProgress = startProgress + AccuracyCircle.NOTCH_WIDTH_PERCENTAGE * 0.5; - this.endProgress = endProgress - AccuracyCircle.NOTCH_WIDTH_PERCENTAGE * 0.5; + this.startProgress = startProgress + AccuracyCircle.GRADE_SPACING_PERCENTAGE * 0.5; + this.endProgress = endProgress - AccuracyCircle.GRADE_SPACING_PERCENTAGE * 0.5; Anchor = Anchor.Centre; Origin = Anchor.Centre; From ba291621f0d9a0e074f8597f2f28aa203ee0c187 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 4 Feb 2024 17:27:07 +0900 Subject: [PATCH 2893/4092] Adjust Android SDK target version --- osu.Android/AndroidManifest.xml | 2 +- osu.Game.Rulesets.Catch.Tests.Android/AndroidManifest.xml | 2 +- osu.Game.Rulesets.Mania.Tests.Android/AndroidManifest.xml | 2 +- osu.Game.Rulesets.Osu.Tests.Android/AndroidManifest.xml | 2 +- osu.Game.Rulesets.Taiko.Tests.Android/AndroidManifest.xml | 2 +- osu.Game.Tests.Android/AndroidManifest.xml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Android/AndroidManifest.xml b/osu.Android/AndroidManifest.xml index a2b55257cb..a85e711cf2 100644 --- a/osu.Android/AndroidManifest.xml +++ b/osu.Android/AndroidManifest.xml @@ -1,6 +1,6 @@  - + - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania.Tests.Android/AndroidManifest.xml b/osu.Game.Rulesets.Mania.Tests.Android/AndroidManifest.xml index f5a49210ea..df4930419c 100644 --- a/osu.Game.Rulesets.Mania.Tests.Android/AndroidManifest.xml +++ b/osu.Game.Rulesets.Mania.Tests.Android/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu.Tests.Android/AndroidManifest.xml b/osu.Game.Rulesets.Osu.Tests.Android/AndroidManifest.xml index ed4725dd94..d0c3484cfd 100644 --- a/osu.Game.Rulesets.Osu.Tests.Android/AndroidManifest.xml +++ b/osu.Game.Rulesets.Osu.Tests.Android/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Taiko.Tests.Android/AndroidManifest.xml b/osu.Game.Rulesets.Taiko.Tests.Android/AndroidManifest.xml index cc88d3080a..0ae9ee43ad 100644 --- a/osu.Game.Rulesets.Taiko.Tests.Android/AndroidManifest.xml +++ b/osu.Game.Rulesets.Taiko.Tests.Android/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/osu.Game.Tests.Android/AndroidManifest.xml b/osu.Game.Tests.Android/AndroidManifest.xml index 6f91fb928c..71793af977 100644 --- a/osu.Game.Tests.Android/AndroidManifest.xml +++ b/osu.Game.Tests.Android/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file From 9923c1b6e6e33fc982c3e61c457aa5210da3ae73 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 4 Feb 2024 17:08:39 +0300 Subject: [PATCH 2894/4092] Fix multiplayer/playlists lounge screen disposing rooms synchronously --- .../OnlinePlay/Lounge/Components/RoomsContainer.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index ac6403bb34..8a3fbbf792 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -141,9 +141,18 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components private void removeRooms(IEnumerable rooms) { - foreach (var r in rooms) + foreach (var room in rooms) { - roomFlow.RemoveAll(d => d.Room == r, true); + var drawableRoom = roomFlow.SingleOrDefault(d => d.Room == room); + if (drawableRoom == null) + continue; + + // expire to trigger async disposal. the room still has to exist somewhere so we move it to internal content of RoomsContainer until next frame. + drawableRoom.Hide(); + drawableRoom.Expire(); + + roomFlow.Remove(drawableRoom, false); + AddInternal(drawableRoom); // selection may have a lease due to being in a sub screen. if (!SelectedRoom.Disabled) From 6f9ee3f526745d073c1cf4978a229a796766ad64 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 5 Feb 2024 00:30:48 +0300 Subject: [PATCH 2895/4092] Fix selected room bindable being set to null regardless of the removed room --- osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index ac6403bb34..bd06890753 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -146,7 +146,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components roomFlow.RemoveAll(d => d.Room == r, true); // selection may have a lease due to being in a sub screen. - if (!SelectedRoom.Disabled) + if (SelectedRoom.Value == r && !SelectedRoom.Disabled) SelectedRoom.Value = null; } } From 44a594ba05325bf28197c3727ef5813b36e4ad61 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 5 Feb 2024 01:03:04 +0300 Subject: [PATCH 2896/4092] Simplify playback logic --- .../Expanded/Accuracy/AccuracyCircle.cs | 29 ++++++------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index e5ba9500ee..d209c305fa 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -85,9 +86,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy /// public static readonly Easing ACCURACY_TRANSFORM_EASING = Easing.OutPow10; - [Resolved] - private SkinManager skins { get; set; } - private readonly ScoreInfo score; private CircularProgress accuracyCircle; @@ -101,7 +99,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy private PoolableSkinnableSample swooshUpSound; private PoolableSkinnableSample rankImpactSound; private PoolableSkinnableSample rankApplauseSound; - private PoolableSkinnableSample rankLegacyApplauseSound; private readonly Bindable tickPlaybackRate = new Bindable(); @@ -263,11 +260,15 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy if (isFailedSDueToMisses) AddInternal(failedSRankText = new RankText(ScoreRank.S)); + var applauseSamples = new List { applauseSampleName }; + if (score.Rank >= ScoreRank.B) + // when rank is B or higher, play legacy applause sample on legacy skins. + applauseSamples.Insert(0, @"applause"); + AddRangeInternal(new Drawable[] { rankImpactSound = new PoolableSkinnableSample(new SampleInfo(impactSampleName)), - rankApplauseSound = new PoolableSkinnableSample(new SampleInfo(applauseSampleName)), - rankLegacyApplauseSound = new PoolableSkinnableSample(new SampleInfo("applause")), + rankApplauseSound = new PoolableSkinnableSample(new SampleInfo(applauseSamples.ToArray())), scoreTickSound = new PoolableSkinnableSample(new SampleInfo(@"Results/score-tick")), badgeTickSound = new PoolableSkinnableSample(new SampleInfo(@"Results/badge-dink")), badgeMaxSound = new PoolableSkinnableSample(new SampleInfo(@"Results/badge-dink-max")), @@ -401,20 +402,8 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy { Schedule(() => { - if (skins.CurrentSkin.Value is LegacySkin) - { - // only play legacy "applause" sound if score rank is B or higher. - if (score.Rank >= ScoreRank.B) - { - rankLegacyApplauseSound.VolumeTo(applause_volume); - rankLegacyApplauseSound.Play(); - } - } - else - { - rankApplauseSound.VolumeTo(applause_volume); - rankApplauseSound.Play(); - } + rankApplauseSound.VolumeTo(applause_volume); + rankApplauseSound.Play(); }); } } From 5f39d4590ba1d69980a76537807a8f86b2476c52 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 5 Feb 2024 18:02:07 +0900 Subject: [PATCH 2897/4092] Update global.json version --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index da113e4cbd..789bff3bd0 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.0", + "version": "8.0.100", "rollForward": "latestFeature", "allowPrerelease": false } From 989e46c791de8261a970901fc8732b087d254a79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 5 Feb 2024 13:05:28 +0100 Subject: [PATCH 2898/4092] Use better test step name --- osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 2bda242de2..3f2b71b320 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Ranking Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo); }); - AddToggleStep("toggle skin", v => + AddToggleStep("toggle legacy classic skin", v => { if (skins != null) skins.CurrentSkinInfo.Value = v ? skins.DefaultClassicSkin.SkinInfo : skins.CurrentSkinInfo.Default; From 2f4211249e0b3863d6058252ed55dc0e4f9d6c7f Mon Sep 17 00:00:00 2001 From: Susko3 Date: Mon, 5 Feb 2024 13:12:03 +0100 Subject: [PATCH 2899/4092] Use cleaner way to specify .exe path --- osu.Desktop/OsuGameDesktop.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index c5175fd549..a6d9ff1653 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -138,10 +138,7 @@ namespace osu.Desktop LoadComponentAsync(new GameplayWinKeyBlocker(), Add); if (OperatingSystem.IsWindows() && IsDeployedBuild) - { - string? executableLocation = Path.GetDirectoryName(typeof(OsuGameDesktop).Assembly.Location); - LoadComponentAsync(new WindowsAssociationManager(Path.Join(executableLocation, @"osu!.exe"), "osu"), Add); - } + LoadComponentAsync(new WindowsAssociationManager(Path.ChangeExtension(typeof(OsuGameDesktop).Assembly.Location, ".exe"), "osu"), Add); LoadComponentAsync(new ElevatedPrivilegesChecker(), Add); From efe6bb25b14db26e7c0b79494aa3eaf038dd99b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 5 Feb 2024 13:21:01 +0100 Subject: [PATCH 2900/4092] Refactor result application around again to remove requirement for fields Co-authored-by: Dean Herbert --- .../Objects/Drawables/DrawableHoldNote.cs | 2 +- .../Objects/Drawables/DrawableHoldNoteBody.cs | 12 +++----- .../Drawables/DrawableManiaHitObject.cs | 2 +- .../Objects/Drawables/DrawableNote.cs | 3 +- .../TestSceneHitCircle.cs | 2 +- .../TestSceneHitCircleLateFade.cs | 2 +- .../Objects/Drawables/DrawableHitCircle.cs | 29 ++++++++++--------- .../Objects/Drawables/DrawableOsuHitObject.cs | 4 +-- .../Objects/Drawables/DrawableDrumRoll.cs | 2 +- .../Objects/Drawables/DrawableDrumRollTick.cs | 6 ++-- .../Objects/Drawables/DrawableFlyingHit.cs | 2 +- .../Objects/Drawables/DrawableHit.cs | 10 +++---- .../Drawables/DrawableStrongNestedHit.cs | 2 +- .../Objects/Drawables/DrawableSwell.cs | 2 +- .../Gameplay/TestSceneDrawableHitObject.cs | 2 +- .../Gameplay/TestScenePoolingRuleset.cs | 8 ++--- .../Rulesets/Judgements/JudgementResult.cs | 2 +- .../Objects/Drawables/DrawableHitObject.cs | 17 +++++++++-- 18 files changed, 59 insertions(+), 50 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 6c70ab3526..2b55e81788 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -265,7 +265,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (Tail.AllJudged) { if (Tail.IsHit) - ApplyResult(static (r, _) => r.Type = r.Judgement.MaxResult); + ApplyMaxResult(); else MissForcefully(); } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteBody.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteBody.cs index 731b1b6298..6259033235 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteBody.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteBody.cs @@ -11,8 +11,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables public override bool DisplayResult => false; - private bool hit; - public DrawableHoldNoteBody() : this(null) { @@ -27,12 +25,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { if (AllJudged) return; - this.hit = hit; - ApplyResult(static (r, hitObject) => - { - var holdNoteBody = (DrawableHoldNoteBody)hitObject; - r.Type = holdNoteBody.hit ? r.Judgement.MaxResult : r.Judgement.MinResult; - }); + if (hit) + ApplyMaxResult(); + else + ApplyMinResult(); } } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index 2d10fa27cd..e98622b8bf 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// /// Causes this to get missed, disregarding all conditions in implementations of . /// - public virtual void MissForcefully() => ApplyResult(static (r, _) => r.Type = r.Judgement.MinResult); + public virtual void MissForcefully() => ApplyMinResult(); } public abstract partial class DrawableManiaHitObject : DrawableManiaHitObject diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index a70253798a..2246552abe 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -91,12 +91,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (!userTriggered) { if (!HitObject.HitWindows.CanBeHit(timeOffset)) - ApplyResult(static (r, _) => r.Type = r.Judgement.MinResult); + ApplyMinResult(); return; } hitResult = HitObject.HitWindows.ResultFor(timeOffset); + if (hitResult == HitResult.None) return; diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs index 8d4145f2c1..abe950f9bb 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs @@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Osu.Tests if (auto && !userTriggered && timeOffset > hitOffset && CheckHittable?.Invoke(this, Time.Current, HitResult.Great) == ClickAction.Hit) { // force success - ApplyResult(static (r, _) => r.Type = HitResult.Great); + ApplyResult(HitResult.Great); } else base.CheckForResult(userTriggered, timeOffset); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs index 2d1e9c1270..838b426cb4 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs @@ -208,7 +208,7 @@ namespace osu.Game.Rulesets.Osu.Tests if (shouldHit && !userTriggered && timeOffset >= 0) { // force success - ApplyResult(static (r, _) => r.Type = HitResult.Great); + ApplyResult(HitResult.Great); } else base.CheckForResult(userTriggered, timeOffset); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index ce5422b180..a014ba2e77 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -44,7 +44,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private Container scaleContainer; private InputManager inputManager; - private HitResult hitResult; public DrawableHitCircle() : this(null) @@ -156,12 +155,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (!userTriggered) { if (!HitObject.HitWindows.CanBeHit(timeOffset)) - ApplyResult(static (r, _) => r.Type = r.Judgement.MinResult); + ApplyMinResult(); return; } - hitResult = ResultFor(timeOffset); + HitResult hitResult = ResultFor(timeOffset); var clickAction = CheckHittable?.Invoke(this, Time.Current, hitResult); if (clickAction == ClickAction.Shake) @@ -170,20 +169,22 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (hitResult == HitResult.None || clickAction != ClickAction.Hit) return; - ApplyResult(static (r, hitObject) => + Vector2? hitPosition = null; + + // Todo: This should also consider misses, but they're a little more interesting to handle, since we don't necessarily know the position at the time of a miss. + if (hitResult.IsHit()) + { + var localMousePosition = ToLocalSpace(inputManager.CurrentState.Mouse.Position); + hitPosition = HitObject.StackedPosition + (localMousePosition - DrawSize / 2); + } + + ApplyResult<(HitResult result, Vector2? position)>((r, state) => { - var hitCircle = (DrawableHitCircle)hitObject; var circleResult = (OsuHitCircleJudgementResult)r; - // Todo: This should also consider misses, but they're a little more interesting to handle, since we don't necessarily know the position at the time of a miss. - if (hitCircle.hitResult.IsHit()) - { - var localMousePosition = hitCircle.ToLocalSpace(hitCircle.inputManager.CurrentState.Mouse.Position); - circleResult.CursorPositionAtHit = hitCircle.HitObject.StackedPosition + (localMousePosition - hitCircle.DrawSize / 2); - } - - circleResult.Type = hitCircle.hitResult; - }); + circleResult.Type = state.result; + circleResult.CursorPositionAtHit = state.position; + }, (hitResult, hitPosition)); } /// diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 6de60a9d51..5271c03e08 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -100,12 +100,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables /// /// Causes this to get hit, disregarding all conditions in implementations of . /// - public void HitForcefully() => ApplyResult(static (r, _) => r.Type = r.Judgement.MaxResult); + public void HitForcefully() => ApplyMaxResult(); /// /// Causes this to get missed, disregarding all conditions in implementations of . /// - public void MissForcefully() => ApplyResult(static (r, _) => r.Type = r.Judgement.MinResult); + public void MissForcefully() => ApplyMinResult(); private RectangleF parentScreenSpaceRectangle => ((DrawableOsuHitObject)ParentHitObject)?.parentScreenSpaceRectangle ?? Parent!.ScreenSpaceDrawQuad.AABBFloat; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index e15298f3ca..1af4719b02 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (timeOffset < 0) return; - ApplyResult(static (r, _) => r.Type = r.Judgement.MaxResult); + ApplyMaxResult(); } protected override void UpdateHitStateTransforms(ArmedState state) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs index aa678d7043..0333fd71a9 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs @@ -49,14 +49,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!userTriggered) { if (timeOffset > HitObject.HitWindow) - ApplyResult(static (r, _) => r.Type = r.Judgement.MinResult); + ApplyMinResult(); return; } if (Math.Abs(timeOffset) > HitObject.HitWindow) return; - ApplyResult(static (r, _) => r.Type = r.Judgement.MaxResult); + ApplyMaxResult(); } public override void OnKilled() @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables base.OnKilled(); if (Time.Current > HitObject.GetEndTime() && !Judged) - ApplyResult(static (r, _) => r.Type = r.Judgement.MinResult); + ApplyMinResult(); } protected override void UpdateHitStateTransforms(ArmedState state) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingHit.cs index 4349dff9f9..aad9214c5e 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingHit.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected override void LoadComplete() { base.LoadComplete(); - ApplyResult(static (r, _) => r.Type = r.Judgement.MaxResult); + ApplyMaxResult(); } protected override void LoadSamples() diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index cf8e4050ee..ca49ddb7e1 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -101,7 +101,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!userTriggered) { if (!HitObject.HitWindows.CanBeHit(timeOffset)) - ApplyResult(static (r, _) => r.Type = r.Judgement.MinResult); + ApplyMinResult(); return; } @@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables return; if (!validActionPressed) - ApplyResult(static (r, _) => r.Type = r.Judgement.MinResult); + ApplyMinResult(); else { ApplyResult(static (r, hitObject) => @@ -217,19 +217,19 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!ParentHitObject.Result.IsHit) { - ApplyResult(static (r, _) => r.Type = r.Judgement.MinResult); + ApplyMinResult(); return; } if (!userTriggered) { if (timeOffset - ParentHitObject.Result.TimeOffset > SECOND_HIT_WINDOW) - ApplyResult(static (r, _) => r.Type = r.Judgement.MinResult); + ApplyMinResult(); return; } if (Math.Abs(timeOffset - ParentHitObject.Result.TimeOffset) <= SECOND_HIT_WINDOW) - ApplyResult(static (r, _) => r.Type = r.Judgement.MaxResult); + ApplyMaxResult(); } public override bool OnPressed(KeyBindingPressEvent e) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs index 8f99538448..11759927a9 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables // it can happen that the hit window of the nested strong hit extends past the lifetime of the parent object. // this is a safety to prevent such cases from causing the nested hit to never be judged and as such prevent gameplay from completing. if (!Judged && Time.Current > ParentHitObject?.HitObject.GetEndTime()) - ApplyResult(static (r, _) => r.Type = r.Judgement.MinResult); + ApplyMinResult(); } } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 0781ea5e2a..6eb62cce22 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -208,7 +208,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables expandingRing.ScaleTo(1f + Math.Min(target_ring_scale - 1f, (target_ring_scale - 1f) * completion * 1.3f), 260, Easing.OutQuint); if (numHits == HitObject.RequiredHits) - ApplyResult(static (r, _) => r.Type = r.Judgement.MaxResult); + ApplyMaxResult(); } else { diff --git a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs index bf1e52aab5..73177e36e1 100644 --- a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs +++ b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs @@ -216,7 +216,7 @@ namespace osu.Game.Tests.Gameplay LifetimeStart = LIFETIME_ON_APPLY; } - public void MissForcefully() => ApplyResult(static (r, _) => r.Type = HitResult.Miss); + public void MissForcefully() => ApplyResult(HitResult.Miss); protected override void UpdateHitStateTransforms(ArmedState state) { diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs index 00bd58e303..b567e8de8d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs @@ -431,7 +431,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected override void CheckForResult(bool userTriggered, double timeOffset) { if (timeOffset > HitObject.Duration) - ApplyResult(static (r, _) => r.Type = r.Judgement.MaxResult); + ApplyMaxResult(); } protected override void UpdateHitStateTransforms(ArmedState state) @@ -468,7 +468,7 @@ namespace osu.Game.Tests.Visual.Gameplay public override void OnKilled() { base.OnKilled(); - ApplyResult(static (r, _) => r.Type = r.Judgement.MinResult); + ApplyMinResult(); } } @@ -547,7 +547,7 @@ namespace osu.Game.Tests.Visual.Gameplay { base.CheckForResult(userTriggered, timeOffset); if (timeOffset >= 0) - ApplyResult(static (r, _) => r.Type = r.Judgement.MaxResult); + ApplyMaxResult(); } } @@ -596,7 +596,7 @@ namespace osu.Game.Tests.Visual.Gameplay { base.CheckForResult(userTriggered, timeOffset); if (timeOffset >= 0) - ApplyResult(static (r, _) => r.Type = r.Judgement.MaxResult); + ApplyMaxResult(); } } diff --git a/osu.Game/Rulesets/Judgements/JudgementResult.cs b/osu.Game/Rulesets/Judgements/JudgementResult.cs index b781a13929..4b98df50d7 100644 --- a/osu.Game/Rulesets/Judgements/JudgementResult.cs +++ b/osu.Game/Rulesets/Judgements/JudgementResult.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Judgements /// /// The time at which this occurred. - /// Populated when this is applied via . + /// Populated when this is applied via . /// /// /// This is used instead of to check whether this should be reverted. diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index e30ce13f08..07fab72814 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -682,17 +682,28 @@ namespace osu.Game.Rulesets.Objects.Drawables UpdateResult(false); } + protected void ApplyMaxResult() => ApplyResult((r, _) => r.Type = r.Judgement.MaxResult); + protected void ApplyMinResult() => ApplyResult((r, _) => r.Type = r.Judgement.MinResult); + + protected void ApplyResult(HitResult type) => ApplyResult(static (result, state) => result.Type = state, type); + + [Obsolete("Use overload with state, preferrably with static delegates to avoid allocation overhead.")] // Can be removed 2024-07-26 + protected void ApplyResult(Action application) => ApplyResult((r, _) => application(r), this); + + protected void ApplyResult(Action application) => ApplyResult(application, this); + /// /// Applies the of this , notifying responders such as /// the of the . /// /// The callback that applies changes to the . Using a `static` delegate is recommended to avoid allocation overhead. - protected void ApplyResult(Action application) + /// The state. + protected void ApplyResult(Action application, T state) { if (Result.HasResult) throw new InvalidOperationException("Cannot apply result on a hitobject that already has a result."); - application?.Invoke(Result, this); + application?.Invoke(Result, state); if (!Result.HasResult) throw new InvalidOperationException($"{GetType().ReadableName()} applied a {nameof(JudgementResult)} but did not update {nameof(JudgementResult.Type)}."); @@ -737,7 +748,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// Checks if a scoring result has occurred for this . /// /// - /// If a scoring result has occurred, this method must invoke to update the result and notify responders. + /// If a scoring result has occurred, this method must invoke to update the result and notify responders. /// /// Whether the user triggered this check. /// The offset from the end time of the at which this check occurred. From 2976f225e0627ffa8cb4efe2ad6060e02a6fa5ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 5 Feb 2024 13:22:58 +0100 Subject: [PATCH 2901/4092] Improve xmldoc of `state` param --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 07fab72814..c5f1878d1f 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -697,7 +697,10 @@ namespace osu.Game.Rulesets.Objects.Drawables /// the of the . /// /// The callback that applies changes to the . Using a `static` delegate is recommended to avoid allocation overhead. - /// The state. + /// + /// Use this parameter to pass any data that requires + /// to apply a result, so that it can remain a `static` delegate and thus not allocate. + /// protected void ApplyResult(Action application, T state) { if (Result.HasResult) From fb80d76b4a814a8b79ce441833515f450ab8e12b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 5 Feb 2024 13:35:41 +0100 Subject: [PATCH 2902/4092] Apply further changes to remove remaining weirdness --- .../Drawables/DrawableEmptyFreeformHitObject.cs | 2 +- .../Drawables/DrawablePippidonHitObject.cs | 9 ++++----- .../Drawables/DrawableEmptyScrollingHitObject.cs | 3 +-- .../Drawables/DrawablePippidonHitObject.cs | 10 ++++------ .../Objects/Drawables/DrawableCatchHitObject.cs | 9 ++++----- .../Objects/Drawables/DrawableNote.cs | 15 ++++----------- .../Objects/Drawables/DrawableHitCircle.cs | 10 +++++----- .../Objects/Drawables/DrawableSpinnerTick.cs | 12 ++++-------- .../Objects/Drawables/DrawableHit.cs | 14 +++----------- .../Objects/Drawables/DrawableSwell.cs | 15 ++++++--------- .../Objects/Drawables/DrawableSwellTick.cs | 13 +++++-------- 11 files changed, 41 insertions(+), 71 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/Drawables/DrawableEmptyFreeformHitObject.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/Drawables/DrawableEmptyFreeformHitObject.cs index 3ad8f06fb4..e53fe01157 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/Drawables/DrawableEmptyFreeformHitObject.cs +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/Drawables/DrawableEmptyFreeformHitObject.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.EmptyFreeform.Objects.Drawables { if (timeOffset >= 0) // todo: implement judgement logic - ApplyResult(static (r, hitObject) => r.Type = HitResult.Perfect); + ApplyResult(HitResult.Perfect); } protected override void UpdateHitStateTransforms(ArmedState state) diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs index 925f2d04bf..b1be25727f 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Audio; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Scoring; using osuTK; using osuTK.Graphics; @@ -50,10 +49,10 @@ namespace osu.Game.Rulesets.Pippidon.Objects.Drawables { if (timeOffset >= 0) { - ApplyResult(static (r, hitObject) => - { - r.Type = hitObject.IsHovered ? HitResult.Perfect : HitResult.Miss; - }); + if (IsHovered) + ApplyMaxResult(); + else + ApplyMinResult(); } } diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/Drawables/DrawableEmptyScrollingHitObject.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/Drawables/DrawableEmptyScrollingHitObject.cs index 408bbea717..adcbd36485 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/Drawables/DrawableEmptyScrollingHitObject.cs +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/Drawables/DrawableEmptyScrollingHitObject.cs @@ -3,7 +3,6 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Scoring; using osuTK; using osuTK.Graphics; @@ -24,7 +23,7 @@ namespace osu.Game.Rulesets.EmptyScrolling.Objects.Drawables { if (timeOffset >= 0) // todo: implement judgement logic - ApplyResult(static (r, hitObject) => r.Type = HitResult.Perfect); + ApplyMaxResult(); } protected override void UpdateHitStateTransforms(ArmedState state) diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs index 2c9eac7f65..3ad636a601 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Textures; using osu.Game.Audio; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Pippidon.UI; -using osu.Game.Rulesets.Scoring; using osuTK; using osuTK.Graphics; @@ -50,11 +49,10 @@ namespace osu.Game.Rulesets.Pippidon.Objects.Drawables { if (timeOffset >= 0) { - ApplyResult(static (r, hitObject) => - { - var pippidonHitObject = (DrawablePippidonHitObject)hitObject; - r.Type = pippidonHitObject.currentLane.Value == pippidonHitObject.HitObject.Lane ? HitResult.Perfect : HitResult.Miss; - }); + if (currentLane.Value == HitObject.Lane) + ApplyMaxResult(); + else + ApplyMinResult(); } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs index 721c6aaa59..64705f9909 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs @@ -64,11 +64,10 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables if (timeOffset >= 0 && Result != null) { - ApplyResult(static (r, hitObject) => - { - var catchHitObject = (DrawableCatchHitObject)hitObject; - r.Type = catchHitObject.CheckPosition!.Invoke(catchHitObject.HitObject) ? r.Judgement.MaxResult : r.Judgement.MinResult; - }); + if (CheckPosition.Invoke(HitObject)) + ApplyMaxResult(); + else + ApplyMinResult(); } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 2246552abe..f6b92ab405 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -38,8 +38,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private Drawable headPiece; - private HitResult hitResult; - public DrawableNote() : this(null) { @@ -96,18 +94,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables return; } - hitResult = HitObject.HitWindows.ResultFor(timeOffset); + var result = HitObject.HitWindows.ResultFor(timeOffset); - if (hitResult == HitResult.None) + if (result == HitResult.None) return; - hitResult = GetCappedResult(hitResult); - - ApplyResult(static (r, hitObject) => - { - var note = (DrawableNote)hitObject; - r.Type = note.hitResult; - }); + result = GetCappedResult(result); + ApplyResult(result); } /// diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index a014ba2e77..b1c9bef6c4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -160,19 +160,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables return; } - HitResult hitResult = ResultFor(timeOffset); - var clickAction = CheckHittable?.Invoke(this, Time.Current, hitResult); + var result = ResultFor(timeOffset); + var clickAction = CheckHittable?.Invoke(this, Time.Current, result); if (clickAction == ClickAction.Shake) Shake(); - if (hitResult == HitResult.None || clickAction != ClickAction.Hit) + if (result == HitResult.None || clickAction != ClickAction.Hit) return; Vector2? hitPosition = null; // Todo: This should also consider misses, but they're a little more interesting to handle, since we don't necessarily know the position at the time of a miss. - if (hitResult.IsHit()) + if (result.IsHit()) { var localMousePosition = ToLocalSpace(inputManager.CurrentState.Mouse.Position); hitPosition = HitObject.StackedPosition + (localMousePosition - DrawSize / 2); @@ -184,7 +184,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables circleResult.Type = state.result; circleResult.CursorPositionAtHit = state.position; - }, (hitResult, hitPosition)); + }, (result, hitPosition)); } /// diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs index 628f07a281..0a77faf924 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs @@ -11,8 +11,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public override bool DisplayResult => false; - private bool hit; - public DrawableSpinnerTick() : this(null) { @@ -39,12 +37,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables /// Whether this tick was reached. internal void TriggerResult(bool hit) { - this.hit = hit; - ApplyResult(static (r, hitObject) => - { - var spinnerTick = (DrawableSpinnerTick)hitObject; - r.Type = spinnerTick.hit ? r.Judgement.MaxResult : r.Judgement.MinResult; - }); + if (hit) + ApplyMaxResult(); + else + ApplyMinResult(); } } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index ca49ddb7e1..4fb69056da 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -37,8 +37,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables private double? lastPressHandleTime; - private HitResult hitResult; - private readonly Bindable type = new Bindable(); public DrawableHit() @@ -105,20 +103,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables return; } - hitResult = HitObject.HitWindows.ResultFor(timeOffset); - if (hitResult == HitResult.None) + var result = HitObject.HitWindows.ResultFor(timeOffset); + if (result == HitResult.None) return; if (!validActionPressed) ApplyMinResult(); else - { - ApplyResult(static (r, hitObject) => - { - var drawableHit = (DrawableHit)hitObject; - r.Type = drawableHit.hitResult; - }); - } + ApplyResult(result); } public override bool OnPressed(KeyBindingPressEvent e) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 6eb62cce22..e1fc28fe16 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -41,8 +41,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables private double? lastPressHandleTime; - private int numHits; - public override bool DisplayResult => false; public DrawableSwell() @@ -194,7 +192,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables nextTick?.TriggerResult(true); - numHits = ticks.Count(r => r.IsHit); + int numHits = ticks.Count(r => r.IsHit); float completion = (float)numHits / HitObject.RequiredHits; @@ -215,7 +213,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (timeOffset < 0) return; - numHits = 0; + int numHits = 0; foreach (var tick in ticks) { @@ -229,11 +227,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables tick.TriggerResult(false); } - ApplyResult(static (r, hitObject) => - { - var swell = (DrawableSwell)hitObject; - r.Type = swell.numHits == swell.HitObject.RequiredHits ? r.Judgement.MaxResult : r.Judgement.MinResult; - }); + if (numHits == HitObject.RequiredHits) + ApplyMaxResult(); + else + ApplyMinResult(); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs index 557438e5e5..04dd01e066 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs @@ -15,8 +15,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { public override bool DisplayResult => false; - private bool hit; - public DrawableSwellTick() : this(null) { @@ -31,13 +29,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public void TriggerResult(bool hit) { - this.hit = hit; HitObject.StartTime = Time.Current; - ApplyResult(static (r, hitObject) => - { - var swellTick = (DrawableSwellTick)hitObject; - r.Type = swellTick.hit ? r.Judgement.MaxResult : r.Judgement.MinResult; - }); + + if (hit) + ApplyMaxResult(); + else + ApplyMinResult(); } protected override void CheckForResult(bool userTriggered, double timeOffset) From 01efd1b353f556795f5d30eb4fe6723b7fcd6200 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Mon, 5 Feb 2024 13:34:03 +0100 Subject: [PATCH 2903/4092] Move `WindowsAssociationManager` to `osu.Desktop` --- osu.Desktop/Program.cs | 2 +- .../Windows}/WindowsAssociationManager.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename {osu.Game/Updater => osu.Desktop/Windows}/WindowsAssociationManager.cs (99%) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index c9ce5ebf1b..edbf39a30a 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -5,6 +5,7 @@ using System; using System.IO; using System.Runtime.Versioning; using osu.Desktop.LegacyIpc; +using osu.Desktop.Windows; using osu.Framework; using osu.Framework.Development; using osu.Framework.Logging; @@ -12,7 +13,6 @@ using osu.Framework.Platform; using osu.Game; using osu.Game.IPC; using osu.Game.Tournament; -using osu.Game.Updater; using SDL2; using Squirrel; diff --git a/osu.Game/Updater/WindowsAssociationManager.cs b/osu.Desktop/Windows/WindowsAssociationManager.cs similarity index 99% rename from osu.Game/Updater/WindowsAssociationManager.cs rename to osu.Desktop/Windows/WindowsAssociationManager.cs index bb0e37a2f4..038788f990 100644 --- a/osu.Game/Updater/WindowsAssociationManager.cs +++ b/osu.Desktop/Windows/WindowsAssociationManager.cs @@ -13,7 +13,7 @@ using osu.Framework.Logging; using osu.Game.Resources.Icons; using osu.Game.Localisation; -namespace osu.Game.Updater +namespace osu.Desktop.Windows { [SupportedOSPlatform("windows")] public partial class WindowsAssociationManager : Component From 7789cc01eb31733fa76147adecf73db186c12759 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Mon, 5 Feb 2024 14:03:16 +0100 Subject: [PATCH 2904/4092] Copy .ico files when publishing These icons should appear in end-user installation folder. --- osu.Desktop/Windows/Icons.cs | 10 ++++++++++ osu.Desktop/Windows/Win32Icon.cs | 16 ++++++++++++++++ osu.Desktop/Windows/WindowsAssociationManager.cs | 5 ++--- osu.Desktop/osu.Desktop.csproj | 3 +++ 4 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 osu.Desktop/Windows/Icons.cs create mode 100644 osu.Desktop/Windows/Win32Icon.cs diff --git a/osu.Desktop/Windows/Icons.cs b/osu.Desktop/Windows/Icons.cs new file mode 100644 index 0000000000..cc60f92810 --- /dev/null +++ b/osu.Desktop/Windows/Icons.cs @@ -0,0 +1,10 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Desktop.Windows +{ + public static class Icons + { + public static Win32Icon Lazer => new Win32Icon(@"lazer.ico"); + } +} diff --git a/osu.Desktop/Windows/Win32Icon.cs b/osu.Desktop/Windows/Win32Icon.cs new file mode 100644 index 0000000000..9544846c55 --- /dev/null +++ b/osu.Desktop/Windows/Win32Icon.cs @@ -0,0 +1,16 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Desktop.Windows +{ + public record Win32Icon + { + public readonly string Path; + + internal Win32Icon(string name) + { + string dir = System.IO.Path.GetDirectoryName(typeof(Win32Icon).Assembly.Location)!; + Path = System.IO.Path.Join(dir, name); + } + } +} diff --git a/osu.Desktop/Windows/WindowsAssociationManager.cs b/osu.Desktop/Windows/WindowsAssociationManager.cs index 038788f990..a5f977d15d 100644 --- a/osu.Desktop/Windows/WindowsAssociationManager.cs +++ b/osu.Desktop/Windows/WindowsAssociationManager.cs @@ -10,7 +10,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Localisation; using osu.Framework.Logging; -using osu.Game.Resources.Icons; using osu.Game.Localisation; namespace osu.Desktop.Windows @@ -194,7 +193,7 @@ namespace osu.Desktop.Windows using (var programKey = classes.CreateSubKey(programId)) { using (var defaultIconKey = programKey.CreateSubKey(DEFAULT_ICON)) - defaultIconKey.SetValue(null, Icon.RegistryString); + defaultIconKey.SetValue(null, Icon.Path); using (var openCommandKey = programKey.CreateSubKey(SHELL_OPEN_COMMAND)) openCommandKey.SetValue(null, $@"""{exePath}"" ""%1"""); @@ -249,7 +248,7 @@ namespace osu.Desktop.Windows protocolKey.SetValue(URL_PROTOCOL, string.Empty); using (var defaultIconKey = protocolKey.CreateSubKey(DEFAULT_ICON)) - defaultIconKey.SetValue(null, Icon.RegistryString); + defaultIconKey.SetValue(null, Icon.Path); using (var openCommandKey = protocolKey.CreateSubKey(SHELL_OPEN_COMMAND)) openCommandKey.SetValue(null, $@"""{exePath}"" ""%1"""); diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index d6a11fa924..c6a95c1623 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -31,4 +31,7 @@ + + + From 4ec9d26657167bc7310984f093837ef183cef24f Mon Sep 17 00:00:00 2001 From: Susko3 Date: Mon, 5 Feb 2024 14:16:35 +0100 Subject: [PATCH 2905/4092] Inline constants --- osu.Desktop/OsuGameDesktop.cs | 2 +- .../Windows/WindowsAssociationManager.cs | 31 ++++++++----------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index a6d9ff1653..2e1b34fb38 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -138,7 +138,7 @@ namespace osu.Desktop LoadComponentAsync(new GameplayWinKeyBlocker(), Add); if (OperatingSystem.IsWindows() && IsDeployedBuild) - LoadComponentAsync(new WindowsAssociationManager(Path.ChangeExtension(typeof(OsuGameDesktop).Assembly.Location, ".exe"), "osu"), Add); + LoadComponentAsync(new WindowsAssociationManager(), Add); LoadComponentAsync(new ElevatedPrivilegesChecker(), Add); diff --git a/osu.Desktop/Windows/WindowsAssociationManager.cs b/osu.Desktop/Windows/WindowsAssociationManager.cs index a5f977d15d..7131067224 100644 --- a/osu.Desktop/Windows/WindowsAssociationManager.cs +++ b/osu.Desktop/Windows/WindowsAssociationManager.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.IO; using System.Runtime.InteropServices; using System.Runtime.Versioning; using Microsoft.Win32; @@ -31,6 +32,14 @@ namespace osu.Desktop.Windows /// public const string SHELL_OPEN_COMMAND = @"Shell\Open\Command"; + public static readonly string EXE_PATH = Path.ChangeExtension(typeof(WindowsAssociationManager).Assembly.Location, ".exe"); + + /// + /// Program ID prefix used for file associations. Should be relatively short since the full program ID has a 39 character limit, + /// see https://learn.microsoft.com/en-us/windows/win32/com/-progid--key. + /// + public const string PROGRAM_ID_PREFIX = "osu"; + private static readonly FileAssociation[] file_associations = { new FileAssociation(@".osz", WindowsAssociationManagerStrings.OsuBeatmap, Icons.Lazer), @@ -50,20 +59,6 @@ namespace osu.Desktop.Windows private IBindable localisationParameters = null!; - private readonly string exePath; - private readonly string programIdPrefix; - - /// Path to the executable to register. - /// - /// Program ID prefix used for file associations. Should be relatively short since the full program ID has a 39 character limit, - /// see https://learn.microsoft.com/en-us/windows/win32/com/-progid--key. - /// - public WindowsAssociationManager(string exePath, string programIdPrefix) - { - this.exePath = exePath; - this.programIdPrefix = programIdPrefix; - } - [BackgroundDependencyLoader] private void load() { @@ -87,10 +82,10 @@ namespace osu.Desktop.Windows return; foreach (var association in file_associations) - association.Install(classes, exePath, programIdPrefix); + association.Install(classes, EXE_PATH, PROGRAM_ID_PREFIX); foreach (var association in uri_associations) - association.Install(classes, exePath); + association.Install(classes, EXE_PATH); } updateDescriptions(); @@ -112,7 +107,7 @@ namespace osu.Desktop.Windows foreach (var association in file_associations) { var b = localisation.GetLocalisedBindableString(association.Description); - association.UpdateDescription(classes, programIdPrefix, b.Value); + association.UpdateDescription(classes, PROGRAM_ID_PREFIX, b.Value); b.UnbindAll(); } @@ -131,7 +126,7 @@ namespace osu.Desktop.Windows } } - public void UninstallAssociations() => UninstallAssociations(programIdPrefix); + public void UninstallAssociations() => UninstallAssociations(PROGRAM_ID_PREFIX); public static void UninstallAssociations(string programIdPrefix) { From 0168ade2e13f200f475081d54363b8b9ee835687 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Mon, 5 Feb 2024 14:19:22 +0100 Subject: [PATCH 2906/4092] Remove tests Can be tested with ``` dotnet publish -f net6.0 -r win-x64 osu.Desktop -o publish -c Debug publish\osu! ``` --- .../TestSceneWindowsAssociationManager.cs | 106 ------------------ 1 file changed, 106 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Updater/TestSceneWindowsAssociationManager.cs diff --git a/osu.Game.Tests/Visual/Updater/TestSceneWindowsAssociationManager.cs b/osu.Game.Tests/Visual/Updater/TestSceneWindowsAssociationManager.cs deleted file mode 100644 index f3eb468334..0000000000 --- a/osu.Game.Tests/Visual/Updater/TestSceneWindowsAssociationManager.cs +++ /dev/null @@ -1,106 +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 System; -using System.Diagnostics; -using System.IO; -using System.Runtime.Versioning; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; -using osu.Framework.Platform; -using osu.Game.Graphics.Sprites; -using osu.Game.Tests.Resources; -using osu.Game.Updater; - -namespace osu.Game.Tests.Visual.Updater -{ - [SupportedOSPlatform("windows")] - [Ignore("These tests modify the windows registry and open programs")] - public partial class TestSceneWindowsAssociationManager : OsuTestScene - { - private static readonly string exe_path = Path.ChangeExtension(typeof(TestSceneWindowsAssociationManager).Assembly.Location, ".exe"); - - [Resolved] - private GameHost host { get; set; } = null!; - - private readonly WindowsAssociationManager associationManager; - - public TestSceneWindowsAssociationManager() - { - Children = new Drawable[] - { - new OsuSpriteText { Text = Environment.CommandLine }, - associationManager = new WindowsAssociationManager(exe_path, "osu.Test"), - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - if (Environment.CommandLine.Contains(".osz", StringComparison.Ordinal)) - ChangeBackgroundColour(ColourInfo.SingleColour(Colour4.DarkOliveGreen)); - - if (Environment.CommandLine.Contains("osu://", StringComparison.Ordinal)) - ChangeBackgroundColour(ColourInfo.SingleColour(Colour4.DarkBlue)); - - if (Environment.CommandLine.Contains("osump://", StringComparison.Ordinal)) - ChangeBackgroundColour(ColourInfo.SingleColour(Colour4.DarkRed)); - } - - [Test] - public void TestInstall() - { - AddStep("install", () => associationManager.InstallAssociations()); - } - - [Test] - public void TestOpenBeatmap() - { - string beatmapPath = null!; - AddStep("create temp beatmap", () => beatmapPath = TestResources.GetTestBeatmapForImport()); - AddAssert("beatmap path ends with .osz", () => beatmapPath, () => Does.EndWith(".osz")); - AddStep("open beatmap", () => host.OpenFileExternally(beatmapPath)); - AddUntilStep("wait for focus", () => host.IsActive.Value); - AddStep("delete temp beatmap", () => File.Delete(beatmapPath)); - } - - /// - /// To check that the icon is correct - /// - [Test] - public void TestPresentBeatmap() - { - string beatmapPath = null!; - AddStep("create temp beatmap", () => beatmapPath = TestResources.GetTestBeatmapForImport()); - AddAssert("beatmap path ends with .osz", () => beatmapPath, () => Does.EndWith(".osz")); - AddStep("show beatmap in explorer", () => host.PresentFileExternally(beatmapPath)); - AddUntilStep("wait for focus", () => host.IsActive.Value); - AddStep("delete temp beatmap", () => File.Delete(beatmapPath)); - } - - [TestCase("osu://s/1")] - [TestCase("osump://123")] - public void TestUrl(string url) - { - AddStep($"open {url}", () => Process.Start(new ProcessStartInfo(url) { UseShellExecute = true })); - } - - [Test] - public void TestUninstall() - { - AddStep("uninstall", () => associationManager.UninstallAssociations()); - } - - /// - /// Useful when testing things out and manually changing the registry. - /// - [Test] - public void TestNotifyShell() - { - AddStep("notify shell of changes", WindowsAssociationManager.NotifyShellUpdate); - } - } -} From 17033e09f679f1f2c7800bdb552545967c42fa18 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Mon, 5 Feb 2024 14:29:17 +0100 Subject: [PATCH 2907/4092] Change to class to satisfy CFS hopefully --- osu.Desktop/Windows/Win32Icon.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/Windows/Win32Icon.cs b/osu.Desktop/Windows/Win32Icon.cs index 9544846c55..401e7a2be3 100644 --- a/osu.Desktop/Windows/Win32Icon.cs +++ b/osu.Desktop/Windows/Win32Icon.cs @@ -3,7 +3,7 @@ namespace osu.Desktop.Windows { - public record Win32Icon + public class Win32Icon { public readonly string Path; From 8b9c9f4fedcb8177e5b33061eec320b3832e2d7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 5 Feb 2024 14:52:08 +0100 Subject: [PATCH 2908/4092] Add NRT annotations to `DrawableManiaRuleset` --- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 070021ef74..decf670c5d 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -61,9 +59,9 @@ namespace osu.Game.Rulesets.Mania.UI // Stores the current speed adjustment active in gameplay. private readonly Track speedAdjustmentTrack = new TrackVirtual(0); - private ISkinSource currentSkin; + private ISkinSource currentSkin = null!; - public DrawableManiaRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) + public DrawableManiaRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList? mods = null) : base(ruleset, beatmap, mods) { BarLines = new BarLineGenerator(Beatmap).BarLines; @@ -114,7 +112,7 @@ namespace osu.Game.Rulesets.Mania.UI updateTimeRange(); } - private ScheduledDelegate pendingSkinChange; + private ScheduledDelegate? pendingSkinChange; private float hitPosition; private void onSkinChange() @@ -160,7 +158,7 @@ namespace osu.Game.Rulesets.Mania.UI protected override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant); - public override DrawableHitObject CreateDrawableRepresentation(ManiaHitObject h) => null; + public override DrawableHitObject? CreateDrawableRepresentation(ManiaHitObject h) => null; protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay); From 9e1a24fdefe24dc3d6d8daa57439ffefe4ddacde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 5 Feb 2024 16:20:50 +0100 Subject: [PATCH 2909/4092] Remove pointless enum --- .../Overlays/FirstRunSetup/ScreenImportFromStable.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 0b2b750136..a56af540e4 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -326,13 +326,4 @@ namespace osu.Game.Overlays.FirstRunSetup } } } - - public enum FileSystemAddition - { - [LocalisableDescription(typeof(FirstRunOverlayImportFromStableScreenStrings), nameof(FirstRunOverlayImportFromStableScreenStrings.ToAvoidEnsureNtfs))] - ToAvoidEnsureNtfs, - - [LocalisableDescription(typeof(FirstRunOverlayImportFromStableScreenStrings), nameof(FirstRunOverlayImportFromStableScreenStrings.ToAvoidEnsureHardLinksSupport))] - ToAvoidEnsureHardLinksSupport, - } } From fa894bda059e58626639e4a487181f191f207837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 5 Feb 2024 16:25:44 +0100 Subject: [PATCH 2910/4092] Fix broken spacing --- osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index a56af540e4..526dffa06f 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -137,6 +137,7 @@ namespace osu.Game.Overlays.FirstRunSetup copyInformation.Text = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? FirstRunOverlayImportFromStableScreenStrings.SecondCopyWillBeMade(FirstRunOverlayImportFromStableScreenStrings.ToAvoidEnsureNtfs) : FirstRunOverlayImportFromStableScreenStrings.SecondCopyWillBeMade(FirstRunOverlayImportFromStableScreenStrings.ToAvoidEnsureHardLinksSupport); + copyInformation.AddText(@" "); // just to ensure correct spacing copyInformation.AddLink(GeneralSettingsStrings.ChangeFolderLocation, () => { game?.PerformFromScreen(menu => menu.Push(new MigrationSelectScreen())); From 148e01327b41ebde80f9f1c0a83f844d14a93f1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 5 Feb 2024 16:27:56 +0100 Subject: [PATCH 2911/4092] Split copy info text into two rather than parameterise I have very low hopes translators would be able to correctly navigate this otherwise (especially in languages with different word order). --- .../FirstRunOverlayImportFromStableScreenStrings.cs | 13 ++++--------- .../FirstRunSetup/ScreenImportFromStable.cs | 4 ++-- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs b/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs index bbd83d2395..04fecab3df 100644 --- a/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs +++ b/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs @@ -67,19 +67,14 @@ namespace osu.Game.Localisation public static LocalisableString LightweightLinkingNotSupported => new TranslatableString(getKey(@"lightweight_linking_not_supported"), @"Lightweight linking of files is not supported on your operating system yet, so a copy of all files will be made during import."); /// - /// "A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install {0}." + /// "A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install (and the file system is NTFS)." /// - public static LocalisableString SecondCopyWillBeMade(LocalisableString extra) => new TranslatableString(getKey(@"second_copy_will_be_made"), @"A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install {0}.", extra); + public static LocalisableString SecondCopyWillBeMadeWindows => new TranslatableString(getKey(@"second_copy_will_be_made_windows"), @"A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install (and the file system is NTFS)."); /// - /// "(and the file system is NTFS)" + /// "A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install (and the file system supports hard links)." /// - public static LocalisableString ToAvoidEnsureNtfs => new TranslatableString(getKey(@"to_avoid_ensure_ntfs"), @"(and the file system is NTFS)"); - - /// - /// "(and the file system supports hard links)" - /// - public static LocalisableString ToAvoidEnsureHardLinksSupport => new TranslatableString(getKey(@"to_avoid_ensure_hard_links_support"), @"(and the file system supports hard links)"); + public static LocalisableString SecondCopyWillBeMadeOtherPlatforms => new TranslatableString(getKey(@"second_copy_will_be_made_other_platforms"), @"A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install (and the file system supports hard links)."); private static string getKey(string key) => $@"{prefix}:{key}"; } diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 526dffa06f..b19a9c6c99 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -135,8 +135,8 @@ namespace osu.Game.Overlays.FirstRunSetup else { copyInformation.Text = RuntimeInfo.OS == RuntimeInfo.Platform.Windows - ? FirstRunOverlayImportFromStableScreenStrings.SecondCopyWillBeMade(FirstRunOverlayImportFromStableScreenStrings.ToAvoidEnsureNtfs) - : FirstRunOverlayImportFromStableScreenStrings.SecondCopyWillBeMade(FirstRunOverlayImportFromStableScreenStrings.ToAvoidEnsureHardLinksSupport); + ? FirstRunOverlayImportFromStableScreenStrings.SecondCopyWillBeMadeWindows + : FirstRunOverlayImportFromStableScreenStrings.SecondCopyWillBeMadeOtherPlatforms; copyInformation.AddText(@" "); // just to ensure correct spacing copyInformation.AddLink(GeneralSettingsStrings.ChangeFolderLocation, () => { From 858f2fc7495867a600631e8683cefb193cc255fc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 5 Feb 2024 18:39:04 +0300 Subject: [PATCH 2912/4092] Use `Clear` to trigger async disposal --- .../Lounge/Components/RoomsContainer.cs | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 8a3fbbf792..fd9aa6cb0e 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -126,7 +126,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components case NotifyCollectionChangedAction.Remove: Debug.Assert(args.OldItems != null); - removeRooms(args.OldItems.Cast()); + if (args.OldItems.Count == roomFlow.Count) + clearRooms(); + else + removeRooms(args.OldItems.Cast()); + break; } } @@ -141,18 +145,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components private void removeRooms(IEnumerable rooms) { - foreach (var room in rooms) + foreach (var r in rooms) { - var drawableRoom = roomFlow.SingleOrDefault(d => d.Room == room); - if (drawableRoom == null) - continue; - - // expire to trigger async disposal. the room still has to exist somewhere so we move it to internal content of RoomsContainer until next frame. - drawableRoom.Hide(); - drawableRoom.Expire(); - - roomFlow.Remove(drawableRoom, false); - AddInternal(drawableRoom); + roomFlow.RemoveAll(d => d.Room == r, true); // selection may have a lease due to being in a sub screen. if (!SelectedRoom.Disabled) @@ -160,6 +155,15 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components } } + private void clearRooms() + { + roomFlow.Clear(); + + // selection may have a lease due to being in a sub screen. + if (!SelectedRoom.Disabled) + SelectedRoom.Value = null; + } + private void updateSorting() { foreach (var room in roomFlow) From 4449ccd1d395c44b7a183f639bdd24d281b82eed Mon Sep 17 00:00:00 2001 From: Loreos7 Date: Mon, 5 Feb 2024 18:44:18 +0300 Subject: [PATCH 2913/4092] bring back missing dot --- osu.Game/Localisation/AudioSettingsStrings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Localisation/AudioSettingsStrings.cs b/osu.Game/Localisation/AudioSettingsStrings.cs index f4537a4c1c..405eec985c 100644 --- a/osu.Game/Localisation/AudioSettingsStrings.cs +++ b/osu.Game/Localisation/AudioSettingsStrings.cs @@ -72,7 +72,7 @@ namespace osu.Game.Localisation /// /// "Based on the last {0} play(s), the suggested offset is {1} ms" /// - public static LocalisableString SuggestedOffsetValueReceived(int plays, string value) => new TranslatableString(getKey(@"suggested_offset_value_received"), @"Based on the last {0} play(s), the suggested offset is {1} ms", plays, value); + public static LocalisableString SuggestedOffsetValueReceived(int plays, string value) => new TranslatableString(getKey(@"suggested_offset_value_received"), @"Based on the last {0} play(s), the suggested offset is {1} ms.", plays, value); /// /// "Apply suggested offset" From 8e82327509ad91decc132d56fb065d5fffc0597a Mon Sep 17 00:00:00 2001 From: Loreos7 Date: Mon, 5 Feb 2024 18:45:32 +0300 Subject: [PATCH 2914/4092] one more dot --- osu.Game/Localisation/AudioSettingsStrings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Localisation/AudioSettingsStrings.cs b/osu.Game/Localisation/AudioSettingsStrings.cs index 405eec985c..24508d6858 100644 --- a/osu.Game/Localisation/AudioSettingsStrings.cs +++ b/osu.Game/Localisation/AudioSettingsStrings.cs @@ -70,7 +70,7 @@ namespace osu.Game.Localisation public static LocalisableString SuggestedOffsetNote => new TranslatableString(getKey(@"suggested_offset_note"), @"Play a few beatmaps to receive a suggested offset!"); /// - /// "Based on the last {0} play(s), the suggested offset is {1} ms" + /// "Based on the last {0} play(s), the suggested offset is {1} ms." /// public static LocalisableString SuggestedOffsetValueReceived(int plays, string value) => new TranslatableString(getKey(@"suggested_offset_value_received"), @"Based on the last {0} play(s), the suggested offset is {1} ms.", plays, value); From 791423651640174323beef88c9607d5cde01690b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 5 Feb 2024 18:49:59 +0300 Subject: [PATCH 2915/4092] Add explanatory note --- osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index fd9aa6cb0e..bff1a8c64c 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -126,6 +126,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components case NotifyCollectionChangedAction.Remove: Debug.Assert(args.OldItems != null); + // clear operations have a separate path that benefits from async disposal, + // since disposing is quite expensive when performed on a high number of drawables synchronously. if (args.OldItems.Count == roomFlow.Count) clearRooms(); else From 48d42ca7d39c66a79cb7d5194c86a4a0fef96afd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Feb 2024 23:58:10 +0800 Subject: [PATCH 2916/4092] 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 d944e2ce8e..d7f29beeb3 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 bd6891f448..a4cd26a372 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From 2932184d24512eb945c49542bfe005479722688a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 5 Feb 2024 18:05:21 +0100 Subject: [PATCH 2917/4092] Rename localisable string --- osu.Game/Localisation/GraphicsSettingsStrings.cs | 2 +- osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Localisation/GraphicsSettingsStrings.cs b/osu.Game/Localisation/GraphicsSettingsStrings.cs index d3952d0b4c..753444daf1 100644 --- a/osu.Game/Localisation/GraphicsSettingsStrings.cs +++ b/osu.Game/Localisation/GraphicsSettingsStrings.cs @@ -162,7 +162,7 @@ namespace osu.Game.Localisation /// /// "Shrink game to avoid cameras and notches" /// - public static LocalisableString ShrinkGameOnMobile => new TranslatableString(getKey(@"shrink_game_on_mobile"), @"Shrink game to avoid cameras and notches"); + public static LocalisableString ShrinkGameToSafeArea => new TranslatableString(getKey(@"shrink_game_to_safe_area"), @"Shrink game to avoid cameras and notches"); private static string getKey(string key) => $@"{prefix}:{key}"; } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 4eb3e54708..ce087f1807 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -115,7 +115,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics }, safeAreaConsiderationsCheckbox = new SettingsCheckbox { - LabelText = GraphicsSettingsStrings.ShrinkGameOnMobile, + LabelText = GraphicsSettingsStrings.ShrinkGameToSafeArea, Current = osuConfig.GetBindable(OsuSetting.SafeAreaConsiderations), }, new SettingsSlider From 87381334ffdb12f6b39ad0933cb95da50c676308 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 5 Feb 2024 18:06:51 +0100 Subject: [PATCH 2918/4092] Use more proper method of formatting --- osu.Game/Localisation/AudioSettingsStrings.cs | 2 +- .../Settings/Sections/Audio/AudioOffsetAdjustControl.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Localisation/AudioSettingsStrings.cs b/osu.Game/Localisation/AudioSettingsStrings.cs index 24508d6858..89db60d8a6 100644 --- a/osu.Game/Localisation/AudioSettingsStrings.cs +++ b/osu.Game/Localisation/AudioSettingsStrings.cs @@ -72,7 +72,7 @@ namespace osu.Game.Localisation /// /// "Based on the last {0} play(s), the suggested offset is {1} ms." /// - public static LocalisableString SuggestedOffsetValueReceived(int plays, string value) => new TranslatableString(getKey(@"suggested_offset_value_received"), @"Based on the last {0} play(s), the suggested offset is {1} ms.", plays, value); + public static LocalisableString SuggestedOffsetValueReceived(int plays, LocalisableString value) => new TranslatableString(getKey(@"suggested_offset_value_received"), @"Based on the last {0} play(s), the suggested offset is {1} ms.", plays, value); /// /// "Apply suggested offset" diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs index ad33434848..b9f043a233 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs @@ -8,6 +8,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -156,7 +157,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio { hintText.Text = SuggestedOffset.Value == null ? AudioSettingsStrings.SuggestedOffsetNote - : AudioSettingsStrings.SuggestedOffsetValueReceived(averageHitErrorHistory.Count, $"{SuggestedOffset.Value:N0}"); + : AudioSettingsStrings.SuggestedOffsetValueReceived(averageHitErrorHistory.Count, SuggestedOffset.Value.ToLocalisableString(@"N0")); applySuggestion.Enabled.Value = SuggestedOffset.Value != null; } From a5aeb2ff9e98f6d0a034548c2bc6664359f8ade2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 5 Feb 2024 18:56:20 +0100 Subject: [PATCH 2919/4092] Use better variable names It's not the 1970s. We can spare a few extra letters. --- osu.Game/Rulesets/Scoring/HitEventExtensions.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs index ab36a54d3d..f9c6d62608 100644 --- a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs +++ b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs @@ -22,8 +22,8 @@ namespace osu.Game.Rulesets.Scoring Debug.Assert(hitEvents.All(ev => ev.GameplayRate != null)); int count = 0; - double m = 0; - double s = 0; + double mean = 0; + double sumOfSquares = 0; foreach (var e in hitEvents) { @@ -34,15 +34,15 @@ namespace osu.Game.Rulesets.Scoring // Division by gameplay rate is to account for TimeOffset scaling with gameplay rate. double currentValue = e.TimeOffset / e.GameplayRate!.Value; - double mNext = m + (currentValue - m) / count; - s += (currentValue - m) * (currentValue - mNext); - m = mNext; + double nextMean = mean + (currentValue - mean) / count; + sumOfSquares += (currentValue - mean) * (currentValue - nextMean); + mean = nextMean; } if (count == 0) return null; - return 10.0 * Math.Sqrt(s / count); + return 10.0 * Math.Sqrt(sumOfSquares / count); } /// From 7b03bebd5fd8b4288ea03da66ada81bf7f831781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 5 Feb 2024 18:57:55 +0100 Subject: [PATCH 2920/4092] Move algorithm description to remarks section of xmldoc --- osu.Game/Rulesets/Scoring/HitEventExtensions.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs index f9c6d62608..6e2852676a 100644 --- a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs +++ b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs @@ -11,8 +11,11 @@ namespace osu.Game.Rulesets.Scoring public static class HitEventExtensions { /// - /// Calculates the "unstable rate" for a sequence of s using Welford's online algorithm. + /// Calculates the "unstable rate" for a sequence of s. /// + /// + /// Uses Welford's online algorithm. + /// /// /// A non-null value if unstable rate could be calculated, /// and if unstable rate cannot be calculated due to being empty. From e1a5aeac198903735125d9867389fef946754e61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 5 Feb 2024 19:10:14 +0100 Subject: [PATCH 2921/4092] Commit benchmark to source Co-authored-by: Andrei Zavatski --- osu.Game.Benchmarks/BenchmarkUnstableRate.cs | 31 ++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 osu.Game.Benchmarks/BenchmarkUnstableRate.cs diff --git a/osu.Game.Benchmarks/BenchmarkUnstableRate.cs b/osu.Game.Benchmarks/BenchmarkUnstableRate.cs new file mode 100644 index 0000000000..aa229c7d06 --- /dev/null +++ b/osu.Game.Benchmarks/BenchmarkUnstableRate.cs @@ -0,0 +1,31 @@ +// 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 BenchmarkDotNet.Attributes; +using osu.Framework.Utils; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Benchmarks +{ + public class BenchmarkUnstableRate : BenchmarkTest + { + private List events = null!; + + public override void SetUp() + { + base.SetUp(); + events = new List(); + + for (int i = 0; i < 1000; i++) + events.Add(new HitEvent(RNG.NextDouble(-200.0, 200.0), RNG.NextDouble(1.0, 2.0), HitResult.Great, new HitObject(), null, null)); + } + + [Benchmark] + public void CalculateUnstableRate() + { + _ = events.CalculateUnstableRate(); + } + } +} From c1feccb4cfc6c7929e7adbfc8c1af04145525f77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 5 Feb 2024 19:53:19 +0100 Subject: [PATCH 2922/4092] Add test coverage for selection behaviour --- .../Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index b938e59d63..0883c626fe 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -64,6 +64,12 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("select first room", () => container.Rooms.First().TriggerClick()); AddAssert("first spotlight selected", () => checkRoomSelected(RoomManager.Rooms.First(r => r.Category.Value == RoomCategory.Spotlight))); + + AddStep("remove last room", () => RoomManager.RemoveRoom(RoomManager.Rooms.MinBy(r => r.RoomID?.Value))); + AddAssert("first spotlight still selected", () => checkRoomSelected(RoomManager.Rooms.First(r => r.Category.Value == RoomCategory.Spotlight))); + + AddStep("remove spotlight room", () => RoomManager.RemoveRoom(RoomManager.Rooms.Single(r => r.Category.Value == RoomCategory.Spotlight))); + AddAssert("selection vacated", () => checkRoomSelected(null)); } [Test] From 4be4ed7ab255fe6cff9b6b882e011a26a5ff15ff Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 5 Feb 2024 23:27:16 +0300 Subject: [PATCH 2923/4092] Add "ranked" attribute to scores --- osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs | 4 ++++ osu.Game/Scoring/ScoreInfo.cs | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index 732da3d5da..42b9d9414f 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -115,6 +115,9 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty("has_replay")] public bool HasReplay { get; set; } + [JsonProperty("ranked")] + public bool Ranked { get; set; } + // These properties are calculated or not relevant to any external usage. public bool ShouldSerializeID() => false; public bool ShouldSerializeUser() => false; @@ -212,6 +215,7 @@ namespace osu.Game.Online.API.Requests.Responses HasOnlineReplay = HasReplay, Mods = mods, PP = PP, + Ranked = Ranked, }; if (beatmap is BeatmapInfo realmBeatmap) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 32e4bbbf29..fd98107792 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -107,6 +107,12 @@ namespace osu.Game.Scoring public double? PP { get; set; } + /// + /// Whether the performance points in this score is awarded to the player. This is used for online display purposes (see ). + /// + [Ignored] + public bool Ranked { get; set; } + /// /// The online ID of this score. /// From 6b7ffc240bbe3eb9138c3620ad175d2d9c7a3487 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 5 Feb 2024 23:28:21 +0300 Subject: [PATCH 2924/4092] Support displaying "unranked PP" placeholder --- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 8 +++--- .../Scores/TopScoreStatisticsSection.cs | 8 +++--- .../Sections/Ranks/DrawableProfileScore.cs | 11 ++++++++ .../UnrankedPerformancePointsPlaceholder.cs | 26 +++++++++++++++++++ 4 files changed, 47 insertions(+), 6 deletions(-) create mode 100644 osu.Game/Scoring/Drawables/UnrankedPerformancePointsPlaceholder.cs diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 1fc997fdad..c8ecb38c86 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -180,10 +180,12 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (showPerformancePoints) { - if (score.PP != null) - content.Add(new StatisticText(score.PP, format: @"N0")); - else + if (!score.Ranked) + content.Add(new UnrankedPerformancePointsPlaceholder { Font = OsuFont.GetFont(size: text_size) }); + else if (score.PP == null) content.Add(new UnprocessedPerformancePointsPlaceholder { Size = new Vector2(text_size) }); + else + content.Add(new StatisticText(score.PP, format: @"N0")); } content.Add(new ScoreboardTime(score.Date, text_size) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index 72e590b009..488b99d620 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -125,10 +125,12 @@ namespace osu.Game.Overlays.BeatmapSet.Scores ppColumn.Alpha = value.BeatmapInfo!.Status.GrantsPerformancePoints() ? 1 : 0; - if (value.PP is double pp) - ppColumn.Text = pp.ToLocalisableString(@"N0"); - else + if (!value.Ranked) + ppColumn.Drawable = new UnrankedPerformancePointsPlaceholder { Font = smallFont }; + else if (value.PP is not double pp) ppColumn.Drawable = new UnprocessedPerformancePointsPlaceholder { Size = new Vector2(smallFont.Size) }; + else + ppColumn.Text = pp.ToLocalisableString(@"N0"); statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(createStatisticsColumn); modsColumn.Mods = value.Mods; diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index c26f2f19ba..c7d7af0bd7 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -216,7 +216,18 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks if (!Score.PP.HasValue) { if (Score.Beatmap?.Status.GrantsPerformancePoints() == true) + { + if (!Score.Ranked) + { + return new UnrankedPerformancePointsPlaceholder + { + Font = OsuFont.GetFont(weight: FontWeight.Bold), + Colour = colourProvider.Highlight1 + }; + } + return new UnprocessedPerformancePointsPlaceholder { Size = new Vector2(16), Colour = colourProvider.Highlight1 }; + } return new OsuSpriteText { diff --git a/osu.Game/Scoring/Drawables/UnrankedPerformancePointsPlaceholder.cs b/osu.Game/Scoring/Drawables/UnrankedPerformancePointsPlaceholder.cs new file mode 100644 index 0000000000..4c44def1ee --- /dev/null +++ b/osu.Game/Scoring/Drawables/UnrankedPerformancePointsPlaceholder.cs @@ -0,0 +1,26 @@ +// 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.Framework.Graphics; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; + +namespace osu.Game.Scoring.Drawables +{ + /// + /// A placeholder used in PP columns for scores that do not award PP. + /// + public partial class UnrankedPerformancePointsPlaceholder : SpriteText, IHasTooltip + { + public LocalisableString TooltipText => "pp is not awarded for this score"; // todo: replace with localised string ScoresStrings.StatusNoPp. + + public UnrankedPerformancePointsPlaceholder() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + Text = "-"; + } + } +} From b78c6ed673353d6d9158dc7b24c5278100a4e74f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 5 Feb 2024 23:28:30 +0300 Subject: [PATCH 2925/4092] Update design of "unprocessed PP" placeholder to match web --- .../Drawables/UnprocessedPerformancePointsPlaceholder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/Drawables/UnprocessedPerformancePointsPlaceholder.cs b/osu.Game/Scoring/Drawables/UnprocessedPerformancePointsPlaceholder.cs index 99eb7e964d..a2cb69062e 100644 --- a/osu.Game/Scoring/Drawables/UnprocessedPerformancePointsPlaceholder.cs +++ b/osu.Game/Scoring/Drawables/UnprocessedPerformancePointsPlaceholder.cs @@ -21,7 +21,7 @@ namespace osu.Game.Scoring.Drawables { Anchor = Anchor.Centre; Origin = Anchor.Centre; - Icon = FontAwesome.Solid.ExclamationTriangle; + Icon = FontAwesome.Solid.Sync; } } } From b0da0859d8c91a5b0c22805894bb56680c7a15c7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 5 Feb 2024 23:28:53 +0300 Subject: [PATCH 2926/4092] Add visual test cases --- .../Visual/Online/TestSceneScoresContainer.cs | 19 +++++++++++ .../Online/TestSceneUserProfileScores.cs | 34 ++++++++++++++++--- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index 2bfbf76c10..33f4d577bd 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -154,6 +154,19 @@ namespace osu.Game.Tests.Visual.Online }); } + [Test] + public void TestUnrankedPP() + { + AddStep("Load scores with unranked PP", () => + { + var allScores = createScores(); + allScores.Scores[0].Ranked = false; + allScores.UserScore = createUserBest(); + allScores.UserScore.Score.Ranked = false; + scoresContainer.Scores = allScores; + }); + } + private ulong onlineID = 1; private APIScoresCollection createScores() @@ -184,6 +197,7 @@ namespace osu.Game.Tests.Visual.Online MaxCombo = 1234, TotalScore = 1234567890, Accuracy = 1, + Ranked = true, }, new SoloScoreInfo { @@ -206,6 +220,7 @@ namespace osu.Game.Tests.Visual.Online MaxCombo = 1234, TotalScore = 1234789, Accuracy = 0.9997, + Ranked = true, }, new SoloScoreInfo { @@ -227,6 +242,7 @@ namespace osu.Game.Tests.Visual.Online MaxCombo = 1234, TotalScore = 12345678, Accuracy = 0.9854, + Ranked = true, }, new SoloScoreInfo { @@ -247,6 +263,7 @@ namespace osu.Game.Tests.Visual.Online MaxCombo = 1234, TotalScore = 1234567, Accuracy = 0.8765, + Ranked = true, }, new SoloScoreInfo { @@ -263,6 +280,7 @@ namespace osu.Game.Tests.Visual.Online MaxCombo = 1234, TotalScore = 123456, Accuracy = 0.6543, + Ranked = true, }, } }; @@ -309,6 +327,7 @@ namespace osu.Game.Tests.Visual.Online MaxCombo = 1234, TotalScore = 123456, Accuracy = 0.6543, + Ranked = true, }, Position = 1337, }; diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs index 5249e8694d..f72980757b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs @@ -40,7 +40,8 @@ namespace osu.Game.Tests.Visual.Online new APIMod { Acronym = new OsuModHardRock().Acronym }, new APIMod { Acronym = new OsuModDoubleTime().Acronym }, }, - Accuracy = 0.9813 + Accuracy = 0.9813, + Ranked = true, }; var secondScore = new SoloScoreInfo @@ -62,7 +63,8 @@ namespace osu.Game.Tests.Visual.Online new APIMod { Acronym = new OsuModHardRock().Acronym }, new APIMod { Acronym = new OsuModDoubleTime().Acronym }, }, - Accuracy = 0.998546 + Accuracy = 0.998546, + Ranked = true, }; var thirdScore = new SoloScoreInfo @@ -79,7 +81,8 @@ namespace osu.Game.Tests.Visual.Online DifficultyName = "Insane" }, EndedAt = DateTimeOffset.Now, - Accuracy = 0.9726 + Accuracy = 0.9726, + Ranked = true, }; var noPPScore = new SoloScoreInfo @@ -95,7 +98,8 @@ namespace osu.Game.Tests.Visual.Online DifficultyName = "[4K] Cataclysmic Hypernova" }, EndedAt = DateTimeOffset.Now, - Accuracy = 0.55879 + Accuracy = 0.55879, + Ranked = true, }; var unprocessedPPScore = new SoloScoreInfo @@ -112,7 +116,26 @@ namespace osu.Game.Tests.Visual.Online Status = BeatmapOnlineStatus.Ranked, }, EndedAt = DateTimeOffset.Now, - Accuracy = 0.55879 + Accuracy = 0.55879, + Ranked = true, + }; + + var unrankedPPScore = new SoloScoreInfo + { + Rank = ScoreRank.B, + Beatmap = new APIBeatmap + { + BeatmapSet = new APIBeatmapSet + { + Title = "C18H27NO3(extend)", + Artist = "Team Grimoire", + }, + DifficultyName = "[4K] Cataclysmic Hypernova", + Status = BeatmapOnlineStatus.Ranked, + }, + EndedAt = DateTimeOffset.Now, + Accuracy = 0.55879, + Ranked = false, }; Add(new FillFlowContainer @@ -129,6 +152,7 @@ namespace osu.Game.Tests.Visual.Online new ColourProvidedContainer(OverlayColourScheme.Green, new DrawableProfileScore(secondScore)), new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileScore(noPPScore)), new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileScore(unprocessedPPScore)), + new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileScore(unrankedPPScore)), new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(firstScore, 0.97)), new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(secondScore, 0.85)), new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(thirdScore, 0.66)), From 0da67e64b371c75aa76a4c01e6c3762aa208c603 Mon Sep 17 00:00:00 2001 From: kongehund <63306696+kongehund@users.noreply.github.com> Date: Tue, 6 Feb 2024 00:28:39 +0100 Subject: [PATCH 2927/4092] Fix deselecting slider adding control points --- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 3575e15d1d..e421d497e7 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -171,7 +171,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders return false; // Allow right click to be handled by context menu case MouseButton.Left: - if (e.ControlPressed && IsSelected) + // If there's more than two objects selected, ctrl+click should deselect + if (e.ControlPressed && IsSelected && selectedObjects.Count < 2) { changeHandler?.BeginChange(); placementControlPoint = addControlPoint(e.MousePosition); From 08fac9772082088526845236c1b6dc471cee0e74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 6 Feb 2024 12:27:45 +0100 Subject: [PATCH 2928/4092] Add resources covering failure case --- .../special-skin/taiko-bar-right@2x.png | Bin 0 -> 39297 bytes .../special-skin/taikohitcircle@2x.png | Bin 0 -> 8720 bytes .../special-skin/taikohitcircleoverlay@2x.png | Bin 0 -> 6478 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taiko-bar-right@2x.png create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taikohitcircle@2x.png create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taikohitcircleoverlay@2x.png diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taiko-bar-right@2x.png b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taiko-bar-right@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..8ad7849e53161dc0e6320582aa54953389236a50 GIT binary patch literal 39297 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taikohitcircle@2x.png b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taikohitcircle@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..73f856b16b67b84add2959868e4abca51bb7dea3 GIT binary patch literal 8720 zcmYM41yoy2^xzXTIDz6)LUGqZae}+HrATqN&?2EY4G^?OOL2$dTAY&Nr8pET5`qQs=A4l`_x$GG7i*}mK}O6-3;+Pgv@}(Xu{q|y1R}t`O(>bB0RUi> ztBQ)DmWm1+?3K5ZtA`^1!1E#GgS=*!;e#jxlka&ngtAC!hi@1gIvStyr##RoL;m_L zo1^iExmqe8F&LgETIQP<^m zHc_4=owM1Hs?XK!;iX2}4ppLi14exv6tI8x(fFXEDrhUeCH_$0{)b|j!H@+h2-)r9 z^!4$f%;8CqCv1^Bsrq9%RW|jzbYEqRMkPOMhmTN;z`-CgRci#Sf$Xtk;`bcXE>f3M zV}2@`c@$ON$G0IkY~DPOcD^c1x^cS3SvkR|PDNsdzylMP&L}ZFwyWPRFw>j}?Ceuq zz2bbuyE+GW)F;O`%GMfspm-r~Q6}g{^`VAC!Q5}`xn*Yr(<#!{+q?-;MaS-+0T*n2q+M)nD0FR#BEd>DV1tmf1>{| zoOFoK50{x4Ctq#g<#Av@!1esyuIunVq4~u6#0lNP^cBKJ4*cu|3 z=8M+=fY77=5)fXf=#Q-=^wrW+Bm9R$KtjVty>(9$z}9zjkA06g5#QdKsE&+TRg!Y%%!-Yb4teVRna+3;y5iN(VzBgCGl zgAz&zX(8|7#m9CSpj>Gb+{*~1eUad9D=(WT*0sNfue(Qr2t*kKOQ3?aReU^UZBN%EX<1t(OOcI-2bGp;iZe|VwN z*n{R9YLveXe>MJ0dGaf*vN+1($b*4!NtBz-=grHHv6-kKl)BAZ<5FdFE5uhGj0J8v zzK}$_HD3(_oC&VO`(63XTaf4!rWMbOq8xV}sRoe7yCH(>@t$gk#(+f4F%~4j_)VnV zrY7_(bO(F~amHJMSbi(70EKZ>y(hZ?KhGz+=FR&-3!Fb7)TCZ4m(S3 zsIDGR2;hbDBUZ{&7x^#~MU7g~F0o?oo}{!PgNYvqvgt^?H#`Bn1HLOVx8W9y!ZRbD zC!WVy)yXz+nG4qlk1^q_5E8`4`$`_%cT<+EpiuY?mlcQASo<`dvbYk|K`YzC?Aw--l&AEWirLT!Z{h{;@*3vM)>Hg6-m z`yv*`dEh=l;-uZ7`$Q~)B%Cv?=nq5m7#lR99pp{G%5h4&Lj0%%C^Rln(I=Ad&e4UD z2{=p;8XoZKB_&QE{Y8oK782h5FZLj~4P19Gc&kSrRX3QswOsO`zeXPdUIg5TFS@xz ziqIAV-6iz4MM&+_N%bB*QsO_bKOSKZ(FNgBz=UB{#%@Y-m~J&Qf*Y!RymkRQS}u&s zFrkxcTEmb?fpi3!tfMniGArk12w_1tbgzCAZ`V(L_>8qJs4% z$Qh!23=z^lKpn(454=xwDA~2}7m-_{T_SF3$o%+&ZPYDX$_0#Y5urBAbv2K-rO%F| z)QCG!Zv$60k3UE7tf0~*7PELJn5OW+%S(WBHk$gVnGfD^HC-YJ6}-Z#CWw2`;Kv`3 z?ys&WhFCUr_Y5_7{w9$c0@~Iy!#HAFDw)K7({icH(`5t!cQ@%y)Kec4qs>-0I;${= zN!m8*ng!0iJ!wUf-S%!>jD+Bq;Q?8OMLY=AF5sj~T7%Tor@JZoc-WK01cuadI;zj9 zC`-Kw|86msVMZ13m*pXr&-Id!u@ZojTUrC}vsla&ee7sl08JDJjKn00(I&0PwTdYk zd|I1WW4?55-zF-q=DPThsRDb~yXUj1GLAK!yd zz(M61Jl*ovXR>()zf`+pavM&K|%f8#&LM*u0_!mNb# zl$kt%&f|!OA2|58ID7L5`7wq4vM)sj21vP-JO3?IwD*KdYjC z-LLT_W8LXhT6!v3IbNmQt#4MzHya)G=!^RPo4Y&B=~Gk)oQ;szN9vWrGW%GL6H%&ZjW zd$TID3innOgoExg4Zge&g)O3MY=Ff$a&B(|q@H`ubxX4MYWn2P78ehXP72(m*ZXNG z^6};d?=R-As0q;}CnHW~pWxz}?kw$}&!v-VjGiwBUWoMg`Q0hv#nT?Dy7FFo-sw4~ z%chk5{oCxjp^sgK_KsRsis#rJ%+3xHvT*#$NaXd$FQnLbYYcIYbYi$^{nRY^a%iM@ z#F^&>Y!wtXw!kv(EbQgpHK(|9&sbVEORLJU-O_Rs`2=9`MR;Q`E)L9&2gB_c`Mr|; zyga8h$L8N@LZ#3TBWq!*IOc%#5ILVH7cbCph>gBjqpFTMV|KQG+LC*jp0EV#?_eBx zfQ1vmlKkGsu9(}TZOO!Ir+4?rBO@m+10ib#OzL4LWf%|QkWdnJKJ+82n)Og0U2E!+ zNG9%d!7@@+#T}9{BCxmb++a{&KjO9Vx$Og!nc(jBG1C=Jn}{=c7G>3LVg@t$u(P0PaA!M~TlIA*rqa|kR_gUDmrTjF1hL}g)c6qg+@EGll#?YDrFpYrwM5VS zd@op9OG`Vmn1bEE+*6HASPGR4Gr!mqyAH;H#^y`T$&Ee2I(OIBLi;u(BolYRRze*s z0qUu;<)ld8y>z8clu2t;p+)~L{(BxViH3Y}MRDK$MK@m5%;&v_3Ij)Q z)yE&4oj91s3J~A6ucpJdjhks+4*kv#n5$pjXuZAWeR!}0cd>Hj_RrqvlPUhxZXe5! zzOUuxVY5JO5R5xK%;xdQEQy-=Z^XXn22<~dhI)y!i$PxV>+2WOQ|sqhD7In3o*w(G zIw>2?7ZM^hEIm8G5royVGEJ^G^G#T>eD83rSHYSncrfv=R?Ay1d*A5N7bhJl= z4=ET;=Yg-|*V@~uTZA-}MmPSsxOxGz#F(4cf5Xm6o~5QCGb#P7gfjdDs9n44aITy7cxk+M}B~ODnu9fcKNA$ z6Ppb@xGgU-{|kC(fd#8qTU*4JL9_($g$KnSiG=(?7RZ*$X+R@#=r*vTdemFrCzlts z^++SS$MD6kYe;$U{rB<{DJ2x8nHFmavvv3E6Xcdb2CtIl|1my4NC8+hrM_inE%|7@LJ% zMr%N@jt5J|kiyt#`P4I^^TX)9LEXFe53ly8O?rBKFUw5^-8|P?|2DpRLB_^%@$17Q zfrXl`{Zld$T9`uuax>ju))UxV5_Y*9Gaq^*-K-ql?Rw41P^g73I( z^uam#O<$*Xsl^wlqoQv7hZtM>G-FA6E7jsJ?rtt2@swz8N5`AXojRX))P{`x{pbh!eO`BBs9(@nVhr-3dMyvrm#v14b8{jc-he*We0taR+gH+cpI4?h0< z>KuSZm?A^It}yA0$E0lLP3SKwR(?LlfFEMtQtJL*7UV@{bVA4LYO9M1Z8!Iem%NHg z>XSY7x!(zxp&o{;%T!fXrR2K8GSZ4}r$iEbem_E_=>^O4X3Vhcbty+1a1Gj+GkPNq zJ){{m1ZnR|3h*qni1>qoO7`}+Ms|B;q%|A=42%uxHaIv&tpPRctA=`owY8x_NWY%I z4XrLh=c1BKLUzt>_nx9$AEW0fOUY^~ev;Wm#Tq*|UN$C9Tl{^$}!#H z0)Y!BC!h6?Ah&y*A}ML6Vjgw-*$FEniMOFtd6nDlnn$-}_>iPQQOU=Z8kYPGRETf2 zj~_r@^7v!F#Gv-Bz^-vIboGoDWuAVsajC7uyC(IWM<7w#Av2YUKXwn5kwj&ERCykJ zk3coHPe+|qUmqtB)VyQsP7o_h<{y2eEvl`I6jVt&*1J+L* zs}hqFy>#`UPz6G#K{S>)cNrL#i9}JjXLyH5gU~BpH3eDlH3rsvk^g(`+??3U*PP5| z!fydMNO>7}p40Jv=vKTQw^I1Y(9o)clsJ#%MQR{&AB89zk(F<0J#wJUFxjI63 z>m}rUV?)EoKXclm)uc`J^!$g0yZtcXD(**^~|2BAb|A?Cv6m-C{y7BYA7mt}N zz6dPS)j5qRDnkZFeW8P+ehaohz8+eUAe(UO>8V#g*N2qV=o60GgPKJ)V(}hiLSE*P!-$;uV#bx6I+@$-@7b$J@Z-^7@=SW z1u|_aLHy$>xwQ+)G%qtaV*0w;gU$>spKqLBDZP==;OCS6kp5fAoUu71B$-8Vukk*& zqA}(7QV_*?^TtuDsDG&W+;m@~;w2(ueI2|sChbhF*W7&aqQc?)T)eC>YLkpCl)c&E zH*Lc4u)RIj6hB>9C|{^`{MS^{mXRM7bz)wzdUk3Qy7P4@lm#%iw=kcU(x)Qc`;g-l zPmcPGAW;3LpguX0egA=9(chtQ7TIV;YeQ60DPNdqv6IjGD<lng< zGI#etr$2miy+l8JW6n?s~JjwsE5jNztv6Le4 zb7i90d4FjHb9eWC-*f%vTV{Wm%e_eaN2(W5gh38hzAr7C$M0urzSFel+s_||y-)qc z*`jdL$T?{9PGsJcmaVX{zJ0YiEH>+n-pY^Jy1v`_$`ju#tAE+=O`lpDEThrL^SfEE z4aE^z3j3cpSFc{pNO`k%5ZnH42&k)-{&qPcAjq=uVni7J@l%&@S()p@zPei>#q6u_ z{StCzB($5S=0hiVq7bVm`wQFWb0n|V(L{&GVFSOGbx7#OQ?rJPFE4Lqy(nZd+#Af` zLc;Ghj*j}P$~m}^W*RGoFIj&W7^x{RadLKjq)ced zKdsg@2W9)ErNlvA2g3sc)r(7CzC7NmOIbP)RnkmlHR#W-82U|e*{<&2u;lX$X=DXq zmljY`E?o?9L`n20WTT+$hHL(H5;hOjb?6sQW_TjC#v&@LTOMC@7{LT*tABd0mX!@42=0nGdVlMMWpN=DkY1_h6b;KoI{R&ecj&_;>oF~eqS_i zNxrdul)5n7Rw*PDcptJ^`b)1Uv8Hif-|bCZ?ah&PJOKe)0zl(iaQ=5c1W5iFKcPfo zCOP5vCr@?;`$3F)nRLBvxgy`KA|;>h{}2>@=i2v*ao7xD)V%PMP%7( z?bQc5rus|6z4C z)K1`tkg(ZfTBvfVI)ZyCG(Ak5v=nFkb{rS6z|<@5a)|;Ahd*d5(M214)@!?^YZ6rQ z#_p&E_9kn3{ha7Y*a+(|sc6}%Y+~Y!LDmWR(e+jT{y(&h5=R3>3EtC}6t&}bU-?6! zWSu@8mwo(f-Kmz-2aq$3<6B&dpT#Fs^jkE;EB(*hKtQ9na}T{;J#a}jG^4K3>on*# zth7}9W@}&Dpt#)u9-;FrNH#!HB7vdQA1-&(_Jh^)d~fv)SEHHqQwhtyIjkW}v`tX@ z_|=Q){kZE{A}z^?;EPT&rpGp-*~djJio+C3At@}!>xV|RkZfnCaol8b((0zVPxouD z63sL%t6Xmy*x++}8lOK~!gppy4>2sV7w?L=6uy14G0-RX+uWRyXVqTt`qJ1~SMVd0 zCoQP0_EmR)o}=6E0qL&d&YvlY7vcDS{xFXW^*okFdfJ4ycIp-w;K+C?1(n`2x4Si( z_zpj@yt=zCNL4Ass^#!ZL!vnJ;^r`pc(1CG)$ek0McfJ2GO^jXI_XttxjYMQBk%b zUgvpg^H^7kMxSQ&p0>^kYw@S(tT};O!|()qlSw`L2kb@|oMC>i#gEg|zAjFx_Li75)z!HQ3y`{(JpVYFQM7$! zk6J>KY7jU)J3BL8SkTM*o9G97?^X8sQEIBkuVK~+-005KkdTbU1-*#Bi9;*7T~794 z{*U*a9GAhz#f%*_z`kzsM?SdeV&b#O44D}hf(#5FFms4_9>fDqqG~CvVqRC`-2f*YdODCO2u2@L5e(BA*zMRB(Yh<(DE5qH!p++ z9^BwRsQhW*9s!-_Im5QD_7Q-1QC@?fx-%lBl%I{sG)L7b^M;&E-1Fb4hBx-;M^Zjj zcMNFXY?r92fn2->L9C-3wV&cgwhhx^V@O1TN5m* z8&Gl$Ict~nP#+=z!g05F5cY(U!b8MG8&<;Jc}z{4XIx4P*Ye;2eZp{>=un>_0VA<2 z__oFgWx4MkyH5LsFjbrG@PcgXta!hBv`Iy^fN$#T43xi8LR`5`Aqt5ehk~HxaThmk zF{r6_CSDd*D>2ke&e4tAjunj7$}#ca2SLVM-J+4iM!erC;Lsgx`4CZ(+D|;T>XW14 zApo);w|9&B2EvL-;JQ25^3m6l&;LMUt429`9)cicS8++GX_QORmK^&TEh0xw?4^uF zfgC%kODS{4lJe%8_;+Ppgo{_!;rkgY${TopfPJbNTvZn`P$U_NUTgm)T}(t`#QTRr zj^~UH55OuRDGDk7bm`P3;_sg3&seQGr>tq4Wu(2Jm`ZKN4g*uL5iLWvursnTHscP$ z=@Lo8I@^I$+UX$PetGSZ6z(7@9fZ*ObJX@Y7c}m$W%gVP1mPU^-uS~%IEdaWZM+L9VX zR9E_$jRWv5Jw$5D%>Ixw+LjdSeJA^d<5mz7#fvIKS!tIv6oB)1d;Rq$NNp4hA-e(` zB1{pjpN+Hown!>!+GMfo=$7UW+J|CN#}}bEwYV1%##^^$p(T=20j~7D%6bz?Kz=|> zed~Lr9o*1@lBV&^Rszr0ne7Q-(MTg?KIqd?AaZgj zc~oYNxtgd^k#66V-z@=JW4$h)Eul{ttx!1m$z<8C!lb8k{iK&Xhg4ecDpiEQd!<73 z*xF<*vclw7ixXMKJ)r}nkTD7^M|u+?K_XvxGAhw;c>uJs4Gr4lm9!rwqdt*^>p7%a? zvpc4A12+Ibr*p4M^s=v2aar~kOnl10l}7yiakdh7HK_mdkF;cow3i2ewM$wYno9~Y z*sx}V2qp{5Jo#IIVL$^5CzBjnYYK)<$NX;HQJyHi%`TI)^Kin?#=;!iWLzNwUn{pa zQ2~P;+M5$5aWZHK8d7K)Cbr}9E$Bc7_*H)B#mH!n>(e6f-z$qL$9OvShUGH9tFBrkXNrFu>_HI?M zBSSTTS)x>jpY6v5jDA~SHd6$db_s#29~fgw=~S}GInr&B?#H_rhhq{!4KHSBz_AD) zD*5Qe1=S_N@0=eA)_JLAaa6Ja(N-S2jCX5fYL9V40RO*+wl=kMg;h2K3V*> zqnp$H;2SOv;OL-e#c@TD#Lgb>a}GfFT0Aab@}4u39VM1XkCcs;O7DpZu&9+OG_5mJ cyt@gr&c0MDy5W3}{VxEZrKYc13AKs*U&JQLk^lez literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taikohitcircleoverlay@2x.png b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taikohitcircleoverlay@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..83647266bceeb3a0e5311ecffa0ee7582ca6d6ec GIT binary patch literal 6478 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 A!2kdN literal 0 HcmV?d00001 From 27b97a3c4d720766e4c2468e2890fcaae69d40f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 6 Feb 2024 12:01:35 +0100 Subject: [PATCH 2929/4092] Convert selected legacy skin sprites to grayscale Matching stable. Closes https://github.com/ppy/osu/issues/9858. I would have liked to apply this in the taiko transformer itself, but the limited accessibility of texture uploads, and as such the raw image data, sort of prevents that... --- osu.Game/Skinning/LegacySkin.cs | 3 + osu.Game/Skinning/LegacyTextureLoaderStore.cs | 92 +++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 osu.Game/Skinning/LegacyTextureLoaderStore.cs diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index cfa5f972d2..816cfc0a2d 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -58,6 +58,9 @@ namespace osu.Game.Skinning { } + protected override IResourceStore CreateTextureLoaderStore(IStorageResourceProvider resources, IResourceStore storage) + => new LegacyTextureLoaderStore(base.CreateTextureLoaderStore(resources, storage)); + protected override void ParseConfigurationStream(Stream stream) { base.ParseConfigurationStream(stream); diff --git a/osu.Game/Skinning/LegacyTextureLoaderStore.cs b/osu.Game/Skinning/LegacyTextureLoaderStore.cs new file mode 100644 index 0000000000..8c466e6aac --- /dev/null +++ b/osu.Game/Skinning/LegacyTextureLoaderStore.cs @@ -0,0 +1,92 @@ +// 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.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using osu.Framework.Graphics.Textures; +using osu.Framework.IO.Stores; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Processing; + +namespace osu.Game.Skinning +{ + public class LegacyTextureLoaderStore : IResourceStore + { + private readonly IResourceStore? wrappedStore; + + public LegacyTextureLoaderStore(IResourceStore? wrappedStore) + { + this.wrappedStore = wrappedStore; + } + + public TextureUpload Get(string name) + { + var textureUpload = wrappedStore?.Get(name); + + if (textureUpload == null) + return null!; + + return shouldConvertToGrayscale(name) + ? convertToGrayscale(textureUpload) + : textureUpload; + } + + public Task GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) + { + var textureUpload = wrappedStore?.Get(name); + + if (textureUpload == null) + return null!; + + return shouldConvertToGrayscale(name) + ? Task.Run(() => convertToGrayscale(textureUpload), cancellationToken) + : Task.FromResult(textureUpload); + } + + // https://github.com/peppy/osu-stable-reference/blob/013c3010a9d495e3471a9c59518de17006f9ad89/osu!/Graphics/Textures/TextureManager.cs#L91-L96 + private static readonly string[] grayscale_sprites = + { + @"taiko-bar-right", + @"taikobigcircle", + @"taikohitcircle", + @"taikohitcircleoverlay" + }; + + private bool shouldConvertToGrayscale(string name) + { + foreach (string grayscaleSprite in grayscale_sprites) + { + // unfortunately at this level of lookup we can encounter `@2x` scale suffixes in the name, + // so straight equality cannot be used. + if (name.StartsWith(grayscaleSprite, StringComparison.OrdinalIgnoreCase)) + return true; + } + + return false; + } + + private TextureUpload convertToGrayscale(TextureUpload textureUpload) + { + var image = Image.LoadPixelData(textureUpload.Data.ToArray(), textureUpload.Width, textureUpload.Height); + + // stable uses `0.299 * r + 0.587 * g + 0.114 * b` + // (https://github.com/peppy/osu-stable-reference/blob/013c3010a9d495e3471a9c59518de17006f9ad89/osu!/Graphics/Textures/pTexture.cs#L138-L153) + // which matches mode BT.601 (https://en.wikipedia.org/wiki/Grayscale#Luma_coding_in_video_systems) + image.Mutate(i => i.Grayscale(GrayscaleMode.Bt601)); + + return new TextureUpload(image); + } + + public Stream? GetStream(string name) => wrappedStore?.GetStream(name); + + public IEnumerable GetAvailableResources() => wrappedStore?.GetAvailableResources() ?? Array.Empty(); + + public void Dispose() + { + wrappedStore?.Dispose(); + } + } +} From a84f53b1693892973535250163d13dfe8981ee9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 6 Feb 2024 13:03:05 +0100 Subject: [PATCH 2930/4092] Allow pp for Blinds The mod does impact pp, but it requires no extra difficulty attributes (https://github.com/ppy/osu/pull/26935#issuecomment-1925734171). --- osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs index 8b0adbe50f..bb0e984418 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs @@ -31,6 +31,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModFlashlight) }; + public override bool Ranked => true; private DrawableOsuBlinds blinds = null!; From 8df593a8e6734f59e9c3dc81b903e8e6dc5f20e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 6 Feb 2024 13:03:38 +0100 Subject: [PATCH 2931/4092] Allow pp for No Scope Deemed as not affecting difficulty or pp in https://github.com/ppy/osu/pull/26935#issuecomment-1925644008, so can be treated pretty much as nomod. --- osu.Game/Rulesets/Mods/ModNoScope.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Mods/ModNoScope.cs b/osu.Game/Rulesets/Mods/ModNoScope.cs index 5b9dfc0430..dd1bd9a719 100644 --- a/osu.Game/Rulesets/Mods/ModNoScope.cs +++ b/osu.Game/Rulesets/Mods/ModNoScope.cs @@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.Fun; public override IconUsage? Icon => FontAwesome.Solid.EyeSlash; public override double ScoreMultiplier => 1; + public override bool Ranked => true; /// /// Slightly higher than the cutoff for . From 9e7912e66310fdfaec7bd9e2b27b1f3369eae81a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 6 Feb 2024 21:56:26 +0900 Subject: [PATCH 2932/4092] Add test showing filled heatmap --- .../TestSceneAccuracyHeatmap.cs | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs index f99518997b..5524af2061 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs @@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Osu.Tests { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(130) + Size = new Vector2(300) } }; }); @@ -85,6 +85,30 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("return user input", () => InputManager.UseParentInput = true); } + [Test] + public void TestAllPoints() + { + AddStep("add points", () => + { + float minX = object1.DrawPosition.X - object1.DrawSize.X / 2; + float maxX = object1.DrawPosition.X + object1.DrawSize.X / 2; + + float minY = object1.DrawPosition.Y - object1.DrawSize.Y / 2; + float maxY = object1.DrawPosition.Y + object1.DrawSize.Y / 2; + + for (int i = 0; i < 10; i++) + { + for (float x = minX; x <= maxX; x += 0.5f) + { + for (float y = minY; y <= maxY; y += 0.5f) + { + accuracyHeatmap.AddPoint(object2.Position, object1.Position, new Vector2(x, y), RNG.NextSingle(10, 500)); + } + } + } + }); + } + protected override bool OnMouseDown(MouseDownEvent e) { accuracyHeatmap.AddPoint(object2.Position, object1.Position, background.ToLocalSpace(e.ScreenSpaceMouseDownPosition), 50); From 891346f7958b517d82022cf4ecaf2f980032c5fe Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 6 Feb 2024 21:56:52 +0900 Subject: [PATCH 2933/4092] Fix hit accuracy heatmap points being offset --- osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index 83bab7dc01..f9d4a3b325 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -191,7 +191,7 @@ namespace osu.Game.Rulesets.Osu.Statistics for (int c = 0; c < points_per_dimension; c++) { - HitPointType pointType = Vector2.Distance(new Vector2(c, r), centre) <= innerRadius + HitPointType pointType = Vector2.Distance(new Vector2(c + 0.5f, r + 0.5f), centre) <= innerRadius ? HitPointType.Hit : HitPointType.Miss; From 5850d6a57807aee271aae79532964bc85d345e1a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 6 Feb 2024 20:06:51 +0900 Subject: [PATCH 2934/4092] Show near-misses on the results-screen heatmap --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 7 +- .../Objects/Drawables/DrawableHitCircle.cs | 74 +++++++++++-------- .../Statistics/AccuracyHeatmap.cs | 60 +++++++++------ 3 files changed, 81 insertions(+), 60 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index 10d7af5e58..0e3f972d41 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -95,12 +95,7 @@ namespace osu.Game.Rulesets.Osu.Mods /// private static void blockInputToObjectsUnderSliderHead(DrawableSliderHead slider) { - var oldHitAction = slider.HitArea.Hit; - slider.HitArea.Hit = () => - { - oldHitAction?.Invoke(); - return !slider.DrawableSlider.AllJudged; - }; + slider.HitArea.CanBeHit = () => !slider.DrawableSlider.AllJudged; } private void applyEarlyFading(DrawableHitCircle circle) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index b1c9bef6c4..3727e78d01 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -10,7 +10,6 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; @@ -43,7 +42,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Drawable IHasApproachCircle.ApproachCircle => ApproachCircle; private Container scaleContainer; - private InputManager inputManager; public DrawableHitCircle() : this(null) @@ -73,14 +71,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { HitArea = new HitReceptor { - Hit = () => - { - if (AllJudged) - return false; - - UpdateResult(true); - return true; - }, + CanBeHit = () => !AllJudged, + Hit = () => UpdateResult(true) }, shakeContainer = new ShakeContainer { @@ -114,13 +106,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue)); } - protected override void LoadComplete() - { - base.LoadComplete(); - - inputManager = GetContainingInputManager(); - } - public override double LifetimeStart { get => base.LifetimeStart; @@ -155,7 +140,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (!userTriggered) { if (!HitObject.HitWindows.CanBeHit(timeOffset)) - ApplyMinResult(); + { + ApplyResult((r, position) => + { + var circleResult = (OsuHitCircleJudgementResult)r; + + circleResult.Type = r.Judgement.MinResult; + circleResult.CursorPositionAtHit = position; + }, computeHitPosition()); + } return; } @@ -169,22 +162,21 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (result == HitResult.None || clickAction != ClickAction.Hit) return; - Vector2? hitPosition = null; - - // Todo: This should also consider misses, but they're a little more interesting to handle, since we don't necessarily know the position at the time of a miss. - if (result.IsHit()) - { - var localMousePosition = ToLocalSpace(inputManager.CurrentState.Mouse.Position); - hitPosition = HitObject.StackedPosition + (localMousePosition - DrawSize / 2); - } - ApplyResult<(HitResult result, Vector2? position)>((r, state) => { var circleResult = (OsuHitCircleJudgementResult)r; circleResult.Type = state.result; circleResult.CursorPositionAtHit = state.position; - }, (result, hitPosition)); + }, (result, computeHitPosition())); + } + + private Vector2? computeHitPosition() + { + if (HitArea.ClosestPressPosition is Vector2 screenSpaceHitPosition) + return HitObject.StackedPosition + (ToLocalSpace(screenSpaceHitPosition) - DrawSize / 2); + + return null; } /// @@ -227,6 +219,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables break; case ArmedState.Idle: + HitArea.ClosestPressPosition = null; HitArea.HitAction = null; break; @@ -247,9 +240,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // IsHovered is used public override bool HandlePositionalInput => true; - public Func Hit; + public Func CanBeHit; + public Action Hit; public OsuAction? HitAction; + public Vector2? ClosestPressPosition; public HitReceptor() { @@ -264,12 +259,31 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public bool OnPressed(KeyBindingPressEvent e) { + if (!(CanBeHit?.Invoke() ?? false)) + return false; + switch (e.Action) { case OsuAction.LeftButton: case OsuAction.RightButton: - if (IsHovered && (Hit?.Invoke() ?? false)) + // Only update closest press position while the object hasn't been hit yet. + if (HitAction == null) { + if (ClosestPressPosition is Vector2 curClosest) + { + float oldDist = Vector2.DistanceSquared(curClosest, ScreenSpaceDrawQuad.Centre); + float newDist = Vector2.DistanceSquared(e.ScreenSpaceMousePosition, ScreenSpaceDrawQuad.Centre); + + if (newDist < oldDist) + ClosestPressPosition = e.ScreenSpaceMousePosition; + } + else + ClosestPressPosition = e.ScreenSpaceMousePosition; + } + + if (IsHovered) + { + Hit?.Invoke(); HitAction ??= e.Action; return true; } diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index f9d4a3b325..813f3b2e7a 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -197,7 +197,9 @@ namespace osu.Game.Rulesets.Osu.Statistics var point = new HitPoint(pointType, this) { - BaseColour = pointType == HitPointType.Hit ? new Color4(102, 255, 204, 255) : new Color4(255, 102, 102, 255) + BaseColour = pointType == HitPointType.Hit + ? new Color4(102, 255, 204, 255) + : new Color4(255, 102, 102, 255) }; points[r][c] = point; @@ -250,12 +252,15 @@ namespace osu.Game.Rulesets.Osu.Statistics var rotatedCoordinate = -1 * new Vector2((float)Math.Cos(rotatedAngle), (float)Math.Sin(rotatedAngle)); Vector2 localCentre = new Vector2(points_per_dimension - 1) / 2; - float localRadius = localCentre.X * inner_portion * normalisedDistance; // The radius inside the inner portion which of the heatmap which the closest point lies. + float localRadius = localCentre.X * inner_portion * normalisedDistance; Vector2 localPoint = localCentre + localRadius * rotatedCoordinate; // Find the most relevant hit point. - int r = Math.Clamp((int)Math.Round(localPoint.Y), 0, points_per_dimension - 1); - int c = Math.Clamp((int)Math.Round(localPoint.X), 0, points_per_dimension - 1); + int r = (int)Math.Round(localPoint.Y); + int c = (int)Math.Round(localPoint.X); + + if (r < 0 || r >= points_per_dimension || c < 0 || c >= points_per_dimension) + return; PeakValue = Math.Max(PeakValue, ((HitPoint)pointGrid.Content[r][c]).Increment()); @@ -298,28 +303,35 @@ namespace osu.Game.Rulesets.Osu.Statistics { base.Update(); - // the point at which alpha is saturated and we begin to adjust colour lightness. - const float lighten_cutoff = 0.95f; - - // the amount of lightness to attribute regardless of relative value to peak point. - const float non_relative_portion = 0.2f; - - float amount = 0; - - // give some amount of alpha regardless of relative count - amount += non_relative_portion * Math.Min(1, count / 10f); - - // add relative portion - amount += (1 - non_relative_portion) * (count / heatmap.PeakValue); - - // apply easing - amount = (float)Interpolation.ApplyEasing(Easing.OutQuint, Math.Min(1, amount)); - - Debug.Assert(amount <= 1); - - Alpha = Math.Min(amount / lighten_cutoff, 1); if (pointType == HitPointType.Hit) + { + // the point at which alpha is saturated and we begin to adjust colour lightness. + const float lighten_cutoff = 0.95f; + + // the amount of lightness to attribute regardless of relative value to peak point. + const float non_relative_portion = 0.2f; + + float amount = 0; + + // give some amount of alpha regardless of relative count + amount += non_relative_portion * Math.Min(1, count / 10f); + + // add relative portion + amount += (1 - non_relative_portion) * (count / heatmap.PeakValue); + + // apply easing + amount = (float)Interpolation.ApplyEasing(Easing.OutQuint, Math.Min(1, amount)); + + Debug.Assert(amount <= 1); + + Alpha = Math.Min(amount / lighten_cutoff, 1); Colour = BaseColour.Lighten(Math.Max(0, amount - lighten_cutoff)); + } + else + { + Alpha = 0.8f; + Colour = BaseColour; + } } } From 99d716f8fc62aaaf09ca65fa7da6f8f876bd6b8a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 6 Feb 2024 22:23:30 +0900 Subject: [PATCH 2935/4092] Make miss points into crosses --- .../Statistics/AccuracyHeatmap.cs | 123 ++++++++++-------- 1 file changed, 68 insertions(+), 55 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index 813f3b2e7a..2120b929a7 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -9,6 +9,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -191,18 +192,22 @@ namespace osu.Game.Rulesets.Osu.Statistics for (int c = 0; c < points_per_dimension; c++) { - HitPointType pointType = Vector2.Distance(new Vector2(c + 0.5f, r + 0.5f), centre) <= innerRadius - ? HitPointType.Hit - : HitPointType.Miss; + bool isHit = Vector2.Distance(new Vector2(c + 0.5f, r + 0.5f), centre) <= innerRadius; - var point = new HitPoint(pointType, this) + if (isHit) { - BaseColour = pointType == HitPointType.Hit - ? new Color4(102, 255, 204, 255) - : new Color4(255, 102, 102, 255) - }; - - points[r][c] = point; + points[r][c] = new HitPoint(this) + { + BaseColour = new Color4(102, 255, 204, 255) + }; + } + else + { + points[r][c] = new MissPoint + { + BaseColour = new Color4(255, 102, 102, 255) + }; + } } } @@ -262,33 +267,21 @@ namespace osu.Game.Rulesets.Osu.Statistics if (r < 0 || r >= points_per_dimension || c < 0 || c >= points_per_dimension) return; - PeakValue = Math.Max(PeakValue, ((HitPoint)pointGrid.Content[r][c]).Increment()); + PeakValue = Math.Max(PeakValue, ((GridPoint)pointGrid.Content[r][c]).Increment()); bufferedGrid.ForceRedraw(); } - private partial class HitPoint : Circle + private abstract partial class GridPoint : CompositeDrawable { /// /// The base colour which will be lightened/darkened depending on the value of this . /// public Color4 BaseColour; - private readonly HitPointType pointType; - private readonly AccuracyHeatmap heatmap; + public override bool IsPresent => Count > 0; - public override bool IsPresent => count > 0; - - public HitPoint(HitPointType pointType, AccuracyHeatmap heatmap) - { - this.pointType = pointType; - this.heatmap = heatmap; - - RelativeSizeAxes = Axes.Both; - Alpha = 1; - } - - private int count; + protected int Count { get; private set; } /// /// Increment the value of this point by one. @@ -296,49 +289,69 @@ namespace osu.Game.Rulesets.Osu.Statistics /// The value after incrementing. public int Increment() { - return ++count; + return ++Count; + } + } + + private partial class MissPoint : GridPoint + { + public MissPoint() + { + RelativeSizeAxes = Axes.Both; + + InternalChild = new SpriteIcon + { + RelativeSizeAxes = Axes.Both, + Icon = FontAwesome.Solid.Times + }; + } + + protected override void Update() + { + Alpha = 0.8f; + Colour = BaseColour; + } + } + + private partial class HitPoint : GridPoint + { + private readonly AccuracyHeatmap heatmap; + + public HitPoint(AccuracyHeatmap heatmap) + { + this.heatmap = heatmap; + + RelativeSizeAxes = Axes.Both; + + InternalChild = new Circle { RelativeSizeAxes = Axes.Both }; } protected override void Update() { base.Update(); - if (pointType == HitPointType.Hit) - { - // the point at which alpha is saturated and we begin to adjust colour lightness. - const float lighten_cutoff = 0.95f; + // the point at which alpha is saturated and we begin to adjust colour lightness. + const float lighten_cutoff = 0.95f; - // the amount of lightness to attribute regardless of relative value to peak point. - const float non_relative_portion = 0.2f; + // the amount of lightness to attribute regardless of relative value to peak point. + const float non_relative_portion = 0.2f; - float amount = 0; + float amount = 0; - // give some amount of alpha regardless of relative count - amount += non_relative_portion * Math.Min(1, count / 10f); + // give some amount of alpha regardless of relative count + amount += non_relative_portion * Math.Min(1, Count / 10f); - // add relative portion - amount += (1 - non_relative_portion) * (count / heatmap.PeakValue); + // add relative portion + amount += (1 - non_relative_portion) * (Count / heatmap.PeakValue); - // apply easing - amount = (float)Interpolation.ApplyEasing(Easing.OutQuint, Math.Min(1, amount)); + // apply easing + amount = (float)Interpolation.ApplyEasing(Easing.OutQuint, Math.Min(1, amount)); - Debug.Assert(amount <= 1); + Debug.Assert(amount <= 1); - Alpha = Math.Min(amount / lighten_cutoff, 1); - Colour = BaseColour.Lighten(Math.Max(0, amount - lighten_cutoff)); - } - else - { - Alpha = 0.8f; - Colour = BaseColour; - } + Alpha = Math.Min(amount / lighten_cutoff, 1); + Colour = BaseColour.Lighten(Math.Max(0, amount - lighten_cutoff)); } } - - private enum HitPointType - { - Hit, - Miss - } } } From c21af1bf3d44f3c5b70c95c573748c2f41cd35f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 6 Feb 2024 14:53:01 +0100 Subject: [PATCH 2936/4092] Use more explicit match `taiko-bar-right-glow` is a prefix of `taiko-bar-right`... --- osu.Game/Skinning/LegacyTextureLoaderStore.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyTextureLoaderStore.cs b/osu.Game/Skinning/LegacyTextureLoaderStore.cs index 8c466e6aac..29206bbb85 100644 --- a/osu.Game/Skinning/LegacyTextureLoaderStore.cs +++ b/osu.Game/Skinning/LegacyTextureLoaderStore.cs @@ -61,8 +61,11 @@ namespace osu.Game.Skinning { // unfortunately at this level of lookup we can encounter `@2x` scale suffixes in the name, // so straight equality cannot be used. - if (name.StartsWith(grayscaleSprite, StringComparison.OrdinalIgnoreCase)) + if (name.Equals(grayscaleSprite, StringComparison.OrdinalIgnoreCase) + || name.Equals($@"{grayscaleSprite}@2x", StringComparison.OrdinalIgnoreCase)) + { return true; + } } return false; From ee05743921d8ab7a6b1041b4d47adadee9540b00 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 6 Feb 2024 22:58:11 +0900 Subject: [PATCH 2937/4092] Bump databased star rating versions --- osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs | 2 +- osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index 6bb6879052..4190e74e51 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty private readonly bool isForCurrentRuleset; private readonly double originalOverallDifficulty; - public override int Version => 20220902; + public override int Version => 20230817; public ManiaDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index ab193caaa3..b84c2d25ee 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { private const double difficulty_multiplier = 1.35; - public override int Version => 20220902; + public override int Version => 20221107; public TaikoDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) From 5265d33c126c612a787bed98fa1c665d061c78c0 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 6 Feb 2024 23:32:54 +0900 Subject: [PATCH 2938/4092] Make coverage into a bindable --- .../TestScenePlayfieldCoveringContainer.cs | 12 ++++---- .../Mods/ManiaModPlayfieldCover.cs | 2 +- .../UI/PlayfieldCoveringWrapper.cs | 28 +++++++++++-------- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestScenePlayfieldCoveringContainer.cs b/osu.Game.Rulesets.Mania.Tests/TestScenePlayfieldCoveringContainer.cs index 2a8dc715f9..341d52afcf 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestScenePlayfieldCoveringContainer.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestScenePlayfieldCoveringContainer.cs @@ -39,18 +39,18 @@ namespace osu.Game.Rulesets.Mania.Tests public void TestScrollingDownwards() { AddStep("set down scroll", () => scrollingContainer.Direction = ScrollingDirection.Down); - AddStep("set coverage = 0.5", () => cover.Coverage = 0.5f); - AddStep("set coverage = 0.8f", () => cover.Coverage = 0.8f); - AddStep("set coverage = 0.2f", () => cover.Coverage = 0.2f); + AddStep("set coverage = 0.5", () => cover.Coverage.Value = 0.5f); + AddStep("set coverage = 0.8f", () => cover.Coverage.Value = 0.8f); + AddStep("set coverage = 0.2f", () => cover.Coverage.Value = 0.2f); } [Test] public void TestScrollingUpwards() { AddStep("set up scroll", () => scrollingContainer.Direction = ScrollingDirection.Up); - AddStep("set coverage = 0.5", () => cover.Coverage = 0.5f); - AddStep("set coverage = 0.8f", () => cover.Coverage = 0.8f); - AddStep("set coverage = 0.2f", () => cover.Coverage = 0.2f); + AddStep("set coverage = 0.5", () => cover.Coverage.Value = 0.5f); + AddStep("set coverage = 0.8f", () => cover.Coverage.Value = 0.8f); + AddStep("set coverage = 0.2f", () => cover.Coverage.Value = 0.2f); } } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs index bc76c5cfe9..18c3ecc073 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Mania.Mods { c.RelativeSizeAxes = Axes.Both; c.Direction = ExpandDirection; - c.Coverage = Coverage.Value; + c.Coverage.BindTo(Coverage); })); } } diff --git a/osu.Game.Rulesets.Mania/UI/PlayfieldCoveringWrapper.cs b/osu.Game.Rulesets.Mania/UI/PlayfieldCoveringWrapper.cs index 92f471e36b..0956b2f98f 100644 --- a/osu.Game.Rulesets.Mania/UI/PlayfieldCoveringWrapper.cs +++ b/osu.Game.Rulesets.Mania/UI/PlayfieldCoveringWrapper.cs @@ -19,6 +19,11 @@ namespace osu.Game.Rulesets.Mania.UI /// public partial class PlayfieldCoveringWrapper : CompositeDrawable { + /// + /// The relative area that should be completely covered. This does not include the fade. + /// + public readonly BindableFloat Coverage = new BindableFloat(); + /// /// The complete cover, including gradient and fill. /// @@ -94,21 +99,20 @@ namespace osu.Game.Rulesets.Mania.UI scrollDirection.BindValueChanged(onScrollDirectionChanged, true); } + protected override void LoadComplete() + { + base.LoadComplete(); + + Coverage.BindValueChanged(c => + { + filled.Height = c.NewValue; + gradient.Y = -c.NewValue; + }, true); + } + private void onScrollDirectionChanged(ValueChangedEvent direction) => cover.Rotation = direction.NewValue == ScrollingDirection.Up ? 0 : 180f; - /// - /// The relative area that should be completely covered. This does not include the fade. - /// - public float Coverage - { - set - { - filled.Height = value; - gradient.Y = -value; - } - } - /// /// The direction in which the cover expands. /// From 6ffe8e171373fa78c067f8c38747971ccf91f6a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 6 Feb 2024 14:48:49 +0100 Subject: [PATCH 2939/4092] Use staggered exponential backoff when retrying in `PersistentEndpointClientConnector` There are suspicions that the straight 5s retry could have caused a situation a few days ago for `osu-server-spectator` wherein it was getting hammered by constant retry requests. This should make that a little less likely to happen. Numbers chosen are arbitrary, but mostly follow stable's bancho retry intervals because why not. Stable also skips the exponential backoff in case of errors it considers transient, but I decided not to bother for now. Starts off from 3 seconds, then ramps up to up to 2 minutes. Added stagger factor is 25% of duration, either direction. The stagger factor helps given that if spectator server is dead, each client has three separate connections to it which it will retry on (one to each hub). --- .../PersistentEndpointClientConnector.cs | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/PersistentEndpointClientConnector.cs b/osu.Game/Online/PersistentEndpointClientConnector.cs index 024a0fea73..9e7543ce2b 100644 --- a/osu.Game/Online/PersistentEndpointClientConnector.cs +++ b/osu.Game/Online/PersistentEndpointClientConnector.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Logging; +using osu.Framework.Utils; using osu.Game.Online.API; namespace osu.Game.Online @@ -31,6 +32,12 @@ namespace osu.Game.Online private CancellationTokenSource connectCancelSource = new CancellationTokenSource(); private bool started; + /// + /// How much to delay before attempting to connect again, in milliseconds. + /// Subject to exponential back-off. + /// + private int retryDelay = 3000; + /// /// Constructs a new . /// @@ -78,6 +85,8 @@ namespace osu.Game.Online private async Task connect() { cancelExistingConnect(); + // reset retry delay to default. + retryDelay = 3000; if (!await connectionLock.WaitAsync(10000).ConfigureAwait(false)) throw new TimeoutException("Could not obtain a lock to connect. A previous attempt is likely stuck."); @@ -134,8 +143,15 @@ namespace osu.Game.Online /// private async Task handleErrorAndDelay(Exception exception, CancellationToken cancellationToken) { - Logger.Log($"{ClientName} connect attempt failed: {exception.Message}", LoggingTarget.Network); - await Task.Delay(5000, cancellationToken).ConfigureAwait(false); + // random stagger factor to avoid mass incidental synchronisation + // compare: https://github.com/peppy/osu-stable-reference/blob/013c3010a9d495e3471a9c59518de17006f9ad89/osu!/Online/BanchoClient.cs#L331 + int thisDelay = (int)(retryDelay * RNG.NextDouble(0.75, 1.25)); + // exponential backoff with upper limit + // compare: https://github.com/peppy/osu-stable-reference/blob/013c3010a9d495e3471a9c59518de17006f9ad89/osu!/Online/BanchoClient.cs#L539 + retryDelay = Math.Min(120000, (int)(retryDelay * 1.5)); + + Logger.Log($"{ClientName} connect attempt failed: {exception.Message}. Next attempt in {thisDelay / 1000:N0} seconds.", LoggingTarget.Network); + await Task.Delay(thisDelay, cancellationToken).ConfigureAwait(false); } /// From 5bc7befbd4d780c8f3e473865e3679521bbfe1f7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 6 Feb 2024 23:47:20 +0900 Subject: [PATCH 2940/4092] Add progressive cover to mania HD and FI mods --- .../Mods/ManiaModFadeIn.cs | 11 +------ .../Mods/ManiaModHidden.cs | 31 +++++++++++++------ .../Mods/ManiaModPlayfieldCover.cs | 2 -- osu.Game/Rulesets/Mods/ModHidden.cs | 4 +-- 4 files changed, 25 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs index 196514c7b1..d436f22cdd 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs @@ -3,13 +3,12 @@ using System; using System.Linq; -using osu.Framework.Bindables; using osu.Framework.Localisation; using osu.Game.Rulesets.Mania.UI; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModFadeIn : ManiaModPlayfieldCover + public class ManiaModFadeIn : ManiaModHidden { public override string Name => "Fade In"; public override string Acronym => "FI"; @@ -19,13 +18,5 @@ namespace osu.Game.Rulesets.Mania.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ManiaModHidden)).ToArray(); protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AlongScroll; - - public override BindableNumber Coverage { get; } = new BindableFloat(0.5f) - { - Precision = 0.1f, - MinValue = 0.1f, - MaxValue = 0.7f, - Default = 0.5f, - }; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs index f23cb335a5..7dcd5816a9 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs @@ -6,24 +6,37 @@ using System.Linq; using osu.Framework.Localisation; using osu.Game.Rulesets.Mania.UI; using osu.Framework.Bindables; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModHidden : ManiaModPlayfieldCover { + /// + /// osu!stable is referenced to 480px. + /// + private const float playfield_height = 480; + + private const float min_coverage = 160f / playfield_height; + private const float max_coverage = 400f / playfield_height; + private const float coverage_increase_per_combo = 0.5f / playfield_height; + public override LocalisableString Description => @"Keys fade out before you hit them!"; public override double ScoreMultiplier => 1; - - public override BindableNumber Coverage { get; } = new BindableFloat(0.5f) - { - Precision = 0.1f, - MinValue = 0.2f, - MaxValue = 0.8f, - Default = 0.5f, - }; - public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ManiaModFadeIn)).ToArray(); + public override BindableNumber Coverage { get; } = new BindableFloat(min_coverage); protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AgainstScroll; + + private readonly BindableInt combo = new BindableInt(); + + public override void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) + { + base.ApplyToScoreProcessor(scoreProcessor); + + combo.UnbindAll(); + combo.BindTo(scoreProcessor.Combo); + combo.BindValueChanged(c => Coverage.Value = Math.Min(max_coverage, min_coverage + c.NewValue * coverage_increase_per_combo), true); + } } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs index 18c3ecc073..aadb9b5717 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs @@ -6,7 +6,6 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Configuration; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; @@ -24,7 +23,6 @@ namespace osu.Game.Rulesets.Mania.Mods /// protected abstract CoverExpandDirection ExpandDirection { get; } - [SettingSource("Coverage", "The proportion of playfield height that notes will be hidden for.")] public abstract BindableNumber Coverage { get; } public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index 5a1abf115f..2915cb9bea 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -16,11 +16,11 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.DifficultyIncrease; public override bool Ranked => UsesDefaultConfiguration; - public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) + public virtual void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) { } - public ScoreRank AdjustRank(ScoreRank rank, double accuracy) + public virtual ScoreRank AdjustRank(ScoreRank rank, double accuracy) { switch (rank) { From bacb1d0dc7cf484a0a6b70f5ecdd0f4ec28bd350 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 6 Feb 2024 23:58:27 +0900 Subject: [PATCH 2941/4092] Add easing to make the transition less awkward --- .../UI/PlayfieldCoveringWrapper.cs | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/PlayfieldCoveringWrapper.cs b/osu.Game.Rulesets.Mania/UI/PlayfieldCoveringWrapper.cs index 0956b2f98f..f0ac18d7ca 100644 --- a/osu.Game.Rulesets.Mania/UI/PlayfieldCoveringWrapper.cs +++ b/osu.Game.Rulesets.Mania/UI/PlayfieldCoveringWrapper.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 System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -8,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Utils; using osu.Game.Rulesets.UI.Scrolling; using osuTK; using osuTK.Graphics; @@ -103,11 +105,20 @@ namespace osu.Game.Rulesets.Mania.UI { base.LoadComplete(); - Coverage.BindValueChanged(c => - { - filled.Height = c.NewValue; - gradient.Y = -c.NewValue; - }, true); + updateHeight(Coverage.Value); + } + + protected override void Update() + { + base.Update(); + + updateHeight((float)Interpolation.DampContinuously(filled.Height, Coverage.Value, 25, Math.Abs(Time.Elapsed))); + } + + private void updateHeight(float height) + { + filled.Height = height; + gradient.Y = -height; } private void onScrollDirectionChanged(ValueChangedEvent direction) From 69db1b2778035b0d02ce94b842f8bedc3b88971c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 7 Feb 2024 00:15:06 +0900 Subject: [PATCH 2942/4092] Add ManiaModCover to take over old roles of the mods --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- osu.Game.Rulesets.Mania/Mods/ManiaModCover.cs | 44 +++++++++++++++++++ .../Mods/ManiaModFadeIn.cs | 6 ++- .../Mods/ManiaModHidden.cs | 9 +++- ...Cover.cs => ManiaModWithPlayfieldCover.cs} | 5 ++- 5 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/Mods/ManiaModCover.cs rename osu.Game.Rulesets.Mania/Mods/{ManiaModPlayfieldCover.cs => ManiaModWithPlayfieldCover.cs} (88%) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index c38d6519bd..f19d43826f 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -247,7 +247,7 @@ namespace osu.Game.Rulesets.Mania new ManiaModHardRock(), new MultiMod(new ManiaModSuddenDeath(), new ManiaModPerfect()), new MultiMod(new ManiaModDoubleTime(), new ManiaModNightcore()), - new MultiMod(new ManiaModFadeIn(), new ManiaModHidden()), + new MultiMod(new ManiaModFadeIn(), new ManiaModHidden(), new ManiaModCover()), new ManiaModFlashlight(), new ModAccuracyChallenge(), }; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModCover.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModCover.cs new file mode 100644 index 0000000000..eb243bfab7 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModCover.cs @@ -0,0 +1,44 @@ +// 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.Linq; +using osu.Framework.Bindables; +using osu.Framework.Localisation; +using osu.Game.Configuration; +using osu.Game.Rulesets.Mania.UI; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModCover : ManiaModWithPlayfieldCover + { + public override string Name => "Cover"; + public override string Acronym => "CO"; + + public override LocalisableString Description => @"Decrease the playfield's viewing area."; + + public override double ScoreMultiplier => 1; + + protected override CoverExpandDirection ExpandDirection => Direction.Value; + + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] + { + typeof(ManiaModHidden), + typeof(ManiaModFadeIn) + }).ToArray(); + + public override bool Ranked => false; + + [SettingSource("Coverage", "The proportion of playfield height that notes will be hidden for.")] + public override BindableNumber Coverage { get; } = new BindableFloat(0.5f) + { + Precision = 0.1f, + MinValue = 0.2f, + MaxValue = 0.8f, + Default = 0.5f, + }; + + [SettingSource("Direction", "The direction on which the cover is applied")] + public Bindable Direction { get; } = new Bindable(); + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs index d436f22cdd..54a0b8f36d 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs @@ -15,7 +15,11 @@ namespace osu.Game.Rulesets.Mania.Mods public override LocalisableString Description => @"Keys appear out of nowhere!"; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ManiaModHidden)).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] + { + typeof(ManiaModHidden), + typeof(ManiaModCover) + }).ToArray(); protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AlongScroll; } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs index 7dcd5816a9..f4d5386d70 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModHidden : ManiaModPlayfieldCover + public class ManiaModHidden : ManiaModWithPlayfieldCover { /// /// osu!stable is referenced to 480px. @@ -23,7 +23,12 @@ namespace osu.Game.Rulesets.Mania.Mods public override LocalisableString Description => @"Keys fade out before you hit them!"; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ManiaModFadeIn)).ToArray(); + + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] + { + typeof(ManiaModFadeIn), + typeof(ManiaModCover) + }).ToArray(); public override BindableNumber Coverage { get; } = new BindableFloat(min_coverage); protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AgainstScroll; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModWithPlayfieldCover.cs similarity index 88% rename from osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs rename to osu.Game.Rulesets.Mania/Mods/ManiaModWithPlayfieldCover.cs index aadb9b5717..bb5807269a 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModWithPlayfieldCover.cs @@ -14,7 +14,7 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mania.Mods { - public abstract class ManiaModPlayfieldCover : ModHidden, IApplicableToDrawableRuleset + public abstract class ManiaModWithPlayfieldCover : ModHidden, IApplicableToDrawableRuleset { public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight) }; @@ -23,6 +23,9 @@ namespace osu.Game.Rulesets.Mania.Mods /// protected abstract CoverExpandDirection ExpandDirection { get; } + /// + /// The relative area that should be completely covered. This does not include the fade. + /// public abstract BindableNumber Coverage { get; } public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) From af20eacc82747661f2408b4540d221814ea5e1d8 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 7 Feb 2024 00:25:22 +0900 Subject: [PATCH 2943/4092] Fix coordinate space --- osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs index f4d5386d70..9def29f82b 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs @@ -13,9 +13,9 @@ namespace osu.Game.Rulesets.Mania.Mods public class ManiaModHidden : ManiaModWithPlayfieldCover { /// - /// osu!stable is referenced to 480px. + /// osu!stable is referenced to 768px. /// - private const float playfield_height = 480; + private const float playfield_height = 768; private const float min_coverage = 160f / playfield_height; private const float max_coverage = 400f / playfield_height; From 9314de640fe6f34f41d5aab45be148d7c0af4a42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 6 Feb 2024 18:30:48 +0100 Subject: [PATCH 2944/4092] Populate `TotalScoreInfo` when converting `SoloScoreInfo` to `ScoreInfo` For use in https://github.com/ppy/osu-tools/pull/195. --- osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index 732da3d5da..e4ae83ca74 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -203,6 +203,7 @@ namespace osu.Game.Online.API.Requests.Responses Ruleset = new RulesetInfo { OnlineID = RulesetID }, Passed = Passed, TotalScore = TotalScore, + LegacyTotalScore = LegacyTotalScore, Accuracy = Accuracy, MaxCombo = MaxCombo, Rank = Rank, From ff7cd67909d72c520ec2aabaf7ac96083d812cd3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 6 Feb 2024 21:14:36 +0300 Subject: [PATCH 2945/4092] Move all the circles into their own container --- .../Expanded/Accuracy/GradedCircles.cs | 66 +++++++++---------- 1 file changed, 31 insertions(+), 35 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs index 57b6d8e4ac..33b71c53a7 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs @@ -20,49 +20,45 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy set { progress = value; - dProgress.RevealProgress = value; - cProgress.RevealProgress = value; - bProgress.RevealProgress = value; - aProgress.RevealProgress = value; - sProgress.RevealProgress = value; - xProgress.RevealProgress = value; + + foreach (var circle in circles) + circle.RevealProgress = value; } } - private readonly GradedCircle dProgress; - private readonly GradedCircle cProgress; - private readonly GradedCircle bProgress; - private readonly GradedCircle aProgress; - private readonly GradedCircle sProgress; - private readonly GradedCircle xProgress; + private readonly Container circles; public GradedCircles(double accuracyC, double accuracyB, double accuracyA, double accuracyS, double accuracyX) { - InternalChildren = new Drawable[] + InternalChild = circles = new Container { - dProgress = new GradedCircle(0.0, accuracyC) + RelativeSizeAxes = Axes.Both, + Children = new[] { - Colour = OsuColour.ForRank(ScoreRank.D), - }, - cProgress = new GradedCircle(accuracyC, accuracyB) - { - Colour = OsuColour.ForRank(ScoreRank.C), - }, - bProgress = new GradedCircle(accuracyB, accuracyA) - { - Colour = OsuColour.ForRank(ScoreRank.B), - }, - aProgress = new GradedCircle(accuracyA, accuracyS) - { - Colour = OsuColour.ForRank(ScoreRank.A), - }, - sProgress = new GradedCircle(accuracyS, accuracyX - AccuracyCircle.VIRTUAL_SS_PERCENTAGE) - { - Colour = OsuColour.ForRank(ScoreRank.S), - }, - xProgress = new GradedCircle(accuracyX - AccuracyCircle.VIRTUAL_SS_PERCENTAGE, 1.0) - { - Colour = OsuColour.ForRank(ScoreRank.X) + new GradedCircle(0.0, accuracyC) + { + Colour = OsuColour.ForRank(ScoreRank.D), + }, + new GradedCircle(accuracyC, accuracyB) + { + Colour = OsuColour.ForRank(ScoreRank.C), + }, + new GradedCircle(accuracyB, accuracyA) + { + Colour = OsuColour.ForRank(ScoreRank.B), + }, + new GradedCircle(accuracyA, accuracyS) + { + Colour = OsuColour.ForRank(ScoreRank.A), + }, + new GradedCircle(accuracyS, accuracyX - AccuracyCircle.VIRTUAL_SS_PERCENTAGE) + { + Colour = OsuColour.ForRank(ScoreRank.S), + }, + new GradedCircle(accuracyX - AccuracyCircle.VIRTUAL_SS_PERCENTAGE, 1.0) + { + Colour = OsuColour.ForRank(ScoreRank.X) + } } }; } From 44b1515cc5395486963615d238f2f77c16b5888c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 7 Feb 2024 03:28:40 +0900 Subject: [PATCH 2946/4092] Remove LangVersion redefintions --- osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj | 1 - osu.Game/osu.Game.csproj | 1 - 2 files changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj index 518ab362ca..7817d55f57 100644 --- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj +++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj @@ -4,7 +4,6 @@ Library true click the circles. to the beat. - 10 diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 935b759e4d..9bcdebc347 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -3,7 +3,6 @@ net8.0 Library true - 10 osu! From 9b8f2064867472009e3c5c621c1fbf5823ad7592 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 7 Feb 2024 03:38:07 +0900 Subject: [PATCH 2947/4092] Enable NRT for DrawableHitCircle to clean up --- .../Objects/Drawables/DrawableHitCircle.cs | 46 +++++++++---------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 3727e78d01..b950ef4bbb 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.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.Diagnostics; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -27,34 +24,30 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public partial class DrawableHitCircle : DrawableOsuHitObject, IHasApproachCircle { - public OsuAction? HitAction => HitArea?.HitAction; + public OsuAction? HitAction => HitArea.HitAction; protected virtual OsuSkinComponents CirclePieceComponent => OsuSkinComponents.HitCircle; - public SkinnableDrawable ApproachCircle { get; private set; } - public HitReceptor HitArea { get; private set; } - public SkinnableDrawable CirclePiece { get; private set; } + public SkinnableDrawable ApproachCircle { get; private set; } = null!; + public HitReceptor HitArea { get; private set; } = null!; + public SkinnableDrawable CirclePiece { get; private set; } = null!; - protected override IEnumerable DimmablePieces => new[] - { - CirclePiece, - }; + protected override IEnumerable DimmablePieces => new[] { CirclePiece }; Drawable IHasApproachCircle.ApproachCircle => ApproachCircle; - private Container scaleContainer; + private Container scaleContainer = null!; + private ShakeContainer shakeContainer = null!; public DrawableHitCircle() : this(null) { } - public DrawableHitCircle([CanBeNull] HitCircle h = null) + public DrawableHitCircle(HitCircle? h = null) : base(h) { } - private ShakeContainer shakeContainer; - [BackgroundDependencyLoader] private void load() { @@ -219,8 +212,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables break; case ArmedState.Idle: - HitArea.ClosestPressPosition = null; - HitArea.HitAction = null; + HitArea.Reset(); break; case ArmedState.Miss: @@ -240,11 +232,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // IsHovered is used public override bool HandlePositionalInput => true; - public Func CanBeHit; - public Action Hit; + public required Func CanBeHit { get; set; } + public required Action Hit { get; set; } - public OsuAction? HitAction; - public Vector2? ClosestPressPosition; + public OsuAction? HitAction { get; private set; } + public Vector2? ClosestPressPosition { get; private set; } public HitReceptor() { @@ -259,7 +251,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public bool OnPressed(KeyBindingPressEvent e) { - if (!(CanBeHit?.Invoke() ?? false)) + if (CanBeHit()) return false; switch (e.Action) @@ -283,7 +275,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (IsHovered) { - Hit?.Invoke(); + Hit(); HitAction ??= e.Action; return true; } @@ -297,13 +289,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public void OnReleased(KeyBindingReleaseEvent e) { } + + public void Reset() + { + HitAction = null; + ClosestPressPosition = null; + } } private partial class ProxyableSkinnableDrawable : SkinnableDrawable { public override bool RemoveWhenNotAlive => false; - public ProxyableSkinnableDrawable(ISkinComponentLookup lookup, Func defaultImplementation = null, ConfineMode confineMode = ConfineMode.NoScaling) + public ProxyableSkinnableDrawable(ISkinComponentLookup lookup, Func? defaultImplementation = null, ConfineMode confineMode = ConfineMode.NoScaling) : base(lookup, defaultImplementation, confineMode) { } From 38f7913b31bbc64f99882668b7d8cb89de4d51fd Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 7 Feb 2024 03:50:56 +0900 Subject: [PATCH 2948/4092] Fix inverted condition --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index b950ef4bbb..c9f2983b1e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -251,7 +251,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public bool OnPressed(KeyBindingPressEvent e) { - if (CanBeHit()) + if (!CanBeHit()) return false; switch (e.Action) From 2fc06f16b50c0112522b3c2f164de6aa2aa36608 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 7 Feb 2024 03:51:28 +0900 Subject: [PATCH 2949/4092] Fix inspections --- osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs index c624fbbe73..9d79cb0db4 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs @@ -91,11 +91,11 @@ namespace osu.Game.Rulesets.Osu.Tests var skinnable = firstObject.ApproachCircle; - if (skin == null && skinnable?.Drawable is DefaultApproachCircle) + if (skin == null && skinnable.Drawable is DefaultApproachCircle) // check for default skin provider return true; - var text = skinnable?.Drawable as SpriteText; + var text = skinnable.Drawable as SpriteText; return text?.Text == skin; }); From e2867986c591f042b63c6da36e1f27d40cb89341 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 7 Feb 2024 03:47:36 +0900 Subject: [PATCH 2950/4092] Add xmldocs --- .../Objects/Drawables/DrawableHitCircle.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index c9f2983b1e..f7237d4c03 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -232,10 +232,24 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // IsHovered is used public override bool HandlePositionalInput => true; + /// + /// Whether the hitobject can still be hit at the current point in time. + /// public required Func CanBeHit { get; set; } + + /// + /// An action that's invoked to perform the hit. + /// public required Action Hit { get; set; } + /// + /// The with which the hit was attempted. + /// public OsuAction? HitAction { get; private set; } + + /// + /// The closest position to the hit receptor at the point where the hit was attempted. + /// public Vector2? ClosestPressPosition { get; private set; } public HitReceptor() @@ -290,6 +304,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { } + /// + /// Resets to a fresh state. + /// public void Reset() { HitAction = null; From 1f13124b3834b846281c5d63acc65275c356258e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 7 Feb 2024 03:52:51 +0900 Subject: [PATCH 2951/4092] Always process position as long as it's hittable For example... If a hitobject is pressed but the result is a shake, this will stop processing hits. --- .../Objects/Drawables/DrawableHitCircle.cs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index f7237d4c03..c3ce6acce9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -272,20 +272,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { case OsuAction.LeftButton: case OsuAction.RightButton: - // Only update closest press position while the object hasn't been hit yet. - if (HitAction == null) + if (ClosestPressPosition is Vector2 curClosest) { - if (ClosestPressPosition is Vector2 curClosest) - { - float oldDist = Vector2.DistanceSquared(curClosest, ScreenSpaceDrawQuad.Centre); - float newDist = Vector2.DistanceSquared(e.ScreenSpaceMousePosition, ScreenSpaceDrawQuad.Centre); + float oldDist = Vector2.DistanceSquared(curClosest, ScreenSpaceDrawQuad.Centre); + float newDist = Vector2.DistanceSquared(e.ScreenSpaceMousePosition, ScreenSpaceDrawQuad.Centre); - if (newDist < oldDist) - ClosestPressPosition = e.ScreenSpaceMousePosition; - } - else + if (newDist < oldDist) ClosestPressPosition = e.ScreenSpaceMousePosition; } + else + ClosestPressPosition = e.ScreenSpaceMousePosition; if (IsHovered) { From 8f59cb7659d2fddaaa588417c9c534bee6cc5ebd Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 6 Feb 2024 12:54:53 -0800 Subject: [PATCH 2952/4092] Hide ruleset selector when on kudosu ranking --- osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs index a23ec18afe..1c743ff152 100644 --- a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs +++ b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs @@ -39,5 +39,15 @@ namespace osu.Game.Overlays.Rankings Icon = OsuIcon.Ranking; } } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(scope => + { + rulesetSelector.FadeTo(scope.NewValue <= RankingsScope.Country ? 1 : 0, 200, Easing.OutQuint); + }); + } } } From 21e5ae5ba975aa072e0246196971ebd347aa05a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Feb 2024 16:43:53 +0800 Subject: [PATCH 2953/4092] Minor adjustments --- .../Rankings/RankingsOverlayHeader.cs | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs index 1c743ff152..cf132ed4da 100644 --- a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs +++ b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs @@ -1,13 +1,11 @@ // 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.Framework.Bindables; -using osu.Game.Localisation; -using osu.Game.Resources.Localisation.Web; using osu.Framework.Graphics; using osu.Game.Graphics; +using osu.Game.Localisation; +using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; using osu.Game.Users; @@ -19,8 +17,8 @@ namespace osu.Game.Overlays.Rankings public Bindable Country => countryFilter.Current; - private OverlayRulesetSelector rulesetSelector; - private CountryFilter countryFilter; + private OverlayRulesetSelector rulesetSelector = null!; + private CountryFilter countryFilter = null!; protected override OverlayTitle CreateTitle() => new RankingsTitle(); @@ -46,8 +44,23 @@ namespace osu.Game.Overlays.Rankings Current.BindValueChanged(scope => { - rulesetSelector.FadeTo(scope.NewValue <= RankingsScope.Country ? 1 : 0, 200, Easing.OutQuint); - }); + rulesetSelector.FadeTo(showRulesetSelector(scope.NewValue) ? 1 : 0, 200, Easing.OutQuint); + }, true); + + bool showRulesetSelector(RankingsScope scope) + { + switch (scope) + { + case RankingsScope.Performance: + case RankingsScope.Spotlights: + case RankingsScope.Score: + case RankingsScope.Country: + return true; + + default: + return false; + } + } } } } From 9a3c947319c93e19299424a40b9aa982dffe11c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 7 Feb 2024 13:35:12 +0100 Subject: [PATCH 2954/4092] Remove unnecessary `#nullable disable` --- .../Scoring/Drawables/UnrankedPerformancePointsPlaceholder.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Scoring/Drawables/UnrankedPerformancePointsPlaceholder.cs b/osu.Game/Scoring/Drawables/UnrankedPerformancePointsPlaceholder.cs index 4c44def1ee..025977e745 100644 --- a/osu.Game/Scoring/Drawables/UnrankedPerformancePointsPlaceholder.cs +++ b/osu.Game/Scoring/Drawables/UnrankedPerformancePointsPlaceholder.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. -#nullable disable using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; From f22828bc50db359957c13abceee296ac7b3b95fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 7 Feb 2024 13:36:46 +0100 Subject: [PATCH 2955/4092] Flip `if`s to reduce nesting --- .../Sections/Ranks/DrawableProfileScore.cs | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index c7d7af0bd7..d1988956be 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -213,22 +213,36 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks private Drawable createDrawablePerformance() { - if (!Score.PP.HasValue) + if (Score.PP.HasValue) { - if (Score.Beatmap?.Status.GrantsPerformancePoints() == true) + return new FillFlowContainer { - if (!Score.Ranked) + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new[] { - return new UnrankedPerformancePointsPlaceholder + new OsuSpriteText { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, Font = OsuFont.GetFont(weight: FontWeight.Bold), + Text = $"{Score.PP:0}", Colour = colourProvider.Highlight1 - }; + }, + new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Text = "pp", + Colour = colourProvider.Light3 + } } + }; + } - return new UnprocessedPerformancePointsPlaceholder { Size = new Vector2(16), Colour = colourProvider.Highlight1 }; - } - + if (Score.Beatmap?.Status.GrantsPerformancePoints() != true) + { return new OsuSpriteText { Font = OsuFont.GetFont(weight: FontWeight.Bold), @@ -237,30 +251,16 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks }; } - return new FillFlowContainer + if (!Score.Ranked) { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Children = new[] + return new UnrankedPerformancePointsPlaceholder { - new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Font = OsuFont.GetFont(weight: FontWeight.Bold), - Text = $"{Score.PP:0}", - Colour = colourProvider.Highlight1 - }, - new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), - Text = "pp", - Colour = colourProvider.Light3 - } - } - }; + Font = OsuFont.GetFont(weight: FontWeight.Bold), + Colour = colourProvider.Highlight1 + }; + } + + return new UnprocessedPerformancePointsPlaceholder { Size = new Vector2(16), Colour = colourProvider.Highlight1 }; } private partial class ScoreBeatmapMetadataContainer : BeatmapMetadataContainer From 3f51148719918f0b5d8d91b1575a87319d21a3b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 7 Feb 2024 13:37:37 +0100 Subject: [PATCH 2956/4092] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 935b759e4d..db41b04e44 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + From 706a16677c1e86b921301d8d499487c7b954c354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 7 Feb 2024 13:38:05 +0100 Subject: [PATCH 2957/4092] Use localised string --- .../Scoring/Drawables/UnrankedPerformancePointsPlaceholder.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Scoring/Drawables/UnrankedPerformancePointsPlaceholder.cs b/osu.Game/Scoring/Drawables/UnrankedPerformancePointsPlaceholder.cs index 025977e745..c5c190e1a1 100644 --- a/osu.Game/Scoring/Drawables/UnrankedPerformancePointsPlaceholder.cs +++ b/osu.Game/Scoring/Drawables/UnrankedPerformancePointsPlaceholder.cs @@ -5,6 +5,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Scoring.Drawables { @@ -13,7 +14,7 @@ namespace osu.Game.Scoring.Drawables /// public partial class UnrankedPerformancePointsPlaceholder : SpriteText, IHasTooltip { - public LocalisableString TooltipText => "pp is not awarded for this score"; // todo: replace with localised string ScoresStrings.StatusNoPp. + public LocalisableString TooltipText => ScoresStrings.StatusNoPp; public UnrankedPerformancePointsPlaceholder() { From 8f995a30af27f3cd90df67d1c45e434e028daf5d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 8 Feb 2024 00:20:32 +0900 Subject: [PATCH 2958/4092] Fix legacy coverage metrics --- .../Mods/ManiaModHidden.cs | 29 +++++++++++++++---- .../Mods/ManiaModWithPlayfieldCover.cs | 4 ++- .../UI/PlayfieldCoveringWrapper.cs | 14 ++++++--- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs index 9def29f82b..211f21513d 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs @@ -6,20 +6,21 @@ using System.Linq; using osu.Framework.Localisation; using osu.Game.Rulesets.Mania.UI; using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModHidden : ManiaModWithPlayfieldCover + public partial class ManiaModHidden : ManiaModWithPlayfieldCover { /// /// osu!stable is referenced to 768px. /// - private const float playfield_height = 768; + private const float reference_playfield_height = 768; - private const float min_coverage = 160f / playfield_height; - private const float max_coverage = 400f / playfield_height; - private const float coverage_increase_per_combo = 0.5f / playfield_height; + private const float min_coverage = 160 / reference_playfield_height; + private const float max_coverage = 400f / reference_playfield_height; + private const float coverage_increase_per_combo = 0.5f / reference_playfield_height; public override LocalisableString Description => @"Keys fade out before you hit them!"; public override double ScoreMultiplier => 1; @@ -43,5 +44,23 @@ namespace osu.Game.Rulesets.Mania.Mods combo.BindTo(scoreProcessor.Combo); combo.BindValueChanged(c => Coverage.Value = Math.Min(max_coverage, min_coverage + c.NewValue * coverage_increase_per_combo), true); } + + protected override PlayfieldCoveringWrapper CreateCover(Drawable content) => new LegacyPlayfieldCover(content); + + private partial class LegacyPlayfieldCover : PlayfieldCoveringWrapper + { + public LegacyPlayfieldCover(Drawable content) + : base(content) + { + } + + protected override float GetHeight(float coverage) + { + if (DrawHeight == 0) + return base.GetHeight(coverage); + + return base.GetHeight(coverage) * reference_playfield_height / DrawHeight; + } + } } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModWithPlayfieldCover.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModWithPlayfieldCover.cs index bb5807269a..864ef6c3d6 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModWithPlayfieldCover.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModWithPlayfieldCover.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Mania.Mods Container hocParent = (Container)hoc.Parent!; hocParent.Remove(hoc, false); - hocParent.Add(new PlayfieldCoveringWrapper(hoc).With(c => + hocParent.Add(CreateCover(hoc).With(c => { c.RelativeSizeAxes = Axes.Both; c.Direction = ExpandDirection; @@ -47,6 +47,8 @@ namespace osu.Game.Rulesets.Mania.Mods } } + protected virtual PlayfieldCoveringWrapper CreateCover(Drawable content) => new PlayfieldCoveringWrapper(content); + protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) { } diff --git a/osu.Game.Rulesets.Mania/UI/PlayfieldCoveringWrapper.cs b/osu.Game.Rulesets.Mania/UI/PlayfieldCoveringWrapper.cs index f0ac18d7ca..2b70c527ae 100644 --- a/osu.Game.Rulesets.Mania/UI/PlayfieldCoveringWrapper.cs +++ b/osu.Game.Rulesets.Mania/UI/PlayfieldCoveringWrapper.cs @@ -43,6 +43,8 @@ namespace osu.Game.Rulesets.Mania.UI private readonly IBindable scrollDirection = new Bindable(); + private float currentCoverage; + public PlayfieldCoveringWrapper(Drawable content) { InternalChild = new BufferedContainer @@ -112,15 +114,19 @@ namespace osu.Game.Rulesets.Mania.UI { base.Update(); - updateHeight((float)Interpolation.DampContinuously(filled.Height, Coverage.Value, 25, Math.Abs(Time.Elapsed))); + updateHeight((float)Interpolation.DampContinuously(currentCoverage, Coverage.Value, 25, Math.Abs(Time.Elapsed))); } - private void updateHeight(float height) + private void updateHeight(float coverage) { - filled.Height = height; - gradient.Y = -height; + filled.Height = GetHeight(coverage); + gradient.Y = -GetHeight(coverage); + + currentCoverage = coverage; } + protected virtual float GetHeight(float coverage) => coverage; + private void onScrollDirectionChanged(ValueChangedEvent direction) => cover.Rotation = direction.NewValue == ScrollingDirection.Up ? 0 : 180f; From 0d7e82ab8df41e6950d97c1fa67e28389ca699d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Feb 2024 00:20:53 +0800 Subject: [PATCH 2959/4092] Improve exception logging of unobserved exceptions via `FireAndForget` Coming from https://github.com/ppy/osu/issues/27076, where the log output makes finding where the exception arrived for nigh impossible. --- osu.Game/Online/Multiplayer/MultiplayerClientExtensions.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClientExtensions.cs b/osu.Game/Online/Multiplayer/MultiplayerClientExtensions.cs index 2083aa4e28..d846e7f566 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClientExtensions.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClientExtensions.cs @@ -23,9 +23,12 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(exception != null); - string message = exception.GetHubExceptionMessage() ?? exception.Message; + if (exception.GetHubExceptionMessage() is string message) + // Hub exceptions generally contain something we can show the user directly. + Logger.Log(message, level: LogLevel.Important); + else + Logger.Error(exception, $"Unobserved exception occurred via {nameof(FireAndForget)} call: {exception.Message}"); - Logger.Log(message, level: LogLevel.Important); onError?.Invoke(exception); } else From dcb195f3c813c72d2bd694d1488e0c37c67f1764 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 8 Feb 2024 02:16:08 +0900 Subject: [PATCH 2960/4092] Add delayed resume for taiko/catch/mania --- .../UI/DrawableCatchRuleset.cs | 3 ++ .../UI/DrawableManiaRuleset.cs | 3 ++ .../UI/DrawableTaikoRuleset.cs | 3 ++ osu.Game/Screens/Play/DelayedResumeOverlay.cs | 32 +++++++++++++++++++ 4 files changed, 41 insertions(+) create mode 100644 osu.Game/Screens/Play/DelayedResumeOverlay.cs diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index f0a327d7ac..580c90bcb4 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -16,6 +16,7 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Scoring; +using osu.Game.Screens.Play; namespace osu.Game.Rulesets.Catch.UI { @@ -52,5 +53,7 @@ namespace osu.Game.Rulesets.Catch.UI protected override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo); public override DrawableHitObject? CreateDrawableRepresentation(CatchHitObject h) => null; + + protected override ResumeOverlay CreateResumeOverlay() => new DelayedResumeOverlay(); } } diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index decf670c5d..275b1311de 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -26,6 +26,7 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Scoring; +using osu.Game.Screens.Play; using osu.Game.Skinning; namespace osu.Game.Rulesets.Mania.UI @@ -164,6 +165,8 @@ namespace osu.Game.Rulesets.Mania.UI protected override ReplayRecorder CreateReplayRecorder(Score score) => new ManiaReplayRecorder(score); + protected override ResumeOverlay CreateResumeOverlay() => new DelayedResumeOverlay(); + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 77b2b06c0e..cd9ed399e6 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -22,6 +22,7 @@ using osu.Game.Rulesets.Timing; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Scoring; +using osu.Game.Screens.Play; using osu.Game.Skinning; using osuTK; @@ -101,5 +102,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new TaikoFramedReplayInputHandler(replay); protected override ReplayRecorder CreateReplayRecorder(Score score) => new TaikoReplayRecorder(score); + + protected override ResumeOverlay CreateResumeOverlay() => new DelayedResumeOverlay(); } } diff --git a/osu.Game/Screens/Play/DelayedResumeOverlay.cs b/osu.Game/Screens/Play/DelayedResumeOverlay.cs new file mode 100644 index 0000000000..ef39c8eb76 --- /dev/null +++ b/osu.Game/Screens/Play/DelayedResumeOverlay.cs @@ -0,0 +1,32 @@ +// 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.Localisation; +using osu.Framework.Threading; + +namespace osu.Game.Screens.Play +{ + /// + /// Simple that resumes after 800ms. + /// + public partial class DelayedResumeOverlay : ResumeOverlay + { + protected override LocalisableString Message => string.Empty; + + private ScheduledDelegate? scheduledResume; + + protected override void PopIn() + { + base.PopIn(); + + scheduledResume?.Cancel(); + scheduledResume = Scheduler.AddDelayed(Resume, 800); + } + + protected override void PopOut() + { + base.PopOut(); + scheduledResume?.Cancel(); + } + } +} From 57d5717e6a6d4fb4007e6f42aa7bca525ea176e6 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Wed, 7 Feb 2024 21:33:23 +0100 Subject: [PATCH 2961/4092] Remove `Win32Icon` class and use plain strings instead --- osu.Desktop/Windows/Icons.cs | 9 ++++++++- osu.Desktop/Windows/Win32Icon.cs | 16 ---------------- osu.Desktop/Windows/WindowsAssociationManager.cs | 8 ++++---- 3 files changed, 12 insertions(+), 21 deletions(-) delete mode 100644 osu.Desktop/Windows/Win32Icon.cs diff --git a/osu.Desktop/Windows/Icons.cs b/osu.Desktop/Windows/Icons.cs index cc60f92810..67915c101a 100644 --- a/osu.Desktop/Windows/Icons.cs +++ b/osu.Desktop/Windows/Icons.cs @@ -1,10 +1,17 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.IO; + namespace osu.Desktop.Windows { public static class Icons { - public static Win32Icon Lazer => new Win32Icon(@"lazer.ico"); + /// + /// Fully qualified path to the directory that contains icons (in the installation folder). + /// + private static readonly string icon_directory = Path.GetDirectoryName(typeof(Icons).Assembly.Location)!; + + public static string Lazer => Path.Join(icon_directory, "lazer.ico"); } } diff --git a/osu.Desktop/Windows/Win32Icon.cs b/osu.Desktop/Windows/Win32Icon.cs deleted file mode 100644 index 401e7a2be3..0000000000 --- a/osu.Desktop/Windows/Win32Icon.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. - -namespace osu.Desktop.Windows -{ - public class Win32Icon - { - public readonly string Path; - - internal Win32Icon(string name) - { - string dir = System.IO.Path.GetDirectoryName(typeof(Win32Icon).Assembly.Location)!; - Path = System.IO.Path.Join(dir, name); - } - } -} diff --git a/osu.Desktop/Windows/WindowsAssociationManager.cs b/osu.Desktop/Windows/WindowsAssociationManager.cs index 7131067224..a93161ae47 100644 --- a/osu.Desktop/Windows/WindowsAssociationManager.cs +++ b/osu.Desktop/Windows/WindowsAssociationManager.cs @@ -173,7 +173,7 @@ namespace osu.Desktop.Windows #endregion - private record FileAssociation(string Extension, LocalisableString Description, Win32Icon Icon) + private record FileAssociation(string Extension, LocalisableString Description, string IconPath) { private string getProgramId(string prefix) => $@"{prefix}.File{Extension}"; @@ -188,7 +188,7 @@ namespace osu.Desktop.Windows using (var programKey = classes.CreateSubKey(programId)) { using (var defaultIconKey = programKey.CreateSubKey(DEFAULT_ICON)) - defaultIconKey.SetValue(null, Icon.Path); + defaultIconKey.SetValue(null, IconPath); using (var openCommandKey = programKey.CreateSubKey(SHELL_OPEN_COMMAND)) openCommandKey.SetValue(null, $@"""{exePath}"" ""%1"""); @@ -225,7 +225,7 @@ namespace osu.Desktop.Windows } } - private record UriAssociation(string Protocol, LocalisableString Description, Win32Icon Icon) + private record UriAssociation(string Protocol, LocalisableString Description, string IconPath) { /// /// "The URL Protocol string value indicates that this key declares a custom pluggable protocol handler." @@ -243,7 +243,7 @@ namespace osu.Desktop.Windows protocolKey.SetValue(URL_PROTOCOL, string.Empty); using (var defaultIconKey = protocolKey.CreateSubKey(DEFAULT_ICON)) - defaultIconKey.SetValue(null, Icon.Path); + defaultIconKey.SetValue(null, IconPath); using (var openCommandKey = protocolKey.CreateSubKey(SHELL_OPEN_COMMAND)) openCommandKey.SetValue(null, $@"""{exePath}"" ""%1"""); From eeba93768641b05a470aa1e8ebe18389596f5d49 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Wed, 7 Feb 2024 21:45:36 +0100 Subject: [PATCH 2962/4092] Make `WindowsAssociationManager` `static` Usages/localisation logic TBD --- osu.Desktop/Program.cs | 2 +- .../Windows/WindowsAssociationManager.cs | 57 ++++++------------- 2 files changed, 19 insertions(+), 40 deletions(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index edbf39a30a..38e4110f62 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -181,7 +181,7 @@ namespace osu.Desktop { tools.RemoveShortcutForThisExe(); tools.RemoveUninstallerRegistryEntry(); - WindowsAssociationManager.UninstallAssociations(@"osu"); + WindowsAssociationManager.UninstallAssociations(); }, onEveryRun: (_, _, _) => { // While setting the `ProcessAppUserModelId` fixes duplicate icons/shortcuts on the taskbar, it currently diff --git a/osu.Desktop/Windows/WindowsAssociationManager.cs b/osu.Desktop/Windows/WindowsAssociationManager.cs index a93161ae47..f1fc98090f 100644 --- a/osu.Desktop/Windows/WindowsAssociationManager.cs +++ b/osu.Desktop/Windows/WindowsAssociationManager.cs @@ -6,9 +6,6 @@ using System.IO; using System.Runtime.InteropServices; using System.Runtime.Versioning; using Microsoft.Win32; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; using osu.Framework.Localisation; using osu.Framework.Logging; using osu.Game.Localisation; @@ -16,7 +13,7 @@ using osu.Game.Localisation; namespace osu.Desktop.Windows { [SupportedOSPlatform("windows")] - public partial class WindowsAssociationManager : Component + public static class WindowsAssociationManager { public const string SOFTWARE_CLASSES = @"Software\Classes"; @@ -54,25 +51,7 @@ namespace osu.Desktop.Windows new UriAssociation(@"osump", WindowsAssociationManagerStrings.OsuMultiplayer, Icons.Lazer), }; - [Resolved] - private LocalisationManager localisation { get; set; } = null!; - - private IBindable localisationParameters = null!; - - [BackgroundDependencyLoader] - private void load() - { - localisationParameters = localisation.CurrentParameters.GetBoundCopy(); - InstallAssociations(); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - localisationParameters.ValueChanged += _ => updateDescriptions(); - } - - public void InstallAssociations() + public static void InstallAssociations(LocalisationManager? localisation) { try { @@ -88,7 +67,7 @@ namespace osu.Desktop.Windows association.Install(classes, EXE_PATH); } - updateDescriptions(); + updateDescriptions(localisation); } catch (Exception e) { @@ -96,7 +75,7 @@ namespace osu.Desktop.Windows } } - private void updateDescriptions() + private static void updateDescriptions(LocalisationManager? localisation) { try { @@ -105,18 +84,10 @@ namespace osu.Desktop.Windows return; foreach (var association in file_associations) - { - var b = localisation.GetLocalisedBindableString(association.Description); - association.UpdateDescription(classes, PROGRAM_ID_PREFIX, b.Value); - b.UnbindAll(); - } + association.UpdateDescription(classes, PROGRAM_ID_PREFIX, getLocalisedString(association.Description)); foreach (var association in uri_associations) - { - var b = localisation.GetLocalisedBindableString(association.Description); - association.UpdateDescription(classes, b.Value); - b.UnbindAll(); - } + association.UpdateDescription(classes, getLocalisedString(association.Description)); NotifyShellUpdate(); } @@ -124,11 +95,19 @@ namespace osu.Desktop.Windows { Logger.Log($@"Failed to update file and URI associations: {e.Message}"); } + + string getLocalisedString(LocalisableString s) + { + if (localisation == null) + return s.ToString(); + + var b = localisation.GetLocalisedBindableString(s); + b.UnbindAll(); + return b.Value; + } } - public void UninstallAssociations() => UninstallAssociations(PROGRAM_ID_PREFIX); - - public static void UninstallAssociations(string programIdPrefix) + public static void UninstallAssociations() { try { @@ -137,7 +116,7 @@ namespace osu.Desktop.Windows return; foreach (var association in file_associations) - association.Uninstall(classes, programIdPrefix); + association.Uninstall(classes, PROGRAM_ID_PREFIX); foreach (var association in uri_associations) association.Uninstall(classes); From f9d257b99ea176222671aa4594ec78c01f157db7 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Wed, 7 Feb 2024 21:56:39 +0100 Subject: [PATCH 2963/4092] Install associations as part of initial squirrel install --- osu.Desktop/OsuGameDesktop.cs | 3 --- osu.Desktop/Program.cs | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 2e1b34fb38..a0db896f46 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -137,9 +137,6 @@ namespace osu.Desktop if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) LoadComponentAsync(new GameplayWinKeyBlocker(), Add); - if (OperatingSystem.IsWindows() && IsDeployedBuild) - LoadComponentAsync(new WindowsAssociationManager(), Add); - LoadComponentAsync(new ElevatedPrivilegesChecker(), Add); osuSchemeLinkIPCChannel = new OsuSchemeLinkIPCChannel(Host, this); diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 38e4110f62..65236940c6 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -174,6 +174,7 @@ namespace osu.Desktop { tools.CreateShortcutForThisExe(); tools.CreateUninstallerRegistryEntry(); + WindowsAssociationManager.InstallAssociations(null); }, onAppUpdate: (_, tools) => { tools.CreateUninstallerRegistryEntry(); From 0563507295dcf68c150cfc99949964d521f98b0e Mon Sep 17 00:00:00 2001 From: Susko3 Date: Wed, 7 Feb 2024 22:03:16 +0100 Subject: [PATCH 2964/4092] Remove duplicate try-catch and move `NotifyShellUpdate()` to less hidden place --- .../Windows/WindowsAssociationManager.cs | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/osu.Desktop/Windows/WindowsAssociationManager.cs b/osu.Desktop/Windows/WindowsAssociationManager.cs index f1fc98090f..c91ab459d6 100644 --- a/osu.Desktop/Windows/WindowsAssociationManager.cs +++ b/osu.Desktop/Windows/WindowsAssociationManager.cs @@ -68,6 +68,7 @@ namespace osu.Desktop.Windows } updateDescriptions(localisation); + NotifyShellUpdate(); } catch (Exception e) { @@ -77,24 +78,15 @@ namespace osu.Desktop.Windows private static void updateDescriptions(LocalisationManager? localisation) { - try - { - using var classes = Registry.CurrentUser.OpenSubKey(SOFTWARE_CLASSES, true); - if (classes == null) - return; + using var classes = Registry.CurrentUser.OpenSubKey(SOFTWARE_CLASSES, true); + if (classes == null) + return; - foreach (var association in file_associations) - association.UpdateDescription(classes, PROGRAM_ID_PREFIX, getLocalisedString(association.Description)); + foreach (var association in file_associations) + association.UpdateDescription(classes, PROGRAM_ID_PREFIX, getLocalisedString(association.Description)); - foreach (var association in uri_associations) - association.UpdateDescription(classes, getLocalisedString(association.Description)); - - NotifyShellUpdate(); - } - catch (Exception e) - { - Logger.Log($@"Failed to update file and URI associations: {e.Message}"); - } + foreach (var association in uri_associations) + association.UpdateDescription(classes, getLocalisedString(association.Description)); string getLocalisedString(LocalisableString s) { From 6bdb07602794d7eb59e7356f9fa5a04188652ff2 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Wed, 7 Feb 2024 22:06:09 +0100 Subject: [PATCH 2965/4092] Move update/install logic into helper --- .../Windows/WindowsAssociationManager.cs | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/osu.Desktop/Windows/WindowsAssociationManager.cs b/osu.Desktop/Windows/WindowsAssociationManager.cs index c91ab459d6..3d61ad534b 100644 --- a/osu.Desktop/Windows/WindowsAssociationManager.cs +++ b/osu.Desktop/Windows/WindowsAssociationManager.cs @@ -55,18 +55,7 @@ namespace osu.Desktop.Windows { try { - using (var classes = Registry.CurrentUser.OpenSubKey(SOFTWARE_CLASSES, writable: true)) - { - if (classes == null) - return; - - foreach (var association in file_associations) - association.Install(classes, EXE_PATH, PROGRAM_ID_PREFIX); - - foreach (var association in uri_associations) - association.Install(classes, EXE_PATH); - } - + updateAssociations(); updateDescriptions(localisation); NotifyShellUpdate(); } @@ -76,6 +65,24 @@ namespace osu.Desktop.Windows } } + /// + /// Installs or updates associations. + /// + private static void updateAssociations() + { + using (var classes = Registry.CurrentUser.OpenSubKey(SOFTWARE_CLASSES, writable: true)) + { + if (classes == null) + return; + + foreach (var association in file_associations) + association.Install(classes, EXE_PATH, PROGRAM_ID_PREFIX); + + foreach (var association in uri_associations) + association.Install(classes, EXE_PATH); + } + } + private static void updateDescriptions(LocalisationManager? localisation) { using var classes = Registry.CurrentUser.OpenSubKey(SOFTWARE_CLASSES, true); From 738c28755c53fd587d7ecee0cc6e8365c6aa8a44 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Wed, 7 Feb 2024 22:17:13 +0100 Subject: [PATCH 2966/4092] Refactor public methods --- osu.Desktop/Program.cs | 3 +- .../Windows/WindowsAssociationManager.cs | 42 ++++++++++++++++++- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 65236940c6..494d0df3c6 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -174,10 +174,11 @@ namespace osu.Desktop { tools.CreateShortcutForThisExe(); tools.CreateUninstallerRegistryEntry(); - WindowsAssociationManager.InstallAssociations(null); + WindowsAssociationManager.InstallAssociations(); }, onAppUpdate: (_, tools) => { tools.CreateUninstallerRegistryEntry(); + WindowsAssociationManager.UpdateAssociations(); }, onAppUninstall: (_, tools) => { tools.RemoveShortcutForThisExe(); diff --git a/osu.Desktop/Windows/WindowsAssociationManager.cs b/osu.Desktop/Windows/WindowsAssociationManager.cs index 3d61ad534b..18a3c2da3d 100644 --- a/osu.Desktop/Windows/WindowsAssociationManager.cs +++ b/osu.Desktop/Windows/WindowsAssociationManager.cs @@ -51,12 +51,18 @@ namespace osu.Desktop.Windows new UriAssociation(@"osump", WindowsAssociationManagerStrings.OsuMultiplayer, Icons.Lazer), }; - public static void InstallAssociations(LocalisationManager? localisation) + /// + /// Installs file and URI associations. + /// + /// + /// Call in a timely fashion to keep descriptions up-to-date and localised. + /// + public static void InstallAssociations() { try { updateAssociations(); - updateDescriptions(localisation); + updateDescriptions(null); // write default descriptions in case `UpdateDescriptions()` is not called. NotifyShellUpdate(); } catch (Exception e) @@ -65,6 +71,38 @@ namespace osu.Desktop.Windows } } + /// + /// Updates associations with latest definitions. + /// + /// + /// Call in a timely fashion to keep descriptions up-to-date and localised. + /// + public static void UpdateAssociations() + { + try + { + updateAssociations(); + NotifyShellUpdate(); + } + catch (Exception e) + { + Logger.Log(@$"Failed to update file and URI associations: {e.Message}"); + } + } + + public static void UpdateDescriptions(LocalisationManager localisationManager) + { + try + { + updateDescriptions(localisationManager); + NotifyShellUpdate(); + } + catch (Exception e) + { + Logger.Log(@$"Failed to update file and URI association descriptions: {e.Message}"); + } + } + /// /// Installs or updates associations. /// From ffdefbc742fa1948d1e9356b438adbf319221bd3 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Wed, 7 Feb 2024 22:18:12 +0100 Subject: [PATCH 2967/4092] Move public methods closer together --- .../Windows/WindowsAssociationManager.cs | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/osu.Desktop/Windows/WindowsAssociationManager.cs b/osu.Desktop/Windows/WindowsAssociationManager.cs index 18a3c2da3d..b7465e5ffc 100644 --- a/osu.Desktop/Windows/WindowsAssociationManager.cs +++ b/osu.Desktop/Windows/WindowsAssociationManager.cs @@ -103,6 +103,28 @@ namespace osu.Desktop.Windows } } + public static void UninstallAssociations() + { + try + { + using var classes = Registry.CurrentUser.OpenSubKey(SOFTWARE_CLASSES, true); + if (classes == null) + return; + + foreach (var association in file_associations) + association.Uninstall(classes, PROGRAM_ID_PREFIX); + + foreach (var association in uri_associations) + association.Uninstall(classes); + + NotifyShellUpdate(); + } + catch (Exception e) + { + Logger.Log($@"Failed to uninstall file and URI associations: {e.Message}"); + } + } + /// /// Installs or updates associations. /// @@ -144,28 +166,6 @@ namespace osu.Desktop.Windows } } - public static void UninstallAssociations() - { - try - { - using var classes = Registry.CurrentUser.OpenSubKey(SOFTWARE_CLASSES, true); - if (classes == null) - return; - - foreach (var association in file_associations) - association.Uninstall(classes, PROGRAM_ID_PREFIX); - - foreach (var association in uri_associations) - association.Uninstall(classes); - - NotifyShellUpdate(); - } - catch (Exception e) - { - Logger.Log($@"Failed to uninstall file and URI associations: {e.Message}"); - } - } - internal static void NotifyShellUpdate() => SHChangeNotify(EventId.SHCNE_ASSOCCHANGED, Flags.SHCNF_IDLIST, IntPtr.Zero, IntPtr.Zero); #region Native interop From da8c4541dbfe8c8c0690ca57d91b6d428e94870d Mon Sep 17 00:00:00 2001 From: Susko3 Date: Wed, 7 Feb 2024 22:21:04 +0100 Subject: [PATCH 2968/4092] Use `Logger.Error` --- osu.Desktop/Windows/WindowsAssociationManager.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Desktop/Windows/WindowsAssociationManager.cs b/osu.Desktop/Windows/WindowsAssociationManager.cs index b7465e5ffc..c978e46b5b 100644 --- a/osu.Desktop/Windows/WindowsAssociationManager.cs +++ b/osu.Desktop/Windows/WindowsAssociationManager.cs @@ -67,7 +67,7 @@ namespace osu.Desktop.Windows } catch (Exception e) { - Logger.Log(@$"Failed to install file and URI associations: {e.Message}"); + Logger.Error(e, @$"Failed to install file and URI associations: {e.Message}"); } } @@ -86,7 +86,7 @@ namespace osu.Desktop.Windows } catch (Exception e) { - Logger.Log(@$"Failed to update file and URI associations: {e.Message}"); + Logger.Error(e, @"Failed to update file and URI associations."); } } @@ -99,7 +99,7 @@ namespace osu.Desktop.Windows } catch (Exception e) { - Logger.Log(@$"Failed to update file and URI association descriptions: {e.Message}"); + Logger.Error(e, @"Failed to update file and URI association descriptions."); } } @@ -121,7 +121,7 @@ namespace osu.Desktop.Windows } catch (Exception e) { - Logger.Log($@"Failed to uninstall file and URI associations: {e.Message}"); + Logger.Error(e, @"Failed to uninstall file and URI associations."); } } From 7f5dedc118059189e0d2bcb77e65ed39444de765 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Wed, 7 Feb 2024 22:23:59 +0100 Subject: [PATCH 2969/4092] Refactor ProgID logic so it's more visible --- osu.Desktop/Windows/WindowsAssociationManager.cs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/osu.Desktop/Windows/WindowsAssociationManager.cs b/osu.Desktop/Windows/WindowsAssociationManager.cs index c978e46b5b..aeda1e6283 100644 --- a/osu.Desktop/Windows/WindowsAssociationManager.cs +++ b/osu.Desktop/Windows/WindowsAssociationManager.cs @@ -35,7 +35,7 @@ namespace osu.Desktop.Windows /// Program ID prefix used for file associations. Should be relatively short since the full program ID has a 39 character limit, /// see https://learn.microsoft.com/en-us/windows/win32/com/-progid--key. /// - public const string PROGRAM_ID_PREFIX = "osu"; + public const string PROGRAM_ID_PREFIX = "osu.File"; private static readonly FileAssociation[] file_associations = { @@ -136,7 +136,7 @@ namespace osu.Desktop.Windows return; foreach (var association in file_associations) - association.Install(classes, EXE_PATH, PROGRAM_ID_PREFIX); + association.Install(classes, EXE_PATH); foreach (var association in uri_associations) association.Install(classes, EXE_PATH); @@ -191,15 +191,13 @@ namespace osu.Desktop.Windows private record FileAssociation(string Extension, LocalisableString Description, string IconPath) { - private string getProgramId(string prefix) => $@"{prefix}.File{Extension}"; + private string programId => $@"{PROGRAM_ID_PREFIX}{Extension}"; /// /// Installs a file extenstion association in accordance with https://learn.microsoft.com/en-us/windows/win32/com/-progid--key /// - public void Install(RegistryKey classes, string exePath, string programIdPrefix) + public void Install(RegistryKey classes, string exePath) { - string programId = getProgramId(programIdPrefix); - // register a program id for the given extension using (var programKey = classes.CreateSubKey(programId)) { @@ -224,14 +222,12 @@ namespace osu.Desktop.Windows public void UpdateDescription(RegistryKey classes, string programIdPrefix, string description) { - using (var programKey = classes.OpenSubKey(getProgramId(programIdPrefix), true)) + using (var programKey = classes.OpenSubKey(programId, true)) programKey?.SetValue(null, description); } public void Uninstall(RegistryKey classes, string programIdPrefix) { - string programId = getProgramId(programIdPrefix); - // importantly, we don't delete the default program entry because some other program could have taken it. using (var extensionKey = classes.OpenSubKey($@"{Extension}\OpenWithProgIds", true)) From 3419b8ffa854b4b40daf26fe248e89a4e812e84a Mon Sep 17 00:00:00 2001 From: Susko3 Date: Wed, 7 Feb 2024 22:26:21 +0100 Subject: [PATCH 2970/4092] Standardise using declaration --- osu.Desktop/Windows/WindowsAssociationManager.cs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/osu.Desktop/Windows/WindowsAssociationManager.cs b/osu.Desktop/Windows/WindowsAssociationManager.cs index aeda1e6283..a786afde55 100644 --- a/osu.Desktop/Windows/WindowsAssociationManager.cs +++ b/osu.Desktop/Windows/WindowsAssociationManager.cs @@ -130,17 +130,15 @@ namespace osu.Desktop.Windows /// private static void updateAssociations() { - using (var classes = Registry.CurrentUser.OpenSubKey(SOFTWARE_CLASSES, writable: true)) - { - if (classes == null) - return; + using var classes = Registry.CurrentUser.OpenSubKey(SOFTWARE_CLASSES, true); + if (classes == null) + return; - foreach (var association in file_associations) - association.Install(classes, EXE_PATH); + foreach (var association in file_associations) + association.Install(classes, EXE_PATH); - foreach (var association in uri_associations) - association.Install(classes, EXE_PATH); - } + foreach (var association in uri_associations) + association.Install(classes, EXE_PATH); } private static void updateDescriptions(LocalisationManager? localisation) From 139072fa818a088f300fce9cfd38682c9c57dbcd Mon Sep 17 00:00:00 2001 From: Susko3 Date: Wed, 7 Feb 2024 22:29:20 +0100 Subject: [PATCH 2971/4092] Standardise using declaration --- osu.Desktop/Windows/WindowsAssociationManager.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Desktop/Windows/WindowsAssociationManager.cs b/osu.Desktop/Windows/WindowsAssociationManager.cs index a786afde55..2373cfa609 100644 --- a/osu.Desktop/Windows/WindowsAssociationManager.cs +++ b/osu.Desktop/Windows/WindowsAssociationManager.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using System.Runtime.Versioning; @@ -108,8 +109,7 @@ namespace osu.Desktop.Windows try { using var classes = Registry.CurrentUser.OpenSubKey(SOFTWARE_CLASSES, true); - if (classes == null) - return; + Debug.Assert(classes != null); foreach (var association in file_associations) association.Uninstall(classes, PROGRAM_ID_PREFIX); @@ -131,8 +131,7 @@ namespace osu.Desktop.Windows private static void updateAssociations() { using var classes = Registry.CurrentUser.OpenSubKey(SOFTWARE_CLASSES, true); - if (classes == null) - return; + Debug.Assert(classes != null); foreach (var association in file_associations) association.Install(classes, EXE_PATH); @@ -144,8 +143,7 @@ namespace osu.Desktop.Windows private static void updateDescriptions(LocalisationManager? localisation) { using var classes = Registry.CurrentUser.OpenSubKey(SOFTWARE_CLASSES, true); - if (classes == null) - return; + Debug.Assert(classes != null); foreach (var association in file_associations) association.UpdateDescription(classes, PROGRAM_ID_PREFIX, getLocalisedString(association.Description)); From bf47221594a805530cee18ab5e2ff802bd54f232 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Wed, 7 Feb 2024 22:42:42 +0100 Subject: [PATCH 2972/4092] Make things testable via 'Run static method' in Rider --- osu.Desktop/Windows/WindowsAssociationManager.cs | 2 +- osu.Desktop/osu.Desktop.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Desktop/Windows/WindowsAssociationManager.cs b/osu.Desktop/Windows/WindowsAssociationManager.cs index 2373cfa609..83fadfcae2 100644 --- a/osu.Desktop/Windows/WindowsAssociationManager.cs +++ b/osu.Desktop/Windows/WindowsAssociationManager.cs @@ -30,7 +30,7 @@ namespace osu.Desktop.Windows /// public const string SHELL_OPEN_COMMAND = @"Shell\Open\Command"; - public static readonly string EXE_PATH = Path.ChangeExtension(typeof(WindowsAssociationManager).Assembly.Location, ".exe"); + public static readonly string EXE_PATH = Path.ChangeExtension(typeof(WindowsAssociationManager).Assembly.Location, ".exe").Replace('/', '\\'); /// /// Program ID prefix used for file associations. Should be relatively short since the full program ID has a 39 character limit, diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index c6a95c1623..57752aa1f7 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -31,7 +31,7 @@ - + From 8049270ad2e17447a5f80446a7e39e73dc5e52e2 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Wed, 7 Feb 2024 22:45:58 +0100 Subject: [PATCH 2973/4092] Remove unused param --- osu.Desktop/Windows/WindowsAssociationManager.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Desktop/Windows/WindowsAssociationManager.cs b/osu.Desktop/Windows/WindowsAssociationManager.cs index 83fadfcae2..83c2a97b56 100644 --- a/osu.Desktop/Windows/WindowsAssociationManager.cs +++ b/osu.Desktop/Windows/WindowsAssociationManager.cs @@ -112,7 +112,7 @@ namespace osu.Desktop.Windows Debug.Assert(classes != null); foreach (var association in file_associations) - association.Uninstall(classes, PROGRAM_ID_PREFIX); + association.Uninstall(classes); foreach (var association in uri_associations) association.Uninstall(classes); @@ -146,7 +146,7 @@ namespace osu.Desktop.Windows Debug.Assert(classes != null); foreach (var association in file_associations) - association.UpdateDescription(classes, PROGRAM_ID_PREFIX, getLocalisedString(association.Description)); + association.UpdateDescription(classes, getLocalisedString(association.Description)); foreach (var association in uri_associations) association.UpdateDescription(classes, getLocalisedString(association.Description)); @@ -216,13 +216,13 @@ namespace osu.Desktop.Windows } } - public void UpdateDescription(RegistryKey classes, string programIdPrefix, string description) + public void UpdateDescription(RegistryKey classes, string description) { using (var programKey = classes.OpenSubKey(programId, true)) programKey?.SetValue(null, description); } - public void Uninstall(RegistryKey classes, string programIdPrefix) + public void Uninstall(RegistryKey classes) { // importantly, we don't delete the default program entry because some other program could have taken it. From dfa0c51bc8b1067a08811f221da52109a6806c94 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Thu, 8 Feb 2024 00:23:46 +0100 Subject: [PATCH 2974/4092] Copy icons to nuget and install package Don't ask me why this uses the folder for .NET Framework 4.5 --- osu.Desktop/osu.nuspec | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Desktop/osu.nuspec b/osu.Desktop/osu.nuspec index f85698680e..66b3970351 100644 --- a/osu.Desktop/osu.nuspec +++ b/osu.Desktop/osu.nuspec @@ -20,6 +20,7 @@ + From 1dc54d6f25d030db88936ecbfec7dd8f55c71d3e Mon Sep 17 00:00:00 2001 From: Susko3 Date: Thu, 8 Feb 2024 00:54:48 +0100 Subject: [PATCH 2975/4092] Fix stable install path lookup `osu` is the `osu://` protocol handler, which gets overriden by lazer. Instead, use `osu!` which is the stable file handler. --- osu.Desktop/OsuGameDesktop.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index a0db896f46..5ac6ac7322 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -86,8 +86,8 @@ namespace osu.Desktop [SupportedOSPlatform("windows")] private string? getStableInstallPathFromRegistry() { - using (RegistryKey? key = Registry.ClassesRoot.OpenSubKey("osu")) - return key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty)?.ToString()?.Split('"')[1].Replace("osu!.exe", ""); + using (RegistryKey? key = Registry.ClassesRoot.OpenSubKey("osu!")) + return key?.OpenSubKey(WindowsAssociationManager.SHELL_OPEN_COMMAND)?.GetValue(string.Empty)?.ToString()?.Split('"')[1].Replace("osu!.exe", ""); } protected override UpdateManager CreateUpdateManager() From 6ded79cf0728010aedfb367cf48f3fd3f84abe6c Mon Sep 17 00:00:00 2001 From: Susko3 Date: Thu, 8 Feb 2024 01:15:37 +0100 Subject: [PATCH 2976/4092] Make `NotifyShellUpdate()` `public` to ease testing --- osu.Desktop/Windows/WindowsAssociationManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Desktop/Windows/WindowsAssociationManager.cs b/osu.Desktop/Windows/WindowsAssociationManager.cs index 83c2a97b56..4bb8e57c9d 100644 --- a/osu.Desktop/Windows/WindowsAssociationManager.cs +++ b/osu.Desktop/Windows/WindowsAssociationManager.cs @@ -125,6 +125,8 @@ namespace osu.Desktop.Windows } } + public static void NotifyShellUpdate() => SHChangeNotify(EventId.SHCNE_ASSOCCHANGED, Flags.SHCNF_IDLIST, IntPtr.Zero, IntPtr.Zero); + /// /// Installs or updates associations. /// @@ -162,8 +164,6 @@ namespace osu.Desktop.Windows } } - internal static void NotifyShellUpdate() => SHChangeNotify(EventId.SHCNE_ASSOCCHANGED, Flags.SHCNF_IDLIST, IntPtr.Zero, IntPtr.Zero); - #region Native interop [DllImport("Shell32.dll")] From 6adf0ac01ebbdd82c19617fe5e96f291c58365c7 Mon Sep 17 00:00:00 2001 From: Berkan Diler Date: Thu, 8 Feb 2024 18:01:00 +0100 Subject: [PATCH 2977/4092] Use new LINQ Order() instead of OrderBy() when possible --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs | 2 +- osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 4 ++-- osu.Game.Rulesets.Taiko/Difficulty/Skills/Peaks.cs | 2 +- osu.Game.Tournament/IO/TournamentStorage.cs | 2 +- osu.Game/Beatmaps/BeatmapImporter.cs | 4 ++-- osu.Game/Collections/CollectionDropdown.cs | 2 +- osu.Game/Database/RealmArchiveModelImporter.cs | 4 ++-- osu.Game/Overlays/Music/PlaylistOverlay.cs | 2 +- .../Overlays/Settings/Sections/Graphics/RendererSettings.cs | 2 +- osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs | 2 +- osu.Game/Rulesets/Edit/Checks/CheckBreaks.cs | 4 ++-- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 2 +- osu.Game/Rulesets/RealmRulesetStore.cs | 2 +- .../Edit/Compose/Components/BeatDivisorPresetCollection.cs | 2 +- .../Edit/Compose/Components/Timeline/DifficultyPointPiece.cs | 2 +- 16 files changed, 20 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index c38d6519bd..75a642924c 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -375,7 +375,7 @@ namespace osu.Game.Rulesets.Mania /// The that corresponds to . private PlayfieldType getPlayfieldType(int variant) { - return (PlayfieldType)Enum.GetValues(typeof(PlayfieldType)).Cast().OrderByDescending(i => i).First(v => variant >= v); + return (PlayfieldType)Enum.GetValues(typeof(PlayfieldType)).Cast().OrderDescending().First(v => variant >= v); } protected override IEnumerable GetValidHitResults() diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index 1947d86a97..0444394d87 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mania.Scoring } protected override IEnumerable EnumerateHitObjects(IBeatmap beatmap) - => base.EnumerateHitObjects(beatmap).OrderBy(ho => ho, JudgementOrderComparer.DEFAULT); + => base.EnumerateHitObjects(beatmap).Order(JudgementOrderComparer.DEFAULT); protected override double ComputeTotalScore(double comboProgress, double accuracyProgress, double bonusPortion) { diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index 15b20a5572..4a6328010b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills // These sections will not contribute to the difficulty. var peaks = GetCurrentStrainPeaks().Where(p => p > 0); - List strains = peaks.OrderByDescending(d => d).ToList(); + List strains = peaks.OrderDescending().ToList(); // We are reducing the highest strains first to account for extreme difficulty spikes for (int i = 0; i < Math.Min(strains.Count, ReducedSectionCount); i++) @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills // 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.OrderByDescending(d => d)) + foreach (double strain in strains.OrderDescending()) { difficulty += strain * weight; weight *= DecayWeight; diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Peaks.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Peaks.cs index ec8e754c5c..91d8e93543 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Peaks.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Peaks.cs @@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills double difficulty = 0; double weight = 1; - foreach (double strain in peaks.OrderByDescending(d => d)) + foreach (double strain in peaks.OrderDescending()) { difficulty += strain * weight; weight *= 0.9; diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 7c5f3e44a7..48cd45fdd4 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -45,6 +45,6 @@ namespace osu.Game.Tournament.IO Logger.Log("Changing tournament storage: " + GetFullPath(string.Empty)); } - public IEnumerable ListTournaments() => AllTournaments.GetDirectories(string.Empty).OrderBy(directory => directory, StringComparer.CurrentCultureIgnoreCase); + public IEnumerable ListTournaments() => AllTournaments.GetDirectories(string.Empty).Order(StringComparer.CurrentCultureIgnoreCase); } } diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 7bb52eef52..5ff3ab64b2 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -266,8 +266,8 @@ namespace osu.Game.Beatmaps if (!base.CanReuseExisting(existing, import)) return false; - var existingIds = existing.Beatmaps.Select(b => b.OnlineID).OrderBy(i => i); - var importIds = import.Beatmaps.Select(b => b.OnlineID).OrderBy(i => i); + var existingIds = existing.Beatmaps.Select(b => b.OnlineID).Order(); + var importIds = import.Beatmaps.Select(b => b.OnlineID).Order(); // force re-import if we are not in a sane state. return existing.OnlineID == import.OnlineID && existingIds.SequenceEqual(importIds); diff --git a/osu.Game/Collections/CollectionDropdown.cs b/osu.Game/Collections/CollectionDropdown.cs index 249a0c35e7..15dd644073 100644 --- a/osu.Game/Collections/CollectionDropdown.cs +++ b/osu.Game/Collections/CollectionDropdown.cs @@ -74,7 +74,7 @@ namespace osu.Game.Collections } else { - foreach (int i in changes.DeletedIndices.OrderByDescending(i => i)) + foreach (int i in changes.DeletedIndices.OrderDescending()) filters.RemoveAt(i + 1); foreach (int i in changes.InsertedIndices) diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index 5383040eb4..bc4954c6ea 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -279,7 +279,7 @@ namespace osu.Game.Database // note that this should really be checking filesizes on disk (of existing files) for some degree of sanity. // or alternatively doing a faster hash check. either of these require database changes and reprocessing of existing files. if (CanSkipImport(existing, item) && - getFilenames(existing.Files).SequenceEqual(getShortenedFilenames(archive).Select(p => p.shortened).OrderBy(f => f)) && + getFilenames(existing.Files).SequenceEqual(getShortenedFilenames(archive).Select(p => p.shortened).Order()) && checkAllFilesExist(existing)) { LogForModel(item, @$"Found existing (optimised) {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); @@ -437,7 +437,7 @@ namespace osu.Game.Database { MemoryStream hashable = new MemoryStream(); - foreach (string? file in reader.Filenames.Where(f => HashableFileTypes.Any(ext => f.EndsWith(ext, StringComparison.OrdinalIgnoreCase))).OrderBy(f => f)) + foreach (string? file in reader.Filenames.Where(f => HashableFileTypes.Any(ext => f.EndsWith(ext, StringComparison.OrdinalIgnoreCase))).Order()) { using (Stream s = reader.GetStream(file)) s.CopyTo(hashable); diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 7784643163..6ecd0f51d3 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -122,7 +122,7 @@ namespace osu.Game.Overlays.Music foreach (int i in changes.InsertedIndices) beatmapSets.Insert(i, sender[i].ToLive(realm)); - foreach (int i in changes.DeletedIndices.OrderByDescending(i => i)) + foreach (int i in changes.DeletedIndices.OrderDescending()) beatmapSets.RemoveAt(i); } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs index d4cef3f4d1..fc5dd34971 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs @@ -36,7 +36,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { LabelText = GraphicsSettingsStrings.Renderer, Current = renderer, - Items = host.GetPreferredRenderersForCurrentPlatform().OrderBy(t => t).Where(t => t != RendererType.Vulkan), + Items = host.GetPreferredRenderersForCurrentPlatform().Order().Where(t => t != RendererType.Vulkan), Keywords = new[] { @"compatibility", @"directx" }, }, // TODO: this needs to be a custom dropdown at some point diff --git a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs index b43a272324..b07e8399c0 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs @@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Difficulty.Skills // Difficulty is the weighted sum of the highest strains from every section. // We're sorting from highest to lowest strain. - foreach (double strain in peaks.OrderByDescending(d => d)) + foreach (double strain in peaks.OrderDescending()) { difficulty += strain * weight; weight *= DecayWeight; diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBreaks.cs b/osu.Game/Rulesets/Edit/Checks/CheckBreaks.cs index 94369443c2..0842ff5453 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBreaks.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBreaks.cs @@ -31,8 +31,8 @@ namespace osu.Game.Rulesets.Edit.Checks public IEnumerable Run(BeatmapVerifierContext context) { - var startTimes = context.Beatmap.HitObjects.Select(ho => ho.StartTime).OrderBy(x => x).ToList(); - var endTimes = context.Beatmap.HitObjects.Select(ho => ho.GetEndTime()).OrderBy(x => x).ToList(); + var startTimes = context.Beatmap.HitObjects.Select(ho => ho.StartTime).Order().ToList(); + var endTimes = context.Beatmap.HitObjects.Select(ho => ho.GetEndTime()).Order().ToList(); foreach (var breakPeriod in context.Beatmap.Breaks) { diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index 77aa5cdc15..19554b6504 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -188,7 +188,7 @@ namespace osu.Game.Rulesets.Mods public void ApplyToBeatmap(IBeatmap beatmap) { var hitObjects = getAllApplicableHitObjects(beatmap.HitObjects).ToList(); - var endTimes = hitObjects.Select(x => x.GetEndTime()).OrderBy(x => x).Distinct().ToList(); + var endTimes = hitObjects.Select(x => x.GetEndTime()).Order().Distinct().ToList(); foreach (HitObject hitObject in hitObjects) { diff --git a/osu.Game/Rulesets/RealmRulesetStore.cs b/osu.Game/Rulesets/RealmRulesetStore.cs index 456f6e399b..ba6f4583d1 100644 --- a/osu.Game/Rulesets/RealmRulesetStore.cs +++ b/osu.Game/Rulesets/RealmRulesetStore.cs @@ -107,7 +107,7 @@ namespace osu.Game.Rulesets } } - availableRulesets.AddRange(detachedRulesets.OrderBy(r => r)); + availableRulesets.AddRange(detachedRulesets.Order()); }); } diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorPresetCollection.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorPresetCollection.cs index 56df0552cc..43ab47d2d7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorPresetCollection.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorPresetCollection.cs @@ -35,7 +35,7 @@ namespace osu.Game.Screens.Edit.Compose.Components presets.Add(maxDivisor / candidate); } - return new BeatDivisorPresetCollection(BeatDivisorType.Custom, presets.Distinct().OrderBy(d => d)); + return new BeatDivisorPresetCollection(BeatDivisorType.Custom, presets.Distinct().Order()); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs index ca1e9a5d9b..fc240c570b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs @@ -169,7 +169,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline InspectorText.Clear(); - double[] sliderVelocities = EditorBeatmap.HitObjects.OfType().Select(sv => sv.SliderVelocityMultiplier).OrderBy(v => v).ToArray(); + double[] sliderVelocities = EditorBeatmap.HitObjects.OfType().Select(sv => sv.SliderVelocityMultiplier).Order().ToArray(); AddHeader("Base velocity (from beatmap setup)"); AddValue($"{beatmapVelocity:#,0.00}x"); From c500264306adceec5edbbab0baa40a7bc13c65c4 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 9 Feb 2024 23:20:31 +0300 Subject: [PATCH 2978/4092] Cache created judgement in HitObject --- .../TestSceneCatchSkinConfiguration.cs | 2 +- osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs | 2 +- osu.Game.Rulesets.Catch/Objects/Banana.cs | 2 +- osu.Game.Rulesets.Catch/Objects/BananaShower.cs | 2 +- osu.Game.Rulesets.Catch/Objects/Droplet.cs | 2 +- osu.Game.Rulesets.Catch/Objects/Fruit.cs | 2 +- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 2 +- osu.Game.Rulesets.Catch/Objects/TinyDroplet.cs | 2 +- osu.Game.Rulesets.Mania/Objects/BarLine.cs | 2 +- osu.Game.Rulesets.Mania/Objects/HoldNote.cs | 2 +- osu.Game.Rulesets.Mania/Objects/HoldNoteBody.cs | 2 +- osu.Game.Rulesets.Mania/Objects/Note.cs | 2 +- osu.Game.Rulesets.Mania/Objects/TailNote.cs | 2 +- .../TestSceneSpinnerRotation.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs | 2 +- osu.Game.Rulesets.Osu/Objects/HitCircle.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 +- osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs | 2 +- osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs | 2 +- osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs | 2 +- osu.Game.Rulesets.Osu/Objects/SliderTick.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Spinner.cs | 2 +- osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs | 2 +- osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs | 2 +- .../TaikoHealthProcessorTest.cs | 8 ++++---- osu.Game.Rulesets.Taiko/Objects/BarLine.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 4 ++-- osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/IgnoreHit.cs | 2 +- .../Objects/StrongNestedHitObject.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/Swell.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/SwellTick.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs | 2 +- osu.Game/Beatmaps/IBeatmap.cs | 2 +- osu.Game/Database/StandardisedScoreMigrationTools.cs | 2 +- .../Difficulty/PerformanceBreakdownCalculator.cs | 4 ++-- .../Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 +- osu.Game/Rulesets/Objects/HitObject.cs | 10 +++++++++- osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs | 2 +- osu.Game/Rulesets/Scoring/JudgementProcessor.cs | 2 +- .../Rulesets/Scoring/LegacyDrainingHealthProcessor.cs | 2 +- 41 files changed, 54 insertions(+), 46 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs index e2fc31d869..0d7aa6af10 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs @@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.Catch.Tests { fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); var drawableFruit = new DrawableFruit(fruit) { X = x }; - var judgement = fruit.CreateJudgement(); + var judgement = fruit.Judgement; catcher.OnNewResult(drawableFruit, new CatchJudgementResult(fruit, judgement) { Type = judgement.MaxResult diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index f60ae29f77..b03fa00f76 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -293,7 +293,7 @@ namespace osu.Game.Rulesets.Catch.Tests private JudgementResult createResult(CatchHitObject hitObject) { - return new CatchJudgementResult(hitObject, hitObject.CreateJudgement()) + return new CatchJudgementResult(hitObject, hitObject.Judgement) { Type = catcher.CanCatch(hitObject) ? HitResult.Great : HitResult.Miss }; diff --git a/osu.Game.Rulesets.Catch/Objects/Banana.cs b/osu.Game.Rulesets.Catch/Objects/Banana.cs index b80527f379..30bdb24b14 100644 --- a/osu.Game.Rulesets.Catch/Objects/Banana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Banana.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Catch.Objects /// public int BananaIndex; - public override Judgement CreateJudgement() => new CatchBananaJudgement(); + protected override Judgement CreateJudgement() => new CatchBananaJudgement(); private static readonly IList default_banana_samples = new List { new BananaHitSampleInfo() }.AsReadOnly(); diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs index 328cc2b52a..86c41fce90 100644 --- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Catch.Objects { public override bool LastInCombo => true; - public override Judgement CreateJudgement() => new IgnoreJudgement(); + protected override Judgement CreateJudgement() => new IgnoreJudgement(); protected override void CreateNestedHitObjects(CancellationToken cancellationToken) { diff --git a/osu.Game.Rulesets.Catch/Objects/Droplet.cs b/osu.Game.Rulesets.Catch/Objects/Droplet.cs index 9c1004a04b..107c6c3979 100644 --- a/osu.Game.Rulesets.Catch/Objects/Droplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Droplet.cs @@ -8,6 +8,6 @@ namespace osu.Game.Rulesets.Catch.Objects { public class Droplet : PalpableCatchHitObject { - public override Judgement CreateJudgement() => new CatchDropletJudgement(); + protected override Judgement CreateJudgement() => new CatchDropletJudgement(); } } diff --git a/osu.Game.Rulesets.Catch/Objects/Fruit.cs b/osu.Game.Rulesets.Catch/Objects/Fruit.cs index 4818fe2cad..17270b803c 100644 --- a/osu.Game.Rulesets.Catch/Objects/Fruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Fruit.cs @@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Catch.Objects { public class Fruit : PalpableCatchHitObject { - public override Judgement CreateJudgement() => new CatchJudgement(); + protected override Judgement CreateJudgement() => new CatchJudgement(); public static FruitVisualRepresentation GetVisualRepresentation(int indexInBeatmap) => (FruitVisualRepresentation)(indexInBeatmap % 4); } diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 671291ef0e..49c24df5b9 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Catch.Objects /// private const float base_scoring_distance = 100; - public override Judgement CreateJudgement() => new IgnoreJudgement(); + protected override Judgement CreateJudgement() => new IgnoreJudgement(); public int RepeatCount { get; set; } diff --git a/osu.Game.Rulesets.Catch/Objects/TinyDroplet.cs b/osu.Game.Rulesets.Catch/Objects/TinyDroplet.cs index 1bf160b5a6..ddcb92875f 100644 --- a/osu.Game.Rulesets.Catch/Objects/TinyDroplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/TinyDroplet.cs @@ -8,6 +8,6 @@ namespace osu.Game.Rulesets.Catch.Objects { public class TinyDroplet : Droplet { - public override Judgement CreateJudgement() => new CatchTinyDropletJudgement(); + protected override Judgement CreateJudgement() => new CatchTinyDropletJudgement(); } } diff --git a/osu.Game.Rulesets.Mania/Objects/BarLine.cs b/osu.Game.Rulesets.Mania/Objects/BarLine.cs index cf576239ed..742b5e4b0d 100644 --- a/osu.Game.Rulesets.Mania/Objects/BarLine.cs +++ b/osu.Game.Rulesets.Mania/Objects/BarLine.cs @@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Mania.Objects set => major.Value = value; } - public override Judgement CreateJudgement() => new IgnoreJudgement(); + protected override Judgement CreateJudgement() => new IgnoreJudgement(); } } diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index 3f930a310b..4aac455bc5 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Mania.Objects }); } - public override Judgement CreateJudgement() => new IgnoreJudgement(); + protected override Judgement CreateJudgement() => new IgnoreJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNoteBody.cs b/osu.Game.Rulesets.Mania/Objects/HoldNoteBody.cs index 47163d0d81..92b649c174 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNoteBody.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNoteBody.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania.Objects /// public class HoldNoteBody : ManiaHitObject { - public override Judgement CreateJudgement() => new HoldNoteBodyJudgement(); + protected override Judgement CreateJudgement() => new HoldNoteBodyJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } diff --git a/osu.Game.Rulesets.Mania/Objects/Note.cs b/osu.Game.Rulesets.Mania/Objects/Note.cs index 0035960c63..b0f2991918 100644 --- a/osu.Game.Rulesets.Mania/Objects/Note.cs +++ b/osu.Game.Rulesets.Mania/Objects/Note.cs @@ -11,6 +11,6 @@ namespace osu.Game.Rulesets.Mania.Objects /// public class Note : ManiaHitObject { - public override Judgement CreateJudgement() => new ManiaJudgement(); + protected override Judgement CreateJudgement() => new ManiaJudgement(); } } diff --git a/osu.Game.Rulesets.Mania/Objects/TailNote.cs b/osu.Game.Rulesets.Mania/Objects/TailNote.cs index def32880f1..bddb4630cb 100644 --- a/osu.Game.Rulesets.Mania/Objects/TailNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/TailNote.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Objects /// public const double RELEASE_WINDOW_LENIENCE = 1.5; - public override Judgement CreateJudgement() => new ManiaJudgement(); + protected override Judgement CreateJudgement() => new ManiaJudgement(); public override double MaximumJudgementOffset => base.MaximumJudgementOffset * RELEASE_WINDOW_LENIENCE; } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index 6706d20080..8d81fe3017 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Osu.Tests // multipled by 2 to nullify the score multiplier. (autoplay mod selected) long totalScore = scoreProcessor.TotalScore.Value * 2; - return totalScore == (int)(drawableSpinner.Result.TotalRotation / 360) * scoreProcessor.GetBaseScoreForResult(new SpinnerTick().CreateJudgement().MaxResult); + return totalScore == (int)(drawableSpinner.Result.TotalRotation / 360) * scoreProcessor.GetBaseScoreForResult(new SpinnerTick().Judgement.MaxResult); }); addSeekStep(0); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs index 2c9292c58b..f07a1e930b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs @@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Osu.Mods { } - public override Judgement CreateJudgement() => new OsuJudgement(); + protected override Judgement CreateJudgement() => new OsuJudgement(); } private partial class StrictTrackingDrawableSliderTail : DrawableSliderTail diff --git a/osu.Game.Rulesets.Osu/Objects/HitCircle.cs b/osu.Game.Rulesets.Osu/Objects/HitCircle.cs index d652db0fd4..6336482ccc 100644 --- a/osu.Game.Rulesets.Osu/Objects/HitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/HitCircle.cs @@ -8,6 +8,6 @@ namespace osu.Game.Rulesets.Osu.Objects { public class HitCircle : OsuHitObject { - public override Judgement CreateJudgement() => new OsuJudgement(); + protected override Judgement CreateJudgement() => new OsuJudgement(); } } diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 506145568e..fc0248cbbd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -275,7 +275,7 @@ namespace osu.Game.Rulesets.Osu.Objects TailSamples = this.GetNodeSamples(repeatCount + 1); } - public override Judgement CreateJudgement() => ClassicSliderBehaviour + protected override Judgement CreateJudgement() => ClassicSliderBehaviour // Final combo is provided by the slider itself - see logic in `DrawableSlider.CheckForResult()` ? new OsuJudgement() // Final combo is provided by the tail circle - see `SliderTailCircle` diff --git a/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs index 2d5a5b7727..8d60864f0b 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Objects protected override HitWindows CreateHitWindows() => HitWindows.Empty; - public override Judgement CreateJudgement() => new SliderEndJudgement(); + protected override Judgement CreateJudgement() => new SliderEndJudgement(); public class SliderEndJudgement : OsuJudgement { diff --git a/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs index 8305481788..4760135081 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs @@ -14,6 +14,6 @@ namespace osu.Game.Rulesets.Osu.Objects /// public bool ClassicSliderBehaviour; - public override Judgement CreateJudgement() => ClassicSliderBehaviour ? new SliderTickJudgement() : base.CreateJudgement(); + protected override Judgement CreateJudgement() => ClassicSliderBehaviour ? new SliderTickJudgement() : base.CreateJudgement(); } } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index ee2490439f..42d8d895e4 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Objects { } - public override Judgement CreateJudgement() => ClassicSliderBehaviour ? new LegacyTailJudgement() : new TailJudgement(); + protected override Judgement CreateJudgement() => ClassicSliderBehaviour ? new LegacyTailJudgement() : new TailJudgement(); public class LegacyTailJudgement : OsuJudgement { diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs index 74ec4d6eb3..1d7ba2fbaf 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs @@ -32,6 +32,6 @@ namespace osu.Game.Rulesets.Osu.Objects protected override HitWindows CreateHitWindows() => HitWindows.Empty; - public override Judgement CreateJudgement() => new SliderTickJudgement(); + protected override Judgement CreateJudgement() => new SliderTickJudgement(); } } diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index e3dfe8e69a..9baa645b3c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -95,7 +95,7 @@ namespace osu.Game.Rulesets.Osu.Objects } } - public override Judgement CreateJudgement() => new OsuJudgement(); + protected override Judgement CreateJudgement() => new OsuJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs index 8d53100529..57db29ef0c 100644 --- a/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs @@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Osu.Objects { public class SpinnerBonusTick : SpinnerTick { - public override Judgement CreateJudgement() => new OsuSpinnerBonusTickJudgement(); + protected override Judgement CreateJudgement() => new OsuSpinnerBonusTickJudgement(); public class OsuSpinnerBonusTickJudgement : OsuSpinnerTickJudgement { diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs index 7989c9b7ff..cb59014909 100644 --- a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Objects /// public double SpinnerDuration { get; set; } - public override Judgement CreateJudgement() => new OsuSpinnerTickJudgement(); + protected override Judgement CreateJudgement() => new OsuSpinnerTickJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoHealthProcessorTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoHealthProcessorTest.cs index f4a1e888c9..b28e870481 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoHealthProcessorTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoHealthProcessorTest.cs @@ -126,11 +126,11 @@ namespace osu.Game.Rulesets.Taiko.Tests foreach (var nested in beatmap.HitObjects[0].NestedHitObjects) { - var nestedJudgement = nested.CreateJudgement(); + var nestedJudgement = nested.Judgement; healthProcessor.ApplyResult(new JudgementResult(nested, nestedJudgement) { Type = nestedJudgement.MaxResult }); } - var judgement = beatmap.HitObjects[0].CreateJudgement(); + var judgement = beatmap.HitObjects[0].Judgement; healthProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], judgement) { Type = judgement.MaxResult }); Assert.Multiple(() => @@ -159,11 +159,11 @@ namespace osu.Game.Rulesets.Taiko.Tests foreach (var nested in beatmap.HitObjects[0].NestedHitObjects) { - var nestedJudgement = nested.CreateJudgement(); + var nestedJudgement = nested.Judgement; healthProcessor.ApplyResult(new JudgementResult(nested, nestedJudgement) { Type = nestedJudgement.MaxResult }); } - var judgement = beatmap.HitObjects[0].CreateJudgement(); + var judgement = beatmap.HitObjects[0].Judgement; healthProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], judgement) { Type = judgement.MaxResult }); Assert.Multiple(() => diff --git a/osu.Game.Rulesets.Taiko/Objects/BarLine.cs b/osu.Game.Rulesets.Taiko/Objects/BarLine.cs index 46b3f13501..d87f8b3232 100644 --- a/osu.Game.Rulesets.Taiko/Objects/BarLine.cs +++ b/osu.Game.Rulesets.Taiko/Objects/BarLine.cs @@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Taiko.Objects set => major.Value = value; } - public override Judgement CreateJudgement() => new IgnoreJudgement(); + protected override Judgement CreateJudgement() => new IgnoreJudgement(); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index f3143de345..50cd722a3f 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Taiko.Objects } } - public override Judgement CreateJudgement() => new IgnoreJudgement(); + protected override Judgement CreateJudgement() => new IgnoreJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Taiko.Objects public class StrongNestedHit : StrongNestedHitObject { // The strong hit of the drum roll doesn't actually provide any score. - public override Judgement CreateJudgement() => new IgnoreJudgement(); + protected override Judgement CreateJudgement() => new IgnoreJudgement(); public StrongNestedHit(TaikoHitObject parent) : base(parent) diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs index dc082ffd21..c1d4102042 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Taiko.Objects Parent = parent; } - public override Judgement CreateJudgement() => new TaikoDrumRollTickJudgement(); + protected override Judgement CreateJudgement() => new TaikoDrumRollTickJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; diff --git a/osu.Game.Rulesets.Taiko/Objects/IgnoreHit.cs b/osu.Game.Rulesets.Taiko/Objects/IgnoreHit.cs index 302f940ef4..44cd700faf 100644 --- a/osu.Game.Rulesets.Taiko/Objects/IgnoreHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/IgnoreHit.cs @@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Taiko.Objects { public class IgnoreHit : Hit { - public override Judgement CreateJudgement() => new IgnoreJudgement(); + protected override Judgement CreateJudgement() => new IgnoreJudgement(); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/StrongNestedHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/StrongNestedHitObject.cs index 14cbe338ed..227ab4ab52 100644 --- a/osu.Game.Rulesets.Taiko/Objects/StrongNestedHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/StrongNestedHitObject.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Taiko.Objects Parent = parent; } - public override Judgement CreateJudgement() => new TaikoStrongJudgement(); + protected override Judgement CreateJudgement() => new TaikoStrongJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; } diff --git a/osu.Game.Rulesets.Taiko/Objects/Swell.cs b/osu.Game.Rulesets.Taiko/Objects/Swell.cs index a8db8df021..76d106f924 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Swell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Swell.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Taiko.Objects } } - public override Judgement CreateJudgement() => new TaikoSwellJudgement(); + protected override Judgement CreateJudgement() => new TaikoSwellJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; } diff --git a/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs index 41fb9cac7e..be1c1101de 100644 --- a/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs @@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Taiko.Objects { public class SwellTick : TaikoHitObject { - public override Judgement CreateJudgement() => new IgnoreJudgement(); + protected override Judgement CreateJudgement() => new IgnoreJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; } diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs index 1a1fde1990..697c23addf 100644 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Taiko.Objects /// public const float DEFAULT_SIZE = 0.475f; - public override Judgement CreateJudgement() => new TaikoJudgement(); + protected override Judgement CreateJudgement() => new TaikoJudgement(); protected override HitWindows CreateHitWindows() => new TaikoHitWindows(); } diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index d97eb00d7e..b5bb6ccafc 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -94,7 +94,7 @@ namespace osu.Game.Beatmaps static void addCombo(HitObject hitObject, ref int combo) { - if (hitObject.CreateJudgement().MaxResult.AffectsCombo()) + if (hitObject.Judgement.MaxResult.AffectsCombo()) combo++; foreach (var nested in hitObject.NestedHitObjects) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index 403e73ab77..576d08f491 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -655,7 +655,7 @@ namespace osu.Game.Database { private readonly Judgement judgement; - public override Judgement CreateJudgement() => judgement; + protected override Judgement CreateJudgement() => judgement; public FakeHit(Judgement judgement) { diff --git a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs index 4563c264f7..946d83b14b 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs @@ -113,9 +113,9 @@ namespace osu.Game.Rulesets.Difficulty private IEnumerable getPerfectHitResults(HitObject hitObject) { foreach (HitObject nested in hitObject.NestedHitObjects) - yield return nested.CreateJudgement().MaxResult; + yield return nested.Judgement.MaxResult; - yield return hitObject.CreateJudgement().MaxResult; + yield return hitObject.Judgement.MaxResult; } } } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index c9192ae3eb..16bd4b565c 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -770,7 +770,7 @@ namespace osu.Game.Rulesets.Objects.Drawables private void ensureEntryHasResult() { Debug.Assert(Entry != null); - Entry.Result ??= CreateResult(HitObject.CreateJudgement()) + Entry.Result ??= CreateResult(HitObject.Judgement) ?? throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); } diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index ef8bd08bf4..aed821332d 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -162,11 +162,19 @@ namespace osu.Game.Rulesets.Objects protected void AddNested(HitObject hitObject) => nestedHitObjects.Add(hitObject); + /// + /// The that represents the scoring information for this . + /// + [JsonIgnore] + public Judgement Judgement => judgement ??= CreateJudgement(); + + private Judgement judgement; + /// /// Creates the that represents the scoring information for this . /// [NotNull] - public virtual Judgement CreateJudgement() => new Judgement(); + protected virtual Judgement CreateJudgement() => new Judgement(); /// /// Creates the for this . diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs index bb36aab0b3..499953dab9 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Objects.Legacy public int ComboOffset { get; set; } - public override Judgement CreateJudgement() => new IgnoreJudgement(); + protected override Judgement CreateJudgement() => new IgnoreJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; } diff --git a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs index e9f3bcb949..0e90330651 100644 --- a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs +++ b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs @@ -149,7 +149,7 @@ namespace osu.Game.Rulesets.Scoring foreach (var obj in EnumerateHitObjects(beatmap)) { - var judgement = obj.CreateJudgement(); + var judgement = obj.Judgement; var result = CreateResult(obj, judgement); if (result == null) diff --git a/osu.Game/Rulesets/Scoring/LegacyDrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/LegacyDrainingHealthProcessor.cs index ce2f7d5624..2bc3ea80ec 100644 --- a/osu.Game/Rulesets/Scoring/LegacyDrainingHealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/LegacyDrainingHealthProcessor.cs @@ -141,7 +141,7 @@ namespace osu.Game.Rulesets.Scoring void increaseHp(HitObject hitObject) { - double amount = GetHealthIncreaseFor(hitObject, hitObject.CreateJudgement().MaxResult); + double amount = GetHealthIncreaseFor(hitObject, hitObject.Judgement.MaxResult); currentHpUncapped += amount; currentHp = Math.Max(0, Math.Min(1, currentHp + amount)); } From 666c57da5023627624c546c4450c7f04af676b5c Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 30 Dec 2023 12:17:14 -0800 Subject: [PATCH 2979/4092] Fix profile current location and interests icons not matching web --- osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs index 85751e7457..83ddb024c6 100644 --- a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs @@ -144,8 +144,8 @@ namespace osu.Game.Overlays.Profile.Header bool anyInfoAdded = false; - anyInfoAdded |= tryAddInfo(FontAwesome.Solid.MapMarker, user.Location); - anyInfoAdded |= tryAddInfo(FontAwesome.Solid.Heart, user.Interests); + anyInfoAdded |= tryAddInfo(FontAwesome.Solid.MapMarkerAlt, user.Location); + anyInfoAdded |= tryAddInfo(FontAwesome.Regular.Heart, user.Interests); anyInfoAdded |= tryAddInfo(FontAwesome.Solid.Suitcase, user.Occupation); if (anyInfoAdded) From ed2362b63da113e45e46756a47b94a3938ce4973 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 30 Dec 2023 12:17:46 -0800 Subject: [PATCH 2980/4092] Fix icon family and weight not transferring to text conversion --- osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs index 83ddb024c6..d5b4d844b2 100644 --- a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs @@ -171,7 +171,7 @@ namespace osu.Game.Overlays.Profile.Header bottomLinkContainer.AddIcon(icon, text => { - text.Font = text.Font.With(size: 10); + text.Font = text.Font.With(icon.Family, 10, icon.Weight); text.Colour = iconColour; }); From c9c39ecb2f616b27b088bb455d5c122323127b10 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 9 Feb 2024 15:15:27 -0800 Subject: [PATCH 2981/4092] Add `RankHighest` to `APIUser` --- .../Visual/Online/TestSceneUserProfileOverlay.cs | 5 +++++ osu.Game/Online/API/Requests/Responses/APIUser.cs | 13 +++++++++++++ 2 files changed, 18 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index bc8f75d4ce..020e020b10 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -137,6 +137,11 @@ namespace osu.Game.Tests.Visual.Online @"top_ranks", @"medals" }, + RankHighest = new APIUser.UserRankHighest + { + Rank = 1, + UpdatedAt = DateTimeOffset.Now, + }, Statistics = new UserStatistics { IsRanked = true, diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index 56eec19fa1..4a31718f28 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -34,6 +34,19 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"previous_usernames")] public string[] PreviousUsernames; + [JsonProperty(@"rank_highest")] + [CanBeNull] + public UserRankHighest RankHighest; + + public class UserRankHighest + { + [JsonProperty(@"rank")] + public int Rank; + + [JsonProperty(@"updated_at")] + public DateTimeOffset UpdatedAt; + } + [JsonProperty(@"country_code")] private string countryCodeString; From 8d1d65a469c717f29115743ab231ce791930668d Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 9 Feb 2024 15:17:34 -0800 Subject: [PATCH 2982/4092] Add `ContentTooltipText` to `ProfileValueDisplay` --- .../Header/Components/ProfileValueDisplay.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/ProfileValueDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/ProfileValueDisplay.cs index 4b1a0409a3..b2c23458b1 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ProfileValueDisplay.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ProfileValueDisplay.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -13,7 +14,7 @@ namespace osu.Game.Overlays.Profile.Header.Components public partial class ProfileValueDisplay : CompositeDrawable { private readonly OsuSpriteText title; - private readonly OsuSpriteText content; + private readonly ContentText content; public LocalisableString Title { @@ -25,6 +26,11 @@ namespace osu.Game.Overlays.Profile.Header.Components set => content.Text = value; } + public LocalisableString ContentTooltipText + { + set => content.TooltipText = value; + } + public ProfileValueDisplay(bool big = false, int minimumWidth = 60) { AutoSizeAxes = Axes.Both; @@ -38,9 +44,9 @@ namespace osu.Game.Overlays.Profile.Header.Components { Font = OsuFont.GetFont(size: 12) }, - content = new OsuSpriteText + content = new ContentText { - Font = OsuFont.GetFont(size: big ? 30 : 20, weight: FontWeight.Light) + Font = OsuFont.GetFont(size: big ? 30 : 20, weight: FontWeight.Light), }, new Container // Add a minimum size to the FillFlowContainer { @@ -56,5 +62,10 @@ namespace osu.Game.Overlays.Profile.Header.Components title.Colour = colourProvider.Content1; content.Colour = colourProvider.Content2; } + + private partial class ContentText : OsuSpriteText, IHasTooltip + { + public LocalisableString TooltipText { get; set; } + } } } From ae89b89928af680dc921cfc4880241aa787e44ce Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 9 Feb 2024 15:33:49 -0800 Subject: [PATCH 2983/4092] Centralise global rank display logic to new class --- .../Header/Components/GlobalRankDisplay.cs | 32 +++++++++++++++++++ .../Profile/Header/Components/MainDetails.cs | 9 ++---- osu.Game/Users/UserRankPanel.cs | 6 ++-- 3 files changed, 37 insertions(+), 10 deletions(-) create mode 100644 osu.Game/Overlays/Profile/Header/Components/GlobalRankDisplay.cs diff --git a/osu.Game/Overlays/Profile/Header/Components/GlobalRankDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/GlobalRankDisplay.cs new file mode 100644 index 0000000000..290b052e27 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/GlobalRankDisplay.cs @@ -0,0 +1,32 @@ +// 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.Extensions.LocalisationExtensions; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; +using osu.Game.Users; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public partial class GlobalRankDisplay : ProfileValueDisplay + { + public readonly Bindable UserStatistics = new Bindable(); + + public GlobalRankDisplay() + : base(true) + { + Title = UsersStrings.ShowRankGlobalSimple; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + UserStatistics.BindValueChanged(s => + { + Content = s.NewValue?.GlobalRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-"; + }, true); + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs b/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs index b89973c5e5..2aea897451 100644 --- a/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs +++ b/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs @@ -22,7 +22,7 @@ namespace osu.Game.Overlays.Profile.Header.Components private readonly Dictionary scoreRankInfos = new Dictionary(); private ProfileValueDisplay medalInfo = null!; private ProfileValueDisplay ppInfo = null!; - private ProfileValueDisplay detailGlobalRank = null!; + private GlobalRankDisplay detailGlobalRank = null!; private ProfileValueDisplay detailCountryRank = null!; private RankGraph rankGraph = null!; @@ -52,10 +52,7 @@ namespace osu.Game.Overlays.Profile.Header.Components Spacing = new Vector2(20), Children = new Drawable[] { - detailGlobalRank = new ProfileValueDisplay(true) - { - Title = UsersStrings.ShowRankGlobalSimple, - }, + detailGlobalRank = new GlobalRankDisplay(), detailCountryRank = new ProfileValueDisplay(true) { Title = UsersStrings.ShowRankCountrySimple, @@ -142,7 +139,7 @@ namespace osu.Game.Overlays.Profile.Header.Components foreach (var scoreRankInfo in scoreRankInfos) scoreRankInfo.Value.RankCount = user?.Statistics?.GradesCount[scoreRankInfo.Key] ?? 0; - detailGlobalRank.Content = user?.Statistics?.GlobalRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-"; + detailGlobalRank.UserStatistics.Value = user?.Statistics; detailCountryRank.Content = user?.Statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-"; rankGraph.Statistics.Value = user?.Statistics; diff --git a/osu.Game/Users/UserRankPanel.cs b/osu.Game/Users/UserRankPanel.cs index 84ff3114fc..ba9ef1eee4 100644 --- a/osu.Game/Users/UserRankPanel.cs +++ b/osu.Game/Users/UserRankPanel.cs @@ -27,7 +27,6 @@ namespace osu.Game.Users [Resolved] private IAPIProvider api { get; set; } = null!; - private ProfileValueDisplay globalRankDisplay = null!; private ProfileValueDisplay countryRankDisplay = null!; private readonly IBindable statistics = new Bindable(); @@ -47,7 +46,6 @@ namespace osu.Game.Users statistics.BindTo(api.Statistics); statistics.BindValueChanged(stats => { - globalRankDisplay.Content = stats.NewValue?.GlobalRank?.ToLocalisableString("\\##,##0") ?? "-"; countryRankDisplay.Content = stats.NewValue?.CountryRank?.ToLocalisableString("\\##,##0") ?? "-"; }, true); } @@ -163,9 +161,9 @@ namespace osu.Game.Users { new Drawable[] { - globalRankDisplay = new ProfileValueDisplay(true) + new GlobalRankDisplay { - Title = UsersStrings.ShowRankGlobalSimple, + UserStatistics = { BindTarget = statistics }, }, countryRankDisplay = new ProfileValueDisplay(true) { From ffd0d9bb3994f7471746921f63a3594efc16c49d Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 9 Feb 2024 15:36:15 -0800 Subject: [PATCH 2984/4092] Add highest rank tooltip to global rank display --- .../Profile/Header/Components/GlobalRankDisplay.cs | 12 ++++++++++++ .../Profile/Header/Components/MainDetails.cs | 1 + osu.Game/Users/UserRankPanel.cs | 6 ++++++ 3 files changed, 19 insertions(+) diff --git a/osu.Game/Overlays/Profile/Header/Components/GlobalRankDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/GlobalRankDisplay.cs index 290b052e27..dcd4129b45 100644 --- a/osu.Game/Overlays/Profile/Header/Components/GlobalRankDisplay.cs +++ b/osu.Game/Overlays/Profile/Header/Components/GlobalRankDisplay.cs @@ -4,6 +4,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Localisation; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Resources.Localisation.Web; using osu.Game.Users; @@ -12,6 +13,7 @@ namespace osu.Game.Overlays.Profile.Header.Components public partial class GlobalRankDisplay : ProfileValueDisplay { public readonly Bindable UserStatistics = new Bindable(); + public readonly Bindable User = new Bindable(); public GlobalRankDisplay() : base(true) @@ -27,6 +29,16 @@ namespace osu.Game.Overlays.Profile.Header.Components { Content = s.NewValue?.GlobalRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-"; }, true); + + // needed as statistics doesn't populate User + User.BindValueChanged(u => + { + var rankHighest = u.NewValue?.RankHighest; + + ContentTooltipText = rankHighest != null + ? UsersStrings.ShowRankHighest(rankHighest.Rank.ToLocalisableString("\\##,##0"), rankHighest.UpdatedAt.ToLocalisableString(@"d MMM yyyy")) + : string.Empty; + }, true); } } } diff --git a/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs b/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs index 2aea897451..ffdf8edc21 100644 --- a/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs +++ b/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs @@ -140,6 +140,7 @@ namespace osu.Game.Overlays.Profile.Header.Components scoreRankInfo.Value.RankCount = user?.Statistics?.GradesCount[scoreRankInfo.Key] ?? 0; detailGlobalRank.UserStatistics.Value = user?.Statistics; + detailGlobalRank.User.Value = user; detailCountryRank.Content = user?.Statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-"; rankGraph.Statistics.Value = user?.Statistics; diff --git a/osu.Game/Users/UserRankPanel.cs b/osu.Game/Users/UserRankPanel.cs index ba9ef1eee4..4a00583094 100644 --- a/osu.Game/Users/UserRankPanel.cs +++ b/osu.Game/Users/UserRankPanel.cs @@ -30,6 +30,7 @@ namespace osu.Game.Users private ProfileValueDisplay countryRankDisplay = null!; private readonly IBindable statistics = new Bindable(); + private readonly IBindable user = new Bindable(); public UserRankPanel(APIUser user) : base(user) @@ -48,6 +49,8 @@ namespace osu.Game.Users { countryRankDisplay.Content = stats.NewValue?.CountryRank?.ToLocalisableString("\\##,##0") ?? "-"; }, true); + + user.BindTo(api.LocalUser!); } protected override Drawable CreateLayout() @@ -164,6 +167,9 @@ namespace osu.Game.Users new GlobalRankDisplay { UserStatistics = { BindTarget = statistics }, + // TODO: make highest rank update, as api.LocalUser doesn't update + // maybe move to statistics in api, so `SoloStatisticsWatcher` can update the value + User = { BindTarget = user }, }, countryRankDisplay = new ProfileValueDisplay(true) { From 7b0b39dbaa515e5d2455de0da7e9884d222fc601 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 9 Feb 2024 15:46:14 -0800 Subject: [PATCH 2985/4092] Fix total play time tooltip area including label --- .../Profile/Header/Components/TotalPlayTime.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/TotalPlayTime.cs b/osu.Game/Overlays/Profile/Header/Components/TotalPlayTime.cs index 08ca59d89b..a3c22d61d2 100644 --- a/osu.Game/Overlays/Profile/Header/Components/TotalPlayTime.cs +++ b/osu.Game/Overlays/Profile/Header/Components/TotalPlayTime.cs @@ -5,25 +5,19 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Localisation; using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.Profile.Header.Components { - public partial class TotalPlayTime : CompositeDrawable, IHasTooltip + public partial class TotalPlayTime : CompositeDrawable { public readonly Bindable User = new Bindable(); - public LocalisableString TooltipText { get; set; } - private ProfileValueDisplay info = null!; public TotalPlayTime() { AutoSizeAxes = Axes.Both; - - TooltipText = "0 hours"; } [BackgroundDependencyLoader] @@ -32,6 +26,7 @@ namespace osu.Game.Overlays.Profile.Header.Components InternalChild = info = new ProfileValueDisplay(minimumWidth: 140) { Title = UsersStrings.ShowStatsPlayTime, + ContentTooltipText = "0 hours", }; User.BindValueChanged(updateTime, true); @@ -40,7 +35,7 @@ namespace osu.Game.Overlays.Profile.Header.Components private void updateTime(ValueChangedEvent user) { int? playTime = user.NewValue?.User.Statistics?.PlayTime; - TooltipText = (playTime ?? 0) / 3600 + " hours"; + info.ContentTooltipText = (playTime ?? 0) / 3600 + " hours"; info.Content = formatTime(playTime); } From 7c04e8bfba9da67118ae3ffc28abe6e2de04191b Mon Sep 17 00:00:00 2001 From: Stoppedpuma <58333920+Stoppedpuma@users.noreply.github.com> Date: Sat, 10 Feb 2024 07:48:24 +0100 Subject: [PATCH 2986/4092] Alias author to creator Allows "author" to show the results of "creator" --- osu.Game/Screens/Select/FilterQueryParser.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 0d8905347b..f6023c0b61 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -68,6 +68,7 @@ namespace osu.Game.Screens.Select return TryUpdateCriteriaRange(ref criteria.OnlineStatus, op, value, tryParseEnum); case "creator": + case "author": return TryUpdateCriteriaText(ref criteria.Creator, op, value); case "artist": From bfeb90c1b6d7e09edf59c42ed14a567a4bcdee7e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 10 Feb 2024 17:20:17 +0900 Subject: [PATCH 2987/4092] Add additional gameplay metadata to room score request --- osu.Game/Online/Rooms/CreateRoomScoreRequest.cs | 10 +++++++++- osu.Game/Screens/Play/RoomSubmittingPlayer.cs | 12 +++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Rooms/CreateRoomScoreRequest.cs b/osu.Game/Online/Rooms/CreateRoomScoreRequest.cs index c31c6a929a..e0f91032fd 100644 --- a/osu.Game/Online/Rooms/CreateRoomScoreRequest.cs +++ b/osu.Game/Online/Rooms/CreateRoomScoreRequest.cs @@ -1,8 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Globalization; using System.Net.Http; using osu.Framework.IO.Network; +using osu.Game.Beatmaps; using osu.Game.Online.API; namespace osu.Game.Online.Rooms @@ -11,12 +13,16 @@ namespace osu.Game.Online.Rooms { private readonly long roomId; private readonly long playlistItemId; + private readonly BeatmapInfo beatmapInfo; + private readonly int rulesetId; private readonly string versionHash; - public CreateRoomScoreRequest(long roomId, long playlistItemId, string versionHash) + public CreateRoomScoreRequest(long roomId, long playlistItemId, BeatmapInfo beatmapInfo, int rulesetId, string versionHash) { this.roomId = roomId; this.playlistItemId = playlistItemId; + this.beatmapInfo = beatmapInfo; + this.rulesetId = rulesetId; this.versionHash = versionHash; } @@ -25,6 +31,8 @@ namespace osu.Game.Online.Rooms var req = base.CreateWebRequest(); req.Method = HttpMethod.Post; req.AddParameter("version_hash", versionHash); + req.AddParameter("beatmap_hash", beatmapInfo.MD5Hash); + req.AddParameter("ruleset_id", rulesetId.ToString(CultureInfo.InvariantCulture)); return req; } diff --git a/osu.Game/Screens/Play/RoomSubmittingPlayer.cs b/osu.Game/Screens/Play/RoomSubmittingPlayer.cs index e21daa737e..3f74f49384 100644 --- a/osu.Game/Screens/Play/RoomSubmittingPlayer.cs +++ b/osu.Game/Screens/Play/RoomSubmittingPlayer.cs @@ -4,6 +4,7 @@ #nullable disable using System.Diagnostics; +using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Scoring; @@ -30,7 +31,16 @@ namespace osu.Game.Screens.Play if (!(Room.RoomID.Value is long roomId)) return null; - return new CreateRoomScoreRequest(roomId, PlaylistItem.ID, Game.VersionHash); + int beatmapId = Beatmap.Value.BeatmapInfo.OnlineID; + int rulesetId = Ruleset.Value.OnlineID; + + if (beatmapId <= 0) + return null; + + if (!Ruleset.Value.IsLegacyRuleset()) + return null; + + return new CreateRoomScoreRequest(roomId, PlaylistItem.ID, Beatmap.Value.BeatmapInfo, rulesetId, Game.VersionHash); } protected override APIRequest CreateSubmissionRequest(Score score, long token) From 5fa54c8c6355aeb85df2daa905d4c02d66995132 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 10 Feb 2024 14:16:45 +0300 Subject: [PATCH 2988/4092] Fix remaining use cases --- .../Objects/EmptyFreeformHitObject.cs | 2 +- .../Objects/PippidonHitObject.cs | 2 +- .../Objects/EmptyScrollingHitObject.cs | 2 +- .../Objects/PippidonHitObject.cs | 2 +- .../Gameplay/TestSceneDrainingHealthProcessor.cs | 2 +- .../Gameplay/TestSceneDrawableHitObject.cs | 2 +- .../Gameplay/TestSceneScoreProcessor.cs | 14 +++++++------- .../Rulesets/Scoring/ScoreProcessorTest.cs | 15 ++++++--------- 8 files changed, 19 insertions(+), 22 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/EmptyFreeformHitObject.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/EmptyFreeformHitObject.cs index 9cd18d2d9f..e166d09f84 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/EmptyFreeformHitObject.cs +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/EmptyFreeformHitObject.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.EmptyFreeform.Objects { public class EmptyFreeformHitObject : HitObject, IHasPosition { - public override Judgement CreateJudgement() => new Judgement(); + protected override Judgement CreateJudgement() => new Judgement(); public Vector2 Position { get; set; } diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/PippidonHitObject.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/PippidonHitObject.cs index 0c22554e82..748e6d3b53 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/PippidonHitObject.cs +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/PippidonHitObject.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Pippidon.Objects { public class PippidonHitObject : HitObject, IHasPosition { - public override Judgement CreateJudgement() => new Judgement(); + protected override Judgement CreateJudgement() => new Judgement(); public Vector2 Position { get; set; } diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/EmptyScrollingHitObject.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/EmptyScrollingHitObject.cs index 9b469be496..4564bd1e09 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/EmptyScrollingHitObject.cs +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/EmptyScrollingHitObject.cs @@ -8,6 +8,6 @@ namespace osu.Game.Rulesets.EmptyScrolling.Objects { public class EmptyScrollingHitObject : HitObject { - public override Judgement CreateJudgement() => new Judgement(); + protected override Judgement CreateJudgement() => new Judgement(); } } diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/PippidonHitObject.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/PippidonHitObject.cs index 9dd135479f..ed16bce9f6 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/PippidonHitObject.cs +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/PippidonHitObject.cs @@ -13,6 +13,6 @@ namespace osu.Game.Rulesets.Pippidon.Objects /// public int Lane; - public override Judgement CreateJudgement() => new Judgement(); + protected override Judgement CreateJudgement() => new Judgement(); } } diff --git a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs index 584a9e09c0..f0f93f59b5 100644 --- a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs @@ -358,7 +358,7 @@ namespace osu.Game.Tests.Gameplay this.maxResult = maxResult; } - public override Judgement CreateJudgement() => new TestJudgement(maxResult); + protected override Judgement CreateJudgement() => new TestJudgement(maxResult); protected override HitWindows CreateHitWindows() => new HitWindows(); private class TestJudgement : Judgement diff --git a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs index 73177e36e1..22643feebb 100644 --- a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs +++ b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs @@ -175,7 +175,7 @@ namespace osu.Game.Tests.Gameplay var hitObject = new HitObject { StartTime = Time.Current }; lifetimeEntry = new HitObjectLifetimeEntry(hitObject) { - Result = new JudgementResult(hitObject, hitObject.CreateJudgement()) + Result = new JudgementResult(hitObject, hitObject.Judgement) { Type = HitResult.Great } diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs index 1a644ad600..a428979015 100644 --- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs @@ -129,10 +129,10 @@ namespace osu.Game.Tests.Gameplay var scoreProcessor = new ScoreProcessor(new OsuRuleset()); scoreProcessor.ApplyBeatmap(beatmap); - scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].CreateJudgement()) { Type = HitResult.Ok }); - scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].CreateJudgement()) { Type = HitResult.LargeTickHit }); - scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[2], beatmap.HitObjects[2].CreateJudgement()) { Type = HitResult.SmallTickMiss }); - scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[3], beatmap.HitObjects[3].CreateJudgement()) { Type = HitResult.SmallBonus }); + scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].Judgement) { Type = HitResult.Ok }); + scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].Judgement) { Type = HitResult.LargeTickHit }); + scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[2], beatmap.HitObjects[2].Judgement) { Type = HitResult.SmallTickMiss }); + scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[3], beatmap.HitObjects[3].Judgement) { Type = HitResult.SmallBonus }); var score = new ScoreInfo { Ruleset = new OsuRuleset().RulesetInfo }; scoreProcessor.FailScore(score); @@ -169,8 +169,8 @@ namespace osu.Game.Tests.Gameplay Assert.That(scoreProcessor.MinimumAccuracy.Value, Is.EqualTo(0)); Assert.That(scoreProcessor.MaximumAccuracy.Value, Is.EqualTo(1)); - scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].CreateJudgement()) { Type = HitResult.Ok }); - scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].CreateJudgement()) { Type = HitResult.Great }); + scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].Judgement) { Type = HitResult.Ok }); + scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].Judgement) { Type = HitResult.Great }); Assert.That(scoreProcessor.Accuracy.Value, Is.EqualTo((double)(100 + 300) / (2 * 300)).Within(Precision.DOUBLE_EPSILON)); Assert.That(scoreProcessor.MinimumAccuracy.Value, Is.EqualTo((double)(100 + 300) / (4 * 300)).Within(Precision.DOUBLE_EPSILON)); @@ -196,7 +196,7 @@ namespace osu.Game.Tests.Gameplay this.maxResult = maxResult; } - public override Judgement CreateJudgement() => new TestJudgement(maxResult); + protected override Judgement CreateJudgement() => new TestJudgement(maxResult); } } } diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index a3f91fffba..ea43a65825 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -112,7 +112,7 @@ namespace osu.Game.Tests.Rulesets.Scoring for (int i = 0; i < 4; i++) { - var judgementResult = new JudgementResult(fourObjectBeatmap.HitObjects[i], fourObjectBeatmap.HitObjects[i].CreateJudgement()) + var judgementResult = new JudgementResult(fourObjectBeatmap.HitObjects[i], fourObjectBeatmap.HitObjects[i].Judgement) { Type = i == 2 ? minResult : hitResult }; @@ -141,7 +141,7 @@ namespace osu.Game.Tests.Rulesets.Scoring for (int i = 0; i < object_count; ++i) { - var judgementResult = new JudgementResult(largeBeatmap.HitObjects[i], largeBeatmap.HitObjects[i].CreateJudgement()) + var judgementResult = new JudgementResult(largeBeatmap.HitObjects[i], largeBeatmap.HitObjects[i].Judgement) { Type = HitResult.Great }; @@ -325,11 +325,11 @@ namespace osu.Game.Tests.Rulesets.Scoring scoreProcessor = new TestScoreProcessor(); scoreProcessor.ApplyBeatmap(beatmap); - scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].CreateJudgement()) { Type = HitResult.Great }); + scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].Judgement) { Type = HitResult.Great }); Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(1)); Assert.That(scoreProcessor.Accuracy.Value, Is.EqualTo(1)); - scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].CreateJudgement()) { Type = HitResult.ComboBreak }); + scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].Judgement) { Type = HitResult.ComboBreak }); Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(0)); Assert.That(scoreProcessor.Accuracy.Value, Is.EqualTo(1)); } @@ -350,7 +350,7 @@ namespace osu.Game.Tests.Rulesets.Scoring for (int i = 0; i < beatmap.HitObjects.Count; i++) { - scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[i], beatmap.HitObjects[i].CreateJudgement()) + scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[i], beatmap.HitObjects[i].Judgement) { Type = i == 0 ? HitResult.Miss : HitResult.Great }); @@ -441,10 +441,7 @@ namespace osu.Game.Tests.Rulesets.Scoring private readonly HitResult maxResult; private readonly HitResult? minResult; - public override Judgement CreateJudgement() - { - return new TestJudgement(maxResult, minResult); - } + protected override Judgement CreateJudgement() => new TestJudgement(maxResult, minResult); public TestHitObject(HitResult maxResult, HitResult? minResult = null) { From ae5f108f01444dc4c7449858ed77f63ba22d153e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 10 Feb 2024 13:08:26 +0100 Subject: [PATCH 2989/4092] Add visual test coverage of user profile info section --- osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index bc8f75d4ce..1b9ca8717a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -206,6 +206,12 @@ namespace osu.Game.Tests.Visual.Online Total = 50 }, SupportLevel = 2, + Location = "Somewhere", + Interests = "Rhythm games", + Occupation = "Gamer", + Twitter = "test_user", + Discord = "test_user", + Website = "https://google.com", }; } } From 6894f17b2311019a18cd1553b569998e263981f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 10 Feb 2024 15:34:12 +0100 Subject: [PATCH 2990/4092] Add test coverage --- .../Editor/TestSceneOsuComposerSelection.cs | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs index 623cefff6b..b97fe5c5a8 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs @@ -124,6 +124,113 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("selection preserved", () => EditorBeatmap.SelectedHitObjects.Count == 2); } + [Test] + public void TestControlClickAddsControlPointsIfSingleSliderSelected() + { + var firstSlider = new Slider + { + StartTime = 0, + Position = new Vector2(0, 0), + Path = new SliderPath + { + ControlPoints = + { + new PathControlPoint(), + new PathControlPoint(new Vector2(100)) + } + } + }; + var secondSlider = new Slider + { + StartTime = 1000, + Position = new Vector2(200, 200), + Path = new SliderPath + { + ControlPoints = + { + new PathControlPoint(), + new PathControlPoint(new Vector2(100, -100)) + } + } + }; + + AddStep("add objects", () => EditorBeatmap.AddRange(new HitObject[] { firstSlider, secondSlider })); + AddStep("select first slider", () => EditorBeatmap.SelectedHitObjects.AddRange(new HitObject[] { secondSlider })); + + AddStep("move mouse to middle of slider", () => + { + var pos = blueprintContainer.SelectionBlueprints + .First(s => s.Item == secondSlider) + .ChildrenOfType().First() + .ScreenSpaceDrawQuad.Centre; + + InputManager.MoveMouseTo(pos); + }); + AddStep("control-click left mouse", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Click(MouseButton.Left); + InputManager.ReleaseKey(Key.ControlLeft); + }); + AddAssert("selection preserved", () => EditorBeatmap.SelectedHitObjects.Count, () => Is.EqualTo(1)); + AddAssert("slider has 3 anchors", () => secondSlider.Path.ControlPoints.Count, () => Is.EqualTo(3)); + } + + [Test] + public void TestControlClickDoesNotAddSliderControlPointsIfMultipleObjectsSelected() + { + var firstSlider = new Slider + { + StartTime = 0, + Position = new Vector2(0, 0), + Path = new SliderPath + { + ControlPoints = + { + new PathControlPoint(), + new PathControlPoint(new Vector2(100)) + } + } + }; + var secondSlider = new Slider + { + StartTime = 1000, + Position = new Vector2(200, 200), + Path = new SliderPath + { + ControlPoints = + { + new PathControlPoint(), + new PathControlPoint(new Vector2(100, -100)) + } + } + }; + + AddStep("add objects", () => EditorBeatmap.AddRange(new HitObject[] { firstSlider, secondSlider })); + AddStep("select first slider", () => EditorBeatmap.SelectedHitObjects.AddRange(new HitObject[] { firstSlider, secondSlider })); + + AddStep("move mouse to middle of slider", () => + { + var pos = blueprintContainer.SelectionBlueprints + .First(s => s.Item == secondSlider) + .ChildrenOfType().First() + .ScreenSpaceDrawQuad.Centre; + + InputManager.MoveMouseTo(pos); + }); + AddStep("control-click left mouse", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Click(MouseButton.Left); + InputManager.ReleaseKey(Key.ControlLeft); + }); + AddAssert("selection not preserved", () => EditorBeatmap.SelectedHitObjects.Count, () => Is.EqualTo(1)); + AddAssert("second slider not selected", + () => blueprintContainer.SelectionBlueprints.First(s => s.Item == secondSlider).IsSelected, + () => Is.False); + AddAssert("slider still has 2 anchors", () => secondSlider.Path.ControlPoints.Count, () => Is.EqualTo(2)); + } + private ComposeBlueprintContainer blueprintContainer => Editor.ChildrenOfType().First(); From d99187302894c50cee5f84b493e7050f899cc21e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 10 Feb 2024 15:54:43 +0100 Subject: [PATCH 2991/4092] Add note to never run release config to contributing guidelines See https://discord.com/channels/188630481301012481/188630652340404224/1205886296439136286 for the latest instance of this, but this really keeps happening. Maybe someone will read this and stop themselves from making the same mistake. One can dream. --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4106641adb..4f969ab915 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -68,6 +68,7 @@ Aside from the above, below is a brief checklist of things to watch out when you - Please do not make code changes via the GitHub web interface. - Please add tests for your changes. We expect most new features and bugfixes to have test coverage, unless the effort of adding them is prohibitive. The visual testing methodology we use is described in more detail [here](https://github.com/ppy/osu-framework/wiki/Development-and-Testing). - Please run tests and code style analysis (via `InspectCode.{ps1,sh}` scripts in the root of this repository) before opening the PR. This is particularly important if you're a first-time contributor, as CI will not run for your PR until we allow it to do so. +- Do not run the game in release configuration at any point during your testing (the sole exception to this being benchmarks). Using release is an unnecessary and harmful practice, and can even lead to you losing your local realm database if you start making changes to the schema. The debug configuration has a completely separated full-stack environment, including a development website instance at https://dev.ppy.sh/. It is permitted to register an account on that development instance for testing purposes and not worry about multi-accounting infractions. After you're done with your changes and you wish to open the PR, please observe the following recommendations: From c5f392c17d92ef061d480782ee195726099dab2e Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sat, 10 Feb 2024 15:25:03 +0000 Subject: [PATCH 2992/4092] only compute flashlight in osu! difficulty calculations when required --- .../Difficulty/OsuDifficultyCalculator.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 3b580a5b59..007cd977e5 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -40,7 +40,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty double aimRatingNoSliders = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; double speedRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier; double speedNotes = ((Speed)skills[2]).RelevantNoteCount(); - double flashlightRating = Math.Sqrt(skills[3].DifficultyValue()) * difficulty_multiplier; + + double flashlightRating = 0.0; + + if (mods.Any(h => h is OsuModFlashlight)) + flashlightRating = Math.Sqrt(skills[3].DifficultyValue()) * difficulty_multiplier; double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1; @@ -126,13 +130,17 @@ namespace osu.Game.Rulesets.Osu.Difficulty protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) { - return new Skill[] + var skills = new List { new Aim(mods, true), new Aim(mods, false), - new Speed(mods), - new Flashlight(mods) + new Speed(mods) }; + + if (mods.Any(h => h is OsuModFlashlight)) + skills.Add(new Flashlight(mods)); + + return skills.ToArray(); } protected override Mod[] DifficultyAdjustmentMods => new Mod[] From bd04377643d3f42836ff10b6fc6af53728fee215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 10 Feb 2024 16:42:19 +0100 Subject: [PATCH 2993/4092] Tell people to not run in release harder Co-authored-by: Dean Herbert --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4f969ab915..0fe6b6fb4d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -68,7 +68,7 @@ Aside from the above, below is a brief checklist of things to watch out when you - Please do not make code changes via the GitHub web interface. - Please add tests for your changes. We expect most new features and bugfixes to have test coverage, unless the effort of adding them is prohibitive. The visual testing methodology we use is described in more detail [here](https://github.com/ppy/osu-framework/wiki/Development-and-Testing). - Please run tests and code style analysis (via `InspectCode.{ps1,sh}` scripts in the root of this repository) before opening the PR. This is particularly important if you're a first-time contributor, as CI will not run for your PR until we allow it to do so. -- Do not run the game in release configuration at any point during your testing (the sole exception to this being benchmarks). Using release is an unnecessary and harmful practice, and can even lead to you losing your local realm database if you start making changes to the schema. The debug configuration has a completely separated full-stack environment, including a development website instance at https://dev.ppy.sh/. It is permitted to register an account on that development instance for testing purposes and not worry about multi-accounting infractions. +- **Do not run the game in release configuration at any point during your testing** (the sole exception to this being benchmarks). Using release is an unnecessary and harmful practice, and can even lead to you losing your local realm database if you start making changes to the schema. The debug configuration has a completely separated full-stack environment, including a development website instance at https://dev.ppy.sh/. It is permitted to register an account on that development instance for testing purposes and not worry about multi-accounting infractions. After you're done with your changes and you wish to open the PR, please observe the following recommendations: From 901b82384d5fbc224c18b96347bc3b07e37b0e8b Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sat, 10 Feb 2024 15:42:55 +0000 Subject: [PATCH 2994/4092] replace linq usage in `Previous` and `Next` with more direct computation --- .../Difficulty/Preprocessing/DifficultyHitObject.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs b/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs index 9ce0906dea..9785865192 100644 --- a/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs +++ b/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs @@ -4,7 +4,6 @@ #nullable disable using System.Collections.Generic; -using System.Linq; using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Difficulty.Preprocessing @@ -65,8 +64,16 @@ namespace osu.Game.Rulesets.Difficulty.Preprocessing EndTime = hitObject.GetEndTime() / clockRate; } - public DifficultyHitObject Previous(int backwardsIndex) => difficultyHitObjects.ElementAtOrDefault(Index - (backwardsIndex + 1)); + public DifficultyHitObject Previous(int backwardsIndex) + { + int index = Index - (backwardsIndex + 1); + return index >= 0 && index < difficultyHitObjects.Count ? difficultyHitObjects[index] : default; + } - public DifficultyHitObject Next(int forwardsIndex) => difficultyHitObjects.ElementAtOrDefault(Index + (forwardsIndex + 1)); + public DifficultyHitObject Next(int forwardsIndex) + { + int index = Index + (forwardsIndex + 1); + return index >= 0 && index < difficultyHitObjects.Count ? difficultyHitObjects[index] : default; + } } } From 7dba21fdaca9d405b1856dcc635a79a4313b91a4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 11 Feb 2024 20:05:58 +0800 Subject: [PATCH 2995/4092] Move init method to bottom of file --- .../LocalCachedBeatmapMetadataSource.cs | 102 +++++++++--------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs b/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs index ff88fecd86..3f93c32283 100644 --- a/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs +++ b/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs @@ -49,57 +49,6 @@ namespace osu.Game.Beatmaps prepareLocalCache(); } - private void prepareLocalCache() - { - string cacheFilePath = storage.GetFullPath(cache_database_name); - string compressedCacheFilePath = $@"{cacheFilePath}.bz2"; - - cacheDownloadRequest = new FileWebRequest(compressedCacheFilePath, $@"https://assets.ppy.sh/client-resources/{cache_database_name}.bz2?{DateTimeOffset.UtcNow:yyyyMMdd}"); - - cacheDownloadRequest.Failed += ex => - { - File.Delete(compressedCacheFilePath); - File.Delete(cacheFilePath); - - Logger.Log($@"{nameof(BeatmapUpdaterMetadataLookup)}'s online cache download failed: {ex}", LoggingTarget.Database); - }; - - cacheDownloadRequest.Finished += () => - { - try - { - using (var stream = File.OpenRead(cacheDownloadRequest.Filename)) - using (var outStream = File.OpenWrite(cacheFilePath)) - using (var bz2 = new BZip2Stream(stream, CompressionMode.Decompress, false)) - bz2.CopyTo(outStream); - - // set to null on completion to allow lookups to begin using the new source - cacheDownloadRequest = null; - } - catch (Exception ex) - { - Logger.Log($@"{nameof(LocalCachedBeatmapMetadataSource)}'s online cache extraction failed: {ex}", LoggingTarget.Database); - File.Delete(cacheFilePath); - } - finally - { - File.Delete(compressedCacheFilePath); - } - }; - - Task.Run(async () => - { - try - { - await cacheDownloadRequest.PerformAsync().ConfigureAwait(false); - } - catch - { - // Prevent throwing unobserved exceptions, as they will be logged from the network request to the log file anyway. - } - }); - } - public bool Available => // no download in progress. cacheDownloadRequest == null @@ -173,6 +122,57 @@ namespace osu.Game.Beatmaps return false; } + private void prepareLocalCache() + { + string cacheFilePath = storage.GetFullPath(cache_database_name); + string compressedCacheFilePath = $@"{cacheFilePath}.bz2"; + + cacheDownloadRequest = new FileWebRequest(compressedCacheFilePath, $@"https://assets.ppy.sh/client-resources/{cache_database_name}.bz2?{DateTimeOffset.UtcNow:yyyyMMdd}"); + + cacheDownloadRequest.Failed += ex => + { + File.Delete(compressedCacheFilePath); + File.Delete(cacheFilePath); + + Logger.Log($@"{nameof(BeatmapUpdaterMetadataLookup)}'s online cache download failed: {ex}", LoggingTarget.Database); + }; + + cacheDownloadRequest.Finished += () => + { + try + { + using (var stream = File.OpenRead(cacheDownloadRequest.Filename)) + using (var outStream = File.OpenWrite(cacheFilePath)) + using (var bz2 = new BZip2Stream(stream, CompressionMode.Decompress, false)) + bz2.CopyTo(outStream); + + // set to null on completion to allow lookups to begin using the new source + cacheDownloadRequest = null; + } + catch (Exception ex) + { + Logger.Log($@"{nameof(LocalCachedBeatmapMetadataSource)}'s online cache extraction failed: {ex}", LoggingTarget.Database); + File.Delete(cacheFilePath); + } + finally + { + File.Delete(compressedCacheFilePath); + } + }; + + Task.Run(async () => + { + try + { + await cacheDownloadRequest.PerformAsync().ConfigureAwait(false); + } + catch + { + // Prevent throwing unobserved exceptions, as they will be logged from the network request to the log file anyway. + } + }); + } + private void logForModel(BeatmapSetInfo set, string message) => RealmArchiveModelImporter.LogForModel(set, $@"[{nameof(LocalCachedBeatmapMetadataSource)}] {message}"); From 5e692345dedb62c67914485347943595748cfc9a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 11 Feb 2024 17:44:54 +0300 Subject: [PATCH 2996/4092] Don't convert pixel data to array --- osu.Game/Beatmaps/BeatmapPanelBackgroundTextureLoaderStore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapPanelBackgroundTextureLoaderStore.cs b/osu.Game/Beatmaps/BeatmapPanelBackgroundTextureLoaderStore.cs index acd60b664d..128e100e4b 100644 --- a/osu.Game/Beatmaps/BeatmapPanelBackgroundTextureLoaderStore.cs +++ b/osu.Game/Beatmaps/BeatmapPanelBackgroundTextureLoaderStore.cs @@ -59,7 +59,7 @@ namespace osu.Game.Beatmaps private TextureUpload limitTextureUploadSize(TextureUpload textureUpload) { - var image = Image.LoadPixelData(textureUpload.Data.ToArray(), textureUpload.Width, textureUpload.Height); + var image = Image.LoadPixelData(textureUpload.Data, textureUpload.Width, textureUpload.Height); // The original texture upload will no longer be returned or used. textureUpload.Dispose(); From ca0819cf7a4529fc27a71d9e9e6ff4ff3a25ff3f Mon Sep 17 00:00:00 2001 From: Stoppedpuma <58333920+Stoppedpuma@users.noreply.github.com> Date: Sun, 11 Feb 2024 20:28:16 +0100 Subject: [PATCH 2997/4092] Alias "mapper" as well --- osu.Game/Screens/Select/FilterQueryParser.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index f6023c0b61..5d3ff1261f 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -69,6 +69,7 @@ namespace osu.Game.Screens.Select case "creator": case "author": + case "mapper": return TryUpdateCriteriaText(ref criteria.Creator, op, value); case "artist": From 36005a5449d4169edf7d77d948d1b0c0b38e2abb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 12 Feb 2024 08:33:08 +0100 Subject: [PATCH 2998/4092] Fix selected legacy skins crashing on zero-length hold notes Closes https://github.com/ppy/osu/issues/27134. --- osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs index 07045b76ca..a8200e0144 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs @@ -243,7 +243,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy bodySprite.FillMode = FillMode.Stretch; // i dunno this looks about right?? - bodySprite.Scale = new Vector2(1, scaleDirection * 32800 / sprite.DrawHeight); + // the guard against zero draw height is intended for zero-length hold notes. yes, such cases have been spotted in the wild. + if (sprite.DrawHeight > 0) + bodySprite.Scale = new Vector2(1, scaleDirection * 32800 / sprite.DrawHeight); } break; From 2ae616a88ea17cc1dc03ab6c0dfa46aeb20f93ee Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 12 Feb 2024 12:29:31 +0300 Subject: [PATCH 2999/4092] Combine other cases of displaying dash in scores PP --- osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs | 2 +- .../BeatmapSet/Scores/TopScoreStatisticsSection.cs | 2 +- .../Profile/Sections/Ranks/DrawableProfileScore.cs | 10 +++++----- .../Drawables/UnrankedPerformancePointsPlaceholder.cs | 8 ++++---- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index c8ecb38c86..9dd0e26da2 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -181,7 +181,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (showPerformancePoints) { if (!score.Ranked) - content.Add(new UnrankedPerformancePointsPlaceholder { Font = OsuFont.GetFont(size: text_size) }); + content.Add(new UnrankedPerformancePointsPlaceholder(ScoresStrings.StatusNoPp) { Font = OsuFont.GetFont(size: text_size) }); else if (score.PP == null) content.Add(new UnprocessedPerformancePointsPlaceholder { Size = new Vector2(text_size) }); else diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index 488b99d620..4aad3cf953 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -126,7 +126,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores ppColumn.Alpha = value.BeatmapInfo!.Status.GrantsPerformancePoints() ? 1 : 0; if (!value.Ranked) - ppColumn.Drawable = new UnrankedPerformancePointsPlaceholder { Font = smallFont }; + ppColumn.Drawable = new UnrankedPerformancePointsPlaceholder(ScoresStrings.StatusNoPp) { Font = smallFont }; else if (value.PP is not double pp) ppColumn.Drawable = new UnprocessedPerformancePointsPlaceholder { Size = new Vector2(smallFont.Size) }; else diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index d1988956be..1211d65816 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -14,6 +14,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; +using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; @@ -243,20 +244,19 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks if (Score.Beatmap?.Status.GrantsPerformancePoints() != true) { - return new OsuSpriteText + return new UnrankedPerformancePointsPlaceholder(UsersStrings.ShowExtraTopRanksNotRanked) { Font = OsuFont.GetFont(weight: FontWeight.Bold), - Text = "-", - Colour = colourProvider.Highlight1 + Colour = colourProvider.Highlight1, }; } if (!Score.Ranked) { - return new UnrankedPerformancePointsPlaceholder + return new UnrankedPerformancePointsPlaceholder(ScoresStrings.StatusNoPp) { Font = OsuFont.GetFont(weight: FontWeight.Bold), - Colour = colourProvider.Highlight1 + Colour = colourProvider.Highlight1, }; } diff --git a/osu.Game/Scoring/Drawables/UnrankedPerformancePointsPlaceholder.cs b/osu.Game/Scoring/Drawables/UnrankedPerformancePointsPlaceholder.cs index c5c190e1a1..0097497ef1 100644 --- a/osu.Game/Scoring/Drawables/UnrankedPerformancePointsPlaceholder.cs +++ b/osu.Game/Scoring/Drawables/UnrankedPerformancePointsPlaceholder.cs @@ -5,22 +5,22 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; -using osu.Game.Resources.Localisation.Web; namespace osu.Game.Scoring.Drawables { /// - /// A placeholder used in PP columns for scores that do not award PP. + /// A placeholder used in PP columns for scores that do not award PP due to a reason specified by . /// public partial class UnrankedPerformancePointsPlaceholder : SpriteText, IHasTooltip { - public LocalisableString TooltipText => ScoresStrings.StatusNoPp; + public LocalisableString TooltipText { get; } - public UnrankedPerformancePointsPlaceholder() + public UnrankedPerformancePointsPlaceholder(LocalisableString tooltipText) { Anchor = Anchor.Centre; Origin = Anchor.Centre; Text = "-"; + TooltipText = tooltipText; } } } From 80b14f1aaef3627935873cdc0be2649d588d4f38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 7 May 2023 20:06:07 +0200 Subject: [PATCH 3000/4092] Add test coverage for beatmap confusion scenarios --- .../BeatmapUpdaterMetadataLookupTest.cs | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs b/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs index 84195f1e7c..e6c06f7aec 100644 --- a/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs +++ b/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs @@ -193,5 +193,110 @@ namespace osu.Game.Tests.Beatmaps Assert.That(beatmap.OnlineID, Is.EqualTo(123456)); } + + [Test] + public void TestReturnedMetadataHasDifferentOnlineID([Values] bool preferOnlineFetch) + { + var lookupResult = new OnlineBeatmapMetadata { BeatmapID = 654321, BeatmapStatus = BeatmapOnlineStatus.Ranked }; + + var targetMock = preferOnlineFetch ? apiMetadataSourceMock : localCachedMetadataSourceMock; + targetMock.Setup(src => src.Available).Returns(true); + targetMock.Setup(src => src.TryLookup(It.IsAny(), out lookupResult)) + .Returns(true); + + var beatmap = new BeatmapInfo { OnlineID = 123456 }; + var beatmapSet = new BeatmapSetInfo(beatmap.Yield()); + beatmap.BeatmapSet = beatmapSet; + + metadataLookup.Update(beatmapSet, preferOnlineFetch); + + Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.None)); + Assert.That(beatmap.OnlineID, Is.EqualTo(-1)); + } + + [Test] + public void TestMetadataLookupForBeatmapWithoutPopulatedIDAndCorrectHash([Values] bool preferOnlineFetch) + { + var lookupResult = new OnlineBeatmapMetadata + { + BeatmapID = 654321, + BeatmapStatus = BeatmapOnlineStatus.Ranked, + MD5Hash = @"deadbeef", + }; + + var targetMock = preferOnlineFetch ? apiMetadataSourceMock : localCachedMetadataSourceMock; + targetMock.Setup(src => src.Available).Returns(true); + targetMock.Setup(src => src.TryLookup(It.IsAny(), out lookupResult)) + .Returns(true); + + var beatmap = new BeatmapInfo + { + MD5Hash = @"deadbeef" + }; + var beatmapSet = new BeatmapSetInfo(beatmap.Yield()); + beatmap.BeatmapSet = beatmapSet; + + metadataLookup.Update(beatmapSet, preferOnlineFetch); + + Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.Ranked)); + Assert.That(beatmap.OnlineID, Is.EqualTo(654321)); + } + + [Test] + public void TestMetadataLookupForBeatmapWithoutPopulatedIDAndIncorrectHash([Values] bool preferOnlineFetch) + { + var lookupResult = new OnlineBeatmapMetadata + { + BeatmapID = 654321, + BeatmapStatus = BeatmapOnlineStatus.Ranked, + MD5Hash = @"cafebabe", + }; + + var targetMock = preferOnlineFetch ? apiMetadataSourceMock : localCachedMetadataSourceMock; + targetMock.Setup(src => src.Available).Returns(true); + targetMock.Setup(src => src.TryLookup(It.IsAny(), out lookupResult)) + .Returns(true); + + var beatmap = new BeatmapInfo + { + MD5Hash = @"deadbeef" + }; + var beatmapSet = new BeatmapSetInfo(beatmap.Yield()); + beatmap.BeatmapSet = beatmapSet; + + metadataLookup.Update(beatmapSet, preferOnlineFetch); + + Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.None)); + Assert.That(beatmap.OnlineID, Is.EqualTo(-1)); + } + + [Test] + public void TestReturnedMetadataHasDifferentHash([Values] bool preferOnlineFetch) + { + var lookupResult = new OnlineBeatmapMetadata + { + BeatmapID = 654321, + BeatmapStatus = BeatmapOnlineStatus.Ranked, + MD5Hash = @"deadbeef" + }; + + var targetMock = preferOnlineFetch ? apiMetadataSourceMock : localCachedMetadataSourceMock; + targetMock.Setup(src => src.Available).Returns(true); + targetMock.Setup(src => src.TryLookup(It.IsAny(), out lookupResult)) + .Returns(true); + + var beatmap = new BeatmapInfo + { + OnlineID = 654321, + MD5Hash = @"cafebabe", + }; + var beatmapSet = new BeatmapSetInfo(beatmap.Yield()); + beatmap.BeatmapSet = beatmapSet; + + metadataLookup.Update(beatmapSet, preferOnlineFetch); + + Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.None)); + Assert.That(beatmap.OnlineID, Is.EqualTo(654321)); + } } } From 834db989f721a38bb03cf8f8c1f9d24e4e5f0ba9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 12 Feb 2024 11:03:08 +0100 Subject: [PATCH 3001/4092] Add more test coverage for more failure --- .../BeatmapUpdaterMetadataLookupTest.cs | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs b/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs index e6c06f7aec..fe9d15a89d 100644 --- a/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs +++ b/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs @@ -298,5 +298,59 @@ namespace osu.Game.Tests.Beatmaps Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.None)); Assert.That(beatmap.OnlineID, Is.EqualTo(654321)); } + + [Test] + public void TestPartiallyModifiedSet([Values] bool preferOnlineFetch) + { + var firstResult = new OnlineBeatmapMetadata + { + BeatmapID = 654321, + BeatmapStatus = BeatmapOnlineStatus.Ranked, + BeatmapSetStatus = BeatmapOnlineStatus.Ranked, + MD5Hash = @"cafebabe" + }; + var secondResult = new OnlineBeatmapMetadata + { + BeatmapID = 666666, + BeatmapStatus = BeatmapOnlineStatus.Ranked, + BeatmapSetStatus = BeatmapOnlineStatus.Ranked, + MD5Hash = @"dededede" + }; + + var targetMock = preferOnlineFetch ? apiMetadataSourceMock : localCachedMetadataSourceMock; + targetMock.Setup(src => src.Available).Returns(true); + targetMock.Setup(src => src.TryLookup(It.Is(bi => bi.OnlineID == 654321), out firstResult)) + .Returns(true); + targetMock.Setup(src => src.TryLookup(It.Is(bi => bi.OnlineID == 666666), out secondResult)) + .Returns(true); + + var firstBeatmap = new BeatmapInfo + { + OnlineID = 654321, + MD5Hash = @"cafebabe", + }; + var secondBeatmap = new BeatmapInfo + { + OnlineID = 666666, + MD5Hash = @"deadbeef" + }; + var beatmapSet = new BeatmapSetInfo(new[] + { + firstBeatmap, + secondBeatmap + }); + firstBeatmap.BeatmapSet = beatmapSet; + secondBeatmap.BeatmapSet = beatmapSet; + + metadataLookup.Update(beatmapSet, preferOnlineFetch); + + Assert.That(firstBeatmap.Status, Is.EqualTo(BeatmapOnlineStatus.Ranked)); + Assert.That(firstBeatmap.OnlineID, Is.EqualTo(654321)); + + Assert.That(secondBeatmap.Status, Is.EqualTo(BeatmapOnlineStatus.None)); + Assert.That(secondBeatmap.OnlineID, Is.EqualTo(666666)); + + Assert.That(beatmapSet.Status, Is.EqualTo(BeatmapOnlineStatus.None)); + } } } From 4f0ae4197a456dde07cf4aa9868026ef0d9d0918 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 12 Feb 2024 11:12:01 +0100 Subject: [PATCH 3002/4092] Refuse to apply online metadata in the most dodgy scenarios --- osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs index b32310990c..13823147b0 100644 --- a/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs +++ b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs @@ -43,7 +43,7 @@ namespace osu.Game.Beatmaps if (!tryLookup(beatmapInfo, preferOnlineFetch, out var res)) continue; - if (res == null) + if (res == null || shouldDiscardLookupResult(res, beatmapInfo)) { beatmapInfo.ResetOnlineInfo(); continue; @@ -72,6 +72,17 @@ namespace osu.Game.Beatmaps } } + private bool shouldDiscardLookupResult(OnlineBeatmapMetadata result, BeatmapInfo beatmapInfo) + { + if (beatmapInfo.OnlineID > 0 && result.BeatmapID != beatmapInfo.OnlineID) + return true; + + if (beatmapInfo.OnlineID == -1 && result.MD5Hash != beatmapInfo.MD5Hash) + return true; + + return false; + } + /// /// Attempts to retrieve the for the given . /// From 87702b331271d1291ad65d0b4399408dbbf5244e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 12 Feb 2024 11:35:11 +0100 Subject: [PATCH 3003/4092] Only check online matching when determining whether to save online metadata After https://github.com/ppy/osu/pull/23362 I'm not sure the `LocallyModified` check is doing anything useful. It was confusing me really hard when trying to parse this logic, and it can misbehave (as `None` will also pass the check). --- osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs index 13823147b0..f46ce11663 100644 --- a/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs +++ b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs @@ -57,13 +57,13 @@ namespace osu.Game.Beatmaps beatmapInfo.BeatmapSet.OnlineID = res.BeatmapSetID; // Some metadata should only be applied if there's no local changes. - if (shouldSaveOnlineMetadata(beatmapInfo)) + if (beatmapInfo.MatchesOnlineVersion) { beatmapInfo.Status = res.BeatmapStatus; beatmapInfo.Metadata.Author.OnlineID = res.AuthorID; } - if (beatmapInfo.BeatmapSet.Beatmaps.All(shouldSaveOnlineMetadata)) + if (beatmapInfo.BeatmapSet.Beatmaps.All(b => b.MatchesOnlineVersion)) { beatmapInfo.BeatmapSet.Status = res.BeatmapSetStatus ?? BeatmapOnlineStatus.None; beatmapInfo.BeatmapSet.DateRanked = res.DateRanked; @@ -115,12 +115,6 @@ namespace osu.Game.Beatmaps return false; } - /// - /// Check whether the provided beatmap is in a state where online "ranked" status metadata should be saved against it. - /// Handles the case where a user may have locally modified a beatmap in the editor and expects the local status to stick. - /// - private static bool shouldSaveOnlineMetadata(BeatmapInfo beatmapInfo) => beatmapInfo.MatchesOnlineVersion || beatmapInfo.Status != BeatmapOnlineStatus.LocallyModified; - public void Dispose() { apiMetadataSource.Dispose(); From 133e61a1b223156b986eb45c45fa7e1b16aefefc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 12 Feb 2024 11:38:13 +0100 Subject: [PATCH 3004/4092] Add another test for even more failure --- .../BeatmapUpdaterMetadataLookupTest.cs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs b/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs index fe9d15a89d..74812236bf 100644 --- a/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs +++ b/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs @@ -352,5 +352,58 @@ namespace osu.Game.Tests.Beatmaps Assert.That(beatmapSet.Status, Is.EqualTo(BeatmapOnlineStatus.None)); } + + [Test] + public void TestPartiallyMaliciousSet([Values] bool preferOnlineFetch) + { + var firstResult = new OnlineBeatmapMetadata + { + BeatmapID = 654321, + BeatmapStatus = BeatmapOnlineStatus.Ranked, + BeatmapSetStatus = BeatmapOnlineStatus.Ranked, + MD5Hash = @"cafebabe" + }; + var secondResult = new OnlineBeatmapMetadata + { + BeatmapStatus = BeatmapOnlineStatus.Ranked, + BeatmapSetStatus = BeatmapOnlineStatus.Ranked, + MD5Hash = @"dededede" + }; + + var targetMock = preferOnlineFetch ? apiMetadataSourceMock : localCachedMetadataSourceMock; + targetMock.Setup(src => src.Available).Returns(true); + targetMock.Setup(src => src.TryLookup(It.Is(bi => bi.OnlineID == 654321), out firstResult)) + .Returns(true); + targetMock.Setup(src => src.TryLookup(It.Is(bi => bi.OnlineID == 666666), out secondResult)) + .Returns(true); + + var firstBeatmap = new BeatmapInfo + { + OnlineID = 654321, + MD5Hash = @"cafebabe", + }; + var secondBeatmap = new BeatmapInfo + { + OnlineID = 666666, + MD5Hash = @"deadbeef" + }; + var beatmapSet = new BeatmapSetInfo(new[] + { + firstBeatmap, + secondBeatmap + }); + firstBeatmap.BeatmapSet = beatmapSet; + secondBeatmap.BeatmapSet = beatmapSet; + + metadataLookup.Update(beatmapSet, preferOnlineFetch); + + Assert.That(firstBeatmap.Status, Is.EqualTo(BeatmapOnlineStatus.Ranked)); + Assert.That(firstBeatmap.OnlineID, Is.EqualTo(654321)); + + Assert.That(secondBeatmap.Status, Is.EqualTo(BeatmapOnlineStatus.None)); + Assert.That(secondBeatmap.OnlineID, Is.EqualTo(-1)); + + Assert.That(beatmapSet.Status, Is.EqualTo(BeatmapOnlineStatus.None)); + } } } From 138fea8c387190605ba5099b9d3385f4bb6903ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 12 Feb 2024 11:47:30 +0100 Subject: [PATCH 3005/4092] Only apply set-level metadata after all difficulties have been processed --- .../Beatmaps/BeatmapUpdaterMetadataLookup.cs | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs index f46ce11663..f395718a93 100644 --- a/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs +++ b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.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.Platform; @@ -38,6 +39,8 @@ namespace osu.Game.Beatmaps /// Whether metadata from an online source should be preferred. If true, the local cache will be skipped to ensure the freshest data state possible. public void Update(BeatmapSetInfo beatmapSet, bool preferOnlineFetch) { + var lookupResults = new List(); + foreach (var beatmapInfo in beatmapSet.Beatmaps) { if (!tryLookup(beatmapInfo, preferOnlineFetch, out var res)) @@ -46,9 +49,12 @@ namespace osu.Game.Beatmaps if (res == null || shouldDiscardLookupResult(res, beatmapInfo)) { beatmapInfo.ResetOnlineInfo(); + lookupResults.Add(null); // mark lookup failure continue; } + lookupResults.Add(res); + beatmapInfo.OnlineID = res.BeatmapID; beatmapInfo.OnlineMD5Hash = res.MD5Hash; beatmapInfo.LastOnlineUpdate = res.LastUpdated; @@ -62,13 +68,17 @@ namespace osu.Game.Beatmaps beatmapInfo.Status = res.BeatmapStatus; beatmapInfo.Metadata.Author.OnlineID = res.AuthorID; } + } - if (beatmapInfo.BeatmapSet.Beatmaps.All(b => b.MatchesOnlineVersion)) - { - beatmapInfo.BeatmapSet.Status = res.BeatmapSetStatus ?? BeatmapOnlineStatus.None; - beatmapInfo.BeatmapSet.DateRanked = res.DateRanked; - beatmapInfo.BeatmapSet.DateSubmitted = res.DateSubmitted; - } + if (beatmapSet.Beatmaps.All(b => b.MatchesOnlineVersion) + && lookupResults.All(r => r != null) + && lookupResults.Select(r => r!.BeatmapSetID).Distinct().Count() == 1) + { + var representative = lookupResults.First()!; + + beatmapSet.Status = representative.BeatmapSetStatus ?? BeatmapOnlineStatus.None; + beatmapSet.DateRanked = representative.DateRanked; + beatmapSet.DateSubmitted = representative.DateSubmitted; } } From e5e0b0e38585f292ce1775f0d4016e9ed55ebec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 12 Feb 2024 11:55:30 +0100 Subject: [PATCH 3006/4092] Add test coverage for correct setting of beatmap set status --- .../BeatmapUpdaterMetadataLookupTest.cs | 41 ++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs b/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs index 74812236bf..11c4c54ea6 100644 --- a/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs +++ b/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs @@ -28,7 +28,12 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestLocalCacheQueriedFirst() { - var localLookupResult = new OnlineBeatmapMetadata { BeatmapID = 123456, BeatmapStatus = BeatmapOnlineStatus.Ranked }; + var localLookupResult = new OnlineBeatmapMetadata + { + BeatmapID = 123456, + BeatmapStatus = BeatmapOnlineStatus.Ranked, + BeatmapSetStatus = BeatmapOnlineStatus.Ranked, + }; localCachedMetadataSourceMock.Setup(src => src.Available).Returns(true); localCachedMetadataSourceMock.Setup(src => src.TryLookup(It.IsAny(), out localLookupResult)) .Returns(true); @@ -42,6 +47,7 @@ namespace osu.Game.Tests.Beatmaps metadataLookup.Update(beatmapSet, preferOnlineFetch: false); Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.Ranked)); + Assert.That(beatmapSet.Status, Is.EqualTo(BeatmapOnlineStatus.Ranked)); localCachedMetadataSourceMock.Verify(src => src.TryLookup(beatmap, out It.Ref.IsAny!), Times.Once); apiMetadataSourceMock.Verify(src => src.TryLookup(It.IsAny(), out It.Ref.IsAny!), Times.Never); } @@ -54,7 +60,12 @@ namespace osu.Game.Tests.Beatmaps localCachedMetadataSourceMock.Setup(src => src.TryLookup(It.IsAny(), out localLookupResult)) .Returns(false); - var onlineLookupResult = new OnlineBeatmapMetadata { BeatmapID = 123456, BeatmapStatus = BeatmapOnlineStatus.Ranked }; + var onlineLookupResult = new OnlineBeatmapMetadata + { + BeatmapID = 123456, + BeatmapStatus = BeatmapOnlineStatus.Ranked, + BeatmapSetStatus = BeatmapOnlineStatus.Ranked, + }; apiMetadataSourceMock.Setup(src => src.Available).Returns(true); apiMetadataSourceMock.Setup(src => src.TryLookup(It.IsAny(), out onlineLookupResult)) .Returns(true); @@ -66,6 +77,7 @@ namespace osu.Game.Tests.Beatmaps metadataLookup.Update(beatmapSet, preferOnlineFetch: false); Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.Ranked)); + Assert.That(beatmapSet.Status, Is.EqualTo(BeatmapOnlineStatus.Ranked)); localCachedMetadataSourceMock.Verify(src => src.TryLookup(beatmap, out It.Ref.IsAny!), Times.Once); apiMetadataSourceMock.Verify(src => src.TryLookup(beatmap, out It.Ref.IsAny!), Times.Once); } @@ -73,12 +85,22 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestPreferOnlineFetch() { - var localLookupResult = new OnlineBeatmapMetadata { BeatmapID = 123456, BeatmapStatus = BeatmapOnlineStatus.Ranked }; + var localLookupResult = new OnlineBeatmapMetadata + { + BeatmapID = 123456, + BeatmapStatus = BeatmapOnlineStatus.Ranked, + BeatmapSetStatus = BeatmapOnlineStatus.Ranked, + }; localCachedMetadataSourceMock.Setup(src => src.Available).Returns(true); localCachedMetadataSourceMock.Setup(src => src.TryLookup(It.IsAny(), out localLookupResult)) .Returns(true); - var onlineLookupResult = new OnlineBeatmapMetadata { BeatmapID = 123456, BeatmapStatus = BeatmapOnlineStatus.Graveyard }; + var onlineLookupResult = new OnlineBeatmapMetadata + { + BeatmapID = 123456, + BeatmapStatus = BeatmapOnlineStatus.Graveyard, + BeatmapSetStatus = BeatmapOnlineStatus.Graveyard, + }; apiMetadataSourceMock.Setup(src => src.Available).Returns(true); apiMetadataSourceMock.Setup(src => src.TryLookup(It.IsAny(), out onlineLookupResult)) .Returns(true); @@ -90,6 +112,7 @@ namespace osu.Game.Tests.Beatmaps metadataLookup.Update(beatmapSet, preferOnlineFetch: true); Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.Graveyard)); + Assert.That(beatmapSet.Status, Is.EqualTo(BeatmapOnlineStatus.Graveyard)); localCachedMetadataSourceMock.Verify(src => src.TryLookup(beatmap, out It.Ref.IsAny!), Times.Never); apiMetadataSourceMock.Verify(src => src.TryLookup(beatmap, out It.Ref.IsAny!), Times.Once); } @@ -97,7 +120,12 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestPreferOnlineFetchFallsBackToLocalCacheIfOnlineSourceUnavailable() { - var localLookupResult = new OnlineBeatmapMetadata { BeatmapID = 123456, BeatmapStatus = BeatmapOnlineStatus.Ranked }; + var localLookupResult = new OnlineBeatmapMetadata + { + BeatmapID = 123456, + BeatmapStatus = BeatmapOnlineStatus.Ranked, + BeatmapSetStatus = BeatmapOnlineStatus.Ranked, + }; localCachedMetadataSourceMock.Setup(src => src.Available).Returns(true); localCachedMetadataSourceMock.Setup(src => src.TryLookup(It.IsAny(), out localLookupResult)) .Returns(true); @@ -111,6 +139,7 @@ namespace osu.Game.Tests.Beatmaps metadataLookup.Update(beatmapSet, preferOnlineFetch: true); Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.Ranked)); + Assert.That(beatmapSet.Status, Is.EqualTo(BeatmapOnlineStatus.Ranked)); localCachedMetadataSourceMock.Verify(src => src.TryLookup(beatmap, out It.Ref.IsAny!), Times.Once); apiMetadataSourceMock.Verify(src => src.TryLookup(beatmap, out It.Ref.IsAny!), Times.Never); } @@ -135,6 +164,7 @@ namespace osu.Game.Tests.Beatmaps metadataLookup.Update(beatmapSet, preferOnlineFetch: false); Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.None)); + Assert.That(beatmapSet.Status, Is.EqualTo(BeatmapOnlineStatus.None)); Assert.That(beatmap.OnlineID, Is.EqualTo(-1)); localCachedMetadataSourceMock.Verify(src => src.TryLookup(beatmap, out It.Ref.IsAny!), Times.Once); apiMetadataSourceMock.Verify(src => src.TryLookup(beatmap, out It.Ref.IsAny!), Times.Once); @@ -163,6 +193,7 @@ namespace osu.Game.Tests.Beatmaps metadataLookup.Update(beatmapSet, preferOnlineFetch); Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.None)); + Assert.That(beatmapSet.Status, Is.EqualTo(BeatmapOnlineStatus.None)); Assert.That(beatmap.OnlineID, Is.EqualTo(123456)); } From 1944a12634817ea43888e8ffec7b6359e6499f44 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Mon, 12 Feb 2024 21:18:31 +0900 Subject: [PATCH 3007/4092] allow `ModMuted` to ranked when setting adjusted --- osu.Game/Rulesets/Mods/ModMuted.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModMuted.cs b/osu.Game/Rulesets/Mods/ModMuted.cs index 3ecd9aa6a1..7aefefc58d 100644 --- a/osu.Game/Rulesets/Mods/ModMuted.cs +++ b/osu.Game/Rulesets/Mods/ModMuted.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Mods public override LocalisableString Description => "Can you still feel the rhythm without music?"; public override ModType Type => ModType.Fun; public override double ScoreMultiplier => 1; - public override bool Ranked => UsesDefaultConfiguration; + public override bool Ranked => true; } public abstract class ModMuted : ModMuted, IApplicableToDrawableRuleset, IApplicableToTrack, IApplicableToScoreProcessor From 581ae2f679a00e98ea3e773a9c0c59494b9be8e5 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Mon, 12 Feb 2024 12:51:35 +0000 Subject: [PATCH 3008/4092] handle key presses when watching legacy relax replays --- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 24 +++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index 3679425389..55d8b6f55f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -38,12 +38,17 @@ namespace osu.Game.Rulesets.Osu.Mods private ReplayState state = null!; private double lastStateChangeTime; + private DrawableOsuRuleset ruleset = null!; + private bool hasReplay; + private bool legacyReplay; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { + ruleset = (DrawableOsuRuleset)drawableRuleset; + // grab the input manager for future use. - osuInputManager = ((DrawableOsuRuleset)drawableRuleset).KeyBindingInputManager; + osuInputManager = ruleset.KeyBindingInputManager; } public void ApplyToPlayer(Player player) @@ -51,6 +56,7 @@ namespace osu.Game.Rulesets.Osu.Mods if (osuInputManager.ReplayInputHandler != null) { hasReplay = true; + legacyReplay = ruleset.ReplayScore.ScoreInfo.IsLegacyScore; return; } @@ -59,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Mods public void Update(Playfield playfield) { - if (hasReplay) + if (hasReplay && !legacyReplay) return; bool requiresHold = false; @@ -125,6 +131,20 @@ namespace osu.Game.Rulesets.Osu.Mods isDownState = down; lastStateChangeTime = time; + // legacy replays do not contain key-presses with Relax mod, so they need to be triggered by themselves. + if (legacyReplay) + { + if (!down) + { + osuInputManager.KeyBindingContainer.TriggerReleased(wasLeft ? OsuAction.RightButton : OsuAction.LeftButton); + return; + } + + osuInputManager.KeyBindingContainer.TriggerPressed(wasLeft ? OsuAction.LeftButton : OsuAction.RightButton); + wasLeft = !wasLeft; + return; + } + state = new ReplayState { PressedActions = new List() From 96711eb185c0692868dc924b6b4368e8e1a8735d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 12 Feb 2024 18:02:34 +0100 Subject: [PATCH 3009/4092] Add test coverage --- .../NonVisual/Filtering/FilterQueryParserTest.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 739a72df08..899be1e06c 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -274,10 +274,12 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.IsTrue(filterCriteria.OnlineStatus.IsUpperInclusive); } - [Test] - public void TestApplyCreatorQueries() + [TestCase("creator")] + [TestCase("author")] + [TestCase("mapper")] + public void TestApplyCreatorQueries(string keyword) { - const string query = "beatmap specifically by creator=my_fav"; + string query = $"beatmap specifically by {keyword}=my_fav"; var filterCriteria = new FilterCriteria(); FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.AreEqual("beatmap specifically by", filterCriteria.SearchText.Trim()); From 2a02566283f831b469a2b37a4a645aae2cb07bc3 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Mon, 12 Feb 2024 17:45:00 +0000 Subject: [PATCH 3010/4092] refactor `down` and `wasLeft` management into respective `PressHandler` classes --- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 67 +++++++++++++++++------ 1 file changed, 51 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index 55d8b6f55f..47b7e543d8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -39,6 +39,7 @@ namespace osu.Game.Rulesets.Osu.Mods private double lastStateChangeTime; private DrawableOsuRuleset ruleset = null!; + private PressHandler pressHandler = null!; private bool hasReplay; private bool legacyReplay; @@ -56,10 +57,16 @@ namespace osu.Game.Rulesets.Osu.Mods if (osuInputManager.ReplayInputHandler != null) { hasReplay = true; + + Debug.Assert(ruleset.ReplayScore != null); legacyReplay = ruleset.ReplayScore.ScoreInfo.IsLegacyScore; + + pressHandler = new LegacyReplayPressHandler(this); + return; } + pressHandler = new PressHandler(this); osuInputManager.AllowGameplayInputs = false; } @@ -131,20 +138,6 @@ namespace osu.Game.Rulesets.Osu.Mods isDownState = down; lastStateChangeTime = time; - // legacy replays do not contain key-presses with Relax mod, so they need to be triggered by themselves. - if (legacyReplay) - { - if (!down) - { - osuInputManager.KeyBindingContainer.TriggerReleased(wasLeft ? OsuAction.RightButton : OsuAction.LeftButton); - return; - } - - osuInputManager.KeyBindingContainer.TriggerPressed(wasLeft ? OsuAction.LeftButton : OsuAction.RightButton); - wasLeft = !wasLeft; - return; - } - state = new ReplayState { PressedActions = new List() @@ -152,11 +145,53 @@ namespace osu.Game.Rulesets.Osu.Mods if (down) { - state.PressedActions.Add(wasLeft ? OsuAction.LeftButton : OsuAction.RightButton); + pressHandler.HandlePress(wasLeft); wasLeft = !wasLeft; } + else + { + pressHandler.HandleRelease(wasLeft); + } + } + } - state.Apply(osuInputManager.CurrentState, osuInputManager); + private class PressHandler + { + protected readonly OsuModRelax Mod; + + public PressHandler(OsuModRelax mod) + { + Mod = mod; + } + + public virtual void HandlePress(bool wasLeft) + { + Mod.state.PressedActions.Add(wasLeft ? OsuAction.LeftButton : OsuAction.RightButton); + Mod.state.Apply(Mod.osuInputManager.CurrentState, Mod.osuInputManager); + } + + public virtual void HandleRelease(bool wasLeft) + { + Mod.state.Apply(Mod.osuInputManager.CurrentState, Mod.osuInputManager); + } + } + + // legacy replays do not contain key-presses with Relax mod, so they need to be triggered by themselves. + private class LegacyReplayPressHandler : PressHandler + { + public LegacyReplayPressHandler(OsuModRelax mod) + : base(mod) + { + } + + public override void HandlePress(bool wasLeft) + { + Mod.osuInputManager.KeyBindingContainer.TriggerPressed(wasLeft ? OsuAction.LeftButton : OsuAction.RightButton); + } + + public override void HandleRelease(bool wasLeft) + { + Mod.osuInputManager.KeyBindingContainer.TriggerReleased(wasLeft ? OsuAction.RightButton : OsuAction.LeftButton); } } } From cc733ea809f3eb1d0887bf8c92605581fd21a9b3 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Mon, 12 Feb 2024 18:00:05 +0000 Subject: [PATCH 3011/4092] add inline comment for supposedly backwards ternary --- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index 47b7e543d8..a5643e5b49 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -191,6 +191,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override void HandleRelease(bool wasLeft) { + // this intentionally releases right when `wasLeft` is true because `wasLeft` is set at point of press and not at point of release Mod.osuInputManager.KeyBindingContainer.TriggerReleased(wasLeft ? OsuAction.RightButton : OsuAction.LeftButton); } } From 4f0f07d55a45be5ff93e271d3adfde3b71945138 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 12 Feb 2024 21:30:10 +0300 Subject: [PATCH 3012/4092] Remove placeholder classes and inline everything --- .../Graphics/Sprites/SpriteIconWithTooltip.cs | 14 +++++++ .../Graphics/Sprites/SpriteTextWithTooltip.cs | 13 +++++++ .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 20 ++++++++-- .../Scores/TopScoreStatisticsSection.cs | 19 +++++++-- .../Sections/Ranks/DrawableProfileScore.cs | 39 +++++++++++++++---- ...UnprocessedPerformancePointsPlaceholder.cs | 27 ------------- .../UnrankedPerformancePointsPlaceholder.cs | 26 ------------- 7 files changed, 91 insertions(+), 67 deletions(-) create mode 100644 osu.Game/Graphics/Sprites/SpriteIconWithTooltip.cs create mode 100644 osu.Game/Graphics/Sprites/SpriteTextWithTooltip.cs delete mode 100644 osu.Game/Scoring/Drawables/UnprocessedPerformancePointsPlaceholder.cs delete mode 100644 osu.Game/Scoring/Drawables/UnrankedPerformancePointsPlaceholder.cs diff --git a/osu.Game/Graphics/Sprites/SpriteIconWithTooltip.cs b/osu.Game/Graphics/Sprites/SpriteIconWithTooltip.cs new file mode 100644 index 0000000000..572a17571b --- /dev/null +++ b/osu.Game/Graphics/Sprites/SpriteIconWithTooltip.cs @@ -0,0 +1,14 @@ +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; + +namespace osu.Game.Graphics.Sprites +{ + /// + /// A with a publicly settable tooltip text. + /// + public partial class SpriteIconWithTooltip : SpriteIcon, IHasTooltip + { + public LocalisableString TooltipText { get; set; } + } +} diff --git a/osu.Game/Graphics/Sprites/SpriteTextWithTooltip.cs b/osu.Game/Graphics/Sprites/SpriteTextWithTooltip.cs new file mode 100644 index 0000000000..db98e8ba57 --- /dev/null +++ b/osu.Game/Graphics/Sprites/SpriteTextWithTooltip.cs @@ -0,0 +1,13 @@ +using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; + +namespace osu.Game.Graphics.Sprites +{ + /// + /// An with a publicly settable tooltip text. + /// + internal partial class SpriteTextWithTooltip : OsuSpriteText, IHasTooltip + { + public LocalisableString TooltipText { get; set; } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 9dd0e26da2..7a817c43eb 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -23,9 +23,9 @@ using osuTK.Graphics; using osu.Framework.Localisation; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets.Mods; -using osu.Game.Scoring.Drawables; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -181,9 +181,23 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (showPerformancePoints) { if (!score.Ranked) - content.Add(new UnrankedPerformancePointsPlaceholder(ScoresStrings.StatusNoPp) { Font = OsuFont.GetFont(size: text_size) }); + { + content.Add(new SpriteTextWithTooltip + { + Text = "-", + Font = OsuFont.GetFont(size: text_size), + TooltipText = ScoresStrings.StatusNoPp + }); + } else if (score.PP == null) - content.Add(new UnprocessedPerformancePointsPlaceholder { Size = new Vector2(text_size) }); + { + content.Add(new SpriteIconWithTooltip + { + Icon = FontAwesome.Solid.Sync, + Size = new Vector2(text_size), + TooltipText = ScoresStrings.StatusProcessing, + }); + } else content.Add(new StatisticText(score.PP, format: @"N0")); } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index 4aad3cf953..17704f63ee 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -22,7 +22,6 @@ using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using osu.Game.Scoring; -using osu.Game.Scoring.Drawables; using osuTK; namespace osu.Game.Overlays.BeatmapSet.Scores @@ -126,9 +125,23 @@ namespace osu.Game.Overlays.BeatmapSet.Scores ppColumn.Alpha = value.BeatmapInfo!.Status.GrantsPerformancePoints() ? 1 : 0; if (!value.Ranked) - ppColumn.Drawable = new UnrankedPerformancePointsPlaceholder(ScoresStrings.StatusNoPp) { Font = smallFont }; + { + ppColumn.Drawable = new SpriteTextWithTooltip + { + Text = "-", + Font = smallFont, + TooltipText = ScoresStrings.StatusNoPp + }; + } else if (value.PP is not double pp) - ppColumn.Drawable = new UnprocessedPerformancePointsPlaceholder { Size = new Vector2(smallFont.Size) }; + { + ppColumn.Drawable = new SpriteIconWithTooltip + { + Icon = FontAwesome.Solid.Sync, + Size = new Vector2(smallFont.Size), + TooltipText = ScoresStrings.StatusProcessing, + }; + } else ppColumn.Text = pp.ToLocalisableString(@"N0"); diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index 1211d65816..63afca8b74 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -8,6 +8,7 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -18,7 +19,6 @@ using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; -using osu.Game.Scoring.Drawables; using osu.Game.Utils; using osuTK; @@ -214,6 +214,8 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks private Drawable createDrawablePerformance() { + var font = OsuFont.GetFont(weight: FontWeight.Bold); + if (Score.PP.HasValue) { return new FillFlowContainer @@ -226,7 +228,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Font = OsuFont.GetFont(weight: FontWeight.Bold), + Font = font, Text = $"{Score.PP:0}", Colour = colourProvider.Highlight1 }, @@ -234,7 +236,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Font = font.With(size: 12), Text = "pp", Colour = colourProvider.Light3 } @@ -244,23 +246,44 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks if (Score.Beatmap?.Status.GrantsPerformancePoints() != true) { - return new UnrankedPerformancePointsPlaceholder(UsersStrings.ShowExtraTopRanksNotRanked) + if (Score.Beatmap?.Status == BeatmapOnlineStatus.Loved) { + return new SpriteIconWithTooltip + { + Icon = FontAwesome.Solid.Heart, + Size = new Vector2(font.Size), + TooltipText = UsersStrings.ShowExtraTopRanksNotRanked, + Colour = colourProvider.Highlight1 + }; + } + + return new SpriteTextWithTooltip + { + Text = "-", Font = OsuFont.GetFont(weight: FontWeight.Bold), - Colour = colourProvider.Highlight1, + TooltipText = UsersStrings.ShowExtraTopRanksNotRanked, + Colour = colourProvider.Highlight1 }; } if (!Score.Ranked) { - return new UnrankedPerformancePointsPlaceholder(ScoresStrings.StatusNoPp) + return new SpriteTextWithTooltip { + Text = "-", Font = OsuFont.GetFont(weight: FontWeight.Bold), - Colour = colourProvider.Highlight1, + TooltipText = ScoresStrings.StatusNoPp, + Colour = colourProvider.Highlight1 }; } - return new UnprocessedPerformancePointsPlaceholder { Size = new Vector2(16), Colour = colourProvider.Highlight1 }; + return new SpriteIconWithTooltip + { + Icon = FontAwesome.Solid.Sync, + Size = new Vector2(font.Size), + TooltipText = ScoresStrings.StatusProcessing, + Colour = colourProvider.Highlight1 + }; } private partial class ScoreBeatmapMetadataContainer : BeatmapMetadataContainer diff --git a/osu.Game/Scoring/Drawables/UnprocessedPerformancePointsPlaceholder.cs b/osu.Game/Scoring/Drawables/UnprocessedPerformancePointsPlaceholder.cs deleted file mode 100644 index a2cb69062e..0000000000 --- a/osu.Game/Scoring/Drawables/UnprocessedPerformancePointsPlaceholder.cs +++ /dev/null @@ -1,27 +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 osu.Framework.Graphics; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Localisation; -using osu.Game.Resources.Localisation.Web; - -namespace osu.Game.Scoring.Drawables -{ - /// - /// A placeholder used in PP columns for scores with unprocessed PP value. - /// - public partial class UnprocessedPerformancePointsPlaceholder : SpriteIcon, IHasTooltip - { - public LocalisableString TooltipText => ScoresStrings.StatusProcessing; - - public UnprocessedPerformancePointsPlaceholder() - { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - Icon = FontAwesome.Solid.Sync; - } - } -} diff --git a/osu.Game/Scoring/Drawables/UnrankedPerformancePointsPlaceholder.cs b/osu.Game/Scoring/Drawables/UnrankedPerformancePointsPlaceholder.cs deleted file mode 100644 index 0097497ef1..0000000000 --- a/osu.Game/Scoring/Drawables/UnrankedPerformancePointsPlaceholder.cs +++ /dev/null @@ -1,26 +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.Framework.Graphics; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Localisation; - -namespace osu.Game.Scoring.Drawables -{ - /// - /// A placeholder used in PP columns for scores that do not award PP due to a reason specified by . - /// - public partial class UnrankedPerformancePointsPlaceholder : SpriteText, IHasTooltip - { - public LocalisableString TooltipText { get; } - - public UnrankedPerformancePointsPlaceholder(LocalisableString tooltipText) - { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - Text = "-"; - TooltipText = tooltipText; - } - } -} From 5bebe9fe0d5c5a5110efd1cd145e5dcb93cd92af Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 12 Feb 2024 21:33:16 +0300 Subject: [PATCH 3013/4092] Add test case for profile scores made on loved beatmaps --- .../Online/TestSceneUserProfileScores.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs index f72980757b..56e4348b65 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs @@ -102,6 +102,24 @@ namespace osu.Game.Tests.Visual.Online Ranked = true, }; + var lovedScore = new SoloScoreInfo + { + Rank = ScoreRank.B, + Beatmap = new APIBeatmap + { + BeatmapSet = new APIBeatmapSet + { + Title = "C18H27NO3(extend)", + Artist = "Team Grimoire", + }, + DifficultyName = "[4K] Cataclysmic Hypernova", + Status = BeatmapOnlineStatus.Loved, + }, + EndedAt = DateTimeOffset.Now, + Accuracy = 0.55879, + Ranked = true, + }; + var unprocessedPPScore = new SoloScoreInfo { Rank = ScoreRank.B, @@ -151,6 +169,7 @@ namespace osu.Game.Tests.Visual.Online new ColourProvidedContainer(OverlayColourScheme.Green, new DrawableProfileScore(firstScore)), new ColourProvidedContainer(OverlayColourScheme.Green, new DrawableProfileScore(secondScore)), new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileScore(noPPScore)), + new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileScore(lovedScore)), new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileScore(unprocessedPPScore)), new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileScore(unrankedPPScore)), new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(firstScore, 0.97)), From 6402f23f0245ecac17f82f1a9e5f06b9bd7898b2 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 12 Feb 2024 21:00:15 +0200 Subject: [PATCH 3014/4092] Added Traceable support for pp --- .../Difficulty/OsuPerformanceCalculator.cs | 12 ++++++++++++ osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 1 + 2 files changed, 13 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index b31f4ff519..4771bce280 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -116,6 +116,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. aimValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); } + else if (score.Mods.Any(h => h is OsuModTraceable)) + { + // Default 2% increase and another is scaled by AR + aimValue *= 1.02 + 0.02 * (12.0 - attributes.ApproachRate); + } // We assume 15% of sliders in a map are difficult since there's no way to tell from the performance calculator. double estimateDifficultSliders = attributes.SliderCount * 0.15; @@ -167,6 +172,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. speedValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); } + else if (score.Mods.Any(h => h is OsuModTraceable)) + { + // More reward for speed because speed on Traceable is annoying + speedValue *= 1.04 + 0.06 * (12.0 - attributes.ApproachRate); + } // Calculate accuracy assuming the worst case scenario double relevantTotalDiff = totalHits - attributes.SpeedNoteCount; @@ -214,6 +224,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty accuracyValue *= 1.14; else if (score.Mods.Any(m => m is OsuModHidden)) accuracyValue *= 1.08; + else if (score.Mods.Any(m => m is OsuModTraceable)) + accuracyValue *= 1.02 + 0.01 * (12.0 - attributes.ApproachRate); if (score.Mods.Any(m => m is OsuModFlashlight)) accuracyValue *= 1.02; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index 9671f53bea..320c0a7040 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -19,6 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Fun; public override LocalisableString Description => "Put your faith in the approach circles..."; public override double ScoreMultiplier => 1; + public override bool Ranked => UsesDefaultConfiguration; public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles), typeof(OsuModDepth) }; From 2d65dfbf09f1e2c139862a649d363e0c12d37030 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 12 Feb 2024 22:10:36 +0300 Subject: [PATCH 3015/4092] Fix Rider EAP whoopsie --- osu.Game/Graphics/Sprites/SpriteIconWithTooltip.cs | 3 +++ osu.Game/Graphics/Sprites/SpriteTextWithTooltip.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/osu.Game/Graphics/Sprites/SpriteIconWithTooltip.cs b/osu.Game/Graphics/Sprites/SpriteIconWithTooltip.cs index 572a17571b..17f4bf53f9 100644 --- a/osu.Game/Graphics/Sprites/SpriteIconWithTooltip.cs +++ b/osu.Game/Graphics/Sprites/SpriteIconWithTooltip.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; diff --git a/osu.Game/Graphics/Sprites/SpriteTextWithTooltip.cs b/osu.Game/Graphics/Sprites/SpriteTextWithTooltip.cs index db98e8ba57..446b621b81 100644 --- a/osu.Game/Graphics/Sprites/SpriteTextWithTooltip.cs +++ b/osu.Game/Graphics/Sprites/SpriteTextWithTooltip.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using osu.Framework.Graphics.Cursor; using osu.Framework.Localisation; From 56391550096a19486e06d98a20e1f5bb1421c090 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 12 Feb 2024 23:31:00 +0200 Subject: [PATCH 3016/4092] Update OsuModTraceable.cs --- osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index 320c0a7040..9671f53bea 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -19,7 +19,6 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Fun; public override LocalisableString Description => "Put your faith in the approach circles..."; public override double ScoreMultiplier => 1; - public override bool Ranked => UsesDefaultConfiguration; public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles), typeof(OsuModDepth) }; From 5101979ac099b7e590e020020bf3b0a7bf134164 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Tue, 13 Feb 2024 00:34:06 +0000 Subject: [PATCH 3017/4092] only use `LegacyReplayPressHandler` on legacy replays --- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index a5643e5b49..d2e4e0c669 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Mods Debug.Assert(ruleset.ReplayScore != null); legacyReplay = ruleset.ReplayScore.ScoreInfo.IsLegacyScore; - pressHandler = new LegacyReplayPressHandler(this); + pressHandler = legacyReplay ? new LegacyReplayPressHandler(this) : new PressHandler(this); return; } From 22e9c4a3b59e414a881ceae5abc885389a0af5b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 13 Feb 2024 10:19:55 +0100 Subject: [PATCH 3018/4092] Use private interface rather than weird inheritance --- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 38 ++++++++++++++--------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index d2e4e0c669..31511c01b8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Mods private double lastStateChangeTime; private DrawableOsuRuleset ruleset = null!; - private PressHandler pressHandler = null!; + private IPressHandler pressHandler = null!; private bool hasReplay; private bool legacyReplay; @@ -155,44 +155,52 @@ namespace osu.Game.Rulesets.Osu.Mods } } - private class PressHandler + private interface IPressHandler { - protected readonly OsuModRelax Mod; + void HandlePress(bool wasLeft); + void HandleRelease(bool wasLeft); + } + + private class PressHandler : IPressHandler + { + private readonly OsuModRelax mod; public PressHandler(OsuModRelax mod) { - Mod = mod; + this.mod = mod; } - public virtual void HandlePress(bool wasLeft) + public void HandlePress(bool wasLeft) { - Mod.state.PressedActions.Add(wasLeft ? OsuAction.LeftButton : OsuAction.RightButton); - Mod.state.Apply(Mod.osuInputManager.CurrentState, Mod.osuInputManager); + mod.state.PressedActions.Add(wasLeft ? OsuAction.LeftButton : OsuAction.RightButton); + mod.state.Apply(mod.osuInputManager.CurrentState, mod.osuInputManager); } - public virtual void HandleRelease(bool wasLeft) + public void HandleRelease(bool wasLeft) { - Mod.state.Apply(Mod.osuInputManager.CurrentState, Mod.osuInputManager); + mod.state.Apply(mod.osuInputManager.CurrentState, mod.osuInputManager); } } // legacy replays do not contain key-presses with Relax mod, so they need to be triggered by themselves. - private class LegacyReplayPressHandler : PressHandler + private class LegacyReplayPressHandler : IPressHandler { + private readonly OsuModRelax mod; + public LegacyReplayPressHandler(OsuModRelax mod) - : base(mod) { + this.mod = mod; } - public override void HandlePress(bool wasLeft) + public void HandlePress(bool wasLeft) { - Mod.osuInputManager.KeyBindingContainer.TriggerPressed(wasLeft ? OsuAction.LeftButton : OsuAction.RightButton); + mod.osuInputManager.KeyBindingContainer.TriggerPressed(wasLeft ? OsuAction.LeftButton : OsuAction.RightButton); } - public override void HandleRelease(bool wasLeft) + public void HandleRelease(bool wasLeft) { // this intentionally releases right when `wasLeft` is true because `wasLeft` is set at point of press and not at point of release - Mod.osuInputManager.KeyBindingContainer.TriggerReleased(wasLeft ? OsuAction.RightButton : OsuAction.LeftButton); + mod.osuInputManager.KeyBindingContainer.TriggerReleased(wasLeft ? OsuAction.RightButton : OsuAction.LeftButton); } } } From 1eb04c5190220e97b901ab989985709f1553fa22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 13 Feb 2024 11:24:31 +0100 Subject: [PATCH 3019/4092] Add failing test coverage for catch --- .../CatchHealthProcessorTest.cs | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 osu.Game.Rulesets.Catch.Tests/CatchHealthProcessorTest.cs diff --git a/osu.Game.Rulesets.Catch.Tests/CatchHealthProcessorTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchHealthProcessorTest.cs new file mode 100644 index 0000000000..8c2d1a91ab --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/CatchHealthProcessorTest.cs @@ -0,0 +1,58 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Rulesets.Catch.Beatmaps; +using osu.Game.Rulesets.Catch.Judgements; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.Scoring; + +namespace osu.Game.Rulesets.Catch.Tests +{ + [TestFixture] + public class CatchHealthProcessorTest + { + private static readonly object[][] test_cases = + [ + // hitobject, starting HP, fail expected after miss + [new Fruit(), 0.01, true], + [new Droplet(), 0.01, true], + [new TinyDroplet(), 0, true], + [new Banana(), 0, false], + ]; + + [TestCaseSource(nameof(test_cases))] + public void TestFailAfterMinResult(CatchHitObject hitObject, double startingHealth, bool failExpected) + { + var healthProcessor = new CatchHealthProcessor(0); + healthProcessor.ApplyBeatmap(new CatchBeatmap + { + HitObjects = { hitObject } + }); + healthProcessor.Health.Value = startingHealth; + + var result = new CatchJudgementResult(hitObject, hitObject.CreateJudgement()); + result.Type = result.Judgement.MinResult; + healthProcessor.ApplyResult(result); + + Assert.That(healthProcessor.HasFailed, Is.EqualTo(failExpected)); + } + + [TestCaseSource(nameof(test_cases))] + public void TestNoFailAfterMaxResult(CatchHitObject hitObject, double startingHealth, bool _) + { + var healthProcessor = new CatchHealthProcessor(0); + healthProcessor.ApplyBeatmap(new CatchBeatmap + { + HitObjects = { hitObject } + }); + healthProcessor.Health.Value = startingHealth; + + var result = new CatchJudgementResult(hitObject, hitObject.CreateJudgement()); + result.Type = result.Judgement.MaxResult; + healthProcessor.ApplyResult(result); + + Assert.That(healthProcessor.HasFailed, Is.False); + } + } +} From 86801aa51d9716228f24fb4728607f95f2ed8f7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 13 Feb 2024 11:57:42 +0100 Subject: [PATCH 3020/4092] Add failing test coverage for osu! --- .../OsuHealthProcessorTest.cs | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/OsuHealthProcessorTest.cs diff --git a/osu.Game.Rulesets.Osu.Tests/OsuHealthProcessorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuHealthProcessorTest.cs new file mode 100644 index 0000000000..cf93e0ce7b --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/OsuHealthProcessorTest.cs @@ -0,0 +1,66 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Scoring; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class OsuHealthProcessorTest + { + private static readonly object[][] test_cases = + [ + // hitobject, starting HP, fail expected after miss + [new HitCircle(), 0.01, true], + [new SliderHeadCircle(), 0.01, true], + [new SliderHeadCircle { ClassicSliderBehaviour = true }, 0.01, true], + [new SliderTick(), 0.01, true], + [new SliderRepeat(new Slider()), 0.01, true], + [new SliderTailCircle(new Slider()), 0, true], + [new SliderTailCircle(new Slider()) { ClassicSliderBehaviour = true }, 0.01, true], + [new Slider(), 0, true], + [new Slider { ClassicSliderBehaviour = true }, 0.01, true], + [new SpinnerTick(), 0, false], + [new SpinnerBonusTick(), 0, false], + [new Spinner(), 0.01, true], + ]; + + [TestCaseSource(nameof(test_cases))] + public void TestFailAfterMinResult(OsuHitObject hitObject, double startingHealth, bool failExpected) + { + var healthProcessor = new OsuHealthProcessor(0); + healthProcessor.ApplyBeatmap(new OsuBeatmap + { + HitObjects = { hitObject } + }); + healthProcessor.Health.Value = startingHealth; + + var result = new OsuJudgementResult(hitObject, hitObject.CreateJudgement()); + result.Type = result.Judgement.MinResult; + healthProcessor.ApplyResult(result); + + Assert.That(healthProcessor.HasFailed, Is.EqualTo(failExpected)); + } + + [TestCaseSource(nameof(test_cases))] + public void TestNoFailAfterMaxResult(OsuHitObject hitObject, double startingHealth, bool _) + { + var healthProcessor = new OsuHealthProcessor(0); + healthProcessor.ApplyBeatmap(new OsuBeatmap + { + HitObjects = { hitObject } + }); + healthProcessor.Health.Value = startingHealth; + + var result = new OsuJudgementResult(hitObject, hitObject.CreateJudgement()); + result.Type = result.Judgement.MaxResult; + healthProcessor.ApplyResult(result); + + Assert.That(healthProcessor.HasFailed, Is.False); + } + } +} From 441a7b3c2ff68fc13fefb439698e4605f9adacc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 13 Feb 2024 12:07:40 +0100 Subject: [PATCH 3021/4092] Add precautionary taiko test coverage --- .../TaikoHealthProcessorTest.cs | 82 ++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoHealthProcessorTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoHealthProcessorTest.cs index f4a1e888c9..aba967cdd6 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoHealthProcessorTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoHealthProcessorTest.cs @@ -147,7 +147,7 @@ namespace osu.Game.Rulesets.Taiko.Tests { HitObjects = { - new DrumRoll { Duration = 2000 } + new Swell { Duration = 2000 } } }; @@ -172,5 +172,85 @@ namespace osu.Game.Rulesets.Taiko.Tests Assert.That(healthProcessor.HasFailed, Is.False); }); } + + [Test] + public void TestMissHitAndHitSwell() + { + var beatmap = new TaikoBeatmap + { + HitObjects = + { + new Hit(), + new Swell { Duration = 2000 } + } + }; + + foreach (var ho in beatmap.HitObjects) + ho.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty); + + var healthProcessor = new TaikoHealthProcessor(); + healthProcessor.ApplyBeatmap(beatmap); + + healthProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new TaikoJudgement()) { Type = HitResult.Miss }); + + foreach (var nested in beatmap.HitObjects[1].NestedHitObjects) + { + var nestedJudgement = nested.CreateJudgement(); + healthProcessor.ApplyResult(new JudgementResult(nested, nestedJudgement) { Type = nestedJudgement.MaxResult }); + } + + var judgement = beatmap.HitObjects[1].CreateJudgement(); + healthProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], judgement) { Type = judgement.MaxResult }); + + Assert.Multiple(() => + { + Assert.That(healthProcessor.Health.Value, Is.EqualTo(0)); + Assert.That(healthProcessor.HasFailed, Is.True); + }); + } + + private static readonly object[][] test_cases = + [ + // hitobject, fail expected after miss + [new Hit(), true], + [new Hit.StrongNestedHit(new Hit()), false], + [new DrumRollTick(new DrumRoll()), false], + [new DrumRollTick.StrongNestedHit(new DrumRollTick(new DrumRoll())), false], + [new DrumRoll(), false], + [new SwellTick(), false], + [new Swell(), false] + ]; + + [TestCaseSource(nameof(test_cases))] + public void TestFailAfterMinResult(TaikoHitObject hitObject, bool failExpected) + { + var healthProcessor = new TaikoHealthProcessor(); + healthProcessor.ApplyBeatmap(new TaikoBeatmap + { + HitObjects = { hitObject } + }); + + var result = new JudgementResult(hitObject, hitObject.CreateJudgement()); + result.Type = result.Judgement.MinResult; + healthProcessor.ApplyResult(result); + + Assert.That(healthProcessor.HasFailed, Is.EqualTo(failExpected)); + } + + [TestCaseSource(nameof(test_cases))] + public void TestNoFailAfterMaxResult(TaikoHitObject hitObject, bool _) + { + var healthProcessor = new TaikoHealthProcessor(); + healthProcessor.ApplyBeatmap(new TaikoBeatmap + { + HitObjects = { hitObject } + }); + + var result = new JudgementResult(hitObject, hitObject.CreateJudgement()); + result.Type = result.Judgement.MaxResult; + healthProcessor.ApplyResult(result); + + Assert.That(healthProcessor.HasFailed, Is.False); + } } } From d07ea8f5b12886883a60cf9c477fa9d632404ff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 13 Feb 2024 12:17:38 +0100 Subject: [PATCH 3022/4092] Add failing test coverage for mania --- .../ManiaHealthProcessorTest.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaHealthProcessorTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaHealthProcessorTest.cs index 315849f7de..a9771a46f3 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaHealthProcessorTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaHealthProcessorTest.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Scoring; @@ -27,5 +28,49 @@ namespace osu.Game.Rulesets.Mania.Tests // No matter what, mania doesn't have passive HP drain. Assert.That(processor.DrainRate, Is.Zero); } + + private static readonly object[][] test_cases = + [ + // hitobject, starting HP, fail expected after miss + [new Note(), 0.01, true], + [new HeadNote(), 0.01, true], + [new TailNote(), 0.01, true], + [new HoldNoteBody(), 0, true], // hold note break + [new HoldNote(), 0, true], + ]; + + [TestCaseSource(nameof(test_cases))] + public void TestFailAfterMinResult(ManiaHitObject hitObject, double startingHealth, bool failExpected) + { + var healthProcessor = new ManiaHealthProcessor(0); + healthProcessor.ApplyBeatmap(new ManiaBeatmap(new StageDefinition(4)) + { + HitObjects = { hitObject } + }); + healthProcessor.Health.Value = startingHealth; + + var result = new JudgementResult(hitObject, hitObject.CreateJudgement()); + result.Type = result.Judgement.MinResult; + healthProcessor.ApplyResult(result); + + Assert.That(healthProcessor.HasFailed, Is.EqualTo(failExpected)); + } + + [TestCaseSource(nameof(test_cases))] + public void TestNoFailAfterMaxResult(ManiaHitObject hitObject, double startingHealth, bool _) + { + var healthProcessor = new ManiaHealthProcessor(0); + healthProcessor.ApplyBeatmap(new ManiaBeatmap(new StageDefinition(4)) + { + HitObjects = { hitObject } + }); + healthProcessor.Health.Value = startingHealth; + + var result = new JudgementResult(hitObject, hitObject.CreateJudgement()); + result.Type = result.Judgement.MaxResult; + healthProcessor.ApplyResult(result); + + Assert.That(healthProcessor.HasFailed, Is.False); + } } } From 16d893d40c10542a2502884df95d54773044ffb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 13 Feb 2024 12:26:37 +0100 Subject: [PATCH 3023/4092] Fix draining processor failing gameplay on bonus misses and ignore hits --- osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs | 5 +++++ osu.Game/Rulesets/Scoring/HealthProcessor.cs | 9 ++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs index 629a84ea62..92a064385b 100644 --- a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs @@ -142,6 +142,11 @@ namespace osu.Game.Rulesets.Scoring } } + protected override bool CanFailOn(JudgementResult result) + { + return !result.Judgement.MaxResult.IsBonus() && result.Type != HitResult.IgnoreHit; + } + protected override void Reset(bool storeResults) { base.Reset(storeResults); diff --git a/osu.Game/Rulesets/Scoring/HealthProcessor.cs b/osu.Game/Rulesets/Scoring/HealthProcessor.cs index b5eb755650..ccf53f075a 100644 --- a/osu.Game/Rulesets/Scoring/HealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/HealthProcessor.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Scoring Health.Value += GetHealthIncreaseFor(result); - if (meetsAnyFailCondition(result)) + if (CanFailOn(result) && meetsAnyFailCondition(result)) TriggerFailure(); } @@ -68,6 +68,13 @@ namespace osu.Game.Rulesets.Scoring /// The health increase. protected virtual double GetHealthIncreaseFor(JudgementResult result) => result.HealthIncrease; + /// + /// Whether a failure can occur on a given . + /// If the return value of this method is , neither nor will be checked + /// after this . + /// + protected virtual bool CanFailOn(JudgementResult result) => true; + /// /// The default conditions for failing. /// From da4ebd0681e05318d7328fc5be587151233e8f44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 9 Feb 2024 10:41:36 +0100 Subject: [PATCH 3024/4092] Refactor `SoloStatisticsWatcher` to no longer require explicit subscription callbacks --- .../Online/TestSceneSoloStatisticsWatcher.cs | 41 ++++--------- osu.Game/Online/Solo/SoloStatisticsWatcher.cs | 59 +++++-------------- .../TransientUserStatisticsUpdateDisplay.cs | 10 ++++ osu.Game/Screens/Play/SubmittingPlayer.cs | 5 ++ osu.Game/Screens/Ranking/SoloResultsScreen.cs | 24 ++++---- 5 files changed, 56 insertions(+), 83 deletions(-) create mode 100644 osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs b/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs index 3607b37c7e..19121b7f58 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs @@ -35,8 +35,6 @@ namespace osu.Game.Tests.Visual.Online private Action? handleGetUsersRequest; private Action? handleGetUserRequest; - private IDisposable? subscription; - private readonly Dictionary<(int userId, string rulesetName), UserStatistics> serverSideStatistics = new Dictionary<(int userId, string rulesetName), UserStatistics>(); [SetUpSteps] @@ -252,26 +250,6 @@ namespace osu.Game.Tests.Visual.Online AddAssert("values after are correct", () => update!.After.TotalScore, () => Is.EqualTo(6_000_000)); } - [Test] - public void TestStatisticsUpdateNotFiredAfterSubscriptionDisposal() - { - int userId = getUserId(); - setUpUser(userId); - - long scoreId = getScoreId(); - var ruleset = new OsuRuleset().RulesetInfo; - - SoloStatisticsUpdate? update = null; - registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate); - AddStep("unsubscribe", () => subscription!.Dispose()); - - feignScoreProcessing(userId, ruleset, 5_000_000); - - AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId)); - AddWaitStep("wait a bit", 5); - AddAssert("update not received", () => update == null); - } - [Test] public void TestGlobalStatisticsUpdatedAfterRegistrationAddedAndScoreProcessed() { @@ -312,13 +290,20 @@ namespace osu.Game.Tests.Visual.Online } private void registerForUpdates(long scoreId, RulesetInfo rulesetInfo, Action onUpdateReady) => - AddStep("register for updates", () => subscription = watcher.RegisterForStatisticsUpdateAfter( - new ScoreInfo(Beatmap.Value.BeatmapInfo, new OsuRuleset().RulesetInfo, new RealmUser()) + AddStep("register for updates", () => + { + watcher.RegisterForStatisticsUpdateAfter( + new ScoreInfo(Beatmap.Value.BeatmapInfo, new OsuRuleset().RulesetInfo, new RealmUser()) + { + Ruleset = rulesetInfo, + OnlineID = scoreId + }); + watcher.LatestUpdate.BindValueChanged(update => { - Ruleset = rulesetInfo, - OnlineID = scoreId - }, - onUpdateReady)); + if (update.NewValue?.Score.OnlineID == scoreId) + onUpdateReady.Invoke(update.NewValue); + }); + }); private void feignScoreProcessing(int userId, RulesetInfo rulesetInfo, long newTotalScore) => AddStep("feign score processing", () => serverSideStatistics[(userId, rulesetInfo.ShortName)] = new UserStatistics { TotalScore = newTotalScore }); diff --git a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs index 55b27fb364..2072e8633f 100644 --- a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs +++ b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs @@ -1,10 +1,10 @@ // 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.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Game.Extensions; @@ -22,14 +22,16 @@ namespace osu.Game.Online.Solo /// public partial class SoloStatisticsWatcher : Component { + public IBindable LatestUpdate => latestUpdate; + private readonly Bindable latestUpdate = new Bindable(); + [Resolved] private SpectatorClient spectatorClient { get; set; } = null!; [Resolved] private IAPIProvider api { get; set; } = null!; - private readonly Dictionary callbacks = new Dictionary(); - private long? lastProcessedScoreId; + private readonly Dictionary watchedScores = new Dictionary(); private Dictionary? latestStatistics; @@ -45,9 +47,7 @@ namespace osu.Game.Online.Solo /// Registers for a user statistics update after the given has been processed server-side. /// /// The score to listen for the statistics update for. - /// The callback to be invoked once the statistics update has been prepared. - /// An representing the subscription. Disposing it is equivalent to unsubscribing from future notifications. - public IDisposable RegisterForStatisticsUpdateAfter(ScoreInfo score, Action onUpdateReady) + public void RegisterForStatisticsUpdateAfter(ScoreInfo score) { Schedule(() => { @@ -57,24 +57,12 @@ namespace osu.Game.Online.Solo if (!score.Ruleset.IsLegacyRuleset() || score.OnlineID <= 0) return; - var callback = new StatisticsUpdateCallback(score, onUpdateReady); - - if (lastProcessedScoreId == score.OnlineID) - { - requestStatisticsUpdate(api.LocalUser.Value.Id, callback); - return; - } - - callbacks.Add(score.OnlineID, callback); + watchedScores.Add(score.OnlineID, score); }); - - return new InvokeOnDisposal(() => Schedule(() => callbacks.Remove(score.OnlineID))); } private void onUserChanged(APIUser? localUser) => Schedule(() => { - callbacks.Clear(); - lastProcessedScoreId = null; latestStatistics = null; if (localUser == null || localUser.OnlineID <= 1) @@ -107,25 +95,22 @@ namespace osu.Game.Online.Solo if (userId != api.LocalUser.Value?.OnlineID) return; - lastProcessedScoreId = scoreId; - - if (!callbacks.TryGetValue(scoreId, out var callback)) + if (!watchedScores.Remove(scoreId, out var scoreInfo)) return; - requestStatisticsUpdate(userId, callback); - callbacks.Remove(scoreId); + requestStatisticsUpdate(userId, scoreInfo); } - private void requestStatisticsUpdate(int userId, StatisticsUpdateCallback callback) + private void requestStatisticsUpdate(int userId, ScoreInfo scoreInfo) { - var request = new GetUserRequest(userId, callback.Score.Ruleset); - request.Success += user => Schedule(() => dispatchStatisticsUpdate(callback, user.Statistics)); + var request = new GetUserRequest(userId, scoreInfo.Ruleset); + request.Success += user => Schedule(() => dispatchStatisticsUpdate(scoreInfo, user.Statistics)); api.Queue(request); } - private void dispatchStatisticsUpdate(StatisticsUpdateCallback callback, UserStatistics updatedStatistics) + private void dispatchStatisticsUpdate(ScoreInfo scoreInfo, UserStatistics updatedStatistics) { - string rulesetName = callback.Score.Ruleset.ShortName; + string rulesetName = scoreInfo.Ruleset.ShortName; api.UpdateStatistics(updatedStatistics); @@ -135,9 +120,7 @@ namespace osu.Game.Online.Solo latestStatistics.TryGetValue(rulesetName, out UserStatistics? latestRulesetStatistics); latestRulesetStatistics ??= new UserStatistics(); - var update = new SoloStatisticsUpdate(callback.Score, latestRulesetStatistics, updatedStatistics); - callback.OnUpdateReady.Invoke(update); - + latestUpdate.Value = new SoloStatisticsUpdate(scoreInfo, latestRulesetStatistics, updatedStatistics); latestStatistics[rulesetName] = updatedStatistics; } @@ -148,17 +131,5 @@ namespace osu.Game.Online.Solo base.Dispose(isDisposing); } - - private class StatisticsUpdateCallback - { - public ScoreInfo Score { get; } - public Action OnUpdateReady { get; } - - public StatisticsUpdateCallback(ScoreInfo score, Action onUpdateReady) - { - Score = score; - OnUpdateReady = onUpdateReady; - } - } } } diff --git a/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs b/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs new file mode 100644 index 0000000000..3917933958 --- /dev/null +++ b/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs @@ -0,0 +1,10 @@ +// 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.Overlays.Toolbar +{ + public class TransientUserStatisticsUpdateDisplay + { + + } +} diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index c8e84f1961..bbd36c05d8 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -17,6 +17,7 @@ using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; +using osu.Game.Online.Solo; using osu.Game.Online.Spectator; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -42,6 +43,9 @@ namespace osu.Game.Screens.Play [Resolved] private SessionStatics statics { get; set; } + [Resolved] + private SoloStatisticsWatcher soloStatisticsWatcher { get; set; } + private readonly object scoreSubmissionLock = new object(); private TaskCompletionSource scoreSubmissionSource; @@ -175,6 +179,7 @@ namespace osu.Game.Screens.Play await submitScore(score).ConfigureAwait(false); spectatorClient.EndPlaying(GameplayState); + soloStatisticsWatcher.RegisterForStatisticsUpdateAfter(score.ScoreInfo); } [Resolved] diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index 22d631e137..cba2fa18c0 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -31,10 +31,7 @@ namespace osu.Game.Screens.Ranking [Resolved] private RulesetStore rulesets { get; set; } = null!; - [Resolved] - private SoloStatisticsWatcher soloStatisticsWatcher { get; set; } = null!; - - private IDisposable? statisticsSubscription; + private IBindable latestUpdate = null!; private readonly Bindable statisticsUpdate = new Bindable(); public SoloResultsScreen(ScoreInfo score, bool allowRetry) @@ -42,14 +39,20 @@ namespace osu.Game.Screens.Ranking { } - protected override void LoadComplete() + [BackgroundDependencyLoader] + private void load(SoloStatisticsWatcher soloStatisticsWatcher) { - base.LoadComplete(); - - Debug.Assert(Score != null); - if (ShowUserStatistics) - statisticsSubscription = soloStatisticsWatcher.RegisterForStatisticsUpdateAfter(Score, update => statisticsUpdate.Value = update); + { + Debug.Assert(Score != null); + + latestUpdate = soloStatisticsWatcher.LatestUpdate.GetBoundCopy(); + latestUpdate.BindValueChanged(update => + { + if (update.NewValue?.Score.MatchesOnlineID(Score) == true) + statisticsUpdate.Value = update.NewValue; + }); + } } protected override StatisticsPanel CreateStatisticsPanel() @@ -84,7 +87,6 @@ namespace osu.Game.Screens.Ranking base.Dispose(isDisposing); getScoreRequest?.Cancel(); - statisticsSubscription?.Dispose(); } } } From 21b9fb95e28c6c4f7e27467ac301727a00469758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 9 Feb 2024 15:19:38 +0100 Subject: [PATCH 3025/4092] Move `SoloStatisticsWatcher` to `OsuGame` Doesn't feel like it needs to be in base, and it being in base was causing problems elsewhere before. --- osu.Game/OsuGame.cs | 2 ++ osu.Game/OsuGameBase.cs | 4 ---- osu.Game/Screens/Play/SubmittingPlayer.cs | 5 +++-- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 4 ++-- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index c244708385..640096a5a8 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -47,6 +47,7 @@ using osu.Game.Localisation; using osu.Game.Online; using osu.Game.Online.Chat; using osu.Game.Online.Rooms; +using osu.Game.Online.Solo; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapListing; using osu.Game.Overlays.Music; @@ -1015,6 +1016,7 @@ namespace osu.Game ScreenStack.Push(CreateLoader().With(l => l.RelativeSizeAxes = Axes.Both)); }); + loadComponentSingleFile(new SoloStatisticsWatcher(), Add, true); loadComponentSingleFile(Toolbar = new Toolbar { OnHome = delegate diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index a2a6322665..81e3d8bed8 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -50,7 +50,6 @@ using osu.Game.Online.API; using osu.Game.Online.Chat; using osu.Game.Online.Metadata; using osu.Game.Online.Multiplayer; -using osu.Game.Online.Solo; using osu.Game.Online.Spectator; using osu.Game.Overlays; using osu.Game.Overlays.Settings; @@ -207,7 +206,6 @@ namespace osu.Game protected MultiplayerClient MultiplayerClient { get; private set; } private MetadataClient metadataClient; - private SoloStatisticsWatcher soloStatisticsWatcher; private RealmAccess realm; @@ -328,7 +326,6 @@ namespace osu.Game dependencies.CacheAs(SpectatorClient = new OnlineSpectatorClient(endpoints)); dependencies.CacheAs(MultiplayerClient = new OnlineMultiplayerClient(endpoints)); dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints)); - dependencies.CacheAs(soloStatisticsWatcher = new SoloStatisticsWatcher()); base.Content.Add(new BeatmapOnlineChangeIngest(beatmapUpdater, realm, metadataClient)); @@ -371,7 +368,6 @@ namespace osu.Game base.Content.Add(SpectatorClient); base.Content.Add(MultiplayerClient); base.Content.Add(metadataClient); - base.Content.Add(soloStatisticsWatcher); base.Content.Add(rulesetConfigCache); diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index bbd36c05d8..c45d46e993 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -43,7 +43,8 @@ namespace osu.Game.Screens.Play [Resolved] private SessionStatics statics { get; set; } - [Resolved] + [Resolved(canBeNull: true)] + [CanBeNull] private SoloStatisticsWatcher soloStatisticsWatcher { get; set; } private readonly object scoreSubmissionLock = new object(); @@ -179,7 +180,7 @@ namespace osu.Game.Screens.Play await submitScore(score).ConfigureAwait(false); spectatorClient.EndPlaying(GameplayState); - soloStatisticsWatcher.RegisterForStatisticsUpdateAfter(score.ScoreInfo); + soloStatisticsWatcher?.RegisterForStatisticsUpdateAfter(score.ScoreInfo); } [Resolved] diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index cba2fa18c0..866440bbd6 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -40,9 +40,9 @@ namespace osu.Game.Screens.Ranking } [BackgroundDependencyLoader] - private void load(SoloStatisticsWatcher soloStatisticsWatcher) + private void load(SoloStatisticsWatcher? soloStatisticsWatcher) { - if (ShowUserStatistics) + if (ShowUserStatistics && soloStatisticsWatcher != null) { Debug.Assert(Score != null); From 14052ae1cc15c45cb81f60fd20341dfcfea7d429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 9 Feb 2024 17:14:15 +0100 Subject: [PATCH 3026/4092] Implement transient stats display on user toolbar button --- .../Menus/TestSceneToolbarUserButton.cs | 91 ++++++++ .../Overlays/Toolbar/ToolbarUserButton.cs | 7 + .../TransientUserStatisticsUpdateDisplay.cs | 215 +++++++++++++++++- 3 files changed, 311 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs index f0506ed35c..69fedf4a3a 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs @@ -2,12 +2,17 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; using osu.Game.Online.API; +using osu.Game.Online.Solo; using osu.Game.Overlays.Toolbar; +using osu.Game.Scoring; +using osu.Game.Users; using osuTK; using osuTK.Graphics; @@ -87,5 +92,91 @@ namespace osu.Game.Tests.Visual.Menus AddStep($"Change state to {state}", () => dummyAPI.SetState(state)); } } + + [Test] + public void TestTransientUserStatisticsDisplay() + { + AddStep("Log in", () => dummyAPI.Login("wang", "jang")); + AddStep("Gain", () => + { + var transientUpdateDisplay = this.ChildrenOfType().Single(); + transientUpdateDisplay.LatestUpdate.Value = new SoloStatisticsUpdate( + new ScoreInfo(), + new UserStatistics + { + GlobalRank = 123_456, + PP = 1234 + }, + new UserStatistics + { + GlobalRank = 111_111, + PP = 1357 + }); + }); + AddStep("Loss", () => + { + var transientUpdateDisplay = this.ChildrenOfType().Single(); + transientUpdateDisplay.LatestUpdate.Value = new SoloStatisticsUpdate( + new ScoreInfo(), + new UserStatistics + { + GlobalRank = 111_111, + PP = 1357 + }, + new UserStatistics + { + GlobalRank = 123_456, + PP = 1234 + }); + }); + AddStep("No change", () => + { + var transientUpdateDisplay = this.ChildrenOfType().Single(); + transientUpdateDisplay.LatestUpdate.Value = new SoloStatisticsUpdate( + new ScoreInfo(), + new UserStatistics + { + GlobalRank = 111_111, + PP = 1357 + }, + new UserStatistics + { + GlobalRank = 111_111, + PP = 1357 + }); + }); + AddStep("Was null", () => + { + var transientUpdateDisplay = this.ChildrenOfType().Single(); + transientUpdateDisplay.LatestUpdate.Value = new SoloStatisticsUpdate( + new ScoreInfo(), + new UserStatistics + { + GlobalRank = null, + PP = null + }, + new UserStatistics + { + GlobalRank = 111_111, + PP = 1357 + }); + }); + AddStep("Became null", () => + { + var transientUpdateDisplay = this.ChildrenOfType().Single(); + transientUpdateDisplay.LatestUpdate.Value = new SoloStatisticsUpdate( + new ScoreInfo(), + new UserStatistics + { + GlobalRank = 111_111, + PP = 1357 + }, + new UserStatistics + { + GlobalRank = null, + PP = null + }); + }); + } } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs index 2620e850c8..96c0b15c44 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs @@ -78,6 +78,13 @@ namespace osu.Game.Overlays.Toolbar } }); + Flow.Add(new TransientUserStatisticsUpdateDisplay + { + Alpha = 0 + }); + Flow.AutoSizeEasing = Easing.OutQuint; + Flow.AutoSizeDuration = 250; + apiState = api.State.GetBoundCopy(); apiState.BindValueChanged(onlineStateChanged, true); diff --git a/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs b/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs index 3917933958..9070ea9030 100644 --- a/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs +++ b/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs @@ -1,10 +1,221 @@ // 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.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Solo; +using osu.Game.Resources.Localisation.Web; +using osuTK; + namespace osu.Game.Overlays.Toolbar { - public class TransientUserStatisticsUpdateDisplay + public partial class TransientUserStatisticsUpdateDisplay : CompositeDrawable { - + public Bindable LatestUpdate { get; } = new Bindable(); + + private Statistic globalRank = null!; + private Statistic pp = null!; + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.Y; + AutoSizeAxes = Axes.X; + Alpha = 0; + + InternalChild = new FillFlowContainer + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Padding = new MarginPadding { Horizontal = 10 }, + Spacing = new Vector2(10), + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + globalRank = new Statistic(UsersStrings.ShowRankGlobalSimple, @"#", Comparer.Create((before, after) => before - after)), + pp = new Statistic(RankingsStrings.StatPerformance, string.Empty, Comparer.Create((before, after) => Math.Sign(after - before))), + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + LatestUpdate.BindValueChanged(val => + { + if (val.NewValue == null) + return; + + var update = val.NewValue; + + // null handling here is best effort because it is annoying. + + globalRank.Alpha = update.After.GlobalRank == null ? 0 : 1; + pp.Alpha = update.After.PP == null ? 0 : 1; + + if (globalRank.Alpha == 0 && pp.Alpha == 0) + return; + + FinishTransforms(true); + + this.FadeIn(500, Easing.OutQuint); + + if (update.After.GlobalRank != null) + { + globalRank.Display( + update.Before.GlobalRank ?? update.After.GlobalRank.Value, + Math.Abs((update.After.GlobalRank.Value - update.Before.GlobalRank) ?? 0), + update.After.GlobalRank.Value); + } + + if (update.After.PP != null) + pp.Display(update.Before.PP ?? update.After.PP.Value, Math.Abs((update.After.PP - update.Before.PP) ?? 0M), update.After.PP.Value); + + this.Delay(4000).FadeOut(500, Easing.OutQuint); + }); + } + + private partial class Statistic : CompositeDrawable + where T : struct, IEquatable, IFormattable + { + private readonly LocalisableString title; + private readonly string mainValuePrefix; + private readonly IComparer valueComparer; + + private Counter mainValue = null!; + private Counter deltaValue = null!; + private OsuSpriteText titleText = null!; + + [Resolved] + private OsuColour colours { get; set; } = null!; + + public Statistic(LocalisableString title, string mainValuePrefix, IComparer valueComparer) + { + this.title = title; + this.mainValuePrefix = mainValuePrefix; + this.valueComparer = valueComparer; + } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.Y; + AutoSizeAxes = Axes.X; + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + mainValue = new Counter + { + ValuePrefix = mainValuePrefix, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Font = OsuFont.GetFont(), + }, + new Container + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Children = new Drawable[] + { + deltaValue = new Counter + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Font = OsuFont.GetFont(size: 12, fixedWidth: false, weight: FontWeight.SemiBold), + AlwaysPresent = true, + }, + titleText = new OsuSpriteText + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Font = OsuFont.Default.With(size: 12, weight: FontWeight.SemiBold), + Text = title, + AlwaysPresent = true, + } + } + } + } + }; + } + + public void Display(T before, T delta, T after) + { + int comparison = valueComparer.Compare(before, after); + + if (comparison > 0) + { + deltaValue.Colour = colours.Lime1; + deltaValue.ValuePrefix = "+"; + } + else if (comparison < 0) + { + deltaValue.Colour = colours.Red1; + deltaValue.ValuePrefix = "-"; + } + else + { + deltaValue.Colour = Colour4.White; + deltaValue.ValuePrefix = string.Empty; + } + + mainValue.SetCountWithoutRolling(before); + deltaValue.SetCountWithoutRolling(delta); + + titleText.Alpha = 1; + deltaValue.Alpha = 0; + + using (BeginDelayedSequence(1000)) + { + titleText.FadeOut(250, Easing.OutQuint); + deltaValue.FadeIn(250, Easing.OutQuint) + .Then().Delay(1000) + .Then().OnComplete(_ => + { + mainValue.Current.Value = after; + deltaValue.Current.SetDefault(); + }); + } + } + } + + private partial class Counter : RollingCounter + where T : struct, IEquatable, IFormattable + { + public const double ROLLING_DURATION = 500; + + public FontUsage Font { get; init; } = OsuFont.Default; + + public string ValuePrefix + { + get => valuePrefix; + set + { + valuePrefix = value; + UpdateDisplay(); + } + } + + private string valuePrefix = string.Empty; + + protected override LocalisableString FormatCount(T count) => LocalisableString.Format(@"{0}{1:N0}", ValuePrefix, count); + protected override OsuSpriteText CreateSpriteText() => base.CreateSpriteText().With(t => t.Font = Font); + protected override double RollingDuration => ROLLING_DURATION; + } } } From eae43f5fd90b5aec6a698253c9f82cb6e3f878e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 9 Feb 2024 17:15:52 +0100 Subject: [PATCH 3027/4092] Consume `SoloStatisticsWatcher` updates in toolbar button --- .../Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs | 5 ++++- osu.Game/Screens/Ranking/ResultsScreen.cs | 3 --- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs b/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs index 9070ea9030..e3c1746e14 100644 --- a/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs +++ b/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs @@ -26,7 +26,7 @@ namespace osu.Game.Overlays.Toolbar private Statistic pp = null!; [BackgroundDependencyLoader] - private void load() + private void load(SoloStatisticsWatcher? soloStatisticsWatcher) { RelativeSizeAxes = Axes.Y; AutoSizeAxes = Axes.X; @@ -45,6 +45,9 @@ namespace osu.Game.Overlays.Toolbar pp = new Statistic(RankingsStrings.StatPerformance, string.Empty, Comparer.Create((before, after) => Math.Sign(after - before))), } }; + + if (soloStatisticsWatcher != null) + ((IBindable)LatestUpdate).BindTo(soloStatisticsWatcher.LatestUpdate); } protected override void LoadComplete() diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 82dade40eb..69cfbed8f2 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -41,9 +41,6 @@ namespace osu.Game.Screens.Ranking public override bool? AllowGlobalTrackControl => true; - // Temporary for now to stop dual transitions. Should respect the current toolbar mode, but there's no way to do so currently. - public override bool HideOverlaysOnEnter => true; - public readonly Bindable SelectedScore = new Bindable(); [CanBeNull] From 62f5251b6ef8eef5f0b1c638621aa0258379fb17 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 13 Feb 2024 11:51:21 +0300 Subject: [PATCH 3028/4092] Rewrite osu!catch playfield layout containers and apply masking around vertical boundaries --- .../UI/CatchPlayfieldAdjustmentContainer.cs | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs index 11531011ee..1bddd06d87 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs @@ -17,24 +17,29 @@ namespace osu.Game.Rulesets.Catch.UI public CatchPlayfieldAdjustmentContainer() { - Anchor = Anchor.TopCentre; - Origin = Anchor.TopCentre; - - // playfields in stable are positioned vertically at three fourths the difference between the playfield height and the window height in stable. - // we can match that in lazer by using relative coordinates for Y and considering window height to be 1, and playfield height to be 0.8. - RelativePositionAxes = Axes.Y; - Y = (1 - playfield_size_adjust) / 4 * 3; - - Size = new Vector2(playfield_size_adjust); + const float base_game_width = 1024f; + const float base_game_height = 768f; InternalChild = new Container { + // This container limits vertical visibility of the playfield to ensure fairness between wide and tall resolutions (i.e. tall resolutions should not see more fruits). + // Note that the container still extends across the screen horizontally, so that hit explosions at the sides of the playfield do not get cut off. + Name = "Visible area", Anchor = Anchor.Centre, Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - FillAspectRatio = 4f / 3, - Child = content = new ScalingContainer { RelativeSizeAxes = Axes.Both, } + RelativeSizeAxes = Axes.X, + Height = base_game_height, + Masking = true, + Child = new Container + { + Name = "Playable area", + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + // playfields in stable are positioned vertically at three fourths the difference between the playfield height and the window height in stable. + Y = base_game_height * ((1 - playfield_size_adjust) / 4 * 3), + Size = new Vector2(base_game_width, base_game_height) * playfield_size_adjust, + Child = content = new ScalingContainer { RelativeSizeAxes = Axes.Both } + }, }; } From 9b17020707a5dd53e45211fd0e5de9a682abf16e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 14 Feb 2024 01:03:21 +0300 Subject: [PATCH 3029/4092] Adjust "Floating Fruits" in line with layout changes --- osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs | 1 - .../UI/CatchPlayfieldAdjustmentContainer.cs | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs index 9d88c90576..f933b9a28f 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs @@ -21,7 +21,6 @@ namespace osu.Game.Rulesets.Catch.Mods public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { drawableRuleset.PlayfieldAdjustmentContainer.Scale = new Vector2(1, -1); - drawableRuleset.PlayfieldAdjustmentContainer.Y = 1 - drawableRuleset.PlayfieldAdjustmentContainer.Y; } } } diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs index 1bddd06d87..64d17b08b6 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs @@ -20,6 +20,9 @@ namespace osu.Game.Rulesets.Catch.UI const float base_game_width = 1024f; const float base_game_height = 768f; + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + InternalChild = new Container { // This container limits vertical visibility of the playfield to ensure fairness between wide and tall resolutions (i.e. tall resolutions should not see more fruits). From a96a66bc9e071f0b8bc2771f194965a23cc62d95 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 14 Feb 2024 02:04:19 +0300 Subject: [PATCH 3030/4092] Add failing test case --- .../Navigation/TestSceneScreenNavigation.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index f59fbc75ac..8ff4fd5ecf 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -986,6 +986,29 @@ namespace osu.Game.Tests.Visual.Navigation } } + [Test] + public void TestPresentBeatmapAfterDeletion() + { + BeatmapSetInfo beatmap = null; + + Screens.Select.SongSelect songSelect = null; + PushAndConfirm(() => songSelect = new TestPlaySongSelect()); + AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); + + AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); + AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + + AddStep("delete selected beatmap", () => + { + beatmap = Game.Beatmap.Value.BeatmapSetInfo; + Game.BeatmapManager.Delete(Game.Beatmap.Value.BeatmapSetInfo); + }); + + AddUntilStep("nothing selected", () => Game.Beatmap.IsDefault); + AddStep("present deleted beatmap", () => Game.PresentBeatmap(beatmap)); + AddAssert("still nothing selected", () => Game.Beatmap.IsDefault); + } + private Func playToResults() { var player = playToCompletion(); From 35649d137ca9fea993c473b7b08d26f53ba13441 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 14 Feb 2024 01:59:45 +0300 Subject: [PATCH 3031/4092] Ignore soft-deleted beatmaps when trying to present from notification --- osu.Game/OsuGame.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index c244708385..11798c22ff 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -630,6 +630,12 @@ namespace osu.Game var detachedSet = databasedSet.PerformRead(s => s.Detach()); + if (detachedSet.DeletePending) + { + Logger.Log("The requested beatmap has since been deleted.", LoggingTarget.Information); + return; + } + PerformFromScreen(screen => { // Find beatmaps that match our predicate. From 5267e0abf788da62e6eaaa1fed1ac35be0fcb428 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 14 Feb 2024 03:38:49 +0300 Subject: [PATCH 3032/4092] Move comment author line to separate component --- .../Overlays/Comments/CommentAuthorLine.cs | 135 ++++++++++++++++++ osu.Game/Overlays/Comments/DrawableComment.cs | 102 +------------ 2 files changed, 139 insertions(+), 98 deletions(-) create mode 100644 osu.Game/Overlays/Comments/CommentAuthorLine.cs diff --git a/osu.Game/Overlays/Comments/CommentAuthorLine.cs b/osu.Game/Overlays/Comments/CommentAuthorLine.cs new file mode 100644 index 0000000000..b6b5dc00e1 --- /dev/null +++ b/osu.Game/Overlays/Comments/CommentAuthorLine.cs @@ -0,0 +1,135 @@ +// 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 osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Resources.Localisation.Web; +using osuTK; + +namespace osu.Game.Overlays.Comments +{ + public partial class CommentAuthorLine : FillFlowContainer + { + private readonly Comment comment; + + private OsuSpriteText deletedLabel = null!; + + public CommentAuthorLine(Comment comment) + { + this.comment = comment; + } + + [BackgroundDependencyLoader] + private void load() + { + AutoSizeAxes = Axes.Both; + Direction = FillDirection.Horizontal; + Spacing = new Vector2(4, 0); + + Add(new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold)) + { + AutoSizeAxes = Axes.Both + }.With(username => + { + if (comment.UserId.HasValue) + username.AddUserLink(comment.User); + else + username.AddText(comment.LegacyName!); + })); + + if (comment.Pinned) + Add(new PinnedCommentNotice()); + + Add(new ParentUsername(comment)); + + Add(deletedLabel = new OsuSpriteText + { + Alpha = 0f, + Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold), + Text = CommentsStrings.Deleted + }); + } + + public void MarkDeleted() + { + deletedLabel.Show(); + } + + private partial class PinnedCommentNotice : FillFlowContainer + { + public PinnedCommentNotice() + { + AutoSizeAxes = Axes.Both; + Direction = FillDirection.Horizontal; + Spacing = new Vector2(2, 0); + Children = new Drawable[] + { + new SpriteIcon + { + Icon = FontAwesome.Solid.Thumbtack, + Size = new Vector2(14), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold), + Text = CommentsStrings.Pinned, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + } + }; + } + } + + private partial class ParentUsername : FillFlowContainer, IHasTooltip + { + public LocalisableString TooltipText => getParentMessage(); + + private readonly Comment? parentComment; + + public ParentUsername(Comment comment) + { + parentComment = comment.ParentComment; + + AutoSizeAxes = Axes.Both; + Direction = FillDirection.Horizontal; + Spacing = new Vector2(3, 0); + Alpha = comment.ParentId == null ? 0 : 1; + Children = new Drawable[] + { + new SpriteIcon + { + Icon = FontAwesome.Solid.Reply, + Size = new Vector2(14), + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true), + Text = parentComment?.User?.Username ?? parentComment?.LegacyName! + } + }; + } + + private LocalisableString getParentMessage() + { + if (parentComment == null) + return string.Empty; + + return parentComment.HasMessage ? parentComment.Message : parentComment.IsDeleted ? CommentsStrings.Deleted : string.Empty; + } + } + } +} diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index ceae17aa5d..70b1809c3e 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -4,12 +4,10 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using osu.Game.Graphics; -using osu.Framework.Graphics.Sprites; using osuTK; using osu.Game.Online.API.Requests.Responses; using osu.Game.Users.Drawables; using osu.Game.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.Bindables; using System.Linq; using osu.Game.Graphics.Sprites; @@ -21,7 +19,6 @@ using osu.Framework.Extensions.IEnumerableExtensions; using System.Collections.Specialized; using System.Diagnostics; using osu.Framework.Extensions.LocalisationExtensions; -using osu.Framework.Localisation; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Graphics.UserInterface; @@ -72,7 +69,7 @@ namespace osu.Game.Overlays.Comments private LinkFlowContainer actionsContainer = null!; private LoadingSpinner actionsLoading = null!; private DeletedCommentsCounter deletedCommentsCounter = null!; - private OsuSpriteText deletedLabel = null!; + private CommentAuthorLine author = null!; private GridContainer content = null!; private VotePill votePill = null!; private Container replyEditorContainer = null!; @@ -98,7 +95,6 @@ namespace osu.Game.Overlays.Comments [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider, DrawableComment? parentComment) { - LinkFlowContainer username; FillFlowContainer info; CommentMarkdownContainer message; @@ -174,27 +170,7 @@ namespace osu.Game.Overlays.Comments }, Children = new Drawable[] { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), - Children = new[] - { - username = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold)) - { - AutoSizeAxes = Axes.Both - }, - Comment.Pinned ? new PinnedCommentNotice() : Empty(), - new ParentUsername(Comment), - deletedLabel = new OsuSpriteText - { - Alpha = 0f, - Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold), - Text = CommentsStrings.Deleted - } - } - }, + author = new CommentAuthorLine(Comment), message = new CommentMarkdownContainer { RelativeSizeAxes = Axes.X, @@ -218,7 +194,7 @@ namespace osu.Game.Overlays.Comments { new DrawableDate(Comment.CreatedAt, 12, false) { - Colour = colourProvider.Foreground1 + Colour = colourProvider.Foreground1, } } }, @@ -311,11 +287,6 @@ namespace osu.Game.Overlays.Comments } }; - if (Comment.UserId.HasValue) - username.AddUserLink(Comment.User); - else - username.AddText(Comment.LegacyName!); - if (Comment.EditedAt.HasValue && Comment.EditedUser != null) { var font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular); @@ -400,7 +371,7 @@ namespace osu.Game.Overlays.Comments /// private void makeDeleted() { - deletedLabel.Show(); + author.MarkDeleted(); content.FadeColour(OsuColour.Gray(0.5f)); votePill.Hide(); actionsContainer.Expire(); @@ -547,70 +518,5 @@ namespace osu.Game.Overlays.Comments Top = 10 }; } - - private partial class PinnedCommentNotice : FillFlowContainer - { - public PinnedCommentNotice() - { - AutoSizeAxes = Axes.Both; - Direction = FillDirection.Horizontal; - Spacing = new Vector2(2, 0); - Children = new Drawable[] - { - new SpriteIcon - { - Icon = FontAwesome.Solid.Thumbtack, - Size = new Vector2(14), - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold), - Text = CommentsStrings.Pinned, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - } - }; - } - } - - private partial class ParentUsername : FillFlowContainer, IHasTooltip - { - public LocalisableString TooltipText => getParentMessage(); - - private readonly Comment? parentComment; - - public ParentUsername(Comment comment) - { - parentComment = comment.ParentComment; - - AutoSizeAxes = Axes.Both; - Direction = FillDirection.Horizontal; - Spacing = new Vector2(3, 0); - Alpha = comment.ParentId == null ? 0 : 1; - Children = new Drawable[] - { - new SpriteIcon - { - Icon = FontAwesome.Solid.Reply, - Size = new Vector2(14), - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true), - Text = parentComment?.User?.Username ?? parentComment?.LegacyName! - } - }; - } - - private LocalisableString getParentMessage() - { - if (parentComment == null) - return string.Empty; - - return parentComment.HasMessage ? parentComment.Message : parentComment.IsDeleted ? CommentsStrings.Deleted : string.Empty; - } - } } } From c4e358044a5f0478119c39309dd7525483847413 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 14 Feb 2024 03:44:32 +0300 Subject: [PATCH 3033/4092] Add API models for comment page metadata --- .../API/Requests/Responses/CommentBundle.cs | 3 ++ .../API/Requests/Responses/CommentableMeta.cs | 28 +++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 osu.Game/Online/API/Requests/Responses/CommentableMeta.cs diff --git a/osu.Game/Online/API/Requests/Responses/CommentBundle.cs b/osu.Game/Online/API/Requests/Responses/CommentBundle.cs index ae8b850723..cbff8bf76c 100644 --- a/osu.Game/Online/API/Requests/Responses/CommentBundle.cs +++ b/osu.Game/Online/API/Requests/Responses/CommentBundle.cs @@ -11,6 +11,9 @@ namespace osu.Game.Online.API.Requests.Responses { public class CommentBundle { + [JsonProperty(@"commentable_meta")] + public List CommentableMeta { get; set; } = new List(); + [JsonProperty(@"comments")] public List Comments { get; set; } diff --git a/osu.Game/Online/API/Requests/Responses/CommentableMeta.cs b/osu.Game/Online/API/Requests/Responses/CommentableMeta.cs new file mode 100644 index 0000000000..1084f1c900 --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/CommentableMeta.cs @@ -0,0 +1,28 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class CommentableMeta + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("owner_id")] + public long? OwnerId { get; set; } + + [JsonProperty("owner_title")] + public string? OwnerTitle { get; set; } + + [JsonProperty("title")] + public string Title { get; set; } = string.Empty; + + [JsonProperty("type")] + public string Type { get; set; } = string.Empty; + + [JsonProperty("url")] + public string Url { get; set; } = string.Empty; + } +} From 72c6134dbff17ba8a7a2ee82a741a61412bdfa1d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 14 Feb 2024 03:46:19 +0300 Subject: [PATCH 3034/4092] Include commentable object metadata in comments --- osu.Game/Overlays/Comments/CommentsContainer.cs | 8 ++++---- osu.Game/Overlays/Comments/DrawableComment.cs | 4 +++- osu.Game/Overlays/Comments/ReplyCommentEditor.cs | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index b4e9a80ff1..2e5f13aa99 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -301,7 +301,7 @@ namespace osu.Game.Overlays.Comments void addNewComment(Comment comment) { - var drawableComment = GetDrawableComment(comment); + var drawableComment = GetDrawableComment(comment, bundle.CommentableMeta); if (comment.ParentId == null) { @@ -333,7 +333,7 @@ namespace osu.Game.Overlays.Comments if (CommentDictionary.ContainsKey(comment.Id)) continue; - topLevelComments.Add(GetDrawableComment(comment)); + topLevelComments.Add(GetDrawableComment(comment, bundle.CommentableMeta)); } if (topLevelComments.Any()) @@ -351,12 +351,12 @@ namespace osu.Game.Overlays.Comments } } - public DrawableComment GetDrawableComment(Comment comment) + public DrawableComment GetDrawableComment(Comment comment, IReadOnlyList meta) { if (CommentDictionary.TryGetValue(comment.Id, out var existing)) return existing; - return CommentDictionary[comment.Id] = new DrawableComment(comment) + return CommentDictionary[comment.Id] = new DrawableComment(comment, meta) { ShowDeleted = { BindTarget = ShowDeleted }, Sort = { BindTarget = Sort }, diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 70b1809c3e..afb8bdcc8b 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -39,6 +39,7 @@ namespace osu.Game.Overlays.Comments public Action RepliesRequested = null!; public readonly Comment Comment; + public readonly IReadOnlyList Meta; public readonly BindableBool ShowDeleted = new BindableBool(); public readonly Bindable Sort = new Bindable(); @@ -87,9 +88,10 @@ namespace osu.Game.Overlays.Comments [Resolved] private OnScreenDisplay? onScreenDisplay { get; set; } - public DrawableComment(Comment comment) + public DrawableComment(Comment comment, IReadOnlyList meta) { Comment = comment; + Meta = meta; } [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/Comments/ReplyCommentEditor.cs b/osu.Game/Overlays/Comments/ReplyCommentEditor.cs index dd4c35ef20..8e9e82507d 100644 --- a/osu.Game/Overlays/Comments/ReplyCommentEditor.cs +++ b/osu.Game/Overlays/Comments/ReplyCommentEditor.cs @@ -60,7 +60,7 @@ namespace osu.Game.Overlays.Comments foreach (var comment in cb.Comments) comment.ParentComment = parentComment; - var drawables = cb.Comments.Select(commentsContainer.GetDrawableComment).ToArray(); + var drawables = cb.Comments.Select(c => commentsContainer.GetDrawableComment(c, cb.CommentableMeta)).ToArray(); OnPost?.Invoke(drawables); OnCancel!.Invoke(); From 4d3b605e04d73ff69031d2d4c97ddcd937cb043f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 14 Feb 2024 03:48:45 +0300 Subject: [PATCH 3035/4092] Add support for displaying "mapper" badges in comments --- .../Overlays/Comments/CommentAuthorLine.cs | 49 ++++++++++++++++++- osu.Game/Overlays/Comments/DrawableComment.cs | 2 +- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Comments/CommentAuthorLine.cs b/osu.Game/Overlays/Comments/CommentAuthorLine.cs index b6b5dc00e1..c269ab4c01 100644 --- a/osu.Game/Overlays/Comments/CommentAuthorLine.cs +++ b/osu.Game/Overlays/Comments/CommentAuthorLine.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; @@ -23,12 +22,14 @@ namespace osu.Game.Overlays.Comments public partial class CommentAuthorLine : FillFlowContainer { private readonly Comment comment; + private readonly IReadOnlyList meta; private OsuSpriteText deletedLabel = null!; - public CommentAuthorLine(Comment comment) + public CommentAuthorLine(Comment comment, IReadOnlyList meta) { this.comment = comment; + this.meta = meta; } [BackgroundDependencyLoader] @@ -49,6 +50,17 @@ namespace osu.Game.Overlays.Comments username.AddText(comment.LegacyName!); })); + var ownerMeta = meta.FirstOrDefault(m => m.Id == comment.CommentableId && m.Type == comment.CommentableType); + + if (ownerMeta?.OwnerId != null && ownerMeta.OwnerId == comment.UserId) + { + Add(new OwnerTitleBadge(ownerMeta.OwnerTitle ?? string.Empty) + { + // add top space to align with username + Margin = new MarginPadding { Top = 2f }, + }); + } + if (comment.Pinned) Add(new PinnedCommentNotice()); @@ -67,6 +79,39 @@ namespace osu.Game.Overlays.Comments deletedLabel.Show(); } + private partial class OwnerTitleBadge : CircularContainer + { + private readonly string title; + + public OwnerTitleBadge(string title) + { + this.title = title; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + AutoSizeAxes = Axes.Both; + Masking = true; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Light1, + }, + new OsuSpriteText + { + Text = title, + Font = OsuFont.Default.With(size: 10, weight: FontWeight.Bold), + Margin = new MarginPadding { Vertical = 2, Horizontal = 5 }, + Colour = colourProvider.Background6, + }, + }; + } + } + private partial class PinnedCommentNotice : FillFlowContainer { public PinnedCommentNotice() diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index afb8bdcc8b..afd4b96c68 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -172,7 +172,7 @@ namespace osu.Game.Overlays.Comments }, Children = new Drawable[] { - author = new CommentAuthorLine(Comment), + author = new CommentAuthorLine(Comment, Meta), message = new CommentMarkdownContainer { RelativeSizeAxes = Axes.X, From c24af5bfeb65ea125ebdd1c37e8c3e0a296ef78e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 14 Feb 2024 03:49:29 +0300 Subject: [PATCH 3036/4092] Add test coverage --- .../Online/TestSceneCommentsContainer.cs | 41 ++++++++- .../Visual/Online/TestSceneDrawableComment.cs | 88 ++++++++++--------- .../UserInterface/ThemeComparisonTestScene.cs | 30 ++++--- 3 files changed, 103 insertions(+), 56 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs index 3d8781d902..fd3552f675 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs @@ -170,6 +170,24 @@ namespace osu.Game.Tests.Visual.Online }); } + [Test] + public void TestPostAsOwner() + { + setUpCommentsResponse(getExampleComments()); + AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123)); + + setUpPostResponse(true); + AddStep("enter text", () => editorTextBox.Current.Value = "comm"); + AddStep("submit", () => commentsContainer.ChildrenOfType().Single().ChildrenOfType().First().TriggerClick()); + + AddUntilStep("comment sent", () => + { + string writtenText = editorTextBox.Current.Value; + var comment = commentsContainer.ChildrenOfType().LastOrDefault(); + return comment != null && comment.ChildrenOfType().Any(y => y.Text == writtenText) && comment.ChildrenOfType().Any(y => y.Text == "MAPPER"); + }); + } + private void setUpCommentsResponse(CommentBundle commentBundle) => AddStep("set up response", () => { @@ -183,7 +201,7 @@ namespace osu.Game.Tests.Visual.Online }; }); - private void setUpPostResponse() + private void setUpPostResponse(bool asOwner = false) => AddStep("set up response", () => { dummyAPI.HandleRequest = request => @@ -191,7 +209,7 @@ namespace osu.Game.Tests.Visual.Online if (!(request is CommentPostRequest req)) return false; - req.TriggerSuccess(new CommentBundle + var bundle = new CommentBundle { Comments = new List { @@ -202,9 +220,26 @@ namespace osu.Game.Tests.Visual.Online LegacyName = "FirstUser", CreatedAt = DateTimeOffset.Now, VotesCount = 98, + CommentableId = 2001, + CommentableType = "test", } } - }); + }; + + if (asOwner) + { + bundle.Comments[0].UserId = 1001; + bundle.Comments[0].User = new APIUser { Id = 1001, Username = "FirstUser" }; + bundle.CommentableMeta.Add(new CommentableMeta + { + Id = 2001, + OwnerId = 1001, + OwnerTitle = "MAPPER", + Type = "test", + }); + } + + req.TriggerSuccess(bundle); return true; }; }); diff --git a/osu.Game.Tests/Visual/Online/TestSceneDrawableComment.cs b/osu.Game.Tests/Visual/Online/TestSceneDrawableComment.cs index 5e83dd4fb3..6f09e4c1f6 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDrawableComment.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDrawableComment.cs @@ -4,62 +4,66 @@ #nullable disable using System; -using NUnit.Framework; -using osu.Framework.Allocation; +using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Containers; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Overlays; using osu.Game.Overlays.Comments; +using osu.Game.Tests.Visual.UserInterface; namespace osu.Game.Tests.Visual.Online { - public partial class TestSceneDrawableComment : OsuTestScene + public partial class TestSceneDrawableComment : ThemeComparisonTestScene { - [Cached] - private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); - - private Container container; - - [SetUp] - public void SetUp() => Schedule(() => + public TestSceneDrawableComment() + : base(false) { - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background4, - }, - container = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - }, - }; - }); - - [TestCaseSource(nameof(comments))] - public void TestComment(string description, string text) - { - AddStep(description, () => - { - comment.Pinned = description == "Pinned"; - comment.Message = text; - container.Add(new DrawableComment(comment)); - }); } - private static readonly Comment comment = new Comment + protected override Drawable CreateContent() => new OsuScrollContainer(Direction.Vertical) { - Id = 1, - LegacyName = "Test User", - CreatedAt = DateTimeOffset.Now, - VotesCount = 0, + RelativeSizeAxes = Axes.Both, + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + ChildrenEnumerable = comments.Select(info => + { + var comment = new Comment + { + Id = 1, + UserId = 1000, + User = new APIUser { Id = 1000, Username = "Someone" }, + CreatedAt = DateTimeOffset.Now, + VotesCount = 0, + Pinned = info[0] == "Pinned", + Message = info[1], + CommentableId = 2001, + CommentableType = "test" + }; + + return new[] + { + new DrawableComment(comment, Array.Empty()), + new DrawableComment(comment, new[] + { + new CommentableMeta + { + Id = 2001, + OwnerId = comment.UserId, + OwnerTitle = "MAPPER", + Type = "test", + }, + new CommentableMeta { Title = "Other Meta" }, + }), + }; + }).SelectMany(c => c) + } }; - private static object[] comments = + private static readonly string[][] comments = { new[] { "Plain", "This is plain comment" }, new[] { "Pinned", "This is pinned comment" }, diff --git a/osu.Game.Tests/Visual/UserInterface/ThemeComparisonTestScene.cs b/osu.Game.Tests/Visual/UserInterface/ThemeComparisonTestScene.cs index 2c894eacab..f0c4b5543f 100644 --- a/osu.Game.Tests/Visual/UserInterface/ThemeComparisonTestScene.cs +++ b/osu.Game.Tests/Visual/UserInterface/ThemeComparisonTestScene.cs @@ -14,31 +14,39 @@ namespace osu.Game.Tests.Visual.UserInterface { public abstract partial class ThemeComparisonTestScene : OsuGridTestScene { - protected ThemeComparisonTestScene() - : base(1, 2) + private readonly bool showNoColourProvider; + + protected ThemeComparisonTestScene(bool showNoColourProvider = true) + : base(1, showNoColourProvider ? 2 : 1) { + this.showNoColourProvider = showNoColourProvider; } [BackgroundDependencyLoader] private void load(OsuColour colours) { - Cell(0, 0).AddRange(new[] + if (showNoColourProvider) { - new Box + Cell(0, 0).AddRange(new[] { - RelativeSizeAxes = Axes.Both, - Colour = colours.GreySeaFoam - }, - CreateContent() - }); + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.GreySeaFoam + }, + CreateContent() + }); + } } protected void CreateThemedContent(OverlayColourScheme colourScheme) { var colourProvider = new OverlayColourProvider(colourScheme); - Cell(0, 1).Clear(); - Cell(0, 1).Add(new DependencyProvidingContainer + int col = showNoColourProvider ? 1 : 0; + + Cell(0, col).Clear(); + Cell(0, col).Add(new DependencyProvidingContainer { RelativeSizeAxes = Axes.Both, CachedDependencies = new (Type, object)[] From 02de9122d4466d2a30a915bbbc19e5f143fec824 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 14 Feb 2024 07:17:05 +0300 Subject: [PATCH 3037/4092] Remove behaviour of flipping catcher plate on direction change --- .../TestSceneCatchSkinConfiguration.cs | 111 ------------------ .../Skinning/CatchSkinConfiguration.cs | 13 -- .../Legacy/CatchLegacySkinTransformer.cs | 13 -- osu.Game.Rulesets.Catch/UI/Catcher.cs | 10 +- 4 files changed, 1 insertion(+), 146 deletions(-) delete mode 100644 osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs delete mode 100644 osu.Game.Rulesets.Catch/Skinning/CatchSkinConfiguration.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs deleted file mode 100644 index e2fc31d869..0000000000 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs +++ /dev/null @@ -1,111 +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 System.Linq; -using NUnit.Framework; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Testing; -using osu.Framework.Utils; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Catch.Judgements; -using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Catch.Objects.Drawables; -using osu.Game.Rulesets.Catch.Skinning; -using osu.Game.Rulesets.Catch.UI; -using osu.Game.Skinning; -using osu.Game.Tests.Visual; -using Direction = osu.Game.Rulesets.Catch.UI.Direction; - -namespace osu.Game.Rulesets.Catch.Tests -{ - public partial class TestSceneCatchSkinConfiguration : OsuTestScene - { - private Catcher catcher; - - private readonly Container container; - - public TestSceneCatchSkinConfiguration() - { - Add(container = new Container { RelativeSizeAxes = Axes.Both }); - } - - [TestCase(false)] - [TestCase(true)] - public void TestCatcherPlateFlipping(bool flip) - { - AddStep("setup catcher", () => - { - var skin = new TestSkin { FlipCatcherPlate = flip }; - container.Child = new SkinProvidingContainer(skin) - { - Child = catcher = new Catcher(new DroppedObjectContainer()) - { - Anchor = Anchor.Centre - } - }; - }); - - Fruit fruit = new Fruit(); - - AddStep("catch fruit", () => catchFruit(fruit, 20)); - - float position = 0; - - AddStep("record fruit position", () => position = getCaughtObjectPosition(fruit)); - - AddStep("face left", () => catcher.VisualDirection = Direction.Left); - - if (flip) - AddAssert("fruit position changed", () => !Precision.AlmostEquals(getCaughtObjectPosition(fruit), position)); - else - AddAssert("fruit position unchanged", () => Precision.AlmostEquals(getCaughtObjectPosition(fruit), position)); - - AddStep("face right", () => catcher.VisualDirection = Direction.Right); - - AddAssert("fruit position restored", () => Precision.AlmostEquals(getCaughtObjectPosition(fruit), position)); - } - - private float getCaughtObjectPosition(Fruit fruit) - { - var caughtObject = catcher.ChildrenOfType().Single(c => c.HitObject == fruit); - return caughtObject.Parent!.ToSpaceOfOtherDrawable(caughtObject.Position, catcher).X; - } - - private void catchFruit(Fruit fruit, float x) - { - fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - var drawableFruit = new DrawableFruit(fruit) { X = x }; - var judgement = fruit.CreateJudgement(); - catcher.OnNewResult(drawableFruit, new CatchJudgementResult(fruit, judgement) - { - Type = judgement.MaxResult - }); - } - - private class TestSkin : TrianglesSkin - { - public bool FlipCatcherPlate { get; set; } - - public TestSkin() - : base(null!) - { - } - - public override IBindable GetConfig(TLookup lookup) - { - if (lookup is CatchSkinConfiguration config) - { - if (config == CatchSkinConfiguration.FlipCatcherPlate) - return SkinUtils.As(new Bindable(FlipCatcherPlate)); - } - - return base.GetConfig(lookup); - } - } - } -} diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchSkinConfiguration.cs b/osu.Game.Rulesets.Catch/Skinning/CatchSkinConfiguration.cs deleted file mode 100644 index ea8d742b1a..0000000000 --- a/osu.Game.Rulesets.Catch/Skinning/CatchSkinConfiguration.cs +++ /dev/null @@ -1,13 +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.Catch.Skinning -{ - public enum CatchSkinConfiguration - { - /// - /// Whether the contents of the catcher plate should be visually flipped when the catcher direction is changed. - /// - FlipCatcherPlate - } -} diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index fb8af9bdb6..d1ef47cf17 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -122,19 +122,6 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy result.Value = LegacyColourCompatibility.DisallowZeroAlpha(result.Value); return (IBindable)result; - - case CatchSkinConfiguration config: - switch (config) - { - case CatchSkinConfiguration.FlipCatcherPlate: - // Don't flip catcher plate contents if the catcher is provided by this legacy skin. - if (GetDrawableComponent(new CatchSkinComponentLookup(CatchSkinComponents.Catcher)) != null) - return (IBindable)new Bindable(); - - break; - } - - break; } return base.GetConfig(lookup); diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 147850a9b7..dca01fc61a 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -112,11 +112,6 @@ namespace osu.Game.Rulesets.Catch.UI public Vector2 BodyScale => Scale * body.Scale; - /// - /// Whether the contents of the catcher plate should be visually flipped when the catcher direction is changed. - /// - private bool flipCatcherPlate; - /// /// Width of the area that can be used to attempt catches during gameplay. /// @@ -339,8 +334,6 @@ namespace osu.Game.Rulesets.Catch.UI skin.GetConfig(CatchSkinColour.HyperDash)?.Value ?? DEFAULT_HYPER_DASH_COLOUR; - flipCatcherPlate = skin.GetConfig(CatchSkinConfiguration.FlipCatcherPlate)?.Value ?? true; - runHyperDashStateTransition(HyperDashing); } @@ -352,8 +345,7 @@ namespace osu.Game.Rulesets.Catch.UI body.Scale = scaleFromDirection; // Inverse of catcher scale is applied here, as catcher gets scaled by circle size and so do the incoming fruit. - caughtObjectContainer.Scale = (1 / Scale.X) * (flipCatcherPlate ? scaleFromDirection : Vector2.One); - hitExplosionContainer.Scale = flipCatcherPlate ? scaleFromDirection : Vector2.One; + caughtObjectContainer.Scale = new Vector2(1 / Scale.X); // Correct overshooting. if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) || From 3d8d0f8430487c4c30e8964f11e52e1a6522f098 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 14 Feb 2024 08:37:20 +0100 Subject: [PATCH 3038/4092] Update test expectations for catch --- osu.Game.Rulesets.Catch.Tests/CatchHealthProcessorTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch.Tests/CatchHealthProcessorTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchHealthProcessorTest.cs index 8c2d1a91ab..d0a8ce4bbc 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchHealthProcessorTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchHealthProcessorTest.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.Tests // hitobject, starting HP, fail expected after miss [new Fruit(), 0.01, true], [new Droplet(), 0.01, true], - [new TinyDroplet(), 0, true], + [new TinyDroplet(), 0, false], [new Banana(), 0, false], ]; From f53bce8ff785a1c52bcd1bffb365e43287ec8bd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 14 Feb 2024 08:38:39 +0100 Subject: [PATCH 3039/4092] Fix catch health processor allowing fail on tiny droplet Closes https://github.com/ppy/osu/issues/27159. In today's episode of "just stable things"... --- .../Scoring/CatchHealthProcessor.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchHealthProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchHealthProcessor.cs index c3cc488941..2e1aec0803 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchHealthProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchHealthProcessor.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; @@ -21,6 +22,19 @@ namespace osu.Game.Rulesets.Catch.Scoring protected override IEnumerable EnumerateNestedHitObjects(HitObject hitObject) => Enumerable.Empty(); + protected override bool CanFailOn(JudgementResult result) + { + // matches stable. + // see: https://github.com/peppy/osu-stable-reference/blob/46cd3a10af7cc6cc96f4eba92ef1812dc8c3a27e/osu!/GameModes/Play/Rulesets/Ruleset.cs#L967 + // the above early-return skips the failure check at the end of the same method: + // https://github.com/peppy/osu-stable-reference/blob/46cd3a10af7cc6cc96f4eba92ef1812dc8c3a27e/osu!/GameModes/Play/Rulesets/Ruleset.cs#L1232 + // making it impossible to fail on a tiny droplet regardless of result. + if (result.Type == HitResult.SmallTickMiss) + return false; + + return base.CanFailOn(result); + } + protected override double GetHealthIncreaseFor(HitObject hitObject, HitResult result) { double increase = 0; From 2981ebe3d5725e4e26ad2d7b9ec22b9afe87fdf5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 14 Feb 2024 17:13:44 +0900 Subject: [PATCH 3040/4092] Fix inspection --- .../Rulesets/TestSceneRulesetSkinProvidingContainer.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs index 11f3fe660d..981258e8d1 100644 --- a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs +++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs @@ -43,13 +43,13 @@ namespace osu.Game.Tests.Rulesets AddStep("setup provider", () => { - var rulesetSkinProvider = new RulesetSkinProvidingContainer(Ruleset.Value.CreateInstance(), Beatmap.Value.Beatmap, Beatmap.Value.Skin); - - rulesetSkinProvider.Add(requester = new SkinRequester()); - + requester = new SkinRequester(); requester.OnLoadAsync += () => textureOnLoad = requester.GetTexture("test-image"); - Child = rulesetSkinProvider; + Child = new RulesetSkinProvidingContainer(Ruleset.Value.CreateInstance(), Beatmap.Value.Beatmap, Beatmap.Value.Skin) + { + requester + }; }); AddAssert("requester got correct initial texture", () => textureOnLoad != null); From 6baa0999065adc10f3b61ee527fecc614f6fa1f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 14 Feb 2024 10:12:01 +0100 Subject: [PATCH 3041/4092] Fix broken delay --- .../Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs b/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs index e3c1746e14..bec5170377 100644 --- a/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs +++ b/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs @@ -188,7 +188,7 @@ namespace osu.Game.Overlays.Toolbar titleText.FadeOut(250, Easing.OutQuint); deltaValue.FadeIn(250, Easing.OutQuint) .Then().Delay(1000) - .Then().OnComplete(_ => + .Then().Schedule(() => { mainValue.Current.Value = after; deltaValue.Current.SetDefault(); From 153024e61b52dc60f7dba39ffc8b131db743fbf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 14 Feb 2024 10:15:27 +0100 Subject: [PATCH 3042/4092] Increase transition durations slightly Maybe slightly better readability. Dunno. --- .../Toolbar/TransientUserStatisticsUpdateDisplay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs b/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs index bec5170377..9960dd4411 100644 --- a/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs +++ b/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs @@ -84,7 +84,7 @@ namespace osu.Game.Overlays.Toolbar if (update.After.PP != null) pp.Display(update.Before.PP ?? update.After.PP.Value, Math.Abs((update.After.PP - update.Before.PP) ?? 0M), update.After.PP.Value); - this.Delay(4000).FadeOut(500, Easing.OutQuint); + this.Delay(5000).FadeOut(500, Easing.OutQuint); }); } @@ -183,11 +183,11 @@ namespace osu.Game.Overlays.Toolbar titleText.Alpha = 1; deltaValue.Alpha = 0; - using (BeginDelayedSequence(1000)) + using (BeginDelayedSequence(1500)) { titleText.FadeOut(250, Easing.OutQuint); deltaValue.FadeIn(250, Easing.OutQuint) - .Then().Delay(1000) + .Then().Delay(1500) .Then().Schedule(() => { mainValue.Current.Value = after; From d189fa0f6907afbe9f8a83a10c6a712e793a9efb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 14 Feb 2024 11:53:41 +0100 Subject: [PATCH 3043/4092] Rename flag --- .../Visual/UserInterface/ThemeComparisonTestScene.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/ThemeComparisonTestScene.cs b/osu.Game.Tests/Visual/UserInterface/ThemeComparisonTestScene.cs index f0c4b5543f..3177695f44 100644 --- a/osu.Game.Tests/Visual/UserInterface/ThemeComparisonTestScene.cs +++ b/osu.Game.Tests/Visual/UserInterface/ThemeComparisonTestScene.cs @@ -14,18 +14,18 @@ namespace osu.Game.Tests.Visual.UserInterface { public abstract partial class ThemeComparisonTestScene : OsuGridTestScene { - private readonly bool showNoColourProvider; + private readonly bool showWithoutColourProvider; - protected ThemeComparisonTestScene(bool showNoColourProvider = true) - : base(1, showNoColourProvider ? 2 : 1) + protected ThemeComparisonTestScene(bool showWithoutColourProvider = true) + : base(1, showWithoutColourProvider ? 2 : 1) { - this.showNoColourProvider = showNoColourProvider; + this.showWithoutColourProvider = showWithoutColourProvider; } [BackgroundDependencyLoader] private void load(OsuColour colours) { - if (showNoColourProvider) + if (showWithoutColourProvider) { Cell(0, 0).AddRange(new[] { @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.UserInterface { var colourProvider = new OverlayColourProvider(colourScheme); - int col = showNoColourProvider ? 1 : 0; + int col = showWithoutColourProvider ? 1 : 0; Cell(0, col).Clear(); Cell(0, col).Add(new DependencyProvidingContainer From 8312f92b4ea24a981b6916f5207aaaf747471546 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 14 Feb 2024 12:24:28 +0100 Subject: [PATCH 3044/4092] Use better value for alignment --- osu.Game/Overlays/Comments/CommentAuthorLine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Comments/CommentAuthorLine.cs b/osu.Game/Overlays/Comments/CommentAuthorLine.cs index c269ab4c01..1f6fef4df3 100644 --- a/osu.Game/Overlays/Comments/CommentAuthorLine.cs +++ b/osu.Game/Overlays/Comments/CommentAuthorLine.cs @@ -57,7 +57,7 @@ namespace osu.Game.Overlays.Comments Add(new OwnerTitleBadge(ownerMeta.OwnerTitle ?? string.Empty) { // add top space to align with username - Margin = new MarginPadding { Top = 2f }, + Margin = new MarginPadding { Top = 1f }, }); } From 68247fa022c0ba3cbab5fe3c92457e11fb48c4a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 14 Feb 2024 13:21:37 +0100 Subject: [PATCH 3045/4092] Fix typo in json property name Would cause the mapper badge to never actually be shown in the real world. --- osu.Game/Online/API/Requests/Responses/Comment.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/Comment.cs b/osu.Game/Online/API/Requests/Responses/Comment.cs index 907632186c..e6a5559d1f 100644 --- a/osu.Game/Online/API/Requests/Responses/Comment.cs +++ b/osu.Game/Online/API/Requests/Responses/Comment.cs @@ -33,7 +33,7 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"votes_count")] public int VotesCount { get; set; } - [JsonProperty(@"commenatble_type")] + [JsonProperty(@"commentable_type")] public string CommentableType { get; set; } = null!; [JsonProperty(@"commentable_id")] From 2c0a5b7ef54169412c805ad4b05ea47cf131c012 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 14 Feb 2024 14:21:48 +0100 Subject: [PATCH 3046/4092] Fix missing tiny droplet not triggering fail with perfect on Stable does this: https://github.com/peppy/osu-stable-reference/blob/46cd3a10af7cc6cc96f4eba92ef1812dc8c3a27e/osu!/GameplayElements/HitObjectManagerFruits.cs#L98-L102 I'd rather not say what I think about it doing that, since it's likely to be unpublishable, but to approximate that, just make it so that only the "default fail condition" is beholden to the weird ebbs and flows of what the ruleset wants. This appears to fix the problem case and I'm hoping it doesn't break something else but I'm like 50/50 on it happening anyway at this point. Just gotta add tests add nauseam. --- .../Scoring/CatchHealthProcessor.cs | 4 ++-- .../Scoring/AccumulatingHealthProcessor.cs | 4 +++- .../Scoring/DrainingHealthProcessor.cs | 7 +++++-- osu.Game/Rulesets/Scoring/HealthProcessor.cs | 18 ++++++------------ 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchHealthProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchHealthProcessor.cs index 2e1aec0803..2f55f9a85f 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchHealthProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchHealthProcessor.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.Scoring protected override IEnumerable EnumerateNestedHitObjects(HitObject hitObject) => Enumerable.Empty(); - protected override bool CanFailOn(JudgementResult result) + protected override bool CheckDefaultFailCondition(JudgementResult result) { // matches stable. // see: https://github.com/peppy/osu-stable-reference/blob/46cd3a10af7cc6cc96f4eba92ef1812dc8c3a27e/osu!/GameModes/Play/Rulesets/Ruleset.cs#L967 @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Catch.Scoring if (result.Type == HitResult.SmallTickMiss) return false; - return base.CanFailOn(result); + return base.CheckDefaultFailCondition(result); } protected override double GetHealthIncreaseFor(HitObject hitObject, HitResult result) diff --git a/osu.Game/Rulesets/Scoring/AccumulatingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/AccumulatingHealthProcessor.cs index 422bf8ea79..bb4c2463a7 100644 --- a/osu.Game/Rulesets/Scoring/AccumulatingHealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/AccumulatingHealthProcessor.cs @@ -1,6 +1,8 @@ // 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.Judgements; + namespace osu.Game.Rulesets.Scoring { /// @@ -9,7 +11,7 @@ namespace osu.Game.Rulesets.Scoring /// public partial class AccumulatingHealthProcessor : HealthProcessor { - protected override bool DefaultFailCondition => JudgedHits == MaxHits && Health.Value < requiredHealth; + protected override bool CheckDefaultFailCondition(JudgementResult _) => JudgedHits == MaxHits && Health.Value < requiredHealth; private readonly double requiredHealth; diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs index 92a064385b..e72a8aaf67 100644 --- a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs @@ -142,9 +142,12 @@ namespace osu.Game.Rulesets.Scoring } } - protected override bool CanFailOn(JudgementResult result) + protected override bool CheckDefaultFailCondition(JudgementResult result) { - return !result.Judgement.MaxResult.IsBonus() && result.Type != HitResult.IgnoreHit; + if (result.Judgement.MaxResult.IsBonus() || result.Type == HitResult.IgnoreHit) + return false; + + return base.CheckDefaultFailCondition(result); } protected override void Reset(bool storeResults) diff --git a/osu.Game/Rulesets/Scoring/HealthProcessor.cs b/osu.Game/Rulesets/Scoring/HealthProcessor.cs index ccf53f075a..9e4c06b783 100644 --- a/osu.Game/Rulesets/Scoring/HealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/HealthProcessor.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Scoring public event Func? Failed; /// - /// Additional conditions on top of that cause a failing state. + /// Additional conditions on top of that cause a failing state. /// public event Func? FailConditions; @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Scoring Health.Value += GetHealthIncreaseFor(result); - if (CanFailOn(result) && meetsAnyFailCondition(result)) + if (meetsAnyFailCondition(result)) TriggerFailure(); } @@ -69,16 +69,10 @@ namespace osu.Game.Rulesets.Scoring protected virtual double GetHealthIncreaseFor(JudgementResult result) => result.HealthIncrease; /// - /// Whether a failure can occur on a given . - /// If the return value of this method is , neither nor will be checked - /// after this . + /// Checks whether the default conditions for failing are met. /// - protected virtual bool CanFailOn(JudgementResult result) => true; - - /// - /// The default conditions for failing. - /// - protected virtual bool DefaultFailCondition => Precision.AlmostBigger(Health.MinValue, Health.Value); + /// if failure should be invoked. + protected virtual bool CheckDefaultFailCondition(JudgementResult result) => Precision.AlmostBigger(Health.MinValue, Health.Value); /// /// Whether the current state of or the provided meets any fail condition. @@ -86,7 +80,7 @@ namespace osu.Game.Rulesets.Scoring /// The judgement result. private bool meetsAnyFailCondition(JudgementResult result) { - if (DefaultFailCondition) + if (CheckDefaultFailCondition(result)) return true; if (FailConditions != null) From f0f37df67fea4f20cc117012457ffd2c9b3bfec7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 14 Feb 2024 15:27:17 +0100 Subject: [PATCH 3047/4092] Revert unnecessary change --- osu.Game/Screens/Select/FilterQueryParser.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 24580c9e96..5fb5859a3b 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -121,8 +121,7 @@ namespace osu.Game.Screens.Select value.EndsWith("ms", StringComparison.Ordinal) ? 1 : value.EndsWith('s') ? 1000 : value.EndsWith('m') ? 60000 : - value.EndsWith('h') ? 3600000 : - value.EndsWith('d') ? 86400000 : 1000; + value.EndsWith('h') ? 3600000 : 1000; private static bool tryParseFloatWithPoint(string value, out float result) => float.TryParse(value, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out result); From d7dfc8b88aa5081a6765898a94097050c8441c2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 14 Feb 2024 15:55:57 +0100 Subject: [PATCH 3048/4092] Add failing test coverage for empty date filter not parsing --- osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 81a73fc99f..814b26a231 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -488,7 +488,8 @@ namespace osu.Game.Tests.NonVisual.Filtering new object[] { "0:3:" }, new object[] { "\"three days\"" }, new object[] { "0.1y0.1M2d" }, - new object[] { "0.99y0.99M2d" } + new object[] { "0.99y0.99M2d" }, + new object[] { string.Empty } }; [Test] From c24328dda347f8cd2196267790e225bb3f038d6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 14 Feb 2024 15:56:32 +0100 Subject: [PATCH 3049/4092] Abandon date filter if no meaningful time bound found during parsing --- osu.Game/Screens/Select/FilterQueryParser.cs | 23 +++++++++++--------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 5fb5859a3b..3612a84ff9 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -394,7 +394,8 @@ namespace osu.Game.Screens.Select if (match == null) return false; - DateTimeOffset dateTimeOffset = DateTimeOffset.Now; + DateTimeOffset? dateTimeOffset = null; + DateTimeOffset now = DateTimeOffset.Now; try { @@ -410,27 +411,27 @@ namespace osu.Game.Screens.Select switch (key) { case "seconds": - dateTimeOffset = dateTimeOffset.AddSeconds(-length); + dateTimeOffset = (dateTimeOffset ?? now).AddSeconds(-length); break; case "minutes": - dateTimeOffset = dateTimeOffset.AddMinutes(-length); + dateTimeOffset = (dateTimeOffset ?? now).AddMinutes(-length); break; case "hours": - dateTimeOffset = dateTimeOffset.AddHours(-length); + dateTimeOffset = (dateTimeOffset ?? now).AddHours(-length); break; case "days": - dateTimeOffset = dateTimeOffset.AddDays(-length); + dateTimeOffset = (dateTimeOffset ?? now).AddDays(-length); break; case "months": - dateTimeOffset = dateTimeOffset.AddMonths(-(int)length); + dateTimeOffset = (dateTimeOffset ?? now).AddMonths(-(int)length); break; case "years": - dateTimeOffset = dateTimeOffset.AddYears(-(int)length); + dateTimeOffset = (dateTimeOffset ?? now).AddYears(-(int)length); break; } } @@ -438,11 +439,13 @@ namespace osu.Game.Screens.Select } catch (ArgumentOutOfRangeException) { - dateTimeOffset = DateTimeOffset.MinValue; - dateTimeOffset = dateTimeOffset.AddMilliseconds(1); + dateTimeOffset = DateTimeOffset.MinValue.AddMilliseconds(1); } - return tryUpdateCriteriaRange(ref dateRange, reverseInequalityOperator(op), dateTimeOffset); + if (!dateTimeOffset.HasValue) + return false; + + return tryUpdateCriteriaRange(ref dateRange, reverseInequalityOperator(op), dateTimeOffset.Value); } // Function to reverse an Operator From a8ae0a032f67900b8466263ac3ceb5f1b79b95d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 14 Feb 2024 15:58:38 +0100 Subject: [PATCH 3050/4092] Simplify parsing --- osu.Game/Screens/Select/FilterQueryParser.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 3612a84ff9..17af0ee8ba 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -403,9 +403,12 @@ namespace osu.Game.Screens.Select foreach (string key in keys) { - if (match[key].Success) + if (!match.TryGetValue(key, out var group) || !group.Success) + continue; + + if (group.Success) { - if (!tryParseDoubleWithPoint(match[key].Value, out double length)) + if (!tryParseDoubleWithPoint(group.Value, out double length)) return false; switch (key) From f1d69abdc8d56502b23beccf62827518275cdc90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 14 Feb 2024 15:59:07 +0100 Subject: [PATCH 3051/4092] Rename test --- osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 814b26a231..a5aac0a4ce 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -532,7 +532,7 @@ namespace osu.Game.Tests.NonVisual.Filtering } [Test] - public void TestOutofrangeDateQuery() + public void TestOutOfRangeDateQuery() { const string query = "played<10000y"; var filterCriteria = new FilterCriteria(); From 414066fd34f18db433fc4c3c2fc10f3624989924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 14 Feb 2024 16:07:42 +0100 Subject: [PATCH 3052/4092] Inline problematic function (and rename things to make more sense) --- osu.Game/Screens/Select/FilterQueryParser.cs | 67 +++++++++++--------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 17af0ee8ba..278ca1ac5f 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -63,7 +63,7 @@ namespace osu.Game.Screens.Select case "played": case "lastplayed": - return tryUpdateDateRange(ref criteria.LastPlayed, op, value); + return tryUpdateDateAgoRange(ref criteria.LastPlayed, op, value); case "divisor": return TryUpdateCriteriaRange(ref criteria.BeatDivisor, op, value, tryParseInt); @@ -381,10 +381,42 @@ namespace osu.Game.Screens.Select return tryUpdateCriteriaRange(ref criteria.Length, op, totalLength, minScale / 2.0); } - private static bool tryUpdateDateRange(ref FilterCriteria.OptionalRange dateRange, Operator op, string val) + /// + /// This function is intended for parsing "days / months / years ago" type filters. + /// + private static bool tryUpdateDateAgoRange(ref FilterCriteria.OptionalRange dateRange, Operator op, string val) { - if (op == Operator.Equal) - return false; + switch (op) + { + case Operator.Equal: + // an equality filter is difficult to define for support here. + // if "3 months 2 days ago" means a single concrete time instant, such a filter is basically useless. + // if it means a range of 24 hours, then that is annoying to write and also comes with its own implications + // (does it mean "time instant 3 months 2 days ago, within 12 hours of tolerance either direction"? + // does it mean "the full calendar day, from midnight to midnight, 3 months 2 days ago"?) + // as such, for simplicity, just refuse to support this. + return false; + + // for the remaining operators, since the value provided to this function is an "ago" type value + // (as in, referring to some amount of time back), + // we'll want to flip the operator, such that `>5d` means "more than five days ago", as in "*before* five days ago", + // as intended by the user. + case Operator.Less: + op = Operator.Greater; + break; + + case Operator.LessOrEqual: + op = Operator.GreaterOrEqual; + break; + + case Operator.Greater: + op = Operator.Less; + break; + + case Operator.GreaterOrEqual: + op = Operator.LessOrEqual; + break; + } GroupCollection? match = null; @@ -448,32 +480,7 @@ namespace osu.Game.Screens.Select if (!dateTimeOffset.HasValue) return false; - return tryUpdateCriteriaRange(ref dateRange, reverseInequalityOperator(op), dateTimeOffset.Value); - } - - // Function to reverse an Operator - private static Operator reverseInequalityOperator(Operator ope) - { - switch (ope) - { - default: - throw new ArgumentOutOfRangeException(nameof(ope), $"Unsupported operator {ope}"); - - case Operator.Equal: - return Operator.Equal; - - case Operator.Greater: - return Operator.Less; - - case Operator.GreaterOrEqual: - return Operator.LessOrEqual; - - case Operator.Less: - return Operator.Greater; - - case Operator.LessOrEqual: - return Operator.GreaterOrEqual; - } + return tryUpdateCriteriaRange(ref dateRange, op, dateTimeOffset.Value); } } } From f7bea00564b7ac537b92a4eb9c4fe395bffb27d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 14 Feb 2024 16:19:32 +0100 Subject: [PATCH 3053/4092] Improve test coverage --- .../Filtering/FilterQueryParserTest.cs | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index a5aac0a4ce..12d6060351 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -508,8 +508,11 @@ namespace osu.Game.Tests.NonVisual.Filtering const string query = "played>50"; var filterCriteria = new FilterCriteria(); FilterQueryParser.ApplyQueries(filterCriteria, query); - Assert.AreEqual(false, filterCriteria.LastPlayed.Max == null); - Assert.AreEqual(true, filterCriteria.LastPlayed.Min == null); + Assert.That(filterCriteria.LastPlayed.Max, Is.Not.Null); + Assert.That(filterCriteria.LastPlayed.Min, Is.Null); + // the parser internally references `DateTimeOffset.Now`, so to not make things too annoying for tests, just assume some tolerance + // (irrelevant in proportion to the actual filter proscribed). + Assert.That(filterCriteria.LastPlayed.Max, Is.EqualTo(DateTimeOffset.Now.AddDays(-50)).Within(TimeSpan.FromSeconds(5))); } [Test] @@ -518,8 +521,25 @@ namespace osu.Game.Tests.NonVisual.Filtering const string query = "played<50"; var filterCriteria = new FilterCriteria(); FilterQueryParser.ApplyQueries(filterCriteria, query); - Assert.AreEqual(true, filterCriteria.LastPlayed.Max == null); - Assert.AreEqual(false, filterCriteria.LastPlayed.Min == null); + Assert.That(filterCriteria.LastPlayed.Max, Is.Null); + Assert.That(filterCriteria.LastPlayed.Min, Is.Not.Null); + // the parser internally references `DateTimeOffset.Now`, so to not make things too annoying for tests, just assume some tolerance + // (irrelevant in proportion to the actual filter proscribed). + Assert.That(filterCriteria.LastPlayed.Min, Is.EqualTo(DateTimeOffset.Now.AddDays(-50)).Within(TimeSpan.FromSeconds(5))); + } + + [Test] + public void TestBothSidesDateQuery() + { + const string query = "played>3M played<1y6M"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.That(filterCriteria.LastPlayed.Min, Is.Not.Null); + Assert.That(filterCriteria.LastPlayed.Max, Is.Not.Null); + // the parser internally references `DateTimeOffset.Now`, so to not make things too annoying for tests, just assume some tolerance + // (irrelevant in proportion to the actual filter proscribed). + Assert.That(filterCriteria.LastPlayed.Min, Is.EqualTo(DateTimeOffset.Now.AddYears(-1).AddMonths(-6)).Within(TimeSpan.FromSeconds(5))); + Assert.That(filterCriteria.LastPlayed.Max, Is.EqualTo(DateTimeOffset.Now.AddMonths(-3)).Within(TimeSpan.FromSeconds(5))); } [Test] From ae9a2661ace43a96a4fbf26072ed3efd0dc0ba54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 14 Feb 2024 16:20:47 +0100 Subject: [PATCH 3054/4092] Sprinkle some raw string prefixes --- osu.Game/Screens/Select/FilterQueryParser.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 278ca1ac5f..2c4077dacf 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -431,7 +431,7 @@ namespace osu.Game.Screens.Select try { - List keys = new List { "seconds", "minutes", "hours", "days", "months", "years" }; + List keys = new List { @"seconds", @"minutes", @"hours", @"days", @"months", @"years" }; foreach (string key in keys) { @@ -445,27 +445,27 @@ namespace osu.Game.Screens.Select switch (key) { - case "seconds": + case @"seconds": dateTimeOffset = (dateTimeOffset ?? now).AddSeconds(-length); break; - case "minutes": + case @"minutes": dateTimeOffset = (dateTimeOffset ?? now).AddMinutes(-length); break; - case "hours": + case @"hours": dateTimeOffset = (dateTimeOffset ?? now).AddHours(-length); break; - case "days": + case @"days": dateTimeOffset = (dateTimeOffset ?? now).AddDays(-length); break; - case "months": + case @"months": dateTimeOffset = (dateTimeOffset ?? now).AddMonths(-(int)length); break; - case "years": + case @"years": dateTimeOffset = (dateTimeOffset ?? now).AddYears(-(int)length); break; } From 53884f61c35a573273922ce3976af6b75c598a8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 14 Feb 2024 16:41:41 +0100 Subject: [PATCH 3055/4092] Apply suggested changes --- .../TransientUserStatisticsUpdateDisplay.cs | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs b/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs index 9960dd4411..52923ea178 100644 --- a/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs +++ b/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs @@ -127,7 +127,7 @@ namespace osu.Game.Overlays.Toolbar ValuePrefix = mainValuePrefix, Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - Font = OsuFont.GetFont(), + Font = OsuFont.GetFont().With(fixedWidth: true), }, new Container { @@ -140,7 +140,7 @@ namespace osu.Game.Overlays.Toolbar { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - Font = OsuFont.GetFont(size: 12, fixedWidth: false, weight: FontWeight.SemiBold), + Font = OsuFont.GetFont(size: 12, fixedWidth: true, weight: FontWeight.SemiBold), AlwaysPresent = true, }, titleText = new OsuSpriteText @@ -183,11 +183,11 @@ namespace osu.Game.Overlays.Toolbar titleText.Alpha = 1; deltaValue.Alpha = 0; - using (BeginDelayedSequence(1500)) + using (BeginDelayedSequence(1200)) { - titleText.FadeOut(250, Easing.OutQuint); - deltaValue.FadeIn(250, Easing.OutQuint) - .Then().Delay(1500) + titleText.FadeOut(250, Easing.OutQuad); + deltaValue.FadeIn(250, Easing.OutQuad) + .Then().Delay(500) .Then().Schedule(() => { mainValue.Current.Value = after; @@ -200,9 +200,9 @@ namespace osu.Game.Overlays.Toolbar private partial class Counter : RollingCounter where T : struct, IEquatable, IFormattable { - public const double ROLLING_DURATION = 500; + public const double ROLLING_DURATION = 1500; - public FontUsage Font { get; init; } = OsuFont.Default; + public FontUsage Font { get; init; } = OsuFont.Default.With(fixedWidth: true); public string ValuePrefix { @@ -217,7 +217,13 @@ namespace osu.Game.Overlays.Toolbar private string valuePrefix = string.Empty; protected override LocalisableString FormatCount(T count) => LocalisableString.Format(@"{0}{1:N0}", ValuePrefix, count); - protected override OsuSpriteText CreateSpriteText() => base.CreateSpriteText().With(t => t.Font = Font); + + protected override OsuSpriteText CreateSpriteText() => base.CreateSpriteText().With(t => + { + t.Font = Font; + t.Spacing = new Vector2(-1.5f, 0); + }); + protected override double RollingDuration => ROLLING_DURATION; } } From aae431e8f6f414abe1155f005d33cf111493220d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 14 Feb 2024 16:48:31 +0100 Subject: [PATCH 3056/4092] Cancel rolling properly --- .../TransientUserStatisticsUpdateDisplay.cs | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs b/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs index 52923ea178..50fc54088f 100644 --- a/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs +++ b/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -98,6 +99,7 @@ namespace osu.Game.Overlays.Toolbar private Counter mainValue = null!; private Counter deltaValue = null!; private OsuSpriteText titleText = null!; + private ScheduledDelegate? valueUpdateSchedule; [Resolved] private OsuColour colours { get; set; } = null!; @@ -159,6 +161,9 @@ namespace osu.Game.Overlays.Toolbar public void Display(T before, T delta, T after) { + valueUpdateSchedule?.Cancel(); + valueUpdateSchedule = null; + int comparison = valueComparer.Compare(before, after); if (comparison > 0) @@ -186,13 +191,16 @@ namespace osu.Game.Overlays.Toolbar using (BeginDelayedSequence(1200)) { titleText.FadeOut(250, Easing.OutQuad); - deltaValue.FadeIn(250, Easing.OutQuad) - .Then().Delay(500) - .Then().Schedule(() => - { - mainValue.Current.Value = after; - deltaValue.Current.SetDefault(); - }); + deltaValue.FadeIn(250, Easing.OutQuad); + + using (BeginDelayedSequence(1250)) + { + valueUpdateSchedule = Schedule(() => + { + mainValue.Current.Value = after; + deltaValue.Current.SetDefault(); + }); + } } } } From c175e036007bf68e0ad131f7d01e31a6b5a7fea8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Feb 2024 02:53:38 +0800 Subject: [PATCH 3057/4092] Inline rolling duration variable --- .../Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs b/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs index 50fc54088f..b77a4cfb94 100644 --- a/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs +++ b/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs @@ -208,8 +208,6 @@ namespace osu.Game.Overlays.Toolbar private partial class Counter : RollingCounter where T : struct, IEquatable, IFormattable { - public const double ROLLING_DURATION = 1500; - public FontUsage Font { get; init; } = OsuFont.Default.With(fixedWidth: true); public string ValuePrefix @@ -232,7 +230,7 @@ namespace osu.Game.Overlays.Toolbar t.Spacing = new Vector2(-1.5f, 0); }); - protected override double RollingDuration => ROLLING_DURATION; + protected override double RollingDuration => 1500; } } } From 9ec79755fb2e2e467439f7086e7f720b607aef77 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Feb 2024 02:54:28 +0800 Subject: [PATCH 3058/4092] Standardise font specs --- .../Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs b/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs index b77a4cfb94..f56a1a3dd2 100644 --- a/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs +++ b/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs @@ -129,7 +129,6 @@ namespace osu.Game.Overlays.Toolbar ValuePrefix = mainValuePrefix, Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - Font = OsuFont.GetFont().With(fixedWidth: true), }, new Container { @@ -142,7 +141,7 @@ namespace osu.Game.Overlays.Toolbar { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - Font = OsuFont.GetFont(size: 12, fixedWidth: true, weight: FontWeight.SemiBold), + Font = OsuFont.Default.With(size: 12, fixedWidth: true, weight: FontWeight.SemiBold), AlwaysPresent = true, }, titleText = new OsuSpriteText From 6e1b4152c07a250426e7e89e632db21043dd31cd Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 15 Feb 2024 00:00:39 +0300 Subject: [PATCH 3059/4092] Redice allocations during aggregate beatmap sort --- .../Select/Carousel/CarouselBeatmapSet.cs | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 6d2e938fb7..d2b71b1d5e 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -127,12 +127,40 @@ namespace osu.Game.Screens.Select.Carousel /// /// All beatmaps which are not filtered and valid for display. /// - protected IEnumerable ValidBeatmaps => Beatmaps.Where(b => !b.Filtered.Value || b.State.Value == CarouselItemState.Selected).Select(b => b.BeatmapInfo); + protected IEnumerable ValidBeatmaps + { + get + { + foreach (var item in Items) // iterating over Items directly to not allocate 2 enumerators + { + if (item is CarouselBeatmap b && (!b.Filtered.Value || b.State.Value == CarouselItemState.Selected)) + yield return b.BeatmapInfo; + } + } + } + + /// + /// Whether there are available beatmaps which are not filtered and valid for display. + /// Cheaper alternative to .Any() + /// + public bool HasValidBeatmaps + { + get + { + foreach (var item in Items) // iterating over Items directly to not allocate 2 enumerators + { + if (item is CarouselBeatmap b && (!b.Filtered.Value || b.State.Value == CarouselItemState.Selected)) + return true; + } + + return false; + } + } private int compareUsingAggregateMax(CarouselBeatmapSet other, Func func) { - bool ourBeatmaps = ValidBeatmaps.Any(); - bool otherBeatmaps = other.ValidBeatmaps.Any(); + bool ourBeatmaps = HasValidBeatmaps; + bool otherBeatmaps = other.HasValidBeatmaps; if (!ourBeatmaps && !otherBeatmaps) return 0; if (!ourBeatmaps) return -1; From 80abf6aab346b2f5eed5745c81a521547bf81c26 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Feb 2024 09:45:24 +0800 Subject: [PATCH 3060/4092] Avoid some further enumerator allocations --- osu.Game/Screens/Select/Carousel/CarouselGroup.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index b2ca117cec..62d694976f 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Framework.Extensions.ListExtensions; +using osu.Framework.Lists; namespace osu.Game.Screens.Select.Carousel { @@ -12,7 +14,7 @@ namespace osu.Game.Screens.Select.Carousel { public override DrawableCarouselItem? CreateDrawableRepresentation() => null; - public IReadOnlyList Items => items; + public SlimReadOnlyListWrapper Items => items.AsSlimReadOnly(); public int TotalItemsNotFiltered { get; private set; } From 674ee91bb5dd28d9f4f79a3f0c6d220274a8c3dd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Feb 2024 11:40:50 +0800 Subject: [PATCH 3061/4092] Define aggregate max delegates as static to further reduce allocations --- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index d2b71b1d5e..cee68cf9a5 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -91,19 +91,19 @@ namespace osu.Game.Screens.Select.Carousel break; case SortMode.LastPlayed: - comparison = -compareUsingAggregateMax(otherSet, b => (b.LastPlayed ?? DateTimeOffset.MinValue).ToUnixTimeSeconds()); + comparison = -compareUsingAggregateMax(otherSet, static b => (b.LastPlayed ?? DateTimeOffset.MinValue).ToUnixTimeSeconds()); break; case SortMode.BPM: - comparison = compareUsingAggregateMax(otherSet, b => b.BPM); + comparison = compareUsingAggregateMax(otherSet, static b => b.BPM); break; case SortMode.Length: - comparison = compareUsingAggregateMax(otherSet, b => b.Length); + comparison = compareUsingAggregateMax(otherSet, static b => b.Length); break; case SortMode.Difficulty: - comparison = compareUsingAggregateMax(otherSet, b => b.StarRating); + comparison = compareUsingAggregateMax(otherSet, static b => b.StarRating); break; case SortMode.DateSubmitted: From a03835bf1c472a477dc992b7c2d05357e1b07da4 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 14 Feb 2024 20:13:27 -0800 Subject: [PATCH 3062/4092] Improve comments --- .../Overlays/Profile/Header/Components/GlobalRankDisplay.cs | 2 +- osu.Game/Users/UserRankPanel.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/GlobalRankDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/GlobalRankDisplay.cs index dcd4129b45..d32f56ab1b 100644 --- a/osu.Game/Overlays/Profile/Header/Components/GlobalRankDisplay.cs +++ b/osu.Game/Overlays/Profile/Header/Components/GlobalRankDisplay.cs @@ -30,7 +30,7 @@ namespace osu.Game.Overlays.Profile.Header.Components Content = s.NewValue?.GlobalRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-"; }, true); - // needed as statistics doesn't populate User + // needed as `UserStatistics` doesn't populate `User` User.BindValueChanged(u => { var rankHighest = u.NewValue?.RankHighest; diff --git a/osu.Game/Users/UserRankPanel.cs b/osu.Game/Users/UserRankPanel.cs index 4a00583094..0b8a5166e6 100644 --- a/osu.Game/Users/UserRankPanel.cs +++ b/osu.Game/Users/UserRankPanel.cs @@ -167,8 +167,8 @@ namespace osu.Game.Users new GlobalRankDisplay { UserStatistics = { BindTarget = statistics }, - // TODO: make highest rank update, as api.LocalUser doesn't update - // maybe move to statistics in api, so `SoloStatisticsWatcher` can update the value + // TODO: make highest rank update, as `api.LocalUser` doesn't update + // maybe move to `UserStatistics` in api, so `SoloStatisticsWatcher` can update the value User = { BindTarget = user }, }, countryRankDisplay = new ProfileValueDisplay(true) From 0c25a9a460a23740ebfd6f8c8969f0661b1e2cf6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 15 Feb 2024 07:31:01 +0300 Subject: [PATCH 3063/4092] Add extra bottom space for catcher to not look cut off on tall resolutions --- .../UI/CatchPlayfieldAdjustmentContainer.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs index 64d17b08b6..74dfa6c1fd 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs @@ -20,6 +20,9 @@ namespace osu.Game.Rulesets.Catch.UI const float base_game_width = 1024f; const float base_game_height = 768f; + // extra bottom space for the catcher to not get cut off at tall resolutions lower than 4:3 (e.g. 5:4). number chosen based on testing with maximum catcher scale (i.e. CS 0). + const float extra_bottom_space = 200f; + Anchor = Anchor.Centre; Origin = Anchor.Centre; @@ -31,7 +34,8 @@ namespace osu.Game.Rulesets.Catch.UI Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, - Height = base_game_height, + Height = base_game_height + extra_bottom_space, + Y = extra_bottom_space / 2, Masking = true, Child = new Container { From 9e9297bfb3c06b816a8416cac44ad05f819e998a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Feb 2024 13:22:03 +0800 Subject: [PATCH 3064/4092] Add inline documentation as to why classic mod is not ranked See https://github.com/ppy/osu/pull/27149#issuecomment-1939509941. --- osu.Game/Rulesets/Mods/ModClassic.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Rulesets/Mods/ModClassic.cs b/osu.Game/Rulesets/Mods/ModClassic.cs index 16cb928bd4..b0f6ba9374 100644 --- a/osu.Game/Rulesets/Mods/ModClassic.cs +++ b/osu.Game/Rulesets/Mods/ModClassic.cs @@ -19,5 +19,16 @@ namespace osu.Game.Rulesets.Mods public override LocalisableString Description => "Feeling nostalgic?"; public override ModType Type => ModType.Conversion; + + /// + /// Classic mods are not to be ranked yet due to compatibility and multiplier concerns. + /// Right now classic mods are considered, for leaderboard purposes, to be equal as scores set on osu-stable. + /// But this is not the case. + /// + /// Some examples for things to resolve before even considering this: + /// - Hit windows differ (https://github.com/ppy/osu/issues/11311). + /// - Sliders always gives combo for slider end, even on miss (https://github.com/ppy/osu/issues/11769). + /// + public sealed override bool Ranked => false; } } From 401bd91dc4aefee2582055b798a471d240e17650 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 14 Feb 2024 22:57:38 -0800 Subject: [PATCH 3065/4092] Add visual test showing overflow on dropdown menu items --- .../Visual/UserInterface/TestSceneOsuDropdown.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuDropdown.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuDropdown.cs index 1678890b73..63f7a2f2cc 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuDropdown.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuDropdown.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Input.States; using osu.Framework.Testing; -using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; @@ -18,13 +17,22 @@ namespace osu.Game.Tests.Visual.UserInterface public partial class TestSceneOsuDropdown : ThemeComparisonTestScene { protected override Drawable CreateContent() => - new OsuEnumDropdown + new OsuEnumDropdown { Anchor = Anchor.Centre, Origin = Anchor.TopCentre, Width = 150 }; + private enum TestEnum + { + [System.ComponentModel.Description("Option")] + Option, + + [System.ComponentModel.Description("Really lonnnnnnng option")] + ReallyLongOption, + } + [Test] // todo: this can be written much better if ThemeComparisonTestScene has a manual input manager public void TestBackAction() @@ -43,7 +51,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("press back", () => dropdown().OnPressed(new KeyBindingPressEvent(new InputState(), GlobalAction.Back))); AddAssert("closed", () => dropdown().ChildrenOfType().Single().State == MenuState.Closed); - OsuEnumDropdown dropdown() => this.ChildrenOfType>().First(); + OsuEnumDropdown dropdown() => this.ChildrenOfType>().First(); } } } From 3d08bc5605242c097531eae2945a89abe7f80955 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 14 Feb 2024 23:00:30 -0800 Subject: [PATCH 3066/4092] Truncate long dropdown menu item text and show tooltip --- osu.Game/Graphics/UserInterface/OsuDropdown.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs index 2dc701dc9d..38e90bf4ea 100644 --- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs @@ -186,6 +186,8 @@ namespace osu.Game.Graphics.UserInterface : base(item) { Foreground.Padding = new MarginPadding(2); + Foreground.AutoSizeAxes = Axes.Y; + Foreground.RelativeSizeAxes = Axes.X; Masking = true; CornerRadius = corner_radius; @@ -247,11 +249,12 @@ namespace osu.Game.Graphics.UserInterface Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, }, - Label = new OsuSpriteText + Label = new TruncatingSpriteText { - X = 15, + Padding = new MarginPadding { Left = 15 }, Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, }, }; } From a037dbf8debe7bcecfa904d68c20875d3844236d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 15 Feb 2024 18:33:48 +0900 Subject: [PATCH 3067/4092] Update test to set Child in more canonical manner --- .../Rulesets/TestSceneRulesetSkinProvidingContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs index 981258e8d1..b089144233 100644 --- a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs +++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Rulesets Child = new RulesetSkinProvidingContainer(Ruleset.Value.CreateInstance(), Beatmap.Value.Beatmap, Beatmap.Value.Skin) { - requester + Child = requester }; }); From 95e745c6fbabce01dda192e567546185bfe62e9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 15 Feb 2024 10:16:06 +0100 Subject: [PATCH 3068/4092] Use better messaging for selected submission failure reasons These have been cropping up rather often lately, mostly courtesy of linux users, but not only: https://github.com/ppy/osu/issues/26840 https://github.com/ppy/osu/issues/27008 https://github.com/ppy/osu/discussions/26962 so this is a proposal for slightly improved messaging for such cases to hopefully get users on the right track. The original error is still logged to network log, so there's no information loss. --- osu.Game/Screens/Play/SubmittingPlayer.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index c45d46e993..c759710aba 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -140,7 +140,13 @@ namespace osu.Game.Screens.Play { switch (exception.Message) { - case "expired token": + case @"missing token header": + case @"invalid client hash": + case @"invalid verification hash": + Logger.Log("You are not able to submit a score. Please ensure that you are using the latest version of the official game releases.", level: LogLevel.Important); + break; + + case @"expired token": Logger.Log("Score submission failed because your system clock is set incorrectly. Please check your system time, date and timezone.", level: LogLevel.Important); break; From 898d5ce88bd4d249f98b8fe7cc6dd5e7cdd635d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 15 Feb 2024 10:40:40 +0100 Subject: [PATCH 3069/4092] Show selected submission failure messages even in solo Previously, if a `SubmittingPlayer` instance deemed it okay to proceed with gameplay despite submission failure, it would silently log all errors and proceed, but the score would still not be submitted. This feels a bit anti-user in the cases wherein something is genuinely wrong with either the client or web, so things like token verification failures or API failures are now shown as notifications to give the user an indication that something went wrong at all. Selected cases (non-user-playable mod, logged out, beatmap is not online) are still logged silently because those are either known and expected, or someone is messing with things. --- osu.Game/Screens/Play/SoloPlayer.cs | 2 +- osu.Game/Screens/Play/SubmittingPlayer.cs | 33 ++++++++++++----------- osu.Game/Tests/Visual/TestPlayer.cs | 2 +- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs index f7ae3eb62b..f4cf2da364 100644 --- a/osu.Game/Screens/Play/SoloPlayer.cs +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -52,7 +52,7 @@ namespace osu.Game.Screens.Play Scores = { BindTarget = LeaderboardScores } }; - protected override bool HandleTokenRetrievalFailure(Exception exception) => false; + protected override bool ShouldExitOnTokenRetrievalFailure(Exception exception) => false; protected override Task ImportScore(Score score) { diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index c759710aba..0873f60791 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -118,7 +118,7 @@ namespace osu.Game.Screens.Play token = r.ID; tcs.SetResult(true); }; - req.Failure += handleTokenFailure; + req.Failure += ex => handleTokenFailure(ex, displayNotification: true); api.Queue(req); @@ -128,14 +128,20 @@ namespace osu.Game.Screens.Play return true; - void handleTokenFailure(Exception exception) + void handleTokenFailure(Exception exception, bool displayNotification = false) { tcs.SetResult(false); - if (HandleTokenRetrievalFailure(exception)) + bool shouldExit = ShouldExitOnTokenRetrievalFailure(exception); + + if (displayNotification || shouldExit) { + string whatWillHappen = shouldExit + ? "You are not able to submit a score." + : "The following score will not be submitted."; + if (string.IsNullOrEmpty(exception.Message)) - Logger.Error(exception, "Failed to retrieve a score submission token."); + Logger.Error(exception, $"{whatWillHappen} Failed to retrieve a score submission token."); else { switch (exception.Message) @@ -143,31 +149,28 @@ namespace osu.Game.Screens.Play case @"missing token header": case @"invalid client hash": case @"invalid verification hash": - Logger.Log("You are not able to submit a score. Please ensure that you are using the latest version of the official game releases.", level: LogLevel.Important); + Logger.Log($"{whatWillHappen} Please ensure that you are using the latest version of the official game releases.", level: LogLevel.Important); break; case @"expired token": - Logger.Log("Score submission failed because your system clock is set incorrectly. Please check your system time, date and timezone.", level: LogLevel.Important); + Logger.Log($"{whatWillHappen} Your system clock is set incorrectly. Please check your system time, date and timezone.", level: LogLevel.Important); break; default: - Logger.Log($"You are not able to submit a score: {exception.Message}", level: LogLevel.Important); + Logger.Log($"{whatWillHappen} {exception.Message}", level: LogLevel.Important); break; } } + } + if (shouldExit) + { Schedule(() => { ValidForResume = false; this.Exit(); }); } - else - { - // Gameplay is allowed to continue, but we still should keep track of the error. - // In the future, this should be visible to the user in some way. - Logger.Log($"Score submission token retrieval failed ({exception.Message})"); - } } } @@ -176,7 +179,7 @@ namespace osu.Game.Screens.Play /// /// The error causing the failure. /// Whether gameplay should be immediately exited as a result. Returning false allows the gameplay session to continue. Defaults to true. - protected virtual bool HandleTokenRetrievalFailure(Exception exception) => true; + protected virtual bool ShouldExitOnTokenRetrievalFailure(Exception exception) => true; protected override async Task PrepareScoreForResultsAsync(Score score) { @@ -237,7 +240,7 @@ namespace osu.Game.Screens.Play /// /// Construct a request to be used for retrieval of the score token. - /// Can return null, at which point will be fired. + /// Can return null, at which point will be fired. /// [CanBeNull] protected abstract APIRequest CreateTokenRequest(); diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index d9cae6b03b..579a1934e0 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual PauseOnFocusLost = pauseOnFocusLost; } - protected override bool HandleTokenRetrievalFailure(Exception exception) => false; + protected override bool ShouldExitOnTokenRetrievalFailure(Exception exception) => false; protected override APIRequest CreateTokenRequest() { From e91d38872d99dc3904b3664331b94f097881a455 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Feb 2024 18:28:59 +0800 Subject: [PATCH 3070/4092] 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 d7f29beeb3..f61ff79b9f 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 a4cd26a372..506bebfd47 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From 755bc7c0507f3ad7b3c8e5b7ed5592ad78497b43 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 15 Feb 2024 20:07:55 +0900 Subject: [PATCH 3071/4092] Fix resolution scaling --- .../Mods/ManiaModHidden.cs | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs index 211f21513d..12f17c6c59 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs @@ -3,11 +3,14 @@ using System; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Localisation; using osu.Game.Rulesets.Mania.UI; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Game.Rulesets.Mania.Skinning; using osu.Game.Rulesets.Scoring; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Mania.Mods { @@ -18,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Mods /// private const float reference_playfield_height = 768; - private const float min_coverage = 160 / reference_playfield_height; + private const float min_coverage = 160f / reference_playfield_height; private const float max_coverage = 400f / reference_playfield_height; private const float coverage_increase_per_combo = 0.5f / reference_playfield_height; @@ -49,17 +52,38 @@ namespace osu.Game.Rulesets.Mania.Mods private partial class LegacyPlayfieldCover : PlayfieldCoveringWrapper { + [Resolved] + private ISkinSource skin { get; set; } = null!; + + private IBindable? hitPosition; + public LegacyPlayfieldCover(Drawable content) : base(content) { } + protected override void LoadComplete() + { + base.LoadComplete(); + + skin.SourceChanged += onSkinChanged; + onSkinChanged(); + } + + private void onSkinChanged() + { + hitPosition = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.HitPosition); + } + protected override float GetHeight(float coverage) { - if (DrawHeight == 0) + // In osu!stable, the cover is applied in absolute (x768) coordinates from the hit position. + float availablePlayfieldHeight = Math.Abs(reference_playfield_height - (hitPosition?.Value ?? Stage.HIT_TARGET_POSITION)); + + if (availablePlayfieldHeight == 0) return base.GetHeight(coverage); - return base.GetHeight(coverage) * reference_playfield_height / DrawHeight; + return base.GetHeight(coverage) * reference_playfield_height / availablePlayfieldHeight; } } } From 9c22fa3a9fbae0fb568e5a7b36d4c688268add3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 15 Feb 2024 12:13:01 +0100 Subject: [PATCH 3072/4092] Fix android test project compile failures --- .../osu.Game.Tests.Android.csproj | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj b/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj index 889f0a3583..b02425eadd 100644 --- a/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj +++ b/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj @@ -31,4 +31,22 @@ + + + + + XamarinJetbrainsAnnotations + + + From d1a51b474c2a4d8ebdd780cc42f0d5ff63a8ab9a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 15 Feb 2024 21:24:00 +0900 Subject: [PATCH 3073/4092] Adjust tests --- .../Mods/TestSceneManiaModFadeIn.cs | 70 +++++++++++++++++-- .../Mods/TestSceneManiaModHidden.cs | 70 +++++++++++++++++-- .../Mods/ManiaModHidden.cs | 15 ++-- 3 files changed, 142 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModFadeIn.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModFadeIn.cs index 2c8c151e7f..fc49dc528d 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModFadeIn.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModFadeIn.cs @@ -1,8 +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.Linq; using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Rulesets.Mania.UI; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests.Mods @@ -11,9 +17,65 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods { protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); - [TestCase(0.5f)] - [TestCase(0.1f)] - [TestCase(0.7f)] - public void TestCoverage(float coverage) => CreateModTest(new ModTestData { Mod = new ManiaModFadeIn { Coverage = { Value = coverage } }, PassCondition = () => true }); + [Test] + public void TestMinCoverageFullWidth() + { + CreateModTest(new ModTestData + { + Mod = new ManiaModHidden(), + PassCondition = () => checkCoverage(ManiaModHidden.MIN_COVERAGE) + }); + } + + [Test] + public void TestMinCoverageHalfWidth() + { + CreateModTest(new ModTestData + { + Mod = new ManiaModHidden(), + PassCondition = () => checkCoverage(ManiaModHidden.MIN_COVERAGE) + }); + + AddStep("set playfield width to 0.5", () => Player.Width = 0.5f); + } + + [Test] + public void TestMaxCoverageFullWidth() + { + CreateModTest(new ModTestData + { + Mod = new ManiaModHidden(), + PassCondition = () => checkCoverage(ManiaModHidden.MAX_COVERAGE) + }); + + AddStep("set combo to 480", () => Player.ScoreProcessor.Combo.Value = 480); + } + + [Test] + public void TestMaxCoverageHalfWidth() + { + CreateModTest(new ModTestData + { + Mod = new ManiaModHidden(), + PassCondition = () => checkCoverage(ManiaModHidden.MAX_COVERAGE) + }); + + AddStep("set combo to 480", () => Player.ScoreProcessor.Combo.Value = 480); + AddStep("set playfield width to 0.5", () => Player.Width = 0.5f); + } + + private bool checkCoverage(float expected) + { + Drawable? cover = this.ChildrenOfType().FirstOrDefault(); + Drawable? filledArea = cover?.ChildrenOfType().LastOrDefault(); + + if (filledArea == null) + return false; + + float scale = cover!.DrawHeight / (768 - Stage.HIT_TARGET_POSITION); + + // A bit of lenience because the test may end up hitting hitobjects before any assertions. + return Precision.AlmostEquals(filledArea.DrawHeight / scale, expected, 0.1); + } } } diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHidden.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHidden.cs index 204f26f151..581cc3b33a 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHidden.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHidden.cs @@ -1,8 +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.Linq; using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Rulesets.Mania.UI; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests.Mods @@ -11,9 +17,65 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods { protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); - [TestCase(0.5f)] - [TestCase(0.2f)] - [TestCase(0.8f)] - public void TestCoverage(float coverage) => CreateModTest(new ModTestData { Mod = new ManiaModHidden { Coverage = { Value = coverage } }, PassCondition = () => true }); + [Test] + public void TestMinCoverageFullWidth() + { + CreateModTest(new ModTestData + { + Mod = new ManiaModHidden(), + PassCondition = () => checkCoverage(ManiaModHidden.MIN_COVERAGE) + }); + } + + [Test] + public void TestMinCoverageHalfWidth() + { + CreateModTest(new ModTestData + { + Mod = new ManiaModHidden(), + PassCondition = () => checkCoverage(ManiaModHidden.MIN_COVERAGE) + }); + + AddStep("set playfield width to 0.5", () => Player.Width = 0.5f); + } + + [Test] + public void TestMaxCoverageFullWidth() + { + CreateModTest(new ModTestData + { + Mod = new ManiaModHidden(), + PassCondition = () => checkCoverage(ManiaModHidden.MAX_COVERAGE) + }); + + AddStep("set combo to 480", () => Player.ScoreProcessor.Combo.Value = 480); + } + + [Test] + public void TestMaxCoverageHalfWidth() + { + CreateModTest(new ModTestData + { + Mod = new ManiaModHidden(), + PassCondition = () => checkCoverage(ManiaModHidden.MAX_COVERAGE) + }); + + AddStep("set combo to 480", () => Player.ScoreProcessor.Combo.Value = 480); + AddStep("set playfield width to 0.5", () => Player.Width = 0.5f); + } + + private bool checkCoverage(float expected) + { + Drawable? cover = this.ChildrenOfType().FirstOrDefault(); + Drawable? filledArea = cover?.ChildrenOfType().LastOrDefault(); + + if (filledArea == null) + return false; + + float scale = cover!.DrawHeight / (768 - Stage.HIT_TARGET_POSITION); + + // A bit of lenience because the test may end up hitting hitobjects before any assertions. + return Precision.AlmostEquals(filledArea.DrawHeight / scale, expected, 0.1); + } } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs index 12f17c6c59..b2c6988319 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs @@ -21,9 +21,9 @@ namespace osu.Game.Rulesets.Mania.Mods /// private const float reference_playfield_height = 768; - private const float min_coverage = 160f / reference_playfield_height; - private const float max_coverage = 400f / reference_playfield_height; - private const float coverage_increase_per_combo = 0.5f / reference_playfield_height; + public const float MIN_COVERAGE = 160f; + public const float MAX_COVERAGE = 400f; + private const float coverage_increase_per_combo = 0.5f; public override LocalisableString Description => @"Keys fade out before you hit them!"; public override double ScoreMultiplier => 1; @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mania.Mods typeof(ManiaModCover) }).ToArray(); - public override BindableNumber Coverage { get; } = new BindableFloat(min_coverage); + public override BindableNumber Coverage { get; } = new BindableFloat(MIN_COVERAGE); protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AgainstScroll; private readonly BindableInt combo = new BindableInt(); @@ -45,7 +45,12 @@ namespace osu.Game.Rulesets.Mania.Mods combo.UnbindAll(); combo.BindTo(scoreProcessor.Combo); - combo.BindValueChanged(c => Coverage.Value = Math.Min(max_coverage, min_coverage + c.NewValue * coverage_increase_per_combo), true); + combo.BindValueChanged(c => + { + Coverage.Value = Math.Min( + MAX_COVERAGE / reference_playfield_height, + MIN_COVERAGE / reference_playfield_height + c.NewValue * coverage_increase_per_combo / reference_playfield_height); + }, true); } protected override PlayfieldCoveringWrapper CreateCover(Drawable content) => new LegacyPlayfieldCover(content); From 878fb2d10d8edb3f559e786751941d8ac3f2d05d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 15 Feb 2024 22:05:25 +0900 Subject: [PATCH 3074/4092] Add break support --- .../Mods/TestSceneManiaModFadeIn.cs | 19 ++++++++++++ .../Mods/TestSceneManiaModHidden.cs | 19 ++++++++++++ .../Mods/ManiaModHidden.cs | 25 ++++++++++----- .../UI/PlayfieldCoveringWrapper.cs | 31 +++++++++++++------ 4 files changed, 78 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModFadeIn.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModFadeIn.cs index fc49dc528d..9620897983 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModFadeIn.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModFadeIn.cs @@ -7,8 +7,12 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Objects; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests.Mods @@ -64,6 +68,21 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods AddStep("set playfield width to 0.5", () => Player.Width = 0.5f); } + [Test] + public void TestNoCoverageDuringBreak() + { + CreateModTest(new ModTestData + { + Mod = new ManiaModHidden(), + Beatmap = new Beatmap + { + HitObjects = Enumerable.Range(1, 100).Select(i => (HitObject)new Note { StartTime = 1000 + 200 * i }).ToList(), + Breaks = { new BreakPeriod(2000, 28000) } + }, + PassCondition = () => Player.IsBreakTime.Value && checkCoverage(0) + }); + } + private bool checkCoverage(float expected) { Drawable? cover = this.ChildrenOfType().FirstOrDefault(); diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHidden.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHidden.cs index 581cc3b33a..ae23c4573c 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHidden.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHidden.cs @@ -7,8 +7,12 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Objects; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests.Mods @@ -64,6 +68,21 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods AddStep("set playfield width to 0.5", () => Player.Width = 0.5f); } + [Test] + public void TestNoCoverageDuringBreak() + { + CreateModTest(new ModTestData + { + Mod = new ManiaModHidden(), + Beatmap = new Beatmap + { + HitObjects = Enumerable.Range(1, 100).Select(i => (HitObject)new Note { StartTime = 1000 + 200 * i }).ToList(), + Breaks = { new BreakPeriod(2000, 28000) } + }, + PassCondition = () => Player.IsBreakTime.Value && checkCoverage(0) + }); + } + private bool checkCoverage(float expected) { Drawable? cover = this.ChildrenOfType().FirstOrDefault(); diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs index b2c6988319..5ddc627642 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs @@ -9,12 +9,15 @@ using osu.Game.Rulesets.Mania.UI; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Rulesets.Mania.Skinning; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; using osu.Game.Skinning; namespace osu.Game.Rulesets.Mania.Mods { - public partial class ManiaModHidden : ManiaModWithPlayfieldCover + public partial class ManiaModHidden : ManiaModWithPlayfieldCover, IApplicableToPlayer, IUpdatableByPlayfield { /// /// osu!stable is referenced to 768px. @@ -37,6 +40,7 @@ namespace osu.Game.Rulesets.Mania.Mods public override BindableNumber Coverage { get; } = new BindableFloat(MIN_COVERAGE); protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AgainstScroll; + private readonly IBindable isBreakTime = new Bindable(); private readonly BindableInt combo = new BindableInt(); public override void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) @@ -45,12 +49,19 @@ namespace osu.Game.Rulesets.Mania.Mods combo.UnbindAll(); combo.BindTo(scoreProcessor.Combo); - combo.BindValueChanged(c => - { - Coverage.Value = Math.Min( - MAX_COVERAGE / reference_playfield_height, - MIN_COVERAGE / reference_playfield_height + c.NewValue * coverage_increase_per_combo / reference_playfield_height); - }, true); + } + + public void ApplyToPlayer(Player player) + { + isBreakTime.UnbindAll(); + isBreakTime.BindTo(player.IsBreakTime); + } + + public void Update(Playfield playfield) + { + Coverage.Value = isBreakTime.Value + ? 0 + : Math.Min(MAX_COVERAGE, MIN_COVERAGE + combo.Value * coverage_increase_per_combo) / reference_playfield_height; } protected override PlayfieldCoveringWrapper CreateCover(Drawable content) => new LegacyPlayfieldCover(content); diff --git a/osu.Game.Rulesets.Mania/UI/PlayfieldCoveringWrapper.cs b/osu.Game.Rulesets.Mania/UI/PlayfieldCoveringWrapper.cs index 2b70c527ae..1cf2be7b06 100644 --- a/osu.Game.Rulesets.Mania/UI/PlayfieldCoveringWrapper.cs +++ b/osu.Game.Rulesets.Mania/UI/PlayfieldCoveringWrapper.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Mania.UI private readonly IBindable scrollDirection = new Bindable(); - private float currentCoverage; + private float currentCoverageHeight; public PlayfieldCoveringWrapper(Drawable content) { @@ -106,23 +106,36 @@ namespace osu.Game.Rulesets.Mania.UI protected override void LoadComplete() { base.LoadComplete(); - - updateHeight(Coverage.Value); + updateCoverSize(true); } protected override void Update() { base.Update(); - - updateHeight((float)Interpolation.DampContinuously(currentCoverage, Coverage.Value, 25, Math.Abs(Time.Elapsed))); + updateCoverSize(false); } - private void updateHeight(float coverage) + private void updateCoverSize(bool instant) { - filled.Height = GetHeight(coverage); - gradient.Y = -GetHeight(coverage); + float targetCoverage; + float targetAlpha; - currentCoverage = coverage; + if (instant) + { + targetCoverage = Coverage.Value; + targetAlpha = Coverage.Value > 0 ? 1 : 0; + } + else + { + targetCoverage = (float)Interpolation.DampContinuously(currentCoverageHeight, Coverage.Value, 25, Math.Abs(Time.Elapsed)); + targetAlpha = (float)Interpolation.DampContinuously(gradient.Alpha, Coverage.Value > 0 ? 1 : 0, 25, Math.Abs(Time.Elapsed)); + } + + filled.Height = GetHeight(targetCoverage); + gradient.Y = -GetHeight(targetCoverage); + gradient.Alpha = targetAlpha; + + currentCoverageHeight = targetCoverage; } protected virtual float GetHeight(float coverage) => coverage; From e705190664cee548c402eb2e67ecbaf742341b62 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Feb 2024 23:23:49 +0800 Subject: [PATCH 3075/4092] Update windows icon metrics to match previous icon --- osu.Desktop/lazer.ico | Bin 67391 -> 76552 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/osu.Desktop/lazer.ico b/osu.Desktop/lazer.ico index f84866b8e93b4c7a449371b71a99b1a8c5567df1..a3280f0de0b6a935309c84594e8b83cdb7d2e43f 100644 GIT binary patch literal 76552 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

X~%=`gEKj(Ksd$CM;}+tqLyrwl(h2dj8hG;X_w+ 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 `fTbt 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 Date: Thu, 15 Feb 2024 16:40:58 +0100 Subject: [PATCH 3076/4092] Make dropdown text nicer --- osu.Game.Rulesets.Mania/UI/PlayfieldCoveringWrapper.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/UI/PlayfieldCoveringWrapper.cs b/osu.Game.Rulesets.Mania/UI/PlayfieldCoveringWrapper.cs index 1cf2be7b06..d8d9705530 100644 --- a/osu.Game.Rulesets.Mania/UI/PlayfieldCoveringWrapper.cs +++ b/osu.Game.Rulesets.Mania/UI/PlayfieldCoveringWrapper.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.ComponentModel; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -13,11 +14,12 @@ using osu.Framework.Utils; using osu.Game.Rulesets.UI.Scrolling; using osuTK; using osuTK.Graphics; +using Container = osu.Framework.Graphics.Containers.Container; namespace osu.Game.Rulesets.Mania.UI { ///

- /// A that has its contents partially hidden by an adjustable "cover". This is intended to be used in a playfield. + /// A that has its contents partially hidden by an adjustable "cover". This is intended to be used in a playfield. /// public partial class PlayfieldCoveringWrapper : CompositeDrawable { @@ -157,11 +159,13 @@ namespace osu.Game.Rulesets.Mania.UI /// /// The cover expands along the scrolling direction. /// + [Description("Along scroll")] AlongScroll, /// /// The cover expands against the scrolling direction. /// + [Description("Against scroll")] AgainstScroll } } From c4bcae86ec02c59dfd0a795ddb9a7f828201b8ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Feb 2024 00:43:20 +0800 Subject: [PATCH 3077/4092] Update iOS icon metrics to match previous icon --- ...0-5cbe0121-ed68-414f-9ddc-dd993ac97e62.png | Bin 278451 -> 453879 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/300076680-5cbe0121-ed68-414f-9ddc-dd993ac97e62.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/300076680-5cbe0121-ed68-414f-9ddc-dd993ac97e62.png index 21f5f0f3a0901ed465ec468164bce1817fb4ea22..9287a71040091fb7a249018bff4133b91b12e1fc 100644 GIT binary patch literal 453879 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 K)~(TTivAB8ZcmZ` literal 278451 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

EM!on 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

b;UQhmR-aO6N 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�k+(fIDq7qxZKK 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�IQvA; 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

-2ku4wGs_zr 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 Date: Thu, 15 Feb 2024 23:05:13 +0100 Subject: [PATCH 3078/4092] Point launch.json to new build directory --- .vscode/launch.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index d93fddf42d..7c5225cff7 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/osu.Desktop/bin/Debug/net6.0/osu!.dll" + "${workspaceRoot}/osu.Desktop/bin/Debug/net8.0/osu!.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build osu! (Debug)", @@ -19,7 +19,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/osu.Desktop/bin/Release/net6.0/osu!.dll" + "${workspaceRoot}/osu.Desktop/bin/Release/net8.0/osu!.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build osu! (Release)", @@ -31,7 +31,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/osu.Game.Tests/bin/Debug/net6.0/osu.Game.Tests.dll" + "${workspaceRoot}/osu.Game.Tests/bin/Debug/net8.0/osu.Game.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build tests (Debug)", @@ -43,7 +43,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/osu.Game.Tests/bin/Release/net6.0/osu.Game.Tests.dll" + "${workspaceRoot}/osu.Game.Tests/bin/Release/net8.0/osu.Game.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build tests (Release)", @@ -55,7 +55,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/osu.Desktop/bin/Debug/net6.0/osu!.dll", + "${workspaceRoot}/osu.Desktop/bin/Debug/net8.0/osu!.dll", "--tournament" ], "cwd": "${workspaceRoot}", @@ -68,7 +68,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/osu.Desktop/bin/Release/net6.0/osu!.dll", + "${workspaceRoot}/osu.Desktop/bin/Release/net8.0/osu!.dll", "--tournament" ], "cwd": "${workspaceRoot}", @@ -81,7 +81,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/net6.0/osu.Game.Tournament.Tests.dll", + "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/net8.0/osu.Game.Tournament.Tests.dll", "--tournament" ], "cwd": "${workspaceRoot}", @@ -94,7 +94,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/net6.0/osu.Game.Tournament.Tests.dll", + "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/net8.0/osu.Game.Tournament.Tests.dll", "--tournament" ], "cwd": "${workspaceRoot}", @@ -105,7 +105,7 @@ "name": "Benchmark", "type": "coreclr", "request": "launch", - "program": "${workspaceRoot}/osu.Game.Benchmarks/bin/Release/net6.0/osu.Game.Benchmarks.dll", + "program": "${workspaceRoot}/osu.Game.Benchmarks/bin/Release/net8.0/osu.Game.Benchmarks.dll", "args": [ "--filter", "*" From a9eac5924de12073d39e8c9b2b57f83be6185de2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 16 Feb 2024 01:18:13 +0300 Subject: [PATCH 3079/4092] Remove seemingly unnecessary float casts --- osu.Game/Screens/Play/PlayerLoader.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 6154e443ef..fff1118622 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -550,8 +550,10 @@ namespace osu.Game.Screens.Play { if (!muteWarningShownOnce.Value) { + double aggregateVolumeTrack = audioManager.Volume.Value * audioManager.VolumeTrack.Value; + // Checks if the notification has not been shown yet and also if master volume is muted, track/music volume is muted or if the whole game is muted. - if (volumeOverlay?.IsMuted.Value == true || (float)(audioManager.Volume.Value * audioManager.VolumeTrack.Value) <= volume_requirement) + if (volumeOverlay?.IsMuted.Value == true || aggregateVolumeTrack <= volume_requirement) { notificationOverlay?.Post(new MutedNotification()); muteWarningShownOnce.Value = true; @@ -580,9 +582,11 @@ namespace osu.Game.Screens.Play volumeOverlay.IsMuted.Value = false; + double aggregateVolumeTrack = audioManager.Volume.Value * audioManager.VolumeTrack.Value; + // Check values before resetting, as the user may have only had mute enabled, in which case we might not need to adjust volumes. // Note that we only restore to -20 dB to ensure the user isn't suddenly overloaded by unexpectedly high volume. - if ((float)(audioManager.Volume.Value * audioManager.VolumeTrack.Value) <= volume_requirement) + if (aggregateVolumeTrack <= volume_requirement) { // Prioritize increasing music over master volume as to avoid also increasing effects volume. const double target = 0.1; From d81b148b099e56f1a9ff8d7a20d282e7b970d7a9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 16 Feb 2024 01:23:18 +0300 Subject: [PATCH 3080/4092] Remove mention of decibel units in comment Decibels are irrelevant in the volume bindables, as mentioned in PR already. --- 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 fff1118622..c755923ace 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -585,7 +585,7 @@ namespace osu.Game.Screens.Play double aggregateVolumeTrack = audioManager.Volume.Value * audioManager.VolumeTrack.Value; // Check values before resetting, as the user may have only had mute enabled, in which case we might not need to adjust volumes. - // Note that we only restore to -20 dB to ensure the user isn't suddenly overloaded by unexpectedly high volume. + // Note that we only restore to 10% to ensure the user isn't suddenly overloaded by unexpectedly high volume. if (aggregateVolumeTrack <= volume_requirement) { // Prioritize increasing music over master volume as to avoid also increasing effects volume. From 5431781f80692d8fc36a5a301126148b00ca916c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 16 Feb 2024 01:23:26 +0300 Subject: [PATCH 3081/4092] Bring back target volume to 50% --- 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 c755923ace..d3f75fe15e 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -589,7 +589,7 @@ namespace osu.Game.Screens.Play if (aggregateVolumeTrack <= volume_requirement) { // Prioritize increasing music over master volume as to avoid also increasing effects volume. - const double target = 0.1; + const double target = 0.5; double result = target / Math.Max(0.01, audioManager.Volume.Value); if (result > 1) From 6751f95eb648b42cdb55baa83efcb07a757ea9a7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 16 Feb 2024 01:28:47 +0300 Subject: [PATCH 3082/4092] Adjust test cases and approximate equality --- osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 67e94a2960..02a0ae6e6c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -268,9 +268,9 @@ namespace osu.Game.Tests.Visual.Gameplay { addVolumeSteps("master and music volumes", () => { - audioManager.Volume.Value = 0.15; - audioManager.VolumeTrack.Value = 0.01; - }, () => audioManager.Volume.Value == 0.15 && audioManager.VolumeTrack.Value == 0.67); + audioManager.Volume.Value = 0.6; + audioManager.VolumeTrack.Value = 0.15; + }, () => Precision.AlmostEquals(audioManager.Volume.Value, 0.6) && Precision.AlmostEquals(audioManager.VolumeTrack.Value, 0.83)); } [Test] @@ -280,7 +280,7 @@ namespace osu.Game.Tests.Visual.Gameplay { audioManager.Volume.Value = 0.01; audioManager.VolumeTrack.Value = 0.15; - }, () => audioManager.Volume.Value == 0.1 && audioManager.VolumeTrack.Value == 1); + }, () => Precision.AlmostEquals(audioManager.Volume.Value, 0.5) && Precision.AlmostEquals(audioManager.VolumeTrack.Value, 1)); } [Test] From 7530b1f362451eb18e6fcfaceb5154f4bddddac6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 16 Feb 2024 01:31:52 +0300 Subject: [PATCH 3083/4092] Adjust comment again --- 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 d3f75fe15e..060074890c 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -585,7 +585,7 @@ namespace osu.Game.Screens.Play double aggregateVolumeTrack = audioManager.Volume.Value * audioManager.VolumeTrack.Value; // Check values before resetting, as the user may have only had mute enabled, in which case we might not need to adjust volumes. - // Note that we only restore to 10% to ensure the user isn't suddenly overloaded by unexpectedly high volume. + // Note that we only restore halfway to ensure the user isn't suddenly overloaded by unexpectedly high volume. if (aggregateVolumeTrack <= volume_requirement) { // Prioritize increasing music over master volume as to avoid also increasing effects volume. From ec85bf0ae66cf0127e5cfc7d0f231742dd87f8e9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 16 Feb 2024 01:45:30 +0300 Subject: [PATCH 3084/4092] Update other VS code configuration files --- .../osu.Game.Rulesets.EmptyFreeform.Tests/.vscode/launch.json | 4 ++-- .../osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json | 4 ++-- .../.vscode/launch.json | 4 ++-- .../osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json | 4 ++-- osu.Game.Rulesets.Catch.Tests/.vscode/launch.json | 4 ++-- osu.Game.Rulesets.Mania.Tests/.vscode/launch.json | 4 ++-- osu.Game.Rulesets.Osu.Tests/.vscode/launch.json | 4 ++-- osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json | 4 ++-- osu.Game.Tournament.Tests/.vscode/launch.json | 4 ++-- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/.vscode/launch.json b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/.vscode/launch.json index b433819346..0d72037393 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/.vscode/launch.json +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/net6.0/osu.Game.Rulesets.EmptyFreeformRuleset.Tests.dll" + "${workspaceRoot}/bin/Debug/net8.0/osu.Game.Rulesets.EmptyFreeformRuleset.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.EmptyFreeformRuleset.Tests.dll" + "${workspaceRoot}/bin/Release/net8.0/osu.Game.Rulesets.EmptyFreeformRuleset.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json index d60bc2571d..ec832d9a72 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/net6.0/osu.Game.Rulesets.Pippidon.Tests.dll" + "${workspaceRoot}/bin/Debug/net8.0/osu.Game.Rulesets.Pippidon.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.Pippidon.Tests.dll" + "${workspaceRoot}/bin/Release/net8.0/osu.Game.Rulesets.Pippidon.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/.vscode/launch.json b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/.vscode/launch.json index f1f37f6363..a60979073b 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/.vscode/launch.json +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/net6.0/osu.Game.Rulesets.EmptyScrolling.Tests.dll" + "${workspaceRoot}/bin/Debug/net8.0/osu.Game.Rulesets.EmptyScrolling.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.EmptyScrolling.Tests.dll" + "${workspaceRoot}/bin/Release/net8.0/osu.Game.Rulesets.EmptyScrolling.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json index d60bc2571d..ec832d9a72 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/net6.0/osu.Game.Rulesets.Pippidon.Tests.dll" + "${workspaceRoot}/bin/Debug/net8.0/osu.Game.Rulesets.Pippidon.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.Pippidon.Tests.dll" + "${workspaceRoot}/bin/Release/net8.0/osu.Game.Rulesets.Pippidon.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", diff --git a/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json b/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json index 201343a036..7b9291c870 100644 --- a/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json +++ b/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/net6.0/osu.Game.Rulesets.Catch.Tests.dll" + "${workspaceRoot}/bin/Debug/net8.0/osu.Game.Rulesets.Catch.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.Catch.Tests.dll" + "${workspaceRoot}/bin/Release/net8.0/osu.Game.Rulesets.Catch.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", diff --git a/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json b/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json index f6a067a831..b8dafda8b5 100644 --- a/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json +++ b/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/net6.0/osu.Game.Rulesets.Mania.Tests.dll" + "${workspaceRoot}/bin/Debug/net8.0/osu.Game.Rulesets.Mania.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.Mania.Tests.dll" + "${workspaceRoot}/bin/Release/net8.0/osu.Game.Rulesets.Mania.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", diff --git a/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json b/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json index 61be25b845..a68d6e12c0 100644 --- a/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json +++ b/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/net6.0/osu.Game.Rulesets.Osu.Tests.dll" + "${workspaceRoot}/bin/Debug/net8.0/osu.Game.Rulesets.Osu.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.Osu.Tests.dll" + "${workspaceRoot}/bin/Release/net8.0/osu.Game.Rulesets.Osu.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", diff --git a/osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json b/osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json index 56ec7d8d9c..5b192c795b 100644 --- a/osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json +++ b/osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/net6.0/osu.Game.Rulesets.Taiko.Tests.dll" + "${workspaceRoot}/bin/Debug/net8.0/osu.Game.Rulesets.Taiko.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.Taiko.Tests.dll" + "${workspaceRoot}/bin/Release/net8.0/osu.Game.Rulesets.Taiko.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", diff --git a/osu.Game.Tournament.Tests/.vscode/launch.json b/osu.Game.Tournament.Tests/.vscode/launch.json index 51aa541811..07a0db448d 100644 --- a/osu.Game.Tournament.Tests/.vscode/launch.json +++ b/osu.Game.Tournament.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/net6.0/osu.Game.Tournament.Tests.dll" + "${workspaceRoot}/bin/Debug/net8.0/osu.Game.Tournament.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/net6.0/osu.Game.Tournament.Tests.dll" + "${workspaceRoot}/bin/Release/net8.0/osu.Game.Tournament.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", From 22ffac1718f14f8509d46be93923a5837e80157e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 16 Feb 2024 01:45:38 +0300 Subject: [PATCH 3085/4092] Update Rider configuration files --- .../.idea/runConfigurations/Benchmarks.xml | 6 +++--- .../.idea/runConfigurations/CatchRuleset__Tests_.xml | 6 +++--- .../.idea/runConfigurations/ManiaRuleset__Tests_.xml | 6 +++--- .../.idea/runConfigurations/OsuRuleset__Tests_.xml | 6 +++--- .../.idea/runConfigurations/TaikoRuleset__Tests_.xml | 6 +++--- .../.idea/runConfigurations/Tournament.xml | 6 +++--- .../.idea/runConfigurations/Tournament__Tests_.xml | 6 +++--- .idea/.idea.osu.Desktop/.idea/runConfigurations/osu_.xml | 6 +++--- .../.idea/runConfigurations/osu___Tests_.xml | 6 +++--- .run/osu! (Second Client).run.xml | 6 +++--- 10 files changed, 30 insertions(+), 30 deletions(-) diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml index d500c595c0..a7a6649a4f 100644 --- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml +++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml @@ -1,8 +1,8 @@ -

e&FE%k)kw1nZ_Ituv@| zsxLFXjg z{hFO;>sYZ(;?(N>-~5$k^EC!@7nhVmB&Z8G`{08Q zUVH5|45V1>8zL-4Us6Ed&dv2b0C2us)>WoXcIFSY08rKN?F!_1wX=oa|IMFIA3fRG z!*{sQ7Wf(x;fC%THlf=3=u0T@7Ws$mU#fxNozk7~7VoFBdZmkt*tGBtx|t%$iP*qKbt zMHimJVVG}tKx0r{@Ih@2ESD3~ckE?TMWD%HMqU#fG`!=ZLp8tN_`j=)fX%$Q1wM3I z1Z#A?Yuw0}6|v;FHE!b1+Ul|*@QkKU{{sgTC?lM~*)aG(el9OFmn(^?+#&?>v*4;1 zJHp;%@Ahq72*nKyY$mhUB&=AzIMy=bOU^TRM@4AToGQ#5f8=tZkd;-b1YX%zu_!*r zq;RKbf{A*3LoD?T4;Ajig7pfq6+Gp2N*`JODW7WY}f; z(MKNvk5WFT);Y@L`5`Bd5AXlmpZU}}8?6^L-`3LEtSx3=aDOOpDsA0z--)NS@}nTU z_Q#Wy(Y3a!{+Dnnfw=0e8*stp!*}&a?u5Dw3ZC)PfXD`c^P`v?v?g>JG5nVglmJeI z<8(OoJl=)+ZkcP`vz~3-=v9mn15K{7#wLeb3ZLIzw>{7?E~$~@ImJj~#J;Uv|_Ea5134TY(DuN*f%&Rm zOb?Lc#l3i(iPCGe#4Jctd<3R>5rdAOW>B6ih4AK$hrj!T9|LB$X%%@zxx$C1{Puaa z8`lkPDb9&#F|R1P^SZr`2LO)UCAeuCWGRNKU;gqJ!22-kyqUfToCb?0pMA{_0N>`_ zIN6Rl-)ECm%U<^!J$PL>aEI9;jPn^g*&mSqe@Q;qP=JvUyR<4d79*d>8zH>oL?En`>)x|Al=udAFWt2!H+%6>2Y)B@-*sPf_WxdNw4yi({4n?1FQa%{{l~b1k!F>{1Ct=pW;W`l+CN z*HXm8#^Zd}w}BJbp6NL~e~*UjsA1 zJdBP^X&1AI3ZSfaQKy$kZ~~BL(aK0CfLXEQI}o&QvvfKkx}hDd+UbtYL-$BJ4c+|u zPwZ}aFNz-WhG;;pI$DNj&H$u9Tfd;|_-+r|6a($Oa~o%hAc00a-LA$I|C|I|EWMml_ejTJ*bB)BzCp&%%DRHEXX2%RyyF=LG1tK( zQ`m)7m7UYe5FUN0M|6JCCMszcJ=tkUwUVZgdTr?{VtE(+e`Us&i+vHbIUh4w@ zw*FS2+&#~i>+%4AqQa!@{=fejcdB7B=Qdo8*aVLO)S8_MB%H21q3!r*PBo?C*At=^ z=?O)?HV%bUiUlqLUWz7IzfCS;25Pa^z+)*3l@xonqR7rR;BACD8L+E&uvswk?E^H6 zD=nK@>q2&-=+XiQKRg|Ul4PfqzOQ;))D2o{UGt6UNMky`q)Vb3v01Bw#_krMOhScX zR%96VwOBlA6s)wmapU_s@Y)Q>5LGd5t_Uj@-mYtHqDvh>aXQiIQUtU3#q2~GELbP3 z-Z`ba6d7i|%@)|i@W333y}qr}dqlY_^|IMT-9@L38IWP&92F7BEVpi_(-oUtm&in| zJ;PH23yAwgP6wmQFXJyxIf8zekj%+p4GH=$8acXKjp}UTeEv9k;?j&{bb&4S zech9iOe>2j-H}Cu;O`t2v1-RmTPpyF;+R0UMmf7*LyF0R-`_yj4Ln#YBR1HRg)9pt z?VO@*0c-3}c~U?1v3ufGP`t1;BjNGD{@!FQ&zqnk%`~zMmf|1{7ur}rMCqfG8I*M}B4od?ft~PhxeOoi=8Q7_x3W&CZzih;@@zNDL+y)13o}!kOYn z0rC;*RKZq{-^HW^eE&+0(nLjzjv(H*Vm}4WcamSs&!t?5WGNo~}iTtgb{bP5SL)>>)|oXL0^w7OxCPWIDLw@|Q2 zTdWm+hD7_>BT5VS?noHk2*I9}A@ba(dX}~9qFb<$&Z&whl_@a!!)oxG-l#EEv$&I} z`7S1iV51^hT^P_bBEaI9C_Bg_a$0QzCBTz~t;r2}>B%xeW;qaICbEty0wlh#imPqO z!~&~uyM8&me3yGlmBDlJ3R%zeo#cAZc8BI#TB1WL6A83(5y zu<7Qd_18BIk66_5XVQFEg*6oNc28cQl zGdQ};q`I~Xjtds$)RKtf>V&_*A*)JHQ#ovG3Km_a4m#kyUgRg(Om>YKSe^Z zXvH5=n=MhqmLskkp)typCQmtWVleKucp2y)K0AJRK*ne*OkU>Z-o1Oj_{A@x2Qehx z_rd*$7AHX#;4))7r>|>w0I)2qD$t=QafSZlkADUxMx}G+_M%{Hp*Idb`3kS9-M!-n z0C-54M*tL)=CAdS8A%Jvr5ZUDajLA}GK+Cpr;Z5evxibyTHC~dimZgZTj=ymojJ)R z^p!NNA)|D=93ZIna_E*MNIQZJx@~kJiCL6_9Gh#}Y{EvCU8mmU7w&)np-=WXXbM z(Ika(C324fj13};CZwY?7vTNt$T9+E(M1qxf7^9iMI?N(W_P$vHhPww0=7V-cNvyMg>75g-m=Zm56 z0-02=Fu-pgDj}QqpyEl#LbO7tkV2RsY$-}HD+SBK7uX45El@ktk(8bUrYI+D0tP=e zYwG4~gk zPu5rwWl=1i$gh{%@=D@~jbEX7>K`6#0G4L7z$Rf~^f#W|`}C#MmN$ONkaSkq?RagOq%* z%r&SX8aa!TVW$ivepr$rS_$(*Y?UgqM3*}CLqXs3J`3P4s;4Wi8z&&Odn^i<=c-xUP3DqLX z3}18_2tQ6{AxyYusR(Mpj!a=TGu;Rw zb9Ey`6+~un@@Dno7h%Ix#Lq6eY+hw$jzIhiX(g<&%vB36!Z;wFqeo;#&}Y0la55ez zhQ(k24rP)jmHcIpDM@78$|G+zX*|p+^&lNssi|yr$Rq-H@(2nl%f=v88sn&&iBgl7 zN=dAhSgeR#WSfZ@c$P>gn8h!mq85jP&p3wf`<9!uGTTxqgX#1PRG8+p0I6LJ5;Db0 zJ$-hJMRD*_aLC6l*GMQph_yCRw;C`IHj zR~D0yA4Ne92`wb%UYL@&Faf=?4J#>QoTd&@O%^!T%xh#1$Cg7cLyh}pbmqnRoa~wiKm71R zd=(g_jitxp&Q-A9*E;OV=~>WM9!&*+mO2y}k^|?BH{QTA08pt?F7?YNcy}5;Rei#D z@iHW~VgE>*qo_b5UN>mswk%d70MCu}-phAZ@*NG+$WT_=5Nuv}pj-W_GiMJ2BQQ`9 z1x}&LaP`Q*8`NMl99%eO0tpo;C3H{D;0d?m8Y+G zoz9l@P+~kn-QaUKN=3kB-%RLqPA~jU3miaf{40N15jKgvEo!!!IuHE#G;ntlc?|>D z_-B2rk(|LP2(U0EB(oAJ3S+S$moWa1OYvx<@aC4;;sdid=SPcWg$ahkJ)Y=c6--+$ z`vyVQIlb^3Z3_X!MpVo`)g<7VJq!1!7bg9^9dd`YSuJtCf$)D6GA1SMC7GUPUN--Xh8FnL!2`l|8 zBOF>Qv-QxBKj#3*Dkyx_a&hFQN;jez-QFX3e?b}TWRzPJ&J-EIjD|~Y!$7o$iH|{{ zL*Oc$Y5L>@eLIPVZDHZze||y^z6LP4q_0a}ypMkK%{TEVFlPUpp1Kk?L}lqE2YIHu z>v#ZwlF}U6jc&?9&f8G$Y&0K!_#y7W0#W=4dg;{heLVoMhsS@j2{W>Y4O^v<6tyM1 z%rvrCJp-dHAC=Rhy!*0jj=v2V)}d)-4{>Fv`-P*%Pq!VhMq8yTcJHZiUTcX9d(CWG z${>xL17;}zdb;D2KfhFmHm1*}WDJu>Q^ikp+8;7o7^nzV*s+?oS48uT#_YFGmMcCr z=(itgvXHCr_pv2SyXRN{wdKUJBIX=OHd!0_nGORBJENgFfT`l7sfY?AS&{OR3fclw&cuM5)r!a%XWBDn zvlCW}Bcf81i51o)1VuULk=z!x3Qyic6w(6C%v@qZ!66EClO9XO+(YRI?@A=49`HD@ z=f@o8)rKt3$~+ZjOgqlfvAOUo5mz&XR=8;}@P-nakN^DH z(N_>5ap+HubRGI2LQ`5nz#8`Y5nx4KSg@e z-B&Z{g(g&|2Y>(a;BQ~vx`P*hvf0LBzi1irdY=X#KbK<|x`cWKQy@x(Pw?nrP_-}! zdAZgvC`7`YgF3O4jbvymZkY^6nTC)dt!WZs-2`)M8tXQlq8nSNk*N%d;0piCUnzo+ z&A=cjPV{p-H4|&w=}gmfqeZ4lj)v48^aO|>L*4OJ@6?E`Ef=#)6wv{uY0o26sWCt< zv2h}jWoeMw7@|UK^}%z#5)^k1$OWuqE1<{8_A^#QRJZ-rdWaTLT9mPezbX0@0Jh4s zT%t)1Fcz;(K-QCz2L^on3@8(Yx72vk1{YEy@K(c%CJ|SJ&}yIZTsUq|S2Wb(|JpL} zUHEp|D+Q`X_8yTO3(s2-Wy;~$$UK{rfDh)t2ea@ViVQGQvFW-;J^-rc*bezLaGJ7( z#E$Fp0P<}r z0=C2P+_gQ)X%AqEC%DoAxFXZ5Kb2YS(A3h<)|`C>U7G2!ycAJUnS*uNZ`!sN5;!od zA@SL-nx-I{sQL_oz_MQi-cc4W2r^5r1nu`di#<6pr8O&J5~Oi#op~sh7m>3b(X$+$ zl~owTuX9XyN4-qSj+q1tn#W(*Uk~uf`ckrhUJyfh!Ya3N!}2k!iZz;1L;*mBtRuHu zPxbah_Ohq!Wr@q}T1C)>J*R(0Q9p?718e*WLp7PL2$bS#;1_@;&tGm#D8<2wH6t-b zCDC{c;H!uDBA9I2X!*DT0^J7_QS>7$p7r%0(9wGG8q(M60AN!Ap>RuN+6P`>u65nQ zUObU;J;KB3Uq9N#efj8qZnA9G*{s#Z8EoRSlcH^FN&eJNlH(T?vJ;>aJ$;H(HM+M? z;$UX)vMH?DVqA;$AZanG! z5EKkx@5!0HZ|0G&1j?{0L!^2KZxf z^WvT{Sg#f=#kl}r3>c=H=LD(voM_UnN+Cq`Q<8u(E_KhE;3!)k=hc#M#kP8I^uW&n zPwbLk9#K)@M)b#zA3u5WMM8$D2S?t z^X2zKP^ZTy5Bz-!-lWE$)T-S)xmIKDA|V7KygH0k$+8Z=c*j$5N;&&{kv3lxBaBz{ zHaB9adKyUwqGVHRZXvP}hznLk>7*A(VwtGq*q2hwa7&WsB-TsC;(%DvW{S_10fU_~ zo^nOOM4UudT1AK5-Yq_@VLMn`Hx#s~nU6(A7GS`4>5>LV`<&2LvMj9-H+g(=YoDK! zE9rFk0=)D-i5qh&oK(K-im+b6;T$-k+f@uCp`={y71V)xG6mSSkWFU*{s>~#3&>L_O3NLpl&RoFEVsk=18mn z03ZNKL_t(z2~JX?l$b94qNPRSAZp!-lu);{3eXH*Y(?0$gG3-xOH%}xlVJ?M*~ah+ z3`Eh);XD`SU|kp!=_vRmEqtl)Lc}4@O0qq$jB%;4KmI|DvLlvtV=z2=Tbo2+sD^FcUJ39#;@h_qdGJDpR3ShFXKP6AuDGyLZ6 ztm?TxgK3Krkwvtwi~!>8JbrxeNXx;& z6C57vV1QQu5HyfpE+edG@p0V`08nZ!!OE05Z@>Kx3Vje2=8}n%2M2gH6$aDZSQ(p| zFNa9Ce{6+dLzZ+Fr$po+$7CAlUC_qt9R>5d`Ts8Z?q+>x!1#cNao{C zNvEh_WQ(sJ5|lgAisnpqH&6xccSe?#wKg8o4lomIFm2p3yxkqNGy_j@)*4OD zCkqE7MwAf~JtP=5rq&=#x4iI`p#70|zxUPw8C)((s|biXUG__*qB3EC%nr7vz)Db- zi?F+%Ec?ZTdeJ6s@(a_L3Si8yF&w$(>~mNKM%l67@?`?x0bka@hi+F-B*H+e=R8X4 zm_PAxBR~@{5#5WWm@!VT1m*M**};lcb}Bfx8iAK8;JSXQZolemUq& zs3$(RiSyJ~_PL57b%+EKAyVn8wmMLu^BzqW4v!8RzcN}xi1++7FuJuQd;%Q(+O=jA zy&|k_KIrmMR*OJbpnKBAgd!~_)RT^_H(FQALkjV@Dnvtx$>1y2ND_GC@n60;`ue&& z2Jp%&uYCOR$2>MhY3XsMCr{!?09OVM4|&MVoH&oBTu|3weT@zPRLe0+x@5pHvkJ;% zD3Tw3_#xIv4uC>l5}|rM_~f5YKKaTo0CmJ*^YhSbvs}lb4(O!1RdnR)A%sDY3+H@? znDa#!f}wak)EEeCxv7LI_+oa~X*IXj&D69tf0GnAyTiJONm*3aH3n=moh*831R^s* z7&aL1$ui!Fg*qGOW zVi?)Imm*)khNjOVSVmKHkNR2ZdR zQY#zaFf6hc<7-_a6KPf#BJwEE?4>f7?w~ zQgPy!H5o4t1XKcArdN$XWIl*e=?+R)z8JzZ{t`!y76NnbDNiZrc~mpb;v zfz)uR2H|Fl zM$2Z~q7n^mT4!H)}(-$ zV`kApt*jh?1)a9~io%>#QS~rTj5tk&$DqUnWaX+iM5R0UoqL$2tYY0#pCP6Mt7AY5#@m!{@T4qfnWzjg^-{{|)vGhT3d3GzvX)p%&2dx->UxIfQFk!Bk}O{)RuB^_AiUDWua?d{)^myz zT=})mCgWAGU90H1loe5SK+L73G{ba!(SUY((iF8A!!dwQW4OUkw}m3Q5d>ED4X9*? znXX(I<7Co&sN+P>5s8C23NLxtI*|42W*5HDWq1IiTN(`HHENjcv$hJOn<~1bGX4w| zVMSkcqUi2*mDvF{4uiHpd|BtLDchB4H&Prv-#k4y^vgg8V^@{@@sEFk zxu~b3D1TQ0qgTJcD2f||a2SAlgp9eWIG28}`vCw-(530MGk~{$e)JJv0p#*s=IXLI zY_RwO06$HKcZui$0JLBKW-)NCfnPN4#3aN0#A~Z9K9Gr=?%}5Jv@q*$VYPL_GSx?p zVz$WoxZT*z)Qr0!QI?A+AUH#KNDi=$DMgo2B|&sp*)&nf!rKLkQ4c+_pvB>rws_Th zz8E~`ZA>|gGSPLH%#uD=MX=KA5j_RO`&NeC?GUIvmfwIA1-woQPCZMQjXk2rT=qM2l`g*}1*gx!zpgGicl4ut7O-flGF zOcjm1ak{q|UG~9Evok>PSEZ$1(jv-AVTuD{U_z@nNuWTmvyWSD#6*rT_|VMKOE=!> z^CN8*BV7j2z2GHFhklcojq{S%KAuA`#uY)yn!CxMba_^S_fOhTJ>ih)4MvDIqyfVO zHfu3Y(k)ae8Uy3S0fAq0SCR>1EuDw+G&{y=YEAGh{x3n)%OAb6WvDr4u$kLs;Bj$} zPkXaK1i%=G_DRm_(%p1=6>R5SlwQb#2Ro@Xh-P!1rO+f+c&zk8pa@oMY)+p((iMQK z{TcuQafSTwhwoxiii2t_4zdaZNpo-*fZKuajKIOc0q6o;Epgos0EW_WaliWNtM9${ z9z-tL0dt8-Hb;D#?&0tLzO%O*ei;*6tcT)UOGp7#yhImhJ%N15kxf7JS!j&87G>cbP=2FM?gzJ>B69;owF>F z(ZbY*CoC)!3~}3_IarsdJWiV1sI?j1?ADPLWfW_7SBO<+NkM4Bh4O^yzU5}=h|0^U#& zx|M61?Xik+F?kBQ#lHw;*&{F_VWLy&x~JR+Y*h-~ikM4D&xNH+&caM*Ozok#uuzVQ zfOKk|LZrKZaKkxr8C2G~emS^oq9Q>KxFc%|61~8bbKg=hXQzsu2A(eNaaVGLem%Eh z58c4br)(`b>0;WhTCgJ4-9n68F*kA@H~||V^rPXJSIGw^SqHf7;?WbN=A|O>rLW$4 z>n+Skm$Uz>G6d|U4g)scJ9oLkU(*8s6dX#bici%%wVEIO=tnQS@B)}9)5|Tcxi>jK z{QN#10NBA>W!RJ%kZPKZmk~A%(d^O82@KCJBJhI>lgWxLHp!WsCP(Lb`$-PG<0YrO~hWedqP*Lj8SOw)Rg8+W(aTH=}p8(K%Fi8LSSWpYg^mD{!ymw0lDYaEir zsVpuz&Av%$H`qc>##!=JrFEvS2(?Qnd=7GNUp;H?rl&f+Zg&k)1-PmL>6+3CHtfVD ztQmk7%8CI<7Uy{@LQM`OQ%x>rc-3@gK{(NzhIGm4YE3E<<6AUx1VEpSpm1e#vJuA95UpNC(f&OqFNV$lHW`%0y>q~*|A?F zqMlWYnR54)@fOdFVuL7}`9Uwk=P#>3bfqny1}+=VQ4ytE1U*l#Gm2R}!U_YA{#FDl zV_Z1L^E?j)5D9MaHGqS^d`@(CG-_W`8mIq%`O9Bmy}!SYL+jdKS5YfWW|pDr@c=v< zfJ*@#a7nSw-CVB&fGQ3b6V~%+QP(WQ4?cJwYaAgg0R#RLl8^uV`Oz2m;{kw-H;)A9 z-@FKw86!u(M9-o`7N;kx)IazU7F=0+hK5eVR_&&joGxv&QA_nwwyxI3v$V#Ko?xmMSG{SebSDRMYU!@opF=mM=w#K-Mhyxo zG!Pv1u)EFo(@T%<^5att5{IRv!&^GdtqfHx;7)?cKv0r)d8Y$2Y${^wY8}~-GFAmC z)d+bhD1-@FnDJ~Mtfgp%n-X%OuTAOuRw?kjQ)ae?Vd1(rb4v!O=wKv2x0EJCQIsHw zSs?Qsk&q03xh-Wtp{sSEq>NQTN;^0y&{s0{Ia|#|_p+lyg>C{Rme~Zh?F=;W$zD;J z8o7n^h--K0vGi?q#efkIlnl_KOTY7aMRdi~n&qHZM5mkOMq=MIeQ`-e9%s#?Mr2`T zzbUWO5TqGZ0EgrfPzIlBc8*J4^asarJ2(y6NROkKr9Kb=hLL_n+Q%64-El<-m7<%q zi6<(O*pwiN9w4O2R&JWX4J3z2{!fb%EDzY4j7{=uMX8k{qFD|YAs97ZsxnEGv*lD} z5eN0So_wpo(C6h)9Jz&>kO9Z06pQF#Jaa{;SSzG%znOJ2O@`4}bKay%)P^hEdGX-4 zd>QCtqljMP4psS*sl3M zb+JaxF4yWwP`s`|%2FXROex!@`?}=D+szy>_boeZ1#GDX+v-R_Fv`*&Fa>MYNNyHU zV}ll5S|#GA2~KYd32c^(#$`wLxt@WBU|xuLJ2HQ_)@M*?^6-FpGM7<4+! zLp&8h3^l}o13Zoa@SN3!#N9Z9^$ozyKRDQeqzW`qSFk;Oa`fQ0 zf5YrQe6t7XkE>{}Ls)F)Z0^&NDQ-{Ni`6g&;V~_Yv&<=V@NX=3K1}KdCEG*NwiA=gYZx5r5;@25*avcLT_0S zs1pDx9tRPc;DZVsNGhm#3@s$8Ck|+9+gc=3l~z?U>6%~u8c)jNn|kV4!pii#FwM7p zuK@~XrWcL~NT(CwTvAtxBm^^ZGAq79sg}-^l)N@cRv8iAQraUw6*3ZRS1Q7aTu(a5 z(;E;DJ>FZ*&H_VJ$BMS=)Jd?ph#7Uv|*tM z803=61nJ^+r8X=uekxUg~4GuoKgqs=R;KO_&?b!`*v(jkx zBbsm_mtNU+#x|9LIF&nwm4yH%c9?4O%Cs%C`CpoxsnA>I$kxco#gR%;uLR9gD$EZ~ zB;&IH)D8=&GzAM0L$!Wb$z*J0DK!D{PYji(w9-ac3V4RCawadaAr~u3r-^#uHRQD; zTgZqF`A}9^@Cub{F=yG1>_%pY!BiAe(qKqgZ%#000GnWmy6)+%9_g<(J-h=N%wBbE$aY z97T)w(H{Kei<@`?nC%d)*ME!-e~gjDTE3`2l$$%&`uUO^C#*<9Q)k6)`C*7cI%}yi zSrFeSknCxZP$VshboE0J{y6}w%YywZ(iy7zu+3^69;J^Jy^^oAX)wv*&;6e+a@;YW zYeBr8!RDbV)uEzkJRiSvF~U%tL2t&>9a*dkiINFk#LvP>!w4qw7;@I&P0R%1MlS z;sC?uyeg^Na>~>cjB^p?2^V~4SYGl1Zr9I}870NaxGI8%WItmk1{?-$qybK%E8(n! zK4~0EdU6dP*XtEQXYCihrCabp!NM9IvUVkHm+i-Ebrrj-e~YLFQE_#zr0cxEJLpAb_w zv`kRN9N8Nx0%2Glm`apW=^}JWCR2nfDL#i~{^gP>aU|40!FY)b&f%wDKl#VkGE{EU z!liFtfBkhl3XB@zBJt4LEAiwTT29?IbNT?iFae(R}f`y_2J3z?If{ z>vphSO{2P8acr^pN=`!c>Tp4B^yms>BCNxJSnkAGt!#nN&up)$UI5!dvSg-51*d$JWA`qn>7X)H|Y zi5rrz1H3XjCA%{P#8t*ut%b(a9MFX7HewsW)k7@m1yiVut1YrOg$*j9xm88Lcm$TZ z6@ar^=#_p76oE*PqGg^eL|KUj|0eDx@hd?MRUGsred>b4 zOc_V3R;oSAPQgE zE-A8KG%R&mhGT{D$zDm6o-*eN*LfJWO%?j$){Ds~6C~(`CF);*DozUX*Yv4`1@}>UvDV33N@g#*Yrj8#T zJoX`2aMz*hYhzy#LXN#PKYr<1Yh6c*n&G^o?& zdPM}V;Ym7opqQ9s6GdlQdgRGV$vlmsfZ5tH!K}<7r;}Mw#DxDr|5_vwA;We3C~%UQ z36>k1ns@=4Wd;D$Z9M)Iwd^Ce_{5}4I;SLQkS4V*nZ3^os)WK$t6sblw^3|t0tGT_ zh!v4FMtaJJ5V))J$?*exg6VoJI9GyO(-E+;GZlvS&c*bz0Vw|=YQS_I;Gms87$6st zyj}+YF09HZ+l7`QP;l?R9|r&hy9$DVogSY&`r~Jek#V!9VC@dmIASYAUE>!bKok{; z^VT?f1QYBA+!5jl#o4=GWMj5c>6C$~e?+Ox`b_rW*oK`#Iz1$d34RxKx&oTw5X)6& zCf1eD8Lak-Y2QPDF{@t6-I5SqkKgel=V;h0Mm&a}GZ<`ym!lKbF?NCjmLJl4iD0$YjL=y5lSZlkAwGPLhf#H$p}HX z<%wXjFS1cQRD^97hxT0&jpeA#Fut4IPG^o6sC2y$kWpGKiU6KJ03jy>pzu4=B_UR* z3KCOHG>Iq$`rWobx(b-_kuQ6i)}Ugsy4nF|NlZ-!GtbFEJ(y&uqFH%nVy%`#K7VRO z5)l$;`Bd5<9yVA6NyLP@c10*!Ms8V#ESnRLGrS$%;>gJO9#veVJo{2L!Re4>CKy{J z)1Q)9nGb^ZsmEmpV)!*JH}XXGZ5cGPjM#c5y9(o~ky?bSuf`~X+t8GjCu0-U?1G7% zH=>1cmKNsR`o4jT<*MOHp3x%$jTi?#_*#v@FracN-n|=xK| zp`~2V-?^7w$gphV3`L7JXfbs@l#Gf>5mI{a*DuM(%XV0=SG>IY?mIaC?>F5!APvDTi!Z+Tlb`&=g>`X-a!$*q z2S<X-54_j`uS`cWB(U!r>rip_?n8trH$`LM}@=J}i#Qh$#%e5u!J*Su^q6Cw> z1(}fVWJ|Y;Jkhe5%(18{iy=-2(V45(4D0Kz0=~{rx)_x;9IyER03ZNKL_t(_?)Lcb zO%HCg3=^_Kg)+wkyC=g=Hp!_1Ewmh-ne0&0GNl zOaGKXD%Xi2z#5X;rnQQ1N#JaZo>!MN4q?vlY=<^^IfXmsmCQTxrO7396H*@Yi@6Mz z+0x1EX->JczvOUU1Fu@sN=6V-!@cu9#6dj%Wl!p+Q=2xexAe%t=kPF;0cy81Ey2N= z+nT4k(nd)IHlPpjfDj+vfSftxC9ik~S_hJz7r;hGqgqR2LW4!km=1B-;rI}@CI-S5 z!%LZ0y|`@26Btz%awXG1^AtdGAT47v#E`HRt_h!Kme2^BBo-fdo4!WtEDDVyj?2xr z9lsn^yR_2|Z_52V z;0nHo(mgX6128o~$75y41oumM)w@iQU{z@}vKqkieGeEa{Mcr4EMD?u7hwLB6oJ-i z-oytGEH1Gzo(cXqHSA*s)rH0;Fw(^POm+K8OeCM;wNZFmBC|}(`dQ7Kq}5=Z;Mzby z!bc-4HMFEoV%@D4vlC|mI=IuNxOCcK=b&dj!aa+9P$i0y7fqrYf!}i7`AfRWkoeM( zVS`yuD9LOLtx(M~DheO>Ot6gzgpIBUaaymIqZ@umX$c-^QQUH|kZaRnN>YUvF!9q7 zd9uSb7q3M!Q~rS|f?J%7z^83?{WLJ`>S^G(VZeGD*0YM}epd1J3=xp*x%vDtBH`S+ zogZNk%Voh;xwQNV5Mh)v*a1sFi=yAvfB*n!n1Kb1tPA zF`rY-7b5U(N7S71h4Z8J5EN{j$6C2w^*MC};qV?tKV48@R2D9UfFFgKUdy!`E%C5m zXXR2+(?xPhE6q@^GnsCdoQ$<_ZtB_*cVRkkwNf%!O(KjH`OT2 zCFAIy4-Y>1iuy5}g{dpq{NM*~;TQnt?+*|0g8zrO8lbV6k@tAO#DInqD|nW5^%wx% z2fBk+x9}a(SAPJonok#8<&)!uD)`MezmJOm#lKqO;BQ|Zef40+7eJ>v7KQfeqXIN( zwP|D%T_t);>ojN@@ufy&MtmT7mY1W_gqyXXdA29eoWGjt;7+fCFNpaZemAL1d|f`{ zMa&$0u=|;=O;=7dj>KpIW$AM(+HEY0-8|Mp{a{8KVv%Nws;Dengv1MpvCl)7LS2*4 zO+?ze2oU!a;p7FA!B5)w&`P8NQfTnV+idCYCLZSF+Y)((0UW(85f^aPU<7bx31ysS zDMelWzQq!D78Z(M!~!xhhCt4v*fE28w_Y$z$S{~r$hDKQ02jU+8EMs*3lMe3MeD2t z!;!K*w;5Ot0%v1M2{jR@M7IPrl`w8vYc&-%S+*sNC}?9GO`^@9vFW#S15IvsCmR*a z(-Ds-6plh7M zKHu=2Ty*X7xNZPQg~B*1CJ<>f@*HIn)nQ>&Q*0>05+z_Njf88M&7UQRjfW^4`&rG1 zL0?is@)?$AL(6cF%j(D)8vV4xeE5 zBa8~1;0(aV$p%$FxnUcY*lc=9UqOqpvYJ}TLMVgavP_aD$a7{j>(W7rIpgZOEG=k7 zrWoSgG=~p_T1SjG7 z!<7fFbGW7fI#N5JDxfw>9mOb-S_di4>KF??D2MNc?1I?k^o}gW=OR)|^X?X>3H%Wh zT?9D1bN3Ei$_cH>Fk@i;(l-b!j=-6oGAhZpt_V3e*J|#0rh)*sNN3Q^30Oc8VWd^* z(juOq=soL9FImeKjWn``Rjg61(KZxJMb1X$#z+;_9G>(dVH^(KPI+=WnoZhuTSUr) zs>?R1XRppR(n!i0F6jGs0H=F-N2~`EgZYqed(c-y5)3H1@ZwOTozY8WnXLpZ6XkZY zcZ(+oVHr4fx1-&=-_7}>=FJ)7TJOD;N901Kfewz>=|RMvQl*@=YlhZ9(*jm18>PsJ ze`m8y2unQOkjql5N`QF-6og6}Y1^d|7ce;Z__ipaF^1PIH&!=t`+7k3WY6(-BRo<% zj$<<*QH;y8jGHW+;bop{W?LMMZais)_aB*teaw3h3Ccmwy{HYbRHIGXQL6}*eRpr; ztq52Vt_pRaCQ)w2bY@XD*Kv!c$q}&*N3<7a984GY2%EY^->{C)pk2X(r5}T@8lrY$QCFP8plo!kM$y; zUkzQdd|f7i!o&^g|N5{0ie|3(a8Lj7@H(y(iPIK_r_V zikIy&cL~y9F*B0Ldu+aaMa=CzTdLw2qFS|bCI-_HoT!ctiE7lVKVwHPi)UJx{6-sz z!gZ@sSy=!ldcar3TsvN!zgHmxg?Z4?F`EtU65+KQ9`K`u?A+PAxsPUZYv=CX&Wm?Q zgLC^98WkE0XY=~VV1$Yd=2*w4PY+M$F@)_1JCow7k{}?nfW{XtGwIxgv6QOwkjFv5NNvp$7*M;`gv;+8 zRtF=V&;I6nhlPIrRb*q)PggA>C?A+PHe&QF8R}~)u~h(RK$gGFWM&Cu+S>|Z&JHiu z$#<0D%%uv3MUNQ_-+;t))ai zW?Km1a|?JSs;y|3!;04VSc7d9qn`cEvew)8q8PF7$jLjcXTEala(D4Zxw9fQzyOY! z-RaXOCzz(=FpsB`F-3df_U;QW?!I2tcz4(z{zW#8_NN%Dp*ch{?RMU2=z;C)Esj*7Z;kOLcf! z#jNsIkxQH4DDI7L7{?r2JxdcbnHXN6v}zW1BU}-1fK~!h|86&tMay~V&P_C%Jx=;PS!d(%S^Rmkp(h_d zV2##@HiN?edO6{|A8qE*;pu&}na8JJv)P>f^U=vSkIBR#!SSh&Lh;o?c;W48&Kd*t z!kufS)GlU?gLirC@8Q-C=9B-t=Ef5-XmT!qurqClno=v&7624cX6VRI4)~Cts|Fr% zN*j%wJr&AW6tPLA?UHW&&s>E-93<37Lh3g&nOU7jFI~;btto#uIIcO1P{d(j?@UR|d; zg)SUYtpX8=Fum2YX&B-hQ{{Rs?xc)7BSgp~#lZ?g1|x16JtG6?*Ej}1zqUox2-@=< zo@s(1l~8TYu(U1?6YIh`R}L9I^T1|Nqc2FX{G$#4xU$PrVK)!|@fAJ@aO;ITY)j7k z-0CHy%J186|G;-D7DvI}#B3hnVFtT;#)QXUCnr33Zt=S!cv9~om#aSjK%SA+?Rhi+ zcmMkdU<|L@@fRya!^1V-uOB`6^y?iwP^j3^3EdARM1rl;w1WXkdz2Y-xLk|#C3~1V z+G5K?l@1IR-Q@q@BRA#Z`7+2m7xRu}G$lQLhjZcDv2b|zJVxUg9Z#)|?aD4|N})>f z8G22yPH*hq#N>YWt(R{7;MH4iy}bM8OI%|Phxr`lZJO~vYXz`*unFUKpKS{#c8@uG z!|WgPx#Lg2KKbna@h4v&fBMboSC4k?A3c3=fEx^VdBT_36uq!-OE%~ca!}y|S@ruw?@hc`9tI9+?`eL|rm0<^= z_M<5S+7INI*P{IA`+_2$dFZ@+Tu`!D0S`{s)~uiT4kM0j>+?x;nu zk;L5Hp=?AzfIHC;!^1-!Ox`~@`QiZ@%<(_IIr-$9rYYp+7mkair(%aqSL5AD@HJkrk&;zK04QcSp%U6+hNJ zr>Qh9(;CEOnk|j5Z^leSB0i(P^5_vvoV83bpM_kNBRm(5@<|8?-QhhikX7aLGg$;g z*DgE5=9U-PaKQHD35pTzAC2$;moabO;b4UF!<>?bolkKr%5@g?Q#3-q#!>fAI)031 zG#K9K5hVH;3tbE3MJI3LR>mZ47JKA_=7b*vm!51vGP^8EEZ}mVrhS##ol+1{%90U^ z$nz~28?o=ey@)P+G^E^>q&^I0D9Pe>1G__>jYj0G*p)+8y9$Tc0EspJ@fdzz_|SSe)rcnFHf0fSy$_x4F=Z09Sti zFbyRtK@?3DL6%-@UwP$~AN=44E}B7f`AK{O_~e^M`~(dg%@i*vOKP2dukCi8uLu)8tKN*ntnfw%JW;x>XSvqY^;96P zNm|!G8;M#A)0Smfq19o!gWGc4a&~uiUb(yf&Z~RxzrmLC!`FD3=;b?@^fQjt1s-gB zgM3wK#$+ayPjh!}?%dtKaTj+H<@*xQ8E_u|=2(HM@)hTmyHoTD7!@dzhznPo)7 zHoCvJkCVH-l+IzML1-q1$jrN@ovLFtSOZ+;&Mxj*!_|*RPY%C&fU#@$<-51uc)L|@3}QH)aq_u}8dFc4#Xw!a1M5(xRl*9$)_Dx3>MYmU zFd3+TYb)52;zCv`MW~5QsfEVnGh7hxi+W`DR7SPcDg#2yc|?G&P)aF2YpdHM_n({` z1P~W5b*T*lNwl90g|dNtmcLE%tS45PO{)hJ&Xy5s%3vfwX+AE zd+n{a-umK;FP5R86Zsh6g%@5}c1#=(V4xdr1VZ8CYRrXR1lT;AS?1sZI5D!Qwn3|r zkIffMEO7qLJMUa*S)40>2t)zm6+kBs5BFZb{D=?y&DNGdE+EpjvpNQQQ#geM6WTj8BzT%~$;w`Naxb5eK`v!gKS>z2HEC^Y&6HLC*zi_e&Jq~>fGhpnZZr{Gc&gHqs+X}@rR!l{l;Ak`e0{J+=)c+WFUhV(toqg2K2XEpE9xmVEMm`>3BX=^y zsz$N!9;$uS)_4NH`REPX86W#|srENO(cc!=`%y(pcCK*oJkKb-ee=y}e(* zd-s>`-u~dty|-W8ed+G*i??}Kn~NHy!u#|1;1h0FJO1p!@$Wx7{OzZQzxxa~I`Tji z$F4BuBPc~;k9G)=TN(X&f!F~jW7tN(1t!6GgFjJ&-nB}i@R&*G)zvcQ+Y^!JjT*fS zkMy8l+Y_d$>}9uaKd-%SrB_upYjPZ%S^2JQ5V(ef6X|#w^U)vvapQmc<+pPA9Vz2( z{`cR1|KI=p-)q+a!gU7^Ah2V?+ktQ$VCfK(TkN@91(q5Q4n$>esrYR;i(S;WG4|c* zuIAPc0Q3U=?i`-q68jw<=HsI$u3D_2?N&KASspPFWpHr~kR1@*q+}2?*`I$l$+bZ$2-G^HMvnzi{`*-@pCwTbLs3 z{($p-UX|`k*b@u>Vif9#@04C`Qu7#a+J^U$zjE)+Pv3g!zkPiC<%7e&{`25Be|!2T z&gzaoe}MCve8!YdA!|ODS?3OWYOmy~`Kn_%6KbavxWO7?(Y0S=*Tt7wFmmMpXSdO_ zbaG)O_Lf2A5=9&=wHBG+DqEOM;5;T~Fb7AF?7s2h{!ic9{~24(EzJ90osaA^D#KJ% zv$1*GxrjCG&wY$B@a7*MM&ADAyO{Exe1;=|e;)q#zd!kpPmlie#f`5X-Pps+DR91) z9{^^*@x0e$z4u?g^FO_R@5_gWzx&6* z|M7>%|K}f0K7WYACmb5(`ySx!rp6wAg2S_7v@zXsgSo5E)v6vctfkNIkb^TIyyO)N zW5etp=M<}sQgrU^B5WSuA**f>HZxi0gHorD4sh-3?tlB~-T(C`IDvxW;o7?mUiFHz zof}cqZ`}Bwe{}NCM@N7B{QqU|&A)6ruKT{8=jqLOm}h__K!5`Pf+R=^qy|!=rO=dY z%aJ2%w4)@-TKOmPlYhusS^gnjS&5w`FFRSXmlfNwO;HjjnG!9L;s6c+K@u|oy!rLq zuk-nScU7Ib=icr&b>G_*U-j*CcJ11=YwumV>eM->s?N$6pU2v~ei@mOsr6bW51vxN z;n_BK>3}7eufVNRI-`Yw(YZH}mZ53VH&p6UzLQr!V-h7C7LV2y2}Hy#g}@RmKqrj} zFMF`}G85f z_uc!cPmOC`kiEC~Z~^Eym?p=qLNE@WlTv=$NorjnsoWLp--GYL-vAW-!vuA-hDqfO z07owHTyJU{^%qt%Ib$}XW_qyykLHg?++>PmgwS-OOk-YV^tR2@{mF-KVHh-i z@A2_l4|k2;f{W_awc1OPe&Xa=RY1)M=s!L>eqxc|%%gYkaF+=lJiJ`{<|`Y|{&4H; zGPerM3DMF|a?mR&Umm2yfH&FCNH7p|7FCtHA0UNgU9OuwFR!Cz1y7AlrFbX*&@OQH z4IxaEJtfQGoErX<4iOM@MOHT$A5T1R6P_dIVYsu{8u#boqBF?M26L8fp|H!BXd#|D zBP~d{<0uoBrr&)R>jc)HeQo_4FXK6zFQ4Hh0B)lx+OiFXkr8<6HiAugSj8SMUW-L< z#>2wVX}vdqU_YFP%j(#~nAaam5gIt~$V{_NG3qz8(vfOnPozuR(~sRf`_YFc?!QTM zqDpW@msk&gT4OfL(YfiPbCVCX*L9l5TDTxUcYtQ!-GlIv*G^&1{^` zN1m3Nschk2h}EN*9Z^(F+Yfi7RRVRvWl>ib*DMZy?T-v);M^J|dHxR{P~4*BE*Bm7 zh&>Z~rXIU%?jvuTc*{xMDV1S3`nz%c1;tfNVn^>@$O}E?sYN>NT*nr!^a>uKN8dd;lo6|aufsx%Vuz)hmN^MsPAJ}c`?q~RgocRh3kXFMX%fgtnad2dvX-I$!u0ezZl8YVos3t;PA=$k4H14+JVzGD zR;Xm=O_3(Q-FA5T@w=HCwD#4P*8b`R?guWNxyY53ZEa-?KXS*sJYc5jpJTjw%C}dCSmR)eY8ki@|steYUYL8bv7%K&$RjNZ~15!HO4>D7SSRDXjoID^%C9?w4&W2uQ$cpqUk1b1>*zmmV;mbUv@)WancP_WG930I zS(>`Klg>!uK<&#IHUtMU!cMyC(UM^dJgr*9|3h_a3yJ4Taa_7nq>wYAl< z5;%1Zl2?@61JGr*#M6f1z5%H5+=;~vK#klhxGlk6@!dl{VK}w`y5$zV0f@&=d&vJ9 zSgrBWoa>^t07|uPUzF>qivpR@;*HNXaU$E=HqI@U`)u4F zH+eKC+vxxr1vU5vyPKO{mQ#&-C|kEI>wsdRL}--s%=%dCP+cfNOx$GcHDgL^l3c*#_tq^r*1FpNtyk%yA2%JGiHPl_5{;s6*ETb6uJAN#IS!>g_h1O z{nD?zclybD!6`8G=Vfir$v;8ReXJW4ONS55&VBqHte`mid;gi`0o(+lJ9@g}rDt?L zNFvd6C}!s+PZ|MovVo(o;%Li5&?{IE9Y{>QTtvh| zO0_d}^65%$lkaof<3)*Nv9fHc0+cB@00b>wdyO*Dgt?Cm--ZQNrCswv=^EKxTQ;d8 zqlb3%CyMazixE9b;R<2s&qKYHmrqUIe7GbH1dtOB4<9*lgnNKfr%rJXKxete(tHP! z2mwQ>n=5}v1-O=$8vw3Z4EhT(soVh6Gw?&=*Qio)qUAcI~fg6U)xxD@|Nr zM`|}F$h+}iY9%VJg8*Z68d6$op8wG*!Wn2mbW-u4Lj6S=gLt68i^A1)25@7CXD1)O zo2Q!^y)hNIglK-MumhxSk?yL2;(1vw25qbu<~RNLT}s0#3QfjZ^7s3jb%&r~iU4KxuVKa`b_QN0#BWKYM?-xn5T3#@!h zmslntY%QU~}@^44FQgHJc>jQ70J} z1(%orGZBDvH1N@lQ5H{mu$y+hWaMqK#GjU~(f_Z1VESEmD%&-TN0qG#$t>CdiQa?V zY)=1ge{%8JS1)N5a*RW_H76=O4X05HL6~SCxJcx$7*?R@s^~R<>>S8L+I&sK+FJ3F zY*De>rJx{|7#5|@3miXjax>96>V(t!hDQIt_SDSdcZ*=}OpVPjHFsI3|H+@QLVav$ zhG|!nprL}qtPf3BB&_#U)Q)766LqF>6^=}U4ecgB$% ziWgCH9M0vI4CfSfx!c)aJO(WWBORYJymTy@}Zf z=+L|K4gZzsxov*?8R03(ZYOhd~p(LSk&gRcY8 z@>1{Yj){8Y!Br)+UZ&G;?V@-PXTFN%Rv@_GYoro7Lz3Ib$P)B5Ok#^k9c@ttdys`s zOy=zu>!*TKpZwIj}F~XzNeEMRDmyGY%jDGiEV#Gfo}xW4+_tyYA#FHF59p zW=qW=gkPz$DDtn(vFMdGjjiaX9=Uz(8!xT=>9Z?eeqrm(MeSAZw*i_@Nw5KxRkn67 zY@<7g1CA;phB?{JN{LWvvy~M17P8LLGbwZ3@mz`v3OdN9Wx+94Wa0xz+)IMy!s=8< z?Oaw5p7@Wx&^$ae`RMI)PrqgI;oH~;Hc7)+=dY{h$Qof0SBoEh&pdTMx6P|xduinh z-&w_NPG1~lKio0xTd<}NSVAwq;88RinelQ!PUOK!<1oo^Jx;1CqiY}w(a@^^*Xh=Z z7KcWW)Hqj5u8VR>gkaRNFtzyipXwO>H$1KbqPnHj@YKV%ad)x&kN(}}%csT{G%)4T zu0C8QKm{;2{XBRloi1Z-)rHQDOK_NUoX%V?fVC zlkQSL1{P2Fj{?`HHbGo~A-ZC&=f&@=TC z4Exvza(Ru9F3rCGzS*bl?^p$>(c6LODQZpk5f1m1DRup16g%_ey)0dw{`yPHfBLM( zb7z;?c7tWaxpk#+utbVnseZ-Cj@=<_sne(|js?>}S49dzWFbljxT5L+OO}Fd(WjPW zWl7S;pJVzsn&>-8las_L=^7EJO^W|)>XXc@3!nYSTW8*PA2X>^xiR$5?Y9W?*AQsA z&7oP^(bPk?to+$`FMRqL_HE=N=%dr)qih|x?kzzvv%iTuQFga*Ba0q8dc|U;Qk$5& zz@$dBkkzBos2T<${QLoI&7a)j+u3YFI>jcWIIq6hH~;ZRXCAvt9H^PE$(cy)ke+NxNRQqP?sfTV|`sF9z{KubWow2{U zDT~I#2MimAs-#U-fJ7~2P193X>QmG$`VQ407*J?SWl1_FJ)+fO5)5Ib*I%2pb>(iqN#yvnd zB$QhFyTP_`&O}~+RmTk~dwFiQDZZpsEYSk&#GuX`wQ`k=k)=De z@GO)7g}CNiQwF|})k)F*X)~-$N@nDOpeJxV{@3o(n3RUHbzzlh?$b}+GyBv7%z)RX zauI%fMsKy%0+vB-RJ$WHYvVRxjoX0Fd~4;ue4kGavt73~KlOt)WrC7CxU21sX@f&L z$@|67R3ax3Cp8E-Mkqi=q;n7x33`BnDU>Q8^DM{~01XXGEei%NjHOhX86wH6!o|Ib zJ3%F9oRo|a1VeuYQ%q}~|Jd7^4WEjOQO@3uDO;Kxz^E#~m4M}9D*R7BJo%QJFMjsh z7ysxP_RAle9%n}5B{rP3f9R!l+MrW!P;Bn#@8BbAOGQr}u|iopv&~u;!$ii=xkV`{OwXl|VOhyf*+>$vUkaa;n)ydGQoCtL zRSS@gZZ}XEL(~vjxjs>E>k@l?&j0MA!tb-GXWoAwA2~Vy2Y)%dG*?M3o9ZH~v#KR7 zxQGM7utZD2@)vyrNLF?BOy{#yRAM6NNE<{%63X(aZ<5CPxpI;`R$oZ3d@^aTH?&3i zVv2%k1M&5}&82_hO+1B;Dr z4VGU$wSM;E#NsRhie_+u8vveGBSsmr`>BP#u^_FpYyfIWy(ag=fmE^=VlozPd#l-i zZvb|wEt0}s06gM7c~aj14&>gN;A**TU0mhU(>(Z5Mt3&hjvai2@9;t-r@vM^&K|&I zJFZ(vO}Fae=mFFWgI5b*M>`60&N?--0@p!UXuuX2;sR$ty=sONvy!ESk7y~TXKGzU zmMxt{@9V5-n%>-eG`?S7U6aw`i2#o)blLMv4?gJ0SDCghO+IiFyMM6Qx7nQW$8+@7 zvTQ`d{cOOv4dC_2)FaPd_~U2RzxgU#g=&&FshC?9R+Ec-Y5SGLQ^}<<7FSQCiDt)B zkk!Q9nDDN^7f)+(Of+7p#{Hij{}8sdV+?~rBuCdQe)ef=7JS4!lF@{${CK;xyf%9C z()>r?&W!&w-5=+H(U05EKWtQvmD}*8v&CO}9Jjgf@4vG8NJow7Uz)@9C?g^_DVHuqnNmLfUf-Jvu+ecc9&?OWu%K!b=pyZF(@3E9{P; zPORa`ka(I0FdP5J0*tUG@4L84M)7st8YUN^#uQkRMvWNdvQqmNLSWEtQz#pxE`H+O zLifYeqYFRxC|jBF;d8zzrjB0CQB{usmRSg73GH%UL~0T#{W zNbATPDSbamhR`QhPKOBYmV~iNQVQ}=Nw3E`Xd^XHbxMIl-x8(&+j4BTW#G~2 zy!E800^$asrFg}1 z-$8c*D&^?WV=-O_6G-j>;szju2qZ^t0J=x^;8_UJ!V?V>?5!Gc)3~=huZUZiU>DLL zh)#$Vz&G8b8-UB^+J)g>C9S`4j(Y&z0I1_}`!H)9kmR8fdnw_wqFe zM5F&4YB=LW&=cQHK(V`0DlvpCc>zimfC5)!sblGxr58i_>JF@G9?^z7G1xRokh(oJ z{=`e`W?1$XeF8|@iRMsI7R#2-t&AL+S@`6;4*z!_VMppT`e*b;_%R>7br{$Yb(>gC z#u+;@H}^9SAO43=FMRUdBZsD$5yW+!SNvi0FuaYZYH$v7u$yH6Pgmba5aR+}D^PQ& z;hJCIX;@qU;-Y|f4E~~Jk!oq<>|-7UAZ~yn$U33**6RA?J8nPnJ5SI5>?0ceM^>Dt zY6-OfKX$OLylRe27-Oc*q2Kz6gM zvUMXzN^^t*Jkt$nPS!`$b%9JnHuVX74t(wd53;2%nYnm&vft~N+s?#^#bdwtJg8CEUrXqM5wS0@`|83C*uY^{)ewwQ)0aGDl}Dz4RcZpL4!3EF0QUO zLZ*z`3uU;yV-P~mF#Dkg*)CRYuzxpNA)ign=Y;2f=Iz{gxOizGGfP5QIW*!JzsT1* zc3Pt$%=Sy%l65+%cQje6jD3iPYSeM`)-FLkZ4!{S*b)^A_sH2QjZB$U!gS{=mNX9? zxSvexd2Db-F9hv->rglZQ1}<)W`dFu`L5^~{+2nUERMEtO2R7;jEULHe4F)Pgry}m zjGLy~v~a?K4gQI_f9Mbq47UJv1xGUP06OSY1;w2gZrGMVN{n|U;cN;k(`#tRQhC15oF{Ap|j(% z7i)ioSNitYOB@_QQ zLS+GWa5mo1_)Uw8pM31-KX`ift+#HRzQ`mT_7BHT_(B#(2yOCI6R?Pi$h;Kmv~FeU z1jfnvRACO}VuzxM@o-4e7#d!hpI}s~BDi<0*yOVI`fQ2j9=~VmEjMqSS4+vFxuDP( zF)WEajSTz}DyN7zk(9JbQYTgM222*ld=v#=ttCqz6|!Uv<&IEACy=s2PUDkM;vIjM zUOF-b_xQs6hu)P=_XUiO0iDEl+QK~DZ)Q;)(Jc37pdiBVntruT!1Q`$%UU=xx zAvOYv(Z4E2ogr^7HP{BOMRQcCHIk??ZOT^OI?FY~1D*oZQ*JA-lmg7pFR;66PvSlz zsSni?pPl1Nz{88W))lwytU;(mY_HXUXJT=m=i1-a_uj^J0W(!1=0t!xs=WX^Ax)kR zD#M|}Wit1i5-fwZ5ay@tso2CPpAzFt9ULMNoKgdo339%ScRW!xX~k+)p%qbLmO6lJ zB)p%8UfcuV6}t9sd&`TF(am!!V>d6(f9#Q&_usEgqY2T*ZVde^i|+>Fr!u)7>rOv< z$M|hWChj|N;Xi(Df}F0Z)+QD^vR;e zg^xXwJ`F+Q4gay-dLN>{qqcb!@ZS4Q9A6mvXP-UynP>F8;iO7x2*TAqYFNn7G$+NmXwTIIy}k9PQdoVVtv@qo zVQRL{Cc0Ds0z{ER0^@|VB@r;9#tEdwaGZMhR(%8mFhAYQtoHHDTgsXD-FxaguWG+1 z$*8Ufo1=>r(n-OnWT{jo(YYx}nrG$nIkU5>!6Vo>gAp#UB=r@r)h&-y4o?a_gvT zHa7VPfV~g5WjZ&Qg98v26fFTI}9gIkD z>m=g%m4uut5F`@f7??M*k4W+&97%TL5dxva&zd+J#?uClA7>-*Ydo zR`}k99&o0rVb+DJX}8MFHJs0?FZq;%PaFyduwodgyBu98Z9#ec6D=)^3sR|Sfbq!* zy=l!16KRe|Gc}Ub zU`L=$UdN4SB>-1u>Ptpeh3OQv9#s4M` z9ELR^iieBApy|D3kMwUAL=yMR(P!O(6v0XsQ|26!#ton5GBeIfYq8aP!M=oI>PPPw zJv771N!qL=lRsH>Um`6{GC@e@uMq;cV#l$0qli3&^1kUF@AjE_^*EO z_^&)QG&Zt%VTC$aW3hg+efS2W_Nc<*Dl&9sY%!_|5)3JS$<1_Dh!XX^9CmuWbcwIV z&b;k5W$b6$x1?hy7MTUaj8Jx9$#T^yNGOYI>2!#I(rvOXOc1omGy>CFvbb<>GxXQv ze_JV7=M4GW6a*%aB}q9DC8|J%ZGx;$EX)lgV0&91hP&U3@%s6k)pAYTe26!bJd!|M zi6Z&3Tvmff@+H$-l~5@jv`HHw6Oq%?0lyJb$AWDSqB=vrU^gxbD;o8bOSZ@qHi~m( zOp`gJZs8*u$HKi~r@3XUvh$9X zQUW)=V7_%L6=BhdR7ZlUO2`x`LD2?mSQr=V0E2=kKp=e%v{8DDK~4UK ztqF5NE-U5k6re~_GFRDnbaq9ngh6~ z{)(B4N$BjldiCo!LT2KY<%tl^mhKSj7O)m~|Kv+%ropH-7iaYU>mOi8R56LszuDb5 zT!jUwF8j>r?Bub({oWJ5_4M%Ut?f%RTUUOr0-N8i<67!nMr!;~Uvi!jptiKiU-Y%5#4wpG653h%6Hh`Uj|t_F zOYtO@aT!LaQqMgEgsfYcR)$tojf(Y(o>`ajdK(U20;F5eoWYH=;h)is7~Pmg+^U&3 zLXtu6)i~z55DO48fOg4LTgW{0;`hS(##w*AZ0DL5{r(5UJ9$$3N81}yd3EnpgP3T+v?!ZsvPNc~OAi9D zEe=i8IdpKfW3t9-T=w+V8!<+P8TIiNn0>g9{SQCI$}shWjN3T&{esl!{MQc(WAbET zY?N8fhyU9TvnS@(#TD*;hsHG8P*4TAzN~)0s=n+lVHJ~gReQI^8b<(X@yOIFjo=fb zcde=q6i4k@!P8J=5~uJXTq6r3F%ps!JBAKzt#9%v(?h@d{+Y+_7S|i2e_7!=0zQMo zjE^4v_`7cUtq+gPO|G9?Uf8yrC{B7f+pKtLGr012k=EiVU(!QyzzCOy#zrUZvZ-k-{S4U@-02NX zvgztpyb}&HhwzA4Bnl`p8P@yDCe(xt+?ihEJ2E$9eS$)PC=5yTv)*wfEmu7Yse{eI zxno#zrLt03(Y(McS*$-Wr_m}C{EETt=O(rcxv0XYAi6LrDpUWG}xeQ{D9Xswv{>{^Ut~({Q0GJWIO0@q%5Ap^e3=}$tKh=gP zFdHhdq`Lp6=~?31YiHRjP;)MG!1+{z995GImWbB=ZbpACJnyXix;8tv+`oZyPO<9> zeummjTQCKPoUJHG^^a|ABQ7OIVh2e_#6opoDj^$uZ;cw#LRo;4)?k1lqOMMBf3*-v zqkqk{9Xk3qA3yOsKgnj~%6VhlR_wRu0RHUD>f}Ag5C7T+7yjmB7+-syj3&G*Nfo7P z;uzPSsJRYd$k)TCQyq6unpqp;7SYLor%;5_MbI9#4Wb2*Bps|RkWd07Dwi-?<=o_< zzxV#x_uP2)-)-(|#n@q}l*2#s$W6cb^zh{9+PRCoqz`s#$+EvIBG(DES}L~fB)QL1 zm8s@}fuS5BVSILK<_?WY2NlMSEsW>}AibYNjLu8s3SJfg?ZZ0M1WwPzTBTrpT ziunSvVI@Hc2@dx!SOJBWw9=Fcoz2~3=H6y_c48tnSnFWZzX-cd4Np(-m2Sk+FQj8Y ztPsbRFt$!nDH0otDOZUFkfQivNocX`W3*ApP--9I;O1MWtHX0vCXf0311T z7@_I~)P0e$DkS^s`kUvr*EZuL0C`{{ck66~@<47+S+p8S(FMsNykT6$w}9Dab3vU> z91k|#ARvgJSt~!$oI9~=MZ2-)(9torvy?c1;11$o2$P*c$-NUn!Y39wg-E&96;lVN zroQGKuUUODJU?~f?|1cyQ$cmD6`RXCErO5<)M3c_Y*~y(dD@ktW|sr<^?;$%R;8 zHjdb4l`2bq=bm~%=o|NtJJrB7;N>v7NFVy>qbGj-X@*xTXD?`wB`0u$mD}j2s1B*N z1ZQ-@DLsW?U=G6gLA%%x*mP*(q__Re#Qi&4LOz@~c6gRisml%7rJI|MbPjpqRGhNl zom$X4%ii@eSxd>r(sv}I=#)81;+RU#iMtk(Yz{4wS!$n!7AHrBC&$K*%|o&u1Cb>B zt|vat!3IPOJ`0u1Xf@Np-IY@%c9=lYDx&{v15gT}OqS6`9;`#7QBh)N0nuY8Rjg-| zvlVR;DyX5+QWI?nS`3y}Hu~d`RgN~;aBxbpdcbYkR22y=HA-#)Nv~j`XM%D}T}#xv zXrThjfi|~RUx`lv>|}-RLiZly7Be(IKY#e};TYnA;hM#}yY9kBieYWtra(q$(L)2i zg}lz_z-|C)xohQXd}zxXfTKr`g5Qa(&a!WCvO9Z#4)Ng^ZHvWV-V(LO{vp9OUusOR zYom*jhq9Qa>6bbs?Sb!d;u=w4!se-q+wM8!&-Q8RF5DseN>s5c)~F&&M=V!)_OwEkT%w8> zP`$s_er5O5TF_`&a*4vpZe>p+r=>(thpkJqPrqgH9bX*S!M5TSi8XeEglq z{?7Y0R@YW9E^Cc!t_#;twDD9~;RP+nC{dbvz~(>=nw7rbpsi_!r^nciQ?eI_*egXY z&qso@PcF-lSOcI9vMd{d)me)o$SoC0VgNcCYNE~^(aebFonBCpoSs309{=a8naYhY zw~N#uiFUFo#EDP>#Z&YUUm}Lg96gCnQ3Cq`@GV|tFGUny`wduy;fYah0Qlyk{S|%%&C1 zXq`*0po!>^H{Bj(HE@daV2(?(+FT}Rcywc$v|doqms|q)uSKMin~dHDH?4!Gnz%Vh z&)QiP@TR5!4WK7zq)QMxfi5jAl?VLvRah+2lD|5lA*98vk)&|D5ctGSO4SA_=<5Xr za|0lyd)>{16^bnaSXfwK>)Gp7^m_Bl`pkh|$HQU+gt|IhH-X^DkIL3Nvh2hts|^Z? zhGPe5@#z9|;Em-_QhTaEmY~^%tY#8qQ4kD?7Py0dVV42O3T1&w%;_n2D_VGhFWHU6 z8p`c|9gOrYlg6q$XK&vvzVXQ>8&mh3xcPTKy7-j8^}`hIe(ph0RQPcQ@NjBmczE{7 zdk_Ei(-RM#+&ptp7g?ier@kJm64UkA)fxsPD}+(MjR}i0L_$OhQ2`=D6{Tgdtyaay z>mXyX7+6t?7h-7nxXv0NNI)lE+SrE3+LjbKPDasXgNIAH>@GCFd##x#0#<+(vlBrb4&LN5jww+X#j^vy_S3;oz zQ3CHe20ON*qDQ7Dc$zA4?HQ6zvWEP?0KOP!`&p4lHCP`Sw4lh=6~d`hpDNln73jN z-DbinRfDt$lzi5r#T`P82?A~aTB4VGi5C7kql38t2%*CqEd^>D0b`njvjAXgb%R^X z(ebh3$@pAySF2$dnf3TzOAT`(KFa%Abx(yl8UV)t3Lb!8-8_ncKo+usEI>(WkrjMU zMzrv6r50|5Nl>x-vce!j?p3EBz5U2Pe0u7U+c!>MxP-Io9>5{0E?1>0RDE8?_7oYfD2`G2 zfoKapXmYC7{!(W2q-?57!-~iMDvxYBB~USWQ~Yoz-Vu);pFi|>-#aoh$>^VNNUNY5 zVLvQjZyBALIPrJiKl`pbFaGc}uZ&a?Q6oM^O{K!)av$lgNbt!8RISbl-l(Ww*R+-$ z>sxgYh-wf4Uu0#fG)fAF5ZUpJH7W#dM05s4TcU@<7rggC4}S zEpCA+xdVWvU^=+wNThbq;PO%?kFwB+b(K6!@atb!pB5;qEqH9rPN3(OH_vM$ptkI} zkiMOn@Nx8LTDi&{D|?re_FDjiLH;@#;?bA%yaCt`;Rk#Pbhp_fuZ?H+H%}+xmr{^b#NUew{g`eQ6!@7AXREY_%xbU(Otba)#YCsYyl|P530iZ zLy<;gyPv$Ul+ZwA?6iYe6*>b9v$WmedJsl=`ZiN}>NR^Rw-hRN{I*}(yuEhXHT z1yjlmWTA{`DZSu>Nz{Z>wGFPmLXhqVD3{4UJQ_ElhhF&xz>ZLa7yy7Y3|w(i+Ao)_ zo(lk=VfredgD%kLW z8}9+?cdyBxsU%gDD@zr|-JYi?gj0(-t7n?mwRLIAR>jE?p#Ve+K>?zPRWub?wf4-a zNew8D!40@c&TzpL%7P(R5u+fJ(CO5HF2o5)Cw!Z7-4i$61w9CV&aNYy{jZ!oKlAXd zxBTwMrtUauIuA5Ei@Oo)w*{y}SJK2CN00o*ho&CALvw+sI5k!!2yUt}M^lo7b#6-y z)I^MddGZlQI}cp^z5QIYHPU$b%cNj02$AQg4F6nwU@S`@=|HC6dFR4UJ|wQ7b}p#1 z*c-9ywZPmXcO3e;cP^hkPZg*x>>@shvumeaL?Bg>5e%h_uIKPJF>$@O^YUw5DyxqHs1jxBrkBzt>yLg(-#txL7(FDwT%8XA0*yUbc&?a zYhK5XMpjD6)G;>zwq`?3>R+oH0F)#&`Wfnp#740KxSPI}y${h1K8Liv$!fskh$obiZD>v z2cD>`pGY`G&)tzbOCh3)wx;P9ockOJl71nTA_PeovRI`iD#25V$r@YAUPcvS0a<4O znPHp?2i;uI2dA6RArIcV>qGl@siz!z<}QX)GF1s;69Il zZmgv^bVz%E4l;y?@-XI&bK4tRdXb$mNEH;DLhjdw|poIzO^H0B39}>cq20fMe zK-FM>gqghg@X}AbZSBIvcsP>Vx+}1+W*JgAPidR3S4mO0F+2O5O z>dz#UJgifJKdO|0%P&)Ls|J0@vX%JXxu_%KC^9y1|qKs<7c#&m; zF>dveO6;Mp$|`I{Pf9MOgC=K6oXx?-s!phFCRV7SC0{BcXYar_kyL1s zG7+oJqux3D8ndna{!cER{GFfn=pVWpqyJ*Mg9zvz;MYGmdH+e)1g9l>P35|JeX(v@ zclHxktu6*~s*G$j47}qI(6YSsry3Hh^zxUr_`HEu<`kSFUO6j?1jhfD#&0>i__L2f zM?90?IQPW@(E|(*Ej)hj(A?zeg=M;n&RS3`sw354oPE?~y{WcTe;E-uMLvL# zWMMpJ73_MULkN>)HBN>A5!xBZeKS2A@%kF!jLfbk=)x!Jvf{;Qy_C%+5jmkp*BhvY zfuIztEnMJ2>5PTH+4uKQBnjULQFP>AzoJJ05ZRT&In*|76Djg5d!i6SU;A__1cL*z z4C%}%-vortA(=)XElSsFSf|iPH-afkcC(CxM&MNi#?uq6=%<*GAVy#ZJo^cw()kC}#s7`x8R z&Bnkr9%qA$0FsemJk&3(4J0s4Ubov-0FSjR#9o2}xB&=@gc2>djt0TZ%*^8AB5-%o zUYg+wcu_YR;-tNO)aKOi!qx44dPNmqr<2R_t)b!YQTcCb0V+nmQAJxNm*!FMrENQN zLKgexssGi5 z3*eRa@Zss>zwx2zdrlzI?a`5uN!CG%l|qMeyEK>M660y#$ng9J9~_;V z;$A?jCvSv4TOgd|*2662Up;$)Zv*QaqS;GahshD-BvGf55ZHnS#;|Hz6iUpgrWwp3 zp^S_ec(59<`Nzv-rU4`ZpVC1FDIo?= zS*AdfZqpSa0ErMGq{)(KpeGDL0_zx&tWM3O-*AZRqr|stk|E$CO^^|lQdV9VnaL@k zYD7A7T3fRzEDB(BUk|xxSA;7pAnB3>$>$Eaa=r%HU>ZpFAeIohwUMV7p}^DrO@gF2stP3Z2)S)U?8foHS%9i{#ZEtT>ycmkz|h>p`nijjMl@rGS7CH# z{?V`T43~eQn!vYFh|s$ye4!|SY55j}w*GA1it-)q;gM1DcJOnACqRLYf{F0m(9v6# z7JlNb@}>i4z8@2T%FTL5PZ$tyhkQ=4Rx}YcC(=WJ zstKgNRarAOvGF~+vP7-`V33jEM~ZE#3LY|>p;GS!0Idl)+JBRe0gH%6iN~6} zww+w7$pyp2u~YIaTTju*w7Idau|bSgz4q6WXSkC#AF>093Gv~r^Q)~Va^X(PStLEb# zg3nR0zHH_R(4h8iQuN!i|2^W=JXU~W`Z2h-VL5H7cetCh3lD?sYm zi3h5)HMaSi#%;;*eazh%3^I zMN#S#aTha?tE{dpN2e+Okr(-FfP?)?2ZiCGrT5*>H;lMM(Gy=$fj5Zu*aE6+YJcM7 z5_|SDP}t^jZl{oMB)1UIDpu9!>eZ3@bN%Xm5!k!aFhm~B!wzWL`CIJvKH8L-yiV?x z6q2x^S}r%dmslh~04Z>H;9=@|yz)|-8kb%svzAP|C|y@j{$i$RQ8M!yj6pA!4X`U-_sFQ08e7U0MjKr8^bLRR}qc8M98XW7bnRCQ>_ zGN4N2lB6jLC1|C*x2KJcx{M7k-mDMWxMszc}7?syD{{x(CQN095gQihPBah4UfW9_f+SIu}rBLtE;P}(K1r!vyAp()79B)I5WBI z8F~2DnC>X@&8oJ+9Us$O2N>b2(Z3yH^TH}WZU9`N$y}E$Xg>&$kdFaJO~F4Lc4C6D zp|*uACEANMRw1+cgt&kTkJA82|xAuwRj=pZfsOW|b3=oh*?AMRUyLzF;83!V~w8EzamtanPJ92DZ$F z(R3IOw6dl>&a@UTdWqPp<$DkvcodbA+r3>NDpxh>U*LcXCaT|6jdDu&FDkh{4o3Bm zAcje~Z_ARTI0~m$kce}7KDYC+-cb(%rL2>91n8|lGyQd*vlQmtl$}hkPB|fq;DKt< zU#Aq;on7J-SrcH1lZRB|0y?HL0|;fYgnk_Zi^%3-Wf8GtE|`7+>6Jz)E>}%Ps4i1| zrjt_IRMIi$=sdSIYsU-s&TLgo53{T3>AVo|M4%nYt1@wD(V{8P%X&b2;R-Y1Wh3|0kDIrOxi16SXDJE9EEV=)ymkdhmQQc z_m50XFvPjBOJf-DNAGCA8C#gX=~q9r_QIPh-+5(hX;zax7<#jZNi_E4SE_v)dg{03 z1qOvyaJ=2*aUKym_^d;qfc!IwQ5<-tGO{pz==~2WM-<__gFQ$(-{R7txlhXtW_PiI zHeKctX}WD7>2;mgR{#JY07*naRG{74?4Zlm${JfaQR}KF#MXm+RKh1x?W@ZmxSC7t z`gTWMMZDYboe&h_p|ID_#dnZ>SG2ua0*B|O6T(R|f-7180kU1HoOw=s?GEK+9?=f* z_@2CHKsqqd%~(z$dWsy1M3Q}| zsFkbmdGAR0F4`U(hjO@btvtzxAGpTMui8r5k&+ch|>{ z5XP6^soRg9{Eeqy{Gb2L@cP#9^dyS`82!^;a45GO&iS%cE@Rd)V)zFDF0!@7hG@FX zhv%>klARyyQE6-hjiXTL`8Z5DD(L0#_VCuF+52yvx&Ne!I{-suOG#rT-I)6kWp0AlJMauqA$I;F2rEi*zEhPwi0 zfemjC;}wI~6uBjH8j?yLIbm`->t&7bP-!TC_F1qX;yLgXkf)xQBybFOyp>!Ts-pi4Xg~jBbFxQ zYuS@@TdAe!ipc+bq}q6MQ`Y{{=c~@-nfYRnJ+r55FxfS|XQqtIIrBN=um0RyjBdWY z>8kP|h)_`iGVoE)EQY$8M?_a~aNA7%f7{%9?iESNuE>vYkgEDwg``xsZcZ*Vv+I$??3fVKQ}h6{W?pdal?_Cp=`EIqd6Em2-ElVQWVN$p^@Rq>FN5x z5_p+7nc2Q1A5(5k5Z98?p+oO^KpY1BS_x?w%BLmB+e7Dn@Y>n$ynO!oSC(Hnwfe@{ zbzUv5ZEW$|*yM$YH1Shy8HYP3zB)2G!pz!{iLud%@v*t-$wPC~#}=oKFU{X^V(zwM z)5n)ar}!pXn#_r^*uO;KaX3zm&)7GDkLdMkupSLe} zP+o1M5zJrzn;b%Jr4J%GOaU>d-8hT+uNVm`=N9F%2c*rF7OZ zErqgYKXci4dToXDSHhV`IsWovQ8r3ZTZqoFQ!Wq3bmyneg&Tk~%T6!3JE{8^TUgK@ zHrR(709}&&HIG*L+%LuL6L@J5&ChLrw^x5HF4r0zzzqPRssR^=-C1<@06icd0YFBk z0Bvtx()9G`JecF-*R|o#9C0@$|F8g{>7Fj<>z7O4*)*+`UhOL3dL^}TU#S?o(@{_k z#fd>EQ;Ks^EZ*q@v9}l7M;`i{k13z$MF{v24C<70J$DOMKUA*ryC_rHD^t01gH_@a z-F{B|5jo4>)VdFS^pW*ve|Yh8-x@zMPvwHigFIh%)n05UDcdL-Cd5zZ6FJI~4&&n! z@iG()mgdh1w=1|E-Jn z-!gT0A-zL`Z!CMr1{|CTHcMZ~4KevVQVF7*lka5n0Nggs&iZBdAS+XC4|#Y4peK3S z=rdh*ctXi@N_~*R`mjAY^Wm(Hn`aLPOua1Vcx5sZ@nz%(P zh&qZT-mictQFCjj&cFDd{>!Uh_{PN-UT2HC@OHWjZI+6yRJlzP2{%fu1yOZtAc@o9 z#Tx5j65qUhl<9^sJIc;rA;B%5D@hXnNqSvfQ-?pE{90pl`LhJ zk}Z0^vZZ8ub)g8E5uQqs0{U0rT{6!COGVMA@Yql^ooWX0&c`Cs&LmwaVb6lLL|H6V zgGFc%g|Lr6r50fFk5s-S3+a56rR81KrAXdHJJZ%vL1gleRztSKN$C zXVIu9L9ftF&w+SWtK`y_5^Y~kD2~|v7TD71)2D$0QwxEWR#sLp8c+HIALinE$Iv&j z)b#rS59S5{-CD{NM=1aV)6-MjKLK>px3cedbUXF{^)~;yVtX!Ez4a#;+5k1@YL++x z`gcikyJ@V@iZ%%qbR{$u`4xb0jD{GrMCqt}{n8;CD3JD9<|zRSJU;fwu0$cIP>-Izw*a_HGj{^WAD80=)3M?f>68=&?G3vL<4aigr`n7 zx6b_Ow=S)3jLc7uUz%87U2}D5K*p#yX2%90dMdb*4W&b=cBxSf*I z4-8vVbE(C~wIHsx=T_`9B}uslod&>XgB>8V!Ea3#o0K!veYPDXX!#!SbjR|Ca}$zc{*y-?+CdL73fJt5yomGSi*KC!?#t)C`|5=s{BY%mXV%YL*toFD%1*U%p3yS& zlxnF}7xl1~tt9h=(Rjqo_)Js1Cnv_{rkLbCbK=nK?Z+4HJ~@BaO|v&2@iS~$*)zQ3 zg+t+YPyv)29$NUoTb954gNvX4_W0p>_GfTI$PeN$3>J`$utYq@k#{g6p-mUN-vjp!8>Llz{*9SKsP>6-!}M-^JZ=>iI-%4e6ZXhhc) z?Cfo9jf^5-TO&(`%OZOckbM_?*e2<&lA_0z^Q%Vr=TATF&p37H6YpkRqGyx+ACwd` zTp6IDKRt^ku&A0YzWDm-Z~Wl&H@<)NJ1=<==L!uvO$XxHEc3bbbXK45CX;QTT++U& z@93^l@iGt3H`wQ$`9c>~R$e}J_M0!Tr#@dZnLN74-0s6~y<_REcg&tVoJMkfAyD2A zBrXT-WY^;pyoW#W*y^`l<_qnkGZUzpT8*tiBbQNR7EGXl1|R+91|ZqV4@=y#*h$DQ zf9*(FQL-en%B%dEL`O(3T^c{UFnRk?vzGDu8mtPN`F!(#|Np-CufD*;?}?cyU8m$k z3ZBSrK-fmSY?+`*cuw}46t&L5BBel`452TOumnkOTDDf!aiR0izxvu2zQLrRTYmDL zOZVR@=aHB3NrXHuRZ;sH5S@YM#^w+I;LkVCUYuNFI;{3{;}W;N!oxyYietP4XjZ%_ zS2P{V2JD*F!MdhSR;{23%GAXA4Hel!si>P&Lv~|3g*E?Q$vE5-#)psOg z>(7|*P}C~G?z--pszm!sh!G+f2E;c6VsN^nAkb0vjvMs`*KY`Mvig!Nx1~l5gp`;< z$a$&+=Zx+*V?~VFMQz|~o*Ht!Ke~Js*s;*p# z;(>)snk$;^+P9W<1JGfNQd%DYhOcvTv(d4u4N6 z(yNu4@|%*R2sN_6Ju^m@NGjPgn<7t>wM7H-zywgrD$z_Ih=z}f0O#ulRjQ0MQU83p zw7u}u12YfZ2EKn|5eaqGp&I%RZ7#2!`r7laf9cz&zxKVA*UoI5_qMP!VkYxUunkMq zCrjg~ec8Zfir&g5aay*WBD&n985&{AJg?)8bG%qM_w5&7`^;CTZ#umA;H}3Wd*JZf z?;cav=Eq+wi|pz|#PJ}7sRvFje(cdV|Ig2kOphbB970228Nj{~-J@ee!|Us7jNmhB zSc}WXV^w{9%M?B(7IHJace9g)k5OkV064zDR|sI5S@p{zsz9m6(Es^=`5AhQ@tG;A z6VZ@F2}vNq2_eylrmDALj0z7TjTnj6H04&z+8TfYnlORc43ABK7~_S&h3~$~!UI+w z-}KZ&w|wNCdYi=6vp+L}EHeV0{`@yjeeN07x^QQPq@d|8FO82g&(}BA@^-F>Z22Hf z35_z2MhBu}aHM(-h`L<*O*z0rh}J^@5|Uay5vTOg)$;`^QEy1}2rwzzJCm@o zlK#6WqPV);pecCgh#@p?r{>tqtyZ~V-U11s$Us8~FA)%=)-9w;(xHkPmZeQiN~oHq z(xcWpNO~Qcl6$504~J4k6-re*YB~)nNOJNYwyCu2Az@LeCvl_(tKeB!1bsvcT)1f< zM1SpPqYQ0_c}~h`o_3w{2W80C;-9$D7I-=yBV!b%E$~1*wRf~@E&^s^L=n-}p?e#? z#GVRqyYgC1avA&Dop z4G5HDZt6L;<_0@;=%$SH;2+1-fC?9tu9OZAjwVdz){_RLuO6I}V4&yIIhaf=4U4NYoGu6dG>F4^Bm7~nesTo?j@7*2N~14>59WJ zFQJ4??f}MAsfSWD*~VEM7YeG>_+wrN^G0Mk9*VPr%Bl0baNvRN(gU}hc=D~spLmc> zVD!>S+dgtuu?H@M6Ace7KK-_ffBxOIZ@t1tc{RQduads9i+sp`diA@W)_y_4Wx$)D zqgLID$R@+uqXgT_4A_K%cq12&{~@aCfRud?u7@}O^0{Y!@6*)E_|&9E+bM*)sxHLf zUuX#wQ5_LJ-AIOJn8gjpB%%~rK?Wgrv@jzRq3|{dfLxTJ_SV*g=U@Hq%Wrb;%00lX zA9*KBgvDatU%tusEc|`(_dcVR&La}NNwdbx>zx?q{sc_8E;X;GDDefA(6Bn%Fp67K z8AH#;9SQT8w*QBuODQdlqz`XfPuq#UD;ap?tf&w2# zmt7HwmwOCz>6hF)4QX!MIJ@<>zGf9!8dj2t*G`@d==Px`0J!lsy}XW686-rn=iFJc zl!7(L#nQX2SO$=jO*@!FY-Ar3qUhy#*b%Qfi<%)8NbZ!l?u`^#6Ur0?og=-?*zX+G|q@^?T2Sy z{o&bD%<%s7U!HvG;afiZj?qaTtM_3rmsGgwleHW>I=}SscfR`UzvmNI<-J+f5JO-U zeIbR35X|4f#!Au{ae}uisrR=HIrwa6lG^%fFo0?z+uNhVQ+FOyIzE;cY+SYE*N{O1 z471qx`QQKS>g#8xk1n$8fl60!gEA0a5!LU6;t8olo6HWyZ5`T`>pTjDvf$)ZN|3_P zW_*xj=dyjcSsk7py|lS`_8Tw6+kiX&#*@qo!mo4>kX}7qgSNY7u_>7R^Ws1M%<}WE zO)kx;O;NL93vNzW4$Y+DyxN;oo{h2DUuq6kN#Z-yC*V+6z^)?feA-Rpg z@fvLI;4Dalo1&4V8a9LWOIH&jvOmXe*vL}R>Q2lXcaIVw1ErsU)`5Y&`~jQu)C z@nIhPM?+V;UQ@fy5(OKz;9N*zPenkWLt4no*s({3(c<72E)+1%P>UYlH!4ZC$2n?MuyyplAzoYiU7b7?a@H8MY?lujtJ z7bf6g?r6cKhLO8D;afCe=(@yA5M{+NG2|rrz$KtL6&;fl>V+JfO|_X3Ige1_ND{#O zzRj)42Tv}3(-+)z5$J2mkiZPCxSlE&uR0S^cCvsmYUC+cr!lI1ojp zY)R0v9MP~-vayqm3B2;jPUa%3MV8>Axb6f-M%glTd-MGA8-M=obKibR&vbwCoi{!C zR+8X?fe+$6m35hW_g!;O+;g7K0vwtf(nnmSkPfoA0G^J}v;d{*HnT)V$GNw$10@>> z?L?W$Ifap7t2<2d_^nCa&*a*qCKV7yLts}dDEApoN>U0| zVyKstj%-;EA!U-}CiDnlEv19Y)>1kNL{kR1Vo*NWxw8J+=e~aaxmQkp=#e}BtM@W4 zSH?Ym6=3<`{POSr=@0+#ulRIv$cJ(veAGBSC;R>R#z11KW92=77JN%6{=sTGmn}v{ z#f45sTgF7j9fX5~)KBWPLQgDX0&aoi8p;{3ykaA1awlzz(x3_x0AfBG(m??xRoNvy zGM3y$z#veL)TGRW10w0h+kZjSBFKaTIZHD6dK7Lyh$g`i$!(W3oG3c0U0EMza@K6w z!l_ZFF>Qkz2o$x6xYjxO1W~M4h z5_blbM9F~@#r6=F>@41Z&+KHwUZ3Hah!TLBd=6!$YfY0A_x;@O8vqn4db+J<@5vS^ zNC0+Fs_FOL7&{jrf6}%t+v>07h^jv%#I56&4mfh#PxGP8(ARu5uB>&2$Q-DYkfSQa zOfp`C8CFt(3kas*JM$)Ka+X5XA!SZ&kcCbyR^4kR$!C!o4up;obxY2m*Og6-E&cQ( z>CxlCTl-rkB%J^LYv23VUwG{cUtfLW9FHoQbC2R4Qn?1@0lB_4nO$a%|A14}IN%fw zJ33$3BxmxDE{RTsU_KvZ#S|BJMb&nALhdQT%t?FgvtK*&%@?>Cxbqj@J9qnWhL?Lk zm{ZBu7cBe;JDq;?(TjiiJ-&L&2N2K@OZXa~QGery6`XypnQsCPD1Hm z5WsO^w%JK3764Q=U@rT5pk#(kn_I7b_NzR9nmM+_4zPGl+$hR_s7)R10Xi9$dZD7! zGL#+`6A$fEa_BALBf!3L@}jH1HCxIm^dLI<9xahJ#{^XLDc&z$}C3wQt0 z2amqvUbn|B?QpOs!FGVtYoGe^i~slMr9Um5$v>W@R&+sJo}$oy#+INmhF1eKjhK4R z>wDGQqJC|S4*8^U#jYc*Pmy-5)!MZh!QHe0+p;xb&sBL%1@1iz*b4o!f;RkQZfD3)gF?EASjid#)Ly4 zY19D87P{72aKzn$0M(x~1`FFA0A-7Ap@kLE!GKf5NhwFWSAyW zp^QbqHFBqn`-R8B>_h%+=OrezZ83$bJpR|#pIWVjKSp1fTc2`ui*0Z1aM_MXXJacG zS%l>HkkHy&tq95%pE_@Wy8u|Fj0LD^3eNzdTuoxzx->b(W2#0!6XYI2XC#a(xREC0 zz}vmqcib`mzWXV~ulefI1`T6VkFAm8h5zuS@BQn~^L-`M7@yV-^tv(d6*0P6%!PAu zRLDR{qUr-yqABDX0z~Yi;aq9hYZQxx{Ya16^;Op(B`1c^m{B4g^Rv?RH_pBA?*R+H zAOHX$07*naRQ~MjvoGKAb5Gp*Q}0SQSGZ^@GRhkC(K<}M<>uKZ?mhRZuZik)hjuSCE%)tEHOy2cX%p{sV5-ZiSzAlO@iuB}f^vuTnP;3w;Zl08PM?sg^h;Dahd z!4ii!sR^eoj%}3=0$z7;L9w}%SHq@B7WYSurelc$g!Oh-H`Q=vK#q>kGb6|(IJ6A~ zjjlnPOQfX=?BLti$hI!Q-e^)zflx`JfRbYwm(zUC5$MzS3rw$Y85{P>vj4cyvGfrx zu;{J+Q5f|M0pf~rOBwA(X}JwKYdY+r>Mlu$=AXig>vN#5DTn}Y{FoO#>o~L8x4Q4Q*=)yu)#3xE9dH^$d zgrPqH`GO#heHOQ`Hvp*JQ#~{%kW|}698xS?M+S-1NG!)hjKzR1rC|;cA!J6`PW5S%Di>s6j8+M}kK#Bs zIYIY&_L(1i^OZNxeCMTm|MrKbjw~p1>%+UH*nt2D$`EH_jM-lozW6NPMUM5&c2}*c zXNqK&3l&T6@6n{xq!LgcOO%k!;`opthQ<}Ua zCy6;foYZ0HhzLeB=gGIvKE>$&KWv^^o|v6h3$m)ArsU&4 z4%o!K=<1ec`h-C=TOh5f!h@6kp?RrlF^B+Dv&->;okNf?FG2t$xqv)fLycMWFEjv4 z;nSDDMqE$BLyUN0v=lmo%C$al>KTGEoB{$D-JLsURW-P;>0qab{&T)MG2kA?&n)|S z)=T*Hb%eo?772+JYFH7YrKj|@TBu>9&H&O%-E2TQS3#9kMGpdKk)=)6qY)~!u%gMy zx|A|WlS5DslnIh;I*IGpP8owHQt9jPXFB%ilR!qh=5;!lnj2z!^%r%jdWl4>{_317 zZ>cKLY=~e)u4EL)AwsuU1gwf4bZBj586Dl^B!fpkTg?65pv3I!4M6GTyS1fHY5cGyaC`<_4t%le}+6-t);c?dTBMh-An|LA6F8Gk5&kQJ&`S%5`|l#H}euJ zBmy#7@6--bM{2s9x0|ZLKu;UySsm)Kre#`No2O7%6@UgA)3Tx9@gFhX8_d4#*1Yv+ z$(mUV@a!uy<)8lg_rLXj{Kr#Yc@A;MXS^d}8oQt|=1rXm5v2D~S~w-8g>R&z$1c{h zr?Qv|d>9OCZR`1GA{3rjT!Umh4cJVoF`xFs4+=j&7b!au@$_)x+{z38?-!R}c;o*6 z<|9jQxg9CXfNX%)ZHl&n(+`}Sd;FerfB3c0qYK;zf{EU!ZhvxR zC(p8=S>+D-rF4_3JpAJvdOwBwb`M2mF&%Y11q!@PRGdcy+r!*F@`aOE{^%=w((dkG z`oK+3J<@beo6}Q3XLUtZX;I12W=g=InEtm zki}?VYBX&fNKRPzCsxh@5W)6{v0g?E?3^nq&N{jRR7U}1k);H$ytF8A3*V4#+{Wj% zWI=UR$FNH=3VCgp9z0aCsFiMd*{)=)U=xb;AO>wLv$4qpkw$cjiB9rCWOvY6-d3q4 zg+gyp)an)3m1Tbw$-xK+CpU?E8R=t{!tW(&hzp>Qh$E7YMY|4c;?u%)n5C8Ga$C6G zQA*U7UgHybvSayZ*B&;MEkRR8^z!O2zY3FEZfoVQ)>T%Vt()C8BkR?PKSlMDRUGSvg*H5!Y5YJZ+khN`?k;(DJpM2=TpFO*^npOfZ z5n_E~gD1h!{I#myFMp+>vZ#hQ>fe^qu?a#e*gl|@${>5*-`s7;`UkPfmb+clp>C*d zjsEo#z^C*QKzM4~M`_5vhb`HegbBy!)KyA|-Y&sWweSB1&j3hY2#&=7G7AOSILaeh z7#N$PV0ab7Be-+ldhwZm{BO@c_v#&=cpux#suXRwNtHQKY7Il^nh!f{%4pLgwj*h0Fp%dL{x5v*q%}=!S+5dW{uzT3aH+oc+u3 z%x*AeS=)DbS+5W;DXE$Wxj?htQCois!_P_awL9}j;$M|kHL+Rtwn32n|Lnc_ucpa$ z-*@+|_v!8#&OYRDW=M)M#gO7I+15fpAiPMiWe7-Q#V`!Zu$})RKO{f`13}`%LLwNE z9Xo;qL)79>)D^!5%!-FY z9Ci*pBsb055mv0n)vL$~jHB8Qw-@KV>WpnjOLUbNSvchD10S=?rc}3!Vp zBq%`)sR1^DWUKo5Cn~cOAYv@zx@sg2UpAelX_T{(1;dIKM0q^`6Ek3!n28`F(;T%G zF<+*e<>^TlegD?~`uUBE*B<<9pU77N4$1r0NIX5Y{Mfxmc@yA2d~;^;7_&?QNgoYwOV%clL3EJFeC%9?S7v<&py3(?6WO|tAREq&@QK+!TSrUY7KlJ9 zO6v>(;UcygDIFBYs3^H-kxtlPC114*?dB{qW)8=l;zXFMRn) zf>qz(l$czN?P|&nlGj2f5LvCI#$dm`wg!dd-_cqNTq;vk`!12OR8*VR4OM`Y%rL-S zw^ELTTnc81X<0$3pvh3~v6RdIUP{i_N;%yru`Dial0izog$r(YUx|kcntaf-lyEMV zR=`wR3GY;ag6MP485FlrW_hN3kJL(Ax&%%reo@p@v#442pkxQ_0@W1k(a^yFi$HY0 zqD%YQ^KY%nzP4;ct8gvZ0|;92Y|>D+DM^r8H>4S=rGCA*u)ex3$z^jMdt2xFTFZ)C z1a)3PbzS}++ZP;^79eUTj~FqKjJyq$GO}oU3giBma34G7v3v^P+O_NPtyo0RNbSLy z?Wjb1jRT{Uw*}Mx2|%bZsy1;j;jbd^Wl$|EdwtytiYp9xK0%lNHO}jJ6_Ge_EyB^5 z5xN)C5&ZtW>sGn7?Dyx~Qtk!o(`VeHk6z}1fV25cU8U2il!?}u8vov!myUM$GD5B!!K1eT)H- zaX}fGz+x72NCFv7pe$)11QBS7gq9^mqrx`C!>Y zKl$lHbPiyH%q-3w|Kayv`GY6)aXlwyLR=G~kU5_s3O)6Y+0YZCOKm0N*oGmzufv9N zqGmfYyky>aoCFZ}82zyFQvFP!7(&W9mP;xO zDNO9Rc>!c_)z2aiOae z-(Y_mw3Pv)74Mui6vGFu(*)SQPTNBQYSVTvOoEL$;ABnVOiAb_nh*+Lj($C`a~hPsz7e3m?_@ zwh@x?o*TZpu=^DgwtBz&O(#*01}wb*=(aERq{vK&d%a}XO4aR zz2HX!;PzSQRyet5QsKE@`NJpvkAJ_rzQsfTx}0eVHG8w5I*LnVWfFkE!{lhv3C@*L>&GrGJiEaYiz5K!Zu735I&G~Y)0zc!Mzj_fshEOswn}h=s z*=|d@NMdK==^5U^RU+4#*|-DVu;xA2zd81BA44(YxFA|I33ePQVB$>xb~->v#2~9d zlhL6lE%rfdP4q(@whjx$14>Hih?XEAmUKep;BXP(4o#RQwwfitMUlh7G9rhUr}=X5 z>?|Lfd-2!)`23$eedZ&Noc_ooCmy?Ro^#`nyRjZRaA$hsWEYB+XnS^+Ty}0{hEYO-2TFAEcNS8-f}~L$hX%dd+iG*Y}t&$=|EEslcWCb z1~->C`Q$ltw$mtw31Q+wcBiJBt=Rx-mckhQCL~;6?*LSV!}nxcg(J(E6W>M-i?pSa zhj+O$6(<1L%lD{?{G~TKw&lC7gHB$|TWE2>-22u;qYb@uwHY`1gErLU^qqUz3rqB| zmS0>>0VS-8W~O=XlR25LQTy`Vz6~CHAtHH5c;WMZ^3*SWfqR7u%>Ma=7D!LHa{A(l z2uNZ1`5)Z`D9b*8G>NjpDVd}I&ZdFtVCOg~8_mg}^0ZfkVY)CfOtB%dRC~&*e5I#i z)DB7HD$3`Dc3EMO7ond0H^0xK`w#w&PvO49Vt-coJhpu7GmoABqi^xLHBa(deQy5h z1^}z0*orcttU~fOXab}}mn3BtA%ZGM1v9-~@CiUu;L=HK7S9}Cy5s29rPY}^76MYZ z#O&VzqYX*71!8L7#8MYMK%tbV>{L_))liUEk#v>QfkdG*gfL6M3MlBX6AM{E0*#Eu zBbSbLFjU9htUS*mRI2Mncnya0#;Z@i#ufe7zxepkci(mNfwRkZpI$tDd~R7qJYBZ- z`h{!Hzj5t_H?O~ZerIh%%eF2r(j3ji)D{Uy<=VeAQl7*b^EpU-y z1d@y_(@Hcc(tipmr2(gin+x1Inpa1Qu#&76lfWj-kT!&lO5T?G6ciA$2DFN8K3Mdm zxf(~9lmVz5LHCo&s<$fiBz(xuUER4kuXw9VB0Ti37hslFmUJg9m%&xAwS8d^*#qsk zl)nS$bc^CJ40pfz;LOy@t=&)Pc-=lIP5?CW;l}7(?3@iu-A0eAMYm6c{WwkZ4gi9* zN(S6YS4#nS7$EKdv;@7t9P>EH(e+52&f-ZA-*FMGNaP`i5Y(}&c(;^&5AVHPUH6Vo z9Ji`Ix=P1058aAg>XZ16oqMC6|Cv2~Qm`JYh>&vQW0yp4Y!SxhWlRHUR`fac&q8kJ^ zXk$pzM9pF3df;HGiCUD4jER;pF_VJ}F9{HiE>cs$hmn^rSbyi2erI-hQJ)Av6EO=3 zMAkzpjj#Nr2j=d-W9|8KGd!=aGs79J{2)y0*O2gOW-D zTj(ih z7*z4hirWH|?k#S3N&Xrj(g9MbXf8|e2#`(yl%Opo&QEy52o@Kzqg^x&z%ePf0<~z8 z5>D;}Ak<^Kua7(3Id4?o?l;+4oK0@CXu}K_^?1Z}mSg8EN3{J?&oQ^3Xt!9#6n{Iv z&?Ke!DlRwqL54TIoW=U0Z`9yc`ehwtX$M1b2C)48dl&9LNfra%V&exz{?^x@dGZ&2 zYx~OD!m$<4tTi`ABW*mV514D2WPvsH4+8D%@sqJaX;HSLTlEF5mnD6UjMU{`n0o#sD>eG2?i$pkv=#TZ91~E` zF&HHi)>DS%BgA)n^pT5SeoDT#pS@L(gyrLm(V0NWA5o+Vii^}M>q~!^s8o~tu!-zs zXL=jfP*p{iN<=J68wH?efOOWhqk91&;y!?kMO-PyR7FgPK+2V)AqG31@xv=SyW7{- z^=%!wP7?4?881T3%#w@xIukZm?Xu&DPSjFafBqRv%D_lci#p9qAhVnYGE3H2At`p( zH`bTuaxJ)CoNDQAc_Yiqp8l75@@b5vTD!*Cxp^t8vWX`*iHw#^n@$Ep_$p*%4_r#i z9&BHUSwTz_8IzHLTSlA%_?dD^88H@sBrL;%fXEkJLKW*xlohOKNKqJY-`kKyx(+3) zWap?mTj)ZCJ_Q`A)Kivz&ABdpStA1-V`q1Lb5+!y4Dn@(sLT8FSUW=?+MvDsrwJgFJ8NgsvfAoZl^OOB-!Hb?i!5n_(N z3_|A=c`9{DAHA)#dFGSvTe<%%(`CN~Ah+TW3Kcw7FTAf{kei~g-Y8Huo#-HmEL%BM z9#=94dp{g)B;}=4``M`yA!bQaP}6#%iKqXiyh#8wkj7WQj51Y$4su^i*NGfmr{rbWgtgO1 zo?Yu8rMgMp#0FEz=>3vS?}}{dK!af;B1$YpY3S17+>8i=c(B#bQ^e_}!Qx(W#VZ!M z2yMSuoa`n|DKt%;USu9^)QfvE)Wo<=L~&68A7E(U>Hh>^FNF`u^FLTQ-T`3lO9T%aU9lIz zm!UUIQ1Yekmi+g^o+DPgx30+K)lx0eGS6i}E?jAn??Ds-ujHsjzvIxtRzIbk`MZuE z`ThscRReUtNdx0F&n-OlfBwIhzWGAT{$r%at!2`q>5AZ(i+e`pd3MZ)G}U2VuK80= z*1>Z@N^>7?M24QC18`DF&W%i^wi2M+rJ@rTSJKX>A}$qy*1Jv)%8IiD6E>%A!9ZK3 zWEAYk>psBT)n{M-_P_WIPG#eT;C@{ZN8gqAzU#>Q@8d;<`6bPS2qZaz+mL01x)Wp-5$3! z3Xvi7Nnt!gBW$^?(XIi)v+LYXW**eaJdkM%ZYzxI&9A#kEN@}gVpTv4n`Dxo0;boc z-fb1;Z7|b)_&ME&4@+}{nh2?U25l&ntk)pAi=a}^O+qS;)k_dLcE8c%~Xp^4d# z=P`A99h0Y+PL+uaYy@VJ0a_DE+n6L#s+k-Z&zZWJlL+{yG&z)W2H^^fDRx65fF=yGv5xgzFi&&VLJ|n6pZ_gVz={QhM*Ql>oZnfC4qCkhRSrF+rb)x@wN1BV_gTq2^%l~<%9@90aNrHao70x4{2*PlbG z)I-34*$1sa5ef_tDA%6a3^D6B%aF6Us0D@7;FguA2{~1>b`PaY1Vq$VvSM4;*Pi)Tzsnc0_}nkg6?it|d5mU4aeY0fIGVMP zykk>m+nV~Q&oZLqq~EXyAR(hHG$m^W8twE`A((>15}-Uu0x$uaEX`8|*9j?HSJG*w z1qvu4Nv;NkVr+?{px4E9mVj3eCBqr(~J7wv<(?>sW-xi;W z^uy5L2ph$YMe#O3=dnnW9Eu`U6;f0+2i{n12>St-?mhj`&;P{9PdvJL=^BTk={e3K zB}phDtpv&IPIe(#MT1fybaWUe;z+8H^@MOHV<{;V9z?0)qy~V2foFk&poS9!dZ>&| zshHJc_8<7RG|n~)u_Dmo7dbO59gh*@#)R}CzqYA~(W);dx!Q6GgjhO|>k%S~lwgA? zCB5z~kAkrr0nmkq)VTwIXH@7FmUQ)`>eipUwr4Sab4Uw`ifPNfKRE|mR!=8NbQ1uR zmYC2olbc(L!7g=@1H~^22MQ{^Em8PY?BN5d$D~rHidB@fvX1)DVM;~J7^fwfgx9Zn z@Fv97=nz%oxvsWKt8nO__kmlYo?xzJk5zLN>EV2$QnnrDz#AS_m?i^RFZ_oIK(Y+z zfeUnV4e&yVLr*bQ;w&TDqmd$+lv|BYnFR)YN+I2&9)4EF_2qP<>mL?!_lAF zcAw)Ba@c3DeBoK2)WKn|5>(kTImoW}V~Ch}3&RCQ*?b;pa} zc<%XM`6IQeCVEaY%L}Xr>R?vD2ymw`tc)?_42r}=rU@ilR8-W}x;!<0@v8A``b;Yi z-}T@>{)w}n`v^j8UcN3t`662xLaYpW*+W$$sZ5gCK>#52l*%@17$>Ci-e@jfGXUxL zM5d)8eu7d_L;NU&6hS(N^cioQ1}Od=Y#>EZVOQ~EKc4-E3T9x2Ng2&*>y|?p(OMCs zRe+lDV{gT^p;QYlw=+DipkL`lER%#y!W8{(C5Z-Sd$bC+&AW&j&FLtgJIF;8B)Dg{}^XO%v7mVvm?`m;Pz zl3fMi>Gk@ml-E(SU@5JFZ$R{l2Z&$jVK@uHbWk5^wrz$a7O!(qhwKD(SCUV&BL$OM zUI7u5U%-{J;se+VKen{88dGYia$aFLIJ0F*&^$eEVzqLpK+tQLbJRv#lvW_%c0AyH zKNSx)C(OiCOl31x-Fm=;@x)~rZg6rZ0P3|jp>Q=*JOVT1uFa6RF(v{q;!p;&5#Vq)0irDqb$%Pz+F`Dx`6C~^AAZ%LLjp`FYsFXq=eb}0L#~js zzBv}wY)0zE4~uDLuUU=(N`QGBB4~mbTA5l~0h-MS(yYfwvaM8t-g@dJ}I&-|8-Issst5=_Y zgPMJ=2vl@mdEdRWCze@dORJqm>@@QhVT@IY`iO!p;w{6@SWvQx6H|e$OV; z-7IP@C@b4iQ3zNy+|utNAXG{5CRnM^wp>YwL47)2aqxgdpesU)Oq1BLX zVTh_;Ed|fyvf}#%c$-9mc>s(+BAN z&itJxR^I35f6_u7%gI4j8Q;D1n_s#9{2TK};^Cj1>%{Ct^A-Q_X~Jdh;>iut^QR(1 z@!U?r*Z_;Ry(yKJxXTGoXPZ^1JRyTLAZ|v|}UEf{X;zQ-TTUs}XCqq1$ayKj- z#N9m9w2r=NnQ57#EVjs_6P5vinf1jL+?d!QyyO;5rBt3PXX|fVdf`|9BTdkB7h99_ zN@>a7S-9ua!UJdc&IZDP(9d{?1O%+^mssP1q;3O{X(I9YvxKsw6ij~4dGfL4Ge7a+ z2mafiIP+5u)dn%3 z*mlGOtgHjWr8&QEX;ew6ne;RJk6IvMC~mU{&k$VQ=~Zj}?^Kjbk`-(>HE(We9{MUKGgaB>O9t}S($(EJ-Ogpq2Z zCYJ`f;i2NlwuHcL6oJs;8xM%T;nSDngeLQ}FD zT^xDd#!&-8j43$H>8vytppWsYhGzWRJDb}8G%~Y1KefQuOtp}TtbiRMh_iw#>$}^# zGxM`k^Rv_ZAdsQu@ZcxLC8N^?H7gEoRv&dD2kg%P!#v)?b|JFR5Yk9c$))G8f&>Gg&(ecuS3mXWmH+RlYhQYL^R+RzxKWUj$3qh7~F`AX&LZ3&pVY*@X$fIaWy z9e_BHA&P?%@h$v-$0TRqCnoWvPXP8(yDFT6JJ&{eZLZ(sxI+ZJO(NJ~Cixt6>I?1; zc>pw=D9as)B!+lr+i$7Iqz_%VQJB^+kMZ%ykSg&e*39hY?Bc@8`|pL8nQl&eyM`wP z**F{?Uiq!BaLs%9?1^b++nV-!j-p|s%>LCQh4ciz%<^p<;jOu_2EveKOS#qY+(}pZ z*S45X&mLcyyX*Me-KXa6JT`ac*zA$TnU#g9#W}UZ-I3MJowbdfD{EWlu57)2dGpl^ zo3CBky{0vn_-@+t0%6Z8JxuOJtSZrhj`Eu8SBwPn_gqoKzScPa05vt^=O%=)fq@NI zZFP>uxxviL=B4W||N56X0g!i^NSKswQYCiSojXO-%k!KvG2sIvseQaOv9Sg{zzZm>><9l#}1d>1vhraCs=2N5zc+Ud_dC~$$1`&2U|2%4fvc-oBxNlcm< zm7*bins8%-4@XE2vq+Vl>whDgrxaR6fx*xt16Xcla4R*-MbnTP1jl}`zLm(RAU7{#4B3lv+sN+`82`NfygkkjHU6Ix8Z8Xv+(^(R9 zV?ztpd1YZK1+fAy>PoBVjSUZqBIhleuBkz)(*9)koisqHk97*wsxVb>nZi1R^mMv9 zOg6-o3hgy8h`6$-bP0%9d`w&HV-65x=$O>PhQwl6WcRdUgG&tHsHRIFE6eL^Yg^pV zz?w8>NAqu(Xpt z0cds3v8HI;W`XI(rKIP*X0Z-S1ydbA(x;BtF337aLPzMz-&CYXJHwquf$j38PLR3T zrZB+L^-4i$rsV5Bo2>chNo3vU6J5A*{eFFocYiIZ%W%Prmgs8Utdc$(J1*)I1Gj@)r{<9l z%c_yC1+$k8Gwj`7ZMVz|P32%zW06x>xzPcD!1RfznxV9AQlO}7JT0lTV5|ih$)@nu zV>8U9fh`hm4Q}$~2uYcUQnSy!jLT0FTAh5~Q5ia-%JPAA--$D((bPq8+kxURr311C^OaEyhZu44Aq+y(lG%6W(h_eNa#1l!AJ^>g~3PV&? zhu}B?sHH;{Cz<8SE@G7NwdofnIxP$iQy#}bsQ?Xxw)UmMGU9f)TGmMh?SU;QiF|W( z_YW3D{isvP6kIBDgW3<~Bua`ELxqc@w%^C+UMgNt#ob+A!s8KttARR+?0Ab&c1Z{! z=GFl=0=vTv*^|plo_^(Qf@%{^HViVp{>8`F-?+GR`Z$;W_3VqC6WveK{h0b&%0QYx z`cfJq3vcynR;mYTW`=n`tIp4!J$B>=9ys#RcP~DC*W8(->4hPcWqGH{d2WVt27ZeV zon86Zd$!)Zvi8KQtAGCD+Sgy)I(L}|s5lMaIU<^JO>HcUyyK{fX9?tF4E5gw1Z^6CZJR;)+7Yb#5I zswtLiP?It?PK3E>rNuO7R@a|@^W;b0JKhnM!GwYEyM2ev7w)@*-?2};cjJ|d>rcPF z_U%_Uo_S;IjmuM)SWb7E^MrgV3=1gS!s($g+tv_O{o+HJEOfI?!b-kC>H-xjSj;wK zC`JawiBc57M9sJ$tC z!TkK%x?Y@sBxZQNSm^3O+T%7y!6Jbb<6Q+_0N@P@VW<5K^@RoZ#MHY`*L|_F%;XMG zOAL6-DgFpU$QaQ2Oiw_VQb@B9n}oVHT}9(b0Adiy80ZKGmu0zk+JtY1Yn9p)-A5p0 zlSt$*RO;j3X@Mz>Kfka=%wV)m^+v{2KUIx_Ml}^j#Rl0_pBPBMv#<|6rW8r50w_GgWXT=1YlIXP=3e?)iejI_pNZ}4nTxpTd6wE zfJO}d2;?T-il-IC?mv_%lFcTAHG*3aEv!mwyb7XPP)Co8z?UVXq|%y5oP$p=0ZxY6 znSXF|4lsx*;<5+bPw(htdo;nrKb(x}nGscW#n2<#GA^`2X3pvPasaLrQ!aPie}`(3 z?bNB&CIVR9wKp!l{`=pU<*F)jNJ~xY<43u(Itu_UNaa+-Oeh8{3u|URCP?8hg@&`; zt7|jImR3IV=+Pg3Z1Iu1W>&JFt5T2&Qp4WI&M=#fK<)81@ci8;`EeGp_T+0xBwk%uwy*zz(Dp5>iox(WBgiajk`@MG3{EsCk)Eu9Rjzo2AXSzVl-he-+cMPj$RSjCNO3e z=BBu!=?8Qie#rv;#K!y@dlXEVA_60<7v#cZHFj~2$S9aO>QzyQ%DVuzG8tM>qxz4e z1Gph78ep4~UlV32Bwd{j1}9(FBDWxRjsP7adC`dH40&-Tusl(~6$frkD%)qW$Ps%I zLpp%sJycC=icL^dw^I2KpFJYN?6Fo{c~up8m|U>_jaS&)4$xSAlUH1=Bp&H8_A;7k zWeu~6ie@TFJu0-gkUtw46wREW9pcl~?h|58IjgsUnLg5=kOXNtmhpY@PGGCkn6EQU z7}`ZPm=0gqhM(-wZwhnc5do2r;8i+7mauc0FrkvFH~^x)blFRvf{$Rz$Y_vaWwzjG zFab<;>$_XlsqMnp-Jw8F^p10 zTG*_@0ESuV&zsx3>sw17xaY*@eqi~-5Avvw6eq<@-E0D}=hn=-()68Xge(8WLyHgJ zx%`*zzx*3tUw`to8D1e+oad@`tr4b|u<|^)8K%o}j*keqP#61E3T9?!S`Pw=YI zLW$ko)tAq2uWrmPFDMA}wn%>pjuV7%DCa@99UC%p^#X;oqofQm`pL0{is-lMB0Dqy)}t(WYVyo-;Zbd3Rd zQ|I96j6*unBBf4;k8HX>3vC@U#YADN5H0GtRdI0ZqAMH$v;PE(2CpKKYGXi|-cprZ z!XS>$bI?Mkh})Zd5jdlGdsFK#>ASN`B`FteC;Xsx(xsG@eXHL%Tw8via1-I#o61BY z93{yG<<7_~v0T6u4Xa=ZMMxCG*%VPxU{r9a$pk65DKe3ROQQ>zzyk-6Y~8xg#I&S% z%qz+V{`8QKtMV!^PZ_MQuGy`+N6quY-0_oO@Chm0hS{m@ijB5uR7g_-bO}1XN_03< zyhs_c;Lz$*4bUZ^TX>REPwoVu)vDDtOrQaa5Gv*X82x%e%V8ogBB|GLFDW8SjT%>4 zm%I);b{U5QQ5X^1)hUDW0K1M^8H63qYRdrIKC zNsFzRXuj>aX0#cmWjc6b<=BsYAEyDAe*NoLfA3p7)Xs?k-&;-wJjfEWU zgyB!KKh#uLi#(?~P(ct^$tc{}-ah~3Cm;M%tuCDV?(B`YUGqCy6TU)PP+I;getBC5YBrYrQTKQalyF&9ESj2Rva7~_S zqp?7Om@SPM#-kU2;Ey=rFC}v9Kp;erg9%AZR{6^8r4^8YcQr|)!mKYbO(_zgn`Fk( zOoKJZ^&svs@?ecv?@n{If*hBA8@9t$CX`3sA~Nb|xx>wksu%v`@SobNVtBJ8LVE|Lx?0P;XyY0JbYzw5kwQkggPIHRQBRxa-$_P-Brur z%BW`GOa5tlh}49x^YV-kM(T zX-Yyb8>Hk>K=#Qkz$igSdH_%-*#R&gW{Ij1OB4jqVuq;?OP(_TA6?}prEr%)`i8OE zMEJ*^ykoSB$tGkQ&`e4Ucf>}Uaj?m)xyo)T8tDkeCBxiBONEhg#&iN0#UfmMiK8t2 z2@$7WgluU@wLEA@zOJg9fJVcEJ10xHFyJ!mE}c2HeBWu6JF-nU*^M>6_T1|izVY0g zUjNl-=%1|SOa6K$o@AIgyA4X#n3Pzu_(!cC?hXVFFbVi^w0g!{JkeH{pwd)KYsSuvep>&&@38v9(LH@ zqAC{A$WL{P5%Y}@&F69Xh?<YG<~R~G7#Md8L@waIYwh0<0>$1q15y4f&W*VZmQ{=7~A`q1f?%~y4l`1+TBSS%x6 z5qa-jyPtfoP6p0h;W44jmo99+aGv#}w%)u9Qr{^j>14nw$r8>Oq4JUbGD?^swr-%4 zQ54x^LN2QZDhwMTQ*0SrqKYI9R@(6|VcewqP1|nJsnAs^DsV6mIz$Twp@o@pay>ii z#Rj$rX3_cLBJ1kZ=7UEMTZDDU%1o>P>~E<1lBZOKqT=O6g^y-V#p*I*{LO{e05i}svQ6DC~RHJnUrsY%|sDtLIi4zNiIH2 z$*UJ}6^6w@bue1i@(++&e#wc&nBq%qr9?N1MM>bjtu6vrod6ch(nzonOWUM1cfncYe6O3fD|vL3G$1QHD&0W89xc9o zn2DYM3>l)$-ad_PBd3Tc`ha?uG64pmDjD(@GOHJ~fs0@QtkI$5<< zo2XdhD{%)@N=w*~N0;V-T3xczyg_xe_I9k_Uns3!!%q0Z6TOm5J6&<1`kg2E_z~HC z;=5ya+aUz8Bi{Pcr#CNLTRwdp3GrV{?zNRz{CoD#Fk=Cf7Ztexl1Z>qpt{cX`sVB% zN6!4*4;}gBBdVw4$aitoe;?gpYdiD&&xybO5f;fg|BGMTI)8QU_zKSg$@-StuF*KE zX#g={aTr>-EX@V>ez{0(!uRZ=E zN;|eRt&vYcPYYHYD-|;SN*K0+dsLxd_sZihO#L0S?QfmaR;-(Z<%Hr0+!;D!h`HI> zlPmldIT_$dhimKGJSN1=ftTOfc;PKx7}$E_($3{IeR_iWJ{jOsbTT03P$03l?+f5& zq9F&3iA0iNK+`Eo)N{_AJ*>}GT$4tiQ>h*$@)T%O7J{S;WKpmn-6#$6Q{D2^LDu!N zG;`~GrqDw2s68u(q##Ms&raexas5)aKzRPc@*o3|FVK-W%%O2cAqqY9DiI{8~By~460(shzCTxh{4?qg zhET~4)=0*(v)k8pYzll+it)Kem*VIIUbn(jy`wR6Pa8Jp=IA&v3E}1*5BM!DFWV<2 zK2g#x*Q0cCw4*p&iaZo`4h;FA8h^)atoR3tObg#!8)30qs&aFT$5T!21fW&SF6;s! zL>tn1ktJw)PDrT0FJF|X-KOa)>XJ1Z^y7qxc4s>- zS-A^YLT<_F`|5F>4IXX|+R+9Bqb(g-eVJZ9wXnPtm;c*S|8b%m91e-5H?OW<`1&(k zU0@P~TpB&%Ctn53t_6%up>l{3BQT-o)b#eoCXcC{{(FD<$j2WRAtuVxhpcv+cNP`P zGAthbp~rX!^xQA}{x-|TA6uHzve#-Y`jocHN>3dETnPHz*N>f9(p%psJWDNiFlDjGY~C4Ic<9X3?z{QCG$#X_uV3OcKu-$!WMKQ! zs@9Lf{rbXyULcnBICzHT!o)(EzXE3{WlX6-QR%rL(S}5(jw(e&b4p`XL!>BewvxdQ3^1+GT`(suU62~834^JK`V-^94)6Z11H}9pPw`;7mNj%3 zr({_}q3S>#6nGEa2Z+Rh|9wfLm9Zos$J91yv1&Accom^y`+q=^NoGPLw`i#>aD$8z zmZM|F&pQ}XackBd(UPF4vUjc;^x9S>1u~fNcgDkR84&f=Smfd+v>@6yh7v?NL5hovZ)l z%$ou(SGXf|!xNhQS8E|72A-Zi`l&}b z<#_X-|IW^}joBj$UYV*huf-H4C{6n96AlPqJayCL2}fC34&@3^$~Zp)x%S${D^I_A z@&i3RE?<;by!+(z!knLOm#7}@nRKW(K_bOUva-scj>;nd$rMQB)6-j*SKs>5lX@S$ zsatq_ukF#aYJtAdw}hP5JK{$c=UI2_qwnU<0ACi{dQ~R_>(9QSlY!SSZC_dCsb)?a zw6v(+8BkPswR0I#wiP=8ZoxLUd1`~vb_9|sZFg_7j*jyrio^_ zJvv^);AI#M4qOS^+??mPuz+S(!<*pKv52BnPiBw8B3?@~EYLYO$IM8b8aCk4h3qY#)0qO(DO!Nc*tw{T*N9h6R z_+7C?AW6k8hTRabbVaKJfA>2S>-^X>su*pu`+%clH(z%jwquK!Ci1>otlJX=Twq(& za&?@(kJKoRktO1eSirm?3ydzd6jkUmrfp&M?5q#1CAf%c9Xgp z38@3)eJWnqgie}h@VNh}pZe&LPrO&CgTD5!HkyQx7?^W_&pftu;X2O(?QU#O@%n*2 zet) ztZz+g*;WEddW9pjwFMB+4$JWom?9&kkt(QS(%`lWUw-PIzwrq^3G7!~Vv_0#-_sj( zl@*+#=Sg{?I1^alci$aTKltwLD{Hzl@XA|SO6a*aeP`gx&gE4oSxSg=E!}X3kvhAq zC?lJREN@KiM3kv%*g~j4^+X{a*`Wkwjk3$U8wp%iDFp1rfsJ>8h94s=h>G)N^|g(4 zVn0Y+9AIx-RN3C%;q67~Z%x_h-Cv(zr_mhKypw7v(;R|3)f#wzRce(Aj2crM3bvA& zt#jHSShZdu1zo8Dg3Q(O_!n@W+pw@ivlYFOE+G?_jk%m$7M9Vmo2ev=)Zr0^yVSR+ zC<{n)^KzUi2csZpw|#0H0(vxL)RrU~9z8(boLHgAKD4R!J{0>FRg+0LUeNcv0@!Tv z7t1kW|1|zMPSD^rUK+0NBI@J8I&;%~=~|-`Q&64<+b-!bQ9_Pi!OEeBBE_J9JVOA& z1*YOFW@2(r^aP-)hr-o8t@1%oiza4;8#Lrl)(}I;3FhB>`!D2A4h}{Yx3Sm4QFDkO zE05a^)g4FjHgUY8v$3JK@3lfaJa)D4=U{)6l2#?8Xk_tVX+ODYY(VbLt}M)*I`TF} zx((HkgcFh~dH&=PULD!Ea$#22ia5Gi4>`d{Is^~x z8o^R;Db4v#4#MgTZV`d3|ci>xwQdJa~HL zV-N3KS>-cxye+`D#@3&CWAn93+izWknA6%BJ~rp8Za$xkLzFCOuGQ#@y=W%wOF0M= zCQ%-aJ8^Xpz1S&)ZB=e;d!4_a98Unu$;N? z<_{|W9GT4^cqXy^(GGY|t3$>b1%LXR5lh7qeLd_r= zTn^Pyz>C)`5vA29ZEEJDJW{f-w4^x7iAP}(T}IWUXO@PGD)$PM_IuukJP}3ck>Yhw zH-=LrMQu6vdau#Vzvl{NLsdt2_jt1b!9*T4W-HVJRI8n1`Sc(pAKVaX3YbH2X4fkpYg1w6R z!lytr@*Gx0O7AHu`J3u*Eq!lVvEJ4u_vzwjg!ptI-51sVe z+2t)&e&*1!5Mm-pjc#7LcJ-OpW?3}dda&ifn?lQfTtnd(fu`#zaz_vbpuGQe9H1+* z{`&kwXYcswPr$j)3&{w{^E``lCx7arYtO#9{_R)hPORvqPYD_BlI|qYNrAY=&qydo z-~?dWueJD^_nvjX=L_V^q687(7?5{)XMnpaCyy-hk%3P?;@bij zI1N~T>NVaO*gAJrZwt)L@Er!70m8!lz0?*%9Za2%2ILO1kmf#`Rc8UCQb zs|2RqV`CQGrlInUO1*8{=hOco5AsQ2tx3AExiq~jtJAGGMd5wNk)*he3Z}v;^xYAv zy%9F#-o-apQY4U1_;U)z4f`sU#}3mNRq;31)@`JsWm%dOk!|S{BczM+7GFG5zRHrI zuEbWW%X@(eeJDUlx`K~t=Kn0m@QtYU4 zlf;GhK6@VJ=j~t1BZ0WU#Hp7f|Hf@_VoyKf%#EWJO_rN zmnAF3`-pjnimU{NohC=PqAv^sm!65-^&>>IhEDkV5oMlYM zB@9|)NsAoSq(mtD0*(?D%qS~k##9`l9Lg4c?0FiG8>9L(y?YI;cmyw2OGuG|yN+jF zJ1$bTpavTd>Fx{*7aq~Uuf%iH>I6VbwEpI$wO7v1vP7W91%)2y*b9y%0qrW6N+};x zETa__h6(@9)YAN^zxfdsVCi%AAEvWS=kGkh!$?yLb2}TGF*EDjP$2fzZ&hQ?g8UN~ zodVe^VHi%Cu08*T8Y!-g1}C}Yxs>@c#~JHOk}L{9JHmh>tPwG_wI+5ig~Am)I?l{o z`S#0ie(`ZBlE;35|LG@;!hI^aTISPjE8C9i{qy&nT>0p`PyMZro%x49diEdx_#Hp{ z>6K5tclOM&owbeaOV_to<6C|T5bE$!b~SnFD8+abr>Tpe z-HFDo4_}kzXNC?5A0WA^sP^^iJ8_ACht9DuN?IJQKo7jXI7UiS7MqM$pwTSB1~Xh` zlogm1XOOm|EbNRSlX+$Nm)~vSs9uN%ne~yAd_`IWbbLyV49`GtD_ap#x;(23=$%w5 zT3L}RIhXn-Q|T$L>`J^3R8h6bC*(;7STd>4KZ04dEGmcc#O@_!snwNN<{iq=jTA5K z+H|gg@tIe!#dcBZc}4Rs?@$wiSthQ|Wb-sTB&tEG-A*tZTAL+i}qPXnMfT&xuHAE)lZyCzO>~KxRA+hD+RVv}KUKT;gt@1uj(qr`V?X@9OTYE?xjT*`VUf%G2uMW&H$oi{R8hR9 zJEeg&fiUbsjwkQ0KKuHqA9zns`pK|N*5}V2Q&jN_X+1Vdd_;{dSA!qXlSowqlvAZ; zrzw~GS>WiE&wu%jk3XhmjyRU+BKLo0sYZPmhb`v@Gt+aYkMdjo&;u+jwZTinPrkbL z7q6^8`{wq!EAj?61Qs|ts0&~(+go;%tBQf$A36k(#{-d>PFsxxu_0F~vTxic+9)aI zs!y%@To?Jy+irzt?$t^*W3a!CWJ@#fgwmYWQE=x%A~ce+mYLmv83zS!bk72@boIJ5 zA~)4&Au<}xIPq9eU zB^QK|tzN9z&?soCcFLNf;sdDL0m{;ZW))ugGpf=T@Iu%b`jBgsLr53|oVRi2#>)V@ zW$jOta^|+YqI+xA7GkbCPF5};A!D#oOZq?LDS|V$RaCMSo&YeR!(nJub=ynlwmVNi z`M1?XaZlMhE;HJ6BrraLzLG>kL)p>R#}W)92lLTzivwGz>71%h{`Cg6s-y=ePICev+glyPihUQ)5>?6g%!GbyJ9W@BbQfE0 z0zCEd%fI#~s#jH4=0Cp_MhX{WV;?NB!tALd%OAY&=|}d%Dx*z^YQ}1W-aWJf3B>sq{L}25jn)n@T*= z-b4|tQt0D#6tuY3I3cRhTye;`N6qE>7ns7-?bmW9`Jn-l3Z5p@c>HbBX{XGXY>isSM`x9})sU|Je= z5_c69bx>9vZLwmaB6Pf4!#(SA`9GHEiZ5@EVG>@Wt_GXRi)@8c2IZN<&EIj)i2+QfjgjE{T zoK3s*@SVp#{m8}7e~pFSD@}EZHAZ*lcz&Edyx}(iPz)qh6u<6Qn%tzM5EMuUq|gLp9hlzZVd3$gl|mV*$Y&E?{mrlPv9qHO z->vWFvM%^L36q9Vv7`*G&XM;7zW=_XuU}q&;?>o!zp(oFOWWtJQaH1+IK>j8en5+g zGP9ge++lm#^uv}7*U3Ajxa`%e|J^wiMNPY^Qt2>gAz~jUcUytg zHf(O>q1ZV5LV->z1l5Hfb$k|LTHtM9CZ-w)*<@$_zgX9X?khXxLbXl#qr;P5!HzeCf`?fAHYl>)Ip_Q*B1*_ol{7)BE z^2u-t=0kYu@dVe#+PcSZh8&%_t?)K5)D3CmVkpvh)D?vn_Yr(pjDGW|NhF7|lb2*d zcK}eVYB&l^UhP|JQ6o*46j_SCf^+vP`3$=@`U+k4R%%yF$Fzakx`D3raWt)2M5r*e zhAIwaqus6TZ4RG>R$<3YIxPhghw?@YfmKK2vn}(bWk3H@B#1JeEASnFPOKq@2cO-# zzP@q($_xu~s4W>xKP6@K%e@E6EHbVAl1fpyyThdC*ry+bi!c8lyy!RCLMS#jd+gJX zO&?j@+1xBXXafNGN#z4jWQ}Y=`Mw+_COT3l2{4D~K_Bq+vNh zUH&g};RvlMqN#1t4>VnPjO7fa<|NIn1D(0mj|xZK5L7Be14VFqRI1m8p)ZA{*-7uq zk+^Gj2&1^|DNXJK00n#0^Sd%Ha*uNmxFK{UtC3yef6w+u2}UtvE7!I{hT38!4n9K? zGW#!Y)u=qqwqsH4A$ls7sblSCIy`7B&0eRbqFrq%n4X&BB5q&F7TLUfo!=~vFLS}$ zhXi{MZQ>U}D65251z8_9VF7Ke=feGWtbFJ}QI-oYz=tAP(8c%Mx%9q!Sj8VrY<-S# zsX^@Q92EkD^4?1CIGpQ{>2eU8mt;3-S1M0l)7h0pPP$mdHJcukBs_&TK!@1!A-_;3 zlu=QG^&KCT%+6f++SAYdn=ir{IG$^tyn=_iMQmB04susuU8a=}J#gx0K6&Rq{p_j# z>Qf7kzH4W7V~f)OrchkxQ-3Kv$BH?VD!Oz;UAa&x99wdMi!DMpiGqIH5AxQy%lI7P=MC)R7)SYH!O1XWcx ziX(!vbf|3NdKPE2auhu>B)TF)VT~#%*;Kor0@Kwh_;$a4X{CfLDA^(v7wPGn8i{Fh zmt}D23%V6n={v|3qi!v&Ku8!Lb8sB19(>l<)^z7u{GH^fU@1(&CMXF&R^q7A-?o(` zY%+wYh60E*F<~cq0)TE~6>H5=3GzAhzP(6n&GJ56BSz{#fv@#M@P%elko7HitZKVDpSN}W!@$}!g9-f zrmZX2w=Q3w@%q6inujs(kX}a(Nyh<0i-Cebb{##WR8h=N|D)3K_ua?TU(=n&`+e#* z*f!V8c^jD9*&#Rb`^#YRg>;n6Uhvdv2F^t;(C`>3al0E^>*p?$=`qbLE%5FsSLpFb z=Zqpy1evr7jKnWxLXuP7TfqT<0IL^5wzgmXcYplGfBcKAQ-ikS@eX7Qgz+%9O~lHx zi|@Vba2}9aZD{? zQJ|Kz#5j)`aC#rY5guD4g^Vu-=InWNHBz3~26;zat{Dmtbwlc{m$6$@U> z2LcVgCAFMexRejX>NRa$UE998Hp9B+7QWk}$zbO~@RS|Vec%p7gy%}q%Hqlg@0Tj$?9;1? zH4LYgAG>?z_!4XQ#EjX}va!?!N**x_^l03ZNKL_t&?a2P|yPAV@yU0Yq(@-ICl z2NQEmR-S1CGGKcuL}qJJgL7|5f{+m0>nOy^q{VJ7DmrUSF(Nj@CK%2)q&EXnz2#o3 zRAdFB2w@P+jYdgqh46qMr!keyc;;R4#GyDU4Jsv#G@NA$U6k=%Zhc$OCDW`Q#c{9@ zHp7Kc1rM{rzU-6n#CO^Lpcv#NqJ^|GL%9$x%+nZSoXkbjab*oInjIp>Y8F6rTy@gJ zKh<1%fQL0j$}m1Cb}$}?Ynn7T8%u(!;S5x6{pH8qvVIk+VLxtfR0Op zsRX2sS%W1L%}vxsqpE_bhNyajB-L!c0CGeJ8;CN?q!Fj40yZ5X%SuTly*1zl)%PkM zvESC_1|QGyX}aXjyKMW*VkRS;>d4XzxE?T>z|CmsKpjTqEH zEbWE@%x1*VVNNbQ%%3^B^zJ)N*q<^tGUCGBr{?ZDv8@lUC`=(QM+L)09Vu?bLX+rT zhHaBD%+3@yPt;&P>PkMHKym`mJ)>`-tyRk9FauU9g$V5NLUQwuHhT0+04cP|EEkN| zUOxZC|MKhCUU)+f0(A|U;X9kL7LXB%ADcc-1AgkGXaC7(kA3b#Q;YLk7q006LS0W4 zg`8p_9F_Tk6?;c%upDkJiXs4;p~b|y5vRu&xl}}6X__|$ef?ti;?XeM1I&jcjHSL2 z5hikTRy-rrY6+Xc81g839l_luG>_u_w%KY!VjfL?e^AuPLn2jnRZs-$I9AI2nI3Ry z71Hgbbg?T%Wgm7;W!Qn|nR1Q@IUKT1M6QDEpev{)QSn~2<`qcfK0h<3_bzJnp^6oMLx74}5p;e&uMYEq(#|$- z2G`K_P!iL95A+Sk5HH=`;13TtBwO`tNH~-|P_ZX^0??{ml?13OHda-kZI^7&w@dO{W{IS=4)5rcCAvZ$N*EJooA+$?lGB}9T^j=WRhU3q`%)XX zV*D;|Se7MLYTJ7;J}CU=mFpfK6<;;Ru*IN2%JI+7ib)s%RY}!$nOM)P`00Yf5{H8p z^B^~KWO0!fG&i-XMYupjTKp#zlR7npL5xXIA4af^Ku+)M>fA}GtyF7I0^s1GmvlAB z8`B&*312)}WEW+STNODy66p3N5LM+jH+$uYm%jZ!|Jt>e&gD5k94^K*_ztSEfJ4SZ zz%F)MdhG7gKl_<8|L8}TKK$U$_4S==8}V|mhYJr48G!Czg^fH|TwAg)xKke&XUh?-NH%;{(Kr6(>lsT?f(t%1OS>O;SqRy}dPa#FYf2mMJPktr4 z$9^xWwt+pWRx~rG6s^h-Y-6aeZmJc=VVc5&21j+Smi*gUjXo$nK|_oa$>GKM$-+Dr&TFuCK!w!z%9zt2$MJldxVO=_yZgBM zqTc!z3m~F|1?9>a&)E3^yPK0oLu3^JfP8$g7CX%^&_{@9tH z|B|j zcAc=TT7wZZ5WbX2 z(ULDpA*g;6k(WkT^`ChK?S)KqPrYxg3u*&tRGcA#0qT*Amjs>FbB0cF<`&P4*ft&e zP`qQ_m5eZ*EP}OH+6HyFElvtRqoN$zLz>VDK&V&MM$v#PrW&HLK|dC01W0g(#oda- z21+x+BxhSb9C+V_9>c5#n!m}6PMY-~olY)Qg=c3;H<;RLOo=}aDymn8nQq_-q`8VS zj{wcY$x>?#Rh?kk@r@%7go-g4Fb`sYl2Y@+lt-B29N~abQYiF@4;m!p;oY%TTGC8l@44wMYw_p6`|M;t~|KT@PoyXzo@}p++&gKRdh&Umf=4Ow4?BTON z|D(q~`$1ku*rxp|*NY-^2?h*{HVPTP{t1xfP?E7p^u%0w$Gg9pR}|YV%(LR^z|x=5_>dcJRU*#z7Z+JtFb@RyMYkQ?ZaFr{6vYnZ zv=$e}2Oq!7=5JNped!0ob(j>9rAUqs!=(q5pU?>a(pTjxP*tj!syvuOvJ5Cf#-z4R zNmA^=z^XZb3ReZ+HZaJl%XQF=ShJXt?{%i5IdF3M-<9^|p-@zxgGk-Xw57kfb`rRx zP++LEl}UQoAg?bKAe{x22nO7%s>Y0)LOAXcS$5!HGRJzR-J483D00(HvT!~h1J=@+ z4m`Hj*Q$vL%1E{HKQO*PX<}__*SqPKZ!e(*VdF(z%DruA!-$Rw{uSLy%OSjE?=Zm1 z`AUDB>I}-bpua0j!I2c&F(8J5K-I z&)o4dpPX8p(+4HA1P3u$9H)3pSUUwIj#XV>FzmuLT82i+j`d>(dVDdEx_Y5`hM5mv zZH(Fz+lG`9RH~E?n9ZjSsfjvR4O4eZ$W+!Kv!ZsDbAvNj$oC`|t}rH#wIy7z=okqq zLgzm7(P&3GhUbJ(?%fik*rDvzex6ZLTt#gp*GxLUP9;i8c6ou-AFs9Z#Jb_Ov%B`% zMK#pAO|48INqnwH@cBZ8c_RSwg^-pQszzYD%rz$4KjN>k#6}UPrX9d(uG1XCTIxo=`&(3x-cg15NB*vaPq&K(wLD$Xtl2%;H5F zO+gcmWUE(y>!(Y=>%G85Tn(+9nKf=G;sFzsyJ~Jmn(4pII7)>g=3*BTRk}{VsMqC( zA(_UEh6^G;hT||{TG#00meLBP_D5w1hm7LQWU`x9zz{38lA_E8Wn}&wQXw>35PC>` zVNtVcCB69Mg=HdRT-i{x{_0yUn0%N?A22kD6qA~XbmQR+6^S0^Bl7+zG&5qN8w$X_18fV1^!?+N=5j;!E7}w1A{?fN5M& ziZUP39DvG430pFyu8D^>lm%(669nY|I7HrM$1;Ug<<^k~#IaM)&X!PPwF>T@7Zvub zHOwWNb+y4dLb*!{oor1!R?uO#2bm86JoD2bH!P%Vc*c-jisbQ2`IXVtMYNGp-vi02 z?pRz&O`J+{32&hIo_rWZB#MYMh0rcIB47wu$t2of(iwnFLm~qh6=nG0YYxB-) zI)*Jg>H_93c12f8ODC+A2LL!xBT@y((x=;OA?fy19G1$3r55{AUiNBAaF=gE)ehsE zPfwd7TS($ln4(ccL*xpXM*0Gmy)DWzHGFN*%ilpl;+b-O3=NLYGS*OedYU@`@p1GU z+5C3f#DZ;{yS(<&`MITqxTj4UprvKer|g9i+~uofz>@aQ)@ni461Q-$%5Aswrvs9C z1-Djgr9G<*)K{jZY~c|?Q^v;}SiUKW<6{n~cx7cPBrnExU&6aVcNuN%05J8FRuM>S z1E47#ks=V?ZVMlPAY)ig=ma1X3$=l({zDQ~JODSCD4o1ThD0K(NGeC5Zd@^)Le3GV zjk}Tn{*5$A2bm(WkXFD{-KG`=qbR6M04iMTxZ%tNfoQaDF3&jDRF>!}3~H^GVN_S0 zqG>5D`6__AmXVc06X(3RQd{i-=p=RHkqvDJi_!upBDM9e3fl8T%^^_aW^A{+g`dj{ z0LVT(;7O(GX&)A4gZOiih9A#aD%H(2d)sMjdbnDZILy~JN*Q8T(1lhuJDD zn(hO~^FPe~^Fbg3%IZFVX!Jo@K4Cq3`P(l&@xTA(r9Qncboc#xXb^gIKTbHhL4gjtU>?)R3iyxJBeAeJICg)*X17hh<`Ox&t+sYE- znm|Xz8%uS76o`J^Xr!eY3%v2s2CjmYC}g1DSX*PX7xBRS zf0`-ywWnWG{nALo>%hRG>DBMN$|vppVZ;F+*M?0P)Ut?eqBA3*3KJ7oYqeD?1wg<- z))EICrImhaC*GYXPGAhxnj+II%?V9gy{qDe$p&T!-%2$W1}X1ofl*q)7miUz1}_ZY zB|!%c4C0SHdRvgNDp3GNu>xwZNV5tpvM+c-Cjg;dRl5Rvsg0Wt(JDaPOCqob;#NaK z8|=U(+tRf#x<;i@WMFC=!D)yrO;{{O=~x2FvpP5Fy!0n77V&(yqE=fGxCk!R6>e#Q zfm=4Z4zuJc47kI3lv|p?O>)4rSY^XFwBSz>cj6l=Y6ZF+L3AxVRJImKtslI0Hn(=x z{pNnhr!;ZM_AK3lv`l7wM9Y}4{WVY`eo-nk^U>2v4VWYh5uZ-%&3Tg5?{G`LklcHm zgd!m{QX5DwP$E6Ti;ODeS$_|*n(?bh6MfNX)X|Jzdu@ayvBgxp6}-H-eP#9K-}=fo z{)bK?iJO%~Dn=)!O6+Dg<*`rb%s!EgWQO zpb%F;xFX2Fys1#K?|c=MsL<4J2zvn=Q7oR6ZEzv~AVTH7ZD0@Z9Bg|Z$)A|uTdkT4 zMK!UVp59s8xb)}Gfh42t1k=vO*2S+rgBx^-!ZL>hf;-KCG=3GS0fMT<48dgERCxi^ zUrW%!L9B@x=K#?##k`QiGx`#>r5$pJilRK2v~W~Q<^wziGlL&<;J%1H=8)~`SeJaV zyRkbo5Anu30M%lHYBinB1zq18qC1%EgiZkV*Qzy5P=Z-sA2@9~81-+8t0GC^r5NSk z1Gf+nvo-W8uW?6$pp77{Rg-}$cCf40EC9b+B^qffZ{$i<-6JUfewyraWudgT)w z>b3^;B0r<23 z?*D$_^M9i6%8~qPa?i0HW;y&@Y^lyPwekZGp846&%p6%DM2Sy#E%50{2t?E*c{u)z zQ{Y0YxGO>Hnw2uxMwV>=Vo(~; zzOV^AMQyD>j$)#swocusUNCNCpdB^TwAB$#6be;FlpH6+KwO}Tv?&_NVk#QU6A$O97DY zb*PtJPv_EyiZHU6U+^&|@YPJ(NL~N(FU~0%xf80~A~hC9-2Ueln(ZmJT01*5SYMzM zVq_5t&(Mn;fzci;5i)&^AoQ&W^l(rwQ1&jMuO|RfnJS87-KoHmrk{n(XK1aL;~aRU zcJ&vTp0+8j)s-dI6j1L7%eTrHgIHx$Ufzxwl8@&EiEPW;&qSG*x$>~N*!VfL>g)qe)sEr2a;qKT*P-S<16;YE&(l{MA^$#4-! zHi0WzE*icXwFJDu7AU-BXwS5p^4;Iq`cpL;beJt$jf>IbWJ%S&QCkO}^##q!e3v@8ZR0UNg6fv7);a zav>MK`||3=Ili^034mG?<>jX#!A`sH<9#+X7TA_%54cTS;_hRE;oss)6uz z`(*+pYsqdGPlK(fThamh;$tpyKpM>bvSaEzZh%fENf?VKSMH?8y=Cj zo-tsK~{PwFoJLbGUXP)f~Yi5C6k9t0&<6M(=u zp?e33z=Lmo5>O)B_7+fSIV6b{t)mMpAm7x7h41v6S%Nc0Gz1l+V~TPD!&?EYmt#fH z;_CTlUVH6#zy9O@>EFHm2Y5&!dsp{j))0*0ij8ja~MJ-X+YAKzG9 zmM^N8;RGlj(I@RdXs zQ!?=CB5)*xqbk}Kqz^QrN*9hGh0G>uBUdyNRNU}h(0I-J)*WR8H!tqAOK{MoD#lkVf_;65O({qxn-7w27(?1gb?Val!cuaR{L7Ns>n2(C~2V0xVKKMz*1aK<)`Z zWO+%@CuO`CSUh_i7?0sggibl4))=8hQbf0THpde_7QplFg#SR zlH{J!1bNj?whiFp7u6nQbz-*`At0!`M&#<`SsG6ywPs0-IdPfv)@IdJL~qXw#MYmk zToZ?kM zFr93hW#&wtrBh`OxSY^UHLhKj)Zs;i#kbG;>$$`vXHC|qq2!=e(377;k}EkEgwckk z#uTfu$cbG#Gkf&=4C<65UzX9DRAPaq26JMj%S}|NhT|%3QV}bQ@Av!YTmF6}z^j0i zss%!3t49L{7<3K}H(AAg@kejG{(pSq`Tz0XzW#5%ITNo4!yvB<>e|SlOVx0riou+W zoQ4MWe));9C+^b5pz#pE{9T_4I-Dk=Fp4>;UEzvN4}$Jqc9eAp-XT{f+t8-#h#5 z7sPi*(<<=>1o&J2(_ed*tIy!rNLqDZVbpnr@O336ZobfExkD0Y{fj(FIwE1ALemCB zG-&L5DI+JANUbdKkVFi{r_smgp9PaPU}B4p75)ZFtm4xKMf)g7IF}!pRhW4jP@KCj zUcM#Um^)j7`eP2VL!lDT&kTyN+qI@Lt`@M*(vt3F6T(&j$5GXqDzuL79^9qP&e1&d zEZPS%Ym?LTr)!NxD?R>1Avun)&Axl$%Th5rIAsWGzB2aDG;zAn(e`0|Y;J;8^ zFQmqYcoUBvoCYUG23R`DN^(9+?D8$4l;&TmNSFL?xgn7ISYpPyvqyWrHaDr*T$vae^Q-=3KaIoqbcx$zx+rMj@6fPW8Puv0 z2WU1{*H8a{KiK=@fe`}$03ZNKL_t*XyPJC_SfiM}FB{1>@R;p<>Df2V{`vDmtj3H= za5sm*sw#CZ0(LzO?nC0gQtl8D8ca9Xp&5laQ~kz!@4xM?GU&f zQCHabWOMC-_3aUo&c1g)wb$%rhasu0I)AO&ctXSYNz#4NJ#f7bTaH=(Xw`1!|t9s>5FO1fTZmXfJ zgT?0HwkPtWy|0nz>V-i0mcM<#CJ=r4(JpiwUw87k{U3mj$_++-qsIa?MdpA!4PYX@ zxi$CJ=@Vc7!OQ>KA3pa#{5}r^F2C~5hPSFjY}g^JUJ~fmYJr-WKGNXmeK+m<@{{xr zY*|as;VPvf71Rz>o$ACOEPuL09jcZY@C~5o4Iypu4A*$wRl041RCX3YXL)3(B>!;) zga#^Au-xsKHiDq3@@xl1mU_lQRkC-c+Pg()nqp9l9hDJs7fvt`VXC5n?lB;dWY!d_ zo=phrQPPM#cwF&5gwsEnksT5?qqiL#a`tqebLel*EKWQqLD~Nor-9a@Dg#89$IQt_bcR zu}pkZIe6A{wU(&TNDYFLRH6kaNd+KDnR6hTi0O+xbL;OPU>Rnl>$?Q3O-c)CFvb=) z#kedgHJKzc>iU`a$@212*NeU00=qX!a&wAKrbW2Yr$eAso;h4)wvvdF zG_cmNz{ScV?OVAUB2nGET5YRbbw7@jPE2bK#b9zhWF=z|^NtQ}l}^h&!WO%lut^uI z+Y1>$OQfE#16-Gcyg57PNU75G6g4)_*@4yWqAk(jF|N3}KJ(+Z_kZ@Gbc=U)MSq{%5gX|@Hjz+^=BN5~PuqhGX{um~^r**% z5K78gVO{WSt;)>?$A^ce$DOxl2lm}#QPY4{a3LMzY1N^FrH(uGF%~R%DJPPoq@!U8 zx5bp=bUg=vHh4ux6Zbz@qK#WDJsO&M%I8dgP?tVr^%O0y)kJRs>MACuzF zmFTTqrA%A2N@#`PK)6og7Rtz@qvVR7Az;L^tJ~d`$}e4%GnoqQWkBrEJdPG6+uuFB zF;LNeNaoV|1S_j_1JDD;tsy4%tAorsbqVO>#?c1bPjQ3UBqCP(?dv=L-Iqs>?!Emt zKd0Lh8=E)wqM#{9e`EFX{IP%WCkwBiYEF&{0-HEHPT#KSPZLB z%RCSm8DI%(Sb~n4C)yv0cK~>6g2gQLwi6!JqOIKwnr)NXC~s`EdlLZFA}ufnwYMBC zYG))P$t|t~EM;M#UQg_j#nibm*w1>hbxUgn=qc3RESS>OWx9ES&n>g96(oL@{-&%) z6GcA3->iMg$a0QS+Ons$v$>=2yT2pRB1*>V0cvdNhI02`F(SQ2QgLKy#TMT#5h7cm zO6&%NR1Ka0H1|y|9Xpd`NY;=*{Ogn|D>)w+DQSiBeWaw_>??` zLblULN6So=kpmM0&kYn+d*^7cp=Gf%7%;cA7v4R;aPq?WXI~#Zym$KEqkA5>ZO{F; zFeT9E!((+2EIku|W$EZB=kHN)B@IlD?){aIFTHYdb7i%EDBa-12r_G`MV2qFZ0NJB zF)!<5pO&W9UzHGaMz|sZD#=|3vfNUlYE(!S8VRsW*^h}phA(D>yv$1MAbL&VZ78mi zvlQJ)6FyvL=gY2Sqd3v0JmK;wiqPd%#d_Wzz)4ChSGkVFkj}Kw;!YySElz2fCNxT1 zbd?+)Szn(oO0$NR^)1R6qi{M9rl9K^C;#9X-jX`{i%-z5Y4{8=Z-DNIh`@~gXBJ-n z-@kU@`>!>}wLdAsS#pH1*r_CN@+=?JB?%D1!7AoR!hNt4Op-M$2}4+H5`|(@S&YXf zwNQuyS4HjYAfs*6+17Hmfanv%E!dVwVnfe>RD8!dEEM~m578}q^%h$Ir6oWC&M zyuXM9^EYifv(@~w?yH@IyNK!D1VEmSD^zVVX_XwyN!;}(U^0&L^K-mBF*(`0b;%M} zEdXa?b4f;lMxcg<6mA-!0z~PGl~mHHY6Uf93wUjcow;=8#jsvG>!GTXd0)QT>8z#X z>xksW>PidxQf5x$f?<$^NaF#{T6zLNLid9TW%u9VqkAP#$RxMZg-so0>MJ$AbMUo7 z>2k|L4gfuF$?BR*&;0brfAK`7k6gVzhJ1Z}jQ*ESTo~rIy;q+jmQ_%)R0=JUuxP5k ztfdq*v67|xR^RY}X~wL|#rU^JfyYN&Sv3InJx8R9k$Q-OlvGNH-LFxU(UlsCN=My& zMJY$EC)qs82dC`(U=L>Anx-~EHE)i?IoZIhpW@XDY3 zq`6l+oY;hE&`Cwn?X!GQmXV!hC2s%YjqzC6HR%)?&g{ZVOy45|h8TK(MgmB#geme! znJjKW0u+25xh)LG4;FUHSczyuh9s6zX7#@dl*g+c3bLnPR;r}7a1yG76v>(td{%~p zwlwLcxxM|2hX6xsX-zrp9)DwG$(9ZFCVdrBxr30rr0#83Q@(v-KzLDP$FRo5Dx*? z=KZ}+%&^tR?SuXyf3uYl8XtF09|CI?VjG6Li=tawTwIKcS9ETryb{#~xE9)?wUiqV zc5ecJwqb>$M2@m|{3f%^827sY${aj5VeM3pCjgdJPXKtKM8mAqrQn0L3wU6QpCNpK zi>@CC2(P$4X1vVAPcEfgtZrn-TzwKNh3$k2xidw}StB4Ag#axnt*A^AGSVmV`E-$A zpUf?cZ%~Ab@!0K$R8P54A+bgoSzC>7GaM>Ldn-5uk&+m4WAnmay>j>$9_J;HcuISN zWc+Y!5R{!>XBN)=`HvCO%?gA@tX9p*zJx?ErNSVPNl14~j8q*V9%u|7*(0zg&pIp> zwZMB9_^%a^TmV^K<02t0ae^am%D7U4khVINhLFh8ydW{u6vM3^=#MD4}aliOivBZmst zH91;MkP#yeKaGR~CD^0OGLLQN*o}Clp`oHgD%(h_l?kyqCJz-=0ijpw`m&DJj1jiq z240PMK7`z}9b^QlVXl-{=B+K3ovh6)9{>HnSUz{<_J91@;RAYOiYCvI9^m?`Ko+OF z(Qd+;H(q@1*zte+moqOOXSZ>t8I+~I%T-Vu28;q&rN+s}=nPG41yiEabbwZ>=1_-~ zKyq-)1f6H@UTf>myu4oHiq@?^OO_Vq1$*Ow;4gPgf!%?dgUwZDYoP_hNag^|QC{kx zNe&M1`KFIRz<`Wn-OS`g0AE0$zquM)BrC`ui$1W0V%H+wn*g+_U|X*eOJ-hSr%HG=-T6oS*KNE)u*Px>iu3-(4Uy?du!8Wk2<`xxPu4Em@*e zNvKL{l}lI?G}sVEkrWN6F@Nmz`M-Sm(9b@m8%BLM;+d}*fAB+={?ZTLn0fJ?SS`wx zg!?{;g*Lf5C0-#_>$B`K zKvF@8CJ3#auhhQ>_oPy(;CN|QV|`%fWu(R#YjTfJ*J|ZT&cPL%RQw=6C6MLSm3iF6 z_`X4B!AyV#xw@{^LGPaDgJP^47(23e@{S|Z_uVpm&(Von5AvCrPagmvcexFb*h$6;QHNs1B&JpbnBYM3o~+olaS3Jd$56 zIF@gzg0C3Vh`I%~NajKj4<`Yyb{LmoM~$$soq1ZM^_HN+OG`@-qZ`xC)#cTZ@liEO zRhH23=850>(%Q*Ces=E7GdKV8lLw!Eki7wdQJT-o9YBN(Asa`VkAo!CQmS}OXz|p= z(|`W_ng8^{GMj#ma0?;b`-g~*M2yUa11x$Vx85gKl%YBGspADRD$!X+dO+?ToR))M1aas zF&^DUy>0R0#Mm7?HtA(x7`J@(3VnLIf}@TFRpklbEHDCU2;sn)bP~e?jw%g;7-$)z zWqWXj)H;iR3_>cm9s|Gw7R7jRb?&q}h#Z?6 z>#G-Md12%7EAI~eQXq zBc!t^Ay3#mv5+5SF*&5hTljn?tB*r-Eks^h8)P!?Zi6cH^S~{3 zq-B}nl^5QgKXLBtcV9aE*+=$0eowU>aHj$i!jcqm`ucc6c>bAJ&wu|lZZK`Ftq+bi zwHKMifSQ8%tA2v4it&ypRYm#0*)^0SO1M@?RVp&F$tc?eZm6$stRq`AF+QHm>(OcS z9~^3YS{Upk%TD-~%#L($?m$Rx?MB*cE^+tMoVk_5XnUC16mN0dke4(%1-ouc)S4#% zsdKw#RTKio{riZzI#Zz|<6`e%0L+{ki?WxVR9=lD8)S!#;hKTLR{g z8;u)g@yzgU9dmkWnmJoOmmC>!wR7+a-gUang!hwt`#&gTKyA_GrzmxD;7Y2tgotK_ zwM6=v>hfC4ZZP8RLfs#!17m5*+U(*mFV5Dgvc|9*An&t|-hOc9o%7o$9M=UjRY(dE z(7(EKgJ*94oi70UfCm5`zD?w5rV!EOW zN+)X;m%b#p2uk2aEbD)UZ`un+I`$~=*h6Bg;X6lQ>D$&@ri% z6b>e-VpW^ckK21XM`inLjdcs&thmtQx>!s3L`624%b7g-*C5rC1V*lM@*opI`Uw{n z7E|s>^$j|SJ#H43=Vuq^j-TPB;i1XV(IfjNZaX-A@6A(p-ZXXRk)esv{HWbL|L$!f zWFw9~aMS1`w=DdS?SRJlCIIG*Wd=O>)$B+6e6#f@?^t7GGGI#4B#SC4<2a~BGgZd* z;;fOecCPYR(RGiwaUnxmIbNh#{SdGQN+UT@pg3M5n2roBEL!u}CvCjIw`Gw*2|nbE zGH^n0G8{G+G9X<9fxTKTXY1-&OYHd2q%q}Odw2v`c-MjT#pT7*m*(C&bLEwHW?nnF^xj353o?FX^sng_{SKvC10CH( zMKy{ELgq-E5krC{$3Z;MG5`mmBa2SXSh7pm@HQDScnW8<5Mq)yy!ROM^%dL(yMnl2fs z(a@WG9#W?^mkZ9$D>s(^-om8PDnZsO3;vJ}f^$wb)TYvX6W@%;2-ckX%gc70x8hyFe1+10ZIy7J=j6aUZm zwNZ4)E32nMbs7eFW;BUD_@G%EehW!qYpoT*G{!4F#lu zv}rZ0Rg*3xD7~sxr{rWA!};Y2aFGE$kJELMh<;+BTktXxH|&*F#$g1Io(7mZ1;lXQ zx#Ed|GCDUr%*tYGu;8dJk*V!SJl#iN4?&U$OxoE<^uo-QSKm4Pofn7qOz>EMSBKd? zh{?d@?S~;QkDENk)g4G#!OT^_-`X%RJ~H{Kdzk=u)kK5+9=!Eu)B{uQ{*lp9mSnK? zrzV5a8v`%|QzULi62-EZqo3MhnpE37x~o>nb-`#uuWur%wy?#)(vRBGPceC&oYAR* zEU0t>miy%31jy{r8JW&x$B#rTfPWJU5}QmEz86Fl&T(Z?fk6%=|FmW0?P??PHBqUN zNV?>&?pX%LJgAbiQE=kky%Q_;H2si09TJ3BgnF3SX~@S}a@qd2NQg|_wzd0+ndh0R z0scltMi@S>tm;j4$XGg3mKY(8ghpH)>>dL(Sa?qjLL_M? zk4kzOmSUl+p`vH>Loxz2nViBI?E#2Zf>I$#7}LzN6%dC0O?B20wncVO?JlBw6M*RGnGd11yt*||ona2A(j=H>560T5z_*fj zEtaaSpUt^wp1Sep&ePr+LdYMX5+O?Hti+UW2mdRnWGZlt=QipUvd99lgSY-%QP<&e zxdL(fUp)10QjNJ|>CjnFFs~OOQa308Xj5SG%xWr8ft(TuIzEcDeCEo;y+@7j`XQ?@ ze*e*d$q{ZBvASJ7w?vFfU$S=)h9#2dL8Y!u#AqZMUDOBYxZnQQ-+bi1|1EaP@6{Y2 z8VK*7z4?3JSiiE6HvfqWf}2kCo?-FOF4ar;rxWDA5ip7Y&a7abeE2rSk_CdI+efz5 zUpRYta^G}F7UD?}RfUB~S`jJD%YX=?dR1|z)uEoxHb6c45?#DR4uz=F(jcjxAHGAu zt!OV|<5R&(;osgsj?@+as1XcL3PmMYn6RQSl%c$0EHVSaDS?f}{Nku?YA&rV&Rn>&UsZ z!l;f58zZ@BmoXW@*9c|khLH9?J5*F+c*cj68P%`FCzAwS_@vBgBiq_GuWT!`V`r=_ zQ!^P;i{SIL1QB*UxMFn!adDYJ`2ZulO^xw4FtrBFfLdf#nc9X`)aJHjWOd6f`K^O= zB;Y;aW*qTD=ty;eeSBd`xpd`EE{7e$_uMnMz4tfR8fayHaqh%9D7izV*?W3xR_0RH z^-ZlHMO3DUJZsSF#k^;*3I^A(3P5T0sGFnwWk-@;1gq)myarFk_+&{#T4*{eCQ$FU z&RFn_?kYA+B|){#jx1$dPH=&wg16D$Z$adT^G-fhm14++h?qZqQ4s(nQgQSKVE~4j zTf^}jrqpDoVY-B29ee5A@CBPR72Ou?l0dD+bYlX5p79(609%9rzy%1S(DVjaVW+^& z2u*tz7UrR;o2*s%?!|QTLsuqyIqrtb8kBztmg5y)Q>&?mP_zwMCkVLCqNLVYmjZGs zEkIj;#vsN>7BaHrK(-BTnX=^^lGM3v4_7QmuLmXYAD3Mv*ch%*db-7T&h>rvA=#xT zLAp0aZ#%#$prxOl9@JWbs4GkoBIy_!N5_SOK?Bym=*r}Ey6q)GzADl```XE4zxRy? z|GTfyzjuEQKt*#!U_QflB4=JWUbg-Wfsv9w9i^GH2<<{CEA8>960MI8*hF^W;?&$X zHGc0+N{jr0x)?&%zCK=wTbi98-#5h#Fr>38Aw~kz33^`!LtN*U?nHr05RY0TP-TiD z$hog+VsaUw%m#_cC}NE315?-OpNR0T2a z?{DVE0&Iphv&hZD*<+{9KJ!X*dVKsQJr;-$3rybD_Ede*&7&HM3!T`x#?Z=LX~7Fjh19BjsCgn>%#9z8dv&(*@nKvOh`8Eferdc zht~QwR_2!YRz<^q8n<;ri&9-*rnwd>&x+MpDsLZ?+a zOW8H`eG)y4;Epnnr%dbC=t|a75CM(Gx$nG$8{GR}{UURK8}=cX>o5%V#+Y3Ej{VDT zoc;C-T8$=KOD`)BYKW2XLh`Su&jAySsv?R$>L`I+ldS*YxSN(e?4;iZFlib{@{84zQkp>`e&s?F7n<!{w(os$@4( zw?62i>kenI=hPGlue1Bev%-%^hdN;Sj$p-9s{V44nvBv()6x0u_|ZI^lWfUd(*!`W z^OJ3rQPzoc001BWNklkt8sF*6)6+pQ@Nl@-6Js+v8;3tJe7Tyz8EJi7>s9>O@ER#w}d=VhZ zj8o9rL&U)UnPIRGYR}9K<{XI+#DqX9dxTs{q;hE$;ABaY3jh~jAkO7&ep|N z#WQ_|m3%>tT6GyvVwl0nQ2|LXgWWw)`>LeAzPY)%0DuE%i-Ir2-IYagRUjdtZN+7;^vyYTcLv&;`kYja|E&Z>|2kQNWfS<<#Xuuh*qHD%#d zTM@8_Bgzt(IMt8RO+@_Z3Q|ZDMyVp_pG374SG75(w(BgUE78BWgfb}skN_O10l{tw zZt>z58Es*Co5=K2aRB;r-ylhh3FgQ`Mp23ZLGtNc2)_TTSq+-eUO_bxXGaqTD@BLn zI-|N|si;4hZgXpGUayMvCLk{lG$uy}Cq|byE@nwlQl2ASeQl}H74 zInWmPQn(h_NR4bSgtU4QcM@{dq*SWfqb;pwE3uQ&l_Vu)Re2Ruj);hx^bAKlmv9bM zieCN^GKe6qZWEZsyu?BhsDdnq3o&ybVI;ktS^$G7PXmMuJdjd#%BjvIdCb6hRb+(Z z3#l}5RfuT78BzI9p>R^fEO*XsJQ%5gEJ0Sd6##r4ZX;_;we;XpK{-{?IupYcMp6DF zQ>iAKpA;v$`;_5bCG5*2w1n?*h`ZQj<;(R*L#^j)!S#@TD4JcI0H6b-fA0O)l`o3n z=S|*L<(u%X*@CZY!^eQ@(H_Fw`t|+5>`>vKNN&J_;dkz4bbZRs9OQAVD1*GFO(AQe z36f5v=`pOf{*3GW))pIodg~0Q1w*m}lhuQ&F!;%@Jqy|0|MW{5xG+S7rd%S-=SCegeL@s}CH}2{{jF1f@<(dJ(39YDc{gt9^yU60 zU#{51vGHZ8hfr3rRW4PFe^xl?pg<*88z+*jsHqYZJi-e^BJOeVc58)-yLeF?b?v0s z4#=Z+GsnlNdOQg-UuWW|0(>VmqGT0D1pHeT0a8U?q;j2VCMHe9Dm>>6A{WHtQNMSt z+n#G1ERaMTZ=C$*bK^H3*mM7_dmp@wAG?c|p+Dm!4HK-Q8GXbKHs;Wwsm77% z_4m#Xj6^#MdqlmMBAF5Egxg9BTUF*wh9hl#V{J~~9_dk#sC)=)_%Lr1Jkv#<>$2MQ7{E^_1Hj;G>@z z>d1NlSn} z2nDMzW@@@>Pr3n5lB2=GGX${^MdS)Q8zBsM2cPBJq;S$vzXBkyu^^R8QIjDZc!@$h zn1wtpPgyLW$$)~glBFs=q-*7nL)2NA ziAf7VFe8&3)<>TmH7}@@!T#Lf(<_p%y$s>H9Oma2qUtPID!gC<$aSk!xpAuMZb-UY z69Clc9Q~sZx{;*sZZw-5!Ejv*ojJB?JTo&BS=#X3kM@ra(*tgeqG!@HqLLDGvhPLIUetp-V$eMI(Njl;Vr;s~9ANq4+};1=i{rN(AeUYV z;NE%E#SKvwG3f<7wVQkM^s)c*o0pz_oe8sc{?V);-TQY{Sq(H$kk{$tV}PnwtFM|8 zVCOQO3+bD9^fsPuxwu-sibI#6h4(JbW(GNCoDS zO+$WutOysKxrw`o9f?etp)`QVig-3cCdm2NFh9NF!z+5r=Vq49UAgqqab6YR%VI1I z+V{wv`yRTD9Z2Fyj=O^7MN;e|6r|0-#K`m`w=KVLVq|=b8B4y+DV_ZLEd zsfy{POzp6;xy1*Cm;4Su?-XQ36JK@U3&*3Qqj&)CdEz*lg4K>l7Z{;KM>&XvPKsWK z0?^y`Vy!jwcr6hUCyba%7M{)a~X-I#w z!FnWmX6!kj&Je?DeLdDD$n*w~2B(t1J|^K(V^=I}Xw?;yL8QKpxEA~0v-)4^+FTrm zM@EThwTTF9y-Y7l%9P5kl=v?vOAumMwj?2piPj{~j{?vlc?*dqHW~b^tat>hL$u=RUiIX*b?u1o+>fZKR%_FupEA2O1}^L;(y=Q8O~ zfKDZ5X7v690m9;xf$8#*E}n+~Ym2jLd-6UB6ke!C2!w;W9eK*0;1(f0o`6uE%dy@h z+>YIYBtFWR3FPc%uB-veyvy{<*}UBTA0E`_=*5_jvkDd}(1nT6RS-Jm6QE&7rflVu zvy>}P$rUN&GKTkn=ifMe;2-qvJ>V?=o~Q0P|A)`AM3|8!HHL1vXS1S`R+wy6Wecwa zO1-S}K%$v|GNtDL=f3+A!>`-F^64X=f0U)$WMmNIi%%{)Z1R4ONXZbNXWYoKv3crS z&%gWset-VW(+vA`?>|QWlFY$EMhWsDmmHUqQY<*MlkBLFi^n{RK{DG-u$!%yhDW+&LFW+3+UOcd08W~RswRP}LrTds zLgyqvrv^@}&4 zq0JuU5Ek; zxwOr8FVwRx1O4m)&n+!JlN1vGwr(UL4@?CRb)%J<$f=Q?luHh&NG*ip1roTPT16|U z%88ZCSeWh76FxFP5O(!3!5CJ9Df+Xm%>h{(6EgwWWRe-Vssd8Hd#8Hpv){y(d~bjcxW#A)}n=R zDFN(sL_&F;n`;POWHws)f^np}YXai|OX(mqa@|;X`;3UOl4K%N*MrL)x&6SNPv3Xp zYtIdi4y(uZl_084J*R^-lIDn+d{EWLKTXuVt@23+b+{W+Q_leyC(gY3?rUc+Uw--c zEx-6ATeYOyuIb}V>YU$4pk$dIi?I`ya$b4$-FN@^Z!Y}JPx-FGQ1z8~Um7G#`AJq^ z8&n|pQmAyW5u}P7$T|pe?NQ$5=Gc8l$M3({r36WZf8m<}Cz$r64#VL#|YzYvgh8nE%10YY3lqgD%?^QuIs+x#? zGOGm1QS-EuDN%RsRROAluz3KaIV*XU-$^<;)j6yjQTPUPN)>^)2BlaijhO(GB(|lZ zGW|?|Pbpt|@%YI~ z><6LS9Nm9Y$YN9a`s^Zi=KHkIMOZZ_*iKmwHPRds4n#o7o-#*Dg0h&ovX-AM@L7P3 zwRLRa=f8dHt81)SX6~Wa*0mx?jgb*&@Cw6;m&(rki6Bk#0LhhOX+RIg<9MO@cTWyr zDyayd++qNv>=a4}^{sZgQ=oK`vVtbhiaCq2frS8eg)EX(3cGyrCFGUZ`cu3ZS2P({ z5ONktlZ#MsE;6`95|mjoQKsT@7NhdPJc&q>%(gHQ-h@?&7@$;-vp5D6gb9%2QeUG( z>6;bioQv$inW5U;oOZHl3$9q(>&@Gs=*n{#Uo@l3CO{wEv5b3fYs3dPr)C`FixyJLg6x$JtC>!Ub}5 zZGCv1w}fn^x@N%AzY10nWu;*i)GA4_5yUk@q(v%d zB()R z2K!l2$uOV&l#ZXBKXLxtvp;3OphHjIfAsT@>Rn;pU!>!uq;@xvOg#zBL(_wM#@A0@ zVGX^s3Vl>Vf^w%4>VizuFr~{nsxzztuoh1Mda4)S(r)gXSi3mSM%IXzgK8<{O)eX1 zN&_ckx;TfbWC);Wo*0^KLfSmRTKsEC0@Hu3MVLUPgk24RBbKOT(v;7%ks(lhd;@sY z69DDwUEmw@yatmmd`S)jl^qE7pgNq;K_P|Ij2z@EEgHhaBalc4dkziqk=fzprDgg8 z-Unhul$IurkI|z;z^ouKma|5K71zj{gi6K9p#y^(02AY<;l9f?Qm}6DfXs?`z8>T$ zOGsai!6%|rm9JMRG1M)THEh=0C^#tSdwQFUwd4f(4jFe9b#s9$c+Np$q7RHMnDkZg zW)(<-aWJA5&!8rd#2Nd2bOkBKk(=t`h!xMp2pe#uON^W$3!I9`ry!Ib zMac%ANuQrTc8Uo=rs~Dn8gr9-^6@)o{_3@X15-TKjnQ~giW+l8L@k9K%2*I}*e#wN zn{}&)E5H_O4Ma^|t;BJ6H#9uNe3fPT^Y5NN|2MDg|HQrfKX%vjy|)bSozVDErraS= ze@d(f_zc#SpPsz<{2P~k^7hi{OT2BuvL!v~_Bc^j1%AHSpDq#wWfhbxAT!jN29!!D zl_;#VPij{5xAPx?)NM?NTV3Htt-A;uwOd=aNAsiCao= zRF)u8VoJ(@FlH}+C)Y8caduIvcFd4T0_R{`bb_-c55Y@}f=HB+nWaJ6q6+I>nm_l< ztCwDW`}AMGbo6tN-tx1L4^3!myk5@tq?dyf=;JQk(7~zIlb3{&BGw}5ku*!>VG>&- zFiN%=i1Ia7F~E(%kkI8|U555eFrQgD!y{q@kRk-7I5nd6qliLS(FT2zJR)EVqk^e( zAXCY<9YNyOlBMDq?WQD(flV5i#E+8-VTXz7F+QuJwCK@?|-Fe{zoVnlKI5w21cOslhr%aNF1bFCz;q7wd^f$ViMI}sB1vDk^> z7c(7T{`kCZeD@cIqOa!>i->1gAgF;AZ3USiML7kvk1}lLS4p2pNh~~)%Zj>199dsP zPB154mSbxHp;!;s+pX4@Iu*HN^xvji zF$&FzOFwx5$O4`KytK45J3D*u;K8Bb>#?XK!0C8J4T++S;7=EWVq)9+>ZBD6; z7~vd2n?$xo;rrVvp&a4;!QV{~n{1S=KUE$}s`&Iw>`!Vob>hO8W}tCOZa3u0vSkv* zAuvUhtQ3uSboL<~!J1Pp$aKO*TSF*~nYiOV`|^ncpXuFe01{ua@u|@Rzxepvb8qVf zZkA13jWBE|CdpNKiUFfE#_*z2`C<(D^xY_R(}aFje?o8A%jj8zG1|}WKyz=MnSb}( z>A(2N_^pSg?m0Sj*OAFP4mbDmS@^No#YMSmfv*>>%`L55nwxw3%t?{}T08(u>VYL#(*&AB4VhHMBvfQc3}TL| z=o*7?eE7N2Bcvv4;GF304Aj-JsmYE#ql0$|A=-3<^`CG&mF_Ncpz~5FMaaJ(+}~Gs+W~6 z3lR{sL8r=~d5+I^N#(Zej27uOfiyx^wv!;Gizb<-!gNvxrnqP!AWvj72D$a8 z;2LpkYxt(UeCq%l!t^SzP;m9ate3I13W2leq{C4(uvg)u`F>6t)KD=A%QOyQ>l zk-IPq*5m?EO$9Haibn{N9BVySR5d&c2pLyMJbv!gxczS(bqy?pA|St5RLDrJl@4M% z0c@ATDg$(0fZ_)88k59di4iKur2W*;>#3vz{06^mEa~PqS@Eq%U5L& zlgz!_2kSBU*d2R6_u$1p{qewoX>J|iV@bVq3ZWFSRv62@tEI-!n)I z8qjYIZSjKj+3&r=2Zk@beEgo@{6cebOc}`&>H_p4utIO>z$C54O6I3?StE%TLCLaU zTbhJw-jbd z6`4Z~3=|ZSF$go2&V{lzuCQU63@n;WKja89<(1sZV^%FZw`E>sRJN)t;3SE7I4us6 zQ6Wx=CQ#*=ebO{3A~@$YVWW?snjl$cdp@Sy-9tDh%VFXPz;za8=POsN>&*0_$ZpsK z0MQx!hXQqp(&uSa9l9Wq1P}zoxTG=q=Xw_NUITFr6lIvNo+UE&Uyi-PG3q3D{IJEwa_ymH2TNp&J&~ma!hPUWuQ99 z+r|nkOIW=$H-DUq0y!PDRj4e;N+caIrREmAYsIG{2L?umL`2Dc^bDb*>cC>?uL#!( zSkzwzNJKhSstB?sI;+A+lzQIaLRWEiU_AKcCzaa6)&CT4p;yjcS$O*_t2Pf~mWbA)Jo<}ZQz$K?Qi4vwfSfj0${;oh z$+)^rm@o;&oKQK#Q%tJX5Y69QDXB=31r|d1@V|gn2$1qHtdM}dDJfn;noXac(EHVY zZc2t69uSAixHwTN;8qZ*lZw)}`q&~2_Dkny-ul14JNM4nhyM95v0sp92+q)hgH6k& zde4|lRM}8wK~x=Gm9kY*OG<}=mKRVhncv3z^6HfZ-J-4~cTXp)LUChv92z<}xiPae zIHoNbN+=*e(7HlVGrYK@%hBuv3Dco(E=k;hBR$~AQjUcZDFT_rsd=%IR9-GtByv9R z#pcw-z*uwQL2VV##z~m&1Z%^ytkv@~u@$Lxw>(ABX!uG%e))b0ZogrKP}aExhP4x6 zSQD%{q$6d}H*@=Pk(J!a(NB|SoWls2ZaWQEd>xD{p$o{I#JI>8bq(9V3W$y+p7Dy* zarDSZkzQn0)~uzuC`$l#s8l_m$WlTi5w-XUU56X$S62LW1!vT_f-RU~t<6p&h++OW zNaJh?g`ub%3P3DUIo6SN4uzl~gEDsk@Q^%G-<~NQ0UUC1ZaFG~CyV}MHHd7Vpy|Pp zTi86tR)>~FDL|2?o(hs^N$LX7(o{Z7d9i}OO+BKlqQj+%Rt2CemiROJ2Ll1o0u(Wr zY+t%`nP?l^LeLE$`{dyCSfAgCQT?crs(P|h=6I%W^}afNP!vqb5ZX9e?`BGY001BW zNkl6*DsmS%NQl-kb-2z65u!6I1R=JL3F72> z!qxe)W!ub4@3gY$Ma=S!-FwsiUwRyoh^ga{Afbq5ch8da2(*UX(hG&8O}X7JP&aIJ z^qm_WO&p_huIeOl&EOT`p|KG*@ZsSAmkLsKYjABd9zlR$At|$w=J*JddO;Z(d<_Xc z=?G_y_x+Pm#RjQ3DTIU;!zPbJ6mVivMEYu>c|>ub&8_LrJTUp_?U(?*y{HnGnZ@Ht z5syvc3jicw8J@WVAgP4ownBXHPU@=rj}UT)R<2G}Awi1z7oafWlml_DBRoz(@s^+R zDrfB^()2SrObjXP0@FBN)Tp1(K+0FI8VuqOu6%?F*ISR1QJRkt9tCRqbLJ z#9Elk=pXg9EeTUN?SCjr)0NVdJ^gqsm{ch#AyEK&ZBn__clP_Q{^Wo8kC$J47gA<` z)~#m;=A!JcfEa$UA=j>IO`(dn!gX=cZIh3poYNO$dKN4w8v(JW7B{NpV@0;8Z(eq`sE8D^@SE6#-=lhu-4H6!E4f{*;GN(aNd;ETlWet|uBPalQfpOb z75U<%GPx3hA{eOPz!MLY@6Zd@WI|71v`6jc!pi2{^2Y2^ z{D8NX+5OtjsTdR23>ZZO0tWiY@(Mos+8_0jYDabC3QCl&*%-+E-l-_ZZ8udF%M9+G!J*Kj}iN%kCZR#_^7f-EzR$6bTVFTL~f zzxboYvzPJC-pm2QhifvR?T2!1X)}SgRLIE-&of}L5mfySBhV6coZMCK! zze|>O!wXd;w45?fju{NUO@zs<>ms))+ZhGJFKV@(G2lq;z}xIKUW!-JnWIxBmt=Ni z;(?pl1B00Ssdr)J+$>)Lrd3F9|L0??OqN+F1EFDRuLWDnVJZ#~wrHmg)?^%ML_7rG zs5yX}Fk6E1MM(PgAWL1hT3obB1LEK=!bibXV+0~ufkMnA0~r-Ek|eq$T+)rGpoq0Z zB2%O#;)2Q2T${Y5cs*|&&e?RXm?#!5Bj_rjo@EENE$AvzMlw`@ zu{lXm%8sb7xOxIGAt5r!RRr3lq#|v61b|Y)!fnL`8A*harHy8{qc>&(z`{80*0&AQ zaV91JZ3>_&7)Jjw@3ii9w@Bn+4vrTtTmTih+E{ft%EzVY2N^a+bF74xpZR%idz-i( z0k7huDGA-RZd_j_tO6hC;Qb_9X}g(aC0vyyL$$OTm{U1tEKX^VLq<^%5EPl)fQnGn z+Sj*m{2V`J$g`$eDcuISk9GJrKh4Xp+@jN*L~2OC)bP~ND!7jp%)mpibmtgDJt0b8 zFF5YAx??Aeq=7sjBbvr!&=I(L)-DEu3#$Z25hl*Xna12>Y?s8dJR(HikeYJ<)JPp$ zdPb=x*Jaz4uRO`ZYidL+dl)S5^4ZHXFTcYJlvDw+_g0f(I&66 z2q!(w!Eka*pFlIr)R=5@etBbtT|_nq_KpwTzJK_kn};7dI&%L_gE#MKj1BXV(e*2f zo6Bo5WGaZP@(`D*L-rBCEUP4rB+Kupv#2&z!xGvj9f>N~6UwA6#_MU+u6zHkeUTCL zsUzKVV8D@_fjJ{fq}DeExd*^~fS4zkG6*zhNChLAoiJg_V5KoQJapyN6R-a6*QEz@ zfZjDv2F<(*%*Uo|Q&plXQ?USz0Z+{Jz$u+0htSmN@@YLN?nuityM4|gO+IpK^WYSV zRlyewa|B_&|y3gLFwqw}Rp zdIA6?d9R1QR{jkJ=?ZxbVC~Edeugke6Qd5IZiVW_b8EFj${%w8;cG#Tcb-Mjffa^Q zxcARX()1fa5L*+3F-6eXTst{ctZQjCFr`b1f^Ctw(jZ1!#HBZ8V64nR#Qq6}1>+aK z+j8kgZ;an@s4BcykObFJC8}1;NGQLmD9Bq+BV#J^)7YTpS>lk(GSm=?h84^T4 zl~t4|=Aw59+)y}%q-3+_i663xIV34fS}Pw>c2;A+q2ARTIIOMsn?zn(sVopt!!C%Z zywklC8j^+&!w4J%6e688vDiBM%&YJI>9e=}!jmpj#dkk&wbqc1t)I|T@*!sOWP}M* zNGe;uiUvyN5JX@m$x)3*M(c$xs)D}Zqx&WwyZzEPU*toiJihkbYI&w*bajeqy8u?Y zq|;xSzr{}kNluzY`EiFM35u|vAP(HaMmz=`+P@^y|vgk-i%mgA*@Cm5{qlD`Z2*|jC z{p+H57pan5}4f7Va zK^`#bmh)&CIA|h?qnci~!VQ=Jpj+qGpT7OCtytRvy0P*nW(%Qbn4^u1sK>jTF#+gI z?LJ1I0@yRY!mib>za)Tr7pki?#{k;JQ-xl=Qg=+{QKOeYWX4=(Mn~ue=@Z*axq949 zX-ASs-BF52ZaMm5TlhIi-5VX5QaI6HUwrnBTfY2s+_ouRUh;Gg2kw`U(#0~?7A9?ibLko^w zU-m2Tyd=H8y51aVvUo=IOP!NZIb)Xb?>@g%IuWv1%y41O!vaRle8xv6I5-9r!h)mU?r|jf5WgiPVpO73at(Zv!tvCzM{x;jYAB+#~`AQfm&eYn`3jG(1Tyfr#k~E~N$&Nf7B`l`^HX zPfB%&wjdBhkvGz=b>u#5EUgSew9uX++%Zx2aMgfJS1b5h+2MU1Bv}HHwvwZ2HS&=g zw}!#-;q(=-V!Kv?ZpL_pwRA_NYbqEdDM;YJcWSh&0^fiM0K0$I=Qkm)Zr3p?YONkL zWc1I>8K7w1M$@WoBenqrS?F5{x)MVRlo=l4H2@yIt4O>qwZS4l9aV=5!TSf?()jK# zZ8gmoc_oXHQIoAdt2S`GeA`<}YreKrX;*bBwH6;$7mYH((1kGqNKq2|8;i%!&AfVI z&m*^MgvSlB9tIcA_j-=}m!Et0%nTzu?G>=jz=weRNjCTWf*p^+znl*YgD`q`Zo^`F zCx`}^H2lN<>2WN5s4pO7@zsFb5QcrtHJeMNxRU}KQnUea*PkSh^(|-6WqLTYgj|Vn zR_W5L!K3rRgOf-8$>({Iy{FgmEUdL&IDYoZ3&*)vnnwQ!YBT7Fg}oYed2z`~$h^x- zE9_3E*+Nz`v(q`@qBkKwPYo7U2M$b5ed&?$r|)Urvd>(d1JZiGWTR8NgZn4=jXrYo z_^0lf`}WK8-+O}&YDdH}}5~vOroQB}Ssj?MN!b#C~RC$q213(xmt%fwm z#LUhF!cU*#%V;bFiae<}Za}WC(h;y^$oFntpx!9ibu1oERQAiG@cJ}GG6Ac3eV+NR)@z%8`}Fdh`B#xL85Ido4Aykbs9Jq zA)Z2{f0yTSf+`Y7H-XMMNX87OGjZi_3ESZcLF>4pk4G6ES9CB@Nv%jMT!lJZVQ(xH z5+oxkqJIjm%v#E0)2fLPKG_7qrY#9Q09fa8@#00gwy0%>aO&nH81iouf|ArA+lFEn zP#^Y2AhL2ds!cFdl4yOWpq;GHmTO^Q5hH@^rcv5th^UqT?HSiQb5!5f3!sAJ?MOO>a?nIaD#N&W3!$QDiKE&g(*!mkJDG4#Q19b_)Xok}XX&bVYiFZx zZG*m-drUED;o9nt+2&6p%{>9IwMqUWY&5|C%^mKErYlL77cQ9 zRNImfRC)1BE!9!UCIV8n`Yt_pjJBG#B zueVC4EK^7p9z`u+2OM7Mx(_E`;`yOphEZQHf%B258;G>y(#z+BC;#Ci2mhNdjXrj( zcCI$+$wPA`srk z0)#KwXQJ>-CXonzeT%1bdqsniFeZ`ihGb0)Uw%=Uonp9Miav>&I#_g}(s|gZ9MY+G z!OBcOb??Mux3AAFG1?2CL0-$AbPV~j;)tIG&Zr#E=R%MXo#)^${Pq^GAQ;S&w3lOp z2QnQuH=2j{9QgTqc(KkR6 z>})FA)45xX&wX+}Rv0#+nehaeyAoTg>+CH?N1?ugePF#)2yYo_(^5>%5{ZS?jmrxg zGmDV%#*3PsW&+uPbGeo>YD!H$gtib#l2o^?B3)ItoLI|30UB56;kQ#8y47-xaYZeA z=D7Q3DP3u=@(`mSGbBWrrfCZx?Ez{dDn}2)!otFpE1Ccl846s0l2icq$|bhzz&7{k z-&dK8{@d37puo1b|Dj|85Sl~NW&)ywz=sJ8_hH1#m-T`dsqag$-5_r6pJ)te(>kt~ zNE33FZbMgUVJSI=1^IU72NQ+o+AHkQ#pWkzlEzk&+M=s#A?c!PMl}CY0nrRO2H=xO zJWr8)!mC%+kc68z=bw4)=+A#5+H%*#-@YJAhFtr8;bUwncmCh})yC4w;OH>h^H)kT z!itD>l3RM%)Yv0sgMtapV_oavIQ$mmfal+*$yZWJ#WvK&&GsDySw zC7D=c=ZMz4|1;>W1~Rsh9iJFqT3lj7FtY1~6h17mygE2B#vFjBSXeHq9*wmS$cuke zM6!;G;@uUOC{o-EIaX&D*o;+gI}|5%mTnyo1eKGRl~;qxk&wi(ATxEOBD88k##}sc zp6O7GxdYQJ6>8O^ znvo4%+LHyM(s2qWSer-nu*ig6 zG9F)L8!n;2gdwkw@r|X5YKoiJR2!ZW{J|6DNa!q46^zu|C3O7GwYzWNB$B-UE(Yg)3Xy~*Al59ee8O{KMO&7ljW2C{CL_v*uU@Bp4|JZpWt!4FRQ-)p2^SBiM-0n0=z2GX9)af~Egg_r|4HIgth ztAOC3mvJR~lEWn3IC%Y?<+4RWf@H3!0Hm;1YDxm4?IqdNC{#^qDodv>vs2{Qtp}uY z4_`O34%1KEz31}}U;5MM2M+Ghf)M0If!w+k6-Kom7MJ$n{#G{9Z^%*38e8KpS0NM*c zwOVS)As|~Lk%bX5UKT6VKS_@0 zFPNULgWLaPjNGvD)}OuH3;3E^AZpe&)rW+q#zKdjUo`yUF zRCb|o*f^Gw8}r$e*QiOmu}7b&~-?amU?FH~Qz^ICb&4 zH}`+y9(@g0Pepqja4c@A4i6pp>QjsqFaC#T))toY+o&F#<%a;$*J@_7OD+C%Csn3Z z+hzQMYs%On(m5=lLX_D-w3Jy8%1H1LIQ?;QL@5N%Iz*#L&^i}Qjj_ zfOL?7k_5J?Op^n`ra3_FZmbZBtL`gsqw<&U5 zID*Vkg&biIQdW&_Y>nS}Wcrb#lOMlp{N9^}_D^<>_^YgjxcS9TuMr-Y9KHYO#Ha6F zKR>&C?A+q>#}{8X0gSatm4Z^hV!68r0c+f%C)M7T07uIKgL8#@5cw_W5pvozJbcnwykHA!gyDS7%#jN@D<^i82BZ z2m3WcL2v!Zmpc+Ur#gLQJ4|E>4sLfQ%DzXSi~s;207*naR2(9e zO3~#^MEcJHh&%{XZ~cw+#nm(4e4YtFe8^qRxaYwx%8mbnUwMk%<<9@R?=gTFnjB?z zC*j=1NVm{QL+GMl#)gW%Q!P(`Aa&UlgKPz7#ui*ng>nZ0khv>^ZQ{{ziiB+y$%G~m zZL)3>KnRgY0{Wgl7B#Vr{ege{)Pb)&1rtQS=JhF9?D|nA(yJh6|MJDv%L}6irX{Zo z=qDUj6q9J$gC@FnpOobirQ6fdajil54<`7YQH!)`) z)2$SuBLOuuKGNUxTB)7n&RKTj%<3b(Lx~&~)gs6wvO~K(#I2v?5FBw+>A+spv#*>u z{Kb!bM7*6fAvgW@XW#u_zs90)`WZSFq;>=F!8)LYBGQN?tL-Sts|G{Ql&VvvG^!&C zwiu|QmjAk(I1q&k7#;ObpsXm&(AJ?}d1C7EJ5?O%Jq+14bFZITpI;u?HzoVgVT2P) zxS)`fNujVw&X{C(niOyASe)lz|3GU zgUK^JId_ix`F`K3y64{8GZ+BVeVbBO->&o4TW^IoRGq3ib?T^zxvkK6*ugL{-MfAL zn)kn9_}#DXzk7?Ho26_mO&ZxiPm=sNLz)r@3-F z?1+G(4zf*dr~|#6A3r^EDa_CHa|iBNDC25B+J#a_Yf1U!OBETgb5k?`?-lXrBaJQj zLI)?1_Vty~f82;%#WaN>|lt!1fK%3B=EnI<=1Z)~C06QCH zmYizhWOF>q*tUMl|M>n5AA6%xtenw5H8nqDN6(&pY)|iSKh?7gRPXqg^#nudlDMNw z=^q>%A03S_;}zDiYCJmELBqoFw!`qd?%Dj0K1dO_a`YeNQ7xp~SKs*8-^K~k7e4!= z>5~_A9)Q-X>9QdTbu_L86?`N;nJOt#Iom3nULl-RuRM9r0lrd?*QLV;ISz@=RwI5b za`6ygaBg&z%qbqqndzxM_8ikgA{rr7ApiV?Q2h~XObqlPa}v7NGMPR;aPEh4!YsRE zJ(U+I`2-PlhC8z_9YzHu5myOc>=d-4C3>OE4rv#PVQYHoD z$>I7BKeY8%Vivt377o}hcx~Sa>Zn;Fr;V6}5R~l22zc-mQW7yPu@sh{xXx_GRYJ;~ z8*ujtMp@>FA|G}w>bQvRwI6wK{iohM@P_SvyVp4}SSJakRtv6{mFk#kt{7$SBG@dw zc3}AJy9V#yG4$3uuRQYn#B;|u7eQYkBwMPbJk=pkg#J<>>qhym5mna&9P;dZf z`U=?f)X`kEr3)d|vuDp?fzgdq5SXyo_CiAi#8!3DPPP=7vLy6sGN-z@Dp$bOV97L= z|0B340r?bSfI@>F&z>?F6pGwRo&W_V88G=4SB+{%2616C5jcGy>7@u!Rf59AMZR4i zO~9ELBj0?A99ZG7z)EQy15`zinRu~#!^a=q@h?9)^yWJ`#$j%1dVyt_=y@eD-Hs)- z_*Lo~j0S?Stn$-jM{C&rX-E^Wj%vaA2%r`k2P4{kDo5lLsBBfR?@wIiq%?v28{=>{ zdraA&GW5_LxBsh8ZCrt)|7hR~3#T4^maqTv;x)Zp<{((fAv0{w5~EzWRpB(SBcMlQ zbG#u#0Nz-)(dwPbpZvtS}lu$x1pJQA#H?33ELu!(UWs&@rinz`N z7aR}_6~GJU4UF=(s8NNiGXRxCJH0g?FmC+hTQ~+y52?I{rDs*Had@t5y7^&Z8ut-O z2-lsgni&s{7QCW5#$w$2i%`oxhN~o5$6tHb{ z>fDv9FP_jN6c4qdPOzlvXP27}a?;!}#3h;{#s;lpley*enM_Iii@Yr##j&bV+q`D; zuYGvSr$0LQ(9ZN6TkG_+B{i&^(al{UCrPAo(R_1o?Fa7P`Y%4A?Th_&oli~2LT1GB zYt3G1j8;Ys9;4=MRVb-BG|WeTsw;z5M}H1BzL*P{u$4@J!?hp_K5{O>Ox|>jwD_eO z1t^&VRFPF!G(`c=(FT~=FAx~Wc(J{%1W=}qTeR+ z1~k`6`dZPibpp^*J`5CwOB9Ns`wQNx_F4!4h;5IGBv-CnVZjfYc7kdS%K+=i-R5;{ zMJPru`*y%ULboV7VRQY$3R;jkQatKdv4&N5u>qPvKowJn3qYRnt725860QJ?A}jGt zV5*2K9W{4!0_nzrI#?zU9UF=>M)9I>_|mfnFZ%C@=|1QBtbP04%mFt3jkhzS zWg0-e`3P+iJ3N#si^IYb4+|7ZV=oW9()pfZ8N5#X)VNGh0PzDB#z@0}5Liq^MQ#K` zoRA5Kf6L7nK~ygW!Lwr;4R845n|J){Pp)~(T~xO7i=eJ*!xN8Pqesu4{_|(+<#W77 z+7qE?J7zV>xRSc3n>{}WlqGGj@U53*IA$BDw}-cW`)=PTmUU3z(l~1AqKDtMYx}?c z6l;OAlhZy%)}P{{8j4OgtGG)!e51;Qf{S7a(znDbdXAD5ct&5Var`+THL#K3>pXHc z1$m5SJX@rAzAsbrtiY5=(y1h01Nh28)@M<7^Z0ASLwBl*#k_A+-&+@#i<`YR&Z@Kc zu;i!*b(ISV28>U=!*fD0goD!theEOW?b1&VJ8lJd)QBa?H3K{T=MQiE_`|bT##uQ+ z_E<{hp7_LQMy42D6z5>z%zv~E@g{+Z{RYNJ1#OCsOPL!Lxj5TO7%^)?!u#{%lS6NO z-Ohjh^BiI1u4BpCk!t@6+_e`^@`*Qn0xYCq-AnDkp>vXTc$_S(B@*V7LzUu?kDUcB znTXQD6eg5oQAG~;^Al73d_(uCs^ydeZ!`||K2U1 z{#bqcdcI07Q?aI8A|z-^pq*j>5;uUEuM}UoL=uu~#UyI(*#7>3I^TE>sWx)1q-+Kw zpDL~eQ>{W3CJDhpT!3m;RX7M*$gCn|68a5#DsM7**4KjGw^6_f3Zf4A3|TK;)CtQs zc48r`KppwAyVumtOVx9=rHMt^Xrq)t=TzysXhP!n78tKnv=A?QFzJRZKr8?-p`rG& zxx|D+vHVbFY%ztlvR&Z>a!&qcrd~wB*?)`xA_=jJ<)`#fd?lw`uPW+Q!!$m1l2j^{ zAD0L>8l<8fV>;726=M!CJw3BmCr*C#FQ~=Jc?b|9(fEUL<-)?iotwA);}38D^hf*e z**bH1G=_JQZMId|^DkIWrY=r}7n>-7s)4QeQsTw3ps3?(R)=1Y`d z1B)_z+tw6?N8~(R>EE?^+owOe?bklszjKp}az*X@frTuPWhk-z+8drGJxMTT4G8oqt=#<$#~g267UGk<*O0O?_`?WL=da-yrcj149t zMmYXqXnx_!^G6iQ2k-|$$9!tF(8XJcJO1fMH-6&bnQIfYR<>}#vHBZDS`OiBgqPci zqjFqORhdW_i4G$1F52uQkLa?%rk{tlAeF)eQ32Hf%bA;>8=qS9w!3%!%TEpP$vklF z>d09y-_?@fd@4Q{$g@pegE60t@@>DNcJUkLQlya0r$H|g6NW^Bpeors^(01?`nA9--=uYbJ%f!j3)0I#A7TPjV2E;Z_z zI0dDOIbt!7CxpB>B+W{&I#Za#e`F3z<681<3AR_Wg`lNU(2Kye4X#`09jnFmUY`NF zWNfw6>qfz=SFc{WbXjdp+lgD9tk<;!z?JBw(?}_z1$a#pfR?<}GAhiJSh#9utWZ{? zOxRE^G)Rfllwty~s3=P*OABNj^=;k2n*ch^CS1lR76`QwTeNBZ2r-BUJ5rRXA<(`k%(Tn(iZ{5IS&E&<-J2TA3WtO8T#jdJXtB; z)(<>Xm(6r$w>JYjJ_Q}i0YyFy77c~G0fT^G`GE|kBn`8JsK$<+ySo3Rl3LILI_s#e z<^bz<{4XEd@~iK`DrT=}ahIxdJM)k~Zh+=b9gKnwi1p}kga8#ktIh<01y8JI0Q@|r zk4Oc>IY;N_+=b`gR>ig-cCu~w#KSxPl}G=PK(w89SZ7z)5ipFhI>A1X{Z*{}dy z@CPVB0?p2ER^)I{nL?n0cm*w3Qnrlo?Fb-AM*qWazkA!Se}borLKC}m{i@hQC91&C zTkqQX>mTcXs`>zlwiUo@YK8Vtb25n4_K8U zYamN$s%^7tA!v?V!qBaq0H~s&rdL;Gq)-G=IxD^fcIJ#;18A4pt1IQTr0US$@Iq_V4;Yswz04Q!~oN>PQYmC>$-+1Zc)sdz!BdPG;1GT>_d?XfqBX|GHx22R`r zQ#*9NiN@UP=99vqe0+uZ*UwP`I1XrZ;>4GqP*mmN<4x9csmUaEdSK7j2VcK^+dug* z+i8a1buYJRPXl;-&+Z9->n*8VoD~M6W6C30nK9YXx-@>X5KL)&dK4gZFy)JjkNimK zR1kP<7i5&`VUEwy*CwT&?zJDhfBV1sXqehaWT#~_N5<9 zoxRM6Qx7O)7xA+c2#9I3ZTi+CjMTUA1_z?e;WJ^b-F+Loav@&UIS&e9p|rQY?t>5P z__v>0|BG*3sCCOoXd%2nu8vBeSm2WTdcqkLQZfEn=c1gPn1CY`sDNI3kYgaCd~8Uv zgS;0Aj`=J=?BWg4k!8H(CnJqtP#8-vhDVeFta##1Am>jvt5t%Wa3j-*sMdK1}*m)#yLf8b4p`>gq zkQ^U4{1AoGSK`5H4n-X2Qw=_R$Cm%~qjipzTsFzTkm}Vn@cQjr|Jg_S?%Bc|fPOyg z!&+5s6|@w@X8Fq;#l#k6PAj-gKe<(q@*sE$X!@Z1Y0WQov77XvdzBJSZKz?mQHR{9n85I~j3X^5{@ z%WC?%_r77rzx?^_zy9$xKX(r@Va%;fL2$>AMUs%5(#o5)9`>M2?8KwU+(Y}8WLT06 zWNCXAqv#@zd<$sP((;#INa&g!pJFp{Pk(*Qd+*!&&p*EX*FUlDJ@=(IJarJV$oq$5Jlj#PC^xA0&&D8$W+j{xZN|{dc%X*z@X%m%0H)6Fq~s}KTmsjNb3j| z@&_uf=w;F{5M`pASjijUwIt5*V3m+imyQIToG$&O1s}AoNTi~OInK-LzvJ(Iuy@^% zm;Z|;mU5ODaLqWeXX@NlUPcU-DN<-hx!r{DWFpntAfATk93cwR$e&-h^vpq>)8O~} zs|h;^Q!z%}1HGGm;cYwrw_n`w^KWKlm$4luOEMN`PUlG@b`YG&4}%f2=DraW`$5p4 z2IHStR1#4L(c)yZtSHaT&s?407Pj`?_ul?*KDFiVyqm|KN<%Ea72_C!UwC34O&bfL zTB*~pff!CzEj1R>0WKDYBoW*Q?GQjh852?s8erkDAXb5cl1b_CznAqvB z^B@SWg8c`N!C5+`nNBcA@7Q{K-yqDOZ6;2t|a` zN&=3E`yUO2jX*Oq>>Xd|+q8E5+wY0^4)`)^iq*EU^&fp`;NGoQzx%?qAH2vRp?tKc zKGdf;ZA4KE5nt4}QA;DtK1jx7EdMj7aq}0MUj6T_(;-McCX1xGOhU@8ha*7;2S;aS z2vG=Qm#L}AI+n?ylQv*ANI8o0HW9j)>1hmR=dZkP^ZS?O#R(U`s5gD?D#zn+RE|WH z^sb9p4!&&~QGYQZV-E*Eq7+kLk&=U8db&Ah_WV-^w!HfR^Pcia$vSwE=41(cWVe6k z#x-xf>++xPnb>=R8Do90kC$B0AKbaGkZvUfdsI0yExf5T`jL~^;y+8qsZx}pZF8hi zFT^94nYoFXuHK%(H||{jp@-JK>s}7E6ERk*G4_gauFKf*bC;eu!~;WZg2A{!ijfRE zM2$u3hUi2hENlR0K}t%Fu&0oJ07<$KruNB~>3Y@=ZvICf=-shFih#6{WyyOf0g7|6 zyxp|JEY_l8@O?9OB73GMn)P#*6WoLwkoYP1_{@r zT`K%*ng9d?)vCcvH5f2tg)+D;K?nlVo+_u6fyWa7QZ1VQ_0ryIJgjxI+RC7i-K(mM z#<*^P>LB!Q2T2~d`nw-`rGmO;oV;8{Qf_ppjG}sacs}0uvGr@- zanHm{rziHD7~g$j`q+6^;u&o)eBp%n*v3GvvQN0Qq#}dh5g|w-mRtC$LJ^`wgKQ+S zf@c6XKR&|=_hiQD69W(3KJ@UN!*9Nm_kHW@)Xgci_Wby%h10D>Pn>t1{`ODL|M6andFk~v;xMG7bg=3V~8R&`})Vp*q)cg22a4+8m9(diR{w?10*{R`+ zDi=LKcmJK6H~;@}a&j<{z8KN)~=}M}QI_Knc_x9ZWx8Jq%SKqIibY%Cx zlqf#f-N7@HKJMA7V8-UFD8e<5gKafXD~&QzHyk|5=aJ*Y)v@E>dWs2vT4j4~k!MGI z^$04swsvsC$KEvb;Lg#fkBmKYc;bbTnaktUmNf+)?x`uz4?t=C$rI0U?}P88(BObc zW0+MEh*Fu}Js4^E<8C!SHO(Zmd$@n_&3CYTF#OhC{jc+8337p`Q_UxMXz^Wta2J03 z;?(&oeE*9Ys!PCO#hEw*zG_&UBH#v4q`LC2?39#i2o(uBlq6e5%9X0an!Fcap^%FUctxKY|vaULtqOzDweaL>EcEdOUo zyaL8@mWyzIy(pKm2>q)>m15AH8Q1)WzpOc~eH+(s=oa}GV|bNiz2;<8h9>~BI;l}k zCYXZ(ltLm{VhetGfLlBPpwcnjY^gmaSTG@u608D9A!4gOGUSsAL3V?*)qIu?rjPF5 zwt;G>CT?gLIxRKW%a(^><{s!Ks=|JTiHQjai;;8tXAsanO}~lEmXHLN%vkAOVA5SL zvT)^X?~XMoo*ce9Wl01Asd#SwaRR&@e*~nu9*g~z_9im z_b>#XK7D2C(3!~tXC@DwWx03$%0wLSm*xX}R>^N}Dg&uBGV%q_5{wmaVwJZ9CNZ%N zH#arAFvDd|a_z0vHVzN$+}O8k%iw+62kzb0yKQ}Zvq{;+(BJ!~mG#PWHjD9-=Z}8= zFQLEh)K3IZ2!CO+xm;|pwV?^^ed1(&&VqPIXjW`BSvjECit>UdNetrrl8O7FrjulpX zN2bTFnmjSy)ze$w^vmxY-@}sAh1jr1y~LSi0{>j;Pt8hBsVH&CxFU}JOk(QNnZk4| zP8%Y~NnY;Slz%hv4WgO}UvY>klDTa?r4&oXLBLa>?-Z&X~ocyZIFO;u!j zk|o9@EiwUM2MD)kj(C?1YWYMw)A7QUZBc&ik6yk~eD;guJ9Hxy?LxiY%Laf}A-;Sz z%ST}@L(QdkrgY2459JFM6moz*R@76lr}kEIG5`P|07*naRD0^omBWAh-G~2=zt_=$ z$W2L1<_aN}=ldl+GKb~yTl4PM&t4p3GuZU;3*5N@W-nZuzcx8PKFt$bb>HS($t4&G z0WY$WsbC@M%H&iJkNA6r`s*9}`RGjF&Q1My+{X8F8}z!eUF?=z)`J{^}~#RzT`MT!eu={wD$nsPWiDM4~B!V~qw< z3KqMkwbVUz@#@J(pSk~^eyWmVxQtr9VB2lEMDt+p5oh0Pv z2JOB^lv=E>E_0#=Z_s5?hv+%q5&)>-$?{JS+ z<>ESJbPk=&)lYd+-=odfeYM;BScIN`$KCT|Q*&1*xM$CtyE=PeboTQ29A6aXO*HuAmacKyoxnReBa3>Wgel`1!4Q&+9exzdSz63lit9$zZOG z&t03CADx;XXFf2iX23v|p(?^6VeUz~2YS_$@Fmm@Lv$l`<`rAk)o)u%PeSGmjx8(q z$_?B~gLq|#&jr$FvRuvfIf*Kqy9;YxVelA(3_v1GchDdKDO*ZZzJ@1U{%=N0rt|Y_ z-+9mQTkj&qD-{)rPJ!r)jX>|Y@9JYOjy`#)kI%IE{1MeqOsc)u6ksY%kS&sQrii42 zqp#ph;ei1ODGZ>A9i?qT+~U9u^1ne;r4mpzoI8XZ3~t1fjsUt%=qBP!x4c)rUCYV! zWiL#Kol06|%UhL~E3MKsyc}tJJp8n?wCDstUqtOrQ-vakYFgl`X%-sph;wrbOaS7$ z%4DviuRH3viPF{1UmjoJHC8u4H90p_)l2pVLU*p*tWqnCNH07--&*~ntt5?BH}^7+R$JiKdY$89>bqvMOXi!oHiC36kUdVyAw;dwoly=FkW z5809O;7%0Bb_>o6n&-9Mnc0QuIc@1-D?dos#MSkDU*8sB0>D7fzn;Nf!AV~#mb}#> zSnH4+DMLxC^;yxdY_fZNT$wN1R4bDb0Zx`59X~!O z{@EtMr@<7t(xj4_yCi^4*}lbZ-3RWUI&pFG!0D;|r>2gc=h@K$9Z1*0_{0Qzb9f=1 zoyX9#`G?OWYbCO(Yl}dfrcOm&2wZz)6wjBmL#=;kaO~=}`6))h9Pv0i*EhA{?(G}i zemBR8uYJoded8&dM*|7&+=Yii)90`9{wqA0*SDi zhv#O!?|`-+1AhV0D^$6bve_XCahdvRRKd}o$qb5gAZ(VFVnQW${(X&=(S*l2(rJTTrffl%kX?AwzpHO;uCV4~`3xtiTFLFDA%!CeMw02N%$Kvfhui431c$7alJj~M)<~d{~ z>L3oF_>rC{vhZJS*Dfq)-?-{_sr7-VgX)#)P^FJLL6(LWQJSzOrYIHYtU^r$-69nO zpjg&WAi?O3NodiD5T9CbqfWuzZr%bs_=k@^{J;M_?p`Z&p)Lur?IHzBdN51P8`0K3 z!#<7Xi%qbYhe7jb z3m=1L`y6cX&jd=oSdE}K2lhoaCCSL7bc&SiO@h&rBxV#ojo0)m&r2Lwu}u1I)1vI~ zoA2NQpEGB#@)G&fv5VtJ&rBRR*~7ykW&xAab=rTupYJ+L^OBRKV@cqr8;R`A@(}v~ z>T9)jspi8cL>MFX*9LcN9Ne{a_<^14AG%}c-tB3l6vWIcQih!qSF(+Tt9y^bGVg_V zv7#yEQzicCA{uXtMT9NE6*d&zQ|hHKG|AORT_WWae@BUlw<8uuX z&I)`8P*p-$3mB=_&KSKw!peeQZA|!^zmOG~gRMV&#-z@TQk_L z$DXRXJ3(L7wHUCLgT1J!xOH4GExr+F#2Es%MC_{_L81$PINH$ggF6TBza3gNla=Gl z4=)!D-oJxgg+1$r*{kFpAsB@N$tQS~2z2x(pA_fBYIEUz=W+BWTv|fcq>Gcfz6W!4+<0)suYD?-}>QRAB&f`E_BNXP>~QXtJSPi zdnBAN;E656*P?|;Qi97^*;LePodCcm&zYnR8=DC@{TfxI&Z*QHWZVCn<2&&8j zmsA1gJi13l^nFJp7$U3YvwX0OSO)>swyYihi?J@&TIM)+LIW4>TNwaWIQ;0LY`a*j z+i9sTM}JOE`R3#SGo;-;EZuP<^Rk|-DbY%h7A6?iAvXopN13a6I8h~miVos}OciiV zltrUH$epZGIW9KJcByJ>Qb);0C8jEA@GG4O!BwMs1rw~Ns_t6_XavHD1y5x9@udFy z&kx^s`#pc>gA|}V3d^aim3XnpQ^hJ`F~*$3Jpq`J2N7qJ#jn^o`n4Y){^Ac=q}MSNn#gFB&r$(He+K<}6vBK!ut)!vsmHuZ+Ym?e2NFla zt`H3~WV+Y!Qx`?Nl3#~N8V{Ajad)im4nA-@105yZJIx}CW?&MGf2xdO{YyU$t&0T@5xN~e%IAVg|$>f#OzJffm6g%$~^E9>H0 zr~Q-1&yRfT*`2@m&Y*WmE8Yu(iE3JFsm5VJ1SBQ1%~eQ8L~KQXrpaVb{Y*IXe({-? z&OfoQmziNa2a`tSf`fcbnkUF##3dScyGcq)le<~nUWyQt)FO;e6sKcxK9Ulwgx&wa zZN+D27u6fveViTv3#Z!**0?0bEd^oy?k_zgeXD5FnZo*7NS0pkt)xMa{Zd}R2L^gD z;R2{)a*WC<38M(2a>CSl3r*mIzM5Ofp=2AZMpkL9O{y#!$YWOux0H~bjG)*FB#K*mv2|!ELnJVCjf@kD{j|-XzR$-#5 zNTPTL06*|S*(smLthL+Lj9nY;8t&640Hi=$zjefh<^cTM83mAZs}3r;V&x4W@tBQ- z(f`=!sL4yJoI2Ti;B$f$S&$`F6wyLfvZ~S-%97S(7;e%|WKxY4#{cST0o)kn#Xp!l z4e%%*bj$%b?<8Q&1oC`fZf@Ua9$k0e&dqPWhaEEIl$%o5l@CbmRi{wF$c(Fzf>Wdi zbFFbHTrr#LP^?t6#_C<&7xy0B{hz)%IXd3Yd6Y~5@<1P1X)hpuG#RxT-K6-FvtWD` zrtlYbMKBqdr;bRKo}_l*WZbQ>CuO-}U9pg23<#mG-nVT%zcmltA@0~AHa^9t5*8+B z<|k%=<+J^R{T|bt3bnu{5J{=8riRH|p8dUS`QT|}&+q_`zxC4RP(K^VanHMlEW}C1Jj5YWW;ia0uFPc=YhjI z$0!f>GsWVxv8^9^V{9hD);i7nD=fd54e63FnsX4+5sSt))2{T8blE*SF~wJ6c>8&9 z(^^gpLjZ7Am}{__p=vCe5Bt$o+~$rq4gH;$tj88aw7O>QPzp0>>D#ek_~AR1t}1qg zJMGL2mEdGjzWp*kHAB~7F5DUrj~Ip-xECoWf3mY6^@kj4TCcJDzab5%ad@H)5k&!6 zT#61?#T2*&Xh~aQa~`@DB*eh{0w5}q7n%|jrX-6ruxyO97MPUG*uJgnoJLXr=%8cj zb@b>lDWjY1>w04Y2OL0)A8KnsE@!ZHnEp!3_NcxmhV>;ai`X1xQGx+5Rp7|#A`y@X zMQ!l<(y8Wf`t%vPcyj2d$dVQZc5Gzx<=o6HcQ36|V8`;}p9UG)c33o!q|y(1EEk2@31i)awQ`}_ubmzMi9IQsNAiHQ8Us|Ui4vGk;1kjVzmuf zkErw;8@Vuk?A-9(+Z+>yy0QgWTD15J9#gZWKdG%B;&(j;)M!L2Dc#sf3(!iU`Ougf zCs?*v6iAg87A`$~knfxJZP~yAN`5nhN~CJ%%383Z7b*aA5v zw$`I#lzG#DchK+pJ0Dm!7FP>;l@dU~U0oM{a^S*G4rpVT`UP1r4!z1ryKyn8W5UdX zNWLAm0db_wxD*kMF6_rcf9yrJ#IOI}tsL|W-AY_aFrR(fHVoXeg*O41@UxcY^?fm_ ze-Vgd9;)+6!QfiB?yflc)0>J@kE2#1VarpR3<#Go0F|I4GR7q-oe5mB<5gS*aL!b&@{@;PE3J|pj1S>{;Vn!L#9zhMCc7*pAduD@G@0L-Q?ut$cXl<+iKIy zjG!xP3wHHn%BFFsW8Q0B6oC!q04>#Ll#dGzt5FQ!Ax01p6%wfu09R8596l$3hFDwZ zmo=D8Yv^^G*_x@-UBVWv%BV>u$u@2*$cz^l|M)iF@G)hDuzSVoF94+nQ*nhxM5J+S zq7PyvcP0iRzeG^7ssKT(+)Nr7lD?2s3QtI1f?AP+{Ex4C^WX5JCPrzs&reHSmezn3 ztko_&f9R*b@kKJ?34pdbth%2?3AkM|h1>nRUpxKy-ahuKdUO{`52MJNe2R6*R1yIc zHXK`1@SiQPSeoww*t}eK7vGV+`1DJH)15-#&xVT@V=<`0YW>Z>HZ~+CKQ3vFX-lX^ zCdw?m)&!jkyGm2%uU>d^e~l*?RK#^m3U$32l{U5-1##*m-Hn9K@j35E3kQA#MDpHx zZ-0M3Hy0iVFh&@8Q?;KNGn?;Q+JM}NY-61qg?Kw>%Kc~xfMb@`A3P#txg0ea!W1!i10+gGZM>@qO~x-ity zx-#N=jpf5q`)(iDW3|3LXC4C3H5Yt{qXYveGAj7Ma1hY(nAxOx-~v ziDGgNLDdvTR25r+b0WnXEiWNU92ITAiq1CJo%JW0zK_GfVI~`1%H$g-)WX>)&Jfn7j61c>$i zf&^f;!0sTX2kj!Lk`Y@Z60+i0rFadXO4b1wHFbBqn zMVTw-SHaezbzem;y#%bRGe6pU@IQZhXlRHdDY=V#&6?Sr7q2xKE3g3CMQSN(A%ffW zEn|}{#nWDeWKO(2ZWEiEpPM>%{_@iYH@y8`y(zomPm+DbYDf}k7tAZpX@w-<fWh(cT88`8+QL6){?uxNRU|MHVX&(mLizCf7J?fD;|T!8-9VH#NPdf=u(P5I9;>SY=pqcYLdohzP^Z;Ls48qx5#a*8YW7 zJCPraBM77Vo(I!J9MeXhXw)W}Gs%rk88>B@3QDp`H(Vh_l?o}50-{QZAYve8SmvCR zeRTprKE%Wj-!J|u%t1hMnkr~8M_Sx`|H90~%u9dt=)i63Ha&c|Zq=RM%$N9Hz1&y= zvK94xm-Zgr_d8$jnwVL$ah+e_^SIxQBFzDOUQcBjpvHm?XTNH1n3F8cd6##}Wn3}z zk7)ot^TsoeJi`P)13OHub2oq+3u!erw0mXG(R{>vdi_^Dje9!uEn^6}W_hJfryxn< z-gTB29{q~B?8Hk^gM+;97)HetC*PB6-80w55C6&cHav7E2e`Rod9BaAOGQ?J*D3*P ze{O2#$Y;OLsnGGgaIFsDtVgX(vMiZW|jq2D&@=eL+5plZN7{UkQ4_Du` zwwBJ2Y00LM;YxtmcwFBwJbmgC_eO4?@NW)OXT}wL`d4Bzj*Yb({Yfyf8$7r1VCsTR zp@Li_5h$YhWmkwZqHzt{$fV&{(zcQ++>~F|Rnnu!N0F12Cr_?#^E#Tg5-*y0qnPD@ zPMkQ2*jv`VsiY@nGTrt{G}P1{x5SuR(-m^WXFCrp^Ya{_onZvQYw0nLZ{ZFx6-~j^UdgLgAs;w}|Pei4*ijY$h<>&uE<#Z7;q2~AQ zrj={?FZXhHIgxYk zZ+>~~$l3n2Lwc-Zd(e|H_sOAUX*_p-V8T*_i+?d0mJoof4k(P&F(n)}>0o!)m7gBI z_~REvxoR*-VP!gZ6Z6wEC%^s^UjOakej)8uR3H6;D_{DA2+1be97xQJuN@KN&HosP zmX@?1)x&Pzgam(1vI~LF!8ucmkk_MupPwO7dJ?&i>+aB5Zwh zNVjx3n!>!t6^uq`SBldI#c2zQEYAE@!V-~bEh|BeRHjaoWLAV%Auf4V2_sb@U#z(1 zbSae5l?w@hb8f!Mi~m4J9Rubj)4yY*2y|>osq~^nEjf~P?W0GJ$_$%p(h`h%G-+qa ztG0Lnw`Kyspqx=`O9?FPGAUh@Nr>4eKvUO3Fq#XSt%wXvRb&D45->B0Lf=x{G6i!b z*>hIoq`7zxSPh9PS6f>N`%ahaS>LLhnP&S_hnEr=Ttg z=AF@c{o27(dw=UIlNYY8&H)-CUa1hKi|!`RUD@*+U%ImQxQ_2YiPoo-YlZ8{nD6SL zZo#qw#q#I%JqOLBpsEXd!k6XA6%%)iZg{ifQzyUp1Ycj)n*ghGfU5LX4s5+%dj81y zAMNYov(WB+jKh;16qmrjHN|8S6z@c=ce3djb5aDaan|b$dH|v`k$vHuyNpb413z`) z*@L28HIgjLPG8lC+5B_pzkY}59GyRg(Y$03=Af=3R2dakGPnOwZxDqQC3K=IMA2W6mT?;`3 zEreD{6v~Jo=wA|IrI12s={h2agQgLka!5%_V~2nu(h5NoR;`OU%*7!Gy8XdBw+K3%&@3YvGX(EO-7Opj@Z^?Hg)q2Q}=7J5Xwj$q3c6nXVB_l9DQNE2#>#w5d|3ugo}wbsae=^h2f`R=nxj91 zhB;>sU;Y~4vAfb0M}LxYpqrh!Jbv(x{+w@+X`;B=t0bXz?!sM42menW_`^rXj-Kt~ zYsZ?-%K*@N=umwKPO3LX+Es-cWV!l6%cykoOMQhqk%;##kq8;0vlmU!v1ksq#~Vm0 zH_n{z3c9-TeEY9oU$zb&LGfc7kQCSmp={@+Gj=@c$Fxz59(e@)Hu;poNlNZfU z^pPYYORylNn4BRr1z+GQCd#VPHcxxsA)Su3`{UHE&=BA5o1VVvq z92y=T_GT#wz->J#Utv3tMRptnbnMtM(DB?LQFQ3SDM0-@Ht+;M7K%f^NyQDwXc+4C!M4Ina)tV(P|1u`-Nj$cbu zg~rv7XbMr!T*|USjS|ml&tBc(>KL6>m`D^KNN8}i+PNoR-18e>oW45Fd|-7B(5%`O z5n{CrtxaDZ+w+@WI{WzEUWQ-12&2^wX*@>%R(g#1!!pWl4O@fwu-?s((lwk7LM9P3 zDK5fFEDMa5qY_!u9r^lC;z&?E2wrt7M*?Wdiar117tcTbLf<-ei0O-@NwaynNPV%H z=&Ec9S@B9O@|26yF@Knv*!BeL?X4-L7bNx$bw~2CW`z2KOZ&59AMQ> zI%p))lvI55zdv^N`_J=GD9s7g!lgCoTYkC#^El873?dPc@tIwIb9+;{3I>M zRWXTFw1LhDQ<92bHKM?dPhK@~6kUx4j(l2>%~_REpU{a_C6Ky<(kvi~-VJN|wybxY zQGz<)Y_nbG&YeGd_ACR`M*V?q_z<8_cA(N4lHEGTLx7f7AIO_-n#UqYBm~q_8qaN+ z18{Id72hHebp*EKv9YnkhYwdNI|JMPchbPk^pj-9EFsk zqMME?M2=}tXk|i{(&?yp-K`btyEXWj1Dt*Q1(udsv0ME(KuCJ!`0CMm_~6&r?*I6O zQ{R1#Pr9xKsP6hBL>J9U*`MJ2Iq{QdOf0Y~|#PtlKCB zRLsvxu;kxXN=aUTRBq!!8kMA^sgIBf`?jvH^Z1Ck=4zCDmH|kX)WrngY^;L^26cps zIux=9616CP87f?gxRnzC#%)@LX?8FcxiTbK_jkfdTSP`nmglC}}c*2s!)ECaMp zGfKi`0bH5h+Tb0V-6|A}&Lm7zp-MfdHtgu#-!uzSWM5xD6LoDm45yS~-UcF=BNI)F zeYeCy6d1_bDT!)9LSMyHbF1Q0*}k;kog{E^Yc^4%61w}UjPMnSD0DM6Mlk=`0Y~p| z0zU`n={ftuz0d!LFYsmA)%O7`+ZEx(+W*w0YkNHU@8zqjHP-$$k;7>&7y6Z-kcudJ zmleorGN!_mQ4x-twPE%48ZCG8L0m86<-s$+J96YGWih6;f>N=-vQ^{pt5J!fa~Im$ zt#qvD^Bka!!*1RN)+Z|39CC@rCCnU#hllV(QsGy~lWLD1Jpz*0wjwIDOATf_8`!yt zFHp>}3n)r#7``k&EH-F$MAzgLD^;O=_P7iTY0lc}Qbx__#CpoHIc~8KnN;&>fm?zl zb8;#Pt2EUTz*p%Doav2FdqqJoP7q%s`xxshq@Mn5r-h}k4q6II?8glA6^TR#Q z|GWP-d7hQs9zWL1g;guI3NMcYkiFObC(d1d?%(~lQ{Q`zo41bcu8#ggk5;jiD@9`o z0R?QskR_N%fJrh7im;N)RmNb`OTP1jJR?$>M2rLo}Ij+p{}m; zPwqeV#m9L}B-2Prmr0b0$pVswZ4!bj1PaO^Yz8_;nb&`9SR!Nc$@-CEm7}r&Yf1ru zPI!me2sC|hbpP)^a`n(D>a)5LNJ3ghryF;7_r%%D`~IJ=jvYIvV*@<;mqM*WD@!Vm zAXN)JhAI_sxLk7G(|ti*lzu6wSOo&@fDX^UmSywF&Bp7c4C32Y z=)tp7tvUxlc{oP@QBh!S;FvEvg7A5a5<&zY1R=zckyE@00H)w?lFy|eJ<{r0WbwJq(j+-}mc(phdi4%?98FhMS6w58Tr*`hg`=0;zUl<>`pc^(k zmB%JgrB#1&3o*``Q88i3uhnOQ}6F%IK{mPWl@E!<)mWI=(Ly@Q#<$JnMz7d ziVjms>6;hI1zj|A$UPS8C}v0*&ga6NzB-G>$Q7FZxn-Ytl88diS@kPe9n5yxgEHlXb;fgQJun zJ)5HM<-FP#e)G%Yr!Qh8tFIU}#J|K4H$k*BeR-54gE-L+FH|ub7+NmJinT{aIMR4EU?d<<$pv9 z7r_VvCpu&wGG6L!A(l`JQ1~~HLR=(k`vJ71is1F4BA1lcqAA;u?O8dwJ;edEuhRy#6sGRn4{3M@cWIQ(k;3PBaQZ163f08np+Y#1Vb3#;(S9DtoL zRXoUW!7x!w9+*N54e@oXA(nRZbl0yIfX9uFjvhI3WZ9&1-GXe7yazn6W8=cioIg$} zlvs=ZNa-;#+f|vSYw^n@^i$Lc|T6?H5FD5I9%x-afI`t1Mp*>g|qs}FL3RgDjRF}|k)p?%Nj(gY7INKN8W ztka`Df{sYlj(67*#xc&Wg(+-FBqUV^;Sv~O#bGlH`e)`?Z$9+d??I&Z0ai^V%WGM$ zHW)VjANYemEGJka_B`~h0KmaT``~ZRgIDk(1 zw|)WuCu{_wUpAjje1Tyo%p8DcB}7!2M>IH)0WiB|3c+)L0akbh2AB-Qe1L@UIk58R zps9f63#h7m517YKbJM&(glbi`jM8-otK3=>6hEojcLh>VG4Yj@cPXl$6vRd zES=><(WL?fNHuQMU5Mt`98%?6K@2?Ibm3hA>7AeZcF9A%yl()0AQ0WX2_s7Mm_ogF zY4;IcIX(UO9_75cZYR+%lZ!hyg`IkAH`}pTy3@`dW@bEe_OnjZ(u+~mxWKmGnopZT_OV@j}k z=3^4)a$TfU>Fe<4esJnr&-4xthKli}qxe%@gbvLaRvM>EB#tsHJnxdtO^i=O7P0GW zaBv7Sqz|(0MXp?xmk~}Sh#mw__!LE$E`?=)#m^qx+U_}hxB;#|2tnh z`K_mWIW_r5fOvjB#t z0abiQV10q&uFVW>w2_p)1!J|djS2?CuBX}AIr_SY(h*(#9MP31Wycu|f<^r65d^*^ zwoTxRWOzMPOIGb+h{k?oL zX3uYa`N$W4NKxFgwYdATb#CWk74u3Y5O;1Y`OxQoz@bd2ppWsKZ{E6vOQ&h(Rzrc( ztd%C+5i1S)7nD*lg`3WRU_0kityd5VZ52DHvcdtBpB^LE10-BkX955 z#BdTw<7Od)w7ZKXgVFsX!mLb}`>ye$=XhTk%VqPA+k)7jX|Vj?+n?UOOsq8oBa#MN z6|jM9hi-=KW@sYko2M)_i6ljJ1)l-9X8mb^gIHisuR#~JXNqON9zTAZE`{aq_L4Dk z)iX~O+6g*^S*i&D{Aj4#b|?^dWHC24&weRfnJQKJ4`Ed?RXDZ)A!u1Zr(Cu+P6Dm# zb6{~8sA|)w6H}ep85(LKoYY4Wh2cc%%61QM?BH4Kt^*{)yeFIGie= zJ~}ZSQGvFv(V-n~1%**#t`u_pF`-`qMjx1?` zDMf=woO0DAg2`5e%(DW`V7pI$|9M_9;#4a=EYj)eEhQ`SRrjhT5TpN@YZH5a`zy!4 z`eXJD*7YSgEt*Orp)7JYENuzYHU9~)hK*M}Oe4S%K^?*B{Z|+bhKo9rViis}6(O*j zhlJN&I3Y+6=ee#lCzjcyL?!tcFpH<`rnVTwir)PlH%1{ZmsUF zgW;$SQLNxd=?c4-Vw6BX8wdGuGgfk>qLjK@3-*m3i8^NwD%fzs&6zm>i@Qg^_T#bB z7w`LrAK&=E9ZVc}e9r>`<-Y1}kp!?_1WRU@_8#5;pa1;)Q~Mc3Y4l&YbE7R(=3BT1 zQYk1s2b^aD!pI4q0Edh*LcMx6p@aYfahO;D_LT##+$bw*A{dO`!J&Hd-3u6rjD-je z2JO`c0Zx7Qr;``1zTwwCx$(g}8DsETFfRt=GFGp*R01?ti=LCaUihuAOrO8nH$2dM z#!Iwu*-Yp=OvNxLTTrf1C^5+BS=mTQ&&l(IxFhuU57c{g+#zXfB=X2142G)?{+D?O z1aj{o0A$Ysrs8gg3S$g&U2I8bbanqf`-OG)>>!_F{JAcx*HT*m_!Wy(Fe^I7`14UW&=ooVX4c1kR*$d0&(FtYK7htDy$&}_NY=M$tVgnVf}o& z!i(V8q0_tx(6?!=jcW0+TFGI#0rNI~?EL6~k?w)s$#ESiUeEm)Pr}?5DkCLxXNjl< zG-?ajDvUxBEZ$W1OF+aZ_t2q3?HNaZ6?j{5 zJ4q)owOzaJUefYu^Q^P5p}I z-7Uv?q`~RqdyY>YIag;RnqG(u73;$EW^!yoVg9+%)N4a))-b4YuP7$N#sd)F#uR{H zfeTGZQY8);9~Vgi#)rO|NnlEB3lOD%EJ>;a)!3*;P(~5LC>e}KHxzj!EK!704kjZa zCn3Ty?&zHfk>Q&9E}=~6g}Q~&W9KftaIB}VzW(()B~>lmNzy6l>gCl+fR-ppd-Q+w z>py-%AjVvbja;~lsSNb4zxQ^Tik=s%2F`EQyEPIh z-8-Xyc3w@Mxy)8E52Iudwg>55fNUbo7{e+Y44ovi$off2nwpws&_kk#X32AC?XaeE zQoCrf!H5^3xTK`f-6r!f-u!0e>uMtzJ#qf(!I7bzn}@b-q!f%qbzF6?Rsyl`j~1@J zbdm?QJQ3odocj>nCDFT-u3jRnqC0mZG7SC`fz;S=)FlkV>Xw3)y2S;e8J&iVMn(rh z4j}!L!SI`%m>zuHZNv9$=h(jJH(E08SdeO(%`=bw^uiDKb@kV##wU=T8dta8x8&eN zY}9*2@Hq}XazE0szop+X~G94=z*(Qm8gPj$uK4FDq8~jxqEkl zP?Mr!s>TJt2S^Uwxp~{){5eTN5~^ksMJ=CC>8DPe`qMxCQ>FpH(HR01M^%8MLItQI zm#?nPCDnHAy1N;3>qFQmy*}f0T$TAK2@LXzX+V`GC?hr~$plIs8ocq1Z+iXf?}fOd z69B1{6{*P+7p^>Yus%E(R;UKY@&jM~^6MbM1vpMM@&~%3^<7uU*hrP)#zq~n4f;&| ziZZEIC1+5@C3w-OKsKT>q<%*~|7Tb&U+5q*Nup@LmYqusT+l`pz&JhapEl3HbMpMv zi+hi9mfzYtxAmqf*QzN z$y0`r0S|;6Fwzl*D7Mx;apv-+y+_#6x$f>A)QsnftDQ-a(<=hXBa|~g-pAVirRR>& z=vWG5Sf}McX+WwkHsBz5XD8sNv9SV(M$GK(|or@@GJyqJ_YzbilSOxbH zKa5X^_@2*}54}-J2WQJn8Fcsp(_J%H$B%vHJ5%Sb&dks9rxqpddpIkRods%uLQv6Y zQ-NgvrLSjmu1JQ2j8U){R2Cf#`gu=cIHHofP5 z0`UAQD{(85g?#t!-TRfVd?oVB5>KKRTXq1{JYuVq0e8e-jtKya>Diy(ji4W|@)@!S zKa|S&o(BT<*;r3cqDKcfQUyFSGkeD!JKy!LcY&#jvYPF3z+wAr^uIiQ>52X7-F!ET zhRJ8o{N*ooYE*_@PFntNtJd|}&-L(RP|XB5wUB0fTR90)O&h4d6g)uusyPIv6jY4@ zCbkjfK1nPKNQCKH!HHg2p$t^@7!gie7I{+^k;rMV2X5K*8Xs`Ea$w}r{uA|~fwgyS zi@qCc>7-#KzJJCpx`iN9_oOGk`_mVG?;B?x-?K0?+sBc zWv4rka#0-4hLB+?U6{ZcZV{8>OyqPG%ktfVFwrsk7oeYUtRYnGFySL3rQtnPPS3b} z;KY@ePWEkBGkp7P)KT~URa0qEz$*`y(cY0S|7id3|Jmf(wDzx|n{+LEu(f2s79x{y ztSCHc!MExYK3hRI-~DCBGxT6++UysNzO>TJWgL;*wxJC`kyYZx85g)35;7O8)trk= z6pzhKOl^4J&RSnx_Z^R-Qrgwa%Sj+fjP<^``NLm$Z2uoTGIsPFXa3Z*c`FP6tuw@y zN#s?3-=MTN51sLe64$Ca$1up$)cOh(=7i>a{8A;fFZd^Clpn8 zbc2Pepc+_1H;7s+-nmy5(UjfGW!6gEN*}pHg*M&M7nRA(>OHr8^i6B-yS=l@q9!5Z zAO7%%Kl#a1N#2rsdJYf-Mu@-anl;EK5p*K3wC?~Ej*tRFWH#>Ovp1$?8W3bfWW@47 z0H0HCi6SjhBN*}m*uQ^2c#uWYUr zEoJg_ty+3Xf(SZ|K8^P<)a8)}{hWD(TJuI)J$XkTryW0Z0)2fPw0i0J!@EZ=od4;; zdw=Bv!#g+2kl5xCon*DHtMFnHKv_suH{Y(V(c|Y1fBEr|@BEbQu=W049oFGxR;*Ww zLuqP+(u;?8l5yjn?I;tVCXxA^A<~CdUKEkir7jQVWF!h3Wu%ms0U$f!G7Lf;1a5X_mfioxt1BQQ z&QZV^<8~1JzuyV5mSv*xDu{OZ%c_+LJ%)?{7$cFf%c7!wDv`Sgw-&!7(A7K8H*;z9 z;Gcef^u)Q>|AUXM+qE5v)%Qk{ieB!Lrh?sF<7Y0t^k2Vy^4rhOk598SBjx)hj zhMJYDpnjo?q>(TcVTBhG7a{v<&4J_^or9v)#@$nhTsn@cFOXv?8EKTV(9I}vbD*bt z`pVeIH-57IO?UZKU>R}HcHHL$m65MMIXg8oTV7vMm)G4tIG}zziA3U}s!?hDRQm=l z^AW;vh{#H)C6MS_X`FKsFx99e3bLFPK;s}hT!;*#Es4PA4+~T!H{Ap|;gJ)OPK{^eh0DTp9Llclcnb@Rs(X>;}3m7g5sJzzd{2_-6Ki{8xijK)kjmjC6?2lfUpO|9Xj|^B{nd5c4u``ed{ZJ$`*L=XY_J~5{`9`i$ zm*S)?DWhT+4V_v?%{P-$%dB#%kwC>cTNxZq8Dl-lq(KzLssgqw>dOMe)v-(aj$ht? zg13Y*m0({xf}n93k>9F+V+rKZUiY;lr#as7#3N5FOwaZX^d|$*JYR<8paCpM#uG4t zHY}0bS@hNbDWt(rPub_(_rXf|U>C_K*zwocXrp@yJK+sMs*0;>Z0h*_-5qHcb+@(1ug&cx}^6| zNigjEVPA2uC=pRVi^ia!kY0+ox!Sw5n8auEs~U_z@LkRe}c0oXpfxhcs!; z2=@3X9@ls@*t>Pz_P_mZUIHX-aNo#ec_B*Y1GS&|%zwFb=@KwCjr!n+i|XW%*b5pk z%Ujb$N?Yy;099bfz}THf0qxbP%BU)xz(ZeC}DDO z)qIGL(nXkws{{cD%?(M~6N$W$Wh$wZ7BLDoXd(|P3fkBtr;-y$vWx_Y9|apZ=oFxZ zu8Gr^*y?@t@M)Hvyv;-Rk=3zXh_}h7a+bIeN7Z?FtQX6@1T@{N$&o)iUPC-ySn+- z>BRV$M}xvJ^ch~eRvo^yPEp0LYPk|=0%e=*At#HZMkcW7lG+7(ndnR=&R!ncv8iwU zFg4Y~qNuL2Ty>TJJxN|fKvw%d^XN-|^4-xxr#Y8~JyhyOP`7^oIQJf*bm6_FFuEa= zC1ZdfBn>j!yEGKYkULp9>>fcnPXxYNwb41ag8+=KfV6phsWcv6+F%?4Gs||f3)i-O z_)YPh85Q3_N3A(T@yzA@zxh>8QetB?JV)bV_%ys`n1g^=qfv{p^IXe_TmzH}9l;~t`T4muZ@gpk zM;|7cav)jf9F;zC;>2e^`&st3L@mJJT(1PE`_nxx->S@NU^-WEm0a7k>+X)!01IXs zz+fY!5DWa&q$;DR7z8Z<4_qF8_~8d0c%Z{i09-HHA5V=h|L8@Qi>N&(|L9ao-KA7- zeaM*es!_a+G;BQa(~vueF!E}QXiXA8At*F~Pr;1MSEXDuheoN*P&J`qa+oW>QhLd= zIkiHC#z|vDr(3gKC@}?;r4UK8!bZPrTp}V8h$gRaqI6vpR8$y-V?{@hu^lVfEJN6(x|!@8-t=mD1{lu3x_HU zghfa}S)HNY=nR}r8V*PMEj4TaDIrmqFiwCJt$<*m6$5heET0YO-teYfVL?=(k=MVkiqh}x6J6G#sV>GW)giL4)gKRlgQ?Oht+blL}8FC8&s?z46%7_?G=!#U0 zEFLtGBoPy^#7aKGSCwM=Aa2P#B{6YHUJ9xJ;>S*xl$CVDpJp=Fk<6}p(+3}1`^Gyv z`=EyEM16Pf-u;ble3Rj51TB4e@KE&`7DkdPGT7~uzfA7{w6kYb>g8Y@Fdcoo`wNU~ zr$=N34{hwY(l~nsxWPL%*EbB!P0z5?bZUB<+g?<)UaK?u$9B~x<@<{2+sa=x+yX~) zs3Kd^6IE#|P11<8O{m2JkVXFCr&JC^RLMu?UbK+2DzO2$J{hdWl_9=!QIl4z8i|s` z9$Z3BuPy&<_9C{X{k!0}?W1gx#MvL}6Chw!P91}@5Iv((mYwDo82z)C{oJz$x4q|q zogaDYdYvvlh47u4~86p7{1N+_1-vp99Z_BsHFw$R!^B zq0?|!p-Rw8yhcV;(8^D@aA@_zNx$y#En%Yy#<@ygrnTlwB?SMfhHl6tN70TJv2t@h z0&PIivakZQtDW4=1mlr8nYv)0_mrn6?1VDvd`EVE9`+B9TsZp|`?kI3!P`Ib)(!XV zOs3*JhZ?17diLaZp5>L_s|QXZe{K3lT^{WTu-W*HaO9ngK^LpfMq(tM zVv|NqAk`LNYI2e%0MWyvdp(z;FI6>^#)MSJrsCN^*o*rQ#YTiFoTqF^<`JDvWCBj6 zqFM+c*boSBX`wUeWrT5g-|?}N=g&WT@Xo*Xb6bDz{`lmGM;_*nB!AT$YKWeM;b>PE zPtuNj<%zRTzQ{rt663%hq$$xbuD9%E69zQmS1VLs;kAFoC@P{&qmU53N|E|gU!Y5J ziZ%h*93>)0b9>QMDU{X*EA3h2pb(A`gNA*q4+F zUS((Ek}A@Ngaub!rjCR;Bz2TLAy?X*K3}W31l$&r^+z9%%}K5NIMaFfzT4YVcM5ax z;6apv+1|{ObllHY$k?^EYuDYKl3FPjS>WPF3~`E+>_qEsDWVgt#7c4b zKMU6B9HsdQ0NcB{hw};8%P$_k`23;qvzPlftcj-q#L}&kePop@|Cjkn&q=e<_}+IA zhW>}Y^!Q7E{O!{}+`~t*Si5B?oYWOUjI~<{TPi7Qb+k!kA*2`Z1O9zQXGg#C#Q2Hxbu6c^4B$j!Jqlex*6Ye&yqc$ zE;WbTldyyCz-PaE@N?h4@a)0)shK{`3+`cOFYQHI&d-0O1*=|4K%v<-)Q3b=@)a)) z$TkGJg>n&AK}$Ks5GmTQhZfO8XTwlU5+LdXCaY1CzoI4OU}b-JUK!d+5dA`pWFJiJAF1ER8L2u)*lUZJ{5#mM|rmBrybQsVN9${x?>v zwK!1XyUi^^+C~$IL@7lyvI=B1lWJ_q31pQf$np(CzTVr`?)V?x zt34OubFqAKeoK9&bawuH?sK2x1GT_1BD+#CLRGi~0fM`wuHlXI@7NQ7YPAqVjN|n< zpmAX6TpR()k)RMm5cUvKJ9FmD`Sa&DZ{A$Zr&Dl!&EW98+b%qHU}3PA_KDH*$47s4 z@PF=J!rU?ES7EJg0KuFa#EtkQ`$b!ITbh?4jG8vGs?>q37Oh5uScOF>b4x35(});# zvTow4gb3X%=-y)CO^G#z;OYkhzz{hrt|B9(?l0W}$bx1kQ40YrAjiINGpb=cyc~S( z_&F`{zWae4AAECJ;uRHRJKxAvt-pLOGbkP{z6-k#pL%Tf`JWydJ$jlk4MYEQW{VYK zB5A@m73m~yExxa4NEg9^PeE&sSY4gy|0nNFo;*9Qd%qrP?h!K}f&e%{A~ifocKA`= zdGizwy?EX^wDra-@BLSJ3 zCWL>qNQ^n7Ew2lSr?98bp53&mi$x#>OohBM%7jy;k2dqJm}nPlQ!JuarHZ| zZ@%|28Di#8vQ<-+iBfZsf}QQBCldW*a4{8N1)xq!)Y!r@3X#j@N8dK)3nzFg z;O#&AtE>O;2G;?;^dJ5HBft6Oe>xY7rew(GaQ*FHauw*xU%kq45w{L{27ybgi8?_e zfQ}&vb0vn=AD1p!5=$R_v+d16;MPzXacs_@HE4Ph0dU5X;0OHoh|IGBg%b9bBV?_;vHHc=Njhl z5I+=2FV&`(RBGzv3|^!ixwp6W>|@K9dH<=cJNCj8z;C_v7G72A&iy&qN3hKqjvYP` z2LK}q6`eX5@KaAYekb+8SAnyD+qZAO`R1D(0Km+CVrV!P;Pmp@wXZ(K%|FX)OYT^3 z!dHL#9+i80_ZadrcIkVD9_YVf+TJ&J&{a^m&E06x}3s3)tzw_Add}V2!ot6fj+adk$%#lF05ZdMGp)~OB<~FkyuF+rp z?wfq!YwzxEP5o`P!&atv=?wyIN#C&N&R*Q~9s6&HZI5o}_!mr8)OrzXH4C*#Xq7CF1s-MhOS2JouTD}Vh8FN1&L55MumAAJ4%vribLlz-=s1dIkD<5sbL-1xyu zKl#PK{qEJje}jkYi665)4z2A7w>k46DPjK-%$6QW078~dGZ~6A=pc)fj;p{52FcB! zTU+^tx&-r+8z~XYwqAV@z8X$j${^ZPfwg9|(ua=hNWzlaQk>1wyc`#LK*(Vv`GA0l zfCV^nFd&>f!8C60jw4Pew4=nod!568r~l|%kNxgfrrhu;VHhK?j(_y!U^0CC+B?7a z^OrvOhc`G!esAXvhel_486`3EtV|N12j6@!99{}A156KxnnI>3LD@u{W!NhGhE7b@ z*{Rw?y^oI$tODuCl5gpeZjBIhcQfQj*AeSAsz}jeoIHExqwoIs)&K6#zWHDLD_-y6 zy+B|54kBr0lex6=>VN&e+>6N-X?ECx^XD_VX<3+uzE1XJNUR&m-YG(gtv*VU-IJy*Eb!(qvX~Fp%HGYn& zDh@3SZUd9Ij$?MM;c+%*M>~e+-%9|%>|8AFwh5+xaat)`ndIZFNjAr5OxnW;$VQg zeLnc!n^*q!HI9F_Kf2*{WOmpazO98|63hBUKq3Fy**o^r?fSACB)j&)Xa88c@UbJe z2Wr2WK9f6oz)Gl9gXtHxYdI{{K>?=)$fCkkX@RT340dXCw0#UA{Fxfa6w@99B#WHn z(Ga#o2-#P#7})}E!P-gM<*B5lv)qEhvw-it{}KmI%>Ez!?H3;Z{jWXt+b`s8Fm0FP zED{^WuH2nk@Q)EPd1z5JC&!ID*Kc!A)Rn(^)mvZQ`v@wty)$RITa3sX{+>2QpsHY6 zZUn<(-17~CB^^|-omHVrzZD5Lwl=r;j8vW0!Kt;iRj#Ji-a+X~nsj>0_#ST6m>8|^ zlRG+=(*dEG#eGQ0U}AdM6sE8Da!iPhy4Gxh)nTM5Qb&yWn$4N@GmIp47~lzo-+un7 zfBwzK|Jm18A35KqBe;bOZv3M?5;&L)cW-ZT3Fw!9`9nUw&fzg<{dtLp@A#*kX{&*@ z#kICyD;fH6B&0ggARD|bRj9Qz2q4j(f+4Fc+b{u~o{&GidFVox>=WFiBjP}yUK_Po zh8loa48IEWkJNqtFMhz;_uu@_|6|?+g(wIAHsXsZLpfv4_y3#!`S$DYUVQxW=FLrV zX`KG&E;vd?s_rt5x2QC?Qk$V>;AeLn;a|Pj15;_Soe&e$*s@|FC##6Ns}Bq}Nc)h3 zqLc6d0IpQBzWw&w1rH3qR=%}tPD|(T)b!(qPQ(E~1#Sdr#18yCKE*I%{pOoLhRWP- z9``x_LxORV+x?$?Z2j5CZoc*lJ|)9$z*ANxY)g?e!~INiX`xvU$<5<2Q!P!qYfIJ* z0wKN@Zs=m-pAiefEvSYMcfAtfa+hqdYsbu-j=>lN6!+`9iFOSa=!sD24hJVSxhZHl zbGs{_@ZuNlTYcyM_`65G^*o0GmtTCgy#%J*@YvHQ`oFm0K6XBbMNLE5T{-0R9V5>? zeDc~mSHJhBuef>lqn(?#*`4`#G*{{gAU`crI|j|4a|+MD|%7dvxNg4FHzX zlpDRLT9`#epUYl?fLGzYj~aSQGe2 zkPs1F$P;s;4g)d;Rxy8ODDi5w2Db=}lsJ8gFQ{<;k6e^JkHPM{B?u?r}Hjtocr3-wjoJ!ER7jPxg_}NtFM_bd`qX^B0Xq2JBGFy z_9THP;sAhvWW#KM889)uPpsa0>nA*Z;Q{e67oFs7;+MYp-1V1!N{+6saYu0uOZu*7 z!9m!2LNDMyhsqbI`q%QVx7VSJEyCfBQd9Jdy9{L_YipV2aIO0@lk;Ko3m%39fIlppmgmWpphnoxZ-008qtT%uD0bjh@yW`!f zex8Z5`4tp79j%bboUCB$Mm05JtJ2^K(@3!teBu$Fq0IbR}9{rdx$ik55`j z)AX=)Egm)}Kl#bKS6}+cmG8a5qw{;8Z1Q0*D4gi^1SA16RK1ug2C9{0j%h<_ipS-n zVsly1h`nMoa@yuUc|x2wZ{OBdJ-c!GG-pzHqkKR8zZgnHN_Ik{jfSbv9_Ry1=-N=i zKk5C3F*l6JcoL!I2Wg|Y_ZE^a60FI^zpxUXm-R4U_ukGYx2}HYM<2cXHo0-}Yrnyj zA}$$S{L0fg5q6QE$#fCJ$I&Vvj;shzh?`SU2$f9$T|z- zPUdJV%Vkxnl+$N;$KUcYkA5=XZe6|p(GTAK+5i0y%a2{;WZq-ne4gL=-*|k9>p9uS zp+O~h;x9g8BQ6%{i(4Kvnslnjo#ML(ypf$#ww#~iyb)LDxpQOh4sTCC!;SuDxZ4LO z363GGG}LJzm$h#$*gY}(m4<}q{yQQOR3dwB@iev}RRt^`R03m>ZqaDL@Ib2$0Mq!2 z>xgAB7cnADl^E3}DTHZWPQMEl(kUJ3ki_x0F*keId-2FUd*+5nkwuc@XAGK4QLVK= z(ivVj{pLaA?(XK5k9o=L2j6{jg)fLb^XTf6m(IWN)Z^cJ;lh`n+<4-$t`Uh*N(GkV zsY++f7+hZ{U~~Lw+GMsT@uL-9buZDakyHumE|{3vg%@9l8PbK7sICti8=XM2xAclY2r z-Dc}rL8sxyUYZTITI(*F%RO{@Q3oH~BuVv|!%I>%N00I|oV4uX#J{p;2pIg;43S|& zTU+I<#O|HlTR(lD4eBTsb2J3HlJ zm02r?~0DWD~RfG!1@>2`^oh>%pcPt9UIPG!UPrGz7LK!=% zY4&ffo!)xq>W}`%f5YjO=l|6oT^2}BaEceQz4O2RHILcs ze0+0x!_R5g;HmS85mSFJg~eM1QUV}fYnvQp5g8HO*X}NRJnMw|2-B^H;&0n_C*MOf zYG+PweB&83cno%|4**mvu3o*$SHSSA@@X`!Fdzqh@PGjxoW~2Clmmc$QK+aL8mSSx zn>TO$@P|L-0AOJDDS1fMzB%#<@zqB!aP{}B_nGkQZ=IZW^Cg|#O6m11vPC~6?@o@H ziW8w&bjcxsfEXf!16NJYvL_bZqt&KyIdKb)*;~R|dv3V+ohhNfHP3THJ6$_XC1J>9H4|yZqH>)*idGw6SVh z@|%@XV$V{f*w0xnu+1cn0OOKvh;yY0TUGWMy?cB6_LYyAxADzmzIw)-gSUAylgo!} zd7tjtGaGA8)6r(el}Bo-nOS*h9g<5I>~f}gTacL@GzQ_mFRvCV)x?X(cIkGCJQNoJ z5*`*-0!#hWQu{Nzt5_==GzTW5Xah5gH*n-oW5xsRu@Q3Lu14iCB(C8JktPVXU!EP0 zs+KBIk&MKC1UL-funKu@|JeEX#;x~1zJ2Q!_oS{rdhy)Tk6e82smFiwxr@&~%~z;- zQ=~6M$H5VV-ri=uIohHWHXZI#>Dr4km}M5S1INI zMlGy5)-{v<+wXtONWz1s`0SO#k8d!T9R%rMx;57CT(dYKcCln;V`gRDIq%wX2_| zj3}kdfTgu%M%`chU*G-Y^`AfK2Sa}E(%0JuL*&V8B$UI9|A!URB>fAQL1}EtxaMlS2XI$sWa;v>x?~kQ(_`xOiP7I%TjHcSvBY|ca8^_ z#5}}wcQl!6-1#?H30c9Z?44O(IsfAEe*pl$ss}j-&GV>i^aE3^vwM0M4~*@KAS7FM z0?G+G02o05AfO|B1HUhhoQ-?qjWGAa^FS8ft+p%gyMKlBNE1K_aH}B^hN?9U}l8hFuQj}@QdAOY$w>a_4bd5KW^R9Y+ zJD=R#xw(07`>skj+~h%L?(zq+xltSU;vjm}pFIksn9-zG`2+7BJ5I<`BDP1`7&px-I%2FR zSH+Vs97A%05C;P$%;odEOT;$>Bw`8Ag2$8dnwrJQ+JO?$Ca*VXr1e8|a;l7nK(;@= zdF#E8ZvFf!nZq3&x8CIrvDSHo^tlo!gt+4C$W!PXsvj@M}j8GW)l0<{_h2 zeI)?spZ#gUEA(SG8>f;sFMZ7z!kiUU`dP#Lf~@XOm5thb0FQ!fYWgk-b^v3-O;g^s zw97qId@X^P^Y*Rfb7aLivSRg#iz|;`y8Ob^Twq*(>>}5Ze7M3m#SiryC<#bREHrcC zOWWFlmFiqh3^RyZO}4Kwn0&;QLy`f2C%m^mxyg|Lhqbhg6RUjTfn+EZw$=zWod7)< zf{ZN|!B0z|$iu5UgKD+pSf)Ed4YD~2^Fp!O8oz*lunN-Y@ZzR*BCrWEP!K>#0W18G z9F-OA;1B^GgIQkU#pJi%zVg;DKH#O&PyG8Y@|s{CA>%kdvFh%S4CYt&86XirE2T4? z+WO$yhd+4x>fgQo@yl=daxL_)ukxmUQsLh6T}~kCh5{~DR=EI^&#S3HOlgv==b#Fh z^lMaH$iI5@hQD7h(6*qNzxZCrPI4>b{_4lhZ=9G10O)AmMahsw84m!ozF7>|KmwQ^ zC#IaB1Auu<2qhab>J}w2E(Hk(&515;#D6X1ADgae%=(V}|i{w-aWU zeGg4K)$YjY9mkCfwd@1}n@$cd-e|ObZUemUjbXWa@8&N*+`V>t`OSA3<+%v3bbgJg zKTiy2R(E0jiA$X8UEwQZ7dN1C{7{oT>{ddS`=d>|bwt5EUaQATCz#fAI`;O}Pk7|+ z_6OHEJG*ydYwM%yJJ)V=?v{Ign9|kxIq&cH3%=b54R8-<2>GjZxsi#QvlEPikvi>t zg(oCuat*5!xI#PiFNJz842OSBN^BKS+4Zq>dQ?ce45(HtKWjnJB-NCm13&CK5ssn{k+^L-%URSxp z*$3?NZYA*R>l=GJ+#~b}lPVs=AcB0VhHN1*Rxh1jebi6U`9ZozFRndu-a~>58}9X2 zmk{TE@if8aXth=5&_Epy@PS_PhgZJdxxLLBpP16|+q(M67Qlxd&~g~yjDKs#25}hU zbc5$Z`~oyh!s6=63ZFCQ1^mwQPo3h8sCRentmpf?L<|fIdLyvaoZ@ZxKHF2W#9gN` zk+LC(s6zLxOr6GgXkzK<|MV%EdEMd0dU&QpI=tD1O;_46ydCw zJ5yDsgEbyhGUXWox;vRrM-Yg&u_VkU2>+EgDAJ;qVmY^t@BvqPmea!&kKwP~^wpWC z_O^Dmm(Hzm5`hax>rXy%@tMaiJpCBg3P=yWJhgIRgS6p6+rv{L;p?qDa*a_s`tBCv z$@cd3+w?v^vVtX}$~J?^_1kwyhRtoam5~e%CU$+bgr;QMZzfsQn2@FIOwXDRHM|hP zg2hu>s=#SYRfZ{YZ56~~BMRkG3od&XxyEB!@4U~`BUs7Hwep&OEb?u=)fFxR7|Lj3y|(Hr#qmH)T2Vghbu;6rlubpz zQXIflUG(In)w++ao6Gi`Zn6d{koEfWPq6KPuNcd5d}V8E>jyvhVcBn;p2~o6_#j-E z)~4fgzAX2>&T;!u_A6c^YDB8SH1NaCB60T>A7Fd_`R5s7kMlY}Hg$&1^WXgPm8CPh zL(q-aO)!Z|0;k^cxu-7G$nD=k=SWqg%>$q|XBYuF6qrj5L4Yz{nhy%j_0QqrEX+Zv zLv;=Z3K-QcUP3mhIg^c}MU6n!;7`5qjKcCU=+(r|k(ub$WR4hf{Cd7_u6!1*z^TU+6*JbE}Vh>6Mky`8(fkDvM0?sn~`ckbNY;wm#! z|7-7k0L=cInH)=;;X_E?@X-- z?d{CbNeVgXN3oGJ)&V#kSX*1$ymebY6PUZ(JA13E4y~Edf}P#rBNx(yno?c}M&-3)799{pZ8*^~r`Hno#L2%zk*zr|o2b14t@~U$R7kf7NEU zco_&mHM!WSm;RdH7pzj>Hs(Z(t$?bNLc2$Cb1CiwW!TB~RGPIE9|VsNvQ zH;-NU_!DRh4lBzSAHBSMe(mha${E^ye$A1Da}E4FKIAJi91n12VHvgDpcQA4v`H2e zGK`oKo<5+*?fuOi-d)3tlbd9!9O`47g*LaF8qG=g(XsoaN|SJ5@h0`DW0rNJ<$ zbIi~Hbrj&LI$8;~E@`cywshuP?*DObcmf^(_~@gLco{wW0uRr6$tLe93=ow}g(j1gXXYz>2Il`R7}I~(ebmafY=21|FoJ;<^$WYnllA2|CDBO@gE3crvFT^E1Z zgUUez)1bR|IRDAFNEPNb2~LIjEz%|T{YYqw>B+r4ztf&W0?|JbNw^pT~cO@lfP5D$(d`nsfF8|p{Uh$%91lPA<~KYnd-{v200-h zb4VN}bGz{oZOl!e>vA-iifCZQuO3Q-UErr%3?^Gl17yLS24p+$;v@lOYwj4uW}VQc*+CJJ+#5Jc4tB&n4ILj{(KQwDU$ zZd}y7j{>isI|qSBEFe0J@Qx{7*u@Nfmt#9zNWY^62d#M6?~#9vBpi%Q4Ng{I2Dx4I z>;lAgGEK+L3Y3iT#M_O|Xt_*H_S){myqjTg;ZQnb8;B7nApNJRg1-X{nOz>*nts;2vqr7^Egj0 zr5_-W)$mk4aLRxoB3l@nytj-K`GE6oxU;>f1QzQ+DaEV9v^+S%UWA^=~*sGcg4(QWN2pjU0y0>w5q#35mf3z7k< z4E{N$Na)#zEX@OXkPS>x_wi6gb|`eqvAE4t0Z0Z*@R*gpIKRp{)=odRwb_#3|MaIn z{rKaL=YAA^bPYf;AH>Y1juAd72LOa=9)(dYfbhAio0YmT0U=CZ14P#C$1%)}t5Et5JbdWmN zQ?O1PT}s2?IS4$6iNQ0(=LTmHaC?F$yH3!uXW_a;GGtdUGyRvv?T}1h2xY)de((-^ zs*ceP+3Jui5OwU`9op5^Ng9SlGEOkTXUUzI>+5T`Z{E^+?B{-Hp04F{b@2@>AE?fo zLZz1*=+Rg_17eqaH(47@wL+)UB$<>`KY#9C&+;6kJEFC7&iK&-cH{gx-v2=I$Nlm} z?l{#~Aeyf7SG+nqGWx9ssHHkOo7Ngna0{GLLXF!Is=%vW3;Z>}TI<0OL{~e7rh>p> z=!szt4dwyZduqUP_Tq-BoB-V%qN8<{`OgrW?Zp)?ysJ$4B5E2?+Ej*&8h3=(Jj$HG z0#|PCY;QZ?jT9KNg$qY^Q}RmB%!F|3s(vz2-a9bd%3|VbRtZWb3inQVYkun0k*U!* z?3YoO`L~^(mp{D}e=?7jQX#I$Od&!WMQv~gNJaM=ntOh{7aWGDVmd+^b3Ptt!otG2 zKHSj&lVD*Ks?zn#Q`mT0b~WD`;=t~!KSRI-Yo7h(Ve8t^#4nZ+fNI|$+KfRfc!5R* z&n6D|u~zAk4oZ6>0#lKyziDoag?yPJX&zV4%$SCf=s|87p3J0!~$x`3~+6A z^%hTGsKbLq^e`#p3X(u2A>)kMph?dW++j2+HA59iHE&Ogv#kWQSJHvTCEXv}GO5_k zkj*WqI9N~Nky?r*E2NeP)UpnI?Q{vMNCK#_)yF`N-Ncsy)rg zettk?GKB;3UEaqaxjXHp7LMY0YG~PseVj3Xy%#EU@Q_Ws2nz!}n7!3JSiZHz8_yvv z{0y=VXN^W&YOSzhwbp?raDry^2?=y49%NZRiUDc~4)K9Oq~JNG^pslQDl}n;2TIK& z*?V4NdHxC5DyzE0v8`W!{q=e=Yq@OjD?Q@(=oI`Lq+|2-2*U^VNJsEJ1oS+1!`wWG zz!m5@^7Yq$1TzN!Cn3aK#ODb0lmGAaIFsFeYe^et9Ti%Vj3%}XW2(=fu_X-LzzkrJ z00wdZMP;j-Qwup??%OPjd5$c&)Xwp9B?sYsZX9F~)7AC0LV??GY$e4(nm>ndgIH%` z%nG+JWmpv8U|IzrAY^4LL^;nVbN`ivrW&Txr#ThM$(TDkJ04r2iu-@Ich)yJ71|C& z!Ii@<11pAa)T0jN1!^^=Fuk^jl8Kvx@30$jPLQ^7%4cP18Rawwaj*n6Q6F>Rk1M(tM~riz1FeN@D<7cDIroY5 zYT6!K(W>c+2>>09w{m7GPlvU>FUKH-mBTl7@Ac*=L{I@IQltSf4=*c|B2-cC{`dBH zRpq_A_qezNp2Fmq^K-yvonne7y79q9voAUNV+TUcFUI$3PXZ?orGgNJg*a>(g+(ZS z{iW_QZbvzYnuh^YFALbYtS#g~0pRCDly8;#;+Z@l^IR$cH?F$fX1S#!QXHdlZhkfs;13DG(Z~BmDmP*RGp5= zZre7idPWCqha9dlo3GI1nY5f`NYhjj4bJ>w(|4_TP5{74)C80>h{-!9b9Yzo()R4R zydJo-xy8}GZ(p()S=E&*LCqK9jU>Lz(H)i;3V?9xDh1hukc+UPNIb08MlPTNRz?AV z&Xj6;KeBbuSXAJoMr1){RM7`HqnAw*yqGLqYa{>FCE5&sfcc^tAa5_NNvY1^qVLCO z(Y{S68r-ehHwm_3Vg#T^xjQE}1cLAwKx!iH&>vTWus1WdRM#gOxjtjez}dby!a`J< zriv*JzyzvqM4bNL;bpQ*IbJ|GzeeRa94g}sJBX+=iWVv{Vw`(fgmoe;aum=RKl6I% z8Ip5_bGR4=KS05Ao>y!`SHuuUhlaU+gY zb@-Gn8a07~m<95~t(>3(fYI=Zrd>WO2cuSj<}m=uv(G-u!MFFS9#`pG-}x7xIlHmK zDKst-;tOf(h97|C*fKl2?$XYx7EsL|jfVt0Cn@s)r34Q$7JeUfn49wtB+R1>!9g9Q z&k8iPV=GS_qE;jR&ukdFX4|oKY;s7~KFwWmo5MLYBcI4SMi0PwVJ-kLxoJF{ay7aX zGcHnAxJ&%Fdw;g?a4iTPO`q{%5moNOzzA&7tPlL5 zQ2T)kl*(U%l(fWvA}L{?xWgo&@fW#R_b=nR4ySX{Z;GegC}``_P?g3LRO{0afY*8b z4yT;Cx0UShX<%;(0vSwCn5G+f?ljAe*7)u{`lnZeRV$7pAIiEYEQLfty`C zliJIOPDi*N$YyeenP}am1UwxX6(s`E4#IRr*Iv-mV4DeQp5xasr-l@I9aAI=m*jv7 z>49LnUe*AtqfQZ!)jpAx5fEwV+8TFTRY+kY6jV9p&s{(Y;*qW*cA!jdE>f#$g^N)U zLG}=*+OZ%3TXLjnT1v--MY0hpO!Nd31Xb`EtyC@#g|#M3l4e?-(mGpdw~Bnt;m}#s z1`<7uDab=3F)Ox^029^HQDgR>XUc|U3P2X2jSnf`j|xw5!L^|WtXGyd7O(N)}kJy&%Qo@4H> z2kM-;U>utx57lre&p-}fQPyoJnqBj6$TBVzK0}-yRg$=KExsBGv#WN<$c+`~LKO!F zC<3UPbVF3JT1greDFIV<>hzTML2;IjGcJ6OoxK?-c)1^X<$_OVCfPEohr8z2@Tj+U zvRbs<6k4m2K|7$f6=8b88-TWVU`2UxaTbaU5U7V{tsnGDfX%Xn9&gLtq-BGXt)-5gVdff+#I%bDsUFjo0*^-@*{ zTz9??o!(IiJoZ>BqOB7IR$%i>BDiBty`&lm6g&<@JQs@uGk(O7;KP+Bn*Q}~>M zlmyLRT3Wuh&eOn~h62{TQ#|p^g$#YKfw6(45_)8Zhx7hu_H zRU=4=!BD+YoaJegY=*7%9+V3h%D@UY&PIe(JZxG1rB<-aleTb8)riNC(pe z-};hFs6M{p=AL}z3jiDg3`YijV2W*2PbP4J4gf}g4u*1&4gx#|z?+!<_>ccMN5%)~ z4-;czE70}`qsd-p5n?Mp783t}v-hVSVE+{n@$s|C+K{$s z9w~UJ^8-16gYt=1BGrelU@B_ZtpQuPJfk~^T@#}*6Y11yXwELY`P=#;y^}G&Zm^+G z+I-Q`r@Dw!q3i2BzuR0pOL-PClk5`yiVVB%WA9^#aDa7s==>VMi6wc1{eY> zm-P#-l+s}^k`8b~-QL;eLOeTZ80}9USLKX#&fk@CJGexed)4Klg2#jELwmNc2u4Z) zaFr!=K;s9{AE{MZW2;Tkhg_#;4SUrw8bvBCR8~bdvAGf9y|G>d;0*%|FZ#x_7QDlUJD}R|6053+ml0N5MNH=-B~eb-lv=e)!>FNS zI$v?3B8X0v(O)D)hU~FiGf*Rm=%_4$H93L~YZ@7J@a0&T$`RS)LB%ApVuEX{kUBlI z!xkulXRr-m&WmHgXHf!>3+w*4d@2dMu41l?r)F62&t{NafCmE#x7)+KUr2FP+t)0t z#ZoD3^@PX>Kn`!BgHUj}$hO0_<_49|Q!=n{i0GqeH*O)K9N)^=+TyJc-sunvg%7FO zVim7jb?(N*@hd?OsA{kc-~p|L-^C=D8VhosGc4Twzw-D+7!246DDwvZKC4R(2`8hR zxC4NrLgRMPqgKEO`WtWj2+X(;bG(7&)gKqW_2sMo`#*SXh>?Wi2HJ;e$C7NyECgBs zIL3^an{J-O%kJIb>$%k79QVcEzsc_22wDP&nlMiiBN@zr%8F!zE;)cZn=f=u=ajgN z*^^1Beqj*TMEtCNk|bj^aX7cM%ubq5{l%g*&cX0nP9EcHcZ%Rku;t1C03ZNKL_t(k zw^e$qm)i26ya!uuWv>D}FI@cP^bgsBdnjQiu(7eeeAee6D+n||JIcW2D)?HVEc)b; zHrrc6kR(F^F69X(%c`iM{e@goVKtzhf5NenMWTwFEnMO^TRG*U>=%ExR_A;lK>@0i4`Jq+r!`C8f$Jl)duS znx|9w=$M0KzvRWa|GEJt(qV$Ft!Jw=a#lYCJVYv7hMdwDwn}4-o%oBv`+hM{dM147 zR$6WUTGB$11w;juN*I9B>>vZOI$CU#t{kDlr4QzLw|LorsesUFg3Ln@1ewU#$FZe@ zlcD(7lPA%hu@!&B;zK+ED*Ztf9*qL3Z9wrPA54JKlxkp6VvC`~MTwk9r84MXwP2A) z4FR!KEn-=UTrZX)C9sQx&sOVQgacip3d*Tcm921eWN2(>T$0u4_IQBXnLl_lVx$4t zs%r~Ok4hhkizBvdc56ASHk^txAX(%w8-^raEEKe-|GEB*NdQhcwMWsoN#-t318<(c za3S6)jD=YnZCXrK)23w_KUE(P8dCcGfHFt3eTtH?aoHfvwarI>*D;(v_swU~&C@a` z;RT@c2LNgit6hv9k_Cr%Lhr9O3>+P)he$bb2LStGw=bGd3&WC9fqLhipYcAX7hZV5 zJ@E;641h;fR-U-Hdt-}_hL8@m^|w`dyswcY&9B$xpR#Fv7Uj*8j3Nis9>jbW-3z?) zCLdJmF2o{NZB6p9Wp!j>$$@aTxe&?WYdw(dBef@O)L`1=ZL%4|S$J0u4}h9P{J~TH z^rckI^G+UL3Cf!hc_nDreFMxLW`6ga1g5lUm`dU#lIh?WZ9J^X(ehiqk`=wZy~ViZ zZRK#aTaQ+uvmdI~^k}F*t&ya?RDv8NWRz;06N6Z8Z;1_niey-2R;WoeT=6u^<*erF zpL!`MjE;Pk66ZdPz+LqQHLRz8<$?5=*02$B$%|lCN*BYC%Q$zC3fh1kQF<7|6%9fOH@+g| z|B&}Vk?d$%k4PPN8{#BASZS7qg0LECho}rH`Vk&#Va}LTQl_J5S(xGReCJMHHlXHv z_wMfOaGQt3loF|kB2<0TiG5w%Y}-+q21yFK77&`s>Y71H_qFa~HDf~{2=%mM$zT$w zY(d3es$$dyLsOY9RgK!L2bay!Xj==t+5tGaAeeb=KQZEP&3Q`Ae7RqLr-3K1dTPYb z5j7dfjiOcXE)sHY1B0=hHGqMB@GO8*QN>!^=q&4@LRXP~M4{D+a$6EEE-R7)KKlz# zF8^}#Pxv{l=__#=cF<9~gB?Elv3iHY1E<9*?oKqYdp<}o{n{nbXRkQZ=>Qk3DzWVgFzjBNw&qCi zd6nqpH8QFv`K>YrKiVcc7HZ(s!_aP+SuovC{-svc{$$41?-e#(5<-`1juqqT^igEt(s6)#7ft}i0EiX!M(z^7SO4ok4&|GVRXgABcEF$s_rYJmB~@Zx*obF!Hw2LSh0 zS7$F9FgFxXZF!7>0@^YwGe|VQl&pItSc&!yzoeT)|2-)HU@HY!R3{2_u&z8deDRj9 z$do)__@e=^my&8VTd&?UMjnG)%tZ>lEx>d&`-zdU8#=>W?l3qei$7p^I)sbra;tYm zU%03x&xUa^UwrTv;)QZi090cVf|MHTKXw|P_01(8*d)Sz&_4I zzEq=TpMHpJKPyjNVooJbjh+u==JRK2qr|CGKmPHLdHR&%{7}Ac!@iHIA#y*RrOpr~R<&tONzs~>EzA(B z(?unYiu-6@V^bo|CYv29!f=Eekn_9L%1Us1|LyIKbLV#1 z5u}tL234uyj!unAL&UR!iwhqZ{SnJ~Wz}%nHD`Am{Yxz<8n#5J+J=xDi?z-jnXVmohn`0GJXY9>3lFMm&fhvB?p8ypHQDSy zbcahu4%-X1S#(94%q23)`Bn}Icq)}{;uh_?O2f%uZdT#akX-2|$+1O?vFkir~T4mv>W?Bf{pefXoQ=ukxWddyJV zDy`?zS5u?lARgE8J`n=bHP#bKiiATEHNIJQjzKGJ0|k z04kvS;Vtg=3fZvu@>!5K8YYojJ=jdJYEi#zseUbb#-n ziMX1z(CnK_VrmUosU^^m`atPM#RKunr)yrtmU^Z7jfuN%djV(A;-KaKjzGiE4v!rzI^l86! z&33`26i=hzh($S9`j23_Ke~I{?3cmxjdH5bKMCM;ymaY$XVFpr^gNB^o-r!uVOc{B z=+4tlA;SzZY-krwi2^NoHHmRx_ueksKFkE1IP-cbV`3-3-knJ$;@T0&BAB)?w}T&) zIbVn(iVAbFdk0D-r3S$@A%L7hFzCT&83iTT^z2Kp=1bQShl;Z((Qgx)q0T?82loIb z+7VC>iTQVkl>QQLJ3<94?283B9HB_92EwC6^1Pz7M$@bnX%KSN% z&#FqVVtN3FIZ{Lzbv*G42u!nVSs-5kdj7Zl0I(MxPrwU6Z{EE5-S2)6e+YaXl@&bt zfohdHC0+P~)XsIB>yvc=kS)l>aT{GMMIwj*lttpHU;N@1Z@lrwx4!i)`r|ks1JHF- zpO=BIKljA#H{LsY`8-(*ihrHXn9!UL=hTqKb>B^GpU5P90D6GUZzj|A_VtW(04$!N z{{X!q=4?mt%<=o$J=gE3Nga)pieSx-%!5HHuF4Hy21SPzPP1fkCSxzq+MQWLsoYNB zP^iu*c5rt*Rf{KPcmQNTo4+yQ?LKkg#fZEVWrfeVdq;RL8XWt8_$DPvR1=JD$cGUw z{^o{mun+FYCe*73*Dz3;wS(jtRaKv=?g$OBVCQumM87pDy)3>XqCztFey3}U z28Qi0uzTG5zRfO~8IlN|%=0qMDl>shjA{aC2$Oq;Qnr+G05HbM4JeHo*E*xanx+hS z31*4yjtF}r>S+fT)}s@nC>Ha5Y6e3*ME^Jo|gMy*wK_(_HT~+b@sWtz%(7<^kqpzVDx!Bvb}~V z?QU?dgi}3A%fo+0y+TBP48+?CR!FfX1)$;`%&)m9O=Vy*b5LpLvvZ`v8(*BYXY&)8 zutrPBFxI94;O-!O>ll$7NN{o|e)O3uDj22f2}0%w^vLOe*A;n#aC;7(uTSzMJkJdh zq88l(ShB^D5*#q(IPEl#rSU)xSD_i5Y)7>wt6{zYMXbtjF0Sq8JQTT~>F9bXFD3U^ zHMvyVhdN8TEeT~PwOD@a!uj8<7l7Vh@6o<}PL%4FD_5>C+vKLu`TTyLg{fg7P;Fhh zkMFpvC+h&9qA`yYaD{Rp3#fpOD058q(n~LK0C3##Z1$HfoxAYQUby+n&-Alzphc_X zADci2`ueB_+=o6$ZXKi2t<@gp)79=1JWAlOF=lL)hw1=XrPvPT=`7Rw5H<$gxh{su zHk5a)p?3G8tf|q*+vsZb8vH#oS0zJDhEIi=;yL-N`%qGVk-={hd3^ z1;>rc8jOcZEfiJjQ>_BC;Jd$EBdXCKSM$<09xBC@-qjvJ0MJVLp^Z?}OxZ;6UyZGr zRiGwO?4c;y)Cm9vQA&ZE=S?=mrWs1BDlsodQ&e|Q$S?$Zkd)w0QZgK5>bAR@Uf;Kd zjIdofwOnn5kCA=)OX1jFQYDDn+nn^~LD)9C;~n|u);CB2&PJ&&aUY`AHLP9M{$rGO z)h*<}0wF0AX4xur<*pJz{ij;6N+yF7TT>ClNCs4^UV+jPMUkh@^rgHEQBY3rNa)`BJNribfD|A_VYOc;3-hf7YQC+Z(UJImg$v zAx_B}{+aASQsa2Al^)HsS?Nv+*imV#1mVWp*R5+DP%0+jF}xMTh^M>TP~W+d0ch^$ z8aT{P*Js16UMZF`$a`1$+8U#ZGd$n-;>!lO9lT#-1Sg)7Y;lOd2THJm)R;>DtUrsh zu`LcTofPf}wkE*c^z;DDtD*KhS!Cz|1*?x`wOHaa2-bzxdbolq+#)d?hb;yhz`k;b z9{UJ?w4E)oo7c(0>TMI0%T`F?831ZsFk2Qh`%b&WiUciJf>d(Bp$b~DN=$SR-=bPHM;2z^ zW>pGQ9;opw^wM0%0z8Z>zF6kg2iwFlB1WTesA5;~v|_opxAC>7moJ~A-tux1f2a_? zlmol~bbEUn+qG?iuOorMP;69(e_#f6AWs5t5)S|ljmwB&g|({1S@26Qz5J(t`loC~ z#~kQnJbO4E1Guufytli%cP96WWpr_2UT4@~AaZ!A&7uvhwe__Kgijcql}m?iPIKfK zRlC+ESQ-lnJb;FefdxH9=*xZl$b%_cL#onU&Y;GSG#E-*t9Ff}jYX|$BW5)d@1S_C zQRkOnx)gMn25JG8%@9K&NuWeX=c%)hO^V7yMXta~Gx61Dxem(!U8h1p!^`;_&eGMX z&>&#eO5p0+QjlRTwu_2OoXbI+(LZ+@=mg4_`39<^NlZplsf+V~@mo~P6wQj&G*h05 z-koU-Ga;GfEqA7+vZx9k4($aq^PPI34PJ6qJ*sg%+^T#qu)G{@QkZq|Crp z4f?>M^Oeai9g$9&kYN&CD50QWCkBEc7=OE_(ZIQ3cB;Cim6A}>ak4c9Sw)3%$p`|L zoQqO~h+(srM2t>N@EHUSEN|vz1DJq|n?pDsymXe2Oe{KvrAcJ9s z-%pB4H4L5sU;qtNyERmHc5tOWeWf_ebOCW!e&|rzj%N+cehn?#tq;nlYhykR5062K zjper9JH( zyFAye)C(zZM5d_r@X9bTb+C&o*EyS#8S?_ES!l7-+FVd!FclEP*a|8d%ql-#0Ce_3 z-T!kkUI5C-&$(v0fWBcTj^ouyc3hCXExq%4z4c|Uo{;s4I{+Y#geIF_%dNuf3tu7O z1Ya;0Wj^`jlgpPc&t;D>${Q~{x%wNA?!5Q$>V*x?0%f~$6Vd1HmK$r^3P3fU5Iakv zK_llf4TeR1uH2MZ0+$X|7)DFeG*jLvMW`*#p{8p?X3{dj4Oh0JS&Nf_cmg|obMbjC zwwgcPO0x?agx-WwukNTC&FqZ{r%Yvk*c-_ZBW0-v7$!@2i=z7u@rACn9CA9!hnr>U zCfOPG$DFp|L>)628pBySUdpy{euF^)UlM(lrId{}HtU4v?3#USc=30)PXBAk&&jT= z&<*V@Y)Qj!&rg+dvV5!9G#1UvR%V(&q2gIv$Y9B`sqPW6nR8U{>831l;VpUlE2THF zRS6d)iD7PVC$=@@uBtGj1~5nsDXKdTF;yDd?GJV_BUQAkeDTE|RlD53QmFt1hl96P zBn6A;S-Ly9)EnhXc_T-Rv?;7IC>WvuG3+`jia0cOUdi!7z3C{;>&HTgT-+;d-ATJV{`x zu{3E3!>Bf*F=_1{^pF~z6{VB`G;EMrC>y~+DK!J%M1!>t4;DEFS2DG=)9$cCRZRlh zd`dI}`CL}oD=Gi!zSwXfuK8vI{2T78NES2-vg8!9Pk;57zxf*6jdk#l%dT)!2@t6> zT!r4?x={DX^tiLi*E4u4*X;FK@Xp)a;DJ(AIhfqgtzi11A&P(KnzIES0Uwm6rs@d= zU@2d;AWp8KoT{w;XGtUtCX0J}OOKtq@Vn22av~PjuV4TE_g`Wppj5wpKIdiAx}Ui2{1{6hMSW{-^mz5$arz*Wy!AbTZKOQ~Y0t**ACm{iD3$$|!zG&R`*frCPc zz5$0-xC6znSjY4Rh_lE&1ENHSyJmoLa^fmzE*pz|Se`tQrR-J%Q;p_~q#>0WmeCqK zK6O&np+U_EUg~6be65YL3&8~0T8mnQ;H}o2-aE@My|H`edaM};ZsO-Ipt|l954zpN zMK%%TyvkI>YaF=LU$6cwTzjb0um5mOAULF8bde=MXpEl$muYIRAe0Iuc+tHe8|gDv@TRq>jRM5c9}dP-Nf)*P=Ggxs5I1U!!j zsY#2O;DVZU+Q6$wHa58DE$8o@BY7mez2hx#oS*Iv0#tj0=w774LGtG9+hs8bpLgW6 zcjhjU1KZbOh(lOXO<5?Ix@If`k7NZ;Owq!iY=Fj$1{|D|I6h&}z&cISCuW=Vb%T@RDE&@?B?m%Y;3mWvnZTK6P{x zLFi3KguiM9Vygj-7uy{LI42s7(kx*{OEt`mfK<`PvLU(~A&-UFc}K({w-U{}v;izj zNnydwkixA`)M^~(45=y}4MmlstJFZZh4EM(#Oly+C}9Y)L3^n-`dH|bBY>)dld*O$ zdZ~M2IxP~3as`CDM!l}+7U;mhh|m3{zEe2gs!=p?FNrA9DL8%k*!ueB?OWUtggIWP z%SS3Nt}Gi3oSM8Th^4CHM_{TGPm!t-okiZRM~PX?n`E`FYq6#J8@(*bN^gF{$F~Yg zSo^{j&3@V?(yEA>@@)MpPp&=lD7fqxyXbMO(^BXvvpDEc@#}d? z0oA^P%*Tb9j#`e(gM)Sn*EpOyD!6m0iYc#A`_6a%9{h;=oadp!*&=xV&&A*W>gnaP zoF`6Bli4lXYc*U7z9l{Gi?2<_RaLXhRw197T4gAaQ(VVKLMn)Y=OSR-`k-bx80I5f zGhIAgZ7Ec880bo5nQi&a+?-7@i=_NYL`?AT7ErQbnc2zE6Zvr0Lj5(w0i~i2ht8Bj zb`of_!&QXH{y^=#JHv74t}+pq=%qf3r@;8<{*P7O|Cl|rQ}x|@JDXcPv&*+&5zj3> zpn<)&Czv3~=%&Q|h9~*Bpa3Qob5{wc|Haf#ZTo+vmJ10gR2d8zhrm>ch%L`A3@k<* zFqX);Zs<&X3l5dol{h_7f_6!DL*b+QE`H6m6cA^@&dGeE(P zEKaITDuPBX{31(dF+|~56g4ccM=~^J1w<0Y9*)9<;MT-Wnq9R$2gfu*?9XRoCX^?y%RjTvMkm|vnC)Q4J@|TWIU-LO$;i!*P2~rnTR^kIMv27-b zq(2=KhN4PLdleTJk!4G~P^%4LkXj*P=`$)!KB}|8GbQC!B-8i|M+<7}C&Oshu$01~ z6j#h`P}q3)CEHQk9gYMjURg?4A5U5em8N1UI9)Gux>GR<@}&Yec&Rgnd2OJFM5gtn zMP*GLWVeX=&5#M8F>CzKtPZHzgvH-zfz9&=ejo5e}b|`K6{(PP3M#yj9!E(K2qUxe7oqiBrWY z2#_(K=z{6Rew=NmQ%7{<=njF98_+eNtooW|jR`Q!EVPbRRLOa_cNM|!6I!6z1l-Tk zjor3pkw$kYYpBK=xcawu(C8W?*#`D(_~B&y=Rq0={*=TV001BWNkl{;GES3`GR3A(xEe5v9vHM}*mw@eKw!5f?&^BG#OaDV&^J1wR6!X==FGA!5F|RjmIGa36JZs-5Lur&eew5R5aky_4k%INA(b{#ojUb{ zAN+vJHFVR!82rUzzNEl2*cMq%O6{ay0T`hqVDq5ws}>ud0|QTg5cIj=aRvL_H=bU7 z`tscmZk#!{##uvOHcwhRUbsn=li3KIQJFK4d*%pK6~yh@Jt1d^+m!!n4iCaD+% zqBbnbMr~7Ga!FS`?92dGQpyVzM&qbLEG4$Cuv$z5n`m1pyQpeLm%kI^LQ@3}C_&4r z1xfCP>{2sjkW`j{iJo*(X=Yp%=sKRWsLYXnMh7hK*q?GgG%sO@w;J&-5x$PJvcfq| z>-y9r}+wq81_qcT8PCN=ddW)}FDr#7be4DH^ zRi(P*S(aBN!BRsekoY(s55klMBq70mgt{Rq-9*W@ zE?!Kbp$p#1x40IUn(}Cjlf&z-xHzU-d}`GmVymT@H?-cl%gcXiDuuW8h#sf^c~@h) z36Hz7mbP{u$Y4i276u}7Cf%d)IH18;N6NsKT=kQ*SmTS`?}>K|$$N zqlLpoyl5g#3@HbpQR}+GtO&O$udkWyHIL`~^v({)02>?Ud>XhSH{>Hdy5`Q#-K{N8 zd6rUUMQi8QsdCAyp~}U?E-nPBYRU9a$Y0C=O*oihYXNF9uF@g3D1N;dp2-mAp-N)U zzfhW$;Y?0PX!S>6Xq73952 zya+~@h6`xbrk7Q@(dx~XHo%A+rR?HzX&BYiK^OB75lH(6Mnt@5grRw+5HO|n0dT`; zw?@^*abVR&8=-}nzrAX*=xF+B;5&wx4G4( z!dtpT51mcF2=5V%+fihAN|j%J^pi3<+J}$BGY+@MB^diWGr(2oa~rp= z^FR(5XB55?bY+!;wgpdYe(b|xpaa*U!lHkbo8|tTW5(P>vMNUa;nlK&S+-!w^eG)t z60K)&04**Xpf*d;oMJe8dE?^mK3}+#u;eWB_rCW%lflQsxbIg_fy=i7YFT(-o6^f1 z?zm%K?>}}qW_M=hM_6Y#!Gm}(g?#(%w|?@IpTIMFbHYC9hgD6luRL+-+;4qpcWb9c zO}gJ~&o)y@!7}xZ%*G?Kwu$bnuiQ-%YI5NNBmuA2BF zNl&^qRPfm$^)^UQDoqjyi;Dpbe?lteq8Cf~FFwB-8zi|wg+(BH&>&_LCMD~xG z_Lt_2oq1AZCvjSYspb5mGbWQb6YWARx?k_=_ zB=#D=%9e1M5>vIO0hy-SFt`5KM}L+HqN2{^Q00ko{Nsjdz1C30CJB{n*Os-E3J`bf zFmW$9I{c*9?yl$c9V{A`{aw8VXClwFg4a8Fm7MwYlBgj0ckx(5TpwdAM8bTJ+?Ds+}It>O`Vl z0Uz6PctFyjtt%K_I3rM5S4z0X$jp<2PdTE}4jq6xTjtn<%CJo4q@F5h(k%5MYb~hC z5W(`iAzK@ZKcaFNVvEg7X_ST;=*DPVdp-asC|y#ATCbH>nL`xF*1Z69-Nhp>Op6-J z7CMHWa&$pkm!J6|2pVPGNOlv|wt=?lh7&fHRJ0!|bBSW9~P6!)3)vKa<0 zN_8$y;b&0`wuLzjUH;BVg*j2nlY0O_m@1S21aZI>z7f^|fWLN)=QF-v49C25K;VMz z(xpH63j0`ge&&6As?45h-rbA8 zzWOtqd|tqdzkE=(3H0efv)1Pd!?b?)-MBolZZp%CTBd37SZBoXU&{gtIq(9l6gYKv z^hT&Uq7p35H3?!rb!dlRs3Ici3Zi7ebR!Lrv-1OX9pVrv&*hnw;$b;4n*V(_PWvFan-XtqNh02S!}H>{H)MH_K~2|>zb=oo+t`_ z>So~yW0q8(7v3ODyb-jjW*MAVkYJ;ld{D!{sI66W!_xAnnUPqo4#vhd!y6malmcff zytd!_{$=4#2A&2kQrHYv3uL+gHlCB=lqdee0FiktU;Zi{Rgcn9wPu;-XEk^LW^7t& zzxXpq9T;*T&^Bbd)SwFtAvvo|kdex-){uuj0DGAww7!bOk^yqx*3h0FY^ zMapX>4Y1u%tI{#Htc8yaM(hYBJv89XH8DUMzffy2Bo+;H=3Te4LT_Q<1NIxLeCPIj;Y50`a_w=-!iTMEI)b4ivZrFH_MZ@y_owW zV=DQ3{$RRyQB{Hw#lKjQm%olw@+U5iu9F1s^neQpU<* z-ND^)UZ698?=wR2rH=DD+^5LQ9=!;_6Y&B71+pXet`hBKAIzz~B@P=n{jZ5ywjD_` zT6+}v>sKzO)p4((XT;`Ed0VZly>gU@K&D_;DOQOjF`I?06UR8#n1{%y!`V8b)XtR1 zqxQioP>D3Stjmc7Q% z)3T~AO}|*w@xYBI)m%^-wQ-~mfy1f6`xMCP&n#rlD%>rq zoGPen4wWxP@R&k55Fg9d*r(95^Vq2XRJ?2B}!MqY47`r9KD?xdd zY@9ZBQ0h4r?a10Xux(b_lS+(~`jJafh;Yx7c#<4546>;y*lq1L%wE3qII;!v1cQmCLfG@~wvR?Cag2z024QtvTgJwlp* z_sz|a`jcVAzcHtoURT^iVJRnqKKr}8OdFWfM}Jw4vCbySvfz_X19RP=9t~&Da@V(V zO%e&qeBfSa>S!8$lxEk4yRXU3%kJ)>8oG#s!$F2lZT7Gtrm)4`q}<9WZB-jRm%J{l zUHbj6gb?y^FPL8P{vUeE;j8;<08ku>&{&+&M9*^o(^7HR&p#`TRL~w*Xyihl5MmIME#0MB$SgA#mUPexgDaWcVSSp zsj8hWuYS9|Q_2jH7${=mF#^YqF_oF3`h72 zt)Aj=*+W_83NP;0>4SU_vtba~m3oqhj$p)oV!ViQqfIA(Og zQmx5WxY3jQuG33m>k&P>N$II{cm#g}YJLVk^8jpC!nCT@)D=F+wk7m#=17}(e(o_&^>EBGg_xK6h;T8wX-mG zW=+!@XWXB2r_D*cp#f(ao8r9yN1KtffaeU9ob@ZdPLp7d^mu0kP-qf~lH#{>PoL&_ zb7pw4E+@xPzBeYMEQhU9OI5(bfbd|VTkKX#yD6qDrHP1%FO32I#xgjoITe7SmIm<^ zilU`DI;bT`3C9?qf)%hh&O{3>&YaoI?B zyU%feN^eDWPpv)OxqaIwN*AO%ThlrdF2NaM#u`xn_LUj;4BW@ms)J$pQ+V*R2qK!i~qBQ5OeR?p3X6dh1O`R)^#<1gWdB8<>!G@T5)j&x8xnS8! z)SS*!q3e$3?JS+|_Q|WA9pCw%ewsbaZ9?rjzRLK#iFcL2!_CXtPvkm&KU6528f`ya zsF^{0Wzk$h)hIQRQq@38vGv-tPJ;~7;4IpKn6}o!oM+cZB}# zbGd0NStFb%*9Z$Z|J4j0`2eQYn6XvbfN6QvCllc@sS0ov4@R0b{je0?Wot$!>7jxt zAr^0W4s6jxn%3$$(=Tom-df1(P|u$AwtkU&+%Cm!KFm1@(!3+ML5RDP;XoP^OJ^n2o5;aK^e+qi6uTJDQm^0u^(T46(lx=0%ymUSqfzo~ z?_B=1f3puNmsO;ky|0zh8;m4r4G)R)(A+*+k*o;@mlx_;f_*K5d9=EMh?c_q${nF| zM-(+1+>j@K2X_&|5=Z5A4pf-1;nwNX_4P55Rut@BZ#@LFQ2SDA9+4@2P+O zTVGyz>eB8ecLA|aw^fxRt$m$Mp9U=s7NwZyT*Lc=$bA@P_n~G8sDn6wp}YX_N0H{7 z<>VYQ;5kXS>QM^F$TerUkFCMBFE930hus1iHT#uClKRGonr6BH6WCUFSY*((Y7B_4 ztgi3|Pr5Xcr?~}a>-JVy$4)m2PxFQc-kG0Hanw#m~NoS(m+Q8UUucbwnKIyljBs1u;7P z4Ch51L#Fwn%9Wz%wfe)^fT~6_2e1w^!8D7&aWBrqF`=2BC<{hbHwWay*pRLaQ>qn{ z5gBnQr8MB7JziXMIpD^LZ0|df#X7mr>$COXxlgCh*k@ojpuhcJFl}~`VgOqh8!Gh( zG)QuW^lYb8O2)6?u`S%~qrLkMwgsn>(cUdgwdiYByY;L`Be{mWzLvY`oR8vc-pa)> z#$l7XVPF=?NG&X8y3@B+k8DM#mL&svruA?=(`Gg3eb4}bcv;A@I*s=%F$~{3dw%WG zAAU`tmJLJxVFP1F`SQy@V3XvS4UFA8z*ANjyUJsdHV37R(_~1ZU}61 zJvD7CyI0@&$_pQP*S&fQD1zs*UfVb7xDG5ADyC^YdRUM# z2e{*h0m&|7FKZpRC3CM9(~^BzBVa%6WJPt-Cop4%cuGf8PAP!<)bKdU)XwzxxzTBIWj)goHxBd-WC zC|fnm0+K{oGE&Q4k=(x_FtuY3gb0pVI&#N*fmvpe-7e*(BT2$6j7tlS!lA*W(%KS$ z=U=mS2B@(Iee?Eh?4+f~9d_QM$D}$C%@muEmxp;m4*Q$+hX6Fh{dD}F11i>^R{gAe{dmA;F} zucqF8BR)lv=mUJ5 zK^d^*;Y^*%Kh>M7<^RFgI{V&wy;2$b+U4z4ozu^>BDT@aHC?0L<#GN4$$gmUnRE5_r5)r~W z7P(MCGr451s3FS$G{q)^9aC;%P>u31$f5wW2j(EZDa=E20Bw!2r4uom}*Wy z?3i_w9ae~V$@bd2-tznhe*)&Xqq!tHi`vbbH^1u9lre%EyX%eop{4 zdsA&fpQ>9s1~S$w=)due|BZ71Aa$qaSl#f1K(RpRDIk1y0nc$p;<+Mgor;Pyp}<;l z#LC-pjTL4Oe2=NIoIvb^*^J7U*?04b)kYxI_7!9(#;PPYI}ZqVR1?o8a~{pvF+fyq zp`jyhGzIetD-ks+>T)S7c%~UY&yHc$eiNH6T=m3)m&f{~;-^1(BqUChco5>+)r*&O zA%R;BPQlDMCzT;e&Vv%_$;ho@OaW1Jp-0J{cHP4oW?jrNy<(+V$l-KrLRHoYm?Xcp(Q@ltQkGAJgDxSi|D|+K~RV62P$ua8DMd&8AWAqnC zC~b=#3WQ(-9GPlN159T6YrMBnsOtWw(5e7txQIVjby3ur*k;8)pSB+d3h{4nC@l9bY zAVET6{e@8%0)n?^Ww}L!A8sSy#u}b4lA50c#gCUqUgo5!+?jx;l4@T1qpjcv~^r@p3b(`{+jUR|>tE4Fkb+cG=n$%txa?zuqkH7JG2xuZkg zMYSWTmZ_N`#6C5-#n=`#Y{;W6Y;7!lL_oNDokuph6pAW;wQtxFr?q z+C#ildgTfppWwY;@cCh(VCB7jDESxegy{OP(5%y#Ia*0gs55Id%1sM+@&TkAOH^f#lm+2 z@M}lNu6JfJk~|nh6^t82!3oBsg;Y;4C!I7w+A|+rWI3TEW*H| z+*1@1=0Fk`K2bv>`(MOMSjg)_@U=d@V2-KgtT2I!CaX$dcEzHer>msF|76Qq;~2K4 zT#2(^uP-x*%SlRB9Yr(#2TC%n{9`Y(xPuAb6pq`!0Q;maFhGj3D2h9C?3t|6#@fZF zN%0}k@w@{*3CeF6@M5UDW$Bm^in-R;n>Dx!6`vwr3AScaIS>&`%A#+HTUD5E&~QgF7B}; zsfu4~gUkM>wf1nHrU_Z$lMZix<%OU9=imL^FCE^%gR@Xo)0i{104-oBT74l#6Bn92 zm0T6~Aht|vueE0KQ&woiCUQ^Cn|P4#(oZ2e^DdVu)m|NJ-MPy~=KygQEB->jVs`Du zwb%dQUgjM?&4>ddyzGdL8lL>YMLR5(5e~m2%$vVlTy=4jW3Q0pjGaIpamIEs_xdsg z0$?sMxwPk^6UqRIBGjaqx!ND0B2DmE{dF2ygYB{gOo>}+WWx&I7~=trj$ zy;Ms~v>ss;Eou=d^;@&GeXW`N_5-x+E?g>~U5>l-YNV|e7UajN*waFJjYS0}c6_d8 zl-ZB5#Z(^g@ejOK1ZAEJ#|!j_hnMgsY4TJ{i@puSP;E;}?edjFoF($yJN7v7;oV!8 zUVL%Whc2rYb>uu2mEJC=U3ztZtri$xdoM<1G^_~tW6wRz@BdtV_ging=j|}pbreF+ zaKZ`IAOG>^vAM(K$IBsariA4I?aPhMCgMCP&+iF9@1?0XQN_N*0E|Q)55W*W(fswV zfBi52@-N@}-uD8Ay||HQAjDe!5Mn_X001BWNklGahenz{-^|UuCa<&Me$1jw{^}Lzm9b4`4?Y! za6g_Z!Aqrj`XBip;A2E1AZb`|^r>Q9 zLFgR|hEyygH=+_I*Kkg8VN4=MiLBDg&J5s?BMK$zZL?quG>#2{opu3RV=`lVd-L_1 zSPO8W+Bdui0X>!fQYn?OLN3l_*{kq9GbSipn=JCQyhoYh~$bvt5 z1v$ZIJt?U&DT5g8>YlaBF*GgHBhZRi@%MtaKxprFyBd@H=9K1ob2zEksni}ZbUFH8 zql4SOIDF(=8!_S2pZs(WPMBK*X=_y>Pg3%?HLzQW6Kq~(;Cc9!H}Rm+g9rC_tm9l{ z9y#XY3lpLxkvN9qi6BoW%AC@!s5OLG`EiXC zcY3`!!1E9I-Z{3&xLk*y?T;B|Ys|dz_%4oZ?S>^l=Yb)B ziVTvw#xa^O$EwhiJ~?+g($cyP$NHEM+!Iy`oExJpU1{sdglg2L*E;h}*eNPc6!Azf z0PG{qa$+ra46%GxAMzr?n>@n9K?$Lz4q@zCMAJDNc{<8n$VVzC$~_#%M{bg6b=Wjb z$~n+npWr8{cB)Moyc=<9O3B=mBNib`7B{1Viwu5pb&hQ>m!eV(!Y4m{o8l3iO+?ddMx?6aCQ2TpA_%uXZnmY#xP=Q8<%#ljbO*)Djhb* zZ``~Uy94glC9tWn7hh-BF3CR65-XjjI;+WW5W+=7$Z1Fzyg( zT%E|F7k<*Ky2q4&I5-j)=W zAk#uAy&0r&)s$G;x!W$~9)K+3CDAbVtbgGo)LsgwXZ7nrI9F zI(Da^fd~zck7C&{L%d_nj}71mFT(+V(=Z@R2SKtmJu}H>T$_HZd9)sg)l&n3qknz+ zQ@i>@eEO5W9l0?ck3SCB`}CLWaT1Q34{?~Z5pJVp$3ty6o2S3daN|N1BQ@7`xV*@n8n!xey=aBk^2 zJB?{9tEa*CV~(SG0AKdRy;m)Vz}~rYms>Sn)zCg6{W-YW?A5isG{pW|Ltl$_EDBsA z2${w+mXy@@1%~i&5x#pF7WM_9P0Vdnfr3e&-6n(;jo^_yN znC;o{;Mr`okcvh3pinhQpsB#3pYH##IOWPK^_Y!pTDCGPGj1g884S>;GTeEcDpJ(w3_`I~&_ZPnt_pS1fnd*6S2`k& zb(ljmQu7Sfnx-Cro)K%wsZ5u-MWZ-e>BD)S!~jj%2f!Lw*+0=yU1?c@tAtjzlw(ro zXu*lQxzwuL`#^w18F(NTv|D)w{YZOM$rY1EADfrkZ{qsG^&2;Ancd1`-+ValL;Z-@ zauIwQtwAWh`l~~5bTZb^cwuI27nWJ%>b6v#sD@Y0aC_nI5B?EeY^YvEyK-5-yLTe0 zCk2kQyu`6rs@Yom@dGSx-oN)#zwt9j;5GQ&lj> zwB!g?6ond^lw^p~7z>#>phMNgr1nA9iah~?!1=K`c(fW8sSp7B%hW>3Yr)hI8pMc#YgIvfGxGsh7VB_;&Nw{CN3 z89ZT3FXv~EfeR^di=kgY43IXeLubhXV3sAICs(H(BZR?2H3+CXY9In~!ht|gOwl>m z%4>BC)uMR#kgs1~UvN=36v6AVH_&cQg3C&i3x&DUSrc zFUPNPU5>AB@tp%afP@bl+<5VYGd@;`E{)A|4geNcN>NhA6n(+pV0r1^;aDeRd2(KkFAgfFGwn?D}hilGH^>94UDV#q_HWl>vHig`I-LwNcK z%rf+F3I}2a2&&~I(l~O`vS`FER{9?UB;rKw;!4mmCDo1*qSi75!4C4?tv9rj#yW|w z{y+c1^Kr-(2X>~+ME1wsm2IWD_C8`D!m$RTSf;s1&wL=5#;u#TunW&6A5T1Ax`_AS zxrN)kwL+J{vnGklZdCh(81j(H*YhnB8)u#}oMa%DITr)l5f;v)2}%U^WrOSx!ZAQ1 z-n~X>U$Uf)+P=6O@Eth98W&jL*pKd>i;=F&-I>rh8%MCP95MdPJuyZ~NCDzVaHsU*sL=BL_Sc zN1U*I_OqYUBP2MaM6a@AkzNy2MTs`~d{4@3B`W82I`1a{>NoVZyRyqx0*0=~Xm!n( zzx)-Py1o7FZ--t(cxIze3(d>F{QmF!mp{J$=G}{z4`X$}YQcv%wP9#iqOf9w&zWWX z1iNpjPK4LU{#>lyjEzH#@7t7xY?Qk$W7HQ}7=_p}9_i3y`53`%TuF=Xt|2lt=m zlE14D_awi|<=pDsmcG9`x7nUeV(6nYnege)`wzxvaATm=f2@^20weq;_?##&zWd$pIVY*?Ps4pG*ye^$N_09V$oOgc z_rCXiAN=44A$x50M|pzQ*brR0e&zMA{>>Zz`+E?u*hi)r6EfCV?ckgoNnY?;cE-vBaHoEPdYEzawOr6=tuI$J? z{C2T`Q%!tQk{?2ePrqrS&!6(~=C7hFE*$+gW#X!l78DLu2~)K@irmO9NH#!TCN!sE zli;ct>?oei(wqj%cLH}MWDm5w1eWZw3y@XqNXXI~dFzKadDA(*&-PJDGYA-We0LC!O)(GZOdcpRWaM9#I)s{ALNHCc9Vz zy_J$Z5U5I)?=GvTIuaUJyKEi_YsU#NRrgalqP-^m?k;()fp1s#YIo@v$ePv(lx55( z$H_%hR5rsMgdA$T~*iPiqvO5^GC2^1vn>zaq-60SAOloSOIX6AXkRkEbu&-YXAUeG(MDT+IFwjiMP|(-pl3vuUzCFUcl|(yKmyB85vN~4VJJj zr%V7)+(~dU(r;EpZqp>lYK*L^s((75%gWFiCu|J_u=!8=&Z$_?NI8YJy9Bvg*}+{l zY!fM~QjM=<@CVwc#Pcf8ant~@Ku*7K&c;=+O#(w@tp%fO?Lx2;GT~M2$O*7^aSo(g zO|S|eO9z`-;T%-R$toTzCpPIPE2`4)bBFk9syee}h#x@3LkMvsv{>?$q|@FcZ!{DE zRycv7QB?vFOHBvMrI*65U96WN7b}(Uq@1jFbnBq83k3h!Q5wM@!%>ax0EY%Z$ZnlD zO(>*XEO)Dv3TF}nCdXY7jTgW?>xd#WK-CVjY$^%TTj}2DdL!L?3S@l;uXEV6x@Sz{ z(6O`5k+l*dhntH0Nx@nduwlWwxwy)}6`-bdbZH!J0@!sD)-Y)JD%!Q{xzO>K$R6Ih zg%3vY@fG2bMR$bk$$DdCnlM(5hPQ>>Y_tX8KYa1Z zAzqL4Pl%Z6OaxrK!`I|-^oU6b4EhY50Jw|YBzLhj5}>z9 zf}R3`^@-non^IXH;AHRCt((}+)nE=P^G~#D3zS`N*?J2-flaw1Eh7qK zOe(vh^~%JUHPR-#PcGI&SVx4$Qi!?IrcucZ{E_4UFLM*0zab1SM+j> z42FO|M8uz!P6>{Z1vpL`MoN&oLWs>#P_$>jg%+%S&Jb1XB*;0E-+Yw7I!Tc48J2kd=X0Oq^FKX@JE4GT~c5pa1+9*4DGp#Nu(~ov*(1i|@Pt#+x{E)a6Dru&)nXji|z@z*viN$=aKD zU;Xw189`vs+N+1KY*;6R)qqK_>e>pPm68hlmwrk^RV#xw35>U|f-b?*4gPeF_5vwi z2;Iws0^2%al6vp2A zc;Zxb;sJ@gREl#MJy{KakSa5-G{zVSPSZ-Tx4>N~xoZrTuxYZ=0>SZP%y>obIUJR8 z4Sa}?)!^xWVGtUEWk-=-C6o{_nHE)PLOlvAyU^uM0@T7jX9)GI zF~9;GO>0?9CKT9K;mLo1pG=O+{Lz^R_S|z9aRSUGKbC!55WRitR&0&wG#r)OvP2Yx zKxr8ery*ITsFav#I?_seO&a+s7EB^EMpl##Byv@kU}4i0CLvWfasg8R2BPZ)hGz0I zp}_(e=Cmzrnyj<{ygB8oDgeTu+GSg%hl^p>h*L;$y0mgFfJaIZPA)K654o!OSWAIL zkTnrn)!hrvxd9fS6c`!^mIdxGBOw5XV_7`>!<)STVO_Y$ABcqeMb1^(Zh@x(dOvtwfNGe<%PbnRQQ628OzcC#-1{(vA|Q&}Imp8i~w7nb6PSOd=ul z+Du5%k>d_#PjX^Nx0?0z*7>vB3+cHv!h>OQM0HBQRfmgDf?fkZX>lL+;RReJ#bz#+ z(nSNqPL!gA1FPn2rZfsk(B4T5QO?bDln}lTij*Jj;oK?uikk62D~|@ zQQ`-Q?%loPN8p^0N#JW4f+s1hadV^ztBqt>Pj-<>)hGvC5=sjY?YI;O5zInV6J^ts ziAlPCQo>j6(ud5V!vt^%!GHFG&|TKD-Fq@p8Z25zcNDo8hH9BaK>Lt{j~D-ILixOo$I5rxGpuEZZ= z!!o&>8G72Sfr018l5W5G+?7LJ{>SJ4{qz)LoZW=3!}ovi>Nci0Mq51g zGvzfMe6o%6E_d=cFV$!11fch+dwkuIFm>x!zVg-YeCIoWd1Y9;JY~&|5B|ip_r2r( zO*|bND@H6XSZ`z9)%rVDHmoFVWg0S+eT6K!<_jadItOhLW*cm+l5_k8#deq3)#kOv zCLTws5dsxI=1?pg3%O8E7&VdIINXi*9zC~@)ifnAVCKwWsJe1;>JTbh3idZ0l-U0I z!_TPd+0gOJoxugi_%nfp45cVh!lYWw64H@&w_wm(W7E+CQ3=QeRLPbJRTX*~Qo9(4 z9zNvtK)}-|2q}9sRR_00%0|(koJ`P2SswHQ=FK;7neGn1l0oW+m+-t6ztD$0Wu_Fg z1bb4Y*q~O-ni&^@Y>8N!qPAdO+K(QHB_34C7T5Ygzk9aR7pg51hy%ao5b>-?+JqUy?56-x7_8URuB!UdO?x+Zp)+rxr!^^eE zbh{WU{zTQPGY@hO6$|{wUnPmT6zgEwOD7))QZZ66hrmkeBxcpdMGnfX+vEg|(j6Q@ z8hF9QldcmFyuzH;DIaN3%BzRiA$uT1+#MZacCFnO0r=m-sb!mvxfbsl2sPgebA%i` zA)(08Y*u4vjX%t+q?GGIz59E3h-dM!VPWn{xAzs)%A`U-L8!pON0r+6la&DJZI!B# zQ^X+<*2H`jEjD%d-~ryri9-%C1nXi1`xx~gNOi0ct8mE^4(_+6Y=<{Rnt^Z)6dx(V zU4X0ac=@gI`(J&rDDc_L{rw;88nCaxOB8t12@_r(!#N8!dU(5uzx5U}lrZ54lc!iQ zolVjwkNV7<0AMLvKYhhR<~E_~-Zvgc|DXHZ=Njrv#mM*Y)#qRN)ek(ldml++Bce7$ zS?$SYg}v5zZ4_$DB_wdUE@}PmTTHam8wKLn?wV7Ek(Am{b|j5;FvR6Ksv;;@kOf;~ z1eL()z}adlwr(JY7$ZuF1?k(_&=K4~tPxY{>I7odT?pFn+?GWZYOb*Ar_zKIT_e+) ziHGXY6a(ks2z3rbK&ZR;9KZdH1(Jgt8RA7rY(KadSE>O`6_yqQVo3CvssK<+k?yi_^wuuzpf0;1CD61N8;fVr?$ykiao;BE~(|*3g-%M zYekVejlg3w%`fypik%`x2V;a0i7^W8?lmGpxNkEud}31YOd)In5Z`PCPcP5XvFh|K zRnEgR<(Mt>xY(X`Qb^n!h%|w%rLq_ZTLLPSx$VUC?%ii+*6Q{U=Tz)Wy#9XS0t7r* zl`jM6Iv<|7$|YZ|OlE+Kz2>yTnoCQC>>UCbR;|3*Uc-W&tfXk&0=xo5S@4V%PAqy3 zs;0&ZUU*U&{0j`+d&FX!2f_?N!aQrDSQ$XZA8VlzN=7%FeohQ>0|CiiA|gteqKYD! zLr5n&q`Bs-61i(1p%6fL>jclJ!cinBJrIlbw$jR%_G*;&HRJ%ecY+j~ z1EEcExJ(h)Mm3W$NSO_&2K99$AgurzcXx4?OSf=Efy)Y91$bp4b5`cGb5iE0MK`$H zVkwoXOu`e{G8Bb?D78j_CA$TxOPAve1UCWD7Z(n2c!~_s1CYfW0>g_Xb5boe z`CtF_U*EosEgU{4iCs4*oUTB_yB2Tq=pP!K24I4|CSqX1js{duHT6dd_H3L0xGOa_ zy`|7-l2S;GqGViu`t+wi4J%&dHcw}GsxsCTtW*aFFaPof4qtoW-t9YB9ld_V^1?M4 zg35sBVkB#AkA{~dyO<67tc{q1#(3opZ_us&B8N^1Pi7dq)*R7 zya09;Z-7Bb+PoAB$w3k*;$D(-pkn);)J%&DNu&oCsXa`rhRqn%#1!hlN1e#ThU7v9 zF>~LMQwql%i1NZb`4rw&7&|>Xu|hr(yGqD>-o_N+R4R zj~nF)5VtC&!n_XzGIUo8>lKvWM$h{6s?R8`C#$gd)Qs7PjKP?YF_xqPpaYIWdks+yA(9Rk$@fLk%#*x-#rk-gSp7P;xMzA#)zZ3Yar}iZBZH|7?S8p zp?-_R!mu$AZUxe-2xIj}hb*|GBFzAUx*f(X+R{=J9=;|VvQ%ZD-t5sja#kzYdzPbB zqY2qahVWnMN&{y61%r5ibTkHviSEmRAV#=CD+JW#RGu+cktRVKP7RcTW#fdbnyt(; zF79Am&}&5(4zC>6mfz8g&xBByDiFh3RT~G;s>pz~N{mj?^DM=HJh2YO!;SGE(8CM7 zV$JW9rc>ZWZemqQ0C(WRV2q>nfyj6fR0Vnu$SDd<*dQoL$ha1UPDAzL^($}F=l|=0 zC9#}gi9Pin{^7@Qn1BEN19Tdy@njc|+%UxW&xH?COw`3W15UAUD?ld(Iv)VTIX@G4 zHckMrD5#64BX}ucl^(+(4N42J;#)vCnS*dHo&t(h>e{>B`ob@~``)d56_{%U*C`gY z$|2vj5>bsmtyF~6X2|&PQ!QM{Hufm8!qB(fCNxQ{wT(aGj&Q;44FL&*GuD8NyPgq` z{}FHUHJCiENRE2yvtt@!DJE^X$k<~UUVS`zQz2MZl%PouC9aB*VkT@k5KzdMNIV*m z*F^KW3m$@?yz|ur}HWkW~N}pdwP7mx{25LDTp$v2%oPL^-&^_uvn`$;HoO34QW!a*+#2}msDeEfz#1H6$>r!s$V0SpF|_R0`t zb#=}`UDsfl2_MP3flq=eD;{3pdpme%xCP94Ci@(99FEO9+a%4k=pQeL%!R${`A zUkE`aIIu?b`S-o^wO@Qc!_=9{2zf@*H^2GKPkiE&$~AY^=(3oUk(5(*P-+4YzBu&v z%>kShcuP2Woo)4*IRQ{Np=aG+ldRE#HHIl+!r*Z(-2|}jnG4TdJ$&WYKYVcb+=F@* z7;A@Cpv+Ax(^3~JRTyGjs#Z|rZT;w=Ft=?`!57+ywb4&sOh=EgkvCFhQanOoOvG> z*9K5VdOdWPiG{D2CqoN2H&G2-4urf_CO#M@)@n>-dAx!Qinzqj%yX7Yv})lqJ5IpX z@RcPL4WQgcb{G__NqAkAJh_m6hYttmWhLvTuFc=!`2IE?_8$VLD*M`bA3QA-` zO;v6pdcut$kyAeC{+J zAHr$Cg`BGvbdc9;a+T&3k)x$XYpTgS7(`>JyM|)64|SIntjETkgtpZ5e}_o5ZGQ zXoWx-o07hdS_H2?UMPF$U|(1jWF2*_Y3Xc;ilX#n#>1ez6pC|z*qh)|DBj09$-|Bt zc?R>WM}6-=EbzTIHkvV1+Z`aTIN2az8#J=q89E#nRz+SDt~DdaEvXL#WWJ)uci{B6 z|3iG+0XJvHso-9~=4QP{9UrN0F*D>HtsZ~5%%WBabwhy_dap5e6iWk3V5Fb6?7 z20S##1n<7xxm_<5KIGGt?95d^@T`Epft_FW;Epgq37SMFojE-VONOC#+Q=&YmcXnH zycBbN(`nzrPKW&iQIu1txjNN@wp`nM4Qs|(6u57iX2hbtkJ~m23)i+JI2@3>24cPB zEZH8h_V@#FWMWv%_)cj2?ytac-ph;bdnR=n(%aE{lk0I%+67OJsPad(uHzfWIBVyV zs2l@6d5^Dh#f{LF!yId_uN;n4a2p$4lT-(B6^z$nc%*aXZ7=2L|BobkS^%6Qed<&H z1ILY*_zFix)j_68sUWM$X2b+N=2M#Jc_H}#@EiC{)J^cOVUyQWt8O1OKeg~D3Z(At zE<200ika%}*a2NH_q9g){Urth{e^4Q`Vfu#E?&{g z@4M}1*lji0ZpdR;dT#{H5gpJMxpmm?ykaJN7oh7!i%qCL{h8+^@##;T;;#2%9Z*}q15FW(Bc00Cir`3TQJnOXAeR53As=JQh-aimM=!&@;DR;mTQD@hk^{wRp2y3((D21c43F^2~|x7>`Ex8_(?Q8 z0)R6C2)mF^Q~xZT0Q6yij1h8A9ohTe|Naj?`N>aB40eCof>ykPgO`5!{dg5PeiayP ziB*NI;%b7Fs(quGkCf+Qx;y4Bo?Tm!U3e3of^aG^ym>l>gmzF|jZ2|%7{rYypBcp` zBig;J9e$hY?M5GGqr6EsDYw}t&?8lk?B^o^j}(^c72Y<(b(ehj=ix)Xu8LPwbxG{e zNa?lRRi`x&kCxYVPD`#M`*}YAdoSF}MrUdpe0X?xjh{Q*j(m6+U0qh6JjAlU?Xpbv zIJI3oyEaY7;T)J<>;thS9mi{bKZEs1z*gi(&>bh)&a%P^&UYG8Zrdj z2E-9Cj;K+;c;o6j|IdHHuH(amPd1Yy*^!U-$aq}*_19l#R{Q|y0s0NYiZ6QOMhZ4` zC}X;IYx8s=!kpf-?ei5$%-@J_zOemOoBt)4)D5QG2iJs&j=5+EqaufV? zJK&U~;qnGv#=?`Ad>n$~ibGOd3gsc`{!Q@Sff&m_IrkHtph2fIpeG3UgrWTvJ%qwo zT)%M*rN-jagP+zwoEFdNHW1%X>W}g8)1T-}99!%-o5JQdzlT^)AmBkP9emW*E;n|k zn`f|{){=Z$JWrsBr%QRlhVI=u^~_Gl+g%s(Y4I#bk5}0U&0p8LRqwU)X-Iws_aZ)n zi|zYj+kCtTkAXUJAaH}OKk=Af<-(EDBfWQff=$z+A9y?y7r*)GFx-`W@xwp$+An>8 z?S7;*9;3r{y5hL}6QB45juLUukHdZ(*5f4#m8j#vQJfFJirWBjV2RxbZwNpSYan@c z06fkFp!dP&6c2hFi=r#=mhjjNPN1h!eU?rD&>{OfOI8ety3T@z;Tzxh#-IM_pTcx5 zZob5daq;TmtN;3E@Tyg|$5MC-;m>-MF9>Cg!16Hi^+6bVE-^j!t z-~C_p6rhfBl=LI}0!121p^<0wqlREq)sIJ9i=k4q4eYSsM zegEW}yRWl1YrCW#tM@T%us5A1}>H*pfxz`#K{280dOWDhRsff5)UsIAN#ngW;in;){+-K^3U+Y!1sQ5J6;8jr6>}j z6zfc_J4!QrRvB+=SFqiR$0pcK{ZGoWrysFEVv~TY0$L`4toyKh7P5NDLMeVcf4m;h zE+;n&@wzH5|MNs^)zeGGvi5j6{AhUNdIauSWB=Vd^V;**r}L=I#$rc!`Eq^|6dP~c z0q?gFHeE{jFP6i0n^873R$4}!|f%V&XT0rX1U*`b1(9FxuM0A9CLZ5@` zGneo&`)qf+@NyWQm*B$^af=uWAr6DMD4y*N(A^0iv&H8YhRghVMUSh0tW>z0i^n@K4yGh;pq~-I{^{3lTkHjSW?ce_GAN>)(2inK@ zbVHwjtqwt6{#Wn6@>8$gyA?M9Vu|4z!*ga$#AVgGvW|E^4}OyY zU#rZYr#(*OCxY3N@MCw?-h>~E9mnqMOnmw?mR0Q7aBrNyF@$c7P1e4Fh{%_(E`D?w z&k^wvIG7$Que~kaZ`?Bw`yJ2RxIEg(umW{GNn+@_Xgrh^;MQV$Am&aS)9y2JRW+ zZG;!z@!C)Rr(YyOk0kBoe9}hrgANqG{N*p>5g;6nw^O?e4uCLDW~xG;TS6mBkREpf z@VYQg21-5VpgOB_fG2$9G1_=)l6tfEewPtus&)--J>v}zXmCen*J#1|^rl!ruKvWU zFa62~?%leB)DaqqF*p9Jzr1i3EA8=;)0#J7H5S=#UR6q%>p06 z!c(wqu~>VJxJ@z)g5_o(;(jY|k3SG3838HvD&?r7=z7&w%7IyeGe9Y0|8(;fwn?$j z@&|pe7uQx49ZT5mfe;t_|3>^gDtEVt`;cEy$zNs|X=LD|W|i1Z+>rnzjOl3D5;NiV zRAJJBMq!mEOkLSAU2ngI@_1{6eNQCxOpfWe$Zh4b-%5BbIV;y$(W$x&+!56@nz zz+;NGv3c3Z$){BTz@;d?Pt0wmI;8;6;2Z$=NKi(gGCbk= zvv&f34w?E+T?A$CILii0^Jjnd|KV+5xM1+)c$Q^s$`3AG#H+v;U%Gbh_ML}uu@c)2 zEK@9Ft|q9WY{SY81Q)%Sx<*Qu7L2rzCe4DaL_v}t%0!kRbEHpEfJG^XB|ZV#c4mZ) zCUn9=abA?6i%P7K9g;Ew6`Hct%gwQrh1zHWoNP^ks7r~^jNbk#NGYqC!V3uuzsh_i ze=`+T-G{~TDFK2OrwC08!6X)>JgtFHe37qZ=Oa^AqjKaxxUZOSxGYFkO@q~cYNMl1 zRWPgh9S;m|s>VYmT8Z@OPp$P+Xfum}{DrW_7z;9WKsgc8ttpqe<_%VhipYgGNmts` zm6Xe%ewNjxjBHfw=nFd!#L$N)s+dB_Z+GBsReZ*Z%EQZ-@Gw<8waBWb^*}TR^57r| zUj)+D3@#W8JX3^AfU#pOmY7FSO4C4CPg5WzNN)|4S|lcG!U|V&64|s?+De>}EdeVj zh^Qqn618u{&`2p#;C*L_(*NVrqr zLvNhq*iEUaa0VC?ev3jTciQCA9Vb&~!MYZoD#B;G^lS9In}Orb_$pclQYOzM4FuXf za^MQDm0sQP@VTp(U;Q^fLwq8&J33#Fh{f?q1@Z zQkD3Y58ma46}$5NN0nm6-XEs{ejCs&MK~~EES!K%PfmCyPXIhH>X%%&R!b6KntF$R z?|a|FdES%j&=WJqihJV&?|kv+>wBPFT`uUnS!)qEb2}1ie|RuX-?(Jlu;nV%WV=vs zC45jr&!M#1w8>dB15&ZvG!&}l!IGJjPIC{GHZC30<)zc2h6@Zzm?V zvsJ#lgudkqaPi}p%Edes@V`x{9dnInP9Zj)*5X2iYD2&5ko7<`)}&gKa3Js}r~>UC z2)j=MA@+4RoJZk7HFgR;d7TBHzdpElz}xrGgo8rc*CDI{jE?wD;-!nRnFeAP?RK=J|k^{%{nhbddSCTqD$+7KCCUb52#X5W(<= zcO>w9&9(Qu?d4y5UzoD>l+S3w!J!(C7es&XgMUEP8f>O;+^-My;8-7r=8$oWu1Qvu zckl8XN@oRv0RWO+4T4FZ3EFcGFpb47s>jzqlP3UbcJCw05?lq+G6YSZ`qclx7_58$ z@q6=87-AK;c=hsYzxk1aOV2&PqC*YWmsmkD76O66A}!t61-)Sb&g`X>=oKYl2SM%D&je4a~+`J8#)!tCl((F z1O$cMhY-7BxJZ+M_6!7H*u8~a8qcP}2k-u(KWpKwUI-!-N!SugF}!i~kN4q1LoO`y zcub3Tz3bb}(Bf}B5CE^46TD?2d4ySbNfmlVDIwJ{gEFgOUbqK7iv~`7x0WwVW7E_D z(4TZ5B4Oo&a-G4lN%oC++xVQGyz z3Rrgz1We&PF~Cb~(t{9-EAClwab?HQ>GIY33~r@EnA}u0{f5{v6$ZM6I*tR@0|B-P z$r#ek}v_LlKX3P z2%5n^%06L$BN(y=S4~4l;6Wq&V1UDQ2qIzE*FW$RclA9`w=aZPpQKu>j7IgOf#7OZjxY_MnIruSfSnM* zI%(;EB)?4MMz2t@D5a26l#ZS{M(O(HU*2ZG>Wlb4C19y|)VngHD^1PQ2i3T`g-?H~GvhH9&+?E@dKE?iD^90P^oUo1al^^u~RjinuE;C zpfZ<*f#TDj{0y!F;uoQ?VSye^Q@2Czcrm!)PR4-=hBgeE%bki)ZqV6Tu7VYQxJM?a ziWWQqvYKB*zCor)4!a>-F}Zv19u8v%{gDH)ZY=vHo6B&~O(^)RU{r9Q;mSK-(eppq zWh36%NIEk7`q#hy`OkkI2ZYEJCC&%x2%dNBF(J8r45?h9!89FnhK64{#@zsr>^jJV z7hX5Ovp^v43}8kT=N@nb)sya&XYvGKvr~3;7W9(|qx*?Z{2@p-Pro+FpMvu6)#qRP z_aDW@V5}ruOCzL9Y5Ia9(;~4@Y5n7okp86XIB`V?8UsLktnhFud-{k*Kx2hZgtZpZ zKrXTcvvspj+ooBMI%3D9x#=M&Pw^+H&m!*BlGq4QCt9<0KbhEMXBDvlHh##E3`^D^ z3uxlW6@OicWp6wWO@l95$6@R67$ijlFqElZq~5g6EUk4 z+*Mj)5{r^39YR=P!NwGVmpzvOlG=QL*;hxF&;X0e{CGU`0iL0&3D2G5Io-Ts4Mb3g zCm}_YbK7c-04#Np42qbK1O`BhdSj?8xE4VDhy&p@NP&yA$LT3CP+@YuwMHNzAVs3| zx)T`CY5G#Fzf*3)TB~<<@HZMS?qOzhx@b!TZ zCa6&;L;&5;5Q=K4WequCI{rWik4W_E1EJU?3sVWHuD)%kLdcz#s}P z5z@O+iwtE7npJhd6BXJ;7+13gKvHv0u?9kZDBUfTD;EgmT7v6KW;1Xg;%~HONb*Vc z9uWqia!*%4M}v?w)weUNpeXIboLEas$VJR`L7)CCUR_7Wr$5;>nVWsfZPJe%B1Xo7 zHvYUFEAOI7Uj3rK@@s?EsH9-N(%h?e@e#+>nYSG*hFp zzZxE)faw7t>onlSUwq%;OE+*%a2_uI`+y3Mh2uaR2a54R#BChD$6@1W|8xvl-H~(i zsE&=B@c6YgoCe@>{(3ZkgcmLX;v4|r&VVZRV&OH#IsNLheFCr^6=>GV8WkBis9*fz zm+&$G>b>7jzju$xh>T)gdFhwlf8{4%xp(tTz6z`<*CVb>_!sLHp#jg!{0u@vTV2O; zn-(R)*5tEoR<^X)T;Dri2SzcdO#iu{{x++)m!jP@pH`WN*1d3xmDuO&C$RrrUYqc!K zBCm8(&uXaXhyTqym_m|8O2`Qu$HpX-o|xfGJ*)b|+%0aHeFIShj94ysJ&>Krdnq8) zZ~E3-yjufN=_UhdlutGzV&ENOJVSKts-MWhwXQdA-h?gFlS}2mp$~T|qUA{zJx{BU ztASuJFO96!N+$<(l*KUhX6Yu#wPV z7{WcB({kiM0C$m>Lh)v55+Q$n67=r9xD-0*APMmZ12I@Y5_^;!h)6k$J`n2dV-5uT zk(u&qy@3#FKKVmWi1ufYl+$V;q|THyTMoo9H4-W-H0Jl>I@Z{A9^l>I8@$n{sESc| zxQ?Jr**PiK1h7yW10jqNt2JT-s;)S4AV3KW9h-9|AYIcVGZMnw8bXaRVIt&s0|EHG z{6t{39}6lLR~+|no2e+~SCkf-KDGuyt`V`Y;Zhqv$y&dP4J3YK08>5!*Lg}-1f??M zvW{jNA=7FgWY4m%5rcH}VJ^nex7}_T0%g}79fETcpktyF)&r4F-O)U9uQh_TY9K@= zPykk!47nHxP)0=OB*%s?RN$JPoWmBrp-7046UNp30};`fr&3D#$`x~&mIDEu>C>;* zov?Egeu+Vi10k?X1~kVwN4@2Eu*A1?;?tk#)AcMKljpZ{Eb(&0W%zPsQ3Ch|!|hUIll?QfoA?d$ z=X~v5Z-Fh=bYsp&$d>YQ@E5=N&2Ps0yLb`eB3oZ?2BRw^qg0mOUpwiw#wG}`V#28f zP6BWf339he#!m#`wP1&k5>Tn zgg)6dNuoZKhQb+Il*?&C=DH(T2P0v4ceF8vSwM(F*1Vz`<4rj%P>R1fQXP6RaC2tg za=|>6lZl3yZWwVQQkQ9$0N~L8qqXt8F0w;#w}h7jXhG#gMx`<#zPsF zDu4ihxWzO_7ND1cuLr_30VYo;FW#CLx-gh?L#Har9Rq=m62lmX<}z9o$~hKdDn#%r zK|HULud7CIY*^8~{1mk&Q^+Msa>s!n*$P1Aa*{=0TB2U?29@W^e?p#YjTo^bP$w_m znpY-;!F&t^j4==dH3u%Ph3Alki~?Uf!2|lbQ;#hXKVN(8D)!=ZHJ)6>R=&~=JtTyH zTWy*E#6wv)gv#C90WTAdpH_vr(wpKaR2nsla$~P5&y{}>XLkDrLODnBM4KuWTJl%} zxp*^^su}|&N+~-T zev;uHZw25SVE)bp5YS!^?ScrHxKoHQ&C+o8(gpS4O+PmKR(l37iPXTdD5i1f3EofSe6g<|dFeEH<;+iN2`KX%4 z*!qN(Mgxjrp_EI2lhE5}m>6a`E!lF_fYKYd4i|0LNCs^s1BEFw0b#*@=Ldms9~Rr- zc!H(2FZ6a$2v27q9JGRuG$^H(Iaa4h?J67EO{}-6OjLUypp;&vMzfrfpd8mdk9U9l zg+4f;SFbO%tAnEnd~t^sW>tT2~|hRG2lrRKH(Z#dsV1(4}Es8+{4jg zXM=n?1K~mJO+ZNVkMN_1i5=|;ggdk2g9ink#mLX#Mw7W=iEj$K39U=Y-}$INCqzVN zA-%2kf=Q0zGobJF>gtJU$W{#Y5J6wMJVwCa=${`*fGQlYa_g&H^$6u4Bu}KBgsLvW zh&Z`4lN}|hFMRY}_@m$X`1p@DZeRP_*KjQurvXfCaWs%sDN*8C9b7LS&2JJG30Noh zW~T@%9_hh-fN?>pA;65wRZT$=!-3NVl#~7xtIzNW0D58F<7`D33i57_Y;~E zm7y<|K)SG2zR+6`W~$n85-ObehP|u6HdTd_JFFcbq?7@h2Lei4A=HGeCGMqQQOs@T z!-u%`k84!?ZXm+<5aqJS%1Utx-@PF7ZD9T?Hn2iLz~_MQGU8bBby%XN0Ty0>^$u{T z)Vd!!uRsp7Sv66G+lbOtV4JGuSDqaJB>tpVV)mW9oW@$Pi-jJ-cw4Q3peV*01C313 zNLf|(c5}=;fPg?ov2a3SNyjT^w|G@!U;93@yR2(i9a{!)!9rf=v?O^2h_>a1g{FrPUc~ zS7?~a#5w-=KRFkGHObZ}s6z|CjLqJ|ACyadQpshrRFv}C7>MvH`-)<1aM~8S6Y%jz zBmU#h48o%-K_y@Yba@Hu5Js~vD>>zPGWfVP9_s>@JlzT_;L06rB2*ZMmP3!N#khk9 zX_LB3Q&TL{u?C_w)2$;(ew8cklt$`cYtR0b8)A8lfUZJ|L8YKs#ta4j(9#DFuD|~s zFaOF1h&&&c|Cz~!3wZtazx}uW%40#cC|iY!4+ybezIyd44*hX-4unsW(xD-t0R~9m zajSsrTN7Y-+=!2pErx+mELyv60&Fpzj_%n$0q{7jJ7el!RHacVJbwK7;~)Pxh9LOWHVvt})HYvbKKf%wjSF7d zoivx0dDoU~o*pq|Solk7dVNh#Ssz^Br$6&&7P3zQyLTWe6z7g&0ZQf2)`$+ISD%LC zkh*=Af$G4i3Us&1*_muwC#5@(zKsh5u`qEbmz{~-8?flicvdH#Qfnttjid9Cem%`# z*6|;Y+v+`dZRAkm;N$Kce0K;rg^wLAn7lWC(Ig{67>+|)B9=9vI&i823(c~}X1w_J zlVr>W0_PsuWsa;F+&C7Q*ku=h`mlkjV@hp;g)kurUZ}FN&;`NJ3~9kURe)MDADv{! zZw8r_u3KOduEe^4`?0(vOy$9WzA2o$WF@>k5ZG7C+{(2^M(V?+f#|RwRY;7sS1oVC^2b}EPw1{AA@Gy=_l>S zQ^Rw$x$xr8zx#z>c+cI}Z|PNFu1>KEa-$OKRiL7xO-5)d2d;G??_5|5xo4idZ9lUK z-W@_I&}m4GLJevPiNqG3+$9g8E9FzAu?ayvRDGpQOoL9Fwj#?pfeqZW4cGybtZMX9 zw=`#o{B@-9Q?;{(*3~ ziEY6%*^(W<+JcX$=9eY(L+gCOG_NbOA=$>G2Vx4asCJTtVRY4`-cjArZ_IP$kxdN! zVjy5}eeIS!!;n(UrhynBdg(i==*(E|@T>Kg>TTV?u~<6L$tG5ofDYC`KochtVZr;9 zIKjY6z--LH0rqyceuzUZ%0acO2$F>{=%QGt7nR98RSm6y=qV7piSNoTn~lOS0P8>$ zzoZni-bN^>OY_2cv$72i_v3KH==Oow4042${exh=n~v}(^OWLLEtKd{_6WolzUvp6 z6rf=tvStef_(welqHLV=VnM76Z|dDpc;fgBx58?JOg6&GN%cj!$7I}b2jWOB0MML8 zk1)s)el+8r8`^~rfaYq*uX6FTT(~AZ{MB)$*A*NpdF&rX6!DkGSkyg$=D~w&?|SX! zU;i+Z=?gsTXF1`EpTGONzl%2F@Q*FvH1ff5JZ?^6=&jAgfQ?`;1im?xEA+fggl~MW zp6M3^?l_>w7ksU+Sfnxna`m5 zjh)I^!7jaU?XCam7w~i05AbW!T+tfzyVYwLn*1Qj?VzO6feEaV4)b;cazd#q%R)Ab|4q(UMXMd zy1vUa#jhjP8>`r&;!-FMYVYb&sPj3d0CMs7C?!`$#T9xPXH$m&;lI+4`Qr}+xDZ}> z4BnEa{FF%=@!?dwxR(A%9xQ=*&7ELTBR00KZrnXHyy=Ae|p%xfk>Hykp=S z?$q&rgt&$aUwQBmzdek{x#E#)J4!%l&32?6+R4#unM3p1&tDA(isD4uXkbe0M7VUJ z(Etleo?aWWcq$+2cez_H7g0clZ@Ak&$h*|N6h#hE_9Le5v{RngI!?6KsJbgd=#{|n z2g1w}mYI+Y|07)Xy) zZnDFsQdYZ1H9iT-^L(s?P~Zra_f+{R8XKQIqeFg^Qyl>^N_du>hsO{3d9UXVUipm= zU%YY%^2O7Ajp-?i7)nI^@|VB-KmX^aad?fXhCUO)p+BDF0c^PwS)>UoriH31@&W_S z0d_I$dbl4a0HEz&kO404Q=UKbCjjcH&5`Iz%NFE_)7(G!gFis0qThR`pK>>!h%VNo z7k}YB&;RsK-F^Kw9t}fcd5aAz9jv*QL1sO;u^?(ijVc#Q>4^w{gmwoX_*{Ao@LC{4 znuEa@C`GjnAG2R7i~4mWy5Vg|Zh~naEX#ZxROGG( zr@ZLe^#De&`dkCOqEmu41G^tIy*wv z)R0sFAdO0e6H>ktLwE0BQ3aR~vq?s^z?~q|E0g^o;5e3j+u7bQfugY-H7u zw#ud7haT)(l~dBjOnO&w9s#yZ7!uI8M-7&MlrT+7fHz4d*dtVzqx%R1=g);>IF1(= zBWFE&Gf*7b&wu_4fAv>?1p&Q$K3c=94qv+d+Q0iK2OfV|sdD9Ff?C@`ryJ+8EfCQV zYanO?A)>G+jFQ9F-60o4nj+P}Vu8da7%M+l^;m%MR_mSHZ{p@a zv~A)E-A2(ukUOXuRB=(7M<#-TW~xHh!Kw;thVaW`BC$z&MRy2WAy^5aMU+QoD-245 z!;BZI_=?^eaij>NZlv4@o5A72!kT5!)j&jY;RT6Y-7$zGi?=k>KN3)bLlaarouUb?87pIYJ$rG+Rh)o57^qnr>(!=X4hd zN(*vP6>B!Cqb8mCC$p#3_yB@ZC2MkvJPAufS83MoN^3Rm$9Mgdz*U zQ1OOt(`s0^N>&I(u~iL3)zKk~#8eO_4Zv2KN`k#QPghR(wHFs^+MEdPWI|S#nTq4@3pc=E^ISLmW~Ww0|kzv>T=#D{Ibe)d9Jy9R_9Q(X@CFn!jxv zZrlN3H-z)>{3_Sze-WY=bV8Lmq!yz>Lll(pit)j#zxk2Fmu{S&%l~RJe);Ra|93wA z9mAzou*60$p8Uyn^-U0)gO1Dbjn=j9+Z2JteE@LEcf#N+|2V)ip{%IpF+f8D78kiP zt7dsz@ki$bVCqNrCIp!bfDosB*>~Ul?stFx_jwb*fHa)dtVa2zfAxVIA9%;z*Wb8+ zw`aJ#)p`}%zE~Q$84N)ypfjGT(Ce6Tt*>C({+g6{vq_kIQJoMIAc7{x1UAw_qZ* z*D+Vyh$#fBQ+fh)7-TCu*q9P(>&9#bP?AUrNrW_%#xxco>*RuRFqUs;1HM&{F%ak| zUMg-8aU6*sRYZ?Db_tC+RZ~6%9P`LF$)R1Z5d#|1W{7YcKwt!$qT9EGodbcafYG1H zT^*73t`Qzkv+-p;U3cd}PqZ5MqsHeB8}UNSd~>!iA<5BuwrnE#Nl<=U9SsW?l>Aj} z{s>O&q1YQ>6Nv^wO(>1&DXc?4qd((Zg|JBleN4w1hyrhfW|M*lU*y{<{HCx!{W%YlLj=i)Vm$=p6BDcnNX0N&LGb>8fE}^C z$9d#6VuVz3`Gr%x4IG~YLHL(?oLPi@9V5xzHMgg)i2q#IXN$F}^&q`!V zBVc**O3XAJ8PtI2x8W_SBy5cZB9+9#AY0Ro=8A3MT)0$Ll|GiW&8%w8Gf;kIf_Hsl zH1Wl-_$o7>Z!xs8GHn_KDC8nB|TmaXPF)~5Gl}haDay)@gtzR#t9HU{fQ&;0Mp*4xf#_E zIPFO%S6(9|LEf&6LY)fqQ{}NT`njbX~BNfwTAX<(d2*e@2C6PSRm{j>JtE??G z3LWKR48)RCkzFb%8y0`8VynU*#O28+qw)tUp1)D4O|1i5NkX7+R3!vosHVbZ=p|c- zDhQ*q)6I|!#Ci@yB0MuRG!4&s8ZpzKz0d;LcpiS;ucFajKY^?VEQ1hTBI3-^T}OO8 zszLxzc>4e4U;PlCWuV9OC=sz|Hhus5-~a93{%wTE3&52Gj{|Y!?RRjQb4&uDTF7Wx zt6INH0O$ImKEUAIeDxFuqL_L_fS=Hz>5sw*z|>P72I(<~(5RdQ5AZj>@eOL^^rDVHH9)i!2)TqQ57cN*{ zq=^2^CQ}0+vf4@+HJFNki&|83m}o9*xUL4GdWqZ8V8f+z^_t1czM_q)2ZGQJNZ05N$+EI69On4bKuFfI zEty_?UG+Ym{)a{l1fO14#X1tMYV|x2#B*8=L_`e!glN_^fJ8OBC;-sRAr!!+cvO`; zWGk(i0H~rx9VUQPlSx&xNeJaU5Jn~;dYSM1)e9#vF439SurtiPPAx(~o1GvYW;wcU zxWVyTLFE{r+-|bts@t7<%=@qPunB5LhrqH-=AdErjap{S_%2`zb ziYMW{swjpfqGmv9lCZ88)%HM?x0cyagG8#)4ocj2pjp>EC9fWVD4jVaB+a|k?cSmym1 zTr>$m@nj~B;doJoZH)Gwa3I1lBWOmhnw$fK&4B4QT6BwHhr?zmqD8}99IVsHY$Zd*>kV_NEdd;&1_$FjQa zMA+Az=#jIaWdh5Ao&wU{pC$KM*5W3>&+{e#TbS!wG&Yu++zc%iAXGv<(w5_>b4|`k zNLQgvdF@j!n8S;Xv8_^7#?*_XNHH%l6H`^8;V3{=wU0}e9z}r!HbZ$SzR2V|)+%G@ ztG;U~Ie~O0hHMG}VYthmMrc!Q7(jYH&RBZ~!XcP`G4yce?SP_B7vWI~W1;v)t4qUB4kx8ZB<~-H!e~C1qSdEAi_vDQBpjQiPKDtHj(u-DQ*W_|jo!p`j)$#VSAh4>< zkzFBQxo^PGDt&V5%{%H$AP+C&Q8Zq+6Tar1m~xmCJhkT z2|j;)>xVayMql4I8 zvC5(5rB_SK9drK)nHIaLjS(qKGzcSzy(|dOtvyeLvqmsgamN*J7V+RAR)9-~hx|N_ zy1sB82)UGc=RmkQk1!BURU|4M5eX0=t=kiVkpN>OFBdyfSunU;1{7}s@QO7Kjzh~& zg5p&)9+E~J#-k!o;=3<= z1RoPJfCe`NATxEfDN0!Zkn+)TMSlkK>gur)y zSNEVr;)W;Z*zGfYGxzAy%TWONu1jRV_u%~tZWk`#YuPydh~EWExS4x6$WDwQaLf@n zh3hWspaCCh(#oxxQ?&_57n>}I2vsB?r4y5B?WOdpBaO-hjP;2<6mFCp01;|BOaQDm z2bgs}BV7}t9!2wTQOgwO5MsEW zo=xE8pSSOveDdOJ|3cjT9|sagV~S1O-rlD^^{HR{#g9er~3G@Z5-%vm*RovSQp)8vn2 zZ)FS^7SW9)^Mqoh+%UngB&m2s95R8dV#K}Vu^zA)>}bt~)F!o(WGoVg`gd4aI7bye~jm*-?R9lu$9N9RZ+XwudH! zz{<^z7Fy6~AMQ+TkLYD$@#;?=(bJwrJcC!ueCLh`k{#BFNHyaz%iq?gEnM&7TC^-7 z7}h4|pMpyxzg#CX5wj=IRA!15BQF8K-?(-o-t`gB{)T_N`tvN;Qrd3YjuzzT;xhf9 z$v-gbu#FvYm(f4Ra;gQ)90T)Ru>LBN2&L!_sz9ySyNsYTgOS3xXkQC~cVR9#e+#mU zbB>)=R)k_UImLkohH9;Zl>xmSE!eSo4YEn>C4ha=4<-Ou`LkY)$r>Tkz@Xw}N>4M$ zm!bgDXK8&VP?w( z6f3EMULp8ldg7bjdhRP<8!0SKW-r4%1Pv(>g+UY_|3YRsu93GEl|CAb)Bln+r^s~g z>A{u<03*{Te()odF3a4O`g_s5bPfP$g`w4!?d86#NXxtr^wXdIG(5{=vU_Rb!~6El zgPsI<@yZRJs>vlLD08uFD`2d9smpb+1w@5%)56<46C3gd+^`ses*Z{@U{Ipbp8(Pf z5e%>%jrr}ki%=QBZy_(rGRYn7@pYuU@f5XQ z2^t&I-to)a+@y-D`JkjYI1T{3&@c_K)UD5iuP)+*VTB}BEBXXWZ^DR|g@QlEt5Nwv zCru%ye1QxXOlGl!XI>nmFltJL?9y~I&(F0zXp@Ti1vQCdRQmCSbeU!5W2b4*iE@ zT9s0R%yxjfM@e`4%uf?&iv(w*%^zaLcYpJgHmCoe`rdbkH}BY~)XDz{XJ7cj7r5D* zB(v{wFhymunds={7~O+I=e*R|v_&A?l3aL2AAFZlTCM}`o{KN30|2##+s*=d%kJz7 zmi3uup3y}BgYUU{?+-zJ5&*_nV*9EW#a76*u&-?Wcb34g!K${lCRbI;oAeklDasOq zMoIyOWR_loW=o!mThpxM%S?={Fe%;3GC2nX{&JfTKo!j+&8A|lwJ@Ts_+VJoCdgID zuOr20=cVB+RC^`p;)DtS@gdmV5v37e2x~8ct!Z9c5>>2ps3Am!MoKeV7;->Abc7i| z@;B><{Rc?mw0&}{O;gG)@;uD18g2JCJ;f5PC~r#w^FR)Kj(lo-8IQYrHx|BAM0rYu?> z#N%mTs-)zZb6a+No-AI3Finz+-(<2>@l}alj$7OZKns@*y-F+o!*zsn>m)6&nx1k- zbD)ke^Ol^AK_LH4$f{q;HB}QXVKyPiD#um#3bx$5(!u;I?nPC^ps|-Z5m*wvR$i*;bn;J~r=^ix<7ny$fmw$z~!}{`&0gp^*i|C#& z0_>e=7Xe~Ti}jXyt8WOY8nW#i=`h%KD{gGiw$WH;k(Z)ZhN{OCK(z+fSRVOZqaCo# z7s~1tj=7vTx?>_(UgaOG?e^H>RRJx0Mt1^QLR~b*f*5$K{mB^G=GT#q9pgJq@k&r` z)tDt{r;UQx6t3o0WjT);CAyKgKO2~U(RYIvu%Y5ZN7xT!pK*se=DTs2 zl#i$GRZsG7~VBGm5o5w)gq)F{Mtgk`qm7uru zb)@~G&e=fgZ?0~oORu zEvKzNPPN5Nu$6LUn;fqqnI1&G8^v8ucU zxe4Z=OJQI$V9@Vd#IgEXmQWL`@+$4V0KB$fl!+5?2x*pj0XFIFj<5?xc-93z1k{)O zICO|RF#Q_bLbW?*&1AbQPMWB;(lSuBt`hDGz-tSJi24&bqxpCn*g2jCZjS?T)|^&~ z@jg>-bCKg1K;_i2HF@&SzVqByz6RZ~_6_?mKf;MjHOBkZU;R}EcvL4f@uJV6Jvb2SV?u3e@f7VPN)`VgwS4(j#dcJOxVI^(@*4-wHW z7HSG8@9+9Iu)*)Y8W(vrpNgS3IT6YeuKiSKd11>u>wT#z?W#Pay^5*ILqxQTg@JJ2 z9C%!nm!j%c5El9~y!!L}^oU+%Ta{Pig50D(5Q)H|S|!}KcB|nA<&wE-sysLjMUe4{ zpPRQh8HUWJlIPCjT1uYo+db7?kgJ#oI^;Chv{#Y#SBP+CF+Tg?4d23dP>6gP0@!Lk zgZsY4C3DqOc?*?c7AhF!eM!}vOSaXx;H$pMrmDhr09O4~m-{GhkG?&S8iaYC+vXm# z7!E<0%ic8bMYC+W?aT+Ys);nOzGysE*~%(x2Z($-rm}^sN6~mISFU$70vAc1f1az* z?b93$pJrxUtUmqJW4yBjeZ7sa!5-<)l`B{H_!lF+Q>WtjB9hf;G{(!l@*H~mxZQEPbz?SZ*nLa4JJJ!i zBz6ck(=G)TORmG_9WRaeg&Q~YbtHB3;IbvL0ePU}z0mI7AX|{!dm69)14K~eF!$w z-n+0RvF+rmj;qS;JWDGL!n$y)e`qBD@%h?ulN;)#^M0E9lHqc& z<+>JcU}JsM*n^gdXMgo~Z12^7?rYDy?g>gdnmSB=_tl3awafU;-~7#A|MgFjIpew$ z?b5`oC1NamGG{L1LPsL#50}6uub0{Zfa|$z)@MQ-ynOxciCh-?!k{C8b}W!T zG3#FUH^Wudp2$9-mcH8^w_6=-No&&s5_U}NzC`DYMSO{-zYd6Ha`|;62ih^QWs7Zh z+-`OBC9O^ODDXnMd&4ONOTP9IQ`Fvj)}}UZNz_PBAE)t zflZlMHkeQdAi3BNAVSVmgUT6=2A`EH!;T+MNZ%>vioX~6C3pZ(`^-%%RwGrgmdpk99A; z>x`*=K(t#TK2%d|g83GGewvZMcjI}rcn{{W!>_y#sKp)-EsH1*{^kdvsB$ds8X6wv zyxa~wx4K41`G6|8LsIt>l5h29`Jqm+@!)(|ymjZnOskE#d#HR9iw^VRga+>dsvm&c zo##Oy@3CqRBBh7Psp=nvHDbq(Sf5Fq?aGtOwl@$4~HS zjthA{kuiU4GPrmcw_^4it!Y>t8$9_PUvuj5xQ+0ro&NuK|L)&$Yd6C=l4p#kVID2& zv7cJk2LQmBs+HpA9@yW)5#}m`k{L`C$+SjSs)J_#s5>AIKZe+W{3t@yh|JaKDk4{b zkAl7b{qH0DqxDg+TmoP9EnofEyWeu_@^zkIkHwjL?PB$dNw1^va%v%s8)rMhHu}F} zcuH ze^@zRynW^kuX@dQ$ESb$cqGLmFk{^R^FROdr%s*X=uRWO+8r@A_cPCQvQLtb8wO_;C2Zfuc9-cmr z^iMu^{;BVO56=O!OfeKczwz_R^uSu6oF6LU0}ID6Xm5|VF|cQ1k41qZ z_pLsk*I^%E)FVU9o!^|Oi`cOZ6qghtm-y;FqiT{PVo;u)X?|?F8pI^y<%h zs?URK{bPIF=*6%s$7FHMf(fqco~8AR_~qGp4EvAATe+CWN%UOzZrtE9biNWaMjdzdP9En0#Upk4pSIRm?lYhH z%tt@^QMMZ-FAW!vZB>^@4}hD6kTq44gOVa?FjYJaEz9IVzx4=ALSDUk^}`?jIgm&2A^=wa`b7X;$B5B>u9h11&$nRXQm5L# zGQWowP%|e%5qBj#M28TC#WkFBjZxUj2DBZaF34sQR(v z@q)-WmovLP@8NXBmrJ8<%9FcXX-urNW;SL#?|9-wd&|crul@ChX#GOEZH~uKol8>Ubt!`{d;+3GWJL0s|g}1)pwfXM<`%uICM4{axa|Uvp@?Zxl4cim8cqdP` z3&`5DC?)_-)z`r{R(TydDHbzsxmP-xtRtX1ruSquTf@f*L_yZ)VLX}Fe8^G|@|`|l z+?88NOg%%*3WiLnq5>k6g^rsm=9TT3@i(Zlsl_>(x0iPgF_O<6ZOvq+3eKv%%w$e7 zvzt-cp*VwXDD2<@L?{cLYp&RmXG^ayFxT_*(%gXoF^iVod7t}w1?v2Z-+e=*$2_g3 zT45Gj&R499;QMsb62GQd$!!6PX6kI~alDvOdzwWXvSlCa_%JFR;kfHmqAX%Yhh>yj|CrME?@Sc z^d8l{zK7;C*u}>mV<*I=@uPko=-A$cOBcD`<&Qw~MN*#Ky!hCqJI9X9*_ki<1>|K^ zff&4A%j=49H8qA}mmZ6CArAmvM(wbzs(Fil0CLF0p#Svo(-$7Q_}rJCjX_nu=${h` zjE)$MJo)YK3}jxX4dGEwB&Y!!uXN{;PI{J8`*jE>4c$%tHCbu^l4AhgiUwX|!V1`3 z#+Sv6t=14i3@Ev%vSiZ%Kur}44*=X_GRr{Hyp#_B+<9Ia+zM(<=5>XyO-%P-`q;;Q z=|_I#N8a+5w=lrsVvXG&rI3UB<7ZC2=6m1$rCBFZNClr~jsUP-n-8|qbyRhIKovt5sMlgoNGYg$%Y?)IjR$9Zit zbbELC>{oyF=bFQP)kVM!P3o#yxR=NSNi9}cA(oo$daQDZ#>`iL#@+}ORXcW!CoA|% zh-fy?*{rk;$p*|UGZxN_QL!^XHS8*rFj?nlB+jG9M@gG&|J}y4NJ&ny8XbJvsV<9% zD{*$g%-xPaN6ZsCXlYU9N6``7LdJ zHv@uWS*bj$sd~veWMZ%dV3Iu-iQKImc;qEaomZIk&RiOb(DM8%_dxTyg1FeqD?xdT z?fMPQi*t;sMSSp*uktaog&J4+g8dGSaS#a5nGTf1Ws%7uB^4j;Sy;_WM!FFWyxBbJaxWycz^CAO%MYKlvprm9Js zL_wR>;>R*;95BfuYluCi&?uG37FI7V0q)Hha-YJf!)CDr*r6j7FyP{mV|OQL03uAE z6n@E!-O4#uC3ECXD0RJ))^j*uM`Zl0g_vGn={XU&6^(k`u`00(Yg7D#I&LK$VWc%d%ySlyl5wn0r;#FmN3c6 z5L}~e)peM&!DJ|S_Uzd~ze^EE+&PL+prs3ElR5lY>e-2t>fpt}z=J#ceB#Y-e#DyOs zoH&!m08y)+2&reSbGy+DGJ3nvq0_Iun?qWptR^|25mhxI*WyIorE0Z|kW@X{If<+W zccunL-`%Is0$V>MOtG4f^{47|{^=L1rs{>p`axcTjg+9&H-#J6>??gPgvU4rQ2)|$ zESaazo*foWysD#88s-Kpgx0o8d^OlhFpB)CQZ`Aj-pJLu12djVU3u;@_d~Mz4zFX! zxCw;Y|MTV_p+uI(N!+#8qEK~KlM!W&WLbiX4vxmvH|G*&{JtTQuMwCWFUe-MV8Q{+ z4vEHO#M~OQXTbI_L`F#YtDJKuPh_1I2_`hTEeVv40)))CF-t@ed4Lew8SMndge|%9 zdSz^t^Ed-_41NeqR7NVCj#jr-K z`O(Ml$Ilg|Z4|{$cAI&j_F?a_C-aQ1rMfJdUL!dNT(g8cIm!$}nn# zG71Ooh-?YG`x=q_TJc(xtdc@T1Qo$&8Wj1}U&$wpQRjX07gqoPAOJ~3K~$D%I$`hb zDXNrJh=4gIX>&#aPd35__88Q^dCz+c zGuxvSSV52NoqYWKofmFC_iuhXE&@b;Ig8Z!pHrro>Spe8BGc*_roP&XDdtvIN4X3d zg9klenItgJ+=D~Nf-hp&*QXBZzlF#7<0-R27Kl~o>{od<6Db_Sfxjoj*ZDbYBZ7bIXHrXHBGAL}zWQy?s;=S|05LG$ID#wOzurjW|7R^-p~BZ z&+q^MMNJI2wKthI8gXxZ+}9m@(wr38G6MTnM&!)<(L#JE0R507RH68{sMTeKFb zxu&Vd4wx{qwp1Im85JkC1oF-K&>&MXf)$30QD)}px(O=@08>WESAt$V&lSd4urmmE z0dW8jA8gML1*mcYcS3oKCU{cX*b(lad1B3P=z53fv{r;5C38)kFceZMlbSPcs^Qs& zwr=k6?Z#v0FJ9n6D7zdj;hl}&^v)kC3uogONP6WUO-rc!ERn_7Dx!E|tDGPg&C=qu zpI+bIB*EPoRyqQ<1guvuoL3^YMa~hy#Mg7+UPDWK|R{JIZQ)_=^H{=Eed{28S%}Kx*m8`kX>Rp)Iq`keySy9gcOUQy+*2gb6>$@zO<>mi}poUY8uj zB&n%ZLr5tv$+4F8H4e>|Jk4yO;+Kwsf$2#qVAd|tRbNwTxc(#N#=of}(#(trs^-!L zy;66&wO`?}R0dI5>0)TQgJRi~3v~-;Dz~Yeni-$oGAT}>UEI_@6LYUAE+XegYsB3f zSFiEZdt3^Obno(FWxg|wkVu7yqO&H1!b`%C_>S&|Czad)XB6*vyN(FT^S`Z+h)edp=4gSYs0t8%mAgWUq3Iocxj- zW2b6>{BaHq*?$JT8i6R*OQ^-}8oyI0B!em<<$z@q0r52X$~DW4BY!F&ylPX}W9pnZKBp? zcw>4AgWt(WY7sNnRoTiW^lGRvQ7WPYVN`MJSK48bb9L_#rnR^-AS;UaZL%JrhBlk@ z3KZbpAl4&DW?n0@MOo7(>ZXoZ!m38Al7hNY0-%&$0&`c*UZG+oWrN8w!&%yFw%F3K z@LafffdLuAS^KA;Ht`%C;h02Ti&ZaKIMuWwL*IEMGZ&#r%;3<~RKa4{MWZP2OyEr& zp{QkpMk~=y`9B&gq#XU<<%IdUx4-U14iK*I~h=g!E@fCBA@=O02 z02+v%)3m9vunNX}Xl_hso_p@O#~yp^UGI7qvcQ|D9;!Z9>f>imow#`R*Z-r!M z&&Db%qj^?LEfASuNuPO^Y!SL*6(FlI4Lg=QMGrA9<*A>P^UY8=&(mngqI3W%g_Zc4 zPwDNKQiL#J3R6C2UkH;+np%_2VM@k2B{Gpas1P-c?t_a1$zAhqkKfRkioRw{oS#IE zlvpy^K64|`eEAGZrAT?k+zBUGYJ{2GJFJos*IpoJv=vw6hJh^!LV6rjUu-v`+ z+-2VM$+DwJeAku(fHL}*KKdHAUj3mXhUj7nWu~go=|44KX}il#7Zc zS#A94Slz`?dC108GpnoYc(H2bpdTDV|B(5kDNh`G!(aW53vYe;NZtHHdfNB>?9cw} zFa6RlanBE_#-_ZTnAhDOeDk5beZT9eTs#b*p?2(Oa)M35eVKFT&!*8o{2ZAuvCFHm zNROjaz5q~__g#Ll?Dsw0!x6@kyXa0WD+3`gxloC7Aj@P-8ku& z$}ySZQ^bvIrWSZ`QIgKBS$z>q&I=!N%2D$`nTV?80#g*>G%&eXVOFl-7ab9#C2a8U zzr)fv&T1^%YS49MS^fbGB-I^LEv~TLj15bB4KB^ZdlY!AXzoKvidmpJ#>~!V0+$w0 zM^rHKCZ>s@JD}mu`80LmmuARTJSdlhop?0a#ekk<)FA&SBswBT6lBG9woC7N^Q*t} zYw$Tbul{reIQ{?r_kVzK8ecgl<-C&Ij1+k}C-(v^huVoe#u%Lq3%k-*SCig@KrFUY z=g*%mhI(p0?U&wcK5AN=44jXx?vuAh7t z>~(+nPoIA3@fWW>&qreOIsIH^`$i|0JpT8!+sSuD{|!T&^T!PQX zY6B&5P$d0qrlwuOmYQ(euOq?>%+_XEXI4^+y|~o-&r8H@{QDCHVj^2M=~jjoy0=or zF0-1VBu-9CSS6X{9-`u>=f!G*#Ew zu-u6a3oV69q37H79}stZFlX(g<3C6>N50Ng+$zt6&}A}yDcLnq%QKbobo zq35Q$A;Km2hZv5X!azF|!&NHgEx8yrhT?xk9od?w&mx=R)IBY~px5D8~TH(ABXMPQOQ?Bd9E| z{^Z8)JgXG>@=_x{d)#jQ9~yIGeFj`M*+Mtfd{tVI&pRS~s;0EwdZenYj;p~}l`t%l zS_ST?>}36SN4J3W$~4!F42zUOcHdpYt<H-CR7*<<>6v7@FiDPj^7BMwrnpJEHXC zs(UC8T7!*5gJG6hwQV%qQ!0yUYz~vjEX?!P3v<=%(vq&^f*Z>sKcl-qh&9J}36xA(6 znLtb&Z@=o#y#3PGd?kP*d-|W;I1#{@t)I?sXY|parwV)&k+}wS%%kD5%2C7oNH8bt z^Y)t)vZ^(9pX0^3IT*O4u;na1!$?N|%A}IxPUfUaGFAT|cmC~fym|Thu@kYx#~K?6wdyu`pOcrp1{)W= zpj`!dQ`XEzb@cMr9nnnPrT=EVmTrgEIK@p2Q{XuQy-b?`Q=4?hePeatKEE3y5;AOT z(m0zH_ssN-MeObBsnE>~4sBm+B<$ci_t?=vOKfpvN5#c~Z8cW8<~!(nvHytJY}XYp@aCx4 zuw<~=;<0Z>n0F9JLz(QC&)`RKY?dWryT+jxuuZwFMoeYf-1Z*iSKD8HIQ@V7%p0G4 z{SP1M)BnH!`@bKl=9~N3IOKWsxUW0fImh}O1@J65kNoEI!L21^5==Gb^INv6rip9*!(Taq!xxorW1sYzDklE;LDP>@uSY{|KOsy3FTyFAUzOZwQ3PrJuM z1ah9$cgVy=VVEm7`wD66h?zqcjFE|=vNCc2)Fdl%fZ2T) z!8{hWcMY8$!yL{Z!V$HyZ4S%73ySB$g@)Tta( zK^!8AN?<^A$A+&-R^$L{=(V3E(mWQ{{0^pbp60Z$B(h1l*;h!E%baDhW93xWR}#CU z?DFW^^%K*87yy+&Wk&xT1L$-$3brbonvD%hpT z>DMNa4{5Lg-4t}=rl~L0Jw}V*K-e>}3>7Rq`nz}h*lWM{-Dh6+1c%&5=hdIR279{u zpREDIGi?(XvoU;U@+LxH^l?~tqvlO35pz4Fv{np!$d(~LW5rC z`vL2i0bfX*+aM5~vt4YdAQ?f1bE%4Zggo_8pw{hCnc-?lTrZY zU--foe)^|>+C?Fc#5@)n&Q#h6BNJ)+ay( zIIH8Jr#SbWISwWhRT9ZdWX0gP1gx@2IrySnYAPq>tNc=GzGPTxoYIg6$W7p?cN$$8 zzg-@J@CMnI$o=*D4t1s;8(}6HhgFt#pHhqJ1qGV@>C%zwL+*u<;ha zRhc4GTiGh}=(}VS+JxK51sn3byGE>*TK%;;@$8rFy{i(F;r0GNYb+5sTfk@$!N zYed8Hqdal3U><8~dR?A6!oGGyQ)L0RvUS${9ekH;LYol5@{A}8G98rDL08?yh6%yU z6j#A5CD}UOUb5C=2hNxq$;wab2rMGOy|N+KBG%5&zMI$nqqFqkddYGIRU8O1IVDxk_(RfHMArbQaI%!s8OY_;fwt5TuZ3b(;S3}gAv>HiDw zc+*qg_w}<7N1(s}CpvU*&v*ap^uJPLpQ4+2avU_Ji=Oc}qi~+VwRXUuN5ceILwv^f znl4<3qk>+FUmYHRl#4kB0V{W-5tk&Db_KBGBU|*qs{X;rFQWs1Wh1HsYB#vuQPP(- zRpihA{71MKNDI)C^O0$CWj=BK%AGraJ(Kz0Fh8sE0?HzW+>&ImM=I5 z92))AdS>VuQjXQP%JP+0<*73GrAq}Y-G$%owaJQplZl|Jjs)fvyi7wdxyWkfp(vhJ z2FF5>T!Tu}=kzD1AUFo_(#uoe*RIAUCY}Z!9Gy(?=9CFE^)i{ky*o|6OBdOefhM0> zYm?{otJih(!cxlqo|~Gu!cdkUYo39D2dHPV#%@QRy8y9=8h1o#uCZku41iV zcjQbQFLR9Tb90Ovz8Us0)Cc;pTd~1<#oaZ7Uuw^(#|6DzQ zb~{NWym$HKcmNQ+)ceW(M^m~>5LRVjzWBv2zVChQGZW^y_nx>947u!GeCJnim>#bK z>YI*OhjS~!;HoR@+XFK4rsbCHg?uVtsfnh^KUagIr|OGR?y|}xaD|Djk_f*pwMHC0*pZ_l|BI?w7NLeGs!4bT3*aPtK=d{l>j+}FX2|6|QE zUII}D4LqiDEev*6!We9%^oAi+wj~b$l*Uv5vj3KZ($sL(SG0phE>(%U{wlxaij-1d z^vk#c%Y+p{8nX?8@kUk+U?*lCBP*3MF<6cut44Ss9i@^;mRHL9Dt3GYEWF}fQt?XA zVG^N~TgX?_UQ)qO2`fN3SB=0W;mK&QG1g3=BH4-{*>ed_OhsG~^jBzF@&;o<239q~ zqhS)8=F)pEFJTN28%6yYj37d3s$4P1)zP#Ah9V4`Nw^BirNy>k;k7V&^=IGp2bp~x zLWDIsu)Zk@7fr?+DUFLxY(Tmw9wlHfN@QVVYVrDyOaqCGDCL4M0Hhg|jXzJG<1w?U zq)l)PmKeD{@y&00{7?VMNYML~p421!B{vP#KK$XI)60KIwGQ4gx4Yb><6{83L|wA> zoRYtGE$h2i|kxZEw7F`P$wIUI`lC297D#@%Yo` zVO+|z-Kp9J?3)$0LKTd$fYq|MM{QfvJt^AMb)gxQ@C=hQgWkoZAVNqmN%kB~#iN(W zN`*#gO$A6<=S~%+U98+L7>}Tc*5HV&q7sm1AmskcxNRoBn493dtDcS8t#NNEj%0cJMFf-o*h5GOK%B3e_C_Fc6~$=p`UPOP#a zqhFe;a+OngCRMys_*%UBGkJ2ua^V64!Ppauxq!pNxuttxl%yCmA5xNw*>FUbqezx> zGst%d5Xn++yfR^KO$9p{TP*;y*Q5+dr7-3IA%y%VQ#||o^sAnJ@3%%V9?EhI6h~tE z>}NmAIa!j|>2r1>9^iS*#yvcoUuVxUWQ-RW-IRah#`8I%Q+o`+Ll)uJ^9Gx4`iGaS z$gM4$hm3Pbx7j%OkQ`rDZuAEeDSOFSO&_ZOfCm};7r*$-PyXajAv}^kTTQqrbKN}i z`d7XFuY5C=ymRYCwnuG!j#WA;p2b|nbwCih)aLIrtE;(WAwf{|pB>ONYO9JMhN1|t zrs^fyZfS%92yZX@ntAXwU|WHkO@n$d#@&V(bh3yGo)gR=hMX-B~?gXmR$m(TIA!YI$ zrM(QOoHgfF0Pe;}s2Wo#iU7uAwTqAg053UWmEfY8D+98Mw%XAG3>A|yH;m;6=@ zc;1ei%JSZiT&@C-ei<4lrpgxH1t~8%4oI7v5|4ra03ZNKL_t&}RIlV1BMwn0idJB` z8zW%=m=-|*(9R7Nm`GQ^)k~(rP0`o`3FDvq05&q zv!~PP|Hzvv_5&3QjMlhWzmD0&09XeMoSx^Pk;4FXNg}zIs*wz5{kh?XeH;R5IFm|C zJ~}eF_lT8tNBIbK=Z+aa2$(wtVd#c>C@ERskAC!{pZw$}N#1R$YYkze(#U;V0Y ze2pAw%g4i>_a`T2_%;|b14u1rHp7-eUk z2}DtvUW`&MKTR((!zc=$g48QUv2{*yOpaNeGBpc&T;$c|F!EoMP3aPPn+yOmuXRAS ztP$x5Rbxtbk#&`So0^{qj~nE67Yp-7*Ini*h1CF6${LEP3A@(_gAI!$3VD{MG`c0I z#BWV;1do-EZSo`xJ6%CuxOp?bik+{1mnumjvLjYbRn4a25}_9Q;1OnZ?v~6}ZIJHJ ze<#nW%Ghd02!e9(aYv{M2Bdp9S(CM<7zFMJ(N>sb=@T3Tu7%@^&F&pv71361*dYz! zlBDtT!(=?Nsy66*+#Ne2243+_;oydkO$v7vGUwr_h$Zqc##BpFmC+u$Bk)c4#;yRa z0$O*3TUajPFiS{kWhENT1D3T5!Be-j-(C!Rm0PpKehPK`#V{WsS~ve9AZ>MrM}J?u zeSw>bzyCcBa@2&+eC9JB`p}2G;xjypJz!2A*C}UooNMP8KriS-37}$-!RR4f-LAut z0L&UVZMo{0|ibgUW zeD&(p@BZ%Z*3wo1kGSb_^*(;;I8`* zgvv^uKs{o3>3?-Vo`-1UiE+53o`SkTr1!jq#<#meo?;K8f*n$5UPUQ_XfBleUv@<5 zNxbpZ_UxSTe(O7TZ*rsIi5OP1?PBZ2WdVG05fH8?ra-9+(e0Kpnw>hLWs@xXc0`0} zNDfJ_T)x7y3amluk9c{q-s+gG6Zs^(b!IO{U6Ny%wI+o>Lqu_hjtKCaP6Sg&WIF^U zapNcHos2R}z4I9|4~Oj5sE!<4m8Qs(i;z8~s0C9mDulqulUK^J#(dw7m?u0r7AeRR z#;oU?+*ySP(uLyG8mDRGp8hHJdZ>_~G=>CQH#hIrXxSiAkw&N?y-*qaEX5(_7p(jy4wBn}jI z>JTpq(G&uW8h<(Hp~xC^;^Nt_{BOVe?9;D`XMdwTkIF=j3}%@BgMaXU{MK*%7HMVH zR6i!R1`?ERZXCiy5t)w&`2EL{-R9wO<+Ry#2?{sjfT+dHCqRQ}a`GGE6}A(I2lZ-`k`O(eGYq9eevoZn){ zd+S!b{E9reV}WmK@||pQO1GHx_7S(T6(<@`j)JDiS^#=c$jCFzF&C~-G{<>IG-bl< zA(F{eN#7ug+R))&s7vj*Y!0b!2xh< z7?GyBMg&8lrs&2Af(@pkuHG3YZ8-89;?|CEUc$FxVHbn+K87Mgnngu~xLF_urFR+F zyyh={-BaHmul|h8Yxa)xh=l0gy}e)iwO{)?f9HSgJsa1MrDt9R7nLkU9tqIBKn&bh zYao|sg}jMBjss%T5|d?VG~(Bw9}RfmZyPvF?OuuTHf-f zFM3)6SC>PSWf>0tvhii{woyW8koZeBDNSr)Xc=>&mn?voAY}8}a_wd@2R=zdox+lN zLRMR9RUJYa14Obljk)7z`L$X$-Z*&RuY1;f8mCBl8ZF=aQ6?esUz!fnZ?JXdKyB^_ z+!;xq%k$`oieXUR51q#YNtY4k0i;g{#p#K(I-!h?$k0kac}914-_UnJ)Ak?q9VoxJ zagJBDupC++cW1^e9I^ETB)zRLglCqcHT0MK=)&5k``Zznwf)wPh%{?QkaJ%ndZv+Y zG?CKojo@r8IEpk8#o!L&S4~|%CIdlq3|T)IuF{8 z54lG{*TwLTVnmzwd?jfA zDt0tcy4V##lZ8&1p%rD00V!~h5w~L^pEN|2IjK$EkS*gv`UR#;6hoo8i05mBf`nH} z&ApQ-G4la2GFUsT0s`Z*gd_*dZxeKUMN~gn zTH=vd6XOR+<*#ygNK7N^o=G0eB36iQ)Nhzdq=?(`1f}KCxQ=$sAx+Oa8S-YABxCgd z;*00L>a|b*m2Uxm)Sms-IEJzKPyN(Sb7;pzzk4i~^>Qt#yQ0hQ-0BBxynA%zWCmH>+ z@du^w%=s$t(@#JB*0;Wu<-p6|()|%=vKA-KojLXRg)jZL4vK_;**-%VfH-{Z`i{ev{Qm10*oF#(|nC ze+KJVteC-Ox;0^mU2Vew28OQ_zIgLidj(ML6xoQKId_I7mN?)Cm$l$UcQABuoCkuY zb=J*au#d= zN0)6{BV6HG@RdU29H>`-GgXBT@~{R~Yo$OhqybBz;w6JI%$f?*V%#tTwBq*T0XuX= z4h3%CeBoAmup~O^E{~N5Yu02n8YI?Rm?ttX0pf3H#5shLsVpoYqe3k+YD=^w)g}%4 zSvx0bC=2@xPz?nmaWD==FleTu`R*>*P^qTunEH!JRuNXyMj40R5d`AF58nOXHY|7J z11Vfr^zPf_8`*X?m})7QV=|@WA^_xBG0W^aqbk*4t}YD5q1e0^S}CTE$g*|^M9e?^ z_9F+5mrk8{(_j0Ji|=?7V_t8ldlrxKSEDa7`1r>^{&)ZG-&HDfu_lUDP|+>|ODUO4 z0ZhnXm<(7IdXSahReV*lY0^DE3Rg{!P=5Jd0a)#9I#!)wq4tft-58ER-~ayib0Em^ z90!6G{OILem>>Vsf8sTN@m;sB#1m0$UUZ1xS9o#^5dS^GSN+pv>3 z2$v1hjCN|08G6Lz2QPUVIF@1A;++`RIV9i$sWKQ`8r`O>I(n0RmQ5C>jwn8&!Nift z^=sGL@FWjC^V#L;705lH7l9^BWRv77MqoR(lnTsNFysI6XQy7+M*@|BPaRPpon}%kHQ0z^{UAgFnZ#-)fQ@f4<~Ug!=HMMs z$%a7j9Q=g~+zaH*d%hBscW<|od?UWMf%O)`QX_R_`~phcCcChvLU2c5+TyjkT;W@( zM6P*IE$9ShzQQ&K8yRc|>4+eoM-D2{1y`G-7}8jv0^IW2T+>du`0aGG5+`IC{Tsms zFy1L#PPKyF(h>8vG0i04>#2ubR@#usG_(MSa39!m^dGu|cf=G@%ucQwssfpOG}=vl zlmrYRx*$$;Kk-d(ef6LFQ|MR6LY?@L{c``skNwz>Gw#y)dndURY87OybB1jcIV;b_ zLk8#!)6H;S>INH0>a0Kf%J#l6-xrdX?-c+q7Vd9W3Cp!{rCgfO;PT8fe{|{6rLX#4QM`YZoxPjCh7B&D&pKV0U z4Eqx%d`H-fdQsr!AFcr8v%k^tr`fPP#&yN)_8ws0rdY6URCLM?7|Wrb!l@%VwyYag zCBBy?$F8}@UkJ7F&oujpzwZy&{KkubVk><2&Wm?WJ@xpT|Htn;`Plg*d-|VmG5y3( z{9hWrGltQmF&Hv!rMU$w2fbUB{ShN|PS4+$V*sMS0~>>WCYaI0KKO`DFW)NwwIgcZ z)a3(NMa^+ZMN_KC%|CpD`b%H>5(^M=rG4~ruIuN%;x%vhufC0g8$Jub8L}J-YFiMC zZOruLRBSVwU_`#Qd3}XCzat#x;0W*IvjS$%-GGyrGC! zbZdnI##WL%V|S>Hqj|e`r`%LdeS(B!E@8pc89G4aRajM4d7{`LDKy6It!J2NT9o{e zY|VD61~ZX_9$-^P4Dgj==k32`p_zxMzUo8UdluQ^Q(^L`ItQA}G@}bUuMspC_t>)Q z^(9%J3XMfhm%mJyLQO!H7BPP$I|p@yi*u%uYBe~(rUTpx-c2@TgOBA_KY zfEvN!dRb`ZG@)GT4cH<~*=_ZoU<22h*Rmtf@PuZ(`ZIbtCXV;=x?)`U2~`hp_%$MF z>#S3D-B=F2J3_bj2-5Od#hYwxfyYsQ9kuhXFRgTc2bU1d5-r6ULLS4q``Yh&_t`hS z8r6}y`Ny^8{VV_QAO0hDBJ4gQYb_8o&Rn4A314J0NUoZwe)bld4qna4ZTid^yZ1wa z*=vQ&BSBFZ0ro+E+~qF@+`*W9aD3#&m-Q6@TE8v| zOnW2t5cL=`8HyVt9o(?OyhwMU*e48lZ`OgbF=A`Z=qGlHc@cmi5f=d*+JbrajkpD{ zi;30e>gCH^mx$B`So>xG%#> z+C9vYty|_zZ0%NI2s9M8x*p89!n~y>Te(lLbRCOVg2r;6Cq8*_?{r?@&kguSUKy$x z*LIg7582L!Y};uT$ghM6Qh`DiTsUfxTqAR zujvXgf-E_~$(OWGB<^nJ<*wlnR4p{2ZOun_A36+UWbli}A;{{!#N697982zub#kfeU zL1tOhm{p#E)(R`kQ%%+!cF-Mypt|ha5it&^4o)d+zG@L&_aIshGvtI7MEof|5xUP8PhE6N6Jz)gat}7sSDK#Q0EkiJG@9*Kl>Gyss_l5I!Ck7BKo-vDg`^z@)R>^{!<~|9-YB3Zy1sQh60UJDr;wGYf_4`%4 z+H1@zuLr4~>xeb&;k>eWI8(u=wW6UiIiBs}Hd#LI*cRP+H0QrDU?L9J5qJ@oC%0JG zB5YpZK7XOV4Q(~#s&byQOHMwyt)7IMc9kc@zS2SR*_W&WYVzn^+!S!*p2eP({UGvs z%P5Zpxzn>Sgj#r{y`YOYxNa@(=SJLi2T0x_z?dHmcZc7+sV7g?^CIM6h%Muo@+`59 z)`4i|v{PZdiqaZEQ4e~Gk;>SMmiBBLLxhDb0Dc1MBSO57x_2jp|M<%h9S!h zksq2`W35kq@{>Gr=^>VsbqFU-9hi|JMQaRxuuI58YnOWq^vVIi*nVCtSYj+!ke8$? zAai!}AN`{r!{sPm1jv>7vDW9*e;1p895Hum zKrLHHf`wyh>ui5SK_37-&!ccuv4dRUYTQLWKtj9F9UQaP5vogc}0LL%HD?#&N zxX9aIN1_1up;RRw(Yt;%-bEW3@W$|CkrfBhZSk9Dt=xB+Z^{0!l)HqTFpFbI!YcAS z{+2`=+5U(r>I3Vw2eC%X8=_QVgLt5ZFvp@JVy~}DOmPelq<0x6GFZ$z(6%IXp!f#@ zJK6n(m5$gg)E3m1IcoBb}0Hb*<$G|S%Da;v-W{Y!q@gCT?@^gRQ z%DvJNy*}H2m)RP>Xb4-@g)LO;4yj%BYN6v`Ks5_ReE0YEos&Km=WID_H z72+0cmbH1p0k4zdPE^%~Ec2k87gr&ba0%E=Gj+r~U@Oh*wWMsY?U@KvovVy>C^pjk zk%_tfs;SDG7zVwH8>t3#4X{KBmY^2)6V!91N^=oobSzHR{+t?Ak zapr3TekLCV-+q!e-&*tcV5pG61WA3#2-D0Xh!I)d9 z)+yLbJ1eXPG9l0gC^(Bt!PIjvnju;OrS!2!;9rzO%ya*-A?D3LUw`SHUjg*U-u&~g z{?-5UOTY9lc+67ct2zdY8s5F@TQi~-eei}{r%n2bdHKk%8~{XLv*^^tB36u8HKt`q zR?iKt8e|sDpZv+6dhWUBI70PRsw1>8Js5Yn*Ash1DB5h27$eA=}3q3kF+QGx6%r z=WpJ)#(Nm!8a@A5J9tDdx6c)iT#Im0RGYYsDxk_rV5vcG->pg7I0dz81MRB1ImyO+ z2^y46Z;5mQgJ7fu0V?xg8W<>U0f@(m$yY@yt9zB5$ui)z6bvxeb}n3s4>mO_hVhiX;?~JM9?(( z6g(zMpZe6NxJref;(&a_#oTCcBhVXkBhZ!W90O#nndR0Y^8-ag#D;=*7c*&7Gi^z| zk&@8NrD_IPho&mKo#5+DK?}HF+he zO39OhaYZP2Mg{SR-qm=(ET%AdOqQDE42=F{axBq$F1?B%Ei4ITwIjuW(|{$3G&L5Q zN+njqnw0DhW1G1RO`335J5n#QM}R_JWQMzFdIX#mB+*NSR_K}CU=rk+*%9RRjU@90C_ zMUHaSUDi%jT|+0U9Y=HF6ml7*y3|-4%N^svxKpR|>qv>>jVOE_DNdiX$c}44Y?@cL znkupZ6(eQX+9P`X{oK=C8Mubs;iB9dL9*rpz$WRoi)qNtT>l2yiC zc$7lg9aBe6%m&NRMFh!pUUHkYs`Uo-w}f{P`3+pksA0A3cCd4j1^b+Nn(Kq-K%<{7-Kudu;U z7$arX-dwx$kTV`XspranoDJd_it{peU;6{^={Ns);ZeAwGUwHwKk*YkLBi~?oRfxL zCY-^`g1I-zVti*ukn{cQGTYE6p8LAP2{U$_d5pIyPxL0V zumxOA$GGqR^0Sw{792lz{NiI5&z_6B|2J?|b(JfvH3_y4zs*fFbn$*qRlQkhM#Eg5oxn7hx8bI)5FE8LA2wQ(@4 zH$~>^V0Q$`)~qIjRg;CTxc|R3M3wiAv^%&WtGp$IU`sHXVI46ErX}813DsS4)l6VE zAeWzgE>HglCuiO+J$5Ny{=+To{M&Vkon0&-Z5oQwqD(-{y4)QO`>Q~BJ9K3lq6|fl zOHFkTx%1NG!pCx+%X-WubNc_*?TcUc=CAtizXv==1k9%y^z>aWTOfU)xHxIhas~`m% z^Y3fQ7%0oJLF1L6j1I3|zIyCLY{~RWP(I(28*?}vJp}&eWzl66^Uv_sZV=S8cB!Z_ zM24<_nY>R&1hx{cMjQy`P*=1SFR4_G?!N*{)2OH19Y+)RVij@4NESr$R6B0{zj^bf z_pKouJ9dN5j0oPdoPJHyvuS<4S{$M&tdSGxe$u97>}SAI5XSoIf~;h0wP09pTb3E^ zw^Im_ays~6(%i4UYP2#B`b53!h3$>#`Am^?AyNWTk~s_NAeh8Z|}mN zc>Npxn{WT@|MpLA-+b}J$v6$1`S*=W-znzMKgMF(3@+O%w?*2>G-I?#PIa3prAUfC z)clu5f{>j-9xBa{sKP4`Onh|Ga5KWj=P1U2>ig6&|7AYPL(fbUhSn?X54favQz-pzmG3?B+Ob6SIP1;VQe zA{qLB00jTMBf#If#k0SBcv!(XOLsh;{ncf$Buzu3-_#LF-0~slG%YF$Tg|GFPYo;g z8Bc4sMl6#7BI&gEt0PJ!dWV*5BY2NE6XfQO5XSRvojO81(ofcqB7}Yvw?yB#Mu0c0 z`6o$^B|^d~y##@kUIXeU=R#Jz7z~lgMG2klKpoNAkOKfdM9qf~Ie`cpuLNb`=cbX{ zocQdSqeoWe()EbjI-=(0w1cFt5r|c9POGiyp@Xt3XA62)27rm;d;FEFN%U~ zo3AFi$-!U?n~)DqKZLHuLCzf7LKCJC<1d70z_|5?)BU%j__n|HXZh?`DEheh!PVfP z%#qcxV^^M|g_=38Kc>1AGgAsn#5(M5#n;snfW zja{s|a#Rs|A;-iTN=I-9%%7CoZw`!`a3W7wbKgokHyP2!cUGZ5u z!tmLz3y)pkLbH(OR3nJQ99+yS!7xP`tS$;x!8Q%T zzL+Jz8Vi*ZrT(N`#Z*&onZ{wf+6^t5RgCqOt+Axn;48vTMhgM#-q19 zP+9Mw0IaNFl;#(qi!I*b$2{^i!vGY_n8E5I0R2fLFIxve)> z|ML&!$`JQWO-*?OByx|$_VzyXp$~ETU!5B(ZYr5;bxuXVB*;opR!CG8>0Olam3cJO zE3GiJfR_?4Oaovj8de1@e|)q0v!DGe`lI+XaBf`r>c{K>^t0?PZ}NEm!Ln zX5j&Yk&6YJ5H>}(ivdoc3dui%)PhKAlp(GK7Pbp&kj`l)zJOfLC*yhL((CFu58tI^3* ze@m;_u$oqai>jj>E6juyP-`uDu%a~c0JdH(E&}sprD-MD#Q?h?RPd&bsIcl=Ws$?? zaJJSEQnLHG`N9j_SQv+GoK%RZ?WxEic1|&aGNfn~6?21OeKSqA2`Pt(B;6Z}sL~cQ z3(|LMgx1srr!iAw?SSr(znS)=5k}yg2U`_@CPr}$j2iP76ZVugGzI^4oj_lLGxgMQ*{QT)BFMjD?|EIlMw~w7VsjX>jRAO7g zhQljcD;|sMvE#I&O30ueiX%%YuOe5K#i?!RyxXWI%lndSLfX-Sk@qNw&}k3+E?Q8y zp}0kfse+HkQ@`;J4W0F2apjB<59Y3(1geg%=Blz<8Dm|3?lMmR>vjv`U%YfN@BVi! zXDQitRT#nz#Z{L>E4Pr_WI<7e;ua;Q0X!79q`it>JLSM zSCyDH=^erXNKRc}dG0c|0MU{6uQ?szehBxgj}Mzmj~~DM&w+8wbhs2R{o%R9vnRj$ zfBMU>`G&V~Ie=3?hYj_h{4~B|C-V3I{@?%gU;p)}NRGHX=(2zb*^?5YSOto5XD@|ywoBx#DO z-@u$r(W=;gdX+c0?5eKOhqPCVnfXX=5(|T`9jp)Ju|3A(+z;*hbK-kCSFc}u{2~uW zwnoYyyzFSIeFa>&yP6Yq47S4k?%h+T;}OiZP^EuXr)BNrw=m~$Ey#8OZ{b;*2Er}% zl?nWEGKqyvJk|^8;*t}MFwB`inmiS|&Xyr6Pm;%d|Ik(aCU$p(088>sj{E9|=v$a& z%FIVrlUNvdPV-98bLTjPe1fNe8Bo4>n{NX@{^S$L1F}+!EgZJOs>@Pd@;gbuN!~hQ z>ATdpBSf+!U%Kq58LGLRd6U&uv#<$iy^)*p1W5zMdkoH=ja{u8Ek<>bGMlyhN$-$i zYBm+v5+#G)cadm4rO^hAv4QHlJV8aN-C^=tj%R=0|NalK6N&vut4j1wbVRgvOWndyfoOxA1UAC}dzlwsIRMzU01V3( zMJ+}WT+|ZAT+5sU$$WA3s6P$NdUE3U>wfr~uKlMkJoky;KKq)-csz)wMO(Y%mZfb6 zV=EM*wl>-%HP{Y6FfLuHvdYnh$yr^hTmue{)npz7U^z9KO>7Mn2?Rvs@*P|@#oeSg zGeV_uh^nmMrR;omi8Z*hZ{i{V&*R?wW1a?%T@7=FL}$*O;YOWUZs+NZ?)zD};f+C6 z^9p*NJid_69>lE|OiJD~`_?)EQ&!9LmKq1zja=}lulMz84!thmH|1dDQrO)^`3zZY zao=Jh4f6=rM;DWRnW{AD`0zSnQyQy!>cT{Z+v}c7<9RNBaVK4o7*ldi+q>XJmjg!VU8rx=r#>SoB|33G=n3wyU^PJgx zoxRukem|?qsj3qK(8>OOZ82%w5+qpM&5*b2cwM$ZgRvnKL7P#a802j|3y$+m-Hy3- zEO+JB!q-$czCyEAe5TSmolTUkKy=Z+izf+Z>X^?WI%LF0&@;m=L(#gi26{tJmcn!j zBP%BO%(SUcX!9WN3@e4c+;)VtGM+o`D$)da<06N-qL$I`Fx&CGT8U8QHwSOvBixUZ zvr{kJEi?|MZrv7qya}8B&J2|McP)<9AM^ciJRK50jE}wqUTFe&40ISrK;Ih`=~1B! z=8zIiW`t5^p!f8HgxaCnHB7%*^$~qBm8#Qxg20z00+$TEFt;LX)XsB+sJlvgDBxw3(0)iQ_mksEp*{vKNE4 z>v&x#U;v`T&_@Nw`}ghmr%!1ldH?-Bw|T%pG+0z&;nRt;ar!#;Z$Fu|n2kOo zzS)Z|2BW}0iS8TH3L4?Fa)?1O5EU`ipO(Rt z+qq6fEHd=x%I;E1d*9ADbv-WxevC2K?Ax*YBA}*v8sYI;s2g25Neq0C9z9`f_gMQ1 zWzQgqYfE0eYP2iH0G^)w6*#J=m5-HTxQz>762^U!2axGLorb9;(?r1kSq?# z_sf~*c*t1UzrPk|83W=E>u%lmcHMAr@2h7)O!K68!l{Ag-h;x%9)s;htv-KQY%l&H z)tQ2p(2vLeAi{dhdFVw@WA|Ox%3=O=W2||8r%zseOSwtA+t-BTk)3dP>S3JIWYoB z-r4D~D0ss5CmjD|n~GuW@gc6Mz_9rvfl6~^x8 z51nKh0fOHYF5XAlZ4UCS930O11NQi@9s|#!1e{Hxfu;-!C32?>2q;jF zEdUOx8s5Ura`ZCtm$-+o45qkI1iSfuuUEtD-LLhj9s9J$b0YlK2_!{B;OL3b^ph~+ z8Uf#}J!WMQ{zidtV|3HoL2)C>rrTwb$KR&kJI=h2murn;5hw1O2L@n`0O9OV&gWZB z;k9mn@#ssVS_hO21(;^_Tv0c=?C0l`i@Lqx5re7c1orc5@W}a0(L~DC5uTpOHW?J) z`H3ipuAwc(r>i@gsJX(S@W_d>VI~O8K6m4@+ZOb>R^Q8b!`k^CzTp{ysnqign!k&K z{{{Gu)AxT5<0So=I)N?ulW=e-&_CdAhkEDkmtr%L&T+6zw)p`0$b0pH8H*u5j z4i_9m?p}Not|f%(=?FZzG`)5EfE$Nvy174Q`uH?1@v))g6}U2eT^9h~PI=$_w-DDs zn;(|19R2ruF&=WqYwQu`ino}!+r~rp_Z{#bm-E1+nh}w=+d0URBJZayy0zH6R0&+u zd{cq8;~Zpavg_#asg=*As>5W7>4qZkEUcZ%7cam01P4sU(x4HJq*u<-GZOnqY3=}$ zCT%&O68mmB6A03wO&EF+{_?zLC+|^)K3lo0zGZqIkGXmGe?l@*M*sSiNh4g^r)erm!`v^HUnKP_(X9lC`gziAK@9qyQe9Rf9cHbS?P320{ z3N{t}LFDUFF@>giKUZC4oF%IZJe}cN!_F8j|Kx`l^lpU)8_l12>2Z7@b^~; zl%YA`w3&x@b_P^lFhbiVky0Ob!fW)<-17*V*X?hs<=&_xhv|=xeCL8n5TP{D>UJb{ zUl*G#@*Qi-dBurV-dU2ApT9Jg#ozRJ+&vR6!dlU~s)=z4-~JhqoW| zui<6@YU9Gf8iP!3nPjfH>SsjPUOTAE%s)$=7ajsb2<)c{2x8Hhf?YhmgYoKtPd$Ul zyz9so(h=y~Pn+b-fFFTdZRR|1xm6kg%*$_o5pu9E$_Eynlaq1VrRTeEdRI=y> zt1mMm0H#y;JwtlJLZTpZT-0$YKufe@?`o;*G2`O^m6+Ks^Yo-QhQRg}zSlE4*rBIx zXnHH;Ph6pgBcap00YR}c4VeC8o4@@^?O0F*YrbZnmfQUxauS`UW(6ZuK5}yMY9?3T zKChafy^4GVwQdcU)T2vv8KVVgeR7GV7crVAzE`#EEf(jGIPLFWw^(1O60Ikt&t~w< zyX=}QKWCbT3Z+P-L-`b+<{`NxJL>*uDZC$vU-Nj;9j^afT}j@Fqsh9EUiyi#^Z8 z?$*yCY#~_8`MwftGeLgmUe!55+MUDelm7PIN-x#^Lj`)4ECyz*Cg|ts*>&X|v|T?> zpS{^4Hqfh-Hnj4Qk5buLYA*j+x}&(;w3kwm{#+#)YI8x3(m~!n$@Mk`UVL12&~dyB z3s#|So5ub7(kg^=n?Lm5Z2yd0ba5DNN*3m@ImNjIXNS#RT}}G-!ru1&vK94%`bCMw zwciN*Qt)4eWw2&M;H4S+Uxzc6EP;oM6fjy-h@DH;k}pPlP2a>1S;e>A-DYMq;$q>f z@G?1JTZg7UQCTc3^pBy}hHN6h&;?y0_Vw~xP;2_2eznlc-7vezJIf|YkG_mbw~DN| z!fIO5hi~;V`<%ewO{j|fB5g=v~cx9rM z_HQFdjjB+ZsfX@Xu6U8f{*sHZ+cXwvdJp8XvM5S0=8vd=+c~ z4$%Wb*BGdlsaG(IomlIQp{bc+N2kSar~AD&zaKR&8QFPdgBy%y$U3%P)^2A8TPtF- zU;n`KPgX5E{hVFv6*x8vSA@s&bA}MY_Qa9Im|lJn<=K29ke7^+-5#Xwl>S>GLK|(e zQ>@``s{>mJ(2(_Y4Pk%8$jGP#hsnVlDO#i&_fn?P1xe-~X+SD)6hDn+A@*xPJG65%p~<8n0-FS=yu;)tH;RERzy(&_C(@e|CEc>-moJ8Hh@Tb#; zDl+aTPv8hAb7jUZPNo8d!YAV$2q=X^RhQBfFKNCDPeSfG&FI&e-X4LOGzBzRTdpuD z=C7qAK6=S|oKl)IXDKHf*iD$Bu7j7#XmkexWQgwDAxS1%R2jyEPdAr6kK_@^{% z^yaYi)}zl+Z(mux!qWdM^?fZZ(W{lCv%E}7|znTi2| z>ZlpGR5}DOt8ehK)kPra|Cb=e#L{;@`e)6EJQNnWcmbZP7!0MU?5Ub+;qm99gT%G2 zYY&o0ozvJ07Bx99!{5VRhr<+(j%>N&Q@WT<{OBC}k~mrP+dJYXfKXkc%}N>L7h0Ev z0tvHx{6dMsJXI%G7VPXLE%vqrP9{Q!i-I@|=Ys)IkE#6&g}tE( z4Hn=XD#fosnW@dlyM7czwasFtq^-LL{$Ya$6Q`kcgyVzF@uFTcz#J0Gajw8H0>WL$h>Ktkf0-J3=kQ=%uW!+UvCKRjK$Tq}ir z`@V_MKoqreEL1x`gSaniuwuFuXw9e~BX4)fhb(o=A_tTpr3O}xb8znSsPL$tq^d@X zhPtnEg<;;`Z>penEKztHeVw3 z@-Qj$r>TiyW?1e)^>D%_$a5(sO;8tMnx?b(>lJVBIocG{)X$!y@dRWw7rZzcy`dYq z?cR9>4b~Hl0^4!!1w=fd(cg`YjD|4j0~Sg~6JRw$*d|upPcld9N>-rvoB$aPToTyA z#d#XP*y|ZAR6+PW0P&~&L=3qXIf}KLI5tOEJ@IzG=g3$^*6|WO-zv2Qj`^Yre4FuJ z`L*M{J+0c`CIFk63^iMX0FNGdHDQ(=V~(RLDnG4dE9sLfgGk&^eMakqSi1}jqvHLD z*u!=Hws{NiNch4rfoTz6w00enoJxw#U`Ak${%3LN>r$sh1Lr7hvRKmwY|#$VQFhX& z*e-q##`((}IG=y%b5>gH`^+Wbd{=PA#nke7UMRcTU#j93RJb}TFQ*V11CDq zNZTIhSqC4c660aqSbMitoNmx`z`}TEVjyaK_KqZ`0{;D+Z!SGjA#1{v|3})?A&i#x z%ojF7qpet#t4i>fk*epgHC1>;g=e1HsY!7KE$&((V23tsGp|I!bwgDF8qOZ!C?8pd z@6$Z6m1-yzFm0^UwAD$0cnjGf@E;x>RVhTwH@RO)eZ$X3WTA1s$L#A=4awIjm_4x7 z`9gPr8Acqzv4qd$`Xi}-8#P~a^36e(^6WDwS`=R238gUKsvT3vY3;n#sTjp?k%4bg zza-%nJ=00V0>5EAASjEf{aA8g=&jKq8jBE0-vv7h#?OyRevw7n5m5o(+k1Pnxkzgu zoL#oS^#ukIGW#%&-IF(bOhgjfou+m^S@OT#coAVen8xfM1Qz|7fZ*j{+CNUj{X^6P zts@5YrtlFNB)`SmsqE|r``CqD)eqtk1j5t~!tJpg8CI?A%u7lV9q52^=sedyUULKC z#e>7)8md`K>&4Od3>?1h8t#7VLqdz&%b1(h*#uw{?T1rgY?UCE?#!3_;K2v&C7L9= zL`*JK$snn{>?ch?neGoMAX-3F{PYFHpZhCZIR{Xe43~~J-b$*U?u6$)s98#Jaqy=tim1JrWH#NoKi8FWGwmz;Q zCc13;82S|~&V$^ZEkhf#E};uh=;LRt>eC$YnS&j2)I4=QxPWr9h^`f9jcxFxw0cCqmmMouY| zI{)u69)6Hz@JF4RAm`D+H#AW}-ShsDPK4g~OK~qA?k8y9RgBLxIuvwWMU`S@{A4s; zcc`kEoXsk94Yc3J=C|WqCrzc(kTetEC0W{N`K-120o}hWRI~i7h^2Vg7=i|`cq))k z8Yx{JvPTpyyJw3D?t3G66c?E`xtd1neo~e;ZasJ3CY(Ax?lc!FN1dG+Yj*tWdU1Xt z7shvb58m|RX~2h$REwZQTEyD3JpC{Exa^Z@iJpt4E14ZDbyl8$oOqi$6fGgPJ!#}} z3l8*ocvaGVTR`U5l@RvT`z{>Np zsf9|gX11)pFAc(vgPl0b7ce012{0~xaKzKqx*n7)f19UcX&Y(6*R1~;&RIoPG1F4E zLUkb(G!4#k8e=3D1wU{h@BGX#F_gZ;r~>ZonxxD-r)e+iCwgxu3E%z=qRoix$$*`T*A5~JZYgH2hz9N2CR9@s3XvAy zR{k5RWX4*W)p=6ai6qzwkWZt@)uoO;T&;T9fkXVpZcI|v=nJlang>>m6G8(qHf%PFIdhhJm`pP{;lnMc zBDG`{z;y^=h|e3|&^5K;Zjp=fkG&eWj=!vOKm@vAaY*fFniY z&%h3s=GAF5l2V=4_Z+*L)%NsT{=_9Q9_Y3tA97vW64bx8MpbAp&^A!FtuT5g9P8m z_jPjWQ8eSlR{*D#fcpIBUa~fmx5c3BzPu zuFropdv7(m$QK~vq1*oC4O|}pr3bAh;eDZH7wlHN_{75xyL~u_v$6E4zS(Y-AH=Jw zcZ`~*jRg8?BgaSWcFAyP@i|rsDPsPSgbp(?3W%A}{#1$@Y*r8g8dU0{RPzrdDzMbr z$p(Gi<6)$=S2N1u!mo_@=H^|%6_#2KTHrAox%yH6$X2JY32H=du0K27S6Z2bx>T!e zT;RR;%R1<@444UkCs(UKxBO_~qFnkTD_3Ws{$(~xoe$-`f-6P6SV7CguTIc~hMyZ% zZkM=Ezkdb9L;T)alTQiQwgl5gj{r=7<&?UAz$V9W?TtA)^+C#!U~kABmtgDAxyMpP zMv?rNsnc{kO-dMA16MJ$rik=q$2qczpkeZyMJ)^> zpbvQP`Vd!qTN$i943VL9p4dZP(ilSu#wCAa#8x9cu=InjeXA<`iiqSnTt->L6Q+Vg z?sL48+WmS!{U$?EQG}{+!*AqbZF@a$EcSztGKYO=X3BHz_o);I3G;~ROhzA#TQkru z#?=yvOXXVrF^H&gX5#$+2qMCLd*=aFeDhxXBkJqV=1 zYC;=yTB-c?K0e&{X50h`K#ggF(11QQ7x5h`h(lkR(80)^UcG%#>o6fEW{7Gyn7HqW zqjnenjQNPYHhMd{HzO8y&^%;vxlr6ClyOgyM9|Y6?Fddx^H*`H-?az#p8V9P-#&@~GLO9elb5mgIgwFY+f)q^s*0npcNyJPfiq%&3K znJ3stQ+OT-c*k=1eB21&DMe~S-I7f;a_Kv^g5z2($g)r(`(2yE3|@KUj4$IqsWfru z!Zez9xW-^|NJsT@66#U^(Q-m!eoVN$z6>fE2z+fk$4tpz#ZF*Yhp3F_ELhF!IyOXT z`@P)=Q9dT3a-5t7jk@wiksxWXjrwM@HP3NY3&k!SJQbo;)~7iHuP#b^-|w^Oy2O3$ zc+}q%;Yvt!ZvE7J2-8N^_gS|^3}|W7d-tm$H)_`PrzQhx)B4YdG1dzTK+QHSUaxm7 z81O;hlPE`r7)4m9Cts$(d0rAt_6W`bCd;B!jtCET5xO*k?`DqYp8Lbf;Yj&H?4!+!HVt&-lf;jr9SkrZ)*~P+mW=<&tDZ^{}Xg5f7~k9R8>Km z7m6?H@S65v0n^f&ZW&yML2z0wSbRoKjsIl~^d0Ju$c1Swa2F(j$v=s3R$PERQ#;*H z6HZ+>+5fUKGb6WtAaY&VRbnxssW&GWSw0(D-OfKV4EQY4ka|xM~y`C{yE7Bi2Qwy|G}VNFiDY ztrC48^B6*!mKe-L)c;N^io?MwPBf=wE>~&R2$plEniFb~+$T??M>~)YslMWz?bw0u zh7 zhOz2EDfk_eT$-a38kwLu4sxIIDxJ{`YywAVhD$&x@lWm{gfScV%EH7#PW_~&R&El zKSX9e*rarLk`oOywwZWI?(xSul}{@XgWxzygdB@8`Bz{S=l3CKg4RR~o5c>wGAr1Y ze1dq_B8dtpgkNGx@uL%3MM-vWkp@cB#sisw)ggNuzCTc*hQ~uAmciTPNs~4mIt7YK z`Je`lMXEbm29}J2gWUJvOH%(3*K32g#OyIV zVS8Pyg{eTrc)8r6F6-fLs6p}Psv($yl}2wB=9T3%sn&h2WR^dFPM8%FUa2z*a$GHf z;AD%b)Pr$8!Kw@imC#%CsyGp&uO1%TYv!t|Qs&L%n)M=C>a{LL{@#-av7)FpP)L;u z9we1KE^GOKSEm{Xf!o3+5e;NbeCr(Zk>clLMU`Mz6pIT%&`*mik5y_v z`pQHAUP)4L_XSxC3HIKieeY*DOU+ojubi--3gj-{>go$MRTYr`!r5?*bJ)_Fv{0Jh zCUm)%;wxced*dxeY(S?vss?e0it-AF^HZ+Y7}!SgdplT<0no|RlJ((e!XmItesD#8 z))&D7d`)jhlC_Y9t3LQsKtd@KZ7@J5--zdD8DQ#hQp~^py!oI=oy}y^o!!_|4*w;H8t?X7C3(POzP?lEU5dUV;RZWYM*@L& zNy_1+$@HA@)7nDm*Im?JF_L0TqX`pfW0Lu4saES}i=psX`|5ID{9fIugkkhyjmVS8 zaoz|fvmr}2e6clsF>}lPuTZBs{eTt_ehm+nHB&?%{-TynMSW!NT~nC7bl$eosDn%n z;ySu$w418P<*?&JUrjK-OQTJdknqO3fk21*e%{Cy7plrn9E~FU zh&nl=KMBfgSo``?(&^g{^V%eL&Ie!sN{|p8ZUa}~q8Inl>S+3Sgo30GLXgc*AI6uk zcU09lZdw`BylP%5CTuw;JmhaZ3Y#eo<8>28U}`dBXh2lM*7^z`KOfs}eBLsqZJh`| zZQ77wK{CHD)|y(3fcn5^Nn>=Z4IFGtp*5G*;THHy^qI zG99*I0qAd8orlY;|K2VjW%F9{_n3X+KvmyyhuWddE33f!BS1E?qPzN_%&F|BC_9auuy*dvaB6bim3wbQ!{JXc*Ls2;aw@{~CkylMA|9 z`1_M~inzwaorTVfTZUWJh zk?HKzDZp$>cJmfd(UeGIdun{LEu*T&sp?e)(y%h32Yq@tVX@B)$SMduGCHa(Jrh)y zhQ!b&B83h`agjmY{p`Y7s8zj*n#O8&yWaxBKOyy)hBRLW}?)m;X~5dC3^ec|30{3K|~R?xGOB zG?{MzT@$2OuEjr?y4yweRxlpL+Uj2fI#FUyG z!uSIW*R!%(F5ox)4up*@6*=}>U74?-m;_yhpQ*{84*rwN;y~jmdbd#2Nts|0ocSvf z6CJSE@|c^_|1Z1o>6F&kQ>A*P)l{V>S;8GV4u&6MMmSoVGSF{X}u}$wdqK^KqT|m3z$)(&XC;#*fv^K-P+L z+~+4#2k4@B4#ndDXWP>zXDTNyiJOoWj9=d>f-;)4a<@y$lZ()r~V>$K0plL^*d z8<%g8kw+WD8*D-j^)00HJoGAzoz~{T!cRS9m?hwI#j5nEGjOfgVYC&rjK1HblRx|c zP5*C~tB8Mo3#cEb8*sIy@BF7WUGQDjXg4cU0ry)UFUAs>b3RP1=fA-{KtZb+CLp0* zivl_nGA~5v-(*cmTd+R%SEU(c83K*PoekprUffL&40^z@*Sg2kL2B%Mv;^Ph zop{18&4%sIXDi&s?pLGprIHiG7(jbE8o7qo=2cJuHL0%mvOMMdIi#zfg(O#V*6 zE(S=*gCW*JAbcK=;HGVd*>))T7j78t0_$@MSp7qd)i&JK=-6ulUwbJ0Lj zUe=qmRiGzYZC$Y%x-C2KTRyM6%{5|ya9dxVJ&@2eQyOD+1^sbq{~T?I z*)_k6awcr5UK|mVwQ7=8=ygw#>BDPP0Z>v|pD8z^G?e|Zz2sjG7)pQqR;_+XD&C1N zw(3O535g&ssPF`~d~Fgge#fXn{P%gAfPd_5ziLG5Kq9B_G+`N|E?0&yc|!!$`o6nU zEYhL8J;uBl5lzeihhkPo%78!q#!6Tf&-B;Vq6NpOy^*rXFqJBo>S+=5^TdnAJ7aJ} z@t1?s54tCb=mP4p0ylk}t`uBMzL(M&SaJ)>Wite^`y)t$mq$|d$Bk^!TQ~7jbsEvU zA4#0Wz0{NcIv-MV{H_`E_RexA`R+!Mucp^%>5WY!F{u`lPGnIJu6!WN`5n%LzDoNU zQe7q%X12K3t}Krw)l)!phTpCmCJVj%tKFR;Ed4u06}>5I3d)&%I=x1;>3ZO1?#p8~ ziL2RI6U>WtBSzjGmDa2JVF{Vf3Kl`7{vdkx$PZ|9|H5^}W*djOZM+|l^0dBm2JXjs z9_U7`9mm#5zOOI@joSFCa}UTHCg*T6kM9_oHp-3qDAu&o%9pzBT9L<$m|K zHurG2|GU@x{`kPM?p|#CLcW3Al5y^LotXOy$xrCV#`mCw0X64@kd$;4kXVIDQT8m` z{}_8zu?JTp!}ZIps-9JV?!6Y>^X1lejFlpn*Q!4Da<6hm3yU<_hhG>^^_^U zq@0tfmo~^vZPyIYeE7NF_6*=>(j<5jxvn$K<(3r}al5`<{rk(`Ax!B|njg;T_7e+o zBr<%`k{G`9=efttZxh z*lE7brbVLh4g(m!|8BBhM9x^CI5F7Rcw!#ea&vUd>=e8i@)D1qTiE2B5kvh3@tS<^ zxW)J4IxX&2!1ZD2S>_zXM^c&nTwG`$R)C;OW74tt9XU!F;JFFrh4Y-Qc0}rY7PY*$ zSHtP5sHi{}y`9$IdQK@WrX_Lx-8{b=t$h@v_(pa#j$li{Ym#;htW+B#| z-dlCxCvOmyT5fjPN7FmouW>q`HMigkIz`{iu424Pp@^X4C<6~-dh~-X^@0R^oXnXW zt7gE6+rlc3Gzsi`}eR-FOtX|H@-y*jl2xDJspTE!Key0askfUT;{NBmB?=*d@|3V;^jIepe(Hv`$lLn?TM|ZPD!jhu*_x0lwgHLL z56z}CUYL31rW&{3GGOy4L_H}*aotC37B7~p2;@j;{1o(Bp2o^s(-Q;lD8{@w%CtdV z43FGWb7^6*w%$D2JgPBLxQHM+31gNBgiZ^%WH8$`Ey`zGji?Ok5CaZ= z!z999*Hm`5>9!zM|!@tP?ZI)V+ zU2DrxNOgkWngD3yjLCw>h>Wc?mKzemn|hWmJ~1Kz!nISq>xqz%NFe!*rNB&c2Q2Lg znPJ9~Bikv)A(NW%sVdQNG5@p2*Sn9Oyf#5~sMDKlwtZRUY<0EBR)&=>C4xc@XvH) z6H?65n@yjB;@Bn=F0aFyMzXXU;$>(PFpRW>r?oM4C)hD8s90ohvuu>x@taLJPj9O4WF^2^#(CgtNI^w}_e zTD)qwNQyib|K1(~Edj!%6* zxKblC>o?u7mRqkTdmpusq?RGWFsh_>n+*FON))9Ci6Y(8-HJwqU8FC6Q+^OfFeO0F%v5C{{r2r)>mx$DV>iRDdgj5p^9p^^8a{F2=Sg|- zTFsh3gWt0gS$0jejY6dz*l0H)O(p>SMY&KRd z(~4io1Ot-LB_F=zgC%1J1pyb(*Ui+$U-B!HxKgL>Ez)TiM%hpSkLHuVpnD8slseNG zF?zB-5w9S5F@Mv1XL+=!xdp1pA~B6YWoQ)xFaTwinWT@S9`>})bars^Qlo}xeTcHJ z9$_3!XFXE}YFT)}AcHv;c0|_-8{05go4L_75VQ&oVltL*a5wHt-{9-A$E~0H%gV}1 zs!KU+W9ooVM!D_KY9&$( zk2>6S4XOU5W75d~J5)E!Vq)8es!!i&^(1Tu2m0UBkp+N@?D_ky z5_{*pM~zc5I1}-^We~c& zoyVzGFm)dO2fZDEt&!$pW>SijFZ7eiLf1ND@c3(Y2MjQUFJ8ltRrffug;7QHrmvXw z+iYX~&xYZ;KLc{d`N`(NlByZiq9K4@y&D?k8R&keAAPDTXpA_mvNdGtp!1Y*XM0rF zU3~WAeQ#C(JjaM9GD!fuxa#C+lI;?hBHx5b4pmy;7w(*oPv-JlR{o->k!VSNSu=u} zKxH$gHd}iGr4)oMg^}DmTW>l%ti5dCcZwklbLllnD)hUl!*w5*f8%-YUvKQGoZtP_ zs_Bw|$KQE)2&q7x>^ly(F9~T&gTx`oNERWf(NorzXiU#-SA%z3XSMjhjMtf(bkocL z8tHi$nXFE$BkuIee@RhptH};XVm(4wbDxBAWoUJxy!fejty=J7qFX;;|KAzVGz-71 zfML-tdPWqj98eNZpBjb{?7~B7zmp3{O#1i}cq}J#LGnJ`J4kahkpi z6eT4jQ5S?}A&6>fU1m8W@qbB=n?JAji=W0pS}EshWw|ih0mqSZ+;iM+4Ub|J4olOM z*{Wu)Z1@VyF+*SmEIXPmUfIN|avLtRP{xj9DtUye4Yi;?Ls=MK$!Ult}!Y9bJi=s#w@$B#h|Yrp?p!_z-uE&Y}W-Cy!sB`R2_OT#l+5! z&bH`Y2qX=|(S_kTzPs)9F_Aq5{`q*yDaJR1$wsYOLu?A`@Pr=R2@3tN61zz^ehf1P zBs-qvE81yDNz*O`sbVDG3V*UcTig0CP`ux-0GXdT+qst3;i;T{OHCC3>Z@y<*1fjg zYJ3)$Yr3BQs(x@vOa|OXP`fP|dS2MYR*8BI8KG?#lEQISByDge{6*>oOIAS2=z*V6 zp+<6K70jL^cNtBlzs%w6H#$gW0by>#${QJUOj(z~>=EI8VK+xpIbxZ@e-CfR$036A z^JbEJ(niVJ^pcViZEbCc!(t1B-9k=e1$_4`x;5f(;fglUYF9w=*N%Vg6S6qgY44K- zVIeus<9Eqoj_mYaJNQV>ty7PP#EDP54_<^1jW>UH@$f&qrsn@x_9F9NU`13z$xGfV zQxL$|Ui`>?{Q0o6(A{xIPQ*H*))2^Gu8NySW|sp~%W$f*9QnVnAX~90U@oqOPfwLm zAm@YlJ4ZZ_3?10^a?xxAEe;n(-!ZF0@8*MB=D@JE zPW*zFW#F3~I>M&_=^lZq!9H(b8z#h-6l6=b5blP`;$W6oL>QT&WkGy^=dScs$h-n# z4pH7cJnXQfiy`TE3Ou?Z+8V|v1m9ZKWA_oxx41};KDBXMxa9$nFzN`D$@|_iBTAf!R6I}IPhIex^aYi##DOLT|;9lAC=8u@QQATz=3@+VjdmTDVl zxJU&G$O(B$JP^wXzMm{4Qkm=LcMvQwk;A24%T>flKXPgJ<>gRVWBC)I0nZ<5BKRf^ z#IGMS3`7@t!;95OX#575%~z!}&5`y&UCem5h9yu@4&T%qGM#;6jwZ7V#bV_HA;eir zH&4g+<12HA71F-9>3c^S1ElP}W|(_B6Z?@c*$&cjTqDMK0?O6tWQT{MbIopEvGOLR z7Kh<)TzN$EZOL<&E_IY(cx@c;0-Nv)pym?UOcYf#NC8OrEV_X~@qoc-=}rtg z(fp=!9BFqNUP4#%(c50*cf>rAL@ubgFDN8Csay-;QHsBAy|0m9BXbrFhYBr*Nnj_t zq$o#Mvef&_mDaO&C_?JRFLg>YnXS1vy=LsCfpYoO@6XU99T1(XqXLlC!tK*@#Q7R8_LYVV_9GT+& z*F~|L3_Ig)SPg+9$F&|5O<(fW-#uy8(wg%5`4+F#O$?A6M0rW;!@@zG+Zllm%=_(H z$memaA~cn>5{8ZXA|8wWtjU^bX=xehecbhDq6F5KADi~JK0c0OZ)jZ#j*w>3a4Vj5 z>lpepLTRYMMR!(+ZnNyYa7*rJCN(eF9T2|!Nk-tK9=9=7LtxQ!x$@hlyx27+LfTxR zSk{X9Qf+5HI=z9qG%aTqzAAQnoIbkHbZn}jeee}x4;$Y%dfXS$@xR9jtuSTFIIz>z zAx>E?4I89h>n?A1OU97J-GdRt&Phj~>e!B_%q4ag7S6dUx&0IBuf7xR*8wkqNntx9 z57g%s$J`&O)uS@!iw7IyzNa(oqB^aTD^CzXg`qnBH@wTatwMPpI5SArw=OtSYy!Bp zQVnw4?fp@ej)JuH_qS59*{5Z?nTvK)eG+H2{xi&WO(zYHmdR#)D2RJ+2E46pfzQTL zO1a?#o1w2W-Nmg)wv6$zh>N4p$3b|3{7s(NOq)w4^US6}2NkpD9`>!->E0jWa0(pp zF2I4Cvg`hLxAd0CPw2RB^ z`)==!?0+YaSfZvPhw}*)Gi@lI3z}P+9APY;Is`&-IPdLX_|3;5elk%X_xUZ9?UgkQ z{xG+oQ@$nsuYo(19W3^)Z2@T83*UG(jfs=JggDlgPuJVqSE*O^5^|b7(2YISkRNwz z*sVx%o%L_h)gkTy2iV)Olaq3D`j|cX*iG5BLYN5TTyGMLi;!A*xE;0QXE-uMq0?B{ zDrKkVf+HcvJ-1kEyL9h^R?5}2d@z{`K`kYmx6>a3WuI6*`&jEt+(U3smr)g6?K}?Q zg|&1KVLRL=ICv*jl-#P1VCYWkjH@NJIm3l{l)_SVLh{mK#Z9&ezv6NgvaKR$G6>K5 zNFiX;-RbNa;+p`e+2qjY z`i;%re-}>}iU~3^FbPouXek14Y#`J#9#Hg>@6vyt^G>_91j20L`;+B8-~n0>Z<2dF z^_{yPr0N6NYmRz#?2!tbcUg47Z+{6%)t-8tC|nQ>T9`W>(;7)`J~0>kT3R}dacmcZ z*>}*(M`3^?0cNw1ROslzu_Y+2wplNAR$Vs*jes#RF*Baf`{xFJU+es8v4M|$ADB}* zGnjWe(G_y_cCnfHcVh2T=h-Cp(0`mwj%d3lVDK8Bml$Bp>=#MJMWe~Bd0Yn|he}{X zNX$bkNwx7~G^lTTQ#W~%GMQY*ICtz^4M|-7Cd?yPKyAUIRP%!pCLS6aV+n*Rjf)r@ zTSpVAiJb6g9$L3B^y7isoLbDnVIO?YevPFAianKXwbQLij%b13~=0)auo$ zEO^BOaFL0S!bA;ah9DrqhNp_0T?vaHV8ie~v_2mC2d=Q#Na-`&344$f9{Y3I+wZ*T zpTBEzcKQ;U{o@4q-~QX5`-{K$3w3xLy}4M$^8(opD|HQxaD*U}c%T_C`_Ka86M%ha z*x~Wi$CBK{xPfFzh%O-tg=!5Y8_w=6MW|g`)^# z+7ToE2q@%;mrMa`X&4J@EQK->!bA*}LZT>r#6&>k>5?M}F-;Bm$xLez1xjk7Uihd) zh*+c}G*MX;m3hPzJi~s>4DKz`P`_7yGkMEpb!c+3>cG*Zbg0dzX z|M5S5{1cz}1STPx-y>U$`5gA8WYN{uI25E5ThxzWtzVESjZXmfb+f9|Yd4_4BxsFt z#787$>^b-`z%|!gd)sZdX)mJth_3w{U}}E$@Jp^=KYjkpS09_4o3>daE(7H1Vd~E- z0vu;Kn)>jqVYgX1plFrQ#6(RG3?}>)OAo$4s@USfW04ShDr_az#K!vispn5}zK(+x zHo%eNM{z9^*P>9!ZQGfz4fGQe%jfNWXy3y2U_^WgfawvoY~FU@;UBjY*ih6$r8G(( zn!-evf<$;&MIu?&$VOPykXahKD|(FBr6ebLy4K<^z|u!YVJIlBFaoq{fEPdv5csDW z!B;TI(Tj4#N2I`Vkq|khgitJEqd)*=JWNRi=f^3RG)tQO+sxl* z|LGLK4%qfBz{cv@i8sFF=70I_>BIJ@WqaMqXfMba38Lr!;UE6t2S4~B6!ADXoCDa{ z?+dM({j+G*<#FBDRAMcGRuswamho!OTVT{r0Ag=|9E<8Pd)`R98lcz>$gR3WYm`xO z7K;|ZV;H~w^{?aYU{_sr)g|y_0MyoJhjEBejErYhQ?*#Ax z0TMr?aXnWs#lK)F^v1?NVNkU3D=z{-z*4MLq>!D8C{ovix{FmZvdIdeDO2g|R0UB- zJeAQM8Y|{lEJi@x-M@jst^do*%Qj^D!EW*JBHsLk1ERAFPeDaDS}gm+W6<#)fVGts z+rH_Mpoiuc_;w^7(uO&(MNE{f-}sT3>((8zz(-7BVlf`<35Fhj#qmCxA}8d6wy>%g zVO{(JM}dwpPii7zVQqC0rs*ETMLC9uGB7BDkV-Mrm&yX)MN6t^l^hu$_Y~<80U~J% z5eAvbM`hC`^2f@`%f1A_nezqv>AWfVR_q1)9XvyD4T}WIT0`hrj59A$hK_`hHUm?O z6?j?c+f$SY3?hj#8Dwh3OW72nBjlZLh)5X2K~Gb$?Np?-%e|oLD#9IvTt|%Q6v?N} zw#vdLzAf5VUO9HxZ8!h;_skq!xFlx(tR9B``|rR1eeb&mUmNodWF8E#20&{GP{#8C zVaUcc!BI1o_n`%*_aVlXLf9PN2T`0;Vqp<$kZ`SD1p&PZ3k9sPY$?{kf{`t2#Hy(X z0TpLaYg98RIRH+_54K)|qhm8|Zq8nQyYj@8rW zp8n%6&tHDjwwzrPL5+P7(KupsZJU)tZV{|iSzuV~M;2BTu^{*=j1vP_6Nh#G zB7g+qSuB*1Dw>MMjwZOEm@69zs)IE#HCx{HQ7PIYTv2Rwp)Zg<;MO#{l!acjN>Tt; zKk8v>E&@m(hQ&fT&=diR)k*?u)JM56HF;EPnu?J)B>>)u zfddkror}kVqVwY?G#A%5{1xYPi4~!Yb$`&pF*0~)vlq(fLVjwBK!sOC5+8Y*@<2@y zt|+#KP)a>uI11KE5|zV%$;RrpGDpG)Xv$GzSz0w*m#+M)WOcyoe{+3#^~fu3#tXk@ zjxAm)vwuuEaDaa9x#w`}|1-}#gVw=Z1#3HO^S~X})YFCS7-ed(h7!{o3Q~@R{b_+w znJl6cpeL=b;~Ug<2w0%Of>vP1YBq;k&4~Z#KsXcS!c&4Kend1#yICV?XjPHbkL>^x zifA8Vi2u@;zV!1y|MNfjlRt^sKUxs&Fd~)hOaq$@ug`&m_xCN>5o;jq7dp^GkJ)nCPc1@{m4!$44yy<$og80gDW%}xtU87 zQ&y_(Q?u6VTQj*=(D*G7nC_o^_IY%4EEhK~;^#ED1rA3d9LB;1tp~JH;Hev7*o_S# zehKw3RcRogg1SU}vylrJicL&^teO>t(16Jllr?ZO_RyM6o*YYnRGqBvj6eq-yDy?qk8D#IfKZB%J#D91tC2s0I5N7CUvYWeryxQKaWSwBWWwG&6*T zY!p<4vd|PJ)c4_sA4bLRy6e^0-&p-^C$>+HUfJ2>hmXGGhO-YpdFtzrO)t(N^Fww) z!G}saAJDi7$%at>prJ0?205V{$s)00M)Onb!d|gV%*q{|Y=#m&2jYuKnejNz!CP%rz z6lD~Np$m(Y0V5&t0oJ}D;3XHzWJ9ImcTfU}N|fBfV4?JwqOxXwr=^fXCe4Pucp*P?=e=?aw6jAYec$Vna82>>=Z z`Vbx?b>YGV44shC%<3h+M-SWX@?uW{70v@N`G5dT^8|EQ-aW=4w`K$>DKyt;H+s>; zJqI!>iys7Eef8D1-+p@pG((J<2KWCQfBDVl9)0T6eUHs7&0Bh?V`>}J%&$y)(GOXn z(SoQWKx$MpQeidPP<2`ZCT@*N2B7dNilS{rN*MxGNfgRvk+mrF@Nd-+Om+fds`QE) z9MY|E?JuX4I~No&G>eg$Y}qXkxGF~h9d<+fm>4e%CSSpkfg-AyDpg%lE=oGuJCwBp zvl!smil;s_ON`JilSgcA^|C&NKbZs;W7G%B`NX~k2o4H5B0I2FYMp%w2PzBI{;Jh3PeDwREpLfY?O^hi*fD_hT>otwiu zF>nUnRXveFpe+7ojif0AI2Icsl90CHQri&G+KLANdToe^r>W3L>0UJ>G&$RHkC2yy z=q^WD$qPL-6sY7nx+`z1TgHhBD6tpHxMSv-j1TxN}zWUX#zU*Z$ zyY|{^QT>rU^JzBb+~r4)z3is5k34ne8;{#LfE{X3A^%9V#0QT;rmroIBp!N}?1O(1 znryCW$VuTJw!(k@i2(loUVHBBTmA77v> zyr6DHCWrh1OoUM`lo^{o3%pzkSWyIQ;D%_%l^5B(eHgHoOjJb~AQY*IddU!)?dDzv zDNF@eBqF)_&m zXj{k~Li8=Xl6I5ys8nk=J?+5ciS`*$Y{x1+sS`l!;x#8q`j+%B-kl3goA0yx!*)n7I4yx9w+0Ob9Sf#f5*I17P#B2kcz}=v!FOU+3oL z@Nl-h4!5O>Fd3xR^pa8n+6^!kvBm;-3e-a;&mDKX80YBtDga-DTmnA`w(QfueEJ*z z_~>2m^gau$n?w%?qD{S#1DfPk|sBXS<%F{MS`j(I)jD6e8_$Y z;9DeTYVKMJWlbn4(CRA(en(>Ce2NDvCYfXbS6^s||ww578|F8`3{XF{x3ZI2NlAC{)`2=M}b8t0gG25iC1+|1VF{Ha3C$PuO|SQ zbMQugcANeRvt#L0QoVAQH=Wrty)8n z43}2zyNZRL zj8I}}?kTi(ty`&D!ClCxk8%%50kfGuGUq9P2{yFy1i}gMCx7xMEMkoV2W2}z*Utn{x2P1%TgI=1L2`_LS}Rc)ulK(N_URTNY-8T;Z&yeB z=76rMqGQ<)2WN5rq%9DzU@i+K7sy(saf{_Nru0+nf?b^Kj`+?26Jp4Cbqi;aUHE<0 zYj06pYPe%ShMsE*kmF7jO=NiFkw?y*JO73^ydI6|4Q>o0y>J|zI5cy7Y4NsemLGfO z+(S=IE#No7X*5*@s6bt+F{6@(tsDN|PhQI8IT zR~Mar{v-!#G%_Bcb^OF}zhBwDXzE!I2uoxm*b=B~l@bB1YqGVPdX$lw3=bY-;1S0)8&k_ZDqu&9(wYd)k@_s7$5Gu8)|d3` zANBa~hwuIU-~WBgC3yW5J-KzUw#^G=X}qn{iVzlZBSrgI-oF;uj}rj&8-K>n3aW)| z-&`!VC6pP$E!A9Hq!8cz%B2m9KmSiV+~MkrU7_e3+g&ad_#rYcD+Z?D>bE!gGN**)48&$PTbp=&MSvvCnZ!wt-Q406nHH<+Hd1`HCmA}n}BOYS7boj8{ z{L`^}_K;Vmv^^?Cl$1bJ?v^Za30=%RfF4s9+g9}m(Hf;yxmEHCKWIbja=0pRB@GM# zKo_PWRKX?kPWPUBFwh{RcA`dvW2(-imFHOeyQU>osi>HZ?^Bdzu0!PGJj| zT`YD*-^n;sq-QPAyGOXaaN1hYS_xa)ozmKhCgOq$?)+K8eg8ksU;P?%N5iZ~*@u;; z4WInvCx7{ue+3b}4_l>dl93k`nG)4hQWNFuWZMKwZZnp~>jP+kJwE}!@Q3I8;PQXh zgdTd#_%ZLt>>oW0U1WRRBA*P{cLgB9Sx2)u+5eCd&t>7##O%KYSurT<9*adXG(`aS zQnpysqmEzu+SjhV_PTHTwik)KU>mt&YKB904sg4l13dJkp9ADE+yPkSsef)buSxdq zoLFqNz9Ni{t;z^!l|5&(4Cy{3tTk4@5}gFE{ld*Z6ZSor9gJ}HeC)F0cGvPQEh3Vz zL*m`yK^mYU+mM|7uzu_CwJvyy;_y5La<_OyyNiUPlAtK9!jQ05-{F--l%m+lgGo_< z;)>Mz+WPA1njK#z>`h77>E`fO?A@DAhiJ8=y7VJKuUEB#t!YV_L^DdkN+xLNaqB&A+W=l;|-<9q#NNtLAQfE2mZveua1GE1l zuej-!fBD`1#81O?ZT+rf4n)rypc~_%g7@6>lV{GHfs8(^$p(8ka)Z#bmZ_M@z>AIt zQoC``*aCZa0?W^L(cA1#lj%1tw;Hv z;qHXBc1S595Kjzt-TUtQ`s-fzx?{(Vb)AOosFnB98Rr0p@3?;Dsgq~F@%YsI3@%Zp zA=YZhamT7_N7?4E&kFFX?KNtVDNsx`7>wFpFM}0e+ZkKpF4W| zxVK_TvgY1)!0M?*X;M2#sIm7JO0HgTm$8ToPNyA)TNnvlLAIx_XtRXREH)^MintwM zs9Tp79x~9O#oS8c$}NGrXo~#7IW(<+61swHM<0852oS_NYf%w50?c+56!-maT*MbS5ZIrU`Dw>!-1mRv+i%75{uge#!qoWh)8!%P4<3SJ>=REsfgk=p z_Sj>Vx=%Ksjl16&SSSXjUm9i3)R z8-k|+2!XSaA`sH>Q-F&&PZ+gxfM%-H#JwwOAQvVTpLyolM<0FkO>cS=8oYIL6tPCC zICE?f=K$-c&!4;R@yY3_LzuSa4#NtjY||M)I7(0qin3nlcRkh;y~XmDn7IcG>C&He zzxB|ewbeD;{g0zm>K>C5M^7BbGdgWIR05Ht<f5h@5wNd2rmYaAOMpvvLxhUg#Ci)vhs4~>dr;b2=} zPfh@E%^kCUJnz3bvBu`dg?}u=^aFGhES#oe27eKwsV~&C;ndH%+7UVMR{>yvW5h$& zIY88RQSG%JIm9%WE!HtE0nIfqQK=#XoWwu->@#=YeK(o{jnLdEk$<#g{9OC!;^CLx zuzvQ!*{?k|F+F9c0eZ7$IvA&sCN^D5EzpYba1rGe^iEYh-9ab7hnS& zH#hN&LOlNm-+39_-cq!9&8uj8R@@&Njy#IpoDe`P)LL}=8 zYjljo!f_yN#EUa3B{OuyL^)ccKDtMJlz}OV^4hMjWj;Bu#Z+u2Koje$%%O}e(Dd4p zCPrsI(Y-AYu00_E+z4bR0Jz)E4`jFsW8Vqb`F3C|2!q9f6vY^KTk@9qv#Kn=tCd;+~+>$iKknUp~u3sDR3eY*8)TsNeT^%Mu1sGgbA(i#b+&f#Dd6}Qn z{fLAN7PtV=>bTEJ};?WvyyIl{tRdadi8Z`;r-=Ex73zy2~nk zB#HSz-%MCu6e2##u(H^yY?43|(`W(;Yb-{&aE!Eo5s5XjqDj6*p3xC<(`x=<5f5}t zFsu;}{AyyvL=xyx#1a8D&pIIb0>)yivWKKFvDi!_rWEC-J8OY8{{Wzd0%R^lo>@JV z6(I4@p;;_64#Q2TI1=Jeg<%OhAPzJbe*LZ3Zk7v#W(zQo))7$Dg~e8-+$lto!qf#& zk^n@x=}s%w{8LtPB%sK%;OV!DN?z$2kjjISB#@a7l5)$V3-8rq&W}EF5$T?EVP==IFTm&pr`r2MuQb_<3$mC&2y#cbq(6-p?sMz*yLy zv<5Ad0OKqGFt|V#EC!7-W;|HXtpP4ND~IOMIR{{RagB;7X9=vbOwt#QXn9l_Kj>Lp zTzJJRE}aL1a^qoG!8yRf+~Jp8@8NO9fy42=_Uf9SwT(-R8 zU9Y<7N8UAk#2%}_FMkqzv_CYLz(YTO_GkZJOd+sfUWD7$ARu?G&EN}qchswXFiNJ- z)D(bm@t|5@mrnrDV=((yXHu=uS1|jx%Z=K7WOD&}o;hIZkI4#j>||Wmu~v#!_`-!J z3^3WK+vtL1;5h)0MTSTU4EW73-YY=l!LF6R1m>e6g|0Qg(S*vPO|WTj4uB^D-FDk; z1>3<^c5w7`4lsM?x>ZFIgUk;Ie1fvb+jrEjrh<+ zya1$)zM^t`>U{!`gkIugQ6?0*z)Q-=5M>}$?3Y31$mMhP=uZyW2!LVz*a?61r|pl9 z5Fv!Dt5S+lh!jQw_2TK*!zxa=$jjAfB?gp*FObof_z0lPH5o`w7_nj0AORc{_q!_ zqyO4JIvgA^^#=p{nflW(Z6p_-24D?Mp6iqZI0t~KI0rya_MsUs2%9M#4yYVkfX%{0 zq=z6qm2nL)0TNJaH1e0f{N)$F_{G;>e?5F@BJHTJv4wCNGe3LyWjA7d<}2U4xUz;v z17V!y;m0mW`EbOFSlxYWPxUfW9_uM!Z4D4XUTjs;ohC=sj(M~~FMu=zM;BqBR91pP zQzQ^EFvTLhq!c0bnF`H3wWBuAR$soj=n&m$UjkaxyzA) z2qlmW#R5sf0v0jRL@?G=l2b|+Wh$)V6hIm>s0cxrf<#CHqDwSUFDp#IM_QSrq(-y? zh!z@QWQ8CXMUe{ytm`oBYq;Kj;k;iI!*k3I+1;)9DX@JLZr8c;mjH^=U=VTDP!SyQ zghnf57MVH06=Sv*m?n&xSpA5BJ4__unjxqxLaRuOLPLnYn%8r>j9}N1lzfbv&Tr+JE%M>wn-alXG@d;xE6&J<5V+0-yi< z=l}J;{=d(jJ&UOV4-HmLeEsf=wNPFlbAAN{^{gO^l(`lRJW)TEN2CRI^8^61>egHT z@sr=K`~J}_Fz?626vHC=O0xrPshQY6L4`~pqu;i=GY~NX0FO=&(}>&%gv4TN6f)Wk z8Y~n9hDLHB5P9z0xvziy>v!IH=apAp$@&6zX)LH3&H-koaWBx+!tCk4d359a%Jjml zp984cUVYRDpBQkcX7$VxjsY5u60&afl9AAh#X@QPP6&MgW03%PK~PY-G#TjFWTRu$ zH)UyJ6c2Rv^qKSL&QJNbTR39jCWNCWj`@+z=O0WX=1D3MN+70KM{8@IMLwRAaljFO zvQj2nOi4)d2@Bn!AecOK52zF((n3~HA>~LRuDdYQ%MnF+Q5YB{GQfouiAr)Rgal%W zHLLd2krQNQ+$>gZRzj9a;YTk*O1RZCpzzzKkg zQE(UgxiFOKD-p0g83~~CVSbOkjc2hH`H&NXGB7w*h*oH@ps(*@GTW;HyjTkgBtdS< z7IHLEPZyeGl~S1JpMU9xK6{pk4)YK828P|sk5=|V4&Cuq;g$=RfmK^7LW{rQSV^r%t(Org+PGZ zBbdknz@pYEMututT3uPio4?F%--_6vY>)oL5AowWiCUv%4okQIc(E2L0wA~MFAM>K zNw0{OZY(rp`4ZV0t}v0q9aH8pgk%YYd(@Mv0I;YvQ)+riQwT*-A))|`VAOjtRVQdF zC*dhz9nmT>+sZ9SVhFlQBm>J)h9)`|CR*+CX$@sG@{*9jX2lZq)VLBOVr7v8fia{B zE=iP$LM%dVZ5ik@ELFjawdxS#Urpjhpp^?NcDTVQ0L}r{H>PH$@#cHXv=R~7ux<^< zM2S)Ug%EuUWGqHlpsi&d^d@$>M?GB}+_xbLHhEDHA%$3k+}bkGXIQF&7i-nYfE2A+ zjA&RhgW*lK!1mu4|LK_>V>dCHPE6oo|I^17um9ifzUF&h1N$X6`{zZ#C!c)sCw}55 z9(?d2Q~{SyQSC6d$85?jjPgbx063%Iw1GHpn97F}l}l+}!3um!u6OMO0C(`?T7K*h zXiwa3v#_w(+7;0Ac-s%Gh1bqE1PHO6A~FXZFD3R{mtkA zUy&=MnYH-DV&pl;D7Y~tWPu#*fX&kv@rnB8jU#|h8-&t9el}lA+?bTh~ zwP|VfQr)e+TQVRCNx;DvFE9*@jSUvqFr4`Zj0B920Lc>eVKGPsGk|=IfgkL17|VyT zU<}3};gQr5l6sL^OYg02b=O{5nN^up^SSpMai4hOdEPhI%F4`spUR5&#=YNrAq#Z8#9UWipXG8-_&y`3M!Ey>M333oFxYg?Qsro6QpPeaY(@sG9!Am zjLDygiyDX{Wp~v`XNDa!B|sMG(|DyRV6+Olei+T+S(=Xw{_sP;V~6$}|FLgB_|8`& z+@P!fvuDq~|NZZO;DOIjN55LdsUhBr=slI+u@+T42@o7jvZN@MYdkm{z&KLelo_V$ zF9NHx0nlom#*T(pO(!PCxvRe23E;%)|N6xQXSBrc+yhuA9T{QYLR$L3mwTOD1dvy9 zFPw~Q#R)KciIl=GvnKOz>=REs@ys*N@&jR<4Bm))0BSXP>>xk0GX2&-3{S(lji|- zMZ@QBgwf^EY6yfS#8q9PF<}CS^ZBOJ9zxZzhmFuxWh0T>b`#*X3fT`eR_JDAN=~WKl|_WL$ah?t@87t zoEWt;vmAgqka7_KLYy)!SN{Ppo#CtZETcPb{-KlEeLu68tA2AmVnsJ) z4}j&)llnNo*TAmK=3Xq|NEHA8AOJ~3K~xS>--J&5_3&^pHp?$ti4V8ifQnOQsi3m_ zH~_nU_5j3TgYE$)jvUx=+by%tpXGL-trOdArOOa))zd$smBz2&xGY5xnrI|Uo(77M zzYMk+YHXP~I~uTastLxrRbK1#*s63}GIK1O&Yx*|bCX56(hU$U;fcjy3vZ=6d-}}W z6`qH&H3tXh{<#966Fhk4K;swMmNlo_JQskJ1LR7rpsmVT1gWg6k`!hs+ao3sw6#NV zl+uE2q?ygEfCUFxLl>~BA%r3AEjBCUPwa=eP?1o&VrF;)hGV|TQg*YI3#L-a&X7P{h@?KI9=Ws|*Js&je+U?QqoGCQ3Q`$1qo zv_d6H>>H-+Qg8Lm)zFP1EG$cGh_)wT4Urr+&#!IS$~Cx=ty~14_BK9Uvmd7)o0wp| z6+;m0Fld1aTdGBi6LOFZIKzySxmEQ|5m^6*2+KnD%VzVboXGaziwuAY6P#ZD@*9HE zdQHJdy`u_(Wm5}zGz65si6jGJ1Z~8J({{ppZhB_d$)hL!kH5M5)h{#Y#$5g5`M>_x zzxvUSev}&Nrf7O5ivd#I8DSlj*w`6E8_if0i33wGNlD8iFeP(Hug?gq!Ulle!*X6d zCMroMVD(S03QiDu-6!2FIMOUtKItaBw8l*tEEG)c*<{KbY+9<(&Yl#Du)>eRU@8lI zFh!863_hJ>CiNHo!Q#Q+i|5m;KdCYA9^uuWjP7_PSM9u3@8P~Bsnae_D8WmWrc&-v zEh3-S7*2Y8pzP0AMY$d@bmVHa982;=H3V42R@UqrP&7oc#E0=MSJt8lWB3#b;9-_7 z7Fjg}08pLY_mpC#hg~CjbMBkVjc*opq@J*94PnOJa&D%Ap%Pn;C3aQ}*G$thB`2Fx z%zP*s_>IU`tq|F1v44<55Gc6 zTdQ0QyQ5|xh-_AsDyo(|b=#)lMR`G%v!g&X*#vWd62UCC1ryR3xawXZ3Pq!xDM%v- z^=v4-6`|D4rjiBBmMO|NLM{xE#UwXksuLU&o24k-jL0YrJi(%nSBjEIIvTO`wfUo=F(O(a*t zb7T1-zEYWWHjZcYOeG6m3K+{eB=bE(VxO*)1$;UKrs`oOINJFpPK_p}GYpUo7$6-& zkOtubA*Cp!etyT2mOZpkalkQdPqxfU2U zvsPUvi{i-Vc2{}1^z4S5alHiaP}cEDhP?`=;@DIX)fJ92t&VqB ztaYv&c;~$*e(XCYZrN}8hA*rdYh53j_i^zfANk0KKm2c)^f{OmNEI?t4zipW6%80d zsU8JZHuw-a0+t!W&-EUGRoDQq`sbuRJtpc(KVbE*&0;Vi=zOgH*Zyu3{Gn)$_IZ7E z+4hqF@Y~?BE^AE7PYQ(yy3A6$;ERYfh!P)6zWfhA{19&hI(hP>#sZ1fxPr?pPGc{R zZA?w1&tg^G^h%q1i_-26hc&nrqP9_VDnL^uliB|WMaBzCt}e(0Zqn3x z;R%e?_-;NUWzPX#mYH?aW$BscV24%eCdhoWDc)^3J_;4jE8IvE()))QsXdnoW4mI? z8q)q6Hyk0hm`xF`w%uB(9PPG~$>6*~J5Gtk8$&O_c84^+>_}}{_6%RfV z`!i5a=?P$L6T1OrWK4XgIj_V^KzTBU6&4Kk=M2C6E|>!0`eq|(M?S~2*La|eUDV0c z`QKy35Mq-Ohf-*K&*%8S5FoqNUgUz$_DoCGvmMj-BcU8C<6e$O&j40ztI!l%v)S1Y zZp|#at+I5p=;eCSV_XGU>aL;%tv~y;f*e9p9NGeSx^;x5KcYk5{l;Ve_#K>bO1lJk zQRu8^G8PFR{pf%BSO4l?(Y%`RwRj`TAq*VOii(D*Neyl!3V!^=+I64NU)?rF2bYr0kN_B+BW~0ux&fX&p-I& z{Dqlqyc)=UCQNtlnU{rAI7#V}{ea8i&PR7N+PFc6}WD4a^( zl~OCj^Q~`BV#LRYzxwlxDZsmCmvr~+-#f~054DD71N1tT%CO3YKUS<)xyPUWnXr*# z?A7TlDH#{EG?l7RvKm_OYOfm&;ZEm<@a8H+W+X}rqA^`iNP9dlL?Wf$@-#6brd26b zw%01cUb%A4CTS|N^oEFnlAX?m2v88M>V-svIT#rCs&Uc~$WnjvN=PZCrt3!#nbGr! z)NEE=F(Q>KVqWwt7L!|i$bOVkWqS&XOcmkKsobbwq25Jg2S)4_bLo|!>1p73n=ZLD zXRie9K2mMEg>cPyUZ26Nv z`G0@m7k+_dn^yT+EiDJxtem1Xj8Mpo_Hc z|6!Zh{^c+F3Xcl#fccu;{a=M%owa>lpN@-27raW9&fP)@xFNK@X7+9C{k}yuyn++o zPY40Zv}AH+=5wF>+~LDFzv30Iz(R<^+IlC526sB~Ghufe*}i`luLipO{MoJiM1WPr z2YjeBKr#Fqg@%VPO&(cTRW#TT#l%*k+!*DQyk*eMBo<{hR!UYsPaH!SuJY>7v+Myh zcH`F0T|0N}o?@@w*johE_#<%X!UF33JX}C6mD62NaD(WuR`&T0@#0ub#e0fUreOg~ zCk{)qCg*>@&d{irN`vXfAK?bu=#dWaWN?`#%>zt@#AZ(!z2YPqfat{!%4x7N#6WYY zAu5!q@V~nu>X0s2h5>AqqRpwDtDBEXF1C(^C#jrfmQ*oV zqEXzQCH&`+-|Yvcj=k$0H~;PXSQ@9*f9=$|&Ql>Z;vfI~vxb9Mg=eS*{f+9sA^rZcetfD@gj zl&%3yxpd8b%X%uJz8;?N^5*tkOd-92M85!vPbxwliYAS(WhsqXz~qf!pa1+9PMkP# z{P^)wH!$m+uxWtjv9|I|@WieAryqNM=9yF5#`S6->vas}s(UfR{GPl&jjCf*IkDXA1)1o+!zNVnho3|ydKnE4fNvq>92g9 z%K{^#e%i)|J0f6Jvq1P{;}K!>Cj;p@uWKe#D4ckUQ``oAu;&eu*V-=G?*8@UYb${J!B1=cDnSk~937N4&JS?9YgEe_SK3wN-3 z(GYzt^cP?|R#yE{10{pq8>qT^!o@kUI27MXPA-k`N>KbGyo^S?hGNWqGiQ|bWxw`V zSwmP+D-6d<^;cND)EDPO8`W^2a>l!+CF#GPG10wboDs5LYrAxL>NPL3*L~h&C&f1O z>i=_}`~17#{T_Y;ROdi_expy>!CIe`3X(E8>U6%-guotHFPZex@_av10n|RjbYn$e zMH>L#W5(%!))w@Ds5bp$Y>c~&gJA@i_M-wlh&9YqJy<2vR{f^Dp~DZr10mjCL-OYC)*-z*Ch4kWu3l}e3}NroRAFaj&w0CX(tSv2$Ozf~w0?AnhCRG2l(mOWU7 z(vR5yRF^VfQkQbGyJj9Prl9!J#`iNgm3qk+4FCegQY0qn>C>kle)!?L@4oxs!Gqe} zg;r&Gon~zhV6Khso}9Y-805uApP9dSdE1ULhBU^1)-O>bn+|N!gKzBR)M)Wa4YHw@ zQ(~76HL_QKo<74(#a26Bhq`s^?tOdM19(Gs=`6&HC@losX!0if4YfvfSXO~+)ewtz zZZL4LvD#)5b^Wqx(Gk_gPD6FT zt_N#b1uk!>wsSwe^!GAtlJNiObR`n9_Q>zdWiA@u{CDp^`j6i}c3`)&x7e>tZm1&` z24DKpm)`T9_dNROqv1Zc@i038h~*8PPzM-@NI5IY8UUD>@Yy_Y1Q?DAf*2kfE&?mv z0B{9>I!EV#VA0IbRt17?R6QzC0oF2$d-UL{L^^TXLtyQ?REjq?TYeA26PQ#-B2xIB zX`30y7hZVbp@$y6=bn4C2Z&)JidgRimFABDZeol*!06tc7r*lCl~WgZfdig1%v(?* zEKXZC6RZ7s-3*7LR@p7*&YtDI0&XvG_O*!}le_oqNu%@icOiTk5`!WzYzT|aI*7uJ zcEM&A-iv+d5_ez_5x%Fav?eF@N>E#uZmy$$xcf_1GXg3lof^V$TJ%Hd-3V!%+`47% z;>_rt9Y=ofZMXcd-(>TJ0hfDjXf-RaG+R`MgNd8f|4;tpySV~DRx>^w&u?uYXF(w} zomEsE>lTD(@ZjzQ3l2ep26uN2?(P!Y-Q6`fgAMLZ2=49#cY^Dk^KjpL^+WgiXLi@F z`fA}>0IZBjyg`UUHg8n)IfJ~ukg~1_1yD9#CC;8Jnmx8zu)Lb3X;&G37iBLLx4FI& z?8N5P)?qdU03D(P?9%1S5+1G<;+EyYajS{d!9VWek$S$J8uU192@qx#Yy_0*7$qV* z#(4c&bKme|P=wX>+I`9_Q*>fRhyE-~De{;sc`7p}O072U7H%VMJzr0{n`5r zLNv_xfw=A)rIZl!Lu09354_|b5`%>Q+RC=FQWzA=L?dnqI=;J_K-wE#=BZhlXHf!B z^y?FDh(=b40Nuu*YB%;jUI+xJ%=XX;HSX2$`S363^5`&+1q>A65_wk9FG28O+%8wE zOWkSw0F-L-U-PQ~j@-GW=VVDw#zz)B;8(vpo(^%p7f$=mZy&zI|FVNr>b(Feam&i5|`XN&~>QS=jqA~J!a-u)kbog{Od06V_W*>6B zv__OQt+1by=(kg=q*d3aaLeT}u%_VSIHy%)}B zC!W4G+r4!!zsyhY<7sN3I9NRX2B&jl4LMnKws(+`Iq^d*DYsoc4IA+AQ}pL{mRVTT z=TagDD27!)9qnJGDW#@kVq1Vr+~cVuv=WcBL+RIRs>C%P^;IsU>J~T+ar&u!Q}y3h zx~~b%4Myh>yom|g6(r(dbh+6zNFx!>(gcj?gs=M@bjj;w=ePM8DKC4A*M5mL+bue* zUyYO$mrH14eJGJB{j1!+xt>Mdn6V$tbv+y2h6at7S&P3u9cAJ2F@oZH>&QXZ)3VmeF!X9_G`_lG(dbmyOi!`vpCJ5xL60$wMhr)vTI9W22mkCC_fRJ20QD$ z>Cya{!*4rwZO%^ar<2z|{I;{>U*B46w>sDZn`{0l8{kx`GCe5f^4_@!KKui{GaD!z zS{&=}_KC~*?^(GVV(S`w;UMFZOZyWlYKTV0m0tQSueS+);596&SJnl(7uQ`mosM%I zE}$FFK$ht!7;DSuZiQ(0_QP%=+_TX~Ts18Ot61JDUDAHE3ha z%_mMj+=9o-SG}%`D(q|9r||!_DjOiG`!6t1HB@Cg=VrPQOEGE6>8X9a7JK-(x>;yLV&9WQBYv6j*nHJt-nbtSOE?JX zAC@3%dOoD`gYc7I1m3R|>m0)=OQ)=J$aa(U(`o>gmC+ms3+mc2Y8G!wOVavY#}W~v z)4zo=ep=ZB+L={tj~D-^{b&X~nl1^+u9d%(i<)+@0iE3i?qpBzNpfR6yr=s_=MRah8ZAzLc)<*trY z`{G5=`&n+a_pwS)`^|;u^Io@GM1R3T-iO~k1A@hu?juYxUX~rX&=mH*!Jm=Hnr79E zIvhR~5eR&2m{y;#4%#_k#F4LbCr42Fg0Qrmk`gz=5R?f}w4*Z;#JRvm%_LQ7V(wb* z6z3MguhX!kY2x9qZ5UQkB{nOk53!CkGBo>PJ`7Ke{iH5*7Gu_g{%t7a+3IwCA=Bg! zGK$+{1fC+C#gS7C!jh5}%oBC`|7y>07j-+!&3t^u^^ezF1LsNALeA7Ums-iyn#z^; zk$q4X>Vx&W!XhZf1N9qpjt1^C))qFa5=@)pt~M8*GFSK=eD)(rU*ftPIwx4#&uJ>j z%ebT}Lj?}sw;sA5KH@*F@y5?U^PUg9{84T_Zax$ySRvXZ0n6A051nu_n-dBd`2oc~G$y|N1Fgffj*gb9r zL(eo1O`XLLSORkX{v2Q!OS;PLc0(Kd4jHbxc)iUS9N>+O>sA&d2R5%(IM$Jqa4Sci z=4VaU$g8<&-78^w`qRx)@qg9o@p<19%fh#Fu$XiD-FjGG+S{+IIVcI22sc)Wa?1K; zt5zadIoM?dNW1*BtOV8W_Keae@Ir#4JVc~wvV^e5YbpJ3()q7+gU98Jb56y*Md z#Qpl4K3*U$CBDt67bqG^55N`3K<^8Tu!d8SGm{QwHY(Cf!t8@{ghF?hsk3CsP2*GN z?Mt12Wu+=88YpR&=Y~p-ikl~J!`ybq=%f2&X_o4Y5pa1k&S7AR->kHksg?(4UVkvA zZju(>7^E6WxkjLFrH)H)u7w~V*0J+N?@K9TJ=j3UaxJAuZM0GBemeH9-)#3(ooxS3 zz$M+BXTxRAqx)hxzSHG*WVX^)Yy9hOr^W|!_v0?)R5kq9oLA1k9>lz%?OIYH(zWPsn1}Mle3yb2jl_|K> z1cky@M66U#@6PfvzD?x{K=<`ck5it0e;HdDqV|= ztF3a%@)f0qd7+at0qu0Y_MuT=52y)JG{TTEZ1_EL_6*AMi+oAUkolEThn11^tAHF) zl{_?|!mkLi0wh-f0?;z>6~Q72p_4;u5GWZaHh>?(_>$^A4L|IO`x$8!=ow0NO?{h$wr9dF|oofw%=M)iU*Mk%u)G-Y%uA@d=+;8K}TjtUNH+qNkq(tCF*B&bLg z@41in+?MyZmd{Ut4lCW>;qVb-{juOG`;0FdAK&j@(*2*DsLk?G83CXUDTo)@B4Rrk zD#8K~M3)E_#$q@3y!&H#6IFSC^J!?O9^*eHROk%#*NrbHpqV6A0DGHA#MK_~h^wv% zcm&tITPVAU=IBZnM!AUbY25F7bq(JBx&~sL&`OMs4RycnOFqT2Y$8D_rh_zg|LL4s zH x@d^t7wK8lD>e8fhCj{)(_&X81P_@ZkvQu`dy{9cYsn~fYp7;^*Jg}SgK;~KHm%}~%o(NvDZ&wi(SJ(AWX zAy{=V8kVRDAS>{x8%ji$c+oV#q_~>3HLf(PI*Y;6M9j7x0Vx0_9G2ZfAYo>CgITuN z>ojjQ$RZ-Yb~3jRvk&?1zng$mTF%{R%2Z*^Q?#tz*kpyIHPd9X?f9t5idpeRIXJ3( zip6?^wm)JQ-2)`M4r(Xmr!G4Gja>WRoQH2dS^H6#i*DU2c5xr^m1j9fDnh2Hc*0r(9b;wu^KGOgL=kAz^k0qsu!|xhB3cgv4h!;|FfnwNS%Ua5NOQBR!=_T z2=K(nf*-RRzW1fxx1*ILf-3|vpqy$>$~olTYLcjaLo@vnRb>w+Lw;ev0ANjlV`4HB zZ$Y`;gHJjITPWlni09+FkKz-oba`BIb0I%A^Z=2H<_xnxm6_779fYhX6mh7`QnfZ} z--x57-4CEmi_F)O*O_125b5zNSalZv3gN_3_@mZ5@d+tc%&NFpmgO}uN(Hw1Xu+V2R2|u!Jou zpvX-x{^dYl(E2w^2m<3x=JgC#3BIWiB@<5}U2Ogah3&u`{!+1(yDd|y@Ud}uw(*d~ zb^V26>C!G=8`Cjg$uw1EW}}WWQ*)}q#2n!Oz0G(eu~xs`h!R%*B~53PVj@jELO;xS ztwlkh0|d+g+B@FIXgeRwj&m9`)?*nO{dVvwCbtfl2`mCqrForKrp^}NSutJN857_> zKOK~Uk}H3)VOU*XGA@q14qmEje02VcJ@h@-%h%mlux)Z9&~L1AhUOk`CI%^X-Y!oNB zvO?PG|BFPB5;d;(&%G4JEzs9t(^%o|n|@UrsJE#WwfdP`oP8lXbF%t33}QTaJaIfd zF;jh5%2Y#0_L$t>C&&VsG)py3i;kCpU9P8oWglx_)j44UR{m96Rxw2fuT1V1P1o@P za5I`UsC1;=>!4E`N59stD^Rl3N{Vh9Svc)@N(Cb*3mRXK_~8Z_WYHf3V3ei*XygcK z3<8z`%4T1{V@h;xd<&Az_A8w2=Qraq6P52WWO*?78v>*8-i+^SBqboknd34>O$snh zSw1whA+*>Z9V83`T+%Y8^m{<(smlRdyy~U@skHs`)>~FO>&W4npN_`!Q~Ts&7D(Hm z{`uoizR$w|nqa?97)XzjaAmY86gu5E=zsvwA;(r^htcci#aHr1Iu0N?lS)%p21&D^Ds+()JP}mqvEX}zF)kCY^2lw&OJf;RP zSbSY25@{IOFuXC$V$Dc7i&v(&}Gh*&Tfaq+!$;BG2_Pr7B z1KxTA6Iz#1oT@9$O}kDPfMmQlyJMFAtpK;Ayf|HNkqsM<5w9084p2TGr!XoRt(+@C zn{EejvHSknk~OKVgCx+}Op?^oYi(p7D_>@f9Q$qnFw_yKqDz75d1t>rtpOe@_}_}6 zH}2WLd5%5!&dreeFf6&z`HODdo4lW{4vmaZ#`|3JUwWc>>{#>OryP2Crl)kZBxL@f zrs)XI=c+AMk97f|Xni;mq2VDiimp9@$oX)>#g*N>(rE&oJR$t{W9o?i9<9E=o;mnV zDI~a1W^H;~XnN{Mj_dv_S*J0yk{4CG?+4nZb74W3y5*@of`ZS}zMaonumOfCLkxHB z$r-TwDwKKMeT@N)R6v*BK4N5EfsOE)uGlVlZyj}Tps!(zoA2$BtCdGn5Ld(1MQu(T zC@_mx@Ps)ZmY$C0jegI?#>UR?dvV6Y$8(f7TXMdTzjpRb9#vbP$G53Jup0r75tDOy z#4Ak#HdxEV0Z;pa!pP?Al^$19qpn!9KBBJ_SNn*DL_nAI#=i`^!H~p5p(VQnxEg_+|D!q(?NV2i@!t-L4!PaVO(B`G=&P5TnajhU3dh+hqj*X{j?v7Ioxr5^ck}oEOf$PyMPT;4$rgg z8os!?-?{IrE}G1G&7_Fif0OhS3OVVraOH@os{Ni^R^0RqF{TQ}FW+xk+pCdZpG+s; zE|=Sq1NNqNWtj@8x_%;w)QEXhm@$#`2(iEu>PB7&4RrM{AZ}Z)9N*bDIWv4Zka11l z{;-C1{B~Ac#&e!pa9`b=jI=H=rYDrMrMD6*q~)CaUO#9`@=c8K(O2Ce(&;n5BrgVGeWRygg{^+0#7vL5Lm-JTLEenCFD}fSB zapaS`jp&{WW_+Werd}QPEwky#(2IJcXd=a-1rjA{kW^)<~i#W;c>JE-c6f38Lpf(uxVlI*uyTuwHz)*jBYJWqyJ4kvFqfVwD#;Ojg06ApLI z?qh*2ek-dc2TSvvidi z&_UYdYb(I^^OHs%Hd@GK9n;&$O4?!aYTUTC2vuygvwLwXW9$u`hv}V{S1nf@b#kq_ zQgGY^_!wGjdq^a?aPYLv*LHEE*gg5K2?jMLks2RLi@A0&&1gwBF{4X0les+{?sIvR zf{%+odOos;Q7Pan4s6r5oCx~B!Xhk+_-sm=ir)AgU#rw<<_ zbf}&w8vea-*fdi)g7pEcIWGc?tG=?qdu?ULx*+gP*l+X5UdTI&5|M_{($ce=tul{O0RH(#TB;0ZMeP~3v^+)}b`#?0Gt8oJT4@1R=N+L^vaMAQ_!~>GZkXKsfGcsp`j$LE0Fja1De4qtk(zd3ok&br2Rt z$3=T|b7|qiu8qO(1-eA#l*D;v*em?ZH>KXehNOn6q8sV zESr;5&FAS@Lqjn_40mC=_Fq?A(!c5(Ya1qSebYzfZnN0>+|h0~Y+47@RI6=e)C>*# z{u+YipIXnxAou4JuWb}osD}0<6`Nu1ILqg{?FibYZQ3e|VwK%QIH7HpAAzzBR3zdf zwp8?EGOliKU*qoPnyPDKu5;`YEm z9WKl)rfwFK6@l^$4$1iEA{F<{pJp>9h_jCjPt#X3DwVdd5sVq!Mf%1PZIROsfPW2u z=X$~J8#%Bp%vCxxEH_BFm+@Ry&8dhHfq;&w7a8DVG?@4XfLE>CX3k(rlt>Q)gAPjO zUL)qf20|jnr{2YyKQ}!NALadDx=jRc?{jOpZF_Gi4Vo%+2ZtYv!?J`v=KP-(dmf{E zT;E?kW(n{1PD@C##iCj#T20?|e~JYpvvWEb(XT*X zM2r!Hr4T*G3`?yX+2eXV3yt=@_dFQliajOJUfn^t3M0SyZ0Nc)IM4r^8uha0b(hDF zHlWSoOTNX8`p>NK(Ttz@lrskNt!GhG8z_K28^{Jzu)rts{$gv2f%GgWVAMX1tiae; zY4W){jQdiy~lt z2rYHgoR4UP1v6(>ZCW_xu~H%sBM@7JP6tYo&Q8Sek%~M6IZP(y2QJ3FtRp~%ukbUR ztQCvoV~`780$QPYvUD z!FXGI=E&)${jRMa$HUq30fe)QRqOXIsh@HVeGS~k>OH#l4le!nj=p~En0f~>oTUtN zVUno+b9h^Hcsr?m&r*}xkOAmJTwQq<>$NNOgppzz4m_dBsDV=@la z**VDov4H_6`$mb#3Y*r7{s$@wxe9tpwaS&0kw(-y=5TGYZufU@NC6qflsp*z3NYi* zh8IB-{72q@|MVZ8g(usx#`e>A)RY$IL$MhTcH_hZ(&s={7pklVZOC3|N_VkMXmpk5 zTa(RHihr<7!BO2DE^OJ?e%Rb`B@B!VcRH%FB2gqta>OEf3a}mkDtTFNR_UCqm!T4g z{wo5)DH(i#tg>`CFU+guX+bb;qVQOXiZ()G1bS0R$$kK=@=ByFo}m?rvQ9u;810j? zE_xucf?$!IWRBDrnFk@=7^01FT9Jxo9j2~Kp{Ux7I;SDJycUr4CtLds2TO%%=Vq9} z+Z;ncvZs|6p}UgaIC-)N>LOEO@UNt={-aKAvx!?Uwu1 z8YE5`zSRv`oE~mU@m(wGrV&T^O|G-}$yM~&|904h&jnJ$!;CSZT33WdxuFSao!9LK zVashM$680&yFm5k%nH24tF@rP+Dh5RA%Irl0#JYR)}CoCs=rB)J=@?ga^I@;M5%9d z>jE{i?iPABp5tj!tNloF`9fQN)Wp`#b9r&<$iUo%qRZ=;F$lQfm3@&Rq#MBU4!|?; z*}1Fr=QsIn4l!h)*g=75f<2u0rkDFFR%-L}d=IcYpgQ(bxUTW3F|#A0Y}pa{zS3l! zrt~9GjIs8`svl0}#4dasE#G&&JZPsK_R?1|-PxADiovh5#Z}e;apA}?+@$gL+tRgc zmY#t2ZYMN8`$DODBbH1CDZF{XKZffc>CuEJPLjTkx;(Tk*8KCO$7PAAInhK_TM>|W z0ppcy{!Z?e5Jh+U1bI;+X;dPbDqOpHE*RU0wI*%j2V44;h46mvXBmwI^eNByM~5;8 z>Cf1ulo{3amdc6a=BxQlUdl?)VJqzFWs%GE&Ci;7B`kb}NbJcps3cE?Eu`;xT(P9P z9@Bfx`NQjQC0aRk61$o02G1o1&krB(9@@PLq0(+-5x_YHE71E8O)LW-h!w*{3_Zvm zD~dttB61rcz}3m{@zEi~i5&wHP9vjm<>saG?3 z{eQWnFD@Ujfix_hucP7=bSSu}NG8P2Wpq+e6r|B5#3jbr#OEhx!T)u|sw(pTaRcNb zUa{jAWL;Z|!r^|{)!fnqJM_36R@rxP`rqv?b^Ey;M7`-}NYC2lExxBAfiuC`?ULWc z#5l=q8QkuN=3Ls%CS&5|&i5Dc`E2O`rI0vtX@;K~EsQb+UnT?4aJqlViBT)X6*tU{ zT+KYa_*!gs-N((4NH?9d5T~xs3>HK#LLMQpXnmmarCqfuL>&Mg2&3J@a>Xg%KCRj? z$O@2!Wt>Jw&S30mMMxN4w(+7m`q};FfT8!RTv+t$I}rvi(mY7B^RENU32xLn9g**$ zR#1#p7%;)xulM35tt0MjgzsVFgWj)D+$(#pnRKGz``H*}55(&%xd6v0joO0giU$2XJ!%rp^yvbK7b73qF$nsh6|j46a58ofQYDK z3EnK-nh|Ece#ztp94KU<#-#To|3AIku;*1H!B^Vt7JNZfT?b!AK|wSV{Oeb>)Slm6 zm4WwJ{qGA%4!Xo+Qx!KwS%slQoyi`T7<1eKt7YM@_x(MuUGXPw&hbqufx8w;YnmFk50XQ*{J9 zSoxtN+Dy{Xd!&XI!32sDM@~WpN7YrS;t=q=otEGs-jI8kzF(PbM@)kvoI}b?x`b%O zRB2(#2oGc%(VPC9O4J_`z7y{h<%Nh2N8}I38M4MgmHCZw6MZ+McTb-KCJfIWLFlH; zaayjV;kSMU%ME+5=vc24_qrI=KrOclo&XE~iMS}oiM_ZzOe-BF9?hN+C%DGWYuKrt z1uCRZ3C4o{;?p={njN=gvlebw6Do0Qc2;l8MG9Rf>)n-}FvVM}8Vo zUVVzpECijH^ji58&d**t#bhrDu>K$HdT1z$wce>fehOyuKPh>pD?~AY4kb0gd4q z;6W78HN<{%YCm)5B@#)Gb@zV@WB=yj1qG*apRwHkj6LU33;nRQ1Rng;Gu9DDng#Ph zNG*!)l@|KnCp@GAOB@b0{U@^*Hh<~;u3Sl(ApOTD z4JEe`=!_(BTm@-7oxZRm7TV(Pj=X(rG!-q;s)1jrF~(;#td3)>JV|5yK2ERrhRzAgw@2vXA#UI=0l$EPY&N7w?%))k?=eJMIhE8XG||~V9A=k zwwPNSd6f6|*mCiCd9PTpP@GsU38?m>?+seRGK^QQ%7go(S`^|Kan&|=_{ty9>`6Ul z1+gZ1-dPAptCr(;v-~jMkWH?@{@1>b&6xa*A!gn>(uSD|8%T#&2teNJfW`VF_N-JY zP2{eeC_y|<2B&3X%Tqog5;lN?!XZYoznDgx3r#horm$nA4Ffl7yMR8DK#e4Sy;$b@ zS8+@jFZ4_G=+o%}`k!2Vw7;qmK?!rk!lsFp1-jztD}Y-y?lswAXU3vY`f9Zb+ca&< z9VR!`KI!{6O2VS>6(YD5?N)NynZo|E-3B5fLFqq-h%$efKQ{W6410ht0Hz#Q z`7nPBnqMAj^`;kz>jnZ8+<=uN9cvZFNU&l*mtO`|DVnCGAuUN7w(o5PtbOl|# zix|0&`BzvUlHDKKH1VI+&~1;mR-|^G;mD2I-u}`EKD|GDL^Jz8jp?u@vMCQE6T$f4 z>ad1eCs7d*0ZvE^F_c5VW`^i)HSc|O1-q{F0g#FnE_oOY7ib}+dCKD~Gn(Mi9_PBz z_^&_26Uu^)JI*i$4Xndemn@DIEkZBK;?O|vhX-1|rNT7pLiPW`p`flX$%}TdMZvoX z)d;X+<5GbYkwZYN9Mv)f-{(0rM@sZ`sNR&lrQtXx_Jk?DPE^a2NlHF_&kR{$`sa_~ z1=;i#CpyTiIp2|;7AHZ7!_N}&9p~9FdSN9~+xxDJ6yFG`>!DZR;;l(`lTAxYn)TL7 z3zzAR=NnD|0dExtrB|sjwfrMkeO=)Xi%t~2SRo)Gc#frW@T#!F!>mTnIs+wP>9bM}|&UBzLsT5~H=^Oe>C{*;d#^WB9c%+Vc5d zS*b{dT~(#C8hp&VuyJ=(D|Zo_6^+s*{f}@!d1y}3w zpLM^jzlikHp;E?I8tR{f%L?%L8jqzJwrGBW%3-%}j8&0|vp*d~P0Nx?D#`o1`6wK= zsiL!NphU^S%&Ixl7*sPI#cEi z4(_drMi2xm6SOFOiZMnq_<2papa|QT>KKGB+KVV5pSYxOcNdKul}c zaduwwm1aY;g$@{PLZ`2QF%CshZe-gGU2tro!={g3Lz&q#bzk1eOCd%R@euusDM!-+ znz8@oL=o(J!q8-|Z(h-8aPF1VdO=fqQDO)``up~~XO z4KmU~1@0xtS0uxdnf?ikl&bmw2p#o8!8PTKb?zLRNB5pM0FI1Y3EQdpfA}h}2H*1s zyro2XS@dX8lsMxZRsY`$K-xfi7_i+g>?Sq($@{C~wE^71*vpD(_3`0*c9prv%|fpL z*9WqDMgGdfC^M0Qo2?5r>$S8SAgq8%N?ejYDF4$QZ}07G38a;X6e-&RZF^yEG)orP zaq&G#AN_Xu->XKzMve99&mNzQ zQ%?qoAi41_@maK{g5^^p5=Hi2@H#3ubhjW(sKu4-2*J&8^ca8(_vtJ14H78AR|%@l)D9_uM_<#k}CmqhgB24^uo;Y7c2pbCTE8v3I! zE;R-6*JWWKi#Y^ap;jrvn(Xiv53CL93;_?SHiaVao-ix)a2TRPvqa$EJw^stlQnpgdYCN14?7|Y1EtlEx!&Xh_3P?I9Y(kI2x zOOb+zPQMc2&&$=QrW=Wdj{g=&7p#P13auJ*$Sag+4UX=}?p?%Lo1n+EGU*Pm1~{ zn~gthBo{|*Fm0*+x?Yl}14BEnRa zfnuqBKmP@crrv9~r0rCkUBWM;lKu_2ZR*pSH7=t{ACQdKJhHTtUurAsPs;h^dH1x! zuzugLr`|&R6zC{j4F9m&zz&F@0NJO^-)~6nB&K74=AFnS0H;M5|7`U7o zbGxrymL-jTo(PI_Hg1C=pYY{aig^o$Vp8M5Qkm}B*!3Lr*V+z9RsI*12#%2DGj(*Y z+X?`k(jb4$4e+eAG60!5+^@6;(~C|qw27y2YDQ5~hgyOb1h?{yBx{3e-K|;_9LdBq zE5KS+kGthK=)yjbV9(#Xg8xWt-M%l|b&V(+qnWYkW(fE78B4(P_BV|hxGT9Uh_AWR z`MH0wUfI2IbIT`VGWZ+87Vk2AC8Q@TzuQq+@J;lNhMESVlx8<(?QB3}X=h91QuZ_H zY*L5TKns;4Fc!y1{TfW+9Bk3}dKPYsZUiIZ;*o;CXxgXFSYNI5A-W9#OEk>2*!O+u zrzLWw3tcp*&1Fjm4FmfV6}eA^51X_=@n>u~`*s=eYN zJ1Us*)no;8raVUa&rVVpf}a%+6*TI%<&EfdU_=HIROs=F%FDU?Y9nMSjbNzYDAIFP2=uR8(Yr{vjp4yq*655TRx8b0jlPLZQZQxVVSU!Gyi6 z@_)wsd$s(E(Hcht2rAXMiR(eTjsa55H1wtiw&DmuU+xn%z4~$+MS-? z_$}#inc;bH3lW+mn`3+BM4RM?!VUykdAtu|a(DJV^Pp99J-ck#m%>8>Sk;zh;th;R z=DNrL)O-30igaMcZcraxgE~g4)HWu2#>L|dvUcSg23~qh9~%5FpGE|Kp3dt@O8z7*I29w2HQ@X@ktK4PG(CfeCL{qJAELk1lHLMCR)@W1& zUMY+0Jzu#%krWP!Q_xr7^zv`UmY0UFvp>*feJuA~K8%s)CP)KMJAh<|M+3ubH z?WRlF)T-L{;q!6T3+mZk;ZMG|%Lp$wOnEb5W_G<>L%{64u6`B)=qvo_omfSOZI;Nd zbuxOJ-Ifx&IP4usc?MNrIRf|%)Fcj_VB{VEo$768V;46rL5Pbh7B(6LQ`)bCo~m(` zYbmj{-Es}Rs}v-QM5SlEdK)AKfF>?4hFq<%FWOvC>q4cgkE_~=lu@$2o_ec*u4RK}7W#|e$==3mmc9-5hWzINYp_jiWW@1?J0 zi@n_j^UncfrBR}lY6Cv89TIW({Q63qNESU$ z4I@}LU{Or5JvfgnDOaL{eGC%BH9C0?4vd0E8hCk+{@JNEa@q|fORxpF=JWg3lW1Np zG^1>vF~j?&)DW}W-Hm9#NTI$IjXQ0HpnA6paWd3_JHJJE(ClH>DfA|2OOWlN6-Gps zQ!r*^L=2|H1S`AGAHdHjX^I@sZ4fo2%( zGMkT!|KEJH?%gJ@fH@_v#c+*2zspIZy-vex8dK-9%VA2-bF!oRqF}4AYd;+OTD#-z zl45=JELz(aKf#8#M|?>&zf@@Lo8T4qQp~0XqfQR)-Z(}z_ti%Qg$ngmx~J4#fO4pA z>RG_0~b z0wqmcY9I5l0#{{JW5XK}RAk?IP8b|0pdD?JD~;LhX~y;4ebe)H1g(4j>Y>x~W?A@q zONpSdLbPp>s47kDaCDvjou>aegzDI$1)A2H*Nk>XK!u&4cUS{#5?Uz2I`a48iGy;r zmMv)vfhs?RG(rf8a8UIbIQX(B!=W13*t%oBj**w@rAgd0{5Kbq1U4-L(1pERRyNjH z)t8DttY~@(rLuv%ti&Wz{1*IcTu5G2S=BFr!N7uQ=e2I1ZA84OmH-0x+e!30li_lp z@X@GkdBm~2p^0i_O1T_R&D~NJ5{Q4?i@(MKc6eOE$py<$MJ+GGKz|FajlW(SZn^9{ z$Nh}D$;rz3a>w4loVV65TRkknoY&#_e#X5$_|$)!o!13bgglWq-E>!h>jC`WO-PS<74M>fgGta{Te!LhL9@^sUee ze>QXMzkV(>zpLPnC&3R3>)lgt`}g_>0-d#|opKIuL=zXFaQ$!h6~uWPipTDgdJ-rJ zZlOB&-RTvjMKL+yu|*P#N5 z&c-U<+hQAKG>Ks$?93D++RI7embutT2x_JthLRzU1?+bvvvF~FvPOW%Y#?&4Nj(4i zcCZ1r-|Hh&%X$#vUb;jtt^EMjNl`b-a<|7JWO08US75XBQ*wH|!Ugp4c8iA`y$s2A zdw!tleQ$4?h&6QRyy68LBR1$ngBb2d z0I`46iU)6JC8ZLT z=mTfw@x59000e5^xTe-_0Q564KiIdN>G_)P2O>)c>Q@J+!Il~<9#hmfKq59EC@k7m z8XBQHg3YcfUFEDi5JFIOxYA;Q7~fycTLz3YC`r!NhLcynTQ%&C95qK$zLZ_?v-+KO z|1T_lJA}K4*gA*zU^a3rNSjnPL|}Gu2eeDLPdM*B&KvIKyqPTGudoU755&J* z@OtJL-0yorhVbQmq}UM1oZ>1e7x(?q%j}yp%XK#hIql~HgfF(JXHC9IPs&#s77(Nwx2oj z%Xmt!#28T_sON*?M$%x4B2=bX*`h=d;WNO8lh;~T&^Ze~H%|3s*PJS$yTy#G2e3jy z1+6FIU*uw(B+;kf<1e$b{0@eWEfnoGn%zE_!XJ~Ct;hq~;kEuTwx#X8Nu$Am+rLb=Sp$7^V1YzoK5a7S4;0Bz9HzrTd z8hD5n1cfRrCp=+B_QtLNXj^Z8_7L_?1rnGvJyYlrkGZ#eJaP}p7LzZ5vIs4Ux+st& zU3-*2RRE|kbXY_&vJ7Ysd3zx-7?+#Be<~{I78Fj3xTLc>1nP;l1l_h#YFE9dKGmEV z_+IZmzAiHPuuFInSXSEPQJt&qfT!HaWHh8(68p5jFkVsBV{Xh&CLIx}SeAuH-L~TH&V$ZJupDR8qa_Q+mD*w$6#;AUgZc5hAYbspaMzCDRkdQAe zWa3uG?N$p?)PPpXpr`^Wz)AB&-W~mlhk$@+6mnXh4zhPF@gWB`EM@lG7j^%9i7%e` z`_HX&?@Mm4^HE|nINdIev4Os;$oQ!WT$c0Hf0|VOrCGt1J>phu65IG@qOnM~;{)fd znT6v_$K%n8pBSnpg2?m^5PmYxG_M~NVEm7$Yl@D;`??dev2C+alZK7msIl!dw$U_c zY-3`(v2EM7jrpd(wf^gSnx~nEJLlfJ@7eq8Z5k}wZpL&-a%b`N@Aj9jn8a9E5tE1` z1pNT{&P&52lZqMUREtqFq}f?CWe6g%P9qNCY~JK)n5H7vI5@sv<>uM8W`-5e4^svV z^QN3)D9W{K55S_KjmQ?~zrU-k#2%-d!8VPy$)9OuF6bM#DiuVB{0arUkOuZg354#= zhQo=!&iETP===#PJ!18l_Owyv@^NwQj5hU{9hkRPXOVOA8rgWYZ@njNy9W!8diyHc zX1WHUB7g4qagYSB4;;m!Z&+Ucqh|ie%lyr_FuZXKg8^bc>c8dnM`n{Wkiy~=5;H$_ zGaVbuP=6ncS`cT7@vm?k{M8fM2OHTn)Z}}*x)`)N?W6eswl13&F<0|bV^D{o+0Th6 zOO8OxfvyT$vHeU6|CQ@EndmRf`GjI;yv|2VOTajVGo(<-)6b6Xw zXHy)V+CplnuA>Ic+PsNa{c#b6nta`f0n#toKx2=@0}-|s@V5}71_Q8y&w@%*&w6z{ zMKz``f}WGs*%Ti5(TCR~uL)vn6yZjHMa0iy;18m{s^W)F-v;nqLiubrAHMsrx)%DQ z-)wECKb<=iDlf`C>D1z$x9NN{792s>AR*x1FIcFrKHM+C0lD%8ppD5-Qml9!qANUK zz7o1c5?_`5{h@aeIJ2fhkBM`Nc5)}_cL#cVsRF&!O$s7OmieWmQID#Q#ZhW`r(x(? zz@J&@QW4OE9Y{{oQBch1?xUTcIdqX>JEoveqM>WB68aC2UzU%|n7YJ-6tgv#@ldLS zBV5J&RY0Gr4!oYIhU54pY_){p=Rm;F>U#bXK>GCI)_$t1(HArds_vJ>(ak9$2qeQr%0{Op^~SfOGwk_=6_kB` z|0=gYb-~P861*=+V}-Ph>Rx1jp^1Wx-sqHydNr{p{h@=)YCGsgziAv*{ z+$R-9yBpb?;u|xPDs(e&XWX_P6{>9|o7RUmnsf!!*ox|VD$uqk?0GC8v^+%fsw#)) z`#fB+`EkDa3_f+=Ws_%+(m1oMO=zggh8e%$@ajKEfiudVpp_=v^pdFeVacv4Gv^HX zG!5M`^9O&&3=8u~7t}@R9NSW~}Ia=O_3|o1N+V(mzeS`?lQc z)L~!*d=vn5--&cCqgkL-x61tH09Ftbqa%;{!O;jv@+pRns!5`LV#D3gz%63WqNe`L z#=tH*Q{+5cUtkD~r&EJO8|W{23aUFw@(=yt+*6M!wM!>pUyo>wjVZ43vyMDY1e-dj zTS+8%)S5I&KGPi$K`9e~LIMTCU>#9eM=29UW?k8%bU{MAQa)>3R% z=EP%=rF&P7&Xd5@?U7#HR$X3swa z#O(`_bEj;c_Tm+NpL$6@mIgmko}3bpoB>2++bV8Hz9r=dIdqB64uK`1Xxa56uzpg= zlv7b%o|}Mr0TRarHRusTDN6Of>VL$*O6;*8&V-`$eChp@!S7Gepr!Q9H#3>#y(TCn z(RABcZUZ~d1Hx?2yKtemzD3aMA!{~Yr;pX((H>qWg^eR~eTtH|>q9t<_^VhZY8c)* zk!;2>@3qb9RGp#}fOU_O|miL7w*}J_SJ!2f$nW8Vs(`p}h zpuXSjPrtW5v_C&U*$eRYry~!XNyzKJ93rL<%e%9&Tgrpf!7p*rm#HvPk3Dn-gs^I- zDV7CJoFK7Z7lQ$;O5${ca!hb?&Ioujz3@z*A$dN?x5WAQf-6?x>*2mMYoT~JFld-i zGt^P3DC4>hJ_tI>|R;)@EDXe)Pb*w(Fc|$;ycj~20Zt$~e5#wYohaEdi(>cp&QCT&J7fAi?fTw88%$~VOF4I0M%T}q9rF*1_g z>0j;J%R%Gq5eZSh2%@BxW+-nqVYMdgJ;(2bXB7jU{v{7qM(gT#*#SV+&T*y%p(14+ z7AuJ0GX`L=`E;o|AeMxGkxQr#9#Km8B0{mtHNGH+I(U|E315dW8g;i_o#En!?rw{` zg&d)dNy(Eg9tB8{2_4_Q<_OGFMJx&GJ7TvHP$FZcwEP>0|C!LPPY5}!%S;Gm_ZcLi z&{#yS`g@q;i=}o_5bEo#j*;|t8BAJjdH{nEhw>MbaWOqUvkHl0i&A2u`R?oGtmZtB z=GS8n^b!_Whs(MElWDfN{pwh!8LRL3c3i>FGLs0&Ro608yjjFXgj+~rO*XpXrhi3$ zI8Yq9UW5|Sl|@Y-e21{?52yAUMin~izkIrAzig!R12OaXJrXuQhb7~ra6FC0cQ91f z)OgVCbmcaDkiK=3zTL2bM!LUe)+ zG9tR`3bAX)m`jWW4dRbXq(3!FEgC>-71})|*6I#9ihj?!9IeZ}sN2nFaq|rE>M*Ki){HxU+ z;HVhSvN9Kkl?<&{kA(1-YF8GLbv~XjP+$2^611L`OOlDV^j3kVqv(}v>2zF|nu|9j zFLz^R1fb3K^@J-cy(iYI)A--@N(@u z*f)MX=kYl=NSHvcd(FPs?6r zf6V&bv@Eux>>YFx59MpwL9y^Z|FQRe+r|aGbw{8IxwyF6IUoLgx9}GrJD-i-rM!0f zCATJ6HJb1PI^amO`bN?mfA9t?xoG})gCVU)aN$b$ayMSpJl<4SZJm!J#iHV=Y7~$&7(nmZ`0MGYJ9QqV-psH?}4j#Pknty4@#u` z_v_|{dRe)au+X#jYG1ducCt{HT#x;2{XNR+K4$A8xmz!GsD-Ky)<8?$>#)!x=O5{J z?^&feGhg=mqURk}QR%(M{r7tWCSGI-N+SKS{>q`FgO>2%@k?Sh(z*#Tzz2m8Wbdc0 z8)Ke1Cd#fQEGQM}wS5yQ6zn;phn`4n z_ig21+bk*b{pFYgw3*NPc8tp+_`<}Buy5tB>t{~DVx=H=Gt+4@=(wPG)v#k$0QmU{ z3>E#jwPsR_CS_(WRdev|6PK(@E**J$OY=T|^;0g}^Eu$mW}YV?0>DSXe>2Ww#V!r~ z6^R}z-7|Cj_OTaa$i0=%M|}p~k3_6J_{jd6YDCdpLP31;91DY&ivw2D?$r3;+`4 zz1ZwA6^#z96Q*_JTQ9!Uo>u0A`Th<^SI>v$*)^=bAc(i$Y3<~aJ=z}M_~=W&6U$?jUcGpT(7;ecpMm-woBR|VWWNOyOl18wj*_|U1e<004}*3 zd;)s2NQpyng!@%zoLm=;@s;n_{{?QiYD_SECfo^h=#=Yys}T)w_#B4@TSW7_mLS$E zRu0a9T$`b@zV0V<0RY+{1_4D_OOXwi80nx_Y()m-^r%AR zA;BG@z+ypSXg7m5R3w$`!}K~DiiS>}aG-pz$jQTJ+XZY#ApZ=XkdT`p>;Tw*bM5AF z!};HoatF_PMyUg&Zr0*>WyJ7oP4-#10(+Otbz4zXpk=S?*Nhw-bG?j^= z^GPA}a}mE8r2eLp(K7nRNfnB&VF(>c=#LHSP^>4JLr`5BE4vI~Oaw3-Q8i59aM1fi7I4y{RjFWodBUajK%v ziOOo@pAE1hm;+w9nwaJV4NyRjPic^IOw?V)jkL+O3!)K%BRN}W)VjG@ExD7kT^pV$ zc;o==;;u$Q{ABXk4-?jgt5h{?FxiL^QQP(>SDQD&N)CCOF)MF|?2o0t-KJw>6J8aR z$pg?rRXdBHz;Q0-?^W{a9(M=c3x0ExFGUehjdz=G-Lq%;;h|0MZ!@g#H|iZX*m*;? zLKq^H)XYV+Kl`vc_qvkl;0-CXWpeY$AVQDXAj>cUwqXs_dcJVZpa(c|2D7N|nea}P zq|{Og|;8rHE@RZfedLoiu(eT7s4P zsWO!-pw2ouUi<0qm(g@l$+M7Llo<@6$u$qiCOR{{sKv zQQj{2DhEDL*5Q$P0A+-exeYXs1kIdR^tL7nFqq$*r2+J*A`$v#_$>W+TRKEXQ8tJ8_L6`94 zu0SD5HLv_;D}_SI7TMME$3H1erY_zx*M9kc$-lVm&Rp&5XFt;@FK`25U(e6(>Duf4 zva|iL93*RYJXzn`UQOSGc(Wn$r+5bbwfFve0QjG1C)2=G(ENpQoAG!QODZ zv9a-0uUSHaW%!0IU=r4W-X94PQ}SVT-v4l7Oh2WDX8RiO72VYE3Q&sgO`3R-FNOYD zq{elr{-?HQe_B+l^Ew2i)|`BTT(pm;)*H1Ve9CPzn0Ivgm+zW<(w8Vb63#rA4yP`{rtluX^?~c z>-YC0RY~Xwn<9DSj&oc4_i57i>8}@mp#aH{5|ZbO$NJyGC`uaMlbFG&Q-ZIlRW}n; zx2S^bZf2g&j***}_0^c(LU4HU!oR$yV}77`qwQ>%_Q}%y?kz>3tVXBY>Mb5n_6dsk z(*`SV)j#7mn)EDNcgvwJ;ih=udmsL8-|Tg=vAY}g0AzTKA}3@=8NJeMde3_7DVJ`VjRN_Abactd6WB$*<1EpsdXW=-7c71OoELmrF4lQ1}^WNU-0eqi^bPb!8` z>eWBoqC?L`pbtKBMU;cGD3U6~>mvov%1rt`RB1kf&8;)Hwi6zB{5Xyxb+dXTi)h^1 zPZri6Nj9&4_qGuq-5?!SWwg87ew_Sx5MP~{2lNA*iHC0bNBmSWFxTJC1W&60!FRRh6b?&J53g4Kq zHT^U$Qinbp^$=W{g^MO$t~tG$ts?Qh*^l=^y@DaHe2GXd)JL<{gF$`-I7-BpZI#p# zOJSFj6qBU*p(4}1%QLz4z0YO~y1(36lKZSr&ky2$1|X^;K#qDtabikF@R;+bOZ{ z&6kXTlV28~(Hkf}BghV{qXm29w>@sDR1tZeG!5$eyq4BovTD{D4cETqsDAYDWKFI6 zBM-5TV^1hBtkS6Fc;swGa{?Mo?vvob z`@m_;9H*if&LERQ%;)@^1+)9EHIEz?z`;b5no}nn|E9=80^C%DdMye9?Dco;scOKz zIdCaP9N|9n(`n~@1?cY3@59`i5`G5TlQHK$mi6{fAzR>jpf$|*dL&oB!*MTU{V+R;h7a9E!5@cLthSnaUmIC+p^2S;8ZF#jxGizi7WgJ|CkHC1d^i|Gi zuJld!TQOtA5IY%}j33mWc4`bc10;_*H`4kQZIyFnGHR@yToc9Qu&{`j;V|!q4wf3! zG!cC{u>4ai8r2c4a9Po>8VC79w60S)YO!mr(l{O%GY_J5rE;Zfmd_a(E*Zk{9xY-k z&D*wa!x&iQqdAuGp$q$Kh%#feOGn|6<;6DDepmpes7W;bmQV;m#;KU4icf(#T!BlQf#n07=U|e0|L)Js+c}+#FS&uQ+7eKu(C%_|Ri!7(7+|jd)oDxPRc8vLgJMNctUQuZ z7x`z}Ok@gIa4?mnnxj$IL_`XUO(X;oB%UUKlirpufiNJo5wDMO`B$tzyAcOQgA#Bx z;!FQRB6fpzYwI_|?vhF*V0xhhpwva8Ef#}kc!h$5K^#sEKJZexVEl<`T|azQ2Jl<2 zyf4FuJ00H$?weLRfL%;LsZSMRy*F~~P;CY~nj-X*hDYCB&o>EW#&n$-s<*%Fno}Rf zCbhvrUJql}^m_N2zUT9&7tN`tujTEp2Z`D|+&jo#-0!#J0YA2}?|6uP(no|oo_0a! zo$vZ@amWh!VywlQb2!0uWSR?}$SGL;A<)KFFr{z@gZ~y%$hWdR@(vJB=|o!KmXx4j z0^r6VJ9yEe_2-N>WIJ1qxt<+uSk&vS@W1pvoLsEXhU>-lVI=$d>vx1kX;sVbMX0Eb z`opQ&svDiDVS(38At%@2Qf%2LJv2RAdw2(XvKmK{n@<6-GR3)ZVp?MP?Y0D)6f>JF ze#ztfFl6rsti>1NPfDsa*s2k~N?FE?$6@6@8^l4x1L(L3mwXu@yw9Y zr8CnAzJlsevn)uuCnT#gz8Edc`vg2_nbZJkv3*;K6#x0YMwPus?=_OnP4Uq^o6T>uH0~4 z-`O9RjHz03^WI-_esNTK0QF%iUM-1ZgpfLZYoG{} zuMocPYWwf-9G`E<_eI6JHOtCKA4XqtDhS@y!GHYtgZq7slm0}?-=xWA`R3dnbSU&b zE3_{wa@09v53?1Rq4^Z+e}~y)Pt5TPkS}4S;j&Cr>In(8D3^}BXE?k+;+il(8|okFfq+J zEuir><02>*7<+OwQg&76U3<`nwV9&RVfsE#+TrpJ)pSeg&se!>;8nQ?_kJEM&HZ7x z(4W_|fK3&L=!@57;j!flBPD|jQ}VB(Hvd%=NU%x(#Um#L)6(@&(ikv?7CS?S+yIDy z{A?sOC6iM4PpkHp47y2f!OjT@kRzzQk+I&h)=)T^jm_qD+*z7vLKSL{mkd&9dqBxX z)vxuo)Iwky&K?w`l`y5@Ef|gP&&c5%(gW@96()cFWTJ|(UHMyzi^)H2=0NjLu}7Ng z;iLyX)ij-Y2TlS!YTnTzQ~sUX^#C4T70`e&Es5Fx+9noJE_LrO(>x5#nUA-=aY7$S zKX-VyQZAPmaEqlRBA`wfN4~_$tWIter_e=M%ex5FzVGKcyn=0Qx9ND_-gNDySJtH8 z>1_LvA;b_mSnI48&`=3EGP-;Nx&*!3`+nSJmr0M0MJuK}b3K>g47iv8SkiFVrTwiu z)%7K~i{{S}RZ+)U;Z7h8HgxsA5}iX#=BAa;L&Huv)8oNdxkA*HU{c?agzasHCvg+t#CwgC9^}Zy&@e?HEdX{arTGE!?SZnt8^k3KIPdV z7Bnt%4lV5)wBR&xWtK_MG0FO?dS<%J=ADUqbAcwCzkaVPsO$Acqp-e>GAk#u zf)6KDgdiQtg#6Q%d_E6BT>n53D!1)I7in{@>E~+ z95`H*`{*&oAsseQ_2x7rf%gFZt1c+NYuwG}wL?KTD1zb$0YWx)Bk2u!&9GT_-)~P( z6HU=?p17k?DHyGWlCMck-Ai$w3|XKl z4<|yKXZd-GxF$^rsN_!+^O@TMdgq*9#+KuYJQnc0`z z1?VtMF8WG(cHOcPwa=Q0L8@@}%23K9xo+#l#n3e$q>bx?UKTrVGcS*tASW_-dlz%^ z1x|JlE9!X8KPjVaAZ~pM=$&jExJ1leYX!ZRa73sx$wtF6PejA+Cjw}Ve@=)2Zq)w% z)Z-EOS&0+iY*n~_F*jZheGt7o$c3vrq47)=cyn~801emteX^e_jDOdOv$^Vwq~N8I zLEfMw_AT>cUo{KY@o8Vsx54=OcY)2b{(G!;RfpRvxGPlt*RD5@+GVs89>?4{aa@AtZ@|9)~AL7L^s2vFxNkd}o- zcuO^=RRkMu5^P=7RT2AGULN~MUVD9T)tI_BJZAaF0i7vkddI99UTEJVh%H5?O=RO` z#B4UdMjt!35#LrrRpGzW<&aSpPjt zTA~}W3c?p?FhR92QoNm1Qx^bdW`bp@pvl`wp(>hqUQ`drvaT(Bl1P3_3{`_gKWay|QJUgfDe&*H`{$J0Zgf4}9so{iIyIqD60F zU6-Jb%Xe@erXQ?eglzDoRb?S4mDag3eGLTwOME0}21T>IG8JL3PkYQ!NX+fyHqo{k zg-0Bo@FAYkz4c3EtcoDHO6+i;uI{T4YoDq=(GAHEd=l-GCe&?ckHf4T7jB%=;SBob z#k@5TD6N*w=YUm0Sd^oP^TEPrIMex{8l}P)x1hE<%15qUb>?o8n@VFEoLuMvL^j&82!|u7T;Scm7`Fmhho84L>ibdRF z(l~fx40Lh%u_%fv;Ji14el?ysyjPuY6hW&fG;9gk0vz&J-AkyVHsa<;(ezKT))lCB z_^*-Oy^YY*+FV2U2b)BNf2bdJF51j*O_Q;6fNuG%;Dn>NRxXBC+{l}gfh z`mF@#eIW!QZRLTak9&^9IrI$5se*|iob5P!ldr_4>S&R_QN9sn2FcVi?@n!2_K-W` zoVFCj$duk2J|iP9=)>rjwL~bSur%%~`cb+-6HP1C`%`KMBQ;KsABc6K2?Q}i-;{KPDzj zG$AydQKHCY%zfYxQc+P6dg?~)cwO)G+fP(?CeG#NdSEg(4c^OLxGzd?@tWrfyOoFq{+krhm6{leK5VlflNQh}i zwI)14ue8Gv1aojSLZ|K_T~IR6+Hb<<1J2XoXoAVVU%cpJoK~WET?-A-hmZ} z??Rx%O@W&s%C`h5X)Uj|VtVYLJEnitLaz*{#2TZ+UW7Seupla5EnYc-ly&5&LgFZVyi- zn5~c3o?X+#t?riYC+9)s&^Xt!ke1YJe`)={nXB+Jj>$8_V$ko0r*`Y~(o9|Y$8oC= zKKISx4Q%~9 zo(W49(ECF8^_HBIA*@B=fV?1%ZrS%kqOx$>mFg>X;?mP%TpS4|#b@5Kw=KqG&x99s4(J01jJI!ujta|3?;87=23HVe%$%pw4sITAM*0< zw+pbZ@5f4{g51@PnlEXGVRl+xi}Kb&?z| zVS(2CCB)?DSTz>UHn)VkRT-L%oQ z`>++*+9T2bXG}r<>UWT8j>9Z7%tzl^XeUn-L|<@Ei$`vp`OaL0y2b)4rM&7Y_{7N1 zl`#kPEu<68<>Wklw%ErlaSS5z3BTq zs{a9w{(R7NTZcglw6N@fjR9dw(%?pyPaXGnidAxZd$^R$G8a$16+%PaBhKpOaa+*9* z28W|F#RxT!(D1_~WI2s|>A=L(NPThV>_*lwC|S$OI`-OVq=7fTl-xKJh*?12y_F+~ zZd&@u4o0i3)+z$d?$}a6%MO{1QUO|KR)sB>lL9~Al<-=Z7K@|MjGGc94-}~}%&AT3 z%eR>g;dK$)nRGR44j6$(2r>EGbl%EE4`=+PhbqP_`cA_BH+9FZ^u{E}TgYuV4V%$! zss12}+2$F1te-uy>NYVT6q6;&LCGmGNOAnSE*|6T23bMrG5PVq5KB*Kt$?136kC&F_|!$pS^+`b1sF$ zQe2oWPhuunObL7zz~}TOU|J`0!V8w{kBK|S116z$my0?4yhtX${)IpM+r4i5vtI3s zw!dEAgNzkXN=$)pDN=MJH;?x8o_17g1v>!GQ8rxTL^e_$h47|FLYrHsk@GV%4L8RG-<(z4qLQb z1jt)t{imQt#o8a|^*4VELE~G|pN1SfX@nd~af8usCs~O-h?ysLfIFZExugY zmpZQjgfqSGJa$?&E|`jMJQ6fso}s<|TDgX(Z1#-Ja9gR_ouJoFzlUY>pL`0?isxlP z5LkdReCuspw*Y6OQ2VQg6I|u7t3_o2hZ1*iBbp_VL+zYNpi8xqSKX=QP+xJ-EXKqH zAR3Qh+F%C2fA&h33QwgC{O4GI<9G%3A_>Bhva%PHR;-Zp^oM5149wtYkmILhZ@9b) z*M0rV>;P^^)4H|u+)w1zwQ;stpj)bV3R@tcsxh_5*dy%)mrIXeWK8Hz^vq<_%o76W zxq@;9x;4M>6p&X?)qh40n>o%n8g7Bjybf>Lw-+ru=73SVkVHO@%qgY&Y~OL_ z70^@3@l)W3IZmP_V2^g*ISz-OW}{tWBP%r;-(K#)O^8WMLF%8H6$YmN_Ns=$BNiwanROlC&B2*%Q`-5)G){Xt+oZYXRJ8A(OVTxDb&KM>Aj7cg^ z+GP92GOF71yaI)>2{d00Z8(*)lAIdE(D`aiOkj_6(vYc)BxD+MBpQL$obMwV* zb?mSoeOP}+BxD5%(w9xnx;74m93{^Ks3Zy!E8+&snvEUGnKj$%;RL&2>D=@hMM~=h z4virSi1>_e(WKBoaZaJwpPC44`?HU>Ei-pzAduR_4}DoUo0MV0y*N z?T?tql$RD#e9GAUqvv~ObS@*f(|pi)s=nC~66XEGZh7bdG^Hx6L5sT|&%>7UZI>U7 z=f}gv`YS5vO5ouGVo$@$+lm%fisv|I6beI=mC!`2o5qCCwgQ#|o<3Ad9aYEi-|J5H zB0()k7S)|EG-t1M>x%Oepw5GWBsPy;F2I?CVHmazG#%}G2L4S#zbiPuYR7#UlhX)@ ze$ABnWs`DIgq3AX6_rck1MR#uoG%YI=>UG#gbp$>mWD`D9E~&_s1c})!UpLThfWi{ z=A-CTOGw-yL+Mf8bfy^<>Px7^B`Ag_m8wrExA$vmTXOjOIeXqJjHtP$nfOK=scRdE zqAnETYDwX6$tH0EFX`QeL7&^TsrQfdMG>~x?8%@VQU)t-ol;!jr5xY#_oxTE+ADe% z#u6U5Xen+P(@AF%6RE}w$P<9iiwxGU$-lC?A5(2SU=l5{K0Ar4=xUZ)3TO_C!dV-gcM0Kw0los?5?eZ~L zDbr}Ja3zJsz>n;=Jz976f8O>PSNAHp)4H=k@uRm5Iv#pBG{jr${wkM5d>;d?gZds3 zZmywnCZ-E%kX?l2D?~xB70SQk9`_+juFmFln|RR9C@U=NxY?EuH*{qbLhfjRsps+Lb_(vU0yKsGrfH?!B)%pMSzN(0U7;ISv|B=x`Bc zMb2RC_SVzL+)btc<5z9A;HC9ysRlEOd_Z7 z+^JWLt;X3HNOZ|hxLv$EnNJpGeq`LSYIT?_4>T|~+3EC~fgVm(W;$JVYd=|QAZvVq zfR-}f=Ii@Tcz7#3cIF38W%UES3b{XRgOjTTnRscm@rvmO#PEP=S?)-s1!MT^fhFk4 zISt`LnOj=mmGb#-rTkD%(d$XBm@dOx-3p_mx+{0Zq8I+MRb%-Zx%`>X;{jl25wuTS= z+lfvHW-bSR+jn$@B2;Qk`rTxhJzm$B$&6ieKRrJE4i93*r}CElG_}>r(Xa1>$+kMP zK0UPebkvklIeE0#!UW0Z{LNmEcD}QZ;H2>yl1JPu0s)UX?uO?OrsDjx0jOe)HgV+b zdHeqWsq8 zW4(5~?NFHXlI&18X#+W;8q2Gd5|o=&wdTcD`cx?s+-FHGE&Ma&VE2Uc0>(CS#~^z~ zK8Y1ba7#$6gmhaplE!^q-67EqZjQ77I1yMN^dhAl=#2^UXZKJ+Q@dp{Wa6+}YRs|= z^O?SlV#3TF1bobzFWt~)xEJ~@bDbvXxB7D7gP0e~*IL410AJs?;NDH?u4UCkipP(= zgjFgVrND{Zci@QzzyHDpb|P2<1Atoo&em7TLBJ@0LpNqRcZC6mAml-A@~oJa%GS2)W3#uMwZEv-es(gy z_QetO$C6(*DZw-Zrh?miFMwZ#+fCAA_7R+e{x%)`@tkU&WtbHV^oSc?z$`RdT#8&r ztb&Iig^(YDnW@c7qpc0#)c$wQ5&qk~;L-TgfCQ4Oxy#h{y`l3j&5u3b()MWlS;UL! ziw&InAr%Au=p%!5i*Owu_n=+y>$G;PI2!?+N7g)%Y*l(;-S;9k#3dOG>rBQk5QQK25sOtxp-Nj?``)vua4L8FAqSuQ}z z5QToR)L0Tep*rpkzT@dF5onQ;XBD8wuP}_`ge-%EHlU*%1b)aUMX6Z8R|YyNpMmry7_kno@hsC|X|h)#i* z@9#@zFZd}3Uf!pT69_#}})ED4V1t%0YW z#ij2Ln4*oZ-UnMhue^WI`N`ASqO$6Z#2M6Z?%fWl(3rsP<*mTN3j2MqniOpRQr=8O z&@m4C9FZ|+GQd6S&p*UhtC271fMFqf!O5kyoHX@;eM@IG$~C-6UdHtOC^;n73Q45V z41TdOVX!Q5Z-1ja-w?gCfyQPxXU(Vd1|Rl&OE&s3T(i{UB#_zU=!JKq;PgkuU)T5F zr;E3BI&Gt)qJpD575k&_`%v#QIn|aE48>Yzvrrkhzg2FtL2rkfuN#lq-Z#=DAGw55 zSs5^Wg~hu3foAa)u|>&Ps3itD$^Y>@`-lJb@!N!@K;8`0*xB`ONVx#Zw@6l!B?*Wg z6{PP~?GX^M2JzI|jW*9?=7PPi-K5V$l47N!fM-J#YtyLbw2gLN;ZF1DOqtVw-a$pM zC}gfRtoRwm$bS|t>RK5BRK)+;&%k*)jymEN6@xKVQ7NI9K19TmKR6B-mQy8v_$lCs zUnSi{qZ$u`zKmTIf~sO6#!}j+tmn94NryJm&IAx{V>3&aM*UcAW7RAf3j`;=Xh1=- z;ir6<7>#03W7@>ZNn!M6>?lU!Z$@c-Itlj4Cf$~478L69iu$pmRqOd2*HD&Zt1%wW z&)8_QN0@1IRaE5Z?&o&@yTE#e)dj1dc{tvtBKi@lBX!)2Pyf8=eR@>V_v1L*w@=dd z;e1sRlc6lo8K?IKz;E6zaE*Wyx*{yXMIs>mSqIX;PDc~l9a=VOrIO@t+rNH)^5S#3 zB*y|&){dn71&p4&m97wQbtn(t^b_IL9~+1*`;KQb5;!CLYM)ovlG0KC8gu&};m;=u ziE#lP!CPm;CG}Ka90N(ob{o6qTHpNW{4|f~q!yswkTpF&2h)b!oDzAnn4x!yH=2Yy zV%qFu(SETNHWxlW33S60gN;wsaiJ1PsDSNVtl{3NUh^H zeiZB+j?ElBZI)dq*J*L3rvJFHo&_;tRQo=2oetfA+2y3VcH05tAg|lzt z^iJvKhn>Guf}M$GqxVdk$$aN z2oBWyv7o@wyerJis5IpOy~ktJ1XXnrY#8K? z0Td(}RNM0N$|6+m7Bk5uFk~EKPJ_fQ_y(k`RAiaY-VY^1G?GPeR4>O85N=R6gah6- zs;3ZH1Uu_HC8C29kkw{ieliGDXZaa%L#Xc~hg%rboq> z-m4;Zx76M!J7v{t&d3n-$v8YwlU=OR^yE^!QYo^(2m1Js+!*d=SNLQ#6DTEI zIPotJZ26`ch(qn*~xHesJW0`!tzQgg{AZ< zEsS`nUgAvJ4l}w`YJWX*7NOdIDhHcZZS{yPz;yNSv*LC$P^33&G<+~DsCiMCO~6Fm zi&wimv_y&~_e%ckD<*w~h}-%VU0tF~jlnfeXASnjVKF`^83nxBZbQ;*4}osQpF;3gNaOX-yt{n zy!vlLphK~y?!BCGSfC8@NM3PGy{-^|o1oG7wkQzc5x*O?k5b2PT?(PtfZjcmE;(tEh z=lVStZ{`)(b?$TazV}{xt+nask31qu9%NB|X(Me~Dzc{T$+>5uz#2ol+}9lv{9N-s zJU6a%IS8=^YI7>R`cz!-4ol4!!s)X}0iPvM^fDUmra=}NVWNmXar;|MxtgoxO9X;D;~1yJ(7Hytkaq*;w(b=ql&~l zz@>oka5~=lA<70Stf!HWq42BU)jSZv^zvJTZR!;LnZD-(#wF5n6V0A!Lr$}ps zS5K}#(v1M^tS0hyo%G~#^gs4uJK0NgJsd%&)$E8oOl6sZ!c}t{8xVCx2IJ4-h&SED zKAPGjAU(Trg1M0U3?sr{ahqtO+tda52X`NtL;Mv9IhqPBTNXM=kYCp<<8r?O$tZ%% z)J2iGAd~SyL7^dKUSy!Dl0&QZyHNzmqyl%TpRxqP)O#mJ{`bFcGH!;|eOwORE3d;j z36iYEO>kV=slb2lMPWbS0b#@%E<6i2UuAsOSM(No`*ie%APN5|$q?T$RZ=C- z^4V@vaE#>OL08@KMrYQ~N1Oz2Kfq(u@y9#B2N9@y-(~-G9JNp_g8iL!w~fiP1A!EP z=yX_MGov1G*4fthpo1WeARPvr850YtCB2N59Jjp1X6&U7YZ8MFdSAgrw0smpF4~Yg z*U#cMQ2K{>4%p|A`=hxsqPeo}Nj_nQ zU@JZ>4cZnd4|8XZv>3kCbZ34+Mqq};oWeAlrP+i{%)G4a079fO96C*SfcDaQkoynl zi85aQ@p-yk(tokXr-gr#E^UmE0ts|+Q6YV2j>$qk1#=-KmA3K5!-G^(2}PQ`>3!h| zRJ58V(jsNuZQVy4Y&>Wlso-MZ=}D?RJ=L^~LF3i$NE!u7o~4D3Xu~FD*c?96Z>MpZ zs`mS)noj$p4SIZ@^=&$&;djhuPJ-(c6YCqJ9Y&>t@v~u5+aRws*MES&eFdlgB?d4L z>AFc*nJ_ffh06ELmX01}WT$YpkX>H{np`wF=&$#GvomM4GU9SbS^uW|$U9qYQt}FJMZFLYDdkuDmW%1(!3BMdDqw>zp37^yCEvzTEx}W2 z_H}}F5z-Ginx3C0^v2@~9lba#%zi1Zo?(Q4`6dq%Ms@H zst6EqYAYHEl^BB?yw<3zX@y&J$(eU|lA_6mGi!$Q#3-CkpqU9eXc?+4SwxJ14^owRmjIQ4QToV>w z(-vCV4z0lu@j(w;?dTOv(0b9Ld$})&NNv4{p~cB9w_? z17)U^huF-7a`8wl8FRgFHVOb}Sf7DO?O%XnH$<=dSz;lwft#ot5Muyx40ar@YxV4a zJQ44!x*_OJ`*XbLpZn*he|+)YV|l9P49*9aDEmyt2z>Ddk<#VNJhLE`9%E(Qtb1C8 zZJIz7++dIfK;)sq!6kBf$P@NRQmYcm9TE(WV2QfTXG!H&q}nT2rnN(ghWl+nr`|2i z17YWgV`}vzEi}jH2*1?8{?beFZMDus|F<~fk~AX{T(fq7f#%SNY72^$g*!;Jy(wRM zMDX7q#wDl$YoKAYKq?$Ie*j<(e&?+RzkGj-YP9;HPV>old7mr0m2!Zckga#AmRcrF zKsI-FIl@te%*7}2G7Xo&F?BMyd_CNLo*cX*i7jemSY&N5au;y|l z`hTA;>2L7Fy9+@zpXY~z*FJ{@jCp>?dugVNM$$PI6XX3x(yM*%Cd?IoFGQQ&!`NX1 z=p10?JBI;+=u$BiEhILqZEhnFehMW%C#ge&4+v+MeI~g-5~1{j;FD`TVN{=q#KIm$ zaNVatggY{(wlyyqK16-Uv}KwwPCD=nqcBc+k(`b-T&lU^k`O1b)e{t@XG!>xtj=ps zH-M&jy6qjUN;%t1njDDwoSOf}{yiXH-$ZRK$d@UY*dMe|4cm*NN*RUaP@)Skz>9>;j6 zMEd@`gQ@UmesGV;)Gt@KS-GX#`7P(%`hQ#i^Im&}M@pv)+A)uC???==OJUQ`7o@eQ zH(ogXrPVSlq%@kDw57oGWv3wk;k`m4O@OYXyu?^&ZnYdcgm9KlhQu1{zjpoScdFd< zdNjeQ%}Z2?tE-WE>m#w#st}kqb~bIUT)e6!yat>YuKLhgtMgUYw9pasdW;FhL8M*a zow&Oomka?Sd>N5&g2_(P>(UI1$7!xK_P+&M|5*%QjpU!ybeU+gEBQdAF#q>nzgvi^ zkK+Ik07LxW$DdtKCvlXV6oM||1Q<;qZ?t7lgBC-OesKrY>@s_ ze*lI9fH)){stHP!^9|*Bk+W-H?RCP_b?^^wY_)4rUS$4f=5*0mX;8WXvVkswX9Zce z3pdRUm>>}^EWGq@Ju+w|p&vnKcvyGq80-rd1kyEPkJ30PGnI3s!0X+NIVR2T^&l## zIV54jLcs$u&`E+2KdmRa5eX<%iernxRccb@NLGjxLFxDAq#BHQjSRLt_v+jTgnCz9 zk+z1$ge`Hd!>+lsg$GeiY{%S0-0UUlUCv1CsRa|(?*V~11&~x9>*7D3Vwm3d0s`B} zg|B)_yp^D1tiax>CIeBIP*{FC82h!@IPEh)%8Gy9Ch1yhuxe57p$W zOe-^7O_hZ<#C&NL2$!3}Holo&mV}5a3oj1b7mq@Ls2Y0c15a~}f6e&MSzw(e__wss zGEEbF%avQfs|D4VXh#lQTSvF}ez-dkYY+3aChf*Z;bQuo?}=MR3~g-^*}BUoZU zQpai;E#+5v}1>vyWtgw!(0biTR2S!GDfK+SmPF?qcL z%U5pBB<9xm?AgZpLxTP{JkLjjsyQ{om?I|mVw#No-MZ-#bI-Qh?ZB$;d+M#JfM+)x z5iXd9zAks@M7309^M4PFv>j*><2C~OdsOza^A7rN0H_039czwf)NSHvSg&IGHBh0U ziB;H(BB0gVn_V0H?8`coEz!>sL{9#hnkK-ZUHic8s4vYd8+|pdA*hx#QLGHEN|~x_ z#7;#B_#&-u-w;p#I-5uC3se1EHd85GLTmLoZG*-Q5kPP#ggi|a@RbpTdkhf?CbFNB z&Ep?Q&w$t?TOJ%3BmsFSR`JS5cpxitcklO9wjKu2?uq*myQ^2m-qevGFGG;c4nt%4 z%!bGn5%TqgLl03ITA5wwQ8{Nr=`nfdlkN5rpT00#k(kV2@LK>n@}z!0?> zdx>Psg*{#lnZmZ-0J1^Ljx6eHf%m_25ivKxJ%jGxYf^ZbK4dugW?=W7{}_S6N*F2} z5vYY+fKKUGB;C(T`{!$$7bEb-%gwtmPzYi1*mI1NQamR0iUn}4NyIpdX)&7=XiKAs))OjvZ z4Nz17VM)3JJ`)nooyZ``?(kcF*~NK$cokMwn)Ua67buj~3j({R32?8=|2l$u=uM1j z5Vrj26tjpV{yVZYC7}qxxJI8Dmt^=d#z)C~i%r{RM&qE<$JL=BHEJ4=|+=e8eKM(Y1G3f58t%yF^Ym7o7jjnrC#O4fqcn3MDb)1dLg&q+H{b>Gq z-#b9lSg)q$$+@Hu(qK~8s6*Cph~}L9YYUM_R3$oea#{mkh`NR8J|d@$4LI#z?m31%V40`>HX2_L*ib-;8-jsAiIPRy6;$(D5lRxG3puak-w zzRvw%o6z5`BD(zF8xGv~CM)@I{Eg?b{GJf{v7q0eJGW3I>0+4$_2Ue1Um|@+ zdjYttSgp5t5Qk$u!=z~z+Wym6KZ|z5thxuON1!jjL*L!+sEPbLZ4f!HQMnVG4P?gL z5{#OW+V3Y;f2&hm_SqecixG|}q}-vY*skKAMNH<(@rlcZzF)xDS*t*AcHq@!eu8T@)lilO@`Q-h1=bXGH5mE?7&}FsJ=A zW4ylNgK*$xMq8zzh1Goan|>vb)Ajz~>WuY%;Ktbt_sETr?aL=l>TjsOJ8`K1udR{7geQF>gI-e$sUvjQ<4*;W$p?I68!I(ro=x zVCs06VEFjfNjH)@i4Ce{t~`6X<(vzNcyOZ8zWpesy#QJ_7v?q?LY4UKeao8bP!7

public interface IStatefulUserHubClient { + /// + /// Invoked when the server requests a client to disconnect. + /// + /// + /// When this request is received, the client must presume any and all further requests to the server + /// will either fail or be ignored. + /// This method is ONLY to be used for the purposes of: + /// + /// actually physically disconnecting from the server, + /// cleaning up / setting up any and all required local client state. + /// + /// Task DisconnectRequested(); } } From 060b01eee8d30d996858515829568feb827cc8b4 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 16 Feb 2024 20:24:02 +0300 Subject: [PATCH 3099/4092] Make CreateJudgement public again and add remarks --- .../Objects/EmptyFreeformHitObject.cs | 2 +- .../osu.Game.Rulesets.Pippidon/Objects/PippidonHitObject.cs | 2 +- .../Objects/EmptyScrollingHitObject.cs | 2 +- .../osu.Game.Rulesets.Pippidon/Objects/PippidonHitObject.cs | 2 +- osu.Game.Rulesets.Catch/Objects/Banana.cs | 2 +- osu.Game.Rulesets.Catch/Objects/BananaShower.cs | 2 +- osu.Game.Rulesets.Catch/Objects/Droplet.cs | 2 +- osu.Game.Rulesets.Catch/Objects/Fruit.cs | 2 +- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 2 +- osu.Game.Rulesets.Catch/Objects/TinyDroplet.cs | 2 +- osu.Game.Rulesets.Mania/Objects/BarLine.cs | 2 +- osu.Game.Rulesets.Mania/Objects/HoldNote.cs | 2 +- osu.Game.Rulesets.Mania/Objects/HoldNoteBody.cs | 2 +- osu.Game.Rulesets.Mania/Objects/Note.cs | 2 +- osu.Game.Rulesets.Mania/Objects/TailNote.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs | 2 +- osu.Game.Rulesets.Osu/Objects/HitCircle.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 +- osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs | 2 +- osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs | 2 +- osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs | 2 +- osu.Game.Rulesets.Osu/Objects/SliderTick.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Spinner.cs | 2 +- osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs | 2 +- osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/BarLine.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 4 ++-- osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/IgnoreHit.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/StrongNestedHitObject.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/Swell.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/SwellTick.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs | 2 +- osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs | 2 +- osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs | 2 +- osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs | 2 +- osu.Game/Database/StandardisedScoreMigrationTools.cs | 2 +- osu.Game/Rulesets/Objects/HitObject.cs | 6 +++++- osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs | 2 +- 39 files changed, 44 insertions(+), 40 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/EmptyFreeformHitObject.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/EmptyFreeformHitObject.cs index e166d09f84..9cd18d2d9f 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/EmptyFreeformHitObject.cs +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/EmptyFreeformHitObject.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.EmptyFreeform.Objects { public class EmptyFreeformHitObject : HitObject, IHasPosition { - protected override Judgement CreateJudgement() => new Judgement(); + public override Judgement CreateJudgement() => new Judgement(); public Vector2 Position { get; set; } diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/PippidonHitObject.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/PippidonHitObject.cs index 748e6d3b53..0c22554e82 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/PippidonHitObject.cs +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/PippidonHitObject.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Pippidon.Objects { public class PippidonHitObject : HitObject, IHasPosition { - protected override Judgement CreateJudgement() => new Judgement(); + public override Judgement CreateJudgement() => new Judgement(); public Vector2 Position { get; set; } diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/EmptyScrollingHitObject.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/EmptyScrollingHitObject.cs index 4564bd1e09..9b469be496 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/EmptyScrollingHitObject.cs +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/EmptyScrollingHitObject.cs @@ -8,6 +8,6 @@ namespace osu.Game.Rulesets.EmptyScrolling.Objects { public class EmptyScrollingHitObject : HitObject { - protected override Judgement CreateJudgement() => new Judgement(); + public override Judgement CreateJudgement() => new Judgement(); } } diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/PippidonHitObject.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/PippidonHitObject.cs index ed16bce9f6..9dd135479f 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/PippidonHitObject.cs +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/PippidonHitObject.cs @@ -13,6 +13,6 @@ namespace osu.Game.Rulesets.Pippidon.Objects /// public int Lane; - protected override Judgement CreateJudgement() => new Judgement(); + public override Judgement CreateJudgement() => new Judgement(); } } diff --git a/osu.Game.Rulesets.Catch/Objects/Banana.cs b/osu.Game.Rulesets.Catch/Objects/Banana.cs index 30bdb24b14..b80527f379 100644 --- a/osu.Game.Rulesets.Catch/Objects/Banana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Banana.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Catch.Objects /// public int BananaIndex; - protected override Judgement CreateJudgement() => new CatchBananaJudgement(); + public override Judgement CreateJudgement() => new CatchBananaJudgement(); private static readonly IList default_banana_samples = new List { new BananaHitSampleInfo() }.AsReadOnly(); diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs index 86c41fce90..328cc2b52a 100644 --- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Catch.Objects { public override bool LastInCombo => true; - protected override Judgement CreateJudgement() => new IgnoreJudgement(); + public override Judgement CreateJudgement() => new IgnoreJudgement(); protected override void CreateNestedHitObjects(CancellationToken cancellationToken) { diff --git a/osu.Game.Rulesets.Catch/Objects/Droplet.cs b/osu.Game.Rulesets.Catch/Objects/Droplet.cs index 107c6c3979..9c1004a04b 100644 --- a/osu.Game.Rulesets.Catch/Objects/Droplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Droplet.cs @@ -8,6 +8,6 @@ namespace osu.Game.Rulesets.Catch.Objects { public class Droplet : PalpableCatchHitObject { - protected override Judgement CreateJudgement() => new CatchDropletJudgement(); + public override Judgement CreateJudgement() => new CatchDropletJudgement(); } } diff --git a/osu.Game.Rulesets.Catch/Objects/Fruit.cs b/osu.Game.Rulesets.Catch/Objects/Fruit.cs index 17270b803c..4818fe2cad 100644 --- a/osu.Game.Rulesets.Catch/Objects/Fruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Fruit.cs @@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Catch.Objects { public class Fruit : PalpableCatchHitObject { - protected override Judgement CreateJudgement() => new CatchJudgement(); + public override Judgement CreateJudgement() => new CatchJudgement(); public static FruitVisualRepresentation GetVisualRepresentation(int indexInBeatmap) => (FruitVisualRepresentation)(indexInBeatmap % 4); } diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 49c24df5b9..671291ef0e 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Catch.Objects /// private const float base_scoring_distance = 100; - protected override Judgement CreateJudgement() => new IgnoreJudgement(); + public override Judgement CreateJudgement() => new IgnoreJudgement(); public int RepeatCount { get; set; } diff --git a/osu.Game.Rulesets.Catch/Objects/TinyDroplet.cs b/osu.Game.Rulesets.Catch/Objects/TinyDroplet.cs index ddcb92875f..1bf160b5a6 100644 --- a/osu.Game.Rulesets.Catch/Objects/TinyDroplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/TinyDroplet.cs @@ -8,6 +8,6 @@ namespace osu.Game.Rulesets.Catch.Objects { public class TinyDroplet : Droplet { - protected override Judgement CreateJudgement() => new CatchTinyDropletJudgement(); + public override Judgement CreateJudgement() => new CatchTinyDropletJudgement(); } } diff --git a/osu.Game.Rulesets.Mania/Objects/BarLine.cs b/osu.Game.Rulesets.Mania/Objects/BarLine.cs index 742b5e4b0d..cf576239ed 100644 --- a/osu.Game.Rulesets.Mania/Objects/BarLine.cs +++ b/osu.Game.Rulesets.Mania/Objects/BarLine.cs @@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Mania.Objects set => major.Value = value; } - protected override Judgement CreateJudgement() => new IgnoreJudgement(); + public override Judgement CreateJudgement() => new IgnoreJudgement(); } } diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index 4aac455bc5..3f930a310b 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Mania.Objects }); } - protected override Judgement CreateJudgement() => new IgnoreJudgement(); + public override Judgement CreateJudgement() => new IgnoreJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNoteBody.cs b/osu.Game.Rulesets.Mania/Objects/HoldNoteBody.cs index 92b649c174..47163d0d81 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNoteBody.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNoteBody.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania.Objects /// public class HoldNoteBody : ManiaHitObject { - protected override Judgement CreateJudgement() => new HoldNoteBodyJudgement(); + public override Judgement CreateJudgement() => new HoldNoteBodyJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } diff --git a/osu.Game.Rulesets.Mania/Objects/Note.cs b/osu.Game.Rulesets.Mania/Objects/Note.cs index b0f2991918..0035960c63 100644 --- a/osu.Game.Rulesets.Mania/Objects/Note.cs +++ b/osu.Game.Rulesets.Mania/Objects/Note.cs @@ -11,6 +11,6 @@ namespace osu.Game.Rulesets.Mania.Objects /// public class Note : ManiaHitObject { - protected override Judgement CreateJudgement() => new ManiaJudgement(); + public override Judgement CreateJudgement() => new ManiaJudgement(); } } diff --git a/osu.Game.Rulesets.Mania/Objects/TailNote.cs b/osu.Game.Rulesets.Mania/Objects/TailNote.cs index bddb4630cb..def32880f1 100644 --- a/osu.Game.Rulesets.Mania/Objects/TailNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/TailNote.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Objects /// public const double RELEASE_WINDOW_LENIENCE = 1.5; - protected override Judgement CreateJudgement() => new ManiaJudgement(); + public override Judgement CreateJudgement() => new ManiaJudgement(); public override double MaximumJudgementOffset => base.MaximumJudgementOffset * RELEASE_WINDOW_LENIENCE; } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs index f07a1e930b..2c9292c58b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs @@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Osu.Mods { } - protected override Judgement CreateJudgement() => new OsuJudgement(); + public override Judgement CreateJudgement() => new OsuJudgement(); } private partial class StrictTrackingDrawableSliderTail : DrawableSliderTail diff --git a/osu.Game.Rulesets.Osu/Objects/HitCircle.cs b/osu.Game.Rulesets.Osu/Objects/HitCircle.cs index 6336482ccc..d652db0fd4 100644 --- a/osu.Game.Rulesets.Osu/Objects/HitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/HitCircle.cs @@ -8,6 +8,6 @@ namespace osu.Game.Rulesets.Osu.Objects { public class HitCircle : OsuHitObject { - protected override Judgement CreateJudgement() => new OsuJudgement(); + public override Judgement CreateJudgement() => new OsuJudgement(); } } diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index fc0248cbbd..506145568e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -275,7 +275,7 @@ namespace osu.Game.Rulesets.Osu.Objects TailSamples = this.GetNodeSamples(repeatCount + 1); } - protected override Judgement CreateJudgement() => ClassicSliderBehaviour + public override Judgement CreateJudgement() => ClassicSliderBehaviour // Final combo is provided by the slider itself - see logic in `DrawableSlider.CheckForResult()` ? new OsuJudgement() // Final combo is provided by the tail circle - see `SliderTailCircle` diff --git a/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs index 8d60864f0b..2d5a5b7727 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Objects protected override HitWindows CreateHitWindows() => HitWindows.Empty; - protected override Judgement CreateJudgement() => new SliderEndJudgement(); + public override Judgement CreateJudgement() => new SliderEndJudgement(); public class SliderEndJudgement : OsuJudgement { diff --git a/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs index 4760135081..8305481788 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs @@ -14,6 +14,6 @@ namespace osu.Game.Rulesets.Osu.Objects /// public bool ClassicSliderBehaviour; - protected override Judgement CreateJudgement() => ClassicSliderBehaviour ? new SliderTickJudgement() : base.CreateJudgement(); + public override Judgement CreateJudgement() => ClassicSliderBehaviour ? new SliderTickJudgement() : base.CreateJudgement(); } } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index 42d8d895e4..ee2490439f 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Objects { } - protected override Judgement CreateJudgement() => ClassicSliderBehaviour ? new LegacyTailJudgement() : new TailJudgement(); + public override Judgement CreateJudgement() => ClassicSliderBehaviour ? new LegacyTailJudgement() : new TailJudgement(); public class LegacyTailJudgement : OsuJudgement { diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs index 1d7ba2fbaf..74ec4d6eb3 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs @@ -32,6 +32,6 @@ namespace osu.Game.Rulesets.Osu.Objects protected override HitWindows CreateHitWindows() => HitWindows.Empty; - protected override Judgement CreateJudgement() => new SliderTickJudgement(); + public override Judgement CreateJudgement() => new SliderTickJudgement(); } } diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index 9baa645b3c..e3dfe8e69a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -95,7 +95,7 @@ namespace osu.Game.Rulesets.Osu.Objects } } - protected override Judgement CreateJudgement() => new OsuJudgement(); + public override Judgement CreateJudgement() => new OsuJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs index 57db29ef0c..8d53100529 100644 --- a/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs @@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Osu.Objects { public class SpinnerBonusTick : SpinnerTick { - protected override Judgement CreateJudgement() => new OsuSpinnerBonusTickJudgement(); + public override Judgement CreateJudgement() => new OsuSpinnerBonusTickJudgement(); public class OsuSpinnerBonusTickJudgement : OsuSpinnerTickJudgement { diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs index cb59014909..7989c9b7ff 100644 --- a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Objects /// public double SpinnerDuration { get; set; } - protected override Judgement CreateJudgement() => new OsuSpinnerTickJudgement(); + public override Judgement CreateJudgement() => new OsuSpinnerTickJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; diff --git a/osu.Game.Rulesets.Taiko/Objects/BarLine.cs b/osu.Game.Rulesets.Taiko/Objects/BarLine.cs index d87f8b3232..46b3f13501 100644 --- a/osu.Game.Rulesets.Taiko/Objects/BarLine.cs +++ b/osu.Game.Rulesets.Taiko/Objects/BarLine.cs @@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Taiko.Objects set => major.Value = value; } - protected override Judgement CreateJudgement() => new IgnoreJudgement(); + public override Judgement CreateJudgement() => new IgnoreJudgement(); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 50cd722a3f..f3143de345 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Taiko.Objects } } - protected override Judgement CreateJudgement() => new IgnoreJudgement(); + public override Judgement CreateJudgement() => new IgnoreJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Taiko.Objects public class StrongNestedHit : StrongNestedHitObject { // The strong hit of the drum roll doesn't actually provide any score. - protected override Judgement CreateJudgement() => new IgnoreJudgement(); + public override Judgement CreateJudgement() => new IgnoreJudgement(); public StrongNestedHit(TaikoHitObject parent) : base(parent) diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs index c1d4102042..dc082ffd21 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Taiko.Objects Parent = parent; } - protected override Judgement CreateJudgement() => new TaikoDrumRollTickJudgement(); + public override Judgement CreateJudgement() => new TaikoDrumRollTickJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; diff --git a/osu.Game.Rulesets.Taiko/Objects/IgnoreHit.cs b/osu.Game.Rulesets.Taiko/Objects/IgnoreHit.cs index 44cd700faf..302f940ef4 100644 --- a/osu.Game.Rulesets.Taiko/Objects/IgnoreHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/IgnoreHit.cs @@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Taiko.Objects { public class IgnoreHit : Hit { - protected override Judgement CreateJudgement() => new IgnoreJudgement(); + public override Judgement CreateJudgement() => new IgnoreJudgement(); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/StrongNestedHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/StrongNestedHitObject.cs index 227ab4ab52..14cbe338ed 100644 --- a/osu.Game.Rulesets.Taiko/Objects/StrongNestedHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/StrongNestedHitObject.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Taiko.Objects Parent = parent; } - protected override Judgement CreateJudgement() => new TaikoStrongJudgement(); + public override Judgement CreateJudgement() => new TaikoStrongJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; } diff --git a/osu.Game.Rulesets.Taiko/Objects/Swell.cs b/osu.Game.Rulesets.Taiko/Objects/Swell.cs index 76d106f924..a8db8df021 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Swell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Swell.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Taiko.Objects } } - protected override Judgement CreateJudgement() => new TaikoSwellJudgement(); + public override Judgement CreateJudgement() => new TaikoSwellJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; } diff --git a/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs index be1c1101de..41fb9cac7e 100644 --- a/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs @@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Taiko.Objects { public class SwellTick : TaikoHitObject { - protected override Judgement CreateJudgement() => new IgnoreJudgement(); + public override Judgement CreateJudgement() => new IgnoreJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; } diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs index 697c23addf..1a1fde1990 100644 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Taiko.Objects /// public const float DEFAULT_SIZE = 0.475f; - protected override Judgement CreateJudgement() => new TaikoJudgement(); + public override Judgement CreateJudgement() => new TaikoJudgement(); protected override HitWindows CreateHitWindows() => new TaikoHitWindows(); } diff --git a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs index f0f93f59b5..584a9e09c0 100644 --- a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs @@ -358,7 +358,7 @@ namespace osu.Game.Tests.Gameplay this.maxResult = maxResult; } - protected override Judgement CreateJudgement() => new TestJudgement(maxResult); + public override Judgement CreateJudgement() => new TestJudgement(maxResult); protected override HitWindows CreateHitWindows() => new HitWindows(); private class TestJudgement : Judgement diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs index a428979015..8ec18377f4 100644 --- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs @@ -196,7 +196,7 @@ namespace osu.Game.Tests.Gameplay this.maxResult = maxResult; } - protected override Judgement CreateJudgement() => new TestJudgement(maxResult); + public override Judgement CreateJudgement() => new TestJudgement(maxResult); } } } diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index ea43a65825..1647fbee42 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -441,7 +441,7 @@ namespace osu.Game.Tests.Rulesets.Scoring private readonly HitResult maxResult; private readonly HitResult? minResult; - protected override Judgement CreateJudgement() => new TestJudgement(maxResult, minResult); + public override Judgement CreateJudgement() => new TestJudgement(maxResult, minResult); public TestHitObject(HitResult maxResult, HitResult? minResult = null) { diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index 576d08f491..403e73ab77 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -655,7 +655,7 @@ namespace osu.Game.Database { private readonly Judgement judgement; - protected override Judgement CreateJudgement() => judgement; + public override Judgement CreateJudgement() => judgement; public FakeHit(Judgement judgement) { diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index aed821332d..317dd35fef 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -173,8 +173,12 @@ namespace osu.Game.Rulesets.Objects /// /// Creates the that represents the scoring information for this . /// + /// + /// Use to avoid unnecessary allocations. + /// This method has been left public for compatibility reasons and eventually will be made protected. + /// [NotNull] - protected virtual Judgement CreateJudgement() => new Judgement(); + public virtual Judgement CreateJudgement() => new Judgement(); /// /// Creates the for this . diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs index 499953dab9..bb36aab0b3 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Objects.Legacy public int ComboOffset { get; set; } - protected override Judgement CreateJudgement() => new IgnoreJudgement(); + public override Judgement CreateJudgement() => new IgnoreJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; } From b0f334c39e4ac1c3b587fa31b7c7759cfa5ec281 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 17 Feb 2024 00:45:30 +0300 Subject: [PATCH 3100/4092] Fix DrawableLinkCompiler allocations --- osu.Game/Online/Chat/DrawableLinkCompiler.cs | 32 ++++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/Chat/DrawableLinkCompiler.cs b/osu.Game/Online/Chat/DrawableLinkCompiler.cs index 883a2496f7..fa107a0e43 100644 --- a/osu.Game/Online/Chat/DrawableLinkCompiler.cs +++ b/osu.Game/Online/Chat/DrawableLinkCompiler.cs @@ -4,9 +4,11 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Extensions.ListExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Lists; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -23,12 +25,21 @@ namespace osu.Game.Online.Chat /// /// Each word part of a chat link (split for word-wrap support). /// - public readonly List Parts; + public readonly SlimReadOnlyListWrapper Parts; [Resolved] private OverlayColourProvider? overlayColourProvider { get; set; } - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parts.Any(d => d.ReceivePositionalInputAt(screenSpacePos)); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) + { + foreach (var part in Parts) + { + if (part.ReceivePositionalInputAt(screenSpacePos)) + return true; + } + + return false; + } protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new LinkHoverSounds(sampleSet, Parts); @@ -39,7 +50,7 @@ namespace osu.Game.Online.Chat public DrawableLinkCompiler(IEnumerable parts) { - Parts = parts.ToList(); + Parts = parts.ToList().AsSlimReadOnly(); } [BackgroundDependencyLoader] @@ -52,15 +63,24 @@ namespace osu.Game.Online.Chat private partial class LinkHoverSounds : HoverClickSounds { - private readonly List parts; + private readonly SlimReadOnlyListWrapper parts; - public LinkHoverSounds(HoverSampleSet sampleSet, List parts) + public LinkHoverSounds(HoverSampleSet sampleSet, SlimReadOnlyListWrapper parts) : base(sampleSet) { this.parts = parts; } - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => parts.Any(d => d.ReceivePositionalInputAt(screenSpacePos)); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) + { + foreach (var part in parts) + { + if (part.ReceivePositionalInputAt(screenSpacePos)) + return true; + } + + return false; + } } } } From ce903987e7e71b22ac138b5985fc6ed37b3c5507 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 17 Feb 2024 00:53:53 +0300 Subject: [PATCH 3101/4092] Fix cursor ripples being added on release positions in replays --- osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs index 4cb91aa103..d898f1a1a8 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs @@ -11,6 +11,7 @@ using osu.Framework.Input.Events; using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Skinning.Default; +using osu.Game.Screens.Play; using osu.Game.Skinning; using osuTK; @@ -39,6 +40,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor public bool OnPressed(KeyBindingPressEvent e) { + if ((Clock as IGameplayClock)?.IsRewinding == true) + return false; + if (showRipples.Value) { AddInternal(ripplePool.Get(r => From 8169d1ac80de9b5cf123c0c5540f4a760fce8f3e Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 21 Jan 2024 18:32:07 -0800 Subject: [PATCH 3102/4092] Add twenty star counter in visual test --- .../Visual/Gameplay/TestSceneStarCounter.cs | 43 +++++++++++-------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStarCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStarCounter.cs index b002e90bb0..fa17b77b55 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStarCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStarCounter.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Utils; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -14,39 +15,47 @@ namespace osu.Game.Tests.Visual.Gameplay public partial class TestSceneStarCounter : OsuTestScene { private readonly StarCounter starCounter; + private readonly StarCounter twentyStarCounter; private readonly OsuSpriteText starsLabel; public TestSceneStarCounter() { - starCounter = new StarCounter + Add(new FillFlowContainer { - Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, Anchor = Anchor.Centre, - }; - - Add(starCounter); - - starsLabel = new OsuSpriteText - { Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Scale = new Vector2(2), - Y = 50, - }; - - Add(starsLabel); + Direction = FillDirection.Vertical, + Spacing = new Vector2(20), + Children = new Drawable[] + { + starCounter = new StarCounter(), + twentyStarCounter = new StarCounter(20), + starsLabel = new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Scale = new Vector2(2), + }, + } + }); setStars(5); - AddRepeatStep("random value", () => setStars(RNG.NextSingle() * (starCounter.StarCount + 1)), 10); - AddSliderStep("exact value", 0f, 10f, 5f, setStars); - AddStep("stop animation", () => starCounter.StopAnimation()); + AddRepeatStep("random value", () => setStars(RNG.NextSingle() * (twentyStarCounter.StarCount + 1)), 10); + AddSliderStep("exact value", 0f, 20f, 5f, setStars); + AddStep("stop animation", () => + { + starCounter.StopAnimation(); + twentyStarCounter.StopAnimation(); + }); AddStep("reset", () => setStars(0)); } private void setStars(float stars) { starCounter.Current = stars; + twentyStarCounter.Current = stars; starsLabel.Text = starCounter.Current.ToString("0.00"); } } From 6e8d8b977e000481ca0e769f514371a8bb188bdf Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 21 Jan 2024 18:34:55 -0800 Subject: [PATCH 3103/4092] Move ternary inside `Math.Max()` --- osu.Game/Graphics/UserInterface/StarCounter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/StarCounter.cs b/osu.Game/Graphics/UserInterface/StarCounter.cs index 720f479216..5fdc6a4904 100644 --- a/osu.Game/Graphics/UserInterface/StarCounter.cs +++ b/osu.Game/Graphics/UserInterface/StarCounter.cs @@ -115,7 +115,7 @@ namespace osu.Game.Graphics.UserInterface star.ClearTransforms(true); - double delay = (current <= newValue ? Math.Max(i - current, 0) : Math.Max(current - 1 - i, 0)) * AnimationDelay; + double delay = Math.Max(current <= newValue ? i - current : current - 1 - i, 0) * AnimationDelay; using (star.BeginDelayedSequence(delay)) star.DisplayAt(getStarScale(i, newValue)); From 7a74eaa2dec61e1342e9b3c8ca3f3eed1a0ce30f Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 21 Jan 2024 18:47:24 -0800 Subject: [PATCH 3104/4092] Fix star counter decrease animation being delayed when current is over displayed star count --- osu.Game/Graphics/UserInterface/StarCounter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/StarCounter.cs b/osu.Game/Graphics/UserInterface/StarCounter.cs index 5fdc6a4904..4e9e34d840 100644 --- a/osu.Game/Graphics/UserInterface/StarCounter.cs +++ b/osu.Game/Graphics/UserInterface/StarCounter.cs @@ -115,7 +115,7 @@ namespace osu.Game.Graphics.UserInterface star.ClearTransforms(true); - double delay = Math.Max(current <= newValue ? i - current : current - 1 - i, 0) * AnimationDelay; + double delay = Math.Max(current <= newValue ? i - current : Math.Min(current, StarCount) - 1 - i, 0) * AnimationDelay; using (star.BeginDelayedSequence(delay)) star.DisplayAt(getStarScale(i, newValue)); From 22f5a66c029bed6b135cbb3c99b63363e4248ecd Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 17 Feb 2024 15:46:38 +0300 Subject: [PATCH 3105/4092] Reduce allocations during beatmap selection --- .../Preprocessing/OsuDifficultyHitObject.cs | 8 ++++- osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 8 +++-- osu.Game.Rulesets.Osu/Objects/Slider.cs | 36 +++++++++++++++---- osu.Game/Rulesets/Objects/SliderPath.cs | 15 ++++---- osu.Game/Screens/Select/BeatmapCarousel.cs | 19 ++++++++-- 5 files changed, 67 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 488d1e2e9f..0e537632b1 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -232,7 +232,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing IList nestedObjects = slider.NestedHitObjects; - SliderTick? lastRealTick = slider.NestedHitObjects.OfType().LastOrDefault(); + SliderTick? lastRealTick = null; + + foreach (var hitobject in slider.NestedHitObjects) + { + if (hitobject is SliderTick tick) + lastRealTick = tick; + } if (lastRealTick?.StartTime > trackingEndTime) { diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 74631400ca..6c77d9189c 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -149,8 +148,11 @@ namespace osu.Game.Rulesets.Osu.Objects { StackHeightBindable.BindValueChanged(height => { - foreach (var nested in NestedHitObjects.OfType()) - nested.StackHeight = height.NewValue; + foreach (var nested in NestedHitObjects) + { + if (nested is OsuHitObject osuHitObject) + osuHitObject.StackHeight = height.NewValue; + } }); } diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 506145568e..8a87e17089 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -252,18 +252,42 @@ namespace osu.Game.Rulesets.Osu.Objects protected void UpdateNestedSamples() { - var firstSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL) - ?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933) + HitSampleInfo firstSample = null; + + for (int i = 0; i < Samples.Count; i++) + { + // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933) + if (i == 0) + { + firstSample = Samples[i]; + continue; + } + + if (Samples[i].Name != HitSampleInfo.HIT_NORMAL) + continue; + + firstSample = Samples[i]; + break; + } + var sampleList = new List(); if (firstSample != null) sampleList.Add(firstSample.With("slidertick")); - foreach (var tick in NestedHitObjects.OfType()) - tick.Samples = sampleList; + foreach (var nested in NestedHitObjects) + { + switch (nested) + { + case SliderTick tick: + tick.Samples = sampleList; + break; - foreach (var repeat in NestedHitObjects.OfType()) - repeat.Samples = this.GetNodeSamples(repeat.RepeatIndex + 1); + case SliderRepeat repeat: + repeat.Samples = this.GetNodeSamples(repeat.RepeatIndex + 1); + break; + } + } if (HeadCircle != null) HeadCircle.Samples = this.GetNodeSamples(0); diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index dc71608132..f33a07f082 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -61,16 +61,17 @@ namespace osu.Game.Rulesets.Objects case NotifyCollectionChangedAction.Add: Debug.Assert(args.NewItems != null); - foreach (var c in args.NewItems.Cast()) - c.Changed += invalidate; + foreach (object? newItem in args.NewItems) + ((PathControlPoint)newItem).Changed += invalidate; + break; case NotifyCollectionChangedAction.Reset: case NotifyCollectionChangedAction.Remove: Debug.Assert(args.OldItems != null); - foreach (var c in args.OldItems.Cast()) - c.Changed -= invalidate; + foreach (object? oldItem in args.OldItems) + ((PathControlPoint)oldItem).Changed -= invalidate; break; } @@ -269,10 +270,10 @@ namespace osu.Game.Rulesets.Objects { List subPath = calculateSubPath(segmentVertices, segmentType); // Skip the first vertex if it is the same as the last vertex from the previous segment - int skipFirst = calculatedPath.Count > 0 && subPath.Count > 0 && calculatedPath.Last() == subPath[0] ? 1 : 0; + bool skipFirst = calculatedPath.Count > 0 && subPath.Count > 0 && calculatedPath.Last() == subPath[0]; - foreach (Vector2 t in subPath.Skip(skipFirst)) - calculatedPath.Add(t); + for (int j = skipFirst ? 1 : 0; j < subPath.Count; j++) + calculatedPath.Add(subPath[j]); } if (i > 0) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 70ecde3858..ae0f397d52 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -510,12 +510,27 @@ namespace osu.Game.Screens.Select if (beatmapInfo?.Hidden != false) return false; - foreach (CarouselBeatmapSet set in beatmapSets) + foreach (var carouselItem in root.Items) { + if (carouselItem is not CarouselBeatmapSet set) + continue; + if (!bypassFilters && set.Filtered.Value) continue; - var item = set.Beatmaps.FirstOrDefault(p => p.BeatmapInfo.Equals(beatmapInfo)); + CarouselBeatmap? item = null; + + foreach (var setCarouselItem in set.Items) + { + if (setCarouselItem is not CarouselBeatmap setCarouselBeatmap) + continue; + + if (!setCarouselBeatmap.BeatmapInfo.Equals(beatmapInfo)) + continue; + + item = setCarouselBeatmap; + break; + } if (item == null) // The beatmap that needs to be selected doesn't exist in this set From 62a7e315bf669fea52ea2bb153995f08f16e9661 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 18 Feb 2024 03:11:29 +0800 Subject: [PATCH 3106/4092] 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 f61ff79b9f..85171cc0fa 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 506bebfd47..f23debd38f 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From 0714a4fc1e23a4a3e7672ff7a075fe86d6878194 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 18 Feb 2024 03:18:50 +0800 Subject: [PATCH 3107/4092] Revert sample lookup logic that was not allocating anything --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 8a87e17089..900f42d96b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -252,23 +252,8 @@ namespace osu.Game.Rulesets.Osu.Objects protected void UpdateNestedSamples() { - HitSampleInfo firstSample = null; - - for (int i = 0; i < Samples.Count; i++) - { - // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933) - if (i == 0) - { - firstSample = Samples[i]; - continue; - } - - if (Samples[i].Name != HitSampleInfo.HIT_NORMAL) - continue; - - firstSample = Samples[i]; - break; - } + var firstSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL) + ?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933) var sampleList = new List(); From 0df6e8f5950aaeac17ff1fefee6fb80b3f73d4f3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 18 Feb 2024 03:22:10 +0800 Subject: [PATCH 3108/4092] Remove list allocations in `UpdateNestedSamples` --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 900f42d96b..79bf91bcae 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -252,20 +252,16 @@ namespace osu.Game.Rulesets.Osu.Objects protected void UpdateNestedSamples() { - var firstSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL) - ?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933) - - var sampleList = new List(); - - if (firstSample != null) - sampleList.Add(firstSample.With("slidertick")); + // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933) + HitSampleInfo tickSample = (Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL) ?? Samples.First()).With("slidertick"); foreach (var nested in NestedHitObjects) { switch (nested) { case SliderTick tick: - tick.Samples = sampleList; + tick.SamplesBindable.Clear(); + tick.SamplesBindable.Add(tickSample); break; case SliderRepeat repeat: From dd82de473a1b42ad77d871ad7faeea6b5f1c718c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 17 Feb 2024 22:42:47 +0300 Subject: [PATCH 3109/4092] Revert BeatmapCarousel changes --- osu.Game/Screens/Select/BeatmapCarousel.cs | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index ae0f397d52..70ecde3858 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -510,27 +510,12 @@ namespace osu.Game.Screens.Select if (beatmapInfo?.Hidden != false) return false; - foreach (var carouselItem in root.Items) + foreach (CarouselBeatmapSet set in beatmapSets) { - if (carouselItem is not CarouselBeatmapSet set) - continue; - if (!bypassFilters && set.Filtered.Value) continue; - CarouselBeatmap? item = null; - - foreach (var setCarouselItem in set.Items) - { - if (setCarouselItem is not CarouselBeatmap setCarouselBeatmap) - continue; - - if (!setCarouselBeatmap.BeatmapInfo.Equals(beatmapInfo)) - continue; - - item = setCarouselBeatmap; - break; - } + var item = set.Beatmaps.FirstOrDefault(p => p.BeatmapInfo.Equals(beatmapInfo)); if (item == null) // The beatmap that needs to be selected doesn't exist in this set From cd8ac6a722124e7827f2a8ee0ef39ef2ab41a932 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sat, 17 Feb 2024 22:12:15 +0200 Subject: [PATCH 3110/4092] Update BeatmapAttributesDisplay.cs --- .../Overlays/Mods/BeatmapAttributesDisplay.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index b9e4896b21..9312940001 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -19,6 +19,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Online.Rooms; using osuTK; namespace osu.Game.Overlays.Mods @@ -42,6 +43,9 @@ namespace osu.Game.Overlays.Mods [Resolved] private Bindable> mods { get; set; } = null!; + [Resolved(CanBeNull = true)] + private IBindable? selectedItem { get; set; } + public BindableBool Collapsed { get; } = new BindableBool(true); private ModSettingChangeTracker? modSettingChangeTracker; @@ -108,6 +112,8 @@ namespace osu.Game.Overlays.Mods updateValues(); }, true); + selectedItem?.BindValueChanged(_ => mods.TriggerChange()); + BeatmapInfo.BindValueChanged(_ => updateValues(), true); Collapsed.BindValueChanged(_ => @@ -164,10 +170,20 @@ namespace osu.Game.Overlays.Mods starRatingDisplay.FinishTransforms(true); }); + Ruleset ruleset = gameRuleset.Value.CreateInstance(); + double rate = 1; foreach (var mod in mods.Value.OfType()) rate = mod.ApplyToRate(0, rate); + if (selectedItem != null && selectedItem.Value != null) + { + var globalMods = selectedItem.Value.RequiredMods.Select(m => m.ToMod(ruleset)); + + foreach (var mod in globalMods.OfType()) + rate = mod.ApplyToRate(0, rate); + } + bpmDisplay.Current.Value = BeatmapInfo.Value.BPM * rate; BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(BeatmapInfo.Value.Difficulty); @@ -175,7 +191,6 @@ namespace osu.Game.Overlays.Mods foreach (var mod in mods.Value.OfType()) mod.ApplyToDifficulty(originalDifficulty); - Ruleset ruleset = gameRuleset.Value.CreateInstance(); BeatmapDifficulty adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(originalDifficulty, rate); TooltipContent = new AdjustedAttributesTooltip.Data(originalDifficulty, adjustedDifficulty); From 572f693eec361a69d83dedc317fa9608bd816099 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 17 Feb 2024 23:28:35 +0300 Subject: [PATCH 3111/4092] Fix failing tests related to slider ticks --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 79bf91bcae..203e829180 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -253,7 +253,7 @@ namespace osu.Game.Rulesets.Osu.Objects protected void UpdateNestedSamples() { // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933) - HitSampleInfo tickSample = (Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL) ?? Samples.First()).With("slidertick"); + HitSampleInfo tickSample = (Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL) ?? Samples.FirstOrDefault())?.With("slidertick"); foreach (var nested in NestedHitObjects) { @@ -261,7 +261,9 @@ namespace osu.Game.Rulesets.Osu.Objects { case SliderTick tick: tick.SamplesBindable.Clear(); - tick.SamplesBindable.Add(tickSample); + + if (tickSample != null) + tick.SamplesBindable.Add(tickSample); break; case SliderRepeat repeat: From ed819fde1589bc0cc6167b4610c5a115e49fc844 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sat, 17 Feb 2024 23:01:31 +0200 Subject: [PATCH 3112/4092] Fixed bugs and added ranked status update --- .../Overlays/Mods/BeatmapAttributesDisplay.cs | 32 ++++++++++++------- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 32 +++++++++++++++++-- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 3 +- 3 files changed, 52 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index 9312940001..da14382175 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -40,11 +40,16 @@ namespace osu.Game.Overlays.Mods public Bindable BeatmapInfo { get; } = new Bindable(); + /// + /// Should attribute display account for the multiplayer room global mods. + /// + public bool AccountForMultiplayerMods = false; + [Resolved] private Bindable> mods { get; set; } = null!; [Resolved(CanBeNull = true)] - private IBindable? selectedItem { get; set; } + private IBindable? multiplayerRoomItem { get; set; } public BindableBool Collapsed { get; } = new BindableBool(true); @@ -112,7 +117,7 @@ namespace osu.Game.Overlays.Mods updateValues(); }, true); - selectedItem?.BindValueChanged(_ => mods.TriggerChange()); + multiplayerRoomItem?.BindValueChanged(_ => mods.TriggerChange()); BeatmapInfo.BindValueChanged(_ => updateValues(), true); @@ -176,23 +181,26 @@ namespace osu.Game.Overlays.Mods foreach (var mod in mods.Value.OfType()) rate = mod.ApplyToRate(0, rate); - if (selectedItem != null && selectedItem.Value != null) - { - var globalMods = selectedItem.Value.RequiredMods.Select(m => m.ToMod(ruleset)); - - foreach (var mod in globalMods.OfType()) - rate = mod.ApplyToRate(0, rate); - } - - bpmDisplay.Current.Value = BeatmapInfo.Value.BPM * rate; - BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(BeatmapInfo.Value.Difficulty); foreach (var mod in mods.Value.OfType()) mod.ApplyToDifficulty(originalDifficulty); + if (AccountForMultiplayerMods && multiplayerRoomItem != null && multiplayerRoomItem.Value != null) + { + var multiplayerRoomMods = multiplayerRoomItem.Value.RequiredMods.Select(m => m.ToMod(ruleset)); + + foreach (var mod in multiplayerRoomMods.OfType()) + rate = mod.ApplyToRate(0, rate); + + foreach (var mod in multiplayerRoomMods.OfType()) + mod.ApplyToDifficulty(originalDifficulty); + } + BeatmapDifficulty adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(originalDifficulty, rate); + bpmDisplay.Current.Value = BeatmapInfo.Value.BPM * rate; + TooltipContent = new AdjustedAttributesTooltip.Data(originalDifficulty, adjustedDifficulty); approachRateDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(originalDifficulty.ApproachRate, adjustedDifficulty.ApproachRate); diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index ddf96c1cb3..0804624ed8 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -25,7 +25,9 @@ using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Localisation; +using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Online.Rooms; using osu.Game.Utils; using osuTK; using osuTK.Input; @@ -42,6 +44,12 @@ namespace osu.Game.Overlays.Mods [Cached] public Bindable> SelectedMods { get; private set; } = new Bindable>(Array.Empty()); + [Resolved(CanBeNull = true)] + private IBindable? multiplayerRoomItem { get; set; } + + [Resolved] + private OsuGameBase game { get; set; } = null!; + /// /// Contains a dictionary with the current of all mods applicable for the current ruleset. /// @@ -92,6 +100,11 @@ namespace osu.Game.Overlays.Mods /// protected virtual bool ShowPresets => false; + /// + /// Should overlay account for the multiplayer room global mods. + /// + public bool AccountForMultiplayerMods = false; + protected virtual ModColumn CreateModColumn(ModType modType) => new ModColumn(modType, false); protected virtual IReadOnlyList ComputeNewModsFromSelection(IReadOnlyList oldSelection, IReadOnlyList newSelection) => newSelection; @@ -278,7 +291,8 @@ namespace osu.Game.Overlays.Mods { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, - BeatmapInfo = { Value = beatmap?.BeatmapInfo } + BeatmapInfo = { Value = beatmap?.BeatmapInfo }, + AccountForMultiplayerMods = AccountForMultiplayerMods }, } }); @@ -332,6 +346,8 @@ namespace osu.Game.Overlays.Mods } }, true); + multiplayerRoomItem?.BindValueChanged(_ => SelectedMods.TriggerChange()); + customisationVisible.BindValueChanged(_ => updateCustomisationVisualState(), true); SearchTextBox.Current.BindValueChanged(query => @@ -460,8 +476,20 @@ namespace osu.Game.Overlays.Mods foreach (var mod in SelectedMods.Value) multiplier *= mod.ScoreMultiplier; - rankingInformationDisplay.ModMultiplier.Value = multiplier; rankingInformationDisplay.Ranked.Value = SelectedMods.Value.All(m => m.Ranked); + + if (AccountForMultiplayerMods && multiplayerRoomItem != null && multiplayerRoomItem.Value != null) + { + Ruleset ruleset = game.Ruleset.Value.CreateInstance(); + var multiplayerRoomMods = multiplayerRoomItem.Value.RequiredMods.Select(m => m.ToMod(ruleset)); + + foreach (var mod in multiplayerRoomMods) + multiplier *= mod.ScoreMultiplier; + + rankingInformationDisplay.Ranked.Value = rankingInformationDisplay.Ranked.Value && multiplayerRoomMods.All(m => m.Ranked); + } + + rankingInformationDisplay.ModMultiplier.Value = multiplier; } private void updateCustomisation() diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 4c0219eff5..71f8bfa0f4 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -244,7 +244,8 @@ namespace osu.Game.Screens.OnlinePlay.Match LoadComponent(UserModsSelectOverlay = new UserModSelectOverlay(OverlayColourScheme.Plum) { SelectedMods = { BindTarget = UserMods }, - IsValidMod = _ => false + IsValidMod = _ => false, + AccountForMultiplayerMods = true }); } From 414e55c90e33d492988914f992f0684284209145 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 18 Feb 2024 01:38:50 +0300 Subject: [PATCH 3113/4092] Add visual test case --- .../Visual/Online/TestSceneDrawableComment.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneDrawableComment.cs b/osu.Game.Tests/Visual/Online/TestSceneDrawableComment.cs index 6f09e4c1f6..5cdf79160c 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDrawableComment.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDrawableComment.cs @@ -81,16 +81,17 @@ namespace osu.Game.Tests.Visual.Online }, // Taken from https://github.com/ppy/osu/issues/13993#issuecomment-885994077 + new[] { "Problematic", @"My tablet doesn't work :( It's a Huion 420 and it's apparently incompatible with OpenTablet Driver. The warning I get is: ""DeviceInUseException: Device is currently in use by another kernel module. To fix this issue, please follow the instructions from https://github.com/OpenTabletDriver/OpenTabletDriver/wiki/Linux-FAQ#arg umentoutofrangeexception-value-0-15"" and it repeats 4 times on the notification before logging subsequent warnings. Checking the logs, it looks for other Huion tablets before sending the notification (e.g. ""2021-07-23 03:52:33 [verbose]: Detect: Searching for tablet 'Huion WH1409 V2' 20 2021-07-23 03:52:33 [error]: DeviceInUseException: Device is currently in use by another kernel module. To fix this issue, please follow the instructions from https://github.com/OpenTabletDriver/OpenTabletDriver/wiki/Linux-FAQ#arg umentoutofrangeexception-value-0-15"") I use an Arch based installation of Linux and the tablet runs perfectly with Digimend kernel driver, with area configuration, pen pressure, etc. On osu!lazer the cursor disappears until I set it to ""Borderless"" instead of ""Fullscreen"" and even after it shows up, it goes to the bottom left corner as soon as a map starts. I have honestly 0 idea of whats going on at this point.", }, new[] { - "Problematic", @"My tablet doesn't work :( -It's a Huion 420 and it's apparently incompatible with OpenTablet Driver. The warning I get is: ""DeviceInUseException: Device is currently in use by another kernel module. To fix this issue, please follow the instructions from https://github.com/OpenTabletDriver/OpenTabletDriver/wiki/Linux-FAQ#arg umentoutofrangeexception-value-0-15"" and it repeats 4 times on the notification before logging subsequent warnings. -Checking the logs, it looks for other Huion tablets before sending the notification (e.g. - ""2021-07-23 03:52:33 [verbose]: Detect: Searching for tablet 'Huion WH1409 V2' - 20 2021-07-23 03:52:33 [error]: DeviceInUseException: Device is currently in use by another kernel module. To fix this issue, please follow the instructions from https://github.com/OpenTabletDriver/OpenTabletDriver/wiki/Linux-FAQ#arg umentoutofrangeexception-value-0-15"") -I use an Arch based installation of Linux and the tablet runs perfectly with Digimend kernel driver, with area configuration, pen pressure, etc. On osu!lazer the cursor disappears until I set it to ""Borderless"" instead of ""Fullscreen"" and even after it shows up, it goes to the bottom left corner as soon as a map starts. -I have honestly 0 idea of whats going on at this point." - } + "Code Block", @"User not found! ;_; + +There are a few possible reasons for this: + + They may have changed their username. + The account may be temporarily unavailable due to security or abuse issues. + You may have made a typo!" + }, }; } } From 91675e097033c249cf7b6947e1023ee2c719ef5f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 18 Feb 2024 02:00:55 +0300 Subject: [PATCH 3114/4092] Update markdown code block implementation in line with framework changes --- ...suMarkdownFencedCodeBlock.cs => OsuMarkdownCodeBlock.cs} | 6 +++--- .../Graphics/Containers/Markdown/OsuMarkdownContainer.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game/Graphics/Containers/Markdown/{OsuMarkdownFencedCodeBlock.cs => OsuMarkdownCodeBlock.cs} (87%) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownFencedCodeBlock.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownCodeBlock.cs similarity index 87% rename from osu.Game/Graphics/Containers/Markdown/OsuMarkdownFencedCodeBlock.cs rename to osu.Game/Graphics/Containers/Markdown/OsuMarkdownCodeBlock.cs index 7d84d368ad..27802f4c0e 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownFencedCodeBlock.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownCodeBlock.cs @@ -10,11 +10,11 @@ using osu.Game.Overlays; namespace osu.Game.Graphics.Containers.Markdown { - public partial class OsuMarkdownFencedCodeBlock : MarkdownFencedCodeBlock + public partial class OsuMarkdownCodeBlock : MarkdownCodeBlock { // TODO : change to monospace font for this component - public OsuMarkdownFencedCodeBlock(FencedCodeBlock fencedCodeBlock) - : base(fencedCodeBlock) + public OsuMarkdownCodeBlock(CodeBlock codeBlock) + : base(codeBlock) { } diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs index b4031752db..d465e53432 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs @@ -67,7 +67,7 @@ namespace osu.Game.Graphics.Containers.Markdown protected override MarkdownHeading CreateHeading(HeadingBlock headingBlock) => new OsuMarkdownHeading(headingBlock); - protected override MarkdownFencedCodeBlock CreateFencedCodeBlock(FencedCodeBlock fencedCodeBlock) => new OsuMarkdownFencedCodeBlock(fencedCodeBlock); + protected override MarkdownCodeBlock CreateCodeBlock(CodeBlock codeBlock) => new OsuMarkdownCodeBlock(codeBlock); protected override MarkdownSeparator CreateSeparator(ThematicBreakBlock thematicBlock) => new OsuMarkdownSeparator(); From 2df5787dc7e46ea573bbbd4c1608cf18564029d0 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 18 Feb 2024 03:13:57 +0200 Subject: [PATCH 3115/4092] Packed changes into separate class --- .../Overlays/Mods/BeatmapAttributesDisplay.cs | 58 ++++----- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 66 +++------- .../Mods/MultiplayerModSelectOverlay.cs | 118 ++++++++++++++++++ .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 5 +- 4 files changed, 166 insertions(+), 81 deletions(-) create mode 100644 osu.Game/Overlays/Mods/MultiplayerModSelectOverlay.cs diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index da14382175..93e7e0ae9c 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -46,10 +46,7 @@ namespace osu.Game.Overlays.Mods public bool AccountForMultiplayerMods = false; [Resolved] - private Bindable> mods { get; set; } = null!; - - [Resolved(CanBeNull = true)] - private IBindable? multiplayerRoomItem { get; set; } + protected Bindable> Mods { get; private set; } = null!; public BindableBool Collapsed { get; } = new BindableBool(true); @@ -61,7 +58,7 @@ namespace osu.Game.Overlays.Mods [Resolved] private OsuGameBase game { get; set; } = null!; - private IBindable gameRuleset = null!; + protected IBindable GameRuleset = null!; private CancellationTokenSource? cancellationSource; private IBindable starDifficulty = null!; @@ -109,16 +106,14 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); - mods.BindValueChanged(_ => + Mods.BindValueChanged(_ => { modSettingChangeTracker?.Dispose(); - modSettingChangeTracker = new ModSettingChangeTracker(mods.Value); + modSettingChangeTracker = new ModSettingChangeTracker(Mods.Value); modSettingChangeTracker.SettingChanged += _ => updateValues(); updateValues(); }, true); - multiplayerRoomItem?.BindValueChanged(_ => mods.TriggerChange()); - BeatmapInfo.BindValueChanged(_ => updateValues(), true); Collapsed.BindValueChanged(_ => @@ -128,8 +123,8 @@ namespace osu.Game.Overlays.Mods updateCollapsedState(); }); - gameRuleset = game.Ruleset.GetBoundCopy(); - gameRuleset.BindValueChanged(_ => updateValues()); + GameRuleset = game.Ruleset.GetBoundCopy(); + GameRuleset.BindValueChanged(_ => updateValues()); BeatmapInfo.BindValueChanged(_ => updateValues(), true); @@ -159,6 +154,23 @@ namespace osu.Game.Overlays.Mods LeftContent.AutoSizeDuration = Content.AutoSizeDuration = transition_duration; } + protected virtual double GetRate() + { + double rate = 1; + foreach (var mod in Mods.Value.OfType()) + rate = mod.ApplyToRate(0, rate); + return rate; + } + + protected virtual BeatmapDifficulty GetDifficulty() + { + BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(BeatmapInfo.Value!.Difficulty); + + foreach (var mod in Mods.Value.OfType()) + mod.ApplyToDifficulty(originalDifficulty); + + return originalDifficulty; + } private void updateValues() => Scheduler.AddOnce(() => { if (BeatmapInfo.Value == null) @@ -175,28 +187,10 @@ namespace osu.Game.Overlays.Mods starRatingDisplay.FinishTransforms(true); }); - Ruleset ruleset = gameRuleset.Value.CreateInstance(); - - double rate = 1; - foreach (var mod in mods.Value.OfType()) - rate = mod.ApplyToRate(0, rate); - - BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(BeatmapInfo.Value.Difficulty); - - foreach (var mod in mods.Value.OfType()) - mod.ApplyToDifficulty(originalDifficulty); - - if (AccountForMultiplayerMods && multiplayerRoomItem != null && multiplayerRoomItem.Value != null) - { - var multiplayerRoomMods = multiplayerRoomItem.Value.RequiredMods.Select(m => m.ToMod(ruleset)); - - foreach (var mod in multiplayerRoomMods.OfType()) - rate = mod.ApplyToRate(0, rate); - - foreach (var mod in multiplayerRoomMods.OfType()) - mod.ApplyToDifficulty(originalDifficulty); - } + double rate = GetRate(); + BeatmapDifficulty originalDifficulty = GetDifficulty(); + Ruleset ruleset = GameRuleset.Value.CreateInstance(); BeatmapDifficulty adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(originalDifficulty, rate); bpmDisplay.Current.Value = BeatmapInfo.Value.BPM * rate; diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 0804624ed8..12719475a8 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -25,9 +25,7 @@ using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Localisation; -using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Online.Rooms; using osu.Game.Utils; using osuTK; using osuTK.Input; @@ -44,11 +42,6 @@ namespace osu.Game.Overlays.Mods [Cached] public Bindable> SelectedMods { get; private set; } = new Bindable>(Array.Empty()); - [Resolved(CanBeNull = true)] - private IBindable? multiplayerRoomItem { get; set; } - - [Resolved] - private OsuGameBase game { get; set; } = null!; /// /// Contains a dictionary with the current of all mods applicable for the current ruleset. @@ -100,11 +93,6 @@ namespace osu.Game.Overlays.Mods /// protected virtual bool ShowPresets => false; - /// - /// Should overlay account for the multiplayer room global mods. - /// - public bool AccountForMultiplayerMods = false; - protected virtual ModColumn CreateModColumn(ModType modType) => new ModColumn(modType, false); protected virtual IReadOnlyList ComputeNewModsFromSelection(IReadOnlyList oldSelection, IReadOnlyList newSelection) => newSelection; @@ -138,8 +126,14 @@ namespace osu.Game.Overlays.Mods private DeselectAllModsButton deselectAllModsButton = null!; private Container aboveColumnsContent = null!; - private RankingInformationDisplay? rankingInformationDisplay; - private BeatmapAttributesDisplay? beatmapAttributesDisplay; + protected RankingInformationDisplay? RankingInformationDisplay; + protected BeatmapAttributesDisplay? BeatmapAttributesDisplay; + protected virtual BeatmapAttributesDisplay GetBeatmapAttributesDisplay => new BeatmapAttributesDisplay + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + BeatmapInfo = { Value = Beatmap?.BeatmapInfo } + }; protected ShearedButton BackButton { get; private set; } = null!; protected ShearedToggleButton? CustomisationButton { get; private set; } @@ -159,8 +153,8 @@ namespace osu.Game.Overlays.Mods if (beatmap == value) return; beatmap = value; - if (IsLoaded && beatmapAttributesDisplay != null) - beatmapAttributesDisplay.BeatmapInfo.Value = beatmap?.BeatmapInfo; + if (IsLoaded && BeatmapAttributesDisplay != null) + BeatmapAttributesDisplay.BeatmapInfo.Value = beatmap?.BeatmapInfo; } } @@ -282,18 +276,12 @@ namespace osu.Game.Overlays.Mods }, Children = new Drawable[] { - rankingInformationDisplay = new RankingInformationDisplay + RankingInformationDisplay = new RankingInformationDisplay { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight }, - beatmapAttributesDisplay = new BeatmapAttributesDisplay - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - BeatmapInfo = { Value = beatmap?.BeatmapInfo }, - AccountForMultiplayerMods = AccountForMultiplayerMods - }, + BeatmapAttributesDisplay = GetBeatmapAttributesDisplay } }); } @@ -329,7 +317,7 @@ namespace osu.Game.Overlays.Mods SelectedMods.BindValueChanged(_ => { - updateRankingInformation(); + UpdateRankingInformation(); updateFromExternalSelection(); updateCustomisation(); @@ -342,12 +330,10 @@ namespace osu.Game.Overlays.Mods // // See https://github.com/ppy/osu/pull/23284#issuecomment-1529056988 modSettingChangeTracker = new ModSettingChangeTracker(SelectedMods.Value); - modSettingChangeTracker.SettingChanged += _ => updateRankingInformation(); + modSettingChangeTracker.SettingChanged += _ => UpdateRankingInformation(); } }, true); - multiplayerRoomItem?.BindValueChanged(_ => SelectedMods.TriggerChange()); - customisationVisible.BindValueChanged(_ => updateCustomisationVisualState(), true); SearchTextBox.Current.BindValueChanged(query => @@ -371,7 +357,7 @@ namespace osu.Game.Overlays.Mods SearchTextBox.PlaceholderText = SearchTextBox.HasFocus ? Resources.Localisation.Web.CommonStrings.InputSearch : ModSelectOverlayStrings.TabToSearch; - if (beatmapAttributesDisplay != null) + if (BeatmapAttributesDisplay != null) { float rightEdgeOfLastButton = footerButtonFlow.Last().ScreenSpaceDrawQuad.TopRight.X; @@ -383,7 +369,7 @@ namespace osu.Game.Overlays.Mods // only update preview panel's collapsed state after we are fully visible, to ensure all the buttons are where we expect them to be. if (Alpha == 1) - beatmapAttributesDisplay.Collapsed.Value = screenIsntWideEnough; + BeatmapAttributesDisplay.Collapsed.Value = screenIsntWideEnough; footerContentFlow.LayoutDuration = 200; footerContentFlow.LayoutEasing = Easing.OutQuint; @@ -466,9 +452,9 @@ namespace osu.Game.Overlays.Mods modState.ValidForSelection.Value = modState.Mod.Type != ModType.System && modState.Mod.HasImplementation && IsValidMod.Invoke(modState.Mod); } - private void updateRankingInformation() + protected virtual void UpdateRankingInformation() { - if (rankingInformationDisplay == null) + if (RankingInformationDisplay == null) return; double multiplier = 1.0; @@ -476,20 +462,8 @@ namespace osu.Game.Overlays.Mods foreach (var mod in SelectedMods.Value) multiplier *= mod.ScoreMultiplier; - rankingInformationDisplay.Ranked.Value = SelectedMods.Value.All(m => m.Ranked); - - if (AccountForMultiplayerMods && multiplayerRoomItem != null && multiplayerRoomItem.Value != null) - { - Ruleset ruleset = game.Ruleset.Value.CreateInstance(); - var multiplayerRoomMods = multiplayerRoomItem.Value.RequiredMods.Select(m => m.ToMod(ruleset)); - - foreach (var mod in multiplayerRoomMods) - multiplier *= mod.ScoreMultiplier; - - rankingInformationDisplay.Ranked.Value = rankingInformationDisplay.Ranked.Value && multiplayerRoomMods.All(m => m.Ranked); - } - - rankingInformationDisplay.ModMultiplier.Value = multiplier; + RankingInformationDisplay.Ranked.Value = SelectedMods.Value.All(m => m.Ranked); + RankingInformationDisplay.ModMultiplier.Value = multiplier; } private void updateCustomisation() diff --git a/osu.Game/Overlays/Mods/MultiplayerModSelectOverlay.cs b/osu.Game/Overlays/Mods/MultiplayerModSelectOverlay.cs new file mode 100644 index 0000000000..ca1df31271 --- /dev/null +++ b/osu.Game/Overlays/Mods/MultiplayerModSelectOverlay.cs @@ -0,0 +1,118 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Rulesets; +using osu.Game.Online.Rooms; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Overlays.Mods +{ + public partial class MultiplayerModSelectOverlay : UserModSelectOverlay + { + public MultiplayerModSelectOverlay(OverlayColourScheme colourScheme = OverlayColourScheme.Plum) + : base(colourScheme) + { + } + + [Resolved(CanBeNull = true)] + private IBindable? multiplayerRoomItem { get; set; } + + [Resolved] + private OsuGameBase game { get; set; } = null!; + + protected override BeatmapAttributesDisplay GetBeatmapAttributesDisplay => new MultiplayerBeatmapAttributesDisplay + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + BeatmapInfo = { Value = Beatmap?.BeatmapInfo }, + AccountForMultiplayerMods = true + }; + + protected override void LoadComplete() + { + base.LoadComplete(); + + multiplayerRoomItem?.BindValueChanged(_ => SelectedMods.TriggerChange()); + } + + protected override void UpdateRankingInformation() + { + if (RankingInformationDisplay == null) + return; + + double multiplier = 1.0; + + foreach (var mod in SelectedMods.Value) + multiplier *= mod.ScoreMultiplier; + + RankingInformationDisplay.Ranked.Value = SelectedMods.Value.All(m => m.Ranked); + + if (multiplayerRoomItem != null && multiplayerRoomItem.Value != null) + { + Ruleset ruleset = game.Ruleset.Value.CreateInstance(); + var multiplayerRoomMods = multiplayerRoomItem.Value.RequiredMods.Select(m => m.ToMod(ruleset)); + + foreach (var mod in multiplayerRoomMods) + multiplier *= mod.ScoreMultiplier; + + RankingInformationDisplay.Ranked.Value = RankingInformationDisplay.Ranked.Value && multiplayerRoomMods.All(m => m.Ranked); + } + + RankingInformationDisplay.ModMultiplier.Value = multiplier; + } + } + + public partial class MultiplayerBeatmapAttributesDisplay : BeatmapAttributesDisplay + { + public MultiplayerBeatmapAttributesDisplay() + : base() + { + } + + [Resolved(CanBeNull = true)] + private IBindable? multiplayerRoomItem { get; set; } + + protected override void LoadComplete() + { + base.LoadComplete(); + multiplayerRoomItem?.BindValueChanged(_ => Mods.TriggerChange()); + } + + protected override double GetRate() + { + double rate = base.GetRate(); + Ruleset ruleset = GameRuleset.Value.CreateInstance(); + + if (AccountForMultiplayerMods && multiplayerRoomItem != null && multiplayerRoomItem.Value != null) + { + var multiplayerRoomMods = multiplayerRoomItem.Value.RequiredMods.Select(m => m.ToMod(ruleset)); + + foreach (var mod in multiplayerRoomMods.OfType()) + rate = mod.ApplyToRate(0, rate); + } + + return rate; + } + + protected override BeatmapDifficulty GetDifficulty() + { + BeatmapDifficulty originalDifficulty = base.GetDifficulty(); + Ruleset ruleset = GameRuleset.Value.CreateInstance(); + + if (multiplayerRoomItem != null && multiplayerRoomItem.Value != null) + { + var multiplayerRoomMods = multiplayerRoomItem.Value.RequiredMods.Select(m => m.ToMod(ruleset)); + + foreach (var mod in multiplayerRoomMods.OfType()) + mod.ApplyToDifficulty(originalDifficulty); + } + + return originalDifficulty; + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 71f8bfa0f4..fbc06cce9e 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -241,11 +241,10 @@ namespace osu.Game.Screens.OnlinePlay.Match } }; - LoadComponent(UserModsSelectOverlay = new UserModSelectOverlay(OverlayColourScheme.Plum) + LoadComponent(UserModsSelectOverlay = new MultiplayerModSelectOverlay() { SelectedMods = { BindTarget = UserMods }, - IsValidMod = _ => false, - AccountForMultiplayerMods = true + IsValidMod = _ => false }); } From 6fb3192648f723af818322021ac5cd5cc5743ad7 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 18 Feb 2024 03:14:36 +0200 Subject: [PATCH 3116/4092] Update BeatmapAttributesDisplay.cs --- osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index 93e7e0ae9c..849444fa32 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -40,11 +40,6 @@ namespace osu.Game.Overlays.Mods public Bindable BeatmapInfo { get; } = new Bindable(); - /// - /// Should attribute display account for the multiplayer room global mods. - /// - public bool AccountForMultiplayerMods = false; - [Resolved] protected Bindable> Mods { get; private set; } = null!; From 4aaf016ee0ad4ff8eaebbbd207d7617e3d60a14b Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 18 Feb 2024 03:15:53 +0200 Subject: [PATCH 3117/4092] quality improvements --- osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs | 2 -- osu.Game/Overlays/Mods/MultiplayerModSelectOverlay.cs | 11 ++--------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index 849444fa32..7517a502c7 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -160,10 +160,8 @@ namespace osu.Game.Overlays.Mods protected virtual BeatmapDifficulty GetDifficulty() { BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(BeatmapInfo.Value!.Difficulty); - foreach (var mod in Mods.Value.OfType()) mod.ApplyToDifficulty(originalDifficulty); - return originalDifficulty; } private void updateValues() => Scheduler.AddOnce(() => diff --git a/osu.Game/Overlays/Mods/MultiplayerModSelectOverlay.cs b/osu.Game/Overlays/Mods/MultiplayerModSelectOverlay.cs index ca1df31271..ab20e9dc89 100644 --- a/osu.Game/Overlays/Mods/MultiplayerModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/MultiplayerModSelectOverlay.cs @@ -29,8 +29,7 @@ namespace osu.Game.Overlays.Mods { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, - BeatmapInfo = { Value = Beatmap?.BeatmapInfo }, - AccountForMultiplayerMods = true + BeatmapInfo = { Value = Beatmap?.BeatmapInfo } }; protected override void LoadComplete() @@ -87,15 +86,12 @@ namespace osu.Game.Overlays.Mods { double rate = base.GetRate(); Ruleset ruleset = GameRuleset.Value.CreateInstance(); - - if (AccountForMultiplayerMods && multiplayerRoomItem != null && multiplayerRoomItem.Value != null) + if (multiplayerRoomItem != null && multiplayerRoomItem.Value != null) { var multiplayerRoomMods = multiplayerRoomItem.Value.RequiredMods.Select(m => m.ToMod(ruleset)); - foreach (var mod in multiplayerRoomMods.OfType()) rate = mod.ApplyToRate(0, rate); } - return rate; } @@ -103,15 +99,12 @@ namespace osu.Game.Overlays.Mods { BeatmapDifficulty originalDifficulty = base.GetDifficulty(); Ruleset ruleset = GameRuleset.Value.CreateInstance(); - if (multiplayerRoomItem != null && multiplayerRoomItem.Value != null) { var multiplayerRoomMods = multiplayerRoomItem.Value.RequiredMods.Select(m => m.ToMod(ruleset)); - foreach (var mod in multiplayerRoomMods.OfType()) mod.ApplyToDifficulty(originalDifficulty); } - return originalDifficulty; } } From 9070e973d3bf7a54dae89a8acd14acf16cdfe69c Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 18 Feb 2024 03:16:18 +0200 Subject: [PATCH 3118/4092] Update BeatmapAttributesDisplay.cs --- osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index 7517a502c7..08e124d8ac 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -19,7 +19,6 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Online.Rooms; using osuTK; namespace osu.Game.Overlays.Mods From a6b63efe7d695354e4ddddb6a7143667fe56e646 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 18 Feb 2024 03:28:24 +0200 Subject: [PATCH 3119/4092] Fixed NVika code quality errors --- osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs | 1 + osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 +- osu.Game/Overlays/Mods/MultiplayerModSelectOverlay.cs | 8 ++++++-- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index 08e124d8ac..f010725c8b 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -163,6 +163,7 @@ namespace osu.Game.Overlays.Mods mod.ApplyToDifficulty(originalDifficulty); return originalDifficulty; } + private void updateValues() => Scheduler.AddOnce(() => { if (BeatmapInfo.Value == null) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 12719475a8..e29e685ece 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -42,7 +42,6 @@ namespace osu.Game.Overlays.Mods [Cached] public Bindable> SelectedMods { get; private set; } = new Bindable>(Array.Empty()); - /// /// Contains a dictionary with the current of all mods applicable for the current ruleset. /// @@ -128,6 +127,7 @@ namespace osu.Game.Overlays.Mods private Container aboveColumnsContent = null!; protected RankingInformationDisplay? RankingInformationDisplay; protected BeatmapAttributesDisplay? BeatmapAttributesDisplay; + protected virtual BeatmapAttributesDisplay GetBeatmapAttributesDisplay => new BeatmapAttributesDisplay { Anchor = Anchor.BottomRight, diff --git a/osu.Game/Overlays/Mods/MultiplayerModSelectOverlay.cs b/osu.Game/Overlays/Mods/MultiplayerModSelectOverlay.cs index ab20e9dc89..b399b6b878 100644 --- a/osu.Game/Overlays/Mods/MultiplayerModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/MultiplayerModSelectOverlay.cs @@ -86,12 +86,14 @@ namespace osu.Game.Overlays.Mods { double rate = base.GetRate(); Ruleset ruleset = GameRuleset.Value.CreateInstance(); - if (multiplayerRoomItem != null && multiplayerRoomItem.Value != null) + + if (multiplayerRoomItem?.Value != null) { var multiplayerRoomMods = multiplayerRoomItem.Value.RequiredMods.Select(m => m.ToMod(ruleset)); foreach (var mod in multiplayerRoomMods.OfType()) rate = mod.ApplyToRate(0, rate); } + return rate; } @@ -99,12 +101,14 @@ namespace osu.Game.Overlays.Mods { BeatmapDifficulty originalDifficulty = base.GetDifficulty(); Ruleset ruleset = GameRuleset.Value.CreateInstance(); - if (multiplayerRoomItem != null && multiplayerRoomItem.Value != null) + + if (multiplayerRoomItem?.Value != null) { var multiplayerRoomMods = multiplayerRoomItem.Value.RequiredMods.Select(m => m.ToMod(ruleset)); foreach (var mod in multiplayerRoomMods.OfType()) mod.ApplyToDifficulty(originalDifficulty); } + return originalDifficulty; } } diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index fbc06cce9e..b20760b114 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -241,7 +241,7 @@ namespace osu.Game.Screens.OnlinePlay.Match } }; - LoadComponent(UserModsSelectOverlay = new MultiplayerModSelectOverlay() + LoadComponent(UserModsSelectOverlay = new MultiplayerModSelectOverlay { SelectedMods = { BindTarget = UserMods }, IsValidMod = _ => false From 24171bd02bf608fdee7b3c39dfecfba11968647e Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 18 Feb 2024 03:34:02 +0200 Subject: [PATCH 3120/4092] Update MultiplayerModSelectOverlay.cs --- osu.Game/Overlays/Mods/MultiplayerModSelectOverlay.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Mods/MultiplayerModSelectOverlay.cs b/osu.Game/Overlays/Mods/MultiplayerModSelectOverlay.cs index b399b6b878..78978e25e7 100644 --- a/osu.Game/Overlays/Mods/MultiplayerModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/MultiplayerModSelectOverlay.cs @@ -51,7 +51,7 @@ namespace osu.Game.Overlays.Mods RankingInformationDisplay.Ranked.Value = SelectedMods.Value.All(m => m.Ranked); - if (multiplayerRoomItem != null && multiplayerRoomItem.Value != null) + if (multiplayerRoomItem?.Value != null) { Ruleset ruleset = game.Ruleset.Value.CreateInstance(); var multiplayerRoomMods = multiplayerRoomItem.Value.RequiredMods.Select(m => m.ToMod(ruleset)); @@ -68,11 +68,6 @@ namespace osu.Game.Overlays.Mods public partial class MultiplayerBeatmapAttributesDisplay : BeatmapAttributesDisplay { - public MultiplayerBeatmapAttributesDisplay() - : base() - { - } - [Resolved(CanBeNull = true)] private IBindable? multiplayerRoomItem { get; set; } From 9655e8c48af03283ee323ee0d35fd6f354454119 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 18 Feb 2024 17:54:29 +0800 Subject: [PATCH 3121/4092] Adjust xmldoc slightly --- osu.Game/Rulesets/Objects/HitObject.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 317dd35fef..04bdc35941 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -171,11 +171,10 @@ namespace osu.Game.Rulesets.Objects private Judgement judgement; /// - /// Creates the that represents the scoring information for this . + /// Should be overridden to create a that represents the scoring information for this . /// /// - /// Use to avoid unnecessary allocations. - /// This method has been left public for compatibility reasons and eventually will be made protected. + /// For read access, use to avoid unnecessary allocations. /// [NotNull] public virtual Judgement CreateJudgement() => new Judgement(); From 415a65bf59cdce4b8fab7d836fe3cdc4f7a2a168 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 17 Feb 2024 19:00:30 +0800 Subject: [PATCH 3122/4092] Add failing tests for beatmap inconsistencies --- .../Navigation/TestSceneScreenNavigation.cs | 63 +++++++++++++++++++ osu.Game/Tests/Visual/OsuGameTestScene.cs | 4 +- 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 8ff4fd5ecf..7e42d4781d 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using System.IO; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -18,6 +19,7 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Collections; using osu.Game.Configuration; +using osu.Game.Extensions; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; @@ -221,6 +223,67 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible); } + [Test] + public void TestAttemptPlayBeatmapWrongHashFails() + { + Screens.Select.SongSelect songSelect = null; + + AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).GetResultSafely()); + PushAndConfirm(() => songSelect = new TestPlaySongSelect()); + AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); + + AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + + AddStep("change beatmap files", () => + { + foreach (var file in Game.Beatmap.Value.BeatmapSetInfo.Files.Where(f => Path.GetExtension(f.Filename) == ".osu")) + { + using (var stream = Game.Storage.GetStream(Path.Combine("files", file.File.GetStoragePath()), FileAccess.ReadWrite)) + stream.WriteByte(0); + } + }); + + AddStep("invalidate cache", () => + { + ((IWorkingBeatmapCache)Game.BeatmapManager).Invalidate(Game.Beatmap.Value.BeatmapSetInfo); + }); + + AddStep("select next difficulty", () => InputManager.Key(Key.Down)); + AddStep("press enter", () => InputManager.Key(Key.Enter)); + + AddUntilStep("wait for player loader", () => Game.ScreenStack.CurrentScreen is PlayerLoader); + AddUntilStep("wait for song select", () => songSelect.IsCurrentScreen()); + } + + [Test] + public void TestAttemptPlayBeatmapMissingFails() + { + Screens.Select.SongSelect songSelect = null; + + AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).GetResultSafely()); + PushAndConfirm(() => songSelect = new TestPlaySongSelect()); + AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); + + AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + + AddStep("delete beatmap files", () => + { + foreach (var file in Game.Beatmap.Value.BeatmapSetInfo.Files.Where(f => Path.GetExtension(f.Filename) == ".osu")) + Game.Storage.Delete(Path.Combine("files", file.File.GetStoragePath())); + }); + + AddStep("invalidate cache", () => + { + ((IWorkingBeatmapCache)Game.BeatmapManager).Invalidate(Game.Beatmap.Value.BeatmapSetInfo); + }); + + AddStep("select next difficulty", () => InputManager.Key(Key.Down)); + AddStep("press enter", () => InputManager.Key(Key.Enter)); + + AddUntilStep("wait for player loader", () => Game.ScreenStack.CurrentScreen is PlayerLoader); + AddUntilStep("wait for song select", () => songSelect.IsCurrentScreen()); + } + [Test] public void TestRetryCountIncrements() { diff --git a/osu.Game/Tests/Visual/OsuGameTestScene.cs b/osu.Game/Tests/Visual/OsuGameTestScene.cs index 6069fe4fb0..b86273b4a3 100644 --- a/osu.Game/Tests/Visual/OsuGameTestScene.cs +++ b/osu.Game/Tests/Visual/OsuGameTestScene.cs @@ -153,6 +153,8 @@ namespace osu.Game.Tests.Visual public new Bindable> SelectedMods => base.SelectedMods; + public new Storage Storage => base.Storage; + public new SpectatorClient SpectatorClient => base.SpectatorClient; // if we don't apply these changes, when running under nUnit the version that gets populated is that of nUnit. @@ -166,7 +168,7 @@ namespace osu.Game.Tests.Visual public TestOsuGame(Storage storage, IAPIProvider api, string[] args = null) : base(args) { - Storage = storage; + base.Storage = storage; API = api; } From 882f11bf79d0ce405fb865cd0a7a1d552f540513 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 18 Feb 2024 23:19:57 +0800 Subject: [PATCH 3123/4092] Fix logo tracking container being off by one frame This was especially visible at the main menu when running in single thread mode. --- osu.Game/Graphics/Containers/LogoTrackingContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs index 08eae25951..57f87b588a 100644 --- a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs +++ b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs @@ -82,9 +82,9 @@ namespace osu.Game.Graphics.Containers absolutePos.Y / Logo.Parent!.RelativeToAbsoluteFactor.Y); } - protected override void Update() + protected override void UpdateAfterChildren() { - base.Update(); + base.UpdateAfterChildren(); if (Logo == null) return; From 6b6a6aea54fadf4dd5b811629fd075fe1b7f5c34 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 18 Feb 2024 23:35:26 +0800 Subject: [PATCH 3124/4092] Apply NRT to `LogoTrackingContainer` --- .../Visual/UserInterface/TestSceneLogoTrackingContainer.cs | 2 +- osu.Game/Graphics/Containers/LogoTrackingContainer.cs | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLogoTrackingContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLogoTrackingContainer.cs index 57ea4ee58e..8d5c961265 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLogoTrackingContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLogoTrackingContainer.cs @@ -282,7 +282,7 @@ namespace osu.Game.Tests.Visual.UserInterface /// /// Check that the logo is tracking the position of the facade, with an acceptable precision lenience. /// - public bool IsLogoTracking => Precision.AlmostEquals(Logo.Position, ComputeLogoTrackingPosition()); + public bool IsLogoTracking => Precision.AlmostEquals(Logo!.Position, ComputeLogoTrackingPosition()); } } } diff --git a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs index 57f87b588a..13c672cbd6 100644 --- a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs +++ b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -19,7 +17,7 @@ namespace osu.Game.Graphics.Containers { public Facade LogoFacade => facade; - protected OsuLogo Logo { get; private set; } + protected OsuLogo? Logo { get; private set; } private readonly InternalFacade facade = new InternalFacade(); @@ -76,7 +74,7 @@ namespace osu.Game.Graphics.Containers /// Will only be correct if the logo's are set to Axes.Both protected Vector2 ComputeLogoTrackingPosition() { - var absolutePos = Logo.Parent!.ToLocalSpace(LogoFacade.ScreenSpaceDrawQuad.Centre); + var absolutePos = Logo!.Parent!.ToLocalSpace(LogoFacade.ScreenSpaceDrawQuad.Centre); return new Vector2(absolutePos.X / Logo.Parent!.RelativeToAbsoluteFactor.X, absolutePos.Y / Logo.Parent!.RelativeToAbsoluteFactor.Y); From 998d8206668ba1733d7f7d6ff1afacde219f8a3a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Feb 2024 00:21:54 +0800 Subject: [PATCH 3125/4092] Ensure audio filters can't be attached before load (or post-disposal) Will probably fix https://github.com/ppy/osu/issues/27225? --- osu.Game/Audio/Effects/AudioFilter.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game/Audio/Effects/AudioFilter.cs b/osu.Game/Audio/Effects/AudioFilter.cs index 682ca4ca7b..c8673372d7 100644 --- a/osu.Game/Audio/Effects/AudioFilter.cs +++ b/osu.Game/Audio/Effects/AudioFilter.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using ManagedBass.Fx; using osu.Framework.Audio.Mixing; +using osu.Framework.Caching; using osu.Framework.Graphics; namespace osu.Game.Audio.Effects @@ -22,6 +23,8 @@ namespace osu.Game.Audio.Effects private bool isAttached; + private readonly Cached filterApplication = new Cached(); + private int cutoff; /// @@ -36,7 +39,7 @@ namespace osu.Game.Audio.Effects return; cutoff = value; - updateFilter(cutoff); + filterApplication.Invalidate(); } } @@ -61,6 +64,17 @@ namespace osu.Game.Audio.Effects Cutoff = getInitialCutoff(type); } + protected override void Update() + { + base.Update(); + + if (!filterApplication.IsValid) + { + updateFilter(cutoff); + filterApplication.Validate(); + } + } + private int getInitialCutoff(BQFType type) { switch (type) From 3059ddf3b2638a0c6c3c1520fd1e93b32c1dc24d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 19 Feb 2024 01:08:40 +0300 Subject: [PATCH 3126/4092] Fix allocations in SliderInputManager --- osu.Game.Rulesets.Osu/OsuInputManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs index e472de1dfe..ceac1989a6 100644 --- a/osu.Game.Rulesets.Osu/OsuInputManager.cs +++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs @@ -1,13 +1,13 @@ // 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.ComponentModel; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Lists; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.UI; @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu { public partial class OsuInputManager : RulesetInputManager { - public IEnumerable PressedActions => KeyBindingContainer.PressedActions; + public SlimReadOnlyListWrapper PressedActions => KeyBindingContainer.PressedActions; /// /// Whether gameplay input buttons should be allowed. From 5a448ce02f5c65a40a57b4a3f51641d907a92de9 Mon Sep 17 00:00:00 2001 From: maromalo Date: Sun, 18 Feb 2024 19:59:56 -0300 Subject: [PATCH 3127/4092] Turn BPMDisplay to RollingCounter --- osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index b9e4896b21..1db02b7cf2 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.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 System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -168,7 +169,7 @@ namespace osu.Game.Overlays.Mods foreach (var mod in mods.Value.OfType()) rate = mod.ApplyToRate(0, rate); - bpmDisplay.Current.Value = BeatmapInfo.Value.BPM * rate; + bpmDisplay.Current.Value = (int)Math.Round(Math.Round(BeatmapInfo.Value.BPM) * rate); BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(BeatmapInfo.Value.Difficulty); @@ -194,11 +195,11 @@ namespace osu.Game.Overlays.Mods RightContent.FadeTo(Collapsed.Value && !IsHovered ? 0 : 1, transition_duration, Easing.OutQuint); } - private partial class BPMDisplay : RollingCounter + private partial class BPMDisplay : RollingCounter { protected override double RollingDuration => 250; - protected override LocalisableString FormatCount(double count) => count.ToLocalisableString("0 BPM"); + protected override LocalisableString FormatCount(int count) => count.ToLocalisableString("0 BPM"); protected override OsuSpriteText CreateSpriteText() => new OsuSpriteText { From c6ca812ea07eb9fdfc24038e05f4b485e9501775 Mon Sep 17 00:00:00 2001 From: Brandon Date: Sun, 18 Feb 2024 21:52:23 -0800 Subject: [PATCH 3128/4092] Add feedback to delete button even when no-op --- osu.Game/Beatmaps/BeatmapManager.cs | 11 +++++++++-- osu.Game/Database/ModelManager.cs | 14 ++++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 1f551f1218..3aed15029d 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -361,13 +361,20 @@ namespace osu.Game.Beatmaps /// public void DeleteVideos(List items, bool silent = false) { - if (items.Count == 0) return; + var noVideosMessage = "No videos found to delete!"; + + if (items.Count == 0) + { + if (!silent) + PostNotification?.Invoke(new ProgressCompletionNotification { Text = noVideosMessage }); + return; + } var notification = new ProgressNotification { Progress = 0, Text = $"Preparing to delete all {HumanisedModelName} videos...", - CompletionText = "No videos found to delete!", + CompletionText = noVideosMessage, State = ProgressNotificationState.Active, }; diff --git a/osu.Game/Database/ModelManager.cs b/osu.Game/Database/ModelManager.cs index 39dae61d36..7a5fb5efbf 100644 --- a/osu.Game/Database/ModelManager.cs +++ b/osu.Game/Database/ModelManager.cs @@ -105,7 +105,12 @@ namespace osu.Game.Database /// public void Delete(List items, bool silent = false) { - if (items.Count == 0) return; + if (items.Count == 0) + { + if (!silent) + PostNotification?.Invoke(new ProgressCompletionNotification { Text = $"No {HumanisedModelName}s found to delete!" }); + return; + } var notification = new ProgressNotification { @@ -142,7 +147,12 @@ namespace osu.Game.Database /// public void Undelete(List items, bool silent = false) { - if (!items.Any()) return; + if (!items.Any()) + { + if (!silent) + PostNotification?.Invoke(new ProgressCompletionNotification { Text = $"No {HumanisedModelName}s found to restore!" }); + return; + } var notification = new ProgressNotification { From 413b7aab602bcd6b4f60265fe893bc88c23b63e8 Mon Sep 17 00:00:00 2001 From: Brandon Date: Sun, 18 Feb 2024 22:12:03 -0800 Subject: [PATCH 3129/4092] Switch to const string --- osu.Game/Beatmaps/BeatmapManager.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 3aed15029d..0610f7f6fb 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -361,12 +361,12 @@ namespace osu.Game.Beatmaps /// public void DeleteVideos(List items, bool silent = false) { - var noVideosMessage = "No videos found to delete!"; + const string no_videos_message = "No videos found to delete!"; if (items.Count == 0) { if (!silent) - PostNotification?.Invoke(new ProgressCompletionNotification { Text = noVideosMessage }); + PostNotification?.Invoke(new ProgressCompletionNotification { Text = no_videos_message }); return; } @@ -374,7 +374,7 @@ namespace osu.Game.Beatmaps { Progress = 0, Text = $"Preparing to delete all {HumanisedModelName} videos...", - CompletionText = noVideosMessage, + CompletionText = no_videos_message, State = ProgressNotificationState.Active, }; From 444ac5ed4d312fc0914b79888c8a2d00f414a728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Feb 2024 09:34:52 +0100 Subject: [PATCH 3130/4092] Add failing test coverage --- .../TestSceneSkinEditorNavigation.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs index 57f1b2fbe9..9c180d43da 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Framework.Threading; @@ -301,6 +302,25 @@ namespace osu.Game.Tests.Visual.Navigation switchToGameplayScene(); } + [Test] + public void TestRulesetInputDisabledWhenSkinEditorOpen() + { + advanceToSongSelect(); + openSkinEditor(); + + AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); + AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + + switchToGameplayScene(); + AddUntilStep("nested input disabled", () => ((Player)Game.ScreenStack.CurrentScreen).ChildrenOfType().All(manager => !manager.UseParentInput)); + + toggleSkinEditor(); + AddUntilStep("nested input enabled", () => ((Player)Game.ScreenStack.CurrentScreen).ChildrenOfType().Any(manager => manager.UseParentInput)); + + toggleSkinEditor(); + AddUntilStep("nested input disabled", () => ((Player)Game.ScreenStack.CurrentScreen).ChildrenOfType().All(manager => !manager.UseParentInput)); + } + private void advanceToSongSelect() { PushAndConfirm(() => songSelect = new TestPlaySongSelect()); From 7f82f103171223518fe34341f07dd07ab4be6c8f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 17 Feb 2024 19:00:43 +0800 Subject: [PATCH 3131/4092] Fix beatmap potentially loading in a bad state Over-caching could mean that a beatmap could load and cause a late crash. Let's catch it early to avoid such a crash occurring. --- osu.Game/Beatmaps/WorkingBeatmapCache.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index 74a85cde7c..8af74d11d8 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -9,6 +9,7 @@ using System.Linq; using JetBrains.Annotations; using osu.Framework.Audio; using osu.Framework.Audio.Track; +using osu.Framework.Extensions; using osu.Framework.Graphics.Rendering; using osu.Framework.Graphics.Rendering.Dummy; using osu.Framework.Graphics.Textures; @@ -143,8 +144,6 @@ namespace osu.Game.Beatmaps { string fileStorePath = BeatmapSetInfo.GetPathForFile(BeatmapInfo.Path); - // TODO: check validity of file - var stream = GetStream(fileStorePath); if (stream == null) @@ -153,6 +152,12 @@ namespace osu.Game.Beatmaps return null; } + if (stream.ComputeMD5Hash() != BeatmapInfo.MD5Hash) + { + Logger.Log($"Beatmap failed to load (file {BeatmapInfo.Path} does not have the expected hash).", level: LogLevel.Error); + return null; + } + using (var reader = new LineBufferedReader(stream)) return Decoder.GetDecoder(reader).Decode(reader); } From 1ca566c6b0001dace3e3a3911c51f66cbf8536fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Feb 2024 09:45:03 +0100 Subject: [PATCH 3132/4092] Disable nested input managers on edited screen when skin editor is open --- .../Overlays/SkinEditor/SkinEditorOverlay.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs index 40cd31934f..93e2f92a1c 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs @@ -10,9 +10,11 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; +using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Screens; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.Containers; @@ -66,6 +68,7 @@ namespace osu.Game.Overlays.SkinEditor private IBindable beatmap { get; set; } = null!; private OsuScreen? lastTargetScreen; + private InvokeOnDisposal? nestedInputManagerDisable; private Vector2 lastDrawSize; @@ -105,6 +108,7 @@ namespace osu.Game.Overlays.SkinEditor if (skinEditor != null) { + disableNestedInputManagers(); skinEditor.Show(); return; } @@ -132,6 +136,8 @@ namespace osu.Game.Overlays.SkinEditor { skinEditor?.Save(false); skinEditor?.Hide(); + nestedInputManagerDisable?.Dispose(); + nestedInputManagerDisable = null; globallyReenableBeatmapSkinSetting(); } @@ -243,6 +249,9 @@ namespace osu.Game.Overlays.SkinEditor /// public void SetTarget(OsuScreen screen) { + nestedInputManagerDisable?.Dispose(); + nestedInputManagerDisable = null; + lastTargetScreen = screen; if (skinEditor == null) return; @@ -271,6 +280,7 @@ namespace osu.Game.Overlays.SkinEditor { skinEditor.Save(false); skinEditor.UpdateTargetScreen(target); + disableNestedInputManagers(); } else { @@ -280,6 +290,21 @@ namespace osu.Game.Overlays.SkinEditor } } + private void disableNestedInputManagers() + { + if (lastTargetScreen == null) + return; + + var nestedInputManagers = lastTargetScreen.ChildrenOfType().Where(manager => manager.UseParentInput).ToArray(); + foreach (var inputManager in nestedInputManagers) + inputManager.UseParentInput = false; + nestedInputManagerDisable = new InvokeOnDisposal(() => + { + foreach (var inputManager in nestedInputManagers) + inputManager.UseParentInput = true; + }); + } + private readonly Bindable beatmapSkins = new Bindable(); private LeasedBindable? leasedBeatmapSkins; From ec26ab51d18d5e4e46ee30782ebc388461c4073f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Feb 2024 13:56:21 +0100 Subject: [PATCH 3133/4092] Use different wording --- osu.Game/Screens/Play/SubmittingPlayer.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 0873f60791..ecb507f382 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -137,11 +137,11 @@ namespace osu.Game.Screens.Play if (displayNotification || shouldExit) { string whatWillHappen = shouldExit - ? "You are not able to submit a score." - : "The following score will not be submitted."; + ? "Play in this state is not permitted." + : "Your score will not be submitted."; if (string.IsNullOrEmpty(exception.Message)) - Logger.Error(exception, $"{whatWillHappen} Failed to retrieve a score submission token."); + Logger.Error(exception, $"Failed to retrieve a score submission token.\n\n{whatWillHappen}"); else { switch (exception.Message) @@ -149,11 +149,11 @@ namespace osu.Game.Screens.Play case @"missing token header": case @"invalid client hash": case @"invalid verification hash": - Logger.Log($"{whatWillHappen} Please ensure that you are using the latest version of the official game releases.", level: LogLevel.Important); + Logger.Log($"Please ensure that you are using the latest version of the official game releases.\n\n{whatWillHappen}", level: LogLevel.Important); break; case @"expired token": - Logger.Log($"{whatWillHappen} Your system clock is set incorrectly. Please check your system time, date and timezone.", level: LogLevel.Important); + Logger.Log($"Your system clock is set incorrectly. Please check your system time, date and timezone.\n\n{whatWillHappen}", level: LogLevel.Important); break; default: From 012d6b7fe1058696e92eaf3c2eee589da50e4f3c Mon Sep 17 00:00:00 2001 From: Mike Will Date: Sun, 18 Feb 2024 22:16:54 -0500 Subject: [PATCH 3134/4092] Change `userBeatmapOffsetClock` to a `FramedOffsetClock` Assuming that the global audio offset is set perfectly, such that any audio latency is fully accounted for, if a specific beatmap still sounds out of sync, that would no longer be a latency issue. Instead, it would indicate a misalignment between the beatmap's track and time codes, the correction for which should be a virtual-time offset, not a real-time offset. --- osu.Game/Beatmaps/FramedBeatmapClock.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/FramedBeatmapClock.cs b/osu.Game/Beatmaps/FramedBeatmapClock.cs index d0ffbdd459..49dff96ff1 100644 --- a/osu.Game/Beatmaps/FramedBeatmapClock.cs +++ b/osu.Game/Beatmaps/FramedBeatmapClock.cs @@ -29,7 +29,7 @@ namespace osu.Game.Beatmaps private readonly OffsetCorrectionClock? userGlobalOffsetClock; private readonly OffsetCorrectionClock? platformOffsetClock; - private readonly OffsetCorrectionClock? userBeatmapOffsetClock; + private readonly FramedOffsetClock? userBeatmapOffsetClock; private readonly IFrameBasedClock finalClockSource; @@ -70,7 +70,7 @@ namespace osu.Game.Beatmaps userGlobalOffsetClock = new OffsetCorrectionClock(platformOffsetClock); // User per-beatmap offset will be applied to this final clock. - finalClockSource = userBeatmapOffsetClock = new OffsetCorrectionClock(userGlobalOffsetClock); + finalClockSource = userBeatmapOffsetClock = new FramedOffsetClock(userGlobalOffsetClock); } else { @@ -122,7 +122,7 @@ namespace osu.Game.Beatmaps Debug.Assert(userBeatmapOffsetClock != null); Debug.Assert(platformOffsetClock != null); - return userGlobalOffsetClock.RateAdjustedOffset + userBeatmapOffsetClock.RateAdjustedOffset + platformOffsetClock.RateAdjustedOffset; + return userGlobalOffsetClock.RateAdjustedOffset + userBeatmapOffsetClock.Offset + platformOffsetClock.RateAdjustedOffset; } } From 24e3fe79a4554b45b9176411402928c35214e172 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Mon, 19 Feb 2024 17:02:53 +0100 Subject: [PATCH 3135/4092] Log `GlobalStatistics` when exporting logs from settings --- osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index fe88413e6a..82cc952e53 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -10,6 +10,7 @@ using osu.Framework.Localisation; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Screens; +using osu.Framework.Statistics; using osu.Game.Configuration; using osu.Game.Localisation; using osu.Game.Overlays.Notifications; @@ -107,6 +108,9 @@ namespace osu.Game.Overlays.Settings.Sections.General try { + GlobalStatistics.OutputToLog(); + Logger.Flush(); + var logStorage = Logger.Storage; using (var outStream = storage.CreateFileSafely(archive_filename)) From 29900353d924e2dd2efc54f98df5b1fdd8bcd38b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 19 Feb 2024 20:26:15 +0300 Subject: [PATCH 3136/4092] Reduce allocations in SliderSelectionBlueprint --- .../Sliders/SliderSelectionBlueprint.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index e421d497e7..4d2b980c23 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -416,8 +416,22 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders DrawableObject.SliderBody?.ToScreenSpace(DrawableObject.SliderBody.PathEndOffset) ?? BodyPiece.ToScreenSpace(BodyPiece.PathEndLocation) }; - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => - BodyPiece.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) + { + if (BodyPiece.ReceivePositionalInputAt(screenSpacePos)) + return true; + + if (ControlPointVisualiser == null) + return false; + + foreach (var p in ControlPointVisualiser.Pieces) + { + if (p.ReceivePositionalInputAt(screenSpacePos)) + return true; + } + + return false; + } protected virtual SliderCircleOverlay CreateCircleOverlay(Slider slider, SliderPosition position) => new SliderCircleOverlay(slider, position); } From c7586403112e4616d12817e0dd3d52e7d8dea487 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 19 Feb 2024 20:49:56 +0300 Subject: [PATCH 3137/4092] Reduce allocations in ComposerDistanceSnapProvider --- .../Edit/ComposerDistanceSnapProvider.cs | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs index b3ca59a5b0..b2f38662cc 100644 --- a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; @@ -124,12 +123,34 @@ namespace osu.Game.Rulesets.Edit private (HitObject before, HitObject after)? getObjectsOnEitherSideOfCurrentTime() { - HitObject? lastBefore = playfield.HitObjectContainer.AliveObjects.LastOrDefault(h => h.HitObject.StartTime < editorClock.CurrentTime)?.HitObject; + HitObject? lastBefore = null; + + foreach (var entry in playfield.HitObjectContainer.AliveEntries) + { + double objTime = entry.Value.HitObject.StartTime; + + if (objTime >= editorClock.CurrentTime) + continue; + + if (objTime > lastBefore?.StartTime) + lastBefore = entry.Value.HitObject; + } if (lastBefore == null) return null; - HitObject? firstAfter = playfield.HitObjectContainer.AliveObjects.FirstOrDefault(h => h.HitObject.StartTime >= editorClock.CurrentTime)?.HitObject; + HitObject? firstAfter = null; + + foreach (var entry in playfield.HitObjectContainer.AliveEntries) + { + double objTime = entry.Value.HitObject.StartTime; + + if (objTime < editorClock.CurrentTime) + continue; + + if (objTime < firstAfter?.StartTime) + firstAfter = entry.Value.HitObject; + } if (firstAfter == null) return null; From 3791ab30c44acaaa312f4180c4a82263d2b1aa7c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 19 Feb 2024 20:55:43 +0300 Subject: [PATCH 3138/4092] Reduce allocations in HitCircleOverlapMarker --- .../HitCircles/Components/HitCircleOverlapMarker.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCircleOverlapMarker.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCircleOverlapMarker.cs index 3cba0610a1..fe335a048d 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCircleOverlapMarker.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCircleOverlapMarker.cs @@ -78,9 +78,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components Scale = new Vector2(hitObject.Scale); - if (hitObject is IHasComboInformation combo) - ring.BorderColour = combo.GetComboColour(skin); - double editorTime = editorClock.CurrentTime; double hitObjectTime = hitObject.StartTime; bool hasReachedObject = editorTime >= hitObjectTime; @@ -92,6 +89,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components ring.Scale = new Vector2(1 + 0.1f * ringScale); content.Alpha = 0.9f * (1 - alpha); + + // TODO: should only update colour on skin/combo/object change. + if (hitObject is IHasComboInformation combo && content.Alpha > 0) + ring.BorderColour = combo.GetComboColour(skin); } else content.Alpha = 0; From 2ff8667dd2e433fd7abb832cc8bb91def14c44d1 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 19 Feb 2024 12:11:12 -0800 Subject: [PATCH 3139/4092] Revert "Centralise global rank display logic to new class" Also don't show on `LoginOverlay` usage for now. --- .../Header/Components/GlobalRankDisplay.cs | 44 ------------------- .../Profile/Header/Components/MainDetails.cs | 17 +++++-- osu.Game/Users/UserRankPanel.cs | 13 +++--- 3 files changed, 19 insertions(+), 55 deletions(-) delete mode 100644 osu.Game/Overlays/Profile/Header/Components/GlobalRankDisplay.cs diff --git a/osu.Game/Overlays/Profile/Header/Components/GlobalRankDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/GlobalRankDisplay.cs deleted file mode 100644 index d32f56ab1b..0000000000 --- a/osu.Game/Overlays/Profile/Header/Components/GlobalRankDisplay.cs +++ /dev/null @@ -1,44 +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.Framework.Bindables; -using osu.Framework.Extensions.LocalisationExtensions; -using osu.Framework.Localisation; -using osu.Game.Online.API.Requests.Responses; -using osu.Game.Resources.Localisation.Web; -using osu.Game.Users; - -namespace osu.Game.Overlays.Profile.Header.Components -{ - public partial class GlobalRankDisplay : ProfileValueDisplay - { - public readonly Bindable UserStatistics = new Bindable(); - public readonly Bindable User = new Bindable(); - - public GlobalRankDisplay() - : base(true) - { - Title = UsersStrings.ShowRankGlobalSimple; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - UserStatistics.BindValueChanged(s => - { - Content = s.NewValue?.GlobalRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-"; - }, true); - - // needed as `UserStatistics` doesn't populate `User` - User.BindValueChanged(u => - { - var rankHighest = u.NewValue?.RankHighest; - - ContentTooltipText = rankHighest != null - ? UsersStrings.ShowRankHighest(rankHighest.Rank.ToLocalisableString("\\##,##0"), rankHighest.UpdatedAt.ToLocalisableString(@"d MMM yyyy")) - : string.Empty; - }, true); - } - } -} diff --git a/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs b/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs index ffdf8edc21..2505c1bc8c 100644 --- a/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs +++ b/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs @@ -22,7 +22,7 @@ namespace osu.Game.Overlays.Profile.Header.Components private readonly Dictionary scoreRankInfos = new Dictionary(); private ProfileValueDisplay medalInfo = null!; private ProfileValueDisplay ppInfo = null!; - private GlobalRankDisplay detailGlobalRank = null!; + private ProfileValueDisplay detailGlobalRank = null!; private ProfileValueDisplay detailCountryRank = null!; private RankGraph rankGraph = null!; @@ -52,7 +52,10 @@ namespace osu.Game.Overlays.Profile.Header.Components Spacing = new Vector2(20), Children = new Drawable[] { - detailGlobalRank = new GlobalRankDisplay(), + detailGlobalRank = new ProfileValueDisplay(true) + { + Title = UsersStrings.ShowRankGlobalSimple, + }, detailCountryRank = new ProfileValueDisplay(true) { Title = UsersStrings.ShowRankCountrySimple, @@ -139,8 +142,14 @@ namespace osu.Game.Overlays.Profile.Header.Components foreach (var scoreRankInfo in scoreRankInfos) scoreRankInfo.Value.RankCount = user?.Statistics?.GradesCount[scoreRankInfo.Key] ?? 0; - detailGlobalRank.UserStatistics.Value = user?.Statistics; - detailGlobalRank.User.Value = user; + detailGlobalRank.Content = user?.Statistics?.GlobalRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-"; + + var rankHighest = user?.RankHighest; + + detailGlobalRank.ContentTooltipText = rankHighest != null + ? UsersStrings.ShowRankHighest(rankHighest.Rank.ToLocalisableString("\\##,##0"), rankHighest.UpdatedAt.ToLocalisableString(@"d MMM yyyy")) + : string.Empty; + detailCountryRank.Content = user?.Statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-"; rankGraph.Statistics.Value = user?.Statistics; diff --git a/osu.Game/Users/UserRankPanel.cs b/osu.Game/Users/UserRankPanel.cs index 0b8a5166e6..b440261a4c 100644 --- a/osu.Game/Users/UserRankPanel.cs +++ b/osu.Game/Users/UserRankPanel.cs @@ -27,10 +27,10 @@ namespace osu.Game.Users [Resolved] private IAPIProvider api { get; set; } = null!; + private ProfileValueDisplay globalRankDisplay = null!; private ProfileValueDisplay countryRankDisplay = null!; private readonly IBindable statistics = new Bindable(); - private readonly IBindable user = new Bindable(); public UserRankPanel(APIUser user) : base(user) @@ -47,10 +47,9 @@ namespace osu.Game.Users statistics.BindTo(api.Statistics); statistics.BindValueChanged(stats => { + globalRankDisplay.Content = stats.NewValue?.GlobalRank?.ToLocalisableString("\\##,##0") ?? "-"; countryRankDisplay.Content = stats.NewValue?.CountryRank?.ToLocalisableString("\\##,##0") ?? "-"; }, true); - - user.BindTo(api.LocalUser!); } protected override Drawable CreateLayout() @@ -164,12 +163,12 @@ namespace osu.Game.Users { new Drawable[] { - new GlobalRankDisplay + globalRankDisplay = new ProfileValueDisplay(true) { - UserStatistics = { BindTarget = statistics }, - // TODO: make highest rank update, as `api.LocalUser` doesn't update + Title = UsersStrings.ShowRankGlobalSimple, + // TODO: implement highest rank tooltip + // `RankHighest` resides in `APIUser`, but `api.LocalUser` doesn't update // maybe move to `UserStatistics` in api, so `SoloStatisticsWatcher` can update the value - User = { BindTarget = user }, }, countryRankDisplay = new ProfileValueDisplay(true) { From 40d6e8ce85cbd516a75510299c230d4048fd5b25 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 20 Feb 2024 15:13:22 +0900 Subject: [PATCH 3140/4092] Remove legacy OpenGL renderer option, it's now just OpenGL --- .../Sections/Graphics/RendererSettings.cs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs index fc5dd34971..a8b127d522 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; -using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Extensions; @@ -28,15 +27,16 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics var renderer = config.GetBindable(FrameworkSetting.Renderer); automaticRendererInUse = renderer.Value == RendererType.Automatic; - SettingsEnumDropdown rendererDropdown; - Children = new Drawable[] { - rendererDropdown = new RendererSettingsDropdown + new RendererSettingsDropdown { LabelText = GraphicsSettingsStrings.Renderer, Current = renderer, - Items = host.GetPreferredRenderersForCurrentPlatform().Order().Where(t => t != RendererType.Vulkan), + Items = host.GetPreferredRenderersForCurrentPlatform().Order() +#pragma warning disable CS0612 // Type or member is obsolete + .Where(t => t != RendererType.Vulkan && t != RendererType.OpenGLLegacy), +#pragma warning restore CS0612 // Type or member is obsolete Keywords = new[] { @"compatibility", @"directx" }, }, // TODO: this needs to be a custom dropdown at some point @@ -79,13 +79,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics })); } }); - - // TODO: remove this once we support SDL+android. - if (RuntimeInfo.OS == RuntimeInfo.Platform.Android) - { - rendererDropdown.Items = new[] { RendererType.Automatic, RendererType.OpenGLLegacy }; - rendererDropdown.SetNoticeText("New renderer support for android is coming soon!", true); - } } private partial class RendererSettingsDropdown : SettingsEnumDropdown From 54ef397d56c0499ceb568b2b867978a94b6b98bd Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Tue, 20 Feb 2024 12:57:28 +0200 Subject: [PATCH 3141/4092] Changed override method now it overrides mods getter instead of calculate fuctions --- .../Overlays/Mods/BeatmapAttributesDisplay.cs | 31 +++------ osu.Game/Overlays/Mods/ModSelectOverlay.cs | 12 ++-- .../Mods/MultiplayerModSelectOverlay.cs | 68 +++++++------------ 3 files changed, 41 insertions(+), 70 deletions(-) diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index f010725c8b..21b3a7faa9 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -41,6 +41,7 @@ namespace osu.Game.Overlays.Mods [Resolved] protected Bindable> Mods { get; private set; } = null!; + protected virtual IEnumerable SelectedMods => Mods.Value; public BindableBool Collapsed { get; } = new BindableBool(true); @@ -148,22 +149,6 @@ namespace osu.Game.Overlays.Mods LeftContent.AutoSizeDuration = Content.AutoSizeDuration = transition_duration; } - protected virtual double GetRate() - { - double rate = 1; - foreach (var mod in Mods.Value.OfType()) - rate = mod.ApplyToRate(0, rate); - return rate; - } - - protected virtual BeatmapDifficulty GetDifficulty() - { - BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(BeatmapInfo.Value!.Difficulty); - foreach (var mod in Mods.Value.OfType()) - mod.ApplyToDifficulty(originalDifficulty); - return originalDifficulty; - } - private void updateValues() => Scheduler.AddOnce(() => { if (BeatmapInfo.Value == null) @@ -180,14 +165,20 @@ namespace osu.Game.Overlays.Mods starRatingDisplay.FinishTransforms(true); }); - double rate = GetRate(); - BeatmapDifficulty originalDifficulty = GetDifficulty(); + double rate = 1; + foreach (var mod in SelectedMods.OfType()) + rate = mod.ApplyToRate(0, rate); + + bpmDisplay.Current.Value = BeatmapInfo.Value.BPM * rate; + + BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(BeatmapInfo.Value.Difficulty); + + foreach (var mod in SelectedMods.OfType()) + mod.ApplyToDifficulty(originalDifficulty); Ruleset ruleset = GameRuleset.Value.CreateInstance(); BeatmapDifficulty adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(originalDifficulty, rate); - bpmDisplay.Current.Value = BeatmapInfo.Value.BPM * rate; - TooltipContent = new AdjustedAttributesTooltip.Data(originalDifficulty, adjustedDifficulty); approachRateDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(originalDifficulty.ApproachRate, adjustedDifficulty.ApproachRate); diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index e29e685ece..f374828a4a 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -114,6 +114,8 @@ namespace osu.Game.Overlays.Mods public IEnumerable AllAvailableMods => AvailableMods.Value.SelectMany(pair => pair.Value); + protected virtual IEnumerable AllSelectedMods => SelectedMods.Value; + private readonly BindableBool customisationVisible = new BindableBool(); private Bindable textSearchStartsActive = null!; @@ -317,7 +319,7 @@ namespace osu.Game.Overlays.Mods SelectedMods.BindValueChanged(_ => { - UpdateRankingInformation(); + updateRankingInformation(); updateFromExternalSelection(); updateCustomisation(); @@ -330,7 +332,7 @@ namespace osu.Game.Overlays.Mods // // See https://github.com/ppy/osu/pull/23284#issuecomment-1529056988 modSettingChangeTracker = new ModSettingChangeTracker(SelectedMods.Value); - modSettingChangeTracker.SettingChanged += _ => UpdateRankingInformation(); + modSettingChangeTracker.SettingChanged += _ => updateRankingInformation(); } }, true); @@ -452,17 +454,17 @@ namespace osu.Game.Overlays.Mods modState.ValidForSelection.Value = modState.Mod.Type != ModType.System && modState.Mod.HasImplementation && IsValidMod.Invoke(modState.Mod); } - protected virtual void UpdateRankingInformation() + private void updateRankingInformation() { if (RankingInformationDisplay == null) return; double multiplier = 1.0; - foreach (var mod in SelectedMods.Value) + foreach (var mod in AllSelectedMods) multiplier *= mod.ScoreMultiplier; - RankingInformationDisplay.Ranked.Value = SelectedMods.Value.All(m => m.Ranked); + RankingInformationDisplay.Ranked.Value = AllSelectedMods.All(m => m.Ranked); RankingInformationDisplay.ModMultiplier.Value = multiplier; } diff --git a/osu.Game/Overlays/Mods/MultiplayerModSelectOverlay.cs b/osu.Game/Overlays/Mods/MultiplayerModSelectOverlay.cs index 78978e25e7..d546392650 100644 --- a/osu.Game/Overlays/Mods/MultiplayerModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/MultiplayerModSelectOverlay.cs @@ -1,13 +1,13 @@ // 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 osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Rulesets; using osu.Game.Online.Rooms; -using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; namespace osu.Game.Overlays.Mods @@ -39,30 +39,21 @@ namespace osu.Game.Overlays.Mods multiplayerRoomItem?.BindValueChanged(_ => SelectedMods.TriggerChange()); } - protected override void UpdateRankingInformation() + protected override IEnumerable AllSelectedMods { - if (RankingInformationDisplay == null) - return; - - double multiplier = 1.0; - - foreach (var mod in SelectedMods.Value) - multiplier *= mod.ScoreMultiplier; - - RankingInformationDisplay.Ranked.Value = SelectedMods.Value.All(m => m.Ranked); - - if (multiplayerRoomItem?.Value != null) + get { - Ruleset ruleset = game.Ruleset.Value.CreateInstance(); - var multiplayerRoomMods = multiplayerRoomItem.Value.RequiredMods.Select(m => m.ToMod(ruleset)); + IEnumerable allMods = SelectedMods.Value; - foreach (var mod in multiplayerRoomMods) - multiplier *= mod.ScoreMultiplier; + if (multiplayerRoomItem?.Value != null) + { + Ruleset ruleset = game.Ruleset.Value.CreateInstance(); + var multiplayerRoomMods = multiplayerRoomItem.Value.RequiredMods.Select(m => m.ToMod(ruleset)); + allMods = allMods.Concat(multiplayerRoomMods); + } - RankingInformationDisplay.Ranked.Value = RankingInformationDisplay.Ranked.Value && multiplayerRoomMods.All(m => m.Ranked); + return allMods; } - - RankingInformationDisplay.ModMultiplier.Value = multiplier; } } @@ -77,34 +68,21 @@ namespace osu.Game.Overlays.Mods multiplayerRoomItem?.BindValueChanged(_ => Mods.TriggerChange()); } - protected override double GetRate() + protected override IEnumerable SelectedMods { - double rate = base.GetRate(); - Ruleset ruleset = GameRuleset.Value.CreateInstance(); - - if (multiplayerRoomItem?.Value != null) + get { - var multiplayerRoomMods = multiplayerRoomItem.Value.RequiredMods.Select(m => m.ToMod(ruleset)); - foreach (var mod in multiplayerRoomMods.OfType()) - rate = mod.ApplyToRate(0, rate); + IEnumerable selectedMods = Mods.Value; + + if (multiplayerRoomItem?.Value != null) + { + Ruleset ruleset = GameRuleset.Value.CreateInstance(); + var multiplayerRoomMods = multiplayerRoomItem.Value.RequiredMods.Select(m => m.ToMod(ruleset)); + selectedMods = selectedMods.Concat(multiplayerRoomMods); + } + + return selectedMods; } - - return rate; - } - - protected override BeatmapDifficulty GetDifficulty() - { - BeatmapDifficulty originalDifficulty = base.GetDifficulty(); - Ruleset ruleset = GameRuleset.Value.CreateInstance(); - - if (multiplayerRoomItem?.Value != null) - { - var multiplayerRoomMods = multiplayerRoomItem.Value.RequiredMods.Select(m => m.ToMod(ruleset)); - foreach (var mod in multiplayerRoomMods.OfType()) - mod.ApplyToDifficulty(originalDifficulty); - } - - return originalDifficulty; } } } From a4288e7ecc8e3b631b8f8a2d43ddbc83c787f033 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Tue, 20 Feb 2024 13:00:59 +0200 Subject: [PATCH 3142/4092] removed unnecessary changes --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index f374828a4a..5b3bafae45 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -127,8 +127,8 @@ namespace osu.Game.Overlays.Mods private DeselectAllModsButton deselectAllModsButton = null!; private Container aboveColumnsContent = null!; - protected RankingInformationDisplay? RankingInformationDisplay; - protected BeatmapAttributesDisplay? BeatmapAttributesDisplay; + private RankingInformationDisplay? rankingInformationDisplay; + private BeatmapAttributesDisplay? beatmapAttributesDisplay; protected virtual BeatmapAttributesDisplay GetBeatmapAttributesDisplay => new BeatmapAttributesDisplay { @@ -155,8 +155,8 @@ namespace osu.Game.Overlays.Mods if (beatmap == value) return; beatmap = value; - if (IsLoaded && BeatmapAttributesDisplay != null) - BeatmapAttributesDisplay.BeatmapInfo.Value = beatmap?.BeatmapInfo; + if (IsLoaded && beatmapAttributesDisplay != null) + beatmapAttributesDisplay.BeatmapInfo.Value = beatmap?.BeatmapInfo; } } @@ -278,12 +278,12 @@ namespace osu.Game.Overlays.Mods }, Children = new Drawable[] { - RankingInformationDisplay = new RankingInformationDisplay + rankingInformationDisplay = new RankingInformationDisplay { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight }, - BeatmapAttributesDisplay = GetBeatmapAttributesDisplay + beatmapAttributesDisplay = GetBeatmapAttributesDisplay } }); } @@ -359,7 +359,7 @@ namespace osu.Game.Overlays.Mods SearchTextBox.PlaceholderText = SearchTextBox.HasFocus ? Resources.Localisation.Web.CommonStrings.InputSearch : ModSelectOverlayStrings.TabToSearch; - if (BeatmapAttributesDisplay != null) + if (beatmapAttributesDisplay != null) { float rightEdgeOfLastButton = footerButtonFlow.Last().ScreenSpaceDrawQuad.TopRight.X; @@ -371,7 +371,7 @@ namespace osu.Game.Overlays.Mods // only update preview panel's collapsed state after we are fully visible, to ensure all the buttons are where we expect them to be. if (Alpha == 1) - BeatmapAttributesDisplay.Collapsed.Value = screenIsntWideEnough; + beatmapAttributesDisplay.Collapsed.Value = screenIsntWideEnough; footerContentFlow.LayoutDuration = 200; footerContentFlow.LayoutEasing = Easing.OutQuint; @@ -456,7 +456,7 @@ namespace osu.Game.Overlays.Mods private void updateRankingInformation() { - if (RankingInformationDisplay == null) + if (rankingInformationDisplay == null) return; double multiplier = 1.0; @@ -464,8 +464,8 @@ namespace osu.Game.Overlays.Mods foreach (var mod in AllSelectedMods) multiplier *= mod.ScoreMultiplier; - RankingInformationDisplay.Ranked.Value = AllSelectedMods.All(m => m.Ranked); - RankingInformationDisplay.ModMultiplier.Value = multiplier; + rankingInformationDisplay.Ranked.Value = AllSelectedMods.All(m => m.Ranked); + rankingInformationDisplay.ModMultiplier.Value = multiplier; } private void updateCustomisation() From 8199a49ee23f12e729a78658a6decd95df56fbc5 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Tue, 20 Feb 2024 13:11:19 +0200 Subject: [PATCH 3143/4092] minor look changes --- osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs | 1 + osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index 21b3a7faa9..7c3c971d76 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -41,6 +41,7 @@ namespace osu.Game.Overlays.Mods [Resolved] protected Bindable> Mods { get; private set; } = null!; + protected virtual IEnumerable SelectedMods => Mods.Value; public BindableBool Collapsed { get; } = new BindableBool(true); diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 5b3bafae45..8a3b1954ed 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -464,8 +464,8 @@ namespace osu.Game.Overlays.Mods foreach (var mod in AllSelectedMods) multiplier *= mod.ScoreMultiplier; - rankingInformationDisplay.Ranked.Value = AllSelectedMods.All(m => m.Ranked); rankingInformationDisplay.ModMultiplier.Value = multiplier; + rankingInformationDisplay.Ranked.Value = AllSelectedMods.All(m => m.Ranked); } private void updateCustomisation() From ed028e8d260854fc800a3331bef952682d5e811f Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Tue, 20 Feb 2024 13:13:13 +0200 Subject: [PATCH 3144/4092] Update MultiplayerModSelectOverlay.cs --- osu.Game/Overlays/Mods/MultiplayerModSelectOverlay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/MultiplayerModSelectOverlay.cs b/osu.Game/Overlays/Mods/MultiplayerModSelectOverlay.cs index d546392650..ee087bb149 100644 --- a/osu.Game/Overlays/Mods/MultiplayerModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/MultiplayerModSelectOverlay.cs @@ -35,7 +35,6 @@ namespace osu.Game.Overlays.Mods protected override void LoadComplete() { base.LoadComplete(); - multiplayerRoomItem?.BindValueChanged(_ => SelectedMods.TriggerChange()); } From 4a314a8e316273ff1f23b4003ea232d413b31ce9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Feb 2024 11:17:18 +0100 Subject: [PATCH 3145/4092] Namespacify data structures used in websocket communications --- osu.Game/Online/Chat/WebSocketChatClient.cs | 2 ++ .../Notifications/WebSocket/{ => Events}/NewChatMessageData.cs | 2 +- .../Notifications/WebSocket/{ => Requests}/EndChatRequest.cs | 2 +- .../Notifications/WebSocket/{ => Requests}/StartChatRequest.cs | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) rename osu.Game/Online/Notifications/WebSocket/{ => Events}/NewChatMessageData.cs (94%) rename osu.Game/Online/Notifications/WebSocket/{ => Requests}/EndChatRequest.cs (89%) rename osu.Game/Online/Notifications/WebSocket/{ => Requests}/StartChatRequest.cs (89%) diff --git a/osu.Game/Online/Chat/WebSocketChatClient.cs b/osu.Game/Online/Chat/WebSocketChatClient.cs index 8e1b501b25..37774a1f5d 100644 --- a/osu.Game/Online/Chat/WebSocketChatClient.cs +++ b/osu.Game/Online/Chat/WebSocketChatClient.cs @@ -13,6 +13,8 @@ using osu.Framework.Logging; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.Notifications.WebSocket; +using osu.Game.Online.Notifications.WebSocket.Events; +using osu.Game.Online.Notifications.WebSocket.Requests; namespace osu.Game.Online.Chat { diff --git a/osu.Game/Online/Notifications/WebSocket/NewChatMessageData.cs b/osu.Game/Online/Notifications/WebSocket/Events/NewChatMessageData.cs similarity index 94% rename from osu.Game/Online/Notifications/WebSocket/NewChatMessageData.cs rename to osu.Game/Online/Notifications/WebSocket/Events/NewChatMessageData.cs index 850fbd226b..ff9f5ee9f7 100644 --- a/osu.Game/Online/Notifications/WebSocket/NewChatMessageData.cs +++ b/osu.Game/Online/Notifications/WebSocket/Events/NewChatMessageData.cs @@ -8,7 +8,7 @@ using Newtonsoft.Json; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; -namespace osu.Game.Online.Notifications.WebSocket +namespace osu.Game.Online.Notifications.WebSocket.Events { /// /// A websocket message sent from the server when new messages arrive. diff --git a/osu.Game/Online/Notifications/WebSocket/EndChatRequest.cs b/osu.Game/Online/Notifications/WebSocket/Requests/EndChatRequest.cs similarity index 89% rename from osu.Game/Online/Notifications/WebSocket/EndChatRequest.cs rename to osu.Game/Online/Notifications/WebSocket/Requests/EndChatRequest.cs index 7f67587f5d..9058fea815 100644 --- a/osu.Game/Online/Notifications/WebSocket/EndChatRequest.cs +++ b/osu.Game/Online/Notifications/WebSocket/Requests/EndChatRequest.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json; -namespace osu.Game.Online.Notifications.WebSocket +namespace osu.Game.Online.Notifications.WebSocket.Requests { /// /// A websocket message notifying the server that the client no longer wants to receive chat messages. diff --git a/osu.Game/Online/Notifications/WebSocket/StartChatRequest.cs b/osu.Game/Online/Notifications/WebSocket/Requests/StartChatRequest.cs similarity index 89% rename from osu.Game/Online/Notifications/WebSocket/StartChatRequest.cs rename to osu.Game/Online/Notifications/WebSocket/Requests/StartChatRequest.cs index 9dd69a7377..bc96415642 100644 --- a/osu.Game/Online/Notifications/WebSocket/StartChatRequest.cs +++ b/osu.Game/Online/Notifications/WebSocket/Requests/StartChatRequest.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json; -namespace osu.Game.Online.Notifications.WebSocket +namespace osu.Game.Online.Notifications.WebSocket.Requests { /// /// A websocket message notifying the server that the client wants to receive chat messages. From 48bf9680e135d1ca528828d411047d338cc07c1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Feb 2024 11:29:59 +0100 Subject: [PATCH 3146/4092] Add new structures for receiving new medal data --- .../Events/NewPrivateNotificationEvent.cs | 39 +++++++++++++++++++ .../WebSocket/Events/UserAchievementUnlock.cs | 34 ++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 osu.Game/Online/Notifications/WebSocket/Events/NewPrivateNotificationEvent.cs create mode 100644 osu.Game/Online/Notifications/WebSocket/Events/UserAchievementUnlock.cs diff --git a/osu.Game/Online/Notifications/WebSocket/Events/NewPrivateNotificationEvent.cs b/osu.Game/Online/Notifications/WebSocket/Events/NewPrivateNotificationEvent.cs new file mode 100644 index 0000000000..1fc9636136 --- /dev/null +++ b/osu.Game/Online/Notifications/WebSocket/Events/NewPrivateNotificationEvent.cs @@ -0,0 +1,39 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace osu.Game.Online.Notifications.WebSocket.Events +{ + /// + /// Reference: https://github.com/ppy/osu-web/blob/master/app/Events/NewPrivateNotificationEvent.php + /// + public class NewPrivateNotificationEvent + { + [JsonProperty("id")] + public ulong ID { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } = string.Empty; + + [JsonProperty("created_at")] + public DateTimeOffset CreatedAt { get; set; } + + [JsonProperty("object_type")] + public string ObjectType { get; set; } = string.Empty; + + [JsonProperty("object_id")] + public ulong ObjectId { get; set; } + + [JsonProperty("source_user_id")] + public uint SourceUserID { get; set; } + + [JsonProperty("is_read")] + public bool IsRead { get; set; } + + [JsonProperty("details")] + public JObject? Details { get; set; } + } +} diff --git a/osu.Game/Online/Notifications/WebSocket/Events/UserAchievementUnlock.cs b/osu.Game/Online/Notifications/WebSocket/Events/UserAchievementUnlock.cs new file mode 100644 index 0000000000..6c7c8af4f4 --- /dev/null +++ b/osu.Game/Online/Notifications/WebSocket/Events/UserAchievementUnlock.cs @@ -0,0 +1,34 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; + +namespace osu.Game.Online.Notifications.WebSocket.Events +{ + /// + /// Reference: https://github.com/ppy/osu-web/blob/master/app/Jobs/Notifications/UserAchievementUnlock.php + /// + public class UserAchievementUnlock + { + [JsonProperty("achievement_id")] + public uint AchievementId { get; set; } + + [JsonProperty("achievement_mode")] + public ushort? AchievementMode { get; set; } + + [JsonProperty("cover_url")] + public string CoverUrl { get; set; } = string.Empty; + + [JsonProperty("slug")] + public string Slug { get; set; } = string.Empty; + + [JsonProperty("title")] + public string Title { get; set; } = string.Empty; + + [JsonProperty("description")] + public string Description { get; set; } = string.Empty; + + [JsonProperty("user_id")] + public uint UserId { get; set; } + } +} From 4911f5208b2ba6903aa3ec807ba246238bf501d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Feb 2024 12:03:12 +0100 Subject: [PATCH 3147/4092] Demote medal "overlay" to animation I need the actual overlay to be doing way more things (receiving the actual websocket events, queueing the medals for display, handling activation mode), so the pre-existing API design of the overlay just will not fly. --- osu.Game.Tests/Visual/Gameplay/TestSceneMedalOverlay.cs | 2 +- osu.Game/Overlays/{MedalOverlay.cs => MedalAnimation.cs} | 4 ++-- osu.Game/Overlays/MedalSplash/DrawableMedal.cs | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) rename osu.Game/Overlays/{MedalOverlay.cs => MedalAnimation.cs} (99%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneMedalOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneMedalOverlay.cs index 71ed0a14a2..afd4427629 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneMedalOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneMedalOverlay.cs @@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep(@"display", () => { - LoadComponentAsync(new MedalOverlay(new Medal + LoadComponentAsync(new MedalAnimation(new Medal { Name = @"Animations", InternalName = @"all-intro-doubletime", diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalAnimation.cs similarity index 99% rename from osu.Game/Overlays/MedalOverlay.cs rename to osu.Game/Overlays/MedalAnimation.cs index eba35ec6f9..80c06be87c 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalAnimation.cs @@ -27,7 +27,7 @@ using osu.Framework.Utils; namespace osu.Game.Overlays { - public partial class MedalOverlay : FocusedOverlayContainer + public partial class MedalAnimation : VisibilityContainer { public const float DISC_SIZE = 400; @@ -45,7 +45,7 @@ namespace osu.Game.Overlays private readonly Container content; - public MedalOverlay(Medal medal) + public MedalAnimation(Medal medal) { this.medal = medal; RelativeSizeAxes = Axes.Both; diff --git a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs index f4f6fd2bc1..2beed6645a 100644 --- a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs +++ b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.MedalSplash public DrawableMedal(Medal medal) { this.medal = medal; - Position = new Vector2(0f, MedalOverlay.DISC_SIZE / 2); + Position = new Vector2(0f, MedalAnimation.DISC_SIZE / 2); FillFlowContainer infoFlow; Children = new Drawable[] @@ -174,7 +174,7 @@ namespace osu.Game.Overlays.MedalSplash .ScaleTo(1); this.ScaleTo(scale_when_unlocked, duration, Easing.OutExpo); - this.MoveToY(MedalOverlay.DISC_SIZE / 2 - 30, duration, Easing.OutExpo); + this.MoveToY(MedalAnimation.DISC_SIZE / 2 - 30, duration, Easing.OutExpo); unlocked.FadeInFromZero(duration); break; @@ -184,7 +184,7 @@ namespace osu.Game.Overlays.MedalSplash .ScaleTo(1); this.ScaleTo(scale_when_full, duration, Easing.OutExpo); - this.MoveToY(MedalOverlay.DISC_SIZE / 2 - 60, duration, Easing.OutExpo); + this.MoveToY(MedalAnimation.DISC_SIZE / 2 - 60, duration, Easing.OutExpo); unlocked.Show(); name.FadeInFromZero(duration + 100); description.FadeInFromZero(duration * 2); From 4f321e242bd7650c0bfe9afec4d3746188df3cf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Feb 2024 12:05:25 +0100 Subject: [PATCH 3148/4092] Enable NRT in `OsuFocusedOverlayContainer` --- .../Containers/OsuFocusedOverlayContainer.cs | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index 162c4b6a59..16539a812d 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.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.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -20,10 +18,10 @@ namespace osu.Game.Graphics.Containers [Cached(typeof(IPreviewTrackOwner))] public abstract partial class OsuFocusedOverlayContainer : FocusedOverlayContainer, IPreviewTrackOwner, IKeyBindingHandler { - private Sample samplePopIn; - private Sample samplePopOut; - protected virtual string PopInSampleName => "UI/overlay-pop-in"; - protected virtual string PopOutSampleName => "UI/overlay-pop-out"; + protected readonly IBindable OverlayActivationMode = new Bindable(OverlayActivation.All); + + protected virtual string PopInSampleName => @"UI/overlay-pop-in"; + protected virtual string PopOutSampleName => @"UI/overlay-pop-out"; protected virtual double PopInOutSampleBalance => 0; protected override bool BlockNonPositionalInput => true; @@ -34,19 +32,20 @@ namespace osu.Game.Graphics.Containers /// protected virtual bool DimMainContent => true; - [Resolved(CanBeNull = true)] - private IOverlayManager overlayManager { get; set; } + [Resolved] + private IOverlayManager? overlayManager { get; set; } [Resolved] - private PreviewTrackManager previewTrackManager { get; set; } + private PreviewTrackManager previewTrackManager { get; set; } = null!; - protected readonly IBindable OverlayActivationMode = new Bindable(OverlayActivation.All); + private Sample? samplePopIn; + private Sample? samplePopOut; - [BackgroundDependencyLoader(true)] - private void load(AudioManager audio) + [BackgroundDependencyLoader] + private void load(AudioManager? audio) { - samplePopIn = audio.Samples.Get(PopInSampleName); - samplePopOut = audio.Samples.Get(PopOutSampleName); + samplePopIn = audio?.Samples.Get(PopInSampleName); + samplePopOut = audio?.Samples.Get(PopOutSampleName); } protected override void LoadComplete() From 2e5b61302ab2652b09ac8fe57e48d76315b0d85a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Feb 2024 12:53:46 +0100 Subject: [PATCH 3149/4092] Implement basic medal display flow --- .../Visual/Gameplay/TestSceneMedalOverlay.cs | 45 +++++-- .../Containers/OsuFocusedOverlayContainer.cs | 11 +- osu.Game/Overlays/MedalAnimation.cs | 15 +-- osu.Game/Overlays/MedalOverlay.cs | 112 ++++++++++++++++++ 4 files changed, 155 insertions(+), 28 deletions(-) create mode 100644 osu.Game/Overlays/MedalOverlay.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneMedalOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneMedalOverlay.cs index afd4427629..ead5c5b418 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneMedalOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneMedalOverlay.cs @@ -1,26 +1,51 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using Newtonsoft.Json.Linq; using NUnit.Framework; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Online.API; +using osu.Game.Online.Notifications.WebSocket; +using osu.Game.Online.Notifications.WebSocket.Events; using osu.Game.Overlays; -using osu.Game.Users; +using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] - public partial class TestSceneMedalOverlay : OsuTestScene + public partial class TestSceneMedalOverlay : OsuManualInputManagerTestScene { - public TestSceneMedalOverlay() + private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; + + private MedalOverlay overlay = null!; + + [SetUpSteps] + public void SetUpSteps() { - AddStep(@"display", () => + AddStep("create overlay", () => Child = overlay = new MedalOverlay()); + } + + [Test] + public void TestBasicAward() + { + AddStep("award medal", () => dummyAPI.NotificationsClient.Receive(new SocketMessage { - LoadComponentAsync(new MedalAnimation(new Medal + Event = @"new", + Data = JObject.FromObject(new NewPrivateNotificationEvent { - Name = @"Animations", - InternalName = @"all-intro-doubletime", - Description = @"More complex than you think.", - }), Add); - }); + Name = @"user_achievement_unlock", + Details = JObject.FromObject(new UserAchievementUnlock + { + Title = "Time And A Half", + Description = "Having a right ol' time. One and a half of them, almost.", + Slug = @"all-intro-doubletime" + }) + }) + })); + AddUntilStep("overlay shown", () => overlay.State.Value, () => Is.EqualTo(Visibility.Visible)); + AddRepeatStep("dismiss", () => InputManager.Key(Key.Escape), 2); + AddUntilStep("overlay hidden", () => overlay.State.Value, () => Is.EqualTo(Visibility.Hidden)); } } } diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index 16539a812d..1945b2f0dd 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -20,8 +20,8 @@ namespace osu.Game.Graphics.Containers { protected readonly IBindable OverlayActivationMode = new Bindable(OverlayActivation.All); - protected virtual string PopInSampleName => @"UI/overlay-pop-in"; - protected virtual string PopOutSampleName => @"UI/overlay-pop-out"; + protected virtual string? PopInSampleName => @"UI/overlay-pop-in"; + protected virtual string? PopOutSampleName => @"UI/overlay-pop-out"; protected virtual double PopInOutSampleBalance => 0; protected override bool BlockNonPositionalInput => true; @@ -44,8 +44,11 @@ namespace osu.Game.Graphics.Containers [BackgroundDependencyLoader] private void load(AudioManager? audio) { - samplePopIn = audio?.Samples.Get(PopInSampleName); - samplePopOut = audio?.Samples.Get(PopOutSampleName); + if (!string.IsNullOrEmpty(PopInSampleName)) + samplePopIn = audio?.Samples.Get(PopInSampleName); + + if (!string.IsNullOrEmpty(PopOutSampleName)) + samplePopOut = audio?.Samples.Get(PopOutSampleName); } protected override void LoadComplete() diff --git a/osu.Game/Overlays/MedalAnimation.cs b/osu.Game/Overlays/MedalAnimation.cs index 80c06be87c..041929be67 100644 --- a/osu.Game/Overlays/MedalAnimation.cs +++ b/osu.Game/Overlays/MedalAnimation.cs @@ -18,11 +18,9 @@ using osu.Framework.Allocation; using osu.Framework.Audio.Sample; using osu.Framework.Audio; using osu.Framework.Graphics.Textures; -using osuTK.Input; using osu.Framework.Graphics.Shapes; using System; using osu.Framework.Graphics.Effects; -using osu.Framework.Input.Events; using osu.Framework.Utils; namespace osu.Game.Overlays @@ -190,17 +188,6 @@ namespace osu.Game.Overlays particleContainer.Add(new MedalParticle(RNG.Next(0, 359))); } - protected override bool OnClick(ClickEvent e) - { - dismiss(); - return true; - } - - protected override void OnFocusLost(FocusLostEvent e) - { - if (e.CurrentState.Keyboard.Keys.IsPressed(Key.Escape)) dismiss(); - } - private const double initial_duration = 400; private const double step_duration = 900; @@ -256,7 +243,7 @@ namespace osu.Game.Overlays this.FadeOut(200); } - private void dismiss() + public void Dismiss() { if (drawableMedal.State != DisplayState.Full) { diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs new file mode 100644 index 0000000000..c3d7b4b9fc --- /dev/null +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -0,0 +1,112 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; +using osu.Game.Input.Bindings; +using osu.Game.Online.API; +using osu.Game.Online.Notifications.WebSocket; +using osu.Game.Online.Notifications.WebSocket.Events; +using osu.Game.Users; + +namespace osu.Game.Overlays +{ + public partial class MedalOverlay : OsuFocusedOverlayContainer + { + protected override string? PopInSampleName => null; + protected override string? PopOutSampleName => null; + + protected override void PopIn() => this.FadeIn(); + + protected override void PopOut() + { + showingMedals = false; + this.FadeOut(); + } + + [Resolved] + private IAPIProvider api { get; set; } = null!; + + private Container medalContainer = null!; + private bool showingMedals; + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.Both; + + api.NotificationsClient.MessageReceived += handleMedalMessages; + + Add(medalContainer = new Container + { + RelativeSizeAxes = Axes.Both + }); + } + + private void handleMedalMessages(SocketMessage obj) + { + if (obj.Event != @"new") + return; + + var data = obj.Data?.ToObject(); + if (data == null || data.Name != @"user_achievement_unlock") + return; + + var details = data.Details?.ToObject(); + if (details == null) + return; + + var medal = new Medal + { + Name = details.Title, + InternalName = details.Slug, + Description = details.Description, + }; + + Show(); + LoadComponentAsync(new MedalAnimation(medal), animation => + { + medalContainer.Add(animation); + showingMedals = true; + }); + } + + protected override void Update() + { + base.Update(); + + if (showingMedals && !medalContainer.Any()) + Hide(); + } + + protected override bool OnClick(ClickEvent e) + { + (medalContainer.FirstOrDefault(anim => anim.IsAlive) as MedalAnimation)?.Dismiss(); + return true; + } + + public override bool OnPressed(KeyBindingPressEvent e) + { + if (e.Action == GlobalAction.Back) + { + (medalContainer.FirstOrDefault(anim => anim.IsAlive) as MedalAnimation)?.Dismiss(); + return true; + } + + return base.OnPressed(e); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (api.IsNotNull()) + api.NotificationsClient.MessageReceived -= handleMedalMessages; + } + } +} From e4971ae121cf6078cf7e1150cf5176e26d093250 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Feb 2024 13:08:02 +0100 Subject: [PATCH 3150/4092] Add display queueing when multiple medals are granted in quick succession --- .../Visual/Gameplay/TestSceneMedalOverlay.cs | 51 ++++++++++++++----- osu.Game/Overlays/MedalOverlay.cs | 12 ++++- 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneMedalOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneMedalOverlay.cs index ead5c5b418..a33c7e662f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneMedalOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneMedalOverlay.cs @@ -29,23 +29,48 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestBasicAward() { - AddStep("award medal", () => dummyAPI.NotificationsClient.Receive(new SocketMessage + awardMedal(new UserAchievementUnlock { - Event = @"new", - Data = JObject.FromObject(new NewPrivateNotificationEvent - { - Name = @"user_achievement_unlock", - Details = JObject.FromObject(new UserAchievementUnlock - { - Title = "Time And A Half", - Description = "Having a right ol' time. One and a half of them, almost.", - Slug = @"all-intro-doubletime" - }) - }) - })); + Title = "Time And A Half", + Description = "Having a right ol' time. One and a half of them, almost.", + Slug = @"all-intro-doubletime" + }); AddUntilStep("overlay shown", () => overlay.State.Value, () => Is.EqualTo(Visibility.Visible)); AddRepeatStep("dismiss", () => InputManager.Key(Key.Escape), 2); AddUntilStep("overlay hidden", () => overlay.State.Value, () => Is.EqualTo(Visibility.Hidden)); } + + [Test] + public void TestMultipleMedalsInQuickSuccession() + { + awardMedal(new UserAchievementUnlock + { + Title = "Time And A Half", + Description = "Having a right ol' time. One and a half of them, almost.", + Slug = @"all-intro-doubletime" + }); + awardMedal(new UserAchievementUnlock + { + Title = "S-Ranker", + Description = "Accuracy is really underrated.", + Slug = @"all-secret-rank-s" + }); + awardMedal(new UserAchievementUnlock + { + Title = "500 Combo", + Description = "500 big ones! You're moving up in the world!", + Slug = @"osu-combo-500" + }); + } + + private void awardMedal(UserAchievementUnlock unlock) => AddStep("award medal", () => dummyAPI.NotificationsClient.Receive(new SocketMessage + { + Event = @"new", + Data = JObject.FromObject(new NewPrivateNotificationEvent + { + Name = @"user_achievement_unlock", + Details = JObject.FromObject(unlock) + }) + })); } } diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index c3d7b4b9fc..70cde43924 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.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 System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.ObjectExtensions; @@ -29,6 +30,8 @@ namespace osu.Game.Overlays this.FadeOut(); } + private readonly Queue queuedMedals = new Queue(); + [Resolved] private IAPIProvider api { get; set; } = null!; @@ -71,7 +74,7 @@ namespace osu.Game.Overlays Show(); LoadComponentAsync(new MedalAnimation(medal), animation => { - medalContainer.Add(animation); + queuedMedals.Enqueue(animation); showingMedals = true; }); } @@ -80,7 +83,12 @@ namespace osu.Game.Overlays { base.Update(); - if (showingMedals && !medalContainer.Any()) + if (!showingMedals || medalContainer.Any()) + return; + + if (queuedMedals.TryDequeue(out var nextMedal)) + medalContainer.Add(nextMedal); + else Hide(); } From b334b78b636a7d4504f3509c88fbf2542d8f4f78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Feb 2024 13:44:25 +0100 Subject: [PATCH 3151/4092] Make medal overlay respect overlay disable via activation mode --- .../Visual/Gameplay/TestSceneMedalOverlay.cs | 35 ++++++++++++++++-- osu.Game/Overlays/MedalOverlay.cs | 36 +++++++++++-------- osu.Game/Properties/AssemblyInfo.cs | 3 ++ 3 files changed, 57 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneMedalOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneMedalOverlay.cs index a33c7e662f..fe9c524285 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneMedalOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneMedalOverlay.cs @@ -1,8 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using Moq; using Newtonsoft.Json.Linq; using NUnit.Framework; +using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Online.API; @@ -16,14 +19,26 @@ namespace osu.Game.Tests.Visual.Gameplay [TestFixture] public partial class TestSceneMedalOverlay : OsuManualInputManagerTestScene { - private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; + private readonly Bindable overlayActivationMode = new Bindable(OverlayActivation.All); + private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; private MedalOverlay overlay = null!; [SetUpSteps] public void SetUpSteps() { - AddStep("create overlay", () => Child = overlay = new MedalOverlay()); + var overlayManagerMock = new Mock(); + overlayManagerMock.Setup(mock => mock.OverlayActivationMode).Returns(overlayActivationMode); + + AddStep("create overlay", () => Child = new DependencyProvidingContainer + { + Child = overlay = new MedalOverlay(), + RelativeSizeAxes = Axes.Both, + CachedDependencies = + [ + (typeof(IOverlayManager), overlayManagerMock.Object) + ] + }); } [Test] @@ -63,6 +78,22 @@ namespace osu.Game.Tests.Visual.Gameplay }); } + [Test] + public void TestDelayMedalDisplayUntilActivationModeAllowsIt() + { + AddStep("disable overlay activation", () => overlayActivationMode.Value = OverlayActivation.Disabled); + awardMedal(new UserAchievementUnlock + { + Title = "Time And A Half", + Description = "Having a right ol' time. One and a half of them, almost.", + Slug = @"all-intro-doubletime" + }); + AddUntilStep("overlay hidden", () => overlay.State.Value, () => Is.EqualTo(Visibility.Hidden)); + + AddStep("re-enable overlay activation", () => overlayActivationMode.Value = OverlayActivation.All); + AddUntilStep("overlay shown", () => overlay.State.Value, () => Is.EqualTo(Visibility.Visible)); + } + private void awardMedal(UserAchievementUnlock unlock) => AddStep("award medal", () => dummyAPI.NotificationsClient.Receive(new SocketMessage { Event = @"new", diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index 70cde43924..03beba2d3b 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -24,11 +24,7 @@ namespace osu.Game.Overlays protected override void PopIn() => this.FadeIn(); - protected override void PopOut() - { - showingMedals = false; - this.FadeOut(); - } + protected override void PopOut() => this.FadeOut(); private readonly Queue queuedMedals = new Queue(); @@ -36,7 +32,6 @@ namespace osu.Game.Overlays private IAPIProvider api { get; set; } = null!; private Container medalContainer = null!; - private bool showingMedals; [BackgroundDependencyLoader] private void load() @@ -51,6 +46,17 @@ namespace osu.Game.Overlays }); } + protected override void LoadComplete() + { + base.LoadComplete(); + + OverlayActivationMode.BindValueChanged(val => + { + if (val.NewValue != OverlayActivation.Disabled && queuedMedals.Any()) + Show(); + }, true); + } + private void handleMedalMessages(SocketMessage obj) { if (obj.Event != @"new") @@ -71,25 +77,25 @@ namespace osu.Game.Overlays Description = details.Description, }; + var medalAnimation = new MedalAnimation(medal); + queuedMedals.Enqueue(medalAnimation); Show(); - LoadComponentAsync(new MedalAnimation(medal), animation => - { - queuedMedals.Enqueue(animation); - showingMedals = true; - }); } protected override void Update() { base.Update(); - if (!showingMedals || medalContainer.Any()) + if (medalContainer.Any()) return; - if (queuedMedals.TryDequeue(out var nextMedal)) - medalContainer.Add(nextMedal); - else + if (!queuedMedals.TryDequeue(out var medal)) + { Hide(); + return; + } + + LoadComponentAsync(medal, medalContainer.Add); } protected override bool OnClick(ClickEvent e) diff --git a/osu.Game/Properties/AssemblyInfo.cs b/osu.Game/Properties/AssemblyInfo.cs index 1b77e45891..be430a0fe4 100644 --- a/osu.Game/Properties/AssemblyInfo.cs +++ b/osu.Game/Properties/AssemblyInfo.cs @@ -11,3 +11,6 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("osu.Game.Tests.Dynamic")] [assembly: InternalsVisibleTo("osu.Game.Tests.iOS")] [assembly: InternalsVisibleTo("osu.Game.Tests.Android")] + +// intended for Moq usage +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] From 8abcc70b938c1ba6d23c1d61ac15cf7cad31b02f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Feb 2024 14:33:08 +0100 Subject: [PATCH 3152/4092] Add medal overlay to game --- .../Navigation/TestSceneScreenNavigation.cs | 25 +++++++++++++++++++ osu.Game/OsuGame.cs | 1 + 2 files changed, 26 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 7e42d4781d..0fa2fd4b0b 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -6,6 +6,7 @@ using System; using System.IO; using System.Linq; +using Newtonsoft.Json.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Configuration; @@ -24,6 +25,8 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.Leaderboards; +using osu.Game.Online.Notifications.WebSocket; +using osu.Game.Online.Notifications.WebSocket.Events; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapListing; using osu.Game.Overlays.Mods; @@ -340,6 +343,28 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("wait for results", () => Game.ScreenStack.CurrentScreen is ResultsScreen); } + [Test] + public void TestShowMedalAtResults() + { + playToResults(); + + AddStep("award medal", () => ((DummyAPIAccess)API).NotificationsClient.Receive(new SocketMessage + { + Event = @"new", + Data = JObject.FromObject(new NewPrivateNotificationEvent + { + Name = @"user_achievement_unlock", + Details = JObject.FromObject(new UserAchievementUnlock + { + Title = "Time And A Half", + Description = "Having a right ol' time. One and a half of them, almost.", + Slug = @"all-intro-doubletime" + }) + }) + })); + AddUntilStep("medal overlay shown", () => Game.ChildrenOfType().Single().State.Value, () => Is.EqualTo(Visibility.Visible)); + } + [Test] public void TestRetryFromResults() { diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 9ffa88947b..a829758f0e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1072,6 +1072,7 @@ namespace osu.Game loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), overlayContent.Add, true); loadComponentSingleFile(wikiOverlay = new WikiOverlay(), overlayContent.Add, true); loadComponentSingleFile(skinEditor = new SkinEditorOverlay(ScreenContainer), overlayContent.Add, true); + loadComponentSingleFile(new MedalOverlay(), overlayContent.Add); loadComponentSingleFile(new LoginOverlay { From 96825915f73c558265f736472e95d7e6c68fa19c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Feb 2024 15:02:30 +0100 Subject: [PATCH 3153/4092] Fix threading failure Implicitly showing the medal overlay fires off some transforms, and the websocket listener runs on a TPL thread. That's a recipe for disaster, so schedule the show call. --- osu.Game/Overlays/MedalOverlay.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index 03beba2d3b..76936e0f5a 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -22,6 +22,8 @@ namespace osu.Game.Overlays protected override string? PopInSampleName => null; protected override string? PopOutSampleName => null; + public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; + protected override void PopIn() => this.FadeIn(); protected override void PopOut() => this.FadeOut(); @@ -52,7 +54,7 @@ namespace osu.Game.Overlays OverlayActivationMode.BindValueChanged(val => { - if (val.NewValue != OverlayActivation.Disabled && queuedMedals.Any()) + if (val.NewValue != OverlayActivation.Disabled && (queuedMedals.Any() || medalContainer.Any())) Show(); }, true); } @@ -79,7 +81,7 @@ namespace osu.Game.Overlays var medalAnimation = new MedalAnimation(medal); queuedMedals.Enqueue(medalAnimation); - Show(); + Scheduler.AddOnce(Show); } protected override void Update() From 611e3fe76bd0930e0ef0320c57ba23658e9b2bc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Feb 2024 15:52:15 +0100 Subject: [PATCH 3154/4092] Delay medal display when wanting to show results animation --- osu.Game/Overlays/MedalOverlay.cs | 5 +++-- .../Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs | 5 +++++ osu.Game/Screens/Ranking/ResultsScreen.cs | 8 ++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index 76936e0f5a..3601566dda 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -54,7 +54,7 @@ namespace osu.Game.Overlays OverlayActivationMode.BindValueChanged(val => { - if (val.NewValue != OverlayActivation.Disabled && (queuedMedals.Any() || medalContainer.Any())) + if (val.NewValue == OverlayActivation.All && (queuedMedals.Any() || medalContainer.Any())) Show(); }, true); } @@ -81,7 +81,8 @@ namespace osu.Game.Overlays var medalAnimation = new MedalAnimation(medal); queuedMedals.Enqueue(medalAnimation); - Scheduler.AddOnce(Show); + if (OverlayActivationMode.Value == OverlayActivation.All) + Scheduler.AddOnce(Show); } protected override void Update() diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index d209c305fa..f807126614 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -31,6 +31,11 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy /// public partial class AccuracyCircle : CompositeDrawable { + /// + /// The total duration of the animation. + /// + public const double TOTAL_DURATION = APPEAR_DURATION + ACCURACY_TRANSFORM_DELAY + ACCURACY_TRANSFORM_DURATION; + /// /// Duration for the transforms causing this component to appear. /// diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 69cfbed8f2..93114b1d18 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -25,8 +25,10 @@ using osu.Game.Input.Bindings; using osu.Game.Localisation; using osu.Game.Online.API; using osu.Game.Online.Placeholders; +using osu.Game.Overlays; using osu.Game.Scoring; using osu.Game.Screens.Play; +using osu.Game.Screens.Ranking.Expanded.Accuracy; using osu.Game.Screens.Ranking.Statistics; using osuTK; @@ -41,6 +43,8 @@ namespace osu.Game.Screens.Ranking public override bool? AllowGlobalTrackControl => true; + protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.UserTriggered; + public readonly Bindable SelectedScore = new Bindable(); [CanBeNull] @@ -160,6 +164,10 @@ namespace osu.Game.Screens.Ranking bool shouldFlair = player != null && !Score.User.IsBot; ScorePanelList.AddScore(Score, shouldFlair); + // this is mostly for medal display. + // we don't want the medal animation to trample on the results screen animation, so we (ab)use `OverlayActivationMode` + // to give the results screen enough time to play the animation out before the medals can be shown. + Scheduler.AddDelayed(() => OverlayActivationMode.Value = OverlayActivation.All, shouldFlair ? AccuracyCircle.TOTAL_DURATION + 1000 : 0); } if (allowWatchingReplay) From 1db5cd3abadcc5f24ceada0092fcf8e2d7ece869 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Feb 2024 16:16:30 +0100 Subject: [PATCH 3155/4092] Move medal overlay to topmost container --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index a829758f0e..dbdc86e016 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1072,7 +1072,6 @@ namespace osu.Game loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), overlayContent.Add, true); loadComponentSingleFile(wikiOverlay = new WikiOverlay(), overlayContent.Add, true); loadComponentSingleFile(skinEditor = new SkinEditorOverlay(ScreenContainer), overlayContent.Add, true); - loadComponentSingleFile(new MedalOverlay(), overlayContent.Add); loadComponentSingleFile(new LoginOverlay { @@ -1088,6 +1087,7 @@ namespace osu.Game loadComponentSingleFile(new AccountCreationOverlay(), topMostOverlayContent.Add, true); loadComponentSingleFile(new DialogOverlay(), topMostOverlayContent.Add, true); + loadComponentSingleFile(new MedalOverlay(), topMostOverlayContent.Add); loadComponentSingleFile(CreateHighPerformanceSession(), Add); From e9aca9226a43285bded225e75465b41045965938 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 20 Feb 2024 19:10:03 +0300 Subject: [PATCH 3156/4092] Reduce allocations in ManiaPlayfield.TotalColumns --- osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 0d36f51943..b3420c49f3 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using System; using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics.Primitives; using osu.Game.Rulesets.Mania.Beatmaps; @@ -149,7 +148,18 @@ namespace osu.Game.Rulesets.Mania.UI /// /// Retrieves the total amount of columns across all stages in this playfield. /// - public int TotalColumns => stages.Sum(s => s.Columns.Length); + public int TotalColumns + { + get + { + int sum = 0; + + foreach (var stage in stages) + sum += stage.Columns.Length; + + return sum; + } + } private Stage getStageByColumn(int column) { From 9b938f333de39b0d497cab545f4e0719e17c9b9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Feb 2024 17:25:11 +0100 Subject: [PATCH 3157/4092] Fix test failures --- osu.Game/Overlays/MedalOverlay.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index 3601566dda..072d7db6c7 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -34,6 +34,7 @@ namespace osu.Game.Overlays private IAPIProvider api { get; set; } = null!; private Container medalContainer = null!; + private MedalAnimation? lastAnimation; [BackgroundDependencyLoader] private void load() @@ -54,7 +55,7 @@ namespace osu.Game.Overlays OverlayActivationMode.BindValueChanged(val => { - if (val.NewValue == OverlayActivation.All && (queuedMedals.Any() || medalContainer.Any())) + if (val.NewValue == OverlayActivation.All && (queuedMedals.Any() || medalContainer.Any() || lastAnimation?.IsLoaded == false)) Show(); }, true); } @@ -89,21 +90,21 @@ namespace osu.Game.Overlays { base.Update(); - if (medalContainer.Any()) + if (medalContainer.Any() || lastAnimation?.IsLoaded == false) return; - if (!queuedMedals.TryDequeue(out var medal)) + if (!queuedMedals.TryDequeue(out lastAnimation)) { Hide(); return; } - LoadComponentAsync(medal, medalContainer.Add); + LoadComponentAsync(lastAnimation, medalContainer.Add); } protected override bool OnClick(ClickEvent e) { - (medalContainer.FirstOrDefault(anim => anim.IsAlive) as MedalAnimation)?.Dismiss(); + lastAnimation?.Dismiss(); return true; } @@ -111,7 +112,7 @@ namespace osu.Game.Overlays { if (e.Action == GlobalAction.Back) { - (medalContainer.FirstOrDefault(anim => anim.IsAlive) as MedalAnimation)?.Dismiss(); + lastAnimation?.Dismiss(); return true; } From 6678c4783bc338287b6465f65cf7a8bb3ee2d288 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 20 Feb 2024 19:31:28 +0300 Subject: [PATCH 3158/4092] Fix PlaybackControl string allocations --- osu.Game/Screens/Edit/Components/PlaybackControl.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Components/PlaybackControl.cs b/osu.Game/Screens/Edit/Components/PlaybackControl.cs index 431336aa60..a5ed0d680f 100644 --- a/osu.Game/Screens/Edit/Components/PlaybackControl.cs +++ b/osu.Game/Screens/Edit/Components/PlaybackControl.cs @@ -97,11 +97,14 @@ namespace osu.Game.Screens.Edit.Components editorClock.Start(); } + private static readonly IconUsage play_icon = FontAwesome.Regular.PlayCircle; + private static readonly IconUsage pause_icon = FontAwesome.Regular.PauseCircle; + protected override void Update() { base.Update(); - playButton.Icon = editorClock.IsRunning ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle; + playButton.Icon = editorClock.IsRunning ? pause_icon : play_icon; } private partial class PlaybackTabControl : OsuTabControl From 33a0dcaf20fad0c79802367f81b9a370a9be035f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Feb 2024 17:59:21 +0100 Subject: [PATCH 3159/4092] NRT-annotate `MedalAnimation` and fix possible nullref --- osu.Game/Overlays/MedalAnimation.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/MedalAnimation.cs b/osu.Game/Overlays/MedalAnimation.cs index 041929be67..25776d50db 100644 --- a/osu.Game/Overlays/MedalAnimation.cs +++ b/osu.Game/Overlays/MedalAnimation.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 osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; @@ -20,6 +18,7 @@ using osu.Framework.Audio; using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Shapes; using System; +using System.Diagnostics; using osu.Framework.Graphics.Effects; using osu.Framework.Utils; @@ -37,9 +36,9 @@ namespace osu.Game.Overlays private readonly BackgroundStrip leftStrip, rightStrip; private readonly CircularContainer disc; private readonly Sprite innerSpin, outerSpin; - private DrawableMedal drawableMedal; - private Sample getSample; + private DrawableMedal? drawableMedal; + private Sample? getSample; private readonly Container content; @@ -197,7 +196,7 @@ namespace osu.Game.Overlays background.FlashColour(Color4.White.Opacity(0.25f), 400); - getSample.Play(); + getSample?.Play(); innerSpin.Spin(20000, RotationDirection.Clockwise); outerSpin.Spin(40000, RotationDirection.Clockwise); @@ -216,6 +215,8 @@ namespace osu.Game.Overlays leftStrip.ResizeWidthTo(1f, step_duration, Easing.OutQuint); rightStrip.ResizeWidthTo(1f, step_duration, Easing.OutQuint); + Debug.Assert(drawableMedal != null); + this.Animate().Schedule(() => { if (drawableMedal.State != DisplayState.Full) @@ -245,7 +246,7 @@ namespace osu.Game.Overlays public void Dismiss() { - if (drawableMedal.State != DisplayState.Full) + if (drawableMedal != null && drawableMedal.State != DisplayState.Full) { // if we haven't yet, play out the animation fully drawableMedal.State = DisplayState.Full; From 871bdb9cf7919088f0344cf9c9f6ca37b204b622 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 20 Feb 2024 19:38:57 +0300 Subject: [PATCH 3160/4092] Reduce string allocations in TimeInfoContainer --- .../Edit/Components/TimeInfoContainer.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs index 9c51258f17..4747828bca 100644 --- a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs +++ b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs @@ -47,11 +47,26 @@ namespace osu.Game.Screens.Edit.Components }; } + private double? lastTime; + private double? lastBPM; + protected override void Update() { base.Update(); - trackTimer.Text = editorClock.CurrentTime.ToEditorFormattedString(); - bpm.Text = @$"{editorBeatmap.ControlPointInfo.TimingPointAt(editorClock.CurrentTime).BPM:0} BPM"; + + if (lastTime != editorClock.CurrentTime) + { + lastTime = editorClock.CurrentTime; + trackTimer.Text = editorClock.CurrentTime.ToEditorFormattedString(); + } + + double newBPM = editorBeatmap.ControlPointInfo.TimingPointAt(editorClock.CurrentTime).BPM; + + if (lastBPM != newBPM) + { + lastBPM = newBPM; + bpm.Text = @$"{newBPM:0} BPM"; + } } } } From e5be8838e68e10cc11c14dc11f5ee1dbfb5a69eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Feb 2024 18:42:01 +0100 Subject: [PATCH 3161/4092] Fix yet another failure --- osu.Game.Tests/Visual/Gameplay/TestSceneMedalOverlay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneMedalOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneMedalOverlay.cs index fe9c524285..5dc553b9df 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneMedalOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneMedalOverlay.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 System.Linq; using Moq; using Newtonsoft.Json.Linq; using NUnit.Framework; @@ -51,6 +52,7 @@ namespace osu.Game.Tests.Visual.Gameplay Slug = @"all-intro-doubletime" }); AddUntilStep("overlay shown", () => overlay.State.Value, () => Is.EqualTo(Visibility.Visible)); + AddUntilStep("wait for load", () => this.ChildrenOfType().Any()); AddRepeatStep("dismiss", () => InputManager.Key(Key.Escape), 2); AddUntilStep("overlay hidden", () => overlay.State.Value, () => Is.EqualTo(Visibility.Hidden)); } From b92cff9a8ed370c59fca69dba9b9cb9923efa737 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 20 Feb 2024 20:29:35 +0300 Subject: [PATCH 3162/4092] Reduce allocations in ManiaSelectionBlueprint --- .../Blueprints/ManiaSelectionBlueprint.cs | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs index 1ae65dd8c0..c645ddd98d 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.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.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Objects; @@ -17,9 +18,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints [Resolved] private Playfield playfield { get; set; } = null!; - [Resolved] - private IScrollingInfo scrollingInfo { get; set; } = null!; - protected ScrollingHitObjectContainer HitObjectContainer => ((ManiaPlayfield)playfield).GetColumn(HitObject.Column).HitObjectContainer; protected ManiaSelectionBlueprint(T hitObject) @@ -28,14 +26,31 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints RelativeSizeAxes = Axes.None; } - protected override void Update() - { - base.Update(); + private readonly IBindable directionBindable = new Bindable(); - var anchor = scrollingInfo.Direction.Value == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre; + [BackgroundDependencyLoader] + private void load(IScrollingInfo scrollingInfo) + { + directionBindable.BindTo(scrollingInfo.Direction); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + 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 override void Update() + { + base.Update(); Position = Parent!.ToLocalSpace(HitObjectContainer.ScreenSpacePositionAtTime(HitObject.StartTime)) - AnchorPosition; Width = HitObjectContainer.DrawWidth; From 2543a48ac83a2983b76da3bfe5d63f55ad3b1544 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 20 Feb 2024 23:18:37 +0300 Subject: [PATCH 3163/4092] Apply padding to GridContainers directly --- .../Chat/ChannelList/ChannelListItem.cs | 56 ++++---- .../Profile/Header/DetailHeaderContainer.cs | 63 ++++----- osu.Game/Screens/Edit/BottomBar.cs | 46 +++---- .../Compose/Components/BeatDivisorControl.cs | 89 +++++------- .../Screens/Edit/EditorScreenWithTimeline.cs | 35 ++--- .../Screens/Edit/Timing/TapTimingControl.cs | 35 ++--- osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 24 ++-- .../Components/MatchBeatmapDetailArea.cs | 52 ++++--- .../Lounge/Components/PillContainer.cs | 30 ++-- .../Playlists/PlaylistsRoomSubScreen.cs | 42 +++--- .../ContractedPanelMiddleContent.cs | 50 ++++--- osu.Game/Screens/Select/BeatmapDetails.cs | 129 +++++++++--------- 12 files changed, 292 insertions(+), 359 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs index 21b6147113..87b1f4ef01 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs @@ -66,41 +66,37 @@ namespace osu.Game.Overlays.Chat.ChannelList Colour = colourProvider.Background4, Alpha = 0f, }, - new Container + new GridContainer { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Left = 18, Right = 10 }, - Child = new GridContainer + ColumnDimensions = new[] { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - }, - Content = new[] - { - new Drawable?[] - { - createIcon(), - text = new TruncatingSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Text = Channel.Name, - Font = OsuFont.Torus.With(size: 17, weight: FontWeight.SemiBold), - Colour = colourProvider.Light3, - Margin = new MarginPadding { Bottom = 2 }, - RelativeSizeAxes = Axes.X, - }, - createMentionPill(), - close = createCloseButton(), - } - }, + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), }, - }, + Content = new[] + { + new Drawable?[] + { + createIcon(), + text = new TruncatingSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = Channel.Name, + Font = OsuFont.Torus.With(size: 17, weight: FontWeight.SemiBold), + Colour = colourProvider.Light3, + Margin = new MarginPadding { Bottom = 2 }, + RelativeSizeAxes = Axes.X, + }, + createMentionPill(), + close = createCloseButton(), + } + } + } }; Action = () => OnRequestSelect?.Invoke(Channel); diff --git a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs index 1f35f39b49..118bf9171e 100644 --- a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs @@ -26,47 +26,42 @@ namespace osu.Game.Overlays.Profile.Header RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background5, }, - new Container + new GridContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Padding = new MarginPadding { Horizontal = WaveOverlayContainer.HORIZONTAL_PADDING, Vertical = 10 }, - Child = new GridContainer + RowDimensions = new[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - RowDimensions = new[] + new Dimension(GridSizeMode.AutoSize), + }, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new Drawable[] { - new Dimension(GridSizeMode.AutoSize), - }, - ColumnDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - }, - Content = new[] - { - new Drawable[] + new MainDetails { - new MainDetails - { - RelativeSizeAxes = Axes.X, - User = { BindTarget = User } - }, - new Box - { - RelativeSizeAxes = Axes.Y, - Width = 2, - Colour = colourProvider.Background6, - Margin = new MarginPadding { Horizontal = 15 } - }, - new ExtendedDetails - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - User = { BindTarget = User } - } + RelativeSizeAxes = Axes.X, + User = { BindTarget = User } + }, + new Box + { + RelativeSizeAxes = Axes.Y, + Width = 2, + Colour = colourProvider.Background6, + Margin = new MarginPadding { Horizontal = 15 } + }, + new ExtendedDetails + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + User = { BindTarget = User } } } } diff --git a/osu.Game/Screens/Edit/BottomBar.cs b/osu.Game/Screens/Edit/BottomBar.cs index aa3c4ba0d0..bc7dfaab88 100644 --- a/osu.Game/Screens/Edit/BottomBar.cs +++ b/osu.Game/Screens/Edit/BottomBar.cs @@ -47,35 +47,31 @@ namespace osu.Game.Screens.Edit RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background4, }, - new Container + new GridContainer { RelativeSizeAxes = Axes.Both, - Child = new GridContainer + ColumnDimensions = new[] { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.Absolute, 170), - new Dimension(), - new Dimension(GridSizeMode.Absolute, 220), - new Dimension(GridSizeMode.Absolute, HitObjectComposer.TOOLBOX_CONTRACTED_SIZE_RIGHT), - }, - Content = new[] - { - new Drawable[] - { - new TimeInfoContainer { RelativeSizeAxes = Axes.Both }, - new SummaryTimeline { RelativeSizeAxes = Axes.Both }, - new PlaybackControl { RelativeSizeAxes = Axes.Both }, - TestGameplayButton = new TestGameplayButton - { - RelativeSizeAxes = Axes.Both, - Size = new Vector2(1), - Action = editor.TestGameplay, - } - }, - } + new Dimension(GridSizeMode.Absolute, 170), + new Dimension(), + new Dimension(GridSizeMode.Absolute, 220), + new Dimension(GridSizeMode.Absolute, HitObjectComposer.TOOLBOX_CONTRACTED_SIZE_RIGHT), }, + Content = new[] + { + new Drawable[] + { + new TimeInfoContainer { RelativeSizeAxes = Axes.Both }, + new SummaryTimeline { RelativeSizeAxes = Axes.Both }, + new PlaybackControl { RelativeSizeAxes = Axes.Both }, + TestGameplayButton = new TestGameplayButton + { + RelativeSizeAxes = Axes.Both, + Size = new Vector2(1), + Action = editor.TestGameplay, + } + }, + } } }; } diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index da1a37d57f..40b97d2137 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -86,35 +86,31 @@ namespace osu.Game.Screens.Edit.Compose.Components RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background3 }, - new Container + new GridContainer { RelativeSizeAxes = Axes.Both, - Child = new GridContainer + Content = new[] { - RelativeSizeAxes = Axes.Both, - Content = new[] + new Drawable[] { - new Drawable[] + new ChevronButton { - new ChevronButton - { - Icon = FontAwesome.Solid.ChevronLeft, - Action = beatDivisor.SelectPrevious - }, - new DivisorDisplay { BeatDivisor = { BindTarget = beatDivisor } }, - new ChevronButton - { - Icon = FontAwesome.Solid.ChevronRight, - Action = beatDivisor.SelectNext - } + Icon = FontAwesome.Solid.ChevronLeft, + Action = beatDivisor.SelectPrevious }, + new DivisorDisplay { BeatDivisor = { BindTarget = beatDivisor } }, + new ChevronButton + { + Icon = FontAwesome.Solid.ChevronRight, + Action = beatDivisor.SelectNext + } }, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.Absolute, 20), - new Dimension(), - new Dimension(GridSizeMode.Absolute, 20) - } + }, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, 20), + new Dimension(), + new Dimension(GridSizeMode.Absolute, 20) } } } @@ -122,42 +118,31 @@ namespace osu.Game.Screens.Edit.Compose.Components }, new Drawable[] { - new Container + new GridContainer { RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + Content = new[] { - new Container + new Drawable[] { - RelativeSizeAxes = Axes.Both, - Child = new GridContainer + new ChevronButton { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] - { - new ChevronButton - { - Icon = FontAwesome.Solid.ChevronLeft, - Action = () => cycleDivisorType(-1) - }, - new DivisorTypeText { BeatDivisor = { BindTarget = beatDivisor } }, - new ChevronButton - { - Icon = FontAwesome.Solid.ChevronRight, - Action = () => cycleDivisorType(1) - } - }, - }, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.Absolute, 20), - new Dimension(), - new Dimension(GridSizeMode.Absolute, 20) - } + Icon = FontAwesome.Solid.ChevronLeft, + Action = () => cycleDivisorType(-1) + }, + new DivisorTypeText { BeatDivisor = { BindTarget = beatDivisor } }, + new ChevronButton + { + Icon = FontAwesome.Solid.ChevronRight, + Action = () => cycleDivisorType(1) } - } + }, + }, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, 20), + new Dimension(), + new Dimension(GridSizeMode.Absolute, 20) } } }, diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index 575a66d421..2b97d363f1 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -57,37 +57,32 @@ namespace osu.Game.Screens.Edit RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background4 }, - new Container + new GridContainer { Name = "Timeline content", RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Padding = new MarginPadding { Horizontal = PADDING, Top = PADDING }, - Child = new GridContainer + Content = new[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Content = new[] + new Drawable[] { - new Drawable[] + TimelineContent = new Container { - TimelineContent = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, }, }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - }, - ColumnDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.Absolute, 90), - } }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + }, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Absolute, 90), + } } } }, diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index bb7a3b8be3..8cdbd97ecb 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -65,35 +65,28 @@ namespace osu.Game.Screens.Edit.Timing { new Drawable[] { - new Container + new GridContainer { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(padding), - Children = new Drawable[] + ColumnDimensions = new[] { - new GridContainer + new Dimension(GridSizeMode.AutoSize), + new Dimension() + }, + Content = new[] + { + new Drawable[] { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] + metronome = new MetronomeDisplay { - new Dimension(GridSizeMode.AutoSize), - new Dimension() - }, - Content = new[] - { - new Drawable[] - { - metronome = new MetronomeDisplay - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, - new WaveformComparisonDisplay() - } + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, }, + new WaveformComparisonDisplay() } - } - }, + }, + } }, new Drawable[] { diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index b6e0450e23..fe508860e0 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs @@ -34,25 +34,21 @@ namespace osu.Game.Screens.Edit.Verify InterpretedDifficulty.Default = StarDifficulty.GetDifficultyRating(EditorBeatmap.BeatmapInfo.StarRating); InterpretedDifficulty.SetDefault(); - Child = new Container + Child = new GridContainer { RelativeSizeAxes = Axes.Both, - Child = new GridContainer + ColumnDimensions = new[] { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] + new Dimension(), + new Dimension(GridSizeMode.Absolute, 250), + }, + Content = new[] + { + new Drawable[] { - new Dimension(), - new Dimension(GridSizeMode.Absolute, 250), + IssueList = new IssueList(), + new IssueSettings(), }, - Content = new[] - { - new Drawable[] - { - IssueList = new IssueList(), - new IssueSettings(), - }, - } } }; } diff --git a/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs b/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs index dec91d8a37..b0ede8d9b5 100644 --- a/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs +++ b/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs @@ -26,48 +26,44 @@ namespace osu.Game.Screens.OnlinePlay.Components [Resolved(typeof(Room))] protected BindableList Playlist { get; private set; } - private readonly Drawable playlistArea; + private readonly GridContainer playlistArea; private readonly DrawableRoomPlaylist playlist; public MatchBeatmapDetailArea() { - Add(playlistArea = new Container + Add(playlistArea = new GridContainer { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Vertical = 10 }, - Child = new GridContainer + Content = new[] { - RelativeSizeAxes = Axes.Both, - Content = new[] + new Drawable[] { - new Drawable[] + new Container { - new Container + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Bottom = 10 }, + Child = playlist = new PlaylistsRoomSettingsPlaylist { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Bottom = 10 }, - Child = playlist = new PlaylistsRoomSettingsPlaylist - { - RelativeSizeAxes = Axes.Both - } + RelativeSizeAxes = Axes.Both } - }, - new Drawable[] - { - new RoundedButton - { - Text = "Add new playlist entry", - RelativeSizeAxes = Axes.Both, - Size = Vector2.One, - Action = () => CreateNewItem?.Invoke() - } - }, + } }, - RowDimensions = new[] + new Drawable[] { - new Dimension(), - new Dimension(GridSizeMode.Absolute, 50), - } + new RoundedButton + { + Text = "Add new playlist entry", + RelativeSizeAxes = Axes.Both, + Size = Vector2.One, + Action = () => CreateNewItem?.Invoke() + } + }, + }, + RowDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Absolute, 50), } }); } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/PillContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/PillContainer.cs index b473ea82c6..5f77742588 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/PillContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/PillContainer.cs @@ -40,35 +40,31 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components Colour = Color4.Black, Alpha = 0.5f }, - new Container + new GridContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, AutoSizeAxes = Axes.Both, Padding = new MarginPadding { Horizontal = padding }, - Child = new GridContainer + ColumnDimensions = new[] { - AutoSizeAxes = Axes.Both, - ColumnDimensions = new[] + new Dimension(GridSizeMode.AutoSize, minSize: 80 - 2 * padding) + }, + Content = new[] + { + new[] { - new Dimension(GridSizeMode.AutoSize, minSize: 80 - 2 * padding) - }, - Content = new[] - { - new[] + new Container { - new Container + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Padding = new MarginPadding { Bottom = 2 }, + Child = content = new Container { AutoSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Padding = new MarginPadding { Bottom = 2 }, - Child = content = new Container - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - } } } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index cf5a8e1985..2460f78c96 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -95,38 +95,34 @@ namespace osu.Game.Screens.OnlinePlay.Playlists new Drawable[] { // Playlist items column - new Container + new GridContainer { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Right = 5 }, - Child = new GridContainer + Content = new[] { - RelativeSizeAxes = Axes.Both, - Content = new[] + new Drawable[] { new OverlinedPlaylistHeader(), }, + new Drawable[] { - new Drawable[] { new OverlinedPlaylistHeader(), }, - new Drawable[] + new DrawableRoomPlaylist { - new DrawableRoomPlaylist + RelativeSizeAxes = Axes.Both, + Items = { BindTarget = Room.Playlist }, + SelectedItem = { BindTarget = SelectedItem }, + AllowSelection = true, + AllowShowingResults = true, + RequestResults = item => { - RelativeSizeAxes = Axes.Both, - Items = { BindTarget = Room.Playlist }, - SelectedItem = { BindTarget = SelectedItem }, - AllowSelection = true, - AllowShowingResults = true, - RequestResults = item => - { - Debug.Assert(RoomId.Value != null); - ParentScreen?.Push(new PlaylistsResultsScreen(null, RoomId.Value.Value, item, false)); - } + Debug.Assert(RoomId.Value != null); + ParentScreen?.Push(new PlaylistsResultsScreen(null, RoomId.Value.Value, item, false)); } - }, + } }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - } + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(), } }, // Spacer diff --git a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs index 195cd03e9b..cfb6465e62 100644 --- a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs @@ -150,44 +150,40 @@ namespace osu.Game.Screens.Ranking.Contracted }, new Drawable[] { - new Container + new GridContainer { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Vertical = 5 }, - Child = new GridContainer + Content = new[] { - RelativeSizeAxes = Axes.Both, - Content = new[] + new Drawable[] { - new Drawable[] + new OsuSpriteText { - new OsuSpriteText + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Current = scoreManager.GetBindableTotalScoreString(score), + Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, fixedWidth: true), + Spacing = new Vector2(-1, 0) + }, + }, + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 2 }, + Child = new DrawableRank(score.Rank) { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Current = scoreManager.GetBindableTotalScoreString(score), - Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, fixedWidth: true), - Spacing = new Vector2(-1, 0) - }, - }, - new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = 2 }, - Child = new DrawableRank(score.Rank) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - } } - }, + } }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - } + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), } } }, diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index dec2c1c1de..2bb60716ff 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -75,99 +75,92 @@ namespace osu.Game.Screens.Select RelativeSizeAxes = Axes.Both, Colour = Colour4.Black.Opacity(0.3f), }, - new Container + new GridContainer { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Horizontal = spacing }, - Children = new Drawable[] + RowDimensions = new[] { - new GridContainer + new Dimension(GridSizeMode.AutoSize), + new Dimension() + }, + Content = new[] + { + new Drawable[] { - RelativeSizeAxes = Axes.Both, - RowDimensions = new[] + new FillFlowContainer { - new Dimension(GridSizeMode.AutoSize), - new Dimension() - }, - Content = new[] - { - new Drawable[] + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Children = new Drawable[] { new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Children = new Drawable[] + Width = 0.5f, + Spacing = new Vector2(spacing), + Padding = new MarginPadding { Right = spacing / 2 }, + Children = new[] { - new FillFlowContainer + new DetailBox().WithChild(new OnlineViewContainer(string.Empty) { RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Width = 0.5f, - Spacing = new Vector2(spacing), - Padding = new MarginPadding { Right = spacing / 2 }, - Children = new[] + Height = 134, + Padding = new MarginPadding { Horizontal = spacing, Top = spacing }, + Child = ratingsDisplay = new UserRatings { - new DetailBox().WithChild(new OnlineViewContainer(string.Empty) - { - RelativeSizeAxes = Axes.X, - Height = 134, - Padding = new MarginPadding { Horizontal = spacing, Top = spacing }, - Child = ratingsDisplay = new UserRatings - { - RelativeSizeAxes = Axes.Both, - }, - }), + RelativeSizeAxes = Axes.Both, }, - }, - new OsuScrollContainer + }), + }, + }, + new OsuScrollContainer + { + RelativeSizeAxes = Axes.X, + Height = 250, + Width = 0.5f, + ScrollbarVisible = false, + Padding = new MarginPadding { Left = spacing / 2 }, + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + LayoutDuration = transition_duration, + LayoutEasing = Easing.OutQuad, + Children = new[] { - RelativeSizeAxes = Axes.X, - Height = 250, - Width = 0.5f, - ScrollbarVisible = false, - Padding = new MarginPadding { Left = spacing / 2 }, - Child = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - LayoutDuration = transition_duration, - LayoutEasing = Easing.OutQuad, - Children = new[] - { - description = new MetadataSectionDescription(query => songSelect?.Search(query)), - source = new MetadataSectionSource(query => songSelect?.Search(query)), - tags = new MetadataSectionTags(query => songSelect?.Search(query)), - }, - }, + description = new MetadataSectionDescription(query => songSelect?.Search(query)), + source = new MetadataSectionSource(query => songSelect?.Search(query)), + tags = new MetadataSectionTags(query => songSelect?.Search(query)), }, }, }, }, - new Drawable[] + }, + }, + new Drawable[] + { + failRetryContainer = new OnlineViewContainer("Sign in to view more details") + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - failRetryContainer = new OnlineViewContainer("Sign in to view more details") + new OsuSpriteText + { + Text = BeatmapsetsStrings.ShowInfoPointsOfFailure, + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14), + }, + failRetryGraph = new FailRetryGraph { RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new OsuSpriteText - { - Text = BeatmapsetsStrings.ShowInfoPointsOfFailure, - Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14), - }, - failRetryGraph = new FailRetryGraph - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = 14 + spacing / 2 }, - }, - }, + Padding = new MarginPadding { Top = 14 + spacing / 2 }, }, - } - } - }, - }, + }, + }, + } + } }, loading = new LoadingLayer(true) }; From 86e3b597b42e79eafdb6ea0722277b6a9882485b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Feb 2024 13:18:51 +0800 Subject: [PATCH 3164/4092] Fix `LegacyApproachCircle` incorrectly applying scaling factor --- .../Skinning/Legacy/LegacyApproachCircle.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyApproachCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyApproachCircle.cs index 0bdea0cab1..8ff85090ca 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyApproachCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyApproachCircle.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Skinning; -using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Legacy @@ -26,10 +25,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy var texture = skin.GetTexture(@"approachcircle"); Debug.Assert(texture != null); Texture = texture.WithMaximumSize(OsuHitObject.OBJECT_DIMENSIONS * 2); - - // account for the sprite being used for the default approach circle being taken from stable, - // when hitcircles have 5px padding on each size. this should be removed if we update the sprite. - Scale = new Vector2(128 / 118f); } protected override void LoadComplete() From a11a83ac480f86f9420ee2f78abb01a2912b858d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Feb 2024 13:44:04 +0800 Subject: [PATCH 3165/4092] Improve comment regarding scale adjust of approach circles --- .../Skinning/Default/DefaultApproachCircle.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultApproachCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultApproachCircle.cs index 272f4b5658..3a4c454bf1 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultApproachCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultApproachCircle.cs @@ -25,8 +25,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { Texture = textures.Get(@"Gameplay/osu/approachcircle").WithMaximumSize(OsuHitObject.OBJECT_DIMENSIONS * 2); - // account for the sprite being used for the default approach circle being taken from stable, - // when hitcircles have 5px padding on each size. this should be removed if we update the sprite. + // In triangles and argon skins, we expanded hitcircles to take up the full 128 px which are clickable, + // but still use the old approach circle sprite. To make it feel correct (ie. disappear as it collides + // with the hitcircle, *not when it overlaps the border*) we need to expand it slightly. Scale = new Vector2(128 / 118f); } From a137fa548080cf6c5ff5b6d79b427cf23a677d2b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Feb 2024 15:43:53 +0800 Subject: [PATCH 3166/4092] Fix classic skin follow circles animating from incorrect starting point --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs index fa2bb9b2ad..4a8b737206 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy // Note that the scale adjust here is 2 instead of DrawableSliderBall.FOLLOW_AREA to match legacy behaviour. // This means the actual tracking area for gameplay purposes is larger than the sprite (but skins may be accounting for this). - this.ScaleTo(0.5f).ScaleTo(2f, Math.Min(180f, remainingTime), Easing.Out) + this.ScaleTo(1f).ScaleTo(2f, Math.Min(180f, remainingTime), Easing.Out) .FadeTo(0).FadeTo(1f, Math.Min(60f, remainingTime)); } From 259be976e870db1fcc6f64f1382a23be02919ad1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 21 Feb 2024 11:42:34 +0100 Subject: [PATCH 3167/4092] Adjust test to fail --- .../SongSelect/TestSceneBeatmapCarousel.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index aa4c879468..de2ae3708f 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -629,7 +629,8 @@ namespace osu.Game.Tests.Visual.SongSelect { var sets = new List(); - const string zzz_string = "zzzzz"; + const string zzz_lowercase = "zzzzz"; + const string zzz_uppercase = "ZZZZZ"; AddStep("Populuate beatmap sets", () => { @@ -640,10 +641,16 @@ namespace osu.Game.Tests.Visual.SongSelect var set = TestResources.CreateTestBeatmapSetInfo(); if (i == 4) - set.Beatmaps.ForEach(b => b.Metadata.Artist = zzz_string); + set.Beatmaps.ForEach(b => b.Metadata.Artist = zzz_uppercase); + + if (i == 8) + set.Beatmaps.ForEach(b => b.Metadata.Artist = zzz_lowercase); + + if (i == 12) + set.Beatmaps.ForEach(b => b.Metadata.Author.Username = zzz_uppercase); if (i == 16) - set.Beatmaps.ForEach(b => b.Metadata.Author.Username = zzz_string); + set.Beatmaps.ForEach(b => b.Metadata.Author.Username = zzz_lowercase); sets.Add(set); } @@ -652,9 +659,11 @@ namespace osu.Game.Tests.Visual.SongSelect loadBeatmaps(sets); AddStep("Sort by author", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Author }, false)); - AddAssert($"Check {zzz_string} is at bottom", () => carousel.BeatmapSets.Last().Metadata.Author.Username == zzz_string); + AddAssert($"Check {zzz_uppercase} is last", () => carousel.BeatmapSets.Last().Metadata.Author.Username == zzz_uppercase); + AddAssert($"Check {zzz_lowercase} is second last", () => carousel.BeatmapSets.SkipLast(1).Last().Metadata.Author.Username == zzz_lowercase); AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false)); - AddAssert($"Check {zzz_string} is at bottom", () => carousel.BeatmapSets.Last().Metadata.Artist == zzz_string); + AddAssert($"Check {zzz_uppercase} is last", () => carousel.BeatmapSets.Last().Metadata.Artist == zzz_uppercase); + AddAssert($"Check {zzz_lowercase} is second last", () => carousel.BeatmapSets.SkipLast(1).Last().Metadata.Artist == zzz_lowercase); } /// From 59235d6c50a796469bb29440745eec9e13d0e926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 21 Feb 2024 12:07:18 +0100 Subject: [PATCH 3168/4092] Implement custom comparer for expected carousel sort behaviour Co-authored-by: Salman Ahmed --- .../Utils/OrdinalSortByCaseStringComparer.cs | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 osu.Game/Utils/OrdinalSortByCaseStringComparer.cs diff --git a/osu.Game/Utils/OrdinalSortByCaseStringComparer.cs b/osu.Game/Utils/OrdinalSortByCaseStringComparer.cs new file mode 100644 index 0000000000..99d73f644f --- /dev/null +++ b/osu.Game/Utils/OrdinalSortByCaseStringComparer.cs @@ -0,0 +1,49 @@ +// 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.Collections.Generic; + +namespace osu.Game.Utils +{ + /// + /// This string comparer is something of a cross-over between and . + /// is used first, but is used as a tie-breaker. + /// + /// + /// This comparer's behaviour somewhat emulates , + /// but non-ordinal comparers - both culture-aware and culture-invariant - have huge performance overheads due to i18n factors (up to 5x slower). + /// + /// + /// Given the following strings to sort: [A, B, C, D, a, b, c, d, A] and a stable sorting algorithm: + /// + /// + /// would return [A, A, B, C, D, a, b, c, d]. + /// This is undesirable as letters are interleaved. + /// + /// + /// would return [A, a, A, B, b, C, c, D, d]. + /// Different letters are not interleaved, but because case is ignored, the As are left in arbitrary order. + /// + /// + /// + /// would return [a, A, A, b, B, c, C, d, D], which is the expected behaviour. + /// + /// + public class OrdinalSortByCaseStringComparer : IComparer + { + public static readonly OrdinalSortByCaseStringComparer INSTANCE = new OrdinalSortByCaseStringComparer(); + + private OrdinalSortByCaseStringComparer() + { + } + + public int Compare(string? a, string? b) + { + int result = StringComparer.OrdinalIgnoreCase.Compare(a, b); + if (result == 0) + result = -StringComparer.Ordinal.Compare(a, b); // negative to place lowercase letters before uppercase. + return result; + } + } +} From 04a2ac3df332fda91ae41c2ee9bc5e1300a60d36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 21 Feb 2024 12:07:28 +0100 Subject: [PATCH 3169/4092] Add benchmarking for custom string comparer --- .../BenchmarkStringComparison.cs | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 osu.Game.Benchmarks/BenchmarkStringComparison.cs diff --git a/osu.Game.Benchmarks/BenchmarkStringComparison.cs b/osu.Game.Benchmarks/BenchmarkStringComparison.cs new file mode 100644 index 0000000000..78e4130abe --- /dev/null +++ b/osu.Game.Benchmarks/BenchmarkStringComparison.cs @@ -0,0 +1,48 @@ +// 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.Collections.Generic; +using BenchmarkDotNet.Attributes; +using osu.Game.Utils; + +namespace osu.Game.Benchmarks +{ + public class BenchmarkStringComparison + { + private string[] strings = null!; + + [GlobalSetup] + public void GlobalSetUp() + { + strings = new string[10000]; + + for (int i = 0; i < strings.Length; ++i) + strings[i] = Guid.NewGuid().ToString(); + + for (int i = 0; i < strings.Length; ++i) + { + if (i % 2 == 0) + strings[i] = strings[i].ToUpperInvariant(); + } + } + + [Benchmark] + public void OrdinalIgnoreCase() => compare(StringComparer.OrdinalIgnoreCase); + + [Benchmark] + public void OrdinalSortByCase() => compare(OrdinalSortByCaseStringComparer.INSTANCE); + + [Benchmark] + public void InvariantCulture() => compare(StringComparer.InvariantCulture); + + private void compare(IComparer comparer) + { + for (int i = 0; i < strings.Length; ++i) + { + for (int j = i + 1; j < strings.Length; ++j) + _ = comparer.Compare(strings[i], strings[j]); + } + } + } +} From 929858226ad208e29746f1b22b80399623f07d52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 21 Feb 2024 12:09:37 +0100 Subject: [PATCH 3170/4092] Use custom comparer in beatmap carousel for expected sort behaviour --- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 6c41bc3805..bf0d7dcbde 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Beatmaps; using osu.Game.Screens.Select.Filter; +using osu.Game.Utils; namespace osu.Game.Screens.Select.Carousel { @@ -67,19 +68,19 @@ namespace osu.Game.Screens.Select.Carousel { default: case SortMode.Artist: - comparison = string.Compare(BeatmapSet.Metadata.Artist, otherSet.BeatmapSet.Metadata.Artist, StringComparison.Ordinal); + comparison = OrdinalSortByCaseStringComparer.INSTANCE.Compare(BeatmapSet.Metadata.Artist, otherSet.BeatmapSet.Metadata.Artist); break; case SortMode.Title: - comparison = string.Compare(BeatmapSet.Metadata.Title, otherSet.BeatmapSet.Metadata.Title, StringComparison.Ordinal); + comparison = OrdinalSortByCaseStringComparer.INSTANCE.Compare(BeatmapSet.Metadata.Title, otherSet.BeatmapSet.Metadata.Title); break; case SortMode.Author: - comparison = string.Compare(BeatmapSet.Metadata.Author.Username, otherSet.BeatmapSet.Metadata.Author.Username, StringComparison.Ordinal); + comparison = OrdinalSortByCaseStringComparer.INSTANCE.Compare(BeatmapSet.Metadata.Author.Username, otherSet.BeatmapSet.Metadata.Author.Username); break; case SortMode.Source: - comparison = string.Compare(BeatmapSet.Metadata.Source, otherSet.BeatmapSet.Metadata.Source, StringComparison.Ordinal); + comparison = OrdinalSortByCaseStringComparer.INSTANCE.Compare(BeatmapSet.Metadata.Source, otherSet.BeatmapSet.Metadata.Source); break; case SortMode.DateAdded: From fb593470d553c68b77e7ec129c47cfd9a21097d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Feb 2024 21:02:20 +0800 Subject: [PATCH 3171/4092] Use `DEFAULT` instead of `INSTANCE` or static field Matches other similar comparers. --- osu.Game.Benchmarks/BenchmarkStringComparison.cs | 2 +- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 8 ++++---- osu.Game/Utils/OrdinalSortByCaseStringComparer.cs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Benchmarks/BenchmarkStringComparison.cs b/osu.Game.Benchmarks/BenchmarkStringComparison.cs index 78e4130abe..d40b92db5f 100644 --- a/osu.Game.Benchmarks/BenchmarkStringComparison.cs +++ b/osu.Game.Benchmarks/BenchmarkStringComparison.cs @@ -31,7 +31,7 @@ namespace osu.Game.Benchmarks public void OrdinalIgnoreCase() => compare(StringComparer.OrdinalIgnoreCase); [Benchmark] - public void OrdinalSortByCase() => compare(OrdinalSortByCaseStringComparer.INSTANCE); + public void OrdinalSortByCase() => compare(OrdinalSortByCaseStringComparer.DEFAULT); [Benchmark] public void InvariantCulture() => compare(StringComparer.InvariantCulture); diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index bf0d7dcbde..43c9c621e8 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -68,19 +68,19 @@ namespace osu.Game.Screens.Select.Carousel { default: case SortMode.Artist: - comparison = OrdinalSortByCaseStringComparer.INSTANCE.Compare(BeatmapSet.Metadata.Artist, otherSet.BeatmapSet.Metadata.Artist); + comparison = OrdinalSortByCaseStringComparer.DEFAULT.Compare(BeatmapSet.Metadata.Artist, otherSet.BeatmapSet.Metadata.Artist); break; case SortMode.Title: - comparison = OrdinalSortByCaseStringComparer.INSTANCE.Compare(BeatmapSet.Metadata.Title, otherSet.BeatmapSet.Metadata.Title); + comparison = OrdinalSortByCaseStringComparer.DEFAULT.Compare(BeatmapSet.Metadata.Title, otherSet.BeatmapSet.Metadata.Title); break; case SortMode.Author: - comparison = OrdinalSortByCaseStringComparer.INSTANCE.Compare(BeatmapSet.Metadata.Author.Username, otherSet.BeatmapSet.Metadata.Author.Username); + comparison = OrdinalSortByCaseStringComparer.DEFAULT.Compare(BeatmapSet.Metadata.Author.Username, otherSet.BeatmapSet.Metadata.Author.Username); break; case SortMode.Source: - comparison = OrdinalSortByCaseStringComparer.INSTANCE.Compare(BeatmapSet.Metadata.Source, otherSet.BeatmapSet.Metadata.Source); + comparison = OrdinalSortByCaseStringComparer.DEFAULT.Compare(BeatmapSet.Metadata.Source, otherSet.BeatmapSet.Metadata.Source); break; case SortMode.DateAdded: diff --git a/osu.Game/Utils/OrdinalSortByCaseStringComparer.cs b/osu.Game/Utils/OrdinalSortByCaseStringComparer.cs index 99d73f644f..6c1532eef5 100644 --- a/osu.Game/Utils/OrdinalSortByCaseStringComparer.cs +++ b/osu.Game/Utils/OrdinalSortByCaseStringComparer.cs @@ -32,7 +32,7 @@ namespace osu.Game.Utils /// public class OrdinalSortByCaseStringComparer : IComparer { - public static readonly OrdinalSortByCaseStringComparer INSTANCE = new OrdinalSortByCaseStringComparer(); + public static readonly OrdinalSortByCaseStringComparer DEFAULT = new OrdinalSortByCaseStringComparer(); private OrdinalSortByCaseStringComparer() { From 6d32cfb7ee7c466a6a8b673ea6b9cac3c66d6ffd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Feb 2024 21:39:33 +0800 Subject: [PATCH 3172/4092] 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 85171cc0fa..30037c868c 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 f23debd38f..463a726856 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From 805f0b6a296aa2507d97fdeec1f14fe78e8c8854 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 21 Feb 2024 14:55:10 +0100 Subject: [PATCH 3173/4092] Remove unused using directive --- osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs index 25fe8170b1..5bf7c0326a 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Diagnostics; -using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; From 4cefa8bb8d256228266d204449610f443c6c37d6 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 21 Feb 2024 22:47:49 +0300 Subject: [PATCH 3174/4092] Reduce allocations in TimelineBlueprintContainer --- .../Components/Timeline/TimelineBlueprintContainer.cs | 8 ++++++-- 1 file changed, 6 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 b60e04afc1..6ebd1961a2 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -116,6 +116,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline updateStacking(); } + private readonly Stack currentConcurrentObjects = new Stack(); + private void updateStacking() { // because only blueprints of objects which are alive (via pooling) are displayed in the timeline, it's feasible to do this every-update. @@ -125,10 +127,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline // after the stack gets this tall, we can presume there is space underneath to draw subsequent blueprints. const int stack_reset_count = 3; - Stack currentConcurrentObjects = new Stack(); + currentConcurrentObjects.Clear(); - foreach (var b in SelectionBlueprints.Reverse()) + for (int i = SelectionBlueprints.Count - 1; i >= 0; i--) { + var b = SelectionBlueprints[i]; + // remove objects from the stack as long as their end time is in the past. while (currentConcurrentObjects.TryPeek(out HitObject hitObject)) { From d01421b951bc308583caa0c687f0715ab55f34de Mon Sep 17 00:00:00 2001 From: Boudewijn Popkema Date: Wed, 21 Feb 2024 23:15:37 +0100 Subject: [PATCH 3175/4092] clear remembered username when checkbox is unticked --- osu.Game/Overlays/Login/LoginForm.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Login/LoginForm.cs b/osu.Game/Overlays/Login/LoginForm.cs index 80dfca93d2..a77baa3186 100644 --- a/osu.Game/Overlays/Login/LoginForm.cs +++ b/osu.Game/Overlays/Login/LoginForm.cs @@ -13,11 +13,11 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Localisation; using osu.Game.Online.API; using osu.Game.Overlays.Settings; using osu.Game.Resources.Localisation.Web; using osuTK; -using osu.Game.Localisation; namespace osu.Game.Overlays.Login { @@ -26,6 +26,7 @@ namespace osu.Game.Overlays.Login private TextBox username = null!; private TextBox password = null!; private ShakeContainer shakeSignIn = null!; + private SettingsCheckbox rememberUsername = null!; [Resolved] private IAPIProvider api { get; set; } = null!; @@ -82,7 +83,7 @@ namespace osu.Game.Overlays.Login }, }, }, - new SettingsCheckbox + rememberUsername = new SettingsCheckbox { LabelText = LoginPanelStrings.RememberUsername, Current = config.GetBindable(OsuSetting.SaveUsername), @@ -130,6 +131,7 @@ namespace osu.Game.Overlays.Login forgottenPasswordLink.AddLink(LayoutStrings.PopupLoginLoginForgot, $"{api.WebsiteRootUrl}/home/password-reset"); password.OnCommit += (_, _) => performLogin(); + rememberUsername.SettingChanged += () => onRememberUsernameChanged(config); if (api.LastLoginError?.Message is string error) { @@ -146,6 +148,12 @@ namespace osu.Game.Overlays.Login shakeSignIn.Shake(); } + private void onRememberUsernameChanged(OsuConfigManager config) + { + if (rememberUsername.Current.Value == false) + config.SetValue(OsuSetting.Username, string.Empty); + } + protected override bool OnClick(ClickEvent e) => true; protected override void OnFocus(FocusEvent e) From 2831ff60e1a361874f0285f1db74be7fa621300a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 22 Feb 2024 09:39:49 +0100 Subject: [PATCH 3176/4092] Add test coverage --- .../Visual/Menus/TestSceneLoginOverlay.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs b/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs index 5fc075ed99..e603f72bb8 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs @@ -8,11 +8,13 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Overlays; using osu.Game.Overlays.Login; +using osu.Game.Overlays.Settings; using osu.Game.Users.Drawables; using osuTK.Input; @@ -25,6 +27,9 @@ namespace osu.Game.Tests.Visual.Menus private LoginOverlay loginOverlay = null!; + [Resolved] + private OsuConfigManager configManager { get; set; } = null!; + [BackgroundDependencyLoader] private void load() { @@ -156,5 +161,36 @@ namespace osu.Game.Tests.Visual.Menus }); AddAssert("login overlay is hidden", () => loginOverlay.State.Value == Visibility.Hidden); } + + [Test] + public void TestUncheckingRememberUsernameClearsIt() + { + AddStep("logout", () => API.Logout()); + AddStep("set username", () => configManager.SetValue(OsuSetting.Username, "test_user")); + AddStep("set remember password", () => configManager.SetValue(OsuSetting.SavePassword, true)); + AddStep("uncheck remember username", () => + { + InputManager.MoveMouseTo(loginOverlay.ChildrenOfType().First()); + InputManager.Click(MouseButton.Left); + }); + AddAssert("remember username off", () => configManager.Get(OsuSetting.SaveUsername), () => Is.False); + AddAssert("remember password off", () => configManager.Get(OsuSetting.SavePassword), () => Is.False); + AddAssert("username cleared", () => configManager.Get(OsuSetting.Username), () => Is.Empty); + } + + [Test] + public void TestUncheckingRememberPasswordClearsToken() + { + AddStep("logout", () => API.Logout()); + AddStep("set token", () => configManager.SetValue(OsuSetting.Token, "test_token")); + AddStep("set remember password", () => configManager.SetValue(OsuSetting.SavePassword, true)); + AddStep("uncheck remember token", () => + { + InputManager.MoveMouseTo(loginOverlay.ChildrenOfType().Last()); + InputManager.Click(MouseButton.Left); + }); + AddAssert("remember password off", () => configManager.Get(OsuSetting.SavePassword), () => Is.False); + AddAssert("token cleared", () => configManager.Get(OsuSetting.Token), () => Is.Empty); + } } } From a1046f0a865641e5b7ffea225d3ad32b45ae4177 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 22 Feb 2024 09:40:27 +0100 Subject: [PATCH 3177/4092] Revert "clear remembered username when checkbox is unticked" This reverts commit d01421b951bc308583caa0c687f0715ab55f34de. --- osu.Game/Overlays/Login/LoginForm.cs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/osu.Game/Overlays/Login/LoginForm.cs b/osu.Game/Overlays/Login/LoginForm.cs index a77baa3186..80dfca93d2 100644 --- a/osu.Game/Overlays/Login/LoginForm.cs +++ b/osu.Game/Overlays/Login/LoginForm.cs @@ -13,11 +13,11 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osu.Game.Localisation; using osu.Game.Online.API; using osu.Game.Overlays.Settings; using osu.Game.Resources.Localisation.Web; using osuTK; +using osu.Game.Localisation; namespace osu.Game.Overlays.Login { @@ -26,7 +26,6 @@ namespace osu.Game.Overlays.Login private TextBox username = null!; private TextBox password = null!; private ShakeContainer shakeSignIn = null!; - private SettingsCheckbox rememberUsername = null!; [Resolved] private IAPIProvider api { get; set; } = null!; @@ -83,7 +82,7 @@ namespace osu.Game.Overlays.Login }, }, }, - rememberUsername = new SettingsCheckbox + new SettingsCheckbox { LabelText = LoginPanelStrings.RememberUsername, Current = config.GetBindable(OsuSetting.SaveUsername), @@ -131,7 +130,6 @@ namespace osu.Game.Overlays.Login forgottenPasswordLink.AddLink(LayoutStrings.PopupLoginLoginForgot, $"{api.WebsiteRootUrl}/home/password-reset"); password.OnCommit += (_, _) => performLogin(); - rememberUsername.SettingChanged += () => onRememberUsernameChanged(config); if (api.LastLoginError?.Message is string error) { @@ -148,12 +146,6 @@ namespace osu.Game.Overlays.Login shakeSignIn.Shake(); } - private void onRememberUsernameChanged(OsuConfigManager config) - { - if (rememberUsername.Current.Value == false) - config.SetValue(OsuSetting.Username, string.Empty); - } - protected override bool OnClick(ClickEvent e) => true; protected override void OnFocus(FocusEvent e) From 01f6ab0336a630a588b0938576660a02150bdeb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 22 Feb 2024 09:44:59 +0100 Subject: [PATCH 3178/4092] Use more correct implementation --- osu.Game/Configuration/OsuConfigManager.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 6b2cb4ee74..a71460ded7 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -77,12 +77,19 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.SavePassword, false).ValueChanged += enabled => { - if (enabled.NewValue) SetValue(OsuSetting.SaveUsername, true); + if (enabled.NewValue) + SetValue(OsuSetting.SaveUsername, true); + else + GetBindable(OsuSetting.Token).SetDefault(); }; SetDefault(OsuSetting.SaveUsername, true).ValueChanged += enabled => { - if (!enabled.NewValue) SetValue(OsuSetting.SavePassword, false); + if (!enabled.NewValue) + { + GetBindable(OsuSetting.Username).SetDefault(); + SetValue(OsuSetting.SavePassword, false); + } }; SetDefault(OsuSetting.ExternalLinkWarning, true); From 81a9908c60012bde9ee9135c254ab2b891be5ecc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 22 Feb 2024 10:27:37 +0100 Subject: [PATCH 3179/4092] Extract common helper for BPM rounding --- osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs | 4 ++-- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 7 ++++--- osu.Game/Utils/FormatUtils.cs | 10 ++++++++++ 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index 1db02b7cf2..2f0b39bfbd 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.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.Collections.Generic; using System.Linq; using System.Threading; @@ -20,6 +19,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Utils; using osuTK; namespace osu.Game.Overlays.Mods @@ -169,7 +169,7 @@ namespace osu.Game.Overlays.Mods foreach (var mod in mods.Value.OfType()) rate = mod.ApplyToRate(0, rate); - bpmDisplay.Current.Value = (int)Math.Round(Math.Round(BeatmapInfo.Value.BPM) * rate); + bpmDisplay.Current.Value = FormatUtils.RoundBPM(BeatmapInfo.Value.BPM, rate); BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(BeatmapInfo.Value.Difficulty); diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index c69cd6ead6..3cab4b67b6 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -31,6 +31,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using osu.Game.Graphics.Containers; using osu.Game.Resources.Localisation.Web; +using osu.Game.Utils; namespace osu.Game.Screens.Select { @@ -405,9 +406,9 @@ namespace osu.Game.Screens.Select foreach (var mod in mods.Value.OfType()) rate = mod.ApplyToRate(0, rate); - int bpmMax = (int)Math.Round(Math.Round(beatmap.ControlPointInfo.BPMMaximum) * rate); - int bpmMin = (int)Math.Round(Math.Round(beatmap.ControlPointInfo.BPMMinimum) * rate); - int mostCommonBPM = (int)Math.Round(Math.Round(60000 / beatmap.GetMostCommonBeatLength()) * rate); + int bpmMax = FormatUtils.RoundBPM(beatmap.ControlPointInfo.BPMMaximum, rate); + int bpmMin = FormatUtils.RoundBPM(beatmap.ControlPointInfo.BPMMinimum, rate); + int mostCommonBPM = FormatUtils.RoundBPM(60000 / beatmap.GetMostCommonBeatLength(), rate); string labelText = bpmMin == bpmMax ? $"{bpmMin}" diff --git a/osu.Game/Utils/FormatUtils.cs b/osu.Game/Utils/FormatUtils.cs index 799dc75ca9..cccad3711c 100644 --- a/osu.Game/Utils/FormatUtils.cs +++ b/osu.Game/Utils/FormatUtils.cs @@ -49,5 +49,15 @@ namespace osu.Game.Utils return precision; } + + /// + /// Applies rounding to the given BPM value. + /// + /// + /// Double-rounding is applied intentionally (see https://github.com/ppy/osu/pull/18345#issue-1243311382 for rationale). + /// + /// The base BPM to round. + /// Rate adjustment, if applicable. + public static int RoundBPM(double baseBpm, double rate = 1) => (int)Math.Round(Math.Round(baseBpm) * rate); } } From 84fdcd24ef17194422a3aab9f3d6653955cda3d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 22 Feb 2024 10:56:50 +0100 Subject: [PATCH 3180/4092] Remove description from mod search terms Closes https://github.com/ppy/osu/issues/27111. --- osu.Game/Overlays/Mods/ModPanel.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs index 2d8d01d8c8..cf173b0d6a 100644 --- a/osu.Game/Overlays/Mods/ModPanel.cs +++ b/osu.Game/Overlays/Mods/ModPanel.cs @@ -77,12 +77,11 @@ namespace osu.Game.Overlays.Mods /// public bool Visible => modState.Visible; - public override IEnumerable FilterTerms => new[] + public override IEnumerable FilterTerms => new LocalisableString[] { Mod.Name, Mod.Name.Replace(" ", string.Empty), Mod.Acronym, - Mod.Description }; public override bool MatchingFilter From b08fcbd4e953af0051bf9560eba57006763ded43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 22 Feb 2024 10:56:13 +0100 Subject: [PATCH 3181/4092] Adjust tests to new behaviour --- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 99a5897dff..b26e126249 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -788,7 +788,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("all columns visible", () => this.ChildrenOfType().All(col => col.IsPresent)); AddStep("set search", () => modSelectOverlay.SearchTerm = "HD"); - AddAssert("one column visible", () => this.ChildrenOfType().Count(col => col.IsPresent) == 1); + AddAssert("two columns visible", () => this.ChildrenOfType().Count(col => col.IsPresent) == 2); AddStep("filter out everything", () => modSelectOverlay.SearchTerm = "Some long search term with no matches"); AddAssert("no columns visible", () => this.ChildrenOfType().All(col => !col.IsPresent)); @@ -812,7 +812,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("all columns visible", () => this.ChildrenOfType().All(col => col.IsPresent)); AddStep("set search", () => modSelectOverlay.SearchTerm = "fail"); - AddAssert("one column visible", () => this.ChildrenOfType().Count(col => col.IsPresent) == 2); + AddAssert("one column visible", () => this.ChildrenOfType().Count(col => col.IsPresent) == 1); AddStep("hide", () => modSelectOverlay.Hide()); AddStep("show", () => modSelectOverlay.Show()); From 47db317df84862a68947c20e71dcfe87c552830f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 22 Feb 2024 11:45:57 +0100 Subject: [PATCH 3182/4092] Enable NRT in `TestSceneDifficultyIcon` --- osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultyIcon.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultyIcon.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultyIcon.cs index 80320c138b..e544177d50 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultyIcon.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultyIcon.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 NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -16,7 +14,7 @@ namespace osu.Game.Tests.Visual.Beatmaps { public partial class TestSceneDifficultyIcon : OsuTestScene { - private FillFlowContainer fill; + private FillFlowContainer fill = null!; protected override void LoadComplete() { @@ -35,7 +33,7 @@ namespace osu.Game.Tests.Visual.Beatmaps [Test] public void CreateDifficultyIcon() { - DifficultyIcon difficultyIcon = null; + DifficultyIcon difficultyIcon = null!; AddRepeatStep("create difficulty icon", () => { From d06c67ad8f921b02990b954256c95c22c5e70f22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 22 Feb 2024 11:57:40 +0100 Subject: [PATCH 3183/4092] Substitute two jank interdependent bool flags for single tri-state enums --- .../Beatmaps/TestSceneDifficultyIcon.cs | 12 ++------ osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 29 ++++++++++++++----- .../Drawables/DifficultyIconTooltip.cs | 11 ++++--- osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs | 2 +- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 2 +- .../Carousel/DrawableCarouselBeatmap.cs | 2 +- 6 files changed, 34 insertions(+), 24 deletions(-) diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultyIcon.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultyIcon.cs index e544177d50..6a226c2b8c 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultyIcon.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultyIcon.cs @@ -50,18 +50,12 @@ namespace osu.Game.Tests.Visual.Beatmaps fill.Add(difficultyIcon = new DifficultyIcon(beatmapInfo, rulesetInfo) { Scale = new Vector2(2), - ShowTooltip = true, - ShowExtendedTooltip = true }); }, 10); - AddStep("hide extended tooltip", () => difficultyIcon.ShowExtendedTooltip = false); - - AddStep("hide tooltip", () => difficultyIcon.ShowTooltip = false); - - AddStep("show tooltip", () => difficultyIcon.ShowTooltip = true); - - AddStep("show extended tooltip", () => difficultyIcon.ShowExtendedTooltip = true); + AddStep("no tooltip", () => difficultyIcon.TooltipType = DifficultyIconTooltipType.None); + AddStep("basic tooltip", () => difficultyIcon.TooltipType = DifficultyIconTooltipType.StarRating); + AddStep("extended tooltip", () => difficultyIcon.TooltipType = DifficultyIconTooltipType.Extended); } } } diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 73073a8286..2e7f894d12 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -32,14 +32,9 @@ namespace osu.Game.Beatmaps.Drawables } /// - /// Whether to display a tooltip on hover. Only works if a beatmap was provided at construction time. + /// Which type of tooltip to show. Only works if a beatmap was provided at construction time. /// - public bool ShowTooltip { get; set; } = true; - - /// - /// Whether to include the difficulty stats in the tooltip or not. Defaults to false. Has no effect if is false. - /// - public bool ShowExtendedTooltip { get; set; } + public DifficultyIconTooltipType TooltipType { get; set; } = DifficultyIconTooltipType.StarRating; private readonly IBeatmapInfo? beatmap; @@ -138,6 +133,24 @@ namespace osu.Game.Beatmaps.Drawables GetCustomTooltip() => new DifficultyIconTooltip(); DifficultyIconTooltipContent IHasCustomTooltip. - TooltipContent => (ShowTooltip && beatmap != null ? new DifficultyIconTooltipContent(beatmap, Current, ruleset, mods, ShowExtendedTooltip) : null)!; + TooltipContent => (TooltipType != DifficultyIconTooltipType.None && beatmap != null ? new DifficultyIconTooltipContent(beatmap, Current, ruleset, mods, TooltipType) : null)!; + } + + public enum DifficultyIconTooltipType + { + /// + /// No tooltip. + /// + None, + + /// + /// Star rating only. + /// + StarRating, + + /// + /// Star rating, OD, HP, CS, AR, length, BPM, and max combo. + /// + Extended, } } diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index 71366de654..952f71332f 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -113,7 +113,7 @@ namespace osu.Game.Beatmaps.Drawables starRating.Current.BindTarget = displayedContent.Difficulty; difficultyName.Text = displayedContent.BeatmapInfo.DifficultyName; - if (!displayedContent.ShowExtendedTooltip) + if (displayedContent.TooltipType == DifficultyIconTooltipType.StarRating) { difficultyFillFlowContainer.Hide(); miscFillFlowContainer.Hide(); @@ -166,15 +166,18 @@ namespace osu.Game.Beatmaps.Drawables public readonly IBindable Difficulty; public readonly IRulesetInfo Ruleset; public readonly Mod[]? Mods; - public readonly bool ShowExtendedTooltip; + public readonly DifficultyIconTooltipType TooltipType; - public DifficultyIconTooltipContent(IBeatmapInfo beatmapInfo, IBindable difficulty, IRulesetInfo rulesetInfo, Mod[]? mods, bool showExtendedTooltip = false) + public DifficultyIconTooltipContent(IBeatmapInfo beatmapInfo, IBindable difficulty, IRulesetInfo rulesetInfo, Mod[]? mods, DifficultyIconTooltipType tooltipType) { + if (tooltipType == DifficultyIconTooltipType.None) + throw new ArgumentOutOfRangeException(nameof(tooltipType), tooltipType, "Cannot instantiate a tooltip without a type"); + BeatmapInfo = beatmapInfo; Difficulty = difficulty; Ruleset = rulesetInfo; Mods = mods; - ShowExtendedTooltip = showExtendedTooltip; + TooltipType = tooltipType; } } } diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index 1f38e2ed6c..5f021803b0 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -297,7 +297,7 @@ namespace osu.Game.Overlays.BeatmapSet }, icon = new DifficultyIcon(beatmapInfo, ruleset) { - ShowTooltip = false, + TooltipType = DifficultyIconTooltipType.None, Current = { Value = new StarDifficulty(beatmapInfo.StarRating, 0) }, Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 44e91c6975..1b8e2d8be6 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -285,7 +285,7 @@ namespace osu.Game.Screens.OnlinePlay difficultyIconContainer.Child = new DifficultyIcon(beatmap, ruleset, requiredMods) { Size = new Vector2(icon_height), - ShowExtendedTooltip = true + TooltipType = DifficultyIconTooltipType.Extended, }; } else diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index baf0a14062..01e58d4ab2 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -122,7 +122,7 @@ namespace osu.Game.Screens.Select.Carousel { difficultyIcon = new DifficultyIcon(beatmapInfo) { - ShowTooltip = false, + TooltipType = DifficultyIconTooltipType.None, Scale = new Vector2(1.8f), }, new FillFlowContainer From 37400643605426647eb80097401002828ff0d7ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 22 Feb 2024 11:59:55 +0100 Subject: [PATCH 3184/4092] Fix test scene not properly setting tooltip type on all icons --- .../Visual/Beatmaps/TestSceneDifficultyIcon.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultyIcon.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultyIcon.cs index 6a226c2b8c..fb6bebe50d 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultyIcon.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultyIcon.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Utils; @@ -14,13 +15,13 @@ namespace osu.Game.Tests.Visual.Beatmaps { public partial class TestSceneDifficultyIcon : OsuTestScene { - private FillFlowContainer fill = null!; + private FillFlowContainer fill = null!; protected override void LoadComplete() { base.LoadComplete(); - Child = fill = new FillFlowContainer + Child = fill = new FillFlowContainer { AutoSizeAxes = Axes.Y, Width = 300, @@ -33,8 +34,6 @@ namespace osu.Game.Tests.Visual.Beatmaps [Test] public void CreateDifficultyIcon() { - DifficultyIcon difficultyIcon = null!; - AddRepeatStep("create difficulty icon", () => { var rulesetInfo = new OsuRuleset().RulesetInfo; @@ -47,15 +46,15 @@ namespace osu.Game.Tests.Visual.Beatmaps beatmapInfo.StarRating = RNG.NextSingle(0, 10); beatmapInfo.BPM = RNG.Next(60, 300); - fill.Add(difficultyIcon = new DifficultyIcon(beatmapInfo, rulesetInfo) + fill.Add(new DifficultyIcon(beatmapInfo, rulesetInfo) { Scale = new Vector2(2), }); }, 10); - AddStep("no tooltip", () => difficultyIcon.TooltipType = DifficultyIconTooltipType.None); - AddStep("basic tooltip", () => difficultyIcon.TooltipType = DifficultyIconTooltipType.StarRating); - AddStep("extended tooltip", () => difficultyIcon.TooltipType = DifficultyIconTooltipType.Extended); + AddStep("no tooltip", () => fill.ForEach(icon => icon.TooltipType = DifficultyIconTooltipType.None)); + AddStep("basic tooltip", () => fill.ForEach(icon => icon.TooltipType = DifficultyIconTooltipType.StarRating)); + AddStep("extended tooltip", () => fill.ForEach(icon => icon.TooltipType = DifficultyIconTooltipType.Extended)); } } } From 7861125e7ac1dcd48c06f5ab005870b5c111c31c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 22 Feb 2024 12:11:49 +0100 Subject: [PATCH 3185/4092] Swap AR and OD on tooltips Matches everything else. --- 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 952f71332f..1f3dcfee8c 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -78,8 +78,8 @@ namespace osu.Game.Beatmaps.Drawables { circleSize = new OsuSpriteText { Font = OsuFont.GetFont(size: 14) }, drainRate = new OsuSpriteText { Font = OsuFont.GetFont(size: 14) }, + overallDifficulty = new OsuSpriteText { Font = OsuFont.GetFont(size: 14) }, approachRate = new OsuSpriteText { Font = OsuFont.GetFont(size: 14) }, - overallDifficulty = new OsuSpriteText { Font = OsuFont.GetFont(size: 14) } } }, miscFillFlowContainer = new FillFlowContainer From bbf3f6b56ca29f367b40fb5cc5414aab349820c2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 22 Feb 2024 16:31:13 +0300 Subject: [PATCH 3186/4092] Fix old-style legacy spinner fade in not matching stable --- .../Skinning/Legacy/LegacyOldStyleSpinner.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs index c57487cf75..c75983f3d2 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs @@ -92,8 +92,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt)) this.FadeOut(); - using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2)) - this.FadeInFromZero(spinner.TimeFadeIn / 2); + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn)) + this.FadeInFromZero(spinner.TimeFadeIn); } protected override void Update() From 99bbbf810bfcdbe2d1ee8fea5cecbc86e46ce667 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 22 Feb 2024 16:58:21 +0100 Subject: [PATCH 3187/4092] Update github actions to resolve most node deprecation warnings As is github tradition, workflows started yelling about running on a node version that was getting sunset, so here we go again. Relevant bumps: - https://github.com/actions/checkout/releases/tag/v4.0.0 - https://github.com/actions/setup-dotnet/releases/tag/v4.0.0 - https://github.com/actions/cache/releases/tag/v4.0.0 - https://github.com/actions/setup-java/releases/tag/v4.0.0 - https://github.com/peter-evans/create-pull-request/releases/tag/v6.0.0 - https://github.com/dorny/test-reporter/releases/tag/v1.8.0 Notably, `actions/upload-artifact` is _not_ bumped to v4, although it should be to resolve the node deprecation warnings, because it has more breaking changes and bumping would break `dorny/test-reporter` (see https://github.com/dorny/test-reporter/issues/363). --- .github/workflows/ci.yml | 20 +++++++++---------- .github/workflows/diffcalc.yml | 2 +- .github/workflows/report-nunit.yml | 2 +- .github/workflows/sentry-release.yml | 2 +- .../workflows/update-web-mod-definitions.yml | 10 +++++----- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de902df93f..1ea4654563 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,10 +13,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install .NET 8.0.x - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: "8.0.x" @@ -27,7 +27,7 @@ jobs: run: dotnet restore osu.Desktop.slnf - name: Restore inspectcode cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ github.workspace }}/inspectcode key: inspectcode-${{ hashFiles('.config/dotnet-tools.json', '.github/workflows/ci.yml', 'osu.sln*', 'osu*.slnf', '.editorconfig', '.globalconfig', 'CodeAnalysis/*', '**/*.csproj', '**/*.props') }} @@ -70,10 +70,10 @@ jobs: timeout-minutes: 60 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install .NET 8.0.x - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: "8.0.x" @@ -99,16 +99,16 @@ jobs: timeout-minutes: 60 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup JDK 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: microsoft java-version: 11 - name: Install .NET 8.0.x - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: "8.0.x" @@ -126,10 +126,10 @@ jobs: timeout-minutes: 60 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install .NET 8.0.x - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: "8.0.x" diff --git a/.github/workflows/diffcalc.yml b/.github/workflows/diffcalc.yml index 5f16e09040..7a2dcecb9c 100644 --- a/.github/workflows/diffcalc.yml +++ b/.github/workflows/diffcalc.yml @@ -140,7 +140,7 @@ jobs: GOOGLE_CREDS_FILE: ${{ steps.set-outputs.outputs.GOOGLE_CREDS_FILE }} steps: - name: Checkout diffcalc-sheet-generator - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: ${{ env.EXECUTION_ID }} repository: 'smoogipoo/diffcalc-sheet-generator' diff --git a/.github/workflows/report-nunit.yml b/.github/workflows/report-nunit.yml index 99e39f6f56..c44f46d70a 100644 --- a/.github/workflows/report-nunit.yml +++ b/.github/workflows/report-nunit.yml @@ -28,7 +28,7 @@ jobs: timeout-minutes: 5 steps: - name: Annotate CI run with test results - uses: dorny/test-reporter@v1.6.0 + uses: dorny/test-reporter@v1.8.0 with: artifact: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}} name: Test Results (${{matrix.os.prettyname}}, ${{matrix.threadingMode}}) diff --git a/.github/workflows/sentry-release.yml b/.github/workflows/sentry-release.yml index ff4165c414..be104d0fd3 100644 --- a/.github/workflows/sentry-release.yml +++ b/.github/workflows/sentry-release.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/.github/workflows/update-web-mod-definitions.yml b/.github/workflows/update-web-mod-definitions.yml index 5827a6cdbf..b19f03ad7d 100644 --- a/.github/workflows/update-web-mod-definitions.yml +++ b/.github/workflows/update-web-mod-definitions.yml @@ -13,23 +13,23 @@ jobs: runs-on: ubuntu-latest steps: - name: Install .NET 8.0.x - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: "8.0.x" - name: Checkout ppy/osu - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: osu - name: Checkout ppy/osu-tools - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: ppy/osu-tools path: osu-tools - name: Checkout ppy/osu-web - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: ppy/osu-web path: osu-web @@ -43,7 +43,7 @@ jobs: working-directory: ./osu-tools - name: Create pull request with changes - uses: peter-evans/create-pull-request@v5 + uses: peter-evans/create-pull-request@v6 with: title: Update mod definitions body: "This PR has been auto-generated to update the mod definitions to match ppy/osu@${{ github.ref_name }}." From e1ceb8a5fa9abb9512c5e1639ae1132b64a9d272 Mon Sep 17 00:00:00 2001 From: SupDos <6813986+SupDos@users.noreply.github.com> Date: Thu, 22 Feb 2024 18:54:28 +0100 Subject: [PATCH 3188/4092] Add missing .olz association to iOS --- osu.iOS/Info.plist | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.iOS/Info.plist b/osu.iOS/Info.plist index cf51fe995b..1330e29bc1 100644 --- a/osu.iOS/Info.plist +++ b/osu.iOS/Info.plist @@ -102,6 +102,19 @@ osz + + UTTypeConformsTo + + sh.ppy.osu.items + + UTTypeIdentifier + sh.ppy.osu.olz + UTTypeTagSpecification + + public.filename-extension + olz + + CFBundleDocumentTypes From 0074bdc5a17be66df7dd656106973829e0909bc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 22 Feb 2024 19:15:02 +0100 Subject: [PATCH 3189/4092] Change `ResultsScreen` constructor boolean params to init-only properties --- .../Background/TestSceneUserDimBackgrounds.cs | 3 ++- .../TestScenePlaylistsResultsScreen.cs | 5 +++-- .../Visual/Ranking/TestSceneResultsScreen.cs | 6 ++++-- osu.Game/OsuGame.cs | 2 +- .../Multiplayer/MultiplayerResultsScreen.cs | 2 +- .../OnlinePlay/Playlists/PlaylistsPlayer.cs | 5 ++++- .../Playlists/PlaylistsResultsScreen.cs | 4 ++-- .../Playlists/PlaylistsRoomSubScreen.cs | 2 +- osu.Game/Screens/Play/Player.cs | 5 +++-- osu.Game/Screens/Play/ReplayPlayer.cs | 2 +- .../Screens/Play/SpectatorResultsScreen.cs | 2 +- osu.Game/Screens/Ranking/ResultsScreen.cs | 19 ++++++++++++------- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 4 ++-- osu.Game/Screens/Select/PlaySongSelect.cs | 2 +- 14 files changed, 38 insertions(+), 25 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index 566ccd6bd5..204dea39b2 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -349,8 +349,9 @@ namespace osu.Game.Tests.Visual.Background private partial class FadeAccessibleResults : ResultsScreen { public FadeAccessibleResults(ScoreInfo score) - : base(score, true) + : base(score) { + AllowRetry = true; } protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index 25ee20b089..fca965052f 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -420,9 +420,10 @@ namespace osu.Game.Tests.Visual.Playlists public new LoadingSpinner RightSpinner => base.RightSpinner; public new ScorePanelList ScorePanelList => base.ScorePanelList; - public TestResultsScreen([CanBeNull] ScoreInfo score, int roomId, PlaylistItem playlistItem, bool allowRetry = true) - : base(score, roomId, playlistItem, allowRetry) + public TestResultsScreen([CanBeNull] ScoreInfo score, int roomId, PlaylistItem playlistItem) + : base(score, roomId, playlistItem) { + AllowRetry = true; } } } diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index cf4bec54ff..ffc5dbc8fb 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -399,8 +399,9 @@ namespace osu.Game.Tests.Visual.Ranking public HotkeyRetryOverlay RetryOverlay; public TestResultsScreen(ScoreInfo score) - : base(score, true) + : base(score) { + AllowRetry = true; ShowUserStatistics = true; } @@ -470,8 +471,9 @@ namespace osu.Game.Tests.Visual.Ranking public HotkeyRetryOverlay RetryOverlay; public UnrankedSoloResultsScreen(ScoreInfo score) - : base(score, true) + : base(score) { + AllowRetry = true; Score!.BeatmapInfo!.OnlineID = 0; Score.BeatmapInfo.Status = BeatmapOnlineStatus.Pending; } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 9ffa88947b..ec44076520 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -768,7 +768,7 @@ namespace osu.Game break; case ScorePresentType.Results: - screen.Push(new SoloResultsScreen(databasedScore.ScoreInfo, false)); + screen.Push(new SoloResultsScreen(databasedScore.ScoreInfo)); break; } }, validScreens: validScreens); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerResultsScreen.cs index f665ed2d41..6ed75508dc 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerResultsScreen.cs @@ -10,7 +10,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer public partial class MultiplayerResultsScreen : PlaylistsResultsScreen { public MultiplayerResultsScreen(ScoreInfo score, long roomId, PlaylistItem playlistItem) - : base(score, roomId, playlistItem, false) + : base(score, roomId, playlistItem) { } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index b0e4585986..74454959a1 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -58,7 +58,10 @@ namespace osu.Game.Screens.OnlinePlay.Playlists protected override ResultsScreen CreateResults(ScoreInfo score) { Debug.Assert(Room.RoomID.Value != null); - return new PlaylistsResultsScreen(score, Room.RoomID.Value.Value, PlaylistItem, true); + return new PlaylistsResultsScreen(score, Room.RoomID.Value.Value, PlaylistItem) + { + AllowRetry = true, + }; } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs index 6a1924dea2..add7aee8cd 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs @@ -41,8 +41,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists [Resolved] private RulesetStore rulesets { get; set; } - public PlaylistsResultsScreen([CanBeNull] ScoreInfo score, long roomId, PlaylistItem playlistItem, bool allowRetry, bool allowWatchingReplay = true) - : base(score, allowRetry, allowWatchingReplay) + public PlaylistsResultsScreen([CanBeNull] ScoreInfo score, long roomId, PlaylistItem playlistItem) + : base(score) { this.roomId = roomId; this.playlistItem = playlistItem; diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 2460f78c96..3fb9de428a 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -114,7 +114,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists RequestResults = item => { Debug.Assert(RoomId.Value != null); - ParentScreen?.Push(new PlaylistsResultsScreen(null, RoomId.Value.Value, item, false)); + ParentScreen?.Push(new PlaylistsResultsScreen(null, RoomId.Value.Value, item)); } } }, diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 11ff5fdbef..8383936f7c 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1224,9 +1224,10 @@ namespace osu.Game.Screens.Play /// /// The to be displayed in the results screen. /// The . - protected virtual ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score, true) + protected virtual ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score) { - ShowUserStatistics = true + AllowRetry = true, + ShowUserStatistics = true, }; private void fadeOut(bool instant = false) diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 3c5b85662a..ff60dbc0d0 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -92,7 +92,7 @@ namespace osu.Game.Screens.Play Scores = { BindTarget = LeaderboardScores } }; - protected override ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score, false); + protected override ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score); public bool OnPressed(KeyBindingPressEvent e) { diff --git a/osu.Game/Screens/Play/SpectatorResultsScreen.cs b/osu.Game/Screens/Play/SpectatorResultsScreen.cs index 393cbddb34..5ee2a5ce0e 100644 --- a/osu.Game/Screens/Play/SpectatorResultsScreen.cs +++ b/osu.Game/Screens/Play/SpectatorResultsScreen.cs @@ -13,7 +13,7 @@ namespace osu.Game.Screens.Play public partial class SpectatorResultsScreen : SoloResultsScreen { public SpectatorResultsScreen(ScoreInfo score) - : base(score, false) + : base(score) { } diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 69cfbed8f2..6beb4d7401 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -63,16 +63,21 @@ namespace osu.Game.Screens.Ranking private bool lastFetchCompleted; - private readonly bool allowRetry; - private readonly bool allowWatchingReplay; + /// + /// Whether the user can retry the beatmap from the results screen. + /// + public bool AllowRetry { get; init; } + + /// + /// Whether the user can watch the replay of the completed play from the results screen. + /// + public bool AllowWatchingReplay { get; init; } = true; private Sample popInSample; - protected ResultsScreen([CanBeNull] ScoreInfo score, bool allowRetry, bool allowWatchingReplay = true) + protected ResultsScreen([CanBeNull] ScoreInfo score) { Score = score; - this.allowRetry = allowRetry; - this.allowWatchingReplay = allowWatchingReplay; SelectedScore.Value = score; } @@ -162,7 +167,7 @@ namespace osu.Game.Screens.Ranking ScorePanelList.AddScore(Score, shouldFlair); } - if (allowWatchingReplay) + if (AllowWatchingReplay) { buttons.Add(new ReplayDownloadButton(SelectedScore.Value) { @@ -171,7 +176,7 @@ namespace osu.Game.Screens.Ranking }); } - if (player != null && allowRetry) + if (player != null && AllowRetry) { buttons.Add(new RetryButton { Width = 300 }); diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index 866440bbd6..38bc13ffb9 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -34,8 +34,8 @@ namespace osu.Game.Screens.Ranking private IBindable latestUpdate = null!; private readonly Bindable statisticsUpdate = new Bindable(); - public SoloResultsScreen(ScoreInfo score, bool allowRetry) - : base(score, allowRetry) + public SoloResultsScreen(ScoreInfo score) + : base(score) { } diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 7b7b8857f3..52f49ba56a 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.Select } protected void PresentScore(ScoreInfo score) => - FinaliseSelection(score.BeatmapInfo, score.Ruleset, () => this.Push(new SoloResultsScreen(score, false))); + FinaliseSelection(score.BeatmapInfo, score.Ruleset, () => this.Push(new SoloResultsScreen(score))); protected override BeatmapDetailArea CreateBeatmapDetailArea() { From 1e53503608aa683a97e3f7f3ca1e9855929c9f94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 22 Feb 2024 19:49:14 +0100 Subject: [PATCH 3190/4092] Show user statistics after completing a playlists / multiplayer score --- .../Ranking/TestSceneStatisticsPanel.cs | 4 +- .../Multiplayer/MultiplayerPlayer.cs | 8 +++- .../OnlinePlay/Playlists/PlaylistsPlayer.cs | 1 + osu.Game/Screens/Ranking/ResultsScreen.cs | 16 ++++++- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 43 ------------------- ...tisticsPanel.cs => UserStatisticsPanel.cs} | 26 +++++++++-- 6 files changed, 46 insertions(+), 52 deletions(-) rename osu.Game/Screens/Ranking/Statistics/{SoloStatisticsPanel.cs => UserStatisticsPanel.cs} (55%) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index d0a45856b2..c7bd52ce8e 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -82,12 +82,12 @@ namespace osu.Game.Tests.Visual.Ranking private void loadPanel(ScoreInfo score) => AddStep("load panel", () => { - Child = new SoloStatisticsPanel(score) + Child = new UserStatisticsPanel(score) { RelativeSizeAxes = Axes.Both, State = { Value = Visibility.Visible }, Score = { Value = score }, - StatisticsUpdate = + DisplayedUserStatisticsUpdate = { Value = new SoloStatisticsUpdate(score, new UserStatistics { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index c5c536eae6..e560c5ca5d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -200,7 +200,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return multiplayerLeaderboard.TeamScores.Count == 2 ? new MultiplayerTeamResultsScreen(score, Room.RoomID.Value.Value, PlaylistItem, multiplayerLeaderboard.TeamScores) - : new MultiplayerResultsScreen(score, Room.RoomID.Value.Value, PlaylistItem); + { + ShowUserStatistics = true, + } + : new MultiplayerResultsScreen(score, Room.RoomID.Value.Value, PlaylistItem) + { + ShowUserStatistics = true + }; } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index 74454959a1..48f63731e1 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -61,6 +61,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists return new PlaylistsResultsScreen(score, Room.RoomID.Value.Value, PlaylistItem) { AllowRetry = true, + ShowUserStatistics = true, }; } diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 6beb4d7401..b63c753721 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -73,6 +73,13 @@ namespace osu.Game.Screens.Ranking /// public bool AllowWatchingReplay { get; init; } = true; + /// + /// Whether the user's personal statistics should be shown on the extended statistics panel + /// after clicking the score panel associated with the being presented. + /// Requires to be present. + /// + public bool ShowUserStatistics { get; init; } + private Sample popInSample; protected ResultsScreen([CanBeNull] ScoreInfo score) @@ -105,7 +112,7 @@ namespace osu.Game.Screens.Ranking RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - StatisticsPanel = CreateStatisticsPanel().With(panel => + StatisticsPanel = createStatisticsPanel().With(panel => { panel.RelativeSizeAxes = Axes.Both; panel.Score.BindTarget = SelectedScore; @@ -243,7 +250,12 @@ namespace osu.Game.Screens.Ranking /// /// Creates the to be used to display extended information about scores. /// - protected virtual StatisticsPanel CreateStatisticsPanel() => new StatisticsPanel(); + private StatisticsPanel createStatisticsPanel() + { + return ShowUserStatistics && Score != null + ? new UserStatisticsPanel(Score) + : new StatisticsPanel(); + } private void fetchScoresCallback(IEnumerable scores) => Schedule(() => { diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index 38bc13ffb9..ee0251b5ac 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -6,70 +6,27 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Online.API.Requests; -using osu.Game.Online.Solo; using osu.Game.Rulesets; using osu.Game.Scoring; -using osu.Game.Screens.Ranking.Statistics; namespace osu.Game.Screens.Ranking { public partial class SoloResultsScreen : ResultsScreen { - /// - /// Whether the user's personal statistics should be shown on the extended statistics panel - /// after clicking the score panel associated with the being presented. - /// - public bool ShowUserStatistics { get; init; } - private GetScoresRequest? getScoreRequest; [Resolved] private RulesetStore rulesets { get; set; } = null!; - private IBindable latestUpdate = null!; - private readonly Bindable statisticsUpdate = new Bindable(); - public SoloResultsScreen(ScoreInfo score) : base(score) { } - [BackgroundDependencyLoader] - private void load(SoloStatisticsWatcher? soloStatisticsWatcher) - { - if (ShowUserStatistics && soloStatisticsWatcher != null) - { - Debug.Assert(Score != null); - - latestUpdate = soloStatisticsWatcher.LatestUpdate.GetBoundCopy(); - latestUpdate.BindValueChanged(update => - { - if (update.NewValue?.Score.MatchesOnlineID(Score) == true) - statisticsUpdate.Value = update.NewValue; - }); - } - } - - protected override StatisticsPanel CreateStatisticsPanel() - { - Debug.Assert(Score != null); - - if (ShowUserStatistics) - { - return new SoloStatisticsPanel(Score) - { - StatisticsUpdate = { BindTarget = statisticsUpdate } - }; - } - - return base.CreateStatisticsPanel(); - } - protected override APIRequest? FetchScores(Action>? scoresCallback) { Debug.Assert(Score != null); diff --git a/osu.Game/Screens/Ranking/Statistics/SoloStatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs similarity index 55% rename from osu.Game/Screens/Ranking/Statistics/SoloStatisticsPanel.cs rename to osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs index 762be61853..280e5ec90f 100644 --- a/osu.Game/Screens/Ranking/Statistics/SoloStatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs @@ -3,25 +3,43 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps; +using osu.Game.Extensions; using osu.Game.Online.Solo; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Statistics.User; namespace osu.Game.Screens.Ranking.Statistics { - public partial class SoloStatisticsPanel : StatisticsPanel + public partial class UserStatisticsPanel : StatisticsPanel { private readonly ScoreInfo achievedScore; - public SoloStatisticsPanel(ScoreInfo achievedScore) + internal readonly Bindable DisplayedUserStatisticsUpdate = new Bindable(); + + private IBindable latestGlobalStatisticsUpdate = null!; + + public UserStatisticsPanel(ScoreInfo achievedScore) { this.achievedScore = achievedScore; } - public Bindable StatisticsUpdate { get; } = new Bindable(); + [BackgroundDependencyLoader] + private void load(SoloStatisticsWatcher? soloStatisticsWatcher) + { + if (soloStatisticsWatcher != null) + { + latestGlobalStatisticsUpdate = soloStatisticsWatcher.LatestUpdate.GetBoundCopy(); + latestGlobalStatisticsUpdate.BindValueChanged(update => + { + if (update.NewValue?.Score.MatchesOnlineID(achievedScore) == true) + DisplayedUserStatisticsUpdate.Value = update.NewValue; + }); + } + } protected override ICollection CreateStatisticItems(ScoreInfo newScore, IBeatmap playableBeatmap) { @@ -37,7 +55,7 @@ namespace osu.Game.Screens.Ranking.Statistics RelativeSizeAxes = Axes.X, Anchor = Anchor.Centre, Origin = Anchor.Centre, - StatisticsUpdate = { BindTarget = StatisticsUpdate } + StatisticsUpdate = { BindTarget = DisplayedUserStatisticsUpdate } })).ToArray(); } From eac4c5f69d6a201ced50c19fd826d8d618c9b7ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 22 Feb 2024 19:50:46 +0100 Subject: [PATCH 3191/4092] Rename `{Solo -> User}StatisticsWatcher` et al. The "solo" prefix is a bit unbecoming now. The updates are not only for solo. --- .../Menus/TestSceneToolbarUserButton.cs | 10 +++++----- .../Online/TestSceneSoloStatisticsWatcher.cs | 20 +++++++++---------- .../Visual/Ranking/TestSceneOverallRanking.cs | 2 +- .../Ranking/TestSceneStatisticsPanel.cs | 2 +- ...sticsUpdate.cs => UserStatisticsUpdate.cs} | 6 +++--- ...icsWatcher.cs => UserStatisticsWatcher.cs} | 8 ++++---- osu.Game/OsuGame.cs | 2 +- .../TransientUserStatisticsUpdateDisplay.cs | 6 +++--- osu.Game/Screens/Play/SubmittingPlayer.cs | 4 ++-- .../Ranking/Statistics/User/OverallRanking.cs | 4 ++-- .../Statistics/User/RankingChangeRow.cs | 4 ++-- .../Ranking/Statistics/UserStatisticsPanel.cs | 6 +++--- 12 files changed, 37 insertions(+), 37 deletions(-) rename osu.Game/Online/Solo/{SoloStatisticsUpdate.cs => UserStatisticsUpdate.cs} (88%) rename osu.Game/Online/Solo/{SoloStatisticsWatcher.cs => UserStatisticsWatcher.cs} (93%) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs index 69fedf4a3a..41ce739c2f 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs @@ -100,7 +100,7 @@ namespace osu.Game.Tests.Visual.Menus AddStep("Gain", () => { var transientUpdateDisplay = this.ChildrenOfType().Single(); - transientUpdateDisplay.LatestUpdate.Value = new SoloStatisticsUpdate( + transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate( new ScoreInfo(), new UserStatistics { @@ -116,7 +116,7 @@ namespace osu.Game.Tests.Visual.Menus AddStep("Loss", () => { var transientUpdateDisplay = this.ChildrenOfType().Single(); - transientUpdateDisplay.LatestUpdate.Value = new SoloStatisticsUpdate( + transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate( new ScoreInfo(), new UserStatistics { @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Menus AddStep("No change", () => { var transientUpdateDisplay = this.ChildrenOfType().Single(); - transientUpdateDisplay.LatestUpdate.Value = new SoloStatisticsUpdate( + transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate( new ScoreInfo(), new UserStatistics { @@ -148,7 +148,7 @@ namespace osu.Game.Tests.Visual.Menus AddStep("Was null", () => { var transientUpdateDisplay = this.ChildrenOfType().Single(); - transientUpdateDisplay.LatestUpdate.Value = new SoloStatisticsUpdate( + transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate( new ScoreInfo(), new UserStatistics { @@ -164,7 +164,7 @@ namespace osu.Game.Tests.Visual.Menus AddStep("Became null", () => { var transientUpdateDisplay = this.ChildrenOfType().Single(); - transientUpdateDisplay.LatestUpdate.Value = new SoloStatisticsUpdate( + transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate( new ScoreInfo(), new UserStatistics { diff --git a/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs b/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs index 19121b7f58..733769b9f3 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Online { protected override bool UseOnlineAPI => false; - private SoloStatisticsWatcher watcher = null!; + private UserStatisticsWatcher watcher = null!; [Resolved] private SpectatorClient spectatorClient { get; set; } = null!; @@ -107,7 +107,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("create watcher", () => { - Child = watcher = new SoloStatisticsWatcher(); + Child = watcher = new UserStatisticsWatcher(); }); } @@ -123,7 +123,7 @@ namespace osu.Game.Tests.Visual.Online var ruleset = new OsuRuleset().RulesetInfo; - SoloStatisticsUpdate? update = null; + UserStatisticsUpdate? update = null; registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate); feignScoreProcessing(userId, ruleset, 5_000_000); @@ -146,7 +146,7 @@ namespace osu.Game.Tests.Visual.Online // note ordering - in this test processing completes *before* the registration is added. feignScoreProcessing(userId, ruleset, 5_000_000); - SoloStatisticsUpdate? update = null; + UserStatisticsUpdate? update = null; registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate); AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId)); @@ -164,7 +164,7 @@ namespace osu.Game.Tests.Visual.Online long scoreId = getScoreId(); var ruleset = new OsuRuleset().RulesetInfo; - SoloStatisticsUpdate? update = null; + UserStatisticsUpdate? update = null; registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate); feignScoreProcessing(userId, ruleset, 5_000_000); @@ -191,7 +191,7 @@ namespace osu.Game.Tests.Visual.Online long scoreId = getScoreId(); var ruleset = new OsuRuleset().RulesetInfo; - SoloStatisticsUpdate? update = null; + UserStatisticsUpdate? update = null; registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate); feignScoreProcessing(userId, ruleset, 5_000_000); @@ -212,7 +212,7 @@ namespace osu.Game.Tests.Visual.Online long scoreId = getScoreId(); var ruleset = new OsuRuleset().RulesetInfo; - SoloStatisticsUpdate? update = null; + UserStatisticsUpdate? update = null; registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate); feignScoreProcessing(userId, ruleset, 5_000_000); @@ -241,7 +241,7 @@ namespace osu.Game.Tests.Visual.Online feignScoreProcessing(userId, ruleset, 6_000_000); - SoloStatisticsUpdate? update = null; + UserStatisticsUpdate? update = null; registerForUpdates(secondScoreId, ruleset, receivedUpdate => update = receivedUpdate); AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, secondScoreId)); @@ -259,7 +259,7 @@ namespace osu.Game.Tests.Visual.Online var ruleset = new OsuRuleset().RulesetInfo; - SoloStatisticsUpdate? update = null; + UserStatisticsUpdate? update = null; registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate); feignScoreProcessing(userId, ruleset, 5_000_000); @@ -289,7 +289,7 @@ namespace osu.Game.Tests.Visual.Online }); } - private void registerForUpdates(long scoreId, RulesetInfo rulesetInfo, Action onUpdateReady) => + private void registerForUpdates(long scoreId, RulesetInfo rulesetInfo, Action onUpdateReady) => AddStep("register for updates", () => { watcher.RegisterForStatisticsUpdateAfter( diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs b/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs index 355a572f95..ee0ab0c880 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs @@ -112,6 +112,6 @@ namespace osu.Game.Tests.Visual.Ranking }); private void displayUpdate(UserStatistics before, UserStatistics after) => - AddStep("display update", () => overallRanking.StatisticsUpdate.Value = new SoloStatisticsUpdate(new ScoreInfo(), before, after)); + AddStep("display update", () => overallRanking.StatisticsUpdate.Value = new UserStatisticsUpdate(new ScoreInfo(), before, after)); } } diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index c7bd52ce8e..ecb8073a88 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.Ranking Score = { Value = score }, DisplayedUserStatisticsUpdate = { - Value = new SoloStatisticsUpdate(score, new UserStatistics + Value = new UserStatisticsUpdate(score, new UserStatistics { Level = new UserStatistics.LevelInfo { diff --git a/osu.Game/Online/Solo/SoloStatisticsUpdate.cs b/osu.Game/Online/Solo/UserStatisticsUpdate.cs similarity index 88% rename from osu.Game/Online/Solo/SoloStatisticsUpdate.cs rename to osu.Game/Online/Solo/UserStatisticsUpdate.cs index cb9dac97c7..03f3abbb66 100644 --- a/osu.Game/Online/Solo/SoloStatisticsUpdate.cs +++ b/osu.Game/Online/Solo/UserStatisticsUpdate.cs @@ -9,7 +9,7 @@ namespace osu.Game.Online.Solo /// /// Contains data about the change in a user's profile statistics after completing a score. /// - public class SoloStatisticsUpdate + public class UserStatisticsUpdate { /// /// The score set by the user that triggered the update. @@ -27,12 +27,12 @@ namespace osu.Game.Online.Solo public UserStatistics After { get; } /// - /// Creates a new . + /// Creates a new . /// /// The score set by the user that triggered the update. /// The user's profile statistics prior to the score being set. /// The user's profile statistics after the score was set. - public SoloStatisticsUpdate(ScoreInfo score, UserStatistics before, UserStatistics after) + public UserStatisticsUpdate(ScoreInfo score, UserStatistics before, UserStatistics after) { Score = score; Before = before; diff --git a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs b/osu.Game/Online/Solo/UserStatisticsWatcher.cs similarity index 93% rename from osu.Game/Online/Solo/SoloStatisticsWatcher.cs rename to osu.Game/Online/Solo/UserStatisticsWatcher.cs index 2072e8633f..2fff92fe0f 100644 --- a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs +++ b/osu.Game/Online/Solo/UserStatisticsWatcher.cs @@ -20,10 +20,10 @@ namespace osu.Game.Online.Solo /// /// A persistent component that binds to the spectator server and API in order to deliver updates about the logged in user's gameplay statistics. /// - public partial class SoloStatisticsWatcher : Component + public partial class UserStatisticsWatcher : Component { - public IBindable LatestUpdate => latestUpdate; - private readonly Bindable latestUpdate = new Bindable(); + public IBindable LatestUpdate => latestUpdate; + private readonly Bindable latestUpdate = new Bindable(); [Resolved] private SpectatorClient spectatorClient { get; set; } = null!; @@ -120,7 +120,7 @@ namespace osu.Game.Online.Solo latestStatistics.TryGetValue(rulesetName, out UserStatistics? latestRulesetStatistics); latestRulesetStatistics ??= new UserStatistics(); - latestUpdate.Value = new SoloStatisticsUpdate(scoreInfo, latestRulesetStatistics, updatedStatistics); + latestUpdate.Value = new UserStatisticsUpdate(scoreInfo, latestRulesetStatistics, updatedStatistics); latestStatistics[rulesetName] = updatedStatistics; } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ec44076520..419e31bacd 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1022,7 +1022,7 @@ namespace osu.Game ScreenStack.Push(CreateLoader().With(l => l.RelativeSizeAxes = Axes.Both)); }); - loadComponentSingleFile(new SoloStatisticsWatcher(), Add, true); + loadComponentSingleFile(new UserStatisticsWatcher(), Add, true); loadComponentSingleFile(Toolbar = new Toolbar { OnHome = delegate diff --git a/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs b/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs index f56a1a3dd2..e7a78f8b80 100644 --- a/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs +++ b/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs @@ -21,13 +21,13 @@ namespace osu.Game.Overlays.Toolbar { public partial class TransientUserStatisticsUpdateDisplay : CompositeDrawable { - public Bindable LatestUpdate { get; } = new Bindable(); + public Bindable LatestUpdate { get; } = new Bindable(); private Statistic globalRank = null!; private Statistic pp = null!; [BackgroundDependencyLoader] - private void load(SoloStatisticsWatcher? soloStatisticsWatcher) + private void load(UserStatisticsWatcher? soloStatisticsWatcher) { RelativeSizeAxes = Axes.Y; AutoSizeAxes = Axes.X; @@ -48,7 +48,7 @@ namespace osu.Game.Overlays.Toolbar }; if (soloStatisticsWatcher != null) - ((IBindable)LatestUpdate).BindTo(soloStatisticsWatcher.LatestUpdate); + ((IBindable)LatestUpdate).BindTo(soloStatisticsWatcher.LatestUpdate); } protected override void LoadComplete() diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index ecb507f382..ee88171789 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -45,7 +45,7 @@ namespace osu.Game.Screens.Play [Resolved(canBeNull: true)] [CanBeNull] - private SoloStatisticsWatcher soloStatisticsWatcher { get; set; } + private UserStatisticsWatcher userStatisticsWatcher { get; set; } private readonly object scoreSubmissionLock = new object(); private TaskCompletionSource scoreSubmissionSource; @@ -189,7 +189,7 @@ namespace osu.Game.Screens.Play await submitScore(score).ConfigureAwait(false); spectatorClient.EndPlaying(GameplayState); - soloStatisticsWatcher?.RegisterForStatisticsUpdateAfter(score.ScoreInfo); + userStatisticsWatcher?.RegisterForStatisticsUpdateAfter(score.ScoreInfo); } [Resolved] diff --git a/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs b/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs index d08a654e99..5c96a2b6c3 100644 --- a/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs +++ b/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Ranking.Statistics.User { private const float transition_duration = 300; - public Bindable StatisticsUpdate { get; } = new Bindable(); + public Bindable StatisticsUpdate { get; } = new Bindable(); private LoadingLayer loadingLayer = null!; private GridContainer content = null!; @@ -86,7 +86,7 @@ namespace osu.Game.Screens.Ranking.Statistics.User FinishTransforms(true); } - private void onUpdateReceived(ValueChangedEvent update) + private void onUpdateReceived(ValueChangedEvent update) { if (update.NewValue == null) { diff --git a/osu.Game/Screens/Ranking/Statistics/User/RankingChangeRow.cs b/osu.Game/Screens/Ranking/Statistics/User/RankingChangeRow.cs index 906bf8d5ca..a477f38cd1 100644 --- a/osu.Game/Screens/Ranking/Statistics/User/RankingChangeRow.cs +++ b/osu.Game/Screens/Ranking/Statistics/User/RankingChangeRow.cs @@ -19,7 +19,7 @@ namespace osu.Game.Screens.Ranking.Statistics.User { public abstract partial class RankingChangeRow : CompositeDrawable { - public Bindable StatisticsUpdate { get; } = new Bindable(); + public Bindable StatisticsUpdate { get; } = new Bindable(); private readonly Func accessor; @@ -113,7 +113,7 @@ namespace osu.Game.Screens.Ranking.Statistics.User StatisticsUpdate.BindValueChanged(onStatisticsUpdate, true); } - private void onStatisticsUpdate(ValueChangedEvent statisticsUpdate) + private void onStatisticsUpdate(ValueChangedEvent statisticsUpdate) { var update = statisticsUpdate.NewValue; diff --git a/osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs index 280e5ec90f..7f8c12ddab 100644 --- a/osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs @@ -18,9 +18,9 @@ namespace osu.Game.Screens.Ranking.Statistics { private readonly ScoreInfo achievedScore; - internal readonly Bindable DisplayedUserStatisticsUpdate = new Bindable(); + internal readonly Bindable DisplayedUserStatisticsUpdate = new Bindable(); - private IBindable latestGlobalStatisticsUpdate = null!; + private IBindable latestGlobalStatisticsUpdate = null!; public UserStatisticsPanel(ScoreInfo achievedScore) { @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Ranking.Statistics } [BackgroundDependencyLoader] - private void load(SoloStatisticsWatcher? soloStatisticsWatcher) + private void load(UserStatisticsWatcher? soloStatisticsWatcher) { if (soloStatisticsWatcher != null) { From d6beae2ce1d34d0efb17eaf9ccc531a72a757ecf Mon Sep 17 00:00:00 2001 From: Brandon Date: Thu, 22 Feb 2024 19:10:15 -0800 Subject: [PATCH 3192/4092] Update delete/restore mod presets message when none --- .../MaintenanceSettingsStrings.cs | 10 +++++++ .../Sections/Maintenance/ModPresetSettings.cs | 27 ++++++++++++++----- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/osu.Game/Localisation/MaintenanceSettingsStrings.cs b/osu.Game/Localisation/MaintenanceSettingsStrings.cs index 469f565f1e..2511e7aecc 100644 --- a/osu.Game/Localisation/MaintenanceSettingsStrings.cs +++ b/osu.Game/Localisation/MaintenanceSettingsStrings.cs @@ -109,11 +109,21 @@ namespace osu.Game.Localisation /// public static LocalisableString DeletedAllModPresets => new TranslatableString(getKey(@"deleted_all_mod_presets"), @"Deleted all mod presets!"); + /// + /// "No mod presets found to delete!" + /// + public static LocalisableString NoModPresetsFoundToDelete => new TranslatableString(getKey(@"no_mod_presets_found_to_delete"), @"No mod presets found to delete!"); + /// /// "Restored all deleted mod presets!" /// public static LocalisableString RestoredAllDeletedModPresets => new TranslatableString(getKey(@"restored_all_deleted_mod_presets"), @"Restored all deleted mod presets!"); + /// + /// "No mod presets found to restore!" + /// + public static LocalisableString NoModPresetsFoundToRestore => new TranslatableString(getKey(@"no_mod_presets_found_to_restore"), @"No mod presets found to restore!"); + /// /// "Please select your osu!stable install location" /// diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/ModPresetSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/ModPresetSettings.cs index ba45d9c896..f0d6d10e51 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/ModPresetSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/ModPresetSettings.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Localisation; using osu.Framework.Logging; @@ -52,36 +53,50 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance }); } - private void deleteAllModPresets() => + private bool deleteAllModPresets() => realm.Write(r => { + bool anyDeleted = false; + foreach (var preset in r.All()) + { + anyDeleted |= !preset.DeletePending; preset.DeletePending = true; + } + + return anyDeleted; }); - private void onAllModPresetsDeleted(Task deletionTask) + private void onAllModPresetsDeleted(Task deletionTask) { deleteAllButton.Enabled.Value = true; if (deletionTask.IsCompletedSuccessfully) - notificationOverlay?.Post(new ProgressCompletionNotification { Text = MaintenanceSettingsStrings.DeletedAllModPresets }); + notificationOverlay?.Post(new ProgressCompletionNotification { Text = deletionTask.GetResultSafely() ? MaintenanceSettingsStrings.DeletedAllModPresets : MaintenanceSettingsStrings.NoModPresetsFoundToDelete }); else if (deletionTask.IsFaulted) Logger.Error(deletionTask.Exception, "Failed to delete all mod presets"); } - private void undeleteModPresets() => + private bool undeleteModPresets() => realm.Write(r => { + bool anyRestored = false; + foreach (var preset in r.All().Where(preset => preset.DeletePending)) + { + anyRestored |= preset.DeletePending; preset.DeletePending = false; + } + + return anyRestored; }); - private void onModPresetsUndeleted(Task undeletionTask) + private void onModPresetsUndeleted(Task undeletionTask) { undeleteButton.Enabled.Value = true; if (undeletionTask.IsCompletedSuccessfully) - notificationOverlay?.Post(new ProgressCompletionNotification { Text = MaintenanceSettingsStrings.RestoredAllDeletedModPresets }); + notificationOverlay?.Post(new ProgressCompletionNotification { Text = undeletionTask.GetResultSafely() ? MaintenanceSettingsStrings.RestoredAllDeletedModPresets : MaintenanceSettingsStrings.NoModPresetsFoundToRestore }); else if (undeletionTask.IsFaulted) Logger.Error(undeletionTask.Exception, "Failed to restore mod presets"); } From 6c8204f9a35cfcc11b4a6fc9003b50dd30e37960 Mon Sep 17 00:00:00 2001 From: Brandon Date: Thu, 22 Feb 2024 19:40:02 -0800 Subject: [PATCH 3193/4092] Update delete collections message when none --- .../Localisation/MaintenanceSettingsStrings.cs | 5 +++++ .../Sections/Maintenance/CollectionsSettings.cs | 17 +++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/osu.Game/Localisation/MaintenanceSettingsStrings.cs b/osu.Game/Localisation/MaintenanceSettingsStrings.cs index 2511e7aecc..2e5f1d29df 100644 --- a/osu.Game/Localisation/MaintenanceSettingsStrings.cs +++ b/osu.Game/Localisation/MaintenanceSettingsStrings.cs @@ -104,6 +104,11 @@ namespace osu.Game.Localisation /// public static LocalisableString DeletedAllCollections => new TranslatableString(getKey(@"deleted_all_collections"), @"Deleted all collections!"); + /// + /// "No collections found to delete!" + /// + public static LocalisableString NoCollectionsFoundToDelete => new TranslatableString(getKey(@"no_collections_found_to_delete"), @"No collections found to delete!"); + /// /// "Deleted all mod presets!" /// diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs index 09acc22c25..b373535a8b 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.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 System.Linq; using osu.Framework.Allocation; using osu.Framework.Localisation; using osu.Game.Collections; @@ -35,8 +36,20 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private void deleteAllCollections() { - realm.Write(r => r.RemoveAll()); - notificationOverlay?.Post(new ProgressCompletionNotification { Text = MaintenanceSettingsStrings.DeletedAllCollections }); + bool anyDeleted = realm.Write(r => + { + if (r.All().Any()) + { + r.RemoveAll(); + return true; + } + else + { + return false; + } + }); + + notificationOverlay?.Post(new ProgressCompletionNotification { Text = anyDeleted ? MaintenanceSettingsStrings.DeletedAllCollections : MaintenanceSettingsStrings.NoCollectionsFoundToDelete }); } } } From 157819c19931b581abf36cbcf3538f70637d970c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Feb 2024 11:23:11 +0800 Subject: [PATCH 3194/4092] Materialise realm collection hashes during song select search process Without this, there's a large overhead to do a realm-live `Contains` search when a collection is selected. This may also help considerably alleviate https://github.com/ppy/osu/discussions/27298#discussioncomment-8552508 as we will be performing the native realm search much less. --- osu.Game/Screens/Select/FilterControl.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 1827eb58ca..6fd22364f6 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -64,7 +65,7 @@ namespace osu.Game.Screens.Select Sort = sortMode.Value, AllowConvertedBeatmaps = showConverted.Value, Ruleset = ruleset.Value, - CollectionBeatmapMD5Hashes = collectionDropdown.Current.Value?.Collection?.PerformRead(c => c.BeatmapMD5Hashes) + CollectionBeatmapMD5Hashes = collectionDropdown.Current.Value?.Collection?.PerformRead(c => c.BeatmapMD5Hashes).ToList() }; if (!minimumStars.IsDefault) From 0113fce02f5888dd775402ed4c15550826a5e999 Mon Sep 17 00:00:00 2001 From: Hivie Date: Fri, 23 Feb 2024 11:27:12 +0100 Subject: [PATCH 3195/4092] Add osu!taiko `Constant Speed` mod --- .../Mods/TaikoModConstantSpeed.cs | 31 +++++++++++++++++++ osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 1 + 2 files changed, 32 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs new file mode 100644 index 0000000000..28de360eee --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs @@ -0,0 +1,31 @@ +// 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.Localisation; +using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Taiko.Beatmaps; + +namespace osu.Game.Rulesets.Taiko.Mods +{ + public class TaikoModConstantSpeed : Mod, IApplicableToBeatmap + { + public override string Name => "Constant Speed"; + public override string Acronym => "CS"; + public override double ScoreMultiplier => 0.8; + public override LocalisableString Description => "No more tricky speed changes!"; + public override IconUsage? Icon => FontAwesome.Solid.Equals; + public override ModType Type => ModType.Conversion; + + public void ApplyToBeatmap(IBeatmap beatmap) + { + var taikoBeatmap = (TaikoBeatmap)beatmap; + + foreach (var effectControlPoint in taikoBeatmap.ControlPointInfo.EffectPoints) + { + effectControlPoint.ScrollSpeed = 1; + } + } + } +} diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 24b0ec5d57..b701d3c25a 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -150,6 +150,7 @@ namespace osu.Game.Rulesets.Taiko new TaikoModClassic(), new TaikoModSwap(), new TaikoModSingleTap(), + new TaikoModConstantSpeed(), }; case ModType.Automation: From 1cbc2f07ab039c54d3788063e5028ab34577937a Mon Sep 17 00:00:00 2001 From: Hivie Date: Fri, 23 Feb 2024 14:01:12 +0100 Subject: [PATCH 3196/4092] use more correct implementation --- .../Mods/TaikoModConstantSpeed.cs | 20 +++++++++---------- .../UI/DrawableTaikoRuleset.cs | 7 ++++++- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs index 28de360eee..4ecb94467e 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs @@ -1,15 +1,17 @@ // 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.Localisation; using osu.Framework.Graphics.Sprites; -using osu.Game.Beatmaps; +using osu.Framework.Localisation; +using osu.Game.Configuration; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.UI; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Taiko.Beatmaps; +using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Taiko.Mods { - public class TaikoModConstantSpeed : Mod, IApplicableToBeatmap + public class TaikoModConstantSpeed : Mod, IApplicableToDrawableRuleset { public override string Name => "Constant Speed"; public override string Acronym => "CS"; @@ -18,14 +20,10 @@ namespace osu.Game.Rulesets.Taiko.Mods public override IconUsage? Icon => FontAwesome.Solid.Equals; public override ModType Type => ModType.Conversion; - public void ApplyToBeatmap(IBeatmap beatmap) + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - var taikoBeatmap = (TaikoBeatmap)beatmap; - - foreach (var effectControlPoint in taikoBeatmap.ControlPointInfo.EffectPoints) - { - effectControlPoint.ScrollSpeed = 1; - } + var taikoRuleset = (DrawableTaikoRuleset)drawableRuleset; + taikoRuleset.VisualisationMethod = ScrollVisualisationMethod.Constant; } } } diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 77b2b06c0e..a476634fb8 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -69,7 +69,12 @@ namespace osu.Game.Rulesets.Taiko.UI TimeRange.Value = ComputeTimeRange(); } - protected virtual double ComputeTimeRange() => PlayfieldAdjustmentContainer.ComputeTimeRange(); + protected virtual double ComputeTimeRange() + { + // Adjust when we're using constant algorithm to not be sluggish. + double multiplier = VisualisationMethod == ScrollVisualisationMethod.Overlapping ? 1 : 4; + return PlayfieldAdjustmentContainer.ComputeTimeRange() / multiplier; + } protected override void UpdateAfterChildren() { From 14b0c41937f39906ddaf86d4919baadf2d5c2174 Mon Sep 17 00:00:00 2001 From: Hivie Date: Fri, 23 Feb 2024 14:22:56 +0100 Subject: [PATCH 3197/4092] remove unnecessary `ComputeTimeRange` override --- osu.Game.Rulesets.Taiko/Edit/DrawableTaikoEditorRuleset.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Edit/DrawableTaikoEditorRuleset.cs b/osu.Game.Rulesets.Taiko/Edit/DrawableTaikoEditorRuleset.cs index 3c7a97c864..217bb8139c 100644 --- a/osu.Game.Rulesets.Taiko/Edit/DrawableTaikoEditorRuleset.cs +++ b/osu.Game.Rulesets.Taiko/Edit/DrawableTaikoEditorRuleset.cs @@ -26,12 +26,5 @@ namespace osu.Game.Rulesets.Taiko.Edit ShowSpeedChanges.BindValueChanged(showChanges => VisualisationMethod = showChanges.NewValue ? ScrollVisualisationMethod.Overlapping : ScrollVisualisationMethod.Constant, true); } - - protected override double ComputeTimeRange() - { - // Adjust when we're using constant algorithm to not be sluggish. - double multiplier = ShowSpeedChanges.Value ? 1 : 4; - return base.ComputeTimeRange() / multiplier; - } } } From 7762d2469b59c4a349d5a05119f0c7a007cd0c5e Mon Sep 17 00:00:00 2001 From: Hivie Date: Fri, 23 Feb 2024 14:24:26 +0100 Subject: [PATCH 3198/4092] exclude EZ/HR for now --- osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs index 4ecb94467e..117dc0ebd2 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs @@ -1,6 +1,8 @@ // 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.Linq; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Configuration; @@ -19,6 +21,7 @@ namespace osu.Game.Rulesets.Taiko.Mods public override LocalisableString Description => "No more tricky speed changes!"; public override IconUsage? Icon => FontAwesome.Solid.Equals; public override ModType Type => ModType.Conversion; + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(TaikoModEasy), typeof(TaikoModHardRock) }).ToArray(); public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { From d1d32fc16cf6474ec8661c83dbd6bd282cff172b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 23 Feb 2024 14:49:46 +0100 Subject: [PATCH 3199/4092] Fix editor displaying combo colours in effectively incorrect order Addresses https://github.com/ppy/osu/discussions/27316. Stable lies about the first combo colour being first; in the `.osu` file it is actually second. It does a thing in editor itself to correct for this. https://github.com/peppy/osu-stable-reference/blob/master/osu!/GameModes/Edit/Forms/SongSetup.cs#L233-L234 --- osu.Game/Screens/Edit/EditorBeatmapSkin.cs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorBeatmapSkin.cs b/osu.Game/Screens/Edit/EditorBeatmapSkin.cs index 80239504d8..71530ee5bc 100644 --- a/osu.Game/Screens/Edit/EditorBeatmapSkin.cs +++ b/osu.Game/Screens/Edit/EditorBeatmapSkin.cs @@ -4,7 +4,7 @@ #nullable disable using System; -using System.Linq; +using System.Collections.Generic; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -38,8 +38,17 @@ namespace osu.Game.Screens.Edit Skin = skin; ComboColours = new BindableList(); - if (Skin.Configuration.ComboColours != null) - ComboColours.AddRange(Skin.Configuration.ComboColours.Select(c => (Colour4)c)); + + if (Skin.Configuration.ComboColours is IReadOnlyList comboColours) + { + // due to the foibles of how `IHasComboInformation` / `ComboIndexWithOffsets` work, + // the actual effective first combo colour that will be used on the beatmap is the one with index 1, not 0. + // see also: `IHasComboInformation.UpdateComboInformation`, + // https://github.com/peppy/osu-stable-reference/blob/46cd3a10af7cc6cc96f4eba92ef1812dc8c3a27e/osu!/GameModes/Edit/Forms/SongSetup.cs#L233-L234. + for (int i = 0; i < comboColours.Count; ++i) + ComboColours.Add(comboColours[(i + 1) % comboColours.Count]); + } + ComboColours.BindCollectionChanged((_, _) => updateColours()); } @@ -47,7 +56,10 @@ namespace osu.Game.Screens.Edit private void updateColours() { - Skin.Configuration.CustomComboColours = ComboColours.Select(c => (Color4)c).ToList(); + // performs the inverse of the index rotation operation described in the ctor. + Skin.Configuration.CustomComboColours.Clear(); + for (int i = 0; i < ComboColours.Count; ++i) + Skin.Configuration.CustomComboColours.Add(ComboColours[(ComboColours.Count + i - 1) % ComboColours.Count]); invokeSkinChanged(); } From ae9c58be3030cb5c0b3c0148446afbf8edf452ac Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 23 Feb 2024 16:42:58 +0300 Subject: [PATCH 3200/4092] Remove "multiplayer" references from subclass and move to appropriate place --- .../OnlinePlay/Match/RoomModSelectOverlay.cs} | 24 ++++++++++--------- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 2 +- 2 files changed, 14 insertions(+), 12 deletions(-) rename osu.Game/{Overlays/Mods/MultiplayerModSelectOverlay.cs => Screens/OnlinePlay/Match/RoomModSelectOverlay.cs} (72%) diff --git a/osu.Game/Overlays/Mods/MultiplayerModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs similarity index 72% rename from osu.Game/Overlays/Mods/MultiplayerModSelectOverlay.cs rename to osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs index ee087bb149..00704b7ec7 100644 --- a/osu.Game/Overlays/Mods/MultiplayerModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// 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; @@ -6,26 +6,28 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Rulesets; using osu.Game.Online.Rooms; +using osu.Game.Overlays; +using osu.Game.Overlays.Mods; +using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -namespace osu.Game.Overlays.Mods +namespace osu.Game.Screens.OnlinePlay.Match { - public partial class MultiplayerModSelectOverlay : UserModSelectOverlay + public partial class RoomModSelectOverlay : UserModSelectOverlay { - public MultiplayerModSelectOverlay(OverlayColourScheme colourScheme = OverlayColourScheme.Plum) + public RoomModSelectOverlay(OverlayColourScheme colourScheme = OverlayColourScheme.Plum) : base(colourScheme) { } [Resolved(CanBeNull = true)] - private IBindable? multiplayerRoomItem { get; set; } + private IBindable? selectedItem { get; set; } [Resolved] private OsuGameBase game { get; set; } = null!; - protected override BeatmapAttributesDisplay GetBeatmapAttributesDisplay => new MultiplayerBeatmapAttributesDisplay + protected override BeatmapAttributesDisplay GetBeatmapAttributesDisplay => new RoomBeatmapAttributesDisplay { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, @@ -35,7 +37,7 @@ namespace osu.Game.Overlays.Mods protected override void LoadComplete() { base.LoadComplete(); - multiplayerRoomItem?.BindValueChanged(_ => SelectedMods.TriggerChange()); + selectedItem?.BindValueChanged(_ => SelectedMods.TriggerChange()); } protected override IEnumerable AllSelectedMods @@ -44,10 +46,10 @@ namespace osu.Game.Overlays.Mods { IEnumerable allMods = SelectedMods.Value; - if (multiplayerRoomItem?.Value != null) + if (selectedItem?.Value != null) { Ruleset ruleset = game.Ruleset.Value.CreateInstance(); - var multiplayerRoomMods = multiplayerRoomItem.Value.RequiredMods.Select(m => m.ToMod(ruleset)); + var multiplayerRoomMods = selectedItem.Value.RequiredMods.Select(m => m.ToMod(ruleset)); allMods = allMods.Concat(multiplayerRoomMods); } @@ -56,7 +58,7 @@ namespace osu.Game.Overlays.Mods } } - public partial class MultiplayerBeatmapAttributesDisplay : BeatmapAttributesDisplay + public partial class RoomBeatmapAttributesDisplay : BeatmapAttributesDisplay { [Resolved(CanBeNull = true)] private IBindable? multiplayerRoomItem { get; set; } diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index b20760b114..97fbb83992 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -241,7 +241,7 @@ namespace osu.Game.Screens.OnlinePlay.Match } }; - LoadComponent(UserModsSelectOverlay = new MultiplayerModSelectOverlay + LoadComponent(UserModsSelectOverlay = new RoomModSelectOverlay { SelectedMods = { BindTarget = UserMods }, IsValidMod = _ => false From f94cd4483cb4f4b0a4b8cd189975ccca92f54720 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 23 Feb 2024 16:47:37 +0300 Subject: [PATCH 3201/4092] Avoid relying on game-wide ruleset bindable --- .../OnlinePlay/Match/RoomModSelectOverlay.cs | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs index 00704b7ec7..e6ff2260e9 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -25,7 +26,7 @@ namespace osu.Game.Screens.OnlinePlay.Match private IBindable? selectedItem { get; set; } [Resolved] - private OsuGameBase game { get; set; } = null!; + private RulesetStore rulesets { get; set; } = null!; protected override BeatmapAttributesDisplay GetBeatmapAttributesDisplay => new RoomBeatmapAttributesDisplay { @@ -48,9 +49,9 @@ namespace osu.Game.Screens.OnlinePlay.Match if (selectedItem?.Value != null) { - Ruleset ruleset = game.Ruleset.Value.CreateInstance(); - var multiplayerRoomMods = selectedItem.Value.RequiredMods.Select(m => m.ToMod(ruleset)); - allMods = allMods.Concat(multiplayerRoomMods); + var rulesetInstance = rulesets.GetRuleset(selectedItem.Value.RulesetID)?.CreateInstance(); + Debug.Assert(rulesetInstance != null); + allMods = allMods.Concat(selectedItem.Value.RequiredMods.Select(m => m.ToMod(rulesetInstance))); } return allMods; @@ -61,12 +62,15 @@ namespace osu.Game.Screens.OnlinePlay.Match public partial class RoomBeatmapAttributesDisplay : BeatmapAttributesDisplay { [Resolved(CanBeNull = true)] - private IBindable? multiplayerRoomItem { get; set; } + private IBindable? selectedItem { get; set; } + + [Resolved] + private RulesetStore rulesets { get; set; } = null!; protected override void LoadComplete() { base.LoadComplete(); - multiplayerRoomItem?.BindValueChanged(_ => Mods.TriggerChange()); + selectedItem?.BindValueChanged(_ => Mods.TriggerChange()); } protected override IEnumerable SelectedMods @@ -75,11 +79,11 @@ namespace osu.Game.Screens.OnlinePlay.Match { IEnumerable selectedMods = Mods.Value; - if (multiplayerRoomItem?.Value != null) + if (selectedItem?.Value != null) { - Ruleset ruleset = GameRuleset.Value.CreateInstance(); - var multiplayerRoomMods = multiplayerRoomItem.Value.RequiredMods.Select(m => m.ToMod(ruleset)); - selectedMods = selectedMods.Concat(multiplayerRoomMods); + var rulesetInstance = rulesets.GetRuleset(selectedItem.Value.RulesetID)?.CreateInstance(); + Debug.Assert(rulesetInstance != null); + selectedMods = selectedMods.Concat(selectedItem.Value.RequiredMods.Select(m => m.ToMod(rulesetInstance))); } return selectedMods; From f86b7f0702790bd21522f86501ca9a19e771d018 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 23 Feb 2024 14:52:44 +0100 Subject: [PATCH 3202/4092] Enable NRT in `EditorBeatmapSkin` --- osu.Game/Screens/Edit/EditorBeatmapSkin.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorBeatmapSkin.cs b/osu.Game/Screens/Edit/EditorBeatmapSkin.cs index 71530ee5bc..07fa1cb49c 100644 --- a/osu.Game/Screens/Edit/EditorBeatmapSkin.cs +++ b/osu.Game/Screens/Edit/EditorBeatmapSkin.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using osu.Framework.Audio.Sample; @@ -20,7 +18,7 @@ namespace osu.Game.Screens.Edit /// public class EditorBeatmapSkin : ISkin { - public event Action BeatmapSkinChanged; + public event Action? BeatmapSkinChanged; /// /// The underlying beatmap skin. @@ -65,10 +63,14 @@ namespace osu.Game.Screens.Edit #region Delegated ISkin implementation - public Drawable GetDrawableComponent(ISkinComponentLookup lookup) => Skin.GetDrawableComponent(lookup); - public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => Skin.GetTexture(componentName, wrapModeS, wrapModeT); - public ISample GetSample(ISampleInfo sampleInfo) => Skin.GetSample(sampleInfo); - public IBindable GetConfig(TLookup lookup) => Skin.GetConfig(lookup); + public Drawable? GetDrawableComponent(ISkinComponentLookup lookup) => Skin.GetDrawableComponent(lookup); + public Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => Skin.GetTexture(componentName, wrapModeS, wrapModeT); + public ISample? GetSample(ISampleInfo sampleInfo) => Skin.GetSample(sampleInfo); + + public IBindable? GetConfig(TLookup lookup) + where TLookup : notnull + where TValue : notnull + => Skin.GetConfig(lookup); #endregion } From 918577d530c0fa27fc58f2677a731df23b20d230 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 23 Feb 2024 16:51:52 +0300 Subject: [PATCH 3203/4092] Compute required mods list once per update --- .../OnlinePlay/Match/RoomModSelectOverlay.cs | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs index e6ff2260e9..5406912f49 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs @@ -35,28 +35,28 @@ namespace osu.Game.Screens.OnlinePlay.Match BeatmapInfo = { Value = Beatmap?.BeatmapInfo } }; + private readonly List roomMods = new List(); + protected override void LoadComplete() { base.LoadComplete(); - selectedItem?.BindValueChanged(_ => SelectedMods.TriggerChange()); - } - protected override IEnumerable AllSelectedMods - { - get + selectedItem?.BindValueChanged(_ => { - IEnumerable allMods = SelectedMods.Value; + roomMods.Clear(); if (selectedItem?.Value != null) { var rulesetInstance = rulesets.GetRuleset(selectedItem.Value.RulesetID)?.CreateInstance(); Debug.Assert(rulesetInstance != null); - allMods = allMods.Concat(selectedItem.Value.RequiredMods.Select(m => m.ToMod(rulesetInstance))); + roomMods.AddRange(selectedItem.Value.RequiredMods.Select(m => m.ToMod(rulesetInstance))); } - return allMods; - } + SelectedMods.TriggerChange(); + }); } + + protected override IEnumerable AllSelectedMods => roomMods.Concat(base.AllSelectedMods); } public partial class RoomBeatmapAttributesDisplay : BeatmapAttributesDisplay @@ -67,27 +67,27 @@ namespace osu.Game.Screens.OnlinePlay.Match [Resolved] private RulesetStore rulesets { get; set; } = null!; + private readonly List roomMods = new List(); + protected override void LoadComplete() { base.LoadComplete(); - selectedItem?.BindValueChanged(_ => Mods.TriggerChange()); - } - protected override IEnumerable SelectedMods - { - get + selectedItem?.BindValueChanged(_ => { - IEnumerable selectedMods = Mods.Value; + roomMods.Clear(); if (selectedItem?.Value != null) { var rulesetInstance = rulesets.GetRuleset(selectedItem.Value.RulesetID)?.CreateInstance(); Debug.Assert(rulesetInstance != null); - selectedMods = selectedMods.Concat(selectedItem.Value.RequiredMods.Select(m => m.ToMod(rulesetInstance))); + roomMods.AddRange(selectedItem.Value.RequiredMods.Select(m => m.ToMod(rulesetInstance))); } - return selectedMods; - } + Mods.TriggerChange(); + }); } + + protected override IEnumerable SelectedMods => roomMods.Concat(base.SelectedMods); } } From 323d7f8e2dabbd090c29b51926338cece1a94f64 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 23 Feb 2024 16:59:08 +0300 Subject: [PATCH 3204/4092] Change `BeatmapAttributesDisplay` retrieval to proper method --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 16 +++--- .../OnlinePlay/Match/RoomModSelectOverlay.cs | 56 +++++++++---------- 2 files changed, 33 insertions(+), 39 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 8a3b1954ed..99981c28d5 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -130,13 +130,6 @@ namespace osu.Game.Overlays.Mods private RankingInformationDisplay? rankingInformationDisplay; private BeatmapAttributesDisplay? beatmapAttributesDisplay; - protected virtual BeatmapAttributesDisplay GetBeatmapAttributesDisplay => new BeatmapAttributesDisplay - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - BeatmapInfo = { Value = Beatmap?.BeatmapInfo } - }; - protected ShearedButton BackButton { get; private set; } = null!; protected ShearedToggleButton? CustomisationButton { get; private set; } protected SelectAllModsButton? SelectAllModsButton { get; set; } @@ -283,7 +276,12 @@ namespace osu.Game.Overlays.Mods Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight }, - beatmapAttributesDisplay = GetBeatmapAttributesDisplay + beatmapAttributesDisplay = CreateBeatmapAttributesDisplay().With(b => + { + b.Anchor = Anchor.BottomRight; + b.Origin = Anchor.BottomRight; + b.BeatmapInfo.Value = Beatmap?.BeatmapInfo; + }), } }); } @@ -293,6 +291,8 @@ namespace osu.Game.Overlays.Mods textSearchStartsActive = configManager.GetBindable(OsuSetting.ModSelectTextSearchStartsActive); } + protected virtual BeatmapAttributesDisplay CreateBeatmapAttributesDisplay() => new BeatmapAttributesDisplay(); + public override void Hide() { base.Hide(); diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs index 5406912f49..4bf8e22d4e 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs @@ -6,7 +6,6 @@ using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Graphics; using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Overlays.Mods; @@ -28,13 +27,6 @@ namespace osu.Game.Screens.OnlinePlay.Match [Resolved] private RulesetStore rulesets { get; set; } = null!; - protected override BeatmapAttributesDisplay GetBeatmapAttributesDisplay => new RoomBeatmapAttributesDisplay - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - BeatmapInfo = { Value = Beatmap?.BeatmapInfo } - }; - private readonly List roomMods = new List(); protected override void LoadComplete() @@ -57,37 +49,39 @@ namespace osu.Game.Screens.OnlinePlay.Match } protected override IEnumerable AllSelectedMods => roomMods.Concat(base.AllSelectedMods); - } - public partial class RoomBeatmapAttributesDisplay : BeatmapAttributesDisplay - { - [Resolved(CanBeNull = true)] - private IBindable? selectedItem { get; set; } + protected override BeatmapAttributesDisplay CreateBeatmapAttributesDisplay() => new RoomBeatmapAttributesDisplay(); - [Resolved] - private RulesetStore rulesets { get; set; } = null!; - - private readonly List roomMods = new List(); - - protected override void LoadComplete() + private partial class RoomBeatmapAttributesDisplay : BeatmapAttributesDisplay { - base.LoadComplete(); + [Resolved(CanBeNull = true)] + private IBindable? selectedItem { get; set; } - selectedItem?.BindValueChanged(_ => + [Resolved] + private RulesetStore rulesets { get; set; } = null!; + + private readonly List roomMods = new List(); + + protected override void LoadComplete() { - roomMods.Clear(); + base.LoadComplete(); - if (selectedItem?.Value != null) + selectedItem?.BindValueChanged(_ => { - var rulesetInstance = rulesets.GetRuleset(selectedItem.Value.RulesetID)?.CreateInstance(); - Debug.Assert(rulesetInstance != null); - roomMods.AddRange(selectedItem.Value.RequiredMods.Select(m => m.ToMod(rulesetInstance))); - } + roomMods.Clear(); - Mods.TriggerChange(); - }); + if (selectedItem?.Value != null) + { + var rulesetInstance = rulesets.GetRuleset(selectedItem.Value.RulesetID)?.CreateInstance(); + Debug.Assert(rulesetInstance != null); + roomMods.AddRange(selectedItem.Value.RequiredMods.Select(m => m.ToMod(rulesetInstance))); + } + + Mods.TriggerChange(); + }); + } + + protected override IEnumerable SelectedMods => roomMods.Concat(base.SelectedMods); } - - protected override IEnumerable SelectedMods => roomMods.Concat(base.SelectedMods); } } From 9ce07a96b2f98358050eacb4d72767fc1dede2cc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 23 Feb 2024 17:28:54 +0300 Subject: [PATCH 3205/4092] Rewrite mods flow and remove `RoomBeatmapAttributesDisplay` --- .../Overlays/Mods/BeatmapAttributesDisplay.cs | 26 ++++--------- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 34 +++++++++++------ .../OnlinePlay/Match/RoomModSelectOverlay.cs | 37 +------------------ 3 files changed, 31 insertions(+), 66 deletions(-) diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index 7c3c971d76..8f84b51127 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -13,7 +13,6 @@ using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; -using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -39,15 +38,10 @@ namespace osu.Game.Overlays.Mods public Bindable BeatmapInfo { get; } = new Bindable(); - [Resolved] - protected Bindable> Mods { get; private set; } = null!; - - protected virtual IEnumerable SelectedMods => Mods.Value; + public Bindable> Mods { get; } = new Bindable>(); public BindableBool Collapsed { get; } = new BindableBool(true); - private ModSettingChangeTracker? modSettingChangeTracker; - [Resolved] private BeatmapDifficultyCache difficultyCache { get; set; } = null!; @@ -102,15 +96,8 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); - Mods.BindValueChanged(_ => - { - modSettingChangeTracker?.Dispose(); - modSettingChangeTracker = new ModSettingChangeTracker(Mods.Value); - modSettingChangeTracker.SettingChanged += _ => updateValues(); - updateValues(); - }, true); - - BeatmapInfo.BindValueChanged(_ => updateValues(), true); + Mods.BindValueChanged(_ => updateValues()); + BeatmapInfo.BindValueChanged(_ => updateValues()); Collapsed.BindValueChanged(_ => { @@ -122,8 +109,9 @@ namespace osu.Game.Overlays.Mods GameRuleset = game.Ruleset.GetBoundCopy(); GameRuleset.BindValueChanged(_ => updateValues()); - BeatmapInfo.BindValueChanged(_ => updateValues(), true); + BeatmapInfo.BindValueChanged(_ => updateValues()); + updateValues(); updateCollapsedState(); } @@ -167,14 +155,14 @@ namespace osu.Game.Overlays.Mods }); double rate = 1; - foreach (var mod in SelectedMods.OfType()) + foreach (var mod in Mods.Value.OfType()) rate = mod.ApplyToRate(0, rate); bpmDisplay.Current.Value = BeatmapInfo.Value.BPM * rate; BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(BeatmapInfo.Value.Difficulty); - foreach (var mod in SelectedMods.OfType()) + foreach (var mod in Mods.Value.OfType()) mod.ApplyToDifficulty(originalDifficulty); Ruleset ruleset = GameRuleset.Value.CreateInstance(); diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 99981c28d5..fa87b0aa0d 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -114,8 +114,6 @@ namespace osu.Game.Overlays.Mods public IEnumerable AllAvailableMods => AvailableMods.Value.SelectMany(pair => pair.Value); - protected virtual IEnumerable AllSelectedMods => SelectedMods.Value; - private readonly BindableBool customisationVisible = new BindableBool(); private Bindable textSearchStartsActive = null!; @@ -319,7 +317,7 @@ namespace osu.Game.Overlays.Mods SelectedMods.BindValueChanged(_ => { - updateRankingInformation(); + UpdateOverlayInformation(SelectedMods.Value); updateFromExternalSelection(); updateCustomisation(); @@ -332,7 +330,7 @@ namespace osu.Game.Overlays.Mods // // See https://github.com/ppy/osu/pull/23284#issuecomment-1529056988 modSettingChangeTracker = new ModSettingChangeTracker(SelectedMods.Value); - modSettingChangeTracker.SettingChanged += _ => updateRankingInformation(); + modSettingChangeTracker.SettingChanged += _ => UpdateOverlayInformation(SelectedMods.Value); } }, true); @@ -454,18 +452,30 @@ namespace osu.Game.Overlays.Mods modState.ValidForSelection.Value = modState.Mod.Type != ModType.System && modState.Mod.HasImplementation && IsValidMod.Invoke(modState.Mod); } - private void updateRankingInformation() + /// + /// Updates any information displayed on the overlay regarding the effects of the selected mods. + /// + /// The list of mods to show effect from. This can be overriden to include effect of mods that are not part of the bindable (e.g. room mods in multiplayer/playlists). + protected virtual void UpdateOverlayInformation(IReadOnlyList mods) { - if (rankingInformationDisplay == null) - return; + if (rankingInformationDisplay != null) + { + double multiplier = 1.0; - double multiplier = 1.0; + foreach (var mod in mods) + multiplier *= mod.ScoreMultiplier; - foreach (var mod in AllSelectedMods) - multiplier *= mod.ScoreMultiplier; + rankingInformationDisplay.ModMultiplier.Value = multiplier; + rankingInformationDisplay.Ranked.Value = mods.All(m => m.Ranked); + } - rankingInformationDisplay.ModMultiplier.Value = multiplier; - rankingInformationDisplay.Ranked.Value = AllSelectedMods.All(m => m.Ranked); + if (beatmapAttributesDisplay != null) + { + if (!ReferenceEquals(beatmapAttributesDisplay.Mods.Value, mods)) + beatmapAttributesDisplay.Mods.Value = mods; + else + beatmapAttributesDisplay.Mods.TriggerChange(); // mods list may be same but a mod setting has changed, trigger change in that case. + } } private void updateCustomisation() diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs index 4bf8e22d4e..dd0ecbad90 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs @@ -48,40 +48,7 @@ namespace osu.Game.Screens.OnlinePlay.Match }); } - protected override IEnumerable AllSelectedMods => roomMods.Concat(base.AllSelectedMods); - - protected override BeatmapAttributesDisplay CreateBeatmapAttributesDisplay() => new RoomBeatmapAttributesDisplay(); - - private partial class RoomBeatmapAttributesDisplay : BeatmapAttributesDisplay - { - [Resolved(CanBeNull = true)] - private IBindable? selectedItem { get; set; } - - [Resolved] - private RulesetStore rulesets { get; set; } = null!; - - private readonly List roomMods = new List(); - - protected override void LoadComplete() - { - base.LoadComplete(); - - selectedItem?.BindValueChanged(_ => - { - roomMods.Clear(); - - if (selectedItem?.Value != null) - { - var rulesetInstance = rulesets.GetRuleset(selectedItem.Value.RulesetID)?.CreateInstance(); - Debug.Assert(rulesetInstance != null); - roomMods.AddRange(selectedItem.Value.RequiredMods.Select(m => m.ToMod(rulesetInstance))); - } - - Mods.TriggerChange(); - }); - } - - protected override IEnumerable SelectedMods => roomMods.Concat(base.SelectedMods); - } + protected override void UpdateOverlayInformation(IReadOnlyList mods) + => base.UpdateOverlayInformation(roomMods.Concat(mods).ToList()); } } From fdc0636554ca83daf3762c229feee047f34966b5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 23 Feb 2024 17:30:31 +0300 Subject: [PATCH 3206/4092] General code cleanup --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 12 +++++----- .../OnlinePlay/Match/RoomModSelectOverlay.cs | 22 +++++++++---------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index fa87b0aa0d..bf43dc3d9c 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -274,12 +274,12 @@ namespace osu.Game.Overlays.Mods Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight }, - beatmapAttributesDisplay = CreateBeatmapAttributesDisplay().With(b => + beatmapAttributesDisplay = new BeatmapAttributesDisplay { - b.Anchor = Anchor.BottomRight; - b.Origin = Anchor.BottomRight; - b.BeatmapInfo.Value = Beatmap?.BeatmapInfo; - }), + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + BeatmapInfo = { Value = Beatmap?.BeatmapInfo }, + }, } }); } @@ -289,8 +289,6 @@ namespace osu.Game.Overlays.Mods textSearchStartsActive = configManager.GetBindable(OsuSetting.ModSelectTextSearchStartsActive); } - protected virtual BeatmapAttributesDisplay CreateBeatmapAttributesDisplay() => new BeatmapAttributesDisplay(); - public override void Hide() { base.Hide(); diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs index dd0ecbad90..db7cfe980c 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs @@ -16,32 +16,32 @@ namespace osu.Game.Screens.OnlinePlay.Match { public partial class RoomModSelectOverlay : UserModSelectOverlay { - public RoomModSelectOverlay(OverlayColourScheme colourScheme = OverlayColourScheme.Plum) - : base(colourScheme) - { - } - - [Resolved(CanBeNull = true)] - private IBindable? selectedItem { get; set; } + [Resolved] + private IBindable selectedItem { get; set; } = null!; [Resolved] private RulesetStore rulesets { get; set; } = null!; private readonly List roomMods = new List(); + public RoomModSelectOverlay() + : base(OverlayColourScheme.Plum) + { + } + protected override void LoadComplete() { base.LoadComplete(); - selectedItem?.BindValueChanged(_ => + selectedItem.BindValueChanged(_ => { roomMods.Clear(); - if (selectedItem?.Value != null) + if (selectedItem.Value is PlaylistItem item) { - var rulesetInstance = rulesets.GetRuleset(selectedItem.Value.RulesetID)?.CreateInstance(); + var rulesetInstance = rulesets.GetRuleset(item.RulesetID)?.CreateInstance(); Debug.Assert(rulesetInstance != null); - roomMods.AddRange(selectedItem.Value.RequiredMods.Select(m => m.ToMod(rulesetInstance))); + roomMods.AddRange(item.RequiredMods.Select(m => m.ToMod(rulesetInstance))); } SelectedMods.TriggerChange(); From 869f0a82de4272806eda4350944c7fd989f52e62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 23 Feb 2024 15:38:52 +0100 Subject: [PATCH 3207/4092] Use hashset for faster lookup --- osu.Game/Screens/Select/FilterControl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 6fd22364f6..17297c9ebf 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -4,7 +4,7 @@ #nullable disable using System; -using System.Linq; +using System.Collections.Immutable; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -65,7 +65,7 @@ namespace osu.Game.Screens.Select Sort = sortMode.Value, AllowConvertedBeatmaps = showConverted.Value, Ruleset = ruleset.Value, - CollectionBeatmapMD5Hashes = collectionDropdown.Current.Value?.Collection?.PerformRead(c => c.BeatmapMD5Hashes).ToList() + CollectionBeatmapMD5Hashes = collectionDropdown.Current.Value?.Collection?.PerformRead(c => c.BeatmapMD5Hashes).ToImmutableHashSet() }; if (!minimumStars.IsDefault) From c1db9d7819496e52db88223134d642d6cba6f0d3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 23 Feb 2024 18:16:35 +0300 Subject: [PATCH 3208/4092] Add test coverage --- .../TestSceneMultiplayerMatchSubScreen.cs | 30 +++++++++++++++++++ .../Overlays/Mods/BeatmapAttributesDisplay.cs | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index a41eff067b..4bedc31f38 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -286,6 +286,36 @@ namespace osu.Game.Tests.Visual.Multiplayer }); } + [Test] + [FlakyTest] // See above + public void TestModSelectOverlay() + { + AddStep("add playlist item", () => + { + SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, + RequiredMods = new[] + { + new APIMod(new OsuModDoubleTime { SpeedChange = { Value = 2.0 } }), + new APIMod(new OsuModStrictTracking()), + }, + AllowedMods = new[] + { + new APIMod(new OsuModFlashlight()), + new APIMod(new OsuModHardRock()), + } + }); + }); + ClickButtonWhenEnabled(); + + AddUntilStep("wait for join", () => RoomJoined); + + ClickButtonWhenEnabled(); + AddAssert("mod select shows unranked", () => this.ChildrenOfType().Single().Ranked.Value == false); + AddAssert("mod select shows different multiplier", () => !this.ChildrenOfType().Single().ModMultiplier.IsDefault); + } + private partial class TestMultiplayerMatchSubScreen : MultiplayerMatchSubScreen { [Resolved(canBeNull: true)] diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index 8f84b51127..472fe6f476 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -184,7 +184,7 @@ namespace osu.Game.Overlays.Mods RightContent.FadeTo(Collapsed.Value && !IsHovered ? 0 : 1, transition_duration, Easing.OutQuint); } - private partial class BPMDisplay : RollingCounter + public partial class BPMDisplay : RollingCounter { protected override double RollingDuration => 250; From 31dabaefaa95dfb04cebe69a25b3965ae549201d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 23 Feb 2024 18:21:11 +0300 Subject: [PATCH 3209/4092] Reduce smoke allocations --- osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs b/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs index 9838cb2c37..f4fe42b8de 100644 --- a/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs +++ b/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -228,7 +227,9 @@ namespace osu.Game.Rulesets.Osu.Skinning int futurePointIndex = ~Source.SmokePoints.BinarySearch(new SmokePoint { Time = CurrentTime }, new SmokePoint.UpperBoundComparer()); points.Clear(); - points.AddRange(Source.SmokePoints.Skip(firstVisiblePointIndex).Take(futurePointIndex - firstVisiblePointIndex)); + + for (int i = firstVisiblePointIndex; i < futurePointIndex; i++) + points.Add(Source.SmokePoints[i]); } protected sealed override void Draw(IRenderer renderer) From 771cdf9cd6eb12de88691ec9cddfecd52e586c88 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 23 Feb 2024 18:30:14 +0300 Subject: [PATCH 3210/4092] Fix `TestSceneModEffectPreviewPanel` --- .../Visual/UserInterface/TestSceneModEffectPreviewPanel.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModEffectPreviewPanel.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModEffectPreviewPanel.cs index b3ad5a499e..cdb6900f06 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModEffectPreviewPanel.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModEffectPreviewPanel.cs @@ -57,6 +57,7 @@ namespace osu.Game.Tests.Visual.UserInterface { Anchor = Anchor.Centre, Origin = Anchor.Centre, + Mods = { BindTarget = SelectedMods }, }); AddStep("set beatmap", () => From d4bc3090e7fd680f0f3452a1e242fa29b09f0178 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 23 Feb 2024 18:42:07 +0300 Subject: [PATCH 3211/4092] Fix incorrect conflict resolution --- osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index 3432529976..7035af1594 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -185,7 +185,7 @@ namespace osu.Game.Overlays.Mods RightContent.FadeTo(Collapsed.Value && !IsHovered ? 0 : 1, transition_duration, Easing.OutQuint); } - public partial class BPMDisplay : RollingCounter + public partial class BPMDisplay : RollingCounter { protected override double RollingDuration => 250; From 65c0b73dd5c0b8a74deeab5aa737fb5d90dce82f Mon Sep 17 00:00:00 2001 From: Hivie Date: Fri, 23 Feb 2024 17:55:49 +0100 Subject: [PATCH 3212/4092] mark `TaikoModConstantSpeed` as incompatible with EZ/HR --- osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs | 3 +++ osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs index 009f2854f8..59d0563f1f 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs @@ -1,6 +1,8 @@ // 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.Linq; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; @@ -10,6 +12,7 @@ namespace osu.Game.Rulesets.Taiko.Mods public class TaikoModEasy : ModEasy { public override LocalisableString Description => @"Beats move slower, and less accuracy required!"; + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(TaikoModConstantSpeed) }).ToArray(); /// /// Multiplier factor added to the scrolling speed. diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs index ba41175461..fa948507c8 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs @@ -1,6 +1,8 @@ // 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.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; @@ -8,6 +10,7 @@ namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModHardRock : ModHardRock { + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(TaikoModConstantSpeed) }).ToArray(); public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1; /// From 46a1f5267f40f03b566c4feb713de3a08a135ee8 Mon Sep 17 00:00:00 2001 From: Hivie Date: Fri, 23 Feb 2024 21:15:18 +0100 Subject: [PATCH 3213/4092] account for beatmap base scroll speed in constant visualisation method --- osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index a476634fb8..c88bbec9bc 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -72,7 +72,7 @@ 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.Overlapping ? 1 : 4; + double multiplier = VisualisationMethod == ScrollVisualisationMethod.Overlapping ? 1 : 4 * Beatmap.Difficulty.SliderMultiplier; return PlayfieldAdjustmentContainer.ComputeTimeRange() / multiplier; } From 4ea9519db839762a088b2ef15fc8f770b0c87905 Mon Sep 17 00:00:00 2001 From: Hivie Date: Fri, 23 Feb 2024 21:15:52 +0100 Subject: [PATCH 3214/4092] revert changes to `IncompatibleMods` --- osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs | 3 --- osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs | 3 --- osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs | 3 --- 3 files changed, 9 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs index 117dc0ebd2..4ecb94467e 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.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.Linq; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Configuration; @@ -21,7 +19,6 @@ namespace osu.Game.Rulesets.Taiko.Mods public override LocalisableString Description => "No more tricky speed changes!"; public override IconUsage? Icon => FontAwesome.Solid.Equals; public override ModType Type => ModType.Conversion; - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(TaikoModEasy), typeof(TaikoModHardRock) }).ToArray(); public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs index 59d0563f1f..009f2854f8 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.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.Linq; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; @@ -12,7 +10,6 @@ namespace osu.Game.Rulesets.Taiko.Mods public class TaikoModEasy : ModEasy { public override LocalisableString Description => @"Beats move slower, and less accuracy required!"; - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(TaikoModConstantSpeed) }).ToArray(); /// /// Multiplier factor added to the scrolling speed. diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs index fa948507c8..ba41175461 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.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.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; @@ -10,7 +8,6 @@ namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModHardRock : ModHardRock { - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(TaikoModConstantSpeed) }).ToArray(); public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1; /// From 8934cf33f069d211511097861696c8af3019bb3d Mon Sep 17 00:00:00 2001 From: jvyden Date: Fri, 23 Feb 2024 22:07:47 -0500 Subject: [PATCH 3215/4092] Apply Discord RPC changes regardless of user's status --- osu.Desktop/DiscordRichPresence.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index f990fd55fc..1944a73c0a 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -92,7 +92,7 @@ namespace osu.Desktop return; } - if (status.Value == UserStatus.Online && activity.Value != null) + if (activity.Value != null) { bool hideIdentifiableInformation = privacyMode.Value == DiscordRichPresenceMode.Limited; presence.State = truncate(activity.Value.GetStatus(hideIdentifiableInformation)); From e80b08c46fe45da19ffa6fba4862329333a6f112 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Feb 2024 09:55:51 +0100 Subject: [PATCH 3216/4092] Fix incorrect standardised score estimation on selected beatmaps --- osu.Game/Database/StandardisedScoreMigrationTools.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index 403e73ab77..0594c80390 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -365,6 +365,17 @@ namespace osu.Game.Database + bonusProportion) * modMultiplier); } + // see similar check above. + // if there is no legacy combo score, all combo conversion operations below + // are either pointless or wildly wrong. + if (maximumLegacyComboScore + maximumLegacyBonusScore == 0) + { + return (long)Math.Round(( + 500000 * comboProportion // as above, zero if mods result in zero multiplier, one otherwise + + 500000 * Math.Pow(score.Accuracy, 5) + + bonusProportion) * modMultiplier); + } + // Assumptions: // - sliders and slider ticks are uniformly distributed in the beatmap, and thus can be ignored without losing much precision. // We thus consider a map of hit-circles only, which gives objectCount == maximumCombo. From e7d8ca3292e4213b248914c7a0416b48b84b2f18 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 24 Feb 2024 14:22:34 +0300 Subject: [PATCH 3217/4092] Fix Argon and Trianles spinner stutter --- osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs | 2 +- osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs index ee9f228137..7ed6d2dce7 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs @@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon { base.Update(); - if (!spmContainer.IsPresent && drawableSpinner.Result?.TimeStarted != null) + if (spmContainer.Alpha != 0 && drawableSpinner.Result?.TimeStarted != null) fadeCounterOnTimeStart(); } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs index 4a76a1aec4..a5973ad444 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs @@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { base.Update(); - if (!spmContainer.IsPresent && drawableSpinner.Result?.TimeStarted != null) + if (spmContainer.Alpha != 0 && drawableSpinner.Result?.TimeStarted != null) fadeCounterOnTimeStart(); } From 4bc92a263f9c95eece33fe97dadc40eeddc897d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Feb 2024 13:20:42 +0100 Subject: [PATCH 3218/4092] Bump score version --- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index c74980abb6..775d87f3f2 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -44,9 +44,10 @@ namespace osu.Game.Scoring.Legacy /// method. Reconvert all scores. /// /// 30000013: All local scores will use lazer definitions of ranks for consistency. Recalculates the rank of all scores. + /// 30000014: Fix edge cases in conversion for osu! scores on selected beatmaps. Reconvert all scores. /// /// - public const int LATEST_VERSION = 30000013; + public const int LATEST_VERSION = 30000014; /// /// The first stable-compatible YYYYMMDD format version given to lazer usage of replays. From f6ceedc7a6dfd727c4ea29c14bca6c63575c6dc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Feb 2024 13:55:44 +0100 Subject: [PATCH 3219/4092] Inline last version which touched ranks when checking for upgrade --- osu.Game/Database/BackgroundDataStoreProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/BackgroundDataStoreProcessor.cs b/osu.Game/Database/BackgroundDataStoreProcessor.cs index be0c83bdb3..872194aa1d 100644 --- a/osu.Game/Database/BackgroundDataStoreProcessor.cs +++ b/osu.Game/Database/BackgroundDataStoreProcessor.cs @@ -382,7 +382,7 @@ namespace osu.Game.Database HashSet scoreIds = realmAccess.Run(r => new HashSet( r.All() - .Where(s => s.TotalScoreVersion < LegacyScoreEncoder.LATEST_VERSION) + .Where(s => s.TotalScoreVersion < 30000013) // last total score version with a significant change to ranks .AsEnumerable() // must be done after materialisation, as realm doesn't support // filtering on nested property predicates or projection via `.Select()` From 9b526912e9f58f9b9250ca4df8f104a988a9644b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 24 Feb 2024 16:41:53 +0300 Subject: [PATCH 3220/4092] Fix incorrect operator --- osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs | 2 +- osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs index 7ed6d2dce7..e168e14858 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs @@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon { base.Update(); - if (spmContainer.Alpha != 0 && drawableSpinner.Result?.TimeStarted != null) + if (spmContainer.Alpha == 0 && drawableSpinner.Result?.TimeStarted != null) fadeCounterOnTimeStart(); } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs index a5973ad444..2088839e82 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs @@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { base.Update(); - if (spmContainer.Alpha != 0 && drawableSpinner.Result?.TimeStarted != null) + if (spmContainer.Alpha == 0 && drawableSpinner.Result?.TimeStarted != null) fadeCounterOnTimeStart(); } From 4bba0eaf4b5b996ff6b053e1b88cb2b86bcd477c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 24 Feb 2024 16:42:44 +0300 Subject: [PATCH 3221/4092] Remove repeated TimeStarted check --- osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs | 2 +- osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs index e168e14858..25ce92d1c5 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs @@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon { base.Update(); - if (spmContainer.Alpha == 0 && drawableSpinner.Result?.TimeStarted != null) + if (spmContainer.Alpha == 0) fadeCounterOnTimeStart(); } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs index 2088839e82..5c4d7ae47b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs @@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { base.Update(); - if (spmContainer.Alpha == 0 && drawableSpinner.Result?.TimeStarted != null) + if (spmContainer.Alpha == 0) fadeCounterOnTimeStart(); } From 2696620d12078432f749f02103c7a7774740f90a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 24 Feb 2024 17:09:21 +0300 Subject: [PATCH 3222/4092] Completely remove transform flow for spm counter --- .../Skinning/Argon/ArgonSpinner.cs | 33 +++++-------------- .../Skinning/Default/DefaultSpinner.cs | 33 +++++-------------- 2 files changed, 16 insertions(+), 50 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs index 25ce92d1c5..f2bbd7373e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs @@ -5,7 +5,6 @@ using System; using System.Globalization; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; @@ -111,42 +110,26 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon { spmCounter.Text = Math.Truncate(spm.NewValue).ToString(@"#0"); }, true); - - drawableSpinner.ApplyCustomUpdateState += updateStateTransforms; - updateStateTransforms(drawableSpinner, drawableSpinner.State.Value); } protected override void Update() { base.Update(); - if (spmContainer.Alpha == 0) - fadeCounterOnTimeStart(); + updateSpmAlpha(); } - private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) - { - if (!(drawableHitObject is DrawableSpinner)) - return; - - fadeCounterOnTimeStart(); - } - - private void fadeCounterOnTimeStart() + private void updateSpmAlpha() { if (drawableSpinner.Result?.TimeStarted is double startTime) { - using (BeginAbsoluteSequence(startTime)) - spmContainer.FadeIn(drawableSpinner.HitObject.TimeFadeIn); + double timeOffset = Math.Clamp(Clock.CurrentTime, startTime, startTime + drawableSpinner.HitObject.TimeFadeIn) - startTime; + spmContainer.Alpha = (float)(timeOffset / drawableSpinner.HitObject.TimeFadeIn); + } + else + { + spmContainer.Alpha = 0; } - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (drawableSpinner.IsNotNull()) - drawableSpinner.ApplyCustomUpdateState -= updateStateTransforms; } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs index 5c4d7ae47b..0bd877b902 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs @@ -5,7 +5,6 @@ using System; using System.Globalization; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; @@ -117,42 +116,26 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { spmCounter.Text = Math.Truncate(spm.NewValue).ToString(@"#0"); }, true); - - drawableSpinner.ApplyCustomUpdateState += updateStateTransforms; - updateStateTransforms(drawableSpinner, drawableSpinner.State.Value); } protected override void Update() { base.Update(); - if (spmContainer.Alpha == 0) - fadeCounterOnTimeStart(); + updateSpmAlpha(); } - private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) - { - if (!(drawableHitObject is DrawableSpinner)) - return; - - fadeCounterOnTimeStart(); - } - - private void fadeCounterOnTimeStart() + private void updateSpmAlpha() { if (drawableSpinner.Result?.TimeStarted is double startTime) { - using (BeginAbsoluteSequence(startTime)) - spmContainer.FadeIn(drawableSpinner.HitObject.TimeFadeIn); + double timeOffset = Math.Clamp(Clock.CurrentTime, startTime, startTime + drawableSpinner.HitObject.TimeFadeIn) - startTime; + spmContainer.Alpha = (float)(timeOffset / drawableSpinner.HitObject.TimeFadeIn); + } + else + { + spmContainer.Alpha = 0; } - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (drawableSpinner.IsNotNull()) - drawableSpinner.ApplyCustomUpdateState -= updateStateTransforms; } } } From 824d671cce71eded91dee5ade2868da18a5f8e4b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 25 Feb 2024 00:12:20 +0800 Subject: [PATCH 3223/4092] Simplify implementation --- osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs | 7 +------ osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs | 7 +------ 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs index f2bbd7373e..3b48d36bb5 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs @@ -122,14 +122,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon private void updateSpmAlpha() { if (drawableSpinner.Result?.TimeStarted is double startTime) - { - double timeOffset = Math.Clamp(Clock.CurrentTime, startTime, startTime + drawableSpinner.HitObject.TimeFadeIn) - startTime; - spmContainer.Alpha = (float)(timeOffset / drawableSpinner.HitObject.TimeFadeIn); - } + spmContainer.Alpha = (float)Math.Clamp((Clock.CurrentTime - startTime) / drawableSpinner.HitObject.TimeFadeIn, 0, 1); else - { spmContainer.Alpha = 0; - } } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs index 0bd877b902..ac56b45b69 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs @@ -128,14 +128,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default private void updateSpmAlpha() { if (drawableSpinner.Result?.TimeStarted is double startTime) - { - double timeOffset = Math.Clamp(Clock.CurrentTime, startTime, startTime + drawableSpinner.HitObject.TimeFadeIn) - startTime; - spmContainer.Alpha = (float)(timeOffset / drawableSpinner.HitObject.TimeFadeIn); - } + spmContainer.Alpha = (float)Math.Clamp((Clock.CurrentTime - startTime) / drawableSpinner.HitObject.TimeFadeIn, 0, 1); else - { spmContainer.Alpha = 0; - } } } } From 1fb19e712922871adca31db706afd27ebb0a247f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 24 Feb 2024 20:18:30 +0300 Subject: [PATCH 3224/4092] Reduce allocations in DrawableSpinner --- .../Objects/Drawables/DrawableSpinner.cs | 32 ++++++++++++++++--- .../Objects/Drawables/DrawableHitObject.cs | 4 ++- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 11120e49b5..8c21e6a6bc 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -279,10 +279,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (HandleUserInput) { bool isValidSpinningTime = Time.Current >= HitObject.StartTime && Time.Current <= HitObject.EndTime; - bool correctButtonPressed = (OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false); RotationTracker.Tracking = !Result.HasResult - && correctButtonPressed + && correctButtonPressed() && isValidSpinningTime; } @@ -292,11 +291,34 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // Ticks can theoretically be judged at any point in the spinner's duration. // A tick must be alive to correctly play back samples, // but for performance reasons, we only want to keep the next tick alive. - var next = NestedHitObjects.FirstOrDefault(h => !h.Judged); + DrawableHitObject nextTick = null; + + foreach (var nested in NestedHitObjects) + { + if (!nested.Judged) + { + nextTick = nested; + break; + } + } // See default `LifetimeStart` as set in `DrawableSpinnerTick`. - if (next?.LifetimeStart == double.MaxValue) - next.LifetimeStart = HitObject.StartTime; + if (nextTick?.LifetimeStart == double.MaxValue) + nextTick.LifetimeStart = HitObject.StartTime; + } + + private bool correctButtonPressed() + { + if (OsuActionInputManager == null) + return false; + + foreach (var action in OsuActionInputManager.PressedActions) + { + if (action == OsuAction.LeftButton || action == OsuAction.RightButton) + return true; + } + + return false; } protected override void UpdateAfterChildren() diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 16bd4b565c..de05219212 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -11,10 +11,12 @@ using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.ListExtensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; +using osu.Framework.Lists; using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Audio; @@ -65,7 +67,7 @@ namespace osu.Game.Rulesets.Objects.Drawables public virtual IEnumerable GetSamples() => HitObject.Samples; private readonly List nestedHitObjects = new List(); - public IReadOnlyList NestedHitObjects => nestedHitObjects; + public SlimReadOnlyListWrapper NestedHitObjects => nestedHitObjects.AsSlimReadOnly(); /// /// Whether this object should handle any user input events. From 9e90f7fb0d731e06d36bce8e5bc269f362f307cd Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 24 Feb 2024 20:36:06 +0300 Subject: [PATCH 3225/4092] Store last enqueued RotationRecord in SpinnerSpmCalculator --- .../Skinning/Default/SpinnerSpmCalculator.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCalculator.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCalculator.cs index 44962c8548..7986108fbd 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCalculator.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCalculator.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.ObjectExtensions; @@ -33,14 +32,16 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default drawableSpinner.HitObjectApplied += resetState; } + private RotationRecord lastRecord; + public void SetRotation(float currentRotation) { // If we've gone back in time, it's fine to work with a fresh set of records for now - if (records.Count > 0 && Time.Current < records.Last().Time) + if (records.Count > 0 && Time.Current < lastRecord.Time) records.Clear(); // Never calculate SPM by same time of record to avoid 0 / 0 = NaN or X / 0 = Infinity result. - if (records.Count > 0 && Precision.AlmostEquals(Time.Current, records.Last().Time)) + if (records.Count > 0 && Precision.AlmostEquals(Time.Current, lastRecord.Time)) return; if (records.Count > 0) @@ -52,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default result.Value = (currentRotation - record.Rotation) / (Time.Current - record.Time) * 1000 * 60 / 360; } - records.Enqueue(new RotationRecord { Rotation = currentRotation, Time = Time.Current }); + records.Enqueue(lastRecord = new RotationRecord { Rotation = currentRotation, Time = Time.Current }); } private void resetState(DrawableHitObject hitObject) From 3b53ed3c3aae40476d7f061a2796186a9f70c8b7 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 24 Feb 2024 22:44:58 +0300 Subject: [PATCH 3226/4092] Reduce allocations in ModColumn --- osu.Game/Overlays/Mods/ModColumn.cs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index d65c94d14d..df33c78ea4 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -67,8 +67,25 @@ namespace osu.Game.Overlays.Mods private IModHotkeyHandler hotkeyHandler = null!; private Task? latestLoadTask; - private ICollection? latestLoadedPanels; - internal bool ItemsLoaded => latestLoadTask?.IsCompleted == true && latestLoadedPanels?.All(panel => panel.Parent != null) == true; + private ModPanel[]? latestLoadedPanels; + internal bool ItemsLoaded => latestLoadTask?.IsCompleted == true && allPanelsLoaded; + + private bool allPanelsLoaded + { + get + { + if (latestLoadedPanels == null) + return false; + + foreach (var panel in latestLoadedPanels) + { + if (panel.Parent == null) + return false; + } + + return true; + } + } public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; From 6d2187e079c278d36ca628b128223bafc31e5e4d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 24 Feb 2024 22:58:23 +0300 Subject: [PATCH 3227/4092] Reduce allocations in ModSelectOverlay --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index ddf96c1cb3..3009297741 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -15,6 +15,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Framework.Utils; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -349,15 +350,23 @@ namespace osu.Game.Overlays.Mods }); } + private static readonly LocalisableString input_search_placeholder = Resources.Localisation.Web.CommonStrings.InputSearch; + private static readonly LocalisableString tab_to_search_placeholder = ModSelectOverlayStrings.TabToSearch; + protected override void Update() { base.Update(); - SearchTextBox.PlaceholderText = SearchTextBox.HasFocus ? Resources.Localisation.Web.CommonStrings.InputSearch : ModSelectOverlayStrings.TabToSearch; + SearchTextBox.PlaceholderText = SearchTextBox.HasFocus ? input_search_placeholder : tab_to_search_placeholder; if (beatmapAttributesDisplay != null) { - float rightEdgeOfLastButton = footerButtonFlow.Last().ScreenSpaceDrawQuad.TopRight.X; + ShearedButton lastFooterButton = null!; + + foreach (var b in footerButtonFlow) + lastFooterButton = b; + + float rightEdgeOfLastButton = lastFooterButton.ScreenSpaceDrawQuad.TopRight.X; // this is cheating a bit; the 640 value is hardcoded based on how wide the expanded panel _generally_ is. // due to the transition applied, the raw screenspace quad of the panel cannot be used, as it will trigger an ugly feedback cycle of expanding and collapsing. From 91d7bd10265a806946ba0e528da8aa6c07cdcd8e Mon Sep 17 00:00:00 2001 From: Detze <92268414+Detze@users.noreply.github.com> Date: Sat, 24 Feb 2024 21:56:44 +0100 Subject: [PATCH 3228/4092] Don't dim slider head in `DrawableSlider` --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 6d492e7b08..b7ce712e2c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override IEnumerable DimmablePieces => new Drawable[] { - HeadCircle, + // HeadCircle should not be added to this list, as it handles dimming itself TailCircle, repeatContainer, Body, From e12f8c03eef09478b8c8f3823b60632d37713192 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 25 Feb 2024 08:18:19 +0800 Subject: [PATCH 3229/4092] Reset `lastRecord` on `resetState` for good measure --- osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCalculator.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCalculator.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCalculator.cs index 7986108fbd..3383989367 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCalculator.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCalculator.cs @@ -58,6 +58,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default private void resetState(DrawableHitObject hitObject) { + lastRecord = default; result.Value = 0; records.Clear(); } From 081aa84718233618bd1d9fc81468e99023dbc705 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 25 Feb 2024 10:36:15 +0300 Subject: [PATCH 3230/4092] Simplify last footer button selection --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 3009297741..ce1d0d27a3 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -361,12 +361,7 @@ namespace osu.Game.Overlays.Mods if (beatmapAttributesDisplay != null) { - ShearedButton lastFooterButton = null!; - - foreach (var b in footerButtonFlow) - lastFooterButton = b; - - float rightEdgeOfLastButton = lastFooterButton.ScreenSpaceDrawQuad.TopRight.X; + float rightEdgeOfLastButton = footerButtonFlow[^1].ScreenSpaceDrawQuad.TopRight.X; // this is cheating a bit; the 640 value is hardcoded based on how wide the expanded panel _generally_ is. // due to the transition applied, the raw screenspace quad of the panel cannot be used, as it will trigger an ugly feedback cycle of expanding and collapsing. From f948f8ee5c49498ea58fe2a2e5817ef7fbb027c7 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 25 Feb 2024 17:59:20 +0300 Subject: [PATCH 3231/4092] Fix HUDOverlay allocations --- osu.Game/Screens/Play/HUDOverlay.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 32ebb82f15..f965d3392a 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -258,13 +258,13 @@ namespace osu.Game.Screens.Play Vector2? highestBottomScreenSpace = null; - foreach (var element in mainComponents.Components) - processDrawable(element); + for (int i = 0; i < mainComponents.Components.Count; i++) + processDrawable(mainComponents.Components[i]); if (rulesetComponents != null) { - foreach (var element in rulesetComponents.Components) - processDrawable(element); + for (int i = 0; i < rulesetComponents.Components.Count; i++) + processDrawable(rulesetComponents.Components[i]); } if (lowestTopScreenSpaceRight.HasValue) From c3fa97d062c370c4d5c60b2cee66e7b8298a79b7 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 25 Feb 2024 18:02:42 +0300 Subject: [PATCH 3232/4092] Reduce allocations in HitObjectLifetimeEntry --- .../Rulesets/Objects/HitObjectLifetimeEntry.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs index 4450f026b4..9f2720b7ca 100644 --- a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs +++ b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs @@ -41,7 +41,22 @@ namespace osu.Game.Rulesets.Objects /// /// Whether and all of its nested objects have been judged. /// - public bool AllJudged => Judged && NestedEntries.All(h => h.AllJudged); + public bool AllJudged + { + get + { + if (!Judged) + return false; + + foreach (var entry in NestedEntries) + { + if (!entry.AllJudged) + return false; + } + + return true; + } + } private readonly IBindable startTimeBindable = new BindableDouble(); From 9e3defebda1d446f815cb0cae6618f822ba482d2 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 25 Feb 2024 19:05:40 +0300 Subject: [PATCH 3233/4092] Remove unused using --- osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs index 9f2720b7ca..4962ac13b5 100644 --- a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs +++ b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics.Performance; using osu.Game.Rulesets.Judgements; From 5c049feca1e855daca7499b2e6bfb6482c99fe8a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 25 Feb 2024 21:18:15 +0300 Subject: [PATCH 3234/4092] Fix advanced stats in beatmap info overlay showing "key count" on non-mania beatmaps --- osu.Game/Overlays/BeatmapSet/Details.cs | 30 ++++++++++++------- .../Screens/Select/Details/AdvancedStats.cs | 29 ++++++------------ osu.Game/Screens/Select/SongSelect.cs | 7 ++++- 3 files changed, 34 insertions(+), 32 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Details.cs b/osu.Game/Overlays/BeatmapSet/Details.cs index cf78f605aa..d656a6b14b 100644 --- a/osu.Game/Overlays/BeatmapSet/Details.cs +++ b/osu.Game/Overlays/BeatmapSet/Details.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapSet.Buttons; +using osu.Game.Rulesets; using osu.Game.Screens.Select.Details; using osuTK; @@ -33,10 +34,10 @@ namespace osu.Game.Overlays.BeatmapSet { if (value == beatmapSet) return; - beatmapSet = value; + basic.BeatmapSet = preview.BeatmapSet = beatmapSet = value; - basic.BeatmapSet = preview.BeatmapSet = BeatmapSet; - updateDisplay(); + if (IsLoaded) + updateDisplay(); } } @@ -50,13 +51,10 @@ namespace osu.Game.Overlays.BeatmapSet if (value == beatmapInfo) return; basic.BeatmapInfo = advanced.BeatmapInfo = beatmapInfo = value; - } - } - private void updateDisplay() - { - Ratings.Ratings = BeatmapSet?.Ratings; - ratingBox.Alpha = BeatmapSet?.Status > 0 ? 1 : 0; + if (IsLoaded) + updateDisplay(); + } } public Details() @@ -101,12 +99,22 @@ namespace osu.Game.Overlays.BeatmapSet }; } - [BackgroundDependencyLoader] - private void load() + [Resolved] + private RulesetStore rulesets { get; set; } + + protected override void LoadComplete() { + base.LoadComplete(); updateDisplay(); } + private void updateDisplay() + { + Ratings.Ratings = BeatmapSet?.Ratings; + ratingBox.Alpha = BeatmapSet?.Status > 0 ? 1 : 0; + advanced.Ruleset.Value = rulesets.GetRuleset(beatmapInfo?.Ruleset.OnlineID ?? 0); + } + private partial class DetailBox : Container { private readonly Container content; diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 0d68a0ec3c..74276795d2 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -38,11 +38,6 @@ namespace osu.Game.Screens.Select.Details [Resolved] private IBindable> mods { get; set; } - [Resolved] - private OsuGameBase game { get; set; } - - private IBindable gameRuleset; - protected readonly StatisticRow FirstValue, HpDrain, Accuracy, ApproachRate; private readonly StatisticRow starDifficulty; @@ -64,6 +59,8 @@ namespace osu.Game.Screens.Select.Details } } + public Bindable Ruleset { get; } = new Bindable(); + public AdvancedStats(int columns = 1) { switch (columns) @@ -137,12 +134,7 @@ namespace osu.Game.Screens.Select.Details { base.LoadComplete(); - // the cached ruleset bindable might be a decoupled bindable provided by SongSelect, - // which we can't rely on in combination with the game-wide selected mods list, - // since mods could be updated to the new ruleset instances while the decoupled bindable is held behind, - // therefore resulting in performing difficulty calculation with invalid states. - gameRuleset = game.Ruleset.GetBoundCopy(); - gameRuleset.BindValueChanged(_ => updateStatistics()); + Ruleset.BindValueChanged(_ => updateStatistics()); mods.BindValueChanged(modsChanged, true); } @@ -169,8 +161,6 @@ namespace osu.Game.Screens.Select.Details IBeatmapDifficultyInfo baseDifficulty = BeatmapInfo?.Difficulty; BeatmapDifficulty adjustedDifficulty = null; - IRulesetInfo ruleset = gameRuleset?.Value ?? beatmapInfo.Ruleset; - if (baseDifficulty != null) { BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(baseDifficulty); @@ -180,24 +170,24 @@ namespace osu.Game.Screens.Select.Details adjustedDifficulty = originalDifficulty; - if (gameRuleset != null) + if (Ruleset.Value != null) { double rate = 1; foreach (var mod in mods.Value.OfType()) rate = mod.ApplyToRate(0, rate); - adjustedDifficulty = ruleset.CreateInstance().GetRateAdjustedDisplayDifficulty(originalDifficulty, rate); + adjustedDifficulty = Ruleset.Value.CreateInstance().GetRateAdjustedDisplayDifficulty(originalDifficulty, rate); TooltipContent = new AdjustedAttributesTooltip.Data(originalDifficulty, adjustedDifficulty); } } - switch (ruleset.OnlineID) + switch (Ruleset.Value?.OnlineID) { case 3: // Account for mania differences locally for now. // Eventually this should be handled in a more modular way, allowing rulesets to return arbitrary difficulty attributes. - ILegacyRuleset legacyRuleset = (ILegacyRuleset)ruleset.CreateInstance(); + ILegacyRuleset legacyRuleset = (ILegacyRuleset)Ruleset.Value.CreateInstance(); // For the time being, the key count is static no matter what, because: // a) The method doesn't have knowledge of the active keymods. Doing so may require considerations for filtering. @@ -206,7 +196,6 @@ namespace osu.Game.Screens.Select.Details FirstValue.Title = BeatmapsetsStrings.ShowStatsCsMania; FirstValue.Value = (keyCount, keyCount); - break; default: @@ -240,8 +229,8 @@ namespace osu.Game.Screens.Select.Details starDifficultyCancellationSource = new CancellationTokenSource(); - var normalStarDifficultyTask = difficultyCache.GetDifficultyAsync(BeatmapInfo, gameRuleset.Value, null, starDifficultyCancellationSource.Token); - var moddedStarDifficultyTask = difficultyCache.GetDifficultyAsync(BeatmapInfo, gameRuleset.Value, mods.Value, starDifficultyCancellationSource.Token); + var normalStarDifficultyTask = difficultyCache.GetDifficultyAsync(BeatmapInfo, Ruleset.Value, null, starDifficultyCancellationSource.Token); + var moddedStarDifficultyTask = difficultyCache.GetDifficultyAsync(BeatmapInfo, Ruleset.Value, mods.Value, starDifficultyCancellationSource.Token); Task.WhenAll(normalStarDifficultyTask, moddedStarDifficultyTask).ContinueWith(_ => Schedule(() => { diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index a603934a9d..15469fad5b 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -286,7 +286,7 @@ namespace osu.Game.Screens.Select AutoSizeAxes = Axes.Y, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Padding = new MarginPadding(10) + Padding = new MarginPadding(10), }, } }, @@ -585,6 +585,11 @@ namespace osu.Game.Screens.Select beatmapInfoPrevious = beatmap; } + // we can't run this in the debounced run due to the selected mods bindable not being debounced, + // since mods could be updated to the new ruleset instances while the decoupled bindable is held behind, + // therefore resulting in performing difficulty calculation with invalid states. + advancedStats.Ruleset.Value = ruleset; + void run() { // clear pending task immediately to track any potential nested debounce operation. From b30a6d522462331e20043f0bada10186a3315196 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 25 Feb 2024 21:23:09 +0300 Subject: [PATCH 3235/4092] Adjust existing test coverage --- .../SongSelect/TestSceneAdvancedStats.cs | 49 ++----------------- 1 file changed, 5 insertions(+), 44 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs index 4bb2b557ff..3b89c70a63 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs @@ -37,15 +37,9 @@ namespace osu.Game.Tests.Visual.SongSelect [SetUp] public void Setup() => Schedule(() => Child = advancedStats = new TestAdvancedStats { - Width = 500 + Width = 500, }); - [SetUpSteps] - public void SetUpSteps() - { - AddStep("reset game ruleset", () => Ruleset.Value = new OsuRuleset().RulesetInfo); - } - private BeatmapInfo exampleBeatmapInfo => new BeatmapInfo { Ruleset = rulesets.AvailableRulesets.First(), @@ -74,45 +68,12 @@ namespace osu.Game.Tests.Visual.SongSelect } [Test] - public void TestManiaFirstBarTextManiaBeatmap() + public void TestFirstBarText() { - AddStep("set game ruleset to mania", () => Ruleset.Value = new ManiaRuleset().RulesetInfo); - - AddStep("set beatmap", () => advancedStats.BeatmapInfo = new BeatmapInfo - { - Ruleset = rulesets.GetRuleset(3) ?? throw new InvalidOperationException("osu!mania ruleset not found"), - Difficulty = new BeatmapDifficulty - { - CircleSize = 5, - DrainRate = 4.3f, - OverallDifficulty = 4.5f, - ApproachRate = 3.1f - }, - StarRating = 8 - }); - - AddAssert("first bar text is correct", () => advancedStats.ChildrenOfType().First().Text == BeatmapsetsStrings.ShowStatsCsMania); - } - - [Test] - public void TestManiaFirstBarTextConvert() - { - AddStep("set game ruleset to mania", () => Ruleset.Value = new ManiaRuleset().RulesetInfo); - - AddStep("set beatmap", () => advancedStats.BeatmapInfo = new BeatmapInfo - { - Ruleset = new OsuRuleset().RulesetInfo, - Difficulty = new BeatmapDifficulty - { - CircleSize = 5, - DrainRate = 4.3f, - OverallDifficulty = 4.5f, - ApproachRate = 3.1f - }, - StarRating = 8 - }); - + AddStep("set ruleset to mania", () => advancedStats.Ruleset.Value = new ManiaRuleset().RulesetInfo); AddAssert("first bar text is correct", () => advancedStats.ChildrenOfType().First().Text == BeatmapsetsStrings.ShowStatsCsMania); + AddStep("set ruleset to osu", () => advancedStats.Ruleset.Value = new OsuRuleset().RulesetInfo); + AddAssert("first bar text is correct", () => advancedStats.ChildrenOfType().First().Text == BeatmapsetsStrings.ShowStatsCs); } [Test] From 4421ff975b330e832ee0405b9171dfeed2255577 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Feb 2024 09:04:39 +0800 Subject: [PATCH 3236/4092] Add local function to perform iteration to better explain the "why" --- osu.Game/Screens/Play/HUDOverlay.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index f965d3392a..2ec2a011a6 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -258,14 +258,10 @@ namespace osu.Game.Screens.Play Vector2? highestBottomScreenSpace = null; - for (int i = 0; i < mainComponents.Components.Count; i++) - processDrawable(mainComponents.Components[i]); + processDrawables(mainComponents); if (rulesetComponents != null) - { - for (int i = 0; i < rulesetComponents.Components.Count; i++) - processDrawable(rulesetComponents.Components[i]); - } + processDrawables(rulesetComponents); if (lowestTopScreenSpaceRight.HasValue) topRightElements.Y = MathHelper.Clamp(ToLocalSpace(new Vector2(0, lowestTopScreenSpaceRight.Value)).Y, 0, DrawHeight - topRightElements.DrawHeight); @@ -282,6 +278,14 @@ namespace osu.Game.Screens.Play else bottomRightElements.Y = 0; + void processDrawables(SkinComponentsContainer components) + { + // Avoid using foreach due to missing GetEnumerator implementation. + // See https://github.com/ppy/osu-framework/blob/e10051e6643731e393b09de40a3a3d209a545031/osu.Framework/Bindables/IBindableList.cs#L41-L44. + for (int i = 0; i < components.Components.Count; i++) + processDrawable(components.Components[i]); + } + void processDrawable(ISerialisableDrawable element) { // Cast can be removed when IDrawable interface includes Anchor / RelativeSizeAxes. From 3502ec456d0131f4f6c1c9faabf01e6197a16ee1 Mon Sep 17 00:00:00 2001 From: Detze <92268414+Detze@users.noreply.github.com> Date: Mon, 26 Feb 2024 04:36:09 +0100 Subject: [PATCH 3237/4092] Match stable's slider border thickness more closely --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs index b39092a467..6bf6776617 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy protected new float CalculatedBorderPortion // Roughly matches osu!stable's slider border portions. - => base.CalculatedBorderPortion * 0.77f; + => base.CalculatedBorderPortion * 0.84f; protected override Color4 ColourAt(float position) { From 4c744ccb698e9f41cc350fcde45159a30ec52944 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Feb 2024 14:11:54 +0800 Subject: [PATCH 3238/4092] Fix "Use current" snap not working Regressed with https://github.com/ppy/osu/pull/27249. I was suspicious of this specific operation at the time but didn't test properly. --- osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs index b2f38662cc..8bff5fe6ac 100644 --- a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs @@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Edit if (objTime >= editorClock.CurrentTime) continue; - if (objTime > lastBefore?.StartTime) + if (lastBefore == null || objTime > lastBefore?.StartTime) lastBefore = entry.Value.HitObject; } @@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Edit if (objTime < editorClock.CurrentTime) continue; - if (objTime < firstAfter?.StartTime) + if (firstAfter == null || objTime < firstAfter?.StartTime) firstAfter = entry.Value.HitObject; } From 9a46e738bdf86cb0f8495e6ef69f5cc8a6d5456c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Feb 2024 15:45:29 +0800 Subject: [PATCH 3239/4092] Fix inspections --- osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs index 8bff5fe6ac..62ad2ce7e9 100644 --- a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs @@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Edit if (objTime >= editorClock.CurrentTime) continue; - if (lastBefore == null || objTime > lastBefore?.StartTime) + if (lastBefore == null || objTime > lastBefore.StartTime) lastBefore = entry.Value.HitObject; } @@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Edit if (objTime < editorClock.CurrentTime) continue; - if (firstAfter == null || objTime < firstAfter?.StartTime) + if (firstAfter == null || objTime < firstAfter.StartTime) firstAfter = entry.Value.HitObject; } From 8962be2ed54f8431d29d6558aa8eacf28e467707 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Feb 2024 17:24:04 +0800 Subject: [PATCH 3240/4092] Allow better menu navigation using same hotkey to progress to destination As touched on in https://github.com/ppy/osu/discussions/27102. You can now use: - `L L L` to get to playlists - `M M M` to get to multiplayer - `S` to get to settings --- osu.Game/Screens/Menu/ButtonSystem.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index d742d2377f..15a2740160 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -102,7 +102,7 @@ namespace osu.Game.Screens.Menu buttonArea.AddRange(new Drawable[] { - new MainMenuButton(ButtonSystemStrings.Settings, string.Empty, OsuIcon.Settings, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O), + new MainMenuButton(ButtonSystemStrings.Settings, string.Empty, OsuIcon.Settings, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O, Key.S), backButton = new MainMenuButton(ButtonSystemStrings.Back, @"back-to-top", OsuIcon.PrevCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, -WEDGE_WIDTH) { @@ -132,11 +132,11 @@ namespace osu.Game.Screens.Menu buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Playlists, @"button-default-select", OsuIcon.Tournament, new Color4(94, 63, 186, 255), onPlaylists, 0, Key.L)); buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play); - buttonsEdit.Add(new MainMenuButton(EditorStrings.BeatmapEditor.ToLower(), @"button-default-select", OsuIcon.Beatmap, new Color4(238, 170, 0, 255), () => OnEditBeatmap?.Invoke(), WEDGE_WIDTH, Key.B)); + buttonsEdit.Add(new MainMenuButton(EditorStrings.BeatmapEditor.ToLower(), @"button-default-select", OsuIcon.Beatmap, new Color4(238, 170, 0, 255), () => OnEditBeatmap?.Invoke(), WEDGE_WIDTH, Key.B, Key.E)); buttonsEdit.Add(new MainMenuButton(SkinEditorStrings.SkinEditor.ToLower(), @"button-default-select", OsuIcon.SkinB, new Color4(220, 160, 0, 255), () => OnEditSkin?.Invoke(), 0, Key.S)); buttonsEdit.ForEach(b => b.VisibleState = ButtonSystemState.Edit); - buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Play, @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P)); + buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Play, @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P, Key.M, Key.L)); buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Edit, @"button-play-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => State = ButtonSystemState.Edit, 0, Key.E)); buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Browse, @"button-default-select", OsuIcon.Beatmap, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.B, Key.D)); From 115d82664bb0a3271e7593dacce987557a2ff098 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 26 Feb 2024 10:37:03 +0100 Subject: [PATCH 3241/4092] Assert proportional scaling rather than assume average Because if scaling is ever actually non-proportional then this should be somewhat loud. Also use the absolute value to prevent funny things happening if/when someone does negative scale. --- osu.Game/Rulesets/Mods/ModFlashlight.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index d714cd3c85..144842def0 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.Runtime.InteropServices; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -13,6 +14,7 @@ using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Shaders.Types; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.OpenGL.Vertices; @@ -151,9 +153,10 @@ namespace osu.Game.Rulesets.Mods if (GetPlayfieldScale != null) { Vector2 playfieldScale = GetPlayfieldScale(); - float rulesetScaleAvg = (playfieldScale.X + playfieldScale.Y) / 2f; - size *= rulesetScaleAvg; + Debug.Assert(Precision.AlmostEquals(Math.Abs(playfieldScale.X), Math.Abs(playfieldScale.Y)), + @"Playfield has non-proportional scaling. Flashlight implementations should be revisited with regard to balance."); + size *= Math.Abs(playfieldScale.X); } if (isBreakTime.Value) From 8e336610d090b2d1f94239620c336a7e0854fc34 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Feb 2024 18:31:36 +0800 Subject: [PATCH 3242/4092] Add xmldoc explaining `Ruleset` bindable's usage --- osu.Game/Screens/Select/Details/AdvancedStats.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 74276795d2..1aba977f44 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -59,6 +59,13 @@ namespace osu.Game.Screens.Select.Details } } + /// + /// Ruleset to be used for certain elements of display. + /// When set, this will override the set 's own ruleset. + /// + /// + /// No checks are done as to whether the ruleset specified is valid for the currently . + /// public Bindable Ruleset { get; } = new Bindable(); public AdvancedStats(int columns = 1) From 8966ea2fa3a81cd567aeacbbee60e857af7d9520 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 26 Feb 2024 11:59:57 +0100 Subject: [PATCH 3243/4092] Add test coverage --- ...tSceneHitObjectComposerDistanceSnapping.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs index 463287fb35..12b7dbbf12 100644 --- a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.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 System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -17,6 +18,7 @@ using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; using osu.Game.Tests.Visual; +using osuTK; namespace osu.Game.Tests.Editing { @@ -228,6 +230,28 @@ namespace osu.Game.Tests.Editing assertSnappedDistance(400, 400); } + [Test] + public void TestUseCurrentSnap() + { + AddStep("add objects to beatmap", () => + { + editorBeatmap.Add(new HitCircle { StartTime = 1000 }); + editorBeatmap.Add(new HitCircle { Position = new Vector2(100), StartTime = 2000 }); + }); + + AddStep("hover use current snap button", () => InputManager.MoveMouseTo(composer.ChildrenOfType().Single())); + AddUntilStep("use current snap expanded", () => composer.ChildrenOfType().Single().Expanded.Value, () => Is.True); + + AddStep("seek before first object", () => EditorClock.Seek(0)); + AddUntilStep("use current snap not available", () => composer.ChildrenOfType().Single().Enabled.Value, () => Is.False); + + AddStep("seek to between objects", () => EditorClock.Seek(1500)); + AddUntilStep("use current snap available", () => composer.ChildrenOfType().Single().Enabled.Value, () => Is.True); + + AddStep("seek after last object", () => EditorClock.Seek(2500)); + AddUntilStep("use current snap not available", () => composer.ChildrenOfType().Single().Enabled.Value, () => Is.False); + } + private void assertSnapDistance(float expectedDistance, HitObject? referenceObject, bool includeSliderVelocity) => AddAssert($"distance is {expectedDistance}", () => composer.DistanceSnapProvider.GetBeatSnapDistanceAt(referenceObject ?? new HitObject(), includeSliderVelocity), () => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON)); From 6dbba705b3127757dbb36f16911790d776934527 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Mon, 26 Feb 2024 12:27:02 +0100 Subject: [PATCH 3244/4092] Refine uninstall logic to account for legacy windows features --- osu.Desktop/Windows/WindowsAssociationManager.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Desktop/Windows/WindowsAssociationManager.cs b/osu.Desktop/Windows/WindowsAssociationManager.cs index 4bb8e57c9d..490faab632 100644 --- a/osu.Desktop/Windows/WindowsAssociationManager.cs +++ b/osu.Desktop/Windows/WindowsAssociationManager.cs @@ -222,12 +222,21 @@ namespace osu.Desktop.Windows programKey?.SetValue(null, description); } + /// + /// Uninstalls the file extenstion association in accordance with https://learn.microsoft.com/en-us/windows/win32/shell/fa-file-types#deleting-registry-information-during-uninstallation + /// public void Uninstall(RegistryKey classes) { - // importantly, we don't delete the default program entry because some other program could have taken it. + using (var extensionKey = classes.OpenSubKey(Extension, true)) + { + // clear our default association so that Explorer doesn't show the raw programId to users + // the null/(Default) entry is used for both ProdID association and as a fallback friendly name, for legacy reasons + if (extensionKey?.GetValue(null) is string s && s == programId) + extensionKey.SetValue(null, string.Empty); - using (var extensionKey = classes.OpenSubKey($@"{Extension}\OpenWithProgIds", true)) - extensionKey?.DeleteValue(programId, throwOnMissingValue: false); + using (var openWithKey = extensionKey?.CreateSubKey(@"OpenWithProgIds")) + openWithKey?.DeleteValue(programId, throwOnMissingValue: false); + } classes.DeleteSubKeyTree(programId, throwOnMissingSubKey: false); } From f2807470efc66a560dbd09da75be2ff70bf83b1d Mon Sep 17 00:00:00 2001 From: Susko3 Date: Mon, 26 Feb 2024 13:03:23 +0100 Subject: [PATCH 3245/4092] Inline `EXE_PATH` usage --- osu.Desktop/Windows/WindowsAssociationManager.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Desktop/Windows/WindowsAssociationManager.cs b/osu.Desktop/Windows/WindowsAssociationManager.cs index 490faab632..3fd566edab 100644 --- a/osu.Desktop/Windows/WindowsAssociationManager.cs +++ b/osu.Desktop/Windows/WindowsAssociationManager.cs @@ -136,10 +136,10 @@ namespace osu.Desktop.Windows Debug.Assert(classes != null); foreach (var association in file_associations) - association.Install(classes, EXE_PATH); + association.Install(classes); foreach (var association in uri_associations) - association.Install(classes, EXE_PATH); + association.Install(classes); } private static void updateDescriptions(LocalisationManager? localisation) @@ -192,7 +192,7 @@ namespace osu.Desktop.Windows /// /// Installs a file extenstion association in accordance with https://learn.microsoft.com/en-us/windows/win32/com/-progid--key /// - public void Install(RegistryKey classes, string exePath) + public void Install(RegistryKey classes) { // register a program id for the given extension using (var programKey = classes.CreateSubKey(programId)) @@ -201,7 +201,7 @@ namespace osu.Desktop.Windows defaultIconKey.SetValue(null, IconPath); using (var openCommandKey = programKey.CreateSubKey(SHELL_OPEN_COMMAND)) - openCommandKey.SetValue(null, $@"""{exePath}"" ""%1"""); + openCommandKey.SetValue(null, $@"""{EXE_PATH}"" ""%1"""); } using (var extensionKey = classes.CreateSubKey(Extension)) @@ -253,7 +253,7 @@ namespace osu.Desktop.Windows /// /// Registers an URI protocol handler in accordance with https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa767914(v=vs.85). /// - public void Install(RegistryKey classes, string exePath) + public void Install(RegistryKey classes) { using (var protocolKey = classes.CreateSubKey(Protocol)) { @@ -263,7 +263,7 @@ namespace osu.Desktop.Windows defaultIconKey.SetValue(null, IconPath); using (var openCommandKey = protocolKey.CreateSubKey(SHELL_OPEN_COMMAND)) - openCommandKey.SetValue(null, $@"""{exePath}"" ""%1"""); + openCommandKey.SetValue(null, $@"""{EXE_PATH}"" ""%1"""); } } From 9b3ec64f411b234391ac653fb319cbb07d1d6fea Mon Sep 17 00:00:00 2001 From: Susko3 Date: Mon, 26 Feb 2024 13:10:37 +0100 Subject: [PATCH 3246/4092] Inline `HKCU\Software\Classes` usage --- .../Windows/WindowsAssociationManager.cs | 52 +++++++++++-------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/osu.Desktop/Windows/WindowsAssociationManager.cs b/osu.Desktop/Windows/WindowsAssociationManager.cs index 3fd566edab..2a1aeba7e0 100644 --- a/osu.Desktop/Windows/WindowsAssociationManager.cs +++ b/osu.Desktop/Windows/WindowsAssociationManager.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using System.Runtime.Versioning; @@ -108,14 +107,11 @@ namespace osu.Desktop.Windows { try { - using var classes = Registry.CurrentUser.OpenSubKey(SOFTWARE_CLASSES, true); - Debug.Assert(classes != null); - foreach (var association in file_associations) - association.Uninstall(classes); + association.Uninstall(); foreach (var association in uri_associations) - association.Uninstall(classes); + association.Uninstall(); NotifyShellUpdate(); } @@ -132,26 +128,20 @@ namespace osu.Desktop.Windows /// private static void updateAssociations() { - using var classes = Registry.CurrentUser.OpenSubKey(SOFTWARE_CLASSES, true); - Debug.Assert(classes != null); - foreach (var association in file_associations) - association.Install(classes); + association.Install(); foreach (var association in uri_associations) - association.Install(classes); + association.Install(); } private static void updateDescriptions(LocalisationManager? localisation) { - using var classes = Registry.CurrentUser.OpenSubKey(SOFTWARE_CLASSES, true); - Debug.Assert(classes != null); - foreach (var association in file_associations) - association.UpdateDescription(classes, getLocalisedString(association.Description)); + association.UpdateDescription(getLocalisedString(association.Description)); foreach (var association in uri_associations) - association.UpdateDescription(classes, getLocalisedString(association.Description)); + association.UpdateDescription(getLocalisedString(association.Description)); string getLocalisedString(LocalisableString s) { @@ -192,8 +182,11 @@ namespace osu.Desktop.Windows /// /// Installs a file extenstion association in accordance with https://learn.microsoft.com/en-us/windows/win32/com/-progid--key /// - public void Install(RegistryKey classes) + public void Install() { + using var classes = Registry.CurrentUser.OpenSubKey(SOFTWARE_CLASSES, true); + if (classes == null) return; + // register a program id for the given extension using (var programKey = classes.CreateSubKey(programId)) { @@ -216,8 +209,11 @@ namespace osu.Desktop.Windows } } - public void UpdateDescription(RegistryKey classes, string description) + public void UpdateDescription(string description) { + using var classes = Registry.CurrentUser.OpenSubKey(SOFTWARE_CLASSES, true); + if (classes == null) return; + using (var programKey = classes.OpenSubKey(programId, true)) programKey?.SetValue(null, description); } @@ -225,8 +221,11 @@ namespace osu.Desktop.Windows /// /// Uninstalls the file extenstion association in accordance with https://learn.microsoft.com/en-us/windows/win32/shell/fa-file-types#deleting-registry-information-during-uninstallation /// - public void Uninstall(RegistryKey classes) + public void Uninstall() { + using var classes = Registry.CurrentUser.OpenSubKey(SOFTWARE_CLASSES, true); + if (classes == null) return; + using (var extensionKey = classes.OpenSubKey(Extension, true)) { // clear our default association so that Explorer doesn't show the raw programId to users @@ -253,8 +252,11 @@ namespace osu.Desktop.Windows /// /// Registers an URI protocol handler in accordance with https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa767914(v=vs.85). /// - public void Install(RegistryKey classes) + public void Install() { + using var classes = Registry.CurrentUser.OpenSubKey(SOFTWARE_CLASSES, true); + if (classes == null) return; + using (var protocolKey = classes.CreateSubKey(Protocol)) { protocolKey.SetValue(URL_PROTOCOL, string.Empty); @@ -267,15 +269,19 @@ namespace osu.Desktop.Windows } } - public void UpdateDescription(RegistryKey classes, string description) + public void UpdateDescription(string description) { + using var classes = Registry.CurrentUser.OpenSubKey(SOFTWARE_CLASSES, true); + if (classes == null) return; + using (var protocolKey = classes.OpenSubKey(Protocol, true)) protocolKey?.SetValue(null, $@"URL:{description}"); } - public void Uninstall(RegistryKey classes) + public void Uninstall() { - classes.DeleteSubKeyTree(Protocol, throwOnMissingSubKey: false); + using var classes = Registry.CurrentUser.OpenSubKey(SOFTWARE_CLASSES, true); + classes?.DeleteSubKeyTree(Protocol, throwOnMissingSubKey: false); } } } From 3e9425fdf2d57dbeff8819bca208a63cb947eda9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 26 Feb 2024 22:12:04 +0900 Subject: [PATCH 3247/4092] Adjust API of HighPerformanceSession + rename --- osu.Game/OsuGame.cs | 4 +- .../Performance/HighPerformanceSession.cs | 42 ------------------- .../HighPerformanceSessionManager.cs | 34 +++++++++++++++ 3 files changed, 36 insertions(+), 44 deletions(-) delete mode 100644 osu.Game/Performance/HighPerformanceSession.cs create mode 100644 osu.Game/Performance/HighPerformanceSessionManager.cs diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 9ffa88947b..f23a78d2dc 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -792,7 +792,7 @@ namespace osu.Game protected virtual UpdateManager CreateUpdateManager() => new UpdateManager(); - protected virtual HighPerformanceSession CreateHighPerformanceSession() => new HighPerformanceSession(); + protected virtual HighPerformanceSessionManager CreateHighPerformanceSessionManager() => new HighPerformanceSessionManager(); protected override Container CreateScalingContainer() => new ScalingContainer(ScalingMode.Everything); @@ -1088,7 +1088,7 @@ namespace osu.Game loadComponentSingleFile(new AccountCreationOverlay(), topMostOverlayContent.Add, true); loadComponentSingleFile(new DialogOverlay(), topMostOverlayContent.Add, true); - loadComponentSingleFile(CreateHighPerformanceSession(), Add); + loadComponentSingleFile(CreateHighPerformanceSessionManager(), Add, true); loadComponentSingleFile(new BackgroundDataStoreProcessor(), Add); diff --git a/osu.Game/Performance/HighPerformanceSession.cs b/osu.Game/Performance/HighPerformanceSession.cs deleted file mode 100644 index 07b5e7da98..0000000000 --- a/osu.Game/Performance/HighPerformanceSession.cs +++ /dev/null @@ -1,42 +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.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Game.Screens.Play; - -namespace osu.Game.Performance -{ - public partial class HighPerformanceSession : Component - { - private readonly IBindable localUserPlaying = new Bindable(); - - [BackgroundDependencyLoader] - private void load(ILocalUserPlayInfo localUserInfo) - { - localUserPlaying.BindTo(localUserInfo.IsPlaying); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - localUserPlaying.BindValueChanged(playing => - { - if (playing.NewValue) - EnableHighPerformanceSession(); - else - DisableHighPerformanceSession(); - }, true); - } - - protected virtual void EnableHighPerformanceSession() - { - } - - protected virtual void DisableHighPerformanceSession() - { - } - } -} diff --git a/osu.Game/Performance/HighPerformanceSessionManager.cs b/osu.Game/Performance/HighPerformanceSessionManager.cs new file mode 100644 index 0000000000..8a49ba0dac --- /dev/null +++ b/osu.Game/Performance/HighPerformanceSessionManager.cs @@ -0,0 +1,34 @@ +// 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.Runtime; +using osu.Framework.Allocation; +using osu.Framework.Graphics; + +namespace osu.Game.Performance +{ + public partial class HighPerformanceSessionManager : Component + { + private GCLatencyMode originalGCMode; + + public IDisposable BeginSession() + { + EnableHighPerformanceSession(); + return new InvokeOnDisposal(this, static m => m.DisableHighPerformanceSession()); + } + + protected virtual void EnableHighPerformanceSession() + { + originalGCMode = GCSettings.LatencyMode; + GCSettings.LatencyMode = GCLatencyMode.LowLatency; + GC.Collect(0); + } + + protected virtual void DisableHighPerformanceSession() + { + if (GCSettings.LatencyMode == GCLatencyMode.LowLatency) + GCSettings.LatencyMode = originalGCMode; + } + } +} From 7a96cf12893c3e5d5d6ec2b679a07230e70efe29 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Feb 2024 21:15:44 +0800 Subject: [PATCH 3248/4092] Expose `AudioFilter.IsAttached` publicly --- osu.Game/Audio/Effects/AudioFilter.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/Audio/Effects/AudioFilter.cs b/osu.Game/Audio/Effects/AudioFilter.cs index c8673372d7..bfa9b31242 100644 --- a/osu.Game/Audio/Effects/AudioFilter.cs +++ b/osu.Game/Audio/Effects/AudioFilter.cs @@ -17,12 +17,15 @@ namespace osu.Game.Audio.Effects /// public const int MAX_LOWPASS_CUTOFF = 22049; // nyquist - 1hz + /// + /// Whether this filter is currently attached to the audio track and thus applying an adjustment. + /// + public bool IsAttached { get; private set; } + private readonly AudioMixer mixer; private readonly BQFParameters filter; private readonly BQFType type; - private bool isAttached; - private readonly Cached filterApplication = new Cached(); private int cutoff; @@ -132,22 +135,22 @@ namespace osu.Game.Audio.Effects private void ensureAttached() { - if (isAttached) + if (IsAttached) return; Debug.Assert(!mixer.Effects.Contains(filter)); mixer.Effects.Add(filter); - isAttached = true; + IsAttached = true; } private void ensureDetached() { - if (!isAttached) + if (!IsAttached) return; Debug.Assert(mixer.Effects.Contains(filter)); mixer.Effects.Remove(filter); - isAttached = false; + IsAttached = false; } protected override void Dispose(bool isDisposing) From c686dfd36164f6eb980f0d96e64700dad95cf8f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Feb 2024 21:16:15 +0800 Subject: [PATCH 3249/4092] Apply safeties for `AudioFilter` usage around drawables which go non-present --- .../Collections/ManageCollectionsDialog.cs | 5 +++ osu.Game/Overlays/DialogOverlay.cs | 8 +++-- osu.Game/Screens/Play/PlayerLoader.cs | 35 +++++++++++++------ 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs index cc0f23d030..16645d6796 100644 --- a/osu.Game/Collections/ManageCollectionsDialog.cs +++ b/osu.Game/Collections/ManageCollectionsDialog.cs @@ -115,6 +115,11 @@ namespace osu.Game.Collections }; } + public override bool IsPresent => base.IsPresent + // Safety for low pass filter potentially getting stuck in applied state due to + // transforms on `this` causing children to no longer be updated. + || lowPassFilter.IsAttached; + protected override void PopIn() { lowPassFilter.CutoffTo(300, 100, Easing.OutCubic); diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index a85f1ecbcd..9ad532ae50 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -27,6 +27,12 @@ namespace osu.Game.Overlays public PopupDialog CurrentDialog { get; private set; } + public override bool IsPresent => Scheduler.HasPendingTasks + || dialogContainer.Children.Count > 0 + // Safety for low pass filter potentially getting stuck in applied state due to + // transforms on `this` causing children to no longer be updated. + || lowPassFilter.IsAttached; + public DialogOverlay() { AutoSizeAxes = Axes.Y; @@ -95,8 +101,6 @@ namespace osu.Game.Overlays } } - public override bool IsPresent => Scheduler.HasPendingTasks || dialogContainer.Children.Count > 0; - protected override bool BlockNonPositionalInput => true; protected override void PopIn() diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index aa62256348..aef7a0739e 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -78,8 +78,8 @@ namespace osu.Game.Screens.Play private readonly BindableDouble volumeAdjustment = new BindableDouble(1); - private AudioFilter lowPassFilter = null!; - private AudioFilter highPassFilter = null!; + private AudioFilter? lowPassFilter; + private AudioFilter? highPassFilter; private SkinnableSound sampleRestart = null!; @@ -158,7 +158,7 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader] - private void load(SessionStatics sessionStatics, AudioManager audio, OsuConfigManager config) + private void load(SessionStatics sessionStatics, OsuConfigManager config) { muteWarningShownOnce = sessionStatics.GetBindable(Static.MutedAudioNotificationShownOnce); batteryWarningShownOnce = sessionStatics.GetBindable(Static.LowBatteryNotificationShownOnce); @@ -205,8 +205,6 @@ namespace osu.Game.Screens.Play }, }, idleTracker = new IdleTracker(750), - lowPassFilter = new AudioFilter(audio.TrackMixer), - highPassFilter = new AudioFilter(audio.TrackMixer, BQFType.HighPass), sampleRestart = new SkinnableSound(new SampleInfo(@"Gameplay/restart", @"pause-retry-click")) }; @@ -284,8 +282,9 @@ namespace osu.Game.Screens.Play // stop the track before removing adjustment to avoid a volume spike. Beatmap.Value.Track.Stop(); Beatmap.Value.Track.RemoveAdjustment(AdjustableProperty.Volume, volumeAdjustment); - lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF); - highPassFilter.CutoffTo(0); + + lowPassFilter?.RemoveAndDisposeImmediately(); + highPassFilter?.RemoveAndDisposeImmediately(); } public override bool OnExiting(ScreenExitEvent e) @@ -425,6 +424,12 @@ namespace osu.Game.Screens.Play settingsScroll.FadeInFromZero(500, Easing.Out) .MoveToX(0, 500, Easing.OutQuint); + AddRangeInternal(new[] + { + lowPassFilter = new AudioFilter(audioManager.TrackMixer), + highPassFilter = new AudioFilter(audioManager.TrackMixer, BQFType.HighPass), + }); + lowPassFilter.CutoffTo(1000, 650, Easing.OutQuint); highPassFilter.CutoffTo(300).Then().CutoffTo(0, 1250); // 1250 is to line up with the appearance of MetadataInfo (750 delay + 500 fade-in) @@ -437,13 +442,23 @@ namespace osu.Game.Screens.Play content.StopTracking(); content.ScaleTo(0.7f, CONTENT_OUT_DURATION * 2, Easing.OutQuint); - content.FadeOut(CONTENT_OUT_DURATION, Easing.OutQuint); + content.FadeOut(CONTENT_OUT_DURATION, Easing.OutQuint) + // Safety for filter potentially getting stuck in applied state due to + // transforms on `this` causing children to no longer be updated. + .OnComplete(_ => + { + highPassFilter?.RemoveAndDisposeImmediately(); + highPassFilter = null; + + lowPassFilter?.RemoveAndDisposeImmediately(); + lowPassFilter = null; + }); settingsScroll.FadeOut(CONTENT_OUT_DURATION, Easing.OutQuint) .MoveToX(settingsScroll.DrawWidth, CONTENT_OUT_DURATION * 2, Easing.OutQuint); - lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, CONTENT_OUT_DURATION); - highPassFilter.CutoffTo(0, CONTENT_OUT_DURATION); + lowPassFilter?.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, CONTENT_OUT_DURATION); + highPassFilter?.CutoffTo(0, CONTENT_OUT_DURATION); } private void pushWhenLoaded() From bfb5098238b451fc768ae855854ac9b925b57816 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 26 Feb 2024 22:13:15 +0900 Subject: [PATCH 3250/4092] Use high performance session during gameplay --- .../Performance/HighPerformanceSessionManager.cs | 4 ++++ osu.Game/Screens/Play/PlayerLoader.cs | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/osu.Game/Performance/HighPerformanceSessionManager.cs b/osu.Game/Performance/HighPerformanceSessionManager.cs index 8a49ba0dac..c5146933b7 100644 --- a/osu.Game/Performance/HighPerformanceSessionManager.cs +++ b/osu.Game/Performance/HighPerformanceSessionManager.cs @@ -22,6 +22,8 @@ namespace osu.Game.Performance { originalGCMode = GCSettings.LatencyMode; GCSettings.LatencyMode = GCLatencyMode.LowLatency; + + // Without doing this, the new GC mode won't kick in until the next GC, which could be at a more noticeable point in time. GC.Collect(0); } @@ -29,6 +31,8 @@ namespace osu.Game.Performance { if (GCSettings.LatencyMode == GCLatencyMode.LowLatency) GCSettings.LatencyMode = originalGCMode; + + // No GC.Collect() as we were already collecting at a higher frequency in the old mode. } } } diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index aa62256348..fe235dd56d 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -25,6 +25,7 @@ using osu.Game.Input; using osu.Game.Localisation; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; +using osu.Game.Performance; using osu.Game.Screens.Menu; using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Skinning; @@ -140,6 +141,8 @@ namespace osu.Game.Screens.Play private bool quickRestart; + private IDisposable? highPerformanceSession; + [Resolved] private INotificationOverlay? notificationOverlay { get; set; } @@ -152,6 +155,9 @@ namespace osu.Game.Screens.Play [Resolved] private BatteryInfo? batteryInfo { get; set; } + [Resolved] + private HighPerformanceSessionManager? highPerformanceSessionManager { get; set; } + public PlayerLoader(Func createPlayer) { this.createPlayer = createPlayer; @@ -264,6 +270,9 @@ namespace osu.Game.Screens.Play Debug.Assert(CurrentPlayer != null); + highPerformanceSession?.Dispose(); + highPerformanceSession = null; + // prepare for a retry. CurrentPlayer = null; playerConsumed = false; @@ -304,6 +313,9 @@ namespace osu.Game.Screens.Play BackgroundBrightnessReduction = false; Beatmap.Value.Track.RemoveAdjustment(AdjustableProperty.Volume, volumeAdjustment); + highPerformanceSession?.Dispose(); + highPerformanceSession = null; + return base.OnExiting(e); } @@ -463,6 +475,10 @@ namespace osu.Game.Screens.Play if (scheduledPushPlayer != null) return; + // Now that everything's been loaded, we can safely switch to a higher performance session without incurring too much overhead. + // Doing this prior to the game being pushed gives us a bit of time to stabilise into the high performance mode before gameplay starts. + 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). From d6622c1756c57569f85a1b1df25f7042e7515a8a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Feb 2024 22:24:35 +0800 Subject: [PATCH 3251/4092] Add test coverage of fast menu keypresses failing to register --- .../Navigation/TestSceneButtonSystemNavigation.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneButtonSystemNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneButtonSystemNavigation.cs index c6d67f2bc6..43b160250c 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneButtonSystemNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneButtonSystemNavigation.cs @@ -28,6 +28,21 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert("state is top level", () => buttons.State == ButtonSystemState.TopLevel); } + [Test] + public void TestFastShortcutKeys() + { + AddAssert("state is initial", () => buttons.State == ButtonSystemState.Initial); + + AddStep("press P three times", () => + { + InputManager.Key(Key.P); + InputManager.Key(Key.P); + InputManager.Key(Key.P); + }); + + AddAssert("entered song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect); + } + [Test] public void TestShortcutKeys() { From 4c6e8a606fbb669c5949910bc2026435180e3eb8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Feb 2024 22:22:43 +0800 Subject: [PATCH 3252/4092] Fix main menu eating keys if user presses too fast --- osu.Game/Screens/Menu/MainMenuButton.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Menu/MainMenuButton.cs b/osu.Game/Screens/Menu/MainMenuButton.cs index 422599a4a8..1dc79e9b1a 100644 --- a/osu.Game/Screens/Menu/MainMenuButton.cs +++ b/osu.Game/Screens/Menu/MainMenuButton.cs @@ -64,6 +64,10 @@ namespace osu.Game.Screens.Menu private Sample? sampleHover; private SampleChannel? sampleChannel; + public override bool IsPresent => base.IsPresent + // Allow keyboard interaction based on state rather than waiting for delayed animations. + || state == ButtonState.Expanded; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos); public MainMenuButton(LocalisableString text, string sampleName, IconUsage symbol, Color4 colour, Action? clickAction = null, float extraWidth = 0, params Key[] triggerKeys) From 8032ce9225950f5fc5d6dba911833e9b3f8afa93 Mon Sep 17 00:00:00 2001 From: Detze <92268414+Detze@users.noreply.github.com> Date: Mon, 26 Feb 2024 18:37:27 +0100 Subject: [PATCH 3253/4092] Match stable's slider border thickness perfectly --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs index 6bf6776617..af82a81e08 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs @@ -26,8 +26,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private const float shadow_portion = 1 - (OsuLegacySkinTransformer.LEGACY_CIRCLE_RADIUS / OsuHitObject.OBJECT_RADIUS); protected new float CalculatedBorderPortion - // Roughly matches osu!stable's slider border portions. - => base.CalculatedBorderPortion * 0.84f; + // Matches osu!stable's slider border portions. + => 0.109375f; protected override Color4 ColourAt(float position) { From 2b73d816a70fab655cdf7a9f716d6630b333ed54 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 26 Feb 2024 21:26:35 +0300 Subject: [PATCH 3254/4092] Bring back mod setting tracker in `BeatmapAttributesDisplay` --- osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs | 12 +++++++++++- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 7 +------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index 7035af1594..c58cf710bd 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -13,6 +13,7 @@ using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -43,6 +44,8 @@ namespace osu.Game.Overlays.Mods public BindableBool Collapsed { get; } = new BindableBool(true); + private ModSettingChangeTracker? modSettingChangeTracker; + [Resolved] private BeatmapDifficultyCache difficultyCache { get; set; } = null!; @@ -97,7 +100,14 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); - Mods.BindValueChanged(_ => updateValues()); + Mods.BindValueChanged(_ => + { + modSettingChangeTracker?.Dispose(); + modSettingChangeTracker = new ModSettingChangeTracker(Mods.Value); + modSettingChangeTracker.SettingChanged += _ => updateValues(); + updateValues(); + }, true); + BeatmapInfo.BindValueChanged(_ => updateValues()); Collapsed.BindValueChanged(_ => diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index bf43dc3d9c..b7e2f744fa 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -468,12 +468,7 @@ namespace osu.Game.Overlays.Mods } if (beatmapAttributesDisplay != null) - { - if (!ReferenceEquals(beatmapAttributesDisplay.Mods.Value, mods)) - beatmapAttributesDisplay.Mods.Value = mods; - else - beatmapAttributesDisplay.Mods.TriggerChange(); // mods list may be same but a mod setting has changed, trigger change in that case. - } + beatmapAttributesDisplay.Mods.Value = mods; } private void updateCustomisation() From 3f9fbb931849a5f6276de638e621a1e341149cc3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 26 Feb 2024 22:25:04 +0300 Subject: [PATCH 3255/4092] Introduce the concept of `ActiveMods` in mod select overlay and rewrite once more --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 37 +++++++++++++------ .../OnlinePlay/Match/RoomModSelectOverlay.cs | 21 +++-------- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index b7e2f744fa..6b80ff6e44 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -42,6 +42,14 @@ namespace osu.Game.Overlays.Mods [Cached] public Bindable> SelectedMods { get; private set; } = new Bindable>(Array.Empty()); + /// + /// Contains a list of mods which should read from to display effects on the selected beatmap. + /// + /// + /// This is different from in screens like online-play rooms, where there are required mods activated from the playlist. + /// + public Bindable> ActiveMods { get; private set; } = new Bindable>(Array.Empty()); + /// /// Contains a dictionary with the current of all mods applicable for the current ruleset. /// @@ -313,22 +321,29 @@ namespace osu.Game.Overlays.Mods if (AllowCustomisation) ((IBindable>)modSettingsArea.SelectedMods).BindTo(SelectedMods); - SelectedMods.BindValueChanged(_ => + SelectedMods.BindValueChanged(mods => { - UpdateOverlayInformation(SelectedMods.Value); + var newMods = ActiveMods.Value.Except(mods.OldValue).Concat(mods.NewValue).ToList(); + ActiveMods.Value = newMods; + updateFromExternalSelection(); updateCustomisation(); + }, true); + + ActiveMods.BindValueChanged(_ => + { + updateOverlayInformation(); modSettingChangeTracker?.Dispose(); if (AllowCustomisation) { - // Importantly, use SelectedMods.Value here (and not the ValueChanged NewValue) as the latter can + // Importantly, use ActiveMods.Value here (and not the ValueChanged NewValue) as the latter can // potentially be stale, due to complexities in the way change trackers work. // // See https://github.com/ppy/osu/pull/23284#issuecomment-1529056988 - modSettingChangeTracker = new ModSettingChangeTracker(SelectedMods.Value); - modSettingChangeTracker.SettingChanged += _ => UpdateOverlayInformation(SelectedMods.Value); + modSettingChangeTracker = new ModSettingChangeTracker(ActiveMods.Value); + modSettingChangeTracker.SettingChanged += _ => updateOverlayInformation(); } }, true); @@ -451,24 +466,24 @@ namespace osu.Game.Overlays.Mods } /// - /// Updates any information displayed on the overlay regarding the effects of the selected mods. + /// Updates any information displayed on the overlay regarding the effects of the active mods. + /// This reads from instead of . /// - /// The list of mods to show effect from. This can be overriden to include effect of mods that are not part of the bindable (e.g. room mods in multiplayer/playlists). - protected virtual void UpdateOverlayInformation(IReadOnlyList mods) + private void updateOverlayInformation() { if (rankingInformationDisplay != null) { double multiplier = 1.0; - foreach (var mod in mods) + foreach (var mod in ActiveMods.Value) multiplier *= mod.ScoreMultiplier; rankingInformationDisplay.ModMultiplier.Value = multiplier; - rankingInformationDisplay.Ranked.Value = mods.All(m => m.Ranked); + rankingInformationDisplay.Ranked.Value = ActiveMods.Value.All(m => m.Ranked); } if (beatmapAttributesDisplay != null) - beatmapAttributesDisplay.Mods.Value = mods; + beatmapAttributesDisplay.Mods.Value = ActiveMods.Value; } private void updateCustomisation() diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs index db7cfe980c..d3fd6de911 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.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.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; @@ -10,7 +9,6 @@ using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; namespace osu.Game.Screens.OnlinePlay.Match { @@ -22,8 +20,6 @@ namespace osu.Game.Screens.OnlinePlay.Match [Resolved] private RulesetStore rulesets { get; set; } = null!; - private readonly List roomMods = new List(); - public RoomModSelectOverlay() : base(OverlayColourScheme.Plum) { @@ -33,22 +29,17 @@ namespace osu.Game.Screens.OnlinePlay.Match { base.LoadComplete(); - selectedItem.BindValueChanged(_ => + selectedItem.BindValueChanged(v => { - roomMods.Clear(); - - if (selectedItem.Value is PlaylistItem item) + if (v.NewValue is PlaylistItem item) { var rulesetInstance = rulesets.GetRuleset(item.RulesetID)?.CreateInstance(); Debug.Assert(rulesetInstance != null); - roomMods.AddRange(item.RequiredMods.Select(m => m.ToMod(rulesetInstance))); + ActiveMods.Value = item.RequiredMods.Select(m => m.ToMod(rulesetInstance)).Concat(SelectedMods.Value).ToList(); } - - SelectedMods.TriggerChange(); - }); + else + ActiveMods.Value = SelectedMods.Value; + }, true); } - - protected override void UpdateOverlayInformation(IReadOnlyList mods) - => base.UpdateOverlayInformation(roomMods.Concat(mods).ToList()); } } From 2151e0a2947e4c2cc4b5b071c2af1fce852359b5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 26 Feb 2024 22:25:20 +0300 Subject: [PATCH 3256/4092] Improve test coverage --- .../TestSceneMultiplayerMatchSubScreen.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 4bedc31f38..bdfe01ba09 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -22,6 +22,7 @@ using osu.Game.Overlays; using osu.Game.Overlays.Dialog; using osu.Game.Overlays.Mods; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Taiko; @@ -303,7 +304,6 @@ namespace osu.Game.Tests.Visual.Multiplayer AllowedMods = new[] { new APIMod(new OsuModFlashlight()), - new APIMod(new OsuModHardRock()), } }); }); @@ -312,8 +312,14 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for join", () => RoomJoined); ClickButtonWhenEnabled(); - AddAssert("mod select shows unranked", () => this.ChildrenOfType().Single().Ranked.Value == false); - AddAssert("mod select shows different multiplier", () => !this.ChildrenOfType().Single().ModMultiplier.IsDefault); + AddAssert("mod select shows unranked", () => screen.UserModsSelectOverlay.ChildrenOfType().Single().Ranked.Value == false); + AddAssert("score multiplier = 1.20", () => screen.UserModsSelectOverlay.ChildrenOfType().Single().ModMultiplier.Value, () => Is.EqualTo(1.2).Within(0.01)); + + AddStep("select flashlight", () => screen.UserModsSelectOverlay.ChildrenOfType().Single(m => m.Mod is ModFlashlight).TriggerClick()); + AddAssert("score multiplier = 1.35", () => screen.UserModsSelectOverlay.ChildrenOfType().Single().ModMultiplier.Value, () => Is.EqualTo(1.35).Within(0.01)); + + AddStep("change flashlight setting", () => ((OsuModFlashlight)screen.UserModsSelectOverlay.SelectedMods.Value.Single()).FollowDelay.Value = 1200); + AddAssert("score multiplier = 1.20", () => screen.UserModsSelectOverlay.ChildrenOfType().Single().ModMultiplier.Value, () => Is.EqualTo(1.2).Within(0.01)); } private partial class TestMultiplayerMatchSubScreen : MultiplayerMatchSubScreen From 8363c39da86b4dde7f700ebf3c3a908a4dca945e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 27 Feb 2024 01:30:20 +0300 Subject: [PATCH 3257/4092] Revert "Match stable's slider border thickness perfectly" This reverts commit 8032ce9225950f5fc5d6dba911833e9b3f8afa93. --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs index af82a81e08..6bf6776617 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs @@ -26,8 +26,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private const float shadow_portion = 1 - (OsuLegacySkinTransformer.LEGACY_CIRCLE_RADIUS / OsuHitObject.OBJECT_RADIUS); protected new float CalculatedBorderPortion - // Matches osu!stable's slider border portions. - => 0.109375f; + // Roughly matches osu!stable's slider border portions. + => base.CalculatedBorderPortion * 0.84f; protected override Color4 ColourAt(float position) { From e01722a266e887b6032b520ca3f0fcd0011df51e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 27 Feb 2024 01:30:20 +0300 Subject: [PATCH 3258/4092] Revert "Match stable's slider border thickness more closely" This reverts commit 3502ec456d0131f4f6c1c9faabf01e6197a16ee1. --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs index 6bf6776617..b39092a467 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy protected new float CalculatedBorderPortion // Roughly matches osu!stable's slider border portions. - => base.CalculatedBorderPortion * 0.84f; + => base.CalculatedBorderPortion * 0.77f; protected override Color4 ColourAt(float position) { From 81e6a6d96a0f62f0ce015e232217ee8b207ea76f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 27 Feb 2024 02:11:32 +0300 Subject: [PATCH 3259/4092] Rewrite `LegacySliderBody` rendering to perfectly match stable --- .../Skinning/Legacy/LegacySliderBody.cs | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs index b39092a467..264bb3145d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs @@ -23,29 +23,29 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private partial class LegacyDrawableSliderPath : DrawableSliderPath { - private const float shadow_portion = 1 - (OsuLegacySkinTransformer.LEGACY_CIRCLE_RADIUS / OsuHitObject.OBJECT_RADIUS); - - protected new float CalculatedBorderPortion - // Roughly matches osu!stable's slider border portions. - => base.CalculatedBorderPortion * 0.77f; - protected override Color4 ColourAt(float position) { - float realBorderPortion = shadow_portion + CalculatedBorderPortion; - float realGradientPortion = 1 - realBorderPortion; - - if (position <= shadow_portion) - return new Color4(0f, 0f, 0f, 0.25f * position / shadow_portion); - - if (position <= realBorderPortion) - return BorderColour; - - position -= realBorderPortion; + const float aa_width = 0.5f / 64f; + const float shadow_portion = 1 - (OsuLegacySkinTransformer.LEGACY_CIRCLE_RADIUS / OsuHitObject.OBJECT_RADIUS); + const float border_portion = 0.1875f; + Color4 shadow = new Color4(0, 0, 0, 0.25f); Color4 outerColour = AccentColour.Darken(0.1f); Color4 innerColour = lighten(AccentColour, 0.5f); - return LegacyUtils.InterpolateNonLinear(position / realGradientPortion, outerColour, innerColour, 0, 1); + if (position <= shadow_portion - aa_width) + return LegacyUtils.InterpolateNonLinear(position, Color4.Black.Opacity(0f), shadow, 0, shadow_portion - aa_width); + + if (position <= shadow_portion + aa_width) + return LegacyUtils.InterpolateNonLinear(position, shadow, BorderColour, shadow_portion - aa_width, shadow_portion + aa_width); + + if (position <= border_portion - aa_width) + return BorderColour; + + if (position <= border_portion + aa_width) + return LegacyUtils.InterpolateNonLinear(position, BorderColour, outerColour, border_portion - aa_width, border_portion + aa_width); + + return LegacyUtils.InterpolateNonLinear(position, outerColour, innerColour, border_portion + aa_width, 1); } /// From 2f547751826c391b765b9f0096f7f18b5649a5d8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 27 Feb 2024 02:16:16 +0300 Subject: [PATCH 3260/4092] Add stable code references --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs index 264bb3145d..fa2b7863db 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs @@ -25,14 +25,17 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { protected override Color4 ColourAt(float position) { + // https://github.com/peppy/osu-stable-reference/blob/3ea48705eb67172c430371dcfc8a16a002ed0d3d/osu!/Graphics/Renderers/MmSliderRendererGL.cs#L99 const float aa_width = 0.5f / 64f; - const float shadow_portion = 1 - (OsuLegacySkinTransformer.LEGACY_CIRCLE_RADIUS / OsuHitObject.OBJECT_RADIUS); - const float border_portion = 0.1875f; Color4 shadow = new Color4(0, 0, 0, 0.25f); Color4 outerColour = AccentColour.Darken(0.1f); Color4 innerColour = lighten(AccentColour, 0.5f); + // https://github.com/peppy/osu-stable-reference/blob/3ea48705eb67172c430371dcfc8a16a002ed0d3d/osu!/Graphics/Renderers/MmSliderRendererGL.cs#L59-L70 + const float shadow_portion = 1 - (OsuLegacySkinTransformer.LEGACY_CIRCLE_RADIUS / OsuHitObject.OBJECT_RADIUS); + const float border_portion = 0.1875f; + if (position <= shadow_portion - aa_width) return LegacyUtils.InterpolateNonLinear(position, Color4.Black.Opacity(0f), shadow, 0, shadow_portion - aa_width); From 18e26e39feb2fb386b433595724a4970ac841617 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 27 Feb 2024 02:20:34 +0300 Subject: [PATCH 3261/4092] Remove `SliderBorderSize` for simplicity --- osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs | 1 - osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs index fb31f88d3c..bda1e6cf41 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs @@ -44,7 +44,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default SnakingOut.BindTo(configSnakingOut); - BorderSize = skin.GetConfig(OsuSkinConfiguration.SliderBorderSize)?.Value ?? 1; BorderColour = skin.GetConfig(OsuSkinColour.SliderBorder)?.Value ?? Color4.White; } diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs index 77fea9d8f7..9685ab685d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs @@ -5,7 +5,6 @@ namespace osu.Game.Rulesets.Osu.Skinning { public enum OsuSkinConfiguration { - SliderBorderSize, SliderPathRadius, CursorCentre, CursorExpand, From 83af9dfb39d222a02fa45c726f1e50d43b27867f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 27 Feb 2024 02:43:02 +0300 Subject: [PATCH 3262/4092] Fix `aa_width` being incorrect --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs index fa2b7863db..a00014ab88 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy protected override Color4 ColourAt(float position) { // https://github.com/peppy/osu-stable-reference/blob/3ea48705eb67172c430371dcfc8a16a002ed0d3d/osu!/Graphics/Renderers/MmSliderRendererGL.cs#L99 - const float aa_width = 0.5f / 64f; + const float aa_width = 3f / 256f; Color4 shadow = new Color4(0, 0, 0, 0.25f); Color4 outerColour = AccentColour.Darken(0.1f); From f2d52fbaa2656b1a334d32b8142b56ff44c5bbb1 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 27 Feb 2024 17:33:24 +0900 Subject: [PATCH 3263/4092] Make class not overrideable --- osu.Game/OsuGame.cs | 4 +--- osu.Game/Performance/HighPerformanceSessionManager.cs | 8 ++++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index f23a78d2dc..13e80e0707 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -792,8 +792,6 @@ namespace osu.Game protected virtual UpdateManager CreateUpdateManager() => new UpdateManager(); - protected virtual HighPerformanceSessionManager CreateHighPerformanceSessionManager() => new HighPerformanceSessionManager(); - protected override Container CreateScalingContainer() => new ScalingContainer(ScalingMode.Everything); #region Beatmap progression @@ -1088,7 +1086,7 @@ namespace osu.Game loadComponentSingleFile(new AccountCreationOverlay(), topMostOverlayContent.Add, true); loadComponentSingleFile(new DialogOverlay(), topMostOverlayContent.Add, true); - loadComponentSingleFile(CreateHighPerformanceSessionManager(), Add, true); + loadComponentSingleFile(new HighPerformanceSessionManager(), Add, true); loadComponentSingleFile(new BackgroundDataStoreProcessor(), Add); diff --git a/osu.Game/Performance/HighPerformanceSessionManager.cs b/osu.Game/Performance/HighPerformanceSessionManager.cs index c5146933b7..22c929083c 100644 --- a/osu.Game/Performance/HighPerformanceSessionManager.cs +++ b/osu.Game/Performance/HighPerformanceSessionManager.cs @@ -14,11 +14,11 @@ namespace osu.Game.Performance public IDisposable BeginSession() { - EnableHighPerformanceSession(); - return new InvokeOnDisposal(this, static m => m.DisableHighPerformanceSession()); + enableHighPerformanceSession(); + return new InvokeOnDisposal(this, static m => m.disableHighPerformanceSession()); } - protected virtual void EnableHighPerformanceSession() + private void enableHighPerformanceSession() { originalGCMode = GCSettings.LatencyMode; GCSettings.LatencyMode = GCLatencyMode.LowLatency; @@ -27,7 +27,7 @@ namespace osu.Game.Performance GC.Collect(0); } - protected virtual void DisableHighPerformanceSession() + private void disableHighPerformanceSession() { if (GCSettings.LatencyMode == GCLatencyMode.LowLatency) GCSettings.LatencyMode = originalGCMode; From dc3b41865cbf8ebb0b31f5d6d9ae042a93a14356 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 27 Feb 2024 19:17:34 +0900 Subject: [PATCH 3264/4092] Add logging --- osu.Game/Performance/HighPerformanceSessionManager.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Performance/HighPerformanceSessionManager.cs b/osu.Game/Performance/HighPerformanceSessionManager.cs index 22c929083c..304ab44975 100644 --- a/osu.Game/Performance/HighPerformanceSessionManager.cs +++ b/osu.Game/Performance/HighPerformanceSessionManager.cs @@ -5,6 +5,7 @@ using System; using System.Runtime; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Logging; namespace osu.Game.Performance { @@ -20,6 +21,8 @@ namespace osu.Game.Performance private void enableHighPerformanceSession() { + Logger.Log("Starting high performance session"); + originalGCMode = GCSettings.LatencyMode; GCSettings.LatencyMode = GCLatencyMode.LowLatency; @@ -29,6 +32,8 @@ namespace osu.Game.Performance private void disableHighPerformanceSession() { + Logger.Log("Ending high performance session"); + if (GCSettings.LatencyMode == GCLatencyMode.LowLatency) GCSettings.LatencyMode = originalGCMode; From 069b400dd08e21ce8e52369e5708721680b2c1f4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 27 Feb 2024 19:36:03 +0900 Subject: [PATCH 3265/4092] Move manager to desktop game --- osu.Desktop/OsuGameDesktop.cs | 6 ++++++ .../Performance/HighPerformanceSessionManager.cs | 6 +++--- osu.Game/OsuGame.cs | 3 --- .../Performance/IHighPerformanceSessionManager.cs | 12 ++++++++++++ osu.Game/Screens/Play/PlayerLoader.cs | 2 +- 5 files changed, 22 insertions(+), 7 deletions(-) rename {osu.Game => osu.Desktop}/Performance/HighPerformanceSessionManager.cs (90%) create mode 100644 osu.Game/Performance/IHighPerformanceSessionManager.cs diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index a0db896f46..5b5f5c2167 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -7,6 +7,7 @@ using System.IO; using System.Reflection; using System.Runtime.Versioning; using Microsoft.Win32; +using osu.Desktop.Performance; using osu.Desktop.Security; using osu.Framework.Platform; using osu.Game; @@ -15,9 +16,11 @@ using osu.Framework; using osu.Framework.Logging; using osu.Game.Updater; using osu.Desktop.Windows; +using osu.Framework.Allocation; using osu.Game.IO; using osu.Game.IPC; using osu.Game.Online.Multiplayer; +using osu.Game.Performance; using osu.Game.Utils; using SDL2; @@ -28,6 +31,9 @@ namespace osu.Desktop private OsuSchemeLinkIPCChannel? osuSchemeLinkIPCChannel; private ArchiveImportIPCChannel? archiveImportIPCChannel; + [Cached(typeof(IHighPerformanceSessionManager))] + private readonly HighPerformanceSessionManager highPerformanceSessionManager = new HighPerformanceSessionManager(); + public OsuGameDesktop(string[]? args = null) : base(args) { diff --git a/osu.Game/Performance/HighPerformanceSessionManager.cs b/osu.Desktop/Performance/HighPerformanceSessionManager.cs similarity index 90% rename from osu.Game/Performance/HighPerformanceSessionManager.cs rename to osu.Desktop/Performance/HighPerformanceSessionManager.cs index 304ab44975..eb2b3be5b9 100644 --- a/osu.Game/Performance/HighPerformanceSessionManager.cs +++ b/osu.Desktop/Performance/HighPerformanceSessionManager.cs @@ -4,12 +4,12 @@ using System; using System.Runtime; using osu.Framework.Allocation; -using osu.Framework.Graphics; using osu.Framework.Logging; +using osu.Game.Performance; -namespace osu.Game.Performance +namespace osu.Desktop.Performance { - public partial class HighPerformanceSessionManager : Component + public class HighPerformanceSessionManager : IHighPerformanceSessionManager { private GCLatencyMode originalGCMode; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 13e80e0707..f65557c6bb 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -55,7 +55,6 @@ using osu.Game.Overlays.Notifications; using osu.Game.Overlays.SkinEditor; using osu.Game.Overlays.Toolbar; using osu.Game.Overlays.Volume; -using osu.Game.Performance; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens; @@ -1086,8 +1085,6 @@ namespace osu.Game loadComponentSingleFile(new AccountCreationOverlay(), topMostOverlayContent.Add, true); loadComponentSingleFile(new DialogOverlay(), topMostOverlayContent.Add, true); - loadComponentSingleFile(new HighPerformanceSessionManager(), Add, true); - loadComponentSingleFile(new BackgroundDataStoreProcessor(), Add); Add(difficultyRecommender); diff --git a/osu.Game/Performance/IHighPerformanceSessionManager.cs b/osu.Game/Performance/IHighPerformanceSessionManager.cs new file mode 100644 index 0000000000..826a0a04f5 --- /dev/null +++ b/osu.Game/Performance/IHighPerformanceSessionManager.cs @@ -0,0 +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 System; + +namespace osu.Game.Performance +{ + public interface IHighPerformanceSessionManager + { + IDisposable BeginSession(); + } +} diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index fe235dd56d..00a4a86c71 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -156,7 +156,7 @@ namespace osu.Game.Screens.Play private BatteryInfo? batteryInfo { get; set; } [Resolved] - private HighPerformanceSessionManager? highPerformanceSessionManager { get; set; } + private IHighPerformanceSessionManager? highPerformanceSessionManager { get; set; } public PlayerLoader(Func createPlayer) { From bbdd85020cc755ee75d8227468bc27c06bb67505 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 27 Feb 2024 11:45:03 +0100 Subject: [PATCH 3266/4092] Fix slider tails sometimes not dimming correctly Originally noticed during review of another change: https://github.com/ppy/osu/pull/27369#issuecomment-1966140198. `DrawableOsuHitObject` tries to solve the initial dimming of objects by applying transform to a list of dimmable parts. For plain drawables this is safe, but if one of the parts is a DHO, it is not safe, because drawable transforms can be cleared at will. In particular, on first use of a drawable slider, `UpdateInitialTransforms()` would fire via `LoadComplete()` on the `DrawableSlider`, but *then*, also via `LoadComplete()`, the `DrawableSliderTail` would update its own state and by doing so inadvertently clear the dim transform just added by the slider. To fix, ensure dim transforms are applied to DHOs via `ApplyCustomUpdateState`. --- .../Objects/Drawables/DrawableOsuHitObject.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 5271c03e08..35cab6459b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -80,6 +80,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.UpdateInitialTransforms(); foreach (var piece in DimmablePieces) + { + // if the specified dimmable piece is a DHO, it is generally not safe to tack transforms onto it directly + // as they may be cleared via the `updateState()` DHO flow, + // so use `ApplyCustomUpdateState` instead. which does not have this pitfall. + if (piece is DrawableHitObject drawableObjectPiece) + drawableObjectPiece.ApplyCustomUpdateState += (dho, _) => applyDim(dho); + else + applyDim(piece); + } + + void applyDim(Drawable piece) { piece.FadeColour(new Color4(195, 195, 195, 255)); using (piece.BeginDelayedSequence(InitialLifetimeOffset - OsuHitWindows.MISS_WINDOW)) From 189c680555e82222bf7115305b5156cb12d3adc8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Feb 2024 20:46:25 +0800 Subject: [PATCH 3267/4092] Add basic xmldoc on interface type --- .../Performance/IHighPerformanceSessionManager.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Performance/IHighPerformanceSessionManager.cs b/osu.Game/Performance/IHighPerformanceSessionManager.cs index 826a0a04f5..d3d1fda8fc 100644 --- a/osu.Game/Performance/IHighPerformanceSessionManager.cs +++ b/osu.Game/Performance/IHighPerformanceSessionManager.cs @@ -5,8 +5,19 @@ using System; namespace osu.Game.Performance { + /// + /// Allows creating a temporary "high performance" session, with the goal of optimising runtime + /// performance for gameplay purposes. + /// + /// On desktop platforms, this will set a low latency GC mode which collects more frequently to avoid + /// GC spikes. + /// public interface IHighPerformanceSessionManager { + /// + /// Start a new high performance session. + /// + /// An which will end the session when disposed. IDisposable BeginSession(); } } From 87509fbf6efc9e14979a97fdb14b5fc6b56bdf16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 27 Feb 2024 13:47:19 +0100 Subject: [PATCH 3268/4092] Privatise registry-related constants Don't see any reason for them to be public. --- .../Windows/WindowsAssociationManager.cs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/osu.Desktop/Windows/WindowsAssociationManager.cs b/osu.Desktop/Windows/WindowsAssociationManager.cs index 2a1aeba7e0..c784d52a4f 100644 --- a/osu.Desktop/Windows/WindowsAssociationManager.cs +++ b/osu.Desktop/Windows/WindowsAssociationManager.cs @@ -15,27 +15,27 @@ namespace osu.Desktop.Windows [SupportedOSPlatform("windows")] public static class WindowsAssociationManager { - public const string SOFTWARE_CLASSES = @"Software\Classes"; + private const string software_classes = @"Software\Classes"; /// /// Sub key for setting the icon. /// https://learn.microsoft.com/en-us/windows/win32/com/defaulticon /// - public const string DEFAULT_ICON = @"DefaultIcon"; + private const string default_icon = @"DefaultIcon"; /// /// Sub key for setting the command line that the shell invokes. /// https://learn.microsoft.com/en-us/windows/win32/com/shell /// - public const string SHELL_OPEN_COMMAND = @"Shell\Open\Command"; + internal const string SHELL_OPEN_COMMAND = @"Shell\Open\Command"; - public static readonly string EXE_PATH = Path.ChangeExtension(typeof(WindowsAssociationManager).Assembly.Location, ".exe").Replace('/', '\\'); + private static readonly string exe_path = Path.ChangeExtension(typeof(WindowsAssociationManager).Assembly.Location, ".exe").Replace('/', '\\'); /// /// Program ID prefix used for file associations. Should be relatively short since the full program ID has a 39 character limit, /// see https://learn.microsoft.com/en-us/windows/win32/com/-progid--key. /// - public const string PROGRAM_ID_PREFIX = "osu.File"; + private const string program_id_prefix = "osu.File"; private static readonly FileAssociation[] file_associations = { @@ -177,24 +177,24 @@ namespace osu.Desktop.Windows private record FileAssociation(string Extension, LocalisableString Description, string IconPath) { - private string programId => $@"{PROGRAM_ID_PREFIX}{Extension}"; + private string programId => $@"{program_id_prefix}{Extension}"; /// /// Installs a file extenstion association in accordance with https://learn.microsoft.com/en-us/windows/win32/com/-progid--key /// public void Install() { - using var classes = Registry.CurrentUser.OpenSubKey(SOFTWARE_CLASSES, true); + using var classes = Registry.CurrentUser.OpenSubKey(software_classes, true); if (classes == null) return; // register a program id for the given extension using (var programKey = classes.CreateSubKey(programId)) { - using (var defaultIconKey = programKey.CreateSubKey(DEFAULT_ICON)) + using (var defaultIconKey = programKey.CreateSubKey(default_icon)) defaultIconKey.SetValue(null, IconPath); using (var openCommandKey = programKey.CreateSubKey(SHELL_OPEN_COMMAND)) - openCommandKey.SetValue(null, $@"""{EXE_PATH}"" ""%1"""); + openCommandKey.SetValue(null, $@"""{exe_path}"" ""%1"""); } using (var extensionKey = classes.CreateSubKey(Extension)) @@ -211,7 +211,7 @@ namespace osu.Desktop.Windows public void UpdateDescription(string description) { - using var classes = Registry.CurrentUser.OpenSubKey(SOFTWARE_CLASSES, true); + using var classes = Registry.CurrentUser.OpenSubKey(software_classes, true); if (classes == null) return; using (var programKey = classes.OpenSubKey(programId, true)) @@ -223,7 +223,7 @@ namespace osu.Desktop.Windows /// public void Uninstall() { - using var classes = Registry.CurrentUser.OpenSubKey(SOFTWARE_CLASSES, true); + using var classes = Registry.CurrentUser.OpenSubKey(software_classes, true); if (classes == null) return; using (var extensionKey = classes.OpenSubKey(Extension, true)) @@ -254,24 +254,24 @@ namespace osu.Desktop.Windows /// public void Install() { - using var classes = Registry.CurrentUser.OpenSubKey(SOFTWARE_CLASSES, true); + using var classes = Registry.CurrentUser.OpenSubKey(software_classes, true); if (classes == null) return; using (var protocolKey = classes.CreateSubKey(Protocol)) { protocolKey.SetValue(URL_PROTOCOL, string.Empty); - using (var defaultIconKey = protocolKey.CreateSubKey(DEFAULT_ICON)) + using (var defaultIconKey = protocolKey.CreateSubKey(default_icon)) defaultIconKey.SetValue(null, IconPath); using (var openCommandKey = protocolKey.CreateSubKey(SHELL_OPEN_COMMAND)) - openCommandKey.SetValue(null, $@"""{EXE_PATH}"" ""%1"""); + openCommandKey.SetValue(null, $@"""{exe_path}"" ""%1"""); } } public void UpdateDescription(string description) { - using var classes = Registry.CurrentUser.OpenSubKey(SOFTWARE_CLASSES, true); + using var classes = Registry.CurrentUser.OpenSubKey(software_classes, true); if (classes == null) return; using (var protocolKey = classes.OpenSubKey(Protocol, true)) @@ -280,7 +280,7 @@ namespace osu.Desktop.Windows public void Uninstall() { - using var classes = Registry.CurrentUser.OpenSubKey(SOFTWARE_CLASSES, true); + using var classes = Registry.CurrentUser.OpenSubKey(software_classes, true); classes?.DeleteSubKeyTree(Protocol, throwOnMissingSubKey: false); } } From 09dee50372f85c38a9432edd76ef3e4f7c5ad1fa Mon Sep 17 00:00:00 2001 From: Gabriel Del Nero <43073074+Gabixel@users.noreply.github.com> Date: Tue, 27 Feb 2024 15:35:51 +0100 Subject: [PATCH 3269/4092] Change initial scroll animation to mod columns Starting from the end (which should be fine with the current number of columns, even on different/wider screen resolutions), and with a custom decay value when it reaches zero offset. --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index ce1d0d27a3..a5fc75aea3 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -341,12 +341,12 @@ namespace osu.Game.Overlays.Mods column.SearchTerm = query.NewValue; }, true); - // Start scrolled slightly to the right to give the user a sense that + // Start scrolling from the end, to give the user a sense that // there is more horizontal content available. ScheduleAfterChildren(() => { - columnScroll.ScrollTo(200, false); - columnScroll.ScrollToStart(); + columnScroll.ScrollToEnd(false); + columnScroll.ScrollTo(0, true, 0.0055); }); } From b3aa9e25d2f3ca99eda2f3e0ac6d7a3dedbd1f61 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 27 Feb 2024 23:18:11 +0300 Subject: [PATCH 3270/4092] Disable legacy slider AA for now --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs index a00014ab88..b54bb44f94 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs @@ -26,7 +26,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy protected override Color4 ColourAt(float position) { // https://github.com/peppy/osu-stable-reference/blob/3ea48705eb67172c430371dcfc8a16a002ed0d3d/osu!/Graphics/Renderers/MmSliderRendererGL.cs#L99 - const float aa_width = 3f / 256f; + // float aaWidth = Math.Min(Math.Max(0.5f / PathRadius, 3.0f / 256.0f), 1.0f / 16.0f); + // applying the aa_width constant from stable makes sliders blurry, especially on CS>5. set to zero for now. + // this might be related to SmoothPath applying AA internally, but disabling that does not seem to have much of an effect. + const float aa_width = 0f; Color4 shadow = new Color4(0, 0, 0, 0.25f); Color4 outerColour = AccentColour.Darken(0.1f); From e053c08f6b137dcf5b62850ad5c17052780c4f71 Mon Sep 17 00:00:00 2001 From: jvyden Date: Tue, 27 Feb 2024 16:23:36 -0500 Subject: [PATCH 3271/4092] Hide social interactions while in Do Not Disturb --- osu.Desktop/DiscordRichPresence.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 1944a73c0a..e118b3d6c0 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -95,8 +95,10 @@ namespace osu.Desktop if (activity.Value != null) { bool hideIdentifiableInformation = privacyMode.Value == DiscordRichPresenceMode.Limited; + bool hideInteractions = status.Value == UserStatus.DoNotDisturb && activity.Value is UserActivity.InLobby; + presence.State = truncate(activity.Value.GetStatus(hideIdentifiableInformation)); - presence.Details = truncate(activity.Value.GetDetails(hideIdentifiableInformation) ?? string.Empty); + presence.Details = truncate(activity.Value.GetDetails(hideIdentifiableInformation || hideInteractions) ?? string.Empty); if (getBeatmapID(activity.Value) is int beatmapId && beatmapId > 0) { From d83aeb73e4186219e1143ba7a289b02efff1c59a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 28 Feb 2024 01:02:34 +0300 Subject: [PATCH 3272/4092] Fix menu cursor tracing rotation while override by gameplay cursor --- osu.Game/Graphics/Cursor/MenuCursorContainer.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Graphics/Cursor/MenuCursorContainer.cs b/osu.Game/Graphics/Cursor/MenuCursorContainer.cs index 8cf47006ab..7e42d45191 100644 --- a/osu.Game/Graphics/Cursor/MenuCursorContainer.cs +++ b/osu.Game/Graphics/Cursor/MenuCursorContainer.cs @@ -220,12 +220,16 @@ namespace osu.Game.Graphics.Cursor { activeCursor.FadeTo(1, 250, Easing.OutQuint); activeCursor.ScaleTo(1, 400, Easing.OutQuint); + activeCursor.RotateTo(0, 400, Easing.OutQuint); + dragRotationState = DragRotationState.NotDragging; } protected override void PopOut() { activeCursor.FadeTo(0, 250, Easing.OutQuint); activeCursor.ScaleTo(0.6f, 250, Easing.In); + activeCursor.RotateTo(0, 400, Easing.OutQuint); + dragRotationState = DragRotationState.NotDragging; } private void playTapSample(double baseFrequency = 1f) From c69c881cd373175859d97ce5a536a8827e6a0ddb Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 28 Feb 2024 07:58:02 +0300 Subject: [PATCH 3273/4092] Combine conditionals and remove "InLobby" check --- osu.Desktop/DiscordRichPresence.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index e118b3d6c0..f0da708766 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -94,11 +94,10 @@ namespace osu.Desktop if (activity.Value != null) { - bool hideIdentifiableInformation = privacyMode.Value == DiscordRichPresenceMode.Limited; - bool hideInteractions = status.Value == UserStatus.DoNotDisturb && activity.Value is UserActivity.InLobby; + bool hideIdentifiableInformation = privacyMode.Value == DiscordRichPresenceMode.Limited || status.Value == UserStatus.DoNotDisturb; presence.State = truncate(activity.Value.GetStatus(hideIdentifiableInformation)); - presence.Details = truncate(activity.Value.GetDetails(hideIdentifiableInformation || hideInteractions) ?? string.Empty); + presence.Details = truncate(activity.Value.GetDetails(hideIdentifiableInformation) ?? string.Empty); if (getBeatmapID(activity.Value) is int beatmapId && beatmapId > 0) { From 351160f94e34712103cc7d16d2ee21ee007660a6 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 27 Feb 2024 22:41:49 -0800 Subject: [PATCH 3274/4092] Move back/quit button from bottom left to fail overlay when spectating --- osu.Game/Screens/Play/FailOverlay.cs | 17 +---------------- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 10 ++++++++++ osu.Game/Screens/Play/PauseOverlay.cs | 10 +--------- osu.Game/Screens/Play/Player.cs | 4 ++-- osu.Game/Screens/Play/SpectatorPlayer.cs | 2 -- 5 files changed, 14 insertions(+), 29 deletions(-) diff --git a/osu.Game/Screens/Play/FailOverlay.cs b/osu.Game/Screens/Play/FailOverlay.cs index 210ae5ceb6..f14cdfc213 100644 --- a/osu.Game/Screens/Play/FailOverlay.cs +++ b/osu.Game/Screens/Play/FailOverlay.cs @@ -6,10 +6,8 @@ using System; using System.Threading.Tasks; using osu.Game.Scoring; -using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osuTK; -using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -26,22 +24,9 @@ namespace osu.Game.Screens.Play public override LocalisableString Header => GameplayMenuOverlayStrings.FailedHeader; - private readonly bool showButtons; - - public FailOverlay(bool showButtons = true) - { - this.showButtons = showButtons; - } - [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { - if (showButtons) - { - AddButton(GameplayMenuOverlayStrings.Retry, colours.YellowDark, () => OnRetry?.Invoke()); - AddButton(GameplayMenuOverlayStrings.Quit, new Color4(170, 27, 39, 255), () => OnQuit?.Invoke()); - } - // from #10339 maybe this is a better visual effect Add(new Container { diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 440b8d37b9..be4b8ff9fe 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -38,6 +38,7 @@ namespace osu.Game.Screens.Play public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + public Action? OnResume; public Action? OnRetry; public Action? OnQuit; @@ -129,6 +130,15 @@ namespace osu.Game.Screens.Play }, }; + if (OnResume != null) + AddButton(GameplayMenuOverlayStrings.Continue, colours.Green, () => OnResume.Invoke()); + + if (OnRetry != null) + AddButton(GameplayMenuOverlayStrings.Retry, colours.YellowDark, () => OnRetry.Invoke()); + + if (OnQuit != null) + AddButton(GameplayMenuOverlayStrings.Quit, new Color4(170, 27, 39, 255), () => OnQuit.Invoke()); + State.ValueChanged += _ => InternalButtons.Deselect(); updateInfoText(); diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs index 2aa2793fd4..c8e8275641 100644 --- a/osu.Game/Screens/Play/PauseOverlay.cs +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -11,18 +11,14 @@ using osu.Framework.Graphics; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Audio; -using osu.Game.Graphics; using osu.Game.Input.Bindings; using osu.Game.Localisation; using osu.Game.Skinning; -using osuTK.Graphics; namespace osu.Game.Screens.Play { public partial class PauseOverlay : GameplayMenuOverlay { - public Action OnResume; - public override bool IsPresent => base.IsPresent || pauseLoop.IsPlaying; public override LocalisableString Header => GameplayMenuOverlayStrings.PausedHeader; @@ -38,12 +34,8 @@ namespace osu.Game.Screens.Play }; [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { - AddButton(GameplayMenuOverlayStrings.Continue, colours.Green, () => OnResume?.Invoke()); - AddButton(GameplayMenuOverlayStrings.Retry, colours.YellowDark, () => OnRetry?.Invoke()); - AddButton(GameplayMenuOverlayStrings.Quit, new Color4(170, 27, 39, 255), () => OnQuit?.Invoke()); - AddInternal(pauseLoop = new SkinnableSound(new SampleInfo("Gameplay/pause-loop")) { Looping = true, diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 11ff5fdbef..88f6ae9e71 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -278,10 +278,10 @@ namespace osu.Game.Screens.Play createGameplayComponents(Beatmap.Value) } }, - FailOverlay = new FailOverlay(Configuration.AllowUserInteraction) + FailOverlay = new FailOverlay { SaveReplay = async () => await prepareAndImportScoreAsync(true).ConfigureAwait(false), - OnRetry = () => Restart(), + OnRetry = Configuration.AllowUserInteraction ? () => Restart() : null, OnQuit = () => PerformExit(true), }, new HotkeyExitOverlay diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index 2faead0ee1..d1404ac184 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -25,8 +25,6 @@ namespace osu.Game.Screens.Play private readonly Score score; - public override bool AllowBackButton => true; - protected override bool CheckModsAllowFailure() { if (!allowFail) From dee57c7e72275fb87fabacbbc802775418e95185 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 27 Feb 2024 22:42:36 -0800 Subject: [PATCH 3275/4092] Refactor test to only allow init of actions --- .../Gameplay/TestSceneGameplayMenuOverlay.cs | 43 ++++++------------- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 6 +-- 2 files changed, 16 insertions(+), 33 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs index aa5e5985c3..73028e6df9 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs @@ -3,7 +3,6 @@ #nullable disable -using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -26,6 +25,8 @@ namespace osu.Game.Tests.Visual.Gameplay private GlobalActionContainer globalActionContainer; + private bool triggeredRetryButton; + [BackgroundDependencyLoader] private void load(OsuGameBase game) { @@ -35,12 +36,18 @@ namespace osu.Game.Tests.Visual.Gameplay [SetUp] public void SetUp() => Schedule(() => { + triggeredRetryButton = false; + globalActionContainer.Children = new Drawable[] { pauseOverlay = new PauseOverlay { OnResume = () => Logger.Log(@"Resume"), - OnRetry = () => Logger.Log(@"Retry"), + OnRetry = () => + { + Logger.Log(@"Retry"); + triggeredRetryButton = true; + }, OnQuit = () => Logger.Log(@"Quit"), }, failOverlay = new FailOverlay @@ -224,17 +231,9 @@ namespace osu.Game.Tests.Visual.Gameplay { showOverlay(); - bool triggered = false; - AddStep("Click retry button", () => - { - var lastAction = pauseOverlay.OnRetry; - pauseOverlay.OnRetry = () => triggered = true; + AddStep("Click retry button", () => getButton(1).TriggerClick()); - getButton(1).TriggerClick(); - pauseOverlay.OnRetry = lastAction; - }); - - AddAssert("Action was triggered", () => triggered); + AddAssert("Retry was triggered", () => triggeredRetryButton); AddAssert("Overlay is closed", () => pauseOverlay.State.Value == Visibility.Hidden); } @@ -252,25 +251,9 @@ namespace osu.Game.Tests.Visual.Gameplay InputManager.Key(Key.Down); }); - bool triggered = false; - Action lastAction = null; - AddStep("Press enter", () => - { - lastAction = pauseOverlay.OnRetry; - pauseOverlay.OnRetry = () => triggered = true; - InputManager.Key(Key.Enter); - }); + AddStep("Press enter", () => InputManager.Key(Key.Enter)); - AddAssert("Action was triggered", () => - { - if (lastAction != null) - { - pauseOverlay.OnRetry = lastAction; - lastAction = null; - } - - return triggered; - }); + AddAssert("Retry was triggered", () => triggeredRetryButton); AddAssert("Overlay is closed", () => pauseOverlay.State.Value == Visibility.Hidden); } diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index be4b8ff9fe..da239d585e 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -38,9 +38,9 @@ namespace osu.Game.Screens.Play public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; - public Action? OnResume; - public Action? OnRetry; - public Action? OnQuit; + public Action? OnResume { get; init; } + public Action? OnRetry { get; init; } + public Action? OnQuit { get; init; } /// /// Action that is invoked when is triggered. From e8a106177771cc23112f8f42e6cc9adb725736cc Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 27 Feb 2024 23:16:20 -0800 Subject: [PATCH 3276/4092] Add test for spectator player exit --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 1c7ede2b19..caf3a05518 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -25,6 +25,7 @@ using osu.Game.Tests.Gameplay; using osu.Game.Tests.Visual.Multiplayer; using osu.Game.Tests.Visual.Spectator; using osuTK; +using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { @@ -214,6 +215,9 @@ namespace osu.Game.Tests.Visual.Gameplay checkPaused(false); // Should continue playing until out of frames checkPaused(true); // And eventually stop after running out of frames and fail. // Todo: Should check for + display a failed message. + + AddStep("exit player", () => InputManager.Key(Key.Escape)); + AddAssert("player exited", () => Stack.CurrentScreen is SoloSpectatorScreen); } [Test] From 8e462fbb38e4ffb41f3cf7cf9e7775978d03464b Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 27 Feb 2024 23:24:04 -0800 Subject: [PATCH 3277/4092] Apply NRT to touched files --- .../Gameplay/TestSceneGameplayMenuOverlay.cs | 8 +++----- .../Visual/Gameplay/TestSceneSpectator.cs | 20 +++++++++---------- osu.Game/Screens/Play/FailOverlay.cs | 4 +--- osu.Game/Screens/Play/PauseOverlay.cs | 4 +--- .../Screens/Play/SaveFailedScoreButton.cs | 17 ++++++++++------ osu.Game/Screens/Play/SoloSpectatorPlayer.cs | 3 +-- osu.Game/Screens/Play/SpectatorPlayer.cs | 11 ++++------ 7 files changed, 30 insertions(+), 37 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs index 73028e6df9..3501c1a521 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -20,10 +18,10 @@ namespace osu.Game.Tests.Visual.Gameplay [Description("player pause/fail screens")] public partial class TestSceneGameplayMenuOverlay : OsuManualInputManagerTestScene { - private FailOverlay failOverlay; - private PauseOverlay pauseOverlay; + private FailOverlay failOverlay = null!; + private PauseOverlay pauseOverlay = null!; - private GlobalActionContainer globalActionContainer; + private GlobalActionContainer globalActionContainer = null!; private bool triggeredRetryButton; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index caf3a05518..c8356cd191 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -40,13 +38,13 @@ namespace osu.Game.Tests.Visual.Gameplay protected override bool UseOnlineAPI => true; [Resolved] - private OsuGameBase game { get; set; } + private OsuGameBase game { get; set; } = null!; private TestSpectatorClient spectatorClient => dependenciesScreen.SpectatorClient; - private DependenciesScreen dependenciesScreen; - private SoloSpectatorScreen spectatorScreen; + private DependenciesScreen dependenciesScreen = null!; + private SoloSpectatorScreen spectatorScreen = null!; - private BeatmapSetInfo importedBeatmap; + private BeatmapSetInfo importedBeatmap = null!; private int importedBeatmapId; [SetUpSteps] @@ -189,7 +187,7 @@ namespace osu.Game.Tests.Visual.Gameplay waitForPlayerCurrent(); - Player lastPlayer = null; + Player lastPlayer = null!; AddStep("store first player", () => lastPlayer = player); start(); @@ -282,7 +280,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestFinalFrameInBundleHasHeader() { - FrameDataBundle lastBundle = null; + FrameDataBundle? lastBundle = null; AddStep("bind to client", () => spectatorClient.OnNewFrames += (_, bundle) => lastBundle = bundle); @@ -291,8 +289,8 @@ namespace osu.Game.Tests.Visual.Gameplay finish(); AddUntilStep("bundle received", () => lastBundle != null); - AddAssert("first frame does not have header", () => lastBundle.Frames[0].Header == null); - AddAssert("last frame has header", () => lastBundle.Frames[^1].Header != null); + AddAssert("first frame does not have header", () => lastBundle?.Frames[0].Header == null); + AddAssert("last frame has header", () => lastBundle?.Frames[^1].Header != null); } [Test] @@ -387,7 +385,7 @@ namespace osu.Game.Tests.Visual.Gameplay } private OsuFramedReplayInputHandler replayHandler => - (OsuFramedReplayInputHandler)Stack.ChildrenOfType().First().ReplayInputHandler; + (OsuFramedReplayInputHandler)Stack.ChildrenOfType().First().ReplayInputHandler!; private Player player => this.ChildrenOfType().Single(); diff --git a/osu.Game/Screens/Play/FailOverlay.cs b/osu.Game/Screens/Play/FailOverlay.cs index f14cdfc213..4a0a6f573c 100644 --- a/osu.Game/Screens/Play/FailOverlay.cs +++ b/osu.Game/Screens/Play/FailOverlay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Threading.Tasks; using osu.Game.Scoring; @@ -20,7 +18,7 @@ namespace osu.Game.Screens.Play { public partial class FailOverlay : GameplayMenuOverlay { - public Func> SaveReplay; + public Func>? SaveReplay; public override LocalisableString Header => GameplayMenuOverlayStrings.FailedHeader; diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs index c8e8275641..3a471acba4 100644 --- a/osu.Game/Screens/Play/PauseOverlay.cs +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Framework.Allocation; @@ -23,7 +21,7 @@ namespace osu.Game.Screens.Play public override LocalisableString Header => GameplayMenuOverlayStrings.PausedHeader; - private SkinnableSound pauseLoop; + private SkinnableSound pauseLoop = null!; protected override Action BackAction => () => { diff --git a/osu.Game/Screens/Play/SaveFailedScoreButton.cs b/osu.Game/Screens/Play/SaveFailedScoreButton.cs index b97c140250..ef27aac1b9 100644 --- a/osu.Game/Screens/Play/SaveFailedScoreButton.cs +++ b/osu.Game/Screens/Play/SaveFailedScoreButton.cs @@ -30,13 +30,13 @@ namespace osu.Game.Screens.Play private readonly Bindable state = new Bindable(); - private readonly Func> importFailedScore; + private readonly Func>? importFailedScore; private ScoreInfo? importedScore; private DownloadButton button = null!; - public SaveFailedScoreButton(Func> importFailedScore) + public SaveFailedScoreButton(Func>? importFailedScore) { Size = new Vector2(50, 30); @@ -60,11 +60,16 @@ namespace osu.Game.Screens.Play case DownloadState.NotDownloaded: state.Value = DownloadState.Importing; - Task.Run(importFailedScore).ContinueWith(t => + + if (importFailedScore != null) { - importedScore = realm.Run(r => r.Find(t.GetResultSafely().ID)?.Detach()); - Schedule(() => state.Value = importedScore != null ? DownloadState.LocallyAvailable : DownloadState.NotDownloaded); - }).FireAndForget(); + Task.Run(importFailedScore).ContinueWith(t => + { + importedScore = realm.Run(r => r.Find(t.GetResultSafely().ID)?.Detach()); + Schedule(() => state.Value = importedScore != null ? DownloadState.LocallyAvailable : DownloadState.NotDownloaded); + }).FireAndForget(); + } + break; } } diff --git a/osu.Game/Screens/Play/SoloSpectatorPlayer.cs b/osu.Game/Screens/Play/SoloSpectatorPlayer.cs index 8d25a0148d..3dafd5f752 100644 --- a/osu.Game/Screens/Play/SoloSpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SoloSpectatorPlayer.cs @@ -50,8 +50,7 @@ namespace osu.Game.Screens.Play { base.Dispose(isDisposing); - if (SpectatorClient != null) - SpectatorClient.OnUserBeganPlaying -= userBeganPlaying; + SpectatorClient.OnUserBeganPlaying -= userBeganPlaying; } } } diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index d1404ac184..520fb43445 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -21,7 +19,7 @@ namespace osu.Game.Screens.Play public abstract partial class SpectatorPlayer : Player { [Resolved] - protected SpectatorClient SpectatorClient { get; private set; } + protected SpectatorClient SpectatorClient { get; private set; } = null!; private readonly Score score; @@ -35,7 +33,7 @@ namespace osu.Game.Screens.Play private bool allowFail; - protected SpectatorPlayer(Score score, PlayerConfiguration configuration = null) + protected SpectatorPlayer(Score score, PlayerConfiguration? configuration = null) : base(configuration) { this.score = score; @@ -98,7 +96,7 @@ namespace osu.Game.Screens.Play foreach (var frame in bundle.Frames) { - IConvertibleReplayFrame convertibleFrame = GameplayState.Ruleset.CreateConvertibleReplayFrame(); + IConvertibleReplayFrame convertibleFrame = GameplayState.Ruleset.CreateConvertibleReplayFrame()!; Debug.Assert(convertibleFrame != null); convertibleFrame.FromLegacy(frame, GameplayState.Beatmap); @@ -134,8 +132,7 @@ namespace osu.Game.Screens.Play { base.Dispose(isDisposing); - if (SpectatorClient != null) - SpectatorClient.OnNewFrames -= userSentFrames; + SpectatorClient.OnNewFrames -= userSentFrames; } } } From 6e03384c6bffba55b403c20cb566bf88c7252b12 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 28 Feb 2024 00:01:16 -0800 Subject: [PATCH 3278/4092] Apply NRT to `SoloSpectatorPlayer` --- osu.Game/Screens/Play/SoloSpectatorPlayer.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Play/SoloSpectatorPlayer.cs b/osu.Game/Screens/Play/SoloSpectatorPlayer.cs index 3dafd5f752..2cc1c4a368 100644 --- a/osu.Game/Screens/Play/SoloSpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SoloSpectatorPlayer.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.Framework.Allocation; using osu.Framework.Screens; using osu.Game.Online.Spectator; From 3fb2662d74a16bf7258d0b38777e599558d214a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Feb 2024 12:05:33 +0100 Subject: [PATCH 3279/4092] Update framework Mainly for the sake of https://github.com/ppy/osu-framework/pull/6196. --- 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 30037c868c..1b395a7c83 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 463a726856..747d6059da 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From b5ce2642aacc33cd47879d72a18e51714936e11b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Feb 2024 13:20:41 +0100 Subject: [PATCH 3280/4092] Fix subscribing to `ApplyCustomUpdateState` too much --- .../Objects/Drawables/DrawableOsuHitObject.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 35cab6459b..5f5deca1ba 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -85,7 +85,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // as they may be cleared via the `updateState()` DHO flow, // so use `ApplyCustomUpdateState` instead. which does not have this pitfall. if (piece is DrawableHitObject drawableObjectPiece) - drawableObjectPiece.ApplyCustomUpdateState += (dho, _) => applyDim(dho); + { + // this method can be called multiple times, and we don't want to subscribe to the event more than once, + // so this is what it is going to have to be... + drawableObjectPiece.ApplyCustomUpdateState -= applyDimToDrawableHitObject; + drawableObjectPiece.ApplyCustomUpdateState += applyDimToDrawableHitObject; + } else applyDim(piece); } @@ -96,6 +101,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables using (piece.BeginDelayedSequence(InitialLifetimeOffset - OsuHitWindows.MISS_WINDOW)) piece.FadeColour(Color4.White, 100); } + + void applyDimToDrawableHitObject(DrawableHitObject dho, ArmedState _) => applyDim(dho); } protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt; From ce994a7a733eacd1bd5b1c30aacda217b071e8b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Feb 2024 13:31:06 +0100 Subject: [PATCH 3281/4092] Fix wireframe misalignment in argon accuracy counter - Closes https://github.com/ppy/osu/issues/27385. - Supersedes / closes https://github.com/ppy/osu/pull/27392. --- osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs | 6 +++--- osu.Game/Screens/Play/HUD/ArgonComboCounter.cs | 5 ++++- .../Screens/Play/HUD/ArgonCounterTextComponent.cs | 15 ++++++++++++--- osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs | 6 ++++-- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs index ca00ab12c7..4cc04e1485 100644 --- a/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs @@ -75,21 +75,21 @@ namespace osu.Game.Screens.Play.HUD AutoSizeAxes = Axes.Both, Child = wholePart = new ArgonCounterTextComponent(Anchor.TopRight, BeatmapsetsStrings.ShowScoreboardHeadersAccuracy.ToUpper()) { - RequiredDisplayDigits = { Value = 3 }, WireframeOpacity = { BindTarget = WireframeOpacity }, + WireframeTemplate = @"###", ShowLabel = { BindTarget = ShowLabel }, } }, fractionPart = new ArgonCounterTextComponent(Anchor.TopLeft) { - RequiredDisplayDigits = { Value = 2 }, WireframeOpacity = { BindTarget = WireframeOpacity }, + WireframeTemplate = @".##", Scale = new Vector2(0.5f), }, percentText = new ArgonCounterTextComponent(Anchor.TopLeft) { Text = @"%", - RequiredDisplayDigits = { Value = 1 }, + WireframeTemplate = @"#", WireframeOpacity = { BindTarget = WireframeOpacity } }, } diff --git a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs index 369c753cb0..3f187650b2 100644 --- a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs @@ -68,7 +68,10 @@ namespace osu.Game.Screens.Play.HUD private void updateWireframe() { - text.RequiredDisplayDigits.Value = getDigitsRequiredForDisplayCount(); + int digitsRequiredForDisplayCount = getDigitsRequiredForDisplayCount(); + + if (digitsRequiredForDisplayCount != text.WireframeTemplate.Length) + text.WireframeTemplate = new string('#', digitsRequiredForDisplayCount); } private int getDigitsRequiredForDisplayCount() diff --git a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs index f8c82feddd..efb4d2108e 100644 --- a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs +++ b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs @@ -25,7 +25,6 @@ namespace osu.Game.Screens.Play.HUD private readonly OsuSpriteText labelText; public IBindable WireframeOpacity { get; } = new BindableFloat(); - public Bindable RequiredDisplayDigits { get; } = new BindableInt(); public Bindable ShowLabel { get; } = new BindableBool(); public Container NumberContainer { get; private set; } @@ -36,6 +35,18 @@ namespace osu.Game.Screens.Play.HUD set => textPart.Text = value; } + /// + /// The template for the wireframe displayed behind the . + /// Any character other than a dot is interpreted to mean a full segmented display "wireframe". + /// + public string WireframeTemplate + { + get => wireframeTemplate; + set => wireframesPart.Text = wireframeTemplate = value; + } + + private string wireframeTemplate = string.Empty; + public ArgonCounterTextComponent(Anchor anchor, LocalisableString? label = null) { Anchor = anchor; @@ -69,8 +80,6 @@ namespace osu.Game.Screens.Play.HUD } } }; - - RequiredDisplayDigits.BindValueChanged(digits => wireframesPart.Text = new string('#', digits.NewValue)); } private string textLookup(char c) diff --git a/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs b/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs index 44b9fb3123..7e0dd161dc 100644 --- a/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs @@ -58,8 +58,10 @@ namespace osu.Game.Screens.Play.HUD private void updateWireframe() { - scoreText.RequiredDisplayDigits.Value = - Math.Max(RequiredDisplayDigits.Value, getDigitsRequiredForDisplayCount()); + int digitsRequiredForDisplayCount = Math.Max(RequiredDisplayDigits.Value, getDigitsRequiredForDisplayCount()); + + if (digitsRequiredForDisplayCount != scoreText.WireframeTemplate.Length) + scoreText.WireframeTemplate = new string('#', Math.Max(RequiredDisplayDigits.Value, digitsRequiredForDisplayCount)); } private int getDigitsRequiredForDisplayCount() From f49aa4d8157eb6368263f919c662dd0afdaca1d0 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Wed, 28 Feb 2024 22:01:39 +0800 Subject: [PATCH 3282/4092] Parse points and segments for path string --- .../Objects/Legacy/ConvertHitObjectParser.cs | 57 ++++++++++++------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index f9e32fe26f..30e4101a84 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -266,30 +266,49 @@ namespace osu.Game.Rulesets.Objects.Legacy // This code takes on the responsibility of handling explicit segments of the path ("X" & "Y" from above). Implicit segments are handled by calls to convertPoints(). string[] pointSplit = pointString.Split('|'); - var controlPoints = new List>(); - int startIndex = 0; - int endIndex = 0; - bool first = true; + Span points = stackalloc Vector2[pointSplit.Length]; + Span<(PathType Type, int StartIndex)> segments = stackalloc (PathType Type, int StartIndex)[pointSplit.Length]; + int pointsCount = 0; + int segmentsCount = 0; - while (++endIndex < pointSplit.Length) + foreach (string s in pointSplit) { - // Keep incrementing endIndex while it's not the start of a new segment (indicated by having an alpha character at position 0). - if (!char.IsLetter(pointSplit[endIndex][0])) - continue; - - // Multi-segmented sliders DON'T contain the end point as part of the current segment as it's assumed to be the start of the next segment. - // The start of the next segment is the index after the type descriptor. - string endPoint = endIndex < pointSplit.Length - 1 ? pointSplit[endIndex + 1] : null; - - controlPoints.AddRange(convertPoints(pointSplit.AsMemory().Slice(startIndex, endIndex - startIndex), endPoint, first, offset)); - startIndex = endIndex; - first = false; + if (char.IsLetter(s[0])) + { + // The start of a new segment(indicated by having an alpha character at position 0). + var pathType = convertPathType(s); + segments[segmentsCount++] = (pathType, pointsCount); + } + else + { + points[pointsCount++] = readPoint(s, offset); + } } - if (endIndex > startIndex) - controlPoints.AddRange(convertPoints(pointSplit.AsMemory().Slice(startIndex, endIndex - startIndex), null, first, offset)); + var controlPoints = new List(pointsCount); - return mergePointsLists(controlPoints); + for (int i = 0; i < segmentsCount; i++) + { + int startIndex = segments[i].StartIndex; + int endIndex = i < segmentsCount - 1 ? segments[i + 1].StartIndex : pointsCount; + Vector2? endPoint = i < segmentsCount - 1 ? points[endIndex] : null; + controlPoints.AddRange(convertPoints(segments[i].Type, points[startIndex..endIndex], endPoint)); + } + + return controlPoints.ToArray(); + + static Vector2 readPoint(string value, Vector2 startPos) + { + string[] vertexSplit = value.Split(':'); + + Vector2 pos = new Vector2((int)Parsing.ParseDouble(vertexSplit[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseDouble(vertexSplit[1], Parsing.MAX_COORDINATE_VALUE)) - startPos; + return pos; + } + } + + private IEnumerable convertPoints(PathType type, ReadOnlySpan points, Vector2? endPoint) + { + throw new NotImplementedException(); } /// From 4bff54d35dbd60aab7cd8a255977fe95e5c3bde7 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Wed, 28 Feb 2024 22:37:14 +0800 Subject: [PATCH 3283/4092] Add ToString on PathControlPoint for debugging --- osu.Game/Rulesets/Objects/PathControlPoint.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Rulesets/Objects/PathControlPoint.cs b/osu.Game/Rulesets/Objects/PathControlPoint.cs index ae9fa08085..1f8e63b269 100644 --- a/osu.Game/Rulesets/Objects/PathControlPoint.cs +++ b/osu.Game/Rulesets/Objects/PathControlPoint.cs @@ -76,5 +76,9 @@ namespace osu.Game.Rulesets.Objects } public bool Equals(PathControlPoint other) => Position == other?.Position && Type == other.Type; + + public override string ToString() => type is null + ? $"Position={Position}" + : $"Position={Position}, Type={type}"; } } From fe34577ee2938c45d295800405a309ad3d55ac3e Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Wed, 28 Feb 2024 22:42:08 +0800 Subject: [PATCH 3284/4092] Update parsing. --- .../Objects/Legacy/ConvertHitObjectParser.cs | 86 ++++++------------- 1 file changed, 24 insertions(+), 62 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 30e4101a84..2d4d2865e6 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -278,6 +278,10 @@ namespace osu.Game.Rulesets.Objects.Legacy // The start of a new segment(indicated by having an alpha character at position 0). var pathType = convertPathType(s); segments[segmentsCount++] = (pathType, pointsCount); + + // First segment is prepended by an extra zero point + if (pointsCount == 0) + points[pointsCount++] = Vector2.Zero; } else { @@ -306,47 +310,30 @@ namespace osu.Game.Rulesets.Objects.Legacy } } - private IEnumerable convertPoints(PathType type, ReadOnlySpan points, Vector2? endPoint) - { - throw new NotImplementedException(); - } - /// /// Converts a given point list into a set of path segments. /// + /// The path type of the point list. /// The point list. /// Any extra endpoint to consider as part of the points. This will NOT be returned. - /// Whether this is the first segment in the set. If true the first of the returned segments will contain a zero point. - /// The positional offset to apply to the control points. - /// The set of points contained by as one or more segments of the path, prepended by an extra zero point if is true. - private IEnumerable> convertPoints(ReadOnlyMemory points, string endPoint, bool first, Vector2 offset) + /// The set of points contained by as one or more segments of the path. + private IEnumerable convertPoints(PathType type, ReadOnlySpan points, Vector2? endPoint) { - PathType type = convertPathType(points.Span[0]); - - int readOffset = first ? 1 : 0; // First control point is zero for the first segment. - int readablePoints = points.Length - 1; // Total points readable from the base point span. - int endPointLength = endPoint != null ? 1 : 0; // Extra length if an endpoint is given that lies outside the base point span. - - var vertices = new PathControlPoint[readOffset + readablePoints + endPointLength]; - - // Fill any non-read points. - for (int i = 0; i < readOffset; i++) - vertices[i] = new PathControlPoint(); + var vertices = new PathControlPoint[points.Length]; + var result = new List(); // Parse into control points. - for (int i = 1; i < points.Length; i++) - readPoint(points.Span[i], offset, out vertices[readOffset + i - 1]); - - // If an endpoint is given, add it to the end. - if (endPoint != null) - readPoint(endPoint, offset, out vertices[^1]); + for (int i = 0; i < points.Length; i++) + vertices[i] = new PathControlPoint { Position = points[i] }; // Edge-case rules (to match stable). if (type == PathType.PERFECT_CURVE) { - if (vertices.Length != 3) + int endPointLength = endPoint is null ? 0 : 1; + + if (vertices.Length + endPointLength != 3) type = PathType.BEZIER; - else if (isLinear(vertices)) + else if (isLinear(stackalloc[] { points[0], points[1], endPoint ?? points[2] })) { // osu-stable special-cased colinear perfect curves to a linear path type = PathType.LINEAR; @@ -365,7 +352,7 @@ namespace osu.Game.Rulesets.Objects.Legacy int startIndex = 0; int endIndex = 0; - while (++endIndex < vertices.Length - endPointLength) + while (++endIndex < vertices.Length) { // Keep incrementing while an implicit segment doesn't need to be started. if (vertices[endIndex].Position != vertices[endIndex - 1].Position) @@ -378,50 +365,25 @@ namespace osu.Game.Rulesets.Objects.Legacy continue; // The last control point of each segment is not allowed to start a new implicit segment. - if (endIndex == vertices.Length - endPointLength - 1) + if (endIndex == vertices.Length - 1) continue; // Force a type on the last point, and return the current control point set as a segment. vertices[endIndex - 1].Type = type; - yield return vertices.AsMemory().Slice(startIndex, endIndex - startIndex); + for (int i = startIndex; i < endIndex; i++) + result.Add(vertices[i]); // Skip the current control point - as it's the same as the one that's just been returned. startIndex = endIndex + 1; } - if (endIndex > startIndex) - yield return vertices.AsMemory().Slice(startIndex, endIndex - startIndex); + for (int i = startIndex; i < endIndex; i++) + result.Add(vertices[i]); - static void readPoint(string value, Vector2 startPos, out PathControlPoint point) - { - string[] vertexSplit = value.Split(':'); + return result; - Vector2 pos = new Vector2((int)Parsing.ParseDouble(vertexSplit[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseDouble(vertexSplit[1], Parsing.MAX_COORDINATE_VALUE)) - startPos; - point = new PathControlPoint { Position = pos }; - } - - static bool isLinear(PathControlPoint[] p) => Precision.AlmostEquals(0, (p[1].Position.Y - p[0].Position.Y) * (p[2].Position.X - p[0].Position.X) - - (p[1].Position.X - p[0].Position.X) * (p[2].Position.Y - p[0].Position.Y)); - } - - private PathControlPoint[] mergePointsLists(List> controlPointList) - { - int totalCount = 0; - - foreach (var arr in controlPointList) - totalCount += arr.Length; - - var mergedArray = new PathControlPoint[totalCount]; - var mergedArrayMemory = mergedArray.AsMemory(); - int copyIndex = 0; - - foreach (var arr in controlPointList) - { - arr.CopyTo(mergedArrayMemory.Slice(copyIndex)); - copyIndex += arr.Length; - } - - return mergedArray; + static bool isLinear(ReadOnlySpan p) => Precision.AlmostEquals(0, (p[1].Y - p[0].Y) * (p[2].X - p[0].X) + - (p[1].X - p[0].X) * (p[2].Y - p[0].Y)); } /// From bcb91f348d746bd4470240c23c72ec812fc54113 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Wed, 28 Feb 2024 22:51:36 +0800 Subject: [PATCH 3285/4092] Use ArrayPool instead of stackalloc --- .../Objects/Legacy/ConvertHitObjectParser.cs | 82 ++++++++++--------- 1 file changed, 44 insertions(+), 38 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 2d4d2865e6..a3ca719ff9 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -18,6 +18,7 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Legacy; using osu.Game.Skinning; using osu.Game.Utils; +using System.Buffers; namespace osu.Game.Rulesets.Objects.Legacy { @@ -266,41 +267,49 @@ namespace osu.Game.Rulesets.Objects.Legacy // This code takes on the responsibility of handling explicit segments of the path ("X" & "Y" from above). Implicit segments are handled by calls to convertPoints(). string[] pointSplit = pointString.Split('|'); - Span points = stackalloc Vector2[pointSplit.Length]; - Span<(PathType Type, int StartIndex)> segments = stackalloc (PathType Type, int StartIndex)[pointSplit.Length]; + var points = ArrayPool.Shared.Rent(pointSplit.Length); + var segments = ArrayPool<(PathType Type, int StartIndex)>.Shared.Rent(pointSplit.Length); int pointsCount = 0; int segmentsCount = 0; - - foreach (string s in pointSplit) + try { - if (char.IsLetter(s[0])) - { - // The start of a new segment(indicated by having an alpha character at position 0). - var pathType = convertPathType(s); - segments[segmentsCount++] = (pathType, pointsCount); - // First segment is prepended by an extra zero point - if (pointsCount == 0) - points[pointsCount++] = Vector2.Zero; - } - else + foreach (string s in pointSplit) { - points[pointsCount++] = readPoint(s, offset); + if (char.IsLetter(s[0])) + { + // The start of a new segment(indicated by having an alpha character at position 0). + var pathType = convertPathType(s); + segments[segmentsCount++] = (pathType, pointsCount); + + // First segment is prepended by an extra zero point + if (pointsCount == 0) + points[pointsCount++] = Vector2.Zero; + } + else + { + points[pointsCount++] = readPoint(s, offset); + } } + + var controlPoints = new List>(pointsCount); + + for (int i = 0; i < segmentsCount; i++) + { + int startIndex = segments[i].StartIndex; + int endIndex = i < segmentsCount - 1 ? segments[i + 1].StartIndex : pointsCount; + Vector2? endPoint = i < segmentsCount - 1 ? points[endIndex] : null; + controlPoints.AddRange(convertPoints(segments[i].Type, new ArraySegment(points, startIndex, endIndex - startIndex), endPoint)); + } + + return controlPoints.SelectMany(s => s).ToArray(); } - - var controlPoints = new List(pointsCount); - - for (int i = 0; i < segmentsCount; i++) + finally { - int startIndex = segments[i].StartIndex; - int endIndex = i < segmentsCount - 1 ? segments[i + 1].StartIndex : pointsCount; - Vector2? endPoint = i < segmentsCount - 1 ? points[endIndex] : null; - controlPoints.AddRange(convertPoints(segments[i].Type, points[startIndex..endIndex], endPoint)); + ArrayPool.Shared.Return(points); + ArrayPool<(PathType Type, int StartIndex)>.Shared.Return(segments); } - return controlPoints.ToArray(); - static Vector2 readPoint(string value, Vector2 startPos) { string[] vertexSplit = value.Split(':'); @@ -317,13 +326,12 @@ namespace osu.Game.Rulesets.Objects.Legacy /// The point list. /// Any extra endpoint to consider as part of the points. This will NOT be returned. /// The set of points contained by as one or more segments of the path. - private IEnumerable convertPoints(PathType type, ReadOnlySpan points, Vector2? endPoint) + private IEnumerable> convertPoints(PathType type, ArraySegment points, Vector2? endPoint) { - var vertices = new PathControlPoint[points.Length]; - var result = new List(); + var vertices = new PathControlPoint[points.Count]; // Parse into control points. - for (int i = 0; i < points.Length; i++) + for (int i = 0; i < points.Count; i++) vertices[i] = new PathControlPoint { Position = points[i] }; // Edge-case rules (to match stable). @@ -333,7 +341,7 @@ namespace osu.Game.Rulesets.Objects.Legacy if (vertices.Length + endPointLength != 3) type = PathType.BEZIER; - else if (isLinear(stackalloc[] { points[0], points[1], endPoint ?? points[2] })) + else if (isLinear(points[0], points[1], endPoint ?? points[2])) { // osu-stable special-cased colinear perfect curves to a linear path type = PathType.LINEAR; @@ -370,20 +378,18 @@ namespace osu.Game.Rulesets.Objects.Legacy // Force a type on the last point, and return the current control point set as a segment. vertices[endIndex - 1].Type = type; - for (int i = startIndex; i < endIndex; i++) - result.Add(vertices[i]); + yield return new ArraySegment(vertices, startIndex, endIndex - startIndex); // Skip the current control point - as it's the same as the one that's just been returned. startIndex = endIndex + 1; } - for (int i = startIndex; i < endIndex; i++) - result.Add(vertices[i]); + if (startIndex < endIndex) + yield return new ArraySegment(vertices, startIndex, endIndex - startIndex); - return result; - - static bool isLinear(ReadOnlySpan p) => Precision.AlmostEquals(0, (p[1].Y - p[0].Y) * (p[2].X - p[0].X) - - (p[1].X - p[0].X) * (p[2].Y - p[0].Y)); + static bool isLinear(Vector2 p0, Vector2 p1, Vector2 p2) + => Precision.AlmostEquals(0, (p1.Y - p0.Y) * (p2.X - p0.X) + - (p1.X - p0.X) * (p2.Y - p0.Y)); } /// From c10ba6ece9aea6d4325482563ae64fbc8261a65e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Feb 2024 15:59:22 +0100 Subject: [PATCH 3286/4092] Fix right mouse scroll clamping not going along well with padding Co-authored-by: Joseph Madamba --- osu.Game/Graphics/Containers/OsuScrollContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/OsuScrollContainer.cs b/osu.Game/Graphics/Containers/OsuScrollContainer.cs index 124becc35a..ffd28957ef 100644 --- a/osu.Game/Graphics/Containers/OsuScrollContainer.cs +++ b/osu.Game/Graphics/Containers/OsuScrollContainer.cs @@ -127,7 +127,7 @@ namespace osu.Game.Graphics.Containers } protected virtual void ScrollFromMouseEvent(MouseEvent e) => - ScrollTo(Clamp(ToLocalSpace(e.ScreenSpaceMousePosition)[ScrollDim] / DrawSize[ScrollDim]) * Content.DrawSize[ScrollDim], true, DistanceDecayOnRightMouseScrollbar); + ScrollTo(Clamp(ToLocalSpace(e.ScreenSpaceMousePosition)[ScrollDim] / DrawSize[ScrollDim] * Content.DrawSize[ScrollDim]), true, DistanceDecayOnRightMouseScrollbar); protected override ScrollbarContainer CreateScrollbar(Direction direction) => new OsuScrollbar(direction); From 470d2be2e1b4043953e067ef91e4c997138cc0ef Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 29 Feb 2024 00:07:00 +0800 Subject: [PATCH 3287/4092] The overhead of LINQ is not ignorable --- .../Objects/Legacy/ConvertHitObjectParser.cs | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index a3ca719ff9..72053648a0 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -302,7 +302,7 @@ namespace osu.Game.Rulesets.Objects.Legacy controlPoints.AddRange(convertPoints(segments[i].Type, new ArraySegment(points, startIndex, endIndex - startIndex), endPoint)); } - return controlPoints.SelectMany(s => s).ToArray(); + return mergePointsLists(controlPoints); } finally { @@ -392,6 +392,25 @@ namespace osu.Game.Rulesets.Objects.Legacy - (p1.X - p0.X) * (p2.Y - p0.Y)); } + private PathControlPoint[] mergePointsLists(List> controlPointList) + { + int totalCount = 0; + + foreach (var arr in controlPointList) + totalCount += arr.Count; + + var mergedArray = new PathControlPoint[totalCount]; + int copyIndex = 0; + + foreach (var arr in controlPointList) + { + arr.AsSpan().CopyTo(mergedArray.AsSpan(copyIndex)); + copyIndex += arr.Count; + } + + return mergedArray; + } + /// /// Creates a legacy Hit-type hit object. /// From e86ebd6cdba85e506a771aeab0fdeb0a71d74fab Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 29 Feb 2024 00:24:24 +0800 Subject: [PATCH 3288/4092] Fix formatting --- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 72053648a0..f042e6ba26 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -271,9 +271,9 @@ namespace osu.Game.Rulesets.Objects.Legacy var segments = ArrayPool<(PathType Type, int StartIndex)>.Shared.Rent(pointSplit.Length); int pointsCount = 0; int segmentsCount = 0; + try { - foreach (string s in pointSplit) { if (char.IsLetter(s[0])) From 5ca6e8c68a54c0341fc1e0debac5c6e3ebb9739e Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 28 Feb 2024 11:03:09 -0800 Subject: [PATCH 3289/4092] Fix some NRT changes --- osu.Game/Screens/Play/SoloSpectatorPlayer.cs | 4 +++- osu.Game/Screens/Play/SpectatorPlayer.cs | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/SoloSpectatorPlayer.cs b/osu.Game/Screens/Play/SoloSpectatorPlayer.cs index 2cc1c4a368..be83a4c6b5 100644 --- a/osu.Game/Screens/Play/SoloSpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SoloSpectatorPlayer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Screens; using osu.Game.Online.Spectator; using osu.Game.Scoring; @@ -48,7 +49,8 @@ namespace osu.Game.Screens.Play { base.Dispose(isDisposing); - SpectatorClient.OnUserBeganPlaying -= userBeganPlaying; + if (SpectatorClient.IsNotNull()) + SpectatorClient.OnUserBeganPlaying -= userBeganPlaying; } } } diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index 520fb43445..b2ac946642 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -1,8 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Diagnostics; using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Game.Beatmaps; @@ -97,7 +97,6 @@ namespace osu.Game.Screens.Play foreach (var frame in bundle.Frames) { IConvertibleReplayFrame convertibleFrame = GameplayState.Ruleset.CreateConvertibleReplayFrame()!; - Debug.Assert(convertibleFrame != null); convertibleFrame.FromLegacy(frame, GameplayState.Beatmap); var convertedFrame = (ReplayFrame)convertibleFrame; @@ -132,7 +131,8 @@ namespace osu.Game.Screens.Play { base.Dispose(isDisposing); - SpectatorClient.OnNewFrames -= userSentFrames; + if (SpectatorClient.IsNotNull()) + SpectatorClient.OnNewFrames -= userSentFrames; } } } From 232ca5778fc7fb86144f7bb74ee93ee2ac462549 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 29 Feb 2024 00:09:48 +0300 Subject: [PATCH 3290/4092] Improve spectator fail test --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index c8356cd191..0de2b6a980 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -10,6 +10,7 @@ using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; +using osu.Game.Localisation; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Spectator; using osu.Game.Rulesets.Osu; @@ -23,7 +24,6 @@ using osu.Game.Tests.Gameplay; using osu.Game.Tests.Visual.Multiplayer; using osu.Game.Tests.Visual.Spectator; using osuTK; -using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { @@ -214,7 +214,9 @@ namespace osu.Game.Tests.Visual.Gameplay checkPaused(true); // And eventually stop after running out of frames and fail. // Todo: Should check for + display a failed message. - AddStep("exit player", () => InputManager.Key(Key.Escape)); + AddAssert("fail overlay present", () => player.ChildrenOfType().Single().IsPresent); + AddAssert("overlay can only quit", () => player.ChildrenOfType().Single().Buttons.Single().Text == GameplayMenuOverlayStrings.Quit); + AddStep("press quit button", () => player.ChildrenOfType().Single().Buttons.Single().TriggerClick()); AddAssert("player exited", () => Stack.CurrentScreen is SoloSpectatorScreen); } From 4a4ef91bc96c9cab11102156c429a778237cdcbf Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 29 Feb 2024 00:42:52 +0300 Subject: [PATCH 3291/4092] Simplify active mods computation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 9 +++++---- .../OnlinePlay/Match/RoomModSelectOverlay.cs | 14 +++++++++++--- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 6b80ff6e44..9605c0b2fe 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -104,6 +104,8 @@ namespace osu.Game.Overlays.Mods protected virtual IReadOnlyList ComputeNewModsFromSelection(IReadOnlyList oldSelection, IReadOnlyList newSelection) => newSelection; + protected virtual IReadOnlyList ComputeActiveMods() => SelectedMods.Value; + protected virtual IEnumerable CreateFooterButtons() { if (AllowCustomisation) @@ -321,13 +323,12 @@ namespace osu.Game.Overlays.Mods if (AllowCustomisation) ((IBindable>)modSettingsArea.SelectedMods).BindTo(SelectedMods); - SelectedMods.BindValueChanged(mods => + SelectedMods.BindValueChanged(_ => { - var newMods = ActiveMods.Value.Except(mods.OldValue).Concat(mods.NewValue).ToList(); - ActiveMods.Value = newMods; - updateFromExternalSelection(); updateCustomisation(); + + ActiveMods.Value = ComputeActiveMods(); }, true); ActiveMods.BindValueChanged(_ => diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs index d3fd6de911..55e077df0f 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.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 System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; @@ -9,6 +10,7 @@ using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; namespace osu.Game.Screens.OnlinePlay.Match { @@ -20,6 +22,8 @@ namespace osu.Game.Screens.OnlinePlay.Match [Resolved] private RulesetStore rulesets { get; set; } = null!; + private readonly List roomRequiredMods = new List(); + public RoomModSelectOverlay() : base(OverlayColourScheme.Plum) { @@ -31,15 +35,19 @@ namespace osu.Game.Screens.OnlinePlay.Match selectedItem.BindValueChanged(v => { + roomRequiredMods.Clear(); + if (v.NewValue is PlaylistItem item) { var rulesetInstance = rulesets.GetRuleset(item.RulesetID)?.CreateInstance(); Debug.Assert(rulesetInstance != null); - ActiveMods.Value = item.RequiredMods.Select(m => m.ToMod(rulesetInstance)).Concat(SelectedMods.Value).ToList(); + roomRequiredMods.AddRange(item.RequiredMods.Select(m => m.ToMod(rulesetInstance))); } - else - ActiveMods.Value = SelectedMods.Value; + + ActiveMods.Value = ComputeActiveMods(); }, true); } + + protected override IReadOnlyList ComputeActiveMods() => roomRequiredMods.Concat(base.ComputeActiveMods()).ToList(); } } From c3a7e998497b136e5ca97b6729de4661cc28c2d5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 29 Feb 2024 01:01:55 +0300 Subject: [PATCH 3292/4092] Remove unnecessary max operation --- osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs b/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs index 7e0dd161dc..a14ab3cbcd 100644 --- a/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs @@ -61,7 +61,7 @@ namespace osu.Game.Screens.Play.HUD int digitsRequiredForDisplayCount = Math.Max(RequiredDisplayDigits.Value, getDigitsRequiredForDisplayCount()); if (digitsRequiredForDisplayCount != scoreText.WireframeTemplate.Length) - scoreText.WireframeTemplate = new string('#', Math.Max(RequiredDisplayDigits.Value, digitsRequiredForDisplayCount)); + scoreText.WireframeTemplate = new string('#', digitsRequiredForDisplayCount); } private int getDigitsRequiredForDisplayCount() From de48c51715c072adb1ed75d7717a93e7af334e56 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 29 Feb 2024 01:11:01 +0300 Subject: [PATCH 3293/4092] Apply renaming in remaining usages --- ...atisticsWatcher.cs => TestSceneUserStatisticsWatcher.cs} | 2 +- .../Toolbar/TransientUserStatisticsUpdateDisplay.cs | 6 +++--- osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs | 6 +++--- osu.Game/Users/UserRankPanel.cs | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) rename osu.Game.Tests/Visual/Online/{TestSceneSoloStatisticsWatcher.cs => TestSceneUserStatisticsWatcher.cs} (99%) diff --git a/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs b/osu.Game.Tests/Visual/Online/TestSceneUserStatisticsWatcher.cs similarity index 99% rename from osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs rename to osu.Game.Tests/Visual/Online/TestSceneUserStatisticsWatcher.cs index 733769b9f3..0b1f50b66f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserStatisticsWatcher.cs @@ -21,7 +21,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Online { [HeadlessTest] - public partial class TestSceneSoloStatisticsWatcher : OsuTestScene + public partial class TestSceneUserStatisticsWatcher : OsuTestScene { protected override bool UseOnlineAPI => false; diff --git a/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs b/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs index e7a78f8b80..21d91ef53f 100644 --- a/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs +++ b/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs @@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Toolbar private Statistic pp = null!; [BackgroundDependencyLoader] - private void load(UserStatisticsWatcher? soloStatisticsWatcher) + private void load(UserStatisticsWatcher? userStatisticsWatcher) { RelativeSizeAxes = Axes.Y; AutoSizeAxes = Axes.X; @@ -47,8 +47,8 @@ namespace osu.Game.Overlays.Toolbar } }; - if (soloStatisticsWatcher != null) - ((IBindable)LatestUpdate).BindTo(soloStatisticsWatcher.LatestUpdate); + if (userStatisticsWatcher != null) + ((IBindable)LatestUpdate).BindTo(userStatisticsWatcher.LatestUpdate); } protected override void LoadComplete() diff --git a/osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs index 7f8c12ddab..c37eb1914b 100644 --- a/osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs @@ -28,11 +28,11 @@ namespace osu.Game.Screens.Ranking.Statistics } [BackgroundDependencyLoader] - private void load(UserStatisticsWatcher? soloStatisticsWatcher) + private void load(UserStatisticsWatcher? userStatisticsWatcher) { - if (soloStatisticsWatcher != null) + if (userStatisticsWatcher != null) { - latestGlobalStatisticsUpdate = soloStatisticsWatcher.LatestUpdate.GetBoundCopy(); + latestGlobalStatisticsUpdate = userStatisticsWatcher.LatestUpdate.GetBoundCopy(); latestGlobalStatisticsUpdate.BindValueChanged(update => { if (update.NewValue?.Score.MatchesOnlineID(achievedScore) == true) diff --git a/osu.Game/Users/UserRankPanel.cs b/osu.Game/Users/UserRankPanel.cs index b440261a4c..0d57b7bb7d 100644 --- a/osu.Game/Users/UserRankPanel.cs +++ b/osu.Game/Users/UserRankPanel.cs @@ -168,7 +168,7 @@ namespace osu.Game.Users Title = UsersStrings.ShowRankGlobalSimple, // TODO: implement highest rank tooltip // `RankHighest` resides in `APIUser`, but `api.LocalUser` doesn't update - // maybe move to `UserStatistics` in api, so `SoloStatisticsWatcher` can update the value + // maybe move to `UserStatistics` in api, so `UserStatisticsWatcher` can update the value }, countryRankDisplay = new ProfileValueDisplay(true) { From 8f97f0503f1107d06576390eb014cb5dfaed4b0b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 29 Feb 2024 01:13:31 +0300 Subject: [PATCH 3294/4092] Move away from `Solo` namespace --- osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs | 2 +- osu.Game.Tests/Visual/Online/TestSceneUserStatisticsWatcher.cs | 2 +- osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs | 2 +- osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs | 2 +- osu.Game/Online/{Solo => }/UserStatisticsUpdate.cs | 2 +- osu.Game/Online/{Solo => }/UserStatisticsWatcher.cs | 2 +- osu.Game/OsuGame.cs | 1 - .../Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs | 2 +- osu.Game/Screens/Play/SubmittingPlayer.cs | 2 +- osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs | 2 +- osu.Game/Screens/Ranking/Statistics/User/RankingChangeRow.cs | 2 +- osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs | 2 +- 12 files changed, 11 insertions(+), 12 deletions(-) rename osu.Game/Online/{Solo => }/UserStatisticsUpdate.cs (97%) rename osu.Game/Online/{Solo => }/UserStatisticsWatcher.cs (99%) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs index 41ce739c2f..1a4ca65975 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs @@ -8,8 +8,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; +using osu.Game.Online; using osu.Game.Online.API; -using osu.Game.Online.Solo; using osu.Game.Overlays.Toolbar; using osu.Game.Scoring; using osu.Game.Users; diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserStatisticsWatcher.cs b/osu.Game.Tests/Visual/Online/TestSceneUserStatisticsWatcher.cs index 0b1f50b66f..e7ad07041c 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserStatisticsWatcher.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserStatisticsWatcher.cs @@ -8,10 +8,10 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Testing; using osu.Game.Models; +using osu.Game.Online; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Online.Solo; using osu.Game.Online.Spectator; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs b/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs index ee0ab0c880..ffc7d88a34 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs @@ -3,7 +3,7 @@ using NUnit.Framework; using osu.Framework.Graphics; -using osu.Game.Online.Solo; +using osu.Game.Online; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Statistics.User; using osu.Game.Users; diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index ecb8073a88..acfa519c81 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -13,7 +13,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Online.Solo; +using osu.Game.Online; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; diff --git a/osu.Game/Online/Solo/UserStatisticsUpdate.cs b/osu.Game/Online/UserStatisticsUpdate.cs similarity index 97% rename from osu.Game/Online/Solo/UserStatisticsUpdate.cs rename to osu.Game/Online/UserStatisticsUpdate.cs index 03f3abbb66..f85b219ef0 100644 --- a/osu.Game/Online/Solo/UserStatisticsUpdate.cs +++ b/osu.Game/Online/UserStatisticsUpdate.cs @@ -4,7 +4,7 @@ using osu.Game.Scoring; using osu.Game.Users; -namespace osu.Game.Online.Solo +namespace osu.Game.Online { /// /// Contains data about the change in a user's profile statistics after completing a score. diff --git a/osu.Game/Online/Solo/UserStatisticsWatcher.cs b/osu.Game/Online/UserStatisticsWatcher.cs similarity index 99% rename from osu.Game/Online/Solo/UserStatisticsWatcher.cs rename to osu.Game/Online/UserStatisticsWatcher.cs index 2fff92fe0f..af32e86ae4 100644 --- a/osu.Game/Online/Solo/UserStatisticsWatcher.cs +++ b/osu.Game/Online/UserStatisticsWatcher.cs @@ -15,7 +15,7 @@ using osu.Game.Online.Spectator; using osu.Game.Scoring; using osu.Game.Users; -namespace osu.Game.Online.Solo +namespace osu.Game.Online { /// /// A persistent component that binds to the spectator server and API in order to deliver updates about the logged in user's gameplay statistics. diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 419e31bacd..44d1e7fad6 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -47,7 +47,6 @@ using osu.Game.Localisation; using osu.Game.Online; using osu.Game.Online.Chat; using osu.Game.Online.Rooms; -using osu.Game.Online.Solo; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapListing; using osu.Game.Overlays.Music; diff --git a/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs b/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs index 21d91ef53f..c6f373d55f 100644 --- a/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs +++ b/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs @@ -13,7 +13,7 @@ using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osu.Game.Online.Solo; +using osu.Game.Online; using osu.Game.Resources.Localisation.Web; using osuTK; diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index ee88171789..62226c46dd 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -14,10 +14,10 @@ using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Database; +using osu.Game.Online; using osu.Game.Online.API; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; -using osu.Game.Online.Solo; using osu.Game.Online.Spectator; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; diff --git a/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs b/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs index 5c96a2b6c3..1e60e09486 100644 --- a/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs +++ b/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs @@ -6,7 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; -using osu.Game.Online.Solo; +using osu.Game.Online; namespace osu.Game.Screens.Ranking.Statistics.User { diff --git a/osu.Game/Screens/Ranking/Statistics/User/RankingChangeRow.cs b/osu.Game/Screens/Ranking/Statistics/User/RankingChangeRow.cs index a477f38cd1..e5f07d9891 100644 --- a/osu.Game/Screens/Ranking/Statistics/User/RankingChangeRow.cs +++ b/osu.Game/Screens/Ranking/Statistics/User/RankingChangeRow.cs @@ -11,7 +11,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Online.Solo; +using osu.Game.Online; using osu.Game.Users; using osuTK; diff --git a/osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs index c37eb1914b..fa3bb1a375 100644 --- a/osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs @@ -8,7 +8,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Extensions; -using osu.Game.Online.Solo; +using osu.Game.Online; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Statistics.User; From 7f5f3804f1695e130a2ab41d5e4fda092a32ed1d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 29 Feb 2024 05:39:36 +0300 Subject: [PATCH 3295/4092] Expose beatmap storyboard as part of `GameplayState` --- osu.Game/Screens/Play/GameplayState.cs | 9 ++++++++- osu.Game/Screens/Play/Player.cs | 8 ++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayState.cs b/osu.Game/Screens/Play/GameplayState.cs index cc399a0fbe..8b0207a340 100644 --- a/osu.Game/Screens/Play/GameplayState.cs +++ b/osu.Game/Screens/Play/GameplayState.cs @@ -10,6 +10,7 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using osu.Game.Storyboards; namespace osu.Game.Screens.Play { @@ -40,6 +41,11 @@ namespace osu.Game.Screens.Play public readonly ScoreProcessor ScoreProcessor; + /// + /// The storyboard associated with the beatmap. + /// + public readonly Storyboard Storyboard; + /// /// Whether gameplay completed without the user failing. /// @@ -62,7 +68,7 @@ namespace osu.Game.Screens.Play private readonly Bindable lastJudgementResult = new Bindable(); - public GameplayState(IBeatmap beatmap, Ruleset ruleset, IReadOnlyList? mods = null, Score? score = null, ScoreProcessor? scoreProcessor = null) + public GameplayState(IBeatmap beatmap, Ruleset ruleset, IReadOnlyList? mods = null, Score? score = null, ScoreProcessor? scoreProcessor = null, Storyboard? storyboard = null) { Beatmap = beatmap; Ruleset = ruleset; @@ -76,6 +82,7 @@ namespace osu.Game.Screens.Play }; Mods = mods ?? Array.Empty(); ScoreProcessor = scoreProcessor ?? ruleset.CreateScoreProcessor(); + Storyboard = storyboard ?? new Storyboard(); } /// diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 88f6ae9e71..10ada09be7 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -255,7 +255,7 @@ namespace osu.Game.Screens.Play Score.ScoreInfo.Ruleset = ruleset.RulesetInfo; Score.ScoreInfo.Mods = gameplayMods; - dependencies.CacheAs(GameplayState = new GameplayState(playableBeatmap, ruleset, gameplayMods, Score, ScoreProcessor)); + dependencies.CacheAs(GameplayState = new GameplayState(playableBeatmap, ruleset, gameplayMods, Score, ScoreProcessor, Beatmap.Value.Storyboard)); var rulesetSkinProvider = new RulesetSkinProvidingContainer(ruleset, playableBeatmap, Beatmap.Value.Skin); @@ -397,7 +397,7 @@ namespace osu.Game.Screens.Play protected virtual GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new MasterGameplayClockContainer(beatmap, gameplayStart); private Drawable createUnderlayComponents() => - DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard, GameplayState.Mods) { RelativeSizeAxes = Axes.Both }; + DimmableStoryboard = new DimmableStoryboard(GameplayState.Storyboard, GameplayState.Mods) { RelativeSizeAxes = Axes.Both }; private Drawable createGameplayComponents(IWorkingBeatmap working) => new ScalingContainer(ScalingMode.Gameplay) { @@ -456,7 +456,7 @@ namespace osu.Game.Screens.Play { RequestSkip = performUserRequestedSkip }, - skipOutroOverlay = new SkipOverlay(Beatmap.Value.Storyboard.LatestEventTime ?? 0) + skipOutroOverlay = new SkipOverlay(GameplayState.Storyboard.LatestEventTime ?? 0) { RequestSkip = () => progressToResults(false), Alpha = 0 @@ -1088,7 +1088,7 @@ namespace osu.Game.Screens.Play DimmableStoryboard.IsBreakTime.BindTo(breakTracker.IsBreakTime); - storyboardReplacesBackground.Value = Beatmap.Value.Storyboard.ReplacesBackground && Beatmap.Value.Storyboard.HasDrawable; + storyboardReplacesBackground.Value = GameplayState.Storyboard.ReplacesBackground && GameplayState.Storyboard.HasDrawable; foreach (var mod in GameplayState.Mods.OfType()) mod.ApplyToPlayer(this); From 847a8ead4fbdd86149b10fad884ed664a94e5352 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 29 Feb 2024 05:39:59 +0300 Subject: [PATCH 3296/4092] Hide taiko scroller when beatmap has storyboard --- .../UI/DrawableTaikoRuleset.cs | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 77b2b06c0e..ff969c3f74 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -5,6 +5,8 @@ using System; using System.Collections.Generic; +using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -22,7 +24,9 @@ using osu.Game.Rulesets.Timing; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Scoring; +using osu.Game.Screens.Play; using osu.Game.Skinning; +using osu.Game.Storyboards; using osuTK; namespace osu.Game.Rulesets.Taiko.UI @@ -39,6 +43,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool UserScrollSpeedAdjustment => false; + [CanBeNull] private SkinnableDrawable scroller; public DrawableTaikoRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) @@ -48,16 +53,24 @@ namespace osu.Game.Rulesets.Taiko.UI VisualisationMethod = ScrollVisualisationMethod.Overlapping; } - [BackgroundDependencyLoader] - private void load() + [BackgroundDependencyLoader(true)] + private void load(GameplayState gameplayState) { new BarLineGenerator(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar)); - FrameStableComponents.Add(scroller = new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.Scroller), _ => Empty()) + var spriteElements = gameplayState.Storyboard.Layers.Where(l => l.Name != @"Overlay") + .SelectMany(l => l.Elements) + .OfType() + .DistinctBy(e => e.Path); + + if (spriteElements.Count() < 10) { - RelativeSizeAxes = Axes.X, - Depth = float.MaxValue - }); + FrameStableComponents.Add(scroller = new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.Scroller), _ => Empty()) + { + RelativeSizeAxes = Axes.X, + Depth = float.MaxValue, + }); + } KeyBindingInputManager.Add(new DrumTouchInputArea()); } @@ -76,7 +89,9 @@ namespace osu.Game.Rulesets.Taiko.UI base.UpdateAfterChildren(); var playfieldScreen = Playfield.ScreenSpaceDrawQuad; - scroller.Height = ToLocalSpace(playfieldScreen.TopLeft + new Vector2(0, playfieldScreen.Height / 20)).Y; + + if (scroller != null) + scroller.Height = ToLocalSpace(playfieldScreen.TopLeft + new Vector2(0, playfieldScreen.Height / 20)).Y; } public MultiplierControlPoint ControlPointAt(double time) From f28f19ed7eeee1ecacc909f0f4f35edb07c7b8cb Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 29 Feb 2024 10:47:16 +0800 Subject: [PATCH 3297/4092] Fix method indent size Co-authored-by: Salman Ahmed --- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index f042e6ba26..db1fbe3fa4 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -389,7 +389,7 @@ namespace osu.Game.Rulesets.Objects.Legacy static bool isLinear(Vector2 p0, Vector2 p1, Vector2 p2) => Precision.AlmostEquals(0, (p1.Y - p0.Y) * (p2.X - p0.X) - - (p1.X - p0.X) * (p2.Y - p0.Y)); + - (p1.X - p0.X) * (p2.Y - p0.Y)); } private PathControlPoint[] mergePointsLists(List> controlPointList) From 4b0b0735a81aec44fe0725eac3be8ece7b733854 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 29 Feb 2024 06:03:57 +0300 Subject: [PATCH 3298/4092] Add test coverage --- .../TestSceneTaikoPlayerScroller.cs | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayerScroller.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayerScroller.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayerScroller.cs new file mode 100644 index 0000000000..c2aa819c3a --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayerScroller.cs @@ -0,0 +1,57 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Taiko.Skinning.Legacy; +using osu.Game.Storyboards; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + public partial class TestSceneTaikoPlayerScroller : LegacySkinPlayerTestScene + { + private Storyboard? currentStoryboard; + + protected override bool HasCustomSteps => true; + + [Test] + public void TestForegroundSpritesHidesScroller() + { + AddStep("load storyboard", () => + { + currentStoryboard = new Storyboard(); + + for (int i = 0; i < 10; i++) + currentStoryboard.GetLayer("Foreground").Add(new StoryboardSprite($"test{i}", Anchor.Centre, Vector2.Zero)); + }); + + CreateTest(); + AddAssert("taiko scroller not present", () => !this.ChildrenOfType().Any()); + } + + [Test] + public void TestOverlaySpritesKeepsScroller() + { + AddStep("load storyboard", () => + { + currentStoryboard = new Storyboard(); + + for (int i = 0; i < 10; i++) + currentStoryboard.GetLayer("Overlay").Add(new StoryboardSprite($"test{i}", Anchor.Centre, Vector2.Zero)); + }); + + CreateTest(); + AddAssert("taiko scroller present", () => this.ChildrenOfType().Single().IsPresent); + } + + protected override Ruleset CreatePlayerRuleset() => new TaikoRuleset(); + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null) + => base.CreateWorkingBeatmap(beatmap, currentStoryboard ?? storyboard); + } +} From dac8f98ea6b7d6f039d5fbe9b86b17a4303969cb Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 29 Feb 2024 07:13:32 +0300 Subject: [PATCH 3299/4092] Fix `GameplayState` not handled as nullable --- osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index ff969c3f74..b8e76be89e 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -54,14 +54,14 @@ namespace osu.Game.Rulesets.Taiko.UI } [BackgroundDependencyLoader(true)] - private void load(GameplayState gameplayState) + private void load([CanBeNull] GameplayState gameplayState) { new BarLineGenerator(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar)); - var spriteElements = gameplayState.Storyboard.Layers.Where(l => l.Name != @"Overlay") + var spriteElements = gameplayState?.Storyboard.Layers.Where(l => l.Name != @"Overlay") .SelectMany(l => l.Elements) .OfType() - .DistinctBy(e => e.Path); + .DistinctBy(e => e.Path) ?? Enumerable.Empty(); if (spriteElements.Count() < 10) { From 5495c2090a76bfd6f6a5efd0fd61530395cc6a68 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Feb 2024 14:15:29 +0800 Subject: [PATCH 3300/4092] Add test coverage of gameplay only running forwards --- .../Visual/Gameplay/TestScenePause.cs | 66 ++++++++++++------- 1 file changed, 43 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 73aa3be73d..030f2592ed 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -16,6 +16,7 @@ using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Rulesets; +using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; using osu.Game.Skinning; using osuTK; @@ -31,6 +32,9 @@ namespace osu.Game.Tests.Visual.Gameplay protected override Container Content => content; + private bool gameplayClockAlwaysGoingForward = true; + private double lastForwardCheckTime; + public TestScenePause() { base.Content.Add(content = new GlobalCursorDisplay { RelativeSizeAxes = Axes.Both }); @@ -67,12 +71,20 @@ namespace osu.Game.Tests.Visual.Gameplay confirmPausedWithNoOverlay(); } + [Test] + public void TestForwardPlaybackGuarantee() + { + hookForwardPlaybackCheck(); + + AddUntilStep("wait for forward playback", () => Player.GameplayClockContainer.CurrentTime > 1000); + AddStep("seek before gameplay", () => Player.GameplayClockContainer.Seek(-5000)); + + checkForwardPlayback(); + } + [Test] public void TestPauseWithLargeOffset() { - double lastStopTime; - bool alwaysGoingForward = true; - AddStep("force large offset", () => { var offset = (BindableDouble)LocalConfig.GetBindable(OsuSetting.AudioOffset); @@ -82,25 +94,7 @@ namespace osu.Game.Tests.Visual.Gameplay offset.Value = -5000; }); - AddStep("add time forward check hook", () => - { - lastStopTime = double.MinValue; - alwaysGoingForward = true; - - Player.OnUpdate += _ => - { - var masterClock = (MasterGameplayClockContainer)Player.GameplayClockContainer; - - double currentTime = masterClock.CurrentTime; - - bool goingForward = currentTime >= lastStopTime; - - alwaysGoingForward &= goingForward; - - if (!goingForward) - Logger.Log($"Went too far backwards (last stop: {lastStopTime:N1} current: {currentTime:N1})"); - }; - }); + hookForwardPlaybackCheck(); AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10))); @@ -108,11 +102,37 @@ namespace osu.Game.Tests.Visual.Gameplay resumeAndConfirm(); - AddAssert("time didn't go too far backwards", () => alwaysGoingForward); + checkForwardPlayback(); AddStep("reset offset", () => LocalConfig.SetValue(OsuSetting.AudioOffset, 0.0)); } + private void checkForwardPlayback() => AddAssert("time didn't go too far backwards", () => gameplayClockAlwaysGoingForward); + + private void hookForwardPlaybackCheck() + { + AddStep("add time forward check hook", () => + { + lastForwardCheckTime = double.MinValue; + gameplayClockAlwaysGoingForward = true; + + Player.OnUpdate += _ => + { + var frameStableClock = Player.ChildrenOfType().Single().Clock; + + double currentTime = frameStableClock.CurrentTime; + + bool goingForward = currentTime >= lastForwardCheckTime; + lastForwardCheckTime = currentTime; + + gameplayClockAlwaysGoingForward &= goingForward; + + if (!goingForward) + Logger.Log($"Went too far backwards (last stop: {lastForwardCheckTime:N1} current: {currentTime:N1})"); + }; + }); + } + [Test] public void TestPauseResume() { From 76e8aee9ccc13637691918de7c98d0cfaa8d1a7d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Feb 2024 13:45:46 +0800 Subject: [PATCH 3301/4092] Disallow backwards seeks during frame stable operation when a replay is not attached --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 8c9cb262af..4011034396 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -150,6 +150,17 @@ namespace osu.Game.Rulesets.UI state = PlaybackState.NotValid; } + // This is a hotfix for https://github.com/ppy/osu/issues/26879 while we figure how the hell time is seeking + // backwards by 11,850 ms for some users during gameplay. + // + // It basically says that "while we're running in frame stable mode, and don't have a replay attached, + // time should never go backwards". If it does, we stop running gameplay until it returns to normal. + if (!hasReplayAttached && FrameStablePlayback && proposedTime > referenceClock.CurrentTime) + { + state = PlaybackState.NotValid; + return; + } + // if the proposed time is the same as the current time, assume that the clock will continue progressing in the same direction as previously. // this avoids spurious flips in direction from -1 to 1 during rewinds. if (state == PlaybackState.Valid && proposedTime != manualClock.CurrentTime) From caad89a4ddd6b773df83e46d2da9c3ac4a81f0f8 Mon Sep 17 00:00:00 2001 From: Detze <92268414+Detze@users.noreply.github.com> Date: Thu, 29 Feb 2024 07:36:35 +0100 Subject: [PATCH 3302/4092] Fix test failure on leap years --- .../NonVisual/Filtering/FilterQueryParserTest.cs | 2 +- osu.Game/Screens/Select/FilterQueryParser.cs | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 12d6060351..bf888348ee 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -538,7 +538,7 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.That(filterCriteria.LastPlayed.Max, Is.Not.Null); // the parser internally references `DateTimeOffset.Now`, so to not make things too annoying for tests, just assume some tolerance // (irrelevant in proportion to the actual filter proscribed). - Assert.That(filterCriteria.LastPlayed.Min, Is.EqualTo(DateTimeOffset.Now.AddYears(-1).AddMonths(-6)).Within(TimeSpan.FromSeconds(5))); + Assert.That(filterCriteria.LastPlayed.Min, Is.EqualTo(DateTimeOffset.Now.AddMonths(-6).AddYears(-1)).Within(TimeSpan.FromSeconds(5))); Assert.That(filterCriteria.LastPlayed.Max, Is.EqualTo(DateTimeOffset.Now.AddMonths(-3)).Within(TimeSpan.FromSeconds(5))); } diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 2c4077dacf..1ee8b9bc05 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -402,19 +402,19 @@ namespace osu.Game.Screens.Select // we'll want to flip the operator, such that `>5d` means "more than five days ago", as in "*before* five days ago", // as intended by the user. case Operator.Less: - op = Operator.Greater; - break; - - case Operator.LessOrEqual: op = Operator.GreaterOrEqual; break; + case Operator.LessOrEqual: + op = Operator.Greater; + break; + case Operator.Greater: - op = Operator.Less; + op = Operator.LessOrEqual; break; case Operator.GreaterOrEqual: - op = Operator.LessOrEqual; + op = Operator.Less; break; } From 9f2ea1e936e84d092cf9d2f5733b72abb43f3553 Mon Sep 17 00:00:00 2001 From: Detze <92268414+Detze@users.noreply.github.com> Date: Thu, 29 Feb 2024 08:15:49 +0100 Subject: [PATCH 3303/4092] Revert incorrect change (I deserve to be shot) --- osu.Game/Screens/Select/FilterQueryParser.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 1ee8b9bc05..2c4077dacf 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -402,19 +402,19 @@ namespace osu.Game.Screens.Select // we'll want to flip the operator, such that `>5d` means "more than five days ago", as in "*before* five days ago", // as intended by the user. case Operator.Less: - op = Operator.GreaterOrEqual; - break; - - case Operator.LessOrEqual: op = Operator.Greater; break; + case Operator.LessOrEqual: + op = Operator.GreaterOrEqual; + break; + case Operator.Greater: - op = Operator.LessOrEqual; + op = Operator.Less; break; case Operator.GreaterOrEqual: - op = Operator.Less; + op = Operator.LessOrEqual; break; } From 7b28a66fc074a869d3af3f86ca2cf5c1d5e463d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 29 Feb 2024 11:11:30 +0100 Subject: [PATCH 3304/4092] Add failing test case --- .../TestSceneSliderInput.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index 12be74c4cc..286e4bd775 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -457,6 +457,33 @@ namespace osu.Game.Rulesets.Osu.Tests assertMidSliderJudgementFail(); } + [Test] + public void TestRewindHandling() + { + performTest(new List + { + new OsuReplayFrame { Position = new Vector2(0), Actions = { OsuAction.LeftButton }, Time = time_slider_start }, + new OsuReplayFrame { Position = new Vector2(175, 0), Actions = { OsuAction.LeftButton }, Time = 3250 }, + new OsuReplayFrame { Position = new Vector2(175, 0), Actions = { OsuAction.LeftButton }, Time = time_slider_end }, + }, new Slider + { + StartTime = time_slider_start, + Position = new Vector2(0, 0), + Path = new SliderPath(PathType.PERFECT_CURVE, new[] + { + Vector2.Zero, + new Vector2(250, 0), + }, 250), + }); + + AddUntilStep("wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value); + AddAssert("no miss judgements recorded", () => judgementResults.All(r => r.Type.IsHit())); + + AddStep("rewind to middle of slider", () => currentPlayer.Seek(time_during_slide_4)); + AddUntilStep("wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value); + AddAssert("no miss judgements recorded", () => judgementResults.All(r => r.Type.IsHit())); + } + private void assertAllMaxJudgements() { AddAssert("All judgements max", () => From 3355764a68535facc627433f7c58d1040f3928e2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Feb 2024 18:21:44 +0800 Subject: [PATCH 3305/4092] "Fix" tests --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 4011034396..487c12830f 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; +using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; @@ -155,7 +156,7 @@ namespace osu.Game.Rulesets.UI // // It basically says that "while we're running in frame stable mode, and don't have a replay attached, // time should never go backwards". If it does, we stop running gameplay until it returns to normal. - if (!hasReplayAttached && FrameStablePlayback && proposedTime > referenceClock.CurrentTime) + if (!hasReplayAttached && FrameStablePlayback && proposedTime > referenceClock.CurrentTime && !DebugUtils.IsNUnitRunning) { state = PlaybackState.NotValid; return; From 3a780e2b676eee99f0b0e845c12348977df22695 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Feb 2024 18:27:28 +0800 Subject: [PATCH 3306/4092] Add logging when workaround is hit --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 487c12830f..03f3b8788f 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -9,6 +9,7 @@ using osu.Framework.Bindables; using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Logging; using osu.Framework.Timing; using osu.Game.Input.Handlers; using osu.Game.Screens.Play; @@ -158,6 +159,7 @@ namespace osu.Game.Rulesets.UI // time should never go backwards". If it does, we stop running gameplay until it returns to normal. if (!hasReplayAttached && FrameStablePlayback && proposedTime > referenceClock.CurrentTime && !DebugUtils.IsNUnitRunning) { + Logger.Log($"Denying backwards seek during gameplay (reference: {referenceClock.CurrentTime:N2} stable: {proposedTime:N2})"); state = PlaybackState.NotValid; return; } From d05b31933fabb30da23e90cad95f3ea91ac40e48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 29 Feb 2024 11:44:14 +0100 Subject: [PATCH 3307/4092] Fix slider tracking state not restoring correctly in all cases on rewind --- .../Objects/Drawables/SliderInputManager.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs index 148cf79337..58fa04bb4c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs @@ -5,11 +5,13 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Screens.Play; using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables @@ -21,6 +23,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables /// public bool Tracking { get; private set; } + [Resolved] + private IGameplayClock? gameplayClock { get; set; } + + private readonly Stack<(double time, bool tracking)> trackingHistory = new Stack<(double, bool)>(); + /// /// The point in time after which we can accept any key for tracking. Before this time, we may need to restrict tracking to the key used to hit the head circle. /// @@ -208,6 +215,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables /// Whether the current mouse position is valid to begin tracking. private void updateTracking(bool isValidTrackingPosition) { + if (gameplayClock?.IsRewinding == true) + { + while (trackingHistory.TryPeek(out var historyEntry) && Time.Current < historyEntry.time) + trackingHistory.Pop(); + + Debug.Assert(trackingHistory.Count > 0); + + Tracking = trackingHistory.Peek().tracking; + return; + } + + bool wasTracking = Tracking; + // from the point at which the head circle is hit, this will be non-null. // it may be null if the head circle was missed. OsuAction? headCircleHitAction = getInitialHitAction(); @@ -247,6 +267,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables && isValidTrackingPosition // valid action && validTrackingAction; + + if (wasTracking != Tracking) + trackingHistory.Push((Time.Current, Tracking)); } private OsuAction? getInitialHitAction() => slider.HeadCircle?.HitAction; From 1d1db951f08731604676bcca3c470a8416e23dce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 29 Feb 2024 11:54:42 +0100 Subject: [PATCH 3308/4092] Reset slider input manager state completely on new object application Kind of scary this wasn't happening already. Mirrors `SpinnerRotationTracker`. --- .../Objects/Drawables/SliderInputManager.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs index 58fa04bb4c..5daf8ed972 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Screens.Play; using osuTK; @@ -56,6 +57,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public SliderInputManager(DrawableSlider slider) { this.slider = slider; + this.slider.HitObjectApplied += resetState; } /// @@ -287,5 +289,22 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables return action == OsuAction.LeftButton || action == OsuAction.RightButton; } + + private void resetState(DrawableHitObject obj) + { + Tracking = false; + trackingHistory.Clear(); + trackingHistory.Push((double.NegativeInfinity, false)); + timeToAcceptAnyKeyAfter = null; + lastPressedActions.Clear(); + screenSpaceMousePosition = null; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + slider.HitObjectApplied -= resetState; + } } } From 876b80642357996f90e8469cc25eb1e490f1f224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 29 Feb 2024 12:11:50 +0100 Subject: [PATCH 3309/4092] Store tracking history to slider judgement result instead --- .../Judgements/OsuSliderJudgementResult.cs | 20 +++++++++++++++++++ .../Objects/Drawables/DrawableSlider.cs | 6 ++++++ .../Objects/Drawables/SliderInputManager.cs | 7 ++----- 3 files changed, 28 insertions(+), 5 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Judgements/OsuSliderJudgementResult.cs diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuSliderJudgementResult.cs b/osu.Game.Rulesets.Osu/Judgements/OsuSliderJudgementResult.cs new file mode 100644 index 0000000000..f52294cdd7 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Judgements/OsuSliderJudgementResult.cs @@ -0,0 +1,20 @@ +// 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 osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Osu.Judgements +{ + public class OsuSliderJudgementResult : OsuJudgementResult + { + public readonly Stack<(double time, bool tracking)> TrackingHistory = new Stack<(double, bool)>(); + + public OsuSliderJudgementResult(HitObject hitObject, Judgement judgement) + : base(hitObject, judgement) + { + TrackingHistory.Push((double.NegativeInfinity, false)); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index b7ce712e2c..e519e51562 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -14,8 +14,10 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Layout; using osu.Game.Audio; using osu.Game.Graphics.Containers; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; @@ -27,6 +29,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public new Slider HitObject => (Slider)base.HitObject; + public new OsuSliderJudgementResult Result => (OsuSliderJudgementResult)base.Result; + public DrawableSliderHead HeadCircle => headContainer.Child; public DrawableSliderTail TailCircle => tailContainer.Child; @@ -134,6 +138,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }, true); } + protected override JudgementResult CreateResult(Judgement judgement) => new OsuSliderJudgementResult(HitObject, judgement); + protected override void OnApply() { base.OnApply(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs index 5daf8ed972..c75ae35a1a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs @@ -27,8 +27,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables [Resolved] private IGameplayClock? gameplayClock { get; set; } - private readonly Stack<(double time, bool tracking)> trackingHistory = new Stack<(double, bool)>(); - /// /// The point in time after which we can accept any key for tracking. Before this time, we may need to restrict tracking to the key used to hit the head circle. /// @@ -219,6 +217,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { if (gameplayClock?.IsRewinding == true) { + var trackingHistory = slider.Result.TrackingHistory; while (trackingHistory.TryPeek(out var historyEntry) && Time.Current < historyEntry.time) trackingHistory.Pop(); @@ -271,7 +270,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables && validTrackingAction; if (wasTracking != Tracking) - trackingHistory.Push((Time.Current, Tracking)); + slider.Result.TrackingHistory.Push((Time.Current, Tracking)); } private OsuAction? getInitialHitAction() => slider.HeadCircle?.HitAction; @@ -293,8 +292,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private void resetState(DrawableHitObject obj) { Tracking = false; - trackingHistory.Clear(); - trackingHistory.Push((double.NegativeInfinity, false)); timeToAcceptAnyKeyAfter = null; lastPressedActions.Clear(); screenSpaceMousePosition = null; From a11e63b184acc5030e3d167f13d85a3691f0b7dc Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 29 Feb 2024 20:02:04 +0800 Subject: [PATCH 3310/4092] Make the code more clear --- .../Objects/Legacy/ConvertHitObjectParser.cs | 42 ++++++++++++------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index db1fbe3fa4..2b058d5e1f 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -265,49 +265,59 @@ namespace osu.Game.Rulesets.Objects.Legacy private PathControlPoint[] convertPathString(string pointString, Vector2 offset) { // This code takes on the responsibility of handling explicit segments of the path ("X" & "Y" from above). Implicit segments are handled by calls to convertPoints(). - string[] pointSplit = pointString.Split('|'); + string[] pointStringSplit = pointString.Split('|'); - var points = ArrayPool.Shared.Rent(pointSplit.Length); - var segments = ArrayPool<(PathType Type, int StartIndex)>.Shared.Rent(pointSplit.Length); - int pointsCount = 0; - int segmentsCount = 0; + var pointsBuffer = ArrayPool.Shared.Rent(pointStringSplit.Length); + var segmentsBuffer = ArrayPool<(PathType Type, int StartIndex)>.Shared.Rent(pointStringSplit.Length); + int currentPointsIndex = 0; + int currentSegmentsIndex = 0; try { - foreach (string s in pointSplit) + foreach (string s in pointStringSplit) { if (char.IsLetter(s[0])) { // The start of a new segment(indicated by having an alpha character at position 0). var pathType = convertPathType(s); - segments[segmentsCount++] = (pathType, pointsCount); + segmentsBuffer[currentSegmentsIndex++] = (pathType, currentPointsIndex); // First segment is prepended by an extra zero point - if (pointsCount == 0) - points[pointsCount++] = Vector2.Zero; + if (currentPointsIndex == 0) + pointsBuffer[currentPointsIndex++] = Vector2.Zero; } else { - points[pointsCount++] = readPoint(s, offset); + pointsBuffer[currentPointsIndex++] = readPoint(s, offset); } } + int pointsCount = currentPointsIndex; + int segmentsCount = currentSegmentsIndex; var controlPoints = new List>(pointsCount); + var allPoints = new ArraySegment(pointsBuffer, 0, pointsCount); for (int i = 0; i < segmentsCount; i++) { - int startIndex = segments[i].StartIndex; - int endIndex = i < segmentsCount - 1 ? segments[i + 1].StartIndex : pointsCount; - Vector2? endPoint = i < segmentsCount - 1 ? points[endIndex] : null; - controlPoints.AddRange(convertPoints(segments[i].Type, new ArraySegment(points, startIndex, endIndex - startIndex), endPoint)); + if (i < segmentsCount - 1) + { + int startIndex = segmentsBuffer[i].StartIndex; + int endIndex = segmentsBuffer[i + 1].StartIndex; + controlPoints.AddRange(convertPoints(segmentsBuffer[i].Type, allPoints[startIndex..endIndex], pointsBuffer[endIndex])); + } + else + { + int startIndex = segmentsBuffer[i].StartIndex; + controlPoints.AddRange(convertPoints(segmentsBuffer[i].Type, allPoints[startIndex..], null)); + } } return mergePointsLists(controlPoints); } finally { - ArrayPool.Shared.Return(points); - ArrayPool<(PathType Type, int StartIndex)>.Shared.Return(segments); + ArrayPool.Shared.Return(pointsBuffer); + ArrayPool<(PathType, int)>.Shared.Return(segmentsBuffer); } static Vector2 readPoint(string value, Vector2 startPos) From 4184a5c1ef380318dae27492c0f68c4ba211aa0e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Feb 2024 20:34:38 +0800 Subject: [PATCH 3311/4092] Add flag to allow backwards seeks in tests --- .../TestSceneTimingBasedNoteColouring.cs | 21 ++++++++++++------- .../NonVisual/FirstAvailableHitWindowsTest.cs | 1 + .../TestSceneCompletionCancellation.cs | 2 ++ .../TestSceneFrameStabilityContainer.cs | 3 +++ .../TestSceneGameplaySamplePlayback.cs | 2 ++ .../TestSceneGameplaySampleTriggerSource.cs | 2 ++ .../Visual/Gameplay/TestSceneHitErrorMeter.cs | 1 + .../Gameplay/TestScenePoolingRuleset.cs | 1 + .../Gameplay/TestSceneStoryboardWithOutro.cs | 2 ++ osu.Game/Rulesets/UI/DrawableRuleset.cs | 20 ++++++++++++++++++ .../Rulesets/UI/FrameStabilityContainer.cs | 5 +++-- osu.Game/Tests/Visual/PlayerTestScene.cs | 12 ++++++++++- 12 files changed, 61 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs index 81557c198d..b5b265792b 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs @@ -34,16 +34,21 @@ namespace osu.Game.Rulesets.Mania.Tests [SetUpSteps] public void SetUpSteps() { - AddStep("setup hierarchy", () => Child = new Container + AddStep("setup hierarchy", () => { - Clock = new FramedClock(clock = new ManualClock()), - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Children = new[] + Child = new Container { - drawableRuleset = (DrawableManiaRuleset)Ruleset.Value.CreateInstance().CreateDrawableRulesetWith(createTestBeatmap()) - } + Clock = new FramedClock(clock = new ManualClock()), + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new[] + { + drawableRuleset = (DrawableManiaRuleset)Ruleset.Value.CreateInstance().CreateDrawableRulesetWith(createTestBeatmap()) + } + }; + + drawableRuleset.AllowBackwardsSeeks = true; }); AddStep("retrieve config bindable", () => { diff --git a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs index 0bdd0ceae6..d4b69c1be2 100644 --- a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs +++ b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs @@ -100,6 +100,7 @@ namespace osu.Game.Tests.NonVisual public override Container FrameStableComponents { get; } public override IFrameStableClock FrameStableClock { get; } internal override bool FrameStablePlayback { get; set; } + public override bool AllowBackwardsSeeks { get; set; } public override IReadOnlyList Mods { get; } public override double GameplayStartTime { get; } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs index 434d853992..f19f4b6690 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs @@ -29,6 +29,8 @@ namespace osu.Game.Tests.Visual.Gameplay protected override bool AllowFail => false; + protected override bool AllowBackwardsSeeks => true; + [SetUpSteps] public override void SetUpSteps() { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFrameStabilityContainer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFrameStabilityContainer.cs index 98a97e1d23..0cff675b28 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFrameStabilityContainer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFrameStabilityContainer.cs @@ -131,6 +131,9 @@ namespace osu.Game.Tests.Visual.Gameplay private void createStabilityContainer(double gameplayStartTime = double.MinValue) => AddStep("create container", () => mainContainer.Child = new FrameStabilityContainer(gameplayStartTime) + { + AllowBackwardsSeeks = true, + } .WithChild(consumer = new ClockConsumingChild())); private void seekManualTo(double time) => AddStep($"seek manual clock to {time}", () => manualClock.CurrentTime = time); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs index 3d35860fef..057197e819 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs @@ -16,6 +16,8 @@ namespace osu.Game.Tests.Visual.Gameplay { public partial class TestSceneGameplaySamplePlayback : PlayerTestScene { + protected override bool AllowBackwardsSeeks => true; + [Test] public void TestAllSamplesStopDuringSeek() { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs index 3cbd5eefac..6981591193 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs @@ -28,6 +28,8 @@ namespace osu.Game.Tests.Visual.Gameplay { public partial class TestSceneGameplaySampleTriggerSource : PlayerTestScene { + protected override bool AllowBackwardsSeeks => true; + private TestGameplaySampleTriggerSource sampleTriggerSource = null!; protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs index 56900a0549..e57177498d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs @@ -288,6 +288,7 @@ namespace osu.Game.Tests.Visual.Gameplay public override Container FrameStableComponents { get; } public override IFrameStableClock FrameStableClock { get; } internal override bool FrameStablePlayback { get; set; } + public override bool AllowBackwardsSeeks { get; set; } public override IReadOnlyList Mods { get; } public override double GameplayStartTime { get; } diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs index b567e8de8d..88effb4a7b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs @@ -269,6 +269,7 @@ namespace osu.Game.Tests.Visual.Gameplay drawableRuleset = (TestDrawablePoolingRuleset)ruleset.CreateDrawableRulesetWith(CreateWorkingBeatmap(beatmap).GetPlayableBeatmap(ruleset.RulesetInfo)); drawableRuleset.FrameStablePlayback = true; + drawableRuleset.AllowBackwardsSeeks = true; drawableRuleset.PoolSize = poolSize; Child = new Container diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs index 98825b27d4..f532921d63 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs @@ -31,6 +31,8 @@ namespace osu.Game.Tests.Visual.Gameplay { protected override bool HasCustomSteps => true; + protected override bool AllowBackwardsSeeks => true; + protected new OutroPlayer Player => (OutroPlayer)base.Player; private double currentBeatmapDuration; diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 4aeb3d4862..13e28279e6 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -81,6 +81,19 @@ namespace osu.Game.Rulesets.UI public override IFrameStableClock FrameStableClock => frameStabilityContainer; + private bool allowBackwardsSeeks; + + public override bool AllowBackwardsSeeks + { + get => allowBackwardsSeeks; + set + { + allowBackwardsSeeks = value; + if (frameStabilityContainer != null) + frameStabilityContainer.AllowBackwardsSeeks = value; + } + } + private bool frameStablePlayback = true; internal override bool FrameStablePlayback @@ -178,6 +191,7 @@ namespace osu.Game.Rulesets.UI InternalChild = frameStabilityContainer = new FrameStabilityContainer(GameplayStartTime) { FrameStablePlayback = FrameStablePlayback, + AllowBackwardsSeeks = AllowBackwardsSeeks, Children = new Drawable[] { FrameStableComponents, @@ -463,6 +477,12 @@ namespace osu.Game.Rulesets.UI /// internal abstract bool FrameStablePlayback { get; set; } + /// + /// When a replay is not attached, we usually block any backwards seeks. + /// This will bypass the check. Should only be used for tests. + /// + public abstract bool AllowBackwardsSeeks { get; set; } + /// /// The mods which are to be applied. /// diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 03f3b8788f..ab48711955 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -6,7 +6,6 @@ using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; -using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; @@ -26,6 +25,8 @@ namespace osu.Game.Rulesets.UI { public ReplayInputHandler? ReplayInputHandler { get; set; } + public bool AllowBackwardsSeeks { get; set; } + /// /// The number of CPU milliseconds to spend at most during seek catch-up. /// @@ -157,7 +158,7 @@ namespace osu.Game.Rulesets.UI // // It basically says that "while we're running in frame stable mode, and don't have a replay attached, // time should never go backwards". If it does, we stop running gameplay until it returns to normal. - if (!hasReplayAttached && FrameStablePlayback && proposedTime > referenceClock.CurrentTime && !DebugUtils.IsNUnitRunning) + if (!hasReplayAttached && FrameStablePlayback && proposedTime > referenceClock.CurrentTime && !AllowBackwardsSeeks) { Logger.Log($"Denying backwards seek during gameplay (reference: {referenceClock.CurrentTime:N2} stable: {proposedTime:N2})"); state = PlaybackState.NotValid; diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index ee184c1f35..43d779261c 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -70,10 +70,20 @@ namespace osu.Game.Tests.Visual AddStep($"Load player for {CreatePlayerRuleset().Description}", LoadPlayer); AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1); + + if (AllowBackwardsSeeks) + { + AddStep("allow backwards seeking", () => + { + Player.DrawableRuleset.AllowBackwardsSeeks = AllowBackwardsSeeks; + }); + } } protected virtual bool AllowFail => false; + protected virtual bool AllowBackwardsSeeks => false; + protected virtual bool Autoplay => false; protected void LoadPlayer() => LoadPlayer(Array.Empty()); @@ -126,6 +136,6 @@ namespace osu.Game.Tests.Visual protected sealed override Ruleset CreateRuleset() => CreatePlayerRuleset(); - protected virtual TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false, false); + protected virtual TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false, false, AllowBackwardsSeeks); } } From cc8b838bd45d5df2be723b6cb1ddd82bec5622b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Feb 2024 23:03:27 +0800 Subject: [PATCH 3312/4092] Add comprehensive log output to help figure out problematic clocks --- osu.Game/Beatmaps/FramedBeatmapClock.cs | 26 ++++++++++++++++++- .../Rulesets/UI/FrameStabilityContainer.cs | 7 +++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/FramedBeatmapClock.cs b/osu.Game/Beatmaps/FramedBeatmapClock.cs index 49dff96ff1..af7be235fc 100644 --- a/osu.Game/Beatmaps/FramedBeatmapClock.cs +++ b/osu.Game/Beatmaps/FramedBeatmapClock.cs @@ -38,6 +38,7 @@ namespace osu.Game.Beatmaps private IDisposable? beatmapOffsetSubscription; private readonly DecouplingFramedClock decoupledTrack; + private readonly InterpolatingFramedClock interpolatedTrack; [Resolved] private OsuConfigManager config { get; set; } = null!; @@ -58,7 +59,7 @@ namespace osu.Game.Beatmaps // An interpolating clock is used to ensure precise time values even when the host audio subsystem is not reporting // high precision times (on windows there's generally only 5-10ms reporting intervals, as an example). - var interpolatedTrack = new InterpolatingFramedClock(decoupledTrack); + interpolatedTrack = new InterpolatingFramedClock(decoupledTrack); if (applyOffsets) { @@ -190,5 +191,28 @@ namespace osu.Game.Beatmaps base.Dispose(isDisposing); beatmapOffsetSubscription?.Dispose(); } + + public string GetSnapshot() + { + return + $"originalSource: {output(Source)}\n" + + $"userGlobalOffsetClock: {output(userGlobalOffsetClock)}\n" + + $"platformOffsetClock: {output(platformOffsetClock)}\n" + + $"userBeatmapOffsetClock: {output(userBeatmapOffsetClock)}\n" + + $"interpolatedTrack: {output(interpolatedTrack)}\n" + + $"decoupledTrack: {output(decoupledTrack)}\n" + + $"finalClockSource: {output(finalClockSource)}\n"; + + string output(IClock? clock) + { + if (clock == null) + return "null"; + + if (clock is IFrameBasedClock framed) + return $"current: {clock.CurrentTime:N2} running: {clock.IsRunning} rate: {clock.Rate} elapsed: {framed.ElapsedFrameTime:N2}"; + + return $"current: {clock.CurrentTime:N2} running: {clock.IsRunning} rate: {clock.Rate}"; + } + } } } diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index ab48711955..c09018e8ca 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -3,13 +3,16 @@ using System; using System.Diagnostics; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; +using osu.Framework.Testing; using osu.Framework.Timing; +using osu.Game.Beatmaps; using osu.Game.Input.Handlers; using osu.Game.Screens.Play; @@ -161,6 +164,10 @@ namespace osu.Game.Rulesets.UI if (!hasReplayAttached && FrameStablePlayback && proposedTime > referenceClock.CurrentTime && !AllowBackwardsSeeks) { Logger.Log($"Denying backwards seek during gameplay (reference: {referenceClock.CurrentTime:N2} stable: {proposedTime:N2})"); + + if (parentGameplayClock is GameplayClockContainer gcc) + Logger.Log($"{gcc.ChildrenOfType().Single().GetSnapshot()}"); + state = PlaybackState.NotValid; return; } From 59b9d29a79c2520cda4b1fca7d2b8373e501c71d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Feb 2024 23:29:50 +0800 Subject: [PATCH 3313/4092] Fix formatting? --- .../Visual/Gameplay/TestSceneFrameStabilityContainer.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFrameStabilityContainer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFrameStabilityContainer.cs index 0cff675b28..c2999e3f5a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFrameStabilityContainer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFrameStabilityContainer.cs @@ -130,11 +130,12 @@ namespace osu.Game.Tests.Visual.Gameplay } private void createStabilityContainer(double gameplayStartTime = double.MinValue) => AddStep("create container", () => + { mainContainer.Child = new FrameStabilityContainer(gameplayStartTime) - { - AllowBackwardsSeeks = true, - } - .WithChild(consumer = new ClockConsumingChild())); + { + AllowBackwardsSeeks = true, + }.WithChild(consumer = new ClockConsumingChild()); + }); private void seekManualTo(double time) => AddStep($"seek manual clock to {time}", () => manualClock.CurrentTime = time); From eb0933c3a5aa2de47820d020d34cabaf7552e7e6 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 29 Feb 2024 20:35:20 +0300 Subject: [PATCH 3314/4092] Fix allocations in EffectPointVisualisation --- .../Summary/Parts/EffectPointVisualisation.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs index d92beba38a..bf87470e01 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.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.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -53,7 +52,18 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts // for changes. ControlPointInfo needs a refactor to make this flow better, but it should do for now. Scheduler.AddDelayed(() => { - var next = beatmap.ControlPointInfo.EffectPoints.FirstOrDefault(c => c.Time > effect.Time); + EffectControlPoint? next = null; + + for (int i = 0; i < beatmap.ControlPointInfo.EffectPoints.Count; i++) + { + var point = beatmap.ControlPointInfo.EffectPoints[i]; + + if (point.Time > effect.Time) + { + next = point; + break; + } + } if (!ReferenceEquals(nextControlPoint, next)) { From 61cc5d6f29606c3513f6c5b699849ef155be2f1f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Mar 2024 11:24:12 +0800 Subject: [PATCH 3315/4092] Fix typos in xmldoc --- osu.Desktop/Windows/WindowsAssociationManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Desktop/Windows/WindowsAssociationManager.cs b/osu.Desktop/Windows/WindowsAssociationManager.cs index c784d52a4f..181403d287 100644 --- a/osu.Desktop/Windows/WindowsAssociationManager.cs +++ b/osu.Desktop/Windows/WindowsAssociationManager.cs @@ -180,7 +180,7 @@ namespace osu.Desktop.Windows private string programId => $@"{program_id_prefix}{Extension}"; /// - /// Installs a file extenstion association in accordance with https://learn.microsoft.com/en-us/windows/win32/com/-progid--key + /// Installs a file extension association in accordance with https://learn.microsoft.com/en-us/windows/win32/com/-progid--key /// public void Install() { @@ -219,7 +219,7 @@ namespace osu.Desktop.Windows } /// - /// Uninstalls the file extenstion association in accordance with https://learn.microsoft.com/en-us/windows/win32/shell/fa-file-types#deleting-registry-information-during-uninstallation + /// Uninstalls the file extension association in accordance with https://learn.microsoft.com/en-us/windows/win32/shell/fa-file-types#deleting-registry-information-during-uninstallation /// public void Uninstall() { From 00527da27d97ceba51f6d8bd46f4ec94542916a5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Mar 2024 11:42:35 +0800 Subject: [PATCH 3316/4092] When discord is set to privacy mode, don't show beatmap being edited --- osu.Game/Users/UserActivity.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Users/UserActivity.cs b/osu.Game/Users/UserActivity.cs index 1b09666df6..404ed141b9 100644 --- a/osu.Game/Users/UserActivity.cs +++ b/osu.Game/Users/UserActivity.cs @@ -151,7 +151,11 @@ namespace osu.Game.Users public EditingBeatmap() { } public override string GetStatus(bool hideIdentifiableInformation = false) => @"Editing a beatmap"; - public override string GetDetails(bool hideIdentifiableInformation = false) => BeatmapDisplayTitle; + + public override string GetDetails(bool hideIdentifiableInformation = false) => hideIdentifiableInformation + // For now let's assume that showing the beatmap a user is editing could reveal unwanted information. + ? string.Empty + : BeatmapDisplayTitle; } [MessagePackObject] From 4ad8bbb9e2d17c6c35fb26f37004c077af87dddf Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 1 Mar 2024 13:20:37 +0900 Subject: [PATCH 3317/4092] remove useless DrawablePool --- osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs index 0f2f9dc323..8bb5ee3617 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs @@ -107,8 +107,6 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters JudgementSpacing.BindValueChanged(_ => updateMetrics(), true); } - private readonly DrawablePool judgementLinePool = new DrawablePool(50); - public void Push(HitErrorShape shape) { Add(shape); From 19ed78eef57828c1da4210e5796bd5e7b7fcdb48 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Mar 2024 12:34:21 +0800 Subject: [PATCH 3318/4092] Log backwards seeks to sentry --- osu.Game/OsuGame.cs | 3 +++ .../Rulesets/UI/FrameStabilityContainer.cs | 15 ++++++++++--- .../Utils/SentryOnlyDiagnosticsException.cs | 21 +++++++++++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 osu.Game/Utils/SentryOnlyDiagnosticsException.cs diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 7d128a808a..eb1219f183 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1190,6 +1190,9 @@ namespace osu.Game { if (entry.Level < LogLevel.Important || entry.Target > LoggingTarget.Database || entry.Target == null) return; + if (entry.Exception is SentryOnlyDiagnosticsException) + return; + const int short_term_display_limit = 3; if (recentLogCount < short_term_display_limit) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index c09018e8ca..884310e44c 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -15,6 +15,7 @@ using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Input.Handlers; using osu.Game.Screens.Play; +using osu.Game.Utils; namespace osu.Game.Rulesets.UI { @@ -29,6 +30,7 @@ namespace osu.Game.Rulesets.UI public ReplayInputHandler? ReplayInputHandler { get; set; } public bool AllowBackwardsSeeks { get; set; } + private double? lastBackwardsSeekLogTime; /// /// The number of CPU milliseconds to spend at most during seek catch-up. @@ -163,10 +165,17 @@ namespace osu.Game.Rulesets.UI // time should never go backwards". If it does, we stop running gameplay until it returns to normal. if (!hasReplayAttached && FrameStablePlayback && proposedTime > referenceClock.CurrentTime && !AllowBackwardsSeeks) { - Logger.Log($"Denying backwards seek during gameplay (reference: {referenceClock.CurrentTime:N2} stable: {proposedTime:N2})"); + if (lastBackwardsSeekLogTime == null || Math.Abs(Clock.CurrentTime - lastBackwardsSeekLogTime.Value) > 1000) + { + lastBackwardsSeekLogTime = Clock.CurrentTime; - if (parentGameplayClock is GameplayClockContainer gcc) - Logger.Log($"{gcc.ChildrenOfType().Single().GetSnapshot()}"); + string loggableContent = $"Denying backwards seek during gameplay (reference: {referenceClock.CurrentTime:N2} stable: {proposedTime:N2})"; + + if (parentGameplayClock is GameplayClockContainer gcc) + loggableContent += $"\n{gcc.ChildrenOfType().Single().GetSnapshot()}"; + + Logger.Error(new SentryOnlyDiagnosticsException("backwards seek"), loggableContent); + } state = PlaybackState.NotValid; return; diff --git a/osu.Game/Utils/SentryOnlyDiagnosticsException.cs b/osu.Game/Utils/SentryOnlyDiagnosticsException.cs new file mode 100644 index 0000000000..1659b8a213 --- /dev/null +++ b/osu.Game/Utils/SentryOnlyDiagnosticsException.cs @@ -0,0 +1,21 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; + +namespace osu.Game.Utils +{ + /// + /// Log to sentry without showing an error notification to the user. + /// + /// + /// This can be used to convey important diagnostics to us developers without + /// getting in the user's way. Should be used sparingly. + internal class SentryOnlyDiagnosticsException : Exception + { + public SentryOnlyDiagnosticsException(string message) + : base(message) + { + } + } +} From 060e17e9898256128632ab1a524ab33c87d0b9c2 Mon Sep 17 00:00:00 2001 From: jvyden Date: Thu, 29 Feb 2024 19:57:32 -0500 Subject: [PATCH 3319/4092] Support Discord game invites in multiplayer lobbies --- osu.Desktop/DiscordRichPresence.cs | 83 +++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 2 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index f0da708766..b85abdb4fe 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -9,10 +9,13 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Logging; +using osu.Game; using osu.Game.Configuration; using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Multiplayer; +using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Users; using LogLevel = osu.Framework.Logging.LogLevel; @@ -22,6 +25,7 @@ namespace osu.Desktop internal partial class DiscordRichPresence : Component { private const string client_id = "367827983903490050"; + public const string DISCORD_PROTOCOL = $"discord-{client_id}://"; private DiscordRpcClient client = null!; @@ -33,6 +37,12 @@ namespace osu.Desktop [Resolved] private IAPIProvider api { get; set; } = null!; + [Resolved] + private OsuGame game { get; set; } = null!; + + [Resolved] + private MultiplayerClient multiplayerClient { get; set; } = null!; + private readonly IBindable status = new Bindable(); private readonly IBindable activity = new Bindable(); @@ -40,7 +50,12 @@ namespace osu.Desktop private readonly RichPresence presence = new RichPresence { - Assets = new Assets { LargeImageKey = "osu_logo_lazer", } + Assets = new Assets { LargeImageKey = "osu_logo_lazer" }, + Secrets = new Secrets + { + JoinSecret = null, + SpectateSecret = null, + }, }; [BackgroundDependencyLoader] @@ -52,8 +67,14 @@ namespace osu.Desktop }; client.OnReady += onReady; + client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network, LogLevel.Error); - client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network); + // set up stuff for spectate/join + // first, we register a uri scheme for when osu! isn't running and a user clicks join/spectate + // the rpc library we use also happens to _require_ that we do this + client.RegisterUriScheme(); + client.Subscribe(EventType.Join); // we have to explicitly tell discord to send us join events. + client.OnJoin += onJoin; config.BindWith(OsuSetting.DiscordRichPresence, privacyMode); @@ -114,6 +135,28 @@ namespace osu.Desktop { presence.Buttons = null; } + + if (!hideIdentifiableInformation && multiplayerClient.Room != null) + { + MultiplayerRoom room = multiplayerClient.Room; + presence.Party = new Party + { + Privacy = string.IsNullOrEmpty(room.Settings.Password) ? Party.PrivacySetting.Public : Party.PrivacySetting.Private, + ID = room.RoomID.ToString(), + // technically lobbies can have infinite users, but Discord needs this to be set to something. + // 1024 just happens to look nice. + // https://discord.com/channels/188630481301012481/188630652340404224/1212967974793642034 + Max = 1024, + Size = room.Users.Count, + }; + + presence.Secrets.JoinSecret = $"{room.RoomID}:{room.Settings.Password}"; + } + else + { + presence.Party = null; + presence.Secrets.JoinSecret = null; + } } else { @@ -139,6 +182,22 @@ namespace osu.Desktop client.SetPresence(presence); } + private void onJoin(object sender, JoinMessage args) + { + game.Window?.Raise(); // users will expect to be brought back to osu! when joining a lobby from discord + + if (!tryParseRoomSecret(args.Secret, out long roomId, out string? password)) + Logger.Log("Failed to parse the room secret Discord gave us", LoggingTarget.Network, LogLevel.Error); + + var request = new GetRoomRequest(roomId); + request.Success += room => Schedule(() => + { + game.PresentMultiplayerMatch(room, password); + }); + request.Failure += _ => Logger.Log("Couldn't find the room Discord gave us", LoggingTarget.Network, LogLevel.Error); + api.Queue(request); + } + private static readonly int ellipsis_length = Encoding.UTF8.GetByteCount(new[] { '…' }); private string truncate(string str) @@ -160,6 +219,26 @@ namespace osu.Desktop }); } + private static bool tryParseRoomSecret(ReadOnlySpan secret, out long roomId, out string? password) + { + roomId = 0; + password = null; + + int roomSecretSplitIndex = secret.IndexOf(':'); + + if (roomSecretSplitIndex == -1) + return false; + + if (!long.TryParse(secret[..roomSecretSplitIndex], out roomId)) + return false; + + // just convert to string here, we're going to have to alloc it later anyways + password = secret[(roomSecretSplitIndex + 1)..].ToString(); + if (password.Length == 0) password = null; + + return true; + } + private int? getBeatmapID(UserActivity activity) { switch (activity) From 92235e7789271da1080f6f8f635e52f5f8490002 Mon Sep 17 00:00:00 2001 From: jvyden Date: Fri, 1 Mar 2024 00:02:20 -0500 Subject: [PATCH 3320/4092] Make truncate and getBeatmapID static Code quality was complaining about hidden variables so I opted for this solution. --- osu.Desktop/DiscordRichPresence.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index b85abdb4fe..91f7f6e1da 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -200,7 +200,7 @@ namespace osu.Desktop private static readonly int ellipsis_length = Encoding.UTF8.GetByteCount(new[] { '…' }); - private string truncate(string str) + private static string truncate(string str) { if (Encoding.UTF8.GetByteCount(str) <= 128) return str; @@ -239,7 +239,7 @@ namespace osu.Desktop return true; } - private int? getBeatmapID(UserActivity activity) + private static int? getBeatmapID(UserActivity activity) { switch (activity) { From 963c0af8143654e20910ed4f6b48ebce5845ad4e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Mar 2024 16:43:47 +0800 Subject: [PATCH 3321/4092] Fix beatmap information still showing when testing a beatmap --- osu.Game/Users/UserActivity.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Users/UserActivity.cs b/osu.Game/Users/UserActivity.cs index 404ed141b9..a5dd2cb37c 100644 --- a/osu.Game/Users/UserActivity.cs +++ b/osu.Game/Users/UserActivity.cs @@ -119,10 +119,10 @@ namespace osu.Game.Users } [MessagePackObject] - public class TestingBeatmap : InGame + public class TestingBeatmap : EditingBeatmap { public TestingBeatmap(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset) - : base(beatmapInfo, ruleset) + : base(beatmapInfo) { } From c6201ea5de4bc0ad9943fd5e5f83783e6172205d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Mar 2024 20:28:52 +0800 Subject: [PATCH 3322/4092] Remove unused ruleset parameter when testing beatmap in editor --- osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs | 3 +-- osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs | 2 +- osu.Game/Users/UserActivity.cs | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index 4df34e6244..91942c391a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -14,7 +14,6 @@ using osu.Game.Beatmaps; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Rulesets; -using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Tests.Beatmaps; using osu.Game.Users; @@ -142,7 +141,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("choosing", () => activity.Value = new UserActivity.ChoosingBeatmap()); AddStep("editing beatmap", () => activity.Value = new UserActivity.EditingBeatmap(new BeatmapInfo())); AddStep("modding beatmap", () => activity.Value = new UserActivity.ModdingBeatmap(new BeatmapInfo())); - AddStep("testing beatmap", () => activity.Value = new UserActivity.TestingBeatmap(new BeatmapInfo(), new OsuRuleset().RulesetInfo)); + AddStep("testing beatmap", () => activity.Value = new UserActivity.TestingBeatmap(new BeatmapInfo())); } [Test] diff --git a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs index 7dff05667d..55607cbb7c 100644 --- a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs +++ b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs @@ -16,7 +16,7 @@ namespace osu.Game.Screens.Edit.GameplayTest private readonly Editor editor; private readonly EditorState editorState; - protected override UserActivity InitialActivity => new UserActivity.TestingBeatmap(Beatmap.Value.BeatmapInfo, Ruleset.Value); + protected override UserActivity InitialActivity => new UserActivity.TestingBeatmap(Beatmap.Value.BeatmapInfo); [Resolved] private MusicController musicController { get; set; } = null!; diff --git a/osu.Game/Users/UserActivity.cs b/osu.Game/Users/UserActivity.cs index a5dd2cb37c..a431b204bc 100644 --- a/osu.Game/Users/UserActivity.cs +++ b/osu.Game/Users/UserActivity.cs @@ -121,7 +121,7 @@ namespace osu.Game.Users [MessagePackObject] public class TestingBeatmap : EditingBeatmap { - public TestingBeatmap(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset) + public TestingBeatmap(IBeatmapInfo beatmapInfo) : base(beatmapInfo) { } From 3df32638c2235029625d929c41ce6237c646e75c Mon Sep 17 00:00:00 2001 From: Susko3 Date: Fri, 1 Mar 2024 16:06:30 +0100 Subject: [PATCH 3323/4092] Fix association descriptions never being written on update --- osu.Desktop/Windows/WindowsAssociationManager.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Desktop/Windows/WindowsAssociationManager.cs b/osu.Desktop/Windows/WindowsAssociationManager.cs index 181403d287..11b5c19ca1 100644 --- a/osu.Desktop/Windows/WindowsAssociationManager.cs +++ b/osu.Desktop/Windows/WindowsAssociationManager.cs @@ -82,6 +82,10 @@ namespace osu.Desktop.Windows try { updateAssociations(); + + // TODO: Remove once UpdateDescriptions() is called as specified in the xmldoc. + updateDescriptions(null); // always write default descriptions, in case of updating from an older version in which file associations were not implemented/installed + NotifyShellUpdate(); } catch (Exception e) From 77b3055978bdd3b872d4297f1b70ba05f6164c05 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 29 Feb 2024 23:23:57 +0300 Subject: [PATCH 3324/4092] Improve sb sprite end time guessing --- osu.Game/Storyboards/CommandTimelineGroup.cs | 41 ++-------------- osu.Game/Storyboards/StoryboardSprite.cs | 50 ++++++++++++++++++-- 2 files changed, 50 insertions(+), 41 deletions(-) diff --git a/osu.Game/Storyboards/CommandTimelineGroup.cs b/osu.Game/Storyboards/CommandTimelineGroup.cs index 0b96db6861..c899cf77d3 100644 --- a/osu.Game/Storyboards/CommandTimelineGroup.cs +++ b/osu.Game/Storyboards/CommandTimelineGroup.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 osuTK; using osuTK.Graphics; using osu.Framework.Graphics; @@ -46,32 +45,10 @@ namespace osu.Game.Storyboards } [JsonIgnore] - public double CommandsStartTime - { - get - { - double min = double.MaxValue; - - for (int i = 0; i < timelines.Length; i++) - min = Math.Min(min, timelines[i].StartTime); - - return min; - } - } + public double CommandsStartTime => timelines.Min(static t => t.StartTime); [JsonIgnore] - public double CommandsEndTime - { - get - { - double max = double.MinValue; - - for (int i = 0; i < timelines.Length; i++) - max = Math.Max(max, timelines[i].EndTime); - - return max; - } - } + public double CommandsEndTime => timelines.Max(static t => t.EndTime); [JsonIgnore] public double CommandsDuration => CommandsEndTime - CommandsStartTime; @@ -83,19 +60,7 @@ namespace osu.Game.Storyboards public virtual double EndTime => CommandsEndTime; [JsonIgnore] - public bool HasCommands - { - get - { - for (int i = 0; i < timelines.Length; i++) - { - if (timelines[i].HasCommands) - return true; - } - - return false; - } - } + public bool HasCommands => timelines.Any(static t => t.HasCommands); public virtual IEnumerable.TypedCommand> GetCommands(CommandTimelineSelector timelineSelector, double offset = 0) { diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index 982185d51b..350438942e 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -85,12 +85,56 @@ namespace osu.Game.Storyboards { get { - double latestEndTime = TimelineGroup.EndTime; + double latestEndTime = double.MaxValue; + + // Ignore the whole setup if there are loops. In theory they can be handled here too, however the logic will be overly complex. + if (loops.Count == 0) + { + // Here we are starting from maximum value and trying to minimise the end time on each step. + // There are few solid guesses we can make using which sprite's end time can be minimised: alpha = 0, scale = 0, colour.a = 0. + double[] deathTimes = + { + double.MaxValue, // alpha + double.MaxValue, // colour alpha + double.MaxValue, // scale + double.MaxValue, // scale x + double.MaxValue, // scale y + }; + + // The loops below are following the same pattern. + // We could be using TimelineGroup.EndValue here, however it's possible to have multiple commands with 0 value in a row + // so we are saving the earliest of them. + foreach (var alphaCommand in TimelineGroup.Alpha.Commands) + { + deathTimes[0] = alphaCommand.EndValue == 0 + ? Math.Min(alphaCommand.EndTime, deathTimes[0]) // commands are ordered by the start time, however end time may vary. Save the earliest. + : double.MaxValue; // If value isn't 0 (sprite becomes visible again), revert the saved state. + } + + foreach (var colourCommand in TimelineGroup.Colour.Commands) + deathTimes[1] = colourCommand.EndValue.A == 0 ? Math.Min(colourCommand.EndTime, deathTimes[1]) : double.MaxValue; + + foreach (var scaleCommand in TimelineGroup.Scale.Commands) + deathTimes[2] = scaleCommand.EndValue == 0 ? Math.Min(scaleCommand.EndTime, deathTimes[2]) : double.MaxValue; + + foreach (var scaleCommand in TimelineGroup.VectorScale.Commands) + { + deathTimes[3] = scaleCommand.EndValue.X == 0 ? Math.Min(scaleCommand.EndTime, deathTimes[3]) : double.MaxValue; + deathTimes[4] = scaleCommand.EndValue.Y == 0 ? Math.Min(scaleCommand.EndTime, deathTimes[4]) : double.MaxValue; + } + + // Take the minimum time of all the potential "death" reasons. + latestEndTime = deathTimes.Min(); + } + + // If the logic above fails to find anything or discarded by the fact that there are loops present, latestEndTime will be double.MaxValue + // and thus conservativeEndTime will be used. + double conservativeEndTime = TimelineGroup.EndTime; foreach (var l in loops) - latestEndTime = Math.Max(latestEndTime, l.StartTime + l.CommandsDuration * l.TotalIterations); + conservativeEndTime = Math.Max(conservativeEndTime, l.StartTime + l.CommandsDuration * l.TotalIterations); - return latestEndTime; + return Math.Min(latestEndTime, conservativeEndTime); } } From 37e7a4dea7f957231a4eb2aaf9e95654ab4d711e Mon Sep 17 00:00:00 2001 From: Jayden Date: Fri, 1 Mar 2024 14:32:44 -0500 Subject: [PATCH 3325/4092] Fix yapping Co-authored-by: Salman Ahmed --- osu.Desktop/DiscordRichPresence.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 91f7f6e1da..035add8044 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -25,7 +25,6 @@ namespace osu.Desktop internal partial class DiscordRichPresence : Component { private const string client_id = "367827983903490050"; - public const string DISCORD_PROTOCOL = $"discord-{client_id}://"; private DiscordRpcClient client = null!; @@ -69,11 +68,9 @@ namespace osu.Desktop client.OnReady += onReady; client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network, LogLevel.Error); - // set up stuff for spectate/join - // first, we register a uri scheme for when osu! isn't running and a user clicks join/spectate - // the rpc library we use also happens to _require_ that we do this + // A URI scheme is required to support game invitations, as well as informing Discord of the game executable path to support launching the game when a user clicks on join/spectate. client.RegisterUriScheme(); - client.Subscribe(EventType.Join); // we have to explicitly tell discord to send us join events. + client.Subscribe(EventType.Join); client.OnJoin += onJoin; config.BindWith(OsuSetting.DiscordRichPresence, privacyMode); @@ -184,7 +181,7 @@ namespace osu.Desktop private void onJoin(object sender, JoinMessage args) { - game.Window?.Raise(); // users will expect to be brought back to osu! when joining a lobby from discord + game.Window?.Raise(); if (!tryParseRoomSecret(args.Secret, out long roomId, out string? password)) Logger.Log("Failed to parse the room secret Discord gave us", LoggingTarget.Network, LogLevel.Error); @@ -232,7 +229,6 @@ namespace osu.Desktop if (!long.TryParse(secret[..roomSecretSplitIndex], out roomId)) return false; - // just convert to string here, we're going to have to alloc it later anyways password = secret[(roomSecretSplitIndex + 1)..].ToString(); if (password.Length == 0) password = null; From e0c73eb36225b1e7e058b3af5cc553f791ae28b6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 1 Mar 2024 22:49:12 +0300 Subject: [PATCH 3326/4092] Fix osu!mania key images potentially showing gaps between columns --- osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs index 48b92a8486..9fd33b9963 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Rulesets.Mania.UI; @@ -49,14 +50,14 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy upSprite = new Sprite { Origin = Anchor.BottomCentre, - Texture = skin.GetTexture(upImage), + Texture = skin.GetTexture(upImage, WrapMode.ClampToEdge, WrapMode.ClampToEdge), RelativeSizeAxes = Axes.X, Width = 1 }, downSprite = new Sprite { Origin = Anchor.BottomCentre, - Texture = skin.GetTexture(downImage), + Texture = skin.GetTexture(downImage, WrapMode.ClampToEdge, WrapMode.ClampToEdge), RelativeSizeAxes = Axes.X, Width = 1, Alpha = 0 From f1b66da469ead2105e6b3f1d83e95e04b7f4ec45 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 1 Mar 2024 22:57:13 +0300 Subject: [PATCH 3327/4092] Add comments --- osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs index 9fd33b9963..6ba91fbbd5 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs @@ -50,6 +50,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy upSprite = new Sprite { Origin = Anchor.BottomCentre, + // ClampToEdge is used to avoid gaps between keys, see: https://github.com/ppy/osu/issues/27431 Texture = skin.GetTexture(upImage, WrapMode.ClampToEdge, WrapMode.ClampToEdge), RelativeSizeAxes = Axes.X, Width = 1 @@ -57,6 +58,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy downSprite = new Sprite { Origin = Anchor.BottomCentre, + // ClampToEdge is used to avoid gaps between keys, see: https://github.com/ppy/osu/issues/27431 Texture = skin.GetTexture(downImage, WrapMode.ClampToEdge, WrapMode.ClampToEdge), RelativeSizeAxes = Axes.X, Width = 1, From 82373ff752ab9c1d961a8c5673499ddb913bec05 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 2 Mar 2024 03:18:42 +0300 Subject: [PATCH 3328/4092] Add failing catch beatmap --- .../CatchBeatmapConversionTest.cs | 1 + .../Beatmaps/1041052-expected-conversion.json | 1 + .../Resources/Testing/Beatmaps/1041052.osu | 210 ++++++++++++++++++ 3 files changed, 212 insertions(+) create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/1041052-expected-conversion.json create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/1041052.osu diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs index d0ecb828df..81e0675aaa 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs @@ -53,6 +53,7 @@ namespace osu.Game.Rulesets.Catch.Tests [TestCase("3689906", new[] { typeof(CatchModDoubleTime), typeof(CatchModEasy) })] [TestCase("3949367", new[] { typeof(CatchModDoubleTime), typeof(CatchModEasy) })] [TestCase("112643")] + [TestCase("1041052", new[] { typeof(CatchModHardRock) })] public new void Test(string name, params Type[] mods) => base.Test(name, mods); protected override IEnumerable CreateConvertValue(HitObject hitObject) diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/1041052-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/1041052-expected-conversion.json new file mode 100644 index 0000000000..01150e701d --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/1041052-expected-conversion.json @@ -0,0 +1 @@ +{"Mappings":[{"StartTime":1155.0,"Objects":[{"StartTime":1155.0,"Position":208.0,"HyperDash":false},{"StartTime":1251.0,"Position":167.675858,"HyperDash":false},{"StartTime":1348.0,"Position":157.087921,"HyperDash":false},{"StartTime":1445.0,"Position":128.5,"HyperDash":false},{"StartTime":1542.0,"Position":105.912064,"HyperDash":false},{"StartTime":1620.0,"Position":102.336205,"HyperDash":false},{"StartTime":1735.0,"Position":55.0,"HyperDash":false}]},{"StartTime":2122.0,"Objects":[{"StartTime":2122.0,"Position":275.0,"HyperDash":false}]},{"StartTime":2509.0,"Objects":[{"StartTime":2509.0,"Position":269.0,"HyperDash":false}]},{"StartTime":3284.0,"Objects":[{"StartTime":3284.0,"Position":448.0,"HyperDash":false},{"StartTime":3380.0,"Position":466.562317,"HyperDash":false},{"StartTime":3477.0,"Position":477.575867,"HyperDash":false},{"StartTime":3556.0,"Position":467.440033,"HyperDash":false},{"StartTime":3671.0,"Position":481.146881,"HyperDash":false}]},{"StartTime":4058.0,"Objects":[{"StartTime":4058.0,"Position":288.0,"HyperDash":false}]},{"StartTime":5025.0,"Objects":[{"StartTime":5025.0,"Position":128.0,"HyperDash":false},{"StartTime":5121.0,"Position":94.67586,"HyperDash":false},{"StartTime":5218.0,"Position":77.08793,"HyperDash":false},{"StartTime":5315.0,"Position":51.5,"HyperDash":false},{"StartTime":5412.0,"Position":77.08794,"HyperDash":false},{"StartTime":5490.0,"Position":111.663795,"HyperDash":false},{"StartTime":5605.0,"Position":128.0,"HyperDash":false}]},{"StartTime":5800.0,"Objects":[{"StartTime":5800.0,"Position":352.0,"HyperDash":false}]},{"StartTime":5993.0,"Objects":[{"StartTime":5993.0,"Position":240.0,"HyperDash":false}]},{"StartTime":6187.0,"Objects":[{"StartTime":6187.0,"Position":336.0,"HyperDash":false}]},{"StartTime":6574.0,"Objects":[{"StartTime":6574.0,"Position":416.0,"HyperDash":false},{"StartTime":6670.0,"Position":394.697662,"HyperDash":false},{"StartTime":6767.0,"Position":365.131775,"HyperDash":false},{"StartTime":6864.0,"Position":344.5659,"HyperDash":false},{"StartTime":6961.0,"Position":314.0,"HyperDash":false},{"StartTime":7057.0,"Position":279.6977,"HyperDash":false},{"StartTime":7154.0,"Position":263.131775,"HyperDash":false},{"StartTime":7233.0,"Position":259.310059,"HyperDash":false},{"StartTime":7348.0,"Position":212.0,"HyperDash":false}]},{"StartTime":8122.0,"Objects":[{"StartTime":8122.0,"Position":488.0,"HyperDash":false},{"StartTime":8218.0,"Position":475.697662,"HyperDash":false},{"StartTime":8315.0,"Position":437.131775,"HyperDash":false},{"StartTime":8394.0,"Position":399.3101,"HyperDash":false},{"StartTime":8509.0,"Position":386.0,"HyperDash":false}]},{"StartTime":8896.0,"Objects":[{"StartTime":8896.0,"Position":457.0,"HyperDash":false},{"StartTime":8992.0,"Position":444.675873,"HyperDash":false},{"StartTime":9089.0,"Position":406.087921,"HyperDash":false},{"StartTime":9186.0,"Position":384.5,"HyperDash":false},{"StartTime":9283.0,"Position":354.912048,"HyperDash":false},{"StartTime":9361.0,"Position":330.3362,"HyperDash":false},{"StartTime":9476.0,"Position":304.0,"HyperDash":false}]},{"StartTime":10058.0,"Objects":[{"StartTime":10058.0,"Position":400.0,"HyperDash":false}]},{"StartTime":10445.0,"Objects":[{"StartTime":10445.0,"Position":304.0,"HyperDash":false},{"StartTime":10541.0,"Position":277.697662,"HyperDash":false},{"StartTime":10638.0,"Position":253.131775,"HyperDash":false},{"StartTime":10735.0,"Position":212.565887,"HyperDash":false},{"StartTime":10832.0,"Position":202.0,"HyperDash":false},{"StartTime":10928.0,"Position":208.302322,"HyperDash":false},{"StartTime":11025.0,"Position":252.868225,"HyperDash":false},{"StartTime":11104.0,"Position":286.6899,"HyperDash":false},{"StartTime":11219.0,"Position":304.0,"HyperDash":false}]},{"StartTime":11606.0,"Objects":[{"StartTime":11606.0,"Position":400.0,"HyperDash":false}]},{"StartTime":11993.0,"Objects":[{"StartTime":11993.0,"Position":240.0,"HyperDash":false},{"StartTime":12089.0,"Position":231.675858,"HyperDash":false},{"StartTime":12186.0,"Position":189.087921,"HyperDash":false},{"StartTime":12283.0,"Position":156.5,"HyperDash":false},{"StartTime":12380.0,"Position":137.912064,"HyperDash":false},{"StartTime":12458.0,"Position":136.336212,"HyperDash":false},{"StartTime":12573.0,"Position":87.0,"HyperDash":false}]},{"StartTime":13154.0,"Objects":[{"StartTime":13154.0,"Position":0.0,"HyperDash":false}]},{"StartTime":13542.0,"Objects":[{"StartTime":13542.0,"Position":112.0,"HyperDash":false},{"StartTime":13638.0,"Position":119.913734,"HyperDash":false},{"StartTime":13735.0,"Position":144.5726,"HyperDash":false},{"StartTime":13832.0,"Position":179.8026,"HyperDash":false},{"StartTime":13929.0,"Position":191.357681,"HyperDash":false},{"StartTime":14007.0,"Position":228.85704,"HyperDash":false},{"StartTime":14122.0,"Position":241.622177,"HyperDash":false}]},{"StartTime":14316.0,"Objects":[{"StartTime":14316.0,"Position":288.0,"HyperDash":false},{"StartTime":14412.0,"Position":328.324127,"HyperDash":false},{"StartTime":14509.0,"Position":338.912079,"HyperDash":false},{"StartTime":14606.0,"Position":364.5,"HyperDash":false},{"StartTime":14703.0,"Position":338.912079,"HyperDash":false},{"StartTime":14781.0,"Position":301.3362,"HyperDash":false},{"StartTime":14896.0,"Position":288.0,"HyperDash":false}]},{"StartTime":15284.0,"Objects":[{"StartTime":15284.0,"Position":192.0,"HyperDash":false},{"StartTime":15362.0,"Position":187.7823,"HyperDash":false},{"StartTime":15477.0,"Position":169.192108,"HyperDash":false}]},{"StartTime":15864.0,"Objects":[{"StartTime":15864.0,"Position":464.0,"HyperDash":false}]},{"StartTime":16638.0,"Objects":[{"StartTime":16638.0,"Position":128.0,"HyperDash":false},{"StartTime":16734.0,"Position":90.86488,"HyperDash":false},{"StartTime":16831.0,"Position":78.3134155,"HyperDash":false},{"StartTime":16928.0,"Position":73.3517761,"HyperDash":false},{"StartTime":17025.0,"Position":34.6746674,"HyperDash":false},{"StartTime":17121.0,"Position":33.0714569,"HyperDash":false},{"StartTime":17218.0,"Position":2.65127468,"HyperDash":false},{"StartTime":17315.0,"Position":19.907608,"HyperDash":false},{"StartTime":17412.0,"Position":34.674675,"HyperDash":false},{"StartTime":17508.0,"Position":56.12827,"HyperDash":false},{"StartTime":17605.0,"Position":78.0694351,"HyperDash":false},{"StartTime":17684.0,"Position":97.97488,"HyperDash":false},{"StartTime":17799.0,"Position":128.0,"HyperDash":false}]},{"StartTime":18187.0,"Objects":[{"StartTime":18187.0,"Position":224.0,"HyperDash":false},{"StartTime":18283.0,"Position":231.165024,"HyperDash":false},{"StartTime":18380.0,"Position":273.681732,"HyperDash":false},{"StartTime":18477.0,"Position":304.374176,"HyperDash":false},{"StartTime":18574.0,"Position":316.397827,"HyperDash":false},{"StartTime":18670.0,"Position":312.8548,"HyperDash":false},{"StartTime":18767.0,"Position":345.5291,"HyperDash":false},{"StartTime":18864.0,"Position":328.007355,"HyperDash":false},{"StartTime":18961.0,"Position":316.397827,"HyperDash":false},{"StartTime":19057.0,"Position":304.594452,"HyperDash":false},{"StartTime":19154.0,"Position":273.924957,"HyperDash":false},{"StartTime":19233.0,"Position":271.063354,"HyperDash":false},{"StartTime":19348.0,"Position":224.0,"HyperDash":false}]},{"StartTime":19735.0,"Objects":[{"StartTime":19735.0,"Position":128.0,"HyperDash":false},{"StartTime":19831.0,"Position":136.324142,"HyperDash":false},{"StartTime":19928.0,"Position":178.912079,"HyperDash":false},{"StartTime":20025.0,"Position":223.5,"HyperDash":false},{"StartTime":20122.0,"Position":230.087936,"HyperDash":false},{"StartTime":20200.0,"Position":260.6638,"HyperDash":false},{"StartTime":20315.0,"Position":281.0,"HyperDash":false}]},{"StartTime":20896.0,"Objects":[{"StartTime":20896.0,"Position":432.0,"HyperDash":false}]},{"StartTime":21284.0,"Objects":[{"StartTime":21284.0,"Position":328.0,"HyperDash":false},{"StartTime":21380.0,"Position":357.324127,"HyperDash":false},{"StartTime":21477.0,"Position":378.912079,"HyperDash":false},{"StartTime":21574.0,"Position":420.5,"HyperDash":false},{"StartTime":21671.0,"Position":430.087952,"HyperDash":false},{"StartTime":21749.0,"Position":447.6638,"HyperDash":false},{"StartTime":21864.0,"Position":481.0,"HyperDash":false}]},{"StartTime":22445.0,"Objects":[{"StartTime":22445.0,"Position":328.0,"HyperDash":false}]},{"StartTime":22832.0,"Objects":[{"StartTime":22832.0,"Position":224.0,"HyperDash":false},{"StartTime":22928.0,"Position":188.675858,"HyperDash":false},{"StartTime":23025.0,"Position":173.087921,"HyperDash":false},{"StartTime":23122.0,"Position":156.5,"HyperDash":false},{"StartTime":23219.0,"Position":121.912064,"HyperDash":false},{"StartTime":23297.0,"Position":91.3362045,"HyperDash":false},{"StartTime":23412.0,"Position":71.0,"HyperDash":false}]},{"StartTime":23993.0,"Objects":[{"StartTime":23993.0,"Position":224.0,"HyperDash":false}]},{"StartTime":24380.0,"Objects":[{"StartTime":24380.0,"Position":112.0,"HyperDash":false},{"StartTime":24476.0,"Position":127.324142,"HyperDash":false},{"StartTime":24573.0,"Position":162.912079,"HyperDash":false},{"StartTime":24670.0,"Position":202.5,"HyperDash":false},{"StartTime":24767.0,"Position":214.087936,"HyperDash":false},{"StartTime":24845.0,"Position":246.663788,"HyperDash":false},{"StartTime":24960.0,"Position":265.0,"HyperDash":false}]},{"StartTime":25541.0,"Objects":[{"StartTime":25541.0,"Position":416.0,"HyperDash":false}]},{"StartTime":25929.0,"Objects":[{"StartTime":25929.0,"Position":304.0,"HyperDash":false},{"StartTime":26025.0,"Position":287.9714,"HyperDash":false},{"StartTime":26122.0,"Position":274.232758,"HyperDash":false},{"StartTime":26219.0,"Position":253.164063,"HyperDash":false},{"StartTime":26316.0,"Position":274.1704,"HyperDash":false},{"StartTime":26394.0,"Position":290.949921,"HyperDash":false},{"StartTime":26509.0,"Position":303.819763,"HyperDash":false}]},{"StartTime":27090.0,"Objects":[{"StartTime":27090.0,"Position":480.0,"HyperDash":false}]},{"StartTime":27477.0,"Objects":[{"StartTime":27477.0,"Position":384.0,"HyperDash":false},{"StartTime":27573.0,"Position":351.697662,"HyperDash":false},{"StartTime":27670.0,"Position":333.0,"HyperDash":false},{"StartTime":27749.0,"Position":356.6899,"HyperDash":false},{"StartTime":27864.0,"Position":384.0,"HyperDash":false}]},{"StartTime":28058.0,"Objects":[{"StartTime":28058.0,"Position":432.0,"HyperDash":false}]},{"StartTime":28445.0,"Objects":[{"StartTime":28445.0,"Position":333.0,"HyperDash":false},{"StartTime":28541.0,"Position":305.697662,"HyperDash":false},{"StartTime":28638.0,"Position":282.0,"HyperDash":false},{"StartTime":28717.0,"Position":319.6899,"HyperDash":false},{"StartTime":28832.0,"Position":333.0,"HyperDash":false}]},{"StartTime":29025.0,"Objects":[{"StartTime":29025.0,"Position":384.0,"HyperDash":false},{"StartTime":29121.0,"Position":341.697662,"HyperDash":false},{"StartTime":29218.0,"Position":333.131775,"HyperDash":false},{"StartTime":29297.0,"Position":293.3101,"HyperDash":false},{"StartTime":29412.0,"Position":282.0,"HyperDash":false}]},{"StartTime":29606.0,"Objects":[{"StartTime":29606.0,"Position":224.0,"HyperDash":false},{"StartTime":29702.0,"Position":206.103424,"HyperDash":false},{"StartTime":29799.0,"Position":176.49205,"HyperDash":false},{"StartTime":29896.0,"Position":149.701324,"HyperDash":false},{"StartTime":29993.0,"Position":147.519775,"HyperDash":false},{"StartTime":30071.0,"Position":144.128265,"HyperDash":false},{"StartTime":30186.0,"Position":148.650436,"HyperDash":false}]},{"StartTime":30574.0,"Objects":[{"StartTime":30574.0,"Position":272.0,"HyperDash":false},{"StartTime":30670.0,"Position":303.302338,"HyperDash":false},{"StartTime":30767.0,"Position":322.868225,"HyperDash":false},{"StartTime":30846.0,"Position":351.6899,"HyperDash":false},{"StartTime":30961.0,"Position":374.0,"HyperDash":false}]},{"StartTime":31154.0,"Objects":[{"StartTime":31154.0,"Position":424.0,"HyperDash":false},{"StartTime":31250.0,"Position":439.371674,"HyperDash":false},{"StartTime":31347.0,"Position":424.705872,"HyperDash":false},{"StartTime":31444.0,"Position":417.305573,"HyperDash":false},{"StartTime":31541.0,"Position":395.3253,"HyperDash":false},{"StartTime":31619.0,"Position":372.2993,"HyperDash":false},{"StartTime":31734.0,"Position":347.633759,"HyperDash":false}]},{"StartTime":32122.0,"Objects":[{"StartTime":32122.0,"Position":224.0,"HyperDash":false},{"StartTime":32218.0,"Position":200.129822,"HyperDash":false},{"StartTime":32315.0,"Position":176.418152,"HyperDash":false},{"StartTime":32394.0,"Position":164.275757,"HyperDash":false},{"StartTime":32509.0,"Position":146.329926,"HyperDash":false}]},{"StartTime":32703.0,"Objects":[{"StartTime":32703.0,"Position":256.0,"HyperDash":false}]},{"StartTime":33284.0,"Objects":[{"StartTime":33284.0,"Position":496.0,"HyperDash":false}]},{"StartTime":33671.0,"Objects":[{"StartTime":33671.0,"Position":304.0,"HyperDash":false},{"StartTime":33767.0,"Position":297.697662,"HyperDash":false},{"StartTime":33864.0,"Position":253.0,"HyperDash":false},{"StartTime":33943.0,"Position":270.6899,"HyperDash":false},{"StartTime":34058.0,"Position":304.0,"HyperDash":false}]},{"StartTime":34251.0,"Objects":[{"StartTime":34251.0,"Position":352.0,"HyperDash":false},{"StartTime":34347.0,"Position":374.083679,"HyperDash":false},{"StartTime":34444.0,"Position":391.360016,"HyperDash":false},{"StartTime":34541.0,"Position":387.812561,"HyperDash":false},{"StartTime":34638.0,"Position":404.369629,"HyperDash":false},{"StartTime":34716.0,"Position":417.578369,"HyperDash":false},{"StartTime":34831.0,"Position":385.75708,"HyperDash":false}]},{"StartTime":35219.0,"Objects":[{"StartTime":35219.0,"Position":280.0,"HyperDash":false},{"StartTime":35315.0,"Position":277.964783,"HyperDash":false},{"StartTime":35412.0,"Position":251.783386,"HyperDash":false},{"StartTime":35491.0,"Position":238.233582,"HyperDash":false},{"StartTime":35606.0,"Position":223.420578,"HyperDash":false}]},{"StartTime":35800.0,"Objects":[{"StartTime":35800.0,"Position":272.0,"HyperDash":false},{"StartTime":35896.0,"Position":303.035217,"HyperDash":false},{"StartTime":35993.0,"Position":300.2166,"HyperDash":false},{"StartTime":36072.0,"Position":326.766418,"HyperDash":false},{"StartTime":36187.0,"Position":328.5794,"HyperDash":false}]},{"StartTime":36380.0,"Objects":[{"StartTime":36380.0,"Position":224.0,"HyperDash":false}]},{"StartTime":36767.0,"Objects":[{"StartTime":36767.0,"Position":176.0,"HyperDash":false},{"StartTime":36863.0,"Position":148.9648,"HyperDash":false},{"StartTime":36960.0,"Position":147.783386,"HyperDash":false},{"StartTime":37039.0,"Position":140.233582,"HyperDash":false},{"StartTime":37154.0,"Position":119.420578,"HyperDash":false}]},{"StartTime":37348.0,"Objects":[{"StartTime":37348.0,"Position":168.0,"HyperDash":false},{"StartTime":37444.0,"Position":170.0352,"HyperDash":false},{"StartTime":37541.0,"Position":196.216614,"HyperDash":false},{"StartTime":37620.0,"Position":195.766418,"HyperDash":false},{"StartTime":37735.0,"Position":224.579422,"HyperDash":false}]},{"StartTime":37928.0,"Objects":[{"StartTime":37928.0,"Position":120.0,"HyperDash":false}]},{"StartTime":38316.0,"Objects":[{"StartTime":38316.0,"Position":304.0,"HyperDash":false},{"StartTime":38412.0,"Position":277.697662,"HyperDash":false},{"StartTime":38509.0,"Position":253.131775,"HyperDash":false},{"StartTime":38588.0,"Position":226.310089,"HyperDash":false},{"StartTime":38703.0,"Position":202.0,"HyperDash":false}]},{"StartTime":38896.0,"Objects":[{"StartTime":38896.0,"Position":88.0,"HyperDash":false}]},{"StartTime":39477.0,"Objects":[{"StartTime":39477.0,"Position":280.0,"HyperDash":false},{"StartTime":39555.0,"Position":292.6114,"HyperDash":false},{"StartTime":39670.0,"Position":331.0,"HyperDash":false}]},{"StartTime":39864.0,"Objects":[{"StartTime":39864.0,"Position":424.0,"HyperDash":false},{"StartTime":39960.0,"Position":428.059753,"HyperDash":false},{"StartTime":40057.0,"Position":441.062134,"HyperDash":false},{"StartTime":40136.0,"Position":432.009247,"HyperDash":false},{"StartTime":40251.0,"Position":420.508881,"HyperDash":false}]},{"StartTime":40445.0,"Objects":[{"StartTime":40445.0,"Position":288.0,"HyperDash":false}]},{"StartTime":41025.0,"Objects":[{"StartTime":41025.0,"Position":32.0,"HyperDash":false}]},{"StartTime":41413.0,"Objects":[{"StartTime":41413.0,"Position":256.0,"HyperDash":false},{"StartTime":41509.0,"Position":291.8391,"HyperDash":false},{"StartTime":41606.0,"Position":302.265045,"HyperDash":false},{"StartTime":41685.0,"Position":310.9909,"HyperDash":false},{"StartTime":41800.0,"Position":352.8495,"HyperDash":false}]},{"StartTime":41993.0,"Objects":[{"StartTime":41993.0,"Position":447.0,"HyperDash":false}]},{"StartTime":42187.0,"Objects":[{"StartTime":42187.0,"Position":440.0,"HyperDash":false},{"StartTime":42283.0,"Position":403.2554,"HyperDash":false},{"StartTime":42380.0,"Position":389.7651,"HyperDash":false},{"StartTime":42459.0,"Position":365.6506,"HyperDash":false},{"StartTime":42574.0,"Position":342.9871,"HyperDash":false}]},{"StartTime":42961.0,"Objects":[{"StartTime":42961.0,"Position":248.0,"HyperDash":false},{"StartTime":43057.0,"Position":264.94986,"HyperDash":false},{"StartTime":43154.0,"Position":281.6838,"HyperDash":false},{"StartTime":43251.0,"Position":305.2995,"HyperDash":false},{"StartTime":43348.0,"Position":330.6694,"HyperDash":false},{"StartTime":43444.0,"Position":352.264221,"HyperDash":false},{"StartTime":43541.0,"Position":377.479736,"HyperDash":false},{"StartTime":43638.0,"Position":371.509277,"HyperDash":false},{"StartTime":43735.0,"Position":330.6694,"HyperDash":false},{"StartTime":43831.0,"Position":294.557373,"HyperDash":false},{"StartTime":43928.0,"Position":281.915039,"HyperDash":false},{"StartTime":44007.0,"Position":283.462677,"HyperDash":false},{"StartTime":44122.0,"Position":248.0,"HyperDash":false}]},{"StartTime":44509.0,"Objects":[{"StartTime":44509.0,"Position":144.0,"HyperDash":false},{"StartTime":44605.0,"Position":140.034851,"HyperDash":false},{"StartTime":44702.0,"Position":110.27771,"HyperDash":false},{"StartTime":44799.0,"Position":69.63604,"HyperDash":false},{"StartTime":44896.0,"Position":61.2429733,"HyperDash":false},{"StartTime":44974.0,"Position":47.09105,"HyperDash":false},{"StartTime":45089.0,"Position":14.520277,"HyperDash":false}]},{"StartTime":45284.0,"Objects":[{"StartTime":45284.0,"Position":56.0,"HyperDash":false}]},{"StartTime":45671.0,"Objects":[{"StartTime":45671.0,"Position":264.0,"HyperDash":false}]},{"StartTime":46058.0,"Objects":[{"StartTime":46058.0,"Position":264.0,"HyperDash":false},{"StartTime":46154.0,"Position":301.302338,"HyperDash":false},{"StartTime":46251.0,"Position":314.868225,"HyperDash":false},{"StartTime":46330.0,"Position":321.6899,"HyperDash":false},{"StartTime":46445.0,"Position":366.0,"HyperDash":false}]},{"StartTime":46638.0,"Objects":[{"StartTime":46638.0,"Position":416.0,"HyperDash":false},{"StartTime":46734.0,"Position":371.675873,"HyperDash":false},{"StartTime":46831.0,"Position":365.087921,"HyperDash":false},{"StartTime":46928.0,"Position":325.5,"HyperDash":false},{"StartTime":47025.0,"Position":313.912048,"HyperDash":false},{"StartTime":47103.0,"Position":306.3362,"HyperDash":false},{"StartTime":47218.0,"Position":263.0,"HyperDash":false}]},{"StartTime":47606.0,"Objects":[{"StartTime":47606.0,"Position":360.0,"HyperDash":false},{"StartTime":47702.0,"Position":324.675873,"HyperDash":false},{"StartTime":47799.0,"Position":309.087921,"HyperDash":false},{"StartTime":47896.0,"Position":293.5,"HyperDash":false},{"StartTime":47993.0,"Position":257.912048,"HyperDash":false},{"StartTime":48071.0,"Position":244.336212,"HyperDash":false},{"StartTime":48186.0,"Position":207.0,"HyperDash":false}]},{"StartTime":48380.0,"Objects":[{"StartTime":48380.0,"Position":160.0,"HyperDash":false},{"StartTime":48476.0,"Position":166.997681,"HyperDash":false},{"StartTime":48573.0,"Position":187.484192,"HyperDash":false},{"StartTime":48652.0,"Position":200.988281,"HyperDash":false},{"StartTime":48767.0,"Position":236.72261,"HyperDash":false}]},{"StartTime":49154.0,"Objects":[{"StartTime":49154.0,"Position":32.0,"HyperDash":false}]},{"StartTime":49542.0,"Objects":[{"StartTime":49542.0,"Position":248.0,"HyperDash":false},{"StartTime":49638.0,"Position":266.302338,"HyperDash":false},{"StartTime":49735.0,"Position":298.868225,"HyperDash":false},{"StartTime":49814.0,"Position":314.6899,"HyperDash":false},{"StartTime":49929.0,"Position":350.0,"HyperDash":false}]},{"StartTime":50316.0,"Objects":[{"StartTime":50316.0,"Position":256.0,"HyperDash":false},{"StartTime":50394.0,"Position":235.3886,"HyperDash":false},{"StartTime":50509.0,"Position":205.0,"HyperDash":false}]},{"StartTime":50703.0,"Objects":[{"StartTime":50703.0,"Position":256.0,"HyperDash":false},{"StartTime":50799.0,"Position":296.302338,"HyperDash":false},{"StartTime":50896.0,"Position":306.868225,"HyperDash":false},{"StartTime":50975.0,"Position":344.6899,"HyperDash":false},{"StartTime":51090.0,"Position":358.0,"HyperDash":false}]},{"StartTime":51284.0,"Objects":[{"StartTime":51284.0,"Position":440.0,"HyperDash":false}]},{"StartTime":51477.0,"Objects":[{"StartTime":51477.0,"Position":352.0,"HyperDash":false},{"StartTime":51573.0,"Position":329.697662,"HyperDash":false},{"StartTime":51670.0,"Position":301.131775,"HyperDash":false},{"StartTime":51749.0,"Position":288.3101,"HyperDash":false},{"StartTime":51864.0,"Position":250.0,"HyperDash":false}]},{"StartTime":52251.0,"Objects":[{"StartTime":52251.0,"Position":128.0,"HyperDash":false},{"StartTime":52347.0,"Position":102.275604,"HyperDash":false},{"StartTime":52444.0,"Position":77.8078156,"HyperDash":false},{"StartTime":52541.0,"Position":41.6188354,"HyperDash":false},{"StartTime":52638.0,"Position":32.3890381,"HyperDash":false},{"StartTime":52716.0,"Position":11.4332657,"HyperDash":false},{"StartTime":52831.0,"Position":4.4833107,"HyperDash":false}]},{"StartTime":53025.0,"Objects":[{"StartTime":53025.0,"Position":88.0,"HyperDash":false}]},{"StartTime":53413.0,"Objects":[{"StartTime":53413.0,"Position":168.0,"HyperDash":false},{"StartTime":53491.0,"Position":145.000992,"HyperDash":false},{"StartTime":53606.0,"Position":155.630676,"HyperDash":false}]},{"StartTime":53800.0,"Objects":[{"StartTime":53800.0,"Position":248.0,"HyperDash":false},{"StartTime":53896.0,"Position":263.12973,"HyperDash":false},{"StartTime":53993.0,"Position":290.128967,"HyperDash":false},{"StartTime":54090.0,"Position":316.20874,"HyperDash":false},{"StartTime":54187.0,"Position":340.6195,"HyperDash":false},{"StartTime":54265.0,"Position":376.1075,"HyperDash":false},{"StartTime":54380.0,"Position":385.246277,"HyperDash":false}]},{"StartTime":54574.0,"Objects":[{"StartTime":54574.0,"Position":472.0,"HyperDash":false}]},{"StartTime":54961.0,"Objects":[{"StartTime":54961.0,"Position":328.0,"HyperDash":false}]},{"StartTime":55348.0,"Objects":[{"StartTime":55348.0,"Position":224.0,"HyperDash":false},{"StartTime":55444.0,"Position":195.951981,"HyperDash":false},{"StartTime":55541.0,"Position":173.643036,"HyperDash":false},{"StartTime":55620.0,"Position":140.030609,"HyperDash":false},{"StartTime":55735.0,"Position":123.025146,"HyperDash":false}]},{"StartTime":55929.0,"Objects":[{"StartTime":55929.0,"Position":72.0,"HyperDash":false},{"StartTime":56025.0,"Position":95.8109741,"HyperDash":false},{"StartTime":56122.0,"Position":121.880394,"HyperDash":false},{"StartTime":56201.0,"Position":145.29776,"HyperDash":false},{"StartTime":56316.0,"Position":172.019226,"HyperDash":false}]},{"StartTime":56509.0,"Objects":[{"StartTime":56509.0,"Position":256.0,"HyperDash":false}]},{"StartTime":56896.0,"Objects":[{"StartTime":56896.0,"Position":328.0,"HyperDash":false},{"StartTime":56992.0,"Position":356.5922,"HyperDash":false},{"StartTime":57089.0,"Position":368.955872,"HyperDash":false},{"StartTime":57186.0,"Position":373.612579,"HyperDash":false},{"StartTime":57283.0,"Position":419.1303,"HyperDash":false},{"StartTime":57361.0,"Position":444.262817,"HyperDash":false},{"StartTime":57476.0,"Position":466.641,"HyperDash":false}]},{"StartTime":57671.0,"Objects":[{"StartTime":57671.0,"Position":416.0,"HyperDash":false},{"StartTime":57767.0,"Position":391.6712,"HyperDash":false},{"StartTime":57864.0,"Position":367.089,"HyperDash":false},{"StartTime":57943.0,"Position":328.06842,"HyperDash":false},{"StartTime":58058.0,"Position":317.924561,"HyperDash":false}]},{"StartTime":58445.0,"Objects":[{"StartTime":58445.0,"Position":144.0,"HyperDash":false}]},{"StartTime":58832.0,"Objects":[{"StartTime":58832.0,"Position":320.0,"HyperDash":false}]},{"StartTime":59219.0,"Objects":[{"StartTime":59219.0,"Position":128.0,"HyperDash":false}]},{"StartTime":59606.0,"Objects":[{"StartTime":59606.0,"Position":112.0,"HyperDash":false}]},{"StartTime":59993.0,"Objects":[{"StartTime":59993.0,"Position":224.0,"HyperDash":false},{"StartTime":60089.0,"Position":217.725632,"HyperDash":false},{"StartTime":60186.0,"Position":227.34639,"HyperDash":false},{"StartTime":60265.0,"Position":214.703522,"HyperDash":false},{"StartTime":60380.0,"Position":206.754791,"HyperDash":false}]},{"StartTime":60767.0,"Objects":[{"StartTime":60767.0,"Position":80.0,"HyperDash":false},{"StartTime":60863.0,"Position":90.79409,"HyperDash":false},{"StartTime":60960.0,"Position":75.87808,"HyperDash":false},{"StartTime":61039.0,"Position":74.4997559,"HyperDash":false},{"StartTime":61154.0,"Position":96.53038,"HyperDash":false}]},{"StartTime":61542.0,"Objects":[{"StartTime":61542.0,"Position":200.0,"HyperDash":false},{"StartTime":61638.0,"Position":196.089508,"HyperDash":false},{"StartTime":61735.0,"Position":236.445679,"HyperDash":false},{"StartTime":61814.0,"Position":270.5239,"HyperDash":false},{"StartTime":61929.0,"Position":286.4193,"HyperDash":false}]},{"StartTime":62316.0,"Objects":[{"StartTime":62316.0,"Position":376.0,"HyperDash":false},{"StartTime":62412.0,"Position":361.9105,"HyperDash":false},{"StartTime":62509.0,"Position":339.554321,"HyperDash":false},{"StartTime":62588.0,"Position":316.4761,"HyperDash":false},{"StartTime":62703.0,"Position":289.5807,"HyperDash":false}]},{"StartTime":63090.0,"Objects":[{"StartTime":63090.0,"Position":184.0,"HyperDash":false},{"StartTime":63186.0,"Position":174.5783,"HyperDash":false},{"StartTime":63283.0,"Position":191.193848,"HyperDash":false},{"StartTime":63362.0,"Position":204.138489,"HyperDash":false},{"StartTime":63477.0,"Position":198.424973,"HyperDash":false}]},{"StartTime":63864.0,"Objects":[{"StartTime":63864.0,"Position":88.0,"HyperDash":false},{"StartTime":63960.0,"Position":45.16764,"HyperDash":false},{"StartTime":64057.0,"Position":38.0766068,"HyperDash":false},{"StartTime":64154.0,"Position":12.98558,"HyperDash":false},{"StartTime":64251.0,"Position":38.0766144,"HyperDash":false},{"StartTime":64329.0,"Position":69.2529,"HyperDash":false},{"StartTime":64444.0,"Position":88.0,"HyperDash":false}]},{"StartTime":64638.0,"Objects":[{"StartTime":64638.0,"Position":312.0,"HyperDash":false}]},{"StartTime":64832.0,"Objects":[{"StartTime":64832.0,"Position":208.0,"HyperDash":false}]},{"StartTime":65025.0,"Objects":[{"StartTime":65025.0,"Position":304.0,"HyperDash":false}]},{"StartTime":65413.0,"Objects":[{"StartTime":65413.0,"Position":360.0,"HyperDash":false},{"StartTime":65491.0,"Position":361.6114,"HyperDash":false},{"StartTime":65606.0,"Position":411.0,"HyperDash":false}]},{"StartTime":65800.0,"Objects":[{"StartTime":65800.0,"Position":462.0,"HyperDash":false},{"StartTime":65878.0,"Position":458.3886,"HyperDash":false},{"StartTime":65993.0,"Position":411.0,"HyperDash":false}]},{"StartTime":66187.0,"Objects":[{"StartTime":66187.0,"Position":344.0,"HyperDash":false},{"StartTime":66283.0,"Position":327.697662,"HyperDash":false},{"StartTime":66380.0,"Position":293.131775,"HyperDash":false},{"StartTime":66459.0,"Position":271.3101,"HyperDash":false},{"StartTime":66574.0,"Position":242.0,"HyperDash":false}]},{"StartTime":66961.0,"Objects":[{"StartTime":66961.0,"Position":152.0,"HyperDash":false},{"StartTime":67057.0,"Position":167.659241,"HyperDash":false},{"StartTime":67154.0,"Position":147.9835,"HyperDash":false},{"StartTime":67233.0,"Position":135.201309,"HyperDash":false},{"StartTime":67348.0,"Position":106.616547,"HyperDash":false}]},{"StartTime":67735.0,"Objects":[{"StartTime":67735.0,"Position":32.0,"HyperDash":false},{"StartTime":67831.0,"Position":42.2565079,"HyperDash":false},{"StartTime":67928.0,"Position":78.75527,"HyperDash":false},{"StartTime":68007.0,"Position":85.89344,"HyperDash":false},{"StartTime":68122.0,"Position":125.752792,"HyperDash":false}]},{"StartTime":68316.0,"Objects":[{"StartTime":68316.0,"Position":208.0,"HyperDash":false}]},{"StartTime":68509.0,"Objects":[{"StartTime":68509.0,"Position":224.0,"HyperDash":false},{"StartTime":68605.0,"Position":240.243561,"HyperDash":false},{"StartTime":68702.0,"Position":270.729248,"HyperDash":false},{"StartTime":68781.0,"Position":291.85675,"HyperDash":false},{"StartTime":68896.0,"Position":317.7006,"HyperDash":false}]},{"StartTime":69284.0,"Objects":[{"StartTime":69284.0,"Position":216.0,"HyperDash":false},{"StartTime":69380.0,"Position":191.846649,"HyperDash":false},{"StartTime":69477.0,"Position":185.704849,"HyperDash":false},{"StartTime":69556.0,"Position":168.950912,"HyperDash":false},{"StartTime":69671.0,"Position":193.879364,"HyperDash":false}]},{"StartTime":70058.0,"Objects":[{"StartTime":70058.0,"Position":360.0,"HyperDash":false},{"StartTime":70154.0,"Position":374.986725,"HyperDash":false},{"StartTime":70251.0,"Position":367.9918,"HyperDash":false},{"StartTime":70330.0,"Position":353.6845,"HyperDash":false},{"StartTime":70445.0,"Position":337.885529,"HyperDash":false}]},{"StartTime":70832.0,"Objects":[{"StartTime":70832.0,"Position":264.0,"HyperDash":false},{"StartTime":70928.0,"Position":225.951981,"HyperDash":false},{"StartTime":71025.0,"Position":213.643036,"HyperDash":false},{"StartTime":71104.0,"Position":187.030609,"HyperDash":false},{"StartTime":71219.0,"Position":163.025146,"HyperDash":false}]},{"StartTime":71413.0,"Objects":[{"StartTime":71413.0,"Position":112.0,"HyperDash":false},{"StartTime":71509.0,"Position":75.97218,"HyperDash":false},{"StartTime":71606.0,"Position":61.6836624,"HyperDash":false},{"StartTime":71685.0,"Position":27.0878525,"HyperDash":false},{"StartTime":71800.0,"Position":11.1066208,"HyperDash":false}]},{"StartTime":71993.0,"Objects":[{"StartTime":71993.0,"Position":40.0,"HyperDash":false},{"StartTime":72071.0,"Position":52.4331474,"HyperDash":false},{"StartTime":72186.0,"Position":68.28971,"HyperDash":false}]},{"StartTime":72380.0,"Objects":[{"StartTime":72380.0,"Position":176.0,"HyperDash":false},{"StartTime":72476.0,"Position":187.970581,"HyperDash":false},{"StartTime":72573.0,"Position":161.344147,"HyperDash":false},{"StartTime":72670.0,"Position":136.575012,"HyperDash":false},{"StartTime":72767.0,"Position":119.0057,"HyperDash":false},{"StartTime":72845.0,"Position":79.55367,"HyperDash":false},{"StartTime":72960.0,"Position":69.6823959,"HyperDash":false}]},{"StartTime":73154.0,"Objects":[{"StartTime":73154.0,"Position":120.0,"HyperDash":false},{"StartTime":73250.0,"Position":160.2306,"HyperDash":false},{"StartTime":73347.0,"Position":169.814178,"HyperDash":false},{"StartTime":73426.0,"Position":170.964127,"HyperDash":false},{"StartTime":73541.0,"Position":209.453659,"HyperDash":false}]},{"StartTime":73929.0,"Objects":[{"StartTime":73929.0,"Position":312.0,"HyperDash":false},{"StartTime":74025.0,"Position":321.048035,"HyperDash":false},{"StartTime":74122.0,"Position":362.356964,"HyperDash":false},{"StartTime":74201.0,"Position":394.9694,"HyperDash":false},{"StartTime":74316.0,"Position":412.974854,"HyperDash":false}]},{"StartTime":74703.0,"Objects":[{"StartTime":74703.0,"Position":336.0,"HyperDash":false},{"StartTime":74781.0,"Position":342.880768,"HyperDash":false},{"StartTime":74896.0,"Position":315.910126,"HyperDash":false}]},{"StartTime":75090.0,"Objects":[{"StartTime":75090.0,"Position":400.0,"HyperDash":false},{"StartTime":75168.0,"Position":399.237152,"HyperDash":false},{"StartTime":75283.0,"Position":417.9073,"HyperDash":false}]},{"StartTime":75477.0,"Objects":[{"StartTime":75477.0,"Position":328.0,"HyperDash":false},{"StartTime":75573.0,"Position":296.892883,"HyperDash":false},{"StartTime":75670.0,"Position":288.19165,"HyperDash":false},{"StartTime":75767.0,"Position":275.9188,"HyperDash":false},{"StartTime":75864.0,"Position":238.263733,"HyperDash":false},{"StartTime":75942.0,"Position":221.022842,"HyperDash":false},{"StartTime":76057.0,"Position":202.919159,"HyperDash":false}]},{"StartTime":76251.0,"Objects":[{"StartTime":76251.0,"Position":296.0,"HyperDash":false},{"StartTime":76347.0,"Position":313.798157,"HyperDash":false},{"StartTime":76444.0,"Position":346.261322,"HyperDash":false},{"StartTime":76523.0,"Position":375.263733,"HyperDash":false},{"StartTime":76638.0,"Position":392.493958,"HyperDash":false}]},{"StartTime":77219.0,"Objects":[{"StartTime":77219.0,"Position":152.0,"HyperDash":false},{"StartTime":77315.0,"Position":119.697678,"HyperDash":false},{"StartTime":77412.0,"Position":101.0,"HyperDash":false},{"StartTime":77491.0,"Position":137.689911,"HyperDash":false},{"StartTime":77606.0,"Position":152.0,"HyperDash":false}]},{"StartTime":77800.0,"Objects":[{"StartTime":77800.0,"Position":320.0,"HyperDash":false}]},{"StartTime":78187.0,"Objects":[{"StartTime":78187.0,"Position":320.0,"HyperDash":false},{"StartTime":78265.0,"Position":323.92218,"HyperDash":false},{"StartTime":78380.0,"Position":369.294647,"HyperDash":false}]},{"StartTime":78574.0,"Objects":[{"StartTime":78574.0,"Position":456.0,"HyperDash":false},{"StartTime":78670.0,"Position":443.684448,"HyperDash":false},{"StartTime":78767.0,"Position":433.251038,"HyperDash":false},{"StartTime":78846.0,"Position":411.9393,"HyperDash":false},{"StartTime":78961.0,"Position":410.384216,"HyperDash":false}]},{"StartTime":79348.0,"Objects":[{"StartTime":79348.0,"Position":288.0,"HyperDash":false},{"StartTime":79444.0,"Position":287.315552,"HyperDash":false},{"StartTime":79541.0,"Position":310.748962,"HyperDash":false},{"StartTime":79620.0,"Position":322.0607,"HyperDash":false},{"StartTime":79735.0,"Position":333.615784,"HyperDash":false}]},{"StartTime":80122.0,"Objects":[{"StartTime":80122.0,"Position":240.0,"HyperDash":false},{"StartTime":80218.0,"Position":206.699463,"HyperDash":false},{"StartTime":80315.0,"Position":192.5893,"HyperDash":false},{"StartTime":80394.0,"Position":180.9993,"HyperDash":false},{"StartTime":80509.0,"Position":144.773514,"HyperDash":false}]},{"StartTime":80703.0,"Objects":[{"StartTime":80703.0,"Position":64.0,"HyperDash":false}]},{"StartTime":80896.0,"Objects":[{"StartTime":80896.0,"Position":40.0,"HyperDash":false},{"StartTime":80992.0,"Position":52.30054,"HyperDash":false},{"StartTime":81089.0,"Position":87.4107056,"HyperDash":false},{"StartTime":81168.0,"Position":99.0006943,"HyperDash":false},{"StartTime":81283.0,"Position":135.226486,"HyperDash":false}]},{"StartTime":81671.0,"Objects":[{"StartTime":81671.0,"Position":248.0,"HyperDash":false},{"StartTime":81767.0,"Position":248.315552,"HyperDash":false},{"StartTime":81864.0,"Position":270.748962,"HyperDash":false},{"StartTime":81943.0,"Position":270.0607,"HyperDash":false},{"StartTime":82058.0,"Position":293.615784,"HyperDash":false}]},{"StartTime":82445.0,"Objects":[{"StartTime":82445.0,"Position":120.0,"HyperDash":false}]},{"StartTime":82832.0,"Objects":[{"StartTime":82832.0,"Position":312.0,"HyperDash":false}]},{"StartTime":83219.0,"Objects":[{"StartTime":83219.0,"Position":400.0,"HyperDash":false},{"StartTime":83297.0,"Position":406.999,"HyperDash":false},{"StartTime":83412.0,"Position":412.369324,"HyperDash":false}]},{"StartTime":83606.0,"Objects":[{"StartTime":83606.0,"Position":360.0,"HyperDash":false},{"StartTime":83684.0,"Position":344.762848,"HyperDash":false},{"StartTime":83799.0,"Position":342.0927,"HyperDash":false}]},{"StartTime":83993.0,"Objects":[{"StartTime":83993.0,"Position":272.0,"HyperDash":false},{"StartTime":84089.0,"Position":267.17865,"HyperDash":false},{"StartTime":84186.0,"Position":223.813,"HyperDash":false},{"StartTime":84265.0,"Position":218.85318,"HyperDash":false},{"StartTime":84380.0,"Position":179.797424,"HyperDash":false}]},{"StartTime":84767.0,"Objects":[{"StartTime":84767.0,"Position":80.0,"HyperDash":false},{"StartTime":84845.0,"Position":91.5179,"HyperDash":false},{"StartTime":84960.0,"Position":96.12762,"HyperDash":false}]},{"StartTime":85154.0,"Objects":[{"StartTime":85154.0,"Position":16.0,"HyperDash":false},{"StartTime":85232.0,"Position":0.0,"HyperDash":false},{"StartTime":85347.0,"Position":16.0,"HyperDash":false}]},{"StartTime":85542.0,"Objects":[{"StartTime":85542.0,"Position":104.0,"HyperDash":false},{"StartTime":85638.0,"Position":112.302322,"HyperDash":false},{"StartTime":85735.0,"Position":154.868225,"HyperDash":false},{"StartTime":85814.0,"Position":182.689911,"HyperDash":false},{"StartTime":85929.0,"Position":206.0,"HyperDash":false}]},{"StartTime":86316.0,"Objects":[{"StartTime":86316.0,"Position":376.0,"HyperDash":false},{"StartTime":86412.0,"Position":382.0039,"HyperDash":false},{"StartTime":86509.0,"Position":424.2578,"HyperDash":false},{"StartTime":86588.0,"Position":445.011047,"HyperDash":false},{"StartTime":86703.0,"Position":472.7657,"HyperDash":false}]},{"StartTime":87090.0,"Objects":[{"StartTime":87090.0,"Position":296.0,"HyperDash":false},{"StartTime":87186.0,"Position":311.353729,"HyperDash":false},{"StartTime":87283.0,"Position":308.602417,"HyperDash":false},{"StartTime":87362.0,"Position":307.240021,"HyperDash":false},{"StartTime":87477.0,"Position":312.385956,"HyperDash":false}]},{"StartTime":87864.0,"Objects":[{"StartTime":87864.0,"Position":24.0,"HyperDash":false}]},{"StartTime":88251.0,"Objects":[{"StartTime":88251.0,"Position":249.0,"HyperDash":false},{"StartTime":88347.0,"Position":233.0,"HyperDash":false},{"StartTime":88444.0,"Position":449.0,"HyperDash":false},{"StartTime":88541.0,"Position":411.0,"HyperDash":false},{"StartTime":88638.0,"Position":75.0,"HyperDash":false},{"StartTime":88735.0,"Position":474.0,"HyperDash":false},{"StartTime":88831.0,"Position":176.0,"HyperDash":false},{"StartTime":88928.0,"Position":1.0,"HyperDash":false},{"StartTime":89025.0,"Position":37.0,"HyperDash":false},{"StartTime":89122.0,"Position":481.0,"HyperDash":false},{"StartTime":89219.0,"Position":375.0,"HyperDash":false},{"StartTime":89315.0,"Position":407.0,"HyperDash":false},{"StartTime":89412.0,"Position":231.0,"HyperDash":false},{"StartTime":89509.0,"Position":338.0,"HyperDash":false},{"StartTime":89606.0,"Position":322.0,"HyperDash":false},{"StartTime":89703.0,"Position":347.0,"HyperDash":false},{"StartTime":89800.0,"Position":365.0,"HyperDash":false}]}]} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/1041052.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/1041052.osu new file mode 100644 index 0000000000..a0cecc1b18 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/1041052.osu @@ -0,0 +1,210 @@ +osu file format v14 + +[General] +AudioFilename: audio.mp3 +AudioLeadIn: 0 +PreviewTime: 65316 +Countdown: 0 +SampleSet: Soft +StackLeniency: 0.7 +Mode: 2 +LetterboxInBreaks: 0 +WidescreenStoryboard: 0 + +[Editor] +DistanceSpacing: 1.4 +BeatDivisor: 4 +GridSize: 8 +TimelineZoom: 1.4 + +[Metadata] +Title:Nanairo Symphony -TV Size- +TitleUnicode:七色シンフォニー -TV Size- +Artist:Coalamode. +ArtistUnicode:コアラモード. +Creator:Ascendance +Version:Aru's Cup +Source:四月は君の嘘 +Tags:shigatsu wa kimi no uso your lie in april opening arusamour tenshichan [superstar] +BeatmapID:1041052 +BeatmapSetID:488149 + +[Difficulty] +HPDrainRate:3 +CircleSize:2.5 +OverallDifficulty:6 +ApproachRate:6 +SliderMultiplier:1.02 +SliderTickRate:2 + +[Events] +//Background and Video events +Video,500,"forty.avi" +0,0,"cropped-1366-768-647733.jpg",0,0 +//Break Periods +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Sound Samples + +[TimingPoints] +1155,387.096774193548,4,2,1,50,1,0 +15284,-100,4,2,1,60,0,0 +16638,-100,4,2,1,50,0,0 +41413,-100,4,2,1,60,0,0 +59993,-100,4,2,1,65,0,0 +66187,-100,4,2,1,70,0,1 +87284,-100,4,2,1,60,0,1 +87864,-100,4,2,1,70,0,0 +87961,-100,4,2,1,50,0,0 +88638,-100,4,2,1,30,0,0 +89413,-100,4,2,1,10,0,0 +89800,-100,4,2,1,5,0,0 + + +[Colours] +Combo1 : 255,128,64 +Combo2 : 0,128,255 +Combo3 : 255,128,192 +Combo4 : 0,128,192 + +[HitObjects] +208,160,1155,6,0,L|45:160,1,153,2|2,0:0|0:0,0:0:0:0: +160,160,2122,1,0,0:0:0:0: +272,160,2509,1,2,0:0:0:0: +448,288,3284,6,0,P|480:240|480:192,1,102,2|0,0:0|0:0,0:0:0:0: +384,96,4058,1,2,0:0:0:0: +128,64,5025,6,0,L|32:64,2,76.5,2|0|0,0:0|0:0|0:0,0:0:0:0: +192,64,5800,1,2,0:0:0:0: +240,64,5993,1,2,0:0:0:0: +288,64,6187,1,2,0:0:0:0: +416,80,6574,6,0,L|192:80,1,204,0|2,0:0|0:0,0:0:0:0: +488,160,8122,2,0,L|376:160,1,102 +457,288,8896,2,0,L|297:288,1,153,2|2,0:0|0:0,0:0:0:0: +400,288,10058,1,0,0:0:0:0: +304,288,10445,6,0,L|192:288,2,102,2|0|2,0:0|0:0|0:0,0:0:0:0: +400,288,11606,1,0,0:0:0:0: +240,288,11993,2,0,L|80:288,1,153,2|0,0:0|0:0,0:0:0:0: +0,288,13154,1,0,0:0:0:0: +112,240,13542,6,0,P|160:288|256:288,1,153,6|2,0:0|0:0,0:0:0:0: +288,288,14316,2,0,L|368:288,2,76.5,2|0|0,0:0|0:0|0:0,0:0:0:0: +192,288,15284,2,0,L|160:224,1,51,0|12,0:0|0:0,0:0:0:0: +312,208,15864,1,6,0:0:0:0: +128,176,16638,6,0,P|64:160|0:96,2,153,6|2|0,0:0|0:0|0:0,0:0:0:0: +224,176,18187,2,0,P|288:192|352:272,2,153,2|2|0,0:0|0:0|0:0,0:0:0:0: +128,176,19735,6,0,L|288:176,1,153,2|2,0:0|0:0,0:0:0:0: +432,176,20896,1,0,0:0:0:0: +328,176,21284,2,0,L|488:176,1,153,2|2,0:0|0:0,0:0:0:0: +328,176,22445,1,0,0:0:0:0: +224,176,22832,6,0,L|64:176,1,153,2|2,0:0|0:0,0:0:0:0: +224,176,23993,1,0,0:0:0:0: +112,176,24380,2,0,L|272:176,1,153,2|2,0:0|0:0,0:0:0:0: +416,176,25541,1,0,0:0:0:0: +304,256,25929,6,0,P|272:208|312:120,1,153,2|2,0:0|0:0,0:0:0:0: +480,112,27090,1,0,0:0:0:0: +384,112,27477,6,0,L|320:112,2,51,2|2|0,0:0|0:0|0:0,0:0:0:0: +432,112,28058,1,2,0:0:0:0: +333,112,28445,2,0,L|282:112,2,51,0|0|0,0:0|0:0|0:0,0:0:0:0: +384,112,29025,6,0,L|272:112,1,102,6|0,0:0|0:0,0:0:0:0: +224,112,29606,2,0,P|160:144|160:240,1,153,2|2,0:0|0:0,0:0:0:0: +272,272,30574,2,0,L|374:272,1,102 +424,272,31154,2,0,P|414:344|348:378,1,153,0|0,0:0|0:0,0:0:0:0: +224,304,32122,6,0,P|176:320|144:368,1,102,2|0,0:0|0:0,0:0:0:0: +200,368,32703,1,2,0:0:0:0: +376,368,33284,1,0,0:0:0:0: +304,296,33671,2,0,L|240:296,2,51,2|2|0,0:0|0:0|0:0,0:0:0:0: +352,296,34251,2,0,P|400:248|384:168,1,153,2|0,0:0|0:0,0:0:0:0: +280,176,35219,6,0,L|216:80,1,102,2|0,0:0|0:0,0:0:0:0: +272,104,35800,2,0,L|336:8,1,102,2|0,0:0|0:0,0:0:0:0: +280,16,36380,1,2,0:0:0:0: +176,32,36767,6,0,L|112:128,1,102,2|0,0:0|0:0,0:0:0:0: +168,128,37348,2,0,L|232:224,1,102,2|0,0:0|0:0,0:0:0:0: +176,224,37928,1,2,0:0:0:0: +304,264,38316,6,0,L|200:264,1,102,2|0,0:0|0:0,0:0:0:0: +144,264,38896,1,2,0:0:0:0: +280,336,39477,2,0,L|336:336,1,51 +424,336,39864,2,0,P|440:304|416:240,1,102,8|0,0:3|0:3,0:3:0:0: +352,232,40445,1,4,0:1:0:0: +160,224,41025,1,8,0:3:0:0: +256,48,41413,6,0,P|302:28|353:31,1,102,6|0,0:0|0:0,0:0:0:0: +400,40,41993,1,0,0:0:0:0: +440,80,42187,2,0,P|389:76|342:96,1,102,2|8,0:0|0:0,0:0:0:0: +248,128,42961,2,0,P|312:176|392:144,2,153,2|2|8,0:0|0:0|0:3,0:0:0:0: +144,136,44509,6,0,P|80:88|0:120,1,153,2|0,0:0|0:0,0:0:0:0: +56,136,45284,1,2,0:0:0:0: +160,144,45671,1,8,0:0:0:0: +264,144,46058,2,0,L|384:144,1,102,2|0,0:0|0:0,0:0:0:0: +416,152,46638,2,0,L|264:152,1,153,2|8,0:0|0:3,0:0:0:0: +360,120,47606,6,0,L|192:120,1,153,2|0,0:0|0:0,0:0:0:0: +160,128,48380,2,0,P|208:80|256:96,1,102,2|8,0:0|0:0,0:0:0:0: +144,136,49154,1,2,0:0:0:0: +248,144,49542,2,0,L|368:144,1,102,0|2,0:0|0:0,0:0:0:0: +256,192,50316,2,0,L|200:192,1,51,10|0,0:0|0:0,0:0:0:0: +256,184,50703,6,0,L|360:184,1,102,2|0,0:0|0:0,0:0:0:0: +400,208,51284,1,0,0:0:0:0: +352,240,51477,2,0,L|240:240,1,102 +128,336,52251,6,0,P|64:336|0:256,1,153,2|2,0:0|0:0,0:0:0:0: +88,264,53025,1,2,0:0:0:0: +168,208,53413,2,0,L|152:144,1,51,8|8,0:0|0:3,0:0:0:0: +248,120,53800,6,0,P|328:152|392:120,1,153,6|0,0:0|0:0,0:0:0:0: +432,120,54574,1,2,0:0:0:0: +328,128,54961,1,8,0:0:0:0: +224,128,55348,6,0,L|112:144,1,102,2|0,0:0|0:0,0:0:0:0: +72,152,55929,2,0,L|192:176,1,102,2|0,0:0|0:0,0:0:0:0: +224,184,56509,1,8,0:3:0:0: +328,176,56896,6,0,P|376:208|472:192,1,153,2|0,0:0|0:0,0:0:0:0: +416,208,57671,2,0,L|304:240,1,102,2|8,0:0|0:0,0:0:0:0: +224,272,58445,5,2,0:0:0:0: +320,296,58832,1,0,0:0:0:0: +224,328,59219,1,2,0:0:0:0: +120,328,59606,1,8,0:3:0:0: +224,264,59993,6,0,P|224:200|192:152,1,102,6|0,0:0|0:0,0:0:0:0: +80,184,60767,2,0,P|76:133|97:87,1,102,2|8,0:0|0:0,0:0:0:0: +200,80,61542,2,0,P|232:112|296:112,1,102,2|0,0:0|0:0,0:0:0:0: +376,160,62316,2,0,P|344:192|280:192,1,102,2|8,0:0|0:0,0:0:0:0: +184,240,63090,6,0,L|200:128,1,102,2|8,0:0|0:0,0:0:0:0: +88,136,63864,2,0,L|8:152,2,76.5,6|2|2,0:0|0:0|0:0,0:0:0:0: +160,112,64638,1,8,0:0:0:0: +208,128,64832,1,8,0:0:0:0: +256,144,65025,1,8,0:0:0:0: +360,152,65413,6,0,L|424:152,1,51,8|0,0:0|0:0,0:0:0:0: +462,152,65800,2,0,L|398:152,1,51,8|8,0:0|0:3,0:0:0:0: +344,144,66187,6,0,L|232:144,1,102,12|8,0:0|0:0,0:0:0:0: +152,120,66961,2,0,P|148:169|107:196,1,102,8|8,0:0|0:0,0:0:0:0: +32,264,67735,6,0,L|144:216,1,102,8|8,0:0|0:0,0:0:0:0: +176,208,68316,1,0,0:0:0:0: +224,200,68509,2,0,L|317:240,1,102,8|8,0:0|0:0,0:0:0:0: +216,256,69284,6,0,P|184:304|200:352,1,102,8|8,0:0|0:0,0:0:0:0: +360,256,70058,2,0,P|368:207|337:167,1,102,8|8,0:0|0:0,0:0:0:0: +264,80,70832,6,0,L|152:96,1,102,8|8,0:0|0:0,0:0:0:0: +112,104,71413,2,0,L|11:89,1,102,8|0,0:0|0:0,0:0:0:0: +40,128,71993,2,0,L|72:176,1,51,8|8,0:0|0:3,0:0:0:0: +176,216,72380,6,0,P|144:280|64:280,1,153,12|0,0:0|0:0,0:0:0:0: +120,280,73154,2,0,P|191:299|216:328,1,102,8|8,0:0|0:0,0:0:0:0: +312,320,73929,6,0,L|424:304,1,102,8|8,0:0|0:0,0:0:0:0: +336,272,74703,2,0,L|312:216,1,51,8|0,0:0|0:0,0:0:0:0: +400,200,75090,2,0,L|424:136,1,51,8|0,0:0|0:0,0:0:0:0: +328,152,75477,6,0,P|280:184|200:136,1,153,12|0,0:0|0:0,0:0:0:0: +296,136,76251,2,0,P|360:136|408:168,1,102,8|8,0:0|0:0,0:0:0:0: +152,248,77219,6,0,L|96:248,2,51,0|12|0,0:0|0:0|0:0,0:0:0:0: +208,248,77800,1,8,0:0:0:0: +320,256,78187,2,0,L|369:243,1,51,8|8,0:0|0:3,0:0:0:0: +456,232,78574,6,0,L|408:136,1,102,12|8,0:0|0:0,0:0:0:0: +288,136,79348,2,0,L|336:40,1,102,8|8,0:0|0:0,0:0:0:0: +240,80,80122,6,0,P|144:80|128:64,1,102,8|8,0:0|0:0,0:0:0:0: +96,72,80703,1,0,0:0:0:0: +40,104,80896,2,0,P|136:104|152:88,1,102,8|8,0:0|0:0,0:0:0:0: +248,128,81671,6,0,L|296:224,1,102,12|8,0:0|0:0,0:0:0:0: +208,272,82445,1,10,0:0:0:0: +312,272,82832,1,8,0:0:0:0: +400,224,83219,6,0,L|416:160,1,51,8|2,0:0|0:0,0:0:0:0: +360,56,83606,2,0,L|336:120,1,51,8|0,0:0|0:0,0:0:0:0: +272,152,83993,2,0,P|192:152|176:136,1,102,0|8,0:0|0:0,0:0:0:0: +80,160,84767,6,0,L|96:208,1,51,8|0,0:0|0:0,0:0:0:0: +16,272,85154,2,0,L|16:328,1,51,8|0,0:0|0:0,0:0:0:0: +104,304,85542,2,0,L|208:304,1,102,2|8,0:0|0:0,0:0:0:0: +376,336,86316,6,0,L|472:304,1,102,4|0,0:0|0:0,0:0:0:0: +296,248,87090,2,0,P|312:168|312:136,1,102,2|8,0:0|0:3,0:0:0:0: +168,96,87864,1,4,0:0:0:0: +256,192,88251,12,0,89800,0:0:0:0: From 285adcb00e65abbcaad8d6a7cffedcc416949ab5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 2 Mar 2024 00:19:45 +0300 Subject: [PATCH 3329/4092] Fix catch hit object position getting randomised when last object has pos=0 --- osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index 200018f28b..198f8f59c6 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -118,7 +118,11 @@ namespace osu.Game.Rulesets.Catch.Beatmaps float offsetPosition = hitObject.OriginalX; double startTime = hitObject.StartTime; - if (lastPosition == null) + if (lastPosition == null || + // some objects can get assigned position zero, making stable incorrectly go inside this if branch on the next object. to maintain behaviour and compatibility, do the same here. + // reference: https://github.com/peppy/osu-stable-reference/blob/3ea48705eb67172c430371dcfc8a16a002ed0d3d/osu!/GameplayElements/HitObjects/Fruits/HitFactoryFruits.cs#L45-L50 + // todo: should be revisited and corrected later probably. + lastPosition == 0) { lastPosition = offsetPosition; lastStartTime = startTime; From 85f131d2f87f7da733c542a38ae8cd6804a43f89 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 2 Mar 2024 22:45:15 +0300 Subject: [PATCH 3330/4092] Fix "unranked explaination" tooltip text using incorrect translation key --- osu.Game/Localisation/ModSelectOverlayStrings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Localisation/ModSelectOverlayStrings.cs b/osu.Game/Localisation/ModSelectOverlayStrings.cs index 9513eacf02..7a9bb698d8 100644 --- a/osu.Game/Localisation/ModSelectOverlayStrings.cs +++ b/osu.Game/Localisation/ModSelectOverlayStrings.cs @@ -67,7 +67,7 @@ namespace osu.Game.Localisation /// /// "Performance points will not be granted due to active mods." /// - public static LocalisableString UnrankedExplanation => new TranslatableString(getKey(@"ranked_explanation"), @"Performance points will not be granted due to active mods."); + public static LocalisableString UnrankedExplanation => new TranslatableString(getKey(@"unranked_explanation"), @"Performance points will not be granted due to active mods."); private static string getKey(string key) => $@"{prefix}:{key}"; } From c05007804fb715b5d542535bcb224d5bd67b8b58 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 3 Mar 2024 20:46:58 +0300 Subject: [PATCH 3331/4092] Use more direct way to apply transforms --- .../Formats/LegacyStoryboardDecoder.cs | 2 +- osu.Game/Storyboards/CommandTimeline.cs | 10 ++- osu.Game/Storyboards/CommandTimelineGroup.cs | 23 +++--- osu.Game/Storyboards/StoryboardSprite.cs | 77 +++++++++++++------ 4 files changed, 75 insertions(+), 37 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index cf4700bf85..a9a4d9cc49 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -212,7 +212,7 @@ namespace osu.Game.Beatmaps.Formats { float startValue = Parsing.ParseFloat(split[4]); float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue; - timelineGroup?.Scale.Add(easing, startTime, endTime, startValue, endValue); + timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startValue), new Vector2(endValue)); break; } diff --git a/osu.Game/Storyboards/CommandTimeline.cs b/osu.Game/Storyboards/CommandTimeline.cs index 0650c97165..2bb606d2bb 100644 --- a/osu.Game/Storyboards/CommandTimeline.cs +++ b/osu.Game/Storyboards/CommandTimeline.cs @@ -24,6 +24,13 @@ namespace osu.Game.Storyboards public T StartValue { get; private set; } public T EndValue { get; private set; } + public string PropertyName { get; } + + public CommandTimeline(string propertyName) + { + PropertyName = propertyName; + } + public void Add(Easing easing, double startTime, double endTime, T startValue, T endValue) { if (endTime < startTime) @@ -31,7 +38,7 @@ namespace osu.Game.Storyboards endTime = startTime; } - commands.Add(new TypedCommand { Easing = easing, StartTime = startTime, EndTime = endTime, StartValue = startValue, EndValue = endValue }); + commands.Add(new TypedCommand { Easing = easing, StartTime = startTime, EndTime = endTime, StartValue = startValue, EndValue = endValue, PropertyName = PropertyName }); if (startTime < StartTime) { @@ -55,6 +62,7 @@ namespace osu.Game.Storyboards public double StartTime { get; set; } public double EndTime { get; set; } public double Duration => EndTime - StartTime; + public string PropertyName { get; set; } public T StartValue; public T EndValue; diff --git a/osu.Game/Storyboards/CommandTimelineGroup.cs b/osu.Game/Storyboards/CommandTimelineGroup.cs index 0b96db6861..cb795e0ffe 100644 --- a/osu.Game/Storyboards/CommandTimelineGroup.cs +++ b/osu.Game/Storyboards/CommandTimelineGroup.cs @@ -3,11 +3,11 @@ using System; using osuTK; -using osuTK.Graphics; using osu.Framework.Graphics; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; +using osu.Framework.Graphics.Colour; namespace osu.Game.Storyboards { @@ -15,16 +15,16 @@ namespace osu.Game.Storyboards public class CommandTimelineGroup { - public CommandTimeline X = new CommandTimeline(); - public CommandTimeline Y = new CommandTimeline(); - public CommandTimeline Scale = new CommandTimeline(); - public CommandTimeline VectorScale = new CommandTimeline(); - public CommandTimeline Rotation = new CommandTimeline(); - public CommandTimeline Colour = new CommandTimeline(); - public CommandTimeline Alpha = new CommandTimeline(); - public CommandTimeline BlendingParameters = new CommandTimeline(); - public CommandTimeline FlipH = new CommandTimeline(); - public CommandTimeline FlipV = new CommandTimeline(); + public CommandTimeline X = new CommandTimeline("X"); + public CommandTimeline Y = new CommandTimeline("Y"); + public CommandTimeline Scale = new CommandTimeline("Scale"); + public CommandTimeline VectorScale = new CommandTimeline("VectorScale"); + public CommandTimeline Rotation = new CommandTimeline("Rotation"); + public CommandTimeline Colour = new CommandTimeline("Colour"); + public CommandTimeline Alpha = new CommandTimeline("Alpha"); + public CommandTimeline BlendingParameters = new CommandTimeline("Blending"); + public CommandTimeline FlipH = new CommandTimeline("FlipH"); + public CommandTimeline FlipV = new CommandTimeline("FlipV"); private readonly ICommandTimeline[] timelines; @@ -109,6 +109,7 @@ namespace osu.Game.Storyboards EndTime = offset + command.EndTime, StartValue = command.StartValue, EndValue = command.EndValue, + PropertyName = command.PropertyName }); } diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index 982185d51b..4d3f1c158f 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -98,8 +98,6 @@ namespace osu.Game.Storyboards private delegate void DrawablePropertyInitializer(Drawable drawable, T value); - private delegate void DrawableTransformer(Drawable drawable, T value, double duration, Easing easing); - public StoryboardSprite(string path, Anchor origin, Vector2 initialPosition) { Path = path; @@ -132,27 +130,23 @@ namespace osu.Game.Storyboards List generated = new List(); - generateCommands(generated, getCommands(g => g.X, triggeredGroups), (d, value) => d.X = value, (d, value, duration, easing) => d.MoveToX(value, duration, easing)); - generateCommands(generated, getCommands(g => g.Y, triggeredGroups), (d, value) => d.Y = value, (d, value, duration, easing) => d.MoveToY(value, duration, easing)); - generateCommands(generated, getCommands(g => g.Scale, triggeredGroups), (d, value) => d.Scale = new Vector2(value), (d, value, duration, easing) => d.ScaleTo(value, duration, easing)); - generateCommands(generated, getCommands(g => g.Rotation, triggeredGroups), (d, value) => d.Rotation = value, (d, value, duration, easing) => d.RotateTo(value, duration, easing)); - generateCommands(generated, getCommands(g => g.Colour, triggeredGroups), (d, value) => d.Colour = value, (d, value, duration, easing) => d.FadeColour(value, duration, easing)); - generateCommands(generated, getCommands(g => g.Alpha, triggeredGroups), (d, value) => d.Alpha = value, (d, value, duration, easing) => d.FadeTo(value, duration, easing)); - generateCommands(generated, getCommands(g => g.BlendingParameters, triggeredGroups), (d, value) => d.Blending = value, (d, value, duration, _) => d.TransformBlendingMode(value, duration), - false); + generateCommands(generated, getCommands(g => g.X, triggeredGroups), (d, value) => d.X = value); + generateCommands(generated, getCommands(g => g.Y, triggeredGroups), (d, value) => d.Y = value); + generateCommands(generated, getCommands(g => g.Scale, triggeredGroups), (d, value) => d.Scale = value); + generateCommands(generated, getCommands(g => g.Rotation, triggeredGroups), (d, value) => d.Rotation = value); + generateCommands(generated, getCommands(g => g.Colour, triggeredGroups), (d, value) => d.Colour = value); + generateCommands(generated, getCommands(g => g.Alpha, triggeredGroups), (d, value) => d.Alpha = value); + generateCommands(generated, getCommands(g => g.BlendingParameters, triggeredGroups), (d, value) => d.Blending = value, false); if (drawable is IVectorScalable vectorScalable) { - generateCommands(generated, getCommands(g => g.VectorScale, triggeredGroups), (_, value) => vectorScalable.VectorScale = value, - (_, value, duration, easing) => vectorScalable.VectorScaleTo(value, duration, easing)); + generateCommands(generated, getCommands(g => g.VectorScale, triggeredGroups), (_, value) => vectorScalable.VectorScale = value); } if (drawable is IFlippable flippable) { - generateCommands(generated, getCommands(g => g.FlipH, triggeredGroups), (_, value) => flippable.FlipH = value, (_, value, duration, _) => flippable.TransformFlipH(value, duration), - false); - generateCommands(generated, getCommands(g => g.FlipV, triggeredGroups), (_, value) => flippable.FlipV = value, (_, value, duration, _) => flippable.TransformFlipV(value, duration), - false); + generateCommands(generated, getCommands(g => g.FlipH, triggeredGroups), (_, value) => flippable.FlipH = value, false); + generateCommands(generated, getCommands(g => g.FlipV, triggeredGroups), (_, value) => flippable.FlipV = value, false); } foreach (var command in generated.OrderBy(g => g.StartTime)) @@ -160,7 +154,7 @@ namespace osu.Game.Storyboards } private void generateCommands(List resultList, IEnumerable.TypedCommand> commands, - DrawablePropertyInitializer initializeProperty, DrawableTransformer transform, bool alwaysInitialize = true) + DrawablePropertyInitializer initializeProperty, bool alwaysInitialize = true) { bool initialized = false; @@ -175,7 +169,7 @@ namespace osu.Game.Storyboards initialized = true; } - resultList.Add(new GeneratedCommand(command, initFunc, transform)); + resultList.Add(new GeneratedCommand(command, initFunc)); } } @@ -209,24 +203,59 @@ namespace osu.Game.Storyboards public double StartTime => command.StartTime; private readonly DrawablePropertyInitializer? initializeProperty; - private readonly DrawableTransformer transform; private readonly CommandTimeline.TypedCommand command; - public GeneratedCommand(CommandTimeline.TypedCommand command, DrawablePropertyInitializer? initializeProperty, DrawableTransformer transform) + public GeneratedCommand(CommandTimeline.TypedCommand command, DrawablePropertyInitializer? initializeProperty) { this.command = command; this.initializeProperty = initializeProperty; - this.transform = transform; } public void ApplyTo(Drawable drawable) { initializeProperty?.Invoke(drawable, command.StartValue); - using (drawable.BeginAbsoluteSequence(command.StartTime)) + switch (command.PropertyName) { - transform(drawable, command.StartValue, 0, Easing.None); - transform(drawable, command.EndValue, command.Duration, command.Easing); + case "VectorScale": + using (drawable.BeginAbsoluteSequence(command.StartTime)) + { + ((IVectorScalable)drawable).TransformTo(command.PropertyName, command.StartValue).Then().TransformTo(command.PropertyName, command.EndValue, command.Duration, command.Easing); + } + + break; + + case "FlipH": + using (drawable.BeginAbsoluteSequence(command.StartTime)) + { + ((IFlippable)drawable).TransformTo(command.PropertyName, command.StartValue).Delay(command.Duration).TransformTo(command.PropertyName, command.EndValue); + } + + break; + + case "FlipV": + using (drawable.BeginAbsoluteSequence(command.StartTime)) + { + ((IFlippable)drawable).TransformTo(command.PropertyName, command.StartValue).Delay(command.Duration).TransformTo(command.PropertyName, command.EndValue); + } + + break; + + case "Blending": + using (drawable.BeginAbsoluteSequence(command.StartTime)) + { + drawable.TransformTo(command.PropertyName, command.StartValue).Delay(command.Duration).TransformTo(command.PropertyName, command.EndValue); + } + + break; + + default: + using (drawable.BeginAbsoluteSequence(command.StartTime)) + { + drawable.TransformTo(command.PropertyName, command.StartValue).Then().TransformTo(command.PropertyName, command.EndValue, command.Duration, command.Easing); + } + + break; } } } From 7193ec66a4dee4abe516bd8984d3ed50559df35d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 3 Mar 2024 21:30:46 +0300 Subject: [PATCH 3332/4092] Make use of framework's transform loops --- osu.Game/Storyboards/CommandLoop.cs | 18 ++++-- osu.Game/Storyboards/CommandTimeline.cs | 2 + osu.Game/Storyboards/StoryboardSprite.cs | 75 +++++++++++++++++++----- 3 files changed, 76 insertions(+), 19 deletions(-) diff --git a/osu.Game/Storyboards/CommandLoop.cs b/osu.Game/Storyboards/CommandLoop.cs index 480d69c12f..a912daea44 100644 --- a/osu.Game/Storyboards/CommandLoop.cs +++ b/osu.Game/Storyboards/CommandLoop.cs @@ -38,11 +38,21 @@ namespace osu.Game.Storyboards public override IEnumerable.TypedCommand> GetCommands(CommandTimelineSelector timelineSelector, double offset = 0) { - for (int loop = 0; loop < TotalIterations; loop++) + double fullLoopDuration = CommandsEndTime - CommandsStartTime; + + foreach (var command in timelineSelector(this).Commands) { - double loopOffset = LoopStartTime + loop * CommandsDuration; - foreach (var command in base.GetCommands(timelineSelector, offset + loopOffset)) - yield return command; + yield return new CommandTimeline.TypedCommand + { + Easing = command.Easing, + StartTime = offset + LoopStartTime + command.StartTime, + EndTime = offset + LoopStartTime + command.EndTime, + StartValue = command.StartValue, + EndValue = command.EndValue, + PropertyName = command.PropertyName, + LoopCount = TotalIterations, + Delay = fullLoopDuration - command.EndTime + command.StartTime + }; } } diff --git a/osu.Game/Storyboards/CommandTimeline.cs b/osu.Game/Storyboards/CommandTimeline.cs index 2bb606d2bb..ce25bfe25b 100644 --- a/osu.Game/Storyboards/CommandTimeline.cs +++ b/osu.Game/Storyboards/CommandTimeline.cs @@ -63,6 +63,8 @@ namespace osu.Game.Storyboards public double EndTime { get; set; } public double Duration => EndTime - StartTime; public string PropertyName { get; set; } + public int LoopCount { get; set; } + public double Delay { get; set; } public T StartValue; public T EndValue; diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index 4d3f1c158f..2c04e4c983 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -218,41 +218,86 @@ namespace osu.Game.Storyboards switch (command.PropertyName) { case "VectorScale": - using (drawable.BeginAbsoluteSequence(command.StartTime)) + if (command.LoopCount == 0) { - ((IVectorScalable)drawable).TransformTo(command.PropertyName, command.StartValue).Then().TransformTo(command.PropertyName, command.EndValue, command.Duration, command.Easing); + using (drawable.BeginAbsoluteSequence(command.StartTime)) + { + ((IVectorScalable)drawable).TransformTo(command.PropertyName, command.StartValue).Then() + .TransformTo(command.PropertyName, command.EndValue, command.Duration, command.Easing); + } + } + else + { + using (drawable.BeginAbsoluteSequence(command.StartTime)) + { + ((IVectorScalable)drawable).TransformTo(command.PropertyName, command.StartValue).Then() + .TransformTo(command.PropertyName, command.EndValue, command.Duration, command.Easing) + .Loop(command.Delay, command.LoopCount); + } } break; case "FlipH": - using (drawable.BeginAbsoluteSequence(command.StartTime)) - { - ((IFlippable)drawable).TransformTo(command.PropertyName, command.StartValue).Delay(command.Duration).TransformTo(command.PropertyName, command.EndValue); - } - - break; - case "FlipV": - using (drawable.BeginAbsoluteSequence(command.StartTime)) + if (command.LoopCount == 0) { - ((IFlippable)drawable).TransformTo(command.PropertyName, command.StartValue).Delay(command.Duration).TransformTo(command.PropertyName, command.EndValue); + using (drawable.BeginAbsoluteSequence(command.StartTime)) + { + ((IFlippable)drawable).TransformTo(command.PropertyName, command.StartValue).Delay(command.Duration) + .TransformTo(command.PropertyName, command.EndValue); + } + } + else + { + using (drawable.BeginAbsoluteSequence(command.StartTime)) + { + ((IFlippable)drawable).TransformTo(command.PropertyName, command.StartValue).Delay(command.Duration) + .TransformTo(command.PropertyName, command.EndValue) + .Loop(command.Delay, command.LoopCount); + } } break; case "Blending": - using (drawable.BeginAbsoluteSequence(command.StartTime)) + if (command.LoopCount == 0) { - drawable.TransformTo(command.PropertyName, command.StartValue).Delay(command.Duration).TransformTo(command.PropertyName, command.EndValue); + using (drawable.BeginAbsoluteSequence(command.StartTime)) + { + drawable.TransformTo(command.PropertyName, command.StartValue).Delay(command.Duration) + .TransformTo(command.PropertyName, command.EndValue); + } + } + else + { + using (drawable.BeginAbsoluteSequence(command.StartTime)) + { + drawable.TransformTo(command.PropertyName, command.StartValue).Delay(command.Duration) + .TransformTo(command.PropertyName, command.EndValue) + .Loop(command.Delay, command.LoopCount); + } } break; default: - using (drawable.BeginAbsoluteSequence(command.StartTime)) + if (command.LoopCount == 0) { - drawable.TransformTo(command.PropertyName, command.StartValue).Then().TransformTo(command.PropertyName, command.EndValue, command.Duration, command.Easing); + using (drawable.BeginAbsoluteSequence(command.StartTime)) + { + drawable.TransformTo(command.PropertyName, command.StartValue).Then() + .TransformTo(command.PropertyName, command.EndValue, command.Duration, command.Easing); + } + } + else + { + using (drawable.BeginAbsoluteSequence(command.StartTime)) + { + drawable.TransformTo(command.PropertyName, command.StartValue).Then() + .TransformTo(command.PropertyName, command.EndValue, command.Duration, command.Easing) + .Loop(command.Delay, command.LoopCount); + } } break; From f5d24e6804e64ffa720adc57116323cd682107e3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 3 Mar 2024 21:54:37 +0300 Subject: [PATCH 3333/4092] Remove unused transform extensions --- .../Drawables/DrawablesExtensions.cs | 30 ------------- osu.Game/Storyboards/Drawables/IFlippable.cs | 42 ------------------- .../Storyboards/Drawables/IVectorScalable.cs | 8 ---- 3 files changed, 80 deletions(-) delete mode 100644 osu.Game/Storyboards/Drawables/DrawablesExtensions.cs diff --git a/osu.Game/Storyboards/Drawables/DrawablesExtensions.cs b/osu.Game/Storyboards/Drawables/DrawablesExtensions.cs deleted file mode 100644 index bbc55a336d..0000000000 --- a/osu.Game/Storyboards/Drawables/DrawablesExtensions.cs +++ /dev/null @@ -1,30 +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.Framework.Graphics; -using osu.Framework.Graphics.Transforms; - -namespace osu.Game.Storyboards.Drawables -{ - public static class DrawablesExtensions - { - /// - /// Adjusts after a delay. - /// - /// A to which further transforms can be added. - public static TransformSequence TransformBlendingMode(this T drawable, BlendingParameters newValue, double delay = 0) - where T : Drawable - => drawable.TransformTo(drawable.PopulateTransform(new TransformBlendingParameters(), newValue, delay)); - } - - public class TransformBlendingParameters : Transform - { - private BlendingParameters valueAt(double time) - => time < EndTime ? StartValue : EndValue; - - public override string TargetMember => nameof(Drawable.Blending); - - protected override void Apply(Drawable d, double time) => d.Blending = valueAt(time); - protected override void ReadIntoStartValue(Drawable d) => StartValue = d.Blending; - } -} diff --git a/osu.Game/Storyboards/Drawables/IFlippable.cs b/osu.Game/Storyboards/Drawables/IFlippable.cs index 165b3d97cc..2a931875ea 100644 --- a/osu.Game/Storyboards/Drawables/IFlippable.cs +++ b/osu.Game/Storyboards/Drawables/IFlippable.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 osu.Framework.Graphics; using osu.Framework.Graphics.Transforms; namespace osu.Game.Storyboards.Drawables @@ -11,45 +10,4 @@ namespace osu.Game.Storyboards.Drawables bool FlipH { get; set; } bool FlipV { get; set; } } - - internal class TransformFlipH : Transform - { - private bool valueAt(double time) - => time < EndTime ? StartValue : EndValue; - - public override string TargetMember => nameof(IFlippable.FlipH); - - protected override void Apply(IFlippable d, double time) => d.FlipH = valueAt(time); - protected override void ReadIntoStartValue(IFlippable d) => StartValue = d.FlipH; - } - - internal class TransformFlipV : Transform - { - private bool valueAt(double time) - => time < EndTime ? StartValue : EndValue; - - public override string TargetMember => nameof(IFlippable.FlipV); - - protected override void Apply(IFlippable d, double time) => d.FlipV = valueAt(time); - protected override void ReadIntoStartValue(IFlippable d) => StartValue = d.FlipV; - } - - internal static class FlippableExtensions - { - /// - /// Adjusts after a delay. - /// - /// A to which further transforms can be added. - public static TransformSequence TransformFlipH(this T flippable, bool newValue, double delay = 0) - where T : class, IFlippable - => flippable.TransformTo(flippable.PopulateTransform(new TransformFlipH(), newValue, delay)); - - /// - /// Adjusts after a delay. - /// - /// A to which further transforms can be added. - public static TransformSequence TransformFlipV(this T flippable, bool newValue, double delay = 0) - where T : class, IFlippable - => flippable.TransformTo(flippable.PopulateTransform(new TransformFlipV(), newValue, delay)); - } } diff --git a/osu.Game/Storyboards/Drawables/IVectorScalable.cs b/osu.Game/Storyboards/Drawables/IVectorScalable.cs index 60a297e126..ab0452df80 100644 --- a/osu.Game/Storyboards/Drawables/IVectorScalable.cs +++ b/osu.Game/Storyboards/Drawables/IVectorScalable.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 osu.Framework.Graphics; using osu.Framework.Graphics.Transforms; using osuTK; @@ -11,11 +10,4 @@ namespace osu.Game.Storyboards.Drawables { Vector2 VectorScale { get; set; } } - - internal static class VectorScalableExtensions - { - public static TransformSequence VectorScaleTo(this T target, Vector2 newVectorScale, double duration = 0, Easing easing = Easing.None) - where T : class, IVectorScalable - => target.TransformTo(nameof(IVectorScalable.VectorScale), newVectorScale, duration, easing); - } } From 4a7635e488169bf09dbcdfc7bf4da489a82d2b1c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 3 Mar 2024 23:04:06 +0300 Subject: [PATCH 3334/4092] Fix broken tests --- osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs index dadf3ca65f..dae3119ea4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs @@ -73,9 +73,9 @@ namespace osu.Game.Tests.Visual.Gameplay var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero); // these should be ignored as we have an alpha visibility blocker proceeding this command. - sprite.TimelineGroup.Scale.Add(Easing.None, loop_start_time, -18000, 0, 1); + sprite.TimelineGroup.Scale.Add(Easing.None, loop_start_time, -18000, Vector2.Zero, Vector2.One); var loopGroup = sprite.AddLoop(loop_start_time, 50); - loopGroup.Scale.Add(Easing.None, loop_start_time, -18000, 0, 1); + loopGroup.Scale.Add(Easing.None, loop_start_time, -18000, Vector2.Zero, Vector2.One); var target = addEventToLoop ? loopGroup : sprite.TimelineGroup; double loopRelativeOffset = addEventToLoop ? -loop_start_time : 0; From bce3bd55e5a863e52f41598c306a248a79638843 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 4 Mar 2024 16:08:17 +0900 Subject: [PATCH 3335/4092] Fix catch by moving cursor-specific handling local --- .../TestSceneResumeOverlay.cs | 16 +++++++++++++--- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 7 +++++++ osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 +- osu.Game/Screens/Play/ResumeOverlay.cs | 3 ++- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs index 25d0b0a3d3..b35984a2fc 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs @@ -6,11 +6,11 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Osu.UI.Cursor; +using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; using osu.Game.Tests.Gameplay; using osu.Game.Tests.Visual; @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Tests public partial class TestSceneResumeOverlay : OsuManualInputManagerTestScene { private ManualOsuInputManager osuInputManager = null!; - private CursorContainer cursor = null!; + private GameplayCursorContainer cursor = null!; private ResumeOverlay resume = null!; private bool resumeFired; @@ -99,7 +99,17 @@ namespace osu.Game.Rulesets.Osu.Tests private void loadContent() { - Child = osuInputManager = new ManualOsuInputManager(new OsuRuleset().RulesetInfo) { Children = new Drawable[] { cursor = new CursorContainer(), resume = new OsuResumeOverlay { GameplayCursor = cursor }, } }; + Child = osuInputManager = new ManualOsuInputManager(new OsuRuleset().RulesetInfo) + { + Children = new Drawable[] + { + cursor = new GameplayCursorContainer(), + resume = new OsuResumeOverlay + { + GameplayCursor = cursor + }, + } + }; resumeFired = false; resume.ResumeAction = () => resumeFired = true; diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index 8a84fe14e5..adc7bd97ff 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -39,6 +39,13 @@ namespace osu.Game.Rulesets.Osu.UI protected override void PopIn() { + // Can't display if the cursor is outside the window. + if (GameplayCursor.LastFrameState == Visibility.Hidden || !Contains(GameplayCursor.ActiveCursor.ScreenSpaceDrawQuad.Centre)) + { + Resume(); + return; + } + base.PopIn(); GameplayCursor.ActiveCursor.Hide(); diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 4aeb3d4862..218fdf5b86 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -227,7 +227,7 @@ namespace osu.Game.Rulesets.UI public override void RequestResume(Action continueResume) { - if (ResumeOverlay != null && UseResumeOverlay && (Cursor == null || (Cursor.LastFrameState == Visibility.Visible && Contains(Cursor.ActiveCursor.ScreenSpaceDrawQuad.Centre)))) + if (ResumeOverlay != null && UseResumeOverlay) { ResumeOverlay.GameplayCursor = Cursor; ResumeOverlay.ResumeAction = continueResume; diff --git a/osu.Game/Screens/Play/ResumeOverlay.cs b/osu.Game/Screens/Play/ResumeOverlay.cs index fae406bd6b..a33dd79888 100644 --- a/osu.Game/Screens/Play/ResumeOverlay.cs +++ b/osu.Game/Screens/Play/ResumeOverlay.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.UI; using osuTK; using osuTK.Graphics; @@ -21,7 +22,7 @@ namespace osu.Game.Screens.Play /// public abstract partial class ResumeOverlay : VisibilityContainer { - public CursorContainer GameplayCursor { get; set; } + public GameplayCursorContainer GameplayCursor { get; set; } /// /// The action to be performed to complete resuming. From 6635d9be04952b43b41ff8e3f2596999a069ff74 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 4 Mar 2024 16:08:25 +0900 Subject: [PATCH 3336/4092] Add countdown display --- .../UI/DrawableCatchRuleset.cs | 3 +- .../Gameplay/TestSceneDelayedResumeOverlay.cs | 44 ++++++ osu.Game/Screens/Play/DelayedResumeOverlay.cs | 135 +++++++++++++++++- 3 files changed, 178 insertions(+), 4 deletions(-) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneDelayedResumeOverlay.cs diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index 580c90bcb4..32ebdc1159 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -17,6 +17,7 @@ using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Scoring; using osu.Game.Screens.Play; +using osuTK; namespace osu.Game.Rulesets.Catch.UI { @@ -54,6 +55,6 @@ namespace osu.Game.Rulesets.Catch.UI public override DrawableHitObject? CreateDrawableRepresentation(CatchHitObject h) => null; - protected override ResumeOverlay CreateResumeOverlay() => new DelayedResumeOverlay(); + protected override ResumeOverlay CreateResumeOverlay() => new DelayedResumeOverlay { Scale = new Vector2(0.65f) }; } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDelayedResumeOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDelayedResumeOverlay.cs new file mode 100644 index 0000000000..241a78b6b8 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDelayedResumeOverlay.cs @@ -0,0 +1,44 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Play; +using osu.Game.Tests.Gameplay; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public partial class TestSceneDelayedResumeOverlay : OsuTestScene + { + private ResumeOverlay resume = null!; + private bool resumeFired; + + [Cached] + private GameplayState gameplayState; + + public TestSceneDelayedResumeOverlay() + { + gameplayState = TestGameplayState.Create(new OsuRuleset()); + } + + [SetUp] + public void SetUp() => Schedule(loadContent); + + [Test] + public void TestResume() + { + AddStep("show", () => resume.Show()); + AddUntilStep("dismissed", () => resumeFired && resume.State.Value == Visibility.Hidden); + } + + private void loadContent() + { + Child = resume = new DelayedResumeOverlay(); + + resumeFired = false; + resume.ResumeAction = () => resumeFired = true; + } + } +} diff --git a/osu.Game/Screens/Play/DelayedResumeOverlay.cs b/osu.Game/Screens/Play/DelayedResumeOverlay.cs index ef39c8eb76..6f70a914f0 100644 --- a/osu.Game/Screens/Play/DelayedResumeOverlay.cs +++ b/osu.Game/Screens/Play/DelayedResumeOverlay.cs @@ -1,8 +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; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Framework.Threading; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Play { @@ -11,21 +21,140 @@ namespace osu.Game.Screens.Play /// public partial class DelayedResumeOverlay : ResumeOverlay { + private const double countdown_time = 800; + protected override LocalisableString Message => string.Empty; + [Resolved] + private OsuColour colours { get; set; } = null!; + private ScheduledDelegate? scheduledResume; + private int countdownCount = 3; + private double countdownStartTime; + + private Drawable content = null!; + private Drawable background = null!; + private SpriteText countdown = null!; + + public DelayedResumeOverlay() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + + [BackgroundDependencyLoader] + private void load() + { + Add(content = new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Masking = true, + BorderColour = colours.Yellow, + BorderThickness = 1, + Children = new[] + { + background = new Box + { + Size = new Vector2(250, 40), + Colour = Color4.Black, + Alpha = 0.8f + }, + new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(5), + Colour = colours.Yellow, + Children = new Drawable[] + { + // new Box + // { + // Anchor = Anchor.Centre, + // Origin = Anchor.Centre, + // Size = new Vector2(40, 3) + // }, + countdown = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + UseFullGlyphHeight = false, + AlwaysPresent = true, + Font = OsuFont.Numeric.With(size: 20, fixedWidth: true) + }, + // new Box + // { + // Anchor = Anchor.Centre, + // Origin = Anchor.Centre, + // Size = new Vector2(40, 3) + // } + } + } + } + } + } + }); + } protected override void PopIn() { - base.PopIn(); + this.FadeIn(); + + content.FadeInFromZero(150, Easing.OutQuint); + content.ScaleTo(new Vector2(1.5f, 1)).Then().ScaleTo(1, 150, Easing.OutElasticQuarter); + + countdownCount = 3; + countdownStartTime = Time.Current; scheduledResume?.Cancel(); - scheduledResume = Scheduler.AddDelayed(Resume, 800); + scheduledResume = Scheduler.AddDelayed(Resume, countdown_time); + } + + protected override void Update() + { + base.Update(); + updateCountdown(); + } + + private void updateCountdown() + { + double amountTimePassed = Math.Min(countdown_time, Time.Current - countdownStartTime) / countdown_time; + int newCount = 3 - (int)Math.Floor(amountTimePassed * 3); + + if (newCount > 0) + { + countdown.Alpha = 1; + countdown.Text = newCount.ToString(); + } + else + countdown.Alpha = 0; + + if (newCount != countdownCount) + { + if (newCount == 0) + content.ScaleTo(new Vector2(1.5f, 1), 150, Easing.OutQuint); + else + content.ScaleTo(new Vector2(1.05f, 1), 50, Easing.OutQuint).Then().ScaleTo(1, 50, Easing.Out); + } + + countdownCount = newCount; } protected override void PopOut() { - base.PopOut(); + this.Delay(150).FadeOut(); + + content.FadeOut(150, Easing.OutQuint); + scheduledResume?.Cancel(); } } From af2b80e0304e9541378fa9fa68caad0ca347a37c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 4 Mar 2024 11:28:34 +0100 Subject: [PATCH 3337/4092] Add failing encode test --- .../Formats/LegacyScoreDecoderTest.cs | 2 +- .../Formats/LegacyScoreEncoderTest.cs | 55 +++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Beatmaps/Formats/LegacyScoreEncoderTest.cs diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index 7e3967dc95..43e471320e 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -432,7 +432,7 @@ namespace osu.Game.Tests.Beatmaps.Formats CultureInfo.CurrentCulture = originalCulture; } - private class TestLegacyScoreDecoder : LegacyScoreDecoder + public class TestLegacyScoreDecoder : LegacyScoreDecoder { private readonly int beatmapVersion; diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreEncoderTest.cs new file mode 100644 index 0000000000..c0a7285f39 --- /dev/null +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreEncoderTest.cs @@ -0,0 +1,55 @@ +// 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.IO; +using NUnit.Framework; +using osu.Game.Beatmaps.Formats; +using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Scoring.Legacy; +using osu.Game.Tests.Resources; + +namespace osu.Game.Tests.Beatmaps.Formats +{ + public class LegacyScoreEncoderTest + { + [TestCase(1, 3)] + [TestCase(1, 0)] + [TestCase(0, 3)] + public void CatchMergesFruitAndDropletMisses(int missCount, int largeTickMissCount) + { + var ruleset = new CatchRuleset().RulesetInfo; + + var scoreInfo = TestResources.CreateTestScoreInfo(ruleset); + var beatmap = new TestBeatmap(ruleset); + scoreInfo.Statistics = new Dictionary + { + [HitResult.Great] = 50, + [HitResult.LargeTickHit] = 5, + [HitResult.Miss] = missCount, + [HitResult.LargeTickMiss] = largeTickMissCount + }; + var score = new Score { ScoreInfo = scoreInfo }; + + var decodedAfterEncode = encodeThenDecode(LegacyBeatmapDecoder.LATEST_VERSION, score, beatmap); + + Assert.That(decodedAfterEncode.ScoreInfo.GetCountMiss(), Is.EqualTo(missCount + largeTickMissCount)); + } + + private static Score encodeThenDecode(int beatmapVersion, Score score, TestBeatmap beatmap) + { + var encodeStream = new MemoryStream(); + + var encoder = new LegacyScoreEncoder(score, beatmap); + encoder.Encode(encodeStream); + + var decodeStream = new MemoryStream(encodeStream.GetBuffer()); + + var decoder = new LegacyScoreDecoderTest.TestLegacyScoreDecoder(beatmapVersion); + var decodedAfterEncode = decoder.Parse(decodeStream); + return decodedAfterEncode; + } + } +} From dcd6b028090a97ea1b2c43a374bb9210417b0adc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 4 Mar 2024 11:35:31 +0100 Subject: [PATCH 3338/4092] Fix incorrect implementation of `GetCountMiss()` for catch --- .../Scoring/Legacy/ScoreInfoExtensions.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs b/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs index 07c35a334f..23624401e2 100644 --- a/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs +++ b/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs @@ -198,10 +198,25 @@ namespace osu.Game.Scoring.Legacy } } - public static int? GetCountMiss(this ScoreInfo scoreInfo) => - getCount(scoreInfo, HitResult.Miss); + public static int? GetCountMiss(this ScoreInfo scoreInfo) + { + switch (scoreInfo.Ruleset.OnlineID) + { + case 0: + case 1: + case 3: + return getCount(scoreInfo, HitResult.Miss); + + case 2: + return (getCount(scoreInfo, HitResult.Miss) ?? 0) + (getCount(scoreInfo, HitResult.LargeTickMiss) ?? 0); + } + + return null; + } public static void SetCountMiss(this ScoreInfo scoreInfo, int value) => + // this does not match the implementation of `GetCountMiss()` for catch, + // but we physically cannot recover that data anymore at this point. scoreInfo.Statistics[HitResult.Miss] = value; private static int? getCount(ScoreInfo scoreInfo, HitResult result) From b896d97a4f844498e0c1c914a0b70c4fdf70449f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 4 Mar 2024 11:45:44 +0100 Subject: [PATCH 3339/4092] Fix catch pp calculator not matching live with respect to miss handling --- .../Difficulty/CatchPerformanceCalculator.cs | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index b30b85be2d..d07f25ba90 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -2,22 +2,21 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Linq; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using osu.Game.Scoring.Legacy; namespace osu.Game.Rulesets.Catch.Difficulty { public class CatchPerformanceCalculator : PerformanceCalculator { - private int fruitsHit; - private int ticksHit; - private int tinyTicksHit; - private int tinyTicksMissed; - private int misses; + private int num300; + private int num100; + private int num50; + private int numKatu; + private int numMiss; public CatchPerformanceCalculator() : base(new CatchRuleset()) @@ -28,11 +27,11 @@ namespace osu.Game.Rulesets.Catch.Difficulty { var catchAttributes = (CatchDifficultyAttributes)attributes; - fruitsHit = score.Statistics.GetValueOrDefault(HitResult.Great); - ticksHit = score.Statistics.GetValueOrDefault(HitResult.LargeTickHit); - tinyTicksHit = score.Statistics.GetValueOrDefault(HitResult.SmallTickHit); - tinyTicksMissed = score.Statistics.GetValueOrDefault(HitResult.SmallTickMiss); - misses = score.Statistics.GetValueOrDefault(HitResult.Miss); + num300 = score.GetCount300() ?? 0; // HitResult.Great + num100 = score.GetCount100() ?? 0; // HitResult.LargeTickHit + num50 = score.GetCount50() ?? 0; // HitResult.SmallTickHit + numKatu = score.GetCountKatu() ?? 0; // HitResult.SmallTickMiss + numMiss = score.GetCountMiss() ?? 0; // HitResult.Miss PLUS HitResult.LargeTickMiss // We are heavily relying on aim in catch the beat double value = Math.Pow(5.0 * Math.Max(1.0, catchAttributes.StarRating / 0.0049) - 4.0, 2.0) / 100000.0; @@ -45,7 +44,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty (numTotalHits > 2500 ? Math.Log10(numTotalHits / 2500.0) * 0.475 : 0.0); value *= lengthBonus; - value *= Math.Pow(0.97, misses); + value *= Math.Pow(0.97, numMiss); // Combo scaling if (catchAttributes.MaxCombo > 0) @@ -86,8 +85,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty } private double accuracy() => totalHits() == 0 ? 0 : Math.Clamp((double)totalSuccessfulHits() / totalHits(), 0, 1); - private int totalHits() => tinyTicksHit + ticksHit + fruitsHit + misses + tinyTicksMissed; - private int totalSuccessfulHits() => tinyTicksHit + ticksHit + fruitsHit; - private int totalComboHits() => misses + ticksHit + fruitsHit; + private int totalHits() => num50 + num100 + num300 + numMiss + numKatu; + private int totalSuccessfulHits() => num50 + num100 + num300; + private int totalComboHits() => numMiss + num100 + num300; } } From 405958f73c560b5c3eae381255d5141e37c819c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 4 Mar 2024 14:43:53 +0100 Subject: [PATCH 3340/4092] Add test scene for drawable ranks --- .../Visual/Ranking/TestSceneDrawableRank.cs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 osu.Game.Tests/Visual/Ranking/TestSceneDrawableRank.cs diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneDrawableRank.cs b/osu.Game.Tests/Visual/Ranking/TestSceneDrawableRank.cs new file mode 100644 index 0000000000..804c8dfc44 --- /dev/null +++ b/osu.Game.Tests/Visual/Ranking/TestSceneDrawableRank.cs @@ -0,0 +1,39 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Online.Leaderboards; +using osu.Game.Scoring; +using osuTK; + +namespace osu.Game.Tests.Visual.Ranking +{ + public partial class TestSceneDrawableRank : OsuTestScene + { + [Test] + public void TestAllRanks() + { + AddStep("create content", () => Child = new FillFlowContainer + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Direction = FillDirection.Vertical, + Padding = new MarginPadding(20), + Spacing = new Vector2(10), + ChildrenEnumerable = Enum.GetValues().OrderBy(v => v).Select(rank => new DrawableRank(rank) + { + RelativeSizeAxes = Axes.None, + Size = new Vector2(50, 25), + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }) + }); + } + } +} From 6080c14dd5b589c982b09365aa1b5eb74ccd14ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 4 Mar 2024 14:47:49 +0100 Subject: [PATCH 3341/4092] Update F rank badge colours to match latest designs --- osu.Game/Graphics/OsuColour.cs | 6 +++++- osu.Game/Online/Leaderboards/DrawableRank.cs | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 985898958c..c479d0cfe4 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -63,8 +63,12 @@ namespace osu.Game.Graphics case ScoreRank.C: return Color4Extensions.FromHex(@"ff8e5d"); - default: + case ScoreRank.D: return Color4Extensions.FromHex(@"ff5a5a"); + + case ScoreRank.F: + default: + return Color4Extensions.FromHex(@"3f3f3f"); } } diff --git a/osu.Game/Online/Leaderboards/DrawableRank.cs b/osu.Game/Online/Leaderboards/DrawableRank.cs index 5177f35478..0b0ab11410 100644 --- a/osu.Game/Online/Leaderboards/DrawableRank.cs +++ b/osu.Game/Online/Leaderboards/DrawableRank.cs @@ -95,8 +95,12 @@ namespace osu.Game.Online.Leaderboards case ScoreRank.C: return Color4Extensions.FromHex(@"473625"); - default: + case ScoreRank.D: return Color4Extensions.FromHex(@"512525"); + + case ScoreRank.F: + default: + return Color4Extensions.FromHex(@"CC3333"); } } } From 9543908c7ab23cc397522ccaa1cfdc97c9d791b1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 4 Mar 2024 23:36:45 +0300 Subject: [PATCH 3342/4092] Fix mod select overlay settings order not always matching panels order --- .../TestSceneModSelectOverlay.cs | 24 +++++++++++++++++++ osu.Game/Overlays/Mods/ModSettingsArea.cs | 11 +++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index b26e126249..6c75530a6e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -859,6 +859,30 @@ namespace osu.Game.Tests.Visual.UserInterface () => modSelectOverlay.ChildrenOfType().Single().ModMultiplier.Value, () => Is.EqualTo(0.3).Within(Precision.DOUBLE_EPSILON)); } + [Test] + public void TestModSettingsOrder() + { + createScreen(); + + AddStep("select DT + HD + DF", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime(), new OsuModHidden(), new OsuModDeflate() }); + AddAssert("mod settings order: DT, HD, DF", () => + { + var columns = this.ChildrenOfType().Single().ChildrenOfType(); + return columns.ElementAt(0).Mod is OsuModDoubleTime && + columns.ElementAt(1).Mod is OsuModHidden && + columns.ElementAt(2).Mod is OsuModDeflate; + }); + + AddStep("replace DT with NC", () => SelectedMods.Value = SelectedMods.Value.Where(m => m is not ModDoubleTime).Append(new OsuModNightcore()).ToList()); + AddAssert("mod settings order: NC, HD, DF", () => + { + var columns = this.ChildrenOfType().Single().ChildrenOfType(); + return columns.ElementAt(0).Mod is OsuModNightcore && + columns.ElementAt(1).Mod is OsuModHidden && + columns.ElementAt(2).Mod is OsuModDeflate; + }); + } + private void waitForColumnLoad() => AddUntilStep("all column content loaded", () => modSelectOverlay.ChildrenOfType().Any() && modSelectOverlay.ChildrenOfType().All(column => column.IsLoaded && column.ItemsLoaded) diff --git a/osu.Game/Overlays/Mods/ModSettingsArea.cs b/osu.Game/Overlays/Mods/ModSettingsArea.cs index 54bfcc7199..d0e0f7e648 100644 --- a/osu.Game/Overlays/Mods/ModSettingsArea.cs +++ b/osu.Game/Overlays/Mods/ModSettingsArea.cs @@ -86,7 +86,10 @@ namespace osu.Game.Overlays.Mods { modSettingsFlow.Clear(); - foreach (var mod in SelectedMods.Value.AsOrdered()) + // Importantly, the selected mods bindable is already ordered by the mod select overlay (following the order of mod columns and panels). + // Using AsOrdered produces a slightly different order (e.g. DT and NC no longer becoming adjacent), + // which breaks user expectations when interacting with the overlay. + foreach (var mod in SelectedMods.Value) { var settings = mod.CreateSettingsControls().ToList(); @@ -110,10 +113,14 @@ namespace osu.Game.Overlays.Mods protected override bool OnMouseDown(MouseDownEvent e) => true; protected override bool OnHover(HoverEvent e) => true; - private partial class ModSettingsColumn : CompositeDrawable + public partial class ModSettingsColumn : CompositeDrawable { + public readonly Mod Mod; + public ModSettingsColumn(Mod mod, IEnumerable settingsControls) { + Mod = mod; + Width = 250; RelativeSizeAxes = Axes.Y; Padding = new MarginPadding { Bottom = 7 }; From 92f455f1995d32d380380b4a06adf34b21bfdc63 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 5 Mar 2024 03:01:01 +0300 Subject: [PATCH 3343/4092] Abstractify performance points calculation to a base class --- .../HUD/DefaultPerformancePointsCounter.cs | 91 +++++++++++++++++++ .../Play/HUD/PerformancePointsCounter.cs | 83 +---------------- 2 files changed, 93 insertions(+), 81 deletions(-) create mode 100644 osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs diff --git a/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs new file mode 100644 index 0000000000..3c4e58e575 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs @@ -0,0 +1,91 @@ +// 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.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Resources.Localisation.Web; +using osuTK; + +namespace osu.Game.Screens.Play.HUD +{ + public partial class DefaultPerformancePointsCounter : PerformancePointsCounter + { + protected override bool IsRollingProportional => true; + + protected override double RollingDuration => 500; + + private const float alpha_when_invalid = 0.3f; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.BlueLighter; + } + + public override bool IsValid + { + get => base.IsValid; + set + { + if (value == IsValid) + return; + + base.IsValid = value; + DrawableCount.FadeTo(value ? 1 : alpha_when_invalid, 1000, Easing.OutQuint); + } + } + + protected override LocalisableString FormatCount(int count) => count.ToString(@"D"); + + protected override IHasText CreateText() => new TextComponent + { + Alpha = alpha_when_invalid + }; + + private partial class TextComponent : CompositeDrawable, IHasText + { + public LocalisableString Text + { + get => text.Text; + set => text.Text = value; + } + + private readonly OsuSpriteText text; + + public TextComponent() + { + AutoSizeAxes = Axes.Both; + + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(2), + Children = new Drawable[] + { + text = new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Font = OsuFont.Numeric.With(size: 16, fixedWidth: true) + }, + new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Text = BeatmapsetsStrings.ShowScoreboardHeaderspp, + Font = OsuFont.Numeric.With(size: 8), + Padding = new MarginPadding { Bottom = 1.5f }, // align baseline better + } + } + }; + } + } + } +} diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index f041e120f6..4b07e7da36 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -13,16 +13,9 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Framework.Localisation; using osu.Game.Beatmaps; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Judgements; @@ -31,7 +24,6 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Skinning; -using osuTK; namespace osu.Game.Screens.Play.HUD { @@ -39,12 +31,6 @@ namespace osu.Game.Screens.Play.HUD { public bool UsesFixedAnchor { get; set; } - protected override bool IsRollingProportional => true; - - protected override double RollingDuration => 500; - - private const float alpha_when_invalid = 0.3f; - [Resolved] private ScoreProcessor scoreProcessor { get; set; } @@ -60,18 +46,11 @@ namespace osu.Game.Screens.Play.HUD private PerformanceCalculator performanceCalculator; private ScoreInfo scoreInfo; - public PerformancePointsCounter() - { - Current.Value = DisplayedCount = 0; - } - private Mod[] clonedMods; [BackgroundDependencyLoader] - private void load(OsuColour colours, BeatmapDifficultyCache difficultyCache) + private void load(BeatmapDifficultyCache difficultyCache) { - Colour = colours.BlueLighter; - if (gameplayState != null) { performanceCalculator = gameplayState.Ruleset.CreatePerformanceCalculator(); @@ -107,19 +86,7 @@ namespace osu.Game.Screens.Play.HUD onJudgementChanged(gameplayState.LastJudgementResult.Value); } - private bool isValid; - - protected bool IsValid - { - set - { - if (value == isValid) - return; - - isValid = value; - DrawableCount.FadeTo(isValid ? 1 : alpha_when_invalid, 1000, Easing.OutQuint); - } - } + public virtual bool IsValid { get; set; } private void onJudgementChanged(JudgementResult judgement) { @@ -151,13 +118,6 @@ namespace osu.Game.Screens.Play.HUD return timedAttributes[Math.Clamp(attribIndex, 0, timedAttributes.Count - 1)].Attributes; } - protected override LocalisableString FormatCount(int count) => count.ToString(@"D"); - - protected override IHasText CreateText() => new TextComponent - { - Alpha = alpha_when_invalid - }; - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -171,45 +131,6 @@ namespace osu.Game.Screens.Play.HUD loadCancellationSource?.Cancel(); } - private partial class TextComponent : CompositeDrawable, IHasText - { - public LocalisableString Text - { - get => text.Text; - set => text.Text = value; - } - - private readonly OsuSpriteText text; - - public TextComponent() - { - AutoSizeAxes = Axes.Both; - - InternalChild = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Spacing = new Vector2(2), - Children = new Drawable[] - { - text = new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Font = OsuFont.Numeric.With(size: 16, fixedWidth: true) - }, - new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Text = BeatmapsetsStrings.ShowScoreboardHeaderspp, - Font = OsuFont.Numeric.With(size: 8), - Padding = new MarginPadding { Bottom = 1.5f }, // align baseline better - } - } - }; - } - } - // TODO: This class shouldn't exist, but requires breaking changes to allow DifficultyCalculator to receive an IBeatmap. private class GameplayWorkingBeatmap : WorkingBeatmap { From 3ee57cdfba7c932dc4dbfb0351d644e1ce46c6d3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 5 Mar 2024 03:01:46 +0300 Subject: [PATCH 3344/4092] Refactor performance points test scene to support skinning --- .../SkinnableHUDComponentTestScene.cs | 13 ++- .../TestScenePerformancePointsCounter.cs | 96 ++++++++----------- .../TestSceneSkinnableAccuracyCounter.cs | 6 +- .../TestSceneSkinnableHealthDisplay.cs | 6 +- .../Play/HUD/ArgonPerformancePointsCounter.cs | 9 ++ 5 files changed, 65 insertions(+), 65 deletions(-) create mode 100644 osu.Game/Screens/Play/HUD/ArgonPerformancePointsCounter.cs diff --git a/osu.Game.Tests/Visual/Gameplay/SkinnableHUDComponentTestScene.cs b/osu.Game.Tests/Visual/Gameplay/SkinnableHUDComponentTestScene.cs index f54f50795e..7933647dd2 100644 --- a/osu.Game.Tests/Visual/Gameplay/SkinnableHUDComponentTestScene.cs +++ b/osu.Game.Tests/Visual/Gameplay/SkinnableHUDComponentTestScene.cs @@ -1,8 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Skinning; @@ -13,8 +13,13 @@ namespace osu.Game.Tests.Visual.Gameplay { protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); - [SetUp] - public void SetUp() => Schedule(() => + [SetUpSteps] + public virtual void SetUpSteps() + { + AddStep("setup components", SetUpComponents); + } + + public void SetUpComponents() { SetContents(skin => { @@ -28,7 +33,7 @@ namespace osu.Game.Tests.Visual.Gameplay implementation.Origin = Anchor.Centre; return implementation; }); - }); + } protected abstract Drawable CreateDefaultImplementation(); protected virtual Drawable CreateArgonImplementation() => CreateDefaultImplementation(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs index 9622caabf5..986167279c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs @@ -3,102 +3,88 @@ #nullable disable -using System; -using System.Diagnostics; +using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Testing; -using osu.Game.Rulesets; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; -using osuTK; +using osu.Game.Tests.Gameplay; namespace osu.Game.Tests.Visual.Gameplay { - public partial class TestScenePerformancePointsCounter : OsuTestScene + public partial class TestScenePerformancePointsCounter : SkinnableHUDComponentTestScene { - private DependencyProvidingContainer dependencyContainer; + [Cached(typeof(ScoreProcessor))] + private readonly ScoreProcessor scoreProcessor = new OsuScoreProcessor(); - private GameplayState gameplayState; - private ScoreProcessor scoreProcessor; + [Cached] + private readonly GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset()); private int iteration; - private Bindable lastJudgementResult = new Bindable(); - private PerformancePointsCounter counter; - [SetUpSteps] - public void SetUpSteps() => AddStep("create components", () => + protected override Drawable CreateDefaultImplementation() => new DefaultPerformancePointsCounter(); + protected override Drawable CreateArgonImplementation() => new ArgonPerformancePointsCounter(); + protected override Drawable CreateLegacyImplementation() => Empty(); + + private Bindable lastJudgementResult => (Bindable)gameplayState.LastJudgementResult; + + public override void SetUpSteps() { - var ruleset = CreateRuleset(); - - Debug.Assert(ruleset != null); - - var beatmap = CreateWorkingBeatmap(ruleset.RulesetInfo) - .GetPlayableBeatmap(ruleset.RulesetInfo); - - lastJudgementResult = new Bindable(); - - gameplayState = new GameplayState(beatmap, ruleset); - gameplayState.LastJudgementResult.BindTo(lastJudgementResult); - - scoreProcessor = new ScoreProcessor(ruleset); - - Child = dependencyContainer = new DependencyProvidingContainer + AddStep("reset", () => { - RelativeSizeAxes = Axes.Both, - CachedDependencies = new (Type, object)[] - { - (typeof(GameplayState), gameplayState), - (typeof(ScoreProcessor), scoreProcessor) - } - }; + var ruleset = new OsuRuleset(); + var beatmap = CreateWorkingBeatmap(ruleset.RulesetInfo) + .GetPlayableBeatmap(ruleset.RulesetInfo); - iteration = 0; - }); + iteration = 0; + scoreProcessor.ApplyBeatmap(beatmap); + lastJudgementResult.SetDefault(); + }); - protected override Ruleset CreateRuleset() => new OsuRuleset(); + base.SetUpSteps(); + } - private void createCounter() => AddStep("Create counter", () => + [Test] + public void TestDisplay() { - dependencyContainer.Child = counter = new PerformancePointsCounter - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(5), - }; - }); + AddSliderStep("pp", 0, 2000, 0, v => this.ChildrenOfType().ForEach(c => c.Current.Value = v)); + AddToggleStep("toggle validity", v => this.ChildrenOfType().ForEach(c => c.IsValid = v)); + } [Test] public void TestBasicCounting() { int previousValue = 0; - createCounter(); - AddAssert("counter displaying zero", () => counter.Current.Value == 0); + AddAssert("counter displaying zero", () => this.ChildrenOfType().All(c => c.Current.Value == 0)); AddRepeatStep("Add judgement", applyOneJudgement, 10); - AddUntilStep("counter non-zero", () => counter.Current.Value > 0); - AddUntilStep("counter opaque", () => counter.Child.Alpha == 1); + AddUntilStep("counter non-zero", () => this.ChildrenOfType().All(c => c.Current.Value > 0)); + AddUntilStep("counter valid", () => this.ChildrenOfType().All(c => c.IsValid)); AddStep("Revert judgement", () => { - previousValue = counter.Current.Value; + previousValue = this.ChildrenOfType().First().Current.Value; scoreProcessor.RevertResult(new JudgementResult(new HitObject(), new OsuJudgement())); }); - AddUntilStep("counter decreased", () => counter.Current.Value < previousValue); + AddUntilStep("counter decreased", () => this.ChildrenOfType().All(c => c.Current.Value < previousValue)); AddStep("Add judgement", applyOneJudgement); - AddUntilStep("counter non-zero", () => counter.Current.Value > 0); + AddUntilStep("counter non-zero", () => this.ChildrenOfType().All(c => c.Current.Value > 0)); } [Test] @@ -106,10 +92,10 @@ namespace osu.Game.Tests.Visual.Gameplay { AddRepeatStep("Add judgement", applyOneJudgement, 10); - createCounter(); + AddStep("recreate counter", SetUpComponents); - AddUntilStep("counter non-zero", () => counter.Current.Value > 0); - AddUntilStep("counter opaque", () => counter.Child.Alpha == 1); + AddUntilStep("counter non-zero", () => this.ChildrenOfType().All(c => c.Current.Value > 0)); + AddUntilStep("counter valid", () => this.ChildrenOfType().All(c => c.IsValid)); } private void applyOneJudgement() diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs index e088d2ca87..d815b80cbc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs @@ -4,7 +4,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Testing; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; @@ -21,10 +20,11 @@ namespace osu.Game.Tests.Visual.Gameplay protected override Drawable CreateDefaultImplementation() => new DefaultAccuracyCounter(); protected override Drawable CreateLegacyImplementation() => new LegacyAccuracyCounter(); - [SetUpSteps] - public void SetUpSteps() + public override void SetUpSteps() { AddStep("Set initial accuracy", () => scoreProcessor.Accuracy.Value = 1); + + base.SetUpSteps(); } [Test] diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs index 1849e8abd0..3fd6caf7f2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs @@ -4,7 +4,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Testing; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Judgements; @@ -25,14 +24,15 @@ namespace osu.Game.Tests.Visual.Gameplay protected override Drawable CreateDefaultImplementation() => new DefaultHealthDisplay { Scale = new Vector2(0.6f) }; protected override Drawable CreateLegacyImplementation() => new LegacyHealthDisplay { Scale = new Vector2(0.6f) }; - [SetUpSteps] - public void SetUpSteps() + public override void SetUpSteps() { AddStep(@"Reset all", delegate { healthProcessor.Health.Value = 1; healthProcessor.Failed += () => false; // health won't be updated if the processor gets into a "fail" state. }); + + base.SetUpSteps(); } protected override void Update() diff --git a/osu.Game/Screens/Play/HUD/ArgonPerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/ArgonPerformancePointsCounter.cs new file mode 100644 index 0000000000..9e6b5f81e0 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/ArgonPerformancePointsCounter.cs @@ -0,0 +1,9 @@ +// 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.Screens.Play.HUD +{ + public partial class ArgonPerformancePointsCounter : PerformancePointsCounter + { + } +} From d7f1e50d66b10529a37d48c99a79a6b0ef2b54f8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 5 Mar 2024 03:34:29 +0300 Subject: [PATCH 3345/4092] Add "Argon" performance points counter --- .../Play/HUD/ArgonPerformancePointsCounter.cs | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/ArgonPerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/ArgonPerformancePointsCounter.cs index 9e6b5f81e0..022c1fab9e 100644 --- a/osu.Game/Screens/Play/HUD/ArgonPerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonPerformancePointsCounter.cs @@ -1,9 +1,79 @@ // 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.Extensions.LocalisationExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; +using osu.Game.Localisation.SkinComponents; +using osu.Game.Resources.Localisation.Web; + namespace osu.Game.Screens.Play.HUD { public partial class ArgonPerformancePointsCounter : PerformancePointsCounter { + private ArgonCounterTextComponent text = null!; + + protected override double RollingDuration => 250; + + private const float alpha_when_invalid = 0.3f; + + [SettingSource("Wireframe opacity", "Controls the opacity of the wire frames behind the digits.")] + public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.25f) + { + Precision = 0.01f, + MinValue = 0, + MaxValue = 1, + }; + + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.ShowLabel), nameof(SkinnableComponentStrings.ShowLabelDescription))] + public Bindable ShowLabel { get; } = new BindableBool(true); + + public override bool IsValid + { + get => base.IsValid; + set + { + if (value == IsValid) + return; + + base.IsValid = value; + text.FadeTo(value ? 1 : alpha_when_invalid, 1000, Easing.OutQuint); + } + } + + public override int DisplayedCount + { + get => base.DisplayedCount; + set + { + base.DisplayedCount = value; + updateWireframe(); + } + } + + private void updateWireframe() + { + int digitsRequiredForDisplayCount = getDigitsRequiredForDisplayCount(); + + if (digitsRequiredForDisplayCount != text.WireframeTemplate.Length) + text.WireframeTemplate = new string('#', digitsRequiredForDisplayCount); + } + + private int getDigitsRequiredForDisplayCount() + { + int digitsRequired = 1; + long c = DisplayedCount; + while ((c /= 10) > 0) + digitsRequired++; + return digitsRequired; + } + + protected override IHasText CreateText() => text = new ArgonCounterTextComponent(Anchor.TopRight, BeatmapsetsStrings.ShowScoreboardHeaderspp.ToUpper()) + { + WireframeOpacity = { BindTarget = WireframeOpacity }, + ShowLabel = { BindTarget = ShowLabel }, + }; } } From 0cbcfcecdc3f777efe1adbda732787c26c294ff6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 5 Mar 2024 03:58:43 +0300 Subject: [PATCH 3346/4092] Integrate "Argon" performance points counter with HUD layout --- osu.Game/Skinning/ArgonSkin.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 6fcab6a977..24e17d21f4 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -118,6 +118,7 @@ namespace osu.Game.Skinning var wedgePieces = container.OfType().ToArray(); var score = container.OfType().FirstOrDefault(); var accuracy = container.OfType().FirstOrDefault(); + var performancePoints = container.OfType().FirstOrDefault(); var combo = container.OfType().FirstOrDefault(); var songProgress = container.OfType().FirstOrDefault(); var keyCounter = container.OfType().FirstOrDefault(); @@ -159,6 +160,13 @@ namespace osu.Game.Skinning accuracy.Origin = Anchor.TopRight; } + if (performancePoints != null && accuracy != null) + { + performancePoints.Position = new Vector2(accuracy.X, accuracy.Y + accuracy.DrawHeight + 10); + performancePoints.Anchor = Anchor.TopRight; + performancePoints.Origin = Anchor.TopRight; + } + var hitError = container.OfType().FirstOrDefault(); if (hitError != null) @@ -224,6 +232,7 @@ namespace osu.Game.Skinning CornerRadius = { Value = 0.5f } }, new ArgonAccuracyCounter(), + new ArgonPerformancePointsCounter(), new ArgonComboCounter { Scale = new Vector2(1.3f) From b1477c30f2218ffd449f98f595c8a5c8a83fcfdf Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 5 Mar 2024 04:30:57 +0300 Subject: [PATCH 3347/4092] Add deserialisation test coverage --- .../Archives/modified-argon-20240305.osk | Bin 0 -> 2390 bytes osu.Game.Tests/Skins/SkinDeserialisationTest.cs | 2 ++ 2 files changed, 2 insertions(+) create mode 100644 osu.Game.Tests/Resources/Archives/modified-argon-20240305.osk diff --git a/osu.Game.Tests/Resources/Archives/modified-argon-20240305.osk b/osu.Game.Tests/Resources/Archives/modified-argon-20240305.osk new file mode 100644 index 0000000000000000000000000000000000000000..6db24352a5e62a877cc2c046fb8130a8dfe9c1b2 GIT binary patch literal 2390 zcmbVNdpMM7AAZL;hIZwA+Kb5{5i^>c%8oM`lZhc|8D~a}bzD}eNvX&oYbf7#IoD85 znQ zx&Z$T4RGBHC;`|Y&lo>~Z;+36V0Z}82`35&dQx6>|{D~0Dxe_rF|}$}{gv0a&zvpaR+JS2gei_Z{r6 z87#>GfE1Vk#*;v_ak4NE3H~{R=u36ctcavK4n??Er}?C(fI;N4mvuHB(cJ=N@i_Xs`G&VOh*bZ&H$FT@j$*ockJ1pgngB>G(%5QWRctuH-Q&R8ArZ5M z1H9NHQkeUH=$IVmLzVxB)esorb=T{1_})r0dP#!xds)$D)38HUEqyWFE=3=7>(y19 z>gh|aoJDIzN*$qdC?LNe^X#*%N)Fipbp^KUPLE$KQ1s!DBE^OyzjAI- zyC3dAXs5ODeFq~M4Y&(!n#egcOEun9LJeBWXf=ok{Z?TF#U?Ic%j_>LY2+^X^7^n! z7iNx%`9d#8Of`J0qJ1K?^an^i$aK4K)l&>5A<|@`6k*XV=|M0UzA`fYgVfas9!und zC$AD7henyTLM>(T-39S>HYQH#n3qxEzop-dUXYzOpAp?N=@325<9V>YFf6~d2;$h$ zMS9(O-qN^w!dHJ{K>aW4q{TZcv6AQov0Kfmjtic{>W@#QeN8wMRzH}7B~$pdDEddY(+E{uEX zM+pwVC5*V(twjGgaP`nWvy)XOsfpvMLMc>?E!n4L_$jUPVq9m_;ReLV+1sV$iGt)` z0=h_1t3R7A^qY(2Hwz0qq5944W!AICvK`Mh6y&>6N>_>Sp0qtT?eaIy5Uoh=Pu8VM z-?sD9^s57V*vDC=gIdQR$SwKn#+$RoEqN`7KOxbV!c@_y<+a11A?D)-{Y9Rv}d)Z^R26=4p{(ZKkS~DpXv2b|ESKXxjTzX|$lqM`mx>!qwN` z*9%K0Ps_)aG+_D2%UTjLtT<+v#kfW0zT|FVC;jH9oYRrXONPvXXxwxje##PIf}%u{ zy+`jXPq%1gM=)Hx_WKVwcV~Lf;%p@fZX;M1mhg-}C*wUiIXBwZ=jGSM$;+vyPAnha zT)x`~;T9$)v}UUnR3Pr}?_QW(P};=7!r<{3Q*(Pq7aeW z2gM8t_x5&ob#`iN&R2K7!4;654B`sNc2G(4py2FEm0jfI>sxo?&Sl?r wP)UM(z`x<*&Ih>dvl9|-`)mi5MB)D@?Qd=-ESe8I0swsAU_bzXD8T*t8{3tr>i_@% literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs index 6423e061c5..d979bdab93 100644 --- a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs +++ b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs @@ -60,6 +60,8 @@ namespace osu.Game.Tests.Skins "Archives/modified-argon-20231106.osk", // Covers "Argon" accuracy/score/combo counters, and wedges "Archives/modified-argon-20231108.osk", + // Covers "Argon" performance points counter + "Archives/modified-argon-20240305.osk", }; /// From 49b3e81e8aa2f9ba438d3218d06a9e167fd30191 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 5 Mar 2024 04:34:41 +0300 Subject: [PATCH 3348/4092] Migrate `DefaultPerformancePointsCounter` and rename it --- .../Visual/Gameplay/TestScenePerformancePointsCounter.cs | 3 ++- osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs | 2 +- osu.Game/Skinning/Skin.cs | 1 + .../Triangles/TrianglesPerformancePointsCounter.cs} | 5 +++-- osu.Game/Skinning/TrianglesSkin.cs | 3 ++- 5 files changed, 9 insertions(+), 5 deletions(-) rename osu.Game/{Screens/Play/HUD/DefaultPerformancePointsCounter.cs => Skinning/Triangles/TrianglesPerformancePointsCounter.cs} (94%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs index 986167279c..eada72326d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs @@ -18,6 +18,7 @@ using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; +using osu.Game.Skinning.Triangles; using osu.Game.Tests.Gameplay; namespace osu.Game.Tests.Visual.Gameplay @@ -32,7 +33,7 @@ namespace osu.Game.Tests.Visual.Gameplay private int iteration; - protected override Drawable CreateDefaultImplementation() => new DefaultPerformancePointsCounter(); + protected override Drawable CreateDefaultImplementation() => new TrianglesPerformancePointsCounter(); protected override Drawable CreateArgonImplementation() => new ArgonPerformancePointsCounter(); protected override Drawable CreateLegacyImplementation() => Empty(); diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index 4b07e7da36..c5ad106bcc 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -27,7 +27,7 @@ using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { - public partial class PerformancePointsCounter : RollingCounter, ISerialisableDrawable + public abstract partial class PerformancePointsCounter : RollingCounter, ISerialisableDrawable { public bool UsesFixedAnchor { get; set; } diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 9ee69d033d..91b21f0308 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -153,6 +153,7 @@ namespace osu.Game.Skinning // handle namespace changes... jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.SongProgress", @"osu.Game.Screens.Play.HUD.DefaultSongProgress"); jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.LegacyComboCounter", @"osu.Game.Skinning.LegacyComboCounter"); + jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.PerformancePointsCounter", @"osu.Game.Skinning.Triangles.TrianglesPerformancePointsCounter"); var deserializedContent = JsonConvert.DeserializeObject>(jsonContent); diff --git a/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs b/osu.Game/Skinning/Triangles/TrianglesPerformancePointsCounter.cs similarity index 94% rename from osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs rename to osu.Game/Skinning/Triangles/TrianglesPerformancePointsCounter.cs index 3c4e58e575..99da2c0942 100644 --- a/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs +++ b/osu.Game/Skinning/Triangles/TrianglesPerformancePointsCounter.cs @@ -11,11 +11,12 @@ using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Resources.Localisation.Web; +using osu.Game.Screens.Play.HUD; using osuTK; -namespace osu.Game.Screens.Play.HUD +namespace osu.Game.Skinning.Triangles { - public partial class DefaultPerformancePointsCounter : PerformancePointsCounter + public partial class TrianglesPerformancePointsCounter : PerformancePointsCounter { protected override bool IsRollingProportional => true; diff --git a/osu.Game/Skinning/TrianglesSkin.cs b/osu.Game/Skinning/TrianglesSkin.cs index a2dca5d333..6158d4c7bf 100644 --- a/osu.Game/Skinning/TrianglesSkin.cs +++ b/osu.Game/Skinning/TrianglesSkin.cs @@ -14,6 +14,7 @@ using osu.Game.Extensions; using osu.Game.IO; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD.HitErrorMeters; +using osu.Game.Skinning.Triangles; using osuTK; using osuTK.Graphics; @@ -167,7 +168,7 @@ namespace osu.Game.Skinning new DefaultKeyCounterDisplay(), new BarHitErrorMeter(), new BarHitErrorMeter(), - new PerformancePointsCounter() + new TrianglesPerformancePointsCounter() } }; From cceb616a18cc862f975da533bed42b49a89d2fa9 Mon Sep 17 00:00:00 2001 From: Jayden Date: Mon, 4 Mar 2024 22:25:36 -0500 Subject: [PATCH 3349/4092] Update failure messages Co-authored-by: Salman Ahmed --- osu.Desktop/DiscordRichPresence.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 035add8044..85b6129043 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -184,14 +184,14 @@ namespace osu.Desktop game.Window?.Raise(); if (!tryParseRoomSecret(args.Secret, out long roomId, out string? password)) - Logger.Log("Failed to parse the room secret Discord gave us", LoggingTarget.Network, LogLevel.Error); + Logger.Log("Could not parse room from Discord RPC Client", LoggingTarget.Network, LogLevel.Important); var request = new GetRoomRequest(roomId); request.Success += room => Schedule(() => { game.PresentMultiplayerMatch(room, password); }); - request.Failure += _ => Logger.Log("Couldn't find the room Discord gave us", LoggingTarget.Network, LogLevel.Error); + request.Failure += _ => Logger.Log($"Could not find room {roomId} from Discord RPC Client", LoggingTarget.Network, LogLevel.Important); api.Queue(request); } From 43841e210d2a1915d3c39bfe191ef62befbd3492 Mon Sep 17 00:00:00 2001 From: Berkan Diler Date: Tue, 5 Mar 2024 09:58:46 +0100 Subject: [PATCH 3350/4092] Use ObjectDisposedException.ThrowIf throw helper --- osu.Game/Database/RealmAccess.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 4bd7f36cdd..167d170c81 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -489,8 +489,7 @@ namespace osu.Game.Database /// The work to run. public Task WriteAsync(Action action) { - if (isDisposed) - throw new ObjectDisposedException(nameof(RealmAccess)); + ObjectDisposedException.ThrowIf(isDisposed, this); // Required to ensure the write is tracked and accounted for before disposal. // Can potentially be avoided if we have a need to do so in the future. @@ -675,8 +674,7 @@ namespace osu.Game.Database private Realm getRealmInstance() { - if (isDisposed) - throw new ObjectDisposedException(nameof(RealmAccess)); + ObjectDisposedException.ThrowIf(isDisposed, this); bool tookSemaphoreLock = false; @@ -1189,8 +1187,7 @@ namespace osu.Game.Database if (!ThreadSafety.IsUpdateThread) throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); - if (isDisposed) - throw new ObjectDisposedException(nameof(RealmAccess)); + ObjectDisposedException.ThrowIf(isDisposed, this); SynchronizationContext? syncContext = null; From 9bac60a98fc098cb4c6b1f118e61c5255bd5b235 Mon Sep 17 00:00:00 2001 From: Berkan Diler Date: Tue, 5 Mar 2024 10:19:47 +0100 Subject: [PATCH 3351/4092] Use ArgumentNullException.ThrowIfNull in more places --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 13e28279e6..bdc0ff85ba 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -135,8 +135,7 @@ namespace osu.Game.Rulesets.UI protected DrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) : base(ruleset) { - if (beatmap == null) - throw new ArgumentNullException(nameof(beatmap), "Beatmap cannot be null."); + ArgumentNullException.ThrowIfNull(beatmap); if (!(beatmap is Beatmap tBeatmap)) throw new ArgumentException($"{GetType()} expected the beatmap to contain hitobjects of type {typeof(TObject)}.", nameof(beatmap)); From a89130348469e1b1ea8e025bb2c6cf4a85da51b3 Mon Sep 17 00:00:00 2001 From: Berkan Diler Date: Tue, 5 Mar 2024 10:20:30 +0100 Subject: [PATCH 3352/4092] Use ArgumentOutOfRangeException throw helper methods --- osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs | 6 +++--- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 2 +- osu.Game/Beatmaps/Timing/TimeSignature.cs | 3 +-- osu.Game/Rulesets/Difficulty/Utils/ReverseQueue.cs | 3 +-- osu.Game/Rulesets/Objects/Types/PathType.cs | 3 +-- osu.Game/Screens/Ranking/Statistics/SimpleStatisticTable.cs | 3 +-- osu.Game/Utils/LimitedCapacityQueue.cs | 3 +-- osu.Game/Utils/StatelessRNG.cs | 2 +- 8 files changed, 10 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs index 835c67ff19..9cc0a8c414 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs @@ -58,9 +58,9 @@ namespace osu.Game.Rulesets.Osu.Beatmaps private void applyStacking(Beatmap beatmap, int startIndex, int endIndex) { - if (startIndex > endIndex) throw new ArgumentOutOfRangeException(nameof(startIndex), $"{nameof(startIndex)} cannot be greater than {nameof(endIndex)}."); - if (startIndex < 0) throw new ArgumentOutOfRangeException(nameof(startIndex), $"{nameof(startIndex)} cannot be less than 0."); - if (endIndex < 0) throw new ArgumentOutOfRangeException(nameof(endIndex), $"{nameof(endIndex)} cannot be less than 0."); + ArgumentOutOfRangeException.ThrowIfGreaterThan(startIndex, endIndex); + ArgumentOutOfRangeException.ThrowIfNegative(startIndex); + ArgumentOutOfRangeException.ThrowIfNegative(endIndex); int extendedEndIndex = endIndex; diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 448cfaf84c..3ead61f64a 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -334,7 +334,7 @@ namespace osu.Game.Rulesets.Osu.Edit /// The from a selected to a target . private OsuDistanceSnapGrid createGrid(Func sourceSelector, int targetOffset = 1) { - if (targetOffset < 1) throw new ArgumentOutOfRangeException(nameof(targetOffset)); + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(targetOffset); int sourceIndex = -1; diff --git a/osu.Game/Beatmaps/Timing/TimeSignature.cs b/osu.Game/Beatmaps/Timing/TimeSignature.cs index 7499a725dc..377a878631 100644 --- a/osu.Game/Beatmaps/Timing/TimeSignature.cs +++ b/osu.Game/Beatmaps/Timing/TimeSignature.cs @@ -23,8 +23,7 @@ namespace osu.Game.Beatmaps.Timing public TimeSignature(int numerator) { - if (numerator < 1) - throw new ArgumentOutOfRangeException(nameof(numerator), numerator, "The numerator of a time signature must be positive."); + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(numerator); Numerator = numerator; } diff --git a/osu.Game/Rulesets/Difficulty/Utils/ReverseQueue.cs b/osu.Game/Rulesets/Difficulty/Utils/ReverseQueue.cs index 3dc2d133ba..17c3c51cfb 100644 --- a/osu.Game/Rulesets/Difficulty/Utils/ReverseQueue.cs +++ b/osu.Game/Rulesets/Difficulty/Utils/ReverseQueue.cs @@ -27,8 +27,7 @@ namespace osu.Game.Rulesets.Difficulty.Utils public ReverseQueue(int initialCapacity) { - if (initialCapacity <= 0) - throw new ArgumentOutOfRangeException(nameof(initialCapacity)); + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(initialCapacity); items = new T[initialCapacity]; capacity = initialCapacity; diff --git a/osu.Game/Rulesets/Objects/Types/PathType.cs b/osu.Game/Rulesets/Objects/Types/PathType.cs index 23f1ccf0bc..6983484dce 100644 --- a/osu.Game/Rulesets/Objects/Types/PathType.cs +++ b/osu.Game/Rulesets/Objects/Types/PathType.cs @@ -40,8 +40,7 @@ namespace osu.Game.Rulesets.Objects.Types public static PathType BSpline(int degree) { - if (degree <= 0) - throw new ArgumentOutOfRangeException(nameof(degree), "The degree of a B-Spline path must be greater than zero."); + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(degree); return new PathType { Type = SplineType.BSpline, Degree = degree }; } diff --git a/osu.Game/Screens/Ranking/Statistics/SimpleStatisticTable.cs b/osu.Game/Screens/Ranking/Statistics/SimpleStatisticTable.cs index ed31bc8643..4abf0007a7 100644 --- a/osu.Game/Screens/Ranking/Statistics/SimpleStatisticTable.cs +++ b/osu.Game/Screens/Ranking/Statistics/SimpleStatisticTable.cs @@ -33,8 +33,7 @@ namespace osu.Game.Screens.Ranking.Statistics /// The s to display in this row. public SimpleStatisticTable(int columnCount, [ItemNotNull] IEnumerable items) { - if (columnCount < 1) - throw new ArgumentOutOfRangeException(nameof(columnCount)); + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(columnCount); this.columnCount = columnCount; this.items = items.ToArray(); diff --git a/osu.Game/Utils/LimitedCapacityQueue.cs b/osu.Game/Utils/LimitedCapacityQueue.cs index d36aa8af2c..b96148d4a0 100644 --- a/osu.Game/Utils/LimitedCapacityQueue.cs +++ b/osu.Game/Utils/LimitedCapacityQueue.cs @@ -35,8 +35,7 @@ namespace osu.Game.Utils /// The number of items the queue can hold. public LimitedCapacityQueue(int capacity) { - if (capacity < 0) - throw new ArgumentOutOfRangeException(nameof(capacity)); + ArgumentOutOfRangeException.ThrowIfNegative(capacity); this.capacity = capacity; array = new T[capacity]; diff --git a/osu.Game/Utils/StatelessRNG.cs b/osu.Game/Utils/StatelessRNG.cs index 3db632fc42..8833dcbbdb 100644 --- a/osu.Game/Utils/StatelessRNG.cs +++ b/osu.Game/Utils/StatelessRNG.cs @@ -58,7 +58,7 @@ namespace osu.Game.Utils /// public static int NextInt(int maxValue, int seed, int series = 0) { - if (maxValue <= 0) throw new ArgumentOutOfRangeException(nameof(maxValue)); + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(maxValue); return (int)(NextULong(seed, series) % (ulong)maxValue); } From 5965db4fd7344822d116aa7bd23ad2fa2ebfa22e Mon Sep 17 00:00:00 2001 From: Berkan Diler Date: Tue, 5 Mar 2024 10:20:57 +0100 Subject: [PATCH 3353/4092] Use ArgumentException.ThrowIfNullOrEmpty throw helper --- osu.Game/IO/WrappedStorage.cs | 3 +-- osu.Game/Online/API/OAuth.cs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/IO/WrappedStorage.cs b/osu.Game/IO/WrappedStorage.cs index 95ff26db6a..5a8f4aef59 100644 --- a/osu.Game/IO/WrappedStorage.cs +++ b/osu.Game/IO/WrappedStorage.cs @@ -80,8 +80,7 @@ namespace osu.Game.IO public override Storage GetStorageForDirectory(string path) { - if (string.IsNullOrEmpty(path)) - throw new ArgumentException("Must be non-null and not empty string", nameof(path)); + ArgumentException.ThrowIfNullOrEmpty(path); if (!path.EndsWith(Path.DirectorySeparatorChar)) path += Path.DirectorySeparatorChar; diff --git a/osu.Game/Online/API/OAuth.cs b/osu.Game/Online/API/OAuth.cs index 4829310870..b06d9c6586 100644 --- a/osu.Game/Online/API/OAuth.cs +++ b/osu.Game/Online/API/OAuth.cs @@ -39,8 +39,8 @@ namespace osu.Game.Online.API internal void AuthenticateWithLogin(string username, string password) { - if (string.IsNullOrEmpty(username)) throw new ArgumentException("Missing username."); - if (string.IsNullOrEmpty(password)) throw new ArgumentException("Missing password."); + ArgumentException.ThrowIfNullOrEmpty(username); + ArgumentException.ThrowIfNullOrEmpty(password); var accessTokenRequest = new AccessTokenRequestPassword(username, password) { From 6fabbe26166df10907358d5c58dc26f8cf17dbbe Mon Sep 17 00:00:00 2001 From: Berkan Diler Date: Tue, 5 Mar 2024 10:27:12 +0100 Subject: [PATCH 3354/4092] Use new ToDictionary() overload without delegates --- osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs | 4 ++-- osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index 6f321fd401..64caddb2fc 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -245,8 +245,8 @@ namespace osu.Game.Online.API.Requests.Responses RulesetID = score.RulesetID, Passed = score.Passed, Mods = score.APIMods, - Statistics = score.Statistics.Where(kvp => kvp.Value != 0).ToDictionary(kvp => kvp.Key, kvp => kvp.Value), - MaximumStatistics = score.MaximumStatistics.Where(kvp => kvp.Value != 0).ToDictionary(kvp => kvp.Key, kvp => kvp.Value), + Statistics = score.Statistics.Where(kvp => kvp.Value != 0).ToDictionary(), + MaximumStatistics = score.MaximumStatistics.Where(kvp => kvp.Value != 0).ToDictionary(), }; } } diff --git a/osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs b/osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs index 2c5b91f10f..afdcef1d21 100644 --- a/osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs +++ b/osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs @@ -42,8 +42,8 @@ namespace osu.Game.Scoring.Legacy { OnlineID = score.OnlineID, Mods = score.APIMods, - Statistics = score.Statistics.Where(kvp => kvp.Value != 0).ToDictionary(kvp => kvp.Key, kvp => kvp.Value), - MaximumStatistics = score.MaximumStatistics.Where(kvp => kvp.Value != 0).ToDictionary(kvp => kvp.Key, kvp => kvp.Value), + Statistics = score.Statistics.Where(kvp => kvp.Value != 0).ToDictionary(), + MaximumStatistics = score.MaximumStatistics.Where(kvp => kvp.Value != 0).ToDictionary(), ClientVersion = score.ClientVersion, }; } From d0f7ab3316584f92957cd738c57e6dfd85fb9fcc Mon Sep 17 00:00:00 2001 From: Berkan Diler Date: Tue, 5 Mar 2024 17:18:59 +0100 Subject: [PATCH 3355/4092] Revert some changes --- osu.Game/Online/API/OAuth.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/API/OAuth.cs b/osu.Game/Online/API/OAuth.cs index b06d9c6586..4829310870 100644 --- a/osu.Game/Online/API/OAuth.cs +++ b/osu.Game/Online/API/OAuth.cs @@ -39,8 +39,8 @@ namespace osu.Game.Online.API internal void AuthenticateWithLogin(string username, string password) { - ArgumentException.ThrowIfNullOrEmpty(username); - ArgumentException.ThrowIfNullOrEmpty(password); + if (string.IsNullOrEmpty(username)) throw new ArgumentException("Missing username."); + if (string.IsNullOrEmpty(password)) throw new ArgumentException("Missing password."); var accessTokenRequest = new AccessTokenRequestPassword(username, password) { From e6f1a722cbb54a905a20c91fe30a8be072ba357c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 5 Mar 2024 18:48:58 +0100 Subject: [PATCH 3356/4092] Remove unused field and commented-out code --- osu.Game/Screens/Play/DelayedResumeOverlay.cs | 34 +++++-------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/osu.Game/Screens/Play/DelayedResumeOverlay.cs b/osu.Game/Screens/Play/DelayedResumeOverlay.cs index 6f70a914f0..08d00f8ac2 100644 --- a/osu.Game/Screens/Play/DelayedResumeOverlay.cs +++ b/osu.Game/Screens/Play/DelayedResumeOverlay.cs @@ -33,7 +33,6 @@ namespace osu.Game.Screens.Play private double countdownStartTime; private Drawable content = null!; - private Drawable background = null!; private SpriteText countdown = null!; public DelayedResumeOverlay() @@ -53,9 +52,9 @@ namespace osu.Game.Screens.Play Masking = true, BorderColour = colours.Yellow, BorderThickness = 1, - Children = new[] + Children = new Drawable[] { - background = new Box + new Box { Size = new Vector2(250, 40), Colour = Color4.Black, @@ -75,29 +74,14 @@ namespace osu.Game.Screens.Play AutoSizeAxes = Axes.Both, Spacing = new Vector2(5), Colour = colours.Yellow, - Children = new Drawable[] + Child = countdown = new OsuSpriteText { - // new Box - // { - // Anchor = Anchor.Centre, - // Origin = Anchor.Centre, - // Size = new Vector2(40, 3) - // }, - countdown = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - UseFullGlyphHeight = false, - AlwaysPresent = true, - Font = OsuFont.Numeric.With(size: 20, fixedWidth: true) - }, - // new Box - // { - // Anchor = Anchor.Centre, - // Origin = Anchor.Centre, - // Size = new Vector2(40, 3) - // } - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + UseFullGlyphHeight = false, + AlwaysPresent = true, + Font = OsuFont.Numeric.With(size: 20, fixedWidth: true) + }, } } } From 57daaa7fed6b7c7021e0c85d83ff8e532f657c7c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Mar 2024 04:17:15 +0800 Subject: [PATCH 3357/4092] Add logging for `GameplayClockContainer` starting or stopping --- osu.Game/Screens/Play/GameplayClockContainer.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index c039d1e535..255877e0aa 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -122,8 +122,17 @@ namespace osu.Game.Screens.Play StopGameplayClock(); } - protected virtual void StartGameplayClock() => GameplayClock.Start(); - protected virtual void StopGameplayClock() => GameplayClock.Stop(); + protected virtual void StartGameplayClock() + { + Logger.Log($"{nameof(GameplayClockContainer)} started via call to {nameof(StartGameplayClock)}"); + GameplayClock.Start(); + } + + protected virtual void StopGameplayClock() + { + Logger.Log($"{nameof(GameplayClockContainer)} stopped via call to {nameof(StopGameplayClock)}"); + GameplayClock.Stop(); + } /// /// Resets this and the source to an initial state ready for gameplay. From 65ce4ca390925c43ebb8167c4aa19c15b8b46b01 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Mar 2024 04:17:51 +0800 Subject: [PATCH 3358/4092] Never set `waitingOnFrames` if a replay is not attached --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 884310e44c..b49924762e 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -189,7 +189,7 @@ namespace osu.Game.Rulesets.UI double timeBehind = Math.Abs(proposedTime - referenceClock.CurrentTime); isCatchingUp.Value = timeBehind > 200; - waitingOnFrames.Value = state == PlaybackState.NotValid; + waitingOnFrames.Value = hasReplayAttached && state == PlaybackState.NotValid; manualClock.CurrentTime = proposedTime; manualClock.Rate = Math.Abs(referenceClock.Rate) * direction; From 8b03acd27bdbdd6a997dbd70a00a7b4568e6b8bc Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 6 Mar 2024 00:05:56 +0300 Subject: [PATCH 3359/4092] Implement StoryboardElementWithDuration --- .../Formats/LegacyStoryboardDecoder.cs | 2 +- osu.Game/Storyboards/CommandLoop.cs | 1 + osu.Game/Storyboards/CommandTimeline.cs | 4 +- osu.Game/Storyboards/CommandTimelineGroup.cs | 9 +- .../Drawables/DrawableStoryboardAnimation.cs | 2 +- .../Drawables/DrawableStoryboardSprite.cs | 2 +- ...pable.cs => IDrawableStoryboardElement.cs} | 6 +- .../Storyboards/Drawables/IVectorScalable.cs | 13 - osu.Game/Storyboards/StoryboardAnimation.cs | 5 +- .../StoryboardElementWithDuration.cs | 261 +++++++++++++++ osu.Game/Storyboards/StoryboardSprite.cs | 297 +----------------- 11 files changed, 284 insertions(+), 318 deletions(-) rename osu.Game/Storyboards/Drawables/{IFlippable.cs => IDrawableStoryboardElement.cs} (54%) delete mode 100644 osu.Game/Storyboards/Drawables/IVectorScalable.cs create mode 100644 osu.Game/Storyboards/StoryboardElementWithDuration.cs diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index a9a4d9cc49..ba328b2dbd 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -17,7 +17,7 @@ namespace osu.Game.Beatmaps.Formats { public class LegacyStoryboardDecoder : LegacyDecoder { - private StoryboardSprite? storyboardSprite; + private StoryboardElementWithDuration? storyboardSprite; private CommandTimelineGroup? timelineGroup; private Storyboard storyboard = null!; diff --git a/osu.Game/Storyboards/CommandLoop.cs b/osu.Game/Storyboards/CommandLoop.cs index a912daea44..6dd782cb7f 100644 --- a/osu.Game/Storyboards/CommandLoop.cs +++ b/osu.Game/Storyboards/CommandLoop.cs @@ -50,6 +50,7 @@ namespace osu.Game.Storyboards StartValue = command.StartValue, EndValue = command.EndValue, PropertyName = command.PropertyName, + IsParameterCommand = command.IsParameterCommand, LoopCount = TotalIterations, Delay = fullLoopDuration - command.EndTime + command.StartTime }; diff --git a/osu.Game/Storyboards/CommandTimeline.cs b/osu.Game/Storyboards/CommandTimeline.cs index ce25bfe25b..4ad31d88c2 100644 --- a/osu.Game/Storyboards/CommandTimeline.cs +++ b/osu.Game/Storyboards/CommandTimeline.cs @@ -25,6 +25,7 @@ namespace osu.Game.Storyboards public T EndValue { get; private set; } public string PropertyName { get; } + public bool IsParameterTimeline { get; set; } public CommandTimeline(string propertyName) { @@ -38,7 +39,7 @@ namespace osu.Game.Storyboards endTime = startTime; } - commands.Add(new TypedCommand { Easing = easing, StartTime = startTime, EndTime = endTime, StartValue = startValue, EndValue = endValue, PropertyName = PropertyName }); + commands.Add(new TypedCommand { Easing = easing, StartTime = startTime, EndTime = endTime, StartValue = startValue, EndValue = endValue, PropertyName = PropertyName, IsParameterCommand = IsParameterTimeline }); if (startTime < StartTime) { @@ -65,6 +66,7 @@ namespace osu.Game.Storyboards public string PropertyName { get; set; } public int LoopCount { get; set; } public double Delay { get; set; } + public bool IsParameterCommand { get; set; } public T StartValue; public T EndValue; diff --git a/osu.Game/Storyboards/CommandTimelineGroup.cs b/osu.Game/Storyboards/CommandTimelineGroup.cs index cb795e0ffe..0362925619 100644 --- a/osu.Game/Storyboards/CommandTimelineGroup.cs +++ b/osu.Game/Storyboards/CommandTimelineGroup.cs @@ -22,9 +22,9 @@ namespace osu.Game.Storyboards public CommandTimeline Rotation = new CommandTimeline("Rotation"); public CommandTimeline Colour = new CommandTimeline("Colour"); public CommandTimeline Alpha = new CommandTimeline("Alpha"); - public CommandTimeline BlendingParameters = new CommandTimeline("Blending"); - public CommandTimeline FlipH = new CommandTimeline("FlipH"); - public CommandTimeline FlipV = new CommandTimeline("FlipV"); + public CommandTimeline BlendingParameters = new CommandTimeline("Blending") { IsParameterTimeline = true }; + public CommandTimeline FlipH = new CommandTimeline("FlipH") { IsParameterTimeline = true }; + public CommandTimeline FlipV = new CommandTimeline("FlipV") { IsParameterTimeline = true }; private readonly ICommandTimeline[] timelines; @@ -109,7 +109,8 @@ namespace osu.Game.Storyboards EndTime = offset + command.EndTime, StartValue = command.StartValue, EndValue = command.EndValue, - PropertyName = command.PropertyName + PropertyName = command.PropertyName, + IsParameterCommand = command.IsParameterCommand }); } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index fae9ec7f2e..8e1a8ce949 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -16,7 +16,7 @@ using osuTK; namespace osu.Game.Storyboards.Drawables { - public partial class DrawableStoryboardAnimation : TextureAnimation, IFlippable, IVectorScalable + public partial class DrawableStoryboardAnimation : TextureAnimation, IDrawableStoryboardElement { public StoryboardAnimation Animation { get; } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index ec875219b6..6772178e85 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -13,7 +13,7 @@ using osuTK; namespace osu.Game.Storyboards.Drawables { - public partial class DrawableStoryboardSprite : Sprite, IFlippable, IVectorScalable + public partial class DrawableStoryboardSprite : Sprite, IDrawableStoryboardElement { public StoryboardSprite Sprite { get; } diff --git a/osu.Game/Storyboards/Drawables/IFlippable.cs b/osu.Game/Storyboards/Drawables/IDrawableStoryboardElement.cs similarity index 54% rename from osu.Game/Storyboards/Drawables/IFlippable.cs rename to osu.Game/Storyboards/Drawables/IDrawableStoryboardElement.cs index 2a931875ea..6652b5509c 100644 --- a/osu.Game/Storyboards/Drawables/IFlippable.cs +++ b/osu.Game/Storyboards/Drawables/IDrawableStoryboardElement.cs @@ -1,13 +1,15 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// 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.Graphics.Transforms; +using osuTK; namespace osu.Game.Storyboards.Drawables { - internal interface IFlippable : ITransformable + public interface IDrawableStoryboardElement : ITransformable { bool FlipH { get; set; } bool FlipV { get; set; } + Vector2 VectorScale { get; set; } } } diff --git a/osu.Game/Storyboards/Drawables/IVectorScalable.cs b/osu.Game/Storyboards/Drawables/IVectorScalable.cs deleted file mode 100644 index ab0452df80..0000000000 --- a/osu.Game/Storyboards/Drawables/IVectorScalable.cs +++ /dev/null @@ -1,13 +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.Framework.Graphics.Transforms; -using osuTK; - -namespace osu.Game.Storyboards.Drawables -{ - internal interface IVectorScalable : ITransformable - { - Vector2 VectorScale { get; set; } - } -} diff --git a/osu.Game/Storyboards/StoryboardAnimation.cs b/osu.Game/Storyboards/StoryboardAnimation.cs index 1a4b6bb923..173acf7ff1 100644 --- a/osu.Game/Storyboards/StoryboardAnimation.cs +++ b/osu.Game/Storyboards/StoryboardAnimation.cs @@ -7,7 +7,7 @@ using osu.Game.Storyboards.Drawables; namespace osu.Game.Storyboards { - public class StoryboardAnimation : StoryboardSprite + public class StoryboardAnimation : StoryboardElementWithDuration { public int FrameCount; public double FrameDelay; @@ -21,8 +21,7 @@ namespace osu.Game.Storyboards LoopType = loopType; } - public override Drawable CreateDrawable() - => new DrawableStoryboardAnimation(this); + public override DrawableStoryboardAnimation CreateStoryboardDrawable() => new DrawableStoryboardAnimation(this); } public enum AnimationLoopType diff --git a/osu.Game/Storyboards/StoryboardElementWithDuration.cs b/osu.Game/Storyboards/StoryboardElementWithDuration.cs new file mode 100644 index 0000000000..0c89b40c36 --- /dev/null +++ b/osu.Game/Storyboards/StoryboardElementWithDuration.cs @@ -0,0 +1,261 @@ +// 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.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics; +using osu.Game.Storyboards.Drawables; +using osuTK; + +namespace osu.Game.Storyboards +{ + public abstract class StoryboardElementWithDuration : IStoryboardElementWithDuration + { + protected readonly List Loops = new List(); + private readonly List triggers = new List(); + + public string Path { get; } + public bool IsDrawable => HasCommands; + + public Anchor Origin; + public Vector2 InitialPosition; + + public readonly CommandTimelineGroup TimelineGroup = new CommandTimelineGroup(); + + public double StartTime + { + get + { + // To get the initial start time, we need to check whether the first alpha command to exist (across all loops) has a StartValue of zero. + // A StartValue of zero governs, above all else, the first valid display time of a sprite. + // + // You can imagine that the first command of each type decides that type's start value, so if the initial alpha is zero, + // anything before that point can be ignored (the sprite is not visible after all). + var alphaCommands = new List<(double startTime, bool isZeroStartValue)>(); + + var command = TimelineGroup.Alpha.Commands.FirstOrDefault(); + if (command != null) alphaCommands.Add((command.StartTime, command.StartValue == 0)); + + foreach (var loop in Loops) + { + command = loop.Alpha.Commands.FirstOrDefault(); + if (command != null) alphaCommands.Add((command.StartTime + loop.LoopStartTime, command.StartValue == 0)); + } + + if (alphaCommands.Count > 0) + { + var firstAlpha = alphaCommands.MinBy(t => t.startTime); + + if (firstAlpha.isZeroStartValue) + return firstAlpha.startTime; + } + + return EarliestTransformTime; + } + } + + public double EarliestTransformTime + { + get + { + // If we got to this point, either no alpha commands were present, or the earliest had a non-zero start value. + // The sprite's StartTime will be determined by the earliest command, regardless of type. + double earliestStartTime = TimelineGroup.StartTime; + foreach (var l in Loops) + earliestStartTime = Math.Min(earliestStartTime, l.StartTime); + return earliestStartTime; + } + } + + public double EndTime + { + get + { + double latestEndTime = TimelineGroup.EndTime; + + foreach (var l in Loops) + latestEndTime = Math.Max(latestEndTime, l.EndTime); + + return latestEndTime; + } + } + + public double EndTimeForDisplay + { + get + { + double latestEndTime = TimelineGroup.EndTime; + + foreach (var l in Loops) + latestEndTime = Math.Max(latestEndTime, l.StartTime + l.CommandsDuration * l.TotalIterations); + + return latestEndTime; + } + } + + public bool HasCommands => TimelineGroup.HasCommands || Loops.Any(l => l.HasCommands); + + protected StoryboardElementWithDuration(string path, Anchor origin, Vector2 initialPosition) + { + Path = path; + Origin = origin; + InitialPosition = initialPosition; + } + + public abstract Drawable CreateDrawable(); + + public CommandLoop AddLoop(double startTime, int repeatCount) + { + var loop = new CommandLoop(startTime, repeatCount); + Loops.Add(loop); + return loop; + } + + public CommandTrigger AddTrigger(string triggerName, double startTime, double endTime, int groupNumber) + { + var trigger = new CommandTrigger(triggerName, startTime, endTime, groupNumber); + triggers.Add(trigger); + return trigger; + } + + protected IEnumerable.TypedCommand> GetCommands(CommandTimelineSelector timelineSelector, IEnumerable>? triggeredGroups) + { + var commands = TimelineGroup.GetCommands(timelineSelector); + foreach (var loop in Loops) + commands = commands.Concat(loop.GetCommands(timelineSelector)); + + if (triggeredGroups != null) + { + foreach (var pair in triggeredGroups) + commands = commands.Concat(pair.Item1.GetCommands(timelineSelector, pair.Item2)); + } + + return commands; + } + + public override string ToString() + => $"{Path}, {Origin}, {InitialPosition}"; + } + + public abstract class StoryboardElementWithDuration : StoryboardElementWithDuration + where U : Drawable, IDrawableStoryboardElement + { + private delegate void DrawablePropertyInitializer(U drawable, T value); + + protected StoryboardElementWithDuration(string path, Anchor origin, Vector2 initialPosition) + : base(path, origin, initialPosition) + { + } + + public override Drawable CreateDrawable() => CreateStoryboardDrawable(); + + public abstract U CreateStoryboardDrawable(); + + public void ApplyTransforms(U drawable, IEnumerable>? triggeredGroups = null) + { + // For performance reasons, we need to apply the commands in order by start time. Not doing so will cause many functions to be interleaved, resulting in O(n^2) complexity. + // To achieve this, commands are "generated" as pairs of (command, initFunc, transformFunc) and batched into a contiguous list + // The list is then stably-sorted (to preserve command order), and applied to the drawable sequentially. + + List> generated = new List>(); + + generateCommands(generated, GetCommands(g => g.X, triggeredGroups), (d, value) => d.X = value); + generateCommands(generated, GetCommands(g => g.Y, triggeredGroups), (d, value) => d.Y = value); + generateCommands(generated, GetCommands(g => g.Scale, triggeredGroups), (d, value) => d.Scale = value); + generateCommands(generated, GetCommands(g => g.Rotation, triggeredGroups), (d, value) => d.Rotation = value); + generateCommands(generated, GetCommands(g => g.Colour, triggeredGroups), (d, value) => d.Colour = value); + generateCommands(generated, GetCommands(g => g.Alpha, triggeredGroups), (d, value) => d.Alpha = value); + generateCommands(generated, GetCommands(g => g.BlendingParameters, triggeredGroups), (d, value) => d.Blending = value, false); + generateCommands(generated, GetCommands(g => g.VectorScale, triggeredGroups), (d, value) => d.VectorScale = value); + generateCommands(generated, GetCommands(g => g.FlipH, triggeredGroups), (d, value) => d.FlipH = value, false); + generateCommands(generated, GetCommands(g => g.FlipV, triggeredGroups), (d, value) => d.FlipV = value, false); + + foreach (var command in generated.OrderBy(g => g.StartTime)) + command.ApplyTo(drawable); + } + + private void generateCommands(List> resultList, IEnumerable.TypedCommand> commands, + DrawablePropertyInitializer initializeProperty, bool alwaysInitialize = true) + { + bool initialized = false; + + foreach (var command in commands) + { + DrawablePropertyInitializer? initFunc = null; + + if (!initialized) + { + if (alwaysInitialize || command.StartTime == command.EndTime) + initFunc = initializeProperty; + initialized = true; + } + + resultList.Add(new GeneratedCommand(command, initFunc)); + } + } + + private interface IGeneratedCommand + where TDrawable : U + { + double StartTime { get; } + + void ApplyTo(TDrawable drawable); + } + + private readonly struct GeneratedCommand : IGeneratedCommand + where TDrawable : U + { + public double StartTime => command.StartTime; + + private readonly DrawablePropertyInitializer? initializeProperty; + private readonly CommandTimeline.TypedCommand command; + + public GeneratedCommand(CommandTimeline.TypedCommand command, DrawablePropertyInitializer? initializeProperty) + { + this.command = command; + this.initializeProperty = initializeProperty; + } + + public void ApplyTo(TDrawable drawable) + { + initializeProperty?.Invoke(drawable, command.StartValue); + + using (drawable.BeginAbsoluteSequence(command.StartTime)) + transform(drawable); + } + + private void transform(TDrawable drawable) + { + if (command.IsParameterCommand) + { + if (command.LoopCount == 0) + { + drawable.TransformTo(command.PropertyName, command.StartValue).Delay(command.Duration) + .TransformTo(command.PropertyName, command.EndValue); + } + else + { + drawable.TransformTo(command.PropertyName, command.StartValue).Delay(command.Duration) + .TransformTo(command.PropertyName, command.EndValue) + .Loop(command.Delay, command.LoopCount); + } + } + else + { + if (command.LoopCount == 0) + { + drawable.TransformTo(command.PropertyName, command.StartValue).Then() + .TransformTo(command.PropertyName, command.EndValue, command.Duration, command.Easing); + } + else + { + drawable.TransformTo(command.PropertyName, command.StartValue).Then() + .TransformTo(command.PropertyName, command.EndValue, command.Duration, command.Easing) + .Loop(command.Delay, command.LoopCount); + } + } + } + } + } +} diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index 2c04e4c983..69994f77a4 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -1,308 +1,21 @@ // 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.Collections.Generic; -using System.Linq; using osu.Framework.Graphics; using osu.Game.Storyboards.Drawables; using osuTK; namespace osu.Game.Storyboards { - public class StoryboardSprite : IStoryboardElementWithDuration + public class StoryboardSprite : StoryboardElementWithDuration { - private readonly List loops = new List(); - private readonly List triggers = new List(); - - public string Path { get; } - public bool IsDrawable => HasCommands; - - public Anchor Origin; - public Vector2 InitialPosition; - - public readonly CommandTimelineGroup TimelineGroup = new CommandTimelineGroup(); - - public double StartTime - { - get - { - // To get the initial start time, we need to check whether the first alpha command to exist (across all loops) has a StartValue of zero. - // A StartValue of zero governs, above all else, the first valid display time of a sprite. - // - // You can imagine that the first command of each type decides that type's start value, so if the initial alpha is zero, - // anything before that point can be ignored (the sprite is not visible after all). - var alphaCommands = new List<(double startTime, bool isZeroStartValue)>(); - - var command = TimelineGroup.Alpha.Commands.FirstOrDefault(); - if (command != null) alphaCommands.Add((command.StartTime, command.StartValue == 0)); - - foreach (var loop in loops) - { - command = loop.Alpha.Commands.FirstOrDefault(); - if (command != null) alphaCommands.Add((command.StartTime + loop.LoopStartTime, command.StartValue == 0)); - } - - if (alphaCommands.Count > 0) - { - var firstAlpha = alphaCommands.MinBy(t => t.startTime); - - if (firstAlpha.isZeroStartValue) - return firstAlpha.startTime; - } - - return EarliestTransformTime; - } - } - - public double EarliestTransformTime - { - get - { - // If we got to this point, either no alpha commands were present, or the earliest had a non-zero start value. - // The sprite's StartTime will be determined by the earliest command, regardless of type. - double earliestStartTime = TimelineGroup.StartTime; - foreach (var l in loops) - earliestStartTime = Math.Min(earliestStartTime, l.StartTime); - return earliestStartTime; - } - } - - public double EndTime - { - get - { - double latestEndTime = TimelineGroup.EndTime; - - foreach (var l in loops) - latestEndTime = Math.Max(latestEndTime, l.EndTime); - - return latestEndTime; - } - } - - public double EndTimeForDisplay - { - get - { - double latestEndTime = TimelineGroup.EndTime; - - foreach (var l in loops) - latestEndTime = Math.Max(latestEndTime, l.StartTime + l.CommandsDuration * l.TotalIterations); - - return latestEndTime; - } - } - - public bool HasCommands => TimelineGroup.HasCommands || loops.Any(l => l.HasCommands); - - private delegate void DrawablePropertyInitializer(Drawable drawable, T value); - public StoryboardSprite(string path, Anchor origin, Vector2 initialPosition) + : base(path, origin, initialPosition) { - Path = path; - Origin = origin; - InitialPosition = initialPosition; } - public CommandLoop AddLoop(double startTime, int repeatCount) - { - var loop = new CommandLoop(startTime, repeatCount); - loops.Add(loop); - return loop; - } - - public CommandTrigger AddTrigger(string triggerName, double startTime, double endTime, int groupNumber) - { - var trigger = new CommandTrigger(triggerName, startTime, endTime, groupNumber); - triggers.Add(trigger); - return trigger; - } - - public virtual Drawable CreateDrawable() - => new DrawableStoryboardSprite(this); - - public void ApplyTransforms(Drawable drawable, IEnumerable>? triggeredGroups = null) - { - // For performance reasons, we need to apply the commands in order by start time. Not doing so will cause many functions to be interleaved, resulting in O(n^2) complexity. - // To achieve this, commands are "generated" as pairs of (command, initFunc, transformFunc) and batched into a contiguous list - // The list is then stably-sorted (to preserve command order), and applied to the drawable sequentially. - - List generated = new List(); - - generateCommands(generated, getCommands(g => g.X, triggeredGroups), (d, value) => d.X = value); - generateCommands(generated, getCommands(g => g.Y, triggeredGroups), (d, value) => d.Y = value); - generateCommands(generated, getCommands(g => g.Scale, triggeredGroups), (d, value) => d.Scale = value); - generateCommands(generated, getCommands(g => g.Rotation, triggeredGroups), (d, value) => d.Rotation = value); - generateCommands(generated, getCommands(g => g.Colour, triggeredGroups), (d, value) => d.Colour = value); - generateCommands(generated, getCommands(g => g.Alpha, triggeredGroups), (d, value) => d.Alpha = value); - generateCommands(generated, getCommands(g => g.BlendingParameters, triggeredGroups), (d, value) => d.Blending = value, false); - - if (drawable is IVectorScalable vectorScalable) - { - generateCommands(generated, getCommands(g => g.VectorScale, triggeredGroups), (_, value) => vectorScalable.VectorScale = value); - } - - if (drawable is IFlippable flippable) - { - generateCommands(generated, getCommands(g => g.FlipH, triggeredGroups), (_, value) => flippable.FlipH = value, false); - generateCommands(generated, getCommands(g => g.FlipV, triggeredGroups), (_, value) => flippable.FlipV = value, false); - } - - foreach (var command in generated.OrderBy(g => g.StartTime)) - command.ApplyTo(drawable); - } - - private void generateCommands(List resultList, IEnumerable.TypedCommand> commands, - DrawablePropertyInitializer initializeProperty, bool alwaysInitialize = true) - { - bool initialized = false; - - foreach (var command in commands) - { - DrawablePropertyInitializer? initFunc = null; - - if (!initialized) - { - if (alwaysInitialize || command.StartTime == command.EndTime) - initFunc = initializeProperty; - initialized = true; - } - - resultList.Add(new GeneratedCommand(command, initFunc)); - } - } - - private IEnumerable.TypedCommand> getCommands(CommandTimelineSelector timelineSelector, IEnumerable>? triggeredGroups) - { - var commands = TimelineGroup.GetCommands(timelineSelector); - foreach (var loop in loops) - commands = commands.Concat(loop.GetCommands(timelineSelector)); - - if (triggeredGroups != null) - { - foreach (var pair in triggeredGroups) - commands = commands.Concat(pair.Item1.GetCommands(timelineSelector, pair.Item2)); - } - - return commands; - } - - public override string ToString() - => $"{Path}, {Origin}, {InitialPosition}"; - - private interface IGeneratedCommand - { - double StartTime { get; } - - void ApplyTo(Drawable drawable); - } - - private readonly struct GeneratedCommand : IGeneratedCommand - { - public double StartTime => command.StartTime; - - private readonly DrawablePropertyInitializer? initializeProperty; - private readonly CommandTimeline.TypedCommand command; - - public GeneratedCommand(CommandTimeline.TypedCommand command, DrawablePropertyInitializer? initializeProperty) - { - this.command = command; - this.initializeProperty = initializeProperty; - } - - public void ApplyTo(Drawable drawable) - { - initializeProperty?.Invoke(drawable, command.StartValue); - - switch (command.PropertyName) - { - case "VectorScale": - if (command.LoopCount == 0) - { - using (drawable.BeginAbsoluteSequence(command.StartTime)) - { - ((IVectorScalable)drawable).TransformTo(command.PropertyName, command.StartValue).Then() - .TransformTo(command.PropertyName, command.EndValue, command.Duration, command.Easing); - } - } - else - { - using (drawable.BeginAbsoluteSequence(command.StartTime)) - { - ((IVectorScalable)drawable).TransformTo(command.PropertyName, command.StartValue).Then() - .TransformTo(command.PropertyName, command.EndValue, command.Duration, command.Easing) - .Loop(command.Delay, command.LoopCount); - } - } - - break; - - case "FlipH": - case "FlipV": - if (command.LoopCount == 0) - { - using (drawable.BeginAbsoluteSequence(command.StartTime)) - { - ((IFlippable)drawable).TransformTo(command.PropertyName, command.StartValue).Delay(command.Duration) - .TransformTo(command.PropertyName, command.EndValue); - } - } - else - { - using (drawable.BeginAbsoluteSequence(command.StartTime)) - { - ((IFlippable)drawable).TransformTo(command.PropertyName, command.StartValue).Delay(command.Duration) - .TransformTo(command.PropertyName, command.EndValue) - .Loop(command.Delay, command.LoopCount); - } - } - - break; - - case "Blending": - if (command.LoopCount == 0) - { - using (drawable.BeginAbsoluteSequence(command.StartTime)) - { - drawable.TransformTo(command.PropertyName, command.StartValue).Delay(command.Duration) - .TransformTo(command.PropertyName, command.EndValue); - } - } - else - { - using (drawable.BeginAbsoluteSequence(command.StartTime)) - { - drawable.TransformTo(command.PropertyName, command.StartValue).Delay(command.Duration) - .TransformTo(command.PropertyName, command.EndValue) - .Loop(command.Delay, command.LoopCount); - } - } - - break; - - default: - if (command.LoopCount == 0) - { - using (drawable.BeginAbsoluteSequence(command.StartTime)) - { - drawable.TransformTo(command.PropertyName, command.StartValue).Then() - .TransformTo(command.PropertyName, command.EndValue, command.Duration, command.Easing); - } - } - else - { - using (drawable.BeginAbsoluteSequence(command.StartTime)) - { - drawable.TransformTo(command.PropertyName, command.StartValue).Then() - .TransformTo(command.PropertyName, command.EndValue, command.Duration, command.Easing) - .Loop(command.Delay, command.LoopCount); - } - } - - break; - } - } - } + public override DrawableStoryboardSprite CreateStoryboardDrawable() => new DrawableStoryboardSprite(this); } } + + From 1c8ede854d5ba97deafeea83abee96c61cb367bb Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 6 Mar 2024 00:17:56 +0300 Subject: [PATCH 3360/4092] Remove blank lines --- osu.Game/Storyboards/StoryboardSprite.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index 69994f77a4..8eaab9428d 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -17,5 +17,3 @@ namespace osu.Game.Storyboards public override DrawableStoryboardSprite CreateStoryboardDrawable() => new DrawableStoryboardSprite(this); } } - - From 07392a4d3eb7ff9c08c863d0aaf27cb33161dac7 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 6 Mar 2024 01:10:22 +0300 Subject: [PATCH 3361/4092] Further simplify transform application --- .../StoryboardElementWithDuration.cs | 37 +++---------------- 1 file changed, 6 insertions(+), 31 deletions(-) diff --git a/osu.Game/Storyboards/StoryboardElementWithDuration.cs b/osu.Game/Storyboards/StoryboardElementWithDuration.cs index 0c89b40c36..06924a26ef 100644 --- a/osu.Game/Storyboards/StoryboardElementWithDuration.cs +++ b/osu.Game/Storyboards/StoryboardElementWithDuration.cs @@ -222,38 +222,13 @@ namespace osu.Game.Storyboards initializeProperty?.Invoke(drawable, command.StartValue); using (drawable.BeginAbsoluteSequence(command.StartTime)) - transform(drawable); - } + { + var sequence = command.IsParameterCommand + ? drawable.TransformTo(command.PropertyName, command.StartValue).Delay(command.Duration).TransformTo(command.PropertyName, command.EndValue) + : drawable.TransformTo(command.PropertyName, command.StartValue).Then().TransformTo(command.PropertyName, command.EndValue, command.Duration, command.Easing); - private void transform(TDrawable drawable) - { - if (command.IsParameterCommand) - { - if (command.LoopCount == 0) - { - drawable.TransformTo(command.PropertyName, command.StartValue).Delay(command.Duration) - .TransformTo(command.PropertyName, command.EndValue); - } - else - { - drawable.TransformTo(command.PropertyName, command.StartValue).Delay(command.Duration) - .TransformTo(command.PropertyName, command.EndValue) - .Loop(command.Delay, command.LoopCount); - } - } - else - { - if (command.LoopCount == 0) - { - drawable.TransformTo(command.PropertyName, command.StartValue).Then() - .TransformTo(command.PropertyName, command.EndValue, command.Duration, command.Easing); - } - else - { - drawable.TransformTo(command.PropertyName, command.StartValue).Then() - .TransformTo(command.PropertyName, command.EndValue, command.Duration, command.Easing) - .Loop(command.Delay, command.LoopCount); - } + if (command.LoopCount > 0) + sequence.Loop(command.Delay, command.LoopCount); } } } From b53777c2a42bab857664cb5c2887e5cced0c0625 Mon Sep 17 00:00:00 2001 From: jvyden Date: Tue, 5 Mar 2024 18:15:53 -0500 Subject: [PATCH 3362/4092] Refactor room secret handling to use JSON Also log room secrets for debugging purposes --- osu.Desktop/DiscordRichPresence.cs | 41 ++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 85b6129043..7315ee0c17 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -5,6 +5,7 @@ using System; using System.Text; using DiscordRPC; using DiscordRPC.Message; +using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -147,7 +148,13 @@ namespace osu.Desktop Size = room.Users.Count, }; - presence.Secrets.JoinSecret = $"{room.RoomID}:{room.Settings.Password}"; + RoomSecret roomSecret = new RoomSecret + { + RoomID = room.RoomID, + Password = room.Settings.Password, + }; + + presence.Secrets.JoinSecret = JsonConvert.SerializeObject(roomSecret); } else { @@ -182,9 +189,13 @@ namespace osu.Desktop private void onJoin(object sender, JoinMessage args) { game.Window?.Raise(); + Logger.Log($"Received room secret from Discord RPC Client: {args.Secret}", LoggingTarget.Network, LogLevel.Debug); if (!tryParseRoomSecret(args.Secret, out long roomId, out string? password)) + { Logger.Log("Could not parse room from Discord RPC Client", LoggingTarget.Network, LogLevel.Important); + return; + } var request = new GetRoomRequest(roomId); request.Success += room => Schedule(() => @@ -216,21 +227,26 @@ namespace osu.Desktop }); } - private static bool tryParseRoomSecret(ReadOnlySpan secret, out long roomId, out string? password) + private static bool tryParseRoomSecret(string secretJson, out long roomId, out string? password) { roomId = 0; password = null; - int roomSecretSplitIndex = secret.IndexOf(':'); + RoomSecret? roomSecret; - if (roomSecretSplitIndex == -1) + try + { + roomSecret = JsonConvert.DeserializeObject(secretJson); + } + catch + { return false; + } - if (!long.TryParse(secret[..roomSecretSplitIndex], out roomId)) - return false; + if (roomSecret == null) return false; - password = secret[(roomSecretSplitIndex + 1)..].ToString(); - if (password.Length == 0) password = null; + roomId = roomSecret.RoomID; + password = roomSecret.Password; return true; } @@ -254,5 +270,14 @@ namespace osu.Desktop client.Dispose(); base.Dispose(isDisposing); } + + private class RoomSecret + { + [JsonProperty(@"roomId", Required = Required.Always)] + public long RoomID { get; set; } + + [JsonProperty(@"password", Required = Required.AllowNull)] + public string? Password { get; set; } + } } } From 98713003176da6b09bafeea7392564959098956b Mon Sep 17 00:00:00 2001 From: Jayden Date: Tue, 5 Mar 2024 18:22:39 -0500 Subject: [PATCH 3363/4092] Improve language of user-facing errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Desktop/DiscordRichPresence.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 7315ee0c17..8fecd015d4 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -193,7 +193,7 @@ namespace osu.Desktop if (!tryParseRoomSecret(args.Secret, out long roomId, out string? password)) { - Logger.Log("Could not parse room from Discord RPC Client", LoggingTarget.Network, LogLevel.Important); + Logger.Log("Could not join multiplayer room.", LoggingTarget.Network, LogLevel.Important); return; } From 6455c0583b5e607baeca7f584410bc63515aa619 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Mar 2024 10:19:40 +0800 Subject: [PATCH 3364/4092] Update usage of `CircularProgress.Current` --- .../Skinning/Argon/ArgonSpinnerProgressArc.cs | 6 +++--- .../Skinning/Argon/ArgonSpinnerRingArc.cs | 6 +++--- osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs | 8 ++++++-- osu.Game/Overlays/Volume/VolumeMeter.cs | 6 +++--- .../Edit/Compose/Components/CircularDistanceSnapGrid.cs | 2 +- osu.Game/Screens/Edit/Timing/TapButton.cs | 6 +++--- osu.Game/Screens/Play/HUD/HoldForMenuButton.cs | 7 ++++++- .../Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs | 6 +++--- .../Screens/Ranking/Expanded/Accuracy/GradedCircles.cs | 2 +- osu.Game/Skinning/LegacySongProgress.cs | 4 ++-- 10 files changed, 31 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerProgressArc.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerProgressArc.cs index 76afeeb2c4..1de5b1f309 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerProgressArc.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerProgressArc.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon Origin = Anchor.Centre, Colour = Color4.White.Opacity(0.25f), RelativeSizeAxes = Axes.Both, - Current = { Value = arc_fill }, + Progress = arc_fill, Rotation = 90 - arc_fill * 180, InnerRadius = arc_radius, RoundedCaps = true, @@ -71,9 +71,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon background.Alpha = spinner.Progress >= 1 ? 0 : 1; fill.Alpha = (float)Interpolation.DampContinuously(fill.Alpha, spinner.Progress > 0 && spinner.Progress < 1 ? 1 : 0, 40f, (float)Math.Abs(Time.Elapsed)); - fill.Current.Value = (float)Interpolation.DampContinuously(fill.Current.Value, spinner.Progress >= 1 ? 0 : arc_fill * spinner.Progress, 40f, (float)Math.Abs(Time.Elapsed)); + fill.Progress = (float)Interpolation.DampContinuously(fill.Progress, spinner.Progress >= 1 ? 0 : arc_fill * spinner.Progress, 40f, (float)Math.Abs(Time.Elapsed)); - fill.Rotation = (float)(90 - fill.Current.Value * 180); + fill.Rotation = (float)(90 - fill.Progress * 180); } private partial class ProgressFill : CircularProgress diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerRingArc.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerRingArc.cs index 702c5c2675..12cd0994b4 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerRingArc.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerRingArc.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Current = { Value = arc_fill }, + Progress = arc_fill, Rotation = -arc_fill * 180, InnerRadius = arc_radius, RoundedCaps = true, @@ -44,10 +44,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon { base.Update(); - fill.Current.Value = (float)Interpolation.DampContinuously(fill.Current.Value, spinner.Progress >= 1 ? arc_fill_complete : arc_fill, 40f, (float)Math.Abs(Time.Elapsed)); + fill.Progress = (float)Interpolation.DampContinuously(fill.Progress, spinner.Progress >= 1 ? arc_fill_complete : arc_fill, 40f, (float)Math.Abs(Time.Elapsed)); fill.InnerRadius = (float)Interpolation.DampContinuously(fill.InnerRadius, spinner.Progress >= 1 ? arc_radius * 2.2f : arc_radius, 40f, (float)Math.Abs(Time.Elapsed)); - fill.Rotation = (float)(-fill.Current.Value * 180); + fill.Rotation = (float)(-fill.Progress * 180); } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs index 5a26a988fb..cd498c474a 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs @@ -86,11 +86,15 @@ namespace osu.Game.Beatmaps.Drawables.Cards Dimmed.BindValueChanged(_ => updateState()); playButton.Playing.BindValueChanged(_ => updateState(), true); - ((IBindable)progress.Current).BindTo(playButton.Progress); - FinishTransforms(true); } + protected override void Update() + { + base.Update(); + progress.Progress = playButton.Progress.Value; + } + private void updateState() { bool shouldDim = Dimmed.Value || playButton.Playing.Value; diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 6ec4971f06..e96cd0fa46 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -235,7 +235,7 @@ namespace osu.Game.Overlays.Volume Bindable.BindValueChanged(volume => { this.TransformTo(nameof(DisplayVolume), volume.NewValue, 400, Easing.OutQuint); }, true); - bgProgress.Current.Value = 0.75f; + bgProgress.Progress = 0.75f; } private int? displayVolumeInt; @@ -265,8 +265,8 @@ namespace osu.Game.Overlays.Volume text.Text = intValue.ToString(CultureInfo.CurrentCulture); } - volumeCircle.Current.Value = displayVolume * 0.75f; - volumeCircleGlow.Current.Value = displayVolume * 0.75f; + volumeCircle.Progress = displayVolume * 0.75f; + volumeCircleGlow.Progress = displayVolume * 0.75f; if (intVolumeChanged && IsLoaded) Scheduler.AddOnce(playTickSound); diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index e33ef66007..92fe52148c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -140,7 +140,7 @@ namespace osu.Game.Screens.Edit.Compose.Components Colour = this.baseColour = baseColour; - Current.Value = 1; + Progress = 1; } protected override void Update() diff --git a/osu.Game/Screens/Edit/Timing/TapButton.cs b/osu.Game/Screens/Edit/Timing/TapButton.cs index fd60fb1b5b..d2ae0e76cf 100644 --- a/osu.Game/Screens/Edit/Timing/TapButton.cs +++ b/osu.Game/Screens/Edit/Timing/TapButton.cs @@ -366,7 +366,7 @@ namespace osu.Game.Screens.Edit.Timing new CircularProgress { RelativeSizeAxes = Axes.Both, - Current = { Value = 1f / light_count - angular_light_gap }, + Progress = 1f / light_count - angular_light_gap, Colour = colourProvider.Background2, }, fillContent = new Container @@ -379,7 +379,7 @@ namespace osu.Game.Screens.Edit.Timing new CircularProgress { RelativeSizeAxes = Axes.Both, - Current = { Value = 1f / light_count - angular_light_gap }, + Progress = 1f / light_count - angular_light_gap, Blending = BlendingParameters.Additive }, // Please do not try and make sense of this. @@ -388,7 +388,7 @@ namespace osu.Game.Screens.Edit.Timing Glow = new CircularProgress { RelativeSizeAxes = Axes.Both, - Current = { Value = 1f / light_count - 0.01f }, + Progress = 1f / light_count - 0.01f, Blending = BlendingParameters.Additive }.WithEffect(new GlowEffect { diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index a260156595..6d045e5f01 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -198,9 +198,14 @@ namespace osu.Game.Screens.Play.HUD bind(); } + protected override void Update() + { + base.Update(); + circularProgress.Progress = Progress.Value; + } + private void bind() { - ((IBindable)circularProgress.Current).BindTo(Progress); Progress.ValueChanged += progress => { icon.Scale = new Vector2(1 + (float)progress.NewValue * 0.2f); diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 83b02a0951..2231346404 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -147,7 +147,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy Colour = OsuColour.Gray(47), Alpha = 0.5f, InnerRadius = accuracy_circle_radius + 0.01f, // Extends a little bit into the circle - Current = { Value = 1 }, + Progress = 1, }, accuracyCircle = new CircularProgress { @@ -268,7 +268,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy if (targetAccuracy < 1 && targetAccuracy >= visual_alignment_offset) targetAccuracy -= visual_alignment_offset; - accuracyCircle.FillTo(targetAccuracy, ACCURACY_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING); + accuracyCircle.ProgressTo(targetAccuracy, ACCURACY_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING); if (withFlair) { @@ -359,7 +359,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy .FadeOut(800, Easing.Out); accuracyCircle - .FillTo(accuracyS - GRADE_SPACING_PERCENTAGE / 2 - visual_alignment_offset, 70, Easing.OutQuint); + .ProgressTo(accuracyS - GRADE_SPACING_PERCENTAGE / 2 - visual_alignment_offset, 70, Easing.OutQuint); badges.Single(b => b.Rank == getRank(ScoreRank.S)) .FadeOut(70, Easing.OutQuint); diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs index 33b71c53a7..633ed6d92e 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs @@ -67,7 +67,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy { public double RevealProgress { - set => Current.Value = Math.Clamp(value, startProgress, endProgress) - startProgress; + set => Progress = Math.Clamp(value, startProgress, endProgress) - startProgress; } private readonly double startProgress; diff --git a/osu.Game/Skinning/LegacySongProgress.cs b/osu.Game/Skinning/LegacySongProgress.cs index 4295060a3a..9af82c4992 100644 --- a/osu.Game/Skinning/LegacySongProgress.cs +++ b/osu.Game/Skinning/LegacySongProgress.cs @@ -72,14 +72,14 @@ namespace osu.Game.Skinning circularProgress.Scale = new Vector2(-1, 1); circularProgress.Anchor = Anchor.TopRight; circularProgress.Colour = new Colour4(199, 255, 47, 153); - circularProgress.Current.Value = 1 - progress; + circularProgress.Progress = 1 - progress; } else { circularProgress.Scale = new Vector2(1); circularProgress.Anchor = Anchor.TopLeft; circularProgress.Colour = new Colour4(255, 255, 255, 153); - circularProgress.Current.Value = progress; + circularProgress.Progress = progress; } } } From b53b752e543d563b1059cc66d6dd8c6077bb6e01 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Mar 2024 10:42:20 +0800 Subject: [PATCH 3365/4092] Update usage of `MathUtils` --- osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 2 +- .../Objects/Drawables/DrawableSliderRepeat.cs | 2 +- osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs | 2 +- .../Skinning/Default/SpinnerRotationTracker.cs | 2 +- osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs | 3 +-- osu.Game/Graphics/Cursor/MenuCursorContainer.cs | 2 +- osu.Game/Graphics/UserInterface/OsuNumberBox.cs | 4 +--- osu.Game/IO/Archives/ZipArchiveReader.cs | 3 +-- .../Overlays/Settings/Sections/Input/TabletAreaSelection.cs | 3 +-- osu.Game/Overlays/Settings/SettingsNumberBox.cs | 2 +- osu.Game/Screens/Menu/LogoVisualisation.cs | 4 ++-- osu.Game/Utils/GeometryUtils.cs | 5 ++--- 13 files changed, 15 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index df9544b71e..992f4d5f03 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Mods // multiply the SPM by 1.01 to ensure that the spinner is completed. if the calculation is left exact, // some spinners may not complete due to very minor decimal loss during calculation float rotationSpeed = (float)(1.01 * spinner.HitObject.SpinsRequired / spinner.HitObject.Duration); - spinner.RotationTracker.AddRotation(MathUtils.RadiansToDegrees((float)rateIndependentElapsedTime * rotationSpeed * MathF.PI * 2.0f)); + spinner.RotationTracker.AddRotation(float.RadiansToDegrees((float)rateIndependentElapsedTime * rotationSpeed * MathF.PI * 2.0f)); } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index 3239565528..fcbd0edfe0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -146,7 +146,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables break; } - float aimRotation = MathUtils.RadiansToDegrees(MathF.Atan2(aimRotationVector.Y - Position.Y, aimRotationVector.X - Position.X)); + float aimRotation = float.RadiansToDegrees(MathF.Atan2(aimRotationVector.Y - Position.Y, aimRotationVector.X - Position.X)); while (Math.Abs(aimRotation - Arrow.Rotation) > 180) aimRotation += aimRotation < Arrow.Rotation ? 360 : -360; diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 1cf6bc91f0..d43e6092c2 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -342,7 +342,7 @@ namespace osu.Game.Rulesets.Osu.Replays // 0.05 rad/ms, or ~477 RPM, as per stable. // the redundant conversion from RPM to rad/ms is here for ease of testing custom SPM specs. const float spin_rpm = 0.05f / (2 * MathF.PI) * 60000; - float radsPerMillisecond = MathUtils.DegreesToRadians(spin_rpm * 360) / 60000; + float radsPerMillisecond = float.DegreesToRadians(spin_rpm * 360) / 60000; switch (h) { diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerRotationTracker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerRotationTracker.cs index 1d75663fd9..7e97f826f9 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerRotationTracker.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerRotationTracker.cs @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default if (mousePosition is Vector2 pos) { - float thisAngle = -MathUtils.RadiansToDegrees(MathF.Atan2(pos.X - DrawSize.X / 2, pos.Y - DrawSize.Y / 2)); + float thisAngle = -float.RadiansToDegrees(MathF.Atan2(pos.X - DrawSize.X / 2, pos.Y - DrawSize.Y / 2)); float delta = lastAngle == null ? 0 : thisAngle - lastAngle.Value; // Normalise the delta to -180 .. 180 diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index f9d4a3b325..4b3b543ea4 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -246,7 +246,7 @@ namespace osu.Game.Rulesets.Osu.Statistics // Likewise sin(pi/2)=1 and sin(3pi/2)=-1, whereas we actually want these values to appear on the bottom/top respectively, so the y-coordinate also needs to be inverted. // // We also need to apply the anti-clockwise rotation. - double rotatedAngle = finalAngle - MathUtils.DegreesToRadians(rotation); + double rotatedAngle = finalAngle - float.DegreesToRadians(rotation); var rotatedCoordinate = -1 * new Vector2((float)Math.Cos(rotatedAngle), (float)Math.Sin(rotatedAngle)); Vector2 localCentre = new Vector2(points_per_dimension - 1) / 2; diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index cf4700bf85..6689f087cb 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using osu.Framework.Graphics; -using osu.Framework.Utils; using osu.Game.Beatmaps.Legacy; using osu.Game.IO; using osu.Game.Storyboards; @@ -230,7 +229,7 @@ namespace osu.Game.Beatmaps.Formats { float startValue = Parsing.ParseFloat(split[4]); float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue; - timelineGroup?.Rotation.Add(easing, startTime, endTime, MathUtils.RadiansToDegrees(startValue), MathUtils.RadiansToDegrees(endValue)); + timelineGroup?.Rotation.Add(easing, startTime, endTime, float.RadiansToDegrees(startValue), float.RadiansToDegrees(endValue)); break; } diff --git a/osu.Game/Graphics/Cursor/MenuCursorContainer.cs b/osu.Game/Graphics/Cursor/MenuCursorContainer.cs index 7e42d45191..696ea62b42 100644 --- a/osu.Game/Graphics/Cursor/MenuCursorContainer.cs +++ b/osu.Game/Graphics/Cursor/MenuCursorContainer.cs @@ -157,7 +157,7 @@ namespace osu.Game.Graphics.Cursor if (dragRotationState == DragRotationState.Rotating && distance > 0) { Vector2 offset = e.MousePosition - positionMouseDown; - float degrees = MathUtils.RadiansToDegrees(MathF.Atan2(-offset.X, offset.Y)) + 24.3f; + float degrees = float.RadiansToDegrees(MathF.Atan2(-offset.X, offset.Y)) + 24.3f; // Always rotate in the direction of least distance float diff = (degrees - activeCursor.Rotation) % 360; diff --git a/osu.Game/Graphics/UserInterface/OsuNumberBox.cs b/osu.Game/Graphics/UserInterface/OsuNumberBox.cs index df92863797..e9b28f4771 100644 --- a/osu.Game/Graphics/UserInterface/OsuNumberBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuNumberBox.cs @@ -1,14 +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.Extensions; - namespace osu.Game.Graphics.UserInterface { public partial class OsuNumberBox : OsuTextBox { protected override bool AllowIme => false; - protected override bool CanAddCharacter(char character) => character.IsAsciiDigit(); + protected override bool CanAddCharacter(char character) => char.IsAsciiDigit(character); } } diff --git a/osu.Game/IO/Archives/ZipArchiveReader.cs b/osu.Game/IO/Archives/ZipArchiveReader.cs index 5ef03b3641..7d7ce858dd 100644 --- a/osu.Game/IO/Archives/ZipArchiveReader.cs +++ b/osu.Game/IO/Archives/ZipArchiveReader.cs @@ -8,7 +8,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.Toolkit.HighPerformance; -using osu.Framework.Extensions; using osu.Framework.IO.Stores; using SharpCompress.Archives.Zip; using SixLabors.ImageSharp.Memory; @@ -36,7 +35,7 @@ namespace osu.Game.IO.Archives var owner = MemoryAllocator.Default.Allocate((int)entry.Size); using (Stream s = entry.OpenEntryStream()) - s.ReadToFill(owner.Memory.Span); + s.ReadExactly(owner.Memory.Span); return new MemoryOwnerMemoryStream(owner); } diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index 686002fe71..33f4f49173 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Input.Handlers.Tablet; -using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK; @@ -196,7 +195,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input var matrix = Matrix3.Identity; MatrixExtensions.TranslateFromLeft(ref matrix, offset); - MatrixExtensions.RotateFromLeft(ref matrix, MathUtils.DegreesToRadians(rotation.Value)); + MatrixExtensions.RotateFromLeft(ref matrix, float.DegreesToRadians(rotation.Value)); usableAreaQuad *= matrix; diff --git a/osu.Game/Overlays/Settings/SettingsNumberBox.cs b/osu.Game/Overlays/Settings/SettingsNumberBox.cs index a0f85eda31..cdf648fc5f 100644 --- a/osu.Game/Overlays/Settings/SettingsNumberBox.cs +++ b/osu.Game/Overlays/Settings/SettingsNumberBox.cs @@ -69,7 +69,7 @@ namespace osu.Game.Overlays.Settings { protected override bool AllowIme => false; - protected override bool CanAddCharacter(char character) => character.IsAsciiDigit(); + protected override bool CanAddCharacter(char character) => char.IsAsciiDigit(character); public new void NotifyInputError() => base.NotifyInputError(); } diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index b722b83280..c47ce91711 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -209,13 +209,13 @@ namespace osu.Game.Screens.Menu if (audioData[i] < amplitude_dead_zone) continue; - float rotation = MathUtils.DegreesToRadians(i / (float)bars_per_visualiser * 360 + j * 360 / visualiser_rounds); + float rotation = float.DegreesToRadians(i / (float)bars_per_visualiser * 360 + j * 360 / visualiser_rounds); float rotationCos = MathF.Cos(rotation); float rotationSin = MathF.Sin(rotation); // taking the cos and sin to the 0..1 range var barPosition = new Vector2(rotationCos / 2 + 0.5f, rotationSin / 2 + 0.5f) * size; - var barSize = new Vector2(size * MathF.Sqrt(2 * (1 - MathF.Cos(MathUtils.DegreesToRadians(360f / bars_per_visualiser)))) / 2f, bar_length * audioData[i]); + var barSize = new Vector2(size * MathF.Sqrt(2 * (1 - MathF.Cos(float.DegreesToRadians(360f / bars_per_visualiser)))) / 2f, bar_length * audioData[i]); // The distance between the position and the sides of the bar. var bottomOffset = new Vector2(-rotationSin * barSize.X / 2, rotationCos * barSize.X / 2); // The distance between the bottom side of the bar and the top side. diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index 725e93d098..dbeba4dfc1 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; -using osu.Framework.Utils; using osu.Game.Rulesets.Objects.Types; using osuTK; @@ -28,8 +27,8 @@ namespace osu.Game.Utils point.Y -= origin.Y; Vector2 ret; - ret.X = point.X * MathF.Cos(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Sin(MathUtils.DegreesToRadians(angle)); - ret.Y = point.X * -MathF.Sin(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Cos(MathUtils.DegreesToRadians(angle)); + ret.X = point.X * MathF.Cos(float.DegreesToRadians(angle)) + point.Y * MathF.Sin(float.DegreesToRadians(angle)); + ret.Y = point.X * -MathF.Sin(float.DegreesToRadians(angle)) + point.Y * MathF.Cos(float.DegreesToRadians(angle)); ret.X += origin.X; ret.Y += origin.Y; From 0696e7de524123d1b332a166c4cd81f5d6c24c15 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Mar 2024 10:42:42 +0800 Subject: [PATCH 3366/4092] Update ImageSharp usages --- osu.Game.Tests/Visual/Gameplay/TestScenePlayerMaxDimensions.cs | 2 +- osu.Game/Beatmaps/BeatmapPanelBackgroundTextureLoaderStore.cs | 2 +- osu.Game/Skinning/LegacyTextureLoaderStore.cs | 2 +- osu.Game/Skinning/MaxDimensionLimitedTextureLoaderStore.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerMaxDimensions.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerMaxDimensions.cs index 53a4abdd07..3f78dedec5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerMaxDimensions.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerMaxDimensions.cs @@ -121,7 +121,7 @@ namespace osu.Game.Tests.Visual.Gameplay private TextureUpload upscale(TextureUpload textureUpload) { - var image = Image.LoadPixelData(textureUpload.Data.ToArray(), textureUpload.Width, textureUpload.Height); + var image = Image.LoadPixelData(textureUpload.Data, textureUpload.Width, textureUpload.Height); // The original texture upload will no longer be returned or used. textureUpload.Dispose(); diff --git a/osu.Game/Beatmaps/BeatmapPanelBackgroundTextureLoaderStore.cs b/osu.Game/Beatmaps/BeatmapPanelBackgroundTextureLoaderStore.cs index 128e100e4b..cf58ae73fe 100644 --- a/osu.Game/Beatmaps/BeatmapPanelBackgroundTextureLoaderStore.cs +++ b/osu.Game/Beatmaps/BeatmapPanelBackgroundTextureLoaderStore.cs @@ -64,7 +64,7 @@ namespace osu.Game.Beatmaps // The original texture upload will no longer be returned or used. textureUpload.Dispose(); - Size size = image.Size(); + Size size = image.Size; // Assume that panel backgrounds are always displayed using `FillMode.Fill`. // Also assume that all backgrounds are wider than they are tall, so the diff --git a/osu.Game/Skinning/LegacyTextureLoaderStore.cs b/osu.Game/Skinning/LegacyTextureLoaderStore.cs index 29206bbb85..5045374c14 100644 --- a/osu.Game/Skinning/LegacyTextureLoaderStore.cs +++ b/osu.Game/Skinning/LegacyTextureLoaderStore.cs @@ -73,7 +73,7 @@ namespace osu.Game.Skinning private TextureUpload convertToGrayscale(TextureUpload textureUpload) { - var image = Image.LoadPixelData(textureUpload.Data.ToArray(), textureUpload.Width, textureUpload.Height); + var image = Image.LoadPixelData(textureUpload.Data, textureUpload.Width, textureUpload.Height); // stable uses `0.299 * r + 0.587 * g + 0.114 * b` // (https://github.com/peppy/osu-stable-reference/blob/013c3010a9d495e3471a9c59518de17006f9ad89/osu!/Graphics/Textures/pTexture.cs#L138-L153) diff --git a/osu.Game/Skinning/MaxDimensionLimitedTextureLoaderStore.cs b/osu.Game/Skinning/MaxDimensionLimitedTextureLoaderStore.cs index f15097a169..58dadbe753 100644 --- a/osu.Game/Skinning/MaxDimensionLimitedTextureLoaderStore.cs +++ b/osu.Game/Skinning/MaxDimensionLimitedTextureLoaderStore.cs @@ -61,7 +61,7 @@ namespace osu.Game.Skinning if (textureUpload.Height > max_supported_texture_size || textureUpload.Width > max_supported_texture_size) { - var image = Image.LoadPixelData(textureUpload.Data.ToArray(), textureUpload.Width, textureUpload.Height); + var image = Image.LoadPixelData(textureUpload.Data, textureUpload.Width, textureUpload.Height); // The original texture upload will no longer be returned or used. textureUpload.Dispose(); From 4c7678225ee018e24724024fe88580c024164b55 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Mar 2024 12:13:41 +0800 Subject: [PATCH 3367/4092] 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 1b395a7c83..4901f30d8a 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 747d6059da..6b63bfa1e2 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From 3a224211aa322a0055342f10bd36e0af3c3b078c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Mar 2024 12:17:00 +0800 Subject: [PATCH 3368/4092] Update various packages which shouldn't cause issues --- osu.Game.Benchmarks/osu.Game.Benchmarks.csproj | 2 +- osu.Game/osu.Game.csproj | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj index 64da5412a8..af84ee47f1 100644 --- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj +++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj @@ -7,7 +7,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 942763e388..b143a3a6b1 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -20,8 +20,8 @@ - - + + @@ -35,14 +35,14 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + From 98ca021e6628d94df508709482f047a2cff7cdde Mon Sep 17 00:00:00 2001 From: jvyden Date: Wed, 6 Mar 2024 01:17:11 -0500 Subject: [PATCH 3369/4092] Catch and warn about osu!stable lobbies --- osu.Desktop/DiscordRichPresence.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 8fecd015d4..b4a7e80d48 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -191,6 +191,15 @@ namespace osu.Desktop game.Window?.Raise(); Logger.Log($"Received room secret from Discord RPC Client: {args.Secret}", LoggingTarget.Network, LogLevel.Debug); + // Stable and Lazer share the same Discord client ID, meaning they can accept join requests from each other. + // Since they aren't compatible in multi, see if stable's format is being used and log to avoid confusion. + // https://discord.com/channels/188630481301012481/188630652340404224/1214697229063946291 + if (args.Secret[0] != '{') + { + Logger.Log("osu!stable rooms are not compatible with lazer.", LoggingTarget.Network, LogLevel.Important); + return; + } + if (!tryParseRoomSecret(args.Secret, out long roomId, out string? password)) { Logger.Log("Could not join multiplayer room.", LoggingTarget.Network, LogLevel.Important); From 53fffc6a75df70e82be5c1e1a3d2fe633f86c834 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 6 Mar 2024 07:57:59 +0100 Subject: [PATCH 3370/4092] Remove unused using directives --- osu.Game/Overlays/Settings/SettingsNumberBox.cs | 1 - osu.Game/Screens/Menu/LogoVisualisation.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsNumberBox.cs b/osu.Game/Overlays/Settings/SettingsNumberBox.cs index cdf648fc5f..fbcdb4a968 100644 --- a/osu.Game/Overlays/Settings/SettingsNumberBox.cs +++ b/osu.Game/Overlays/Settings/SettingsNumberBox.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index c47ce91711..6d9d2f69b7 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -14,7 +14,6 @@ using osu.Framework.Graphics.Rendering; using osu.Framework.Graphics.Rendering.Vertices; using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Textures; -using osu.Framework.Utils; using osu.Game.Beatmaps; using osuTK; using osuTK.Graphics; From 08609e19d6bb13669cb4f44e0ae6b80a7444c786 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 6 Mar 2024 11:40:10 +0100 Subject: [PATCH 3371/4092] Fix osu! standardised score estimation algorithm violating basic invariants As it turns out, the "lower" and "upper" estimates of the combo portion of the score being converted were misnomers. In selected cases (scores with high accuracy but combo being lower than max by more than a few objects) the janky score-based math could overestimate the count of remaining objects in a map. For instance, in one case the numbers worked out something like this: - Accuracy: practically 100% - Max combo on beatmap: 571x - Max combo for score: 551x The score-based estimation attempts to extract a "remaining object count" from score, by doing something along of sqrt(571^2 - 551^2). That comes out to _almost 150_. Which leads to the estimation overshooting the total max combo count on the beatmap by some hundred objects. To curtail this nonsense, enforce some basic invariants: - Neither estimate is allowed to exceed maximum achievable - Ensure that lower estimate is really lower and upper is really upper by just looking at the values and making sure that is so rather than just saying that it is. --- .../Database/StandardisedScoreMigrationTools.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index 0594c80390..53ff1a25ca 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -415,7 +415,7 @@ namespace osu.Game.Database // Calculate how many times the longest combo the user has achieved in the play can repeat // without exceeding the combo portion in score V1 as achieved by the player. - // This is a pessimistic estimate; it intentionally does not operate on object count and uses only score instead. + // This it intentionally does not operate on object count and uses only score instead. double maximumOccurrencesOfLongestCombo = Math.Floor(comboPortionInScoreV1 / comboPortionFromLongestComboInScoreV1); double comboPortionFromRepeatedLongestCombosInScoreV1 = maximumOccurrencesOfLongestCombo * comboPortionFromLongestComboInScoreV1; @@ -426,13 +426,12 @@ namespace osu.Game.Database // ...and then based on that raw combo length, we calculate how much this last combo is worth in standardised score. double remainingComboPortionInStandardisedScore = Math.Pow(remainingCombo, 1 + ScoreProcessor.COMBO_EXPONENT); - double lowerEstimateOfComboPortionInStandardisedScore + double scoreBasedEstimateOfComboPortionInStandardisedScore = maximumOccurrencesOfLongestCombo * comboPortionFromLongestComboInStandardisedScore + remainingComboPortionInStandardisedScore; // Compute approximate upper estimate new score for that play. // This time, divide the remaining combo among remaining objects equally to achieve longest possible combo lengths. - // There is no rigorous proof that doing this will yield a correct upper bound, but it seems to work out in practice. remainingComboPortionInScoreV1 = comboPortionInScoreV1 - comboPortionFromLongestComboInScoreV1; double remainingCountOfObjectsGivingCombo = maximumLegacyCombo - score.MaxCombo - score.Statistics.GetValueOrDefault(HitResult.Miss); // Because we assumed all combos were equal, `remainingComboPortionInScoreV1` @@ -449,7 +448,17 @@ namespace osu.Game.Database // we can skip adding the 1 and just multiply by x ^ 0.5. remainingComboPortionInStandardisedScore = remainingCountOfObjectsGivingCombo * Math.Pow(lengthOfRemainingCombos, ScoreProcessor.COMBO_EXPONENT); - double upperEstimateOfComboPortionInStandardisedScore = comboPortionFromLongestComboInStandardisedScore + remainingComboPortionInStandardisedScore; + double objectCountBasedEstimateOfComboPortionInStandardisedScore = comboPortionFromLongestComboInStandardisedScore + remainingComboPortionInStandardisedScore; + + // Enforce some invariants on both of the estimates. + // In rare cases they can produce invalid results. + scoreBasedEstimateOfComboPortionInStandardisedScore = + Math.Clamp(scoreBasedEstimateOfComboPortionInStandardisedScore, 0, maximumAchievableComboPortionInStandardisedScore); + objectCountBasedEstimateOfComboPortionInStandardisedScore = + Math.Clamp(objectCountBasedEstimateOfComboPortionInStandardisedScore, 0, maximumAchievableComboPortionInStandardisedScore); + + double lowerEstimateOfComboPortionInStandardisedScore = Math.Min(scoreBasedEstimateOfComboPortionInStandardisedScore, objectCountBasedEstimateOfComboPortionInStandardisedScore); + double upperEstimateOfComboPortionInStandardisedScore = Math.Max(scoreBasedEstimateOfComboPortionInStandardisedScore, objectCountBasedEstimateOfComboPortionInStandardisedScore); // Approximate by combining lower and upper estimates. // As the lower-estimate is very pessimistic, we use a 30/70 ratio From 5b6703ec0da46259daddd777ee88be5317aba2d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Mar 2024 19:23:29 +0800 Subject: [PATCH 3372/4092] Move optimisation to isolated method --- osu.Game/Storyboards/StoryboardSprite.cs | 76 +++++++++++++----------- 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index 350438942e..4992ae128d 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -90,41 +90,8 @@ namespace osu.Game.Storyboards // Ignore the whole setup if there are loops. In theory they can be handled here too, however the logic will be overly complex. if (loops.Count == 0) { - // Here we are starting from maximum value and trying to minimise the end time on each step. - // There are few solid guesses we can make using which sprite's end time can be minimised: alpha = 0, scale = 0, colour.a = 0. - double[] deathTimes = - { - double.MaxValue, // alpha - double.MaxValue, // colour alpha - double.MaxValue, // scale - double.MaxValue, // scale x - double.MaxValue, // scale y - }; - - // The loops below are following the same pattern. - // We could be using TimelineGroup.EndValue here, however it's possible to have multiple commands with 0 value in a row - // so we are saving the earliest of them. - foreach (var alphaCommand in TimelineGroup.Alpha.Commands) - { - deathTimes[0] = alphaCommand.EndValue == 0 - ? Math.Min(alphaCommand.EndTime, deathTimes[0]) // commands are ordered by the start time, however end time may vary. Save the earliest. - : double.MaxValue; // If value isn't 0 (sprite becomes visible again), revert the saved state. - } - - foreach (var colourCommand in TimelineGroup.Colour.Commands) - deathTimes[1] = colourCommand.EndValue.A == 0 ? Math.Min(colourCommand.EndTime, deathTimes[1]) : double.MaxValue; - - foreach (var scaleCommand in TimelineGroup.Scale.Commands) - deathTimes[2] = scaleCommand.EndValue == 0 ? Math.Min(scaleCommand.EndTime, deathTimes[2]) : double.MaxValue; - - foreach (var scaleCommand in TimelineGroup.VectorScale.Commands) - { - deathTimes[3] = scaleCommand.EndValue.X == 0 ? Math.Min(scaleCommand.EndTime, deathTimes[3]) : double.MaxValue; - deathTimes[4] = scaleCommand.EndValue.Y == 0 ? Math.Min(scaleCommand.EndTime, deathTimes[4]) : double.MaxValue; - } - // Take the minimum time of all the potential "death" reasons. - latestEndTime = deathTimes.Min(); + latestEndTime = calculateOptimisedEndTime(TimelineGroup); } // If the logic above fails to find anything or discarded by the fact that there are loops present, latestEndTime will be double.MaxValue @@ -238,6 +205,47 @@ namespace osu.Game.Storyboards return commands; } + private static double calculateOptimisedEndTime(CommandTimelineGroup timelineGroup) + { + // Here we are starting from maximum value and trying to minimise the end time on each step. + // There are few solid guesses we can make using which sprite's end time can be minimised: alpha = 0, scale = 0, colour.a = 0. + double[] deathTimes = + { + double.MaxValue, // alpha + double.MaxValue, // colour alpha + double.MaxValue, // scale + double.MaxValue, // scale x + double.MaxValue, // scale y + }; + + // The loops below are following the same pattern. + // We could be using TimelineGroup.EndValue here, however it's possible to have multiple commands with 0 value in a row + // so we are saving the earliest of them. + foreach (var alphaCommand in timelineGroup.Alpha.Commands) + { + if (alphaCommand.EndValue == 0) + // commands are ordered by the start time, however end time may vary. Save the earliest. + deathTimes[0] = Math.Min(alphaCommand.EndTime, deathTimes[0]); + else + // If value isn't 0 (sprite becomes visible again), revert the saved state. + deathTimes[0] = double.MaxValue; + } + + foreach (var colourCommand in timelineGroup.Colour.Commands) + deathTimes[1] = colourCommand.EndValue.A == 0 ? Math.Min(colourCommand.EndTime, deathTimes[1]) : double.MaxValue; + + foreach (var scaleCommand in timelineGroup.Scale.Commands) + deathTimes[2] = scaleCommand.EndValue == 0 ? Math.Min(scaleCommand.EndTime, deathTimes[2]) : double.MaxValue; + + foreach (var scaleCommand in timelineGroup.VectorScale.Commands) + { + deathTimes[3] = scaleCommand.EndValue.X == 0 ? Math.Min(scaleCommand.EndTime, deathTimes[3]) : double.MaxValue; + deathTimes[4] = scaleCommand.EndValue.Y == 0 ? Math.Min(scaleCommand.EndTime, deathTimes[4]) : double.MaxValue; + } + + return deathTimes.Min(); + } + public override string ToString() => $"{Path}, {Origin}, {InitialPosition}"; From 672f645cbab747d02cbb61d31283f604a3244ed8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 6 Mar 2024 18:39:18 +0300 Subject: [PATCH 3373/4092] Clamp only on horizontal sides --- osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs index 6ba91fbbd5..8f9a2d7e74 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs @@ -47,19 +47,18 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy AutoSizeAxes = Axes.Y, Children = new Drawable[] { + // Key images are placed side-to-side on the playfield, therefore ClampToEdge must be used to prevent any gaps between each key. upSprite = new Sprite { Origin = Anchor.BottomCentre, - // ClampToEdge is used to avoid gaps between keys, see: https://github.com/ppy/osu/issues/27431 - Texture = skin.GetTexture(upImage, WrapMode.ClampToEdge, WrapMode.ClampToEdge), + Texture = skin.GetTexture(upImage, WrapMode.ClampToEdge, default), RelativeSizeAxes = Axes.X, Width = 1 }, downSprite = new Sprite { Origin = Anchor.BottomCentre, - // ClampToEdge is used to avoid gaps between keys, see: https://github.com/ppy/osu/issues/27431 - Texture = skin.GetTexture(downImage, WrapMode.ClampToEdge, WrapMode.ClampToEdge), + Texture = skin.GetTexture(downImage, WrapMode.ClampToEdge, default), RelativeSizeAxes = Axes.X, Width = 1, Alpha = 0 From 3121cf81e6f8ad0dd8ecb80b5dfc2cff4f55c306 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 6 Mar 2024 21:30:09 +0100 Subject: [PATCH 3374/4092] Bump score version --- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 775d87f3f2..4ee4231925 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -45,9 +45,10 @@ namespace osu.Game.Scoring.Legacy /// /// 30000013: All local scores will use lazer definitions of ranks for consistency. Recalculates the rank of all scores. /// 30000014: Fix edge cases in conversion for osu! scores on selected beatmaps. Reconvert all scores. + /// 30000015: Fix osu! standardised score estimation algorithm violating basic invariants. Reconvert all scores. /// /// - public const int LATEST_VERSION = 30000014; + public const int LATEST_VERSION = 30000015; /// /// The first stable-compatible YYYYMMDD format version given to lazer usage of replays. From aa3cd402ca9ed3d666aac0e9934f9290af01b828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 6 Mar 2024 21:30:31 +0100 Subject: [PATCH 3375/4092] Fix broken english --- osu.Game/Database/StandardisedScoreMigrationTools.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index 53ff1a25ca..6f2f8d64fa 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -415,7 +415,7 @@ namespace osu.Game.Database // Calculate how many times the longest combo the user has achieved in the play can repeat // without exceeding the combo portion in score V1 as achieved by the player. - // This it intentionally does not operate on object count and uses only score instead. + // This intentionally does not operate on object count and uses only score instead. double maximumOccurrencesOfLongestCombo = Math.Floor(comboPortionInScoreV1 / comboPortionFromLongestComboInScoreV1); double comboPortionFromRepeatedLongestCombosInScoreV1 = maximumOccurrencesOfLongestCombo * comboPortionFromLongestComboInScoreV1; From 336a6180e537ba1c205c488cead7c5494446b705 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 7 Mar 2024 08:20:20 +0300 Subject: [PATCH 3376/4092] Expose `TRANSITION_LENGTH` from tab control --- osu.Game/Graphics/UserInterface/OsuTabControl.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index c260c92b43..f24977927f 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -116,18 +116,18 @@ namespace osu.Game.Graphics.UserInterface } } - private const float transition_length = 500; + protected const float TRANSITION_LENGTH = 500; - protected void FadeHovered() + protected virtual void FadeHovered() { - Bar.FadeIn(transition_length, Easing.OutQuint); - Text.FadeColour(Color4.White, transition_length, Easing.OutQuint); + Bar.FadeIn(TRANSITION_LENGTH, Easing.OutQuint); + Text.FadeColour(Color4.White, TRANSITION_LENGTH, Easing.OutQuint); } - protected void FadeUnhovered() + protected virtual void FadeUnhovered() { - Bar.FadeTo(IsHovered ? 1 : 0, transition_length, Easing.OutQuint); - Text.FadeColour(IsHovered ? Color4.White : AccentColour, transition_length, Easing.OutQuint); + Bar.FadeTo(IsHovered ? 1 : 0, TRANSITION_LENGTH, Easing.OutQuint); + Text.FadeColour(IsHovered ? Color4.White : AccentColour, TRANSITION_LENGTH, Easing.OutQuint); } protected override bool OnHover(HoverEvent e) From 0fe139a1892423030b1de36df957bff095fc6587 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 7 Mar 2024 08:20:46 +0300 Subject: [PATCH 3377/4092] Adjust editor screen switcher control design and behaviour --- .../Menus/EditorScreenSwitcherControl.cs | 40 ++++++++++++++----- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Menus/EditorScreenSwitcherControl.cs b/osu.Game/Screens/Edit/Components/Menus/EditorScreenSwitcherControl.cs index 1f6d61d0ad..2b0b1ea219 100644 --- a/osu.Game/Screens/Edit/Components/Menus/EditorScreenSwitcherControl.cs +++ b/osu.Game/Screens/Edit/Components/Menus/EditorScreenSwitcherControl.cs @@ -9,6 +9,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Edit.Components.Menus { @@ -21,7 +22,7 @@ namespace osu.Game.Screens.Edit.Components.Menus TabContainer.RelativeSizeAxes &= ~Axes.X; TabContainer.AutoSizeAxes = Axes.X; - TabContainer.Padding = new MarginPadding(10); + TabContainer.Spacing = Vector2.Zero; } [BackgroundDependencyLoader] @@ -42,30 +43,51 @@ namespace osu.Game.Screens.Edit.Components.Menus private partial class TabItem : OsuTabItem { - private const float transition_length = 250; + private readonly Box background; + private Color4 backgroundIdleColour; + private Color4 backgroundHoverColour; public TabItem(EditorScreenMode value) : base(value) { - Text.Margin = new MarginPadding(); + Text.Margin = new MarginPadding(10); Text.Anchor = Anchor.CentreLeft; Text.Origin = Anchor.CentreLeft; Text.Font = OsuFont.TorusAlternate; + Add(background = new Box + { + RelativeSizeAxes = Axes.Both, + Depth = float.MaxValue, + }); + Bar.Expire(); } - protected override void OnActivated() + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) { - base.OnActivated(); - Bar.ScaleTo(new Vector2(1, 5), transition_length, Easing.OutQuint); + backgroundIdleColour = colourProvider.Background2; + backgroundHoverColour = colourProvider.Background1; } - protected override void OnDeactivated() + protected override void LoadComplete() { - base.OnDeactivated(); - Bar.ScaleTo(Vector2.One, transition_length, Easing.OutQuint); + base.LoadComplete(); + background.Colour = backgroundIdleColour; + } + + protected override void FadeHovered() + { + base.FadeHovered(); + background.FadeColour(backgroundHoverColour, TRANSITION_LENGTH, Easing.OutQuint); + } + + protected override void FadeUnhovered() + { + base.FadeUnhovered(); + background.FadeColour(backgroundIdleColour, TRANSITION_LENGTH, Easing.OutQuint); } } } From 56caf1935043112ec13c5d0b1d43e9d23b8d1f7e Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 6 Mar 2024 22:48:54 -0800 Subject: [PATCH 3378/4092] Add visual test for failed S display --- .../TestSceneExpandedPanelMiddleContent.cs | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs index d71c72f4ec..ceb2d4927c 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs @@ -88,8 +88,21 @@ namespace osu.Game.Tests.Visual.Ranking AddAssert("play time not displayed", () => !this.ChildrenOfType().Any()); } - private void showPanel(ScoreInfo score) => - Child = new ExpandedPanelMiddleContentContainer(score); + [TestCase(false)] + [TestCase(true)] + public void TestFailedSDisplay(bool withFlair) + { + AddStep("show failed S score", () => + { + var score = TestResources.CreateTestScoreInfo(createTestBeatmap(new RealmUser())); + score.Rank = ScoreRank.A; + score.Accuracy = 0.975; + showPanel(score, withFlair); + }); + } + + private void showPanel(ScoreInfo score, bool withFlair = false) => + Child = new ExpandedPanelMiddleContentContainer(score, withFlair); private BeatmapInfo createTestBeatmap([NotNull] RealmUser author) { @@ -107,7 +120,7 @@ namespace osu.Game.Tests.Visual.Ranking private partial class ExpandedPanelMiddleContentContainer : Container { - public ExpandedPanelMiddleContentContainer(ScoreInfo score) + public ExpandedPanelMiddleContentContainer(ScoreInfo score, bool withFlair) { Anchor = Anchor.Centre; Origin = Anchor.Centre; @@ -119,7 +132,7 @@ namespace osu.Game.Tests.Visual.Ranking RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex("#444"), }, - new ExpandedPanelMiddleContent(score) + new ExpandedPanelMiddleContent(score, withFlair) }; } } From c36232bc02ff9289b0ecc16a6235049f3ddaa63a Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 6 Mar 2024 20:08:46 -0800 Subject: [PATCH 3379/4092] Fix results screen accuracy circle not showing correctly for failed S with no flair --- .../Expanded/Accuracy/AccuracyCircle.cs | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 54829a6274..f04e4a6444 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -194,11 +194,11 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy rankText = new RankText(score.Rank) }; + if (isFailedSDueToMisses) + AddInternal(failedSRankText = new RankText(ScoreRank.S)); + if (withFlair) { - if (isFailedSDueToMisses) - AddInternal(failedSRankText = new RankText(ScoreRank.S)); - var applauseSamples = new List { applauseSampleName }; if (score.Rank >= ScoreRank.B) // when rank is B or higher, play legacy applause sample on legacy skins. @@ -326,24 +326,25 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy { rankText.Appear(); - if (!withFlair) return; - - Schedule(() => - { - isTicking = false; - rankImpactSound.Play(); - }); - - const double applause_pre_delay = 545f; - const double applause_volume = 0.8f; - - using (BeginDelayedSequence(applause_pre_delay)) + if (withFlair) { Schedule(() => { - rankApplauseSound.VolumeTo(applause_volume); - rankApplauseSound.Play(); + isTicking = false; + rankImpactSound.Play(); }); + + const double applause_pre_delay = 545f; + const double applause_volume = 0.8f; + + using (BeginDelayedSequence(applause_pre_delay)) + { + Schedule(() => + { + rankApplauseSound.VolumeTo(applause_volume); + rankApplauseSound.Play(); + }); + } } } From 1cafb0997713ee387f8994e972d64ad908aa4181 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 7 Mar 2024 17:22:38 +0900 Subject: [PATCH 3380/4092] Increase border thickness --- osu.Game/Screens/Play/DelayedResumeOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/DelayedResumeOverlay.cs b/osu.Game/Screens/Play/DelayedResumeOverlay.cs index 08d00f8ac2..ba49810b2b 100644 --- a/osu.Game/Screens/Play/DelayedResumeOverlay.cs +++ b/osu.Game/Screens/Play/DelayedResumeOverlay.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Play AutoSizeAxes = Axes.Both, Masking = true, BorderColour = colours.Yellow, - BorderThickness = 1, + BorderThickness = 2, Children = new Drawable[] { new Box From 039520d55dc9b9cc55e143a6da8bb87f684ef882 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 7 Mar 2024 09:49:20 +0100 Subject: [PATCH 3381/4092] Use slightly nicer parameterisation in test --- .../Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs index ceb2d4927c..d97946a1d5 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs @@ -88,9 +88,8 @@ namespace osu.Game.Tests.Visual.Ranking AddAssert("play time not displayed", () => !this.ChildrenOfType().Any()); } - [TestCase(false)] - [TestCase(true)] - public void TestFailedSDisplay(bool withFlair) + [Test] + public void TestFailedSDisplay([Values] bool withFlair) { AddStep("show failed S score", () => { From ca92a31cf97bcb28fd8d9e67fe0ac83b1085ac80 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 7 Mar 2024 21:10:11 +0900 Subject: [PATCH 3382/4092] Fix missing event unbinds --- osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs | 9 +++++++++ .../Components/Timeline/TimelineHitObjectBlueprint.cs | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs index 5ddc627642..3365b206cf 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Localisation; using osu.Game.Rulesets.Mania.UI; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Game.Rulesets.Mania.Skinning; using osu.Game.Rulesets.Mods; @@ -101,6 +102,14 @@ namespace osu.Game.Rulesets.Mania.Mods return base.GetHeight(coverage) * reference_playfield_height / availablePlayfieldHeight; } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (skin.IsNotNull()) + skin.SourceChanged -= onSkinChanged; + } } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 47dc3fb82e..d4afcc0151 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -6,6 +6,7 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -265,6 +266,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return !Precision.AlmostIntersects(maskingBounds, rect); } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (skin.IsNotNull()) + skin.SourceChanged -= updateColour; + } + private partial class Tick : Circle { public Tick() From 585ab5976877281be70cb6f52721382c5a86a861 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 6 Mar 2024 19:39:28 +0300 Subject: [PATCH 3383/4092] Apply major refactor to the storyboard commands flow structrure --- .../TestSceneDrawableStoryboardSprite.cs | 3 +- .../Visual/Gameplay/TestSceneLeadIn.cs | 16 +- .../Gameplay/TestSceneStoryboardWithOutro.cs | 2 +- .../TestSceneMultiSpectatorScreen.cs | 2 +- .../Formats/LegacyStoryboardDecoder.cs | 39 +-- osu.Game/Storyboards/CommandLoop.cs | 63 ----- osu.Game/Storyboards/CommandTimeline.cs | 101 -------- osu.Game/Storyboards/CommandTimelineGroup.cs | 120 --------- .../Commands/IStoryboardCommand.cs | 28 +++ .../Commands/StoryboardAlphaCommand.cs | 19 ++ .../StoryboardBlendingParametersCommand.cs | 21 ++ .../Commands/StoryboardColourCommand.cs | 20 ++ .../Storyboards/Commands/StoryboardCommand.cs | 54 ++++ .../Commands/StoryboardCommandGroup.cs | 115 +++++++++ .../Commands/StoryboardCommandList.cs | 41 +++ .../Commands/StoryboardFlipHCommand.cs | 23 ++ .../Commands/StoryboardFlipVCommand.cs | 23 ++ .../Commands/StoryboardLoopingGroup.cs | 71 ++++++ .../Commands/StoryboardRotationCommand.cs | 21 ++ .../Commands/StoryboardScaleCommand.cs | 22 ++ .../StoryboardTriggerGroup.cs} | 7 +- .../Commands/StoryboardVectorScaleCommand.cs | 24 ++ .../Commands/StoryboardXCommand.cs | 21 ++ .../Commands/StoryboardYCommand.cs | 21 ++ .../Drawables/IDrawableStoryboardElement.cs | 4 +- osu.Game/Storyboards/StoryboardAnimation.cs | 4 +- .../StoryboardElementWithDuration.cs | 236 ------------------ osu.Game/Storyboards/StoryboardSprite.cs | 153 +++++++++++- 28 files changed, 713 insertions(+), 561 deletions(-) delete mode 100644 osu.Game/Storyboards/CommandLoop.cs delete mode 100644 osu.Game/Storyboards/CommandTimeline.cs delete mode 100644 osu.Game/Storyboards/CommandTimelineGroup.cs create mode 100644 osu.Game/Storyboards/Commands/IStoryboardCommand.cs create mode 100644 osu.Game/Storyboards/Commands/StoryboardAlphaCommand.cs create mode 100644 osu.Game/Storyboards/Commands/StoryboardBlendingParametersCommand.cs create mode 100644 osu.Game/Storyboards/Commands/StoryboardColourCommand.cs create mode 100644 osu.Game/Storyboards/Commands/StoryboardCommand.cs create mode 100644 osu.Game/Storyboards/Commands/StoryboardCommandGroup.cs create mode 100644 osu.Game/Storyboards/Commands/StoryboardCommandList.cs create mode 100644 osu.Game/Storyboards/Commands/StoryboardFlipHCommand.cs create mode 100644 osu.Game/Storyboards/Commands/StoryboardFlipVCommand.cs create mode 100644 osu.Game/Storyboards/Commands/StoryboardLoopingGroup.cs create mode 100644 osu.Game/Storyboards/Commands/StoryboardRotationCommand.cs create mode 100644 osu.Game/Storyboards/Commands/StoryboardScaleCommand.cs rename osu.Game/Storyboards/{CommandTrigger.cs => Commands/StoryboardTriggerGroup.cs} (68%) create mode 100644 osu.Game/Storyboards/Commands/StoryboardVectorScaleCommand.cs create mode 100644 osu.Game/Storyboards/Commands/StoryboardXCommand.cs create mode 100644 osu.Game/Storyboards/Commands/StoryboardYCommand.cs delete mode 100644 osu.Game/Storyboards/StoryboardElementWithDuration.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs index 32693c2bb2..6209b42cbb 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs @@ -175,7 +175,8 @@ namespace osu.Game.Tests.Visual.Gameplay var layer = storyboard.GetLayer("Background"); var sprite = new StoryboardSprite(lookupName, origin, initialPosition); - sprite.AddLoop(Time.Current, 100).Alpha.Add(Easing.None, 0, 10000, 1, 1); + var loop = sprite.AddLoopingGroup(Time.Current, 100); + loop.AddAlpha(0, 10000, 1, 1, Easing.None); layer.Elements.Clear(); layer.Add(sprite); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs index dae3119ea4..c3eef4da9b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.Gameplay var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero); - sprite.TimelineGroup.Alpha.Add(Easing.None, firstStoryboardEvent, firstStoryboardEvent + 500, 0, 1); + sprite.Group.AddAlpha(firstStoryboardEvent, firstStoryboardEvent + 500, 0, 1, Easing.None); storyboard.GetLayer("Background").Add(sprite); @@ -73,17 +73,17 @@ namespace osu.Game.Tests.Visual.Gameplay var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero); // these should be ignored as we have an alpha visibility blocker proceeding this command. - sprite.TimelineGroup.Scale.Add(Easing.None, loop_start_time, -18000, Vector2.Zero, Vector2.One); - var loopGroup = sprite.AddLoop(loop_start_time, 50); - loopGroup.Scale.Add(Easing.None, loop_start_time, -18000, Vector2.Zero, Vector2.One); + sprite.Group.AddScale(loop_start_time, -18000, 0, 1, Easing.None); + var loopGroup = sprite.AddLoopingGroup(loop_start_time, 50); + loopGroup.AddScale(loop_start_time, -18000, 0, 1, Easing.None); - var target = addEventToLoop ? loopGroup : sprite.TimelineGroup; + var target = addEventToLoop ? loopGroup : sprite.Group; double loopRelativeOffset = addEventToLoop ? -loop_start_time : 0; - target.Alpha.Add(Easing.None, loopRelativeOffset + firstStoryboardEvent, loopRelativeOffset + firstStoryboardEvent + 500, 0, 1); + target.AddAlpha(loopRelativeOffset + firstStoryboardEvent, loopRelativeOffset + firstStoryboardEvent + 500, 0, 1, Easing.None); // these should be ignored due to being in the future. - sprite.TimelineGroup.Alpha.Add(Easing.None, 18000, 20000, 0, 1); - loopGroup.Alpha.Add(Easing.None, 38000, 40000, 0, 1); + sprite.Group.AddAlpha(18000, 20000, 0, 1, Easing.None); + loopGroup.AddAlpha(38000, 40000, 0, 1, Easing.None); storyboard.GetLayer("Background").Add(sprite); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs index f532921d63..9269c3f4ae 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs @@ -216,7 +216,7 @@ namespace osu.Game.Tests.Visual.Gameplay { var storyboard = new Storyboard(); var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero); - sprite.TimelineGroup.Alpha.Add(Easing.None, 0, duration, 1, 0); + sprite.Group.AddAlpha(0, duration, 1, 0, Easing.None); storyboard.GetLayer("Background").Add(sprite); return storyboard; } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index cebc75f90c..62a2bfeaab 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -424,7 +424,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestIntroStoryboardElement() => testLeadIn(b => { var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero); - sprite.TimelineGroup.Alpha.Add(Easing.None, -2000, 0, 0, 1); + sprite.Group.AddAlpha(-2000, 0, 0, 1, Easing.None); b.Storyboard.GetLayer("Background").Add(sprite); }); diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index ba328b2dbd..33cdaa085e 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -10,6 +10,7 @@ using osu.Framework.Utils; using osu.Game.Beatmaps.Legacy; using osu.Game.IO; using osu.Game.Storyboards; +using osu.Game.Storyboards.Commands; using osuTK; using osuTK.Graphics; @@ -17,8 +18,8 @@ namespace osu.Game.Beatmaps.Formats { public class LegacyStoryboardDecoder : LegacyDecoder { - private StoryboardElementWithDuration? storyboardSprite; - private CommandTimelineGroup? timelineGroup; + private StoryboardSprite? storyboardSprite; + private StoryboardCommandGroup? currentGroup; private Storyboard storyboard = null!; @@ -165,7 +166,7 @@ namespace osu.Game.Beatmaps.Formats else { if (depth < 2) - timelineGroup = storyboardSprite?.TimelineGroup; + currentGroup = storyboardSprite?.Group; string commandType = split[0]; @@ -177,7 +178,7 @@ namespace osu.Game.Beatmaps.Formats double startTime = split.Length > 2 ? Parsing.ParseDouble(split[2]) : double.MinValue; double endTime = split.Length > 3 ? Parsing.ParseDouble(split[3]) : double.MaxValue; int groupNumber = split.Length > 4 ? Parsing.ParseInt(split[4]) : 0; - timelineGroup = storyboardSprite?.AddTrigger(triggerName, startTime, endTime, groupNumber); + currentGroup = storyboardSprite?.AddTriggerGroup(triggerName, startTime, endTime, groupNumber); break; } @@ -185,7 +186,7 @@ namespace osu.Game.Beatmaps.Formats { double startTime = Parsing.ParseDouble(split[1]); int repeatCount = Parsing.ParseInt(split[2]); - timelineGroup = storyboardSprite?.AddLoop(startTime, Math.Max(0, repeatCount - 1)); + currentGroup = storyboardSprite?.AddLoopingGroup(startTime, Math.Max(0, repeatCount - 1)); break; } @@ -204,7 +205,7 @@ namespace osu.Game.Beatmaps.Formats { float startValue = Parsing.ParseFloat(split[4]); float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue; - timelineGroup?.Alpha.Add(easing, startTime, endTime, startValue, endValue); + currentGroup?.AddAlpha(startTime, endTime, startValue, endValue, easing); break; } @@ -212,7 +213,7 @@ namespace osu.Game.Beatmaps.Formats { float startValue = Parsing.ParseFloat(split[4]); float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue; - timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startValue), new Vector2(endValue)); + currentGroup?.AddScale(startTime, endTime, startValue, endValue, easing); break; } @@ -222,7 +223,7 @@ namespace osu.Game.Beatmaps.Formats float startY = Parsing.ParseFloat(split[5]); float endX = split.Length > 6 ? Parsing.ParseFloat(split[6]) : startX; float endY = split.Length > 7 ? Parsing.ParseFloat(split[7]) : startY; - timelineGroup?.VectorScale.Add(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY)); + currentGroup?.AddVectorScale(startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY), easing); break; } @@ -230,7 +231,7 @@ namespace osu.Game.Beatmaps.Formats { float startValue = Parsing.ParseFloat(split[4]); float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue; - timelineGroup?.Rotation.Add(easing, startTime, endTime, MathUtils.RadiansToDegrees(startValue), MathUtils.RadiansToDegrees(endValue)); + currentGroup?.AddRotation(startTime, endTime, MathUtils.RadiansToDegrees(startValue), MathUtils.RadiansToDegrees(endValue), easing); break; } @@ -240,8 +241,8 @@ namespace osu.Game.Beatmaps.Formats float startY = Parsing.ParseFloat(split[5]); float endX = split.Length > 6 ? Parsing.ParseFloat(split[6]) : startX; float endY = split.Length > 7 ? Parsing.ParseFloat(split[7]) : startY; - timelineGroup?.X.Add(easing, startTime, endTime, startX, endX); - timelineGroup?.Y.Add(easing, startTime, endTime, startY, endY); + currentGroup?.AddX(startTime, endTime, startX, endX, easing); + currentGroup?.AddY(startTime, endTime, startY, endY, easing); break; } @@ -249,7 +250,7 @@ namespace osu.Game.Beatmaps.Formats { float startValue = Parsing.ParseFloat(split[4]); float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue; - timelineGroup?.X.Add(easing, startTime, endTime, startValue, endValue); + currentGroup?.AddX(startTime, endTime, startValue, endValue, easing); break; } @@ -257,7 +258,7 @@ namespace osu.Game.Beatmaps.Formats { float startValue = Parsing.ParseFloat(split[4]); float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue; - timelineGroup?.Y.Add(easing, startTime, endTime, startValue, endValue); + currentGroup?.AddY(startTime, endTime, startValue, endValue, easing); break; } @@ -269,9 +270,9 @@ namespace osu.Game.Beatmaps.Formats float endRed = split.Length > 7 ? Parsing.ParseFloat(split[7]) : startRed; float endGreen = split.Length > 8 ? Parsing.ParseFloat(split[8]) : startGreen; float endBlue = split.Length > 9 ? Parsing.ParseFloat(split[9]) : startBlue; - timelineGroup?.Colour.Add(easing, startTime, endTime, + currentGroup?.AddColour(startTime, endTime, new Color4(startRed / 255f, startGreen / 255f, startBlue / 255f, 1), - new Color4(endRed / 255f, endGreen / 255f, endBlue / 255f, 1)); + new Color4(endRed / 255f, endGreen / 255f, endBlue / 255f, 1), easing); break; } @@ -282,16 +283,16 @@ namespace osu.Game.Beatmaps.Formats switch (type) { case "A": - timelineGroup?.BlendingParameters.Add(easing, startTime, endTime, BlendingParameters.Additive, - startTime == endTime ? BlendingParameters.Additive : BlendingParameters.Inherit); + currentGroup?.AddBlendingParameters(startTime, endTime, BlendingParameters.Additive, + startTime == endTime ? BlendingParameters.Additive : BlendingParameters.Inherit, easing); break; case "H": - timelineGroup?.FlipH.Add(easing, startTime, endTime, true, startTime == endTime); + currentGroup?.AddFlipH(startTime, endTime, true, startTime == endTime, easing); break; case "V": - timelineGroup?.FlipV.Add(easing, startTime, endTime, true, startTime == endTime); + currentGroup?.AddFlipV(startTime, endTime, true, startTime == endTime, easing); break; } diff --git a/osu.Game/Storyboards/CommandLoop.cs b/osu.Game/Storyboards/CommandLoop.cs deleted file mode 100644 index 6dd782cb7f..0000000000 --- a/osu.Game/Storyboards/CommandLoop.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. - -using System; -using System.Collections.Generic; - -namespace osu.Game.Storyboards -{ - public class CommandLoop : CommandTimelineGroup - { - public double LoopStartTime; - - /// - /// The total number of times this loop is played back. Always greater than zero. - /// - public readonly int TotalIterations; - - public override double StartTime => LoopStartTime + CommandsStartTime; - - public override double EndTime => - // In an ideal world, we would multiply the command duration by TotalIterations here. - // Unfortunately this would clash with how stable handled end times, and results in some storyboards playing outro - // sequences for minutes or hours. - StartTime + CommandsDuration; - - /// - /// Construct a new command loop. - /// - /// The start time of the loop. - /// The number of times the loop should repeat. Should be greater than zero. Zero means a single playback. - public CommandLoop(double startTime, int repeatCount) - { - if (repeatCount < 0) throw new ArgumentException("Repeat count must be zero or above.", nameof(repeatCount)); - - LoopStartTime = startTime; - TotalIterations = repeatCount + 1; - } - - public override IEnumerable.TypedCommand> GetCommands(CommandTimelineSelector timelineSelector, double offset = 0) - { - double fullLoopDuration = CommandsEndTime - CommandsStartTime; - - foreach (var command in timelineSelector(this).Commands) - { - yield return new CommandTimeline.TypedCommand - { - Easing = command.Easing, - StartTime = offset + LoopStartTime + command.StartTime, - EndTime = offset + LoopStartTime + command.EndTime, - StartValue = command.StartValue, - EndValue = command.EndValue, - PropertyName = command.PropertyName, - IsParameterCommand = command.IsParameterCommand, - LoopCount = TotalIterations, - Delay = fullLoopDuration - command.EndTime + command.StartTime - }; - } - } - - public override string ToString() - => $"{LoopStartTime} x{TotalIterations}"; - } -} diff --git a/osu.Game/Storyboards/CommandTimeline.cs b/osu.Game/Storyboards/CommandTimeline.cs deleted file mode 100644 index 4ad31d88c2..0000000000 --- a/osu.Game/Storyboards/CommandTimeline.cs +++ /dev/null @@ -1,101 +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 osu.Framework.Graphics; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace osu.Game.Storyboards -{ - public class CommandTimeline : ICommandTimeline - { - private readonly List commands = new List(); - - public IEnumerable Commands => commands.OrderBy(c => c.StartTime); - - public bool HasCommands => commands.Count > 0; - - public double StartTime { get; private set; } = double.MaxValue; - public double EndTime { get; private set; } = double.MinValue; - - public T StartValue { get; private set; } - public T EndValue { get; private set; } - - public string PropertyName { get; } - public bool IsParameterTimeline { get; set; } - - public CommandTimeline(string propertyName) - { - PropertyName = propertyName; - } - - public void Add(Easing easing, double startTime, double endTime, T startValue, T endValue) - { - if (endTime < startTime) - { - endTime = startTime; - } - - commands.Add(new TypedCommand { Easing = easing, StartTime = startTime, EndTime = endTime, StartValue = startValue, EndValue = endValue, PropertyName = PropertyName, IsParameterCommand = IsParameterTimeline }); - - if (startTime < StartTime) - { - StartValue = startValue; - StartTime = startTime; - } - - if (endTime > EndTime) - { - EndValue = endValue; - EndTime = endTime; - } - } - - public override string ToString() - => $"{commands.Count} command(s)"; - - public class TypedCommand : ICommand - { - public Easing Easing { get; set; } - public double StartTime { get; set; } - public double EndTime { get; set; } - public double Duration => EndTime - StartTime; - public string PropertyName { get; set; } - public int LoopCount { get; set; } - public double Delay { get; set; } - public bool IsParameterCommand { get; set; } - - public T StartValue; - public T EndValue; - - public int CompareTo(ICommand other) - { - int result = StartTime.CompareTo(other.StartTime); - if (result != 0) return result; - - return EndTime.CompareTo(other.EndTime); - } - - public override string ToString() - => $"{StartTime} -> {EndTime}, {StartValue} -> {EndValue} {Easing}"; - } - } - - public interface ICommandTimeline - { - double StartTime { get; } - double EndTime { get; } - bool HasCommands { get; } - } - - public interface ICommand : IComparable - { - Easing Easing { get; set; } - double StartTime { get; set; } - double EndTime { get; set; } - double Duration { get; } - } -} diff --git a/osu.Game/Storyboards/CommandTimelineGroup.cs b/osu.Game/Storyboards/CommandTimelineGroup.cs deleted file mode 100644 index 0362925619..0000000000 --- a/osu.Game/Storyboards/CommandTimelineGroup.cs +++ /dev/null @@ -1,120 +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 System; -using osuTK; -using osu.Framework.Graphics; -using System.Collections.Generic; -using System.Linq; -using Newtonsoft.Json; -using osu.Framework.Graphics.Colour; - -namespace osu.Game.Storyboards -{ - public delegate CommandTimeline CommandTimelineSelector(CommandTimelineGroup commandTimelineGroup); - - public class CommandTimelineGroup - { - public CommandTimeline X = new CommandTimeline("X"); - public CommandTimeline Y = new CommandTimeline("Y"); - public CommandTimeline Scale = new CommandTimeline("Scale"); - public CommandTimeline VectorScale = new CommandTimeline("VectorScale"); - public CommandTimeline Rotation = new CommandTimeline("Rotation"); - public CommandTimeline Colour = new CommandTimeline("Colour"); - public CommandTimeline Alpha = new CommandTimeline("Alpha"); - public CommandTimeline BlendingParameters = new CommandTimeline("Blending") { IsParameterTimeline = true }; - public CommandTimeline FlipH = new CommandTimeline("FlipH") { IsParameterTimeline = true }; - public CommandTimeline FlipV = new CommandTimeline("FlipV") { IsParameterTimeline = true }; - - private readonly ICommandTimeline[] timelines; - - public CommandTimelineGroup() - { - timelines = new ICommandTimeline[] - { - X, - Y, - Scale, - VectorScale, - Rotation, - Colour, - Alpha, - BlendingParameters, - FlipH, - FlipV - }; - } - - [JsonIgnore] - public double CommandsStartTime - { - get - { - double min = double.MaxValue; - - for (int i = 0; i < timelines.Length; i++) - min = Math.Min(min, timelines[i].StartTime); - - return min; - } - } - - [JsonIgnore] - public double CommandsEndTime - { - get - { - double max = double.MinValue; - - for (int i = 0; i < timelines.Length; i++) - max = Math.Max(max, timelines[i].EndTime); - - return max; - } - } - - [JsonIgnore] - public double CommandsDuration => CommandsEndTime - CommandsStartTime; - - [JsonIgnore] - public virtual double StartTime => CommandsStartTime; - - [JsonIgnore] - public virtual double EndTime => CommandsEndTime; - - [JsonIgnore] - public bool HasCommands - { - get - { - for (int i = 0; i < timelines.Length; i++) - { - if (timelines[i].HasCommands) - return true; - } - - return false; - } - } - - public virtual IEnumerable.TypedCommand> GetCommands(CommandTimelineSelector timelineSelector, double offset = 0) - { - if (offset != 0) - { - return timelineSelector(this).Commands.Select(command => - new CommandTimeline.TypedCommand - { - Easing = command.Easing, - StartTime = offset + command.StartTime, - EndTime = offset + command.EndTime, - StartValue = command.StartValue, - EndValue = command.EndValue, - PropertyName = command.PropertyName, - IsParameterCommand = command.IsParameterCommand - }); - } - - return timelineSelector(this).Commands; - } - } -} diff --git a/osu.Game/Storyboards/Commands/IStoryboardCommand.cs b/osu.Game/Storyboards/Commands/IStoryboardCommand.cs new file mode 100644 index 0000000000..848dcab575 --- /dev/null +++ b/osu.Game/Storyboards/Commands/IStoryboardCommand.cs @@ -0,0 +1,28 @@ +// 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.Graphics; +using osu.Framework.Graphics.Transforms; + +namespace osu.Game.Storyboards.Commands +{ + public interface IStoryboardCommand + { + /// + /// The start time of the storyboard command. + /// + double StartTime { get; } + + /// + /// The end time of the storyboard command. + /// + double EndTime { get; } + + /// + /// Applies the transforms described by this storyboard command to the target drawable. + /// + /// The target drawable. + /// The sequence of transforms applied to the target drawable. + TransformSequence ApplyTransform(Drawable d); + } +} diff --git a/osu.Game/Storyboards/Commands/StoryboardAlphaCommand.cs b/osu.Game/Storyboards/Commands/StoryboardAlphaCommand.cs new file mode 100644 index 0000000000..729ecd72a7 --- /dev/null +++ b/osu.Game/Storyboards/Commands/StoryboardAlphaCommand.cs @@ -0,0 +1,19 @@ +// 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.Graphics; +using osu.Framework.Graphics.Transforms; + +namespace osu.Game.Storyboards.Commands +{ + public class StoryboardAlphaCommand : StoryboardCommand + { + public StoryboardAlphaCommand(double startTime, double endTime, float startValue, float endValue, Easing easing) + : base(startTime, endTime, startValue, endValue, easing) + { + } + + public override void SetInitialValue(Drawable d) => d.Alpha = StartValue; + public override TransformSequence ApplyTransform(Drawable d) => d.FadeTo(StartValue).Then().FadeTo(EndValue, Duration, Easing); + } +} diff --git a/osu.Game/Storyboards/Commands/StoryboardBlendingParametersCommand.cs b/osu.Game/Storyboards/Commands/StoryboardBlendingParametersCommand.cs new file mode 100644 index 0000000000..cc54909837 --- /dev/null +++ b/osu.Game/Storyboards/Commands/StoryboardBlendingParametersCommand.cs @@ -0,0 +1,21 @@ +// 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.Graphics; +using osu.Framework.Graphics.Transforms; + +namespace osu.Game.Storyboards.Commands +{ + public class StoryboardBlendingParametersCommand : StoryboardCommand + { + public StoryboardBlendingParametersCommand(double startTime, double endTime, BlendingParameters startValue, BlendingParameters endValue, Easing easing) + : base(startTime, endTime, startValue, endValue, easing) + { + } + + public override void SetInitialValue(Drawable d) => d.Blending = StartValue; + + public override TransformSequence ApplyTransform(Drawable d) + => d.TransformTo(nameof(d.Blending), StartValue).Delay(Duration).TransformTo(nameof(d.Blending), EndValue); + } +} diff --git a/osu.Game/Storyboards/Commands/StoryboardColourCommand.cs b/osu.Game/Storyboards/Commands/StoryboardColourCommand.cs new file mode 100644 index 0000000000..be56a1d71b --- /dev/null +++ b/osu.Game/Storyboards/Commands/StoryboardColourCommand.cs @@ -0,0 +1,20 @@ +// 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.Graphics; +using osu.Framework.Graphics.Transforms; +using osuTK.Graphics; + +namespace osu.Game.Storyboards.Commands +{ + public class StoryboardColourCommand : StoryboardCommand + { + public StoryboardColourCommand(double startTime, double endTime, Color4 startValue, Color4 endValue, Easing easing) + : base(startTime, endTime, startValue, endValue, easing) + { + } + + public override void SetInitialValue(Drawable d) => d.Colour = StartValue; + public override TransformSequence ApplyTransform(Drawable d) => d.FadeColour(StartValue).Then().FadeColour(EndValue, Duration, Easing); + } +} diff --git a/osu.Game/Storyboards/Commands/StoryboardCommand.cs b/osu.Game/Storyboards/Commands/StoryboardCommand.cs new file mode 100644 index 0000000000..883b9f57c1 --- /dev/null +++ b/osu.Game/Storyboards/Commands/StoryboardCommand.cs @@ -0,0 +1,54 @@ +// 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.Graphics; +using osu.Framework.Graphics.Transforms; + +namespace osu.Game.Storyboards.Commands +{ + public abstract class StoryboardCommand : IStoryboardCommand + { + public double StartTime { get; } + public double EndTime { get; } + public double Duration => EndTime - StartTime; + + protected StoryboardCommand(double startTime, double endTime, T startValue, T endValue, Easing easing) + { + if (endTime < startTime) + endTime = startTime; + + StartTime = startTime; + StartValue = startValue; + EndTime = endTime; + EndValue = endValue; + Easing = easing; + } + + public Easing Easing { get; set; } + public int LoopCount { get; set; } + public double Delay { get; set; } + + public T StartValue; + public T EndValue; + + /// + /// Sets the value of the corresponding property in to the start value of this command. + /// + public abstract void SetInitialValue(Drawable d); + + /// + /// Transforms a corresponding property in that corresponds to this command group with the specified parameters. + /// + public abstract TransformSequence ApplyTransform(Drawable d); + + public int CompareTo(IStoryboardCommand other) + { + int result = StartTime.CompareTo(other.StartTime); + if (result != 0) return result; + + return EndTime.CompareTo(other.EndTime); + } + + public override string ToString() => $"{StartTime} -> {EndTime}, {StartValue} -> {EndValue} {Easing}"; + } +} diff --git a/osu.Game/Storyboards/Commands/StoryboardCommandGroup.cs b/osu.Game/Storyboards/Commands/StoryboardCommandGroup.cs new file mode 100644 index 0000000000..02c43c9f60 --- /dev/null +++ b/osu.Game/Storyboards/Commands/StoryboardCommandGroup.cs @@ -0,0 +1,115 @@ +// 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 Newtonsoft.Json; +using osu.Framework.Graphics; +using osu.Framework.Lists; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Storyboards.Commands +{ + public class StoryboardCommandGroup + { + public SortedList> X; + public SortedList> Y; + public SortedList> Scale; + public SortedList> VectorScale; + public SortedList> Rotation; + public SortedList> Colour; + public SortedList> Alpha; + public SortedList> BlendingParameters; + public SortedList> FlipH; + public SortedList> FlipV; + + private readonly IReadOnlyList[] lists; + + /// + /// Returns the earliest start time of the commands added to this group. + /// + [JsonIgnore] + public double StartTime { get; private set; } + + /// + /// Returns the latest end time of the commands added to this group. + /// + [JsonIgnore] + public double EndTime { get; private set; } + + [JsonIgnore] + public double Duration => EndTime - StartTime; + + [JsonIgnore] + public bool HasCommands { get; private set; } + + public StoryboardCommandGroup() + { + lists = new IReadOnlyList[] + { + X = new SortedList>(), + Y = new SortedList>(), + Scale = new SortedList>(), + VectorScale = new SortedList>(), + Rotation = new SortedList>(), + Colour = new SortedList>(), + Alpha = new SortedList>(), + BlendingParameters = new SortedList>(), + FlipH = new SortedList>(), + FlipV = new SortedList>() + }; + } + + /// + /// Returns all commands contained by this group unsorted. + /// + public virtual IEnumerable GetAllCommands() => lists.SelectMany(l => l); + + public void AddX(double startTime, double endTime, float startValue, float endValue, Easing easing) + => AddCommand(X, new StoryboardXCommand(startTime, endTime, startValue, endValue, easing)); + + public void AddY(double startTime, double endTime, float startValue, float endValue, Easing easing) + => AddCommand(Y, new StoryboardYCommand(startTime, endTime, startValue, endValue, easing)); + + public void AddScale(double startTime, double endTime, float startValue, float endValue, Easing easing) + => AddCommand(Scale, new StoryboardScaleCommand(startTime, endTime, startValue, endValue, easing)); + + public void AddVectorScale(double startTime, double endTime, Vector2 startValue, Vector2 endValue, Easing easing) + => AddCommand(VectorScale, new StoryboardVectorScaleCommand(startTime, endTime, startValue, endValue, easing)); + + public void AddRotation(double startTime, double endTime, float startValue, float endValue, Easing easing) + => AddCommand(Rotation, new StoryboardRotationCommand(startTime, endTime, startValue, endValue, easing)); + + public void AddColour(double startTime, double endTime, Color4 startValue, Color4 endValue, Easing easing) + => AddCommand(Colour, new StoryboardColourCommand(startTime, endTime, startValue, endValue, easing)); + + public void AddAlpha(double startTime, double endTime, float startValue, float endValue, Easing easing) + => AddCommand(Alpha, new StoryboardAlphaCommand(startTime, endTime, startValue, endValue, easing)); + + public void AddBlendingParameters(double startTime, double endTime, BlendingParameters startValue, BlendingParameters endValue, Easing easing) + => AddCommand(BlendingParameters, new StoryboardBlendingParametersCommand(startTime, endTime, startValue, endValue, easing)); + + public void AddFlipH(double startTime, double endTime, bool startValue, bool endValue, Easing easing) + => AddCommand(FlipH, new StoryboardFlipHCommand(startTime, endTime, startValue, endValue, easing)); + + public void AddFlipV(double startTime, double endTime, bool startValue, bool endValue, Easing easing) + => AddCommand(FlipV, new StoryboardFlipVCommand(startTime, endTime, startValue, endValue, easing)); + + /// + /// Adds the given storyboard to the target . + /// Can be overriden to apply custom effects to the given command before adding it to the list (e.g. looping or time offsets). + /// + /// The value type of the target property affected by this storyboard command. + protected virtual void AddCommand(ICollection> list, StoryboardCommand command) + { + list.Add(command); + + if (command.StartTime < StartTime) + StartTime = command.StartTime; + + if (command.EndTime > EndTime) + EndTime = command.EndTime; + } + } +} diff --git a/osu.Game/Storyboards/Commands/StoryboardCommandList.cs b/osu.Game/Storyboards/Commands/StoryboardCommandList.cs new file mode 100644 index 0000000000..67012e9d49 --- /dev/null +++ b/osu.Game/Storyboards/Commands/StoryboardCommandList.cs @@ -0,0 +1,41 @@ +// 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.Storyboards.Commands +{ + // public class StoryboardCommandList : IStoryboardCommandList + // { + // // todo: change to sorted list and avoid enumerable type on exposed properties? + // private readonly List> commands = new List>(); + // + // public IEnumerable> Commands => commands.OrderBy(c => c.StartTime); + // + // IEnumerable IStoryboardCommandList.Commands => Commands; + // public bool HasCommands => commands.Count > 0; + // + // public double StartTime { get; private set; } = double.MaxValue; + // public double EndTime { get; private set; } = double.MinValue; + // + // public T? StartValue { get; private set; } + // public T? EndValue { get; private set; } + // + // public void Add(StoryboardCommand command) + // { + // commands.Add(command); + // + // if (command.StartTime < StartTime) + // { + // StartValue = command.StartValue; + // StartTime = command.StartTime; + // } + // + // if (command.EndTime > EndTime) + // { + // EndValue = command.EndValue; + // EndTime = command.EndTime; + // } + // } + // + // public override string ToString() => $"{commands.Count} command(s)"; + // } +} diff --git a/osu.Game/Storyboards/Commands/StoryboardFlipHCommand.cs b/osu.Game/Storyboards/Commands/StoryboardFlipHCommand.cs new file mode 100644 index 0000000000..9bcb687d3c --- /dev/null +++ b/osu.Game/Storyboards/Commands/StoryboardFlipHCommand.cs @@ -0,0 +1,23 @@ +// 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.Graphics; +using osu.Framework.Graphics.Transforms; +using osu.Game.Storyboards.Drawables; + +namespace osu.Game.Storyboards.Commands +{ + public class StoryboardFlipHCommand : StoryboardCommand + { + public StoryboardFlipHCommand(double startTime, double endTime, bool startValue, bool endValue, Easing easing) + : base(startTime, endTime, startValue, endValue, easing) + { + } + + public override void SetInitialValue(Drawable d) => ((IDrawableStoryboardElement)d).FlipH = StartValue; + + public override TransformSequence ApplyTransform(Drawable d) + => d.TransformTo(nameof(IDrawableStoryboardElement.FlipH), StartValue).Delay(Duration) + .TransformTo(nameof(IDrawableStoryboardElement.FlipH), EndValue); + } +} diff --git a/osu.Game/Storyboards/Commands/StoryboardFlipVCommand.cs b/osu.Game/Storyboards/Commands/StoryboardFlipVCommand.cs new file mode 100644 index 0000000000..9f1f5faa33 --- /dev/null +++ b/osu.Game/Storyboards/Commands/StoryboardFlipVCommand.cs @@ -0,0 +1,23 @@ +// 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.Graphics; +using osu.Framework.Graphics.Transforms; +using osu.Game.Storyboards.Drawables; + +namespace osu.Game.Storyboards.Commands +{ + public class StoryboardFlipVCommand : StoryboardCommand + { + public StoryboardFlipVCommand(double startTime, double endTime, bool startValue, bool endValue, Easing easing) + : base(startTime, endTime, startValue, endValue, easing) + { + } + + public override void SetInitialValue(Drawable d) => ((IDrawableStoryboardElement)d).FlipV = StartValue; + + public override TransformSequence ApplyTransform(Drawable d) + => d.TransformTo(nameof(IDrawableStoryboardElement.FlipV), StartValue).Delay(Duration) + .TransformTo(nameof(IDrawableStoryboardElement.FlipV), EndValue); + } +} diff --git a/osu.Game/Storyboards/Commands/StoryboardLoopingGroup.cs b/osu.Game/Storyboards/Commands/StoryboardLoopingGroup.cs new file mode 100644 index 0000000000..e520353bd6 --- /dev/null +++ b/osu.Game/Storyboards/Commands/StoryboardLoopingGroup.cs @@ -0,0 +1,71 @@ +// 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.Collections.Generic; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Transforms; + +namespace osu.Game.Storyboards.Commands +{ + public class StoryboardLoopingGroup : StoryboardCommandGroup + { + public double LoopStartTime; + + /// + /// The total number of times this loop is played back. Always greater than zero. + /// + public readonly int TotalIterations; + + /// + /// Construct a new command loop. + /// + /// The start time of the loop. + /// The number of times the loop should repeat. Should be greater than zero. Zero means a single playback. + public StoryboardLoopingGroup(double startTime, int repeatCount) + { + if (repeatCount < 0) throw new ArgumentException("Repeat count must be zero or above.", nameof(repeatCount)); + + LoopStartTime = startTime; + TotalIterations = repeatCount + 1; + } + + protected override void AddCommand(ICollection> list, StoryboardCommand command) + { + // todo: this is broke! + double fullLoopDuration = EndTime - StartTime; + double loopDelay = fullLoopDuration - command.EndTime + command.StartTime; + base.AddCommand(list, new StoryboardLoopingCommand(command, LoopStartTime, TotalIterations, loopDelay)); + } + + public override string ToString() => $"{LoopStartTime} x{TotalIterations}"; + + private class StoryboardLoopingCommand : StoryboardCommand + { + private readonly StoryboardCommand command; + private readonly int loopCount; + private readonly double loopDelay; + + public StoryboardLoopingCommand(StoryboardCommand command, double loopStartTime, int loopCount, double loopDelay) + // In an ideal world, we would multiply the command duration by TotalIterations in command end time. + // Unfortunately this would clash with how stable handled end times, and results in some storyboards playing outro + // sequences for minutes or hours. + : base(loopStartTime + command.StartTime, loopStartTime + command.EndTime, command.StartValue, command.EndValue, command.Easing) + { + this.command = command; + this.loopCount = loopCount; + this.loopDelay = loopDelay; + } + + public override void SetInitialValue(Drawable d) => command.SetInitialValue(d); + + public override TransformSequence ApplyTransform(Drawable d) + { + if (loopCount == 0) + return command.ApplyTransform(d); + + return command.ApplyTransform(d).Loop(loopDelay, loopCount); + } + } + } +} diff --git a/osu.Game/Storyboards/Commands/StoryboardRotationCommand.cs b/osu.Game/Storyboards/Commands/StoryboardRotationCommand.cs new file mode 100644 index 0000000000..c56dcd130f --- /dev/null +++ b/osu.Game/Storyboards/Commands/StoryboardRotationCommand.cs @@ -0,0 +1,21 @@ +// 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.Graphics; +using osu.Framework.Graphics.Transforms; + +namespace osu.Game.Storyboards.Commands +{ + public class StoryboardRotationCommand : StoryboardCommand + { + public StoryboardRotationCommand(double startTime, double endTime, float startValue, float endValue, Easing easing) + : base(startTime, endTime, startValue, endValue, easing) + { + } + + public override void SetInitialValue(Drawable d) => d.Rotation = StartValue; + + public override TransformSequence ApplyTransform(Drawable d) + => d.RotateTo(StartValue).Then().RotateTo(EndValue, Duration, Easing); + } +} diff --git a/osu.Game/Storyboards/Commands/StoryboardScaleCommand.cs b/osu.Game/Storyboards/Commands/StoryboardScaleCommand.cs new file mode 100644 index 0000000000..9dbdd6ebd8 --- /dev/null +++ b/osu.Game/Storyboards/Commands/StoryboardScaleCommand.cs @@ -0,0 +1,22 @@ +// 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.Graphics; +using osu.Framework.Graphics.Transforms; +using osuTK; + +namespace osu.Game.Storyboards.Commands +{ + public class StoryboardScaleCommand : StoryboardCommand + { + public StoryboardScaleCommand(double startTime, double endTime, float startValue, float endValue, Easing easing) + : base(startTime, endTime, startValue, endValue, easing) + { + } + + public override void SetInitialValue(Drawable d) => d.Scale = new Vector2(StartValue); + + public override TransformSequence ApplyTransform(Drawable d) + => d.ScaleTo(StartValue).Then().ScaleTo(EndValue, Duration, Easing); + } +} diff --git a/osu.Game/Storyboards/CommandTrigger.cs b/osu.Game/Storyboards/Commands/StoryboardTriggerGroup.cs similarity index 68% rename from osu.Game/Storyboards/CommandTrigger.cs rename to osu.Game/Storyboards/Commands/StoryboardTriggerGroup.cs index 011f345df2..dfb6f8cb1b 100644 --- a/osu.Game/Storyboards/CommandTrigger.cs +++ b/osu.Game/Storyboards/Commands/StoryboardTriggerGroup.cs @@ -1,16 +1,17 @@ // 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.Storyboards +namespace osu.Game.Storyboards.Commands { - public class CommandTrigger : CommandTimelineGroup + // todo: this is not implemented and has never been, keep that in mind. + public class StoryboardTriggerGroup : StoryboardCommandGroup { public string TriggerName; public double TriggerStartTime; public double TriggerEndTime; public int GroupNumber; - public CommandTrigger(string triggerName, double startTime, double endTime, int groupNumber) + public StoryboardTriggerGroup(string triggerName, double startTime, double endTime, int groupNumber) { TriggerName = triggerName; TriggerStartTime = startTime; diff --git a/osu.Game/Storyboards/Commands/StoryboardVectorScaleCommand.cs b/osu.Game/Storyboards/Commands/StoryboardVectorScaleCommand.cs new file mode 100644 index 0000000000..fefb21b257 --- /dev/null +++ b/osu.Game/Storyboards/Commands/StoryboardVectorScaleCommand.cs @@ -0,0 +1,24 @@ +// 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.Graphics; +using osu.Framework.Graphics.Transforms; +using osu.Game.Storyboards.Drawables; +using osuTK; + +namespace osu.Game.Storyboards.Commands +{ + public class StoryboardVectorScaleCommand : StoryboardCommand + { + public StoryboardVectorScaleCommand(double startTime, double endTime, Vector2 startValue, Vector2 endValue, Easing easing) + : base(startTime, endTime, startValue, endValue, easing) + { + } + + public override void SetInitialValue(Drawable d) => ((IDrawableStoryboardElement)d).VectorScale = StartValue; + + public override TransformSequence ApplyTransform(Drawable d) + => d.TransformTo(nameof(IDrawableStoryboardElement.VectorScale), StartValue).Then() + .TransformTo(nameof(IDrawableStoryboardElement.VectorScale), EndValue, Duration, Easing); + } +} diff --git a/osu.Game/Storyboards/Commands/StoryboardXCommand.cs b/osu.Game/Storyboards/Commands/StoryboardXCommand.cs new file mode 100644 index 0000000000..a9f9cd4f8f --- /dev/null +++ b/osu.Game/Storyboards/Commands/StoryboardXCommand.cs @@ -0,0 +1,21 @@ +// 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.Graphics; +using osu.Framework.Graphics.Transforms; + +namespace osu.Game.Storyboards.Commands +{ + public class StoryboardXCommand : StoryboardCommand + { + public StoryboardXCommand(double startTime, double endTime, float startValue, float endValue, Easing easing) + : base(startTime, endTime, startValue, endValue, easing) + { + } + + public override void SetInitialValue(Drawable d) => d.X = StartValue; + + public override TransformSequence ApplyTransform(Drawable d) + => d.MoveToX(StartValue).Then().MoveToX(EndValue, Duration, Easing); + } +} diff --git a/osu.Game/Storyboards/Commands/StoryboardYCommand.cs b/osu.Game/Storyboards/Commands/StoryboardYCommand.cs new file mode 100644 index 0000000000..eb30b36720 --- /dev/null +++ b/osu.Game/Storyboards/Commands/StoryboardYCommand.cs @@ -0,0 +1,21 @@ +// 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.Graphics; +using osu.Framework.Graphics.Transforms; + +namespace osu.Game.Storyboards.Commands +{ + public class StoryboardYCommand : StoryboardCommand + { + public StoryboardYCommand(double startTime, double endTime, float startValue, float endValue, Easing easing) + : base(startTime, endTime, startValue, endValue, easing) + { + } + + public override void SetInitialValue(Drawable d) => d.Y = StartValue; + + public override TransformSequence ApplyTransform(Drawable d) + => d.MoveToY(StartValue).Then().MoveToY(EndValue, Duration, Easing); + } +} diff --git a/osu.Game/Storyboards/Drawables/IDrawableStoryboardElement.cs b/osu.Game/Storyboards/Drawables/IDrawableStoryboardElement.cs index 6652b5509c..04bae88c76 100644 --- a/osu.Game/Storyboards/Drawables/IDrawableStoryboardElement.cs +++ b/osu.Game/Storyboards/Drawables/IDrawableStoryboardElement.cs @@ -1,12 +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.Graphics.Transforms; +using osu.Framework.Graphics; using osuTK; namespace osu.Game.Storyboards.Drawables { - public interface IDrawableStoryboardElement : ITransformable + public interface IDrawableStoryboardElement : IDrawable { bool FlipH { get; set; } bool FlipV { get; set; } diff --git a/osu.Game/Storyboards/StoryboardAnimation.cs b/osu.Game/Storyboards/StoryboardAnimation.cs index 173acf7ff1..0b714633c9 100644 --- a/osu.Game/Storyboards/StoryboardAnimation.cs +++ b/osu.Game/Storyboards/StoryboardAnimation.cs @@ -7,7 +7,7 @@ using osu.Game.Storyboards.Drawables; namespace osu.Game.Storyboards { - public class StoryboardAnimation : StoryboardElementWithDuration + public class StoryboardAnimation : StoryboardSprite { public int FrameCount; public double FrameDelay; @@ -21,7 +21,7 @@ namespace osu.Game.Storyboards LoopType = loopType; } - public override DrawableStoryboardAnimation CreateStoryboardDrawable() => new DrawableStoryboardAnimation(this); + public override Drawable CreateDrawable() => new DrawableStoryboardAnimation(this); } public enum AnimationLoopType diff --git a/osu.Game/Storyboards/StoryboardElementWithDuration.cs b/osu.Game/Storyboards/StoryboardElementWithDuration.cs deleted file mode 100644 index 06924a26ef..0000000000 --- a/osu.Game/Storyboards/StoryboardElementWithDuration.cs +++ /dev/null @@ -1,236 +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 System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Graphics; -using osu.Game.Storyboards.Drawables; -using osuTK; - -namespace osu.Game.Storyboards -{ - public abstract class StoryboardElementWithDuration : IStoryboardElementWithDuration - { - protected readonly List Loops = new List(); - private readonly List triggers = new List(); - - public string Path { get; } - public bool IsDrawable => HasCommands; - - public Anchor Origin; - public Vector2 InitialPosition; - - public readonly CommandTimelineGroup TimelineGroup = new CommandTimelineGroup(); - - public double StartTime - { - get - { - // To get the initial start time, we need to check whether the first alpha command to exist (across all loops) has a StartValue of zero. - // A StartValue of zero governs, above all else, the first valid display time of a sprite. - // - // You can imagine that the first command of each type decides that type's start value, so if the initial alpha is zero, - // anything before that point can be ignored (the sprite is not visible after all). - var alphaCommands = new List<(double startTime, bool isZeroStartValue)>(); - - var command = TimelineGroup.Alpha.Commands.FirstOrDefault(); - if (command != null) alphaCommands.Add((command.StartTime, command.StartValue == 0)); - - foreach (var loop in Loops) - { - command = loop.Alpha.Commands.FirstOrDefault(); - if (command != null) alphaCommands.Add((command.StartTime + loop.LoopStartTime, command.StartValue == 0)); - } - - if (alphaCommands.Count > 0) - { - var firstAlpha = alphaCommands.MinBy(t => t.startTime); - - if (firstAlpha.isZeroStartValue) - return firstAlpha.startTime; - } - - return EarliestTransformTime; - } - } - - public double EarliestTransformTime - { - get - { - // If we got to this point, either no alpha commands were present, or the earliest had a non-zero start value. - // The sprite's StartTime will be determined by the earliest command, regardless of type. - double earliestStartTime = TimelineGroup.StartTime; - foreach (var l in Loops) - earliestStartTime = Math.Min(earliestStartTime, l.StartTime); - return earliestStartTime; - } - } - - public double EndTime - { - get - { - double latestEndTime = TimelineGroup.EndTime; - - foreach (var l in Loops) - latestEndTime = Math.Max(latestEndTime, l.EndTime); - - return latestEndTime; - } - } - - public double EndTimeForDisplay - { - get - { - double latestEndTime = TimelineGroup.EndTime; - - foreach (var l in Loops) - latestEndTime = Math.Max(latestEndTime, l.StartTime + l.CommandsDuration * l.TotalIterations); - - return latestEndTime; - } - } - - public bool HasCommands => TimelineGroup.HasCommands || Loops.Any(l => l.HasCommands); - - protected StoryboardElementWithDuration(string path, Anchor origin, Vector2 initialPosition) - { - Path = path; - Origin = origin; - InitialPosition = initialPosition; - } - - public abstract Drawable CreateDrawable(); - - public CommandLoop AddLoop(double startTime, int repeatCount) - { - var loop = new CommandLoop(startTime, repeatCount); - Loops.Add(loop); - return loop; - } - - public CommandTrigger AddTrigger(string triggerName, double startTime, double endTime, int groupNumber) - { - var trigger = new CommandTrigger(triggerName, startTime, endTime, groupNumber); - triggers.Add(trigger); - return trigger; - } - - protected IEnumerable.TypedCommand> GetCommands(CommandTimelineSelector timelineSelector, IEnumerable>? triggeredGroups) - { - var commands = TimelineGroup.GetCommands(timelineSelector); - foreach (var loop in Loops) - commands = commands.Concat(loop.GetCommands(timelineSelector)); - - if (triggeredGroups != null) - { - foreach (var pair in triggeredGroups) - commands = commands.Concat(pair.Item1.GetCommands(timelineSelector, pair.Item2)); - } - - return commands; - } - - public override string ToString() - => $"{Path}, {Origin}, {InitialPosition}"; - } - - public abstract class StoryboardElementWithDuration : StoryboardElementWithDuration - where U : Drawable, IDrawableStoryboardElement - { - private delegate void DrawablePropertyInitializer(U drawable, T value); - - protected StoryboardElementWithDuration(string path, Anchor origin, Vector2 initialPosition) - : base(path, origin, initialPosition) - { - } - - public override Drawable CreateDrawable() => CreateStoryboardDrawable(); - - public abstract U CreateStoryboardDrawable(); - - public void ApplyTransforms(U drawable, IEnumerable>? triggeredGroups = null) - { - // For performance reasons, we need to apply the commands in order by start time. Not doing so will cause many functions to be interleaved, resulting in O(n^2) complexity. - // To achieve this, commands are "generated" as pairs of (command, initFunc, transformFunc) and batched into a contiguous list - // The list is then stably-sorted (to preserve command order), and applied to the drawable sequentially. - - List> generated = new List>(); - - generateCommands(generated, GetCommands(g => g.X, triggeredGroups), (d, value) => d.X = value); - generateCommands(generated, GetCommands(g => g.Y, triggeredGroups), (d, value) => d.Y = value); - generateCommands(generated, GetCommands(g => g.Scale, triggeredGroups), (d, value) => d.Scale = value); - generateCommands(generated, GetCommands(g => g.Rotation, triggeredGroups), (d, value) => d.Rotation = value); - generateCommands(generated, GetCommands(g => g.Colour, triggeredGroups), (d, value) => d.Colour = value); - generateCommands(generated, GetCommands(g => g.Alpha, triggeredGroups), (d, value) => d.Alpha = value); - generateCommands(generated, GetCommands(g => g.BlendingParameters, triggeredGroups), (d, value) => d.Blending = value, false); - generateCommands(generated, GetCommands(g => g.VectorScale, triggeredGroups), (d, value) => d.VectorScale = value); - generateCommands(generated, GetCommands(g => g.FlipH, triggeredGroups), (d, value) => d.FlipH = value, false); - generateCommands(generated, GetCommands(g => g.FlipV, triggeredGroups), (d, value) => d.FlipV = value, false); - - foreach (var command in generated.OrderBy(g => g.StartTime)) - command.ApplyTo(drawable); - } - - private void generateCommands(List> resultList, IEnumerable.TypedCommand> commands, - DrawablePropertyInitializer initializeProperty, bool alwaysInitialize = true) - { - bool initialized = false; - - foreach (var command in commands) - { - DrawablePropertyInitializer? initFunc = null; - - if (!initialized) - { - if (alwaysInitialize || command.StartTime == command.EndTime) - initFunc = initializeProperty; - initialized = true; - } - - resultList.Add(new GeneratedCommand(command, initFunc)); - } - } - - private interface IGeneratedCommand - where TDrawable : U - { - double StartTime { get; } - - void ApplyTo(TDrawable drawable); - } - - private readonly struct GeneratedCommand : IGeneratedCommand - where TDrawable : U - { - public double StartTime => command.StartTime; - - private readonly DrawablePropertyInitializer? initializeProperty; - private readonly CommandTimeline.TypedCommand command; - - public GeneratedCommand(CommandTimeline.TypedCommand command, DrawablePropertyInitializer? initializeProperty) - { - this.command = command; - this.initializeProperty = initializeProperty; - } - - public void ApplyTo(TDrawable drawable) - { - initializeProperty?.Invoke(drawable, command.StartValue); - - using (drawable.BeginAbsoluteSequence(command.StartTime)) - { - var sequence = command.IsParameterCommand - ? drawable.TransformTo(command.PropertyName, command.StartValue).Delay(command.Duration).TransformTo(command.PropertyName, command.EndValue) - : drawable.TransformTo(command.PropertyName, command.StartValue).Then().TransformTo(command.PropertyName, command.EndValue, command.Duration, command.Easing); - - if (command.LoopCount > 0) - sequence.Loop(command.Delay, command.LoopCount); - } - } - } - } -} diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index 8eaab9428d..dfd184a909 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -1,19 +1,164 @@ // 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.Collections.Generic; +using System.Linq; using osu.Framework.Graphics; +using osu.Game.Storyboards.Commands; using osu.Game.Storyboards.Drawables; using osuTK; namespace osu.Game.Storyboards { - public class StoryboardSprite : StoryboardElementWithDuration + public class StoryboardSprite : IStoryboardElementWithDuration { - public StoryboardSprite(string path, Anchor origin, Vector2 initialPosition) - : base(path, origin, initialPosition) + private readonly List loopGroups = new List(); + private readonly List triggerGroups = new List(); + + public string Path { get; } + public bool IsDrawable => HasCommands; + + public Anchor Origin; + public Vector2 InitialPosition; + + public readonly StoryboardCommandGroup Group = new StoryboardCommandGroup(); + + public double StartTime { + get + { + // To get the initial start time, we need to check whether the first alpha command to exist (across all loops) has a StartValue of zero. + // A StartValue of zero governs, above all else, the first valid display time of a sprite. + // + // You can imagine that the first command of each type decides that type's start value, so if the initial alpha is zero, + // anything before that point can be ignored (the sprite is not visible after all). + var alphaCommands = new List<(double startTime, bool isZeroStartValue)>(); + + var command = Group.Alpha.FirstOrDefault(); + if (command != null) alphaCommands.Add((command.StartTime, command.StartValue == 0)); + + foreach (var loop in loopGroups) + { + command = loop.Alpha.FirstOrDefault(); + if (command != null) alphaCommands.Add((command.StartTime + loop.LoopStartTime, command.StartValue == 0)); + } + + if (alphaCommands.Count > 0) + { + var firstAlpha = alphaCommands.MinBy(t => t.startTime); + + if (firstAlpha.isZeroStartValue) + return firstAlpha.startTime; + } + + return EarliestTransformTime; + } } - public override DrawableStoryboardSprite CreateStoryboardDrawable() => new DrawableStoryboardSprite(this); + public double EarliestTransformTime + { + get + { + // If we got to this point, either no alpha commands were present, or the earliest had a non-zero start value. + // The sprite's StartTime will be determined by the earliest command, regardless of type. + double earliestStartTime = Group.StartTime; + foreach (var l in loopGroups) + earliestStartTime = Math.Min(earliestStartTime, l.StartTime); + return earliestStartTime; + } + } + + public double EndTime + { + get + { + double latestEndTime = Group.EndTime; + + foreach (var l in loopGroups) + latestEndTime = Math.Max(latestEndTime, l.EndTime); + + return latestEndTime; + } + } + + public double EndTimeForDisplay + { + get + { + double latestEndTime = Group.StartTime; + + foreach (var l in loopGroups) + latestEndTime = Math.Max(latestEndTime, l.StartTime + l.Duration * l.TotalIterations); + + return latestEndTime; + } + } + + public bool HasCommands => Group.HasCommands || loopGroups.Any(l => l.HasCommands); + + public StoryboardSprite(string path, Anchor origin, Vector2 initialPosition) + { + Path = path; + Origin = origin; + InitialPosition = initialPosition; + } + + public virtual Drawable CreateDrawable() => new DrawableStoryboardSprite(this); + + public StoryboardLoopingGroup AddLoopingGroup(double loopStartTime, int repeatCount) + { + var loop = new StoryboardLoopingGroup(loopStartTime, repeatCount); + loopGroups.Add(loop); + return loop; + } + + public StoryboardTriggerGroup AddTriggerGroup(string triggerName, double startTime, double endTime, int groupNumber) + { + var trigger = new StoryboardTriggerGroup(triggerName, startTime, endTime, groupNumber); + triggerGroups.Add(trigger); + return trigger; + } + + public override string ToString() => $"{Path}, {Origin}, {InitialPosition}"; + + public void ApplyTransforms(Drawable drawable, IEnumerable>? triggeredGroups = null) + { + // For performance reasons, we need to apply the commands in order by start time. Not doing so will cause many functions to be interleaved, resulting in O(n^2) complexity. + + var commands = Group.GetAllCommands(); + commands = commands.Concat(loopGroups.SelectMany(l => l.GetAllCommands())); + + // todo: triggers are not implemented yet. + // if (triggeredGroups != null) + // commands = commands.Concat(triggeredGroups.SelectMany(tuple => tuple.Item1.GetAllCommands(tuple.Item2))); + + foreach (var command in commands.OrderBy(c => c.StartTime)) + { + using (drawable.BeginAbsoluteSequence(command.StartTime)) + command.ApplyTransform(drawable); + } + } + + // todo: need to revisit property initialisation. apparently it has to be done per first command of every affected property (transforms are supposed to do that already?). + // private void generateCommands(List resultList, IEnumerable.TypedCommand> commands, + // DrawablePropertyInitializer initializeProperty, DrawableTransform transform, bool alwaysInitialize = true) + // { + // bool initialized = false; + // + // foreach (var command in commands) + // { + // DrawablePropertyInitializer? initFunc = null; + // + // if (!initialized) + // { + // if (alwaysInitialize || command.StartTime == command.EndTime) + // initFunc = initializeProperty; + // initialized = true; + // } + // + // resultList.Add(new GeneratedCommand(command, initFunc, transform)); + // } + // } } } From 9b77d8c972abefe0e7e0d524e26a90b457f41abf Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 8 Mar 2024 01:59:26 +0300 Subject: [PATCH 3384/4092] Fix group start/end time not calculating correctly --- osu.Game/Storyboards/Commands/StoryboardCommandGroup.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Storyboards/Commands/StoryboardCommandGroup.cs b/osu.Game/Storyboards/Commands/StoryboardCommandGroup.cs index 02c43c9f60..5bb7ee6acf 100644 --- a/osu.Game/Storyboards/Commands/StoryboardCommandGroup.cs +++ b/osu.Game/Storyboards/Commands/StoryboardCommandGroup.cs @@ -30,13 +30,13 @@ namespace osu.Game.Storyboards.Commands /// Returns the earliest start time of the commands added to this group. /// [JsonIgnore] - public double StartTime { get; private set; } + public double StartTime { get; private set; } = double.MaxValue; /// /// Returns the latest end time of the commands added to this group. /// [JsonIgnore] - public double EndTime { get; private set; } + public double EndTime { get; private set; } = double.MinValue; [JsonIgnore] public double Duration => EndTime - StartTime; From 6c257e515996d04a7e31c35ac9bf7219442e2f2e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 8 Mar 2024 01:59:42 +0300 Subject: [PATCH 3385/4092] Fix `HasCommands` property not set at all --- osu.Game/Storyboards/Commands/StoryboardCommandGroup.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Storyboards/Commands/StoryboardCommandGroup.cs b/osu.Game/Storyboards/Commands/StoryboardCommandGroup.cs index 5bb7ee6acf..0d4a79079b 100644 --- a/osu.Game/Storyboards/Commands/StoryboardCommandGroup.cs +++ b/osu.Game/Storyboards/Commands/StoryboardCommandGroup.cs @@ -104,6 +104,7 @@ namespace osu.Game.Storyboards.Commands protected virtual void AddCommand(ICollection> list, StoryboardCommand command) { list.Add(command); + HasCommands = true; if (command.StartTime < StartTime) StartTime = command.StartTime; From 87b065b8c3c82a365be4a2b9274042480e417a36 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 8 Mar 2024 02:00:23 +0300 Subject: [PATCH 3386/4092] Fix incorrect start time calculations `LoopStartTime` is now baked into each `IStoryboardCommand`. --- osu.Game/Storyboards/StoryboardSprite.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index dfd184a909..fc3c5d343c 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -41,7 +41,7 @@ namespace osu.Game.Storyboards foreach (var loop in loopGroups) { command = loop.Alpha.FirstOrDefault(); - if (command != null) alphaCommands.Add((command.StartTime + loop.LoopStartTime, command.StartValue == 0)); + if (command != null) alphaCommands.Add((command.StartTime, command.StartValue == 0)); } if (alphaCommands.Count > 0) @@ -120,8 +120,6 @@ namespace osu.Game.Storyboards return trigger; } - public override string ToString() => $"{Path}, {Origin}, {InitialPosition}"; - public void ApplyTransforms(Drawable drawable, IEnumerable>? triggeredGroups = null) { // For performance reasons, we need to apply the commands in order by start time. Not doing so will cause many functions to be interleaved, resulting in O(n^2) complexity. @@ -140,6 +138,8 @@ namespace osu.Game.Storyboards } } + public override string ToString() => $"{Path}, {Origin}, {InitialPosition}"; + // todo: need to revisit property initialisation. apparently it has to be done per first command of every affected property (transforms are supposed to do that already?). // private void generateCommands(List resultList, IEnumerable.TypedCommand> commands, // DrawablePropertyInitializer initializeProperty, DrawableTransform transform, bool alwaysInitialize = true) From 3755dd059af7855219a1867d894aee803e722fa2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 8 Mar 2024 01:58:58 +0300 Subject: [PATCH 3387/4092] Calculate loop delays at point of transform application --- .../Commands/StoryboardLoopingGroup.cs | 36 +++++++++---------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/osu.Game/Storyboards/Commands/StoryboardLoopingGroup.cs b/osu.Game/Storyboards/Commands/StoryboardLoopingGroup.cs index e520353bd6..39b81ead28 100644 --- a/osu.Game/Storyboards/Commands/StoryboardLoopingGroup.cs +++ b/osu.Game/Storyboards/Commands/StoryboardLoopingGroup.cs @@ -10,7 +10,7 @@ namespace osu.Game.Storyboards.Commands { public class StoryboardLoopingGroup : StoryboardCommandGroup { - public double LoopStartTime; + private readonly double loopStartTime; /// /// The total number of times this loop is played back. Always greater than zero. @@ -26,45 +26,41 @@ namespace osu.Game.Storyboards.Commands { if (repeatCount < 0) throw new ArgumentException("Repeat count must be zero or above.", nameof(repeatCount)); - LoopStartTime = startTime; + loopStartTime = startTime; TotalIterations = repeatCount + 1; } protected override void AddCommand(ICollection> list, StoryboardCommand command) - { - // todo: this is broke! - double fullLoopDuration = EndTime - StartTime; - double loopDelay = fullLoopDuration - command.EndTime + command.StartTime; - base.AddCommand(list, new StoryboardLoopingCommand(command, LoopStartTime, TotalIterations, loopDelay)); - } + => base.AddCommand(list, new StoryboardLoopingCommand(command, this)); - public override string ToString() => $"{LoopStartTime} x{TotalIterations}"; + public override string ToString() => $"{loopStartTime} x{TotalIterations}"; private class StoryboardLoopingCommand : StoryboardCommand { private readonly StoryboardCommand command; - private readonly int loopCount; - private readonly double loopDelay; + private readonly StoryboardLoopingGroup loopingGroup; - public StoryboardLoopingCommand(StoryboardCommand command, double loopStartTime, int loopCount, double loopDelay) + public StoryboardLoopingCommand(StoryboardCommand command, StoryboardLoopingGroup loopingGroup) // In an ideal world, we would multiply the command duration by TotalIterations in command end time. // Unfortunately this would clash with how stable handled end times, and results in some storyboards playing outro // sequences for minutes or hours. - : base(loopStartTime + command.StartTime, loopStartTime + command.EndTime, command.StartValue, command.EndValue, command.Easing) + : base(loopingGroup.loopStartTime + command.StartTime, loopingGroup.loopStartTime + command.EndTime, command.StartValue, command.EndValue, command.Easing) { this.command = command; - this.loopCount = loopCount; - this.loopDelay = loopDelay; + this.loopingGroup = loopingGroup; } - public override void SetInitialValue(Drawable d) => command.SetInitialValue(d); + public override string PropertyName => command.PropertyName; - public override TransformSequence ApplyTransform(Drawable d) + public override void ApplyInitialValue(Drawable d) => command.ApplyInitialValue(d); + + public override TransformSequence ApplyTransforms(Drawable d) { - if (loopCount == 0) - return command.ApplyTransform(d); + if (loopingGroup.TotalIterations == 0) + return command.ApplyTransforms(d); - return command.ApplyTransform(d).Loop(loopDelay, loopCount); + double loopingGroupDuration = loopingGroup.Duration; + return command.ApplyTransforms(d).Loop(loopingGroupDuration - Duration, loopingGroup.TotalIterations); } } } From 2ca36254f46c3501d97a8782bcc7a699104557d1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 8 Mar 2024 02:02:22 +0300 Subject: [PATCH 3388/4092] Fix comparison interface not implemented on storyboard command classes --- osu.Game/Storyboards/Commands/StoryboardCommand.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Storyboards/Commands/StoryboardCommand.cs b/osu.Game/Storyboards/Commands/StoryboardCommand.cs index 883b9f57c1..1647faf243 100644 --- a/osu.Game/Storyboards/Commands/StoryboardCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardCommand.cs @@ -1,12 +1,13 @@ // 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.Graphics; using osu.Framework.Graphics.Transforms; namespace osu.Game.Storyboards.Commands { - public abstract class StoryboardCommand : IStoryboardCommand + public abstract class StoryboardCommand : IStoryboardCommand, IComparable> { public double StartTime { get; } public double EndTime { get; } @@ -41,10 +42,14 @@ namespace osu.Game.Storyboards.Commands /// public abstract TransformSequence ApplyTransform(Drawable d); - public int CompareTo(IStoryboardCommand other) + public int CompareTo(StoryboardCommand? other) { + if (other == null) + return 1; + int result = StartTime.CompareTo(other.StartTime); - if (result != 0) return result; + if (result != 0) + return result; return EndTime.CompareTo(other.EndTime); } From b450abb687823132c9bb3c6b1041bb70bb05d8ea Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 8 Mar 2024 02:02:45 +0300 Subject: [PATCH 3389/4092] Support applying initial values of storyboard commands --- .../Storyboards/Commands/IStoryboardCommand.cs | 14 +++++++++++++- .../Storyboards/Commands/StoryboardAlphaCommand.cs | 8 ++++++-- .../StoryboardBlendingParametersCommand.cs | 9 ++++++--- .../Commands/StoryboardColourCommand.cs | 8 ++++++-- osu.Game/Storyboards/Commands/StoryboardCommand.cs | 12 ++++-------- .../Storyboards/Commands/StoryboardFlipHCommand.cs | 6 ++++-- .../Storyboards/Commands/StoryboardFlipVCommand.cs | 6 ++++-- .../Commands/StoryboardRotationCommand.cs | 6 ++++-- .../Storyboards/Commands/StoryboardScaleCommand.cs | 6 ++++-- .../Commands/StoryboardVectorScaleCommand.cs | 6 ++++-- .../Storyboards/Commands/StoryboardXCommand.cs | 6 ++++-- .../Storyboards/Commands/StoryboardYCommand.cs | 6 ++++-- osu.Game/Storyboards/StoryboardSprite.cs | 10 +++++++++- 13 files changed, 72 insertions(+), 31 deletions(-) diff --git a/osu.Game/Storyboards/Commands/IStoryboardCommand.cs b/osu.Game/Storyboards/Commands/IStoryboardCommand.cs index 848dcab575..05613a987d 100644 --- a/osu.Game/Storyboards/Commands/IStoryboardCommand.cs +++ b/osu.Game/Storyboards/Commands/IStoryboardCommand.cs @@ -18,11 +18,23 @@ namespace osu.Game.Storyboards.Commands /// double EndTime { get; } + /// + /// The name of the property affected by this storyboard command. + /// Used to apply initial property values based on the list of commands given in . + /// + string PropertyName { get; } + + /// + /// Sets the value of the corresponding property in to the start value of this command. + /// + /// The target drawable. + void ApplyInitialValue(Drawable d); + /// /// Applies the transforms described by this storyboard command to the target drawable. /// /// The target drawable. /// The sequence of transforms applied to the target drawable. - TransformSequence ApplyTransform(Drawable d); + TransformSequence ApplyTransforms(Drawable d); } } diff --git a/osu.Game/Storyboards/Commands/StoryboardAlphaCommand.cs b/osu.Game/Storyboards/Commands/StoryboardAlphaCommand.cs index 729ecd72a7..1d91d6ccc1 100644 --- a/osu.Game/Storyboards/Commands/StoryboardAlphaCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardAlphaCommand.cs @@ -13,7 +13,11 @@ namespace osu.Game.Storyboards.Commands { } - public override void SetInitialValue(Drawable d) => d.Alpha = StartValue; - public override TransformSequence ApplyTransform(Drawable d) => d.FadeTo(StartValue).Then().FadeTo(EndValue, Duration, Easing); + public override string PropertyName => nameof(Drawable.Alpha); + + public override void ApplyInitialValue(Drawable d) => d.Alpha = StartValue; + + public override TransformSequence ApplyTransforms(Drawable d) + => d.FadeTo(StartValue).Then().FadeTo(EndValue, Duration, Easing); } } diff --git a/osu.Game/Storyboards/Commands/StoryboardBlendingParametersCommand.cs b/osu.Game/Storyboards/Commands/StoryboardBlendingParametersCommand.cs index cc54909837..3a2d372a66 100644 --- a/osu.Game/Storyboards/Commands/StoryboardBlendingParametersCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardBlendingParametersCommand.cs @@ -13,9 +13,12 @@ namespace osu.Game.Storyboards.Commands { } - public override void SetInitialValue(Drawable d) => d.Blending = StartValue; + public override string PropertyName => nameof(Drawable.Blending); - public override TransformSequence ApplyTransform(Drawable d) - => d.TransformTo(nameof(d.Blending), StartValue).Delay(Duration).TransformTo(nameof(d.Blending), EndValue); + public override void ApplyInitialValue(Drawable d) => d.Blending = StartValue; + + public override TransformSequence ApplyTransforms(Drawable d) + => d.TransformTo(nameof(d.Blending), StartValue).Delay(Duration) + .TransformTo(nameof(d.Blending), EndValue); } } diff --git a/osu.Game/Storyboards/Commands/StoryboardColourCommand.cs b/osu.Game/Storyboards/Commands/StoryboardColourCommand.cs index be56a1d71b..fbde7e1af7 100644 --- a/osu.Game/Storyboards/Commands/StoryboardColourCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardColourCommand.cs @@ -14,7 +14,11 @@ namespace osu.Game.Storyboards.Commands { } - public override void SetInitialValue(Drawable d) => d.Colour = StartValue; - public override TransformSequence ApplyTransform(Drawable d) => d.FadeColour(StartValue).Then().FadeColour(EndValue, Duration, Easing); + public override string PropertyName => nameof(Drawable.Colour); + + public override void ApplyInitialValue(Drawable d) => d.Colour = StartValue; + + public override TransformSequence ApplyTransforms(Drawable d) + => d.FadeColour(StartValue).Then().FadeColour(EndValue, Duration, Easing); } } diff --git a/osu.Game/Storyboards/Commands/StoryboardCommand.cs b/osu.Game/Storyboards/Commands/StoryboardCommand.cs index 1647faf243..58fcb148ff 100644 --- a/osu.Game/Storyboards/Commands/StoryboardCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardCommand.cs @@ -32,15 +32,11 @@ namespace osu.Game.Storyboards.Commands public T StartValue; public T EndValue; - /// - /// Sets the value of the corresponding property in to the start value of this command. - /// - public abstract void SetInitialValue(Drawable d); + public abstract string PropertyName { get; } - /// - /// Transforms a corresponding property in that corresponds to this command group with the specified parameters. - /// - public abstract TransformSequence ApplyTransform(Drawable d); + public abstract void ApplyInitialValue(Drawable d); + + public abstract TransformSequence ApplyTransforms(Drawable d); public int CompareTo(StoryboardCommand? other) { diff --git a/osu.Game/Storyboards/Commands/StoryboardFlipHCommand.cs b/osu.Game/Storyboards/Commands/StoryboardFlipHCommand.cs index 9bcb687d3c..c381b0bb64 100644 --- a/osu.Game/Storyboards/Commands/StoryboardFlipHCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardFlipHCommand.cs @@ -14,9 +14,11 @@ namespace osu.Game.Storyboards.Commands { } - public override void SetInitialValue(Drawable d) => ((IDrawableStoryboardElement)d).FlipH = StartValue; + public override string PropertyName => nameof(IDrawableStoryboardElement.FlipH); - public override TransformSequence ApplyTransform(Drawable d) + public override void ApplyInitialValue(Drawable d) => ((IDrawableStoryboardElement)d).FlipH = StartValue; + + public override TransformSequence ApplyTransforms(Drawable d) => d.TransformTo(nameof(IDrawableStoryboardElement.FlipH), StartValue).Delay(Duration) .TransformTo(nameof(IDrawableStoryboardElement.FlipH), EndValue); } diff --git a/osu.Game/Storyboards/Commands/StoryboardFlipVCommand.cs b/osu.Game/Storyboards/Commands/StoryboardFlipVCommand.cs index 9f1f5faa33..e43e5e9205 100644 --- a/osu.Game/Storyboards/Commands/StoryboardFlipVCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardFlipVCommand.cs @@ -14,9 +14,11 @@ namespace osu.Game.Storyboards.Commands { } - public override void SetInitialValue(Drawable d) => ((IDrawableStoryboardElement)d).FlipV = StartValue; + public override string PropertyName => nameof(IDrawableStoryboardElement.FlipV); - public override TransformSequence ApplyTransform(Drawable d) + public override void ApplyInitialValue(Drawable d) => ((IDrawableStoryboardElement)d).FlipV = StartValue; + + public override TransformSequence ApplyTransforms(Drawable d) => d.TransformTo(nameof(IDrawableStoryboardElement.FlipV), StartValue).Delay(Duration) .TransformTo(nameof(IDrawableStoryboardElement.FlipV), EndValue); } diff --git a/osu.Game/Storyboards/Commands/StoryboardRotationCommand.cs b/osu.Game/Storyboards/Commands/StoryboardRotationCommand.cs index c56dcd130f..2a449af843 100644 --- a/osu.Game/Storyboards/Commands/StoryboardRotationCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardRotationCommand.cs @@ -13,9 +13,11 @@ namespace osu.Game.Storyboards.Commands { } - public override void SetInitialValue(Drawable d) => d.Rotation = StartValue; + public override string PropertyName => nameof(Drawable.Rotation); - public override TransformSequence ApplyTransform(Drawable d) + public override void ApplyInitialValue(Drawable d) => d.Rotation = StartValue; + + public override TransformSequence ApplyTransforms(Drawable d) => d.RotateTo(StartValue).Then().RotateTo(EndValue, Duration, Easing); } } diff --git a/osu.Game/Storyboards/Commands/StoryboardScaleCommand.cs b/osu.Game/Storyboards/Commands/StoryboardScaleCommand.cs index 9dbdd6ebd8..bf9796148c 100644 --- a/osu.Game/Storyboards/Commands/StoryboardScaleCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardScaleCommand.cs @@ -14,9 +14,11 @@ namespace osu.Game.Storyboards.Commands { } - public override void SetInitialValue(Drawable d) => d.Scale = new Vector2(StartValue); + public override string PropertyName => nameof(Drawable.Scale); - public override TransformSequence ApplyTransform(Drawable d) + public override void ApplyInitialValue(Drawable d) => d.Scale = new Vector2(StartValue); + + public override TransformSequence ApplyTransforms(Drawable d) => d.ScaleTo(StartValue).Then().ScaleTo(EndValue, Duration, Easing); } } diff --git a/osu.Game/Storyboards/Commands/StoryboardVectorScaleCommand.cs b/osu.Game/Storyboards/Commands/StoryboardVectorScaleCommand.cs index fefb21b257..638dc6a5ee 100644 --- a/osu.Game/Storyboards/Commands/StoryboardVectorScaleCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardVectorScaleCommand.cs @@ -15,9 +15,11 @@ namespace osu.Game.Storyboards.Commands { } - public override void SetInitialValue(Drawable d) => ((IDrawableStoryboardElement)d).VectorScale = StartValue; + public override string PropertyName => nameof(IDrawableStoryboardElement.VectorScale); - public override TransformSequence ApplyTransform(Drawable d) + public override void ApplyInitialValue(Drawable d) => ((IDrawableStoryboardElement)d).VectorScale = StartValue; + + public override TransformSequence ApplyTransforms(Drawable d) => d.TransformTo(nameof(IDrawableStoryboardElement.VectorScale), StartValue).Then() .TransformTo(nameof(IDrawableStoryboardElement.VectorScale), EndValue, Duration, Easing); } diff --git a/osu.Game/Storyboards/Commands/StoryboardXCommand.cs b/osu.Game/Storyboards/Commands/StoryboardXCommand.cs index a9f9cd4f8f..809a77256c 100644 --- a/osu.Game/Storyboards/Commands/StoryboardXCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardXCommand.cs @@ -13,9 +13,11 @@ namespace osu.Game.Storyboards.Commands { } - public override void SetInitialValue(Drawable d) => d.X = StartValue; + public override string PropertyName => nameof(Drawable.X); - public override TransformSequence ApplyTransform(Drawable d) + public override void ApplyInitialValue(Drawable d) => d.X = StartValue; + + public override TransformSequence ApplyTransforms(Drawable d) => d.MoveToX(StartValue).Then().MoveToX(EndValue, Duration, Easing); } } diff --git a/osu.Game/Storyboards/Commands/StoryboardYCommand.cs b/osu.Game/Storyboards/Commands/StoryboardYCommand.cs index eb30b36720..d054135878 100644 --- a/osu.Game/Storyboards/Commands/StoryboardYCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardYCommand.cs @@ -13,9 +13,11 @@ namespace osu.Game.Storyboards.Commands { } - public override void SetInitialValue(Drawable d) => d.Y = StartValue; + public override string PropertyName => nameof(Drawable.Y); - public override TransformSequence ApplyTransform(Drawable d) + public override void ApplyInitialValue(Drawable d) => d.Y = StartValue; + + public override TransformSequence ApplyTransforms(Drawable d) => d.MoveToY(StartValue).Then().MoveToY(EndValue, Duration, Easing); } } diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index fc3c5d343c..9d7ab66692 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -131,10 +131,18 @@ namespace osu.Game.Storyboards // if (triggeredGroups != null) // commands = commands.Concat(triggeredGroups.SelectMany(tuple => tuple.Item1.GetAllCommands(tuple.Item2))); + HashSet appliedProperties = new HashSet(); + foreach (var command in commands.OrderBy(c => c.StartTime)) { + if (!appliedProperties.Contains(command.PropertyName)) + { + command.ApplyInitialValue(drawable); + appliedProperties.Add(command.PropertyName); + } + using (drawable.BeginAbsoluteSequence(command.StartTime)) - command.ApplyTransform(drawable); + command.ApplyTransforms(drawable); } } From fa9b2f0cd541bb4c80c895a0bbeced418ccd85e1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 8 Mar 2024 03:06:49 +0300 Subject: [PATCH 3390/4092] Add generics to `ApplyInitialValue`/`ApplyTransforms` for ability to return custom transform sequences *sigh* --- .../Visual/Gameplay/TestSceneLeadIn.cs | 8 +-- .../Gameplay/TestSceneStoryboardWithOutro.cs | 2 +- .../TestSceneMultiSpectatorScreen.cs | 2 +- .../Formats/LegacyStoryboardDecoder.cs | 32 ++++----- .../Commands/IStoryboardCommand.cs | 7 +- .../Commands/StoryboardAlphaCommand.cs | 4 +- .../StoryboardBlendingParametersCommand.cs | 4 +- .../Commands/StoryboardColourCommand.cs | 4 +- .../Storyboards/Commands/StoryboardCommand.cs | 19 +++--- .../Commands/StoryboardCommandGroup.cs | 48 ++++--------- .../Commands/StoryboardCommandList.cs | 41 ------------ .../Commands/StoryboardFlipHCommand.cs | 10 +-- .../Commands/StoryboardFlipVCommand.cs | 10 +-- .../Commands/StoryboardLoopingGroup.cs | 5 +- .../Commands/StoryboardRotationCommand.cs | 4 +- .../Commands/StoryboardScaleCommand.cs | 4 +- .../Commands/StoryboardTriggerGroup.cs | 1 - .../Commands/StoryboardVectorScaleCommand.cs | 10 +-- .../Commands/StoryboardXCommand.cs | 4 +- .../Commands/StoryboardYCommand.cs | 4 +- .../Drawables/DrawableStoryboardAnimation.cs | 2 +- .../Drawables/DrawableStoryboardSprite.cs | 6 +- ...ableStoryboardElement.cs => IFlippable.cs} | 4 +- .../Storyboards/Drawables/IVectorScalable.cs | 13 ++++ osu.Game/Storyboards/StoryboardSprite.cs | 67 ++++++------------- 25 files changed, 122 insertions(+), 193 deletions(-) delete mode 100644 osu.Game/Storyboards/Commands/StoryboardCommandList.cs rename osu.Game/Storyboards/Drawables/{IDrawableStoryboardElement.cs => IFlippable.cs} (72%) create mode 100644 osu.Game/Storyboards/Drawables/IVectorScalable.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs index c3eef4da9b..b2196e77b4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.Gameplay var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero); - sprite.Group.AddAlpha(firstStoryboardEvent, firstStoryboardEvent + 500, 0, 1, Easing.None); + sprite.Commands.AddAlpha(firstStoryboardEvent, firstStoryboardEvent + 500, 0, 1, Easing.None); storyboard.GetLayer("Background").Add(sprite); @@ -73,16 +73,16 @@ namespace osu.Game.Tests.Visual.Gameplay var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero); // these should be ignored as we have an alpha visibility blocker proceeding this command. - sprite.Group.AddScale(loop_start_time, -18000, 0, 1, Easing.None); + sprite.Commands.AddScale(loop_start_time, -18000, 0, 1, Easing.None); var loopGroup = sprite.AddLoopingGroup(loop_start_time, 50); loopGroup.AddScale(loop_start_time, -18000, 0, 1, Easing.None); - var target = addEventToLoop ? loopGroup : sprite.Group; + var target = addEventToLoop ? loopGroup : sprite.Commands; double loopRelativeOffset = addEventToLoop ? -loop_start_time : 0; target.AddAlpha(loopRelativeOffset + firstStoryboardEvent, loopRelativeOffset + firstStoryboardEvent + 500, 0, 1, Easing.None); // these should be ignored due to being in the future. - sprite.Group.AddAlpha(18000, 20000, 0, 1, Easing.None); + sprite.Commands.AddAlpha(18000, 20000, 0, 1, Easing.None); loopGroup.AddAlpha(38000, 40000, 0, 1, Easing.None); storyboard.GetLayer("Background").Add(sprite); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs index 9269c3f4ae..8bdb7297fe 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs @@ -216,7 +216,7 @@ namespace osu.Game.Tests.Visual.Gameplay { var storyboard = new Storyboard(); var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero); - sprite.Group.AddAlpha(0, duration, 1, 0, Easing.None); + sprite.Commands.AddAlpha(0, duration, 1, 0, Easing.None); storyboard.GetLayer("Background").Add(sprite); return storyboard; } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 62a2bfeaab..7cd2e2fdaa 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -424,7 +424,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestIntroStoryboardElement() => testLeadIn(b => { var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero); - sprite.Group.AddAlpha(-2000, 0, 0, 1, Easing.None); + sprite.Commands.AddAlpha(-2000, 0, 0, 1, Easing.None); b.Storyboard.GetLayer("Background").Add(sprite); }); diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 33cdaa085e..83277b71c8 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -19,7 +19,7 @@ namespace osu.Game.Beatmaps.Formats public class LegacyStoryboardDecoder : LegacyDecoder { private StoryboardSprite? storyboardSprite; - private StoryboardCommandGroup? currentGroup; + private StoryboardCommandGroup? currentCommandsGroup; private Storyboard storyboard = null!; @@ -166,7 +166,7 @@ namespace osu.Game.Beatmaps.Formats else { if (depth < 2) - currentGroup = storyboardSprite?.Group; + currentCommandsGroup = storyboardSprite?.Commands; string commandType = split[0]; @@ -178,7 +178,7 @@ namespace osu.Game.Beatmaps.Formats double startTime = split.Length > 2 ? Parsing.ParseDouble(split[2]) : double.MinValue; double endTime = split.Length > 3 ? Parsing.ParseDouble(split[3]) : double.MaxValue; int groupNumber = split.Length > 4 ? Parsing.ParseInt(split[4]) : 0; - currentGroup = storyboardSprite?.AddTriggerGroup(triggerName, startTime, endTime, groupNumber); + currentCommandsGroup = storyboardSprite?.AddTriggerGroup(triggerName, startTime, endTime, groupNumber); break; } @@ -186,7 +186,7 @@ namespace osu.Game.Beatmaps.Formats { double startTime = Parsing.ParseDouble(split[1]); int repeatCount = Parsing.ParseInt(split[2]); - currentGroup = storyboardSprite?.AddLoopingGroup(startTime, Math.Max(0, repeatCount - 1)); + currentCommandsGroup = storyboardSprite?.AddLoopingGroup(startTime, Math.Max(0, repeatCount - 1)); break; } @@ -205,7 +205,7 @@ namespace osu.Game.Beatmaps.Formats { float startValue = Parsing.ParseFloat(split[4]); float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue; - currentGroup?.AddAlpha(startTime, endTime, startValue, endValue, easing); + currentCommandsGroup?.AddAlpha(startTime, endTime, startValue, endValue, easing); break; } @@ -213,7 +213,7 @@ namespace osu.Game.Beatmaps.Formats { float startValue = Parsing.ParseFloat(split[4]); float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue; - currentGroup?.AddScale(startTime, endTime, startValue, endValue, easing); + currentCommandsGroup?.AddScale(startTime, endTime, startValue, endValue, easing); break; } @@ -223,7 +223,7 @@ namespace osu.Game.Beatmaps.Formats float startY = Parsing.ParseFloat(split[5]); float endX = split.Length > 6 ? Parsing.ParseFloat(split[6]) : startX; float endY = split.Length > 7 ? Parsing.ParseFloat(split[7]) : startY; - currentGroup?.AddVectorScale(startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY), easing); + currentCommandsGroup?.AddVectorScale(startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY), easing); break; } @@ -231,7 +231,7 @@ namespace osu.Game.Beatmaps.Formats { float startValue = Parsing.ParseFloat(split[4]); float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue; - currentGroup?.AddRotation(startTime, endTime, MathUtils.RadiansToDegrees(startValue), MathUtils.RadiansToDegrees(endValue), easing); + currentCommandsGroup?.AddRotation(startTime, endTime, MathUtils.RadiansToDegrees(startValue), MathUtils.RadiansToDegrees(endValue), easing); break; } @@ -241,8 +241,8 @@ namespace osu.Game.Beatmaps.Formats float startY = Parsing.ParseFloat(split[5]); float endX = split.Length > 6 ? Parsing.ParseFloat(split[6]) : startX; float endY = split.Length > 7 ? Parsing.ParseFloat(split[7]) : startY; - currentGroup?.AddX(startTime, endTime, startX, endX, easing); - currentGroup?.AddY(startTime, endTime, startY, endY, easing); + currentCommandsGroup?.AddX(startTime, endTime, startX, endX, easing); + currentCommandsGroup?.AddY(startTime, endTime, startY, endY, easing); break; } @@ -250,7 +250,7 @@ namespace osu.Game.Beatmaps.Formats { float startValue = Parsing.ParseFloat(split[4]); float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue; - currentGroup?.AddX(startTime, endTime, startValue, endValue, easing); + currentCommandsGroup?.AddX(startTime, endTime, startValue, endValue, easing); break; } @@ -258,7 +258,7 @@ namespace osu.Game.Beatmaps.Formats { float startValue = Parsing.ParseFloat(split[4]); float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue; - currentGroup?.AddY(startTime, endTime, startValue, endValue, easing); + currentCommandsGroup?.AddY(startTime, endTime, startValue, endValue, easing); break; } @@ -270,7 +270,7 @@ namespace osu.Game.Beatmaps.Formats float endRed = split.Length > 7 ? Parsing.ParseFloat(split[7]) : startRed; float endGreen = split.Length > 8 ? Parsing.ParseFloat(split[8]) : startGreen; float endBlue = split.Length > 9 ? Parsing.ParseFloat(split[9]) : startBlue; - currentGroup?.AddColour(startTime, endTime, + currentCommandsGroup?.AddColour(startTime, endTime, new Color4(startRed / 255f, startGreen / 255f, startBlue / 255f, 1), new Color4(endRed / 255f, endGreen / 255f, endBlue / 255f, 1), easing); break; @@ -283,16 +283,16 @@ namespace osu.Game.Beatmaps.Formats switch (type) { case "A": - currentGroup?.AddBlendingParameters(startTime, endTime, BlendingParameters.Additive, + currentCommandsGroup?.AddBlendingParameters(startTime, endTime, BlendingParameters.Additive, startTime == endTime ? BlendingParameters.Additive : BlendingParameters.Inherit, easing); break; case "H": - currentGroup?.AddFlipH(startTime, endTime, true, startTime == endTime, easing); + currentCommandsGroup?.AddFlipH(startTime, endTime, true, startTime == endTime, easing); break; case "V": - currentGroup?.AddFlipV(startTime, endTime, true, startTime == endTime, easing); + currentCommandsGroup?.AddFlipV(startTime, endTime, true, startTime == endTime, easing); break; } diff --git a/osu.Game/Storyboards/Commands/IStoryboardCommand.cs b/osu.Game/Storyboards/Commands/IStoryboardCommand.cs index 05613a987d..6efb19afe4 100644 --- a/osu.Game/Storyboards/Commands/IStoryboardCommand.cs +++ b/osu.Game/Storyboards/Commands/IStoryboardCommand.cs @@ -3,6 +3,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Transforms; +using osu.Game.Storyboards.Drawables; namespace osu.Game.Storyboards.Commands { @@ -28,13 +29,15 @@ namespace osu.Game.Storyboards.Commands /// Sets the value of the corresponding property in to the start value of this command. /// /// The target drawable. - void ApplyInitialValue(Drawable d); + void ApplyInitialValue(TDrawable d) + where TDrawable : Drawable, IFlippable, IVectorScalable; /// /// Applies the transforms described by this storyboard command to the target drawable. /// /// The target drawable. /// The sequence of transforms applied to the target drawable. - TransformSequence ApplyTransforms(Drawable d); + TransformSequence ApplyTransforms(TDrawable d) + where TDrawable : Drawable, IFlippable, IVectorScalable; } } diff --git a/osu.Game/Storyboards/Commands/StoryboardAlphaCommand.cs b/osu.Game/Storyboards/Commands/StoryboardAlphaCommand.cs index 1d91d6ccc1..f4a90b372a 100644 --- a/osu.Game/Storyboards/Commands/StoryboardAlphaCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardAlphaCommand.cs @@ -15,9 +15,9 @@ namespace osu.Game.Storyboards.Commands public override string PropertyName => nameof(Drawable.Alpha); - public override void ApplyInitialValue(Drawable d) => d.Alpha = StartValue; + public override void ApplyInitialValue(TDrawable d) => d.Alpha = StartValue; - public override TransformSequence ApplyTransforms(Drawable d) + public override TransformSequence ApplyTransforms(TDrawable d) => d.FadeTo(StartValue).Then().FadeTo(EndValue, Duration, Easing); } } diff --git a/osu.Game/Storyboards/Commands/StoryboardBlendingParametersCommand.cs b/osu.Game/Storyboards/Commands/StoryboardBlendingParametersCommand.cs index 3a2d372a66..3e2e8bb0e8 100644 --- a/osu.Game/Storyboards/Commands/StoryboardBlendingParametersCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardBlendingParametersCommand.cs @@ -15,9 +15,9 @@ namespace osu.Game.Storyboards.Commands public override string PropertyName => nameof(Drawable.Blending); - public override void ApplyInitialValue(Drawable d) => d.Blending = StartValue; + public override void ApplyInitialValue(TDrawable d) => d.Blending = StartValue; - public override TransformSequence ApplyTransforms(Drawable d) + public override TransformSequence ApplyTransforms(TDrawable d) => d.TransformTo(nameof(d.Blending), StartValue).Delay(Duration) .TransformTo(nameof(d.Blending), EndValue); } diff --git a/osu.Game/Storyboards/Commands/StoryboardColourCommand.cs b/osu.Game/Storyboards/Commands/StoryboardColourCommand.cs index fbde7e1af7..66390eb305 100644 --- a/osu.Game/Storyboards/Commands/StoryboardColourCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardColourCommand.cs @@ -16,9 +16,9 @@ namespace osu.Game.Storyboards.Commands public override string PropertyName => nameof(Drawable.Colour); - public override void ApplyInitialValue(Drawable d) => d.Colour = StartValue; + public override void ApplyInitialValue(TDrawable d) => d.Colour = StartValue; - public override TransformSequence ApplyTransforms(Drawable d) + public override TransformSequence ApplyTransforms(TDrawable d) => d.FadeColour(StartValue).Then().FadeColour(EndValue, Duration, Easing); } } diff --git a/osu.Game/Storyboards/Commands/StoryboardCommand.cs b/osu.Game/Storyboards/Commands/StoryboardCommand.cs index 58fcb148ff..4f2f0f04a2 100644 --- a/osu.Game/Storyboards/Commands/StoryboardCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardCommand.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Transforms; +using osu.Game.Storyboards.Drawables; namespace osu.Game.Storyboards.Commands { @@ -11,6 +12,11 @@ namespace osu.Game.Storyboards.Commands { public double StartTime { get; } public double EndTime { get; } + + public T StartValue { get; } + public T EndValue { get; } + public Easing Easing { get; } + public double Duration => EndTime - StartTime; protected StoryboardCommand(double startTime, double endTime, T startValue, T endValue, Easing easing) @@ -25,18 +31,13 @@ namespace osu.Game.Storyboards.Commands Easing = easing; } - public Easing Easing { get; set; } - public int LoopCount { get; set; } - public double Delay { get; set; } - - public T StartValue; - public T EndValue; - public abstract string PropertyName { get; } - public abstract void ApplyInitialValue(Drawable d); + public abstract void ApplyInitialValue(TDrawable d) + where TDrawable : Drawable, IFlippable, IVectorScalable; - public abstract TransformSequence ApplyTransforms(Drawable d); + public abstract TransformSequence ApplyTransforms(TDrawable d) + where TDrawable : Drawable, IFlippable, IVectorScalable; public int CompareTo(StoryboardCommand? other) { diff --git a/osu.Game/Storyboards/Commands/StoryboardCommandGroup.cs b/osu.Game/Storyboards/Commands/StoryboardCommandGroup.cs index 0d4a79079b..fb847d2e44 100644 --- a/osu.Game/Storyboards/Commands/StoryboardCommandGroup.cs +++ b/osu.Game/Storyboards/Commands/StoryboardCommandGroup.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Linq; using Newtonsoft.Json; using osu.Framework.Graphics; using osu.Framework.Lists; @@ -13,18 +12,20 @@ namespace osu.Game.Storyboards.Commands { public class StoryboardCommandGroup { - public SortedList> X; - public SortedList> Y; - public SortedList> Scale; - public SortedList> VectorScale; - public SortedList> Rotation; - public SortedList> Colour; - public SortedList> Alpha; - public SortedList> BlendingParameters; - public SortedList> FlipH; - public SortedList> FlipV; + public SortedList> X = new SortedList>(); + public SortedList> Y = new SortedList>(); + public SortedList> Scale = new SortedList>(); + public SortedList> VectorScale = new SortedList>(); + public SortedList> Rotation = new SortedList>(); + public SortedList> Colour = new SortedList>(); + public SortedList> Alpha = new SortedList>(); + public SortedList> BlendingParameters = new SortedList>(); + public SortedList> FlipH = new SortedList>(); + public SortedList> FlipV = new SortedList>(); - private readonly IReadOnlyList[] lists; + public IReadOnlyList AllCommands => allCommands; + + private readonly List allCommands = new List(); /// /// Returns the earliest start time of the commands added to this group. @@ -44,28 +45,6 @@ namespace osu.Game.Storyboards.Commands [JsonIgnore] public bool HasCommands { get; private set; } - public StoryboardCommandGroup() - { - lists = new IReadOnlyList[] - { - X = new SortedList>(), - Y = new SortedList>(), - Scale = new SortedList>(), - VectorScale = new SortedList>(), - Rotation = new SortedList>(), - Colour = new SortedList>(), - Alpha = new SortedList>(), - BlendingParameters = new SortedList>(), - FlipH = new SortedList>(), - FlipV = new SortedList>() - }; - } - - /// - /// Returns all commands contained by this group unsorted. - /// - public virtual IEnumerable GetAllCommands() => lists.SelectMany(l => l); - public void AddX(double startTime, double endTime, float startValue, float endValue, Easing easing) => AddCommand(X, new StoryboardXCommand(startTime, endTime, startValue, endValue, easing)); @@ -104,6 +83,7 @@ namespace osu.Game.Storyboards.Commands protected virtual void AddCommand(ICollection> list, StoryboardCommand command) { list.Add(command); + allCommands.Add(command); HasCommands = true; if (command.StartTime < StartTime) diff --git a/osu.Game/Storyboards/Commands/StoryboardCommandList.cs b/osu.Game/Storyboards/Commands/StoryboardCommandList.cs deleted file mode 100644 index 67012e9d49..0000000000 --- a/osu.Game/Storyboards/Commands/StoryboardCommandList.cs +++ /dev/null @@ -1,41 +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.Storyboards.Commands -{ - // public class StoryboardCommandList : IStoryboardCommandList - // { - // // todo: change to sorted list and avoid enumerable type on exposed properties? - // private readonly List> commands = new List>(); - // - // public IEnumerable> Commands => commands.OrderBy(c => c.StartTime); - // - // IEnumerable IStoryboardCommandList.Commands => Commands; - // public bool HasCommands => commands.Count > 0; - // - // public double StartTime { get; private set; } = double.MaxValue; - // public double EndTime { get; private set; } = double.MinValue; - // - // public T? StartValue { get; private set; } - // public T? EndValue { get; private set; } - // - // public void Add(StoryboardCommand command) - // { - // commands.Add(command); - // - // if (command.StartTime < StartTime) - // { - // StartValue = command.StartValue; - // StartTime = command.StartTime; - // } - // - // if (command.EndTime > EndTime) - // { - // EndValue = command.EndValue; - // EndTime = command.EndTime; - // } - // } - // - // public override string ToString() => $"{commands.Count} command(s)"; - // } -} diff --git a/osu.Game/Storyboards/Commands/StoryboardFlipHCommand.cs b/osu.Game/Storyboards/Commands/StoryboardFlipHCommand.cs index c381b0bb64..26aef23226 100644 --- a/osu.Game/Storyboards/Commands/StoryboardFlipHCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardFlipHCommand.cs @@ -14,12 +14,12 @@ namespace osu.Game.Storyboards.Commands { } - public override string PropertyName => nameof(IDrawableStoryboardElement.FlipH); + public override string PropertyName => nameof(IFlippable.FlipH); - public override void ApplyInitialValue(Drawable d) => ((IDrawableStoryboardElement)d).FlipH = StartValue; + public override void ApplyInitialValue(TDrawable d) => d.FlipH = StartValue; - public override TransformSequence ApplyTransforms(Drawable d) - => d.TransformTo(nameof(IDrawableStoryboardElement.FlipH), StartValue).Delay(Duration) - .TransformTo(nameof(IDrawableStoryboardElement.FlipH), EndValue); + public override TransformSequence ApplyTransforms(TDrawable d) + => d.TransformTo(nameof(IFlippable.FlipH), StartValue).Delay(Duration) + .TransformTo(nameof(IFlippable.FlipH), EndValue); } } diff --git a/osu.Game/Storyboards/Commands/StoryboardFlipVCommand.cs b/osu.Game/Storyboards/Commands/StoryboardFlipVCommand.cs index e43e5e9205..88423da2af 100644 --- a/osu.Game/Storyboards/Commands/StoryboardFlipVCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardFlipVCommand.cs @@ -14,12 +14,12 @@ namespace osu.Game.Storyboards.Commands { } - public override string PropertyName => nameof(IDrawableStoryboardElement.FlipV); + public override string PropertyName => nameof(IFlippable.FlipV); - public override void ApplyInitialValue(Drawable d) => ((IDrawableStoryboardElement)d).FlipV = StartValue; + public override void ApplyInitialValue(TDrawable d) => d.FlipV = StartValue; - public override TransformSequence ApplyTransforms(Drawable d) - => d.TransformTo(nameof(IDrawableStoryboardElement.FlipV), StartValue).Delay(Duration) - .TransformTo(nameof(IDrawableStoryboardElement.FlipV), EndValue); + public override TransformSequence ApplyTransforms(TDrawable d) + => d.TransformTo(nameof(IFlippable.FlipV), StartValue).Delay(Duration) + .TransformTo(nameof(IFlippable.FlipV), EndValue); } } diff --git a/osu.Game/Storyboards/Commands/StoryboardLoopingGroup.cs b/osu.Game/Storyboards/Commands/StoryboardLoopingGroup.cs index 39b81ead28..e97de84ab7 100644 --- a/osu.Game/Storyboards/Commands/StoryboardLoopingGroup.cs +++ b/osu.Game/Storyboards/Commands/StoryboardLoopingGroup.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using osu.Framework.Graphics; using osu.Framework.Graphics.Transforms; namespace osu.Game.Storyboards.Commands @@ -52,9 +51,9 @@ namespace osu.Game.Storyboards.Commands public override string PropertyName => command.PropertyName; - public override void ApplyInitialValue(Drawable d) => command.ApplyInitialValue(d); + public override void ApplyInitialValue(TDrawable d) => command.ApplyInitialValue(d); - public override TransformSequence ApplyTransforms(Drawable d) + public override TransformSequence ApplyTransforms(TDrawable d) { if (loopingGroup.TotalIterations == 0) return command.ApplyTransforms(d); diff --git a/osu.Game/Storyboards/Commands/StoryboardRotationCommand.cs b/osu.Game/Storyboards/Commands/StoryboardRotationCommand.cs index 2a449af843..4347dc9d77 100644 --- a/osu.Game/Storyboards/Commands/StoryboardRotationCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardRotationCommand.cs @@ -15,9 +15,9 @@ namespace osu.Game.Storyboards.Commands public override string PropertyName => nameof(Drawable.Rotation); - public override void ApplyInitialValue(Drawable d) => d.Rotation = StartValue; + public override void ApplyInitialValue(TDrawable d) => d.Rotation = StartValue; - public override TransformSequence ApplyTransforms(Drawable d) + public override TransformSequence ApplyTransforms(TDrawable d) => d.RotateTo(StartValue).Then().RotateTo(EndValue, Duration, Easing); } } diff --git a/osu.Game/Storyboards/Commands/StoryboardScaleCommand.cs b/osu.Game/Storyboards/Commands/StoryboardScaleCommand.cs index bf9796148c..b0f33fd6b8 100644 --- a/osu.Game/Storyboards/Commands/StoryboardScaleCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardScaleCommand.cs @@ -16,9 +16,9 @@ namespace osu.Game.Storyboards.Commands public override string PropertyName => nameof(Drawable.Scale); - public override void ApplyInitialValue(Drawable d) => d.Scale = new Vector2(StartValue); + public override void ApplyInitialValue(TDrawable d) => d.Scale = new Vector2(StartValue); - public override TransformSequence ApplyTransforms(Drawable d) + public override TransformSequence ApplyTransforms(TDrawable d) => d.ScaleTo(StartValue).Then().ScaleTo(EndValue, Duration, Easing); } } diff --git a/osu.Game/Storyboards/Commands/StoryboardTriggerGroup.cs b/osu.Game/Storyboards/Commands/StoryboardTriggerGroup.cs index dfb6f8cb1b..89a68e9ec0 100644 --- a/osu.Game/Storyboards/Commands/StoryboardTriggerGroup.cs +++ b/osu.Game/Storyboards/Commands/StoryboardTriggerGroup.cs @@ -3,7 +3,6 @@ namespace osu.Game.Storyboards.Commands { - // todo: this is not implemented and has never been, keep that in mind. public class StoryboardTriggerGroup : StoryboardCommandGroup { public string TriggerName; diff --git a/osu.Game/Storyboards/Commands/StoryboardVectorScaleCommand.cs b/osu.Game/Storyboards/Commands/StoryboardVectorScaleCommand.cs index 638dc6a5ee..5d3fef5948 100644 --- a/osu.Game/Storyboards/Commands/StoryboardVectorScaleCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardVectorScaleCommand.cs @@ -15,12 +15,12 @@ namespace osu.Game.Storyboards.Commands { } - public override string PropertyName => nameof(IDrawableStoryboardElement.VectorScale); + public override string PropertyName => nameof(IVectorScalable.VectorScale); - public override void ApplyInitialValue(Drawable d) => ((IDrawableStoryboardElement)d).VectorScale = StartValue; + public override void ApplyInitialValue(TDrawable d) => d.VectorScale = StartValue; - public override TransformSequence ApplyTransforms(Drawable d) - => d.TransformTo(nameof(IDrawableStoryboardElement.VectorScale), StartValue).Then() - .TransformTo(nameof(IDrawableStoryboardElement.VectorScale), EndValue, Duration, Easing); + public override TransformSequence ApplyTransforms(TDrawable d) + => d.TransformTo(nameof(d.VectorScale), StartValue).Then() + .TransformTo(nameof(d.VectorScale), EndValue, Duration, Easing); } } diff --git a/osu.Game/Storyboards/Commands/StoryboardXCommand.cs b/osu.Game/Storyboards/Commands/StoryboardXCommand.cs index 809a77256c..7df9a75768 100644 --- a/osu.Game/Storyboards/Commands/StoryboardXCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardXCommand.cs @@ -15,9 +15,9 @@ namespace osu.Game.Storyboards.Commands public override string PropertyName => nameof(Drawable.X); - public override void ApplyInitialValue(Drawable d) => d.X = StartValue; + public override void ApplyInitialValue(TDrawable d) => d.X = StartValue; - public override TransformSequence ApplyTransforms(Drawable d) + public override TransformSequence ApplyTransforms(TDrawable d) => d.MoveToX(StartValue).Then().MoveToX(EndValue, Duration, Easing); } } diff --git a/osu.Game/Storyboards/Commands/StoryboardYCommand.cs b/osu.Game/Storyboards/Commands/StoryboardYCommand.cs index d054135878..d7dc32a0f3 100644 --- a/osu.Game/Storyboards/Commands/StoryboardYCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardYCommand.cs @@ -15,9 +15,9 @@ namespace osu.Game.Storyboards.Commands public override string PropertyName => nameof(Drawable.Y); - public override void ApplyInitialValue(Drawable d) => d.Y = StartValue; + public override void ApplyInitialValue(TDrawable d) => d.Y = StartValue; - public override TransformSequence ApplyTransforms(Drawable d) + public override TransformSequence ApplyTransforms(TDrawable d) => d.MoveToY(StartValue).Then().MoveToY(EndValue, Duration, Easing); } } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index 8e1a8ce949..fae9ec7f2e 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -16,7 +16,7 @@ using osuTK; namespace osu.Game.Storyboards.Drawables { - public partial class DrawableStoryboardAnimation : TextureAnimation, IDrawableStoryboardElement + public partial class DrawableStoryboardAnimation : TextureAnimation, IFlippable, IVectorScalable { public StoryboardAnimation Animation { get; } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index 6772178e85..507a51aca4 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; @@ -13,7 +14,7 @@ using osuTK; namespace osu.Game.Storyboards.Drawables { - public partial class DrawableStoryboardSprite : Sprite, IDrawableStoryboardElement + public partial class DrawableStoryboardSprite : Sprite, IFlippable, IVectorScalable { public StoryboardSprite Sprite { get; } @@ -101,6 +102,9 @@ namespace osu.Game.Storyboards.Drawables else Texture = textureStore.Get(Sprite.Path); + if (Sprite.Path == "SB/textbox.png") + Debugger.Break(); + Sprite.ApplyTransforms(this); } diff --git a/osu.Game/Storyboards/Drawables/IDrawableStoryboardElement.cs b/osu.Game/Storyboards/Drawables/IFlippable.cs similarity index 72% rename from osu.Game/Storyboards/Drawables/IDrawableStoryboardElement.cs rename to osu.Game/Storyboards/Drawables/IFlippable.cs index 04bae88c76..79f98ea6ef 100644 --- a/osu.Game/Storyboards/Drawables/IDrawableStoryboardElement.cs +++ b/osu.Game/Storyboards/Drawables/IFlippable.cs @@ -2,14 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osuTK; namespace osu.Game.Storyboards.Drawables { - public interface IDrawableStoryboardElement : IDrawable + public interface IFlippable : IDrawable { bool FlipH { get; set; } bool FlipV { get; set; } - Vector2 VectorScale { get; set; } } } diff --git a/osu.Game/Storyboards/Drawables/IVectorScalable.cs b/osu.Game/Storyboards/Drawables/IVectorScalable.cs new file mode 100644 index 0000000000..ce6047c8f6 --- /dev/null +++ b/osu.Game/Storyboards/Drawables/IVectorScalable.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. + +using osu.Framework.Graphics; +using osuTK; + +namespace osu.Game.Storyboards.Drawables +{ + public interface IVectorScalable : IDrawable + { + Vector2 VectorScale { get; set; } + } +} diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index 9d7ab66692..f2c011bfca 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -13,7 +13,7 @@ namespace osu.Game.Storyboards { public class StoryboardSprite : IStoryboardElementWithDuration { - private readonly List loopGroups = new List(); + private readonly List loopingGroups = new List(); private readonly List triggerGroups = new List(); public string Path { get; } @@ -22,7 +22,7 @@ namespace osu.Game.Storyboards public Anchor Origin; public Vector2 InitialPosition; - public readonly StoryboardCommandGroup Group = new StoryboardCommandGroup(); + public readonly StoryboardCommandGroup Commands = new StoryboardCommandGroup(); public double StartTime { @@ -35,10 +35,10 @@ namespace osu.Game.Storyboards // anything before that point can be ignored (the sprite is not visible after all). var alphaCommands = new List<(double startTime, bool isZeroStartValue)>(); - var command = Group.Alpha.FirstOrDefault(); + var command = Commands.Alpha.FirstOrDefault(); if (command != null) alphaCommands.Add((command.StartTime, command.StartValue == 0)); - foreach (var loop in loopGroups) + foreach (var loop in loopingGroups) { command = loop.Alpha.FirstOrDefault(); if (command != null) alphaCommands.Add((command.StartTime, command.StartValue == 0)); @@ -62,8 +62,8 @@ namespace osu.Game.Storyboards { // If we got to this point, either no alpha commands were present, or the earliest had a non-zero start value. // The sprite's StartTime will be determined by the earliest command, regardless of type. - double earliestStartTime = Group.StartTime; - foreach (var l in loopGroups) + double earliestStartTime = Commands.StartTime; + foreach (var l in loopingGroups) earliestStartTime = Math.Min(earliestStartTime, l.StartTime); return earliestStartTime; } @@ -73,9 +73,9 @@ namespace osu.Game.Storyboards { get { - double latestEndTime = Group.EndTime; + double latestEndTime = Commands.EndTime; - foreach (var l in loopGroups) + foreach (var l in loopingGroups) latestEndTime = Math.Max(latestEndTime, l.EndTime); return latestEndTime; @@ -86,16 +86,16 @@ namespace osu.Game.Storyboards { get { - double latestEndTime = Group.StartTime; + double latestEndTime = Commands.StartTime; - foreach (var l in loopGroups) + foreach (var l in loopingGroups) latestEndTime = Math.Max(latestEndTime, l.StartTime + l.Duration * l.TotalIterations); return latestEndTime; } } - public bool HasCommands => Group.HasCommands || loopGroups.Any(l => l.HasCommands); + public bool HasCommands => Commands.HasCommands || loopingGroups.Any(l => l.HasCommands); public StoryboardSprite(string path, Anchor origin, Vector2 initialPosition) { @@ -109,7 +109,7 @@ namespace osu.Game.Storyboards public StoryboardLoopingGroup AddLoopingGroup(double loopStartTime, int repeatCount) { var loop = new StoryboardLoopingGroup(loopStartTime, repeatCount); - loopGroups.Add(loop); + loopingGroups.Add(loop); return loop; } @@ -120,26 +120,20 @@ namespace osu.Game.Storyboards return trigger; } - public void ApplyTransforms(Drawable drawable, IEnumerable>? triggeredGroups = null) + public void ApplyTransforms(TDrawable drawable) + where TDrawable : Drawable, IFlippable, IVectorScalable { - // For performance reasons, we need to apply the commands in order by start time. Not doing so will cause many functions to be interleaved, resulting in O(n^2) complexity. - - var commands = Group.GetAllCommands(); - commands = commands.Concat(loopGroups.SelectMany(l => l.GetAllCommands())); - - // todo: triggers are not implemented yet. - // if (triggeredGroups != null) - // commands = commands.Concat(triggeredGroups.SelectMany(tuple => tuple.Item1.GetAllCommands(tuple.Item2))); - HashSet appliedProperties = new HashSet(); + // For performance reasons, we need to apply the commands in chronological order. + // Not doing so will cause many functions to be interleaved, resulting in O(n^2) complexity. + IEnumerable commands = Commands.AllCommands; + commands = commands.Concat(loopingGroups.SelectMany(l => l.AllCommands)); + foreach (var command in commands.OrderBy(c => c.StartTime)) { - if (!appliedProperties.Contains(command.PropertyName)) - { + if (appliedProperties.Add(command.PropertyName)) command.ApplyInitialValue(drawable); - appliedProperties.Add(command.PropertyName); - } using (drawable.BeginAbsoluteSequence(command.StartTime)) command.ApplyTransforms(drawable); @@ -147,26 +141,5 @@ namespace osu.Game.Storyboards } public override string ToString() => $"{Path}, {Origin}, {InitialPosition}"; - - // todo: need to revisit property initialisation. apparently it has to be done per first command of every affected property (transforms are supposed to do that already?). - // private void generateCommands(List resultList, IEnumerable.TypedCommand> commands, - // DrawablePropertyInitializer initializeProperty, DrawableTransform transform, bool alwaysInitialize = true) - // { - // bool initialized = false; - // - // foreach (var command in commands) - // { - // DrawablePropertyInitializer? initFunc = null; - // - // if (!initialized) - // { - // if (alwaysInitialize || command.StartTime == command.EndTime) - // initFunc = initializeProperty; - // initialized = true; - // } - // - // resultList.Add(new GeneratedCommand(command, initFunc, transform)); - // } - // } } } From 79da6d861326ca9d6d897d7a0059471a4548d8bf Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 8 Mar 2024 03:07:59 +0300 Subject: [PATCH 3391/4092] Fix refactor error on `EndTimeForDisplay` Not sure when this happened >.> --- osu.Game/Storyboards/StoryboardSprite.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index f2c011bfca..f961019883 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -86,7 +86,7 @@ namespace osu.Game.Storyboards { get { - double latestEndTime = Commands.StartTime; + double latestEndTime = Commands.EndTime; foreach (var l in loopingGroups) latestEndTime = Math.Max(latestEndTime, l.StartTime + l.Duration * l.TotalIterations); From 87b4406bdc01d5ff5a9dd0cbff59fdf4f25f5829 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Mar 2024 09:41:28 +0800 Subject: [PATCH 3392/4092] Pad at minimum three digits for argon pp display --- osu.Game/Screens/Play/HUD/ArgonPerformancePointsCounter.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonPerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/ArgonPerformancePointsCounter.cs index 022c1fab9e..c57e7bc7ea 100644 --- a/osu.Game/Screens/Play/HUD/ArgonPerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonPerformancePointsCounter.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 System; using osu.Framework.Bindables; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; @@ -55,7 +56,7 @@ namespace osu.Game.Screens.Play.HUD private void updateWireframe() { - int digitsRequiredForDisplayCount = getDigitsRequiredForDisplayCount(); + int digitsRequiredForDisplayCount = Math.Max(3, getDigitsRequiredForDisplayCount()); if (digitsRequiredForDisplayCount != text.WireframeTemplate.Length) text.WireframeTemplate = new string('#', digitsRequiredForDisplayCount); From f2753ef7a225316ec96ecb1ca8e1be261d2ca570 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Mar 2024 09:41:38 +0800 Subject: [PATCH 3393/4092] Decrease size of pp display relative to accuracy --- osu.Game/Skinning/ArgonSkin.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 24e17d21f4..953badaf65 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -232,7 +232,10 @@ namespace osu.Game.Skinning CornerRadius = { Value = 0.5f } }, new ArgonAccuracyCounter(), - new ArgonPerformancePointsCounter(), + new ArgonPerformancePointsCounter + { + Scale = new Vector2(0.8f), + }, new ArgonComboCounter { Scale = new Vector2(1.3f) From ae2ef8ee1ece9083ca41118dc59846047d021022 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Mar 2024 10:19:00 +0800 Subject: [PATCH 3394/4092] Fix typo in wireframe description --- osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs | 2 +- osu.Game/Screens/Play/HUD/ArgonComboCounter.cs | 2 +- osu.Game/Screens/Play/HUD/ArgonPerformancePointsCounter.cs | 2 +- osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs index 4cc04e1485..d7fe1f52ff 100644 --- a/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs @@ -19,7 +19,7 @@ namespace osu.Game.Screens.Play.HUD { protected override double RollingDuration => 250; - [SettingSource("Wireframe opacity", "Controls the opacity of the wire frames behind the digits.")] + [SettingSource("Wireframe opacity", "Controls the opacity of the wireframes behind the digits.")] public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.25f) { Precision = 0.01f, diff --git a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs index 3f187650b2..db0480c566 100644 --- a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs @@ -23,7 +23,7 @@ namespace osu.Game.Screens.Play.HUD protected override double RollingDuration => 250; - [SettingSource("Wireframe opacity", "Controls the opacity of the wire frames behind the digits.")] + [SettingSource("Wireframe opacity", "Controls the opacity of the wireframes behind the digits.")] public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.25f) { Precision = 0.01f, diff --git a/osu.Game/Screens/Play/HUD/ArgonPerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/ArgonPerformancePointsCounter.cs index c57e7bc7ea..d4d842bd9a 100644 --- a/osu.Game/Screens/Play/HUD/ArgonPerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonPerformancePointsCounter.cs @@ -20,7 +20,7 @@ namespace osu.Game.Screens.Play.HUD private const float alpha_when_invalid = 0.3f; - [SettingSource("Wireframe opacity", "Controls the opacity of the wire frames behind the digits.")] + [SettingSource("Wireframe opacity", "Controls the opacity of the wireframes behind the digits.")] public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.25f) { Precision = 0.01f, diff --git a/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs b/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs index a14ab3cbcd..8658651407 100644 --- a/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs @@ -20,7 +20,7 @@ namespace osu.Game.Screens.Play.HUD protected override double RollingDuration => 250; - [SettingSource("Wireframe opacity", "Controls the opacity of the wire frames behind the digits.")] + [SettingSource("Wireframe opacity", "Controls the opacity of the wireframes behind the digits.")] public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.25f) { Precision = 0.01f, From 0ebb12f67f97d82ef8931df095445c1c6542ba08 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Mar 2024 10:23:43 +0800 Subject: [PATCH 3395/4092] Move skinnable interface specification to non-`abstract` classes --- osu.Game/Screens/Play/HUD/ArgonPerformancePointsCounter.cs | 3 ++- osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs | 2 +- .../Skinning/Triangles/TrianglesPerformancePointsCounter.cs | 4 +--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonPerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/ArgonPerformancePointsCounter.cs index d4d842bd9a..1620da2f2e 100644 --- a/osu.Game/Screens/Play/HUD/ArgonPerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonPerformancePointsCounter.cs @@ -9,10 +9,11 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; using osu.Game.Localisation.SkinComponents; using osu.Game.Resources.Localisation.Web; +using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { - public partial class ArgonPerformancePointsCounter : PerformancePointsCounter + public partial class ArgonPerformancePointsCounter : PerformancePointsCounter, ISerialisableDrawable { private ArgonCounterTextComponent text = null!; diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index c5ad106bcc..25c1387220 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -27,7 +27,7 @@ using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { - public abstract partial class PerformancePointsCounter : RollingCounter, ISerialisableDrawable + public abstract partial class PerformancePointsCounter : RollingCounter { public bool UsesFixedAnchor { get; set; } diff --git a/osu.Game/Skinning/Triangles/TrianglesPerformancePointsCounter.cs b/osu.Game/Skinning/Triangles/TrianglesPerformancePointsCounter.cs index 99da2c0942..d6753a81ba 100644 --- a/osu.Game/Skinning/Triangles/TrianglesPerformancePointsCounter.cs +++ b/osu.Game/Skinning/Triangles/TrianglesPerformancePointsCounter.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.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -16,7 +14,7 @@ using osuTK; namespace osu.Game.Skinning.Triangles { - public partial class TrianglesPerformancePointsCounter : PerformancePointsCounter + public partial class TrianglesPerformancePointsCounter : PerformancePointsCounter, ISerialisableDrawable { protected override bool IsRollingProportional => true; From 928639863325278dd66fe428bdd10870b9ca0e64 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Mar 2024 10:26:08 +0800 Subject: [PATCH 3396/4092] Move naming migrations to more correct place --- osu.Game/Skinning/Skin.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 91b21f0308..e4ca908d90 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -133,6 +133,11 @@ namespace osu.Game.Skinning SkinLayoutInfo? layoutInfo = null; + // handle namespace changes... + jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.SongProgress", @"osu.Game.Screens.Play.HUD.DefaultSongProgress"); + jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.LegacyComboCounter", @"osu.Game.Skinning.LegacyComboCounter"); + jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.PerformancePointsCounter", @"osu.Game.Skinning.Triangles.TrianglesPerformancePointsCounter"); + try { // First attempt to deserialise using the new SkinLayoutInfo format @@ -150,11 +155,6 @@ namespace osu.Game.Skinning // If deserialisation using SkinLayoutInfo fails, attempt to deserialise using the old naked list. if (layoutInfo == null) { - // handle namespace changes... - jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.SongProgress", @"osu.Game.Screens.Play.HUD.DefaultSongProgress"); - jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.LegacyComboCounter", @"osu.Game.Skinning.LegacyComboCounter"); - jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.PerformancePointsCounter", @"osu.Game.Skinning.Triangles.TrianglesPerformancePointsCounter"); - var deserializedContent = JsonConvert.DeserializeObject>(jsonContent); if (deserializedContent == null) From fcc35a6accce8c849ec1de154d01e4fffa7ebc4a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Mar 2024 11:37:13 +0800 Subject: [PATCH 3397/4092] Fix cross-talk between pooled `DrawableSliderRepeat` usage causing incorrect rotation --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index fcbd0edfe0..27c5278614 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -79,6 +79,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.OnApply(); Position = HitObject.Position - DrawableSlider.Position; + hasRotation = false; } protected override void CheckForResult(bool userTriggered, double timeOffset) => DrawableSlider.SliderInputManager.TryJudgeNestedObject(this, timeOffset); From 5e7d9ea04ac544b43bb349b99151f2599da5c747 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Mar 2024 13:59:04 +0800 Subject: [PATCH 3398/4092] Adjust scroll speed back to original --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index a5fc75aea3..dcd78833bd 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -346,7 +346,7 @@ namespace osu.Game.Overlays.Mods ScheduleAfterChildren(() => { columnScroll.ScrollToEnd(false); - columnScroll.ScrollTo(0, true, 0.0055); + columnScroll.ScrollTo(0); }); } From 44d0dc6113a408a15a18025325e55646a2147b14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 8 Mar 2024 11:05:07 +0100 Subject: [PATCH 3399/4092] Fix 1px flashlight gaps when gameplay scaling mode is active Closes https://github.com/ppy/osu/issues/27522. Concerns mostly taiko and catch. Not much of a proper fix rather than a workaround but it is what it is. I tried a few other things, including setting `MaskingSmoothness = 0` on the scaling container itself, to no avail. --- osu.Game/Rulesets/Mods/ModFlashlight.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index c3775b0875..c924915bd0 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -7,6 +7,7 @@ using System.Runtime.InteropServices; 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.Rendering; using osu.Framework.Graphics.Rendering.Vertices; @@ -88,7 +89,13 @@ namespace osu.Game.Rulesets.Mods flashlight.Combo.BindTo(Combo); flashlight.GetPlayfieldScale = () => drawableRuleset.Playfield.Scale; - drawableRuleset.Overlays.Add(flashlight); + drawableRuleset.Overlays.Add(new Container + { + RelativeSizeAxes = Axes.Both, + // workaround for 1px gaps on the edges of the playfield which would sometimes show with "gameplay" screen scaling active. + Padding = new MarginPadding(-1), + Child = flashlight, + }); } protected abstract Flashlight CreateFlashlight(); From 46e5bda40c68e15aca0dcbd3d468144d9efe6065 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Mar 2024 19:02:12 +0800 Subject: [PATCH 3400/4092] Fix test failures --- osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs index 3dca179f2f..075fdd88ca 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods PassCondition = () => { var flashlightOverlay = Player.DrawableRuleset.Overlays - .OfType.Flashlight>() + .ChildrenOfType.Flashlight>() .First(); return Precision.AlmostEquals(mod.DefaultFlashlightSize * .5f, flashlightOverlay.GetSize()); From ad842b60f5416c83edf0f341575bd80572465b58 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 8 Mar 2024 21:43:18 +0900 Subject: [PATCH 3401/4092] Add support for Argon hitsounds --- osu.Game/Skinning/ArgonProSkin.cs | 6 ++++-- osu.Game/Skinning/ArgonSkin.cs | 5 ++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/ArgonProSkin.cs b/osu.Game/Skinning/ArgonProSkin.cs index b753dd8fbe..6ec9945c0e 100644 --- a/osu.Game/Skinning/ArgonProSkin.cs +++ b/osu.Game/Skinning/ArgonProSkin.cs @@ -24,9 +24,11 @@ namespace osu.Game.Skinning { foreach (string lookup in sampleInfo.LookupNames) { - string remappedLookup = lookup.Replace(@"Gameplay/", @"Gameplay/ArgonPro/"); + var sample = Samples?.Get(lookup) + ?? Resources.AudioManager?.Samples.Get(lookup.Replace(@"Gameplay/", @"Gameplay/ArgonPro/")) + ?? Resources.AudioManager?.Samples.Get(lookup.Replace(@"Gameplay/", @"Gameplay/Argon/")) + ?? Resources.AudioManager?.Samples.Get(lookup); - var sample = Samples?.Get(remappedLookup) ?? Resources.AudioManager?.Samples.Get(remappedLookup); if (sample != null) return sample; } diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 953badaf65..8fd393fcc5 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -77,7 +77,10 @@ namespace osu.Game.Skinning { foreach (string lookup in sampleInfo.LookupNames) { - var sample = Samples?.Get(lookup) ?? Resources.AudioManager?.Samples.Get(lookup); + var sample = Samples?.Get(lookup) + ?? Resources.AudioManager?.Samples.Get(lookup.Replace(@"Gameplay/", @"Gameplay/Argon/")) + ?? Resources.AudioManager?.Samples.Get(lookup); + if (sample != null) return sample; } From 48c83195677736a41d1bb3b8e8d563247481de72 Mon Sep 17 00:00:00 2001 From: Hivie Date: Fri, 8 Mar 2024 16:01:57 +0100 Subject: [PATCH 3402/4092] change multiplier to 0.9x --- osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs index 4ecb94467e..81973e65cc 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Taiko.Mods { public override string Name => "Constant Speed"; public override string Acronym => "CS"; - public override double ScoreMultiplier => 0.8; + public override double ScoreMultiplier => 0.9; public override LocalisableString Description => "No more tricky speed changes!"; public override IconUsage? Icon => FontAwesome.Solid.Equals; public override ModType Type => ModType.Conversion; From a8792b35850c53a5946e6e20176282dc33292075 Mon Sep 17 00:00:00 2001 From: Hivie Date: Fri, 8 Mar 2024 16:02:17 +0100 Subject: [PATCH 3403/4092] better assertion --- osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index c88bbec9bc..ee7acec65c 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -72,7 +72,7 @@ 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.Overlapping ? 1 : 4 * Beatmap.Difficulty.SliderMultiplier; + double multiplier = VisualisationMethod == ScrollVisualisationMethod.Constant ? 4 * Beatmap.Difficulty.SliderMultiplier : 1; return PlayfieldAdjustmentContainer.ComputeTimeRange() / multiplier; } From 27d78fdb087e0be54973ab40fb02818e747b9e62 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Sat, 9 Mar 2024 01:10:28 +0900 Subject: [PATCH 3404/4092] Add fallback to find spinner samples without a bank prefix --- osu.Game/Audio/HitSampleInfo.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Audio/HitSampleInfo.cs b/osu.Game/Audio/HitSampleInfo.cs index 24cb1730bf..f9c93d72ff 100644 --- a/osu.Game/Audio/HitSampleInfo.cs +++ b/osu.Game/Audio/HitSampleInfo.cs @@ -80,6 +80,8 @@ namespace osu.Game.Audio yield return $"Gameplay/{Bank}-{Name}{Suffix}"; yield return $"Gameplay/{Bank}-{Name}"; + + yield return $"Gameplay/{Name}"; } } From a85be2a46d574fe62416e7313afa3bfdd3873dd0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 8 Mar 2024 20:22:26 +0300 Subject: [PATCH 3405/4092] Fix merge conflicts --- osu.Game/Storyboards/StoryboardSprite.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index a6312ccb79..944d77e745 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -97,10 +97,10 @@ namespace osu.Game.Storyboards // If the logic above fails to find anything or discarded by the fact that there are loops present, latestEndTime will be double.MaxValue // and thus conservativeEndTime will be used. - double conservativeEndTime = TimelineGroup.EndTime; + double conservativeEndTime = Commands.EndTime; - foreach (var l in loops) - conservativeEndTime = Math.Max(conservativeEndTime, l.StartTime + l.CommandsDuration * l.TotalIterations); + foreach (var l in loopingGroups) + conservativeEndTime = Math.Max(conservativeEndTime, l.StartTime + l.Duration * l.TotalIterations); return Math.Min(latestEndTime, conservativeEndTime); } From c1649b76d638dbe0f32217be7027e7a98b5b6026 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 8 Mar 2024 21:33:46 +0300 Subject: [PATCH 3406/4092] Reorder command properties to match general format I had them shuffled around in the middle of the refactor. --- .../TestSceneDrawableStoryboardSprite.cs | 2 +- .../Visual/Gameplay/TestSceneLeadIn.cs | 12 +++--- .../Gameplay/TestSceneStoryboardWithOutro.cs | 2 +- .../TestSceneMultiSpectatorScreen.cs | 2 +- .../Formats/LegacyStoryboardDecoder.cs | 28 ++++++------- .../Commands/StoryboardAlphaCommand.cs | 4 +- .../StoryboardBlendingParametersCommand.cs | 4 +- .../Commands/StoryboardColourCommand.cs | 4 +- .../Storyboards/Commands/StoryboardCommand.cs | 2 +- .../Commands/StoryboardCommandGroup.cs | 40 +++++++++---------- .../Commands/StoryboardFlipHCommand.cs | 4 +- .../Commands/StoryboardFlipVCommand.cs | 4 +- .../Commands/StoryboardLoopingGroup.cs | 2 +- .../Commands/StoryboardRotationCommand.cs | 4 +- .../Commands/StoryboardScaleCommand.cs | 4 +- .../Commands/StoryboardVectorScaleCommand.cs | 4 +- .../Commands/StoryboardXCommand.cs | 4 +- .../Commands/StoryboardYCommand.cs | 4 +- 18 files changed, 65 insertions(+), 65 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs index 6209b42cbb..6bfa141d85 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs @@ -176,7 +176,7 @@ namespace osu.Game.Tests.Visual.Gameplay var sprite = new StoryboardSprite(lookupName, origin, initialPosition); var loop = sprite.AddLoopingGroup(Time.Current, 100); - loop.AddAlpha(0, 10000, 1, 1, Easing.None); + loop.AddAlpha(Easing.None, 0, 10000, 1, 1); layer.Elements.Clear(); layer.Add(sprite); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs index b2196e77b4..5a71369976 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.Gameplay var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero); - sprite.Commands.AddAlpha(firstStoryboardEvent, firstStoryboardEvent + 500, 0, 1, Easing.None); + sprite.Commands.AddAlpha(Easing.None, firstStoryboardEvent, firstStoryboardEvent + 500, 0, 1); storyboard.GetLayer("Background").Add(sprite); @@ -73,17 +73,17 @@ namespace osu.Game.Tests.Visual.Gameplay var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero); // these should be ignored as we have an alpha visibility blocker proceeding this command. - sprite.Commands.AddScale(loop_start_time, -18000, 0, 1, Easing.None); + sprite.Commands.AddScale(Easing.None, loop_start_time, -18000, 0, 1); var loopGroup = sprite.AddLoopingGroup(loop_start_time, 50); - loopGroup.AddScale(loop_start_time, -18000, 0, 1, Easing.None); + loopGroup.AddScale(Easing.None, loop_start_time, -18000, 0, 1); var target = addEventToLoop ? loopGroup : sprite.Commands; double loopRelativeOffset = addEventToLoop ? -loop_start_time : 0; - target.AddAlpha(loopRelativeOffset + firstStoryboardEvent, loopRelativeOffset + firstStoryboardEvent + 500, 0, 1, Easing.None); + target.AddAlpha(Easing.None, loopRelativeOffset + firstStoryboardEvent, loopRelativeOffset + firstStoryboardEvent + 500, 0, 1); // these should be ignored due to being in the future. - sprite.Commands.AddAlpha(18000, 20000, 0, 1, Easing.None); - loopGroup.AddAlpha(38000, 40000, 0, 1, Easing.None); + sprite.Commands.AddAlpha(Easing.None, 18000, 20000, 0, 1); + loopGroup.AddAlpha(Easing.None, 38000, 40000, 0, 1); storyboard.GetLayer("Background").Add(sprite); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs index 8bdb7297fe..aff6139c08 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs @@ -216,7 +216,7 @@ namespace osu.Game.Tests.Visual.Gameplay { var storyboard = new Storyboard(); var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero); - sprite.Commands.AddAlpha(0, duration, 1, 0, Easing.None); + sprite.Commands.AddAlpha(Easing.None, 0, duration, 1, 0); storyboard.GetLayer("Background").Add(sprite); return storyboard; } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 7cd2e2fdaa..2b17f91e68 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -424,7 +424,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestIntroStoryboardElement() => testLeadIn(b => { var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero); - sprite.Commands.AddAlpha(-2000, 0, 0, 1, Easing.None); + sprite.Commands.AddAlpha(Easing.None, -2000, 0, 0, 1); b.Storyboard.GetLayer("Background").Add(sprite); }); diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 739e81a2fe..195d59d0eb 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -204,7 +204,7 @@ namespace osu.Game.Beatmaps.Formats { float startValue = Parsing.ParseFloat(split[4]); float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue; - currentCommandsGroup?.AddAlpha(startTime, endTime, startValue, endValue, easing); + currentCommandsGroup?.AddAlpha(easing, startTime, endTime, startValue, endValue); break; } @@ -212,7 +212,7 @@ namespace osu.Game.Beatmaps.Formats { float startValue = Parsing.ParseFloat(split[4]); float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue; - currentCommandsGroup?.AddScale(startTime, endTime, startValue, endValue, easing); + currentCommandsGroup?.AddScale(easing, startTime, endTime, startValue, endValue); break; } @@ -222,7 +222,7 @@ namespace osu.Game.Beatmaps.Formats float startY = Parsing.ParseFloat(split[5]); float endX = split.Length > 6 ? Parsing.ParseFloat(split[6]) : startX; float endY = split.Length > 7 ? Parsing.ParseFloat(split[7]) : startY; - currentCommandsGroup?.AddVectorScale(startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY), easing); + currentCommandsGroup?.AddVectorScale(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY)); break; } @@ -230,7 +230,7 @@ namespace osu.Game.Beatmaps.Formats { float startValue = Parsing.ParseFloat(split[4]); float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue; - currentCommandsGroup?.AddRotation(startTime, endTime, float.RadiansToDegrees(startValue), float.RadiansToDegrees(endValue), easing); + currentCommandsGroup?.AddRotation(easing, startTime, endTime, float.RadiansToDegrees(startValue), float.RadiansToDegrees(endValue)); break; } @@ -240,8 +240,8 @@ namespace osu.Game.Beatmaps.Formats float startY = Parsing.ParseFloat(split[5]); float endX = split.Length > 6 ? Parsing.ParseFloat(split[6]) : startX; float endY = split.Length > 7 ? Parsing.ParseFloat(split[7]) : startY; - currentCommandsGroup?.AddX(startTime, endTime, startX, endX, easing); - currentCommandsGroup?.AddY(startTime, endTime, startY, endY, easing); + currentCommandsGroup?.AddX(easing, startTime, endTime, startX, endX); + currentCommandsGroup?.AddY(easing, startTime, endTime, startY, endY); break; } @@ -249,7 +249,7 @@ namespace osu.Game.Beatmaps.Formats { float startValue = Parsing.ParseFloat(split[4]); float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue; - currentCommandsGroup?.AddX(startTime, endTime, startValue, endValue, easing); + currentCommandsGroup?.AddX(easing, startTime, endTime, startValue, endValue); break; } @@ -257,7 +257,7 @@ namespace osu.Game.Beatmaps.Formats { float startValue = Parsing.ParseFloat(split[4]); float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue; - currentCommandsGroup?.AddY(startTime, endTime, startValue, endValue, easing); + currentCommandsGroup?.AddY(easing, startTime, endTime, startValue, endValue); break; } @@ -269,9 +269,9 @@ namespace osu.Game.Beatmaps.Formats float endRed = split.Length > 7 ? Parsing.ParseFloat(split[7]) : startRed; float endGreen = split.Length > 8 ? Parsing.ParseFloat(split[8]) : startGreen; float endBlue = split.Length > 9 ? Parsing.ParseFloat(split[9]) : startBlue; - currentCommandsGroup?.AddColour(startTime, endTime, + currentCommandsGroup?.AddColour(easing, startTime, endTime, new Color4(startRed / 255f, startGreen / 255f, startBlue / 255f, 1), - new Color4(endRed / 255f, endGreen / 255f, endBlue / 255f, 1), easing); + new Color4(endRed / 255f, endGreen / 255f, endBlue / 255f, 1)); break; } @@ -282,16 +282,16 @@ namespace osu.Game.Beatmaps.Formats switch (type) { case "A": - currentCommandsGroup?.AddBlendingParameters(startTime, endTime, BlendingParameters.Additive, - startTime == endTime ? BlendingParameters.Additive : BlendingParameters.Inherit, easing); + currentCommandsGroup?.AddBlendingParameters(easing, startTime, endTime, BlendingParameters.Additive, + startTime == endTime ? BlendingParameters.Additive : BlendingParameters.Inherit); break; case "H": - currentCommandsGroup?.AddFlipH(startTime, endTime, true, startTime == endTime, easing); + currentCommandsGroup?.AddFlipH(easing, startTime, endTime, true, startTime == endTime); break; case "V": - currentCommandsGroup?.AddFlipV(startTime, endTime, true, startTime == endTime, easing); + currentCommandsGroup?.AddFlipV(easing, startTime, endTime, true, startTime == endTime); break; } diff --git a/osu.Game/Storyboards/Commands/StoryboardAlphaCommand.cs b/osu.Game/Storyboards/Commands/StoryboardAlphaCommand.cs index f4a90b372a..1c17da7592 100644 --- a/osu.Game/Storyboards/Commands/StoryboardAlphaCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardAlphaCommand.cs @@ -8,8 +8,8 @@ namespace osu.Game.Storyboards.Commands { public class StoryboardAlphaCommand : StoryboardCommand { - public StoryboardAlphaCommand(double startTime, double endTime, float startValue, float endValue, Easing easing) - : base(startTime, endTime, startValue, endValue, easing) + public StoryboardAlphaCommand(Easing easing, double startTime, double endTime, float startValue, float endValue) + : base(easing, startTime, endTime, startValue, endValue) { } diff --git a/osu.Game/Storyboards/Commands/StoryboardBlendingParametersCommand.cs b/osu.Game/Storyboards/Commands/StoryboardBlendingParametersCommand.cs index 3e2e8bb0e8..9ac6613708 100644 --- a/osu.Game/Storyboards/Commands/StoryboardBlendingParametersCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardBlendingParametersCommand.cs @@ -8,8 +8,8 @@ namespace osu.Game.Storyboards.Commands { public class StoryboardBlendingParametersCommand : StoryboardCommand { - public StoryboardBlendingParametersCommand(double startTime, double endTime, BlendingParameters startValue, BlendingParameters endValue, Easing easing) - : base(startTime, endTime, startValue, endValue, easing) + public StoryboardBlendingParametersCommand(Easing easing, double startTime, double endTime, BlendingParameters startValue, BlendingParameters endValue) + : base(easing, startTime, endTime, startValue, endValue) { } diff --git a/osu.Game/Storyboards/Commands/StoryboardColourCommand.cs b/osu.Game/Storyboards/Commands/StoryboardColourCommand.cs index 66390eb305..da8a20647c 100644 --- a/osu.Game/Storyboards/Commands/StoryboardColourCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardColourCommand.cs @@ -9,8 +9,8 @@ namespace osu.Game.Storyboards.Commands { public class StoryboardColourCommand : StoryboardCommand { - public StoryboardColourCommand(double startTime, double endTime, Color4 startValue, Color4 endValue, Easing easing) - : base(startTime, endTime, startValue, endValue, easing) + public StoryboardColourCommand(Easing easing, double startTime, double endTime, Color4 startValue, Color4 endValue) + : base(easing, startTime, endTime, startValue, endValue) { } diff --git a/osu.Game/Storyboards/Commands/StoryboardCommand.cs b/osu.Game/Storyboards/Commands/StoryboardCommand.cs index 4f2f0f04a2..60c28e7833 100644 --- a/osu.Game/Storyboards/Commands/StoryboardCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardCommand.cs @@ -19,7 +19,7 @@ namespace osu.Game.Storyboards.Commands public double Duration => EndTime - StartTime; - protected StoryboardCommand(double startTime, double endTime, T startValue, T endValue, Easing easing) + protected StoryboardCommand(Easing easing, double startTime, double endTime, T startValue, T endValue) { if (endTime < startTime) endTime = startTime; diff --git a/osu.Game/Storyboards/Commands/StoryboardCommandGroup.cs b/osu.Game/Storyboards/Commands/StoryboardCommandGroup.cs index fb847d2e44..40dd8f78e6 100644 --- a/osu.Game/Storyboards/Commands/StoryboardCommandGroup.cs +++ b/osu.Game/Storyboards/Commands/StoryboardCommandGroup.cs @@ -45,35 +45,35 @@ namespace osu.Game.Storyboards.Commands [JsonIgnore] public bool HasCommands { get; private set; } - public void AddX(double startTime, double endTime, float startValue, float endValue, Easing easing) - => AddCommand(X, new StoryboardXCommand(startTime, endTime, startValue, endValue, easing)); + public void AddX(Easing easing, double startTime, double endTime, float startValue, float endValue) + => AddCommand(X, new StoryboardXCommand(easing, startTime, endTime, startValue, endValue)); - public void AddY(double startTime, double endTime, float startValue, float endValue, Easing easing) - => AddCommand(Y, new StoryboardYCommand(startTime, endTime, startValue, endValue, easing)); + public void AddY(Easing easing, double startTime, double endTime, float startValue, float endValue) + => AddCommand(Y, new StoryboardYCommand(easing, startTime, endTime, startValue, endValue)); - public void AddScale(double startTime, double endTime, float startValue, float endValue, Easing easing) - => AddCommand(Scale, new StoryboardScaleCommand(startTime, endTime, startValue, endValue, easing)); + public void AddScale(Easing easing, double startTime, double endTime, float startValue, float endValue) + => AddCommand(Scale, new StoryboardScaleCommand(easing, startTime, endTime, startValue, endValue)); - public void AddVectorScale(double startTime, double endTime, Vector2 startValue, Vector2 endValue, Easing easing) - => AddCommand(VectorScale, new StoryboardVectorScaleCommand(startTime, endTime, startValue, endValue, easing)); + public void AddVectorScale(Easing easing, double startTime, double endTime, Vector2 startValue, Vector2 endValue) + => AddCommand(VectorScale, new StoryboardVectorScaleCommand(easing, startTime, endTime, startValue, endValue)); - public void AddRotation(double startTime, double endTime, float startValue, float endValue, Easing easing) - => AddCommand(Rotation, new StoryboardRotationCommand(startTime, endTime, startValue, endValue, easing)); + public void AddRotation(Easing easing, double startTime, double endTime, float startValue, float endValue) + => AddCommand(Rotation, new StoryboardRotationCommand(easing, startTime, endTime, startValue, endValue)); - public void AddColour(double startTime, double endTime, Color4 startValue, Color4 endValue, Easing easing) - => AddCommand(Colour, new StoryboardColourCommand(startTime, endTime, startValue, endValue, easing)); + public void AddColour(Easing easing, double startTime, double endTime, Color4 startValue, Color4 endValue) + => AddCommand(Colour, new StoryboardColourCommand(easing, startTime, endTime, startValue, endValue)); - public void AddAlpha(double startTime, double endTime, float startValue, float endValue, Easing easing) - => AddCommand(Alpha, new StoryboardAlphaCommand(startTime, endTime, startValue, endValue, easing)); + public void AddAlpha(Easing easing, double startTime, double endTime, float startValue, float endValue) + => AddCommand(Alpha, new StoryboardAlphaCommand(easing, startTime, endTime, startValue, endValue)); - public void AddBlendingParameters(double startTime, double endTime, BlendingParameters startValue, BlendingParameters endValue, Easing easing) - => AddCommand(BlendingParameters, new StoryboardBlendingParametersCommand(startTime, endTime, startValue, endValue, easing)); + public void AddBlendingParameters(Easing easing, double startTime, double endTime, BlendingParameters startValue, BlendingParameters endValue) + => AddCommand(BlendingParameters, new StoryboardBlendingParametersCommand(easing, startTime, endTime, startValue, endValue)); - public void AddFlipH(double startTime, double endTime, bool startValue, bool endValue, Easing easing) - => AddCommand(FlipH, new StoryboardFlipHCommand(startTime, endTime, startValue, endValue, easing)); + public void AddFlipH(Easing easing, double startTime, double endTime, bool startValue, bool endValue) + => AddCommand(FlipH, new StoryboardFlipHCommand(easing, startTime, endTime, startValue, endValue)); - public void AddFlipV(double startTime, double endTime, bool startValue, bool endValue, Easing easing) - => AddCommand(FlipV, new StoryboardFlipVCommand(startTime, endTime, startValue, endValue, easing)); + public void AddFlipV(Easing easing, double startTime, double endTime, bool startValue, bool endValue) + => AddCommand(FlipV, new StoryboardFlipVCommand(easing, startTime, endTime, startValue, endValue)); /// /// Adds the given storyboard to the target . diff --git a/osu.Game/Storyboards/Commands/StoryboardFlipHCommand.cs b/osu.Game/Storyboards/Commands/StoryboardFlipHCommand.cs index 26aef23226..fa07ff6645 100644 --- a/osu.Game/Storyboards/Commands/StoryboardFlipHCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardFlipHCommand.cs @@ -9,8 +9,8 @@ namespace osu.Game.Storyboards.Commands { public class StoryboardFlipHCommand : StoryboardCommand { - public StoryboardFlipHCommand(double startTime, double endTime, bool startValue, bool endValue, Easing easing) - : base(startTime, endTime, startValue, endValue, easing) + public StoryboardFlipHCommand(Easing easing, double startTime, double endTime, bool startValue, bool endValue) + : base(easing, startTime, endTime, startValue, endValue) { } diff --git a/osu.Game/Storyboards/Commands/StoryboardFlipVCommand.cs b/osu.Game/Storyboards/Commands/StoryboardFlipVCommand.cs index 88423da2af..fa6a170c25 100644 --- a/osu.Game/Storyboards/Commands/StoryboardFlipVCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardFlipVCommand.cs @@ -9,8 +9,8 @@ namespace osu.Game.Storyboards.Commands { public class StoryboardFlipVCommand : StoryboardCommand { - public StoryboardFlipVCommand(double startTime, double endTime, bool startValue, bool endValue, Easing easing) - : base(startTime, endTime, startValue, endValue, easing) + public StoryboardFlipVCommand(Easing easing, double startTime, double endTime, bool startValue, bool endValue) + : base(easing, startTime, endTime, startValue, endValue) { } diff --git a/osu.Game/Storyboards/Commands/StoryboardLoopingGroup.cs b/osu.Game/Storyboards/Commands/StoryboardLoopingGroup.cs index e97de84ab7..a886998679 100644 --- a/osu.Game/Storyboards/Commands/StoryboardLoopingGroup.cs +++ b/osu.Game/Storyboards/Commands/StoryboardLoopingGroup.cs @@ -43,7 +43,7 @@ namespace osu.Game.Storyboards.Commands // In an ideal world, we would multiply the command duration by TotalIterations in command end time. // Unfortunately this would clash with how stable handled end times, and results in some storyboards playing outro // sequences for minutes or hours. - : base(loopingGroup.loopStartTime + command.StartTime, loopingGroup.loopStartTime + command.EndTime, command.StartValue, command.EndValue, command.Easing) + : base(command.Easing, loopingGroup.loopStartTime + command.StartTime, loopingGroup.loopStartTime + command.EndTime, command.StartValue, command.EndValue) { this.command = command; this.loopingGroup = loopingGroup; diff --git a/osu.Game/Storyboards/Commands/StoryboardRotationCommand.cs b/osu.Game/Storyboards/Commands/StoryboardRotationCommand.cs index 4347dc9d77..7e097fce25 100644 --- a/osu.Game/Storyboards/Commands/StoryboardRotationCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardRotationCommand.cs @@ -8,8 +8,8 @@ namespace osu.Game.Storyboards.Commands { public class StoryboardRotationCommand : StoryboardCommand { - public StoryboardRotationCommand(double startTime, double endTime, float startValue, float endValue, Easing easing) - : base(startTime, endTime, startValue, endValue, easing) + public StoryboardRotationCommand(Easing easing, double startTime, double endTime, float startValue, float endValue) + : base(easing, startTime, endTime, startValue, endValue) { } diff --git a/osu.Game/Storyboards/Commands/StoryboardScaleCommand.cs b/osu.Game/Storyboards/Commands/StoryboardScaleCommand.cs index b0f33fd6b8..832533af5e 100644 --- a/osu.Game/Storyboards/Commands/StoryboardScaleCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardScaleCommand.cs @@ -9,8 +9,8 @@ namespace osu.Game.Storyboards.Commands { public class StoryboardScaleCommand : StoryboardCommand { - public StoryboardScaleCommand(double startTime, double endTime, float startValue, float endValue, Easing easing) - : base(startTime, endTime, startValue, endValue, easing) + public StoryboardScaleCommand(Easing easing, double startTime, double endTime, float startValue, float endValue) + : base(easing, startTime, endTime, startValue, endValue) { } diff --git a/osu.Game/Storyboards/Commands/StoryboardVectorScaleCommand.cs b/osu.Game/Storyboards/Commands/StoryboardVectorScaleCommand.cs index 5d3fef5948..06983a1590 100644 --- a/osu.Game/Storyboards/Commands/StoryboardVectorScaleCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardVectorScaleCommand.cs @@ -10,8 +10,8 @@ namespace osu.Game.Storyboards.Commands { public class StoryboardVectorScaleCommand : StoryboardCommand { - public StoryboardVectorScaleCommand(double startTime, double endTime, Vector2 startValue, Vector2 endValue, Easing easing) - : base(startTime, endTime, startValue, endValue, easing) + public StoryboardVectorScaleCommand(Easing easing, double startTime, double endTime, Vector2 startValue, Vector2 endValue) + : base(easing, startTime, endTime, startValue, endValue) { } diff --git a/osu.Game/Storyboards/Commands/StoryboardXCommand.cs b/osu.Game/Storyboards/Commands/StoryboardXCommand.cs index 7df9a75768..d52e9c8a05 100644 --- a/osu.Game/Storyboards/Commands/StoryboardXCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardXCommand.cs @@ -8,8 +8,8 @@ namespace osu.Game.Storyboards.Commands { public class StoryboardXCommand : StoryboardCommand { - public StoryboardXCommand(double startTime, double endTime, float startValue, float endValue, Easing easing) - : base(startTime, endTime, startValue, endValue, easing) + public StoryboardXCommand(Easing easing, double startTime, double endTime, float startValue, float endValue) + : base(easing, startTime, endTime, startValue, endValue) { } diff --git a/osu.Game/Storyboards/Commands/StoryboardYCommand.cs b/osu.Game/Storyboards/Commands/StoryboardYCommand.cs index d7dc32a0f3..90dfe4d995 100644 --- a/osu.Game/Storyboards/Commands/StoryboardYCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardYCommand.cs @@ -8,8 +8,8 @@ namespace osu.Game.Storyboards.Commands { public class StoryboardYCommand : StoryboardCommand { - public StoryboardYCommand(double startTime, double endTime, float startValue, float endValue, Easing easing) - : base(startTime, endTime, startValue, endValue, easing) + public StoryboardYCommand(Easing easing, double startTime, double endTime, float startValue, float endValue) + : base(easing, startTime, endTime, startValue, endValue) { } From 0efa12a86a5b3f19251e90b65f80f523a9ded5bc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 8 Mar 2024 21:52:56 +0300 Subject: [PATCH 3407/4092] Fix parameter commands applying initial value before start time --- osu.Game/Storyboards/Commands/IStoryboardCommand.cs | 3 +++ .../Commands/StoryboardBlendingParametersCommand.cs | 6 +++++- osu.Game/Storyboards/Commands/StoryboardFlipHCommand.cs | 6 +++++- osu.Game/Storyboards/Commands/StoryboardFlipVCommand.cs | 6 +++++- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/osu.Game/Storyboards/Commands/IStoryboardCommand.cs b/osu.Game/Storyboards/Commands/IStoryboardCommand.cs index 6efb19afe4..ea14f5fa40 100644 --- a/osu.Game/Storyboards/Commands/IStoryboardCommand.cs +++ b/osu.Game/Storyboards/Commands/IStoryboardCommand.cs @@ -28,6 +28,9 @@ namespace osu.Game.Storyboards.Commands /// /// Sets the value of the corresponding property in to the start value of this command. /// + /// + /// Parameter commands (e.g. / / ) only apply the start value if they have zero duration, i.e. take "permanent" effect regardless of time. + /// /// The target drawable. void ApplyInitialValue(TDrawable d) where TDrawable : Drawable, IFlippable, IVectorScalable; diff --git a/osu.Game/Storyboards/Commands/StoryboardBlendingParametersCommand.cs b/osu.Game/Storyboards/Commands/StoryboardBlendingParametersCommand.cs index 9ac6613708..cf9cadf1a7 100644 --- a/osu.Game/Storyboards/Commands/StoryboardBlendingParametersCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardBlendingParametersCommand.cs @@ -15,7 +15,11 @@ namespace osu.Game.Storyboards.Commands public override string PropertyName => nameof(Drawable.Blending); - public override void ApplyInitialValue(TDrawable d) => d.Blending = StartValue; + public override void ApplyInitialValue(TDrawable d) + { + if (StartTime == EndTime) + d.Blending = StartValue; + } public override TransformSequence ApplyTransforms(TDrawable d) => d.TransformTo(nameof(d.Blending), StartValue).Delay(Duration) diff --git a/osu.Game/Storyboards/Commands/StoryboardFlipHCommand.cs b/osu.Game/Storyboards/Commands/StoryboardFlipHCommand.cs index fa07ff6645..fbf7295f15 100644 --- a/osu.Game/Storyboards/Commands/StoryboardFlipHCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardFlipHCommand.cs @@ -16,7 +16,11 @@ namespace osu.Game.Storyboards.Commands public override string PropertyName => nameof(IFlippable.FlipH); - public override void ApplyInitialValue(TDrawable d) => d.FlipH = StartValue; + public override void ApplyInitialValue(TDrawable d) + { + if (StartTime == EndTime) + d.FlipH = StartValue; + } public override TransformSequence ApplyTransforms(TDrawable d) => d.TransformTo(nameof(IFlippable.FlipH), StartValue).Delay(Duration) diff --git a/osu.Game/Storyboards/Commands/StoryboardFlipVCommand.cs b/osu.Game/Storyboards/Commands/StoryboardFlipVCommand.cs index fa6a170c25..136bd52f1f 100644 --- a/osu.Game/Storyboards/Commands/StoryboardFlipVCommand.cs +++ b/osu.Game/Storyboards/Commands/StoryboardFlipVCommand.cs @@ -16,7 +16,11 @@ namespace osu.Game.Storyboards.Commands public override string PropertyName => nameof(IFlippable.FlipV); - public override void ApplyInitialValue(TDrawable d) => d.FlipV = StartValue; + public override void ApplyInitialValue(TDrawable d) + { + if (StartTime == EndTime) + d.FlipV = StartValue; + } public override TransformSequence ApplyTransforms(TDrawable d) => d.TransformTo(nameof(IFlippable.FlipV), StartValue).Delay(Duration) From 6861d9a302f2995ac05ff717e8911d9eabe3e25b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 8 Mar 2024 22:28:13 +0300 Subject: [PATCH 3408/4092] Expose storyboard command lists as read-only and remove unnecessary memory footprint Mutation should be done only with the methods exposed by `StoryboardCommandGroup`. --- .../Commands/StoryboardCommandGroup.cs | 80 +++++++++++++------ 1 file changed, 57 insertions(+), 23 deletions(-) diff --git a/osu.Game/Storyboards/Commands/StoryboardCommandGroup.cs b/osu.Game/Storyboards/Commands/StoryboardCommandGroup.cs index 40dd8f78e6..0925231412 100644 --- a/osu.Game/Storyboards/Commands/StoryboardCommandGroup.cs +++ b/osu.Game/Storyboards/Commands/StoryboardCommandGroup.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Linq; using Newtonsoft.Json; using osu.Framework.Graphics; using osu.Framework.Lists; @@ -12,20 +13,45 @@ namespace osu.Game.Storyboards.Commands { public class StoryboardCommandGroup { - public SortedList> X = new SortedList>(); - public SortedList> Y = new SortedList>(); - public SortedList> Scale = new SortedList>(); - public SortedList> VectorScale = new SortedList>(); - public SortedList> Rotation = new SortedList>(); - public SortedList> Colour = new SortedList>(); - public SortedList> Alpha = new SortedList>(); - public SortedList> BlendingParameters = new SortedList>(); - public SortedList> FlipH = new SortedList>(); - public SortedList> FlipV = new SortedList>(); + private readonly SortedList> x = new SortedList>(); - public IReadOnlyList AllCommands => allCommands; + public IReadOnlyList> X => x; - private readonly List allCommands = new List(); + private readonly SortedList> y = new SortedList>(); + + public IReadOnlyList> Y => y; + + private readonly SortedList> scale = new SortedList>(); + + public IReadOnlyList> Scale => scale; + + private readonly SortedList> vectorScale = new SortedList>(); + + public IReadOnlyList> VectorScale => vectorScale; + + private readonly SortedList> rotation = new SortedList>(); + + public IReadOnlyList> Rotation => rotation; + + private readonly SortedList> colour = new SortedList>(); + + public IReadOnlyList> Colour => colour; + + private readonly SortedList> alpha = new SortedList>(); + + public IReadOnlyList> Alpha => alpha; + + private readonly SortedList> blendingParameters = new SortedList>(); + + public IReadOnlyList> BlendingParameters => blendingParameters; + + private readonly SortedList> flipH = new SortedList>(); + + public IReadOnlyList> FlipH => flipH; + + private readonly SortedList> flipV = new SortedList>(); + + public IReadOnlyList> FlipV => flipV; /// /// Returns the earliest start time of the commands added to this group. @@ -45,35 +71,44 @@ namespace osu.Game.Storyboards.Commands [JsonIgnore] public bool HasCommands { get; private set; } + private readonly IReadOnlyList[] lists; + + public IEnumerable AllCommands => lists.SelectMany(g => g); + + public StoryboardCommandGroup() + { + lists = new IReadOnlyList[] { X, Y, Scale, VectorScale, Rotation, Colour, Alpha, BlendingParameters, FlipH, FlipV }; + } + public void AddX(Easing easing, double startTime, double endTime, float startValue, float endValue) - => AddCommand(X, new StoryboardXCommand(easing, startTime, endTime, startValue, endValue)); + => AddCommand(x, new StoryboardXCommand(easing, startTime, endTime, startValue, endValue)); public void AddY(Easing easing, double startTime, double endTime, float startValue, float endValue) - => AddCommand(Y, new StoryboardYCommand(easing, startTime, endTime, startValue, endValue)); + => AddCommand(y, new StoryboardYCommand(easing, startTime, endTime, startValue, endValue)); public void AddScale(Easing easing, double startTime, double endTime, float startValue, float endValue) - => AddCommand(Scale, new StoryboardScaleCommand(easing, startTime, endTime, startValue, endValue)); + => AddCommand(scale, new StoryboardScaleCommand(easing, startTime, endTime, startValue, endValue)); public void AddVectorScale(Easing easing, double startTime, double endTime, Vector2 startValue, Vector2 endValue) - => AddCommand(VectorScale, new StoryboardVectorScaleCommand(easing, startTime, endTime, startValue, endValue)); + => AddCommand(vectorScale, new StoryboardVectorScaleCommand(easing, startTime, endTime, startValue, endValue)); public void AddRotation(Easing easing, double startTime, double endTime, float startValue, float endValue) - => AddCommand(Rotation, new StoryboardRotationCommand(easing, startTime, endTime, startValue, endValue)); + => AddCommand(rotation, new StoryboardRotationCommand(easing, startTime, endTime, startValue, endValue)); public void AddColour(Easing easing, double startTime, double endTime, Color4 startValue, Color4 endValue) - => AddCommand(Colour, new StoryboardColourCommand(easing, startTime, endTime, startValue, endValue)); + => AddCommand(colour, new StoryboardColourCommand(easing, startTime, endTime, startValue, endValue)); public void AddAlpha(Easing easing, double startTime, double endTime, float startValue, float endValue) - => AddCommand(Alpha, new StoryboardAlphaCommand(easing, startTime, endTime, startValue, endValue)); + => AddCommand(alpha, new StoryboardAlphaCommand(easing, startTime, endTime, startValue, endValue)); public void AddBlendingParameters(Easing easing, double startTime, double endTime, BlendingParameters startValue, BlendingParameters endValue) - => AddCommand(BlendingParameters, new StoryboardBlendingParametersCommand(easing, startTime, endTime, startValue, endValue)); + => AddCommand(blendingParameters, new StoryboardBlendingParametersCommand(easing, startTime, endTime, startValue, endValue)); public void AddFlipH(Easing easing, double startTime, double endTime, bool startValue, bool endValue) - => AddCommand(FlipH, new StoryboardFlipHCommand(easing, startTime, endTime, startValue, endValue)); + => AddCommand(flipH, new StoryboardFlipHCommand(easing, startTime, endTime, startValue, endValue)); public void AddFlipV(Easing easing, double startTime, double endTime, bool startValue, bool endValue) - => AddCommand(FlipV, new StoryboardFlipVCommand(easing, startTime, endTime, startValue, endValue)); + => AddCommand(flipV, new StoryboardFlipVCommand(easing, startTime, endTime, startValue, endValue)); /// /// Adds the given storyboard to the target . @@ -83,7 +118,6 @@ namespace osu.Game.Storyboards.Commands protected virtual void AddCommand(ICollection> list, StoryboardCommand command) { list.Add(command); - allCommands.Add(command); HasCommands = true; if (command.StartTime < StartTime) From 1942d46a38fac400e28dbe95a3d5a2d6c0a95cc2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 8 Mar 2024 22:37:27 +0300 Subject: [PATCH 3409/4092] Remove leftover debugging code --- osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index 507a51aca4..da4b5c641d 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -102,9 +102,6 @@ namespace osu.Game.Storyboards.Drawables else Texture = textureStore.Get(Sprite.Path); - if (Sprite.Path == "SB/textbox.png") - Debugger.Break(); - Sprite.ApplyTransforms(this); } From 8c92bb0595f6528c86c39a0851fd7e6edd8be5f6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 8 Mar 2024 23:09:16 +0300 Subject: [PATCH 3410/4092] Remove unused using directive --- osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index da4b5c641d..ec875219b6 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; From 9f71eac1dbe26df48cdcdaff181a0ca6be71f219 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 8 Mar 2024 23:09:50 +0300 Subject: [PATCH 3411/4092] Remove extra end line --- osu.Game/Storyboards/StoryboardSprite.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index 944d77e745..80a76cd831 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -195,4 +195,3 @@ namespace osu.Game.Storyboards public override string ToString() => $"{Path}, {Origin}, {InitialPosition}"; } } - From db1c59475b0e31fd17d6944804c23a65b206c756 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 9 Feb 2024 15:10:45 -0800 Subject: [PATCH 3412/4092] Wrap beatmap listing filters and match web spacing --- .../BeatmapListingSearchControl.cs | 1 + .../BeatmapListing/BeatmapSearchFilterRow.cs | 37 ++++--------------- ...BeatmapSearchMultipleSelectionFilterRow.cs | 4 +- .../Overlays/BeatmapListing/FilterTabItem.cs | 2 - 4 files changed, 9 insertions(+), 35 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index 3fa0fc7a77..bab64165cb 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -128,6 +128,7 @@ namespace osu.Game.Overlays.BeatmapListing RelativeSizeAxes = Axes.X, Direction = FillDirection.Vertical, Padding = new MarginPadding { Horizontal = 10 }, + Spacing = new Vector2(5), Children = new Drawable[] { generalFilter = new BeatmapSearchGeneralFilterRow(), diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs index 6d75521cb0..f79695a123 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs @@ -1,14 +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.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osuTK; using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Localisation; @@ -49,8 +47,6 @@ namespace osu.Game.Overlays.BeatmapListing { new OsuSpriteText { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, Font = OsuFont.GetFont(size: 13), Text = header }, @@ -69,10 +65,8 @@ namespace osu.Game.Overlays.BeatmapListing { public BeatmapSearchFilter() { - Anchor = Anchor.BottomLeft; - Origin = Anchor.BottomLeft; RelativeSizeAxes = Axes.X; - Height = 15; + AutoSizeAxes = Axes.Y; TabContainer.Spacing = new Vector2(10, 0); @@ -83,33 +77,16 @@ namespace osu.Game.Overlays.BeatmapListing } } - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - if (Dropdown is FilterDropdown fd) - fd.AccentColour = colourProvider.Light2; - } - - protected override Dropdown CreateDropdown() => new FilterDropdown(); + protected override Dropdown CreateDropdown() => null!; protected override TabItem CreateTabItem(T value) => new FilterTabItem(value); - private partial class FilterDropdown : OsuTabDropdown + protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer { - protected override DropdownHeader CreateHeader() => new FilterHeader - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight - }; - - private partial class FilterHeader : OsuTabDropdownHeader - { - public FilterHeader() - { - Background.Height = 1; - } - } - } + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + AllowMultiline = true, + }; } } } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs index abd2643a41..e59beb43ff 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs @@ -52,10 +52,8 @@ namespace osu.Game.Overlays.BeatmapListing [BackgroundDependencyLoader] private void load() { - Anchor = Anchor.BottomLeft; - Origin = Anchor.BottomLeft; RelativeSizeAxes = Axes.X; - Height = 15; + AutoSizeAxes = Axes.Y; Spacing = new Vector2(10, 0); AddRange(GetValues().Select(CreateTabItem)); diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index c33d5056fa..831cf812ff 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -33,8 +33,6 @@ namespace osu.Game.Overlays.BeatmapListing private void load() { AutoSizeAxes = Axes.Both; - Anchor = Anchor.BottomLeft; - Origin = Anchor.BottomLeft; AddRangeInternal(new Drawable[] { text = new OsuSpriteText From 26c97ef73314a5e307beb5b1b2b755fa78188dc7 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 9 Mar 2024 00:51:33 +0300 Subject: [PATCH 3413/4092] Fix WikiPanelContainer causing poor performance --- osu.Game/Overlays/Wiki/WikiPanelContainer.cs | 68 +++++++++++++------- 1 file changed, 43 insertions(+), 25 deletions(-) diff --git a/osu.Game/Overlays/Wiki/WikiPanelContainer.cs b/osu.Game/Overlays/Wiki/WikiPanelContainer.cs index cbffe5732e..555dab852e 100644 --- a/osu.Game/Overlays/Wiki/WikiPanelContainer.cs +++ b/osu.Game/Overlays/Wiki/WikiPanelContainer.cs @@ -3,7 +3,6 @@ #nullable disable -using System; using Markdig.Syntax; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -22,29 +21,61 @@ using osuTK.Graphics; namespace osu.Game.Overlays.Wiki { - public partial class WikiPanelContainer : Container + public partial class WikiPanelContainer : CompositeDrawable { - private WikiPanelMarkdownContainer panelContainer; + private const float padding = 3; private readonly string text; - private readonly bool isFullWidth; public WikiPanelContainer(string text, bool isFullWidth = false) { this.text = text; this.isFullWidth = isFullWidth; - - RelativeSizeAxes = Axes.X; - Padding = new MarginPadding(3); } + private PanelBackground background; + [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider, IAPIProvider api) + private void load(IAPIProvider api) { - Children = new Drawable[] + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + InternalChildren = new Drawable[] { + background = new PanelBackground + { + BypassAutoSizeAxes = Axes.Both + }, new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(padding), + Child = new WikiPanelMarkdownContainer(isFullWidth) + { + CurrentPath = $@"{api.WebsiteRootUrl}/wiki/", + Text = text, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + } + } + }; + } + + protected override void Update() + { + base.Update(); + background.Size = Parent!.DrawSize * new Vector2(Size.X, 1); + } + + private partial class PanelBackground : CompositeDrawable + { + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + Padding = new MarginPadding(padding); + InternalChild = new Container { RelativeSizeAxes = Axes.Both, Masking = true, @@ -60,22 +91,9 @@ namespace osu.Game.Overlays.Wiki { Colour = colourProvider.Background4, RelativeSizeAxes = Axes.Both, - }, - }, - panelContainer = new WikiPanelMarkdownContainer(isFullWidth) - { - CurrentPath = $@"{api.WebsiteRootUrl}/wiki/", - Text = text, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - } - }; - } - - protected override void Update() - { - base.Update(); - Height = Math.Max(panelContainer.Height, Parent!.DrawHeight); + } + }; + } } private partial class WikiPanelMarkdownContainer : WikiMarkdownContainer From 82048df9f16607c8b9887ddf91cf880fdb5927c3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 9 Mar 2024 04:42:17 +0300 Subject: [PATCH 3414/4092] Add basic test scene for asserting storyboard commands behaviour Pending actual test coverage. --- .../Gameplay/TestSceneStoryboardCommands.cs | 166 ++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardCommands.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardCommands.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardCommands.cs new file mode 100644 index 0000000000..1893182b32 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardCommands.cs @@ -0,0 +1,166 @@ +// 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.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.IO.Stores; +using osu.Framework.Timing; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Mods; +using osu.Game.Storyboards; +using osu.Game.Storyboards.Drawables; +using osu.Game.Tests.Resources; +using osuTK; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public partial class TestSceneStoryboardCommands : OsuTestScene + { + [Cached(typeof(Storyboard))] + private TestStoryboard storyboard { get; set; } = new TestStoryboard + { + UseSkinSprites = false, + AlwaysProvideTexture = true, + }; + + private readonly ManualClock manualClock = new ManualClock { Rate = 1, IsRunning = true }; + private bool forward; + + private const string lookup_name = "hitcircleoverlay"; + private const double clock_limit = 2500; + + protected override Container Content => content; + + private Container content = null!; + private SpriteText timelineText = null!; + private Box timelineMarker = null!; + + [BackgroundDependencyLoader] + private void load() + { + base.Content.Children = new Drawable[] + { + content = new Container + { + RelativeSizeAxes = Axes.Both, + }, + timelineText = new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Margin = new MarginPadding { Bottom = 60 }, + }, + timelineMarker = new Box + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomCentre, + RelativePositionAxes = Axes.X, + Size = new Vector2(2, 50), + }, + }; + } + + [SetUp] + public void SetUp() + { + manualClock.CurrentTime = 0; + forward = true; + } + + [Test] + public void TestLoop() + { + AddStep("create sprite", () => Child = createSprite(s => + { + var loop = s.AddLoopingGroup(600, 10); + loop.AddY(Easing.OutBounce, 0, 500, 100, 240); + loop.AddY(Easing.OutQuint, 700, 1000, 240, 100); + })); + } + + protected override void Update() + { + base.Update(); + + if (manualClock.CurrentTime > clock_limit || manualClock.CurrentTime < 0) + forward = !forward; + + manualClock.CurrentTime += Time.Elapsed * (forward ? 1 : -1); + timelineText.Text = $"Time: {manualClock.CurrentTime:0}ms"; + timelineMarker.X = (float)(manualClock.CurrentTime / clock_limit); + } + + private DrawableStoryboard createSprite(Action? addCommands = null) + { + var layer = storyboard.GetLayer("Background"); + + var sprite = new StoryboardSprite(lookup_name, Anchor.Centre, new Vector2(320, 240)); + sprite.Commands.AddScale(Easing.None, 0, clock_limit, 0.5f, 0.5f); + sprite.Commands.AddAlpha(Easing.None, 0, clock_limit, 1, 1); + addCommands?.Invoke(sprite); + + layer.Elements.Clear(); + layer.Add(sprite); + + return storyboard.CreateDrawable().With(c => c.Clock = new FramedClock(manualClock)); + } + + private partial class TestStoryboard : Storyboard + { + public override DrawableStoryboard CreateDrawable(IReadOnlyList? mods = null) + { + return new TestDrawableStoryboard(this, mods); + } + + public bool AlwaysProvideTexture { get; set; } + + public override string GetStoragePathFromStoryboardPath(string path) => AlwaysProvideTexture ? path : string.Empty; + + private partial class TestDrawableStoryboard : DrawableStoryboard + { + private readonly bool alwaysProvideTexture; + + public TestDrawableStoryboard(TestStoryboard storyboard, IReadOnlyList? mods) + : base(storyboard, mods) + { + alwaysProvideTexture = storyboard.AlwaysProvideTexture; + } + + protected override IResourceStore CreateResourceLookupStore() => alwaysProvideTexture + ? new AlwaysReturnsTextureStore() + : new ResourceStore(); + + internal class AlwaysReturnsTextureStore : IResourceStore + { + private const string test_image = "Resources/Textures/test-image.png"; + + private readonly DllResourceStore store; + + public AlwaysReturnsTextureStore() + { + store = TestResources.GetStore(); + } + + public void Dispose() => store.Dispose(); + + public byte[] Get(string name) => store.Get(test_image); + + public Task GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) => store.GetAsync(test_image, cancellationToken); + + public Stream GetStream(string name) => store.GetStream(test_image); + + public IEnumerable GetAvailableResources() => store.GetAvailableResources(); + } + } + } + } +} From dd36942508aee46ff0b0357daed108f293260924 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 9 Mar 2024 13:58:05 +0300 Subject: [PATCH 3415/4092] Reduce allocations in DrawableFlag tooltip --- osu.Game/Users/Drawables/DrawableFlag.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Users/Drawables/DrawableFlag.cs b/osu.Game/Users/Drawables/DrawableFlag.cs index 289f68ee7f..08c323d42c 100644 --- a/osu.Game/Users/Drawables/DrawableFlag.cs +++ b/osu.Game/Users/Drawables/DrawableFlag.cs @@ -15,11 +15,14 @@ namespace osu.Game.Users.Drawables { private readonly CountryCode countryCode; - public LocalisableString TooltipText => countryCode == CountryCode.Unknown ? string.Empty : countryCode.GetDescription(); + public LocalisableString TooltipText => countryCode == CountryCode.Unknown ? string.Empty : description; + + private readonly string description; public DrawableFlag(CountryCode countryCode) { this.countryCode = countryCode; + description = countryCode.GetDescription(); } [BackgroundDependencyLoader] From 58b6acde10eee69faedd57fcf5c6f1869eb8fba7 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 9 Mar 2024 14:10:32 +0300 Subject: [PATCH 3416/4092] Further simplify tooltip text creation --- osu.Game/Users/Drawables/DrawableFlag.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Users/Drawables/DrawableFlag.cs b/osu.Game/Users/Drawables/DrawableFlag.cs index 08c323d42c..39f0ab370b 100644 --- a/osu.Game/Users/Drawables/DrawableFlag.cs +++ b/osu.Game/Users/Drawables/DrawableFlag.cs @@ -15,14 +15,14 @@ namespace osu.Game.Users.Drawables { private readonly CountryCode countryCode; - public LocalisableString TooltipText => countryCode == CountryCode.Unknown ? string.Empty : description; + public LocalisableString TooltipText => tooltipText; - private readonly string description; + private readonly string tooltipText; public DrawableFlag(CountryCode countryCode) { this.countryCode = countryCode; - description = countryCode.GetDescription(); + tooltipText = countryCode == CountryCode.Unknown ? string.Empty : countryCode.GetDescription(); } [BackgroundDependencyLoader] From b8a362fcb69390fb774bcb5a4afc78bb138ddf7e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 9 Mar 2024 20:17:27 +0800 Subject: [PATCH 3417/4092] Simplify assignment by using an auto property --- osu.Game/Users/Drawables/DrawableFlag.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Users/Drawables/DrawableFlag.cs b/osu.Game/Users/Drawables/DrawableFlag.cs index 39f0ab370b..6813b13cef 100644 --- a/osu.Game/Users/Drawables/DrawableFlag.cs +++ b/osu.Game/Users/Drawables/DrawableFlag.cs @@ -15,14 +15,12 @@ namespace osu.Game.Users.Drawables { private readonly CountryCode countryCode; - public LocalisableString TooltipText => tooltipText; - - private readonly string tooltipText; + public LocalisableString TooltipText { get; } public DrawableFlag(CountryCode countryCode) { this.countryCode = countryCode; - tooltipText = countryCode == CountryCode.Unknown ? string.Empty : countryCode.GetDescription(); + TooltipText = countryCode == CountryCode.Unknown ? string.Empty : countryCode.GetDescription(); } [BackgroundDependencyLoader] From 31739be49912ffedf1ae7a4deeb747b8b2ec9204 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 9 Mar 2024 20:46:48 +0800 Subject: [PATCH 3418/4092] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index b143a3a6b1..55bd68dea0 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + From 549a8d678e4a9d8c91a1a220bef6f3519aaa687d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 9 Mar 2024 20:50:54 +0300 Subject: [PATCH 3419/4092] Reduce allocations in ControlPointList --- .../Screens/Edit/Timing/ControlPointList.cs | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointList.cs b/osu.Game/Screens/Edit/Timing/ControlPointList.cs index 7cd1dbc630..4e4090ccd0 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointList.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointList.cs @@ -162,26 +162,43 @@ namespace osu.Game.Screens.Edit.Timing // If the selected group only has one control point, update the tracking type. case 1: - trackedType = selectedGroup.Value?.ControlPoints.Single().GetType(); + trackedType = selectedGroup.Value?.ControlPoints[0].GetType(); break; // If the selected group has more than one control point, choose the first as the tracking type // if we don't already have a singular tracked type. default: - trackedType ??= selectedGroup.Value?.ControlPoints.FirstOrDefault()?.GetType(); + trackedType ??= selectedGroup.Value?.ControlPoints[0].GetType(); break; } } if (trackedType != null) { + double accurateTime = clock.CurrentTimeAccurate; + // We don't have an efficient way of looking up groups currently, only individual point types. // To improve the efficiency of this in the future, we should reconsider the overall structure of ControlPointInfo. // Find the next group which has the same type as the selected one. - var found = Beatmap.ControlPointInfo.Groups - .Where(g => g.ControlPoints.Any(cp => cp.GetType() == trackedType)) - .LastOrDefault(g => g.Time <= clock.CurrentTimeAccurate); + ControlPointGroup? found = null; + + for (int i = 0; i < Beatmap.ControlPointInfo.Groups.Count; i++) + { + var g = Beatmap.ControlPointInfo.Groups[i]; + + if (g.Time > accurateTime) + continue; + + for (int j = 0; j < g.ControlPoints.Count; j++) + { + if (g.ControlPoints[j].GetType() == trackedType) + { + found = g; + break; + } + } + } if (found != null) selectedGroup.Value = found; From e08651668c46f3c7cbef87b2fadd23d609e9fee2 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 9 Mar 2024 21:55:00 +0300 Subject: [PATCH 3420/4092] Fix TestSceneSkinnableSound failing --- osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs index 3f78dbfd96..6c8dc6a220 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual.Gameplay private TestSkinSourceContainer skinSource = null!; private PausableSkinnableSound skinnableSound = null!; - private const string sample_lookup = "Gameplay/normal-sliderslide"; + private const string sample_lookup = "Gameplay/Argon/normal-sliderslide"; [SetUpSteps] public void SetUpSteps() From 8a1c5a754763cb8cc25963ab0e5b3439683a357a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 10 Mar 2024 07:23:22 +0300 Subject: [PATCH 3421/4092] Adjust time values --- .../Visual/Gameplay/TestSceneStoryboardCommands.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardCommands.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardCommands.cs index 1893182b32..11c07824d3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardCommands.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardCommands.cs @@ -81,9 +81,9 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("create sprite", () => Child = createSprite(s => { - var loop = s.AddLoopingGroup(600, 10); - loop.AddY(Easing.OutBounce, 0, 500, 100, 240); - loop.AddY(Easing.OutQuint, 700, 1000, 240, 100); + var loop = s.AddLoopingGroup(500, 10); + loop.AddY(Easing.OutBounce, 0, 600, 100, 240); + loop.AddY(Easing.OutQuint, 800, 1200, 240, 100); })); } From d039b565621790c8df3b5eaf0243deaa0cd9d5bb Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 10 Mar 2024 07:26:27 +0300 Subject: [PATCH 3422/4092] Add test case for running with high number of loops --- .../Visual/Gameplay/TestSceneStoryboardCommands.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardCommands.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardCommands.cs index 11c07824d3..1392cc3a21 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardCommands.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardCommands.cs @@ -87,6 +87,17 @@ namespace osu.Game.Tests.Visual.Gameplay })); } + [Test] + public void TestLoopManyTimes() + { + AddStep("create sprite", () => Child = createSprite(s => + { + var loop = s.AddLoopingGroup(500, 10000); + loop.AddY(Easing.OutBounce, 0, 60, 100, 240); + loop.AddY(Easing.OutQuint, 80, 120, 240, 100); + })); + } + protected override void Update() { base.Update(); From 99b06102b1bb67cd13c2e582c9796212d31c65d1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 10 Mar 2024 09:01:31 +0300 Subject: [PATCH 3423/4092] Add enough test coverage --- .../Gameplay/TestSceneStoryboardCommands.cs | 115 +++++++++++++++--- 1 file changed, 101 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardCommands.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardCommands.cs index 1392cc3a21..4af3d23463 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardCommands.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardCommands.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; @@ -13,6 +14,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.IO.Stores; +using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mods; @@ -33,7 +35,7 @@ namespace osu.Game.Tests.Visual.Gameplay }; private readonly ManualClock manualClock = new ManualClock { Rate = 1, IsRunning = true }; - private bool forward; + private int clockDirection; private const string lookup_name = "hitcircleoverlay"; private const double clock_limit = 2500; @@ -69,28 +71,75 @@ namespace osu.Game.Tests.Visual.Gameplay }; } - [SetUp] - public void SetUp() + [SetUpSteps] + public void SetUpSteps() { - manualClock.CurrentTime = 0; - forward = true; + AddStep("start clock", () => clockDirection = 1); + AddStep("pause clock", () => clockDirection = 0); + AddStep("set clock = 0", () => manualClock.CurrentTime = 0); } [Test] - public void TestLoop() + public void TestNormalCommandPlayback() { - AddStep("create sprite", () => Child = createSprite(s => + AddStep("create storyboard", () => Child = createStoryboard(s => { - var loop = s.AddLoopingGroup(500, 10); - loop.AddY(Easing.OutBounce, 0, 600, 100, 240); - loop.AddY(Easing.OutQuint, 800, 1200, 240, 100); + s.Commands.AddY(Easing.OutBounce, 500, 900, 100, 240); + s.Commands.AddY(Easing.OutQuint, 1100, 1500, 240, 100); })); + + assert(0, 100); + assert(500, 100); + assert(1000, 240); + assert(1500, 100); + assert(clock_limit, 100); + assert(1500, 100); + assert(1000, 240); + assert(500, 100); + assert(0, 100); + + void assert(double time, double y) + { + AddStep($"set clock = {time}", () => manualClock.CurrentTime = time); + AddAssert($"sprite y = {y} at t = {time}", () => this.ChildrenOfType().Single().Y == y); + } + } + + [Test] + public void TestLoopingCommandsPlayback() + { + AddStep("create storyboard", () => Child = createStoryboard(s => + { + var loop = s.AddLoopingGroup(250, 1); + loop.AddY(Easing.OutBounce, 0, 400, 100, 240); + loop.AddY(Easing.OutQuint, 600, 1000, 240, 100); + })); + + assert(0, 100); + assert(250, 100); + assert(850, 240); + assert(1250, 100); + assert(1850, 240); + assert(2250, 100); + assert(clock_limit, 100); + assert(2250, 100); + assert(1850, 240); + assert(1250, 100); + assert(850, 240); + assert(250, 100); + assert(0, 100); + + void assert(double time, double y) + { + AddStep($"set clock = {time}", () => manualClock.CurrentTime = time); + AddAssert($"sprite y = {y} at t = {time}", () => this.ChildrenOfType().Single().Y == y); + } } [Test] public void TestLoopManyTimes() { - AddStep("create sprite", () => Child = createSprite(s => + AddStep("create storyboard", () => Child = createStoryboard(s => { var loop = s.AddLoopingGroup(500, 10000); loop.AddY(Easing.OutBounce, 0, 60, 100, 240); @@ -98,19 +147,57 @@ namespace osu.Game.Tests.Visual.Gameplay })); } + [Test] + public void TestParameterTemporaryEffect() + { + AddStep("create storyboard", () => Child = createStoryboard(s => + { + s.Commands.AddFlipV(Easing.None, 1000, 1500, true, false); + })); + + AddAssert("sprite not flipped at t = 0", () => !this.ChildrenOfType().Single().FlipV); + + AddStep("set clock = 1250", () => manualClock.CurrentTime = 1250); + AddAssert("sprite flipped at t = 1250", () => this.ChildrenOfType().Single().FlipV); + + AddStep("set clock = 2000", () => manualClock.CurrentTime = 2000); + AddAssert("sprite not flipped at t = 2000", () => !this.ChildrenOfType().Single().FlipV); + + AddStep("resume clock", () => clockDirection = 1); + } + + [Test] + public void TestParameterPermanentEffect() + { + AddStep("create storyboard", () => Child = createStoryboard(s => + { + s.Commands.AddFlipV(Easing.None, 1000, 1000, true, true); + })); + + AddAssert("sprite flipped at t = 0", () => this.ChildrenOfType().Single().FlipV); + + AddStep("set clock = 1250", () => manualClock.CurrentTime = 1250); + AddAssert("sprite flipped at t = 1250", () => this.ChildrenOfType().Single().FlipV); + + AddStep("set clock = 2000", () => manualClock.CurrentTime = 2000); + AddAssert("sprite flipped at t = 2000", () => this.ChildrenOfType().Single().FlipV); + + AddStep("resume clock", () => clockDirection = 1); + } + protected override void Update() { base.Update(); if (manualClock.CurrentTime > clock_limit || manualClock.CurrentTime < 0) - forward = !forward; + clockDirection = -clockDirection; - manualClock.CurrentTime += Time.Elapsed * (forward ? 1 : -1); + manualClock.CurrentTime += Time.Elapsed * clockDirection; timelineText.Text = $"Time: {manualClock.CurrentTime:0}ms"; timelineMarker.X = (float)(manualClock.CurrentTime / clock_limit); } - private DrawableStoryboard createSprite(Action? addCommands = null) + private DrawableStoryboard createStoryboard(Action? addCommands = null) { var layer = storyboard.GetLayer("Background"); From 6ff4b1d7e3d461adf4b67c676a62f39ecfd7ec21 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 10 Mar 2024 15:42:03 +0300 Subject: [PATCH 3424/4092] Don't update SubTreeMasking in OsuPlayfield --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 411a02c5af..80379094ae 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -9,6 +9,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; @@ -35,6 +36,8 @@ namespace osu.Game.Rulesets.Osu.UI private readonly JudgementPooler judgementPooler; + public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) => false; + public SmokeContainer Smoke { get; } public FollowPointRenderer FollowPoints { get; } From 6ecef33fd7a9dcee06349e1075e10ed3bce9e7c7 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 10 Mar 2024 22:45:29 +0300 Subject: [PATCH 3425/4092] Fic incorrect ExtendableCircle gradient --- .../Components/Timeline/TimelineHitObjectBlueprint.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index d4afcc0151..7e6e886ff7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -471,6 +471,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline /// public partial class ExtendableCircle : CompositeDrawable { + public new ColourInfo Colour + { + get => Content.Colour; + set => Content.Colour = value; + } + protected readonly Circle Content; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Content.ReceivePositionalInputAt(screenSpacePos); From 283de215d37aca9605bbf5b0a11dc440455bb348 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 11 Mar 2024 01:01:26 +0300 Subject: [PATCH 3426/4092] Adjust log message and comment --- osu.Desktop/DiscordRichPresence.cs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index b4a7e80d48..2e5db2f5c1 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -189,20 +189,14 @@ namespace osu.Desktop private void onJoin(object sender, JoinMessage args) { game.Window?.Raise(); - Logger.Log($"Received room secret from Discord RPC Client: {args.Secret}", LoggingTarget.Network, LogLevel.Debug); - // Stable and Lazer share the same Discord client ID, meaning they can accept join requests from each other. + Logger.Log($"Received room secret from Discord RPC Client: \"{args.Secret}\"", LoggingTarget.Network, LogLevel.Debug); + + // Stable and lazer share the same Discord client ID, meaning they can accept join requests from each other. // Since they aren't compatible in multi, see if stable's format is being used and log to avoid confusion. - // https://discord.com/channels/188630481301012481/188630652340404224/1214697229063946291 - if (args.Secret[0] != '{') + if (args.Secret[0] != '{' || !tryParseRoomSecret(args.Secret, out long roomId, out string? password)) { - Logger.Log("osu!stable rooms are not compatible with lazer.", LoggingTarget.Network, LogLevel.Important); - return; - } - - if (!tryParseRoomSecret(args.Secret, out long roomId, out string? password)) - { - Logger.Log("Could not join multiplayer room.", LoggingTarget.Network, LogLevel.Important); + Logger.Log("Could not join multiplayer room, invitation is invalid or incompatible.", LoggingTarget.Network, LogLevel.Important); return; } @@ -211,7 +205,7 @@ namespace osu.Desktop { game.PresentMultiplayerMatch(room, password); }); - request.Failure += _ => Logger.Log($"Could not find room {roomId} from Discord RPC Client", LoggingTarget.Network, LogLevel.Important); + request.Failure += _ => Logger.Log($"Could not join multiplayer room, room could not be found (room ID: {roomId}).", LoggingTarget.Network, LogLevel.Important); api.Queue(request); } From 2be6d1f1c60f6e146b056f3fc42c1205cea22826 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Mar 2024 11:26:03 +0800 Subject: [PATCH 3427/4092] Apply NRT to `OsuPlayfield` --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 80379094ae..9b8128a107 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Diagnostics; using System.Linq; @@ -79,7 +77,7 @@ namespace osu.Game.Rulesets.Osu.UI NewResult += onNewResult; } - private IHitPolicy hitPolicy; + private IHitPolicy hitPolicy = null!; public IHitPolicy HitPolicy { @@ -119,12 +117,12 @@ namespace osu.Game.Rulesets.Osu.UI judgementAboveHitObjectLayer.Add(judgement.ProxiedAboveHitObjectsContent); } - [BackgroundDependencyLoader(true)] - private void load(OsuRulesetConfigManager config, IBeatmap beatmap) + [BackgroundDependencyLoader] + private void load(OsuRulesetConfigManager? config, IBeatmap? beatmap) { config?.BindWith(OsuRulesetSetting.PlayfieldBorderStyle, playfieldBorder.PlayfieldBorderStyle); - var osuBeatmap = (OsuBeatmap)beatmap; + var osuBeatmap = (OsuBeatmap?)beatmap; RegisterPool(20, 100); From f3d154a9955697cd25db6a2dd3393daf668acac2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Mar 2024 11:28:15 +0800 Subject: [PATCH 3428/4092] Add inline comment explaining optimisation --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 9b8128a107..63030293ac 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -34,6 +34,8 @@ namespace osu.Game.Rulesets.Osu.UI private readonly JudgementPooler judgementPooler; + // For osu! gameplay, everything is always on screen. + // Skipping masking calculations improves performance in intense beatmaps (ie. https://osu.ppy.sh/beatmapsets/150945#osu/372245) public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) => false; public SmokeContainer Smoke { get; } From e3936930c7df32ccd53f6c8f4ea2dca14dec3934 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Mar 2024 13:11:57 +0800 Subject: [PATCH 3429/4092] Remove local optimisation in `DrawableHitObject` --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index de05219212..28aa2ccb79 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -15,7 +15,6 @@ using osu.Framework.Extensions.ListExtensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Primitives; using osu.Framework.Lists; using osu.Framework.Threading; using osu.Framework.Utils; @@ -632,8 +631,6 @@ namespace osu.Game.Rulesets.Objects.Drawables #endregion - public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) => false; - protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); From d12b11e234f0e9d279e70c71ab595a063ec98e47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Mar 2024 13:14:29 +0800 Subject: [PATCH 3430/4092] Revert "Remove local optimisation in `DrawableHitObject`" This reverts commit e3936930c7df32ccd53f6c8f4ea2dca14dec3934. --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 28aa2ccb79..de05219212 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -15,6 +15,7 @@ using osu.Framework.Extensions.ListExtensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Primitives; using osu.Framework.Lists; using osu.Framework.Threading; using osu.Framework.Utils; @@ -631,6 +632,8 @@ namespace osu.Game.Rulesets.Objects.Drawables #endregion + public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) => false; + protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); From 091425db3091104269531f28218b3b309e50c426 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Mar 2024 14:43:54 +0800 Subject: [PATCH 3431/4092] Fix nullability hint --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 63030293ac..a55a55f760 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.UI public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); - protected override GameplayCursorContainer CreateCursor() => new OsuCursorContainer(); + protected override GameplayCursorContainer? CreateCursor() => new OsuCursorContainer(); private readonly Container judgementAboveHitObjectLayer; From fc05268fc33fc16f214501f3dffe1ec33a301e29 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Mar 2024 14:45:52 +0800 Subject: [PATCH 3432/4092] Apply NRT to `DrawableOsuEditorRuleset` too --- osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs index 14e7b93f3a..68c565af4d 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; @@ -25,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Edit private partial class OsuEditorPlayfield : OsuPlayfield { - protected override GameplayCursorContainer CreateCursor() => null; + protected override GameplayCursorContainer? CreateCursor() => null; public OsuEditorPlayfield() { From e0f1f70b820030452c2f32d1d7dfe6417230f9c7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 11 Mar 2024 15:52:38 +0900 Subject: [PATCH 3433/4092] Adjust NRT to prevent future issues This way, it will yet at us if the setter is ever moved out of the ctor. --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index a55a55f760..4933eb4041 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -79,11 +80,12 @@ namespace osu.Game.Rulesets.Osu.UI NewResult += onNewResult; } - private IHitPolicy hitPolicy = null!; + private IHitPolicy hitPolicy; public IHitPolicy HitPolicy { get => hitPolicy; + [MemberNotNull(nameof(hitPolicy))] set { hitPolicy = value ?? throw new ArgumentNullException(nameof(value)); From 226df7163e1b34eb4a78f02f0038493b673a8dce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Mar 2024 16:55:49 +0800 Subject: [PATCH 3434/4092] Update client ID --- osu.Desktop/DiscordRichPresence.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 2e5db2f5c1..4e3db2db2d 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -25,7 +25,7 @@ namespace osu.Desktop { internal partial class DiscordRichPresence : Component { - private const string client_id = "367827983903490050"; + private const string client_id = "1216669957799018608"; private DiscordRpcClient client = null!; From 169e2e1b4e21c824e586cd1be88b33875e9a2e30 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 11 Mar 2024 09:54:49 +0300 Subject: [PATCH 3435/4092] Change maximum room number to closest powers of two --- osu.Desktop/DiscordRichPresence.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 4e3db2db2d..886038bcf0 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -142,9 +142,8 @@ namespace osu.Desktop Privacy = string.IsNullOrEmpty(room.Settings.Password) ? Party.PrivacySetting.Public : Party.PrivacySetting.Private, ID = room.RoomID.ToString(), // technically lobbies can have infinite users, but Discord needs this to be set to something. - // 1024 just happens to look nice. - // https://discord.com/channels/188630481301012481/188630652340404224/1212967974793642034 - Max = 1024, + // to make party display sensible, assign a powers of two above participants count (8 at minimum). + Max = (int)Math.Max(8, Math.Pow(2, Math.Ceiling(Math.Log2(room.Users.Count)))), Size = room.Users.Count, }; From 8b730acb082379174f9a48b5fd132b184b4e9a81 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 11 Mar 2024 11:18:59 +0300 Subject: [PATCH 3436/4092] Update presence on changes to multiplayer room --- osu.Desktop/DiscordRichPresence.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 886038bcf0..8de1a08e7a 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -8,6 +8,7 @@ using DiscordRPC.Message; using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game; @@ -91,6 +92,8 @@ namespace osu.Desktop activity.BindValueChanged(_ => updateStatus()); privacyMode.BindValueChanged(_ => updateStatus()); + multiplayerClient.RoomUpdated += updateStatus; + client.Initialize(); } @@ -269,6 +272,9 @@ namespace osu.Desktop protected override void Dispose(bool isDisposing) { + if (multiplayerClient.IsNotNull()) + multiplayerClient.RoomUpdated -= updateStatus; + client.Dispose(); base.Dispose(isDisposing); } From e5e7c8f26841654a6819f3c88c40a347184c086b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 11 Mar 2024 14:58:28 +0100 Subject: [PATCH 3437/4092] Wrap beatmap listing filter names too While we're here fixing... Addresses https://github.com/ppy/osu/discussions/15452#discussioncomment-2734237. --- osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs index f79695a123..0b54a921f5 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs @@ -6,10 +6,10 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osuTK; using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Localisation; +using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.BeatmapListing { @@ -45,9 +45,10 @@ namespace osu.Game.Overlays.BeatmapListing { new[] { - new OsuSpriteText + new OsuTextFlowContainer(t => t.Font = OsuFont.GetFont(size: 13)) { - Font = OsuFont.GetFont(size: 13), + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Text = header }, filter = CreateFilter() From f30dfcb728b216659bf80ff513f7c0cdb97d56f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 11 Mar 2024 21:34:10 +0100 Subject: [PATCH 3438/4092] Fix ruleset medals not displaying due to deserialisation failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤦🤦🤦🤦🤦🤦🤦🤦🤦🤦🤦🤦🤦🤦🤦🤦🤦🤦 Reported in https://discord.com/channels/188630481301012481/188630652340404224/1216812697589518386. --- .../Notifications/WebSocket/Events/UserAchievementUnlock.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Notifications/WebSocket/Events/UserAchievementUnlock.cs b/osu.Game/Online/Notifications/WebSocket/Events/UserAchievementUnlock.cs index 6c7c8af4f4..3f803a4fe5 100644 --- a/osu.Game/Online/Notifications/WebSocket/Events/UserAchievementUnlock.cs +++ b/osu.Game/Online/Notifications/WebSocket/Events/UserAchievementUnlock.cs @@ -14,7 +14,7 @@ namespace osu.Game.Online.Notifications.WebSocket.Events public uint AchievementId { get; set; } [JsonProperty("achievement_mode")] - public ushort? AchievementMode { get; set; } + public string? AchievementMode { get; set; } [JsonProperty("cover_url")] public string CoverUrl { get; set; } = string.Empty; From 5580ce31fa6c37c7c65a703cfe820705928453bd Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 11 Mar 2024 18:15:18 -0400 Subject: [PATCH 3439/4092] Log Discord RPC updates --- osu.Desktop/DiscordRichPresence.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 8de1a08e7a..6c8bd26d3d 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -114,6 +114,8 @@ namespace osu.Desktop return; } + Logger.Log("Updating Discord RPC", LoggingTarget.Network, LogLevel.Debug); + if (activity.Value != null) { bool hideIdentifiableInformation = privacyMode.Value == DiscordRichPresenceMode.Limited || status.Value == UserStatus.DoNotDisturb; From e4e7dd14f30ee5c2d28ccc967a0a891fbbd076b7 Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 11 Mar 2024 18:16:13 -0400 Subject: [PATCH 3440/4092] Revert "Update presence on changes to multiplayer room" This reverts commit 8b730acb082379174f9a48b5fd132b184b4e9a81. --- osu.Desktop/DiscordRichPresence.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 6c8bd26d3d..ca26cab0fd 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -8,7 +8,6 @@ using DiscordRPC.Message; using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game; @@ -92,8 +91,6 @@ namespace osu.Desktop activity.BindValueChanged(_ => updateStatus()); privacyMode.BindValueChanged(_ => updateStatus()); - multiplayerClient.RoomUpdated += updateStatus; - client.Initialize(); } @@ -274,9 +271,6 @@ namespace osu.Desktop protected override void Dispose(bool isDisposing) { - if (multiplayerClient.IsNotNull()) - multiplayerClient.RoomUpdated -= updateStatus; - client.Dispose(); base.Dispose(isDisposing); } From 83052fe1d98174a832e4ed7e9c52b04c5eb7c6c3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Mar 2024 15:41:50 +0800 Subject: [PATCH 3441/4092] Downgrade realm to work around crashes on latest release See https://github.com/ppy/osu/issues/27577. --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 55bd68dea0..0b70515abf 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + From 961058f5c8ca538b1a96a82651b30e49b92c90c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 12 Mar 2024 09:05:28 +0100 Subject: [PATCH 3442/4092] Add failing test case --- .../Archives/skin-with-subfolder-zip-entries.osk | Bin 0 -> 894 bytes osu.Game.Tests/Skins/IO/ImportSkinTest.cs | 10 ++++++++++ 2 files changed, 10 insertions(+) create mode 100644 osu.Game.Tests/Resources/Archives/skin-with-subfolder-zip-entries.osk diff --git a/osu.Game.Tests/Resources/Archives/skin-with-subfolder-zip-entries.osk b/osu.Game.Tests/Resources/Archives/skin-with-subfolder-zip-entries.osk new file mode 100644 index 0000000000000000000000000000000000000000..013bca3801866f95f7139fbed30f96fb2a6b2639 GIT binary patch literal 894 zcmWIWW@Zs#0D)fToCq)jN+aJ8o04|V8AWw2I zfK1XY?5o`hL%~fhOr?=4GDkw_SJ0K%k|1ep1|jmnGM2G-Y{9ME;m|BpsAa zdiX}EdTqz%zyg(Z44p4_Fr3YD|D0*^dB@$=T(75H%;>qrZuxLUl1|$^qZKpCQXYC3 z#yQ^GeEcET*{AILmN4?)b)Ee8k;Bi_RSP&LwpzKFwp97OxN&0g#UpQ@>CDl1clVL& zm59~7Cw(RLCr>>h`BUbIZ}-+&SvgO1Zgt--I_0?Z*!?H>cjm9{y*o)W$#hTc2C>C+ z_D=kJe3g2DHzSih1FjfU0eTMvKmi5@6ekwJnX*M{T1U&$?CAfWZSSn=z9F%;pB5t+Rm`J%J$Gz#u;>J*p6w-*6=rWE-9W ZZGa^l;!I{`18QMl0m2o)Fg*YcR{%bq?(hHr literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index 606a5afac2..62e7a80435 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -173,6 +173,16 @@ namespace osu.Game.Tests.Skins.IO assertCorrectMetadata(import1, "name 1 [my custom skin 1]", "author 1", 1.0m, osu); }); + [Test] + public Task TestImportWithSubfolder() => runSkinTest(async osu => + { + const string filename = "Archives/skin-with-subfolder-zip-entries.osk"; + var import = await loadSkinIntoOsu(osu, new ImportTask(TestResources.OpenResource(filename), filename)); + + assertCorrectMetadata(import, $"Totally fully features skin [Real Skin with Real Features] [{filename[..^4]}]", "Unknown", 2.7m, osu); + Assert.That(import.PerformRead(r => r.Files.Count), Is.EqualTo(3)); + }); + #endregion #region Cases where imports should be uniquely imported From 83e47b3c4c1c3a59c6ded7249797daa436d46572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 12 Mar 2024 09:06:24 +0100 Subject: [PATCH 3443/4092] Fix exports containing zero byte files after import from specific ZIP archives Closes https://github.com/ppy/osu/issues/27540. As it turns out, some ZIP archivers (like 7zip) will decide to add fake entries for directories, and some (like windows zipfolders) won't. The "directory" entries aren't really properly supported using any actual data or attributes, they're detected by sharpcompress basically by heuristics: https://github.com/adamhathcock/sharpcompress/blob/ab5535eba365ec8fae58f92d53763ddf2dbf45af/src/SharpCompress/Common/Zip/Headers/ZipFileEntry.cs#L19-L31 When importing into realm we have thus far presumed that these directory entries will not be a thing. Having them be a thing breaks multiple things, like: - When importing from ZIPs with separate directory entries, a separate `RealmFile` is created for a directory entry even though it doesn't represent a real file - As a result, when re-exporting a model with files imported from such an archive, a zero-byte file would be created because to the database it looks like it was originally a zero-byte file. If you want to have fun, google "zip empty directories". You'll see a whole gamut of languages, libraries, and developers stepping on this rake. Yet another episode of underspecced mistakes from decades ago that were somebody's "good idea" but continue to wreak havoc forevermore because now there are two competing conventions you can't just pick one. --- osu.Game/IO/Archives/ZipArchiveReader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/IO/Archives/ZipArchiveReader.cs b/osu.Game/IO/Archives/ZipArchiveReader.cs index 7d7ce858dd..cc5c65d184 100644 --- a/osu.Game/IO/Archives/ZipArchiveReader.cs +++ b/osu.Game/IO/Archives/ZipArchiveReader.cs @@ -46,7 +46,7 @@ namespace osu.Game.IO.Archives archiveStream.Dispose(); } - public override IEnumerable Filenames => archive.Entries.Select(e => e.Key).ExcludeSystemFileNames(); + public override IEnumerable Filenames => archive.Entries.Where(e => !e.IsDirectory).Select(e => e.Key).ExcludeSystemFileNames(); private class MemoryOwnerMemoryStream : Stream { From 88ec0cdbc704e041bd6b77ff24d1a4206d888896 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Mar 2024 17:35:00 +0800 Subject: [PATCH 3444/4092] Fix seek ending too early in sample playback test --- .../TestSceneGameplaySamplePlayback.cs | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs index 057197e819..ad3fe7cb7e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs @@ -18,6 +18,8 @@ namespace osu.Game.Tests.Visual.Gameplay { protected override bool AllowBackwardsSeeks => true; + private bool seek; + [Test] public void TestAllSamplesStopDuringSeek() { @@ -42,7 +44,7 @@ namespace osu.Game.Tests.Visual.Gameplay if (!samples.Any(s => s.Playing)) return false; - Player.ChildrenOfType().First().Seek(40000); + seek = true; return true; }); @@ -55,10 +57,27 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("sample playback still disabled", () => sampleDisabler.SamplePlaybackDisabled.Value); + AddStep("stop seeking", () => seek = false); + AddUntilStep("seek finished, sample playback enabled", () => !sampleDisabler.SamplePlaybackDisabled.Value); AddUntilStep("any sample is playing", () => Player.ChildrenOfType().Any(s => s.IsPlaying)); } + protected override void Update() + { + base.Update(); + + if (seek) + { + // Frame stable playback is too fast to catch up these days. + // + // We want to keep seeking while asserting various test conditions, so + // continue to seek until we unset the flag. + var gameplayClockContainer = Player.ChildrenOfType().First(); + gameplayClockContainer.Seek(gameplayClockContainer.CurrentTime > 30000 ? 0 : 60000); + } + } + private IEnumerable allSounds => Player.ChildrenOfType(); private IEnumerable allLoopingSounds => allSounds.Where(sound => sound.Looping); From a4a433b92add36153d8f9b06ffa757480ff3b5ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Mar 2024 20:14:50 +0800 Subject: [PATCH 3445/4092] Remember login by default Kinda what is expected from a user's perspective --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index a71460ded7..f4a4c553d8 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -75,7 +75,7 @@ namespace osu.Game.Configuration #pragma warning restore CS0618 // Type or member is obsolete SetDefault(OsuSetting.AutomaticallyDownloadMissingBeatmaps, false); - SetDefault(OsuSetting.SavePassword, false).ValueChanged += enabled => + SetDefault(OsuSetting.SavePassword, true).ValueChanged += enabled => { if (enabled.NewValue) SetValue(OsuSetting.SaveUsername, true); From e2e99fc5cc392b57276a1c79f3b68c2831c64ac0 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 12 Mar 2024 21:07:21 -0700 Subject: [PATCH 3446/4092] Rearrange rankings overlay tabs to match web --- osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs | 2 +- osu.Game/Overlays/Rankings/RankingsScope.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs index cf132ed4da..0eaa6ce827 100644 --- a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs +++ b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs @@ -52,9 +52,9 @@ namespace osu.Game.Overlays.Rankings switch (scope) { case RankingsScope.Performance: - case RankingsScope.Spotlights: case RankingsScope.Score: case RankingsScope.Country: + case RankingsScope.Spotlights: return true; default: diff --git a/osu.Game/Overlays/Rankings/RankingsScope.cs b/osu.Game/Overlays/Rankings/RankingsScope.cs index 356a861764..0740c17e8c 100644 --- a/osu.Game/Overlays/Rankings/RankingsScope.cs +++ b/osu.Game/Overlays/Rankings/RankingsScope.cs @@ -11,15 +11,15 @@ namespace osu.Game.Overlays.Rankings [LocalisableDescription(typeof(RankingsStrings), nameof(RankingsStrings.TypePerformance))] Performance, - [LocalisableDescription(typeof(RankingsStrings), nameof(RankingsStrings.TypeCharts))] - Spotlights, - [LocalisableDescription(typeof(RankingsStrings), nameof(RankingsStrings.TypeScore))] Score, [LocalisableDescription(typeof(RankingsStrings), nameof(RankingsStrings.TypeCountry))] Country, + [LocalisableDescription(typeof(RankingsStrings), nameof(RankingsStrings.TypeCharts))] + Spotlights, + [LocalisableDescription(typeof(RankingsStrings), nameof(RankingsStrings.TypeKudosu))] Kudosu, } From 20c760835ab81c4a90c46656f69d8b59b0903fc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 13 Mar 2024 13:55:49 +0100 Subject: [PATCH 3447/4092] Fix audio in video check crashing on unexpected failures --- osu.Game/Rulesets/Edit/Checks/CheckAudioInVideo.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckAudioInVideo.cs b/osu.Game/Rulesets/Edit/Checks/CheckAudioInVideo.cs index f712a7867d..38976dd4b5 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckAudioInVideo.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckAudioInVideo.cs @@ -1,8 +1,10 @@ // 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.Collections.Generic; using System.IO; +using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.IO.FileAbstraction; using osu.Game.Rulesets.Edit.Checks.Components; @@ -75,6 +77,11 @@ namespace osu.Game.Rulesets.Edit.Checks { issue = new IssueTemplateFileError(this).Create(filename, "Unsupported format"); } + catch (Exception ex) + { + issue = new IssueTemplateFileError(this).Create(filename, "Internal failure - see logs for more info"); + Logger.Log($"Failed when running {nameof(CheckAudioInVideo)}: {ex}"); + } yield return issue; } From d32f19b546e63e844acaa3c4c3911225cb757ace Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 13 Mar 2024 15:18:20 +0100 Subject: [PATCH 3448/4092] Fix first word bold not applying correctly after first language change Closes https://github.com/ppy/osu/issues/27549. I'm not entirely sure why the old solution failed exactly, but also think it's unimportant because I think past me was an idiot and was playing stupid games with the juggling of indices between two callbacks with no ordering guarantees and expecting not to win stupid prizes. I'm not sure this requires any follow-up reconsiderations of that entire text flow API, but if opinions differ, I'll re-examine. --- osu.Game/Overlays/Mods/ModSelectColumn.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectColumn.cs b/osu.Game/Overlays/Mods/ModSelectColumn.cs index b2c5a054e1..61b29ef65b 100644 --- a/osu.Game/Overlays/Mods/ModSelectColumn.cs +++ b/osu.Game/Overlays/Mods/ModSelectColumn.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 System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -8,9 +9,11 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; +using System.Linq; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; @@ -181,17 +184,15 @@ namespace osu.Game.Overlays.Mods { headerText.Clear(); - int wordIndex = 0; + ITextPart part = headerText.AddText(text); + part.DrawablePartsRecreated += applySemiBoldToFirstWord; + applySemiBoldToFirstWord(part.Drawables); - ITextPart part = headerText.AddText(text, t => + void applySemiBoldToFirstWord(IEnumerable d) { - if (wordIndex == 0) - t.Font = t.Font.With(weight: FontWeight.SemiBold); - wordIndex += 1; - }); - - // Reset the index so that if the parts are refreshed (e.g. through changes in localisation) the correct word is re-emboldened. - part.DrawablePartsRecreated += _ => wordIndex = 0; + if (d.FirstOrDefault() is OsuSpriteText firstWord) + firstWord.Font = firstWord.Font.With(weight: FontWeight.SemiBold); + } } [BackgroundDependencyLoader] From 789a9f4dfa071dacc9157914e93792231d568923 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 14 Mar 2024 11:01:57 +0900 Subject: [PATCH 3449/4092] Initial redesign following flyte's design --- osu.Game/Screens/Play/DelayedResumeOverlay.cs | 125 +++++++++++------- 1 file changed, 79 insertions(+), 46 deletions(-) diff --git a/osu.Game/Screens/Play/DelayedResumeOverlay.cs b/osu.Game/Screens/Play/DelayedResumeOverlay.cs index ba49810b2b..e9dd26a06b 100644 --- a/osu.Game/Screens/Play/DelayedResumeOverlay.cs +++ b/osu.Game/Screens/Play/DelayedResumeOverlay.cs @@ -3,10 +3,12 @@ using System; 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.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Framework.Threading; using osu.Game.Graphics; @@ -21,7 +23,12 @@ namespace osu.Game.Screens.Play /// public partial class DelayedResumeOverlay : ResumeOverlay { - private const double countdown_time = 800; + private const float outer_size = 200; + private const float inner_size = 150; + private const float progress_stroke_width = 7; + private const float progress_size = inner_size + progress_stroke_width / 2f; + + private const double countdown_time = 3000; protected override LocalisableString Message => string.Empty; @@ -31,9 +38,15 @@ namespace osu.Game.Screens.Play private ScheduledDelegate? scheduledResume; private int countdownCount = 3; private double countdownStartTime; + private bool countdownComplete; - private Drawable content = null!; - private SpriteText countdown = null!; + private Drawable outerContent = null!; + private Container innerContent = null!; + + private Container countdownComponents = null!; + private Drawable countdownBackground = null!; + private SpriteText countdownText = null!; + private CircularProgress countdownProgress = null!; public DelayedResumeOverlay() { @@ -44,44 +57,48 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load() { - Add(content = new CircularContainer + Add(outerContent = new Circle { Anchor = Anchor.Centre, Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Masking = true, - BorderColour = colours.Yellow, - BorderThickness = 2, - Children = new Drawable[] + Size = new Vector2(outer_size), + Colour = Color4.Black.Opacity(0.25f) + }); + + Add(innerContent = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Children = new[] { - new Box - { - Size = new Vector2(250, 40), - Colour = Color4.Black, - Alpha = 0.8f - }, - new Container + countdownBackground = new Circle { Anchor = Anchor.Centre, Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, + Size = new Vector2(inner_size), + Colour = Color4.Black.Opacity(0.25f) + }, + countdownComponents = new Container + { + RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - new FillFlowContainer + countdownProgress = new CircularProgress { Anchor = Anchor.Centre, Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Spacing = new Vector2(5), - Colour = colours.Yellow, - Child = countdown = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - UseFullGlyphHeight = false, - AlwaysPresent = true, - Font = OsuFont.Numeric.With(size: 20, fixedWidth: true) - }, + Size = new Vector2(progress_size), + InnerRadius = progress_stroke_width / progress_size, + RoundedCaps = true + }, + countdownText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + UseFullGlyphHeight = false, + AlwaysPresent = true, + Font = OsuFont.Torus.With(size: 70, weight: FontWeight.Light) } } } @@ -93,14 +110,26 @@ namespace osu.Game.Screens.Play { this.FadeIn(); - content.FadeInFromZero(150, Easing.OutQuint); - content.ScaleTo(new Vector2(1.5f, 1)).Then().ScaleTo(1, 150, Easing.OutElasticQuarter); + // The transition effects. + outerContent.FadeIn().ScaleTo(Vector2.Zero).Then().ScaleTo(Vector2.One, 200, Easing.OutQuint); + innerContent.FadeIn().ScaleTo(Vector2.Zero).Then().ScaleTo(Vector2.One, 400, Easing.OutElasticHalf); + countdownComponents.FadeOut().Then().Delay(50).FadeTo(1, 100); + // Reset states for various components. + countdownBackground.FadeIn(); + countdownText.FadeIn(); + countdownProgress.FadeIn().ScaleTo(1); + + countdownComplete = false; countdownCount = 3; countdownStartTime = Time.Current; scheduledResume?.Cancel(); - scheduledResume = Scheduler.AddDelayed(Resume, countdown_time); + scheduledResume = Scheduler.AddDelayed(() => + { + countdownComplete = true; + Resume(); + }, countdown_time); } protected override void Update() @@ -114,20 +143,14 @@ namespace osu.Game.Screens.Play double amountTimePassed = Math.Min(countdown_time, Time.Current - countdownStartTime) / countdown_time; int newCount = 3 - (int)Math.Floor(amountTimePassed * 3); - if (newCount > 0) - { - countdown.Alpha = 1; - countdown.Text = newCount.ToString(); - } - else - countdown.Alpha = 0; + countdownProgress.Current.Value = amountTimePassed; + countdownText.Text = Math.Max(1, newCount).ToString(); + countdownProgress.InnerRadius = progress_stroke_width / progress_size / countdownProgress.Scale.X; - if (newCount != countdownCount) + if (countdownCount != newCount && newCount > 0) { - if (newCount == 0) - content.ScaleTo(new Vector2(1.5f, 1), 150, Easing.OutQuint); - else - content.ScaleTo(new Vector2(1.05f, 1), 50, Easing.OutQuint).Then().ScaleTo(1, 50, Easing.Out); + countdownText.ScaleTo(0.25f).Then().ScaleTo(1, 200, Easing.OutQuint); + outerContent.Delay(25).Then().ScaleTo(1.05f, 100).Then().ScaleTo(1f, 200, Easing.Out); } countdownCount = newCount; @@ -135,9 +158,19 @@ namespace osu.Game.Screens.Play protected override void PopOut() { - this.Delay(150).FadeOut(); + this.Delay(300).FadeOut(); - content.FadeOut(150, Easing.OutQuint); + outerContent.FadeOut(); + countdownBackground.FadeOut(); + countdownText.FadeOut(); + + if (countdownComplete) + { + countdownProgress.ScaleTo(2f, 300, Easing.OutQuint); + countdownProgress.Delay(200).FadeOut(100, Easing.Out); + } + else + countdownProgress.FadeOut(); scheduledResume?.Cancel(); } From b431bb11764c4c4088eea49f4cd7f5e7611e4aa9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 14 Mar 2024 12:24:12 +0900 Subject: [PATCH 3450/4092] Resolve post-merge issues --- osu.Game/Screens/Play/DelayedResumeOverlay.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/DelayedResumeOverlay.cs b/osu.Game/Screens/Play/DelayedResumeOverlay.cs index e9dd26a06b..196ca24358 100644 --- a/osu.Game/Screens/Play/DelayedResumeOverlay.cs +++ b/osu.Game/Screens/Play/DelayedResumeOverlay.cs @@ -32,9 +32,6 @@ namespace osu.Game.Screens.Play protected override LocalisableString Message => string.Empty; - [Resolved] - private OsuColour colours { get; set; } = null!; - private ScheduledDelegate? scheduledResume; private int countdownCount = 3; private double countdownStartTime; @@ -143,7 +140,7 @@ namespace osu.Game.Screens.Play double amountTimePassed = Math.Min(countdown_time, Time.Current - countdownStartTime) / countdown_time; int newCount = 3 - (int)Math.Floor(amountTimePassed * 3); - countdownProgress.Current.Value = amountTimePassed; + countdownProgress.Progress = amountTimePassed; countdownText.Text = Math.Max(1, newCount).ToString(); countdownProgress.InnerRadius = progress_stroke_width / progress_size / countdownProgress.Scale.X; From b309aad895b001e042ec85f2c6103457fdd801a1 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 8 Mar 2024 13:13:08 -0800 Subject: [PATCH 3451/4092] Fix noticeable masking artifact of beatmap cards when already downloaded MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Had a similar fix before seeing https://github.com/ppy/osu/pull/20743#discussion_r994955470, but using that diff instead so co-authoring. Co-Authored-By: Bartłomiej Dach --- .../Drawables/Cards/CollapsibleButtonContainer.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainer.cs b/osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainer.cs index fe2ee8c7cc..32df1755a7 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainer.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainer.cs @@ -77,7 +77,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards downloadTracker, background = new Container { - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.Y, Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, Child = new Box @@ -165,9 +165,11 @@ namespace osu.Game.Beatmaps.Drawables.Cards private void updateState() { - float targetWidth = Width - (ShowDetails.Value ? ButtonsExpandedWidth : ButtonsCollapsedWidth); + float buttonAreaWidth = ShowDetails.Value ? ButtonsExpandedWidth : ButtonsCollapsedWidth; + float mainAreaWidth = Width - buttonAreaWidth; - mainArea.ResizeWidthTo(targetWidth, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + mainArea.ResizeWidthTo(mainAreaWidth, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + background.ResizeWidthTo(buttonAreaWidth + BeatmapCard.CORNER_RADIUS, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); background.FadeColour(downloadTracker.State.Value == DownloadState.LocallyAvailable ? colours.Lime0 : colourProvider.Background3, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); buttons.FadeTo(ShowDetails.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); From b4cee12db96644a8364ed1525aea51f526c1dcb6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 14 Mar 2024 09:21:29 +0300 Subject: [PATCH 3452/4092] Use defined colours for counter background --- osu.Game/Screens/Play/DelayedResumeOverlay.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/DelayedResumeOverlay.cs b/osu.Game/Screens/Play/DelayedResumeOverlay.cs index 196ca24358..c6cfeca142 100644 --- a/osu.Game/Screens/Play/DelayedResumeOverlay.cs +++ b/osu.Game/Screens/Play/DelayedResumeOverlay.cs @@ -3,7 +3,6 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -13,8 +12,8 @@ using osu.Framework.Localisation; using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; using osuTK; -using osuTK.Graphics; namespace osu.Game.Screens.Play { @@ -54,12 +53,15 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load() { + // todo: this shouldn't define its own colour provider, but nothing in Player screen does, so let's do that for now. + var colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + Add(outerContent = new Circle { Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(outer_size), - Colour = Color4.Black.Opacity(0.25f) + Colour = colourProvider.Background6, }); Add(innerContent = new Container @@ -74,7 +76,7 @@ namespace osu.Game.Screens.Play Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(inner_size), - Colour = Color4.Black.Opacity(0.25f) + Colour = colourProvider.Background4, }, countdownComponents = new Container { From f8a841e907e9b2deb3ca08c425a0e665a5c07775 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 14 Mar 2024 00:17:22 -0700 Subject: [PATCH 3453/4092] Add comment explaining why width is limited to the button area Same comment as https://github.com/ppy/osu/blob/a47ccb8edd2392258b6b7e176b222a9ecd511fc0/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs#L91-L92. --- osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainer.cs b/osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainer.cs index 32df1755a7..a29724032e 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainer.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainer.cs @@ -169,6 +169,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards float mainAreaWidth = Width - buttonAreaWidth; mainArea.ResizeWidthTo(mainAreaWidth, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + + // By limiting the width we avoid this box showing up as an outline around the drawables that are on top of it. background.ResizeWidthTo(buttonAreaWidth + BeatmapCard.CORNER_RADIUS, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); background.FadeColour(downloadTracker.State.Value == DownloadState.LocallyAvailable ? colours.Lime0 : colourProvider.Background3, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); From 42bd558d7c159857ca7356b8485d710802685fc6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Mar 2024 22:41:29 +0800 Subject: [PATCH 3454/4092] Only update text when necessary (reducing unnecessary string allocadtions) --- osu.Game/Screens/Play/DelayedResumeOverlay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/DelayedResumeOverlay.cs b/osu.Game/Screens/Play/DelayedResumeOverlay.cs index c6cfeca142..468e67901d 100644 --- a/osu.Game/Screens/Play/DelayedResumeOverlay.cs +++ b/osu.Game/Screens/Play/DelayedResumeOverlay.cs @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Play protected override LocalisableString Message => string.Empty; private ScheduledDelegate? scheduledResume; - private int countdownCount = 3; + private int? countdownCount; private double countdownStartTime; private bool countdownComplete; @@ -120,7 +120,7 @@ namespace osu.Game.Screens.Play countdownProgress.FadeIn().ScaleTo(1); countdownComplete = false; - countdownCount = 3; + countdownCount = null; countdownStartTime = Time.Current; scheduledResume?.Cancel(); @@ -143,11 +143,11 @@ namespace osu.Game.Screens.Play int newCount = 3 - (int)Math.Floor(amountTimePassed * 3); countdownProgress.Progress = amountTimePassed; - countdownText.Text = Math.Max(1, newCount).ToString(); countdownProgress.InnerRadius = progress_stroke_width / progress_size / countdownProgress.Scale.X; if (countdownCount != newCount && newCount > 0) { + countdownText.Text = Math.Max(1, newCount).ToString(); countdownText.ScaleTo(0.25f).Then().ScaleTo(1, 200, Easing.OutQuint); outerContent.Delay(25).Then().ScaleTo(1.05f, 100).Then().ScaleTo(1f, 200, Easing.Out); } From d7769ec3e25cc985fea44e6fb0d02ab668e2dd33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Mar 2024 22:38:59 +0800 Subject: [PATCH 3455/4092] Adjust animation --- osu.Game/Screens/Play/DelayedResumeOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/DelayedResumeOverlay.cs b/osu.Game/Screens/Play/DelayedResumeOverlay.cs index 468e67901d..7c34050bbf 100644 --- a/osu.Game/Screens/Play/DelayedResumeOverlay.cs +++ b/osu.Game/Screens/Play/DelayedResumeOverlay.cs @@ -112,7 +112,7 @@ namespace osu.Game.Screens.Play // The transition effects. outerContent.FadeIn().ScaleTo(Vector2.Zero).Then().ScaleTo(Vector2.One, 200, Easing.OutQuint); innerContent.FadeIn().ScaleTo(Vector2.Zero).Then().ScaleTo(Vector2.One, 400, Easing.OutElasticHalf); - countdownComponents.FadeOut().Then().Delay(50).FadeTo(1, 100); + countdownComponents.FadeOut().Delay(50).FadeTo(1, 100); // Reset states for various components. countdownBackground.FadeIn(); @@ -166,7 +166,7 @@ namespace osu.Game.Screens.Play if (countdownComplete) { countdownProgress.ScaleTo(2f, 300, Easing.OutQuint); - countdownProgress.Delay(200).FadeOut(100, Easing.Out); + countdownProgress.FadeOut(100, Easing.Out); } else countdownProgress.FadeOut(); From 888245b44fa13e46a7fb23b4a801f3fd36d38fee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Mar 2024 22:44:26 +0800 Subject: [PATCH 3456/4092] Reorder methods --- osu.Game/Screens/Play/DelayedResumeOverlay.cs | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/Play/DelayedResumeOverlay.cs b/osu.Game/Screens/Play/DelayedResumeOverlay.cs index 7c34050bbf..4b703ec3cf 100644 --- a/osu.Game/Screens/Play/DelayedResumeOverlay.cs +++ b/osu.Game/Screens/Play/DelayedResumeOverlay.cs @@ -131,6 +131,25 @@ namespace osu.Game.Screens.Play }, countdown_time); } + protected override void PopOut() + { + this.Delay(300).FadeOut(); + + outerContent.FadeOut(); + countdownBackground.FadeOut(); + countdownText.FadeOut(); + + if (countdownComplete) + { + countdownProgress.ScaleTo(2f, 300, Easing.OutQuint); + countdownProgress.FadeOut(100, Easing.Out); + } + else + countdownProgress.FadeOut(); + + scheduledResume?.Cancel(); + } + protected override void Update() { base.Update(); @@ -154,24 +173,5 @@ namespace osu.Game.Screens.Play countdownCount = newCount; } - - protected override void PopOut() - { - this.Delay(300).FadeOut(); - - outerContent.FadeOut(); - countdownBackground.FadeOut(); - countdownText.FadeOut(); - - if (countdownComplete) - { - countdownProgress.ScaleTo(2f, 300, Easing.OutQuint); - countdownProgress.FadeOut(100, Easing.Out); - } - else - countdownProgress.FadeOut(); - - scheduledResume?.Cancel(); - } } } From 2845303a74986d4310d7fb4a77b56a1bd980a8d3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Mar 2024 22:44:57 +0800 Subject: [PATCH 3457/4092] Fix non-matching scale outwards animation --- osu.Game/Screens/Play/DelayedResumeOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/DelayedResumeOverlay.cs b/osu.Game/Screens/Play/DelayedResumeOverlay.cs index 4b703ec3cf..c3c98510e3 100644 --- a/osu.Game/Screens/Play/DelayedResumeOverlay.cs +++ b/osu.Game/Screens/Play/DelayedResumeOverlay.cs @@ -142,7 +142,7 @@ namespace osu.Game.Screens.Play if (countdownComplete) { countdownProgress.ScaleTo(2f, 300, Easing.OutQuint); - countdownProgress.FadeOut(100, Easing.Out); + countdownProgress.FadeOut(300, Easing.OutQuint); } else countdownProgress.FadeOut(); From 23975d4dd162b503ccb0e79f0c0d570c5606f901 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Mar 2024 22:49:53 +0800 Subject: [PATCH 3458/4092] Add flash and reduce overall time for countdown to 2 seconds --- osu.Game/Screens/Play/DelayedResumeOverlay.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/DelayedResumeOverlay.cs b/osu.Game/Screens/Play/DelayedResumeOverlay.cs index c3c98510e3..fd1ce5d829 100644 --- a/osu.Game/Screens/Play/DelayedResumeOverlay.cs +++ b/osu.Game/Screens/Play/DelayedResumeOverlay.cs @@ -22,12 +22,15 @@ namespace osu.Game.Screens.Play /// public partial class DelayedResumeOverlay : ResumeOverlay { + // todo: this shouldn't define its own colour provider, but nothing in Player screen does, so let's do that for now. + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + private const float outer_size = 200; private const float inner_size = 150; private const float progress_stroke_width = 7; private const float progress_size = inner_size + progress_stroke_width / 2f; - private const double countdown_time = 3000; + private const double countdown_time = 2000; protected override LocalisableString Message => string.Empty; @@ -53,9 +56,6 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load() { - // todo: this shouldn't define its own colour provider, but nothing in Player screen does, so let's do that for now. - var colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); - Add(outerContent = new Circle { Anchor = Anchor.Centre, @@ -169,6 +169,8 @@ namespace osu.Game.Screens.Play countdownText.Text = Math.Max(1, newCount).ToString(); countdownText.ScaleTo(0.25f).Then().ScaleTo(1, 200, Easing.OutQuint); outerContent.Delay(25).Then().ScaleTo(1.05f, 100).Then().ScaleTo(1f, 200, Easing.Out); + + countdownBackground.FlashColour(colourProvider.Background3, 400, Easing.Out); } countdownCount = newCount; From 1aa695add9db68d3aef453f476486209a3aeb5f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 15 Mar 2024 09:14:17 +0100 Subject: [PATCH 3459/4092] Implement alternate design of player loader disclaimers --- .../Screens/Play/PlayerLoaderDisclaimer.cs | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 osu.Game/Screens/Play/PlayerLoaderDisclaimer.cs diff --git a/osu.Game/Screens/Play/PlayerLoaderDisclaimer.cs b/osu.Game/Screens/Play/PlayerLoaderDisclaimer.cs new file mode 100644 index 0000000000..c8112055c8 --- /dev/null +++ b/osu.Game/Screens/Play/PlayerLoaderDisclaimer.cs @@ -0,0 +1,67 @@ +// 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.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Screens.Play +{ + public partial class PlayerLoaderDisclaimer : CompositeDrawable + { + private readonly LocalisableString title; + private readonly LocalisableString content; + + public PlayerLoaderDisclaimer(LocalisableString title, LocalisableString content) + { + this.title = title; + this.content = content; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Width = SettingsToolboxGroup.CONTAINER_WIDTH; + AutoSizeAxes = Axes.Y; + + InternalChildren = new Drawable[] + { + new Circle + { + Width = 7, + Height = 15, + Margin = new MarginPadding { Top = 2 }, + Colour = colours.Orange1, + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Left = 12 }, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 2), + Children = new[] + { + new TextFlowContainer(t => t.Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 17)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Text = title, + }, + new TextFlowContainer(t => t.Font = OsuFont.GetFont(size: 16)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Text = content, + } + } + } + }; + } + } +} From 42ae18976ff4287567f1b69c27d27215353573a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 15 Mar 2024 09:27:33 +0100 Subject: [PATCH 3460/4092] Replace existing epilepsy warning with inline display --- .../Visual/Gameplay/TestScenePlayerLoader.cs | 35 +----- osu.Game/Screens/Play/EpilepsyWarning.cs | 102 ------------------ osu.Game/Screens/Play/PlayerLoader.cs | 67 +++++------- 3 files changed, 26 insertions(+), 178 deletions(-) delete mode 100644 osu.Game/Screens/Play/EpilepsyWarning.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index c6827d4197..dac8545729 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -334,13 +334,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for current", () => loader.IsCurrentScreen()); - AddAssert($"epilepsy warning {(warning ? "present" : "absent")}", () => (getWarning() != null) == warning); - - if (warning) - { - AddUntilStep("sound volume decreased", () => Beatmap.Value.Track.AggregateVolume.Value == 0.25); - AddUntilStep("sound volume restored", () => Beatmap.Value.Track.AggregateVolume.Value == 1); - } + AddAssert($"epilepsy warning {(warning ? "present" : "absent")}", () => this.ChildrenOfType().Count(), () => Is.EqualTo(warning ? 1 : 0)); restoreVolumes(); } @@ -357,30 +351,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for current", () => loader.IsCurrentScreen()); - AddUntilStep("epilepsy warning absent", () => getWarning() == null); - - restoreVolumes(); - } - - [Test] - public void TestEpilepsyWarningEarlyExit() - { - saveVolumes(); - setFullVolume(); - - AddStep("enable storyboards", () => config.SetValue(OsuSetting.ShowStoryboard, true)); - AddStep("set epilepsy warning", () => epilepsyWarning = true); - AddStep("load dummy beatmap", () => resetPlayer(false)); - - AddUntilStep("wait for current", () => loader.IsCurrentScreen()); - - AddUntilStep("wait for epilepsy warning", () => getWarning().Alpha > 0); - AddUntilStep("warning is shown", () => getWarning().State.Value == Visibility.Visible); - - AddStep("exit early", () => loader.Exit()); - - AddUntilStep("warning is hidden", () => getWarning().State.Value == Visibility.Hidden); - AddUntilStep("sound volume restored", () => Beatmap.Value.Track.AggregateVolume.Value == 1); + AddUntilStep("epilepsy warning absent", () => this.ChildrenOfType().Single().Alpha, () => Is.Zero); restoreVolumes(); } @@ -479,8 +450,6 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("click notification", () => notification.TriggerClick()); } - private EpilepsyWarning getWarning() => loader.ChildrenOfType().SingleOrDefault(w => w.IsAlive); - private partial class TestPlayerLoader : PlayerLoader { public new VisualSettings VisualSettings => base.VisualSettings; diff --git a/osu.Game/Screens/Play/EpilepsyWarning.cs b/osu.Game/Screens/Play/EpilepsyWarning.cs deleted file mode 100644 index 6316bbdb4e..0000000000 --- a/osu.Game/Screens/Play/EpilepsyWarning.cs +++ /dev/null @@ -1,102 +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 osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Screens.Backgrounds; -using osuTK; - -namespace osu.Game.Screens.Play -{ - public partial class EpilepsyWarning : VisibilityContainer - { - public const double FADE_DURATION = 250; - - public EpilepsyWarning() - { - RelativeSizeAxes = Axes.Both; - Alpha = 0f; - } - - private BackgroundScreenBeatmap dimmableBackground; - - public BackgroundScreenBeatmap DimmableBackground - { - get => dimmableBackground; - set - { - dimmableBackground = value; - - if (IsLoaded) - updateBackgroundFade(); - } - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Children = new Drawable[] - { - new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - new SpriteIcon - { - Colour = colours.Yellow, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Icon = FontAwesome.Solid.ExclamationTriangle, - Size = new Vector2(50), - }, - new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 25)) - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - TextAnchor = Anchor.Centre, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }.With(tfc => - { - tfc.AddText("This beatmap contains scenes with "); - tfc.AddText("rapidly flashing colours", s => - { - s.Font = s.Font.With(weight: FontWeight.Bold); - s.Colour = colours.Yellow; - }); - tfc.AddText("."); - - tfc.NewParagraph(); - tfc.AddText("Please take caution if you are affected by epilepsy."); - }), - } - } - }; - } - - protected override void PopIn() - { - updateBackgroundFade(); - - this.FadeIn(FADE_DURATION, Easing.OutQuint); - } - - private void updateBackgroundFade() - { - DimmableBackground?.FadeColour(OsuColour.Gray(0.5f), FADE_DURATION, Easing.OutQuint); - } - - protected override void PopOut() => this.FadeOut(FADE_DURATION); - } -} diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index aa730a8e0f..75240ba2ba 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -71,6 +71,7 @@ namespace osu.Game.Screens.Play protected Task? DisposalTask { get; private set; } + private FillFlowContainer disclaimers = null!; private OsuScrollContainer settingsScroll = null!; private Bindable showStoryboards = null!; @@ -137,7 +138,7 @@ namespace osu.Game.Screens.Play private ScheduledDelegate? scheduledPushPlayer; - private EpilepsyWarning? epilepsyWarning; + private PlayerLoaderDisclaimer? epilepsyWarning; private bool quickRestart; @@ -188,6 +189,16 @@ namespace osu.Game.Screens.Play Origin = Anchor.Centre, }, }), + disclaimers = new FillFlowContainer + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Padding = new MarginPadding(padding), + Spacing = new Vector2(padding), + }, settingsScroll = new OsuScrollContainer { Anchor = Anchor.TopRight, @@ -216,11 +227,7 @@ namespace osu.Game.Screens.Play if (Beatmap.Value.BeatmapInfo.EpilepsyWarning) { - AddInternal(epilepsyWarning = new EpilepsyWarning - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }); + disclaimers.Add(epilepsyWarning = new PlayerLoaderDisclaimer("This beatmap contains scenes with rapidly flashing colours", "Please take caution if you are affected by epilepsy.")); } } @@ -229,6 +236,9 @@ namespace osu.Game.Screens.Play base.LoadComplete(); inputManager = GetContainingInputManager(); + + showStoryboards.BindValueChanged(val => epilepsyWarning?.FadeTo(val.NewValue ? 1 : 0, 250, Easing.OutQuint), true); + epilepsyWarning?.FinishTransforms(true); } #region Screen handling @@ -237,15 +247,10 @@ namespace osu.Game.Screens.Play { base.OnEntering(e); - ApplyToBackground(b => - { - if (epilepsyWarning != null) - epilepsyWarning.DimmableBackground = b; - }); - Beatmap.Value.Track.AddAdjustment(AdjustableProperty.Volume, volumeAdjustment); - // Start off-screen. + // Start side content off-screen. + disclaimers.MoveToX(-disclaimers.DrawWidth); settingsScroll.MoveToX(settingsScroll.DrawWidth); content.ScaleTo(0.7f); @@ -301,9 +306,6 @@ namespace osu.Game.Screens.Play cancelLoad(); ContentOut(); - // If the load sequence was interrupted, the epilepsy warning may already be displayed (or in the process of being displayed). - epilepsyWarning?.Hide(); - // Ensure the screen doesn't expire until all the outwards fade operations have completed. this.Delay(CONTENT_OUT_DURATION).FadeOut(); @@ -433,6 +435,8 @@ namespace osu.Game.Screens.Play content.FadeInFromZero(500, Easing.OutQuint); content.ScaleTo(1, 650, Easing.OutQuint).Then().Schedule(prepareNewPlayer); + disclaimers.FadeInFromZero(500, Easing.Out) + .MoveToX(0, 500, Easing.OutQuint); settingsScroll.FadeInFromZero(500, Easing.Out) .MoveToX(0, 500, Easing.OutQuint); @@ -466,6 +470,8 @@ namespace osu.Game.Screens.Play lowPassFilter = null; }); + disclaimers.FadeOut(CONTENT_OUT_DURATION, Easing.Out) + .MoveToX(-disclaimers.DrawWidth, CONTENT_OUT_DURATION * 2, Easing.OutQuint); settingsScroll.FadeOut(CONTENT_OUT_DURATION, Easing.OutQuint) .MoveToX(settingsScroll.DrawWidth, CONTENT_OUT_DURATION * 2, Easing.OutQuint); @@ -503,33 +509,8 @@ namespace osu.Game.Screens.Play TransformSequence pushSequence = this.Delay(0); - // only show if the warning was created (i.e. the beatmap needs it) - // and this is not a restart of the map (the warning expires after first load). - // - // note the late check of storyboard enable as the user may have just changed it - // from the settings on the loader screen. - if (epilepsyWarning?.IsAlive == true && showStoryboards.Value) - { - const double epilepsy_display_length = 3000; - - pushSequence - .Delay(CONTENT_OUT_DURATION) - .Schedule(() => epilepsyWarning.State.Value = Visibility.Visible) - .TransformBindableTo(volumeAdjustment, 0.25, EpilepsyWarning.FADE_DURATION, Easing.OutQuint) - .Delay(epilepsy_display_length) - .Schedule(() => - { - epilepsyWarning.Hide(); - epilepsyWarning.Expire(); - }) - .Delay(EpilepsyWarning.FADE_DURATION); - } - else - { - // This goes hand-in-hand with the restoration of low pass filter in contentOut(). - this.TransformBindableTo(volumeAdjustment, 0, CONTENT_OUT_DURATION, Easing.OutCubic); - epilepsyWarning?.Expire(); - } + // 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(() => { From f3a444b7aca2295e92d473e7fe84a23aefd239b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 15 Mar 2024 09:41:37 +0100 Subject: [PATCH 3461/4092] Add disclaimer for loved/qualified status --- .../Visual/Gameplay/TestScenePlayerLoader.cs | 43 ++++++++++++++++++- osu.Game/Screens/Play/PlayerLoader.cs | 12 ++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index dac8545729..91e35b067b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -16,6 +16,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Framework.Utils; +using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; @@ -37,6 +38,7 @@ namespace osu.Game.Tests.Visual.Gameplay private TestPlayer player; private bool epilepsyWarning; + private BeatmapOnlineStatus onlineStatus; [Resolved] private AudioManager audioManager { get; set; } @@ -118,8 +120,9 @@ namespace osu.Game.Tests.Visual.Gameplay // Add intro time to test quick retry skipping (TestQuickRetry). workingBeatmap.BeatmapInfo.AudioLeadIn = 60000; - // Turn on epilepsy warning to test warning display (TestEpilepsyWarning). + // Set up data for testing disclaimer display. workingBeatmap.BeatmapInfo.EpilepsyWarning = epilepsyWarning; + workingBeatmap.BeatmapInfo.Status = onlineStatus; Beatmap.Value = workingBeatmap; @@ -356,6 +359,44 @@ namespace osu.Game.Tests.Visual.Gameplay restoreVolumes(); } + [TestCase(BeatmapOnlineStatus.Loved, 1)] + [TestCase(BeatmapOnlineStatus.Qualified, 1)] + [TestCase(BeatmapOnlineStatus.Graveyard, 0)] + public void TestStatusWarning(BeatmapOnlineStatus status, int expectedDisclaimerCount) + { + saveVolumes(); + setFullVolume(); + + AddStep("enable storyboards", () => config.SetValue(OsuSetting.ShowStoryboard, true)); + AddStep("disable epilepsy warning", () => epilepsyWarning = false); + AddStep("set beatmap status", () => onlineStatus = status); + AddStep("load dummy beatmap", () => resetPlayer(false)); + + AddUntilStep("wait for current", () => loader.IsCurrentScreen()); + + AddAssert($"disclaimer count is {expectedDisclaimerCount}", () => this.ChildrenOfType().Count(), () => Is.EqualTo(expectedDisclaimerCount)); + + restoreVolumes(); + } + + [Test] + public void TestCombinedWarnings() + { + saveVolumes(); + setFullVolume(); + + AddStep("enable storyboards", () => config.SetValue(OsuSetting.ShowStoryboard, true)); + AddStep("disable epilepsy warning", () => epilepsyWarning = true); + AddStep("set beatmap status", () => onlineStatus = BeatmapOnlineStatus.Loved); + AddStep("load dummy beatmap", () => resetPlayer(false)); + + AddUntilStep("wait for current", () => loader.IsCurrentScreen()); + + AddAssert($"disclaimer count is 2", () => this.ChildrenOfType().Count(), () => Is.EqualTo(2)); + + restoreVolumes(); + } + [TestCase(true, 1.0, false)] // on battery, above cutoff --> no warning [TestCase(false, 0.1, false)] // not on battery, below cutoff --> no warning [TestCase(true, 0.25, true)] // on battery, at cutoff --> warning diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 75240ba2ba..2d75b69b4a 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -18,6 +18,7 @@ using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Audio; using osu.Game.Audio.Effects; +using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -229,6 +230,17 @@ namespace osu.Game.Screens.Play { disclaimers.Add(epilepsyWarning = new PlayerLoaderDisclaimer("This beatmap contains scenes with rapidly flashing colours", "Please take caution if you are affected by epilepsy.")); } + + switch (Beatmap.Value.BeatmapInfo.Status) + { + case BeatmapOnlineStatus.Loved: + disclaimers.Add(new PlayerLoaderDisclaimer("This beatmap is loved", "No performance points will be awarded.\nLeaderboards may be reset by the beatmap creator.")); + break; + + case BeatmapOnlineStatus.Qualified: + disclaimers.Add(new PlayerLoaderDisclaimer("This beatmap is qualified", "No performance points will be awarded.\nLeaderboards will be reset when the beatmap is ranked.")); + break; + } } protected override void LoadComplete() From 4688a53cf435861af05f8253d029f30a4e5db342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 15 Mar 2024 09:43:09 +0100 Subject: [PATCH 3462/4092] Stay on player loader a bit longer if disclaimers are present Just to make reading the text easier. --- 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 2d75b69b4a..29ad6d2a47 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Play protected const double CONTENT_OUT_DURATION = 300; - protected virtual double PlayerPushDelay => 1800; + protected virtual double PlayerPushDelay => 1800 + disclaimers.Count * 500; public override bool HideOverlaysOnEnter => hideOverlays; From 4e2098adb86c73f8391255331c08dc5349567e67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 15 Mar 2024 09:46:18 +0100 Subject: [PATCH 3463/4092] Fix test crosstalk --- .../Visual/Gameplay/TestScenePlayerLoader.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 91e35b067b..0ec4284a00 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -37,8 +37,8 @@ namespace osu.Game.Tests.Visual.Gameplay private TestPlayerLoader loader; private TestPlayer player; - private bool epilepsyWarning; - private BeatmapOnlineStatus onlineStatus; + private bool? epilepsyWarning; + private BeatmapOnlineStatus? onlineStatus; [Resolved] private AudioManager audioManager { get; set; } @@ -83,7 +83,12 @@ namespace osu.Game.Tests.Visual.Gameplay } [SetUp] - public void Setup() => Schedule(() => player = null); + public void Setup() => Schedule(() => + { + player = null; + epilepsyWarning = null; + onlineStatus = null; + }); [SetUpSteps] public override void SetUpSteps() @@ -121,8 +126,8 @@ namespace osu.Game.Tests.Visual.Gameplay workingBeatmap.BeatmapInfo.AudioLeadIn = 60000; // Set up data for testing disclaimer display. - workingBeatmap.BeatmapInfo.EpilepsyWarning = epilepsyWarning; - workingBeatmap.BeatmapInfo.Status = onlineStatus; + workingBeatmap.BeatmapInfo.EpilepsyWarning = epilepsyWarning ?? false; + workingBeatmap.BeatmapInfo.Status = onlineStatus ?? BeatmapOnlineStatus.Ranked; Beatmap.Value = workingBeatmap; From 49a087f7fcf764fe4764e4dff0c178287f2dc5f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 15 Mar 2024 10:58:00 +0100 Subject: [PATCH 3464/4092] Add localisation support --- osu.Game/Localisation/PlayerLoaderStrings.cs | 48 ++++++++++++++++++++ osu.Game/Screens/Play/PlayerLoader.cs | 6 +-- 2 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 osu.Game/Localisation/PlayerLoaderStrings.cs diff --git a/osu.Game/Localisation/PlayerLoaderStrings.cs b/osu.Game/Localisation/PlayerLoaderStrings.cs new file mode 100644 index 0000000000..eba98c7aa7 --- /dev/null +++ b/osu.Game/Localisation/PlayerLoaderStrings.cs @@ -0,0 +1,48 @@ +// 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.Localisation; + +namespace osu.Game.Localisation +{ + public static class PlayerLoaderStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.PlayerLoader"; + + /// + /// "This beatmap contains scenes with rapidly flashing colours" + /// + public static LocalisableString EpilepsyWarningTitle => new TranslatableString(getKey(@"epilepsy_warning_title"), @"This beatmap contains scenes with rapidly flashing colours"); + + /// + /// "Please take caution if you are affected by epilepsy." + /// + public static LocalisableString EpilepsyWarningContent => new TranslatableString(getKey(@"epilepsy_warning_content"), @"Please take caution if you are affected by epilepsy."); + + /// + /// "This beatmap is loved" + /// + public static LocalisableString LovedBeatmapDisclaimerTitle => new TranslatableString(getKey(@"loved_beatmap_disclaimer_title"), @"This beatmap is loved"); + + /// + /// "No performance points will be awarded. + /// Leaderboards may be reset by the beatmap creator." + /// + public static LocalisableString LovedBeatmapDisclaimerContent => new TranslatableString(getKey(@"loved_beatmap_disclaimer_content"), @"No performance points will be awarded. +Leaderboards may be reset by the beatmap creator."); + + /// + /// "This beatmap is qualified" + /// + public static LocalisableString QualifiedBeatmapDisclaimerTitle => new TranslatableString(getKey(@"qualified_beatmap_disclaimer_title"), @"This beatmap is qualified"); + + /// + /// "No performance points will be awarded. + /// Leaderboards will be reset when the beatmap is ranked." + /// + public static LocalisableString QualifiedBeatmapDisclaimerContent => new TranslatableString(getKey(@"qualified_beatmap_disclaimer_content"), @"No performance points will be awarded. +Leaderboards will be reset when the beatmap is ranked."); + + private static string getKey(string key) => $@"{prefix}:{key}"; + } +} diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 29ad6d2a47..ad9814e8c9 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -228,17 +228,17 @@ namespace osu.Game.Screens.Play if (Beatmap.Value.BeatmapInfo.EpilepsyWarning) { - disclaimers.Add(epilepsyWarning = new PlayerLoaderDisclaimer("This beatmap contains scenes with rapidly flashing colours", "Please take caution if you are affected by epilepsy.")); + disclaimers.Add(epilepsyWarning = new PlayerLoaderDisclaimer(PlayerLoaderStrings.EpilepsyWarningTitle, PlayerLoaderStrings.EpilepsyWarningContent)); } switch (Beatmap.Value.BeatmapInfo.Status) { case BeatmapOnlineStatus.Loved: - disclaimers.Add(new PlayerLoaderDisclaimer("This beatmap is loved", "No performance points will be awarded.\nLeaderboards may be reset by the beatmap creator.")); + disclaimers.Add(new PlayerLoaderDisclaimer(PlayerLoaderStrings.LovedBeatmapDisclaimerTitle, PlayerLoaderStrings.LovedBeatmapDisclaimerContent)); break; case BeatmapOnlineStatus.Qualified: - disclaimers.Add(new PlayerLoaderDisclaimer("This beatmap is qualified", "No performance points will be awarded.\nLeaderboards will be reset when the beatmap is ranked.")); + disclaimers.Add(new PlayerLoaderDisclaimer(PlayerLoaderStrings.QualifiedBeatmapDisclaimerTitle, PlayerLoaderStrings.QualifiedBeatmapDisclaimerContent)); break; } } From 5513a727488fb1825a2ca60514ab478b92623b30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 15 Mar 2024 10:33:32 +0100 Subject: [PATCH 3465/4092] Improve hiding transition when multiple disclaimers are visible --- osu.Game/Screens/Play/PlayerLoader.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index ad9814e8c9..0cc8ed0f4b 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -194,8 +194,10 @@ namespace osu.Game.Screens.Play { Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, + Width = SettingsToolboxGroup.CONTAINER_WIDTH + padding * 2, + AutoSizeAxes = Axes.Y, + AutoSizeDuration = 250, + AutoSizeEasing = Easing.OutQuint, Direction = FillDirection.Vertical, Padding = new MarginPadding(padding), Spacing = new Vector2(padding), From 87682008fd0de936edb734c69d49cb0e7e4c4b78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 15 Mar 2024 11:26:48 +0100 Subject: [PATCH 3466/4092] Fix code quality inspection --- 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 0ec4284a00..4b00a86950 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -397,7 +397,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for current", () => loader.IsCurrentScreen()); - AddAssert($"disclaimer count is 2", () => this.ChildrenOfType().Count(), () => Is.EqualTo(2)); + AddAssert("disclaimer count is 2", () => this.ChildrenOfType().Count(), () => Is.EqualTo(2)); restoreVolumes(); } From 51568ba06a0465b3e3c3de13f4d9ce885c6e82ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 15 Mar 2024 11:31:45 +0100 Subject: [PATCH 3467/4092] Add background to disclaimers --- .../Screens/Play/PlayerLoaderDisclaimer.cs | 52 ++++++++++++------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoaderDisclaimer.cs b/osu.Game/Screens/Play/PlayerLoaderDisclaimer.cs index c8112055c8..b41a2d3112 100644 --- a/osu.Game/Screens/Play/PlayerLoaderDisclaimer.cs +++ b/osu.Game/Screens/Play/PlayerLoaderDisclaimer.cs @@ -24,40 +24,56 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, OverlayColourProvider colourProvider) { - Width = SettingsToolboxGroup.CONTAINER_WIDTH; + RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; + Masking = true; + CornerRadius = 5; InternalChildren = new Drawable[] { - new Circle + new Box { - Width = 7, - Height = 15, - Margin = new MarginPadding { Top = 2 }, - Colour = colours.Orange1, + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background4, }, - new FillFlowContainer + new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Left = 12 }, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 2), - Children = new[] + Padding = new MarginPadding(10), + Children = new Drawable[] { - new TextFlowContainer(t => t.Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 17)) + new Circle { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Text = title, + Width = 7, + Height = 15, + Margin = new MarginPadding { Top = 2 }, + Colour = colours.Orange1, }, - new TextFlowContainer(t => t.Font = OsuFont.GetFont(size: 16)) + new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Text = content, + Padding = new MarginPadding { Left = 12 }, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 2), + Children = new[] + { + new TextFlowContainer(t => t.Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 17)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Text = title, + }, + new TextFlowContainer(t => t.Font = OsuFont.GetFont(size: 16)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Text = content, + } + } } } } From e6883b841802c5a8689ca80b13ca277022901b71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 15 Mar 2024 11:38:51 +0100 Subject: [PATCH 3468/4092] Tweak visuals further --- osu.Game/Screens/Play/PlayerLoader.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 0cc8ed0f4b..196180f3dd 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -200,7 +200,7 @@ namespace osu.Game.Screens.Play AutoSizeEasing = Easing.OutQuint, Direction = FillDirection.Vertical, Padding = new MarginPadding(padding), - Spacing = new Vector2(padding), + Spacing = new Vector2(20), }, settingsScroll = new OsuScrollContainer { @@ -269,9 +269,12 @@ namespace osu.Game.Screens.Play content.ScaleTo(0.7f); - contentIn(); + const double metadata_delay = 750; - MetadataInfo.Delay(750).FadeIn(500, Easing.OutQuint); + contentIn(); + MetadataInfo.Delay(metadata_delay).FadeIn(500, Easing.OutQuint); + disclaimers.Delay(metadata_delay).FadeInFromZero(500, Easing.Out) + .MoveToX(0, 500, Easing.OutQuint); // after an initial delay, start the debounced load check. // this will continue to execute even after resuming back on restart. @@ -449,8 +452,6 @@ namespace osu.Game.Screens.Play content.FadeInFromZero(500, Easing.OutQuint); content.ScaleTo(1, 650, Easing.OutQuint).Then().Schedule(prepareNewPlayer); - disclaimers.FadeInFromZero(500, Easing.Out) - .MoveToX(0, 500, Easing.OutQuint); settingsScroll.FadeInFromZero(500, Easing.Out) .MoveToX(0, 500, Easing.OutQuint); From a78210c88f6d3d519c4aa884033b3a30e8e82657 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 15 Mar 2024 11:45:27 +0100 Subject: [PATCH 3469/4092] Handle hover so that users can hover disclaimer to block load & read it --- osu.Game/Screens/Play/PlayerLoaderDisclaimer.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Play/PlayerLoaderDisclaimer.cs b/osu.Game/Screens/Play/PlayerLoaderDisclaimer.cs index b41a2d3112..9dd5a3832c 100644 --- a/osu.Game/Screens/Play/PlayerLoaderDisclaimer.cs +++ b/osu.Game/Screens/Play/PlayerLoaderDisclaimer.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Overlays; @@ -79,5 +80,8 @@ namespace osu.Game.Screens.Play } }; } + + // handle hover so that users can hover the disclaimer to delay load if they want to read it. + protected override bool OnHover(HoverEvent e) => true; } } From 0f8d526453b1ada099a52fa7d69340b9cb04963f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 16 Mar 2024 10:09:49 +0800 Subject: [PATCH 3470/4092] Adjust timings and delay disclaimers the same as settings --- osu.Game/Screens/Play/PlayerLoader.cs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 196180f3dd..75c97d594d 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -269,12 +269,10 @@ namespace osu.Game.Screens.Play content.ScaleTo(0.7f); - const double metadata_delay = 750; + const double metadata_delay = 500; - contentIn(); MetadataInfo.Delay(metadata_delay).FadeIn(500, Easing.OutQuint); - disclaimers.Delay(metadata_delay).FadeInFromZero(500, Easing.Out) - .MoveToX(0, 500, Easing.OutQuint); + contentIn(metadata_delay + 250); // after an initial delay, start the debounced load check. // this will continue to execute even after resuming back on restart. @@ -352,7 +350,7 @@ namespace osu.Game.Screens.Play { if (this.IsCurrentScreen()) content.StartTracking(logo, resuming ? 0 : 500, Easing.InOutExpo); - }, resuming ? 0 : 500); + }, resuming ? 0 : 250); } protected override void LogoExiting(OsuLogo logo) @@ -445,15 +443,21 @@ namespace osu.Game.Screens.Play this.MakeCurrent(); } - private void contentIn() + private void contentIn(double delayBeforeSideDisplays = 0) { MetadataInfo.Loading = true; content.FadeInFromZero(500, Easing.OutQuint); content.ScaleTo(1, 650, Easing.OutQuint).Then().Schedule(prepareNewPlayer); - 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); + } AddRangeInternal(new[] { From a49c4ebea634cff84243192ef59e1fa273f5042f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 16 Mar 2024 10:23:21 +0800 Subject: [PATCH 3471/4092] Match settings panels' backgrounds visually and behaviourally --- .../Screens/Play/PlayerLoaderDisclaimer.cs | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoaderDisclaimer.cs b/osu.Game/Screens/Play/PlayerLoaderDisclaimer.cs index 9dd5a3832c..3637a4b210 100644 --- a/osu.Game/Screens/Play/PlayerLoaderDisclaimer.cs +++ b/osu.Game/Screens/Play/PlayerLoaderDisclaimer.cs @@ -18,6 +18,8 @@ namespace osu.Game.Screens.Play private readonly LocalisableString title; private readonly LocalisableString content; + private Box background = null!; + public PlayerLoaderDisclaimer(LocalisableString title, LocalisableString content) { this.title = title; @@ -34,10 +36,11 @@ namespace osu.Game.Screens.Play InternalChildren = new Drawable[] { - new Box + background = new Box { RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background4, + Alpha = 0.1f, }, new Container { @@ -81,7 +84,24 @@ namespace osu.Game.Screens.Play }; } - // handle hover so that users can hover the disclaimer to delay load if they want to read it. - protected override bool OnHover(HoverEvent e) => true; + protected override bool OnHover(HoverEvent e) + { + updateFadeState(); + + // handle hover so that users can hover the disclaimer to delay load if they want to read it. + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateFadeState(); + base.OnHoverLost(e); + } + + private void updateFadeState() + { + // Matches SettingsToolboxGroup + background.FadeTo(IsHovered ? 1 : 0.1f, (float)500, Easing.OutQuint); + } } } From bde3da2746c4f80e33dd9a632a8a9d95e7bef186 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 16 Mar 2024 11:11:42 +0800 Subject: [PATCH 3472/4092] Fix failing test --- .../Visual/Background/TestSceneUserDimBackgrounds.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index 204dea39b2..7d517b3155 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Extensions; +using osu.Framework.Extensions.PolygonExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; @@ -90,12 +91,13 @@ namespace osu.Game.Tests.Visual.Background AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new LoadBlockingTestPlayer { BlockLoad = true }))); AddUntilStep("Wait for Player Loader to load", () => playerLoader?.IsLoaded ?? false); AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent()); - AddStep("Trigger background preview", () => + + AddUntilStep("Screen is dimmed and blur applied", () => { - InputManager.MoveMouseTo(playerLoader.ScreenPos); InputManager.MoveMouseTo(playerLoader.VisualSettingsPos); + return songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied(); }); - AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); + AddStep("Stop background preview", () => InputManager.MoveMouseTo(playerLoader.ScreenPos)); AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.CheckBackgroundBlur(playerLoader.ExpectedBackgroundBlur)); } From a598ea5b97fd467395e901f8dbe08de4b620120b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 16 Mar 2024 12:14:32 +0800 Subject: [PATCH 3473/4092] Remove unused using statement --- osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index 7d517b3155..c5540eae88 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -9,7 +9,6 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Extensions; -using osu.Framework.Extensions.PolygonExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; From c0ae94dc60d6ac9df97784760cee4b5e6baa046b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 16 Mar 2024 12:15:12 +0800 Subject: [PATCH 3474/4092] Attempt to fix test better --- .../Visual/Background/TestSceneUserDimBackgrounds.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index c5540eae88..aac7689b1b 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -89,7 +89,11 @@ namespace osu.Game.Tests.Visual.Background setupUserSettings(); AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new LoadBlockingTestPlayer { BlockLoad = true }))); AddUntilStep("Wait for Player Loader to load", () => playerLoader?.IsLoaded ?? false); - AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent()); + AddAssert("Background retained from song select", () => + { + InputManager.MoveMouseTo(playerLoader); + return songSelect.IsBackgroundCurrent(); + }); AddUntilStep("Screen is dimmed and blur applied", () => { From 15c0b1a2ec786dbdac2263655229430764f2a1d3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 16 Mar 2024 13:18:42 +0800 Subject: [PATCH 3475/4092] Remove redundant cast --- osu.Game/Screens/Play/PlayerLoaderDisclaimer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerLoaderDisclaimer.cs b/osu.Game/Screens/Play/PlayerLoaderDisclaimer.cs index 3637a4b210..592af166ba 100644 --- a/osu.Game/Screens/Play/PlayerLoaderDisclaimer.cs +++ b/osu.Game/Screens/Play/PlayerLoaderDisclaimer.cs @@ -101,7 +101,7 @@ namespace osu.Game.Screens.Play private void updateFadeState() { // Matches SettingsToolboxGroup - background.FadeTo(IsHovered ? 1 : 0.1f, (float)500, Easing.OutQuint); + background.FadeTo(IsHovered ? 1 : 0.1f, 500, Easing.OutQuint); } } } From ea3a9314f9dd3bb19fcc0aff6c1b212cf96cc560 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 13 Mar 2024 23:08:00 +0300 Subject: [PATCH 3476/4092] Improve TimelineControlPointDisplay performance --- .../Timeline/TimelineControlPointDisplay.cs | 104 +++++++++++++----- .../Components/Timeline/TopPointPiece.cs | 6 +- 2 files changed, 78 insertions(+), 32 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs index cd97b293ba..60d113ef58 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs @@ -1,10 +1,9 @@ // 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.Specialized; -using System.Diagnostics; -using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Caching; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; @@ -15,6 +14,16 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline /// public partial class TimelineControlPointDisplay : TimelinePart { + [Resolved] + private Timeline? timeline { get; set; } + + /// + /// The visible time/position range of the timeline. + /// + private (float min, float max) visibleRange = (float.MinValue, float.MaxValue); + + private readonly Cached groupCache = new Cached(); + private readonly IBindableList controlPointGroups = new BindableList(); protected override void LoadBeatmap(EditorBeatmap beatmap) @@ -23,34 +32,71 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline controlPointGroups.UnbindAll(); controlPointGroups.BindTo(beatmap.ControlPointInfo.Groups); - controlPointGroups.BindCollectionChanged((_, args) => + controlPointGroups.BindCollectionChanged((_, _) => { - switch (args.Action) - { - case NotifyCollectionChangedAction.Reset: - Clear(); - break; - - case NotifyCollectionChangedAction.Add: - Debug.Assert(args.NewItems != null); - - foreach (var group in args.NewItems.OfType()) - Add(new TimelineControlPointGroup(group)); - break; - - case NotifyCollectionChangedAction.Remove: - Debug.Assert(args.OldItems != null); - - foreach (var group in args.OldItems.OfType()) - { - var matching = Children.SingleOrDefault(gv => ReferenceEquals(gv.Group, group)); - - matching?.Expire(); - } - - break; - } + invalidateGroups(); }, true); } + + protected override void Update() + { + base.Update(); + + if (timeline == null || DrawWidth <= 0) return; + + (float, float) newRange = ( + (ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopLeft).X - TopPointPiece.WIDTH) / DrawWidth * Content.RelativeChildSize.X, + (ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopRight).X) / DrawWidth * Content.RelativeChildSize.X); + + if (visibleRange != newRange) + { + visibleRange = newRange; + invalidateGroups(); + } + + if (!groupCache.IsValid) + recreateDrawableGroups(); + } + + private void invalidateGroups() => groupCache.Invalidate(); + + private void recreateDrawableGroups() + { + // Remove groups outside the visible range + for (int i = Count - 1; i >= 0; i--) + { + var g = Children[i]; + + if (!shouldBeVisible(g.Group)) + g.Expire(); + } + + // Add remaining ones + foreach (var group in controlPointGroups) + { + if (!shouldBeVisible(group)) + continue; + + bool alreadyVisible = false; + + foreach (var g in this) + { + if (ReferenceEquals(g.Group, group)) + { + alreadyVisible = true; + break; + } + } + + if (alreadyVisible) + continue; + + Add(new TimelineControlPointGroup(group)); + } + + groupCache.Validate(); + } + + private bool shouldBeVisible(ControlPointGroup group) => group.Time >= visibleRange.min && group.Time <= visibleRange.max; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TopPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TopPointPiece.cs index 243cdc6ddd..a40a805361 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TopPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TopPointPiece.cs @@ -19,12 +19,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected OsuSpriteText Label { get; private set; } = null!; - private const float width = 80; + public const float WIDTH = 80; public TopPointPiece(ControlPoint point) { Point = point; - Width = width; + Width = WIDTH; Height = 16; Margin = new MarginPadding { Vertical = 4 }; @@ -65,7 +65,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline new Container { RelativeSizeAxes = Axes.Y, - Width = width - triangle_portion, + Width = WIDTH - triangle_portion, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Colour = Point.GetRepresentingColour(colours), From e825db61eeebc0e1f94b2f62b6dba30c478dd58a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 16 Mar 2024 12:26:56 +0300 Subject: [PATCH 3477/4092] Fix enumerator allocation --- .../Components/Timeline/TimelineControlPointDisplay.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs index 60d113ef58..950b717ffb 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs @@ -72,8 +72,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } // Add remaining ones - foreach (var group in controlPointGroups) + for (int i = 0; i < controlPointGroups.Count; i++) { + var group = controlPointGroups[i]; + if (!shouldBeVisible(group)) continue; From 981ee54cdca16653753c85a71192f404cb80e85c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 16 Mar 2024 15:05:52 +0300 Subject: [PATCH 3478/4092] Fix transforms overhead in TimelineTickDisplay --- .../Edit/Compose/Components/Timeline/TimelineTickDisplay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index 7e7bef8cf2..5348d03a38 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -165,7 +165,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline // save a few drawables beyond the currently used for edge cases. while (drawableIndex < Math.Min(usedDrawables + 16, Count)) - Children[drawableIndex++].Hide(); + Children[drawableIndex++].Alpha = 0; // expire any excess while (drawableIndex < Count) @@ -182,7 +182,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline point = Children[drawableIndex]; drawableIndex++; - point.Show(); + point.Alpha = 1; return point; } From 34a5e2d606070ba043afc501030933bd4f158412 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 16 Mar 2024 15:20:37 +0300 Subject: [PATCH 3479/4092] Don't update subtree masking in TimelineTickDisplay --- .../Edit/Compose/Components/Timeline/TimelineTickDisplay.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index 5348d03a38..c3adb43032 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Graphics; +using osu.Framework.Graphics.Primitives; using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -18,6 +19,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public partial class TimelineTickDisplay : TimelinePart { + // With current implementation every tick in the sub-tree should be visible, no need to check whether they are masked away. + public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) => false; + [Resolved] private EditorBeatmap beatmap { get; set; } = null!; From 0a6960296ec1947594b06623cfd925433e09d380 Mon Sep 17 00:00:00 2001 From: Vlad Frangu Date: Sat, 16 Mar 2024 21:32:55 +0200 Subject: [PATCH 3480/4092] feat: Support filtering for multiple statuses --- .../Filtering/FilterQueryParserTest.cs | 22 ++++++++++---- osu.Game/Screens/Select/FilterCriteria.cs | 17 ++++++++++- osu.Game/Screens/Select/FilterQueryParser.cs | 29 ++++++++++++++++++- 3 files changed, 60 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index bf888348ee..c109e5bad2 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -256,8 +256,9 @@ namespace osu.Game.Tests.NonVisual.Filtering const string query = "status=r"; var filterCriteria = new FilterCriteria(); FilterQueryParser.ApplyQueries(filterCriteria, query); - Assert.AreEqual(BeatmapOnlineStatus.Ranked, filterCriteria.OnlineStatus.Min); - Assert.AreEqual(BeatmapOnlineStatus.Ranked, filterCriteria.OnlineStatus.Max); + Assert.IsNotNull(filterCriteria.OnlineStatus.Values); + Assert.IsNotEmpty(filterCriteria.OnlineStatus.Values!); + Assert.Contains(BeatmapOnlineStatus.Ranked, filterCriteria.OnlineStatus.Values); } [Test] @@ -268,10 +269,19 @@ namespace osu.Game.Tests.NonVisual.Filtering FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.AreEqual("I want the pp", filterCriteria.SearchText.Trim()); Assert.AreEqual(4, filterCriteria.SearchTerms.Length); - Assert.AreEqual(BeatmapOnlineStatus.Ranked, filterCriteria.OnlineStatus.Min); - Assert.IsTrue(filterCriteria.OnlineStatus.IsLowerInclusive); - Assert.AreEqual(BeatmapOnlineStatus.Ranked, filterCriteria.OnlineStatus.Max); - Assert.IsTrue(filterCriteria.OnlineStatus.IsUpperInclusive); + Assert.IsNotNull(filterCriteria.OnlineStatus.Values); + Assert.Contains(BeatmapOnlineStatus.Ranked, filterCriteria.OnlineStatus.Values); + } + + [Test] + public void TestApplyStatusMatches() + { + const string query = "status=ranked status=loved"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.IsNotNull(filterCriteria.OnlineStatus.Values); + Assert.Contains(BeatmapOnlineStatus.Ranked, filterCriteria.OnlineStatus.Values); + Assert.Contains(BeatmapOnlineStatus.Loved, filterCriteria.OnlineStatus.Values); } [TestCase("creator")] diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 46083f7c88..dd6b602ade 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -34,7 +34,7 @@ namespace osu.Game.Screens.Select public OptionalRange Length; public OptionalRange BPM; public OptionalRange BeatDivisor; - public OptionalRange OnlineStatus; + public OptionalArray OnlineStatus; public OptionalRange LastPlayed; public OptionalTextFilter Creator; public OptionalTextFilter Artist; @@ -114,6 +114,21 @@ namespace osu.Game.Screens.Select public IRulesetFilterCriteria? RulesetCriteria { get; set; } + public struct OptionalArray : IEquatable> + where T : struct + { + public bool HasFilter => Values?.Length > 0; + + public bool IsInRange(T value) + { + return Values?.Contains(value) ?? false; + } + + public T[]? Values; + + public bool Equals(OptionalArray other) => Values?.SequenceEqual(other.Values ?? Array.Empty()) ?? false; + } + public struct OptionalRange : IEquatable> where T : struct { diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 2c4077dacf..208e7f559e 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -69,7 +69,7 @@ namespace osu.Game.Screens.Select return TryUpdateCriteriaRange(ref criteria.BeatDivisor, op, value, tryParseInt); case "status": - return TryUpdateCriteriaRange(ref criteria.OnlineStatus, op, value, tryParseEnum); + return TryUpdateArrayRange(ref criteria.OnlineStatus, op, value, tryParseEnum); case "creator": case "author": @@ -300,6 +300,33 @@ namespace osu.Game.Screens.Select where T : struct => parseFunction.Invoke(val, out var converted) && tryUpdateCriteriaRange(ref range, op, converted); + /// + /// Attempts to parse a keyword filter of type , + /// from the specified and . + /// If can be parsed into using , the function returns true + /// and the resulting range constraint is stored into the 's expected values. + /// + /// The to store the parsed data into, if successful. + /// The operator for the keyword filter. Currently, only can be used. + /// The value of the keyword filter. + /// Function used to determine if can be converted to type . + public static bool TryUpdateArrayRange(ref FilterCriteria.OptionalArray range, Operator op, string val, TryParseFunction parseFunction) + where T : struct + => parseFunction.Invoke(val, out var converted) && tryUpdateArrayRange(ref range, op, converted); + + private static bool tryUpdateArrayRange(ref FilterCriteria.OptionalArray range, Operator op, T value) + where T : struct + { + if (op != Operator.Equal) + return false; + + range.Values ??= Array.Empty(); + + range.Values = range.Values.Append(value).ToArray(); + + return true; + } + private static bool tryUpdateCriteriaRange(ref FilterCriteria.OptionalRange range, Operator op, T value) where T : struct { From e1c1609271bee3b21c38383596f20f31c2a3573e Mon Sep 17 00:00:00 2001 From: Vlad Frangu Date: Sat, 16 Mar 2024 21:48:44 +0200 Subject: [PATCH 3481/4092] chore: correct equal logic --- osu.Game/Screens/Select/FilterCriteria.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index dd6b602ade..6cec7a387d 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -126,7 +126,13 @@ namespace osu.Game.Screens.Select public T[]? Values; - public bool Equals(OptionalArray other) => Values?.SequenceEqual(other.Values ?? Array.Empty()) ?? false; + public bool Equals(OptionalArray other) + { + if (Values is null && other.Values is null) + return true; + + return Values?.SequenceEqual(other.Values ?? Array.Empty()) ?? false; + } } public struct OptionalRange : IEquatable> From 63816adbc081065fe68b9bc20b49b9329c4abbd2 Mon Sep 17 00:00:00 2001 From: Arthur Araujo Date: Sat, 16 Mar 2024 21:20:12 -0300 Subject: [PATCH 3482/4092] Add verify checks to unused audio at the end --- .../Checks/CheckUnusedAudioAtEndTest.cs | 90 +++++++++++++++++++ .../CheckUnusedAudioAtEndStrings.cs | 19 ++++ osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 2 + .../Edit/Checks/CheckUnusedAudioAtEnd.cs | 50 +++++++++++ 4 files changed, 161 insertions(+) create mode 100644 osu.Game.Tests/Editing/Checks/CheckUnusedAudioAtEndTest.cs create mode 100644 osu.Game/Localisation/CheckUnusedAudioAtEndStrings.cs create mode 100644 osu.Game/Rulesets/Edit/Checks/CheckUnusedAudioAtEnd.cs diff --git a/osu.Game.Tests/Editing/Checks/CheckUnusedAudioAtEndTest.cs b/osu.Game.Tests/Editing/Checks/CheckUnusedAudioAtEndTest.cs new file mode 100644 index 0000000000..687feae63d --- /dev/null +++ b/osu.Game.Tests/Editing/Checks/CheckUnusedAudioAtEndTest.cs @@ -0,0 +1,90 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using Moq; +using NUnit.Framework; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap; + +namespace osu.Game.Tests.Editing.Checks +{ + public class CheckUnusedAudioTest + { + private CheckUnusedAudioAtEnd check = null!; + + private IBeatmap beatmapNotFullyMapped = null!; + + private IBeatmap beatmapFullyMapped = null!; + + [SetUp] + public void Setup() + { + check = new CheckUnusedAudioAtEnd(); + beatmapNotFullyMapped = new Beatmap + { + HitObjects = + { + new HitCircle { StartTime = 0 }, + new HitCircle { StartTime = 1_298 }, + }, + BeatmapInfo = new BeatmapInfo + { + Metadata = new BeatmapMetadata { AudioFile = "abc123.jpg" } + } + }; + beatmapFullyMapped = new Beatmap + { + HitObjects = + { + new HitCircle { StartTime = 0 }, + new HitCircle { StartTime = 9000 }, + }, + BeatmapInfo = new BeatmapInfo + { + Metadata = new BeatmapMetadata { AudioFile = "abc123.jpg" } + } + }; + } + + [Test] + public void TestAudioNotFullyUsed() + { + var context = getContext(beatmapNotFullyMapped); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckUnusedAudioAtEnd.IssueTemplateUnusedAudioAtEnd); + } + + [Test] + public void TestAudioFullyUsed() + { + var context = getContext(beatmapFullyMapped); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(0)); + } + + private BeatmapVerifierContext getContext(IBeatmap beatmap) + { + return new BeatmapVerifierContext(beatmap, getMockWorkingBeatmap(beatmap).Object); + } + + private Mock getMockWorkingBeatmap(IBeatmap beatmap) + { + var mockTrack = new TrackVirtualStore(new FramedClock()).GetVirtual(10000, "virtual"); + + var mockWorkingBeatmap = new Mock(); + mockWorkingBeatmap.SetupGet(w => w.Beatmap).Returns(beatmap); + mockWorkingBeatmap.SetupGet(w => w.Track).Returns(mockTrack); + + return mockWorkingBeatmap; + } + } +} diff --git a/osu.Game/Localisation/CheckUnusedAudioAtEndStrings.cs b/osu.Game/Localisation/CheckUnusedAudioAtEndStrings.cs new file mode 100644 index 0000000000..46f92237a9 --- /dev/null +++ b/osu.Game/Localisation/CheckUnusedAudioAtEndStrings.cs @@ -0,0 +1,19 @@ +// 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.Localisation; + +namespace osu.Game.Localisation +{ + public static class CheckUnusedAudioAtEndStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.CheckUnusedAudioAtEnd"; + + /// + /// "{0}% of the audio is not mapped." + /// + public static LocalisableString OfTheAudioIsNot(double percentageLeft) => new TranslatableString(getKey(@"of_the_audio_is_not"), @"{0}% of the audio is not mapped.", percentageLeft); + + private static string getKey(string key) => $@"{prefix}:{key}"; + } +} diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index dcf5eb4da9..4bba72d828 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -36,12 +36,14 @@ namespace osu.Game.Rulesets.Edit new CheckConcurrentObjects(), new CheckZeroLengthObjects(), new CheckDrainLength(), + new CheckUnusedAudioAtEnd(), // Timing new CheckPreviewTime(), // Events new CheckBreaks() + }; public IEnumerable Run(BeatmapVerifierContext context) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckUnusedAudioAtEnd.cs b/osu.Game/Rulesets/Edit/Checks/CheckUnusedAudioAtEnd.cs new file mode 100644 index 0000000000..c120e0993a --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checks/CheckUnusedAudioAtEnd.cs @@ -0,0 +1,50 @@ +// 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.Collections.Generic; +using System.Linq; +using osu.Game.Rulesets.Edit.Checks.Components; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Edit.Checks +{ + public class CheckUnusedAudioAtEnd : ICheck + { + public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Compose, "More than 20% unused audio at the end"); + + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplateUnusedAudioAtEnd(this), + }; + + public IEnumerable Run(BeatmapVerifierContext context) + { + double mappedLength = context.Beatmap.HitObjects.Last().GetEndTime(); + double trackLength = context.WorkingBeatmap.Track.Length; + + double mappedPercentage = calculatePercentage(mappedLength, trackLength); + + if (mappedPercentage < 80) + { + yield return new IssueTemplateUnusedAudioAtEnd(this).Create(); + } + + } + + private double calculatePercentage(double mappedLenght, double trackLenght) + { + return Math.Round(mappedLenght / trackLenght * 100); + } + + public class IssueTemplateUnusedAudioAtEnd : IssueTemplate + { + public IssueTemplateUnusedAudioAtEnd(ICheck check) + : base(check, IssueType.Problem, "There is more than 20% unused audio at the end.") + { + } + + public Issue Create() => new Issue(this); + } + } +} From f7aff76592c108ff3df1b97b3d2b8e8d7ded6938 Mon Sep 17 00:00:00 2001 From: Arthur Araujo Date: Sat, 16 Mar 2024 23:03:06 -0300 Subject: [PATCH 3483/4092] Fix codefactor issues --- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 1 - osu.Game/Rulesets/Edit/Checks/CheckUnusedAudioAtEnd.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index 4bba72d828..4a316afd22 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -43,7 +43,6 @@ namespace osu.Game.Rulesets.Edit // Events new CheckBreaks() - }; public IEnumerable Run(BeatmapVerifierContext context) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckUnusedAudioAtEnd.cs b/osu.Game/Rulesets/Edit/Checks/CheckUnusedAudioAtEnd.cs index c120e0993a..9c1f2748e9 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckUnusedAudioAtEnd.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckUnusedAudioAtEnd.cs @@ -29,7 +29,6 @@ namespace osu.Game.Rulesets.Edit.Checks { yield return new IssueTemplateUnusedAudioAtEnd(this).Create(); } - } private double calculatePercentage(double mappedLenght, double trackLenght) From 80f24a07916e9ae03fa4631e7f9018812139214d Mon Sep 17 00:00:00 2001 From: Arthur Araujo Date: Sat, 16 Mar 2024 23:30:59 -0300 Subject: [PATCH 3484/4092] Fix test class name not matching file name --- osu.Game.Tests/Editing/Checks/CheckUnusedAudioAtEndTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckUnusedAudioAtEndTest.cs b/osu.Game.Tests/Editing/Checks/CheckUnusedAudioAtEndTest.cs index 687feae63d..29c5cb96fd 100644 --- a/osu.Game.Tests/Editing/Checks/CheckUnusedAudioAtEndTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckUnusedAudioAtEndTest.cs @@ -14,7 +14,7 @@ using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap; namespace osu.Game.Tests.Editing.Checks { - public class CheckUnusedAudioTest + public class CheckUnusedAudioAtEndTest { private CheckUnusedAudioAtEnd check = null!; From d0678bfbee7952e2b82c6d25bd2abc5952937421 Mon Sep 17 00:00:00 2001 From: Vlad Frangu Date: Mon, 18 Mar 2024 15:30:43 +0200 Subject: [PATCH 3485/4092] chore: requested changes --- .../Filtering/FilterQueryParserTest.cs | 20 ++++++-- osu.Game/Screens/Select/FilterCriteria.cs | 19 +++---- osu.Game/Screens/Select/FilterQueryParser.cs | 50 +++++++++++++++---- 3 files changed, 67 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index c109e5bad2..98d3286409 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -256,8 +256,7 @@ namespace osu.Game.Tests.NonVisual.Filtering const string query = "status=r"; var filterCriteria = new FilterCriteria(); FilterQueryParser.ApplyQueries(filterCriteria, query); - Assert.IsNotNull(filterCriteria.OnlineStatus.Values); - Assert.IsNotEmpty(filterCriteria.OnlineStatus.Values!); + Assert.IsNotEmpty(filterCriteria.OnlineStatus.Values); Assert.Contains(BeatmapOnlineStatus.Ranked, filterCriteria.OnlineStatus.Values); } @@ -269,7 +268,7 @@ namespace osu.Game.Tests.NonVisual.Filtering FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.AreEqual("I want the pp", filterCriteria.SearchText.Trim()); Assert.AreEqual(4, filterCriteria.SearchTerms.Length); - Assert.IsNotNull(filterCriteria.OnlineStatus.Values); + Assert.IsNotEmpty(filterCriteria.OnlineStatus.Values); Assert.Contains(BeatmapOnlineStatus.Ranked, filterCriteria.OnlineStatus.Values); } @@ -279,11 +278,24 @@ namespace osu.Game.Tests.NonVisual.Filtering const string query = "status=ranked status=loved"; var filterCriteria = new FilterCriteria(); FilterQueryParser.ApplyQueries(filterCriteria, query); - Assert.IsNotNull(filterCriteria.OnlineStatus.Values); + Assert.IsNotEmpty(filterCriteria.OnlineStatus.Values); Assert.Contains(BeatmapOnlineStatus.Ranked, filterCriteria.OnlineStatus.Values); Assert.Contains(BeatmapOnlineStatus.Loved, filterCriteria.OnlineStatus.Values); } + [Test] + public void TestApplyRangeStatusMatches() + { + const string query = "status>=r"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.IsNotEmpty(filterCriteria.OnlineStatus.Values); + Assert.Contains(BeatmapOnlineStatus.Ranked, filterCriteria.OnlineStatus.Values); + Assert.Contains(BeatmapOnlineStatus.Approved, filterCriteria.OnlineStatus.Values); + Assert.Contains(BeatmapOnlineStatus.Qualified, filterCriteria.OnlineStatus.Values); + Assert.Contains(BeatmapOnlineStatus.Loved, filterCriteria.OnlineStatus.Values); + } + [TestCase("creator")] [TestCase("author")] [TestCase("mapper")] diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 6cec7a387d..1d155574cf 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -34,7 +34,7 @@ namespace osu.Game.Screens.Select public OptionalRange Length; public OptionalRange BPM; public OptionalRange BeatDivisor; - public OptionalArray OnlineStatus; + public OptionalSet OnlineStatus = new OptionalSet(); public OptionalRange LastPlayed; public OptionalTextFilter Creator; public OptionalTextFilter Artist; @@ -114,24 +114,25 @@ namespace osu.Game.Screens.Select public IRulesetFilterCriteria? RulesetCriteria { get; set; } - public struct OptionalArray : IEquatable> + public struct OptionalSet : IEquatable> where T : struct { - public bool HasFilter => Values?.Length > 0; + public bool HasFilter => Values.Count > 0; public bool IsInRange(T value) { - return Values?.Contains(value) ?? false; + return Values.Contains(value); } - public T[]? Values; + public SortedSet Values = new SortedSet(); - public bool Equals(OptionalArray other) + public OptionalSet() { - if (Values is null && other.Values is null) - return true; + } - return Values?.SequenceEqual(other.Values ?? Array.Empty()) ?? false; + public bool Equals(OptionalSet other) + { + return Values.SetEquals(other.Values); } } diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 208e7f559e..fbb09b93bd 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -69,7 +69,7 @@ namespace osu.Game.Screens.Select return TryUpdateCriteriaRange(ref criteria.BeatDivisor, op, value, tryParseInt); case "status": - return TryUpdateArrayRange(ref criteria.OnlineStatus, op, value, tryParseEnum); + return TryUpdateSetRange(ref criteria.OnlineStatus, op, value, tryParseEnum); case "creator": case "author": @@ -306,23 +306,55 @@ namespace osu.Game.Screens.Select /// If can be parsed into using , the function returns true /// and the resulting range constraint is stored into the 's expected values. /// - /// The to store the parsed data into, if successful. + /// The to store the parsed data into, if successful. /// The operator for the keyword filter. Currently, only can be used. /// The value of the keyword filter. /// Function used to determine if can be converted to type . - public static bool TryUpdateArrayRange(ref FilterCriteria.OptionalArray range, Operator op, string val, TryParseFunction parseFunction) + public static bool TryUpdateSetRange(ref FilterCriteria.OptionalSet range, Operator op, string val, TryParseFunction parseFunction) where T : struct - => parseFunction.Invoke(val, out var converted) && tryUpdateArrayRange(ref range, op, converted); + => parseFunction.Invoke(val, out var converted) && tryUpdateSetRange(ref range, op, converted); - private static bool tryUpdateArrayRange(ref FilterCriteria.OptionalArray range, Operator op, T value) + private static bool tryUpdateSetRange(ref FilterCriteria.OptionalSet range, Operator op, T value) where T : struct { - if (op != Operator.Equal) - return false; + var enumValues = (T[])Enum.GetValues(typeof(T)); - range.Values ??= Array.Empty(); + foreach (var enumValue in enumValues) + { + switch (op) + { + case Operator.Less: + if (Comparer.Default.Compare(enumValue, value) < 0) + range.Values.Add(enumValue); - range.Values = range.Values.Append(value).ToArray(); + break; + + case Operator.LessOrEqual: + if (Comparer.Default.Compare(enumValue, value) <= 0) + range.Values.Add(enumValue); + + break; + + case Operator.Equal: + range.Values.Add(value); + break; + + case Operator.GreaterOrEqual: + if (Comparer.Default.Compare(enumValue, value) >= 0) + range.Values.Add(enumValue); + + break; + + case Operator.Greater: + if (Comparer.Default.Compare(enumValue, value) > 0) + range.Values.Add(enumValue); + + break; + + default: + return false; + } + } return true; } From 77119b2cdb1c4f2b743aa9a99eac1f48f74064d1 Mon Sep 17 00:00:00 2001 From: Vlad Frangu Date: Mon, 18 Mar 2024 16:44:42 +0200 Subject: [PATCH 3486/4092] chore: correct doc string --- osu.Game/Screens/Select/FilterQueryParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index fbb09b93bd..76d58963e7 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -307,7 +307,7 @@ namespace osu.Game.Screens.Select /// and the resulting range constraint is stored into the 's expected values. /// /// The to store the parsed data into, if successful. - /// The operator for the keyword filter. Currently, only can be used. + /// The operator for the keyword filter. /// The value of the keyword filter. /// Function used to determine if can be converted to type . public static bool TryUpdateSetRange(ref FilterCriteria.OptionalSet range, Operator op, string val, TryParseFunction parseFunction) From a3f3dcf853b6148c6c2cbebe41b88711c01d468a Mon Sep 17 00:00:00 2001 From: Arthur Araujo Date: Mon, 18 Mar 2024 13:27:43 -0300 Subject: [PATCH 3487/4092] Inline percentage calculation --- osu.Game/Rulesets/Edit/Checks/CheckUnusedAudioAtEnd.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckUnusedAudioAtEnd.cs b/osu.Game/Rulesets/Edit/Checks/CheckUnusedAudioAtEnd.cs index 9c1f2748e9..8795eeac2d 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckUnusedAudioAtEnd.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckUnusedAudioAtEnd.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Edit.Checks double mappedLength = context.Beatmap.HitObjects.Last().GetEndTime(); double trackLength = context.WorkingBeatmap.Track.Length; - double mappedPercentage = calculatePercentage(mappedLength, trackLength); + double mappedPercentage = Math.Round(mappedLength / trackLength * 100); if (mappedPercentage < 80) { @@ -31,11 +31,6 @@ namespace osu.Game.Rulesets.Edit.Checks } } - private double calculatePercentage(double mappedLenght, double trackLenght) - { - return Math.Round(mappedLenght / trackLenght * 100); - } - public class IssueTemplateUnusedAudioAtEnd : IssueTemplate { public IssueTemplateUnusedAudioAtEnd(ICheck check) From 915a9682b5be54b090d39ae2a44beae8ea2c9ce7 Mon Sep 17 00:00:00 2001 From: Arthur Araujo Date: Mon, 18 Mar 2024 13:51:36 -0300 Subject: [PATCH 3488/4092] Fix issue type and display percentage left --- osu.Game/Rulesets/Edit/Checks/CheckUnusedAudioAtEnd.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckUnusedAudioAtEnd.cs b/osu.Game/Rulesets/Edit/Checks/CheckUnusedAudioAtEnd.cs index 8795eeac2d..d22303b7df 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckUnusedAudioAtEnd.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckUnusedAudioAtEnd.cs @@ -27,18 +27,19 @@ namespace osu.Game.Rulesets.Edit.Checks if (mappedPercentage < 80) { - yield return new IssueTemplateUnusedAudioAtEnd(this).Create(); + double percentageLeft = Math.Abs(mappedPercentage - 100); + yield return new IssueTemplateUnusedAudioAtEnd(this).Create(percentageLeft); } } public class IssueTemplateUnusedAudioAtEnd : IssueTemplate { public IssueTemplateUnusedAudioAtEnd(ICheck check) - : base(check, IssueType.Problem, "There is more than 20% unused audio at the end.") + : base(check, IssueType.Warning, "Currently there is {0}% unused audio at the end. Ensure the outro significantly contributes to the song, otherwise cut the outro.") { } - public Issue Create() => new Issue(this); + public Issue Create(double percentageLeft) => new Issue(this, percentageLeft); } } } From c23212f4efad0c58207265877a5130177f47d2e5 Mon Sep 17 00:00:00 2001 From: Arthur Araujo Date: Mon, 18 Mar 2024 14:02:33 -0300 Subject: [PATCH 3489/4092] Use `GetLastObjectTime` to calculate mapped length --- osu.Game/Rulesets/Edit/Checks/CheckUnusedAudioAtEnd.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckUnusedAudioAtEnd.cs b/osu.Game/Rulesets/Edit/Checks/CheckUnusedAudioAtEnd.cs index d22303b7df..d9a675fd17 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckUnusedAudioAtEnd.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckUnusedAudioAtEnd.cs @@ -3,9 +3,8 @@ using System; using System.Collections.Generic; -using System.Linq; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit.Checks.Components; -using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Edit.Checks { @@ -20,7 +19,7 @@ namespace osu.Game.Rulesets.Edit.Checks public IEnumerable Run(BeatmapVerifierContext context) { - double mappedLength = context.Beatmap.HitObjects.Last().GetEndTime(); + double mappedLength = context.Beatmap.GetLastObjectTime(); double trackLength = context.WorkingBeatmap.Track.Length; double mappedPercentage = Math.Round(mappedLength / trackLength * 100); From 0edc249637d56208bca655db17e274738df0b70e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 18 Mar 2024 20:38:19 +0300 Subject: [PATCH 3490/4092] Make Timeline non-nullable --- .../Components/Timeline/TimelineControlPointDisplay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs index 950b717ffb..1bf12e40d1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs @@ -15,7 +15,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public partial class TimelineControlPointDisplay : TimelinePart { [Resolved] - private Timeline? timeline { get; set; } + private Timeline timeline { get; set; } = null!; /// /// The visible time/position range of the timeline. @@ -42,7 +42,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { base.Update(); - if (timeline == null || DrawWidth <= 0) return; + if (DrawWidth <= 0) return; (float, float) newRange = ( (ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopLeft).X - TopPointPiece.WIDTH) / DrawWidth * Content.RelativeChildSize.X, From 7ca45c75b3348f66c0ec86e02a72c114abb27c8c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 18 Mar 2024 20:46:38 +0300 Subject: [PATCH 3491/4092] Don't iterate backwards on children without a reason --- .../Components/Timeline/TimelineControlPointDisplay.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs index 1bf12e40d1..8e522fa715 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs @@ -63,12 +63,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void recreateDrawableGroups() { // Remove groups outside the visible range - for (int i = Count - 1; i >= 0; i--) + foreach (var drawableGroup in this) { - var g = Children[i]; - - if (!shouldBeVisible(g.Group)) - g.Expire(); + if (!shouldBeVisible(drawableGroup.Group)) + drawableGroup.Expire(); } // Add remaining ones From f6d7f18f2592071d98872e2cda18f8de9d20cfa1 Mon Sep 17 00:00:00 2001 From: Arthur Araujo Date: Mon, 18 Mar 2024 15:59:19 -0300 Subject: [PATCH 3492/4092] Remove unused localisation file --- .../CheckUnusedAudioAtEndStrings.cs | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 osu.Game/Localisation/CheckUnusedAudioAtEndStrings.cs diff --git a/osu.Game/Localisation/CheckUnusedAudioAtEndStrings.cs b/osu.Game/Localisation/CheckUnusedAudioAtEndStrings.cs deleted file mode 100644 index 46f92237a9..0000000000 --- a/osu.Game/Localisation/CheckUnusedAudioAtEndStrings.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.Framework.Localisation; - -namespace osu.Game.Localisation -{ - public static class CheckUnusedAudioAtEndStrings - { - private const string prefix = @"osu.Game.Resources.Localisation.CheckUnusedAudioAtEnd"; - - /// - /// "{0}% of the audio is not mapped." - /// - public static LocalisableString OfTheAudioIsNot(double percentageLeft) => new TranslatableString(getKey(@"of_the_audio_is_not"), @"{0}% of the audio is not mapped.", percentageLeft); - - private static string getKey(string key) => $@"{prefix}:{key}"; - } -} From 5241c999c1f3300f36723a1d070c23240936c8da Mon Sep 17 00:00:00 2001 From: Arthur Araujo Date: Mon, 18 Mar 2024 16:08:41 -0300 Subject: [PATCH 3493/4092] Add different warning to maps with storyboard/video --- .../Checks/CheckUnusedAudioAtEndTest.cs | 51 +++++++++++++++++-- .../Edit/Checks/CheckUnusedAudioAtEnd.cs | 37 +++++++++++++- 2 files changed, 84 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckUnusedAudioAtEndTest.cs b/osu.Game.Tests/Editing/Checks/CheckUnusedAudioAtEndTest.cs index 29c5cb96fd..33d73a8086 100644 --- a/osu.Game.Tests/Editing/Checks/CheckUnusedAudioAtEndTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckUnusedAudioAtEndTest.cs @@ -4,12 +4,15 @@ using System.Linq; using Moq; using NUnit.Framework; +using osu.Framework.Graphics; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Checks; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Storyboards; +using osuTK; using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap; namespace osu.Game.Tests.Editing.Checks @@ -47,7 +50,7 @@ namespace osu.Game.Tests.Editing.Checks }, BeatmapInfo = new BeatmapInfo { - Metadata = new BeatmapMetadata { AudioFile = "abc123.jpg" } + Metadata = new BeatmapMetadata { AudioFile = "abc123.jpg" }, } }; } @@ -62,6 +65,42 @@ namespace osu.Game.Tests.Editing.Checks Assert.That(issues.Single().Template is CheckUnusedAudioAtEnd.IssueTemplateUnusedAudioAtEnd); } + [Test] + public void TestAudioNotFullyUsedWithVideo() + { + var storyboard = new Storyboard(); + + var video = new StoryboardVideo("abc123.mp4", 0); + + storyboard.GetLayer("Video").Add(video); + + var mockWorkingBeatmap = getMockWorkingBeatmap(beatmapNotFullyMapped, storyboard); + + var context = getContext(beatmapNotFullyMapped, mockWorkingBeatmap); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckUnusedAudioAtEnd.IssueTemplateUnusedAudioAtEndStoryboardOrVideo); + } + + [Test] + public void TestAudioNotFullyUsedWithStoryboardElement() + { + var storyboard = new Storyboard(); + + var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero); + + storyboard.GetLayer("Background").Add(sprite); + + var mockWorkingBeatmap = getMockWorkingBeatmap(beatmapNotFullyMapped, storyboard); + + var context = getContext(beatmapNotFullyMapped, mockWorkingBeatmap); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckUnusedAudioAtEnd.IssueTemplateUnusedAudioAtEndStoryboardOrVideo); + } + [Test] public void TestAudioFullyUsed() { @@ -73,16 +112,22 @@ namespace osu.Game.Tests.Editing.Checks private BeatmapVerifierContext getContext(IBeatmap beatmap) { - return new BeatmapVerifierContext(beatmap, getMockWorkingBeatmap(beatmap).Object); + return new BeatmapVerifierContext(beatmap, getMockWorkingBeatmap(beatmap, new Storyboard()).Object); } - private Mock getMockWorkingBeatmap(IBeatmap beatmap) + private BeatmapVerifierContext getContext(IBeatmap beatmap, Mock workingBeatmap) + { + return new BeatmapVerifierContext(beatmap, workingBeatmap.Object); + } + + private Mock getMockWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard) { var mockTrack = new TrackVirtualStore(new FramedClock()).GetVirtual(10000, "virtual"); var mockWorkingBeatmap = new Mock(); mockWorkingBeatmap.SetupGet(w => w.Beatmap).Returns(beatmap); mockWorkingBeatmap.SetupGet(w => w.Track).Returns(mockTrack); + mockWorkingBeatmap.SetupGet(w => w.Storyboard).Returns(storyboard); return mockWorkingBeatmap; } diff --git a/osu.Game/Rulesets/Edit/Checks/CheckUnusedAudioAtEnd.cs b/osu.Game/Rulesets/Edit/Checks/CheckUnusedAudioAtEnd.cs index d9a675fd17..2f768b6ffa 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckUnusedAudioAtEnd.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckUnusedAudioAtEnd.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit.Checks.Components; +using osu.Game.Storyboards; namespace osu.Game.Rulesets.Edit.Checks { @@ -15,6 +16,7 @@ namespace osu.Game.Rulesets.Edit.Checks public IEnumerable PossibleTemplates => new IssueTemplate[] { new IssueTemplateUnusedAudioAtEnd(this), + new IssueTemplateUnusedAudioAtEndStoryboardOrVideo(this), }; public IEnumerable Run(BeatmapVerifierContext context) @@ -27,10 +29,33 @@ namespace osu.Game.Rulesets.Edit.Checks if (mappedPercentage < 80) { double percentageLeft = Math.Abs(mappedPercentage - 100); - yield return new IssueTemplateUnusedAudioAtEnd(this).Create(percentageLeft); + + bool storyboardIsPresent = isAnyStoryboardElementPresent(context.WorkingBeatmap.Storyboard); + + if (storyboardIsPresent) + { + yield return new IssueTemplateUnusedAudioAtEndStoryboardOrVideo(this).Create(percentageLeft); + } + else + { + yield return new IssueTemplateUnusedAudioAtEnd(this).Create(percentageLeft); + } } } + private bool isAnyStoryboardElementPresent(Storyboard storyboard) + { + foreach (var layer in storyboard.Layers) + { + foreach (var _ in layer.Elements) + { + return true; + } + } + + return false; + } + public class IssueTemplateUnusedAudioAtEnd : IssueTemplate { public IssueTemplateUnusedAudioAtEnd(ICheck check) @@ -40,5 +65,15 @@ namespace osu.Game.Rulesets.Edit.Checks public Issue Create(double percentageLeft) => new Issue(this, percentageLeft); } + + public class IssueTemplateUnusedAudioAtEndStoryboardOrVideo : IssueTemplate + { + public IssueTemplateUnusedAudioAtEndStoryboardOrVideo(ICheck check) + : base(check, IssueType.Warning, "Currently there is {0}% unused audio at the end. Ensure the outro significantly contributes to the song, or is being occupied by the video or storyboard, otherwise cut the outro.") + { + } + + public Issue Create(double percentageLeft) => new Issue(this, percentageLeft); + } } } From a8ce6a0bba5c65ab12360191d7589365d0996133 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 19 Mar 2024 02:43:34 +0300 Subject: [PATCH 3494/4092] Use `Slice` method instead of index range operators for readability --- .../Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 2b058d5e1f..283a59b7ed 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -303,16 +303,16 @@ namespace osu.Game.Rulesets.Objects.Legacy { int startIndex = segmentsBuffer[i].StartIndex; int endIndex = segmentsBuffer[i + 1].StartIndex; - controlPoints.AddRange(convertPoints(segmentsBuffer[i].Type, allPoints[startIndex..endIndex], pointsBuffer[endIndex])); + controlPoints.AddRange(convertPoints(segmentsBuffer[i].Type, allPoints.Slice(startIndex, endIndex - startIndex), pointsBuffer[endIndex])); } else { int startIndex = segmentsBuffer[i].StartIndex; - controlPoints.AddRange(convertPoints(segmentsBuffer[i].Type, allPoints[startIndex..], null)); + controlPoints.AddRange(convertPoints(segmentsBuffer[i].Type, allPoints.Slice(startIndex), null)); } } - return mergePointsLists(controlPoints); + return mergeControlPointsLists(controlPoints); } finally { @@ -402,7 +402,7 @@ namespace osu.Game.Rulesets.Objects.Legacy - (p1.X - p0.X) * (p2.Y - p0.Y)); } - private PathControlPoint[] mergePointsLists(List> controlPointList) + private PathControlPoint[] mergeControlPointsLists(List> controlPointList) { int totalCount = 0; From af713a78695f4abd3e8023a98245f034fbb55da8 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 19 Mar 2024 17:20:59 +0900 Subject: [PATCH 3495/4092] Fix incorrectly encoded score IsPerfect value --- .../Formats/LegacyScoreEncoderTest.cs | 38 ++++++++++++++++--- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 7 +++- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 2 +- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreEncoderTest.cs index c0a7285f39..c0bf47dfc9 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreEncoderTest.cs @@ -6,6 +6,7 @@ using System.IO; using NUnit.Framework; using osu.Game.Beatmaps.Formats; using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Scoring.Legacy; @@ -21,9 +22,9 @@ namespace osu.Game.Tests.Beatmaps.Formats public void CatchMergesFruitAndDropletMisses(int missCount, int largeTickMissCount) { var ruleset = new CatchRuleset().RulesetInfo; - var scoreInfo = TestResources.CreateTestScoreInfo(ruleset); var beatmap = new TestBeatmap(ruleset); + scoreInfo.Statistics = new Dictionary { [HitResult.Great] = 50, @@ -31,14 +32,41 @@ namespace osu.Game.Tests.Beatmaps.Formats [HitResult.Miss] = missCount, [HitResult.LargeTickMiss] = largeTickMissCount }; - var score = new Score { ScoreInfo = scoreInfo }; - var decodedAfterEncode = encodeThenDecode(LegacyBeatmapDecoder.LATEST_VERSION, score, beatmap); + var score = new Score { ScoreInfo = scoreInfo }; + var decodedAfterEncode = encodeThenDecode(LegacyBeatmapDecoder.LATEST_VERSION, score, beatmap, out _); Assert.That(decodedAfterEncode.ScoreInfo.GetCountMiss(), Is.EqualTo(missCount + largeTickMissCount)); } - private static Score encodeThenDecode(int beatmapVersion, Score score, TestBeatmap beatmap) + [Test] + public void ScoreWithMissIsNotPerfect() + { + var ruleset = new OsuRuleset().RulesetInfo; + var scoreInfo = TestResources.CreateTestScoreInfo(ruleset); + var beatmap = new TestBeatmap(ruleset); + + scoreInfo.Statistics = new Dictionary + { + [HitResult.Great] = 2, + [HitResult.Miss] = 1, + }; + + scoreInfo.MaximumStatistics = new Dictionary + { + [HitResult.Great] = 3 + }; + + // Hit -> Miss -> Hit + scoreInfo.Combo = 1; + scoreInfo.MaxCombo = 1; + + encodeThenDecode(LegacyBeatmapDecoder.LATEST_VERSION, new Score { ScoreInfo = scoreInfo }, beatmap, out var decoder); + + Assert.That(decoder.DecodedPerfectValue, Is.False); + } + + private static Score encodeThenDecode(int beatmapVersion, Score score, TestBeatmap beatmap, out LegacyScoreDecoderTest.TestLegacyScoreDecoder decoder) { var encodeStream = new MemoryStream(); @@ -47,7 +75,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var decodeStream = new MemoryStream(encodeStream.GetBuffer()); - var decoder = new LegacyScoreDecoderTest.TestLegacyScoreDecoder(beatmapVersion); + decoder = new LegacyScoreDecoderTest.TestLegacyScoreDecoder(beatmapVersion); var decodedAfterEncode = decoder.Parse(decodeStream); return decodedAfterEncode; } diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index 65e2c02655..f2c096da15 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -27,6 +27,11 @@ namespace osu.Game.Scoring.Legacy { public abstract class LegacyScoreDecoder { + /// + /// The decoded "IsPerfect" value. This isn't used by osu!lazer. + /// + public bool DecodedPerfectValue { get; private set; } + private IBeatmap currentBeatmap; private Ruleset currentRuleset; @@ -82,7 +87,7 @@ namespace osu.Game.Scoring.Legacy scoreInfo.MaxCombo = sr.ReadUInt16(); /* score.Perfect = */ - sr.ReadBoolean(); + DecodedPerfectValue = sr.ReadBoolean(); scoreInfo.Mods = currentRuleset.ConvertFromLegacyMods((LegacyMods)sr.ReadInt32()).ToArray(); diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 4ee4231925..1df54565e9 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -93,7 +93,7 @@ namespace osu.Game.Scoring.Legacy sw.Write((ushort)(score.ScoreInfo.GetCountMiss() ?? 0)); sw.Write((int)(score.ScoreInfo.TotalScore)); sw.Write((ushort)score.ScoreInfo.MaxCombo); - sw.Write(score.ScoreInfo.Combo == score.ScoreInfo.MaxCombo); + sw.Write(score.ScoreInfo.Combo == score.ScoreInfo.GetMaximumAchievableCombo()); sw.Write((int)score.ScoreInfo.Ruleset.CreateInstance().ConvertToLegacyMods(score.ScoreInfo.Mods)); sw.Write(getHpGraphFormatted()); From 6e3350941749a87b21ed2c819c063e7a556509f0 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 19 Mar 2024 17:44:37 +0900 Subject: [PATCH 3496/4092] Remove added property, use local decoding instead --- .../Formats/LegacyScoreEncoderTest.cs | 34 ++++++++++++++++--- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 7 +--- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreEncoderTest.cs index c0bf47dfc9..806f538249 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreEncoderTest.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using NUnit.Framework; using osu.Game.Beatmaps.Formats; +using osu.Game.IO.Legacy; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; @@ -34,7 +35,7 @@ namespace osu.Game.Tests.Beatmaps.Formats }; var score = new Score { ScoreInfo = scoreInfo }; - var decodedAfterEncode = encodeThenDecode(LegacyBeatmapDecoder.LATEST_VERSION, score, beatmap, out _); + var decodedAfterEncode = encodeThenDecode(LegacyBeatmapDecoder.LATEST_VERSION, score, beatmap); Assert.That(decodedAfterEncode.ScoreInfo.GetCountMiss(), Is.EqualTo(missCount + largeTickMissCount)); } @@ -61,12 +62,35 @@ namespace osu.Game.Tests.Beatmaps.Formats scoreInfo.Combo = 1; scoreInfo.MaxCombo = 1; - encodeThenDecode(LegacyBeatmapDecoder.LATEST_VERSION, new Score { ScoreInfo = scoreInfo }, beatmap, out var decoder); + using (var ms = new MemoryStream()) + { + new LegacyScoreEncoder(new Score { ScoreInfo = scoreInfo }, beatmap).Encode(ms, true); - Assert.That(decoder.DecodedPerfectValue, Is.False); + ms.Seek(0, SeekOrigin.Begin); + + using (var sr = new SerializationReader(ms)) + { + sr.ReadByte(); // ruleset id + sr.ReadInt32(); // version + sr.ReadString(); // beatmap hash + sr.ReadString(); // username + sr.ReadString(); // score hash + sr.ReadInt16(); // count300 + sr.ReadInt16(); // count100 + sr.ReadInt16(); // count50 + sr.ReadInt16(); // countGeki + sr.ReadInt16(); // countKatu + sr.ReadInt16(); // countMiss + sr.ReadInt32(); // total score + sr.ReadInt16(); // max combo + bool isPerfect = sr.ReadBoolean(); // full combo + + Assert.That(isPerfect, Is.False); + } + } } - private static Score encodeThenDecode(int beatmapVersion, Score score, TestBeatmap beatmap, out LegacyScoreDecoderTest.TestLegacyScoreDecoder decoder) + private static Score encodeThenDecode(int beatmapVersion, Score score, TestBeatmap beatmap) { var encodeStream = new MemoryStream(); @@ -75,7 +99,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var decodeStream = new MemoryStream(encodeStream.GetBuffer()); - decoder = new LegacyScoreDecoderTest.TestLegacyScoreDecoder(beatmapVersion); + var decoder = new LegacyScoreDecoderTest.TestLegacyScoreDecoder(beatmapVersion); var decodedAfterEncode = decoder.Parse(decodeStream); return decodedAfterEncode; } diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index f2c096da15..65e2c02655 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -27,11 +27,6 @@ namespace osu.Game.Scoring.Legacy { public abstract class LegacyScoreDecoder { - /// - /// The decoded "IsPerfect" value. This isn't used by osu!lazer. - /// - public bool DecodedPerfectValue { get; private set; } - private IBeatmap currentBeatmap; private Ruleset currentRuleset; @@ -87,7 +82,7 @@ namespace osu.Game.Scoring.Legacy scoreInfo.MaxCombo = sr.ReadUInt16(); /* score.Perfect = */ - DecodedPerfectValue = sr.ReadBoolean(); + sr.ReadBoolean(); scoreInfo.Mods = currentRuleset.ConvertFromLegacyMods((LegacyMods)sr.ReadInt32()).ToArray(); From f6069d8d93b05c752b1aeaa36e4c269580880f26 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 19 Mar 2024 17:46:18 +0900 Subject: [PATCH 3497/4092] Compare against MaxCombo instead --- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 1df54565e9..93f51ee74d 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -93,7 +93,7 @@ namespace osu.Game.Scoring.Legacy sw.Write((ushort)(score.ScoreInfo.GetCountMiss() ?? 0)); sw.Write((int)(score.ScoreInfo.TotalScore)); sw.Write((ushort)score.ScoreInfo.MaxCombo); - sw.Write(score.ScoreInfo.Combo == score.ScoreInfo.GetMaximumAchievableCombo()); + sw.Write(score.ScoreInfo.MaxCombo == score.ScoreInfo.GetMaximumAchievableCombo()); sw.Write((int)score.ScoreInfo.Ruleset.CreateInstance().ConvertToLegacyMods(score.ScoreInfo.Mods)); sw.Write(getHpGraphFormatted()); From 0de5ca8d2df633b044b41d121bb3ed97aee73e47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Mar 2024 01:26:39 +0800 Subject: [PATCH 3498/4092] Update incorrect xmldoc --- osu.Game/Screens/Play/DelayedResumeOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/DelayedResumeOverlay.cs b/osu.Game/Screens/Play/DelayedResumeOverlay.cs index fd1ce5d829..8bb3ae8182 100644 --- a/osu.Game/Screens/Play/DelayedResumeOverlay.cs +++ b/osu.Game/Screens/Play/DelayedResumeOverlay.cs @@ -18,7 +18,7 @@ using osuTK; namespace osu.Game.Screens.Play { /// - /// Simple that resumes after 800ms. + /// Simple that resumes after a short delay. /// public partial class DelayedResumeOverlay : ResumeOverlay { From feaf59e15f701e1348122e17c4650420b8a581ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 19 Mar 2024 18:19:06 +0100 Subject: [PATCH 3499/4092] Use `HashSet` instead of `SortedSet` No need for it to be sorted. --- .../NonVisual/Filtering/FilterQueryParserTest.cs | 16 ++++++++-------- osu.Game/Screens/Select/FilterCriteria.cs | 12 +++--------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 98d3286409..bd706b5b4c 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -257,7 +257,7 @@ namespace osu.Game.Tests.NonVisual.Filtering var filterCriteria = new FilterCriteria(); FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.IsNotEmpty(filterCriteria.OnlineStatus.Values); - Assert.Contains(BeatmapOnlineStatus.Ranked, filterCriteria.OnlineStatus.Values); + Assert.That(filterCriteria.OnlineStatus.Values, Contains.Item(BeatmapOnlineStatus.Ranked)); } [Test] @@ -269,7 +269,7 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.AreEqual("I want the pp", filterCriteria.SearchText.Trim()); Assert.AreEqual(4, filterCriteria.SearchTerms.Length); Assert.IsNotEmpty(filterCriteria.OnlineStatus.Values); - Assert.Contains(BeatmapOnlineStatus.Ranked, filterCriteria.OnlineStatus.Values); + Assert.That(filterCriteria.OnlineStatus.Values, Contains.Item(BeatmapOnlineStatus.Ranked)); } [Test] @@ -279,8 +279,8 @@ namespace osu.Game.Tests.NonVisual.Filtering var filterCriteria = new FilterCriteria(); FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.IsNotEmpty(filterCriteria.OnlineStatus.Values); - Assert.Contains(BeatmapOnlineStatus.Ranked, filterCriteria.OnlineStatus.Values); - Assert.Contains(BeatmapOnlineStatus.Loved, filterCriteria.OnlineStatus.Values); + Assert.That(filterCriteria.OnlineStatus.Values, Contains.Item(BeatmapOnlineStatus.Ranked)); + Assert.That(filterCriteria.OnlineStatus.Values, Contains.Item(BeatmapOnlineStatus.Loved)); } [Test] @@ -290,10 +290,10 @@ namespace osu.Game.Tests.NonVisual.Filtering var filterCriteria = new FilterCriteria(); FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.IsNotEmpty(filterCriteria.OnlineStatus.Values); - Assert.Contains(BeatmapOnlineStatus.Ranked, filterCriteria.OnlineStatus.Values); - Assert.Contains(BeatmapOnlineStatus.Approved, filterCriteria.OnlineStatus.Values); - Assert.Contains(BeatmapOnlineStatus.Qualified, filterCriteria.OnlineStatus.Values); - Assert.Contains(BeatmapOnlineStatus.Loved, filterCriteria.OnlineStatus.Values); + Assert.That(filterCriteria.OnlineStatus.Values, Contains.Item(BeatmapOnlineStatus.Ranked)); + Assert.That(filterCriteria.OnlineStatus.Values, Contains.Item(BeatmapOnlineStatus.Approved)); + Assert.That(filterCriteria.OnlineStatus.Values, Contains.Item(BeatmapOnlineStatus.Qualified)); + Assert.That(filterCriteria.OnlineStatus.Values, Contains.Item(BeatmapOnlineStatus.Loved)); } [TestCase("creator")] diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 1d155574cf..6750b07aba 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -119,21 +119,15 @@ namespace osu.Game.Screens.Select { public bool HasFilter => Values.Count > 0; - public bool IsInRange(T value) - { - return Values.Contains(value); - } + public bool IsInRange(T value) => Values.Contains(value); - public SortedSet Values = new SortedSet(); + public HashSet Values = new HashSet(); public OptionalSet() { } - public bool Equals(OptionalSet other) - { - return Values.SetEquals(other.Values); - } + public bool Equals(OptionalSet other) => Values.SetEquals(other.Values); } public struct OptionalRange : IEquatable> From 320373e3e0a50c5b94a0f5a68a108ce24b3da2e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 19 Mar 2024 18:28:13 +0100 Subject: [PATCH 3500/4092] Rename method to match convention better --- osu.Game/Screens/Select/FilterQueryParser.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 76d58963e7..194a288426 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -69,7 +69,7 @@ namespace osu.Game.Screens.Select return TryUpdateCriteriaRange(ref criteria.BeatDivisor, op, value, tryParseInt); case "status": - return TryUpdateSetRange(ref criteria.OnlineStatus, op, value, tryParseEnum); + return TryUpdateCriteriaSet(ref criteria.OnlineStatus, op, value, tryParseEnum); case "creator": case "author": @@ -310,11 +310,11 @@ namespace osu.Game.Screens.Select /// The operator for the keyword filter. /// The value of the keyword filter. /// Function used to determine if can be converted to type . - public static bool TryUpdateSetRange(ref FilterCriteria.OptionalSet range, Operator op, string val, TryParseFunction parseFunction) + public static bool TryUpdateCriteriaSet(ref FilterCriteria.OptionalSet range, Operator op, string val, TryParseFunction parseFunction) where T : struct - => parseFunction.Invoke(val, out var converted) && tryUpdateSetRange(ref range, op, converted); + => parseFunction.Invoke(val, out var converted) && tryUpdateCriteriaSet(ref range, op, converted); - private static bool tryUpdateSetRange(ref FilterCriteria.OptionalSet range, Operator op, T value) + private static bool tryUpdateCriteriaSet(ref FilterCriteria.OptionalSet range, Operator op, T value) where T : struct { var enumValues = (T[])Enum.GetValues(typeof(T)); From af3f7dcbbfb3e5b955f45e56815238eae71ccbdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 19 Mar 2024 18:31:07 +0100 Subject: [PATCH 3501/4092] Retouch update criteria method --- osu.Game/Screens/Select/FilterQueryParser.cs | 30 ++++++++------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 194a288426..32b533e18d 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -311,44 +311,38 @@ namespace osu.Game.Screens.Select /// The value of the keyword filter. /// Function used to determine if can be converted to type . public static bool TryUpdateCriteriaSet(ref FilterCriteria.OptionalSet range, Operator op, string val, TryParseFunction parseFunction) - where T : struct + where T : struct, Enum => parseFunction.Invoke(val, out var converted) && tryUpdateCriteriaSet(ref range, op, converted); - private static bool tryUpdateCriteriaSet(ref FilterCriteria.OptionalSet range, Operator op, T value) - where T : struct + private static bool tryUpdateCriteriaSet(ref FilterCriteria.OptionalSet range, Operator op, T pivotValue) + where T : struct, Enum { - var enumValues = (T[])Enum.GetValues(typeof(T)); + var allDefinedValues = Enum.GetValues(); - foreach (var enumValue in enumValues) + foreach (var val in allDefinedValues) { + int compareResult = Comparer.Default.Compare(val, pivotValue); + switch (op) { case Operator.Less: - if (Comparer.Default.Compare(enumValue, value) < 0) - range.Values.Add(enumValue); - + if (compareResult < 0) range.Values.Add(val); break; case Operator.LessOrEqual: - if (Comparer.Default.Compare(enumValue, value) <= 0) - range.Values.Add(enumValue); - + if (compareResult <= 0) range.Values.Add(val); break; case Operator.Equal: - range.Values.Add(value); + if (compareResult == 0) range.Values.Add(val); break; case Operator.GreaterOrEqual: - if (Comparer.Default.Compare(enumValue, value) >= 0) - range.Values.Add(enumValue); - + if (compareResult >= 0) range.Values.Add(val); break; case Operator.Greater: - if (Comparer.Default.Compare(enumValue, value) > 0) - range.Values.Add(enumValue); - + if (compareResult > 0) range.Values.Add(val); break; default: From 0211ae12adbed9b0d37881fbc87774ca901a0953 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 19 Mar 2024 19:16:33 +0100 Subject: [PATCH 3502/4092] Add failing test case for crash on empty beatmap --- .../Editing/Checks/CheckUnusedAudioAtEndTest.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game.Tests/Editing/Checks/CheckUnusedAudioAtEndTest.cs b/osu.Game.Tests/Editing/Checks/CheckUnusedAudioAtEndTest.cs index 33d73a8086..bf996b06ea 100644 --- a/osu.Game.Tests/Editing/Checks/CheckUnusedAudioAtEndTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckUnusedAudioAtEndTest.cs @@ -55,6 +55,16 @@ namespace osu.Game.Tests.Editing.Checks }; } + [Test] + public void TestEmptyBeatmap() + { + var context = getContext(new Beatmap()); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckUnusedAudioAtEnd.IssueTemplateUnusedAudioAtEnd); + } + [Test] public void TestAudioNotFullyUsed() { From 2b83e6bc4cc325375f1db483e84af25f73330087 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 19 Mar 2024 19:17:22 +0100 Subject: [PATCH 3503/4092] Fix check crash on empty beatmap --- osu.Game/Rulesets/Edit/Checks/CheckUnusedAudioAtEnd.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckUnusedAudioAtEnd.cs b/osu.Game/Rulesets/Edit/Checks/CheckUnusedAudioAtEnd.cs index 2f768b6ffa..2e97fbeb99 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckUnusedAudioAtEnd.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckUnusedAudioAtEnd.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit.Checks.Components; using osu.Game.Storyboards; @@ -21,7 +22,7 @@ namespace osu.Game.Rulesets.Edit.Checks public IEnumerable Run(BeatmapVerifierContext context) { - double mappedLength = context.Beatmap.GetLastObjectTime(); + double mappedLength = context.Beatmap.HitObjects.Any() ? context.Beatmap.GetLastObjectTime() : 0; double trackLength = context.WorkingBeatmap.Track.Length; double mappedPercentage = Math.Round(mappedLength / trackLength * 100); From e4418547feba011e5461d2a9d5358d198d341427 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 19 Mar 2024 19:19:07 +0100 Subject: [PATCH 3504/4092] Document `GetLastObjectTime()` exception on empty beatmap --- osu.Game/Beatmaps/IBeatmap.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index b5bb6ccafc..6fe494ca0f 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.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 System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps.ControlPoints; @@ -129,6 +130,7 @@ namespace osu.Game.Beatmaps /// /// It's not super efficient so calls should be kept to a minimum. /// + /// If has no objects. public static double GetLastObjectTime(this IBeatmap beatmap) => beatmap.HitObjects.Max(h => h.GetEndTime()); #region Helper methods From 808d6e09436468c8690311f235852ed0dd7834c9 Mon Sep 17 00:00:00 2001 From: jvyden Date: Tue, 19 Mar 2024 16:02:06 -0400 Subject: [PATCH 3505/4092] Prevent potential threading issues --- osu.Desktop/DiscordRichPresence.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index ca26cab0fd..080032a298 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -97,11 +97,13 @@ namespace osu.Desktop private void onReady(object _, ReadyMessage __) { Logger.Log("Discord RPC Client ready.", LoggingTarget.Network, LogLevel.Debug); - updateStatus(); + Schedule(updateStatus); } private void updateStatus() { + Debug.Assert(ThreadSafety.IsUpdateThread); + if (!client.IsInitialized) return; From 0ecfa580d7a48d247ab9fa9907fdd0f1d739d732 Mon Sep 17 00:00:00 2001 From: jvyden Date: Tue, 19 Mar 2024 16:03:32 -0400 Subject: [PATCH 3506/4092] Move room code from activity code, prevent duplicate RPC updates --- osu.Desktop/DiscordRichPresence.cs | 68 +++++++++++++++++------------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 080032a298..633b6324b7 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -2,12 +2,14 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.Text; using DiscordRPC; using DiscordRPC.Message; using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game; @@ -48,6 +50,8 @@ namespace osu.Desktop private readonly Bindable privacyMode = new Bindable(); + private int usersCurrentlyInLobby = 0; + private readonly RichPresence presence = new RichPresence { Assets = new Assets { LargeImageKey = "osu_logo_lazer" }, @@ -115,10 +119,10 @@ namespace osu.Desktop Logger.Log("Updating Discord RPC", LoggingTarget.Network, LogLevel.Debug); + bool hideIdentifiableInformation = privacyMode.Value == DiscordRichPresenceMode.Limited || status.Value == UserStatus.DoNotDisturb; + if (activity.Value != null) { - bool hideIdentifiableInformation = privacyMode.Value == DiscordRichPresenceMode.Limited || status.Value == UserStatus.DoNotDisturb; - presence.State = truncate(activity.Value.GetStatus(hideIdentifiableInformation)); presence.Details = truncate(activity.Value.GetDetails(hideIdentifiableInformation) ?? string.Empty); @@ -137,33 +141,6 @@ namespace osu.Desktop { presence.Buttons = null; } - - if (!hideIdentifiableInformation && multiplayerClient.Room != null) - { - MultiplayerRoom room = multiplayerClient.Room; - presence.Party = new Party - { - Privacy = string.IsNullOrEmpty(room.Settings.Password) ? Party.PrivacySetting.Public : Party.PrivacySetting.Private, - ID = room.RoomID.ToString(), - // technically lobbies can have infinite users, but Discord needs this to be set to something. - // to make party display sensible, assign a powers of two above participants count (8 at minimum). - Max = (int)Math.Max(8, Math.Pow(2, Math.Ceiling(Math.Log2(room.Users.Count)))), - Size = room.Users.Count, - }; - - RoomSecret roomSecret = new RoomSecret - { - RoomID = room.RoomID, - Password = room.Settings.Password, - }; - - presence.Secrets.JoinSecret = JsonConvert.SerializeObject(roomSecret); - } - else - { - presence.Party = null; - presence.Secrets.JoinSecret = null; - } } else { @@ -171,6 +148,39 @@ namespace osu.Desktop presence.Details = string.Empty; } + if (!hideIdentifiableInformation && multiplayerClient.Room != null) + { + MultiplayerRoom room = multiplayerClient.Room; + + if (room.Users.Count == usersCurrentlyInLobby) + return; + + presence.Party = new Party + { + Privacy = string.IsNullOrEmpty(room.Settings.Password) ? Party.PrivacySetting.Public : Party.PrivacySetting.Private, + ID = room.RoomID.ToString(), + // technically lobbies can have infinite users, but Discord needs this to be set to something. + // to make party display sensible, assign a powers of two above participants count (8 at minimum). + Max = (int)Math.Max(8, Math.Pow(2, Math.Ceiling(Math.Log2(room.Users.Count)))), + Size = room.Users.Count, + }; + + RoomSecret roomSecret = new RoomSecret + { + RoomID = room.RoomID, + Password = room.Settings.Password, + }; + + presence.Secrets.JoinSecret = JsonConvert.SerializeObject(roomSecret); + usersCurrentlyInLobby = room.Users.Count; + } + else + { + presence.Party = null; + presence.Secrets.JoinSecret = null; + usersCurrentlyInLobby = 0; + } + // update user information if (privacyMode.Value == DiscordRichPresenceMode.Limited) presence.Assets.LargeImageText = string.Empty; From c71daba4f663164a8180561bbadc8e0ee6f78a46 Mon Sep 17 00:00:00 2001 From: jvyden Date: Tue, 19 Mar 2024 16:05:13 -0400 Subject: [PATCH 3507/4092] Improve logging of RPC Co-authored-by: Salman Ahmed --- osu.Desktop/DiscordRichPresence.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 633b6324b7..f47b2eaba5 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -117,8 +117,6 @@ namespace osu.Desktop return; } - Logger.Log("Updating Discord RPC", LoggingTarget.Network, LogLevel.Debug); - bool hideIdentifiableInformation = privacyMode.Value == DiscordRichPresenceMode.Limited || status.Value == UserStatus.DoNotDisturb; if (activity.Value != null) @@ -181,6 +179,8 @@ namespace osu.Desktop usersCurrentlyInLobby = 0; } + Logger.Log($"Updating Discord RPC presence with activity status: {presence.State}, details: {presence.Details}", LoggingTarget.Network, LogLevel.Debug); + // update user information if (privacyMode.Value == DiscordRichPresenceMode.Limited) presence.Assets.LargeImageText = string.Empty; From 4305c3db5b70b70d1e28ba1deeb807b28742481e Mon Sep 17 00:00:00 2001 From: jvyden Date: Tue, 19 Mar 2024 16:15:22 -0400 Subject: [PATCH 3508/4092] Show login overlay when joining room while not logged in --- osu.Desktop/DiscordRichPresence.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index f47b2eaba5..a2d7ace0e0 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -19,6 +19,7 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; +using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Users; using LogLevel = osu.Framework.Logging.LogLevel; @@ -42,6 +43,8 @@ namespace osu.Desktop [Resolved] private OsuGame game { get; set; } = null!; + private LoginOverlay? login { get; set; } + [Resolved] private MultiplayerClient multiplayerClient { get; set; } = null!; @@ -65,6 +68,8 @@ namespace osu.Desktop [BackgroundDependencyLoader] private void load(OsuConfigManager config) { + login = game.Dependencies.Get(); + client = new DiscordRpcClient(client_id) { SkipIdenticalPresence = false // handles better on discord IPC loss, see updateStatus call in onReady. @@ -203,6 +208,12 @@ namespace osu.Desktop { game.Window?.Raise(); + if (!api.IsLoggedIn) + { + Schedule(() => login?.Show()); + return; + } + Logger.Log($"Received room secret from Discord RPC Client: \"{args.Secret}\"", LoggingTarget.Network, LogLevel.Debug); // Stable and lazer share the same Discord client ID, meaning they can accept join requests from each other. From 1a08dbaa2ba929c26e3463163f3c8ac9523809c5 Mon Sep 17 00:00:00 2001 From: jvyden Date: Tue, 19 Mar 2024 17:03:30 -0400 Subject: [PATCH 3509/4092] Fix code style --- osu.Desktop/DiscordRichPresence.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index a2d7ace0e0..d8013aabfe 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -53,7 +53,7 @@ namespace osu.Desktop private readonly Bindable privacyMode = new Bindable(); - private int usersCurrentlyInLobby = 0; + private int usersCurrentlyInLobby; private readonly RichPresence presence = new RichPresence { From d83a53fc944eea90ad8244e248ec762e6d6d6ad3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Mar 2024 12:10:05 +0800 Subject: [PATCH 3510/4092] Remove unused `ScreenBreadcrumbControl` See https://github.com/ppy/osu-framework/pull/6218#discussion_r1529932798. --- .../TestSceneScreenBreadcrumbControl.cs | 138 ------------------ .../UserInterface/ScreenBreadcrumbControl.cs | 48 ------ 2 files changed, 186 deletions(-) delete mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneScreenBreadcrumbControl.cs delete mode 100644 osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneScreenBreadcrumbControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneScreenBreadcrumbControl.cs deleted file mode 100644 index 968cf9f9db..0000000000 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneScreenBreadcrumbControl.cs +++ /dev/null @@ -1,138 +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 NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Screens; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Graphics.UserInterfaceV2; -using osu.Game.Screens; -using osuTK; - -namespace osu.Game.Tests.Visual.UserInterface -{ - [TestFixture] - public partial class TestSceneScreenBreadcrumbControl : OsuTestScene - { - private readonly ScreenBreadcrumbControl breadcrumbs; - private readonly OsuScreenStack screenStack; - - public TestSceneScreenBreadcrumbControl() - { - OsuSpriteText titleText; - - IScreen startScreen = new TestScreenOne(); - - screenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }; - screenStack.Push(startScreen); - - Children = new Drawable[] - { - screenStack, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(10), - Children = new Drawable[] - { - breadcrumbs = new ScreenBreadcrumbControl(screenStack) - { - RelativeSizeAxes = Axes.X, - }, - titleText = new OsuSpriteText(), - }, - }, - }; - - breadcrumbs.Current.ValueChanged += screen => titleText.Text = $"Changed to {screen.NewValue}"; - breadcrumbs.Current.TriggerChange(); - - waitForCurrent(); - pushNext(); - waitForCurrent(); - pushNext(); - waitForCurrent(); - - AddStep(@"make start current", () => startScreen.MakeCurrent()); - - waitForCurrent(); - pushNext(); - waitForCurrent(); - AddAssert(@"only 2 items", () => breadcrumbs.Items.Count == 2); - AddStep(@"exit current", () => screenStack.CurrentScreen.Exit()); - AddAssert(@"current screen is first", () => startScreen == screenStack.CurrentScreen); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - breadcrumbs.StripColour = colours.Blue; - } - - private void pushNext() => AddStep(@"push next screen", () => ((TestScreen)screenStack.CurrentScreen).PushNext()); - private void waitForCurrent() => AddUntilStep("current screen", () => screenStack.CurrentScreen.IsCurrentScreen()); - - private abstract partial class TestScreen : OsuScreen - { - protected abstract string NextTitle { get; } - protected abstract TestScreen CreateNextScreen(); - - public TestScreen PushNext() - { - TestScreen screen = CreateNextScreen(); - this.Push(screen); - - return screen; - } - - protected TestScreen() - { - InternalChild = new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(10), - Children = new Drawable[] - { - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = Title, - }, - new RoundedButton - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Width = 100, - Text = $"Push {NextTitle}", - Action = () => PushNext(), - }, - }, - }; - } - } - - private partial class TestScreenOne : TestScreen - { - public override string Title => @"Screen One"; - protected override string NextTitle => @"Two"; - protected override TestScreen CreateNextScreen() => new TestScreenTwo(); - } - - private partial class TestScreenTwo : TestScreen - { - public override string Title => @"Screen Two"; - protected override string NextTitle => @"One"; - protected override TestScreen CreateNextScreen() => new TestScreenOne(); - } - } -} diff --git a/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs deleted file mode 100644 index 65dce422d6..0000000000 --- a/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs +++ /dev/null @@ -1,48 +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 System.Linq; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Screens; - -namespace osu.Game.Graphics.UserInterface -{ - /// - /// A which follows the active screen (and allows navigation) in a stack. - /// - public partial class ScreenBreadcrumbControl : BreadcrumbControl - { - public ScreenBreadcrumbControl(ScreenStack stack) - { - stack.ScreenPushed += onPushed; - stack.ScreenExited += onExited; - - if (stack.CurrentScreen != null) - onPushed(null, stack.CurrentScreen); - } - - protected override void SelectTab(TabItem tab) - { - // override base method to prevent current item from being changed on click. - // depend on screen push/exit to change current item instead. - tab.Value.MakeCurrent(); - } - - private void onPushed(IScreen lastScreen, IScreen newScreen) - { - AddItem(newScreen); - Current.Value = newScreen; - } - - private void onExited(IScreen lastScreen, IScreen newScreen) - { - if (newScreen != null) - Current.Value = newScreen; - - Items.ToList().SkipWhile(s => s != Current.Value).Skip(1).ForEach(RemoveItem); - } - } -} From b11ae1c5714b43d33ebbec569faf6569d0304c25 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 20 Mar 2024 06:40:18 +0300 Subject: [PATCH 3511/4092] Organise code, still hook up to `RoomChanged` to update room privacy mode, and use `SkipIdenticalPresence` + scheduling to avoid potential rate-limits --- osu.Desktop/DiscordRichPresence.cs | 87 ++++++++++++++++++------------ 1 file changed, 54 insertions(+), 33 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index d8013aabfe..8e4af5c5b1 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -2,16 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using System.Text; using DiscordRPC; using DiscordRPC.Message; using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Development; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Logging; +using osu.Framework.Threading; using osu.Game; using osu.Game.Configuration; using osu.Game.Extensions; @@ -53,8 +53,6 @@ namespace osu.Desktop private readonly Bindable privacyMode = new Bindable(); - private int usersCurrentlyInLobby; - private readonly RichPresence presence = new RichPresence { Assets = new Assets { LargeImageKey = "osu_logo_lazer" }, @@ -72,7 +70,9 @@ namespace osu.Desktop client = new DiscordRpcClient(client_id) { - SkipIdenticalPresence = false // handles better on discord IPC loss, see updateStatus call in onReady. + // SkipIdenticalPresence allows us to fire SetPresence at any point and leave it to the underlying implementation + // to check whether a difference has actually occurred before sending a command to Discord (with a minor caveat that's handled in onReady). + SkipIdenticalPresence = true }; client.OnReady += onReady; @@ -95,10 +95,11 @@ namespace osu.Desktop activity.BindTo(u.NewValue.Activity); }, true); - ruleset.BindValueChanged(_ => updateStatus()); - status.BindValueChanged(_ => updateStatus()); - activity.BindValueChanged(_ => updateStatus()); - privacyMode.BindValueChanged(_ => updateStatus()); + ruleset.BindValueChanged(_ => updatePresence()); + status.BindValueChanged(_ => updatePresence()); + activity.BindValueChanged(_ => updatePresence()); + privacyMode.BindValueChanged(_ => updatePresence()); + multiplayerClient.RoomUpdated += onRoomUpdated; client.Initialize(); } @@ -106,24 +107,44 @@ namespace osu.Desktop private void onReady(object _, ReadyMessage __) { Logger.Log("Discord RPC Client ready.", LoggingTarget.Network, LogLevel.Debug); - Schedule(updateStatus); + + // when RPC is lost and reconnected, we have to clear presence state for updatePresence to work (see DiscordRpcClient.SkipIdenticalPresence). + if (client.CurrentPresence != null) + client.SetPresence(null); + + updatePresence(); } - private void updateStatus() + private void onRoomUpdated() => updatePresence(); + + private ScheduledDelegate? presenceUpdateDelegate; + + private void updatePresence() { - Debug.Assert(ThreadSafety.IsUpdateThread); - - if (!client.IsInitialized) - return; - - if (status.Value == UserStatus.Offline || privacyMode.Value == DiscordRichPresenceMode.Off) + presenceUpdateDelegate?.Cancel(); + presenceUpdateDelegate = Scheduler.AddDelayed(() => { - client.ClearPresence(); - return; - } + if (!client.IsInitialized) + return; - bool hideIdentifiableInformation = privacyMode.Value == DiscordRichPresenceMode.Limited || status.Value == UserStatus.DoNotDisturb; + if (status.Value == UserStatus.Offline || privacyMode.Value == DiscordRichPresenceMode.Off) + { + client.ClearPresence(); + return; + } + bool hideIdentifiableInformation = privacyMode.Value == DiscordRichPresenceMode.Limited || status.Value == UserStatus.DoNotDisturb; + + updatePresenceStatus(hideIdentifiableInformation); + updatePresenceParty(hideIdentifiableInformation); + updatePresenceAssets(); + + client.SetPresence(presence); + }, 200); + } + + private void updatePresenceStatus(bool hideIdentifiableInformation) + { if (activity.Value != null) { presence.State = truncate(activity.Value.GetStatus(hideIdentifiableInformation)); @@ -150,14 +171,14 @@ namespace osu.Desktop presence.State = "Idle"; presence.Details = string.Empty; } + } + private void updatePresenceParty(bool hideIdentifiableInformation) + { if (!hideIdentifiableInformation && multiplayerClient.Room != null) { MultiplayerRoom room = multiplayerClient.Room; - if (room.Users.Count == usersCurrentlyInLobby) - return; - presence.Party = new Party { Privacy = string.IsNullOrEmpty(room.Settings.Password) ? Party.PrivacySetting.Public : Party.PrivacySetting.Private, @@ -175,17 +196,16 @@ namespace osu.Desktop }; presence.Secrets.JoinSecret = JsonConvert.SerializeObject(roomSecret); - usersCurrentlyInLobby = room.Users.Count; } else { presence.Party = null; presence.Secrets.JoinSecret = null; - usersCurrentlyInLobby = 0; } + } - Logger.Log($"Updating Discord RPC presence with activity status: {presence.State}, details: {presence.Details}", LoggingTarget.Network, LogLevel.Debug); - + private void updatePresenceAssets() + { // update user information if (privacyMode.Value == DiscordRichPresenceMode.Limited) presence.Assets.LargeImageText = string.Empty; @@ -200,17 +220,15 @@ namespace osu.Desktop // update ruleset presence.Assets.SmallImageKey = ruleset.Value.IsLegacyRuleset() ? $"mode_{ruleset.Value.OnlineID}" : "mode_custom"; presence.Assets.SmallImageText = ruleset.Value.Name; - - client.SetPresence(presence); } - private void onJoin(object sender, JoinMessage args) + private void onJoin(object sender, JoinMessage args) => Scheduler.AddOnce(() => { game.Window?.Raise(); if (!api.IsLoggedIn) { - Schedule(() => login?.Show()); + login?.Show(); return; } @@ -231,7 +249,7 @@ namespace osu.Desktop }); request.Failure += _ => Logger.Log($"Could not join multiplayer room, room could not be found (room ID: {roomId}).", LoggingTarget.Network, LogLevel.Important); api.Queue(request); - } + }); private static readonly int ellipsis_length = Encoding.UTF8.GetByteCount(new[] { '…' }); @@ -294,6 +312,9 @@ namespace osu.Desktop protected override void Dispose(bool isDisposing) { + if (multiplayerClient.IsNotNull()) + multiplayerClient.RoomUpdated -= onRoomUpdated; + client.Dispose(); base.Dispose(isDisposing); } From 5f86b5a2fa2b109ec63d283a84356dc46efb67ac Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 20 Mar 2024 07:36:15 +0300 Subject: [PATCH 3512/4092] Use DI correctly --- osu.Desktop/DiscordRichPresence.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 8e4af5c5b1..d78459ff28 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -43,6 +43,7 @@ namespace osu.Desktop [Resolved] private OsuGame game { get; set; } = null!; + [Resolved] private LoginOverlay? login { get; set; } [Resolved] @@ -66,8 +67,6 @@ namespace osu.Desktop [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - login = game.Dependencies.Get(); - client = new DiscordRpcClient(client_id) { // SkipIdenticalPresence allows us to fire SetPresence at any point and leave it to the underlying implementation From fd509c82f50a59293fee0978769bcaa02c28f852 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Mar 2024 12:52:54 +0800 Subject: [PATCH 3513/4092] Adjust code structure slightly --- .../Timeline/TimelineControlPointDisplay.cs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs index 8e522fa715..116a3ee105 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs @@ -32,10 +32,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline controlPointGroups.UnbindAll(); controlPointGroups.BindTo(beatmap.ControlPointInfo.Groups); - controlPointGroups.BindCollectionChanged((_, _) => - { - invalidateGroups(); - }, true); + controlPointGroups.BindCollectionChanged((_, _) => groupCache.Invalidate(), true); } protected override void Update() @@ -51,19 +48,20 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (visibleRange != newRange) { visibleRange = newRange; - invalidateGroups(); + groupCache.Invalidate(); } if (!groupCache.IsValid) + { recreateDrawableGroups(); + groupCache.Validate(); + } } - private void invalidateGroups() => groupCache.Invalidate(); - private void recreateDrawableGroups() { // Remove groups outside the visible range - foreach (var drawableGroup in this) + foreach (TimelineControlPointGroup drawableGroup in this) { if (!shouldBeVisible(drawableGroup.Group)) drawableGroup.Expire(); @@ -93,8 +91,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Add(new TimelineControlPointGroup(group)); } - - groupCache.Validate(); } private bool shouldBeVisible(ControlPointGroup group) => group.Time >= visibleRange.min && group.Time <= visibleRange.max; From 1f343b75454c416b8c17d98428330096213c7fc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 20 Mar 2024 08:27:49 +0100 Subject: [PATCH 3514/4092] Add extended logging when discarding online metadata lookup results Related to: https://github.com/ppy/osu/issues/27674 Relevant log output for that particular case: [network] 2024-03-20 07:25:30 [verbose]: Performing request osu.Game.Online.API.Requests.GetBeatmapRequest [network] 2024-03-20 07:25:30 [verbose]: Request to https://dev.ppy.sh/api/v2/beatmaps/lookup successfully completed! [network] 2024-03-20 07:25:30 [verbose]: GetBeatmapRequest finished with response size of 3,170 bytes [database] 2024-03-20 07:25:30 [verbose]: [4fe02] [APIBeatmapMetadataSource] Online retrieval mapped Tsukiyama Sae - Hana Saku Iro wa Koi no Gotoshi (Log Off Now) [Destiny] to 744883 / 1613507. [database] 2024-03-20 07:25:30 [verbose]: Discarding metadata lookup result due to mismatching online ID (expected: 1570982 actual: 1613507) [network] 2024-03-20 07:25:30 [verbose]: Performing request osu.Game.Online.API.Requests.GetBeatmapRequest [network] 2024-03-20 07:25:30 [verbose]: Request to https://dev.ppy.sh/api/v2/beatmaps/lookup successfully completed! [network] 2024-03-20 07:25:30 [verbose]: GetBeatmapRequest finished with response size of 2,924 bytes [database] 2024-03-20 07:25:30 [verbose]: [4fe02] [APIBeatmapMetadataSource] Online retrieval mapped Tsukiyama Sae - Hana Saku Iro wa Koi no Gotoshi (Log Off Now) [Easy] to 744883 / 1570982. [database] 2024-03-20 07:25:30 [verbose]: Discarding metadata lookup result due to mismatching online ID (expected: 1613507 actual: 1570982) Note that the online IDs are swapped. --- osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs index f395718a93..034ec31ee4 100644 --- a/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs +++ b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Online.API; @@ -85,10 +86,16 @@ namespace osu.Game.Beatmaps private bool shouldDiscardLookupResult(OnlineBeatmapMetadata result, BeatmapInfo beatmapInfo) { if (beatmapInfo.OnlineID > 0 && result.BeatmapID != beatmapInfo.OnlineID) + { + Logger.Log($"Discarding metadata lookup result due to mismatching online ID (expected: {beatmapInfo.OnlineID} actual: {result.BeatmapID})", LoggingTarget.Database); return true; + } if (beatmapInfo.OnlineID == -1 && result.MD5Hash != beatmapInfo.MD5Hash) + { + Logger.Log($"Discarding metadata lookup result due to mismatching hash (expected: {beatmapInfo.MD5Hash} actual: {result.MD5Hash})", LoggingTarget.Database); return true; + } return false; } From c78e203df5434495a79589559b973917a4088a3e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 20 Mar 2024 17:30:34 +0900 Subject: [PATCH 3515/4092] Fix infinite health processor loop when no top-level objects This is unlikely to occur in actual gameplay, but occurs in the follow-up test. --- osu.Game/Rulesets/Scoring/LegacyDrainingHealthProcessor.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Rulesets/Scoring/LegacyDrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/LegacyDrainingHealthProcessor.cs index 2bc3ea80ec..7cee5ebecf 100644 --- a/osu.Game/Rulesets/Scoring/LegacyDrainingHealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/LegacyDrainingHealthProcessor.cs @@ -108,6 +108,9 @@ namespace osu.Game.Rulesets.Scoring increaseHp(h); } + if (topLevelObjectCount == 0) + return testDrop; + if (!fail && currentHp < lowestHpEnd) { fail = true; From 66ace02e5872bd900acdaf7682a9178eea7dc168 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 20 Mar 2024 17:31:11 +0900 Subject: [PATCH 3516/4092] Add test for banana shower fail --- osu.Game.Rulesets.Catch.Tests/CatchHealthProcessorTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Catch.Tests/CatchHealthProcessorTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchHealthProcessorTest.cs index d0a8ce4bbc..1b46be01fb 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchHealthProcessorTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchHealthProcessorTest.cs @@ -19,6 +19,7 @@ namespace osu.Game.Rulesets.Catch.Tests [new Droplet(), 0.01, true], [new TinyDroplet(), 0, false], [new Banana(), 0, false], + [new BananaShower(), 0, false] ]; [TestCaseSource(nameof(test_cases))] From bf5640049a73e2eb01c5cc02814aaea43d7c00e5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 20 Mar 2024 17:31:31 +0900 Subject: [PATCH 3517/4092] Fix banana showers causing fails when hp is at 0 --- osu.Game.Rulesets.Catch/Scoring/CatchHealthProcessor.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchHealthProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchHealthProcessor.cs index 2f55f9a85f..b2509091fe 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchHealthProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchHealthProcessor.cs @@ -32,6 +32,10 @@ namespace osu.Game.Rulesets.Catch.Scoring if (result.Type == HitResult.SmallTickMiss) return false; + // on stable, banana showers don't exist as concrete objects themselves, so they can't cause a fail. + if (result.HitObject is BananaShower) + return false; + return base.CheckDefaultFailCondition(result); } From 4904c49c38b418f3a151c34c114be96983bb03b7 Mon Sep 17 00:00:00 2001 From: Arthur Araujo Date: Wed, 20 Mar 2024 09:31:58 -0300 Subject: [PATCH 3518/4092] Add abnormal difficulty settings checks --- .../CheckAbnormalDifficultySettingsTest.cs | 93 +++++++++++++++++++ osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 5 +- .../Checks/CheckAbnormalDifficultySettings.cs | 82 ++++++++++++++++ 3 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Editing/Checks/CheckAbnormalDifficultySettingsTest.cs create mode 100644 osu.Game/Rulesets/Edit/Checks/CheckAbnormalDifficultySettings.cs diff --git a/osu.Game.Tests/Editing/Checks/CheckAbnormalDifficultySettingsTest.cs b/osu.Game.Tests/Editing/Checks/CheckAbnormalDifficultySettingsTest.cs new file mode 100644 index 0000000000..94305755c4 --- /dev/null +++ b/osu.Game.Tests/Editing/Checks/CheckAbnormalDifficultySettingsTest.cs @@ -0,0 +1,93 @@ +// 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.Edit.Checks; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Edit; +using osu.Game.Tests.Beatmaps; +using System.Linq; + +namespace osu.Game.Tests.Editing.Checks +{ + [TestFixture] + public class CheckAbnormalDifficultySettingsTest + { + private CheckAbnormalDifficultySettings check = null!; + + private IBeatmap beatmap = new Beatmap(); + + [SetUp] + public void Setup() + { + check = new CheckAbnormalDifficultySettings(); + beatmap.Difficulty = new(); + } + + [Test] + public void TestSettingsNormal() + { + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(0)); + } + + [Test] + public void TestAllSettingsMoreThanOneDecimal() + { + beatmap.Difficulty = new() + { + ApproachRate = 5.55f, + OverallDifficulty = 7.7777f, + CircleSize = 4.444f, + DrainRate = 1.1111111111f, + }; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(4)); + } + + [Test] + public void TestAllSettingsLessThanZero() + { + beatmap.Difficulty = new() + { + ApproachRate = -1, + OverallDifficulty = -20, + CircleSize = -11, + DrainRate = -34, + }; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(4)); + } + + [Test] + public void TestAllSettingsHigherThanTen() + { + beatmap.Difficulty = new() + { + ApproachRate = 14, + OverallDifficulty = 24, + CircleSize = 30, + DrainRate = 90, + }; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(4)); + } + + private BeatmapVerifierContext getContext() + { + return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); + } + } +} diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index dcf5eb4da9..edabe941b7 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -41,7 +41,10 @@ namespace osu.Game.Rulesets.Edit new CheckPreviewTime(), // Events - new CheckBreaks() + new CheckBreaks(), + + // Settings + new CheckAbnormalDifficultySettings(), }; public IEnumerable Run(BeatmapVerifierContext context) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckAbnormalDifficultySettings.cs b/osu.Game/Rulesets/Edit/Checks/CheckAbnormalDifficultySettings.cs new file mode 100644 index 0000000000..73049b323d --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checks/CheckAbnormalDifficultySettings.cs @@ -0,0 +1,82 @@ +// 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 osu.Game.Rulesets.Edit.Checks.Components; + + +namespace osu.Game.Rulesets.Edit.Checks +{ + public class CheckAbnormalDifficultySettings : ICheck + { + public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Settings, "Abnormal difficulty settings"); + + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplateMoreThanOneDecimal(this), + new IssueTemplateOutOfRange(this), + }; + + public IEnumerable Run(BeatmapVerifierContext context) + { + var diff = context.Beatmap.Difficulty; + + if (hasMoreThanOneDecimalPlace(diff.ApproachRate)) + yield return new IssueTemplateMoreThanOneDecimal(this).Create("Approach rate", diff.ApproachRate); + + if (isOutOfRange(diff.ApproachRate)) + yield return new IssueTemplateOutOfRange(this).Create("Approach rate", diff.ApproachRate); + + + if (hasMoreThanOneDecimalPlace(diff.OverallDifficulty)) + yield return new IssueTemplateMoreThanOneDecimal(this).Create("Overall difficulty", diff.OverallDifficulty); + + if (isOutOfRange(diff.OverallDifficulty)) + yield return new IssueTemplateOutOfRange(this).Create("Overall difficulty", diff.OverallDifficulty); + + + if (hasMoreThanOneDecimalPlace(diff.CircleSize)) + yield return new IssueTemplateMoreThanOneDecimal(this).Create("Circle size", diff.CircleSize); + + if (isOutOfRange(diff.CircleSize)) + yield return new IssueTemplateOutOfRange(this).Create("Circle size", diff.CircleSize); + + + if (hasMoreThanOneDecimalPlace(diff.DrainRate)) + yield return new IssueTemplateMoreThanOneDecimal(this).Create("Drain rate", diff.DrainRate); + + if (isOutOfRange(diff.DrainRate)) + yield return new IssueTemplateOutOfRange(this).Create("Drain rate", diff.DrainRate); + } + + private bool isOutOfRange(float setting) + { + return setting < 0f || setting > 10f; + } + + private bool hasMoreThanOneDecimalPlace(float setting) + { + return float.Round(setting, 1) != setting; + } + + public class IssueTemplateMoreThanOneDecimal : IssueTemplate + { + public IssueTemplateMoreThanOneDecimal(ICheck check) + : base(check, IssueType.Problem, "{0} {1} has more than one decimal place.") + { + } + + public Issue Create(string settingName, float settingValue) => new Issue(this, settingName, settingValue); + } + + public class IssueTemplateOutOfRange : IssueTemplate + { + public IssueTemplateOutOfRange(ICheck check) + : base(check, IssueType.Warning, "{0} is {1} although it is capped between 0 to 10 in-game.") + { + } + + public Issue Create(string settingName, float settingValue) => new Issue(this, settingName, settingValue); + } + } +} From ac7fca10d63e3f92db65572ea77ae0ab4bb58df6 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 21 Mar 2024 00:47:37 +0900 Subject: [PATCH 3519/4092] Warn about not using an official "deployed" build --- osu.Game/Localisation/NotificationsStrings.cs | 5 +++++ osu.Game/Updater/UpdateManager.cs | 6 ++++++ osu.Game/Utils/OfficialBuildAttribute.cs | 12 ++++++++++++ 3 files changed, 23 insertions(+) create mode 100644 osu.Game/Utils/OfficialBuildAttribute.cs diff --git a/osu.Game/Localisation/NotificationsStrings.cs b/osu.Game/Localisation/NotificationsStrings.cs index f4965e4ebe..5328bcd066 100644 --- a/osu.Game/Localisation/NotificationsStrings.cs +++ b/osu.Game/Localisation/NotificationsStrings.cs @@ -125,6 +125,11 @@ Click to see what's new!", version); /// public static LocalisableString UpdateReadyToInstall => new TranslatableString(getKey(@"update_ready_to_install"), @"Update ready to install. Click to restart!"); + /// + /// "This is not an official build of the game and scores will not be submitted." + /// + public static LocalisableString NotOfficialBuild => new TranslatableString(getKey(@"not_official_build"), @"This is not an official build of the game and scores will not be submitted."); + /// /// "Downloading update..." /// diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index 8f13e0f42a..bcb28d8b14 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -1,7 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Reflection; using System.Threading.Tasks; +using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -11,6 +13,7 @@ using osu.Game.Graphics; using osu.Game.Localisation; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; +using osu.Game.Utils; using osuTK; namespace osu.Game.Updater @@ -51,6 +54,9 @@ namespace osu.Game.Updater // only show a notification if we've previously saved a version to the config file (ie. not the first run). if (!string.IsNullOrEmpty(lastVersion)) Notifications.Post(new UpdateCompleteNotification(version)); + + if (RuntimeInfo.EntryAssembly.GetCustomAttribute() == null) + Notifications.Post(new SimpleNotification { Text = NotificationsStrings.NotOfficialBuild }); } // debug / local compilations will reset to a non-release string. diff --git a/osu.Game/Utils/OfficialBuildAttribute.cs b/osu.Game/Utils/OfficialBuildAttribute.cs new file mode 100644 index 0000000000..66c1ef5591 --- /dev/null +++ b/osu.Game/Utils/OfficialBuildAttribute.cs @@ -0,0 +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 System; +using JetBrains.Annotations; + +namespace osu.Game.Utils +{ + [UsedImplicitly] + [AttributeUsage(AttributeTargets.Assembly)] + public class OfficialBuildAttribute : Attribute; +} From c605e463a41388f43a0493d1246a813f1f7d4ab1 Mon Sep 17 00:00:00 2001 From: Arthur Araujo Date: Wed, 20 Mar 2024 15:52:16 -0300 Subject: [PATCH 3520/4092] Add mania keycount check --- .../Editor/Checks/CheckKeyCountTest.cs | 75 +++++++++++++++++++ .../Edit/Checks/CheckKeyCount.cs | 55 ++++++++++++++ .../Edit/ManiaBeatmapVerifier.cs | 24 ++++++ osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 + .../Checks/CheckAbnormalDifficultySettings.cs | 4 +- 5 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/Editor/Checks/CheckKeyCountTest.cs create mode 100644 osu.Game.Rulesets.Mania/Edit/Checks/CheckKeyCount.cs create mode 100644 osu.Game.Rulesets.Mania/Edit/ManiaBeatmapVerifier.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/Checks/CheckKeyCountTest.cs b/osu.Game.Rulesets.Mania.Tests/Editor/Checks/CheckKeyCountTest.cs new file mode 100644 index 0000000000..91361e2a62 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Editor/Checks/CheckKeyCountTest.cs @@ -0,0 +1,75 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Tests.Beatmaps; +using osu.Game.Rulesets.Mania.Edit.Checks; +using System.Linq; + +namespace osu.Game.Rulesets.Mania.Tests.Editor.Checks +{ + [TestFixture] + public class CheckKeyCountTest + { + private CheckKeyCount check = null!; + + private IBeatmap beatmap = null!; + + [SetUp] + public void Setup() + { + check = new CheckKeyCount(); + + beatmap = new Beatmap() + { + BeatmapInfo = new BeatmapInfo + { + Ruleset = new ManiaRuleset().RulesetInfo + } + }; + } + + [Test] + public void TestKeycountFour() + { + beatmap.Difficulty.CircleSize = 4; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(0)); + } + + [Test] + public void TestKeycountSmallerThanFour() + { + beatmap.Difficulty.CircleSize = 1; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckKeyCount.IssueTemplateKeycountTooLow); + } + + [Test] + public void TestKeycountHigherThanTen() + { + beatmap.Difficulty.CircleSize = 11; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckKeyCount.IssueTemplateKeycountNonStandard); + } + + private BeatmapVerifierContext getContext() + { + return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/Checks/CheckKeyCount.cs b/osu.Game.Rulesets.Mania/Edit/Checks/CheckKeyCount.cs new file mode 100644 index 0000000000..57991bd3ff --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/Checks/CheckKeyCount.cs @@ -0,0 +1,55 @@ +// 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 osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks.Components; + +namespace osu.Game.Rulesets.Mania.Edit.Checks +{ + public class CheckKeyCount : ICheck + { + public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Settings, "Check mania keycount."); + + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplateKeycountTooLow(this), + new IssueTemplateKeycountNonStandard(this), + }; + + public IEnumerable Run(BeatmapVerifierContext context) + { + var diff = context.Beatmap.Difficulty; + + if (diff.CircleSize < 4) + { + yield return new IssueTemplateKeycountTooLow(this).Create(diff.CircleSize); + } + + if (diff.CircleSize > 10) + { + yield return new IssueTemplateKeycountNonStandard(this).Create(diff.CircleSize); + } + } + + public class IssueTemplateKeycountTooLow : IssueTemplate + { + public IssueTemplateKeycountTooLow(ICheck check) + : base(check, IssueType.Problem, "Key count is {0} and must be 4 or higher.") + { + } + + public Issue Create(float current) => new Issue(this, current); + } + + public class IssueTemplateKeycountNonStandard : IssueTemplate + { + public IssueTemplateKeycountNonStandard(ICheck check) + : base(check, IssueType.Warning, "Key count {0} is higher than 10.") + { + } + + public Issue Create(float current) => new Issue(this, current); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBeatmapVerifier.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBeatmapVerifier.cs new file mode 100644 index 0000000000..778dcfc6e1 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBeatmapVerifier.cs @@ -0,0 +1,24 @@ +// 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 osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks.Components; +using osu.Game.Rulesets.Mania.Edit.Checks; + +namespace osu.Game.Rulesets.Mania.Edit +{ + public class ManiaBeatmapVerifier : IBeatmapVerifier + { + private readonly List checks = new List + { + new CheckKeyCount(), + }; + + public IEnumerable Run(BeatmapVerifierContext context) + { + return checks.SelectMany(check => check.Run(context)); + } + } +} diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 0b54fb3da0..3d4803f1e4 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -65,6 +65,8 @@ namespace osu.Game.Rulesets.Mania public override HitObjectComposer CreateHitObjectComposer() => new ManiaHitObjectComposer(this); + public override IBeatmapVerifier CreateBeatmapVerifier() => new ManiaBeatmapVerifier(); + public override ISkin? CreateSkinTransformer(ISkin skin, IBeatmap beatmap) { switch (skin) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckAbnormalDifficultySettings.cs b/osu.Game/Rulesets/Edit/Checks/CheckAbnormalDifficultySettings.cs index 73049b323d..3c454a57fe 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckAbnormalDifficultySettings.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckAbnormalDifficultySettings.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using osu.Game.Rulesets.Edit.Checks.Components; - namespace osu.Game.Rulesets.Edit.Checks { public class CheckAbnormalDifficultySettings : ICheck @@ -20,6 +19,7 @@ namespace osu.Game.Rulesets.Edit.Checks public IEnumerable Run(BeatmapVerifierContext context) { var diff = context.Beatmap.Difficulty; + string ruleset = context.Beatmap.BeatmapInfo.Ruleset.ShortName; if (hasMoreThanOneDecimalPlace(diff.ApproachRate)) yield return new IssueTemplateMoreThanOneDecimal(this).Create("Approach rate", diff.ApproachRate); @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Edit.Checks if (hasMoreThanOneDecimalPlace(diff.CircleSize)) yield return new IssueTemplateMoreThanOneDecimal(this).Create("Circle size", diff.CircleSize); - if (isOutOfRange(diff.CircleSize)) + if (ruleset != "mania" && isOutOfRange(diff.CircleSize)) yield return new IssueTemplateOutOfRange(this).Create("Circle size", diff.CircleSize); From 2d6a3b8e2b5e3f0023957868a0706f5df162e110 Mon Sep 17 00:00:00 2001 From: Arthur Araujo Date: Wed, 20 Mar 2024 16:51:27 -0300 Subject: [PATCH 3521/4092] Remove warning for 10K+ --- .../Editor/Checks/CheckKeyCountTest.cs | 12 ------------ .../Edit/Checks/CheckKeyCount.cs | 16 ---------------- 2 files changed, 28 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/Checks/CheckKeyCountTest.cs b/osu.Game.Rulesets.Mania.Tests/Editor/Checks/CheckKeyCountTest.cs index 91361e2a62..564c611548 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/Checks/CheckKeyCountTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/Checks/CheckKeyCountTest.cs @@ -55,18 +55,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor.Checks Assert.That(issues.Single().Template is CheckKeyCount.IssueTemplateKeycountTooLow); } - [Test] - public void TestKeycountHigherThanTen() - { - beatmap.Difficulty.CircleSize = 11; - - var context = getContext(); - var issues = check.Run(context).ToList(); - - Assert.That(issues, Has.Count.EqualTo(1)); - Assert.That(issues.Single().Template is CheckKeyCount.IssueTemplateKeycountNonStandard); - } - private BeatmapVerifierContext getContext() { return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); diff --git a/osu.Game.Rulesets.Mania/Edit/Checks/CheckKeyCount.cs b/osu.Game.Rulesets.Mania/Edit/Checks/CheckKeyCount.cs index 57991bd3ff..51ead5f423 100644 --- a/osu.Game.Rulesets.Mania/Edit/Checks/CheckKeyCount.cs +++ b/osu.Game.Rulesets.Mania/Edit/Checks/CheckKeyCount.cs @@ -14,7 +14,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Checks public IEnumerable PossibleTemplates => new IssueTemplate[] { new IssueTemplateKeycountTooLow(this), - new IssueTemplateKeycountNonStandard(this), }; public IEnumerable Run(BeatmapVerifierContext context) @@ -25,11 +24,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Checks { yield return new IssueTemplateKeycountTooLow(this).Create(diff.CircleSize); } - - if (diff.CircleSize > 10) - { - yield return new IssueTemplateKeycountNonStandard(this).Create(diff.CircleSize); - } } public class IssueTemplateKeycountTooLow : IssueTemplate @@ -41,15 +35,5 @@ namespace osu.Game.Rulesets.Mania.Edit.Checks public Issue Create(float current) => new Issue(this, current); } - - public class IssueTemplateKeycountNonStandard : IssueTemplate - { - public IssueTemplateKeycountNonStandard(ICheck check) - : base(check, IssueType.Warning, "Key count {0} is higher than 10.") - { - } - - public Issue Create(float current) => new Issue(this, current); - } } } From b132734f9c429da05b976544b266528c765019e6 Mon Sep 17 00:00:00 2001 From: Arthur Araujo Date: Wed, 20 Mar 2024 18:54:38 -0300 Subject: [PATCH 3522/4092] Fix codefactor issues --- .../Rulesets/Edit/Checks/CheckAbnormalDifficultySettings.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckAbnormalDifficultySettings.cs b/osu.Game/Rulesets/Edit/Checks/CheckAbnormalDifficultySettings.cs index 3c454a57fe..db154f4efc 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckAbnormalDifficultySettings.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckAbnormalDifficultySettings.cs @@ -27,21 +27,18 @@ namespace osu.Game.Rulesets.Edit.Checks if (isOutOfRange(diff.ApproachRate)) yield return new IssueTemplateOutOfRange(this).Create("Approach rate", diff.ApproachRate); - if (hasMoreThanOneDecimalPlace(diff.OverallDifficulty)) yield return new IssueTemplateMoreThanOneDecimal(this).Create("Overall difficulty", diff.OverallDifficulty); if (isOutOfRange(diff.OverallDifficulty)) yield return new IssueTemplateOutOfRange(this).Create("Overall difficulty", diff.OverallDifficulty); - if (hasMoreThanOneDecimalPlace(diff.CircleSize)) yield return new IssueTemplateMoreThanOneDecimal(this).Create("Circle size", diff.CircleSize); if (ruleset != "mania" && isOutOfRange(diff.CircleSize)) yield return new IssueTemplateOutOfRange(this).Create("Circle size", diff.CircleSize); - if (hasMoreThanOneDecimalPlace(diff.DrainRate)) yield return new IssueTemplateMoreThanOneDecimal(this).Create("Drain rate", diff.DrainRate); From a07d5115bfef749e2b766a554ceb420c82206dd0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Mar 2024 11:42:22 +0800 Subject: [PATCH 3523/4092] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index de7497d58e..0e091dbd37 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + From 55d66d4615e65bdbcc794d385da3210aa130d7cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Mar 2024 11:45:46 +0800 Subject: [PATCH 3524/4092] Add sounds to countdown --- osu.Game/Screens/Play/DelayedResumeOverlay.cs | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/DelayedResumeOverlay.cs b/osu.Game/Screens/Play/DelayedResumeOverlay.cs index 8bb3ae8182..147d48ae02 100644 --- a/osu.Game/Screens/Play/DelayedResumeOverlay.cs +++ b/osu.Game/Screens/Play/DelayedResumeOverlay.cs @@ -3,6 +3,8 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -47,6 +49,8 @@ namespace osu.Game.Screens.Play private SpriteText countdownText = null!; private CircularProgress countdownProgress = null!; + private Sample? sampleCountdown; + public DelayedResumeOverlay() { Anchor = Anchor.Centre; @@ -54,7 +58,7 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio) { Add(outerContent = new Circle { @@ -103,6 +107,8 @@ namespace osu.Game.Screens.Play } } }); + + sampleCountdown = audio.Samples.Get(@"Gameplay/resume-countdown"); } protected override void PopIn() @@ -164,13 +170,24 @@ namespace osu.Game.Screens.Play countdownProgress.Progress = amountTimePassed; countdownProgress.InnerRadius = progress_stroke_width / progress_size / countdownProgress.Scale.X; - if (countdownCount != newCount && newCount > 0) + if (countdownCount != newCount) { - countdownText.Text = Math.Max(1, newCount).ToString(); - countdownText.ScaleTo(0.25f).Then().ScaleTo(1, 200, Easing.OutQuint); - outerContent.Delay(25).Then().ScaleTo(1.05f, 100).Then().ScaleTo(1f, 200, Easing.Out); + if (newCount > 0) + { + countdownText.Text = Math.Max(1, newCount).ToString(); + countdownText.ScaleTo(0.25f).Then().ScaleTo(1, 200, Easing.OutQuint); + outerContent.Delay(25).Then().ScaleTo(1.05f, 100).Then().ScaleTo(1f, 200, Easing.Out); - countdownBackground.FlashColour(colourProvider.Background3, 400, Easing.Out); + countdownBackground.FlashColour(colourProvider.Background3, 400, Easing.Out); + } + + var chan = sampleCountdown?.GetChannel(); + + if (chan != null) + { + chan.Frequency.Value = newCount == 0 ? 0.5f : 1; + chan.Play(); + } } countdownCount = newCount; From b99b0337cffd33519b1323adbda98cb13b395f63 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Mar 2024 13:06:25 +0800 Subject: [PATCH 3525/4092] Adjust text slightly --- osu.Game/Localisation/NotificationsStrings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Localisation/NotificationsStrings.cs b/osu.Game/Localisation/NotificationsStrings.cs index 5328bcd066..3188ca5533 100644 --- a/osu.Game/Localisation/NotificationsStrings.cs +++ b/osu.Game/Localisation/NotificationsStrings.cs @@ -126,9 +126,9 @@ Click to see what's new!", version); public static LocalisableString UpdateReadyToInstall => new TranslatableString(getKey(@"update_ready_to_install"), @"Update ready to install. Click to restart!"); /// - /// "This is not an official build of the game and scores will not be submitted." + /// "This is not an official build of the game. Scores will not be submitted and other online systems may not work as intended." /// - public static LocalisableString NotOfficialBuild => new TranslatableString(getKey(@"not_official_build"), @"This is not an official build of the game and scores will not be submitted."); + public static LocalisableString NotOfficialBuild => new TranslatableString(getKey(@"not_official_build"), @"This is not an official build of the game. Scores will not be submitted and other online systems may not work as intended."); /// /// "Downloading update..." From 5f7028b5741f88e5c68e05cbd878ecc110d32eec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 Mar 2024 17:45:56 +0100 Subject: [PATCH 3526/4092] Add failing tests --- .../Formats/LegacyScoreDecoderTest.cs | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index 5dae86d9e9..050259c2fa 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -31,6 +31,7 @@ using osu.Game.Rulesets.Taiko; using osu.Game.Scoring; using osu.Game.Scoring.Legacy; using osu.Game.Tests.Resources; +using osuTK; namespace osu.Game.Tests.Beatmaps.Formats { @@ -178,6 +179,94 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(decodedAfterEncode.Replay.Frames[1].Time, Is.EqualTo(second_frame_time)); } + [Test] + public void TestNegativeFrameSkipped() + { + var ruleset = new OsuRuleset().RulesetInfo; + var scoreInfo = TestResources.CreateTestScoreInfo(ruleset); + var beatmap = new TestBeatmap(ruleset); + + var score = new Score + { + ScoreInfo = scoreInfo, + Replay = new Replay + { + Frames = new List + { + new OsuReplayFrame(0, new Vector2()), + new OsuReplayFrame(1000, OsuPlayfield.BASE_SIZE), + new OsuReplayFrame(500, OsuPlayfield.BASE_SIZE / 2), + new OsuReplayFrame(2000, OsuPlayfield.BASE_SIZE), + } + } + }; + + var decodedAfterEncode = encodeThenDecode(LegacyScoreEncoder.LATEST_VERSION, score, beatmap); + + Assert.That(decodedAfterEncode.Replay.Frames, Has.Count.EqualTo(3)); + Assert.That(decodedAfterEncode.Replay.Frames[0].Time, Is.EqualTo(0)); + Assert.That(decodedAfterEncode.Replay.Frames[1].Time, Is.EqualTo(1000)); + Assert.That(decodedAfterEncode.Replay.Frames[2].Time, Is.EqualTo(2000)); + } + + [Test] + public void FirstTwoFramesSwappedIfInWrongOrder() + { + var ruleset = new OsuRuleset().RulesetInfo; + var scoreInfo = TestResources.CreateTestScoreInfo(ruleset); + var beatmap = new TestBeatmap(ruleset); + + var score = new Score + { + ScoreInfo = scoreInfo, + Replay = new Replay + { + Frames = new List + { + new OsuReplayFrame(100, new Vector2()), + new OsuReplayFrame(50, OsuPlayfield.BASE_SIZE / 2), + new OsuReplayFrame(1000, OsuPlayfield.BASE_SIZE), + } + } + }; + + var decodedAfterEncode = encodeThenDecode(LegacyScoreEncoder.LATEST_VERSION, score, beatmap); + + Assert.That(decodedAfterEncode.Replay.Frames, Has.Count.EqualTo(3)); + Assert.That(decodedAfterEncode.Replay.Frames[0].Time, Is.EqualTo(0)); + Assert.That(decodedAfterEncode.Replay.Frames[1].Time, Is.EqualTo(100)); + Assert.That(decodedAfterEncode.Replay.Frames[2].Time, Is.EqualTo(1000)); + } + + [Test] + public void FirstTwoFramesPulledTowardThirdIfTheyAreAfterIt() + { + var ruleset = new OsuRuleset().RulesetInfo; + var scoreInfo = TestResources.CreateTestScoreInfo(ruleset); + var beatmap = new TestBeatmap(ruleset); + + var score = new Score + { + ScoreInfo = scoreInfo, + Replay = new Replay + { + Frames = new List + { + new OsuReplayFrame(0, new Vector2()), + new OsuReplayFrame(500, OsuPlayfield.BASE_SIZE / 2), + new OsuReplayFrame(-1500, OsuPlayfield.BASE_SIZE), + } + } + }; + + var decodedAfterEncode = encodeThenDecode(LegacyScoreEncoder.LATEST_VERSION, score, beatmap); + + Assert.That(decodedAfterEncode.Replay.Frames, Has.Count.EqualTo(3)); + Assert.That(decodedAfterEncode.Replay.Frames[0].Time, Is.EqualTo(-1500)); + Assert.That(decodedAfterEncode.Replay.Frames[1].Time, Is.EqualTo(-1500)); + Assert.That(decodedAfterEncode.Replay.Frames[2].Time, Is.EqualTo(-1500)); + } + [Test] public void TestCultureInvariance() { From 990a07af0eb7070b2e92ed37c033bf183721e299 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 Mar 2024 21:01:51 +0100 Subject: [PATCH 3527/4092] Rewrite handling of legacy replay frame quirks to match stable closer --- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 77 +++++++++---------- 1 file changed, 37 insertions(+), 40 deletions(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index b16cdffe82..af514a4b59 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -21,6 +21,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; +using osuTK; using SharpCompress.Compressors.LZMA; namespace osu.Game.Scoring.Legacy @@ -240,15 +241,7 @@ namespace osu.Game.Scoring.Legacy private void readLegacyReplay(Replay replay, StreamReader reader) { float lastTime = beatmapOffset; - bool negativeFrameEncounted = false; - ReplayFrame currentFrame = null; - - // the negative time amount that must be "paid back" by positive frames before we start including frames again. - // When a negative frame occurs in a replay, all future frames are skipped until the sum total of their times - // is equal to or greater than the time of that negative frame. - // This value will be negative if we are in a time deficit, ie we have a negative frame that must be paid back. - // Otherwise it will be 0. - float timeDeficit = 0; + var legacyFrames = new List(); string[] frames = reader.ReadToEnd().Split(','); @@ -271,40 +264,44 @@ namespace osu.Game.Scoring.Legacy lastTime += diff; - if (i < 2 && mouseX == 256 && mouseY == -500) - // at the start of the replay, stable places two replay frames, at time 0 and SkipBoundary - 1, respectively. - // both frames use a position of (256, -500). - // ignore these frames as they serve no real purpose (and can even mislead ruleset-specific handlers - see mania) - continue; - - // negative frames are only counted towards the deficit after the very beginning of the replay. - // When the two skip frames are present (see directly above), the third frame will have a large - // negative time roughly equal to SkipBoundary. This shouldn't be counted towards the deficit, otherwise - // any replay data before the skip would be, well, skipped. - // - // On testing against stable, it appears that stable ignores the negative time of only the first - // negative frame of the first three replay frames, regardless of if the skip frames are present. - // Hence the condition here. - // But there is a possibility this is incorrect and may need to be revisited later. - if (i > 2 || negativeFrameEncounted) - { - timeDeficit += diff; - timeDeficit = Math.Min(0, timeDeficit); - } - - if (diff < 0) - negativeFrameEncounted = true; - - // still paying back the deficit from a negative frame. Skip this frame. - if (timeDeficit < 0) - continue; - - currentFrame = convertFrame(new LegacyReplayFrame(lastTime, + legacyFrames.Add(new LegacyReplayFrame(lastTime, mouseX, mouseY, - (ReplayButtonState)Parsing.ParseInt(split[3])), currentFrame); + (ReplayButtonState)Parsing.ParseInt(split[3]))); + } - replay.Frames.Add(currentFrame); + // https://github.com/peppy/osu-stable-reference/blob/e53980dd76857ee899f66ce519ba1597e7874f28/osu!/GameModes/Play/ReplayWatcher.cs#L62-L67 + if (legacyFrames.Count >= 2 && legacyFrames[1].Time < legacyFrames[0].Time) + { + legacyFrames[1].Time = legacyFrames[0].Time; + legacyFrames[0].Time = 0; + } + + // https://github.com/peppy/osu-stable-reference/blob/e53980dd76857ee899f66ce519ba1597e7874f28/osu!/GameModes/Play/ReplayWatcher.cs#L69-L71 + if (legacyFrames.Count >= 3 && legacyFrames[0].Time > legacyFrames[2].Time) + legacyFrames[0].Time = legacyFrames[1].Time = legacyFrames[2].Time; + + // at the start of the replay, stable places two replay frames, at time 0 and SkipBoundary - 1, respectively. + // both frames use a position of (256, -500). + // ignore these frames as they serve no real purpose (and can even mislead ruleset-specific handlers - see mania) + if (legacyFrames.Count >= 2 && legacyFrames[1].Position == new Vector2(256, -500)) + legacyFrames.RemoveAt(1); + + if (legacyFrames.Count >= 1 && legacyFrames[0].Position == new Vector2(256, -500)) + legacyFrames.RemoveAt(0); + + ReplayFrame currentFrame = null; + + foreach (var legacyFrame in legacyFrames) + { + // never allow backwards time traversal in relation to the current frame. + // this handles frames with negative delta. + // this doesn't match stable 100% as stable will do something similar to adding an interpolated "intermediate frame" + // at the point wherein time flow changes from backwards to forwards, but it'll do for now. + if (currentFrame != null && legacyFrame.Time < currentFrame.Time) + continue; + + replay.Frames.Add(currentFrame = convertFrame(legacyFrame, currentFrame)); } } From a4822fd615ef983be0399182f98159fc8546e427 Mon Sep 17 00:00:00 2001 From: Arthur Araujo Date: Fri, 22 Mar 2024 01:37:06 -0300 Subject: [PATCH 3528/4092] Make `CheckAbnormalDifficultySettings` abstract --- .../CheckAbnormalDifficultySettingsTest.cs | 93 ------------------- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 3 - .../Checks/CheckAbnormalDifficultySettings.cs | 41 ++------ 3 files changed, 8 insertions(+), 129 deletions(-) delete mode 100644 osu.Game.Tests/Editing/Checks/CheckAbnormalDifficultySettingsTest.cs diff --git a/osu.Game.Tests/Editing/Checks/CheckAbnormalDifficultySettingsTest.cs b/osu.Game.Tests/Editing/Checks/CheckAbnormalDifficultySettingsTest.cs deleted file mode 100644 index 94305755c4..0000000000 --- a/osu.Game.Tests/Editing/Checks/CheckAbnormalDifficultySettingsTest.cs +++ /dev/null @@ -1,93 +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.Edit.Checks; -using NUnit.Framework; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Edit; -using osu.Game.Tests.Beatmaps; -using System.Linq; - -namespace osu.Game.Tests.Editing.Checks -{ - [TestFixture] - public class CheckAbnormalDifficultySettingsTest - { - private CheckAbnormalDifficultySettings check = null!; - - private IBeatmap beatmap = new Beatmap(); - - [SetUp] - public void Setup() - { - check = new CheckAbnormalDifficultySettings(); - beatmap.Difficulty = new(); - } - - [Test] - public void TestSettingsNormal() - { - var context = getContext(); - var issues = check.Run(context).ToList(); - - Assert.That(issues, Has.Count.EqualTo(0)); - } - - [Test] - public void TestAllSettingsMoreThanOneDecimal() - { - beatmap.Difficulty = new() - { - ApproachRate = 5.55f, - OverallDifficulty = 7.7777f, - CircleSize = 4.444f, - DrainRate = 1.1111111111f, - }; - - var context = getContext(); - var issues = check.Run(context).ToList(); - - Assert.That(issues, Has.Count.EqualTo(4)); - } - - [Test] - public void TestAllSettingsLessThanZero() - { - beatmap.Difficulty = new() - { - ApproachRate = -1, - OverallDifficulty = -20, - CircleSize = -11, - DrainRate = -34, - }; - - var context = getContext(); - var issues = check.Run(context).ToList(); - - Assert.That(issues, Has.Count.EqualTo(4)); - } - - [Test] - public void TestAllSettingsHigherThanTen() - { - beatmap.Difficulty = new() - { - ApproachRate = 14, - OverallDifficulty = 24, - CircleSize = 30, - DrainRate = 90, - }; - - var context = getContext(); - var issues = check.Run(context).ToList(); - - Assert.That(issues, Has.Count.EqualTo(4)); - } - - private BeatmapVerifierContext getContext() - { - return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); - } - } -} diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index edabe941b7..2eb52e88c7 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -42,9 +42,6 @@ namespace osu.Game.Rulesets.Edit // Events new CheckBreaks(), - - // Settings - new CheckAbnormalDifficultySettings(), }; public IEnumerable Run(BeatmapVerifierContext context) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckAbnormalDifficultySettings.cs b/osu.Game/Rulesets/Edit/Checks/CheckAbnormalDifficultySettings.cs index db154f4efc..93592a866b 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckAbnormalDifficultySettings.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckAbnormalDifficultySettings.cs @@ -6,9 +6,9 @@ using osu.Game.Rulesets.Edit.Checks.Components; namespace osu.Game.Rulesets.Edit.Checks { - public class CheckAbnormalDifficultySettings : ICheck + public abstract class CheckAbnormalDifficultySettings : ICheck { - public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Settings, "Abnormal difficulty settings"); + public abstract CheckMetadata Metadata { get; } public IEnumerable PossibleTemplates => new IssueTemplate[] { @@ -16,42 +16,17 @@ namespace osu.Game.Rulesets.Edit.Checks new IssueTemplateOutOfRange(this), }; - public IEnumerable Run(BeatmapVerifierContext context) - { - var diff = context.Beatmap.Difficulty; - string ruleset = context.Beatmap.BeatmapInfo.Ruleset.ShortName; + public abstract IEnumerable Run(BeatmapVerifierContext context); - if (hasMoreThanOneDecimalPlace(diff.ApproachRate)) - yield return new IssueTemplateMoreThanOneDecimal(this).Create("Approach rate", diff.ApproachRate); - - if (isOutOfRange(diff.ApproachRate)) - yield return new IssueTemplateOutOfRange(this).Create("Approach rate", diff.ApproachRate); - - if (hasMoreThanOneDecimalPlace(diff.OverallDifficulty)) - yield return new IssueTemplateMoreThanOneDecimal(this).Create("Overall difficulty", diff.OverallDifficulty); - - if (isOutOfRange(diff.OverallDifficulty)) - yield return new IssueTemplateOutOfRange(this).Create("Overall difficulty", diff.OverallDifficulty); - - if (hasMoreThanOneDecimalPlace(diff.CircleSize)) - yield return new IssueTemplateMoreThanOneDecimal(this).Create("Circle size", diff.CircleSize); - - if (ruleset != "mania" && isOutOfRange(diff.CircleSize)) - yield return new IssueTemplateOutOfRange(this).Create("Circle size", diff.CircleSize); - - if (hasMoreThanOneDecimalPlace(diff.DrainRate)) - yield return new IssueTemplateMoreThanOneDecimal(this).Create("Drain rate", diff.DrainRate); - - if (isOutOfRange(diff.DrainRate)) - yield return new IssueTemplateOutOfRange(this).Create("Drain rate", diff.DrainRate); - } - - private bool isOutOfRange(float setting) + /// + /// If the setting is out of the boundaries set by the editor (0 - 10) + /// + protected bool OutOfRange(float setting) { return setting < 0f || setting > 10f; } - private bool hasMoreThanOneDecimalPlace(float setting) + protected bool HasMoreThanOneDecimalPlace(float setting) { return float.Round(setting, 1) != setting; } From e2df0981843c3428fa11a232d341d97ac3e0505f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 22 Mar 2024 08:25:03 +0100 Subject: [PATCH 3529/4092] Add failing test case for desired artist sort behaviour --- .../SongSelect/TestSceneBeatmapCarousel.cs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index de2ae3708f..c0102b238c 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -666,6 +666,56 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert($"Check {zzz_lowercase} is second last", () => carousel.BeatmapSets.SkipLast(1).Last().Metadata.Artist == zzz_lowercase); } + [Test] + public void TestSortByArtistUsesTitleAsTiebreaker() + { + var sets = new List(); + + AddStep("Populuate beatmap sets", () => + { + sets.Clear(); + + for (int i = 0; i < 20; i++) + { + var set = TestResources.CreateTestBeatmapSetInfo(); + + if (i == 4) + { + set.Beatmaps.ForEach(b => + { + b.Metadata.Artist = "ZZZ"; + b.Metadata.Title = "AAA"; + }); + } + + if (i == 8) + { + set.Beatmaps.ForEach(b => + { + b.Metadata.Artist = "ZZZ"; + b.Metadata.Title = "ZZZ"; + }); + } + + sets.Add(set); + } + }); + + loadBeatmaps(sets); + + AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false)); + AddAssert("Check last item", () => + { + var lastItem = carousel.BeatmapSets.Last(); + return lastItem.Metadata.Artist == "ZZZ" && lastItem.Metadata.Title == "ZZZ"; + }); + AddAssert("Check second last item", () => + { + var secondLastItem = carousel.BeatmapSets.SkipLast(1).Last(); + return secondLastItem.Metadata.Artist == "ZZZ" && secondLastItem.Metadata.Title == "AAA"; + }); + } + /// /// Ensures stability is maintained on different sort modes for items with equal properties. /// From a1880e89c299f55a8907050988b8200e854ed34b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 22 Mar 2024 08:31:59 +0100 Subject: [PATCH 3530/4092] Use title as tiebreaker when sorting beatmap carousel by artist Closes https://github.com/ppy/osu/issues/27548. Reference: https://github.com/peppy/osu-stable-reference/blob/e53980dd76857ee899f66ce519ba1597e7874f28/osu!/GameplayElements/Beatmaps/BeatmapTreeManager.cs#L341-L347 --- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 43c9c621e8..7e15699804 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -69,6 +69,8 @@ namespace osu.Game.Screens.Select.Carousel default: case SortMode.Artist: comparison = OrdinalSortByCaseStringComparer.DEFAULT.Compare(BeatmapSet.Metadata.Artist, otherSet.BeatmapSet.Metadata.Artist); + if (comparison == 0) + goto case SortMode.Title; break; case SortMode.Title: From 6fa663c8cac732c6d705955f52565f1f78b7eddd Mon Sep 17 00:00:00 2001 From: Arthur Araujo Date: Fri, 22 Mar 2024 14:48:22 -0300 Subject: [PATCH 3531/4092] Make check ruleset specific --- ...heckCatchAbnormalDifficultySettingsTest.cs | 158 ++++++++++++++ .../Edit/CatchBeatmapVerifier.cs | 3 +- .../CheckCatchAbnormalDifficultySettings.cs | 38 ++++ ...heckManiaAbnormalDifficultySettingsTest.cs | 121 +++++++++++ .../CheckManiaAbnormalDifficultySettings.cs | 32 +++ .../Edit/ManiaBeatmapVerifier.cs | 2 + .../CheckOsuAbnormalDifficultySettingsTest.cs | 194 ++++++++++++++++++ .../CheckOsuAbnormalDifficultySettings.cs | 43 ++++ .../Edit/OsuBeatmapVerifier.cs | 3 + ...heckTaikoAbnormalDifficultySettingsTest.cs | 84 ++++++++ .../CheckTaikoAbnormalDifficultySettings.cs | 26 +++ .../Edit/TaikoBeatmapVerifier.cs | 24 +++ osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 + 13 files changed, 729 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Catch.Tests/Editor/Checks/CheckCatchAbnormalDifficultySettingsTest.cs create mode 100644 osu.Game.Rulesets.Catch/Edit/Checks/CheckCatchAbnormalDifficultySettings.cs create mode 100644 osu.Game.Rulesets.Mania.Tests/Editor/Checks/CheckManiaAbnormalDifficultySettingsTest.cs create mode 100644 osu.Game.Rulesets.Mania/Edit/Checks/CheckManiaAbnormalDifficultySettings.cs create mode 100644 osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOsuAbnormalDifficultySettingsTest.cs create mode 100644 osu.Game.Rulesets.Osu/Edit/Checks/CheckOsuAbnormalDifficultySettings.cs create mode 100644 osu.Game.Rulesets.Taiko.Tests/Editor/Checks/CheckTaikoAbnormalDifficultySettingsTest.cs create mode 100644 osu.Game.Rulesets.Taiko/Edit/Checks/CheckTaikoAbnormalDifficultySettings.cs create mode 100644 osu.Game.Rulesets.Taiko/Edit/TaikoBeatmapVerifier.cs diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/Checks/CheckCatchAbnormalDifficultySettingsTest.cs b/osu.Game.Rulesets.Catch.Tests/Editor/Checks/CheckCatchAbnormalDifficultySettingsTest.cs new file mode 100644 index 0000000000..2ae2e20215 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Editor/Checks/CheckCatchAbnormalDifficultySettingsTest.cs @@ -0,0 +1,158 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Edit.Checks; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Objects; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Rulesets.Catch.Tests.Editor.Checks +{ + [TestFixture] + public class CheckCatchAbnormalDifficultySettingsTest + { + private CheckCatchAbnormalDifficultySettings check = null!; + + private IBeatmap beatmap = new Beatmap(); + + [SetUp] + public void Setup() + { + check = new CheckCatchAbnormalDifficultySettings(); + + beatmap.BeatmapInfo.Ruleset = new CatchRuleset().RulesetInfo; + beatmap.Difficulty = new BeatmapDifficulty() + { + ApproachRate = 5, + CircleSize = 5, + DrainRate = 5, + }; + } + + [Test] + public void TestNormalSettings() + { + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(0)); + } + + [Test] + public void TestApproachRateTwoDecimals() + { + beatmap.Difficulty.ApproachRate = 5.55f; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateMoreThanOneDecimal); + } + + [Test] + public void TestCircleSizeTwoDecimals() + { + beatmap.Difficulty.CircleSize = 5.55f; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateMoreThanOneDecimal); + } + + [Test] + public void TestDrainRateTwoDecimals() + { + beatmap.Difficulty.DrainRate = 5.55f; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateMoreThanOneDecimal); + } + + [Test] + public void TestApproachRateUnder() + { + beatmap.Difficulty.ApproachRate = -10; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateOutOfRange); + } + + [Test] + public void TestCircleSizeUnder() + { + beatmap.Difficulty.CircleSize = -10; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateOutOfRange); + } + + [Test] + public void TestDrainRateUnder() + { + beatmap.Difficulty.DrainRate = -10; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateOutOfRange); + } + + [Test] + public void TestApproachRateOver() + { + beatmap.Difficulty.ApproachRate = 20; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateOutOfRange); + } + + [Test] + public void TestCircleSizeOver() + { + beatmap.Difficulty.CircleSize = 20; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateOutOfRange); + } + + [Test] + public void TestDrainRateOver() + { + beatmap.Difficulty.DrainRate = 20; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateOutOfRange); + } + + private BeatmapVerifierContext getContext() + { + return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); + } + } +} diff --git a/osu.Game.Rulesets.Catch/Edit/CatchBeatmapVerifier.cs b/osu.Game.Rulesets.Catch/Edit/CatchBeatmapVerifier.cs index c7a41a4e22..71da6d5014 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchBeatmapVerifier.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchBeatmapVerifier.cs @@ -13,7 +13,8 @@ namespace osu.Game.Rulesets.Catch.Edit { private readonly List checks = new List { - new CheckBananaShowerGap() + new CheckBananaShowerGap(), + new CheckCatchAbnormalDifficultySettings(), }; public IEnumerable Run(BeatmapVerifierContext context) diff --git a/osu.Game.Rulesets.Catch/Edit/Checks/CheckCatchAbnormalDifficultySettings.cs b/osu.Game.Rulesets.Catch/Edit/Checks/CheckCatchAbnormalDifficultySettings.cs new file mode 100644 index 0000000000..8295795f00 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/Checks/CheckCatchAbnormalDifficultySettings.cs @@ -0,0 +1,38 @@ +// 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 osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Edit.Checks.Components; + +namespace osu.Game.Rulesets.Catch.Edit.Checks +{ + public class CheckCatchAbnormalDifficultySettings : CheckAbnormalDifficultySettings + { + public override CheckMetadata Metadata => new CheckMetadata(CheckCategory.Settings, "Checks catch relevant settings"); + + public override IEnumerable Run(BeatmapVerifierContext context) + { + var diff = context.Beatmap.Difficulty; + + if (HasMoreThanOneDecimalPlace(diff.ApproachRate)) + yield return new IssueTemplateMoreThanOneDecimal(this).Create("Approach rate", diff.ApproachRate); + + if (OutOfRange(diff.ApproachRate)) + yield return new IssueTemplateOutOfRange(this).Create("Approach rate", diff.ApproachRate); + + if (HasMoreThanOneDecimalPlace(diff.CircleSize)) + yield return new IssueTemplateMoreThanOneDecimal(this).Create("Circle size", diff.CircleSize); + + if (OutOfRange(diff.CircleSize)) + yield return new IssueTemplateOutOfRange(this).Create("Circle size", diff.CircleSize); + + if (HasMoreThanOneDecimalPlace(diff.DrainRate)) + yield return new IssueTemplateMoreThanOneDecimal(this).Create("Drain rate", diff.DrainRate); + + if (OutOfRange(diff.DrainRate)) + yield return new IssueTemplateOutOfRange(this).Create("Drain rate", diff.DrainRate); + } + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/Checks/CheckManiaAbnormalDifficultySettingsTest.cs b/osu.Game.Rulesets.Mania.Tests/Editor/Checks/CheckManiaAbnormalDifficultySettingsTest.cs new file mode 100644 index 0000000000..6c585aace3 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Editor/Checks/CheckManiaAbnormalDifficultySettingsTest.cs @@ -0,0 +1,121 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Edit.Checks; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Objects; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Rulesets.Mania.Tests.Editor.Checks +{ + [TestFixture] + public class CheckManiaAbnormalDifficultySettingsTest + { + private CheckManiaAbnormalDifficultySettings check = null!; + + private IBeatmap beatmap = new Beatmap(); + + [SetUp] + public void Setup() + { + check = new CheckManiaAbnormalDifficultySettings(); + + beatmap.BeatmapInfo.Ruleset = new ManiaRuleset().RulesetInfo; + beatmap.Difficulty = new BeatmapDifficulty() + { + OverallDifficulty = 5, + DrainRate = 5, + }; + } + + [Test] + public void TestNormalSettings() + { + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(0)); + } + + [Test] + public void TestOverallDifficultyTwoDecimals() + { + beatmap.Difficulty.OverallDifficulty = 5.55f; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateMoreThanOneDecimal); + } + + [Test] + public void TestDrainRateTwoDecimals() + { + beatmap.Difficulty.DrainRate = 5.55f; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateMoreThanOneDecimal); + } + + [Test] + public void TestOverallDifficultyUnder() + { + beatmap.Difficulty.OverallDifficulty = -10; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateOutOfRange); + } + + [Test] + public void TestDrainRateUnder() + { + beatmap.Difficulty.DrainRate = -10; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateOutOfRange); + } + + [Test] + public void TestOverallDifficultyOver() + { + beatmap.Difficulty.OverallDifficulty = 20; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateOutOfRange); + } + + [Test] + public void TestDrainRateOver() + { + beatmap.Difficulty.DrainRate = 20; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateOutOfRange); + } + + private BeatmapVerifierContext getContext() + { + return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/Checks/CheckManiaAbnormalDifficultySettings.cs b/osu.Game.Rulesets.Mania/Edit/Checks/CheckManiaAbnormalDifficultySettings.cs new file mode 100644 index 0000000000..ae0cc3aa4c --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/Checks/CheckManiaAbnormalDifficultySettings.cs @@ -0,0 +1,32 @@ +// 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 osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Edit.Checks.Components; + +namespace osu.Game.Rulesets.Mania.Edit.Checks +{ + public class CheckManiaAbnormalDifficultySettings : CheckAbnormalDifficultySettings + { + public override CheckMetadata Metadata => new CheckMetadata(CheckCategory.Settings, "Checks mania relevant settings"); + + public override IEnumerable Run(BeatmapVerifierContext context) + { + var diff = context.Beatmap.Difficulty; + + if (HasMoreThanOneDecimalPlace(diff.OverallDifficulty)) + yield return new IssueTemplateMoreThanOneDecimal(this).Create("Overall difficulty", diff.OverallDifficulty); + + if (OutOfRange(diff.OverallDifficulty)) + yield return new IssueTemplateOutOfRange(this).Create("Overall difficulty", diff.OverallDifficulty); + + if (HasMoreThanOneDecimalPlace(diff.DrainRate)) + yield return new IssueTemplateMoreThanOneDecimal(this).Create("Drain rate", diff.DrainRate); + + if (OutOfRange(diff.DrainRate)) + yield return new IssueTemplateOutOfRange(this).Create("Drain rate", diff.DrainRate); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBeatmapVerifier.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBeatmapVerifier.cs index 778dcfc6e1..4adabfa4d7 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBeatmapVerifier.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBeatmapVerifier.cs @@ -13,7 +13,9 @@ namespace osu.Game.Rulesets.Mania.Edit { private readonly List checks = new List { + // Settings new CheckKeyCount(), + new CheckManiaAbnormalDifficultySettings(), }; public IEnumerable Run(BeatmapVerifierContext context) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOsuAbnormalDifficultySettingsTest.cs b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOsuAbnormalDifficultySettingsTest.cs new file mode 100644 index 0000000000..53ccd3c7a7 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOsuAbnormalDifficultySettingsTest.cs @@ -0,0 +1,194 @@ +// 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.Osu.Edit.Checks; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Edit; +using osu.Game.Tests.Beatmaps; +using System.Linq; + +namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks +{ + [TestFixture] + public class CheckOsuAbnormalDifficultySettingsTest + { + private CheckOsuAbnormalDifficultySettings check = null!; + + private IBeatmap beatmap = new Beatmap(); + + [SetUp] + public void Setup() + { + check = new CheckOsuAbnormalDifficultySettings(); + + beatmap.Difficulty = new BeatmapDifficulty() + { + ApproachRate = 5, + CircleSize = 5, + DrainRate = 5, + OverallDifficulty = 5, + }; + } + + [Test] + public void TestNormalSettings() + { + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(0)); + } + + [Test] + public void TestApproachRateTwoDecimals() + { + beatmap.Difficulty.ApproachRate = 5.55f; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateMoreThanOneDecimal); + } + + [Test] + public void TestCircleSizeTwoDecimals() + { + beatmap.Difficulty.CircleSize = 5.55f; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateMoreThanOneDecimal); + } + + [Test] + public void TestDrainRateTwoDecimals() + { + beatmap.Difficulty.DrainRate = 5.55f; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateMoreThanOneDecimal); + } + + [Test] + public void TestOverallDifficultyTwoDecimals() + { + beatmap.Difficulty.OverallDifficulty = 5.55f; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateMoreThanOneDecimal); + } + + [Test] + public void TestApproachRateUnder() + { + beatmap.Difficulty.ApproachRate = -10; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateOutOfRange); + } + + [Test] + public void TestCircleSizeUnder() + { + beatmap.Difficulty.CircleSize = -10; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateOutOfRange); + } + + [Test] + public void TestDrainRateUnder() + { + beatmap.Difficulty.DrainRate = -10; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateOutOfRange); + } + + [Test] + public void TestOverallDifficultyUnder() + { + beatmap.Difficulty.OverallDifficulty = -10; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateOutOfRange); + } + + [Test] + public void TestApproachRateOver() + { + beatmap.Difficulty.ApproachRate = 20; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateOutOfRange); + } + + [Test] + public void TestCircleSizeOver() + { + beatmap.Difficulty.CircleSize = 20; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateOutOfRange); + } + + [Test] + public void TestDrainRateOver() + { + beatmap.Difficulty.DrainRate = 20; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateOutOfRange); + } + + [Test] + public void TestOverallDifficultyOver() + { + beatmap.Difficulty.OverallDifficulty = 20; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateOutOfRange); + } + + private BeatmapVerifierContext getContext() + { + return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOsuAbnormalDifficultySettings.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOsuAbnormalDifficultySettings.cs new file mode 100644 index 0000000000..c1eca7fff7 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOsuAbnormalDifficultySettings.cs @@ -0,0 +1,43 @@ +// 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 osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Edit.Checks.Components; + +namespace osu.Game.Rulesets.Osu.Edit.Checks +{ + public class CheckOsuAbnormalDifficultySettings : CheckAbnormalDifficultySettings + { + public override CheckMetadata Metadata => new CheckMetadata(CheckCategory.Settings, "Checks osu relevant settings"); + public override IEnumerable Run(BeatmapVerifierContext context) + { + var diff = context.Beatmap.Difficulty; + + if (HasMoreThanOneDecimalPlace(diff.ApproachRate)) + yield return new IssueTemplateMoreThanOneDecimal(this).Create("Approach rate", diff.ApproachRate); + + if (OutOfRange(diff.ApproachRate)) + yield return new IssueTemplateOutOfRange(this).Create("Approach rate", diff.ApproachRate); + + if (HasMoreThanOneDecimalPlace(diff.OverallDifficulty)) + yield return new IssueTemplateMoreThanOneDecimal(this).Create("Overall difficulty", diff.OverallDifficulty); + + if (OutOfRange(diff.OverallDifficulty)) + yield return new IssueTemplateOutOfRange(this).Create("Overall difficulty", diff.OverallDifficulty); + + if (HasMoreThanOneDecimalPlace(diff.CircleSize)) + yield return new IssueTemplateMoreThanOneDecimal(this).Create("Circle size", diff.CircleSize); + + if (OutOfRange(diff.CircleSize)) + yield return new IssueTemplateOutOfRange(this).Create("Circle size", diff.CircleSize); + + if (HasMoreThanOneDecimalPlace(diff.DrainRate)) + yield return new IssueTemplateMoreThanOneDecimal(this).Create("Drain rate", diff.DrainRate); + + if (OutOfRange(diff.DrainRate)) + yield return new IssueTemplateOutOfRange(this).Create("Drain rate", diff.DrainRate); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs b/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs index 325e9ed4cb..4b01a1fc39 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs @@ -21,6 +21,9 @@ namespace osu.Game.Rulesets.Osu.Edit new CheckTimeDistanceEquality(), new CheckLowDiffOverlaps(), new CheckTooShortSliders(), + + // Settings + new CheckOsuAbnormalDifficultySettings(), }; public IEnumerable Run(BeatmapVerifierContext context) diff --git a/osu.Game.Rulesets.Taiko.Tests/Editor/Checks/CheckTaikoAbnormalDifficultySettingsTest.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/Checks/CheckTaikoAbnormalDifficultySettingsTest.cs new file mode 100644 index 0000000000..f10e62f3bf --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/Editor/Checks/CheckTaikoAbnormalDifficultySettingsTest.cs @@ -0,0 +1,84 @@ +// 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.Taiko.Edit.Checks; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Edit; +using osu.Game.Tests.Beatmaps; +using System.Linq; + +namespace osu.Game.Rulesets.Taiko.Tests.Editor.Checks +{ + [TestFixture] + public class CheckTaikoAbnormalDifficultySettingsTest + { + private CheckTaikoAbnormalDifficultySettings check = null!; + + private IBeatmap beatmap = new Beatmap(); + + [SetUp] + public void Setup() + { + check = new CheckTaikoAbnormalDifficultySettings(); + + beatmap.BeatmapInfo.Ruleset = new TaikoRuleset().RulesetInfo; + beatmap.Difficulty = new BeatmapDifficulty() + { + OverallDifficulty = 5, + }; + } + + [Test] + public void TestNormalSettings() + { + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(0)); + } + + [Test] + public void TestOverallDifficultyTwoDecimals() + { + beatmap.Difficulty.OverallDifficulty = 5.55f; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateMoreThanOneDecimal); + } + + [Test] + public void TestOverallDifficultyUnder() + { + beatmap.Difficulty.OverallDifficulty = -10; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateOutOfRange); + } + + [Test] + public void TestOverallDifficultyOver() + { + beatmap.Difficulty.OverallDifficulty = 20; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateOutOfRange); + } + + private BeatmapVerifierContext getContext() + { + return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Edit/Checks/CheckTaikoAbnormalDifficultySettings.cs b/osu.Game.Rulesets.Taiko/Edit/Checks/CheckTaikoAbnormalDifficultySettings.cs new file mode 100644 index 0000000000..ce35f21853 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Edit/Checks/CheckTaikoAbnormalDifficultySettings.cs @@ -0,0 +1,26 @@ +// 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 osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Edit.Checks.Components; + +namespace osu.Game.Rulesets.Taiko.Edit.Checks +{ + public class CheckTaikoAbnormalDifficultySettings : CheckAbnormalDifficultySettings + { + public override CheckMetadata Metadata => new CheckMetadata(CheckCategory.Settings, "Checks taiko relevant settings"); + + public override IEnumerable Run(BeatmapVerifierContext context) + { + var diff = context.Beatmap.Difficulty; + + if (HasMoreThanOneDecimalPlace(diff.OverallDifficulty)) + yield return new IssueTemplateMoreThanOneDecimal(this).Create("Overall difficulty", diff.OverallDifficulty); + + if (OutOfRange(diff.OverallDifficulty)) + yield return new IssueTemplateOutOfRange(this).Create("Overall difficulty", diff.OverallDifficulty); + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoBeatmapVerifier.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoBeatmapVerifier.cs new file mode 100644 index 0000000000..f5c3f1846d --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoBeatmapVerifier.cs @@ -0,0 +1,24 @@ +// 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 osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks.Components; +using osu.Game.Rulesets.Taiko.Edit.Checks; + +namespace osu.Game.Rulesets.Taiko.Edit +{ + public class TaikoBeatmapVerifier : IBeatmapVerifier + { + private readonly List checks = new List + { + new CheckTaikoAbnormalDifficultySettings(), + }; + + public IEnumerable Run(BeatmapVerifierContext context) + { + return checks.SelectMany(check => check.Run(context)); + } + } +} diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 24b0ec5d57..d7184bce60 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -188,6 +188,8 @@ namespace osu.Game.Rulesets.Taiko public override HitObjectComposer CreateHitObjectComposer() => new TaikoHitObjectComposer(this); + public override IBeatmapVerifier CreateBeatmapVerifier() => new TaikoBeatmapVerifier(); + public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new TaikoDifficultyCalculator(RulesetInfo, beatmap); public override PerformanceCalculator CreatePerformanceCalculator() => new TaikoPerformanceCalculator(); From c7c03302653790668d0fdb49f25dfceb016eaf3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 22 Mar 2024 19:05:58 +0100 Subject: [PATCH 3532/4092] Attempt to disable rulesets that can be linked to an unhandled crash --- osu.Game/OsuGameBase.cs | 4 ++- osu.Game/Rulesets/RealmRulesetStore.cs | 48 ++++++++++++++++++++++++-- osu.Game/Rulesets/RulesetStore.cs | 24 +++++++++---- 3 files changed, 65 insertions(+), 11 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 81e3d8bed8..8bda8fb6c2 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -678,12 +678,14 @@ namespace osu.Game /// /// Allows a maximum of one unhandled exception, per second of execution. /// - private bool onExceptionThrown(Exception _) + private bool onExceptionThrown(Exception ex) { bool continueExecution = Interlocked.Decrement(ref allowableExceptions) >= 0; Logger.Log($"Unhandled exception has been {(continueExecution ? $"allowed with {allowableExceptions} more allowable exceptions" : "denied")} ."); + RulesetStore.TryDisableCustomRulesetsCausing(ex); + // restore the stock of allowable exceptions after a short delay. Task.Delay(1000).ContinueWith(_ => Interlocked.Increment(ref allowableExceptions)); diff --git a/osu.Game/Rulesets/RealmRulesetStore.cs b/osu.Game/Rulesets/RealmRulesetStore.cs index ba6f4583d1..36eae7af2c 100644 --- a/osu.Game/Rulesets/RealmRulesetStore.cs +++ b/osu.Game/Rulesets/RealmRulesetStore.cs @@ -3,8 +3,11 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.IO; using System.Linq; using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Database; @@ -13,17 +16,20 @@ namespace osu.Game.Rulesets { public class RealmRulesetStore : RulesetStore { + private readonly RealmAccess realmAccess; public override IEnumerable AvailableRulesets => availableRulesets; private readonly List availableRulesets = new List(); - public RealmRulesetStore(RealmAccess realm, Storage? storage = null) + public RealmRulesetStore(RealmAccess realmAccess, Storage? storage = null) : base(storage) { - prepareDetachedRulesets(realm); + this.realmAccess = realmAccess; + prepareDetachedRulesets(); + informUserAboutBrokenRulesets(); } - private void prepareDetachedRulesets(RealmAccess realmAccess) + private void prepareDetachedRulesets() { realmAccess.Write(realm => { @@ -143,5 +149,41 @@ namespace osu.Game.Rulesets instance.CreateBeatmapProcessor(converter.Convert()); } + + private void informUserAboutBrokenRulesets() + { + if (RulesetStorage == null) + return; + + foreach (string brokenRulesetDll in RulesetStorage.GetFiles(@".", @"*.dll.broken")) + { + Logger.Log($"Ruleset '{Path.GetFileNameWithoutExtension(brokenRulesetDll)}' has been disabled due to causing a crash.\n\n" + + "Please update the ruleset or report the issue to the developers of the ruleset if no updates are available.", level: LogLevel.Important); + } + } + + internal void TryDisableCustomRulesetsCausing(Exception exception) + { + var stackTrace = new StackTrace(exception); + + foreach (var frame in stackTrace.GetFrames()) + { + var declaringAssembly = frame.GetMethod()?.DeclaringType?.Assembly; + if (declaringAssembly == null) + continue; + + if (UserRulesetAssemblies.Contains(declaringAssembly)) + { + string sourceLocation = declaringAssembly.Location; + string destinationLocation = Path.ChangeExtension(sourceLocation, @".dll.broken"); + + if (File.Exists(sourceLocation)) + { + Logger.Log($"Unhandled exception traced back to custom ruleset {Path.GetFileNameWithoutExtension(sourceLocation)}. Marking as broken."); + File.Move(sourceLocation, destinationLocation); + } + } + } + } } } diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index ac36ee6494..f33d42a53e 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -18,6 +18,8 @@ namespace osu.Game.Rulesets private const string ruleset_library_prefix = @"osu.Game.Rulesets"; protected readonly Dictionary LoadedAssemblies = new Dictionary(); + protected readonly HashSet UserRulesetAssemblies = new HashSet(); + protected readonly Storage? RulesetStorage; /// /// All available rulesets. @@ -41,9 +43,9 @@ namespace osu.Game.Rulesets // to load as unable to locate the game core assembly. AppDomain.CurrentDomain.AssemblyResolve += resolveRulesetDependencyAssembly; - var rulesetStorage = storage?.GetStorageForDirectory(@"rulesets"); - if (rulesetStorage != null) - loadUserRulesets(rulesetStorage); + RulesetStorage = storage?.GetStorageForDirectory(@"rulesets"); + if (RulesetStorage != null) + loadUserRulesets(RulesetStorage); } /// @@ -105,7 +107,11 @@ namespace osu.Game.Rulesets var rulesets = rulesetStorage.GetFiles(@".", @$"{ruleset_library_prefix}.*.dll"); foreach (string? ruleset in rulesets.Where(f => !f.Contains(@"Tests"))) - loadRulesetFromFile(rulesetStorage.GetFullPath(ruleset)); + { + var assembly = loadRulesetFromFile(rulesetStorage.GetFullPath(ruleset)); + if (assembly != null) + UserRulesetAssemblies.Add(assembly); + } } private void loadFromDisk() @@ -126,21 +132,25 @@ namespace osu.Game.Rulesets } } - private void loadRulesetFromFile(string file) + private Assembly? loadRulesetFromFile(string file) { string filename = Path.GetFileNameWithoutExtension(file); if (LoadedAssemblies.Values.Any(t => Path.GetFileNameWithoutExtension(t.Assembly.Location) == filename)) - return; + return null; try { - addRuleset(Assembly.LoadFrom(file)); + var assembly = Assembly.LoadFrom(file); + addRuleset(assembly); + return assembly; } catch (Exception e) { LogFailedLoad(filename, e); } + + return null; } private void addRuleset(Assembly assembly) From 6fbe1a5b8d1a80c6a5b1e277ee015162b7ea8083 Mon Sep 17 00:00:00 2001 From: Arthur Araujo Date: Sat, 23 Mar 2024 19:22:47 -0300 Subject: [PATCH 3533/4092] Add video resolution check --- .../Checks/CheckVideoResolutionTest.cs | 86 +++++++++++++ .../Videos/test-video-resolution-high.mp4 | Bin 0 -> 13655 bytes osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 1 + .../Edit/Checks/CheckVideoResolution.cs | 117 ++++++++++++++++++ 4 files changed, 204 insertions(+) create mode 100644 osu.Game.Tests/Editing/Checks/CheckVideoResolutionTest.cs create mode 100644 osu.Game.Tests/Resources/Videos/test-video-resolution-high.mp4 create mode 100644 osu.Game/Rulesets/Edit/Checks/CheckVideoResolution.cs diff --git a/osu.Game.Tests/Editing/Checks/CheckVideoResolutionTest.cs b/osu.Game.Tests/Editing/Checks/CheckVideoResolutionTest.cs new file mode 100644 index 0000000000..ab677a15d4 --- /dev/null +++ b/osu.Game.Tests/Editing/Checks/CheckVideoResolutionTest.cs @@ -0,0 +1,86 @@ +using System.IO; +using System.Linq; +using Moq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Objects; +using osu.Game.Storyboards; +using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Resources; + +namespace osu.Game.Tests.Editing.Checks +{ + [TestFixture] + public class CheckVideoResolutionTest + { + private CheckVideoResolution check = null!; + + private IBeatmap beatmap = null!; + + [SetUp] + public void Setup() + { + check = new CheckVideoResolution(); + beatmap = new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + BeatmapSet = new BeatmapSetInfo + { + Files = + { + CheckTestHelpers.CreateMockFile("mp4"), + } + } + } + }; + } + + [Test] + public void TestNoVideo() + { + beatmap.BeatmapInfo.BeatmapSet?.Files.Clear(); + + var issues = check.Run(getContext(null)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(0)); + } + + [Test] + public void TestVideoAcceptableResolution() + { + using (var resourceStream = TestResources.OpenResource("Videos/test-video.mp4")) + { + var issues = check.Run(getContext(resourceStream)).ToList(); + Assert.That(issues, Has.Count.EqualTo(0)); + } + } + + [Test] + public void TestVideoHighResolution() + { + using (var resourceStream = TestResources.OpenResource("Videos/test-video-resolution-high.mp4")) + { + var issues = check.Run(getContext(resourceStream)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckVideoResolution.IssueTemplateHighResolution); + } + } + + private BeatmapVerifierContext getContext(Stream? resourceStream) + { + var storyboard = new Storyboard(); + var layer = storyboard.GetLayer("Video"); + layer.Add(new StoryboardVideo("abc123.mp4", 0)); + + var mockWorkingBeatmap = new Mock(beatmap, null, null); + mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny())).Returns(resourceStream); + mockWorkingBeatmap.As().SetupGet(w => w.Storyboard).Returns(storyboard); + + return new BeatmapVerifierContext(beatmap, mockWorkingBeatmap.Object); + } + } +} diff --git a/osu.Game.Tests/Resources/Videos/test-video-resolution-high.mp4 b/osu.Game.Tests/Resources/Videos/test-video-resolution-high.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..fbdb00d3ad464a7f2d9d223d5fa9edb67fdaa689 GIT binary patch literal 13655 zcmeHNeQX>@6@PczVL*m4tPP}$L-S}hg&bg*ePO=GZEeMbb5vo#p-P>LJ4)5Ns zch`~B8a%$qmwy`2jYNdw-vopmjfC`>E|#gtJsYT8{RYT1XfIi(P|$oB0*sq zpGtgdCop3W(sEt%T;z?q;d(ye&y`r0=D_UPkB{GRO@&2S21 z&}dfEgEGsv;N9w1a+;yDHdS9Tv$+8j7L1zyp!~F#Yx%lH&CyvKmRS9q5yz17<*GYe zFKwWF+V$LELs*+*Y}1gl#EwF5+qsKNF8ok7uW)6Fy!Ny38Jjq{as2fL>sSr>ss76H3uE$U3*>WF{yW;; zS7^sF$iI3!awdDWcf;49u{YO+Uj+8=_C5X|^ksaRZv88BzT$eSg+46Z3hUPkE&d4T z1?bT2pq~)cuLpgPXkjPl+eC|&f%Zdfmx3N8TKrznZWtB?^fJ+sEudc#E!_+{3U&Pe z=xM0)I?#8Cn&Y4+iCR{H9wSK@P;qSbeTP7$?k0{x8W<{l8{ zvt~W$5K$ZE({_NU^M24Ik&Ly=uMkBupmRjAH0U%@9P^2vAnLjU^gL0`iECI}{{Z+_ zo=GI1LQF|~ttkM0=7S}mr&MI6x3achDOuZ^zPGlvPft+Du4rvtYb3|B`WW7|lC`bA zT(GuvZr<7kZT|^NTm6HArM-WerOm-LVsrgeT6UP~7=o#?l+ay`DLq9dCMH(koK+!T z)Kkx(FgroBZ>$DVJ@26{=copy5EFID92VLDn{E#8&Rf55hC9wNv|}>~h>gXFy))Yt z;ME*n!{N0Y&TK}|el~~Sz~OT^d@hH-hr@5=a3+~xKJz#nN2Q1}+Zym?VhY+XmBZI?cpHbe zb2!{uaU2});yh&zk8(KEgFtRw9KM#r6C8dEhr=8d$A{@7rYn$3=yTKD0rZd9J}~{6 zAu%$T=9cDT-{!;gFM#P^0MlO&-KdA@UkF#Ua38h@`?Kg#m=2tghTU*SIIoN0!W%yc z`W4*KX3$Z%BeAh$M+~HoECS8%R*Y8ZoV8d3r?i6y$fAx}T#+ z&3t$cC*vr#%|^2*}0oF;OLOU>ZK-H zVOmerR8LKP=XZU7I(+_g|BoJS`;ByZ*`>dX#S&doyQEr56N-{R zMSlkxbbfeq-|g*PQty_HXsPKcDmGg7xS(;Fu?DN7h zlw61940bXu9U0Fw(8jX8td(=SB~?i)YBDO}6CxQ+NSdBDEOjUu?UuV`Ny#b3xT`1S zv3R#U7LCUxQ%{bV8Oa?`lrBn7e%2^GINT(D{u|2<(&MGcr zl67>fD=ux-vx6Bg4Q7_D=Td`~joLhDqr;FsjcsjzFMW01iHl#4H$78-=&7z- z4xjYDkRrje_QmgaED5ki<63Bk!l=n^X-e2CzC5w+{s z<0>4<%Q!UP3&&p^b*3;Qfwx5h!@V%cR_`OT+jo9+>Wx=Ks94RF--nr3P~jEgQFw(I zyauYL9p1w7Qi3SIZ& z%Mf(K7%uP7rKTTw9q2M{o*u$IJTOVZS6C0xX!(fObUFh3+6;^T0SSbM0*w|PoaGr8 z*^7+GZM?OX0CtN#FS<<~4c6f5rYbqvVZ z(nW@16+U&ug<=(SORYVicoAHevAaoT#n7$^<57MaF=7Y|I1}+YI4_; zWvcsMx&r+1wrR28bqL9r)&9TvrLxR$iVManI16#X*fgu{@3ydztX$xtgRx>%1PvD# nl9krXg0TnQ0l!rC9a$sfC&QIzd5O0gqJFh2Q*qv;=F9&8=0zb8 literal 0 HcmV?d00001 diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index 4a316afd22..95f79391ef 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -18,6 +18,7 @@ namespace osu.Game.Rulesets.Edit // Resources new CheckBackgroundPresence(), new CheckBackgroundQuality(), + new CheckVideoResolution(), // Audio new CheckAudioPresence(), diff --git a/osu.Game/Rulesets/Edit/Checks/CheckVideoResolution.cs b/osu.Game/Rulesets/Edit/Checks/CheckVideoResolution.cs new file mode 100644 index 0000000000..831962e2e9 --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checks/CheckVideoResolution.cs @@ -0,0 +1,117 @@ +// 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.Collections.Generic; +using System.IO; +using osu.Framework.Logging; +using osu.Game.Beatmaps; +using osu.Game.IO.FileAbstraction; +using osu.Game.Rulesets.Edit.Checks.Components; +using osu.Game.Storyboards; +using TagLib; +using File = TagLib.File; + +namespace osu.Game.Rulesets.Edit.Checks +{ + public class CheckVideoResolution : ICheck + { + private const int max_video_width = 1280; + + private const int max_video_height = 720; + + public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Resources, "Too high video resolution."); + + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplateHighResolution(this), + new IssueTemplateFileError(this), + }; + + public IEnumerable Run(BeatmapVerifierContext context) + { + var beatmapSet = context.Beatmap.BeatmapInfo.BeatmapSet; + var videoPaths = getVideoPaths(context.WorkingBeatmap.Storyboard); + + foreach (string filename in videoPaths) + { + string? storagePath = beatmapSet?.GetPathForFile(filename); + + // Don't report any issues for missing video here since another check is already doing that (CheckAudioInVideo) + if (storagePath == null) continue; + + Issue issue; + + try + { + using (Stream data = context.WorkingBeatmap.GetStream(storagePath)) + using (File tagFile = File.Create(new StreamFileAbstraction(filename, data))) + { + int height = tagFile.Properties.VideoHeight; + int width = tagFile.Properties.VideoWidth; + + if (height <= max_video_height || width <= max_video_width) + continue; + + issue = new IssueTemplateHighResolution(this).Create(filename, width, height); + } + } + catch (CorruptFileException) + { + issue = new IssueTemplateFileError(this).Create(filename, "Corrupt file"); + } + catch (UnsupportedFormatException) + { + issue = new IssueTemplateFileError(this).Create(filename, "Unsupported format"); + } + catch (Exception ex) + { + issue = new IssueTemplateFileError(this).Create(filename, "Internal failure - see logs for more info"); + Logger.Log($"Failed when running {nameof(CheckVideoResolution)}: {ex}"); + } + + yield return issue; + } + } + + private List getVideoPaths(Storyboard storyboard) + { + var videoPaths = new List(); + + foreach (var layer in storyboard.Layers) + { + foreach (var element in layer.Elements) + { + if (element is not StoryboardVideo video) + continue; + + if (!videoPaths.Contains(video.Path)) + videoPaths.Add(video.Path); + } + } + + return videoPaths; + } + + public class IssueTemplateHighResolution : IssueTemplate + { + public IssueTemplateHighResolution(ICheck check) + : base(check, IssueType.Problem, "\"{0}\" resolution exceeds 1280x720 ({1}x{2})") + { + } + + public Issue Create(string filename, int width, int height) => new Issue(this, filename, width, height); + } + + public class IssueTemplateFileError : IssueTemplate + { + public IssueTemplateFileError(ICheck check) + : base(check, IssueType.Error, "Could not check resolution for \"{0}\" ({1}).") + { + } + + public Issue Create(string filename, string errorReason) => new Issue(this, filename, errorReason); + } + + } +} From eb938272040be14caa292fe65800356a6ae03a1a Mon Sep 17 00:00:00 2001 From: Arthur Araujo Date: Sat, 23 Mar 2024 23:11:13 -0300 Subject: [PATCH 3534/4092] Add missing copyright header --- osu.Game.Tests/Editing/Checks/CheckVideoResolutionTest.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Editing/Checks/CheckVideoResolutionTest.cs b/osu.Game.Tests/Editing/Checks/CheckVideoResolutionTest.cs index ab677a15d4..1e16c67aab 100644 --- a/osu.Game.Tests/Editing/Checks/CheckVideoResolutionTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckVideoResolutionTest.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using System.IO; using System.Linq; using Moq; From 8a05fecad5862d744d49b8febcb389e4e22b9850 Mon Sep 17 00:00:00 2001 From: Arthur Araujo Date: Sat, 23 Mar 2024 23:28:55 -0300 Subject: [PATCH 3535/4092] Fix formatting issue --- osu.Game/Rulesets/Edit/Checks/CheckVideoResolution.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckVideoResolution.cs b/osu.Game/Rulesets/Edit/Checks/CheckVideoResolution.cs index 831962e2e9..1b603b7e47 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckVideoResolution.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckVideoResolution.cs @@ -112,6 +112,5 @@ namespace osu.Game.Rulesets.Edit.Checks public Issue Create(string filename, string errorReason) => new Issue(this, filename, errorReason); } - } } From ef2a16dd8ff260efa5117a10352e6ec771dfa2c3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Mar 2024 16:41:40 +0800 Subject: [PATCH 3536/4092] Various renaming and class updates to allow multiple menu banners --- .../Visual/Menus/TestSceneMainMenu.cs | 46 +++++++++----- ...tleRequest.cs => GetMenuContentRequest.cs} | 4 +- .../API/Requests/Responses/APIMenuContent.cs | 42 +++++++++++++ .../API/Requests/Responses/APIMenuImage.cs | 57 +++++++++++++++++ .../API/Requests/Responses/APISystemTitle.cs | 30 --------- osu.Game/Screens/Menu/MainMenu.cs | 8 +-- .../{SystemTitle.cs => OnlineMenuBanner.cs} | 62 ++++++++++--------- 7 files changed, 169 insertions(+), 80 deletions(-) rename osu.Game/Online/API/Requests/{GetSystemTitleRequest.cs => GetMenuContentRequest.cs} (74%) create mode 100644 osu.Game/Online/API/Requests/Responses/APIMenuContent.cs create mode 100644 osu.Game/Online/API/Requests/Responses/APIMenuImage.cs delete mode 100644 osu.Game/Online/API/Requests/Responses/APISystemTitle.cs rename osu.Game/Screens/Menu/{SystemTitle.cs => OnlineMenuBanner.cs} (79%) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs index 7053a9d544..3c78edb8a5 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs @@ -13,30 +13,48 @@ namespace osu.Game.Tests.Visual.Menus { public partial class TestSceneMainMenu : OsuGameTestScene { - private SystemTitle systemTitle => Game.ChildrenOfType().Single(); + private OnlineMenuBanner onlineMenuBanner => Game.ChildrenOfType().Single(); [Test] - public void TestSystemTitle() + public void TestOnlineMenuBanner() { - AddStep("set system title", () => systemTitle.Current.Value = new APISystemTitle + AddStep("set online content", () => onlineMenuBanner.Current.Value = new APIMenuContent { - Image = @"https://assets.ppy.sh/main-menu/project-loved-2@2x.png", - Url = @"https://osu.ppy.sh/home/news/2023-12-21-project-loved-december-2023", + Images = new[] + { + new APIMenuImage + { + Image = @"https://assets.ppy.sh/main-menu/project-loved-2@2x.png", + Url = @"https://osu.ppy.sh/home/news/2023-12-21-project-loved-december-2023", + } + } }); - AddAssert("system title not visible", () => systemTitle.State.Value, () => Is.EqualTo(Visibility.Hidden)); + AddAssert("system title not visible", () => onlineMenuBanner.State.Value, () => Is.EqualTo(Visibility.Hidden)); AddStep("enter menu", () => InputManager.Key(Key.Enter)); - AddUntilStep("system title visible", () => systemTitle.State.Value, () => Is.EqualTo(Visibility.Visible)); - AddStep("set another title", () => systemTitle.Current.Value = new APISystemTitle + AddUntilStep("system title visible", () => onlineMenuBanner.State.Value, () => Is.EqualTo(Visibility.Visible)); + AddStep("set another title", () => onlineMenuBanner.Current.Value = new APIMenuContent { - Image = @"https://assets.ppy.sh/main-menu/wf2023-vote@2x.png", - Url = @"https://osu.ppy.sh/community/contests/189", + Images = new[] + { + new APIMenuImage + { + Image = @"https://assets.ppy.sh/main-menu/wf2023-vote@2x.png", + Url = @"https://osu.ppy.sh/community/contests/189", + } + } }); - AddStep("set title with nonexistent image", () => systemTitle.Current.Value = new APISystemTitle + AddStep("set title with nonexistent image", () => onlineMenuBanner.Current.Value = new APIMenuContent { - Image = @"https://test.invalid/@2x", // .invalid TLD reserved by https://datatracker.ietf.org/doc/html/rfc2606#section-2 - Url = @"https://osu.ppy.sh/community/contests/189", + Images = new[] + { + new APIMenuImage + { + Image = @"https://test.invalid/@2x", // .invalid TLD reserved by https://datatracker.ietf.org/doc/html/rfc2606#section-2 + Url = @"https://osu.ppy.sh/community/contests/189", + } + } }); - AddStep("unset system title", () => systemTitle.Current.Value = null); + AddStep("unset system title", () => onlineMenuBanner.Current.Value = new APIMenuContent()); } } } diff --git a/osu.Game/Online/API/Requests/GetSystemTitleRequest.cs b/osu.Game/Online/API/Requests/GetMenuContentRequest.cs similarity index 74% rename from osu.Game/Online/API/Requests/GetSystemTitleRequest.cs rename to osu.Game/Online/API/Requests/GetMenuContentRequest.cs index 52ca0c11eb..ad2bac6696 100644 --- a/osu.Game/Online/API/Requests/GetSystemTitleRequest.cs +++ b/osu.Game/Online/API/Requests/GetMenuContentRequest.cs @@ -5,9 +5,9 @@ using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests { - public class GetSystemTitleRequest : OsuJsonWebRequest + public class GetMenuContentRequest : OsuJsonWebRequest { - public GetSystemTitleRequest() + public GetMenuContentRequest() : base(@"https://assets.ppy.sh/lazer-status.json") { } diff --git a/osu.Game/Online/API/Requests/Responses/APIMenuContent.cs b/osu.Game/Online/API/Requests/Responses/APIMenuContent.cs new file mode 100644 index 0000000000..acee6c99ba --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIMenuContent.cs @@ -0,0 +1,42 @@ +// 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 Newtonsoft.Json; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIMenuContent : IEquatable + { + /// + /// Images which should be displayed in rotation. + /// + [JsonProperty(@"images")] + public APIMenuImage[] Images { get; init; } = Array.Empty(); + + public DateTimeOffset LastUpdated { get; init; } + + public bool Equals(APIMenuContent? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + + return LastUpdated.Equals(other.LastUpdated); + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + + if (obj.GetType() != GetType()) return false; + + return Equals((APIMenuContent)obj); + } + + public override int GetHashCode() + { + return LastUpdated.GetHashCode(); + } + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIMenuImage.cs b/osu.Game/Online/API/Requests/Responses/APIMenuImage.cs new file mode 100644 index 0000000000..4824e23d4b --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIMenuImage.cs @@ -0,0 +1,57 @@ +// 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 Newtonsoft.Json; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIMenuImage : IEquatable + { + /// + /// A URL pointing to the image which should be displayed. Generally should be an @2x image filename. + /// + [JsonProperty(@"image")] + public string Image { get; init; } = string.Empty; + + /// + /// A URL that should be opened on clicking the image. + /// + [JsonProperty(@"url")] + public string Url { get; init; } = string.Empty; + + /// + /// The time at which this item should begin displaying. If null, will display immediately. + /// + [JsonProperty(@"begins")] + public DateTimeOffset? Begins { get; set; } + + /// + /// The time at which this item should stop displaying. If null, will display indefinitely. + /// + [JsonProperty(@"expires")] + public DateTimeOffset? Expires { get; set; } + + public bool Equals(APIMenuImage? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + + return Image == other.Image && Url == other.Url; + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + + return Equals((APIMenuImage)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(Image, Url); + } + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APISystemTitle.cs b/osu.Game/Online/API/Requests/Responses/APISystemTitle.cs deleted file mode 100644 index bfa5c1043b..0000000000 --- a/osu.Game/Online/API/Requests/Responses/APISystemTitle.cs +++ /dev/null @@ -1,30 +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 System; -using Newtonsoft.Json; - -namespace osu.Game.Online.API.Requests.Responses -{ - public class APISystemTitle : IEquatable - { - [JsonProperty(@"image")] - public string Image { get; set; } = string.Empty; - - [JsonProperty(@"url")] - public string Url { get; set; } = string.Empty; - - public bool Equals(APISystemTitle? other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - - return Image == other.Image && Url == other.Url; - } - - public override bool Equals(object? obj) => obj is APISystemTitle other && Equals(other); - - // ReSharper disable NonReadonlyMemberInGetHashCode - public override int GetHashCode() => HashCode.Combine(Image, Url); - } -} diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index decb901c32..235c5d5c56 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -98,7 +98,7 @@ namespace osu.Game.Screens.Menu private ParallaxContainer buttonsContainer; private SongTicker songTicker; private Container logoTarget; - private SystemTitle systemTitle; + private OnlineMenuBanner onlineMenuBanner; private MenuTip menuTip; private FillFlowContainer bottomElementsFlow; private SupporterDisplay supporterDisplay; @@ -178,7 +178,7 @@ namespace osu.Game.Screens.Menu Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, }, - systemTitle = new SystemTitle + onlineMenuBanner = new OnlineMenuBanner { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -201,12 +201,12 @@ namespace osu.Game.Screens.Menu case ButtonSystemState.Initial: case ButtonSystemState.Exit: ApplyToBackground(b => b.FadeColour(Color4.White, 500, Easing.OutSine)); - systemTitle.State.Value = Visibility.Hidden; + onlineMenuBanner.State.Value = Visibility.Hidden; break; default: ApplyToBackground(b => b.FadeColour(OsuColour.Gray(0.8f), 500, Easing.OutSine)); - systemTitle.State.Value = Visibility.Visible; + onlineMenuBanner.State.Value = Visibility.Visible; break; } }; diff --git a/osu.Game/Screens/Menu/SystemTitle.cs b/osu.Game/Screens/Menu/OnlineMenuBanner.cs similarity index 79% rename from osu.Game/Screens/Menu/SystemTitle.cs rename to osu.Game/Screens/Menu/OnlineMenuBanner.cs index 813a470ed6..cf20196f85 100644 --- a/osu.Game/Screens/Menu/SystemTitle.cs +++ b/osu.Game/Screens/Menu/OnlineMenuBanner.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; @@ -18,42 +19,28 @@ using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Screens.Menu { - public partial class SystemTitle : VisibilityContainer + public partial class OnlineMenuBanner : VisibilityContainer { - internal Bindable Current { get; } = new Bindable(); + internal Bindable Current { get; } = new Bindable(new APIMenuContent()); private const float transition_duration = 500; private Container content = null!; private CancellationTokenSource? cancellationTokenSource; - private SystemTitleImage? currentImage; - - private ScheduledDelegate? openUrlAction; + private MenuImage? currentImage; [BackgroundDependencyLoader] - private void load(OsuGame? game) + private void load() { AutoSizeAxes = Axes.Both; AutoSizeDuration = transition_duration; AutoSizeEasing = Easing.OutQuint; - InternalChild = content = new OsuClickableContainer + InternalChild = content = new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, AutoSizeAxes = Axes.Both, - Action = () => - { - currentImage?.Flash(); - - // Delay slightly to allow animation to play out. - openUrlAction?.Cancel(); - openUrlAction = Scheduler.AddDelayed(() => - { - if (!string.IsNullOrEmpty(Current.Value?.Url)) - game?.HandleLink(Current.Value.Url); - }, 250); - } }; } @@ -98,7 +85,7 @@ namespace osu.Game.Screens.Menu private void checkForUpdates() { - var request = new GetSystemTitleRequest(); + var request = new GetMenuContentRequest(); Task.Run(() => request.Perform()) .ContinueWith(r => { @@ -121,12 +108,12 @@ namespace osu.Game.Screens.Menu cancellationTokenSource = null; currentImage?.FadeOut(500, Easing.OutQuint).Expire(); - if (string.IsNullOrEmpty(Current.Value?.Image)) + if (Current.Value.Images.Length == 0) return; - LoadComponentAsync(new SystemTitleImage(Current.Value), loaded => + LoadComponentAsync(new MenuImage(Current.Value.Images.First()), loaded => { - if (!loaded.SystemTitle.Equals(Current.Value)) + if (!loaded.Image.Equals(Current.Value.Images.First())) loaded.Dispose(); content.Add(currentImage = loaded); @@ -134,22 +121,24 @@ namespace osu.Game.Screens.Menu } [LongRunningLoad] - private partial class SystemTitleImage : CompositeDrawable + private partial class MenuImage : OsuClickableContainer { - public readonly APISystemTitle SystemTitle; + public readonly APIMenuImage Image; private Sprite flash = null!; - public SystemTitleImage(APISystemTitle systemTitle) + private ScheduledDelegate? openUrlAction; + + public MenuImage(APIMenuImage image) { - SystemTitle = systemTitle; + Image = image; } [BackgroundDependencyLoader] - private void load(LargeTextureStore textureStore) + private void load(LargeTextureStore textureStore, OsuGame game) { - Texture? texture = textureStore.Get(SystemTitle.Image); - if (texture != null && SystemTitle.Image.Contains(@"@2x")) + Texture? texture = textureStore.Get(Image.Image); + if (texture != null && Image.Image.Contains(@"@2x")) texture.ScaleAdjust *= 2; AutoSizeAxes = Axes.Both; @@ -163,6 +152,19 @@ namespace osu.Game.Screens.Menu Blending = BlendingParameters.Additive, }, }; + + Action = () => + { + Flash(); + + // Delay slightly to allow animation to play out. + openUrlAction?.Cancel(); + openUrlAction = Scheduler.AddDelayed(() => + { + if (!string.IsNullOrEmpty(Image.Url)) + game?.HandleLink(Image.Url); + }, 250); + }; } protected override void LoadComplete() From 4c82e44291fdf23bf324d1d8f4a3092ede9437da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Mar 2024 20:07:17 +0800 Subject: [PATCH 3537/4092] Add isolated test coverage of online menu banner --- .../Visual/Menus/TestSceneMainMenu.cs | 23 ------ .../Visual/Menus/TestSceneOnlineMenuBanner.cs | 71 +++++++++++++++++++ 2 files changed, 71 insertions(+), 23 deletions(-) create mode 100644 osu.Game.Tests/Visual/Menus/TestSceneOnlineMenuBanner.cs diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs index 3c78edb8a5..e2a841d79a 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs @@ -32,29 +32,6 @@ namespace osu.Game.Tests.Visual.Menus AddAssert("system title not visible", () => onlineMenuBanner.State.Value, () => Is.EqualTo(Visibility.Hidden)); AddStep("enter menu", () => InputManager.Key(Key.Enter)); AddUntilStep("system title visible", () => onlineMenuBanner.State.Value, () => Is.EqualTo(Visibility.Visible)); - AddStep("set another title", () => onlineMenuBanner.Current.Value = new APIMenuContent - { - Images = new[] - { - new APIMenuImage - { - Image = @"https://assets.ppy.sh/main-menu/wf2023-vote@2x.png", - Url = @"https://osu.ppy.sh/community/contests/189", - } - } - }); - AddStep("set title with nonexistent image", () => onlineMenuBanner.Current.Value = new APIMenuContent - { - Images = new[] - { - new APIMenuImage - { - Image = @"https://test.invalid/@2x", // .invalid TLD reserved by https://datatracker.ietf.org/doc/html/rfc2606#section-2 - Url = @"https://osu.ppy.sh/community/contests/189", - } - } - }); - AddStep("unset system title", () => onlineMenuBanner.Current.Value = new APIMenuContent()); } } } diff --git a/osu.Game.Tests/Visual/Menus/TestSceneOnlineMenuBanner.cs b/osu.Game.Tests/Visual/Menus/TestSceneOnlineMenuBanner.cs new file mode 100644 index 0000000000..a80212e0a1 --- /dev/null +++ b/osu.Game.Tests/Visual/Menus/TestSceneOnlineMenuBanner.cs @@ -0,0 +1,71 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Screens.Menu; + +namespace osu.Game.Tests.Visual.Menus +{ + public partial class TestSceneOnlineMenuBanner : OsuTestScene + { + private OnlineMenuBanner onlineMenuBanner = null!; + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("Create banner", () => + { + Child = onlineMenuBanner = new OnlineMenuBanner + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + State = { Value = Visibility.Visible } + }; + }); + } + + [Test] + public void TestBasic() + { + AddAssert("system title not visible", () => onlineMenuBanner.State.Value, () => Is.EqualTo(Visibility.Hidden)); + AddStep("set online content", () => onlineMenuBanner.Current.Value = new APIMenuContent + { + Images = new[] + { + new APIMenuImage + { + Image = @"https://assets.ppy.sh/main-menu/project-loved-2@2x.png", + Url = @"https://osu.ppy.sh/home/news/2023-12-21-project-loved-december-2023", + } + }, + }); + AddStep("set another title", () => onlineMenuBanner.Current.Value = new APIMenuContent + { + Images = new[] + { + new APIMenuImage + { + Image = @"https://assets.ppy.sh/main-menu/wf2023-vote@2x.png", + Url = @"https://osu.ppy.sh/community/contests/189", + } + } + }); + AddStep("set title with nonexistent image", () => onlineMenuBanner.Current.Value = new APIMenuContent + { + Images = new[] + { + new APIMenuImage + { + Image = @"https://test.invalid/@2x", // .invalid TLD reserved by https://datatracker.ietf.org/doc/html/rfc2606#section-2 + Url = @"https://osu.ppy.sh/community/contests/189", + } + } + }); + AddStep("unset system title", () => onlineMenuBanner.Current.Value = new APIMenuContent()); + } + } +} From ec4a9a5fdd4ffa1df41b2e26227d27d1277ae8d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Mar 2024 23:07:31 +0800 Subject: [PATCH 3538/4092] Make work again for simple case --- .../API/Requests/Responses/APIMenuContent.cs | 12 ++++++--- osu.Game/Screens/Menu/OnlineMenuBanner.cs | 25 +++++++------------ 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIMenuContent.cs b/osu.Game/Online/API/Requests/Responses/APIMenuContent.cs index acee6c99ba..7b53488030 100644 --- a/osu.Game/Online/API/Requests/Responses/APIMenuContent.cs +++ b/osu.Game/Online/API/Requests/Responses/APIMenuContent.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using Newtonsoft.Json; namespace osu.Game.Online.API.Requests.Responses @@ -14,14 +15,12 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"images")] public APIMenuImage[] Images { get; init; } = Array.Empty(); - public DateTimeOffset LastUpdated { get; init; } - public bool Equals(APIMenuContent? other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; - return LastUpdated.Equals(other.LastUpdated); + return Images.SequenceEqual(other.Images); } public override bool Equals(object? obj) @@ -36,7 +35,12 @@ namespace osu.Game.Online.API.Requests.Responses public override int GetHashCode() { - return LastUpdated.GetHashCode(); + var hash = new HashCode(); + + foreach (var image in Images) + hash.Add(image.GetHashCode()); + + return hash.ToHashCode(); } } } diff --git a/osu.Game/Screens/Menu/OnlineMenuBanner.cs b/osu.Game/Screens/Menu/OnlineMenuBanner.cs index cf20196f85..613a6eed4c 100644 --- a/osu.Game/Screens/Menu/OnlineMenuBanner.cs +++ b/osu.Game/Screens/Menu/OnlineMenuBanner.cs @@ -78,7 +78,7 @@ namespace osu.Game.Screens.Menu { base.LoadComplete(); - Current.BindValueChanged(_ => loadNewImage(), true); + Current.BindValueChanged(_ => loadNewImages(), true); checkForUpdates(); } @@ -102,7 +102,7 @@ namespace osu.Game.Screens.Menu }); } - private void loadNewImage() + private void loadNewImages() { cancellationTokenSource?.Cancel(); cancellationTokenSource = null; @@ -131,19 +131,19 @@ namespace osu.Game.Screens.Menu public MenuImage(APIMenuImage image) { + AutoSizeAxes = Axes.Both; + Image = image; } [BackgroundDependencyLoader] - private void load(LargeTextureStore textureStore, OsuGame game) + private void load(LargeTextureStore textureStore, OsuGame? game) { Texture? texture = textureStore.Get(Image.Image); if (texture != null && Image.Image.Contains(@"@2x")) texture.ScaleAdjust *= 2; - AutoSizeAxes = Axes.Both; - - InternalChildren = new Drawable[] + Children = new Drawable[] { new Sprite { Texture = texture }, flash = new Sprite @@ -155,7 +155,9 @@ namespace osu.Game.Screens.Menu Action = () => { - Flash(); + flash.FadeInFromZero(50) + .Then() + .FadeOut(500, Easing.OutQuint); // Delay slightly to allow animation to play out. openUrlAction?.Cancel(); @@ -174,15 +176,6 @@ namespace osu.Game.Screens.Menu this.FadeInFromZero(500, Easing.OutQuint); flash.FadeOutFromOne(4000, Easing.OutQuint); } - - public Drawable Flash() - { - flash.FadeInFromZero(50) - .Then() - .FadeOut(500, Easing.OutQuint); - - return this; - } } } } From a4c619ea97c190a9a4929d89268677af7b72a985 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 24 Mar 2024 15:14:56 +0800 Subject: [PATCH 3539/4092] Add basic support for loading multiple images --- .../Visual/Menus/TestSceneOnlineMenuBanner.cs | 21 ++++- osu.Game/Screens/Menu/OnlineMenuBanner.cs | 80 ++++++++++--------- 2 files changed, 64 insertions(+), 37 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneOnlineMenuBanner.cs b/osu.Game.Tests/Visual/Menus/TestSceneOnlineMenuBanner.cs index a80212e0a1..4cc379a18b 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneOnlineMenuBanner.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneOnlineMenuBanner.cs @@ -31,7 +31,6 @@ namespace osu.Game.Tests.Visual.Menus [Test] public void TestBasic() { - AddAssert("system title not visible", () => onlineMenuBanner.State.Value, () => Is.EqualTo(Visibility.Hidden)); AddStep("set online content", () => onlineMenuBanner.Current.Value = new APIMenuContent { Images = new[] @@ -43,6 +42,7 @@ namespace osu.Game.Tests.Visual.Menus } }, }); + AddStep("set another title", () => onlineMenuBanner.Current.Value = new APIMenuContent { Images = new[] @@ -54,6 +54,24 @@ namespace osu.Game.Tests.Visual.Menus } } }); + + AddStep("set multiple images", () => onlineMenuBanner.Current.Value = new APIMenuContent + { + Images = new[] + { + new APIMenuImage + { + Image = @"https://assets.ppy.sh/main-menu/project-loved-2@2x.png", + Url = @"https://osu.ppy.sh/home/news/2023-12-21-project-loved-december-2023", + }, + new APIMenuImage + { + Image = @"https://assets.ppy.sh/main-menu/wf2023-vote@2x.png", + Url = @"https://osu.ppy.sh/community/contests/189", + } + }, + }); + AddStep("set title with nonexistent image", () => onlineMenuBanner.Current.Value = new APIMenuContent { Images = new[] @@ -65,6 +83,7 @@ namespace osu.Game.Tests.Visual.Menus } } }); + AddStep("unset system title", () => onlineMenuBanner.Current.Value = new APIMenuContent()); } } diff --git a/osu.Game/Screens/Menu/OnlineMenuBanner.cs b/osu.Game/Screens/Menu/OnlineMenuBanner.cs index 613a6eed4c..587a93fb67 100644 --- a/osu.Game/Screens/Menu/OnlineMenuBanner.cs +++ b/osu.Game/Screens/Menu/OnlineMenuBanner.cs @@ -27,7 +27,6 @@ namespace osu.Game.Screens.Menu private Container content = null!; private CancellationTokenSource? cancellationTokenSource; - private MenuImage? currentImage; [BackgroundDependencyLoader] private void load() @@ -48,32 +47,6 @@ namespace osu.Game.Screens.Menu protected override void PopOut() => content.FadeOut(transition_duration, Easing.OutQuint); - protected override bool OnHover(HoverEvent e) - { - content.ScaleTo(1.05f, 2000, Easing.OutQuint); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - content.ScaleTo(1f, 500, Easing.OutQuint); - base.OnHoverLost(e); - } - - protected override bool OnMouseDown(MouseDownEvent e) - { - content.ScaleTo(0.95f, 500, Easing.OutQuint); - return base.OnMouseDown(e); - } - - protected override void OnMouseUp(MouseUpEvent e) - { - content - .ScaleTo(0.95f) - .ScaleTo(1, 500, Easing.OutElastic); - base.OnMouseUp(e); - } - protected override void LoadComplete() { base.LoadComplete(); @@ -106,17 +79,26 @@ namespace osu.Game.Screens.Menu { cancellationTokenSource?.Cancel(); cancellationTokenSource = null; - currentImage?.FadeOut(500, Easing.OutQuint).Expire(); - if (Current.Value.Images.Length == 0) + var newContent = Current.Value; + + foreach (var i in content) + { + i.FadeOutFromOne(100, Easing.OutQuint) + .Expire(); + } + + if (newContent.Images.Length == 0) return; - LoadComponentAsync(new MenuImage(Current.Value.Images.First()), loaded => + LoadComponentsAsync(newContent.Images.Select(i => new MenuImage(i)), loaded => { - if (!loaded.Image.Equals(Current.Value.Images.First())) - loaded.Dispose(); + if (!newContent.Equals(Current.Value)) + return; - content.Add(currentImage = loaded); + content.AddRange(loaded); + + loaded.First().Show(); }, (cancellationTokenSource ??= new CancellationTokenSource()).Token); } @@ -132,6 +114,8 @@ namespace osu.Game.Screens.Menu public MenuImage(APIMenuImage image) { AutoSizeAxes = Axes.Both; + Anchor = Anchor.BottomCentre; + Origin = Anchor.BottomCentre; Image = image; } @@ -169,13 +153,37 @@ namespace osu.Game.Screens.Menu }; } - protected override void LoadComplete() + public override void Show() { - base.LoadComplete(); - this.FadeInFromZero(500, Easing.OutQuint); flash.FadeOutFromOne(4000, Easing.OutQuint); } + + protected override bool OnHover(HoverEvent e) + { + this.ScaleTo(1.05f, 2000, Easing.OutQuint); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + this.ScaleTo(1f, 500, Easing.OutQuint); + base.OnHoverLost(e); + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + this.ScaleTo(0.95f, 500, Easing.OutQuint); + return true; + } + + protected override void OnMouseUp(MouseUpEvent e) + { + this + .ScaleTo(0.95f) + .ScaleTo(1, 500, Easing.OutElastic); + base.OnMouseUp(e); + } } } } From d0b164b44f6d986f965c469cd838bfacbe0a3de6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 24 Mar 2024 23:37:30 +0800 Subject: [PATCH 3540/4092] Add automatic rotation support --- osu.Game/Screens/Menu/OnlineMenuBanner.cs | 38 +++++++++++++++++++---- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Menu/OnlineMenuBanner.cs b/osu.Game/Screens/Menu/OnlineMenuBanner.cs index 587a93fb67..a4648265ae 100644 --- a/osu.Game/Screens/Menu/OnlineMenuBanner.cs +++ b/osu.Game/Screens/Menu/OnlineMenuBanner.cs @@ -28,6 +28,10 @@ namespace osu.Game.Screens.Menu private Container content = null!; private CancellationTokenSource? cancellationTokenSource; + private int displayIndex = -1; + + private ScheduledDelegate? nextDisplay; + [BackgroundDependencyLoader] private void load() { @@ -77,16 +81,16 @@ namespace osu.Game.Screens.Menu private void loadNewImages() { + nextDisplay?.Cancel(); + cancellationTokenSource?.Cancel(); cancellationTokenSource = null; var newContent = Current.Value; - foreach (var i in content) - { - i.FadeOutFromOne(100, Easing.OutQuint) - .Expire(); - } + // A better fade out would be nice, but the menu content changes *very* rarely + // so let's keep things simple for now. + content.Clear(true); if (newContent.Images.Length == 0) return; @@ -96,12 +100,34 @@ namespace osu.Game.Screens.Menu if (!newContent.Equals(Current.Value)) return; + // start hidden + foreach (var image in loaded) + image.Hide(); + content.AddRange(loaded); - loaded.First().Show(); + displayIndex = -1; + showNext(); }, (cancellationTokenSource ??= new CancellationTokenSource()).Token); } + private void showNext() + { + nextDisplay?.Cancel(); + + bool previousShowing = displayIndex >= 0; + if (previousShowing) + content[displayIndex % content.Count].FadeOut(400, Easing.OutQuint); + + displayIndex++; + + using (BeginDelayedSequence(previousShowing ? 300 : 0)) + content[displayIndex % content.Count].Show(); + + if (content.Count > 1) + nextDisplay = Scheduler.AddDelayed(showNext, 12000); + } + [LongRunningLoad] private partial class MenuImage : OsuClickableContainer { From 3847aae57d88ea2156c7599c53a0e994fffd4760 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Mar 2024 12:14:40 +0800 Subject: [PATCH 3541/4092] Don't rotate when hovering --- osu.Game/Screens/Menu/OnlineMenuBanner.cs | 26 ++++++++++++++++------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Menu/OnlineMenuBanner.cs b/osu.Game/Screens/Menu/OnlineMenuBanner.cs index a4648265ae..74062d5b9f 100644 --- a/osu.Game/Screens/Menu/OnlineMenuBanner.cs +++ b/osu.Game/Screens/Menu/OnlineMenuBanner.cs @@ -21,6 +21,8 @@ namespace osu.Game.Screens.Menu { public partial class OnlineMenuBanner : VisibilityContainer { + public double DelayBetweenRotation = 7500; + internal Bindable Current { get; } = new Bindable(new APIMenuContent()); private const float transition_duration = 500; @@ -115,21 +117,29 @@ namespace osu.Game.Screens.Menu { nextDisplay?.Cancel(); - bool previousShowing = displayIndex >= 0; - if (previousShowing) - content[displayIndex % content.Count].FadeOut(400, Easing.OutQuint); + // If the user is hovering a banner, don't rotate yet. + bool anyHovered = content.Any(i => i.IsHovered); - displayIndex++; + if (!anyHovered) + { + bool previousShowing = displayIndex >= 0; + if (previousShowing) + content[displayIndex % content.Count].FadeOut(400, Easing.OutQuint); - using (BeginDelayedSequence(previousShowing ? 300 : 0)) - content[displayIndex % content.Count].Show(); + displayIndex++; + + using (BeginDelayedSequence(previousShowing ? 300 : 0)) + content[displayIndex % content.Count].Show(); + } if (content.Count > 1) - nextDisplay = Scheduler.AddDelayed(showNext, 12000); + { + nextDisplay = Scheduler.AddDelayed(showNext, DelayBetweenRotation); + } } [LongRunningLoad] - private partial class MenuImage : OsuClickableContainer + public partial class MenuImage : OsuClickableContainer { public readonly APIMenuImage Image; From e9f15534ed7ea64b7cfe77360f488b3a63e703db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Mar 2024 12:14:47 +0800 Subject: [PATCH 3542/4092] Improve test coverage --- .../Visual/Menus/TestSceneOnlineMenuBanner.cs | 75 ++++++++++++++++--- osu.Game/Screens/Menu/OnlineMenuBanner.cs | 7 +- 2 files changed, 71 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneOnlineMenuBanner.cs b/osu.Game.Tests/Visual/Menus/TestSceneOnlineMenuBanner.cs index 4cc379a18b..6be5b80983 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneOnlineMenuBanner.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneOnlineMenuBanner.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 System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -21,6 +22,8 @@ namespace osu.Game.Tests.Visual.Menus { Child = onlineMenuBanner = new OnlineMenuBanner { + FetchOnlineContent = false, + DelayBetweenRotation = 500, Anchor = Anchor.Centre, Origin = Anchor.Centre, State = { Value = Visibility.Visible } @@ -43,6 +46,18 @@ namespace osu.Game.Tests.Visual.Menus }, }); + AddUntilStep("wait for one image shown", () => + { + var images = onlineMenuBanner.ChildrenOfType(); + + if (images.Count() != 1) + return false; + + var image = images.Single(); + + return image.IsPresent && image.Image.Url == "https://osu.ppy.sh/home/news/2023-12-21-project-loved-december-2023"; + }); + AddStep("set another title", () => onlineMenuBanner.Current.Value = new APIMenuContent { Images = new[] @@ -55,6 +70,40 @@ namespace osu.Game.Tests.Visual.Menus } }); + AddUntilStep("wait for new image shown", () => + { + var images = onlineMenuBanner.ChildrenOfType(); + + if (images.Count() != 1) + return false; + + var image = images.Single(); + + return image.IsPresent && image.Image.Url == "https://osu.ppy.sh/community/contests/189"; + }); + + AddStep("set title with nonexistent image", () => onlineMenuBanner.Current.Value = new APIMenuContent + { + Images = new[] + { + new APIMenuImage + { + Image = @"https://test.invalid/@2x", // .invalid TLD reserved by https://datatracker.ietf.org/doc/html/rfc2606#section-2 + Url = @"https://osu.ppy.sh/community/contests/189", + } + } + }); + + AddUntilStep("wait for no image shown", () => !onlineMenuBanner.ChildrenOfType().Any()); + + AddStep("unset system title", () => onlineMenuBanner.Current.Value = new APIMenuContent()); + + AddUntilStep("wait for no image shown", () => !onlineMenuBanner.ChildrenOfType().Any()); + } + + [Test] + public void TestMultipleImages() + { AddStep("set multiple images", () => onlineMenuBanner.Current.Value = new APIMenuContent { Images = new[] @@ -72,19 +121,25 @@ namespace osu.Game.Tests.Visual.Menus }, }); - AddStep("set title with nonexistent image", () => onlineMenuBanner.Current.Value = new APIMenuContent + AddUntilStep("wait for first image shown", () => { - Images = new[] - { - new APIMenuImage - { - Image = @"https://test.invalid/@2x", // .invalid TLD reserved by https://datatracker.ietf.org/doc/html/rfc2606#section-2 - Url = @"https://osu.ppy.sh/community/contests/189", - } - } + var images = onlineMenuBanner.ChildrenOfType(); + + if (images.Count() != 2) + return false; + + return images.First().IsPresent && !images.Last().IsPresent; }); - AddStep("unset system title", () => onlineMenuBanner.Current.Value = new APIMenuContent()); + AddUntilStep("wait for second image shown", () => + { + var images = onlineMenuBanner.ChildrenOfType(); + + if (images.Count() != 2) + return false; + + return !images.First().IsPresent && images.Last().IsPresent; + }); } } } diff --git a/osu.Game/Screens/Menu/OnlineMenuBanner.cs b/osu.Game/Screens/Menu/OnlineMenuBanner.cs index 74062d5b9f..2ab6417370 100644 --- a/osu.Game/Screens/Menu/OnlineMenuBanner.cs +++ b/osu.Game/Screens/Menu/OnlineMenuBanner.cs @@ -21,7 +21,9 @@ namespace osu.Game.Screens.Menu { public partial class OnlineMenuBanner : VisibilityContainer { - public double DelayBetweenRotation = 7500; + public double DelayBetweenRotation { get; set; } = 7500; + + public bool FetchOnlineContent { get; set; } = true; internal Bindable Current { get; } = new Bindable(new APIMenuContent()); @@ -64,6 +66,9 @@ namespace osu.Game.Screens.Menu private void checkForUpdates() { + if (!FetchOnlineContent) + return; + var request = new GetMenuContentRequest(); Task.Run(() => request.Perform()) .ContinueWith(r => From f0614928b182bf2d575c5e298e29d2254fd28ecb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Mar 2024 13:19:12 +0800 Subject: [PATCH 3543/4092] Read from new location --- osu.Game/Online/API/Requests/GetMenuContentRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/GetMenuContentRequest.cs b/osu.Game/Online/API/Requests/GetMenuContentRequest.cs index ad2bac6696..26747489d6 100644 --- a/osu.Game/Online/API/Requests/GetMenuContentRequest.cs +++ b/osu.Game/Online/API/Requests/GetMenuContentRequest.cs @@ -8,7 +8,7 @@ namespace osu.Game.Online.API.Requests public class GetMenuContentRequest : OsuJsonWebRequest { public GetMenuContentRequest() - : base(@"https://assets.ppy.sh/lazer-status.json") + : base(@"https://assets.ppy.sh/menu-content.json") { } } From 057f86dd145dd5d0ba573737ca61aceb27dcc70a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Mar 2024 14:28:23 +0800 Subject: [PATCH 3544/4092] Add handling of expiration --- .../Visual/Menus/TestSceneOnlineMenuBanner.cs | 60 +++++++++++++++++++ .../API/Requests/Responses/APIMenuImage.cs | 4 ++ osu.Game/Screens/Menu/OnlineMenuBanner.cs | 60 ++++++++++++------- 3 files changed, 102 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneOnlineMenuBanner.cs b/osu.Game.Tests/Visual/Menus/TestSceneOnlineMenuBanner.cs index 6be5b80983..2dd08ce306 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneOnlineMenuBanner.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneOnlineMenuBanner.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 System; using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; @@ -141,5 +142,64 @@ namespace osu.Game.Tests.Visual.Menus return !images.First().IsPresent && images.Last().IsPresent; }); } + + [Test] + public void TestExpiry() + { + AddStep("set multiple images, second expiring soon", () => onlineMenuBanner.Current.Value = new APIMenuContent + { + Images = new[] + { + new APIMenuImage + { + Image = @"https://assets.ppy.sh/main-menu/project-loved-2@2x.png", + Url = @"https://osu.ppy.sh/home/news/2023-12-21-project-loved-december-2023", + }, + new APIMenuImage + { + Image = @"https://assets.ppy.sh/main-menu/wf2023-vote@2x.png", + Url = @"https://osu.ppy.sh/community/contests/189", + Expires = DateTimeOffset.Now.AddSeconds(2), + } + }, + }); + + AddUntilStep("wait for first image shown", () => + { + var images = onlineMenuBanner.ChildrenOfType(); + + if (images.Count() != 2) + return false; + + return images.First().IsPresent && !images.Last().IsPresent; + }); + + AddUntilStep("wait for second image shown", () => + { + var images = onlineMenuBanner.ChildrenOfType(); + + if (images.Count() != 2) + return false; + + return !images.First().IsPresent && images.Last().IsPresent; + }); + + AddUntilStep("wait for expiry", () => + { + return onlineMenuBanner + .ChildrenOfType() + .Any(i => !i.Image.IsCurrent); + }); + + AddUntilStep("wait for first image shown", () => + { + var images = onlineMenuBanner.ChildrenOfType(); + + if (images.Count() != 2) + return false; + + return images.First().IsPresent && !images.Last().IsPresent; + }); + } } } diff --git a/osu.Game/Online/API/Requests/Responses/APIMenuImage.cs b/osu.Game/Online/API/Requests/Responses/APIMenuImage.cs index 4824e23d4b..42129ca96e 100644 --- a/osu.Game/Online/API/Requests/Responses/APIMenuImage.cs +++ b/osu.Game/Online/API/Requests/Responses/APIMenuImage.cs @@ -20,6 +20,10 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"url")] public string Url { get; init; } = string.Empty; + public bool IsCurrent => + (Begins == null || Begins < DateTimeOffset.UtcNow) && + (Expires == null || Expires > DateTimeOffset.UtcNow); + /// /// The time at which this item should begin displaying. If null, will display immediately. /// diff --git a/osu.Game/Screens/Menu/OnlineMenuBanner.cs b/osu.Game/Screens/Menu/OnlineMenuBanner.cs index 2ab6417370..55ceb84d7b 100644 --- a/osu.Game/Screens/Menu/OnlineMenuBanner.cs +++ b/osu.Game/Screens/Menu/OnlineMenuBanner.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Menu private const float transition_duration = 500; - private Container content = null!; + private Container content = null!; private CancellationTokenSource? cancellationTokenSource; private int displayIndex = -1; @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Menu AutoSizeDuration = transition_duration; AutoSizeEasing = Easing.OutQuint; - InternalChild = content = new Container + InternalChild = content = new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -59,8 +59,7 @@ namespace osu.Game.Screens.Menu { base.LoadComplete(); - Current.BindValueChanged(_ => loadNewImages(), true); - + Current.BindValueChanged(loadNewImages, true); checkForUpdates(); } @@ -86,25 +85,24 @@ namespace osu.Game.Screens.Menu }); } - private void loadNewImages() + /// + /// Takes and materialises and displays drawables for all valid images to be displayed. + /// + /// + private void loadNewImages(ValueChangedEvent images) { nextDisplay?.Cancel(); cancellationTokenSource?.Cancel(); cancellationTokenSource = null; - var newContent = Current.Value; - // A better fade out would be nice, but the menu content changes *very* rarely // so let's keep things simple for now. content.Clear(true); - if (newContent.Images.Length == 0) - return; - - LoadComponentsAsync(newContent.Images.Select(i => new MenuImage(i)), loaded => + LoadComponentsAsync(images.NewValue.Images.Select(i => new MenuImage(i)), loaded => { - if (!newContent.Equals(Current.Value)) + if (!images.NewValue.Equals(Current.Value)) return; // start hidden @@ -127,20 +125,38 @@ namespace osu.Game.Screens.Menu if (!anyHovered) { - bool previousShowing = displayIndex >= 0; - if (previousShowing) - content[displayIndex % content.Count].FadeOut(400, Easing.OutQuint); + int previousIndex = displayIndex; - displayIndex++; + if (displayIndex == -1) + displayIndex = 0; - using (BeginDelayedSequence(previousShowing ? 300 : 0)) - content[displayIndex % content.Count].Show(); + // To handle expiration simply, arrange all images in best-next order. + // Fade in the first valid one, then handle fading out the last if required. + var currentRotation = content + .Skip(displayIndex + 1) + .Concat(content.Take(displayIndex + 1)); + + foreach (var image in currentRotation) + { + if (!image.Image.IsCurrent) continue; + + using (BeginDelayedSequence(previousIndex >= 0 ? 300 : 0)) + { + displayIndex = content.IndexOf(image); + + if (displayIndex != previousIndex) + image.Show(); + + break; + } + } + + if (previousIndex >= 0 && previousIndex != displayIndex) + content[previousIndex].FadeOut(400, Easing.OutQuint); } - if (content.Count > 1) - { - nextDisplay = Scheduler.AddDelayed(showNext, DelayBetweenRotation); - } + // Re-scheduling this method will both handle rotation and re-checking for expiration dates. + nextDisplay = Scheduler.AddDelayed(showNext, DelayBetweenRotation); } [LongRunningLoad] From bb9fa52fda51f46e4e0fe39e2be8196ab0dc0fba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Mar 2024 14:53:05 +0800 Subject: [PATCH 3545/4092] Fix `displayIndex` not being correctly set to `-1` after last expiry date --- .../Visual/Menus/TestSceneOnlineMenuBanner.cs | 36 ++++++++++++++++++- osu.Game/Screens/Menu/OnlineMenuBanner.cs | 11 +++--- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneOnlineMenuBanner.cs b/osu.Game.Tests/Visual/Menus/TestSceneOnlineMenuBanner.cs index 2dd08ce306..380085ce04 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneOnlineMenuBanner.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneOnlineMenuBanner.cs @@ -144,7 +144,41 @@ namespace osu.Game.Tests.Visual.Menus } [Test] - public void TestExpiry() + public void TestFutureSingle() + { + AddStep("set image with time constraints", () => onlineMenuBanner.Current.Value = new APIMenuContent + { + Images = new[] + { + new APIMenuImage + { + Image = @"https://assets.ppy.sh/main-menu/project-loved-2@2x.png", + Url = @"https://osu.ppy.sh/home/news/2023-12-21-project-loved-december-2023", + Begins = DateTimeOffset.Now.AddSeconds(2), + Expires = DateTimeOffset.Now.AddSeconds(5), + }, + }, + }); + + AddUntilStep("wait for no image shown", () => !onlineMenuBanner.ChildrenOfType().Any(i => i.IsPresent)); + + AddUntilStep("wait for one image shown", () => + { + var images = onlineMenuBanner.ChildrenOfType(); + + if (images.Count() != 1) + return false; + + var image = images.Single(); + + return image.IsPresent && image.Image.Url == "https://osu.ppy.sh/home/news/2023-12-21-project-loved-december-2023"; + }); + + AddUntilStep("wait for no image shown", () => !onlineMenuBanner.ChildrenOfType().Any(i => i.IsPresent)); + } + + [Test] + public void TestExpiryMultiple() { AddStep("set multiple images, second expiring soon", () => onlineMenuBanner.Current.Value = new APIMenuContent { diff --git a/osu.Game/Screens/Menu/OnlineMenuBanner.cs b/osu.Game/Screens/Menu/OnlineMenuBanner.cs index 55ceb84d7b..37bec7aa63 100644 --- a/osu.Game/Screens/Menu/OnlineMenuBanner.cs +++ b/osu.Game/Screens/Menu/OnlineMenuBanner.cs @@ -127,14 +127,15 @@ namespace osu.Game.Screens.Menu { int previousIndex = displayIndex; - if (displayIndex == -1) - displayIndex = 0; - // To handle expiration simply, arrange all images in best-next order. // Fade in the first valid one, then handle fading out the last if required. var currentRotation = content - .Skip(displayIndex + 1) - .Concat(content.Take(displayIndex + 1)); + .Skip(Math.Max(0, previousIndex) + 1) + .Concat(content.Take(Math.Max(0, previousIndex) + 1)); + + // After the loop, displayIndex will be the new valid index or -1 if + // none valid. + displayIndex = -1; foreach (var image in currentRotation) { From 78037fa4773dfc8f8063d45e104bd84131515bcc Mon Sep 17 00:00:00 2001 From: Arthur Araujo Date: Mon, 25 Mar 2024 04:19:14 -0300 Subject: [PATCH 3546/4092] Handle new combo on `HandleReverse` --- .../Edit/CatchSelectionHandler.cs | 33 ++++++++++++++++--- .../Edit/OsuSelectionHandler.cs | 20 +++++++++++ 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs index 418351e2f3..e3d82cc517 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs @@ -76,21 +76,44 @@ namespace osu.Game.Rulesets.Catch.Edit public override bool HandleReverse() { + var hitObjects = EditorBeatmap.SelectedHitObjects; + double selectionStartTime = SelectedItems.Min(h => h.StartTime); double selectionEndTime = SelectedItems.Max(h => h.GetEndTime()); - EditorBeatmap.PerformOnSelection(hitObject => - { - hitObject.StartTime = selectionEndTime - (hitObject.GetEndTime() - selectionStartTime); + var newComboPlaces = hitObjects + .OfType() + .Where(h => h.NewCombo) + .Select(obj => obj.StartTime) + .ToList(); - if (hitObject is JuiceStream juiceStream) + foreach (var h in hitObjects) + { + h.StartTime = selectionEndTime - (h.GetEndTime() - selectionStartTime); + + if (h is JuiceStream juiceStream) { juiceStream.Path.Reverse(out Vector2 positionalOffset); juiceStream.OriginalX += positionalOffset.X; juiceStream.LegacyConvertedY += positionalOffset.Y; EditorBeatmap.Update(juiceStream); } - }); + } + + foreach (var h in hitObjects) + { + if (h is CatchHitObject obj) obj.NewCombo = false; + } + + foreach (double place in newComboPlaces) + { + hitObjects + .OfType() + .Where(obj => obj.StartTime == place) + .ToList() + .ForEach(obj => obj.NewCombo = true); + } + return true; } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index cea2adc6e2..b4980b55d4 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -85,6 +85,12 @@ namespace osu.Game.Rulesets.Osu.Edit bool moreThanOneObject = hitObjects.Count > 1; + var newComboPlaces = hitObjects + .OfType() + .Where(h => h.NewCombo) + .Select(obj => obj.StartTime) + .ToList(); + foreach (var h in hitObjects) { if (moreThanOneObject) @@ -97,6 +103,20 @@ namespace osu.Game.Rulesets.Osu.Edit } } + foreach (var h in hitObjects) + { + if (h is OsuHitObject obj) obj.NewCombo = false; + } + + foreach (double place in newComboPlaces) + { + hitObjects + .OfType() + .Where(obj => obj.StartTime == place) + .ToList() + .ForEach(obj => obj.NewCombo = true); + } + return true; } From fb08d6816ba21cc6cb3351694ff587a2100fc86a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 25 Mar 2024 11:33:15 +0100 Subject: [PATCH 3547/4092] Only attempt to disable rulesets when decidedly crashing out --- osu.Game/OsuGameBase.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 8bda8fb6c2..fb7a238c46 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -678,18 +678,21 @@ namespace osu.Game /// /// Allows a maximum of one unhandled exception, per second of execution. /// + /// Whether to ignore the exception and continue running. private bool onExceptionThrown(Exception ex) { - bool continueExecution = Interlocked.Decrement(ref allowableExceptions) >= 0; - - Logger.Log($"Unhandled exception has been {(continueExecution ? $"allowed with {allowableExceptions} more allowable exceptions" : "denied")} ."); - - RulesetStore.TryDisableCustomRulesetsCausing(ex); + if (Interlocked.Decrement(ref allowableExceptions) < 0) + { + Logger.Log("Too many unhandled exceptions, crashing out."); + RulesetStore.TryDisableCustomRulesetsCausing(ex); + return false; + } + Logger.Log($"Unhandled exception has been allowed with {allowableExceptions} more allowable exceptions."); // restore the stock of allowable exceptions after a short delay. Task.Delay(1000).ContinueWith(_ => Interlocked.Increment(ref allowableExceptions)); - return continueExecution; + return true; } protected override void Dispose(bool isDisposing) From 4979305b2d59188e97efd03e4e49e90aacb32485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 25 Mar 2024 11:34:29 +0100 Subject: [PATCH 3548/4092] Ensure `TryDisableCustomRulesetsCausing()` never actually crashes itself --- osu.Game/Rulesets/RealmRulesetStore.cs | 31 ++++++++++++++++---------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/osu.Game/Rulesets/RealmRulesetStore.cs b/osu.Game/Rulesets/RealmRulesetStore.cs index 36eae7af2c..2455a9a73f 100644 --- a/osu.Game/Rulesets/RealmRulesetStore.cs +++ b/osu.Game/Rulesets/RealmRulesetStore.cs @@ -164,26 +164,33 @@ namespace osu.Game.Rulesets internal void TryDisableCustomRulesetsCausing(Exception exception) { - var stackTrace = new StackTrace(exception); - - foreach (var frame in stackTrace.GetFrames()) + try { - var declaringAssembly = frame.GetMethod()?.DeclaringType?.Assembly; - if (declaringAssembly == null) - continue; + var stackTrace = new StackTrace(exception); - if (UserRulesetAssemblies.Contains(declaringAssembly)) + foreach (var frame in stackTrace.GetFrames()) { - string sourceLocation = declaringAssembly.Location; - string destinationLocation = Path.ChangeExtension(sourceLocation, @".dll.broken"); + var declaringAssembly = frame.GetMethod()?.DeclaringType?.Assembly; + if (declaringAssembly == null) + continue; - if (File.Exists(sourceLocation)) + if (UserRulesetAssemblies.Contains(declaringAssembly)) { - Logger.Log($"Unhandled exception traced back to custom ruleset {Path.GetFileNameWithoutExtension(sourceLocation)}. Marking as broken."); - File.Move(sourceLocation, destinationLocation); + string sourceLocation = declaringAssembly.Location; + string destinationLocation = Path.ChangeExtension(sourceLocation, @".dll.broken"); + + if (File.Exists(sourceLocation)) + { + Logger.Log($"Unhandled exception traced back to custom ruleset {Path.GetFileNameWithoutExtension(sourceLocation)}. Marking as broken."); + File.Move(sourceLocation, destinationLocation); + } } } } + catch (Exception ex) + { + Logger.Log($"Attempt to trace back crash to custom ruleset failed: {ex}"); + } } } } From 3db88fbcea288f5c4136739bd26e0bdab9e0b05a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 25 Mar 2024 17:54:20 +0100 Subject: [PATCH 3549/4092] Use less confusing message format when logging discord errors The "code" is a number, so it looked weird when put in the middle without any nearby punctuation. Example: An error occurred with Discord RPC Client: 5005 secrets cannot currently be sent with buttons --- osu.Desktop/DiscordRichPresence.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index d78459ff28..eaa9a90f27 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -75,7 +75,7 @@ namespace osu.Desktop }; client.OnReady += onReady; - client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network, LogLevel.Error); + client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Message} ({e.Code})", LoggingTarget.Network, LogLevel.Error); // A URI scheme is required to support game invitations, as well as informing Discord of the game executable path to support launching the game when a user clicks on join/spectate. client.RegisterUriScheme(); From e95f29cf4ba8840e0305bbae7637d01ddc03f285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 25 Mar 2024 17:57:13 +0100 Subject: [PATCH 3550/4092] Rename `updatePresence() => schedulePresenceUpdate()` The method doesn't actually update anything by itself, and I want to free up the `updatePresence()` name for the actual update. --- osu.Desktop/DiscordRichPresence.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index eaa9a90f27..811f2f3548 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -94,10 +94,10 @@ namespace osu.Desktop activity.BindTo(u.NewValue.Activity); }, true); - ruleset.BindValueChanged(_ => updatePresence()); - status.BindValueChanged(_ => updatePresence()); - activity.BindValueChanged(_ => updatePresence()); - privacyMode.BindValueChanged(_ => updatePresence()); + ruleset.BindValueChanged(_ => schedulePresenceUpdate()); + status.BindValueChanged(_ => schedulePresenceUpdate()); + activity.BindValueChanged(_ => schedulePresenceUpdate()); + privacyMode.BindValueChanged(_ => schedulePresenceUpdate()); multiplayerClient.RoomUpdated += onRoomUpdated; client.Initialize(); @@ -111,14 +111,14 @@ namespace osu.Desktop if (client.CurrentPresence != null) client.SetPresence(null); - updatePresence(); + schedulePresenceUpdate(); } - private void onRoomUpdated() => updatePresence(); + private void onRoomUpdated() => schedulePresenceUpdate(); private ScheduledDelegate? presenceUpdateDelegate; - private void updatePresence() + private void schedulePresenceUpdate() { presenceUpdateDelegate?.Cancel(); presenceUpdateDelegate = Scheduler.AddDelayed(() => From a398754a27ac26a1f6e57790b2c609226ad805fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 25 Mar 2024 18:00:42 +0100 Subject: [PATCH 3551/4092] Merge all presence methods into one I'm about to make them interdependent (and it's discord's fault), so it doesn't really make sense to make them separate at this point I don't think. And it felt weird anyway. --- osu.Desktop/DiscordRichPresence.cs | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 811f2f3548..d67437ba62 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -134,16 +134,14 @@ namespace osu.Desktop bool hideIdentifiableInformation = privacyMode.Value == DiscordRichPresenceMode.Limited || status.Value == UserStatus.DoNotDisturb; - updatePresenceStatus(hideIdentifiableInformation); - updatePresenceParty(hideIdentifiableInformation); - updatePresenceAssets(); - + updatePresence(hideIdentifiableInformation); client.SetPresence(presence); }, 200); } - private void updatePresenceStatus(bool hideIdentifiableInformation) + private void updatePresence(bool hideIdentifiableInformation) { + // user activity if (activity.Value != null) { presence.State = truncate(activity.Value.GetStatus(hideIdentifiableInformation)); @@ -170,10 +168,8 @@ namespace osu.Desktop presence.State = "Idle"; presence.Details = string.Empty; } - } - private void updatePresenceParty(bool hideIdentifiableInformation) - { + // user party if (!hideIdentifiableInformation && multiplayerClient.Room != null) { MultiplayerRoom room = multiplayerClient.Room; @@ -201,11 +197,9 @@ namespace osu.Desktop presence.Party = null; presence.Secrets.JoinSecret = null; } - } - private void updatePresenceAssets() - { - // update user information + // game images: + // large image tooltip if (privacyMode.Value == DiscordRichPresenceMode.Limited) presence.Assets.LargeImageText = string.Empty; else @@ -216,7 +210,7 @@ namespace osu.Desktop presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.GlobalRank > 0 ? $" (rank #{user.Value.Statistics.GlobalRank:N0})" : string.Empty); } - // update ruleset + // small image presence.Assets.SmallImageKey = ruleset.Value.IsLegacyRuleset() ? $"mode_{ruleset.Value.OnlineID}" : "mode_custom"; presence.Assets.SmallImageText = ruleset.Value.Name; } From 53c3aec3c364a2541180e1bc34b4c40ee688f2b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 25 Mar 2024 18:02:54 +0100 Subject: [PATCH 3552/4092] Fix discord RPC errors in multiplayer Reproduction steps: 1. Go to multiplayer 2. Create a room 3. Play a map to completion 4. Wait for "secrets cannot currently be sent with buttons" error messages The fix is to clear the buttons since they're the less important ones. --- osu.Desktop/DiscordRichPresence.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index d67437ba62..6e8554d617 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -191,6 +191,9 @@ namespace osu.Desktop }; presence.Secrets.JoinSecret = JsonConvert.SerializeObject(roomSecret); + // discord cannot handle both secrets and buttons at the same time, so we need to choose something. + // the multiplayer room seems more important. + presence.Buttons = null; } else { From 6266af8a5696edb32da2f57b877f84dbcab66e3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 25 Mar 2024 18:57:58 +0100 Subject: [PATCH 3553/4092] Fix taiko legacy score simulator not including swell tick score gain into bonus portion Reported in https://discord.com/channels/188630481301012481/1097318920991559880/1221836384038551613. Example score: https://osu.ppy.sh/scores/1855965185 The cause of the overestimation was an error in taiko's score simulator. In lazer taiko, swell ticks don't give any score anymore, while they did in stable. For all intents and purposes, swell ticks can be considered "bonus" objects that "don't give any actual bonus score". Which is to say, during simulation of a legacy score swell ticks hit should be treated as bonus, because if they aren't, then otherwise they will be treated essentially as *normal hits*, meaning that they will be included in the *accuracy* portion of score, which breaks all sorts of follow-up assumptions: - The accuracy portion of the best possible total score becomes overinflated in comparison to reality, while the combo portion of that maximum score becomes underestimated. - Because the affected score has low accuracy, the estimated accuracy portion of the score (as given by maximmum accuracy portion of score times the actual numerical accuracy of the score) is also low. - However, the next step is estimating the combo portion, which is done by taking legacy total score, subtracting the aforementioned estimation for accuracy total score from that, and then dividing the result by the maximum achievable combo score on the map. Because most of actual "combo" score from swell ticks was "moved" into the accuracy portion due to the aforementioned error, the maximum achievable combo score becomes so small that the estimated combo portion exceeds 1. Instead, this change makes it so that gains from swell ticks are treated as "bonus", which means that they are excluded from the accuracy portion of score and instead count into the bonus portion of score, bringing the scores concerned more in line with expectations - although due to pessimistic assumptions in the simulation of the swell itself, the conversion will still overestimate total score for affected scores, just not by *that* much. --- osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs index 66ff0fc3d9..9839d94277 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs @@ -95,6 +95,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty case SwellTick: scoreIncrease = 300; increaseCombo = false; + isBonus = true; + bonusResult = HitResult.IgnoreHit; break; case DrumRollTick: From 73926592b91e1cf6348b3e7ae3c68142dbbd8d06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 25 Mar 2024 19:27:38 +0100 Subject: [PATCH 3554/4092] Bump legacy score version to recalculate all scores --- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 93f51ee74d..0f00cce080 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -46,9 +46,10 @@ namespace osu.Game.Scoring.Legacy /// 30000013: All local scores will use lazer definitions of ranks for consistency. Recalculates the rank of all scores. /// 30000014: Fix edge cases in conversion for osu! scores on selected beatmaps. Reconvert all scores. /// 30000015: Fix osu! standardised score estimation algorithm violating basic invariants. Reconvert all scores. + /// 30000016: Fix taiko standardised score estimation algorithm not including swell tick score gain into bonus portion. Reconvert all scores. /// /// - public const int LATEST_VERSION = 30000015; + public const int LATEST_VERSION = 30000016; /// /// The first stable-compatible YYYYMMDD format version given to lazer usage of replays. From 10683de578590d9a17c690df4ef9fc89515599a5 Mon Sep 17 00:00:00 2001 From: Arthur Araujo Date: Tue, 26 Mar 2024 04:59:47 -0300 Subject: [PATCH 3555/4092] Use order of new combo flags instead of `StartTime` --- .../Edit/CatchSelectionHandler.cs | 22 +++++++------------ .../Edit/OsuSelectionHandler.cs | 22 +++++++------------ 2 files changed, 16 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs index e3d82cc517..e3d2347c4d 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs @@ -81,12 +81,13 @@ namespace osu.Game.Rulesets.Catch.Edit double selectionStartTime = SelectedItems.Min(h => h.StartTime); double selectionEndTime = SelectedItems.Max(h => h.GetEndTime()); - var newComboPlaces = hitObjects + var newComboOrder = hitObjects .OfType() - .Where(h => h.NewCombo) - .Select(obj => obj.StartTime) + .Select(obj => obj.NewCombo) .ToList(); + newComboOrder.Reverse(); + foreach (var h in hitObjects) { h.StartTime = selectionEndTime - (h.GetEndTime() - selectionStartTime); @@ -100,18 +101,11 @@ namespace osu.Game.Rulesets.Catch.Edit } } - foreach (var h in hitObjects) + int i = 0; + foreach (bool newCombo in newComboOrder) { - if (h is CatchHitObject obj) obj.NewCombo = false; - } - - foreach (double place in newComboPlaces) - { - hitObjects - .OfType() - .Where(obj => obj.StartTime == place) - .ToList() - .ForEach(obj => obj.NewCombo = true); + hitObjects.OfType().ToList()[i].NewCombo = newCombo; + i++; } return true; diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index b4980b55d4..df39ab8fce 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -85,12 +85,13 @@ namespace osu.Game.Rulesets.Osu.Edit bool moreThanOneObject = hitObjects.Count > 1; - var newComboPlaces = hitObjects + var newComboOrder = hitObjects .OfType() - .Where(h => h.NewCombo) - .Select(obj => obj.StartTime) + .Select(obj => obj.NewCombo) .ToList(); + newComboOrder.Reverse(); + foreach (var h in hitObjects) { if (moreThanOneObject) @@ -103,18 +104,11 @@ namespace osu.Game.Rulesets.Osu.Edit } } - foreach (var h in hitObjects) + int i = 0; + foreach (bool newCombo in newComboOrder) { - if (h is OsuHitObject obj) obj.NewCombo = false; - } - - foreach (double place in newComboPlaces) - { - hitObjects - .OfType() - .Where(obj => obj.StartTime == place) - .ToList() - .ForEach(obj => obj.NewCombo = true); + hitObjects.OfType().ToList()[i].NewCombo = newCombo; + i++; } return true; From 9b923b19094bbcdf3f130d005085de70368cb8c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 26 Mar 2024 10:55:49 +0100 Subject: [PATCH 3556/4092] Fix code quality issues --- .../Editor/Checks/CheckCatchAbnormalDifficultySettingsTest.cs | 4 ++-- .../Editor/Checks/CheckKeyCountTest.cs | 2 +- .../Editor/Checks/CheckManiaAbnormalDifficultySettingsTest.cs | 4 ++-- .../Editor/Checks/CheckOsuAbnormalDifficultySettingsTest.cs | 4 ++-- .../Edit/Checks/CheckOsuAbnormalDifficultySettings.cs | 1 + .../Editor/Checks/CheckTaikoAbnormalDifficultySettingsTest.cs | 4 ++-- 6 files changed, 10 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/Checks/CheckCatchAbnormalDifficultySettingsTest.cs b/osu.Game.Rulesets.Catch.Tests/Editor/Checks/CheckCatchAbnormalDifficultySettingsTest.cs index 2ae2e20215..33aa4cba5d 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/Checks/CheckCatchAbnormalDifficultySettingsTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/Checks/CheckCatchAbnormalDifficultySettingsTest.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor.Checks { private CheckCatchAbnormalDifficultySettings check = null!; - private IBeatmap beatmap = new Beatmap(); + private readonly IBeatmap beatmap = new Beatmap(); [SetUp] public void Setup() @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor.Checks check = new CheckCatchAbnormalDifficultySettings(); beatmap.BeatmapInfo.Ruleset = new CatchRuleset().RulesetInfo; - beatmap.Difficulty = new BeatmapDifficulty() + beatmap.Difficulty = new BeatmapDifficulty { ApproachRate = 5, CircleSize = 5, diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/Checks/CheckKeyCountTest.cs b/osu.Game.Rulesets.Mania.Tests/Editor/Checks/CheckKeyCountTest.cs index 564c611548..b40a62176c 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/Checks/CheckKeyCountTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/Checks/CheckKeyCountTest.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor.Checks { check = new CheckKeyCount(); - beatmap = new Beatmap() + beatmap = new Beatmap { BeatmapInfo = new BeatmapInfo { diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/Checks/CheckManiaAbnormalDifficultySettingsTest.cs b/osu.Game.Rulesets.Mania.Tests/Editor/Checks/CheckManiaAbnormalDifficultySettingsTest.cs index 6c585aace3..da5ab037e5 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/Checks/CheckManiaAbnormalDifficultySettingsTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/Checks/CheckManiaAbnormalDifficultySettingsTest.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor.Checks { private CheckManiaAbnormalDifficultySettings check = null!; - private IBeatmap beatmap = new Beatmap(); + private readonly IBeatmap beatmap = new Beatmap(); [SetUp] public void Setup() @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor.Checks check = new CheckManiaAbnormalDifficultySettings(); beatmap.BeatmapInfo.Ruleset = new ManiaRuleset().RulesetInfo; - beatmap.Difficulty = new BeatmapDifficulty() + beatmap.Difficulty = new BeatmapDifficulty { OverallDifficulty = 5, DrainRate = 5, diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOsuAbnormalDifficultySettingsTest.cs b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOsuAbnormalDifficultySettingsTest.cs index 53ccd3c7a7..5f49714d93 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOsuAbnormalDifficultySettingsTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOsuAbnormalDifficultySettingsTest.cs @@ -17,14 +17,14 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks { private CheckOsuAbnormalDifficultySettings check = null!; - private IBeatmap beatmap = new Beatmap(); + private readonly IBeatmap beatmap = new Beatmap(); [SetUp] public void Setup() { check = new CheckOsuAbnormalDifficultySettings(); - beatmap.Difficulty = new BeatmapDifficulty() + beatmap.Difficulty = new BeatmapDifficulty { ApproachRate = 5, CircleSize = 5, diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOsuAbnormalDifficultySettings.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOsuAbnormalDifficultySettings.cs index c1eca7fff7..7ad861f317 100644 --- a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOsuAbnormalDifficultySettings.cs +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOsuAbnormalDifficultySettings.cs @@ -11,6 +11,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks public class CheckOsuAbnormalDifficultySettings : CheckAbnormalDifficultySettings { public override CheckMetadata Metadata => new CheckMetadata(CheckCategory.Settings, "Checks osu relevant settings"); + public override IEnumerable Run(BeatmapVerifierContext context) { var diff = context.Beatmap.Difficulty; diff --git a/osu.Game.Rulesets.Taiko.Tests/Editor/Checks/CheckTaikoAbnormalDifficultySettingsTest.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/Checks/CheckTaikoAbnormalDifficultySettingsTest.cs index f10e62f3bf..4a6cf0313a 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Editor/Checks/CheckTaikoAbnormalDifficultySettingsTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Editor/Checks/CheckTaikoAbnormalDifficultySettingsTest.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Editor.Checks { private CheckTaikoAbnormalDifficultySettings check = null!; - private IBeatmap beatmap = new Beatmap(); + private readonly IBeatmap beatmap = new Beatmap(); [SetUp] public void Setup() @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Editor.Checks check = new CheckTaikoAbnormalDifficultySettings(); beatmap.BeatmapInfo.Ruleset = new TaikoRuleset().RulesetInfo; - beatmap.Difficulty = new BeatmapDifficulty() + beatmap.Difficulty = new BeatmapDifficulty { OverallDifficulty = 5, }; From 8fb308c1925e1cdbda3eba6e58b7ef5c8fb1fe89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 26 Mar 2024 10:57:20 +0100 Subject: [PATCH 3557/4092] Add failing test coverage for checking taiko HP too I was wrong, taiko uses HP (to calculate miss penalty). --- ...heckTaikoAbnormalDifficultySettingsTest.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/osu.Game.Rulesets.Taiko.Tests/Editor/Checks/CheckTaikoAbnormalDifficultySettingsTest.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/Checks/CheckTaikoAbnormalDifficultySettingsTest.cs index 4a6cf0313a..6a50fd0956 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Editor/Checks/CheckTaikoAbnormalDifficultySettingsTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Editor/Checks/CheckTaikoAbnormalDifficultySettingsTest.cs @@ -52,6 +52,18 @@ namespace osu.Game.Rulesets.Taiko.Tests.Editor.Checks Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateMoreThanOneDecimal); } + [Test] + public void TestDrainRateTwoDecimals() + { + beatmap.Difficulty.DrainRate = 5.55f; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateMoreThanOneDecimal); + } + [Test] public void TestOverallDifficultyUnder() { @@ -76,6 +88,30 @@ namespace osu.Game.Rulesets.Taiko.Tests.Editor.Checks Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateOutOfRange); } + [Test] + public void TestDrainRateUnder() + { + beatmap.Difficulty.DrainRate = -10; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateOutOfRange); + } + + [Test] + public void TestDrainRateOver() + { + beatmap.Difficulty.DrainRate = 20; + + var context = getContext(); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateOutOfRange); + } + private BeatmapVerifierContext getContext() { return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); From e7cf1ab4df36879306ef6e4c38b535ba370a0e90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 26 Mar 2024 10:58:39 +0100 Subject: [PATCH 3558/4092] Add checks for taiko drain rate --- .../Edit/Checks/CheckTaikoAbnormalDifficultySettings.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Edit/Checks/CheckTaikoAbnormalDifficultySettings.cs b/osu.Game.Rulesets.Taiko/Edit/Checks/CheckTaikoAbnormalDifficultySettings.cs index ce35f21853..10e2867ca0 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Checks/CheckTaikoAbnormalDifficultySettings.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Checks/CheckTaikoAbnormalDifficultySettings.cs @@ -21,6 +21,12 @@ namespace osu.Game.Rulesets.Taiko.Edit.Checks if (OutOfRange(diff.OverallDifficulty)) yield return new IssueTemplateOutOfRange(this).Create("Overall difficulty", diff.OverallDifficulty); + + if (HasMoreThanOneDecimalPlace(diff.DrainRate)) + yield return new IssueTemplateMoreThanOneDecimal(this).Create("Drain rate", diff.DrainRate); + + if (OutOfRange(diff.DrainRate)) + yield return new IssueTemplateOutOfRange(this).Create("Drain rate", diff.DrainRate); } } } From 1866b4b6b10d1990db311eb96ae02c5c5609e918 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 26 Mar 2024 11:13:03 +0100 Subject: [PATCH 3559/4092] Refactor abstract check to reduce duplication --- .../CheckCatchAbnormalDifficultySettings.cs | 25 +++++++------- .../CheckManiaAbnormalDifficultySettings.cs | 17 +++++----- .../CheckOsuAbnormalDifficultySettings.cs | 33 ++++++++++--------- .../CheckTaikoAbnormalDifficultySettings.cs | 17 +++++----- .../Checks/CheckAbnormalDifficultySettings.cs | 13 +++++--- 5 files changed, 57 insertions(+), 48 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Checks/CheckCatchAbnormalDifficultySettings.cs b/osu.Game.Rulesets.Catch/Edit/Checks/CheckCatchAbnormalDifficultySettings.cs index 8295795f00..d2c3df0872 100644 --- a/osu.Game.Rulesets.Catch/Edit/Checks/CheckCatchAbnormalDifficultySettings.cs +++ b/osu.Game.Rulesets.Catch/Edit/Checks/CheckCatchAbnormalDifficultySettings.cs @@ -15,24 +15,25 @@ namespace osu.Game.Rulesets.Catch.Edit.Checks public override IEnumerable Run(BeatmapVerifierContext context) { var diff = context.Beatmap.Difficulty; + Issue? issue; - if (HasMoreThanOneDecimalPlace(diff.ApproachRate)) - yield return new IssueTemplateMoreThanOneDecimal(this).Create("Approach rate", diff.ApproachRate); + if (HasMoreThanOneDecimalPlace("Approach rate", diff.ApproachRate, out issue)) + yield return issue; - if (OutOfRange(diff.ApproachRate)) - yield return new IssueTemplateOutOfRange(this).Create("Approach rate", diff.ApproachRate); + if (OutOfRange("Approach rate", diff.ApproachRate, out issue)) + yield return issue; - if (HasMoreThanOneDecimalPlace(diff.CircleSize)) - yield return new IssueTemplateMoreThanOneDecimal(this).Create("Circle size", diff.CircleSize); + if (HasMoreThanOneDecimalPlace("Circle size", diff.CircleSize, out issue)) + yield return issue; - if (OutOfRange(diff.CircleSize)) - yield return new IssueTemplateOutOfRange(this).Create("Circle size", diff.CircleSize); + if (OutOfRange("Circle size", diff.CircleSize, out issue)) + yield return issue; - if (HasMoreThanOneDecimalPlace(diff.DrainRate)) - yield return new IssueTemplateMoreThanOneDecimal(this).Create("Drain rate", diff.DrainRate); + if (HasMoreThanOneDecimalPlace("Drain rate", diff.DrainRate, out issue)) + yield return issue; - if (OutOfRange(diff.DrainRate)) - yield return new IssueTemplateOutOfRange(this).Create("Drain rate", diff.DrainRate); + if (OutOfRange("Drain rate", diff.DrainRate, out issue)) + yield return issue; } } } diff --git a/osu.Game.Rulesets.Mania/Edit/Checks/CheckManiaAbnormalDifficultySettings.cs b/osu.Game.Rulesets.Mania/Edit/Checks/CheckManiaAbnormalDifficultySettings.cs index ae0cc3aa4c..233c602c21 100644 --- a/osu.Game.Rulesets.Mania/Edit/Checks/CheckManiaAbnormalDifficultySettings.cs +++ b/osu.Game.Rulesets.Mania/Edit/Checks/CheckManiaAbnormalDifficultySettings.cs @@ -15,18 +15,19 @@ namespace osu.Game.Rulesets.Mania.Edit.Checks public override IEnumerable Run(BeatmapVerifierContext context) { var diff = context.Beatmap.Difficulty; + Issue? issue; - if (HasMoreThanOneDecimalPlace(diff.OverallDifficulty)) - yield return new IssueTemplateMoreThanOneDecimal(this).Create("Overall difficulty", diff.OverallDifficulty); + if (HasMoreThanOneDecimalPlace("Overall difficulty", diff.OverallDifficulty, out issue)) + yield return issue; - if (OutOfRange(diff.OverallDifficulty)) - yield return new IssueTemplateOutOfRange(this).Create("Overall difficulty", diff.OverallDifficulty); + if (OutOfRange("Overall difficulty", diff.OverallDifficulty, out issue)) + yield return issue; - if (HasMoreThanOneDecimalPlace(diff.DrainRate)) - yield return new IssueTemplateMoreThanOneDecimal(this).Create("Drain rate", diff.DrainRate); + if (HasMoreThanOneDecimalPlace("Drain rate", diff.DrainRate, out issue)) + yield return issue; - if (OutOfRange(diff.DrainRate)) - yield return new IssueTemplateOutOfRange(this).Create("Drain rate", diff.DrainRate); + if (OutOfRange("Drain rate", diff.DrainRate, out issue)) + yield return issue; } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOsuAbnormalDifficultySettings.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOsuAbnormalDifficultySettings.cs index 7ad861f317..1c44d54633 100644 --- a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOsuAbnormalDifficultySettings.cs +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOsuAbnormalDifficultySettings.cs @@ -15,30 +15,31 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks public override IEnumerable Run(BeatmapVerifierContext context) { var diff = context.Beatmap.Difficulty; + Issue? issue; - if (HasMoreThanOneDecimalPlace(diff.ApproachRate)) - yield return new IssueTemplateMoreThanOneDecimal(this).Create("Approach rate", diff.ApproachRate); + if (HasMoreThanOneDecimalPlace("Approach rate", diff.ApproachRate, out issue)) + yield return issue; - if (OutOfRange(diff.ApproachRate)) - yield return new IssueTemplateOutOfRange(this).Create("Approach rate", diff.ApproachRate); + if (OutOfRange("Approach rate", diff.ApproachRate, out issue)) + yield return issue; - if (HasMoreThanOneDecimalPlace(diff.OverallDifficulty)) - yield return new IssueTemplateMoreThanOneDecimal(this).Create("Overall difficulty", diff.OverallDifficulty); + if (HasMoreThanOneDecimalPlace("Overall difficulty", diff.OverallDifficulty, out issue)) + yield return issue; - if (OutOfRange(diff.OverallDifficulty)) - yield return new IssueTemplateOutOfRange(this).Create("Overall difficulty", diff.OverallDifficulty); + if (OutOfRange("Overall difficulty", diff.OverallDifficulty, out issue)) + yield return issue; - if (HasMoreThanOneDecimalPlace(diff.CircleSize)) - yield return new IssueTemplateMoreThanOneDecimal(this).Create("Circle size", diff.CircleSize); + if (HasMoreThanOneDecimalPlace("Circle size", diff.CircleSize, out issue)) + yield return issue; - if (OutOfRange(diff.CircleSize)) - yield return new IssueTemplateOutOfRange(this).Create("Circle size", diff.CircleSize); + if (OutOfRange("Circle size", diff.CircleSize, out issue)) + yield return issue; - if (HasMoreThanOneDecimalPlace(diff.DrainRate)) - yield return new IssueTemplateMoreThanOneDecimal(this).Create("Drain rate", diff.DrainRate); + if (HasMoreThanOneDecimalPlace("Drain rate", diff.DrainRate, out issue)) + yield return issue; - if (OutOfRange(diff.DrainRate)) - yield return new IssueTemplateOutOfRange(this).Create("Drain rate", diff.DrainRate); + if (OutOfRange("Drain rate", diff.DrainRate, out issue)) + yield return issue; } } } diff --git a/osu.Game.Rulesets.Taiko/Edit/Checks/CheckTaikoAbnormalDifficultySettings.cs b/osu.Game.Rulesets.Taiko/Edit/Checks/CheckTaikoAbnormalDifficultySettings.cs index 10e2867ca0..38ba7b1b01 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Checks/CheckTaikoAbnormalDifficultySettings.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Checks/CheckTaikoAbnormalDifficultySettings.cs @@ -15,18 +15,19 @@ namespace osu.Game.Rulesets.Taiko.Edit.Checks public override IEnumerable Run(BeatmapVerifierContext context) { var diff = context.Beatmap.Difficulty; + Issue? issue; - if (HasMoreThanOneDecimalPlace(diff.OverallDifficulty)) - yield return new IssueTemplateMoreThanOneDecimal(this).Create("Overall difficulty", diff.OverallDifficulty); + if (HasMoreThanOneDecimalPlace("Overall difficulty", diff.OverallDifficulty, out issue)) + yield return issue; - if (OutOfRange(diff.OverallDifficulty)) - yield return new IssueTemplateOutOfRange(this).Create("Overall difficulty", diff.OverallDifficulty); + if (OutOfRange("Overall difficulty", diff.OverallDifficulty, out issue)) + yield return issue; - if (HasMoreThanOneDecimalPlace(diff.DrainRate)) - yield return new IssueTemplateMoreThanOneDecimal(this).Create("Drain rate", diff.DrainRate); + if (HasMoreThanOneDecimalPlace("Drain rate", diff.DrainRate, out issue)) + yield return issue; - if (OutOfRange(diff.DrainRate)) - yield return new IssueTemplateOutOfRange(this).Create("Drain rate", diff.DrainRate); + if (OutOfRange("Drain rate", diff.DrainRate, out issue)) + yield return issue; } } } diff --git a/osu.Game/Rulesets/Edit/Checks/CheckAbnormalDifficultySettings.cs b/osu.Game/Rulesets/Edit/Checks/CheckAbnormalDifficultySettings.cs index 93592a866b..638f0cfd53 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckAbnormalDifficultySettings.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckAbnormalDifficultySettings.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using osu.Game.Rulesets.Edit.Checks.Components; namespace osu.Game.Rulesets.Edit.Checks @@ -21,14 +22,18 @@ namespace osu.Game.Rulesets.Edit.Checks /// /// If the setting is out of the boundaries set by the editor (0 - 10) /// - protected bool OutOfRange(float setting) + protected bool OutOfRange(string setting, float value, [NotNullWhen(true)] out Issue? issue) { - return setting < 0f || setting > 10f; + bool hasIssue = value < 0f || value > 10f; + issue = hasIssue ? new IssueTemplateOutOfRange(this).Create(setting, value) : null; + return hasIssue; } - protected bool HasMoreThanOneDecimalPlace(float setting) + protected bool HasMoreThanOneDecimalPlace(string setting, float value, [NotNullWhen(true)] out Issue? issue) { - return float.Round(setting, 1) != setting; + bool hasIssue = float.Round(value, 1) != value; + issue = hasIssue ? new IssueTemplateMoreThanOneDecimal(this).Create(setting, value) : null; + return hasIssue; } public class IssueTemplateMoreThanOneDecimal : IssueTemplate From c24eb066dc4927580ffecfe79460819bc5486f84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 26 Mar 2024 12:03:24 +0100 Subject: [PATCH 3560/4092] Update tests to match new expected behaviour Co-authored-by: Vlad Frangu --- .../Filtering/FilterQueryParserTest.cs | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index bd706b5b4c..ea14412f55 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -273,12 +273,21 @@ namespace osu.Game.Tests.NonVisual.Filtering } [Test] - public void TestApplyStatusMatches() + public void TestApplyMultipleEqualityStatusQueries() { const string query = "status=ranked status=loved"; var filterCriteria = new FilterCriteria(); FilterQueryParser.ApplyQueries(filterCriteria, query); - Assert.IsNotEmpty(filterCriteria.OnlineStatus.Values); + Assert.That(filterCriteria.OnlineStatus.Values, Is.Empty); + } + + [Test] + public void TestApplyEqualStatusQueryWithMultipleValues() + { + const string query = "status=ranked,loved"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.That(filterCriteria.OnlineStatus.Values, Is.Not.Empty); Assert.That(filterCriteria.OnlineStatus.Values, Contains.Item(BeatmapOnlineStatus.Ranked)); Assert.That(filterCriteria.OnlineStatus.Values, Contains.Item(BeatmapOnlineStatus.Loved)); } @@ -289,13 +298,43 @@ namespace osu.Game.Tests.NonVisual.Filtering const string query = "status>=r"; var filterCriteria = new FilterCriteria(); FilterQueryParser.ApplyQueries(filterCriteria, query); - Assert.IsNotEmpty(filterCriteria.OnlineStatus.Values); + Assert.That(filterCriteria.OnlineStatus.Values, Has.Count.EqualTo(4)); Assert.That(filterCriteria.OnlineStatus.Values, Contains.Item(BeatmapOnlineStatus.Ranked)); Assert.That(filterCriteria.OnlineStatus.Values, Contains.Item(BeatmapOnlineStatus.Approved)); Assert.That(filterCriteria.OnlineStatus.Values, Contains.Item(BeatmapOnlineStatus.Qualified)); Assert.That(filterCriteria.OnlineStatus.Values, Contains.Item(BeatmapOnlineStatus.Loved)); } + [Test] + public void TestApplyRangeStatusWithMultipleMatchesQuery() + { + const string query = "status>=r,l"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.That(filterCriteria.OnlineStatus.Values, Is.EquivalentTo(Enum.GetValues())); + } + + [Test] + public void TestApplyTwoRangeStatusQuery() + { + const string query = "status>r status Date: Tue, 26 Mar 2024 12:20:38 +0100 Subject: [PATCH 3561/4092] Update `OptionalSet` implementation to intersect across multiple filters rather than union --- osu.Game/Screens/Select/FilterCriteria.cs | 9 +- osu.Game/Screens/Select/FilterQueryParser.cs | 86 ++++++++++++-------- 2 files changed, 56 insertions(+), 39 deletions(-) diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 6750b07aba..01b0e9b7d9 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -114,17 +114,18 @@ namespace osu.Game.Screens.Select public IRulesetFilterCriteria? RulesetCriteria { get; set; } - public struct OptionalSet : IEquatable> - where T : struct + public readonly struct OptionalSet : IEquatable> + where T : struct, Enum { - public bool HasFilter => Values.Count > 0; + public bool HasFilter => true; public bool IsInRange(T value) => Values.Contains(value); - public HashSet Values = new HashSet(); + public HashSet Values { get; } public OptionalSet() { + Values = Enum.GetValues().ToHashSet(); } public bool Equals(OptionalSet other) => Values.SetEquals(other.Values); diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 32b533e18d..4e49495f47 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -69,7 +69,7 @@ namespace osu.Game.Screens.Select return TryUpdateCriteriaRange(ref criteria.BeatDivisor, op, value, tryParseInt); case "status": - return TryUpdateCriteriaSet(ref criteria.OnlineStatus, op, value, tryParseEnum); + return TryUpdateCriteriaSet(ref criteria.OnlineStatus, op, value); case "creator": case "author": @@ -302,54 +302,70 @@ namespace osu.Game.Screens.Select /// /// Attempts to parse a keyword filter of type , - /// from the specified and . - /// If can be parsed into using , the function returns true + /// from the specified and . + /// If can be parsed successfully, the function returns true /// and the resulting range constraint is stored into the 's expected values. /// /// The to store the parsed data into, if successful. /// The operator for the keyword filter. - /// The value of the keyword filter. - /// Function used to determine if can be converted to type . - public static bool TryUpdateCriteriaSet(ref FilterCriteria.OptionalSet range, Operator op, string val, TryParseFunction parseFunction) - where T : struct, Enum - => parseFunction.Invoke(val, out var converted) && tryUpdateCriteriaSet(ref range, op, converted); - - private static bool tryUpdateCriteriaSet(ref FilterCriteria.OptionalSet range, Operator op, T pivotValue) + /// The value of the keyword filter. + public static bool TryUpdateCriteriaSet(ref FilterCriteria.OptionalSet range, Operator op, string filterValue) where T : struct, Enum { - var allDefinedValues = Enum.GetValues(); + var matchingValues = new HashSet(); - foreach (var val in allDefinedValues) + if (op == Operator.Equal && filterValue.Contains(',')) { - int compareResult = Comparer.Default.Compare(val, pivotValue); + string[] splitValues = filterValue.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - switch (op) + foreach (string splitValue in splitValues) { - case Operator.Less: - if (compareResult < 0) range.Values.Add(val); - break; - - case Operator.LessOrEqual: - if (compareResult <= 0) range.Values.Add(val); - break; - - case Operator.Equal: - if (compareResult == 0) range.Values.Add(val); - break; - - case Operator.GreaterOrEqual: - if (compareResult >= 0) range.Values.Add(val); - break; - - case Operator.Greater: - if (compareResult > 0) range.Values.Add(val); - break; - - default: + if (!tryParseEnum(splitValue, out var parsedValue)) return false; + + matchingValues.Add(parsedValue); + } + } + else + { + if (!tryParseEnum(filterValue, out var pivotValue)) + return false; + + var allDefinedValues = Enum.GetValues(); + + foreach (var val in allDefinedValues) + { + int compareResult = Comparer.Default.Compare(val, pivotValue); + + switch (op) + { + case Operator.Less: + if (compareResult < 0) matchingValues.Add(val); + break; + + case Operator.LessOrEqual: + if (compareResult <= 0) matchingValues.Add(val); + break; + + case Operator.Equal: + if (compareResult == 0) matchingValues.Add(val); + break; + + case Operator.GreaterOrEqual: + if (compareResult >= 0) matchingValues.Add(val); + break; + + case Operator.Greater: + if (compareResult > 0) matchingValues.Add(val); + break; + + default: + return false; + } } } + range.Values.IntersectWith(matchingValues); return true; } From 9474156df4bb33bda65c0bd2d68d565825f1b3d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Mar 2024 20:21:12 +0800 Subject: [PATCH 3562/4092] Improve equality implementations --- .../API/Requests/Responses/APIMenuContent.cs | 10 +--------- .../API/Requests/Responses/APIMenuImage.cs | 17 +++++------------ 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIMenuContent.cs b/osu.Game/Online/API/Requests/Responses/APIMenuContent.cs index 7b53488030..6aad0f6c87 100644 --- a/osu.Game/Online/API/Requests/Responses/APIMenuContent.cs +++ b/osu.Game/Online/API/Requests/Responses/APIMenuContent.cs @@ -23,15 +23,7 @@ namespace osu.Game.Online.API.Requests.Responses return Images.SequenceEqual(other.Images); } - public override bool Equals(object? obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - - if (obj.GetType() != GetType()) return false; - - return Equals((APIMenuContent)obj); - } + public override bool Equals(object? other) => other is APIMenuContent content && Equals(content); public override int GetHashCode() { diff --git a/osu.Game/Online/API/Requests/Responses/APIMenuImage.cs b/osu.Game/Online/API/Requests/Responses/APIMenuImage.cs index 42129ca96e..8aff08099a 100644 --- a/osu.Game/Online/API/Requests/Responses/APIMenuImage.cs +++ b/osu.Game/Online/API/Requests/Responses/APIMenuImage.cs @@ -28,34 +28,27 @@ namespace osu.Game.Online.API.Requests.Responses /// The time at which this item should begin displaying. If null, will display immediately. /// [JsonProperty(@"begins")] - public DateTimeOffset? Begins { get; set; } + public DateTimeOffset? Begins { get; init; } /// /// The time at which this item should stop displaying. If null, will display indefinitely. /// [JsonProperty(@"expires")] - public DateTimeOffset? Expires { get; set; } + public DateTimeOffset? Expires { get; init; } public bool Equals(APIMenuImage? other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; - return Image == other.Image && Url == other.Url; + return Image == other.Image && Url == other.Url && Begins == other.Begins && Expires == other.Expires; } - public override bool Equals(object? obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != GetType()) return false; - - return Equals((APIMenuImage)obj); - } + public override bool Equals(object? other) => other is APIMenuImage content && Equals(content); public override int GetHashCode() { - return HashCode.Combine(Image, Url); + return HashCode.Combine(Image, Url, Begins, Expires); } } } From fd649edabae36603cccfc8a847a81de05b32f257 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Mar 2024 20:21:48 +0800 Subject: [PATCH 3563/4092] Also don't rotate images during a drag operation --- osu.Game/Screens/Menu/OnlineMenuBanner.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/OnlineMenuBanner.cs b/osu.Game/Screens/Menu/OnlineMenuBanner.cs index 37bec7aa63..260c021719 100644 --- a/osu.Game/Screens/Menu/OnlineMenuBanner.cs +++ b/osu.Game/Screens/Menu/OnlineMenuBanner.cs @@ -120,8 +120,8 @@ namespace osu.Game.Screens.Menu { nextDisplay?.Cancel(); - // If the user is hovering a banner, don't rotate yet. - bool anyHovered = content.Any(i => i.IsHovered); + // If the user is interacting with a banner, don't rotate yet. + bool anyHovered = content.Any(i => i.IsHovered || i.IsDragged); if (!anyHovered) { @@ -242,6 +242,8 @@ namespace osu.Game.Screens.Menu .ScaleTo(1, 500, Easing.OutElastic); base.OnMouseUp(e); } + + protected override bool OnDragStart(DragStartEvent e) => true; } } } From e77d4c8cfaf66e1e717b0094632a3704b77e73f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Mar 2024 20:28:03 +0800 Subject: [PATCH 3564/4092] Remove unnecessary `Math.Max` --- osu.Game/Screens/Menu/OnlineMenuBanner.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/OnlineMenuBanner.cs b/osu.Game/Screens/Menu/OnlineMenuBanner.cs index 260c021719..6f98b73939 100644 --- a/osu.Game/Screens/Menu/OnlineMenuBanner.cs +++ b/osu.Game/Screens/Menu/OnlineMenuBanner.cs @@ -130,8 +130,8 @@ namespace osu.Game.Screens.Menu // To handle expiration simply, arrange all images in best-next order. // Fade in the first valid one, then handle fading out the last if required. var currentRotation = content - .Skip(Math.Max(0, previousIndex) + 1) - .Concat(content.Take(Math.Max(0, previousIndex) + 1)); + .Skip(previousIndex + 1) + .Concat(content.Take(previousIndex + 1)); // After the loop, displayIndex will be the new valid index or -1 if // none valid. From dee88573a756f9652628d13f2033cd4b78244870 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 26 Mar 2024 13:44:12 +0100 Subject: [PATCH 3565/4092] Fix test failure in visual browser I'm not sure why it's failing headless and I'm not particularly interested in finding that out right now. --- osu.Game.Tests/Visual/Menus/TestSceneOnlineMenuBanner.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneOnlineMenuBanner.cs b/osu.Game.Tests/Visual/Menus/TestSceneOnlineMenuBanner.cs index 380085ce04..60e42838d8 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneOnlineMenuBanner.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneOnlineMenuBanner.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Online.API.Requests.Responses; using osu.Game.Screens.Menu; +using osuTK; namespace osu.Game.Tests.Visual.Menus { @@ -95,7 +96,7 @@ namespace osu.Game.Tests.Visual.Menus } }); - AddUntilStep("wait for no image shown", () => !onlineMenuBanner.ChildrenOfType().Any()); + AddUntilStep("wait for no image shown", () => onlineMenuBanner.ChildrenOfType().Single().Size, () => Is.EqualTo(Vector2.Zero)); AddStep("unset system title", () => onlineMenuBanner.Current.Value = new APIMenuContent()); From b4ccbc68e447f0dba68470d70510195bfad009f5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Mar 2024 21:20:22 +0800 Subject: [PATCH 3566/4092] Fix failing test --- osu.Game.Tests/Visual/Menus/TestSceneOnlineMenuBanner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneOnlineMenuBanner.cs b/osu.Game.Tests/Visual/Menus/TestSceneOnlineMenuBanner.cs index 60e42838d8..0b90fd13c3 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneOnlineMenuBanner.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneOnlineMenuBanner.cs @@ -96,7 +96,7 @@ namespace osu.Game.Tests.Visual.Menus } }); - AddUntilStep("wait for no image shown", () => onlineMenuBanner.ChildrenOfType().Single().Size, () => Is.EqualTo(Vector2.Zero)); + AddUntilStep("wait for no image shown", () => onlineMenuBanner.ChildrenOfType().SingleOrDefault()?.Size, () => Is.EqualTo(Vector2.Zero)); AddStep("unset system title", () => onlineMenuBanner.Current.Value = new APIMenuContent()); From a5f15a119e325f954e8f5358227269a99dec4e5e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Mar 2024 22:51:54 +0800 Subject: [PATCH 3567/4092] Apply rate adjust fix in all cases rather than specifically for `Clock.Rate == 1` --- osu.Game/Graphics/Containers/BeatSyncedContainer.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index a14dfd4d64..de9a5aff3a 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -96,10 +96,8 @@ namespace osu.Game.Graphics.Containers // In the case of gameplay, we are usually within a hierarchy with the correct rate applied to our `Drawable.Clock`. // This means that the amount of early adjustment is adjusted in line with audio track rate changes. // But other cases like the osu! logo at the main menu won't correctly have this rate information. - // - // So for cases where the rate of the source isn't in sync with our hierarchy, let's assume we need to account for it locally. - if (Clock.Rate == 1 && BeatSyncSource.Clock.Rate != Clock.Rate) - early *= BeatSyncSource.Clock.Rate; + // We can adjust here to ensure the applied early activation always matches expectations. + early *= BeatSyncSource.Clock.Rate / Clock.Rate; currentTrackTime = BeatSyncSource.Clock.CurrentTime + early; From 4c1a1b54be7ffab08eb6e2713dbeca953f15044c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 27 Mar 2024 00:07:51 +0900 Subject: [PATCH 3568/4092] Fix NVAPI startup exception on non-Windows platforms --- osu.Desktop/NVAPI.cs | 2 ++ osu.Desktop/Program.cs | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Desktop/NVAPI.cs b/osu.Desktop/NVAPI.cs index 78a814c585..554f89a847 100644 --- a/osu.Desktop/NVAPI.cs +++ b/osu.Desktop/NVAPI.cs @@ -8,11 +8,13 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; +using System.Runtime.Versioning; using osu.Framework.Logging; namespace osu.Desktop { [SuppressMessage("ReSharper", "InconsistentNaming")] + [SupportedOSPlatform("windows")] internal static class NVAPI { private const string osu_filename = "osu!.exe"; diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 73670adc49..2d7ec5aa5f 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -70,7 +70,8 @@ namespace osu.Desktop // NVIDIA profiles are based on the executable name of a process. // Lazer and stable share the same executable name. // Stable sets this setting to "Off", which may not be what we want, so let's force it back to the default "Auto" on startup. - NVAPI.ThreadedOptimisations = NvThreadControlSetting.OGL_THREAD_CONTROL_DEFAULT; + if (OperatingSystem.IsWindows()) + NVAPI.ThreadedOptimisations = NvThreadControlSetting.OGL_THREAD_CONTROL_DEFAULT; // Back up the cwd before DesktopGameHost changes it string cwd = Environment.CurrentDirectory; From 01a72d5afaa788e707308bea0764565ad1b044e6 Mon Sep 17 00:00:00 2001 From: Arthur Araujo Date: Tue, 26 Mar 2024 12:10:40 -0300 Subject: [PATCH 3569/4092] Fix combo not reversing properly depending on the order of selection --- .../Edit/CatchSelectionHandler.cs | 17 +++++++++-------- .../Edit/OsuSelectionHandler.cs | 17 +++++++++-------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs index e3d2347c4d..f8fe9805e6 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs @@ -76,17 +76,15 @@ namespace osu.Game.Rulesets.Catch.Edit public override bool HandleReverse() { - var hitObjects = EditorBeatmap.SelectedHitObjects; + var hitObjects = EditorBeatmap.SelectedHitObjects + .OfType() + .OrderBy(obj => obj.StartTime) + .ToList(); double selectionStartTime = SelectedItems.Min(h => h.StartTime); double selectionEndTime = SelectedItems.Max(h => h.GetEndTime()); - var newComboOrder = hitObjects - .OfType() - .Select(obj => obj.NewCombo) - .ToList(); - - newComboOrder.Reverse(); + var newComboOrder = hitObjects.Select(obj => obj.NewCombo).ToList(); foreach (var h in hitObjects) { @@ -101,10 +99,13 @@ namespace osu.Game.Rulesets.Catch.Edit } } + // re-order objects again after flipping their times + hitObjects = [.. hitObjects.OrderBy(obj => obj.StartTime)]; + int i = 0; foreach (bool newCombo in newComboOrder) { - hitObjects.OfType().ToList()[i].NewCombo = newCombo; + hitObjects[i].NewCombo = newCombo; i++; } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index df39ab8fce..0e889cab81 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -78,19 +78,17 @@ namespace osu.Game.Rulesets.Osu.Edit public override bool HandleReverse() { - var hitObjects = EditorBeatmap.SelectedHitObjects; + var hitObjects = EditorBeatmap.SelectedHitObjects + .OfType() + .OrderBy(obj => obj.StartTime) + .ToList(); double endTime = hitObjects.Max(h => h.GetEndTime()); double startTime = hitObjects.Min(h => h.StartTime); bool moreThanOneObject = hitObjects.Count > 1; - var newComboOrder = hitObjects - .OfType() - .Select(obj => obj.NewCombo) - .ToList(); - - newComboOrder.Reverse(); + var newComboOrder = hitObjects.Select(obj => obj.NewCombo).ToList(); foreach (var h in hitObjects) { @@ -104,10 +102,13 @@ namespace osu.Game.Rulesets.Osu.Edit } } + // re-order objects again after flipping their times + hitObjects = [.. hitObjects.OrderBy(obj => obj.StartTime)]; + int i = 0; foreach (bool newCombo in newComboOrder) { - hitObjects.OfType().ToList()[i].NewCombo = newCombo; + hitObjects[i].NewCombo = newCombo; i++; } From e02ad6cf94d3a9bba0cbcac6ecee5a0125f9da89 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Mar 2024 23:38:34 +0800 Subject: [PATCH 3570/4092] Fix test regression --- osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs index fdb1cac3e5..8c81431770 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods private SessionStatics statics { get; set; } = null!; private ScoreAccessibleSoloPlayer currentPlayer = null!; - private readonly ManualClock manualClock = new ManualClock { Rate = 0 }; + private readonly ManualClock manualClock = new ManualClock { Rate = 1 }; protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null) => new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(manualClock), Audio); From 0b29a762b8aa712d1a0b697f367ae809a0896f15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 26 Mar 2024 17:36:00 +0100 Subject: [PATCH 3571/4092] Add precautionary guard to avoid potential div-by-zero Probably wouldn't happen outside of tests, but I'd rather not find out next release. --- osu.Game/Graphics/Containers/BeatSyncedContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index de9a5aff3a..7210371ebf 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -97,7 +97,8 @@ namespace osu.Game.Graphics.Containers // This means that the amount of early adjustment is adjusted in line with audio track rate changes. // But other cases like the osu! logo at the main menu won't correctly have this rate information. // We can adjust here to ensure the applied early activation always matches expectations. - early *= BeatSyncSource.Clock.Rate / Clock.Rate; + if (Clock.Rate > 0) + early *= BeatSyncSource.Clock.Rate / Clock.Rate; currentTrackTime = BeatSyncSource.Clock.CurrentTime + early; From 600098d845611a45147154690cc09f992c961119 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 27 Mar 2024 04:05:04 +0900 Subject: [PATCH 3572/4092] Fix bulbs on Catmull sliders --- osu.Game/Rulesets/Objects/SliderPath.cs | 68 +++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index f33a07f082..5398d6c45f 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -42,6 +42,17 @@ namespace osu.Game.Rulesets.Objects private readonly List cumulativeLength = new List(); private readonly Cached pathCache = new Cached(); + /// + /// Any additional length of the path which was optimised out during piecewise approximation, but should still be considered as part of . + /// + /// + /// This is a hack for Catmull paths. + /// + private double optimisedLength; + + /// + /// The final calculated length of the path. + /// private double calculatedLength; private readonly List segmentEnds = new List(); @@ -244,6 +255,7 @@ namespace osu.Game.Rulesets.Objects { calculatedPath.Clear(); segmentEnds.Clear(); + optimisedLength = 0; if (ControlPoints.Count == 0) return; @@ -268,7 +280,8 @@ namespace osu.Game.Rulesets.Objects calculatedPath.Add(segmentVertices[0]); else if (segmentVertices.Length > 1) { - List subPath = calculateSubPath(segmentVertices, segmentType); + List subPath = calculateSubPath(segmentVertices, segmentType, ref optimisedLength); + // Skip the first vertex if it is the same as the last vertex from the previous segment bool skipFirst = calculatedPath.Count > 0 && subPath.Count > 0 && calculatedPath.Last() == subPath[0]; @@ -287,7 +300,7 @@ namespace osu.Game.Rulesets.Objects } } - private List calculateSubPath(ReadOnlySpan subControlPoints, PathType type) + private static List calculateSubPath(ReadOnlySpan subControlPoints, PathType type, ref double optimisedLength) { switch (type.Type) { @@ -295,6 +308,7 @@ namespace osu.Game.Rulesets.Objects return PathApproximator.LinearToPiecewiseLinear(subControlPoints); case SplineType.PerfectCurve: + { if (subControlPoints.Length != 3) break; @@ -305,9 +319,55 @@ namespace osu.Game.Rulesets.Objects break; return subPath; + } case SplineType.Catmull: - return PathApproximator.CatmullToPiecewiseLinear(subControlPoints); + { + List subPath = PathApproximator.CatmullToPiecewiseLinear(subControlPoints); + + // At draw time, osu!stable optimises paths by only keeping piecewise segments that are 6px apart. + // For the most part we don't care about this optimisation, and its additional heuristics are hard to reproduce in every implementation. + // + // However, it matters for Catmull paths which form "bulbs" around sequential knots with identical positions, + // so we'll apply a very basic form of the optimisation here and return a length representing the optimised portion. + // The returned length is important so that the optimisation doesn't cause the path to get extended to match the value of ExpectedDistance. + + List optimisedPath = new List(subPath.Count); + + Vector2? lastStart = null; + double lengthRemovedSinceStart = 0; + + for (int i = 0; i < subPath.Count; i++) + { + if (lastStart == null) + { + optimisedPath.Add(subPath[i]); + lastStart = subPath[i]; + continue; + } + + Debug.Assert(i > 0); + + double distFromStart = Vector2.Distance(lastStart.Value, subPath[i]); + lengthRemovedSinceStart += Vector2.Distance(subPath[i - 1], subPath[i]); + + // See PathApproximator.catmull_detail. + const int catmull_detail = 50; + const int catmull_segment_length = catmull_detail * 2; + + // Either 6px from the start, the last vertex at every knot, or the end of the path. + if (distFromStart > 6 || (i + 1) % catmull_segment_length == 0 || i == subPath.Count - 1) + { + optimisedPath.Add(subPath[i]); + optimisedLength += lengthRemovedSinceStart - distFromStart; + + lastStart = null; + lengthRemovedSinceStart = 0; + } + } + + return optimisedPath; + } } return PathApproximator.BSplineToPiecewiseLinear(subControlPoints, type.Degree ?? subControlPoints.Length); @@ -315,7 +375,7 @@ namespace osu.Game.Rulesets.Objects private void calculateLength() { - calculatedLength = 0; + calculatedLength = optimisedLength; cumulativeLength.Clear(); cumulativeLength.Add(0); From 4490dbf8960214dcc9d3f67dbfc88ab5255c6e94 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 27 Mar 2024 13:37:47 +0900 Subject: [PATCH 3573/4092] Update diffcalc workflow --- .github/workflows/diffcalc.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/diffcalc.yml b/.github/workflows/diffcalc.yml index 7a2dcecb9c..2ed176fe8d 100644 --- a/.github/workflows/diffcalc.yml +++ b/.github/workflows/diffcalc.yml @@ -110,10 +110,14 @@ jobs: if: ${{ github.event_name == 'workflow_dispatch' || contains(github.event.comment.body, '!diffcalc') }} steps: - name: Check permissions - if: ${{ github.event_name != 'workflow_dispatch' }} - uses: actions-cool/check-user-permission@a0668c9aec87f3875fc56170b6452a453e9dd819 # v2.2.0 - with: - require: 'write' + run: | + ALLOWED_USERS=(smoogipoo peppy bdach) + for i in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$i" ]]; then + exit 0 + fi + done + exit 1 create-comment: name: Create PR comment From 60c93d2c6de6690913ba6f9552615116c2470a29 Mon Sep 17 00:00:00 2001 From: Arthur Araujo Date: Wed, 27 Mar 2024 08:22:51 -0300 Subject: [PATCH 3574/4092] Add reverse pattern visual tests --- .../Editor/TestSceneCatchReverseSelection.cs | 313 ++++++++++++++++ .../Editor/TestSceneOsuReverseSelection.cs | 347 ++++++++++++++++++ 2 files changed, 660 insertions(+) create mode 100644 osu.Game.Rulesets.Catch.Tests/Editor/TestSceneCatchReverseSelection.cs create mode 100644 osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuReverseSelection.cs diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneCatchReverseSelection.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneCatchReverseSelection.cs new file mode 100644 index 0000000000..c8a48f76eb --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneCatchReverseSelection.cs @@ -0,0 +1,313 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Objects; +using osu.Game.Tests.Beatmaps; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Rulesets.Catch.Tests.Editor +{ + [TestFixture] + public partial class TestSceneCatchReverseSelection : TestSceneEditor + { + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false); + + [Test] + public void TestReverseSelectionTwoFruits() + { + float fruit1OldX = default; + float fruit2OldX = default; + + addObjects([ + new Fruit + { + StartTime = 200, + X = fruit1OldX = 0, + }, + new Fruit + { + StartTime = 400, + X = fruit2OldX = 20, + } + ]); + + selectEverything(); + reverseSelection(); + + AddAssert("fruit1 is at fruit2's X", + () => EditorBeatmap.HitObjects.OfType().ElementAt(0).EffectiveX, + () => Is.EqualTo(fruit2OldX) + ); + + AddAssert("fruit2 is at fruit1's X", + () => EditorBeatmap.HitObjects.OfType().ElementAt(1).EffectiveX, + () => Is.EqualTo(fruit1OldX) + ); + + AddAssert("fruit2 is not a new combo", + () => EditorBeatmap.HitObjects.OfType().ElementAt(1).NewCombo, + () => Is.EqualTo(false) + ); + } + + [Test] + public void TestReverseSelectionThreeFruits() + { + float fruit1OldX = default; + float fruit2OldX = default; + float fruit3OldX = default; + + addObjects([ + new Fruit + { + StartTime = 200, + X = fruit1OldX = 0, + }, + new Fruit + { + StartTime = 400, + X = fruit2OldX = 20, + }, + new Fruit + { + StartTime = 600, + X = fruit3OldX = 40, + } + ]); + + selectEverything(); + reverseSelection(); + + AddAssert("fruit1 is at fruit3's X", + () => EditorBeatmap.HitObjects.OfType().ElementAt(0).EffectiveX, + () => Is.EqualTo(fruit3OldX) + ); + + AddAssert("fruit2's X is unchanged", + () => EditorBeatmap.HitObjects.OfType().ElementAt(1).EffectiveX, + () => Is.EqualTo(fruit2OldX) + ); + + AddAssert("fruit3's is at fruit1's X", + () => EditorBeatmap.HitObjects.OfType().ElementAt(2).EffectiveX, + () => Is.EqualTo(fruit1OldX) + ); + + AddAssert("fruit3 is not a new combo", + () => EditorBeatmap.HitObjects.OfType().ElementAt(2).NewCombo, + () => Is.EqualTo(false) + ); + } + + [Test] + public void TestReverseSelectionFruitAndJuiceStream() + { + addObjects([ + new Fruit + { + StartTime = 200, + X = 0, + }, + new JuiceStream + { + StartTime = 400, + X = 20, + Path = new SliderPath + { + ControlPoints = + { + new PathControlPoint(), + new PathControlPoint(new Vector2(50)) + } + } + } + ]); + + selectEverything(); + reverseSelection(); + + AddAssert("First element is juice stream", + () => EditorBeatmap.HitObjects.First().GetType(), + () => Is.EqualTo(typeof(JuiceStream)) + ); + + AddAssert("Last element is fruit", + () => EditorBeatmap.HitObjects.Last().GetType(), + () => Is.EqualTo(typeof(Fruit)) + ); + + AddAssert("Fruit is not new combo", + () => EditorBeatmap.HitObjects.OfType().ElementAt(0).NewCombo, + () => Is.EqualTo(false) + ); + } + + [Test] + public void TestReverseSelectionTwoFruitsAndJuiceStream() + { + addObjects([ + new Fruit + { + StartTime = 200, + X = 0, + }, + new Fruit + { + StartTime = 400, + X = 20, + }, + new JuiceStream + { + StartTime = 600, + X = 40, + Path = new SliderPath + { + ControlPoints = + { + new PathControlPoint(), + new PathControlPoint(new Vector2(50)) + } + } + } + ]); + + selectEverything(); + reverseSelection(); + + AddAssert("First element is juice stream", + () => EditorBeatmap.HitObjects.First().GetType(), + () => Is.EqualTo(typeof(JuiceStream)) + ); + + AddAssert("Middle element is Fruit", + () => EditorBeatmap.HitObjects.ElementAt(1).GetType(), + () => Is.EqualTo(typeof(Fruit)) + ); + + AddAssert("Last element is Fruit", + () => EditorBeatmap.HitObjects.Last().GetType(), + () => Is.EqualTo(typeof(Fruit)) + ); + + AddAssert("Last fruit is not new combo", + () => EditorBeatmap.HitObjects.OfType().Last().NewCombo, + () => Is.EqualTo(false) + ); + } + + [Test] + public void TestReverseSelectionTwoCombos() + { + float fruit1OldX = default; + float fruit2OldX = default; + float fruit3OldX = default; + + float fruit4OldX = default; + float fruit5OldX = default; + float fruit6OldX = default; + + addObjects([ + new Fruit + { + StartTime = 200, + X = fruit1OldX = 0, + }, + new Fruit + { + StartTime = 400, + X = fruit2OldX = 20, + }, + new Fruit + { + StartTime = 600, + X = fruit3OldX = 40, + }, + + new Fruit + { + StartTime = 800, + NewCombo = true, + X = fruit4OldX = 60, + }, + new Fruit + { + StartTime = 1000, + X = fruit5OldX = 80, + }, + new Fruit + { + StartTime = 1200, + X = fruit6OldX = 100, + } + ]); + + selectEverything(); + reverseSelection(); + + AddAssert("fruit1 is at fruit6 position", + () => EditorBeatmap.HitObjects.OfType().ElementAt(0).EffectiveX, + () => Is.EqualTo(fruit6OldX) + ); + + AddAssert("fruit2 is at fruit5 position", + () => EditorBeatmap.HitObjects.OfType().ElementAt(1).EffectiveX, + () => Is.EqualTo(fruit5OldX) + ); + + AddAssert("fruit3 is at fruit4 position", + () => EditorBeatmap.HitObjects.OfType().ElementAt(2).EffectiveX, + () => Is.EqualTo(fruit4OldX) + ); + + AddAssert("fruit4 is at fruit3 position", + () => EditorBeatmap.HitObjects.OfType().ElementAt(3).EffectiveX, + () => Is.EqualTo(fruit3OldX) + ); + + AddAssert("fruit5 is at fruit2 position", + () => EditorBeatmap.HitObjects.OfType().ElementAt(4).EffectiveX, + () => Is.EqualTo(fruit2OldX) + ); + + AddAssert("fruit6 is at fruit1 position", + () => EditorBeatmap.HitObjects.OfType().ElementAt(5).EffectiveX, + () => Is.EqualTo(fruit1OldX) + ); + + AddAssert("fruit1 is new combo", + () => EditorBeatmap.HitObjects.OfType().ElementAt(0).NewCombo, + () => Is.EqualTo(true) + ); + + AddAssert("fruit4 is new combo", + () => EditorBeatmap.HitObjects.OfType().ElementAt(3).NewCombo, + () => Is.EqualTo(true) + ); + } + + private void addObjects(CatchHitObject[] hitObjects) => AddStep("Add objects", () => EditorBeatmap.AddRange(hitObjects)); + + private void selectEverything() + { + AddStep("Select everything", () => + { + EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects); + }); + } + + private void reverseSelection() + { + AddStep("Reverse selection", () => + { + InputManager.PressKey(Key.LControl); + InputManager.Key(Key.G); + InputManager.ReleaseKey(Key.LControl); + }); + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuReverseSelection.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuReverseSelection.cs new file mode 100644 index 0000000000..33104288ab --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuReverseSelection.cs @@ -0,0 +1,347 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Beatmaps; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Rulesets.Osu.Tests.Editor +{ + [TestFixture] + public partial class TestSceneOsuReverseSelection : TestSceneOsuEditor + { + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false); + + [Test] + public void TestReverseSelectionTwoCircles() + { + Vector2 circle1OldPosition = default; + Vector2 circle2OldPosition = default; + + AddStep("Add circles", () => + { + var circle1 = new HitCircle + { + StartTime = 0, + Position = circle1OldPosition = new Vector2(208, 240) + }; + var circle2 = new HitCircle + { + StartTime = 200, + Position = circle2OldPosition = new Vector2(256, 144) + }; + + EditorBeatmap.AddRange([circle1, circle2]); + }); + + AddStep("Select circles", () => + { + EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects); + }); + + AddStep("Reverse selection", () => + { + InputManager.PressKey(Key.LControl); + InputManager.Key(Key.G); + InputManager.ReleaseKey(Key.LControl); + }); + + AddAssert("circle1 is at circle2 position", + () => EditorBeatmap.HitObjects.OfType().ElementAt(0).Position, + () => Is.EqualTo(circle2OldPosition) + ); + + AddAssert("circle2 is at circle1 position", + () => EditorBeatmap.HitObjects.OfType().ElementAt(1).Position, + () => Is.EqualTo(circle1OldPosition) + ); + + AddAssert("circle2 is not a new combo", + () => EditorBeatmap.HitObjects.OfType().ElementAt(1).NewCombo, + () => Is.EqualTo(false) + ); + } + + [Test] + public void TestReverseSelectionThreeCircles() + { + Vector2 circle1OldPosition = default; + Vector2 circle2OldPosition = default; + Vector2 circle3OldPosition = default; + + AddStep("Add circles", () => + { + var circle1 = new HitCircle + { + StartTime = 0, + Position = circle1OldPosition = new Vector2(208, 240) + }; + var circle2 = new HitCircle + { + StartTime = 200, + Position = circle2OldPosition = new Vector2(256, 144) + }; + var circle3 = new HitCircle + { + StartTime = 400, + Position = circle3OldPosition = new Vector2(304, 240) + }; + + EditorBeatmap.AddRange([circle1, circle2, circle3]); + }); + + AddStep("Select circles", () => + { + EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects); + }); + + AddStep("Reverse selection", () => + { + InputManager.PressKey(Key.LControl); + InputManager.Key(Key.G); + InputManager.ReleaseKey(Key.LControl); + }); + + AddAssert("circle1 is at circle3 position", + () => EditorBeatmap.HitObjects.OfType().ElementAt(0).Position, + () => Is.EqualTo(circle3OldPosition) + ); + + AddAssert("circle3 is at circle1 position", + () => EditorBeatmap.HitObjects.OfType().ElementAt(2).Position, + () => Is.EqualTo(circle1OldPosition) + ); + + AddAssert("circle3 is not a new combo", + () => EditorBeatmap.HitObjects.OfType().ElementAt(2).NewCombo, + () => Is.EqualTo(false) + ); + } + + [Test] + public void TestReverseSelectionCircleAndSlider() + { + Vector2 circleOldPosition = default; + Vector2 sliderHeadOldPosition = default; + Vector2 sliderTailOldPosition = default; + + AddStep("Add objects", () => + { + var circle = new HitCircle + { + StartTime = 0, + Position = circleOldPosition = new Vector2(208, 240) + }; + var slider = new Slider + { + StartTime = 200, + Position = sliderHeadOldPosition = new Vector2(257, 144), + Path = new SliderPath + { + ControlPoints = + { + new PathControlPoint(), + new PathControlPoint(new Vector2(100)) + } + } + }; + + sliderTailOldPosition = slider.EndPosition; + + EditorBeatmap.AddRange([circle, slider]); + }); + + AddStep("Select objects", () => + { + var circle = (HitCircle)EditorBeatmap.HitObjects[0]; + var slider = (Slider)EditorBeatmap.HitObjects[1]; + + EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects); + }); + + AddStep("Reverse selection", () => + { + InputManager.PressKey(Key.LControl); + InputManager.Key(Key.G); + InputManager.ReleaseKey(Key.LControl); + }); + + AddAssert("circle is at the same position", + () => EditorBeatmap.HitObjects.OfType().ElementAt(0).Position, + () => Is.EqualTo(circleOldPosition) + ); + + AddAssert("Slider head is at slider tail", () => + Vector2.Distance(EditorBeatmap.HitObjects.OfType().ElementAt(0).Position, sliderTailOldPosition) < 1); + + AddAssert("Slider tail is at slider head", () => + Vector2.Distance(EditorBeatmap.HitObjects.OfType().ElementAt(0).EndPosition, sliderHeadOldPosition) < 1); + } + + [Test] + public void TestReverseSelectionTwoCirclesAndSlider() + { + Vector2 circle1OldPosition = default; + Vector2 circle2OldPosition = default; + + Vector2 sliderHeadOldPosition = default; + Vector2 sliderTailOldPosition = default; + + AddStep("Add objects", () => + { + var circle1 = new HitCircle + { + StartTime = 0, + Position = circle1OldPosition = new Vector2(208, 240) + }; + var circle2 = new HitCircle + { + StartTime = 200, + Position = circle2OldPosition = new Vector2(256, 144) + }; + var slider = new Slider + { + StartTime = 200, + Position = sliderHeadOldPosition = new Vector2(304, 240), + Path = new SliderPath + { + ControlPoints = + { + new PathControlPoint(), + new PathControlPoint(new Vector2(100)) + } + } + }; + + sliderTailOldPosition = slider.EndPosition; + + EditorBeatmap.AddRange([circle1, circle2, slider]); + }); + + AddStep("Select objects", () => + { + EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects); + }); + + AddStep("Reverse selection", () => + { + InputManager.PressKey(Key.LControl); + InputManager.Key(Key.G); + InputManager.ReleaseKey(Key.LControl); + }); + + AddAssert("circle1 is at circle2 position", + () => EditorBeatmap.HitObjects.OfType().ElementAt(0).Position, + () => Is.EqualTo(circle2OldPosition) + ); + + AddAssert("circle2 is at circle1 position", + () => EditorBeatmap.HitObjects.OfType().ElementAt(1).Position, + () => Is.EqualTo(circle1OldPosition) + ); + + AddAssert("Slider head is at slider tail", () => + Vector2.Distance(EditorBeatmap.HitObjects.OfType().ElementAt(0).Position, sliderTailOldPosition) < 1); + + AddAssert("Slider tail is at slider head", () => + Vector2.Distance(EditorBeatmap.HitObjects.OfType().ElementAt(0).EndPosition, sliderHeadOldPosition) < 1); + } + + [Test] + public void TestReverseSelectionTwoCombos() + { + Vector2 circle1OldPosition = default; + Vector2 circle2OldPosition = default; + Vector2 circle3OldPosition = default; + + Vector2 circle4OldPosition = default; + Vector2 circle5OldPosition = default; + Vector2 circle6OldPosition = default; + + AddStep("Add circles", () => + { + var circle1 = new HitCircle + { + StartTime = 0, + Position = circle1OldPosition = new Vector2(216, 240) + }; + var circle2 = new HitCircle + { + StartTime = 200, + Position = circle2OldPosition = new Vector2(120, 192) + }; + var circle3 = new HitCircle + { + StartTime = 400, + Position = circle3OldPosition = new Vector2(216, 144) + }; + + var circle4 = new HitCircle + { + StartTime = 646, + NewCombo = true, + Position = circle4OldPosition = new Vector2(296, 240) + }; + var circle5 = new HitCircle + { + StartTime = 846, + Position = circle5OldPosition = new Vector2(392, 162) + }; + var circle6 = new HitCircle + { + StartTime = 1046, + Position = circle6OldPosition = new Vector2(296, 144) + }; + + EditorBeatmap.AddRange([circle1, circle2, circle3, circle4, circle5, circle6]); + }); + + AddStep("Select circles", () => + { + EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects); + }); + + AddStep("Reverse selection", () => + { + InputManager.PressKey(Key.LControl); + InputManager.Key(Key.G); + InputManager.ReleaseKey(Key.LControl); + }); + + AddAssert("circle1 is at circle6 position", + () => EditorBeatmap.HitObjects.OfType().ElementAt(0).Position, + () => Is.EqualTo(circle6OldPosition) + ); + + AddAssert("circle2 is at circle5 position", + () => EditorBeatmap.HitObjects.OfType().ElementAt(1).Position, + () => Is.EqualTo(circle5OldPosition) + ); + + AddAssert("circle3 is at circle4 position", + () => EditorBeatmap.HitObjects.OfType().ElementAt(2).Position, + () => Is.EqualTo(circle4OldPosition) + ); + + AddAssert("circle4 is at circle3 position", + () => EditorBeatmap.HitObjects.OfType().ElementAt(3).Position, + () => Is.EqualTo(circle3OldPosition) + ); + + AddAssert("circle5 is at circle2 position", + () => EditorBeatmap.HitObjects.OfType().ElementAt(4).Position, + () => Is.EqualTo(circle2OldPosition) + ); + + AddAssert("circle6 is at circle1 position", + () => EditorBeatmap.HitObjects.OfType().ElementAt(5).Position, + () => Is.EqualTo(circle1OldPosition) + ); + } + } +} From 53900d5472ac14a41003f5353bd704e42e245a7b Mon Sep 17 00:00:00 2001 From: Susko3 Date: Wed, 27 Mar 2024 18:56:26 +0100 Subject: [PATCH 3575/4092] Fix tests failing locally due to not using invariant culture --- osu.Game/Skinning/SkinImporter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinImporter.cs b/osu.Game/Skinning/SkinImporter.cs index 3e948a8afb..59c7f0ba26 100644 --- a/osu.Game/Skinning/SkinImporter.cs +++ b/osu.Game/Skinning/SkinImporter.cs @@ -132,7 +132,7 @@ namespace osu.Game.Skinning { // skins without a skin.ini are supposed to import using the "latest version" spec. // see https://github.com/peppy/osu-stable-reference/blob/1531237b63392e82c003c712faa028406073aa8f/osu!/Graphics/Skinning/SkinManager.cs#L297-L298 - newLines.Add($"Version: {SkinConfiguration.LATEST_VERSION}"); + newLines.Add(FormattableString.Invariant($"Version: {SkinConfiguration.LATEST_VERSION}")); // In the case a skin doesn't have a skin.ini yet, let's create one. writeNewSkinIni(); From a9cbabf71135ff97c587a1c01079b56850ef27f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Mar 2024 10:05:26 +0100 Subject: [PATCH 3576/4092] Simplify tests --- .../Editor/TestSceneCatchReverseSelection.cs | 196 ++++++------------ .../Editor/TestSceneOsuReverseSelection.cs | 177 ++++++---------- 2 files changed, 133 insertions(+), 240 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneCatchReverseSelection.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneCatchReverseSelection.cs index c8a48f76eb..36a0e3388e 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneCatchReverseSelection.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneCatchReverseSelection.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 System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Game.Beatmaps; @@ -20,93 +21,78 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor [Test] public void TestReverseSelectionTwoFruits() { - float fruit1OldX = default; - float fruit2OldX = default; + CatchHitObject[] objects = null!; + bool[] newCombos = null!; addObjects([ new Fruit { StartTime = 200, - X = fruit1OldX = 0, + X = 0, }, new Fruit { StartTime = 400, - X = fruit2OldX = 20, + X = 20, } ]); + AddStep("store objects & new combo data", () => + { + objects = getObjects().ToArray(); + newCombos = getObjectNewCombos().ToArray(); + }); + selectEverything(); reverseSelection(); - AddAssert("fruit1 is at fruit2's X", - () => EditorBeatmap.HitObjects.OfType().ElementAt(0).EffectiveX, - () => Is.EqualTo(fruit2OldX) - ); - - AddAssert("fruit2 is at fruit1's X", - () => EditorBeatmap.HitObjects.OfType().ElementAt(1).EffectiveX, - () => Is.EqualTo(fruit1OldX) - ); - - AddAssert("fruit2 is not a new combo", - () => EditorBeatmap.HitObjects.OfType().ElementAt(1).NewCombo, - () => Is.EqualTo(false) - ); + AddAssert("objects reversed", getObjects, () => Is.EqualTo(objects.Reverse())); + AddAssert("new combo positions preserved", getObjectNewCombos, () => Is.EqualTo(newCombos)); } [Test] public void TestReverseSelectionThreeFruits() { - float fruit1OldX = default; - float fruit2OldX = default; - float fruit3OldX = default; + CatchHitObject[] objects = null!; + bool[] newCombos = null!; addObjects([ new Fruit { StartTime = 200, - X = fruit1OldX = 0, + X = 0, }, new Fruit { StartTime = 400, - X = fruit2OldX = 20, + X = 20, }, new Fruit { StartTime = 600, - X = fruit3OldX = 40, + X = 40, } ]); + AddStep("store objects & new combo data", () => + { + objects = getObjects().ToArray(); + newCombos = getObjectNewCombos().ToArray(); + }); + selectEverything(); reverseSelection(); - AddAssert("fruit1 is at fruit3's X", - () => EditorBeatmap.HitObjects.OfType().ElementAt(0).EffectiveX, - () => Is.EqualTo(fruit3OldX) - ); - - AddAssert("fruit2's X is unchanged", - () => EditorBeatmap.HitObjects.OfType().ElementAt(1).EffectiveX, - () => Is.EqualTo(fruit2OldX) - ); - - AddAssert("fruit3's is at fruit1's X", - () => EditorBeatmap.HitObjects.OfType().ElementAt(2).EffectiveX, - () => Is.EqualTo(fruit1OldX) - ); - - AddAssert("fruit3 is not a new combo", - () => EditorBeatmap.HitObjects.OfType().ElementAt(2).NewCombo, - () => Is.EqualTo(false) - ); + AddAssert("objects reversed", getObjects, () => Is.EqualTo(objects.Reverse())); + AddAssert("new combo positions preserved", getObjectNewCombos, () => Is.EqualTo(newCombos)); } [Test] public void TestReverseSelectionFruitAndJuiceStream() { + CatchHitObject[] objects = null!; + bool[] newCombos = null!; + addObjects([ new Fruit { @@ -128,28 +114,25 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor } ]); + AddStep("store objects & new combo data", () => + { + objects = getObjects().ToArray(); + newCombos = getObjectNewCombos().ToArray(); + }); + selectEverything(); reverseSelection(); - AddAssert("First element is juice stream", - () => EditorBeatmap.HitObjects.First().GetType(), - () => Is.EqualTo(typeof(JuiceStream)) - ); - - AddAssert("Last element is fruit", - () => EditorBeatmap.HitObjects.Last().GetType(), - () => Is.EqualTo(typeof(Fruit)) - ); - - AddAssert("Fruit is not new combo", - () => EditorBeatmap.HitObjects.OfType().ElementAt(0).NewCombo, - () => Is.EqualTo(false) - ); + AddAssert("objects reversed", getObjects, () => Is.EqualTo(objects.Reverse())); + AddAssert("new combo positions preserved", getObjectNewCombos, () => Is.EqualTo(newCombos)); } [Test] public void TestReverseSelectionTwoFruitsAndJuiceStream() { + CatchHitObject[] objects = null!; + bool[] newCombos = null!; + addObjects([ new Fruit { @@ -176,122 +159,79 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor } ]); + AddStep("store objects & new combo data", () => + { + objects = getObjects().ToArray(); + newCombos = getObjectNewCombos().ToArray(); + }); + selectEverything(); reverseSelection(); - AddAssert("First element is juice stream", - () => EditorBeatmap.HitObjects.First().GetType(), - () => Is.EqualTo(typeof(JuiceStream)) - ); - - AddAssert("Middle element is Fruit", - () => EditorBeatmap.HitObjects.ElementAt(1).GetType(), - () => Is.EqualTo(typeof(Fruit)) - ); - - AddAssert("Last element is Fruit", - () => EditorBeatmap.HitObjects.Last().GetType(), - () => Is.EqualTo(typeof(Fruit)) - ); - - AddAssert("Last fruit is not new combo", - () => EditorBeatmap.HitObjects.OfType().Last().NewCombo, - () => Is.EqualTo(false) - ); + AddAssert("objects reversed", getObjects, () => Is.EqualTo(objects.Reverse())); + AddAssert("new combo positions preserved", getObjectNewCombos, () => Is.EqualTo(newCombos)); } [Test] public void TestReverseSelectionTwoCombos() { - float fruit1OldX = default; - float fruit2OldX = default; - float fruit3OldX = default; - - float fruit4OldX = default; - float fruit5OldX = default; - float fruit6OldX = default; + CatchHitObject[] objects = null!; + bool[] newCombos = null!; addObjects([ new Fruit { StartTime = 200, - X = fruit1OldX = 0, + X = 0, }, new Fruit { StartTime = 400, - X = fruit2OldX = 20, + X = 20, }, new Fruit { StartTime = 600, - X = fruit3OldX = 40, + X = 40, }, new Fruit { StartTime = 800, NewCombo = true, - X = fruit4OldX = 60, + X = 60, }, new Fruit { StartTime = 1000, - X = fruit5OldX = 80, + X = 80, }, new Fruit { StartTime = 1200, - X = fruit6OldX = 100, + X = 100, } ]); + AddStep("store objects & new combo data", () => + { + objects = getObjects().ToArray(); + newCombos = getObjectNewCombos().ToArray(); + }); + selectEverything(); reverseSelection(); - AddAssert("fruit1 is at fruit6 position", - () => EditorBeatmap.HitObjects.OfType().ElementAt(0).EffectiveX, - () => Is.EqualTo(fruit6OldX) - ); - - AddAssert("fruit2 is at fruit5 position", - () => EditorBeatmap.HitObjects.OfType().ElementAt(1).EffectiveX, - () => Is.EqualTo(fruit5OldX) - ); - - AddAssert("fruit3 is at fruit4 position", - () => EditorBeatmap.HitObjects.OfType().ElementAt(2).EffectiveX, - () => Is.EqualTo(fruit4OldX) - ); - - AddAssert("fruit4 is at fruit3 position", - () => EditorBeatmap.HitObjects.OfType().ElementAt(3).EffectiveX, - () => Is.EqualTo(fruit3OldX) - ); - - AddAssert("fruit5 is at fruit2 position", - () => EditorBeatmap.HitObjects.OfType().ElementAt(4).EffectiveX, - () => Is.EqualTo(fruit2OldX) - ); - - AddAssert("fruit6 is at fruit1 position", - () => EditorBeatmap.HitObjects.OfType().ElementAt(5).EffectiveX, - () => Is.EqualTo(fruit1OldX) - ); - - AddAssert("fruit1 is new combo", - () => EditorBeatmap.HitObjects.OfType().ElementAt(0).NewCombo, - () => Is.EqualTo(true) - ); - - AddAssert("fruit4 is new combo", - () => EditorBeatmap.HitObjects.OfType().ElementAt(3).NewCombo, - () => Is.EqualTo(true) - ); + AddAssert("objects reversed", getObjects, () => Is.EqualTo(objects.Reverse())); + AddAssert("new combo positions preserved", getObjectNewCombos, () => Is.EqualTo(newCombos)); } private void addObjects(CatchHitObject[] hitObjects) => AddStep("Add objects", () => EditorBeatmap.AddRange(hitObjects)); + private IEnumerable getObjects() => EditorBeatmap.HitObjects.OfType(); + + private IEnumerable getObjectNewCombos() => getObjects().Select(ho => ho.NewCombo); + private void selectEverything() { AddStep("Select everything", () => diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuReverseSelection.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuReverseSelection.cs index 33104288ab..28c1577fcb 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuReverseSelection.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuReverseSelection.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 System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Game.Beatmaps; @@ -20,30 +21,33 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor [Test] public void TestReverseSelectionTwoCircles() { - Vector2 circle1OldPosition = default; - Vector2 circle2OldPosition = default; + OsuHitObject[] objects = null!; + bool[] newCombos = null!; AddStep("Add circles", () => { var circle1 = new HitCircle { StartTime = 0, - Position = circle1OldPosition = new Vector2(208, 240) + Position = new Vector2(208, 240) }; var circle2 = new HitCircle { StartTime = 200, - Position = circle2OldPosition = new Vector2(256, 144) + Position = new Vector2(256, 144) }; EditorBeatmap.AddRange([circle1, circle2]); }); - AddStep("Select circles", () => + AddStep("store objects & new combo data", () => { - EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects); + objects = getObjects().ToArray(); + newCombos = getObjectNewCombos().ToArray(); }); + AddStep("Select circles", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); + AddStep("Reverse selection", () => { InputManager.PressKey(Key.LControl); @@ -51,55 +55,45 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor InputManager.ReleaseKey(Key.LControl); }); - AddAssert("circle1 is at circle2 position", - () => EditorBeatmap.HitObjects.OfType().ElementAt(0).Position, - () => Is.EqualTo(circle2OldPosition) - ); - - AddAssert("circle2 is at circle1 position", - () => EditorBeatmap.HitObjects.OfType().ElementAt(1).Position, - () => Is.EqualTo(circle1OldPosition) - ); - - AddAssert("circle2 is not a new combo", - () => EditorBeatmap.HitObjects.OfType().ElementAt(1).NewCombo, - () => Is.EqualTo(false) - ); + AddAssert("objects reversed", getObjects, () => Is.EqualTo(objects.Reverse())); + AddAssert("new combo positions preserved", getObjectNewCombos, () => Is.EqualTo(newCombos)); } [Test] public void TestReverseSelectionThreeCircles() { - Vector2 circle1OldPosition = default; - Vector2 circle2OldPosition = default; - Vector2 circle3OldPosition = default; + OsuHitObject[] objects = null!; + bool[] newCombos = null!; AddStep("Add circles", () => { var circle1 = new HitCircle { StartTime = 0, - Position = circle1OldPosition = new Vector2(208, 240) + Position = new Vector2(208, 240) }; var circle2 = new HitCircle { StartTime = 200, - Position = circle2OldPosition = new Vector2(256, 144) + Position = new Vector2(256, 144) }; var circle3 = new HitCircle { StartTime = 400, - Position = circle3OldPosition = new Vector2(304, 240) + Position = new Vector2(304, 240) }; EditorBeatmap.AddRange([circle1, circle2, circle3]); }); - AddStep("Select circles", () => + AddStep("store objects & new combo data", () => { - EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects); + objects = getObjects().ToArray(); + newCombos = getObjectNewCombos().ToArray(); }); + AddStep("Select circles", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); + AddStep("Reverse selection", () => { InputManager.PressKey(Key.LControl); @@ -107,26 +101,16 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor InputManager.ReleaseKey(Key.LControl); }); - AddAssert("circle1 is at circle3 position", - () => EditorBeatmap.HitObjects.OfType().ElementAt(0).Position, - () => Is.EqualTo(circle3OldPosition) - ); - - AddAssert("circle3 is at circle1 position", - () => EditorBeatmap.HitObjects.OfType().ElementAt(2).Position, - () => Is.EqualTo(circle1OldPosition) - ); - - AddAssert("circle3 is not a new combo", - () => EditorBeatmap.HitObjects.OfType().ElementAt(2).NewCombo, - () => Is.EqualTo(false) - ); + AddAssert("objects reversed", getObjects, () => Is.EqualTo(objects.Reverse())); + AddAssert("new combo positions preserved", getObjectNewCombos, () => Is.EqualTo(newCombos)); } [Test] public void TestReverseSelectionCircleAndSlider() { - Vector2 circleOldPosition = default; + OsuHitObject[] objects = null!; + bool[] newCombos = null!; + Vector2 sliderHeadOldPosition = default; Vector2 sliderTailOldPosition = default; @@ -135,7 +119,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor var circle = new HitCircle { StartTime = 0, - Position = circleOldPosition = new Vector2(208, 240) + Position = new Vector2(208, 240) }; var slider = new Slider { @@ -156,14 +140,14 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor EditorBeatmap.AddRange([circle, slider]); }); - AddStep("Select objects", () => + AddStep("store objects & new combo data", () => { - var circle = (HitCircle)EditorBeatmap.HitObjects[0]; - var slider = (Slider)EditorBeatmap.HitObjects[1]; - - EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects); + objects = getObjects().ToArray(); + newCombos = getObjectNewCombos().ToArray(); }); + AddStep("Select objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); + AddStep("Reverse selection", () => { InputManager.PressKey(Key.LControl); @@ -171,10 +155,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor InputManager.ReleaseKey(Key.LControl); }); - AddAssert("circle is at the same position", - () => EditorBeatmap.HitObjects.OfType().ElementAt(0).Position, - () => Is.EqualTo(circleOldPosition) - ); + AddAssert("objects reversed", getObjects, () => Is.EqualTo(objects.Reverse())); + AddAssert("new combo positions preserved", getObjectNewCombos, () => Is.EqualTo(newCombos)); AddAssert("Slider head is at slider tail", () => Vector2.Distance(EditorBeatmap.HitObjects.OfType().ElementAt(0).Position, sliderTailOldPosition) < 1); @@ -186,8 +168,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor [Test] public void TestReverseSelectionTwoCirclesAndSlider() { - Vector2 circle1OldPosition = default; - Vector2 circle2OldPosition = default; + OsuHitObject[] objects = null!; + bool[] newCombos = null!; Vector2 sliderHeadOldPosition = default; Vector2 sliderTailOldPosition = default; @@ -197,12 +179,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor var circle1 = new HitCircle { StartTime = 0, - Position = circle1OldPosition = new Vector2(208, 240) + Position = new Vector2(208, 240) }; var circle2 = new HitCircle { StartTime = 200, - Position = circle2OldPosition = new Vector2(256, 144) + Position = new Vector2(256, 144) }; var slider = new Slider { @@ -223,11 +205,14 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor EditorBeatmap.AddRange([circle1, circle2, slider]); }); - AddStep("Select objects", () => + AddStep("store objects & new combo data", () => { - EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects); + objects = getObjects().ToArray(); + newCombos = getObjectNewCombos().ToArray(); }); + AddStep("Select objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); + AddStep("Reverse selection", () => { InputManager.PressKey(Key.LControl); @@ -235,15 +220,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor InputManager.ReleaseKey(Key.LControl); }); - AddAssert("circle1 is at circle2 position", - () => EditorBeatmap.HitObjects.OfType().ElementAt(0).Position, - () => Is.EqualTo(circle2OldPosition) - ); - - AddAssert("circle2 is at circle1 position", - () => EditorBeatmap.HitObjects.OfType().ElementAt(1).Position, - () => Is.EqualTo(circle1OldPosition) - ); + AddAssert("objects reversed", getObjects, () => Is.EqualTo(objects.Reverse())); + AddAssert("new combo positions preserved", getObjectNewCombos, () => Is.EqualTo(newCombos)); AddAssert("Slider head is at slider tail", () => Vector2.Distance(EditorBeatmap.HitObjects.OfType().ElementAt(0).Position, sliderTailOldPosition) < 1); @@ -255,57 +233,55 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor [Test] public void TestReverseSelectionTwoCombos() { - Vector2 circle1OldPosition = default; - Vector2 circle2OldPosition = default; - Vector2 circle3OldPosition = default; - - Vector2 circle4OldPosition = default; - Vector2 circle5OldPosition = default; - Vector2 circle6OldPosition = default; + OsuHitObject[] objects = null!; + bool[] newCombos = null!; AddStep("Add circles", () => { var circle1 = new HitCircle { StartTime = 0, - Position = circle1OldPosition = new Vector2(216, 240) + Position = new Vector2(216, 240) }; var circle2 = new HitCircle { StartTime = 200, - Position = circle2OldPosition = new Vector2(120, 192) + Position = new Vector2(120, 192) }; var circle3 = new HitCircle { StartTime = 400, - Position = circle3OldPosition = new Vector2(216, 144) + Position = new Vector2(216, 144) }; var circle4 = new HitCircle { StartTime = 646, NewCombo = true, - Position = circle4OldPosition = new Vector2(296, 240) + Position = new Vector2(296, 240) }; var circle5 = new HitCircle { StartTime = 846, - Position = circle5OldPosition = new Vector2(392, 162) + Position = new Vector2(392, 162) }; var circle6 = new HitCircle { StartTime = 1046, - Position = circle6OldPosition = new Vector2(296, 144) + Position = new Vector2(296, 144) }; EditorBeatmap.AddRange([circle1, circle2, circle3, circle4, circle5, circle6]); }); - AddStep("Select circles", () => + AddStep("store objects & new combo data", () => { - EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects); + objects = getObjects().ToArray(); + newCombos = getObjectNewCombos().ToArray(); }); + AddStep("Select circles", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); + AddStep("Reverse selection", () => { InputManager.PressKey(Key.LControl); @@ -313,35 +289,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor InputManager.ReleaseKey(Key.LControl); }); - AddAssert("circle1 is at circle6 position", - () => EditorBeatmap.HitObjects.OfType().ElementAt(0).Position, - () => Is.EqualTo(circle6OldPosition) - ); - - AddAssert("circle2 is at circle5 position", - () => EditorBeatmap.HitObjects.OfType().ElementAt(1).Position, - () => Is.EqualTo(circle5OldPosition) - ); - - AddAssert("circle3 is at circle4 position", - () => EditorBeatmap.HitObjects.OfType().ElementAt(2).Position, - () => Is.EqualTo(circle4OldPosition) - ); - - AddAssert("circle4 is at circle3 position", - () => EditorBeatmap.HitObjects.OfType().ElementAt(3).Position, - () => Is.EqualTo(circle3OldPosition) - ); - - AddAssert("circle5 is at circle2 position", - () => EditorBeatmap.HitObjects.OfType().ElementAt(4).Position, - () => Is.EqualTo(circle2OldPosition) - ); - - AddAssert("circle6 is at circle1 position", - () => EditorBeatmap.HitObjects.OfType().ElementAt(5).Position, - () => Is.EqualTo(circle1OldPosition) - ); + AddAssert("objects reversed", getObjects, () => Is.EqualTo(objects.Reverse())); + AddAssert("new combo positions preserved", getObjectNewCombos, () => Is.EqualTo(newCombos)); } + + private IEnumerable getObjects() => EditorBeatmap.HitObjects.OfType(); + + private IEnumerable getObjectNewCombos() => getObjects().Select(ho => ho.NewCombo); } } From 2f786ffc32d925d15d3417f696ac3a046dc2af80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Mar 2024 10:12:27 +0100 Subject: [PATCH 3577/4092] Simplify implementation --- .../Edit/CatchSelectionHandler.cs | 21 +++++++++---------- .../Edit/OsuSelectionHandler.cs | 21 +++++++++---------- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs index f8fe9805e6..a2784126eb 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs @@ -77,13 +77,16 @@ namespace osu.Game.Rulesets.Catch.Edit public override bool HandleReverse() { var hitObjects = EditorBeatmap.SelectedHitObjects - .OfType() - .OrderBy(obj => obj.StartTime) - .ToList(); + .OfType() + .OrderBy(obj => obj.StartTime) + .ToList(); double selectionStartTime = SelectedItems.Min(h => h.StartTime); double selectionEndTime = SelectedItems.Max(h => h.GetEndTime()); + // the expectation is that even if the objects themselves are reversed temporally, + // the position of new combos in the selection should remain the same. + // preserve it for later before doing the reversal. var newComboOrder = hitObjects.Select(obj => obj.NewCombo).ToList(); foreach (var h in hitObjects) @@ -99,15 +102,11 @@ namespace osu.Game.Rulesets.Catch.Edit } } - // re-order objects again after flipping their times - hitObjects = [.. hitObjects.OrderBy(obj => obj.StartTime)]; + // re-order objects by start time again after reversing, and restore new combo flag positioning + hitObjects = hitObjects.OrderBy(obj => obj.StartTime).ToList(); - int i = 0; - foreach (bool newCombo in newComboOrder) - { - hitObjects[i].NewCombo = newCombo; - i++; - } + for (int i = 0; i < hitObjects.Count; ++i) + hitObjects[i].NewCombo = newComboOrder[i]; return true; } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 0e889cab81..b33272968b 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -79,15 +79,18 @@ namespace osu.Game.Rulesets.Osu.Edit public override bool HandleReverse() { var hitObjects = EditorBeatmap.SelectedHitObjects - .OfType() - .OrderBy(obj => obj.StartTime) - .ToList(); + .OfType() + .OrderBy(obj => obj.StartTime) + .ToList(); double endTime = hitObjects.Max(h => h.GetEndTime()); double startTime = hitObjects.Min(h => h.StartTime); bool moreThanOneObject = hitObjects.Count > 1; + // the expectation is that even if the objects themselves are reversed temporally, + // the position of new combos in the selection should remain the same. + // preserve it for later before doing the reversal. var newComboOrder = hitObjects.Select(obj => obj.NewCombo).ToList(); foreach (var h in hitObjects) @@ -102,15 +105,11 @@ namespace osu.Game.Rulesets.Osu.Edit } } - // re-order objects again after flipping their times - hitObjects = [.. hitObjects.OrderBy(obj => obj.StartTime)]; + // re-order objects by start time again after reversing, and restore new combo flag positioning + hitObjects = hitObjects.OrderBy(obj => obj.StartTime).ToList(); - int i = 0; - foreach (bool newCombo in newComboOrder) - { - hitObjects[i].NewCombo = newCombo; - i++; - } + for (int i = 0; i < hitObjects.Count; ++i) + hitObjects[i].NewCombo = newComboOrder[i]; return true; } From 5febd40bd9910f73e65d4bf37b08069833828e7d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 28 Mar 2024 22:30:39 +0900 Subject: [PATCH 3578/4092] Add HP and AR to LegacyBeatmapConversionDifficultyInfo --- .../LegacyBeatmapConversionDifficultyInfo.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/Legacy/LegacyBeatmapConversionDifficultyInfo.cs b/osu.Game/Rulesets/Scoring/Legacy/LegacyBeatmapConversionDifficultyInfo.cs index 7d69069455..f8b8567305 100644 --- a/osu.Game/Rulesets/Scoring/Legacy/LegacyBeatmapConversionDifficultyInfo.cs +++ b/osu.Game/Rulesets/Scoring/Legacy/LegacyBeatmapConversionDifficultyInfo.cs @@ -18,6 +18,16 @@ namespace osu.Game.Rulesets.Scoring.Legacy /// public IRulesetInfo SourceRuleset { get; set; } = new RulesetInfo(); + /// + /// The beatmap drain rate. + /// + public float DrainRate { get; set; } + + /// + /// The beatmap approach rate. + /// + public float ApproachRate { get; set; } + /// /// The beatmap circle size. /// @@ -41,8 +51,6 @@ namespace osu.Game.Rulesets.Scoring.Legacy /// public int TotalObjectCount { get; set; } - float IBeatmapDifficultyInfo.DrainRate => 0; - float IBeatmapDifficultyInfo.ApproachRate => 0; double IBeatmapDifficultyInfo.SliderMultiplier => 0; double IBeatmapDifficultyInfo.SliderTickRate => 0; @@ -51,6 +59,8 @@ namespace osu.Game.Rulesets.Scoring.Legacy public static LegacyBeatmapConversionDifficultyInfo FromBeatmap(IBeatmap beatmap) => new LegacyBeatmapConversionDifficultyInfo { SourceRuleset = beatmap.BeatmapInfo.Ruleset, + DrainRate = beatmap.Difficulty.DrainRate, + ApproachRate = beatmap.Difficulty.ApproachRate, CircleSize = beatmap.Difficulty.CircleSize, OverallDifficulty = beatmap.Difficulty.OverallDifficulty, EndTimeObjectCount = beatmap.HitObjects.Count(h => h is IHasDuration), @@ -60,6 +70,8 @@ namespace osu.Game.Rulesets.Scoring.Legacy public static LegacyBeatmapConversionDifficultyInfo FromBeatmapInfo(IBeatmapInfo beatmapInfo) => new LegacyBeatmapConversionDifficultyInfo { SourceRuleset = beatmapInfo.Ruleset, + DrainRate = beatmapInfo.Difficulty.DrainRate, + ApproachRate = beatmapInfo.Difficulty.ApproachRate, CircleSize = beatmapInfo.Difficulty.CircleSize, OverallDifficulty = beatmapInfo.Difficulty.OverallDifficulty, EndTimeObjectCount = beatmapInfo.EndTimeObjectCount, From 64399e9dd9841b7062e5f218c688424440c00b65 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 28 Mar 2024 22:32:27 +0900 Subject: [PATCH 3579/4092] Refactor pattern generation to not require ManiaBeatmap --- .../Beatmaps/ManiaBeatmapConverter.cs | 30 ++++++++++++++----- .../Legacy/EndTimeObjectPatternGenerator.cs | 4 +-- .../Legacy/HitObjectPatternGenerator.cs | 6 ++-- .../Legacy/PathObjectPatternGenerator.cs | 4 +-- .../Patterns/Legacy/PatternGenerator.cs | 22 +++++--------- .../Beatmaps/Patterns/PatternGenerator.cs | 8 ++--- 6 files changed, 41 insertions(+), 33 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index def22608d6..cc975c7def 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -27,8 +27,24 @@ namespace osu.Game.Rulesets.Mania.Beatmaps /// private const int max_notes_for_density = 7; + /// + /// The total number of columns. + /// + public int TotalColumns => TargetColumns * (Dual ? 2 : 1); + + /// + /// The number of columns per-stage. + /// public int TargetColumns; + + /// + /// Whether to double the number of stages. + /// public bool Dual; + + /// + /// Whether the beatmap instantiated with is for the mania ruleset. + /// public readonly bool IsForCurrentRuleset; private readonly int originalTargetColumns; @@ -152,7 +168,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps /// The hit objects generated. private IEnumerable generateSpecific(HitObject original, IBeatmap originalBeatmap) { - var generator = new SpecificBeatmapPatternGenerator(Random, original, beatmap, lastPattern, originalBeatmap); + var generator = new SpecificBeatmapPatternGenerator(Random, original, originalBeatmap, TotalColumns, lastPattern); foreach (var newPattern in generator.Generate()) { @@ -171,13 +187,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps /// The hit objects generated. private IEnumerable generateConverted(HitObject original, IBeatmap originalBeatmap) { - Patterns.PatternGenerator conversion = null; + Patterns.PatternGenerator? conversion = null; switch (original) { case IHasPath: { - var generator = new PathObjectPatternGenerator(Random, original, beatmap, lastPattern, originalBeatmap); + var generator = new PathObjectPatternGenerator(Random, original, originalBeatmap, TotalColumns, lastPattern); conversion = generator; var positionData = original as IHasPosition; @@ -195,7 +211,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps case IHasDuration endTimeData: { - conversion = new EndTimeObjectPatternGenerator(Random, original, beatmap, lastPattern, originalBeatmap); + conversion = new EndTimeObjectPatternGenerator(Random, original, originalBeatmap, TotalColumns, lastPattern); recordNote(endTimeData.EndTime, new Vector2(256, 192)); computeDensity(endTimeData.EndTime); @@ -206,7 +222,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps { computeDensity(original.StartTime); - conversion = new HitObjectPatternGenerator(Random, original, beatmap, lastPattern, lastTime, lastPosition, density, lastStair, originalBeatmap); + conversion = new HitObjectPatternGenerator(Random, original, originalBeatmap, TotalColumns, lastPattern, lastTime, lastPosition, density, lastStair); recordNote(original.StartTime, positionData.Position); break; @@ -231,8 +247,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps /// private class SpecificBeatmapPatternGenerator : Patterns.Legacy.PatternGenerator { - public SpecificBeatmapPatternGenerator(LegacyRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap) - : base(random, hitObject, beatmap, previousPattern, originalBeatmap) + public SpecificBeatmapPatternGenerator(LegacyRandom random, HitObject hitObject, IBeatmap beatmap, int totalColumns, Pattern previousPattern) + : base(random, hitObject, beatmap, previousPattern, totalColumns) { } diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs index 2265d3d347..52bb87ae19 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs @@ -17,8 +17,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy private readonly int endTime; private readonly PatternType convertType; - public EndTimeObjectPatternGenerator(LegacyRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap) - : base(random, hitObject, beatmap, previousPattern, originalBeatmap) + public EndTimeObjectPatternGenerator(LegacyRandom random, HitObject hitObject, IBeatmap beatmap, int totalColumns, Pattern previousPattern) + : base(random, hitObject, beatmap, previousPattern, totalColumns) { endTime = (int)((HitObject as IHasDuration)?.EndTime ?? 0); diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs index 27cb681300..ad45a3fb21 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs @@ -23,9 +23,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy private readonly PatternType convertType; - public HitObjectPatternGenerator(LegacyRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, double previousTime, Vector2 previousPosition, double density, - PatternType lastStair, IBeatmap originalBeatmap) - : base(random, hitObject, beatmap, previousPattern, originalBeatmap) + public HitObjectPatternGenerator(LegacyRandom random, HitObject hitObject, IBeatmap beatmap, int totalColumns, Pattern previousPattern, double previousTime, Vector2 previousPosition, + double density, PatternType lastStair) + : base(random, hitObject, beatmap, previousPattern, totalColumns) { StairType = lastStair; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PathObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PathObjectPatternGenerator.cs index 4922915c7d..6d593a75e7 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PathObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PathObjectPatternGenerator.cs @@ -31,8 +31,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy private PatternType convertType; - public PathObjectPatternGenerator(LegacyRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap) - : base(random, hitObject, beatmap, previousPattern, originalBeatmap) + public PathObjectPatternGenerator(LegacyRandom random, HitObject hitObject, IBeatmap beatmap, int totalColumns, Pattern previousPattern) + : base(random, hitObject, beatmap, previousPattern, totalColumns) { convertType = PatternType.None; if (!Beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime).KiaiMode) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs index 77f93b4ef9..48b8778501 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs @@ -27,20 +27,12 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// protected readonly LegacyRandom Random; - /// - /// The beatmap which is being converted from. - /// - protected readonly IBeatmap OriginalBeatmap; - - protected PatternGenerator(LegacyRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap) - : base(hitObject, beatmap, previousPattern) + protected PatternGenerator(LegacyRandom random, HitObject hitObject, IBeatmap beatmap, Pattern previousPattern, int totalColumns) + : base(hitObject, beatmap, totalColumns, previousPattern) { ArgumentNullException.ThrowIfNull(random); - ArgumentNullException.ThrowIfNull(originalBeatmap); Random = random; - OriginalBeatmap = originalBeatmap; - RandomStart = TotalColumns == 8 ? 1 : 0; } @@ -104,17 +96,17 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (conversionDifficulty != null) return conversionDifficulty.Value; - HitObject lastObject = OriginalBeatmap.HitObjects.LastOrDefault(); - HitObject firstObject = OriginalBeatmap.HitObjects.FirstOrDefault(); + HitObject lastObject = Beatmap.HitObjects.LastOrDefault(); + HitObject firstObject = Beatmap.HitObjects.FirstOrDefault(); // Drain time in seconds - int drainTime = (int)(((lastObject?.StartTime ?? 0) - (firstObject?.StartTime ?? 0) - OriginalBeatmap.TotalBreakTime) / 1000); + int drainTime = (int)(((lastObject?.StartTime ?? 0) - (firstObject?.StartTime ?? 0) - Beatmap.TotalBreakTime) / 1000); if (drainTime == 0) drainTime = 10000; - IBeatmapDifficultyInfo difficulty = OriginalBeatmap.Difficulty; - conversionDifficulty = ((difficulty.DrainRate + Math.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + (double)OriginalBeatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15; + IBeatmapDifficultyInfo difficulty = Beatmap.Difficulty; + conversionDifficulty = ((difficulty.DrainRate + Math.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + (double)Beatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15; conversionDifficulty = Math.Min(conversionDifficulty.Value, 12); return conversionDifficulty.Value; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs index 3d3c35773b..8d98515fa4 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns @@ -25,11 +26,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns /// /// The beatmap which is a part of. /// - protected readonly ManiaBeatmap Beatmap; + protected readonly IBeatmap Beatmap; protected readonly int TotalColumns; - protected PatternGenerator(HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern) + protected PatternGenerator(HitObject hitObject, IBeatmap beatmap, int totalColumns, Pattern previousPattern) { ArgumentNullException.ThrowIfNull(hitObject); ArgumentNullException.ThrowIfNull(beatmap); @@ -38,8 +39,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns HitObject = hitObject; Beatmap = beatmap; PreviousPattern = previousPattern; - - TotalColumns = Beatmap.TotalColumns; + TotalColumns = totalColumns; } /// From 10edb5461490568a06f43c443dc6cc7f19b14c8a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 28 Mar 2024 22:39:15 +0900 Subject: [PATCH 3580/4092] Add ability to query key count with mods --- .../Beatmaps/ManiaBeatmapConverter.cs | 93 ++++++++++--------- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 4 +- osu.Game/Rulesets/ILegacyRuleset.cs | 5 +- .../Carousel/DrawableCarouselBeatmap.cs | 7 +- .../Screens/Select/Details/AdvancedStats.cs | 2 +- 5 files changed, 61 insertions(+), 50 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index cc975c7def..8b339239a0 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.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.Mania.Objects; using System; using System.Linq; @@ -14,6 +12,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Mania.Beatmaps.Patterns; using osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring.Legacy; using osu.Game.Utils; using osuTK; @@ -50,17 +49,21 @@ namespace osu.Game.Rulesets.Mania.Beatmaps private readonly int originalTargetColumns; // Internal for testing purposes - internal LegacyRandom Random { get; private set; } + internal readonly LegacyRandom Random; private Pattern lastPattern = new Pattern(); - private ManiaBeatmap beatmap; - public ManiaBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) - : base(beatmap, ruleset) + : this(beatmap, LegacyBeatmapConversionDifficultyInfo.FromBeatmap(beatmap), ruleset) { - IsForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo); - TargetColumns = GetColumnCount(LegacyBeatmapConversionDifficultyInfo.FromBeatmap(beatmap)); + } + + private ManiaBeatmapConverter(IBeatmap? beatmap, LegacyBeatmapConversionDifficultyInfo difficulty, Ruleset ruleset) + : base(beatmap!, ruleset) + { + IsForCurrentRuleset = difficulty.SourceRuleset.Equals(ruleset.RulesetInfo); + Random = new LegacyRandom((int)MathF.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)MathF.Round(difficulty.ApproachRate)); + TargetColumns = getColumnCount(difficulty); if (IsForCurrentRuleset && TargetColumns > ManiaRuleset.MAX_STAGE_KEYS) { @@ -69,51 +72,57 @@ namespace osu.Game.Rulesets.Mania.Beatmaps } originalTargetColumns = TargetColumns; + + static int getColumnCount(LegacyBeatmapConversionDifficultyInfo difficulty) + { + double roundedCircleSize = Math.Round(difficulty.CircleSize); + + if (difficulty.SourceRuleset.ShortName == ManiaRuleset.SHORT_NAME) + return (int)Math.Max(1, roundedCircleSize); + + double roundedOverallDifficulty = Math.Round(difficulty.OverallDifficulty); + + if (difficulty.TotalObjectCount > 0 && difficulty.EndTimeObjectCount >= 0) + { + int countSliderOrSpinner = difficulty.EndTimeObjectCount; + + // In osu!stable, this division appears as if it happens on floats, but due to release-mode + // optimisations, it actually ends up happening on doubles. + double percentSpecialObjects = (double)countSliderOrSpinner / difficulty.TotalObjectCount; + + if (percentSpecialObjects < 0.2) + return 7; + if (percentSpecialObjects < 0.3 || roundedCircleSize >= 5) + return roundedOverallDifficulty > 5 ? 7 : 6; + if (percentSpecialObjects > 0.6) + return roundedOverallDifficulty > 4 ? 5 : 4; + } + + return Math.Max(4, Math.Min((int)roundedOverallDifficulty + 1, 7)); + } } - public static int GetColumnCount(LegacyBeatmapConversionDifficultyInfo difficulty) + public static int GetColumnCount(IBeatmapInfo beatmapInfo, IReadOnlyList? mods = null) + => GetColumnCount(LegacyBeatmapConversionDifficultyInfo.FromBeatmapInfo(beatmapInfo), mods); + + public static int GetColumnCount(LegacyBeatmapConversionDifficultyInfo difficulty, IReadOnlyList? mods = null) { - double roundedCircleSize = Math.Round(difficulty.CircleSize); + var converter = new ManiaBeatmapConverter(null, difficulty, new ManiaRuleset()); - if (difficulty.SourceRuleset.ShortName == ManiaRuleset.SHORT_NAME) - return (int)Math.Max(1, roundedCircleSize); - - double roundedOverallDifficulty = Math.Round(difficulty.OverallDifficulty); - - if (difficulty.TotalObjectCount > 0 && difficulty.EndTimeObjectCount >= 0) + if (mods != null) { - int countSliderOrSpinner = difficulty.EndTimeObjectCount; - - // In osu!stable, this division appears as if it happens on floats, but due to release-mode - // optimisations, it actually ends up happening on doubles. - double percentSpecialObjects = (double)countSliderOrSpinner / difficulty.TotalObjectCount; - - if (percentSpecialObjects < 0.2) - return 7; - if (percentSpecialObjects < 0.3 || roundedCircleSize >= 5) - return roundedOverallDifficulty > 5 ? 7 : 6; - if (percentSpecialObjects > 0.6) - return roundedOverallDifficulty > 4 ? 5 : 4; + foreach (var m in mods.OfType()) + m.ApplyToBeatmapConverter(converter); } - return Math.Max(4, Math.Min((int)roundedOverallDifficulty + 1, 7)); + return converter.TotalColumns; } public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition); - protected override Beatmap ConvertBeatmap(IBeatmap original, CancellationToken cancellationToken) - { - IBeatmapDifficultyInfo difficulty = original.Difficulty; - - int seed = (int)MathF.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)MathF.Round(difficulty.ApproachRate); - Random = new LegacyRandom(seed); - - return base.ConvertBeatmap(original, cancellationToken); - } - protected override Beatmap CreateBeatmap() { - beatmap = new ManiaBeatmap(new StageDefinition(TargetColumns), originalTargetColumns); + ManiaBeatmap beatmap = new ManiaBeatmap(new StageDefinition(TargetColumns), originalTargetColumns); if (Dual) beatmap.Stages.Add(new StageDefinition(TargetColumns)); @@ -131,10 +140,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps } var objects = IsForCurrentRuleset ? generateSpecific(original, beatmap) : generateConverted(original, beatmap); - - if (objects == null) - yield break; - foreach (ManiaHitObject obj in objects) yield return obj; } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 3d4803f1e4..77168dca68 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -423,8 +423,8 @@ namespace osu.Game.Rulesets.Mania public override DifficultySection CreateEditorDifficultySection() => new ManiaDifficultySection(); - public int GetKeyCount(IBeatmapInfo beatmapInfo) - => ManiaBeatmapConverter.GetColumnCount(LegacyBeatmapConversionDifficultyInfo.FromBeatmapInfo(beatmapInfo)); + public int GetKeyCount(IBeatmapInfo beatmapInfo, IReadOnlyList? mods = null) + => ManiaBeatmapConverter.GetColumnCount(beatmapInfo, mods); } public enum PlayfieldType diff --git a/osu.Game/Rulesets/ILegacyRuleset.cs b/osu.Game/Rulesets/ILegacyRuleset.cs index 18d86f477a..e116f7a1a3 100644 --- a/osu.Game/Rulesets/ILegacyRuleset.cs +++ b/osu.Game/Rulesets/ILegacyRuleset.cs @@ -1,7 +1,9 @@ // 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 osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring.Legacy; namespace osu.Game.Rulesets @@ -18,8 +20,7 @@ namespace osu.Game.Rulesets /// /// Retrieves the number of mania keys required to play the beatmap. /// - /// - int GetKeyCount(IBeatmapInfo beatmapInfo) => 0; + int GetKeyCount(IBeatmapInfo beatmapInfo, IReadOnlyList? mods = null) => 0; ILegacyScoreSimulator CreateLegacyScoreSimulator(); } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 01e58d4ab2..2752beb645 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -28,6 +28,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osuTK; using osuTK.Graphics; @@ -75,6 +76,9 @@ namespace osu.Game.Screens.Select.Carousel [Resolved] private IBindable ruleset { get; set; } = null!; + [Resolved] + private IBindable>? mods { get; set; } = null!; + private IBindable starDifficultyBindable = null!; private CancellationTokenSource? starDifficultyCancellationSource; @@ -185,6 +189,7 @@ namespace osu.Game.Screens.Select.Carousel base.LoadComplete(); ruleset.BindValueChanged(_ => updateKeyCount()); + mods?.BindValueChanged(_ => updateKeyCount()); } protected override void Selected() @@ -255,7 +260,7 @@ namespace osu.Game.Screens.Select.Carousel ILegacyRuleset legacyRuleset = (ILegacyRuleset)ruleset.Value.CreateInstance(); keyCountText.Alpha = 1; - keyCountText.Text = $"[{legacyRuleset.GetKeyCount(beatmapInfo)}K]"; + keyCountText.Text = $"[{legacyRuleset.GetKeyCount(beatmapInfo, mods?.Value)}K]"; } else keyCountText.Alpha = 0; diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 1aba977f44..cb820f4da9 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -199,7 +199,7 @@ namespace osu.Game.Screens.Select.Details // For the time being, the key count is static no matter what, because: // a) The method doesn't have knowledge of the active keymods. Doing so may require considerations for filtering. // b) Using the difficulty adjustment mod to adjust OD doesn't have an effect on conversion. - int keyCount = baseDifficulty == null ? 0 : legacyRuleset.GetKeyCount(BeatmapInfo); + int keyCount = baseDifficulty == null ? 0 : legacyRuleset.GetKeyCount(BeatmapInfo, mods.Value); FirstValue.Title = BeatmapsetsStrings.ShowStatsCsMania; FirstValue.Value = (keyCount, keyCount); From ce21235db495bb10e0f49763b7351f4f70fa51b3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 28 Mar 2024 22:47:43 +0900 Subject: [PATCH 3581/4092] Remove unused OriginalTargetColumns --- osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs | 6 ------ osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs | 6 +----- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs index 28cdf8907e..8222e5477d 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs @@ -22,11 +22,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps /// public int TotalColumns => Stages.Sum(g => g.Columns); - /// - /// The total number of columns that were present in this before any user adjustments. - /// - public readonly int OriginalTotalColumns; - /// /// Creates a new . /// @@ -35,7 +30,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps public ManiaBeatmap(StageDefinition defaultStage, int? originalTotalColumns = null) { Stages.Add(defaultStage); - OriginalTotalColumns = originalTotalColumns ?? defaultStage.Columns; } public override IEnumerable GetStatistics() diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 8b339239a0..bed04a882f 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -46,8 +46,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps /// public readonly bool IsForCurrentRuleset; - private readonly int originalTargetColumns; - // Internal for testing purposes internal readonly LegacyRandom Random; @@ -71,8 +69,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps Dual = true; } - originalTargetColumns = TargetColumns; - static int getColumnCount(LegacyBeatmapConversionDifficultyInfo difficulty) { double roundedCircleSize = Math.Round(difficulty.CircleSize); @@ -122,7 +118,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps protected override Beatmap CreateBeatmap() { - ManiaBeatmap beatmap = new ManiaBeatmap(new StageDefinition(TargetColumns), originalTargetColumns); + ManiaBeatmap beatmap = new ManiaBeatmap(new StageDefinition(TargetColumns)); if (Dual) beatmap.Stages.Add(new StageDefinition(TargetColumns)); From c08a4898b27347201d63200d162718ddaf874068 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 28 Mar 2024 22:58:39 +0900 Subject: [PATCH 3582/4092] Refactor score simulator to use GetColumnCount() --- .../Difficulty/ManiaLegacyScoreSimulator.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaLegacyScoreSimulator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaLegacyScoreSimulator.cs index d9fd96ac6a..8a1b127265 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaLegacyScoreSimulator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaLegacyScoreSimulator.cs @@ -51,13 +51,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty return multiplier; // Apply key mod multipliers. - int originalColumns = ManiaBeatmapConverter.GetColumnCount(difficulty); - int actualColumns = originalColumns; - - actualColumns = mods.OfType().SingleOrDefault()?.KeyCount ?? actualColumns; - if (mods.Any(m => m is ManiaModDualStages)) - actualColumns *= 2; + int actualColumns = ManiaBeatmapConverter.GetColumnCount(difficulty, mods); if (actualColumns > originalColumns) multiplier *= 0.9; From 9fd6449fd8b7c8b7a9019d1d3a25cb46a5b5562c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 28 Mar 2024 23:02:25 +0900 Subject: [PATCH 3583/4092] Add mods to FilterCriteria, pass to ruleset method --- osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs | 5 ++--- .../NonVisual/Filtering/FilterMatchingTest.cs | 2 +- .../NonVisual/Filtering/FilterQueryParserTest.cs | 2 +- osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs | 3 ++- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 2 +- .../Select/Carousel/DrawableCarouselBeatmap.cs | 6 +++--- osu.Game/Screens/Select/FilterControl.cs | 13 ++++++++++--- osu.Game/Screens/Select/FilterCriteria.cs | 2 ++ 8 files changed, 22 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs b/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs index 930ca217cd..07ed3ebd63 100644 --- a/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs +++ b/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs @@ -4,7 +4,6 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Filter; using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Scoring.Legacy; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Filter; @@ -14,9 +13,9 @@ namespace osu.Game.Rulesets.Mania { private FilterCriteria.OptionalRange keys; - public bool Matches(BeatmapInfo beatmapInfo) + public bool Matches(BeatmapInfo beatmapInfo, FilterCriteria criteria) { - return !keys.HasFilter || keys.IsInRange(ManiaBeatmapConverter.GetColumnCount(LegacyBeatmapConversionDifficultyInfo.FromBeatmapInfo(beatmapInfo))); + return !keys.HasFilter || keys.IsInRange(ManiaBeatmapConverter.GetColumnCount(beatmapInfo, criteria.Mods)); } public bool TryParseCustomKeywordCriteria(string key, Operator op, string value) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index c7a32ebbc4..78d8eabba7 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -309,7 +309,7 @@ namespace osu.Game.Tests.NonVisual.Filtering match = shouldMatch; } - public bool Matches(BeatmapInfo beatmapInfo) => match; + public bool Matches(BeatmapInfo beatmapInfo, FilterCriteria criteria) => match; public bool TryParseCustomKeywordCriteria(string key, Operator op, string value) => false; } } diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index ea14412f55..b0ceed45b9 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -502,7 +502,7 @@ namespace osu.Game.Tests.NonVisual.Filtering { public string? CustomValue { get; set; } - public bool Matches(BeatmapInfo beatmapInfo) => true; + public bool Matches(BeatmapInfo beatmapInfo, FilterCriteria criteria) => true; public bool TryParseCustomKeywordCriteria(string key, Operator op, string value) { diff --git a/osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs b/osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs index dd2ad2cbfa..f926b04db4 100644 --- a/osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs +++ b/osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs @@ -18,11 +18,12 @@ namespace osu.Game.Rulesets.Filter /// in addition to the ones mandated by song select. /// /// The beatmap to test the criteria against. + /// The filter criteria. /// /// true if the beatmap matches the ruleset-specific custom filtering criteria, /// false otherwise. /// - bool Matches(BeatmapInfo beatmapInfo); + bool Matches(BeatmapInfo beatmapInfo, FilterCriteria criteria); /// /// Attempts to parse a single custom keyword criterion, given by the user via the song select search box. diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 43461a48bb..8f38ae710c 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -85,7 +85,7 @@ namespace osu.Game.Screens.Select.Carousel match &= criteria.CollectionBeatmapMD5Hashes?.Contains(BeatmapInfo.MD5Hash) ?? true; if (match && criteria.RulesetCriteria != null) - match &= criteria.RulesetCriteria.Matches(BeatmapInfo); + match &= criteria.RulesetCriteria.Matches(BeatmapInfo, criteria); return match; } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 2752beb645..f725d98342 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Select.Carousel private IBindable ruleset { get; set; } = null!; [Resolved] - private IBindable>? mods { get; set; } = null!; + private IBindable> mods { get; set; } = null!; private IBindable starDifficultyBindable = null!; private CancellationTokenSource? starDifficultyCancellationSource; @@ -189,7 +189,7 @@ namespace osu.Game.Screens.Select.Carousel base.LoadComplete(); ruleset.BindValueChanged(_ => updateKeyCount()); - mods?.BindValueChanged(_ => updateKeyCount()); + mods.BindValueChanged(_ => updateKeyCount()); } protected override void Selected() @@ -260,7 +260,7 @@ namespace osu.Game.Screens.Select.Carousel ILegacyRuleset legacyRuleset = (ILegacyRuleset)ruleset.Value.CreateInstance(); keyCountText.Alpha = 1; - keyCountText.Text = $"[{legacyRuleset.GetKeyCount(beatmapInfo, mods?.Value)}K]"; + keyCountText.Text = $"[{legacyRuleset.GetKeyCount(beatmapInfo, mods.Value)}K]"; } else keyCountText.Alpha = 0; diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 17297c9ebf..b19a7699c5 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using System.Collections.Generic; using System.Collections.Immutable; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -22,6 +23,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Screens.Select.Filter; using osuTK; using osuTK.Graphics; @@ -65,6 +67,7 @@ namespace osu.Game.Screens.Select Sort = sortMode.Value, AllowConvertedBeatmaps = showConverted.Value, Ruleset = ruleset.Value, + Mods = mods.Value, CollectionBeatmapMD5Hashes = collectionDropdown.Current.Value?.Collection?.PerformRead(c => c.BeatmapMD5Hashes).ToImmutableHashSet() }; @@ -84,7 +87,7 @@ namespace osu.Game.Screens.Select base.ReceivePositionalInputAt(screenSpacePos) || sortTabs.ReceivePositionalInputAt(screenSpacePos); [BackgroundDependencyLoader(permitNulls: true)] - private void load(OsuColour colours, IBindable parentRuleset, OsuConfigManager config) + private void load(OsuColour colours, OsuConfigManager config) { sortMode = config.GetBindable(OsuSetting.SongSelectSortingMode); groupMode = config.GetBindable(OsuSetting.SongSelectGroupingMode); @@ -214,8 +217,8 @@ namespace osu.Game.Screens.Select config.BindWith(OsuSetting.DisplayStarsMaximum, maximumStars); maximumStars.ValueChanged += _ => updateCriteria(); - ruleset.BindTo(parentRuleset); ruleset.BindValueChanged(_ => updateCriteria()); + mods.BindValueChanged(_ => updateCriteria()); groupMode.BindValueChanged(_ => updateCriteria()); sortMode.BindValueChanged(_ => updateCriteria()); @@ -239,7 +242,11 @@ namespace osu.Game.Screens.Select searchTextBox.HoldFocus = true; } - private readonly IBindable ruleset = new Bindable(); + [Resolved] + private IBindable ruleset { get; set; } = null!; + + [Resolved] + private IBindable> mods { get; set; } = null!; private readonly Bindable showConverted = new Bindable(); private readonly Bindable minimumStars = new BindableDouble(); diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 01b0e9b7d9..190efd0fb0 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -10,6 +10,7 @@ using osu.Game.Beatmaps; using osu.Game.Collections; using osu.Game.Rulesets; using osu.Game.Rulesets.Filter; +using osu.Game.Rulesets.Mods; using osu.Game.Screens.Select.Filter; namespace osu.Game.Screens.Select @@ -50,6 +51,7 @@ namespace osu.Game.Screens.Select public OptionalTextFilter[] SearchTerms = Array.Empty(); public RulesetInfo? Ruleset; + public IReadOnlyList? Mods; public bool AllowConvertedBeatmaps; private string searchText = string.Empty; From 6e746a0fa053eb5c5c0d1b8cde1d0a1e1ba2c737 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 28 Mar 2024 23:56:46 +0900 Subject: [PATCH 3584/4092] Fix carousel reoder on initial enter --- osu.Game/Screens/Select/FilterControl.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index b19a7699c5..7b8b5393bd 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -218,7 +218,13 @@ namespace osu.Game.Screens.Select maximumStars.ValueChanged += _ => updateCriteria(); ruleset.BindValueChanged(_ => updateCriteria()); - mods.BindValueChanged(_ => updateCriteria()); + mods.BindValueChanged(_ => + { + // Mods are updated once by the mod select overlay when song select is entered, regardless of if there are any mods. + // Updating the criteria here so early triggers a re-ordering of panels on song select, via... some mechanism. + // Todo: Investigate/fix the above and remove this schedule. + Scheduler.AddOnce(updateCriteria); + }); groupMode.BindValueChanged(_ => updateCriteria()); sortMode.BindValueChanged(_ => updateCriteria()); From cbbb46cad87d40b3416e23c60f8dc5bf391004c9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 29 Mar 2024 00:25:03 +0900 Subject: [PATCH 3585/4092] Update action versions in diffcalc workflow --- .github/workflows/diffcalc.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/diffcalc.yml b/.github/workflows/diffcalc.yml index 2ed176fe8d..7fd0f798cd 100644 --- a/.github/workflows/diffcalc.yml +++ b/.github/workflows/diffcalc.yml @@ -126,7 +126,7 @@ jobs: if: ${{ github.event_name == 'issue_comment' && github.event.issue.pull_request }} steps: - name: Create comment - uses: thollander/actions-comment-pull-request@363c6f6eae92cc5c3a66e95ba016fc771bb38943 # v2.4.2 + uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0 with: comment_tag: ${{ env.EXECUTION_ID }} message: | @@ -253,7 +253,7 @@ jobs: - name: Restore cache id: restore-cache - uses: maxnowack/local-cache@038cc090b52e4f205fbc468bf5b0756df6f68775 # v1 + uses: maxnowack/local-cache@720e69c948191660a90aa1cf6a42fc4d2dacdf30 # v2 with: path: ${{ steps.query.outputs.DATA_NAME }}.tar.bz2 key: ${{ steps.query.outputs.DATA_NAME }} @@ -284,7 +284,7 @@ jobs: - name: Restore cache id: restore-cache - uses: maxnowack/local-cache@038cc090b52e4f205fbc468bf5b0756df6f68775 # v1 + uses: maxnowack/local-cache@720e69c948191660a90aa1cf6a42fc4d2dacdf30 # v2 with: path: ${{ steps.query.outputs.DATA_NAME }}.tar.bz2 key: ${{ steps.query.outputs.DATA_NAME }} @@ -358,7 +358,7 @@ jobs: steps: - name: Update comment on success if: ${{ needs.generator.result == 'success' }} - uses: thollander/actions-comment-pull-request@363c6f6eae92cc5c3a66e95ba016fc771bb38943 # v2.4.2 + uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0 with: comment_tag: ${{ env.EXECUTION_ID }} mode: upsert @@ -369,7 +369,7 @@ jobs: - name: Update comment on failure if: ${{ needs.generator.result == 'failure' }} - uses: thollander/actions-comment-pull-request@363c6f6eae92cc5c3a66e95ba016fc771bb38943 # v2.4.2 + uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0 with: comment_tag: ${{ env.EXECUTION_ID }} mode: upsert @@ -379,7 +379,7 @@ jobs: - name: Update comment on cancellation if: ${{ needs.generator.result == 'cancelled' }} - uses: thollander/actions-comment-pull-request@363c6f6eae92cc5c3a66e95ba016fc771bb38943 # v2.4.2 + uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0 with: comment_tag: ${{ env.EXECUTION_ID }} mode: delete From c51a2e169d374b1cb820e2975def1cb2bbd902f7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Mar 2024 12:19:06 +0800 Subject: [PATCH 3586/4092] Add test coverage of crash scenario --- osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs b/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs index e603f72bb8..24c9d1294f 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs @@ -74,6 +74,10 @@ namespace osu.Game.Tests.Visual.Menus }); AddStep("enter code", () => loginOverlay.ChildrenOfType().First().Text = "88800088"); assertAPIState(APIState.Online); + + AddStep("set failing", () => { dummyAPI.SetState(APIState.Failing); }); + AddStep("return to online", () => { dummyAPI.SetState(APIState.Online); }); + AddStep("clear handler", () => dummyAPI.HandleRequest = null); } From fef8afb833b7ddfd42b97260faff7593da514868 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Mar 2024 12:03:05 +0800 Subject: [PATCH 3587/4092] Fix double binding causing game crash after API enters failing state See https://sentry.ppy.sh/organizations/ppy/issues/33406/?alert_rule_id=4&alert_timestamp=1711655107332&alert_type=email&environment=production&project=2&referrer=alert_email --- osu.Game/Overlays/Login/LoginPanel.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Login/LoginPanel.cs b/osu.Game/Overlays/Login/LoginPanel.cs index 25bf612bc3..d5c7ed29b8 100644 --- a/osu.Game/Overlays/Login/LoginPanel.cs +++ b/osu.Game/Overlays/Login/LoginPanel.cs @@ -30,7 +30,7 @@ namespace osu.Game.Overlays.Login [Resolved] private OsuColour colours { get; set; } = null!; - private UserDropdown dropdown = null!; + private UserDropdown? dropdown; /// /// Called to request a hide of a parent displaying this container. @@ -68,6 +68,14 @@ namespace osu.Game.Overlays.Login apiState.BindValueChanged(onlineStateChanged, true); } + protected override void LoadComplete() + { + base.LoadComplete(); + + userStatus.BindTo(api.LocalUser.Value.Status); + userStatus.BindValueChanged(e => updateDropdownCurrent(e.NewValue), true); + } + private void onlineStateChanged(ValueChangedEvent state) => Schedule(() => { form = null; @@ -144,9 +152,6 @@ namespace osu.Game.Overlays.Login }, }; - userStatus.BindTo(api.LocalUser.Value.Status); - userStatus.BindValueChanged(e => updateDropdownCurrent(e.NewValue), true); - dropdown.Current.BindValueChanged(action => { switch (action.NewValue) @@ -171,6 +176,7 @@ namespace osu.Game.Overlays.Login break; } }, true); + break; } @@ -180,6 +186,9 @@ namespace osu.Game.Overlays.Login private void updateDropdownCurrent(UserStatus? status) { + if (dropdown == null) + return; + switch (status) { case UserStatus.Online: From d9cf5b5440ff3146e94d1c5da5c3c7c11bd53dd8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Mar 2024 15:44:01 +0800 Subject: [PATCH 3588/4092] Fix bindable not being correctly re-bound across local user changes --- .../Visual/Menus/TestSceneLoginOverlay.cs | 12 +++++++++ osu.Game/Overlays/Login/LoginPanel.cs | 25 +++++++++++-------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs b/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs index 24c9d1294f..460d7814e0 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs @@ -15,6 +15,7 @@ using osu.Game.Online.API.Requests; using osu.Game.Overlays; using osu.Game.Overlays.Login; using osu.Game.Overlays.Settings; +using osu.Game.Users; using osu.Game.Users.Drawables; using osuTK.Input; @@ -72,13 +73,24 @@ namespace osu.Game.Tests.Visual.Menus return false; }); + AddStep("enter code", () => loginOverlay.ChildrenOfType().First().Text = "88800088"); assertAPIState(APIState.Online); + assertDropdownState(UserAction.Online); AddStep("set failing", () => { dummyAPI.SetState(APIState.Failing); }); AddStep("return to online", () => { dummyAPI.SetState(APIState.Online); }); AddStep("clear handler", () => dummyAPI.HandleRequest = null); + + assertDropdownState(UserAction.Online); + AddStep("change user state", () => dummyAPI.LocalUser.Value.Status.Value = UserStatus.DoNotDisturb); + assertDropdownState(UserAction.DoNotDisturb); + } + + private void assertDropdownState(UserAction state) + { + AddAssert($"dropdown state is {state}", () => loginOverlay.ChildrenOfType().First().Current.Value, () => Is.EqualTo(state)); } private void assertAPIState(APIState expected) => diff --git a/osu.Game/Overlays/Login/LoginPanel.cs b/osu.Game/Overlays/Login/LoginPanel.cs index d5c7ed29b8..a8adf4ce8c 100644 --- a/osu.Game/Overlays/Login/LoginPanel.cs +++ b/osu.Game/Overlays/Login/LoginPanel.cs @@ -15,6 +15,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Settings; using osu.Game.Users; using osuTK; @@ -37,8 +38,10 @@ namespace osu.Game.Overlays.Login /// public Action? RequestHide; + private IBindable user = null!; + private readonly Bindable status = new Bindable(); + private readonly IBindable apiState = new Bindable(); - private readonly Bindable userStatus = new Bindable(); [Resolved] private IAPIProvider api { get; set; } = null!; @@ -61,19 +64,21 @@ namespace osu.Game.Overlays.Login AutoSizeAxes = Axes.Y; } - [BackgroundDependencyLoader] - private void load() - { - apiState.BindTo(api.State); - apiState.BindValueChanged(onlineStateChanged, true); - } - protected override void LoadComplete() { base.LoadComplete(); - userStatus.BindTo(api.LocalUser.Value.Status); - userStatus.BindValueChanged(e => updateDropdownCurrent(e.NewValue), true); + apiState.BindTo(api.State); + apiState.BindValueChanged(onlineStateChanged, true); + + user = api.LocalUser.GetBoundCopy(); + user.BindValueChanged(u => + { + status.UnbindBindings(); + status.BindTo(u.NewValue.Status); + }, true); + + status.BindValueChanged(e => updateDropdownCurrent(e.NewValue), true); } private void onlineStateChanged(ValueChangedEvent state) => Schedule(() => From 233b9eb1723fb44a26c1a65c1d583e3e6f87f7b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Mar 2024 17:11:55 +0800 Subject: [PATCH 3589/4092] Avoid reporting an import as successful when all beatmaps failed to import --- osu.Game/Beatmaps/BeatmapImporter.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 5ff3ab64b2..9ca0aafddd 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -434,6 +434,9 @@ namespace osu.Game.Beatmaps } } + if (!beatmaps.Any()) + throw new ArgumentException($"No valid beatmap files found in the beatmap archive."); + return beatmaps; } } From df4a28db915653f79603ef78ea44af48d781391e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Mar 2024 17:32:20 +0800 Subject: [PATCH 3590/4092] Fix failing test due to missing ruleset store --- osu.Game.Tests/Database/LegacyBeatmapImporterTest.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Database/LegacyBeatmapImporterTest.cs b/osu.Game.Tests/Database/LegacyBeatmapImporterTest.cs index 5f722e381c..016928c6d6 100644 --- a/osu.Game.Tests/Database/LegacyBeatmapImporterTest.cs +++ b/osu.Game.Tests/Database/LegacyBeatmapImporterTest.cs @@ -12,6 +12,7 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.IO; +using osu.Game.Rulesets; using osu.Game.Tests.Resources; namespace osu.Game.Tests.Database @@ -77,6 +78,7 @@ namespace osu.Game.Tests.Database { using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) using (var tmpStorage = new TemporaryNativeStorage("stable-songs-folder")) + using (new RealmRulesetStore(realm, storage)) { var stableStorage = new StableStorage(tmpStorage.GetFullPath(""), host); var songsStorage = stableStorage.GetStorageForDirectory(StableStorage.STABLE_DEFAULT_SONGS_PATH); From 2d3b273974de4cb85dea652f3915a37f7ee0451e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 29 Mar 2024 10:36:17 +0100 Subject: [PATCH 3591/4092] Remove redundant string interpolation --- osu.Game/Beatmaps/BeatmapImporter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 9ca0aafddd..2137f33e77 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -435,7 +435,7 @@ namespace osu.Game.Beatmaps } if (!beatmaps.Any()) - throw new ArgumentException($"No valid beatmap files found in the beatmap archive."); + throw new ArgumentException("No valid beatmap files found in the beatmap archive."); return beatmaps; } From e06df34a1c7f57ffec818be87264c4e82b61e508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 29 Mar 2024 11:16:31 +0100 Subject: [PATCH 3592/4092] Apply partial fade on pp display on results screen when score will not give pp --- .../TestSceneExpandedPanelMiddleContent.cs | 37 +++++++++++++++++++ osu.Game/Localisation/ResultsScreenStrings.cs | 24 ++++++++++++ .../Statistics/PerformanceStatistic.cs | 32 ++++++++++++++-- 3 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 osu.Game/Localisation/ResultsScreenStrings.cs diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs index d97946a1d5..9f7726313a 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs @@ -14,13 +14,16 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; +using osu.Game.Localisation; using osu.Game.Models; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Scoring; using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking.Expanded; +using osu.Game.Screens.Ranking.Expanded.Statistics; using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Resources; using osuTK; @@ -67,6 +70,40 @@ namespace osu.Game.Tests.Visual.Ranking AddAssert("play time displayed", () => this.ChildrenOfType().Any()); } + [Test] + public void TestPPShownAsProvisionalWhenBeatmapHasNoLeaderboard() + { + AddStep("show example score", () => + { + var beatmap = createTestBeatmap(new RealmUser()); + beatmap.Status = BeatmapOnlineStatus.Graveyard; + showPanel(TestResources.CreateTestScoreInfo(beatmap)); + }); + + AddAssert("pp display faded out", () => + { + var ppDisplay = this.ChildrenOfType().Single(); + return ppDisplay.Alpha == 0.5 && ppDisplay.TooltipText == ResultsScreenStrings.NoPPForUnrankedBeatmaps; + }); + } + + [Test] + public void TestPPShownAsProvisionalWhenUnrankedModsArePresent() + { + AddStep("show example score", () => + { + var score = TestResources.CreateTestScoreInfo(createTestBeatmap(new RealmUser())); + score.Mods = score.Mods.Append(new OsuModDifficultyAdjust()).ToArray(); + showPanel(score); + }); + + AddAssert("pp display faded out", () => + { + var ppDisplay = this.ChildrenOfType().Single(); + return ppDisplay.Alpha == 0.5 && ppDisplay.TooltipText == ResultsScreenStrings.NoPPForUnrankedMods; + }); + } + [Test] public void TestWithDefaultDate() { diff --git a/osu.Game/Localisation/ResultsScreenStrings.cs b/osu.Game/Localisation/ResultsScreenStrings.cs new file mode 100644 index 0000000000..54e7717af9 --- /dev/null +++ b/osu.Game/Localisation/ResultsScreenStrings.cs @@ -0,0 +1,24 @@ +// 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.Localisation; + +namespace osu.Game.Localisation +{ + public static class ResultsScreenStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.ResultsScreen"; + + /// + /// "Performance points are not granted for this score because the beatmap is not ranked." + /// + public static LocalisableString NoPPForUnrankedBeatmaps => new TranslatableString(getKey(@"no_pp_for_unranked_beatmaps"), @"Performance points are not granted for this score because the beatmap is not ranked."); + + /// + /// "Performance points are not granted for this score because of unranked mods." + /// + public static LocalisableString NoPPForUnrankedMods => new TranslatableString(getKey(@"no_pp_for_unranked_mods"), @"Performance points are not granted for this score because of unranked mods."); + + private static string getKey(string key) => $@"{prefix}:{key}"; + } +} diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs index 22c1e26d43..0a9c68eafc 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs @@ -4,20 +4,26 @@ #nullable disable using System; +using System.Linq; using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Resources.Localisation.Web; using osu.Game.Scoring; +using osu.Game.Localisation; namespace osu.Game.Screens.Ranking.Expanded.Statistics { - public partial class PerformanceStatistic : StatisticDisplay + public partial class PerformanceStatistic : StatisticDisplay, IHasTooltip { + public LocalisableString TooltipText { get; private set; } + private readonly ScoreInfo score; private readonly Bindable performance = new Bindable(); @@ -37,7 +43,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics { if (score.PP.HasValue) { - setPerformanceValue(score.PP.Value); + setPerformanceValue(score, score.PP.Value); } else { @@ -52,15 +58,33 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics var result = await performanceCalculator.CalculateAsync(score, attributes.Value.Attributes, cancellationToken ?? default).ConfigureAwait(false); - Schedule(() => setPerformanceValue(result.Total)); + Schedule(() => setPerformanceValue(score, result.Total)); }, cancellationToken ?? default); } } - private void setPerformanceValue(double? pp) + private void setPerformanceValue(ScoreInfo scoreInfo, double? pp) { if (pp.HasValue) + { performance.Value = (int)Math.Round(pp.Value, MidpointRounding.AwayFromZero); + + if (!scoreInfo.BeatmapInfo!.Status.GrantsPerformancePoints()) + { + Alpha = 0.5f; + TooltipText = ResultsScreenStrings.NoPPForUnrankedBeatmaps; + } + else if (scoreInfo.Mods.Any(m => !m.Ranked)) + { + Alpha = 0.5f; + TooltipText = ResultsScreenStrings.NoPPForUnrankedMods; + } + else + { + Alpha = 1f; + TooltipText = default; + } + } } public override void Appear() From c21805589eb2014bd07e6386b9398734e9a99dcb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Mar 2024 22:40:04 +0800 Subject: [PATCH 3593/4092] Fix taiko mascot size not matching stable --- osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs b/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs index 7b1e31112e..e863c4c2e4 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs @@ -27,7 +27,8 @@ namespace osu.Game.Rulesets.Taiko.UI InternalChild = textureAnimation = createTextureAnimation(state).With(animation => { animation.Origin = animation.Anchor = Anchor.BottomLeft; - animation.Scale = new Vector2(0.51f); // close enough to stable + // matches stable (https://github.com/peppy/osu-stable-reference/blob/054d0380c19aa5972be176d9d242ceb0e1630ae6/osu!/GameModes/Play/Rulesets/Taiko/TaikoMascot.cs#L34) + animation.Scale = new Vector2(0.6f); }); RelativeSizeAxes = Axes.Both; From 51f79c33e1c93ecb4cbf71e5de820941a0fa622e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 29 Mar 2024 23:33:04 +0300 Subject: [PATCH 3594/4092] Fix URL pointing to non-existent commit --- osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs b/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs index e863c4c2e4..90f7782aba 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Taiko.UI InternalChild = textureAnimation = createTextureAnimation(state).With(animation => { animation.Origin = animation.Anchor = Anchor.BottomLeft; - // matches stable (https://github.com/peppy/osu-stable-reference/blob/054d0380c19aa5972be176d9d242ceb0e1630ae6/osu!/GameModes/Play/Rulesets/Taiko/TaikoMascot.cs#L34) + // matches stable (https://github.com/peppy/osu-stable-reference/blob/e53980dd76857ee899f66ce519ba1597e7874f28/osu!/GameModes/Play/Rulesets/Taiko/TaikoMascot.cs#L34) animation.Scale = new Vector2(0.6f); }); From 8d6358a138605828c5260b4efca907049ed73e92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=C3=AAn=20Minh=20H=E1=BB=93?= Date: Sat, 30 Mar 2024 16:02:31 +0700 Subject: [PATCH 3595/4092] Fix editor rotation allowing spinner only bug --- osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs index 1998e02a5c..d48bc6a90b 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.Edit { var quad = GeometryUtils.GetSurroundingQuad(selectedMovableObjects); CanRotateSelectionOrigin.Value = quad.Width > 0 || quad.Height > 0; - CanRotatePlayfieldOrigin.Value = selectedItems.Any(); + CanRotatePlayfieldOrigin.Value = selectedMovableObjects.Any(); } private OsuHitObject[]? objectsInRotation; From 5d497ba4a8ada3ae5ad733ed6d856328b39a9576 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=C3=AAn=20Minh=20H=E1=BB=93?= Date: Sat, 30 Mar 2024 16:04:22 +0700 Subject: [PATCH 3596/4092] Simplify TooltipText for EditorRadioButton --- .../Edit/PreciseRotationPopover.cs | 8 +++++++- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 8 +++++++- .../Components/RadioButtons/EditorRadioButton.cs | 2 +- .../Edit/Components/RadioButtons/RadioButton.cs | 13 +++---------- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs index 2cf6799279..6c29184be4 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs @@ -68,7 +68,13 @@ namespace osu.Game.Rulesets.Osu.Edit } } }; - selectionCentreButton.TooltipTextWhenDisabled = "We can't rotate a circle around itself! Can we?"; + selectionCentreButton.Selected.DisabledChanged += (isDisabled) => + { + if (isDisabled) + selectionCentreButton.TooltipText = "We can't rotate a circle around itself! Can we?"; + else + selectionCentreButton.TooltipText = string.Empty; + }; } protected override void LoadComplete() diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index bc8de7f4b2..09bac7a791 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -214,7 +214,13 @@ namespace osu.Game.Rulesets.Edit foreach (var item in toolboxCollection.Items) { - item.TooltipTextWhenDisabled = "Add at least one timing point first!"; + item.Selected.DisabledChanged += (isDisabled) => + { + if (isDisabled) + item.TooltipText = "Add at least one timing point first!"; + else + item.TooltipText = string.Empty; + }; } TernaryStates = CreateTernaryButtons().ToArray(); diff --git a/osu.Game/Screens/Edit/Components/RadioButtons/EditorRadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/EditorRadioButton.cs index 9d1f87e1e0..29bb24eb43 100644 --- a/osu.Game/Screens/Edit/Components/RadioButtons/EditorRadioButton.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/EditorRadioButton.cs @@ -94,6 +94,6 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons X = 40f }; - public LocalisableString TooltipText => Enabled.Value ? Button.TooltipTextWhenEnabled : Button.TooltipTextWhenDisabled; + public LocalisableString TooltipText => Button.TooltipText; } } diff --git a/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs index 2d1416c9c6..f49fc6f6ab 100644 --- a/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs @@ -16,16 +16,6 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons /// public readonly BindableBool Selected; - /// - /// Tooltip text that will be shown on hover if button is enabled. - /// - public LocalisableString TooltipTextWhenEnabled { get; set; } = string.Empty; - - /// - /// Tooltip text that will be shown on hover if button is disabled. - /// - public LocalisableString TooltipTextWhenDisabled { get; set; } = string.Empty; - /// /// The item related to this button. /// @@ -62,5 +52,8 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons /// Deselects this . /// public void Deselect() => Selected.Value = false; + + // Tooltip text that will be shown when hovered over + public LocalisableString TooltipText { get; set; } = string.Empty; } } From 6f782266b51b717e996cc258b05da8fb737533d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=C3=AAn=20Minh=20H=E1=BB=93?= Date: Sat, 30 Mar 2024 17:03:40 +0700 Subject: [PATCH 3597/4092] Fix inspection --- osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs | 7 ++----- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 7 ++----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs index 6c29184be4..70441b33dd 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs @@ -68,12 +68,9 @@ namespace osu.Game.Rulesets.Osu.Edit } } }; - selectionCentreButton.Selected.DisabledChanged += (isDisabled) => + selectionCentreButton.Selected.DisabledChanged += isDisabled => { - if (isDisabled) - selectionCentreButton.TooltipText = "We can't rotate a circle around itself! Can we?"; - else - selectionCentreButton.TooltipText = string.Empty; + selectionCentreButton.TooltipText = isDisabled ? "We can't rotate a circle around itself! Can we?" : string.Empty; }; } diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 09bac7a791..4d92a08bed 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -214,12 +214,9 @@ namespace osu.Game.Rulesets.Edit foreach (var item in toolboxCollection.Items) { - item.Selected.DisabledChanged += (isDisabled) => + item.Selected.DisabledChanged += isDisabled => { - if (isDisabled) - item.TooltipText = "Add at least one timing point first!"; - else - item.TooltipText = string.Empty; + item.TooltipText = isDisabled ? "Add at least one timing point first!" : string.Empty; }; } From b445e27ad6bfae93244b89acc1544aef01978bdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=C3=AAn=20Minh=20H=E1=BB=93?= Date: Sat, 30 Mar 2024 17:54:27 +0700 Subject: [PATCH 3598/4092] Aggregate two CanRotate bindable for enabling the Rotate button --- osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs index 19590e9b6e..3e2cbe9d60 100644 --- a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs @@ -51,9 +51,17 @@ namespace osu.Game.Rulesets.Osu.Edit { base.LoadComplete(); + // aggregate two values into canRotate + RotationHandler.CanRotatePlayfieldOrigin.BindValueChanged(_ => updateCanRotateAggregate()); + RotationHandler.CanRotateSelectionOrigin.BindValueChanged(_ => updateCanRotateAggregate()); + + void updateCanRotateAggregate() + { + canRotate.Value = RotationHandler.CanRotatePlayfieldOrigin.Value || RotationHandler.CanRotateSelectionOrigin.Value; + } + // bindings to `Enabled` on the buttons are decoupled on purpose // due to the weird `OsuButton` behaviour of resetting `Enabled` to `false` when `Action` is set. - canRotate.BindTo(RotationHandler.CanRotatePlayfieldOrigin); canRotate.BindValueChanged(_ => rotateButton.Enabled.Value = canRotate.Value, true); } From 54472e6452c61671d487f59d6e61af89daaa6d59 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 31 Mar 2024 01:20:27 +0300 Subject: [PATCH 3599/4092] Decouple GlowingDrawable from GlowingSpriteText --- osu.Game/Graphics/Sprites/GlowingDrawable.cs | 41 +++++++++++++++++++ .../Graphics/Sprites/GlowingSpriteText.cs | 39 ++++-------------- 2 files changed, 50 insertions(+), 30 deletions(-) create mode 100644 osu.Game/Graphics/Sprites/GlowingDrawable.cs diff --git a/osu.Game/Graphics/Sprites/GlowingDrawable.cs b/osu.Game/Graphics/Sprites/GlowingDrawable.cs new file mode 100644 index 0000000000..10085ad38b --- /dev/null +++ b/osu.Game/Graphics/Sprites/GlowingDrawable.cs @@ -0,0 +1,41 @@ +// 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.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Utils; +using osuTK; + +namespace osu.Game.Graphics.Sprites +{ + public abstract partial class GlowingDrawable : BufferedContainer + { + // Inflate draw quad to prevent glow from trimming at the edges. + // Padding won't suffice since it will affect drawable position in cases when it's not centered. + protected override Quad ComputeScreenSpaceDrawQuad() + => base.ComputeScreenSpaceDrawQuad().AABBFloat.Inflate(new Vector2(Blur.KernelSize(BlurSigma.X), Blur.KernelSize(BlurSigma.Y))); + + public ColourInfo GlowColour + { + get => EffectColour; + set + { + EffectColour = value; + BackgroundColour = value.MultiplyAlpha(0f); + } + } + + protected GlowingDrawable() + : base(cachedFrameBuffer: true) + { + AutoSizeAxes = Axes.Both; + RedrawOnScale = false; + DrawOriginal = true; + Child = CreateDrawable(); + } + + protected abstract Drawable CreateDrawable(); + } +} diff --git a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs index 669c5da01e..3ac13bf862 100644 --- a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs +++ b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs @@ -4,24 +4,17 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; -using osu.Framework.Utils; using osuTK; namespace osu.Game.Graphics.Sprites { - public partial class GlowingSpriteText : BufferedContainer, IHasText + public partial class GlowingSpriteText : GlowingDrawable, IHasText { private const float blur_sigma = 3f; - // Inflate draw quad to prevent glow from trimming at the edges. - // Padding won't suffice since it will affect text position in cases when it's not centered. - protected override Quad ComputeScreenSpaceDrawQuad() => base.ComputeScreenSpaceDrawQuad().AABBFloat.Inflate(Blur.KernelSize(blur_sigma)); - - private readonly OsuSpriteText text; + private OsuSpriteText text = null!; public LocalisableString Text { @@ -47,16 +40,6 @@ namespace osu.Game.Graphics.Sprites set => text.Colour = value; } - public ColourInfo GlowColour - { - get => EffectColour; - set - { - EffectColour = value; - BackgroundColour = value.MultiplyAlpha(0f); - } - } - public Vector2 Spacing { get => text.Spacing; @@ -76,20 +59,16 @@ namespace osu.Game.Graphics.Sprites } public GlowingSpriteText() - : base(cachedFrameBuffer: true) { - AutoSizeAxes = Axes.Both; BlurSigma = new Vector2(blur_sigma); - RedrawOnScale = false; - DrawOriginal = true; EffectBlending = BlendingParameters.Additive; - EffectPlacement = EffectPlacement.InFront; - Child = text = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Shadow = false, - }; } + + protected override Drawable CreateDrawable() => text = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Shadow = false, + }; } } From 58a68e94af79205aa13989fb8b896c9d27c0f9c0 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 31 Mar 2024 01:30:08 +0300 Subject: [PATCH 3600/4092] Simplify glowing icons in break overlay --- osu.Game/Screens/Play/Break/BlurredIcon.cs | 44 ++--------------- osu.Game/Screens/Play/Break/GlowIcon.cs | 57 ++++++++-------------- 2 files changed, 23 insertions(+), 78 deletions(-) diff --git a/osu.Game/Screens/Play/Break/BlurredIcon.cs b/osu.Game/Screens/Play/Break/BlurredIcon.cs index 2bf59ea63b..9cd617d3e3 100644 --- a/osu.Game/Screens/Play/Break/BlurredIcon.cs +++ b/osu.Game/Screens/Play/Break/BlurredIcon.cs @@ -1,52 +1,16 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// 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.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; -using osuTK; namespace osu.Game.Screens.Play.Break { - public partial class BlurredIcon : BufferedContainer + public partial class BlurredIcon : GlowIcon { - private readonly SpriteIcon icon; - - public IconUsage Icon - { - set => icon.Icon = value; - get => icon.Icon; - } - - public override Vector2 Size - { - set - { - icon.Size = value; - base.Size = value + BlurSigma * 5; - ForceRedraw(); - } - get => base.Size; - } - public BlurredIcon() - : base(cachedFrameBuffer: true) { - RelativePositionAxes = Axes.X; - Child = icon = new SpriteIcon - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Shadow = false, - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Colour = colours.BlueLighter; + EffectBlending = BlendingParameters.Additive; + DrawOriginal = false; } } } diff --git a/osu.Game/Screens/Play/Break/GlowIcon.cs b/osu.Game/Screens/Play/Break/GlowIcon.cs index 8e2b9da0ad..a68cfdac42 100644 --- a/osu.Game/Screens/Play/Break/GlowIcon.cs +++ b/osu.Game/Screens/Play/Break/GlowIcon.cs @@ -3,64 +3,45 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using osuTK; namespace osu.Game.Screens.Play.Break { - public partial class GlowIcon : Container + public partial class GlowIcon : GlowingDrawable { - private readonly SpriteIcon spriteIcon; - private readonly BlurredIcon blurredIcon; - - public override Vector2 Size - { - get => base.Size; - set - { - blurredIcon.Size = spriteIcon.Size = value; - blurredIcon.ForceRedraw(); - } - } - - public Vector2 BlurSigma - { - get => blurredIcon.BlurSigma; - set => blurredIcon.BlurSigma = value; - } + private SpriteIcon icon = null!; public IconUsage Icon { - get => spriteIcon.Icon; - set => spriteIcon.Icon = blurredIcon.Icon = value; + set => icon.Icon = value; + get => icon.Icon; + } + + public new Vector2 Size + { + set => icon.Size = value; + get => icon.Size; } public GlowIcon() { RelativePositionAxes = Axes.X; - AutoSizeAxes = Axes.Both; - Children = new Drawable[] - { - blurredIcon = new BlurredIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - spriteIcon = new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Shadow = false, - } - }; } [BackgroundDependencyLoader] private void load(OsuColour colours) { - blurredIcon.Colour = colours.Blue; + GlowColour = colours.BlueLighter; } + + protected override Drawable CreateDrawable() => icon = new SpriteIcon + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Shadow = false, + }; } } From 11b113580496fd979bfa083573e0e17ac6cb8f64 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 31 Mar 2024 01:55:34 +0300 Subject: [PATCH 3601/4092] Adjust blurred icons position due to size handling differences --- osu.Game/Screens/Play/Break/BreakArrows.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Break/BreakArrows.cs b/osu.Game/Screens/Play/Break/BreakArrows.cs index 41277c7557..40474a7137 100644 --- a/osu.Game/Screens/Play/Break/BreakArrows.cs +++ b/osu.Game/Screens/Play/Break/BreakArrows.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Play.Break private const int blurred_icon_blur_sigma = 20; private const int blurred_icon_size = 130; - private const float blurred_icon_final_offset = 0.35f; + private const float blurred_icon_final_offset = 0.38f; private const float blurred_icon_offscreen_offset = 0.7f; private readonly GlowIcon leftGlowIcon; From 450e7016bcbf2e778910f6114da6688140d3cbd5 Mon Sep 17 00:00:00 2001 From: Arthur Araujo Date: Sat, 30 Mar 2024 21:23:05 -0300 Subject: [PATCH 3602/4092] Bind `StackHeight` changes to visual update methods --- .../TestScenePathControlPointVisualiser.cs | 48 +++++++++++++++++++ .../PathControlPointConnectionPiece.cs | 2 + .../Components/PathControlPointPiece.cs | 2 + 3 files changed, 52 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs index 2b53554ed1..0c12e6fb21 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs @@ -172,6 +172,54 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertControlPointPathType(4, null); } + [Test] + public void TestStackingUpdatesPointsPosition() + { + createVisualiser(true); + + Vector2[] points = [ + new Vector2(200), + new Vector2(300), + new Vector2(500, 300), + new Vector2(700, 200), + new Vector2(500, 100) + ]; + + foreach (var point in points) addControlPointStep(point); + + AddStep("apply stacking", () => slider.StackHeightBindable.Value += 1); + + for (int i = 0; i < points.Length; i++) + addAssertPointPositionChanged(points, i); + } + + [Test] + public void TestStackingUpdatesConnectionPosition() + { + createVisualiser(true); + + Vector2 connectionPosition = default!; + + addControlPointStep(connectionPosition = new Vector2(300)); + addControlPointStep(new Vector2(600)); + + // Apply a big number in stacking so the person running the test can clearly see if it fails + AddStep("apply stacking", () => slider.StackHeightBindable.Value += 10); + + AddAssert($"Connection at {connectionPosition} changed", + () => visualiser.Connections[0].Position, + () => !Is.EqualTo(connectionPosition) + ); + } + + private void addAssertPointPositionChanged(Vector2[] points, int index) + { + AddAssert($"Point at {points.ElementAt(index)} changed", + () => visualiser.Pieces[index].Position, + () => !Is.EqualTo(points.ElementAt(index)) + ); + } + private void createVisualiser(bool allowSelection) => AddStep("create visualiser", () => Child = visualiser = new PathControlPointVisualiser(slider, allowSelection) { Anchor = Anchor.Centre, diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs index 7e7d653dbd..56dc16dd95 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs @@ -56,6 +56,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components pathVersion = hitObject.Path.Version.GetBoundCopy(); pathVersion.BindValueChanged(_ => Scheduler.AddOnce(updateConnectingPath)); + hitObject.StackHeightBindable.BindValueChanged(_ => updateConnectingPath()); + updateConnectingPath(); } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index e741d67e3b..ee306fb6d7 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -105,6 +105,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components hitObjectScale = hitObject.ScaleBindable.GetBoundCopy(); hitObjectScale.BindValueChanged(_ => updateMarkerDisplay()); + hitObject.StackHeightBindable.BindValueChanged(_ => updateMarkerDisplay()); + IsSelected.BindValueChanged(_ => updateMarkerDisplay()); updateMarkerDisplay(); From 86def7e263ff0bc811a9f7cd4b51c4d7ee1d7129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=C3=AAn=20Minh=20H=E1=BB=93?= <32929093+honguyenminh@users.noreply.github.com> Date: Sun, 31 Mar 2024 16:00:47 +0700 Subject: [PATCH 3603/4092] Change editor rotate button disabled tooltip text Co-authored-by: Dean Herbert --- osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs index 70441b33dd..da50233920 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Osu.Edit }; selectionCentreButton.Selected.DisabledChanged += isDisabled => { - selectionCentreButton.TooltipText = isDisabled ? "We can't rotate a circle around itself! Can we?" : string.Empty; + selectionCentreButton.TooltipText = isDisabled ? "Select more than one circles to perform rotation." : string.Empty; }; } From 19f0caa0f363427d0df39e5f9eb7e34c822f2699 Mon Sep 17 00:00:00 2001 From: Arthur Araujo Date: Sun, 31 Mar 2024 13:39:19 -0300 Subject: [PATCH 3604/4092] Fix wrong formatting in array creation --- .../Editor/TestScenePathControlPointVisualiser.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs index 0c12e6fb21..335ccb5280 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs @@ -177,7 +177,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { createVisualiser(true); - Vector2[] points = [ + Vector2[] points = + [ new Vector2(200), new Vector2(300), new Vector2(500, 300), From 7615c71efef1742e2c19620086964474d03a69f4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 1 Apr 2024 15:28:20 +0900 Subject: [PATCH 3605/4092] Fix inspection --- .../Editor/TestScenePathControlPointVisualiser.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs index 335ccb5280..0ca30e00bc 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs @@ -199,8 +199,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { createVisualiser(true); - Vector2 connectionPosition = default!; - + Vector2 connectionPosition; addControlPointStep(connectionPosition = new Vector2(300)); addControlPointStep(new Vector2(600)); From 099ad22a92658f8bc016a1abe72fb2b13fb99da6 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 1 Apr 2024 15:31:34 +0900 Subject: [PATCH 3606/4092] Use local bindable instead Binding events directly to an external bindable will cause that bindable to hold a permanent reference to the current object. We use `GetBoundCopy()` or otherwise a local bindable + `.BindTo()` to create a weak-referenced copy of the target bindable. When the local bindable's lifetime expires, so does the external bindable's reference to it. --- .../Sliders/Components/PathControlPointConnectionPiece.cs | 4 +++- .../Blueprints/Sliders/Components/PathControlPointPiece.cs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs index 56dc16dd95..9b3d8fc7a7 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs @@ -28,6 +28,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private IBindable hitObjectPosition; private IBindable pathVersion; + private IBindable stackHeight; public PathControlPointConnectionPiece(T hitObject, int controlPointIndex) { @@ -56,7 +57,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components pathVersion = hitObject.Path.Version.GetBoundCopy(); pathVersion.BindValueChanged(_ => Scheduler.AddOnce(updateConnectingPath)); - hitObject.StackHeightBindable.BindValueChanged(_ => updateConnectingPath()); + stackHeight = hitObject.StackHeightBindable.GetBoundCopy(); + stackHeight.BindValueChanged(_ => updateConnectingPath()); updateConnectingPath(); } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index ee306fb6d7..c6e05d3ca3 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -48,6 +48,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private IBindable hitObjectPosition; private IBindable hitObjectScale; + private IBindable stackHeight; public PathControlPointPiece(T hitObject, PathControlPoint controlPoint) { @@ -105,7 +106,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components hitObjectScale = hitObject.ScaleBindable.GetBoundCopy(); hitObjectScale.BindValueChanged(_ => updateMarkerDisplay()); - hitObject.StackHeightBindable.BindValueChanged(_ => updateMarkerDisplay()); + stackHeight = hitObject.StackHeightBindable.GetBoundCopy(); + stackHeight.BindValueChanged(_ => updateMarkerDisplay()); IsSelected.BindValueChanged(_ => updateMarkerDisplay()); From d12a2e7df7131fea37974bbb0707705cc2f0e977 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 1 Apr 2024 17:02:02 +0900 Subject: [PATCH 3607/4092] Replace schedule with SequenceEqual() --- osu.Game/Screens/Select/FilterControl.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 7b8b5393bd..0bfd927234 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -218,12 +219,16 @@ namespace osu.Game.Screens.Select maximumStars.ValueChanged += _ => updateCriteria(); ruleset.BindValueChanged(_ => updateCriteria()); - mods.BindValueChanged(_ => + mods.BindValueChanged(m => { - // Mods are updated once by the mod select overlay when song select is entered, regardless of if there are any mods. + // Mods are updated once by the mod select overlay when song select is entered, + // regardless of if there are any mods or any changes have taken place. // Updating the criteria here so early triggers a re-ordering of panels on song select, via... some mechanism. - // Todo: Investigate/fix the above and remove this schedule. - Scheduler.AddOnce(updateCriteria); + // Todo: Investigate/fix and potentially remove this. + if (m.NewValue.SequenceEqual(m.OldValue)) + return; + + updateCriteria(); }); groupMode.BindValueChanged(_ => updateCriteria()); From 8e0ca11d1cca1571b0d0efd61e0322b618333e5d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 1 Apr 2024 17:02:32 +0900 Subject: [PATCH 3608/4092] Fully qualify LegacyBeatmapConversionDifficultyInfo --- osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs | 3 --- osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs | 3 ++- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index bed04a882f..39ee3d209b 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -98,9 +98,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps } } - public static int GetColumnCount(IBeatmapInfo beatmapInfo, IReadOnlyList? mods = null) - => GetColumnCount(LegacyBeatmapConversionDifficultyInfo.FromBeatmapInfo(beatmapInfo), mods); - public static int GetColumnCount(LegacyBeatmapConversionDifficultyInfo difficulty, IReadOnlyList? mods = null) { var converter = new ManiaBeatmapConverter(null, difficulty, new ManiaRuleset()); diff --git a/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs b/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs index 07ed3ebd63..ea7eb5b8f0 100644 --- a/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs +++ b/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs @@ -4,6 +4,7 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Filter; using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Scoring.Legacy; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Filter; @@ -15,7 +16,7 @@ namespace osu.Game.Rulesets.Mania public bool Matches(BeatmapInfo beatmapInfo, FilterCriteria criteria) { - return !keys.HasFilter || keys.IsInRange(ManiaBeatmapConverter.GetColumnCount(beatmapInfo, criteria.Mods)); + return !keys.HasFilter || keys.IsInRange(ManiaBeatmapConverter.GetColumnCount(LegacyBeatmapConversionDifficultyInfo.FromBeatmapInfo(beatmapInfo), criteria.Mods)); } public bool TryParseCustomKeywordCriteria(string key, Operator op, string value) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 77168dca68..b5614e2b56 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -424,7 +424,7 @@ namespace osu.Game.Rulesets.Mania public override DifficultySection CreateEditorDifficultySection() => new ManiaDifficultySection(); public int GetKeyCount(IBeatmapInfo beatmapInfo, IReadOnlyList? mods = null) - => ManiaBeatmapConverter.GetColumnCount(beatmapInfo, mods); + => ManiaBeatmapConverter.GetColumnCount(LegacyBeatmapConversionDifficultyInfo.FromBeatmapInfo(beatmapInfo), mods); } public enum PlayfieldType From 4806ea54f1a55c1220c0772d2b385f674a06661d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 1 Apr 2024 17:22:50 +0900 Subject: [PATCH 3609/4092] Only optimise Catmull segments in osu ruleset --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 +- osu.Game/Rulesets/Objects/SliderPath.cs | 25 +++++++++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 203e829180..cc3ffd376e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Objects public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t); - private readonly SliderPath path = new SliderPath(); + private readonly SliderPath path = new SliderPath { OptimiseCatmull = true }; public SliderPath Path { diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 5398d6c45f..e8e769e3fa 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -134,6 +134,24 @@ namespace osu.Game.Rulesets.Objects } } + private bool optimiseCatmull; + + /// + /// Whether to optimise Catmull path segments, usually resulting in removing bulbs around stacked knots. + /// + /// + /// This changes the path shape and should therefore not be used. + /// + public bool OptimiseCatmull + { + get => optimiseCatmull; + set + { + optimiseCatmull = value; + invalidate(); + } + } + /// /// Computes the slider path until a given progress that ranges from 0 (beginning of the slider) /// to 1 (end of the slider) and stores the generated path in the given list. @@ -280,7 +298,7 @@ namespace osu.Game.Rulesets.Objects calculatedPath.Add(segmentVertices[0]); else if (segmentVertices.Length > 1) { - List subPath = calculateSubPath(segmentVertices, segmentType, ref optimisedLength); + List subPath = calculateSubPath(segmentVertices, segmentType); // Skip the first vertex if it is the same as the last vertex from the previous segment bool skipFirst = calculatedPath.Count > 0 && subPath.Count > 0 && calculatedPath.Last() == subPath[0]; @@ -300,7 +318,7 @@ namespace osu.Game.Rulesets.Objects } } - private static List calculateSubPath(ReadOnlySpan subControlPoints, PathType type, ref double optimisedLength) + private List calculateSubPath(ReadOnlySpan subControlPoints, PathType type) { switch (type.Type) { @@ -325,6 +343,9 @@ namespace osu.Game.Rulesets.Objects { List subPath = PathApproximator.CatmullToPiecewiseLinear(subControlPoints); + if (!OptimiseCatmull) + return subPath; + // At draw time, osu!stable optimises paths by only keeping piecewise segments that are 6px apart. // For the most part we don't care about this optimisation, and its additional heuristics are hard to reproduce in every implementation. // From ed5dd5c8cd100f99223a20360e10e52668c64edb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Apr 2024 13:04:34 +0800 Subject: [PATCH 3610/4092] Bind using local bindables to avoid potentially event pollution --- osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs index 3e2cbe9d60..9499bacade 100644 --- a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs @@ -22,6 +22,9 @@ namespace osu.Game.Rulesets.Osu.Edit private EditorToolButton rotateButton = null!; + private Bindable canRotatePlayfieldOrigin = null!; + private Bindable canRotateSelectionOrigin = null!; + public SelectionRotationHandler RotationHandler { get; init; } = null!; public TransformToolboxGroup() @@ -52,8 +55,11 @@ namespace osu.Game.Rulesets.Osu.Edit base.LoadComplete(); // aggregate two values into canRotate - RotationHandler.CanRotatePlayfieldOrigin.BindValueChanged(_ => updateCanRotateAggregate()); - RotationHandler.CanRotateSelectionOrigin.BindValueChanged(_ => updateCanRotateAggregate()); + canRotatePlayfieldOrigin = RotationHandler.CanRotatePlayfieldOrigin.GetBoundCopy(); + canRotatePlayfieldOrigin.BindValueChanged(_ => updateCanRotateAggregate()); + + canRotateSelectionOrigin = RotationHandler.CanRotateSelectionOrigin.GetBoundCopy(); + canRotateSelectionOrigin.BindValueChanged(_ => updateCanRotateAggregate()); void updateCanRotateAggregate() { From eca242c1c73567433a25e34e075740b35ec7119d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Apr 2024 13:17:16 +0800 Subject: [PATCH 3611/4092] Change tooltip text slightly --- osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs index da50233920..caf02d1dda 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Osu.Edit }; selectionCentreButton.Selected.DisabledChanged += isDisabled => { - selectionCentreButton.TooltipText = isDisabled ? "Select more than one circles to perform rotation." : string.Empty; + selectionCentreButton.TooltipText = isDisabled ? "Select more than one objects to perform selection-based rotation." : string.Empty; }; } From 6642702fa94e1819ec13243d87a6626c88e88a1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=C3=AAn=20Minh=20H=E1=BB=93?= <32929093+honguyenminh@users.noreply.github.com> Date: Tue, 2 Apr 2024 12:36:44 +0700 Subject: [PATCH 3612/4092] Update plurals in editor rotate button tooltip Co-authored-by: Joseph Madamba --- osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs index caf02d1dda..88c3d7414b 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Osu.Edit }; selectionCentreButton.Selected.DisabledChanged += isDisabled => { - selectionCentreButton.TooltipText = isDisabled ? "Select more than one objects to perform selection-based rotation." : string.Empty; + selectionCentreButton.TooltipText = isDisabled ? "Select more than one object to perform selection-based rotation." : string.Empty; }; } From 2a2a372595a7a64dc1b2fe660e6f053e4522cd1e Mon Sep 17 00:00:00 2001 From: Arthur Araujo Date: Tue, 2 Apr 2024 07:45:27 -0300 Subject: [PATCH 3613/4092] Check if `blueprint` is in `SelectionBlueprints` before changing its depth --- .../Editor/TestSceneObjectMerging.cs | 132 ++++++++++++++++++ .../Compose/Components/BlueprintContainer.cs | 4 +- 2 files changed, 135 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs index 3d35ab79f7..2da4c83a42 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs @@ -231,6 +231,138 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor (pos: circle2.Position, pathType: null))); } + [Test] + public void TestMergeSliderSliderSameStartTime() + { + Slider? slider1 = null; + SliderPath? slider1Path = null; + Slider? slider2 = null; + + AddStep("select two sliders", () => + { + slider1 = (Slider)EditorBeatmap.HitObjects.First(h => h is Slider); + slider1Path = new SliderPath(slider1.Path.ControlPoints.Select(p => new PathControlPoint(p.Position, p.Type)).ToArray(), slider1.Path.ExpectedDistance.Value); + slider2 = (Slider)EditorBeatmap.HitObjects.First(h => h is Slider && h.StartTime > slider1.StartTime); + EditorClock.Seek(slider1.StartTime); + EditorBeatmap.SelectedHitObjects.AddRange([slider1, slider2]); + }); + + AddStep("move sliders to the same start time", () => + { + slider2!.StartTime = slider1!.StartTime; + }); + + mergeSelection(); + + AddAssert("slider created", () => + { + if (slider1 is null || slider2 is null || slider1Path is null) + return false; + + var controlPoints1 = slider1Path.ControlPoints; + var controlPoints2 = slider2.Path.ControlPoints; + (Vector2, PathType?)[] args = new (Vector2, PathType?)[controlPoints1.Count + controlPoints2.Count - 1]; + + for (int i = 0; i < controlPoints1.Count - 1; i++) + { + args[i] = (controlPoints1[i].Position + slider1.Position, controlPoints1[i].Type); + } + + for (int i = 0; i < controlPoints2.Count; i++) + { + args[i + controlPoints1.Count - 1] = (controlPoints2[i].Position + controlPoints1[^1].Position + slider1.Position, controlPoints2[i].Type); + } + + return sliderCreatedFor(args); + }); + + AddAssert("samples exist", sliderSampleExist); + + AddAssert("merged slider matches first slider", () => + { + var mergedSlider = (Slider)EditorBeatmap.SelectedHitObjects.First(); + return slider1 is not null && mergedSlider.HeadCircle.Samples.SequenceEqual(slider1.HeadCircle.Samples) + && mergedSlider.TailCircle.Samples.SequenceEqual(slider1.TailCircle.Samples) + && mergedSlider.Samples.SequenceEqual(slider1.Samples); + }); + + AddAssert("slider end is at same completion for last slider", () => + { + if (slider1Path is null || slider2 is null) + return false; + + var mergedSlider = (Slider)EditorBeatmap.SelectedHitObjects.First(); + return Precision.AlmostEquals(mergedSlider.Path.Distance, slider1Path.CalculatedDistance + slider2.Path.Distance); + }); + } + + [Test] + public void TestMergeSliderSliderSameStartAndEndTime() + { + Slider? slider1 = null; + SliderPath? slider1Path = null; + Slider? slider2 = null; + + AddStep("select two sliders", () => + { + slider1 = (Slider)EditorBeatmap.HitObjects.First(h => h is Slider); + slider1Path = new SliderPath(slider1.Path.ControlPoints.Select(p => new PathControlPoint(p.Position, p.Type)).ToArray(), slider1.Path.ExpectedDistance.Value); + slider2 = (Slider)EditorBeatmap.HitObjects.First(h => h is Slider && h.StartTime > slider1.StartTime); + EditorClock.Seek(slider1.StartTime); + EditorBeatmap.SelectedHitObjects.AddRange([slider1, slider2]); + }); + + AddStep("move sliders to the same start & end time", () => + { + slider2!.StartTime = slider1!.StartTime; + slider2.Path = slider1.Path; + }); + + mergeSelection(); + + AddAssert("slider created", () => + { + if (slider1 is null || slider2 is null || slider1Path is null) + return false; + + var controlPoints1 = slider1Path.ControlPoints; + var controlPoints2 = slider2.Path.ControlPoints; + (Vector2, PathType?)[] args = new (Vector2, PathType?)[controlPoints1.Count + controlPoints2.Count - 1]; + + for (int i = 0; i < controlPoints1.Count - 1; i++) + { + args[i] = (controlPoints1[i].Position + slider1.Position, controlPoints1[i].Type); + } + + for (int i = 0; i < controlPoints2.Count; i++) + { + args[i + controlPoints1.Count - 1] = (controlPoints2[i].Position + controlPoints1[^1].Position + slider1.Position, controlPoints2[i].Type); + } + + return sliderCreatedFor(args); + }); + + AddAssert("samples exist", sliderSampleExist); + + AddAssert("merged slider matches first slider", () => + { + var mergedSlider = (Slider)EditorBeatmap.SelectedHitObjects.First(); + return slider1 is not null && mergedSlider.HeadCircle.Samples.SequenceEqual(slider1.HeadCircle.Samples) + && mergedSlider.TailCircle.Samples.SequenceEqual(slider1.TailCircle.Samples) + && mergedSlider.Samples.SequenceEqual(slider1.Samples); + }); + + AddAssert("slider end is at same completion for last slider", () => + { + if (slider1Path is null || slider2 is null) + return false; + + var mergedSlider = (Slider)EditorBeatmap.SelectedHitObjects.First(); + return Precision.AlmostEquals(mergedSlider.Path.Distance, slider1Path.CalculatedDistance + slider2.Path.Distance); + }); + } + + private void mergeSelection() { AddStep("merge selection", () => diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 2d6e234e57..c66be90605 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -512,7 +512,9 @@ namespace osu.Game.Screens.Edit.Compose.Components protected virtual void OnBlueprintDeselected(SelectionBlueprint blueprint) { - SelectionBlueprints.ChangeChildDepth(blueprint, 0); + if (SelectionBlueprints.Contains(blueprint)) + SelectionBlueprints.ChangeChildDepth(blueprint, 0); + SelectionHandler.HandleDeselected(blueprint); } From 9315aefe41a48501aa6ad222df849d0dd9b3dc89 Mon Sep 17 00:00:00 2001 From: Arthur Araujo Date: Tue, 2 Apr 2024 08:48:01 -0300 Subject: [PATCH 3614/4092] Fix remove additional blank line --- osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs index 2da4c83a42..76982b05a7 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs @@ -362,7 +362,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor }); } - private void mergeSelection() { AddStep("merge selection", () => From 94cbe1838fff6ce48d01fccea2ba14ac9c5b2067 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Apr 2024 01:50:39 +0800 Subject: [PATCH 3615/4092] Replace usages of `is null` with `== null` --- .../Editor/TestSceneObjectMerging.cs | 14 +++++++------- .../Editor/TestSceneSliderSplitting.cs | 6 +++--- .../Blueprints/Sliders/SliderSelectionBlueprint.cs | 2 +- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 +- .../Objects/Legacy/ConvertHitObjectParser.cs | 2 +- osu.Game/Rulesets/Objects/PathControlPoint.cs | 2 +- osu.Game/Rulesets/Objects/SliderPathExtensions.cs | 4 ++-- osu.Game/Rulesets/UI/ModIcon.cs | 2 +- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs index 76982b05a7..dfe950c01e 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("slider created", () => { - if (circle1 is null || circle2 is null || slider is null) + if (circle1 == null || circle2 == null || slider == null) return false; var controlPoints = slider.Path.ControlPoints; @@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("slider created", () => { - if (slider1 is null || slider2 is null || slider1Path is null) + if (slider1 == null || slider2 == null || slider1Path == null) return false; var controlPoints1 = slider1Path.ControlPoints; @@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("slider end is at same completion for last slider", () => { - if (slider1Path is null || slider2 is null) + if (slider1Path == null || slider2 == null) return false; var mergedSlider = (Slider)EditorBeatmap.SelectedHitObjects.First(); @@ -256,7 +256,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("slider created", () => { - if (slider1 is null || slider2 is null || slider1Path is null) + if (slider1 == null || slider2 == null || slider1Path == null) return false; var controlPoints1 = slider1Path.ControlPoints; @@ -288,7 +288,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("slider end is at same completion for last slider", () => { - if (slider1Path is null || slider2 is null) + if (slider1Path == null || slider2 == null) return false; var mergedSlider = (Slider)EditorBeatmap.SelectedHitObjects.First(); @@ -322,7 +322,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("slider created", () => { - if (slider1 is null || slider2 is null || slider1Path is null) + if (slider1 == null || slider2 == null || slider1Path == null) return false; var controlPoints1 = slider1Path.ControlPoints; @@ -354,7 +354,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("slider end is at same completion for last slider", () => { - if (slider1Path is null || slider2 is null) + if (slider1Path == null || slider2 == null) return false; var mergedSlider = (Slider)EditorBeatmap.SelectedHitObjects.First(); diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs index 6c7733e68a..d68cbe6265 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs @@ -178,7 +178,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("add hitsounds", () => { - if (slider is null) return; + if (slider == null) return; sample = new HitSampleInfo("hitwhistle", HitSampleInfo.BANK_SOFT, volume: 70); slider.Samples.Add(sample.With()); @@ -228,7 +228,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { AddStep($"move mouse to control point {index}", () => { - if (slider is null || visualiser is null) return; + if (slider == null || visualiser == null) return; Vector2 position = slider.Path.ControlPoints[index].Position + slider.Position; InputManager.MoveMouseTo(visualiser.Pieces[0].Parent!.ToScreenSpace(position)); @@ -239,7 +239,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { AddStep($"click context menu item \"{contextMenuText}\"", () => { - if (visualiser is null) return; + if (visualiser == null) return; MenuItem? item = visualiser.ContextMenuItems?.FirstOrDefault(menuItem => menuItem.Text.Value == contextMenuText); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 4d2b980c23..2da462caf4 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -311,7 +311,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders foreach (var splitPoint in controlPointsToSplitAt) { - if (splitPoint == controlPoints[0] || splitPoint == controlPoints[^1] || splitPoint.Type is null) + if (splitPoint == controlPoints[0] || splitPoint == controlPoints[^1] || splitPoint.Type == null) continue; // Split off the section of slider before this control point so the remaining control points to split are in the latter part of the slider. diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 53c24dc828..3325cfe407 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -781,7 +781,7 @@ namespace osu.Game.Overlays.Mods /// > public bool OnPressed(KeyBindingPressEvent e) { - if (e.Repeat || e.Action != PlatformAction.SelectAll || SelectAllModsButton is null) + if (e.Repeat || e.Action != PlatformAction.SelectAll || SelectAllModsButton == null) return false; SelectAllModsButton.TriggerClick(); diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 283a59b7ed..66b3033f90 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -347,7 +347,7 @@ namespace osu.Game.Rulesets.Objects.Legacy // Edge-case rules (to match stable). if (type == PathType.PERFECT_CURVE) { - int endPointLength = endPoint is null ? 0 : 1; + int endPointLength = endPoint == null ? 0 : 1; if (vertices.Length + endPointLength != 3) type = PathType.BEZIER; diff --git a/osu.Game/Rulesets/Objects/PathControlPoint.cs b/osu.Game/Rulesets/Objects/PathControlPoint.cs index 1f8e63b269..32245e9080 100644 --- a/osu.Game/Rulesets/Objects/PathControlPoint.cs +++ b/osu.Game/Rulesets/Objects/PathControlPoint.cs @@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Objects public bool Equals(PathControlPoint other) => Position == other?.Position && Type == other.Type; - public override string ToString() => type is null + public override string ToString() => type == null ? $"Position={Position}" : $"Position={Position}, Type={type}"; } diff --git a/osu.Game/Rulesets/Objects/SliderPathExtensions.cs b/osu.Game/Rulesets/Objects/SliderPathExtensions.cs index 29b34ae4f0..c03d3646da 100644 --- a/osu.Game/Rulesets/Objects/SliderPathExtensions.cs +++ b/osu.Game/Rulesets/Objects/SliderPathExtensions.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Objects { var controlPoints = sliderPath.ControlPoints; - var inheritedLinearPoints = controlPoints.Where(p => sliderPath.PointsInSegment(p)[0].Type == PathType.LINEAR && p.Type is null).ToList(); + var inheritedLinearPoints = controlPoints.Where(p => sliderPath.PointsInSegment(p)[0].Type == PathType.LINEAR && p.Type == null).ToList(); // Inherited points after a linear point, as well as the first control point if it inherited, // should be treated as linear points, so their types are temporarily changed to linear. @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Objects inheritedLinearPoints.ForEach(p => p.Type = null); // Recalculate middle perfect curve control points at the end of the slider path. - if (controlPoints.Count >= 3 && controlPoints[^3].Type == PathType.PERFECT_CURVE && controlPoints[^2].Type is null && segmentEnds.Any()) + if (controlPoints.Count >= 3 && controlPoints[^3].Type == PathType.PERFECT_CURVE && controlPoints[^2].Type == null && segmentEnds.Any()) { double lastSegmentStart = segmentEnds.Length > 1 ? segmentEnds[^2] : 0; double lastSegmentEnd = segmentEnds[^1]; diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index d1776c5c0b..5d9fafd60c 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -177,7 +177,7 @@ namespace osu.Game.Rulesets.UI modAcronym.Text = value.Acronym; modIcon.Icon = value.Icon ?? FontAwesome.Solid.Question; - if (value.Icon is null) + if (value.Icon == null) { modIcon.FadeOut(); modAcronym.FadeIn(); From b5adcf2e0e8f94056af1f868d3fcc1baf37f08c0 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 3 Apr 2024 17:32:02 +0900 Subject: [PATCH 3616/4092] Fix SpectatorClient holding references to Player --- osu.Game/Online/Spectator/SpectatorClient.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 07ee9115d6..fb7a3d13ca 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -248,6 +248,9 @@ namespace osu.Game.Online.Spectator isPlaying = false; currentBeatmap = null; + currentScore = null; + currentScoreProcessor = null; + currentScoreToken = null; if (state.HasPassed) currentState.State = SpectatedUserState.Passed; From ce68f6adb76c909af038284621d410e720beae61 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 3 Apr 2024 17:46:26 +0900 Subject: [PATCH 3617/4092] Fix SkinEditor binding event to external bindable --- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index d3af928907..ac9649bcba 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -52,6 +52,7 @@ namespace osu.Game.Overlays.SkinEditor private OsuTextFlowContainer headerText = null!; private Bindable currentSkin = null!; + private Bindable clipboardContent = null!; [Resolved] private OsuGame? game { get; set; } @@ -243,7 +244,8 @@ namespace osu.Game.Overlays.SkinEditor canCopy.Value = canCut.Value = SelectedComponents.Any(); }, true); - clipboard.Content.BindValueChanged(content => canPaste.Value = !string.IsNullOrEmpty(content.NewValue), true); + clipboardContent = clipboard.Content.GetBoundCopy(); + clipboardContent.BindValueChanged(content => canPaste.Value = !string.IsNullOrEmpty(content.NewValue), true); Show(); From 05fe8968d88f763621afe3042d665afaa8934331 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 3 Apr 2024 11:39:12 +0200 Subject: [PATCH 3618/4092] Only interact with clipboard via bound copy --- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index ac9649bcba..619eac8f4a 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -66,9 +66,6 @@ namespace osu.Game.Overlays.SkinEditor [Resolved] private RealmAccess realm { get; set; } = null!; - [Resolved] - private EditorClipboard clipboard { get; set; } = null!; - [Resolved] private SkinEditorOverlay? skinEditorOverlay { get; set; } @@ -114,7 +111,7 @@ namespace osu.Game.Overlays.SkinEditor } [BackgroundDependencyLoader] - private void load() + private void load(EditorClipboard clipboard) { RelativeSizeAxes = Axes.Both; @@ -225,6 +222,8 @@ namespace osu.Game.Overlays.SkinEditor } } }; + + clipboardContent = clipboard.Content.GetBoundCopy(); } protected override void LoadComplete() @@ -244,7 +243,6 @@ namespace osu.Game.Overlays.SkinEditor canCopy.Value = canCut.Value = SelectedComponents.Any(); }, true); - clipboardContent = clipboard.Content.GetBoundCopy(); clipboardContent.BindValueChanged(content => canPaste.Value = !string.IsNullOrEmpty(content.NewValue), true); Show(); @@ -497,7 +495,7 @@ namespace osu.Game.Overlays.SkinEditor protected void Copy() { - clipboard.Content.Value = JsonConvert.SerializeObject(SelectedComponents.Cast().Select(s => s.CreateSerialisedInfo()).ToArray()); + clipboardContent.Value = JsonConvert.SerializeObject(SelectedComponents.Cast().Select(s => s.CreateSerialisedInfo()).ToArray()); } protected void Clone() @@ -517,7 +515,7 @@ namespace osu.Game.Overlays.SkinEditor changeHandler?.BeginChange(); - var drawableInfo = JsonConvert.DeserializeObject(clipboard.Content.Value); + var drawableInfo = JsonConvert.DeserializeObject(clipboardContent.Value); if (drawableInfo == null) return; From 16276dfcd6ce5c35d50fa0c63877ff293c2bbdd0 Mon Sep 17 00:00:00 2001 From: Mafalda Fernandes Date: Mon, 1 Apr 2024 19:21:05 +0100 Subject: [PATCH 3619/4092] Fix #27105: Mod search box doesnt track external focus changes In the Mod selection area, the search bar's focus could be changed by pressing TAB. However, when clicking outside of the search bar, the focus would be killed but two TABs were required to get the focus back on the search bar. This happened because the action of clicking in an empty area would trigger the search bar to change its appearence, but not its internal state. In my solution, I made the OnClick function aware of the search bar's state, so it would not only change its appearance, but also its state. Now, after clicking in an empty area, there is only needed one TAB to select the search box again, as expected. --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 3325cfe407..5ca26c739e 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -420,7 +420,7 @@ namespace osu.Game.Overlays.Mods yield return new ColumnDimContainer(new ModPresetColumn { Margin = new MarginPadding { Right = 10 } - }); + }, this); } yield return createModColumnContent(ModType.DifficultyReduction); @@ -438,7 +438,7 @@ namespace osu.Game.Overlays.Mods column.Margin = new MarginPadding { Right = 10 }; }); - return new ColumnDimContainer(column); + return new ColumnDimContainer(column, this); } private void createLocalMods() @@ -899,13 +899,17 @@ namespace osu.Game.Overlays.Mods [Resolved] private OsuColour colours { get; set; } = null!; - public ColumnDimContainer(ModSelectColumn column) + private ModSelectOverlay modSelectOverlayInstance; + + public ColumnDimContainer(ModSelectColumn column, ModSelectOverlay modSelectOverlay) { AutoSizeAxes = Axes.X; RelativeSizeAxes = Axes.Y; Child = Column = column; column.Active.BindTo(Active); + + this.modSelectOverlayInstance = modSelectOverlay; } [BackgroundDependencyLoader] @@ -953,7 +957,7 @@ namespace osu.Game.Overlays.Mods RequestScroll?.Invoke(this); // Killing focus is done here because it's the only feasible place on ModSelectOverlay you can click on without triggering any action. - Scheduler.Add(() => GetContainingInputManager().ChangeFocus(null)); + modSelectOverlayInstance.setTextBoxFocus(false); return true; } From 524a5815bc3bf0842e6161efe7719abea550713f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 3 Apr 2024 16:11:23 +0200 Subject: [PATCH 3620/4092] Add test coverage --- .../Gameplay/TestSceneStoryboardWithIntro.cs | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithIntro.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithIntro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithIntro.cs new file mode 100644 index 0000000000..502a0de616 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithIntro.cs @@ -0,0 +1,89 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Play; +using osu.Game.Storyboards; +using osuTK; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public partial class TestSceneStoryboardWithIntro : PlayerTestScene + { + protected override bool HasCustomSteps => true; + protected override bool AllowFail => true; + + protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + { + var beatmap = new Beatmap(); + beatmap.HitObjects.Add(new HitCircle { StartTime = firstObjectStartTime }); + return beatmap; + } + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null) + { + return base.CreateWorkingBeatmap(beatmap, createStoryboard(storyboardStartTime)); + } + + private Storyboard createStoryboard(double startTime) + { + var storyboard = new Storyboard(); + var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero); + sprite.TimelineGroup.Alpha.Add(Easing.None, startTime, 0, 0, 1); + storyboard.GetLayer("Background").Add(sprite); + return storyboard; + } + + private double firstObjectStartTime; + private double storyboardStartTime; + + [SetUpSteps] + public override void SetUpSteps() + { + base.SetUpSteps(); + AddStep("enable storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, true)); + AddStep("set dim level to 0", () => LocalConfig.SetValue(OsuSetting.DimLevel, 0)); + AddStep("reset first hitobject time", () => firstObjectStartTime = 0); + AddStep("reset storyboard start time", () => storyboardStartTime = 0); + } + + [TestCase(-5000, 0)] + [TestCase(-5000, 30000)] + public void TestStoryboardSingleSkip(double storyboardStart, double firstObject) + { + AddStep($"set storyboard start time to {storyboardStart}", () => storyboardStartTime = storyboardStart); + AddStep($"set first object start time to {firstObject}", () => firstObjectStartTime = firstObject); + CreateTest(); + + AddStep("skip", () => InputManager.Key(osuTK.Input.Key.Space)); + AddAssert("skip performed", () => Player.ChildrenOfType().Any(s => s.SkipCount == 1)); + AddUntilStep("gameplay clock advanced", () => Player.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(firstObject - 2000)); + } + + [Test] + public void TestStoryboardDoubleSkip() + { + AddStep("set storyboard start time to -11000", () => storyboardStartTime = -11000); + AddStep("set first object start time to 11000", () => firstObjectStartTime = 11000); + CreateTest(); + + AddStep("skip", () => InputManager.Key(osuTK.Input.Key.Space)); + AddAssert("skip performed", () => Player.ChildrenOfType().Any(s => s.SkipCount == 1)); + AddUntilStep("gameplay clock advanced", () => Player.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(0)); + + AddStep("skip", () => InputManager.Key(osuTK.Input.Key.Space)); + AddAssert("skip performed", () => Player.ChildrenOfType().Any(s => s.SkipCount == 2)); + AddUntilStep("gameplay clock advanced", () => Player.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(9000)); + } + } +} From 9d54f1a09270b9d9758a2df837c958476e6ec16b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 3 Apr 2024 16:12:20 +0200 Subject: [PATCH 3621/4092] Fix some maps requiring multiple intro skips that weren't there on stable Closes https://github.com/ppy/osu/issues/25633. The reason why that particular beatmap did not have a double skip on stable is here: https://github.com/peppy/osu-stable-reference/blob/e53980dd76857ee899f66ce519ba1597e7874f28/osu!/GameModes/Play/Player.cs#L1761-L1770 The particular place of interest is the `leadInTime < 10000` check. If `leadInTime < 10000`, then `leadIn == leadInTime`, and it turns out that `AudioEngine.Time` will always be more than or equal to `leadIn`, because it's also the gameplay start time: https://github.com/peppy/osu-stable-reference/blob/e53980dd76857ee899f66ce519ba1597e7874f28/osu!/GameModes/Play/Player.cs#L2765 This essentially means that if the `leadInTime` is less than 10000, that particular check is just dead. So a double skip can only occur if the gameplay starts at time -10000 or earlier due to the storyboard. --- osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 93bdcb1cab..b2f0ae5561 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -124,7 +124,7 @@ namespace osu.Game.Screens.Play double skipTarget = skipTargetTime - MINIMUM_SKIP_TIME; - if (GameplayClock.CurrentTime < 0 && skipTarget > 6000) + if (StartTime < -10000 && GameplayClock.CurrentTime < 0 && skipTarget > 6000) // double skip exception for storyboards with very long intros skipTarget = 0; From 8e00368f7cf7a4be473d18316ca4fe56080918eb Mon Sep 17 00:00:00 2001 From: Arthur Araujo Date: Wed, 3 Apr 2024 11:30:14 -0300 Subject: [PATCH 3622/4092] Add custom message in the case of a invalid beatmap_hash --- osu.Game/Screens/Play/SubmittingPlayer.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 62226c46dd..ce8260a52f 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -152,6 +152,10 @@ namespace osu.Game.Screens.Play Logger.Log($"Please ensure that you are using the latest version of the official game releases.\n\n{whatWillHappen}", level: LogLevel.Important); break; + case @"invalid beatmap hash": + Logger.Log($"A new version of this beatmapset is available please update. \n\n{whatWillHappen}", level: LogLevel.Important); + break; + case @"expired token": Logger.Log($"Your system clock is set incorrectly. Please check your system time, date and timezone.\n\n{whatWillHappen}", level: LogLevel.Important); break; From 9e92ebaa437c8adbbd2a406e8a493f8443d9d05d Mon Sep 17 00:00:00 2001 From: Arthur Araujo <90941580+64ArthurAraujo@users.noreply.github.com> Date: Wed, 3 Apr 2024 19:15:22 -0300 Subject: [PATCH 3623/4092] Make message more general Co-authored-by: Walavouchey <36758269+Walavouchey@users.noreply.github.com> --- osu.Game/Screens/Play/SubmittingPlayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index ce8260a52f..8ccfd039ec 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -153,7 +153,7 @@ namespace osu.Game.Screens.Play break; case @"invalid beatmap hash": - Logger.Log($"A new version of this beatmapset is available please update. \n\n{whatWillHappen}", level: LogLevel.Important); + Logger.Log($"This beatmap does not match the online version. Please update or redownload it.\n\n{whatWillHappen}", level: LogLevel.Important); break; case @"expired token": From 9521c1e3e42e4c73fd94f840ce41585ff0552570 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Apr 2024 14:31:40 +0800 Subject: [PATCH 3624/4092] Update hit error metre to use new icons - [ ] Depends on https://github.com/ppy/osu-resources/pull/317. --- osu.Game/Graphics/OsuIcon.cs | 8 ++++++++ .../Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/OsuIcon.cs b/osu.Game/Graphics/OsuIcon.cs index 3cd10b1315..32e780f11c 100644 --- a/osu.Game/Graphics/OsuIcon.cs +++ b/osu.Game/Graphics/OsuIcon.cs @@ -175,6 +175,8 @@ namespace osu.Game.Graphics public static IconUsage EditorSelect => get(OsuIconMapping.EditorSelect); public static IconUsage EditorSound => get(OsuIconMapping.EditorSound); public static IconUsage EditorWhistle => get(OsuIconMapping.EditorWhistle); + public static IconUsage Tortoise => get(OsuIconMapping.Tortoise); + public static IconUsage Hare => get(OsuIconMapping.Hare); private static IconUsage get(OsuIconMapping glyph) => new IconUsage((char)glyph, FONT_NAME); @@ -380,6 +382,12 @@ namespace osu.Game.Graphics [Description(@"Editor/whistle")] EditorWhistle, + + [Description(@"tortoise")] + Tortoise, + + [Description(@"hare")] + Hare, } public class OsuIconStore : ITextureStore, ITexturedGlyphLookupStore diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 443863fb2f..a71a46ec2a 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -303,13 +303,13 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters labelEarly.Child = new SpriteIcon { Size = new Vector2(icon_size), - Icon = FontAwesome.Solid.ShippingFast, + Icon = OsuIcon.Hare }; labelLate.Child = new SpriteIcon { Size = new Vector2(icon_size), - Icon = FontAwesome.Solid.Bicycle, + Icon = OsuIcon.Tortoise }; break; From cd474de61f963c924d42291a8872ae7c6ffa0988 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 4 Apr 2024 15:55:05 +0900 Subject: [PATCH 3625/4092] Update osu.Framework package --- 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 4901f30d8a..2d7a9d2652 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 6b63bfa1e2..b2e3fc0779 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From 10d1308a0a1b69a463be814639425b4967191689 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Apr 2024 15:05:59 +0800 Subject: [PATCH 3626/4092] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 0e091dbd37..feaf47b809 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + From fb8fb4f34e2e7ef325248aaf264f8035a05795fc Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 4 Apr 2024 16:43:26 +0900 Subject: [PATCH 3627/4092] Disable Discord URI registration on macOS for now --- osu.Desktop/DiscordRichPresence.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 6e8554d617..7553924d1b 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -6,6 +6,7 @@ using System.Text; using DiscordRPC; using DiscordRPC.Message; using Newtonsoft.Json; +using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.ObjectExtensions; @@ -78,9 +79,13 @@ namespace osu.Desktop client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Message} ({e.Code})", LoggingTarget.Network, LogLevel.Error); // A URI scheme is required to support game invitations, as well as informing Discord of the game executable path to support launching the game when a user clicks on join/spectate. - client.RegisterUriScheme(); - client.Subscribe(EventType.Join); - client.OnJoin += onJoin; + // The library doesn't properly support URI registration when ran from an app bundle on macOS. + if (!RuntimeInfo.IsApple) + { + client.RegisterUriScheme(); + client.Subscribe(EventType.Join); + client.OnJoin += onJoin; + } config.BindWith(OsuSetting.DiscordRichPresence, privacyMode); From 7b92c725b1e4004d1e082989522906335efe4859 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 5 Apr 2024 14:45:49 +0900 Subject: [PATCH 3628/4092] Revert "Merge pull request #27454 from EVAST9919/sb-lifetime-improvements" This reverts commit 0881e7c8c1f003416cc37507ce6448f74e9d1a7f, reversing changes made to 29a37e35852649c2f88b6c917b2921950c9e2dd9. --- osu.Game/Storyboards/CommandTimelineGroup.cs | 41 +++++++++++++- osu.Game/Storyboards/StoryboardSprite.cs | 58 +------------------- 2 files changed, 41 insertions(+), 58 deletions(-) diff --git a/osu.Game/Storyboards/CommandTimelineGroup.cs b/osu.Game/Storyboards/CommandTimelineGroup.cs index c899cf77d3..0b96db6861 100644 --- a/osu.Game/Storyboards/CommandTimelineGroup.cs +++ b/osu.Game/Storyboards/CommandTimelineGroup.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 System; using osuTK; using osuTK.Graphics; using osu.Framework.Graphics; @@ -45,10 +46,32 @@ namespace osu.Game.Storyboards } [JsonIgnore] - public double CommandsStartTime => timelines.Min(static t => t.StartTime); + public double CommandsStartTime + { + get + { + double min = double.MaxValue; + + for (int i = 0; i < timelines.Length; i++) + min = Math.Min(min, timelines[i].StartTime); + + return min; + } + } [JsonIgnore] - public double CommandsEndTime => timelines.Max(static t => t.EndTime); + public double CommandsEndTime + { + get + { + double max = double.MinValue; + + for (int i = 0; i < timelines.Length; i++) + max = Math.Max(max, timelines[i].EndTime); + + return max; + } + } [JsonIgnore] public double CommandsDuration => CommandsEndTime - CommandsStartTime; @@ -60,7 +83,19 @@ namespace osu.Game.Storyboards public virtual double EndTime => CommandsEndTime; [JsonIgnore] - public bool HasCommands => timelines.Any(static t => t.HasCommands); + public bool HasCommands + { + get + { + for (int i = 0; i < timelines.Length; i++) + { + if (timelines[i].HasCommands) + return true; + } + + return false; + } + } public virtual IEnumerable.TypedCommand> GetCommands(CommandTimelineSelector timelineSelector, double offset = 0) { diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index 4992ae128d..982185d51b 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -85,23 +85,12 @@ namespace osu.Game.Storyboards { get { - double latestEndTime = double.MaxValue; - - // Ignore the whole setup if there are loops. In theory they can be handled here too, however the logic will be overly complex. - if (loops.Count == 0) - { - // Take the minimum time of all the potential "death" reasons. - latestEndTime = calculateOptimisedEndTime(TimelineGroup); - } - - // If the logic above fails to find anything or discarded by the fact that there are loops present, latestEndTime will be double.MaxValue - // and thus conservativeEndTime will be used. - double conservativeEndTime = TimelineGroup.EndTime; + double latestEndTime = TimelineGroup.EndTime; foreach (var l in loops) - conservativeEndTime = Math.Max(conservativeEndTime, l.StartTime + l.CommandsDuration * l.TotalIterations); + latestEndTime = Math.Max(latestEndTime, l.StartTime + l.CommandsDuration * l.TotalIterations); - return Math.Min(latestEndTime, conservativeEndTime); + return latestEndTime; } } @@ -205,47 +194,6 @@ namespace osu.Game.Storyboards return commands; } - private static double calculateOptimisedEndTime(CommandTimelineGroup timelineGroup) - { - // Here we are starting from maximum value and trying to minimise the end time on each step. - // There are few solid guesses we can make using which sprite's end time can be minimised: alpha = 0, scale = 0, colour.a = 0. - double[] deathTimes = - { - double.MaxValue, // alpha - double.MaxValue, // colour alpha - double.MaxValue, // scale - double.MaxValue, // scale x - double.MaxValue, // scale y - }; - - // The loops below are following the same pattern. - // We could be using TimelineGroup.EndValue here, however it's possible to have multiple commands with 0 value in a row - // so we are saving the earliest of them. - foreach (var alphaCommand in timelineGroup.Alpha.Commands) - { - if (alphaCommand.EndValue == 0) - // commands are ordered by the start time, however end time may vary. Save the earliest. - deathTimes[0] = Math.Min(alphaCommand.EndTime, deathTimes[0]); - else - // If value isn't 0 (sprite becomes visible again), revert the saved state. - deathTimes[0] = double.MaxValue; - } - - foreach (var colourCommand in timelineGroup.Colour.Commands) - deathTimes[1] = colourCommand.EndValue.A == 0 ? Math.Min(colourCommand.EndTime, deathTimes[1]) : double.MaxValue; - - foreach (var scaleCommand in timelineGroup.Scale.Commands) - deathTimes[2] = scaleCommand.EndValue == 0 ? Math.Min(scaleCommand.EndTime, deathTimes[2]) : double.MaxValue; - - foreach (var scaleCommand in timelineGroup.VectorScale.Commands) - { - deathTimes[3] = scaleCommand.EndValue.X == 0 ? Math.Min(scaleCommand.EndTime, deathTimes[3]) : double.MaxValue; - deathTimes[4] = scaleCommand.EndValue.Y == 0 ? Math.Min(scaleCommand.EndTime, deathTimes[4]) : double.MaxValue; - } - - return deathTimes.Min(); - } - public override string ToString() => $"{Path}, {Origin}, {InitialPosition}"; From fefcd17db90150b8cd18079270a47cf173f96f47 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 8 Apr 2024 22:00:05 +0900 Subject: [PATCH 3629/4092] Fix gameplay PP counter not matching results screen --- .../Difficulty/DifficultyCalculator.cs | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 00c90bd317..5973a83fd3 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -108,9 +108,18 @@ namespace osu.Game.Rulesets.Difficulty var skills = CreateSkills(Beatmap, playableMods, clockRate); var progressiveBeatmap = new ProgressiveCalculationBeatmap(Beatmap); + // There is a one-to-many relationship between the hitobjects in the beatmap and the "difficulty hitobjects". + // Each iteration of the loop bellow will add at most one hitobject to the progressive beatmap, + // representing the most-parenting hitobject - the hitobject from the original beatmap. + Dictionary hitObjectParentLinks = + createHitObjectParentLinks(Beatmap) + .ToDictionary(k => k.obj, k => k.mostParentingObject); + foreach (var hitObject in getDifficultyHitObjects()) { - progressiveBeatmap.HitObjects.Add(hitObject.BaseObject); + HitObject parent = hitObjectParentLinks[hitObject.BaseObject]; + if (progressiveBeatmap.HitObjects.Count == 0 || parent != progressiveBeatmap.HitObjects[^1]) + progressiveBeatmap.HitObjects.Add(parent); foreach (var skill in skills) { @@ -122,6 +131,23 @@ namespace osu.Game.Rulesets.Difficulty } return attribs; + + static IEnumerable<(HitObject obj, HitObject mostParentingObject)> createHitObjectParentLinks(IBeatmap beatmap) + { + foreach (var link in createNestedLinks(beatmap.HitObjects, null)) + yield return link; + + static IEnumerable<(HitObject obj, HitObject mostParentingObject)> createNestedLinks(IReadOnlyList objects, [CanBeNull] HitObject parent) + { + foreach (var o in objects) + { + yield return (o, parent ?? o); + + foreach (var n in createNestedLinks(o.NestedHitObjects, parent ?? o)) + yield return n; + } + } + } } /// From 7d8fe5117834aeb296cd6be28a89effa2bb9083f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 8 Apr 2024 23:25:45 +0900 Subject: [PATCH 3630/4092] Fix possible crash due to race in DiscordRichPresence --- osu.Desktop/DiscordRichPresence.cs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 7553924d1b..ed9d4ca2d2 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -36,8 +36,6 @@ namespace osu.Desktop [Resolved] private IBindable ruleset { get; set; } = null!; - private IBindable user = null!; - [Resolved] private IAPIProvider api { get; set; } = null!; @@ -50,9 +48,11 @@ namespace osu.Desktop [Resolved] private MultiplayerClient multiplayerClient { get; set; } = null!; + [Resolved] + private OsuConfigManager config { get; set; } = null!; + private readonly IBindable status = new Bindable(); private readonly IBindable activity = new Bindable(); - private readonly Bindable privacyMode = new Bindable(); private readonly RichPresence presence = new RichPresence @@ -65,8 +65,10 @@ namespace osu.Desktop }, }; + private IBindable? user; + [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load() { client = new DiscordRpcClient(client_id) { @@ -87,6 +89,13 @@ namespace osu.Desktop client.OnJoin += onJoin; } + client.Initialize(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + config.BindWith(OsuSetting.DiscordRichPresence, privacyMode); user = api.LocalUser.GetBoundCopy(); @@ -104,8 +113,6 @@ namespace osu.Desktop activity.BindValueChanged(_ => schedulePresenceUpdate()); privacyMode.BindValueChanged(_ => schedulePresenceUpdate()); multiplayerClient.RoomUpdated += onRoomUpdated; - - client.Initialize(); } private void onReady(object _, ReadyMessage __) @@ -146,6 +153,9 @@ namespace osu.Desktop private void updatePresence(bool hideIdentifiableInformation) { + if (user == null) + return; + // user activity if (activity.Value != null) { From 14c26926f328c646ee3d7d4ec28f6199976238f8 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Tue, 9 Apr 2024 09:55:50 +0200 Subject: [PATCH 3631/4092] Upgrade to SDL3 --- osu.Desktop/OsuGameDesktop.cs | 11 ++++++----- osu.Desktop/Program.cs | 25 ++++++++++++++----------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 2b232db274..e8783c997a 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -22,7 +22,7 @@ using osu.Game.IPC; using osu.Game.Online.Multiplayer; using osu.Game.Performance; using osu.Game.Utils; -using SDL2; +using SDL; namespace osu.Desktop { @@ -161,7 +161,7 @@ namespace osu.Desktop host.Window.Title = Name; } - protected override BatteryInfo CreateBatteryInfo() => new SDL2BatteryInfo(); + protected override BatteryInfo CreateBatteryInfo() => new SDL3BatteryInfo(); protected override void Dispose(bool isDisposing) { @@ -170,13 +170,14 @@ namespace osu.Desktop archiveImportIPCChannel?.Dispose(); } - private class SDL2BatteryInfo : BatteryInfo + private unsafe class SDL3BatteryInfo : BatteryInfo { public override double? ChargeLevel { get { - SDL.SDL_GetPowerInfo(out _, out int percentage); + int percentage; + SDL3.SDL_GetPowerInfo(null, &percentage); if (percentage == -1) return null; @@ -185,7 +186,7 @@ namespace osu.Desktop } } - public override bool OnBattery => SDL.SDL_GetPowerInfo(out _, out _) == SDL.SDL_PowerState.SDL_POWERSTATE_ON_BATTERY; + public override bool OnBattery => SDL3.SDL_GetPowerInfo(null, null) == SDL_PowerState.SDL_POWERSTATE_ON_BATTERY; } } } diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 2d7ec5aa5f..29b05a402f 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -13,7 +13,7 @@ using osu.Framework.Platform; using osu.Game; using osu.Game.IPC; using osu.Game.Tournament; -using SDL2; +using SDL; using Squirrel; namespace osu.Desktop @@ -52,16 +52,19 @@ namespace osu.Desktop // See https://www.mongodb.com/docs/realm/sdk/dotnet/compatibility/ if (windowsVersion.Major < 6 || (windowsVersion.Major == 6 && windowsVersion.Minor <= 2)) { - // If users running in compatibility mode becomes more of a common thing, we may want to provide better guidance or even consider - // disabling it ourselves. - // We could also better detect compatibility mode if required: - // https://stackoverflow.com/questions/10744651/how-i-can-detect-if-my-application-is-running-under-compatibility-mode#comment58183249_10744730 - SDL.SDL_ShowSimpleMessageBox(SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR, - "Your operating system is too old to run osu!", - "This version of osu! requires at least Windows 8.1 to run.\n" - + "Please upgrade your operating system or consider using an older version of osu!.\n\n" - + "If you are running a newer version of windows, please check you don't have \"Compatibility mode\" turned on for osu!", IntPtr.Zero); - return; + unsafe + { + // If users running in compatibility mode becomes more of a common thing, we may want to provide better guidance or even consider + // disabling it ourselves. + // We could also better detect compatibility mode if required: + // https://stackoverflow.com/questions/10744651/how-i-can-detect-if-my-application-is-running-under-compatibility-mode#comment58183249_10744730 + SDL3.SDL_ShowSimpleMessageBox(SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR, + "Your operating system is too old to run osu!"u8, + "This version of osu! requires at least Windows 8.1 to run.\n"u8 + + "Please upgrade your operating system or consider using an older version of osu!.\n\n"u8 + + "If you are running a newer version of windows, please check you don't have \"Compatibility mode\" turned on for osu!"u8, null); + return; + } } setupSquirrel(); From 6cb5bffdfc07cbd17abe6bd4ddf01148d975d928 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Apr 2024 13:03:37 +0800 Subject: [PATCH 3632/4092] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 61ad2a4f5a..3ea756a8b8 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + From 3e8ddbd2a9d49173139dd06f709693d0999f456e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Apr 2024 11:33:16 +0800 Subject: [PATCH 3633/4092] Add new entries to dotsettings (Rider 2024.1) --- osu.sln.DotSettings | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 452f90ecea..dd71744bf0 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -774,9 +774,19 @@ See the LICENCE file in the repository root for full licence text. <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Private" Description="Constant fields (private)"><ElementKinds><Kind Name="CONSTANT_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Type parameters"><ElementKinds><Kind Name="TYPE_PARAMETER" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Constant fields (not private)"><ElementKinds><Kind Name="CONSTANT_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Local functions"><ElementKinds><Kind Name="LOCAL_FUNCTION" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Enum members"><ElementKinds><Kind Name="ENUM_MEMBER" /></ElementKinds></Descriptor><Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /></Policy> <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="private methods"><ElementKinds><Kind Name="ASYNC_METHOD" /><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public" Description="internal/protected/public methods"><ElementKinds><Kind Name="ASYNC_METHOD" /><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="private properties"><ElementKinds><Kind Name="PROPERTY" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Local constants"><ElementKinds><Kind Name="LOCAL_CONSTANT" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /></Policy> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static readonly fields (not private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /></Policy> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public" Description="internal/protected/public properties"><ElementKinds><Kind Name="PROPERTY" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> @@ -841,6 +851,7 @@ See the LICENCE file in the repository root for full licence text. True True True + True TestFolder True True From f5555b9fa4f82564b2fa17c41aa0af91ab9e177b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 11 Apr 2024 16:53:04 +0900 Subject: [PATCH 3634/4092] Also add potentially missed intermediate parents --- osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 5973a83fd3..084ead30c9 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -117,9 +117,11 @@ namespace osu.Game.Rulesets.Difficulty foreach (var hitObject in getDifficultyHitObjects()) { + // Add hitobjects between the original and progressive beatmap until the current hitobject's parent appears in the progressive beatmap. + // This covers cases where hitobjects aren't assigned "difficulty" representations because they don't meaningfully contribute to the calculations. HitObject parent = hitObjectParentLinks[hitObject.BaseObject]; - if (progressiveBeatmap.HitObjects.Count == 0 || parent != progressiveBeatmap.HitObjects[^1]) - progressiveBeatmap.HitObjects.Add(parent); + while (progressiveBeatmap.HitObjects.LastOrDefault() != parent) + progressiveBeatmap.HitObjects.Add(Beatmap.HitObjects[progressiveBeatmap.HitObjects.Count]); foreach (var skill in skills) { From bf63ba3f82a367033d21f001c45d0951a36c38c4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 11 Apr 2024 17:56:34 +0900 Subject: [PATCH 3635/4092] Add test --- .../TestSceneTimedDifficultyCalculation.cs | 191 ++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 osu.Game.Tests/NonVisual/TestSceneTimedDifficultyCalculation.cs diff --git a/osu.Game.Tests/NonVisual/TestSceneTimedDifficultyCalculation.cs b/osu.Game.Tests/NonVisual/TestSceneTimedDifficultyCalculation.cs new file mode 100644 index 0000000000..b0b06ce292 --- /dev/null +++ b/osu.Game.Tests/NonVisual/TestSceneTimedDifficultyCalculation.cs @@ -0,0 +1,191 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Threading; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.UI; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Tests.NonVisual +{ + [TestFixture] + public class TestSceneTimedDifficultyCalculation + { + [Test] + public void TestAttributesGeneratedForAllNonSkippedObjects() + { + var beatmap = new Beatmap + { + HitObjects = + { + new TestHitObject(), + new TestHitObject { Nested = 1 }, + new TestHitObject(), + } + }; + + List attribs = new TestDifficultyCalculator(new TestWorkingBeatmap(beatmap)).CalculateTimed(); + + Assert.That(attribs.Count, Is.EqualTo(4)); + assertEquals(attribs[0], beatmap.HitObjects[0]); + assertEquals(attribs[1], beatmap.HitObjects[0], beatmap.HitObjects[1]); + assertEquals(attribs[2], beatmap.HitObjects[0], beatmap.HitObjects[1]); // From the nested object. + assertEquals(attribs[3], beatmap.HitObjects[0], beatmap.HitObjects[1], beatmap.HitObjects[2]); + } + + [Test] + public void TestAttributesNotGeneratedForSkippedObjects() + { + var beatmap = new Beatmap + { + HitObjects = + { + // The first object is usually skipped in all implementations + new TestHitObject { Skip = true }, + // An intermediate skipped object. + new TestHitObject { Skip = true }, + new TestHitObject(), + } + }; + + List attribs = new TestDifficultyCalculator(new TestWorkingBeatmap(beatmap)).CalculateTimed(); + + Assert.That(attribs.Count, Is.EqualTo(1)); + assertEquals(attribs[0], beatmap.HitObjects[0], beatmap.HitObjects[1], beatmap.HitObjects[2]); + } + + [Test] + public void TestNestedObjectOnlyAddsParentOnce() + { + var beatmap = new Beatmap + { + HitObjects = + { + new TestHitObject { Skip = true, Nested = 2 }, + } + }; + + List attribs = new TestDifficultyCalculator(new TestWorkingBeatmap(beatmap)).CalculateTimed(); + + Assert.That(attribs.Count, Is.EqualTo(2)); + assertEquals(attribs[0], beatmap.HitObjects[0]); + assertEquals(attribs[1], beatmap.HitObjects[0]); + } + + private void assertEquals(TimedDifficultyAttributes attribs, params HitObject[] expected) + { + Assert.That(((TestDifficultyAttributes)attribs.Attributes).Objects, Is.EquivalentTo(expected)); + } + + private class TestHitObject : HitObject + { + /// + /// Whether to skip generating a difficulty representation for this object. + /// + public bool Skip { get; set; } + + /// + /// Whether to generate nested difficulty representations for this object, and if so, how many. + /// + public int Nested { get; set; } + + protected override void CreateNestedHitObjects(CancellationToken cancellationToken) + { + for (int i = 0; i < Nested; i++) + AddNested(new TestHitObject()); + } + } + + private class TestRuleset : Ruleset + { + public override IEnumerable GetModsFor(ModType type) => Enumerable.Empty(); + + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList? mods = null) => throw new NotImplementedException(); + + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new PassThroughBeatmapConverter(beatmap); + + public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new TestDifficultyCalculator(beatmap); + + public override string Description => string.Empty; + public override string ShortName => string.Empty; + + private class PassThroughBeatmapConverter : IBeatmapConverter + { + public event Action>? ObjectConverted + { + add { } + remove { } + } + + public IBeatmap Beatmap { get; } + + public PassThroughBeatmapConverter(IBeatmap beatmap) + { + Beatmap = beatmap; + } + + public bool CanConvert() => true; + + public IBeatmap Convert(CancellationToken cancellationToken = default) => Beatmap; + } + } + + private class TestDifficultyCalculator : DifficultyCalculator + { + public TestDifficultyCalculator(IWorkingBeatmap beatmap) + : base(new TestRuleset().RulesetInfo, beatmap) + { + } + + protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) + => new TestDifficultyAttributes { Objects = beatmap.HitObjects.ToArray() }; + + protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) + { + List objects = new List(); + + foreach (var obj in beatmap.HitObjects.OfType()) + { + if (!obj.Skip) + objects.Add(new DifficultyHitObject(obj, obj, clockRate, objects, objects.Count)); + + foreach (var nested in obj.NestedHitObjects) + objects.Add(new DifficultyHitObject(nested, nested, clockRate, objects, objects.Count)); + } + + return objects; + } + + protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => new Skill[] { new PassThroughSkill(mods) }; + + private class PassThroughSkill : Skill + { + public PassThroughSkill(Mod[] mods) + : base(mods) + { + } + + public override void Process(DifficultyHitObject current) + { + } + + public override double DifficultyValue() => 1; + } + } + + private class TestDifficultyAttributes : DifficultyAttributes + { + public HitObject[] Objects = Array.Empty(); + } + } +} From 19cc847be6dd9674ac374d3bfef7e9a295f08e9f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 11 Apr 2024 23:40:40 +0900 Subject: [PATCH 3636/4092] Implement a less failure-prone method --- .../TestSceneTimedDifficultyCalculation.cs | 33 ++++++++++++---- .../Difficulty/DifficultyCalculator.cs | 39 +++++-------------- 2 files changed, 35 insertions(+), 37 deletions(-) diff --git a/osu.Game.Tests/NonVisual/TestSceneTimedDifficultyCalculation.cs b/osu.Game.Tests/NonVisual/TestSceneTimedDifficultyCalculation.cs index b0b06ce292..4c32d3bf1c 100644 --- a/osu.Game.Tests/NonVisual/TestSceneTimedDifficultyCalculation.cs +++ b/osu.Game.Tests/NonVisual/TestSceneTimedDifficultyCalculation.cs @@ -28,9 +28,13 @@ namespace osu.Game.Tests.NonVisual { HitObjects = { - new TestHitObject(), - new TestHitObject { Nested = 1 }, - new TestHitObject(), + new TestHitObject { StartTime = 1 }, + new TestHitObject + { + StartTime = 2, + Nested = 1 + }, + new TestHitObject { StartTime = 3 }, } }; @@ -51,10 +55,18 @@ namespace osu.Game.Tests.NonVisual HitObjects = { // The first object is usually skipped in all implementations - new TestHitObject { Skip = true }, + new TestHitObject + { + StartTime = 1, + Skip = true + }, // An intermediate skipped object. - new TestHitObject { Skip = true }, - new TestHitObject(), + new TestHitObject + { + StartTime = 2, + Skip = true + }, + new TestHitObject { StartTime = 3 }, } }; @@ -71,7 +83,12 @@ namespace osu.Game.Tests.NonVisual { HitObjects = { - new TestHitObject { Skip = true, Nested = 2 }, + new TestHitObject + { + StartTime = 1, + Skip = true, + Nested = 2 + }, } }; @@ -102,7 +119,7 @@ namespace osu.Game.Tests.NonVisual protected override void CreateNestedHitObjects(CancellationToken cancellationToken) { for (int i = 0; i < Nested; i++) - AddNested(new TestHitObject()); + AddNested(new TestHitObject { StartTime = StartTime + 0.1 * i }); } } diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 084ead30c9..5d608deae2 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -108,20 +108,18 @@ namespace osu.Game.Rulesets.Difficulty var skills = CreateSkills(Beatmap, playableMods, clockRate); var progressiveBeatmap = new ProgressiveCalculationBeatmap(Beatmap); - // There is a one-to-many relationship between the hitobjects in the beatmap and the "difficulty hitobjects". - // Each iteration of the loop bellow will add at most one hitobject to the progressive beatmap, - // representing the most-parenting hitobject - the hitobject from the original beatmap. - Dictionary hitObjectParentLinks = - createHitObjectParentLinks(Beatmap) - .ToDictionary(k => k.obj, k => k.mostParentingObject); - foreach (var hitObject in getDifficultyHitObjects()) { - // Add hitobjects between the original and progressive beatmap until the current hitobject's parent appears in the progressive beatmap. - // This covers cases where hitobjects aren't assigned "difficulty" representations because they don't meaningfully contribute to the calculations. - HitObject parent = hitObjectParentLinks[hitObject.BaseObject]; - while (progressiveBeatmap.HitObjects.LastOrDefault() != parent) - progressiveBeatmap.HitObjects.Add(Beatmap.HitObjects[progressiveBeatmap.HitObjects.Count]); + // Implementations expect the progressive beatmap to only contain top-level objects from the original beatmap. + // At the same time, we also need to consider the possibility DHOs may not be generated for any given object, + // so we'll add all remaining objects up to the current point in time to the progressive beatmap. + for (int i = progressiveBeatmap.HitObjects.Count; i < Beatmap.HitObjects.Count; i++) + { + if (Beatmap.HitObjects[i].StartTime > hitObject.BaseObject.StartTime) + break; + + progressiveBeatmap.HitObjects.Add(Beatmap.HitObjects[i]); + } foreach (var skill in skills) { @@ -133,23 +131,6 @@ namespace osu.Game.Rulesets.Difficulty } return attribs; - - static IEnumerable<(HitObject obj, HitObject mostParentingObject)> createHitObjectParentLinks(IBeatmap beatmap) - { - foreach (var link in createNestedLinks(beatmap.HitObjects, null)) - yield return link; - - static IEnumerable<(HitObject obj, HitObject mostParentingObject)> createNestedLinks(IReadOnlyList objects, [CanBeNull] HitObject parent) - { - foreach (var o in objects) - { - yield return (o, parent ?? o); - - foreach (var n in createNestedLinks(o.NestedHitObjects, parent ?? o)) - yield return n; - } - } - } } /// From e9b319f4c6e43d2488cf4ebee2c9d56f7f828b70 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 12 Apr 2024 00:11:54 +0900 Subject: [PATCH 3637/4092] Ensure all remaining objects are added in the last iteration --- .../TestSceneTimedDifficultyCalculation.cs | 27 +++++++++++++++++++ .../Difficulty/DifficultyCalculator.cs | 9 ++++--- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/NonVisual/TestSceneTimedDifficultyCalculation.cs b/osu.Game.Tests/NonVisual/TestSceneTimedDifficultyCalculation.cs index 4c32d3bf1c..1a75f735ef 100644 --- a/osu.Game.Tests/NonVisual/TestSceneTimedDifficultyCalculation.cs +++ b/osu.Game.Tests/NonVisual/TestSceneTimedDifficultyCalculation.cs @@ -99,6 +99,33 @@ namespace osu.Game.Tests.NonVisual assertEquals(attribs[1], beatmap.HitObjects[0]); } + [Test] + public void TestSkippedLastObjectAddedInLastIteration() + { + var beatmap = new Beatmap + { + HitObjects = + { + new TestHitObject { StartTime = 1 }, + new TestHitObject + { + StartTime = 2, + Skip = true + }, + new TestHitObject + { + StartTime = 3, + Skip = true + }, + } + }; + + List attribs = new TestDifficultyCalculator(new TestWorkingBeatmap(beatmap)).CalculateTimed(); + + Assert.That(attribs.Count, Is.EqualTo(1)); + assertEquals(attribs[0], beatmap.HitObjects[0], beatmap.HitObjects[1], beatmap.HitObjects[2]); + } + private void assertEquals(TimedDifficultyAttributes attribs, params HitObject[] expected) { Assert.That(((TestDifficultyAttributes)attribs.Attributes).Objects, Is.EquivalentTo(expected)); diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 5d608deae2..1599dff8d9 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -107,15 +107,16 @@ namespace osu.Game.Rulesets.Difficulty var skills = CreateSkills(Beatmap, playableMods, clockRate); var progressiveBeatmap = new ProgressiveCalculationBeatmap(Beatmap); + var difficultyObjects = getDifficultyHitObjects().ToArray(); - foreach (var hitObject in getDifficultyHitObjects()) + foreach (var obj in difficultyObjects) { // Implementations expect the progressive beatmap to only contain top-level objects from the original beatmap. // At the same time, we also need to consider the possibility DHOs may not be generated for any given object, // so we'll add all remaining objects up to the current point in time to the progressive beatmap. for (int i = progressiveBeatmap.HitObjects.Count; i < Beatmap.HitObjects.Count; i++) { - if (Beatmap.HitObjects[i].StartTime > hitObject.BaseObject.StartTime) + if (obj != difficultyObjects[^1] && Beatmap.HitObjects[i].StartTime > obj.BaseObject.StartTime) break; progressiveBeatmap.HitObjects.Add(Beatmap.HitObjects[i]); @@ -124,10 +125,10 @@ namespace osu.Game.Rulesets.Difficulty foreach (var skill in skills) { cancellationToken.ThrowIfCancellationRequested(); - skill.Process(hitObject); + skill.Process(obj); } - attribs.Add(new TimedDifficultyAttributes(hitObject.EndTime * clockRate, CreateDifficultyAttributes(progressiveBeatmap, playableMods, skills, clockRate))); + attribs.Add(new TimedDifficultyAttributes(obj.EndTime * clockRate, CreateDifficultyAttributes(progressiveBeatmap, playableMods, skills, clockRate))); } return attribs; From 8b2017be453fb03905bc857bd117c2555cb05e69 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 12 Apr 2024 01:02:40 +0900 Subject: [PATCH 3638/4092] Update Sentry to fix iOS build --- osu.Game/Utils/SentryLogger.cs | 2 +- osu.Game/osu.Game.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index 61622a7122..896f4daf33 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -64,7 +64,7 @@ namespace osu.Game.Utils localUser = user.GetBoundCopy(); localUser.BindValueChanged(u => { - SentrySdk.ConfigureScope(scope => scope.User = new User + SentrySdk.ConfigureScope(scope => scope.User = new SentryUser { Username = u.NewValue.Username, Id = u.NewValue.Id.ToString(), diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3ea756a8b8..21b5bc60a5 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + From 3ec93745a45127a204b1b25df81784bc1392ac2f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Apr 2024 01:07:52 +0800 Subject: [PATCH 3639/4092] Fix test failures due to sentry oversight --- osu.Game/Utils/SentryLogger.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index 896f4daf33..8d3e5fb834 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -39,12 +39,13 @@ namespace osu.Game.Utils public SentryLogger(OsuGame game) { this.game = game; + + if (!game.IsDeployedBuild || !game.CreateEndpoints().WebsiteRootUrl.EndsWith(@".ppy.sh", StringComparison.Ordinal)) + return; + sentrySession = SentrySdk.Init(options => { - // Not setting the dsn will completely disable sentry. - if (game.IsDeployedBuild && game.CreateEndpoints().WebsiteRootUrl.EndsWith(@".ppy.sh", StringComparison.Ordinal)) - options.Dsn = "https://ad9f78529cef40ac874afb95a9aca04e@sentry.ppy.sh/2"; - + options.Dsn = "https://ad9f78529cef40ac874afb95a9aca04e@sentry.ppy.sh/2"; options.AutoSessionTracking = true; options.IsEnvironmentUser = false; options.IsGlobalModeEnabled = true; @@ -59,6 +60,9 @@ namespace osu.Game.Utils public void AttachUser(IBindable user) { + if (sentrySession == null) + return; + Debug.Assert(localUser == null); localUser = user.GetBoundCopy(); From c0dce94f1593ef13d3d9bb214d2ef32331da1f07 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Apr 2024 16:24:46 +0800 Subject: [PATCH 3640/4092] Fix newly placed items in skin editor not getting correct anchor placement --- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 619eac8f4a..bc929177d1 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -454,6 +454,7 @@ namespace osu.Game.Overlays.SkinEditor } SelectedComponents.Add(component); + SkinSelectionHandler.ApplyClosestAnchor(drawableComponent); return true; } @@ -666,8 +667,6 @@ namespace osu.Game.Overlays.SkinEditor SelectedComponents.Clear(); placeComponent(sprite, false); - - SkinSelectionHandler.ApplyClosestAnchor(sprite); }); return Task.CompletedTask; From c7f3a599c98f49e821e5790a436ff508e90ce097 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 13 Apr 2024 13:17:06 +0900 Subject: [PATCH 3641/4092] Fix crash when entering multiplayer on macOS --- osu.Desktop/DiscordRichPresence.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index ed9d4ca2d2..f1c796d0cd 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -205,7 +205,9 @@ namespace osu.Desktop Password = room.Settings.Password, }; - presence.Secrets.JoinSecret = JsonConvert.SerializeObject(roomSecret); + if (client.HasRegisteredUriScheme) + presence.Secrets.JoinSecret = JsonConvert.SerializeObject(roomSecret); + // discord cannot handle both secrets and buttons at the same time, so we need to choose something. // the multiplayer room seems more important. presence.Buttons = null; From feb9b5bdb8a74e566e35d00ce23e492f129b6f05 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sat, 13 Apr 2024 13:42:57 +0300 Subject: [PATCH 3642/4092] Make traceable pp match HD --- .../Difficulty/OsuPerformanceCalculator.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 4771bce280..e7e9308eb5 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -118,8 +118,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty } else if (score.Mods.Any(h => h is OsuModTraceable)) { - // Default 2% increase and another is scaled by AR - aimValue *= 1.02 + 0.02 * (12.0 - attributes.ApproachRate); + // The same as HD, placeholder bonus + aimValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); } // We assume 15% of sliders in a map are difficult since there's no way to tell from the performance calculator. @@ -174,8 +174,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty } else if (score.Mods.Any(h => h is OsuModTraceable)) { - // More reward for speed because speed on Traceable is annoying - speedValue *= 1.04 + 0.06 * (12.0 - attributes.ApproachRate); + // The same as HD, placeholder bonus + speedValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); } // Calculate accuracy assuming the worst case scenario @@ -225,7 +225,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty else if (score.Mods.Any(m => m is OsuModHidden)) accuracyValue *= 1.08; else if (score.Mods.Any(m => m is OsuModTraceable)) - accuracyValue *= 1.02 + 0.01 * (12.0 - attributes.ApproachRate); + accuracyValue *= 1.08; if (score.Mods.Any(m => m is OsuModFlashlight)) accuracyValue *= 1.02; From 4a21ff97263ac0219905e7a4fc51b8d5e1c55705 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sat, 13 Apr 2024 13:59:09 +0300 Subject: [PATCH 3643/4092] removed duplication --- .../Difficulty/OsuPerformanceCalculator.cs | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index e7e9308eb5..18a4b8be0c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -111,16 +111,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (score.Mods.Any(m => m is OsuModBlinds)) aimValue *= 1.3 + (totalHits * (0.0016 / (1 + 2 * effectiveMissCount)) * Math.Pow(accuracy, 16)) * (1 - 0.003 * attributes.DrainRate * attributes.DrainRate); - else if (score.Mods.Any(h => h is OsuModHidden)) + else if (score.Mods.Any(m => m is OsuModHidden || m is OsuModTraceable)) { // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. aimValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); } - else if (score.Mods.Any(h => h is OsuModTraceable)) - { - // The same as HD, placeholder bonus - aimValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); - } // We assume 15% of sliders in a map are difficult since there's no way to tell from the performance calculator. double estimateDifficultSliders = attributes.SliderCount * 0.15; @@ -167,16 +162,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty // Increasing the speed value by object count for Blinds isn't ideal, so the minimum buff is given. speedValue *= 1.12; } - else if (score.Mods.Any(m => m is OsuModHidden)) + else if (score.Mods.Any(m => m is OsuModHidden || m is OsuModTraceable)) { // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. speedValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); } - else if (score.Mods.Any(h => h is OsuModTraceable)) - { - // The same as HD, placeholder bonus - speedValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); - } // Calculate accuracy assuming the worst case scenario double relevantTotalDiff = totalHits - attributes.SpeedNoteCount; @@ -222,9 +212,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty // Increasing the accuracy value by object count for Blinds isn't ideal, so the minimum buff is given. if (score.Mods.Any(m => m is OsuModBlinds)) accuracyValue *= 1.14; - else if (score.Mods.Any(m => m is OsuModHidden)) - accuracyValue *= 1.08; - else if (score.Mods.Any(m => m is OsuModTraceable)) + else if (score.Mods.Any(m => m is OsuModHidden || m is OsuModTraceable)) accuracyValue *= 1.08; if (score.Mods.Any(m => m is OsuModFlashlight)) From 5a8b8908dd5ba8064f87d803fb1308340139582a Mon Sep 17 00:00:00 2001 From: Loreos7 Date: Sat, 13 Apr 2024 14:53:51 +0300 Subject: [PATCH 3644/4092] fix missing underscore --- osu.Game/Screens/Play/SubmittingPlayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 8ccfd039ec..6c5f7fab9e 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -152,7 +152,7 @@ namespace osu.Game.Screens.Play Logger.Log($"Please ensure that you are using the latest version of the official game releases.\n\n{whatWillHappen}", level: LogLevel.Important); break; - case @"invalid beatmap hash": + case @"invalid beatmap_hash": Logger.Log($"This beatmap does not match the online version. Please update or redownload it.\n\n{whatWillHappen}", level: LogLevel.Important); break; From 9833dd955f8c09ff8455ab499257ac044362414c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Vaj=C4=8F=C3=A1k?= Date: Sun, 14 Apr 2024 01:30:59 +0200 Subject: [PATCH 3645/4092] Fix toolbar volume bar masking --- osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs index 5da0056787..718789e3c7 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs @@ -21,7 +21,7 @@ namespace osu.Game.Overlays.Toolbar { public partial class ToolbarMusicButton : ToolbarOverlayToggleButton { - private Circle volumeBar; + private Box volumeBar; protected override Anchor TooltipAnchor => Anchor.TopRight; @@ -45,14 +45,15 @@ namespace osu.Game.Overlays.Toolbar Height = IconContainer.Height, Margin = new MarginPadding { Horizontal = 2.5f }, Masking = true, - Children = new[] + CornerRadius = 3f, + Children = new Drawable[] { new Circle { RelativeSizeAxes = Axes.Both, Colour = Color4.White.Opacity(0.25f), }, - volumeBar = new Circle + volumeBar = new Box { RelativeSizeAxes = Axes.Both, Height = 0f, From ed6680a61d535849beb78bd26738e55172e2aabc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Vaj=C4=8F=C3=A1k?= Date: Sun, 14 Apr 2024 15:10:05 +0200 Subject: [PATCH 3646/4092] Fixed type inconsistency and rounding --- osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs index 718789e3c7..51b95b7d32 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs @@ -37,7 +37,7 @@ namespace osu.Game.Overlays.Toolbar StateContainer = music; Flow.Padding = new MarginPadding { Horizontal = Toolbar.HEIGHT / 4 }; - Flow.Add(volumeDisplay = new Container + Flow.Add(volumeDisplay = new CircularContainer { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -45,10 +45,9 @@ namespace osu.Game.Overlays.Toolbar Height = IconContainer.Height, Margin = new MarginPadding { Horizontal = 2.5f }, Masking = true, - CornerRadius = 3f, - Children = new Drawable[] + Children = new[] { - new Circle + new Box { RelativeSizeAxes = Axes.Both, Colour = Color4.White.Opacity(0.25f), From f282152f996b3e15dffa9ae7564c1e09788430dd Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 14 Apr 2024 15:53:29 -0700 Subject: [PATCH 3647/4092] Enable NRT to `ScoreManager` --- .../Database/BackgroundDataStoreProcessor.cs | 17 ++++++++++------- osu.Game/Scoring/ScoreManager.cs | 18 ++++++++---------- osu.Game/Screens/Play/SaveFailedScoreButton.cs | 2 +- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/osu.Game/Database/BackgroundDataStoreProcessor.cs b/osu.Game/Database/BackgroundDataStoreProcessor.cs index 872194aa1d..52336c0242 100644 --- a/osu.Game/Database/BackgroundDataStoreProcessor.cs +++ b/osu.Game/Database/BackgroundDataStoreProcessor.cs @@ -287,14 +287,17 @@ namespace osu.Game.Database { var score = scoreManager.Query(s => s.ID == id); - scoreManager.PopulateMaximumStatistics(score); - - // Can't use async overload because we're not on the update thread. - // ReSharper disable once MethodHasAsyncOverload - realmAccess.Write(r => + if (score != null) { - r.Find(id)!.MaximumStatisticsJson = JsonConvert.SerializeObject(score.MaximumStatistics); - }); + scoreManager.PopulateMaximumStatistics(score); + + // Can't use async overload because we're not on the update thread. + // ReSharper disable once MethodHasAsyncOverload + realmAccess.Write(r => + { + r.Find(id)!.MaximumStatisticsJson = JsonConvert.SerializeObject(score.MaximumStatistics); + }); + } ++processedCount; } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 1ee99e9e93..1cdf4b0c13 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics; @@ -28,7 +26,7 @@ namespace osu.Game.Scoring public class ScoreManager : ModelManager, IModelImporter { private readonly Func beatmaps; - private readonly OsuConfigManager configManager; + private readonly OsuConfigManager? configManager; private readonly ScoreImporter scoreImporter; private readonly LegacyScoreExporter scoreExporter; @@ -43,7 +41,7 @@ namespace osu.Game.Scoring } public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm, IAPIProvider api, - OsuConfigManager configManager = null) + OsuConfigManager? configManager = null) : base(storage, realm) { this.beatmaps = beatmaps; @@ -67,7 +65,7 @@ namespace osu.Game.Scoring /// /// The query. /// The first result for the provided query, or null if no results were found. - public ScoreInfo Query(Expression> query) + public ScoreInfo? Query(Expression> query) { return Realm.Run(r => r.All().FirstOrDefault(query)?.Detach()); } @@ -104,7 +102,7 @@ namespace osu.Game.Scoring /// /// The to provide the total score of. /// The config. - public TotalScoreBindable(ScoreInfo score, OsuConfigManager configManager) + public TotalScoreBindable(ScoreInfo score, OsuConfigManager? configManager) { configManager?.BindWith(OsuSetting.ScoreDisplayMode, scoringMode); scoringMode.BindValueChanged(mode => Value = score.GetDisplayScore(mode.NewValue), true); @@ -126,7 +124,7 @@ namespace osu.Game.Scoring } } - public void Delete([CanBeNull] Expression> filter = null, bool silent = false) + public void Delete(Expression>? filter = null, bool silent = false) { Realm.Run(r => { @@ -165,9 +163,9 @@ namespace osu.Game.Scoring public Task Export(ScoreInfo score) => scoreExporter.ExportAsync(score.ToLive(Realm)); - public Task> ImportAsUpdate(ProgressNotification notification, ImportTask task, ScoreInfo original) => scoreImporter.ImportAsUpdate(notification, task, original); + public Task?> ImportAsUpdate(ProgressNotification notification, ImportTask task, ScoreInfo original) => scoreImporter.ImportAsUpdate(notification, task, original); - public Live Import(ScoreInfo item, ArchiveReader archive = null, ImportParameters parameters = default, CancellationToken cancellationToken = default) => + public Live? Import(ScoreInfo item, ArchiveReader? archive = null, ImportParameters parameters = default, CancellationToken cancellationToken = default) => scoreImporter.ImportModel(item, archive, parameters, cancellationToken); /// @@ -182,7 +180,7 @@ namespace osu.Game.Scoring #region Implementation of IPresentImports - public Action>> PresentImport + public Action>>? PresentImport { set => scoreImporter.PresentImport = value; } diff --git a/osu.Game/Screens/Play/SaveFailedScoreButton.cs b/osu.Game/Screens/Play/SaveFailedScoreButton.cs index ef27aac1b9..4f665b87e8 100644 --- a/osu.Game/Screens/Play/SaveFailedScoreButton.cs +++ b/osu.Game/Screens/Play/SaveFailedScoreButton.cs @@ -137,7 +137,7 @@ namespace osu.Game.Screens.Play { if (state.NewValue != DownloadState.LocallyAvailable) return; - scoreManager.Export(importedScore); + if (importedScore != null) scoreManager.Export(importedScore); this.state.ValueChanged -= exportWhenReady; } From ed8b59632561cfbbfa7e949daa4709660cc3356d Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 14 Apr 2024 16:22:58 -0700 Subject: [PATCH 3648/4092] Fix replay export not working correctly from online leaderboards --- osu.Game/OsuGame.cs | 19 ++--------- osu.Game/Scoring/ScoreManager.cs | 34 +++++++++++++++++-- .../Screens/Ranking/ReplayDownloadButton.cs | 4 ++- 3 files changed, 36 insertions(+), 21 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 732d5f867c..bcf12b308d 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -706,24 +706,9 @@ namespace osu.Game { Logger.Log($"Beginning {nameof(PresentScore)} with score {score}"); - // The given ScoreInfo may have missing properties if it was retrieved from online data. Re-retrieve it from the database - // to ensure all the required data for presenting a replay are present. - ScoreInfo databasedScoreInfo = null; + ScoreInfo databasedScoreInfo = ScoreManager.GetDatabasedScoreInfo(score); - if (score.OnlineID > 0) - databasedScoreInfo = ScoreManager.Query(s => s.OnlineID == score.OnlineID); - - if (score.LegacyOnlineID > 0) - databasedScoreInfo ??= ScoreManager.Query(s => s.LegacyOnlineID == score.LegacyOnlineID); - - if (score is ScoreInfo scoreInfo) - databasedScoreInfo ??= ScoreManager.Query(s => s.Hash == scoreInfo.Hash); - - if (databasedScoreInfo == null) - { - Logger.Log("The requested score could not be found locally.", LoggingTarget.Information); - return; - } + if (databasedScoreInfo == null) return; var databasedScore = ScoreManager.GetScore(databasedScoreInfo); diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 1cdf4b0c13..f699e32ac7 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -8,8 +8,8 @@ using System.Linq; using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; -using JetBrains.Annotations; using osu.Framework.Bindables; +using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Configuration; @@ -70,6 +70,34 @@ namespace osu.Game.Scoring return Realm.Run(r => r.All().FirstOrDefault(query)?.Detach()); } + /// + /// Re-retrieve a given from the database to ensure all the required data for presenting / exporting a replay are present. + /// + /// The to attempt querying on. + /// The databased score info. Null if the score on the database cannot be found. + /// Can be used when the was retrieved from online data, as it may have missing properties. + public ScoreInfo? GetDatabasedScoreInfo(IScoreInfo originalScoreInfo) + { + ScoreInfo? databasedScoreInfo = null; + + if (originalScoreInfo.OnlineID > 0) + databasedScoreInfo = Query(s => s.OnlineID == originalScoreInfo.OnlineID); + + if (originalScoreInfo.LegacyOnlineID > 0) + databasedScoreInfo ??= Query(s => s.LegacyOnlineID == originalScoreInfo.LegacyOnlineID); + + if (originalScoreInfo is ScoreInfo scoreInfo) + databasedScoreInfo ??= Query(s => s.Hash == scoreInfo.Hash); + + if (databasedScoreInfo == null) + { + Logger.Log("The requested score could not be found locally.", LoggingTarget.Information); + return null; + } + + return databasedScoreInfo; + } + /// /// Retrieves a bindable that represents the total score of a . /// @@ -78,7 +106,7 @@ namespace osu.Game.Scoring /// /// The to retrieve the bindable for. /// The bindable containing the total score. - public Bindable GetBindableTotalScore([NotNull] ScoreInfo score) => new TotalScoreBindable(score, configManager); + public Bindable GetBindableTotalScore(ScoreInfo score) => new TotalScoreBindable(score, configManager); /// /// Retrieves a bindable that represents the formatted total score string of a . @@ -88,7 +116,7 @@ namespace osu.Game.Scoring /// /// The to retrieve the bindable for. /// The bindable containing the formatted total score string. - public Bindable GetBindableTotalScoreString([NotNull] ScoreInfo score) => new TotalScoreStringBindable(GetBindableTotalScore(score)); + public Bindable GetBindableTotalScoreString(ScoreInfo score) => new TotalScoreStringBindable(GetBindableTotalScore(score)); /// /// Provides the total score of a . Responds to changes in the currently-selected . diff --git a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs index df5f9c7a8a..9bacfc5ed3 100644 --- a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs +++ b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs @@ -147,7 +147,9 @@ namespace osu.Game.Screens.Ranking { if (state.NewValue != DownloadState.LocallyAvailable) return; - scoreManager.Export(Score.Value); + ScoreInfo? databasedScoreInfo = scoreManager.GetDatabasedScoreInfo(Score.Value); + + if (databasedScoreInfo != null) scoreManager.Export(databasedScoreInfo); State.ValueChanged -= exportWhenReady; } From 8506da725dcf0aac225bd6ab5c230fb0150827e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 15 Apr 2024 11:49:47 +0200 Subject: [PATCH 3649/4092] Add failing test --- .../TestSceneExpandedPanelMiddleContent.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs index 9f7726313a..02a321d22f 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs @@ -104,6 +104,21 @@ namespace osu.Game.Tests.Visual.Ranking }); } + [Test] + public void TestPPNotShownAsProvisionalIfClassicModIsPresentDueToLegacyScore() + { + AddStep("show example score", () => + { + var score = TestResources.CreateTestScoreInfo(createTestBeatmap(new RealmUser())); + score.PP = 400; + score.Mods = score.Mods.Append(new OsuModClassic()).ToArray(); + score.IsLegacyScore = true; + showPanel(score); + }); + + AddAssert("pp display faded out", () => this.ChildrenOfType().Single().Alpha == 1); + } + [Test] public void TestWithDefaultDate() { From 7c4c8ee75c746620f955f1fbb2b1e43f26badf4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 15 Apr 2024 11:53:03 +0200 Subject: [PATCH 3650/4092] Fix stable scores showing with faded out pp display due to classic mod presence --- .../Expanded/Statistics/PerformanceStatistic.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs index 0a9c68eafc..8366f8d7ef 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -17,6 +18,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Resources.Localisation.Web; using osu.Game.Scoring; using osu.Game.Localisation; +using osu.Game.Rulesets.Mods; namespace osu.Game.Screens.Ranking.Expanded.Statistics { @@ -74,7 +76,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics Alpha = 0.5f; TooltipText = ResultsScreenStrings.NoPPForUnrankedBeatmaps; } - else if (scoreInfo.Mods.Any(m => !m.Ranked)) + else if (hasUnrankedMods(scoreInfo)) { Alpha = 0.5f; TooltipText = ResultsScreenStrings.NoPPForUnrankedMods; @@ -87,6 +89,16 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics } } + private static bool hasUnrankedMods(ScoreInfo scoreInfo) + { + IEnumerable modsToCheck = scoreInfo.Mods; + + if (scoreInfo.IsLegacyScore) + modsToCheck = modsToCheck.Where(m => m is not ModClassic); + + return modsToCheck.Any(m => !m.Ranked); + } + public override void Appear() { base.Appear(); From fe7df808b6065dcbb405f7775fdaaa0d5a441f52 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 15 Apr 2024 21:07:08 +0900 Subject: [PATCH 3651/4092] Add tests --- .../SongSelect/TestScenePlaySongSelect.cs | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index ce241f3676..e03ffd48f1 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -29,6 +29,7 @@ using osu.Game.Overlays.Dialog; using osu.Game.Overlays.Mods; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; @@ -1147,6 +1148,62 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("filter text cleared", () => songSelect!.FilterControl.ChildrenOfType().First().Text, () => Is.Empty); } + [Test] + public void TestNonFilterableModChange() + { + addRulesetImportStep(0); + + createSongSelect(); + + // Mod that is guaranteed to never re-filter. + AddStep("add non-filterable mod", () => SelectedMods.Value = new Mod[] { new OsuModCinema() }); + AddAssert("filter count is 1", () => songSelect!.FilterCount, () => Is.EqualTo(1)); + + // Removing the mod should still not re-filter. + AddStep("remove non-filterable mod", () => SelectedMods.Value = Array.Empty()); + AddAssert("filter count is 1", () => songSelect!.FilterCount, () => Is.EqualTo(1)); + } + + [Test] + public void TestFilterableModChange() + { + addRulesetImportStep(3); + + createSongSelect(); + + // Change to mania ruleset. + AddStep("filter to mania ruleset", () => Ruleset.Value = rulesets.AvailableRulesets.First(r => r.OnlineID == 3)); + AddAssert("filter count is 2", () => songSelect!.FilterCount, () => Is.EqualTo(2)); + + // Apply a mod, but this should NOT re-filter because there's no search text. + AddStep("add filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModKey3() }); + AddAssert("filter count is 2", () => songSelect!.FilterCount, () => Is.EqualTo(2)); + + // Set search text. Should re-filter. + AddStep("set search text to match mods", () => songSelect!.FilterControl.CurrentTextSearch.Value = "keys=3"); + AddAssert("filter count is 3", () => songSelect!.FilterCount, () => Is.EqualTo(3)); + + // Change filterable mod. Should re-filter. + AddStep("change new filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModKey5() }); + AddAssert("filter count is 4", () => songSelect!.FilterCount, () => Is.EqualTo(4)); + + // Add non-filterable mod. Should NOT re-filter. + AddStep("apply non-filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModNoFail(), new ManiaModKey5() }); + AddAssert("filter count is 4", () => songSelect!.FilterCount, () => Is.EqualTo(4)); + + // Remove filterable mod. Should re-filter. + AddStep("remove filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModNoFail() }); + AddAssert("filter count is 5", () => songSelect!.FilterCount, () => Is.EqualTo(5)); + + // Remove non-filterable mod. Should NOT re-filter. + AddStep("remove filterable mod", () => SelectedMods.Value = Array.Empty()); + AddAssert("filter count is 5", () => songSelect!.FilterCount, () => Is.EqualTo(5)); + + // Add filterable mod. Should re-filter. + AddStep("add filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModKey3() }); + AddAssert("filter count is 6", () => songSelect!.FilterCount, () => Is.EqualTo(6)); + } + private void waitForInitialSelection() { AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault); From 343b3ba0e614dcfd68e9f3b6046b7d119776c95e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 15 Apr 2024 21:07:36 +0900 Subject: [PATCH 3652/4092] Don't re-filter unless mods may change the filter --- .../ManiaFilterCriteria.cs | 20 +++++++++++++++++++ .../NonVisual/Filtering/FilterMatchingTest.cs | 5 +++++ .../Filtering/FilterQueryParserTest.cs | 5 +++++ .../Rulesets/Filter/IRulesetFilterCriteria.cs | 10 ++++++++++ osu.Game/Screens/Select/FilterControl.cs | 13 ++++++------ 5 files changed, 47 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs b/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs index ea7eb5b8f0..8c6efbc72d 100644 --- a/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs +++ b/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs @@ -1,9 +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.Collections.Generic; +using System.Linq; +using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Rulesets.Filter; using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring.Legacy; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Filter; @@ -30,5 +35,20 @@ namespace osu.Game.Rulesets.Mania return false; } + + public bool FilterMayChangeFromMods(ValueChangedEvent> mods) + { + if (keys.HasFilter) + { + // Interpreting as the Mod type is required for equality comparison. + HashSet oldSet = mods.OldValue.OfType().AsEnumerable().ToHashSet(); + HashSet newSet = mods.NewValue.OfType().AsEnumerable().ToHashSet(); + + if (!oldSet.SetEquals(newSet)) + return true; + } + + return false; + } } } diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index 78d8eabba7..10e0e46f4c 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -1,10 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using NUnit.Framework; +using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Filter; +using osu.Game.Rulesets.Mods; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Carousel; using osu.Game.Screens.Select.Filter; @@ -311,6 +314,8 @@ namespace osu.Game.Tests.NonVisual.Filtering public bool Matches(BeatmapInfo beatmapInfo, FilterCriteria criteria) => match; public bool TryParseCustomKeywordCriteria(string key, Operator op, string value) => false; + + public bool FilterMayChangeFromMods(ValueChangedEvent> mods) => false; } } } diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index b0ceed45b9..7897b3d8c0 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -2,10 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Rulesets.Filter; +using osu.Game.Rulesets.Mods; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Carousel; using osu.Game.Screens.Select.Filter; @@ -514,6 +517,8 @@ namespace osu.Game.Tests.NonVisual.Filtering return false; } + + public bool FilterMayChangeFromMods(ValueChangedEvent> mods) => false; } private static readonly object[] correct_date_query_examples = diff --git a/osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs b/osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs index f926b04db4..c374fe315d 100644 --- a/osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs +++ b/osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs @@ -1,7 +1,10 @@ // 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 osu.Framework.Bindables; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Filter; @@ -52,5 +55,12 @@ namespace osu.Game.Rulesets.Filter /// while ignored criteria are included in . /// bool TryParseCustomKeywordCriteria(string key, Operator op, string value); + + /// + /// Whether to reapply the filter as a result of the given change in applied mods. + /// + /// The change in mods. + /// Whether the filter should be re-applied. + bool FilterMayChangeFromMods(ValueChangedEvent> mods); } } diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 0bfd927234..73c122dda6 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -49,15 +50,14 @@ namespace osu.Game.Screens.Select } private OsuTabControl sortTabs; - private Bindable sortMode; - private Bindable groupMode; - private FilterControlTextBox searchTextBox; - private CollectionDropdown collectionDropdown; + [CanBeNull] + private FilterCriteria currentCriteria; + public FilterCriteria CreateCriteria() { string query = searchTextBox.Text; @@ -228,7 +228,8 @@ namespace osu.Game.Screens.Select if (m.NewValue.SequenceEqual(m.OldValue)) return; - updateCriteria(); + if (currentCriteria?.RulesetCriteria?.FilterMayChangeFromMods(m) == true) + updateCriteria(); }); groupMode.BindValueChanged(_ => updateCriteria()); @@ -263,7 +264,7 @@ namespace osu.Game.Screens.Select private readonly Bindable minimumStars = new BindableDouble(); private readonly Bindable maximumStars = new BindableDouble(); - private void updateCriteria() => FilterChanged?.Invoke(CreateCriteria()); + private void updateCriteria() => FilterChanged?.Invoke(currentCriteria = CreateCriteria()); protected override bool OnClick(ClickEvent e) => true; From 7e4782d4b1aeec427ea41d3a5d3bfe5d25a2f1f4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Apr 2024 09:50:51 +0800 Subject: [PATCH 3653/4092] Allow nested high performance sessions Mostly just for safety, since I noticed this would pretty much fall over in this scenario until now. --- .../HighPerformanceSessionManager.cs | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/osu.Desktop/Performance/HighPerformanceSessionManager.cs b/osu.Desktop/Performance/HighPerformanceSessionManager.cs index eb2b3be5b9..058d247aee 100644 --- a/osu.Desktop/Performance/HighPerformanceSessionManager.cs +++ b/osu.Desktop/Performance/HighPerformanceSessionManager.cs @@ -11,16 +11,24 @@ namespace osu.Desktop.Performance { public class HighPerformanceSessionManager : IHighPerformanceSessionManager { + private int activeSessions; + private GCLatencyMode originalGCMode; public IDisposable BeginSession() { - enableHighPerformanceSession(); - return new InvokeOnDisposal(this, static m => m.disableHighPerformanceSession()); + enterSession(); + return new InvokeOnDisposal(this, static m => m.exitSession()); } - private void enableHighPerformanceSession() + private void enterSession() { + if (Interlocked.Increment(ref activeSessions) > 1) + { + Logger.Log($"High performance session requested ({activeSessions} others already running)"); + return; + } + Logger.Log("Starting high performance session"); originalGCMode = GCSettings.LatencyMode; @@ -30,8 +38,14 @@ namespace osu.Desktop.Performance GC.Collect(0); } - private void disableHighPerformanceSession() + private void exitSession() { + if (Interlocked.Decrement(ref activeSessions) > 0) + { + Logger.Log($"High performance session finished ({activeSessions} others remain)"); + return; + } + Logger.Log("Ending high performance session"); if (GCSettings.LatencyMode == GCLatencyMode.LowLatency) From d89edd2b4f52fe776c77a66a414c1dcd2472bede Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Apr 2024 09:51:43 +0800 Subject: [PATCH 3654/4092] Expose high performance session state --- osu.Desktop/Performance/HighPerformanceSessionManager.cs | 3 +++ osu.Game/Performance/IHighPerformanceSessionManager.cs | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/osu.Desktop/Performance/HighPerformanceSessionManager.cs b/osu.Desktop/Performance/HighPerformanceSessionManager.cs index 058d247aee..34762de04d 100644 --- a/osu.Desktop/Performance/HighPerformanceSessionManager.cs +++ b/osu.Desktop/Performance/HighPerformanceSessionManager.cs @@ -3,6 +3,7 @@ using System; using System.Runtime; +using System.Threading; using osu.Framework.Allocation; using osu.Framework.Logging; using osu.Game.Performance; @@ -11,6 +12,8 @@ namespace osu.Desktop.Performance { public class HighPerformanceSessionManager : IHighPerformanceSessionManager { + public bool IsSessionActive => activeSessions > 0; + private int activeSessions; private GCLatencyMode originalGCMode; diff --git a/osu.Game/Performance/IHighPerformanceSessionManager.cs b/osu.Game/Performance/IHighPerformanceSessionManager.cs index d3d1fda8fc..cc995e4942 100644 --- a/osu.Game/Performance/IHighPerformanceSessionManager.cs +++ b/osu.Game/Performance/IHighPerformanceSessionManager.cs @@ -14,6 +14,11 @@ namespace osu.Game.Performance /// public interface IHighPerformanceSessionManager { + /// + /// Whether a high performance session is currently active. + /// + bool IsSessionActive { get; } + /// /// Start a new high performance session. /// From a651cb8d507c8720a83db85acef9738107046461 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Apr 2024 09:51:58 +0800 Subject: [PATCH 3655/4092] Stop background processing from running when inside a high performance session --- osu.Game/Database/BackgroundDataStoreProcessor.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/BackgroundDataStoreProcessor.cs b/osu.Game/Database/BackgroundDataStoreProcessor.cs index 872194aa1d..f3b37f608c 100644 --- a/osu.Game/Database/BackgroundDataStoreProcessor.cs +++ b/osu.Game/Database/BackgroundDataStoreProcessor.cs @@ -16,6 +16,7 @@ using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; +using osu.Game.Performance; using osu.Game.Rulesets; using osu.Game.Scoring; using osu.Game.Scoring.Legacy; @@ -51,6 +52,9 @@ namespace osu.Game.Database [Resolved] private ILocalUserPlayInfo? localUserPlayInfo { get; set; } + [Resolved] + private IHighPerformanceSessionManager? highPerformanceSessionManager { get; set; } + [Resolved] private INotificationOverlay? notificationOverlay { get; set; } @@ -493,7 +497,9 @@ namespace osu.Game.Database private void sleepIfRequired() { - while (localUserPlayInfo?.IsPlaying.Value == true) + // Importantly, also sleep if high performance session is active. + // If we don't do this, memory usage can become runaway due to GC running in a more lenient mode. + while (localUserPlayInfo?.IsPlaying.Value == true || highPerformanceSessionManager?.IsSessionActive == true) { Logger.Log("Background processing sleeping due to active gameplay..."); Thread.Sleep(TimeToSleepDuringGameplay); From 67cfcddc7727fb5a735cff9ce3dab8d0bae0e004 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 15 Apr 2024 20:18:24 -0700 Subject: [PATCH 3656/4092] Use another beatmap query to not depend on databased score info --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index bcf12b308d..6a29767a8e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -718,7 +718,7 @@ namespace osu.Game return; } - var databasedBeatmap = BeatmapManager.QueryBeatmap(b => b.ID == databasedScoreInfo.BeatmapInfo.ID); + var databasedBeatmap = BeatmapManager.QueryBeatmap(b => b.OnlineID == score.Beatmap.OnlineID); if (databasedBeatmap == null) { From 514e316b49ad9721e0e7997d02fc14dae3c86504 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 15 Apr 2024 20:48:51 -0700 Subject: [PATCH 3657/4092] Make `getDatabasedScoreInfo()` private and move to `GetScore()` and `Export()` --- osu.Game/OsuGame.cs | 6 ++---- osu.Game/Scoring/ScoreManager.cs | 16 +++++++++++++--- osu.Game/Screens/Ranking/ReplayDownloadButton.cs | 4 +--- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 6a29767a8e..ccdf9d151f 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -706,11 +706,9 @@ namespace osu.Game { Logger.Log($"Beginning {nameof(PresentScore)} with score {score}"); - ScoreInfo databasedScoreInfo = ScoreManager.GetDatabasedScoreInfo(score); + var databasedScore = ScoreManager.GetScore(score); - if (databasedScoreInfo == null) return; - - var databasedScore = ScoreManager.GetScore(databasedScoreInfo); + if (databasedScore == null) return; if (databasedScore.Replay == null) { diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index f699e32ac7..3f6c6ee49d 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -58,7 +58,12 @@ namespace osu.Game.Scoring }; } - public Score GetScore(ScoreInfo score) => scoreImporter.GetScore(score); + public Score? GetScore(IScoreInfo scoreInfo) + { + ScoreInfo? databasedScoreInfo = getDatabasedScoreInfo(scoreInfo); + + return databasedScoreInfo == null ? null : scoreImporter.GetScore(databasedScoreInfo); + } /// /// Perform a lookup query on available s. @@ -76,7 +81,7 @@ namespace osu.Game.Scoring /// The to attempt querying on. /// The databased score info. Null if the score on the database cannot be found. /// Can be used when the was retrieved from online data, as it may have missing properties. - public ScoreInfo? GetDatabasedScoreInfo(IScoreInfo originalScoreInfo) + private ScoreInfo? getDatabasedScoreInfo(IScoreInfo originalScoreInfo) { ScoreInfo? databasedScoreInfo = null; @@ -189,7 +194,12 @@ namespace osu.Game.Scoring public Task>> Import(ProgressNotification notification, ImportTask[] tasks, ImportParameters parameters = default) => scoreImporter.Import(notification, tasks); - public Task Export(ScoreInfo score) => scoreExporter.ExportAsync(score.ToLive(Realm)); + public Task? Export(ScoreInfo scoreInfo) + { + ScoreInfo? databasedScoreInfo = getDatabasedScoreInfo(scoreInfo); + + return databasedScoreInfo == null ? null : scoreExporter.ExportAsync(databasedScoreInfo.ToLive(Realm)); + } public Task?> ImportAsUpdate(ProgressNotification notification, ImportTask task, ScoreInfo original) => scoreImporter.ImportAsUpdate(notification, task, original); diff --git a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs index 9bacfc5ed3..df5f9c7a8a 100644 --- a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs +++ b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs @@ -147,9 +147,7 @@ namespace osu.Game.Screens.Ranking { if (state.NewValue != DownloadState.LocallyAvailable) return; - ScoreInfo? databasedScoreInfo = scoreManager.GetDatabasedScoreInfo(Score.Value); - - if (databasedScoreInfo != null) scoreManager.Export(databasedScoreInfo); + scoreManager.Export(Score.Value); State.ValueChanged -= exportWhenReady; } From c7b1524b9ff6d4294b09eb2c3fc4fc4f04881a8f Mon Sep 17 00:00:00 2001 From: Arthur Araujo Date: Tue, 16 Apr 2024 05:25:52 -0300 Subject: [PATCH 3658/4092] Add new audio samples --- .../Resources/Samples/test-sample.ogg | Bin 0 -> 36347 bytes .../Resources/Samples/test-sample.webm | Bin 0 -> 34247 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 osu.Game.Tests/Resources/Samples/test-sample.ogg create mode 100644 osu.Game.Tests/Resources/Samples/test-sample.webm diff --git a/osu.Game.Tests/Resources/Samples/test-sample.ogg b/osu.Game.Tests/Resources/Samples/test-sample.ogg new file mode 100644 index 0000000000000000000000000000000000000000..b33119cfaf89615cece2655a30c95c82127093ee GIT binary patch literal 36347 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Resources/Samples/test-sample.webm b/osu.Game.Tests/Resources/Samples/test-sample.webm new file mode 100644 index 0000000000000000000000000000000000000000..3964d248f4dc6fc0f84a6f3f4fcfe9db97461efa GIT binary patch literal 34247 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�tSAml_ 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 Date: Tue, 16 Apr 2024 16:37:11 +0800 Subject: [PATCH 3659/4092] Update HighPerformanceSessionManager.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Desktop/Performance/HighPerformanceSessionManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/Performance/HighPerformanceSessionManager.cs b/osu.Desktop/Performance/HighPerformanceSessionManager.cs index 34762de04d..0df87ab007 100644 --- a/osu.Desktop/Performance/HighPerformanceSessionManager.cs +++ b/osu.Desktop/Performance/HighPerformanceSessionManager.cs @@ -28,7 +28,7 @@ namespace osu.Desktop.Performance { if (Interlocked.Increment(ref activeSessions) > 1) { - Logger.Log($"High performance session requested ({activeSessions} others already running)"); + Logger.Log($"High performance session requested ({activeSessions} running in total)"); return; } From 9ef27104ce50c5844004659b3782d984f3c05c8f Mon Sep 17 00:00:00 2001 From: Arthur Araujo Date: Tue, 16 Apr 2024 06:15:21 -0300 Subject: [PATCH 3660/4092] Add checks for audio formats --- .../Checks/CheckHitsoundsFormatTest.cs | 96 +++++++++++++++++ .../Editing/Checks/CheckSongFormatTest.cs | 100 ++++++++++++++++++ osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 2 + .../Edit/Checks/CheckHitsoundsFormat.cs | 93 ++++++++++++++++ .../Rulesets/Edit/Checks/CheckSongFormat.cs | 81 ++++++++++++++ 5 files changed, 372 insertions(+) create mode 100644 osu.Game.Tests/Editing/Checks/CheckHitsoundsFormatTest.cs create mode 100644 osu.Game.Tests/Editing/Checks/CheckSongFormatTest.cs create mode 100644 osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs create mode 100644 osu.Game/Rulesets/Edit/Checks/CheckSongFormat.cs diff --git a/osu.Game.Tests/Editing/Checks/CheckHitsoundsFormatTest.cs b/osu.Game.Tests/Editing/Checks/CheckHitsoundsFormatTest.cs new file mode 100644 index 0000000000..912a7468f5 --- /dev/null +++ b/osu.Game.Tests/Editing/Checks/CheckHitsoundsFormatTest.cs @@ -0,0 +1,96 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.IO; +using System.Linq; +using ManagedBass; +using Moq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Objects; +using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Resources; +using osuTK.Audio; + +namespace osu.Game.Tests.Editing.Checks +{ + [TestFixture] + public class CheckHitsoundsFormatTest + { + private CheckHitsoundsFormat check = null!; + + private IBeatmap beatmap = null!; + + [SetUp] + public void Setup() + { + check = new CheckHitsoundsFormat(); + beatmap = new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + BeatmapSet = new BeatmapSetInfo + { + Files = { CheckTestHelpers.CreateMockFile("wav") } + } + } + }; + + // 0 = No output device. This still allows decoding. + if (!Bass.Init(0) && Bass.LastError != Errors.Already) + throw new AudioException("Could not initialize Bass."); + } + + [Test] + public void TestMP3Audio() + { + using (var resourceStream = TestResources.OpenResource("Samples/test-sample-cut.mp3")) + { + var issues = check.Run(getContext(resourceStream)).ToList(); + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckHitsoundsFormat.IssueTemplateIncorrectFormat); + } + } + + [Test] + public void TestOGGAudio() + { + using (var resourceStream = TestResources.OpenResource("Samples/test-sample.ogg")) + { + var issues = check.Run(getContext(resourceStream)).ToList(); + Assert.That(issues, Has.Count.EqualTo(0)); + } + } + + [Test] + public void TestWAVAudio() + { + using (var resourceStream = TestResources.OpenResource("Samples/hitsound-delay.wav")) + { + var issues = check.Run(getContext(resourceStream)).ToList(); + Assert.That(issues, Has.Count.EqualTo(0)); + } + } + + [Test] + public void TestWEBMAudio() + { + using (var resourceStream = TestResources.OpenResource("Samples/test-sample.webm")) + { + var issues = check.Run(getContext(resourceStream)).ToList(); + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckHitsoundsFormat.IssueTemplateFormatUnsupported); + } + } + + private BeatmapVerifierContext getContext(Stream? resourceStream) + { + var mockWorkingBeatmap = new Mock(beatmap, null, null); + mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny())).Returns(resourceStream); + + return new BeatmapVerifierContext(beatmap, mockWorkingBeatmap.Object); + } + } +} diff --git a/osu.Game.Tests/Editing/Checks/CheckSongFormatTest.cs b/osu.Game.Tests/Editing/Checks/CheckSongFormatTest.cs new file mode 100644 index 0000000000..acbf25ebad --- /dev/null +++ b/osu.Game.Tests/Editing/Checks/CheckSongFormatTest.cs @@ -0,0 +1,100 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.IO; +using System.Linq; +using ManagedBass; +using Moq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Objects; +using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Resources; +using osuTK.Audio; + +namespace osu.Game.Tests.Editing.Checks +{ + [TestFixture] + public partial class CheckSongFormatTest + { + private CheckSongFormat check = null!; + + private IBeatmap beatmap = null!; + + [SetUp] + public void Setup() + { + check = new CheckSongFormat(); + beatmap = new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + BeatmapSet = new BeatmapSetInfo + { + Files = { CheckTestHelpers.CreateMockFile("mp3") } + } + } + }; + + // 0 = No output device. This still allows decoding. + if (!Bass.Init(0) && Bass.LastError != Errors.Already) + throw new AudioException("Could not initialize Bass."); + } + + [Test] + public void TestMP3Audio() + { + using (var resourceStream = TestResources.OpenResource("Samples/test-sample-cut.mp3")) + { + beatmap.Metadata.AudioFile = "abc123.mp3"; + var issues = check.Run(getContext(resourceStream)).ToList(); + Assert.That(issues, Has.Count.EqualTo(0)); + } + } + + [Test] + public void TestOGGAudio() + { + using (var resourceStream = TestResources.OpenResource("Samples/test-sample.ogg")) + { + beatmap.Metadata.AudioFile = "abc123.mp3"; + var issues = check.Run(getContext(resourceStream)).ToList(); + Assert.That(issues, Has.Count.EqualTo(0)); + } + } + + [Test] + public void TestWAVAudio() + { + using (var resourceStream = TestResources.OpenResource("Samples/hitsound-delay.wav")) + { + beatmap.Metadata.AudioFile = "abc123.mp3"; + var issues = check.Run(getContext(resourceStream)).ToList(); + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckSongFormat.IssueTemplateIncorrectFormat); + } + } + + [Test] + public void TestWEBMAudio() + { + using (var resourceStream = TestResources.OpenResource("Samples/test-sample.webm")) + { + beatmap.Metadata.AudioFile = "abc123.mp3"; + var issues = check.Run(getContext(resourceStream)).ToList(); + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckSongFormat.IssueTemplateFormatUnsupported); + } + } + + private BeatmapVerifierContext getContext(Stream? resourceStream) + { + var mockWorkingBeatmap = new Mock(beatmap, null, null); + mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny())).Returns(resourceStream); + + return new BeatmapVerifierContext(beatmap, mockWorkingBeatmap.Object); + } + } +} diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index 7d3c7d0b2f..a9681e13ba 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -28,6 +28,8 @@ namespace osu.Game.Rulesets.Edit new CheckTooShortAudioFiles(), new CheckAudioInVideo(), new CheckDelayedHitsounds(), + new CheckSongFormat(), + new CheckHitsoundsFormat(), // Files new CheckZeroByteFiles(), diff --git a/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs b/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs new file mode 100644 index 0000000000..e490a23963 --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs @@ -0,0 +1,93 @@ +// 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.IO; +using System.Linq; +using ManagedBass; +using osu.Framework.Audio.Callbacks; +using osu.Game.Beatmaps; +using osu.Game.Extensions; +using osu.Game.Rulesets.Edit.Checks.Components; + +namespace osu.Game.Rulesets.Edit.Checks +{ + public class CheckHitsoundsFormat : ICheck + { + public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Audio, "Checks for hitsound formats."); + + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplateFormatUnsupported(this), + new IssueTemplateIncorrectFormat(this), + }; + + private IEnumerable allowedFormats => new ChannelType[] + { + ChannelType.WavePCM, + ChannelType.WaveFloat, + ChannelType.OGG, + ChannelType.Wave | ChannelType.OGG, + }; + + public IEnumerable Run(BeatmapVerifierContext context) + { + var beatmapSet = context.Beatmap.BeatmapInfo.BeatmapSet; + var audioFile = beatmapSet?.GetFile(context.Beatmap.Metadata.AudioFile); + + if (beatmapSet == null) yield break; + + foreach (var file in beatmapSet.Files) + { + if (audioFile != null && file.File == audioFile.File) continue; + + using (Stream data = context.WorkingBeatmap.GetStream(file.File.GetStoragePath())) + { + if (data == null) + continue; + + var fileCallbacks = new FileCallbacks(new DataStreamFileProcedures(data)); + int decodeStream = Bass.CreateStream(StreamSystem.NoBuffer, BassFlags.Decode | BassFlags.Prescan, fileCallbacks.Callbacks, fileCallbacks.Handle); + + // If the format is not supported by BASS + if (decodeStream == 0) + { + if (AudioCheckUtils.HasAudioExtension(file.Filename) && probablyHasAudioData(data)) + yield return new IssueTemplateFormatUnsupported(this).Create(file.Filename); + + continue; + } + + var audioInfo = Bass.ChannelGetInfo(decodeStream); + + if (!allowedFormats.Contains(audioInfo.ChannelType)) + { + yield return new IssueTemplateIncorrectFormat(this).Create(file.Filename, audioInfo.ChannelType.ToString()); + } + } + } + } + + private bool probablyHasAudioData(Stream data) => data.Length > 100; + + public class IssueTemplateFormatUnsupported : IssueTemplate + { + public IssueTemplateFormatUnsupported(ICheck check) + : base(check, IssueType.Problem, "\"{0}\" is using a unsupported format; Use wav or ogg for hitsounds.") + { + } + + public Issue Create(string file) => new Issue(this, file); + } + + public class IssueTemplateIncorrectFormat : IssueTemplate + { + public IssueTemplateIncorrectFormat(ICheck check) + : base(check, IssueType.Problem, "\"{0}\" is using a incorrect format ({1}); Use wav or ogg for hitsounds.") + { + } + + public Issue Create(string file, string format) => new Issue(this, file, format); + } + } +} diff --git a/osu.Game/Rulesets/Edit/Checks/CheckSongFormat.cs b/osu.Game/Rulesets/Edit/Checks/CheckSongFormat.cs new file mode 100644 index 0000000000..4162bf20a3 --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checks/CheckSongFormat.cs @@ -0,0 +1,81 @@ +// 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.Collections.Generic; +using System.IO; +using System.Linq; +using ManagedBass; +using osu.Framework.Audio.Callbacks; +using osu.Game.Beatmaps; +using osu.Game.Extensions; +using osu.Game.Rulesets.Edit.Checks.Components; + +namespace osu.Game.Rulesets.Edit.Checks +{ + public class CheckSongFormat : ICheck + { + public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Audio, "Checks for song formats."); + + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplateFormatUnsupported(this), + new IssueTemplateIncorrectFormat(this), + }; + + private IEnumerable allowedFormats => new ChannelType[] + { + ChannelType.MP3, + ChannelType.OGG, + }; + + public IEnumerable Run(BeatmapVerifierContext context) + { + var beatmapSet = context.Beatmap.BeatmapInfo.BeatmapSet; + var audioFile = beatmapSet?.GetFile(context.Beatmap.Metadata.AudioFile); + + if (beatmapSet == null) yield break; + if (audioFile == null) yield break; + + using (Stream data = context.WorkingBeatmap.GetStream(audioFile.File.GetStoragePath())) + { + if (data == null || data.Length <= 0) yield break; + + var fileCallbacks = new FileCallbacks(new DataStreamFileProcedures(data)); + int decodeStream = Bass.CreateStream(StreamSystem.NoBuffer, BassFlags.Decode | BassFlags.Prescan, fileCallbacks.Callbacks, fileCallbacks.Handle); + + // If the format is not supported by BASS + if (decodeStream == 0) + { + yield return new IssueTemplateFormatUnsupported(this).Create(audioFile.Filename); + yield break; + } + + var audioInfo = Bass.ChannelGetInfo(decodeStream); + + if (!allowedFormats.Contains(audioInfo.ChannelType)) + yield return new IssueTemplateIncorrectFormat(this).Create(audioFile.Filename, audioInfo.ChannelType.ToString()); + } + } + + public class IssueTemplateFormatUnsupported : IssueTemplate + { + public IssueTemplateFormatUnsupported(ICheck check) + : base(check, IssueType.Problem, "\"{0}\" is using a unsupported format; Use mp3 or ogg for the song's audio.") + { + } + + public Issue Create(string file) => new Issue(this, file); + } + + public class IssueTemplateIncorrectFormat : IssueTemplate + { + public IssueTemplateIncorrectFormat(ICheck check) + : base(check, IssueType.Problem, "\"{0}\" is using a incorrect format ({1}); Use mp3 or ogg for the song's audio.") + { + } + + public Issue Create(string file, string format) => new Issue(this, file, format); + } + } +} From c32d99250f1dce9d3bed129fd19d1ea7d9a02d22 Mon Sep 17 00:00:00 2001 From: Arthur Araujo Date: Tue, 16 Apr 2024 06:53:55 -0300 Subject: [PATCH 3661/4092] Deal with corrupt audio files This removes the corrupt file check from CheckTooShortAudioFiles and makes the audio formats checks deal with it instead to avoid redundant messages. --- .../Checks/CheckHitsoundsFormatTest.cs | 11 +++++++++ .../Editing/Checks/CheckSongFormatTest.cs | 12 ++++++++++ .../Checks/CheckTooShortAudioFilesTest.cs | 12 ---------- .../Edit/Checks/CheckHitsoundsFormat.cs | 2 +- .../Rulesets/Edit/Checks/CheckSongFormat.cs | 2 +- .../Edit/Checks/CheckTooShortAudioFiles.cs | 24 +------------------ 6 files changed, 26 insertions(+), 37 deletions(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckHitsoundsFormatTest.cs b/osu.Game.Tests/Editing/Checks/CheckHitsoundsFormatTest.cs index 912a7468f5..f85a296c74 100644 --- a/osu.Game.Tests/Editing/Checks/CheckHitsoundsFormatTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckHitsoundsFormatTest.cs @@ -85,6 +85,17 @@ namespace osu.Game.Tests.Editing.Checks } } + [Test] + public void TestCorruptAudio() + { + using (var resourceStream = TestResources.OpenResource("Samples/corrupt.wav")) + { + var issues = check.Run(getContext(resourceStream)).ToList(); + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckHitsoundsFormat.IssueTemplateFormatUnsupported); + } + } + private BeatmapVerifierContext getContext(Stream? resourceStream) { var mockWorkingBeatmap = new Mock(beatmap, null, null); diff --git a/osu.Game.Tests/Editing/Checks/CheckSongFormatTest.cs b/osu.Game.Tests/Editing/Checks/CheckSongFormatTest.cs index acbf25ebad..0755fdd8ac 100644 --- a/osu.Game.Tests/Editing/Checks/CheckSongFormatTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckSongFormatTest.cs @@ -89,6 +89,18 @@ namespace osu.Game.Tests.Editing.Checks } } + [Test] + public void TestCorruptAudio() + { + using (var resourceStream = TestResources.OpenResource("Samples/corrupt.wav")) + { + beatmap.Metadata.AudioFile = "abc123.mp3"; + var issues = check.Run(getContext(resourceStream)).ToList(); + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckSongFormat.IssueTemplateFormatUnsupported); + } + } + private BeatmapVerifierContext getContext(Stream? resourceStream) { var mockWorkingBeatmap = new Mock(beatmap, null, null); diff --git a/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs b/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs index 4918369460..b646e63955 100644 --- a/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs @@ -95,18 +95,6 @@ namespace osu.Game.Tests.Editing.Checks } } - [Test] - public void TestCorruptAudioFile() - { - using (var resourceStream = TestResources.OpenResource("Samples/corrupt.wav")) - { - var issues = check.Run(getContext(resourceStream)).ToList(); - - Assert.That(issues, Has.Count.EqualTo(1)); - Assert.That(issues.Single().Template is CheckTooShortAudioFiles.IssueTemplateBadFormat); - } - } - private BeatmapVerifierContext getContext(Stream? resourceStream) { var mockWorkingBeatmap = new Mock(beatmap, null, null); diff --git a/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs b/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs index e490a23963..f65b89fc01 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Edit.Checks public class IssueTemplateFormatUnsupported : IssueTemplate { public IssueTemplateFormatUnsupported(ICheck check) - : base(check, IssueType.Problem, "\"{0}\" is using a unsupported format; Use wav or ogg for hitsounds.") + : base(check, IssueType.Problem, "\"{0}\" may be corrupt or using a unsupported audio format; Use wav or ogg for hitsounds.") { } diff --git a/osu.Game/Rulesets/Edit/Checks/CheckSongFormat.cs b/osu.Game/Rulesets/Edit/Checks/CheckSongFormat.cs index 4162bf20a3..ae90dd96d5 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckSongFormat.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckSongFormat.cs @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Edit.Checks public class IssueTemplateFormatUnsupported : IssueTemplate { public IssueTemplateFormatUnsupported(ICheck check) - : base(check, IssueType.Problem, "\"{0}\" is using a unsupported format; Use mp3 or ogg for the song's audio.") + : base(check, IssueType.Problem, "\"{0}\" may be corrupt or using a unsupported audio format; Use mp3 or ogg for the song's audio.") { } diff --git a/osu.Game/Rulesets/Edit/Checks/CheckTooShortAudioFiles.cs b/osu.Game/Rulesets/Edit/Checks/CheckTooShortAudioFiles.cs index 32a3aa5ad9..3f85926e04 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckTooShortAudioFiles.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckTooShortAudioFiles.cs @@ -13,14 +13,12 @@ namespace osu.Game.Rulesets.Edit.Checks public class CheckTooShortAudioFiles : ICheck { private const int ms_threshold = 25; - private const int min_bytes_threshold = 100; public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Audio, "Too short audio files"); public IEnumerable PossibleTemplates => new IssueTemplate[] { new IssueTemplateTooShort(this), - new IssueTemplateBadFormat(this) }; public IEnumerable Run(BeatmapVerifierContext context) @@ -39,15 +37,7 @@ namespace osu.Game.Rulesets.Edit.Checks var fileCallbacks = new FileCallbacks(new DataStreamFileProcedures(data)); int decodeStream = Bass.CreateStream(StreamSystem.NoBuffer, BassFlags.Decode | BassFlags.Prescan, fileCallbacks.Callbacks, fileCallbacks.Handle); - if (decodeStream == 0) - { - // If the file is not likely to be properly parsed by Bass, we don't produce Error issues about it. - // Image files and audio files devoid of audio data both fail, for example, but neither would be issues in this check. - if (AudioCheckUtils.HasAudioExtension(file.Filename) && probablyHasAudioData(data)) - yield return new IssueTemplateBadFormat(this).Create(file.Filename); - - continue; - } + if (decodeStream == 0) continue; long length = Bass.ChannelGetLength(decodeStream); double ms = Bass.ChannelBytes2Seconds(decodeStream, length) * 1000; @@ -60,8 +50,6 @@ namespace osu.Game.Rulesets.Edit.Checks } } - private bool probablyHasAudioData(Stream data) => data.Length > min_bytes_threshold; - public class IssueTemplateTooShort : IssueTemplate { public IssueTemplateTooShort(ICheck check) @@ -71,15 +59,5 @@ namespace osu.Game.Rulesets.Edit.Checks public Issue Create(string filename, double ms) => new Issue(this, filename, ms, ms_threshold); } - - public class IssueTemplateBadFormat : IssueTemplate - { - public IssueTemplateBadFormat(ICheck check) - : base(check, IssueType.Error, "Could not check whether \"{0}\" is too short (code \"{1}\").") - { - } - - public Issue Create(string filename) => new Issue(this, filename, Bass.LastError); - } } } From c4bf03e6400d5047d2ebf916a7bd532334fff19c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 16 Apr 2024 12:42:12 +0200 Subject: [PATCH 3662/4092] Add failing test --- .../TestSceneResume.cs | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneResume.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneResume.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneResume.cs new file mode 100644 index 0000000000..023016c32d --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneResume.cs @@ -0,0 +1,69 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Screens.Play; +using osu.Game.Tests.Visual; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public partial class TestSceneResume : PlayerTestScene + { + protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); + + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(true, false, AllowBackwardsSeeks); + + [Test] + public void TestPauseViaKeyboard() + { + AddStep("move mouse to center", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.Centre)); + AddUntilStep("wait for gameplay start", () => Player.LocalUserPlaying.Value); + AddStep("press escape", () => InputManager.PressKey(Key.Escape)); + AddUntilStep("wait for pause overlay", () => Player.ChildrenOfType().Single().State.Value, () => Is.EqualTo(Visibility.Visible)); + AddStep("release escape", () => InputManager.ReleaseKey(Key.Escape)); + AddStep("resume", () => + { + InputManager.Key(Key.Down); + InputManager.Key(Key.Space); + }); + AddUntilStep("pause overlay present", () => Player.DrawableRuleset.ResumeOverlay.State.Value, () => Is.EqualTo(Visibility.Visible)); + } + + [Test] + public void TestPauseViaKeyboardWhenMouseOutsidePlayfield() + { + AddStep("move mouse outside playfield", () => InputManager.MoveMouseTo(Player.DrawableRuleset.Playfield.ScreenSpaceDrawQuad.BottomRight + new Vector2(1))); + AddUntilStep("wait for gameplay start", () => Player.LocalUserPlaying.Value); + AddStep("press escape", () => InputManager.PressKey(Key.Escape)); + AddUntilStep("wait for pause overlay", () => Player.ChildrenOfType().Single().State.Value, () => Is.EqualTo(Visibility.Visible)); + AddStep("release escape", () => InputManager.ReleaseKey(Key.Escape)); + AddStep("resume", () => + { + InputManager.Key(Key.Down); + InputManager.Key(Key.Space); + }); + AddUntilStep("pause overlay present", () => Player.DrawableRuleset.ResumeOverlay.State.Value, () => Is.EqualTo(Visibility.Visible)); + } + + [Test] + public void TestPauseViaKeyboardWhenMouseOutsideScreen() + { + AddStep("move mouse outside playfield", () => InputManager.MoveMouseTo(new Vector2(-20))); + AddUntilStep("wait for gameplay start", () => Player.LocalUserPlaying.Value); + AddStep("press escape", () => InputManager.PressKey(Key.Escape)); + AddUntilStep("wait for pause overlay", () => Player.ChildrenOfType().Single().State.Value, () => Is.EqualTo(Visibility.Visible)); + AddStep("release escape", () => InputManager.ReleaseKey(Key.Escape)); + AddStep("resume", () => + { + InputManager.Key(Key.Down); + InputManager.Key(Key.Space); + }); + AddUntilStep("pause overlay not present", () => Player.DrawableRuleset.ResumeOverlay.State.Value, () => Is.EqualTo(Visibility.Hidden)); + } + } +} From f9873968a5f06fa355b074f834fad8e9579a3ab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 16 Apr 2024 13:37:12 +0200 Subject: [PATCH 3663/4092] Apply NRT in `OsuResumeOverlay` --- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index adc7bd97ff..19d8a94f0a 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -19,12 +17,12 @@ namespace osu.Game.Rulesets.Osu.UI { public partial class OsuResumeOverlay : ResumeOverlay { - private Container cursorScaleContainer; - private OsuClickToResumeCursor clickToResumeCursor; + private Container cursorScaleContainer = null!; + private OsuClickToResumeCursor clickToResumeCursor = null!; - private OsuCursorContainer localCursorContainer; + private OsuCursorContainer? localCursorContainer; - public override CursorContainer LocalCursor => State.Value == Visibility.Visible ? localCursorContainer : null; + public override CursorContainer? LocalCursor => State.Value == Visibility.Visible ? localCursorContainer : null; protected override LocalisableString Message => "Click the orange cursor to resume"; @@ -71,8 +69,8 @@ namespace osu.Game.Rulesets.Osu.UI { public override bool HandlePositionalInput => true; - public Action ResumeRequested; - private Container scaleTransitionContainer; + public Action? ResumeRequested; + private Container scaleTransitionContainer = null!; public OsuClickToResumeCursor() { From e5e345712efea6467ae895219cad53a461403672 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 16 Apr 2024 13:24:30 +0200 Subject: [PATCH 3664/4092] Fix resume overlay not appearing after pausing inside window but outside of actual playfield area Related to https://github.com/ppy/osu/discussions/27871 (although does not actually fix the issue with the pause button, _if_ it is to be considered an issue - the problem there is that the gameplay cursor gets hidden, so the other condition in the modified check takes over). Regressed in https://github.com/ppy/osu/commit/bce3bd55e5a863e52f41598c306a248a79638843. Reasoning for breakage is silent change in `this` when moving the `Contains()` check (`DrawableRuleset` will encompass screen bounds, while `OsuResumeOverlay` is only as big as the actual playfield). --- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index 19d8a94f0a..a04ea80640 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -10,6 +10,7 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Rulesets.Osu.UI.Cursor; +using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; using osuTK.Graphics; @@ -26,6 +27,9 @@ namespace osu.Game.Rulesets.Osu.UI protected override LocalisableString Message => "Click the orange cursor to resume"; + [Resolved] + private DrawableRuleset? drawableRuleset { get; set; } + [BackgroundDependencyLoader] private void load() { @@ -38,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.UI protected override void PopIn() { // Can't display if the cursor is outside the window. - if (GameplayCursor.LastFrameState == Visibility.Hidden || !Contains(GameplayCursor.ActiveCursor.ScreenSpaceDrawQuad.Centre)) + if (GameplayCursor.LastFrameState == Visibility.Hidden || drawableRuleset?.Contains(GameplayCursor.ActiveCursor.ScreenSpaceDrawQuad.Centre) == false) { Resume(); return; From 6c943681b0518848b5ace9755835e16996c505aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 16 Apr 2024 16:07:56 +0200 Subject: [PATCH 3665/4092] Fix preview tracks playing after their owning overlay has hidden RFC. Closes https://github.com/ppy/osu/issues/27883. The idea here is that `PopOut()` is called _when the hide is requested_, so once an overlay trigger would hide, the overlay would `StopAnyPlaying()`, but because of async load things, the actual track would start playing after that but before the overlay has fully hidden. (That last part is significant because after the overlay has fully hidden, schedules save the day.) Due to the loose coupling between `PreviewTrackManager` and `IPreviewTrackOwner` there's really no easy way to handle this locally to the usages of the preview tracks. Heck, `PreviewTrackManager` doesn't really know which preview track owner is to be considered _present_ at any time, it just kinda works on vibes based on DI until the owner tells all of its preview tracks to stop. This solution causes the preview tracks to stop a little bit later but maybe that's fine? Just trying to not overthink the issue is all. No tests because this is going to suck to test automatically while it is pretty easy to test manually (got it in a few tries on master). The issue also mentions that the track can sometimes resume playing after the overlay is pulled up again, but I don't see that as a problem necessarily, and even if it was, it's not going to be that easy to address due to the aforementioned loose coupling - to fix that, play buttons would have to know who is the current preview track owner and cancel schedules upon determining that their preview track owner has gone away. --- osu.Game/Overlays/WaveOverlayContainer.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/WaveOverlayContainer.cs b/osu.Game/Overlays/WaveOverlayContainer.cs index 0295ff467a..7744db5dd5 100644 --- a/osu.Game/Overlays/WaveOverlayContainer.cs +++ b/osu.Game/Overlays/WaveOverlayContainer.cs @@ -40,10 +40,12 @@ namespace osu.Game.Overlays protected override void PopOut() { - base.PopOut(); - Waves.Hide(); - this.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.InQuint); + this.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.InQuint) + // base call is responsible for stopping preview tracks. + // delay it until the fade has concluded to ensure that nothing inside the overlay has triggered + // another preview track playback in the meantime, leaving an "orphaned" preview playing. + .OnComplete(_ => base.PopOut()); } } } From a386068ed3cdad2b7f3009780c7e6f7709778119 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 17 Apr 2024 08:51:53 +0200 Subject: [PATCH 3666/4092] Add `ScoreInfo.TotalScoreWithoutMods` --- osu.Game/Database/RealmAccess.cs | 3 ++- osu.Game/Scoring/ScoreInfo.cs | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 167d170c81..12a71f3510 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -91,8 +91,9 @@ namespace osu.Game.Database /// 38 2023-12-10 Add EndTimeObjectCount and TotalObjectCount to BeatmapInfo. /// 39 2023-12-19 Migrate any EndTimeObjectCount and TotalObjectCount values of 0 to -1 to better identify non-calculated values. /// 40 2023-12-21 Add ScoreInfo.Version to keep track of which build scores were set on. + /// 41 2024-04-17 Add ScoreInfo.TotalScoreWithoutMods for future mod multiplier rebalances. /// - private const int schema_version = 40; + private const int schema_version = 41; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index fd98107792..92c18c9c1e 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -65,8 +65,19 @@ namespace osu.Game.Scoring public bool DeletePending { get; set; } + /// + /// The total number of points awarded for the score. + /// public long TotalScore { get; set; } + /// + /// The total number of points awarded for the score without including mod multipliers. + /// + /// + /// The purpose of this property is to enable future lossless rebalances of mod multipliers. + /// + public long TotalScoreWithoutMods { get; set; } + /// /// The version of processing applied to calculate total score as stored in the database. /// If this does not match , From 6b0d577f0bb3943cc66cd5bff118b52315ea7418 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 17 Apr 2024 08:52:27 +0200 Subject: [PATCH 3667/4092] Add backmigration of `TotalScoreWithoutMods` for existing scores --- osu.Game/Database/RealmAccess.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 12a71f3510..ee8eb4491b 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -1109,6 +1109,19 @@ namespace osu.Game.Database } } + break; + + case 41: + foreach (var score in migration.NewRealm.All()) + { + double modMultiplier = 1; + + foreach (var mod in score.Mods) + modMultiplier *= mod.ScoreMultiplier; + + score.TotalScoreWithoutMods = (long)Math.Round(score.TotalScore / modMultiplier); + } + break; } From e0178802b81f5a62ad0ddb8ed02cab7d98d5ebd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 17 Apr 2024 08:52:47 +0200 Subject: [PATCH 3668/4092] Populate `TotalScoreWithoutMods` on scores set locally --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 9d12daad04..70d7f0fe37 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -56,6 +56,14 @@ namespace osu.Game.Rulesets.Scoring /// public readonly BindableLong TotalScore = new BindableLong { MinValue = 0 }; + /// + /// The total number of points awarded for the score without including mod multipliers. + /// + /// + /// The purpose of this property is to enable future lossless rebalances of mod multipliers. + /// + public readonly BindableLong TotalScoreWithoutMods = new BindableLong { MinValue = 0 }; + /// /// The current accuracy. /// @@ -363,7 +371,8 @@ namespace osu.Game.Rulesets.Scoring double comboProgress = maximumComboPortion > 0 ? currentComboPortion / maximumComboPortion : 1; double accuracyProcess = maximumAccuracyJudgementCount > 0 ? (double)currentAccuracyJudgementCount / maximumAccuracyJudgementCount : 1; - TotalScore.Value = (long)Math.Round(ComputeTotalScore(comboProgress, accuracyProcess, currentBonusPortion) * scoreMultiplier); + TotalScoreWithoutMods.Value = (long)Math.Round(ComputeTotalScore(comboProgress, accuracyProcess, currentBonusPortion)); + TotalScore.Value = (long)Math.Round(TotalScoreWithoutMods.Value * scoreMultiplier); } private void updateRank() @@ -446,6 +455,7 @@ namespace osu.Game.Rulesets.Scoring score.MaximumStatistics[result] = MaximumResultCounts.GetValueOrDefault(result); // Populate total score after everything else. + score.TotalScoreWithoutMods = TotalScoreWithoutMods.Value; score.TotalScore = TotalScore.Value; } From 2f1a4cdaa4d46c0dfd230f618c57563cee6bb654 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 17 Apr 2024 09:08:15 +0200 Subject: [PATCH 3669/4092] Export and import `TotalScoreWithoutMods` to replays (or recalculate if it missing) --- .../Formats/LegacyScoreDecoderTest.cs | 74 +++++++++++++++++++ osu.Game/Database/RealmAccess.cs | 9 +-- .../Legacy/LegacyReplaySoloScoreInfo.cs | 4 + osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 15 ++++ 4 files changed, 94 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index 43e471320e..2f8cb9a3b3 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -412,6 +412,80 @@ namespace osu.Game.Tests.Beatmaps.Formats }); } + [Test] + public void TestTotalScoreWithoutModsReadIfPresent() + { + var ruleset = new OsuRuleset().RulesetInfo; + + var scoreInfo = TestResources.CreateTestScoreInfo(ruleset); + scoreInfo.Mods = new Mod[] + { + new OsuModDoubleTime { SpeedChange = { Value = 1.1 } } + }; + scoreInfo.OnlineID = 123123; + scoreInfo.ClientVersion = "2023.1221.0"; + scoreInfo.TotalScoreWithoutMods = 1_000_000; + scoreInfo.TotalScore = 1_020_000; + + var beatmap = new TestBeatmap(ruleset); + var score = new Score + { + ScoreInfo = scoreInfo, + Replay = new Replay + { + Frames = new List + { + new OsuReplayFrame(2000, OsuPlayfield.BASE_SIZE / 2, OsuAction.LeftButton) + } + } + }; + + var decodedAfterEncode = encodeThenDecode(LegacyBeatmapDecoder.LATEST_VERSION, score, beatmap); + + Assert.Multiple(() => + { + Assert.That(decodedAfterEncode.ScoreInfo.TotalScoreWithoutMods, Is.EqualTo(1_000_000)); + Assert.That(decodedAfterEncode.ScoreInfo.TotalScore, Is.EqualTo(1_020_000)); + }); + } + + [Test] + public void TestTotalScoreWithoutModsBackwardsPopulatedIfMissing() + { + var ruleset = new OsuRuleset().RulesetInfo; + + var scoreInfo = TestResources.CreateTestScoreInfo(ruleset); + scoreInfo.Mods = new Mod[] + { + new OsuModDoubleTime { SpeedChange = { Value = 1.1 } } + }; + scoreInfo.OnlineID = 123123; + scoreInfo.ClientVersion = "2023.1221.0"; + scoreInfo.TotalScoreWithoutMods = 0; + scoreInfo.TotalScore = 1_020_000; + + var beatmap = new TestBeatmap(ruleset); + var score = new Score + { + ScoreInfo = scoreInfo, + Replay = new Replay + { + Frames = new List + { + new OsuReplayFrame(2000, OsuPlayfield.BASE_SIZE / 2, OsuAction.LeftButton) + } + } + }; + + var decodedAfterEncode = encodeThenDecode(LegacyBeatmapDecoder.LATEST_VERSION, score, beatmap); + + Assert.Multiple(() => + { + Assert.That(decodedAfterEncode.ScoreInfo.TotalScoreWithoutMods, Is.EqualTo(1_000_000)); + Assert.That(decodedAfterEncode.ScoreInfo.TotalScore, Is.EqualTo(1_020_000)); + }); + } + private static Score encodeThenDecode(int beatmapVersion, Score score, TestBeatmap beatmap) { var encodeStream = new MemoryStream(); diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index ee8eb4491b..c075c3d82b 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -1113,14 +1113,7 @@ namespace osu.Game.Database case 41: foreach (var score in migration.NewRealm.All()) - { - double modMultiplier = 1; - - foreach (var mod in score.Mods) - modMultiplier *= mod.ScoreMultiplier; - - score.TotalScoreWithoutMods = (long)Math.Round(score.TotalScore / modMultiplier); - } + LegacyScoreDecoder.PopulateTotalScoreWithoutMods(score); break; } diff --git a/osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs b/osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs index afdcef1d21..c11e18462a 100644 --- a/osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs +++ b/osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs @@ -38,6 +38,9 @@ namespace osu.Game.Scoring.Legacy [JsonProperty("client_version")] public string ClientVersion = string.Empty; + [JsonProperty("total_score_without_mods")] + public long? TotalScoreWithoutMods { get; set; } + public static LegacyReplaySoloScoreInfo FromScore(ScoreInfo score) => new LegacyReplaySoloScoreInfo { OnlineID = score.OnlineID, @@ -45,6 +48,7 @@ namespace osu.Game.Scoring.Legacy Statistics = score.Statistics.Where(kvp => kvp.Value != 0).ToDictionary(), MaximumStatistics = score.MaximumStatistics.Where(kvp => kvp.Value != 0).ToDictionary(), ClientVersion = score.ClientVersion, + TotalScoreWithoutMods = score.TotalScoreWithoutMods > 0 ? score.TotalScoreWithoutMods : null, }; } } diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index 65e2c02655..2358c0dfec 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -129,6 +129,11 @@ namespace osu.Game.Scoring.Legacy score.ScoreInfo.MaximumStatistics = readScore.MaximumStatistics; score.ScoreInfo.Mods = readScore.Mods.Select(m => m.ToMod(currentRuleset)).ToArray(); score.ScoreInfo.ClientVersion = readScore.ClientVersion; + + if (readScore.TotalScoreWithoutMods is long totalScoreWithoutMods) + score.ScoreInfo.TotalScoreWithoutMods = totalScoreWithoutMods; + else + PopulateTotalScoreWithoutMods(score.ScoreInfo); }); } } @@ -237,6 +242,16 @@ namespace osu.Game.Scoring.Legacy #pragma warning restore CS0618 } + public static void PopulateTotalScoreWithoutMods(ScoreInfo score) + { + double modMultiplier = 1; + + foreach (var mod in score.Mods) + modMultiplier *= mod.ScoreMultiplier; + + score.TotalScoreWithoutMods = (long)Math.Round(score.TotalScore / modMultiplier); + } + private void readLegacyReplay(Replay replay, StreamReader reader) { float lastTime = beatmapOffset; From 18bb81e7a7e4de6cf15ce0674ae44ef9f041ff84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 17 Apr 2024 09:11:47 +0200 Subject: [PATCH 3670/4092] Populate total score without mods when performing standardised score migration --- osu.Game/Database/StandardisedScoreMigrationTools.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index 6f2f8d64fa..7d09ebdb40 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -16,6 +16,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring.Legacy; using osu.Game.Scoring; +using osu.Game.Scoring.Legacy; namespace osu.Game.Database { @@ -248,6 +249,7 @@ namespace osu.Game.Database score.Accuracy = computeAccuracy(score, scoreProcessor); score.Rank = computeRank(score, scoreProcessor); score.TotalScore = convertFromLegacyTotalScore(score, ruleset, beatmap); + LegacyScoreDecoder.PopulateTotalScoreWithoutMods(score); } /// From e11e9fe14ff41af53eb6ccbbf9eb79952d045989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 17 Apr 2024 09:15:50 +0200 Subject: [PATCH 3671/4092] Add `TotalScoreWithoutMods` to `SoloScoreInfo` End goal being storing it server-side. --- osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index 64caddb2fc..36f1311f9d 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -33,6 +33,9 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty("total_score")] public long TotalScore { get; set; } + [JsonProperty("total_score_without_mods")] + public long TotalScoreWithoutMods { get; set; } + [JsonProperty("accuracy")] public double Accuracy { get; set; } @@ -206,6 +209,7 @@ namespace osu.Game.Online.API.Requests.Responses Ruleset = new RulesetInfo { OnlineID = RulesetID }, Passed = Passed, TotalScore = TotalScore, + TotalScoreWithoutMods = TotalScoreWithoutMods, LegacyTotalScore = LegacyTotalScore, Accuracy = Accuracy, MaxCombo = MaxCombo, @@ -239,6 +243,7 @@ namespace osu.Game.Online.API.Requests.Responses { Rank = score.Rank, TotalScore = score.TotalScore, + TotalScoreWithoutMods = score.TotalScoreWithoutMods, Accuracy = score.Accuracy, PP = score.PP, MaxCombo = score.MaxCombo, From 03e13ddc3095e8713a90dab9bb5f672b0ae75d4d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 17 Apr 2024 17:10:19 +0900 Subject: [PATCH 3672/4092] Globally silence Discord RPC registration failures --- osu.Desktop/DiscordRichPresence.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index f1c796d0cd..74ebd38f2c 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -6,7 +6,6 @@ using System.Text; using DiscordRPC; using DiscordRPC.Message; using Newtonsoft.Json; -using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.ObjectExtensions; @@ -80,14 +79,20 @@ namespace osu.Desktop client.OnReady += onReady; client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Message} ({e.Code})", LoggingTarget.Network, LogLevel.Error); - // A URI scheme is required to support game invitations, as well as informing Discord of the game executable path to support launching the game when a user clicks on join/spectate. - // The library doesn't properly support URI registration when ran from an app bundle on macOS. - if (!RuntimeInfo.IsApple) + try { client.RegisterUriScheme(); client.Subscribe(EventType.Join); client.OnJoin += onJoin; } + catch (Exception ex) + { + // This is known to fail in at least the following sandboxed environments: + // - macOS (when packaged as an app bundle) + // - flatpak (see: https://github.com/flathub/sh.ppy.osu/issues/170) + // There is currently no better way to do this offered by Discord, so the best we can do is simply ignore it for now. + Logger.Log($"Failed to register Discord URI scheme: {ex}"); + } client.Initialize(); } From 2a3ae6bce178124e65ca14d8ea62e82a6aa05ded Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Apr 2024 02:34:41 +0300 Subject: [PATCH 3673/4092] Update all `TabItem` implementations to play select sample on `OnActivatedByUser` --- .../Graphics/UserInterface/OsuTabControl.cs | 24 ++++++++++++------- .../Graphics/UserInterface/PageTabControl.cs | 14 ++++++++++- .../BeatmapListingCardSizeTabControl.cs | 12 ++++++++-- .../Overlays/BeatmapListing/FilterTabItem.cs | 12 ++++++++-- .../OverlayPanelDisplayStyleControl.cs | 14 ++++++++++- osu.Game/Overlays/OverlayRulesetTabItem.cs | 14 ++++++++++- osu.Game/Overlays/OverlayStreamItem.cs | 12 ++++++++-- osu.Game/Overlays/OverlayTabControl.cs | 14 ++++++++++- .../Toolbar/ToolbarRulesetSelector.cs | 4 ---- .../Toolbar/ToolbarRulesetTabButton.cs | 12 ++++++++++ .../Match/Components/MatchTypePicker.cs | 11 +++++++-- 11 files changed, 119 insertions(+), 24 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index f24977927f..5ce384c53c 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -8,6 +8,8 @@ using System.Linq; using osuTK; using osuTK.Graphics; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; @@ -143,13 +145,6 @@ namespace osu.Game.Graphics.UserInterface FadeUnhovered(); } - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - if (accentColour == default) - AccentColour = colours.Blue; - } - public OsuTabItem(T value) : base(value) { @@ -196,10 +191,21 @@ namespace osu.Game.Graphics.UserInterface Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, }, - new HoverClickSounds(HoverSampleSet.TabSelect) + new HoverSounds(HoverSampleSet.TabSelect) }; } + private Sample selectSample; + + [BackgroundDependencyLoader] + private void load(OsuColour colours, AudioManager audio) + { + if (accentColour == default) + AccentColour = colours.Blue; + + selectSample = audio.Samples.Get(@"UI/tabselect-select"); + } + protected override void OnActivated() { Text.Font = Text.Font.With(weight: FontWeight.Bold); @@ -211,6 +217,8 @@ namespace osu.Game.Graphics.UserInterface Text.Font = Text.Font.With(weight: FontWeight.Medium); FadeUnhovered(); } + + protected override void OnActivatedByUser() => selectSample.Play(); } } } diff --git a/osu.Game/Graphics/UserInterface/PageTabControl.cs b/osu.Game/Graphics/UserInterface/PageTabControl.cs index 2fe8acfbd5..44c659f945 100644 --- a/osu.Game/Graphics/UserInterface/PageTabControl.cs +++ b/osu.Game/Graphics/UserInterface/PageTabControl.cs @@ -7,6 +7,8 @@ using System; using osuTK; using osuTK.Graphics; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; @@ -53,6 +55,8 @@ namespace osu.Game.Graphics.UserInterface } } + private Sample selectSample = null!; + public PageTabItem(T value) : base(value) { @@ -78,12 +82,18 @@ namespace osu.Game.Graphics.UserInterface Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, }, - new HoverClickSounds(HoverSampleSet.TabSelect) + new HoverSounds(HoverSampleSet.TabSelect) }; Active.BindValueChanged(active => Text.Font = Text.Font.With(Typeface.Torus, weight: active.NewValue ? FontWeight.Bold : FontWeight.Medium), true); } + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + selectSample = audio.Samples.Get(@"UI/tabselect-select"); + } + protected virtual LocalisableString CreateText() => (Value as Enum)?.GetLocalisableDescription() ?? Value.ToString(); protected override bool OnHover(HoverEvent e) @@ -112,6 +122,8 @@ namespace osu.Game.Graphics.UserInterface protected override void OnActivated() => slideActive(); protected override void OnDeactivated() => slideInactive(); + + protected override void OnActivatedByUser() => selectSample.Play(); } } } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingCardSizeTabControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingCardSizeTabControl.cs index 9cd0031e3d..63a533c92e 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingCardSizeTabControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingCardSizeTabControl.cs @@ -5,6 +5,8 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -47,13 +49,15 @@ namespace osu.Game.Overlays.BeatmapListing [Resolved] private OverlayColourProvider colourProvider { get; set; } + private Sample selectSample = null!; + public TabItem(BeatmapCardSize value) : base(value) { } [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio) { AutoSizeAxes = Axes.Both; Masking = true; @@ -79,8 +83,10 @@ namespace osu.Game.Overlays.BeatmapListing Icon = getIconForCardSize(Value) } }, - new HoverClickSounds(HoverSampleSet.TabSelect) + new HoverSounds(HoverSampleSet.TabSelect) }; + + selectSample = audio.Samples.Get(@"UI/tabselect-select"); } private static IconUsage getIconForCardSize(BeatmapCardSize cardSize) @@ -111,6 +117,8 @@ namespace osu.Game.Overlays.BeatmapListing updateState(); } + protected override void OnActivatedByUser() => selectSample.Play(); + protected override void OnDeactivated() { if (IsLoaded) diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index 831cf812ff..89bf61dd18 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -5,6 +5,8 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; @@ -24,13 +26,15 @@ namespace osu.Game.Overlays.BeatmapListing private OsuSpriteText text; + private Sample selectSample = null!; + public FilterTabItem(T value) : base(value) { } [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio) { AutoSizeAxes = Axes.Both; AddRangeInternal(new Drawable[] @@ -40,10 +44,12 @@ namespace osu.Game.Overlays.BeatmapListing Font = OsuFont.GetFont(size: 13, weight: FontWeight.Regular), Text = LabelFor(Value) }, - new HoverClickSounds(HoverSampleSet.TabSelect) + new HoverSounds(HoverSampleSet.TabSelect) }); Enabled.Value = true; + + selectSample = audio.Samples.Get(@"UI/tabselect-select"); } protected override void LoadComplete() @@ -71,6 +77,8 @@ namespace osu.Game.Overlays.BeatmapListing protected override void OnDeactivated() => UpdateState(); + protected override void OnActivatedByUser() => selectSample.Play(); + /// /// Returns the label text to be used for the supplied . /// diff --git a/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs b/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs index d7d6bd4a2a..c2bea0ed91 100644 --- a/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs +++ b/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs @@ -11,6 +11,8 @@ using osuTK; using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osuTK.Graphics; using osu.Framework.Graphics.Cursor; using osu.Framework.Localisation; @@ -65,6 +67,8 @@ namespace osu.Game.Overlays private readonly SpriteIcon icon; + private Sample selectSample = null!; + public PanelDisplayTabItem(OverlayPanelDisplayStyle value) : base(value) { @@ -78,14 +82,22 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fit }, - new HoverClickSounds() + new HoverSounds(HoverSampleSet.TabSelect) }); } + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + selectSample = audio.Samples.Get(@"UI/tabselect-select"); + } + protected override void OnActivated() => updateState(); protected override void OnDeactivated() => updateState(); + protected override void OnActivatedByUser() => selectSample.Play(); + protected override bool OnHover(HoverEvent e) { updateState(); diff --git a/osu.Game/Overlays/OverlayRulesetTabItem.cs b/osu.Game/Overlays/OverlayRulesetTabItem.cs index 6d318820b3..b245486adf 100644 --- a/osu.Game/Overlays/OverlayRulesetTabItem.cs +++ b/osu.Game/Overlays/OverlayRulesetTabItem.cs @@ -10,6 +10,8 @@ using osu.Game.Rulesets; using osuTK.Graphics; using osuTK; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Graphics.Cursor; using osu.Framework.Localisation; using osu.Game.Graphics.Containers; @@ -39,6 +41,8 @@ namespace osu.Game.Overlays public LocalisableString TooltipText => Value.Name; + private Sample selectSample = null!; + public OverlayRulesetTabItem(RulesetInfo value) : base(value) { @@ -59,12 +63,18 @@ namespace osu.Game.Overlays Icon = value.CreateInstance().CreateIcon(), }, }, - new HoverClickSounds() + new HoverSounds(HoverSampleSet.TabSelect) }); Enabled.Value = true; } + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + selectSample = audio.Samples.Get(@"UI/tabselect-select"); + } + protected override void LoadComplete() { base.LoadComplete(); @@ -90,6 +100,8 @@ namespace osu.Game.Overlays protected override void OnDeactivated() => updateState(); + protected override void OnActivatedByUser() => selectSample.Play(); + private void updateState() { AccentColour = Enabled.Value ? getActiveColour() : colourProvider.Foreground1; diff --git a/osu.Game/Overlays/OverlayStreamItem.cs b/osu.Game/Overlays/OverlayStreamItem.cs index 45181c13e4..f0ae0b41fc 100644 --- a/osu.Game/Overlays/OverlayStreamItem.cs +++ b/osu.Game/Overlays/OverlayStreamItem.cs @@ -11,6 +11,8 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Framework.Graphics.Sprites; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Game.Graphics.Sprites; using osu.Game.Graphics; using osuTK.Graphics; @@ -49,8 +51,10 @@ namespace osu.Game.Overlays Margin = new MarginPadding(PADDING); } + private Sample selectSample; + [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider, OsuColour colours) + private void load(OverlayColourProvider colourProvider, OsuColour colours, AudioManager audio) { AddRange(new Drawable[] { @@ -87,9 +91,11 @@ namespace osu.Game.Overlays CollapsedSize = 2, Expanded = true }, - new HoverClickSounds() + new HoverSounds(HoverSampleSet.TabSelect) }); + selectSample = audio.Samples.Get(@"UI/tabselect-select"); + SelectedItem.BindValueChanged(_ => updateState(), true); } @@ -105,6 +111,8 @@ namespace osu.Game.Overlays protected override void OnDeactivated() => updateState(); + protected override void OnActivatedByUser() => selectSample.Play(); + protected override bool OnHover(HoverEvent e) { updateState(); diff --git a/osu.Game/Overlays/OverlayTabControl.cs b/osu.Game/Overlays/OverlayTabControl.cs index 884e31868f..a27caa13f1 100644 --- a/osu.Game/Overlays/OverlayTabControl.cs +++ b/osu.Game/Overlays/OverlayTabControl.cs @@ -4,6 +4,8 @@ #nullable disable using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; @@ -80,6 +82,8 @@ namespace osu.Game.Overlays } } + private Sample selectSample = null!; + public OverlayTabItem(T value) : base(value) { @@ -101,10 +105,16 @@ namespace osu.Game.Overlays ExpandedSize = 5f, CollapsedSize = 0 }, - new HoverClickSounds(HoverSampleSet.TabSelect) + new HoverSounds(HoverSampleSet.TabSelect) }; } + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + selectSample = audio.Samples.Get(@"UI/tabselect-select"); + } + protected override bool OnHover(HoverEvent e) { base.OnHover(e); @@ -136,6 +146,8 @@ namespace osu.Game.Overlays Text.Font = Text.Font.With(weight: FontWeight.Medium); } + protected override void OnActivatedByUser() => selectSample.Play(); + private void updateState() { if (Active.Value) diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs index 723c24597a..518152e455 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs @@ -88,10 +88,6 @@ namespace osu.Game.Overlays.Toolbar if (SelectedTab != null) { ModeButtonLine.MoveToX(SelectedTab.DrawPosition.X, !hasInitialPosition ? 0 : 500, Easing.OutElasticQuarter); - - if (hasInitialPosition) - selectionSamples[SelectedTab.Value.ShortName]?.Play(); - hasInitialPosition = true; } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs index 3287ac6eaa..0315bede64 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; @@ -17,6 +19,8 @@ namespace osu.Game.Overlays.Toolbar { private readonly RulesetButton ruleset; + private Sample? selectSample; + public ToolbarRulesetTabButton(RulesetInfo value) : base(value) { @@ -34,10 +38,18 @@ namespace osu.Game.Overlays.Toolbar ruleset.SetIcon(rInstance.CreateIcon()); } + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + selectSample = audio.Samples.Get($@"UI/ruleset-select-{Value.ShortName}"); + } + protected override void OnActivated() => ruleset.Active = true; protected override void OnDeactivated() => ruleset.Active = false; + protected override void OnActivatedByUser() => selectSample?.Play(); + private partial class RulesetButton : ToolbarButton { protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(); diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchTypePicker.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchTypePicker.cs index 995fce085e..477336e8ea 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchTypePicker.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchTypePicker.cs @@ -4,6 +4,8 @@ #nullable disable using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -78,14 +80,17 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components }, }, }, - new HoverClickSounds(), + new HoverSounds(HoverSampleSet.TabSelect), }; } + private Sample selectSample; + [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, AudioManager audio) { selection.Colour = colours.Yellow; + selectSample = audio.Samples.Get(@"UI/tabselect-select"); } protected override bool OnHover(HoverEvent e) @@ -109,6 +114,8 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components { selection.FadeOut(transition_duration, Easing.OutQuint); } + + protected override void OnActivatedByUser() => selectSample.Play(); } } } From 50a21fb7273489f682b680877ad69200a2d4c227 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Apr 2024 02:38:39 +0300 Subject: [PATCH 3674/4092] Change `ToolbarRulesetSelector` to use `SelectItem` to trigger new sound feedback path --- osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs index 518152e455..d49c340ed4 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs @@ -3,11 +3,8 @@ #nullable disable -using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -24,8 +21,6 @@ namespace osu.Game.Overlays.Toolbar { protected Drawable ModeButtonLine { get; private set; } - private readonly Dictionary selectionSamples = new Dictionary(); - public ToolbarRulesetSelector() { RelativeSizeAxes = Axes.Y; @@ -33,7 +28,7 @@ namespace osu.Game.Overlays.Toolbar } [BackgroundDependencyLoader] - private void load(AudioManager audio) + private void load() { AddRangeInternal(new[] { @@ -59,9 +54,6 @@ namespace osu.Game.Overlays.Toolbar } }, }); - - foreach (var ruleset in Rulesets.AvailableRulesets) - selectionSamples[ruleset.ShortName] = audio.Samples.Get($"UI/ruleset-select-{ruleset.ShortName}"); } protected override void LoadComplete() @@ -117,7 +109,7 @@ namespace osu.Game.Overlays.Toolbar RulesetInfo found = Rulesets.AvailableRulesets.ElementAtOrDefault(requested); if (found != null) - Current.Value = found; + SelectItem(found); return true; } From 42892acc1adaf8be01897de0c4e5fef7b6319a4d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Apr 2024 02:42:24 +0300 Subject: [PATCH 3675/4092] Fix multiple selection filter tab items no longer playing sounds These tab items are not managed by a `TabControl`, activation events are manually served by the class itself toggling `Active` when clicked. --- .../BeatmapSearchMultipleSelectionFilterRow.cs | 1 + osu.Game/Overlays/BeatmapListing/FilterTabItem.cs | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs index e59beb43ff..0a4c2b1e21 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs @@ -129,6 +129,7 @@ namespace osu.Game.Overlays.BeatmapListing { base.OnClick(e); Active.Toggle(); + SelectSample.Play(); return true; } } diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index 89bf61dd18..ee188d34ce 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -26,7 +26,7 @@ namespace osu.Game.Overlays.BeatmapListing private OsuSpriteText text; - private Sample selectSample = null!; + protected Sample SelectSample { get; private set; } = null!; public FilterTabItem(T value) : base(value) @@ -49,7 +49,7 @@ namespace osu.Game.Overlays.BeatmapListing Enabled.Value = true; - selectSample = audio.Samples.Get(@"UI/tabselect-select"); + SelectSample = audio.Samples.Get(@"UI/tabselect-select"); } protected override void LoadComplete() @@ -77,7 +77,7 @@ namespace osu.Game.Overlays.BeatmapListing protected override void OnDeactivated() => UpdateState(); - protected override void OnActivatedByUser() => selectSample.Play(); + protected override void OnActivatedByUser() => SelectSample.Play(); /// /// Returns the label text to be used for the supplied . From 9e692686760ea1f00cf661bc011da18e8b4b43a0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Apr 2024 02:48:32 +0300 Subject: [PATCH 3676/4092] Change editor screen selection logic to use `SelectItem` for sound feedback --- osu.Game/Screens/Edit/Editor.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index c1f6c02301..37f4b4f5be 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -372,7 +372,7 @@ namespace osu.Game.Screens.Edit } } }, - new EditorScreenSwitcherControl + screenSwitcher = new EditorScreenSwitcherControl { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, @@ -662,23 +662,23 @@ namespace osu.Game.Screens.Edit return true; case GlobalAction.EditorComposeMode: - Mode.Value = EditorScreenMode.Compose; + screenSwitcher.SelectItem(EditorScreenMode.Compose); return true; case GlobalAction.EditorDesignMode: - Mode.Value = EditorScreenMode.Design; + screenSwitcher.SelectItem(EditorScreenMode.Design); return true; case GlobalAction.EditorTimingMode: - Mode.Value = EditorScreenMode.Timing; + screenSwitcher.SelectItem(EditorScreenMode.Timing); return true; case GlobalAction.EditorSetupMode: - Mode.Value = EditorScreenMode.SongSetup; + screenSwitcher.SelectItem(EditorScreenMode.SongSetup); return true; case GlobalAction.EditorVerifyMode: - Mode.Value = EditorScreenMode.Verify; + screenSwitcher.SelectItem(EditorScreenMode.Verify); return true; case GlobalAction.EditorTestGameplay: @@ -959,6 +959,8 @@ namespace osu.Game.Screens.Edit [CanBeNull] private ScheduledDelegate playbackDisabledDebounce; + private EditorScreenSwitcherControl screenSwitcher; + private void updateSampleDisabledState() { bool shouldDisableSamples = clock.SeekingOrStopped.Value From 24e8b88320af93d257674452178e878253ccb4fc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Apr 2024 03:00:27 +0300 Subject: [PATCH 3677/4092] Add inline comment to the custom implementation of multiple-selection tab items --- .../BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs index 0a4c2b1e21..4bd25f6561 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs @@ -128,6 +128,9 @@ namespace osu.Game.Overlays.BeatmapListing protected override bool OnClick(ClickEvent e) { base.OnClick(e); + + // this tab item implementation is not managed by a TabControl, + // therefore we have to manually update Active state and play select sound when this tab item is clicked. Active.Toggle(); SelectSample.Play(); return true; From a7e043bdbe3b145b5e33c392e06061e9e71fc7b2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Apr 2024 03:08:29 +0300 Subject: [PATCH 3678/4092] Fix beatmap rulest selector playing sound for initial ruleset selection --- osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs index 426fbcdb8d..29744f27fc 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs @@ -21,7 +21,7 @@ namespace osu.Game.Overlays.BeatmapSet // propagate value to tab items first to enable only available rulesets. beatmapSet.Value = value; - SelectTab(TabContainer.TabItems.FirstOrDefault(t => t.Enabled.Value)); + Current.Value = TabContainer.TabItems.FirstOrDefault(t => t.Enabled.Value)?.Value; } } From 8d94f8d995cd34c2f9d7d6ab68a169b7e554c0f4 Mon Sep 17 00:00:00 2001 From: Arthur Araujo Date: Wed, 17 Apr 2024 08:14:19 -0300 Subject: [PATCH 3679/4092] Remove `BassFlags.Prescan` and free the decodeStream --- osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs | 7 ++++--- osu.Game/Rulesets/Edit/Checks/CheckSongFormat.cs | 4 +++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs b/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs index f65b89fc01..c71c7b0ef3 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Edit.Checks continue; var fileCallbacks = new FileCallbacks(new DataStreamFileProcedures(data)); - int decodeStream = Bass.CreateStream(StreamSystem.NoBuffer, BassFlags.Decode | BassFlags.Prescan, fileCallbacks.Callbacks, fileCallbacks.Handle); + int decodeStream = Bass.CreateStream(StreamSystem.NoBuffer, BassFlags.Decode, fileCallbacks.Callbacks, fileCallbacks.Handle); // If the format is not supported by BASS if (decodeStream == 0) @@ -61,9 +61,10 @@ namespace osu.Game.Rulesets.Edit.Checks var audioInfo = Bass.ChannelGetInfo(decodeStream); if (!allowedFormats.Contains(audioInfo.ChannelType)) - { yield return new IssueTemplateIncorrectFormat(this).Create(file.Filename, audioInfo.ChannelType.ToString()); - } + + + Bass.StreamFree(decodeStream); } } } diff --git a/osu.Game/Rulesets/Edit/Checks/CheckSongFormat.cs b/osu.Game/Rulesets/Edit/Checks/CheckSongFormat.cs index ae90dd96d5..9ed1350f56 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckSongFormat.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckSongFormat.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Edit.Checks if (data == null || data.Length <= 0) yield break; var fileCallbacks = new FileCallbacks(new DataStreamFileProcedures(data)); - int decodeStream = Bass.CreateStream(StreamSystem.NoBuffer, BassFlags.Decode | BassFlags.Prescan, fileCallbacks.Callbacks, fileCallbacks.Handle); + int decodeStream = Bass.CreateStream(StreamSystem.NoBuffer, BassFlags.Decode, fileCallbacks.Callbacks, fileCallbacks.Handle); // If the format is not supported by BASS if (decodeStream == 0) @@ -55,6 +55,8 @@ namespace osu.Game.Rulesets.Edit.Checks if (!allowedFormats.Contains(audioInfo.ChannelType)) yield return new IssueTemplateIncorrectFormat(this).Create(audioFile.Filename, audioInfo.ChannelType.ToString()); + + Bass.StreamFree(decodeStream); } } From ac03856ebdb8f8ffff860879263f16ab2564756b Mon Sep 17 00:00:00 2001 From: Arthur Araujo Date: Wed, 17 Apr 2024 08:22:05 -0300 Subject: [PATCH 3680/4092] Fix test methods names --- osu.Game.Tests/Editing/Checks/CheckHitsoundsFormatTest.cs | 8 ++++---- osu.Game.Tests/Editing/Checks/CheckSongFormatTest.cs | 8 ++++---- osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs | 1 - 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckHitsoundsFormatTest.cs b/osu.Game.Tests/Editing/Checks/CheckHitsoundsFormatTest.cs index f85a296c74..9a806f6cb7 100644 --- a/osu.Game.Tests/Editing/Checks/CheckHitsoundsFormatTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckHitsoundsFormatTest.cs @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Editing.Checks } [Test] - public void TestMP3Audio() + public void TestMp3Audio() { using (var resourceStream = TestResources.OpenResource("Samples/test-sample-cut.mp3")) { @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Editing.Checks } [Test] - public void TestOGGAudio() + public void TestOggAudio() { using (var resourceStream = TestResources.OpenResource("Samples/test-sample.ogg")) { @@ -65,7 +65,7 @@ namespace osu.Game.Tests.Editing.Checks } [Test] - public void TestWAVAudio() + public void TestWavAudio() { using (var resourceStream = TestResources.OpenResource("Samples/hitsound-delay.wav")) { @@ -75,7 +75,7 @@ namespace osu.Game.Tests.Editing.Checks } [Test] - public void TestWEBMAudio() + public void TestWebmAudio() { using (var resourceStream = TestResources.OpenResource("Samples/test-sample.webm")) { diff --git a/osu.Game.Tests/Editing/Checks/CheckSongFormatTest.cs b/osu.Game.Tests/Editing/Checks/CheckSongFormatTest.cs index 0755fdd8ac..98a4e1f9e9 100644 --- a/osu.Game.Tests/Editing/Checks/CheckSongFormatTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckSongFormatTest.cs @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Editing.Checks } [Test] - public void TestMP3Audio() + public void TestMp3Audio() { using (var resourceStream = TestResources.OpenResource("Samples/test-sample-cut.mp3")) { @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Editing.Checks } [Test] - public void TestOGGAudio() + public void TestOggAudio() { using (var resourceStream = TestResources.OpenResource("Samples/test-sample.ogg")) { @@ -66,7 +66,7 @@ namespace osu.Game.Tests.Editing.Checks } [Test] - public void TestWAVAudio() + public void TestWavAudio() { using (var resourceStream = TestResources.OpenResource("Samples/hitsound-delay.wav")) { @@ -78,7 +78,7 @@ namespace osu.Game.Tests.Editing.Checks } [Test] - public void TestWEBMAudio() + public void TestWebmAudio() { using (var resourceStream = TestResources.OpenResource("Samples/test-sample.webm")) { diff --git a/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs b/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs index c71c7b0ef3..3343d07f25 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs @@ -63,7 +63,6 @@ namespace osu.Game.Rulesets.Edit.Checks if (!allowedFormats.Contains(audioInfo.ChannelType)) yield return new IssueTemplateIncorrectFormat(this).Create(file.Filename, audioInfo.ChannelType.ToString()); - Bass.StreamFree(decodeStream); } } From f92833b588577f6e55f53948f97a8ed284867fb0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Apr 2024 12:02:49 +0800 Subject: [PATCH 3681/4092] Add ability to quick exit from results screen --- osu.Game/Screens/Ranking/ResultsScreen.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index e579c3fe51..f28b9b2554 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -191,6 +191,16 @@ namespace osu.Game.Screens.Ranking }); } + AddInternal(new HotkeyExitOverlay + { + Action = () => + { + if (!this.IsCurrentScreen()) return; + + this.Exit(); + }, + }); + if (player != null && AllowRetry) { buttons.Add(new RetryButton { Width = 300 }); From d08b1e5ae7a413c3ec6a958b1bc592e20baf6c8e Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 17 Apr 2024 21:18:54 -0700 Subject: [PATCH 3682/4092] Add xmldoc to `GetScore()` and `Export()` and remove xmldoc from private `getDatabasedScoreInfo()` --- osu.Game/Scoring/ScoreManager.cs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 3f6c6ee49d..b6bb637537 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -58,6 +58,15 @@ namespace osu.Game.Scoring }; } + /// + /// Retrieve a from a given . + /// + /// The to convert. + /// The . Null if the score on the database cannot be found. + /// + /// The is re-retrieved from the database to ensure all the required data + /// for retrieving a replay are present (may have missing properties if it was retrieved from online data). + /// public Score? GetScore(IScoreInfo scoreInfo) { ScoreInfo? databasedScoreInfo = getDatabasedScoreInfo(scoreInfo); @@ -75,12 +84,6 @@ namespace osu.Game.Scoring return Realm.Run(r => r.All().FirstOrDefault(query)?.Detach()); } - /// - /// Re-retrieve a given from the database to ensure all the required data for presenting / exporting a replay are present. - /// - /// The to attempt querying on. - /// The databased score info. Null if the score on the database cannot be found. - /// Can be used when the was retrieved from online data, as it may have missing properties. private ScoreInfo? getDatabasedScoreInfo(IScoreInfo originalScoreInfo) { ScoreInfo? databasedScoreInfo = null; @@ -194,6 +197,15 @@ namespace osu.Game.Scoring public Task>> Import(ProgressNotification notification, ImportTask[] tasks, ImportParameters parameters = default) => scoreImporter.Import(notification, tasks); + /// + /// Export a replay from a given . + /// + /// The to export. + /// The . Null if the score on the database cannot be found. + /// + /// The is re-retrieved from the database to ensure all the required data + /// for exporting a replay are present (may have missing properties if it was retrieved from online data). + /// public Task? Export(ScoreInfo scoreInfo) { ScoreInfo? databasedScoreInfo = getDatabasedScoreInfo(scoreInfo); From 46e2cfdba526d77106d1eff41ec5e542ac7e8bca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Apr 2024 18:33:30 +0800 Subject: [PATCH 3683/4092] 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 2d7a9d2652..bf02a5d8e2 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 b2e3fc0779..4f973dbeb9 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From a41ae99dd7e8f39c909a3519cce1817469b139a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Apr 2024 15:14:52 +0800 Subject: [PATCH 3684/4092] 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 bf02a5d8e2..c61977cfa3 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 4f973dbeb9..6389172fe7 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From 362a7b2c7735fa753e81c7075dba38b4d634e1be Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 19 Apr 2024 18:03:13 +0900 Subject: [PATCH 3685/4092] Remove unused members from GameplaySkinComponentLookup --- osu.Game.Rulesets.Catch/CatchSkinComponentLookup.cs | 4 ---- osu.Game.Rulesets.Mania/ManiaSkinComponentLookup.cs | 4 ---- osu.Game.Rulesets.Osu/OsuSkinComponentLookup.cs | 4 ---- osu.Game.Rulesets.Taiko/TaikoSkinComponentLookup.cs | 4 ---- osu.Game/Skinning/GameplaySkinComponentLookup.cs | 3 --- 5 files changed, 19 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchSkinComponentLookup.cs b/osu.Game.Rulesets.Catch/CatchSkinComponentLookup.cs index 149aae1cb4..596b102ac5 100644 --- a/osu.Game.Rulesets.Catch/CatchSkinComponentLookup.cs +++ b/osu.Game.Rulesets.Catch/CatchSkinComponentLookup.cs @@ -11,9 +11,5 @@ namespace osu.Game.Rulesets.Catch : base(component) { } - - protected override string RulesetPrefix => "catch"; // todo: use CatchRuleset.SHORT_NAME; - - protected override string ComponentName => Component.ToString().ToLowerInvariant(); } } diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponentLookup.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponentLookup.cs index 44120e16e6..046d1c5b34 100644 --- a/osu.Game.Rulesets.Mania/ManiaSkinComponentLookup.cs +++ b/osu.Game.Rulesets.Mania/ManiaSkinComponentLookup.cs @@ -15,10 +15,6 @@ namespace osu.Game.Rulesets.Mania : base(component) { } - - protected override string RulesetPrefix => ManiaRuleset.SHORT_NAME; - - protected override string ComponentName => Component.ToString().ToLowerInvariant(); } public enum ManiaSkinComponents diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponentLookup.cs b/osu.Game.Rulesets.Osu/OsuSkinComponentLookup.cs index 81d5811f85..3b3653e1ba 100644 --- a/osu.Game.Rulesets.Osu/OsuSkinComponentLookup.cs +++ b/osu.Game.Rulesets.Osu/OsuSkinComponentLookup.cs @@ -11,9 +11,5 @@ namespace osu.Game.Rulesets.Osu : base(component) { } - - protected override string RulesetPrefix => OsuRuleset.SHORT_NAME; - - protected override string ComponentName => Component.ToString().ToLowerInvariant(); } } diff --git a/osu.Game.Rulesets.Taiko/TaikoSkinComponentLookup.cs b/osu.Game.Rulesets.Taiko/TaikoSkinComponentLookup.cs index c35971e9fd..8841c3d3ca 100644 --- a/osu.Game.Rulesets.Taiko/TaikoSkinComponentLookup.cs +++ b/osu.Game.Rulesets.Taiko/TaikoSkinComponentLookup.cs @@ -11,9 +11,5 @@ namespace osu.Game.Rulesets.Taiko : base(component) { } - - protected override string RulesetPrefix => TaikoRuleset.SHORT_NAME; - - protected override string ComponentName => Component.ToString().ToLowerInvariant(); } } diff --git a/osu.Game/Skinning/GameplaySkinComponentLookup.cs b/osu.Game/Skinning/GameplaySkinComponentLookup.cs index ec159873f8..c317a17e21 100644 --- a/osu.Game/Skinning/GameplaySkinComponentLookup.cs +++ b/osu.Game/Skinning/GameplaySkinComponentLookup.cs @@ -24,8 +24,5 @@ namespace osu.Game.Skinning { Component = component; } - - protected virtual string RulesetPrefix => string.Empty; - protected virtual string ComponentName => Component.ToString(); } } From 9d04b44a8872663476798264d8ab91943768f0da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 19 Apr 2024 11:04:05 +0200 Subject: [PATCH 3686/4092] Add failing test case --- .../UserInterface/TestSceneModSelectOverlay.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 6c75530a6e..8ddbd84890 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -612,6 +612,23 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("search text box unfocused", () => !modSelectOverlay.SearchTextBox.HasFocus); } + [Test] + public void TestSearchBoxFocusToggleRespondsToExternalChanges() + { + AddStep("text search does not start active", () => configManager.SetValue(OsuSetting.ModSelectTextSearchStartsActive, false)); + createScreen(); + + AddUntilStep("search text box not focused", () => !modSelectOverlay.SearchTextBox.HasFocus); + + AddStep("press tab", () => InputManager.Key(Key.Tab)); + AddAssert("search text box focused", () => modSelectOverlay.SearchTextBox.HasFocus); + + AddStep("unfocus search text box externally", () => InputManager.ChangeFocus(null)); + + AddStep("press tab", () => InputManager.Key(Key.Tab)); + AddAssert("search text box focused", () => modSelectOverlay.SearchTextBox.HasFocus); + } + [Test] public void TestTextSearchDoesNotBlockCustomisationPanelKeyboardInteractions() { From 2dcbb823ef327f947547ed8f74990e74d021712d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 19 Apr 2024 11:08:34 +0200 Subject: [PATCH 3687/4092] Use textbox focus state directly rather than trying to track it independently --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 5ca26c739e..47362c0003 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -143,8 +143,6 @@ namespace osu.Game.Overlays.Mods protected ShearedToggleButton? CustomisationButton { get; private set; } protected SelectAllModsButton? SelectAllModsButton { get; set; } - private bool textBoxShouldFocus; - private Sample? columnAppearSample; private WorkingBeatmap? beatmap; @@ -542,7 +540,7 @@ namespace osu.Game.Overlays.Mods if (customisationVisible.Value) SearchTextBox.KillFocus(); else - setTextBoxFocus(textBoxShouldFocus); + setTextBoxFocus(textSearchStartsActive.Value); } /// @@ -798,15 +796,13 @@ namespace osu.Game.Overlays.Mods return false; // TODO: should probably eventually support typical platform search shortcuts (`Ctrl-F`, `/`) - setTextBoxFocus(!textBoxShouldFocus); + setTextBoxFocus(!SearchTextBox.HasFocus); return true; } - private void setTextBoxFocus(bool keepFocus) + private void setTextBoxFocus(bool focus) { - textBoxShouldFocus = keepFocus; - - if (textBoxShouldFocus) + if (focus) SearchTextBox.TakeFocus(); else SearchTextBox.KillFocus(); From 509862490e88f5ebea63005e23463336cb43f9d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 19 Apr 2024 11:11:18 +0200 Subject: [PATCH 3688/4092] Revert unnecessary changes --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 47362c0003..25293e8e20 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -418,7 +418,7 @@ namespace osu.Game.Overlays.Mods yield return new ColumnDimContainer(new ModPresetColumn { Margin = new MarginPadding { Right = 10 } - }, this); + }); } yield return createModColumnContent(ModType.DifficultyReduction); @@ -436,7 +436,7 @@ namespace osu.Game.Overlays.Mods column.Margin = new MarginPadding { Right = 10 }; }); - return new ColumnDimContainer(column, this); + return new ColumnDimContainer(column); } private void createLocalMods() @@ -895,17 +895,13 @@ namespace osu.Game.Overlays.Mods [Resolved] private OsuColour colours { get; set; } = null!; - private ModSelectOverlay modSelectOverlayInstance; - - public ColumnDimContainer(ModSelectColumn column, ModSelectOverlay modSelectOverlay) + public ColumnDimContainer(ModSelectColumn column) { AutoSizeAxes = Axes.X; RelativeSizeAxes = Axes.Y; Child = Column = column; column.Active.BindTo(Active); - - this.modSelectOverlayInstance = modSelectOverlay; } [BackgroundDependencyLoader] @@ -953,7 +949,7 @@ namespace osu.Game.Overlays.Mods RequestScroll?.Invoke(this); // Killing focus is done here because it's the only feasible place on ModSelectOverlay you can click on without triggering any action. - modSelectOverlayInstance.setTextBoxFocus(false); + Scheduler.Add(() => GetContainingInputManager().ChangeFocus(null)); return true; } From eac9dededfb59ab668cedc0e788aef11532cf152 Mon Sep 17 00:00:00 2001 From: Arthur Araujo <90941580+64ArthurAraujo@users.noreply.github.com> Date: Fri, 19 Apr 2024 07:20:59 -0300 Subject: [PATCH 3689/4092] Use periods instead of semicolons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs b/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs index 3343d07f25..d716f5ba3c 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Edit.Checks public class IssueTemplateFormatUnsupported : IssueTemplate { public IssueTemplateFormatUnsupported(ICheck check) - : base(check, IssueType.Problem, "\"{0}\" may be corrupt or using a unsupported audio format; Use wav or ogg for hitsounds.") + : base(check, IssueType.Problem, "\"{0}\" may be corrupt or using a unsupported audio format. Use wav or ogg for hitsounds.") { } @@ -83,7 +83,7 @@ namespace osu.Game.Rulesets.Edit.Checks public class IssueTemplateIncorrectFormat : IssueTemplate { public IssueTemplateIncorrectFormat(ICheck check) - : base(check, IssueType.Problem, "\"{0}\" is using a incorrect format ({1}); Use wav or ogg for hitsounds.") + : base(check, IssueType.Problem, "\"{0}\" is using a incorrect format ({1}). Use wav or ogg for hitsounds.") { } From 68e86b5105fae6a4dd72bada15774b4ad2a052e0 Mon Sep 17 00:00:00 2001 From: Arthur Araujo Date: Fri, 19 Apr 2024 07:38:55 -0300 Subject: [PATCH 3690/4092] Remove unsued import and add a blankline before `yield break;` --- osu.Game/Rulesets/Edit/Checks/CheckSongFormat.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckSongFormat.cs b/osu.Game/Rulesets/Edit/Checks/CheckSongFormat.cs index 9ed1350f56..7dd469833e 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckSongFormat.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckSongFormat.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.Collections.Generic; using System.IO; using System.Linq; @@ -48,6 +47,7 @@ namespace osu.Game.Rulesets.Edit.Checks if (decodeStream == 0) { yield return new IssueTemplateFormatUnsupported(this).Create(audioFile.Filename); + yield break; } From 92b85beff6735e83f3324427478833d0b62d504d Mon Sep 17 00:00:00 2001 From: Arthur Araujo Date: Fri, 19 Apr 2024 08:01:23 -0300 Subject: [PATCH 3691/4092] Remove `ChannelType` from messages --- osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs | 6 +++--- osu.Game/Rulesets/Edit/Checks/CheckSongFormat.cs | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs b/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs index d716f5ba3c..184311b1c3 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Edit.Checks var audioInfo = Bass.ChannelGetInfo(decodeStream); if (!allowedFormats.Contains(audioInfo.ChannelType)) - yield return new IssueTemplateIncorrectFormat(this).Create(file.Filename, audioInfo.ChannelType.ToString()); + yield return new IssueTemplateIncorrectFormat(this).Create(file.Filename); Bass.StreamFree(decodeStream); } @@ -83,11 +83,11 @@ namespace osu.Game.Rulesets.Edit.Checks public class IssueTemplateIncorrectFormat : IssueTemplate { public IssueTemplateIncorrectFormat(ICheck check) - : base(check, IssueType.Problem, "\"{0}\" is using a incorrect format ({1}). Use wav or ogg for hitsounds.") + : base(check, IssueType.Problem, "\"{0}\" is using a incorrect format. Use wav or ogg for hitsounds.") { } - public Issue Create(string file, string format) => new Issue(this, file, format); + public Issue Create(string file) => new Issue(this, file); } } } diff --git a/osu.Game/Rulesets/Edit/Checks/CheckSongFormat.cs b/osu.Game/Rulesets/Edit/Checks/CheckSongFormat.cs index 7dd469833e..0cb54709af 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckSongFormat.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckSongFormat.cs @@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Edit.Checks var audioInfo = Bass.ChannelGetInfo(decodeStream); if (!allowedFormats.Contains(audioInfo.ChannelType)) - yield return new IssueTemplateIncorrectFormat(this).Create(audioFile.Filename, audioInfo.ChannelType.ToString()); + yield return new IssueTemplateIncorrectFormat(this).Create(audioFile.Filename); Bass.StreamFree(decodeStream); } @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Edit.Checks public class IssueTemplateFormatUnsupported : IssueTemplate { public IssueTemplateFormatUnsupported(ICheck check) - : base(check, IssueType.Problem, "\"{0}\" may be corrupt or using a unsupported audio format; Use mp3 or ogg for the song's audio.") + : base(check, IssueType.Problem, "\"{0}\" may be corrupt or using a unsupported audio format. Use mp3 or ogg for the song's audio.") { } @@ -73,11 +73,11 @@ namespace osu.Game.Rulesets.Edit.Checks public class IssueTemplateIncorrectFormat : IssueTemplate { public IssueTemplateIncorrectFormat(ICheck check) - : base(check, IssueType.Problem, "\"{0}\" is using a incorrect format ({1}); Use mp3 or ogg for the song's audio.") + : base(check, IssueType.Problem, "\"{0}\" is using a incorrect format. Use mp3 or ogg for the song's audio.") { } - public Issue Create(string file, string format) => new Issue(this, file, format); + public Issue Create(string file) => new Issue(this, file); } } } From ddc1b90ee1836662e788e02f266992cecad91e91 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 19 Apr 2024 20:36:24 +0900 Subject: [PATCH 3692/4092] Remove unused method --- .../Skinning/ISerialisableDrawableContainer.cs | 5 ----- osu.Game/Skinning/SkinComponentsContainer.cs | 15 --------------- 2 files changed, 20 deletions(-) diff --git a/osu.Game/Skinning/ISerialisableDrawableContainer.cs b/osu.Game/Skinning/ISerialisableDrawableContainer.cs index a19c8c5162..57ea75bc7e 100644 --- a/osu.Game/Skinning/ISerialisableDrawableContainer.cs +++ b/osu.Game/Skinning/ISerialisableDrawableContainer.cs @@ -30,11 +30,6 @@ namespace osu.Game.Skinning /// void Reload(); - /// - /// Reload this target from the provided skinnable information. - /// - void Reload(SerialisedDrawableInfo[] skinnableInfo); - /// /// Add a new skinnable component to this target. /// diff --git a/osu.Game/Skinning/SkinComponentsContainer.cs b/osu.Game/Skinning/SkinComponentsContainer.cs index adf0a288b4..02ba43fd39 100644 --- a/osu.Game/Skinning/SkinComponentsContainer.cs +++ b/osu.Game/Skinning/SkinComponentsContainer.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Linq; using System.Threading; using osu.Framework.Bindables; @@ -44,20 +43,6 @@ namespace osu.Game.Skinning Lookup = lookup; } - public void Reload(SerialisedDrawableInfo[] skinnableInfo) - { - var drawables = new List(); - - foreach (var i in skinnableInfo) - drawables.Add(i.CreateInstance()); - - Reload(new Container - { - RelativeSizeAxes = Axes.Both, - Children = drawables, - }); - } - public void Reload() => Reload(CurrentSkin.GetDrawableComponent(Lookup) as Container); public void Reload(Container? componentsContainer) From bac70da1a1a12ddae4769da8ead2374ed6932311 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 19 Apr 2024 21:40:02 +0900 Subject: [PATCH 3693/4092] Give SerialisedDrawableInfo sane defaults --- osu.Game/Skinning/SerialisedDrawableInfo.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/SerialisedDrawableInfo.cs b/osu.Game/Skinning/SerialisedDrawableInfo.cs index 2d6113ff70..ac1aa80d29 100644 --- a/osu.Game/Skinning/SerialisedDrawableInfo.cs +++ b/osu.Game/Skinning/SerialisedDrawableInfo.cs @@ -34,15 +34,15 @@ namespace osu.Game.Skinning public float Rotation { get; set; } - public Vector2 Scale { get; set; } + public Vector2 Scale { get; set; } = Vector2.One; public float? Width { get; set; } public float? Height { get; set; } - public Anchor Anchor { get; set; } + public Anchor Anchor { get; set; } = Anchor.TopLeft; - public Anchor Origin { get; set; } + public Anchor Origin { get; set; } = Anchor.TopLeft; /// public bool UsesFixedAnchor { get; set; } From 4cffc39dffde5e14172df2693e71eebb4f3964ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 20 Apr 2024 04:53:31 +0800 Subject: [PATCH 3694/4092] Don't require hold for quick exit at results screen --- osu.Game/Screens/Ranking/ResultsScreen.cs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index f28b9b2554..ebb0530046 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -191,16 +191,6 @@ namespace osu.Game.Screens.Ranking }); } - AddInternal(new HotkeyExitOverlay - { - Action = () => - { - if (!this.IsCurrentScreen()) return; - - this.Exit(); - }, - }); - if (player != null && AllowRetry) { buttons.Add(new RetryButton { Width = 300 }); @@ -400,6 +390,15 @@ namespace osu.Game.Screens.Ranking switch (e.Action) { + case GlobalAction.QuickExit: + if (this.IsCurrentScreen()) + { + this.Exit(); + return true; + } + + break; + case GlobalAction.Select: StatisticsPanel.ToggleVisibility(); return true; From 722fa228cedfb81726dcc08e7b2baa4c524fb958 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 21 Apr 2024 17:40:35 +0300 Subject: [PATCH 3695/4092] Don't consider editor table content for input --- osu.Game/Screens/Edit/EditorTable.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Edit/EditorTable.cs b/osu.Game/Screens/Edit/EditorTable.cs index e5dc540b06..49b41fac21 100644 --- a/osu.Game/Screens/Edit/EditorTable.cs +++ b/osu.Game/Screens/Edit/EditorTable.cs @@ -30,6 +30,10 @@ namespace osu.Game.Screens.Edit protected readonly FillFlowContainer BackgroundFlow; + // We can avoid potentially thousands of objects being added to the input sub-tree since item selection is being handled by the BackgroundFlow + // and no items in the underlying table are clickable. + protected override bool ShouldBeConsideredForInput(Drawable child) => base.ShouldBeConsideredForInput(child) && child == BackgroundFlow; + protected EditorTable() { RelativeSizeAxes = Axes.X; From 1d3fd65d86f517805f573517ad7650d1a89277af Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 22 Apr 2024 07:02:49 +0300 Subject: [PATCH 3696/4092] Adjust execution order --- osu.Game/Screens/Edit/EditorTable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/EditorTable.cs b/osu.Game/Screens/Edit/EditorTable.cs index 49b41fac21..4d8393e829 100644 --- a/osu.Game/Screens/Edit/EditorTable.cs +++ b/osu.Game/Screens/Edit/EditorTable.cs @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Edit // We can avoid potentially thousands of objects being added to the input sub-tree since item selection is being handled by the BackgroundFlow // and no items in the underlying table are clickable. - protected override bool ShouldBeConsideredForInput(Drawable child) => base.ShouldBeConsideredForInput(child) && child == BackgroundFlow; + protected override bool ShouldBeConsideredForInput(Drawable child) => child == BackgroundFlow && base.ShouldBeConsideredForInput(child); protected EditorTable() { From 40c48f903bdfdb0f11809c5aeea268aec198fbb6 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 22 Apr 2024 14:58:45 +0900 Subject: [PATCH 3697/4092] Add failing test --- .../TestSceneCursorTrail.cs | 31 +++++++++++++++---- .../Skinning/Legacy/LegacyCursorTrail.cs | 16 +++++----- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs index 421a32b9eb..afb0246b21 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; @@ -13,6 +14,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Rendering; using osu.Framework.Graphics.Textures; +using osu.Framework.Testing; using osu.Framework.Testing.Input; using osu.Game.Audio; using osu.Game.Rulesets.Osu.Skinning.Legacy; @@ -70,6 +72,22 @@ namespace osu.Game.Rulesets.Osu.Tests }); } + [Test] + public void TestLegacyDisjointCursorTrailViaNoCursor() + { + createTest(() => + { + var skinContainer = new LegacySkinContainer(renderer, false, false); + var legacyCursorTrail = new LegacyCursorTrail(skinContainer); + + skinContainer.Child = legacyCursorTrail; + + return skinContainer; + }); + + AddAssert("trail is disjoint", () => this.ChildrenOfType().Single().DisjointTrail, () => Is.True); + } + private void createTest(Func createContent) => AddStep("create trail", () => { Clear(); @@ -87,11 +105,13 @@ namespace osu.Game.Rulesets.Osu.Tests { private readonly IRenderer renderer; private readonly bool disjoint; + private readonly bool provideCursor; - public LegacySkinContainer(IRenderer renderer, bool disjoint) + public LegacySkinContainer(IRenderer renderer, bool disjoint, bool provideCursor = true) { this.renderer = renderer; this.disjoint = disjoint; + this.provideCursor = provideCursor; RelativeSizeAxes = Axes.Both; } @@ -102,12 +122,11 @@ namespace osu.Game.Rulesets.Osu.Tests { switch (componentName) { - case "cursortrail": - var tex = new Texture(renderer.WhitePixel); + case "cursor": + return provideCursor ? new Texture(renderer.WhitePixel) : null; - if (disjoint) - tex.ScaleAdjust = 1 / 25f; - return tex; + case "cursortrail": + return new Texture(renderer.WhitePixel); case "cursormiddle": return disjoint ? null : renderer.WhitePixel; diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs index af71e2a5d9..4ebafd0c4b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private readonly ISkin skin; private const double disjoint_trail_time_separation = 1000 / 60.0; - private bool disjointTrail; + public bool DisjointTrail { get; private set; } private double lastTrailTime; private IBindable cursorSize = null!; @@ -36,9 +36,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy cursorSize = config.GetBindable(OsuSetting.GameplayCursorSize).GetBoundCopy(); Texture = skin.GetTexture("cursortrail"); - disjointTrail = skin.GetTexture("cursormiddle") == null; + DisjointTrail = skin.GetTexture("cursormiddle") == null; - if (disjointTrail) + if (DisjointTrail) { bool centre = skin.GetConfig(OsuSkinConfiguration.CursorCentre)?.Value ?? true; @@ -57,19 +57,19 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy } } - protected override double FadeDuration => disjointTrail ? 150 : 500; + protected override double FadeDuration => DisjointTrail ? 150 : 500; protected override float FadeExponent => 1; - protected override bool InterpolateMovements => !disjointTrail; + protected override bool InterpolateMovements => !DisjointTrail; protected override float IntervalMultiplier => 1 / Math.Max(cursorSize.Value, 1); - protected override bool AvoidDrawingNearCursor => !disjointTrail; + protected override bool AvoidDrawingNearCursor => !DisjointTrail; protected override void Update() { base.Update(); - if (!disjointTrail || !currentPosition.HasValue) + if (!DisjointTrail || !currentPosition.HasValue) return; if (Time.Current - lastTrailTime >= disjoint_trail_time_separation) @@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy protected override bool OnMouseMove(MouseMoveEvent e) { - if (!disjointTrail) + if (!DisjointTrail) return base.OnMouseMove(e); currentPosition = e.ScreenSpaceMousePosition; From 0170c04baf447abd9a4f986bcc169f940e92f9d4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 22 Apr 2024 15:00:09 +0900 Subject: [PATCH 3698/4092] Fix `cursormiddle` not using the same source as `cursor` --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs index 4ebafd0c4b..da52956719 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs @@ -31,12 +31,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy } [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuConfigManager config, ISkinSource skinSource) { cursorSize = config.GetBindable(OsuSetting.GameplayCursorSize).GetBoundCopy(); Texture = skin.GetTexture("cursortrail"); - DisjointTrail = skin.GetTexture("cursormiddle") == null; + + var cursorProvider = skinSource.FindProvider(s => s.GetTexture("cursor") != null); + DisjointTrail = cursorProvider?.GetTexture("cursormiddle") == null; if (DisjointTrail) { From fb1d20bd39605eca96837d2d8e9b2cc394800cba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Apr 2024 15:07:11 +0800 Subject: [PATCH 3699/4092] Comment in some clarification --- .../TestSceneCursorTrail.cs | 14 +++++++------- .../Skinning/Legacy/LegacyCursorTrail.cs | 3 +++ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs index afb0246b21..4db66fde4b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.Tests { createTest(() => { - var skinContainer = new LegacySkinContainer(renderer, false); + var skinContainer = new LegacySkinContainer(renderer, provideMiddle: false); var legacyCursorTrail = new LegacyCursorTrail(skinContainer); skinContainer.Child = legacyCursorTrail; @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Osu.Tests { createTest(() => { - var skinContainer = new LegacySkinContainer(renderer, true); + var skinContainer = new LegacySkinContainer(renderer, provideMiddle: true); var legacyCursorTrail = new LegacyCursorTrail(skinContainer); skinContainer.Child = legacyCursorTrail; @@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Osu.Tests { createTest(() => { - var skinContainer = new LegacySkinContainer(renderer, false, false); + var skinContainer = new LegacySkinContainer(renderer, provideMiddle: false, provideCursor: false); var legacyCursorTrail = new LegacyCursorTrail(skinContainer); skinContainer.Child = legacyCursorTrail; @@ -104,13 +104,13 @@ namespace osu.Game.Rulesets.Osu.Tests private partial class LegacySkinContainer : Container, ISkinSource { private readonly IRenderer renderer; - private readonly bool disjoint; + private readonly bool provideMiddle; private readonly bool provideCursor; - public LegacySkinContainer(IRenderer renderer, bool disjoint, bool provideCursor = true) + public LegacySkinContainer(IRenderer renderer, bool provideMiddle, bool provideCursor = true) { this.renderer = renderer; - this.disjoint = disjoint; + this.provideMiddle = provideMiddle; this.provideCursor = provideCursor; RelativeSizeAxes = Axes.Both; @@ -129,7 +129,7 @@ namespace osu.Game.Rulesets.Osu.Tests return new Texture(renderer.WhitePixel); case "cursormiddle": - return disjoint ? null : renderer.WhitePixel; + return provideMiddle ? null : renderer.WhitePixel; } return null; diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs index da52956719..ca0002d8c0 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs @@ -37,6 +37,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Texture = skin.GetTexture("cursortrail"); + // Cursor and cursor trail components are sourced from potentially different skin sources. + // Stable always chooses cursor trail disjoint behaviour based on the cursor texture lookup source, so we need to fetch where that occurred. + // See https://github.com/peppy/osu-stable-reference/blob/3ea48705eb67172c430371dcfc8a16a002ed0d3d/osu!/Graphics/Skinning/SkinManager.cs#L269 var cursorProvider = skinSource.FindProvider(s => s.GetTexture("cursor") != null); DisjointTrail = cursorProvider?.GetTexture("cursormiddle") == null; From 49563f4e5bc9ce3ea72b5645456a6d0e8af6a89c Mon Sep 17 00:00:00 2001 From: Arthur Araujo Date: Mon, 22 Apr 2024 04:44:38 -0300 Subject: [PATCH 3700/4092] Use bitwise to check hitsound formats --- osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs b/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs index 184311b1c3..9779696e4b 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.IO; -using System.Linq; using ManagedBass; using osu.Framework.Audio.Callbacks; using osu.Game.Beatmaps; @@ -22,14 +21,6 @@ namespace osu.Game.Rulesets.Edit.Checks new IssueTemplateIncorrectFormat(this), }; - private IEnumerable allowedFormats => new ChannelType[] - { - ChannelType.WavePCM, - ChannelType.WaveFloat, - ChannelType.OGG, - ChannelType.Wave | ChannelType.OGG, - }; - public IEnumerable Run(BeatmapVerifierContext context) { var beatmapSet = context.Beatmap.BeatmapInfo.BeatmapSet; @@ -60,7 +51,7 @@ namespace osu.Game.Rulesets.Edit.Checks var audioInfo = Bass.ChannelGetInfo(decodeStream); - if (!allowedFormats.Contains(audioInfo.ChannelType)) + if ((audioInfo.ChannelType & ChannelType.Wave) == 0 && audioInfo.ChannelType != ChannelType.OGG) yield return new IssueTemplateIncorrectFormat(this).Create(file.Filename); Bass.StreamFree(decodeStream); From 09b0f3005e28cb2248ddb206b80d3eb3a94c6e2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Apr 2024 10:15:56 +0200 Subject: [PATCH 3701/4092] Apply generic math-related changes --- osu.Game/Graphics/UserInterface/ExpandableSlider.cs | 8 ++++---- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 9 +++++---- osu.Game/Graphics/UserInterface/RoundedSliderBar.cs | 5 +++-- osu.Game/Graphics/UserInterface/ShearedSliderBar.cs | 5 +++-- osu.Game/Graphics/UserInterfaceV2/LabelledSliderBar.cs | 4 ++-- .../Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs | 8 ++++---- osu.Game/Overlays/Settings/Sections/SizeSlider.cs | 3 ++- osu.Game/Overlays/Settings/SettingsPercentageSlider.cs | 4 ++-- osu.Game/Overlays/Settings/SettingsSlider.cs | 6 +++--- .../Edit/Timing/IndeterminateSliderWithTextBoxInput.cs | 8 ++++---- osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs | 4 ++-- 11 files changed, 34 insertions(+), 30 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ExpandableSlider.cs b/osu.Game/Graphics/UserInterface/ExpandableSlider.cs index 121a1eef49..a7a8561b94 100644 --- a/osu.Game/Graphics/UserInterface/ExpandableSlider.cs +++ b/osu.Game/Graphics/UserInterface/ExpandableSlider.cs @@ -1,7 +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 System; +using System.Numerics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -10,7 +10,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osuTK; +using Vector2 = osuTK.Vector2; namespace osu.Game.Graphics.UserInterface { @@ -18,7 +18,7 @@ namespace osu.Game.Graphics.UserInterface /// An implementation for the UI slider bar control. /// public partial class ExpandableSlider : CompositeDrawable, IExpandable, IHasCurrentValue - where T : struct, IEquatable, IComparable, IConvertible + where T : struct, INumber, IMinMaxValue where TSlider : RoundedSliderBar, new() { private readonly OsuSpriteText label; @@ -129,7 +129,7 @@ namespace osu.Game.Graphics.UserInterface /// An implementation for the UI slider bar control. /// public partial class ExpandableSlider : ExpandableSlider> - where T : struct, IEquatable, IComparable, IConvertible + where T : struct, INumber, IMinMaxValue { } } diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 191a7ca246..9cb6356cab 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Numerics; using System.Globalization; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -15,7 +16,7 @@ using osu.Game.Utils; namespace osu.Game.Graphics.UserInterface { public abstract partial class OsuSliderBar : SliderBar, IHasTooltip - where T : struct, IEquatable, IComparable, IConvertible + where T : struct, INumber, IMinMaxValue { public bool PlaySamplesOnAdjust { get; set; } = true; @@ -85,11 +86,11 @@ namespace osu.Game.Graphics.UserInterface private LocalisableString getTooltipText(T value) { if (CurrentNumber.IsInteger) - return value.ToInt32(NumberFormatInfo.InvariantInfo).ToString("N0"); + return int.CreateTruncating(value).ToString("N0"); - double floatValue = value.ToDouble(NumberFormatInfo.InvariantInfo); + double floatValue = double.CreateTruncating(value); - decimal decimalPrecision = normalise(CurrentNumber.Precision.ToDecimal(NumberFormatInfo.InvariantInfo), max_decimal_digits); + decimal decimalPrecision = normalise(decimal.CreateTruncating(CurrentNumber.Precision), max_decimal_digits); // Find the number of significant digits (we could have less than 5 after normalize()) int significantDigits = FormatUtils.FindPrecision(decimalPrecision); diff --git a/osu.Game/Graphics/UserInterface/RoundedSliderBar.cs b/osu.Game/Graphics/UserInterface/RoundedSliderBar.cs index 0981881ead..56047173bb 100644 --- a/osu.Game/Graphics/UserInterface/RoundedSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/RoundedSliderBar.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osuTK; +using System.Numerics; using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -11,11 +11,12 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Overlays; +using Vector2 = osuTK.Vector2; namespace osu.Game.Graphics.UserInterface { public partial class RoundedSliderBar : OsuSliderBar - where T : struct, IEquatable, IComparable, IConvertible + where T : struct, INumber, IMinMaxValue { protected readonly Nub Nub; protected readonly Box LeftBox; diff --git a/osu.Game/Graphics/UserInterface/ShearedSliderBar.cs b/osu.Game/Graphics/UserInterface/ShearedSliderBar.cs index 60a6670492..0df1c1d204 100644 --- a/osu.Game/Graphics/UserInterface/ShearedSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/ShearedSliderBar.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osuTK; +using System.Numerics; using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -12,11 +12,12 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Overlays; using static osu.Game.Graphics.UserInterface.ShearedNub; +using Vector2 = osuTK.Vector2; namespace osu.Game.Graphics.UserInterface { public partial class ShearedSliderBar : OsuSliderBar - where T : struct, IEquatable, IComparable, IConvertible + where T : struct, INumber, IMinMaxValue { protected readonly ShearedNub Nub; protected readonly Box LeftBox; diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledSliderBar.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledSliderBar.cs index 4585d3a4c9..4912a21fab 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledSliderBar.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledSliderBar.cs @@ -1,14 +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 System.Numerics; using osu.Framework.Graphics; using osu.Game.Overlays.Settings; namespace osu.Game.Graphics.UserInterfaceV2 { public partial class LabelledSliderBar : LabelledComponent, TNumber> - where TNumber : struct, IEquatable, IComparable, IConvertible + where TNumber : struct, INumber, IMinMaxValue { public LabelledSliderBar() : base(true) diff --git a/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs b/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs index e5ba7f61bf..abd828e98f 100644 --- a/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs +++ b/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs @@ -1,7 +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 System; +using System.Numerics; using System.Globalization; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -10,12 +10,12 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Game.Overlays.Settings; using osu.Game.Utils; -using osuTK; +using Vector2 = osuTK.Vector2; namespace osu.Game.Graphics.UserInterfaceV2 { public partial class SliderWithTextBoxInput : CompositeDrawable, IHasCurrentValue - where T : struct, IEquatable, IComparable, IConvertible + where T : struct, INumber, IMinMaxValue { /// /// A custom step value for each key press which actuates a change on this control. @@ -138,7 +138,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 { if (updatingFromTextBox) return; - decimal decimalValue = slider.Current.Value.ToDecimal(NumberFormatInfo.InvariantInfo); + decimal decimalValue = decimal.CreateTruncating(slider.Current.Value); textBox.Text = decimalValue.ToString($@"N{FormatUtils.FindPrecision(decimalValue)}"); } } diff --git a/osu.Game/Overlays/Settings/Sections/SizeSlider.cs b/osu.Game/Overlays/Settings/Sections/SizeSlider.cs index c73831d8d1..14ef58ff88 100644 --- a/osu.Game/Overlays/Settings/Sections/SizeSlider.cs +++ b/osu.Game/Overlays/Settings/Sections/SizeSlider.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Numerics; using System.Globalization; using osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; @@ -12,7 +13,7 @@ namespace osu.Game.Overlays.Settings.Sections /// A slider intended to show a "size" multiplier number, where 1x is 1.0. /// public partial class SizeSlider : RoundedSliderBar - where T : struct, IEquatable, IComparable, IConvertible, IFormattable + where T : struct, INumber, IMinMaxValue, IFormattable { public override LocalisableString TooltipText => Current.Value.ToString(@"0.##x", NumberFormatInfo.CurrentInfo); } diff --git a/osu.Game/Overlays/Settings/SettingsPercentageSlider.cs b/osu.Game/Overlays/Settings/SettingsPercentageSlider.cs index fa59d18de1..d7a09d3392 100644 --- a/osu.Game/Overlays/Settings/SettingsPercentageSlider.cs +++ b/osu.Game/Overlays/Settings/SettingsPercentageSlider.cs @@ -1,7 +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 System; +using System.Numerics; using osu.Framework.Graphics; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; @@ -13,7 +13,7 @@ namespace osu.Game.Overlays.Settings /// Mostly provided for convenience of use with . /// public partial class SettingsPercentageSlider : SettingsSlider - where TValue : struct, IEquatable, IComparable, IConvertible + where TValue : struct, INumber, IMinMaxValue { protected override Drawable CreateControl() => ((RoundedSliderBar)base.CreateControl()).With(sliderBar => sliderBar.DisplayAsPercentage = true); } diff --git a/osu.Game/Overlays/Settings/SettingsSlider.cs b/osu.Game/Overlays/Settings/SettingsSlider.cs index 6c81fece13..2460d78099 100644 --- a/osu.Game/Overlays/Settings/SettingsSlider.cs +++ b/osu.Game/Overlays/Settings/SettingsSlider.cs @@ -1,7 +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 System; +using System.Numerics; using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; @@ -9,12 +9,12 @@ using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings { public partial class SettingsSlider : SettingsSlider> - where T : struct, IEquatable, IComparable, IConvertible + where T : struct, INumber, IMinMaxValue { } public partial class SettingsSlider : SettingsItem - where TValue : struct, IEquatable, IComparable, IConvertible + where TValue : struct, INumber, IMinMaxValue where TSlider : RoundedSliderBar, new() { protected override Drawable CreateControl() => new TSlider diff --git a/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs index 151d469415..26f374ba85 100644 --- a/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs +++ b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs @@ -1,7 +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 System; +using System.Numerics; using System.Globalization; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -12,7 +12,7 @@ using osu.Framework.Localisation; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays.Settings; using osu.Game.Utils; -using osuTK; +using Vector2 = osuTK.Vector2; namespace osu.Game.Screens.Edit.Timing { @@ -22,7 +22,7 @@ namespace osu.Game.Screens.Edit.Timing /// by providing an "indeterminate state". /// public partial class IndeterminateSliderWithTextBoxInput : CompositeDrawable, IHasCurrentValue - where T : struct, IEquatable, IComparable, IConvertible + where T : struct, INumber, IMinMaxValue { /// /// A custom step value for each key press which actuates a change on this control. @@ -136,7 +136,7 @@ namespace osu.Game.Screens.Edit.Timing slider.Current.Value = nonNullValue; // use the value from the slider to ensure that any precision/min/max set on it via the initial indeterminate value have been applied correctly. - decimal decimalValue = slider.Current.Value.ToDecimal(NumberFormatInfo.InvariantInfo); + decimal decimalValue = decimal.CreateTruncating(slider.Current.Value); textBox.Text = decimalValue.ToString($@"N{FormatUtils.FindPrecision(decimalValue)}"); textBox.PlaceholderText = string.Empty; } diff --git a/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs b/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs index 88b778fafb..1fc1155c0b 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs @@ -1,7 +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 System; +using System.Numerics; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics; @@ -11,7 +11,7 @@ using osu.Game.Overlays.Settings; namespace osu.Game.Screens.Play.PlayerSettings { public partial class PlayerSliderBar : SettingsSlider - where T : struct, IEquatable, IComparable, IConvertible + where T : struct, INumber, IMinMaxValue { public RoundedSliderBar Bar => (RoundedSliderBar)Control; From 1bcf835d227ce3899d1e16ff16cfba4a9c08dcd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Apr 2024 10:25:46 +0200 Subject: [PATCH 3702/4092] Remove redundant array type specification --- osu.Game/Rulesets/Edit/Checks/CheckSongFormat.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckSongFormat.cs b/osu.Game/Rulesets/Edit/Checks/CheckSongFormat.cs index 0cb54709af..dd01fe110a 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckSongFormat.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckSongFormat.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Edit.Checks new IssueTemplateIncorrectFormat(this), }; - private IEnumerable allowedFormats => new ChannelType[] + private IEnumerable allowedFormats => new[] { ChannelType.MP3, ChannelType.OGG, From ceeb9b081903dab467a5659595f6746403ccc845 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Apr 2024 10:27:30 +0200 Subject: [PATCH 3703/4092] Use explicit reference equality check --- osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs b/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs index 9779696e4b..728567b490 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Edit.Checks foreach (var file in beatmapSet.Files) { - if (audioFile != null && file.File == audioFile.File) continue; + if (audioFile != null && ReferenceEquals(file.File, audioFile.File)) continue; using (Stream data = context.WorkingBeatmap.GetStream(file.File.GetStoragePath())) { From b28bf4d2ecf2dbc6b11b01ffdca5ae6adbcaecaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Apr 2024 10:43:20 +0200 Subject: [PATCH 3704/4092] Add test covering non-audio file formats not being checked --- .../Checks/CheckHitsoundsFormatTest.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/osu.Game.Tests/Editing/Checks/CheckHitsoundsFormatTest.cs b/osu.Game.Tests/Editing/Checks/CheckHitsoundsFormatTest.cs index 9a806f6cb7..cb1cf21734 100644 --- a/osu.Game.Tests/Editing/Checks/CheckHitsoundsFormatTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckHitsoundsFormatTest.cs @@ -85,6 +85,27 @@ namespace osu.Game.Tests.Editing.Checks } } + [Test] + public void TestNotAnAudioFile() + { + beatmap = new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + BeatmapSet = new BeatmapSetInfo + { + Files = { CheckTestHelpers.CreateMockFile("png") } + } + } + }; + + using (var resourceStream = TestResources.OpenResource("Textures/test-image.png")) + { + var issues = check.Run(getContext(resourceStream)).ToList(); + Assert.That(issues, Has.Count.EqualTo(0)); + } + } + [Test] public void TestCorruptAudio() { From 70a5288a6859dae28f7870f338e82cbbdcf4c1fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Apr 2024 10:45:09 +0200 Subject: [PATCH 3705/4092] Do not attempt to pass files that don't look like audio to BASS --- osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs b/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs index 728567b490..9b6a861358 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs @@ -37,14 +37,16 @@ namespace osu.Game.Rulesets.Edit.Checks if (data == null) continue; + if (!AudioCheckUtils.HasAudioExtension(file.Filename) || !probablyHasAudioData(data)) + continue; + var fileCallbacks = new FileCallbacks(new DataStreamFileProcedures(data)); int decodeStream = Bass.CreateStream(StreamSystem.NoBuffer, BassFlags.Decode, fileCallbacks.Callbacks, fileCallbacks.Handle); // If the format is not supported by BASS if (decodeStream == 0) { - if (AudioCheckUtils.HasAudioExtension(file.Filename) && probablyHasAudioData(data)) - yield return new IssueTemplateFormatUnsupported(this).Create(file.Filename); + yield return new IssueTemplateFormatUnsupported(this).Create(file.Filename); continue; } From 4ae9f81c73f39828e4e812cc362bacbda2656c9f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Apr 2024 18:43:15 +0800 Subject: [PATCH 3706/4092] Apply transforms to storyboard videos This requires that they are a `StoryboardSprite` for simplicity. Luckily this works just fine. --- .../Beatmaps/Formats/LegacyStoryboardDecoder.cs | 2 +- osu.Game/Storyboards/StoryboardVideo.cs | 15 +++++---------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 6689f087cb..b5d9ad1194 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -114,7 +114,7 @@ namespace osu.Game.Beatmaps.Formats if (!OsuGameBase.VIDEO_EXTENSIONS.Contains(Path.GetExtension(path).ToLowerInvariant())) break; - storyboard.GetLayer("Video").Add(new StoryboardVideo(path, offset)); + storyboard.GetLayer("Video").Add(storyboardSprite = new StoryboardVideo(path, offset)); break; } diff --git a/osu.Game/Storyboards/StoryboardVideo.cs b/osu.Game/Storyboards/StoryboardVideo.cs index 8c11e19a06..bd1b933b6f 100644 --- a/osu.Game/Storyboards/StoryboardVideo.cs +++ b/osu.Game/Storyboards/StoryboardVideo.cs @@ -3,23 +3,18 @@ using osu.Framework.Graphics; using osu.Game.Storyboards.Drawables; +using osuTK; namespace osu.Game.Storyboards { - public class StoryboardVideo : IStoryboardElement + public class StoryboardVideo : StoryboardSprite { - public string Path { get; } - - public bool IsDrawable => true; - - public double StartTime { get; } - public StoryboardVideo(string path, double offset) + : base(path, Anchor.Centre, Vector2.Zero) { - Path = path; - StartTime = offset; + TimelineGroup.Alpha.Add(Easing.None, offset, offset, 0, 1); } - public Drawable CreateDrawable() => new DrawableStoryboardVideo(this); + public override Drawable CreateDrawable() => new DrawableStoryboardVideo(this); } } From c43c383abf5d05caa5827b23b1fd53f61cff5b55 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Apr 2024 18:43:34 +0800 Subject: [PATCH 3707/4092] Allow storboard videos to take on no-relative size when specified by a mapper --- .../Drawables/DrawableStoryboardVideo.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs index 9a5db4bb39..98cb01d5f3 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs @@ -23,7 +23,17 @@ namespace osu.Game.Storyboards.Drawables { Video = video; - RelativeSizeAxes = Axes.Both; + // In osu-stable, a mapper can add a scale command for a storyboard. + // This allows scaling based on the video's absolute size. + // + // If not specified we take up the full available space. + bool useRelative = !video.TimelineGroup.Scale.HasCommands; + + RelativeSizeAxes = useRelative ? Axes.Both : Axes.None; + AutoSizeAxes = useRelative ? Axes.None : Axes.Both; + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; } [BackgroundDependencyLoader(true)] @@ -36,7 +46,7 @@ namespace osu.Game.Storyboards.Drawables InternalChild = drawableVideo = new Video(stream, false) { - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = RelativeSizeAxes, FillMode = FillMode.Fill, Anchor = Anchor.Centre, Origin = Anchor.Centre, From 9e7182acf0cfa3fc658758b78af4dc7378cef536 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Apr 2024 18:45:02 +0800 Subject: [PATCH 3708/4092] Remove unused DI beatmap paramater --- osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs index 98cb01d5f3..ca2c7b774c 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs @@ -2,12 +2,10 @@ // 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.Textures; using osu.Framework.Graphics.Video; -using osu.Game.Beatmaps; namespace osu.Game.Storyboards.Drawables { @@ -37,7 +35,7 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader(true)] - private void load(IBindable beatmap, TextureStore textureStore) + private void load(TextureStore textureStore) { var stream = textureStore.GetStream(Video.Path); From 2eda56ff0859a0e0fe44e59d2b7f936139dcc94b Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 22 Apr 2024 11:15:50 -0700 Subject: [PATCH 3709/4092] Revert beatmap query change --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ccdf9d151f..98533a5c82 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -716,7 +716,7 @@ namespace osu.Game return; } - var databasedBeatmap = BeatmapManager.QueryBeatmap(b => b.OnlineID == score.Beatmap.OnlineID); + var databasedBeatmap = BeatmapManager.QueryBeatmap(b => b.ID == databasedScore.ScoreInfo.BeatmapInfo.ID); if (databasedBeatmap == null) { From a59bf6d373cfdc234261fc1ce2d489803bd35f14 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 22 Apr 2024 11:17:27 -0700 Subject: [PATCH 3710/4092] Improve xmldoc wording --- osu.Game/Scoring/ScoreManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index b6bb637537..f37ee2b101 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -62,7 +62,7 @@ namespace osu.Game.Scoring /// Retrieve a from a given . /// /// The to convert. - /// The . Null if the score on the database cannot be found. + /// The . Null if the score cannot be found in the database. /// /// The is re-retrieved from the database to ensure all the required data /// for retrieving a replay are present (may have missing properties if it was retrieved from online data). @@ -201,7 +201,7 @@ namespace osu.Game.Scoring /// Export a replay from a given . /// /// The to export. - /// The . Null if the score on the database cannot be found. + /// The . Null if the score cannot be found in the database. /// /// The is re-retrieved from the database to ensure all the required data /// for exporting a replay are present (may have missing properties if it was retrieved from online data). From 35eddf35c5651b883a65e955a750ddd2c05b47be Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 22 Apr 2024 11:22:39 -0700 Subject: [PATCH 3711/4092] Return `Task.CompletedTask` instead of `null` --- osu.Game/Scoring/ScoreManager.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index f37ee2b101..1ba5c7d4cf 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -201,16 +201,16 @@ namespace osu.Game.Scoring /// Export a replay from a given . /// /// The to export. - /// The . Null if the score cannot be found in the database. + /// The . Return if the score cannot be found in the database. /// /// The is re-retrieved from the database to ensure all the required data /// for exporting a replay are present (may have missing properties if it was retrieved from online data). /// - public Task? Export(ScoreInfo scoreInfo) + public Task Export(ScoreInfo scoreInfo) { ScoreInfo? databasedScoreInfo = getDatabasedScoreInfo(scoreInfo); - return databasedScoreInfo == null ? null : scoreExporter.ExportAsync(databasedScoreInfo.ToLive(Realm)); + return databasedScoreInfo == null ? Task.CompletedTask : scoreExporter.ExportAsync(databasedScoreInfo.ToLive(Realm)); } public Task?> ImportAsUpdate(ProgressNotification notification, ImportTask task, ScoreInfo original) => scoreImporter.ImportAsUpdate(notification, task, original); From 49154c0e231c25f78c849ecf7180b4240b9e0145 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 22 Apr 2024 11:23:38 -0700 Subject: [PATCH 3712/4092] Fix code quality --- osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs | 2 +- .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 2 +- osu.Game/Screens/Play/Player.cs | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs index 6590339311..004d1de116 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs @@ -170,7 +170,7 @@ namespace osu.Game.Tests.Visual.Navigation BeatmapInfo = beatmap.Beatmaps.First(), Ruleset = ruleset ?? new OsuRuleset().RulesetInfo, User = new GuestUser(), - }).Value; + })!.Value; }); AddAssert($"import {i} succeeded", () => imported != null); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 529874b71e..e2fe10fa74 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -104,7 +104,7 @@ namespace osu.Game.Tests.Visual.UserInterface Files = { new RealmNamedFileUsage(new RealmFile { Hash = $"{i}" }, string.Empty) } }; - importedScores.Add(scoreManager.Import(score).Value); + importedScores.Add(scoreManager.Import(score)!.Value); } }); }); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 4fcc52bc5d..3a80caf259 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1200,6 +1200,7 @@ namespace osu.Game.Screens.Play var importableScore = score.ScoreInfo.DeepClone(); var imported = scoreManager.Import(importableScore, replayReader); + Debug.Assert(imported != null); imported.PerformRead(s => { From beee76d64ab5b7604787603a2c9c8f559910d604 Mon Sep 17 00:00:00 2001 From: DavidBeh <67109172+DavidBeh@users.noreply.github.com> Date: Mon, 22 Apr 2024 20:25:43 +0200 Subject: [PATCH 3713/4092] enabled and fixed judgements for the magnetised mod --- osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs | 13 ++++++++++--- osu.Game.Rulesets.Osu/Objects/Slider.cs | 9 +++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs index b49fb931d1..860f96965a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs @@ -13,7 +13,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; using osuTK; @@ -37,12 +36,16 @@ namespace osu.Game.Rulesets.Osu.Mods MaxValue = 1.0f, }; + // Bindable Setting for Show Judgements + [SettingSource("Show Judgements", "Whether to show judgements or not.")] + public BindableBool ShowJudgements { get; } = new BindableBool(true); + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { // Hide judgment displays and follow points as they won't make any sense. // Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart. - drawableRuleset.Playfield.DisplayJudgements.Value = false; - (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); + drawableRuleset.Playfield.DisplayJudgements.Value = ShowJudgements.Value; + //(drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); } public void Update(Playfield playfield) @@ -78,6 +81,10 @@ namespace osu.Game.Rulesets.Osu.Mods float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, clock.ElapsedFrameTime); float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, clock.ElapsedFrameTime); + // I added these two lines + if (hitObject is DrawableOsuHitObject h) + h.HitObject.Position = new Vector2(x, y); + hitObject.Position = new Vector2(x, y); } } diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index cc3ffd376e..2660933a70 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -8,6 +8,7 @@ using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; using osu.Game.Rulesets.Objects; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; using Newtonsoft.Json; using osu.Framework.Bindables; @@ -24,6 +25,8 @@ namespace osu.Game.Rulesets.Osu.Objects { public class Slider : OsuHitObject, IHasPathWithRepeats, IHasSliderVelocity, IHasGenerateTicks { + private static readonly ConditionalWeakTable> SliderProgress = new ConditionalWeakTable>(); + public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; [JsonIgnore] @@ -201,6 +204,7 @@ namespace osu.Game.Rulesets.Osu.Objects Position = Position + Path.PositionAt(e.PathProgress), StackHeight = StackHeight, }); + SliderProgress.AddOrUpdate(NestedHitObjects.Last(), new StrongBox(e.PathProgress)); break; case SliderEventType.Head: @@ -232,6 +236,7 @@ namespace osu.Game.Rulesets.Osu.Objects Position = Position + Path.PositionAt(e.PathProgress), StackHeight = StackHeight, }); + SliderProgress.Add(NestedHitObjects.Last(), new StrongBox(e.PathProgress)); break; } } @@ -248,6 +253,10 @@ namespace osu.Game.Rulesets.Osu.Objects if (TailCircle != null) TailCircle.Position = EndPosition; + + foreach (var hitObject in NestedHitObjects) + if (hitObject is SliderTick or SliderRepeat) + ((OsuHitObject)hitObject).Position = Position + Path.PositionAt(SliderProgress.TryGetValue(hitObject, out var progress) ? progress?.Value ?? 0 : 0); } protected void UpdateNestedSamples() From 50afd48812aaf7e4deb437855202836280c93339 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Apr 2024 23:04:56 +0800 Subject: [PATCH 3714/4092] Add manual test coverage of storyboard videos --- .../TestSceneDrawableStoryboardSprite.cs | 98 ++++++++++++++----- 1 file changed, 75 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs index 32693c2bb2..6ac112cc5f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs @@ -10,6 +10,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.IO.Stores; using osu.Framework.Testing; @@ -40,7 +41,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("disallow all lookups", () => { storyboard.UseSkinSprites = false; - storyboard.AlwaysProvideTexture = false; + storyboard.ProvideResources = false; }); AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); @@ -55,7 +56,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("allow storyboard lookup", () => { storyboard.UseSkinSprites = false; - storyboard.AlwaysProvideTexture = true; + storyboard.ProvideResources = true; }); AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); @@ -67,13 +68,47 @@ namespace osu.Game.Tests.Visual.Gameplay assertStoryboardSourced(); } + [TestCase(false)] + [TestCase(true)] + public void TestVideo(bool scaleTransformProvided) + { + AddStep("allow storyboard lookup", () => + { + storyboard.ProvideResources = true; + }); + + AddStep("create video", () => SetContents(_ => + { + var layer = storyboard.GetLayer("Video"); + + var sprite = new StoryboardVideo("Videos/test-video.mp4", Time.Current); + + sprite.AddLoop(Time.Current, 100).Alpha.Add(Easing.None, 0, 10000, 1, 1); + + if (scaleTransformProvided) + sprite.TimelineGroup.Scale.Add(Easing.None, Time.Current, Time.Current, 1, 1); + + layer.Elements.Clear(); + layer.Add(sprite); + + return new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + storyboard.CreateDrawable() + } + }; + })); + } + [Test] public void TestSkinLookupPreferredOverStoryboard() { AddStep("allow all lookups", () => { storyboard.UseSkinSprites = true; - storyboard.AlwaysProvideTexture = true; + storyboard.ProvideResources = true; }); AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); @@ -91,7 +126,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("allow skin lookup", () => { storyboard.UseSkinSprites = true; - storyboard.AlwaysProvideTexture = false; + storyboard.ProvideResources = false; }); AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); @@ -109,7 +144,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("allow all lookups", () => { storyboard.UseSkinSprites = true; - storyboard.AlwaysProvideTexture = true; + storyboard.ProvideResources = true; }); AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); @@ -127,7 +162,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("allow all lookups", () => { storyboard.UseSkinSprites = true; - storyboard.AlwaysProvideTexture = true; + storyboard.ProvideResources = true; }); AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); @@ -142,7 +177,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("allow all lookups", () => { storyboard.UseSkinSprites = true; - storyboard.AlwaysProvideTexture = true; + storyboard.ProvideResources = true; }); AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); @@ -156,7 +191,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("allow all lookups", () => { storyboard.UseSkinSprites = true; - storyboard.AlwaysProvideTexture = true; + storyboard.ProvideResources = true; }); AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); @@ -170,7 +205,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("origin back", () => sprites.All(s => s.Origin == Anchor.TopLeft)); } - private DrawableStoryboard createSprite(string lookupName, Anchor origin, Vector2 initialPosition) + private Drawable createSprite(string lookupName, Anchor origin, Vector2 initialPosition) { var layer = storyboard.GetLayer("Background"); @@ -180,7 +215,14 @@ namespace osu.Game.Tests.Visual.Gameplay layer.Elements.Clear(); layer.Add(sprite); - return storyboard.CreateDrawable().With(s => s.RelativeSizeAxes = Axes.Both); + return new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + storyboard.CreateDrawable() + } + }; } private void assertStoryboardSourced() @@ -202,42 +244,52 @@ namespace osu.Game.Tests.Visual.Gameplay return new TestDrawableStoryboard(this, mods); } - public bool AlwaysProvideTexture { get; set; } + public bool ProvideResources { get; set; } - public override string GetStoragePathFromStoryboardPath(string path) => AlwaysProvideTexture ? path : string.Empty; + public override string GetStoragePathFromStoryboardPath(string path) => ProvideResources ? path : string.Empty; private partial class TestDrawableStoryboard : DrawableStoryboard { - private readonly bool alwaysProvideTexture; + private readonly bool provideResources; public TestDrawableStoryboard(TestStoryboard storyboard, IReadOnlyList? mods) : base(storyboard, mods) { - alwaysProvideTexture = storyboard.AlwaysProvideTexture; + provideResources = storyboard.ProvideResources; } - protected override IResourceStore CreateResourceLookupStore() => alwaysProvideTexture - ? new AlwaysReturnsTextureStore() + protected override IResourceStore CreateResourceLookupStore() => provideResources + ? new ResourcesTextureStore() : new ResourceStore(); - internal class AlwaysReturnsTextureStore : IResourceStore + internal class ResourcesTextureStore : IResourceStore { - private const string test_image = "Resources/Textures/test-image.png"; - private readonly DllResourceStore store; - public AlwaysReturnsTextureStore() + public ResourcesTextureStore() { store = TestResources.GetStore(); } public void Dispose() => store.Dispose(); - public byte[] Get(string name) => store.Get(test_image); + public byte[] Get(string name) => store.Get(map(name)); - public Task GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) => store.GetAsync(test_image, cancellationToken); + public Task GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) => store.GetAsync(map(name), cancellationToken); - public Stream GetStream(string name) => store.GetStream(test_image); + public Stream GetStream(string name) => store.GetStream(map(name)); + + private string map(string name) + { + switch (name) + { + case lookup_name: + return "Resources/Textures/test-image.png"; + + default: + return $"Resources/{name}"; + } + } public IEnumerable GetAvailableResources() => store.GetAvailableResources(); } From 7eeac0f3b90d34d71a8e2d7390b4a0310b89288b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Apr 2024 17:34:52 +0800 Subject: [PATCH 3715/4092] Fix incorrect xmldoc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs index ca2c7b774c..848699a4d2 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs @@ -21,7 +21,7 @@ namespace osu.Game.Storyboards.Drawables { Video = video; - // In osu-stable, a mapper can add a scale command for a storyboard. + // In osu-stable, a mapper can add a scale command for a storyboard video. // This allows scaling based on the video's absolute size. // // If not specified we take up the full available space. From 17ca29c2c6e0e5f141f5393923a511cfa2d61437 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Apr 2024 17:37:37 +0800 Subject: [PATCH 3716/4092] Actually apply transforms to the video --- osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs index 848699a4d2..f2454be190 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs @@ -50,6 +50,8 @@ namespace osu.Game.Storyboards.Drawables Origin = Anchor.Centre, Alpha = 0, }; + + Video.ApplyTransforms(drawableVideo); } protected override void LoadComplete() From a978518a74c362016d2aeb39d4a8f1fdcb78a28d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 23 Apr 2024 12:32:52 +0200 Subject: [PATCH 3717/4092] Add failing tests --- .../Settings/TestSceneKeyBindingPanel.cs | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs index 57c9770c9a..86008a56a4 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs @@ -296,7 +296,7 @@ namespace osu.Game.Tests.Visual.Settings } [Test] - public void TestBindingConflictResolvedByRollback() + public void TestBindingConflictResolvedByRollbackViaMouse() { AddStep("reset taiko section to default", () => { @@ -315,7 +315,7 @@ namespace osu.Game.Tests.Visual.Settings } [Test] - public void TestBindingConflictResolvedByOverwrite() + public void TestBindingConflictResolvedByOverwriteViaMouse() { AddStep("reset taiko section to default", () => { @@ -333,6 +333,46 @@ namespace osu.Game.Tests.Visual.Settings checkBinding("Left (rim)", "M1"); } + [Test] + public void TestBindingConflictResolvedByRollbackViaKeyboard() + { + AddStep("reset taiko & global sections to default", () => + { + panel.ChildrenOfType().First(section => new TaikoRuleset().RulesetInfo.Equals(section.Ruleset)) + .ChildrenOfType().Single().TriggerClick(); + + panel.ChildrenOfType().First().TriggerClick(); + }); + AddStep("move mouse to centre", () => InputManager.MoveMouseTo(panel.ScreenSpaceDrawQuad.Centre)); + scrollToAndStartBinding("Left (rim)"); + AddStep("attempt to bind M1 to two keys", () => InputManager.Click(MouseButton.Left)); + + AddUntilStep("wait for popover", () => panel.ChildrenOfType().SingleOrDefault(), () => Is.Not.Null); + AddStep("press Esc", () => InputManager.Key(Key.Escape)); + checkBinding("Left (centre)", "M1"); + checkBinding("Left (rim)", "M2"); + } + + [Test] + public void TestBindingConflictResolvedByOverwriteViaKeyboard() + { + AddStep("reset taiko & global sections to default", () => + { + panel.ChildrenOfType().First(section => new TaikoRuleset().RulesetInfo.Equals(section.Ruleset)) + .ChildrenOfType().Single().TriggerClick(); + + panel.ChildrenOfType().First().TriggerClick(); + }); + AddStep("move mouse to centre", () => InputManager.MoveMouseTo(panel.ScreenSpaceDrawQuad.Centre)); + scrollToAndStartBinding("Left (rim)"); + AddStep("attempt to bind M1 to two keys", () => InputManager.Click(MouseButton.Left)); + + AddUntilStep("wait for popover", () => panel.ChildrenOfType().SingleOrDefault(), () => Is.Not.Null); + AddStep("press Enter", () => InputManager.Key(Key.Enter)); + checkBinding("Left (centre)", InputSettingsStrings.ActionHasNoKeyBinding.ToString()); + checkBinding("Left (rim)", "M1"); + } + [Test] public void TestBindingConflictCausedByResetToDefaultOfSingleRow() { From 1e0db1ab9f676ffb781afe7d151c2d6d95126329 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 23 Apr 2024 12:44:16 +0200 Subject: [PATCH 3718/4092] Allow confirming keybinding overwrite on conflict via "select" binding --- .../Sections/Input/KeyBindingConflictPopover.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingConflictPopover.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingConflictPopover.cs index 60d1bd31be..05aeb0b810 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingConflictPopover.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingConflictPopover.cs @@ -152,6 +152,17 @@ namespace osu.Game.Overlays.Settings.Sections.Input newPreview.IsChosen.Value = applyNewButton.IsHovered; } + public override bool OnPressed(KeyBindingPressEvent e) + { + if (e.Action == GlobalAction.Select && !e.Repeat) + { + applyNew(); + return true; + } + + return base.OnPressed(e); + } + private partial class ConflictingKeyBindingPreview : CompositeDrawable { private readonly object action; From 564dec7a142b8fe3bf6729b9223bc490df18b5c7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Apr 2024 19:21:55 +0800 Subject: [PATCH 3719/4092] Add test coverage of transforms actually being applied to video --- .../Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs index 6ac112cc5f..fc52d749b3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs @@ -86,7 +86,10 @@ namespace osu.Game.Tests.Visual.Gameplay sprite.AddLoop(Time.Current, 100).Alpha.Add(Easing.None, 0, 10000, 1, 1); if (scaleTransformProvided) - sprite.TimelineGroup.Scale.Add(Easing.None, Time.Current, Time.Current, 1, 1); + { + sprite.TimelineGroup.Scale.Add(Easing.None, Time.Current, Time.Current + 1000, 1, 2); + sprite.TimelineGroup.Scale.Add(Easing.None, Time.Current + 1000, Time.Current + 2000, 2, 1); + } layer.Elements.Clear(); layer.Add(sprite); From f7626aba1821f5f346dfa36a06438836bbc819ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 23 Apr 2024 13:36:12 +0200 Subject: [PATCH 3720/4092] Fix mod select overlay columns not displaying properly sometimes Closes https://github.com/ppy/osu/issues/26504. As far as I can tell the issue is basically another manifestation of https://github.com/ppy/osu-framework/issues/5129, i.e. presence overrides causing dropped invalidations and thus completely bogus hierarchy state. The fix is to raise the appropriate invalidation manually. --- osu.Game/Overlays/Mods/ModColumn.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index df33c78ea4..e9f21338bd 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -69,6 +69,7 @@ namespace osu.Game.Overlays.Mods private Task? latestLoadTask; private ModPanel[]? latestLoadedPanels; internal bool ItemsLoaded => latestLoadTask?.IsCompleted == true && allPanelsLoaded; + private bool? wasPresent; private bool allPanelsLoaded { @@ -192,6 +193,15 @@ namespace osu.Game.Overlays.Mods { base.Update(); + // we override `IsPresent` to include the scheduler's pending task state to make async loads work correctly when columns are masked away + // (see description of https://github.com/ppy/osu/pull/19783). + // however, because of that we must also ensure that we signal correct invalidations (https://github.com/ppy/osu-framework/issues/5129). + // failing to do so causes columns to be stuck in "present" mode despite actually not being present themselves. + // this works because `Update()` will always run after a scheduler update, which is what causes the presence state change responsible for the failure. + if (wasPresent != null && wasPresent != IsPresent) + Invalidate(Invalidation.Presence); + wasPresent = IsPresent; + if (selectionDelay == initial_multiple_selection_delay || Time.Current - lastSelection >= selectionDelay) { if (pendingSelectionOperations.TryDequeue(out var dequeuedAction)) From 804b1b0d884da13b1a79a31ffd09e23a16bffbe8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Apr 2024 20:54:44 +0800 Subject: [PATCH 3721/4092] Fix settings colour scheme wrong when viewing gameplay from skin editor button Closes https://github.com/ppy/osu/issues/27949. --- osu.Game/Screens/Play/Player.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 3a80caf259..42ff1d74f3 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -101,6 +101,11 @@ namespace osu.Game.Screens.Play /// public IBindable ShowingOverlayComponents = new Bindable(); + // Should match PlayerLoader for consistency. Cached here for the rare case we push a Player + // without the loading screen (one such usage is the skin editor's scene library). + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); + [Resolved] private ScoreManager scoreManager { get; set; } From 436203a8c12093c076a9d4e5d01264ad9ce26f55 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Apr 2024 21:32:32 +0800 Subject: [PATCH 3722/4092] Add a chevron to distinguish editor menus with submenus --- .../Edit/Components/Menus/EditorMenuBar.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs index 0e125d0ec0..152bcee214 100644 --- a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs +++ b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.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 System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -184,6 +185,17 @@ namespace osu.Game.Screens.Edit.Components.Menus { } + private bool hasSubmenu => Item.Items.Any(); + + protected override TextContainer CreateTextContainer() => base.CreateTextContainer().With(c => + { + c.Padding = new MarginPadding + { + // Add some padding for the chevron below. + Right = hasSubmenu ? 5 : 0, + }; + }); + [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { @@ -191,6 +203,18 @@ namespace osu.Game.Screens.Edit.Components.Menus BackgroundColourHover = colourProvider.Background1; Foreground.Padding = new MarginPadding { Vertical = 2 }; + + if (hasSubmenu) + { + AddInternal(new SpriteIcon + { + Margin = new MarginPadding(6), + Size = new Vector2(8), + Icon = FontAwesome.Solid.ChevronRight, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + }); + } } } } From 602b16f533dd5e578025144d025dc3a9c7336fa7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Apr 2024 22:03:32 +0800 Subject: [PATCH 3723/4092] Fix fade-in no longer working on videos --- .../Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs | 2 -- osu.Game/Storyboards/StoryboardVideo.cs | 4 +++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs index fc52d749b3..8fa2c9922e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs @@ -83,8 +83,6 @@ namespace osu.Game.Tests.Visual.Gameplay var sprite = new StoryboardVideo("Videos/test-video.mp4", Time.Current); - sprite.AddLoop(Time.Current, 100).Alpha.Add(Easing.None, 0, 10000, 1, 1); - if (scaleTransformProvided) { sprite.TimelineGroup.Scale.Add(Easing.None, Time.Current, Time.Current + 1000, 1, 2); diff --git a/osu.Game/Storyboards/StoryboardVideo.cs b/osu.Game/Storyboards/StoryboardVideo.cs index bd1b933b6f..5573162d26 100644 --- a/osu.Game/Storyboards/StoryboardVideo.cs +++ b/osu.Game/Storyboards/StoryboardVideo.cs @@ -12,7 +12,9 @@ namespace osu.Game.Storyboards public StoryboardVideo(string path, double offset) : base(path, Anchor.Centre, Vector2.Zero) { - TimelineGroup.Alpha.Add(Easing.None, offset, offset, 0, 1); + // This is just required to get a valid StartTime based on the incoming offset. + // Actual fades are handled inside DrawableStoryboardVideo for now. + TimelineGroup.Alpha.Add(Easing.None, offset, offset, 0, 0); } public override Drawable CreateDrawable() => new DrawableStoryboardVideo(this); From d0edf72a0c384004f7e6fbb252d368af3fa14cdf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Apr 2024 22:04:28 +0800 Subject: [PATCH 3724/4092] 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 c61977cfa3..97dfe5d9f7 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 6389172fe7..66347acdf0 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From 787e60f70649d5346c1916332ff74cab0518f959 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 23 Apr 2024 18:37:15 +0200 Subject: [PATCH 3725/4092] Fix `LegacyDrainingHealthProcessor` drain rate computation diverging to infinity --- osu.Game/Rulesets/Scoring/LegacyDrainingHealthProcessor.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Rulesets/Scoring/LegacyDrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/LegacyDrainingHealthProcessor.cs index 7cee5ebecf..25c5b3643a 100644 --- a/osu.Game/Rulesets/Scoring/LegacyDrainingHealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/LegacyDrainingHealthProcessor.cs @@ -129,6 +129,13 @@ namespace osu.Game.Rulesets.Scoring OnIterationFail?.Invoke($"FAILED drop {testDrop}: recovery too low ({recovery} < {hpRecoveryAvailable})"); } + if (!fail && double.IsInfinity(HpMultiplierNormal)) + { + OnIterationSuccess?.Invoke("Drain computation algorithm diverged to infinity. PASSING with zero drop, resetting HP multiplier to 1."); + HpMultiplierNormal = 1; + return 0; + } + if (!fail) { OnIterationSuccess?.Invoke($"PASSED drop {testDrop}"); From cbbf2dd1584da084293db3afe4bd4e6108e5f7e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 23 Apr 2024 18:58:40 +0200 Subject: [PATCH 3726/4092] Fix `DifficultyBindable` bound desync between `maxValue` and `CurrentNumber.MaxValue` --- osu.Game/Rulesets/Mods/DifficultyBindable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/DifficultyBindable.cs b/osu.Game/Rulesets/Mods/DifficultyBindable.cs index a207048882..5f6fd21860 100644 --- a/osu.Game/Rulesets/Mods/DifficultyBindable.cs +++ b/osu.Game/Rulesets/Mods/DifficultyBindable.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Mods } } - private float maxValue; + private float maxValue = 10; // matches default max value of `CurrentNumber` public float MaxValue { From 331f1f31b08c508d4d51008a402df2b9d6b46238 Mon Sep 17 00:00:00 2001 From: DavidBeh <67109172+DavidBeh@users.noreply.github.com> Date: Tue, 23 Apr 2024 19:19:11 +0200 Subject: [PATCH 3727/4092] Attempt to position DrawableOsuJudgement based on its DrawableOsuHitObject instead of DrawableOsuHitObject.HitObject --- osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs | 4 ++-- .../Objects/Drawables/DrawableOsuJudgement.cs | 9 ++++++--- .../Objects/Drawables/DrawableSliderTick.cs | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs index 860f96965a..f0ad284019 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs @@ -82,9 +82,9 @@ namespace osu.Game.Rulesets.Osu.Mods float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, clock.ElapsedFrameTime); // I added these two lines - if (hitObject is DrawableOsuHitObject h) + /*if (hitObject is DrawableOsuHitObject h) h.HitObject.Position = new Vector2(x, y); - +*/ hitObject.Position = new Vector2(x, y); } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 76ae7340ff..0960748320 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -39,10 +39,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Lighting.ResetAnimation(); Lighting.SetColourFrom(JudgedObject, Result); - if (JudgedObject?.HitObject is OsuHitObject osuObject) + if (JudgedObject is DrawableOsuHitObject osuObject) { - Position = osuObject.StackedEndPosition; - Scale = new Vector2(osuObject.Scale); + Position = osuObject.ToSpaceOfOtherDrawable(Vector2.Zero, Parent); + // Works only for normal hit circles, also with magnetised: + // Position = osuObject.Position; + + Scale = new Vector2(osuObject.HitObject.Scale); } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index 73c061afbd..0c7ba180f2 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public const float DEFAULT_TICK_SIZE = 16; - protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject; + public DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject; private SkinnableDrawable scaleContainer; From 3a914b9337ce15cff315162431fef5769903ba8e Mon Sep 17 00:00:00 2001 From: DavidBeh <67109172+DavidBeh@users.noreply.github.com> Date: Tue, 23 Apr 2024 23:24:51 +0200 Subject: [PATCH 3728/4092] Fixed judgements with MG mod without causing side effects --- osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs | 4 ---- .../Objects/Drawables/DrawableOsuJudgement.cs | 5 +---- osu.Game.Rulesets.Osu/Objects/Slider.cs | 8 +------- 3 files changed, 2 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs index f0ad284019..c64b5a18bc 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs @@ -81,10 +81,6 @@ namespace osu.Game.Rulesets.Osu.Mods float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, clock.ElapsedFrameTime); float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, clock.ElapsedFrameTime); - // I added these two lines - /*if (hitObject is DrawableOsuHitObject h) - h.HitObject.Position = new Vector2(x, y); -*/ hitObject.Position = new Vector2(x, y); } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 0960748320..d0270c68f6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -41,10 +41,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (JudgedObject is DrawableOsuHitObject osuObject) { - Position = osuObject.ToSpaceOfOtherDrawable(Vector2.Zero, Parent); - // Works only for normal hit circles, also with magnetised: - // Position = osuObject.Position; - + Position = osuObject.ToSpaceOfOtherDrawable(osuObject.OriginPosition, Parent!); Scale = new Vector2(osuObject.HitObject.Scale); } } diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 2660933a70..5b52996e22 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -8,7 +8,6 @@ using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; using osu.Game.Rulesets.Objects; using System.Linq; -using System.Runtime.CompilerServices; using System.Threading; using Newtonsoft.Json; using osu.Framework.Bindables; @@ -25,7 +24,6 @@ namespace osu.Game.Rulesets.Osu.Objects { public class Slider : OsuHitObject, IHasPathWithRepeats, IHasSliderVelocity, IHasGenerateTicks { - private static readonly ConditionalWeakTable> SliderProgress = new ConditionalWeakTable>(); public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; @@ -204,7 +202,6 @@ namespace osu.Game.Rulesets.Osu.Objects Position = Position + Path.PositionAt(e.PathProgress), StackHeight = StackHeight, }); - SliderProgress.AddOrUpdate(NestedHitObjects.Last(), new StrongBox(e.PathProgress)); break; case SliderEventType.Head: @@ -236,7 +233,6 @@ namespace osu.Game.Rulesets.Osu.Objects Position = Position + Path.PositionAt(e.PathProgress), StackHeight = StackHeight, }); - SliderProgress.Add(NestedHitObjects.Last(), new StrongBox(e.PathProgress)); break; } } @@ -254,9 +250,7 @@ namespace osu.Game.Rulesets.Osu.Objects if (TailCircle != null) TailCircle.Position = EndPosition; - foreach (var hitObject in NestedHitObjects) - if (hitObject is SliderTick or SliderRepeat) - ((OsuHitObject)hitObject).Position = Position + Path.PositionAt(SliderProgress.TryGetValue(hitObject, out var progress) ? progress?.Value ?? 0 : 0); + // Positions of other nested hitobjects are not updated } protected void UpdateNestedSamples() From f863ea30e10fce9dce2d388a0e8b0f7f7532c9f1 Mon Sep 17 00:00:00 2001 From: DavidBeh <67109172+DavidBeh@users.noreply.github.com> Date: Tue, 23 Apr 2024 23:31:23 +0200 Subject: [PATCH 3729/4092] Made judgements always on and disabled follow paths again --- osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs index c64b5a18bc..97ec669703 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; using osuTK; @@ -36,16 +37,11 @@ namespace osu.Game.Rulesets.Osu.Mods MaxValue = 1.0f, }; - // Bindable Setting for Show Judgements - [SettingSource("Show Judgements", "Whether to show judgements or not.")] - public BindableBool ShowJudgements { get; } = new BindableBool(true); - public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { // Hide judgment displays and follow points as they won't make any sense. // Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart. - drawableRuleset.Playfield.DisplayJudgements.Value = ShowJudgements.Value; - //(drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); + (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); } public void Update(Playfield playfield) From 7dac5afd90bf3fa7194e46c20c9b10e3fe7450e9 Mon Sep 17 00:00:00 2001 From: DavidBeh <67109172+DavidBeh@users.noreply.github.com> Date: Tue, 23 Apr 2024 23:57:27 +0200 Subject: [PATCH 3730/4092] Enabled judgements for repel (but not in depth). Updated comments in repel, mag, depth --- osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs | 6 ++++-- osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 3 +-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs index a9111eec1f..6d4a621f4d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs @@ -47,10 +47,12 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - // Hide judgment displays and follow points as they won't make any sense. + // Hide follow points as they won't make any sense. // Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart. - drawableRuleset.Playfield.DisplayJudgements.Value = false; (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); + // Hide judgements as they don't move with the drawables after appearing, which does look bad. + // They would need to either move with them or disappear sooner. + drawableRuleset.Playfield.DisplayJudgements.Value = false; } private void applyTransform(DrawableHitObject drawable, ArmedState state) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs index 97ec669703..b2553e295c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - // Hide judgment displays and follow points as they won't make any sense. + // Hide follow points as they won't make any sense. // Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart. (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index ced98f0cd5..302e17432e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -38,9 +38,8 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - // Hide judgment displays and follow points as they won't make any sense. + // Hide follow points as they won't make any sense. // Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart. - drawableRuleset.Playfield.DisplayJudgements.Value = false; (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); } From 16190a4ed7efe012de82aa9cf2b25925556e4a3c Mon Sep 17 00:00:00 2001 From: DavidBeh <67109172+DavidBeh@users.noreply.github.com> Date: Wed, 24 Apr 2024 00:23:45 +0200 Subject: [PATCH 3731/4092] Made judgements follow DrawableOsuHitObjects. Enabled judgements for depth mod --- osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs | 3 --- .../Objects/Drawables/DrawableOsuJudgement.cs | 11 +++++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs index 6d4a621f4d..306dcee839 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs @@ -50,9 +50,6 @@ namespace osu.Game.Rulesets.Osu.Mods // Hide follow points as they won't make any sense. // Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart. (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); - // Hide judgements as they don't move with the drawables after appearing, which does look bad. - // They would need to either move with them or disappear sooner. - drawableRuleset.Playfield.DisplayJudgements.Value = false; } private void applyTransform(DrawableHitObject drawable, ArmedState state) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index d0270c68f6..64bf25cceb 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -46,6 +46,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } + + protected override void Update() + { + base.Update(); + if (JudgedObject is DrawableOsuHitObject osuObject && Parent != null && osuObject.HitObject != null) + { + Position = osuObject.ToSpaceOfOtherDrawable(osuObject.OriginPosition, Parent!); + Scale = new Vector2(osuObject.HitObject.Scale); + } + } + protected override void ApplyHitAnimations() { bool hitLightingEnabled = config.Get(OsuSetting.HitLighting); From 16fdd4e08d81cfc00ddc1a6d370fdece195c3461 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Apr 2024 09:01:31 +0800 Subject: [PATCH 3732/4092] Add ability to show beatmap source using skin editor's beatmap attribute text As per https://github.com/ppy/osu/discussions/27955. --- osu.Game/Localisation/EditorSetupStrings.cs | 32 +++++++++++++------ .../Components/BeatmapAttributeText.cs | 3 ++ 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/osu.Game/Localisation/EditorSetupStrings.cs b/osu.Game/Localisation/EditorSetupStrings.cs index eff6f9e6b8..350517734f 100644 --- a/osu.Game/Localisation/EditorSetupStrings.cs +++ b/osu.Game/Localisation/EditorSetupStrings.cs @@ -42,7 +42,8 @@ namespace osu.Game.Localisation /// /// "If enabled, an "Are you ready? 3, 2, 1, GO!" countdown will be inserted at the beginning of the beatmap, assuming there is enough time to do so." /// - public static LocalisableString CountdownDescription => new TranslatableString(getKey(@"countdown_description"), @"If enabled, an ""Are you ready? 3, 2, 1, GO!"" countdown will be inserted at the beginning of the beatmap, assuming there is enough time to do so."); + public static LocalisableString CountdownDescription => new TranslatableString(getKey(@"countdown_description"), + @"If enabled, an ""Are you ready? 3, 2, 1, GO!"" countdown will be inserted at the beginning of the beatmap, assuming there is enough time to do so."); /// /// "Countdown speed" @@ -52,7 +53,8 @@ namespace osu.Game.Localisation /// /// "If the countdown sounds off-time, use this to make it appear one or more beats early." /// - public static LocalisableString CountdownOffsetDescription => new TranslatableString(getKey(@"countdown_offset_description"), @"If the countdown sounds off-time, use this to make it appear one or more beats early."); + public static LocalisableString CountdownOffsetDescription => + new TranslatableString(getKey(@"countdown_offset_description"), @"If the countdown sounds off-time, use this to make it appear one or more beats early."); /// /// "Countdown offset" @@ -67,7 +69,8 @@ namespace osu.Game.Localisation /// /// "Allows storyboards to use the full screen space, rather than be confined to a 4:3 area." /// - public static LocalisableString WidescreenSupportDescription => new TranslatableString(getKey(@"widescreen_support_description"), @"Allows storyboards to use the full screen space, rather than be confined to a 4:3 area."); + public static LocalisableString WidescreenSupportDescription => + new TranslatableString(getKey(@"widescreen_support_description"), @"Allows storyboards to use the full screen space, rather than be confined to a 4:3 area."); /// /// "Epilepsy warning" @@ -77,7 +80,8 @@ namespace osu.Game.Localisation /// /// "Recommended if the storyboard or video contain scenes with rapidly flashing colours." /// - public static LocalisableString EpilepsyWarningDescription => new TranslatableString(getKey(@"epilepsy_warning_description"), @"Recommended if the storyboard or video contain scenes with rapidly flashing colours."); + public static LocalisableString EpilepsyWarningDescription => + new TranslatableString(getKey(@"epilepsy_warning_description"), @"Recommended if the storyboard or video contain scenes with rapidly flashing colours."); /// /// "Letterbox during breaks" @@ -87,7 +91,8 @@ namespace osu.Game.Localisation /// /// "Adds horizontal letterboxing to give a cinematic look during breaks." /// - public static LocalisableString LetterboxDuringBreaksDescription => new TranslatableString(getKey(@"letterbox_during_breaks_description"), @"Adds horizontal letterboxing to give a cinematic look during breaks."); + public static LocalisableString LetterboxDuringBreaksDescription => + new TranslatableString(getKey(@"letterbox_during_breaks_description"), @"Adds horizontal letterboxing to give a cinematic look during breaks."); /// /// "Samples match playback rate" @@ -97,7 +102,8 @@ namespace osu.Game.Localisation /// /// "When enabled, all samples will speed up or slow down when rate-changing mods are enabled." /// - public static LocalisableString SamplesMatchPlaybackRateDescription => new TranslatableString(getKey(@"samples_match_playback_rate_description"), @"When enabled, all samples will speed up or slow down when rate-changing mods are enabled."); + public static LocalisableString SamplesMatchPlaybackRateDescription => new TranslatableString(getKey(@"samples_match_playback_rate_description"), + @"When enabled, all samples will speed up or slow down when rate-changing mods are enabled."); /// /// "The size of all hit objects" @@ -117,7 +123,8 @@ namespace osu.Game.Localisation /// /// "The harshness of hit windows and difficulty of special objects (ie. spinners)" /// - public static LocalisableString OverallDifficultyDescription => new TranslatableString(getKey(@"overall_difficulty_description"), @"The harshness of hit windows and difficulty of special objects (ie. spinners)"); + public static LocalisableString OverallDifficultyDescription => + new TranslatableString(getKey(@"overall_difficulty_description"), @"The harshness of hit windows and difficulty of special objects (ie. spinners)"); /// /// "Tick Rate" @@ -127,7 +134,8 @@ namespace osu.Game.Localisation /// /// "Determines how many "ticks" are generated within long hit objects. A tick rate of 1 will generate ticks on each beat, 2 would be twice per beat, etc." /// - public static LocalisableString TickRateDescription => new TranslatableString(getKey(@"tick_rate_description"), @"Determines how many ""ticks"" are generated within long hit objects. A tick rate of 1 will generate ticks on each beat, 2 would be twice per beat, etc."); + public static LocalisableString TickRateDescription => new TranslatableString(getKey(@"tick_rate_description"), + @"Determines how many ""ticks"" are generated within long hit objects. A tick rate of 1 will generate ticks on each beat, 2 would be twice per beat, etc."); /// /// "Base Velocity" @@ -137,7 +145,8 @@ namespace osu.Game.Localisation /// /// "The base velocity of the beatmap, affecting things like slider velocity and scroll speed in some rulesets." /// - public static LocalisableString BaseVelocityDescription => new TranslatableString(getKey(@"base_velocity_description"), @"The base velocity of the beatmap, affecting things like slider velocity and scroll speed in some rulesets."); + public static LocalisableString BaseVelocityDescription => new TranslatableString(getKey(@"base_velocity_description"), + @"The base velocity of the beatmap, affecting things like slider velocity and scroll speed in some rulesets."); /// /// "Metadata" @@ -159,6 +168,11 @@ namespace osu.Game.Localisation /// public static LocalisableString Creator => new TranslatableString(getKey(@"creator"), @"Creator"); + /// + /// "Source" + /// + public static LocalisableString Source => new TranslatableString(getKey(@"source"), @"Source"); + /// /// "Difficulty Name" /// diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index 52c439a624..5c5e509fb2 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -48,6 +48,7 @@ namespace osu.Game.Skinning.Components [BeatmapAttribute.Artist] = EditorSetupStrings.Artist, [BeatmapAttribute.DifficultyName] = EditorSetupStrings.DifficultyHeader, [BeatmapAttribute.Creator] = EditorSetupStrings.Creator, + [BeatmapAttribute.Source] = EditorSetupStrings.Source, [BeatmapAttribute.Length] = ArtistStrings.TracklistLength.ToTitle(), [BeatmapAttribute.RankedStatus] = BeatmapDiscussionsStrings.IndexFormBeatmapsetStatusDefault, [BeatmapAttribute.BPM] = BeatmapsetsStrings.ShowStatsBpm, @@ -88,6 +89,7 @@ namespace osu.Game.Skinning.Components valueDictionary[BeatmapAttribute.Artist] = new RomanisableString(workingBeatmap.BeatmapInfo.Metadata.ArtistUnicode, workingBeatmap.BeatmapInfo.Metadata.Artist); valueDictionary[BeatmapAttribute.DifficultyName] = workingBeatmap.BeatmapInfo.DifficultyName; valueDictionary[BeatmapAttribute.Creator] = workingBeatmap.BeatmapInfo.Metadata.Author.Username; + valueDictionary[BeatmapAttribute.Source] = workingBeatmap.BeatmapInfo.Metadata.Source; valueDictionary[BeatmapAttribute.Length] = TimeSpan.FromMilliseconds(workingBeatmap.BeatmapInfo.Length).ToFormattedDuration(); valueDictionary[BeatmapAttribute.RankedStatus] = workingBeatmap.BeatmapInfo.Status.GetLocalisableDescription(); valueDictionary[BeatmapAttribute.BPM] = workingBeatmap.BeatmapInfo.BPM.ToLocalisableString(@"F2"); @@ -132,6 +134,7 @@ namespace osu.Game.Skinning.Components StarRating, Title, Artist, + Source, DifficultyName, Creator, Length, From f97c519451e0b17c95309f2d78bbddd30614e5c9 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 24 Apr 2024 00:19:10 -0700 Subject: [PATCH 3733/4092] Add chevron to distinguish all menus with submenus --- .../UserInterface/DrawableOsuMenuItem.cs | 23 +++++++++++++++++++ .../Edit/Components/Menus/EditorMenuBar.cs | 23 ------------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs index 2f2cb7e5f8..06ef75cf58 100644 --- a/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs @@ -3,6 +3,7 @@ #nullable disable +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -12,6 +13,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Graphics.Sprites; +using osuTK; using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface @@ -40,6 +42,27 @@ namespace osu.Game.Graphics.UserInterface AddInternal(hoverClickSounds = new HoverClickSounds()); updateTextColour(); + + bool hasSubmenu = Item.Items.Any(); + + // Only add right chevron if direction of menu items is vertical (i.e. width is relative size, see `DrawableMenuItem.SetFlowDirection()`). + if (hasSubmenu && RelativeSizeAxes == Axes.X) + { + AddInternal(new SpriteIcon + { + Margin = new MarginPadding(6), + Size = new Vector2(8), + Icon = FontAwesome.Solid.ChevronRight, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + }); + + text.Padding = new MarginPadding + { + // Add some padding for the chevron above. + Right = 5, + }; + } } protected override void LoadComplete() diff --git a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs index 152bcee214..c410c2519b 100644 --- a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs +++ b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs @@ -185,17 +185,6 @@ namespace osu.Game.Screens.Edit.Components.Menus { } - private bool hasSubmenu => Item.Items.Any(); - - protected override TextContainer CreateTextContainer() => base.CreateTextContainer().With(c => - { - c.Padding = new MarginPadding - { - // Add some padding for the chevron below. - Right = hasSubmenu ? 5 : 0, - }; - }); - [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { @@ -203,18 +192,6 @@ namespace osu.Game.Screens.Edit.Components.Menus BackgroundColourHover = colourProvider.Background1; Foreground.Padding = new MarginPadding { Vertical = 2 }; - - if (hasSubmenu) - { - AddInternal(new SpriteIcon - { - Margin = new MarginPadding(6), - Size = new Vector2(8), - Icon = FontAwesome.Solid.ChevronRight, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - }); - } } } } From 5f463b81a8ecc883ed8040cacf70139e11b042dd Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 24 Apr 2024 00:22:20 -0700 Subject: [PATCH 3734/4092] Remove hardcoded chevrons in test --- osu.Game.Tests/Visual/UserInterface/TestSceneContextMenu.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneContextMenu.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneContextMenu.cs index 7b80549854..2a2f267fc8 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneContextMenu.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneContextMenu.cs @@ -77,21 +77,21 @@ namespace osu.Game.Tests.Visual.UserInterface new OsuMenuItem(@"Some option"), new OsuMenuItem(@"Highlighted option", MenuItemType.Highlighted), new OsuMenuItem(@"Another option"), - new OsuMenuItem(@"Nested option >") + new OsuMenuItem(@"Nested option") { Items = new MenuItem[] { new OsuMenuItem(@"Sub-One"), new OsuMenuItem(@"Sub-Two"), new OsuMenuItem(@"Sub-Three"), - new OsuMenuItem(@"Sub-Nested option >") + new OsuMenuItem(@"Sub-Nested option") { Items = new MenuItem[] { new OsuMenuItem(@"Double Sub-One"), new OsuMenuItem(@"Double Sub-Two"), new OsuMenuItem(@"Double Sub-Three"), - new OsuMenuItem(@"Sub-Sub-Nested option >") + new OsuMenuItem(@"Sub-Sub-Nested option") { Items = new MenuItem[] { From 4f7c9f297068b5ec27aaa3d34e8176d6e0e7fe1a Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 24 Apr 2024 01:00:03 -0700 Subject: [PATCH 3735/4092] Remove unused using --- osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs index c410c2519b..0e125d0ec0 100644 --- a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs +++ b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.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.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; From 8c2a4eb78ab0b900f552f91641ba7b526d33443f Mon Sep 17 00:00:00 2001 From: DavidBeh <67109172+DavidBeh@users.noreply.github.com> Date: Wed, 24 Apr 2024 12:40:23 +0200 Subject: [PATCH 3736/4092] Fix formatting --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 64bf25cceb..ffbf45291f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -46,10 +46,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - protected override void Update() { base.Update(); + if (JudgedObject is DrawableOsuHitObject osuObject && Parent != null && osuObject.HitObject != null) { Position = osuObject.ToSpaceOfOtherDrawable(osuObject.OriginPosition, Parent!); diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 5b52996e22..248f40208a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -24,7 +24,6 @@ namespace osu.Game.Rulesets.Osu.Objects { public class Slider : OsuHitObject, IHasPathWithRepeats, IHasSliderVelocity, IHasGenerateTicks { - public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; [JsonIgnore] From 72726809cb6cdd53fb3f01d13b2438d323c47e72 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 24 Apr 2024 09:47:41 -0700 Subject: [PATCH 3737/4092] Ignore autogenerated .idea android file --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 525b3418cd..11fee27f28 100644 --- a/.gitignore +++ b/.gitignore @@ -340,4 +340,5 @@ inspectcode # Fody (pulled in by Realm) - schema file FodyWeavers.xsd -.idea/.idea.osu.Desktop/.idea/misc.xml \ No newline at end of file +.idea/.idea.osu.Desktop/.idea/misc.xml +.idea/.idea.osu.Android/.idea/deploymentTargetDropDown.xml From 94275f148e4eb523fbf4247503c7e072f66ef780 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 25 Apr 2024 09:01:47 +0200 Subject: [PATCH 3738/4092] Fix adding slider control points via context menu not undoing correctly Closes https://github.com/ppy/osu/issues/27985. --- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 2da462caf4..49fdf12d60 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -403,7 +403,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders public override MenuItem[] ContextMenuItems => new MenuItem[] { - new OsuMenuItem("Add control point", MenuItemType.Standard, () => addControlPoint(rightClickPosition)), + new OsuMenuItem("Add control point", MenuItemType.Standard, () => + { + changeHandler?.BeginChange(); + addControlPoint(rightClickPosition); + changeHandler?.EndChange(); + }), new OsuMenuItem("Convert to stream", MenuItemType.Destructive, convertToStream), }; From da953b34a721540e266b33d495ce11525bf1c8fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 25 Apr 2024 09:52:26 +0200 Subject: [PATCH 3739/4092] Apply nullability annotations to `ResultsScreen` & inheritors --- .../Visual/Ranking/TestSceneResultsScreen.cs | 2 +- .../MultiplayerTeamResultsScreen.cs | 8 ++-- .../Spectate/MultiSpectatorResultsScreen.cs | 6 +-- .../Playlists/PlaylistsResultsScreen.cs | 35 +++++++--------- osu.Game/Screens/Ranking/ResultsScreen.cs | 42 +++++++++---------- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 4 +- 6 files changed, 43 insertions(+), 54 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index ffc5dbc8fb..fca1d0f82a 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -424,7 +424,7 @@ namespace osu.Game.Tests.Visual.Ranking scores.Add(score); } - scoresCallback?.Invoke(scores); + scoresCallback.Invoke(scores); return null; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerTeamResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerTeamResultsScreen.cs index a8c513603c..ab83860ba7 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerTeamResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerTeamResultsScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -28,8 +26,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { private readonly SortedDictionary teamScores; - private Container winnerBackground; - private Drawable winnerText; + private Container winnerBackground = null!; + private Drawable winnerText = null!; public MultiplayerTeamResultsScreen(ScoreInfo score, long roomId, PlaylistItem playlistItem, SortedDictionary teamScores) : base(score, roomId, playlistItem) @@ -41,7 +39,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } [Resolved] - private OsuColour colours { get; set; } + private OsuColour colours { get; set; } = null!; [BackgroundDependencyLoader] private void load() diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorResultsScreen.cs index 2afc187e40..c240bbea0c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorResultsScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using osu.Game.Online.API; @@ -25,8 +23,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate Scheduler.AddDelayed(() => StatisticsPanel.ToggleVisibility(), 1000); } - protected override APIRequest FetchScores(Action> scoresCallback) => null; + protected override APIRequest? FetchScores(Action> scoresCallback) => null; - protected override APIRequest FetchNextPage(int direction, Action> scoresCallback) => null; + protected override APIRequest? FetchNextPage(int direction, Action> scoresCallback) => null; } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs index add7aee8cd..fdb83b5ae8 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs @@ -1,13 +1,10 @@ // 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.Diagnostics; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -25,23 +22,23 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private readonly long roomId; private readonly PlaylistItem playlistItem; - protected LoadingSpinner LeftSpinner { get; private set; } - protected LoadingSpinner CentreSpinner { get; private set; } - protected LoadingSpinner RightSpinner { get; private set; } + protected LoadingSpinner LeftSpinner { get; private set; } = null!; + protected LoadingSpinner CentreSpinner { get; private set; } = null!; + protected LoadingSpinner RightSpinner { get; private set; } = null!; - private MultiplayerScores higherScores; - private MultiplayerScores lowerScores; + private MultiplayerScores? higherScores; + private MultiplayerScores? lowerScores; [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; [Resolved] - private ScoreManager scoreManager { get; set; } + private ScoreManager scoreManager { get; set; } = null!; [Resolved] - private RulesetStore rulesets { get; set; } + private RulesetStore rulesets { get; set; } = null!; - public PlaylistsResultsScreen([CanBeNull] ScoreInfo score, long roomId, PlaylistItem playlistItem) + public PlaylistsResultsScreen(ScoreInfo? score, long roomId, PlaylistItem playlistItem) : base(score) { this.roomId = roomId; @@ -123,11 +120,11 @@ namespace osu.Game.Screens.OnlinePlay.Playlists return userScoreReq; } - protected override APIRequest FetchNextPage(int direction, Action> scoresCallback) + protected override APIRequest? FetchNextPage(int direction, Action> scoresCallback) { Debug.Assert(direction == 1 || direction == -1); - MultiplayerScores pivot = direction == -1 ? higherScores : lowerScores; + MultiplayerScores? pivot = direction == -1 ? higherScores : lowerScores; if (pivot?.Cursor == null) return null; @@ -147,7 +144,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists /// The callback to perform with the resulting scores. /// An optional score pivot to retrieve scores around. Can be null to retrieve scores from the highest score. /// The indexing . - private APIRequest createIndexRequest(Action> scoresCallback, [CanBeNull] MultiplayerScores pivot = null) + private APIRequest createIndexRequest(Action> scoresCallback, MultiplayerScores? pivot = null) { var indexReq = pivot != null ? new IndexPlaylistScoresRequest(roomId, playlistItem.ID, pivot.Cursor, pivot.Params) @@ -180,7 +177,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists /// The callback to invoke with the final s. /// The s that were retrieved from s. /// An optional pivot around which the scores were retrieved. - private void performSuccessCallback([NotNull] Action> callback, [NotNull] List scores, [CanBeNull] MultiplayerScores pivot = null) => Schedule(() => + private void performSuccessCallback(Action> callback, List scores, MultiplayerScores? pivot = null) => Schedule(() => { var scoreInfos = scores.Select(s => s.CreateScoreInfo(scoreManager, rulesets, playlistItem, Beatmap.Value.BeatmapInfo)).OrderByTotalScore().ToArray(); @@ -201,7 +198,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists hideLoadingSpinners(pivot); }); - private void hideLoadingSpinners([CanBeNull] MultiplayerScores pivot = null) + private void hideLoadingSpinners(MultiplayerScores? pivot = null) { CentreSpinner.Hide(); @@ -217,7 +214,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists /// The to set positions on. /// The pivot. /// The amount to increment the pivot position by for each in . - private void setPositions([NotNull] MultiplayerScores scores, [CanBeNull] MultiplayerScores pivot, int increment) + private void setPositions(MultiplayerScores scores, MultiplayerScores? pivot, int increment) => setPositions(scores, pivot?.Scores[^1].Position ?? 0, increment); /// @@ -226,7 +223,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists /// The to set positions on. /// The pivot position. /// The amount to increment the pivot position by for each in . - private void setPositions([NotNull] MultiplayerScores scores, int pivotPosition, int increment) + private void setPositions(MultiplayerScores scores, int pivotPosition, int increment) { foreach (var s in scores.Scores) { diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index ebb0530046..1c3518909d 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.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 osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -45,25 +42,24 @@ namespace osu.Game.Screens.Ranking protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.UserTriggered; - public readonly Bindable SelectedScore = new Bindable(); + public readonly Bindable SelectedScore = new Bindable(); - [CanBeNull] - public readonly ScoreInfo Score; + public readonly ScoreInfo? Score; - protected ScorePanelList ScorePanelList { get; private set; } + protected ScorePanelList ScorePanelList { get; private set; } = null!; - protected VerticalScrollContainer VerticalScrollContent { get; private set; } - - [Resolved(CanBeNull = true)] - private Player player { get; set; } + protected VerticalScrollContainer VerticalScrollContent { get; private set; } = null!; [Resolved] - private IAPIProvider api { get; set; } + private Player? player { get; set; } - protected StatisticsPanel StatisticsPanel { get; private set; } + [Resolved] + private IAPIProvider api { get; set; } = null!; - private Drawable bottomPanel; - private Container detachedPanelContainer; + protected StatisticsPanel StatisticsPanel { get; private set; } = null!; + + private Drawable bottomPanel = null!; + private Container detachedPanelContainer = null!; private bool lastFetchCompleted; @@ -84,9 +80,9 @@ namespace osu.Game.Screens.Ranking /// public bool ShowUserStatistics { get; init; } - private Sample popInSample; + private Sample? popInSample; - protected ResultsScreen([CanBeNull] ScoreInfo score) + protected ResultsScreen(ScoreInfo? score) { Score = score; @@ -182,11 +178,11 @@ namespace osu.Game.Screens.Ranking Scheduler.AddDelayed(() => OverlayActivationMode.Value = OverlayActivation.All, shouldFlair ? AccuracyCircle.TOTAL_DURATION + 1000 : 0); } - if (AllowWatchingReplay) + if (SelectedScore.Value != null && AllowWatchingReplay) { buttons.Add(new ReplayDownloadButton(SelectedScore.Value) { - Score = { BindTarget = SelectedScore }, + Score = { BindTarget = SelectedScore! }, Width = 300 }); } @@ -225,7 +221,7 @@ namespace osu.Game.Screens.Ranking if (lastFetchCompleted) { - APIRequest nextPageRequest = null; + APIRequest? nextPageRequest = null; if (ScorePanelList.IsScrolledToStart) nextPageRequest = FetchNextPage(-1, fetchScoresCallback); @@ -245,7 +241,7 @@ namespace osu.Game.Screens.Ranking /// /// A callback which should be called when fetching is completed. Scheduling is not required. /// An responsible for the fetch operation. This will be queued and performed automatically. - protected virtual APIRequest FetchScores(Action> scoresCallback) => null; + protected virtual APIRequest? FetchScores(Action> scoresCallback) => null; /// /// Performs a fetch of the next page of scores. This is invoked every frame until a non-null is returned. @@ -253,7 +249,7 @@ namespace osu.Game.Screens.Ranking /// The fetch direction. -1 to fetch scores greater than the current start of the list, and 1 to fetch scores lower than the current end of the list. /// A callback which should be called when fetching is completed. Scheduling is not required. /// An responsible for the fetch operation. This will be queued and performed automatically. - protected virtual APIRequest FetchNextPage(int direction, Action> scoresCallback) => null; + protected virtual APIRequest? FetchNextPage(int direction, Action> scoresCallback) => null; /// /// Creates the to be used to display extended information about scores. @@ -327,7 +323,7 @@ namespace osu.Game.Screens.Ranking panel.Alpha = 0; } - private ScorePanel detachedPanel; + private ScorePanel? detachedPanel; private void onStatisticsStateChanged(ValueChangedEvent state) { diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index ee0251b5ac..33b4bf976b 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Ranking { } - protected override APIRequest? FetchScores(Action>? scoresCallback) + protected override APIRequest? FetchScores(Action> scoresCallback) { Debug.Assert(Score != null); @@ -35,7 +35,7 @@ namespace osu.Game.Screens.Ranking return null; getScoreRequest = new GetScoresRequest(Score.BeatmapInfo, Score.Ruleset); - getScoreRequest.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => !s.MatchesOnlineID(Score)).Select(s => s.ToScoreInfo(rulesets, Beatmap.Value.BeatmapInfo))); + getScoreRequest.Success += r => scoresCallback.Invoke(r.Scores.Where(s => !s.MatchesOnlineID(Score)).Select(s => s.ToScoreInfo(rulesets, Beatmap.Value.BeatmapInfo))); return getScoreRequest; } From 9e919b784d99e80371bcdab948eeb80346b4892a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 25 Apr 2024 11:19:29 +0200 Subject: [PATCH 3740/4092] Add test case covering ignoring non-basic results --- .../Ranking/TestSceneHitEventTimingDistributionGraph.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs index 325a535731..3e38b66029 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs @@ -82,6 +82,14 @@ namespace osu.Game.Tests.Visual.Ranking }).ToList()); } + [Test] + public void TestNonBasicHitResultsAreIgnored() + { + createTest(CreateDistributedHitEvents(0, 50) + .Select(h => new HitEvent(h.TimeOffset, 1.0, h.TimeOffset > 0 ? HitResult.Ok : HitResult.LargeTickHit, placeholder_object, placeholder_object, null)) + .ToList()); + } + [Test] public void TestMultipleWindowsOfHitResult() { From b250a924b19687d626737c78dffbd6e692859d92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 25 Apr 2024 11:20:07 +0200 Subject: [PATCH 3741/4092] Do not show non-basic results in timing distribution graph Closes https://github.com/ppy/osu/issues/24274. Bit of an ad-hoc resolution but maybe fine? This basically proposes to bypass the problem described in the issue by just not showing tick hits at all on the distribution graph. --- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 1260ec2339..47807a8346 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -59,7 +59,7 @@ namespace osu.Game.Screens.Ranking.Statistics /// The s to display the timing distribution of. public HitEventTimingDistributionGraph(IReadOnlyList hitEvents) { - this.hitEvents = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit()).ToList(); + this.hitEvents = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsBasic() && e.Result.IsHit()).ToList(); bins = Enumerable.Range(0, total_timing_distribution_bins).Select(_ => new Dictionary()).ToArray>(); } From d2e9c33b6a34442a0b3e1bead88eac96d20c53e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 25 Apr 2024 12:49:25 +0200 Subject: [PATCH 3742/4092] Add failing test case --- .../Editing/TestSceneDifficultyDelete.cs | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultyDelete.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultyDelete.cs index 12e00c4485..0f99270a9b 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultyDelete.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultyDelete.cs @@ -12,6 +12,7 @@ using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Edit; using osu.Game.Storyboards; using osu.Game.Tests.Beatmaps.IO; using osuTK.Input; @@ -83,6 +84,49 @@ namespace osu.Game.Tests.Visual.Editing } } + [Test] + public void TestDeleteDifficultyWithPendingChanges() + { + Guid deletedDifficultyID = Guid.Empty; + int countBeforeDeletion = 0; + string beatmapSetHashBefore = string.Empty; + + AddUntilStep("wait for editor to load", () => Editor?.ReadyForUse == true); + + AddStep("store selected difficulty", () => + { + deletedDifficultyID = EditorBeatmap.BeatmapInfo.ID; + countBeforeDeletion = Beatmap.Value.BeatmapSetInfo.Beatmaps.Count; + beatmapSetHashBefore = Beatmap.Value.BeatmapSetInfo.Hash; + }); + + AddStep("make change to difficulty", () => + { + EditorBeatmap.BeginChange(); + EditorBeatmap.BeatmapInfo.DifficultyName = "changin' things"; + EditorBeatmap.EndChange(); + }); + + AddStep("click File", () => this.ChildrenOfType().First().TriggerClick()); + + AddStep("click delete", () => getDeleteMenuItem().TriggerClick()); + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog != null); + AddAssert("dialog is deletion confirmation dialog", () => DialogOverlay.CurrentDialog, Is.InstanceOf); + AddStep("confirm", () => InputManager.Key(Key.Number1)); + + AddUntilStep("no next dialog", () => DialogOverlay.CurrentDialog == null); + AddUntilStep("switched to different difficulty", + () => this.ChildrenOfType().SingleOrDefault() != null && EditorBeatmap.BeatmapInfo.ID != deletedDifficultyID); + + AddAssert($"difficulty is unattached from set", + () => Beatmap.Value.BeatmapSetInfo.Beatmaps.Select(b => b.ID), () => Does.Not.Contain(deletedDifficultyID)); + AddAssert("beatmap set difficulty count decreased by one", + () => Beatmap.Value.BeatmapSetInfo.Beatmaps.Count, () => Is.EqualTo(countBeforeDeletion - 1)); + AddAssert("set hash changed", () => Beatmap.Value.BeatmapSetInfo.Hash, () => Is.Not.EqualTo(beatmapSetHashBefore)); + AddAssert($"difficulty is deleted from realm", + () => Realm.Run(r => r.Find(deletedDifficultyID)), () => Is.Null); + } + private DrawableOsuMenuItem getDeleteMenuItem() => this.ChildrenOfType() .Single(item => item.ChildrenOfType().Any(text => text.Text.ToString().StartsWith("Delete", StringComparison.Ordinal))); } From 19d006d8182812710bffd94d26a4fb2512c101cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 25 Apr 2024 12:51:30 +0200 Subject: [PATCH 3743/4092] Fix deleting modified difficulty via editor leaving user in broken state Closes https://github.com/ppy/osu/issues/22783. If the difficulty being edited has unsaved changes, the editor exit flow would prompt for save *after* the deletion method has run. This is undesirable from a UX standpoint, and also leaves the user in a broken state. Thus, just fake an update of the last saved hash of the beatmap to fool the editor into thinking that it's not dirty, so that the exit flow will not show a save dialog. --- osu.Game/Screens/Edit/Editor.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 37f4b4f5be..980c613311 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1088,6 +1088,13 @@ namespace osu.Game.Screens.Edit var difficultiesBeforeDeletion = groupedOrderedBeatmaps.SelectMany(g => g).ToList(); + // if the difficulty being currently deleted has unsaved changes, + // the editor exit flow would prompt for save *after* this method has done its thing. + // this is generally undesirable and also ends up leaving the user in a broken state. + // therefore, just update the last saved hash to make the exit flow think the deleted beatmap is not dirty, + // so that it will not show the save dialog on exit. + updateLastSavedHash(); + beatmapManager.DeleteDifficultyImmediately(difficultyToDelete); int deletedIndex = difficultiesBeforeDeletion.IndexOf(difficultyToDelete); From c1107d2797ae807d33cdefff3edd54459781919d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 25 Apr 2024 14:31:13 +0200 Subject: [PATCH 3744/4092] Fully refetch working beatmap when entering editor Closes https://github.com/ppy/osu/issues/21794. I'm not actually super sure as to what the exact mode of failure is here, but it's 99% to do with working beatmap cache invalidation. Likely this can be even considered as another case of https://github.com/ppy/osu/issues/21357, but because this is a one-liner "fix," I'm PRing it anyways. The issue is confusing to understand when working with the swap scenario given in the issue, but it's a little easier to understand when performing the following: 1. Have a beatmap set with 2 difficulties. Let's call them "A" and "B". 2. From song select, without ever exiting to main menu, edit "A". Change the difficulty name to "AA". Save and exit back to song select; do not exit out to main menu. 3. From song select, edit "B". Change the difficulty name to "BB". Save and exit back to song select. 4. The difficulty names will be "A" and "BB". Basically what I *think* is causing this, is the fact that even though editor invalidates the working beatmap by refetching it afresh on exit, song select is blissfully unaware of this, and continues working with its own `BeatmapInfo` instances which have backlinks to `BeatmapSetInfo`. When editing the first of the two difficulties and then the second, the editing of the first one only invalidates the first one rather than the entire set, and the second difficulty continues to have a stale reference to the first one via the beatmap set, and as such ends up overwriting the changes from the first save when passed into the editor and modified again. --- osu.Game/Screens/Select/SongSelect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 15469fad5b..16879d0cf0 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -425,7 +425,7 @@ namespace osu.Game.Screens.Select if (!AllowEditing) throw new InvalidOperationException($"Attempted to edit when {nameof(AllowEditing)} is disabled"); - Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo ?? beatmapInfoNoDebounce); + Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo ?? beatmapInfoNoDebounce, true); this.Push(new EditorLoader()); } From 1756da0dda45363e89c9e3aaf3a70fb32356977d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Apr 2024 21:14:09 +0800 Subject: [PATCH 3745/4092] Fix redundant string interpolations --- osu.Game.Tests/Visual/Editing/TestSceneDifficultyDelete.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultyDelete.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultyDelete.cs index 0f99270a9b..d4bd77642c 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultyDelete.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultyDelete.cs @@ -118,12 +118,12 @@ namespace osu.Game.Tests.Visual.Editing AddUntilStep("switched to different difficulty", () => this.ChildrenOfType().SingleOrDefault() != null && EditorBeatmap.BeatmapInfo.ID != deletedDifficultyID); - AddAssert($"difficulty is unattached from set", + AddAssert("difficulty is unattached from set", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.Select(b => b.ID), () => Does.Not.Contain(deletedDifficultyID)); AddAssert("beatmap set difficulty count decreased by one", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.Count, () => Is.EqualTo(countBeforeDeletion - 1)); AddAssert("set hash changed", () => Beatmap.Value.BeatmapSetInfo.Hash, () => Is.Not.EqualTo(beatmapSetHashBefore)); - AddAssert($"difficulty is deleted from realm", + AddAssert("difficulty is deleted from realm", () => Realm.Run(r => r.Find(deletedDifficultyID)), () => Is.Null); } From 387fcb87819f15dbaa4c8d10423ea0d6bd01b6db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Apr 2024 21:31:36 +0800 Subject: [PATCH 3746/4092] Add a brief inline comment to make sure we don't undo the fix --- osu.Game/Screens/Select/SongSelect.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 16879d0cf0..6225534e95 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -425,6 +425,7 @@ namespace osu.Game.Screens.Select if (!AllowEditing) throw new InvalidOperationException($"Attempted to edit when {nameof(AllowEditing)} is disabled"); + // Forced refetch is important here to guarantee correct invalidation across all difficulties. Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo ?? beatmapInfoNoDebounce, true); this.Push(new EditorLoader()); } From e0e790fa9412368ff7b414791348476f05e28f2f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Apr 2024 14:44:44 +0800 Subject: [PATCH 3747/4092] Fix a couple of xmldoc typos --- osu.Game/OsuGameBase.cs | 2 +- .../OnlinePlay/Multiplayer/Spectate/SpectatorSyncManager.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index fb7a238c46..0122afb239 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -94,7 +94,7 @@ namespace osu.Game public const int SAMPLE_DEBOUNCE_TIME = 20; /// - /// The maximum volume at which audio tracks should playback. This can be set lower than 1 to create some head-room for sound effects. + /// The maximum volume at which audio tracks should play back at. This can be set lower than 1 to create some head-room for sound effects. /// private const double global_track_volume_adjust = 0.8; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/SpectatorSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/SpectatorSyncManager.cs index fd61b60fe4..5ff52be8bc 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/SpectatorSyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/SpectatorSyncManager.cs @@ -130,7 +130,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate } /// - /// Updates the catchup states of all player clocks clocks. + /// Updates the catchup states of all player clocks. /// private void updatePlayerCatchup() { From 21d65568651a2760891dd3b2c5c6d55c1c99a688 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Apr 2024 15:29:59 +0800 Subject: [PATCH 3748/4092] Remove managed clocks from `SpectatorSyncManager` on gameplay completion / abort --- .../Multiplayer/Spectate/MultiSpectatorScreen.cs | 13 +++++++++++-- .../Multiplayer/Spectate/SpectatorSyncManager.cs | 1 + osu.Game/Screens/Spectate/SpectatorScreen.cs | 7 +++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index e2159f0e3b..cb00763e6b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -244,10 +244,19 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate playerArea.LoadScore(spectatorGameplayState.Score); }); - protected override void FailGameplay(int userId) + protected override void FailGameplay(int userId) => Schedule(() => { // We probably want to visualise this in the future. - } + + var instance = instances.Single(i => i.UserId == userId); + syncManager.RemoveManagedClock(instance.SpectatorPlayerClock); + }); + + protected override void PassGameplay(int userId) => Schedule(() => + { + var instance = instances.Single(i => i.UserId == userId); + syncManager.RemoveManagedClock(instance.SpectatorPlayerClock); + }); protected override void QuitGameplay(int userId) => Schedule(() => { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/SpectatorSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/SpectatorSyncManager.cs index 5ff52be8bc..9eb448d9d0 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/SpectatorSyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/SpectatorSyncManager.cs @@ -76,6 +76,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public void RemoveManagedClock(SpectatorPlayerClock clock) { playerClocks.Remove(clock); + Logger.Log($"Removing managed clock from {nameof(SpectatorSyncManager)} ({playerClocks.Count} remain)"); clock.IsRunning = false; } diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index c4aef3c878..ddc638b7c5 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -135,6 +135,7 @@ namespace osu.Game.Screens.Spectate case SpectatedUserState.Passed: markReceivedAllFrames(userId); + PassGameplay(userId); break; case SpectatedUserState.Failed: @@ -233,6 +234,12 @@ namespace osu.Game.Screens.Spectate /// The gameplay state. protected abstract void StartGameplay(int userId, SpectatorGameplayState spectatorGameplayState); + /// + /// Fired when a user passes gameplay. + /// + /// The user which passed. + protected virtual void PassGameplay(int userId) { } + /// /// Quits gameplay for a user. /// Thread safety is not guaranteed – should be scheduled as required. From fb2d28f7e03aba53ae905b906fcea30da9a9c4ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Apr 2024 15:30:26 +0800 Subject: [PATCH 3749/4092] Fix audio being paused in a spectator session when all players finish playing --- .../OnlinePlay/Multiplayer/Spectate/SpectatorSyncManager.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/SpectatorSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/SpectatorSyncManager.cs index 9eb448d9d0..1638102089 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/SpectatorSyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/SpectatorSyncManager.cs @@ -177,7 +177,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// private void updateMasterState() { - MasterClockState newState = playerClocks.Any(s => !s.IsCatchingUp) ? MasterClockState.Synchronised : MasterClockState.TooFarAhead; + // Clocks are removed as players complete the beatmap. + // Once there are no clocks we want to make sure the track plays out to the end. + MasterClockState newState = playerClocks.Count == 0 || playerClocks.Any(s => !s.IsCatchingUp) ? MasterClockState.Synchronised : MasterClockState.TooFarAhead; if (masterState == newState) return; From 694e3900dbd849bf5638efc8e8dbeec9cf57faf0 Mon Sep 17 00:00:00 2001 From: Taevas <67872932+TTTaevas@users.noreply.github.com> Date: Sat, 27 Apr 2024 23:43:27 +0200 Subject: [PATCH 3750/4092] Add missing space in setup wizard --- osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index b19a9c6c99..983cb0bbb4 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -128,6 +128,7 @@ namespace osu.Game.Overlays.FirstRunSetup if (available) { copyInformation.Text = FirstRunOverlayImportFromStableScreenStrings.DataMigrationNoExtraSpace; + copyInformation.AddText(@" "); // just to ensure correct spacing copyInformation.AddLink(FirstRunOverlayImportFromStableScreenStrings.LearnAboutHardLinks, LinkAction.OpenWiki, @"Client/Release_stream/Lazer/File_storage#via-hard-links"); } else if (!RuntimeInfo.IsDesktop) From 48c608e0169a1f9280f75fb3bdc0eadaa3466976 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 18 Mar 2024 12:22:23 -0700 Subject: [PATCH 3751/4092] Make player width a const --- osu.Game/Overlays/NowPlayingOverlay.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index ab99370603..1145ebaa2f 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -33,6 +33,7 @@ namespace osu.Game.Overlays public LocalisableString Title => NowPlayingStrings.HeaderTitle; public LocalisableString Description => NowPlayingStrings.HeaderDescription; + private const float player_width = 400; private const float player_height = 130; private const float transition_length = 800; private const float progress_height = 10; @@ -70,7 +71,7 @@ namespace osu.Game.Overlays public NowPlayingOverlay() { - Width = 400; + Width = player_width; Margin = new MarginPadding(margin); } @@ -319,15 +320,15 @@ namespace osu.Game.Overlays switch (direction) { case TrackChangeDirection.Next: - newBackground.Position = new Vector2(400, 0); + newBackground.Position = new Vector2(player_width, 0); newBackground.MoveToX(0, 500, Easing.OutCubic); - background.MoveToX(-400, 500, Easing.OutCubic); + background.MoveToX(-player_width, 500, Easing.OutCubic); break; case TrackChangeDirection.Prev: - newBackground.Position = new Vector2(-400, 0); + newBackground.Position = new Vector2(-player_width, 0); newBackground.MoveToX(0, 500, Easing.OutCubic); - background.MoveToX(400, 500, Easing.OutCubic); + background.MoveToX(player_width, 500, Easing.OutCubic); break; } From d4951a093fd9746c1ac885a8af13874dfc5390a8 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 18 Mar 2024 12:24:02 -0700 Subject: [PATCH 3752/4092] Scroll now playing overlay text when overflowing --- .../TestSceneNowPlayingOverlay.cs | 20 +++- osu.Game/Overlays/NowPlayingOverlay.cs | 100 +++++++++++++++++- 2 files changed, 115 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs index d07b90025f..40e0d9250d 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Game.Beatmaps; using osu.Game.Overlays; using osu.Game.Rulesets.Osu; @@ -22,8 +23,6 @@ namespace osu.Game.Tests.Visual.UserInterface [BackgroundDependencyLoader] private void load() { - Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); - nowPlayingOverlay = new NowPlayingOverlay { Origin = Anchor.Centre, @@ -37,9 +36,26 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestShowHideDisable() { + AddStep(@"set beatmap", () => Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo)); AddStep(@"show", () => nowPlayingOverlay.Show()); AddToggleStep(@"toggle beatmap lock", state => Beatmap.Disabled = state); AddStep(@"hide", () => nowPlayingOverlay.Hide()); } + + [Test] + public void TestLongMetadata() + { + AddStep(@"set beatmap", () => Beatmap.Value = CreateWorkingBeatmap(new Beatmap + { + Metadata = + { + Artist = "very very very very very very very very very very very long artist", + ArtistUnicode = "very very very very very very very very very very very long artist", + Title = "very very very very very very very very very very very long title", + TitleUnicode = "very very very very very very very very very very very long title", + } + })); + AddStep(@"show", () => nowPlayingOverlay.Show()); + } } } diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 1145ebaa2f..be405257ca 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -6,6 +6,7 @@ using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -48,7 +49,7 @@ namespace osu.Game.Overlays private IconButton nextButton = null!; private IconButton playlistButton = null!; - private SpriteText title = null!, artist = null!; + private ScrollingTextContainer title = null!, artist = null!; private PlaylistOverlay? playlist; @@ -102,7 +103,7 @@ namespace osu.Game.Overlays Children = new[] { background = Empty(), - title = new OsuSpriteText + title = new ScrollingTextContainer { Origin = Anchor.BottomCentre, Anchor = Anchor.TopCentre, @@ -111,7 +112,7 @@ namespace osu.Game.Overlays Colour = Color4.White, Text = @"Nothing to play", }, - artist = new OsuSpriteText + artist = new ScrollingTextContainer { Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, @@ -470,5 +471,98 @@ namespace osu.Game.Overlays base.OnHoverLost(e); } } + + private partial class ScrollingTextContainer : CompositeDrawable + { + private const float initial_move_delay = 1000; + private const float pixels_per_second = 50; + + private LocalisableString text; + private OsuSpriteText mainSpriteText = null!; + private OsuSpriteText fillerSpriteText = null!; + + public LocalisableString Text + { + get => text; + set + { + text = value; + Schedule(updateText); + } + } + + public FontUsage Font + { + set => + Schedule(() => + { + mainSpriteText.Font = value; + fillerSpriteText.Font = value; + + updateText(); + }); + } + + public ScrollingTextContainer() + { + AutoSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new[] + { + mainSpriteText = new OsuSpriteText { Padding = new MarginPadding { Horizontal = margin } }, + fillerSpriteText = new OsuSpriteText { Padding = new MarginPadding { Horizontal = margin }, Alpha = 0 }, + } + }; + } + + private void updateText() + { + mainSpriteText.Text = text; + fillerSpriteText.Alpha = 0; + + ClearTransforms(); + X = 0; + + float textOverflowWidth = mainSpriteText.Width - player_width; + + if (textOverflowWidth > 0) + { + fillerSpriteText.Alpha = 1; + fillerSpriteText.Text = text; + + float initialX; + float targetX; + + if (Anchor.HasFlagFast(Anchor.x0)) + { + initialX = 0; + targetX = -mainSpriteText.Width; + } + else if (Anchor.HasFlagFast(Anchor.x1)) + { + initialX = (textOverflowWidth + mainSpriteText.Width) / 2; + targetX = (textOverflowWidth - mainSpriteText.Width) / 2; + } + else // Anchor.x2 + { + initialX = textOverflowWidth + mainSpriteText.Width; + targetX = textOverflowWidth; + } + + this.MoveToX(initialX) + .Delay(initial_move_delay) + .MoveToX(targetX, mainSpriteText.Width * 1000 / pixels_per_second) + .Loop(); + } + } + } } } From b262497083ccef7d72b9c838d37bf13706ee4faf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 28 Apr 2024 19:07:39 +0800 Subject: [PATCH 3753/4092] Check realm file can be written to before attempting further initialisation Rather than creating a "corrupt" realm file in such cases, the game will now refuse to start. This behaviour is usually what we want. In most cases a second click on the game will start it successfully (the previous instance's file handles are still doing stuff, or windows defender is being silly). Closes https://github.com/ppy/osu/issues/28018. --- osu.Game/Database/RealmAccess.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 167d170c81..4bc7ec4979 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -300,6 +300,21 @@ namespace osu.Game.Database private Realm prepareFirstRealmAccess() { + // Before attempting to initialise realm, make sure the realm file isn't locked and has correct permissions. + // + // This is to avoid failures like: + // Realms.Exceptions.RealmException: SetEndOfFile() failed: unknown error (1224) + // + // which can occur due to file handles still being open by a previous instance. + if (storage.Exists(Filename)) + { + // If this fails we allow it to block game startup. + // It's better than any alternative we can offer. + using (var _ = storage.GetStream(Filename, FileAccess.ReadWrite)) + { + } + } + string newerVersionFilename = $"{Filename.Replace(realm_extension, string.Empty)}_newer_version{realm_extension}"; // Attempt to recover a newer database version if available. @@ -321,7 +336,7 @@ namespace osu.Game.Database { Logger.Error(e, "Your local database is too new to work with this version of osu!. Please close osu! and install the latest release to recover your data."); - // If a newer version database already exists, don't backup again. We can presume that the first backup is the one we care about. + // If a newer version database already exists, don't create another backup. We can presume that the first backup is the one we care about. if (!storage.Exists(newerVersionFilename)) createBackup(newerVersionFilename); } From a4bc5a8fc9059e2f13d736965e8cc52eff95f7ea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Apr 2024 10:35:37 +0800 Subject: [PATCH 3754/4092] Use helper method for backup retry attempts --- osu.Game/Database/RealmAccess.cs | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 4bc7ec4979..b5faa898e7 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -35,6 +35,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Scoring.Legacy; using osu.Game.Skinning; +using osu.Game.Utils; using osuTK.Input; using Realms; using Realms.Exceptions; @@ -1157,33 +1158,18 @@ namespace osu.Game.Database { Logger.Log($"Creating full realm database backup at {backupFilename}", LoggingTarget.Database); - int attempts = 10; - - while (true) + FileUtils.AttemptOperation(() => { - try + using (var source = storage.GetStream(Filename, mode: FileMode.Open)) { - using (var source = storage.GetStream(Filename, mode: FileMode.Open)) - { - // source may not exist. - if (source == null) - return; + // source may not exist. + if (source == null) + return; - using (var destination = storage.GetStream(backupFilename, FileAccess.Write, FileMode.CreateNew)) - source.CopyTo(destination); - } - - return; + using (var destination = storage.GetStream(backupFilename, FileAccess.Write, FileMode.CreateNew)) + source.CopyTo(destination); } - catch (IOException) - { - if (attempts-- <= 0) - throw; - - // file may be locked during use. - Thread.Sleep(500); - } - } + }, 20); } /// From 1c1ee22aa70cfa3e92786fba7a208f2af08a2e69 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Apr 2024 10:36:49 +0800 Subject: [PATCH 3755/4092] Add retry attempts to hopefully fix windows tests runs --- osu.Game/Database/RealmAccess.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index b5faa898e7..31ae22178f 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -311,9 +311,12 @@ namespace osu.Game.Database { // If this fails we allow it to block game startup. // It's better than any alternative we can offer. - using (var _ = storage.GetStream(Filename, FileAccess.ReadWrite)) + FileUtils.AttemptOperation(() => { - } + using (var _ = storage.GetStream(Filename, FileAccess.ReadWrite)) + { + } + }); } string newerVersionFilename = $"{Filename.Replace(realm_extension, string.Empty)}_newer_version{realm_extension}"; From a3d239c11aa85215b3565171de2b580b8b1c8411 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Apr 2024 18:48:07 +0800 Subject: [PATCH 3756/4092] Remove unused method --- osu.Game/Database/RealmArchiveModelImporter.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index bc4954c6ea..0014e246dc 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -449,16 +449,6 @@ namespace osu.Game.Database return reader.Name.ComputeSHA2Hash(); } - /// - /// Create all required s for the provided archive, adding them to the global file store. - /// - private List createFileInfos(ArchiveReader reader, RealmFileStore files, Realm realm) - { - var fileInfos = new List(); - - return fileInfos; - } - private IEnumerable<(string original, string shortened)> getShortenedFilenames(ArchiveReader reader) { string prefix = reader.Filenames.GetCommonPrefix(); From 45c2327509911032984c863acb912406c44075fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 29 Apr 2024 13:00:22 +0200 Subject: [PATCH 3757/4092] Apply adjustments after framework-side `FriendlyGameName` changes --- osu.Desktop/Program.cs | 6 +++++- osu.Game/OsuGameBase.cs | 12 +++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 29b05a402f..d8364fc6e6 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -107,7 +107,11 @@ namespace osu.Desktop } } - using (DesktopGameHost host = Host.GetSuitableDesktopHost(gameName, new HostOptions { IPCPort = !tournamentClient ? OsuGame.IPC_PORT : null })) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(gameName, new HostOptions + { + IPCPort = !tournamentClient ? OsuGame.IPC_PORT : null, + FriendlyGameName = OsuGameBase.GAME_NAME, + })) { if (!host.IsPrimaryInstance) { diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 0122afb239..5533ee8337 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -75,6 +75,12 @@ namespace osu.Game { public static readonly string[] VIDEO_EXTENSIONS = { ".mp4", ".mov", ".avi", ".flv", ".mpg", ".wmv", ".m4v" }; +#if DEBUG + public const string GAME_NAME = "osu! (development)"; +#else + public const string GAME_NAME = "osu!"; +#endif + public const string OSU_PROTOCOL = "osu://"; public const string CLIENT_STREAM_NAME = @"lazer"; @@ -241,11 +247,7 @@ namespace osu.Game public OsuGameBase() { - Name = @"osu!"; - -#if DEBUG - Name += " (development)"; -#endif + Name = GAME_NAME; allowableExceptions = UnhandledExceptionsBeforeCrash; } From 9fc56f1cc7e916a7c41f7073be25b47642860df3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 29 Apr 2024 13:07:36 +0200 Subject: [PATCH 3758/4092] Apply adjustments after migration of android to SDL3 --- osu.Android/AndroidJoystickSettings.cs | 76 -------------------- osu.Android/AndroidMouseSettings.cs | 97 -------------------------- osu.Android/OsuGameAndroid.cs | 22 ------ 3 files changed, 195 deletions(-) delete mode 100644 osu.Android/AndroidJoystickSettings.cs delete mode 100644 osu.Android/AndroidMouseSettings.cs diff --git a/osu.Android/AndroidJoystickSettings.cs b/osu.Android/AndroidJoystickSettings.cs deleted file mode 100644 index bf69461f0d..0000000000 --- a/osu.Android/AndroidJoystickSettings.cs +++ /dev/null @@ -1,76 +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.Framework.Allocation; -using osu.Framework.Android.Input; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Localisation; -using osu.Game.Localisation; -using osu.Game.Overlays.Settings; - -namespace osu.Android -{ - public partial class AndroidJoystickSettings : SettingsSubsection - { - protected override LocalisableString Header => JoystickSettingsStrings.JoystickGamepad; - - private readonly AndroidJoystickHandler joystickHandler; - - private readonly Bindable enabled = new BindableBool(true); - - private SettingsSlider deadzoneSlider = null!; - - private Bindable handlerDeadzone = null!; - - private Bindable localDeadzone = null!; - - public AndroidJoystickSettings(AndroidJoystickHandler joystickHandler) - { - this.joystickHandler = joystickHandler; - } - - [BackgroundDependencyLoader] - private void load() - { - // use local bindable to avoid changing enabled state of game host's bindable. - handlerDeadzone = joystickHandler.DeadzoneThreshold.GetBoundCopy(); - localDeadzone = handlerDeadzone.GetUnboundCopy(); - - Children = new Drawable[] - { - new SettingsCheckbox - { - LabelText = CommonStrings.Enabled, - Current = enabled - }, - deadzoneSlider = new SettingsSlider - { - LabelText = JoystickSettingsStrings.DeadzoneThreshold, - KeyboardStep = 0.01f, - DisplayAsPercentage = true, - Current = localDeadzone, - }, - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - enabled.BindTo(joystickHandler.Enabled); - enabled.BindValueChanged(e => deadzoneSlider.Current.Disabled = !e.NewValue, true); - - handlerDeadzone.BindValueChanged(val => - { - bool disabled = localDeadzone.Disabled; - - localDeadzone.Disabled = false; - localDeadzone.Value = val.NewValue; - localDeadzone.Disabled = disabled; - }, true); - - localDeadzone.BindValueChanged(val => handlerDeadzone.Value = val.NewValue); - } - } -} diff --git a/osu.Android/AndroidMouseSettings.cs b/osu.Android/AndroidMouseSettings.cs deleted file mode 100644 index fd01b11164..0000000000 --- a/osu.Android/AndroidMouseSettings.cs +++ /dev/null @@ -1,97 +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 Android.OS; -using osu.Framework.Allocation; -using osu.Framework.Android.Input; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Localisation; -using osu.Game.Configuration; -using osu.Game.Localisation; -using osu.Game.Overlays.Settings; -using osu.Game.Overlays.Settings.Sections.Input; - -namespace osu.Android -{ - public partial class AndroidMouseSettings : SettingsSubsection - { - private readonly AndroidMouseHandler mouseHandler; - - protected override LocalisableString Header => MouseSettingsStrings.Mouse; - - private Bindable handlerSensitivity = null!; - - private Bindable localSensitivity = null!; - - private Bindable relativeMode = null!; - - public AndroidMouseSettings(AndroidMouseHandler mouseHandler) - { - this.mouseHandler = mouseHandler; - } - - [BackgroundDependencyLoader] - private void load(OsuConfigManager osuConfig) - { - // use local bindable to avoid changing enabled state of game host's bindable. - handlerSensitivity = mouseHandler.Sensitivity.GetBoundCopy(); - localSensitivity = handlerSensitivity.GetUnboundCopy(); - - relativeMode = mouseHandler.UseRelativeMode.GetBoundCopy(); - - // High precision/pointer capture is only available on Android 8.0 and up - if (Build.VERSION.SdkInt >= BuildVersionCodes.O) - { - AddRange(new Drawable[] - { - new SettingsCheckbox - { - LabelText = MouseSettingsStrings.HighPrecisionMouse, - TooltipText = MouseSettingsStrings.HighPrecisionMouseTooltip, - Current = relativeMode, - Keywords = new[] { @"raw", @"input", @"relative", @"cursor", @"captured", @"pointer" }, - }, - new MouseSettings.SensitivitySetting - { - LabelText = MouseSettingsStrings.CursorSensitivity, - Current = localSensitivity, - }, - }); - } - - AddRange(new Drawable[] - { - new SettingsCheckbox - { - LabelText = MouseSettingsStrings.DisableMouseWheelVolumeAdjust, - TooltipText = MouseSettingsStrings.DisableMouseWheelVolumeAdjustTooltip, - Current = osuConfig.GetBindable(OsuSetting.MouseDisableWheel), - }, - new SettingsCheckbox - { - LabelText = MouseSettingsStrings.DisableClicksDuringGameplay, - Current = osuConfig.GetBindable(OsuSetting.MouseDisableButtons), - }, - }); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - relativeMode.BindValueChanged(relative => localSensitivity.Disabled = !relative.NewValue, true); - - handlerSensitivity.BindValueChanged(val => - { - bool disabled = localSensitivity.Disabled; - - localSensitivity.Disabled = false; - localSensitivity.Value = val.NewValue; - localSensitivity.Disabled = disabled; - }, true); - - localSensitivity.BindValueChanged(val => handlerSensitivity.Value = val.NewValue); - } - } -} diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index 52cfb67f42..a235913ef3 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -5,13 +5,9 @@ using System; using Android.App; using Microsoft.Maui.Devices; using osu.Framework.Allocation; -using osu.Framework.Android.Input; using osu.Framework.Extensions.ObjectExtensions; -using osu.Framework.Input.Handlers; using osu.Framework.Platform; using osu.Game; -using osu.Game.Overlays.Settings; -using osu.Game.Overlays.Settings.Sections.Input; using osu.Game.Updater; using osu.Game.Utils; @@ -88,24 +84,6 @@ namespace osu.Android protected override BatteryInfo CreateBatteryInfo() => new AndroidBatteryInfo(); - public override SettingsSubsection CreateSettingsSubsectionFor(InputHandler handler) - { - switch (handler) - { - case AndroidMouseHandler mh: - return new AndroidMouseSettings(mh); - - case AndroidJoystickHandler jh: - return new AndroidJoystickSettings(jh); - - case AndroidTouchHandler th: - return new TouchSettings(th); - - default: - return base.CreateSettingsSubsectionFor(handler); - } - } - private class AndroidBatteryInfo : BatteryInfo { public override double? ChargeLevel => Battery.ChargeLevel; From fa3aeca09d8656ec76ae9e0401047b4f5f15646a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 29 Apr 2024 14:06:02 +0200 Subject: [PATCH 3759/4092] Add failing test for skins not saving on change --- .../TestSceneSkinEditorNavigation.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs index 9c180d43da..38fb2846aa 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -321,6 +322,30 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("nested input disabled", () => ((Player)Game.ScreenStack.CurrentScreen).ChildrenOfType().All(manager => !manager.UseParentInput)); } + [Test] + public void TestSkinSavesOnChange() + { + advanceToSongSelect(); + openSkinEditor(); + + Guid editedSkinId = Guid.Empty; + AddStep("save skin id", () => editedSkinId = Game.Dependencies.Get().CurrentSkinInfo.Value.ID); + AddStep("add skinnable component", () => + { + skinEditor.ChildrenOfType().First().TriggerClick(); + }); + + AddStep("change to triangles skin", () => Game.Dependencies.Get().SetSkinFromConfiguration(SkinInfo.TRIANGLES_SKIN.ToString())); + AddUntilStep("components loaded", () => Game.ChildrenOfType().All(c => c.ComponentsLoaded)); + // sort of implicitly relies on song select not being skinnable. + // TODO: revisit if the above ever changes + AddUntilStep("skin changed", () => !skinEditor.ChildrenOfType().Any()); + + AddStep("change back to modified skin", () => Game.Dependencies.Get().SetSkinFromConfiguration(editedSkinId.ToString())); + AddUntilStep("components loaded", () => Game.ChildrenOfType().All(c => c.ComponentsLoaded)); + AddUntilStep("changes saved", () => skinEditor.ChildrenOfType().Any()); + } + private void advanceToSongSelect() { PushAndConfirm(() => songSelect = new TestPlaySongSelect()); From f78abf801c2f2d9af66bc6baa71c0a0c6ec52406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 29 Apr 2024 14:06:23 +0200 Subject: [PATCH 3760/4092] Autosave edited skin on change --- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index bc929177d1..690c6b35e3 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -255,8 +255,11 @@ namespace osu.Game.Overlays.SkinEditor // schedule ensures this only happens when the skin editor is visible. // also avoid some weird endless recursion / bindable feedback loop (something to do with tracking skins across three different bindable types). // probably something which will be factored out in a future database refactor so not too concerning for now. - currentSkin.BindValueChanged(_ => + currentSkin.BindValueChanged(val => { + if (val.OldValue != null && hasBegunMutating) + save(val.OldValue); + hasBegunMutating = false; Scheduler.AddOnce(skinChanged); }, true); @@ -537,7 +540,9 @@ namespace osu.Game.Overlays.SkinEditor protected void Redo() => changeHandler?.RestoreState(1); - public void Save(bool userTriggered = true) + public void Save(bool userTriggered = true) => save(currentSkin.Value); + + private void save(Skin skin, bool userTriggered = true) { if (!hasBegunMutating) return; @@ -551,11 +556,11 @@ namespace osu.Game.Overlays.SkinEditor return; foreach (var t in targetContainers) - currentSkin.Value.UpdateDrawableTarget(t); + skin.UpdateDrawableTarget(t); // In the case the save was user triggered, always show the save message to make them feel confident. - if (skins.Save(skins.CurrentSkin.Value) || userTriggered) - onScreenDisplay?.Display(new SkinEditorToast(ToastStrings.SkinSaved, currentSkin.Value.SkinInfo.ToString() ?? "Unknown")); + if (skins.Save(skin) || userTriggered) + onScreenDisplay?.Display(new SkinEditorToast(ToastStrings.SkinSaved, skin.SkinInfo.ToString() ?? "Unknown")); } protected override bool OnHover(HoverEvent e) => true; From 85c085e5879d59fea0533c78261bf8b111d878c3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Apr 2024 23:22:25 +0800 Subject: [PATCH 3761/4092] Reduce startup volume Constant complaints about startup volume mean we should reduce it further and let users adjust as they see fit. --- osu.Game/OsuGame.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 98533a5c82..7c89314014 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -841,7 +841,10 @@ namespace osu.Game { // General expectation that osu! starts in fullscreen by default (also gives the most predictable performance). // However, macOS is bound to have issues when using exclusive fullscreen as it takes full control away from OS, therefore borderless is default there. - { FrameworkSetting.WindowMode, RuntimeInfo.OS == RuntimeInfo.Platform.macOS ? WindowMode.Borderless : WindowMode.Fullscreen } + { FrameworkSetting.WindowMode, RuntimeInfo.OS == RuntimeInfo.Platform.macOS ? WindowMode.Borderless : WindowMode.Fullscreen }, + { FrameworkSetting.VolumeUniversal, 0.6 }, + { FrameworkSetting.VolumeMusic, 0.6 }, + { FrameworkSetting.VolumeEffect, 0.6 }, }; } From fd3f4a9e7b30560c6270845af4a77ae1ed8073ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Apr 2024 22:26:44 +0800 Subject: [PATCH 3762/4092] Preserve storyboard events when saving a beatmap in the editor Until we have full encoding support for storyboards, this stop-gap measure ensures that storyboards don't just disappear from existence. --- .../Beatmaps/Formats/LegacyBeatmapEncoderTest.cs | 15 +++++++++++++++ osu.Game/Beatmaps/Beatmap.cs | 2 ++ osu.Game/Beatmaps/BeatmapConverter.cs | 1 + osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 14 ++++++++++++++ osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 3 +++ osu.Game/Beatmaps/IBeatmap.cs | 6 ++++++ .../Rulesets/Difficulty/DifficultyCalculator.cs | 2 ++ osu.Game/Screens/Edit/EditorBeatmap.cs | 2 ++ osu.Game/Tests/Beatmaps/TestBeatmap.cs | 1 + 9 files changed, 46 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index e847b61fbe..ef30f020ce 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -37,6 +37,21 @@ namespace osu.Game.Tests.Beatmaps.Formats private static IEnumerable allBeatmaps = beatmaps_resource_store.GetAvailableResources().Where(res => res.EndsWith(".osu", StringComparison.Ordinal)); + [Test] + public void TestUnsupportedStoryboardEvents() + { + const string name = "Resources/storyboard_only_video.osu"; + + var decoded = decodeFromLegacy(beatmaps_resource_store.GetStream(name), name); + var decodedAfterEncode = decodeFromLegacy(encodeToLegacy(decoded), name); + + Assert.That(decoded.beatmap.UnhandledEventLines.Count, Is.EqualTo(1)); + Assert.That(decoded.beatmap.UnhandledEventLines.Single(), Is.EqualTo("Video,0,\"video.avi\"")); + + Assert.That(decodedAfterEncode.beatmap.UnhandledEventLines.Count, Is.EqualTo(1)); + Assert.That(decodedAfterEncode.beatmap.UnhandledEventLines.Single(), Is.EqualTo("Video,0,\"video.avi\"")); + } + [TestCaseSource(nameof(allBeatmaps))] public void TestEncodeDecodeStability(string name) { diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 6db9febf36..ae77e4adcf 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -63,6 +63,8 @@ namespace osu.Game.Beatmaps public List Breaks { get; set; } = new List(); + public List UnhandledEventLines { get; set; } = new List(); + [JsonIgnore] public double TotalBreakTime => Breaks.Sum(b => b.Duration); diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index c7c244bf0e..b68c80d4b3 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -66,6 +66,7 @@ namespace osu.Game.Beatmaps beatmap.ControlPointInfo = original.ControlPointInfo; beatmap.HitObjects = convertHitObjects(original.HitObjects, original, cancellationToken).OrderBy(s => s.StartTime).ToList(); beatmap.Breaks = original.Breaks; + beatmap.UnhandledEventLines = original.UnhandledEventLines; return beatmap; } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 386dada328..7407c3590f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -420,6 +420,10 @@ namespace osu.Game.Beatmaps.Formats if (!Enum.TryParse(split[0], out LegacyEventType type)) throw new InvalidDataException($@"Unknown event type: {split[0]}"); + // Until we have full storyboard encoder coverage, let's track any lines which aren't handled + // and store them to a temporary location such that they aren't lost on editor save / export. + bool lineSupportedByEncoder = false; + switch (type) { case LegacyEventType.Sprite: @@ -427,7 +431,11 @@ namespace osu.Game.Beatmaps.Formats // In some older beatmaps, it is not present and replaced by a storyboard-level background instead. // Allow the first sprite (by file order) to act as the background in such cases. if (string.IsNullOrEmpty(beatmap.BeatmapInfo.Metadata.BackgroundFile)) + { beatmap.BeatmapInfo.Metadata.BackgroundFile = CleanFilename(split[3]); + lineSupportedByEncoder = true; + } + break; case LegacyEventType.Video: @@ -439,12 +447,14 @@ namespace osu.Game.Beatmaps.Formats if (!OsuGameBase.VIDEO_EXTENSIONS.Contains(Path.GetExtension(filename).ToLowerInvariant())) { beatmap.BeatmapInfo.Metadata.BackgroundFile = filename; + lineSupportedByEncoder = true; } break; case LegacyEventType.Background: beatmap.BeatmapInfo.Metadata.BackgroundFile = CleanFilename(split[2]); + lineSupportedByEncoder = true; break; case LegacyEventType.Break: @@ -452,8 +462,12 @@ namespace osu.Game.Beatmaps.Formats double end = Math.Max(start, getOffsetTime(Parsing.ParseDouble(split[2]))); beatmap.Breaks.Add(new BreakPeriod(start, end)); + lineSupportedByEncoder = true; break; } + + if (!lineSupportedByEncoder) + beatmap.UnhandledEventLines.Add(line); } private void handleTimingPoint(string line) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 290d29090a..186b565c39 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -156,6 +156,9 @@ namespace osu.Game.Beatmaps.Formats foreach (var b in beatmap.Breaks) writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Break},{b.StartTime},{b.EndTime}")); + + foreach (string l in beatmap.UnhandledEventLines) + writer.WriteLine(l); } private void handleControlPoints(TextWriter writer) diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 6fe494ca0f..5cc38e5b84 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -42,6 +42,12 @@ namespace osu.Game.Beatmaps /// List Breaks { get; } + /// + /// All lines from the [Events] section which aren't handled in the encoding process yet. + /// These lines shoule be written out to the beatmap file on save or export. + /// + List UnhandledEventLines { get; } + /// /// Total amount of break time in the beatmap. /// diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 1599dff8d9..d37cfc28b9 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -330,6 +330,8 @@ namespace osu.Game.Rulesets.Difficulty } public List Breaks => baseBeatmap.Breaks; + public List UnhandledEventLines => baseBeatmap.UnhandledEventLines; + public double TotalBreakTime => baseBeatmap.TotalBreakTime; public IEnumerable GetStatistics() => baseBeatmap.GetStatistics(); public double GetMostCommonBeatLength() => baseBeatmap.GetMostCommonBeatLength(); diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index dc1fda13f4..7a3ea474fb 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -174,6 +174,8 @@ namespace osu.Game.Screens.Edit public List Breaks => PlayableBeatmap.Breaks; + public List UnhandledEventLines => PlayableBeatmap.UnhandledEventLines; + public double TotalBreakTime => PlayableBeatmap.TotalBreakTime; public IReadOnlyList HitObjects => PlayableBeatmap.HitObjects; diff --git a/osu.Game/Tests/Beatmaps/TestBeatmap.cs b/osu.Game/Tests/Beatmaps/TestBeatmap.cs index ff670e1232..de7bcfcfaa 100644 --- a/osu.Game/Tests/Beatmaps/TestBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestBeatmap.cs @@ -27,6 +27,7 @@ namespace osu.Game.Tests.Beatmaps BeatmapInfo = baseBeatmap.BeatmapInfo; ControlPointInfo = baseBeatmap.ControlPointInfo; Breaks = baseBeatmap.Breaks; + UnhandledEventLines = baseBeatmap.UnhandledEventLines; if (withHitObjects) HitObjects = baseBeatmap.HitObjects; From 19897c4c074fbca635b9bdbee66f4fb6e368a126 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Apr 2024 00:50:40 +0800 Subject: [PATCH 3763/4092] Add testing for actual presence of video after encode-decode --- .../Beatmaps/Formats/LegacyBeatmapEncoderTest.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index ef30f020ce..b931896898 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -25,6 +25,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Taiko; using osu.Game.Skinning; +using osu.Game.Storyboards; using osu.Game.Tests.Resources; using osuTK; @@ -43,13 +44,14 @@ namespace osu.Game.Tests.Beatmaps.Formats const string name = "Resources/storyboard_only_video.osu"; var decoded = decodeFromLegacy(beatmaps_resource_store.GetStream(name), name); - var decodedAfterEncode = decodeFromLegacy(encodeToLegacy(decoded), name); - Assert.That(decoded.beatmap.UnhandledEventLines.Count, Is.EqualTo(1)); Assert.That(decoded.beatmap.UnhandledEventLines.Single(), Is.EqualTo("Video,0,\"video.avi\"")); - Assert.That(decodedAfterEncode.beatmap.UnhandledEventLines.Count, Is.EqualTo(1)); - Assert.That(decodedAfterEncode.beatmap.UnhandledEventLines.Single(), Is.EqualTo("Video,0,\"video.avi\"")); + var memoryStream = encodeToLegacy(decoded); + + var storyboard = new LegacyStoryboardDecoder().Decode(new LineBufferedReader(memoryStream)); + StoryboardLayer video = storyboard.Layers.Single(l => l.Name == "Video"); + Assert.That(video.Elements.Count, Is.EqualTo(1)); } [TestCaseSource(nameof(allBeatmaps))] From b455e793dd7f3e3d8a75c57d3304e326ad75ab32 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Apr 2024 01:14:58 +0800 Subject: [PATCH 3764/4092] Add test coverage of reading japanese file from stable export --- .../Resources/Archives/japanese-filename.osz | Bin 0 -> 816 bytes .../Skins/TestSceneBeatmapSkinResources.cs | 9 +++++++++ 2 files changed, 9 insertions(+) create mode 100644 osu.Game.Tests/Resources/Archives/japanese-filename.osz diff --git a/osu.Game.Tests/Resources/Archives/japanese-filename.osz b/osu.Game.Tests/Resources/Archives/japanese-filename.osz new file mode 100644 index 0000000000000000000000000000000000000000..4825c88179bb6d351f8766c4b4a17d89a50a0af5 GIT binary patch literal 816 zcmWIWW@Zs#U|`^2h!mU`q3R&=>KG#fg9S4KgE~+&v8W`oxI{r$p(L{;CsjeCAhn>N zQd1#1B{MB8Gr2UUq%u}7zqqtE$Twfgk!SDvE&mhyz6c0KtqLn{p1`k?5n%4SG|X~_ zq;qWB%&F-L3-|Av$Ji9IP_8Vz?)6l9$IN{%u3jy@@gwDM_XQ!-4SBL=Gq(H^vf-L= zV2#=f&b>E1+utqUHH)o#=}nekr+MwyH_96w(h~b(9of?+SUZ{N{WHcJVor1JMF{!y z^l7MlV4E$*^K)rOxocg=1h*|qB_gzEzU@0{_Qm~*-SyCKZ%#b!7r1tG&6n9pOx&FF zjOWU{nsUGW!EE)(YvndDstaBXusi-?MzPEKRKCI$(K)wcV{H}Px#FUubWY^N2(96i zHe2D!GA;1E@A`$Z{a>y|D@afIk(6KbB}~WceY!H+r+CGM|1_$OxQWe6pYkGc_WKup ztdcTYblDB|_o?b4qr)&hc$rPbw$9 zS+L$&g2#wlFe7?$5&$@yl&)v2%MRPC{zeJOZuv_kp3n)O!39UpCeI%+XL{y*z` zsQUWj2j9i-(+qxFe|N_7X(!)n+McOjta beatmap = importBeatmapFromArchives(@"japanese-filename.osz")); + AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo(@"見本")) != null); + } + [Test] public void TestRetrieveOggAudio() { From 97da47f69ca90fe35dad7914b04e97c7363e7df4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Apr 2024 01:27:45 +0800 Subject: [PATCH 3765/4092] Add test coverage of reading japanese filename after exporting --- .../Skins/TestSceneBeatmapSkinResources.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs index b566e892cd..e0922c52f7 100644 --- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.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 System.IO; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio.Track; @@ -12,6 +13,7 @@ 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.Skins { @@ -25,9 +27,23 @@ namespace osu.Game.Tests.Skins public void TestRetrieveJapaneseFilename() { IWorkingBeatmap beatmap = null!; + MemoryStream outStream = null!; + // Ensure importer encoding is correct AddStep("import beatmap", () => beatmap = importBeatmapFromArchives(@"japanese-filename.osz")); AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo(@"見本")) != null); + + // Ensure exporter encoding is correct (round trip) + AddStep("export", () => + { + outStream = new MemoryStream(); + + new LegacyBeatmapExporter(LocalStorage) + .ExportToStream((BeatmapSetInfo)beatmap.BeatmapInfo.BeatmapSet!, outStream, null); + }); + + AddStep("import beatmap again", () => beatmap = importBeatmapFromStream(outStream)); + AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo(@"見本")) != null); } [Test] @@ -54,6 +70,12 @@ namespace osu.Game.Tests.Skins AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo(@"spinner-osu")) != null); } + 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(); From c8f7f2215b4c9b3bcb5df8fdc46b7f906890dd2c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Apr 2024 18:49:17 +0800 Subject: [PATCH 3766/4092] Force encoding to Shift-JIS for archive filenames MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After way too much time investigating this, the encoding situation is not great right now. - Stable sets the "default code page" to be used for encoding filenames to Shift-JIS (932): https://github.com/peppy/osu-stable-reference/blob/c29ebd7fc52113013fb4ac2db230699d81e1fe2c/osu!/GameBase.cs#L3099 - Lazer does nothing (therefore using UTF-8). When importing to lazer, stable files are assumed to be UTF-8. This means that the linked beatmaps don't work correctly. Forcing lazer to decompress *and* compress using Shift-JIS will fix this. Here's a rough idea of how things look for japanese character filenames in current `master`: | | stable | lazer | |--------|--------|--------| | export encoding | shift-jis | utf8 | | utf8 [bit flag](https://superuser.com/a/1507988) set | ❌ | ❌ | | import stable export osz | ✅ | ❌ | | import lazer export osz | ❌ | ✅ | | windows unzip | ❌ | ❌ | | macos unzip | ✅ | ✅ | and after this change | | stable | lazer | |--------|--------|--------| | export encoding | shift-jis | shift-jis | | utf8 [bit flag](https://superuser.com/a/1507988) set | ❌ | ❌ | | import stable export osz | ✅ | ✅ | | import lazer export osz | ✅ | ✅ | | windows unzip | ❌ | ❌ | | macos unzip | ✅ | ✅ | A future endeavour to improve compatibility would be to look at setting the utf8 flag in lazer, switching the default to utf8, and ensuring the stable supports this flag (I don't believe it does right now). --- osu.Game/Database/LegacyArchiveExporter.cs | 6 +++++- osu.Game/IO/Archives/ZipArchiveReader.cs | 24 +++++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/LegacyArchiveExporter.cs b/osu.Game/Database/LegacyArchiveExporter.cs index 9805207591..1d9d252220 100644 --- a/osu.Game/Database/LegacyArchiveExporter.cs +++ b/osu.Game/Database/LegacyArchiveExporter.cs @@ -7,6 +7,7 @@ using System.Threading; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Extensions; +using osu.Game.IO.Archives; using osu.Game.Overlays.Notifications; using Realms; using SharpCompress.Common; @@ -29,7 +30,10 @@ namespace osu.Game.Database public override void ExportToStream(TModel model, Stream outputStream, ProgressNotification? notification, CancellationToken cancellationToken = default) { - using (var writer = new ZipWriter(outputStream, new ZipWriterOptions(CompressionType.Deflate))) + using (var writer = new ZipWriter(outputStream, new ZipWriterOptions(CompressionType.Deflate) + { + ArchiveEncoding = ZipArchiveReader.DEFAULT_ENCODING + })) { int i = 0; int fileCount = model.Files.Count(); diff --git a/osu.Game/IO/Archives/ZipArchiveReader.cs b/osu.Game/IO/Archives/ZipArchiveReader.cs index cc5c65d184..6bb2a314e7 100644 --- a/osu.Game/IO/Archives/ZipArchiveReader.cs +++ b/osu.Game/IO/Archives/ZipArchiveReader.cs @@ -7,23 +7,45 @@ using System.Buffers; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; using Microsoft.Toolkit.HighPerformance; using osu.Framework.IO.Stores; using SharpCompress.Archives.Zip; +using SharpCompress.Common; +using SharpCompress.Readers; using SixLabors.ImageSharp.Memory; namespace osu.Game.IO.Archives { public sealed class ZipArchiveReader : ArchiveReader { + /// + /// Archives created by osu!stable still write out as Shift-JIS. + /// We want to force this fallback rather than leave it up to the library/system. + /// In the future we may want to change exports to set the zip UTF-8 flag and use that instead. + /// + public static readonly ArchiveEncoding DEFAULT_ENCODING; + private readonly Stream archiveStream; private readonly ZipArchive archive; + static ZipArchiveReader() + { + // Required to support rare code pages. + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + + DEFAULT_ENCODING = new ArchiveEncoding(Encoding.GetEncoding(932), Encoding.GetEncoding(932)); + } + public ZipArchiveReader(Stream archiveStream, string name = null) : base(name) { this.archiveStream = archiveStream; - archive = ZipArchive.Open(archiveStream); + + archive = ZipArchive.Open(archiveStream, new ReaderOptions + { + ArchiveEncoding = DEFAULT_ENCODING + }); } public override Stream GetStream(string name) From a8416f3572bfa143c6019322365211fb0df1837b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Apr 2024 12:39:18 +0800 Subject: [PATCH 3767/4092] Move `exists` check inside retry operation Might help? --- osu.Game/Database/RealmAccess.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 31ae22178f..465d7b15a7 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -307,17 +307,17 @@ namespace osu.Game.Database // Realms.Exceptions.RealmException: SetEndOfFile() failed: unknown error (1224) // // which can occur due to file handles still being open by a previous instance. - if (storage.Exists(Filename)) + // + // If this fails we allow it to block game startup. It's better than any alternative we can offer. + FileUtils.AttemptOperation(() => { - // If this fails we allow it to block game startup. - // It's better than any alternative we can offer. - FileUtils.AttemptOperation(() => + if (storage.Exists(Filename)) { using (var _ = storage.GetStream(Filename, FileAccess.ReadWrite)) { } - }); - } + } + }); string newerVersionFilename = $"{Filename.Replace(realm_extension, string.Empty)}_newer_version{realm_extension}"; From 0bfad74907be51a9755e43cad6f44db372b57f21 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Apr 2024 14:09:29 +0800 Subject: [PATCH 3768/4092] Move realm error handling to avoid triggering in test scenarios --- osu.Game/Database/RealmAccess.cs | 38 +++++++++++++++++--------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 465d7b15a7..057bbe02e6 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -301,24 +301,6 @@ namespace osu.Game.Database private Realm prepareFirstRealmAccess() { - // Before attempting to initialise realm, make sure the realm file isn't locked and has correct permissions. - // - // This is to avoid failures like: - // Realms.Exceptions.RealmException: SetEndOfFile() failed: unknown error (1224) - // - // which can occur due to file handles still being open by a previous instance. - // - // If this fails we allow it to block game startup. It's better than any alternative we can offer. - FileUtils.AttemptOperation(() => - { - if (storage.Exists(Filename)) - { - using (var _ = storage.GetStream(Filename, FileAccess.ReadWrite)) - { - } - } - }); - string newerVersionFilename = $"{Filename.Replace(realm_extension, string.Empty)}_newer_version{realm_extension}"; // Attempt to recover a newer database version if available. @@ -346,6 +328,26 @@ namespace osu.Game.Database } else { + // This error can occur due to file handles still being open by a previous instance. + // If this is the case, rather than assuming the realm file is corrupt, block game startup. + if (e.Message.StartsWith("SetEndOfFile() failed", StringComparison.Ordinal)) + { + // This will throw if the realm file is not available for write access after 5 seconds. + FileUtils.AttemptOperation(() => + { + if (storage.Exists(Filename)) + { + using (var _ = storage.GetStream(Filename, FileAccess.ReadWrite)) + { + } + } + }, 20); + + // If the above eventually succeeds, try and continue startup as per normal. + // This may throw again but let's allow it to, and block startup. + return getRealmInstance(); + } + Logger.Error(e, "Realm startup failed with unrecoverable error; starting with a fresh database. A backup of your database has been made."); createBackup($"{Filename.Replace(realm_extension, string.Empty)}_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}_corrupt{realm_extension}"); } From ba9f4e4baff591cc4b35787216edfa4d88ccabc1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Apr 2024 16:08:30 +0800 Subject: [PATCH 3769/4092] Don't skip lines in beatmap decoding Was added in cc76c58f5f250151bb85ad5efa3f6ce008f0cbb0 without any specific reasoning. Likely not required (and will fix some storyboard elements inside `.osu` files from not being correctly saved). --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 7407c3590f..84f3c0d82a 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -167,8 +167,6 @@ namespace osu.Game.Beatmaps.Formats beatmapInfo.SamplesMatchPlaybackRate = false; } - protected override bool ShouldSkipLine(string line) => base.ShouldSkipLine(line) || line.StartsWith(' ') || line.StartsWith('_'); - protected override void ParseLine(Beatmap beatmap, Section section, string line) { switch (section) From a3213fc36dd23f835db6cac300b1963948757e75 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Apr 2024 21:40:04 +0800 Subject: [PATCH 3770/4092] Change `.olz` to use UTF-8 encoding --- osu.Game/Database/BeatmapExporter.cs | 2 ++ osu.Game/Database/LegacyArchiveExporter.cs | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/BeatmapExporter.cs b/osu.Game/Database/BeatmapExporter.cs index f37c57dea5..01ef09d3d7 100644 --- a/osu.Game/Database/BeatmapExporter.cs +++ b/osu.Game/Database/BeatmapExporter.cs @@ -17,6 +17,8 @@ namespace osu.Game.Database { } + protected override bool UseFixedEncoding => false; + protected override string FileExtension => @".olz"; } } diff --git a/osu.Game/Database/LegacyArchiveExporter.cs b/osu.Game/Database/LegacyArchiveExporter.cs index 1d9d252220..b0e5304ffd 100644 --- a/osu.Game/Database/LegacyArchiveExporter.cs +++ b/osu.Game/Database/LegacyArchiveExporter.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; +using System.Text; using System.Threading; using osu.Framework.Logging; using osu.Framework.Platform; @@ -23,6 +24,11 @@ namespace osu.Game.Database public abstract class LegacyArchiveExporter : LegacyExporter where TModel : RealmObject, IHasNamedFiles, IHasGuidPrimaryKey { + /// + /// Whether to always use Shift-JIS encoding for archive filenames (like osu!stable did). + /// + protected virtual bool UseFixedEncoding => true; + protected LegacyArchiveExporter(Storage storage) : base(storage) { @@ -32,7 +38,7 @@ namespace osu.Game.Database { using (var writer = new ZipWriter(outputStream, new ZipWriterOptions(CompressionType.Deflate) { - ArchiveEncoding = ZipArchiveReader.DEFAULT_ENCODING + ArchiveEncoding = UseFixedEncoding ? ZipArchiveReader.DEFAULT_ENCODING : new ArchiveEncoding(Encoding.UTF8, Encoding.UTF8) })) { int i = 0; From 6a7e2dc258a5cb4d2ea2a72b022cf75eec32186e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Apr 2024 21:47:03 +0800 Subject: [PATCH 3771/4092] Fix formatting --- osu.Game/Database/LegacyArchiveExporter.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/LegacyArchiveExporter.cs b/osu.Game/Database/LegacyArchiveExporter.cs index b0e5304ffd..e4d3ed4681 100644 --- a/osu.Game/Database/LegacyArchiveExporter.cs +++ b/osu.Game/Database/LegacyArchiveExporter.cs @@ -36,10 +36,12 @@ namespace osu.Game.Database public override void ExportToStream(TModel model, Stream outputStream, ProgressNotification? notification, CancellationToken cancellationToken = default) { - using (var writer = new ZipWriter(outputStream, new ZipWriterOptions(CompressionType.Deflate) - { - ArchiveEncoding = UseFixedEncoding ? ZipArchiveReader.DEFAULT_ENCODING : new ArchiveEncoding(Encoding.UTF8, Encoding.UTF8) - })) + var zipWriterOptions = new ZipWriterOptions(CompressionType.Deflate) + { + ArchiveEncoding = UseFixedEncoding ? ZipArchiveReader.DEFAULT_ENCODING : new ArchiveEncoding(Encoding.UTF8, Encoding.UTF8) + }; + + using (var writer = new ZipWriter(outputStream, zipWriterOptions)) { int i = 0; int fileCount = model.Files.Count(); From 38e239a435409e66cb1ccde05696b17afe2eae71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 30 Apr 2024 16:07:37 +0200 Subject: [PATCH 3772/4092] Expand test to cover both legacy and non-legacy export paths --- .../Skins/TestSceneBeatmapSkinResources.cs | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs index e0922c52f7..5086b64433 100644 --- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Skins private BeatmapManager beatmaps { get; set; } = null!; [Test] - public void TestRetrieveJapaneseFilename() + public void TestRetrieveAndLegacyExportJapaneseFilename() { IWorkingBeatmap beatmap = null!; MemoryStream outStream = null!; @@ -46,6 +46,29 @@ namespace osu.Game.Tests.Skins AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo(@"見本")) != null); } + [Test] + public void TestRetrieveAndNonLegacyExportJapaneseFilename() + { + IWorkingBeatmap beatmap = null!; + MemoryStream outStream = null!; + + // Ensure importer encoding is correct + AddStep("import beatmap", () => beatmap = importBeatmapFromArchives(@"japanese-filename.osz")); + AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo(@"見本")) != null); + + // Ensure exporter encoding is correct (round trip) + AddStep("export", () => + { + outStream = new MemoryStream(); + + new BeatmapExporter(LocalStorage) + .ExportToStream((BeatmapSetInfo)beatmap.BeatmapInfo.BeatmapSet!, outStream, null); + }); + + AddStep("import beatmap again", () => beatmap = importBeatmapFromStream(outStream)); + AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo(@"見本")) != null); + } + [Test] public void TestRetrieveOggAudio() { From ff108416d86a920dbe9f652898c59b263f2379a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 May 2024 00:05:14 +0800 Subject: [PATCH 3773/4092] Fix incorrect background being loaded due to async race If the API login (and thus user set) completed between `load` and `LoadComplete`, the re-fetch on user change would not yet be hooked up, causing an incorrect default background to be used instead. Of note, moving this out of async load doesn't really affect load performance as the bulk of the load operation is already scheduled and `LoadComponentAsync`ed anyway --- osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index a552b22c11..090e006671 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -56,10 +56,6 @@ namespace osu.Game.Screens.Backgrounds introSequence = config.GetBindable(OsuSetting.IntroSequence); AddInternal(seasonalBackgroundLoader); - - // Load first background asynchronously as part of BDL load. - currentDisplay = RNG.Next(0, background_count); - Next(); } protected override void LoadComplete() @@ -73,6 +69,9 @@ namespace osu.Game.Screens.Backgrounds introSequence.ValueChanged += _ => Scheduler.AddOnce(next); seasonalBackgroundLoader.SeasonalBackgroundChanged += () => Scheduler.AddOnce(next); + currentDisplay = RNG.Next(0, background_count); + Next(); + // helper function required for AddOnce usage. void next() => Next(); } From 44091b1f352273c36f7545bfe44eaaeb0ef19003 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 May 2024 17:33:03 +0800 Subject: [PATCH 3774/4092] Fix some lines still getting forgotten about --- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 75 ++++++++++--------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 84f3c0d82a..3ecc29bd02 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -415,53 +415,54 @@ namespace osu.Game.Beatmaps.Formats { string[] split = line.Split(','); - if (!Enum.TryParse(split[0], out LegacyEventType type)) - throw new InvalidDataException($@"Unknown event type: {split[0]}"); // Until we have full storyboard encoder coverage, let's track any lines which aren't handled // and store them to a temporary location such that they aren't lost on editor save / export. bool lineSupportedByEncoder = false; - switch (type) + if (Enum.TryParse(split[0], out LegacyEventType type)) { - case LegacyEventType.Sprite: - // Generally, the background is the first thing defined in a beatmap file. - // In some older beatmaps, it is not present and replaced by a storyboard-level background instead. - // Allow the first sprite (by file order) to act as the background in such cases. - if (string.IsNullOrEmpty(beatmap.BeatmapInfo.Metadata.BackgroundFile)) - { - beatmap.BeatmapInfo.Metadata.BackgroundFile = CleanFilename(split[3]); + switch (type) + { + case LegacyEventType.Sprite: + // Generally, the background is the first thing defined in a beatmap file. + // In some older beatmaps, it is not present and replaced by a storyboard-level background instead. + // Allow the first sprite (by file order) to act as the background in such cases. + if (string.IsNullOrEmpty(beatmap.BeatmapInfo.Metadata.BackgroundFile)) + { + beatmap.BeatmapInfo.Metadata.BackgroundFile = CleanFilename(split[3]); + lineSupportedByEncoder = true; + } + + break; + + case LegacyEventType.Video: + string filename = CleanFilename(split[2]); + + // Some very old beatmaps had incorrect type specifications for their backgrounds (ie. using 1 for VIDEO + // instead of 0 for BACKGROUND). To handle this gracefully, check the file extension against known supported + // video extensions and handle similar to a background if it doesn't match. + if (!OsuGameBase.VIDEO_EXTENSIONS.Contains(Path.GetExtension(filename).ToLowerInvariant())) + { + beatmap.BeatmapInfo.Metadata.BackgroundFile = filename; + lineSupportedByEncoder = true; + } + + break; + + case LegacyEventType.Background: + beatmap.BeatmapInfo.Metadata.BackgroundFile = CleanFilename(split[2]); lineSupportedByEncoder = true; - } + break; - break; + case LegacyEventType.Break: + double start = getOffsetTime(Parsing.ParseDouble(split[1])); + double end = Math.Max(start, getOffsetTime(Parsing.ParseDouble(split[2]))); - case LegacyEventType.Video: - string filename = CleanFilename(split[2]); - - // Some very old beatmaps had incorrect type specifications for their backgrounds (ie. using 1 for VIDEO - // instead of 0 for BACKGROUND). To handle this gracefully, check the file extension against known supported - // video extensions and handle similar to a background if it doesn't match. - if (!OsuGameBase.VIDEO_EXTENSIONS.Contains(Path.GetExtension(filename).ToLowerInvariant())) - { - beatmap.BeatmapInfo.Metadata.BackgroundFile = filename; + beatmap.Breaks.Add(new BreakPeriod(start, end)); lineSupportedByEncoder = true; - } - - break; - - case LegacyEventType.Background: - beatmap.BeatmapInfo.Metadata.BackgroundFile = CleanFilename(split[2]); - lineSupportedByEncoder = true; - break; - - case LegacyEventType.Break: - double start = getOffsetTime(Parsing.ParseDouble(split[1])); - double end = Math.Max(start, getOffsetTime(Parsing.ParseDouble(split[2]))); - - beatmap.Breaks.Add(new BreakPeriod(start, end)); - lineSupportedByEncoder = true; - break; + break; + } } if (!lineSupportedByEncoder) From 67c0d7590a93a07cc642ecd1b03f70d6768f4638 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 May 2024 19:31:39 +0800 Subject: [PATCH 3775/4092] Decrease alpha of delayed resume overlay as count approaches zero Allows better visibility of playfield underneath it. --- osu.Game/Screens/Play/DelayedResumeOverlay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Play/DelayedResumeOverlay.cs b/osu.Game/Screens/Play/DelayedResumeOverlay.cs index 147d48ae02..32cdabcf98 100644 --- a/osu.Game/Screens/Play/DelayedResumeOverlay.cs +++ b/osu.Game/Screens/Play/DelayedResumeOverlay.cs @@ -170,6 +170,8 @@ namespace osu.Game.Screens.Play countdownProgress.Progress = amountTimePassed; countdownProgress.InnerRadius = progress_stroke_width / progress_size / countdownProgress.Scale.X; + Alpha = 0.2f + 0.8f * newCount / 3f; + if (countdownCount != newCount) { if (newCount > 0) From 87e814e2019d0480d638f2287f2392ec14e7a1ea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 May 2024 19:34:42 +0800 Subject: [PATCH 3776/4092] Fix incorrect xmldoc and adjust colour provider to match `Player` --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 +- osu.Game/Screens/Play/DelayedResumeOverlay.cs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index a422761800..a28b2716cb 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -529,7 +529,7 @@ namespace osu.Game.Rulesets.UI public ResumeOverlay ResumeOverlay { get; protected set; } /// - /// Whether the should be used to return the user's cursor position to its previous location after a pause. + /// Whether a should be displayed on resuming after a pause. /// /// /// Defaults to true. diff --git a/osu.Game/Screens/Play/DelayedResumeOverlay.cs b/osu.Game/Screens/Play/DelayedResumeOverlay.cs index 32cdabcf98..3202a524a8 100644 --- a/osu.Game/Screens/Play/DelayedResumeOverlay.cs +++ b/osu.Game/Screens/Play/DelayedResumeOverlay.cs @@ -24,8 +24,9 @@ namespace osu.Game.Screens.Play /// public partial class DelayedResumeOverlay : ResumeOverlay { - // todo: this shouldn't define its own colour provider, but nothing in Player screen does, so let's do that for now. - private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + // todo: this shouldn't define its own colour provider, but nothing in DrawableRuleset guarantees this, so let's do it locally for now. + // (of note, Player does cache one but any test which uses a DrawableRuleset without Player will fail without this). + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); private const float outer_size = 200; private const float inner_size = 150; From b8209b92f61d1a4098ccf0a42c36c7b7aceeb473 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 May 2024 19:49:45 +0800 Subject: [PATCH 3777/4092] Add delay before delayed resume starts It was previously a bit sudden after dismissing the pause screen. Now there's a short delay before the actual countdown begins. --- osu.Game/Screens/Play/DelayedResumeOverlay.cs | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Play/DelayedResumeOverlay.cs b/osu.Game/Screens/Play/DelayedResumeOverlay.cs index 3202a524a8..8acb94a5af 100644 --- a/osu.Game/Screens/Play/DelayedResumeOverlay.cs +++ b/osu.Game/Screens/Play/DelayedResumeOverlay.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; -using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; @@ -35,9 +34,10 @@ namespace osu.Game.Screens.Play private const double countdown_time = 2000; + private const int total_count = 3; + protected override LocalisableString Message => string.Empty; - private ScheduledDelegate? scheduledResume; private int? countdownCount; private double countdownStartTime; private bool countdownComplete; @@ -121,21 +121,17 @@ namespace osu.Game.Screens.Play innerContent.FadeIn().ScaleTo(Vector2.Zero).Then().ScaleTo(Vector2.One, 400, Easing.OutElasticHalf); countdownComponents.FadeOut().Delay(50).FadeTo(1, 100); + countdownProgress.Progress = 0; + // Reset states for various components. countdownBackground.FadeIn(); countdownText.FadeIn(); + countdownText.Text = string.Empty; countdownProgress.FadeIn().ScaleTo(1); countdownComplete = false; countdownCount = null; - countdownStartTime = Time.Current; - - scheduledResume?.Cancel(); - scheduledResume = Scheduler.AddDelayed(() => - { - countdownComplete = true; - Resume(); - }, countdown_time); + countdownStartTime = Time.Current + 200; } protected override void PopOut() @@ -153,8 +149,6 @@ namespace osu.Game.Screens.Play } else countdownProgress.FadeOut(); - - scheduledResume?.Cancel(); } protected override void Update() @@ -165,13 +159,16 @@ namespace osu.Game.Screens.Play private void updateCountdown() { - double amountTimePassed = Math.Min(countdown_time, Time.Current - countdownStartTime) / countdown_time; - int newCount = 3 - (int)Math.Floor(amountTimePassed * 3); + if (State.Value == Visibility.Hidden || countdownComplete || Time.Current < countdownStartTime) + return; + + double amountTimePassed = Math.Clamp((Time.Current - countdownStartTime) / countdown_time, 0, countdown_time); + int newCount = Math.Clamp(total_count - (int)Math.Floor(amountTimePassed * total_count), 0, total_count); countdownProgress.Progress = amountTimePassed; countdownProgress.InnerRadius = progress_stroke_width / progress_size / countdownProgress.Scale.X; - Alpha = 0.2f + 0.8f * newCount / 3f; + Alpha = 0.2f + 0.8f * newCount / total_count; if (countdownCount != newCount) { @@ -194,6 +191,12 @@ namespace osu.Game.Screens.Play } countdownCount = newCount; + + if (countdownCount == 0) + { + countdownComplete = true; + Resume(); + } } } } From f0eef329139f0dbe25fb0aa5ee534b33386f466f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 1 May 2024 15:21:39 +0200 Subject: [PATCH 3778/4092] Fix code quality inspection --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 3ecc29bd02..6fa78fa8e6 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -415,7 +415,6 @@ namespace osu.Game.Beatmaps.Formats { string[] split = line.Split(','); - // Until we have full storyboard encoder coverage, let's track any lines which aren't handled // and store them to a temporary location such that they aren't lost on editor save / export. bool lineSupportedByEncoder = false; From 981a19f6a5c6a9b2d9ef904e9644ac82af5e7007 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 1 May 2024 17:17:50 +0300 Subject: [PATCH 3779/4092] Disable naming inspections in p/invoke code --- osu.Desktop/NVAPI.cs | 3 +++ osu.Desktop/Windows/WindowsAssociationManager.cs | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/osu.Desktop/NVAPI.cs b/osu.Desktop/NVAPI.cs index 554f89a847..c1622e7cc1 100644 --- a/osu.Desktop/NVAPI.cs +++ b/osu.Desktop/NVAPI.cs @@ -489,6 +489,7 @@ namespace osu.Desktop public static uint Stride => (uint)Marshal.SizeOf(typeof(NvApplication)) | (2 << 16); } + // ReSharper disable InconsistentNaming internal enum NvStatus { OK = 0, // Success. Request is completed. @@ -738,4 +739,6 @@ namespace osu.Desktop OGL_THREAD_CONTROL_NUM_VALUES = 2, OGL_THREAD_CONTROL_DEFAULT = 0 } + + // ReSharper restore InconsistentNaming } diff --git a/osu.Desktop/Windows/WindowsAssociationManager.cs b/osu.Desktop/Windows/WindowsAssociationManager.cs index 11b5c19ca1..e4c070934e 100644 --- a/osu.Desktop/Windows/WindowsAssociationManager.cs +++ b/osu.Desktop/Windows/WindowsAssociationManager.cs @@ -163,6 +163,8 @@ namespace osu.Desktop.Windows [DllImport("Shell32.dll")] private static extern void SHChangeNotify(EventId wEventId, Flags uFlags, IntPtr dwItem1, IntPtr dwItem2); + // ReSharper disable InconsistentNaming + private enum EventId { /// @@ -174,9 +176,12 @@ namespace osu.Desktop.Windows private enum Flags : uint { + // ReSharper disable once InconsistentNaming SHCNF_IDLIST = 0x0000 } + // ReSharper restore InconsistentNaming + #endregion private record FileAssociation(string Extension, LocalisableString Description, string IconPath) From 02be275554beed4e450c8eca29d1cf979890d489 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 1 May 2024 17:20:20 +0300 Subject: [PATCH 3780/4092] Disable naming inspections in country/language enums --- osu.Game/Localisation/Language.cs | 2 ++ osu.Game/Users/CountryCode.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/osu.Game/Localisation/Language.cs b/osu.Game/Localisation/Language.cs index 711e95486f..e946779cba 100644 --- a/osu.Game/Localisation/Language.cs +++ b/osu.Game/Localisation/Language.cs @@ -4,6 +4,8 @@ using System.ComponentModel; using JetBrains.Annotations; +// ReSharper disable InconsistentNaming + namespace osu.Game.Localisation { [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] diff --git a/osu.Game/Users/CountryCode.cs b/osu.Game/Users/CountryCode.cs index edaa1562c7..6a0ed63648 100644 --- a/osu.Game/Users/CountryCode.cs +++ b/osu.Game/Users/CountryCode.cs @@ -6,6 +6,8 @@ using JetBrains.Annotations; using Newtonsoft.Json; using Newtonsoft.Json.Converters; +// ReSharper disable InconsistentNaming + namespace osu.Game.Users { /// From 2fd8950b2135edde3d63431ea68e25f951a93b5b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 1 May 2024 17:22:08 +0300 Subject: [PATCH 3781/4092] Disable inconsistent naming in some fields of `LegacyManiaSkinConfigurationLookup` --- osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index cacca0de23..1550fc8a47 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -67,7 +67,10 @@ namespace osu.Game.Skinning LeftStageImage, RightStageImage, BottomStageImage, + + // ReSharper disable once InconsistentNaming Hit300g, + Hit300, Hit200, Hit100, From 97fc2a5473854b403525bcc028aaf24711af58ec Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 1 May 2024 17:20:57 +0300 Subject: [PATCH 3782/4092] Disable inconsistent naming in some fields of `ScoreRank` --- osu.Game/Scoring/ScoreRank.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Scoring/ScoreRank.cs b/osu.Game/Scoring/ScoreRank.cs index 327e4191d7..957cfc9b95 100644 --- a/osu.Game/Scoring/ScoreRank.cs +++ b/osu.Game/Scoring/ScoreRank.cs @@ -35,6 +35,7 @@ namespace osu.Game.Scoring [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.RankSH))] [Description(@"S+")] + // ReSharper disable once InconsistentNaming SH, [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.RankX))] @@ -43,6 +44,7 @@ namespace osu.Game.Scoring [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.RankXH))] [Description(@"SS+")] + // ReSharper disable once InconsistentNaming XH, } } From 16bae4f0046f0c9bde8cdadb2a96f535c0904744 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 1 May 2024 17:22:42 +0300 Subject: [PATCH 3783/4092] Update naming of enum fields in `StartMode` --- .../Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 66acd6d1b0..5446211ced 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -486,16 +486,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match Off = 0, [Description("30 seconds")] - Seconds_30 = 30, + Seconds30 = 30, [Description("1 minute")] - Seconds_60 = 60, + Seconds60 = 60, [Description("3 minutes")] - Seconds_180 = 180, + Seconds180 = 180, [Description("5 minutes")] - Seconds_300 = 300 + Seconds300 = 300 } } } From e8a63813953ac17f3f646fe02dcfdf6f404cb158 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 1 May 2024 17:22:52 +0300 Subject: [PATCH 3784/4092] Update naming of enum fields in `ObjType` --- osu.Game/IO/Legacy/SerializationReader.cs | 76 +++++++++++------------ osu.Game/IO/Legacy/SerializationWriter.cs | 40 ++++++------ 2 files changed, 58 insertions(+), 58 deletions(-) diff --git a/osu.Game/IO/Legacy/SerializationReader.cs b/osu.Game/IO/Legacy/SerializationReader.cs index 2d3d5bffd5..fc61b028a4 100644 --- a/osu.Game/IO/Legacy/SerializationReader.cs +++ b/osu.Game/IO/Legacy/SerializationReader.cs @@ -123,58 +123,58 @@ namespace osu.Game.IO.Legacy switch (t) { - case ObjType.boolType: + case ObjType.BoolType: return ReadBoolean(); - case ObjType.byteType: + case ObjType.ByteType: return ReadByte(); - case ObjType.uint16Type: + case ObjType.UInt16Type: return ReadUInt16(); - case ObjType.uint32Type: + case ObjType.UInt32Type: return ReadUInt32(); - case ObjType.uint64Type: + case ObjType.UInt64Type: return ReadUInt64(); - case ObjType.sbyteType: + case ObjType.SByteType: return ReadSByte(); - case ObjType.int16Type: + case ObjType.Int16Type: return ReadInt16(); - case ObjType.int32Type: + case ObjType.Int32Type: return ReadInt32(); - case ObjType.int64Type: + case ObjType.Int64Type: return ReadInt64(); - case ObjType.charType: + case ObjType.CharType: return ReadChar(); - case ObjType.stringType: + case ObjType.StringType: return base.ReadString(); - case ObjType.singleType: + case ObjType.SingleType: return ReadSingle(); - case ObjType.doubleType: + case ObjType.DoubleType: return ReadDouble(); - case ObjType.decimalType: + case ObjType.DecimalType: return ReadDecimal(); - case ObjType.dateTimeType: + case ObjType.DateTimeType: return ReadDateTime(); - case ObjType.byteArrayType: + case ObjType.ByteArrayType: return ReadByteArray(); - case ObjType.charArrayType: + case ObjType.CharArrayType: return ReadCharArray(); - case ObjType.otherType: + case ObjType.OtherType: throw new IOException("Deserialization of arbitrary type is not supported."); default: @@ -185,25 +185,25 @@ namespace osu.Game.IO.Legacy public enum ObjType : byte { - nullType, - boolType, - byteType, - uint16Type, - uint32Type, - uint64Type, - sbyteType, - int16Type, - int32Type, - int64Type, - charType, - stringType, - singleType, - doubleType, - decimalType, - dateTimeType, - byteArrayType, - charArrayType, - otherType, - ILegacySerializableType + NullType, + BoolType, + ByteType, + UInt16Type, + UInt32Type, + UInt64Type, + SByteType, + Int16Type, + Int32Type, + Int64Type, + CharType, + StringType, + SingleType, + DoubleType, + DecimalType, + DateTimeType, + ByteArrayType, + CharArrayType, + OtherType, + LegacySerializableType } } diff --git a/osu.Game/IO/Legacy/SerializationWriter.cs b/osu.Game/IO/Legacy/SerializationWriter.cs index 10572a6478..afe86cd096 100644 --- a/osu.Game/IO/Legacy/SerializationWriter.cs +++ b/osu.Game/IO/Legacy/SerializationWriter.cs @@ -34,11 +34,11 @@ namespace osu.Game.IO.Legacy { if (str == null) { - Write((byte)ObjType.nullType); + Write((byte)ObjType.NullType); } else { - Write((byte)ObjType.stringType); + Write((byte)ObjType.StringType); base.Write(str); } } @@ -125,94 +125,94 @@ namespace osu.Game.IO.Legacy { if (obj == null) { - Write((byte)ObjType.nullType); + Write((byte)ObjType.NullType); } else { switch (obj) { case bool boolObj: - Write((byte)ObjType.boolType); + Write((byte)ObjType.BoolType); Write(boolObj); break; case byte byteObj: - Write((byte)ObjType.byteType); + Write((byte)ObjType.ByteType); Write(byteObj); break; case ushort ushortObj: - Write((byte)ObjType.uint16Type); + Write((byte)ObjType.UInt16Type); Write(ushortObj); break; case uint uintObj: - Write((byte)ObjType.uint32Type); + Write((byte)ObjType.UInt32Type); Write(uintObj); break; case ulong ulongObj: - Write((byte)ObjType.uint64Type); + Write((byte)ObjType.UInt64Type); Write(ulongObj); break; case sbyte sbyteObj: - Write((byte)ObjType.sbyteType); + Write((byte)ObjType.SByteType); Write(sbyteObj); break; case short shortObj: - Write((byte)ObjType.int16Type); + Write((byte)ObjType.Int16Type); Write(shortObj); break; case int intObj: - Write((byte)ObjType.int32Type); + Write((byte)ObjType.Int32Type); Write(intObj); break; case long longObj: - Write((byte)ObjType.int64Type); + Write((byte)ObjType.Int64Type); Write(longObj); break; case char charObj: - Write((byte)ObjType.charType); + Write((byte)ObjType.CharType); base.Write(charObj); break; case string stringObj: - Write((byte)ObjType.stringType); + Write((byte)ObjType.StringType); base.Write(stringObj); break; case float floatObj: - Write((byte)ObjType.singleType); + Write((byte)ObjType.SingleType); Write(floatObj); break; case double doubleObj: - Write((byte)ObjType.doubleType); + Write((byte)ObjType.DoubleType); Write(doubleObj); break; case decimal decimalObj: - Write((byte)ObjType.decimalType); + Write((byte)ObjType.DecimalType); Write(decimalObj); break; case DateTime dateTimeObj: - Write((byte)ObjType.dateTimeType); + Write((byte)ObjType.DateTimeType); Write(dateTimeObj); break; case byte[] byteArray: - Write((byte)ObjType.byteArrayType); + Write((byte)ObjType.ByteArrayType); base.Write(byteArray); break; case char[] charArray: - Write((byte)ObjType.charArrayType); + Write((byte)ObjType.CharArrayType); base.Write(charArray); break; From 9dc1a58ce7a4fb00a973a3ce4498b5bff8164e16 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 1 May 2024 17:23:27 +0300 Subject: [PATCH 3785/4092] Add few abbreviations from existing enums to appease naming inspection --- osu.sln.DotSettings | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index dd71744bf0..51af281ac6 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -340,6 +340,7 @@ API ARGB BPM + DDKK EF FPS GC @@ -357,6 +358,8 @@ IP IPC JIT + KDDK + KKDD LTRB MD5 NS @@ -375,6 +378,7 @@ QAT BNG UI + WIP False HINT <?xml version="1.0" encoding="utf-16"?> From 30fd40efd16a651a6c00b5c89289a85ffcbe546b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 1 May 2024 21:18:56 +0300 Subject: [PATCH 3786/4092] Avoid disable/restore pairs --- osu.Desktop/NVAPI.cs | 9 ++++++--- osu.Desktop/Windows/WindowsAssociationManager.cs | 8 +++----- osu.Game/Localisation/Language.cs | 4 ++-- osu.Game/Users/CountryCode.cs | 4 ++-- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/osu.Desktop/NVAPI.cs b/osu.Desktop/NVAPI.cs index c1622e7cc1..0b09613ba0 100644 --- a/osu.Desktop/NVAPI.cs +++ b/osu.Desktop/NVAPI.cs @@ -489,7 +489,7 @@ namespace osu.Desktop public static uint Stride => (uint)Marshal.SizeOf(typeof(NvApplication)) | (2 << 16); } - // ReSharper disable InconsistentNaming + [SuppressMessage("ReSharper", "InconsistentNaming")] internal enum NvStatus { OK = 0, // Success. Request is completed. @@ -612,6 +612,7 @@ namespace osu.Desktop FIRMWARE_REVISION_NOT_SUPPORTED = -200, // The device's firmware is not supported. } + [SuppressMessage("ReSharper", "InconsistentNaming")] internal enum NvSystemType { UNKNOWN = 0, @@ -619,6 +620,7 @@ namespace osu.Desktop DESKTOP = 2 } + [SuppressMessage("ReSharper", "InconsistentNaming")] internal enum NvGpuType { UNKNOWN = 0, @@ -626,6 +628,7 @@ namespace osu.Desktop DGPU = 2, // Discrete } + [SuppressMessage("ReSharper", "InconsistentNaming")] internal enum NvSettingID : uint { OGL_AA_LINE_GAMMA_ID = 0x2089BF6C, @@ -718,6 +721,7 @@ namespace osu.Desktop INVALID_SETTING_ID = 0xFFFFFFFF } + [SuppressMessage("ReSharper", "InconsistentNaming")] internal enum NvShimSetting : uint { SHIM_RENDERING_MODE_INTEGRATED = 0x00000000, @@ -732,6 +736,7 @@ namespace osu.Desktop SHIM_RENDERING_MODE_DEFAULT = SHIM_RENDERING_MODE_AUTO_SELECT } + [SuppressMessage("ReSharper", "InconsistentNaming")] internal enum NvThreadControlSetting : uint { OGL_THREAD_CONTROL_ENABLE = 0x00000001, @@ -739,6 +744,4 @@ namespace osu.Desktop OGL_THREAD_CONTROL_NUM_VALUES = 2, OGL_THREAD_CONTROL_DEFAULT = 0 } - - // ReSharper restore InconsistentNaming } diff --git a/osu.Desktop/Windows/WindowsAssociationManager.cs b/osu.Desktop/Windows/WindowsAssociationManager.cs index e4c070934e..b32c01433d 100644 --- a/osu.Desktop/Windows/WindowsAssociationManager.cs +++ b/osu.Desktop/Windows/WindowsAssociationManager.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Runtime.InteropServices; using System.Runtime.Versioning; @@ -163,8 +164,7 @@ namespace osu.Desktop.Windows [DllImport("Shell32.dll")] private static extern void SHChangeNotify(EventId wEventId, Flags uFlags, IntPtr dwItem1, IntPtr dwItem2); - // ReSharper disable InconsistentNaming - + [SuppressMessage("ReSharper", "InconsistentNaming")] private enum EventId { /// @@ -174,14 +174,12 @@ namespace osu.Desktop.Windows SHCNE_ASSOCCHANGED = 0x08000000 } + [SuppressMessage("ReSharper", "InconsistentNaming")] private enum Flags : uint { - // ReSharper disable once InconsistentNaming SHCNF_IDLIST = 0x0000 } - // ReSharper restore InconsistentNaming - #endregion private record FileAssociation(string Extension, LocalisableString Description, string IconPath) diff --git a/osu.Game/Localisation/Language.cs b/osu.Game/Localisation/Language.cs index e946779cba..4e1fc3a474 100644 --- a/osu.Game/Localisation/Language.cs +++ b/osu.Game/Localisation/Language.cs @@ -2,12 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using JetBrains.Annotations; -// ReSharper disable InconsistentNaming - namespace osu.Game.Localisation { + [SuppressMessage("ReSharper", "InconsistentNaming")] [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] public enum Language { diff --git a/osu.Game/Users/CountryCode.cs b/osu.Game/Users/CountryCode.cs index 6a0ed63648..59fcd5d625 100644 --- a/osu.Game/Users/CountryCode.cs +++ b/osu.Game/Users/CountryCode.cs @@ -2,18 +2,18 @@ // See the LICENCE file in the repository root for full licence text. using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using JetBrains.Annotations; using Newtonsoft.Json; using Newtonsoft.Json.Converters; -// ReSharper disable InconsistentNaming - namespace osu.Game.Users { /// /// Matches `osu_countries` database table. /// [JsonConverter(typeof(StringEnumConverter))] + [SuppressMessage("ReSharper", "InconsistentNaming")] [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] public enum CountryCode { From eb45a406e153e9f533f51efaa95d30bb5bc0abe8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 1 May 2024 23:00:24 +0300 Subject: [PATCH 3787/4092] Add failing test case --- .../Formats/LegacyScoreEncoderTest.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreEncoderTest.cs index 806f538249..1e57bd76cf 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreEncoderTest.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Beatmaps.Formats [TestCase(1, 3)] [TestCase(1, 0)] [TestCase(0, 3)] - public void CatchMergesFruitAndDropletMisses(int missCount, int largeTickMissCount) + public void TestCatchMergesFruitAndDropletMisses(int missCount, int largeTickMissCount) { var ruleset = new CatchRuleset().RulesetInfo; var scoreInfo = TestResources.CreateTestScoreInfo(ruleset); @@ -41,7 +41,22 @@ namespace osu.Game.Tests.Beatmaps.Formats } [Test] - public void ScoreWithMissIsNotPerfect() + public void TestFailPreserved() + { + var ruleset = new OsuRuleset().RulesetInfo; + var scoreInfo = TestResources.CreateTestScoreInfo(); + var beatmap = new TestBeatmap(ruleset); + + scoreInfo.Rank = ScoreRank.F; + + var score = new Score { ScoreInfo = scoreInfo }; + var decodedAfterEncode = encodeThenDecode(LegacyBeatmapDecoder.LATEST_VERSION, score, beatmap); + + Assert.That(decodedAfterEncode.ScoreInfo.Rank, Is.EqualTo(ScoreRank.F)); + } + + [Test] + public void TestScoreWithMissIsNotPerfect() { var ruleset = new OsuRuleset().RulesetInfo; var scoreInfo = TestResources.CreateTestScoreInfo(ruleset); From 0106f1fe3ead4d5bdbef25c1e0262b3988131894 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 1 May 2024 23:31:23 +0300 Subject: [PATCH 3788/4092] Preserve score rank on encode/decode --- osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs | 6 ++++++ osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs b/osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs index afdcef1d21..32c18b3af2 100644 --- a/osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs +++ b/osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; +using Newtonsoft.Json.Converters; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Scoring; @@ -38,6 +39,10 @@ namespace osu.Game.Scoring.Legacy [JsonProperty("client_version")] public string ClientVersion = string.Empty; + [JsonProperty("rank")] + [JsonConverter(typeof(StringEnumConverter))] + public ScoreRank? Rank; + public static LegacyReplaySoloScoreInfo FromScore(ScoreInfo score) => new LegacyReplaySoloScoreInfo { OnlineID = score.OnlineID, @@ -45,6 +50,7 @@ namespace osu.Game.Scoring.Legacy Statistics = score.Statistics.Where(kvp => kvp.Value != 0).ToDictionary(), MaximumStatistics = score.MaximumStatistics.Where(kvp => kvp.Value != 0).ToDictionary(), ClientVersion = score.ClientVersion, + Rank = score.Rank, }; } } diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index 65e2c02655..b0d7087ed1 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -40,6 +40,7 @@ namespace osu.Game.Scoring.Legacy }; WorkingBeatmap workingBeatmap; + ScoreRank? decodedRank = null; using (SerializationReader sr = new SerializationReader(stream)) { @@ -129,6 +130,7 @@ namespace osu.Game.Scoring.Legacy score.ScoreInfo.MaximumStatistics = readScore.MaximumStatistics; score.ScoreInfo.Mods = readScore.Mods.Select(m => m.ToMod(currentRuleset)).ToArray(); score.ScoreInfo.ClientVersion = readScore.ClientVersion; + decodedRank = readScore.Rank; }); } } @@ -140,6 +142,9 @@ namespace osu.Game.Scoring.Legacy StandardisedScoreMigrationTools.UpdateFromLegacy(score.ScoreInfo, workingBeatmap); + if (decodedRank != null) + score.ScoreInfo.Rank = decodedRank.Value; + // before returning for database import, we must restore the database-sourced BeatmapInfo. // if not, the clone operation in GetPlayableBeatmap will cause a dereference and subsequent database exception. score.ScoreInfo.BeatmapInfo = workingBeatmap.BeatmapInfo; From 4ffeb5b469b86bdfbf80dde018fc3c9d32ec4e4b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 1 May 2024 23:57:21 +0300 Subject: [PATCH 3789/4092] Resolve post-merge-conflict issues --- .../Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs | 4 ++-- .../Visual/Gameplay/TestSceneStoryboardWithIntro.cs | 2 +- osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs | 2 +- osu.Game/Storyboards/StoryboardVideo.cs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs index dd6f833ec5..800857c973 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs @@ -85,8 +85,8 @@ namespace osu.Game.Tests.Visual.Gameplay if (scaleTransformProvided) { - sprite.TimelineGroup.Scale.Add(Easing.None, Time.Current, Time.Current + 1000, 1, 2); - sprite.TimelineGroup.Scale.Add(Easing.None, Time.Current + 1000, Time.Current + 2000, 2, 1); + sprite.Commands.AddScale(Easing.None, Time.Current, Time.Current + 1000, 1, 2); + sprite.Commands.AddScale(Easing.None, Time.Current + 1000, Time.Current + 2000, 2, 1); } layer.Elements.Clear(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithIntro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithIntro.cs index 502a0de616..ee6a6938a9 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithIntro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithIntro.cs @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.Gameplay { var storyboard = new Storyboard(); var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero); - sprite.TimelineGroup.Alpha.Add(Easing.None, startTime, 0, 0, 1); + sprite.Commands.AddAlpha(Easing.None, startTime, 0, 0, 1); storyboard.GetLayer("Background").Add(sprite); return storyboard; } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs index f2454be190..08c9459042 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs @@ -25,7 +25,7 @@ namespace osu.Game.Storyboards.Drawables // This allows scaling based on the video's absolute size. // // If not specified we take up the full available space. - bool useRelative = !video.TimelineGroup.Scale.HasCommands; + bool useRelative = !video.Commands.Scale.Any(); RelativeSizeAxes = useRelative ? Axes.Both : Axes.None; AutoSizeAxes = useRelative ? Axes.None : Axes.Both; diff --git a/osu.Game/Storyboards/StoryboardVideo.cs b/osu.Game/Storyboards/StoryboardVideo.cs index 5573162d26..14189a1a6c 100644 --- a/osu.Game/Storyboards/StoryboardVideo.cs +++ b/osu.Game/Storyboards/StoryboardVideo.cs @@ -14,7 +14,7 @@ namespace osu.Game.Storyboards { // This is just required to get a valid StartTime based on the incoming offset. // Actual fades are handled inside DrawableStoryboardVideo for now. - TimelineGroup.Alpha.Add(Easing.None, offset, offset, 0, 0); + Commands.AddAlpha(Easing.None, offset, offset, 0, 0); } public override Drawable CreateDrawable() => new DrawableStoryboardVideo(this); From 6d6f165884ab31f4aec606bae0ce6ee3eba4f9fd Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 2 May 2024 00:00:56 +0300 Subject: [PATCH 3790/4092] Make video sprites flippable and vector-scalable to fix type constraint errors Stable supports these on videos already from a quick read on code (since videos are generally a subclass of sprites). --- .../Drawables/DrawableStoryboardVideo.cs | 69 ++++++++++++++++++- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs index 08c9459042..329564a345 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs @@ -1,11 +1,16 @@ // 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 System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Video; +using osu.Framework.Utils; +using osuTK; namespace osu.Game.Storyboards.Drawables { @@ -13,7 +18,7 @@ namespace osu.Game.Storyboards.Drawables { public readonly StoryboardVideo Video; - private Video? drawableVideo; + private DrawableVideo? drawableVideo; public override bool RemoveWhenNotAlive => false; @@ -42,7 +47,7 @@ namespace osu.Game.Storyboards.Drawables if (stream == null) return; - InternalChild = drawableVideo = new Video(stream, false) + InternalChild = drawableVideo = new DrawableVideo(stream, false) { RelativeSizeAxes = RelativeSizeAxes, FillMode = FillMode.Fill, @@ -70,5 +75,65 @@ namespace osu.Game.Storyboards.Drawables drawableVideo.FadeOut(500); } } + + private partial class DrawableVideo : Video, IFlippable, IVectorScalable + { + private bool flipH; + + public bool FlipH + { + get => flipH; + set + { + if (flipH == value) + return; + + flipH = value; + Invalidate(Invalidation.MiscGeometry); + } + } + + private bool flipV; + + public bool FlipV + { + get => flipV; + set + { + if (flipV == value) + return; + + flipV = value; + Invalidate(Invalidation.MiscGeometry); + } + } + + private Vector2 vectorScale = Vector2.One; + + public Vector2 VectorScale + { + get => vectorScale; + set + { + if (vectorScale == value) + return; + + if (!Validation.IsFinite(value)) throw new ArgumentException($@"{nameof(VectorScale)} must be finite, but is {value}."); + + vectorScale = value; + Invalidate(Invalidation.MiscGeometry); + } + } + + protected override Vector2 DrawScale + => new Vector2(FlipH ? -base.DrawScale.X : base.DrawScale.X, FlipV ? -base.DrawScale.Y : base.DrawScale.Y) * VectorScale; + + public override Anchor Origin => StoryboardExtensions.AdjustOrigin(base.Origin, VectorScale, FlipH, FlipV); + + public DrawableVideo(Stream stream, bool startAtCurrentTime = true) + : base(stream, startAtCurrentTime) + { + } + } } } From b1aff91bba0d9aefdfb09d0e40e0b79673a691f3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 2 May 2024 00:31:48 +0300 Subject: [PATCH 3791/4092] Use throw helper methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Storyboards/Commands/StoryboardLoopingGroup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/Commands/StoryboardLoopingGroup.cs b/osu.Game/Storyboards/Commands/StoryboardLoopingGroup.cs index a886998679..fe334ad608 100644 --- a/osu.Game/Storyboards/Commands/StoryboardLoopingGroup.cs +++ b/osu.Game/Storyboards/Commands/StoryboardLoopingGroup.cs @@ -23,7 +23,7 @@ namespace osu.Game.Storyboards.Commands /// The number of times the loop should repeat. Should be greater than zero. Zero means a single playback. public StoryboardLoopingGroup(double startTime, int repeatCount) { - if (repeatCount < 0) throw new ArgumentException("Repeat count must be zero or above.", nameof(repeatCount)); + ArgumentOutOfRangeException.ThrowIfNegative(repeatCount); loopStartTime = startTime; TotalIterations = repeatCount + 1; From 1eac34667d8c91679889475d17c8030e3bb85171 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 2 May 2024 08:02:55 +0200 Subject: [PATCH 3792/4092] Fix test failure --- osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index 43e471320e..383c08c10f 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -352,6 +352,7 @@ namespace osu.Game.Tests.Beatmaps.Formats [HitResult.Great] = 200, [HitResult.LargeTickHit] = 1, }; + scoreInfo.Rank = ScoreRank.A; var beatmap = new TestBeatmap(ruleset); var score = new Score From f9ef689492775d7c419fc1640621fe6cdf9f79e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 May 2024 15:36:40 +0800 Subject: [PATCH 3793/4092] Don't hide playfield layer with HUD --- osu.Game/Screens/Play/HUDOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 2ec2a011a6..9d7a05bc90 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -171,7 +171,7 @@ namespace osu.Game.Screens.Play }, }; - hideTargets = new List { mainComponents, playfieldComponents, topRightElements }; + hideTargets = new List { mainComponents, topRightElements }; if (rulesetComponents != null) hideTargets.Add(rulesetComponents); From 093c25539cd85911f4a06ba780e647737c7ac676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 2 May 2024 13:59:40 +0200 Subject: [PATCH 3794/4092] Add failing test case --- .../Editor/TestSceneOsuEditorGrids.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index d14e593587..b720eb148b 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -5,6 +5,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Testing; using osu.Framework.Utils; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; using osu.Game.Tests.Visual; @@ -54,6 +55,30 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType().Any()); } + [Test] + public void TestDistanceSnapAdjustDoesNotHideTheGrid() + { + double distanceSnap = double.PositiveInfinity; + + AddStep("enable distance snap grid", () => InputManager.Key(Key.T)); + + AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1))); + AddUntilStep("distance snap grid visible", () => this.ChildrenOfType().Any()); + AddStep("store distance snap", () => distanceSnap = this.ChildrenOfType().First().DistanceSpacingMultiplier.Value); + + AddStep("increase distance", () => + { + InputManager.PressKey(Key.AltLeft); + InputManager.PressKey(Key.ControlLeft); + InputManager.ScrollVerticalBy(1); + InputManager.ReleaseKey(Key.ControlLeft); + InputManager.ReleaseKey(Key.AltLeft); + }); + + AddUntilStep("distance snap increased", () => this.ChildrenOfType().First().DistanceSpacingMultiplier.Value, () => Is.GreaterThan(distanceSnap)); + AddUntilStep("distance snap grid still visible", () => this.ChildrenOfType().Any()); + } + [Test] public void TestGridSnapMomentaryToggle() { From 14658824e712a9aea3d1e6697d169a6e0b873201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 2 May 2024 14:11:44 +0200 Subject: [PATCH 3795/4092] Adjust distance snap grid momentary toggle logic to not hide it on spacing adjust --- .../Rulesets/Edit/ComposerDistanceSnapProvider.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs index 62ad2ce7e9..b9850a94a3 100644 --- a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs @@ -63,6 +63,7 @@ namespace osu.Game.Rulesets.Edit public readonly Bindable DistanceSnapToggle = new Bindable(); private bool distanceSnapMomentary; + private TernaryState? distanceSnapStateBeforeMomentaryToggle; private EditorToolboxGroup? toolboxGroup; @@ -213,10 +214,19 @@ namespace osu.Game.Rulesets.Edit { bool altPressed = key.AltPressed; - if (altPressed != distanceSnapMomentary) + if (altPressed && !distanceSnapMomentary) { - distanceSnapMomentary = altPressed; + distanceSnapStateBeforeMomentaryToggle = DistanceSnapToggle.Value; DistanceSnapToggle.Value = DistanceSnapToggle.Value == TernaryState.False ? TernaryState.True : TernaryState.False; + distanceSnapMomentary = true; + } + + if (!altPressed && distanceSnapMomentary) + { + Debug.Assert(distanceSnapStateBeforeMomentaryToggle != null); + DistanceSnapToggle.Value = distanceSnapStateBeforeMomentaryToggle.Value; + distanceSnapStateBeforeMomentaryToggle = null; + distanceSnapMomentary = false; } } From 6000ffed2a7eb000efb4ea045055d620be2dcbbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 2 May 2024 14:15:46 +0200 Subject: [PATCH 3796/4092] Add extended test coverage for intended behaviour --- .../Editor/TestSceneOsuEditorGrids.cs | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index b720eb148b..5798869210 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -53,10 +53,17 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddUntilStep("distance snap grid visible", () => this.ChildrenOfType().Any()); AddStep("release alt", () => InputManager.ReleaseKey(Key.AltLeft)); AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType().Any()); + + AddStep("enable distance snap grid", () => InputManager.Key(Key.T)); + AddUntilStep("distance snap grid visible", () => this.ChildrenOfType().Any()); + AddStep("hold alt", () => InputManager.PressKey(Key.AltLeft)); + AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType().Any()); + AddStep("release alt", () => InputManager.ReleaseKey(Key.AltLeft)); + AddUntilStep("distance snap grid visible", () => this.ChildrenOfType().Any()); } [Test] - public void TestDistanceSnapAdjustDoesNotHideTheGrid() + public void TestDistanceSnapAdjustDoesNotHideTheGridIfStartingEnabled() { double distanceSnap = double.PositiveInfinity; @@ -79,6 +86,34 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddUntilStep("distance snap grid still visible", () => this.ChildrenOfType().Any()); } + [Test] + public void TestDistanceSnapAdjustShowsGridMomentarilyIfStartingDisabled() + { + double distanceSnap = double.PositiveInfinity; + + AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1))); + AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType().Any()); + AddStep("store distance snap", () => distanceSnap = this.ChildrenOfType().First().DistanceSpacingMultiplier.Value); + + AddStep("start increasing distance", () => + { + InputManager.PressKey(Key.AltLeft); + InputManager.PressKey(Key.ControlLeft); + }); + + AddUntilStep("distance snap grid visible", () => this.ChildrenOfType().Any()); + + AddStep("finish increasing distance", () => + { + InputManager.ScrollVerticalBy(1); + InputManager.ReleaseKey(Key.ControlLeft); + InputManager.ReleaseKey(Key.AltLeft); + }); + + AddUntilStep("distance snap increased", () => this.ChildrenOfType().First().DistanceSpacingMultiplier.Value, () => Is.GreaterThan(distanceSnap)); + AddUntilStep("distance snap hidden in the end", () => !this.ChildrenOfType().Any()); + } + [Test] public void TestGridSnapMomentaryToggle() { From 802666e621254fc4574f6c6db7fec2a1dd9f9548 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 2 May 2024 16:08:09 +0200 Subject: [PATCH 3797/4092] Update local metadata lookup cache more often As proposed in https://github.com/ppy/osu/issues/27332#issuecomment-1962308306. --- .../LocalCachedBeatmapMetadataSource.cs | 55 ++++++++++++++++--- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs b/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs index 3f93c32283..27bc803449 100644 --- a/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs +++ b/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs @@ -44,11 +44,34 @@ namespace osu.Game.Beatmaps this.storage = storage; - // avoid downloading / using cache for unit tests. - if (!DebugUtils.IsNUnitRunning && !storage.Exists(cache_database_name)) + if (shouldFetchCache()) prepareLocalCache(); } + private bool shouldFetchCache() + { + // avoid downloading / using cache for unit tests. + if (DebugUtils.IsNUnitRunning) + return false; + + if (!storage.Exists(cache_database_name)) + { + log(@"Fetching local cache because it does not exist."); + return true; + } + + // periodically update the cache to include newer beatmaps. + var fileInfo = new FileInfo(storage.GetFullPath(cache_database_name)); + + if (fileInfo.LastWriteTime < DateTime.Now.AddMonths(-1)) + { + log($@"Refetching local cache because it was last written to on {fileInfo.LastWriteTime}."); + return true; + } + + return false; + } + public bool Available => // no download in progress. cacheDownloadRequest == null @@ -124,6 +147,8 @@ namespace osu.Game.Beatmaps private void prepareLocalCache() { + bool isRefetch = storage.Exists(cache_database_name); + string cacheFilePath = storage.GetFullPath(cache_database_name); string compressedCacheFilePath = $@"{cacheFilePath}.bz2"; @@ -132,9 +157,15 @@ namespace osu.Game.Beatmaps cacheDownloadRequest.Failed += ex => { File.Delete(compressedCacheFilePath); - File.Delete(cacheFilePath); - Logger.Log($@"{nameof(BeatmapUpdaterMetadataLookup)}'s online cache download failed: {ex}", LoggingTarget.Database); + // don't clobber the cache when refetching if the download didn't succeed. seems excessive. + // consequently, also null the download request to allow the existing cache to be used (see `Available`). + if (isRefetch) + cacheDownloadRequest = null; + else + File.Delete(cacheFilePath); + + log($@"Online cache download failed: {ex}"); }; cacheDownloadRequest.Finished += () => @@ -143,15 +174,22 @@ namespace osu.Game.Beatmaps { using (var stream = File.OpenRead(cacheDownloadRequest.Filename)) using (var outStream = File.OpenWrite(cacheFilePath)) - using (var bz2 = new BZip2Stream(stream, CompressionMode.Decompress, false)) - bz2.CopyTo(outStream); + { + // ensure to clobber any and all existing data to avoid accidental corruption. + outStream.SetLength(0); + + using (var bz2 = new BZip2Stream(stream, CompressionMode.Decompress, false)) + bz2.CopyTo(outStream); + } // set to null on completion to allow lookups to begin using the new source cacheDownloadRequest = null; + log(@"Local cache fetch completed successfully."); } catch (Exception ex) { - Logger.Log($@"{nameof(LocalCachedBeatmapMetadataSource)}'s online cache extraction failed: {ex}", LoggingTarget.Database); + log($@"Online cache extraction failed: {ex}"); + // at this point clobber the cache regardless of whether we're refetching, because by this point who knows what state the cache file is in. File.Delete(cacheFilePath); } finally @@ -173,6 +211,9 @@ namespace osu.Game.Beatmaps }); } + private static void log(string message) + => Logger.Log($@"[{nameof(LocalCachedBeatmapMetadataSource)}] {message}", LoggingTarget.Database); + private void logForModel(BeatmapSetInfo set, string message) => RealmArchiveModelImporter.LogForModel(set, $@"[{nameof(LocalCachedBeatmapMetadataSource)}] {message}"); From f534c4aadab0a274674bd5b077e4acea797f177e Mon Sep 17 00:00:00 2001 From: Fabian van Oeffelt Date: Thu, 2 May 2024 18:42:35 +0200 Subject: [PATCH 3798/4092] Initial implementation --- .../Input/Bindings/GlobalActionContainer.cs | 8 ++ .../GlobalActionKeyBindingStrings.cs | 10 ++ osu.Game/Screens/Select/SongSelect.cs | 97 ++++++++++++++++++- 3 files changed, 113 insertions(+), 2 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 296232d9ea..ff7d9f0a6d 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -134,6 +134,8 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Shift, InputKey.F2 }, GlobalAction.SelectPreviousRandom), new KeyBinding(InputKey.F3, GlobalAction.ToggleBeatmapOptions), new KeyBinding(InputKey.BackSpace, GlobalAction.DeselectAllMods), + new KeyBinding(InputKey.PageUp, GlobalAction.IncreaseSpeed), // Not working with minus and other keys.... + new KeyBinding(InputKey.PageDown, GlobalAction.DecreaseSpeed), }; public IEnumerable AudioControlKeyBindings => new[] @@ -364,5 +366,11 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorToggleRotateControl))] EditorToggleRotateControl, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.IncreaseSpeed))] + IncreaseSpeed, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.DecreaseSpeed))] + DecreaseSpeed, } } diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index 8356c480dd..40fe4064ed 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -349,6 +349,16 @@ namespace osu.Game.Localisation /// public static LocalisableString EditorToggleRotateControl => new TranslatableString(getKey(@"editor_toggle_rotate_control"), @"Toggle rotate control"); + /// + /// "Increase Speed" + /// + public static LocalisableString IncreaseSpeed => new TranslatableString(getKey(@"increase_speed"), @"Increase Speed"); + + /// + /// "Decrease Speed" + /// + public static LocalisableString DecreaseSpeed => new TranslatableString(getKey(@"decrease_speed"), @"Decrease Speed"); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 34ee0ae4e8..f2036f017d 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -755,7 +755,95 @@ namespace osu.Game.Screens.Select return false; } - + public void IncreaseSpeed() + { + // find way of grabbing all types of ModSpeedAdjust + var rateAdjustStates = ModSelect.AllAvailableMods.Where(pair => pair.Mod.Acronym == "DT" || pair.Mod.Acronym == "NC" || pair.Mod.Acronym == "HT" || pair.Mod.Acronym == "DC"); + var stateDoubleTime = ModSelect.AllAvailableMods.First(pair => pair.Mod.Acronym == "DT"); + bool oneActive = false; + double newRate = 1.05d; + foreach (var state in rateAdjustStates) + { + ModRateAdjust mod = (ModRateAdjust)state.Mod; + if (state.Active.Value) + { + oneActive = true; + newRate = mod.SpeedChange.Value + 0.05d; + if (mod.Acronym == "DT" || mod.Acronym == "NC") + { + mod.SpeedChange.Value = newRate; + return; + } + else + { + if (newRate == 1.0d) + { + state.Active.Value = false; + } + if (newRate > 1d) + { + state.Active.Value = false; + stateDoubleTime.Active.Value = true; + ((ModDoubleTime)stateDoubleTime.Mod).SpeedChange.Value = newRate; + return; + } + if (newRate < 1d) + { + mod.SpeedChange.Value = newRate; + } + } + } + } + if (!oneActive) + { + stateDoubleTime.Active.Value = true; + ((ModDoubleTime)stateDoubleTime.Mod).SpeedChange.Value = newRate; + } + } + public void DecreaseSpeed() + { + var rateAdjustStates = ModSelect.AllAvailableMods.Where(pair => pair.Mod.Acronym == "DT" || pair.Mod.Acronym == "NC" || pair.Mod.Acronym == "HT" || pair.Mod.Acronym == "DC"); + var stateHalfTime = ModSelect.AllAvailableMods.First(pair => pair.Mod.Acronym == "HT"); + bool oneActive = false; + double newRate = 0.95d; + foreach (var state in rateAdjustStates) + { + ModRateAdjust mod = (ModRateAdjust)state.Mod; + if (state.Active.Value) + { + oneActive = true; + newRate = mod.SpeedChange.Value - 0.05d; + if (mod.Acronym == "HT" || mod.Acronym == "DC") + { + mod.SpeedChange.Value = newRate; + return; + } + else + { + if (newRate == 1.0d) + { + state.Active.Value = false; + } + if (newRate < 1d) + { + state.Active.Value = false; + stateHalfTime.Active.Value = true; + ((ModHalfTime)stateHalfTime.Mod).SpeedChange.Value = newRate; + return; + } + if (newRate > 1d) + { + mod.SpeedChange.Value = newRate; + } + } + } + } + if (!oneActive) + { + stateHalfTime.Active.Value = true; + ((ModHalfTime)stateHalfTime.Mod).SpeedChange.Value = newRate; + } + } protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -955,12 +1043,17 @@ namespace osu.Game.Screens.Select return false; if (!this.IsCurrentScreen()) return false; - switch (e.Action) { case GlobalAction.Select: FinaliseSelection(); return true; + case GlobalAction.IncreaseSpeed: + IncreaseSpeed(); + return true; + case GlobalAction.DecreaseSpeed: + DecreaseSpeed(); + return true; } return false; From 5c21a0330addcf81db97e7ec157510d894cbd6a0 Mon Sep 17 00:00:00 2001 From: Fabian van Oeffelt Date: Thu, 2 May 2024 19:05:07 +0200 Subject: [PATCH 3799/4092] F1 also does not work with minus in song select, same behaviour --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index ff7d9f0a6d..204ecb9061 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -134,7 +134,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Shift, InputKey.F2 }, GlobalAction.SelectPreviousRandom), new KeyBinding(InputKey.F3, GlobalAction.ToggleBeatmapOptions), new KeyBinding(InputKey.BackSpace, GlobalAction.DeselectAllMods), - new KeyBinding(InputKey.PageUp, GlobalAction.IncreaseSpeed), // Not working with minus and other keys.... + new KeyBinding(InputKey.PageUp, GlobalAction.IncreaseSpeed), new KeyBinding(InputKey.PageDown, GlobalAction.DecreaseSpeed), }; From 7527ddbc6865c35e26a7c4deef850e0d4e93d786 Mon Sep 17 00:00:00 2001 From: Fabian van Oeffelt Date: Thu, 2 May 2024 19:05:43 +0200 Subject: [PATCH 3800/4092] Comment, make code more readable, functions are now private --- osu.Game/Screens/Select/SongSelect.cs | 133 ++++++++++++-------------- 1 file changed, 60 insertions(+), 73 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index f2036f017d..5de4a7a9a3 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -755,93 +755,80 @@ namespace osu.Game.Screens.Select return false; } - public void IncreaseSpeed() + private void increaseSpeed() { - // find way of grabbing all types of ModSpeedAdjust - var rateAdjustStates = ModSelect.AllAvailableMods.Where(pair => pair.Mod.Acronym == "DT" || pair.Mod.Acronym == "NC" || pair.Mod.Acronym == "HT" || pair.Mod.Acronym == "DC"); - var stateDoubleTime = ModSelect.AllAvailableMods.First(pair => pair.Mod.Acronym == "DT"); - bool oneActive = false; - double newRate = 1.05d; - foreach (var state in rateAdjustStates) - { - ModRateAdjust mod = (ModRateAdjust)state.Mod; - if (state.Active.Value) - { - oneActive = true; - newRate = mod.SpeedChange.Value + 0.05d; - if (mod.Acronym == "DT" || mod.Acronym == "NC") - { - mod.SpeedChange.Value = newRate; - return; - } - else - { - if (newRate == 1.0d) - { - state.Active.Value = false; - } - if (newRate > 1d) - { - state.Active.Value = false; - stateDoubleTime.Active.Value = true; - ((ModDoubleTime)stateDoubleTime.Mod).SpeedChange.Value = newRate; - return; - } - if (newRate < 1d) - { - mod.SpeedChange.Value = newRate; - } - } - } - } - if (!oneActive) + var rateAdjustStates = ModSelect.AllAvailableMods.Where(pair => pair.Mod is ModRateAdjust); + var stateDoubleTime = ModSelect.AllAvailableMods.First(pair => pair.Mod is ModDoubleTime); + bool rateModActive = ModSelect.AllAvailableMods.Where(pair => pair.Mod is ModRateAdjust && pair.Active.Value).Count() > 0; + double stepSize = 0.05d; + double newRate = 1d + stepSize; + // If no mod rateAdjust mod is currently active activate DoubleTime with speed newRate + if (!rateModActive) { stateDoubleTime.Active.Value = true; ((ModDoubleTime)stateDoubleTime.Mod).SpeedChange.Value = newRate; + return; } - } - public void DecreaseSpeed() - { - var rateAdjustStates = ModSelect.AllAvailableMods.Where(pair => pair.Mod.Acronym == "DT" || pair.Mod.Acronym == "NC" || pair.Mod.Acronym == "HT" || pair.Mod.Acronym == "DC"); - var stateHalfTime = ModSelect.AllAvailableMods.First(pair => pair.Mod.Acronym == "HT"); - bool oneActive = false; - double newRate = 0.95d; + // Find current active rateAdjust mod and modify speed, enable DoubleTime if necessary foreach (var state in rateAdjustStates) { ModRateAdjust mod = (ModRateAdjust)state.Mod; - if (state.Active.Value) + if (!state.Active.Value) continue; + newRate = mod.SpeedChange.Value + stepSize; + if (mod.Acronym == "DT" || mod.Acronym == "NC") + mod.SpeedChange.Value = newRate; + else { - oneActive = true; - newRate = mod.SpeedChange.Value - 0.05d; - if (mod.Acronym == "HT" || mod.Acronym == "DC") + if (newRate == 1.0d) + state.Active.Value = false; + if (newRate > 1d) { + state.Active.Value = false; + stateDoubleTime.Active.Value = true; + ((ModDoubleTime)stateDoubleTime.Mod).SpeedChange.Value = newRate; + break; + } + if (newRate < 1d) mod.SpeedChange.Value = newRate; - return; - } - else - { - if (newRate == 1.0d) - { - state.Active.Value = false; - } - if (newRate < 1d) - { - state.Active.Value = false; - stateHalfTime.Active.Value = true; - ((ModHalfTime)stateHalfTime.Mod).SpeedChange.Value = newRate; - return; - } - if (newRate > 1d) - { - mod.SpeedChange.Value = newRate; - } - } } } - if (!oneActive) + } + private void decreaseSpeed() + { + var rateAdjustStates = ModSelect.AllAvailableMods.Where(pair => pair.Mod is ModRateAdjust); + var stateHalfTime = ModSelect.AllAvailableMods.First(pair => pair.Mod is ModHalfTime); + bool rateModActive = ModSelect.AllAvailableMods.Where(pair => pair.Mod is ModRateAdjust && pair.Active.Value).Count() > 0; + double stepSize = 0.05d; + double newRate = 1d - stepSize; + // If no mod rateAdjust mod is currently active activate HalfTime with speed newRate + if (!rateModActive) { stateHalfTime.Active.Value = true; ((ModHalfTime)stateHalfTime.Mod).SpeedChange.Value = newRate; + return; + } + // Find current active rateAdjust mod and modify speed, enable HalfTime if necessary + foreach (var state in rateAdjustStates) + { + ModRateAdjust mod = (ModRateAdjust)state.Mod; + if (!state.Active.Value) continue; + newRate = mod.SpeedChange.Value - stepSize; + if (mod.Acronym == "HT" || mod.Acronym == "DC") + mod.SpeedChange.Value = newRate; + else + { + if (newRate == 1.0d) + state.Active.Value = false; + if (newRate < 1d) + { + state.Active.Value = false; + stateHalfTime.Active.Value = true; + ((ModHalfTime)stateHalfTime.Mod).SpeedChange.Value = newRate; + break; + } + if (newRate > 1d) + mod.SpeedChange.Value = newRate; + } } } protected override void Dispose(bool isDisposing) @@ -1049,10 +1036,10 @@ namespace osu.Game.Screens.Select FinaliseSelection(); return true; case GlobalAction.IncreaseSpeed: - IncreaseSpeed(); + increaseSpeed(); return true; case GlobalAction.DecreaseSpeed: - DecreaseSpeed(); + decreaseSpeed(); return true; } From 588badf29216782699ae359700d6449e82638187 Mon Sep 17 00:00:00 2001 From: Fabian van Oeffelt Date: Thu, 2 May 2024 19:22:39 +0200 Subject: [PATCH 3801/4092] Fix Formatting --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index f6bc002e32..5dacb6db4d 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -416,7 +416,7 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.DecreaseSpeed))] DecreaseSpeed, - + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.IncreaseOffset))] IncreaseOffset, From 4b5ea6bd0bc46cf37612efa0de3c81f3d4fe52bc Mon Sep 17 00:00:00 2001 From: Fabian van Oeffelt Date: Thu, 2 May 2024 19:41:00 +0200 Subject: [PATCH 3802/4092] Fix Code Inspection --- osu.Game/Screens/Select/SongSelect.cs | 36 +++++++++++++++++++++------ 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 73ce029d3f..de0f24aa90 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -808,13 +808,15 @@ namespace osu.Game.Screens.Select return false; } + private void increaseSpeed() { var rateAdjustStates = ModSelect.AllAvailableMods.Where(pair => pair.Mod is ModRateAdjust); var stateDoubleTime = ModSelect.AllAvailableMods.First(pair => pair.Mod is ModDoubleTime); - bool rateModActive = ModSelect.AllAvailableMods.Where(pair => pair.Mod is ModRateAdjust && pair.Active.Value).Count() > 0; - double stepSize = 0.05d; - double newRate = 1d + stepSize; + bool rateModActive = ModSelect.AllAvailableMods.Count(pair => pair.Mod is ModRateAdjust && pair.Active.Value) > 0; + const double stepsize = 0.05d; + double newRate = 1d + stepsize; + // If no mod rateAdjust mod is currently active activate DoubleTime with speed newRate if (!rateModActive) { @@ -822,18 +824,23 @@ namespace osu.Game.Screens.Select ((ModDoubleTime)stateDoubleTime.Mod).SpeedChange.Value = newRate; return; } + // Find current active rateAdjust mod and modify speed, enable DoubleTime if necessary foreach (var state in rateAdjustStates) { ModRateAdjust mod = (ModRateAdjust)state.Mod; + if (!state.Active.Value) continue; - newRate = mod.SpeedChange.Value + stepSize; + + newRate = mod.SpeedChange.Value + stepsize; + if (mod.Acronym == "DT" || mod.Acronym == "NC") mod.SpeedChange.Value = newRate; else { if (newRate == 1.0d) state.Active.Value = false; + if (newRate > 1d) { state.Active.Value = false; @@ -841,18 +848,21 @@ namespace osu.Game.Screens.Select ((ModDoubleTime)stateDoubleTime.Mod).SpeedChange.Value = newRate; break; } + if (newRate < 1d) mod.SpeedChange.Value = newRate; } } } + private void decreaseSpeed() { var rateAdjustStates = ModSelect.AllAvailableMods.Where(pair => pair.Mod is ModRateAdjust); var stateHalfTime = ModSelect.AllAvailableMods.First(pair => pair.Mod is ModHalfTime); - bool rateModActive = ModSelect.AllAvailableMods.Where(pair => pair.Mod is ModRateAdjust && pair.Active.Value).Count() > 0; - double stepSize = 0.05d; - double newRate = 1d - stepSize; + bool rateModActive = ModSelect.AllAvailableMods.Count(pair => pair.Mod is ModRateAdjust && pair.Active.Value) > 0; + const double stepsize = 0.05d; + double newRate = 1d - stepsize; + // If no mod rateAdjust mod is currently active activate HalfTime with speed newRate if (!rateModActive) { @@ -860,18 +870,23 @@ namespace osu.Game.Screens.Select ((ModHalfTime)stateHalfTime.Mod).SpeedChange.Value = newRate; return; } + // Find current active rateAdjust mod and modify speed, enable HalfTime if necessary foreach (var state in rateAdjustStates) { ModRateAdjust mod = (ModRateAdjust)state.Mod; + if (!state.Active.Value) continue; - newRate = mod.SpeedChange.Value - stepSize; + + newRate = mod.SpeedChange.Value - stepsize; + if (mod.Acronym == "HT" || mod.Acronym == "DC") mod.SpeedChange.Value = newRate; else { if (newRate == 1.0d) state.Active.Value = false; + if (newRate < 1d) { state.Active.Value = false; @@ -879,11 +894,13 @@ namespace osu.Game.Screens.Select ((ModHalfTime)stateHalfTime.Mod).SpeedChange.Value = newRate; break; } + if (newRate > 1d) mod.SpeedChange.Value = newRate; } } } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -1086,14 +1103,17 @@ namespace osu.Game.Screens.Select return false; if (!this.IsCurrentScreen()) return false; + switch (e.Action) { case GlobalAction.Select: FinaliseSelection(); return true; + case GlobalAction.IncreaseSpeed: increaseSpeed(); return true; + case GlobalAction.DecreaseSpeed: decreaseSpeed(); return true; From 2f075e32479542e8b3dc0abd0ae3c93c4f0dae9f Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 2 May 2024 17:01:40 -0700 Subject: [PATCH 3803/4092] Apply half margin of tolerance on both sides before text scrolls --- .../TestSceneNowPlayingOverlay.cs | 22 ++++++++++++++----- osu.Game/Overlays/NowPlayingOverlay.cs | 4 +++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs index 40e0d9250d..1670741cbd 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs @@ -45,16 +45,28 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestLongMetadata() { - AddStep(@"set beatmap", () => Beatmap.Value = CreateWorkingBeatmap(new Beatmap + AddStep(@"set metadata within tolerance", () => Beatmap.Value = CreateWorkingBeatmap(new Beatmap { Metadata = { - Artist = "very very very very very very very very very very very long artist", - ArtistUnicode = "very very very very very very very very very very very long artist", - Title = "very very very very very very very very very very very long title", - TitleUnicode = "very very very very very very very very very very very long title", + Artist = "very very very very very very very very very very verry long artist", + ArtistUnicode = "very very very very very very very very very very verry long artist", + Title = "very very very very very verry long title", + TitleUnicode = "very very very very very verry long title", } })); + + AddStep(@"set metadata outside bounds", () => Beatmap.Value = CreateWorkingBeatmap(new Beatmap + { + Metadata = + { + Artist = "very very very very very very very very very very verrry long artist", + ArtistUnicode = "very very very very very very very very very very verrry long artist", + Title = "very very very very very verrry long title", + TitleUnicode = "very very very very very verrry long title", + } + })); + AddStep(@"show", () => nowPlayingOverlay.Show()); } } diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index be405257ca..b1f72fd792 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -9,6 +9,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; @@ -533,7 +534,8 @@ namespace osu.Game.Overlays float textOverflowWidth = mainSpriteText.Width - player_width; - if (textOverflowWidth > 0) + // apply half margin of tolerance on both sides before the text scrolls + if (textOverflowWidth > margin) { fillerSpriteText.Alpha = 1; fillerSpriteText.Text = text; From 269077f85496142f470b3fce4306ac33ac8216ea Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 2 May 2024 17:04:22 -0700 Subject: [PATCH 3804/4092] Only support centre anchors --- osu.Game/Overlays/NowPlayingOverlay.cs | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index b1f72fd792..7329a3b404 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -540,24 +540,8 @@ namespace osu.Game.Overlays fillerSpriteText.Alpha = 1; fillerSpriteText.Text = text; - float initialX; - float targetX; - - if (Anchor.HasFlagFast(Anchor.x0)) - { - initialX = 0; - targetX = -mainSpriteText.Width; - } - else if (Anchor.HasFlagFast(Anchor.x1)) - { - initialX = (textOverflowWidth + mainSpriteText.Width) / 2; - targetX = (textOverflowWidth - mainSpriteText.Width) / 2; - } - else // Anchor.x2 - { - initialX = textOverflowWidth + mainSpriteText.Width; - targetX = textOverflowWidth; - } + float initialX = (textOverflowWidth + mainSpriteText.Width) / 2; + float targetX = (textOverflowWidth - mainSpriteText.Width) / 2; this.MoveToX(initialX) .Delay(initial_move_delay) From 381ddb067618abb7b7cfceed944669e346b1bdf3 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 2 May 2024 17:05:12 -0700 Subject: [PATCH 3805/4092] Fix weird formatting --- osu.Game/Overlays/NowPlayingOverlay.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 7329a3b404..1488f59246 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -494,14 +494,13 @@ namespace osu.Game.Overlays public FontUsage Font { - set => - Schedule(() => - { - mainSpriteText.Font = value; - fillerSpriteText.Font = value; + set => Schedule(() => + { + mainSpriteText.Font = value; + fillerSpriteText.Font = value; - updateText(); - }); + updateText(); + }); } public ScrollingTextContainer() From c15a68507152b0710b90a4fa947cb5b994258bf9 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 2 May 2024 17:07:49 -0700 Subject: [PATCH 3806/4092] Remove unused usings --- osu.Game/Overlays/NowPlayingOverlay.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 1488f59246..8f97ce50cc 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -6,10 +6,8 @@ using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; From aa4d16bdb873d7296b899cee7b7491ffdf5cd6ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 May 2024 12:13:52 +0800 Subject: [PATCH 3807/4092] Fix beatmap listing cards being far too large --- osu.Game/Overlays/BeatmapListingOverlay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index a645683c5f..9b2f26e8ae 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -198,6 +198,7 @@ namespace osu.Game.Overlays { c.Anchor = Anchor.TopCentre; c.Origin = Anchor.TopCentre; + c.Scale = new Vector2(0.8f); })).ToArray(); private static ReverseChildIDFillFlowContainer createCardContainerFor(IEnumerable newCards) From 3249ecee2731f49f95d8095e4bb96371d301e4a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 May 2024 12:31:19 +0800 Subject: [PATCH 3808/4092] Fix chat overlay being far too large --- osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs | 4 ++-- osu.Game/Overlays/Chat/ChatLine.cs | 2 +- osu.Game/Overlays/Chat/ChatTextBar.cs | 9 ++++++--- osu.Game/Overlays/ChatOverlay.cs | 3 +-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs index 87b1f4ef01..e8c251e7fd 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs @@ -49,7 +49,7 @@ namespace osu.Game.Overlays.Chat.ChannelList [BackgroundDependencyLoader] private void load() { - Height = 30; + Height = 25; RelativeSizeAxes = Axes.X; Children = new Drawable[] @@ -87,7 +87,7 @@ namespace osu.Game.Overlays.Chat.ChannelList Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Text = Channel.Name, - Font = OsuFont.Torus.With(size: 17, weight: FontWeight.SemiBold), + Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold), Colour = colourProvider.Light3, Margin = new MarginPadding { Bottom = 2 }, RelativeSizeAxes = Axes.X, diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index bbc3ee5bf4..9bcca3ac9d 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -47,7 +47,7 @@ namespace osu.Game.Overlays.Chat public IReadOnlyCollection DrawableContentFlow => drawableContentFlow; - protected virtual float FontSize => 20; + protected virtual float FontSize => 14; protected virtual float Spacing => 15; diff --git a/osu.Game/Overlays/Chat/ChatTextBar.cs b/osu.Game/Overlays/Chat/ChatTextBar.cs index 16a8d14b10..0a42363279 100644 --- a/osu.Game/Overlays/Chat/ChatTextBar.cs +++ b/osu.Game/Overlays/Chat/ChatTextBar.cs @@ -19,6 +19,8 @@ namespace osu.Game.Overlays.Chat { public partial class ChatTextBar : Container { + public const float HEIGHT = 40; + public readonly BindableBool ShowSearch = new BindableBool(); public event Action? OnChatMessageCommitted; @@ -45,7 +47,7 @@ namespace osu.Game.Overlays.Chat private void load(OverlayColourProvider colourProvider) { RelativeSizeAxes = Axes.X; - Height = 60; + Height = HEIGHT; Children = new Drawable[] { @@ -76,7 +78,7 @@ namespace osu.Game.Overlays.Chat Child = chattingText = new TruncatingSpriteText { MaxWidth = chatting_text_width - padding * 2, - Font = OsuFont.Torus.With(size: 20), + Font = OsuFont.Torus, Colour = colourProvider.Background1, Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, @@ -91,7 +93,7 @@ namespace osu.Game.Overlays.Chat Icon = FontAwesome.Solid.Search, Origin = Anchor.CentreRight, Anchor = Anchor.CentreRight, - Size = new Vector2(20), + Size = new Vector2(OsuFont.DEFAULT_FONT_SIZE), Margin = new MarginPadding { Right = 2 }, }, }, @@ -101,6 +103,7 @@ namespace osu.Game.Overlays.Chat Padding = new MarginPadding { Right = padding }, Child = chatTextBox = new ChatTextBox { + FontSize = OsuFont.DEFAULT_FONT_SIZE, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.X, diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 8f3b7031c2..b11483e678 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -55,7 +55,6 @@ namespace osu.Game.Overlays private const int transition_length = 500; private const float top_bar_height = 40; private const float side_bar_width = 190; - private const float chat_bar_height = 60; protected override string PopInSampleName => @"UI/overlay-big-pop-in"; protected override string PopOutSampleName => @"UI/overlay-big-pop-out"; @@ -136,7 +135,7 @@ namespace osu.Game.Overlays Padding = new MarginPadding { Left = side_bar_width, - Bottom = chat_bar_height, + Bottom = ChatTextBar.HEIGHT, }, Children = new Drawable[] { From 058bd5ced66d8d6937f77d41f3e4704089177530 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 May 2024 13:38:27 +0800 Subject: [PATCH 3809/4092] Stop using visually noisy `bg4` for default backgrounds This has always really annoyed me. --- osu.Game/Beatmaps/DummyWorkingBeatmap.cs | 2 +- osu.Game/Overlays/NowPlayingOverlay.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index d254945a51..35067f4055 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -53,7 +53,7 @@ namespace osu.Game.Beatmaps protected override IBeatmap GetBeatmap() => new Beatmap(); - public override Texture GetBackground() => textures?.Get(@"Backgrounds/bg4"); + public override Texture GetBackground() => textures?.Get(@"Backgrounds/bg2"); protected override Track GetBeatmapTrack() => GetVirtualTrack(); diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index ab99370603..7e4f4f4e9b 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -422,7 +422,7 @@ namespace osu.Game.Overlays [BackgroundDependencyLoader] private void load(LargeTextureStore textures) { - sprite.Texture = beatmap.GetBackground() ?? textures.Get(@"Backgrounds/bg4"); + sprite.Texture = beatmap.GetBackground() ?? textures.Get(@"Backgrounds/bg2"); } } From c1e9b6d4cae0793de9bc15cc055808db4995b1a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 May 2024 13:42:56 +0800 Subject: [PATCH 3810/4092] Fix beatmap backgrounds loading default briefly before final display Due to the way `ModelBackedDrawable` works, the default starts to get loaded even though a final `Beatmap` has been set. This avoids loading the default fallback unless a beatmap has been set (and has no background itself). --- .../Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs index 0bb60847e5..f067af5360 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs @@ -52,6 +52,9 @@ namespace osu.Game.Beatmaps.Drawables private Drawable getDrawableForModel(IBeatmapInfo? model) { + if (model == null) + return Empty(); + // prefer online cover where available. if (model?.BeatmapSet is IBeatmapSetOnlineInfo online) return new OnlineBeatmapSetCover(online, beatmapSetCoverType); From c21b7c7df994fa6a00a3edf7bc79653035bbaa8c Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 2 May 2024 22:46:42 -0700 Subject: [PATCH 3811/4092] Use `IsLoaded` instead of `Schedule` --- osu.Game/Overlays/NowPlayingOverlay.cs | 35 ++++++++++++++++++++------ 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 8f97ce50cc..744d32ff7e 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -476,29 +476,35 @@ namespace osu.Game.Overlays private const float initial_move_delay = 1000; private const float pixels_per_second = 50; - private LocalisableString text; private OsuSpriteText mainSpriteText = null!; private OsuSpriteText fillerSpriteText = null!; + private LocalisableString text; + public LocalisableString Text { get => text; set { text = value; - Schedule(updateText); + + if (IsLoaded) + updateText(); } } + private FontUsage font = OsuFont.Default; + public FontUsage Font { - set => Schedule(() => + get => font; + set { - mainSpriteText.Font = value; - fillerSpriteText.Font = value; + font = value; - updateText(); - }); + if (IsLoaded) + updateFontAndText(); + } } public ScrollingTextContainer() @@ -521,6 +527,21 @@ namespace osu.Game.Overlays }; } + protected override void LoadComplete() + { + base.LoadComplete(); + + updateFontAndText(); + } + + private void updateFontAndText() + { + mainSpriteText.Font = font; + fillerSpriteText.Font = font; + + updateText(); + } + private void updateText() { mainSpriteText.Text = text; From c935d3bf6c12e0b6cd5e9213dcd9f3d68c27b22b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 May 2024 14:00:28 +0800 Subject: [PATCH 3812/4092] Reduce font size in channel listing too --- osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs b/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs index 9c85c73ee4..466f8b2f5d 100644 --- a/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs +++ b/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs @@ -44,7 +44,7 @@ namespace osu.Game.Overlays.Chat.Listing [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; - private const float text_size = 18; + private const float text_size = 14; private const float icon_size = 14; private const float vertical_margin = 1.5f; From d1a50ff85bbfb4a502bd4df25a53d93386d7f920 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 3 May 2024 08:11:00 +0200 Subject: [PATCH 3813/4092] Remove redundant conditional access --- .../Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs index f067af5360..6f71fa90b8 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs @@ -56,7 +56,7 @@ namespace osu.Game.Beatmaps.Drawables return Empty(); // prefer online cover where available. - if (model?.BeatmapSet is IBeatmapSetOnlineInfo online) + if (model.BeatmapSet is IBeatmapSetOnlineInfo online) return new OnlineBeatmapSetCover(online, beatmapSetCoverType); if (model is BeatmapInfo localModel) From 42e49067e55ddb232d6fe1af0af44234a772f5fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 May 2024 17:10:59 +0800 Subject: [PATCH 3814/4092] Move `Room.Status` updates to a common location --- osu.Game/Online/Rooms/GetRoomsRequest.cs | 19 +++++++++++++++++++ osu.Game/Online/Rooms/Room.cs | 6 ++---- .../Lounge/Components/RoomStatusPill.cs | 12 +----------- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 3 +++ 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/osu.Game/Online/Rooms/GetRoomsRequest.cs b/osu.Game/Online/Rooms/GetRoomsRequest.cs index 7feb709acb..bfb2629c64 100644 --- a/osu.Game/Online/Rooms/GetRoomsRequest.cs +++ b/osu.Game/Online/Rooms/GetRoomsRequest.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 System; using System.Collections.Generic; using osu.Framework.IO.Network; using osu.Game.Extensions; using osu.Game.Online.API; +using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Screens.OnlinePlay.Lounge.Components; namespace osu.Game.Online.Rooms @@ -33,6 +35,23 @@ namespace osu.Game.Online.Rooms return req; } + protected override void PostProcess() + { + base.PostProcess(); + + if (Response != null) + { + // API doesn't populate status so let's do it here. + foreach (var room in Response) + { + if (room.EndDate.Value != null && DateTimeOffset.Now >= room.EndDate.Value) + room.Status.Value = new RoomStatusEnded(); + else + room.Status.Value = new RoomStatusOpen(); + } + } + } + protected override string Target => "rooms"; } } diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 8f346c4057..23c77f8773 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -111,8 +111,9 @@ namespace osu.Game.Online.Rooms [JsonProperty("current_user_score")] public readonly Bindable UserScore = new Bindable(); + [Cached] [JsonProperty("has_password")] - public readonly BindableBool HasPassword = new BindableBool(); + public readonly Bindable HasPassword = new Bindable(); [Cached] [JsonProperty("recent_participants")] @@ -201,9 +202,6 @@ namespace osu.Game.Online.Rooms CurrentPlaylistItem.Value = other.CurrentPlaylistItem.Value; AutoSkip.Value = other.AutoSkip.Value; - if (EndDate.Value != null && DateTimeOffset.Now >= EndDate.Value) - Status.Value = new RoomStatusEnded(); - other.RemoveExpiredPlaylistItems(); if (!Playlist.SequenceEqual(other.Playlist)) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs index aae82b6721..96d698a184 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs @@ -1,13 +1,11 @@ // 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.Sprites; using osu.Game.Graphics; using osu.Game.Online.Rooms; -using osu.Game.Online.Rooms.RoomStatuses; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { @@ -36,18 +34,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components private void updateDisplay() { - RoomStatus status = getDisplayStatus(); + RoomStatus status = Status.Value; Pill.Background.FadeColour(status.GetAppropriateColour(colours), 100); TextFlow.Text = status.Message; } - - private RoomStatus getDisplayStatus() - { - if (EndDate.Value < DateTimeOffset.Now) - return new RoomStatusEnded(); - - return Status.Value; - } } } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index ff536a65c4..83df1c6161 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -77,6 +77,9 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] public Bindable Password { get; private set; } + [Resolved(typeof(Room))] + public Bindable HasPassword { get; private set; } + [Resolved(typeof(Room))] protected Bindable Duration { get; private set; } From 7141177966c82ffa6f9e947687422f45645c4966 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 May 2024 17:11:29 +0800 Subject: [PATCH 3815/4092] Better signify private rooms by showing a different status pill design --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 2 +- osu.Game/Online/Rooms/GetRoomsRequest.cs | 2 ++ .../Rooms/RoomStatuses/RoomStatusOpenPrivate.cs | 14 ++++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Online/Rooms/RoomStatuses/RoomStatusOpenPrivate.cs diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index bbf0e3697a..871fbc15b3 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -396,7 +396,7 @@ namespace osu.Game.Online.Multiplayer switch (state) { case MultiplayerRoomState.Open: - APIRoom.Status.Value = new RoomStatusOpen(); + APIRoom.Status.Value = APIRoom.HasPassword.Value ? new RoomStatusOpenPrivate() : new RoomStatusOpen(); break; case MultiplayerRoomState.Playing: diff --git a/osu.Game/Online/Rooms/GetRoomsRequest.cs b/osu.Game/Online/Rooms/GetRoomsRequest.cs index bfb2629c64..1b5e08c729 100644 --- a/osu.Game/Online/Rooms/GetRoomsRequest.cs +++ b/osu.Game/Online/Rooms/GetRoomsRequest.cs @@ -46,6 +46,8 @@ namespace osu.Game.Online.Rooms { if (room.EndDate.Value != null && DateTimeOffset.Now >= room.EndDate.Value) room.Status.Value = new RoomStatusEnded(); + else if (room.HasPassword.Value) + room.Status.Value = new RoomStatusOpenPrivate(); else room.Status.Value = new RoomStatusOpen(); } diff --git a/osu.Game/Online/Rooms/RoomStatuses/RoomStatusOpenPrivate.cs b/osu.Game/Online/Rooms/RoomStatuses/RoomStatusOpenPrivate.cs new file mode 100644 index 0000000000..d71e706c76 --- /dev/null +++ b/osu.Game/Online/Rooms/RoomStatuses/RoomStatusOpenPrivate.cs @@ -0,0 +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 osu.Game.Graphics; +using osuTK.Graphics; + +namespace osu.Game.Online.Rooms.RoomStatuses +{ + public class RoomStatusOpenPrivate : RoomStatus + { + public override string Message => "Open (Private)"; + public override Color4 GetAppropriateColour(OsuColour colours) => colours.GreenDark; + } +} From f57818f5a2245d2b2b27a19555fc65f7e5575ac1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 3 May 2024 11:25:50 +0200 Subject: [PATCH 3816/4092] Add visual test coverage of private room status --- osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs index 4ffccdbf0e..98242e2d92 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs @@ -69,8 +69,9 @@ namespace osu.Game.Tests.Visual.Multiplayer }), createLoungeRoom(new Room { - Name = { Value = "Multiplayer room" }, - Status = { Value = new RoomStatusOpen() }, + Name = { Value = "Private room" }, + Status = { Value = new RoomStatusOpenPrivate() }, + HasPassword = { Value = true }, EndDate = { Value = DateTimeOffset.Now.AddDays(1) }, Type = { Value = MatchType.HeadToHead }, Playlist = From 221b4cd599df85d23212519a8a68bec8bb1e7797 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 3 May 2024 11:36:18 +0200 Subject: [PATCH 3817/4092] Remove unused cache --- osu.Game/Online/Rooms/Room.cs | 1 - osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs | 3 --- 2 files changed, 4 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 23c77f8773..5abf5034d9 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -111,7 +111,6 @@ namespace osu.Game.Online.Rooms [JsonProperty("current_user_score")] public readonly Bindable UserScore = new Bindable(); - [Cached] [JsonProperty("has_password")] public readonly Bindable HasPassword = new Bindable(); diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 83df1c6161..ff536a65c4 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -77,9 +77,6 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] public Bindable Password { get; private set; } - [Resolved(typeof(Room))] - public Bindable HasPassword { get; private set; } - [Resolved(typeof(Room))] protected Bindable Duration { get; private set; } From 7d31af6f16f3ecbc6d04051bad0a2b5284cb9326 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 3 May 2024 11:34:42 +0200 Subject: [PATCH 3818/4092] Fix room status not updating when password is changed while inside the room --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 871fbc15b3..77ede1fd35 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -816,6 +816,7 @@ namespace osu.Game.Online.Multiplayer Room.Settings = settings; APIRoom.Name.Value = Room.Settings.Name; APIRoom.Password.Value = Room.Settings.Password; + APIRoom.Status.Value = string.IsNullOrEmpty(Room.Settings.Password) ? new RoomStatusOpen() : new RoomStatusOpenPrivate(); APIRoom.Type.Value = Room.Settings.MatchType; APIRoom.QueueMode.Value = Room.Settings.QueueMode; APIRoom.AutoStartDuration.Value = Room.Settings.AutoStartDuration; From 1b7652e60d37d496c9c1f01a984e22b743904d35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 3 May 2024 13:47:10 +0200 Subject: [PATCH 3819/4092] Add failing tests --- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 192 ++++++++++++++++++++ 1 file changed, 192 insertions(+) diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index ebbc329b9d..eb2c098ab8 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -15,6 +15,7 @@ using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.IO.Archives; using osu.Game.Online.API; +using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; @@ -23,6 +24,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Tests.Beatmaps.IO; using osu.Game.Tests.Resources; +using osu.Game.Users; namespace osu.Game.Tests.Scores.IO { @@ -284,6 +286,196 @@ namespace osu.Game.Tests.Scores.IO } } + [Test] + public void TestUserLookedUpForOnlineScore() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + { + try + { + var osu = LoadOsuIntoHost(host, true); + + var api = (DummyAPIAccess)osu.API; + api.HandleRequest = req => + { + switch (req) + { + case GetUserRequest userRequest: + userRequest.TriggerSuccess(new APIUser + { + Username = "Test user", + CountryCode = CountryCode.JP, + Id = 1234 + }); + return true; + + default: + return false; + } + }; + + var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely(); + + var toImport = new ScoreInfo + { + Rank = ScoreRank.B, + TotalScore = 987654, + Accuracy = 0.8, + MaxCombo = 500, + Combo = 250, + User = new APIUser { Username = "Test user" }, + Date = DateTimeOffset.Now, + OnlineID = 12345, + Ruleset = new OsuRuleset().RulesetInfo, + BeatmapInfo = beatmap.Beatmaps.First() + }; + + var imported = LoadScoreIntoOsu(osu, toImport); + + Assert.AreEqual(toImport.Rank, imported.Rank); + Assert.AreEqual(toImport.TotalScore, imported.TotalScore); + Assert.AreEqual(toImport.Accuracy, imported.Accuracy); + Assert.AreEqual(toImport.MaxCombo, imported.MaxCombo); + Assert.AreEqual(toImport.User.Username, imported.User.Username); + Assert.AreEqual(toImport.Date, imported.Date); + Assert.AreEqual(toImport.OnlineID, imported.OnlineID); + Assert.AreEqual(toImport.User.Username, imported.RealmUser.Username); + Assert.AreEqual(1234, imported.RealmUser.OnlineID); + } + finally + { + host.Exit(); + } + } + } + + [Test] + public void TestUserLookedUpForLegacyOnlineScore() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + { + try + { + var osu = LoadOsuIntoHost(host, true); + + var api = (DummyAPIAccess)osu.API; + api.HandleRequest = req => + { + switch (req) + { + case GetUserRequest userRequest: + userRequest.TriggerSuccess(new APIUser + { + Username = "Test user", + CountryCode = CountryCode.JP, + Id = 1234 + }); + return true; + + default: + return false; + } + }; + + var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely(); + + var toImport = new ScoreInfo + { + Rank = ScoreRank.B, + TotalScore = 987654, + Accuracy = 0.8, + MaxCombo = 500, + Combo = 250, + User = new APIUser { Username = "Test user" }, + Date = DateTimeOffset.Now, + LegacyOnlineID = 12345, + Ruleset = new OsuRuleset().RulesetInfo, + BeatmapInfo = beatmap.Beatmaps.First() + }; + + var imported = LoadScoreIntoOsu(osu, toImport); + + Assert.AreEqual(toImport.Rank, imported.Rank); + Assert.AreEqual(toImport.TotalScore, imported.TotalScore); + Assert.AreEqual(toImport.Accuracy, imported.Accuracy); + Assert.AreEqual(toImport.MaxCombo, imported.MaxCombo); + Assert.AreEqual(toImport.User.Username, imported.User.Username); + Assert.AreEqual(toImport.Date, imported.Date); + Assert.AreEqual(toImport.OnlineID, imported.OnlineID); + Assert.AreEqual(toImport.User.Username, imported.RealmUser.Username); + Assert.AreEqual(1234, imported.RealmUser.OnlineID); + } + finally + { + host.Exit(); + } + } + } + + [Test] + public void TestUserNotLookedUpForOfflineScore() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + { + try + { + var osu = LoadOsuIntoHost(host, true); + + var api = (DummyAPIAccess)osu.API; + api.HandleRequest = req => + { + switch (req) + { + case GetUserRequest userRequest: + userRequest.TriggerSuccess(new APIUser + { + Username = "Test user", + CountryCode = CountryCode.JP, + Id = 1234 + }); + return true; + + default: + return false; + } + }; + + var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely(); + + var toImport = new ScoreInfo + { + Rank = ScoreRank.B, + TotalScore = 987654, + Accuracy = 0.8, + MaxCombo = 500, + Combo = 250, + User = new APIUser { Username = "Test user" }, + Date = DateTimeOffset.Now, + OnlineID = -1, + LegacyOnlineID = -1, + Ruleset = new OsuRuleset().RulesetInfo, + BeatmapInfo = beatmap.Beatmaps.First() + }; + + var imported = LoadScoreIntoOsu(osu, toImport); + + Assert.AreEqual(toImport.Rank, imported.Rank); + Assert.AreEqual(toImport.TotalScore, imported.TotalScore); + Assert.AreEqual(toImport.Accuracy, imported.Accuracy); + Assert.AreEqual(toImport.MaxCombo, imported.MaxCombo); + Assert.AreEqual(toImport.User.Username, imported.User.Username); + Assert.AreEqual(toImport.Date, imported.Date); + Assert.AreEqual(toImport.OnlineID, imported.OnlineID); + Assert.AreEqual(toImport.User.Username, imported.RealmUser.Username); + Assert.That(imported.RealmUser.OnlineID, Is.LessThanOrEqualTo(1)); + } + finally + { + host.Exit(); + } + } + } + public static ScoreInfo LoadScoreIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null) { // clone to avoid attaching the input score to realm. From afb491dff064967f4ccc6fe18f26e94e9322c570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 3 May 2024 13:48:06 +0200 Subject: [PATCH 3820/4092] Do not perform username lookups for scores without an online ID --- osu.Game/Scoring/ScoreImporter.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index 768c28cc38..4ae8e51f6d 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -127,6 +127,9 @@ namespace osu.Game.Scoring if (model.RealmUser.OnlineID == APIUser.SYSTEM_USER_ID) return; + if (model.OnlineID < 0 && model.LegacyOnlineID <= 0) + return; + string username = model.RealmUser.Username; if (usernameLookupCache.TryGetValue(username, out var existing)) From a23d25e0a15c5ac6f3ab8565d3e4b2733501ff81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 3 May 2024 14:27:34 +0200 Subject: [PATCH 3821/4092] Fix `BeatmapAttributeText` breaking due to enum serialisation woes --- osu.Game/Skinning/Components/BeatmapAttributeText.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index 5c5e509fb2..c467b2e946 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -125,6 +125,8 @@ namespace osu.Game.Skinning.Components protected override void SetFont(FontUsage font) => text.Font = font.With(size: 40); } + // WARNING: DO NOT ADD ANY VALUES TO THIS ENUM ANYWHERE ELSE THAN AT THE END. + // Doing so will break existing user skins. public enum BeatmapAttribute { CircleSize, @@ -134,11 +136,11 @@ namespace osu.Game.Skinning.Components StarRating, Title, Artist, - Source, DifficultyName, Creator, Length, RankedStatus, BPM, + Source, } } From e0e7e123bf7d052b074d1cbfbda82e6678a5d3cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Brandst=C3=B6tter?= Date: Fri, 3 May 2024 16:57:31 +0200 Subject: [PATCH 3822/4092] Keep menus open when clicking a stateful item with CTRL held --- .../UserInterface/DrawableStatefulMenuItem.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs b/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs index 5af275c9e7..d63aaf2053 100644 --- a/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs @@ -4,6 +4,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input; using osuTK; namespace osu.Game.Graphics.UserInterface @@ -19,6 +20,17 @@ namespace osu.Game.Graphics.UserInterface protected override TextContainer CreateTextContainer() => new ToggleTextContainer(Item); + private InputManager inputManager = null!; + + public override bool CloseMenuOnClick => !inputManager.CurrentState.Keyboard.ControlPressed; + + protected override void LoadComplete() + { + base.LoadComplete(); + + inputManager = GetContainingInputManager(); + } + private partial class ToggleTextContainer : TextContainer { private readonly StatefulMenuItem menuItem; From 0b61e2cd421d2bd8468fc836c23d93dfd07d429c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Apr 2024 15:52:36 +0800 Subject: [PATCH 3823/4092] Use closest origin along with closest anchor --- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 2 +- .../SkinEditor/SkinSelectionHandler.cs | 35 ++++++++++++------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 690c6b35e3..5bf28ae79b 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -457,7 +457,7 @@ namespace osu.Game.Overlays.SkinEditor } SelectedComponents.Add(component); - SkinSelectionHandler.ApplyClosestAnchor(drawableComponent); + SkinSelectionHandler.ApplyClosestAnchorOrigin(drawableComponent); return true; } diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index cf6fb60636..f41bad4716 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -204,15 +204,20 @@ namespace osu.Game.Overlays.SkinEditor drawable.Position += drawable.ScreenSpaceDeltaToParentSpace(moveEvent.ScreenSpaceDelta); - if (item.UsesFixedAnchor) continue; - - ApplyClosestAnchor(drawable); + if (!item.UsesFixedAnchor) + ApplyClosestAnchorOrigin(drawable); } return true; } - public static void ApplyClosestAnchor(Drawable drawable) => applyAnchor(drawable, getClosestAnchor(drawable)); + public static void ApplyClosestAnchorOrigin(Drawable drawable) + { + var closest = getClosestAnchor(drawable); + + applyAnchor(drawable, closest); + applyOrigin(drawable, closest); + } protected override void OnSelectionChanged() { @@ -325,15 +330,10 @@ namespace osu.Game.Overlays.SkinEditor { var drawable = (Drawable)item; - if (origin == drawable.Origin) continue; + applyOrigin(drawable, origin); - var previousOrigin = drawable.OriginPosition; - drawable.Origin = origin; - drawable.Position += drawable.OriginPosition - previousOrigin; - - if (item.UsesFixedAnchor) continue; - - ApplyClosestAnchor(drawable); + if (item.UsesFixedAnchor) + ApplyClosestAnchorOrigin(drawable); } OnOperationEnded(); @@ -368,7 +368,7 @@ namespace osu.Game.Overlays.SkinEditor foreach (var item in SelectedItems) { item.UsesFixedAnchor = false; - ApplyClosestAnchor((Drawable)item); + ApplyClosestAnchorOrigin((Drawable)item); } OnOperationEnded(); @@ -414,6 +414,15 @@ namespace osu.Game.Overlays.SkinEditor drawable.Position -= drawable.AnchorPosition - previousAnchor; } + private static void applyOrigin(Drawable drawable, Anchor origin) + { + if (origin == drawable.Origin) return; + + var previousOrigin = drawable.OriginPosition; + drawable.Origin = origin; + drawable.Position += drawable.OriginPosition - previousOrigin; + } + private static void adjustScaleFromAnchor(ref Vector2 scale, Anchor reference) { // cancel out scale in axes we don't care about (based on which drag handle was used). From e7ca02ffde25da718116566947f8d7b09e7ba961 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 4 May 2024 13:28:33 +0800 Subject: [PATCH 3824/4092] Fix position changing when origin updates during a drag --- osu.Game/Overlays/SkinEditor/SkinBlueprint.cs | 4 +++- osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs b/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs index 8f8d899fad..6b59d940cc 100644 --- a/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs +++ b/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs @@ -40,7 +40,9 @@ namespace osu.Game.Overlays.SkinEditor public override bool Contains(Vector2 screenSpacePos) => drawableQuad.Contains(screenSpacePos); - public override Vector2 ScreenSpaceSelectionPoint => drawable.ToScreenSpace(drawable.OriginPosition); + public override Vector2 ScreenSpaceSelectionPoint => + // Important to use a stable position (not based on origin) as origin may be automatically updated during drag operations. + drawable.ScreenSpaceDrawQuad.Centre; protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => drawableQuad.Contains(screenSpacePos); diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index f41bad4716..680cc02311 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -202,10 +202,10 @@ namespace osu.Game.Overlays.SkinEditor var item = c.Item; Drawable drawable = (Drawable)item; - drawable.Position += drawable.ScreenSpaceDeltaToParentSpace(moveEvent.ScreenSpaceDelta); - if (!item.UsesFixedAnchor) ApplyClosestAnchorOrigin(drawable); + + drawable.Position += drawable.ScreenSpaceDeltaToParentSpace(moveEvent.ScreenSpaceDelta); } return true; @@ -332,7 +332,7 @@ namespace osu.Game.Overlays.SkinEditor applyOrigin(drawable, origin); - if (item.UsesFixedAnchor) + if (!item.UsesFixedAnchor) ApplyClosestAnchorOrigin(drawable); } From 2cb367fdce2a04181bb71e0c2545e07ae975f6e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 4 May 2024 13:29:05 +0800 Subject: [PATCH 3825/4092] Disable "origin" menu when in "Closest" placement mode --- .../Overlays/SkinEditor/SkinSelectionHandler.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index 680cc02311..8b9e6436b1 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -23,6 +23,8 @@ namespace osu.Game.Overlays.SkinEditor { public partial class SkinSelectionHandler : SelectionHandler { + private OsuMenuItem originMenu = null!; + [Resolved] private SkinEditor skinEditor { get; set; } = null!; @@ -248,10 +250,15 @@ namespace osu.Game.Overlays.SkinEditor .ToArray() }; - yield return new OsuMenuItem("Origin") + yield return originMenu = new OsuMenuItem("Origin"); + + closestItem.State.BindValueChanged(s => { - Items = createAnchorItems((d, o) => ((Drawable)d).Origin == o, applyOrigins).ToArray() - }; + // For UX simplicity, origin should only be user-editable when "closest" anchor mode is disabled. + originMenu.Items = s.NewValue == TernaryState.True + ? Array.Empty() + : createAnchorItems((d, o) => ((Drawable)d).Origin == o, applyOrigins).ToArray(); + }, true); yield return new OsuMenuItemSpacer(); From b35f2c99e64e67b21fea9c272de843c9aa41c51d Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sat, 4 May 2024 18:43:04 +0800 Subject: [PATCH 3826/4092] add failed test --- .../UserInterface/TestSceneModPresetPanel.cs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs index c79cbd3691..d0303b3849 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs @@ -123,6 +123,34 @@ namespace osu.Game.Tests.Visual.UserInterface assertSelectedModsEquivalentTo(new Mod[] { new OsuModTouchDevice(), new OsuModHardRock(), new OsuModDoubleTime { SpeedChange = { Value = 1.5 } } }); } + [Test] + public void TestActivatingPresetWithAutoplayWhenSystemModEnabled() + { + ModPresetPanel? panel = null; + + AddStep("create panel", () => Child = panel = new ModPresetPanel(new ModPreset + { + Name = "Autoplay include", + Description = "no way", + Mods = new Mod[] + { + new OsuModAutoplay() + }, + Ruleset = new OsuRuleset().RulesetInfo + }.ToLiveUnmanaged()) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 0.5f + }); + + AddStep("Add touch device to selected mod", () => SelectedMods.Value = new Mod[] { new OsuModTouchDevice() }); + AddStep("activate panel", () => panel.AsNonNull().TriggerClick()); + + // touch device should be removed due to incompatible with autoplay. + assertSelectedModsEquivalentTo(new Mod[] { new OsuModAutoplay() }); + } + private void assertSelectedModsEquivalentTo(IEnumerable mods) => AddAssert("selected mods changed correctly", () => new HashSet(SelectedMods.Value).SetEquals(mods)); From f9be9ed479555a355be8bc3673540ce312fbf39e Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sat, 4 May 2024 18:44:47 +0800 Subject: [PATCH 3827/4092] remove incompatible system mods before enable preset --- osu.Game/Overlays/Mods/ModPresetPanel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModPresetPanel.cs b/osu.Game/Overlays/Mods/ModPresetPanel.cs index 3982abeba7..ca7e64957f 100644 --- a/osu.Game/Overlays/Mods/ModPresetPanel.cs +++ b/osu.Game/Overlays/Mods/ModPresetPanel.cs @@ -55,7 +55,8 @@ namespace osu.Game.Overlays.Mods protected override void Select() { - var selectedSystemMods = selectedMods.Value.Where(mod => mod.Type == ModType.System); + var selectedSystemMods = selectedMods.Value.Where(mod => mod.Type == ModType.System + && !mod.IncompatibleMods.Any(t => Preset.Value.Mods.Any(m => m.GetType() == t))); // will also have the side effect of activating the preset (see `updateActiveState()`). selectedMods.Value = Preset.Value.Mods.Concat(selectedSystemMods).ToArray(); } From 6af30a3d45ef7d4a971082be060407778c148a4b Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sat, 4 May 2024 20:02:35 +0800 Subject: [PATCH 3828/4092] add test for non-td system mod --- .../Visual/UserInterface/TestSceneModPresetPanel.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs index d0303b3849..9a141e0df0 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs @@ -149,6 +149,15 @@ namespace osu.Game.Tests.Visual.UserInterface // touch device should be removed due to incompatible with autoplay. assertSelectedModsEquivalentTo(new Mod[] { new OsuModAutoplay() }); + + AddStep("deactivate panel", () => panel.AsNonNull().TriggerClick()); + assertSelectedModsEquivalentTo(Array.Empty()); + + // just for test purpose + AddStep("Add score v2 to selected mod", () => SelectedMods.Value = new Mod[] { new ModScoreV2() }); + AddStep("activate panel", () => panel.AsNonNull().TriggerClick()); + + assertSelectedModsEquivalentTo(new Mod[] { new OsuModAutoplay(), new ModScoreV2() }); } private void assertSelectedModsEquivalentTo(IEnumerable mods) From fe30ca3d397b774eb8ab8eafe576a5d42b383b83 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sat, 4 May 2024 20:11:59 +0800 Subject: [PATCH 3829/4092] fix linq logic --- osu.Game/Overlays/Mods/ModPresetPanel.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModPresetPanel.cs b/osu.Game/Overlays/Mods/ModPresetPanel.cs index ca7e64957f..9f5dda4a75 100644 --- a/osu.Game/Overlays/Mods/ModPresetPanel.cs +++ b/osu.Game/Overlays/Mods/ModPresetPanel.cs @@ -55,8 +55,10 @@ namespace osu.Game.Overlays.Mods protected override void Select() { - var selectedSystemMods = selectedMods.Value.Where(mod => mod.Type == ModType.System - && !mod.IncompatibleMods.Any(t => Preset.Value.Mods.Any(m => m.GetType() == t))); + var selectedSystemMods = selectedMods.Value.Where(mod => mod.Type == ModType.System && + !mod.IncompatibleMods.Any(t => Preset.Value.Mods.Any(t.IsInstanceOfType))); + + // will also have the side effect of activating the preset (see `updateActiveState()`). selectedMods.Value = Preset.Value.Mods.Concat(selectedSystemMods).ToArray(); } From 1f92f1d19b15a652e580f0531b0a70f7a5788c94 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sat, 4 May 2024 20:41:36 +0800 Subject: [PATCH 3830/4092] remove blank line nt --- osu.Game/Overlays/Mods/ModPresetPanel.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModPresetPanel.cs b/osu.Game/Overlays/Mods/ModPresetPanel.cs index 9f5dda4a75..450c684e54 100644 --- a/osu.Game/Overlays/Mods/ModPresetPanel.cs +++ b/osu.Game/Overlays/Mods/ModPresetPanel.cs @@ -58,7 +58,6 @@ namespace osu.Game.Overlays.Mods var selectedSystemMods = selectedMods.Value.Where(mod => mod.Type == ModType.System && !mod.IncompatibleMods.Any(t => Preset.Value.Mods.Any(t.IsInstanceOfType))); - // will also have the side effect of activating the preset (see `updateActiveState()`). selectedMods.Value = Preset.Value.Mods.Concat(selectedSystemMods).ToArray(); } From 21917218ce17a2f3422b1e34e4d2ea42de0cb52d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Brandst=C3=B6tter?= Date: Sat, 4 May 2024 16:00:22 +0200 Subject: [PATCH 3831/4092] No longer keep menu open when CTRL is held --- .../Graphics/UserInterface/DrawableStatefulMenuItem.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs b/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs index d63aaf2053..000a2f9f91 100644 --- a/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs @@ -2,9 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; -using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input; +using osu.Framework.Graphics; using osuTK; namespace osu.Game.Graphics.UserInterface @@ -20,15 +19,8 @@ namespace osu.Game.Graphics.UserInterface protected override TextContainer CreateTextContainer() => new ToggleTextContainer(Item); - private InputManager inputManager = null!; - - public override bool CloseMenuOnClick => !inputManager.CurrentState.Keyboard.ControlPressed; - - protected override void LoadComplete() { - base.LoadComplete(); - inputManager = GetContainingInputManager(); } private partial class ToggleTextContainer : TextContainer From c62952ea3afd2d81ba3f703491525c5249b79cac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Brandst=C3=B6tter?= Date: Sat, 4 May 2024 16:01:31 +0200 Subject: [PATCH 3832/4092] Invoke the registered Action when a stateful item is right clicked --- .../Graphics/UserInterface/DrawableStatefulMenuItem.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs b/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs index 000a2f9f91..0c4e575621 100644 --- a/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs @@ -4,6 +4,8 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics; +using osu.Framework.Input.Events; +using osuTK.Input; using osuTK; namespace osu.Game.Graphics.UserInterface @@ -19,8 +21,16 @@ namespace osu.Game.Graphics.UserInterface protected override TextContainer CreateTextContainer() => new ToggleTextContainer(Item); + protected override bool OnMouseDown(MouseDownEvent e) { + if (!IsActionable) + return true; + if (e.Button != MouseButton.Right) + return true; + + Item.Action.Value?.Invoke(); + return true; } private partial class ToggleTextContainer : TextContainer From 78d6f24fcae2e11c1c5fe7ba148fe4dd15f4a3b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Brandst=C3=B6tter?= Date: Sat, 4 May 2024 16:25:19 +0200 Subject: [PATCH 3833/4092] Add Test --- .../TestSceneStatefulMenuItem.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs index 88187f1808..63497040db 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs @@ -114,6 +114,51 @@ namespace osu.Game.Tests.Visual.UserInterface => AddAssert($"state is {expected}", () => state.Value == expected); } + [Test] + public void TestItemRespondsToRightClick() + { + OsuMenu menu = null; + + Bindable state = new Bindable(TernaryState.Indeterminate); + + AddStep("create menu", () => + { + state.Value = TernaryState.Indeterminate; + + Child = menu = new OsuMenu(Direction.Vertical, true) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Items = new[] + { + new TernaryStateToggleMenuItem("First"), + new TernaryStateToggleMenuItem("Second") { State = { BindTarget = state } }, + new TernaryStateToggleMenuItem("Third") { State = { Value = TernaryState.True } }, + } + }; + }); + + checkState(TernaryState.Indeterminate); + + click(); + checkState(TernaryState.True); + + click(); + checkState(TernaryState.False); + + AddStep("change state via bindable", () => state.Value = TernaryState.True); + + void click() => + AddStep("click", () => + { + InputManager.MoveMouseTo(menu.ScreenSpaceDrawQuad.Centre); + InputManager.Click(MouseButton.Right); + }); + + void checkState(TernaryState expected) + => AddAssert($"state is {expected}", () => state.Value == expected); + } + [Test] public void TestCustomState() { From b1696db9c87d59fd047aa3b94c2b34577e68c593 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Brandst=C3=B6tter?= Date: Sat, 4 May 2024 16:32:18 +0200 Subject: [PATCH 3834/4092] Reorder imports with `dotnet format` --- osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs b/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs index 0c4e575621..8ed52593a7 100644 --- a/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs @@ -2,11 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; -using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; -using osuTK.Input; using osuTK; +using osuTK.Input; namespace osu.Game.Graphics.UserInterface { From cf313cd67f3c1a89b77edd90c94a73a5794e31d7 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 4 May 2024 21:53:48 +0300 Subject: [PATCH 3835/4092] Use single path to display slider control point connections --- .../TestScenePathControlPointVisualiser.cs | 35 ---------------- ...Piece.cs => PathControlPointConnection.cs} | 42 +++++-------------- .../Components/PathControlPointVisualiser.cs | 29 +------------ 3 files changed, 11 insertions(+), 95 deletions(-) rename osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/{PathControlPointConnectionPiece.cs => PathControlPointConnection.cs} (51%) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs index 0ca30e00bc..9af028fd8c 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs @@ -30,23 +30,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); }); - [Test] - public void TestAddOverlappingControlPoints() - { - createVisualiser(true); - - addControlPointStep(new Vector2(200)); - addControlPointStep(new Vector2(300)); - addControlPointStep(new Vector2(300)); - addControlPointStep(new Vector2(500, 300)); - - AddAssert("last connection displayed", () => - { - var lastConnection = visualiser.Connections.Last(c => c.ControlPoint.Position == new Vector2(300)); - return lastConnection.DrawWidth > 50; - }); - } - [Test] public void TestPerfectCurveTooManyPoints() { @@ -194,24 +177,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor addAssertPointPositionChanged(points, i); } - [Test] - public void TestStackingUpdatesConnectionPosition() - { - createVisualiser(true); - - Vector2 connectionPosition; - addControlPointStep(connectionPosition = new Vector2(300)); - addControlPointStep(new Vector2(600)); - - // Apply a big number in stacking so the person running the test can clearly see if it fails - AddStep("apply stacking", () => slider.StackHeightBindable.Value += 10); - - AddAssert($"Connection at {connectionPosition} changed", - () => visualiser.Connections[0].Position, - () => !Is.EqualTo(connectionPosition) - ); - } - private void addAssertPointPositionChanged(Vector2[] points, int index) { AddAssert($"Point at {points.ElementAt(index)} changed", diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnection.cs similarity index 51% rename from osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs rename to osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnection.cs index 9b3d8fc7a7..5706ed4baf 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnection.cs @@ -4,10 +4,7 @@ #nullable disable using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Lines; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osuTK; @@ -15,36 +12,21 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { /// - /// A visualisation of the line between two s. + /// A visualisation of the lines between s. /// - /// The type of which this visualises. - public partial class PathControlPointConnectionPiece : CompositeDrawable where T : OsuHitObject, IHasPath + /// The type of which this visualises. + public partial class PathControlPointConnection : SmoothPath where T : OsuHitObject, IHasPath { - public readonly PathControlPoint ControlPoint; - - private readonly Path path; private readonly T hitObject; - public int ControlPointIndex { get; set; } private IBindable hitObjectPosition; private IBindable pathVersion; private IBindable stackHeight; - public PathControlPointConnectionPiece(T hitObject, int controlPointIndex) + public PathControlPointConnection(T hitObject) { this.hitObject = hitObject; - ControlPointIndex = controlPointIndex; - - Origin = Anchor.Centre; - AutoSizeAxes = Axes.Both; - - ControlPoint = hitObject.Path.ControlPoints[controlPointIndex]; - - InternalChild = path = new SmoothPath - { - Anchor = Anchor.Centre, - PathRadius = 1 - }; + PathRadius = 1; } protected override void LoadComplete() @@ -68,18 +50,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components /// private void updateConnectingPath() { - Position = hitObject.StackedPosition + ControlPoint.Position; + Position = hitObject.StackedPosition; - path.ClearVertices(); + ClearVertices(); - int nextIndex = ControlPointIndex + 1; - if (nextIndex == 0 || nextIndex >= hitObject.Path.ControlPoints.Count) - return; + foreach (var controlPoint in hitObject.Path.ControlPoints) + AddVertex(controlPoint.Position); - path.AddVertex(Vector2.Zero); - path.AddVertex(hitObject.Path.ControlPoints[nextIndex].Position - ControlPoint.Position); - - path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero); + OriginPosition = PositionInBoundingBox(Vector2.Zero); } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index b2d1709531..7212de322d 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -37,7 +37,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // allow context menu to appear outside of the playfield. internal readonly Container> Pieces; - internal readonly Container> Connections; private readonly IBindableList controlPoints = new BindableList(); private readonly T hitObject; @@ -63,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components InternalChildren = new Drawable[] { - Connections = new Container> { RelativeSizeAxes = Axes.Both }, + new PathControlPointConnection(hitObject), Pieces = new Container> { RelativeSizeAxes = Axes.Both } }; } @@ -185,17 +184,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components case NotifyCollectionChangedAction.Add: Debug.Assert(e.NewItems != null); - // If inserting in the path (not appending), - // update indices of existing connections after insert location - if (e.NewStartingIndex < Pieces.Count) - { - foreach (var connection in Connections) - { - if (connection.ControlPointIndex >= e.NewStartingIndex) - connection.ControlPointIndex += e.NewItems.Count; - } - } - for (int i = 0; i < e.NewItems.Count; i++) { var point = (PathControlPoint)e.NewItems[i]; @@ -209,8 +197,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components d.DragInProgress = DragInProgress; d.DragEnded = DragEnded; })); - - Connections.Add(new PathControlPointConnectionPiece(hitObject, e.NewStartingIndex + i)); } break; @@ -222,19 +208,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { foreach (var piece in Pieces.Where(p => p.ControlPoint == point).ToArray()) piece.RemoveAndDisposeImmediately(); - foreach (var connection in Connections.Where(c => c.ControlPoint == point).ToArray()) - connection.RemoveAndDisposeImmediately(); - } - - // If removing before the end of the path, - // update indices of connections after remove location - if (e.OldStartingIndex < Pieces.Count) - { - foreach (var connection in Connections) - { - if (connection.ControlPointIndex >= e.OldStartingIndex) - connection.ControlPointIndex -= e.OldItems.Count; - } } break; From e319a3e885e9ae7d545c70a274ecad87220f62c7 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 4 May 2024 22:07:08 +0300 Subject: [PATCH 3836/4092] Don't perform masking updates in PathControlPointVisualiser --- .../Sliders/Components/PathControlPointVisualiser.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 7212de322d..836d348ff4 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -77,6 +77,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components controlPoints.BindTo(hitObject.Path.ControlPoints); } + // Generally all the control points are within the visible area all the time. + public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) => true; + /// /// Handles correction of invalid path types. /// From 9e7712740b2d881c14bb8f0872e124d95436fe0a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 5 May 2024 23:32:24 +0800 Subject: [PATCH 3837/4092] Refactor for legibility --- .../Graphics/UserInterface/DrawableStatefulMenuItem.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs b/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs index 8ed52593a7..686c490930 100644 --- a/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs @@ -23,13 +23,11 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnMouseDown(MouseDownEvent e) { - if (!IsActionable) - return true; + // Right mouse button is a special case where we allow actioning without dismissing the menu. + // This is achieved by not calling `Clicked` (as done by the base implementation in OnClick). + if (IsActionable && e.Button == MouseButton.Right) + Item.Action.Value?.Invoke(); - if (e.Button != MouseButton.Right) - return true; - - Item.Action.Value?.Invoke(); return true; } From 848e497c94d1d0a440cc74b05dfa441f19dfb40b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 5 May 2024 21:32:25 +0300 Subject: [PATCH 3838/4092] Add "HP" to the abbreviations list --- osu.sln.DotSettings | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 51af281ac6..08eb264aab 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -347,6 +347,7 @@ GL GLSL HID + HP HSL HSPA HSV From 1665c5e0e1e3e3434cd98c0f0075362b0e7ea5a5 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 5 May 2024 14:41:48 -0700 Subject: [PATCH 3839/4092] Use existing `AutomaticallyDownloadMissingBeatmaps` localisation on solo spectator screen --- osu.Game/Screens/Play/SoloSpectatorScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SoloSpectatorScreen.cs b/osu.Game/Screens/Play/SoloSpectatorScreen.cs index 2db751402c..95eb2d4376 100644 --- a/osu.Game/Screens/Play/SoloSpectatorScreen.cs +++ b/osu.Game/Screens/Play/SoloSpectatorScreen.cs @@ -16,6 +16,7 @@ using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Localisation; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; @@ -138,7 +139,7 @@ namespace osu.Game.Screens.Play }, automaticDownload = new SettingsCheckbox { - LabelText = "Automatically download beatmaps", + LabelText = OnlineSettingsStrings.AutomaticallyDownloadMissingBeatmaps, Current = config.GetBindable(OsuSetting.AutomaticallyDownloadMissingBeatmaps), Anchor = Anchor.Centre, Origin = Anchor.Centre, From eb92e8de370d5fff1407c64527b3760d6b63ac82 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 5 May 2024 15:15:14 -0700 Subject: [PATCH 3840/4092] Edit title/artist unicode values and add unicode toggle in test --- .../UserInterface/TestSceneNowPlayingOverlay.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs index 1670741cbd..d84089fb6f 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Overlays; @@ -21,8 +22,10 @@ namespace osu.Game.Tests.Visual.UserInterface private NowPlayingOverlay nowPlayingOverlay; [BackgroundDependencyLoader] - private void load() + private void load(FrameworkConfigManager frameworkConfig) { + AddToggleStep("toggle unicode", v => frameworkConfig.SetValue(FrameworkSetting.ShowUnicode, v)); + nowPlayingOverlay = new NowPlayingOverlay { Origin = Anchor.Centre, @@ -50,9 +53,9 @@ namespace osu.Game.Tests.Visual.UserInterface Metadata = { Artist = "very very very very very very very very very very verry long artist", - ArtistUnicode = "very very very very very very very very very very verry long artist", + ArtistUnicode = "very very very very very very very very very very verry long artist unicode", Title = "very very very very very verry long title", - TitleUnicode = "very very very very very verry long title", + TitleUnicode = "very very very very very verry long title unicode", } })); @@ -61,9 +64,9 @@ namespace osu.Game.Tests.Visual.UserInterface Metadata = { Artist = "very very very very very very very very very very verrry long artist", - ArtistUnicode = "very very very very very very very very very very verrry long artist", + ArtistUnicode = "not very long artist unicode", Title = "very very very very very verrry long title", - TitleUnicode = "very very very very very verrry long title", + TitleUnicode = "not very long title unicode", } })); From 359238395f273686b611ac87f5ae1ceef1072a04 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 5 May 2024 15:17:03 -0700 Subject: [PATCH 3841/4092] Fix now playing overlay text scroll breaking when toggling metadata language setting --- osu.Game/Overlays/NowPlayingOverlay.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 29052ace8e..76c8c237d5 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -5,6 +5,7 @@ using System; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Configuration; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; @@ -479,6 +480,11 @@ namespace osu.Game.Overlays private OsuSpriteText mainSpriteText = null!; private OsuSpriteText fillerSpriteText = null!; + private Bindable showUnicode = null!; + + [Resolved] + private FrameworkConfigManager frameworkConfig { get; set; } = null!; + private LocalisableString text; public LocalisableString Text @@ -531,6 +537,9 @@ namespace osu.Game.Overlays { base.LoadComplete(); + showUnicode = frameworkConfig.GetBindable(FrameworkSetting.ShowUnicode); + showUnicode.BindValueChanged(_ => updateText()); + updateFontAndText(); } From 12cd3bbe1c79d7f2134ed79f03cb1e8005086d99 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 May 2024 11:34:37 +0800 Subject: [PATCH 3842/4092] Fix incorrect scale handling due to selection point changes --- osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index 8b9e6436b1..75bb77fa73 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -139,7 +139,7 @@ namespace osu.Game.Overlays.SkinEditor var drawableItem = (Drawable)b.Item; // each drawable's relative position should be maintained in the scaled quad. - var screenPosition = b.ScreenSpaceSelectionPoint; + var screenPosition = drawableItem.ToScreenSpace(drawableItem.OriginPosition); var relativePositionInOriginal = new Vector2( From 4c7e6b125cdc889b12039b8b4048ab09afc963b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 6 May 2024 08:49:30 +0200 Subject: [PATCH 3843/4092] Add clarification comment --- osu.Game/Overlays/Mods/ModPresetPanel.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/Mods/ModPresetPanel.cs b/osu.Game/Overlays/Mods/ModPresetPanel.cs index 450c684e54..568ca5ecc9 100644 --- a/osu.Game/Overlays/Mods/ModPresetPanel.cs +++ b/osu.Game/Overlays/Mods/ModPresetPanel.cs @@ -55,6 +55,9 @@ namespace osu.Game.Overlays.Mods protected override void Select() { + // this implicitly presumes that if a system mod declares incompatibility with a non-system mod, + // the non-system mod should take precedence. + // if this assumption is ever broken, this should be reconsidered. var selectedSystemMods = selectedMods.Value.Where(mod => mod.Type == ModType.System && !mod.IncompatibleMods.Any(t => Preset.Value.Mods.Any(t.IsInstanceOfType))); From cb4af794161d325143e4ba4421774f746137b51f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 6 May 2024 08:53:41 +0200 Subject: [PATCH 3844/4092] Touch up test case --- .../UserInterface/TestSceneModPresetPanel.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs index 9a141e0df0..f87d8e0d2b 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs @@ -124,17 +124,17 @@ namespace osu.Game.Tests.Visual.UserInterface } [Test] - public void TestActivatingPresetWithAutoplayWhenSystemModEnabled() + public void TestSystemModsNotPreservedIfIncompatibleWithPresetMods() { ModPresetPanel? panel = null; AddStep("create panel", () => Child = panel = new ModPresetPanel(new ModPreset { - Name = "Autoplay include", + Name = "Autopilot included", Description = "no way", Mods = new Mod[] { - new OsuModAutoplay() + new OsuModAutopilot() }, Ruleset = new OsuRuleset().RulesetInfo }.ToLiveUnmanaged()) @@ -144,20 +144,20 @@ namespace osu.Game.Tests.Visual.UserInterface Width = 0.5f }); - AddStep("Add touch device to selected mod", () => SelectedMods.Value = new Mod[] { new OsuModTouchDevice() }); + AddStep("Add touch device to selected mods", () => SelectedMods.Value = new Mod[] { new OsuModTouchDevice() }); AddStep("activate panel", () => panel.AsNonNull().TriggerClick()); - // touch device should be removed due to incompatible with autoplay. - assertSelectedModsEquivalentTo(new Mod[] { new OsuModAutoplay() }); + // touch device should be removed due to incompatibility with autopilot. + assertSelectedModsEquivalentTo(new Mod[] { new OsuModAutopilot() }); AddStep("deactivate panel", () => panel.AsNonNull().TriggerClick()); assertSelectedModsEquivalentTo(Array.Empty()); - // just for test purpose + // just for test purposes, can't/shouldn't happen in reality AddStep("Add score v2 to selected mod", () => SelectedMods.Value = new Mod[] { new ModScoreV2() }); AddStep("activate panel", () => panel.AsNonNull().TriggerClick()); - assertSelectedModsEquivalentTo(new Mod[] { new OsuModAutoplay(), new ModScoreV2() }); + assertSelectedModsEquivalentTo(new Mod[] { new OsuModAutopilot(), new ModScoreV2() }); } private void assertSelectedModsEquivalentTo(IEnumerable mods) From cf87e9cd40008cedba6f0e183f5c07bf0978643f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 6 May 2024 11:22:56 +0200 Subject: [PATCH 3845/4092] Do not show integration settings on mobile Closes https://github.com/ppy/osu/issues/28097. The settings weren't actually doing anything at all there anyway. --- osu.Game/Overlays/Settings/Sections/OnlineSection.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs index 1484f2c756..6593eb69fa 100644 --- a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs +++ b/osu.Game/Overlays/Settings/Sections/OnlineSection.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.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; @@ -25,8 +26,10 @@ namespace osu.Game.Overlays.Settings.Sections { new WebSettings(), new AlertsAndPrivacySettings(), - new IntegrationSettings() }; + + if (RuntimeInfo.IsDesktop) + Add(new IntegrationSettings()); } } } From f066026503b4f50377fe3eca5ee55f1261ff00f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 6 May 2024 11:55:49 +0200 Subject: [PATCH 3846/4092] Fix sizing of gameplay preview in skin editor not updating on scaling mode change Closes https://github.com/ppy/osu/issues/28115. --- osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs index 2f4820e207..748e9c6c0b 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Primitives; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Layout; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -30,7 +31,6 @@ using osu.Game.Screens.Play; using osu.Game.Screens.Select; using osu.Game.Users; using osu.Game.Utils; -using osuTK; namespace osu.Game.Overlays.SkinEditor { @@ -70,12 +70,14 @@ namespace osu.Game.Overlays.SkinEditor private OsuScreen? lastTargetScreen; private InvokeOnDisposal? nestedInputManagerDisable; - private Vector2 lastDrawSize; + private LayoutValue drawSizeLayout; public SkinEditorOverlay(ScalingContainer scalingContainer) { this.scalingContainer = scalingContainer; RelativeSizeAxes = Axes.Both; + + AddLayout(drawSizeLayout = new LayoutValue(Invalidation.DrawSize)); } [BackgroundDependencyLoader] @@ -199,10 +201,10 @@ namespace osu.Game.Overlays.SkinEditor { base.Update(); - if (game.DrawSize != lastDrawSize) + if (!drawSizeLayout.IsValid) { - lastDrawSize = game.DrawSize; updateScreenSizing(); + drawSizeLayout.Validate(); } } From 353a07f7a5fa4a24c3d91b545defda6f8128efe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 6 May 2024 12:23:11 +0200 Subject: [PATCH 3847/4092] Fix code quality inspection --- osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs index 748e9c6c0b..571f99bd08 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs @@ -70,7 +70,7 @@ namespace osu.Game.Overlays.SkinEditor private OsuScreen? lastTargetScreen; private InvokeOnDisposal? nestedInputManagerDisable; - private LayoutValue drawSizeLayout; + private readonly LayoutValue drawSizeLayout; public SkinEditorOverlay(ScalingContainer scalingContainer) { From 4d9ccdc3b2391463355d297387c12181f72a6fc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 3 May 2024 13:23:41 +0200 Subject: [PATCH 3848/4092] Encode user ID to replays --- osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs b/osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs index 32c18b3af2..b71d9d916e 100644 --- a/osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs +++ b/osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs @@ -43,6 +43,9 @@ namespace osu.Game.Scoring.Legacy [JsonConverter(typeof(StringEnumConverter))] public ScoreRank? Rank; + [JsonProperty("user_id")] + public int UserID = -1; + public static LegacyReplaySoloScoreInfo FromScore(ScoreInfo score) => new LegacyReplaySoloScoreInfo { OnlineID = score.OnlineID, @@ -51,6 +54,7 @@ namespace osu.Game.Scoring.Legacy MaximumStatistics = score.MaximumStatistics.Where(kvp => kvp.Value != 0).ToDictionary(), ClientVersion = score.ClientVersion, Rank = score.Rank, + UserID = score.UserID, }; } } From 554ead0d9dd596a01bf55ab36c72f8eb346f81bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 3 May 2024 13:34:02 +0200 Subject: [PATCH 3849/4092] Decode user ID from score if available --- .../Beatmaps/Formats/LegacyScoreDecoderTest.cs | 9 +++++++++ osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 2 ++ 2 files changed, 11 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index 383c08c10f..cc7b37e6a8 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -14,6 +14,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Legacy; using osu.Game.IO.Legacy; +using osu.Game.Models; using osu.Game.Replays; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; @@ -31,6 +32,7 @@ using osu.Game.Rulesets.Taiko; using osu.Game.Scoring; using osu.Game.Scoring.Legacy; using osu.Game.Tests.Resources; +using osu.Game.Users; namespace osu.Game.Tests.Beatmaps.Formats { @@ -224,6 +226,12 @@ namespace osu.Game.Tests.Beatmaps.Formats new OsuModDoubleTime { SpeedChange = { Value = 1.1 } } }; scoreInfo.OnlineID = 123123; + scoreInfo.RealmUser = new RealmUser + { + Username = "spaceman_atlas", + OnlineID = 3035836, + CountryCode = CountryCode.PL + }; scoreInfo.ClientVersion = "2023.1221.0"; var beatmap = new TestBeatmap(ruleset); @@ -248,6 +256,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(decodedAfterEncode.ScoreInfo.MaximumStatistics, Is.EqualTo(scoreInfo.MaximumStatistics)); Assert.That(decodedAfterEncode.ScoreInfo.Mods, Is.EqualTo(scoreInfo.Mods)); Assert.That(decodedAfterEncode.ScoreInfo.ClientVersion, Is.EqualTo("2023.1221.0")); + Assert.That(decodedAfterEncode.ScoreInfo.RealmUser.OnlineID, Is.EqualTo(3035836)); }); } diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index b0d7087ed1..00e294fdcd 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -131,6 +131,8 @@ namespace osu.Game.Scoring.Legacy score.ScoreInfo.Mods = readScore.Mods.Select(m => m.ToMod(currentRuleset)).ToArray(); score.ScoreInfo.ClientVersion = readScore.ClientVersion; decodedRank = readScore.Rank; + if (readScore.UserID > 1) + score.ScoreInfo.RealmUser.OnlineID = readScore.UserID; }); } } From bd869b6cdcd41c3406022fb784f54fe2fdebf849 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 6 May 2024 13:24:24 +0200 Subject: [PATCH 3850/4092] Add failing tests for looking up users by online ID if present when importing scores --- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 82 ++++++++++++++++++++- 1 file changed, 79 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index eb2c098ab8..9c72804a6b 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -287,7 +287,7 @@ namespace osu.Game.Tests.Scores.IO } [Test] - public void TestUserLookedUpForOnlineScore() + public void TestUserLookedUpByUsernameForOnlineScoreIfUserIDMissing() { using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { @@ -301,6 +301,9 @@ namespace osu.Game.Tests.Scores.IO switch (req) { case GetUserRequest userRequest: + if (userRequest.Lookup != "Test user") + return false; + userRequest.TriggerSuccess(new APIUser { Username = "Test user", @@ -350,7 +353,7 @@ namespace osu.Game.Tests.Scores.IO } [Test] - public void TestUserLookedUpForLegacyOnlineScore() + public void TestUserLookedUpByUsernameForLegacyOnlineScore() { using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { @@ -364,6 +367,9 @@ namespace osu.Game.Tests.Scores.IO switch (req) { case GetUserRequest userRequest: + if (userRequest.Lookup != "Test user") + return false; + userRequest.TriggerSuccess(new APIUser { Username = "Test user", @@ -413,7 +419,7 @@ namespace osu.Game.Tests.Scores.IO } [Test] - public void TestUserNotLookedUpForOfflineScore() + public void TestUserNotLookedUpForOfflineScoreIfUserIDMissing() { using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { @@ -427,6 +433,9 @@ namespace osu.Game.Tests.Scores.IO switch (req) { case GetUserRequest userRequest: + if (userRequest.Lookup != "Test user") + return false; + userRequest.TriggerSuccess(new APIUser { Username = "Test user", @@ -476,6 +485,73 @@ namespace osu.Game.Tests.Scores.IO } } + [Test] + public void TestUserLookedUpByOnlineIDIfPresent([Values] bool isOnlineScore) + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + { + try + { + var osu = LoadOsuIntoHost(host, true); + + var api = (DummyAPIAccess)osu.API; + api.HandleRequest = req => + { + switch (req) + { + case GetUserRequest userRequest: + if (userRequest.Lookup != "5555") + return false; + + userRequest.TriggerSuccess(new APIUser + { + Username = "Some other guy", + CountryCode = CountryCode.DE, + Id = 5555 + }); + return true; + + default: + return false; + } + }; + + var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely(); + + var toImport = new ScoreInfo + { + Rank = ScoreRank.B, + TotalScore = 987654, + Accuracy = 0.8, + MaxCombo = 500, + Combo = 250, + User = new APIUser { Id = 5555 }, + Date = DateTimeOffset.Now, + Ruleset = new OsuRuleset().RulesetInfo, + BeatmapInfo = beatmap.Beatmaps.First() + }; + if (isOnlineScore) + toImport.OnlineID = 12345; + + var imported = LoadScoreIntoOsu(osu, toImport); + + Assert.AreEqual(toImport.Rank, imported.Rank); + Assert.AreEqual(toImport.TotalScore, imported.TotalScore); + Assert.AreEqual(toImport.Accuracy, imported.Accuracy); + Assert.AreEqual(toImport.MaxCombo, imported.MaxCombo); + Assert.AreEqual(toImport.Date, imported.Date); + Assert.AreEqual(toImport.OnlineID, imported.OnlineID); + Assert.AreEqual("Some other guy", imported.RealmUser.Username); + Assert.AreEqual(5555, imported.RealmUser.OnlineID); + Assert.AreEqual(CountryCode.DE, imported.RealmUser.CountryCode); + } + finally + { + host.Exit(); + } + } + } + public static ScoreInfo LoadScoreIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null) { // clone to avoid attaching the input score to realm. From abfb2c00bcaaf071b264f82e5205350ec7a5edf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 6 May 2024 13:24:34 +0200 Subject: [PATCH 3851/4092] Look up users by ID if available when importing scores --- osu.Game/Scoring/ScoreImporter.cs | 71 ++++++++++++++++++++++++++----- 1 file changed, 61 insertions(+), 10 deletions(-) diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index 4ae8e51f6d..69c53af16f 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -103,6 +103,14 @@ namespace osu.Game.Scoring } // Very naive local caching to improve performance of large score imports (where the username is usually the same for most or all scores). + + // TODO: `UserLookupCache` cannot currently be used here because of async foibles. + // It only supports lookups by user ID (username would require web changes), and even then the ID lookups cannot be used. + // That is because that component provides an async interface, and async functions cannot be consumed safely here due to the rigid structure of `RealmArchiveModelImporter`. + // The importer has two paths, one async and one sync; the async path runs the sync path in a task. + // This means that sometimes `PostImport()` is called from a sync context, and sometimes from an async one, whilst itself being a sync method. + // That in turn makes `.GetResultSafely()` not callable inside `PostImport()`, as it will throw when called from an async context, + private readonly Dictionary idLookupCache = new Dictionary(); private readonly Dictionary usernameLookupCache = new Dictionary(); protected override void PostImport(ScoreInfo model, Realm realm, ImportParameters parameters) @@ -127,24 +135,34 @@ namespace osu.Game.Scoring if (model.RealmUser.OnlineID == APIUser.SYSTEM_USER_ID) return; - if (model.OnlineID < 0 && model.LegacyOnlineID <= 0) - return; - - string username = model.RealmUser.Username; - - if (usernameLookupCache.TryGetValue(username, out var existing)) + if (model.RealmUser.OnlineID > 1) { - model.User = existing; + model.User = lookupUserById(model.RealmUser.OnlineID) ?? model.User; return; } - var userRequest = new GetUserRequest(username); + if (model.OnlineID < 0 && model.LegacyOnlineID <= 0) + return; + + model.User = lookupUserByName(model.RealmUser.Username) ?? model.User; + } + + private APIUser? lookupUserById(int id) + { + if (idLookupCache.TryGetValue(id, out var existing)) + { + return existing; + } + + var userRequest = new GetUserRequest(id); api.Perform(userRequest); if (userRequest.Response is APIUser user) { - usernameLookupCache.TryAdd(username, new APIUser + APIUser cachedUser; + + idLookupCache.TryAdd(id, cachedUser = new APIUser { // Because this is a permanent cache, let's only store the pieces we're interested in, // rather than the full API response. If we start to store more than these three fields @@ -154,8 +172,41 @@ namespace osu.Game.Scoring CountryCode = user.CountryCode, }); - model.User = user; + return cachedUser; } + + return null; + } + + private APIUser? lookupUserByName(string username) + { + if (usernameLookupCache.TryGetValue(username, out var existing)) + { + return existing; + } + + var userRequest = new GetUserRequest(username); + + api.Perform(userRequest); + + if (userRequest.Response is APIUser user) + { + APIUser cachedUser; + + usernameLookupCache.TryAdd(username, cachedUser = new APIUser + { + // Because this is a permanent cache, let's only store the pieces we're interested in, + // rather than the full API response. If we start to store more than these three fields + // in realm, this should be undone. + Id = user.Id, + Username = user.Username, + CountryCode = user.CountryCode, + }); + + return cachedUser; + } + + return null; } } } From 9c6968c13a2c6a1a0392649dddd1c0cfb7828f1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 6 May 2024 16:29:03 +0200 Subject: [PATCH 3852/4092] Use `score.User.OnlineID` instead of `score.UserID` You'd hope that they'd be the same thing, but post-https://github.com/ppy/osu-server-spectator/pull/230 it turns out that cannot be guaranteed, so just attempt to use `User` in the encoder consistently everywhere... --- osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs b/osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs index b71d9d916e..60bec687f4 100644 --- a/osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs +++ b/osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs @@ -54,7 +54,7 @@ namespace osu.Game.Scoring.Legacy MaximumStatistics = score.MaximumStatistics.Where(kvp => kvp.Value != 0).ToDictionary(), ClientVersion = score.ClientVersion, Rank = score.Rank, - UserID = score.UserID, + UserID = score.User.OnlineID, }; } } From a694f4625309667534c4a76e794ce0728c4b5b7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Wolfschl=C3=A4ger?= Date: Mon, 6 May 2024 18:10:58 +0200 Subject: [PATCH 3853/4092] Add new localisable strings --- .../DeleteConfirmationContentStrings.cs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 osu.Game/Localisation/DeleteConfirmationContentStrings.cs diff --git a/osu.Game/Localisation/DeleteConfirmationContentStrings.cs b/osu.Game/Localisation/DeleteConfirmationContentStrings.cs new file mode 100644 index 0000000000..26b7133456 --- /dev/null +++ b/osu.Game/Localisation/DeleteConfirmationContentStrings.cs @@ -0,0 +1,47 @@ +// 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.Localisation; + +namespace osu.Game.Localisation +{ + public static class DeleteConfirmationContentStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.DeleteConfirmationContent"; + + /// + /// "All beatmaps?" + /// + public static LocalisableString Beatmaps => new TranslatableString(getKey(@"beatmaps"), @"All beatmaps?"); + + /// + /// "All beatmaps videos? This cannot be undone!" + /// + public static LocalisableString BeatmapVideos => new TranslatableString(getKey(@"beatmap_videos"), @"All beatmaps videos? This cannot be undone!"); + + /// + /// "All skins? This cannot be undone!" + /// + public static LocalisableString Skins => new TranslatableString(getKey(@"skins"), @"All skins? This cannot be undone!"); + + /// + /// "All collections? This cannot be undone!" + /// + public static LocalisableString Collections => new TranslatableString(getKey(@"collections"), @"All collections? This cannot be undone!"); + + /// + /// "All scores? This cannot be undone!" + /// + public static LocalisableString Scores => new TranslatableString(getKey(@"collections"), @"All scores? This cannot be undone!"); + + /// + /// "All mod presets?" + /// + public static LocalisableString ModPresets => new TranslatableString(getKey(@"mod_presets"), @"All mod presets?"); + + + private static string getKey(string key) => $@"{prefix}:{key}"; + + } +} From 32444e0e30f10e4baadc6aac971c2f66f0bfbce1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Wolfschl=C3=A4ger?= Date: Mon, 6 May 2024 18:11:40 +0200 Subject: [PATCH 3854/4092] Make deletion confirmation content less confusing --- .../Sections/Maintenance/BeatmapSettings.cs | 6 +++--- .../Sections/Maintenance/CollectionsSettings.cs | 2 +- .../Maintenance/MassDeleteConfirmationDialog.cs | 5 +++-- .../MassVideoDeleteConfirmationDialog.cs | 16 ---------------- .../Sections/Maintenance/ModPresetSettings.cs | 2 +- .../Sections/Maintenance/ScoreSettings.cs | 2 +- .../Sections/Maintenance/SkinSettings.cs | 2 +- 7 files changed, 10 insertions(+), 25 deletions(-) delete mode 100644 osu.Game/Overlays/Settings/Sections/Maintenance/MassVideoDeleteConfirmationDialog.cs diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs index 4b1836ed86..d0a8fc7d2c 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs @@ -31,7 +31,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { deleteBeatmapsButton.Enabled.Value = false; Task.Run(() => beatmaps.Delete()).ContinueWith(_ => Schedule(() => deleteBeatmapsButton.Enabled.Value = true)); - })); + }, DeleteConfirmationContentStrings.Beatmaps)); } }); @@ -40,11 +40,11 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Text = MaintenanceSettingsStrings.DeleteAllBeatmapVideos, Action = () => { - dialogOverlay?.Push(new MassVideoDeleteConfirmationDialog(() => + dialogOverlay?.Push(new MassDeleteConfirmationDialog(() => { deleteBeatmapVideosButton.Enabled.Value = false; Task.Run(beatmaps.DeleteAllVideos).ContinueWith(_ => Schedule(() => deleteBeatmapVideosButton.Enabled.Value = true)); - })); + }, DeleteConfirmationContentStrings.BeatmapVideos)); } }); AddRange(new Drawable[] diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs index b373535a8b..b1c44aa93c 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs @@ -29,7 +29,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Text = MaintenanceSettingsStrings.DeleteAllCollections, Action = () => { - dialogOverlay?.Push(new MassDeleteConfirmationDialog(deleteAllCollections)); + dialogOverlay?.Push(new MassDeleteConfirmationDialog(deleteAllCollections, DeleteConfirmationContentStrings.Collections)); } }); } diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MassDeleteConfirmationDialog.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MassDeleteConfirmationDialog.cs index 99ef62d94b..7ead815fe9 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MassDeleteConfirmationDialog.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MassDeleteConfirmationDialog.cs @@ -2,15 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Localisation; using osu.Game.Overlays.Dialog; namespace osu.Game.Overlays.Settings.Sections.Maintenance { public partial class MassDeleteConfirmationDialog : DangerousActionDialog { - public MassDeleteConfirmationDialog(Action deleteAction) + public MassDeleteConfirmationDialog(Action deleteAction, LocalisableString deleteContent) { - BodyText = "Everything?"; + BodyText = deleteContent; DangerousAction = deleteAction; } } diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MassVideoDeleteConfirmationDialog.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MassVideoDeleteConfirmationDialog.cs deleted file mode 100644 index 6312e09b3e..0000000000 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MassVideoDeleteConfirmationDialog.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 System; - -namespace osu.Game.Overlays.Settings.Sections.Maintenance -{ - public partial class MassVideoDeleteConfirmationDialog : MassDeleteConfirmationDialog - { - public MassVideoDeleteConfirmationDialog(Action deleteAction) - : base(deleteAction) - { - BodyText = "All beatmap videos? This cannot be undone!"; - } - } -} diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/ModPresetSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/ModPresetSettings.cs index f0d6d10e51..9c55308abe 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/ModPresetSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/ModPresetSettings.cs @@ -42,7 +42,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { deleteAllButton.Enabled.Value = false; Task.Run(deleteAllModPresets).ContinueWith(t => Schedule(onAllModPresetsDeleted, t)); - })); + }, DeleteConfirmationContentStrings.ModPresets)); } }, undeleteButton = new SettingsButton diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs index c6f4f1e1a5..235f239c7c 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs @@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { deleteScoresButton.Enabled.Value = false; Task.Run(() => scores.Delete()).ContinueWith(_ => Schedule(() => deleteScoresButton.Enabled.Value = true)); - })); + }, DeleteConfirmationContentStrings.Scores)); } }); } diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs index c3ac49af6d..e962118a36 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs @@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { deleteSkinsButton.Enabled.Value = false; Task.Run(() => skins.Delete()).ContinueWith(_ => Schedule(() => deleteSkinsButton.Enabled.Value = true)); - })); + }, DeleteConfirmationContentStrings.Skins)); } }); } From 12522d9ae5dc1ef36055a7698518231f8a74ebbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Wolfschl=C3=A4ger?= Date: Mon, 6 May 2024 18:29:46 +0200 Subject: [PATCH 3855/4092] Fix formatting issues --- osu.Game/Localisation/DeleteConfirmationContentStrings.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Localisation/DeleteConfirmationContentStrings.cs b/osu.Game/Localisation/DeleteConfirmationContentStrings.cs index 26b7133456..d9e90675f7 100644 --- a/osu.Game/Localisation/DeleteConfirmationContentStrings.cs +++ b/osu.Game/Localisation/DeleteConfirmationContentStrings.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 osu.Framework.Localisation; namespace osu.Game.Localisation @@ -40,8 +39,6 @@ namespace osu.Game.Localisation /// public static LocalisableString ModPresets => new TranslatableString(getKey(@"mod_presets"), @"All mod presets?"); - private static string getKey(string key) => $@"{prefix}:{key}"; - } } From f824bd14414e9e37af05a9dba130c02c7aa5a62d Mon Sep 17 00:00:00 2001 From: Susko3 Date: Mon, 6 May 2024 21:52:03 +0200 Subject: [PATCH 3856/4092] Fix `userTriggered` not being passed to private helper --- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 5bf28ae79b..67fd6a9550 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -540,7 +540,7 @@ namespace osu.Game.Overlays.SkinEditor protected void Redo() => changeHandler?.RestoreState(1); - public void Save(bool userTriggered = true) => save(currentSkin.Value); + public void Save(bool userTriggered = true) => save(currentSkin.Value, userTriggered); private void save(Skin skin, bool userTriggered = true) { From 91c684151a740005e24c3c52ff188772489b5171 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 May 2024 14:16:25 +0800 Subject: [PATCH 3857/4092] Update bundled beatmaps --- .../Drawables/BundledBeatmapDownloader.cs | 504 +++++++++++------- 1 file changed, 312 insertions(+), 192 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs b/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs index 21ab1b78ea..3aa34a5580 100644 --- a/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs +++ b/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text.RegularExpressions; using osu.Framework.Allocation; @@ -21,6 +22,8 @@ using osu.Game.Utils; namespace osu.Game.Beatmaps.Drawables { + [SuppressMessage("ReSharper", "StringLiteralTypo")] + [SuppressMessage("ReSharper", "CommentTypo")] public partial class BundledBeatmapDownloader : CompositeDrawable { private readonly bool shouldPostNotifications; @@ -50,7 +53,7 @@ namespace osu.Game.Beatmaps.Drawables { queueDownloads(always_bundled_beatmaps); - queueDownloads(bundled_osu, 8); + queueDownloads(bundled_osu, 6); queueDownloads(bundled_taiko, 3); queueDownloads(bundled_catch, 3); queueDownloads(bundled_mania, 3); @@ -128,6 +131,26 @@ namespace osu.Game.Beatmaps.Drawables } } + /* + * criteria for bundled maps (managed by pishifat) + * + * auto: + * - licensed song + * - includes ENHI diffs + * - between 60s and 240s + * + * manual: + * - bg is explicitly permitted as okay to use. lots of artists say some variation of "it's ok for personal use/non-commercial use/with credit" + * (which is prob fine when maps are presented as user-generated content), but for a new osu! player, it's easy to assume bundled maps are + * commercial content like other rhythm games, so it's best to be cautious about using not-explicitly-permitted artwork. + * + * - no ai/thirst bgs + * - no controversial/explicit song content or titles + * - no repeating bundled songs (within each mode) + * - no songs that are relatively low production value + * - no songs with limited accessibility (annoying high pitch vocals, noise rock, etc) + */ + private const string tutorial_filename = "1011011 nekodex - new beginnings.osz"; /// @@ -135,215 +158,312 @@ namespace osu.Game.Beatmaps.Drawables /// private static readonly string[] always_bundled_beatmaps = { - // This thing is 40mb, I'm not sure we want it here... + // winner of https://osu.ppy.sh/home/news/2013-09-06-osu-monthly-beatmapping-contest-1 + @"123593 Rostik - Liquid (Paul Rosenthal Remix).osz", + // winner of https://osu.ppy.sh/home/news/2013-10-28-monthly-beatmapping-contest-2-submissions-open + @"140662 cYsmix feat. Emmy - Tear Rain.osz", + // winner of https://osu.ppy.sh/home/news/2013-12-15-monthly-beatmapping-contest-3-submissions-open + @"151878 Chasers - Lost.osz", + // winner of https://osu.ppy.sh/home/news/2014-02-14-monthly-beatmapping-contest-4-submissions-now + @"163112 Kuba Oms - My Love.osz", + // winner of https://osu.ppy.sh/home/news/2014-05-07-monthly-beatmapping-contest-5-submissions-now + @"190390 Rameses B - Flaklypa.osz", + // winner of https://osu.ppy.sh/home/news/2014-09-24-monthly-beatmapping-contest-7 + @"241526 Soleily - Renatus.osz", + // winner of https://osu.ppy.sh/home/news/2015-02-11-monthly-beatmapping-contest-8 + @"299224 raja - the light.osz", + // winner of https://osu.ppy.sh/home/news/2015-04-13-monthly-beatmapping-contest-9-taiko-only + @"319473 Furries in a Blender - Storm World.osz", + // winner of https://osu.ppy.sh/home/news/2015-06-15-monthly-beatmapping-contest-10-ctb-only + @"342751 Hylian Lemon - Foresight Is for Losers.osz", + // winner of https://osu.ppy.sh/home/news/2015-08-22-monthly-beatmapping-contest-11-mania-only + @"385056 Toni Leys - Dragon Valley (Toni Leys Remix feat. Esteban Bellucci).osz", + // winner of https://osu.ppy.sh/home/news/2016-03-04-beatmapping-contest-12-osu + @"456054 IAHN - Candy Luv (Short Ver.).osz", + // winner of https://osu.ppy.sh/home/news/2020-11-30-a-labour-of-love + // (this thing is 40mb, I'm not sure if we want it here...) @"1388906 Raphlesia & BilliumMoto - My Love.osz", - // Winner of Triangles mapping competition: https://osu.ppy.sh/home/news/2022-10-06-results-triangles + // winner of https://osu.ppy.sh/home/news/2022-05-31-triangles @"1841885 cYsmix - triangles.osz", + // winner of https://osu.ppy.sh/home/news/2023-02-01-twin-trials-contest-beatmapping-phase + @"1971987 James Landino - Aresene's Bazaar.osz", }; private static readonly string[] bundled_osu = { - "682286 Yuyoyuppe - Emerald Galaxy.osz", - "682287 baker - For a Dead Girl+.osz", - "682289 Hige Driver - I Wanna Feel Your Love (feat. shully).osz", - "682290 Hige Driver - Miracle Sugite Yabai (feat. shully).osz", - "682416 Hige Driver - Palette.osz", - "682595 baker - Kimi ga Kimi ga -vocanico remix-.osz", - "716211 yuki. - Spring Signal.osz", - "716213 dark cat - BUBBLE TEA (feat. juu & cinders).osz", - "716215 LukHash - CLONED.osz", - "716219 IAHN - Snowdrop.osz", - "716249 *namirin - Senaka Awase no Kuukyo (with Kakichoco).osz", - "716390 sakuraburst - SHA.osz", - "716441 Fractal Dreamers - Paradigm Shift.osz", - "729808 Thaehan - Leprechaun.osz", - "751771 Cranky - Hanaarashi.osz", - "751772 Cranky - Ran.osz", - "751773 Cranky - Feline, the White....osz", - "751774 Function Phantom - Variable.osz", - "751779 Rin - Daishibyo set 14 ~ Sado no Futatsuiwa.osz", - "751782 Fractal Dreamers - Fata Morgana.osz", - "751785 Cranky - Chandelier - King.osz", - "751846 Fractal Dreamers - Celestial Horizon.osz", - "751866 Rin - Moriya set 08 ReEdit ~ Youkai no Yama.osz", - "751894 Fractal Dreamers - Blue Haven.osz", - "751896 Cranky - Rave 2 Rave.osz", - "751932 Cranky - La fuite des jours.osz", - "751972 Cranky - CHASER.osz", - "779173 Thaehan - Superpower.osz", - "780932 VINXIS - A Centralized View.osz", - "785572 S3RL - I'll See You Again (feat. Chi Chi).osz", - "785650 yuki. feat. setsunan - Hello! World.osz", - "785677 Dictate - Militant.osz", - "785731 S3RL - Catchit (Radio Edit).osz", - "785774 LukHash - GLITCH.osz", - "786498 Trial & Error - Tokoyami no keiyaku KEGARETA-SHOUJO feat. GUMI.osz", - "789374 Pulse - LP.osz", - "789528 James Portland - Sky.osz", - "789529 Lexurus - Gravity.osz", - "789544 Andromedik - Invasion.osz", - "789905 Gourski x Himmes - Silence.osz", - "791667 cYsmix - Babaroque (Short Ver.).osz", - "791798 cYsmix - Behind the Walls.osz", - "791845 cYsmix - Little Knight.osz", - "792241 cYsmix - Eden.osz", - "792396 cYsmix - The Ballad of a Mindless Girl.osz", - "795432 Phonetic - Journey.osz", - "831322 DJ'TEKINA//SOMETHING - Hidamari no Uta.osz", - "847764 Cranky - Crocus.osz", - "847776 Culprate & Joe Ford - Gaucho.osz", - "847812 J. Pachelbel - Canon (Cranky Remix).osz", - "847900 Cranky - Time Alter.osz", - "847930 LukHash - 8BIT FAIRY TALE.osz", - "848003 Culprate - Aurora.osz", - "848068 nanobii - popsicle beach.osz", - "848090 Trial & Error - DAI*TAN SENSATION feat. Nanahira, Mii, Aitsuki Nakuru (Short Ver.).osz", - "848259 Culprate & Skorpion - Jester.osz", - "848976 Dictate - Treason.osz", - "851543 Culprate - Florn.osz", - "864748 Thaehan - Angry Birds Epic (Remix).osz", - "873667 OISHII - ONIGIRI FREEWAY.osz", - "876227 Culprate, Keota & Sophie Meiers - Mechanic Heartbeat.osz", - "880487 cYsmix - Peer Gynt.osz", - "883088 Wisp X - Somewhere I'd Rather Be.osz", - "891333 HyuN - White Aura.osz", - "891334 HyuN - Wild Card.osz", - "891337 HyuN feat. LyuU - Cross Over.osz", - "891338 HyuN & Ritoru - Apocalypse in Love.osz", - "891339 HyuN feat. Ato - Asu wa Ame ga Yamukara.osz", - "891345 HyuN - Infinity Heaven.osz", - "891348 HyuN - Guitian.osz", - "891356 HyuN - Legend of Genesis.osz", - "891366 HyuN - Illusion of Inflict.osz", - "891417 HyuN feat. Yu-A - My life is for you.osz", - "891441 HyuN - You'Re aRleAdY dEAd.osz", - "891632 HyuN feat. YURI - Disorder.osz", - "891712 HyuN - Tokyo's Starlight.osz", - "901091 *namirin - Ciel etoile.osz", - "916990 *namirin - Koishiteiku Planet.osz", - "929284 tieff - Sense of Nostalgia.osz", - "933940 Ben Briggs - Yes (Maybe).osz", - "934415 Ben Briggs - Fearless Living.osz", - "934627 Ben Briggs - New Game Plus.osz", - "934666 Ben Briggs - Wave Island.osz", - "936126 siromaru + cranky - conflict.osz", - "940377 onumi - ARROGANCE.osz", - "940597 tieff - Take Your Swimsuit.osz", - "941085 tieff - Our Story.osz", - "949297 tieff - Sunflower.osz", - "952380 Ben Briggs - Why Are We Yelling.osz", - "954272 *namirin - Kanzen Shouri*Esper Girl.osz", - "955866 KIRA & Heartbreaker - B.B.F (feat. Hatsune Miku & Kagamine Rin).osz", - "961320 Kuba Oms - All In All.osz", - "964553 The Flashbulb - You Take the World's Weight Away.osz", - "965651 Fractal Dreamers - Ad Astra.osz", - "966225 The Flashbulb - Passage D.osz", - "966324 DJ'TEKINA//SOMETHING - Hidamari no Uta.osz", - "972810 James Landino & Kabuki - Birdsong.osz", - "972932 James Landino - Hide And Seek.osz", - "977276 The Flashbulb - Mellann.osz", - "981616 *namirin - Mizutamari Tobikoete (with Nanahira).osz", - "985788 Loki - Wizard's Tower.osz", - "996628 OISHII - ONIGIRI FREEWAY.osz", - "996898 HyuN - White Aura.osz", - "1003554 yuki. - Nadeshiko Sensation.osz", - "1014936 Thaehan - Bwa !.osz", - "1019827 UNDEAD CORPORATION - Sad Dream.osz", - "1020213 Creo - Idolize.osz", - "1021450 Thaehan - Chiptune & Baroque.osz", + @"682286 Yuyoyuppe - Emerald Galaxy.osz", + @"682287 baker - For a Dead Girl+.osz", + @"682595 baker - Kimi ga Kimi ga -vocanico remix-.osz", + @"1048705 Thaehan - Never Give Up.osz", + @"1050185 Carpool Tunnel - Hooked Again.osz", + @"1052846 Carpool Tunnel - Impressions.osz", + @"1062477 Ricky Montgomery - Line Without a Hook.osz", + @"1081119 Celldweller - Pulsar.osz", + @"1086289 Frums - 24eeev0-$.osz", + @"1133317 PUP - Free At Last.osz", + @"1171188 PUP - Full Blown Meltdown.osz", + @"1177043 PUP - My Life Is Over And I Couldn't Be Happier.osz", + @"1250387 Circle of Dust - Humanarchy (Cut Ver.).osz", + @"1255411 Wisp X - Somewhere I'd Rather Be.osz", + @"1320298 nekodex - Little Drummer Girl.osz", + @"1323877 Masahiro ""Godspeed"" Aoki - Blaze.osz", + @"1342280 Minagu feat. Aitsuki Nakuru - Theater Endroll.osz", + @"1356447 SECONDWALL - Boku wa Boku de shika Nakute.osz", + @"1368054 SECONDWALL - Shooting Star.osz", + @"1398580 La priere - Senjou no Utahime.osz", + @"1403962 m108 - Sunflower.osz", + @"1405913 fiend - FEVER DREAM (feat. yzzyx).osz", + @"1409184 Omoi - Hey William (New Translation).osz", + @"1413418 URBANGARDE - KAMING OUT (Cut Ver.).osz", + @"1417793 P4koo (NONE) - Sogaikan Utopia.osz", + @"1428384 DUAL ALTER WORLD - Veracila.osz", + @"1442963 PUP - DVP.osz", + @"1460370 Sound Souler - Empty Stars.osz", + @"1485184 Koven - Love Wins Again.osz", + @"1496811 T & Sugah - Wicked Days (Cut Ver.).osz", + @"1501511 Masahiro ""Godspeed"" Aoki - Frostbite (Cut Ver.).osz", + @"1511518 T & Sugah X Zazu - Lost On My Own (Cut Ver.).osz", + @"1516617 wotoha - Digital Life Hacker.osz", + @"1524273 Michael Cera Palin - Admiral.osz", + @"1564234 P4koo - Fly High (feat. rerone).osz", + @"1572918 Lexurus - Take Me Away (Cut Ver.).osz", + @"1577313 Kurubukko - The 84th Flight.osz", + @"1587839 Amidst - Droplet.osz", + @"1595193 BlackY - Sakura Ranman Cleopatra.osz", + @"1667560 xi - FREEDOM DiVE.osz", + @"1668789 City Girl - L2M (feat. Kelsey Kuan).osz", + @"1672934 xi - Parousia.osz", + @"1673457 Boom Kitty - Any Other Way (feat. Ivy Marie).osz", + @"1685122 xi - Time files.osz", + @"1689372 NIWASHI - Y.osz", + @"1729551 JOYLESS - Dream.osz", + @"1742868 Ritorikal - Synergy.osz", + @"1757511 KINEMA106 - KARASU.osz", + @"1778169 Ricky Montgomery - Cabo.osz", + @"1848184 FRASER EDWARDS - Ruination.osz", + @"1862574 Pegboard Nerds - Try This (Cut Ver.).osz", + @"1873680 happy30 - You spin my world.osz", + @"1890055 A.SAKA - Mutsuki Akari no Yuki.osz", + @"1911933 Marmalade butcher - Waltz for Chroma (feat. Natsushiro Takaaki).osz", + @"1940007 Mili - Ga1ahad and Scientific Witchery.osz", + @"1948970 Shadren - You're Here Forever.osz", + @"1967856 Annabel - alpine blue.osz", + @"1969316 Silentroom - NULCTRL.osz", + @"1978614 Krimek - Idyllic World.osz", + @"1991315 Feint - Tower Of Heaven (You Are Slaves) (Cut Ver.).osz", + @"1997470 tephe - Genjitsu Escape.osz", + @"1999116 soowamisu - .vaporcore.osz", + @"2010589 Junk - Yellow Smile (bms edit).osz", + @"2022054 Yokomin - STINGER.osz", + @"2025686 Aice room - For U.osz", + @"2035357 C-Show feat. Ishizawa Yukari - Border Line.osz", + @"2039403 SECONDWALL - Freedom.osz", + @"2046487 Rameses B - Against the Grain (feat. Veela).osz", + @"2052201 ColBreakz & Vizzen - Remember.osz", + @"2055535 Sephid - Thunderstrike 1988.osz", + @"2057584 SAMString - Ataraxia.osz", + @"2067270 Blue Stahli - The Fall.osz", + @"2075039 garlagan - Skyless.osz", + @"2079089 Hamu feat. yuiko - Innocent Letter.osz", + @"2082895 FATE GEAR - Heart's Grave.osz", + @"2085974 HoneyComeBear - Twilight.osz", + @"2094934 F.O.O.L & Laura Brehm - Waking Up.osz", + @"2097481 Mameyudoufu - Wave feat. Aitsuki Nakuru.osz", + @"2106075 MYUKKE. - The 89's Momentum.osz", + @"2117392 t+pazolite & Komiya Mao - Elustametat.osz", + @"2123533 LeaF - Calamity Fortune.osz", + @"2143876 Alkome - Your Voice.osz", + @"2145826 Sephid - Cross-D Skyline.osz", + @"2153172 Emiru no Aishita Tsukiyo ni Dai San Gensou Kyoku wo - Eternal Bliss.osz", }; private static readonly string[] bundled_taiko = { - "707824 Fractal Dreamers - Fortuna Redux.osz", - "789553 Cranky - Ran.osz", - "827822 Function Phantom - Neuronecia.osz", - "847323 Nakanojojo - Bittersweet (feat. Kuishinboakachan a.k.a Kiato).osz", - "847433 Trial & Error - Tokoyami no keiyaku KEGARETA-SHOUJO feat. GUMI.osz", - "847576 dark cat - hot chocolate.osz", - "847957 Wisp X - Final Moments.osz", - "876282 VINXIS - Greetings.osz", - "876648 Thaehan - Angry Birds Epic (Remix).osz", - "877069 IAHN - Transform (Original Mix).osz", - "877496 Thaehan - Leprechaun.osz", - "877935 Thaehan - Overpowered.osz", - "878344 yuki. - Be Your Light.osz", - "918446 VINXIS - Facade.osz", - "918903 LukHash - Ghosts.osz", - "919251 *namirin - Hitokoto no Kyori.osz", - "919704 S3RL - I Will Pick You Up (feat. Tamika).osz", - "921535 SOOOO - Raven Haven.osz", - "927206 *namirin - Kanzen Shouri*Esper Girl.osz", - "927544 Camellia feat. Nanahira - Kansoku Eisei.osz", - "930806 Nakanojojo - Pararara (feat. Amekoya).osz", - "931741 Camellia - Quaoar.osz", - "935699 Rin - Mythic set ~ Heart-Stirring Urban Legends.osz", - "935732 Thaehan - Yuujou.osz", - "941145 Function Phantom - Euclid.osz", - "942334 Dictate - Cauldron.osz", - "946540 nanobii - astral blast.osz", - "948844 Rin - Kishinjou set 01 ~ Mist Lake.osz", - "949122 Wisp X - Petal.osz", - "951618 Rin - Kishinjou set 02 ~ Mermaid from the Uncharted Land.osz", - "957412 Rin - Lunatic set 16 ~ The Space Shrine Maiden Returns Home.osz", - "961335 Thaehan - Insert Coin.osz", - "965178 The Flashbulb - DIDJ PVC.osz", - "966087 The Flashbulb - Creep.osz", - "966277 The Flashbulb - Amen Iraq.osz", - "966407 LukHash - ROOM 12.osz", - "966451 The Flashbulb - Six Acid Strings.osz", - "972301 BilliumMoto - four veiled stars.osz", - "973173 nanobii - popsicle beach.osz", - "973954 BilliumMoto - Rocky Buinne (Short Ver.).osz", - "975435 BilliumMoto - life flashes before weeb eyes.osz", - "978759 L. V. Beethoven - Moonlight Sonata (Cranky Remix).osz", - "982559 BilliumMoto - HDHR.osz", - "984361 The Flashbulb - Ninedump.osz", - "1023681 Inferi - The Ruin of Mankind.osz", - "1034358 ALEPH - The Evil Spirit.osz", - "1037567 ALEPH - Scintillations.osz", + "1048153 Chroma - [@__@].osz", + "1229307 Venetian Snares - Shaky Sometimes.osz", + "1236083 meganeko - Sirius A (osu! edit).osz", + "1248594 Noisia - Anomaly.osz", + "1272851 siqlo - One Way Street.osz", + "1290736 Kola Kid - good old times.osz", + "1318825 SECONDWALL - Light.osz", + "1320872 MYUKKE. - The 89's Momentum.osz", + "1337389 cute girls doing cute things - Main Heroine.osz", + "1397782 Reku Mochizuki - Yorixiro.osz", + "1407228 II-L - VANGUARD-1.osz", + "1422686 II-L - VANGUARD-2.osz", + "1429217 Street - Phi.osz", + "1442235 2ToneDisco x Cosmicosmo - Shoelaces (feat. Puniden).osz", + "1447478 Cres. - End Time.osz", + "1449942 m108 - Crescent Sakura.osz", + "1463778 MuryokuP - A tree without a branch.osz", + "1465152 fiend - Fever Dream (feat. yzzyx).osz", + "1472397 MYUKKE. - Boudica.osz", + "1488148 Aoi vs. siqlo - Hacktivism.osz", + "1522733 wotoha - Digital Life Hacker.osz", + "1540010 Marmalade butcher - Floccinaucinihilipilification.osz", + "1584690 MYUKKE. - AKKERA-COUNTRY-BOY.osz", + "1608857 BLOOD STAIN CHILD - S.O.P.H.I.A.osz", + "1609365 Reku Mochizuki - Faith of Eastward.osz", + "1622545 METAROOM - I - DINKI THE STARGUIDE.osz", + "1629336 METAROOM - PINK ORIGINS.osz", + "1644680 Neko Hacker - Pictures feat. 4s4ki.osz", + "1650835 RiraN - Ready For The Madness.osz", + "1661508 PTB10 - Starfall.osz", + "1671987 xi - World Fragments II.osz", + "1703065 tokiwa - wasurena feat. Sennzai.osz", + "1703527 tokiwa feat. Nakamura Sanso - Kotodama Refrain.osz", + "1704340 A-One feat. Shihori - Magic Girl !!.osz", + "1712783 xi - Parousia.osz", + "1718774 Harumaki Gohan - Suisei ni Nareta nara.osz", + "1719687 EmoCosine - Love Kills U.osz", + "1733940 WHITEFISTS feat. Sennzai - Paralyzed Ash.osz", + "1734692 EmoCosine - Cutter.osz", + "1739529 luvlxckdown - tbh i dont like being social.osz", + "1756970 Kurubukko vs. yukitani - Minamichita EVOLVED.osz", + "1762209 Marmalade butcher - Immortality Math Club.osz", + "1765720 ZxNX - FORTALiCE.osz", + "1786165 NILFRUITS - Arandano.osz", + "1787258 SAMString - Night Fighter.osz", + "1791462 ZxNX - Schadenfreude.osz", + "1793821 Kobaryo - The Lightning Sword.osz", + "1796440 kuru x miraie - re:start.osz", + "1799285 Origami Angel - 666 Flags.osz", + "1812415 nanobii - Rainbow Road.osz", + "1814682 NIWASHI - Y.osz", + "1818361 meganeko - Feral (osu! edit).osz", + "1818924 fiend - Disconnect.osz", + "1838730 Pegboard Nerds - Disconnected.osz", + "1854710 Blaster & Extra Terra - Spacecraft (Cut Ver.).osz", + "1859322 Hino Isuka - Delightness Brightness.osz", + "1884102 Maduk - Go (feat. Lachi) (Cut Ver.).osz", + "1884578 Neko Hacker - People People feat. Nanahira.osz", + "1897902 uma vs. Morimori Atsushi - Re: End of a Dream.osz", + "1905582 KINEMA106 - Fly Away (Cut Ver.).osz", + "1934686 ARForest - Rainbow Magic!!.osz", + "1963076 METAROOM - S.N.U.F.F.Y.osz", + "1968973 Stars Hollow - Out the Sunroof..osz", + "1971951 James Landino - Shiba Paradise.osz", + "1972518 Toromaru - Sleight of Hand.osz", + "1982302 KINEMA106 - INVITE.osz", + "1983475 KNOWER - The Government Knows.osz", + "2010165 Junk - Yellow Smile (bms edit).osz", + "2022737 Andora - Euphoria (feat. WaMi).osz", + "2025023 tephe - Genjitsu Escape.osz", + "2052754 P4koo - 8th:Planet ~Re:search~.osz", + "2054122 Raimukun - Myths Orbis.osz", + "2121470 Raimukun - Nyarlathotep's Dreamland.osz", + "2122284 Agressor Bunx - Tornado (Cut Ver.).osz", + "2125034 Agressor Bunx - Acid Mirage (Cut Ver.).osz", + "2136263 Se-U-Ra - Cris Fortress.osz", }; private static readonly string[] bundled_catch = { - "554256 Helblinde - When Time Sleeps.osz", - "693123 yuki. - Nadeshiko Sensation.osz", - "767009 OISHII - PIZZA PLAZA.osz", - "767346 Thaehan - Bwa !.osz", - "815162 VINXIS - Greetings.osz", - "840964 cYsmix - Breeze.osz", - "932657 Wisp X - Eventide.osz", - "933700 onumi - CONFUSION PART ONE.osz", - "933984 onumi - PERSONALITY.osz", - "934785 onumi - FAKE.osz", - "936545 onumi - REGRET PART ONE.osz", - "943803 Fractal Dreamers - Everything for a Dream.osz", - "943876 S3RL - I Will Pick You Up (feat. Tamika).osz", - "946773 Trial & Error - DREAMING COLOR (Short Ver.).osz", - "955808 Trial & Error - Tokoyami no keiyaku KEGARETA-SHOUJO feat. GUMI (Short Ver.).osz", - "957808 Fractal Dreamers - Module_410.osz", - "957842 antiPLUR - One Life Left to Live.osz", - "965730 The Flashbulb - Lawn Wake IV (Black).osz", - "966240 Creo - Challenger.osz", - "968232 Rin - Lunatic set 15 ~ The Moon as Seen from the Shrine.osz", - "972302 VINXIS - A Centralized View.osz", - "972887 HyuN - Illusion of Inflict.osz", - "1008600 LukHash - WHEN AN ANGEL DIES.osz", - "1032103 LukHash - H8 U.osz", + @"693123 yuki. - Nadeshiko Sensation.osz", + @"833719 FOLiACETATE - Heterochromia Iridis.osz", + @"981762 siromaru + cranky - conflict.osz", + @"1008600 LukHash - WHEN AN ANGEL DIES.osz", + @"1071294 dark cat - pursuit of happiness.osz", + @"1102115 meganeko - Nova.osz", + @"1115500 Chopin - Etude Op. 25, No. 12 (meganeko Remix).osz", + @"1128274 LeaF - Wizdomiot.osz", + @"1141049 HyuN feat. JeeE - Fallen Angel.osz", + @"1148215 Zekk - Fluctuation.osz", + @"1151833 ginkiha - nightfall.osz", + @"1158124 PUP - Dark Days.osz", + @"1184890 IAHN - Transform (Original Mix).osz", + @"1195922 Disasterpeace - Home.osz", + @"1197461 MIMI - Nanimo nai Youna.osz", + @"1197924 Camellia feat. Nanahira - Looking For A New Adventure.osz", + @"1203594 ginkiha - Anemoi.osz", + @"1211572 MIMI - Lapis Lazuli.osz", + @"1231601 Lime - Harmony.osz", + @"1240162 P4koo - 8th:Planet ~Re:search~.osz", + @"1246000 Zekk - Calling.osz", + @"1249928 Thaehan - Yuujou.osz", + @"1258751 Umeboshi Chazuke - ICHIBANBOSHI*ROCKET.osz", + @"1264818 Umeboshi Chazuke - Panic! Pop'n! Picnic! (2019 REMASTER).osz", + @"1280183 IAHN - Mad Halloween.osz", + @"1303201 Umeboshi Chazuke - Run*2 Run To You!!.osz", + @"1328918 Kobaryo - Theme for Psychopath Justice.osz", + @"1338215 Lime - Renai Syndrome.osz", + @"1338796 uma vs. Morimori Atsushi - Re:End of a Dream.osz", + @"1340492 MYUKKE. - The 89's Momentum.osz", + @"1393933 Mastermind (xi+nora2r) - Dreadnought.osz", + @"1400205 m108 - XIII Charlotte.osz", + @"1471328 Lime - Chronomia.osz", + @"1503591 Origami Angel - The Title Track.osz", + @"1524173 litmus* as Ester - Krave.osz", + @"1541235 Getty vs. DJ DiA - Grayed Out -Antifront-.osz", + @"1554250 Shawn Wasabi - Otter Pop (feat. Hollis).osz", + @"1583461 Sound Souler - Absent Color.osz", + @"1638487 tokiwa - wasurena feat. Sennzai.osz", + @"1698949 ZxNX - Schadenfreude.osz", + @"1704324 xi - Time files.osz", + @"1756405 Fractal Dreamers - Kingdom of Silence.osz", + @"1769575 cYsmix - Peer Gynt.osz", + @"1770054 Ardolf - Split.osz", + @"1772648 in love with a ghost - interdimensional portal leading to a cute place feat. snail's house.osz", + @"1776379 in love with a ghost - i thought we were lovers w/ basil.osz", + @"1779476 URBANGARDE - KIMI WA OKUMAGASO.osz", + @"1789435 xi - Parousia.osz", + @"1794190 Se-U-Ra - The Endless for Traveler.osz", + @"1799889 Waterflame - Ricochet Love.osz", + @"1816401 Gram vs. Yooh - Apocalypse.osz", + @"1826327 -45 - Total Eclipse of The Sun.osz", + @"1830796 xi - Halcyon.osz", + @"1924231 Mili - Nine Point Eight.osz", + @"1952903 Cres. - End Time.osz", + @"1970946 Good Kid - Slingshot.osz", + @"1982063 linear ring - enchanted love.osz", + @"2000438 Toromaru - Erinyes.osz", + @"2124277 II-L - VANGUARD-3.osz", + @"2147529 Nashimoto Ui - AaAaAaAAaAaAAa (Cut Ver.).osz", }; private static readonly string[] bundled_mania = { - "943516 antiPLUR - Clockwork Spooks.osz", - "946394 VINXIS - Three Times The Original Charm.osz", - "966408 antiPLUR - One Life Left to Live.osz", - "971561 antiPLUR - Runengon.osz", - "983864 James Landino - Shiba Island.osz", - "989512 BilliumMoto - 1xMISS.osz", - "994104 James Landino - Reaction feat. Slyleaf.osz", - "1003217 nekodex - circles!.osz", - "1009907 James Landino & Kabuki - Birdsong.osz", - "1015169 Thaehan - Insert Coin.osz", + @"1008419 BilliumMoto - Four Veiled Stars.osz", + @"1025170 Frums - We Want To Run.osz", + @"1092856 F-777 - Viking Arena.osz", + @"1139247 O2i3 - Heart Function.osz", + @"1154007 LeaF - ATHAZA.osz", + @"1170054 Zekk - Fallen.osz", + @"1212132 Street - Koiyamai (TV Size).osz", + @"1226466 Se-U-Ra - Elif to Shiro Kura no Yoru -Called-.osz", + @"1247210 Frums - Credits.osz", + @"1254196 ARForest - Regret.osz", + @"1258829 Umeboshi Chazuke - Cineraria.osz", + @"1300398 ARForest - The Last Page.osz", + @"1305627 Frums - Star of the COME ON!!.osz", + @"1348806 Se-U-Ra - LOA2.osz", + @"1375449 yuki. - Nadeshiko Sensation.osz", + @"1448292 Cres. - End Time.osz", + @"1479741 Reku Mochizuki - FORViDDEN ENERZY -Fataldoze-.osz", + @"1494747 Fractal Dreamers - Whispers from a Distant Star.osz", + @"1505336 litmus* - Rush-More.osz", + @"1508963 ARForest - Rainbow Magic!!.osz", + @"1727126 Chroma - Strange Inventor.osz", + @"1737101 ZxNX - FORTALiCE.osz", + @"1740952 Sobrem x Silentroom - Random.osz", + @"1756251 Plum - Mad Piano Party.osz", + @"1909163 Frums - theyaremanycolors.osz", + @"1916285 siromaru + cranky - conflict.osz", + @"1948972 Ardolf - Split.osz", + @"1957138 GLORYHAMMER - Rise Of The Chaos Wizards.osz", + @"1972411 James Landino - Shiba Paradise.osz", + @"1978179 Andora - Flicker (feat. RANASOL).osz", + @"1987180 cygnus - The Evolution of War.osz", + @"1994458 tephe - Genjitsu Escape.osz", + @"1999339 Aice room - Nyan Nyan Dive (EmoCosine Remix).osz", + @"2015361 HoneyComeBear - Rainy Girl.osz", + @"2028108 HyuN - Infinity Heaven.osz", + @"2055329 miraie & blackwinterwells - facade.osz", + @"2069877 Sephid - Thunderstrike 1988.osz", + @"2119716 Aethoro - Snowy.osz", + @"2120379 Synthion - VIVIDVELOCITY.osz", + @"2124805 Frums (unknown ""lambda"") - 19ZZ.osz", + @"2127811 Wiklund - Joy of Living (Cut Ver.).osz", }; } } From 1d6915478f485998a47e6926bd3927dd533d1309 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 May 2024 14:26:49 +0800 Subject: [PATCH 3858/4092] Add message letting users know that beatmaps are donwloading in the background --- .../FirstRunSetupBeatmapScreenStrings.cs | 5 +++++ .../Overlays/FirstRunSetup/ScreenBeatmaps.cs | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/osu.Game/Localisation/FirstRunSetupBeatmapScreenStrings.cs b/osu.Game/Localisation/FirstRunSetupBeatmapScreenStrings.cs index a77ee066e4..50a417312d 100644 --- a/osu.Game/Localisation/FirstRunSetupBeatmapScreenStrings.cs +++ b/osu.Game/Localisation/FirstRunSetupBeatmapScreenStrings.cs @@ -39,6 +39,11 @@ namespace osu.Game.Localisation /// public static LocalisableString BundledButton => new TranslatableString(getKey(@"bundled_button"), @"Get recommended beatmaps"); + /// + /// "Beatmaps will be downloaded in the background. You can continue with setup while this happens!" + /// + public static LocalisableString DownloadingInBackground => new TranslatableString(getKey(@"downloading_in_background"), @"Beatmaps will be downloaded in the background. You can continue with setup while this happens!"); + /// /// "You can also obtain more beatmaps from the main menu "browse" button at any time." /// diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs b/osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs index 385695f669..da60951ab6 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs @@ -15,6 +15,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Localisation; using osu.Game.Online; using osuTK; +using osuTK.Graphics; using Realms; namespace osu.Game.Overlays.FirstRunSetup @@ -25,6 +26,8 @@ namespace osu.Game.Overlays.FirstRunSetup private ProgressRoundedButton downloadBundledButton = null!; private ProgressRoundedButton downloadTutorialButton = null!; + private OsuTextFlowContainer downloadInBackgroundText = null!; + private OsuTextFlowContainer currentlyLoadedBeatmaps = null!; private BundledBeatmapDownloader? tutorialDownloader; @@ -100,6 +103,15 @@ namespace osu.Game.Overlays.FirstRunSetup Text = FirstRunSetupBeatmapScreenStrings.BundledButton, Action = downloadBundled }, + downloadInBackgroundText = new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE)) + { + Colour = OverlayColourProvider.Light2, + Alpha = 0, + TextAnchor = Anchor.TopCentre, + Text = FirstRunSetupBeatmapScreenStrings.DownloadingInBackground, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + }, new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE)) { Colour = OverlayColourProvider.Content1, @@ -169,6 +181,10 @@ namespace osu.Game.Overlays.FirstRunSetup if (bundledDownloader != null) return; + downloadInBackgroundText + .FlashColour(Color4.White, 500) + .FadeIn(200); + bundledDownloader = new BundledBeatmapDownloader(false); AddInternal(bundledDownloader); From 0ddd3cbdc5cba5ccc60e72ed97ebe07629915249 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 May 2024 17:02:25 +0800 Subject: [PATCH 3859/4092] Randomise which menu content is shown on arriving at the menu --- osu.Game/Screens/Menu/OnlineMenuBanner.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/OnlineMenuBanner.cs b/osu.Game/Screens/Menu/OnlineMenuBanner.cs index 6f98b73939..b9d269c82a 100644 --- a/osu.Game/Screens/Menu/OnlineMenuBanner.cs +++ b/osu.Game/Screens/Menu/OnlineMenuBanner.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; using osu.Framework.Threading; +using osu.Framework.Utils; using osu.Game.Graphics.Containers; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; @@ -111,7 +112,9 @@ namespace osu.Game.Screens.Menu content.AddRange(loaded); - displayIndex = -1; + // Many users don't spend much time at the main menu, so let's randomise where in the + // carousel of available images we start at to give each a fair chance. + displayIndex = RNG.Next(0, images.NewValue.Images.Length) - 1; showNext(); }, (cancellationTokenSource ??= new CancellationTokenSource()).Token); } From ab2677d913552b5d381c91675d8bf6628853585e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Wolfschl=C3=A4ger?= Date: Tue, 7 May 2024 17:15:49 +0200 Subject: [PATCH 3860/4092] Changed default delete confirmation content strings for better translatability --- .../DeleteConfirmationContentStrings.cs | 24 +++++++++---------- .../DeleteConfirmationDialogStrings.cs | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game/Localisation/DeleteConfirmationContentStrings.cs b/osu.Game/Localisation/DeleteConfirmationContentStrings.cs index d9e90675f7..563fbf5654 100644 --- a/osu.Game/Localisation/DeleteConfirmationContentStrings.cs +++ b/osu.Game/Localisation/DeleteConfirmationContentStrings.cs @@ -10,34 +10,34 @@ namespace osu.Game.Localisation private const string prefix = @"osu.Game.Resources.Localisation.DeleteConfirmationContent"; /// - /// "All beatmaps?" + /// "Are you sure you want to delete all beatmaps?" /// - public static LocalisableString Beatmaps => new TranslatableString(getKey(@"beatmaps"), @"All beatmaps?"); + public static LocalisableString Beatmaps => new TranslatableString(getKey(@"beatmaps"), @"Are you sure you want to delete all beatmaps?"); /// - /// "All beatmaps videos? This cannot be undone!" + /// "Are you sure you want to delete all beatmaps videos? This cannot be undone!" /// - public static LocalisableString BeatmapVideos => new TranslatableString(getKey(@"beatmap_videos"), @"All beatmaps videos? This cannot be undone!"); + public static LocalisableString BeatmapVideos => new TranslatableString(getKey(@"beatmap_videos"), @"Are you sure you want to delete all beatmaps videos? This cannot be undone!"); /// - /// "All skins? This cannot be undone!" + /// "Are you sure you want to delete all skins? This cannot be undone!" /// - public static LocalisableString Skins => new TranslatableString(getKey(@"skins"), @"All skins? This cannot be undone!"); + public static LocalisableString Skins => new TranslatableString(getKey(@"skins"), @"Are you sure you want to delete all skins? This cannot be undone!"); /// - /// "All collections? This cannot be undone!" + /// "Are you sure you want to delete all collections? This cannot be undone!" /// - public static LocalisableString Collections => new TranslatableString(getKey(@"collections"), @"All collections? This cannot be undone!"); + public static LocalisableString Collections => new TranslatableString(getKey(@"collections"), @"Are you sure you want to delete all collections? This cannot be undone!"); /// - /// "All scores? This cannot be undone!" + /// "Are you sure you want to delete all scores? This cannot be undone!" /// - public static LocalisableString Scores => new TranslatableString(getKey(@"collections"), @"All scores? This cannot be undone!"); + public static LocalisableString Scores => new TranslatableString(getKey(@"collections"), @"Are you sure you want to delete all scores? This cannot be undone!"); /// - /// "All mod presets?" + /// "Are you sure you want to delete all mod presets?" /// - public static LocalisableString ModPresets => new TranslatableString(getKey(@"mod_presets"), @"All mod presets?"); + public static LocalisableString ModPresets => new TranslatableString(getKey(@"mod_presets"), @"Are you sure you want to delete all mod presets?"); private static string getKey(string key) => $@"{prefix}:{key}"; } diff --git a/osu.Game/Localisation/DeleteConfirmationDialogStrings.cs b/osu.Game/Localisation/DeleteConfirmationDialogStrings.cs index 33738fe95e..25997eadd3 100644 --- a/osu.Game/Localisation/DeleteConfirmationDialogStrings.cs +++ b/osu.Game/Localisation/DeleteConfirmationDialogStrings.cs @@ -10,9 +10,9 @@ namespace osu.Game.Localisation private const string prefix = @"osu.Game.Resources.Localisation.DeleteConfirmationDialog"; /// - /// "Confirm deletion of" + /// "Caution" /// - public static LocalisableString HeaderText => new TranslatableString(getKey(@"header_text"), @"Confirm deletion of"); + public static LocalisableString HeaderText => new TranslatableString(getKey(@"header_text"), @"Caution"); /// /// "Yes. Go for it." From 7551cf01d18a4e924ae9720417fa1797e8b38db5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 8 May 2024 09:45:52 +0200 Subject: [PATCH 3861/4092] Fix test failure --- osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index cc7b37e6a8..8ca141bb4f 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -14,7 +14,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Legacy; using osu.Game.IO.Legacy; -using osu.Game.Models; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Replays; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; @@ -226,10 +226,10 @@ namespace osu.Game.Tests.Beatmaps.Formats new OsuModDoubleTime { SpeedChange = { Value = 1.1 } } }; scoreInfo.OnlineID = 123123; - scoreInfo.RealmUser = new RealmUser + scoreInfo.User = new APIUser { Username = "spaceman_atlas", - OnlineID = 3035836, + Id = 3035836, CountryCode = CountryCode.PL }; scoreInfo.ClientVersion = "2023.1221.0"; From 88b20b357ae0d6ac5d1549981ec07e9a3f731cee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 May 2024 14:38:10 +0800 Subject: [PATCH 3862/4092] Increase padding around text in dialogs --- osu.Game/Overlays/Dialog/PopupDialog.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index 4ac37a63e2..a23c394c9f 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -210,7 +210,7 @@ namespace osu.Game.Overlays.Dialog RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, TextAnchor = Anchor.TopCentre, - Padding = new MarginPadding { Horizontal = 5 }, + Padding = new MarginPadding { Horizontal = 15 }, }, body = new OsuTextFlowContainer(t => t.Font = t.Font.With(size: 18)) { @@ -219,7 +219,7 @@ namespace osu.Game.Overlays.Dialog TextAnchor = Anchor.TopCentre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Horizontal = 5 }, + Padding = new MarginPadding { Horizontal = 15 }, }, buttonsContainer = new FillFlowContainer { From 3b8b56cbcbdb0365a03231aa151efb0050c12ebf Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 9 May 2024 20:18:53 +0900 Subject: [PATCH 3863/4092] Apply required changes after framework masking updates --- .../Sliders/Components/PathControlPointVisualiser.cs | 2 +- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 3 +-- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 3 +-- osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs | 2 +- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 3 +-- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 2 +- .../Edit/Compose/Components/Timeline/TimelineTickDisplay.cs | 3 +-- 7 files changed, 7 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 836d348ff4..afc2d407e9 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components } // Generally all the control points are within the visible area all the time. - public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) => true; + public override bool UpdateSubTreeMasking() => true; /// /// Handles correction of invalid path types. diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 4933eb4041..93c3450904 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -8,7 +8,6 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Primitives; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; @@ -37,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.UI // For osu! gameplay, everything is always on screen. // Skipping masking calculations improves performance in intense beatmaps (ie. https://osu.ppy.sh/beatmapsets/150945#osu/372245) - public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) => false; + public override bool UpdateSubTreeMasking() => false; public SmokeContainer Smoke { get; } public FollowPointRenderer FollowPoints { get; } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 0510f08068..bdcb341fb4 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -7,7 +7,6 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Primitives; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Judgements; @@ -345,7 +344,7 @@ namespace osu.Game.Rulesets.Taiko.UI { public void Add(Drawable proxy) => AddInternal(proxy); - public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) + public override bool UpdateSubTreeMasking() { // DrawableHitObject disables masking. // Hitobject content is proxied and unproxied based on hit status and the IsMaskedAway value could get stuck because of this. diff --git a/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs b/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs index 3a2db4fc71..111dede815 100644 --- a/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs +++ b/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs @@ -22,7 +22,7 @@ namespace osu.Game.Tournament.Screens.Ladder protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false; - public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) => false; + public override bool UpdateSubTreeMasking() => false; protected override void OnDrag(DragEvent e) { diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index de05219212..3ce6cc3cef 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -15,7 +15,6 @@ using osu.Framework.Extensions.ListExtensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Primitives; using osu.Framework.Lists; using osu.Framework.Threading; using osu.Framework.Utils; @@ -632,7 +631,7 @@ namespace osu.Game.Rulesets.Objects.Drawables #endregion - public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) => false; + public override bool UpdateSubTreeMasking() => false; protected override void UpdateAfterChildren() { diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index b49924762e..c4feb249f4 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.UI break; base.UpdateSubTree(); - UpdateSubTreeMasking(this, ScreenSpaceDrawQuad.AABBFloat); + UpdateSubTreeMasking(); } while (state == PlaybackState.RequiresCatchUp && stopwatch.ElapsedMilliseconds < max_catchup_milliseconds); return true; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index c3adb43032..e16c8519e5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -7,7 +7,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Graphics; -using osu.Framework.Graphics.Primitives; using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -20,7 +19,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public partial class TimelineTickDisplay : TimelinePart { // With current implementation every tick in the sub-tree should be visible, no need to check whether they are masked away. - public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) => false; + public override bool UpdateSubTreeMasking() => false; [Resolved] private EditorBeatmap beatmap { get; set; } = null!; From c4d6318c0d0026bb5b581fb198aded74376de775 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 May 2024 22:12:09 +0800 Subject: [PATCH 3864/4092] 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 97dfe5d9f7..e20ac2e0b7 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 66347acdf0..103ef50e0c 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From de05998421b2c04d669bf08897e6fd82e49d1815 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 May 2024 22:17:00 +0800 Subject: [PATCH 3865/4092] Avoid weird codestyle rules --- osu.Desktop/Program.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index d8364fc6e6..23e56cdce9 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -107,11 +107,13 @@ namespace osu.Desktop } } - using (DesktopGameHost host = Host.GetSuitableDesktopHost(gameName, new HostOptions - { - IPCPort = !tournamentClient ? OsuGame.IPC_PORT : null, - FriendlyGameName = OsuGameBase.GAME_NAME, - })) + var hostOptions = new HostOptions + { + IPCPort = !tournamentClient ? OsuGame.IPC_PORT : null, + FriendlyGameName = OsuGameBase.GAME_NAME, + }; + + using (DesktopGameHost host = Host.GetSuitableDesktopHost(gameName, hostOptions)) { if (!host.IsPrimaryInstance) { From 6b91b4abf41660fb030f2d593df28159442cf145 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 8 May 2024 03:58:10 +0300 Subject: [PATCH 3866/4092] Add simple implementation for extended mods display in new footer design --- .../TestSceneFooterButtonModsV2.cs | 120 +++++++++ osu.Game/Screens/Play/HUD/ModDisplay.cs | 7 +- .../Select/FooterV2/FooterButtonModsV2.cs | 249 +++++++++++++++++- .../Screens/Select/FooterV2/FooterButtonV2.cs | 126 ++++----- 4 files changed, 436 insertions(+), 66 deletions(-) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonModsV2.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonModsV2.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonModsV2.cs new file mode 100644 index 0000000000..7e8bba6573 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonModsV2.cs @@ -0,0 +1,120 @@ +// 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.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Screens.Select.FooterV2; +using osu.Game.Utils; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public partial class TestSceneFooterButtonModsV2 : OsuTestScene + { + private readonly TestFooterButtonModsV2 footerButtonMods; + + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); + + public TestSceneFooterButtonModsV2() + { + Add(footerButtonMods = new TestFooterButtonModsV2 + { + Anchor = Anchor.Centre, + Origin = Anchor.CentreLeft, + X = -100, + Action = () => { }, + }); + } + + [Test] + public void TestDisplay() + { + AddStep("one mod", () => changeMods(new List { new OsuModHidden() })); + AddStep("two mods", () => changeMods(new List { new OsuModHidden(), new OsuModHardRock() })); + AddStep("three mods", () => changeMods(new List { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime() })); + AddStep("four mods", () => changeMods(new List { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime(), new OsuModClassic() })); + AddStep("five mods", () => changeMods(new List { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime(), new OsuModClassic(), new OsuModDifficultyAdjust() })); + + AddStep("clear mods", () => changeMods(Array.Empty())); + AddWaitStep("wait", 3); + AddStep("one mod", () => changeMods(new List { new OsuModHidden() })); + + AddStep("clear mods", () => changeMods(Array.Empty())); + AddWaitStep("wait", 3); + AddStep("five mods", () => changeMods(new List { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime(), new OsuModClassic(), new OsuModDifficultyAdjust() })); + } + + [Test] + public void TestIncrementMultiplier() + { + var hiddenMod = new Mod[] { new OsuModHidden() }; + AddStep(@"Add Hidden", () => changeMods(hiddenMod)); + AddAssert(@"Check Hidden multiplier", () => assertModsMultiplier(hiddenMod)); + + var hardRockMod = new Mod[] { new OsuModHardRock() }; + AddStep(@"Add HardRock", () => changeMods(hardRockMod)); + AddAssert(@"Check HardRock multiplier", () => assertModsMultiplier(hardRockMod)); + + var doubleTimeMod = new Mod[] { new OsuModDoubleTime() }; + AddStep(@"Add DoubleTime", () => changeMods(doubleTimeMod)); + AddAssert(@"Check DoubleTime multiplier", () => assertModsMultiplier(doubleTimeMod)); + + var multipleIncrementMods = new Mod[] { new OsuModDoubleTime(), new OsuModHidden(), new OsuModHardRock() }; + AddStep(@"Add multiple Mods", () => changeMods(multipleIncrementMods)); + AddAssert(@"Check multiple mod multiplier", () => assertModsMultiplier(multipleIncrementMods)); + } + + [Test] + public void TestDecrementMultiplier() + { + var easyMod = new Mod[] { new OsuModEasy() }; + AddStep(@"Add Easy", () => changeMods(easyMod)); + AddAssert(@"Check Easy multiplier", () => assertModsMultiplier(easyMod)); + + var noFailMod = new Mod[] { new OsuModNoFail() }; + AddStep(@"Add NoFail", () => changeMods(noFailMod)); + AddAssert(@"Check NoFail multiplier", () => assertModsMultiplier(noFailMod)); + + var multipleDecrementMods = new Mod[] { new OsuModEasy(), new OsuModNoFail() }; + AddStep(@"Add Multiple Mods", () => changeMods(multipleDecrementMods)); + AddAssert(@"Check multiple mod multiplier", () => assertModsMultiplier(multipleDecrementMods)); + } + + [Test] + public void TestUnrankedBadge() + { + AddStep(@"Add unranked mod", () => changeMods(new[] { new OsuModDeflate() })); + AddUntilStep("Unranked badge shown", () => footerButtonMods.UnrankedBadge.Alpha == 1); + AddStep(@"Clear selected mod", () => changeMods(Array.Empty())); + AddUntilStep("Unranked badge not shown", () => footerButtonMods.UnrankedBadge.Alpha == 0); + } + + private void changeMods(IReadOnlyList mods) + { + footerButtonMods.Current.Value = mods; + } + + private bool assertModsMultiplier(IEnumerable mods) + { + double multiplier = mods.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier); + string expectedValue = ModUtils.FormatScoreMultiplier(multiplier).ToString(); + + return expectedValue == footerButtonMods.MultiplierText.Current.Value; + } + + private partial class TestFooterButtonModsV2 : FooterButtonModsV2 + { + public new Container UnrankedBadge => base.UnrankedBadge; + public new OsuSpriteText MultiplierText => base.MultiplierText; + } + } +} diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index ba948b516e..75db720603 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -20,6 +20,7 @@ namespace osu.Game.Screens.Play.HUD /// public partial class ModDisplay : CompositeDrawable, IHasCurrentValue> { + private readonly bool showExtendedInformation; private const int fade_duration = 1000; public ExpansionMode ExpansionMode = ExpansionMode.ExpandOnHover; @@ -39,8 +40,10 @@ namespace osu.Game.Screens.Play.HUD private readonly FillFlowContainer iconsContainer; - public ModDisplay() + public ModDisplay(bool showExtendedInformation = true) { + this.showExtendedInformation = showExtendedInformation; + AutoSizeAxes = Axes.Both; InternalChild = iconsContainer = new ReverseChildIDFillFlowContainer @@ -64,7 +67,7 @@ namespace osu.Game.Screens.Play.HUD iconsContainer.Clear(); foreach (Mod mod in mods.NewValue.AsOrdered()) - iconsContainer.Add(new ModIcon(mod) { Scale = new Vector2(0.6f) }); + iconsContainer.Add(new ModIcon(mod, showExtendedInformation: showExtendedInformation) { Scale = new Vector2(0.6f) }); appearTransform(); } diff --git a/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs b/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs index b8c9f0b34b..f9d923ddcf 100644 --- a/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs +++ b/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs @@ -1,20 +1,261 @@ // 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.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Configuration; using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Localisation; +using osu.Game.Overlays; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Play.HUD; +using osu.Game.Utils; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Select.FooterV2 { - public partial class FooterButtonModsV2 : FooterButtonV2 + public partial class FooterButtonModsV2 : FooterButtonV2, IHasCurrentValue> { - [BackgroundDependencyLoader] - private void load(OsuColour colour) + // todo: see https://github.com/ppy/osu-framework/issues/3271 + private const float torus_scale_factor = 1.2f; + + private readonly BindableWithCurrent> current = new BindableWithCurrent>(Array.Empty()); + + public Bindable> Current { + get => current.Current; + set => current.Current = value; + } + + private Container modDisplayBar = null!; + + protected Container UnrankedBadge { get; private set; } = null!; + + private ModDisplay modDisplay = null!; + private OsuSpriteText modCountText = null!; + + protected OsuSpriteText MultiplierText { get; private set; } = null!; + + [Resolved] + private OsuColour colours { get; set; } = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load() + { + const float bar_shear_width = 7f; + const float bar_height = 37f; + const float display_rel_width = 0.65f; + + var barShear = new Vector2(bar_shear_width / bar_height, 0); + Text = "Mods"; Icon = FontAwesome.Solid.ExchangeAlt; - AccentColour = colour.Lime1; + AccentColour = colours.Lime1; + + AddRange(new[] + { + UnrankedBadge = new Container + { + Position = new Vector2(BUTTON_WIDTH + 5f, -5f), + Depth = float.MaxValue, + Origin = Anchor.BottomLeft, + Shear = barShear, + CornerRadius = CORNER_RADIUS, + AutoSizeAxes = Axes.Both, + Masking = true, + BorderColour = Color4.White, + BorderThickness = 2f, + Children = new Drawable[] + { + new Box + { + Colour = colours.Red2, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Shear = -barShear, + Text = ModSelectOverlayStrings.Unranked.ToUpper(), + Margin = new MarginPadding { Horizontal = 15, Vertical = 5 }, + Font = OsuFont.Torus.With(size: 14 * torus_scale_factor, weight: FontWeight.Bold), + Colour = Color4.Black, + } + } + }, + modDisplayBar = new Container + { + Y = -5f, + Depth = float.MaxValue, + Origin = Anchor.BottomLeft, + Shear = barShear, + CornerRadius = CORNER_RADIUS, + Size = new Vector2(BUTTON_WIDTH, bar_height), + Masking = true, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Radius = 4, + // Figma says 50% opacity, but it does not match up visually if taken at face value, and looks bad. + Colour = Colour4.Black.Opacity(0.25f), + Offset = new Vector2(0, 2), + }, + Children = new Drawable[] + { + new Box + { + Colour = colourProvider.Background4, + RelativeSizeAxes = Axes.Both, + }, + new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Both, + Width = 1f - display_rel_width, + Masking = true, + Child = MultiplierText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Shear = -barShear, + Font = OsuFont.Torus.With(size: 14 * torus_scale_factor, weight: FontWeight.Bold) + } + }, + new Container + { + CornerRadius = CORNER_RADIUS, + RelativeSizeAxes = Axes.Both, + Width = display_rel_width, + Masking = true, + Children = new Drawable[] + { + new Box + { + Colour = colourProvider.Background3, + RelativeSizeAxes = Axes.Both, + }, + modDisplay = new ModDisplay(showExtendedInformation: false) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Shear = -barShear, + Scale = new Vector2(0.6f), + Current = { Value = new List { new ModCinema() } }, + ExpansionMode = ExpansionMode.AlwaysContracted, + }, + modCountText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Shear = -barShear, + Font = OsuFont.Torus.With(size: 14 * torus_scale_factor, weight: FontWeight.Bold), + } + } + }, + } + }, + }); + } + + private ModSettingChangeTracker? modSettingChangeTracker; + + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(m => + { + modSettingChangeTracker?.Dispose(); + + updateDisplay(); + + if (m.NewValue != null) + { + modSettingChangeTracker = new ModSettingChangeTracker(m.NewValue); + modSettingChangeTracker.SettingChanged += _ => updateDisplay(); + } + }, true); + + FinishTransforms(true); + } + + private const double duration = 240; + private const Easing easing = Easing.OutQuint; + + private void updateDisplay() + { + if (Current.Value.Count == 0) + { + modDisplayBar.MoveToY(20, duration, easing); + modDisplayBar.FadeOut(duration, easing); + modDisplay.FadeOut(duration, easing); + modCountText.FadeOut(duration, easing); + + UnrankedBadge.MoveToY(20, duration, easing); + UnrankedBadge.FadeOut(duration, easing); + + // add delay to let unranked indicator hide first before resizing the button back to its original width. + this.Delay(duration).ResizeWidthTo(BUTTON_WIDTH, duration, easing); + } + else + { + if (Current.Value.Count >= 5) + { + modCountText.Text = $"{Current.Value.Count} MODS"; + modCountText.FadeIn(duration, easing); + modDisplay.FadeOut(duration, easing); + } + else + { + modDisplay.Current.Value = Current.Value; + modDisplay.FadeIn(duration, easing); + modCountText.FadeOut(duration, easing); + } + + if (Current.Value.Any(m => !m.Ranked)) + { + UnrankedBadge.MoveToX(BUTTON_WIDTH + 5, duration, easing); + UnrankedBadge.FadeIn(duration, easing); + + this.ResizeWidthTo(BUTTON_WIDTH + UnrankedBadge.DrawWidth + 10, duration, easing); + } + else + { + UnrankedBadge.MoveToX(BUTTON_WIDTH + 5 - UnrankedBadge.DrawWidth, duration, easing); + UnrankedBadge.FadeOut(duration, easing); + + this.ResizeWidthTo(BUTTON_WIDTH, duration, easing); + } + + modDisplayBar.MoveToY(-5, duration, Easing.OutQuint); + UnrankedBadge.MoveToY(-5, duration, easing); + modDisplayBar.FadeIn(duration, easing); + } + + double multiplier = Current.Value?.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier) ?? 1; + MultiplierText.Text = ModUtils.FormatScoreMultiplier(multiplier); + + if (multiplier > 1) + MultiplierText.FadeColour(colours.Red1, duration, easing); + else if (multiplier < 1) + MultiplierText.FadeColour(colours.Lime1, duration, easing); + else + MultiplierText.FadeColour(Color4.White, duration, easing); } } } diff --git a/osu.Game/Screens/Select/FooterV2/FooterButtonV2.cs b/osu.Game/Screens/Select/FooterV2/FooterButtonV2.cs index 2f5046d2bb..a7bd1b8abd 100644 --- a/osu.Game/Screens/Select/FooterV2/FooterButtonV2.cs +++ b/osu.Game/Screens/Select/FooterV2/FooterButtonV2.cs @@ -24,17 +24,18 @@ namespace osu.Game.Screens.Select.FooterV2 { public partial class FooterButtonV2 : OsuClickableContainer, IKeyBindingHandler { - private const int button_height = 90; - private const int button_width = 140; - private const int corner_radius = 10; private const int transition_length = 500; // This should be 12 by design, but an extra allowance is added due to the corner radius specification. public const float SHEAR_WIDTH = 13.5f; + public const int CORNER_RADIUS = 10; + public const int BUTTON_HEIGHT = 90; + public const int BUTTON_WIDTH = 140; + public Bindable OverlayState = new Bindable(); - protected static readonly Vector2 SHEAR = new Vector2(SHEAR_WIDTH / button_height, 0); + protected static readonly Vector2 BUTTON_SHEAR = new Vector2(SHEAR_WIDTH / BUTTON_HEIGHT, 0); [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; @@ -70,68 +71,73 @@ namespace osu.Game.Screens.Select.FooterV2 public FooterButtonV2() { - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Radius = 4, - // Figma says 50% opacity, but it does not match up visually if taken at face value, and looks bad. - Colour = Colour4.Black.Opacity(0.25f), - Offset = new Vector2(0, 2), - }; - Shear = SHEAR; - Size = new Vector2(button_width, button_height); - Masking = true; - CornerRadius = corner_radius; - Children = new Drawable[] - { - backgroundBox = new Box - { - RelativeSizeAxes = Axes.Both - }, + Size = new Vector2(BUTTON_WIDTH, BUTTON_HEIGHT); + Margin = new MarginPadding { Horizontal = SHEAR_WIDTH / 2f }; - // For elements that should not be sheared. - new Container + Child = new Container + { + EdgeEffect = new EdgeEffectParameters { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Shear = -SHEAR, - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - TextContainer = new Container - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Y = 42, - AutoSizeAxes = Axes.Both, - Child = text = new OsuSpriteText - { - // figma design says the size is 16, but due to the issues with font sizes 19 matches better - Font = OsuFont.TorusAlternate.With(size: 19), - AlwaysPresent = true - } - }, - icon = new SpriteIcon - { - Y = 12, - Size = new Vector2(20), - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre - }, - } + Type = EdgeEffectType.Shadow, + Radius = 4, + // Figma says 50% opacity, but it does not match up visually if taken at face value, and looks bad. + Colour = Colour4.Black.Opacity(0.25f), + Offset = new Vector2(0, 2), }, - new Container + Shear = BUTTON_SHEAR, + Masking = true, + CornerRadius = CORNER_RADIUS, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - Shear = -SHEAR, - Anchor = Anchor.BottomCentre, - Origin = Anchor.Centre, - Y = -corner_radius, - Size = new Vector2(120, 6), - Masking = true, - CornerRadius = 3, - Child = bar = new Box + backgroundBox = new Box { + RelativeSizeAxes = Axes.Both + }, + // For elements that should not be sheared. + new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Shear = -BUTTON_SHEAR, RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + TextContainer = new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Y = 42, + AutoSizeAxes = Axes.Both, + Child = text = new OsuSpriteText + { + // figma design says the size is 16, but due to the issues with font sizes 19 matches better + Font = OsuFont.TorusAlternate.With(size: 19), + AlwaysPresent = true + } + }, + icon = new SpriteIcon + { + Y = 12, + Size = new Vector2(20), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre + }, + } + }, + new Container + { + Shear = -BUTTON_SHEAR, + Anchor = Anchor.BottomCentre, + Origin = Anchor.Centre, + Y = -CORNER_RADIUS, + Size = new Vector2(120, 6), + Masking = true, + CornerRadius = 3, + Child = bar = new Box + { + RelativeSizeAxes = Axes.Both, + } } } }; From d7b658ec76dff55ec15cd2751861fe04ef414668 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 10 May 2024 02:21:06 +0300 Subject: [PATCH 3867/4092] Add similar test steps to song select footer test scene --- .../SongSelect/TestSceneSongSelectFooterV2.cs | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectFooterV2.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectFooterV2.cs index 013bad55bc..0c7725db5a 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectFooterV2.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectFooterV2.cs @@ -1,6 +1,8 @@ // 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.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -10,7 +12,9 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Testing; using osu.Game.Overlays; using osu.Game.Overlays.Mods; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.Select.FooterV2; using osuTK.Input; @@ -47,7 +51,7 @@ namespace osu.Game.Tests.Visual.SongSelect overlay = new DummyOverlay() }; - footer.AddButton(modsButton = new FooterButtonModsV2(), overlay); + footer.AddButton(modsButton = new FooterButtonModsV2 { Current = SelectedMods }, overlay); footer.AddButton(randomButton = new FooterButtonRandomV2 { NextRandom = () => nextRandomCalled = true, @@ -61,9 +65,28 @@ namespace osu.Game.Tests.Visual.SongSelect [SetUpSteps] public void SetUpSteps() { + AddStep("clear mods", () => SelectedMods.Value = Array.Empty()); AddStep("set beatmap", () => Beatmap.Value = CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo))); } + [Test] + public void TestMods() + { + AddStep("one mod", () => SelectedMods.Value = new List { new OsuModHidden() }); + AddStep("two mods", () => SelectedMods.Value = new List { new OsuModHidden(), new OsuModHardRock() }); + AddStep("three mods", () => SelectedMods.Value = new List { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime() }); + AddStep("four mods", () => SelectedMods.Value = new List { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime(), new OsuModClassic() }); + AddStep("five mods", () => SelectedMods.Value = new List { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime(), new OsuModClassic(), new OsuModDifficultyAdjust() }); + + AddStep("clear mods", () => SelectedMods.Value = Array.Empty()); + AddWaitStep("wait", 3); + AddStep("one mod", () => SelectedMods.Value = new List { new OsuModHidden() }); + + AddStep("clear mods", () => SelectedMods.Value = Array.Empty()); + AddWaitStep("wait", 3); + AddStep("five mods", () => SelectedMods.Value = new List { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime(), new OsuModClassic(), new OsuModDifficultyAdjust() }); + } + [Test] public void TestShowOptions() { From 49692e168eb8a7a72b93a6bb40f83f12575c4976 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 10 May 2024 02:21:24 +0300 Subject: [PATCH 3868/4092] Fix `ModDisplay` expanding on load with "always contracted/expanded" modes This is especially visible when reloading `SongSelectFooterV2` while multiple mods are already selected. The mods will appear expanded then contract. --- osu.Game/Screens/Play/HUD/ModDisplay.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index 75db720603..f8c8232d45 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -60,6 +60,9 @@ namespace osu.Game.Screens.Play.HUD Current.BindValueChanged(updateDisplay, true); iconsContainer.FadeInFromZero(fade_duration, Easing.OutQuint); + + if (ExpansionMode == ExpansionMode.AlwaysExpanded || ExpansionMode == ExpansionMode.AlwaysContracted) + FinishTransforms(true); } private void updateDisplay(ValueChangedEvent> mods) From bff34a1c04f8151c053a1c32b31043b26f4e520c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 10 May 2024 06:38:48 +0300 Subject: [PATCH 3869/4092] Add localisation and tooltip for mods count text --- .../SongSelect/TestSceneSongSelectFooterV2.cs | 6 ++ .../TestSceneFooterButtonModsV2.cs | 4 ++ .../Localisation/FooterButtonModsV2Strings.cs | 19 ++++++ .../Select/FooterV2/FooterButtonModsV2.cs | 62 +++++++++++++++++-- 4 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 osu.Game/Localisation/FooterButtonModsV2Strings.cs diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectFooterV2.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectFooterV2.cs index 0c7725db5a..93402e42ce 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectFooterV2.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectFooterV2.cs @@ -78,6 +78,12 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("four mods", () => SelectedMods.Value = new List { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime(), new OsuModClassic() }); AddStep("five mods", () => SelectedMods.Value = new List { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime(), new OsuModClassic(), new OsuModDifficultyAdjust() }); + AddStep("modified", () => SelectedMods.Value = new List { new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } }); + AddStep("modified + one", () => SelectedMods.Value = new List { new OsuModHidden(), new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } }); + AddStep("modified + two", () => SelectedMods.Value = new List { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } }); + AddStep("modified + three", () => SelectedMods.Value = new List { new OsuModHidden(), new OsuModHardRock(), new OsuModClassic(), new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } }); + AddStep("modified + four", () => SelectedMods.Value = new List { new OsuModHidden(), new OsuModHardRock(), new OsuModClassic(), new OsuModDifficultyAdjust(), new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } }); + AddStep("clear mods", () => SelectedMods.Value = Array.Empty()); AddWaitStep("wait", 3); AddStep("one mod", () => SelectedMods.Value = new List { new OsuModHidden() }); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonModsV2.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonModsV2.cs index 7e8bba6573..af2eea6062 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonModsV2.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonModsV2.cs @@ -44,6 +44,10 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("four mods", () => changeMods(new List { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime(), new OsuModClassic() })); AddStep("five mods", () => changeMods(new List { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime(), new OsuModClassic(), new OsuModDifficultyAdjust() })); + AddStep("modified", () => changeMods(new List { new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } })); + AddStep("modified + one", () => changeMods(new List { new OsuModHidden(), new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } })); + AddStep("modified + two", () => changeMods(new List { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } })); + AddStep("clear mods", () => changeMods(Array.Empty())); AddWaitStep("wait", 3); AddStep("one mod", () => changeMods(new List { new OsuModHidden() })); diff --git a/osu.Game/Localisation/FooterButtonModsV2Strings.cs b/osu.Game/Localisation/FooterButtonModsV2Strings.cs new file mode 100644 index 0000000000..2cb297d8ef --- /dev/null +++ b/osu.Game/Localisation/FooterButtonModsV2Strings.cs @@ -0,0 +1,19 @@ +// 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.Localisation; + +namespace osu.Game.Localisation +{ + public static class FooterButtonModsV2Strings + { + private const string prefix = @"osu.Game.Resources.Localisation.FooterButtonModsV2"; + + /// + /// "{0} mods" + /// + public static LocalisableString Mods(int count) => new TranslatableString(getKey(@"mods"), @"{0} mods", count); + + private static string getKey(string key) => $@"{prefix}:{key}"; + } +} diff --git a/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs b/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs index f9d923ddcf..7beadd1576 100644 --- a/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs +++ b/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs @@ -9,12 +9,14 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Game.Configuration; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Localisation; using osu.Game.Overlays; @@ -155,15 +157,16 @@ namespace osu.Game.Screens.Select.FooterV2 Origin = Anchor.Centre, Shear = -barShear, Scale = new Vector2(0.6f), - Current = { Value = new List { new ModCinema() } }, + Current = { BindTarget = Current }, ExpansionMode = ExpansionMode.AlwaysContracted, }, - modCountText = new OsuSpriteText + modCountText = new ModCountText { Anchor = Anchor.Centre, Origin = Anchor.Centre, Shear = -barShear, Font = OsuFont.Torus.With(size: 14 * torus_scale_factor, weight: FontWeight.Bold), + Mods = { BindTarget = Current }, } } }, @@ -216,13 +219,11 @@ namespace osu.Game.Screens.Select.FooterV2 { if (Current.Value.Count >= 5) { - modCountText.Text = $"{Current.Value.Count} MODS"; modCountText.FadeIn(duration, easing); modDisplay.FadeOut(duration, easing); } else { - modDisplay.Current.Value = Current.Value; modDisplay.FadeIn(duration, easing); modCountText.FadeOut(duration, easing); } @@ -257,5 +258,58 @@ namespace osu.Game.Screens.Select.FooterV2 else MultiplierText.FadeColour(Color4.White, duration, easing); } + + private partial class ModCountText : OsuSpriteText, IHasCustomTooltip> + { + public readonly Bindable> Mods = new Bindable>(); + + protected override void LoadComplete() + { + base.LoadComplete(); + Mods.BindValueChanged(v => Text = FooterButtonModsV2Strings.Mods(v.NewValue.Count).ToUpper(), true); + } + + public ITooltip> GetCustomTooltip() => new ModTooltip(); + + public IReadOnlyList? TooltipContent => Mods.Value; + + public partial class ModTooltip : VisibilityContainer, ITooltip> + { + private ModDisplay extendedModDisplay = null!; + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + AutoSizeAxes = Axes.Both; + CornerRadius = CORNER_RADIUS; + Masking = true; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background5, + }, + extendedModDisplay = new ModDisplay + { + Margin = new MarginPadding { Vertical = 2f, Horizontal = 10f }, + Scale = new Vector2(0.6f), + ExpansionMode = ExpansionMode.AlwaysExpanded, + }, + }; + } + + public void SetContent(IReadOnlyList content) + { + extendedModDisplay.Current.Value = content; + } + + public void Move(Vector2 pos) => Position = pos; + + protected override void PopIn() => this.FadeIn(240, Easing.OutQuint); + protected override void PopOut() => this.FadeOut(240, Easing.OutQuint); + } + } } } From c1ea9d2c9fd5c63f1c43830870e74a0bb090139a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 10 May 2024 06:39:06 +0300 Subject: [PATCH 3870/4092] Adjust unranked badge height --- osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs b/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs index 7beadd1576..ba4abc4025 100644 --- a/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs +++ b/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs @@ -78,7 +78,8 @@ namespace osu.Game.Screens.Select.FooterV2 Origin = Anchor.BottomLeft, Shear = barShear, CornerRadius = CORNER_RADIUS, - AutoSizeAxes = Axes.Both, + AutoSizeAxes = Axes.X, + Height = bar_height, Masking = true, BorderColour = Color4.White, BorderThickness = 2f, @@ -91,9 +92,12 @@ namespace osu.Game.Screens.Select.FooterV2 }, new OsuSpriteText { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Shear = -barShear, Text = ModSelectOverlayStrings.Unranked.ToUpper(), - Margin = new MarginPadding { Horizontal = 15, Vertical = 5 }, + Margin = new MarginPadding { Horizontal = 15 }, + UseFullGlyphHeight = false, Font = OsuFont.Torus.With(size: 14 * torus_scale_factor, weight: FontWeight.Bold), Colour = Color4.Black, } @@ -135,6 +139,7 @@ namespace osu.Game.Screens.Select.FooterV2 Anchor = Anchor.Centre, Origin = Anchor.Centre, Shear = -barShear, + UseFullGlyphHeight = false, Font = OsuFont.Torus.With(size: 14 * torus_scale_factor, weight: FontWeight.Bold) } }, From 6ddf8f849805e203fbfb0469cce923c0e9af2f68 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 10 May 2024 06:47:48 +0300 Subject: [PATCH 3871/4092] Few cleanups --- osu.Game/Screens/Play/HUD/ModDisplay.cs | 2 +- .../Screens/Select/FooterV2/FooterButtonModsV2.cs | 7 +++---- osu.Game/Screens/Select/FooterV2/FooterButtonV2.cs | 11 +++++------ osu.Game/Screens/Select/FooterV2/FooterV2.cs | 2 +- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index f8c8232d45..b37d41e7a2 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -20,7 +20,6 @@ namespace osu.Game.Screens.Play.HUD /// public partial class ModDisplay : CompositeDrawable, IHasCurrentValue> { - private readonly bool showExtendedInformation; private const int fade_duration = 1000; public ExpansionMode ExpansionMode = ExpansionMode.ExpandOnHover; @@ -38,6 +37,7 @@ namespace osu.Game.Screens.Play.HUD } } + private readonly bool showExtendedInformation; private readonly FillFlowContainer iconsContainer; public ModDisplay(bool showExtendedInformation = true) diff --git a/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs b/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs index ba4abc4025..bb259898ea 100644 --- a/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs +++ b/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs @@ -16,7 +16,6 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Game.Configuration; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Localisation; using osu.Game.Overlays; @@ -61,7 +60,7 @@ namespace osu.Game.Screens.Select.FooterV2 { const float bar_shear_width = 7f; const float bar_height = 37f; - const float display_rel_width = 0.65f; + const float mod_display_portion = 0.65f; var barShear = new Vector2(bar_shear_width / bar_height, 0); @@ -132,7 +131,7 @@ namespace osu.Game.Screens.Select.FooterV2 Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Both, - Width = 1f - display_rel_width, + Width = 1f - mod_display_portion, Masking = true, Child = MultiplierText = new OsuSpriteText { @@ -147,7 +146,7 @@ namespace osu.Game.Screens.Select.FooterV2 { CornerRadius = CORNER_RADIUS, RelativeSizeAxes = Axes.Both, - Width = display_rel_width, + Width = mod_display_portion, Masking = true, Children = new Drawable[] { diff --git a/osu.Game/Screens/Select/FooterV2/FooterButtonV2.cs b/osu.Game/Screens/Select/FooterV2/FooterButtonV2.cs index a7bd1b8abd..2c841f6ae6 100644 --- a/osu.Game/Screens/Select/FooterV2/FooterButtonV2.cs +++ b/osu.Game/Screens/Select/FooterV2/FooterButtonV2.cs @@ -27,15 +27,15 @@ namespace osu.Game.Screens.Select.FooterV2 private const int transition_length = 500; // This should be 12 by design, but an extra allowance is added due to the corner radius specification. - public const float SHEAR_WIDTH = 13.5f; + private const float shear_width = 13.5f; - public const int CORNER_RADIUS = 10; - public const int BUTTON_HEIGHT = 90; - public const int BUTTON_WIDTH = 140; + protected const int CORNER_RADIUS = 10; + protected const int BUTTON_HEIGHT = 90; + protected const int BUTTON_WIDTH = 140; public Bindable OverlayState = new Bindable(); - protected static readonly Vector2 BUTTON_SHEAR = new Vector2(SHEAR_WIDTH / BUTTON_HEIGHT, 0); + protected static readonly Vector2 BUTTON_SHEAR = new Vector2(shear_width / BUTTON_HEIGHT, 0); [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; @@ -72,7 +72,6 @@ namespace osu.Game.Screens.Select.FooterV2 public FooterButtonV2() { Size = new Vector2(BUTTON_WIDTH, BUTTON_HEIGHT); - Margin = new MarginPadding { Horizontal = SHEAR_WIDTH / 2f }; Child = new Container { diff --git a/osu.Game/Screens/Select/FooterV2/FooterV2.cs b/osu.Game/Screens/Select/FooterV2/FooterV2.cs index 0529f0d082..370c28e2a5 100644 --- a/osu.Game/Screens/Select/FooterV2/FooterV2.cs +++ b/osu.Game/Screens/Select/FooterV2/FooterV2.cs @@ -72,7 +72,7 @@ namespace osu.Game.Screens.Select.FooterV2 Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, Direction = FillDirection.Horizontal, - Spacing = new Vector2(-FooterButtonV2.SHEAR_WIDTH + 7, 0), + Spacing = new Vector2(7, 0), AutoSizeAxes = Axes.Both } }; From e5b2023155006c15ecb7673d4395c612e400bce3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 10 May 2024 07:05:32 +0300 Subject: [PATCH 3872/4092] Remove unappealing fade transition between mod display and count text Fading out the mod display looks quite ugly, since alpha is applied per mod sprite, introducing bad visual on the intersection between mods. --- .../Screens/Select/FooterV2/FooterButtonModsV2.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs b/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs index bb259898ea..ed558d513b 100644 --- a/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs +++ b/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs @@ -221,16 +221,13 @@ namespace osu.Game.Screens.Select.FooterV2 } else { + modDisplay.Hide(); + modCountText.Hide(); + if (Current.Value.Count >= 5) - { - modCountText.FadeIn(duration, easing); - modDisplay.FadeOut(duration, easing); - } + modCountText.Show(); else - { - modDisplay.FadeIn(duration, easing); - modCountText.FadeOut(duration, easing); - } + modDisplay.Show(); if (Current.Value.Any(m => !m.Ranked)) { From f5ab9a7ff863a2f32870b6d517448b4c8a4b18c6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 10 May 2024 07:12:28 +0300 Subject: [PATCH 3873/4092] Make unranked indicator orange to match mod select overlay --- osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs b/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs index ed558d513b..3937f1e42a 100644 --- a/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs +++ b/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs @@ -86,7 +86,7 @@ namespace osu.Game.Screens.Select.FooterV2 { new Box { - Colour = colours.Red2, + Colour = colours.Orange2, RelativeSizeAxes = Axes.Both, }, new OsuSpriteText From 0392f7b04cfb913f5fe23ddc0da3726ffcbb7d80 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 10 May 2024 07:36:59 +0300 Subject: [PATCH 3874/4092] Add tooltip to unranked indicator --- osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs b/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs index 3937f1e42a..08b0407a79 100644 --- a/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs +++ b/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -70,7 +71,7 @@ namespace osu.Game.Screens.Select.FooterV2 AddRange(new[] { - UnrankedBadge = new Container + UnrankedBadge = new ContainerWithTooltip { Position = new Vector2(BUTTON_WIDTH + 5f, -5f), Depth = float.MaxValue, @@ -82,6 +83,7 @@ namespace osu.Game.Screens.Select.FooterV2 Masking = true, BorderColour = Color4.White, BorderThickness = 2f, + TooltipText = ModSelectOverlayStrings.UnrankedExplanation, Children = new Drawable[] { new Box @@ -312,5 +314,10 @@ namespace osu.Game.Screens.Select.FooterV2 protected override void PopOut() => this.FadeOut(240, Easing.OutQuint); } } + + private partial class ContainerWithTooltip : Container, IHasTooltip + { + public LocalisableString TooltipText { get; set; } + } } } From b6d6a8940bfb15313b8787185f45463391a79f1f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 10 May 2024 08:24:39 +0300 Subject: [PATCH 3875/4092] Improve animation of new song select footer buttons --- .../Screens/Select/FooterV2/FooterButtonV2.cs | 66 +++++++++---------- 1 file changed, 31 insertions(+), 35 deletions(-) diff --git a/osu.Game/Screens/Select/FooterV2/FooterButtonV2.cs b/osu.Game/Screens/Select/FooterV2/FooterButtonV2.cs index 2c841f6ae6..21337327fa 100644 --- a/osu.Game/Screens/Select/FooterV2/FooterButtonV2.cs +++ b/osu.Game/Screens/Select/FooterV2/FooterButtonV2.cs @@ -24,8 +24,6 @@ namespace osu.Game.Screens.Select.FooterV2 { public partial class FooterButtonV2 : OsuClickableContainer, IKeyBindingHandler { - private const int transition_length = 500; - // This should be 12 by design, but an extra allowance is added due to the corner radius specification. private const float shear_width = 13.5f; @@ -68,6 +66,7 @@ namespace osu.Game.Screens.Select.FooterV2 protected Container TextContainer; private readonly Box bar; private readonly Box backgroundBox; + private readonly Box flashLayer; public FooterButtonV2() { @@ -137,8 +136,15 @@ namespace osu.Game.Screens.Select.FooterV2 { RelativeSizeAxes = Axes.Both, } - } - } + }, + flashLayer = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.White.Opacity(0.9f), + Blending = BlendingParameters.Additive, + Alpha = 0, + }, + }, }; } @@ -154,7 +160,15 @@ namespace osu.Game.Screens.Select.FooterV2 public GlobalAction? Hotkey; - private bool handlingMouse; + protected override bool OnClick(ClickEvent e) + { + if (Enabled.Value) + Flash(); + + return base.OnClick(e); + } + + protected virtual void Flash() => flashLayer.FadeOutFromOne(800, Easing.OutQuint); protected override bool OnHover(HoverEvent e) { @@ -162,20 +176,6 @@ namespace osu.Game.Screens.Select.FooterV2 return true; } - protected override bool OnMouseDown(MouseDownEvent e) - { - handlingMouse = true; - updateDisplay(); - return base.OnMouseDown(e); - } - - protected override void OnMouseUp(MouseUpEvent e) - { - handlingMouse = false; - updateDisplay(); - base.OnMouseUp(e); - } - protected override void OnHoverLost(HoverLostEvent e) => updateDisplay(); public virtual bool OnPressed(KeyBindingPressEvent e) @@ -190,27 +190,23 @@ namespace osu.Game.Screens.Select.FooterV2 private void updateDisplay() { - Color4 backgroundColour = colourProvider.Background3; + Color4 backgroundColour = OverlayState.Value == Visibility.Visible ? buttonAccentColour : colourProvider.Background3; + Color4 textColour = OverlayState.Value == Visibility.Visible ? colourProvider.Background6 : colourProvider.Content1; + Color4 accentColour = OverlayState.Value == Visibility.Visible ? colourProvider.Background6 : buttonAccentColour; if (!Enabled.Value) - { - backgroundColour = colourProvider.Background3.Darken(0.4f); - } - else - { - if (OverlayState.Value == Visibility.Visible) - backgroundColour = buttonAccentColour.Darken(0.5f); + backgroundColour = backgroundColour.Darken(1f); + else if (IsHovered) + backgroundColour = backgroundColour.Lighten(0.2f); - if (IsHovered) - { - backgroundColour = backgroundColour.Lighten(0.3f); + backgroundBox.FadeColour(backgroundColour, 150, Easing.OutQuint); - if (handlingMouse) - backgroundColour = backgroundColour.Lighten(0.3f); - } - } + if (!Enabled.Value) + textColour = textColour.Opacity(0.6f); - backgroundBox.FadeColour(backgroundColour, transition_length, Easing.OutQuint); + text.FadeColour(textColour, 150, Easing.OutQuint); + icon.FadeColour(accentColour, 150, Easing.OutQuint); + bar.FadeColour(accentColour, 150, Easing.OutQuint); } } } From 09c52f03d8ea7ed896721a9ce629ec58d8d67e0c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 10 May 2024 08:25:16 +0300 Subject: [PATCH 3876/4092] Fix "options" button behaving weirdly when clicking on it while open --- .../Select/FooterV2/BeatmapOptionsPopover.cs | 6 +-- .../Select/FooterV2/FooterButtonOptionsV2.cs | 43 ++++++++++--------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/osu.Game/Screens/Select/FooterV2/BeatmapOptionsPopover.cs b/osu.Game/Screens/Select/FooterV2/BeatmapOptionsPopover.cs index f81036f745..648f536bb1 100644 --- a/osu.Game/Screens/Select/FooterV2/BeatmapOptionsPopover.cs +++ b/osu.Game/Screens/Select/FooterV2/BeatmapOptionsPopover.cs @@ -188,9 +188,9 @@ namespace osu.Game.Screens.Select.FooterV2 protected override void UpdateState(ValueChangedEvent state) { base.UpdateState(state); - - if (state.NewValue == Visibility.Hidden) - footerButton.IsActive.Value = false; + // intentionally scheduling to let the button have a chance whether the popover will hide from clicking the button or clicking outside + // see the "hidingFromClick" field in FooterButtonOptionsV2. + Schedule(() => footerButton.OverlayState.Value = state.NewValue); } } } diff --git a/osu.Game/Screens/Select/FooterV2/FooterButtonOptionsV2.cs b/osu.Game/Screens/Select/FooterV2/FooterButtonOptionsV2.cs index a1559d32dc..2ed8480b46 100644 --- a/osu.Game/Screens/Select/FooterV2/FooterButtonOptionsV2.cs +++ b/osu.Game/Screens/Select/FooterV2/FooterButtonOptionsV2.cs @@ -2,12 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Input.Bindings; @@ -15,7 +15,10 @@ namespace osu.Game.Screens.Select.FooterV2 { public partial class FooterButtonOptionsV2 : FooterButtonV2, IHasPopover { - public readonly BindableBool IsActive = new BindableBool(); + /// + /// True if the next click is for hiding the popover. + /// + private bool hidingFromClick; [BackgroundDependencyLoader] private void load(OsuColour colour) @@ -25,31 +28,29 @@ namespace osu.Game.Screens.Select.FooterV2 AccentColour = colour.Purple1; Hotkey = GlobalAction.ToggleBeatmapOptions; - Action = () => IsActive.Toggle(); + Action = () => + { + if (OverlayState.Value == Visibility.Hidden && !hidingFromClick) + this.ShowPopover(); + + hidingFromClick = false; + }; } - protected override void LoadComplete() + protected override bool OnMouseDown(MouseDownEvent e) { - base.LoadComplete(); + if (OverlayState.Value == Visibility.Visible) + hidingFromClick = true; - IsActive.BindValueChanged(active => - { - OverlayState.Value = active.NewValue ? Visibility.Visible : Visibility.Hidden; - }); + return base.OnMouseDown(e); + } - OverlayState.BindValueChanged(state => - { - switch (state.NewValue) - { - case Visibility.Hidden: - this.HidePopover(); - break; + protected override void Flash() + { + if (hidingFromClick) + return; - case Visibility.Visible: - this.ShowPopover(); - break; - } - }); + base.Flash(); } public Popover GetPopover() => new BeatmapOptionsPopover(this); From bc6f3b8cb41570e8a8d85491abcd5e75b096c064 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 May 2024 13:48:08 +0800 Subject: [PATCH 3877/4092] Update iOS logo --- ...0-5cbe0121-ed68-414f-9ddc-dd993ac97e62.png | Bin 453879 -> 394881 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/300076680-5cbe0121-ed68-414f-9ddc-dd993ac97e62.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/300076680-5cbe0121-ed68-414f-9ddc-dd993ac97e62.png index 9287a71040091fb7a249018bff4133b91b12e1fc..7b62835cdcfa85d60814f17a89653e2b389a520f 100644 GIT binary patch literal 394881 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 z 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

DatA2W^x6OxQqa_*YszP3 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

dN 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

~Hp0Ng>X@!#&} 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 p)(cq3~%~<$) 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
7teB@p7cU2&ME)tf)cjk^ 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 z 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∓U&Os)M%-|e`T>a3l!b}};EaI2Y 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⪻Vj#%ZZO(pEsHP1rtSEfo>#2PAE%NO?i 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 literal 453879 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 K)~(TTivAB8ZcmZ` From c25ed2cddde9764306fbbdcd490101eb6fb72aad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 May 2024 14:12:32 +0800 Subject: [PATCH 3878/4092] Update desktop logo --- osu.Desktop/lazer.ico | Bin 76552 -> 76679 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/osu.Desktop/lazer.ico b/osu.Desktop/lazer.ico index a3280f0de0b6a935309c84594e8b83cdb7d2e43f..24c1c29ba269aaeb38a9b9c604a1c364c986a753 100644 GIT binary patch literal 76679 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 literal 76552 zcmZ^K1ymf(*5JSZgKG$G!JXjlZi8FUz~IiH!94^9*WjLD2@u@ff=eI}Z14mbAOQl* z^1b)=|Nq^y+kNWPt*do!cURS^TMYo90MG#cTqpo~z{Dv4p!@{$@%_8)$N~Tmo&f;# z^#5+N5di=Jq$mJF!vD~pM2lHa0B?1)Rq(JWv7bco)KnGq{-OR;FfpDgbMI>VrwYwM zPD>5|_?(P$Z-f5Srngnq(*gj3o=k*C0|0kVsPIDo!1u}Yu{8i7o&x}odFFTMNeCFL*Y)pyLIRxr+mgG_f82UY8`aWzqNa)Y@0JoQMLUr6{rLjO1A|0!wu|CRh7l>bOd z^8VxQ|8W1`vHchJDY(+ulDz-*pro<)DO(l+09k;VqMShh%Fk}&l)4q)gI0+@U&&I( z04RiLa*_1xZ*`~C`b#4|vvLCG9+}mZ>nA>XjiNQuG#~F2Z zbl#S#;wz;9NMysG#3KNh>%o8hL^mFhk1h>o-w&XFx(}}K+5kaCKQ^^Fe|=ejSsm*6 zZPa7<34N-Gu)zE2Y;6qer2Mwj_kW@qKzN$nv>dl)^}8b@BVEV-vX2_Lx562{DDLKAHuJOVYdnC-Jh`SQl?%-0(e0&_GB)2pOUa{E!M) zOeocB?E{At>x%H1{w=j(i3YiC^a}qCNO{Bt$`-#PuJ6rvW{k(vW7BCX*03Z=nj+@M zny)YUJ{gX20~t7@RrY$BkvSM>^PQ)pi+!hnb8NQW>GLzf6aVo|2Jk^ulNB#GF08Hv zCknMDylSr~8yRu$5AS6_#$%whkaeCCJ8yk)cFfj^+5N0nRaJ&;z6XzQvWK}`a~)VU zz%J3J2@2C3gw8DjF{1za8-wNtm_0jzeM$!23rAr_u0Obn3JRmzEm}>1qZz~D=WX|` zdvqDdMhqZEyoEoJ;3o-yxpl?Ig{ClvN@Ux;6apHQbt8c2bPvYk!n&fkJ)0CDywAMP z79^)OZad*g!SDlx;}om<2A#Gq9Bmpuynp7MfK)G<)rek+-o+mz*fS*!R(hNfB>ZIf z&G+ELKOvHQRt*a)o89C^Vg>&G-8$Y|_~+{5qmsYO^SHC|FpI|;L|9`dN6K@*!zbM| zY}zzM3SBc}VTy&_QX2OgtCs3*(J*dQ2jFG~(&tuYQMONRr~bVx@X5)X3B4*6W3EdX zGpW||0BnzyZUp64ZMXx@H`@Et)8E$j7^^gZ>5`g`USvw2=%Q`%Ok&uVd&mg}>vYDo z04Ky#Z@aN@pT;%gdSNoC&%exdj6j>c=d;c2?ne+oW!2=*$H2XDrpl^`tDYEsAqk7h zKOFhF>;fRN$th`C$*x)&FugQAvI@}&>?hayA^~tMD;M7l(-{uae*_}9{st~TlKN9| zxG?v?`3lLkUFSSMD z`ofF1D?;?HTy+^qnZBc6(0J}@cy_esKNm<8#C)Ys;Y5vrX|vDHcp>6B^B0_DYa;op z6Z;1y9J8ax&8bBR~MesxqLM45OsG zN4qi(Tk5D-&ITFzkZ6P<0BRPW4dl0O*}&!Z25@Hk`6!% zTu}pdYjwev7!>xGH#XCdEAUN*uyOo9!p+7qmvHa|BK`PWCRk3OD+@8Io!$Srf`67j z%=yzPjJ8RfrYop}%M0tB&$mjN8Q71(>)F$5oewmk>?o-PlEoX{WhN1= zcPz~C&Q$lN1GWONSMkK3p-*WC|opTN#CwYa<7|zFuiq zu7N*9-IQClG2+SwO70cr&RPtF^pv92 z*1LTeIx$ytIjGWD;rEUghgxO?D$$RaJ!^AylqFnnI^}WX%jtRTVcK?m-1z5LYsT>R z`ioon(W@Or=$oMfU|8)ra-P2Looa)G$l7T`Ao>`+=_&H#eJ>9(^l2I*8?JMi>A9*F zmuh;jHTxkGR*P#Xz6X@N`!#i?nY;W;gu1D@BMgu6>l7Fmvm*2uo?z}AIkeu7`xl_-b)QHd%if3;876dZ$!$ ztl8hC1=~{7>c!-ji$a2j5@JtQUEoW>`<}VtRKC>6%T-h zC}IVj{=}nhZ^w@3r(+fOu!xFi#U^&TOPR)nx_JHWh=Oc}v}Q?pw11v*@@!Lux%Dd` zkVW^%-$7=0b|+R$-Ila=ki@AwYQ*-_G}V8m<;K;JQ9<`u&K2?}*L%Ojp?PTK;Wir_ z1O`{-+qW2>ZI3ShXzHB*@m<208GbTqCDO#1+7u)^3eaIN%={5ScdJ+m6s{0b56yUQ z9P{&KXA)lCX( z&yV=Z`^qNm;{Y1uNWBrz98`)yw3t&D)e4{yd@+t0|}_Ji_`+!ks&x!aVu_*91?J3;7q( zaJwYCkn!=YZ2M_Bu$hkhbdMgA&~S2N^`@&<@^{ytKM>!tDNo*fh;&nreD6`l)MQYm|D1V&N{K zS}6>KG3HvC|B;WHM_B|zV9UEW{(=#3G}T7!IQBJD9`1!_wuOf_ldL)FYh+FNW`ANG zucIK~LrY@2|Vj-lS+N8A*SQ3|$3#M&lxTn{h@DL?Hd5u>d zDwHfZh)yGIgpq;%(>i3Ui@%|YnBa`vXN~x0*dH3mSqND${gyV0KPbR`D=cIBY2ejr zcTHGPtgxeBjGOTYF)Cz6W+KRjF1)|ax0)Wy)WeX$6o~wI6*Op~5AKT^Dxb~@t%M$>E2>eows*EE^mEDQFW#fNEOk)i} zf8Py4X1dMrx=vae0}g!2f?Uq?hwj4QVXr#=#ty6`PWpu|8LP$)DgMNydqa1;Vh7+7LC) zuK{wU4|4RAGRL^c99V#M(<-n9LsF}O1o%&`~Bs&J@ zjY)M?^SySzOcv~s%lZ1acYdTHh*e=L1KA2k!k*Tq2fgrY^m@s^-eMQV$r%fq!Hu;K zhmeQ#THjw6yFa>Ne-=&uQpgb89SXl>J2aCZ3WRcTN+@9EI^hqnUGFwgq6GWN5~#7} z|7<&T{DPa;cBn{NSt64`U|j*+gKOn@!hqTJnGszRGs7GL1?mF2YbZnf4uQ;tdl4EA z1XIP8ZAbH~Lb#w}&pw9Fxj=2a8i;&1S4U5;bbvMgUAuwG260~71}4G682^A-^3c0daWpmo zgv$Xdq&uadD2?lZ5!UlUYOAHhyPKPyN9K!vJ=YKF-DSjkUbx(L`s=@&<4A&r z-*Uw+O^(sVGlm$jpTQ-p9v2o~Kjv5V9xr+Ws=30>RvSa%84kOmTko``1S;w|fc`6I z@^@;`5dUDVZ%#}JFuKoU*#$v5eet=&edx)-_WT_Cf~wwfJmNwSg`+$<%Z`?&G*G<7 z3(nN@Ty*^Iny(Ee3UaT045zEgV|*y|*ylt8oi9TyU9s4(CZNA9OXJHL$k*llKMWq; zzXSvj60YBcU!olSUIRk5>qN2?=mIrN7fE8t#=LmW!zPdm$14L*=T9O}KR*VfdvX zdi0H;r=Eq`9+xTm@AOI7$Z-?6yamp=MjG3;8w#(@}oJ7XJ{mL@j- zeEH(LMjOckR!#KC`Z77G^YL)P`saCZX==UPzdzyEI<1^qp{RboI^|mcY_QX*sa%=Kl1Kv%$Q=8NI9SZZ zE+#nuK^+0jvh5NLF!rqat}#E|#e)~dK*5Wy8+qv0%vO%S(Fd3#LQ7Ls64fiux5>UH zpH5nAfs`p`P6sX8&_k<&;a4*Z*Yn`a%j578Ijq=ap%&~R3fwDPlvaW&e69?o4Iasx z&u=Dvy!j?o*t&ZQz=%NVxt)KcLeu$MGxFi(Dz%RXQYH;aaeH}(VK{wP)q?=iAp(~V zphbgHZa=YhKP8{0fR31Pm~I@Msv|g5H6XD=(ESkYK{sCYeLfGl z*r#s&yncn-``8Ieif|Q-#x3A|_4md>7QC2Z%ms5%9EumIo{Wkrfibx6nP~b;CG!<5 z#u#AhC0aW#FVpR14_koKEA*c#9Wd>ynkm~Ff4)H$>}`#11~E)yWHDnT6E+dwl%*h+ zyrdfNq3ku)%KMOuUQXoMgwK3(;IFB1zP2y^_cP5a#%*4~t4)8W65ScccWjO9Z_juR zneUpp@CLu`Nhg*ykGTGvTN2mzi*pn&<`%)tA_;3DuSdT%rMg)}8=2gGp>~ioE6|R; zd7(%xC-LhCRr3*3r41AC_H|39`#$RY!NS~~AfK3yrc*bqCpJ3Mb5gz?9-tgg2gfkO zEBMHlGNSOxxr;Gza)HBg*KkJQcMQZUNtBEzA^p%PSPXvNn z%eOwY8%^nIL?L<@r4sLBzDLdh;|2wL&Qcx!G>o4*5tr^G{qQT;ZZ{SoPDw32q9kw5ol?Pv zwZ$-&$X+^udAfFbJ|jvdqiAo-H%fL`q&87-lPMZQvc+4Cbu!JeQf2UY&u^LtJ06>} zmfcl_tIvf-@B#^L%!ik6fK(UNw(q{H3vz(VliX4hA;anR3$+hDy*%r>oFu$K zTe;v3uHp*W#oAc0VsA1#1^Z}#^)00r(Q zckpqDD2dljepgR-G`7-Lvo1=eQ{7fXV@)`_^{Fz#`fm31>R;LoW>F+J$_qIC`S8V==E8PaMXH?I zgg|Idlnj?gqEW)gc%I5O3;51Kc~r4A(g6a{6fbo}M=-v~?KO;deT9dQC1HU7&dp5b zBqP89N5TZ{OV2b$h+jwnjexpDC}ZEE#EHWL!#lguymFf3JW$jx>rX5X`^(k}4rn+Y zogb+0hX3*mSwyM~J@#fHX}(-T+KJn^2vC-+-&8bTp5Fib0}j3Z?7d?Z`aMospfU1y z@l4hI={Y6lap?M+$9jjca%a~5HcxWm*vtZd!E$m3(U^=Xl%OMzNKY}SJCx)Jl+ z@PT>5xD~%XdJ`?mBGewK*_ZgU41qvixk!*Te2`B1gpyBxKjTB1w>4z~#1!W}l%IMS zD15QTzf<{rk^QnIijMp2OTVn;iE0d`g_m%jGK`d*j=fpZQPmpAaPewEM$7bZS${A7 zv1Wda6`Tn)Bg!9b2Y8e|Rv9jD67^2H6Sn=t08pU1U6}@I|Mlm&kmAhh6cC725%fvY7t3WV4B`2bU zvVPE*A5OpcQFhY6p8YY#cprGu^fp0z?S{L4XRPpcvCmhBwtj|{R#IT5BwDy@&$zZy zVqKYxWvpVqp9~br+2q88t4WpJMZyC4Bs>oY|BAb*{=UG%V z9d@EEBsgocIoXR$^1jnZ&PXERlvXM-WbABm!OtLPHPn z$|5GWr}Q;8x)f)ulnrjYnqmN9H~CwbN=1PrG3?W> zAp|vfG_?JCrE|^C+1k6RqBESa9@lQcDg4D95kFovEHARMncL*eWAy3skV#%Anny8= z2pT$k#L`i3gLqGE$CJa5U7q(SvJ?H5fn9&-#(E)Dlx9mO9V(BvE0afI*8#cny)$`r z$(KRxXW40HNM;M!I$M08a}?xf2L!(+XtI3 z4UR_*-fN4T=<9QnldyIPoLmt^F`~FV)hYMti^_I_xMAsUhyQ}3Kc^H*uC~Z5nT(T0 z&s46VTT#9p?^P#l#Q0LrrK4b&*Lk+s4Lby7W#y*t}E^+k5sFnR*X` z`wQ}M%UpVk-M!gl)40=ISxms6K7M%kb~4&XCvd?n`lXfN;ZwrLm_xRuJ|;!Kd_ilf z62wA*R-hD2`~5XJ2f;&hV8zSj>D(dyA1_^^2GKVtZ8D{)G5vC=`Wu${2$4T z=j3G`xMMhz@5(vKP04{I6ewfRVi5|=2u=r292#n8mY{4d;`5NuVU%bIa=f^mt#f(C z8sCI6CV{5C;D_6Wi=Fq-uGRLLPyhL*@2mD5fiv+UR*)Fxy~Qn3^Vd|>n3b;aN?Joc zt}|C*|NR>TUDjsc0n#DNVp{S%Lqg`yjTGQeI?R9mEP`G_#zI;%x~3bKHA%sAN%h$T z;ea&&mwNt)U%TpyOJXvK?}S*JM%&ON-zB#~c5XXE90)CY7^o<$Bh?>&a>H?&pD|dW zD}q=^r^yMid}TCuSNs7y@MW7ND5=M+%_w;~JKs$Vst3y5#F-n)yF&qK50LCzD`oti z@UD8V;qa8HO$eV(Igr;RZz>eck%YPr#aUnRE@xk6x0CsJDDt`;q5rsWO1kP7HQ@CcqXOa_LYp_MzL%-O3Le7Ht%(@@b?rm z$vi3C)L`7P-?FcVQj>|QzN5u$w~jQ3;!L=|PmRa^uKj*hNBfw9C7Gc|*cn!v(}ZRU zS;Kz5ruI(MT@cSQu2bpONVHB;ExD`wi{V4D0Q}>6E~wxN{)HbxgI7b*?mI4Xxdv@f z$s*D#Y!-->3Xs(q;#3rNIlh7iqRs)PqxmIj9jvi4Vh%aN2rZj_&h~xq!#GtxdSMP62c<*6zHhz<9vc zYz%fjff+-hG3Rb$%Fv7y)(8?l6YEGI;D_tYh4f1Yo8XfWV*K~Ue$trdkhCzB!cNPE zoHt4s0QMrkZD3*W;2EC82PvfKpF^2mE00U5SCvaECZ$TYN{1um3@1QZlS-A_P!*=X1|idynpOlTpO$X9>z(6>v;RKt00y91?=62XD~TNZO!V zJ8i@>^(nVJwb|jdZzr-1MoG*jSi3X7C$Mld_p+|+GHBqB#@0YObC-z+$TF}1vAn%WMjY0Z7lTmcj} zko`COL)gM+ncz>jFvQ#zjTn9(GQuwS0QPBy6zaYQF^3!}zl~8#s+kcfzx$M`BAZ z<7_@CtQ9Mkan>{^SrJ7DZI&BejN{}2KWWrZ2Rl{{(syI0(9Yw{O=-#>e-~Z&AUT|p zumqEh6opPA1vm6@zyI!lelXKO?FuF ze~;MJNqM!^%Rs?%8`#H812B*)EZvg%eGZnWzEO1lm1BAE@bLFKdb12-MsWLy%y0LT zjJ^+%zy1dswKyB2vX63x?zA*~YNAFHg%*l`@YlX#ShWr7+La3r?JuGK9w8Q!J!R2|B1@_eC&M~Z zGCG9(>=5EqLH$5RkfCoCtOz!|4DjF@ehrrpil!!TN6gxKQ>-rJ5w$Qr@ zUaLR7yR*Z)&haJZW4zPgV-y0PcERez^rDfCe(RsB{b^O7ISb&Xc)}0?8oLJ0_Clwi#=s@hm%BJI{s>-rm zP2S194QY@`6gC3zgM|zzYXc1xhCRI$@HWV+CnGXP@fFd|`BW>|L9LTMihHPo;Q5`y@xmKaac<@=eZuz<(y$r;SF7K+LXDW0dA% zl%hncO5)>VdP~$pYTfgj!S=R^=VBegjqgKaeoel4gkN7{D_dBkqMKoxQM}17cC_d( z+qR`B`MN>8ebATXixFoiTVL2AEE~-cHmjuaxe_;|X(zEx?#+A8^Z5_nJ2P~@Wzuvn zw5bmO>y%v~tYZ@W?M%RMj-f=PKb&>*PtBgT)VZSAt-~fKl}gqGmw-$`FV9|p(lQ>$ zj{ErtKu2}oMzU=>-XKzU_Rw$O#5yf@f-iizEk5dXhZ@)k_pQf_QlQ_DX!Xd?6V`pI zmO4AUcJc9$W|aAtgAfyxq%`QxU(|KdtygVrOyt313TZv{8GoKN0mI*xm1i?}tNv+b z#ON+m-$Q8|>x}k(gNaew@SN99({f~5+u^p1+M5r{^{S-E(HEaD5K;cvlPLK>j=3}} zbcvJLm3M|;HzoS{Xhug ztUG8#EyL?He`}8in^4I%EUup3c)2ji+p9~&UzR!|D}-2O1#&GJ=7^Tv*nQ@({BF;L z682s#ztohH$FqTFYD9K|VTnp_WMGyUDABhCz#BZ-4k8zq33w@irf{xVR35xTQ8TZ{ zd~-69XnS*5%b;P6>R^pY&Wig(?bn;FU#tY(cv$&`tQlwoFQyxn*6Z|{`7%-?ee9AQ zn1faY4SgLq}jmSV|T?;Zw+v*~K5ayC%&XAxpZ)6N%6WeEfnvfKdfZ+fD4X z3))}yx@Vk`z}U2P>H3oo>W4WZuA~ItF!|kKbA04?l`nQ=vz1rd7%Qru&5 z^sgdj1lM`Zu1X02Y55gMhrLZvC+?eAx}poS+lU@GzX$Dm%j88i$Pk2CDEUPJwXqDqlfV?|5*+Z2}PR zDfCjrs#Cl_B(16FI-i>wL!sN#uCTEWJNbe{#IJ5Mh1&GF-A73jtIx4s3}j;0W}OA! zfkq5Ilfe1-h|w7BXHHiZIGO^^$N4bOhapycT<8mew>v5n6}5c96TFx$@5-hFc8UtJ zf##Dj(r_;d7Apslfz-1D;1FXg#`){_6W^<=u3qNt24x0-;v!QXKizMp;F9@reliu< z31Y}ct0I7EN5^C9Asx-xVzY_ZYl_3_QxVtT3vX zMt44_GJzb|9s_JfF3im8rld9wqg~w2D0h;y^+zL@+_RRpz7Ybc^D=`HgspeNpc6*K zb(zNAqA@MEMv#JU{1|4kTiQCM9o6{IFr=(~(^WXFS@8&sszbVn`z}KThffrpdJwp@uya*JaIJ zW7!ZJdySDg!pvhjK3ZUfZer&yccdwXnKH`xy#`EkQL>dgTI9h7qv_U2>E$RCfhE0U z*ixbZFrCwmUr-^=GK(CKX3|4hn_bQ3L{_`bR#cDwHW&$E&lItBLSBpIa2g)tI7*u#q#j0t+3)G>gQ9X=9HISYjLq3o6Hq|Rkqy2-(-sWCQpmZX=+JERFQ`X#=wOLMg=??I9tyT8 z_`2As1uW=qOrp-N5uQ4r?LPvN@I)BYZ{M)ztA*@dP#z^pbPOd!^$7CgcI#%)G7 zU^n3dB+d1?ZV+hn05y~LRfJOki0g=SCq2l+8U#~02=vbK$KYw}+yaI_8?5PGzwC9; za{^1d2%3F*zCC@9hu67QwLLRV;`l=btYOm0FI_xm32BnY2#ob|MHliZneOu8^$ zi*-u^F&c#vr*R(ZWy_drwz_zT2`xN(!*)QSZF~MZU^)($8JY$DECm^5Ph|s)NY4kEvc%Y` z)brD5J;CWFy4diGs%_$B;Zp>f0`OuGa9_g!C5E-hI@6mxbdbi`>CfK)a_|M=3(RiW zz(kU6L8gsnVM*_AhC%EEffm$+s%%i&x6vK}k%JcLeuSPNLR^N9&n}0}-pV%S6ijjzh$42RK&@HijHP=x!-zDBR^Wyi`Y96W%TWJgqCZjK=Wx?IA9K_PCqtf~F z;wDPQ-`TZkt1xkhZTY--X@%HRh^_Xpi-Fe4eo|o(@V*%pWWA=H#J8k;t?}T3kYs|& zS^TcUQMz(_G4wO+QV~t4gYIvc$5D;+U18DHSXIti6e;D{$m?IF>dn*AtyXsu;8j6s zU>w!QQ<|hyK}S|eCcb8<)?!yiQIhK#=``@&x#PEK24mlX{BorQrx@WD;b(Xg^OypP zJa5k5GLjV*FS9O0)5shTHu%vZQi%e&ntR zEYfso;sl*n(&D*I^vAVMaw!fay?3(vC`(~ft({yuypCP2?T=1-u{v zXP=T&S|*Pe*Npo9!f=yZd6X&8jHc-C1H`GWch8vAVR*iljWTE{#nF{A9s?9Ra$arm zl#Se6)~)iQcm-(XXZCLXfGIu`U2qb-)s$~eg?gnF6~=M|D_*lNBS^1{O@$Z7)I{uN z7TuQPn&O`%2pCv;$l!#+i3u46nmUZd zNwN#jQ#5#|#hAZGkZ6~jWHVqMg{y_ag`8FhDX7HW9>}VM?B=G#N~3)=AIoT~tZ3A( z`ZRocSbJycu}JzTI#w+imVjmvWgy;1PF^ia$b_NQQhi^Rnr?L$y4r7nGIl|OH@(c0 z(`gM1XS!p*O`ZT>wVmZXM$NqK4QQ*Celu0ZgcB+v--P{1K18lPL3MoUk&Uzn?-@t3 z8{k$Lo089hgn2SHoB>7uh#&&hgPJ0#boMe$d(y>xN2N4UJ^=+QktnZ^p-jnJXv@uY zkP1Y;z1vu?jRQk4>?-*^c!#6@=*sk1SLd`TR$HIG;`ln{GJ$6t*g@WWMR<5l`c-`~ z7!_-_^Vd$9XZg0Aj+1@JI9Lh|P4@nX)nsktyVB2+r+wB+S_w&=5)ifEi*3H$Wm^eH zN`pS{Kmb;K_e3#ix`BP{Gx&k=hi3Ms2QHb9b>U~&n)eR?qvc)}ip@xBvH^<}6j@Zc zIH`&6ti1aO zx}xP#b<>XHI>%4QwWqf;y6Ww(Og{u+(sJ~R9H)tfJfuB8zr|S(ufQW_nUp|T*Ug_F z_9b>CGeh-E_eWsf=@JtYi})TMlKEB#8;txRRNX^r046;#t7LXW1Kxa^f^uW6^+j{_ z1-&M!mjPK)4R#QdUwO$19j0MQ%N{xWsAWPelytn4G5Ab+vrn>@aUTr}{cUp#uHj)H ziDruJ=N$vkr%gR#3eWwV@qf@hv4cMB={rk?xXyh{S3R|6cU%;3nIs8=jgwpg*fvB9s-%_{sxJX5) zf}Ur*N@OcjG~wXm(Q%$B#*rj{yMjQKPzPo;(+lmrs4-iDE16!k`lTIP8gF%}ll3c>Q z^ho@hf(D5*6j|)p5qMK?mNDy9?_VR@cc9SVLss-#__G*DS#`zTNE%TSh)e-#i%WK; zCC~VD@^qpSC6I6AO?%Keo)8~u&8Tmvma^@!S(3#0o|J_|`Q!4<_X-Hm z=QD|ZFLE;c3mm5?if0P-#WaFz0b3=QhjI~Qmcg9rBkMFwerXCKPVa-b-`-nRa^YBJ zzB?fl+u6emNF5Y%0n8Ow}c-bDtfUAqOfUj$;~Mqyl}L@rqt@gwRxC0 zUy3-_^B1P)uI_SWI$J=KXDqeOdJCw!VCFFy(Km%2me-jpML5h}^f8%@<-k@vy!r*a z|B6CZ*-^2_&QZRnc!##O3R3fNhi-|sl0+a<7%mw}eKAerUCuu?UKYg=vmVdy{-Nd{ zNu%IE0oJrU&Z^P$+qOyfXN&7#xX-_poZn^}M1PPj3`|WoUK6E8dMB~|X>-f>ecRFB zv`2QnVTg8^*h493m*{;=BsrE8!OTK?V=q;k`Am|~LMHo-p@O|Cwv@P8t*0%a_3Jg! z76a|Dhl>J9sy?6}UHXC7-%IZUA%N=YmcZXlk+;vHb~X0kR?<6f9&bZ?J>IM8x+e#H zrO)`NY@&allDomM&bQ(k+^RvCkQx1_&urp@&HCc1Cn*b_z>RgS(`&DDl{MhoRpw?T zU2SH5E_GQyXz?m)V&%fkUwj$dr!_tr!vwe5U}0Cu-CM{jGO7X$Zh}%RQ=If*tyg?{ ztriJ%KzX>5DQ`IBBU)dwqctkk9z*&Oc`==+xMe1WIX|zceMs@wt!GV*;Kw=8HJN+^ z=cg^OoSqI_PUt7CybM;tp%=NF`W{6&!Jdq8XQMqZwnSywdqXWHNtQHmOjVd64QAoI zku}HzcmCi+>&r}em*L0D=9ZXp}C)R^=B_^WE}4hTqc z+wjm^{vjgoty2939 z6m)Y>%zi;`c~MjCVchiK7g8ClHr+wQk;c>`D@w2+gDdF*3VekhV+n0CqQ%l0ulR1= z@+oe5%?jq?JXoM0bfnF#2smB@t!Edd7$({K!a4oxW5+N5J1THSKUiCNRK7oeUDTkQ zGjF&=EO6vD;*>amy&@z;&{8K?vBE3IvsfIvup5IaAW=NAo&@W~TO5yY_9@Aj*<%E!n#l zt~El>$N1xIc2h1sMz-B2{`9^c&wilkr>RA~vBm^%oPPTi03cK;)ZC$f{ART6)IYwT z+*}B|G`0DijUC(xoA$_j)qdaUr04bafvjL9j$hA?t;Lx}R7tn_5#Fx6Mnv>Fb%?dZ zgrnX?xe5rNgAK&zDt{gIeA%3H%b@2(LRx&$UvvIe!<15WUg$cSBy!bIJR`#P0Cm`W z84d)>q6XruU=ogQk89-|ZvrCEmNccK;S4J6zuIEwyrD5sNg%7s;Z{k|{MIIS&$Rq)E+39=fym{G`-d;b*ID zY)7&)SuHvv>bR3B{-65bM&fb!^?p@5Rv4v&~>%IFWh zfO>YW(-)*+Og%$$>39?f?#H|J42al6MkJRz`mu2XDl9w2o+5&mU{g8&CncMg(x6LjKyDvaI1xBZFI zMCem_r@!X?L}!fp*P$!RMnXDv2L$w)4!Cgvvm6b;dx@i^_nRjV4gMQplV#msQDbtoghcb({T z-F+-Hd0nman3Q5riOIO6mN1>D5r4@P8eXqeWKER7!KmP#8*8XyE970cjT)!M`iy{uz}OA#~EbMmF^PEn%5$xW(@8<7yueK;lp@zm0v`aU=W)v1NMe z7htz2xlmV-*oJhbf)@j&@|%3eD_RNQ91JTG=pXr6llx+!R=t*4DN9}3B}J2tWOlA= ztcOam#`TSOr?Tf~tF8WcduB7wTR!%i=1_$6*8rrbnxdM*TN7qM74P2mQZ^gTIPa}< zOgj#4Z{8rkgA>Fb`t%{cMn`Myp1JXUg$esL3&(L^%n}r2R%E}bSTJ*|;ISB<`c00V z$rnKI^XX*4Nb!#DV&9*iAG%Cw(uRM~4KW!7rnol+HD;>L6Q_U(2NEWKK%QZ({ zbAn|Na5NHo!89wMBvwA_z60yzo7zW1(&YDzA^aQKKf+Ip@h-~Y)>5?isIBQsqJzN- z%5ZtncHI8-lZgWQDAkjU?H~?<^_F~Sg@afg|GXEXp}W^U3{Zwo%C<+pdemCwFpyaU zsr8Z@%YDChe7re9PT#?j9`KQv%526^_Bo0RtDG5a*1O2~=wFN+mtpt8&*}4AQ^7Ll zOf>YWN&XguOuVpqem zdgbjcW>J=QEd^l*H1wGro&$IpxSf<}8i#5<i*752p)DCYgsa>7;;4Rj6lq2GbetW~Y<;EapnGB09-($zl~m30a332%6THwV8k{xWDOcgsndz;1%?ofTj@4ccfREJkCofA zN6Yf$hk<-3DAgPyP*z*SK$&jy0HKMpnSvT@XXn^5MdyiwzZlsoM8!$I@n>6Tm^qp< zBt7_~ye%JfZf66R9@uo5mm&Fz8;5}*v8u$%w|-SJ@VN^R=>+ejkt=S*gGw(U6Qa)* zY6wlZ;P2MI)UP~p@d zw5$yNb+uPHRgYO}+Etu57@f5aJXrKVHJ02fPv?dl~os<8xh63Boa4U>n#thYoY>~XLi6$xr6^admYn9xaUEP^HNcHC#0`w7dpi%i*H>87ZVD(UKjIx$fz>elbI_npF$m<;)i-TDP zv0T~oT|{t={9AKu>9uiq1pf$wr)5z+`x^GKfC#*x6q5V4pfchBwk?M;kV#`RzbpYJ z&H)IuCSV(&Ei`v#84nhgI5t@qhJTo`vX8^cu^*jeTUCaK}P%B_

X~%=`gEKj(Ksd$CM;}+tqLyrwl(h2dj8hG;X_w+ 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 kH>Hhmey{g?x&fFIPJqrc*zp~?i`MerS--#2`8( Date: Fri, 10 May 2024 10:04:55 +0200 Subject: [PATCH 3879/4092] Update android icon assets --- osu.Android/Resources/drawable/monochrome.xml | 30 ++++-------------- .../Resources/mipmap-hdpi/ic_launcher.png | Bin 6403 -> 6088 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 7232 -> 17696 bytes .../Resources/mipmap-mdpi/ic_launcher.png | Bin 3582 -> 3280 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 4040 -> 14331 bytes .../Resources/mipmap-xhdpi/ic_launcher.png | Bin 9537 -> 8745 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 11248 -> 22749 bytes .../Resources/mipmap-xxhdpi/ic_launcher.png | Bin 16409 -> 15300 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 21398 -> 35969 bytes .../Resources/mipmap-xxxhdpi/ic_launcher.png | Bin 25006 -> 22562 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 35444 -> 50294 bytes 11 files changed, 7 insertions(+), 23 deletions(-) diff --git a/osu.Android/Resources/drawable/monochrome.xml b/osu.Android/Resources/drawable/monochrome.xml index e12af03bfb..600c070c3e 100644 --- a/osu.Android/Resources/drawable/monochrome.xml +++ b/osu.Android/Resources/drawable/monochrome.xml @@ -1,24 +1,8 @@ - - - - - - - + + + + + + diff --git a/osu.Android/Resources/mipmap-hdpi/ic_launcher.png b/osu.Android/Resources/mipmap-hdpi/ic_launcher.png index 7870430484b7a5d9433afea0af2fe479ac0e967c..a1f717cf6c414a2587644f19444e404289b33a16 100644 GIT binary patch literal 6088 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

e&FE%k)kw1nZ_Ituv@| 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 Rc+vm>002ovPDHLkV1m2~j8Omp diff --git a/osu.Android/Resources/mipmap-hdpi/ic_launcher_foreground.png b/osu.Android/Resources/mipmap-hdpi/ic_launcher_foreground.png index b2ec3e49da191d5840d72a7ae2e5421dd468fecf..36651206e8c7c4aed92db0369b84c279ab962465 100644 GIT binary patch literal 17696 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 KQZ*oxkpBZPcw#^R literal 7232 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 D#&zr+ diff --git a/osu.Android/Resources/mipmap-mdpi/ic_launcher_foreground.png b/osu.Android/Resources/mipmap-mdpi/ic_launcher_foreground.png index e22f2565625261d8ad104f2d09c70ffccb5d2ee8..85f308442952d8b6e5dab778b955d5055e9abeda 100644 GIT binary patch literal 14331 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 oqR+3uGXD=dF@LF?zS(*JxNC=6p119V}|-T(jq literal 4040 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 diff --git a/osu.Android/Resources/mipmap-xhdpi/ic_launcher.png b/osu.Android/Resources/mipmap-xhdpi/ic_launcher.png index b5e1a9e3798869f72105dec0643dfe18e8634a54..28be07512ad61c4d8caae199e300b5746ac047b0 100644 GIT binary patch literal 8745 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 Tsg}M000000NkvXXu0mjff3M09 literal 9537 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�>IX}``GPV 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 diff --git a/osu.Android/Resources/mipmap-xhdpi/ic_launcher_foreground.png b/osu.Android/Resources/mipmap-xhdpi/ic_launcher_foreground.png index 1cc3fa9072d0bddb5511ec97d47bd80a7513c78a..613b817f0a54e6a0d14e606d07fe4fa8611554a4 100644 GIT binary patch literal 22749 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�d`^ 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 literal 11248 zcmd5?_ct8Q*G>ot7Abm7;-lBqtrig_MBgPk(V|CRETXOG(TNfztE?_qy)UwAR_}?m zy6C;VzW>ELXUZ=#b7t+`SCYuIPFoad7o z^2D{gby#-E6W)9}v*g)^+kX`D{QqCwqZjEH40&$yBGh|(+RfS4b~Vkx!NJAR#pNb` za&q!(eSQ5>JqmS8IY8DTpJqdG8`y+E;N5Oyb5QLSWo1{E3JRSXm#4e!_wi>+kBLP? zHM2op1tV*hB#ggyr9mKZA1^QTPw-)Qt*L+lwE|D~g(uVMxg=RkUlb?hRbg)AzhN znYl5ohIYGfTgjixZbmv8wHm+!yMB<;>J#j+TvOaw9MuE@9WVP8B#j)tv9iJiT)jHW z3Vac=hw6D=IpyzYY-|kRb@^HG%f(X^r5oV)f_hg*$?!=tm+u9S8ilEveck|<$Gkg+ z#Q1IF-%>7D7yut{u0e#G;_7q_GhJ;MtimS`ZzL%eaM^bWAILSw9!RhhZvLbn@9kAH z4RFsg6{9%yH)%N@edmssIq3IV7hgkKL{>C{vdeeC0ln%{I+tAdgo$JfJb}4B zgp{K!4;#5v;&8x$%x((bENpvCK31=bJ8~Wuy%wXfnqxX^)j&^a`KQ3LXwEU5&bhAz z9>UXD7&Lx8E8w=8^v2_79WGYh8PAN0udJ-V7T6|~|GIuwu(7d`?;SIr0yA#EVelA$ zcnR9fsvXoEbQ7j%7->!DiT_M+g22ZvMz_XKM!g+^(us@@PrF^uP_wSow{Q%hy^VrY ztce*vqbYhN7W3&ZKCzb{Vpg*X=XJ~R!PQKLk!FmN=LqQg~y?cwKp-(qiZAqSu_8<9AM@Yq=mFjB@h z#$a=-U44XtoBYIUH7owP#Ky=rZoi z%mqzdJ(fvXcr>{5{YUMd8$*$^ReWQ36h|5RZo@~l08eH}VEQ}Lolf%DRznWdP#}D3 z(lzWkY#zYF&E5F(=g-5$iZ2|_ZORkfX`sboVtx0yN6zeAk}UKJSmCUpp|HOG#6Uq> zxT&dl6AVJgv=~8%1~^@-pnOGnuZ*sC1UxS7qYgffp^)Ow{n+)oQgv<$yT^ZzBh543 z+Ie^!lFbWrH)K4A!T$U*)zcHniY?Wj0*(Hj&hJHHyYfq*%SIEO_XO{=Q#@i!*g#h~ z=d8x7G;rTYru2S*O$wn728EAtjqpo^^gprV=dURLgA-$KYBLx-)Q;6fH=OM4BWG&u z$1JW{NV+E6V@KA=3Z7dT$EFQ%?fi_p4gokZ_hk2wPX5e{v;YBek_yZ=$3*wI`_{#JS^>#C3@c=z&VSs?BB5bJmBuO4J5>63EoUDeAII-sCoU@GjR2xzW3P>>Dxfwd zxYQCux^<>1ZYM#k5aRTRTo0v-19vA$y^;93*i|VM8)!mDUua?DaVd-opMu!Mk=@ny1Ic{yhGYn-hk(t$V7d>9G9tIsKHKNonSv zyif!wa>RNa6?bNt((EvS3_4PUxOIqd0fGBLZgY8W4HB>F9!}^rIULwdRNn`d!qx5@ zA~_i@ZCmIm6d)K;Az3qn$d`zg$DDZnn9W10LKR3z*OP#zRw-^Eaz=m z9HkoMWMEhp(_N69zOnITnF>gTSlnmPckOs+XlTw8-!YJAk4w{|mq%zILi35J76i3x zkdchembMoTs84#q$5m!;nCC=^)6Pnt;97JRyn1J_8hhpu1Jy(O)yq3^Z7-nJ`OMPZ zux>Uprw-xuqm8lK!NtyGy$iA~C-i zT&P8c`D2ag0Ix^ZQZ;F1nFDe2pW^&Ls1~v9&f{8r?u>>wUC#0!lNivP^8*M35_Ir| zma)oYYIO85f>+7ZA=qz#R`A_7wFg97Z-2eRk<|y)5#TAVY^`n^Fs)ekuPl92-7g?| z;w6x>s@!-@6%`eIRn>l4nPoIFQ|L=Jw)Us1#|*lMiC|2U;z)@}Y3) zYff;SO!L6D<}P1>G{uw^146bnp(45U{cJL1T;HY=@F$Ljh9+)bHGcUsZWfEh+Bb=d z;#2jS+IMVt7^zAzga0s~vsrkrB}s7pT>V{q=d(|Fv}OxHl4x&8S<$iano;IIes zk&NU~Ei9gcQ7|iYTc&(T*TLR?M#8%PG0gKd9oKx|mk%t)p`|;N zp46qx^dnUCa+0TC59C6==GNPY*ySD;1z%NU)#`AgGAPW-2+aD~&4qx6NJn7hjv5im zgzL6nV=+g*3NWA-)OWCuzTmd^r;`=>m`O{ab6>nrhs9mFd@16 zwHITi>CBN3mmC1lo5x$2`CF<-*Ond$r%~+iEe<$r>Ed6TPzlaUy^NjYaGA1s!ld$x zsyn)br_hX;tQWl(V!IRWwB-x|Lm=0|yUi`>Qm@WT3=L-hrX&u-w%WGSq+)s0q(n+x z&!lrLT2<}OjOHdq@5!3F66`)utb%t+zAJE_i?CeBuYZuOY*RyzYk*NwsYZg-+GCg8 zV4#)bsJqadS$RhLw(0d`{x@j)ZZr7V!$%ULVq!aEDfgo86Djx;km@niD|6RWsJaj+ z_KcZc+u}yado*&t#tF%pk+2$2MkH5%=sqjEOi5}w^-pZeN#duR%`1(YE_^MP)1Uja zg7jOCDJ}H&C_-PFI0s1e^w~3yQWa$6b&kSVkyd-j9Z-4vFjs1vDt{6$S#rX*A%z+V+eK~&L6&m3#{!+zg%WWKZgaeKI%BYlPfg8!V z)X7)Mx0h$d_8PH8B$lHhXL&b^KVkfMRu6av5BU4utUX3Xwn6R0fSjBUTO)-zCPx}M z^|*y&uTQxU3%sV;=%IzR)7ml}p`J=-WKj%fjGsQZ z|9u3NynE|)!TRz@umX27b-NxExG_BDP(>AGLpSJ$;Aj(&On>(mk>n`QX2=YUx zQr6bj=d|HdjaS4{1Bc1)O66A24gSl*X16y~xLe3GTqH%4GPH(mo(i1st}{AP3HO1? zcODa({|)}QtmPFQpyz`q0%MWoOSonyGD{Aoit-Td8f)Jt^QAaB{h7rj06)5hh4yzMBn&-}hpU?NCA`gJ!GPBPr- zv9@YyK53p#}V%7$;oh-f9&6EEa3dnvHI3$s%=#8$dMetQB8vNR)gYc}7VnA`hS^HkI-h zoM@-ks@sa!1J0`bJ$d*F$f(C{ATo4aQ({sVee-$q)Z_=!cefaqxpqI^48Po2Mog^r z<u;NLQOVEF^UVlq*jVYl zH8Ebxl$NVBOjhT&h^w}N`e~F_8)f1I!%NIZP6k~%2XN>Uctexy{sP|HQ&*`+&gS{o z0Vt#o9crU#gkGC<{y}6?IMnTKcFbHGu;%Qm;O*_5uF~6m!9VqHzVT2~SA?RuMC+G< z)_hAfFF_RDC0 zNwuD*y(DIp#<8Au2o`SS!F%-KLfL>8((WDQu4us1jUh8sXnZlieOoe>WuCdx_sY&c zB2i$?@jR-ATxCO8p01|%jUIZ=n{dZMEB$&D#G@=79sA0c;kNEMz+88u_-DJFk-vJLMpsCkAC`mHHd++k7WKXJ_Xa72>Q*{;4}Ct1)@& z`A+*d&I}pT2Tr#L*=3f<8p1|v_i87lY8lSOX=9g7lWSmt)J&3E<#KaQd~DqQHHnS> z;1p5UZM6wzv+ENgV}=#=^ivQsqr~^P*w|bTt%|&s)*@%e9xH{Mtj1=p%vPo0h7|CA zTr0p+*!rD$L5s0jzZ&9f)vH+01g@;tY-=Ibrx#1hHPcb$rT=6LK$zJu>1}Rda&8qn zf?LIPKB2+QCcQT|Z_m7qU_6#rUS~BifB*jM$4${%g|9gzLVE|8qU7vrPO@8|cOO#p zK^#z9Qs!A(tD`o~0uobn`Sjt0f&XQaFbVP2Cp8FfnQ)taLweNOgEfQze4j?3WZhkE79Sf7 z=tU$X+Xk`Y<3}%UDc@&-{hnUFkVsBW9;~^>z57?=1MzZ!)U?~2 z;f{?ZBTk{Jc@uC-M~alQmJnB?S9R(qu%Xib6L4^IKujHDUXGpQoWU`=t4;J+fycCM zx@pJ;b7koEOR5FOh{Qn?-qwT~c9h!=7J61(jz_|MVx9L)$hSW}MYtIXJvWKS4ECMr z5Sm1!MP{%XvK5Te;Qd+6w$6Ce)zt@xZdg}{1_LpFYHDhT>VvX=8MkQB(@_-|UpdpJ z&I1R?!vQ#+l#zMqMrlXV3wrPyz zv>Pw8Hc3bRAdT>r{t!UzPvNP5t*;Os4Irt2f^VGmbVjux7Qj7AhuAJ1_2O-3f6j*h zKHm?b{#B6Rts2Jn$=w~u_#$mgzbeEz{O=Yq+~z)(vrO9x4%hKwq-&YN|Nfmk(x-U7 z;WU3iYiML_tal!CmahURTMCw^BBrsw?8}%B_fO8BB_I4s;G({grx-%bvG&$3^QF(B zB}8L@!u)OQ{)vN`od+Ye97@qQgDfv$T-U`sq4{Rn2GR3n1nQGq>?;GWMZO2H>I;6>1A=ShKVJUF|SBO zELjfaA`AZ1!ovLEmi4tfA#wfaNxX<}f~(%*86ASDDP4^)d^o_ZC?`dj&ATS}jozZ( zoYT)?4F@?@@Y2-lxxx^>0eE%WHFsyxuGT1R@2Y5C6hCz95X)F&UeT;f3oOC@{(?yMx8&$xMyWo9;|YS00G+1~@5;zY_f&lvm{mQrRtx z2u9zzC>EaFFEj~DL8&D?>TUdK1D)nm%fpeI{j`_Z^Tqt+)F=w81L>fP5=@=U+MfSbRG!MS|UFY!&KHtHo5fKWUDi_l>n2)ViXRwBV3>0tb z=HWoe6`02q*0b*PONCl&o{O?KCEm(nNsvq44MV!=RQ=swPj8GnzP9s%U&7kj`fB)2 z?d+A`!Nd>HB1FC6Jn_Qy`|E1vZ{I#EU~c`;*5HH>AEi(~!}%ebod*A{e{?})8N`o< ze^o+~W`qM@l>4_^oN}v?8zNioQ&B6!Ya?GoZ^$q*G@ZxGNoH!kGk*Q+w|_Bc;#}-m z?cpaUXl96WpOYJ%t9l@pvKMacQ#04x%SX*rEckV0!5JU3lyGr6Y|nyxyym=o1jW*+ z40Cf34VpZ2gt%2yTXV9Q3qw~ut}ho@&?jcI3Ysu>KvY!xlZ`)md1724A9Qo|aZY|1 z%+yfqw_>6C*weot%1UNGR){O;C91$dz(Me74FP_5ri9oTry`jDjBV}r{yy0TR5gr& zGSmR`$Xvl|t+vzt#AU<7M|*iM91}_{cf$jzxcwTH6R1cpJ^V0-pikzy!vrMdB*08B z9^vlIgi7nSmT6KreqXxoJ)7rV;Pl7iTeS-oNxj8^0Li%2jt&gdMJ+4TRvp$^(MUkaR zw7vX=$vV;8cD=+7m7=Q;y4X@G<;sv{?-xizTe;`k)adG8#rm@k&pPlv{Bh$_4$?n( zw#Cc~s-pszZ0HJoFo`e{39V#zbO(s}MLXFmY>HGO#S82$&P62jjyu)IF?dmq6`lR< z1!YQ(KFcV5&3^Z}0AV6xld9#&)ckw>@9xmt8cWdC{;rR=_pF>lc>R+WA}UVl zo^^$8*GB;I9r6hdOG1#ug$?qpoBl-ZblWp~Fw=1k@blHO01}FSP`5I}@$d4PkIv?R z^u!q*hHuVSIeHB_{#KB>wJN&h??CJcYmvI0RyZW0JeS9h+o=R}OIARu>x?|4PMBO~ zKu({%pdOhm{{dUjacDHD;M8;OKl`eRm#ub!4);uq=MOdZI9-R>)t5eu^I3U{yE$Wh zH@U<`C|JD-ZC%W`k_SxQ(&Ytx+R+NS7^_6p*UxVHn(i2&%sU*6p7xh!3+Qx7B z>r$qqX4P|FibH(Wq1lI&Z?q4h=Z^mi2#v?b&Xyv76zg_{t`51Md<(&#aN||4G}ohC zy0H($LKxCU7??qx>k=#(8cVL7(*tRD;kwHU*6-KzTgZJ11h6$tV6$W>XXUCmjr(88 z@whJjYFZ(_>M1Sj+uUj3A-6prl`lfKr{Sv-_^+^ewZ!b^H?Es%F=dFSQ|)Hft7bu0 zpRxkASw^K@bM5X2dto2tEu94BY~B+jxBCY_jo=5LDz>Zi1{fil4vg!9y(*+cZ_Y=8 zdI^3U$i0^*BHt3EqM-rVdRQc+TAy%eGbrkxqg^b{lKDVXx&75+X}Z56|J&TGMfuNv zJQmZ&M74UtR@5C11Z3j!`gy^Xe}4g=Bv<1J9bD^Ab%_f~%HYf2XWqD(*n}q(QpAI8 z*nd8ZVy0Hu*jmy{`s!a$P?ud}CjFXIMWy)g6}@C7pMb~<7`K?Ca)@K=*;?!-OZus* zMAZN+zNOuCOXy&Abv5~gA{;is#;bnp=py5_38wQzU#uDAyo2E63w69)y+TED{%H$A zYbYaz@}~RrwR;&=N_vf7@lSYRRA)Cci}QX&sg3;5#nzzKKYo0GrU>HJ&eqGO`>PBA z5W7dlYI+UCXKF=ewXMH-DMqEQ?PyxyV3*g@i*?|U-bI8Vq&MCd|5lH56Mc6y1TZYb zyq=L%-u~k23B$fTaFJ6@iIf@#>!|N&@T+|*g&_yz50BYAKU_Qo1s@(Tw)&d&GGSDk z_AbT1*@@~qu|wniQy%tzDWaG!@4K$DO+rP_-i>Lo3D69sK9FNFdZnv+S z*E-?qH7{0D#L=jmNtA6LA&5sP>1Lhq=f*{CeU8Tq325H4Cd3a zvfz*8YNto@XA%P>$!IMYmydhDu1#&k?l~_r!vz5|gIp#>e0CpxsHi*daDB923faQe z?wd0lFT_DtAaZ|kz{t$UkZc;dihX!0uXP!56CM{6t-a!3E=DTC)=XvQZ{Ua|N7OQFR^`EqAST{3DH^UOLl!uhfb3aKLf`{~t%il-?;^`W zp%0W}c{WP&JT|uX9ziJl7@xqa`DOn5N;9>r4+qlNWeqf1KJm0)c?n7BJxkA7Os*y2 z9nVtYy?W&s9T|BZsREL`kcIjM0_EkGep8e}ck_iG=;@THMJyG*Pa)9?2veXJG29T4 zZFoh_QWR&_rFDSEbX)MLBRak3F@L)36C>y`(c+JXiHI$t>SV-Z9af&IS?=Pw`pjvt z8M}qPead=Wgk<21>Auj;YrmL)t>b(E6!E5AXU)PXZe(odCU<^&4b@!Nj&0iu7gW7; z{450Ri(_u*aQ0d{nN?O}M)B>vX=muf5~jh2i^iWMzyZ6VXJhOXrmx}pqH0$Wk>&%xA2OQ@N&dut5rGLtxVA?KBoYasv zBH7#m8|9WRYZVcOC?P(mzH#6V?8Tnvx`i7(+ja-x5Sxf3F~C8FPg?rY#`M|v%7Qi& z7~N%-_v#$j`irK5YBFPz3i@betUL7WG9lc(yWirTUyGujq_=B`RqiY@0tZV8zC49U zd;FTa;8!{p+K-d|*MMfjnG;4a(h8bgiMYV#h;xLD2})n&WfLGe^=6z-)7#50{TjGG zR$9XuwmjDJ*A(W+iE%V*9NcB&>)#A~fgM-52?n&NLy6c<{&HjJMLJ>=>SuH)5f^*d z=gOD1kiG#+;};R}NL4{b5K_o!v>Tb4?xauXcuBrJ25e%y*%N`2>r|uNI@jafUnw?@ zKS}5?b5rtFh36RvX17M>oZucase45=^e7B&fd#XN7dF=C*VosJ{g)#-16LBx?=}@H z#a*8+QQ8RF8t*^zc-9*43OH(Lj4ZM6Yk(vsFI#Q%Le1!uv3uchqMiRGlsdluZ-Qo6 zIZep3jcrHeNfzs3q9(IVOu~lSWI$HX@;Vy4TSF7z=C*I!HX`Bs=h;zfB= zQSOO6tGygeg&$4T#)sAxUEPB6g_ez&q zqN1liXJzsA_w6WXnI6uTR@st`%7t#Z)e%&9DWg+JsaQ!J$-86XsVJt#0>O=vq9ND@ zo{|Xj$ety6Ee+UYw^!#U_$g&($rzseb4yFh1kAjB`N=N3&{WXP;rClxL&FRl5f`6( z(taO5T)2_T(40MoTHY%Lc&ardu@WFVG!biKRrk3??+_& z3x&Xb4WpXxvJ#>TH8M}E;%i$U$RkzUE{-;CCl?o8%#CC1G5kfzkpr;?itpX>Mk|c< z$}Vq~_IR}4UBTT8!};pgHcE44uAxRy0)Lg-9TQwAUSe$BQsKd~ICUQ>hl$*+p2%nK zX=cL+*m7#&Jp+8*2P3lmxDq)d3bp#=b^aRr2{&g*>Y>xJqJr{rc2Yt zoC##UfS2NeZhun5?6zZzQ+u&3+Va8IB%kXPUBx44bl5lIyfumLBT1jPOx3FNwJiVm zw9+j8!vD;+2F<@?o((~KE`_7DWRJ(SWP7{vYeeh++*8{}@9pgcIosR2i3;oyVdl}7 zOv>=3rr#NslagFl=a**7J1`YF+4j0D3w+~_@nN==ARL*MqbaY_$|qaSfAx1_T(_*; zBF2AgVxleR_QsxNWBRe*eT>`Koz#^rzr|Omt{?5m-Gb1RxXs0?G{VH(dZX#Re@kDa zk4tF@=8}Ql=-dDPvj_i&UbszUyF%jg=o~QIW3D9YYFb+6y{9{wEV#oeT`?-Z2d)c& zp^RHW*dm?#WvgLCAVCU=m|{Wqi+_fj8a5uRTV0tskBv*yNy{TT85?`qWK`!HFIRP6 zGJcx_mYqBOO6K3CBPyn-iin5QrN7fJzrB!JJ{GzvlcCJM9zopU5lpZqV(e zA}JZ!lBbApOO8(c8XtRjBd^od9+77bwF%{|D7*dO2hj1Bp+jjOQPLaDOy=t7qY=i8 zJ66R;?tCTbHhy2mQjup?)i9LqtJ`3Dj^zErLg6-oJu>+e!^ zAe!Qf{5<&TRl3-G<%(^(*!PUdgeCWwS7z!go|vrWpo(kgq&vYJI=P=R8oOX;(${#; zze2R#Ew4{IM*Xb6qrdH{+%>>aRav>0!QDP7xPFru6YHnKKTV<({@R>Qe5vs2JE9}n zl%cNwi3=$U#fFwk{zdKUYb-(TkeJ8n;CFuj?zP=lPlrdWWSAs!7MZh?J92q=^Nv%l zly*z0UX4jhw5!NVY7bQ4-YMcQtX{VyVWY*C_}ltKbQ@>1_o?Xop0q-m(uY;tMa&4> zFD5E)P%6mqF3txr0CT&mlT6n(sX7D#eNO&d{$n$h)3tVh_TTZ=pN6Z&JN9*AfmYE0 zB7+xmO_l1dZVP=LaOUd~J8<);Xj#}-)d|Mgl>@mn65@n0EH=OWa$$GzFAyZ!(-5Ww zI1>1fdM06er#DcNKcCLj0#AFaaFe7%USinqXfDfSQ*Z+fjY$Y%hXR#W z6plN~Q|2UI^!0FsFzhM*fS&$apgcSd?6dMj7ofENcWhEBfCx>S`2vwE>vZQnP#XR2| zUushes?|xVtv0c27S=CTB*w27+novy2=~W-kK#ODb()o~kLD=`wfu!M!l5x!n${Od zdl|t+ApNrIS$@>~K{uO9+!ZAi*uY3ai17!9K=gqGQ~tQTOMbM8^VY9FA>o;B_bm(5 z{Gil$KHp3p3Qh*r59>3=+|y(zTWf2lN95$_KR_B%bQzTX6^)$7o~KGT>*985kjgxR$XG4>vPBmOk(X^;!)rq(+dGhF2?z^f$WizwIIJ+F*RTjD1Ihh4Bb!1rA5|y?p z7l+?$TflDX=}j0pe||{`NoCuSDNbzf8^CtIzz$0VmG><1@iOzO9$#EE>x0)_H?HM0 zyWTOiOUHCp#_Jslt|efS@ODFskI(`GdWORSS^pCmBYl1EhJu3beVZ_?jNv<2K?R)# zTJ>6VoO#pml@oU-vb_GgOU%V1b6;jQ&9E&}Gt_>p@cQ76;TMGH>-W}Fi;b%6IRy~0 zwwG2`o)#hy<#91FOvl-~ur>2D*q)I*GUTItlqE^fR7XKxEWE;HFyym4W#i?)=Ae4V zsfrA`V-hj1kBrH?qE)yeEUXW<{!jpMbm^;ZMg-w zxIPN?l=y+R``Ax~eIOB;vpAHFZUH5$eEGDQ0pP`amBHdYxb%#+ZkVR8gk lBpm;*vQ@h{`)?m|hNrC%8FFaeW%Ay8tEBm+;^qk z&SH`eY(x@&HUx|vkO4bJEj9x}Aa;zlmV~6%-uw2JJM~x9satn>y}tKa!W{853Yu1NlOPt=RzT8ghZ+y1v?~NTD9o_UR zL=L@=NrUBT@i|jGDgc8|GgX!ZfZUDZ8=rNgK-Ed_Q+i(kNCT!_e3pwx18}l*WRL)p zBR<6+i|vm06PG&b0i;2*QUj))jtX)+#Q#={&$^?XI~|n(@;Ht2M|9MHrW7eK2_W4^ zMx!aB= zu5?rY=h9ppu$)vnDw`XKzM7Bq? zlcq<`Y5}DCpe)f5PDeC=gs5s4gLO+ho@h^{QgWa`{FdYG$6(gh)>e%Z4G#}fEEba> z!FHigFkr%`yzPSz}D2% z(BR;p1PnM2zyxuIzj?d7?U6vb{zw#UM-+erZnDCucZcXOSX2@m;5yJPKo~&$&3Xq0 z3x8uUZBSK;02X{8wg3`v8U_-$5`(>R?UADGh|-Z_bc9fQ`^C444tj;b!(@vB3=#&9 z^`GGHl_mx*zR?z&@HdOt%8|QDJn=U1qTNbI2+cSL(&Fk~ig5Ztzp%vtbPfy>M2LM1 zlvi+rN)z7;9Fe&VzQKV2DgYa8Lpv+o9&z7Jf#ns?KmU9u%^+3Re6a|=oNRW%-wN>D zMJ2)uc-z5gFhKm~g9F)C7Y3BAp-L0D4DJVo9__(4i&TIMps7}S#66ou8*W^<@Fb_R zv$KO{h-QQXDP6~Tm{e?POv1qM8yyr^Jb=|upqXsTkGT$8VE{Y+u7*Aa8WKsUb=SBgZ@6!b1RCw!IK?Rt2&;TmD<<$fah?RFJ$^b9>r66k2PPC_5 zdKo~A`wHMT7YfBy#~d?nWcTjf&r=ntsvM}jeZ`&P>yK!P!~g{i)(2D;5|MxRlhqJu6$g+8&ssWiQY(H{sIwY+QAGhHz_Uby zr_xDWA5b)PYY=Uu3Q@%zi2^*UJ^;^*Q!~aujKxZ-5LLtq)8K(~u(~GF2NXpaB->@P zS>Z0~e3i;fWe1Q2o)74VqRN1%tNE+XlEg&E+%AfYqRS?D149-{;C_E#ea%EZZF7&H*P z$!%?IBs{ou!Uo-Xi~=Y&Y8B5St5f zJ`vi5m#czY5g=Mx4ikUc04n9@;oKPv7Q6k;O~qS|&clG>ZL5a(;6Xe8{PXFglTM-~ zOU{(v#~gDEO@{!g&6_t%Fm2qpkzRfERk?30Vu0YK!i!`RXEs(L!=SSqexfn}XazV= z$iob9aCRQQVK(vGhTkd~&jMEgXfC_#*JDgzWrDvXbMsh00@p6SY z0S*8{Ff@udO{5B`x*Iz>)-I)K(6m}NR%{C$0Pe%0D@fov_`Uo9QUkSQ$r4(*a-{?f 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 F{})kc|3?4- literal 21398 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 JIpTig{{TBU2Ppsm diff --git a/osu.Android/Resources/mipmap-xxxhdpi/ic_launcher.png b/osu.Android/Resources/mipmap-xxxhdpi/ic_launcher.png index 65751e15c9432447c18a4af6a03ec970ad82c945..2e722ee884f65d1932e8cf3683d2b10c28251bda 100644 GIT binary patch literal 22562 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 JPDHLkV1h{Eakc;e literal 25006 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 E0Sm8>N&o-= diff --git a/osu.Android/Resources/mipmap-xxxhdpi/ic_launcher_foreground.png b/osu.Android/Resources/mipmap-xxxhdpi/ic_launcher_foreground.png index 05c6829a47625e8c647bcd3141594d1d5cea7b3a..f22db0c6168001e8f817147c13be1f55b9689cb0 100644 GIT binary patch literal 50294 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 literal 35444 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 Wm9}oz-ruXr00f?{elF{r5}E)w6xqZ8 From ca608a8443cfaa7ac6b8f1204235e750fd9fe89c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 May 2024 18:36:44 +0800 Subject: [PATCH 3880/4092] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index e784e4e0b2..5452a648d1 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + From 4add1bf8163c4463c296b04cfcddf76b77acbd5e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 May 2024 18:38:11 +0800 Subject: [PATCH 3881/4092] Update metadata assets --- assets/lazer-nuget.png | Bin 11930 -> 17094 bytes assets/lazer.png | Bin 328536 -> 448852 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/assets/lazer-nuget.png b/assets/lazer-nuget.png index fed2f45149d74aee14421e74fa75dc117469d01a..fabfcc223e742536a3939e4ebbf0187484146450 100644 GIT binary patch literal 17094 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 BHID!Q literal 11930 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 b!4O3$rBAdmT`k@`=Kuv673nHTP~iUodp;H% diff --git a/assets/lazer.png b/assets/lazer.png index 2ee44225bf02b4bcd19b0f6590f0695027f3f1ee..f564b93d6f58e2e2243b8b07d1f578ff6c8ebbc1 100644 GIT binary patch literal 448852 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 xr?ZQv-Cu+`CC5YiIv=YP{w28A0vz5KIH5x|r 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ΝTvK+yE1&XHz`YHthniw>FjH07F^P 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Π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

-wwC`Zj%52 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%0AV1l?--1cn%xmxk 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

bTTg3sarrv0>eYawBo=o8tZJLG;Z5he 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 XR#}gv+Z#gw^pC#Ib?q`uOvL{IyBilS literal 328536 zcmc$_by(B=_dmXk(H#;}qXeY8+2{tb=#mnVPKj-lppuGUPy&i52$GT;0s_(@-I5Zc z2W(?seBbx|`TzIV?_AgR+OBJ_v-3RXc^>ESJZF1y!`y(D>M9ig0H8H8)V&D+fG=Ny z0Tg7H7X-Ih+~tMR-_Rxq0ALmR`v4l<6x;)ll294xYTXW>-foMz`~(15KqzG^1^s>b z|D$*;%m*@)-4j8w{`@`(#<{GQiVKZz7~g$~AI=l7IZS$OwAf4X|Jgp5KsA+xI-j>e zYk0ewD;fi@#yQrsD{>>Ed7+oO5c4;|p2KrN69N-k%1s9AEejZ2Ldzjf;6<;?->>53 zqxw?{a9sZopvyKFLU1HJZCS`TeBESF!C1l3)qtIvz+e&Exr1^Vk!kB2TqwcIdlsLb z32(R8|Cz#AKU;Wr!;yMxpdzUmzO!|F{smqei7Gsw<9T%JVjeU5^f)3#AS~G)y1O;e z{-m!53#rg^+s`N9%ea^>72$Pk*zeXfj{C5@uu1ped;8UKa5+-sV^ev!W`5dJ_X~Eb z8A9V-LQ%iQAOHiqG14PX^}Szl1zpRxfJp5~~@~wv@I3;YcnaP7^~IEpOAt; zrt_O6l`|L`W{ek83hKS-fOUt39=^kBhjLV?O|isMtdl3cHVyR*&GH$@ZK}3s6T^(0 z&OHA-Cn>i6@ag+09#PZ+wDab`>#I*^-fxFaUE9*k_j_aV*?ecT&@a+HVE5x!cdz`x z)o+1~PDNF0x@r?BH*1>JS9<83_M66YML;d!rR{&^)XR-Z*`(B4>FWz(b@1kmc>%`&(ltO z>XE(LyYt_6I)%R&yl-`EbKns8t8a6@oXunZmV;Sh_O;uuZhIXFC#Yvl153Xu{M2YRbz^%!Ii(kdXcddCZ zdM|lje`(xw76li}HLvLUz5h#>|7%CkVgop?jU#VHdJFwKkoQ^+i2MOrT&7)U!qv*J z#hTRPehU5t>aLA|=b1C({26D~;&rcj+!U!2zK7P@&iTn-HF#J^+x|`7>@e?Wa_czHa4@cI@%;j( z#IWM_;EVbDaE0qBSo)IzVfioDseYT5j9>g>DTO4(U-h>nio4HF6f(&>1oOG^X%B5^ zOMj5mEp8H*Z@u#YZco0v)p~;#h+R0=e@jgNRV|Ry5+hOI?iK~T3w_=sVmRNdVnR>XBec3T_37 zRYXE2^$Ju?zHcc44*p{2U$J|=c&g@J={vr$o`;&ycdB2EB(ZVcHMytXnVFiF#``Ka zH`?FJYm;&wh`Gm%3w*GVXkC)DA-`2tTbJ?XG~V4hly=3%=J8AA^$T}}qz#%awegZ4 zujt=X^QHc_vN3qisoh9hd_!7YJ*u9?K^v2NXQ0*2fqkzN5PeMLuEvs}Ip3#j$gL=f z75NMBe??IvLia7|ai)^y0OowXc4s|d)2q}Ws^zo&?(XiqoV9FkC+84ODyOnAJ#hDB=pvr1dd&hSCs-hU<)axAqQKqU? z9v&XMyquigTbr9x+{*AuvsLDmE96wvBo~_1p>aiY)bZl~rXt!yk+4)u@P|1COvA?? zZte%GxI{WmulQjwm<6lyyBHt&tBH5--c9w#ol!Sei#K5f1{x^GF?e;hvvX)4;lq#j z82>x-qTaDdnrmT%)aD>bI`y6@D*8jl6q4xZ=*W@d=kK|w#E-Tls_UC~2^kp~b++Xv3~%x6mMksZKQQq! zYsEpk;#(l7K_CC=ibU^~^;tv|;cW%<=k%g0m+;6kno-3O(xq|z=nyN-lPCmoR%1RfI zG%Lp>;+cGgytQp%0^o+~JPRRpWSi9vDCj(on;5yYh9GF<_^fba>g?>SOW(i%+p9}* z8TwJG-~d>099hee?$bgppp*j_tp4A%E`~Cg{+aN(I9CrwAGy&Y6kqjoiujzkvxQ`X zG5fpQE{f+G_kJeb|IQf=-i|p&tFV1t6UrH4KlJ75lIjVF-Q3(f85kIttGY`v2U0s| zBw4}UnRrHZRlJXJXhAQT?_aQ(z1ry^z%^ZxlB|g1cRvcDCWc<9h4uIMYka4~88k@X zlo@ja2MBMj{o$OvQ3+HdyZAbuCr~)}Tu^1e8>?c|-kgQ~R0!({quNOSS%}oK{0J#P zrBPh@Pkp-j3(S?PsO+#%r`)NDr6~8T5_M_mO22>r%RV?;{wU&YQfuJAuTzBLl33d{ z4C}Y(+AOrpWCQKGdEl@t?~YJy*JGWtIPF zVvBrcW+v>9IXfa4c3VCGwo*bHU)&Tf?f#Eyq{P_IiuYlsA<{M(Q&wvD)v~3EyPvV* zQc_UKop5!ejR1NIv_dXuy}b6;k5_4h8`mSlIPH$_ko(WhhR(s}#&p>zL>@-F zn6lfA=`r&%IX)Ep@J9SW6yNJN?*)cCD%n|o|28oV*1aJwW65kJc;Dk`P*wM}U_CMx zk8&!p=z>}5th(&1$VfjwB5`!#m@CLagRtXI9k0#Xn!mC#KR?`LMI1fcixi1+4-P(R zk389aRHDjopMnltXbDo4h4HSGN^vRt zYC{jJO6?_~uE}T7{{2eUaB;WP|($2UH z=-OwpYs1OIGryhJV#poyVebYk?5aN|`m+`>qQ5TAh1230#owHBj`2M`rp>1#rTcui zHM?BNoi8~#F*SAO*tCRbu3I8LBkXFOc%ZpuHuAKv;*Fjo+!Py`KrBtuJAUvzJ<^ay zWGYPo{I7ZbO{Ju8>*FTh$ksiNg_eqxNeP7ODN#9mZ}HP5WLS*b?zf2WlWaF36AW}` zW6)aIvPRF7jo~7U<>6!act|!7DrIlMs72J|qD%Zsm(fu*>!Z-Egr=XwU-g9HO85i< z;q1J)bnojo1*7NDb<$Iu@kb=jQmu)KKenY_8kb)Zg`&v1keN1Xtu|?Hk!|rCxMts8 zExNzfA>ZB>&UczS=u#rUwJ?4cx4kt{HwJT5f;@74dMBO>_E@CI2W}cJ`^UA9I(u|)l6w52^VgZ6qo9|IBLNitC2y6%4hCNy z>enuhxYSioADx{z?#iJy{k*(vyr@rGP&Z2-nvK@3KrmfE$;tm zt?#~x7FLD)QVqabB!M9N6;4nsS)hG6iqQEb`&(muNyz9xj*-LdEx@@nC@5FPqy8;!mTQAv%TkCbsg&$URDYLDrtoH1Ex0_eDGUm;JypHcyLvF8yK)ET@IrhOxcDQ< zu#-LL+YJ%*UzgQZ@l{3p*e7W{NKTP!0*RN7T0lq1GC@Hy&mqvSgJFi{6@0^CqZDbmae^9{CP3Z^*hCV{@U%<2e`9~aI zD1qmX#(B=2;BJhJmRM9szBIPrbWKg_W-$o?yi$63xntO#b(Y|8xP_7;(5$!Mbx0_~ zB0b4R=1UEgx0Q(J5ZINJpWtVm8!0Ha&yFUI;$g{hmP0 zOQlLNdqo#~%34O7!4lJ_K;2@?^_T05o1c3KUtQ)E+g@jT?qZ1|;IIpxkyeO#_CxYO z9MD-ygx%G#Z~ZX8Ht47+I07kobz?meieCU-;nx4VW087=z8E++*&n~h_w)!+F6g|x zJn|8F!s8@-Zml9f+?4+y;{DA>ADWtQZPFSr1JaFM;L?q^h>^d0m9@~njdlKL0zS}j zezL`YxYLgky~O7?f<0+F*fncg67({Jzd_IlzP#oSE!k29YRaNp(?V(YYF(247IN!X zve(C;wml2hkBEr#dkyv%F*v(L`uLsuZ?+(?+O!Ze%thR*Td+C` zgVNK|Mi<{K#cS&xh{ph(r2lq8A5;8cXa0JG1kC$azO{{DW~?2m;D_|J1zz>Tyd!5^=zo^~!XOGrq_ zkpoq)qTy?H6ay@@G(?YK%}W?Apli7?QcqZ-@O6s1L$u)}a;t^Tdb0UBN&nzt&G5>q zu};3zE2Qviv$W>H&s)EO3vZfz$@=uN*Y(#yLdJuy>-R#MBP&%yKh?j1Ug(Fc>_I=b zSKy0Ok485CARLu#T7nnD6`F{@9_%8Dir&mkIBF0->kxTS@P>1zlP_zvPasc;loxhS z#*4l1pJI8`lMIg2b)?F@&RxVLk~ZE^+|)2+q~&?@;;vDsl2`fdqF>f{5wkKuaWni! zvpU>iOV^utcSehm@3^+KK2wjime&lS2)|bxg~bGW6z=x4A+ACEdK%Tp`Wl{T24Yv? z{KBlR<&J$9wpQwNe_!mZu2b`A#lQ(2PSVvs%z>bF1$O|Qd{E0OlEg(%1;#h1pb>YG zVb+ja^t87{E~|(3yS--@1pwHdCUH^YV(vRM!;q`er)eQ%E~mBi`pNV3Ngq}5r+N_L z?8kTeUuYt|Xlu7Q8Fx78O{4N0(1~w3GQVhcJa|CY8Dmps|ElevVar-wBSo7icp!Yu znVT~)B%Yl&?w1mkm?EfJiB!N?z0dUfh7o_*o@@XGndQh5pPNpc&zkn_fz8Tn`bc+| zPmv$ZcAuSdWx>s=+F0H`R5MpsmDF5Kvq(Zb^Zc;CjHfNL&uQC=QL{Fi*YUb;=K8io zMpNsoHKxB$_S4`~S@M*6_dMp;oSscaLGQIlI`d+;l+8a}o?eH7x_Bwl2fqN2j=Udl z{BP;|QaJo&I3GYq5ostyC(5xReS*x}ATl<)Hq>dj=S!e069r8@yOLKVdlQH=z4 zOvwm8(#f$gVcoOho&SU9@dsB{jO5=67N&GpCT*7qcj#5#nm^L5H>xQVrHC|`?vr!X zHA>TF$Ei`rob4X5<3q&jh9^qBABCzas%gHqEIc(nGoJTg~^gM?d(?U`=y+y#iwG>XbTj&ky{q(wMM32ZkgxwIQ^ybPBq63p z27#Y0z*X5!@F_;3s4jkD`giFRg#ASy^^SuLuI-v}!-9owuw7_;;c-euYxYjWirrKl zF58XMQ|jlq8T|TZ#+w%LR)w38L*tbvm80xT;rvtD-=83R4FM{=hxbW~Vrc1_Qw+Yl z`no*~?u}0wR+VCQF(|c)^@YfGXsg zT{hHF5}R)hLTsTK+@vd}Hkm%SpCe@#OZ8`d0V|O!f(0Y-m2 z03n$iqy~VWLmfuBHT0Qx;{t4uneFSNB>K&>7VK5k$A_Q9NzJNZXV_(ah{Aq#$@Dn-uWoWLx(n)gIRZ%eeCr*z-c zmhqj{cj^N=Ci;=x7v9`j-CxCVO2ZqK8C_k~{jOq@x&T)qxpxGVlHHd>5>%)4PWMvnT_uZ=Dn8Zq=zs4~vE zJvqTf!kuXwzCBYG;=fJKSv|Uibz8rh+K8%s$^MEqQ(!SV5IlP&i>#QNgoX4qU?W_| z!v=WSFnXB)M?Gx@ov&SDcq z7&us*y z(pSq}^ZV*jZ>h%&$DB-zsPaXp_;$&jq=mVHRK{=3?~_*L5B8?~KH_@Bu_gaK20}-r zEFPY(EEB%seZ^Bix|xQ;K8&Zx6eRJw0L5KJ@o^hTtlPS#p6B!GELoM@darV`eMesS zs!d1w5&1T6XBjjVNpGI3+L;PyvC>OLc5oD&D@Irx=?8)Ti!-&tg;$4D5uud?B6RVv z{$}PX{RZDw)A+z(*{6&RM{JMW)axB`uHkiC{CNfHrB4H<^I>~UNv=Ql#I&3Yo~vZN zDIz3Q()+=A1Zm+ofd2?mUPHq#=9UMzXX-qX93r#^8M|`O>Vvy@$-L08ig8-!st5AFo$%#C7U?L~YA@ye$Ttyr(Nsm&AJ!zoOI1+0(oklf9O2s%si|5WXbv>HYtIaf4U?}IblNxp@Ql1t0|PdJw>5d~NG(?$q>!ClIiBn1)Lkv~Fc+Bq8$ zmt~DBstcdZ+TEM^Ts>#;E$QNOnXZMdQ^Ii#U4GAks_sF8IFnK4s^A?5eylt{ZQeNJ zYx00OW58?rq%TZv>2BA4K`@~70Md-zMoQ|c#(gwhz2*|feCrFe!0*>o*AA?aq_CO_ zcp<3ErMPQlaN@AgGArL>dod^&jcp5C#?TIVpT4QAUNj2ek#^(YpkdE&2N(dWuCse> z0+A?{4!XG<|M#Rt{9S0}2tqRHY|1;AwuJ_jAMe3CIE$Ng$TR-_z)nz8L`1}01x3Y+ zym7+13g~|1mm3AMYG$11zW>R?Q@fmHsjWww7f-(BYyTnosb97}P@8}LL?p~FxTIxz z7@y40=M=YMBdg-X5ZdJj%AYKLo?8THD3|Qq;#QWB8#efFW{@e3RgzR^cTUr@kd}6@=Rp!H|ft|0K8uq`UXeBXuAai$fFK6mn$WB7q@61r1oH!L~Rt_;ZDdIX&|j z=BZBKAgWgmq2L?=12iWsEiFo*zWeO~7mzORMu93Y>K*L=GK1DaZ0k4X367Kz!GK^Q z2+TADt@|w>Lep3QEI~U;nvRWbFVg z?@t>!wv5bwy2>Y%uMZj-OK_uVvnV`frhptnPIN;_?qLette?mNs`|liDV2dM%`rb7 zN9OgPm`9}6E9g!}rnLvz#G%U(rUGp~*{kc1I!?YbVqSCk{tin2-FwDx-*m7iOngta zRe+0}Z&BV^aEem?yH>#j_W&?cVT0Yx8Kwk$L#lF4LS>uA4FX>cftR;Xn*-lGQx~2v z`|z8%->Ys#-$IbfYvN9PTh19n$f^wu3xj?Q!^{tUys_x1dH8y!Cc0pr{@D?brN;OD ze=ymea%7cuNaBNtzF)wDz>*fkLO*Pp%tkWXwRQD!|rRoCiH>-B`ic z^pH+XZediqg82u_p^pVS$)f6Hlh89gP)ehELV@cyr#hD-r-@heIfEMU&S#{t7=Lt? zGVS7RLoRZ_7e4(E7Jz*o@_Et|^+FopH0jfVW@J`|3`B?&kfXQJhn@m4K>C8H!s$NI zj_w)9>sRah9HWg5FQZr{3fXtcn%xSJ55h~%$uFPQOI z{m&ITJ}wXV#|4(@m6t0R5h?I*BrS&nYQ&E%Eri1(|C%ly#tPmoUtfWYR>D*Urfdm* zZr8fUj~y1SN}E4(adVpPco&ja(?eZ!1|i7wSjDKOw^4qqwlLBxJW-crWrP{3sPZyJ zrrYuDBAMJ6!eCF7D9&Rj$fcxEncOT~Z;S=nQqa9qeADax3|s0vYwnMm$#6#@%8wtm zYjS0$^%pJb@mwLlRVv zu!COj+YlzOKp-X;$Pks>#A5I3CDI5g(hE+{v3@R!4vMPIq~p36zk0!2KqSR!eQV_c zs)_dQP-9TU3#o2`ycI93U=av5j{h-mDktS&gn@I z%CdhG^vSkBv=cg>2HfxM#l{>>v2`ZMYZ)~*RW$DCy`ezMTRdt~F8tis zx$3FNH#WTqcLtYh1CPLt1`NlpJSxzE-e>Nd<+$Eawo%ozD*Lu4n-#;VY4Oe-vg5yZ ztjAr=5ihg%Ijn!2t;7uSrc(0k=dRjzq|&*G3!2BW0SB@Sjjt@GTN;?TG_>)Doam~8 zOK&XEZP3Q=&~+u!0hLKdm||&*S{}v>-#7af%EAS@eXx9Cb&6OU`jqccJ|nKPc(vYS zY<11&K@iU7$-#o41eiJjq|iRlv~H{a9|2#~#edBiKks5h#PaTk^+M|de*ZuW4-bFs ze!@Q$v{38LHW1?OiRI}}ZLOC4T%!4h<=4u_wHE|6n=&cZ4U-~V(l+jU)bp(V8{c_9 zc1#Qh!cGKYS~Os$2c${!7@de#CbQH5fn&#_V;)P=cUH1*pFaYCZL?lB$3?{<^NaUa zXucmYn8`Vo?5JN;R&X_HHV=1*+1d0SaqY-w%zL)mRmXCwY3;e#By0|D3GH@6*RJkM z8#7Bk2u(;9f!^psa=x=bZpY}xK}NV>SxF?aWE43zOv+@>nLwRuy|Hmxk=nnQky*%f zkx`l$hugCLD^1?j)wBj?I*Zfd)XFCAKx)nNjUvRCb3ptID8oM1jdVo?DE@y{91XrO zHbPZwTey~OWA=h9poAkS=6&MN&Sz|#JC>L?(svax>Jh5m=ldsRE=$hoqq{H=RCun5chloiVZD1oo;>%#f1y{*|^-j zo4f`;4*9nshw<0aJ#DWc3xtbXrX&UhhbFhBnU(t&cfjJKBG!LMX87eJdnFpxb+E%S z?&=b~2ahJD&<)G@CHrozMO{dy2Orw&yE&*|oFZ4s_F9{Or?cs!sRa!7w1P|U2`Cr8klRAOEQ!S>_d4)gcH_-45ILVjKra?;zWoQJ2rcCL`(>ecaNk) zvm4ZKB2{2<@v>N79L3RVo?da{Eie?RED%Iyn~4nllW=9I(u=P{PG8XZQU}7K$x2MV zUS3`eZ$}6dGevU>@5Tm2d&sj1$}{_HejZzxMIby9oXqgAwbl0MT9D)(%@7ag$wXkHc>)J#*K z`=evHQn&8W0F8L>aoaJKEImcAPiF>`^4*%1m;h65FqIn}S_YPmLiLgV;)Z-1p?j^w zZ!99gn4fqH35bqSwu2>*7mWaYEH)skB!5n)O<_HNM%w$20rFrTMoTS8w#B+vY8Qbv zq4!rUs_V{mrq1fWhzf z*CYN5hST1PLsVzW#pP@arBj=-@BwSbyv_K~L3q&33_4}`i1=U?03K>4gxQSM?C(3BWj zxAjX32j|t{X=T)vDet<_vYJ-&QtL&(-!`V`*VEN3tGT#!=`HG}d%sYnSrlmtc`Qdd z1s3MW3j(CZu%Cw|x+48wF({KqbpdinrVkhn=|S-fyfh)C;pS|clxWdCx1s0KJ~~OP z@!%Ssu`1CSQw$b3O|Fii;W#_$aD&U~YC7i~jkLTaSI> zMsPm(r*b*!TIp|uxKCHfG0yZPhywNW$gBAFkEriNE?23Wr<5|o*at_4h3BIbrl<0% zWsHOgcE~x1?}b^i5LhVB2ov}h{>j`F?Fqx#0>9Deh?_ge-cEY$veaD57Avns{s0V|MKRWIf|(5u3E0~fGTe& zWN%3cR8ic|+y-W{+B-@H(!uWUQ;@8J6T#$h4`Z_3E?Ur&CDS1ACTp9GXyk6p2ec%4 zD4FxTiHY__KI}0vi$?uU09o=XP0UjPq*f{*Rux2XfG}DG9iSSg-N{WORcFo?KW$TN z2G(GpMh{2itqkrfqBk#p%Rr9Ui{r(A$|61G0J_)!j7gTi0Bd|X{^M#5As2cYgm=5V z`go}lk-W<}DwRgrcBMgGhC5qRL{)WUsvG%7+Nrt<@zSH;M1e0V9xnsUH9QLW;+>43 zFQSc{PG;J^M@3Ut z&A^$gkjTaGo5q*CR<%Ms7Z^g-tc$Ee!?lZQq=M%JF4>)PA`-?0N{&dBHi5;^0Q>1+ zj{!#{Tt67nxsh}vVH$m}fk&u5;^+*jQC_VQ-z6?+NIX1gSNfhTA6z%05qi+~TFx&l zH9Q0~6j(?P=!$-)|9=GL%a$!7IT@dK0lDBgLrs}riFq1@DO^oMQ)U`noYg-%F`jlr zw}88HyOsv04IiA0P)zVneGoGoNI|>>Tv}*bq=0Hs5aWAmap2-ZohMME-)x1vdM70< zIR-}@_aoU14qk6-<)rHCVt$`Th}U2D@E+4NDkb+K9cGKSJPEb&#TM-WEy)?XK3ZG0 z*~fkvo!lIk(iz{k)i1f1>ip&2`)1ymI6?#sN6X;I;dk7=YP0Z}`FnFxvhjeO=i49< zksDxRKpCsEB@UFQ+Edbh4Gbn9=>rDKykhjCu_qhUdh^2a44Fj7<6IDr%mvYHGlV;G zfgB$LI!L*Pe2|sgs-TzK$o*{!=M5si3RsYnoCCBqz8x2s{A z4~>k?qB=$h26UUymH@w#eG^2!4c3 zPaMTG`EX3D>zNy53>Qyf3oQDErwN9XrO)dk8Zrp4w&OOaU#$om-Z~l_5;@)imKr?J zD4g^KHwZ&OJWMmvpLSn_F|64ia6dniyy~7itdIr>l-de6!_MAm{<85S5X5DHbl=9t z1TmGN8y4*YT4}g?!;EVVYYP}B;uw`R?|A~-gBbeS7RdIvhYN%|Z$h9UJwd7!+ z5=-W_SAA+%W!Mz#U~*)$c=!8ffPTN2VBUfNC%yYOz-0M{GmKT0&V*u6A{wj=HoPG{ zMhAJQ{TQjdx5!Ws2o${%UQo!nr>B~hjd+#oh)RSDh|4+0XOh+owP2U9^O2Z|)jarH z*2jXOW|TS zu9>y+)vW4Kloz(0iQOMVRkSV%C$k?)7yXydzb zhCBV8hNvznS*N{)vU83j8Tq86<3U!D#tAsQDrou0r8tMVrX1`@}9 z3;)4d_G_e>o(RMa4tpiX3%2k$%bL1|nJ$MoP)3_hzs!j>Cg}4Vuw{7OKia14@Ck61v$tZT7!^~0ieEc z4Ndx!7?hNLCi^!g5ZRi^5S?YVP9lI$l$)Z1OuzwHb1gm#&0=A339%!sHydft976yC zvY;K11014T>_dK4835cTE9fr}V}_J*zkdxpte%#V8lY<-RBz9Abyb;MgDB_hFi~P8 zRWAFGs5P9bE{MbjFbGWFAQ@8ZCHtpi*~Bw)aqAtbX549%fN8?CBEnC$r=*@U3^PW~ zU(I?~PJ6XyMI(f7ub{heV&ek)Kl)>(xG^PR$q-+fN zS-sm#zSQbMR%(06tX~RYo=ztS>(OXHOv7EaD0nLc(4%BSRRAb6qzmZm0Qz_HGMdCA z0!ImqLWU9N`IjwYtWo$z;z^PMzN}=`K6IH-K4m^pg~=1n?Pi-enwS+T<|UkQydC;t zp@*kPI$&0Q|LPWwRNP##M*#0)r)joDzJ#6GONi&)#K*q?-*NKe_Yclz)IYOhhX?Z- z<8;s4?`uv^2yHIz1Qp%aESDQ*6WJ#(?F!WWL)a0GF%Y!RjzOYJwji_eAgwXSE^4g4 zTTA+42`(})JBHeyqC07@B!o*Y^eP}+YCuktwP+|nIy)vgj%pDMpyCFLbfP0NTNqUl z_gu8a$p-`nSdq7o!*3o?frB{A%Y{}}p4u`QhXS-NoE8}vN?@iY|Dg_S!0LJXq4X?HNA6^Z9X_ zgW$U_SrWXSwAAxGiUaoDKCR=1y{&JC=?~l0lAsh&LnwBAg4NaK0hG^hN7(-XO8=BH z1z>i8P}mG#g{SPvwP_hcN6x&)vLZ{gPJYY7YbFJxm(pyvV#*|Re3T0W>D{Ca z+~||MXA^>DM?@6fx5hCr$0n(zKO~p=kwg*MOoy~SgnzedPRYi+bZ@?kUFQ~7X9eaz z`z<#n`fjV|Y>5DH0G@yju#MphqSP-ABn|M_ z%E#>O+}vdv$chKq!~I;~O@07S>nGJWZ_NXgd&px?KfIYcW7-r37cT%sFYFRdOOEl>x@Zgs>LHm@_$d>py z`FA`sTun2!80H=CWXO;<>x8;fgfr^b1PY2>!@*d7SyfeRj0~qStYjQwq?vBR za`vs0BY-^SLgJkPvZ25{_6qFA%>pz&cCnx)^!ELFiq{pTLy*CcPzn)q>KlrHL}Zea zHWY@Q=2-UsUB>C7B$BIeVx*F)3=)(>qRYv0i~M39kY%cnKp-C@fCqL(1AuYXhDdnb zYFZE9*JWXIDKz|JqLmH$!wX@HdA{WqB{c)2PBE>2(97q5|0V#mR=xYPDJ)-&UHiXjQHawWA|!2i=9Cvw6QXD*5e{HuoPMb2|lTJ8=3(Zc7#?ifQ+F=x4>g8>f9#+VyCnpV3pc|veA&pQdTrAd$GGot>`;@>ity_ zsSKi$1FjtZ2Mmg!ZUn>rAoJM^LzL4N;;{5q$&VWyXj`0vk3dTK-&0sh1uEaC6?^vZ zI5%f`Fod9>eX5k~Ap<@7V-Nx5C%_|!y5^T843nO8`t=?WK6WT)_>L+aCvfa6& zEHn;jT3z78+-imrqkzhQg8@DbYGI_f@)RQAWA*XyDhEjY4>6`hU{C+K5Wq#NpDw(gjYYQs9ejoP z_8d}LtstPP=J%K^5cL_|k#Jw;;}r0BCkI3VHf)Aeqbx8*hmp-HED-Jn*o=Hoc~%uI zMU?iQ2~lne9*!qT)KWy#g{5JufFJ`{j3Vq01*AIppI$=sXSYYga8y@?KE7xiZo7_X z7G?*g@Pj>h&nx;^is#4Mce_4c}eu zlPml*z?8r;1K{4UVQ#hjK<6YM2PnSs z5SD%x>&r^VaD^tGlux943fPU#paCXP8jlYwmEutj0o(gT^`GD|j!N?PpjJ7+>q|^! z2xb=X2bVQ*N{~9R#q1#VJwlgTB(kpfAD1WY9#@P_OdxD|1VAw`TPtDMkSL_Y1nK_o zc-(>Zu!!)eoWLJKRG!Pnf$D2&tjCCdu1@qTww}Q2Hs{x?6eOoDhUGq6G?be*v=-63jajL7M&` z{ph;L02qzym7|Y9%uhPzS>C{7kJim3#s?E&q(>nzzcnFhZ;J7wdU z9xsbPvnWYBctu$G#lw<|XK-P;U!i{%ku=P1)badr8Uc?1{UJP*8Qsns6hi@uRV4Qa zfDN+d9#|=wNxzyT9&CBNz=72MHCjG|tl%?pod$_?-ll_}i~&(=VGVL$vX(6u#zW>2dQt>P?3DZ- zTusv8oYMH*Ap&Y}o-x0`e)-O*7;3uXNwH)#QUNB3&xd&Zl>ZPLZ5S~ACR!=tDPAw0 zxA386##2Mh2ThST4@7VUKb(wDV3WASz_wO-GoD`9)FWqJKE5^=a-fZr;5&g#|IWyuMxyxHf6=;!3gUkk z?eJ3ans+x}<*~_1`l+MY_@m}dUk0lZcjw&5YeNr_7KroFdXs|1Dgg+U`0RbUe^HRH z;*9*eDx&7N=h->xc-#(Ue2kE?9Yb{IS5{rlRI1bRiCT35|-XV`l|f%^Vz zkL2o7He*sDPX5DB{Ki?{bA{r@?2I+#)u`?arnOAKdPPZZw65(fC+>y`YRiiQ!)T|$ z5@Nh&C!{~2byK7`=!!W&+&hf2FVZnl2S!S!phLz1LRN6Yl5&B5Ff{;w&PsQj021;& zw<6&UFxg5Q*w*t^4rvTNRU(f7KJkaNQ<6Uwa4u5?@dTyI#&p%>7KqV18*Dt@q^^pt zXnvbJ7u~VTp@RW$Y;J6XujPi#pB0Bv?e&80`2fJYv2`)y68|PGK*D!3TZF*;)D}0l z?6@#Hi(e4^gX-i7dEBR5dwld*HY*47j^r3iBfn1WJ#_^04*S5Xm{0s(ot7i9&|Z) zLU|bWlXdcVZ)U2r_BgW$KJP22^O?5tYia4GIos-lq?WHBxDlYK?E_HkrT8}w@4rL< zlMwJRVkkE6a*jrneRRV69-CL&_4eJ@Lm%#`eO}*z2bWFFI{oQ;zlJ^@6SfR+!|;+7 zdP~#}yarKdH6c}6e{${y2|FOKnyP=36aIq8@@~j!dY+yqvZ}}+?E-hWK8SEjlh?6YK=_}f{cX2p zoI5{zL!5iFcInFguOAn_mE^p+{F~+<3dv4X-W|Nk$l(3OB)gA`2tWt{LZGw#-K#)I zI(T9mo_=*!mARNV`i%PtV9F}E&!J7=HZIN)ueDN@h#dEVq{x4OnIQ~2mX9-~vtHci zDtU;gDl>edJ|6kA8nAbjix*&nkuavz_@bk`_SIaZL2(~mVEPLhtc^aqu|d<;@LOTC zfwtRTWExSs21gdg#%atiCh1&4upq=qM{g*t zAcXLIc~GN8sNbmr|IG?&u-(cZWiLIBO2`yw&M^p$K9V+je~Odma<+({MPKRaQox z<1%1_;Cbcw`5#1F=HfksZYsHtB%nQ7(%BR!45%Uj^iwgw{vS=>9Z&V+{r|cHSC?!z z`$nQ{QWWkrLS#i!b`r8P5^=9px6BX~A|o=gXXRcJ%HCOBvuk8;?)cr_pYQMW2M_;v zxW`$~=XsvBDUiKOpJLcI z(Wu9?@Q~U>KzraEVbxp@E5Yg72>eG|-2W<1yU)jk*(;ISOjx0)8YPr8HBm2}Tc*i` z`1AQYIpUu(5IY~7RkQoJjyHUc(yIU~(`2IV@a`7t9M`#f(sXo=AH~#<{FWTY)5k_P z$jmoJQJxh;>qk*p^4ji4gF9hoDLInwh(+EEkSJ8C-r7jY!fX?Hp`VnZ4;p6X`kmf{ z60g46F8ggVqgT%FJU5rVAiT3*b?_ zNNl>qsOVn)ZsMjx{<5F`UOWt@n40@_I-@?9ZdlW^=)^88*W(^w^oFS+^n<7rJ>prH zSzzPL8o4due_4zw!EO4M1Xq0A^pm5~P|#jEYb?bRy=1unIG|GV?y z>ut(6Z>~**BdX}OtsL7&fxEK_uJ|M>?w~jcHQIN1z$6INnq^M5AK=DZ+a^8p7N_u+ z=Jto{qi}d)uE^am(>10G18gl>L)+S=BJ;T?KukA8p)GlYz%OUBBn!$`jr>1*T+zU9 z_-X!5;yAvMk{p1f9jCnfelVFE7`PNb*N5>Z&)kT}e21{R)aMs25A}OrAV>dx!S6IJ z#6QiMsjE1Ui8pq&9g@jBUWvcC>+3Y^Chveh^urTt5Nm9C4UMA^G&Lox729Mv@MNiy88`%A`~RZ~NEFf4?%7 z$~lA6N8ou2`F}74ujENeETeyfD1fiFA|}Fz`Xi+yhfV`F$$sHJ0e`d(iTEpbESZ1{ z5LlRXvp&6Fi5NGd_rCph<*#&mPQa8PI!@3xp!Pg9pe0;RQ^iyWl$_y{y$JQO1e#SL zrose1kgFJO3s{8W#HQJ#;<2{d#95;2QE9Ef$n-0ye{$i*ps|frE6zJ%f}w2;*t#sm z{J-jKzU}oCcciyU5!{qpUd8&k2rcJo4g;NOa;!a58xdxW_=?Kzc&Wgn>+K{|$a5AIN>N?s2=l zT<6=cJ88CmD|jO!oz>OVixWgp)Z1ZRn^16#Ay(_1y=e>1Mab{}BJ?MnYq2lfV0(|Z zhSj}O582y3yK_k;a7mPd#;-@lzbtJCX*z!-)-BR%FG?+3*IN(snvv)atKM*$#jtT; z+-A~h*XN#*&WvLo+_xA-U0EB($7Z*Rd~8zd;hkmsPA#Y3L=f0#j$Si#$9HzPGOM;y zaX9c*H{ycZuVMP!{0u_($ne3mISB;w|9I!gX2V=a8auUtx>EchrU2GtzqvWE-YhAt zN;vkAWhrX_{GGa1&SHsB-61>nJ|b^Fn=jVY0UlT2HqUux_yqe50V(D1pophjWEDDg zZaljbN{Pj8@?>IHb9ewfz=Z)C4;;n)AubQ+HoY6B@=?O5K_P)3NfA?hj0I<=xz!^l z+k3^J`yR6~kRv|OI|$lw?0>PSOU>;-MNtQSQ!jnGUG*o6u85VZ(MxPbQqx`^c;A|( zsp^q!TSay$>6&EmZV?WQ<@Tdi3?~j1bHu^_{Gcic^Y)LNXKh{p9iE24j^2ODIttcP zraN``JmfQLVCvaw4{CQwK{hiZM zsKPQe%En1r?B_50)XNDA%N(;78M2kkBD1Rb7%Tk@ z9C1T5+ZjAAJmkkH*`u!uoL&^|gSue-9w^4YS3~|hfo2N-nl{1Q!eQD@XGEE&iIaC! zz{pdJ29UO(cOU4J&)1p zru$aTAB-suFS3b}HK3l9a1zH;97)_#2s@iA`?6hE`-CMH>zbX3LBv(9r{c~Hw^o2AIrI|xv zG2YHvs#$|9?dMYX-$RyM(9CM=yN1kG0kcmRQs{I~j)4WQ{@@_o&RUqy_y_;{Ms}Ou zzMY~&3nVLQXiN`}V4HoEKf_6AZ{Ppt6EtBgGS)Ai=~jDxSA4JT5o?b7iZ zU_|JD9`&|HWn3f~6a870^X_}R03Fz8QTg-9c&}ar#GSs=ac`IH(?bqSJZI^g1xA#X zt2`@LMCIHZrBbe(8Mj9{I)XMab~t>@-ZM8Ul%s=l7A2I2k3Jm{rj+Z7hxyhF7AiNg z??Prq3o$0LGxoRjM+UeT9yV8{5W>}VE}yugJ70uHiH8oGe;gc5tLu`I$Z2TtvFath zJHJ>EaCr5Mu;eeslq_%6nXdy4*xea51a`$$Hlr+u=jAd(NrWBtn;=**b{gi>7Ylew zbl~mnEP)m1{3|A;)O6u8^!nxm?1eG<27fv3bJ36yRu^5G${1^8NS~r7R(@`~F^bh| zdX+imOYn+{lC)gu_4EP_w#{$C!qRpRBKwRGSm-f|U^*TKxOL?#djt&hbbbmZCNfrm|slJJ<% zr8;~dmWN>Zi)Z2iFOiOTZ0(fZ-!>JjuJenP#j&fOp_8(`7c?(o5eGi?W)8vsOWLEB z^F*fA_~)o(Ov|d`?}6XHHQFtxOg)!RDm#ZDC`6B`sCKEZx$Cnyym{h%k68=Mi#_;e z!O;M^AOEgA-JN`Hob4(Gu_|$9mx83eoh@q>`C6?>);wa!xFc}&So7d<%7RyRQKCp{ znTi2CA~%c8BGmqnw5yS0Ykb%6xua^Zd0J)(B3T(3W@P_Ko5*aZ%IGMYM{@IXyw?>y zqyU<(6YpWc5+osSKz3J}>GZ_u0LBY3Yu5(-fBeJLDNN;Jz-3Se44Da}Fw)_*4lj$& zC{~glLI8A)AH5GWA~w_=iL?8a@sWX_g$P@XALx1v)$1GLRtLR0ny;BT{(VW<#PX9A*(&dTF3Fho$`HvgLcR>D!dT&8t5H8w#!0c4e#?KOJ1T zDLxdbiukE9`lqGi9lV2)Knr?dJbVJ{<8X!_cQIlXhBXG>zcJ-t;qFJ{To@l;wx3D-D`BqVir)n(z8>G` z*tn2ad=0*2J4Wj`{~JY+e8C@?a`zb78@-(O_*4%=ba!-RL*jjiPCTkkIwBmM&BLAw zbG4*fBT5WQIC*19kU-Bj;45HLVfpCKOo?2){GgX&=qpS(U4nT-JeC#u$lgJZQ22uV z?}vuo`+7}PXRi=P94`%opJP67n5gx{M<)UIz}z(_Qd}hdFTcc1cHl*JAK@&h-hRi{ zckbvWx0AEgWo;fC;*&+ZdTYa$cr3&0LP(=hvtj(3649>o%5^A0qZ6b<2kU1qT)%VX zBei$%n|Q$?f*qrCY%=gG7Kh5^$-{F}w5mnisD9rpFlUcBufl&Q3s1gGome?qT$be8 zjM0$uJYB0rSUIozI!O-4p?iGH%co8>*?Ki%R{(&Pm3}59tlnJdF;8J1)M{=x{TA+) z6{dBxzfQ9O7@x>?)im^dc7JslWGc*l1#~Mv9Ub0<%QJm2bVa{(6o92W>( z(Oa(S8X>QH7$Ul(!+N5tyIh>#IGO$glVh>Yl58CTP_zWSu1m6Izs&gLdK%VDkf+no zwva>)`j=@Kdngtuh7|(+RkQOWX^+L@@1$mqH4(kaZ+WTjlx&lBZ0NG%5Q|~vY;n+8 zcGrKr%Ec1tMg@jl>cH07#nm!5+FIKlU7<$_rr(vg^tWN!fPrf49Kx$LmSug!y!osW zRk!UF?dr8exik`kmWz3GAzF;p#c*TGgL%NCtC5$Q) zm>CQ%5-u+Bmpj`GT~3^C{5BLDreZB71ld!gd@b45ubT2H>zhaY5ZKnjRe_PqIkJ&cr)lN(_%h1+I%^mF|3ay*pMZ8pH|Eie* zmkPOpXz`p;j&0Y>ghSKew=lAKfZ9`0y%K>F7(WkU+vnV`~lx#TZxHf86D^L{O;MqG`@uhmFeM_wZr^tv8^B}!yh6Y`dYC$ zu5Mb=-#WxU83s=!xhHE6lnb7?_<<-R-hCVhvg$aMO_PuMSVG@>#Y_1pLeAgw-~q{)KxCaV%_ zPw1p!MSamKkn8^&2zwO%z)XjxE@V_=bSTke;=^{KN0WhTlMQ8ezG7&lrBt8VZFi}{ z9@={kBgu_+v-Iw$*9UY1=jf6V5eMxYw7fR^4t(B`dn^8?7&ZRD@GYvK>_lEI&kL%b z@@$z8hXQ-3+9cf3v3}vgvgJG8cz*{Erdg&?Wa;Qyj~=MGo%9FKNaACM#)8;4=XX=e z<#X2GoOl`jWF|=AIZHd)xujrX{mKKv zP4s&|@ka~wN6V-CX!A3`$u^%V5Jft1fDt(7r*$7appl3%gjAq?VHTC(JJr$IwQOfH zGB|44)C^#XU>i3AcMohs5Co@FT!j~~R~Ta>HLxU@gC}-4PsyCV)KTKO@Az3^pUB*I zwGr4K+~#r@Rh0l5p#V3(s39=PsacHta@xJpVXjAS{K)}}rb>uL< z{rg@vWV_lxvXd;2Ig~0~szdpj8fM+5w3%i{E&AI#pCz(a-$o~QexR<#a{8jcC=1bH zdogO%MR6*WcQ58SZSq1lU6O=GGd>aJ;bcmTnQSEPST(ZTNa55JyhPf4VLB-#PV?hb zY>mQab7R{Tck-b5b@wiyWYolC&S{FB85OrP#tFlC#GcryRv1;CFBg{Ge=O^7e`teD z>19YXQmHtBQ|OTz^Y(Mp>0waU-EBx*)}Sl1-%_IdEF|0tfFocQ#*VW>Ua{{Dx=i0s z=ws3s1nmg)YK1cuXi0B4^@qAmR{{Z0X}Tqg*f#1fgD2IWX^OjKu$n5ZRg`Sj>`$uy z^ctd6(@&p{PAVmZSl*BTWZI3fXSlGnZ)E=y$c<%>kR@8}S15BSn7AYNue7zP&ouGj zFQKk=hfNdt+lb0J)MVG!H>y*_-EEO=+{gi!%51O0tVyAHIBMvCX*TRQnk@46MIk;! zUk&7oYCCtDPL`$NP;sOSml+0nok%u!&gyt>0(pb8@9Sf)=Mkuh9S+i>6WbehdGaAE zzfu!%TZ@?Bp3I;)**PoU>gvnyJ1H7SPv(Z7a=yUq&`LyyUw=)nxa}~`!tgQEF0>o^3~!+^01kM66KYcl(pWU)MQfVM63CC>NPwF78oV_N8q1Tv?S<)=uTWN}i``;<4@^_-hb(~QL@TeX5 z)C=bCxC z)fQqQHXUWpkBrAgn%7IC8yQ{Ou|pi?wwD<_Zt4L$AQb`h1mj37=7M`(h}EOc+n?X> z+ajjVJck(b_!1^sJOCK9Z2?gHKipM9d&;k*L!GOpMFi8V-G~zO)2*}t-`k%v_kYtD zH&c`;_w;I)wtM|N#q%v6pfFm$dPM@LCb{`poHVOx@1oJptRJ+yZc7pz7%)om;k+d8 zzL-8OES~IFEkZfgJjx&D(SveyZ*ddMcjBK&1ta)Y*={ICo8|g6=roZ-0$#mhoi7k@ zl7{>iu*3T|NOM{nFK+x^p8q&bH7)dt>WSXrzl(Wgu|B&Cdi$?2q0i5=Cr_(}A&^G^ zlz)P&0-eI>^M}{Px0%DoKnOHA17m-WZjqXWC60tc@lNRT+z0nWx$DEK{o(b;jIP3KhJs`~v|<+qw)F$`aa;tauE%>Qfbz8lcq|3n=f7muSr^I0Ql z(zmx_&bV$rox&gAUu81Bzq?KRIr3Ybcuv}Ih$ulF@1hHJTZ@!ZLG_#0 z814iFIF>wA1H=LJt=C?YA>o9kq%5ysH%1XKtlh(f$=mK)I>5-2sD2o)dQ$LIVl#84fcip zJ%!IsVwKnEV={e@J_irNT^acse6U&)O1t*Cf4_gSYAAvM9@ejJ?G~7S&-!=H0wC-) z-WJu05#XYgmlB0>f)8DxouE!mE^~FP3Dr zZn9HL;~06Via3hdYQV0T1}O2!9rp zd|L>TTaq6n$}QP*PX+w$RXiw0W!r=4CwNhKH|0KrJ^ z>2sv6AG3*4PmHJHC>$0`G-J+Bst@Xxe*art=G8BHb5PH9efC*yfNv%Wv`k$j$puga zpKGFMX|t(ef}5!KGL-wlty$T#VV+eMlP1?t56Crb+)LE)I{w)(G>0moNwe93ohi>@ z``d-`h<>79i^5MO=HRDqcV0pA1#cbQ7W0xtGK9dWy5E9jfs2Sw<2JZY2t`nNtOxeh zK)f>hiF>60{hv4zJ_|!uXG^0M!RPZq?UZ%g~4?K-OrRA!dF+c;6bk} zC8s6O-Sv70!a?u9b_&CS-PZVG-qld`uR!NxAT7+<^8Zu#-yXKaL=9ZTEUx#~=jZ1; zU;esZJ@qccrL-n)Y(x%kV1_Z+f7W4NvL;tUHQgtQn_)z$q8s~Ph7OOx7+(}kykUvz zS@M_m;R8-<81rdo^H${xZj9|y$n^Nrq#ZRTGzRMweVZY2`pYTTf#gTz6#?~c7Zufi zVWiUG-&|#NWGrue&j%H8AwBOn#nB-eM80}K>JAg9u`y`?;ElT(iq$sHb0REMVoCMZ z1mSMR?Bkn4&PATL^srg1(?7vhBGVirjfZ@%zIcA7?UoEzf{tdPt~oBB-pIm|17TPQ zQjQCq+Wo)O$Xk09`4iPjC3oN>?ITAq-G3Y=!~sWpwwDs zjn`C~C&j+Zv{lSwwQlKQDJ{7qPkM>|IqhKqn6vb~ab`J#AymmW>fYKk^LrMF7Ke7B!J*phuw~wbSCOMtdvD7Tm)$rG0Ci%qmM!$d7 z&5#ahIUrcm?YapxL-xRk_lm0}-)D(0IL6>?qGGsDD3c)Tpcl;X$= zn~&5f>a%zg9qNT;%&7osQzd?fKGUZsEnREm7UIRRrpkFF75RD8@r9h|E{m;j;he-t zKm{S4Iqo3O5;UT1#V`L%D1*Me{2t9nl*r~_r_bYgv!}rEoQrnzce-<|3G`T$g%>VD z>i@;$Q2cfksNNY#H+GZ<-3RK9 zJ!Rzy613J6lVXf?B!tJyeAGZcqmy>v>XRPuO8Rh|VT+^DdCSi4wto zr1{^Y8%In}D)8m)s2fx|iZAz^s3nSSd3#ceejQe2ayJJhtnk;2U_!GuImnx%`|x2@~;wT!wp&c~SZ7hh0vw>jZml zaa~!=4^{mmG=^8CPV6v)XK7PjvHbUnd+a_h{u6mURJ|v<`LeG4pIP?qXxVi5=notH z{>Qh)u+96-^uPHRGZHt>Si}W*LJF?c0XXQxk8fW<*LYr{3!KeOkS>?5fPXO)#6i-W zRva*YAz#?q?}k)G(jI0`dxg8N^I|w%;6(fRGGxE&k18nQArS5SUx~F9;oXlM-i;!E z7GT^zU^>LVJnE%NaK8IkH{sDh*Vw8|?7b2fY_A>?(4&lbY8W z7eSTlI^fO&7LSB>(=q;u;~fW^lY&a>sY#nB6KS)4tyY;nOZ%F?p!MeXIBF zt>`WuS~vFb)#F>XB7T|*62DGzbcMf#FOZa8`+>?EeAK%b$e*5#o)mYep5%A|)e>+x z8QxZVdl}Z}0rdKX!5xud-YYDeORoxu(47;<@cUE&bni>D5T2t zV$WX5FWkZJ^wr%t%PjbhUG!)3#b%tggYnSC#MFuVTsqcIYe2Ah7?#U{(HVl%0Mej+ z>VN4fxNK|mH}&34b??!<=!`>4CVHZTA_W|#7WHmla@i#n#(I&R#%8?BdSnB$>X_mm zN)XQNceORY>tiM?0Fv8L$n4ADF1~*_AaM0%*!Q@oGpqJDzNe7g6KDd)87c~?qO_4Y zeCxk4CjG2bNzMk!fSamGeRWXWg3{9nG;)Z>yuhh3&6b(bJ~|HB^H7z?BWlCE9~7Z- zs7Xy@Jq}uYiI|-&6A_JK)R(au!xY6K1F38HN8uz6 zX6!ut^|Hyh5@ea|7fKqn{D*YLBim^%>(-I;^`ExS^D-0i?tYdYG-$|pR(v-t+2G^( z1(9=~XUPWnFH&JiNEocuT_MKmX8inOg3;Iuu5C~v65xcQInL;S&pbl#{NOg~tdx#P zjZL)qA_8STVTFalut6MFYP?ESK^1;$V}AQ9O?Ah6U4OgQy7p9W+V6UpJTmE>&R%~U zCV_0buT-1Z{0w+Hbc%_`H!;d}xdb9dZ3MGambUqaw(*%ulojV=~E+cFdY` zfr({vvT4~XR=^(&e31)U4HgtclCKoX&a1kX4oCl8&ui>kYXtmLW%UiQtPU1@rvxN< z%&^uR1REu8D1ZWPK{65&SkAN-fuhd%WV-n#*J53AKvxv|*rEJh05kM&W<%2RhmRr! z2qhI?M!f9n&tImng>($w6gY!iFievTH4>zLjB0(2t|Df#qbK@DOiRJ0W@ct@?)gzy zA;kMs^9-6b{eXOyz{A_N+Jg(rhwojdKaS>7mlAZX*Y1FGP~SG_c95SvSkE&0N^RL# ze@X6kB4r()jh;_XlFYIS#QZqm%GeB39FglM$?2viZ|dp()C_%dMdWdt>uYtC2lwa< z;b5_NDyY^t=#nNFbdUUW4c>wG#;3h4n<8<5!RqAo)H^LUCT_kPR>RRV;~HFxUhie+ z)1pW7;oqEwqf2U8hNF3x!HzlpAa|WFMl8_qe7i8)y-1*t5h;HS3E}~96Bw@{$H95x zScegsj-9wX zrwOES5eROeJ|y68Da`kzIj-~N`;rv~?Sm=I4tr^DsOXOf z^$hvh2c53bLi2>pfJA#xnEm-!KabB$o%~W+npQVLA||SRPjcHF@i#cA3N6P zWfq9VZt(E>aP$*ff;(R1>u@7`VBt-KqW?_MvX+#;<_Oy}1UdqA@)~lk(wY@n3aWnU zi=38b0QcxC6Rv@E86(d0mBiKZ1Igfb20jq02!dzD1FX8eEy8wkJDmUQ*h42**F{V^ zuStuA08}0`VStTAVrwCV^yfMwi72WuN0f!#SID08Dz-S^8SwRbTDs28BacCvU)yo` zOqLRD|K+xvAEjXwb72LadL*1bYF|l}Xg$27+t24xoh489OIWzdPFDEDp14^@zb-!B zf=wQkS96zhDs9*Ap$cS8tge6Coma+syw)3E$V%AXinDog7I)yWgn4Yn7EG)9^t|K? zoOrugLz7ho1`w?`_IES$pGXu;1%QsVS^ge2n)a@gb6pB+WM2#MhS(wtWw|vGPsop& zZawnI=7J?Yzz%fA#>AX~At#kx;Rm&>V5|VT2z2iT77DWbUJtT-`!c+cvEKWQTiqNI zNPuS?2KkrlXW@{hF}zCi=9R1OY| z!kapkOLCuwg*v!{vxv81K<_ZI-WCP-IY57l58~_8MMfR;7)Egz5%R?Y8$mW?jAT2+ zRoiMP(h|DCu7Y>BFQt5cc(Mn6B{0;p1Rgr~9=_%sJJ)WFC=Ynw*yq-WUbL`9Bl$G3 z4=$qsFbI05SYENOn2|jK9m_J0RAB%O6oalC0*%qnOgH-K=LN`0(x{ zwm?sZB$Nu(VZZJxl_z%3?=^+5X*eS3Tv-MDycX&K*`rZ`ew%Vkj-Izb+&DAE6kGcek4}10p=`SmUX*=sOI9kdd zM9jU}i1g`Am$cnjXLc=;l?U&CXvC5<-v_*3Cc=wAFivRsD*y@w_3_d#0-DqhvxWnA zxE_0w_0KX0zQ;EBKwg_QTxai?1n%2RU-x3xpJSX>4rVGHuD)M9lXSXm#JbUciKGHU zt^$lpuK=`kL*6QIp8j}>HXl+M`P6CVU0P^(i~UmNVrTb^d;d!WMsSx{)tY$H@rwu- zPQFMO%{5x zQyr|%-7O16Qrdy9`80y)Z$`nWq287Nvp~3t zZVjSuql}3pf;Q9|R|k0=>lg;iJn(D;u4l{Iur5aU8SVdiJ`_!8FdAf_ef43SdOQ{0 zNGP;M@Q2D`heZhc8<;GGm6qW@E8ySlDFC4u#-4<(E_pA5y#_X8MGk^FCxKe=X)!M7 zYpG#Ci(TKnn7Q1KecINIIq@8*g%t@6f5f36$FQ8>ap3Ep8+?=X4Y$HY;&_d_5&N`d zErZi5!1pd1eyJfZZrrigf5*D^35fSf{$iFYye;A{({Rl!m zq<$p%+=^Hb=|MS5-1|h+AE9Zd?`BEGp&|atUIU~1)*Y&Qda2(h$h`BgkI|{Acg=hk8^0z6WyGv6{`EulU~XrGJ&r@3 zh&RI1)(HFbWB2d3oXU8R;eFnjYo-hV;r-Vd(XN%U0N>HY@J7=zC0u9`{CgimThuc6 z6LSmtqk)u@Vq7)U6rBzMqBx5{cay<+GT4Z@0Zs?-pDtE;L^61G5;%TrfGuTAOvN74 z9cdCU3|b9a@3p_^-oSt1oK`KdxL19Dvg|by`w=RS7#+QzIrYTC}kXqrvqhn@TAk(iJ|-Zxi$e~-dEf6$BDJ=*rb46d-{pi;<&Wtf)3 zf`_!|iV~WZJh@=^D6M%z{M|f&z{O>84(0^27`zyuyXi%^b08d1VH3y3s&%dU)Io=h_Sh{2+mL>Za#>Y^)Hsa ze$v*V+6M87k!qwMgB~xw&JM_cdO8ScXiiuJdXQ6M80dvxn}%H?m`T8p!vHr7cN5a} zP~R+? zR8kjrQJ#F%nFGTznl3#j9q6^$n~Dd0rONYGRZfOiT9WEF6b`=l&(#TweX)cHw@0y> zpBi$6yy56APn%*1sZ0LR^$1MA%|iC(rZ%~npaZ~>yU1^gw`>c|5!q~R@%|Hw;Axj? zi5rNG%(Ka|(w4S`hKN(fQg3~RHfp&=%qG5_5`|sDf{H-A28Mt>Sk<*>81VZjLxBEE z)_xEyG7$p0!HTv~Qg;@EIO{foC9Y-xzrll+|!CSUy@@Z^d=?Y1Q zMCbP7Deo%ksr5AJL(J1--tUJbmNvK8{m5;R^<4>@+IYO?mtNG@QKE~$dab_lczL&i z3Z=ux7yfPsi>y2yDf&*7Q4xI|x`2+|d>}^d275UIb4+tA#+bFZvAR z8K0QmJvog;j~XJG=U;|fusS$2fTEBa^={0B6u>To$wL`1VcuwG7~q#R2d8d8vnUwN zmF17{2OBC6DMz~^p1;2wUYgYe-1wB~zovwfAjDdD^S^X6H%)for1Is(9tw1my{8&? zFD+@}wbMT5+58Gn8LtTn7&2i5hX@j;K-N#;aA$C_wV2XB@+QIW2TGOJam2NSqsGTpl2KP~Y@xDl zQ-3(jl%RqsV~2I|C_Z(l3Kbi0Bo(29$#?NzNUJ$o=uU6Zy_`tgenC0Z&BqYYR8^qT zBz$Y~?}~w?`pMtVR^~6us>rHYmW))A94tTG(ON3;!*dnD(JsqcDj^f8jX)3&#A%oz zd|As10S3K)!qlkk4?4BNKVvUe!;iiLe&U6NZz2ic6JRXTGzcWgj$Zi`3r|4;FmMAu zHj-Yf98|Z#?5fDJO-l91;M-nz)EMx9>3i!hoCPyM69C@=|Dv0bhrkH(SIefuil6mR z09r9tlKTeZ=oJt`^?tnFeF+mI{MXw5aiq;>i};ge6wM)8{<8)(pX1o;BzoD7`etwE z-hsPUORKx9gYn45ZOiFkqMD-x6ejSUr;k?F7WjVpP>-<}Tyg|DE?%dwSpD*0Y^xM=qaheB~{Tc_~nb)hd=dlM>`LPJ`Z4>=_R8N4A(_)NA zpL&%nmYSrAE!y$lT8aJbl)={b_}2-CLgz0Dl$~*+rBbXD&98FW&VG85ZO{4Cw#v!% zZH*l&r0liF<#@+~uayT>vW6W~++3o0^1=n|)d>m0$^TZu$JejBW)I~QI2vlKa z@UI8h(tSlaU;;|2V=D}wuZEC!3Q&}~3M9e8@Z_m7XgOD5#&dul`j6^xd%K*t8Qx8v5<=bRR#i-Dpzr*+*Iol*%(&l@5>+NYqf)6VL<3OC^z50PnYa+ovftB;o z%+arBD^VT7i6>Ts)?K?exN>ROf%CM$7ORsXfBFCv zG#N-|j_%b!7X1Mp9-ddadX(=XMY0q4aB;FyqU0*~n{C>D5aTN*8C>g?y>RnB8MG`7x0yaLO+*Kdj?RGC zYh#uU&}W$kI=3Gn&4pz5_j;NCZpT_^mTR3}xr1I5B*!^bvRY~xT$3|cWVoS~5c0EQ zd2XC9-k@AZgF+>*Pd^D;Cdq%X#7fmhgGfwaz4uqZ(`gSA>SR5N%n;zCNrguCpLugv z{OLXPY7rbd99^*ju2ua4_&y}0C4B*)CkUN|gk3RBAR?1Nv zATCuZi@r+e^-CaHV>a0LkG<IQBBl0JR)hb?szSHo4G^*KM2dazD6_z$CcjMY>^Zw{Z_b_yYYt%(yJu6tC>m_G z$F@NgB(+>hS$Sj?o~V%d2Ud^f!NMCXUj{gu$2GDa{Ek}$J8Aic&!oky_|d(@la5wp z1fc*skX`zX6`hF2g34;Z0#N;GXvS+pbMT+ixterq@ELZWzAdm1JAoch=Rh7}UyN#7 znwT!>4l5ysu$&4P^DAVG1kh?Q2PxCJWKcEde=VR#JZ3oq6D&7)Xit9!Eoo7cQY3pz z?4d7_%K7BkP)q`*_B^`I)LHf!rws5iEHaY__8xeKrNDdmG!FrCv$(mna@Yh>KySI^mE0aDOSW@mkZg7=C-MvpODB8|S8Kjj_RTJ|Z?zXt;4g9%-zpA@rM zSH720kDppSc<$DL`+ls_rZ5|^(A^Xsx7}=(IKe+nv_A^Sd^j~^H{Q3-)zes!+uMU8 zfIz+Uqw`X1BrfAmmpLSr;8Vzt zJg;+70>p1qJ9#fotnVH}nlup|vGshpUHj#`RsWBqD-VbA?ZWSju@0FiLzJ2E4j%0(r?2!mh?TrH{_r3`^hk(NSkc{QX-c;oZT)LZe}_76p^=>JH*Q<^;X_6+D( z8g%2!+<%E;_m$TzG#%J}yZtp$flhE^Ga0p4UVTfbS31?7@afx3-^;G8#lqSr7I&zNSk>}MpYG9&)vsPEQutsx*r$Eqv8xt@5lwC}3H zWo75sMx81PpNoDP?VpeSY3WVnBu0F#n;4pU{HgteekQdpkNxwb0IUmzE!c(BlPXA8 z>V-Ze@b+=)>&}4}`CRR6?Bl1s|MdZwV*J-)G$nE#=>QmY1aI|S90m&4bGN3R66YyL zg+*=_Q&Nt~0pyzf=dCRdQX8>cbxm})vk)ri84em2a=plZbgf=$*X0v&twULD>xVUp zyLfT>q)kUi0QyJnMXJG>u5bsleVAR8Ihbbhhu0=?zv9`*W3&)@+RjRX?s!foyE_=( z?ZE~L2sF``6+nioN?7b1tx1;NYRqfG2Vb_BU-$V?Nf#>ZiRpm~<PIm?w-M1pP!X{yHjBh# zZUbGR-dAeVM)wld61L`<2TdbLnK$bpC9aI4*fglx7u<7RQQ0hiE2K!q`LKC89{YQU zrrm-_%ng001T`dJ3ng<1s5seV#RPdRG5# zMD@6&-qd)yd8bGfdx9x`rVi}+h4biqgzELbAB6j}%Sz+6T*7MPU|oQ0uVld+@pQsy zI&*ZGJ+nbD>;9my5KG8j>~)9+N6}3HwDHNv*Ts>?VUw)kA@zNlM=TWgXmHKjXBe)# zy5{to58s~2D$mP;S$#5+{)w|kXyT=ET_SqRO?$TdC7$9_e=5e{t^VZJL#VAXg*ZHl zeC5rs`!~*SQ5yZ6NW))hLcd8o$W}|FIYmROTWH?( zaN1}vxy8Jxxou}fa0j~*`KFz;pBABiZE`qsiVE7y+)=mTp|v}mQM2J8U@fpC7TD-Z z*h*i7Rncw|WME^#A$t!YdQ=W{e#fs-E+XIMCs`q<0QDB^cn>k9q6{f;?Ql%ubCjNN zQ{M-^J_E}7y@LfGe#RU@>S@e?{mGgM1CmB7B`*vHKv3{01&$GH>i>J>g_E}96YiBpeffv6O0*F^0M~6N4*2YWM4D za1ZZGGAe@aJ{D3YZq2?0i`mQejHWKrYfgPL<0=n>?o3-cV*r9EjD2)zi>3`%RDwSA z9FWStSTte-Kh)h**ID^lo6mlKKhMA3lP%d2OkCYAO5Bv0&S^)U7sDAc#in!Yvtasn zhC+ZG;0fN#u(Mv1-JfqcoM(xSh%^uF4DVH0 zeQ3uo(<7v0j_jSb&<>=BDJh;06oXNIM_nFeN=mBv)y_2NJ+JO!Bx%->m@MG+OV|D3 z*}pFR5zvVuvu96SwwP!0Kh1-Gc9a6M9z3rk~hnTiG^Ft>byxcP8nqdm81d+bOD~M?^Ku z&a%2}bVAw~hxeHUz~ zG_9{|m7{!=t)a{Hh+~Y~OV1%r!jctIbM}E!n+9vSMX*TR>QXpl zt4q44EP^t>0I+k~jdZr~uR=JB@%Z0`Ztr4qb8)fJ$LxDgai-6oNSmQnUb!dRTN5?kmAbZmHqih#m>BdU3-ILm4WUGJ|yRwuoFA$C)st8M!O!)TbeSeV-wKP6V$cvds?gU-*VzmiuqKDf}_YQhA55fq@l z)dlQQ=c_FUFr9sD6;$r-+!H=|E0clr%2eU!|Dm5^-HzOP(w*Nn+;_@*(Uf_O4uq;A z<|0zVgGxw{Sk72R26r?-cVv0WEjti7hijryh;(mcxa-bL%(BPoQqJA@mqlC!(p_uw zgWOXpq{a_7;s51`S^P83F%$OkdMK$>1STOzbcs}>sSTW^- zb|`*iyM6v6gBXwfVY9$|+D+(S@2B^oj&pct%-U_S8X4>bFua5I$Tns7)6TUavxo;6 z*aCyLZA9A0{rqOGOma?tY{lWD=Z7mS1@s&fmNlv3X*)b5Vqxm)-#ctgwDuJSyo2I# z!!EbbzS#$H2|5vqv9Fvl(DAUI8Cn_S48a%U0Y^jH@g-`^v!4_3{RZ!+r*7B2FCNUm zfF_{=2Pq=<^DZN6k=eG8mtLjVe$W4SOp-+MLB!u3_XYg9<>bXq7EnHZqX~92nOnN08$J_8(t#zi+GNJ3^inqr; z#I{Ew-QOMOoxN@wSe}oh25*GWf+ZdkUD?N|+v|m)gC@B5hKAYGOm7+?lc5G-$0Qkaa19M z15#6^=b9}kipojFQI5VuWpt%MovL9~H_uRCE1U(E34|68P8hk>d3ks(&1uN*K&9yD z+OSg&>yks)3vPrtugV?>It+oVrzZA{xXQE|AUtK%%2>?2v)i|CANy{<6Vp8hcKk{l zl=(_i?t%F|*)&N{_J-9o-1X`sL*WG|0^XhQ>8@2W#*r8ir?>w25o6k|F0v9FlFCtu zh}I*|kr9b#-?R1Rvqx<@e6(bZV}hKqHkj(4 z-k`pZm+!A9g`EA)37V-_z8~J8l4(~p_FwbTvp`xusO3-sh$%dombyy72z($6~PhW8vYP?#;)Q#Fo|GOt|hh zb2Jpb_lp@Zn}glbFIpI8CoOz z*`8nYLgu*4a%Ncft`F}^y&L?Sh-EHZjdp+UcG9vs`FR7#JNbWg^Gh$OVwPQGY$!AY z2~(l&`vDAZ4|++Y`+bcX;|<2I{yByf_1H)E&C9Rv%fKt$eTY{oy@K5u0ht!)F1X)s z^#gVH>b=}7y=qIk+r_*50EKWpLfR|rtc`F4=e18b)(>(wfF5%2sZwn#&-#6a6rf?e zYYYFQLl+%YYhG8l@Dd~^QTC;;^bV5DxB!(m>r+EaX74l%Vhdczf=78SD%&<}D!fQg zR4mS7bv8NAzrNX@{(fH+{TzCJ-zw8|D(4nchVC94G{hj_SHcl@*&&W$N1bs$ae-EU!rd7&KT@}x~DIS#Bu)2Kd|}a zHM;TbEV8typ3-ys{hYygz&x^`q#jxh?6E?`BE*VO$vBF3Jg;LD?zZ^vlgQy+La2aS z{{>M`hF&Sk*bC{J$=*Mz<}@~BH8TN4L_`qf^`U)&`^$W&t54$B9|?i*vKQdy`<& z&v2l(GIBN=R=4Bm<2SchA3;sZG5^&~2xLEG6dg+GSQ%F_zo32$5pl3UN%Zx{ceZc1 z&Nh9(sx=QJ{CWMFuV#q*xzpj69Pxm0ZH?9AA66OL!|*rAKVoH6fbuvOAtU5#h^QgcpnrU_T;t*1w;~-U$I^_qh<2KOAy(05AEC?gKf;gW7d&R5k2Aj$&qkEO+zHAJQkG+uDvSmTB-L z9~Xlr5pVhaA@Pf?I;{B3_=}$G#wKDQ&9C{;Hh7rh+$!nl-rW|7Yi@~S8h^YtTC<4A zG-egJNC%b636qsXF}jl)>}{k5EAZS3J1(crKD(N|&B8vK6#S=DA*FbPrl_QfN-cYd zD0SV5;~P2x880~`2yOh2iSE}6@TA)_4Tq66PXwl#;uqk9Z*K{#Waf2WYML0auqi%# z1-qb^-jiHkXn%%Eqe?D#H^{s@NnKq(h zvxFbp2}gI&fBtml6~%CmFEX(27CMi= zTuZgCZ@WdcL=@$FY`9Fp_&3F2V4J1D(}C=XAq{(q&i34) zvv<91fK^NQg{)5vKJp-^1$HhJZ(#v?R{2`I*8F>QdCVn9Mi}t8fW3;_g;Gg_GBC>( z@w|r{P8c=dD2h{@Lz>=l@+f0n5-$?4yFCvD+)rwj?XLRrWvJe&_Acw}Mb3j7%e}&C z?Hqgd9{}2<$x~feuY;jbPIqr-^*4N#H2NrP3kjAnnle|;^}rXt(0kE;?)tG+r+Qb1 z_GUFc>0%QwphIIm1o5G&+);F9n4tfOJ=sI}ps<}{OLSyCy9V#Oe(BAhlu{`U+&gn( z-R+Eyq~uNA)SJ9G=ho;dmFvBSyeHI@_?qB=<;`+HhBaUUbt77+(TTu?K4v{ zIKg!2whN!l5xJnkN}&@^W)Z8Fvsc@h9~c`>Ox*%@mMZawIM3w%cGYf86S{!9=;5_J z*R{xEhUG~k0cd8(a64D!xuuO|Q+>Dfah;0Cymw~_^dpD2bk!te8c{8EDZUdXSMqn0 zLf=2e9Cj(iL%=Wm`mfWssJ3jh+e`F617BkTa6Hg(4{aCIP*DR>O;3O!vfS<^{DT)! zjEdz(pelF~u0#)vUI60xUeLqeG9U>ckKQ*SUByt}C?DS?@OsnMC&D#Pns2!2SQ}U3 zS*{F`GGzJ1D(6?vNx+1*sBpJ!{4-NrG@KB=cf(00`y$?^eii%Chh27x`!O*o3yvB3 z2iGh`Q!}?gu9ma4!>6W`O#M;FveHPwWB5l zPpx$*lDq`P_k4{iZ+TUZdDlw$QJMs<|O!e}y^NGc;B^S{Tw3cX7%Ws|S8F#9ek9D74 z-a9}e2TlET9EM#dL4ZhV!;cu;!;(Yns9DLIaOn4(XQ75V^PcyU-EmVlk9s`Y3e*p~ zV1wX=$LLGio5T7X8skHs4_+$Oj{BYTRwl+y;@a5XA#$6*h2Xnvvi|gZa~ht_T5val zr0lt84LS~7un{FsBXjF>>s9vE>lI(a-e!j4P^h8f$$kJLC;@H{HKYof`7Eidd|A|6 z={5yaKpM22DbaU^2SiBvupeX=`vW&xOuEs=;V1rdXsE5tP~*NDknFdGQxL8YSTqhA z+Yza4TF(}st0gzMr-}CVRPv1pOfcTw?8AMMIIF5+NILQZ`!nyWn`V^WRdPMmSO&~pYn0L zgKaviL&d;HiZ=X(=8#QYH<{5Ze0-tgMc=&P%=X~*z!3cJ|4cuNK6x+e%?B&WdAExB zAfkf-#Ek?AF!v0Pmg#zXCPUf7Aly}W0!7Kt)8r}dMIk!IDjblt1wy&y`cP$5?_M3B z8M2tuxBzl94Z?9+c>QB7cpoY?6B4h6uO>Ysa#*d8kB`Hnpo0%4=k^ekeX6M|%?j#l z;1hEhr2`%37PBjZ>|RAzR%h4!hHpIfWF8tNsKIJW?F!qb2sNEIr`~=WbY(ehMLWbb z_4Y~6ziSRVMI*Mf$(chodb>ZGdV1Iw%J@hH+kA`S4a8eq!nTn>>Er#*$!0DG?W~$~QUL;ri1#FZiCtUWu_vsuF180o_^~Auwc#!;K6auU zGer#MXk%#Jgi-tG3O$6!EN?P_2kt?u3Ocds7RH6x!=Xfj&(ZdA@oOGsp)DJiKg01% z2l2Y;Hn1PFQC|1WH%y%0Lmvc9CKjnucf)GlK=dX0^S|C31tBTva|RBBT-tea1}W^2 zPmv%?p{hBSeE_{4km&ZfKnSI!?2O!$@)JSwefH@t?G~0J%@gESXY(~cxG1U6s&Tpi;xu|279pU79`KW zXiZ}>Y}maC;`06v?=nI-^rybk;H;YKoTs$qbtMfZBLp#>-!mT`P%^d>G;@vSC2<)( zJ|`}C>7)%Ll`Cm};4N^N1^P-cpIyQ}VFN`8u*$_R@rUiOBtsvBn-gAvWFL!bfU-Q^ z*=r+2yud~r`Cb3a!E`q{ijE666-TP@%rYURIikaab7vMIM;WqS58JKnmi&)fuFfO! z;ZVUF2*>r+6^MJvv;`6aKFvuC^)R3f=lAF7J0O7xm%C$XnG{#1J29B-M^9~J4Hb{k$g_9&C>F`A;0sPMO^zj+*oX< z1~;$g?B6DWl)1q4p30E`3}SD{UB@<}G`-h>t+zg1!O|TLm0v6AEichfHbtgd-Y`ZM zPDxlj<2g~5no`2$42yQ1P%y!oxaFcJ#zm&zrs01VysYmF|EG$!&l#Nd&C9Ge=W;w_ zuc(R}mA$J=DY-X)?+h*+vf$NQ9zi^6C0X)fPJNqfPwF+6;jBKb(Z#K26^-IJ%ma9h zfvo}Jb-A0Zr(ua{__DMHW3RN9_O<<&2j?~OBM#b$AJ`^y--Lr)!bY7wv7J1JK~5;8 z6Ox|6{grT){g#up#Iu#&hXJNrP7)`jm?yrIv&fW5-v|BYcSuO;ea`v0xfbU2xvXV> z3{4rf-q^e#Lr!rl!DbdUZgFH+D9r379It;43o`KaH0X2~HN7|w|53X|IUlbJ7a&N`B>eU4 zQw^4>G;7rI@HE^vNBdGtNM+HpVALqASkCIb)^gs{k3IbcbPs-PfniBCxS{;7beB@Y zt^S*pgtpyrI)Uu2BVrmMr4M72s>#aZ%e>-ypyO514!mI-9Md|3JF+9*1l#s)HaC8| z)BQIXHnok`dAg|_+;BEk>!N+y}C^5lWh8IoPKgjePAng5X!6JOJ6J zG3hCaZ}>t|`Poa=;MP8~3t9VSr(iWQ^7RaV#%~TiT~gw|0T36LSu%4mVY{jjW+qFY z$vMu-0g`WavAx&tkD6L^^4h+Zb+Xck%P00TZJ2wAVRvHEGj?%&o!Kkg3<$Alh(wsQ z%U`;%F4!*FL!?j;9%t&$hYwQ}$1Uce^^MN^UJuR&L2>pQr-86< z-qAXru)&q9M=n^+7-;dRgma?$tq2 z64Tm7a;4|E(bW|v77W?*%Pg+=!eALWf>ky#RLk3JRF{f7D%~SBHRwd~8udYRN1~M>Lg5!CPY5(efZgS92 zmw%Cb#r$>T*akzxww@Y6>-`0SlADwc)Se6zQt4F;#RA#k1DOPPe3OyW8xk?V*k1}4 zwlBhPfacrnl|#tWg8WYKqzU_RMG0^1`$edYOCg>M`p+p-ctGeNqt*&p3D-y-NM}qp z!C-63n9*}$TCtwjud=bkEn4W|5wDE_=b(m#oFI12uc$@J!nc}fkmydXJ9V+onGO?y z1#*e0?DdQoel7J&eDz3{y=&M=CHAdTeKYObMO6sZROW6y$qJ@iMJh7J55Qx{jgB@z zR&=sAo{RcOweQ$Qt<56MU*Vy>1!$EUe`?bYSt9lfa2j6pky=0jdIk1Ikp3M;G5&Bd zdNbH%dt^TX8o>{BAj0Z>!}?5sX&B-<){Vy1+(&T$z?@wXrXc$4v=_2`@q`BE1xNRK zEMho%MB5?r4S>udqn4a0d=HUh3eQOrsN_ya0u@_}6y3Jw_-Bnio3o)TC<^o+Q`q4- z@gjgyOiA}2?>zIb_7I6*2{u`95Gxzra1w7OU+Q3j z(g!>*O6ep97bQ%|*#VhBm4zU;3Lmth*?y||-d^T*V9x<6uY=Y5NkqcuLzkeO>so2h zYAqKMz_PiN0Lh11@XWS<7SYtaGH`89x76nSdI|`asRaLgc2IgUogDd$_~0@<{H_0s zX8_$0gxMb$NolOEEZ{7wvrfzcwNY582i_gAbaF~E&R1)6m&Q`oN|=hXQ2V=^;S8~Y zJqbSW)j=vE>rTPd@SDmz=yp=8|r+(2LIug=)%i0NMH;O>sPa(gdCozGe7VIpZX*hU05-1P?+NL z!Rz6&H&LcmdgadWr+@)|VD&`#yt)fcGZ($P;usUCZ9EOBqC5AeLFk`;q_z8gB*DVH zRNNKh>&h(1)pRirg03Kc+zE#q-OnGP4CgGwJ&V~(~-!&+iox>YJ{FLB)+3{u1QX2OzkA3`#Oi-@aW3Q- zgWgC_2={9b)@GR_aI?1E7j*Z9exQ(99S1~M`0wD%o{%bEg!8O--d!6^q;J^7rBDRV z$s0#YUZKOeeDfmzhS*_68TF*u-GBE8-+yfn2cHCs|bK-th44oXbu{16>K(0Ek|9fD&J>O06buQ=3dCOR4CA`5??cupgJQLO$ zh3S4@B{x1k1JeD!L3ATB@JKE-z>!je!u#porW(g&KB`)@8ZY;>hjNuZyenE4UHZdm zVdUC0gVLz8qtex;s4g+HBUGpo?I zLflfCY(Hr+@EZuH)dbU zjiC){yC3xpBarE!mCfVV)NlQE+Xkm~&4f8xjIVC-Mg$I+)k9z3pc{ot(P^r)$btgj ziAs23I%M_|B7XL@Cz$|7?dB&ke*yd9u&h#Evnxwk)P|MC9?8fftC`yiFBAx~h z077%33(=;ybwm}f)25l|ejg8cz4i_@$10}SU`6ZdN%J38#74alW-nho1t_&I>eE%< zu$sLu(V?S*!DD|d194F7B7X6t##hU` zd0;B%%9SM<9&o|!M{gZ9(->~)AA{?T?>X*WVH^yJcWP+XyaP&W&28)@h|2N*G5^PF zwJJU2FB^m?sEv4lMF?777~~W7Y`l=L&YEqTStJBxWWnuPTirc`q;0DY=r7wOP3+TX3|;@!J6K$IKg)xaFYZRp$--i)Ibn0ZuWv1Exlk zcW%DD$^Kbv=ymUU@!8J)sBFJ4oLQ5}$iK0A7!H%&P&o8rk5C%a%)3mQKm{0vL*2Z3 z8tH(BafJAg-GHu0I>mB)y13nDSn~}8Uj*?|8#H#;)w=|_Y>W`NvHQQ7ivDhL4ny!E z;342^&8dw+8*Hb!ZRL~i*4mce&QSl-&ZmM`B*YIL)&S3YrUMob-gU_Q=v=STRUGr_ zoh*2|kFK0Ze7%rEP28ST3R9mZ$i;=bqT&m!3n+5@UI`K#I(Y*o;{kjv^FbfK?`7=r ziqVeFx8heyP+(Q_<&7rbO*_Vgi@fa)Xkgof*A}&#Jj!r z&xP!{haDZCo)4eOo1s)*8NPKe{71t*`Y(XxLhT;;bV6J@B7pml>)c9G z%je3}Xg^bFd+Y&CPn;_*ebF#Ml{kNhjuYNG8S~h zp`W9p+hv^^)3P>T=-?GJbF7w6?_cXM$CQDn>#U+Q_S3;{x8G_i#!A3TR%g_z7PAX) zh3y3T6Dx0lsPTz?zcC&Adh)_Laln!7qF+#2@Q4QJ z-t!3)SYn{Y^7(RI>LJHezE}8oV5;2{h~ISuOX?Yn8qk_GsD&4d?E42fOAMP=w7ALo z;`7KRO=>O5{v^&Dsg&vmA?40w2Be2(X!kcTpK7>wF6AtI?s}0|o3+=JOQH+a(49BetTor1d?&cq zDEC%(J=CI=Q;jaHd5K1B4mvZy*oo8?Y~Up<>WWFJIvFtx``9F_RwzGp670$B)#s4V zEUoXedWGi+dt-n$U4Am3d?RPWY1RcQRnh%HnIOB^~m zOVEXd&SzCR^=6M3G{N-qh~Ldig86P6%5gPjcHn%p;A(0{lo4=Oj)Q;N!4!^MPBIihX{a5E&(z316mYitg#{j-2okiNti|M|KF@VFH-6CprggrzIJ>Xvb@h-L-eX zZ@Pc&tcPUkL5BHLPw$-ZvpfQ@RG6&?;1)}(zRB89g0y+E3xFtN9)XXQQ8pt(z3g)Q zkOl-eIFN`OeLMvbyP;k7X}`H+5vqHE7-a^y1*I-t6~N~Up)%HFM{fo3v|S!OKIvKW zvm@HUs+8x3D}8aa7S1jiIim4icIkyD21-FR;7E@UFN=2>31!0Z0|2z2MV$xEn9FZ0 z*Y3?7nM>lsJr>N4t?9KAbIWAUbi5;P^UnfJYNKq|wB#}PH?fIL(^-d5P{NRa0{Oxu6?Ie{|*Bnmq z@0Ed8rMOO?rv5y5JSWVxVZ$cO7T%z&efgyQ5bNnd}D-N7F*#H zWF~9?9AEh9&38@%uoG>bfR!)6r^*-Np+ny}@8#&X5<94q_bvcC2%kbHSM46&_QTh( zrPX@aQmXdT3=f}cNW^0kNAVRO(&M4aI<aOkqk z(*$8n*Oo&8YJ2uTKc3s@-#=Z%ZytU-?`m32EI)aov74ed3N#WvsSf67U9KpD zyX`*H0ocN0g$BSe|RBuW=sWQwWeqwgLBd37j z<~9&zFQ&=pLihv^q!O?aeSVU(p-aj4YSB_whX!X|ig)Xis3%!x-S`JEs}R z3TfO_(od912>EH>yWW)KEs#aCGdeD57(7R#St}pop>T~xoz%D&5zjJZ36v9+>dk@V zSAbt8_kUy*58;-Z>U|Mq9ORF(15rz$JukRd55@nf1M(8?unm?6v_KmT-c>$%wZ}je zRJ^Z|`>0sMCi%{C-O6{>9sHExp8lw%``Q6)C4YJ3 zjI-YEwETFcOmLfl8vJ9!eV^#+NIHAm>+kPQ?lT^q163vIF$G}C_0jc*f1)DVS%4x} zXVZ}<&zM`P)U$46^)eaKBxi%!uuiOhNgx74wVyH%cqOz#~0J8?5CS}UbN9|?(f z-%0-D>ts{F`|l__zHw5%aE3Yl)M!lzVAtL`iEVDHnrlYQAlEt8j$Vv*k~FS{#k5&$lxGl&7nQ=9^^LeBm)@l9c1poo}2LLR6jL zpIua0MDsX;yuCSx?D3UHw!V^5-r3Lzp)&a?8sFRLJ5gE&>)3N#8C3w3o_mUeYVRzi z`H>dY_9eUOR<5w+>+i7)4-ZGR`ACieXa#0b8FW*Ed3if9&E|-9=Lu+OAW|_0<)4*W}E)J zg?DFSUcK*5?=3l|Bl%$Aec62Pu8AK%=w7D$VSROMjd7JuIMJc&Q?!7Gnk)4vz|y)& zP1ga3tY`l3Jr48mI2^>!*h4-y)ah)Et$+FgtyOAV4*{C&pE^$>q9?PwzhNn@M1dKk zVkhQ|pFFQmG+wWEb~`mo53)pvVu7Wr;S877d*JR;IzaUXvz!O0>IM3wjY1*&@$R&m zy(D2BE84wyw}zxZ_y9;|je%=#b!iQB?1Hmi$-=X_M@XUL4jd3t07x}V;(F6D!_xT% zK^4V$7j3T}FAz_f`lL@;aUdUzsP<_b`fIe)G+l0`&ic4t`du{2@RzjN#ph=+R{19% zKHC2%|KUuNBv+2i(cFxh{pY-vijWT{>n#S`Qn>w#Q{wZQAV{m_y<7mN; zrE}7-3R|!@BpaLRQ0PaTADUNcBLo%fWW=lxj((fBvt-@hr5D-~u4B9*ednZWqli6y z&pYnz;C(%u!Cak^q1(U zvAGI!rE<02M~d7Y&%3`TTmHOul@v+hjO}f(r(tm-@>XG-#zr^vKQ%e_#HfrX35tMM z)0`?B!ZGC&S4oHWp*rE<)(ROkRh8fKB4e1t@L!6$45QEvtg6IdD3NU$HW|*O}@vf0IUXbO%`MFKuub z2RHjzAv;MfEUBW!Da8hz{p?2Ew>E+_+>k8slpHMv`;NfcZcOz_xa-rV6RjGO%w#gh5!-f&s`did{y__zD>G5*yp^kYEiWCqLy+sgiel)x?Qd^d{SxXd70AOF?V`9 z^8Sa@NU)w&<_HHmS^h@e5ZVmthSRW+gbokJhPIb$O+|m+h3;J6X z#jLP@EE3OCfy=I|Du)axt3f?Tl1wi5A7}x!|0WWIL;5pl{*k|l8Q2fgQnF5J<;9ti zjwMwIlYE;R?QEY0d#uczaTeI53y5nlAIYy@&V=d@l6sKFPDtb5g#W@K^(*lt`{9pjMlelc?_QgIM<os{x?xOvM?ja?{Gth5F%3|A$cpM&4# zioy3e*B!+s$!hE?vfEvv@Re8W%7)~uYq_JW*=kte$K&*dKI_MRmj=FI7)~vACxdFZ zyHnxdQ!TrPSFoC2Dq#Z}G23#dY~|IhR}I3ugy8nR6G8vv3!v@ zIzLOOC}QHra5uTGQ*r5*OVZWlBR~FDPzFZsN!_?^c;k8l_m1YBC#S_9M4fg-q{caB zob|(rP_B^fT}CY;u5GhoMxlHoiVM7dr7 z`b!Y=<>tDG?{Y=E4p5J#?7}=l?1x&PA@HI8MnYS-pKQwaH&$@iz}o3g^+;z0Js${s zVtZfMyo+HpLf*@&6@`Ds6%bxk5-r|_w7NkW+Y$mM+Nb7=K1&y~l*cu1#_6l}lj8K_mQ7|F+-ScSVp^vyqoTG{tE#OZcyQr_bB(ef zSr-LLP>C1GyRZjRuEhIWuON~`jh>UbS606(Npl8Uk8p(E*U|fIn{6%kQ)N|ETf5Gd zn{{%7Y-)^1D238F{{Nj8yR`|nVC-75BtiZa-CuJDQT8W zukyVx{Vh!+W7y)iWIWuB57M7wcXoB71unp62gspc%T^BZKe*9oa_(RvcS``vCE|0s zB~QW=nVRddGekz{PV}=xPF+rasY=MJba#b{`V_dadKFZSHqEDP0ac-z9*>6Pxc4ST zUHOvZ62fN&?Rb}ajEZ@TRFQ+}WrpB{gI z9z0E*Yy)GeQ^r@IYOY-u^lmEfNb33g3)rGl@Zu^_1vc#^?qv;@!2TN>8?dm6ppMAV z*>Tw7Iq_U^H8FeB1z~?f^cFJ*-A25m3pypV?XRfczYK0Mr`OeGn>PUvI0*6{)*CdYUOd+gxzgSP0#UDS*LM%vOjuN--lg`fTtO!8Q}7fI{a zOmPQ+eVQS8C1_QT=~To9uU@<%I3rU9-4%*#nTVZDz_1s*5}8xFtuR1n*}UQlfQ<4X zbGsFgRxP`6(m<&Hw&$)E@k(nf;4ZngLIz0rQ~M$Y2q%hT?8a4K@%J#VO*S` z-UwJS;bCozI6eL4IrwnNcZ-b?g;V$SJWN1p6LB+}> z`iajH`+VleiR>a7c=jvNgxiw2GeMT>IiWlH$jvS|{50(MI37-`hCIl`w>T9mrFhtV zpg9aIbYpvuX1>C)CM~N~w*`vc(8I~gtR{O7n?2^lRf3X$5h7k}Ev*j_6 z-*3MY9cKTbbUVu9r~4G$P$@E=vo`VpXUCaVWGpQ535E@_w|9oQ9~bcu%!ttA8Opm_ z?s?R{n){44B9Mv_Rny_HPF*J{KBfC$wGmTg-^`%GZQcV^6A-b{94AwGDLM{R8^qo0 z;~{}}>v!QkbJ{y1gU`6e%|PIDYKRZB*r$Ac+ug5b9xP;&0k;Y>!PZ!(N2K-{_Lq~( zf8(VCuQy|#%-m}xXg!sN4YvHPjbSr?&)+%M9KZO&5n|7Y4##BenbW8ESnQD7Z0?5j zr_}@}myAbMU(a>=6OY?l%fQwFV|zB=oIZ^H%s; zlmDY+?pb#IE^4-q2ubLsW=1AtB8>&us=AJYRFf7@T!hTWj*WmC*jRKp_w#AWWW2@a6o+*-|Pt+Us$T{-R# z7c$WI*wgm>e*4cac~h(Ac0WKSrrgmk4I?Z3`jmAtDrXB3UWaFkE1IOhw|;h?+jxb*&BqcS!yg%QI?sIeM|i^BKwl; z2`S7_BKs~SDim#uJ*gSlC0nu=QQ7x>nR&m*_m7LeT=UHHoO7T1zRx+mdIW*%r!Zn0 z&WKJ*lk5%;vr@cIYlXx4>O7I=tBnDdz&UhFV5tbPYlv-4X8Zem7Tb`48XDh@IAbJD z{W`(uV2SVFFuS2e3h6!SSLK3a5hY2){@VLn*|zaj(y-Op$;m0d0#(6+_-+juS!Oq- zaEc|WUDZlXbgra*e+$p|lwX2z?|X^UkDk=uQS}`)`bE#TO7rfU416U)dxDy@{}Db@ z3#VCb5nnzLgg>~MfFNKtKr{YByezLKae1rjBC*)q*!W5<_8hW?f1~|$i&+1P#jMI! zwaww-k#AAw)&%1=Pae)FdXbdslOsA7C?ES|-q-%?xh$KWs!P=9v%8VI#vcZ1OYuD9 z?oC4JcT)5P*>f>rV53<<7#Z?7gBfLV{^*Rb#9_pqJ4GDwNe?~y5PsdP-NTIbGZ09e z_KMaeua$}od|6USyh$?Af_yk@9-T!Ls6g9LjSA@|@|K4TNg5TJ8L0c4!Qtmht0+U&>5(}BfPGonJUqQ#-X8-29zPJU*Vu%ZHTM${6nHdxOq$O3sZm7bJId|cFQH)<`t ze51d@bt#Fm(8LqDhGClbd@`V1?RrA2YS8Fq2uIx&-v0KY(h(9$fBDg zf?{94Ywvj+BmGhY%^>c4Uh2ES;R(%sisWt*4U~2_&$Vm*{#+y_&WL?9#@w2P9y&-5 z<5;M7A%+v5g}i0x4w^yt^mCra&V1DO6+8}%bn*gOl#a$ao>@u(0eW?UZ+ zGU1#dELQQ*6aEft#|vhuA8&o7T%Ls=OO2mg|Ci#j=WNbBGj{3Ew`=bW;Zu{iyP-*4 zHVsD|zHP%ks+RD7!-K9T>CxB4dpJlv+94F!-+KNK=V^?!O-iZe#89k$kd%u@f8cS2 zKQnV~*T!xywK^$nc0FXrd+D%8dJb7SYpBh1qlysLN~xQkpXyO54$ z@y_p(z#MrP^^k*#{N#R5lt5b5{0YuV7|kGG|3&w%L;Yu~QJY3>cc*%d2bbuFbcA|< zUs#)r#60->ORQCIBCLUm6ow`k#eSL;h=xK}+({e4)8bE)Y&7#8!!IUCJeJh|-byaI zTT+WnNFoI~$FJR-p3>J@Vp}fryPr`8rBC;hq=YYIb=y>0=@D zgJk{b8qaz&mn9DAyKHaLL@saT`D_In#KZY~H1p~`^;d#$;Xhp3dXm(CTb#|rtoqok zWTSTH2uZd?6om~{=pf7QgX+z0zpDG~*`5!n)W?cA^mujydrj1vJVF z`JSA`4;V_<^CpJPPVo)$ZYjPKb?`GS#V>I*%ncTLYC3@E%ZP}bd8UVKJG z8N+z1?Nbz~6Y5~b*Zx`j^`otF%}RTb&2O?bMUqoa=((jFzV-wjivn2*lUTuk&w&nK zf}=LzT+m4WrVqYn_-wL)(?=>A9>AB;e#;zNAQqbKxi2Xc(gM%|H&6|{@CTp7eWBBE z%s%H_D+RFfE9S^mib+>k{neuHa->Y2wvC23RdtQIe|z)OC13zv>m(Q*BY%Orhp#&I z@09|v>~@((Zr0y-Z^=a_%0{Rhakk5Mux(lKx>CWcTQ`Fq@jZ90Dod5n1wRP@X*^?I? zf%7l+4}1<=Wv*Yttut8X-a{Z>bux+>t|1!xN&bx|6Y{Ye(%$=DaZ@5QxvqeV|^a*JoK!UQojwyu1cjD4C7c8l~ZqCxh3GP zcj)Ql5~6Q@Nbdf**VBY#c|{G(_2}L-GE06xhtWG*oXY8ba;rL@YR(9~Lq~Wf&3eSk zN%pR&)m5qR@?%Bid>4{4w9l5vVCLs4j-sWw(F)*Ju`@iEQsSxt*d`HWRWW>~3Qz~k zXxY884_d>A(5*-%-S|KmOzhJKQH77u5aYLeDc1thIK#L@*RkFm*|kdsh=Gi=h+ml| zP$6#d$*0J#2Em$Up^*Ldotv8(c~as@zkmP!TgyjcG=n@26~z-V{zzTsgOsjD!)5Gt zHRB!{?gtO#YTl)b6z;^!IC8x289tx3-l?CbaFSlWf@5yR72fI5<(!1;e74*L;By@Ulm8LaiDsx~>>?MCj`clZCj@^jOF zGrE-+pq@lFOq?HU!tALOgp4rRQRbKKq4fKTnh1}sGlJ~oP`|S0{NHjfM53~udk~|~V1x@<&O1azv70zAjx`d$Zw56Wa}J-8VGTRVg2{9% z>2j`>A~C4hezCrB2pBoGp-xa*Umm#H&zn@nJC0IAxV;;H)~oQFK`gO2*%%QE<|^z4 zy6QX%g>q10wvd|@Y4C^=$nX;oe%<~RU5G|sDh$&KT>JM;HF9B#ICG<{x>R~-r&U=Q z5G`E`^nwAH)peiuH+pNAf6{)I{$H5Dz(ewixsw2FKvTWb@+t_8&Gm0p@Fx1PNAg_0y2 zKEvmmGk`{K1}6^ua|XTuq@fnZR<7!449cs-khC|Cg}#_IYJYT!DELjF&UNJQi;2Xe zTBjvmoqsx~wA%6ozd@h|lsaqw6L-Ph@^lY$AP}w|N8zRMibe=M6HDraO9dqU4BlsZ z$9@WuB6a5(l?)Jh@+%0X>dQ+<`p@c!xSZJ69b#_};u4xT%45pXdJR#rQlgG*e1DMg zT^h!2K?cV=#|-u1j8_WaN;MKeYq>q^hcP&%1TKEvqdZL0jIJdZy9G75OtVl z?1z`9tQyu#Qtcc5?baz}ya>*SH~g4eI#zgl*ltS$)%Hp4o-0<0$X+{5N56*bi$kf^&YVH$s^O=s1Buts*ytT{@?y+I3#mT9z>qwWof*+3g%J zR0#P297sj=GPki(>z@aM4O5{|JL@wWhEBV-`eV{4scUNsIC@ZbDCDBDpI9c7&VXM7 zvkj8H51FzM^w%<9z=K@N{ zwG>Pj#g`j+|&6S9p2S^^R>qP{;}#*RN(5|2AFFXPmic@_KGc;Fk2lGYrJB+s&!AT#3rA|(Vn5e z$m-+DUb3H2p$x{$_RpAS0)e#c6miU{-ZA-H{`JT&6Uid}?8gxcWGa@- z2daxB8pp|WPiB%C?UmITI*vADt}+J$It9a1+!B4hUoOE!YL8N;HMDZ!b=wQWkg>@V z7F#&qjK?)m>*gQ;%@S3G;2U26>m^MakqCrs@Vm^+r0DbixKtiP`Kj?{sjx~)8$8*fUDEsQ%ZsMuBy)Q>V3WId(i>ghNj#iEp%?QX|uL` zSE7li>`$!r=1f9VXv3Q@GHLA!(QA!taa{{D3ZnIUf`Fnxp@v`gae5 zQ|~bgA9|9@1<~z$=1c9-ZHTFuwz~KF1Xv3?ZgWhFbg5Ag1$Lu$ykU zONCi>yDfbKDumkJv@Y7QSM;SG8r|^J%-BU~+GHOf0@2@TbZ(mHdxPD9CGw-M6LZ^7 z;`L5$i)(i~`!IVDHe%fa(Rb?OFJDm(owG9YQ}8NZ`W{#pJ#qQl$*9rwfRUI0U0Aa; z8Kz=_0K)l@2L@&&dcV+2P>NaBWRuX;A8!wCQ9lpNa) z!2e2hoIlg}a4PriqSbvaB?=#2>VWUh_fLU4s)!X&J>kHgfLs|fUtM|l@S%GuzU6P; zym=LFpK-ux0qk|{15H#-zlM3IxAI=^*>m>dbm0sJyaepDExEfi^-0m?(}X6*G1W(x z_qurhh`89JH;K?Unc9Z3ANCK|r`Y^X2vz=yv+-YzQSgeINT)s7aY78N+^+HfMbr*K zwn7!-XPaOv6vU<=YyfD)D^7@B3+%1&I>-amWgtxnd{dsv-}mg1{`Ko2fuy2SAXLbX zaHyYw9JEMBdD6kNcy^T`wGQ-s9MDi}g?tWLD~OItP=7A02hFlC|FT~))fUOm&p&ZE zPr&dYm(mZ+GRLv&XclJtp94ZlS`EVQM^6)Dx0e>dQh~6{O;KZ{aN5*+sk>QkugJqv zyNZEu?-D$yvA&m;Nu2*tIE||g|IpD4OJCdSauR~eZeM<*#K%t7(w7=4lFUL0ylZGr zKVp*3#c_x*xdvp4i*DqNy3b zRoVr$xY56z#5>U_3Gha=(Uj2;FmxJPzC3qumG`9O!qGQ##s zD1?4>l_A$E6e_^wF78UtrO8;Zk@`uNY^6FgFO2OB-an8 z$W4yqxKG5oAsV@($MCxpZklQWao9>9#;ttl3R|Sl4V1wDC3h4I_)6;+{~g-MW@+}57{EKZ*ut#)uo276tg54R`c@<&L=P$?b~*qLd{ z8WYO1*~8B!g>Em+UCvoO*dQt-M z^vcbjIMc?C-aA;Zpz-Qe`qKe<7*yadE>qxRwSPs^=w#A{Jtm>!YW3qBNNn=FNJ!AG z$bCur>WA!O$xB{ECx3Q-O4eE33b1YYM6qE@zOzNncl@x`F{U1l?j!a;j0!Crq5Zso zJ%vWow{nuAw1ae0sxDyfM?|S9GDe`(=1wk2?v6QOw}}I&xgi6rw$1!lB0;t<8dbq0 zb>0(?=|pnLkZz$Mxo5{w0BRJt4~Ifu)1JOQEw+NN-CoO+iAYR};VRYVM&1C9aM7=2 zP>tpXUAnU%K=*w!Q1XD~y8Xat!6mq9n-V{5ldPoBq#RK3 zmF5~2$UXaW{g7-SWPdetKBrn)?kMHaeKnu4&tZL1jC?*&C(|B^JD>Jh2ZBRiHeO{DckacWF56g$HV}{yogFryWMCFp#$ZB%1dbY z`7$Qh$OuS(_>5Oa_di*sa>{5KXbXeBCUd!b`ypWuZM>B}G<@{``05=8>DDanlN%3g zI70ad8Y$>!CFWRCx0imt^Y#09xOk6w4krlfH*HV#5o7oB7W8Iu<#cqz955Cn8OaOG z!ej$6v{=%@nE-Ojhnt?nZzGSe+1?gm*fj8Elh9Zu8eL*Do#a@3viE` zZw5!K{odbnAIz-Gt4s=n2ggD&bGF+ZSGLQCQlpIl_%pCZz`g$8Im&kFqnz zJ_^<0a%<;Ap=(5Aa(JyFDapcql1=J~aRx3K$Q3bI2npXRt3@Wyr&^L!o`efy#m52u z=FrCfMq0dxOm5feF@ttL{(;0E7J?e)qWm1L&fWIXrP-a5Mh6?(d}HG8Rjh>DZ`SO(^6NYLX`9DVZ+*qB&aB&wfEo_1B}7WPk`bdV+8m&G0Hx*|0{_ur`RiQERlv`UwU ztfktkV!!OIYYQ}O;jV_%ckDw0M}rGk+|oUlmwrc}!kfa*9tL21_Q&u0&wK4OHeh9RNq*sf8Oss+tGi^W?tbkvc(2GES78gQG9co1C_S?Grd$#|+dnp=a zz*|b4c!+XAunX3*o>l)-8{tsTK@gkc|{ zdDa>|)2zNDN{u^1+U)mnLq1+|7c zV99*o@EmBq(pbK$_fcDQT;%aD+8{(xnXo0_QUjwmf=-rH*E=FQeX{Ri929syKi_Q7 z_SGqMK!w%FJ3QvHD1(>`Wn&m+pqb2c$98MLc|xN2+SAX{k;2;xhu$_YscC71to{)e zXROQnV@EbzY`=jnI`)6;w%Yo3Pc<9hFtNilitkXFE5)Rd=z9#2SpI~V>P4Jp-Fz*v zk(U76S4Z<&GX0QHJS;(wvTf(7Qn&j?+Y32=tX?9{KsoV^A81V0 zVZzS}KTs9fVmXP?n|#cn!}CI8Z-|M#^IF@*)TgP_b`{s;-qgGoiRiq0W>00Lo?$Tx zn|fwecCa~p ztj;3mJVDk?t6BzgXNVqn1h3Z9-dqb@BKFm-+W*Ef;Hn3%vn?mpM8@m z>9n7hI!#$^|Lj@0Kl1hx%)Mhiu%AXa>Y=||T-km3YI^(kIo&gIzrB4>RGv-=fve9t z5zjlI5y{-_7mq$HPyA=>=ovozLNctcs6ODtP*GHny>VRr=bqwZwHLgbnv#=|6lWG9 z{EMJO6n*1XW`xBH_vF*78nwv{r49pZszG+t%`!gQHR8t&f~+pqgcswb71_RqG;Gvv zC?(yM%2(CgnzQ^Q|DPj5`6pp;HUiaBuG5`c(NL;nhsIDDs3nr~j1CtC^Lr12rSk=R zq;{j8Bc*#!ghQ3LAvK*08-|-!q*tjT<(cIQLK9*mlbq?^PCUrR-iS+2p%;MRQd53F zpCYYr_7k~a!!F{r;I)JH5cG|2D?Wa|nqC;u#)>TAq3tpK$Ud0=_Lskz#ME74t~G4$ z#!THiw_7qt{}v8w-Y63l`)#F88zXp)o({{-BTpn-N)e<|y^eEn>GP#0p6-&lPwKB+ z5q`h+Z4@G)a*JjIOF|~ymzF@T8R%AF*X)olfs^MC4iNv*AY{ZpkfI9N2i^3 zs!u5!IfuoYV8<&}N*!p|HtL{a1lpkfSj0+wqS0LN78SX8+Ua=$*}?w%X~lIt>?uC9 zU*q|L%Zj(KgYv#9i1S}|WBtEzm6F`gGt1sWJ-P`!JN?WBVGHa5!9vS$W zp4hMl^hA6@!aKC`2Aq+1a~3y1i(4Tk@6kN_iLvy#K6o*!U=h#}Rt0~@N{wjtk<*mN z<2}r0Yy`1KMy7o;=O3%8r$I)aL=4Ro(+SkIUd@mQg(Nd9$$vIRsWzQs&${feVCZ5H zG@j8^pO>u9aev{q)+?SHqQ1tIH=kAaRBfmS^!nh~v8uMaoLCoq#S7;SqbC8!B^!Mx zhu_bX0qypZ%h|$C67%v9dSAVQ?I7&0?Onfw6xDC>)_OJJFCw&tt_HA0ctveNUzf!(-05Y1AOear= z!%TBHsk`I_Rg5yWpUaD(VJovEs?YAx>ZPsa&y>&zJJKmlVfQ=z{z*R0`dX?P=5@iz zW4{A1bB}3)T?Qk<11c{>GBo$5XP(fWd^j8`yEwy=oDmB+9rajeS+#Hi^{&m?*FxwE z_CS;H!mbA~VM@Q%y788d2MP3Q@Q{^&jt=1a@)YVUZgHqtyWZMpKnjc3AUg=q{F}a{W#4eG9Gr<}9=n2E#q_MU+S|P@CObTq zkL}eS@wZj()!w{zT9 zl!fi$=!DY4=L`1ZwjJt$r@KCE$GJWflVS^mtRv``%45$$>hJjQqFa|ExRKgalc@gf zaBu87ut`Z^M%T3CN}+oCf8b3^*vnuHX1^fmOG;BubaT9uqMX7A)Nt9TQVOTIwwFD& zv@Z(9c<)xSK{aYLRRT2{-%px=#D25)eb@7(HWXWu!n9dxu4N!Y19fhyP~Sr?aiR8= zsLNFkt=XQ5Kt>&jP!Bhk{waFXwrW1CO%HxmLHpiFtX&wxx+=;hcC?_Mj zCd_n1tm^QweYZIF!suV$&j?RNpkgv!&qSb%x!Ysw9&MxtIzA`JW4v5zXr*{d6lU4l zc?uQtq?HpMk&@=-@0^Q9rN%^UwnS))e&WlXtFv>2eR{pur9<4Ij%IdoYL znyGTH-ap0aZ6UpC>VdwURd!LzBBXjyK~(0v4gTXXhg0;)M2Ua|CmSs2~^@pvFSH@Hm^lZX3sL#_4U zo>k69q8_k%kXZzs6!kJTGYrZkGY0CelvIxYwWlznPl1ho4ODXSFq-no;Ymvk|782o zo%yD$CJp_bK&?>yo!EqpyZ_V`4J_Ux^3mRyPh^NJnNLgj5@~(u#?n+0D)WJ3L;-K9 zgXq1v)1qhw;XvWn>A$}Vku8`pVhf3=xvTzm*Lg6HbhlzncaI2Q-iD15@T~bOpA4?( zjyOj&`r45g@P-i=*7U%^uxpXB%8e2K-v5jLp;Kr{I&8_de7P*(SFFqaV3btF`7>>c z+RvARGZM7FSVQv=7kJu;($IJ*;J!Rro&aN4aX;7W2Y{SP6m6qP1>1oE6Ty+ryr29} zoL!|BsJ~~{q#y+?7s0jG4B1&lvT9*DEFsI z4F4sm#|}SuZeH#pv;3FXIAXVG6yH?P_{`q_hm-!N)x)~joDY$xkqz3p49Ts}K7~IL zy@p!GRc#z9ajQ+pMt|I)hR8iz5aO0b01!LHgVq}0_RzpQ;J=v8Y7MRDUhpg9VXh zwru$GvOttJ14IKKo(0i!*uk~TDhsKxuX}TPt6HkMWQ<*bdwilP&ia&*}9&{gGRz^>#Rb<#BvzL++D_rfPhMs6{4>|mtjeK)|o_J!PWd_H&_>tKT} zg^F*0bi#nniMZ)n&mPHGPV^M3 z<%#WDy$iezn_>$f5Vy~bLIV>RS$eYjLtBJvxJWY#-ZNA zA0E_Y^{7gF>{z8sAo6n#nIxgQ$eigX)r8|wwO5+Xbg=~Im1HWjjZ)0hpAG0dcWTqh z+IQ1qyv{%@u_G+QbRex!>>DE^P@MjS>1PDc1QdN%JtB<^EC!hj`B2$@51?41P(fgD|pYEcvuq65};Vdv^1I&zM;vj&`+d^0~}yCn-fa^YrMcw~viz{1dp-&vO^TAIcCO zU;kB>``1LQ z!}>oQ|EA}&%SP8uOX7cCYXA1*!jpaSc&3|EeGr!;B?iTh8SNT9f?bb{iG!;JpJeI) z{SifAxnoDF5X3Nn0#j|y&|&cA2ql?JwUj^T0r{l&PckU=d)NaK>aHEWN|xg0hQx-T z%pf_`?L5XpNU_iuaSH>vSA0HJ2z@N>2pr+hHAvLlm?~f(@?AN*v@pU?pYNj2tDFn4 z9#X26#MjC{YJQv-Pw`>9YX@4~jE50Qj}=he z*AQxG2g$iY{$uFzx9v#6vcy?Lza`U&U{tp?l=It2{(biRoxOZ>GKcW6EGAA7Or&ZL zx@ZQOvKVB48^u^W>}2eAAsqm#`XuZGZs8DbD!VS+v$>S3`s?=O8Xry65P3vZUpke+ z-a2o-;8u}l$1d!3(}EW!uTZ4V4jn6oUH0>sSMtng`hW5oUBFL@giyuZi~dV3%5^dM z%MyuKwe9^+u+?a>qx#5OySmNF)qAqIg=4oBLfyFv&VX<-)ErG*=i?z;3@rVa3-%!j zmj1)kdo*gJOUIAx=-KD+zqA@x;!o)SoG828;~c5&MLdhZd>zD0s$lBlfC}Bq*t2Sp z(^%*@Dj5s0T`7K+k&b-(+mcibV0O8c(%6qg{alkxT6JLCx1P4{VI&7Ut&iJPwFcx@ z@Z}q5gAr$GyzuYDQ>}r!#N#5(UVE?d;VnA=!AI&h_QzH)InngHV1|I)Z__)V9z0AV zCV~AL>-Or5q?> zLCyQKyf&g?a~pMCcDkf?-PQz+(s6OqFOfD97>$BTf&k$043Z;-$lXXRexk1XB!NmR z%pZ!OEB^U_bE@|bd+f_D!hE0fybE+gU7z7qfVhyLhCMdThQIS#g7MF@!>AHMlBQPD zM`Tg&m2_nCua9v#kS1O$(9r$EhwJ)m!LqbcUceFi9rV5MAe*_zxh&ktF!;Pu zZtL)Y0z^9((kC5h#C_AcHP z4iI%G{?p34Yri%+)Uv#Fv+&LQM{UMdX0%Us5O&<|8bJnVU60nZ0gCfRsN$s>qPJ#5 zVhGpI0P>rQ33F%nw^($kzYq_m`3NvIzt@>_8(c$vmN{0-{_GFqG%qvtvK8rT@q1|M zKRK$lG^2~=Ww94X)p*4=g_2I0XFz=DEyMK zE0n^elqi_K%lQw!HA1<(fZL)Gm+P5{jyG?vs-I7}KU$X|Oxw+~T+h1!yT2R1Rm4!9 zCpig+IGGd<6U}JElp94w2NFu^ZpjGU``^PX8K(*wZxbQT`FRz+(>}TTO^}#Msz#rD z4{@?~dxY!|f}O5;;5aB@%BktS!XDBU+m*wXXH(xA-kE+ged}#!nBP$W?H%r<-)~tg zwxo5HLratIE`5)Q3YC1N0z?5WOwO9NjiV#G$5#(W?3xNF^?3nKVS&(BhOBBYzXvp3 ztODZgfXl4<__vw95OJJZud!1aVwN3Bm9LcqMpeJ$*@+h$^NeaI3ZYj8C#V6@FQ{P(NL-^LZy=lz!&RAupjL8{#)b`Q~WqRN`k@W`XMX<+z z1qObZ6_bwvcgMU7>(D7K!dcuA!GhJ5vf>Xe`x#Jc0_AR23jDQhulRmxN>t z!r$i6uJezs>yk>7m@&&Ajsb~aFB}w80or?x&-rz2`{Ak+wTF*m#2lULiFPca=;Tmr z>+qwKTXE%DV!*VKgi^cM;ZL53fl>M#17!jQX#4>XiH6$AMP zb|*^54eUh$&;RLHOw?<#!|I{)_kCqcLl91`i1^Jf2&GuiKZ&BgD2Egt0l|MHyp@A(grJT0`yqJ=PEt|Ns4#Zyib53B;d!_Cj#G zPNo^niYTfcL}3cX zhv5-!^oB!3;X^{qnl_C+5Tn(!wAmD=q5e8*GwF(!%qVOf7-1ur#-+>vP zgJi%Yps&QxL(Jc_r!g3;Q#ugKJ6%NjrGXNAP|@bpOUbBDy-n^TR?zbMh`C(m9s0BI z*#giy(ar%0zXq1Rfo~^~Kw9tLrU;XtdG>oGoH5=w zQ~UMvEbw<65EY7;`P2Wci2=Vx^6Dhr9LPw~m0O1D^yUP_T-oU-M?J?8B=W@E z8hXv=z+r@l;YwC@wRW9^i7Dgos<9{b#wYL z1B-PPS7ADwBWi~~8S=4VL$B{m)#SpFk0?KbE|;o?`_+Gy8opSs=PS?cC41&}CGMU? zI+1wv{sjfW;8K*3OTzyCy(0L}QVRq6DCIw6^7yY;a)2e($JR_mnSDVz2CL9Ze!w*X zr;idyy4dj=vpoJg%%Gol9GiCfYCoRGKhu;S(|oTHS<5j4wLW`}t~`u+d`j_|BqR(0 zTxQ8eEqpt57EO8z$)b|qLt=~tRiq@%r(QDGLY93wq{R=Fd29v+2GUa5Zd$%9&p zPnxY=YSa!p2ri$%L*Yd)&P8l@`R(8Q&~XabPq_)C3TDimc42I@2BY26e#hrKYmp1f zvp`eKOy?y%e3M`a<)5J5XeV93Kr_w4QX?x(GHEmb%14Etm}nmXLw6Q_SVKT#_Ig$Z zn$+~2nenmAk#>!&ki@LQn+?du!T#geZG9CQMKu-U;OKHPLxT0? z3Qj?;21xo1z}7Mh6pM@cFWD(mxf|T*Ku7T~iq$cpnb`ig0wK%oHKa0@%0Mz9y%}Z| zvxaQhfB+`Os3BwYx*g3x`VJrN{*TjSCMqP!XHTXF9umI*<8J$^4V5qNxC~_F_7}rEw5&dO2c+b} zKf#N` zqqdNvqedqhG}4UR*4q?1_=m3yW}E8`$UO(P9hqah@Etr2mwM^V=dAhUD&h?%buc62_y!zx zxeMS~_oe9zD!WO2#8UcN0a^yXcH`~Y)c7JIznwm};bxlj56pz-i}4!Y09NLz!QV zrO;v*SZ-h8MFze5uU;t#DLjD%2*pS8=gyzmC9;Bu1%~SL6qW3QHYmaHlY*fC--fRU zhiY2dH0}}n?K9kWBH)Ta4@_>DO1;)M?7~?|8&zYXAG3Vc#ym#wQ&yK%C9T{^DGwJ*l`0$)ac?oFP z;Att_|k4sGS?<>{0U%s0bjQk zC-RFMjlpQV|NTcOGF<#VDSYck#M+mlY!t}^;;=h3be}ZA*nOYmc}SZ&^$HM*^5fsz z60N_c^)R?=e??BG6ybp4DM>P9l68f85M26uoCw*tZ~IC8#EF&TKj?6??|!o)$f3Te z_~wNt)v1!i?HgrjttNn5j2Jq;A=*P+4WG@0Pl>-+5Pe0Dn_7RJy=yM<0I5-ONK*XE zi5ILzdHPZxdY$^Fl;4VL-WJ)qTH~oC{T~Lu^??1}?SM(l*O$t}I#gMWaS6}H00n%% zj%j-d?n4F+!}hI7d+zY7&u)(O44w=3qWtZ#Vht^ppFMuX5~g(zJS#$IF?s_Wsn&SV zMuk$3OBBJ*Ky9w!uA0RQQ&GF92t;~8Bv-NsAXl|n40hkm`k=J&$HO##o(4C;gaz@C z6R1e{(c_Lc(A)ZBW?Hpj>a4C{*0po6xp~)^RaH`1#3_DK`URrxkRgaE5q+(|bfI5K zdm}*O?;6l#+ve0*rpg>%2F44rSiv;>Lp8}_gyNdMOR6acFQGyc;JeS8NpCjVMA@@3OLP#EK)jUU0>v{$u{RCR9b1y56aje>d#D$N1A zK8uTi?R3`xC0Vi3rL3zwddUvPNh=RaMKS1=7ZS?-XYPjEL`*;F>@zEvd1(9IAa)>Y zi~XGq>E{>kT&AfkO-USZH3-LwZq6uS2UBDW1PFe{1_b;API`3$-)Iaf8hp#5Xe=-z zy;;Ti2?pSVo=t+L^2n&3T={XM$Y=XbZXM_uu?#iHg2^w+CHVu?jak;HMui#%);npv zoz!M2igexkATO4M^s}KdP(F$8EDvu;rJon<@;&}b#g#!0tT5E=Ks|d3NeezpJ@|f~ zyn-9Rc39ZD60Wc>-h`h9|C)K>9ND(?VLeMdb-{j{34HJ_Vw2wuF8G~sXW^XEw8oPB}eSK43GD7#Hu4 zIoYU&?IZ$q%9I+ZF66<&82|mOB7XKU|L%=1$O6t#=m8kzf>aV8FXZ8;Rs*_xcje?a zltnudFGnq0S~5W%sU8d8ThLEGq-)uZv@nByVv?(}gn{;h7xos~QT^j=%>iXBI&IR3 z^W>Jx3owj+S>=9RV`$6XuAqu&e(dg{35xTYm5fZ_2r&2cY?{Zzmv<>WyXFIQP8U~5 z?JGkm(f+ERFzYbg+enBm))}BE_2Wdrpj+^%(k_q1ypq20Pu$`IT~Y3nN7{0)ri<@$ zY%f~uf$82e0oS?~0-3Gzcf9C8!#`UUC=7&jN#mygEXrZ#z15^0uiiDTU#nN(TH?8R|=W&>|-p?3}vrr=ZRvr4+9+Fl45|N zI*BuyRH8yvCDG$W6{rGBr~-9au+n%yTJ?Q=R61(8;efc{@3n>Gn4w@srKkT7iy!sq z*1eT58}-TprGj>8DT8@FKS`@Lg;BZFhi+Il33>r{6)WL@yeZa&Z4F(}r}@1Hm_g*- zCi76u#MiXE4=heR4;&(!3w%PGd+&uQvv`{@L;BzU3@bEs{bC;ZHd*SMG@92SWWPel z`V!IAD}PAd)x(9X@#J0KAI%e+_p-)*@ozn*6ph5a{{)Q)GhcCID93H}#YB0zN_;Jv z_rlhE*Hq_6cZP_2VY~NT_I)K$)hgs}AmbUJ4yt$vD-eLYA8(N(L%`yPnbCYMr9{r$ zEQ2JKQv5^`Ds-%0t2x9*O)*};8qzT?kO$rlC4Q0%qV9w>R3Q3>$GSfQ*a;IMS8HhG z^Rn*E#UHAvq~>o7%p9Oepb167l0feyu!ZI??9qYOUZ4jyKVE8XX7)LgQThEf0i(@Z zxR!!rSJsKSv2;qShnX->Itj9mVurl522yrw)x>Aie4 zzua7IsmKJOqLfl7Fcg<6SeP|QnBQx^+FkFRT#45yo<7+nIaQQ?W^lw;Hn<;|7;p|_ zQrA@%Uf47!J0N!cjh;Pra6nm$6G9Jm?R)!)Qgz)O>Q5`0h=N)^8a4kUDeC!!?J+Zd z2VHE37&gAj$Ief{&XkH2l{72anL7zfPK+Y4YQn;sS_|Tv#rHycD3}%o!-g8!>s^^B-}zqnX09;`H-fKFs##HEO&oe6!sC)ihk`@B)4k z-cbpky68XL8*%pdF6B5N0*2`i8Stj}@AYoMY?iRcDhR`IkVYw&z6&sG);Y1 z(ZUZ9S(Fqs)KQASpxxV>b6|;qkRJrJiA8W6Dcr6ysFACZSzwvuiWaDpAcv|76Fs_U>M$ zv!CXh0t8c%B4#;TvKRPSO8Y>zx>0K3=OnHk>#=Y4fM*(bZH-IEPbww@17B(sDpSu1 z0YX;%iyha1M+LgU9GE|kVo)-ebJy*}R+=LhY7BVc^Nefl+kXNsU~5Y8y~0>hnu{b& z`end_GU!Mmuv+32*MB@jWhIgeM_H4PqiQ;AA)%K!&}&J^P!3J{3SCL##RniX&_K{B z{dwB=g0lZEO)CB5$&)LwYLJ$p&5Vxyt{!6fCj3O0rawkZ+o8N*fZ|Nev$FI*$DKjNk?j^* zEk9mEb@8hOCWq<6yXnD;te@>^PY$Ja-wjvT?GQ!R1ZyZ-9&Z2-&_iGGnV2dUnr7M< z*;2=)fSG5{+e!U>Z%zkraOb|0EvC7!`1z3MI%FvR3Ddvb;*Aqd^_=Lc9ve=~__LEP z0vwO^I?+{$46o7^F$STfR42_zmbTJ=$ZipYL5(Ezu>JlgNCD!=1Otb-d-FfYkH5`G zMJ=0@J-xM%rWmTo>Y6Nn>V=mCYwAtXWnpAtCTaciA1KBQoS)0!AL2yu@i#Ops#yyB zQ}fK3%}}0J0om1i|Pwl!|@{SQZYC)I5=ZPu>)SpU&5Kk0ITItoMlqX*BY`6 z%_xrV1#7Q%r-iJAam?NNH)NJ6tP~-6exb%ZG;_hRxqNVM@Xz_pFZTnMgX3GxIOxbcF8M&ybY6($^P1?b1!BYnB=gyV1CuUh74gSZ`bq7NE|MBOJGdg5- z2sx5bSERC6zOq+ZC$bBPtdMnwWMq?-l2vKhvv6c5tFl+N?0GnM&+pUEAO7Nxd+zgm z-tYHoy*nx_PLCXLJm3G8a(zD`F%cj8Vd}Kx-A|&@p@L&)TN88Sqj1hKTq7dQtfH6W zvEFXdM5nVU*+g+EqL90KK*LweWefjD{?>(HlX&5R|%No%= zcDfgj>t>#P9Mg-AXIMg|xB(GgsX0;)L-K79p|_|=(6={!ANvm^GK20MU5bU2TUWnO z#~lY6DUPcuym#*^_^giJTY<3Y+A?FD@s^%^L^s5Y8MNQ0+fqM)c%1`xyK_ zBX8ckX~~JkX|GYtV`=1x;;3WEz|q0Vr46?u+-xI1Qz!CrVJwYn+Df+?48^W41K}=l zA7`s8uC3bsY@AxEWY@i!bFw||OK5c#w^&`v;6+Yh*JM%MsHgaGcOOE@53Wz@=IYg} z&3bub!mCc3Pg;^>#m7C!W{Q+8UT9-$`&4I>lZDXjBGt|J`JL9Z@c zTaONPtV`1ta}`lY46 z-VcAo}csuk-{OZ?x*+z(d>b(uk9Seml0V;{i>HPQJ_=gfrdF}HB(zjosY=mih z5!dYnfu7Th^7lG!Z%5;4k;!`mTGY{@BS-v?PD68TnjL=dl_BoQQdn1`#(4~SqlpmW zl)GZ(U2L*xo$cAm#P>liX)>L8!bBdU++9GogVYbZ;#1zoMs#t5c%Kme)iNA0HxY$> zLQF)tsrify?>BtO)q^)A&U_y#b$~@*ZINBrnU_{*aJ}5%Ba)BaLCZ^Q8ox; zt~;`J{<4S4LjP-qVsfxf`ZaytTa;5%e(i!AhPLY`oV?N`_MA7@k53icNLkrqL1F?e z>Yu-1bian6_Ud#uH5BB>8l${a_Sp-_+V+>*jxRMK|3;2Ih_uIXUyDI~^(IfGdR<$ zbA;KU;{pPQ6IZoTIy_r*#(YN4^b$T(9Z)+V>~L+F{ikn-#(GB4fQWlp2X|pn8+m?z z9&JvZDt}^osL}JeMc1YDeuF^w<3mzRVAGXii-)y3%KI_mhCiZw{c>20u7|J&-9bv% zX!7-E8XZ0P%aicue5-7#LHjV{XSvIx5^Z_O&e)OyK}>Bnp2#hjn6+%%FjlV?Q}(lH zasa*3tWo?D?{*=;Kzrz2p1zzQx>*4Isj+ciYzd`9DRLW^_{|?imlpHAs{yGSi~3rr zBy0UDUr83cpz0z^nKT%(10l00!dM4^E$5+z? zi#m*ZAeO|kK9TW{(#O(aeduTOKcvHE-0UulL}ZZ>JTOD}HyiR7TGrMmaqTGH`h114 zzUbsImFE9$H;Y31;sU5yLb@jCwxr*r!wCn4f>5cXnZ$4vw^ZD~iBQJK7c^HTh?L^O z(XL*s#QBh^C2+viZh@s~PE~~qX>~RewzPI@uH`m8#e3u{gYfGm7vmFdN#fz`%i_C3 zHZK$sNY(dCTHY5+n|~6P4q5A##O-CB{KQqP-p7f%XdqQY4`Laz9G}_+=egoue_<9I z+k5!UCPfUV-uDcZ2>gwvQtFMx#6*l{d^S83(WKB%3^R>IsdXFE6%kzoHiVizL$}`a z`UO||2w)6E)H#yS)-RI3sTws6E-(SeI0Mj5_ock_G3u#qh|%mSXf?`m@4J-4Q#A2G zUR_+k%*lrUH2h&@mCCm~jOAG9T<-m^GcQ1zTSafTC>QXxq z;av;6<`eN8D{^VD!huv1A%f<4pf*fP00{}+$WGhC{{&`L%wIlyBzeGfMrWvT9T;Qz ztg{zB@Pu=WP)83eJ4rEQ=7P}G^U8sx)A!l(KXI;xvGllRyz&ofn0j&1cCy*qK*TN9 zk-`&;BG*jfGXBGpW+s+?IjUj4`ela!qJRb6d>g3tnMKaYBP231?1WF^+v+T#r>NHB z-xM&4{OG+v8zrR^D-QD2CE{UzHrgQ0j8CLRZKJr#0-Aw%Po1xZjQGqT111Rzi1LIz zUdAmFJ^9MfgWTKcg;MripG4lh5J_kK3MBGXJnuqE-~MdO4=V_%*Ol3(8^LkUXlIO{ zHy;v8^&G0b7W;mhMbd=!ex8V;P5f}uv7pqmcY<6hOMV`zLo+y2;-tg4ZNiy59h}CB z_b**_QcA>Wi57C`J`EvrHv2L-VG7@lXIU=U^pTCQiR1o3XULiijvQqgUU4qkP0nfR zHk=J1F0_fh$CHl}u*hHLjF_*z?-YOwtJC+Dvr_TD*X^4|!DA38Y!jT5m$Qc5wv-9* zp21JrJ+b){kWmz$IX8A0k4?aY*o!L0TD6;eFOFx!F^TIQeI*ofPB5DX`=dEzRLLHS zH*gdu>4Egncmq!a%W*Sk6yuhVjP&|67i|s5%VgFo z70LF9CQn2UWfT!Fb+#RHyA4ZX?=upA9D-}SkkT6$wC&GLt&fjffYk&eya~jAWCFO= zZD;^UjHI2JBdk&Y3rxt^2f*u>-Fn;nJJnNn-|h@}WnaOFeTxufWIer}DgLX|kJq0{ zSE05tu2xX6q9~CQ(!jT~b7IaVWky$9B-_qoWo%C-61uz>Kvz7e$~8ae$&IE(ur+b% z-an2x_=-3fwlogmUsj%K)zG#P9zeTxl&z#O#5IpLAoaDa{{+Yb z?OjP5U+a9bW&GoFwQIAN9FW`qKwwAqS)_;F`|ta8N#Yz*-JJBF7Dl}u%*N-4o2xvz z(YRa5Kk^=k7iZ4_>``Zs+1i`F?J=~Of0qr~P^5M~=q22tNKMu*&Zb(8CxQnu`=K}3 z_#uE*ZmKhyPzt%Z&z$~osFvpp-_lzFaUSxJ9s{NRR%<}l0g^}_f$4ELal-L;PP?rar3R4)BD4Zf@r$*B893KE5 za<#eP=&kIufyZ78muyQLY4hV0um`Ej7E)e`mJ&~`gXxW&m@@l*eA77Js&TTIxN=i$ z!j@_Atcf+$?Jj;`r~R&sb-lBHn@k#RPu2>Ib<}d zdPlFIn#DQ=NBqiywYoh)wi7AoKX25if> z#{%VAHIy-5?L4S>iQh3OIesUOs(`ttmP8VQGz(24@sbwM_b{}zq$i>Z*lvY5#fb^1 z9#SzLY_PG|mNX5v39|F2Fn=>)@a@S6@9@29@tsV{LSC zAXuW6nYVMA<%&F)@}-3K0w;EXx5B-2&n_CQ9xc?ZdLUx0&Kk#(VqK=w@mf_#lh{~1 zd1Tcg7G>R_Ja8ax_Xp3U;!P=rSW0OHz@&0eEZrhm#V^RXuwNaGs{gP0!j8uITE7Ou z(N87EtZ&p-uz-Rd53tGWVC)|QL`UCn??Mi#Tb z;~9=a8F*j5-QLyWhFb?|ZS02c7>3?4BT4~4d9Z^lRJtOyS6IqPMES7TI;g~5)?w~0 zR$uyM147>TSNdYvYvWaML`LES8d2fZlqf^gh?9q0*^z8UKJDJvk!kipR&F?bf?DXw03v2|~t6TZFNPw2RIcYr3O;9;*omEBpD7m<#R z9pBn}Ol%F1Zv?y}16aw&KH zn3~R!jR}$I^s^~UCQjP&n9+fF0rcR*&Bp^z!%TTGMaFTK3l=zO%N3@3`L4Klfbd4$ zU1KJZ|D z=k5W?JjUclYA*-r8)_ZtfcO}@Yv_si80B5J_IaTPz6L~Txh#W%*R9AJEYX(2UTFj*GNID?n6uq}pZ$5rFCpY1@zd0+U$%pR}5{U4YsybQ@ z-+8|a+i1dX?X#t0Si%gD?G^#8?zoSPgn$@vm8Hhj;>_63zU7vP~=nLH* zVt#JwVExBp3&gu-SvvAyutBvZ;Eyla1jmp#qJJNGiw*q2ggLE%*;5#)aeq*t?9d&Q zw5^dZhKs+*fj%aLu|JWY&%#JdMr^N-rUSC{)MO06-+(a5)gB@6SL7Vzj@;JAkc>Kz z@0L|K?%HJUE)uj`R4eI4(|b94KriqGH;-&feR}Q<9n=Y~vFRMR5dH_JS+#}k=p8(R zt)9i2Y-jy_mK>6>0N?+x2HaB%ly_8pC|?^5 zn|{uNeMXA{@2RV`GDiKrv+I(1erpM{yeD4tXhSC|*GYk13}2)qY<)_lQk6cs5wk$@_Ctcad$>f5pK08~N;g7jq69|P{Vy#ez0F~Mr`!^f+yf@gmV z!F#RnjiKv`X!T0^i|vr%IVe#X4US=H+R7==6xP8tJ6U{G@#@H7{Ksc9MPCCPv472d z3EQJ9*D3YuTH7$umS&Zg1_K_Z6Q;{NFfNCqny+iaH~y+sQh%~R5_Rtxq$g&2FOc+p zd~j;oTsrThWG6U58CHuIf9rg6s{fVq=Hsl5I}c;-9zMK({NktfK@nMQN0~Pa3xai9 z7Y6puiLR=Oc+_NOrGr47aP>g^`Wx0c{JKp84|Sa&2P$T5PXpfi58M`E?W5V`re0!s z|1>5l2$$U4qd_0rouF`aP6Tt>tRLMhyNJ?xj)yjcSTwv1X7THzWH8d?@8h8Xy6xPL zkNuVJ-G0ZTmyIVW@}Xn7NPm#L&tsuqhWb^Itj9le%8fl%&;2EiSa;<^&aK!O`}|Xr zig&M#X*_F3S*IYtB>3e1H3u^b_$oR%sTDSjpuQuV?ZTMxROp$>9p6&%;0)-nE z7vNky4&4dD*lwJNRRpb6?wS%bxCer2`c(@{9EjVzm!++pOoEHTtNqseh}xO?Eyo`` z*kbK#m%Lw9*`CBmxQKA-JSn68{dl0iN^j%+T>e%1C;CTcKJ*p=ehqKpzdAt1n?}zU>p_eG@M8th@C{Q(&U0RB+Ewu*oWvzY% z>gffM%UH%^+ORQL3m<9lYl6j4v-_;Mpb90MlA{6=m80R4?{}?ss8%(x*I>q!)@`bk zvMVQ?O-D?p&1iw}7PA7yF8ZHm#pGYo-{FhQ~}OYp>;r zDTMV7r{*m-?7h9DCy}@&-1VCPt7p8)Iu?EE2b;xv4z%Stq|O@<@yUxhOln}TX~@dq zw@dS8!w~<>nxFk9)R@*nU?PuuKG500zq=lZ`gvLL2&0n(1}g~cLcj46<)%^~^YXcU zZBGF1_4V$<`gP|6sabrR45n$}N=x{HL3?6j5GWAVhu>>~E`-ZfU;UN&QC)4Q9Rvpe zz7sF_PZ}mvL)OBOHUP}R!p0WV*oiceO)aUt&RpSv#G)ZK=sa!9fEeIpu$?7)#)gp? zOM9^hAIV(vZB+`2Y=eV1?^-KV?@)TrZHngXS|z}rymR62$s6@UV(_zv)Nq|gUmbWR zip4(PIDTT`5_(!F!Cqp!iutRL$>OE%A98I&6F%a#vidHpC+sr|W%o8t6uxAp?^pHS z=XKCfPz5?a#SL;}tQqHi6ihCOkvDLIYc2>>W8K^dCQ9q!}D{ttC=(uXoGR zE~lA+uOeRnE#GQ_+m_`=)2w5XsnulZ9sIhIj$W-S9~ONZ=_;#;{P?!d+#Q)_0kCD$ zBG+SF-fIDCFMHct>xJ&xfc-fg#t-YMyK;%tW;SSyl{mv=$VlWY{^`Y_{t6W@mCXjp z9TW;cePp$BRQM4vk@(3az;_}|>nu(bXj0Ggn{30DdS^7Ytt2x}0^_c%sgd^as$Mb> zT)Y7iwJk!N>3#OQ*YD3RU!aX$J|CpSU5wF86fUhS{Jva&T&X)Xdi3 zyw!g-@PzmL1t0aoaWw1O=ts+(B3sgyZfY+Br{&m}RKGtmCkt?3x8#2vVV?(`*qSwe z2ns0i5Pj}=B7R^(9Y?^)(!VC>Z4Kq#WNJS+ z;u8y2^g!;p20M1yl75IjQQYgm^n4$pjd)Uv*w_0fFoy#j^YgFd9L8txZLclm2PFM2 z&n!R4_X`^rKzlNxCq>=edvLYH32zJrA8kN1`wkhMH7{hUs^%syh|JRC|MT<`qnmj> zR+@;CULR14Eh+kXxcPmg3E}o^gtO-?-RrfPyU;%8iG6YcoW2Y+i&^!Lx8^45f~W_M z$(yr(_iyC}3XQPuNFEhYfOgMp)}K2@<(VTmQMHdmr6#WAp4E_Pm8Z6Jya|Vbz z(b7=}WATlHe2y1gI>9CR`&(6rEBEJEubrE*sKf`>2s0uHIp^9%``5FCTQ}g&uuo?_ znePFte6j!pc0*Nlg4y)zO&|J6>{w7c5}dIwYHmJdYlH_8pYQly(xYfp{MLE+aP8p> zd*XIRJM>6j9c+kU{r~B!0k31xgH5%A({1p=C5{`1?#aP-m(x`MnDgvs#ID`}sKgvp zwdO!Le@CbfcF=@J)%|(}FisRoCd6Pv2K6 z zkB>5gm(81v(+2bI1)o?0DL#BfFrd@mP&$$kHQdy| z0z>J&(0%-EXoX9}8+O z8_{>O@bOD4&&VP0>#CJt2nVVd;z}@{Bn#8%isF;Q)WPk>&f*{KuyxS%r~Hy?(woWl z;XjXl7OBrKE@kw+umg}+Oh5YG5Kq6T{c<@c`mhF|_%nE>9(2YTR7r4Y9$?NAD}XFJ zT0y4!gTXoCM#vlz+vt+vbM)MVqdVf`a~bFll3j*$M`+J<`|$0FxHsqSv?KW@KTp8a z3HSzcjh-A-M*l1L_8(-MIdV=PzipKtH@FBApw(gzACgzc*4mN#R*wg+%F?p_uSFej zoT^{!C;a!lZ7;Q-U{|x7rO5#olM`bIvTT<6gBhfDV zJbqojof3dO^^xkAZC7l~6pf9Rk#@8RO=HucKmVcL)C=cyD-51LZ8Q#g1{lF1N z_qtP$W7xHH4w(Ajn1%YXEbC1*;k-0$`eXdp;Q5SVdL(86Js0kXkx2-2MdIQJOlYc)6A&7S87j+Q=n?{K2>7m#AT#g|f5YkP|E z@w<1In~uLaZ+4|U;gqBQQ#fw>&%9}Vg)M?l^+KU`1dAu`qOnw!(TdS8bbL+GBxjvj zUST`lI=vK6+AXtd2#m94dv*hpW`>&_B{5=WOTG2g6I)NAtxM&h{;f;_K}5faHss3O zc3JtzoV)JYAE}^Iek%vplXt@+`sMD+sQ*EvU1oTf36;4FBh(W;0gP6%@i0X69LQ<} z9?p&h@oF>XK(egJJKiYJaQJQ*Y%e!ba_3#^(5H<-;#Mbq^t}Wf~fGkpHn7@{)uAASMZQ zK*@C0i;CWm!1>$A)#()ChaA=W^J4?Gr)%6E$m>yLE?v6xf7jH6)>0~D(F(;el?X-laK^`_YHs+}y*L_`A@ zlTPOXyl~{qLEHfWevMHr@XU3?Y5nSmZbbds|5NHGRyd9rOerdfSZ8+Mq)Id!o>o78 z&v1%VIVrTfpFUK7>|AjC60^=XmgdG?Fgd!H9&Z#k6?}#&`((Rp`{}%e9tk;&%;=&! z^ZU}xj965MCHps_0hCdw0NP~$9sg?qrA!CxDw8Mj{dSasyY3!Em-Bzg{}sn!u<&3} zYheHgBl@!O>U_jMkTnM*)Lq;C(fS;8Nos!C;z*x+(W)EyIQZN+XbKshLKN>;6TEBv zcx8L>r_hax)RG2KF3L`a?1fS&dF7rWw3$NOpSrWWO%P7(sf8aMz)Lz_01GZf|`D)ncbaJkrEbi z3~1jdI&p|rR-wfv@7cI8{IOoXV#asTXLXAcWyj|5wRM&oM|0?hUX(iB7v9ZY)b4nhPXaA^N!iO^EJx+K9c;LQJuYRXe%#tdv+TRpk#Y9F_87DdMm+?) zO5Hi4!e;#Ak+|!9(I`0Rzu{YtWHBbWM=mo*KeS!C5Mf}WMFU+rpXyjjl56v{0_J^9 z|B5bNr_xdawTKc93>)WZ>;&y4EBeHS+ebWGF3t|Y#%GV%e!qXI4bs&DBF3`_1^HHc zqCPj@WbS)U9>xucwD1(-TJ0Idtxqq_uyg_Y=?<^(E^}^*S{q0UCOpZ3Gig$R z@TL8U^w}v$qG;oxB7{GEs_dfV7p>%59r1aN$fIm&*VjdFB#Xs;EU`Mp?UCiZ)EES81llFhwyt&-W)TD;SuABdivdP96CCojsB=E+XW0ifogU9pLi5@wx0!}!_uUvHw` zAT#MM^!=e5f`y5PbbgGJ-2xK$1uiAc^;$T|Ja>slO54e*rKN>t@f-od2!t6l^}&5@ zl9+^;(=VqdvTzIT-+Cn~oYGScXB@8ePf*7u0)PJga4Rd$w9epg+pTOX)TcBAdqwyY zu;`&QqBQ&90qwVswzJyd3Z{MF$`aLqE2G79$~oKu)KoKwIx>Z0Iwi3DE0^(xqIl`F zspA2Z?K|d&WZkbx81mLkgS@3%(5WSbs)1G~znhv_l?q(+r^3;)jO|LW}SL zd_+MEQVjU**B|&ygfE2wmau>y9bsDntU;ksy+P{PX;Xh9&*7B4P2pBOz~v~=x<2XaPFxb#89h7K41}- znnTFt@OU!y>ZduIY}H!zsRrq7OLmNX{h%B$i8O&be`@sCad<@`Dhym;hP4oL4;r6T ze2@At-cV-RM*(yEN!ZGdRt-t7XdRjWNPuDXI=O!BoST`AKKOFZVrKju86|QqK);kN z06JkSPI9tG=;)KQCDJMZLw&JzLf4ae(kJjN`Ytq(%=$(I&R{Vaa=fRneg}z_6$Edu zD;O4?51o%D{6DBT0hWwQL_XK0Y0G3CTy+Iq!dDO5i+{}zp(J?44BJcahB`%IrBl7I zur5{39DaE!y=R7yZ^iD&kCT; zPMlm9Txb2!WFi+4TKb7+-74+62ph5VOgdV97~yJTbS@s@`a1EBFde7~UC7Bv%lawg zUQb|e=`*2dO9fTV{6Jy;W%s+!7IDxB0xO%TOP^Lr*U5uZT*;zehP4jr|5A-TlJZhI zc^FA&v-Ue3#uLNF;kSMSUh0z2sP3x$sMfOU zZG%`j;%+&q?8t{HDdair)Qc8r_d{G7b(qrI@xXOT;zYB;;cBBM_e5OZfscO+2>v9o5XOAu|oDh z35Eu z|5+!`uqKLt>f-LN&y>$DwvVIj?wu!JdRwkZWCvSFo4MBV;quzEtr`#p3ml`wudHiI zoaj!{gOut_4i=!{8^c3|%rtRa?lT))IWu~hWOveto=KJHy-LH$`2M#kj0jNoOK}6HP(rIj5NpAs^{~0W9kIH-+ z=#&R0s15$UtAw4y=3lpv0xEu9`6cb#eArJfWuRf%F*XxjpjqRwG?;_sH`P!lY+x|d zEl}aV9}0t%c5oRJ6RodE4v~j(z|&8K)om%9YYob4wd{!9u7lg-ZI$#=D~jKo&ef+ z0$B^`7Fa@&dDbYS3&SaPprT4tde*UTDvz#Jj~a(ax+QusVFJ>ani}(T%mkco>Z=RW zY4VX4^DmS@X)N&}{uT^HtqV+UyV~8L4xY29T4(KoAimd0DD@FQY-F-X`#=WTv`g}3 zU>~9akfrtHKow3u-&7icpEtuUKx(SE7gleoM^p*vqX>P1b=(E!7u0UU!TX@|v^jKU zgp#l4((7@SKS?ubn;HD@^@ES}p14q06P2Ei{`vhvwL`p4H@X(S!S*W&tNYXT3Qad} z#Y!53r6H=D@e&RY`gu(;dqnB3UBeiU*h@_8Wse~|4*UCt7k?}|rvbc5r`XwPg}K|O zr}67Txw1I!;rBdP!(ZsUX;dqFgL`Zki(Pi#=y~KFyk-}Xz34Z7-OA@%anV>CKmyd| zA$bWo4Uc=M4hScsUPf>6S^)Zxwnqu8-;?18HqL*^QuGyMdR9t~7(6K3Pb?1PiUcqp z4gy5QUFrc5%+f7*?ZgISLHfw!R$UdX9zx;NT;q1$K~!$ z=>0oSoK&7^AwG!pBJRx7Xm@V5geNwf+o6|4ACSB&k0BWb+3hY^tNCW}syuzdN-CGX zwVqicum4Tjexa7hbhokCWLM`)kx5rEdr5t4i^di0^I!1u5wCyTy-;LQY%SFj*>Z8$M@93-2e;EklzFYL9tO9at^cFp{3kLho#_@ACr%MZVvOh|>L=I`50n z%2F|T>K~JIZ4Jpduh+07$(Cgjb~Md(?5+KMbuUp*I>HIigg?G_6dlG5xP;?l2_VB+ zdAdKQMeZnARqm*uT>ZmI26Ozr*B3v0S9sI=#Z_tCb(!}(r!V~t)6?HUR^eV#h7g@v z{8`sx!&+>zd5K|VaU59tOFJi-5Eq_>t(0y;nO87GeTFtOXi5H^c7qDrrZAe=0fFYc zVmk@(FA9;da?E=~>I}c((EWRS#8rmZ(S%c4BsnR%w548<-%Q&E>84~dC-LBvNs_rpC|D?RI%J%)E{c8BP`Ov~+w zc;M)*8%klI<_w5-dvu{BA>JnO3U1P88Cp1XSq_?-wq*ozGG1;O(lsnyUN!Y(%_%Hb z5P8Bql*O%Edw@fD?}L*RUDf(bk}*xYq*orVFUy|_GO(e$knPqi8?m}}^3RJE*iq#h z!a5O7`08qFJGBZ%lRY)T*Te7^1u9_;8A~$I?`J29UyNw9 z5h-qqe(FMpxQh4V$ulYKoi*M%{iKMe683-Yhj$mwjGdQ69Z75C_Z6kIF% z_gh7i*1RH5asjozcQNhqm~5W>!3`%K3~AYQf`87o{rEq$}Sz;e^CTqW&)j4I|t5}1zbvJO_F)qMYv_UH6k1ccGTjxe()5D?xvlsZbV)p2;^*}&l z1ZcVP{53EqqP&FMUb4HigxK$SJ=-6qb*VAOt6$$)N+~a}cR&*%)pqKBq*B^(Z z3Pl%sU$oJT*69vH=FqXqIl6tky;O$Yd3kM5)A_}QDsH9LR6WY=qt2q%DG2>)Z3e%f z4BQw9zidG~$+<;(JN9{y5Vir2RSCa-iz8Y1!9yO5o8_IZ$B#rY*t5UU@yko7_*)tS zrSS=%(*7(mStvT6!%jpo%j(>epf@v3Z$(xC`}1paR@s@i-_wHfaB#M$JK87 z$v~kRInc6Odu{x}4}XWwsZh6n*5^LB#97K>T_gDV1_Ox=)LS>UQW$W!!+W=g99r#s zi!j&%RA3AX9d@X3?N7YSIE>4I6{&=ncvwYjhA;yz&U#S9cB~YjiXUE(XhPO6od8A| z1>r`IWJ&dsGT=_1A5s5phu;_F_dgg4;l`r%i1Lnwv=^Q|tTdN7qOn?!vtH`S+e|UIW!KN{PJz?JNaEK&8+0r= z;^lc1n=D#l_{^4+FRNpas`TE~)YigTDtmi`Uo1)rqxs%&&7cJ-iUvY3UJMqTp;|oj zG`uGB<8xVC8-9@w^5X1k^W+rnyH=ZJ*BPSy2ZjNpAyLC>jGnJC6|1UDt%V#WqP01 z+T^@+INENAqLc>-2}!mDeTdn#7CJT`Fltxb-4l54KhA=@l_~ln-ES4PFyNjU=c7DF zeQ|p~xn2Z^xhP=nDXrN85>XIy*hFx=GbD?Bv&glJpVHql}Tsm z6J<&CinUZPI1x>l^G^tz!#jfSX83}Z`jp7TZaKY8Ftg4jnl$;f>D8{pBTX9FmOw;s z46RJo!WwlzBs%MI5W#o?MXqW}RSew7gt6t+ajCmdhuC0XYv9$pYmvC;h|%}op)xvX z3{t0m=h8T;`0C^`%q|84Gm9bZt?3ofyY{b;y@&f-6WB%A3XJ9gRurFKF{f9AyGISj_2Co9rh8 z?w(QsvD4X41xKb#N2ujw`3a6IOuSju$DW4SN??d8#@E$&(W|5S0K@F^h>sP3&dAfL z3qV-9iye?}7eNO|_gIJzLcips_A95SyMk!bpLe0{@zZo5rdkQ%p+Q=f`gwF{nOc|$ z9pTmEc1xxsVc{=3D-Q`2e#!Egw*{+ItH-DQN~vUsmAFT~{nn~< zur+Zh&t)(!XpJpEbgp=>R)CT6P4ztf_JDnW>|%dpJNd`-B<}{@^FYH@ANN&hBo^zO z`6QGS^!ZRpRSpO*HkSSIZJd~ja$fXEMks2Y1{50ow!stVQWd4+-D$j*oxH2OIBXIF zTa`P0-{@we9<-Z50nDmKXd#wS&rDhHzv0nz zv)sdd2a}xux)ykAJx>=;`ssbGpwDtf_^a#|o|i$KC)<1yZlT)5VivISNC|#PU^d~l zLEHuNv6YVunDpHEeCSO)1HB=W#lm=msT?Fsw=AW?c}K)2wWy*eIWR46$A(9qrk2X5f0lWySPhT)}0QX7KrJ}>x14D;A3B57SRU2$Ta zcjO1Qqv>VHBAgPwmFT?a2-2N|7_Q&(%g(%0ODvJ1dlrq5G)%uD1=wVgMl}^rstqZQ zBhNW!KI_8#)>5(jaLIIFR>gAq#GmrBcPX`A*pj)}PPvPV=}Ia5F<}~D+ex@x12dbO zo6+jqIRr0Z7TZJd3?&0D0*z~r7G?Qu_YT`^%icI}g$O`kO7 z_&STlntX)=6ME$KGnAzqFPc>Paj7321Pt{ts0L|c0mBC#e4If)5qNd67%IW8cE}Pp z`BjIV*|LUj;CBN)jpa8ww&ArX#ShUv+E4YJ+wcnpW68Ioo{<1tRj+^Q6E=qWfYi$7 zj-lt=I*tCFlFboUT)tFW4aF`a%1Hl}W!ASMM1KljdKvQz!a<%pK%xnj9Fev7NL64> zSR{^^J=^yW>k>FrdiNa?1t>IA_}bz9dmWID>vJR5EwHS1$TxBEhdr^Z>kFdu`KfbM zaI^<@V6gDzCoE-Em-&~*TlgwH`qiBS>+R+6)!BvmAqp%Z8JwC&v--`v>g``1-8o<2?Jla~ zdOBO_e1c1CPIwcWDsP*u@~;yOP2W@;>OI5yy{|V%LBgoy{s`#LrnjJ*Iy7tG3(2TjJf*XW_1YSq5&RJI4hO@C9Q zd+o-?BZt+%a`LyV>XAP&-q)}zc3;MwK2EO{jKxyWS~Z5U3ew}k^%b|q>mEC=&s8Wz33o5?9h=1V7qvFMuy+)8LRycg2`PV z5wrH8Ghu^%Ah37-crcPIuy8Ipg$@GJP>Fj7Fo{HZdyT4H<*lN)SG{soXlFmbwSmg{ zPln;_ng>hWSb=3D=&X~#MxgBp(>ia~l$WtuoDXNLUCwC5-hcF;nf`WLOk;C?fx;8D zVy@J^>zcQQN~@fr*nK9Wv*f?f9qIZZbf=j-HZA{gV71mTV*L4Z?2ED=m#ot-veMxS z(^-eBe2Vex{fesIqd&gK#W{jKQ zCLi%)+$O@-kHEr#tPWx8?^!`MmGJ|j z5%C!a)b8<-6kk#F<)8{?FOXkXa*QqDjokl5Aak=I)ho+XHuD@E$T|1=_v;7U>r+0E zy<2Y!;lV+DJS=%MA)RJy6f9@))px@mfI_#7qO}6=kUywq8EhBVKO2Okt=IK$F3Tj-Rt@ z_~G%+TxxO{q_;EM6o!u@o;o*cRNugp6dIU9*LT*>kA(fpiLm87#$YR~0_vz`t;vn3 zEwg0zbdfIc*7mtxbPNC=9hlMaH?0wmcBEqy`W{|MYnE<3t~Jjav-V&;=^QV-$JINN zMNm?l*h*S>E>6CZN}J7J*nIV~ca|F3J6lWFn}4S@_t}IR_np1X&+oqe_3s@r4t)NS z>DQGA*2lwH_UybN9*EFGS1r-Z>fAPFYegi0IX{^^_RO)gW-sal~dU zopMjJJ+X!#8eu6E5Q^;k8o#nzR_0C!9rW7LwGP2>=qkga>bmHifuV*5hi)WfBn^-n z5$P8Rm62{lIwb@K1nE{lr9(hzBt&B96cA~UMq0Y#e%J30{s8rP&K&mHd+ilEuO!c? z{;fAnI@6o)m;9SEKU@rO0>QKgLe~Y#bGzV-xm@;!<|dxK_xB~)AM|aD9WoCt=U!eP z(;j6ua?6p}UGnHY7v26Z(*+gK1&r5-IhvlelZtE|A>Nk|<8)2zCOWt9PzYW2*d}aD4Q>W|5gF_G3xz4v~TUf%Qz?dr6O-PKEX zCdl4V;G^gYEQNp)NP|#(5bESr7KT8RfPYwe-Wiqivi`wEhJD^az+Y(h3d)l2$wLyb zOAa5&z-*bk7axw8q853H1uL!%^gKmFoWwx|-WUllJa7BCZb#65Wtz7IH$)gm+j6_h9 z5JRJV^FLCQkI{-C32Z7nq?Vi`cdnWlqN#JqV)45l^BLY&`nQjs=*WtT_i*`ebt=5W zjz1MaUCg%DQ}&%F?NUKP0bVyX5{4cv4sbkXb-8JV$%sX6Eag;4ia_{kF68+_rAe&P zW2NT3o0FuRF#xLO7H3Ddw19ZMr3IwuEJh*|e>B67Y@~oeT4$s*ESCj%d2!Dy38r!v zcL`OUjH5D&_&>|zuc{6-cC7is@(mJ|R zK@5`VYU1I!6IhZ?i(2Cyh#2eaRO^u!Isczv8(haI+MapzOJaL9q~Tq$nlY)HHC!}s z*;Tv#$s2e76CpGcRfnABP{=#+H(qpcS>O0p|K@&SG#GDX&{Z(g zMg!VCh_;Ri<(X@_dzSN4o*Vv{o5?z#fE9*blzc=@U$Uu9Db%uN5%q+|Q_C8PwqNbR z2_yH4l%BT0Fjf$Li7i#b{iN0E0Sa^(lgYOm7v-wCpb|lG94vn3@$Z_hiUYqX2bW-( z4&F={Ym}Fp`7aP^!_~m1Wu(bTiv#2UF8|(oCu90T<12C#M4fU-1JwVX)SeTOGS}Ph z3O~MJ_*<@ol*&sLdtdf~lwfG?^WhP(Vm(}@QLd%PCx>}{Y>~NbPC!-u?-(uhq3Y{{6}RUiW7Dqn6*mTRMzA z)Vzcda6)>Y=fr@-C6vXXvUvcW`W|gQi+eF=Qpdano_s3Uehr~|FilW?xmFk9R>E*R8!aUxk$R}(ng-MDhKi|A%m7oo}^xUT5Axe`Toxg20?-3Dp zXW_l-S1}SK?R&_g^+<|b0e=f>s6Ej1vd?iIZAdKpfGSt7?w z%VwE=DiUF--*stBk{;rWUh>I0^a*6ElJIB}9uXSNV}QVL11LDPc6aMCg6LP1?uv-R z^7G>CD+_Qxe08rmYc#1U-S_ql`JLk;<-yPQ(m6~O%D#Nl;f&={kI3^uztzPdzPrC> z0|W>#D5!t)vhCIvCbbsFxD%L8nay_IO}OcDU2S8J`KF(r-%EjyniHPT9%&vp@xDfX zrvh;O_v`%Yh)It6Esm4F>VO{kvTxFs<VlPtN=3G$jj`T|7QR5vM6 zlbK|8hjG}Fsp5@Aa53PTk<`5*_ME@(gtSkaoAg$inZD%ky7~(c<1gNQCk>k~C7k$l z@cRRSEM#!UDy{j4$m7(U7|%)i?y7>q*}hw!l7!v(>l=yS!pAAQwA z`^|0T&i_yI1LWC3T<{Ih5XVC@|3f4R+rlKM$`{9hZa*xIr#?la%-P_}=Sl5r59$wYrv~7T?__$GKTDLp zC@F8D^OmXXR2f$(4RE#jlq`IURpIvnwQGrdr^0?2^$YYJkL=hD^3EW}9Md0qcG2^$ zFUoPpCGj1FB@p*Wr}JT_!ODnk>CY9Yf{r0Q))A7)b)!6q4}w!qqKA=ED48KvVw}-k zVKj)Atz-sydY`AC%(FW#kM&!vx8$vEW(26)KnY}+eYnb>AnhJ!Yq4F!pY>J{21n3x zcgdTm5Hb|J$qM{&WBmznjZ-4=vY8G2pFn11pj4*&T3Vs ztpSp)(^tZZGLq2N)Uo8PX1jEGP{e>prin zQ0N=AmqO{sgwUtiiB}W0ev;|%;aA6IZj_hMyuY=Dhj%@w=^N%iK2U4akNvsFI)dbM zohI|^ga4il_RWA!=$Ir(--R(E48^l2vgE?U`ydkXZJd4Cz1PU@(c=n5ohsTSIYzw+ z!GA~t5=ikuj!&oIJGa)jEa72vDRRB^s0cKQDYKiP?3py|7855OjQxof@SDo5P_zqh zawm{{bmt+o5nO{j6g>A=239vC3EY~Y=ce@%qNBq7T&;a%3v^F9c zMC8(}%Q7a<2vk8mO5FQ3P(;50EJTqUf{y3%c7p}6eKv~R-PCak_bcQJF|o!)(oa@T zcb3wiK$w5T$!4|_LJlK|=v(*vV?y2@nZn-R`dd{^g?xxg z)zcgFUbaxQL*)73X-Mg;@2LNS1e+BkN%QP35+yt2rM6RK{h5BysmChoJxbpyTK#)i z_#3r0zj(uGV*f3S`{BNAeQ7 zF%$H*#&7>4W)W#5$M?azdI6nAMk+@L#Fn+3?D!dQxbsdgY{~lfl@k=q?)34)2WCy7 z!Tb`#NXl2ALw|wiYgF}4{}zGVpv9w%gDYtCC;%Bk)+)ebB63{104pDTeM2SDj^P1k zP}qV6HQ&G*>G$L-0iLrX;aeoWk~-fZ433PyejfW=;c-2Zkuoge<(+gbw^#_>c&6wu z6%vwN2rdUuC}8Bahb^<|D@zQZ8KObty2Q!sEnVnTp4S?ix?n-G6ZNH8jcYa&aEthK zKkr}ip4QRF;V#Rmru>Lx!RMMlzBGoa3Na1w{5*KSChYg3zUYiL z!P_uFE)ecn_8<{wXA zl^O~=sY!GZH$g_DCC7f@x-WUp1_Wh8uU~>GnqWQzQ3F`BJP@LM5h0rvg63rFW6_Jf z)jhm$@nDq-C-HfPSSYP(;4D(MoFth@v+-pyN&HI}BRHFk^A$`YI-g{ux9Kb4^E2Enq`3XnITgQ0*=|(!DTk z`?5A6vle+M*y?TV$3HKqJnqn4eDQtskf2Aq+&@81}5k&4K5_NwtyM|me?hVgu@?@ z=K`wF8xZfgC{<(u)!ybSvjTlrr84(huE00ae`^=V3a#4G|2?7K+1V~GVjvro?Wcv( ztp8Aj=_@KIa_YkE5?c1FsIK1<^a|f1P0!g}Xup1C;Pju;*if!hWdqe1fx#Cye0txR z|B7~rq=$GhHSc7KX)D*2h|#bvHZDMNsaXbTX7zE?J;B)4 zuDX&U=!jpJ&1v#ojyo`-f)0bi%_I{PiBS0QK4H`xo<%f!i{M_!_d#gbn3L6U7GocD zTUc3cR@n1A_xOIOP%ekosMZ&O)b8EvKNTAiVc4?l%ed_0#ue;y2)~P=9lyWc#B68A zcrQNS4z-|`yU%XjYD;Xa`<-9s%XU9^@|&2`jI+Iq;}CdF3QJfSxwaAnUE9qQQa5tR zd>OWY1*UPwhEB6 zgka-E|G);t-(A+4PpXoQDK|WlwaQKSF&44xz?kWg%BhI&8wJ&Z4ztsU~ELH#e%8Hcz4-Y5gjn!lVhzTAE z8`yv=|1a)FW*CTn@}c+82^>at@qx; zO^{pO!Wt0xxdyJAsjqiJb;bP>>`vqddDV8s_a2*k-MBO{kNs^deieS10k(;4g1NkR zvo14ouW}&Mkku#p-Ac<5>~|K3M1NMkEh5nwTpTM;4BF`}S`$aek!G8w`k0&cdS@r|%>pQ$GAbNX4M(B{JA*Fx z8xE^~bi&2sK93=VJ?q8;5#%ogAp9%>aTO)usbS#=@TPCI!8Za@=}5!KHrf>K9JE;a z9>8~KGrSS#I9^E0CDQsQ`YY1ipex+mNIB72yX@;t)!pl3_ukVb+=y}+ zP>e5CZrg__bgjO!KC_a>rQR3%L!4Z@Gb@uXMAEhoaVJzirDzp#3gvV6m}#>y1rI|A z0PSh0YD0|%&2h6BeOYl8wxFEIRY6n(8;GVRXV48ps6a~S*?Y(4yL_jyRWKwYi=_r` z@Xn?XfGi$GCfYU!nTT|KP`BREh!fLcL#ceoVMr*)c>Qr(X8sEWRzU%r_``npB!*(o z0EIZ>1*nR*JF{bjWf#7)NAp$Zf1wghPBS~ zEIoheK5nBTiSdF2nOjm+m^zdU+S?0!v$WE85bz7ec?j0{FVJP2{IDAWeGJ1Z zj^d1+X-o`V6k^3owtIjzo=vWCIWDfTK??OD1o6z5ywuv9%TPr0&9&ylNV5*wBT^{8 zhLbG>VT&Zmp?rp==x70Lw$)!R)s^g5e4}j_P<>nQ_y(zN;f3mx1lQ{&I%~?0&k`>p z`Vfq03C6x#yb~q7Itdj<+E2`-jTM79VYHm0oYhQGbV9j;JrUQ19$tT=OY`FzF90X3 zHD$q7n%#+z4vQ99VNaEw-EtHDyW#(9ip5ENb@ zdo8~Wcc&+_Hh;h7@0lmeVa6^t9T{Ci0JNq)gGw-sERS4(FUW9-a*-Er0VJ z;#VOUhx&3p3e&>}{hv&y7V;>lY?4=IZn{b1D`&5cYQXr)Q$u>3IzgN#6zEfn+T}H4 z#?-Kx7Pf#~ku<=}`8Fw_Uo(Cc8{7(J_#I7@>cn(aRlt4cC&mo71E9~EOPcnoDLO?a z18j<9k`qv(2OnHDiCtw8-rSEJP|}WIDB({DJKnSuF>IN#y=KeJFeuO)t#+Spubj5e zzCh#0x24{|`p@On3oib0?|-bd)z$A;9pBsi-S(lDnB=uC49!b~=S5-E=3Zsf=_U!I zm_tHNPB}MgMzHw0R}GvYAnsboJdwFsBP$m3AJsIKE>)H!ijuw=uJ3=e^~8b@tYLxL z&o z2a>qK-O`_KG<+PD<&fc&OX7BPlA3vK0as16|_y7PUmJvEqYB4vhEqQ90k;!K@qTu z?;amrd^`$3uaTxvWOVv4I+&lZJQA;dM1LhVg)O%iN>U{dkYmy>Lw|nv)rM%QnL7HB z=cZ}=2bl@G-_NUSUIRop#c^gN!+ZqeredAI3Uj3pAkW=968fj`nh|F&K z4CZ(ET79D2>F*EYI^V=E&kVkGj*Vtn@EI&t}H z8RVqXv)2wLXvNQ-KbxwW9x+*KJL&0ra}2isp(Vg+U##u7tjJjOYR1tXD|oMO`Gtj6 zaqkrzJLzh+xtD+ue#`rBuxl9#{8-5)E zjb5-NlD|~(@aIiqL})CJ=CvF4EsQ_g;YsL9k}Rri`KXNR|Nf#+v*LNwYeHx2wt3(f z!VG*}5xiU40=AzIqz4>?$m>o-Qv%n$eq7F*Smr-<`2KTCO$AVdZ7NTGaebv-Og};c z@M2-;TvA+;1uVA(z0MK0v#(5#|Jz%OFyLWYuG_tv2`2naZ z>3WcSY^4`)bl;l%*}+#-RgS&N*m-zgg!;0sr)F55IAZ^^&dbc4ipcgU1nH!5p%5 z`-@6i6OKRNcKkXRfd~oG;`&jgIr!5bnW#bUDgN5AG1s-|#WAyfd-O4)y`uRPZk4{{ zo-5B!L{*vgTj}`i6L!wqq)Rs8e#3zwC-}EFG?Hl}Z^qG^_1iEY+sos8K8*`UhYVjj z(W5wB$U}*86~oATucT4XirpNBpq*b^UeMb!fq3RS7e40qp4Y}(C^8@`$aN2eanuXP zI--?pSZslb2p~exTJQ|09~?riIKQMtmD#hQc9@_rGmxVl{CopcUzKO-XBG4DN#g;I zl41Zai;-4)yc#`_$n5S{fH2K5S(B3}7sJt+#)Ug*yn_H*0!8-U@;?f!B$b zeo073d{mFH`a5to>Ew4;+I_`uf`$t)+=v;Ig$>q7kb}ve3$g)iAnqEMLdXz0pY3E` z^$#h6^pWU%Ua^z8Q>;U~ChcC!#d>^jpyggu(jAgA-cljitcs~T{?N}oSGisYnyTiR znz%q=T*#6XLu(Md#IM5Z35&nm6X${^n$mB_;kjh z1KvTY(YW^g_o<|&{4C#U$j@aQVmJiXNr#Y-HV_dNHr?d;@GxJTT6?e^PDqHKQ+anB zAbxNa%5FL)yUw!A4OoM#6Pt(csG4`LeS*blkdd0Qn(k#VDjQ&8Z}JhXx4t^_{>p*O zNg%^3Qj2mTI0ug1&WLAQ@A|$6NFZiZvCH0FQ1pDMrwXbEHv6ZZxPBDxN6|^jI5{_Z z;O6(=fLa{r#0@NQ30H&r^WZ_JoApzWgjVI$4k$?C@0yoMzNanMh5 za@R(FR(4JmM!9F0b+i0mSZtW1NE+GOF*UwoBXn-S*mHBt<)?Nm2{NWbY`Q&&_K8s7 z!-uw2#7M|9Bb=0Oo@a=ShZ%?9)76_dPRzs^KR9f8?$Tfy;=-&H(FH;}EAga4g*W3) zHjwj9I2Q9_r6UuON`LiWIY1fMytXak3r@JWzqolV?&>U^mKHFW@EXwu7C!)tWr#8n zN6cf$>9dh42Xh)Hn4!C4R;29+!FwQvwc3;+kxMG)pBc0jN&7ooF zB{b>(lSt{D@v=J8k(lul?8Ff;f@i)qX?|TOe&v(|ip+yG+QMBTTAC`h`{0Vdk3tK` zu`>d`FMn_d8Wmi{rWdfD5iGALWG0zDCRY=>_Fl!%bQ`jRze#_W&ww*WT`XI7>aJg! zZ0v>TD=*wvF3t{juu+j@ZD^oe5>?d9KR>hC%}xHYT#FES+F=-K4r6Qt(80!i&`hHK zx*TUQVymTj$fcvs6tD&D!>=toQ{(Lkd zAy?tl1{diiYp4v7)rEkC!{22YmL?eBWM>hDqM&L*o9_bU=DRhDV_+_pZ*3rua@)3twYE`dmN;QzAv zEu3a>b%ih|+YCGR718m5glP3A)RiVDj~8Ogh;llcZdY|Fy*IA|DqB{(|x;5C{;&sX|I5&5O9sAFm1mJ>6dIS#vq8Zm@v=6#n z-;0p&QPYV3umxxlW$EP@LHY;6w84LuDKm(0o;XXlxAkmuw{6bX?g2U^JGydYxS4Z- ze^1N&4CLADanj@)W_Bg-aP=Msob+vO+CSR|zg_Fx1hh;N+sU6VyELH@xp7%N`O*2lcfvJ`$tYM&m~vh!9j>_}wNm86n{e8*nLE25Sa66` zc_M3??o-26b2Gt0nVUSQ2n$%&CVMM2CDZnL$SbqUsPj)zdDW)PN+*Um$A$gutvZV~ z+H%p#=9yU+)scp#EC9C<86FKgBi zaHROb(^UQaF(E#R2lzX)z-pQR|DH$!7!dRhyq+hK*1CqkYwLW`?p0&uT|f(Ekt>zW zlL8|Y7!v3UE(hr9z)0g4L6?BSDOkdm%{p*G0_4IJd4M`wz={)iKEnO)K7~MTf&LznKdv_&Jzpci(M1yCH5*YbLA$pTLRn(UC^ktRh+%9= zySN=-fjpAcdl07r(}1&6R9E5W1bCS(iyqMcDg<*$iy{fXcbng@iI2e~S6={L23ptP zw+L|}MB~K-gUy71pZfy)@Icn3$t@6c`*_#sihr!ch($yQmw&Z{AZy><0;|i8K}!sf zy5&I#?12y9bKHCBDw{az;B?Xu_O=j|sDY^|pt;}Z+FRX_hIq0g` zdkLrXV_P&$7}~1MhR$)j#8lKoUkA^fWHD71T;L4klv_DUK)!j+%QI@s0FLC1PP4@5 z2SSu?`ST~RH)GC(VRE6uC|WE|7^7r1pJA7C@i>P1%%PSFS|E=0T@ENcL(t$X7*T2x9)9c8LA*KWR zh6>;~xr>ZD7I02-FyLsyX8Tim+*r>9DAKFx;Il^rD~o6ayU(5Wpt~AJR?v4gdBm^9 znp5@WZ8NgwMQBg^Oxg)WuG01Q*3}lXBl(=aEMfgj8E)YS^&4|FYa1~E@*iIQc;V+0 z$22xm^`!s%AJ>5B%Hjx zPi9;fvMsOl2wE4~?PHA#AjM9cwP_#)&prot!;BI-lR8n;f{@r$k8w{k;5 z{K4WALX5{(=uHya{`W<^M^4Z5W-@*|iaMaj-Fh-o^9CgR`Yz6s zuC~4&0u$V-h_cMTX_KGkv3Q@4`{Cx+M(Cvsrv)YtsO!G44|k{c%MqP+gt8ItV^tEO zSBcr~lGC)0h@>3ZRB%T9t~TXAWNFqs!Yn z41bl2VyRFn%n^w1X~idyloSlQ2%u&3-P|>b0wlk==y~Q@65`9hv+u-+@=0hOyW9m- zLk@qIoxi$Oqy~#qz^#R;#^~IDp>%RlWr1DPd_V`oFCc}`)F8;7too*=Bpg2i!#0FC z#O5@KE1r1C3vEQ&<7eFoaodD*_KLqL<3^>WUCSvkHAEPj1_dQ+F*qg)Z2xzwXK4W8 z?bl0g+YQGvvAtx|FHIWa8k`6C`rE}$K~q`PZ|C*=)PTflzzb4bt$0v^I+L-VZ_2wz z8UlU(o4kQ-C9jv#_>(*|G?Whg%bkorF&UE+wns5*plN4FiyqBXo;XQGCA8)a9B&TN zz3|ZVFl}~p%CLjX8+!)3-x5tX5kbzf!B`w`y3t0tXhvj6u67q==1P*>BbBu>(J>7) zNueuyydme4$>5L+>gj2*?8uP8uu*oG_=&4|Nb$t!rMC@tfd$>Yt(5uP)KKp$G#n+F z!?@Fp^@-RZ5A2nTfCE1pI4){=a9oHDCVDtd0iWX~0r+mG0ISD8%Y1)QU>>$*Q{q0~ zt-SG6qOcgNqC9kcMJ|V(4#MRaWQ7MQ<9PTUb788*Vh|pHaqYQW#lwCZE5CS(lYC2L zh3@#on-4&yhaoYJcavxkD$KToKsJB@NPPDNY12UPbY^BIz!@S2>E>CLq%m+c6wxXKdM&o$#F+C%{!}TgdM&p z2==SnCosKIO>7!we#6D7!K{PEN!$F3ZW$nmL$I14$pcVFdr#+<-;n_QApw!WvJDEr z;4}>(eqEdv2#9ZR9kWhH^1v}9xWgxexLjB!9nK{zi|Aqf40U!clo0seJpolVizEaM zK+CpZkEmt}6OHeR%WA)cOO*S!%AY_9{=2@FCg9l^nDvtE{2tKAf_p?j5(yhRbEL-* zH=rWHZ+S1kP&h6D6rnn+d?b4~+*A*_;fCgEfK6AbkTIqo+ILs;1;|f`55JuX4)m~) zIlc7Qpi79`S{mi=4y4|8I4@>%5n-j*d6n7ra(A(Cy9kaJ^|FxldrDB(_|))qdK_l` zheW%W8`9TJ@*yktTHK!B4EwEyhyr6;r>W;82B3^dha zyJF*Bgd2C=wO(LMZWR-Xyqmr6ZZRc*f=bM2pP8QH8(sGZfxfR3IA zEDt8skEcaW!KF)+)>H5~$naD)dIj7N2DIo&L&U#PMre7@V~c3$sEjJp1HUR%I{ctz zACT)I~?} zm2oCSBCh%zMtH9Q$6jnvd81~{K4rU2GxM_Jw*sc?i?mGqPueM2mU>TPU zI!cfMj`s!g8!C9-7vccCX{!0I^28KhVz?GfMd&lZtjRraP0 zN`HgtAVb+DHML%Zl}AkK$+5g#;UhzSe`2Z+AAktrumBrg24r^wCM%zcJcp%G+h|1RYE zGn^5;#qieLX=hK32J;ngC&W;Vy!-rC1U|Q#27y~CIgFY&KebVY6a%M|U$}wCcY$bx zZBF3}0W;wzbSSKT6oPdij=u8brap}Op&|(&NI2g6lWU05S$c|9+1GIx<`o|SyV_j+ z2AzEc;yAl;^SqeFxVeF->oQ-ys2)lULc0Ozn!->$EJolF{EoxZDpdtQ|1 zS2BFNjvspckq`dOAUEN8# znRRi^O&8WHnTW$GU*y*5`^Ed4XOikHHi5sgKJPBJjpkS}Q|WU8wm)IZz%200od5e-zn6l~!k-b;b|Ziw&@nVnbAE8x|j^^6&X?w@*%U}e}kew>CF zJ%@m=4fO3JXRjJ5xpc5N66$RtVbQ)y4^6gi^LHmEZ6Nw29K-q~js*6yI>cJ&4Diu( zxpZiC^qlWwZ+;q{SaK70AWm(2zDTFz>%Z%FpVrLmXmW?0 zNQ{(7=yOz*d32JtrbRPrXIVc*YmjVc>V55&i5Xgpc;3yak9c^uc)P;AThvqhPy#e1 z+B8UEzGqK{EA^usWU;ukfrz&@&C(%9N|qXm^Kl^G!LeFf$HTJ8%p&1p*|u1f3lK}3(9Q8IQ+_c6CCU6Qa^8(Z`XL)4+2h5QGnr;|EfB{Yj<2-)eG1gJBuMu9r zdUOI4j9z_v3$ifj2s-Ph1q$c_@5HMRP$#UqXUsiV2Urc*Mqyr2_K8EIu#}1KFSkYR zO=;B-9E1RAUhqr<;!KOE0E5d}Szf1$U)JRp;J`m;d%M*v6A`PCo{;0poThpq-|pp^ z+KT{NP&rFV4s5DB3y|Lf^ZmjSMO$4)$2ACZ^$vu|ds=W6W(cYnaeL0m+OCX2%!0jb z_Pf+)RNZ$!56k6j=p^>FeH-;@B#l)qc}YY-^eE-BvPH)mXZhauzi^n6k)CupX4XYa z;y<4P(LV-M$Th9q1#H=nD3ou`nDmur*ZePpj(jqO85zTAqmvbj%=(^7p)k*1pjEP3 zgi+X6Bn_(=5(og$6A^7(;2mhpi=`QVDJduVDTxR5$Bza>4ahZpTzSn0^*zvRWZxSj zy`p1fEZaRf0gT;3iOA=Nb1b9!I{_}eBi)$^;GAnbq+BPpO-9C7GL-Hz@TnBvv zlwh}(|2&#U?oQ`A;fbD;6tE_mPaS}c)7DlFg^e)fT5Hes=+za$Ook(j-`FzLE3Ruv zmzfT1D4fjx*}oBmxTx-f<}A%<(X-N*FClu2DlPKP4}u`EFEkc}bfiAuV|Btb;Uomg z-GO!Lk3)n3Ktzbi2=;mnUEH%pSt_X2IpJW1QGaUhsrTxl1KfZ3PFJZD0`b7>ROVjs z$WyTaKMGWE3g2{a-Ov27rQsjAuK`hmzX3{43Y;PtOpO^g4Aa^IVoQ^5;hqdr#;M?5 zcHa@)uzT@h{TM=;eosUk_{Re~+JG7Klc|emZbo+XKL~NYoaw;MDX4>=UulKoggaj^ z?f;vYsCvnUyXF0beeZ{WZs+pJpoj+lTUHvN?})ZlP!Q;H8X`DR(J0K^F_CmW!c2_x zwmn$aa@9)cc1st@zO`RdWftTSl5b~4?o`!4#U1WjP|y7H`~kETl59rCtpAWKy@e|3 zo7#62Lpl2Q?*~0Ilov$~>_XIq^P-_+|8Q&pFW;?0)B9wg?FDy24wRs(pUuFz+yRdW z)wk&0h)XhUo_QVYn4=Jw77f{ymEjj%y-b$A?bbFuZUCUg{M?F-!WUKRsQx6sYs@_K zQaZty&)4o!K<%p$7VtEk4g@e>HPira>LQyvv=(xBPm0Tb4`d!7V$4ZMfjT8kSf(8j z4C*l_PL%zMn7I``o!Q@_9b;i;v?V}A#M0oy;vW7rCeaMz<>u$MP^1H%6RX_mGD{}f z8S$+w_fLdj44Pn8YOa_N6u#+CmrCSxZ4=9`A zuBPW`$F)Y@zgt}uEkO-VJ_|EY9Jxokjx1s8PXS5||D+OzNr>)Xh<*&^;mHtaIMW3F zR6}FQxm$==lx983@wc_Oxeii4k6{|LY;%nn%LnMO>bZjSOgu`r19{=*fW`P^J8u}zy|Pke&hipKObPJJW_55!KnKpzft1r((dY8@W_t-h<^4-3T~=c z(^-9NocBdBC-)%WhpKWcO;94VbKW#5y4GCG-Tl7HgL^bb6SC030IU$Sa%|-^b8Pu^ zbPO^be&l!BYjSnouXNx6K=0|lRX%juoKPj<#jqG(ee#84d~O8+aXSGFJ@@yXB~Q&W zrf>Ug`i8Zr8$2c=h z+euyjbk*ubiNS;6@q`P@NBV5L2E_yUER0Eb?h@dTT*?m zPK#bRdfEC6#WnW7dL+(w8AnFO7G3P4n9hJ*Q+)2a`|5>84;1Mjz)W=ip{;(?tMa(? z0#9MojXG@dQPJD`B2n~h@LV}`)Gaz>(e{756j42%YlxWVLFu|H9vfzX6v_J<5h?Xc znq$a|>5iuh&xYI{w&~M#kf_^d)fP-Xn(7E!z~aP0QBJLm2u`N@{<1|9Acl6C8MuMK zGyUKffX_WihL^b%d=&(;+%~^Hf{I37ndd`bGjS&X^^1tw+(Rh)rb9y(^smsjC|tLR z;tZchu<_&?v;p^EhO5MyTEuhnnA2EoF*5LOVVnxF9?+r+(iIAkZVr$u_t11;&rK|$i>EX!9{>Nz}ZInL{kLX zoT`ViO{oo1?-f+=d&^}pu3R%12x^whfK^2l_V29f?v0RVHnRUQEM?UpgrQ6N#=mb$=quyf1Rhe^xvlz$#$&))fRSfAjl_?I=wNxB>A7J92HqA zi3IB(iCx-wSS3ODj;#P4iCjK^(6uVL;!N79>wM^WkAll?8=n0?hResckCY~{UzSYh zs++&%wad%vFVyg%M}2T*+s{zYjDs5XlUR|38aT`zlr+wQ!dM<|tJ5nQ#$e+tI%5+% z6s}Vti9duQy6?AHs#U?hD3%RAZV;`wg?>zCo|S-C$Q!}p;_q1V*ix^5hI;W}G5FLJ z2?uq$D7!&W$k9ef=FV)2X83qn6Fa($ei+GnxQ2*&_JG+yL6`}B%Q_1GP3&pM)Ai?4 z!X#KCbZ$7c7xi1$$kQVo&t%v3>u>f^z9wZjJn1F!ztiOKi&h5tu4N*+ZkJN_@B({+Fz)%0VX7a z4`BtGKpiC}8CVU5*l;;h1s-gCGco z(F44Q&K`YwRw38Y=?;c>Q*sZ10%5?177E5X4|_tsSOqsZi|@ZFA}hx=VL$ zCJz&yV}TgNT!iQ!mp7Nd%W-Reo@$D6@h@d+>S5sK0L#WwrJw)<<>0G~RsY@XWEvEf z+gR`w6|{(p*g()mn)yF6){wI9Tu00qiWcplX&10-W~TSha(PEItEx3^yF2MkPLe2W zi|Ce^_no)wnG{=3V`vJKUhbV6qT}9rM`6UohnxmoglH6IjT>-_2I%pF1Tm9Jz$Ytw zAF<>di?HBJY~`#@ia^-^dXC0yGQ!w+JBotA)-Omn=kr}pvfF?f3rv)07?8ZbZ(%}! z!BXoYFD`K)R_6l@&lTAe1#NPF|=fPUB+F=9XlxN zpOO}@>{IRRP(jxu_QB6}%jaoFawSF6-n-Ii8WfzDMQ0M0O`WMpu*wWN@Byn|Za^SB z_}|JU@xONzl7R=*M9tOT`WDAlB9qYIK48rU2psdN0fxZ$wP>&m2_VTOBrug@c;AH1 zi_BHo?sqqG=;$WeEMJV?(CwA`5cp)&wmoUQDcw@vijiZ`MJ7ihIy=dNw_o*9UvWi8 z@C_fC`kKxsogIxLC9|QbRQpI9eQ0L|FSrf!O8qSL?uTIXX#I-9JN z*aV=Kwi&qT#kUwjm?A8V+B_SVc#{j|bhIfbM64l%C2uei#cWy1n27X2B4?$DwUCIG zpmsS|RkM`E#<_+IM@DR?n;3{>=ggcl!%V9dZg1T= zMvhla#X>3KMV}e?^UKwn@d5M%>DXKmh28!FUUV4XFNs^EK8KbH`S&Pse{{P2dy8!! z#vyizUS$8=YEA@IQ&ka@<_%9Ean0f#6Sj;)Xl+y;>MgonDVvM5yq#C3V+NUkGYAeQ z6XgJ-QaTRegdo7!C9P`LO_b7U8ZTA#zn&|$^Qh&R$g{wnq;YwGZ5}2LylN2d^K3kQ zp_Vx{Fl6({kN89z2JP+_^YVxz$&-FkRPRy@Yrz8?^dRIue2N>sSe7w~LVc1)Z>)xB zgNT19gI_Ndp5uY8yJ^S2_vq%}zJVWtWYR)QY7I;fWDEfu>1DhM56>X~HSN`P^Y z18UT^anm1R_Le1{lOMl4f9yjVP4mJ~BYtO|tfhrdSoFI`YdNPjJ>L7Z-vh%}qIRtE z&cpCu@l<8^BPGf&vh1%`c1k6$)W^h5d@R46`!Mra+DYycx<``!`_A`;a_FHH1#}XoZ&4x#Ae;28SMDnfL9jYcWj0GZoqu3>^DYoU}quDBkS46 zRgQsS_@Ha<@}e8UZxhD0=+Yl5{AErUsW5JwnX2>bk7N4YZ;c`OBL3d|45Gk;heN-l zLNREaN38o@*or7rr~O}+@^m5$q}YW&VI%%y#*htTKkK#ZJC}b=vq73nWBq6u>&D@0 zpq#Z3YZAg$iE#46AhroP9ZL+FNSQICY4KLjY9WKzgcJIG&j-6O$noH7MiA_nS_+Jz zh@D}1Ktvp7gn;$QLk}|iu<|ur{X=(NQg*{<6!AH^=3_8Y!iqyD~BQEmi z05RwyL@367AtnKzDu^~JvFa&4D*c-rHu~@Peya6Q0)hHDY9;TF>gWJALXi%EmV!Uw zfRxJPko9|BF0dOHj0VXX?0}tvBc!EjI5+Tb+d*W_S`J-06fxutL7oqz?Cm@eeJH7$ zPwdH*E>IQjHD{yG~6u75{_O2~Xvgo&+_(pz)3MgIY0w&xIkQKr)7 z7|+GS9OJ>>uTJgs=Gi0UhAJ67n1eI_<_;jeN2xY1T|%X`{KFIPricJU&8_?{0oZBos>i(Dc^_L^Dg=>oiqtA}^r zLpS{)-PXKi!W%r;Q6;3uAJEAQy@Itsf%*#CeA0Q1FF6<=9P7zTy-)PqeFW}JtBA#E zC8q?Dm6Sdf3&l8D4s*bd|1f9?DR8D^(V6?6yYOVe+B$IX+xig$?X6~-Z($Z{ES&!wA8)nA37ZEO@aaL`rwi6 z^rk93aqln&u`P>`ltlwpuayJ53}1@4&*m}xq!a>QHGn2NGoT&ekK%l;0vPquaY)9V zlT6^za7XwnAO(M_GV`msZ;Cb3;{7|tAnu8+r0RjArHn-_Ah9ZyE)Gl=h5hnzAzHzx z|L1avLnfDE&~3gqkc4dHj!n$7b~LP@JuZ7uedOp%dYu$>fx=PLo4@xrN2tOoq~tl0 z!819&F9n6F$(f{)>8@G+ccV&PBLG42OYSvE zmZ01czJ*R*3@5 z-W3-~6^ERXRqqE=Tw%Y?z$ne1QWq3V5I%<=QuBQ}e_#6V$cJh4+75hr95!{fj0QAr zT%02(RrA-~8Wx|{x`I0aZ`t2m9ey4&`@XZVyv$J&QoAnY_f}RIi=PZ$PHmEA+Q;>^ z?i?YUDQ`S(9~=yIk$rajt^Z}#U!v$?Ptzc4@cO3mj=He;p9XGmW!XJSmR9b8A|G_EY3%9s(0j-+m;V|=4?o(w=v^LEPNnDR0(7nAp$KdNTC~< zyl~&0Ae&EV^J8Dv{C4t=8agS-pkB$pCd$y#Kqlq}x~ko9WjpY|bR}95KQqS#VW9Bs zv_Dc)+|Xn+|n(Q?KyG{q+yjt-VAKS{N6Z^zcYk(f#gad1U1_6Q>5zX^U}) z4TtDFdSp5%sLZI?8TWhSX-tu(HhZ;4%>}7poYHG5(h3sg*ONl)GqxPkj(y*vx+-fg zW`i2qE<^;Oe4Le2Hk3SrNul@~9gf5iE8p~RsKl>wA7dLf=)zfgPMEwx9CUnG5z(g? zXMaKbPW2&9DQR62_6oV0HPfN1YQf`#XJ)*}SET6lx5+=Pi+s*;<_ON!6?dHWspI@fi8;+Nw9Z*$RS4?{~>un{Hb9UFem;ab9YS0Z6w>1tXQ zn|GCmQ|}(ZMGA}?P&7ZcBF&>UZr^w&t*)06Sen-td1y(&1fCppx`ml%jxONg;$2Cp zRN?4y7ojrk8-2Qn#(zW|`<;YvO46TUl-ij&^tPN>M=-JIz=`O<@aed`=YTN*5Z=?O z`p=sHC;`b{IFf1|1HrlrMZ&@UbA`|IB5xE;k$lX8tf|YA7OMLgdE!tg!iLsY7adQ< zWt9`*LfWjG5aj9Zb2t6Io*XLaH$?a5mW~~Qe!tIx>;4~Sm4U{_y8<>q%kpzsaF-B# z&L|0p)}jwQ?g>%G06Q8>`J25iKCP&ikLo>SBA59fg(kU+P({8s$2 zO}0pmB_b@xEeDx}AKZ;G{1Xl)w&|3{`~1bT?TX7`|D;&=xa-?WjGP3&1QN4F+~2Uh zeHP|wq-3kfU+*9))0_Q6my4CpY$G(umQ!HL_?`P?K@;Dg-^%e7mmA$4zUDXCz;t6j zV!K_O{XiKHhAIksWxqoMe?^))V3z%pZ7oNA?RV`;+T!tGV5eng=9HA#tU!^)-VENu z^fY43)lmT=4dx!-XY>IiiCaC7%ubtc>G$`pA6QEL%u*VLasAX4jB2b+<|jI*x_tJV zh?>Hyl+wic5!VxsIOH47=<+&Nrq;gS{KduTxGZ(pAE}fQ!gPB5vE5@oeCL>_3EY&4 z?tOc6=FL3YYJC;&Jd^XrIAKk=bFexms^x9)t!`1gLs(PfhZ z@1Su@7(>{B&6HIJO@LL7STeJ6(Qj$zEg!FX5C;z*HCrXcv@MV%Mjl&NG*tn3cFDbYXx5f0Jvw1V4L5ihl z_Xh+7cf}1!~!#erYl+=8^ku4g%+k>>eAg75}&PmW>@n?7jnVdQTK! z!_=3t`26^Hnct*Gm~5CQPHTf7_?a4;ed8FUe@J4TR7^aX1z?g@`?D(7kpT zzxLH1TQ1m2oZm*ibjNv^-_qKhkCd9s&$XlVxAwS$O0T3Damk47^lX305El1Qcel1+ z|D4waUK5QkQiq}yBVro+XENoBdO6Fu`JccPzHJVzN$9sguH?L;?(&v7Lg?oe6|dJ6 zj79zm`J|a6-T5XL=_MJ)1gR4-qT}6S%es$SNtB~b+QMiD1r?X$EA>leLYV!_NfKFQ zRdRc+;r%360_?FT3}xUfR;48aUz`c3zkna}t3izE6hYOg5_l`RC5LO<@x@1UG|tUt zl_FE3ShSoSQuaf~`C^qgWon3+-z7i;1+@$XLRVI+a1Rv88k@na5x&cxKv?_&elsyXf~HY8Xe=O#~PWcv{1 zA>4gl;SzjG3NZ!*0z~hzjhNokB0?n zN=Fgl_S9jVj&Q;h@r~>9wPrlR;TSbuYfln6F!8FXS%|12J@t}xS-;7bv+ju-F2Ch^ zCek%Uj<2iW5~TLo zAIU{5QjuKq+{a1rW9up+*kWDc=}wnYGQAUs7wwaR%VU7JpR-guS}`poLwK*$r#g#P zb>tF*ZX0KitCvvUqB@1CQ*HziRli7mk>cCvfe?d- zP*#!+uYwv_Egf8TGTh};Td2tp4GnD{q)mr-QC%w4Nie%WX1|keWjC)3L;-spEEm5@Q01c{U<6 zwXTJD3c?!0(&4xv>BH>$&xDuMWP5(G8i z1%28 z7sE_9hxzf_Q1KN_d%aK9tH<|NZlj@32Zv zsEzP^hi1wjMIdewYxyv3^4Xr{!qZw)y4^SO*OEz-N2Zn{mqpK9Lk&MGh`7t%c+#7= zw-j|<<5Zry6EQws55ye!^x&O`>`#NAhU*@;)Ra+W4N&!BS36Z`UswDoylINaRXhQo z@CPrJh)R-hj7_@bhpfQ?X_np9u<7{i@_pnEnZ)m0Q<;dw6Cq)853-J@$D(uYuV*~n zY)vSFq`rE{@d?)3gz&7Mo?vvhNvP(i=EXtadk5qjoOhWFhD`$|Xh;$vbi zPO>({WkEKj@RAqRd`J`3prDb!l28dy0Uc)z&HR>JtC{yWFp$3b0$9nzbztD^dwnL` z@KI6Vg*@N2+|)Ahj_8lmL#54rODRsuuHBQihP6p!*% zsC>{<6BR2V1g)-s2NdE!=waZkEQ$gk)=)JtkdWCoJ3H$*^JIa;01zn&P1_e^0~h9c zs_;LmtA78C7bA3@iu~g(I)SsYt5>o*$G%eD;^hdxzO(uJHTCp(gHk!rq4xL3;Ew^v zzDh!7nY`6gEeruJQ%uILYy5iklm4Gj?Z(781_v4HH&#r2Y5v8*-->&xUo*4$8J&-P z_bq;u=ByKt8#XWT{`l9UDZ%W_D%y*0EiScrHoq_FGZ{ROFCD3UtZNDbo&PZetW(i0 zcz<{BQk_j0vnVxY~p{6@;BLGH)9Bcr@M z%qfQ_YaKswkI>N_Wg)*OJY);Kx5Z~9J-Hze001@-U*W<7&6&WL(jUvjT{+8S-L~aD zFr8JqRnnbF)4z9t)F4QhsXT^{(682Ds=An%fwQh!Yb&+4jX7k9A&MXR-dASzP;qH> z?W9nSS2&^f!~5sky~z-o4cJsRZaDfDfm38-jt}R=Z!2zfSQ6m}@8EQl@gKF70xrrI z_2D~KCT9Awyc5lWzI~0q&wZt37oP5Yo-@mw6@<#0DG_bzA$?GJEAO#O-9B*kG~cd^ z4`&p{;NSf8!8Uolgqbg~-_o2J=k4e9oPt#hb95XQD7;_a2d_ra?zZ=#vvd;HTmai6zVDwU zC?x|2vrt4ZyNavHNB!&}Y&LtcOZimxe-4iJch*<#!ie0b!G{bGPT_~*t|{D- zP$3+-%Kr17smlL+_#D$d!r13Va)LjQe)702X7%|;=N>i|T4>5^BL;C+{4#uiIxw}UN+2D7W)98M(lwJ|n^bn>=#V%=zSw)QpsZSCR~oX_0;{pqQoRI`5d z?S^*#V6g0E+TouYuSA*J9qdD~?*VE5@B>eYF)^>`M@{tdq-jCXIfhRu`Suv&)!FCp zN041o0$}8QNd?T6->mKVLea}kI?i`CqiIT?T&_muiCUq+I8GP=hYm}nG?*c7ozLky z=uVaJT{8FoNGR7lZ^F6;2y7K{D8R;s$YaSyfKz&5LnG|k9Tw6dkAHu+K4%Hq-*;kw zKsxXMs8+x%xgp?l31Fn!Lj?h(9g5q;+MQYuNT1H?YX>FI(y3_T51j;Ylk0}JV$Ln{ zH92;|0Q(fG)cxZuaKxRfFvE;k5Q|P+^F3ckF5qwP zGVG`J=(d~{cv^9#ao7IZOYhwoj!;rK!xY_7&@^p{_!cqE@f!vOJ5;LhXQIw)`E?J^ z>rNIs%R2_mp_a!_Q@QanI|(6{4Ev5rzwsUSt{pL7%J+Y{O^L&w*Ynb;+6~Nd@>0y` zH{0(j#8B~DUrHOXC)OXs6a6P`-8Xss*hwaCi978@q@d{8&+prC47ZY-mH#?I1_Mck z7PC$*d2@sB8I2|H*8Rs2TN__78f(iWv`e*&7WoT5c&5)%%_m|_Z~XI?WU=gzBl#w# z>wS5t;mRY5S6e(x`6P4=+fm>Ell0f>Jz<&%aVur;xC-yozE-xUAsxVs3O+W?eglZ$ z*hJfr?Q40bBAe)Mi$FyxKAEE@rP6$>d+XW7Yan3#6ryO{AeM(WV6}JSWRWD5dx_D+ z;Pp_a(09_>u62`Ju)by7>_CF2db}8HOSwJ~Srb-RzlkU9#Q4ZqnEl{0hQMU#c`@9v z@0_HO$g(h<28X%c91oBQz>tq2vQOWPQGs9@Bqk;p9H_?`{%yiV4LXanw&t^^Z`;Ue zte%n-+-;@^tJH)aeO!|M#@&FC^cN$4w9Umvo&%UIun>ad2yi*!Kb*U`K)Zmp+Ia8TE}XAFos zpE3bX@2AX8P$0YTu%0P(EDhzuKf;P9pLv4}`U^W-g`pG5+4RH}bZgqwJs!iKzAEd- z#2qy}=6w>PeQ@bAd#rAHK@;`8doUM^@)gIT4c~}mCAAM+?Bo0V z$3im>)oL|(BUV}$RG|l=&ZJ3xm4{sE;$)v;5NkW^&sb9O`#)AgwGf;Oe8@uTyTXBV z2S!S3M%jk=U)R@zs3F3(uCA-Cv_*=DKYgJxad&ZCcaaWXyHT8>xgo-3ixlAR20e@D zy~EZKWR~w^>N%x^AGr&9C^B$uBlksg02lk^^Y<+HANG*w_llE2@gE$fH#&|F(txUz z$iI|v+A$$A1cYdv1d;WP8DZ0k`K5cLGiifjfe%ZXciXT7?;brCGx27uQ~MM5hK4@? zR`|qF8-OejJXOPDWROQhzpxVLIa==yDf$CP^8acjDvalMt#Txt5dKHI3r_+dRqS)~ z{rZ|{ea;Ik#NpxLji#o`$~DEfImz5BInmyDsnb*x|UrO$12o2oRj5zUg2!8_KTYSKixOG8q@x?{? zvbiXZ(5f-AxVl6gYWX^Ubg3}YmbhuPM;JlAo0Na32d$lRM{`qpCHmfpY~Yv>2CC4R zVDzX|0CIvb5vK0AV59AQi4B>$$U{=ht>#SB(se@HIfacI23pJ?Oyb8zTqL_{VKK%N zS&r`QY_8&LsGuPVf9VXQ*1{AZ=!YP{pAaDA5Qg`z#37gn7z5E1MItvl}a3*Yu|Ri^%0EK2BISxCa^Q$QO1b- z^*6RooeBgWah(6!LBC!s*Uytl;#2~PU>aBoaXcCwcjrEQ+h# zGx8#2lRQygY7jEhLCT3*Uuq_va#6%1BcV;C4jQ9f=& zQW0$|*ltGEFvJ`}5W!ycj?$jGV{=hA%XSBeUu#cBG=+OdZmJS777v#9m!rODunqlr9;OCv72+LH3Io{q1Kl^&`%z^%Uw?RXQ50X9egiZS#Z zGH(u-3;=V3TgX7Y9Lh;IyRl%}~6 zPSa|iA$GDGuq}V{isZYEh|W>K^?-t1(slhM*J{{7q3N4oonH;FatUx4cg9Fr^c(yB zvs*gCb9f=9eL*KYD&KSSTl)Shm9ExEgd4fZLKvL{{GQXDs`y}Kn1(=c|EVz#6=5`X zzF$-NGM%*Rjs%su6&Yz+gJtUe#~%XoiyeyJ%+D1lM$hjb9le_z>yZg3`xQ8|vw7`) z^KqN-YjD>;?Bz`Jq0o9nm%Bl`aB6G*cmwg(Pd~gD`nV=Ah*dvzUZB2nv&N!yFka)B zu|jf)euxw$9Hb?L^Myq#$srC|J}!0VTmM4M<#g3NyA~I`Z~Nh}pgx3SM5~Kgh|pt$ zM%S0qU&VcfTh8H2dQxchk#|l^9+D2Zut9+OSF^y6=wZb z=zv({tz*z3u4+tMUp3nC79fcQ0oR5e9svKl$tNBZ#5i*xMBLl9(nD1Ef4SOJM1UgL z20)bJAz86dasWvI8Xe+3*tHS@X&@`IBa>Wd-SO^+=!9kjZ!-jzWRp2=_fP!dD#5zWy z+FqM3+KmHP4&q*YUBx6q>BkOb{vjEx3i-ZdzkSNSvybJ%Dh)$dkQTlCcmcJ2S8iL% z7o3$+9Y@`a!01$b zlZ~4#S+~xWTbRc+EC>mW@Z`aA$gVPNWGSO1s@uJ!#Jw}2fl>c$|9CkUB6IL&KyG{^ zB>j@WuV6E2|A(-dN0RG4$JDd7=5+dz4ITHN7q&L(6WHGCuPcr`pHE`tmu!>|#IXrY zq%Ny%aQ%mFKr=I7P=A-Fj>%O@ldmSz%n><{+w&M*L|;!BMU6kZ>tM2Y@Gm_)D0JZQ zos^2nv%g2UFATBXh3mfkIo>O|tBDM1|X$s%e z*-W>8eCv-VF#o~Ew^8Vz*y#wGjdS^Bx~v&u^EG!%G-#LQSz|`v0|eSN->##zT&kJ_liyEjg!XIb(Z^uI@f_io zy&fFpT7xBk7;08b>udn|bvUt!P&*rL0wF~DX1bn1)jIzDZ%!nR}*=6)Iz}0)4AqNm{nWW%0M=Idr zm+AKBcRzN6Z(SSguLa0~nKRlEB~--`nun8T3i&Ex0pE^3FnGTFM&tZMm1xiA9RaSBJ4R}R)~JC_yM4vL`0#L@+yt8{+Z;6JRsZunB`;2JqSpDN}@*T}DxnlHcpJ@I7?vpw~^ z_?ynD`7DB|r$}J`nJ%*rLhyVzaFX%~xytiU)j$7iv2Uqzk@g3ii>y7}QYsX#{N*S< zq1<`v$!A$n66#~g&z%9Zk;3Kdl9-SLW*MT(sLIpz`35>)ctzQJp?lfL+2Q#{^?CM1 zFv;21f5S~j^U5Fh2Mj37^Q(K8fdki0yQNq0-p|K=j0sm^V6xMA1Pyj$_fZTWS2#LDdJv^9*-kuA}{%5 zzxRv|7?5i<{lp;{^!y>YyP(qjY?%Ea6`7Jmith8|-2+FmB#*gneYG+O;$C8uZmR&F z@G;s(s*3GuNU9t`O6ZGvQy^Q-qwAfz`^y>*oggOSJNvW~dU1(O#7K5-*})4S)F8=6 zadTE>qMB|dabiP_Mi>WfNhHh6q~}*{Q)MvvnW;OmyY>a`F57d#d{lV1?|iQN zf%f)>H+=@CcDD6Hche!Fv69b`rbM;q{^O-=z+`QAk4+cWj+FhWl=n?-oELSnDjnlt zV?a3^Ws_jO-h|EOh21ax=!V5uNK z^#}Ghzosp)z0yCD+2P0t#M5sdkz)Jf3BUZTmFFgyF}6`paopyA<>@3v^`AZEqiIjm zb&K*;=75jZ1?PScGm3gNi=#?|)C-q&bCZN1DybfezElQ?XprLCi&BUBT&*wJr=J>w zoz|xD&~WWSkGB{HxI&mQWykM2 zG|jLE64vYZS%*>zm~Nf`P(unb3Q>_W5LJLcdwjKaF}vc)c0K=jj=G2y zZL9C8Emq<9nH5;ZFF<5XviU$^1Ip&7fyP8{6Gp$v)Ww4YqiFqyK05Xq3b-YzwwXN) zU)epW{z{VQCa%erh`gwHarVHSnEO$bXPk+)=qHz9qw)1$C0DN;lKo0ddQ(fSBz>Ho zyi+b4rj!Wzmdk{D4MGK^8s|Ax4JTd{q`{*}dCLUN0E{^~#5D`9`{Rg)0Qs(H!NW9j zR~MPAvcZms&h*^Q`&X3_IeeXys{Q9Fq9)NV?$B8TCUYAP@pR0uwy^k#0QF6cA-b3= z{9?c2aI?xK1SD8)o07#J$4yR2()7p(?8n=1pUx`~cjh?!pt#Z2QdT8hL0XVpY%b19IhiLUII2A)tDqvAj*afsFKHM^+F%%j9Let? zPQSm<*TUNg+%NoXZW1bnO$1yJX@Bg*KE92t2fwa&NdUqBs*Dnt;~-ZleIN)-#1MFu z{hS;eQ-Rp`*oE^iIJMq%8QSej2nfPlA)QT77$4C<`#ZaCCz^?`7D=d?Bd)E?Czgq; z|6LP%1^xLV7bky>X9>PszQ-ENuc=nyA{}~Zt-_ktMv5$V)JdVL2260yYBZ{o9YcG5 zvH~P4E?CC<13+w-F92`=1IYD!Nx#32qx*;Ba~AL-f4`Y~U-;}DxsWrVE`W)Jz;nk*gVk|54~ zvRwEn78V3|P!ySv-et#gL90=m_@6irK|$FB-cimP+g94k)kg_0ibRitpVrjA2~?@E zvPw6zzw9q|x-8+QxPdwa6W_)@@6o+=7dIy58xymX{~7uG+)bcnqMK+VgX*dWy${r$ zZ&Um<+2h|Lm1p3PL|N*`GZnkf{3?`iOJVRejc*+T5b1k5VuhFe?t#cWmgK^R3MCHL z&SzrC_Ml7xo0SpZ6He`-rIvKZy5@k#!BUVxW$m|TJUdN{{y@*jpeTYoQtioqH&FT; zii}f#^g4p!uPKYE8j69yZ~Z57ugSR;kPB+~i8JoPn&SbtyB!8=JBKGe1wv(%){dit zF_G82{tzfg^1wrS09kcF9-jfQ-v_wGDS^`$I0yh-Q=X)fAd24s1R|w&kq#<&@fdd+dC;c))oR8606!&jQSlmbY$%I6TiM(1xKjjClD}zb2 z8c^`68f1B+TX7*YG!!lKXNG6$1KE0%`JK!zYBlxSTAa2RKpc?QuXG(p@&;a@L&UMr zd~QXyR)dU%IG9z#0v{ZfNiBh@aI)#Jm?AyR)Bl!FY-F9)8;F&s-=8`665fpoTz*@E z+=>0-7xXtcTc2BateN#p6G}f~MP?+M8?$f!;$lpkSM(#}lV`OUp_997&8(ZzBckh) z(FqW;#!dR`YHGHQKh+ixsM%CiJEm`_p4W(0)Z37Y8>j#+_JtTDvP)qS;!+{BfpyR9 z&nB1SDcN5%biQA_@9TBZ*ckEg~bVUlLWP3vB=9WYrx@XnD&>Nh{6_mQXUH0NxJqQQ2!3B>F zKmSlnt&iTMJ;JLGPX6`_p*$}E$scC6JL28#WPX)Eh^rADj(Yv@HZ?JV{QaHG_ori3 z8v6R)UoKmJ-(M3#CnCP#0NHmVPRE)P@SvwB)Q%8JGD!cra|=G;fjHQJ!q1G`uHaOP z7#SHOl%p=XV!!#H1wU+^|L=~pHD`?qVK>Lm&5HnClC^ikpT24G54PC&_f zRm?!)b0n=Wd7YNq&CrCQ>IUsY#1VGUffCmJvQGq@YA=BA?)e+I%v=Bbb9SBujpvF! z>h8<0Uts}6UNbq7oD7$H^|*nYLG0X}M~$|m7PCFhcbRn8SuLp$#DB zgh>~VGgbF_4Vh~PX^GsyN&#S71aqVYsj>DblT0bbJJT}8x)v&(K{e^ zO!3S6(c=+Yc}qFOkDe|fy!#*i-pS7)!=$|QxyvsKggf1qf78Dik=i~Db%BD7SjVz1 z4rr1Cko9j9gjss%z|a!{OOqD$!JtfDh!xW;2>0(;i`zyo}~vl58P4j{f<68 z$P~HX(bhzL!qTM0YD#1dAqYX?kOz8A6Y@V5=d~H?$Pw@XMmB&pnn!~@ zgrTzy*hqiT;d6)9AnRO$*KU4w%>GaGofEA7;a`RWz%I%X)Mo#;i%Jn{Ok?)uyX0Oi zb7IqH`m_A}xIXEb#;0dl$4O&fmc3>~-ze=hePs@F=fCr`{VRC;r}jWG^OTI_DmE1t z!?o;|#TqmFQ9WvP*@dF%t$O`EKLb%EjsM1*VQ?0gapYj152IKwTJY4%FVFMryROx4 zz^wmHVN?B}w_<4n1I4F9|NfN3rdzYH(mzYmCqk`=F5vs`1*a4>vQ&?E^>3vf7l?S1 z!!zGibvT|3XyWOgOD)L$S0#E7cML<}W@)2)7Qwzhc*4wAH960%_Ux(!>f(uYw0ZN5k62@WB0Z-zr1U%l;-FKc7D zDz~iZ!|e_#=`}mZSvL5UeCPUvSENo|3eXD-k&}kn=T*=5Fs{>J0CW);XEeTUkp2J# zghLO}!W*Mv!v_M!H&6!)T4v~O=pTiOpf&tJ6~F2Wy2y&zpP^fSpy_WzbNIGqqP`Fs zm4-ZU=3TZ(A^{e@*$~#6hm2prp5xj90 zBzXeFI0T&0LeU3+QDY8zEZ7_$j`3O(UuCp%XRIsh0R()^n#`e^(yTomA5fn*nh zU|L?w7Ii988L#R}kB-uG2+~y?l@$T6t|E7EawWS4M^LJDlz|URAwO z8o!bB@vPgM#pq$G2vqbTZx4z~bkxX@W^dL&J71BkAc+%NNKHW?e~IC*7~4I+^vYpT{Y2{8&mZ6uM!{XA;}_6TzxNp~A}sFn z9H|`UF!h}~Q*)7X#U}U6I$Re-^Q{0}sEZsk_NkhWez@67Bv}qTiVK?~L;=bOkBax4 z&tCrh_~}f`SEgmLIi=8mkmeOHu0>9y3{t@mcO#tfHFpnuc&iIh!-hycr#}{)&TUb8 zR96UzNh?qb3t9IKV4tL zp`tb(-e_73!WCPBjV4m2g>#g^h)bKoP@d_p<+85N0@@81bT1+6y46CfG>sU!oof~f zmUE?QwZDUOU;)bo^9On8BQ~FdmUmAmS^xY5dRbzU9sLJ+7aOABcpG(HQ4z9Rv8cWr zc=hA$sVkSGwK^VHq)hFi#{#?md>_m5z*J2jTIraG5@X*bRQG1iE7B#40DyEg>a44) zJIT+K*DgE6N<`aP7!_hzKJD5Ewe+!7@he$z_E6j{1d{m!OrhTWgG)+eDuE(Xj#Y4N zwFQTa1_q@feC^h(Q<&|KOzX)b6&QBf0Icd>V#2w8Fh#mq^jfq)hy=9*n38xQjCc zA{`VBFs*rBTLzLvjLG*%^UsT{wYWU~TLJ{HI316A0T=xb9Six?>? z2<90bx7)#!x>r6qk0ClH)Nbp#pyPrrwm5Jo?`2mBbX>P`KYUw@uSKVt!kC)Dq?)QT z{k)B?lh=V9QN!}BM;_rrDD*2MyYlw~Vo*DF>$qGqc4Kk(!EZ-HMnvfHkk9Tw-OLoS zX6=ZkV-Xz_p%C;ClqKXh*AQC_kVH7#soU61B%it#kNc4D@I z#^_kE7cl1L26u!2E!*yA8p8@%3d~bmv}}Jw^;LXXjyc1Oj%WSam`d)NZI^i+#CBQ} zLXN2%ahFK_{5)ik{NLqtkap3>VJ|hyw>Jy&32B`AHJG-|KT*=zXF{l74|Me3(${wu zie%H9%s}CbD&^4Xh!{(SWkp+ydG*=luI3=fdiVyXgn$h#rB0NNSowd>4Qy9CsGR@4 zr2o}SILd0FNqzShhmt|6#hn)J5Ze3>u~5<9YWb3}o)Sy(ZvZT`J;b=kM*0txiAurN z?$J-(67l+)Iq_wit|Tff0h^#+__3_;2fCC`K@bC5-m9VIBXVNFhqs(F_HLyosE3O( ztp=(W1T>PTQfu(RUk(Zd@A^dVr{x+ylomb$(BrJaf&3HEH0dQkiitRcP94HP^ZS)R zJ}_3~0W4YF00^{$@=tqBSOLVS10^DYABL0ObVS+ttYTV^qxe6gN8Fw;M)O|**NIAp zm&Nnm*+Gy=p$+zZm&Y$=xGj?mCLi$|Dx%hRAb=r-a-*omRZ)|4=-a}=vtD$;PU8~Q zZFj59;Z9+qaA1sY!+Du?9!}h&qiS1#jn}qa{YsTC&0CJA_(Q6rh^21J_=@b)S0)fy z7aNliGW8V$6~a`m+(rtVmCUFnmcl&vl7YN2jLE1}XNq&C=5Y#m;cnG__~=^deDdsQ ze|<*CQobP~a-J*3n`be?1f@)KJE?f=E`GG`%0n;pUJ+kpxK(~{c8K1O0^LY(4Gxs$%QHYlEm7OnGY_Z(+?xh z>qPGn_&<1j$>gDs_Y4Cj(Ue?3LCy{oeoXtum0tlS-?`8(#B4&M6Q1E0c|T{0EOl=4 zOr?6KMl%u%1=>GF%-gf*=Z9!2W^p8p@ zt}t_t@|px$D-5kY5_Jtt7grUIHLkx3)zWEaZi1IkrSble5!k@hV z8xr}T`Kt+$hhjAZ31k&Q0TAMMg&9jR1PeN&kq~yY{fsyFnFjd!@OXHi)pK2U+pYA7 z(-qQ!q2ZV*^^B)Gqk4q;>+mG~ysaT$ISg@{wKGNh^`4>KI#Ns)ADx32sQLlioEl|U zDmjlYUG%gzLjzNOj+k--%SbTO1hoN-|3QXNWG~h!z!?Of8|*b&w}n?LJJOeJlL-k4 zUDj5jS_jVjH``UEfcknQ;@G@rlb(^@3?uYy>^l^h4(Ly`k(>i!Y5t=|O2sI2%_c z<5ss$uKp+g;Jrl|nUUw<-;^AAmDxd5l|`i*N_9S> zQmD4)?G2z)*G+%6r&a8&N@%9<|EOrj5;##{2=tRMUi-_|5+Nk^gNi)Om-^48b5`&y z^173#t9WtGDd0rYa@9JhyyF7>mI7@yQW^hgvo>Eb;XFZ;*p}>c4BOr_WBjtWs}=?I z<{ypeUGUT4P!Wjnckktx?w1pQ|*=t1r946w8u*zYBsRu_ERWqrMX z`0)4x_)yC;Kl#pb(9Y|yuQ$-MllPpK`V zeVK6ND(pck993$3D#Hi(*3iHi2m&)I*r`td=Z_;_Y8IP23TIBTxZIj-mDJ9 zQkbHPc;hh^+A$IBd1*Fo#r`4A#)O)*By$+>!8Wu2aHyG;_x@G=#jjDFoQG>F0k>+V zOA7{y3S}#^@6Qt6xnFyV*)z$$z>R_KT~ZD@_vSJL@(NC|H1V0rCNIyS@hK9RZc}kiG)a?POz_3KhVmpPa`A>;$VPib z*Y@l9*?+Y7)Fqb~b~i4%`<*jQccV?+I#r{Rm{9x&K$2uu{bC)nNwNIM@Ax@sDx?g` zPY(XRYNpoIHf%7b1jGG9cml*9;Id?^{1)O=Xd@QcC%_4Ssu)Kcq{4PB0D4gHhs#_L zAE^GTB43@8=IA0kA?*ts;$#dJ5;8mH#r*S`4EiOe;;Zy4_AR01^0^y%%W=Ov;A<{C z{#a;EFuFQ$=&J`NGPn!Ns6TL$ZWQf^N7&!+{!b)cwV$6~0Su3^^kLy7)UxPW5ytqe zr#lE=0ATyQgvkB|8xYX4_GUc@m1P13^@KZb>O{uZB+_9Za|(jc)m-XtZWrc%_1Z*@ zzYAk2MA(SbFel-Dd0llHzW#`XQY%%v;rZUB!Q`BM@;YA2kEC(#6zYdM8u8cPUuq8+ zKfW_Pb<7kE91y+YXcrMcDZY&~9v=}Qt@IRV{X;QVbrHPdL4vhPSs3E>Yzx3PDK(T0 zw#(~}o06Bh809Tfa3W^H4nhjq1Kz!6)qAY3KVNcWubnemu9Euhc!X~Fs}Fa;{Ooc{ zxl#@rl9)*o0%e^dR(`kUZfox6{X@#Uvq9U>EnB=jP7BfC=P{Nv7Jrw{gh(S5+a@ef#dq@# zqT{;6f)5Tlt*NZCMG0e=w58C_BdCGFv@W8;b=;|RNYX!mSjXjqKwdKeRd#BC4~&V? zmTKs=!-II{j(>B{EIRYt)*^kI!7ZH$ps%Mn4*X=dCkWsJHU^Z{eakx;MUiG2^pv2M z$8D$tW?k2~AxpN^HQa*lIJik05l*V~!&~iJx}35+-n|lYn?G!xJI1S)Maxl8jH7AB zD4RtoXDq8PPfal?4(F#83TGx{-EBr}Jq(x=?C8R@`Al=fnz0d#swhxTw*>*oyoa>x(Ei#k*%2XfELqc2-0j=M7b>;*F?4Us0;vowIb_ji= z9AyykRFM}u={@v6qI24Dyhy_Ryz!CIx4jxhnCy<2gRxrIFJ;mnswAphN(KM=d{6|Q zGeG>3{6;L@6X@5hf)`s}A?px%X`QYa%s3qB_)O@3PJy+Rb+Voy5MZQXFW9l%#{*7i ztU@K@I2xQ&gI{o3O6-8{vu7a-*VrSFn}OG|-?x?1UW{Z*q)CF*@brv){vQ zXJFV054&}I_3HS_TucGgYIBlP&ecDB3=vX-*9Oej8lN;IsCXjAE5Za#mN*hVm)>P< zFI_8;i^UTigC5e6adR!!Nd!)Xg0D80maH|ruz&kp8W{^(t`({jEav<>b5OLIZahb0|F2`{ryfUNeo- z#m_zoYwIATc2c1Qm?PnA(PtLAHouerW?`nf6{UHhd$*(b-{Luq2gtlWj_6_n~l~RsDeP#yRw$~ca_I5 zJiSy40LNl)+w|HZU1OtCl`J=dt5pT>v4TeLjL<9Bq_Vc0Axc&C0}_Y=b0<^t3wZu_|@h zFz}=>#&Nf{N@ip_KDw=z=4fnp>6zcv%mW&tjP&vDLfyo#z*S z+efl4skQx{gnz60KWm-gfMp4Q9loFP+JQzuw&mcGQ(~gHoV{s|R&(*uB!H{cIeP3* z8ZfL1uWYLeRt_9uKW!*5(Pq_giOZ%tif96iOCP|FZOcb~o2UPJuyq(IV3p<2+}Z3%tZh>IM|T^6B!=E+IKEW8V!_` zlrk)Dg0ZutyROU9_Aq@5Y6FO|IN?9rjs&iu7}XZuX$9z3DxhUuP66;kxB zUzGar!>(0}uC%Gn-r4a#IC>h%DJLSrbSQq%+~e@Ka&tuC$%CZe1Vv&%)8%dDvrHHf zXWhb{5GgG>tgiUb<0?!#g7xO{_5)d>l`#*gYtKFm$wyBMJTwLJUDMfS9c4Zok?2n} zmCQ)vAVhA@%!Jefxf8$>S>`gnGb~8Cr9CXn`8$>6}IJogtSwVzbi{FW*YSm zea5svs%+G?jxTRt$-fa9~@+$>Jv;i=a2M~rO+s6Z-zvqPOb2f?N z7z#7WUor#t_ugTMz-IvURZMJ24w$W*RO(APztYk#<#`2;&a*TSV{=%aFR7a;; z&u($(owfgyYZN|qC`&MUcBkLai3JDrBj~^y61%`$mpAEoWCawH36b$39j0R(%?$ySVKye&!wT~NV-d)wN zfqhw(INN(>KBPv0dDhA86%tQ9_P!P>p2avLbcH3D!lZjzzS3BF;}_3ZO2#uD0yUlH zn79YcQOLt7t6{p~{^-g?qWZPWHJ3ta&(<(5vf_4_$vm1sx3OP`3lSB2G-&^Vf7VgO z{tfh?Plkd`Gq_sK06p1zwX@5A`mCv`6&hABwKi7{vr)-c92niMFGgYg6 zM~XvO21S*Cd$fH*$|5lp-QOEV+lBmi^Z;hhhofUgcD`u+{k|JJ?0Zn6Y7A`t5l&Pn zEz#>*&MN>8x3Qu_YLTa#frGV{H{tqTXavN>-I-vZ{Mco9?Mc38lR{DGm%boX;>IYa zqJ0^qAb^1`=3al+f6|NV_PFO~L;o(i4y5@uDM(&4ddSRE17LMv29hzg@F{)@7{H%G z#jepkQSDcEpH5#M80#y1AZKl$5FC%ah)FrB`Dmg+BD%yH6msC6-hVOw*FiKjoFb-jy=` zf}$J$#aoF#Rqf1gf85bO{MU9Rx7?_Pq)g2SYbK#MR4g3uvyi&~LQ0E}dE!6nq*VIt zs?eRUt2JC$S=|<*&XSOu3u#S3l)ad$F-!bdaqs=fICF3Xl%6+JIh8^;npdm#gSth? z>3^8g+xb`2UZ&02&Ft)(SoP?pY6|U4TYKX(q{Ntd1|{=IiNU z)G5FyvxA5e3QgHzXo{;xU_1?pq4)i3R+IAesSgw+0$^ZbptJ|*H#|5DOkFn@Zuv4D zfhrEE>oY|2WTbSn?A*RA{|@8dZl?eTG9aE#c+OSsYJA7=E3Vj zo>gubFuQD#p64?9qf&5hWyR>uT;N`F;lw`|2;i{PX7$jx0qjUt5J3GFZiWmHzIg&# zfW6NozX*U0m^sZC4qD|X3b7QhoyJiF^v&A!G64()W*7(6+4Jsdu4us;uF7)Kiwy-y z^z;3m?(vqdjC>wtUn9^VuG zrz|NjPI~-5&VPaNF3vZB>g0m{Ey>tmw;N(BL0D%De4whiaEQ%8?iZpjNXx{LT*% z1rWWueqSt#td?Zf?v_rHoE9&BWi>)NMwNsg#y9Y`Mo}M^&gb1~Ha`2&wEKR|q=CKs z2Sj8uGXsg$LYM$|blKFmmNDtHwS?2|N1 zKjnKh)c*Pah|IFpdJ4xL&QE8U-6wYaU82XlyK_c`l9-qy8h-&NWUQg)crr31p6m$`0B*%6JR#nS?= z5Wh(>X4b1YS<}kQXVMclLT-torMLV>jOE|%pFfNgWv5ZF zr4I5-vwL5e^69%gsSdRzEhe_PlZThu?3dFY0DNzM_ULkJRB#5ky#f^g9?rzxoF_-} z`DlTT$l-M(v~=s?B>*J;I{ahYA3y!pL+A~D%neU~ohksJ@Ww|+$rn@&ZQYqOy{UWaSG6S`=K6LgAQWdi#LrZOmOoVvlsSz&}46BtKAO2Hf> zAO_4vnIPh!AaD`9b?a<<1m-FcseMCxugA8?Wr_St)+=o$QX3ep)-d@;jx?(r=Nuve z3OT^u!xP7L`J<|%`w$FrkUBN+`~`BRpu=jkm!^6 zs5|i|AO5c6S4qO%-~pd|hC^=cHGGCHQzwlZ4pU2iTqc`o9IiQJDP{j$d;7Pf!`6wj z$Lzw<{g<<;dAxkB@VB&X+)Bu~iO0^Z-jX7QB~s>rb+6R%pMJ8ne$RW^mcK=wB#bW{ zvG`?;KIYUHkMZ=fk$0r-*MBAOC##guGeSHr*M|XiB+z}LH|>Rj1I|~l5(FO3dYLT} z21p6llzAEcdWn<;aVn=&H|I1L_6JypR(jXd`F$jst`QmCIa#0l&p#gvdX=`y(Z{FI z*1x5eQ{f)f-6?SDBpLz3pzz#H0BC?i3Iqr(N`4H@@maX!%b3i1l+pW2N&Ef>XG4@z z4D#;VttI4uHg`L7$|X)Mj4DDd5`dAcM9nxDNL9(eeq2t*|LavRBe0PF2g8(8TBI*U zW#5mbuN9S)l>F}e`#aIz3mD(RJsgI(Lzxg5LWZjtg9u48VgRz+I(!#Qfr)9I$S`+_ zb{O9Ir75gk+ZQ*{L3}5iampcQ^Vz1fN)VKjjf+Nfuai;SNM{RyiUny3N%dPylMMF~ z`*yNbDImuc@V$mlI)*0ZRPN#`3%6S3>S}3TC0$vjMqFx~INhRE&UrvsNS7A&t*Fkw z*WcX#Jf8o3gKOC`=bxa*ac}QXWMO|8401J>>Mald@7jpg4|H7>Wp#q<)^E#CvNVAu z!CC0M<@|GxhEhTGuxpwC@ifs0^;W-y6X_e}64Ett*+uMKd;<3!F=nPV;f*`!rtdSl zPc%Q-r*la>6DeIcmngNJ@AL3r^!%TC`+#1&vvp52IthK<_v1 zwe#`PAxQR=r`Aqvz>J}RtF{zqsgC9XI&wbMSXt?SP0^;0e{XRT3YnsTwgkXa+-M!& zhhf)_-+?mH91&|Bp~@~q4-*?C*Mo#v*Lc9%*--Ps6nwt#GD1dG9}FqVHO2eJ1q(SO z;(`KE@(WJqutyGKhHhrRlmWFg`0_J2+HQ@o_(u}#V03e#5AZP)kaBygI3~;$hQCk&9g5GMwCoxMqcO;)s;G#-=Q4Szk`nEpX zzn>@Te;A;8!1iT@YZrl{@tL3%d(}GLh$||$D+rYm|CoVewI zWe)qcfM6KRnBe&On%P2*7Qiy|fLoutse?}xhmCes-09z3131m61#7ox(v_}&I!NIl zwJlP3;TL!JM*=0e2v9KyK>>RaD7F=)H_dDyz)p@IQS%D$<%TL|83Z_30GMc&-QBCp zR}nbt$hURRPmA*?q`24h?NO^2kzxmoVmtKLVIbSOR^Lt*dKCHpMgh>k2Eiq}vzZ0{ z@hDKYm{&9rBN-`lq(KH;Z9m+5po3%vO{8W`!(f6|9EGc2!o^-*a%G_c&Ux%+Jv#^C zC!7W;O_txWMnn5tKV2w_`fbN%AIUq9Obs_Vf1Hc0X%?)UVNM8QbiCD^kj~bdbtPV0tAZ`eg4e9B<2C7;QnZ0o+M}hYO}HOaP*hPyrny6y&%J z@GetRQ^9naZo>9^UikjURp8@K#tadz)Rdzo;X z#46fd(;qxwd6YIFNyB|dg!wUiC*w%_GE51*d7k6JslnYh0=M= zV2Dg~sBHnRCO~Cg4~`>0sTXY6z7+jILMs|}g8@mKfHnx#39zHc9!1*{8r zF3_4?r}G`oi5P=Fnp<{8u0u6#aZI4G#Y0lSDadmp%yn;&e2|S#;5K8|vsVi*oSg|t zae~ayK^m4=aS#Feq&ubWY4Ondli3U~?q{+Zdi!abmXFMW*0hsPq4+0to6o#Y-{=;+ z?y)N1&wOBEM0W?vO-hSo{Pw$M%DH}e%RAt$^5YpYSJV5^RXo#%j_P{TQ}^BG)M-Ka zHWEWSqyCCPJ?Z)0F6sq5V7&-|4l+R4Db9RckM!l zha>}JD2YN>aG4SXCZQDx@buZo?HvAA1+}$4<(hxo@)we29`k=-zJD3kLdmBvxViLU z@vHNOg^e=#J6{Ispx|=XS8k$Al-e#L2mkISot-N-`duV4fy1EV-;s`i+y8YKgru&j zYU!E?R7uwF>-~PIs^A{zC^(Yd{znm|yS`ym<@`O!!ltB3>SzR+iVWwmmd3V$&)%9> zuEwl;bDV0emBX#(omAvH1)DywIH;J5SpRmJcU*#U+@o%@)QhQ#m-n&52gN1jOt4)2 zPyRMZOyV_Jfe*Uqt&qOkYTYJ10HWosu$FbC!9fo&bLF#H`DyQ6de9p3W}KN*XX%*s zoa8Z>&!)bl00JxzLDR zruQ{3xCk6j9*`ao6%m~MgtHn7iwi;U2H;NimP7tN_H8iw zy~-cc105Z$EDX#cvw=JL$E$R}Y+8XL)=tja_{uQ7zt=mG&s!`=kL?CGPhSy{+4`mar`7d`+1sw86j^Td z1dE7(FE8Q)D$NQ4oc7zcM$ekJpP!uj{O%=Q$WR~7HC$u{EH;OXH=dqse|xrl)+D^}!%V#Rv!3ATP>V$<2UY3F*8 zFx%ce4^8^{l6uy(%*ne$OZC$9Vs4yZ*Zv(n$XtsDXam5H`-gXNl!ifBn*0z6uPD2` zaB>OlBocOGl?3p2k|Qwn(2)8irXs!%atrB^QkWp8m3)cwA5^NeL zOTb$a0L+j`rq6h^S45a!aGqY#Y-@@HH1+fB(mF^;z3GRAd-2%wK+DZ|1VmA6f$ zls+nb2=N|)^G5kTe;%z9ymoG)KM{!|EsMeDF#cA2%QmrrIHS2?_mbga7~HTT1T-By z@!30HOnT}0_gIRCRQpiyxO!dSx%LgWQ&i&vx5mdiFXQ98$$#zU9w&|wt;s`QrDYM- z^PRn<#MiXt@>VrjXX0ilR^y&3)UEbkb@uYQW$Hd{Bj8;c=hxo&?=IO&^N!rn)P;g) zsV1KIV?}jl{ogTbQsdpFM^Sqds_cCVUlabEl}E$03TCfIRMk> zJtPJ&hu&#qsQdBmqsG=XvNXT{HPEyl*KsUJ9{w#ZWw&4d*>_<|W&NKCj{|&=A^CLMNFv&#WQ*mMipQ7FzR}|EGaYPa z-L}25y|=6O{d*)TGRq>Jj~?u~oo}vnQ*4Fzj&rYkTa<6O_|tW~DCNBW-lwxk;C4g4 zqtA$piFg6j<+8z%BJ*Y#;=^P`(%2t|N`YUcwN&W2lH#6r*jbeu=DWBK6f@HL=0Eb> zJ!XCDd^E0it)3W-TrEwwhvR?;P8eC*O3$S18CkY-Pt47`u(o;F{bOOmg)%YuGHJnJ zBshVIy?3vN2;I|SF7Y(1Hn<3DvE_l#&RO|*?X||ZP;6}F!}$HP zgao5slk@K5c0Tql;p>6Hg+>*@HSs@pJhJ(tRc=sL z+x}NH_p!oK4a0z!QK5jROmjsa`#FL4S^WmdIAMm`$KAFhptgE^Tq}Xn)6;nB@r``& z_?^qCN=!#Sn3C=}M0d{IFPqDvMQ%jUGD%x-+k4)L8B%=L$FLlQvIr&23G5R+6y~-c zdxO*|Vr@eGI5+J(%0`;Lq!v{ijZz{_faXAXFNfb#(i zp1=X8^v!ck=_7L+oBirOKIbRgn2O(={%Sck5R?$G;pgREKn>c#qk)Hj(|ABc42&?} zzk8)r20Z*2rSw-|lM>L+c=E3P6ocG;R%nXfSlBx5U5<6k<_LOEfSZ=iJ%Opfx0%E0 z1#Vpj*gmeX30*k&g(_2F0$wb*iUhfkg$-D-IZWg@LEoUWgp?C}tjWoywF7M0=Bzl# z&eStr2Z4#@0l6{vUMpT&xBE?^LDcEd@i=RDGr}1D5fkwedA9w-C!;CtEaX}xk?*cv zvy=?NtVHRRX>-nB_u}f&Hz}Oo{LTwyj9C)a$4kf0yR*-`iCdOh91f=sfczmh$jJnx z;GC=c5-=-j2>~GNgGCKBRLo%hXUAOdFq=^7GEUq-E=6b^6FG!QW6N0n9WD%>TPZq6`SX))KneA2bcm4jwWQ*DI1 zB31^Aa(}&Y?o<3`sU_jY{PbMRdkANcdiHzE+4%dvY3(xF0_C|@Qv~-&tl4$f9!m0d zO4`9E{JKqA{338tzZhYxXIhbZLMBWt7;|tMPc+2RFr5OT@FaZ$iCEn38MW z?Rj=XuC2)zAMe;ce*Z8-($>w%O=6D1vSy%jUKl3;LB)hQ6gvaQ6Be0Z%V+lirxOn# zHDh%|8AZ^jn=@8s0@tzK;pR)gR~7+XByAB|kTr(dfm-5Y4t-urj9C9AM6_iVC9HjF ze~7zAidZx%`qq$L#NO*~M!<;y>2uO5t4tI@;?lA9OLrSF@ZC$u`{g4YI!C(yrK45j z6AkH$DWh6xO-)VW0074yEC0)SL9zm6t2d_;5~Q%6Jsp$?98S5}1wcC`fIg`V2s9jJ zRyh0V61+Fpli(#92UjcE%kqh$sPpyPFSg%kzM^x*&jl>L|RV1HU0;&FXVUiFwEkAmvI#l$%lw)FzpL}! zef}D>&#H8L!4>H$1cQTsF|?j*+&onffx#*P++bU^&{KI~CxC&pCHKjJ4mwBM3nU*{ zhAY|J`59ceHeYaPIw9}7#eParR*x6nIa}R)lDj|+k54sdr-n|=P>AdW#9X;^r;`%= zG%JmUPaFWhd1sogog4m>tg!njHo0+Tk-_l$V#8A;SW0a9es1Nu6`z`9CMR=eeA7N> zV5f2D+2F+(TH7vmbu07W)`(L$HGjL1og-YeDl&I`9mn$53fCIj{C- zjN`C0(XUcS|h{Xy~vMAI%Jb&-flZs6J2Y ztUXG8nODXU8d@_7$FdWwUy$&71imD+D(EJl=aNpjDa{(o`z+HdWP8TePFssDmDDLg zLy9xVktmb3xEzq|%RT09bp~635Kfu2uCa=U>ti z#4nabaVO3k1#rPMxpVrl!^Erl3#`q13{25>GTa4$A)8axL>I_w;TjGJz$ILg9@Zxm z#%U`87_?71A2C8LnL9NlR)O?_*)-(hVCbLaZ!LDfS^cXzqkLP~hdqD7?ZSMS$48~uzO4+NuhDwHfawUq9t=_* z=K%$AO>#>;56S*Acq0dEcJFxS_GJt_x}^Iq7S7 z`L^ZXv==*AuzI|Yv! zBCmZ0*dgyBcbzBw)=aU!!A$jDlgwflJm9jOAPjfjWu;SG;{-aY0$*}63p5MIO;3i3 zc$5tPt1vhB(U))g#g1IJh$tE!|CM>e{j}-O<+wlf$VM);jI9`RG1C_`SM}Gp)P!eV z58Dg2uGjdz5VXi-fP*`x!6%DDbe6%qC?G*b1ps902mYhOLgkVg3)p}Iz)1>zP=bUe zZVe#c7(yh7aXc*iO7biJ%UA#JI)oc30jG5TX;H_eugCKNd_9nOXndt&K>gquvP@*` zgt?MFJEW3AC%C66sAP88<5-q{!!}TI^jTY4!O^#}^npL??HflH+RuJi6k4V*-Dsro z6n9LI9P8_|G1}GeA$+@NCJAl!AU(I0UrpRp)l2@oELlI0lYKBCOVN>*ybRfiw*`4* z*sOoLOq%njvYXPZq^hV0K|8T`F#6$dFcWOZRY7Rm#bYzSfGBE&6gz{dmMguF8f(pm zXb_Pe2}PyZ*Ku7}huyO&)6{Zet~;vad{LCpp8Y*OzqN}n;inD?5icUTls7eVBu@Rv zQRvy>+ObV6g%M_V_uOXhn9FNloo4M*P`&zO!C|psdit1~kM*8sU?3ou;FZ&8BZ1(shP%hgt_4DA$%V{`2DfS zrySNUrls1~MAB?kS)eOT;M>1UkPHI=W0Ao%{Ell(Jr5m-$A#K`zJmrB=c9L^Ym<@+ zB!9hyz;!~MC{95ONy`J>QZkIz5ZFU|(8NzU3L(Mb7omvGYWn5hNn(>_(U2&Mjp_Cc zNRbYt)<_bqVC$$g%|BcM`vun2eU4>;@dj9Tm;OV!O4NFBCW~2l6}TsaM%boPoiB_} zA60jKAwiOYGV6YNd)pm(rSWz4l}oC zVpQ&ITJ0rS67v1{zsw=a9ugDtY1O|d!x|n1{!F2GF?TS&eRfTs;(rjIjM3d*wSA846A}0j!&FgYR3-a)?VAp(gdB|L(fS4KCm@F8!cB z-~ys5kSubCZVj9UqX?v@NsCdU_-&ef#<~l8G_QasnpbG#jKUm!?suKCcDMGG2niV} z1HSRZAd4CM?Lz^^Fcl4c&;J9)M~{23@vNHPUMv}ibY@SkQZtbDmb@t62w2SB zr&6S`t9JlqB$sffKQ4-{Eq*#gb_yk->H}U3ct`gR4F?w~k-|X3(H-44M_W z-gD*iXoyYc=w!z0Fg;in18 z_EpXCMU{z5YNv%Ftt(g7n|E$`do%B-@p5In_zN5Xk%7%-X6n>Kn5ADfiCc!6*^2y^ zUcY=LBqRb&Eczav=!;julbIMwK+t1p=+!b3g?ov@(bDMf!T~K4_&2eAteDb*0{AnL z=%5}VQMbv1!Y6nKxL}+b)c&0?ydZ46eYcDX#wSXkke9{=`loN)rf_8iwZu^9#YTI#XATFeGOuH;a8!kbU$6| zLy`A)uU4_~$7Ku!oZvs41ea==z}<%7>hlPX65X48j~dt6LPo&=igHjms}iS#wXBSo6i7F91q;b_ zP@E;MM!W*U?pIL6^xsyJ_WWT=MEPQulp_Vh3I;_zO$``8M!li~(eD+x`)#Dsez$+C zByA0`=ykJM5>fr}I^lP2Gp8gnB((a6NMOit=WH?)55UrqNm~`im-yeluQllKb&HjC zaY_IRD{_=?T2yfNuP}y>Pw4-6iHYM8voNt1RdUe&tO*=hG1kB={skjRn3X^-Da&=_ zL(fu*@e(S$ouPEP8JsD5io3jmr_~*p^XA%QPnUd`v(FX81Nm~2jR*W^!YE|N;9-JE zh-8`&WUV8KPlvCp!ScmOj4l5cBzs)Fe61dIcwj{+Zpk3 zu6V(B6t;ZJS`V6@lH}t46ipA-?mr!XV-ek)p%PvF>hrRide!-ZXd-|as}5-xUAJV@ zG<4Z8wm(!DgbkVczm~cZax@$e)Q|m8r)jv>c>DB{N6_}Kb*k~}FD|;qxWWhULD#}g z+V01gzrE0-mZ6*G&f4`MvZRN>W0DmCBET#=4ECO8FF7s;rw=EM>9GX!jaw~oul>HB zjh`tTg5$n^=~|c9=QI&}ayy~KZ~l)vusq;Tx)WdVARVxVz4S^cel>Zck2>a>6eT5A zXQ3Ei1LIS`M{0o4W90#obufIc00)(`nbs9qQqybBd#}VDqP{f;pA+EOy%%W!!`6Pt ztr5F_r(lPvM;1!Oq!~M1N)ED8`HrcVA-j!XMDSdhciZt9ibJIjUKPBdg(iW68cmf` z2UM`Hpw#5dqgxUAp-A4MuTv?AQBaScMx#KN=zjVwi8IxQUq2miAnTW}uHE z*b@brZMKdWO5ctP#8o48t|q7XiXRZ&j5eTC#tf*)ftovgz?95WcA>ba_LR$6hfq1p z3Fk{V(Pw~AeI+!e|AG>Gi5+_l4sr?EEYLAN&pDH%yc)3^#Js3_!bMzof7in%9GdqA z`{c>!*5Njj@FCk5mA@5fKx`Hog&F?1n5vHieE9q~LqbwNDz3+jwcJQg%Q)>k>e*vE z`{rLTx$UGt?C(K3j+we}ya9zlvh7-qr9|{MbDJu0%N>_fjaqVON%zCv9s2nCU6}u? zW&T&Y%!F?uKk>GxQw_V>1T5JyES7noSjArg7XDwVA$JzaQZG5}5gbPP zvdZL7+gI<^m=Ec}9aOao_Tu_LXZb$n@>{s)yAq`x;Yb_@CsqpHIB!)cmy`aOc={AO znrd?WBKu&xt1p+(yk5QxQ=1t!bF@sDmL`ZE9j2!FXLfoLX^;0<-PY zjjbFW4eWB0Lg~N@kb~rW3&4HwmM45Y+)&CfU+o%B#MwVCqgejuX@;bxOW(*$M)Ts% zw@q7S&U+IwEbVWGxCFA>1DFB4ThuSkoK|gGfp^kEsX-Bi1#uRRXVWb~9OG}+-0=R= z0v4r=hm!RYSLJJvfL0Wo2W7{`LqH4De*ghQevz3CqF0Gm0O*-#hAKy0up=G{bKRZq zD174Q*7$VcaOO_$<`sJA|3dZR;D$m+kX6fd`|c~)g0EdU5mo1*K3}nO^8M43_q8h& zgy+r%-D8R;io;HfOPpHgCrCoMG;Q_r)*w*X;?d2LJC1L))^=61Rb}3oU*^tg;=3NA z0K5I%@Z?c<$ouGQp4AJ=^R(a3G^&`wgYreW0zU2AFs%+WxU3AS8%lf{`l!0#A;5{* zl?Z$oP0hRhQRxT_=KaoUHr|}Ilb1i&2b%wph}e;Y5%ozwSG_%1an~@R1SV^hx}EAxj4o1}Y$>_MyQc?jPCpY$uYg!!*he z>dc&kF;fvru+?pB3m;rDP_gn&gq2ZR^r(_EexSfWE3uwBA1&KjY`#zv0n$24x!s)h zvN-*=2icMAfNpZiPBi>_?A+QQ4`R{-y52*i&JSvg3YP?s3Kq!4AG52Hflgta-)=R` z=jUWKY>s1rR|XW%2!2a9UYy?=8P8`IHp!;%lUkZ4T;4e;d5v5VGI;aWi}tJ^`F0|0 zfoXi^r5iN3b~yhNMzXb$!t(A%TW5;=j}dnd0O-Q*ktPBl1LIZ(su~~?s+D1w{HInT zp#2oWuphMxnS9@O=U?R!-tw3x_8$GtIwJ$R?jjJ0hKH z&vGxz1DhWTdON~u(b-93r4kpk`wqAPPC2R5F0zWszYhB?IM?Dmc=u2iuRRXs}<_Qa$#vXk!UK$OTrLXmQbi4=p)`a&s=#Z6!^ zxc)HL&FOxa3pqXB$z0A;wsP}pZFd8YQ?_()L&s+Fz;D61Yh^d6dyL<1Q>P$*XuaKJ z)z-@ZxbmA07uDotpFd&8@||D_5AP@VI;r2M0xv;D=QpB270fYsKTX#Gh)5YDDE7X~ zd~*NlQRlOULK2Vwa~W!_qxedz1Fc~vli_zS5`A5GZw+`atXlmLU4xeySH^3K=ygI_ zdEY!xf_za%ahul2e`<6&AWATcg?VP_E^$UGly@r*WaKHHi*yMkqL2>x*+8=}oq zViW5QMK@m3;%Vyv5`~q8J{bDQCuo{v6aWM#91u|`L(2}+s z6)^_bq(VW=l}o0`FN%w|o9{K3|4jLAd*|p}(O0OKKGjG%Ise8ZtFFhn8xE~LOBY$) z4wjxz>>hpbOe*xqjb19|vBrd#)5NPdbo2XcWGJ}RmQrU=5yjEzGkTxU!6t?gH|6vZ zA&)PTHidJrb5~L45;kj%*K|rpHuxMex$LW$`t5I9^aV~eKO<=i&=sLy6AZsw_A)f_ z)7vZSjI>owBgwmXX4)0c!GmA3=0D)R9n}x$blmKVzFbkY+nA>Wc>QjcG37Njt`Y_e z$keb|G)oos>{*zCygCX^rChfWz=h4tW$vhK+OE{g<^pECE8Tx}U-&3e;JIblz60E-{`hdD-L5xUI~BtokS>pWiF&t~`DJSR_^7ao^52e^8j}afNMT|MqCT2_uib6 zf7hDlwOIQ5P~jpJfXd(UFhCubMNw$LD2UAEKX3gmFN!376kJq+;OETR(%xD#gYTC( zSU*e-^b2!N-1#{o*<}|oyy>RUtN?9a-n~OG~qyID0Mq7N?td^yY<;oy0erTNc#Za#E_v^@e z|JVV!j=G%Qqi3RuqCcbg7NPLVf4v$6`UAgVKa*V$!L?vZnDn3SedtA#=2r6cF;j@r#?$b^!Mv!LMZ9s~^u% zvhHd!b|s2ZL9CMU^ldAv{Z!kxr^P(7yu1edPH*@IIt>H>{HpTKwp^N4=#+?R>wCT* zfw-fdIzpVY3KE+uVfLUvKW8H2hyr0V&o;dZ6_kD$rDRfy1t#9>m^@elO7+e*Asqug^BS_$nzM6t1 zi)DH|VX&k~1w_MSZ-fuks&n7bzwQCuyyUIdY0ocVn8o4Iv@l)BE;w8`6RG1F{NFAY z0^h%kTdiN5o{cz?67^CV1TSyP??}D2AE6X+gpr3XrOxGAdxI1$19iUMYg~m74m%+_ zu2bM}{_akBeu5~ZDES|0kt5F^aBd+*AbXWAyaE~MGJ}rcwjT=1u}=zs;KD<~--m=F zh&%A?8^hJOm@J=Z;yMjP?1k))|8pm0B)LIKiM9JE_y{SoFUWJxh3~$Eddo8)` zw^|js>+wXb8rflg^O_9`iI5s;%fSRKg-_?NunN@s4&inwBS!3wcl#x5L?fQQlon=^*T^EJV1VeZCAR?e39g+hmBB2P1 zqzv69rPK@|7Ns;wsB}q#ATX$uQqn0P64FZ7%=f(CANlPy=Q(HZwb#9NUF1bMzM&HV zYT;;KK=U;r{maQWvk91z!UbP7EZxf92L~Az+Vg@JCNvYQ5a+ZBDN;sszO5DL{2M7D zL)?%6o_(_I7}lG3&~NoXwRL)p?rcQqU>_0Ruwm!q@-e~Y_MEcO=`FJU@v)!7B;Tzm z^eU-D)q-raR1ClLS1in)wJ5lB*1|pP;I~y zJOTb39tp%E&;84@;S8QZ!%sAh>$HOUd#~W-5>F#WlaE`7G|Jj{aZhamV-$j5B9w}m z{9OyR?Pu!mtFJyua%BW~5#jv;-5+4Zl<1b)p=JC1efG)pGBMsA;Nx#VgfxyEfpJFy z2-3hhvV#Hd@Q{g>H|yrc~qM);dpCC=f? zK0iKd(k@4f11&(!z56*S{(krSqMkg}QJ%zQ?x5mFxjc z^WK(|6Hz&>uDj5z=z1I66d5k}H0HUBm8cmgJ^EtT(`8mhqqz92lnLM|SvP;RECpu^ z`J9$c-7|>`^0zzyO@@8vG)gFm=^xp&=B$fht>v>7(o(Y}Rpmm+1m-Qr zG)?;qW~vVVYRsD5a9vhkmLXD{*2iU08VZ1?G(`3&CaMtiIKfQ+x>~*AnvyT&Zn8>u zA?IBES11Oxmi@!mSurSv$*ABBj3AG%vxF}^#+XHFfeY?CAN6TG)Sj^A;An^uBVgS5 z&U#Z~t4}M%+`mGoIe}^@+b5kpou3BeBH`sMl|xK`Bb_Cox4$kE7WRXi@Ton|3UHjS+6G$Df&}upF{@7~TU_P#Y~{PV zM`}|kV7pPelxM{1_U#*BxZDVQta~icNT0_e1ylN}&*=G*`T;gVYaGxdK^ z35X(~IYIYjzyi(#G?Y$-kpl$qaOLe2VlNc!pP+QKIrYk{h`8@qztE~q1KogMpJTY& z-|-)8Zg$sUXL{QmB)O00M8a$0)3ozy4SE7tBzI;f>A{@*)tAz%)Mxp+&v_y_w~}sJ zua(zk-^9g}iskETo#PydkBQV;b1xv=2bQ@XxJxW&|8;Y@PHPthKb7m~SNHWg-tkYedr*l8YmA<$dMXD^!G`;sn2}x|@ zYG%EIVUWByqxtN<&C=bPuxYpr;s&$`kVghc(8H)&_^~`75} zr#?x0nPREaegx`<@b97ev!ZU8RhSceo*Rxcw>QKZNVzMZ{(VY;oSD7{%ny!eSxB|r zX#Qg$y|qYuaM)h99s8FjiNbqxvEG#8@F5dOi%9wMtnLjTFr2?z)jcSq;PrF;tV5yY zub0XK)KdIDEXYXe6M;c5>VFbpD&#OaF9x<0YJ-6=GJMGMZIUHp7qw=>+*jk zZZN>Zqa6PW3Y#c>Aj($if&|fD0f(zRaY1U0XZqtsDd*U$szp&jr4MVZkuW6kNnx|I zLl?0+Z1}lOxb%$7G8sit-ciH3cb3Yn4PTpu(sA}@kpr`eNFoJ3`QHcs9f1Y<4E5@> z_QK`B>%!u+;(f9d*PEr+vTZKe^p=K4yQJLt)Bo3uhAuPXr`Noh7##IV41UH31*gQz zZw%9M2h~);I;t%&JV*~{VE+BkZ18efd*0c~%%>6F<7vBcU4ZAZfk7kjj-OpUHC62_ zqWKk1ji>|x1G$xe&SM0=DnD>P-)=o9YicndKBKISq=_ucA+k$QijRMNn0dXemGV&e z#?;;C0g6Kpv-|Ji(VtRb2=nw>W($pg*a(}Ib2*DrRQP95twqTQP_I+hQ*6E=y!S7( zsg1=KI{bPFo?;nncscn;84HQbIzKA!FbHj)^^ew5_=y3AQiI9+FdzbL;TDJImE|?F z#mW>z)8{BZ4E?T@s1;g*X)xRK$?5djd<0haDqBxUrD%w~O1C_atLUGc*lEQ7@emv@C z!q;*5-NhU~ttjVeIkOA2Q2LU)kud)6b5FVqX^R3JMgmYM5^4!&kOhzjH4vHxs*~a* zA?fc>BVHH!qjI|ag*3mvF?G-AiI`Q>JY9&-$0^YlVg9#2I^HbWoO;UzQmVeTuL?r= zK@bMJBI08E;|u&AHpxJMO2k&gbQ^*SVdyxPDE-pyY@_t6SI(^7-Yht3c(m83ysg0f zsf!(MUbjzOOY|O3^e4LecVt+V{Sj{FZzHeXxlLPB&oDhiC=_9)_W$vT*XgE@y1dc5 zc9DFPTQnmB_9GspWu8G$`jf`DF#*&B8}XVhFU^ll$Y4KT=`Y7sUEBQjSLSkWXK=6Z zG~;bOHEGut2^iiES@1L*(X#?8Z0Ec(@?GcYgR-|;&^BvP5g11Nr0B)wcFdwDZXW(5 z1;;#54i%VitI9~x6vjiroH3stQcRb=a30ujsH>DaQ8PH_-;7zuH|T>gbu;Nns@OZu zBi(2q|MD1+v(gy8#gHKeq%bsYh^272ZBUl@vY|YtnF0Rv?Gk@Fj+NprxUCPn>dX;U zmCY6-7=erUi3GBT_lDf6u2-4$lIbNw3uOQv4E~gcX*&Q= zD4apGZ2x(C)Sh!nJv4IorEX)kLxYH;^mb92{O~IyCKL~~JEFi)&Ze^DY;wU_?sXj` z?!m!nFuzyWFNjJNq|1vV-?XE|Vpjn5~Q3Kd1B zc;0_ByCIqa>~}4%?N3mDgjx&jiPWcFKcBs}{XT2*_~7=`9r&cN>8`hhp&>E!pZPH9 z$y_U?cy3BNg=68_a);?T{g@B#e*AB65+mYs8Dp)#ekRFVTL|nIw5ewX_>V7`lOEHx zwVKL_Hu<3LMCL5!V(hAE%|cMuTnpRmqVGiJiAu?tBY55B<%s&v{USsesu5gvtp0c3 zl{q-UP+G-|1dIqCbVa+)`bqT$wTQ~Dd@-UAHBRa?4nnWEoQ^sM9=2R5xA`?#(E(l9 z8h95NlAT<-)o0&q++r(n&U3L z@rp_PX_DC5js^M}Fb)uonONkZM00Y=+J zW&zviS@@Vv;3qf-@h(VG@-UUao^?k;EveElm1@@3ySzrPZa z)2Ee4?C_z)j1}J-A_J#;j3b3Fp9=o-#*gJDyLx7O00mloMvLC-wH^;?U6ca1nbjP%Z{y|Bu z7@-3YziP<)4YIVC(OX&XqzfZAG0bLat_7!YSyc;B( zKnD)$Znt-bZsG2S<-RUrPJOsc^``8i&#Q<=FFS9sCvYHXdh-MAeFg?ITEh$-kBCf> zU=>AWj_T3B-vxMt^3&fxV7y_TTx74yCA0D)^pbU;i`vak{z%(^!KGWlCz1c?r;K$T zL(gNo3;o6^dbpi%U9^dTPe!~?li{aH$FUP?vu9Q6sYXt&ruF=%tonA@FU;8*kukIP z0P=akyz#q?DgNWmn>Ij}_9Y-(7q^ebOc`5PX2d5-th4jtKN1z}JX|RF zAlkOM=fble0>V&oBC1u(ILW^(MQL=$A}*gIt$8!UQEiPb)`FpmROgtv>*iQ)_WnDz zwY5f*Mk9vH%<4gh@38!e8%4Qy%*Z1r6g?Z}sUg>ZK1@4>?5k!v74mAC%w2vwFCvLC z$2Pr~6_2qt2-g~+zEPtO;f)DLog5|{f4@3z`b*6jcwg=8LXsxS2ge!|sPk%V+#g{9 zX1J>Cp-@L*Sx?n%2A=%eNbesKjKlD#!M{V5#@6BD%*whZb}F_Y$6#M=0%1HSakUIg zbgKC_x2X(V26<<^*JWQci;V!xUep5^j13BPpur2m;lptjrL8_aF%!f)VW@gOYh zE4YxtcDLs^()3dL9AO|%-)d9s;ejg`4F9@c?PPtK0@yUQvaCkfav_ zyLT!PhGS13$D;CZv_vVB2i!@}yo=0TVB}82R{=YAvYZWFn}3BTi(hy}%TLtT0`$#9 zepv||X2;eAHlg>~SN)c?c-3E)*^R0r)mV1E)12N|9D|Yq5FyR6Vi!8umoJY8$vvWR znv~**;A_dk!)RzCihhn(-ki)fP?mEblSJ#Hv<_!~h+^7d2kpP`x0`Cp>>M?b8@8dT zdTW2fwjPbiT7@-rk6tm7!Wg3lAZ-^_mXE}Assf1-&5vq34;&f^6Ea(k$E}l582B!U zp&?~_->BfeAJ)50^Ys;;cs1h|PS?7fmA^NwROcoJMuq725ur zI0h9_+v=WDjh4f#Np|{^MLMebli>2#gRwP9U?>@rS#^uc;g%c4xo3@Idr9Sqs*=~b zVN?^h3!eC5G$ikD^-_Uh)`EIujt0ONQ!WmST~Tzr520raq8ApQGIXpjO=E$w;6)fA zUDG4iwVIkBm2%4vX}Es~0MFU4RoN7%vP0+OcqDhL#(`3sTQoBJVv}$bz&p*dV$TxH zA7$BZ^Y>7|oK|(kpgf7X9;?()xyOJ|cF#K0zcgMBQZ4H3Xz{lmz73)WXq!xDgyK7d zB&6QbfWx+HP)h=`Pk#%m3)#|SQvw21Qwd*LHX?^oFqIbKk}hBKu>T5Op@bNn^@NfE zJ*ujGepShEb799bn+6GL)MLT=kp z3bZ3;gQi2#LMkq*K}*CzId*X;2EB@xoNE+*>v`>F>kZ@;j!8@&dAd*upm;Q$9%iW=}}iuu4Jh>F|G=9-m;C{mu!_6PAs9UI|?pEGbAdm_>@U&I$u zp#*XS@b=r)96P^W77%e833Mqy?oDI`7!$bL7m0#w8ROp#spdI>U1h&bzm|6*6K?)I z*@Py8UcNpQ(if#a_7Ga$p3e$QV?LUxR`OVZi`OGzbJr}|;44X_kIL^=qB+;Ea^f9H zjHT^sa#P@y+*ObhWd#Pwffxl1_{hfO0p2ER;D7jyoCBC^r=QeTmTCa#0D$JN>Rsxm z$4nXs{N$xw7>nOEw7v^HCilz2A}L zK-Y`MHRxFjV*-bQr1y7BO!{;jqU#otN-nnlmOKlcs$pfyJm(nOFyO_+{j?atz2c>^ z+>*J|{ETYpQU9Mv3`g&oB`I+@{JHO!$Fy)<;X&uQzA@zUH_xw&|6z|S-nl?$eDZrF zr@(DSgr}>e)+;}CsrmhAv#R5TRbstMZ|msVMXNv?=l;Bl^XZ{|FaNIhA9s56Va@wL zaN>~jYw!_kL@4TC1AoMkbSMulwbO40pC05KT5irBqmfVrXF*cTLal&#K0c?8;|>GC zIAY?uRT@WlohsP^(5O;#r60j1f|_ zP}~P5fttG&4l2>?Z+xKc#cK2ZLA2hyz|C#ez|zxu&JLy)H8rgz*Qh5XK5gDEPcF1N zSa=MqBSts?1uv^6{>zloD2JD7r(N7ES@wHOMwPuH`NA3>9gzjucDoZa@0)Sq*Q{f> zV9bL-c%<`={%K( zp;#hAj2G@_qtoH>_J-d5xjN7lU*Y<$XBM)kKk0N#0kW@P3@=<0|8xwVBV+6JS3EyE zuBNRlH$1j{q$a$voP#tuv$fk`Hkni6+?~EVbQx4+<$RZtV}n_4bn>hHzU`2FXulln zA#BAJA4yK%mteX>AJX&3uAc%b<1syu5`m3^Nu~*MHedMqgHzBTh&{y zPy2>M*`mS<9BnN6vKlHgyd^p7_ivQwaPQrGzpM2@qLc#e8>SV7nQUr$J;Yf<;byaS z&!n;bdN0lDOvX|6*0mOfVa?oygaH^f zNL~6Z+xceq@j1MP9K*0!jlw2J1L7!kc*pcZUQP z31)Ino>)0q?f5Zv+LX1+S(Z zui=Icb?dEF;iq8~5fab@Yg%(gy=tFgj#btM8&8mv_aC*%Uyp#^pGO?wK&me?_p`!> z;419y@x3S6T(C5RIhWxZGd_S&?7*tatbCv_r<8miec9NFCkP2sxq-ilq-CWzzY}hU z?I>mMaI3m&4s6BQl`pP6iZ#q%>qrgWjs$q=FcmQHMwb>stY{dfWp5@(@mF)4n7=na z$xY4>;EQMl+%>Hj?oX{?p{2Py^-)^bLZ)_mm_M|b0)LqUWL`4<4hSuG)Ll3uNECpu zHzW0k7A8GfA@jq=yv~uoRle!I-D$CK5G{VB2nkHfSyMr&!26UnKMwV{`Ao}$D8Z9l9p9=aQJ zy#$K-Vmz>+#E**mRCdDqyGs&Bkv;MWb=1R-p_vLs+urK^9jbohpTp`;U%Y1}CdyxM zysf-hWROdI(Qd0KSpm4HFD6J8Gw)2C%UK)T;peuke8c~5m6Qs73+0f4FHWxlpKrvR z%o?<{<_s82;tGFU!+;@k(p?(jcg?w0?Z>AzS=k!zY1idm#I!<$u3zu{=kq-=`HxgR z5H$5iAslVtXu7Yb>4%Fz z>t-gST>ZcomCxxu)p=k4n#7)h35xLT-Vj6<%oLusT<=VM@8DJo z%;kct6ymHkxs= zdvQGSDXy2NYpdTe+j_LW7C$jpO$=%MYI#TNWR2~(eN@w8pvWDOhO_%cSIc=w@d;XA-fzz^x)szhNG8ptI9Bj|9l`&u9ZCWGBW~04YDNg5D@6= z$iev&(jUWkf#q8Y3JSQ+yzPdEg|vz1m|#)H`FGs(FBmN*w7xM{SBw> zfK7s`^0|yd%afKi`Gv5$Yo_oJIpCf<% z@Q3u+-Z=H;*v{HS_US&+7YT1u(5!8%Sp%;vPEPc2J5l4=cbB!d`GXEph1^a#cfU{A zuZ+m9I6)3E$3zEawnUl2re6(T8hzL#{~ zJ8V6i$#@&5QuVe*td+8dg6yJ|xty}*6y~`-oMui1`v_b`NI_%eIeo!i{tvf<8cj|0 zR-$Bf=yNDLq+VDUKA`b-jE+MCSTb>~+T#^{1Um}D{@G<@f$a~^6ErJwG%oiJ5?aDc z>CJM7($3t%FmA5GQw&OaY4B(H(7^$DK4)IGV!qEy5H_n?3GM;!ka+!6(c&vitFgw` zaBl-Wa9$w%QPdHl?)ILk<@a*!KKS;WLds40Mmh$}@R0^Un@a$e=VomtknAfnL1j%-A%p54RHis>7RU--&Z1yf)Lri{n1W)RPTh9=N}uJoKT9uaB`f z2cIn2IH99>C|Q{woEY&#>;^S~$?OVCii+i(xHRF|>lPMMfv;!)Ba8-Od%97e%T9wF zn~OxxGU`ItB8Za5SN_*)T!rK`PcJXrc;2>RYf0Ofp(`cE@bY3l-wW`HNWXB-UhMkJ zeNUDwYR;Eu;n{Y43Ur7nvOx2LtJPcAiv^}$*F?9+M0ftUo+hi9Hmcs?XCC9CW%D>N z0~=AH!yZ(6pRDVVXpOYOv|!} ze~*5}v?H zQv~4v|DmcY60GdzD1jcLZx(~0K3v41&W>6R42r^a{-YJ^kU6r(Zn-b!+|QR9;BN2b z#bEMi_lsh@+R8)RP1MHv5{0{pIW;rG0=u~=Hy3`7Gg;FS>hQ-LWH1GLrweJF@+CNf z3!^?M%g)mO_W>l_`RifBxRd*LTPiQ^lGgXJwI%QiaAZY}bWIb4`{)_ z$cWr`=v>yn=SzIo*yRdpI%{99VqEF%M|Y$MeOhgT1` zjfSwzS5q?YKYMU1Q@4p;B~eAL@Ix!zw&}&&E}R5ul^u{9j2T~?$SBc@DLYYVR(r2- z#p}zdhazX!G(U4n$1^V?P55atf_HN%U9XR}?N7_2&8jBzk;Tg@A3OKXW~%Aj!$>{{0UCxPM^k+-c`;x$MD`R0&~ii9{R|Gy z+SV4i==Tbfp~)c|$*2k7T{JYVar$;cJvGL=5MY*nk1%AcKkmo>HC|S=0Esi!6*&Z} zZT7>oJ@)Oh!-v}Bpw@FFU1RNDbF>}nNn;p_@D6H81_M~Q0F+)?@Z>$&i^Ys3FH4CF zH1u#s%S%jFph3PupAL_WfMMGAUAOpqbZpq6Vl~R%eA**-#<{js0hnQ}<|i}z++;ss z{MUo4V?e#H!^RQM8u`@nP~eiU*muk?#Q9G6nfhUOvqL-ryB3#T{SCg7R$&XUktE~j zG}G-v?aKQ48&(KRN9~#Wsa@Dx_v3$mY+oC10a*yg zDS+fS6YyGxV|9Tom@`&YRb7PO$#jL{W*IoSnM-I!cyD2+12<+rG%v#GRdEzXeJtYK zhXr`v^4ARtv5J*Y_(n~Q2-oZvBa*L3?!S|?<@X)@)a-@&rgM1L=NAC`jkT);+hx;{ zsOCfdHw925aQfN(-btR^AWSptd_~xi`VCsIT1y9~3^2wQWp${Lk$Q7s)J&U~5{=g&*B_;M)3>2~~v#BagOxd1 zuu#jJW}4N=g#TIao(V{cn0APazI_%`@#Hbv^W7rx5k7hJ%HCIS<6_^XmQ+{^1sj8i zq1E0q?a zeZOpc*bZ=>`EE?hk{+GCC1W$;AIf~DwEG`4)hzNyk{(kKFUjrn%i}1+%Lg$dR^N$d zJw&%Tui&FEHPNKsrPhrl*Bhsw@J}DFaMG6*0A$zlIl?y<2FvUQ{@d>g=5Y@;1l+`( z95sXLZP%Z86Mxq@jDiZ)-`xc>=>*8?D^ni}H@1Ck=PjF{sgw+RyqW>kLiIhZuuq9>|E zD8gsK_;qOu*D|Ggq*IA1ka<=azAF<{ppx~6+iiwKd8BT)q!~|z98AH;fUV46EBs3< zw@JOvffO6~*M*3v+@blu)C~XKkQ*CTzZd$>gB&YR1&3ZW$wAb1+$1nZVHjY;$5aR? zW#|h62;Y2T`cfCnjp)*YGS?2< zMz3w7zDC19wiwaXdH68mf%7-;ZcK2%Yky^_f#Fo**-3iF!K+SM+Bj#Ssrg|8hM+;j z4gl*XUh$1TodQ4B@H!nRe1zPP)Z(g0(vIdf{VIv^sey@e>>EdpuMDTbYhJ?FIx}mD zZ(ASbOfUS!c0Je+*|-sSBO;UDYx;EDqQ+`sYBeZtc+T{%>nh{8C#?GwFeR5xGGWdU z)nZ21P>4No?9{NPk_>|>A#?)CVva^n`2BfrwQLLqSSn422N=7jvncDa1YaG{ruKb$ z4sTpY)H5LCY{C=cQ^BHy0(xj~>f!XO2QS=eRX2YPolF;*Nqk(eW>m@_s{!bW(Y+kL zu+UQWgl2mJmj$vT9M_dmEQG7~)+RZkR&_`2Nn*yLu(`WV#4=UWuKin#Pzc-V*`J3p zJNy_ARB>BrU+mzx@5mG8@k0JqnRQ)&#qIDi?h)zPWY5t4vrDokw@9YIX{Y5u^aYSb zEe=9GDYIVtTH$>*94?vfF)K5V-E9uAW+YKo_7&ZugQ&HFo0;Ytb_U|G!%{D;Y%y34 z1^U$(NyiL8w;?Z|HUa~~lSGq1;amg^n*W+@89m%>ZlF(>dfVm7jZyqXN390&CIFv! z6`<{yZ{Y!3H;W)VTNTt(TSWvU3FatlU6bjHa=2jVkj$B#wwn7HXIdyt<3TEYE-41> zS?LplBiI!iDe&0`s8<=t zZr{tWoF3F~?}6eYw&OVix<3gXsyDB&2EO0=<)uPUs^9lGy6N_EvB&uu2Q>>yw}xIf zhdL*Z&Y1C3^8HmCLW3$ApQLrogvGl_Qi6`SXdbvTx##ClbgOwGBd;~ouCfj>%$EDi zlb)-lJ?VkR$Cd2F8-h-%o>DNpAhSUJ%4G%Kz}7`_-D_$L0fgkUjm^{i%+25Q*@2z> z$BaCZ0G=nMg*L7<-~2*Bb)FFMHFTZVb^RxX0l&k&CJ<|;DUJLjs?#-f8Z2yJru9iX z&7XP>NfIWj74`nEe}VVG&s#=@W()e*Tj^GxL;g*W$d?)rwkMm}8*vo0wHK5Bvk9-N zD7*Y(g2^u-0T80ET{wTD?jQ~zy8O0m8k&HCP9frmx*~qq`f0!YL&Kqn9#t(Hr;^Sb zSOq%v9KJyf^0Y-s(;HA6J(Bn51zG0eFlPog7BfjA&DE2rkMSRMdfCW6 zMLOy|p91^QmqA-<91PTr$1(@N z`^vS<7+0~ig}7_(-9!5)KH6Gvh`*$RAz)73YREn%`oE2L^Xj1(e$;)169%68A7{;` zngDEb|DjvY(S!~H2!WQn%}(GU17t(PQ6C=ziALXzzYFw1o{kGWf3eT z>U!aJ!+FR=Mi!{b334yJG%3(|KXdgFzvJd>+7Lo}%!^briVa&GJ;QhFOWC~saxdtv z65+Pbs?oEV*Dm!2&cPr1a4G6ViLnM#jy==z$E=d*Z`|Vqwm~m=&1VHio(*B(;uAC9tqP zZy~3D)0>O{y@Nq> zfpBW|nYEj0$M4q8R*gkU{#?FKn~#Z~M(N@4c*T91P0mAd!H0w?7{PAp=9SKK;qIA5 z5uptRe=%BXog1QJ!YfRI4cI0fZ+^Qguiu`@7>?{{tNMBrnA#SS=(D?Pt%isGBN70> zpEx$1y+7}K*)q)>1t6V-EL+o{VQg$8eem=M0ly zxh(_e+e)B`krM0MpAAS@X&o#?u8a$~?-icZxVp?udWsZ!@7bwbW#OHMAS*LM$K%q%*_ip?8{6X&M?e$B28r&z+=s?#4WBtNEPmWWv>i z9^E>dDI&_TE#C&qVxE2JfA)Qa|5BNCAu(NvNAl+uO%Tj}SQD#ULh2)hua~%RPV(5_ z>1gChgwlb(19lo}Xs8Itz;7u@@Cx9+RtVDI+iJmi1jaL%7D9SO5OW8rXPRr0M8#e} z=w{!Cd^G45HAy>+ywGhpFurOnHU+m~y+J-bd=%u_@{#QmkCWTgR|G=)ORl%>D>^T_D8-SE@l*1y`BP-`>V?F_ z?5gS$gaH(cZmHA;J@*t+1>oa3Tf3yt>O#6OZYXX&fr zmh3z-#_+jL@f8vMQ6?%XFt4N{;%0Srko7NoOlLS-0zKlh*J-Agu@c< zL+A~h!GAH_^Y-714iF0tMvWSJPM+{62MnQ4Ud~a*cz~h_Bko3^(jfiV11yup3X*}8 zwXl2|AkE!fEN69nxJRXnuVF_lMb*0w6G8ULea><|wSJr$+pg5GCy&PNt8#W#->2-lP(>;{Na?Shefi;gmWGI4Aw`MnN2I!Q!EobB zds5{yb-&u`?0_jUFm9}Ux^&q3?cr+w5$nuZPKu!$;j2A@Um9It46Yib&eQ;#n zxGQ*H?UyU{x7aTe0&J-feoh=nxK^)!n%+yTzLr5bC^=5hoG5AB7*ZI%#j?*-)LzUENDxJ`omw$;tq7CxCbqExx7>7L{c(tSxmL ziVc00+d8zep?a}gk)ZdKedx<9$zdV2!j3O!XnR@S7NqNfI z>|q8$7KXX*-6!n}Ss&WKVf%duYh0hGeqAL6LiAT3L;uwrhqW_+pXPf@qGyBrc?@6| z;Q8-xT0$iV$f1gn0^2dr!1%OiLv!)VJF+DG<4cKEUo(Wt?4VnG!PbFcU*NUXVMEt% zoX6#hLj&R>DVm4z9d)>lHoQ-`Zcfr$g^+1#<)6TmG(^*vy>fNS! ziix+ny8gSCn3(-9rX8_=pXRa2@so0n_ANrUE%F-t5s6KpaUIL{*v)wi`2ttrUH%&0 zg851xZajy7{&+_fRDl6j5P^pTJq<$OYFwTkMMd$@7>gZUj%<{J086C(R)E}nX3E>5 z2*C3=4Bqj5gIi{7@6^3h=PFJCgujO>?xK&a4?0_q&wslQM-Lc8;~Bk%JA3WicbsA1 zgpX!7{ZIT9&xO@f59KdWgE&97?_}_Cc_%5=5Y;xT%+|XL_HjS?0W^y~!JWJ~$=PFDa8@;S35J>3u3SDmYO0b!Up%_$&tZJ)z5k-FzrM=5LItolb)TVZv!GCWJGD%RV>sgghHb|*C=3=B6LlC^ z3?nc>cFr0E9uDlG5eE)(z(psII zglPWwRw}YS@NvD&z*_7Aj<^o6RtDzAl0vvfrkg!;Ks>@4Hs|ajvkX$ z5q@R(fqS=lOiws}ll@ZgA= z{<#ox;^y`wl}0ofh*;9Wabw7UkubcA32#0EoTa!_OKrR|MRGc!P=*KE78pc=1Hv%B zVkhq68s-(D1J281P&a)-}*w;gP!~GIXo;*z>66V<8DCm>by2*v_V_ZVC%L zymFN29uQ`12cM(#2hvSo#a}MHl!t$9SEonD5?WUo_AfD;3gAzBlb~PzKbip?S%3?i z;QqDBY}njgwopp^xFj4pyZEXP?^1ewXgzH!2cfJl!_(9LS$jX&KZ%=xtw?msj8IY? z=9cRPRZ8kaGCro&wrOBMZ!_9@Z6eZ^ax=fw``*;eMp<5uHEUnx>%D{PtyH9EH=;9& zNkZ;YqH>cI#3}uzR{yuE_or9me_b{Td>Ayb_AcK0d)|g({Uh>~msY3hGP|^eQ8&@Z zT6c$ZF}$|Y4X)u=WZsxs5D=HO;Tm?B+jOQ8)bS@DxnN*Dv$Fa;<)TjRx1qcYek^?Tq``+@1CW>`DG0IiL+fzwJ7l#6H(oy% z;Yx_{jVeBR^kJ<@QvF{l#n;CB(PTmwNl*;B<0W;wE8iZzW9z`Q?Sl^#VtLDuA%J^k#-80m%4E1xDKcv4X9e{E9 z$wHv)(`vq_fpd;*@>!bMrD&KvB-{-RPMi{=!G{)~;uJN`$hqQzJ|`nnYqV36H2HU5 zxXh>{?DPDSb=m%M-}?C4Ndi(J>OWWp3OhEhxFp`Wn4M25s5M+=NZJskFwIDJh-lz7 z1@6=h1sFe$e3OPXr&)u$7L9dVXFVe~3%lGiuU;^+jY%?S!|Um1737#->iiaYYsHN> zenjmL%J5`{ziDbror7{zJ#LNEyf#4JaJU{eb`WXZaG7%Wniko&`9V5k{qMH!2I`R? zV&dTDCJu1iw4*nld7_jZOYMP-M$ts@KjW`nIm)x(5H7MVA880zusNYI8e(Cdra ztRwxQM-rgdSO_HlQm*N0EZ>nWUDVi?vS7!XDzEVEvOlDCR-k~QfFfxJA|c9Yyl_p2 ziWB(b&X7nxR$zz%2-B25e-Av^4L>l70VEV8W-cU2gSD8}ektn_!wNbWu2rSPL77gp~kP;UF&mCP8-SE+MV= zP-gqy3(-0x?6(y2)ukCnD=kRLtNo0LlS6PQL|}z-0jM%=M5! zM-t?Wr1DJR1bDkR`~u%TdnpB4F2vMHAE34T{L;(iplE_j6!aX6z|h=!&_uAT6PVx@ zmOkeJSVl(#*_@XkX86Bl(qvwgp4^NCAv!J~x&eW0ac8v{gR+D>Uk-m1bc~-LEB!R4 z4AcAW%=vYiLt4)?RLz0uhFPZqp|pdmD`0a@@cTnz0Bj@-BC2>akPO z-Rudb946K9J1Kai#&qkFDT9aGJyqmn&%VMlRMFI+?*d3Az{izg4xs#G`Oj+L>_H-L zd47dT`jxkbEUcuvECcbG=CURmKTPFpOA}zwH#RA(DP!}Y0u;CIMKwKIJbPJqsz8aV z$qZnR;`II%+)pWkZAQqTolzAkSS-MdndzO4^{7XZD&$MjiNr@!-;K^Ew7unk-B--{ zX&U!=ed$5#m=R*mYbXz9$c_NE|60*2cf+eI@?{8&#`D;HC1C`9#`o9R^yVM_*4CVk zS<~+Z+uUvmQg@r;Ac#gx?X<%*l@f^QOF$cN0PA`!z(=SYdMIDpWfp@jawG*1VMt!J zPr*EQp#*#8Q1IlRF`$!5f!!K=oWCyTA zn6)>}w;;IXSnEM4r^!pkLa0ZSZ@agk9_c^DRO$J;yHRLw7F4`>&4Pu->8APj%^*#& zu7D3T3^Cz#fH#y8;Lc5O2N)xyp%~O*xB|llWL$mrRVcw51$u7&zPk0hG*M;jouk5b zOPl*?fX43wZc9htPaOaH8Lp7xLE62tFXOMjqA006Tt=Pbo<*@MSjL_fy*C%&4(qOM^CBGT^>+!wlFz zDg33_aruYpTcH=y3PHZN&KaJINNxy-Z*A~8&(z9Jz;i3i)J8LJPBSY{`~09$lz${% z^;C>_k-^<^0O{%{86;;Sp!tT6D} zhzj%|5p#u-qvMr!2GjCsLOc|p8;;$E%Pe@DJvQKPYQ@i{g#VAFvkZ%>eY^fO!O)FJ zGfGHziol>Kt)z5FOG$SOAc(Yrbc!fScPR`8NJ>bjBAp^#^X&Woz90F*F|haSeVyxE z>$gzO@w9@bg40B2GHacW>0nUSS!Qz<37RN2=Dso=?w&Q=H-Z+2^^nS@8h~{|dz?=+ zNanGZ?iyP`{CD8bt%pF*bw$kILCblae~ zhaU?$4%}URN3|V_id_RXsq`f~<*4#-h4n@w6sD=+>+3!KEB}QsE-XXy9}N*$RgqyK zu=@!m62jD3G2@UxM3Vtj6Ip4VSpP5_epTc4$9L#^oBNWAgx7_u2y@_XxuMN}X96w6 zQTRTBlH05Id$mXIsc<|JCll+Q(yrdtT+xn9HK5iCfbOo8i0kjx9QneAwwUCz_wmnH z&vrf#^K^(hS@RcapHaq%-^(Jn0nlS6ABS^|_MN435^ELb)~2}2$pGj}MQW0UZw02o zVhF~FDi~1>R%ZG=P0!M>tx>OZE^^tMO==-DwQy@D$Mcb*Cdac1NY0Ki}x2JdP6%=pm-lNCW-58{=l6`^Q(DmqOD--1YWwSVSz3I*Pt9d*O zkj2qpAAAGYcHuPW2Q*v;4*uNqcCQhGu$X+n3R-w07G|2ge!MaK&u4C}+tyUr6?cVI zz-^g(Bj1MhZRU!-4aJ{8!TcxUz&JOKCF!qJ#Ch81;%mDrzr_OtYpGG^*vzRgUBdct zIq^G?A>~4m0mRKVOMfs|r;w?XoWG!s&?0_Yx6|Hm8Hp&|537ujzZ)qVODvlFB|yIR z(y72m-F{*3@*#BXkK`V-9x}q=Fk24SwBOI(Hsp?z_1?=6&8+*H2BYiqB5ZH^Y_1sA zUA)v}Hoe8_;2STp1;%Ua-Z1%G|49c5M8^4YEq}sfXdnfk&g`}C@{c#ppZi~YKVvhq znR}wJHc$~XeGqI{EB6)y*{4M0`AE^s*vv3elTzBphn2U2o)VK8v)>0eq)g4>BiH$` zd7FX(LUQZ}Z#Zy780qPEcq*m(+`aC=*r62xqdFry%4^5?K4Z8WENukP7Adb@jb>26 zG@Y4oJq&>75#R^j1y!|3@cf#6m0T>EsN~@u?{T5|yvgxbi8=^VYOr@UcNmY4RFgSf zkoh9K^NI!r2LczLPcY|4n}?n0`JGKA{JQdU=5p|0AcS}mlAliT92-02Rz#6g6xF_IFs$3OnUgUPKp3rR zh!qgH7oWvz^m-UzU$m^1n$5l`CIQNm-*61ndbk(>9v2H_LNJZ4_fK7j;`2!|sYgHd#1MQF12jyGg(=S;jV(7{3Pyfo%1=MLC`)pIH9TgP z6mS1(yDaM987!oH>3KRhHg%)oDPAk=Ba0?NPO)f@W{7bsYT%hJJSC z-6w3!znz<9CcS)nTUB32zc0p?6fq%TUcv0s*{M)O0EJhBVXn^;{r*65wYAsBe2sV_ z@CR{7;PYui8TO7;qybUI#Hn!OF?(m^!6n_NNVR0sx|!Ux%abpE{a4CO#&153O1ty) zWsC@0oefE9i5n75QbeGATND?M^POSw`{f>Z6K19u&@meJxLnSp?(68)jd~jfP*(Vc zsh%1kaWH-$`YCl5oXaTTJPkWo8l6Ai@;k)*Q+*SX#NH@m2BUI67tV)sU`o0`iBH(; z68e5e*uLmsoLEl)%(b@j3w%%xlXT3)>n!V!XDM0U`She=`XDM34)62qPf|#^2|4M; zg?K)IrqYms#bF^+cD6Gz7R$T@@-a&W&1Or5v;tUk$zKfAoh&=7aA>>tC|6A!459&M z^yxQxTq76o0Hqy@p*EQ*r3eIAiLQ>jOXdE2fKqhR70QB+P8lEvZ_wDkF++-o05Nr* zF$RFEven$Z#e?m;lvj*Ni)?$KttnAOCTIy@WzUS|9@$-`>ieQlz$zRxQv?18B=G}W zeY%%8D*g8`jbJZhBkthBx>95Dn&@)MRK%P&Y`6Nc75F}18w;QlW+W853;i4kE?mLV zEp2ApgZx_odw=!eKzP#&qL*5CSQCT0pn1Ye+|VJGLSG^4lR45;!0bFI@2F_0r{`bJ zia2e(mUfNcQ8`GcdVW{k*wkrM&Bogk+oGgBpZ41D<9x_KeTavA4}dR(ixMk+l^}t|{__3K(-2z? z$sevhVIi%93B_bq5CmuYkMk=bF2bBo(UoohBws7ww0Y{0#e@K45-lZ&f{WrJF)Xi( z=r@lD2J}83g(D;ONp6!(pG%|PDb10^N(>u@4^9t*3*D-n_rc5Nj0}nm`jW(agVAaN z=YU*0AB93EcJWUxAx%a_!1x*0Sr`UjAq0|rv%D_c-Ejt5)8uKh;e^H|Jp>@+Px*kaBDpO z9Xwe%+2Pw*u=Mc>B4BIC66F(mko0Xo_@lS{<-k#o5GT=8s0I7%WqVD<7s{@c_s~(G z!loFWYdd8^jUm8$m_K$%Y`c2+|7{(WOdu@B7D{3N>?)~-pquoKFcui_WYx)6h68G7 zpNRley(kdOR}byc&#?NXL{>8mPd~H?AdyLq(X~6UTQiqH~Bv(j3BRH!^F4 zEGB3qRS=`sva*y-;a0gQzLp2}W4LjT@%!d$6+XFYlw51trPJceq6T=wQ~aC3Gex&+ zPZ#o?w%>S(ACE6QB<~388kcIN6#6KA%Q^eWoNw3Vw!^Waj%g-zVpc(t%zMPs)Qz0} z))d9vA#xd79yqv_&ZMK38YmHJlTw;#^h?^MU+5m4O@)exOyN(M)!I`yFtLw3!g63u zaK&>S`oQW%4HpsIC0!Sb2&yj0el(6d^Joe9=JG-ZjaV&lqO(-+Js+J59959PHD?bef7ervdwoqnc!fQr$ zfW5liioN#=cF?{mn-T_$ZhxEeb$E=a>X5=d7KWAFC1f9~au5MMJn)R}rjCiiTQ`vS z1Ze5ijQaaNQR~r-beJ-jM7iH!GsWrSsah{O`fqb!ACtON9UTR z(8=R3;d};nQu)F^LI)RoCdAY?#eFWeg*j$-yEV6+5&>igaoS2ubsZ9?OSO#LkWF}& zBnmmx|DV8+Y;?sHNCwCc6IekiAiD^(_&d0Q@P$!%a~A`gMUuYF5F9|SQ^+|EVuDUT z?`fZSUQ159tr#;txYy>#MhNEFKNR(fN8q7efO-B_Pf@69J0!>kh5MEZ04V@GHi%uc6Z({Y!BsdCRz?ITf}VFfZ}A zc_Z)OqjC>>%df!w^3X4mR|I!E9zyw z5-qJeO53)Ut$4dtqT;Sc^pJ0&wv8(<@~tK#Mqrq0u}*VLZkmRw7({u^j_D+|+?h`v zOL-@hs675yR@rc|C){`ZY|Ej_&E`i98pBe86jek8i)Ne^Y*7?+o{l^wI-mT{hPCWw zGbConJBj~7+2=zD1FDUwI$kUhU;dKn5rR6wz#$mWPL>t{3&M`R6SmT#3Y`vEDDEP% zXmfZ~>?Xh!xU&z%Gvd++sO4Ox?UljRmH~q`I9R_VaRrm~Q&qRl{SFU8id*?F1HtE?x%)%c`Uutk? z!A(LNNlRu0+VdhLeZ0{@+z?wU8d801ck>&ku_wXU7OnN{jU({o57PRDqZ=GqfN|&( zP>N7iYEBV;3rzw5Bj5maMU>+gOGt4<2rC{ktiXT0OSZ8fw5780i58#m2SbXWQA07H z{*XaE|F9})<5SJGlSmar%7@d)F+zqnmq5_MN?T=0*;dc3Xe&RU5+>+L|G=Fi{#D^O zRdQD?9zvUuY@WK6$}%r$sF`EfGsk}oJADIS$}ZU(Q(QiG-k_JI+ zsVpEUtL@@VX`Xfk7>-4Yb&?fjAdfu?U>v>NMa-Djvu|NggcA_m*oLNChhHp>2jMES z9qlG7a~rbaG-mHsn|w|}{C>R)GHcw_8zi(UcrCxin0$}Ek=Ry_ua30N*q#8ykCC*<03UkNTA5db-<1}P$&8?qx#*&=(XKgh zDb<|YM;>15k2_ZioJMkYEwGErrnTolb_ZjFdnt>FZI)t6jmWZ>120Dzgb5g{=e`OW z!TPl9nXM-OQE%jg)(;o|mGSufm4w}ydm5m~K{GiuPMH`NL$tu!0Q%m;(DeT?VZ3({ zhy}{Pz(D#|I<|L~auI6RRx@JL1XTKI7=c-t*jjK)CNn@MCF@?I*$%l9e+BPYY)x0lchu9SMpfhjL=ZihN_rs%~H1yZ7w?x{EQ1v2oNf2_V29R2uLdN#F)&Fban?v4}Ehh$=<5J-Wog`SxjGiswI3aV6<@|xlv ztiq|(tBA@x*iPnU_1tSeIdU0+1DkJug1!508(L}?+BA;Eg3dsx8U@P@S*YKWXd2Nj z3xjMVZ?m-;WIbn6ImoYDNlP6MUv+lrB5coqHvF;-4Spq`Ugakgpd*BbijyiX1H+Hk z>^8KnrcuDb6agm01p+|#rF0gRmX^+RWkF5I^w@kYD2n%l2e$j%`3W_oQQ=gHap)<~ zbH|tw1C4O=VN3LpJj7zi{15|U1w=x!9*4C;S}N0h4(12`0954BEkd0QPCTzN|0sc*V2+FwVxOr? z8qnjEu%HLdKnZYLJpN=$p!MymR`DQamDlU3tkOT)SMCdk%=*w*(x_06Yp_%qy}&St z;jPIom)fKehfuEd3cGT2(sKDO+S(nXtqKjC@)a&bUu-gJ;jN6@Z^o*Rc}_=Q>hMmO5}dp}~d%tM^}+Tp*+8 z#M%>=6>U|r=O?m5M%uq3!;H2Z19pZ;%}GrbDiV}Ih^+0z7XWaTP2P3;Ztxn4K`nfA>6K(|qqd?Uc2DuBi z7gh;@wseO=>;)5eOOA$Nf7CTLDyH?!y_8#@eewMEycEFkK}tp05qwzV!|+0~eDS>>E;Ko3Vt{f{?;I%8A7l-C#OJa=}p zmaII2weE5NCgg&?inpcQpDDAm^;r6}3r5_^eFdhvw zo3QWw?|l-yHlH`R9ZfMcpn;TVw*d>LxnuG^d&vdkq-c!atsd`oYWe+YMo<{{4%dIM zyVE77-MaYD-nJ&5zFg(t*e zuOhdKI~}n)%9O(9aM(WhL$XZxYlEWSh)M2o{pr^?mm0EQ`QAW4rnALl+6TYGkO_LK z`63+wB2C{{6|X#$1MaV8y@4)dEFOizVog^#m(>=Brlw9A7{EiwCWfg&LLYOmo|zif z_XT=)VEQ1F60}aB2to+r!k=>gSbVW8!-ztaQac#$3Wd|x*7%<$dWf(K!kuI(pmeRw&RaA1PJ{&y2UR2 z!$21d5)sxLowiRnq3Y=HMOEg+>kTunIxV2PU?b$J`AGU$p#eiw-2~r@V$f9HA&fAw zdnr=F1+bgl;nn{jfKc5);D7-y0_|Hc;T$Xo!zQ0lP8v2>VwqRW$beBNa_ke|Dz|b3 zc5#Rri^ergx@}|_Zk==SdL^7Jr?GE3`>PI1)5)_W}&AAs--CGn2a8}B6WeQ1!V^%U$DI5 z(d)u4=d0&YjKNwje<>Avj4|pVthg9KO0p}&OzD{dPHH?6+}K;!B3&hc!1qP1kgszk zKi|IBu1FsO(Aq{@-U-%o9>J!|&~5^+)&98?`dctPdeLM@*3WibY)69@K*oPYpm%W& zzpS5bZy6SnsrYROBw zoz(i@ zY~cxMwt{t7huA4yU!zj=dR(2Zt-jcVEnvBS0|AV6#lS8zrS_LZ(IMFsV)(+Xgzn^l zZf7%dMTAx11U$7H^Mr&8s77n&-#s6?BNnPfiSL_j3+SEQ=xUxN-?l2mhTgrF*?GZ_ zJ===@)G39v9n}>O4`b}>yv8|KMyA9+?C29o3<$LB_2+$=OA)#7c&jN zempLf!XvmQU>sG=xypV+g%0exC|3JX)Hh48y|lfoNRq654r<5?1Yx^U&~Qq`1gu37 zXcTw}h3(7B%P-z){QB$W^RAT7BBER8U5v0Zi#H3U#u{p8s0*ZGX%!8f-K2l+xzj3JNmOBoCxR;_zz z-ce*U*jM5yJ^`&co#peGd45B^(=A0zGx|ZN?Y8*RP5}&Lr_AIbSOx!)xTus|`}VEV z6CoE+ibRvfl38meX5;_R6kz=8V^|YpVXQ zVEcSTh~XVk+ag#2aXm<$JP}2tWpj}-E5GdNBcE$rNGIBUb=zIIW^eHDiAbnIAIVc@ zb_GQ*Tm!*>#scc7&3QZ$>P0i00AX^U1J2mtq675Maj$y7%jAMM>GI9gYY=%{-JBOE(P zSwMKs1{Fbw;fOi}SY9p% ztgXWb_h%uyhV~UZgF*}HDV}d!pqc_hrB9N1bB3E1z52w;Oq_LKLUxco_E}sBA%+_$ z!_Jhjja>ttPiBtpG=B0f-0-(={hu*m^`_vz``B+4is>N*-sBaOBYYyJ+Fus(H*PUn zQZbgX8?r?(LzMfk>v3tPvQ7-()%#8O?)8W%9^8w7uRa!xH_6=lWL_%$^*A9tVQ>8L z>&b=kOg|xgBUAmEEG*=_li=S7F>o|>gup)=m8fd^KRTyjEay)+p(QMrv-|ACU5Kv5 z&0**C8S4!DCv0uE#^zh)-X>RDygWz$Aho{zw9x0ZaTWCZ4wqMg$~3MVkDhc4aEeaH zJg+a*JKs9cqUu-p!QD00DI6D7{fnQS-{Ow8V7{drUh2DSX8z;|-+Z-F+tr(zjzQgN zudg71ZCR%Fbwq)aXH@Hq{|r=|A^P@#R1mGQqNpz^e>L6YCSVzx^RkPfEKuEZe5E_){6hHU@bk9No1CnrxF@G};~WF{nwe#!gM?G2_(2_%S)M(In7$H$37c3DAUt zg{hvfHE7%nJ?nDE*ode(Ya=qgqVEWh%!&1$pDAGlKy2;4Tr0=PCM z8U^coE`(cEnc1*BZkxUsB=I}1|FS)HCVlVvrbOSTka94VsPz{}R~ECSjD8Ju5YKkG z7PC@EFL(sinkCpvC(#So^YhtC8_)OAV7(zx6pkssS_%Ur3(0T?+oQ~jeW5eby0Dmb z5iLf*$Hus|ViY$`RvHuk`U@`_~Dng5uYwuXrP|^-kN~_-64Dp$Fkego(8k~n)XS$fed*w1~BT&JVt;a*E)z2mg zICmJMr`}%q3x77fmtih&lDkbcQ2?wC>9N%elj`IO-$6+h4|7*!=?qlxY;KY${=@(Z zxHrKQAIMj*6M)w=01JM?2fOa<;4%0v>jZ35i~9??lg#m1VBOS@06WblSc2aMoK-6PyXEYVaQ8FYM> zP)IDp@*xP{$%FfSePS}Cn%FzES8K&_NDEz~ zV0(FVgG1*~KJ?>1rv&GesiFIy@+Pw4#&zjt2Cbc%azgBDL(3|kpgv6!N>|wvpX$u_ zEl`a!Mtp%xh7?_7pihh3-Do|knZDZ7$fN^^IwXhvt~e%@p`5eCpwdtn%Sr-sjk+>N z)+sjBp$VwmfCIEIL^A7r40+n%C3b8+7sf6C%D142s2T$`oosMY=pxqhLK~W<(L{5I zX*bwilG}u|Ap&UYYE~Ngh{$hovrB1nL0&&vLYT{akBV4puH13^Ny8`_F|L;obG<83 ziptSWNn#^^FQ8MUzEQGd?9YU5|K-S=t$b=seQ0xT=|jc4?S2C%*WoEQuSS z7x-^h;S{Y^c)9Q00wQ+-wn7|wg=`6$Hl)s616ETYkoo!EjyCka{4ZY}3MCS7N!Jcl z+D}I-NfaPXyQz>lq1b?dU9UNGqI@Ix0!CHZ#`Zur&68u#m-=@|DS!KP-e#YDmhL=E9efzyVS(71y{@*zRkD(3f`BmRhfz;fF3s;;C@f4Y=;LT zA-5b94G^~sjfSr`#81+CV1hzxQSRf#4s$TxUK__8eyU` zmJ{ug zf!HC$^GOfNNAW9LH?Uv5ynQL3DWw{wWU6TIyhaKY5Uk_hf1yDD?P_*H zfG+0(WY!vn47tzlrv-3E7&VO4MSQpRbjz%Z09X>chlzQFRq1wOJl(hBeqGosx1`0U zZ);IizoiZO0;?V*Un{9Ot$LRpE!EJoy4d#Zq(kx>>L)!}FFVga6X_$b{>ntP(o>&m zrcVjR@eN^bZ*`uYqTG9lX(mn-N@T7(a_y(eCr*XMXaq7R(5F-s`$tuLDqS4rf9}yk zy@9pMu7Ub=rYk@0^(Yg7%S;&9tAnvXbFG&$f=(GgGwYP~um76FtixfuD%n!5Ee{|< zwvhR=m;rO2(GgoEA-<%?jUE@i0!>!{aC?)p@B7lUH%Z$*jQ!vFOdQ#z+vS*4XtRJz zTs7`}4KKaC%W-h|356_umGTzkrs-Hv7CSZ~mGm4=`bDQc7~W42r)T$HbgKSG@lvu0 zpF!J|!`qNDr)0|OIISQPRWIh=YXz(85Id{2(JXk5J}`fo&=hikkTy$d2>5|mB^vOy zf>n9nj?bkLiwv_fs4D)W-e(?ft_Z4dx#tIk`sH`)EzK(_c*d@e70_tL!hi-ozo($w zwAOd?>x+`vgbtZ@M50t-GABPp<<6hKGGpnV=a#q;+ z+t$Fn&#Jf(;fVV?=6>>u{sT~_jVJ5HK(p=y2R$Y!0~P7{*5|&(7Dn-IkoX|p!!4!D zn7v~Bsxh}McvDIa4W;*X?5^~tY3BP^@u*U^6P`zvVcYFMa=!{a)>W7U_}(S5LI7;_ zx+){kJ~MoPL;;P{rWI4P@$~?8Y0DI?1aoLIF zDt}x;mcX;>Lv^hA$M5D&o6TK;_<2tsMgN{HR%ybZr*Lq*?ei^zxyNF0;@cVhAU*y^rKe8@(|HM{CLJvJVrx{?45LOW9GY^^%lJ=`zWaRGKGUaGhNx z0t{RmJ56^xe!P9F55QX?mC`Lp8J5lu-s?UKF3Ti`Qh|>Xbgsd9R+`8Q96)?3_#<-( zwVnVN*yYjChwnZX{#poZwGBF8F7`Q$s3PJfiw|pv7 z2X7X;7{1r@f8TaPeu+cg!Ms@eL!u@WD6=Q4J50Eb*q9vEUz*Do7vr(7u1NcK+EYE^ zCd0;V_VIUO=wtE>cJH?@wzwT>I{Tb%Xe%mhN5a4NqF!l=!OOZ*GR=K_twCdl!ShsO3zV=Ia{I>hxoyzSzP9ls0>$riQ zQj|SjNhu03WmcCXp>O}}%Q+`V{w#%k)+I=V9V*G;zdIv_5+$Uj8+JH0IwzPYxFGnK z#dD_UuEyST3s^Wr+~VDUyK77XHhR=Uf%R#+#u^Q%#p7k>VJ5R@8XSpDVXq}Otn2J&Z_u5!uwVY1ZF?s1@c#KlXrsd>OX_o0T>!i|906xXHMH#z zbuK@;F99~|U0^kjWoaMEN)m96R9=db{SI_IOW)nu!Dc$Q@6@q)z3_F|fNK)ml($zqVoj2o!3&Wz`$fhre_SLElH!b!9M=Z4LtiT4^AIg4GycTepRAp0+Abg0#w@ z5X#mPSP5zXbq@L$_LV;P2oy%e35F&5qgJ=S)dzrB@a^iavP1Vo-G-*Lo8II@0$?cd zn91eKr+mnXE^sv+9GV)6&H=TY?$<^Uz8Cka*K{)5>N! z`g9n1Ro3^UW+9E-YK6$v>aXU=SY0b!L|}(D3SC??i2VM`Ycc29+>;eA3Sc9$aeLF* zFSQI##0(}N8TA8%)62_I<$IoQa+Ht|6EN(1Jh2xve>NAk-_f2V3+3T$hB(2(;zQp5 zRQ+gZF|_>}iKDcxK`!V)E1w(n$b}K>d~$%BKkuJEe+JbinXAr5`79!I=0bHNWV)>& z62w4*ag~5wx6u7&=XF7?q@73aF9NhfOkZLG^;X^(C@fs%r;L`m4k!E5%P`TknK>`7 z!wivW?-#R`e!=tvY2V0I1`GQeD#ME+MDp!Q2Gh`Q2ULzv1f5(xZeZWL=kS2hj*V zG-&7eMg#ia{NaP4CFR~aZDd6?U`1m6pL6sI(J_@sMh^h}B70e!vIxv-m7?G?wv`ly zEYQGFj1o+ID2Lx=28U^c@XlK#5TfTS6m5m`o6V-RZ;n))*=(_t716!En>|NZIp;1v zA2e2fg`Wr`8ALMafsX`sUz$9T_ye88_XZa1$Q#q~a~fGNAO^JI&^I`-p`0?QHirz= zES!De^Btmj_;p)aLA<|eBgGQ_*LKQg&xrS+?jkpGEHwI*Zw?aRVybFJZEkQm7(&)}MHZ9(|9$MR{VqD6P_spG^ z_oX<5Jpw!zkce^ND0X_YOaXQAIE8mUnN*-wDO5;N^4T@nhgA|tyk_E+%7;{aoXei# zo}QyyT@Difoqv(@XX9Y%x6h~%kMnQ|0SUku!nwGRAPx7x^|A}vycQ0yFQl-MikigO*E~?@-w{Xu$G|}KeT7C!`!b6l(Q7s(Q<#P5y;+^J!LxwMi(NA4u>LHFx zrfwSx8Yn)iN_^OFS(dKZ`&tuvZVN}sK4Jh&Mlscw`Af8c@@w{n`11buuUpN_TvVcU zf_U{_BO9{jm%3n5bf6vKpS$y)ah5ysaz3Y4PxW99SzT-DDoo1hh zByn+19X^N%9mDs!<_{sKo%yg_8CPIWZwK>f0F$e2t1!?PWm1#Iz+!(DX3LLzN>uQd z7FFHF+{%0j*NS8(QS?39S=lfeekKG;tHz^fUYo=M>$01MpI$=o#xH!Iozy{1_$|9r z<}YHsAXVu_k@!p7yc+@rz^VfJM<5{`oFYD})TW8Es`6CQ=uovYWGD68_U9Le^P0`c zMBfcUog=3u1Et0;E@w zb+OD9zn4Cg^IbCC#a5_MvL>uP#m=Zj=Yc3w(9&KBN$ zPAA&;pqi{?k+zZ{FaE>|UwuM)7(mwf@+<&?-vam#$*&e$-?If z%~zT?j)LB~S$PW72sK>v9yLeW&HNb`XOIAx2G_->TvNtyJ1(nK{lwgzb#Hu{GYN62 zMB41hgy;G3U%Vk1h=KO;Z@f`}DjoPUcVAK#k{3|ChK??9Rfu?qU%{VOE~$L*;k|X! z@Z)A89SA?U{vGU z>It{tOIekQ+in(ZQnw<=sIjH^IX%aGhDLJuY%Uq-_tldw%=j|-IE@eL@v{}u>Lk{HGL;h3(P*d@NP?w8mP z*C~!=>&b`TW}qEs1{Uoi@sn9W=rt#8X8kIt}nB@^g}HZr|jW-SG-{9 zhUv`Y2dSfuC1;Korz-wt&hl#}@5+l`%b)pvL7y8oaoXZN6xKJiZU}jqk4&&D6d|!& zS|eltGjzE-q22Jcc~aT;ejfvw8se1HS~fCr9wI9+?*D73Ug53{h|?{6IWOy1tcT0k2e$X(Wvz3)m` zt37V4y>;~duqUxP>G^h#O~uA!?)F$0n#_OODsK9y_=?-vs&T06_$D51i>JIAb&BV7 zOb95ndApd#lO$U9d^5cDpf=NPWl{FqVVUr>P92E#3bn&N+NRT_xlJ8=Vnz#Oat zpllBANr3G?Ra}s;xf>~nLSf2ayTX^>+K&}0EIzf&7w@j71^(&rASD8+JV0Ch=AUY1 zVoYV0dBMJmYn4@a@e-xmKb71tG9VFeK_-t?60kneQ5$At^p4?r+C_#yYd35J<48vj zJ!4R#7h6fXOLP_+3L77|HLUc%Vg&(kXNtksYnGI#+8h3R52 z?1JWFZTq!JGATz7V~PPD!sKzM9lsBOrFuyL>K^JOzEIE2Jt@GdUj-ZFwVDGMAPE;^ zgjw69UJ=@PWd;3#x@y{$FYc3d!`1{=vBBrVY%b_uGlAp!depZFo0QPDpj zpU+EVuXFR;fVTatoh)YukV|~-(+JVxtHABSh9lC~N!Q1j#2V8G7BU|iJ!qW6$CREA zaQjuaZ4~`|a^KuraKqAvDu7j-bhAP}>R(kSByAbsg>NMP5an>b-L=WkYU3|X1}vUM z9{3Pi3JuEJKD*r$N(n;z?*%5Up5}i1wCnn zcrd>+&9`?mPE6nh;i{0e>XYl|$pIzP-%q16yHIhQCV^oJsrr}PBzX0U8!eM&Rmh&V z(YoKp$@AKjKcFwWjKV)=a{;_!&<-Q#xKA;0XXoX_!~HtCRAn8(_kO<8_I|#;Hn-1; zZ0?MVyitwi{K#8irIwk-^L$n}?j_G1go?kZ2tjhejk_DxtqB)UfoLdX_jKNAX{zf7r4cd|0z{Hod=snPeX=$)3B zG?UxI%jLUNU`mQ;UXMOrJ?GDfrv|CaMPs7FZY4LCQ+uJX;OR?L$@iRib1Q|Zd;y#0 zsjf>viPa4R1-@Bvys60|JT}Q4H*I7ItB=GJy$i?W$zg(1;f@P$7n2q`pU|$-!j?YF zyzm+y{qVuYC!PqaR-l3;`pJPtBgS~25t;{QId)ZzD?kko_ ztMr=o6_2c(T|Z{7%AFjwW>PU;dP$RR<8krjua3AQMc><0$hNJH3ik7Fh~a=y$$Y(P z3dek(?;KPsQ;xCZ!f0hywNmkuT1KL%L2SHnrvp^$=70Rm zN@#3;f;vinKLNlX0b?dph7v*;_{*ouW< z+iAI-dL;Je#Jn8wr0FnRH6KJD6gQU3rKO)!k1@F8qqyuDcnP*Va)1Jw5{jbBbpK=~rhrn-u@e<1C!B znNUBze0_^KY`5`N=+_XVA~Wt&^Ow8yyCQavj}a-}eYbEUi2d=jl;Stf9X27QUri^= zW?jE*UnTd2QEu|lM>QLXSm~^;H)KnA6$~CaOIR$zV+AM7&LsVYgy}|d$CjKmUp9v9 zY^Uys63oAoxnVwAAoP}i%zy2i5{~us8ILqMi%$VODFkL0m6pms{(PlUyDF5mmoFsV z`fOm3;EFzVs-6>8iP)(s!#cUcOKcrc5O*@*7=%M}M0p%u;j4K!WLSd2MObP)Jg)&X zuysli{VT9LWhz)zBiK7KOaQ0~rP4r-eJ}*X5GxX502mOKvF$SagcXliiU*C+W$Ou^ z_0{tmp1nSqD*;00qO9#f{SwPUdv;sjg`RiQapJGUPF?eI-*i08U$V$J8g7IsKTrHw z^bo%naE&144%iF{DV>zpCI$^R@4S!SP#^wGpIZn=XJ-clX8wEEY7wJ&Xui`x^IXZ0 zooinQm8?r5xUW8e0q9kknzmt1yVl^nocHG|@038n=pxU%z3&?R?OOMH*k}OfBiSRA zy=#k5>@lcw>egIJmL;0oqlF4V`X-~jeXX^QO_B9h9il~u5cJ)5-K9BhxFs% zJW7|yzQZsym8fuxa5Q!=45pTXyHJ#2$8M}Sb&MAql)moFceXjUCUC_E5?^Z3Af-Ro za^zf{uz1;1 zyqux|dn`PF^TllX#m*-I3)O$V)573n zvUzBqpg`9ePPgk0!fj#UlY)F1V)pu0E+w%v0vI0wqcC-YmotaGDaqFsZr$Z|s=JH+ zp{K}S95XJ-`Mc_oJl(8HL46;BOYYQ+xAY3C| z=}yqkG8!LT&~2hJM(+9#zge|g&qKVdRLW(;sw(@fl9uZl{WM%X^Eh$`Sqnbm$vV8r zFRP^mU(;c%{M%_%-745upn_O8=6;*IH*?SvEolXLKcCGB)3>K*P7&Zxa+`zKIECB1 z;z)fleJ=nmz%kM}9C;0=5I!dQPc($$a_6$CNQP~E`o;L3WW)td91tTy1T5sX@=BBb z?kon}bQp9Y`}g#6K%IsUhPzK0YM5^`y^*pg5qci~zHat$w8|8Sn6YwTN*>k^sJh_m z$2+2*O^eci!G~CEp0rGt`k#*xB$%qjIW;s)J^FL%p!!xl`G#g1oTwpi$p8&M?Va)e zQDpzcz57xj!$JMVC+qdSd2!b#bQ$tHJ2aaTK@WGtSos^X-=>dc_833Oc%3gSncX!Y z;T)xGRX8!YYBMovHjFDQEFCk-6G3 z>EE$M5}rr2)9ZPq*6h_?c@I`PE`eG+&V|SR=xik|i1PB{d%)LunLr`-g$*-Ca?7r+ zHyg83PD63am5?T#r5>mRLVTAE~*$#9<_#02Y6yJ#|ED!jWn~VdVIjfio#KI zXMTJ-iU7~0%yC8rX# zBW_5P`)Qbpmxwj4MqFDG8_4ow-lQj|OA~u^uzmHAMe>TxzXb$378M8_XVFWWN+1rO zBr~~@Q8LNIaA=;TRv+T$ci~uF_gnVy%Q|!V$G#_(`L*SdgHmpW6m&pnkbGu4yJ^CD zC+w5f+I!2e^&rp}>9VJ+V^bsK&ZT9qfmbUzNS)6`f@$sdoXjh_-U^<3!-N?+I!MpC zRoU3-bz-VO-@22ptmN=lMD8Er_LVWbs-S^9-j^(cLJXshpbK5W@&a{a^q|aOgdLpX zKI`t&2Sk`_yGr=3yR3jisdhbYKI_2dT~RG&!!&AH`A)jzzV&kNzJ=XW`KF|8?=t0vOVubT>#NT?0Y7Q|T7z zZWy76bV`HrC8WC~Mt6uvBM8#nAhBn^=U*7?y?f7nopV$U11xW-gR0-$hBUT%R{9`}(-SC>+dQL!wB z7HgNQ!i32~9NF6U*=?SgRllUzh`YG+FrVwY0PFcM_rK1uo8+-Y?WBXz!Mr(wadp+y z0;6b47XJ8emt&Fs5Zc&p8Q?-@x9>p%J>5Dww&MWSu%wt9aor^qrW)Tq7Z>)Frk;)t zXXBeKLQCqa!_K|J@r`La@K%hGGhe$=kt)+kM4(NzENsW{^|fDXnEI%jz@gy2Fn zcO82?KOIMHGl&&;cU8sNM-c8%ccfPT2|Shju|Foe2=!V7>MU69G(77UQJ z0JGmYL6McFrl#j4ug!I4KVg*5;MX4+2qsm8#WwL`n+8W86iZ8q2r1{E63{u)=55=f zHm}f8L`$oVVkAkGB4zQ>Bgs{RFFtnjNCJAGsXAtDjiR0M#fuxq;-DQTJ3Obvr{i`I zS`d)AX8R}d%z*rN%tDt*lhlYf79C{v6c=^nkiFNWHaa475 z7n%9w+=NHMA3?+3ebCe@^&o;n{f=t%IFKM5PnUkKte*Q$U8l)25I-U^)tY4W%SoQ{ zP#)=u1jefNUYWbeXY9XzQQ%^o0vi6m%>&038w0Bp*U=V|qoR4DlNOjC;{kGAN7%=W zO87+fxNi&>>S%E6-*;M}{iA;!uLDbP?SXgI;}XrN z5%k@WH!Q7LhU#kli{^1Vyq_1(MJyepI_4b~+lYc~E{6YWoWoX5|H*!ZbC7vE7#WUiqEe&_Yx z$zKF*h!OM=do6T|tj0`Y*ibi;;3O0gAF}^RQ8GSC_tXig{*O~hLR@^S?-`Jfy5H_% zPY9Mh5zzoyjQ$D+!#5UC-zFgiQ7nK585FMfzxYgv8-DQG^SHP;5;bshz4~2c%6DR* zicZYZ5?=ULzx(s|KH{JV`^!s*fc2g` z>OcoDx%YQGY+z=%(9SJ&l#)>iZdbaOMCSB2gX@DwKYl8xG+e&uON{ASad*nGMM8kw zjF#}g8&EPZwJ2kwNyN?oU*rn*+uy2|e>kw0x^WG3fC#`A#bK+E#)t}(ugkUetqfu< z-xL_AKhn!1@hup*OI5}!^4`i%ncXMt+Rh5SY2-MN5fkU(qpbhLOble@&fR|GMDVcu zxxzLIeIfu{D+7ISYvfwHvu;DItz(N_n0edRjq1oePVn0s%u?-weaZqI19a?28TbQ; z860Gbv|zy~z(@Gg6M8*`XTmG=;8$R6ETjam36^^Tr>VI+B~#!3#J#2GneU5Oitp5Q z+Iyv>JPuIg-_;aE^)@JiZ@RmzD8Upgz==3i;5rdtuYn?<#MxNt8mU>kCfk;}d~O2a zbV~0iF6h@dSe3tmzG6m&XMu2`^%!`K3_S?x^YduXie%Jr5|I#WCLAFo2(qY;BbIli z66-Y)O{DfXbXtB6oDBGSzXY>0*N`b8Lchb6(aHnJpB5Z?C*J-T3<^g{5Zz}tQr<6h zwGIHUBXA$(lzBg!I2QV=+Ar0V`+dUYfE-8^3(nyCFQF_7czn6+jW{Z*ZtVQ`?{^ zT@pA>IZ%WCgd`sg!G1u{?Wp-9RX&h7NCR>+_V>T-%7VJk9ZJ}Z3~p+J5N*bGD~v+s zog2att9kb0$DzU!{!@kj0%wH#Szk6h;M9j*hw6Bykl!LFfl_RG2sTwaM&%bH!JXFM zFW(qk^|EZ~^iUu3HqZ&DSx_lt}D0ot$Rm9BBVHDF|1Nz zYSB=;3WdhyYqJ2boY~JJ1+FKv44Uhn$68?I@bai>diE0_U3Kb|_AQmFr?`0ReoL)7 zjZNzyPV)3)g0=odO26I8VORfpnct3@P=S@B{AKdT7A@abamo)qD%v%6T5GEW%78`C z47Ju{`adviV2TRCn$FdGGHTS&KA46Hi-Zv(U|RhNEX_U%Q^X)j6VQ@-dwmQ7xGXjP zphdIRND3{$cVi8Y=%@lmOqW8SbW;+5T{sf#~w!oo9R7iiz}f(yi(W&kMSS zIKD+QkRUE(&wx8Wn|O)9L#k z;9~tg_{aW>F6v3uc(ruS+ zM5vhk=CM@}wQM*shJMdHB4y+6N4a=iboQH59rVG{ZNj?E7bI#Px?)2aEfNleZ=Kij zEQw_Xf=pEE-J?+dac?|I~=5u%F%Zco}jB)6ocr9F%dWW)oBOXz)-Yp)GR z1PZVV6zD*15C8kTT-Hpm5%_!CP$DpV1n4~A06N>e_h*k6HY=3pefnqFWoihaKwy^+ zr~t=>@@^;GIPMrsB15lAIKCvmwQXa7(Xh$mPErPLREIsek(Lh7ZA(1${NvichzZJ4 zjF5R*cLxugYFbAA*y;o zZ@Vr&>)m!$yt}&;B8f$Tu;H{5yV!2_NdxacTxTuT7c$MWPLzDu`JUOU;!5?XBBmVw z#A7cyM-V%JZSiM-+aTrW`%lBgf4x}w0)`rSs-z(Qzo`p3g5wok8@_faP_p+AtfQ{g zP{f3S3b5nnCT&HxS1@jLupCPQc6z1+Mp!r?lKo!5yqIxE!8xBWPGZ5zV>h%QxVS!o z81%y+Ty$$eqlQpR02%%S1E)1V~2ZUfOZ^ZxWx=fIj@aNUOVhJT+YH>|C2QZrEcOf0i| zd)Dr5d0|!Xp1RvwZ);6-{j_i}-Y0pDAEGcpdg|5^v}eL7p+rVXDs5q{)&#P_M)du+ zk`9!DEb_6Cmn3MY;jQgcANma_g2t;Fx+Z)GK4}#B?Ppyx6jIH6wA04^FdTaS6xWF} z_@w|b;B?rl&}=v5_2cl$#OdcVVaAkgv;5I*g`^N!I?O+bH**A8RQ9BQoKzrusAGFp zbP(??B_?6Fb4z-FR0P_;AWgQE!sXdbWxK=-FghPMUFTcjhiF+PH_?Q2L8Sp6PveqI z&^|gr>lHOKM>wi8 z@gCUd1!HDqVQY44=MuM}6t+jAtD**DUV*F#%6rdclNg2g&+L95#)NZKQ|35NjU4J_ zgEw;%4Z|yWX$5qbVv}OMIEmAlzZD8Z+}xOiF9pSJa}+cy1M)wxiFvK?hYVcHU~gw9 z`{GF@3*(9Z@_#Z0LvL~vBHo(}wlZrxMEnhS{`xn! zPO**!zXh?LFh130R=aGCcK6*)XzAq>=vhF!<`?*HL%wIB_9gHM)4S_aiO=}GrwOO- z=P#Z2sFCEEm{N9cPE5%xCp|6s2crX}$o%rxQ0MJ1+637u^z@eoU3J!!qrj2YLD z_9kvIeO+tSuNBRYGO$u72FlR+qliO2dux5F`o)`Hyr3O4hM9Yb(gW2zE1=PvdoqQ8 z#ofGY6-Sl>UK6IeH&)A+exR#1{qDo>zMnH{sJ`NA-W^#C1q43vkbgNP4S#JjFu!#D zn)ih53e>&Pax6vLo(}r1NO{s4&PVS`QTtJ?gc3FU-{3ai(Dr^b{W;w&)pAt+bKDp5 z(g#=sP7wBP@c7cJdh|GolqB1+a-V;3?3~hYcvf3)1#ceHi}PYmwsTEl&D9B_$GT z&eP+9UR!eB$@~lnu?hI`sA+msJ|#hJ6a5_IHu7^l=uYIAI7b&L#CTxn3ax zZFG1p=t3GeOYPiQ!w?Qy#PCV)0=ou5+_9KcECh_a`wR#x1JCdxiNNgdzX$*<;I>uH zk13#RDO;dG9*LJ98R`v_&7kVi7}9R2%IjK=x!_x z*kDiOu0Q_d!bac*CM5%MDr@!FVV@>B4{s1FRhREd3sk;n6`U7E1XE8w?{VZCwCYT| z?)5K=l$vE!S+ow;iC~U0+}cT|Qp&Il`cH~xZuGaPdqw9you^0kPJbFVFS+qbJS@<=sn7` zBS6SMeZ-14eS)}?f_?(7Du1iW z!PhWQ(wQRoAOtu+%og-7@LLH+G$3~6&EMk_M@o+>g}L25VS!*=-~cr(&u%co1()T> zLO@{sY*CVKhblGT4xInAKmaL7nDdtd!V2sTA%4jL71IJ?LSiZE!!w5CUve*ps+OIW zGC$!{&DVB`NU4h^?7Aid$HJ250FS(%iz-=_ zjpxcShRQz2N6N%uwwd5?Mr$V9Q%f0rRsOPR8LmiFR_W%XWoetlXz)2asE`h66Y(aH zKd0hTY3W*}-1O6P>l_#%?oZN+fUb{$VZl|HkHE)(Sd5u~A8RsZO;L^_!GY*Ya&5wX z%J=v+%0e5V$q2RR65Q=*#@>Ld*XRSRz>O=pxQxk$ojG09b^kml>&404Jxi zl|3CO;yI}1e-Ao3mQWeJY+CrD-}+H@B_e=HG5$W3*SvGtg}9}WH()gtw-){0YW!a2*1K8Wg}gt{^H%i%H0Go_KMnnfJwJPFpt^Wu?|myz}Q_?3XYyd+_`V9sp z6ujpus{o9HYZJWCaRag}P_QlBf)*w(U{=SwZuPkcypto_AIk}Gi=7sMt3x-NR<+^3 zK+MpfE};BL;=^7@Lgsa!Bw-mMKU&^XKdIH@@oVerrct(u%*A76-%JxuZAuK4GkIko zHBuf1V17&o3b3FdgI__KSy$V$Y@_7vliIq{K4IrL9*!94yX>H;JtgsUTujh9o(b=N zhE#STp-`nisJnBpGBOeEp3HwIO*qS5RB>};b^hbgW;6WFx#4pV{C!ps8%yoqmhICC zfDG#NVfFJqBUY#x#_~;*1Th@4V|VDMEB%)B-M&6HBA&9&0E0iuf0{8a{9SXpPODh* ze?UQk!&J4KXZiUqM-*j`;r{0Be;pH8%$+Z8$Rg+1;)w<$hgxVit)<%nA6KyAxlzrzxOV+*;r&ODN) zwtcT(JY5DfWJjVk$TTW_pv~TZst<;AL8BVLw*&)T^xh3~YO1jp7s%R2-z4aMpO+GX zbk%=_%Vab4tGiiuY7bZpI*O~UOka;}xz~?TV&88b|2ghSqv@83L;1X2MG-5Z7+7%Y zQItk}5wG&0LXW*>sw{{A`$x7?YIMkw%)deTktGd%-z*w2!3t_hZom{9DSTLIBH-5c z&6h?w!}8BM$vKe(=q;v9fXm>7CcTt98J0xAkn|QdeIVjD;BqRGE)N$}6*qzJ6xf9! z%xU2paAJ@sR?QH`TlyXQj{7mgj0cZM_!;6j`Ux=&6=5EE1SVFH7pBlTTaExSI0oe zMDY_cDwCz+y)oGi_!DMUK^8t37P}vvIF&$zkOsk-41^x~Zui6SQgvogTP)NP%8Pq! zT`oSa!3dUjYM2E^al1%b+D z*k|Jc71YFv-1aevOT<1AA$+X6&*33M`$8vd6vlBLuq-M?SRnIo&IR3|6x^F~P_bIK zyYb^(Ik52T+sb|D&l<@|s8)(~FPVpWcD&oh2B^+%x1JcYef2OP%N>dwlv~Y{vdpYZ zSNiauV|1UeDX*)r^72H!jQQ!fYsfiD2fXH5^oV*cd^N*-u}yJ{#Pz$j8?ZJj-NdT@$s&Au4Md%5wck%Dr_j{{0XOR zIV;KM69uu~w4`ha;pT16JzB43l{=%R>q$J*6Pqaol&d&?j-l*oN zFxr#u7YG$_OnD&Uvbs$~2<3s;@g`|@oD&7t>|m(lk*i7c*s{7|Bab+ZpT9%R&%|<1SDo-4TMG($rN~IKZrR*c zT^|Mk9pj1a2h`sy%#=Ro$^53C%h1m>Y)Pc9t@hxjz|wnw(ARb*_*1LS6s302jfsi7 z<+CA93OByMf=by|qX!;Z`$S^Xd+X}v*S|@$A$)-fTIut+T<$mnJFd#Ta29VROg)Gh zS(qOisH?FcJ;O^8HYK09hiHfdJ^~HTphm2~e}~L~0>Yw^1$^|7%L19wI8jngQNCdH zl)sBjWf}63c%dj;ht>5wl~%TOPJ{nxdGJMAU)o{AcBMd3gne5@=|3-eB^!hi z7=S_YEppv9=5SsY4UI-@@3te?&=-J9)S9=+_uHE}_dqj7iIv)eUK=6_vg(YosQd^l zgzh)}q_u8W)n7R1H#addj`ht~Q^T5V=Q0+CG=ewP(@u>ZLr%`+yNf&;!nP51ZNx2! zFgW3)6xu*$^Uz{i+$~+O5TY2=V*Q19O0ModyfZ=OMQ}0?mGTC+oWx0a>kQKBh0rQhh7;+I*x=1Ja zGdzJB{YSp~-TSXqR}*$K)rYZbw)B%rW=Q*zb~t^7%$UGyOlBo7k7*oA78N7DSJ;Sm z#DEd9aQlI9m+2uwVQYH0o;7-@b6dE8CpY%iiqb)B*yI|Wr7@@j#A=GQ14aag&(XOr zDhPE_*c)mhdX4P|X87&I@p#m_Q>V*~P5q|gx%z43XL*K`^b1K8@&(Gh(7`{r-of8F zG&<(f4 zJ=Ox($D}tSFs236?5S!;g^Y6Y&b-#6`DPu;kl4Y+nltp8I4Au}GPxL*%cyYy01sqI z1*PI-$GW;UQv( z6B9c?@syi{{23D%%JnRxf2F*_D6=p%a<<$4+<27 zh4Mf`2~DUWG+4|7qNL37AB|?syB!JA-gUc=!HtP3l>KESt1R!yY*5CE0%D}hHGt%- zX%CkP8wrClVKQcW{$bGgIumlgz8vNI1QEmuP=g90%l&}09JO_y^}cA|#z%CI8wyO238?!GC`*?=J)LfOi<+i(=kg+Q3P}${mgC>`oA$>>Akz- z3!BxM-6jFT4q}o!hoW;IttO(mm-@3w&<&6^8Kusju0=8SIZ%^xZj+OJ(IOeJp90k; zlF;L4!T1rOlvv!`{c4V_9dMpDzSze0h^2?MZ(H*duTuIB*T19`A4QD@y;L6e5{y~1 zZ+5^O@_A>mJUGu^M`mp@WQr)PoMz7W(*AlsX93f>FhyW6{8@u|%Hrm)` zFW0nflzspzA~3{NI&pjWoSXcHU>0d^i+WeLwfDIDPlj6PeR_ihH^X9yRGrtcp(R zb|;FqeXEw$rny#2$e&sKo2>=TQX7Rtj=s75glJJNpqInsvtXtm)b-=4v&aPEDH1`@ zt@vAJsGK)~EyA1F$ee;+EA5kWVB^*jR0L*#?JGJ#HPk|r~o)3(eP3e4f0YS5VQ)@UrN(k>$|pF?8C6f&2R8*&{bY@0M5T&ej{z=5MNerXygzIlVuU-C2&@Olt zlQBII?GQ3~@+Q)gUNpYfBExa+DBQ*ll}T;A`tYce%ovF@BPTe^+41S1{bzeB)J~(Q zm521ifyKY)lO;o$?9S~-x+nn+KL9?t5&dtwWu?-D8)(Wg_e{ESqlOJ@_ww2Rv5$;GWE|bIOCjmF8C+bZ9FP!l~E;2=&jjbawX?WIR z&ep?WePD5FfdgFNhP3&4)f131b9gdh+A}q&Kxj@kGx1OMXxiusSC)Ti`3E7Ptk>&L z&;qJ|__A>D&E;4GH4$Goy%8vHszW^Y`E2pk*|IDt33o%3ctq&rAzi4U3K4xR&q!ui zx-+qpgZdxpaRIk5ZIKVjufy!3UT$=eqDj?P1=L0#X*W!7>9YQFi4rzgJdla^He?NJ zN|+@8XaQzGMFC@qnU=Vc83;c}QB-o32-jvnK<3E87 z-_j$sBNr4g#k=j-O$aalStw5uvwU-xE`}il_JyT)HOUK^o!NXuGHD?BeK~NjMaY^@ z^@vouANo-rmCWehI_?%C9-bFnP^s5X2Es}C=eg)aAvO^gd{)`rzXMX(U|-mqStnkB`?Pe0TKFHLc>D*ByjKDS1l9nAKJP-y@S;n5+(>> z&FmmT2CSYgvNGE`#IdTadQK_MaPx_jTtE6KoAX&4OmEw+5?bl8-|@mnTB(ZJg;;l@ zv9?GNSbk@%xdfhKE&Q1V40*}|7v8`P^a+4d0=U@%0`pqHe77NZ&42}P!bUm^2?-^z zO*@?JQ0P7)<#MwD(5iHrF%Q@NV$@cCgXTwN4is?t&co#+#;B z9nj}kMD)wT>gH+}8&86Jjz`zzlmTu~L68>NCll&{I6(h5J_daF<){T~YTscN`70sR zmA-LrrH5}_>YMWO(-zN`3gI%Mjo*hhJS&FkVC}0 z;qvChze9sVmq1iY5qD(gO|Gg0t4mE{>5JVScM z$Wu+wvHW1P1@Xa_&!!l?CYAKu^o(3NFIrMhF>a>`M`2~m^M-ssAoRsR>B*Z;Qsr-R zUm7=ubrsrcUyh(_Ar3f3Grc;d_TGJMwX>jk5s|Cu-I16Fh3SJB!g!}H)l2AN#We}G z&(~rlm5&gBXp4vaS;(aRcUfRaGS)if2_g}A>kS2d(ZJVm`r(2mFaOlSu?d;pC>J+9 z|Dz?tIGOrekPxJ9t=zron7(%lc zTmXhIJe4Q~fhKXiff_ZHu;gYL%B?&*IJToknQc2au~Wwau*gvG5F9$^L>ccIo}{4Jap z+l0eMc#p$}Uk8_{{WietYCZ1o{D(T#?mKw}cjbp|a3Ia4+cHR!&h zpyb+hUUxvIWd769y>e)hdQ{}*r%C1jahw#Ajr}%ne|Lp_^?3j6=WT>N-h{P(yp3*9 zD;qY#{orn_4uOE3Y<_J^EN(Em4iiV;Eb)YQ$>_C|17dLr5xD4(JquY1uB9KtEo5lU z?~qIclmL>5(jfB&o(aFBFWnR_ds1+W*#TZ>5s8>#){?-iNcKP^f2+=N#6GJKHC^OP zwq($Lwdbvk*Nxu%LMWmjsvSLOYVv%k=$XY}3{`Ro`P0I=ky?KN*7F_%gc>PJyJ`FX03h^eW(j?*h{0dDLA~K@G<(=)+Ro-{5-dDLs9(D*>7F z=eb$dSMD#tR%|!U4v%EjjtQ}zS}wAElBrx?ofVWXjFk}%a$q%xn2C@RepQK!*?+B^ z@v1rX+S7W~{W&rqDSLm7pMM31?(1os-+tIBi~)72W+=oF31SUoLR`uWGO73Jm8WnvHs(uM%FSHc&L@5eh{+yr;UV9uN;K{>u4Kz?l|?6$HY2|{DB*h)q-8Z)UCi|V zuWIwk)ts;}-cw)^vt|-&jf4;wDK3B_@#|;~5Wp1OATjXVpn{z96t&q$V{2HI`ERu& z`Q5Q@?-eghS_wdgNhB`YS)o4W*>5}y7dofT3KJztU>k>SWDIas<18G^_s7>+H*x&w zAN}QJt>zxah-UPG*-hGqqmt~(2e!=F?b$45*C>OhU*bqJE;Dv%_$C~K6$|CPr_G>K zJA)!Wzw%quX0g3_y4hEs{zAyQhOs8B}@g1hLJd_qzUcq;%Xz<>Kapr1UWsXYj z-s=j>E9C|j7hrkSj!2hdP0ot3&xVgoUSMqnhcp9Rlo^(k-*LlGd06=(QQN^58r4;N zWXAZJv6eusL1BNHRWO8?sYqYc3Z->_h!kR7;)WN zW7`U-LKAI2l|lR_`es0QW1@FLwef@n)TAMx7sShAS`%^K^Al%dzzY$umVNj7%g$)6 z`6)pp6Xb)pVx2nZYc8Y!Mo(lB4C)KC4wdz2+sq>(uD8Ms_m%kY0s}_{6duc<4x}Z1 z`#OyyZ)(43?RtQ!wa$U@R_egLOOuAZcp#c z31j)_W5(!y805b=V`(kidyzPu{NS4Vn0}ugy|;(3u6{FS)Q?Dm!g*kbd2u)FIHL$L zqhW)1{!rZ`2nvh7k`CwU1;*bXY~mI*FiI!p&I0%2(q$p(*_&bF9N_Ys)Ev{r<(fY= z!K1Om4Hn-`0`=;jOLi*+`DnwrkLH)--hbDzuM_Ffcv<$YUdzI z^a#8$-^l}XFr(Yr6JH_XftpHS^Ykf*z9NjsPl!Z zu7NoloD(=|t^{7AORww_*nHk-c|_88xt!#nhvHGw1NCAGRLGU`t0{=GF8elE?$weZ zkofJtNUgiHX_!{TYjTjvRNGy5|JK>=`S4*8_rtD)eTQ{f)VMxt2+`i%h8?0(T=*z_ zG~I!6K5?P{^jnNWlk7{GRX#^-MnF~~{FzFIGCCan{qNuFb2rbM9M-yC);~_{hBjlO zSin$JD~G)$CZ2UqdMG%^)k5-0Pc9K~n?TX)WYLBUn@GD0|ET;`R?=?Q^l1JS`YY8%MyD zFcmzJF;bi^!5>a(voY_(xxtE<2g$imX7KiNtS zI$;Tg`Cp2|>UJ@`;-@b%*KxywCef;$Bx1UXY&sh?oVQ?ahq|QJ_0+hpVkZ%6Ef@1A z`e33u5|ZPZpg-GWmo9xn^kfiOevo(&=S<;uoPU=VxvQfq=*1I`stft4 zn+K#t*n9H)^)FT72z6n;s6~i=gg)M^tVpU|LVj^4c?jGdKoibOUj9<`iL~Y3jVqOX z6_^>iTtyYx6uL0i86;`4@!_w!<|;4b+g!aoRYJ-XTaW+V3o%#j#1*Bd48;zajnf&> zEmh|9`?p(TnJCi;G*@S&d)2%+|I??izP3~sc#DOQ2Cuy$1UyL4nXX*rUn8;^fi`U* z=w&Jdb-DMeJlZRJCm^JdgF+I5le)KjTk-Iu$ebu$(5YU*rmFx)z-;RE5;Lev6K(ZV zo@AZg6d?_oY}qBhgWU-TH$e&`-Ec{xI^91v9fiLXA)PbEQs-Cnoylf){oJ?PwrkIu zu}E|-L|FWVaA`&ROw9_g?mzuBJM*tE8uR%gg9Wq$L|FP3Zjx5Wvv z4_oW$QM}XcTw(9RO^II=zQxO11Fh9b+J={f)rVIwPF2RHc7(gr#*=;Rc51ort(}An&BU8REl&Fn!($%#L`DB}v53hY^4M^M`Ew*ZHuc zuYa+7H3udCPuiG^%3+KJmeqt2d`ii!^1295N@Ku-8nWx_o2LwmrDlhqR1;l|5YA1(VD{c+K>xbmm_6~y z9Gc4IWVatK`y}g85i(>l^LRJR-|#RR)CqJ4UUhQOO9Yhoz=D|u6jgxIF^cl-P4@4v zpbjw^#ryB^&{|R2b3=T5Dq-}<3`V8_r3`Ci7%Rj8Cra^T6-`)2jMuVy2-<>o&@%-F z07clEOQK;Te(-%3T7hdtT`STB>!m1tyZYW=?6>9x)oUq8I3o`p$u|P?<>8hvk`h8* z^R&EXQ7pC8X-2_7Q~Y)u=AYwg)p(WnDEJB6^Hx)mg%Mn~Z=p)*i{|nzi#{VSYmw;) zlFaG0OxLXcT(>E&GCv&izN9xkzJAt39`*kHZ?kua4|c7Q7)$Rs8m^|5J4!LhcN?XC zR@lzW#5RsCJzcu&L}f@%c4!FZkKH(~h{Nk;X3$dO`=fK=HwAwgW-(+5Cm%h(DDQ?I z4q=quY5r^49+#_EP%+k${z3yZIQ3+HQuwF&87~Vh#r%{7zu5ZgKu_~E<)f<77xKls zAxFmT)xejVQzf(d+3{HUf|Z4&8w=TQs)~bCv|Hbha{^wiPoD%R0gO9}PLs@4wk016& zDv3(Xe!|h}s;2p35?uyxc5UOJn2t>84qd}WE}2ol(WJ6K`M;do7FIw7tcRO}O(D81 zjWG@2VLc}+P#)LPzKdkxF zV|rBt#y5C8Xe#Wg%Ih(7w-fHyC_v28w-7hlV8HQZ;EJ4@=Sjy3Yjk#BviIu0+2+TX zzVuLgYc&SIrT%c+`M@D%N(GQt0njb?!B9AFQ^B(WPQVC^b_6tm@9Q44(I<|ChhzJR zoP~G9Ei_9yjwYv3APE;eVp<<`PUJ13ECN zW^f31nCRm(*W(5^z?gz<1$K6L9(I~|eAAv1Wb)|0C^k91g%b$#Hhh;fYeC*w2A(NH zs+@JIxHd_P3i#aufDQ=;1(XUlx&-fjuC4yYEDd;T0D*TDFx<#o49caPsQ)+dS#5YS z|3g_342p;?AJG_nz%56+pC7(ZT4E!ViZRk4b{S3lwGc$#K(@Y`q@&gBe<=CV zv91vn$eKMZD+q@joalj;-c!S0V+M<$eerq_Q4)<(&V0weuAL&gE5uC9HUAC|?UI248=dM(IYV|*lH7;%pE#kG` z-->;1f*@%{EQPN|PBT*2?%u;AR<^Z-U}`=zef5lqSe0}hEfS~ykN!6&UAD5;a#Z1MMiXp`06LMOGU^&G67dC)DJ+-D1HD+i`HHzGX(2zsXB7=v(r|By@< zDQbh=pK&Z^Q!fsOuEguneG}9_Q&EB5B|I(D@9k4~B%eniZx@IF_`}p~EtlFHQSCA`d#A;c9V`uo|(&8e5Z{(A-ATzqXK2@z+jQuwF8=Fb3K zqIp9bPa3m9Y`!2mXC?eXFBb*L8r}6VP2;muHi}oELfV-Au;DOy##;WurQ`7*1dh*9X6~ zpV{~9=uAj?LF~jtY-%)NWSIb1_#qg;FDkpdF=rfK7o}S=Burc}ONvaIR1s=w(a2h7e;jN*Sf+qD^ z^)gTt(3Ooillw<^lNmZ&r+m_d%EZGpA?f_37TPB&SeGwcMY6n5zFR~zeyjP6oP}ubkY%!IL%pmS#d>9B(u#$Fj^YG)Dc0$6ykCueN;54$Q zz&8T~IWN1VPh7q$4~+~uV7_~-*842G*khhL{iuLfVivC=x7blq8BpnprYag3?Zp4+ zNA8U==M=Ps08W0{r|I0S3O`I{7$>*n9*bM~#AQcWYe~$Pu1BemQTK*bL+V!-9tx%@ z!5)?JLP~GfpP)!bIxr4Wzm`(6M?N&OWle+Ro}lCwn>$+;aw_*;7{=%AYM10VlJR7! znhuVA8~5H?`zloY@P&*kNmW23_p9o#(Z3<03b00udju<8XFzuvTl$ z)dA*RywKXfu)`&QW+^FquZB^9D!@%8U`hr&uS}H11XC2p{eb{shYn_;&o)5&EyeoY z{OEHXoYVU#Z^nACED1fsJ!vFCH1!wK0q_!M+n{~@_-|SYh%ykD$1f&iBYp#gC^4At zSoauM*^ur=(t!|miY9Ax?zi?GGSW=l#{*JDsQWzF<7Z@Ji%TEabM01LMExYRtaH?9 zTow+)0QOM12T$KU(Fdl~V*eI;htLWW$Uc7xe9G$f%)q;bn1PscT4Tm)Wu84OiHIjo zkSU^S@Ib!%89`FVr>*zDe}KmbQrpC1x2*)EdOSe6)?b@3NC z*bJN*CW-WcF*4I=cyuXXLKgN0V}jeM|c2ty7IEUzz&kj`}PO!ibbQvc?@O#3``Jot%HO{W>hIZA!} z`b_$#BlvSZEvadZ|AO}=jzzc;!&Bm`*@ zkVZsc@TI$@8${`DHo-tqKtNhTy1NC2(%oGG5|Yxnz5Bg?!k*Z3-RC;zb9Tpsz$lqu3HZQ=k*IiL8anFX35-^Y7Zd=7iW+xT2p-8{|5gH{ilqddc5dJx`Qr zjgpmpo_ImLj?Ze8k<-!c%rewubZ109a4RTV^+%F~ghYEjS*;V)X4oETzM{&yz!mt!39k4cOIM!9=@?c zCWDvv=0}$2Yb|J&Vpi+H)Z8)!7{(vH0)xUwkV*$&i)(zoK*zeEe%>9w-udp;grM7p zI3Tb(a1p%mh=P@Vg_$DDU>CT<{Eb1UHs4Ha61w|9aU^~TW1ix4F#WAN=If7rNXrjx<|HeD>lH^|G(6%&D z!K5FaYj?F^c~JBP**|vf4-GJz0vya&C}Ei)FY~Jnf&^+TM?~&uIkXsN^2sH&)=J?6 zB9Xv&9mceB7B)BJl%fBMGIC`yv&GLEl}btp@_;H?J&$328F4c z$j6mLwT}JD;$Yty@d_{3=!lOy{g}M|%=#(9_z=#0x8+W-60xp_irpxC`HzU`5Jp|-DHJo^=kjoe@Bd~%{bn}f+VI4Lg06;oUHIeCp@l zTX$~8J@@$pgsGTN``h2yEHNfd#GxsJePlX?HG%NU)CYS0`_Q6H89v|14@*y)AV(QI z<syp~I6g#xQ-@Yc*0;=ytMDq+`jm=UT5}A$*1qUNc%g17VN=5j3+_^eUBd)prBv!%Iwv3WFfBKbH`Jrk1}@n=zUeoPKcX`3(8~)cuAlQYpJIz{ z34G7-Nml&$73U3+iWM`gs1!ywMuDXuHug8mOn8!#Rr`8`yf8+77#gURTLN^rV_H!2 zT^BzeArnqB^E(63<3S{v-IAb$Bb@$>a2UAL9In`dWfgbaRHW1M!Nwr zAUDbOal|jt{fTC?$Mz#0wudbPH%hIr>+%OW6kWk+BG@$1z52sBl5_b)r~jtMGo@YY zqxy~ne83StIIGD{jo<{^dxA2b3$WldKF;|8qXS#<*dL$%xKVN+-qWNpe1ANXGH|1f zN@H0mkeEsthKF+MVEGt(l#^^8ioQ{vUvGHijIl{VRilTxQfD99mDKLD^fWLY=k@#n zDO4{W5G4I;Ir_K9i`PwIpoz^vQEsA9`1{rX3VJ;I$nPZu9&*&w{++K=uV_7=7o35P zb|=sK9;h3Iy2X!{<79o-T*roGUafHRL5|EI{riznqS`2&qC?x=A+ z(pE8n?N%k%v-G(2u%P;6tr* z$U#|!i^e;R>pR<;;O$SCTm&M4Ax6pY-{lE;)(MG<@~1C}fuD6U=@#DkY!G{wpH!+I z`|alp!jqhnzW=SA>$7jkh86TTMb$-k-^)Jfd+_g%8PKl(L!FbBcbtV!^zg?P5Cvr>Lb zWXV(BAh$(%(#+q1hV@Jxe8U!I-9J`z?j}By2G6m~J zLU+GdZohW)SsJ8*@7|0LB=Ecx9y!RH3T|(ujkzyWC@DDHL(jrM(252#IbQPPSzqSeFa*HiA!V9Sx=xycN`vV^{-%7 z3!E!g$wpHGxc|+516npoz~Yq%!6In|zeHDrqYeayn=DWn?$_Lo>*5x<}AsPfvKY|f+UY6BeQbPlONj`4G|LB7=K@t>Lt zOacnBmuVld8@;Z>B`@V)TRh3(Kpb2N!|#P9x?(7W4AY=l{eeE$I>G;zMwzQBU0Ky4v z@|qi&3YWulx%cv^dAwe2cU@!LJpSO#wPUvBjN|BxR`uGGgj$pK8Q))F1?jgdLmkXx zdeK?z)P469YZuw-xv$3WS6tw+UP;{Kmx?Bx-$xG?T4;sp*vz-xP#X{kQ6hS?uve=X z3%Qy}qNRESBF3yVP%&;M98}euz2j*3Wf1Fv-S0b>{<*yo?#ssLdtcr5PeEl5mlnmFy-v#xXI3s{N1(O?K@%;`1?8l z86&;wa4UBVT+ls$nBKG~WI)`6=F%)!Iv)!-hjg;LN9P$bgHF^J%vjOUj#B^I6 zCzmGhvJbalvUJ_)&qy#8h!xDjGxtb}tq`mX*pRyawir(5Xn6<_iJzSGRcoZkUqD`~ z(om_VY&5K}y>{FAH3ADye^cf###`eZ8-h-dn-9l_z^>X4<|uWhELc^)J6JB?wmEn5 z#oC(X(YE~kAUjI;;5P@XC!s;(GcSbUR_55QHSF(t$QecMrZDEhTM*-E`!MQE5EuAD8K9!yW0la z@7?X1lw7!ucmMUtqG-d>5g3`+c}q4}$!*K8F4gZJn-fj7Cm{8Miv)57l|j6$Q*3U@biFa4_H%)yFdyZ=egBQ z|3@eMPp$MS(?#PU;YkH1qi_%bjxfF|abIl20ICF$@34y_T*l<6vcjXYsKqcsjsV*tKQhY6{=+PZb;0?JpKom% z9tQOm{x?kxXBFTrQ@!|&SP6j31YPq|AXjBg|Eq|+#%k>08BiS6)I;*m<0Kp_(_p#$ zDWF$2496KD-+qp49~zlblYDsoxryk@)r!PnsJE^~u524Y1iEG)PxE)ONiS-kDhlXu zjHrP@n~@z|o1r4gM{0mv2l7cys-;nZxs3C%=mC1KA@Px`#6J4E`elFBU4B#LwCPL$ zEx~Bu8*jq<>PcrDu|f<82Hm8qo;Ft>8M@U{v#AAh`#QBb!!86SbDo9k!Hf73_$1Nf zuq_hX@e_8|!ktC}#D(Tp%N(^5#svm!kLxq4S0H8m*Npwh?fdQs3BuL~c*3&Wtp$lC=ovBkcqbQ#vAEmoT2E=GxW+m9 z@$R_$5RbHdU~M;S=GOFwXo(z9T(vJA>VuFtEuaNq4Tcba5@@At__STf9O_B6KQ_{J ze}A8dvTrf7iPcpk@q6gIbOq5r<3Iz3Ym6+V{(Bb0O2RgK?k<#v{)jQi$`RX#w(l0b zWDCmIU@cFY+fZ~5DFma6ZAADf4y$aYbfbe=b9}mAw(?tFA{F#+kG$;1+~g?{Zww|N z6Oz81w6XHoPNVz6zZiV<%L$yKr0Z#NSL9=xE_%W8G(*@P0WOO4l*8Ig7Wa1N=4MN+ zl658LyrryWv>=Pd? zLnzYmQ*Qc4z3uIcLo(JUuX_8iAUew|4s`T~A76`{g?y((gaN^cQv)D~js2l1H1>zl zr26yW&;;_F+JXGzt&wDlUyjXgG_Ms*MHX4t$tH>I{}uc>`uqREqwSf z6C9@oC2*P4Ir93dWJnAbooPXt<(B#K4mFYQaB3T^58F@*HEs0Un_daO`8QBS+ufX; zSLJkr_f>I37&Rwl>!4joAuV{lTLnd+d&*7uAd&ID1}1_0pd!6BcF?;vhke8kj3x^! zg5G^I%*wIB2D*)p2)Ki1LbEPpKoO>+HH-j!0$)MwKJA?@45-{g+*P%iT3Vu1gdfkc zs6Rp`jS^PI5uK_vu~5j*;*_0+)YGceKQH$yyl>#tITmEw?SuX>4|N& z2#2k9DBC<&Y;}mdRmoh4Md7;m7hWmr)-e3(>-fmXW#w2=)7Tb#u_@-5+p+XTWA-S9 zuQ!aiD2WBX>f$A8;$X_w6(X@4tK3bv_>s-X%V7~wQoAfx{9YP}@YZ-Y>Hc58CHAt? zgADuV(h`*nHTmd$QhMxS287!CEOA-37LxO^xBk0@oV<5i;Z=^T{YB5#tR<}&iE3|Y z&8PI<8ETE-#DcnCTk8$F(9io2ED?R*78b6JkzSKc`#>|Vzk+XbAKsT>vG`vZ6d4E^ zQMr@dBI5+qd}3^J-?0(A9M%cOxenlu%UMv3i(udqdv{=*CLY1xU?^BRd;X$QD?(%W zg;8b)blJ4Ca>BAUn$Rly8y-T3xAgt!dg zY?TfKPW58egEJjux4snwUn2jl;p>LVT-7%vGIik$5_m4LtoKsh$D}iGAmq_B(vRiW zWqp{*o7yWrDW4FHC4>{zF;>;b#jI8a?3cON1}ia6Qq*I`k;d=}xY$gKiOy&XR0Wy% zvGcPcd9IEt;Zyl0)xvSAceS?y z>UUzx-Xonfo2SuSd~G}z?JpzXm-iCxP>MW;x%N0cE?*qcoa?G5?EyEk(>f>qhTF-z zmWF_RGe`LjV|jSz-}h(zg$7h&l7=PFx8>zk$1yd<=w=$WX=iTrw~v*st6qa*<8%!7 z&}t+oo7?wZ#ZF3HEBa*$IEYxB*fl~3C?tgSChh2T{|veaXb*O5+G0a2KNP;cJ3J6p zo!2t-P5#DC&sqdU@Oy=l0XgqQ_!~Q{O9#rZ4{xqPq3W7}cePuLDvFk&;M=0!i~0Lu zILz7%+1qPZOa!Sa*a{uAo^mT;2^`1hrS+f%tmU*V+TF+#e%+{%X;g2?rhy#uGby+F z{Q%XPx#E3e6ZGL%yX>w!5|o7mZ95gwF1lz?#a*7E_yIeo^2gXnIl3hH7Xg7A?c`k7 zp5@txGNMsg8~ugYSwW9NRf%TuRHCN{J&7oj`h;SkgD7}7cxD#WzWk%UU}OMwzoTls zVd9(s6jbuwpjpv*fX5KxB)k9a z){tw~?PoQ(Qtq=@u<|IvaZ4uymq+kaa3}-3vc6YKK3~8;97Ao5tdQ zy~`&ezg+j*UzJpwRodjVeP%IclBPaMobumeFhi?z|G!=J#v7)!shscziS3F zFlTs8C6b@toq9#d>9?T>JKD}C1c))reyKyYRf&xdP;wZkAlV@s0@Rj{``_9nzIrc< zO6>X>_7iKD772cfX|;i|nkdfDpIwu)(&uKIu11ZKu7*W=hQo}?=_?{J;am5CbQ0~v z=#@4{8q=f4jV2A(l5nM>KGMOyLUa$qFA5+E$8GBBBqPZ($)~Wdi3mF95mMFTO1NVW zj+R_WU;5U|WvX-Tt7$?YV#;KttBt6<%X9yZkL>`A7X)uA`l=qB$TW>vYLh^NlQf8| zX7p(eu{=5X2|#J9Pn$0@DE4-6W0quD8azk8G}AK!86zVj?F)5aa(0{&Sa~u3kGTV8 z{s)2$XQi%g{#9tcWYB_`rG2K|nc)E{>wm98N;zTUh^Vyl_ta5C~mC=wy#Ah`QN z_u7|xoJB{sLMb715<=r$qf3^>e^RVewohC`8tO3g+`tDh{VON1v`;7AF47bP>~4&|-$*4tnl zdECit2W}dW+=6K|U=$xnh{(c84FFU@`NmZ<>(sWhg`C43=X zjEz7jA=rzK=B1%Bz%h-yg=G;HX(AEwvB_imO;d_QU->V*KQNT(#QErnIQe@@ok=cU zi3S*W8#=!(;&JOU1K+5B=wF+R`f-wym~pe#EG8`TBs&; zk4gWDeK92v@!XSJV#_+zw}f@aVMO?WG{F#ZJ2;7@6! zuiq($^);M;J&o#~G4#<#qS^HM*)6LQWZWRgj(r5I&D&XjeSC0;3 zNNqCy>LQ^RjnH5fe_bDkYK5h=xZ_~5`zs=(yAvJ;lF8s5wyY0wIl*-}T6-G;!-1)Q z!W%YINPi3^IY67#6VVA6q+q+fcy*p!t$Q*`faD)70xDNPhmdOc0=q>NyTu>z2;U`{X{mTTd%{~BPA&aLimFLh7t3Oy|5_aXYv<;;%nY~lY3WD* znZ@})bBgjmhCg@bjqF7)eP?EUuzqH$EZ%&Q_nGU;%t^r4xVLPYMaGT>f^Qzn>F^RU z%sHS(bkb}?X{?r2j*ZwWP(2ST-^qGyJ$l2`X%EwTH#a$NbAoB-fZapPg^t+ki#u@z z3rZ0gKZ`(vzL05M-F$uJiA=2a}I%xZ9$Z>?qJyl$C*=!QW66#Bj zy*`_iRjlbe2FW0LbKCxmbiA|+*Jj$GD=Pb!>&PjqczD8R}~*M*xMr5nGqNxE`;XczpEO7eCXtrjJbp zH&n6B_u=!lD0%S}=}bXf}>D^>!XTC>Y{6Ro{By{ z%ESJI`S~aPDGoYoUh{1a(jA+c;wNKB*$SlWJ@@Xb=TNWbBlr-Q!TlQZ>8Vy4NKNU( zri|l=f7k~RR#oZM5T;5fYS1bF=Yu#ouk2=wYl+{9)9rr@Q4r=iRyV#a`Wv z%GDPlJ-D-@s!7DPv!pZ-cKOkgbl%*Ad$)~Z*KV;?6_Kf|C<>?Wgn(SP|>Ba zpPUT-BWp;VV7y3H4uzFmczg7&R;bWecbT@Ycxe44 zg~SN8u1bmT!e&`@jDCF|7_a=T2|wCTOhZ4Hk#g1Idn9qA9L5^hgaHHbxSUth{|x#h zAnkZ|Z$U~!;Wst)S*`(|NHGZ^FZM9!bXZkHofQ+_s6)y2V`}w1a%Ef>WKjRRx*Jjb zyX}F^i7w7fB1GKWJpbLI%MQ`>SG#g+yAMA&MTSSr_^+iO=wZsKKhymM2@iic+a>*T z{L?y;njVasmM0tXlls=Bxhc-Hqq$22t6W7l%wZ!`XR4&j|PKF(Sgn!t!d{}G#sZ`7_^!KvzT)?R1lXIJ@ zmjpdiR9l!BUOQoj`rQ&#kQ41*UWToFDOr8v-f}xjCBk)85C60o-Mjp^+xRG%uKigQ zZPR~%^)C?Yk;GJa%?XGNauzvM|EsLaDSN=#=I*S92YOfF4iK;c$tt2iDBPjdnaVHy zH9ws5plp2Q9VT`^)3sA_oU&SR8_x=}B|o6lA~Yuk9*l}J4~u=ieYczl z6@vK~MRD*AJ#9a6(jN^Dmo)SqU6fHac6pD01;A|xkqn#tA8Iy_t3)>Ae3%5LS;V$3 znB>i!E+@^O1}vJ(ctJ0|+^h+)$HgizO zX4PEUciH~N(`QP&Ms*V%mC~Y1f~h%P`}uW}Av-U|}d`va;XD<3tWgS18+m4wTFR+f9QIc!X@u7bEh<)*-awi+de9)_^ zqST74J=`}ZkEusy{*~WZvMTOdlKB=TWrz#8M#q_jMZRLH9nC)1ymXCtkEKhu%%#y@ z%A?7ifZHJ?ozTLF{hk?sqV|oW=7PxA? zfd?&u7n&>x+{6K^gxFI^uP*?@1V~}9;U4VO_UP5;&qfFgIy*u$@xE_7U8?I+fpt7o z%@{q$1o8N8P$UXU9az>l-?e=H?U<%>sekDnmzD82S^KsJZzka#pV6 zM*U<_Q(ETETZj6+K}9U7GUuA~Q^KqS(>iP)Q{ZNq5Dly$K8#Vih%#-|vbi*T#}6A4 zAgvjajRaWA^9%IXmrfjA?Lnx+ps*^3?}#ry4`iM4d*}2nHMC=fQ6-;KNvL)9uJPn_ zmF69v{#ogFn;87k5H+~(NnY6MwUI0Nt$iZodd+m=XmBCle9=s;ld?*I@VM;alc+;k z&$R}G#BFoQ@uU39MlB=AJ+*BV5hvp?ZC+W~$xkQ!abMw1x(l?;XUT?L>nJ_V6%o5H zbF-eMctAt{i)i>7U&3~reh*A00tb8s;Vstwx;(l}lw8R_Zr-ID{sg_P7x!x9jln?J zt&~1*j)j$ZTbw~f-`HS;sU&9%zaY-HZdj~ujY1U^#LZ9yt8bpA&)&**QHOSnVO7nt z*hyb^wd1DU@82vwkQ@j(A6A-(iyo*JpOHDG#3;MuYxG?(Xz6g1tt_9wZ&CnjU{kw$ zUorrhGDA9S+NNzeunMGQzou!^+FT8Gp@cP}98G-^-q0ejV|^@^IGu~VF=cJ@d4A*X zxy)idek3#oY9TAr3Bynw=b`q5c`D4aqB5}ZnG~3oa6u7P=SMqeN{UCYsAe5}ascRo z5T=Co&&CF^XtzD*%pQ_)cX0E3{YY`%F$GBHr!luWM(!ux!Xme)-Of(R_H$|iDbMaU z)p|E6g&O|3D_8~h71Bn@@_9r_&EIrwoLhLWY>!=kL~z;Ob5r$ zlO0E}Tr@wnXjK5l`?Ow-`{d0g zX1kGs@iIVc4WQW80zr@EXV_ZaUFe2F7@PC$!kXJZ2jT|N#i(RI7Tx<#V3VHGh(Y`DLSvUxCDVVH&iInj#~ajk%7G%2~PK zp?wbgptt&zqdV#-Bge#DV4u4B-_ARlgYd@O`D3sc&M$9m?UV@M62UG|*K zHN#u$gUW^>ftM zrKxm0Y%r#%Q&@dUS7ZFUqNDwash@P!dp2^B8?{K>R*(Jos_o|u1}<8J-V7#M5n)+B z_%-AAagp*m#q~dY!{@DaLeC#P@UFZ3AyG$Bfu5FMa2BK7vR%5oZx%D{E- z*|E@0Rg?|;$@gQ|e>47Pzx_-+N~QTyV&{pWe{bpbvqiq!4)?*emP}Fg!?1oo!7piX zIlYE`CL3x@-ov8Y=0~xToiT>uq(_;kv ztrmlmt)8l1m*pZW|37&S0_v~{&8(9PfYhqL*&DN0po`IqpoYa^7et)!S0=s%|_pL|bMhTa}9-o3sO zDfKd&gxoz49lRdWeNSFac7}R_c!Ag0(xb6Qc6aCMJ9CdOHe!HrZ~O#Bj4~tdu+#5C zin3x?6g080ovkB3W<4n@VCi5wo~@>-R!YJL9(-`+dQ*^*@+|OpX&5>>osfZrMB6xP zZGlxR;0b{Rrx^Sjl0jsw{^!z9sDqM*0^@}43_f4zCF-01@|9JEd6O5?{it*o_Ifa})-38Cdtsi;P7?F!W!hdcl5MxT(stPo_nzRsjnl~+d$_de zuci+LwUkSYPoEdMbun=pzqKuqH|A4xRe~}N-~nJgBite=ziw_RV`7q;p0MFGT?HTG zs;O@U6&5{aX{tEDx{uI5+cjOEw5}>{6pnK8nx(?f14D zq^Q!zR*2z$_%da6;GanT`R}9-+lBq^HzCh)3WmP3A{-nHeUM>vkoSC5K1(y`w=gTn z%tNN$1uPvzPC>{1C(!x(?%DhBRo~W`VSL`BtE&LN3mT?KG+gD5tQq|f2mH1)ns$FD zX%@;VrK~Jqg}Tf{UuB}B2tkoYUfl9&?UsnQZNdOuB(;0YV0s;Y~7i!G)b|$U` zK$FRGSs+Y}A}Qp82-&?6uG{F8iKd|Fxcx)R+~M%=F}qOTC`F^@5>Iyca1X7Cj|8 z3`OHKgkW#4W5Musxsulj&`zjSbP|Ghi%KmFG@+#azIz&!GPW*oVHPMX3ZnL`#{M zs4?K_q+L3D6L{DzYg<1={c4HMibE=Uqj5)h=_lJs8_*B|YV(ALW^x$Iq6z={Ay&P02pB3EVaQihfka?7?j zj9b;laA}dL^}jTS1|G%7$1_Rz>;<`fF)aEXs-c@RytktPJ!>!3LDKpf-r8sD?QhwgYh+Z&e z^$EN->G?Oa+7LtRLMFt8< zTAyNSSXmo5X%P~PAXZi%BV4K?s1VW*jqpb^IPieVZ2Sd_-@&5fxc!c<$Qe5YG~u)V zjOwMH>!Co#opk=MlfW0+k$xR5b@>-A`dD(XUClDodAuLe=-A0M);aP!`hA} z6Dug4wv_pDMxVsh5<;41Y4_KRP4|3YJ9x5f^C*@v*y&mR6);JK;r(!who z^XM3P|Iu!R0$AC2Iu7gC@tZ+CT+Fpj{u=GFO#p~b6-GF2G3PesZbkqocg;RygUal6 z;~yD61OUDSt`2K>#ct4tq1}UsK2^2o-KR1uZ8IDGUw`Zf<4STgQO}}na=RMP`lfA< z^h~nG*%C;>>GjIUZu(UIn(V`cyN>W|(s!jd!SCB6Y|HZQzfjx#Cj}*k@q_3?V#faI_!z&8KD*V8oP%6vpugi1 zEy((yLW`dz{CinP7s{0R9{o-o#rRVGzq5zr4*EuZ4wnd4D)yUD=X{WbGJ9a7 zQNT5kvMP4)P_Tcl`uk1$y(Rcq?e>lWf0Ov(-vQ`UO<9g+;qa+`RYvUr@{q*>-S{UH z3(VFnJ%Al`T$O03%Lq&|iy2}$n$<^ywfPtq8iu~Lkai4*5K8moK#HGgcWvDpzlkVs zm0i#DBZb2OK6b)w7STu#-i7)QB|ZdOMu1-hd_h*;9DYy{_dsQ068QyegM52Y>QdUG z?`7xH>;7ZM!NDO@-FNERV5dcRnRQ}@#jHv&c>srkMJQl(#KXr$nBxM(;xsvNq?^E2mI&+d%x)>u% z;Bu*pl!lk{Cxiw7i?|a68YYK6dCA{Be(w1U+A&P-Fqr07ITt4p=V`NJKk*QO-?3iX zWoE9ty{`|;mT4<`DrwT^xxNH{SV{C$O}D&eJRPlk-?_k`<0woB5BR8?+=?XkY=;xx zHRav~eEd<|7A4ri5%c2Gxf$odk!5b=uM0ly?ZvZ0Y7Xv4lH>P%E4Cgyr%w01($FNr zsdaKL>MGi-ZEtYKNT`U<8IzN62?p4LY-fl?`yB=Sp2Jb<*QNZ_oI`hE{;S5@xrh39 zB}p2Fhbgyr`bjtzWLbHJ`F2_JckgLRoD28Ko^INB#PqlOx(}!*|IrRMYlM@$#%D$VSvD_(0PqV~84(P)C6A9uUIJ4ox=)E$EWMr;eQNhie_{jyKvF5)0V; z4lC)e;GXQd-r8c_jo-9>Z-Xl z=J<8AHY!YJAYC&Zg1OfLIZ4E!L!~Yv)RJ%4+SB+^J=yAgYnh7i)SHX;=U9l@!Z(%! zKQq7SzY~VoF0r$rwmYF=6ui=M7jnIa__r*F~@auQ2kri0jFpWh)svdW7S*ngyix!#8 zmIs<%ymq#c@S0rZNp%l=7 zN@8FP3EMSWsn!CF)s&x`eWnbg&#NCE3K@Qlk)!$rEidl5X8%3(o`jh0OY!go&uD?} z#7R?OKTm{(zX0sh6KMYpMGhZ6c6Ga1hb1?C2c!^eSu*xKtU&F=C z&0k^fjSn~cm+@W|5R@vm$iehLZVNM5EReLPyACto^38E__I##6V}L>96cuUa)aaa8 z{oUqIiqYBD-{KrWGQJ;CgQH;iz)6yU z53w(upTcZDLGZVj78qMV>&x_MVgINerB$?B;lT|)oseStUZ z<@)Z->djo!!pO@8u6WeQ-8T~2yYr_}q8P?zCo@89(Hn^z6h974b82K7SsANNQKS<{ zlh$sFK77*i{>bFNT_r_~9a-f9?v8G{Hdzyi7LOa><2Ui}=0LvaW%Gb5t+EML7AY~Z zSyR5TxIJo?iPjV{Cu6ULa8%|7+YO`btX#Z50iffbb*{;CoO-M-ssn2}i3jx4*LtptT>Zvi_4EAy};|&5P5t zuzy<*``txcfyqBg>vf&l(Jd!6)z2`sV>GN{E#ZYzgupAv8MoHe$S0L*da|wLTgB*@ z7-@0M85srLdm(Pv30I%Xl2cUq1ecnR=>|M^e-O7 z;H!%LXAC%t-94KrC(1I~e(HUmp5!BCvoKD@q=2GrnAMMw3Zoq@7Z=#Fr z#wKwu;YzX}anX!10pI||-uo*v zvB#b@Zt$9k_D<7xEX}qJ_u}I5FKIV?`lI^E14yJn4$i|&iNzR^bLp%l%tyMP>jMCV zhJW+<1}*VYBk-)M>T!HqtU2BFLKcVTY_!y}lQ3s-5i>8Gc@fA4V$` ztm7o85Yfxdd`VkgNyW1K{*ZlWH!q`|Fe2+6-GepNKjz^e_i5GTKRC=^7ktKvg@Uem zv14V(^v)9mwFeYRtO`SHG?uRyQ=|Xr-A2u~)1u|&rkmir>OG}-YbSZB6`xPXQ766k z#{*vyDm{22%fO?ej*6Er{-5Gb3Up`(74&%y=*{k@+6gde9Y2#yeYRTBzOyv~0Y}^8 zuMHV?(78vv0C9T?5QXHarUg@{Df@f;j1YwF+~r}y7H7`eEP*=UBaKG{N(B&2C5o$O z)^0Dopz&BeZEqQ~FV+juZ{0$b{#9Hp>bIhcP$N}*o8BR=N;3Mf0z*7@DpN++vLD4( zs!1xW9F@T_uc8-twkB3HR2||&@|wiS{}Z&&6h^eT&&*|mZ2&;nW{2^s=I!#(m@!B(oqQGAa4ooO^REBo3BhcG=GoSp-mDcGEov_{qZg7CHcT7>XOM*?pIXy zmLB&!MW4fA%=k##ND&`}XB1wfPNCl!yS)f}KC3YQ;X!mh9e{eUBJ7|{Pc!z?cmb8c+O-w@2qBA2g0s= z6bEpt%F~~`@RPX?24METi(gx$NABurLiBi;veqQqlKs=H+dH3B{BGk3@peoHQSex8 zu(Q%p$nUTxL&3d`5}U#J+vq)%KF~S-paFZ7#Y>CeZkV)x6tVS6OKTWbbniFYmK2zs zr-UMWfD<;L#6y_zK?{>SphpmJCAEePm=TRMS)Gv-><5&-Jya$yZM;^K@gc+6S7~%9 zhKJ|@4%9$8mAAPZ|1aYd7LR|Lk^@wfR$h&n9}rpdVJ{k~%O$b1!m^#9=}YLB)e&mZ zy`EPaCi=pENw6}}TI|^mzKjgZ7NO;}-#rhqKggVfXC7=5a=8f-_yiif6t<>j)d9r6 z4RTBV!$oY$FbkRY_>|2S7;4@jLp;w+y=>#zy_jfzxBJqveqAKp9d4S;M2|<~-qwg6gD>S#K3!GR=QSSItHrGkp;at{oc)Jw!;?u;C~hH#lH3mViY`*YHzh!UwaRiP1U={S>-HtLSNDGUMvl+l)s+hddmPY1b3N^4*aprSbDe(_PSU+*e+SO!1TZFbsWkOXL_?b# zyHK1!MUpRO0n>=tyLkQHq4rj&xrv-}rQ`5md)AAxS3RQuQ0=Bad0_X&1nQPH`qmaW zP9tRs-}2BsNmuNUb&H{%%b zq#*E5)s#S&_rVN(B_*nf9PkF9xhOvXz39RT^cd|tJUk@AZwm>*PYRJ(tq@c(U=xXVX2A^F1TS zsSvl0WuA|`JPS)jVi=x00F9xi1Bt^aisd{gfJl-WP|(a%9R;;{8M0{c6T(lPIs$JH#tP?S*7iZCD%t7yO}VIqQse%@dy59+d$V=+Q55@ymbfD|?f5ch$o8N<$`|`c%+VO1l{@ zh`z~44w=k4y<-9DOK%=WMd+z{ht36r{ViIU=Fkv3%Wqg=h{rWM>ye_*e1ff6|0F!; z5(I2lwBg(V(9;n6$GCvj|L}^IdOB&L8Tv|e<I^&cdiNtU7Grfnb)1V!)54Sjwcd5DRE7HbWk7UenAxhye+z=4hJs1?1;NK7YSMu% zTo6Fg5|U3{Zx!m@{MGU&vt~@mfp^<^GXS_)dn*i>^WZYFn6ErTHz)bBK5N^dMghS( z4}S5n_F+tooFt9!cyTtXMLo0f5cXP4x}ZScLoFT}stdQ84s)H>im^kop{Ro$a(ncj zEF0=m6E&;Zj6yGmrvCO6gbvjm=>ni{-)KIa%E>|wqm+J96LZkXiU99_c!&Ioi>rkNtCm7}6#Y1o`6RRJLd`-7$CvhMMRr0#h?6O5hgl z)-qu*k^&FU0pPRBGblR%J$sI^>3;l@=lS#do&234b*8???y_n)1^046JV+*woD%$+ zOP&<@qSdAo42b&TGM;;6f!m&3A;!BVYo=*%l`&?`uos?O&)B{fHKCRt``titiB(dy zS|)%Eb{XJ+`YQQ+Oko6+K)UBOE^@2e%?wUX7M481Rb0coN1~Gd_FP21cEo7DgIj;Q zN=S?=-HvL4>-R!blTJyqxopc*jv}ZuYH!O8B{`7|!Fwut5%Y3aMOP5C`Q=1*TSNo= zvP?Xi>IKZddES3|*?Rp?x*&(|Y6kcK79)slE^sI^=?Fx5<=DoHA*sAyWezsx8Tw?F ztw8{wt*2?lyR~q~f!cMZ&*cK`)vS()&zJV4xnEVWm^)pW2%Lm?iGV;RZWoZ;&j`Rk*67IgD?Fg23@HSjf7{J9uLzPq{I*C6%}ZVY zyXCGLZ0L1A(Y%Rj)qJ_|5*QY1PW4~`0nrq|;-XVZ<-X+axh+`IrZ|LV)_c_bf?mvv z{OH#yxulCN0Av_q0^)k;7R}N%WuV^Mecn!8LE$^!YiZf{=&C~+ETHCJ4l!Wl`oDE->mDfZ`=S++&rLa6>dP_zL#-vOJpgcP8 z(U5oZ9;PdJU_gxnx4xCw1(o%t!X6vFBXGQ4quuyS3=?=v5%RK>l}CV&l0ZJ38``H{ zHApd`+&Nx+vR@@raB{hOAl^3;&oYL80HntU%$nUEziV9Sc;tetg7o%ptNrP1{PA8D zPOOBA4vzL7JA-7`-7{pv>y-WQ6rx#TN?(UP7?MDb(?M>8R<(5)n;cNLGT{j@u6$ka zXOY|~9ueDO)Ek9FJq};M>(=?LQIP%cO5k;MWZ2C9zk1s5TV4``ecr_mOp_MZ%C-he2tjS!;wGL zhmeKYf7+KuQ0^84i@e5)+_tCX49ZzzWb43aHIYoFFYF@;(1)!5_?L$>Gul)#P7J>B z+#z%CX#b@?vT0BAB{8T(Y7}=D@k0i%VnypqX19Pm2j-#+u$gvTp52Q>pQ5`F6J+(M zQC$!Z&}tRIcS?1Uu0<~n-;JcUaBy~3#s#V{(2_+42Gmu0kQN{VHVx=K{->~R(f;O0 zAD%p~HP$`|@H3+7lSBTMLsF9l45?(0Um5?7*dR%Y<5XuVewSAulaHBerEKuLM$BmI) zS2oze%Dl{LZ2<#tp zT9V4(R@>Z|)wkpjNsODxeS>_s^T7a2Ds)>Ty-(x;{=-$hRZXKJ?QW-@HqVxwxZgE3 z{MnLmgMPl|U;o&xg2RFWa6 zSP2U_S!BWl{A3UvcV20dh{a_cd^if{3WbmM6pVn8(n%mn9AKq_wmv`~F*5UFI)DI6 zoW3S-2}uDMAcF*5ZV!tXOATJnIFW*Ljz0j?h38#10zY13hp|Zg75|Rifx1ELDSW4} z+&>?kT=}8Q+?7iz%}{5Vpdo*5)+G}hI?-brNTjk0`(?FYDV2YSFR$C2K@vT&(C`?{ zW+E1Ms+uO;=?}w=TvgFiJ0Jwu0b!61Ni7LeTdE?#23(;KD=eALh(MC4rcT%y-DJ*X zQhY1+RSmAnTan%zU9345x5GMtfEi5pu5&7*NJ#KP(DSr%86W1yWo3Da_A7&|JgUqMBEW( z8N&_$Q%#G?9nE6@es$I2XdEdoAkr045>0X3;%0TntcqtIp99`fL4{ zuS2z}o^`p4`XzN61d*Ee_*DSF5A?A}MEAcb`sGat+`)-|KZ{wxz%1*nhI=P>_iReZ|n1;BX6zIPef84tyYl_JPnnzHV#i z-oi-Af@r~8Enp)p3T;mW*Y)%u+pFIQj&CS1$;Yq`>TJ6*BR91A%o&>OVa@ z#m^#HFm2WkQ}m#Tc7f%cD#LIG1}zl!?{HE!Nv;63pYqN11E%|q3HSfvzNuI6@z<@bq?`Ha*xIeF*`O@TS{;AiA9`k7y8_vX=@$3N4Q6;4OA`ex8zP zROT%Ffyb7x;P!jw?$#gjvQpZcx@tjXu*-Jq6KjKwt7>De0hHud5TgxXDy2DfCN_x! z5yDG5y2Jf9_GhfiqHLx7u0knJFg8>Sr7eTE-9E=&q1$+q zUR7Uz{-+HCO8LbPfSMsqO>HEBK0JW^3T4RxY)XNQg0MBFr@ml41MtK_;!J(*UVGoj z8$Qh%b5gNKFTLpvT}pvvI8nd%Rbf?M9*zA10Bl?CW>q#oP~f=lTi<# zEWLeb-Y7B8In1dffHSegrt8%bU{5Y8KTk`oEMDdGUGNJF%`l}W z(sReG_E?&1kLvkM?g5kQRRM_;Dkp~5K!Nkkm-wmTuQ~E2ZApLZ&?3HU2IYp4r`O9} z0#4}DhG6aQ!Ike?DY&6}Xj>9BXlyN6uko1J?YLCva+RAp+BH5{_6;9zYq;sl%XbGJ zHxF!0N2NO)IR^NPJARsUeJx3MblC{lPm~^|=5q&5g#owmy^c_?=?m41Ixp0v7^bJl zpbaDRC*a+7E%gKaCTK@E`g~r+_IY~L*4WOECQ*u`($Ysfnflnk`M0O(QGpNbKR)yJ zi6O)x+g|#D_nP}nxZ*EO4dU&m(^B(L_-_Na(|T6Fz9cCa@TbZ(#+~rYW#@{K2Vl<$ zdnF1<_gh|fk^C{Q zU@;wI*mJn_gSaZBR3&B016Ux^ct!QV*J?~V zITQ-Ogc|j%sU&mCH(pVq0!HWwZN#Jcs!4HuczMh2Nc&Iq6B+_8$c4Sv`xDVGsH-R9 z_7Zd@kM)vNy^)=_EXZZj%UkFp5!@gI2 zrxMpI{8t0^W#+YB6jp-rP-a^&38qyZ%V4RI$#d?{KE&g~IF}=2zckY5_GBgO)Iavx z@$;>DD8&js($$Fnwz$qKauMSZbc;QWiqZ3>AW8HBoF51ck&<-jlfF#~&x)ir4 zEv@kPo1QPkuf+C<>4la;=MbT#6sZ0p7d9JTFtEKQR^owK&^D?ROjJ3aE{TT)3=Jhl z)$?X?_uItA5#~aa#K~x8kUs`XcyH@^;S4Hm3%0QQSBB33HMpDYzC)C^^;ISeoPL~>EZ41J@Sx8i@CX!n2GgQe>H4{jIVE{z2438awe03THL2Vw(y!_ zi^1w%2XT%a$OkGE%S*O67mnYD3E_~TFWv)kqWl=*`cGShE-Op9kr5MQUU`STp%c#U zy`G4JZ=r?^fJGP&;}vaWayal>ryMA==^{*;bdOq+-X`Al|J}n{*|;g5VaSVh`ubU# ziuGtzY3Za}W;k<)nKBR8eaAytEf$n#tw2?nCnJ4mHfy~JW2JY5=!?JzS*H7M(#MBp zQ)^huypm`53Vopz9kC`A5+qO}K2E6F?~bXYS}n<$-)k=rA7cwRZ zrcyutQ})(YwAQGdq~Ivi6j<-XsK@_+Rqr(uWc;WjuS(V`I#65Cc{z?>!cWfC;3Z7% z%mM()>c=^7_ddE2Vgmn&q84}VfxDYyx`1KDyzg-S^VNne**O~dk_j~9O+ea1Z;b!Y zTF+G8gnEs7wpnLIh6T9Rrbme`{fRV1Pr}SZz5V|mLe~Hw4(zK1FajK{AYjVQTM&hM zBLtvzhXYJ2P8!e4o)ak$2XNlKyu9p)wsD1|VA+j%`X^g)d>ixek?o5g)Lb60>Ec=V+LX3(iDt0aR_P*qNa1I zq*9KzdUpTjEQY=r`6b&r=l}>eX8vqf6u?~Wt-F>}nYb`Az2#yH1XujjA{n1Xj!I}( zPs^6VG^2SvA6E3jeim#SVgwHnUpE=^@s9pNX zPv0K!)wo#N4zMze7qsvkcw_?~BOa^lei;v8i779B_G(~^xX*JqY3p-;TCuUXkMhE6 z1zx=}iDgA7mmr^}l*Trl=)P6)Sb|Zrfp1Q1eD4G*0#Apl`2B0GJv$mee$)H=>c4r9 z!wxkIXAl-io@kvw4FMJ?WhWdZM{B{2TTsl~?-{l`u#8ilfoL}!ZyEh8+P9zr>hT;u zo?LRegov&kG>M-_OVIKmJ2kB?v7|{jKEAQZl zQkEBJ3V?RF;G3EYEz8=vK9yL^q_73+1Nj(R!3PQW}Ymy zUUMKQ7+NY*RwOz+UtCsZ*I4uDrijlC5_=+!;6hH6$4OmUn5x(vy8I=;{iBPCv%rXjnt_crm(c zGmYf95=T2z4Qu*dnHwfn*q@7IBefRBwN>NPF)%-vhboDq7DC-N1po#=JZY|K;;8zoj0wPUAO9mSkm;t^G(($M?a?L}oIsT3-el8 zTYq>w#ygGUUTms3jVOy@28qa=arh~g8^&W5ZKisR*TQ$6NB4Wq7$fZ`=WG>TJ$)pZ zm-Hkyaw9~XKX4b|X&+&sQWkxy(6qw3M^d{)D!z2VKcy>M-8=1eZ@g~dt7#*T*u zrEzOPfTBD+q=P~MQmW=QH%0Z=A#&f16X_;VPvbZKTh>RfdmyY$Uj@@>AcJ4H%if! z{-ZIrQ!h%A)b~)1OEpE~nhaE3O4fGt9Q%_0q6xp47_9u8DEa|>4&VZP5`g%9FCS2$ zx&ID{IwAo#GX&55??OJT^m<7G0?(jl#ONhqZlqx8w9+Lfr(ZpvGPC0vGbq>P`dvt{ z``8Hpwc~wJo)&Sk@GA}OV-bIuSR+z;Fb?kev*2pO*L-STy#;Cs7DP^2BJyZP`};~U zX;n2g^aZZV`#5bFknKW_D2Aq6?l`|MQc~H23()@ne9?CeXA(4*k|7vwq(eM*Q15|v=R z&F~cQA>7=Ar!CYsDtjCcWBvJiEaU3}iyEK))*?3?joOfTGB0?~1JA`+7FV!Q^oQuP z{QyCNTcbyn@|*SR6GSafNz)er&znR07as}RHDXX_QunaS0QxXCxUSb`9 zguM}>#O}k!lftfVLHHL2j$cFr$bz3nKqUdH9%}a?*Sj}y0{UEqs;7*qfgk&#A+s-v zLxhmRIELA>KtB7e)5EY?5+3NHxV^jzA2f z3zTJ$Z;tsmq6?Vg&Z8mQq#-3QpWQ@5lut3NImh?zEW}tIwCazPoGSNT#?ks!Q}0Ty ztBSiX6MN4_(F3A69-6@T)eR}uO`>S`4CSj_?xTTlb8QSe-Fw!4KR-=4Y1!L;DdUFPyv1oN{Ch3o zBgr^X3D~I7wz)PQaNdkg{(;&Ge>`4s0RaW~%x3o3O5DJ3!Ofrp9zAx@!kDr+Y<<;T z@Ky{~^Cw_3g@7!x^T83_S;U(jeqlh2`6kAB0+k-kzVz(OZO9JPM>ivmoYbQ!%R#}g zfDi)tUN~P809X$E5eC3a^1ng#&xR#Hi(l-ui&hyFp?O4oFk(8%>%e*Z9GALJ=?5J_ zpTa;oC5-a6Fvl`%nv1A5LMaGEle0>F)TI0kEam~Ad_*NB%X`~EU?F!Cgl`dZ{P8^G z$Tn*e9@PU;T9k=%rTyGDsqq6d;}L$V8>2+R9_DN~*pa!jME%J^`;1Y!unFUi8g*d1 zuZC-SvH!O7?ogWf+sV{6Y~d>?|4c)4w;kMzA!{|yW-J+=FFLyX2(%B@#P&w$5=yv3 zFr!ht%XhHKDN>r|%6RqYIQ)u|5F2kj%M&?{c%uv)I<{zaCZmT>3M74#er6v7YO_l_ z=86H2Kf>`Cz?@$VMWij5!~wz6AUs* ztVcZJT8sUV=lGBg6P-meC@U7~!R*;6)-u)m7WrBhK33tugF6Oa)L#2TyiVEC{lsTH zR2;QvA8S2$tKXPuXa{5#5&py}zWsJHjnDtd?ziQxU-IZT00r(jz#Hp*7w{-8rkW>0 zx8yXVbup(}B7=1!UF!S^{$t+!191Y{w`)T_Ry=!%b_xqXK7x@EngNERb z^Ry;Ni6eF2Oyh^9DMBUShs0HCp=|5t$PVFCYB z7Pvm+E{yxWt8txzD2qK8mE_+$T5@YycnvuM>i;3F`zJDszQqcl%}61#f(y_uY(FjV z)Pp1}{?E;ItL$d?t1Bd8nnVpzqc!Z|!)3!gpGO=YV|OmFjnF z>Q{rqHC-yMnAH3_1B8_@ zCMt5a9b;s^r5wT{j6!@>AOH28FLNjNW8vT4YMBZoV48BuM@k+)!<(!f!CTZT2hdNz zoiB)=R_eWkd%WNzZAuE1-H&BHQ&i`)ziUWpUyoDacwBKxjR|0H`S{pny#jV6F!^6f z4a9PXugzFU{+l6ZghpHas+m*aTBP*V{2q9)?fLMe$!KD{3Z#dt6*Ni9Q!8pXVWCEL ztMS>%ynoM#nYmc+7DMMly{)RCxsf!}o_?Fjt84scVDqVOI-|W+p$8QxdSJ8DSgq%5 zUwR;=HO3dMW3TioIEJ!>x8x}JQDf00F5qfV&d1fqv8!|hY7Y8I$R2`4sWG=-ueQ(( zfLILbFk8h>XIx7-pTNo_8UxCKw)EtZu22Q>yD{I)3=KdwC(mZc1!Q;Q45fl8(G3nJq2;xjk=m%q{sTDy*WRf;p+`YOD$cN%!Z;9!Ij|R&(30h&G zq&ypJ&8fV1@=$0}5vfc@P!eU!7zXIPT9LL%YsMabEqddg`64oPcV@V0*iFFKOv3AX zuz`j1w@KvC_M;ru{3o(v6j_%6%*=GGEiqfi4odsl6AR*`e(#=crXnku z$NO>rEEQt;LUiwo@;jgOUf)Ox#4$-%s`U73U{D+04Dqd06rPaY>BjuoF@;~NNTb!hC6hQYE>lwbfT zUZ(uIjP8MWI~b@nAL))y<35=`pOtVL&>#GM&&IB@6y8ezd=2%i6i$4DhFR`vJ{@Hh z?^Pw_eO)xsrvW*vK89k9vw71w{#qCCD(P^qSDnlu2+n~#Q4+T!2``AcuSjGy4#D?N z^UHqg^s6hqg=$U@BM*YOS?KI%x5)lsAf)A_f)A8=0|kH;7#utRKo0>li-6`0$)U3# zH73kkb8Xyn_vKc1ls0-t#)!$f`j2eq@hk=+(IchBlD5D8O|v|9Btl_N{TVU+V>+)m zD<4Xz*TNTeZ|Y>#ivLL#uPV)xW6Hm$PZ&sIoRm!!R!_wymyaxE{no)?@5cIu25O*R z@+#t^hEwH-I^u%J#)8 z$rAlrQQt)(^Jd4qaYv;U-E?x9M&XSj*$3%U8|FFCIHLIrmo^ryuX({k^zx|OeQi5-- z!d=0YApL5{?=xBUnRV^#WkR##T4KbSB7e5IzEU@Ct=k#@O90?}pt0$yJdsh|bB)L} z!VUvIj=Bq)x5bNr+c80_8U3|Lpp7-$1K9nBj}iW;4?G&@tt`UVp(MiB=F3tJ0(0vc z^zAa$W!x*%j4eeB>X59Ril#8^QLN~S1&dy;d_0r&G*mV&JfHNDB7x7=$_*fm9=QBd z9d|96&Ax`DQC&r^?Jz#eZn9if-4y%1{=*)`XsB=8;uaSJVwPL7GWzY7!Rh}#OQNfG z>&SMSPRcfVu*-VA^J-x?f;Gi$OMXBFeL1NzL zoL<|!pNS;M&lAR(Pswy`l1(gve563UI5!^JCB-VCLZ}B2N#*xw3m;5!f2dC?d7pyG zQL>zx7&W72e9L0Ckgq=A#6SrKOfo*vq5EUZ=#+FP-Rh_65hoXzIP??*J#SU%maj8DO(9*GF=# zl{lMHU5R)k;lP}ag4-D6du-iG{AIrONN>D@h{+qh`eXx$ z!UaPWqVs_*h^=ax4CGjOpWkyk+3Y^Mx0OZ6E^qWRGVNzb!{p}uSBVjy^me@N!helK zYP<$f5&d^R278w`j+`wswC7W@`|}CJA!qvNXBP2``(fI>r9)U?rJ0sO%|{c2*W~hBXYJs;)#Bw1H+VJ=3t(UM z&%Ul~{4+V1{y1vX6=lJ^^U(W0PA!j?v_%L&&oQ8YJR_zW&_@pLD(>hA3?H8Po9yi< zcAcbU3~wk)#{VO5&wB8xBV#Sg7m;&paiLAeaF?b^q{_H(Pr_;)Cn~Q=6XA7A5$%pMTBH;O zV8rV%DD+~>xU!wr@Qo85zCP1ncX(y?Y#-y1tq2_k?PC%!=xK~8u}UHQR)CL55oA{Y2LUQI421acPo9KocAXLcFGG$fC|+*yyR{+H-UNm z?t2~S8S;F=4o$6Ckxs8CM3Ud|m|e&T%(YrAP{vNg7<5efM@M4gF`ZzrDFJP^p4CV61sj#xpSUl^&0I|}3`D)tci_urA4=lJU z4dIi>kky;<7tI-ChBJgtn7mVL-5x_0^MFa(*s*%IdHa^}8`RBEM|S&aqWYz$Zvfe6*FR4ffCwjQtUgmxY3}jNm8*)!#~gQfxwuqZAq?xj9I%VV=1fTCB&h6J)fJ14yv-Pi-I`l_x3jiJA)Pm zqVLnCFd0`WJfMDi@c$B#^d0kE#RE11tIr{U2sy zB?2tlNvwvz@3jD5gC7TI>!xoO^uCFH!?er)0z>eH-#9uGAhMNER)RJkpEP8?9h`z` z##fEo872O+SAZed<9US*iy4n;m9btwL<*bGI$B{26LE%%hE00Z3TttuGMXg; zo$f630`7>>y5o&xa_!=XjFlj6Gpv#F9($+Kra*7!774#wuYFwH#1u-bSuE06M04}6 zxt=C1mEiT%Ob5=aj(C2JTopx{VaRw$HZ0~8qE3Js91D^QUhRTK436~&g?^gkV|IC2 zMzDtnN%b~-&`2q~IurdP)c3xhotE_ACsRsv46Y<9%lVDD75vkL++)Di3O+dQ))dC; z{)kwa?=K_ZW8Q!I+|x7@VCu7t;l=`q`X%6X`QCi5nMQVKGD71)xZ!Ot8q#<`vpbxl z-eK=;zOzhbj@wv@rPl3cXp6qd^ipL_sG%7Xd;MawYILpf>DVQUA4BTuiRjEOTENT` z7&0%t-TUy@IO;c0_qH$9Owg~vZm<(+6r|2Z>p!rSf8@h>`{_ahqC}uL7FI!P6DpLl zIXe;b$=vK1Blq!z^tMyW*;^*v>7cUK-i%#m&~3oy9J8-S+$sc8YP0gqob$LO^%~qB z+Q+ht=J(mHq83yJ^T<^)ed%q=>^*?)y7e(&oL7P5OPhei-X|=f`iX3=S9J|P8FYx- z>)xOeb*X>bC*CFpsVWIY!**4#Z-n*|wNa`Z0KfbJN)}+eMs-xi6tpMBeX2pE@tRA@ zgP^@005ltwzA`(3f&=nJpmPxrKt=(6O^43@8*Qry(+1N)QRq9K^!8EVPFdM~&zIq${(rsAiHTt9~<>Ii8DT%DfOXigjO<#P+dn8Oz zARp$Pqt4!CTV-KpcbM)bsH*WVV-_A?0HiBoV$IeRV5qB-*Lj#I`sUkGy9_Gw9eUawYN2>QPnJ&_;_2htxnYiyvaF191T}gfaTu2-JQ=EZfi-`5_(e%aDilXas(Pg6F9G-?3 z4Nrz_{JQ+n8S1>AzP0fS=c$Bi>B?S0o42O9bbEjPR$A7T$-`(sSq~QI$iG%-vREQs z>y2x^gzu>(2zv34>>K0}!m9*epq87008vgEzGhi(Qs_{J;TCGM`A& z5}Yrb@Xq-vXnol!D&+qEdk3>@$aU=b^&j4&d;I4w{34fHN)oDVd$a9Fl((BtBW|V! zNU~Ka){}n#I_{s_7V<$8CI;XhzVfF;**~&(Qy zel`(f=`w?q24;mIx%`%`NjQ0RoOhz}1Byw!FlY0?$Oj-(#8Re z9S5C}IX-oIO9Xp1r2(XTyvd)gJ`q>x?J-Ad>UJw7GU5_t1pooCM*^lnum0VrfD?1~ zVxcTBN7{0hK2HKzmgNz>tYlP{nm;SX9-jX(ADSTZZ!z~9)$9N9x3@^N z_g@R=gMom}X%q-xy+UmO*wEDvJiCaIUc5xYXwP0Y4F~JWs`iz zl`a$<&zvxB8$|P>Uc!r}Wxz9}axEBNMyNc&h3Z{x&;Br0ng1~jN7nP73mx|@sWVHQ z{5Kk+Ss`5Cpvl4VTbq)2-OUYreqT@4i!n?QyU&~c4wUxG-s26S06jX$yrKy=%E99ElrCZkFq&;OeOSHtdaVmc4U$KbUFtIF|! zMR)ZHmU39#bB}=9yUEp!d_#Pz!qZ|!{VTVg%21osN;AfGzUI@Kzw3#ssBZ78y^P-& z&z6}P7#GMu+*GaeXI5OXO`C`JGj)TctBSHLgo}XI z{%?kWq`m+<`oEVy;Gy@M-FCl^}n5-4aL}#b1m#=D|SdWUHH1L&Y1RM>3vU~OVtk;nBBrGCQmKLC8G~($u zMg=OLN7>aHUo};-t6tls8LOy&DttU_Tw%3BMg@QRBcm6yn$@A#DI&5LOP!+mzl{a+TW8?~Ckw%|nTqbK4WToI;9sv~!ehBl zh1!s1WEk8xt{_g6%6~s-c`zRZJ1scu0v28l^DWEQdQZts>o81AlEd`}r2nxf}hBkhr9`g6{>R znSy+!b#w(h4>M9VgsvA}cNCA{rjLp_ybS&mD754a4<9}1cK7luyaW1r=6e6~TGDb6 z6q5~6VhJ~V#0$DGTeJRB^Jy+L(g^|c`^N5lh5BZYvVwfBg$}ktzlwd?LeZdEM+0cp ze1;xwq9mFqKCP&$%m30MT514GVr~r4|pSX2Gg1v6y)Z>h$u`W=KH_MbkLFg{V*_Xzwr0D7ok%8Iz%y(f* zpcZ<~ippZs=dl3Q%N((jpA6Rtrz(Zp{a}y@N8HqShVS%rs>eWbIGDGD#JC6u(}}g6 zU;OoD>S>}T1O5%$On~y>ZxyLuZv(0yI=lHqKgGBd^gW3^J%;-suxXa$kdvPt_4~Gl zKG1~l`9m7|KQ$G$a|et6t$uAkIYHc@^+GaV_9T-TvewO9fwPaq_SKD}dFh+Fz^msOP>jUMI=wsSF0i#4&ty?-7jANkK#H2BX>Y|f27P{D>E0gtS?_nn)j{OI(Q zRLy?b7vDinq{DOHxoZV}?wU}&d}8e5`ZFPqpKFEi4ZlYYI%ZIsDmWhC7>!W)8!qaY zbvD;pP*Ymxp^!#!Wfpz0)}8m_&t!(xbE&A4{B7fHF>w0$T^!?@7ytxoriTr=VgYTT z<4!K)LFuFBA|Xz86Kz$;&zo?09Mbhn?W(?Z zPF|1Yc-Sn9`hz~m6F9tdRjd5K+qp(>n11qY%_82_MgEwPb*E^qXNS4(v5kHTA$Dw{ z(lrEv5bF&(W8gatN;Z5rj5qT@Ay84WiIrazNZ$XAvrzd;1LBgYaUH6Rq;FbZTMJg7=Ujz1l6tX z2$W|&TUlH54e`-pe+tF{wZgsHo_Z81?P_g4cQWL+s=p)zW=Q2Jq?`*HyUgTst~(nU zDdnTz6ybjTA;TpV_aGqXDCp5Y#1;7OwM$0am!j0920rABoGg^xtQ23i5kw!vdqmfQQsZjz-cQZixM=K~G z3?xKqbdEjyKd<*{pKbT9>zs3)uLBWiK0*ozehWAN!Ij4NNJZt_xwMCrxeB5c(uOk@ z(&0VVVJwYrKlxL1sC#&=MD|*hxP=a0Uo2|%(N>Ln(61L){QZ_pPe_sU&nPaq&IDqL zZ^e=)DkH9$47`|@AgsJ`tcX#vIIMs&aA&Q>_@xGaIFHGMuTfg_zOTKz((9e$*U5eq zCisVOi+L*ofv-rC!v)PDr^8cckED#{#Tp;3Yz?--$$L!rEyE^<84R zo_0BN1}}OdYb%r|`0!Nk~1+%$7&vPUJ zV+r5OA-Zt012;N0!^huJ~hIog^m$K)AM88RpX`~Tr3=(AhppOtb zqqd6xBNyM#Mm3s2nb4MD-sQNdEf>+7Ggl^wCZ$SA<5%YFXvW#hw~fNh6-q=q7Ii0t z_te*tPk>gZlIQ|K;&grM}V38}6XJ-AHILR`> z)5HhuGuFXh=4ILP|GR3+2;!#q7e*5cplvRS+iI`Skpf=M2SuHqvKODH-uo^mZ~pof zs_yXvi+rY9$ba zHJ3JBv$84{od0K=e_l4ohg-$v=xt0)Xz0yuXI_6kf^FVfJn3ZUvPDy+a*!uuKs9?@ zBBV>fo~ND2qjP$Wg+t)^`F>ClOB9Q@KC>KGq#Q=pQ-PmPlhjvaqC2NDa@FyBng@T0 zzCjexy*1S-{nvD=6T0?|WFklg1mBuD`JU9MA|OG^KzFCswR#`jJSPYP zDmPalTq{+t>SS2O;oJADWUd;$aO>Cbl@IGQfgJCsIv0qSW?hCVudGtrebfOXaNas4 zg-9;X7u80U$Z=Y(PV1sV?HQ|hV3eqEO z2+&aQj;FpQ1l^O;2FXYeRADZ51OO9Jl^jZ_ z8?^1knAf&DiChnSUpMSBx#P=1l6m^`QX4vsW&-}h;jd>{Wq?LtIH9BnG4GGb;JuHd zR$k&)uo@y-3P&8UuyZGKWA7LFeZIJopXPaS7VrM+^ z%9xO-@2B&i6@x6uHX=U&BfBsw3p;j3w`qDySS>&7CJ=HHFuX*pll)40i zISq^0_>r-}f~OgdR9|95sWq9#p3#|QB$gLlkOBHw#n130m{;7dt$&_ysLDMKIKUJ`ga4scE~6S%m)6#YOo{(&3l!DWDWsa zjCTZPD+Gi@gKxHTS%vvCAgEPLEz-00$D!6T-^bfzRAY^UDKAvEXd1pwHGQ=AO~sdP zE#xFrWxwogjqwGY%=UaQ+B5hRCn9s_lLE9oRUsD>One;r^YT&fY+O5)ZYFRp z&}VvvXi!VkM2rj66O725a2kC<+(b1Avv3T0_HA+^#)k{2leG|%991HazIgrvSzd2L#??(&K>_B@H06Rn$xiE#p6)?H3?SaUvxVwb3y+nU zQGgIK0(wT4%o?J_4T+C@e+MV`p&R!tt5Ga}jK_lrz8yEXeBNpo$|Vpaw0j@_45;%c zgCeQz1i?r|uNl-m7XjnlwYuZDlu9r{YRBoBN^osjuIHH4C@?KgCVQ?$MJb zMJ4}=&UOA(Rz4xGzW(#o%fSc58NO_Y9$fb21PO}OqGzh*CVJO?8BOwJIguvkZ61?T zw_gkqqCJ=yDc@6dnfv$jWys-#*ZE6YZC%>L#*jDwYy2~8OaMM(!O@kz7JGlq`n>o3 z7ME5a=&^G3_16swXKCj~AG&%FU4H{v?9I`Mvl7UUN%nrB_7rbbc}V&@$2a?6`AN9@ zw)BeR&&2=YRy8vwXi{77lT1#<(|sU^7h0ZNas1PXQW;)}AO3L_|489Gl3bjqA2@+m^V zIdcnj+>tqm!BRbA>DlZ#MI&>hdD6%)_3R6o0#l^xuKE=f&sm-R{SG!uDU@H#A=2L# z5wi%18|*DKi?H;{L*wl;pdg{Hr?+R7nWaL)z320fYS20lkP0kl9SHvS10OS>3x;27 z0&F;N1*Uvl01Q3CCSL^6F)=-P!wi*Pm&s?czx+||J#FQV*I~JBQ3Q=O&Yoew`;b7% z0!sCs#1Bv8qsa)**spapQSGsdB5XBdvF6no>pT_E^U$x}s`ZSnq#~qLdq%4h4p<;d z9@XA*QfBI{O57NYj3T)Z&?-GO)tsG;Z9z};2v)6a;OiB4Q(Hm zwg6D?D6&xR`{edxe3pw+{bgztI;>aQW5C&}Lto_`QHL^K$voMM-dLVx(!E=nx~p2#P!yN9r3`k(4iIwt>rNSG4U&itJuUW5PGI~v;C}qc zz?o!~R5qLg(Yh^V)CKbi(ZHg8mti9}!6o4}rCLs8WcOw{=@C63J9hnGTI~4rNSeU; z`?g8_vCV_Kk9*vQ?j|#B(|^E5ey58o zueQ;1Uq5iA*}i>t`mKC>{NaZ28ePfkB(jWuejn`qZA0Q;r&D1kd8mE;`%1ccLx6nM z!7gE-DK{X+X|g}=h!n@B>T-pK6rFnyS&nGep{TsD{{QJc<%J~!@X2E%zvvYpBJvhU z-u`UFu5yMXIOgfOx?4v-tkZOo!kg_wpX`17^8*A8GR!8T`s|BW(Y?U)Oce9RFvpMo za&D|@3uM=398G_WZjoOSnmMY~NswO}jylw;HKk%}eEvxekNr&77A0D{A?Pwgsn5^# z&7|koJEL(^2muoJ0ddNhJemz1bhvuaD;pwmUbH-(f1z+9M_eDs6BeA(;K3^!Ts$Bc z+2?-N?t)?_bbp(KJ-!8=czB}dOO=*Ebq42bk-T9E{{(`{C_FZK(74Kh3D}Um-Dphz7av3$1Zrs$*dP7?O+xKQSi^sKQ z^JGLkSa6jX+ycb8A=f^JMU&y*;jIsM1iTrbY2RjbT5nnD>3t$PFYmYn9|(ZlkW2ab9i{0lM?b3}bD z<&0Qozp8mTgLZle1Q*OI zdghF=jj=}6)q;5}y>3oSiaMQ`JIR%4=mJTAJP5w@JreRkpGc~y?qmUdscvLZxtH%< z)V*62`XD6&XoFM=(cUCL05Ky_b81fmV%i<j5o=nQ=jbPhehK9%ce)n!tquL&%E-g^{(md zI;xqsg7mCk@gIqeg-$qrU+=%iErL1v@gbH55NV)dP%O^zd&*cQeM=(e`CC5Yk^-LE zb(&TA!&g~#fXlkgqsW{>_@pp=X=1R4y#Ri65f*Q(WP}uGL>>9jf|*+nzG< z{jQ(~E6Cht25oe~nxL<=+`z2WWs|@N6s&qi+H~j0U-w3QOQP!*5pC5F*^s1;rj=p& zjLSRhKGV~x`JPMo(simlgC}iEvGo0x-=x3rD1+e7Fbl3kQ9Qc=z z6Um>vuVdigTAu%!ZL!zFH^LBH6_RIm3ZL7%W9Q%yo__TEk6$fL+GAT;9xI5s)5k~6 zqv8kl@0%l+zH4@0C-&!x3l{lHPsIku^+3Gtq{CCF9-^<%f5Tc(9_Mr=)KXLa>iz_9 zkDFq7+14j`gKp3%qsQsNf+MvJo@^YQZ~phhPRFd50R+Vq>}+9Bioc`AUXPuQAkIJW z>yN_^7R-c_ULN^TFqtsLt?pcbiv(_tgG1a8lUYUPIa(k>3isHIo5z)}L>;OwjGApa z1##%E1XZ*?!1wE2+Ef`i%oQvJR}u~u1Rvs@1atjOlFb#&&WOgW=&@V{l2{OsjNWgymi6X!ilXU^v?z(yurhxWxr^Di^pf7$m6<; zGXpdH*Zp!YdZ#Iv4Hls3pYNR-b|jx%R=b0|Ad6gd9Ry!~W}9W2m_X0VM#_zD*%`T1 z2!FGq&qt}=?b6`fDba(D^8K>JJDr2=OyYh8CWDEoyqoO!=!lU;;l(q)Ge3%*V(s~} z&tfj(%_HLaIZc02D2z1NVbe)5=|c>t-!W%L5Qx&&Q`B!yr+wQ zbM^G|97pZ*ADu|fUs+w>LWe}hng;K9SkY!l6s>#R1a8McqLD!6gAf_^^}LuGV+ zRZ~iO5=bSx!FT=Dxqa;2u%kNn7zF=xC1YjKvb@6LXF-HOyJHnK+#-orFsJk_eukCY z66?n1ghx$MGvWO8hoC;-=to>dvdWPauh*Az0U0WbKFYi#%A8CyXS#y3d83`%YQBWk zh7l9*onp(Kghd_>Z3^K~7k8wIe=v!Crlu z)K)vSzv4&qb5BSo^(Bhum90~?7-)?zGQUi}IMipvZy__8`wP@v z|8h3jy5uiX5}@pL82Xe`=|($nc0RN+lR>Ev>8!2qHUL`k-!f0sd<&_)ccT3DFC=}? zHC2b}nt}jLoDFOHB=a@I>Z=W0$ySHiT^N}?C_lXN`emYssSW{>y9&FI#fMD-oBVdC zez(A2zjsFmQ_Wah&}$Ve#9|tSvy;JXkUiCWLM40$_u$EkuwL}zf|Ge7?daT9I(9@<{ zqAaQc&I==A!=t!WZYj?qLaW}id!moFuKZIe7;;7vl6~j9U-4Pt3z>@{2o7X5xRmqNx1Cz z&H$>=iUV2QQlFq4I)UsAv*lW~jri5T6f=YSxeTlcb|ceOZ9&uG;oK$Yr2X~{9%LY+ zYl?RH^%J3ZbUbSb#|zw#`n+Y?Uj{biC`DXVVyGt#`kio`gqEB5oxVtt{tP#Jweiuq zbN}s|pE_c95t-}QAO08}q`4SdCH+=hFlz)svmt|?<3fqpKb1oZPj4rC4KEZq{2ZBf zQlq|fq=yj8V8v&>Mh8&|R%y*=y?aI7hq8YepjdWh35MzrwBtfW(Cg)yWdIv7pn3e{ z44pp&AbwyWUI408(2zWCLikrOMhv&XL+sn5Zz(58_%Qn+EvtOy`LwOp2ZQ{Xk&?WZ z*amz+WO{eERICyQoTCb22eE#8_s5-Jc{A~lfrDk4e@(jJ8SV2dNlp=L2Y6HtjprK+ zhskeTNPda{_EvD73E?;SD5SeOUNTjw+TXqCtZI>8>v>EHTPDV#B0*8(c03=Qv82xP zB4@wDu354})^H;f_Rg4m;Td=_ta*Q<$E6#Ma9vo}*i}NVZC#21?@C@pek~L33|_xi z)R30n5*~USw9lr+tqR%iJAd#Yc~(^4`WA|n3f_Hk`kAV=sb1<;G{H{${iftUb`z_L zUJO?Wk%+Lu#91~xsg}GrxJbHHpX4>?ierBQ( z-yU!k(A6i9>+2=RhwfQZ^sLC=Wet8VPw-#Rh(SdGM0f&6Mj}*^5S?+l9xB<6`g%qO z+M?rJMSoUbRP~ad6B0ozX9HL=o-R^8KQ*yc0>z6LjP2kqSXAN}OUWTsS6FF+9NkCx z0cEKN9KT+>h?s}BPkj0?bI-tJeuv#R$djlvZ-8xx86rFfY;VcL;kLXFQva<3evE4o z8NWI72Nh0KA#2#Y9}k05UGA!T&SL-FJ&YDg)NN3QWuwbh-i}GOiLqjw%MMy9d=tZ% z`yLayl3LToM}H(DuPj3dy_JX3bl(L}2r`XxxodMXye~_4?h{~cFbFtzm>i+lhP1;% zCla@=OKU|`O65NsQ@d_u6Zy{cD%o3ecEWav5Y@DG(`C6+$Ugh!h2bPj??x zZ%Uu6I-~3q%#Dkctw*qI$ew~Ui9kF?0yIJ4cY`lipii>(f>bga zV;`t^NMB{oaqcVnFEvmXsZ|pcq>ToL?eJ5_5h5Lgp{LlwAn?Zc&(TpOF_e_?Kw%0A zv6u=nDAU}*T*NYF6W8bBf##5pLt9iiO7C^~hd75vJw!xYtMrewJ4bsk>E}aLwK?AL zAhQxA@^K`Q=+R<#M}^os(SOD!#;rpfu;2N+6mgXulfV@@Q#=Lqxbt7~!2UxBvkk$g zY%?K34Nc(E&iD&eXlv}QnXiaPz!Xd}bXbcRi=P8G%O-#kSgNHZ4#TbAjoe4{M?s#P zc8eO~X8Yj@SBnL*_pIN0<)TXlJSdXKnMivajGK%xkKo;Z%e##bJ3M7@Wp6QE&#cpm zxC8&T&dK|gyZ4(j4o?9MFtO0uqJq;mBgv1i3QU+qOdjX*^iz;D{Ll&U-M3?#-)5_1 zY^3eZ6jfOwX2W*3Lo^}ddcMAy?Zt}~>wGcdByKLYPG{!+^~u*p)|Klg#IBL%_SZyf zVwdIgUw6uWf4O)4{pks@wekk^;?jC}oj)O|Tw81_p76r;iRu=iRg+luqYCbpl!!hU zC$<=&1?4JLj$>|GGJNdQhB_kj%QETHz{_jqcs2pYm~YLO`=p&0jL82gR>rcL&3MRs z2s2x*ITdu9;?>Z@+T7?zqJykiRQbK)OAQnbGh5VeFn#zxrTuI97q7F|n`Db^5}4$t zZD#qZ(yi;>s9mF9wxBsJhq{LQAl-tps5D_74O=_R-Au`c4;-r+Kr!~C;3DAg|6gG`K!~g77}ElJt?DRfy}PFO zqUQG1yufdOR68Lb|7qjvX3CdOQ!fxdxCe#x6T+-XCVYhgOvy8^AC_|p>-g&wU8$Z* zlH@VNO4|YR1(r+ zRSS0MVD<~v3V&Ck5KDkq@^`^XK!$<%ZP)c#_`U9&xT~VH>N;~~4*BFx!>cEh{677j zjWB$WFNlh;^XflxoyM7lLBA00CQQ%fpiYQ~ha8OkU~KyY?VmL zLw}kLWdXJQOedO{(BW1Bi@|Y)iQkplCh2IIt z7GgdbJXNJ{e{&nNBnoJ*veiE8coNnYXJd_|9ubW5u|%kM27L3LS=dI3Iw` z*Rda7ABmZ>PYzgdMMS=V;!za;jYY5XfX1!~}9vv65Z2 z)3M5LwxdR_{XyFGs*h zKzOY5sE~A>2+$V?KhmZ8)1&~+jFE~uD$mTZ@89h*&-nz;YK-R*{KKIjU*~|loTe90 z)Q7@JFX@mt%N8&mg0ze)wB5%%D+Qk)&zTmNqwifJciE##*VR<)s^DKYg$esBuVJ71 z0bOOs#$4e~EK-EpKm3E!kmyIS9OImc!0e<%t&^6bbhPZ2TFaMruzLbT#d4j8gGGxs zZN$oVT0M)8`%&TkOT|}4_N}WiUz;fqvd)ZsWAMv^%sytF59cWsM^Am79Cu2#7?t$Zs@Z&sx4 zD>z-NDB{{1B1O5q8TO*GMeC?~=E+EZ`%3d-%yd{;Coa>ngJYXpBAL`=dA z5Cdk_=UcIKFJfk_ZI$*@g)RM7ka@-8{=dv;0VLgz-#T?`ytuXq-^{(=2=kq? z>E2h{dI0O&cZp+Wbb3EI50GB$lZK)UrJAvfiV5db$p1oigwPHJ-&|#J+?SGnl;0Pd z-?CB|Jf_qd8WWfDdp|J8L|necT(ID%fUdc>KDYPBb2j=irx0)B_Dbll=YJYCPdI*x zUR33X;tXhHZ>;(+#=>6+dG6CSari#ajCdF!&5e{K|ZV zDXRc|ig#ZC-Y^6CxQOqdj*jML|65Gkp&cK8Cj@n%%pGbi-H3P`nH&gBDhz}sU?HvF z3X;UCZ?#ee>ay(^8HxB#n(Q?Def=VcC{LoAD3{1=B9Hv?q&C9*VmB%?!PC+Mhd}!o z2|)gm@G11z(MS?ZSNeEtD{1j}v)68V4<1RKLxUlduwKBSFT(Swk%X zwFn<8dF?5~4a0I|I{Ydk>JQcdkxx6}oOxBd4G`v?)GM6#X2p}Oa93>O&+cGW?r&qW zhM?t50x{h48B49183C5RLI06+&PEW?^<$S9s$^3RE3e_s3_y^KD=J5vk-MS=ehNHwZiVJ>%&<8WcW$3x*?E9- zkz3;*zgBE>PFjEH{_bR}cAPW6BXDx$F&2nqVzI6LEAXJ^B_4DFIrqCAuq`82+tVU4 z+o>P?y7kQ9gH|rVSGDG+M;LU#IU$6KPDDQv>_D@en=7tQOTz$RH=ZQ65 z5LQ(hDAk(Dpk$E|CvOW2QHE@$oZ-q2>D>%lcGZy zO;LuwAjib72c!saf#zQCMJwLtx+kMsd2Ir5hpZo)Bf0k$@4OcU-mLW3h+>T~a4wI! zC0p>{!R^(9*a@|R2pjHki)}-mt7Ipgg<)AJO`hREa3LZpnvVb%U(xpWTEZaO?d8Xz7G5@qF_j6WVimAx$zn0@&C%vkvIxlCHMD@f>@_&mgxArFGgi8T>c6=5Y2Ze$1K@6%}wzgYp;{KrBr_R;(c!PA~ z>UBQX{bK}wP|EKXAkU`@W%H@thFttGC!=GY*+OSck76Eez(y1!c7 z{2e;K72`@{aKEs|zj^U@G*-(ozq%+ni47ak{9SIID22h!NbT;CZypzFtl-5-b^v^QACK`ovTpBEq9uCwqWjou-)O?35Ndnrfqq_t>9 z^fUfjCzyFpYCGCI7&E!s@F}32PclakmFMG(9RCyCN|+U@2j0d~rSGESw`WqIg-<3A zz8;m?_19>6mTgRE=*sl^7cY~mMXI<7RkB%WwlARjLzCB-{%=qjJ`n5_Qk(p$53jh* zR|$+PTq2TEo3l>17>M;lS@{~pDbw@7!*_Z?rM3s*N3ZlhJ+gXsg}NS;uQl4A;ZZ#2DcCo$KRthHc^kL!_E_NvXROMhCUzq@G0et9pGcLSgrVpLtm1 z#?J2WqSIHp##sGOAO$C6>%W^QHaUB??^5w+~^QCR`*A{(j$I?C{eAEn?&($HN}3FY9xhEJJsv4WfQH>PF>h(wE48R%g9Hg@o77AAjUG0#}elce0Ars@**iDIqq(P7mUjb?~BKGpSfC zT2Nf59)8r0=j4-yxB+OWo%c(raiqnaT6gl-o?i_-;p8IyI8X{e*dj6?iyhfOvFE?({~43 zSPy9v_)t&Njs)^mNFA~#pDM!r?wcWi5??f+tC~4;Vo^wIkJ=`wr|_#G0VCVQDh;23 z=XhGusNZ&z!94)o-k!GIk*n)OBZezql$E=hs07hQ82(A#>GJ74{LKVoG1b=M3;SpT zh~-nwUj{1y+t-Mix0@)|kFbTl3xsCb2)>V=uk00FiDF&T&Xupe)sz*?$krfvS|EyG zm_`FkAmU}&xuem2?QT~+BFO96U02eFGGG0#>7F8^SH7~Ad@X7edHH$VC(1RQ5`hLC zb=8{6wp*7PuVzyrmjrh3j?`X(`5zaQjEn_F6~DuN;OA6f5ptfQ?GoD|vRHCV23Sul zx7Cr({I3a5r2Y|BYf}+0@~Er@uMSE$qCIqG)1QxZVXFSkSRrv|M-KBzB0 zSm}9&@6xoPcKjl84Z#BRY(EMF;%#_|X-(cS_n_~Y3{mzRvUg+n$O*(uCWmnO3QAe? z1f$OXzJzc~t}+0FNJ!Z;tL=Vbw6_%)NdDhS9g@&S&c=Lu7lScVPm${y4R3)p^n3lV z&HoQHhLtP;b!fkN>7c@vL5vuxcN8!trjTWY*yyF&a7seBsY=ykhjRVrj2N&Yk_}ff zd2YU5pM$SLq*1G|5$a>BOJF!`^Znw>Qr*AgAF$-DN1 zx-ZR4-yjfn4yaPQ8Qu!EoJOOG9;8755Vymd`Zr_llfyVj{;eml>F98JrV+@aV&+yG z2leeikuM@=azu+Bu1R1P>xN(arQC!-=U&p$meaN6t&LW&q(VH)QW_k|<>Bp@yl=>^AD_9)vtqc+lx#=&d92^)MP?;dVa1sxR#IOQ^HeShoGc9H$|mWi8WA zGov%EIE-_7+Pq0N+iA`f{Y*~OkwmuU+U8g^N~BA4P`O?#k?o&GsN>LgpuG=&9^7)E z8EGW_Y0qcO(K33pkNCHA|4^{~a6{7Se(%{L{Y_19h{_r5er)3LX)ypXRsWE=PVwu| zkH#a}U-w4eefX1*>wLDX+R+V`$d{X_P3r5qQGCFiS12ZT?<^MdQJ8mnz^!hztdqmXDN@wD!Z$r@{_x1w^td6aL)`!g^Pw)|!97kB;bpO6rD zYIJA}I+kfP;<{hx!5ouH`3*R*6k#|_%z!(d8%tYD81f%LE+n}n^Y-8O)N=Twxcxh5 zH^|^GX({lSL?;Wlv%6K_ zty$r+J_VN+)kQznE;eLZFs#k#>YOjNMrf=J_2#;(^4Th{HJ>-~QlZt^Y1|($Vp`Y8Q`L z>X~x%OsNn1+Ld2FSa{D6M9hfj`yOMc@-C@NqN2S;-Ahp6^})cpyr<++uHh+xjbZI~ zUIp_IboF8J9c0c)t=1_<4)67&Dp9pF%gbGQ&T|NJ90j;XGk?jzOWxD|DW_1fMAb1C zOY|his4ds0U@VmvXZuR8n=h|(@cX%)fXRT&8RVkSWiVe8{oTOZv?cybVug{nk)wmQ zo(-|Y?N`tZUvowe&=q~cles3m89iUeH#J$i2uk%ig@>vQ!Q1-tfdnflTd@_O^)Y8#+D zP3yX+$4wLEV_-&v-(l4_*)(R?J!bQbmoZ#JcO<$puJkM|nUAq(vF!Pj29C{dphJXK z1mELGkCpb;_pJ+BOL4BLD@{vSiGd?^f-DH{z%nl!{@57zPLZmqec=+nf5ObpUS6!^ z&p<2?J8rsY*xDKN`D1X?g?%!?qzuz+@e}ydNe?|5QG&?Q=z>>tk_b_wLztKX%1`*m z*5DP}20a+FV-xNzbV6_YE6}-nXkSg(syRC?{>_#eGBi?~3ed0V;m~j4ULDCP#`_TP zioOTqUX8wW27a17jzsyjILE3~^x+88!plehVpJ&iLk7m|<2Y1WLXUcDq+#iiQ4paYpGYLdTZ z;Z#_QUcoqt-7Tf$@x?E{0cjdulX4=sE$rJGMV;GNTT+YA?I(OMyFb@B(7!pk)|O7G zO&((9~PSN`d7#^sKk-vBKlSn zP8w|9<$|y=Mos3e!Es$b$NLT_>K+1b&faxF7uSeuUn}oZ3bTgE@tyizBR6v9FOQ8d zVdwxjM3s&J9dwHBy@XU;^+Pe{APJ%tQ|Wo!eF&t0phzKiys@`{l=oT~No#U&Yn8L| z-TuP=+v0oq@e)8PmczzA?Q?pQ;ZJODzQg0M9BRLV^*%%A*(Z~ek2(GlJSM;CIZb#a zaNJazs`K1(Hi~||1kYBm>LRontfDzhk4tV!#+Em0LF|6qN<@5H^`%G(QOSTq`rnF5 zv=rGYwiFiKGbR1U8itNCYo--7tTqkf7WZ5SWHKU<=t`fH6N7{8Ru4o}=1jfs?}=wr zG1B1L7&s$Vea7y__bbL3-+~Tu{<{($LKWt2eX{`!fvvB*oa8kSDX-y}*KK{cSX~l;PRHdY6v(`~{ zMPvakt&gL5&SgeeUI*VaHhHOMbHRP2)M{SYuy_ z>mje%Esd^oC=g?-DGBEZW3gDis!MZ`XL!Bn8~TQl)Uu%RBjSDX_`187ewP>07|p}G z?`CH|Xg&PuNhNAM)bpYLFO&(haX#HbbKV!P37?^pW(nBdVE+AJ&%q7DKrINm0F;wL z%RxWofZT_hV)RY1;bcNMX5p+vFNEvN)X1AK0JpMEDjL`TXM-5lo?}0Dt%_S(g~n?y zzaO&`37?q$IYlP#$LD5U3LKNe1~XBrpk31}MWuS9m=<$;GOPhv-=1#e!wB!RSc827 z<#rL`9>|yNLMm({I}^eo?j%%gn|?>*n*T;oK-w4HlULTW7+Re|MUb7dx z(WF{gc|iPE&ETK3y5G%PMrCHHj}&X`k8Nh}@mRn6?y)>MU^VYF$e5(s5nh9UJdg7m zX-2P^$dc_7@pyaU(#`GU4LLxgv#-uNrblttb48F0y0oB;U-)yb*u!fLubvR(bk%-4 z^Wj*duXh4L&vNc=h|nhrWdB)Ja(};bts#=v=amin8}>qI0JUm`te$gfDT*U?vE;_~zc#Z@ zq6I21u{D>|`5&}Is=w(&HXg8_iLRi-w>VjE%s*cjXQ9)Oka2YTA-VgKLXU!3&@(vFL^}l-6~e^LTN7w8E*Uj7QET+{UVa_ z-OhoH;bo%a;Nu@L{8Gp>c<3AU?c?WtQ`LhJ5#i1QujGH?2TY?jK0H@ISR>UPP6;mw ze>(Z5)=un;z!vE{VH9A1aoD5^VgV<*IZ#>fRfXWCSIqti`{~8f$+pWQ+#FnNPN(hS zDs+$M_0;l<^Zf*AQ0pqF@=J7TwoWm=0GpmY%&$;!E&8m>hxN6kTiw^zCEVxK<6Ci_t-%~tSNI8 zrXUf%7V-1F%6{HMwB{+A>mGe*cc+ZGv1z1yNW+4tu7ob6WuV zu6mDA`O@xuehlzP4|Hni=aoytw&3rgTiBR(f3wp!8Y*W2Z`03qE1k#n0w^^R@LW9YJn~;!hT% zd-_*wm695`e@jhOJ-)7am@PEAZMaYXVY2GW}aV{oJMfo zXm!GduEZa}6O({MiA}oBs+|1$<_+}c!U7|sR!SI$qDbTOZ5caG{XS!@oacjn_Zxae-)_xxG0(u5y|LKJU zic?6!I>-rv1V`v7Do(8#JTch_-_@x&IEMv-5wW2hLU1XHB zdnXd7vFcmfrR|38d(AXqJAc$3FruftJ^szGN8j$WRjXr)+L<2POvJHd`-*II22RiO zIoo^1Z{pM+vEgjRCq1U>zu#+E>zvqgof7itwG^)xtJtVKE>Jm=O++_>;Fk~U->Qeg z&#M27LayZ(xe|Nsk@3p+?UWBQhv?ONNEx^b7bxRgCIwvX`dO7fr2{sb&x=cL#&?1}(U}1W|L_hr2)L7RdyUOcI=6yzz*RkWujjvJOWW-ms9L(} z!zr-HsM3k(Lmmvh@k)gJKx3S=wy??^(HZzpUeUX$>p+N-an6_!k?AwOkHKU-UQ5I&X;6~87b zk_($gZVGfx!6jcMOS{<~_0-e1g!9_Bd31sqQ4$#)FwQl6uRG<-hamti2i_yC zc}f)`8-9Df@GMsd)iAgB4RFAeqd0QFYe8)Pp`~9q>42_N#P0hIaDhTxgeIV!5Whs4 zo(yP%F*OJ=Nsx!LV#wE7ci>K+yO*e%QRP{D3RXH{5ATPa7f&2j>pe^Ba2zz*v*rFc ze?PMvf>uUE8%1nJDMn1;PbE<+Ef9VpMJL|xjP(v6SUfs+;sz%fe7HZ=(#;kQ6to%U zDE?>Za*Y1|<6$c^m8T3SCek(jBK^oQa?E>3i8#X25#c*C7qhKai$3E{CmIyz+fsMX zF#a-wH}Uz@hy5ix%?|%c@VfbnCMxc?$+yW;FI^a^tx&BtM3-NWX`yFw!XJQPUwM$n z6k3M1qZC49{`0bk$o#w#t(Ytp97yv!E4ze^M@-IAgiu1$jdWiEMCBB)0M0h58LY^e z|1cpNWBsg_M7Sx*<@PXfXl|LZwyrec%!3m;X}q2%YSYTppCoG7nGQ*wEl#7YT@ZV8 ze^LzQ90bRW!IfChy*F6$#NU6`7KP?bQ}`<3I8o517j`k6>N+pZ-Q+v!jSb=h4RtNpy&GC6?K_+`^%lZ41388Db82kcTHdm3bm2_zO#k zeN11eS!yC%vfqntsr9(o4_}5DylS+K&8&@ZQrM1DeAG4Lt0!b~D(7P3v86m!%PzQGUq z=re53>xp5Uy}jddEIOyPH(o?;{C8)~PW6oN4Diru+*e#75st)yO|`64ibL7$sP#Tt z)W{sy(C_1kp$Wm~Pa{g=3IU->KLP_EOAm zwPO}~qZXQaqjnoikQ)2v3pxy0B=jV42HxOI)Y;SnTPzJrPCNyUZN0#fQ_RC|?pt$@ zAH1|!-p~1Q_^5Jl`H)eVa%(75+$s;EL!V!u_t8ZAPZvqvY&NJi^W84)f@LWQp=&WfV! z6Sj6+;wu>VO!%$_6u#5m(M|H}9 z4eT`c!Jy>*iCb3#=9ngaueI;IOfrvQxYcACYEF^LyNvX22W2HB-ericOyM3P)T+*+ ze<@zJd2|#J@jcw_bQC?z+i~pFffHcl$vAbc?!bMBS37DZuH`#&NnU+HdHI@O30 zD$=wpVa@8K6{noTBmS7++Q;5^K7SN^IwJ22+Oy89^#}z8-3(^!ypW2Dm?snd45j*O zZsTv!+vv_8T&FZ%`2SadtuJLmzy5d|trO!JnmGE{V56|)I!*f}O#TvDH3I@n*|RvN z<#xaPy5jG$9ruOhzgAD%wklJ5q35b&Mkt3XS%sZWv|GlFYkMtS2OTlZo#$>013B?2=67I z!lkiN;hue0I(8&}u>@I1YPC4?Q8SBnu6_72D^lQLhJ5*TDhf^85< zI#Mk`l4W0|dObdzT3HY~m^dIrhdibeu`m3F$_M4H2@JutW$X+k9g%f5rkFXW(FXr5 z{NuyRF9WZwuRqI?xoWNF7f5&x3COHE9)dfV?yXV%@m0J<(E&b1&H6BqCCm!^f+apC z_z2Gf(7ZUi6oi$myY~RwOT&H!&;$JWX*M}O2c#GHEe|>5oMk0N^#TKI5_NvYhp1el zC4pj;?c_N|esMrNr0rO!*S9Z>iNrqfUFW2Vya+B0-L56}j~kc#9U5XNySj|z#6EVF zJQ|sD>#(ZwfXc}~o&901S)z0yvvKczjUcls zduql0(Os+e{lYb7z${gb5w!m~gqx`}`FSXk{>n4QW66lAF@B)2YnV?4SjOIW#wjiH zVQYCb5rrE8v!B59nMrM)(h`p1;-{!+=&53wh;Nfo<|_F(Te-apZ=3wMGfjcDm!?Y6Ouu=@k!=y-S#r=k7yJ0)5;3v2K0#D zX0955zFp)Z80WmD8c zv?j;$Qk}>{Z7fXFtYrTQ=;p97l;69dJ1Dh2&kBRzpuUokQ18X+Eyn;?e-CY7z55z~otD=Lz z>fG)2?&S>&us%N+0lvuA_o`6%Q56e~gU zedX!VeA~%quGnMY!6`lQ;VMU3uF6zr_>bc%oQ+mc$6Er$9^G~BR4P5aP`e*utNxUf zTtG1xd&ha-!-Tj(4GG47n1K_5rO_PFiH+r$U<5Nz#niljFCwVsNA(`&z8l2HxxfaJ zxEnN4s`e8td}M<-bezNJn{Udh*c1Xi>O9T%OL0cK>eIb3CBJcR0_z%KaK zK`SrvI8i*?({la$d5T_iZS7`{L7?7yFp&#<_t_Ope+k}#@K5j(0;p4la0=vI6H6FB zNtE!U)lG$v%S|1Yz7o+3u%*9T3Jnd7yWWs4P(*pL*$PkCzNK6SyBIQ6++5x$|SejnCIia`ml8lwn@@a%i%=eW8Y6@`X7 zD)znigKOj6?QI6 z#|h6oL-$^9v>0G6oJF$;3F>8;d*=;K!;QQe7-#zZC8SHU1mX?p{m*TrdnpLdzYzs{>gq4?3TMT3)|@USHv|Dflx{(AB;+ z)uIwDW&Q!oXXLbj>8qgDiG%F9Fv_bm&^?t2wF+FC<^H8x5%q;u}@O=+nB{kckA)F6! zk8d}SwYQpNQZGq-<+A}*yL0rA1ZRj%i62v(bdeqX_{>>=i@b@0Xq)d`jMc~C%#Ki5Qr>V9Yh zNMt3r0V_g~!896L_dzMRK+pzHkM2$vwK2h%_0pL#NTEAJ1qhD`hv(#v@u|jfym0Oe zH2kWWuCrIV;c!hO;ptP0K5IP%lZWV0d?m8ry8W-PvKp>m6ZMq{-ydYo=yQ{P(|->g zUHO@%xF^!Ypl&31OKK(=Ge}ZF#;)K%4cn+f#;wheBvN9&%7wgK9G0)^wp!LJ%V5{v zd-Wny(r6FY8()#SFp>gn7Pb3|YrPeHzdo057H*w$C#s5~7kKwNoxEhz>k-Py{{dLa z8$9NeGPIaXr z7gw8r5jWrj3tKWP;T{19xUEw@u5`IhtZ~8Eo-L}9)F$FTIs2_@RK=9;`+EOTvxf0f z;>XhKuhhLX_Ej16PwV9$_M4qYduVb0D5xTY2m&iLwj^pHq=I?YO5EK+JUzeg?7a1` zV(Gx8KN1+ugfNtjFQf!B=m|bf4;sPp&&Q_ulx&T|yVx)hM37B(=+2%>;wreoNb@5nbC*!6q>d-RELM7wvti&O2=I%8=u{2BR=(jwRR$zv6Xw@ zKA|eEMK^a>OsVN|DqTcBZFzyxiAF^Gt;{|duSscUP!hegto2Sj1w}i zm>}ej=CEIJ-+(M+!`r~kj^z97oPKZ9bt z>o!y1^#tg~#1Asv;l6OTeBg(UN~$;)I|NT#dy7E!KjQ^3+yiZW>nGEx+1j(swM<>F z@geyW!5e*XJ_ywl{~jgwyB3PG1pwNeT(pmwpS#ysp4M>6ZMvCW@l1bHeX1+m@MgR3 zk54SL4TmDwDd;kd&di3ruT=L#T>8R_gY9y2-xgIu6OMbsoOCh%PS0`UpJ@qFt_}$i z_xKZ=XmqU(OmJ`d!N65g%4|SmI0KJS0sCiqazx?hG_zGVG4Yn@y z5_v$%-0K+C1IQR@#djF&4!O-3s{&IVuThLe0GTd+UD)nJ!0FK3V}!&<4|S}7+GGK& zHbL7!Sr%!NO`qu~h-?g2<$^9(?qLEScORQV)c3r5Yy_idw)tJEMG-dM;&ncPAf~tO zFYA+G`R7zopWih1>0eQXnOpPb^UFxpqwCjsYfC5&d-j8*w7wJ2B~= zVPn$u(#C;74-Dt*Bs`A>*Q&|iATJzh2z*??;z4wMa*I>VyNh1q39)Ll#sLq>khrxe zeT?Bzj7Ok`{buB)0VyK3O+i5u7lr*|+wJS@h#V5?`;KfB_JHx7u^t1B_8d1mSM((sb0lt8bIyDBu zQPjJ+Z2uj|KNWD@Qszl6Ta7Ko6+vJcK_2-h?BIm)8HD)SSr9^wX;5@=FJf|}*2zwK zLCtFG^rB;3Q)6S+Q#52$dss@i+^p+_(BZI8ZgMJikLU8s8CWlNtU5k6aP z<&@zG-e6}WC@zEme$of&6z}z9JQ*SA1AKs7~SjedwGE{8YbIzLe?z!{NFce;`GjU zFbN^sXE_`I0y}fXGT(|_dC-hrWX>`4AIp85aI;1|_Nkv=U}`w3v_k)?fsH_YQBSZ` zVc?nZr6@(|fTh@0&pk51m78zWJJp7FZrlaKX6hvg89i)tCc-81?;tBbtV_kub-hbSMP`Tbd+Yj`Bh%Ytlj!rwt*86cQ_!19i2yTyxqAWLyZ8b6 zSZ?uw*5%_s&li}mUPp^NAM~8`8Xf{N*CK2o@cfv>goZ$gC{;@{K<@9W>a-aA;=2ok z@Cd1T7?$Ob8r<~aUcbK66)M-sZyz$dc~| z{WCcJ9rH{dP^uo8s%1Z!l#cSR$DJsqKk{ew*LZ>y@;{G+WhS7$3Phm3-e^K<7{7tz z7llRzXmR3F^|)M|?S+fz)y2}cj*(#Hle14g1r=YiIV$xX){ZzQaiuv5D$)t4T`Tmj zpuD077*3@K)Np1PO7^F4C!dzyUJ^IfsUiVuUaQOUh z7)hY!m_!d`*I8~1{NbV&z=S^zT!3;H&TzqiOHEC!^kU%l;%TVqJ1<|CcJyEuf369D zR^>c`>8SvgD1s$iM1q5m%ZfeQdQU*`?4Qj*3$jO&k}9Cu$Iu)zD5apFCi)VKQU-kB zU48WjHa1*l0_Rn+mUkglL*FOwc1GkKnq))&(lB%F@aLVDBV3Oy*MRccJUKU^rE2op zf1Z-#E6l&A&PCMeWzo+qjD%NX_mCi7IS^DG--mIKWB#f>@42kA#r&F#sd|_s+~rL9JoJtaZCxjS6KI-8Qof`F==mi?DXE77B)t$J|-587Li1s z3o+07ms6)2ke-6aUQ{Q=1|=TiSL*~B6OA4)?^K9T9~?XW-oV4Gkgmmz6Fxy=CBP6+ zIxuG@h^&D8Q;e~UOUe)Lt^-E^!!=5NCM8oa_{mxmT3n)T=@!(o|LfbeN*Llx-W%X; zI^`oU{>|dt0zeu8IIo}J7s>qQ_K*Syl&j+En$ocX9?R9kmCdE`R*_Km4WE7Iub24rnc4yy8J$?I?D&){;G zbiCCvWVfdOr3_}8r2XHhX1>sAtzP!9oum=*W@Hc)U8R~YGod2Z@UmJ8fG6CMQtHDH6Ok^pAl&%Y^0@WL*Ra%5dns zqK-aX0z z8ZKiFN6>ENpnOG?%5-wr1)fB|Ohye2w-C-zN+wq#8zSQ=A~`9%69EZ)0b$||tHefR z?t2=1RGw@kh>>V=th>!TOF|anuJMs2I-Qi-kqrG2c? z@P0;6=SLb`LDxPam!=IP5ssIdr6F1lyUgsMz9TDDrFEL!3X}`wK3CA-*6L)i@Bo^r zqJdf$U9YZpO{-+{fMEMANM~#3_b+W$h<~!4i2wz3AyN~XWDy2JhR*{MASCS0aWb$_ z#}1W1Gt$QZd&y+TGl)wIoIZN$gLB1V?%NrgA=mOi?82u^5K3p6pyp7BU6##f3AOZ!$?$hT*^;I zL8o9BM4~oHIF_MTv; zK7m;Nh4Z?AGn(imcdJGa zya-zYjTkv!!Rz8@zJl_cQUC|+DHLN)X2}6=|8y2mtkI|xyN<|!h@!|_IhqWd$?CMM zO-NJ>Y};EAYND|sP|WY)FaKmV@#4_hvxm+OjTTE-0rp5yF$Q2cOuOeJ@ZnMm&FFz= z92Abxe3m}%O7`6(Mqng{<%Som#V`m1Y|+9h!)dSed)vlH{dFzZfi6`{Vx=v6-SVsfIqB%eZm!nrjC?-ImOmGt@gCHMBH)+E$Ahs?mtmH;wy?(kN}o^Is1E7T4GRZ+rm0l zHt}OG(@~_AKZg}oS-k?wC*YoTg*5G0)ac4WPQ6Uf3}3=OqM8=;vx#W8y*(l+V0Wno zk->J2lsg0uh}MCO@UVKn9UO$XJ|~pw0~}$U z2SM-$6F34K%dU&P?E)j&-tLZtr=&~E^Bi0_S&P_!UQFP_Zf2;&K~5bAU5hnl42)XD zbkAzMzYDV1WJMN1C8iW%iFN!f1Or?bBxJJMLTw+|tWv*LHWsw;^Za#B^v!#!p7d%x zZ1J=k5F$u5dj{u~B1FTZBHTXD@P6!mrZbi6-RoTFpFQ>{!M^3l-pUhmO^g#M^|6&l zsOqJJnTdZ@%TF9ub^5LT#PeO;Wm=f6wl}Q*_TC_rPd<>bEnayo^(TvNb97BSf!Z?& z8MnWpl-+~MM5)dWz>w>vbNCfg3`?~0O9@<}cI3m3=zWj8{a-3!ri^RLS^=N{H)gyF z?*NcaK1t*Rb{NzD3herCL~2Z}k>vLIvHia4G!d&G^A7(IBNv&l;RvwiH`vrhw~XLh zfuhHEI00<}_4r6zJ=<2>d?*LUzG;QdLVD+(;1H#YKIO#1@`3ELJ-8%eg2Z#`|x1=>aQi@Mvqr){CG%hm)nV5ob+W9XVy9 zODdkRG%h$<%J9G9nrifDBRcs1AOQ({w~fri90Hg>!dq7`WCaY4T2Z$>cq3Y)W_PE@ zku)x~0sLj@%QfxOL|Hm5-bm%Fs<;&{#=abqoHH-NBD^Lg0 zTg3&=PU0boIkIYp@)N8c{Xc$WGXvUS9|DKfxPTFWEsIsc7+8Vi12Vs^M8oRAx$gXb z$eN=~u*O9%MtX|oIryT=XW+2g>#V$=Pyt?ogs{qpz9_=eH-3h7T>GTbm(c3TbS+b^ zlLA}d(pTT@Xs#)XDZ7VI{Lp4kRWTY0PsPcpqAOeUI5k)vzBhfdlTT~(_Xy1y$>M%f zyz~8&C=n;YGj!~ccChVXaK!+Fi?WZK=K4NY>i*Kv zAuANGGkfd6$cJKM=TjW!V{f;m^qR{g+_KKzWKxDxK9#+?8C{>a{P}V%2-$8x7vw2u zxdL4*se`dTKrR(T*?JaVih>s=vf91|vMvz(2Hb#~f>dcyWtWWWd2$qej{1YenwpgC zK|doE0+$0z;su~uIu{DtltNc_Ik=o(>+4d7mr{!c!Ee8BaL$lQ8(!9B&8u(RTD7P- zmDN8ePj&pI>07LJEsjII^yDG*@uh~xHKW*vJZ2~~>;GZsf>p~MTI#7DogwCQ>c7Ne zgvS6-N%iPw8+i4_?n|X9Csei-hHT5+L5)-qo{F;UW4@Eml8xf|-r%5YG!V3V-GuG= zeOR=A4_)9D3Rm>)d7ZGD&EP1wx$ieJ59;oz@cx^tqVVPe#ak_q>gO@R zT7uY z?T^)=cYh{z+6Hdo0Fux^>1lW3YtLz~|ANzHg~FxLFSAgD8ErIrZ`d`bWJ|bvL+p_o z8Tj>sh-pIlatG;-(I1S58Sd`<#GUS3R)*IG8)Lpr!$}R0J5c=fG||4yJ6?7!WZu2A z109lRuLQoW3Ajh7=RcV8Xm5gct{H3wRA5vo&Nhx`c)~hY=E8pVu|0~D_|x<>E{#ma zwvLA>11{;`K_e!QK8 zR4fk?tj!l0^4t^W4nX)_-Qxgf1PkS%p5$4Ws|Xe9RaH2s!a(eswv(4U*Oo_Ww7yPn za@1e)?N1mp?HcyBZ1}1R?YgINze-4lbSkL+zhG1IvNo)KG{vvj4xtg^u zR-Ipo4X9@*tS`>#C&b2iU#2{eFEd_bn^ZjR)4T0i8TdmI9sQHoATtY>sj+O8N_fu2 zyfry^yMdU2%l%^!tuUAU0}MpXwDIkaeY#db(+;I+hhR8oUdsF2Zb#a__RRZijGJ5U zbPb#wmeVjM)VzsWAburc9-A%J^EbPMGmkind_&q|ZNGvEcM;TN^^0NYN1t3ucU%FB zmDN@lgSVinG z7XiwDLAHIxck!1Qa;UNYrcxVnq!}fdh0?Z%*kj}R*v5reuCNehgKo=LA?;D$Kk^cV)p2GFQSDSqT?_ zP5=stoOUjPR7)pGlferBhE;E2htEZkYLNY8=7qe_Xz}B1oY%eIDr9L}`~bg)Vl@lx z;I#nDHq*ICuDCM8Nv;T!c(^&0?;~*swsYx7m?>A##AE-_3^+f#mLWxEQG8};9;r1x zW?5o5C5*eqS}83SMaz3*tz$(Z*cW^$HxcH^d^Dx%9*e@ZA<2EJ)KE4Es%t=onFhRb zoXc~B#M}E1bR)h^Yqt^Aye{X6Bg=&OlX~ME-)Uyv!YEuJ-;q9vg%8PuOFSP)M&*yg z^D4YxP6NUXvvTAvEQV8eh)HgKGF;-A>pP5WCU)Ho)dMh(PVZE^;mS`Y_HD?ky2XIk zceMgRwA*Bdb)9$LiEwAijS{c}f~m5Dw+G8eU(lDgm=L3P@^#IQL&)Vhj zHYy@+&nfPXy0amQ`j`k)czokX%1hJsGNPF9rrNpjQ{%YQ`0G45=HV>4XGU+2iZh0t zTf>V_qjUWiIZ~TX{t+vtX-koTDr8|cI6Z%*Vp%_=JQl3odk-uB6y)IK4C#j~;kUf-XsQ{s;KUONoUg*?Y+ zx9>m;L$Zc~?;RHe)fqhCDvyC7M^z=b5dikqSM!nfDQB6EMWDe7z5}eb8voviN0VSw z{svu`FzF%S<8L8CRlm{tOfp9p(8RXq)UAI%T?bX~-jooji$UQJ*-2^0$V^rbh@mHa zFFXh5lnX6m)x&4<{Rda7lrW|HTMBWZ zy7<|;lkL6^tED_>UgeOQ1=sN<1b@IKPcpVp3tee`wXUOqSUmM+9c&mp7TV%O_K#H) zJ%MJs(F}#a$ws5+C{kZ|ykE@a;o!~16DMpUfA?L*_`LAp+oH^B=c?B0V}S9nt8K(Q z-Bx7n*e+Dzg{5uzib;8hVOZutd1$r^FLaTK7DPLob%psx4C}|g{0V(mzj2ACSvTJ| z`HHoatWzTDz*Yv7X0B=O?6i@49g|GJ6F+GoAM*0pf38;Z!j+Yfsc}f%L+Iq z9qSKrxh!ktodNDtAr5kC-2fBL#C*syJ<2*k5K;LMa9|m~05sB}7BM02>IVu1ta5;# zp^)%4JQ~*%lxYU0aNDx7}@{X1J>eUNtNKDp4IHTEsYi?PK4E$**74y zN620GYpecr{4`@;TTrFxnK{;exPjqJ#Rr4uSBQ*%YV}#&0dq#y{KSrV$pB8Fq@)CN~4>&0|Oj?VFKp zzPC5UrbvdTEBAwN69zkl3k{N|DBB91_Z&6exDC-eR&QS_WsjVo zFZLB)uWodjhUdq`a4vH{f*Gy2?i;od;op(DS@N;bD4qOic%CxG)XZb26Nf@pQ*ppL zq)#w8ajH=zG3wLt4&;{ABaDLvC~F~oyr0HAlnOpv?(p6X7YA!-KMcxbuG)K6AqoTpOe&~_~8 zEJIrmZ%EpFr^Wc&pTb^#iT=4|z^kC4)_3r*y8D}atclGCk?!Au!*(i%6wMNZsm%l(XtC1fDOP>C6v_`jibo#+h`}Jo{ z!}=KB*Bu;3&8OY@?kgse1Ry6oR(3%?r2Y0ql2-rQ@|N9Os5(Mc}98d}%Pk?`z zJikE$1eW~m3yca4wn*JXBpM|rL2uEi@h~FD#)#A7q|gVsAVq4geC_i^aL4NU*ufS* zlFABn$ZHD%r@^qL+CJ+e#Z$znqo%GJ+sHai&5chaUAo!=eYO6nnc&pe>8P8$&yA&* z2F6KuMlC*=6ls=qqSs!|!7gf{H{E-m&>*xos!;ER$YwA3Kd;cftr>VO9sQ}=>{9ir z@K5bw$~@U9#{B(1{6dEh!#g)vauaGBq?v5!c}cWWV8Nv7bDp8Me=PRK(jf>w92J+2 z;#+aeR)o)DrnT)!g!oGuDTBO+cy?0dCSNuY^NxFjDxAV#t)YQ?0;k+T2scJ<8e|DA zVt%D}di$6#$y?%`BcRp)HV1k&bSS7V1fzS|94s`;I7<)Vn zrm_;e)(-arwtZFC3M6ACq2NXP=avZ6$u^v<5Rj#2wD9F@Ndpu;_s`^ zGJ}z61iNd%VeOv7iFq9S6~+=pRMRWQ#`OQHZ{wY;$d{&SD?I9231TPblLJO{lEyxq znYc6cXa6Yno2BrvGg}v@SdpPsyO4UZ@I=D(C^_jvitLg;4>koc|IZ4WD@okkq6eDY z#iSDLLy;n6h37gL+#Q^4Y&^>1Z}X?jkI8kx&;Jvh(6%P$E+@EncX}QRxD=?tc^x|A z3up_`c4RJqb1J|^d0*~&FZhYI75Rtq>^pIl4h^?zTF0(<_r$Ig$*`g~KhMl+Uv+FM zx#X93(QpaJRyxh{0sOR%_o<*U4dy`2uxI1JGg>Qp<$_;!$HY!CcO-kr+ACnq74+03 z-fatCbP#hs9nuD_yKmKhg#4=k>{*}%7=Qe4MDvEzGfU3IR>v9`&PDGb1PX3$o-vT2 zuFrREw2k)NXd6w7cgQJ#>xlrUtf?wtiQ};6iLsn4w{KydP6{wit*^W`h6jwV)r-=V zCet2gsb~T%b(0l4&O822?z$%q6aw76y(Jktxo30bWZIXme5*WU-^8**rfD~AB(S5u z-8ui{%{kysLbnk&hL(uhnhDFjOHR!G0RKiBe?lMk-N_lb(~gV~KZKh!9!E%(savgR zh1P+WWTEEa^~NYzx1pZQ#0NXBFF4v5$~1m-?99HeTmA8UUo-ude|2}m+}5z`nidr1 zEWx`gkgDeSDCotW4n0{`$W9~l_5A>saRhq$o8 zaa1bf9^j!4ACM!!VwB}3jlXH|uh~y&UuxMZ3>_X5zild6+^fa&tyI#g&A#kGd|abj{DJWqOY4FH__|C(v=TzK3puQHB#HAgf3Z zh@vp*R)mCgFbGSj1ncYs0iohuuXC_8b5^{rF_4+UOylN0t43-)56=~r(76V)<)R6c zzDnvUs-64sUa+O)x90+Wd#8o+sn@D#>0mDQ4}D;bAVSmaL~6dPMEY<62QMuIyA`F; zzaED`EI;~LzM1g>&Xlg4{F$=lQn=-`*oNJ4^C(&vft;jvjjp!KO z#zLBvWgGD*t~DXR8_KSbnk^^|w0vC~ZMbT&nud$YT@e|(yG9eL_DI5xG@E+EzT@Ay z9AX9JpnpDRc0M>p`2{9n!Ag7S&e)uV?(Ff{P3F5LyxViU0JD=7cm{Z+;t>mb z@>0O}t4Cli{!x{PgBy^~RuoMLaYY1p4Y(*`|Cu4tP&T`b@Kd<8g@U)9wz7YAT!ie>kYk%Py7i0g~~wTI>Jzx&dML-5s80#Nwd z@A;rZ`(trTsc(E>S0g533I|!o|8Lz!UyG$WaQQ>gjZNUk% zC(6~i_w7$BKqGNbxjV%14aum#RHN>c`~~vV@y{Gmol!X2XN6I~a~q9n-6~}Nan_P; zv-V$-l*P=gbP90&x_b<*PwsNSQ3LBt*nWv1a%fKK%g#1lk_y)(Bi3sULQSEj=^M-M>~$);lhuYqe|TS0npbRgPDkor0f zdY)(J*vcI;EqS}lnNTB;VEIIKCr^r;f`%~4^ycMk@ zp6AH6y^p4aHyJ#&pvl243$S7boc`JgRUZGi`TT-i@Yx#Eaf_*<_5vCVtDxBXD2p<0 zl|kDJelq00g6&t20KyMh0ZYyqh+8 zzEbahroJqykMbdHvj+$KQNxS+4*qZZcQmNvmTSZ+F^Qpbn*E251Z}Pu_dg3g-i~?| zP&f$c;5zA!DatMV7~oz`yNy251$=}ONYB(y`}FoxHw4GDGrXE3b;-}N;a?@lygE>x!CAPdJ6=>=ycmZjR{OZgO@ zFY8I%K@cuG0oY}&pH1ES3AJFjGqdJzW;z5V{bB5k;Y)hyV6uK4*hGgyKO-Ij=%`5} zkVOV=(ibo~aqk-&?_1cwJp!a~dIpC&CxH+5AfqeB+%>TJA200$MK+slP8mo;?_&2y zE13gQg{S!&1FYhy6)R{?Zn3AXK>zU$Tt%?^%- zxL?T==|VWXHPM1L_dR-@NBK`9GxoBXRiucpRKL+$z$8v#lzQ@^zCq;v*;t62oC*_m z_`zt|C}#{+uhF+=wL|_Dx)A{$FkyF0dF-VNj^Xx(Da#`MkCna?J9OKT!lz3c!4R<6 z*9Z5N1jIrZo|djp{H(BX5TVr53lm@ z_bL+t*$;#HJ7e0r!C@TWyI|{G{M~~x+{Z641ssBKCy{x?4(hUE0VnpT3xsm%@k2Kz zq7jFPjv_tcUjy|RW2jkGFa}~y3Sl{cYh>YEC@MN_L?}kQq;|C5>QO>0k) zdBZyI7X1sF0%+2}jG5)PR6BLxsOw;{MgPPXX!Id(kgO1`7?ww-c0_kMo$U71+!0RR zp>Sp65!LHjp`5@2Oo#Wn*vm|D$`VD|z1~y#1zUSV_@VwpZr=()Gs^Q-&0)GtEqLl6 z$LGQtGk;%8-52@q`o4Y-y67O@GTO$Q+`jIbUXW3&5Gk)uT-Nn?<=fo?!1XgQoj*YBMtYpZ`*TQG_I{g=79F@+9s-ZMyIs^8JiI zH4q3l3)k_Ml>5%MqNT-iN`g2ARl`4rpSB@$<8i!@9+Y;J&TwoQ{2l*FL!^@>7 zt`1~0`KPF~s-KXJSS`Qr;X=)P{P69eQ3L-IiRmfd2s+gD`bRbyV$XP!dD(bBU81tE zHTzfJch2mNEx4;>U0%*rrI03V*=Mmr0S~GvANCA>3BEI@&bF2*F={|Z(zk7Lu>36B*(^gJe3vcljCHWws@AkUZ6jS^W zbo*Qs7bsi^qc-415c|YuGnfQMF4ci>#pEWd6V9x7W`3cbl6tC=g}%hY+cLJ{2+gu--lUvVl&cVNJt(aw1;=^L92{dFuyvG zS$;-!`0Vl3$7Bkel=#ku4JYilyQ#ye}By zrjIhTFXM7MeW9)tQn^8n`r0413-cMFsPV9)HUj87m*1zJM&$`=+qlm3d|1{(fg2Y$ zv^pCuZh$|oc2l*2q}4Win5~gj_AT-5dW+!0=6tp6gM=zdl2HY-SIrVEflIhC-gRaj z$dmq^pEmbxKA;Uqu?F;5TYcpclwQ8iKvUbyVC4Z@^MlVhJt()V0S)hT>2^3jtF)kP zltd%tjQP7eI$7bz(}r#vi=!N-`HY6QE?!W0_V{Soh0%k$Ge67Cv`L3t#`RKAZc3+eF3u&#-h2+`3 zPLR(_9-(VV^x>zT)Z7*XGG{UOk<^aBs;in8)-yu#aF|ZTK0JmINyEE$w}C!?wb%!% z#DL2%UW+AbKv~c>=o~iR?jsq9U0@Hb0}Qn}JZ~s+5T>L#sid+jecGkq*^XD$ffGm% zUTc62IUYfHW&{j-##PE*7-)-Ev~LXerE27E?0vk~{inaH#m+;shngZL|MuSZ=cjQG zj*`FI}*x=~KUjO9`yUV{EhOC}W%{LKE z#=~myDMBJEnVv!|2|X*_yqIxjs%5oW=ZP8kx??Smfbl1sJmuI@XSY1iw2iu>zs$a0 z_D%-=vy&L#H`%zI|6BgK37Oj3KzzE1YVx-8)ga}T-R4E-8cv-ko#S!wjobF}_6k){ z{MWU!Aw4f^rf6S1`QzmK_l)c+rsy|8z|wV+@}&IMWzw|dQ)5yx{Ya+6S&eGt&cr@xPKhMW zL<9I}z(NWBA~7U&a|CrB4Rsk8=KkzIG+vr(_ZBo74sqzS$Xl78l(yB)jnz5{1} zf$;?!u-;%;v{PuWosk!61}*&vQqB2Ns`hSbr0@hj-n%fM?!^MjqT58u2>|lD%rD>dH{^-KC-c38eZRPJ#*$4 z&j${X-6gLbRKz45S<9n;;p890Ay=0rrx6m?=^iHbaKC&dfIjyeu@)lfaXGi6O`ozg zYb6Vc+YT4s$FT~w?0+Nv7MKXjWyQ4m+3UA|!`)vnR5YU^fMu|pX)ps$8}s~ZQ2Drp zcc$q`-TE7@wcnkfY3Cpl1_edZj=j8yV zkrG=nuIH%HXsjW7q8$6~vLA*1s>W|+d8-L!OWSwPJ5G>;?L%+>kEiE3>{ESsT6W&p z^tsu&*xw=P#Phv%qJ){duAN=VMN9sDv9P=MXM8D#sx2)i@A|qSIYS<;8!yrO!wWY8 zgO>I^VB;SN*H1s?doh2XpU)kWF8@g|9e+p_i?fx4;g53JRQfEXXB)a(bN8Ubr;NC6c_Jxf{wBY+1O2k zuSCvf)jHR1Y|RdLNwa}09TiI0fu~_LW-E$*n0>MYDX{@VOTVc_5QFQ3#MW1{9v7u@=|U9!+hY6IdL_aZ5oM-jja{E|f^T;ar-3)v3kd~rtV&8$eBQ#%^S ze){a2#T*i|x+H81FIEhWM9h8>j}LkXi8@F_7NGRr7`w@S!U*_K-4aYX;8_OqHf@}c z5wKhls;WHU%5|)c{!p_t48oPhksco;eF6jNfzZ774hd}yL~>vK+K)d!q#A5j`JxVp zg%eU>ji}rR93Y2^y7@_$CaxE1k+MJc%-&T%=G}0XWiM`8^IBE`U1WX4V)^Hq=I?JO zM&@m9VRFxm6IF^rf4fXU8E=o2JG!R5760BROS9e+=Yf}%N3&fd0~f+j>rcFg+*_tM z%+S5^f}O~UdoO3d@Q3{nmPMe~w@_zWh}~WCzo*{sH!E<8&BWE|^JfQPf*f=lSo#2p`Yk6zRWLtE#D+hyFF{4*%t?0t zm)$O0+}xOr7S#AeVPI7o()Kj`3|g9cN{AKP?mzo%t9IGq*lq5Bi)w+NOUFgJj)gZ} zAvSPt{UP4_MHX+xmmc7{uBRqGL@PcvSKMzox5SEpH1YaZpAqh-yC{m>b9P3My9gnY zk-XEY)l&x-$+@zxs5&$*tQj?J5xweiw8e$rh(msjGNvs03@c=jgtCjxaH8;%a@QSk zP5&e5y5p((|Nps{YhBs7WM>wxkzKe^GRmrC+{}s+u9fXxG9uYh$Vfg7vd6uJi)HGV`eLVbm&UwFI<2hb0wgh$b@uDW`(W(>E6g=*;Mz62^HB9 zgk+@s72hUfqViw$@riB6!Mq=6<`z3?la<`#uZRZqFih|l<{~ya6QgpC|551N^Tf(3F|u#Cp$3}I3ZT7x7Vy-@=>xC zFW;_d+kj^Klix|#(nGbk3RWk{;Ol=PGOVa5`>ns)qYplVD^$55{OMzpW8E!s-_p>G z12Dq~$N@I&m?nTj942l%-)+vZ(F|2q=d{7YUb0|7{}a|V`3(IYM4z)XTuP2$se?(K zY77*hC;bRy^tp;0*!?oO5P9(d(J9=0@>hYM+H02k%2DzdqYI>8=XsZ6o`xZnVI9vm zj87$ch6G)sA@(s*bgt!-2$L)+A&09{3E%XC-qTay5ogSI45WZmz`W}cE)UVlAPl8- z+~w5!rpXr^JIfm)9Z7`xwQoa~p5n8xCdwj~UGoZ=rj1&YvN6fsb)p^p8N9T=SMVY} zKD5IUG|-Ec1Pd+y#fKsA-YR~OQo}?@Jdmr@n1lC0Hl}3+W=x$|I6WU@fZ?Ot_%Ah? zaMPo9v-LTP;3cB;);rD7!+e*4olG-!;6BU>Cd>%qbO1!J4NywISdxy;iDj(?$GfW1 zs(+plP?uqk)U4=1l>TJ8#LRH1Lt`-v`3u{iV)lt=S^IM8d2Bs{@^EGpQho2Z4S}U0 zPuwE93HT z==O^$jG#+)Z8j~Hz6d@Qa_7qa4I4IJyK*SZ&slW|R%7?^8dhL>u-sNblFUwRQcT?A zD~icCsBHyc9J6>Cm^O!>eFTEs(`j!3hAL3E&{fv}BbK6{N~SwFK;f`3)*r?% zDGp0;UbXm+oQjdmKCqZG2%7$3cQsSHR?&)`a2m=$!Z@i0{kH?R$^s&pHSz^54P)!? z2uKb;J1*oy36~mLHB>EP?7NqXwKN`*fW}Y5mgjp6h*WP0Ck}MxYoz*GHz#_E@+|G# zrnbEiwB`kF_dEsM+-^FKo^^|9^PO#S`GfMJ#zU!<4V5TA(43vGJr(+fBa99W#Z!U1 zkBj^XGT=Vvn&`OeP;UV)j>K=>)mqi9)VB85l700T_1>IO(9*~Eq4u;Hn|7Fe9bkOepI)!DzcKyo zSkz?H$u-nbDP;!HAi(`q_LROLV8GCBiZ;FKnu^q48o(_-&#%1EWqfJ54UwvrQPFSx z>3$5ywMuQA2;Z~YS#1SxHv|zh6?Zm#mwKSDd;Jyi*@gK@7LbMxd{R4E6ABBiv&LqH zg{hx>cZhSD!+#RpLB5!QClkGdfL98hg6ZFxv1*-Io}^?vpO(KSBZxfDMEKfE#qRVW zhVLYUZf6q+ZRB9*Iirg{fBJqayAFL%^7^~kk8c|r!DpWIT7#dD$<5Tle*D=3dIZL0 zx0{CTCQv(m9wPiWxn& zj&*<~Oqc|uazKO1VK|>pu=tKQWJqPlKr?Y}DE%nq?HC@7QVVV>7WCcqNZUBSCV-li?39q0hePBkpRF^ZT}+R6^# z)GtO!{IbpDk@pr{jV+)*`=B+XC~us9A3h}}VSs305EW;|@HA&~0+lZY@aNIHR@j>_A@bd1JKX zlQ+>9hs4P5wM5yah+Uo9#!=6l_i0#B8$kih2-TBCxOXoP}Jb=u#HVMt6E zG%~37i5^Ps7m_ffYU|GleRazN@^f6gh9J+krZvlxvskzWz4iM$8*jc)?7`0%4rSZm-KyyFv;a>2j-0N<#P=hUZ+JLs*B4 z5eI^$gL5Aena4J$R@dMO$0tj+8@~<(+Uhm5=l z-yo{i=z()i!X6mt2StK4(&A9c$_`6%r19z3f5e&?CGKh19`*ivPh>ad&qGHZ_vE0# zvZYP`dt-@ki2X^JLkOSwG$|5!I-T~NuMLO%Pvq3sNy^%_7mDBc(zAu!#cs@>-Gu8) z0lBpDr4E28oti|YDc6SLvYHGp@9>Y~0W$K>Dkgr>fKqR*@jGVr*PGtV-7NL&U!KNQ zx7vPYBH8X)ZCG9GIWwYav8{#^S*^tyI6Mq)YG1y0dp>R{(mSu^C~_5Iwmya{IDO=x zIvHk(I*pz#>wO|%+0|8Ry7r)qI^HTR8fVleGmgvXqU4NwI_~P_rXL!z7qZk_6G~lNzfE@ zBka=vy_@%yEULwXLoCE$5Bb{a`2EH`*GH;chv_^F18a$LOW<63zI&{dqa2V=>+(DZ z#*+$_E!d^AsTU-sn65JMA5~bxZ!<|9om5Sv)E6Hgob0c$C1y3fKjsDYkAIA9d&7MD zoMrF8f9T#~diab;n!uy~EMJsYBBzYEl$=JMPnCeC%*_)t@S?=NgV2BG!g)ucFpvA} zBuj7!qk4VHa_t>Q8&{FukmyBbECcq{cdWnmkdQF0LmRFL@`*RVpHRptv|tXq`*BLw z+bVqh*-`RxWPAGF$#$=Y|Jc~?Jr-_i3Y4hN3l5|~5%q)|A6Gbg8u-_JoGPTlU4fqL z<6HmJdvNGTY$g*_cynI_k+yp-YM%}m3=Q$XS3cb1Ftje0VQ1=N6$g-1U3~@yVv$oo zgvjt0-4PrUdruI$kir5R^$bpH3R`{^7gT8N->;AVk3Zbe3_Bt=-{>!2&G-ZTB|wHn z4kzMHS@eoAC^XaiMGnT|v~~ZW7?eFPhklLR8e{jm{=q@qN0>4`vPacmVmk`7ggmZ@ zH{OH36X9{8>1*Cu7s@8D2?-I|hz$&sCbOAvK+wnO*d+kb$N=84o)#(tXO8g4Rv;T? z&O}Q<)7RMpN~2{|(ZifF>R3NsWe-}m^OtN-Vg2mzqN5-U_;eN!=sXQ;4S?l}346gx zQ4JR-4|}x7>w^4BRKYdf==|yBdS2g~QkeCVc{4jj6p&8GuOkI~f{RUapB)t;s$oO# zHYNVHc%DdiYq74cr#CZPQ8Xa_=TXMPUYli#dQq}Zf_nkM=#P2$eU?IxG;$1diyqN0 z6sOKzgP#i^}yf>~>V)P0;RVXq*&cNe{T z{A2w;eKcF_3|tnu2p_7OgXjLvq+3T~rNWT}25Q8Xv>bsc+SFT>)9X|PjmmY* zoY8T&M`(pg%Mt$V3rbA~$ac)Sto^wH-q4PPj?U!`)>W4%+S==wD-kFlr1%zXDG6|~ z+Jxus-jX9gwy`m3Il^5V!RLVsa?l_8Mc^WuPZ~MWV+%xB-UeDN)f%38Cu5`LWj<$M zJ`v1@#yH>(I(dm&A6rQRo-HE@_pK&#sM!~HM7Gt`-|KAshCTMS`P=m~n&IB6E`R8* z`%a{%gK~OF{wvJ&N6qCEj##$Ac7%=uYU&(&jqrBhexz#I#^e?32HB2{NCKFve9JlP zalboV?0=tc&4rbeG;7ebIb75Y!P~oUy}C>h@O8I_cLX(T`jm8dJLN=a36*jfdH0NX zpJHzxpcj;db4n=4U#pMylgMeio1(p~P#fP{w%8D0zQ)@dv);U}9-u|Zd^lyhO4{HZ zK7|EP3r*%49zNH&1o9$ShBu2McfK zgOoOz#?=0`>?*ZUj1{Q*-I{B4^yt-{A#fcJ9_J)}0pGt*ql#L^q<}Fi%>`g{#nJ6# zs71hwJz#}O;)@!BfXNk8)-@to9@76Xg)e*<-@ zx2HHTE**cL*bVJR`pW!NzMy?!=VJdc;^@-f$sc2bp}Ul`L_#@%4JTe5!n?=>g)rI@ zBBs}5o;~eqR3~*aXl#uWHBQk%dch2tg{{2KlLWfVtm4~4i<}JFB!(Ev$&YKS?u|bk zuNX~mJ5$TLDCMAeDxY{q-D2x(mfs5o-BQ3@#eH{WWfu3jTi#)&@+ev~+O$JGmGbm#I!Lnv zF$GeoNPj<3H95Ix#yLvow%yh<@uJvj_Uh`-ih>pV8ebnCHU0U~BlH8SvGt$X&kA}2 zm7|d40Atjz#d}SR5fQtS?blVdwP6KdyCHHl4eJPthMvgBd%{)v{?TSVCqfq~gYeFt}!cB)XXD6o4_-hmCp6*GD z=uLDTgs=@Mjiy}-Kl+TLeDQ?sPyT561)f8C1UojrGQo!2Jt)ViakZ?&ZJH(We%&;5p?M&CXaxM^s zvoZQJomuJg+{QC!h`3us;VuVQ$@2|u{ScO*mhug<;^^5Xd`(B*F8N}9diK!^=nR7= zz2J3A@Zp<;ZSNuU;ZtPG5z9RNl-f+|n=^Z#6f z(S!jAMm3r<3!2H?PeC>4%tHOlm<1;j)YckoKTHEQ007Gb3oBPU?{K<}=F^4YvW+AE ztP9AP?U;=$<#yMrSC2v@d*9Pt)cF!r-c?|2*TzzP^k%uyB)*&H!_UR<8?>6!pJR|? z6(m6a<<6-^yY~@cP+3@!EB}iKw6xEZaa`TfdgrboO?PQ7T<>eXU7rgNs`?^C8%dy3 zDeI^?Tb1CcBL#QeM)4TTS2)UtJT;^94|CPIBZ!tyS7(<>9j~pQemZ8H=|Tp|7Zg&w-5XcLX+-tYxwMzFK?k=1&n29( z0SA)77Q=Er1O^APe)fdgfSJ=UHD2K+z&83GVFh8I0)r4}MO1_4{`hPfsv+#IoZc9k zf;xvJUati#Ua_#+0^$*HHhzJU(U)u0h4d+LjphRR%FBUw-?S;fOwVj_edr*bdAu70 z?95^C?k07Ddk@obmht2JZ+uO>LW`vHZrc<>5LUM9;FmAYOS_Dko-W#UD+C>^iA}?G zM00_}c?-+96KFJeC=61TAIr_t&^L%j#Te^{hyj$;m zc^^5-H2wwqM>xKB_FhftNmn_(#|y%q@+{Hqp3FSNqWPQ`c8@8}PkFjy94PNeTh-)4 zbDyb0RNbdzlOd2?K7_B}M;-V2f{ZsAzzIvxZAH5X{QDZzJ;A%YaJjA_1@s%ilQ}TA zX@$$lKT>JmtqbKWAWEHk1xspyC%>zdN0nUJ17I?z#R;-VqF0ejZkQPDobj1rbHkuG z17qcbIFI7>LgyBh#i|LK%Hv<=`wk!Jy~CC@i1?L4 zX_Sgr`B&*HQ}Y?M$t&^eRR}02q8SEB>RL%DqW#;tB09@_9oD0R|I$(eb%r#BCkw;P z_YtbRp}N16Ul6H{d;`#!Otg|R>I;P2Tc;p1wS07aK&8K3r^pJ$2=vs*TVn16Rek*D z%=V@I2?-{GRvDGgS)!nNOIWuGEiX|+;pU9t3WM7`*po1T6^Szl6zm8rhYKxoi1&AD@PaUvnG$IdC9lfzn_}N z)9@u!tB&PE$7SO=wQFxk!Z~hM7+1!Kf_r;R*)qy}D8d7NnMaz7-d)A}U4nN*vkY&V0zs9A9V?+1jY z1l}pwprG*0tj)VyS@JA!Ax@ljyy-TSY6&neSnhd(FgNPG^5E*n;$Ox=eE2VMzxi z&OQAfzJ>%;I?owQI)cMi%jD0;Cd)%d;DfDa#)FjfIegZE$<{}!Wqj?99mILL#=ev~ zCAD@=ijijQC+Jl$49|63Or{+aQ(X4%G=*t1&wJ1T+OH~xWB5uqZgsCP0`RZIBE zpcwX-aFmy&lK_^mk(Pjrql;sgZA55gx5~6Q&!|aP)lTUIx#Apn_B)n!X}PK5+hj57 zg;sfklN$Na&H0O>N^*`ME9Kw*0Wx%XCaj^H1>x`vk1o9x-^)1-+@mah2hW~JI)Z;I z%z%h5V4iXU3Uf(_kT@6!8^Ho;u|ULdrWy)bG;2zj{cB>@f!OH0Q@_Rh6tWKfW{1v? zW`*?V)~1}kPQyI^e&s0=z{7yR&}*#t9D99vV8XnrEI5$uB|4QEf8S-_^^_5A z#oJh4fTpng(%@_4>I$NPQ*sYZKwgqYM8%}rdS0HSNsCQx#+OpPl?kf?-TwpZ;*cpd zc7(4x<-~^OW!9uESBB1#FJ56iyu^RJGSgoawhbDz^qAugi)$I?m-gs748_ypEdnYK z{3}{zLAPg=nqn~Pz+-5}rAxBD!TVmXBPb-6r)9Peby%g;whLfe7qtcYy$k9tfPERp zjD&tJQviAe=G=+iu-a^Ei>p9KvidIHyLBS-Hsr+?v3ITc)- zhKyrIH~1>DGs(Nbz8cSD$+jRc6WX-7wt@A>v)uU)8J{P;$4b4k>&x*hxzUM8IEqZ3 z38x{d8oZ)Ij3^nudXTOD6nR&+6%j>`ZK#J6r~aTal8|aBNzWT`cGx|WcqCRW1Ir^w zg9WV)?CxIx^H(Ov3zQ^nc{D?8Oy+K0DDRT38Q=QhzoIScwt$BqnAf2CUd~Ih6BjU} zaJS>cWck15z<-JQ&>MEkjGa&iFsvZhZo+HRAle;Cz@3?99$kNfYM2h8e>?3;Hdr%$ zuD|_J7e@E^Atsz16D1H{oBe0ujaVT?z3N-sQdNKPVppb6uI6IMQb^zgrDuomn}wJn z@z?EWG=SL9UfF7Dg~`6uc-9}0PS1BGKwgMQ=%W|YF|yAT)5%oAy0B7o3ips0Ewoq{ z+_n+Zp(NI@lZiw-NV1{;dK7=}txKqJ^5ua&bP*Q>)uCwhDqe#c#w^MfnZD1X!R zgnMCa@RBIh_(;!uio0VM`TEF$6!7KSC{o^Gdd&9r9Jan_kaJM!@79fBLCAE`@JgcsOLAp zy3XhXP3ah;?d|%HiZxlPWY;-*u?ywex*mR|<+ZdYQ^M>m0a% zCxU$Hofyaeza|weO6GEWCA9MQUPf@2NYomihsX6IHXe*Rd%?0?ykR_YK-0sEPgV|| z%vp_XrhR?;;y)Q-N!sfc2%hT;G=#I5I}B_->a@2gZ>F0)N94vf>X=Vm*mGt^AMt-c zF86EA9=As)DhtmFd<-(%pX!Tu)}-nA+jpQV8t-heU)1xNuw3Y8!HJ53>Rlrys}_=s z{e8t2qdgX$zkjwOkaw{QrpZ?}FZ3sO0W8mIbZ=no{lERoJLh(g5f-!I%_b#^F>+S8 zFG4z8Z6+yf1;mDpY^3GFOgKn^^o9)v*!1`pH~(~r$1I=!y)J|zM0sDHOcy&@=4y7O ziY{vppX7vI=#30 zm`e%AY1m@}%|5rW(@)6J@CGsX{CSaY z0yml>_s&An6%}-9yduhwa|LDi;ty)1Q^o*08$$Ycuk2NuBA(Q+jKGDNUPMi8*}YXS z^rc8XTP6>l*f}havSxnUtGb1Th+t?cf)}iB@6NAef(4`SnNn4#+G(($l}BHwK1(&W3BZA;E(PX()KtZ5Q|Ja7}DuSL#3GRUS%v4x`ER$1VFV$XmV2hkmDiAE|GUxUWV9#s@;P(cUI>!O?6YgwN+K^cVV5Tf8nOD&+Nc zMdno-Tevr8zZed3#*oZZ(Wz$|5Uj_MNin#*)E{z0&ES*8CZb=J?~vkl4x6yj16;P} zdlsw<6FN0^MrU~k$zVe=Ji^hHc)THfmn!1NO3|aG(1Y*z8|&b;r;5QsfArV1mrhpT zoeiwttT;0NN6Tcs`bJ*+`zKQkVb18fo&N2(AIZ2cmf(OQSh1^JxRdkw*dy|P7q@^5 zNG6qY(6N;;*mFUYD+Ucfl=bArBwVZ*&82Te`}_NH371@h{8r)uwGA0xJ&-l6onMml8`Vis$bHZ9K!b&~?W-h{WpAJR#h$!8Nf;r}J+cT3 z3rwCL?Ejj#o^On8*cU77hhHytdBY*SIrm--&9KyoxIU_4SIgE!wx2k3ROnxC#iQ*r z3+dLGA*I>x?%hi9w`O-4Ca8+2~(v;4fSyEqi zIuc#zTOSHdIbb*>HelbGr=8vTc4TrNgrguGwWum;gbiZ^?RBQXQPSVxp=)@fHGbqB;$-dt$&i4on19_#}EL*$}wR z(FYZhv49?@%5P)?@q4ou45tW51L7<8*CviF=d6O3j+fh!_GaQD_LC>CIC5`Xf-&g! zlipVNGAvoaV_g$}EL*F2GRr)g@C+@wSUnx^Q$-R><0UaD{5iou==v_k>I+YX>PVoHn&o%>9K|IY)Ij6F{AU55SnK| zl2_aeZ49~IfkX>m{x9_w=X7iX7-XDfkV2K1gNxuA2SP8~Aq}u8{Ng=|eh{#&Q-zh9 zpAP>m7fzP3Ixu-a$U{yjEB5y@X_A_o=F_CZZ?* zP@?@?D`W=hKOLxaDh-tV`V0Yo7{vTu+kmTu>B16`Q>pYq!5fceHP>%Gkec}Shu-RIkJVSq5Iut@siK_8Pfmd3nBB~rm>6^P ztyFJk&-Fv@k5gHl8CF;+5lK`l6vv~LGLy;>~UbI;@SgzGFc510p~LxzfJ{`K#E=zHg3YQyyDh%3R$NNk7`NfC}JC(J?!Ql3A0 zZXLyQ6$gvO&g8}(zwfX5{_B<&9eS_{fmIE`XN+lM!CyxnkmzY>7yocj6K}P1Qr#vx zd8R{bVm)y9E48m(Dq>)!4E%81TL#|jcZ4JoCQ~DLPZ0mQ-B@HB@ACN*(02$hl*ls? z{H370*0*;B4dg3a@PyJF#8vpiPNwL!50<}0v#vvt=wf}KU**FiohKc(@A{<*5 zT0HV6&igN;sov{rdK6M=-M8j$x!AR*5OXhU_YKScFce+-(~A<|2&I~^a{SJlhDd{9 zxebLpyS{xyft6euqIP|y86TAj-yy4pTYS6>)$Q0Cza3D9v3fMgU((g5=mBJn$dZ67Yrcp()<5TkuZab6oI zZO6A)Jb@s$d}kk(kr;u8cHey!Vq8Yf|pC)a{h%-_e(i zt%Q~eZkKdV^sZ(v5;EA-vb&OCzu5Wyl#2~QYgwYc9~!t$Ef{fsJ2L`R*2<>mm#Fuy z(qv!f?)uk{N#)uJD_tc6*EJxY-%{B<%Asi%yS@m}@=G78ywPa3EzirQT&RpPyJz{| zBK{lCn#)h6%sN2?F|^Ihf-F3&+fP=ZVW%#Z%p5+F*%TT+5jt48lmAq%mg{inU%!%* z%)foCvYC@gxnS@;A)$pTOa~R1BG}@?{ZgieDl3axx_Htx7Zy3Vo-T& z*u>F8qcpg^?AN=70j1+uDO3YuwKLBDL6q-(7VHOCvg!TD^710+xQNg^9`uy0+T{hx zy6(*NS;ifV_%G|J@NZo@-i=AWCZ=OoOR+%{a{6AKg{H6dO5J4mWpQ~HXb8!CQ)u+_ zL`cbnPnUcf5PL)&2p+mQ3_hi2U6W1k+P>bT?MAt_fag0F@}s1W;M1TN(+oxrZOQ6Q z4ML6E{?;Qicx{hMt0I(}tT5rSNVVOvHo;@-Ifs8Oixii=S*6t{cp_igZGfk=@TX~q zPd)SL^!ZMuWxe-Ct}9XBLS*Ut)96^bt8~t_5ngTA4^(Zvsd6sqO)33Ad#f$dc=(Ay zt&b+MpP$A4rE}(%z3S%Nqj$fEraB*`o;Yba!({aC{VeL8W8m4$fmqT=^NM|B7_xQ{ zvvoZJ-uFceE{j0VohMtcq4XZj>jXXDL1skLPESX>L!?g@@}S2w=JK)rpKBdt=3y}M z()-;q&7(j0cFKw|V$_S~D`1)4fokgw=uG&Z5`mOBnfy$E)tT@L4vdoRCmH*fD8i2- z_Wwq&@TJ=Z{tbWzqV&tv4xC^6&9{p`>M~QkgRtyzzoj5f@CTaffEh!Ab&<}S^FifK zZ!PnUtg6x&fNKGK^csc3gu>mP!P!zxC&G6D=S*#+TqvLKdca*C0c>Kwk+(%dtg%tq zCvOsFbn-fO%IR-t!L3wCj9qV+O6i({GOfrPzJ1HppI;FyKi z){>SP@Jf9J=)TX|mo-$QSKgMAbI>f=BW@L;nF0YFN8g9=*7Y$s8Aov7Gr0L3gi>>c zYIm}#El1yZfZ5R&fE503MS?UPV()cdLR)Yr)&XLuhSw1(K|w+3R|Ko?h9p2l*D|6Z zv0QI?Q-1lhyRgOkVnOG&>_;{dEhnY10)`pWgS_jk>O9NFXH zc?z`S>jzfK%ExLL4>466CFRhdsv=M+Jw#@!Y!{kWph#z6U>y4C1;yFwY7tm@sr4l# zfA`8SD7cz%Qd?{^poS~phvi1XZD?LDNhGvf#=iV4VHck3Ys?^ZM!5q4QKil~A$vk* zjPak7qE|QZU%iXYH(a`T8ZabK4RtVdIxxfX2<}Vt8Ui_CNkx=Lgyyp1%YpAk4NABV zc0&0V10U2hWW4x)LZvoiyT5shBf_iJs*-=tVqjh|K>b7ngem zv3bgWgK{S!jUqh{Z3)pww{KU$Q8=xb<2zPhP0Yq5%8&A1y(q!0H(Qe;R!KJE0N)tD zf>dpg#$xEk1UOiZH{XX))S5L=&zOmc&{un%PO3^a4Mn)Y;4y?j&B&L`#y6M?PWA>+-@f}@=Dz|O% z=gC8u@Xk+@!stHbf=7*2F^scy&UQWX%;!>krl2wC1Sjh%JK>1X#%{z(iGGJ97!+kl zYpsfA%Sc0t-uNB~$r&kd&*$nABxeTGOVeLJcSR;ao~Ve>WA&fV{3+pLu+_e$7^LKc zFO!|`gDeKW$RFEJa!RRWE60CD%>~eAS$oxane30b105W}PHN=B!vS%ao%?BEwhnO2 z16q;xvCThjb;A3P3?e>4?k7mw_#d>5fEopt5%>EfK8Gk}&I4FXfTi!-H|`l(p~v5> zHFD8>5*XP~C*@3Oq1G;Ia?;5a-3iKB!ZHQljedDo`l@35O-_y~pYv7#^TwKiAqy8^LCJ@?FnVE2(%G=5&W2Z^s~fW^l(lfIqqb~e@5G8&!ZON)0H;M@ehYUwtb?cZKL49hdnN{;;NE`aGuJR z6!=-R^uu!VYQ4UcRngu?t1Z!%>Po)Ai26k|;yp zWfau-&BMpb)Walf97Ga5b*iZ(>F-}zu^*7APT3K;)a4O5;g~|P%&JlY?2Ro0Exo5T zC<3}_jriZJzs~NYugGGZuXx`6nBu^SgCXG>a#lH$rT z@4es^^S1mYntibDKl}0eGNd(?$g*n6urvAq5f{&_sM16K+U{;wRsv5W#Lzv|{D}nq z`A49cw@8;dU`5Cihr?B+zv;eL=^3QvqT#$-~Qs24h4f2P08w01$J%f>a7%qA zH9Z>|Y4@8pPfa!bhjfpd7&!gl&HBxFR;)eOfE*jt56GhI9o}8ZSi+ltW!%#`@9Cj~ z!Gsn&M(ETXA=ojh!SV|A`ZFD_*F#ZF9JY^SU_14qf&Kd`yf8#ys3WgO*N?} zzeatH{rIN}rHJ=<$GMKRchFgWK&I{Y4LOJH?=5D%%ulakivvX&Pxu&?(WG8}Oh-gtC|rK3<{*7)CLMRP>Oqe*rGqq`Ow z7q@U@wkQlMc->K{tHm1@z^JBjYYnm4;e)Rvhhe3hy&T$U(1YW0fIIrnBy7x(bQH-w zr9*?=Xj(KX;zyYJhGOH$V9xi=i=N^G?a?ZcVD07mt`x<($ zl;b~b_>+!5(&8yr8VO0@5_v^^$kTX`}KO4gHfBeFFq*=rvjza1e$Wv z11r5$`S37pS8dq~!o%Wf@qUaFftue68ysSc$ko&pT!POfUEiVGb>yLAKkKiRRsU&W zr(Ol66daBh4$gJldnmXjh3Z9oylKqa3Dx}f6Be?7pLJbcx>tmwTdDC$t*-#aY%k3J#g6 z=on~(pZ&Zod4J$($Sx-x<(KR&(I;xNC$AUvzg}x=mT9}-C=kFkxGT^*ZFvXBcKo$3I9TAk?gyDObUOUL8 ziX(TGvl}6d{d!oS9+ko zZ}Cn^*eSD{9s(PLP1gpk6av={p3o0&>PO90KT_at(w9vUXZRGD+4bmk=;WGn$*F+Z zGG!bj*RZT35)3xJY!}~#tEJ%_UF}7%4W!D8ik(yvG1tkO1{J&tPYL`|!s%)DB#$!hws*?#+<*;PW|gE}abXO&L?>#h~K(a98FPL&&W?WaGgV1vCY zsWX{xqtRa!0;ir1>zPo8|7#!CD{n@%%-|OeAe@KfNKq}1VZ2lQ8vMMgZLtlW-pl|+ z_OvSvJ_BqR7y$V`q3XuaN$HVZgSVBrNUmM?zteCc#+q7!4;YjpBPHN@skM@B* z{lx^LXtga6(uyQN0=N_d*fb;v`%7}M>5ntI7!MxJhd*T{x`X3s!EYkq1Po(` zKlo!BYBBTLLt*Q=21k*33-j>pdKYxNuUezT?J>f>YwSE^UX}e_Zp&5xi*EJ8^3MtPbRcNwHnzSf{33C;%~~XYv_>;*m5?q0rHH=}0Lq1mT}CMl z`ejjcpl*T$hyzw99`C@Z#pYFinZpI$Io^h28dUrlR8(fE9Z$9kd8%IT%Qd|zon7n) zu{iELAz!N5GM@TsYSMX`a+9~gEuCuQGX;@+LYYYd?q64{O?XL7V$^(bNoxMQ0WCBP zfcIY3E+okKv?8=bjV6M9;c$Db4(50qiAFH-P=)W%U|-i{Q} zT!QoMobB<7E=tv-t5*bwxkZ~Y&uj`W4Rc8p>I#qu<@jeGBX-O1@%{o(H8K^RP6^k# zGo>>@l$W1ub(4GcbmHS*JP)xi9Ltk&i=snSa3tO)D} z{_q}BBo(gzXdm92OEC#emj#m#dcRP|%JDN{k3U2IK%(cHd%f-QBAa$DdFmYSSfjYc zE<{W)FoUqyg%M?P?@e5>YPcr_v@Hi8L3=l*sFdTJz3P;s1)3kNyvScjVYAa1r5c){ zNTWOS?R^Z3u?9YEH-fO*Fw5mU=9X;k`vza6x%te$RPsK7&biNTATu!4QY2P8o+%y|P_zry@ zNST|1=kuVMQQe5vcS|Tj@OI=3G%)@R@~I`qlseP2kUOX}JZE^zWZVls=PLSo`pz)c zyF0RVNvMWu^shgH3~}$9Il&hRl$zT;rrSA)e|!K@Jdf@xJWikN5gy(|1ODDWO=fW1vJK8^GyoZuKh>w>_Eqhom*+cE( zE?;A0UIV1Rsucd;HboIM(*MN?D(_^u{?7xcxmw7s>W@y9gvMLT&G~0@FX$K`sWjX} zyFyLc4!n|Ze$wW}A9*44XYIyDt6WRcIvqU{tUwcqEcyDCoMvi*-0}V=(NqfZCD^Ca zwFSp$0k;s4$3Tn=umw1n?3KJ`A-%9wLrqTL(ntKpN=0w+i%V{Dnum`E_tRZD3Sc#{ z9JxaCu#`i{>SRje0qaQHVlCSAugT&L^m_?nX)5FI`h2w=I-G!izM4P6O(*z?!*f6lHqKK%yk zO2wdF6lO*{m6BpU*LG$a+#S3k@sUX7>@NZvceN|~AS_ZKKQm(;L!c%I9CHd575@imRU#*>>X<4Au> z!d()($LU=JOSfRg6G|1 zyA2m;g!OL|dJ{^Rh2Ll=(=2>=13~11)#=L;eMDLB``5nTr!hN^x|rlzsHN9m*DLnN zhE7EIB^z#;Kb5}gTcn#eY2}r=kZ=s$-`X%_cO#-^>+BAain|O=4$1~jz{hg*Xh>eT zgFLkepP?$CDu?i?N6VS2FL%k#Yl=6YWGuY$yQ)>w*x2~9bvMWUE@*P&3pIb`-?)(J zoOt}_!j&8zjep&xP8Sm*QM--33m%)w@;zWPd-B zJ15kzaT{z@us$zDaCvg|fPP6;z2QsO8LOHrxcMbS|4}wdsv+%5X~$Ev=B;h&RA*WW}vSzTG{eEJ({Z_a$ybC34OcsX#IyhNGLd-0;hMQM|2mr zPc7(NhC4t)iHOj!*+ITeMDQdH8lxcp3WxyRRQ^uZrr{_#W$oW(1LFU>j#W7KPC>oi ziLIz~oF7Fs>Z+)X{tVg4u^9YtD0d(HV@aegXZ%BitzEu|S2;m5DNKQUtNh+HQ@}bQ z8eZGzvT%iQ!y=QdZ*W8GmJY0*x?6&eTMAlxe#z~h7NA;e8)kYf`xac7KR4>Ku23fU z)eUj|%cvJiE3WLRwBSqizLA5~6R9X9#ruvar1TE65dM=%ia-uvLxQST7w z-;LL(Hs79P`k(ouZ`=^tVAQZc^GuTVU2hSsaBZsut=LumX!tvETCpsC$ZVrVfo5fq z#kXoa<1F@T3nU&GL2VpUgHiw5SuO$q899|Gfi^bmLr?SZJq}PbzwFj?VEDKTyu?{e zw}-7YwW4vFZ+ryY=hfa{D)W9*P#y<;;crlUrA4zn0w+C;CEx|ShH3Htg7Tm#6^+ZY z<+&~+E}1G*(~X+8TZoMzf>pXny{k#r1P@~^sguTPG{vC)iR$ko4_B(qw6^VD!k%~$ z2${QND<5SrfR=_*Kqp*KK}1*}lbZ>`Kt|sQI;R>IJp4DlLo}&v6$OT>lH5duFgbHH z1i0{PRv^QHSmq$>@C>ixoZhOvz`%h;EYEk?Fv$)qf82qDX$2uVoABxZ;xyDTksk|ng*8)j%x z$WTJazKgN1Gjspf@BgVj)Q9`dd(L~F=RD^;BDX-rZ8NtH5{x3@!~H$zY_8aMcp)_R zcm4egUSl=4V%Oh;>-(wV8;mxniyR9rNs1A2h#UeBP@v=koVF~| zX>YFA|Ld04?|$@3ZzQlwG?9XLcIsOl;CU508PXQGye+*yRCe^wQ~d3R`<)RFoReSB zH8WzzxDn*AuXl4NIAhM;QseA&`r^qEH1;|{#GM{A9I8BL;>L87FNH_ zu^JgqchF1p`85Q)+4k;Mi}>u<_0kCETzjT{iOPEm`U@`}CbF9@Xx5Zc^{Joek{!2i z{d>uaT6z8y{JV~2g9ksga!Y0APJdsINiMoNvlF`FqNe$+ZF(GTWq=xlqY@pVbt>J? z&WhdktoREpf)W;X{oIxL_R)}sE`u+k8pdWt>PCN%6!lkRbk1WH;u`0mT z27s3%Ih7YWj_xf8arAB*e5j~c5+6McyXO}!x8UO>d6$B1mC(<;d&(1#V-Yv9ioLAE zCu#L6pgf79t;ke)HUbO3|FPLfJ1~G}Z2n>W?Xb4h%I`^&BW#WkJ;(jqaDE-`2Q4;D z>1;#%QwiENIycs~O!oNh6@YvDr^CT2nL0=PF+;sF?%GTuba4J;um`%U2goR(^j^M$ zCfA_xx=46EFO$NYoIljP`P!o&qy$o5LKEgkAPY=A=aS}wf24p)6@kV&DT@Jgvtc6& z*Y`C_(3j<{>7L3FGuhlB=)nno@b8+GzWr5@BEU4>tJRMm#Fp;*Uj^B#5klhO^_;CV%MmXA`HB&QB4uXCTrM zTJwH$esm{aECFE^wM%&{4~h=sIZ%*~5scAwg*k-#8MLm5fBIl$XaW^Ac*KT;DLIZJ z!0QQ=zo?O;;CKgn^#iJxJl~vhRvWaRbgLq(VFY_%_rE zKgc12pcnOg-aqB?JN>`5nr;of)+H!1i!j64+ZSWtuo|fBJEYO4iXD1b6r~{j}@T8#SlXn@R zmVcdsbwTW+lb0m58T_+eDfCJVX2qLaTPQ7U4da#(Z%4qM+Q5$RS$J>S5+D`gONHiL zl@H;2#=T6uvSeOsnD9#uA5%glM>I`M^?`SUozpk%RKvuRXhdJ;_9?WYFHujba3lQp ziH|E6n6t#FpUa6t1|ytFspcze<~KgFlp*-bT9K(8Mx)BB|CtTS|Ko~Zhxc=1=1AGM zmk8yY{oHKCZd;=I&)7-hd>x{?wF?$_k%;ORN)`y;Me}>#;DR@y)F!|%-!*u?De-Ls z4Ch`XGzquz#FF>>Mxz{8jP0{S#=n{i5Y+aLn@e9w_GtAyDt7N_{EqJTCvuuI@*DWn zC`Qn@k)vA09zJ&N&_BAuw2|YEr4m>ffScQvQnb=$6RLJlCeY^VrSr7mXOQr7&$wU1 zp`D{DY`W*{3H(JWr>}Pj~i;`)UIc&f8MU##8z(*?A{;G{f7bA6XZil( z+M}Q&F-nbh_n<9-8ehVr-s4V{?V_)aj|wsoGs|~f{Y(e-WG#;tDRXt?D~n zziiOYB1rp>AR~jQIQW*C4Qyf_;4Vc}5-KOm1cm(KN{*lEQ_A<%I+b@mmzCqCp2Ee= z7@oGPBEJU02l?|Uud4V%v7Qx~n`cjCI#6un&y)rWqHahZohLrXs+(%Maf-FSd;5)n z2fML{2{CU7J|brS&o5N{4s)Il1#x^}b455OP`-}NcC3C|0O==LUJ zgpXs%tB-T=#%BjW7Gp}MyqsA0FXGnJdWGa3c)xnV$bG^)A1vIYp{OKf+Uy=ZrWGFn zznoIgr*#ZoUkh2DNkiAF%oeDfk`WwZ%j9qackThw0rmt1=qWKP{xUzkYd)fpnvU_3^(l8<*MjnBTM+mIr4OA_FBTbXD?xMo3|qft#KwHdo!u%&@Q;~N*edSAUF@~* zyXT`#d&Iy99p4M#ZqYUP=>+6C z_b<&0bx2}zD{^n(#~z+71P7b#Y)8V?#-7tgw^ohcvx;pG-64{{eMT<*_<`KXw*DHu z2OT3HE?*@brIknfw4T%rhQFp!=EuT83WGRQt+zDX5VZ5=LzmvCXok}tBhfv}#khRX z&Z|2b9HXO}WF0q+1Q=B-9hFB_f~5u|;Ao3rizy-G5;>igB3!J5*L7nyZ~w#pnVJ37QN@l>+O?cF z-U4a%@J04|p_zBiYnOgYZOxqkt~`o%HGH|3x0uxA*w^2)`1G`9dUpX~yM)NbyaYRI z#~Ua?48BmfUlI)Wm_Xb@?f`WNqQ(r@znptaW=!20(o699`qe2!>&|-X%-}j?eFMei zczs?|Ladz|#M47AJSogT%3mv(qq2;J*OR(Ybo@P`C^VP|AvZ#*qt!RIOg z>M3Qw3??qSi>Xv0uO=C+eFKlFTI@644#iuwWafy(?VMKUMRO=V0wNRP(A3h=>piR+ zG3TNT!V)9(yl!SQ$Z&_RxZabF4n2$23a3X&)_}p2Q>lj&XIoakM>5rxzde^c};^kO4g%=6(34*r$XnL(DClhY{aoi#Pc#N81)Q>QJ+f#aUBj@VhsyN)Oi0c zRO8lX{Lhz@_}LX=WyqK5My=5d{t*|^y`)7o%TH zyap^9c8D(h32_g4r#klgnB`d5YN;)U>)AGDtoH*f+oZ7{l zYq$BVTg^mrl1)@Oy-N=3IaHneJlI%;ly+Z0NgM3rmWMuAzQDXvcG>S)<}V^>MKHNP zI+-s@6)uTIISh=dhaFP_)Wn7BEe#$*MN{=GJF)u?k@su#i#6l?>%_c~=oy5x3VOq} z`b-+WML>yJFB2J&cpS|u8VjZRC=}rhWN6I{oyQgtkb9LwUiX*j$`YK~H5)Msj@aw4 z_;79REYpoY-JnFdU4^p4^ARD2O2wY#dfAFpg}&n8x|Awt4H-)zm@s*m$*VwY9ZPR1 z5DQOk&(7i)D}eMPe+8M-CZvW1$x>V`oXgOTdiJb)=7|ED>@ z;hRVce`|3DeNlPa@d%PVho?g78|=+Zyu(6Gn~9}1t^eCfr|yVK=icbYS-RI)^lvYG zbXu4nvqM;)b~W_F2CW$jqu^-Z#>;9%JcH_!gCxAS*IzMi59ORR&cVSng!|4H6|e7U zI`qa5m=G5Vhyr-hzULQ5Oo7GPOK9^muNvQoUXjnQi7m}+|axD?M_nFF1`z4Y=k6zkn_s;Rh z*w!^xg1J3lfe3JcIV+RV;j3{JDct}!|BhgmkfpopEB4{vObJ|FfLk2gQ6K?Ok>7AV zc~VbH{ne(}%m;QE2P)6ddiJlXF^_HMopQwfI?DGjaGjzLd05D=u0~#9y?(F$uv-a& zKy>R8ET%7zk-B}u zef-pZofE|<|Hpk0`tW4h*Pbl7j(2M$9D}ER-=_R&`fq=zD~PXdV5m`zls0l}lb~8+ zh#@DsN7-ZM0HbT6f>zts5I6=v&s{7$@AQaqNIAXm>6auM*kKnKcW?zv))c}@+GFfa zwKsR0zr&Wo?|l^6qXpkgceuqfKrsavdG(3_5Dkmqj5M{|*_W}G)R-rksxFTfI9HvQ z-*!G#e;wBzrW$3@wN9GdQ2ERh@=AI{@@7#5KV&hCg**VXgwP&(P_!4%A`}gw440}1 zL<%!W(FPt^->|cr&Ym#rdyabRU$_b{Ty2XsSc_DH6Er7)>@WP3Ow)`@bvG;jj(8u%Zw< zL0Pw7Ardk})#@X*DeXv6?Xl=BGE&ku2zsr%4-uu8FhhD3GFD$cM)Edw>I1BFXH-ED+y`e&*BAWjL<UqFxmVXtD&NC< z_{@=P>G+So*`0rb-W^7lphe^)OoV8r8xRoS2YcEGueSyE+Z2dT$)$jnD)gnaW6nH4 zDZpQe;s`+ObwbKiwD-;$;5r&GJ1Lkh@P4_eb4o%%4cg1 zH>=pr^>^>+>R*m67{HyS7HiCIrjIDh?*<(0GkLw>xaUA0UJJAgJHFto@g;0Bdp?svFv;i%4uwFLFwyAMU?H=7-p*>z9%}n---r`{?1Y+2DQrtdK6;_OQiQOa2#Juo0 zy##2!Y$p2R<%#HvHDgd^S>H<^b=lxSu?_2boe+aPAinh>hMXFkMZD=JzAX!f{@)mJ zObT`58#4aU1j3o0B%#94P!*!da>mZ$A6{lu{4>=UR*~zNjwtcl?yxhR&A{tzfZw9Z zKx$Dp-i}dWfoXfUMkRuF!Lh=C@ox}1z!O}6DbULtTMQo{N_sH|AYgtwJQcH*K&5ux z2J3VskYY>tevgqKBiYzD6_vBU$>Ix)Z4tzZQjcEAbZ3+1{KLcR$PAD6fH~lMe-jJDtPXB8B`IjVp%(QYuQHjBh@|Q z@Yy7cF*v^J6t-0}#Wht+h-H4(I#h2cEOGoERytk*eUsw=h~xyeXj)V&qi>cSK?7Xv z$PfkF4gPEJh#A~p^ReiKgI9E+6G_NL*^L{h3t)E9$!q>CBl!7E%O%GBnWM)@;N9Hs z>=F)jxBm8@*|pCH3usceJRhhm4sDE@j~n5kkebD>FjzTy6$N%}Gzt(f_A*I=z;4KS zs4{%mAi>rM4>*N60}IgYksI{?*;nC&zj51m=oZ$u(M#>REXF7vP&)+713gJcC{?MR z5wo&y`aTAEe^J~O_Is7Ncr81p?c^TQ$M)IH4gdZTQG>X)^J9W1L#~8+Zfve*Nk$Z1 zn7r^Jkd;i02o|}>Cz924f%u?+7T2G|0lytPNj@sTBG-M-het&<2&;#3ogPBht;*tNB6snBABA#zioNv^zVoQ){-Wx4QcW1>o)R?VI$pic) zTF)}2@7C4lOV3~SKO=V6`VEDfzTbVUMjef2{OWzgK$SCwUbsd*{IaT`3R-RE_9;#0 z_FZ|qCN;=FaP;P-+3~6m3S#1cca?zk!C&1X?;sQ}B6QtHh&ZV~4MDR3YMo{KJkJ!G zHzA6SrH-{)3RSDYQ=PAgvAzgAZ#@t5t))&bZwu@o1VEg?*u}G)h4!s2xmS;@*uorW(B4Io24kE-T!Up!4 zE#QH@y}kalc+=I}nEhbtv7~#$HyXW;zLyfVU>$2E$eRK&7nhhF1~<*@!~UsDtl~Z! zMrH{y8^YO(_5Sf>1Y=^9iw(-nQdWm~@+jrGVvmHTqkHmS?ltXEDN@+y#l*+SAFkXE z%MPG2)AmocHZ;Er_?8cev+30*?WB{tk6(ND7roN@4B|%ouq%J{LtyI=`!U!SgEEDq zZPQU6?KP*C!MV%rY~A`{DfCg+16Xny(kPnFAg1;+1@=`|X%yMm{#8o=_KTxMZpByK~+22sY`DG0HfZbFGgdQnyuqUM6AzT$8 zAR6I$l7t%GpZCuqtTutfq+TZ7@Xdwa{m-+yo9%;Nrj&<|9fn!)xaCx`sts2oaS(NW`dA%;ksY;a36(z zd;7^YDRdAnKkVf1MnNE#?kU2xz_DCczthr8Xf0K=TLbN&z{ZBSGygrsOSd`7-1wd` z$`+Z6@;JF-ybhV5XX8#?tFJh-`x=ZW-y%i0Nof9^+LhW}yLhyGIvO%?2+a> zaqXPc&kK+P9GNVN=|_y{8^4T>oyh;&Yj$r33EsS~=GYm1{i-P;QW!RPcpj+z`LVLI z-0-IlE?9E$V;j>L>LksCBQQ5deCNHPrbyKB+}3eQ%0>w3!J`+|qn|%Y_9Ee+l&=D;!LJblbWjMbQ+ z9010+oJMjkNk)HHP@l(v42)-f+LhY|$1aVwluwL%AZUi<>;G#B>dMeXq?Tnz+2rK|HD;I?WX~T!opNCL@1zA$LY0yV%e# z=5q!3Wy7Z&(Or&@z_8QnaG90J@oTO1R+4Dou>L$2d^oX-n$Gon*Fu8a9QrlU&xyRh z#|CvnxBh8XaOU)_CDl4jgS@4yoA-RcY~ek0#i!9ZW03v7l1;D?aw}jWL~8@QPd0&@ z_JFTod@=lt6VuNv*@6lfy8`bKz9ZJ$*8f#7ntRLQ;q4pXW>~E}vJmdhhF`>Ol>BwQf_>*=v!<8g{jpawx z?oJKS&t@!o@8F?@fXt_ott2}<+-EZe3uM~I_~@CzJkCy!=(d4 ztWd5TB7)w(@h`qQG9Ih(ZZcvD4hr;-e0jaRRuJ~CVJVt6syT6ry%eC%*aDB?P&s1$ z`%)!q07&07>zfCp5$R<LV2Xe+G(KM9z(Y{?>Bvw${#?9g zYsBp0DCppG-wQo$taV8i5CaB%2e!k ze#5PSYsb$H7|k*uO~)*qKQZlvOXc*ajNPNU_XZWAFU$A*t&KYvk1GOw>$OJs5)$aOZ$k~ z#zffZ;^1ZLUuIHriOk*~qG4ay$jKAhY(AgNs;eb>95|#sKYt4m0UivM-_Nli885Al z?DRawSqh_TmiQ?W%po_ZZGH?lV5Qc1f@93~G{!hGuySNR4q`!W5CI5ucOn09;V?Sa z&VE{JH8;e;yg9HKC$hx?pza!v=aghBIyjA)NrW_M67ER*qtwc{JcoZXmV22odsN#{ zF31p1$@|U^4aJ`KwLCGM#&doElzZH2XlDSL^$H=sD8wo=f)U9x&vSN zG-ccwqNXw0xQOltL_oG+e{@up1<*O%bxro_ShT@Pq-jtR@*fbvDR+)^VKN8rv)z^h z=KMJi%^+OyT{{C!42|4_{3$8P;h-|rNOfb>bmI_&T?=UCWnfA;D6@U=@rMNw(n6%v zN}y5O%}1H+y$KWvem|=tH-F$}KdWf(V_z8$)V-PJv7u&he{ferA+(s(I50QhCFTUj z&~$@-j9BiIq>k`W$7j4d(MOy7z0wic78E`II62ROD@IE!(+#0kt-+dXZk@+@a_(xU zMf}>$^5(Hqg*vxSQG`b5ek15zS}=%)^Ie6%-j_ziGPYm4bVZxY$a| zHEg-{nDT@GYlz=+t5$)2)46zn`lOF30K%zk`P$M8unXei;?6#|O`X6^nO}y!$M7XZ z>QKkqibq;>A!CAT{!QvEiT|70hAs6O2Z9a!IJZ&JQSEo8YWr^{K&emR2>E*+L3Hp| z&^?VAjh7EEMD~@gTf@{t)v~s$Rq8QgV_BYx;pvPAP&*<4McXzkprp_5YQxcK?<&V} zr$!P44mDXXz`lXG89s*oRUOCSY{cXUXor;us?6_9zju;_3=h{AWybj`uwZS}{l0na z;9gHFKO6lp4yOI!_`ZlYUarJJJC+T>? z&Pm_WyQ3~s*nrr(@s|gr|G}a48eEd(6t}xh796A zcH=X*a|QOKIDrWGbo@ES*ee$p<6El&7@fGKqQrCDS6tz~`RX57kf)r;S=E?#|Ep!QeC*2eX!EmigmI&dEqb7SBi+ z%-!*to1rDjL-z=G789jTaz9Z}WMpWda`d4ujbn}fez9qig?IiNBD2xRE_enKt@$$w!umm9sNISp)_jlB5k z-%Hj~s7QJRH}@r1-v_9&;;KvL1*U|@NTH+8Ob{1xG*1+Sg>JnV@g@0( zGa@K2z?~JN7ef~G$X&tl{-`0#kDbGTgQy?bT6gQ?0vj5h24sGjH`vVUq+84esq9cf zuORllu$kkc^;M6Umye%4RkffkCm@`6mxpoUH)@5gFp3l27&*8c?drx%d(!C2yXn8K zBr8JgCrQkSmm5!iv>S^Snm|~u{Q!iTRX-yg_ou+^1C$ zW=EAkJ_>kECETP7YFyUTmX#8kDK79wD!3L1|S_w@;5i z6~u1QoKy}Ho>2^8Dd_2tUfSn2_Aw-|_uL0Gyt5Te36uT}(;4`@0>Z~2PiBbH{iP3v z&Y%>)hwjt4Y2lH=fZFlkEDH{!@k-%JH|e&Z_-BV#mS>$R#|=G<%(fOUoONjREj}9)qW{tpv8NmA6h=Z=KvePGRh?fS;vYGlX z?azm8HdU8!e=Ng48RfX*X#kuL=#x|?RSdOKb&L&6$r(wfKOWM7XLXN`l7^?^x0dez-Eg5Bt_Nwrhwm*IiRu8n%Wq8yztGp1 z2%lUqxO1{w<)X`_iMPurfufe>u`ftSw{xmVq~T!jdC2(58$3F?!O@4t3`XRtcnz{3 zIEjgeWonIm1SGZ`^@!gzemVw(P84LyQSS-RY1RDm2n2pnxL!Vp1PO0iFb3;`|; z=;{DZ;Hi9=1=!~&aEsc9e;^FEOKs4C5113ct%j>|b9uD=i<5{g z7HcjzTal!@PRshkyGz;M;U|hElWNQe{qW*uo@o)A?dJxg8Mplu z%mpH+L!2qHdHelL3*nwk+^ZE5P&S!Cwbyd|+@+Cq}6N-4nn81A6CS-v&=UuZ>r zl|)V5Z(npxet>_`7>TF<+cfI}3p@OOyjlZ$Z7k_g9t%8gJCB-x)oV#ad}@n4Rmuc( z?l2l84Xjaq?{FOOIEV>F!XZd6q$`W5R?L%da5e;5huEoivSDo08m6ZZb{9g#7r`$S ztY`17b)c&#g?w)7sV{#Ft~X!rbFKdNCbwbW4*z{GwPQ^mGM=4eso^fVXF(UzGE)Ee z5L3a7JNN8E{OkBD?@FZ`!=5|Zs|AdBKC_>7WEsQ0waB*d`xLgYBv}$h+uu)0_xgU@ z@6F=NcLSoZw7TZty~CO%i7wY!!e+NfVvy)UHT9Fe2v(kdG3{+!P;penec~DRoF1Jd z-Z#k$i1V(aiPxIojTEL5s(t9by2-b<&A9>*Coe^;jT((y8jH3YM~Jt9cSZeenY$l7 zmmoH2hq$NFUp_%Ku4$=_U0D%dfyhT!1F&R6YLiy=?e#1kTK%{9UwQlS30OHNrP}s{ zAw!jFCn4#t!?M?^92q7}V9PqxbcG?)v*bj*LC{psplM9 z$00uB!*%KHugjiG`jMsLaiz1*z8cH;WHuf= zUL=7Ct`+teQHH_rl(=0OXYFp)>rYpY<;vCH7f5<9c%0N-p44(%{v6xb_sOcUh?1|3vh zfee|88^XZ%5LTr?-(c((p-hs@{6bPh8*Z+`zs$Mvmm0o}hnkbyo=-Ikc*O8yjju<^ zQbexx&%EkMT(F<4DB(m=X1P0;a8ktl0+*bywB({KRVTOgQJ$G0>&nX(s|uvIlPK{* zs}Z3Z%lZA7&Brds;1of(NAm+UsIG+ zK)4FBg7R+7^8Lzr$WkVlq35_kf$AW=`R$|v{I`9tX;c)qROhTS*t_z*TGql8 z_fU#f(w=~M87l@lh%Pp_(1b%zOkggCfMc+3zJq|(0_;p2+=2+1sn6JW5t)}C+&F}EZ0sQO1730t&Sim}MGGhioZUgs3Jx87H9sKh7 zo;Y_KNW9@G9O`|4^tr3f>2K zRYtac%)d2Xnfv!Rl-|56eGbi;l#yEE5^G0D%j`!1*-PZCSanP(%$y1%x%g+D#)~>K_ZnW zjOen1!zinVcKYZsNps{?YXVDDJtpF(-%M{!0VRi2TOP6?B8%1n&1HN#m^>MD7KN3l zk4=u&Csd3z-DDo)DRMteIER>FRlkg}|HcBf=P@TkVC3+q&$I1aNX%F?8F|TD z7C?z7#G7d0Pt-}x7v{)k>8r}YzEv=@C2tQB}w zh8L`8{uEJ{r%N^GCWJS*whoMv-<%c{7 zfkDme$jL~HY2N3|2(WvBf}@Ls1!deuPQ7MKV>80m%or8@3p8X+Wkf||}V1325h83zR6t&L^A_Qc~e3g=b z1pbYHULf=?>gnkXZvFeS9{F9#kKWIEk@8QzIC#r?KR=2uPxTNN)FK(=WmmVYl>R-# z2WTKqhDUU`{=V`FU!k_?ywIpE)F-L8uW~6#j8b)Cw%l=G%K-mqJLmV%Ge#HG?y;Ol zz|qimA45wYo&LuU*kmxar<>?3ug|H?9- z`sn|4qNHES51a|;s+e`TQN>g1MG9_GT`{h}$$Wf&asd)2K*Z;IYd0}{SSUF@MIFxD zh^~)ldnLe-wta~r+&oBoW4Qjy;wTLo%fzPH07;R-#5uLUo$CDS^ji&XMf?&YoHi-t zdN>iSw8u&@sxWHTYnP*fhKKc~ZhFo?jICaif;8zw@XK4MZwfJ7nCArmB*3o@czDrA znNZG}{8PlO6#{7Af5JT{B!Y)m<*QjcU-RV!C)T9doFi1Xix6bU#_>RQ6lZJ93kpk^ zn(6L{GNsuOSe5^ze)gM^g+>*`%r5nt7OE;PG=6m|sFgEr)ZmEk82mAZ$ zh7+G3dA&jV`>>cgA;>37zFp0JA)dv`1ou^~vu3x6&CzW&_nJ*=GHd*OL&Mxtyq^O( z!e#j3TYEmpEU2fyz8$vF(YK#*`;Aw#BleZf7pBQJq*7CpQwnN>fx)%0V9nT4!S(qQq1#<{EU@7H}_W7{U}Pp=*{z&<1*4@EotoXzzVT)xq3Hdmw3`9N`cnMYR!-Xm(6vIt z&{o~+;Xx!|$WqS2FoT8+5rI4%?TdUn5Bl5360BOYy_yE?*dMftG#=&bx^fHY_c$Qo z#t=l=Mh2@sJRIe_yWL~n1p#oV#m|jS6Et>GJy6z_u%oMgzd#XI#}PhA-5{lei}s&6 z(|eC3BQt;M3WnsXLBdIo$;gmBRS%K(tgR4W_JU-js8shxhm7x`X|@Bt1-5XOmcPX)ruw61c7LQm0GtO z>mkQ(O#JDRcDrw2?9iqB?`(NRY5x}k-{7o0llAT^{Y4!0;S?*+k)!yi=vn8QiRfBK z=*<=;ChPG)B3d{WaD0SPw+lp)^(QU?&h-M?X2ROLrcj>)`uS<3-^Cm_A850GEv6Z- z&=O5Yd)<6?{;fMCqyk@xoqhDZZVl|zq|lytO!AzV&Mkf=2)c}b?rK!1QvI^V@G+c) z*7WZs%|IJN)d6M>fs#EJ*f~f__7}#8(}Yy})<*@&j#>xoOALRaVkxWorQ3zN9AojC zUAjxJGO>Ln+Q15o1=zmtoeZ#ak8eC=S6D)*c$->s>NKrK8nUPpQ+T;W9ghcU6kF;_0gZ_W_ zMJ2L>1{d+4+bjDfEn{o;3#We;;NQ@k{yg*W;HKB*;O#doZ}`&R3DDx8-sa~$I)5Zg z`XB@nnK4}{5Nug2G&xv9;R{hsdQ9RAdQ7o=V{`R5gntD_pS>v%UTNWb^g>;6(rf30)i7r|fIYnIQZ((pk$D=V6QaEDN%*z z2bnAmKY*x#=3U5h^D4AX6a7N`*GTrqPKXY@Rq=0FIpC9lFpOo07m+N$rOsKzes%Ce zo`G7aOq#AFJPOov$Xq!4v+df*)7&z|mCnVmiM9{CKbmU(JY|t-3>Dq(vsf*2JBbrZ z&Qe$yvJWNku8v>2hjZbEBrqme^JcHk{s@#^VgfX4i>_8juk7@mfAU=H!|+CqW|h{% zpo-P^8Bn3Gixs|c0t;xwBOOo1-U@2RpHc#UPUezxAoBX1r;(=!BCP`pqDMm}6LQ~b zl&8B~TVn8T;fqub7)lwtBN?`T>aJY*bD!ZdVssHw?b|a>e}t5a@C0;vHH)na?C8ys zLlOE+WHv$<0Cjhlf`!M~d*f{e?sya53};RQ>gjoD;5$+D8_$9syIcUlAEKB#n1LPGF9gjJq$!e;B4?T=JNy^*GFCTKlZf zqX1%#0><1XQf%h8jywj}6|-;N$e5EU)|Hz1+r-t0y}q1t+obp5*SYL>&!T7Z%tN<%V&EK9vxa*FRnLfk#aqQzI> zRy^2PLG=XDW4PxW&#sc*heTY`fx=K9t-kObS!$~DFV{M*MNnvD*(*G9 zesxqc^f7K`)c)y(_oGwK-(0xiX`@n@2&ok>>S{Qy;#0lu6@d!R}=Okpt=S4L>vnoJnsb*maB8Ib?YAbU= za3A+Y-d-Ex3*FcKzQvViu^Q7S3hf;4xLEF}E=z5f5?}6}43t{Pfe3FuI`+=3($RgD zSs4189vaQ&n28czIR5$Gg`h7;m6zElwu&Ff5K3)ysbq)b&U*QD{kf3*4>z&NlNxKr zW#dYaUabw`5(G#@PqgDnXGGr5=DJDnG@+{8MC6kHe1uY}*kN~w4Tl8(*Rt3AC7hY< zS6yrNgZ@J#7Fx(DdfN~^`ao=gC=0!W5oE_KLPwyH+)zO1=>k}xKeVKLc@&vujzvut zqyW~!E1x4GA}loE|Bjz{y%gru`G|xyaSw^(6oawRzjy8U$^GoTU$(a3@&CK4e;ebXVj=a_FFyly4ZyroI}VBoBhSP z*B`=4Pi3PXy?BDsgW}BpX)U2F<1A$(TDSHncJ56witQp9dAe}bI3Ll5*mNE+THZCw zH?m{A`r!&H_^<{L0LA4NVQ|t9uu_dG;2;WBhrGPNpCX;?c0zLS@nXrfL(zv-cYI{n zW-WyT?#7l4Kjgq2p$^dM+x~rWovOt<27Ee(Y1fozzZLy+y9{C2*>PW0eNy}!0YZ3v zD$H27dQSs|#I`1_6X-Xc`p_&;B8spEiC(RXkd-;#;erSZ!lI$uC$SCD;(&t<)r=Q4 z6V(mc>j@(a74^F(KtHu9*EKpjG;{ePp8~pmSRb!AwX1MgWW)5ah2dif+s41SQS`#U zx#c^S0SBx${(~K3oc>jsB0O?~bSXegA)*h^?W|B=SAKBG{=4(a0{kI zs(-@U4GBWKnOxLioD9R_NUgQQ;bsj)$d!-C;>=%WFs%wgNV369O2o-A>Ls1@rVHX4 z+BL?Fa~rI@wV;{Y3)Ft0Z+`Rb7vAT?`us5xKNTseA5b3AZqu7)rr7vdgYItDSdef; z_+PgG6>4)WpcPsSZwM_^vQZj-7IO3JWWz?wYNH!+bO*Z6+5+79ip38m2in>JR@BBJ zL<=q7e$uu1#D^GHf=%Qdk7T$%*MH-JQa#Kn)kUX=M))HBoV zBT(ZE7urjgfxvQ2b}e1BB-2@JFL9(Ig)lNLB@3D&mBMY!>E~+# z!lYKVO`_%5fQHmy<%7W|TQ4r2Io*5eVf}D<)Vl3A)fdPUZ8vr2;!#4kB%NFEbQ|?l zFeCiaGJHDZm0AmWb$8JAc1AHtebbus_l zmj*5%X6!5sWbMCS1-xNtQJd1>ZTZOfB8uP-ou(^34^F{ya7xuWTDB+*{hR9?Ka?)d zxWD%LUCWp-_(o|e@&-x(Zs+dN=}A+$nc?V~t5+|L!Ag(=yfw100zFrTu2bPp6K4&> z0xjsPb~FqX9K$w({szA%XSjsN$M)D)PYt)63Q@v%G3ZpS&9jXcU5RG||V6s}U zgV2_s3h}d(K-C-VZuyRL`lgUQg&wRN%kPZsqkUkUtYwPOVU16WmImOfQqyPwGGiA@ z>4w9lU&KOqR^JT z!do`tmR4}QX2|}PKj6SLm+PyRlW&_Mt(Qp*56^s**!de;Ha&mYQ~1kbC?u{+W1>n> zwt}q%)b)C{ps$uykJ!dP7#y6VeISA?d>rra4sQg-L*g#FYFNmSQH@w+@|40Je&pcU zh2zmqdwP*({q12d;zq#|E#&#N)bwH$4^zC|5aKZfBmkOSu2rKX869kMCw1Z}x+y1) zO&Fk8lwMuwd20YX1CtY*lXBmg;2@D|D5eh`4t-{T2Ah%csXqgUG|y<+JpY5X`K3o6 zeqSykuvif4!sHT#oTir$u6;aA%$|A`_fz7h%}|nll|nGrqZ^Iwi{BK!1z?cuv%YPC zzu3BkW#1Cs{B#DD7fW%!143R6$F{(YR43GO14@#Dfc*Lo;u9(G3$=2I1{ax**QDJ! z0JW>u=SxGasjoAs|My&qHb$e%3m{VYT{~N_4B-`%%v*7LGeH zyp$g}n0wiUQ4~E4);OQhCI|icw}#`B0Xf*!%Yg8-II^ClA`3lX$1Z$5{%MwYYzb@D z*a_BIRQq~t&{FaGTuyAWSzu7t!X@)ppX*^8b#b9>7&n2CS5_gAZa!I;84w*SA`@OT z7t}l`YWD_{b^#~!k~7*85leoyBt)c&ovkRl)7IrjE|I<{0J|V8X>6aX@RA^#QZ~=r z^;D`CEOED;&26U$C{C`Pk48k^A?(WD=EC3q6#U2=#^Ny(XAQtptHgSf5h;dNf=)*L z-~&G;w~rVSJhpxMqPVegi=mM1ue=knL)j-8e>Tr)kO+IFkIpUae^rCA$vrdz*dV&A#nRbDuUH-n8C(R4r_uz>A&^L4a`zY@$0S>m#PW)tc&f zN;y38kkxa2Y}GWcBrzmvLwPIu3*(z@)vrR1XeNqppH(Z$&owu6e|+yRj!MyX;TGWw zxFSgtao|3GZ4eZz}FVWup66))UzB-B8A~S-w@@_Iv9tUxJ1bldM zdK7Y}5F{YNkY2E&(3^~DF9E|5WH_7fX)t?4Aai!XA$S2RDT3-x+P&Cz0}ez>X`8KW zz7a04gfaS@vLt}>bVR#{z3`ittcWT4nl{zGfnO(AQ0lBW4a4AifY@Ls8%}7#3_-zf%d=Z1UqP{+%pf03w%r4!#A&Lg=am0cd z{o^3MeJ^4X-YDYQWVmtm4x;}60cPAf#1(c3cO5Cy+AAjnZ~Wv-0=OQp_bgrP+GO;k z#3}nf6Dntg(?`K1xY4E;XU?E0HGb#Yg6CC24#%%kB*MNFpQ|DDNLokuy z%guJSn3Cpn3C9pCK2D7QeTsP}ztvV(RW2xdvZV#i)Kc_{Kvp)G6FrGSoy8GM(KTRKy;*xj(JONyoGUI}NzhVn5I7@#daLz)uuGHk>425uS6R^=Q# z`{D|+L9T5fZoTzmdt*T@(ATGb_I^R7DjR>{8B&7hLE}mHl$^E%;uk?-c?WAOj&+e@ zUF%1a`iMwIv%VhL2WfH#e4PBYPSkUE0O^eYjV3`7Dcf#K8Pi1ffel%UesZ4iWX%)NiY5y(1_LwOa({q3sJcG8X z+Mi5guvUUzjmp&Z4MVB}fyecqMwXn}J!q#0L`MzDEU4nr@T+!kw?S4ppRW57a8dT45t0(1H$!JnhrG-UpXS3E z97F2UbR)Rc&`I0Y^E;@^z#jcKj%!QRnH@%Be3B+piKBm>qwqixmhxff@*xU3IbOPcT8^%nHDzl+Qd9aVzyZ5Ud6 zS9$dG)ytN@K$*Fi6L4>U^{9{~M<~hia5H{q^BsBgQ^chSD&OHQA{VSdqwy9x)=Ccx zQBVzFX;xXE-LIV@{}6i4<-#>V50*JZIaq0u*L5G2uw535tDDb3>aD(yA@#$p3%D}?g$??gIpQmkVHZA#NQGWDkT zW~bxP6Go%-L2G}TumeU1h zjgr_~Ol_Rd-yOBXC^k9lpiFi%WGKIc?29_|MkqC7ew~9Aa*O2p4|pL+Q-R=O&A?!A zCSCeC#d<;0z5BT4-ob@lY^G!Vl2Cc|&ELGS`NvmdQH~p#W(jKira(fArl8$$;0P9ec?NIjh3q=NcU?o8ZrHZuWq)Rw^vd^J z%bSK{nsAVVa^U%%;brI_3iV3Ig?7aV7Ok0?gHPqJczC>7?em5+DS>}61+>_HnpxEv zVuL(0Zgrd7H9|WxgN<<`O|+s;oyv?cgoXprCx@$HTdI(YO#WgIpaKP#m;jZjdOA~Q z8*DX-vI=(8wlVsx%$xLPsT%)$i~LK!#SG;-rwT8A%GK|3V{!ebac1KYyidw&J|Q6Q zNBh)7>vM;|9@ymox+NB`$I#85cUXI=I=L$Gc8?E^vac2=p4##1@M-A?jnNyNXd13) z8vYdII%X+`j98aMd?SheI{TnG^3c5r;ki9?>MHL`QmqWEj+%5HmQ5n~pq^biWb03N0+2#?|^qD_H=tp-6e4(A_TE<0~=C7(3!y^Dv+;+ST* z@r;mRyuUVJs3B$#g?=;L2`)712zBJ94Nxc4`z3W%Iy@%kj#hl_+~3s(q~##s6Roo~ zT!MoPp1%QTfIPkfm3Mn2?q~k|R?ta3Uf><;-6r^}rnJ{G!;2wiD*(6Wpe< zJ3VM4#CTuj)j1C;;$J@Ms(oTV*6}EidW8foJwUFH)~m$i()S*ypf#1V<3eksh$p(ehu1q1;;1ZBmVk#gq@dE**CVy zehl4|v;cw8K-%N8T(bwiOrBH@t>(RD(ep~o9=U1_&`CFTB&itNxFkbp_5N!AsYFPh_ zcu1w#(Ry-G8>ER3CQo|^C)S@Bx(vQ)FK<=@oT8M7H|ZBwOn zvu-_EymqP5((z(~j6eKMqEm(#D(Ra-Fqx80qepxeDx9+4!Cb4TRCd9k-x6f|s3yAw zs1QHHzjMgfs#O700T$`a;0=xa$z%47vu8Ll?kKHM+o5jN{1;gICwlTIH9a>pzo0END z`E+%FN;=4wiLI`@5G1U7+BQf#2m)_lEn{6?#%jAkR9zm^alW0syIJ;*bt>gFoA6HF z&A9bRADTIkK{f->24t>2Y=pt4UGp{eEVkRe8s4koFDUVo)W$VL3}N#O&9|M!pUI2z z8UhsT9baJXQ&=&hG`jQjkhSEAS@f~v751Q%E&+~g;l>GZfsi(i$RO?5v-L*kj!dF;GD+r+rj8e79Bpkm z9r5p^;RYp2KovBHS=$0`9yejddzH6VMd{(MpXS302S;`DseGuH>wZx@VJilic|j(F z4MTUXSSoamu@&-I<5r8&06kL_EX;`9=SK~XaM2Tpoih&X z=vv&sJ$x+_TetGB$+WbosA;$>NSi97aXM{lC+%#Sn5iXXH&zCI6fNVALhK)nswVnm zKc@6??J@NpaR~aIb|N{{Rk~*KpU*83^l2g$BxmEvjFZ$7f{-jJKEifiR)MxaUTpzi zpHrC{oOIK9QZPVI9GP&2GiCKH3?0M;!v%ve>Ze=M_g9U4Z-Q2D*43_r#z;dtgz3zT z!16xzEFe4sojk*meX?(MqW5~2$HNo+@g^VaBo%J+;nJm&Cegq76j(Z@E@?e^PI#-S zw9G3aLC4^G0Z}$KXTx_mnAk<1!Zr75tBLbaec?k(YhKVRbMOGaLN+1YAhLN$dgbSM z0c*1#j%wdqtD1(b9P#Loq~F-~=j6mGYJJCrTz=fUFef>ZlAleCSPW96tiGyhk7BbGI4G{+s0`Y^`N~O$PlcK?+@%1&@??l(0f;V!*1zr%)4JN8{L^Sz&j5CYg<9o%vM^tEQ|U%%+W6_uPzT z>%GdT2T#+#u2}q)NcZj0xgb~9m#yV8C$nYbEO{TB@`gy^1w-aKNf(}%wT8A;etpH7 zbbBIhtRF##$-K4iY`*bcMApQnU2 zKheSEi`by~N)bo9-zzICXLcXFvL!Zy-^c~#)}HGWJK_e_y|U-vg;=7ex0CU%oY zVT`C5<>N8j?d8>h@O$+zO8CD4b;%LzSizr`nHl|vE7rP0*y>n@$tQL1wQtAUYjvfLCl`Z?2 zi{rSI-mN}>f`RRc5qh-rMqcm$I0WR=irbX}xIbky@WwA?N!-;3^UzRtxDB19fdkZ_ z&79v~EBc9BfSm>df6)-_bT-AXC48Ly{u<^m>7zISa#&5z*0V5S!FhOJ#NtA4q;78g z@&U8Qtg-0QO@$4MNS;{?$7AYmDP$7NWtWT_q5{u)O)!CJ9?7J1gmu0}Ab=td8u)5U5bralX#*%V zT-S&SWNpow4QeV5v^e`iff_-amdZtY4Ri?PCh13Yi3+Q;;}c|4DxyE#8)4^B z$P^waV>s|Pe5WaWh^NWgZa>fGBx5AqlWql;B~uk<>SO#-sJgM^*7Lva^UP>*-PY~(73Xe{i5vx2tosc9;;(G zCZ@?dn|2P#`KW&K=$kT&?+pYdS_OeaMJaJ>bE|`c_HmWS=%C;>gb*(dZ$L>?Y|PBe zda^r>$~!}lyfFVx^uDL>?@x4q7JGn-lspq$0wQ(+S6;=9GkCsE=h2@d-vOdOje0_L z|3(_TtL;eL4y6rj3cT6V_s~6-%xH!a(BwjH7O3ru?Q})a#3#;YUaw7J10L>A5zF|- zc~cj0I(P9JBXR>@Z5KSI8#|_MGKbz;uB(N4HMBE4SZbw{rt4xX_~O&5+bwV5sInZS zZLKi;$jZ?LAHkDg@vpG=zzWXPnI#dT$?EuaC9DZPB<)31{5*#3A37Sng+dF~JJ?v? zw?CKfF&(^N)*%8PS8CoHcZdgrL;(2iWAaho38>h5t^)8yoS%c}{hd3XUqShYPd*y0 z_%CQuWBn?${%8XdT`PFlRt%t&&Z-=I0xI^2F5HX(vIQBLM!5!5Xjf&LRN zRPxZ)tdOr;?)Meu*s9bzM9FMq?BV^9G?P%ra4nkWHM`3XB3hgltDg-O@1cJe>>S|5 zbt7dctseqMHqQ_8jKmFQrYse+!hXcH7PGdT8;>i!{evFa78JEn=@+qAHfgkI1%ZE; zz&Z^-nst$a3~=Z3ynGdFzTdpCgvGj$ zQaz}Uu0!xmd8C3Lbs9pIEs?!@{@a^_B*y;Bf1H#QCZM3Y{Oi1f3l%1ybreQJm2M@v{gCJ(Mj!5@1)*J6wLr3HOwk+#dMFvF; zvp1pFnf+1vB3AdSxeq4D|8#I|viBYRjoI!y$kibjaocv?b}0Br-18=H_$tTH%`0#< z;qlYW_-NL_`UbvC%E3}Us+QpAtEId$NEbTd6e`i6{-e4I@Bp*^ebjO3fX zK2R{eSRnl&b$l@x-|XIUpJ?}+&1p-y!l?B`WHMLPA=9eP7JuMG+~3ystg(NF^tiqm@+lDhW}m{pmxwoP=o$qQCZ`)=r z@7jj1*?qYEwPXBrof6-aXFHKGY(vlRkWuByAx2Gs*10d#Pj)a(zx0RfJ@mhGlCVh@ zZBdZog5i&otVtuFi`m7G8YvFQx8LRjTw(xcX!z7%avbG394JBs1qBhK=oqK>%mlK= z*PCzj2^H}deH2g-{BR8-uD}Fv8pxjw{5Zf+Hu3s#Oeh}!0WM!JH@xEZuk5k_Bsxb* zo(|THziOB`2EtZ-4?}Z%{{1;%P_W@1*~+fIz-T`rA;{D-r)kP9crmQBgsi#XZO&_c z^yaI#>OW{v_KJ>$EVwoIzsDK{N@MaT_xw8mcgrW%>kU6k0cTC)^gVBH$x1F(E#S7Hc)lEXHQSe z7@_AsbE9K`UGzUgZQvQC&U4`CQh&j(;?%{#p8da9JhyM4E)&+@pr1z?1N4vtQ8fF- z`&?-r4TTVEb8V>~uOZ5CIEh2*2iD&$o3r?`az>yDDOA(_y76mg{;`p^R}{7f_la7> zT)x!#z*3L)L(R`JDJJsW`ouc&{hnN8&#d6)qtn<#GRbE9F@IRd?js9Frhia8e)t$G zYG^_qSme~|K!6hsgKAz$ci&u+Oc-gcWEE(E;kJ|3x{GCd{xx8to!>uX)BFo;7g(G( z+zNw*=5ONO%ABm00ct1WJb%P(jM7sDU#-zRPe4tZPjS{9RS@Zi>5&}&q8E7Z6MExw zkEoXbjJP zN?e_RRQ)0BMbZHs5bR(~J5KN(--HRFV@%kV!C1oyY(3h4Vp0hRz>183$@q0(Ox9Bp z(u&Ij(s^*t>2BsKEarhG0k-c_|E;})WKq}TV?5G2F6d7~Ka1d&7;4hKWV4Hw=HxCV zwE+O%o6q^QV8N1mYZtPLHU^+Q8u{fCN5`ZEb` z)P#R?s#G;DV=FSqlUX}cp-|3%$9iCMcL`y<6oPfd;cq^#b z4Mu!0`r-v(4MFeEuZdAMi+@UJ{KQ)jFX(y)EQM{-Ai^~sg zGI53jC+ipC06mbvgM>kRuGvB_;LOHOpfdqxZLsmCEKD0sEd#xwZS3vs3rD+xgKNVE z7(KtfDHAqU_&#>)i74F-%2kXQr|F+);5xT$*pwcD#k~Nx%}8ddGrgR}QfCQqd@Z=M{VNXKp_=I zuPYFWu>i)#q(vnE{&X_x#=ra^PvWCQg*U|jvnU}6S*GGR~l^kZBd`^5^?Zo?ZPTw4V68)!N`|@Ae z2j37LRFfj>8xN^qLFsUIa5^A2AGLG{!rRK`FKcPNhsWsH$a^RN+xTTv1*`D~E$VdJ z7$9pg(5(1IINF=B`y>p=XWLB8ptIk_$h&Oq308Qedr zugl=`zZZSNd~zyXv&T+F8p16{Yz8s=-hc`E=#^-76h5#Wn|zq0^^P^^{txide$=i9G);pG^Uop|Gihl#%sTB9fyjk==f^}JK1^o8J~JYB-ED@E^9 zS_ztqwEU8%gbkt)-rmIPv_aydTvW}(S`ux@eumw@(-rPM%G{1vI&=Vq{q<|k`uh4M zpHi51$JOsIU&;i|z;H$Y3l#m(5<1EFG@`~<$?gOc9QI=a%}>89jBjHUYPk(IuuJZJ zDEzW!+cYlKOt_YR(v+@*x_65i4K@H2sx#EmCSK2{)<}(A-cL1<51OvYk{WXiAeXpG z+P$so?Y#EJ9UG##OA%}5yKZ1-Jt58a-Pl5*hwWu^!Jq3~-FG<1>z2GaO~XHDz%%k_ zRVPsUA;gr)vB;aD-70|0SFJ7Az!m*rq-W!SW7<+`$_cS}j3DZXXsI;sAmZceR|M?Z zQfgj-@x%(92ICYsbnhkOxe&b3?IWQaVZvrN)u#uMPT?A*nbmC8$q-Oy5_~rw+M&GX z#h+934Ji+%5L3@r$nW32eY?w@Xy{wV0@eh$GJy*Kn92M+;V*>`;f0|O@?pzoc&-NC zW&8}s)lWEuZG#r6ORMvmyy-Y4!USKCXB&#lG6OC8$V~_Mb$l-8#Su{tm>}cMF=(V4 zb27(nvI-zg$57SXJNh`7Z_x9%-b0!9yc+{02eGx8moP%^-%n3mO!10o{OFzHqA2pP z0`B1!+xMpf7r1ndw^!kxPUecvr+-k!^u~U(SByPtsuts?A_hv@4AU=CMvX)HI6L~ zmq#A{R8@kX12_%@^^^E%3#5t`5T5EQIf!E%a@Lj^-+|#qKA%m}213>|_S>`<(DC9{ zP%yZh%%mzZD#K~c2__1QP|pw{>3Ym9$Z#Zb1I9{!8~ zQCR$y2c;N1Lif$Qi5n$@!M_+aL%aHWKQ4QVs@5WG6-E)NG&IvF zc9ZNdlXRTMI5-S~=*#2SwbV7_%3ih|s(|ocEcpb5Jcpy-GvK;wc$v@>WM+uQ`Lq~7 zQcD>Jb|E%-UX0XX4b*th z8K#_|wt=w>yC~x)tT0RNoZ-amDgO4`xk;8W^-q&2o%2vTRe6%ypU%nOm(e zZ-(0fw9AY1MQ6%X2R@opAoE5iy`WrX_pOXv?QmRe%r)M2pCg5{&K|SW>Gfh3K!UE5 z)X{_n-|6mm{DVi$dn`KQ@+6z#^jr$0L7e@A&sv6Od=_a*%7t~kyu3V@Md_3pWsu0I z=Sj90r|B8)dr8Vu_-HBrz$t=$OXO$rD9as_1V-TU|Mnw38YmxyxErI{paKj!nSdcK zQh?{q>!FjdgbcdKsT)k_5;$YNR>aXVcqb!^QfWWm_-;my?wHr-UiouV@yi_QCPoE=@<-?MtWV{Kf9a@Ih(R+G>1;_g(ya%johArbUraW5n|%IOs5 zh5C-NElnGQAU6>nlz5ZQ3KU@vt0>gk3QSUF_M-A%+n&8M{qgREL*7N*9>mb$K{qv@ z;FlpkNc_~mC-t3Jc6#vY6vBt*Ycz#bp!^pv%(neD?&r!g0o94}Q8G**=_Do#WYA5+ zf&7jeOdyA z_GEL;dt7rnVdP1Z_%4bRM)P_Pa|5|0+~di)@*oDzY`h5qZMiC>tOz0 zaO15ua2yh}B(_0}kKv7cR6nZ=7Y7vA;LUh=nJJwPD$)YfL`b0}ASz7A?>=XL>^ne@ z*ag*g-%CqN54~3%zA{yJ|CwFT0c&YPtbyG3N!-;dXs-onxaod;7UWNLxF>0$7Qj58 zUjm2Qw0E}&U+Vj$D>Wn}vw3sI+HtwE3D<4F%Nf-Wt@96B3_`GK-|GfR%pgEK$>Ohh z{SHx;fvL%c_nk_gtN=>SpldFZY}27w((v$U=;kc%K;eP%Ke|uPZu_3|CfS~45};|j z^XTVLdxCE-Iq#ER6EJtbVTblPmX2(tRyR|L7G1{=<)RB`Q_r(t;J_c`KBd-TU`~NN z&@_QzE&uQU_t3o3IB+NtQ0krhGpKokU*F>zh%?AwCz_8GEzlN z0Mkl)yP3s}J#ij3HR>4DB?eMWolh=}>~t@zmd)Sqbg2lbzV)bUo3yRyFSP0BZa`z= z=ai2|%q;nJ4V}Qi+$e=|GpAe1I3{JDE^6JobLaC}pYpbpwIMYcnp(4-Ph%QItRV`y zB{5}@C~t4Ue@0XYWVh!s(RnBSbMO$i2UyO<%J zN_h`-0BbsbPT&Fzr)79znaNM;Xv*m`=mu9|qxi~uhOsG_tb$T(TZK6UY#D$U^Y7Hm0XC4nv4j#0h$&SCH_!-|^;| zwlr-`t0KU&^SeZ`TG@}%=PLtn%6?rE*Qp>hOqvuCj&36SEPx^@owcqM)Fm}4x(B(T z+|$pvgbm%L^Bm1%tsg=lDAco32hZz+ZuNkXh91;^Z=2ZB=F1nrA`tVz4u$zAgjc88 z?r%;~rSl2ZxyOXxsZ6~9!$#Cvo#Pkp@EfU0?BgP$0<97YJ` zLF5e?a$>ZX{;WPE9jb3{x2!vHEVt*LZjmiwZK-SmQxqij?%V|Zt-tSBEvczprdI%& z4>u(FVQ@+L1FP+%Ts9t>Rb9g57msu>W@U}zH ztBDn;D6eCPU68P`BOc#&c4|7tseW7J<$mNzuLGbcRw}s7iq@~J`kn8otu*-BKXi0>ZR+nWpbZ9s&)<0xm)e_ zAA4>IlP}UW7YC%IYxsez=gg3EvPeTt`0TNuXF2M?!|=ozZ)O!bIsAa~9NEs>d-e1c z(H+ql8(+DBQ=|RQmdH$!(89Cf$>1qP0{H7JFc+lDWwgRwU7(l+JwZspH-;Ss#>4Vg zv=X*9U;h`F;Mv?&YOWh!-4acT+9emef~r{XCE<=qTd#wrmNxSv&+qHj%sU@cved0m z`HZ%*4X3au+UXkC4P+Pt{(wYsN4?nVibP2ey_PUXlmY_iy!@pf6}vX1O5-X;4U)YZ zkMV-1Mt|=CHuRiAJWMmEZ|eXRkJ9L-8OL1VdMCPDfS=rHMkkog;H~I(C1qu?0tWO- z?^T~8rbftxxwQGYmrFyfvl{C7dxREW`mOhnCuyT7CEU=xf(W|I5A6yVm1gz9D{9Z& zSp5sUp6zL1njP9O|BJe5>w9?0!@sI-*E_l|i3FMqVjZ3WfEU@VI=h6E#PxP;F6G%* zrP!CX0d+fty#0fjXLs9j3?X?XcSqh_%LZTD1+oPekwLL@V495?#JU|FbdlS*NtB3E zl=2+7+=wM-73vtF`hQ%YL=*t%ex$QC1&|HkDF$&eZdu%OEm^?F&G=Z%r82b*{hS4K zu>u%M{g3xHh)PfWNRPk{qq^n6t^wyK$%YCz>@9`y{x9zNx$0xLTXGReW|}_HF;@h* z>a(ZCaWW4y`RbW&aTI5@kwmJ$BD|r_hQ0Cun)CAHUkeQ_N_xwroA`*^e0qnEUXVIA zUu}v;nuJE|d|F+Z%bw8P^cdc>)o21tK{ZJCOuWz1m>!vPU)<12x7tq^Q*uy603%*jtOmWvgv1MgT1hUvdAfeMd)F0@!=Bnq+qER&dRd;L`s z!jlBaXxqlx7rp9P25Xs#r@LC*sckh;w%YV4w)Dnclft%v3Hb ztGEk;)TFj=BZjL|P_6Vn>2ct@IQKvtw80z_rH0oc9Cl0^h8|p=qg|W>0r)}UK`vDu zOgIEkIAPewK9{NQtJOaLfvFOzi3eMpKY(rdC@s|@Ay`o`PyzFA0rs^o^gtTLffQPR zk0l=Pv&5tOWU=H`?}@4ir-2l*F$n}z{|yazg198|3eM3*V^&`qU{#ViD`oUL3Q#hd zLNDTgS4r#?+f;8{D9f3}DQy*_-+{HGU7NC<^>NjW*T|8O*y`-5&c!{#htT*Hoz2l> zlBRtH>Tx)4M-6z)J`p!RG>Dbja}FTA_Uvo%SUKdqdW>5ND)~%K_(Zo)UyQ^e)>d;i zIaVh(IaJzK4|oT$qNHbvl&&5sUXz-Gm?M4~=KJ3|8;!#i&<-QIT^LUE;vGxoG!qA0$9JCuFlcwvj=C^U20jp(NU z3(R(`84f@uM5it(xjZOHc6XBfIC@fPR~%}03weLSK1*mR4y9{bwk|LvbWwXyD@Ize za3|JxxdC?lniwD8^Q=+EEkZxqpUwVdN-Qh%q&LmYOrAfKpYVt{)*K z(YRQVz8%E)l-L?su@8ZN!RC(YQdtu65f&rO*P_5q3Gz|t z0%`V;mU{p{4;aO3lC=R0_zUPQPTUTZQx%@d@}^K#;8X&K^c~iTCx9fIOx`v6>=L{+#D3T(LR=ru`F;qv zb5VH7If;;$^uzwj_%?RltnP;z)ffD(Mh*9WO#?hn1^Q@k^qFm5Ng^)+4CQmvdgH`L z6zUe$1kz2+&PCntdI14K9P6mZLJtX|DC3KDpoa%~OB@6nKryBbGtgyW3=p7`JIWY= z;d+>hIQkAVrv>zgLqKJ#<&>l~`4fBZ`@80HDZpik?71rgV_7%bkiaK~!ze(c_>Y2B zue-dSBmj@E@CreB4P|3|+z!KA9Pe~{a~vSNQhf^k3_4;~be=EkSon?7Bfqce~H|sV{ct^c=&pB8w?wW4TKwNm9-_=2%<+9 z@W%2iFL5*TOlDRXekCU0B+B1D(dHY}N7NuQENZ?T&iNkcx2 z1NI8t=)rWsW-n7F#){!7{MP1V;MB5z`KJ{flVvE1_eWfKI0EH_@FLQGaE@`{UWvGk zV5u5s!`#`dUp}mEA<@?betjIl85D!Qu|566#yKLf?ljItpzqBvWbD;gc@z`$>b&Q(Y{G;Dtn*Hxf}eYSzAcs7d*>7 z1k|~la(SPxe}V=_teX7PE5nVr5e*&0ZFj(fdh4(AE-3>PMwEwC{~wa`c2`3cE*dA0 z77D=+GpWM$aUb~6y?<)NkM(C~U`0+%JQta8Er2CEr#JKRqs=b|k&NAsU%+ef+&7;{ zdmhWrK&qnQn*{t4XPPY^8ecmp($j068!)SdNG1HvJDj}TANz#V*Vo{Cm_;f@E`7aP)PS~Pd?Dwr5cOL{#F}rN->6c=@WTwC(f`+*YZ)@1Zfdy{ z>7y5Eu&$j=BdX)L(D;K+kbbQEqT9fcdgw-4cH8d9&IMc>Y*qVTAkjBtM_*-ueh#e% zMNU!ZJL2dC`h--Lj~te`?J9BblbRYM-Hiav3P!r4f`HyjOHxiPhf(|c`yC5J7=yx`XgTfxyxh3#FKqv$d1jXx-OZ6}@PS`%`PvV8D zU!bFk$Q8dPlj(!{m(qdRxr-TvN>@BAe8PbJ8XYU3_+|>{0vh>(Ufn2NyZ|yglxFbx zR_}bMBR?@J5H=*<$N^)FM)vN}MNgbhWrXDy;Jx1DW7_6S@|B4K3o3D_m@R5=YbnTk z#eotQ+u^UW>wb7&3<-9?YK|zJ1I0=N**0oq-9r$6^Ghzd!)b3SuqOLui~sk5FNpx~P{=KP5Od^~1(I8d zmZkTA$3@@XL(+KRN)lURVA!FD9z~pTpQ`oX=(s3U|E|d{Sjxf6nznJp!Y>r+6NW4K zu#Yx3<3qcM@=x47TYys7khBnhy1wZvdAP&mvq12|1y4?~sL3SN@SF-Zk{C^4ch-gX z@%vA6wgJ~&)EDb?2CMvElA2`XKyiQaJJxGWXih7mkM5?yb7z0sEP0WbuZUil|KfiR zW_Y%`WvJz#kI(bM?m>s5kr4l{i{s~QK`1`MnO?XU+omVA=0!1~%Wv01Et8+6qilm; zkYm_EH6_UGRWE_{?1a<*uo5TXZR*FXoW}xybwEV|1mT}wf>iN?m(^va0hVwH0?3P_ z&y7=f-a!U)KmeK@v{SJ!_cS@^n@x5Yc;dB`7xx}Lh|-GZDS20_LRug~%| zxO$ww*DlwiW~i>buO@=d8==+`7^T(`7>>RboY)eG%ndss1=gj$q&Q=`1@IwV?W9hx z%)0KH&4~}JPx;wqsXe00DEVcVXMdamwc|Ml@JvbO$_1PY$*vQ=!F`F6agdls{C0Y& zA4o}EK>2n%v6q`2#6kXZ_<~(Kmr^EOVXv5 zSHX6IM$h#3QA2>%t!Li>uFMB0wK=-06ccI`^l`%dE`R8^OZ96Qv>5g?t$}=o#B)P7 z*f*Zsh`Y;&pva$&wH0!=Cv4C7N155Di?k0m(77pp>%9wrKt2J)?A3AS0{t_mGbzc2 zG)=+>JPRRzYDTU%vH{E3N^z?Y+>^ox(gwli6G!5(ujbS!&m8~g?(Dkj=+A`EdHh@% z<|Ea9HM!oXnxs9FZ%xL4RP(CH7es)dR^Xa7rR^1}D@==EqatYSOe)est?mEeQh|@2 zqs_=+o2`QiQ-P^e0MZJiHl;H*4_x?XrzRMn3@de1Ia{Bp>-E5;4!{62N^R)D+Nudt z|N9ctzD{ytP!_AJa@O}cjr$(DX-*Da2QD{xR&KeaJOT4jIYKp3B`)hf%rSd;_NBh~ zjqY)q>s*-&Ui#(wV=YXsD&Kkqsx#AL@!<7w|FZ$R!Cgp=a|`t9TM;+7c`W`!beeUs zP5DVh_@AWcp*|6EZC!p9=`)VX4C8S1o@RK__y(@g+;?ANa^1I@Y@EQO^wX7e+FlLN z$+yTCzESj|R%EgcC}mwI<``7*fqd1k;dwcs~M~QDY)8c^=|b^U3umY zfvLnyg^q%IqRhZ2({1b0w}yI!BF@NJW(f((e+5_7yh86K=S-({41!-}uBOgIt!HPK zmu_vmUJvfvY`S+{r6K=KqaPbk{w+p-ekV`x)+z6A7P$Pexi369N1D(_k@tj6Vjdlb zI@Ju6iC2-gUnDw{B-q(j4}yC!7nC+)$>Mvc97APcK(A|Ej)B}Yf{mozMRFMtC>Z}| z*EGk^z}|xa+`mY({R9jUIvTeh-ah~Q1Ka0fy5?g)v)YTf+~nI z5iG!FVSwdKl@<0`4!5uE|wkTH7~42xq|zXThV^&du!@ASf45d`m+`!NVgva0>HBk#LpIH z0hPU0Ac6RBC<}mJe7b^fpaQj) zcVe>Fu9uHcH`qz%B5Bl4@4Ld?PQZsV<{ANzm%E;gODe9@`L5Y^c#?(hu(q&GosZr2 z>=Rb|MN1->=FB&s%jX;UWf`1-0Ab6oXov>sf@C9p>2asQcTU_$*gtK3O600aT zF>plgze{euJaeO0Iz^ZIrv&)EZ2=BY-Zu2N>3Xc{wE|E2q0KV9xXXs)@<8uN88V`&_|FFrOY_8DtjT*Kc0g2pdR6SxQTN-3u@8s zzlSOoLDz$$s3Q4QfLxHO4@id1?=D0I>-e5-#lh5cC$}eCrO%@JJ;9ms`ohw;(QF`x zmMX^p7JT55W_ zVWfn;MZl$>h380-!^q=kT8+9|Go>PF!`uw9>bv3sq?{jv~v*o31kvd}CyUSAT zj-Xv?6AQ$J&vbs-Xkqq+8Jv|P42_;oE;6@f=@2~4vRtlel6^b1!4~s`$u6k%dk_p{ z!hb>z|M=CAn=QhOo46A%@3(ABKUal(8qAusccN=>y;MM-cu%miFrPT!^*AUGNY+-% z(WVZRpZl(v?FNM4;Hn+UiX0K@{t2!|Mq7a+_uhLJuRviF=k>co83_8P%W zh!l=G(13D27X8n32iz{8I4*#II)VD*2Ef}yAwp#ZLeIq-vvvFeU6sHlFxEK3hmV%) z!A2rCS`ruo6rwJl;DRQ-g+|J(_sUG|mkZONA2&O=E4JQYGNR$3{llpSG*O@E4fw6@ zKLHn7px;xmv6q5J^{WDER?Z=I$Lfu8IdI?b%T71)bARv0k5+{Upjb$n)<@6M3ClN6 zSq9!}h0r`Z@;|nTxu`oi7xEh?xAgRbG^Z8!&uD4q-rZBDQ#wsg)PwLl6lYt}gspGb zi(WE7gYU*V?s$u)zO-gF;q%Q!&`o{)uDwya;OUC`dEdxI@{BDi{UCUeEWMui5{|AW z54-{@(wvs^tH@gWy49qHK7r(`IEVJrzcA*%;sk~OBIxD0erkxVI!NO&+-N@N@hwmt zm8=ZN0@~J2{R-TFjg3wOw#2cubS((8wiYhx^F*VBuguSPrROx}0sSd~g&PR+D9 zwm%Nt;M;IyEd8Qoi#ln5IC~9R8(ra!OniQjGgK=8f^|F!SejU!0IlMbwl6$);AYc) z&v;&qj^|9*iCw`{JtBy+KV=-)+j+xrTiSWM{MkW?0GVd%Z24~u=+=c$=ww6kd|Wt2iw`AzJi3GY zONB{FTnB@8up1d{j_J4UW3GWfueN}81-3V=wLnT#6J!H#9C7!X0)g^4 z5yFw&b^!yU+%on=br|2orxL3j{mHEeSI-^>CK^S%Q=9Hx3l29B6-|XC2&;ij71{p) zIk(}L#L=qo?VCntXK#JL8So<09AP)aU1MR^Wqn^y_tNZsG+Pk1D@EZak+;M1EEx3k zi&2pD4xhv?8VJ!wnEX|BC}*o!->zVu#oo>_m9~!ecLb50v>g>dH*@&&q;qcBnlv_q zitE`oZVhW$b9gf3a3q~{yt+R+13Lv4?lJVu;816uhLUK_lQbWGf^U3lAm{B-s3ni# zKl)B=+67>@$N+IDb&6j(;GQR`f(qXL3jrk?sK7+INIDh>oj=w}AF!s_8&hI*SEH5) zIzOl(SKfn@cwu*#XX~g{?_ZQxJ~7OYo!d>|RcZd@YK>>0@$$UuJwim5SU)cE=0F7b zGIldGnmPp9USJbp<7@$aS$H9Q**qtw*z0m+qR_GwN9Em`DeJ9tqoOI=l~fMty2aDS zc4wN@#55o~{12I%3<8_ZceW3O-pafROWHYvp`5HgmrLg2YAb#8(TY=^}0( zCuGzozBb=5qgoLxBc1D*&m$kF8_8PDI9xeTSsuMA%ZLYifBChFH-z*}sQgG2pcp1r zSUQtJKEaPp+S^a9+Z|j<<2!Y=PmK4Mt;i&H6#7Q&hVeiDdNwV*1}f_e1I=&<(h{jG zp@3nm2iIT5(twE%BiopgUizqgTOWPh``?pEncT-Sc$5y);DF(j7%JTaChBhsOJbw2K!`~4jC@iH zP|>pRx$}stTsoDIpV^=h{lv3rVfBLTNMFW_|2F+j#LP@sCnB2ZwU8E$t$ zkPDNej9nyL#PMRfZ=~B+X3jxk_W@pP>7;tEkBK6`p7@9q(j-s~#Lkh{sLtK|DLlT9 z@W0skY}%CYjEa~JwY&_#NXoNQUGYJGLu^0=VD;dpS`db3Rv1l3#n#e~NEEKXMRVH| zK9`h~;A(1Xeaurn9(4lA1J|Q9*6tt6A9|`DqT~;7UnXzgSn``OSzw<131UQbJ1Vjv z^w9!zu=ZQG=~Ahx*1=*Kqa3+c=d8riKgRUR@M%2@Cb;GgYhs1Bf6T2_@B{E1lY3-o+qEp2{s{qPF<72mv3JY&RJ> z{0yn-LE}->0555uC>!UM2!%E>%^D$H4ZP(=Op|Hq)b(% zJ?V#`cv%QH?ZM4RwUF!fr9`H?T#3?(gmsW+-O&Z`fgEX>!o9p9=0@-&4wCN?D0IR| zj>>%0udgy$hQ2cLVetNQUcPQeG5!>5`b9Ey+8xe7=U5KEWK1J02Q+QBL;zrK5a7bM z^~fE{vCgD8_pvl$(L7$A^6)h(>dHT$CmF)lQpoE~pF|ajgn??cmC(_B%(IqI?3Frd zp!sxGOO$@W1H?`jTT7jD04y!DqxA+q|CO^&8U*OT@k-~9)~9XiuAQ5SS&euj?RKYT zp)&zfU=fy^SATTR*BiZLX)R|fh-hu`H{$f&yMj>lX-ep7J7AdmuUhElBok8Rt0f_yq;Vi3)RZqmralyotoV%-SqUsy<&dWIpgPkpOIO}#7svVflzKU}!I?@jdW^dID z@n#Alq)>jmm~-AMyJG?|4z_exaO}RKowz8oL?~7@u(DN@qOu!l3DK)Mzw9Ts{ZUc z5twr$ZLZ83ERyXzfr48ghncqcHyqM_oAgT2teqO{zDf%byaswtN1%eqOJG~`IYET7 zc^dQ)zf%I8r-`G$MN4Q7C-3>kSFq(YD;ZpVGa<{l@Zhbfc!1*YF&X;QS6tdX!)()C zQpc5*7L0Ms`b7Eqj>f+-j#hx~N!JDG)OZ_h2BDy^kJB)2gJZRksZ%(c=OR~4Qdtf#LJAllTn)``Q^A8lJscN1=B+>GG$LLD>iJ zgj@<;r+^#j-Bf-U5I8%$KHalLWget!p|W=VbOi$qYLT^|yYYHp&1;S{udtX$ksuWb zQ0;UvT4-JfdO;6XCZq>D&nGtU_)W??ssVxXD+DkN2OL4cN8rP_Wq>&u?B{sv^$ou^ zi+F!Ige1>~RwGZY{%OxB*?I4f=@Dd`0Y`VG(_byQbcfun4^a<{ZTkrd5+uF&AZ#fl ztR7O6ir?$yjz~ECPQUn+BYR_|YEPzIE@%4nD1Y$m;`(5x(KY|ih_as-CBEec^$tgv zUca6h$=$9CwXD*n+OTa{lkPa!X1g#;0 z07kJ(?5c}0NT$6vSS6!2H|*fqP&<|Cqo7L;&BOsbigKE6VofP{+<*+^?yi0;|yI=3;HJTYB|KH3Ep7E`6E0y2Ey{Z zmReQ?bcv$@Ocyr2vaaUZPfFF{c^dJ6fEFb*~v^P^ovy>vtK|2Ee`ej zakyVK+l^*|s}XDcX=I7gL)%YIdj=h@`cQ(mQSOwb3!yhwzeJEXgP!_WSo}JYnD@<3 zM~((=j1 zK+msPk+jgvS>pqy)V}uJ5lizTiZ05;0)35L9D#VBp?jWj4YeG;9)6T_(uvsW{%TK$c(e48aBPue_mvs~}fH#nXOz-f-&QZBQa7L0D* z+RhgUCWP%hau6jZx(blu8OYqC&g-t13Pu^aPg$tS09s`U$n!pSA{r=qG_VBqIXu2P z%`ZJ*T>Ok^BD294r$PUW2D94<}Bm@Sfxb`yUKfPi1K*EwpmOXp`$BPN0Ag&z0~ zJ)01;NDrn@t9{)nmZ>HIbU-Vf$<;62Cs1%BhNaXfOYxn`rlxe8#9ZVPZ^d6zh@qYd zOVPcb2tS$pAXE4dTYdU-A<2w@yOO%g@xAhE9t}QOkP-L(p~ecaIOwm{Eeuz>NYE+A z1Im`c%+NHYkfO7i@1pi~AtJoRw(+KMyj!rt^1Y-<#hsmy)|%O1J^F@Q%oxaAwvuun ztZr_;^wezZOK!yF{o^W)`1{)V=~|Q6-w_c=mY8#f5(KGLHO)OS=#yeh5FvG&XY3h0 zC9-xI8eykau?bqER^~r*W7; z15)KW>mTuiJu*Xo_0q&)|91<` zC~1A(Z(sCDs|UB9RpH9|)-Y7_eZM+p8Q;Tb=REC${}%QWvl&hwWc_%u=IQjz>1dG| zWpl76p6GgQ^)^*Qj!I{r`Sq~_7CiHtVq8O(_)u8yge5(OY~%C2_~)SO)|X{|{o}Yd zV-!QKc#`zd(`BE)KAfAtK2Pf9aIb*c-JYXraocN7qH=B)Ie9xjk%S)XDwN-r4o zWm%w<{HmLyh{O;LS6R`n46evMd0r(WcQW)2K3(!1qr*shfAp^!9Re^#8Eoc@ep04_ zwqs1QsYZ88+ikVI3Ec6CFa+}vC z(8{!}fhAnQGjY?4m3ssDFq=l5Yi4EX6H0DbtrNF!cF>l};Tl^RR8SFSP?Jh}71r?S z=&~LLX|fRZx&MeGOpU?rY4O#(5~=`g}T#2qsz~?HewW?*Vo||N5{kViSHYUb>rQ zX6uCIDC!Wv6jrWyVZ~p5v_6WLzr}RU&|ERc8p@(xH)=ljIVufu zZ|w>t7zMn^{eUEU<1ZC564Y&8n^Q?wlMK&(#wwaGSo|oIm87jP#nz;M^+2qC@|=^= z^_5=EUD(5WK6e)j;SN64x>ZhHZ_vM2jSoy~(ceD!Py6eU9NX)=+1q8k<`$gZ?+KX7 zs(gj{y)P#tH?E5(eqGwcwf7~QYbovHJ$$vI20LBHP~<+WQam2N{Fr$~WwOcnY@bTfcd$HeR+>{dV${~GG}(S0PEirW z;V%$HO4yFoOe4Od3&b;T{Cou*A$qy@nf3GP*`JAZ7aT{Ek?Qr^f7<5~t$ReHQ4LGS zUo@d;Dwr^@#v18(wVGpSO<0%NZk5E537Blr4l)3Zqu2MAZ zWz|J0&Fs65y0wAFjb5Zv0k;o7`WQT^_D-6!dQK8ct5+>anjf0#QHT6Wkf?5(t27z+ z$7fm+ zmX(a8`*qO7+^5p&`7iT^p6;JpRis=+5?h7y&&wZ5$%Mc@ka4WZw-(-c*<&ml)Y_so z;bN_iirye;?ZvTf+EA1zGyhdWsV(6-#xSo17gxJIs)0&4NO27r;tQtS`1l3I5fyk8 z{Do{rXeT&aS<*Qbk-VmL3jQmcSTiZGDLdO@d8E(|)T+7dm95D%Wf9!s8Mu%-oYu-cj3e><{7i$?&+?R zcA~ZTZgoRMOia7~G0MWtb0$06`)+-XfM;m;PAxcG+M0|{JSSJk9q}o*EgCbq6C90c zJQb@%_H5+ARr)eFDpR<~audGX(G=W+x$;*O;j=4J8N|O@1AbaOi5G+(XXwxjZcb78 zd4RmB-0yCLQTjARn^$!IsK1ls z>+Ih*eoRzhT3CMrR!H#Q{~#SEn~gm9cG2EfT5U_FusuqE1L-wl{)xQwUFuk_sGUVy zv!b1Wl3Apa(D+sRIag_0E8K`^_ZoE{SoWH_oDZy#2}YGD4p9cSj-LaYz+%6n_SfRf zP}S@C>Idzg{@NhWyEABD#RzkfB4qG0D1kY(25kpygu}&qg0&uOfGI*(RUk9kLLfpRFO?Tfo%i=fuOXt#c;ni!wfmW-L;G6y{_OFn7+se~Tj#k_ zsT4j%JmsZs^1#wW16^<|d&?%3VTa|_CL2L%!cghQA+3g~OOXbcxPbi{jz_Ldd2K?s zc8u#9in121*ZPJvLf?G#VLWy$;r{P?lk@y!R33&ODRW|To3sLcIZ)*KQ)s`Arwyf` z98kPUsxR|t8}?eP$2`MqmhO2}dw)>g#_>^WZbv(9PJFf{;O-P};~j-avC(lDPIA*O z7bA*y{pjPl_4atRlx2Yua?o#q=cwPK(F3KWZ0l|b5pM}~9HKk@e%b~LeDw$e(u%er?-8??bh%bGonjF9V z?shna+hxZ#K;hjASN!TdcX{WOcjMSsA*K>^2mbXGwG-0La@lleoNZ_?^?WRIlTV)$z5s>gqs?y<^{NsFYvh zhX>gN-!_y~#uhkivJLekpF&*LiN0p&)ZHOGOj`Kv2CM0CcV5o z*@~YrNxaWL=4M9JO^*!#zFYzbVeXV_gH7llVp%wA-|B$EsBAF-tAhjT z06iCFZu`d}MQQTZKANKdD`D()^M%d=E{hMfypf;3h!-R8D{SOpmX3KFFiOWor(#=V z&7u4U43yzHI2YOP=r8h~tyY1UiTboAWPzKwum(cVW8r58U(bkNr zir!SQwp9|QykH%6Sn@6JSg}|*+Kzd}y$2HKLOMOd1a!U<5efz>4*5%)fJ4zLudT0- zUbd~@K6~O-Z_7YAjBCL8oFxZfYPMO`$@fFo8BpX6r$oNrDw}z5W77PU-0)|Otq#vo z`Rm3{r~blQbe}FWOzd1QOja2h>)@iJgic^~E<(u3`g?*6>3Rha9MG4s1Ced59%wm6 zuQW+WMvxLB3zWPWcfqG&@%2`chNKphrUMq`Rc7324d)}wF2<-TfMD&J1@r<}i_$s=33+zy|>r&Mqw7`m&K{ z_7`Z)tgJFJKuzZIZ1#J)?F3L}vEp6kg7x=w{W^&C6?Vj>JRV@MxTRg-FEwD8Mz|Ny zUby%07I26PloQZm`MlI9_sZ-med*QKeCUHY8BgcNdAumuV@oU=rC>wgNEE+An6!eQ z+`U8${O!9yek)22SIi{;bF-j|QbWjryBJ%qxCsw9Bp#F(prwt@xbRM};IjC;CI`H%Z#;%S_}~H{qD&a^}aJ|86H-VjZmA1KGTbXykEpf_e*~o4L+Fe|vx` z*)H5{*GT}~f5Q#~8j0Hkuu70ZYnYrk#4H~qCn}mOiYZAMpa%yJv<0jCl`h~ zAvKJMtNuk+@J4hmi-SR9Tr|8gAP-}3YBwkIUDM#7n8s{& z&fXBXutEYi^B6}p2_lN1-rl}{O14{%iS1LCQ$>>$?t8X0-XD|f%`^1k)1UFKnM$0WXVvTPV*Xq4kXeu-Hi zaFE&@dPq6L7^^woL*i$GOy#_>?#A&Sun4N~)uR0gm=m7!?q|Udl?wXlX2%dEhRm_9 zk>Q0R=C7Z8)CFC5V(npaZN2^224AqUlOc_v`6XF>0J>#QO@0XbRlP@H)P;m>fPfKCv zC>oB^9FK$Bjb1`M2(U?kyJju=#z&m{=PoPol8`1i0;zyDeqq1?Q-=KZK$uXC=^!3z zqrIs*LeZaSRP#YI#|FG$vyB*6^f_I*uFX*FSxei8hxHHVx2(U09w^>o9eXz94+5bz z+}7$`d$J^veH!?ty$iIAi00+^B3P zvl>nsFU29{H{Gr{Vd`angk36u^OG)P!(vV8PL(4E_M_je#0l%m7>jWQbf(z-~Z2~k9 zxh0bh$m2yjao-c4tlJjX&jJduUAK8?9)vn*i{rqrk5{{;|JP`yf=b8bZyf>qRu@8MaIt>e{{0#V#Zz#Zw z%nNl92F#ADE;(%#f-qkt*n7Yx&Ax)JobpIORvP!{W#6M~bjjSSwEF#M+W_^&N)i^tcr+(oOoK^B~m%z(VPnHhJ-as#I2a!195|p4DZ~=}=(0^qC(~xA+dyx@p%Kp!R5?BRSY43OjjlaKQU8 z3piR5waNRRrNHFK`SzC=!j@U-cAGP~=Z>tM$?ZRD^52q=O_BGvxLwFz;$8XNqP;Sl zWMDZE7ukLdUrILp#g&O1x%d08QenX_h+_z+mkUJj?_!ZW^aCM;288f5!FVQPKnw_2 z3|WA!H^EO06!NNEC0c5C?26< zL;-_T`?2_leSX3Ku{CWtKIZjL;;OR3=Fty8<9vA-DfdrF_kr5W z6t?H&{+ln&C^g%bx+tZiYIBO#5~~~PulPWvG=2eb;0D)7135-#ARb`3)JFT$10)6U zABEPRM|2yruvq}2^IHcupGV+?dVl~_w&;dZ?(MwYHV$KA#vLkq;c=-uRDT>`3qEjf zxepbGJ0)nyzAt{^@xHCb!G$c1$rqPLk5m;+^+0M)9-u5g&RQ^h{2_N40f_R&C(b%>$IQhoG+ zsbD-q*pU%MlQHG@kl*nDYrYYW7-yyX7Q>3Su(45peTQnK&;SH-M1jASek?kwrTjjs zA2L!;5FuZ9u1CzY-Dfm*J}CqaDZk_E!-$$+5hx)IjP)6uZXz*&dtUe0auX#7_2HHX zVxlO&gTG!HMBp2N+6KU;w_ZTZ6G0{3ROj;1h4G;Kjr@L3EvUy1KzXX;t_U6x@zZx= z@uq3UY}*o$MN9h-mr;QICVBvU9`g@Wf)suN|Lw2^*kBiu*R(fc<2xNO?vfsucBGm{ z;8^sJu`@VLRPiAv7?5Ino*D*DID}*1Jg46a0ZPv9B|IY0h!`!o*O$2Wq~Wj00AZJh zL9y+sE^!Iy$KBTS;)>w={-6N~NTDt$pL1~FJWYuvctBP*j@?UI?5*6jg0usM0-@>x zhS^oiIMtSyT6b8&PirTAzEm_ep2)>>mK9o+eH51h?$xFRZwhqZe*E*ZwFmw>rjk`%~Xy6ec_Y`HrD)G|t3Ygln{C$||acUMg&7B5tf!t?F7DJ`R zsD+p6TPxS_?{Wzm`tf9jH)+Q)g*Yw3=RkQ>=a$MDRNkSA4F7zoJPN;pe@o`OAZ13e zB5?mE4$t;&5l*czT3d;H|6T}MBXU7Y%8=PlCo&uWWv1uZMj>X2plm)^0;o_KQf7=W z3dJUyo=pm+X5;-x8ygnLn+}`34gPe#Us@UZ_~)Mz{|A50wk^U*N+a?MYg0PS_S4zB zgnF{ft*a(+cSK3uCi@Bv)*dCKRUeeO%{~X?5ORG*AjQ6{GXF9$F75*+aqqd5%bhg< z(IVFp%uf1seo^nY*`R@WPJ-4sO#kc`?5GMEm#er!z8K7*-ppLtq4;JFK4)(l5 zjK~3DNd%6~8jQt^4@S`}`;5pJ2NAO94;h}*P9(b!YKuZG$58 z<=MMKh*73lK^%cP_55oEZv`~6)JSDIUlX{mw) z(r!mHe&3jvhr(V+o$M`;@9j9^&8Q!{fCiKy3_T#Ha~(`#(Z9(#ycg@$SHXjHj$DOQs~uN= zDnd8nLDeohu8377um-zjALu@{x^LnI^~2up&$%(pA$vij}pdJTTXguEYcs&gm#?zXf?b1_KC%DuB zcLQq12USTzww?TC8bBL{%pkQTNr;zGH6N@0D)`D-|8?+N18C9s?Lr3C;rEagH)VO2 z+vOlPzA-cB%1`HEXYPIx#d6dQBPv>p>7Ob^eX{$Kx6=+M?>u9L6aBVvuQR=IsE1F7s3LlK33vpMD z+Y9q?9C3}4coA~9$gr7Q-)}FgB2;&ZQl-scr_JL0u+(UXgi?lhKoL;F!=u zHR8&d?V*g;?a1J-Pb-<=xX;?MlCRg;@yqCRBJA2JEMMU#($PZ#~Kw*LCx(FJ0jGpDXchCGWL;fzllk(HG{ zp-Q!9-?j(uM|9hLr>DW7@BajS5ll)EPLhP)SHQl9W>iTQtf7IG4ag(F39RodW&0j} z`uOp}mj^>7)Yh++3Z6pH5e&@x6QswhgKvx469rH*x(A)97&`;5i^`IO#fUdgDkA8Q zkgn?ir4D0)S;ueBppdA&Gbjgu@3)Y%BjvY=@6;KQ8?;e(31OKiWDq9pxM&;DKHxXT zntb!>BND_v3v7DpBv`EnRH6(KzJ^tXX0drG02WrnsQ)dU76x7Pbb?8C4U{g7NPU-c zz$8Aw#P)hp!{L6$i5x)Rgr9t4g@^alJQzY^?thOD?u3=Gd_D`>NLz}~Usbx2?C^n@ zrLkUC-J3L8nY(Lw?rhSnQ$ySQ+XW#8gT60_!nMX? zB-0t+=VU8FSJ>3W=51U=9EOWzzf_JN)ymxGwWO2_CnGcV|3--*tXM&fB|29UK%^F6 z15@bDD6}tCaSuqY&SRJp;00ER`f(0PLRb7vXxJDlqrPW@C)i>cz{|V^ml8`H zO)tPMxlz#9p3qFgn8A1GE`9$A9C^#VVD!#qLxR|jXM)3{sTV_0`56X;V{zuMD`NuB z$r(eqjs|${{!qkVN3+wQ9!4wMV*s#+L28?&`Rt68s1$(I_8FAHe@=FAnfQ&{+Ep+Q z4M@rn1K$JhW4EtHzT2|l^oB88S#$ACB_G9;ldZ!%Gd=pnMf z5490iv0R)UH_#cOv^SIcBdyyE`X#9*7`p(=5j290grd5)egin)caVR#HCH{@=Q@c7 z^o2FF$E#$tS8!qu1x!sL9V-O_+juF@If)m%T4W54j-PMseyP`#+ZEah8tALhhMUKe zZ)8@kB)TF4C?@-2KnAD1(1E~wU;M-gs-$QUZMLtTiD{PkLPB=@i-AGt=~l~Eu4qp> zD;^81Wwehr+DEEE&@mev@InG&dft%-(tIAqez$=pDj=*y0rrVu>>1jER@RzqGi6|6 zN&WOfLfB(!Id8fEuj$5v2f)6WhK&T38Nx5(@t%XI*eMnsw6(t6bpzS?wXd2qNle zUC{bxIvhAur<9>u7yN{G#ML5Y6rmZl=V2k!A74E~WZ&Pq0+FwSfc%gd|clVdFBvTjNPp@sY*J*(xPm7F?BN=eb0T*CDMYlcFk zziKucUQ#e3Eglb%?6QyOjoE7To+Iz;hiultkfI@Qh2+C8dMKW0-MW}4xY5bi%zT-X zc^{00&Ej{$`NEZdW(RcW2!-LUZo}~01@Y*PVO3zkcdR@j3;dp`q6g#&6W4>p$gldx zatZSr1q?*JyMjKK1rBiL_vD2H+zv!%MJdU16t90Tr0I%(Os6Uk`He+=gEOALX?ro< zq4~kd{%T@>-X7INu)RU_AWiCXY%`OO*F!#Bl}pzTfSiK^(_|{L0eBdA@w<1P2ea~` zzu+r2QNv<|gf*OwB z|Dp$<{*;1@+v-x;A8N%(e*7JFnF00JtOD%K6SaX$?-?pm-VZDGOF}nZdRDrk4cQ$h zl)$Ymcg`c~xQD?3<8SH5o`E8!0idJYQWIbsgr-aMKB^&ARKo zPtgQ+p7?%sU+I5CF}f8A)_;BJ{Eb_YJg;t*KLD^q!M#Zk`?;X!GB$YuM>NW3OIvMI{o}IzJ zAX2?c&X2q1%{eWG6p!vk%IqA;qdYfa{uPt~-a@Jg60Zo251~#~W=YjS*s>QCdD6(f zJwKZYelfx z8KuR{#=ZaiZ=8hn0Iw>yoXpnZN({&X>eizK`E&p?9?50xSZ^&kLyc=V7N z{KlMyV08_D9+A$N2M%Ed(!F$ic{)t~50iA57;aRdcUb5QBhM*yTpDNFE{R$b!qzNB zA>}>HX|LS8*LI}w&U>=9bb#R+>mA0X)2%P0r9T+&XPQ*07chW>{v*5B)B8Z_^6av~ zrTugn2hGf$;~C`Xre>eU(Nv#L^YPM__e9NVcb`UxX6>)32)n>>?aIC*nXi~~YPiDB zlf-Ht(mN-3`F`reVrkd!3A_O@swV%zqmyj9m0FYSun@oxJqXB;(+S)~mgH`?I|={4 zYC7h7Q3<~s?hHjAF~|;cf=#Ja`_RI7lA7B;GPq}V1XKb}uXHO)A7!(6fSm7wCA{VcWMmfYIr8K?r@q5}TCc(ok;n9~I`Btl=W zgBWDG83A2!d&&{=fu;W$5WM++g$i4v$HZNe+|NR0;Mj6zKe_o)ni>MVJ*3-ClKLsn zjTK9}SDaB}wglyD7GQDGaX{XJCQz$C`sk74A0%N_<32$5DzMsr*2!L?;L@SDtytmh z8Mgs!;>8aLg52BoLk;AM)k@?Gfk!?}(~sv9rj6AWQw93hmpG-S>o_7jd&T-nr#dxJ ziT(J^k<=q#r9rRbmkMVz2L8TvQ#8jbpU2~6QJmJC4pr3tl) z!%)&tCqwx@xrz@4p#n(4T(#mwer@dq;ViDc@jror#~H6T61zc)^v#@#Y_)q%h{W%= zLJeGpoD?z{h{;4N!@R4i3wEMLRIp`?#WUap9!Pib z#iTao#%BnH5>ENC$x_*)u(NQ()8QM-@+w`fu1h%H~sl8mmr9M4TcQG99oyCVc zxyBj?!}z_>7AWi0{*YBYYdbkHwpPhf(?@Ej-SWI&Ehmz3U4#R*567q1m-}U;6x&sYnw?|vUq;`DCm*Qq zHJfRpE)gOI%2SXjzkEf=z1=dtrTJT>XDCla7D!_||L>8*RJ9Ru76^I0z4ss`)&OR3 z=EUL;My}`Qgq7L<%PjcBs2YuEl!tw3kA51d`Z=}Z+6T5jkA}Le!BI+1Lq8Rwb-Buj zEziA(PG5;uF)!)AAv+b6RnS8%^Bhn&@M9EX~P(mI{u@Q(_Ei#8^cHAO#D6vB`-@l%`CWFp3%+dqqDI#)VSvKa0w?;uBiKbP0>xQ`WWT7ZZLI_Kp>?`To zCTUfFwN^_spB&P=up*G0tL7NrK^?o;u(c1luZvvO|I;6Doji8y2T)jgYvf;}4i+2{bZ62URImmDBE_akBAtLgKC$AZ;8!-7*k0}Pjg3CwMx*qBh8 zO=$Oj-Ekf9-VEiwN+x;NyBdGMjbS5c_ZoPmt+vND;>VP03HqCqj$XLX5BU5KWWM(W zHZNDMM4Z{vxW2KK=%+)RG5JsTUfH0nI}Od*=lfbO(|ureMD}FgMwqjgqbo7IBC)eOV!80TmIdPNiQ4TNfD!rBzWB zFoO7xs)%dlzOv@`>xe2Z0uoBnlxtwY3H6bXBBCS?2wtaEVV*#{lXUej)|XS;{`+Wl zyH|Re&J8nF%XXO87g^Yz^lN659>?79qeY6r~Df7HP+_lv*HobojT23-!~nW+Z3jn)tt@~EYV_2Kk|&D zqLt$VSRaJ(SLIf8L9{`Tf9aj(6jp{V(_^vkA~Sa}^ksl?a==vV^6+hH<_i#+m39(! z_j6r9$!ez)R_{Hva^JY6*%s!%zPpaF&3_9k7m#&_HY5(?4Ec;5OwHCe^?PVliWg5-oo~-FNVe<7tAl!zo19!w|}7a!2R=&h^h=+SSrWwBas4UY!RU zq?+s6M|kzdUniJHmS!m&$2nlPs@*gkl&UpPj0(@$AY*y7DQl}!Bv|%jd2!;gTUdhO z_T77aXvTxC)Mm`FBQY3MTJ$q=oX|FkNk`|g1qYk6+sH7qgPxV}xm=i|DfdU18ako9dBW<0Li@aM*4RM3?L!`P&M?%6Y>4>toKv@kz zafqst*C04Z?k3dX$>uYrfMB^Jt`68eia@6fhfVT%?P0WP8BM)K)v6Y7koMhiI(fc0*q&O{Mvsqz}~% z{!-rg2djcYd#8=Oq@}5@Av~@c7&5v3Ge`t_EA1IP{A)EjxnO}Qffo~ zYS8(+-@f=da?O_44d(=EQGM#uTpSS}z~5oBujg=n;6*V^^K+4+F|y+{J=; zACtSne4;oOAHmsWmbT+a!TV@z<@pvjSzQQReP{6js*;B()&j~>RjEQtAshgUs`KuE zF4Z$-Ur)Ng`{c5w4l}O)YWYq72z>33IEv?f4gw@z_L)?dfixqIzQ~Ma6#9fBDiV^G z>NUqnjTj&I_$%grDsNmAdzOn7_FIWv_s(`tTdiE0ZyQ5?Ll@13jg3S;=U53o)azw1 z^Vz0$F~4lCPRwf|)kPE5k2zVN19tK%C_B(NdGXo8KunG(eZ!XF`KaE%o2l~@7Kutl zoU^)EMOys=mvj?_)+Iz)!oaVaczX=+<6$+P*8KZjSq5VBwbqA)FnBRK01DGCz1OV*cXd=Ea&nbEUr_+FAp zgL9OhOB&sK3OAqP4^vENbS<7fRI7dDlQ8|cm>>SuAPE!JJ~6tahZkm7UsK>ew6gv4 zaqM&<`LUXfU|c}X5a!a^4Byq-KB0MmsSEd(7dZz*9t~i;AA@$SDI9ePSX8gLs_POX0MxIY-k&QXZtMJvkpufuvqZBy%Dfvt7=>x0H+4pgw- z1XOR-^@}pelUVFuIQxiP1f#x)e&;vsd zz5ti-h~A8kx8S!e+mXNHV71p$Eec};T*T)&)4UQBVe$2V{so*e+E&8cCb?QtuhUtU ze%ks9XYMBSKauD5$?{XAdqYcg&$UQN_ndAS#{%$27S$t_4FgNrfQphU>ENlKlmky^ zJWj6Pbl!Hr+M=;Y_Wy1w9ZAmqg`5&T-yc6{QwSokk&)%r(;1y(sb+#xU*1JkT)l$n zE7NLQO9$BXy>TeBRw`d?_ctNNxB{?IWOwZM_&;YkY%IVA=I;oP>4)ray}4bjGVYUp5xRTM6h`h=xFwCK%*rKq=S z@22HxEqgx<|H#NtyUgcMqSWwmYq+@@7F!+7a7Rz#wG}Ahu*+QLmi^7 zl?M}=JL^vW!O;R3!1cjLo9m1SH3tL;0|D@|^DH`BXt{dmK1e~_&VpFZ{k3sIT1d9B z(%x~Y2>1~QSa5iEO|C659AI-cHyof|-JzBpCx2W*-c68^vNn72Ax51{&Y>XDvE{lcY?UX6+GV+=SR<|KA`uK z>JQ7&s6k%f;kYhpSf89d9#sL13$;_R5}4Qs%(>se=K%|AIwQ#2=!{#MlN8v!xTZ?q zn9pGq-mk&RD&29o1Mn+J(m*9cB=M3G9gt{$ACB`NzLE|dE;nGHpH&FY`n9FJSu*{2 zxxT~8D>e~lH96=h8cI?V9inh5%TpM8SIUIFCU)ZIzY9X*<=gG)O@v?Hg{2JKk%NnX z5`M)GPJ0aBPTv0SpEsV;ngGkm`Brwm^T2JqQB`I6mW8cS6>hZG`r+Ri2Jibz_hve? zC_gSa5W^5*=!;9 zEHxK*ZFq5%E28G0#P)A8n@w+(xn1K54#e~Q5$#mhVUAeq5b^F>oZl?-(+P?+GM5SG z+<*=&ZY$ke-5We&f>ekGBt~fp)b!?!rE=j)*hHhtW|wvx56#2Hi=#3&gNAn&!|y;q zC1R4*=M0iEeuQ1y zXMiy`!ep#i_~qU*eShYgP*4$uRBiVcT;wj+_A_m~mndgZ%}YJ0)a>}#Jr=9yuD@C{ zI^5~gLVr*s1N;eEl}ic(DoxtD2jIQuM@^F8b{3=Rw$&TH5wRiLj@M5xM#{n7EpS{) zH=A1@ z2>QE_BplxO@=2|9lR|cBGFJ`ybSqv6O>!5wQvMOJxO;2RIHx4sNKf(gWcvkyB zMIn{>FT1WI@vlIZKO4#iaF_enF!=lvZ})SS@Q)e8z8Qm=KUUMHWHh}x81%a+!-?!ho;mL2KKHrbq}!l`8u4PhM#+9n%Itil;Z-5FBu2rjoJ8WKs)VY_K#JVU+L?R@&MTE^Od*oDR@pleXOLYnownk#5>Njxb|-^TI_M3&NRSbmBi$>NS`aifQbbX+j!|R*X*r3vX#Bp`@bgFcOC;k_VzQkn! From b026309e36a2bfec75ecc893016de53472445af4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 May 2024 20:32:21 +0800 Subject: [PATCH 3882/4092] Add setting to allow hiding all country flags There have been enough requests for this at this point to implement it. --- osu.Game/Configuration/OsuConfigManager.cs | 5 ++- .../Localisation/OnlineSettingsStrings.cs | 5 +++ .../Online/AlertsAndPrivacySettings.cs | 5 +++ osu.Game/Users/Drawables/UpdateableFlag.cs | 31 +++++++++++++++---- 4 files changed, 39 insertions(+), 7 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index f4a4c553d8..affcaffe02 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -200,6 +200,8 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.EditorLimitedDistanceSnap, false); SetDefault(OsuSetting.EditorShowSpeedChanges, false); + SetDefault(OsuSetting.HideCountryFlags, false); + SetDefault(OsuSetting.MultiplayerRoomFilter, RoomPermissionsFilter.All); SetDefault(OsuSetting.LastProcessedMetadataId, -1); @@ -435,6 +437,7 @@ namespace osu.Game.Configuration TouchDisableGameplayTaps, ModSelectTextSearchStartsActive, UserOnlineStatus, - MultiplayerRoomFilter + MultiplayerRoomFilter, + HideCountryFlags, } } diff --git a/osu.Game/Localisation/OnlineSettingsStrings.cs b/osu.Game/Localisation/OnlineSettingsStrings.cs index 0660bac172..8e8c81cf59 100644 --- a/osu.Game/Localisation/OnlineSettingsStrings.cs +++ b/osu.Game/Localisation/OnlineSettingsStrings.cs @@ -79,6 +79,11 @@ namespace osu.Game.Localisation ///

public static LocalisableString DiscordPresenceOff => new TranslatableString(getKey(@"discord_presence_off"), @"Off"); + /// + /// "Hide country flags" + /// + public static LocalisableString HideCountryFlags => new TranslatableString(getKey(@"hide_country_flags"), @"Hide country flags"); + private static string getKey(string key) => $"{prefix}:{key}"; } } diff --git a/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs b/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs index e7b6aa56a8..7bd0829add 100644 --- a/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs @@ -28,6 +28,11 @@ namespace osu.Game.Overlays.Settings.Sections.Online LabelText = OnlineSettingsStrings.NotifyOnPrivateMessage, Current = config.GetBindable(OsuSetting.NotifyOnPrivateMessage) }, + new SettingsCheckbox + { + LabelText = OnlineSettingsStrings.HideCountryFlags, + Current = config.GetBindable(OsuSetting.HideCountryFlags) + }, }; } } diff --git a/osu.Game/Users/Drawables/UpdateableFlag.cs b/osu.Game/Users/Drawables/UpdateableFlag.cs index 8f8d7052e5..136478c7bb 100644 --- a/osu.Game/Users/Drawables/UpdateableFlag.cs +++ b/osu.Game/Users/Drawables/UpdateableFlag.cs @@ -3,9 +3,11 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; +using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; @@ -13,14 +15,20 @@ namespace osu.Game.Users.Drawables { public partial class UpdateableFlag : ModelBackedDrawable { + private CountryCode countryCode; + public CountryCode CountryCode { - get => Model; - set => Model = value; + get => countryCode; + set + { + countryCode = value; + updateModel(); + } } /// - /// Whether to show a place holder on unknown country. + /// Whether to show a placeholder on unknown country. /// public bool ShowPlaceholderOnUnknown = true; @@ -30,9 +38,21 @@ namespace osu.Game.Users.Drawables ///
public Action? Action; + private readonly Bindable hideFlags = new BindableBool(); + + [Resolved] + private RankingsOverlay? rankingsOverlay { get; set; } + public UpdateableFlag(CountryCode countryCode = CountryCode.Unknown) { CountryCode = countryCode; + hideFlags.BindValueChanged(_ => updateModel()); + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + config.BindWith(OsuSetting.HideCountryFlags, hideFlags); } protected override Drawable? CreateDrawable(CountryCode countryCode) @@ -54,14 +74,13 @@ namespace osu.Game.Users.Drawables }; } - [Resolved] - private RankingsOverlay? rankingsOverlay { get; set; } - protected override bool OnClick(ClickEvent e) { Action?.Invoke(); rankingsOverlay?.ShowCountry(CountryCode); return true; } + + private void updateModel() => Model = hideFlags.Value ? CountryCode.Unknown : countryCode; } } From e887f93eca53131304d345517b798421fe363829 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 May 2024 22:45:59 +0800 Subject: [PATCH 3883/4092] Always show placeholder on unknown / missing country --- osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs | 1 - .../Overlays/BeatmapSet/Scores/TopScoreUserSection.cs | 1 - osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs | 1 - osu.Game/Overlays/Rankings/Tables/RankingsTable.cs | 1 - osu.Game/Users/Drawables/UpdateableFlag.cs | 8 -------- 5 files changed, 12 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 7a817c43eb..a6868efb5d 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -160,7 +160,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores new UpdateableFlag(score.User.CountryCode) { Size = new Vector2(19, 14), - ShowPlaceholderOnUnknown = false, }, username, #pragma warning disable 618 diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs index 9dc2ce204f..13ba9fb74b 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs @@ -118,7 +118,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Origin = Anchor.CentreLeft, Size = new Vector2(19, 14), Margin = new MarginPadding { Top = 3 }, // makes spacing look more even - ShowPlaceholderOnUnknown = false, }, } } diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index c9e5068b2a..165a576c03 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -165,7 +165,6 @@ namespace osu.Game.Overlays.Profile.Header userFlag = new UpdateableFlag { Size = new Vector2(28, 20), - ShowPlaceholderOnUnknown = false, }, userCountryContainer = new OsuHoverContainer { diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index 27d894cdc2..b9f7e443ca 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -99,7 +99,6 @@ namespace osu.Game.Overlays.Rankings.Tables new UpdateableFlag(GetCountryCode(item)) { Size = new Vector2(28, 20), - ShowPlaceholderOnUnknown = false, }, CreateFlagContent(item) } diff --git a/osu.Game/Users/Drawables/UpdateableFlag.cs b/osu.Game/Users/Drawables/UpdateableFlag.cs index 136478c7bb..ac52599bc9 100644 --- a/osu.Game/Users/Drawables/UpdateableFlag.cs +++ b/osu.Game/Users/Drawables/UpdateableFlag.cs @@ -27,11 +27,6 @@ namespace osu.Game.Users.Drawables } } - /// - /// Whether to show a placeholder on unknown country. - /// - public bool ShowPlaceholderOnUnknown = true; - /// /// Perform an action in addition to showing the country ranking. /// This should be used to perform auxiliary tasks and not as a primary action for clicking a flag (to maintain a consistent UX). @@ -57,9 +52,6 @@ namespace osu.Game.Users.Drawables protected override Drawable? CreateDrawable(CountryCode countryCode) { - if (countryCode == CountryCode.Unknown && !ShowPlaceholderOnUnknown) - return null; - return new Container { RelativeSizeAxes = Axes.Both, From ea35ad46899b3a11ed026377a351f6f3d46593de Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 May 2024 23:26:43 +0800 Subject: [PATCH 3884/4092] Fix nullability inspection --- osu.Game/Users/Drawables/UpdateableFlag.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Users/Drawables/UpdateableFlag.cs b/osu.Game/Users/Drawables/UpdateableFlag.cs index ac52599bc9..6a587212a3 100644 --- a/osu.Game/Users/Drawables/UpdateableFlag.cs +++ b/osu.Game/Users/Drawables/UpdateableFlag.cs @@ -50,7 +50,7 @@ namespace osu.Game.Users.Drawables config.BindWith(OsuSetting.HideCountryFlags, hideFlags); } - protected override Drawable? CreateDrawable(CountryCode countryCode) + protected override Drawable CreateDrawable(CountryCode countryCode) { return new Container { From f64cf5c037f839da3ee3d2a6e686bab1c1bcad0a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 11 May 2024 09:25:07 +0300 Subject: [PATCH 3885/4092] Fix button extending beyond unranked indicator width --- osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs b/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs index 08b0407a79..2ceddedeb5 100644 --- a/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs +++ b/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs @@ -73,7 +73,8 @@ namespace osu.Game.Screens.Select.FooterV2 { UnrankedBadge = new ContainerWithTooltip { - Position = new Vector2(BUTTON_WIDTH + 5f, -5f), + Margin = new MarginPadding { Left = BUTTON_WIDTH + 5f }, + Y = -5f, Depth = float.MaxValue, Origin = Anchor.BottomLeft, Shear = barShear, @@ -233,14 +234,14 @@ namespace osu.Game.Screens.Select.FooterV2 if (Current.Value.Any(m => !m.Ranked)) { - UnrankedBadge.MoveToX(BUTTON_WIDTH + 5, duration, easing); + UnrankedBadge.MoveToX(0, duration, easing); UnrankedBadge.FadeIn(duration, easing); - this.ResizeWidthTo(BUTTON_WIDTH + UnrankedBadge.DrawWidth + 10, duration, easing); + this.ResizeWidthTo(BUTTON_WIDTH + 5 + UnrankedBadge.DrawWidth, duration, easing); } else { - UnrankedBadge.MoveToX(BUTTON_WIDTH + 5 - UnrankedBadge.DrawWidth, duration, easing); + UnrankedBadge.MoveToX(-UnrankedBadge.DrawWidth, duration, easing); UnrankedBadge.FadeOut(duration, easing); this.ResizeWidthTo(BUTTON_WIDTH, duration, easing); From da096376af7b84ec7c6590e976a1505ab33a394a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 11 May 2024 09:31:32 +0300 Subject: [PATCH 3886/4092] Fix DI error in mod tooltip --- .../Screens/Select/FooterV2/FooterButtonModsV2.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs b/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs index 2ceddedeb5..02eb2028c5 100644 --- a/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs +++ b/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs @@ -267,13 +267,16 @@ namespace osu.Game.Screens.Select.FooterV2 { public readonly Bindable> Mods = new Bindable>(); + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + protected override void LoadComplete() { base.LoadComplete(); Mods.BindValueChanged(v => Text = FooterButtonModsV2Strings.Mods(v.NewValue.Count).ToUpper(), true); } - public ITooltip> GetCustomTooltip() => new ModTooltip(); + public ITooltip> GetCustomTooltip() => new ModTooltip(colourProvider); public IReadOnlyList? TooltipContent => Mods.Value; @@ -281,8 +284,16 @@ namespace osu.Game.Screens.Select.FooterV2 { private ModDisplay extendedModDisplay = null!; + [Cached] + private OverlayColourProvider colourProvider; + + public ModTooltip(OverlayColourProvider colourProvider) + { + this.colourProvider = colourProvider; + } + [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private void load() { AutoSizeAxes = Axes.Both; CornerRadius = CORNER_RADIUS; From ac7598cb6868764478daa50b195e4a39aef7da91 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 11 May 2024 20:25:47 +0800 Subject: [PATCH 3887/4092] Move localisation to existing file to avoid silly new class --- .../Localisation/FooterButtonModsV2Strings.cs | 19 ------------------- .../Localisation/ModSelectOverlayStrings.cs | 8 +++++++- .../Select/FooterV2/FooterButtonModsV2.cs | 2 +- 3 files changed, 8 insertions(+), 21 deletions(-) delete mode 100644 osu.Game/Localisation/FooterButtonModsV2Strings.cs diff --git a/osu.Game/Localisation/FooterButtonModsV2Strings.cs b/osu.Game/Localisation/FooterButtonModsV2Strings.cs deleted file mode 100644 index 2cb297d8ef..0000000000 --- a/osu.Game/Localisation/FooterButtonModsV2Strings.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.Framework.Localisation; - -namespace osu.Game.Localisation -{ - public static class FooterButtonModsV2Strings - { - private const string prefix = @"osu.Game.Resources.Localisation.FooterButtonModsV2"; - - /// - /// "{0} mods" - /// - public static LocalisableString Mods(int count) => new TranslatableString(getKey(@"mods"), @"{0} mods", count); - - private static string getKey(string key) => $@"{prefix}:{key}"; - } -} diff --git a/osu.Game/Localisation/ModSelectOverlayStrings.cs b/osu.Game/Localisation/ModSelectOverlayStrings.cs index 7a9bb698d8..cf01081772 100644 --- a/osu.Game/Localisation/ModSelectOverlayStrings.cs +++ b/osu.Game/Localisation/ModSelectOverlayStrings.cs @@ -14,10 +14,16 @@ namespace osu.Game.Localisation /// public static LocalisableString ModSelectTitle => new TranslatableString(getKey(@"mod_select_title"), @"Mod Select"); + /// + /// "{0} mods" + /// + public static LocalisableString Mods(int count) => new TranslatableString(getKey(@"mods"), @"{0} mods", count); + /// /// "Mods provide different ways to enjoy gameplay. Some have an effect on the score you can achieve during ranked play. Others are just for fun." /// - public static LocalisableString ModSelectDescription => new TranslatableString(getKey(@"mod_select_description"), @"Mods provide different ways to enjoy gameplay. Some have an effect on the score you can achieve during ranked play. Others are just for fun."); + public static LocalisableString ModSelectDescription => new TranslatableString(getKey(@"mod_select_description"), + @"Mods provide different ways to enjoy gameplay. Some have an effect on the score you can achieve during ranked play. Others are just for fun."); /// /// "Mod Customisation" diff --git a/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs b/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs index 02eb2028c5..d0351ac348 100644 --- a/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs +++ b/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs @@ -273,7 +273,7 @@ namespace osu.Game.Screens.Select.FooterV2 protected override void LoadComplete() { base.LoadComplete(); - Mods.BindValueChanged(v => Text = FooterButtonModsV2Strings.Mods(v.NewValue.Count).ToUpper(), true); + Mods.BindValueChanged(v => Text = ModSelectOverlayStrings.Mods(v.NewValue.Count).ToUpper(), true); } public ITooltip> GetCustomTooltip() => new ModTooltip(colourProvider); From a988bbd3cb6125c4f300f3d092d32b5e962365dc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 11 May 2024 20:58:51 +0800 Subject: [PATCH 3888/4092] Tidy up `UnrankedBadge` implementation --- .../TestSceneFooterButtonModsV2.cs | 12 +- .../Select/FooterV2/FooterButtonModsV2.cs | 116 +++++++++--------- 2 files changed, 65 insertions(+), 63 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonModsV2.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonModsV2.cs index af2eea6062..4aca9dde3d 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonModsV2.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonModsV2.cs @@ -7,7 +7,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; using osu.Game.Rulesets.Mods; @@ -97,15 +97,12 @@ namespace osu.Game.Tests.Visual.UserInterface public void TestUnrankedBadge() { AddStep(@"Add unranked mod", () => changeMods(new[] { new OsuModDeflate() })); - AddUntilStep("Unranked badge shown", () => footerButtonMods.UnrankedBadge.Alpha == 1); + AddUntilStep("Unranked badge shown", () => footerButtonMods.ChildrenOfType().Single().Alpha == 1); AddStep(@"Clear selected mod", () => changeMods(Array.Empty())); - AddUntilStep("Unranked badge not shown", () => footerButtonMods.UnrankedBadge.Alpha == 0); + AddUntilStep("Unranked badge not shown", () => footerButtonMods.ChildrenOfType().Single().Alpha == 0); } - private void changeMods(IReadOnlyList mods) - { - footerButtonMods.Current.Value = mods; - } + private void changeMods(IReadOnlyList mods) => footerButtonMods.Current.Value = mods; private bool assertModsMultiplier(IEnumerable mods) { @@ -117,7 +114,6 @@ namespace osu.Game.Tests.Visual.UserInterface private partial class TestFooterButtonModsV2 : FooterButtonModsV2 { - public new Container UnrankedBadge => base.UnrankedBadge; public new OsuSpriteText MultiplierText => base.MultiplierText; } } diff --git a/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs b/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs index d0351ac348..44db49b927 100644 --- a/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs +++ b/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs @@ -32,6 +32,11 @@ namespace osu.Game.Screens.Select.FooterV2 { // todo: see https://github.com/ppy/osu-framework/issues/3271 private const float torus_scale_factor = 1.2f; + private const float bar_shear_width = 7f; + private const float bar_height = 37f; + private const float mod_display_portion = 0.65f; + + private static readonly Vector2 bar_shear = new Vector2(bar_shear_width / bar_height, 0); private readonly BindableWithCurrent> current = new BindableWithCurrent>(Array.Empty()); @@ -43,7 +48,7 @@ namespace osu.Game.Screens.Select.FooterV2 private Container modDisplayBar = null!; - protected Container UnrankedBadge { get; private set; } = null!; + private Drawable unrankedBadge = null!; private ModDisplay modDisplay = null!; private OsuSpriteText modCountText = null!; @@ -59,58 +64,19 @@ namespace osu.Game.Screens.Select.FooterV2 [BackgroundDependencyLoader] private void load() { - const float bar_shear_width = 7f; - const float bar_height = 37f; - const float mod_display_portion = 0.65f; - - var barShear = new Vector2(bar_shear_width / bar_height, 0); - Text = "Mods"; Icon = FontAwesome.Solid.ExchangeAlt; AccentColour = colours.Lime1; AddRange(new[] { - UnrankedBadge = new ContainerWithTooltip - { - Margin = new MarginPadding { Left = BUTTON_WIDTH + 5f }, - Y = -5f, - Depth = float.MaxValue, - Origin = Anchor.BottomLeft, - Shear = barShear, - CornerRadius = CORNER_RADIUS, - AutoSizeAxes = Axes.X, - Height = bar_height, - Masking = true, - BorderColour = Color4.White, - BorderThickness = 2f, - TooltipText = ModSelectOverlayStrings.UnrankedExplanation, - Children = new Drawable[] - { - new Box - { - Colour = colours.Orange2, - RelativeSizeAxes = Axes.Both, - }, - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Shear = -barShear, - Text = ModSelectOverlayStrings.Unranked.ToUpper(), - Margin = new MarginPadding { Horizontal = 15 }, - UseFullGlyphHeight = false, - Font = OsuFont.Torus.With(size: 14 * torus_scale_factor, weight: FontWeight.Bold), - Colour = Color4.Black, - } - } - }, + unrankedBadge = new UnrankedBadge(), modDisplayBar = new Container { Y = -5f, Depth = float.MaxValue, Origin = Anchor.BottomLeft, - Shear = barShear, + Shear = bar_shear, CornerRadius = CORNER_RADIUS, Size = new Vector2(BUTTON_WIDTH, bar_height), Masking = true, @@ -140,7 +106,7 @@ namespace osu.Game.Screens.Select.FooterV2 { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Shear = -barShear, + Shear = -bar_shear, UseFullGlyphHeight = false, Font = OsuFont.Torus.With(size: 14 * torus_scale_factor, weight: FontWeight.Bold) } @@ -162,7 +128,7 @@ namespace osu.Game.Screens.Select.FooterV2 { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Shear = -barShear, + Shear = -bar_shear, Scale = new Vector2(0.6f), Current = { BindTarget = Current }, ExpansionMode = ExpansionMode.AlwaysContracted, @@ -171,7 +137,7 @@ namespace osu.Game.Screens.Select.FooterV2 { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Shear = -barShear, + Shear = -bar_shear, Font = OsuFont.Torus.With(size: 14 * torus_scale_factor, weight: FontWeight.Bold), Mods = { BindTarget = Current }, } @@ -216,8 +182,8 @@ namespace osu.Game.Screens.Select.FooterV2 modDisplay.FadeOut(duration, easing); modCountText.FadeOut(duration, easing); - UnrankedBadge.MoveToY(20, duration, easing); - UnrankedBadge.FadeOut(duration, easing); + unrankedBadge.MoveToY(20, duration, easing); + unrankedBadge.FadeOut(duration, easing); // add delay to let unranked indicator hide first before resizing the button back to its original width. this.Delay(duration).ResizeWidthTo(BUTTON_WIDTH, duration, easing); @@ -234,21 +200,21 @@ namespace osu.Game.Screens.Select.FooterV2 if (Current.Value.Any(m => !m.Ranked)) { - UnrankedBadge.MoveToX(0, duration, easing); - UnrankedBadge.FadeIn(duration, easing); + unrankedBadge.MoveToX(0, duration, easing); + unrankedBadge.FadeIn(duration, easing); - this.ResizeWidthTo(BUTTON_WIDTH + 5 + UnrankedBadge.DrawWidth, duration, easing); + this.ResizeWidthTo(BUTTON_WIDTH + 5 + unrankedBadge.DrawWidth, duration, easing); } else { - UnrankedBadge.MoveToX(-UnrankedBadge.DrawWidth, duration, easing); - UnrankedBadge.FadeOut(duration, easing); + unrankedBadge.MoveToX(-unrankedBadge.DrawWidth, duration, easing); + unrankedBadge.FadeOut(duration, easing); this.ResizeWidthTo(BUTTON_WIDTH, duration, easing); } modDisplayBar.MoveToY(-5, duration, Easing.OutQuint); - UnrankedBadge.MoveToY(-5, duration, easing); + unrankedBadge.MoveToY(-5, duration, easing); modDisplayBar.FadeIn(duration, easing); } @@ -327,9 +293,49 @@ namespace osu.Game.Screens.Select.FooterV2 } } - private partial class ContainerWithTooltip : Container, IHasTooltip + internal partial class UnrankedBadge : CompositeDrawable, IHasTooltip { - public LocalisableString TooltipText { get; set; } + public LocalisableString TooltipText { get; } + + public UnrankedBadge() + { + Margin = new MarginPadding { Left = BUTTON_WIDTH + 5f }; + Y = -5f; + Depth = float.MaxValue; + Origin = Anchor.BottomLeft; + Shear = bar_shear; + CornerRadius = CORNER_RADIUS; + AutoSizeAxes = Axes.X; + Height = bar_height; + Masking = true; + BorderColour = Color4.White; + BorderThickness = 2f; + TooltipText = ModSelectOverlayStrings.UnrankedExplanation; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + InternalChildren = new Drawable[] + { + new Box + { + Colour = colours.Orange2, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Shear = -bar_shear, + Text = ModSelectOverlayStrings.Unranked.ToUpper(), + Margin = new MarginPadding { Horizontal = 15 }, + UseFullGlyphHeight = false, + Font = OsuFont.Torus.With(size: 14 * torus_scale_factor, weight: FontWeight.Bold), + Colour = Color4.Black, + } + }; + } } } } From 4cf6ab40f6534dc0a8f479b3d561b5a3bcd8b403 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 11 May 2024 22:33:18 +0300 Subject: [PATCH 3889/4092] Use `MustDisposeResource` annotation to appease ReSharper inspections --- osu.Game/Database/EmptyRealmSet.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Database/EmptyRealmSet.cs b/osu.Game/Database/EmptyRealmSet.cs index 02dfa50fe5..c34974cb03 100644 --- a/osu.Game/Database/EmptyRealmSet.cs +++ b/osu.Game/Database/EmptyRealmSet.cs @@ -6,6 +6,7 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; +using JetBrains.Annotations; using Realms; using Realms.Schema; @@ -15,8 +16,12 @@ namespace osu.Game.Database { private IList emptySet => Array.Empty(); + [MustDisposeResource] public IEnumerator GetEnumerator() => emptySet.GetEnumerator(); + + [MustDisposeResource] IEnumerator IEnumerable.GetEnumerator() => emptySet.GetEnumerator(); + public int Count => emptySet.Count; public T this[int index] => emptySet[index]; public int IndexOf(object? item) => item == null ? -1 : emptySet.IndexOf((T)item); From 5e8f6df79952b26793c4bca8b74cfcdf1c0a90a3 Mon Sep 17 00:00:00 2001 From: Thomas Mok <42684333+tomm13@users.noreply.github.com> Date: Sun, 12 May 2024 01:55:18 +0100 Subject: [PATCH 3890/4092] Add glow to footerV2 buttons --- osu.Game/Screens/Select/FooterV2/FooterButtonV2.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/Select/FooterV2/FooterButtonV2.cs b/osu.Game/Screens/Select/FooterV2/FooterButtonV2.cs index 2c841f6ae6..d856780dfe 100644 --- a/osu.Game/Screens/Select/FooterV2/FooterButtonV2.cs +++ b/osu.Game/Screens/Select/FooterV2/FooterButtonV2.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; @@ -68,6 +69,7 @@ namespace osu.Game.Screens.Select.FooterV2 protected Container TextContainer; private readonly Box bar; private readonly Box backgroundBox; + private readonly Box glowBox; public FooterButtonV2() { @@ -93,6 +95,10 @@ namespace osu.Game.Screens.Select.FooterV2 { RelativeSizeAxes = Axes.Both }, + glowBox = new Box + { + RelativeSizeAxes = Axes.Both + }, // For elements that should not be sheared. new Container { @@ -211,6 +217,7 @@ namespace osu.Game.Screens.Select.FooterV2 } backgroundBox.FadeColour(backgroundColour, transition_length, Easing.OutQuint); + glowBox.Colour = ColourInfo.GradientVertical(buttonAccentColour.Opacity(0f), buttonAccentColour.Opacity(0.2f)); } } } From fa6ccc854d5324d08baf4e6ee77ce7373d75ef0a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 12 May 2024 05:24:20 +0300 Subject: [PATCH 3891/4092] Revert behavioural changes on options button --- .../Select/FooterV2/BeatmapOptionsPopover.cs | 4 +-- .../Select/FooterV2/FooterButtonOptionsV2.cs | 31 +------------------ 2 files changed, 2 insertions(+), 33 deletions(-) diff --git a/osu.Game/Screens/Select/FooterV2/BeatmapOptionsPopover.cs b/osu.Game/Screens/Select/FooterV2/BeatmapOptionsPopover.cs index 648f536bb1..d98164c306 100644 --- a/osu.Game/Screens/Select/FooterV2/BeatmapOptionsPopover.cs +++ b/osu.Game/Screens/Select/FooterV2/BeatmapOptionsPopover.cs @@ -188,9 +188,7 @@ namespace osu.Game.Screens.Select.FooterV2 protected override void UpdateState(ValueChangedEvent state) { base.UpdateState(state); - // intentionally scheduling to let the button have a chance whether the popover will hide from clicking the button or clicking outside - // see the "hidingFromClick" field in FooterButtonOptionsV2. - Schedule(() => footerButton.OverlayState.Value = state.NewValue); + footerButton.OverlayState.Value = state.NewValue; } } } diff --git a/osu.Game/Screens/Select/FooterV2/FooterButtonOptionsV2.cs b/osu.Game/Screens/Select/FooterV2/FooterButtonOptionsV2.cs index 2ed8480b46..555215056a 100644 --- a/osu.Game/Screens/Select/FooterV2/FooterButtonOptionsV2.cs +++ b/osu.Game/Screens/Select/FooterV2/FooterButtonOptionsV2.cs @@ -3,11 +3,9 @@ using osu.Framework.Allocation; using osu.Framework.Extensions; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Input.Bindings; @@ -15,11 +13,6 @@ namespace osu.Game.Screens.Select.FooterV2 { public partial class FooterButtonOptionsV2 : FooterButtonV2, IHasPopover { - /// - /// True if the next click is for hiding the popover. - /// - private bool hidingFromClick; - [BackgroundDependencyLoader] private void load(OsuColour colour) { @@ -28,29 +21,7 @@ namespace osu.Game.Screens.Select.FooterV2 AccentColour = colour.Purple1; Hotkey = GlobalAction.ToggleBeatmapOptions; - Action = () => - { - if (OverlayState.Value == Visibility.Hidden && !hidingFromClick) - this.ShowPopover(); - - hidingFromClick = false; - }; - } - - protected override bool OnMouseDown(MouseDownEvent e) - { - if (OverlayState.Value == Visibility.Visible) - hidingFromClick = true; - - return base.OnMouseDown(e); - } - - protected override void Flash() - { - if (hidingFromClick) - return; - - base.Flash(); + Action = this.ShowPopover; } public Popover GetPopover() => new BeatmapOptionsPopover(this); From 260c224619289efa6e6f603c8cd8584ad445eeed Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 14 May 2024 01:23:40 +0900 Subject: [PATCH 3892/4092] Add failing test --- .../TestSceneOsuTouchInput.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs index 5bf7c0326a..d3711c0cc6 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs @@ -19,6 +19,7 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Screens.Play.HUD; using osu.Game.Tests.Visual; @@ -578,6 +579,25 @@ namespace osu.Game.Rulesets.Osu.Tests assertKeyCounter(1, 1); } + [Test] + [Solo] + public void TestTouchJudgedCircle() + { + addHitCircleAt(TouchSource.Touch1); + addHitCircleAt(TouchSource.Touch2); + + beginTouch(TouchSource.Touch1); + endTouch(TouchSource.Touch1); + + // Hold the second touch (this becomes the primary touch). + beginTouch(TouchSource.Touch2); + + // Touch again on the first circle. + // Because it's been judged, the cursor should not move here. + beginTouch(TouchSource.Touch1); + checkPosition(TouchSource.Touch2); + } + private void addHitCircleAt(TouchSource source) { AddStep($"Add circle at {source}", () => @@ -590,6 +610,7 @@ namespace osu.Game.Rulesets.Osu.Tests { Clock = new FramedClock(new ManualClock()), Position = mainContent.ToLocalSpace(getSanePositionForSource(source)), + CheckHittable = (_, _, _) => ClickAction.Hit }); }); } From ff0c0d54c9127df3006bcb4249fea225d5cc3f6f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 14 May 2024 01:23:55 +0900 Subject: [PATCH 3893/4092] Fix taps on judged circles changing cursor position --- osu.Game.Rulesets.Osu/OsuInputManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs index ceac1989a6..65bd585e98 100644 --- a/osu.Game.Rulesets.Osu/OsuInputManager.cs +++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Osu // // Based on user feedback of more nuanced scenarios (where touch doesn't behave as expected), // this can be expanded to a more complex implementation, but I'd still want to keep it as simple as we can. - NonPositionalInputQueue.OfType().Any(c => c.ReceivePositionalInputAt(screenSpacePosition)); + NonPositionalInputQueue.OfType().Any(c => c.CanBeHit() && c.ReceivePositionalInputAt(screenSpacePosition)); public OsuInputManager(RulesetInfo ruleset) : base(ruleset, 0, SimultaneousBindingMode.Unique) From 20e28964352c140bb71198c4be743b6189a5edfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 13 May 2024 18:48:08 +0200 Subject: [PATCH 3894/4092] Remove leftover `[Solo]` attribute --- osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs index d3711c0cc6..bf0ab8efa0 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs @@ -580,7 +580,6 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - [Solo] public void TestTouchJudgedCircle() { addHitCircleAt(TouchSource.Touch1); From cfb2c8272b1db1d774678d127a3b2ee70e264b7d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 May 2024 22:56:15 +0800 Subject: [PATCH 3895/4092] Set a rudimentary lifetime end to improve seek performance in scrolling rulesets --- .../Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 4e72291b9c..e70e181a50 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.UI.Scrolling // Scroll info is not available until loaded. // The lifetime of all entries will be updated in the first Update. if (IsLoaded) - setComputedLifetimeStart(entry); + setComputedLifetime(entry); base.Add(entry); } @@ -171,7 +171,7 @@ namespace osu.Game.Rulesets.UI.Scrolling layoutComputed.Clear(); foreach (var entry in Entries) - setComputedLifetimeStart(entry); + setComputedLifetime(entry); algorithm.Value.Reset(); @@ -234,12 +234,13 @@ namespace osu.Game.Rulesets.UI.Scrolling return algorithm.Value.GetDisplayStartTime(entry.HitObject.StartTime, startOffset, timeRange.Value, scrollLength); } - private void setComputedLifetimeStart(HitObjectLifetimeEntry entry) + private void setComputedLifetime(HitObjectLifetimeEntry entry) { double computedStartTime = computeDisplayStartTime(entry); // always load the hitobject before its first judgement offset entry.LifetimeStart = Math.Min(entry.HitObject.StartTime - entry.HitObject.MaximumJudgementOffset, computedStartTime); + entry.LifetimeEnd = entry.HitObject.GetEndTime() + timeRange.Value; } private void updateLayoutRecursive(DrawableHitObject hitObject, double? parentHitObjectStartTime = null) @@ -261,7 +262,7 @@ namespace osu.Game.Rulesets.UI.Scrolling // Nested hitobjects don't need to scroll, but they do need accurate positions and start lifetime updatePosition(obj, hitObject.HitObject.StartTime, parentHitObjectStartTime); - setComputedLifetimeStart(obj.Entry); + setComputedLifetime(obj.Entry); } } From 7f3fde2a25d0d78277a6794cc48ec856691e689a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 14 May 2024 11:13:06 +0200 Subject: [PATCH 3896/4092] Add failing test case --- .../Visual/Navigation/TestScenePresentScore.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs index 004d1de116..212783d047 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs @@ -145,6 +145,19 @@ namespace osu.Game.Tests.Visual.Navigation presentAndConfirm(secondImport, type); } + [Test] + public void TestPresentTwoImportsWithSameOnlineIDButDifferentHashes([Values] ScorePresentType type) + { + AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo?.Invoke()); + AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded); + + var firstImport = importScore(1); + presentAndConfirm(firstImport, type); + + var secondImport = importScore(1); + presentAndConfirm(secondImport, type); + } + private void returnToMenu() { // if we don't pause, there's a chance the track may change at the main menu out of our control (due to reaching the end of the track). From 03a279a48d476b2529d01d975022cb927eb80875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 14 May 2024 11:14:46 +0200 Subject: [PATCH 3897/4092] Use hash rather than online ID as primary lookup key when presenting score Something I ran into when investigating https://github.com/ppy/osu/issues/28169. If there are two scores with the same online ID available in the database - for instance, one being recorded locally, and one recorded by spectator server, of one single play - the lookup code would use online ID first to find the score and pick any first one that matched. This could lead to the wrong replay being refetched and presented / exported. (In the case of the aforementioned issue, I was confused as to whether after restarting spectator server midway through a play and importing the replay saved by spectator server after the restart, I was seeing a complete replay with no dropped frames, even though there was nothing in the code that prevented the frame drop. It turns out that I was getting presented the locally recorded replay instead all along.) Instead, jiggle the fallback preference to use hash first. --- osu.Game/Scoring/ScoreManager.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 1ba5c7d4cf..0c707ffa19 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -88,15 +88,15 @@ namespace osu.Game.Scoring { ScoreInfo? databasedScoreInfo = null; + if (originalScoreInfo is ScoreInfo scoreInfo) + databasedScoreInfo = Query(s => s.Hash == scoreInfo.Hash); + if (originalScoreInfo.OnlineID > 0) - databasedScoreInfo = Query(s => s.OnlineID == originalScoreInfo.OnlineID); + databasedScoreInfo ??= Query(s => s.OnlineID == originalScoreInfo.OnlineID); if (originalScoreInfo.LegacyOnlineID > 0) databasedScoreInfo ??= Query(s => s.LegacyOnlineID == originalScoreInfo.LegacyOnlineID); - if (originalScoreInfo is ScoreInfo scoreInfo) - databasedScoreInfo ??= Query(s => s.Hash == scoreInfo.Hash); - if (databasedScoreInfo == null) { Logger.Log("The requested score could not be found locally.", LoggingTarget.Information); From 4f6777a0a12fe204a399a19bcc7a1fa54a369f2c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 4 Apr 2024 16:00:18 +0900 Subject: [PATCH 3898/4092] Remove existing per-column touch input --- osu.Game.Rulesets.Mania/UI/Column.cs | 36 +--------------------------- 1 file changed, 1 insertion(+), 35 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 6cd55bb099..c05a8f2a29 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -93,8 +93,7 @@ namespace osu.Game.Rulesets.Mania.UI // For input purposes, the background is added at the highest depth, but is then proxied back below all other elements externally // (see `Stage.columnBackgrounds`). BackgroundContainer, - TopLevelContainer, - new ColumnTouchInputArea(this) + TopLevelContainer }; var background = new SkinnableDrawable(new ManiaSkinComponentLookup(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground()) @@ -181,38 +180,5 @@ namespace osu.Game.Rulesets.Mania.UI public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) // This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border => DrawRectangle.Inflate(new Vector2(Stage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos)); - - public partial class ColumnTouchInputArea : Drawable - { - private readonly Column column; - - [Resolved(canBeNull: true)] - private ManiaInputManager maniaInputManager { get; set; } - - private KeyBindingContainer keyBindingContainer; - - public ColumnTouchInputArea(Column column) - { - RelativeSizeAxes = Axes.Both; - - this.column = column; - } - - protected override void LoadComplete() - { - keyBindingContainer = maniaInputManager?.KeyBindingContainer; - } - - protected override bool OnTouchDown(TouchDownEvent e) - { - keyBindingContainer?.TriggerPressed(column.Action.Value); - return true; - } - - protected override void OnTouchUp(TouchUpEvent e) - { - keyBindingContainer?.TriggerReleased(column.Action.Value); - } - } } } From ef40197713009fd58c04ddc4f516a6cefd001ed8 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 4 Apr 2024 17:11:15 +0900 Subject: [PATCH 3899/4092] Add mania touch overlay Adjust default anchor/origin --- osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs | 20 ++- .../UI/ManiaTouchInputOverlay.cs | 144 ++++++++++++++++++ 2 files changed, 161 insertions(+), 3 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/UI/ManiaTouchInputOverlay.cs diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index b3420c49f3..7610e48582 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics.Primitives; using osu.Game.Rulesets.Mania.Beatmaps; @@ -60,10 +61,23 @@ namespace osu.Game.Rulesets.Mania.UI throw new ArgumentException("Can't have zero or fewer stages."); GridContainer playfieldGrid; - AddInternal(playfieldGrid = new GridContainer + + RelativeSizeAxes = Axes.Y; + AutoSizeAxes = Axes.X; + + AddRangeInternal(new Drawable[] { - RelativeSizeAxes = Axes.Both, - Content = new[] { new Drawable[stageDefinitions.Count] } + playfieldGrid = new GridContainer + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Content = new[] { new Drawable[stageDefinitions.Count] }, + ColumnDimensions = Enumerable.Range(0, stageDefinitions.Count).Select(_ => new Dimension(GridSizeMode.AutoSize)).ToArray() + }, + new ManiaTouchInputOverlay + { + RelativeSizeAxes = Axes.Both, + } }); var normalColumnAction = ManiaAction.Key1; diff --git a/osu.Game.Rulesets.Mania/UI/ManiaTouchInputOverlay.cs b/osu.Game.Rulesets.Mania/UI/ManiaTouchInputOverlay.cs new file mode 100644 index 0000000000..476461959d --- /dev/null +++ b/osu.Game.Rulesets.Mania/UI/ManiaTouchInputOverlay.cs @@ -0,0 +1,144 @@ +// 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 osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Configuration; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Rulesets.Mania.UI +{ + public partial class ManiaTouchInputOverlay : CompositeDrawable, ISerialisableDrawable + { + [SettingSource("Spacing", "The spacing between input receptors.")] + public BindableFloat Spacing { get; } = new BindableFloat(10) + { + Precision = 1, + MinValue = 0, + MaxValue = 100, + }; + + [Resolved] + private ManiaPlayfield playfield { get; set; } = null!; + + public ManiaTouchInputOverlay() + { + Anchor = Anchor.BottomCentre; + Origin = Anchor.BottomCentre; + RelativeSizeAxes = Axes.Both; + Height = 0.5f; + } + + [BackgroundDependencyLoader] + private void load() + { + List receptorGridContent = new List(); + List receptorGridDimensions = new List(); + + bool first = true; + + foreach (var stage in playfield.Stages) + { + foreach (var column in stage.Columns) + { + if (!first) + { + receptorGridContent.Add(new Gutter { Spacing = { BindTarget = Spacing } }); + receptorGridDimensions.Add(new Dimension(GridSizeMode.AutoSize)); + } + + receptorGridContent.Add(new InputReceptor()); + receptorGridDimensions.Add(new Dimension()); + + first = false; + } + } + + InternalChild = new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] { receptorGridContent.ToArray() }, + ColumnDimensions = receptorGridDimensions.ToArray() + }; + } + + public bool UsesFixedAnchor { get; set; } + + public partial class InputReceptor : CompositeDrawable + { + private readonly Box highlightOverlay; + + public InputReceptor() + { + RelativeSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 10, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.15f, + }, + highlightOverlay = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + Blending = BlendingParameters.Additive, + } + } + } + }; + } + + protected override bool OnTouchDown(TouchDownEvent e) + { + updateHighlight(true); + return true; + } + + protected override void OnTouchUp(TouchUpEvent e) + { + updateHighlight(false); + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + updateHighlight(true); + return true; + } + + protected override void OnMouseUp(MouseUpEvent e) + { + updateHighlight(false); + } + + private void updateHighlight(bool enabled) + { + highlightOverlay.FadeTo(enabled ? 0.1f : 0, enabled ? 80 : 400, Easing.OutQuint); + } + } + + private partial class Gutter : Drawable + { + public readonly IBindable Spacing = new Bindable(); + + public Gutter() + { + Spacing.BindValueChanged(s => Size = new Vector2(s.NewValue)); + } + } + } +} From 39337f5189fb371ba91c6b5145374213cbbfba71 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 4 Apr 2024 17:25:05 +0900 Subject: [PATCH 3900/4092] Hook up input manager --- .../UI/ManiaTouchInputOverlay.cs | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/ManiaTouchInputOverlay.cs b/osu.Game.Rulesets.Mania/UI/ManiaTouchInputOverlay.cs index 476461959d..10de89e950 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaTouchInputOverlay.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaTouchInputOverlay.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Mania.UI receptorGridDimensions.Add(new Dimension(GridSizeMode.AutoSize)); } - receptorGridContent.Add(new InputReceptor()); + receptorGridContent.Add(new InputReceptor { Action = { BindTarget = column.Action } }); receptorGridDimensions.Add(new Dimension()); first = false; @@ -72,8 +72,15 @@ namespace osu.Game.Rulesets.Mania.UI public partial class InputReceptor : CompositeDrawable { + public readonly IBindable Action = new Bindable(); + private readonly Box highlightOverlay; + [Resolved] + private ManiaInputManager? inputManager { get; set; } + + private bool isPressed; + public InputReceptor() { RelativeSizeAxes = Axes.Both; @@ -105,29 +112,43 @@ namespace osu.Game.Rulesets.Mania.UI protected override bool OnTouchDown(TouchDownEvent e) { - updateHighlight(true); + updateButton(true); return true; } protected override void OnTouchUp(TouchUpEvent e) { - updateHighlight(false); + updateButton(false); } protected override bool OnMouseDown(MouseDownEvent e) { - updateHighlight(true); + updateButton(true); return true; } protected override void OnMouseUp(MouseUpEvent e) { - updateHighlight(false); + updateButton(false); } - private void updateHighlight(bool enabled) + private void updateButton(bool press) { - highlightOverlay.FadeTo(enabled ? 0.1f : 0, enabled ? 80 : 400, Easing.OutQuint); + if (press == isPressed) + return; + + isPressed = press; + + if (press) + { + inputManager?.KeyBindingContainer?.TriggerPressed(Action.Value); + highlightOverlay.FadeTo(0.1f, 80, Easing.OutQuint); + } + else + { + inputManager?.KeyBindingContainer?.TriggerReleased(Action.Value); + highlightOverlay.FadeTo(0, 400, Easing.OutQuint); + } } } From e3f2e1ba08c79e01b9dc9a0d76ff9bf21f41c32e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 4 Apr 2024 18:20:30 +0900 Subject: [PATCH 3901/4092] Add opacity setting --- .../UI/ManiaTouchInputOverlay.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/UI/ManiaTouchInputOverlay.cs b/osu.Game.Rulesets.Mania/UI/ManiaTouchInputOverlay.cs index 10de89e950..ddff064133 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaTouchInputOverlay.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaTouchInputOverlay.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.UI { public partial class ManiaTouchInputOverlay : CompositeDrawable, ISerialisableDrawable { - [SettingSource("Spacing", "The spacing between input receptors.")] + [SettingSource("Spacing", "The spacing between receptors.")] public BindableFloat Spacing { get; } = new BindableFloat(10) { Precision = 1, @@ -24,6 +24,14 @@ namespace osu.Game.Rulesets.Mania.UI MaxValue = 100, }; + [SettingSource("Opacity", "The receptor opacity.")] + public BindableFloat Opacity { get; } = new BindableFloat(1) + { + Precision = 0.1f, + MinValue = 0, + MaxValue = 1 + }; + [Resolved] private ManiaPlayfield playfield { get; set; } = null!; @@ -68,6 +76,12 @@ namespace osu.Game.Rulesets.Mania.UI }; } + protected override void LoadComplete() + { + base.LoadComplete(); + Opacity.BindValueChanged(o => Alpha = o.NewValue, true); + } + public bool UsesFixedAnchor { get; set; } public partial class InputReceptor : CompositeDrawable From a761a7bced2deacea3f7ee9c2e765c59e7cd0670 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 4 Apr 2024 18:20:51 +0900 Subject: [PATCH 3902/4092] Hook up touch device mod --- .../Mods/TestSceneModTouchDevice.cs | 64 +++++++++++++++++++ osu.Game.Rulesets.Mania/ManiaRuleset.cs | 8 +++ osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs | 14 +++- 3 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/Mods/TestSceneModTouchDevice.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneModTouchDevice.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneModTouchDevice.cs new file mode 100644 index 0000000000..4c5e4933ef --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneModTouchDevice.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.Linq; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Input; +using osu.Framework.Testing; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Mods; +using osu.Game.Skinning; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Mania.Tests.Mods +{ + public partial class TestSceneModTouchDevice : ModTestScene + { + protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); + + [Test] + public void TestOverlayVisibleWithMod() => CreateModTest(new ModTestData + { + Mod = new ModTouchDevice(), + Autoplay = false, + PassCondition = () => getSkinnableOverlay()?.IsPresent == true + }); + + [Test] + public void TestOverlayNotVisibleWithoutMod() => CreateModTest(new ModTestData + { + Autoplay = false, + PassCondition = () => getSkinnableOverlay()?.IsPresent == false + }); + + [Test] + public void TestPressReceptors() + { + CreateModTest(new ModTestData + { + Mod = new ModTouchDevice(), + Autoplay = false, + PassCondition = () => true + }); + + for (int i = 0; i < 4; i++) + { + int index = i; + + AddStep($"touch receptor {index}", () => InputManager.BeginTouch(new Touch(TouchSource.Touch1, getReceptor(index).ScreenSpaceDrawQuad.Centre))); + + AddAssert("action sent", + () => this.ChildrenOfType().SelectMany(m => m.KeyBindingContainer.PressedActions), + () => Does.Contain(getReceptor(index).Action.Value)); + + AddStep($"release receptor {index}", () => InputManager.EndTouch(new Touch(TouchSource.Touch1, getReceptor(index).ScreenSpaceDrawQuad.Centre))); + } + } + + private Drawable? getSkinnableOverlay() => this.ChildrenOfType() + .SingleOrDefault(d => d.Lookup.Equals(new ManiaSkinComponentLookup(ManiaSkinComponents.TouchOverlay))); + + private ManiaTouchInputOverlay.InputReceptor getReceptor(int index) => this.ChildrenOfType().ElementAt(index); + } +} diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index b5614e2b56..23004e36a0 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -163,6 +163,9 @@ namespace osu.Game.Rulesets.Mania if (mods.HasFlagFast(LegacyMods.ScoreV2)) yield return new ModScoreV2(); + + if (mods.HasFlagFast(LegacyMods.TouchDevice)) + yield return new ModTouchDevice(); } public override LegacyMods ConvertToLegacyMods(Mod[] mods) @@ -225,6 +228,10 @@ namespace osu.Game.Rulesets.Mania case ManiaModRandom: value |= LegacyMods.Random; break; + + case ModTouchDevice: + value |= LegacyMods.TouchDevice; + break; } } @@ -296,6 +303,7 @@ namespace osu.Game.Rulesets.Mania case ModType.System: return new Mod[] { + new ModTouchDevice(), new ModScoreV2(), }; diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 7610e48582..385a47f8b8 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -12,6 +12,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Primitives; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; @@ -26,6 +27,8 @@ namespace osu.Game.Rulesets.Mania.UI private readonly List stages = new List(); + private readonly ManiaTouchInputOverlay touchOverlay; + public override Quad SkinnableComponentScreenSpaceDrawQuad { get @@ -74,9 +77,9 @@ namespace osu.Game.Rulesets.Mania.UI Content = new[] { new Drawable[stageDefinitions.Count] }, ColumnDimensions = Enumerable.Range(0, stageDefinitions.Count).Select(_ => new Dimension(GridSizeMode.AutoSize)).ToArray() }, - new ManiaTouchInputOverlay + touchOverlay = new ManiaTouchInputOverlay { - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.Both } }); @@ -97,6 +100,13 @@ namespace osu.Game.Rulesets.Mania.UI } } + protected override void LoadComplete() + { + base.LoadComplete(); + + touchOverlay.Alpha = Mods?.Any(m => m is ModTouchDevice) == true ? 1 : 0; + } + public override void Add(HitObject hitObject) => getStageByColumn(((ManiaHitObject)hitObject).Column).Add(hitObject); public override bool Remove(HitObject hitObject) => getStageByColumn(((ManiaHitObject)hitObject).Column).Remove(hitObject); From cb49147d1e17d14c8b6a63c5a2e3b535a36f57a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 14 May 2024 12:57:30 +0200 Subject: [PATCH 3903/4092] Apply NRT to `ScorePanelList` --- osu.Game/Screens/Ranking/ResultsScreen.cs | 2 ++ osu.Game/Screens/Ranking/ScorePanelList.cs | 19 +++++++------------ 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 1c3518909d..44b270db53 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -329,6 +330,7 @@ namespace osu.Game.Screens.Ranking { if (state.NewValue == Visibility.Visible) { + Debug.Assert(SelectedScore.Value != null); // Detach the panel in its original location, and move into the desired location in the local container. var expandedPanel = ScorePanelList.GetPanelForScore(SelectedScore.Value); var screenSpacePos = expandedPanel.ScreenSpaceDrawQuad.TopLeft; diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 95c90e35a0..e711bed729 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -1,14 +1,11 @@ // 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.Diagnostics; using System.Linq; using System.Threading; -using JetBrains.Annotations; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -64,14 +61,14 @@ namespace osu.Game.Screens.Ranking /// /// An action to be invoked if a is clicked while in an expanded state. /// - public Action PostExpandAction; + public Action? PostExpandAction; - public readonly Bindable SelectedScore = new Bindable(); + public readonly Bindable SelectedScore = new Bindable(); private readonly CancellationTokenSource loadCancellationSource = new CancellationTokenSource(); private readonly Flow flow; private readonly Scroll scroll; - private ScorePanel expandedPanel; + private ScorePanel? expandedPanel; /// /// Creates a new . @@ -174,7 +171,7 @@ namespace osu.Game.Screens.Ranking /// Brings a to the centre of the screen and expands it. /// /// The to present. - private void selectedScoreChanged(ValueChangedEvent score) + private void selectedScoreChanged(ValueChangedEvent score) { // avoid contracting panels unnecessarily when TriggerChange is fired manually. if (score.OldValue != null && !score.OldValue.Equals(score.NewValue)) @@ -317,7 +314,7 @@ namespace osu.Game.Screens.Ranking protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - loadCancellationSource?.Cancel(); + loadCancellationSource.Cancel(); } private partial class Flow : FillFlowContainer @@ -326,11 +323,9 @@ namespace osu.Game.Screens.Ranking public int GetPanelIndex(ScoreInfo score) => applySorting(Children).TakeWhile(s => !s.Panel.Score.Equals(score)).Count(); - [CanBeNull] - public ScoreInfo GetPreviousScore(ScoreInfo score) => applySorting(Children).TakeWhile(s => !s.Panel.Score.Equals(score)).LastOrDefault()?.Panel.Score; + public ScoreInfo? GetPreviousScore(ScoreInfo score) => applySorting(Children).TakeWhile(s => !s.Panel.Score.Equals(score)).LastOrDefault()?.Panel.Score; - [CanBeNull] - public ScoreInfo GetNextScore(ScoreInfo score) => applySorting(Children).SkipWhile(s => !s.Panel.Score.Equals(score)).ElementAtOrDefault(1)?.Panel.Score; + public ScoreInfo? GetNextScore(ScoreInfo score) => applySorting(Children).SkipWhile(s => !s.Panel.Score.Equals(score)).ElementAtOrDefault(1)?.Panel.Score; private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() .OrderByDescending(GetLayoutPosition) From 10a8e84046ac4e8abb06e44ab112e2b3274bb2d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 14 May 2024 13:01:26 +0200 Subject: [PATCH 3904/4092] Apply NRT to `StatisticsPanel` --- .../Ranking/Statistics/StatisticsPanel.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 19bd0c4393..f9f5254bc2 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using System.Threading; @@ -28,19 +26,20 @@ namespace osu.Game.Screens.Ranking.Statistics { public const float SIDE_PADDING = 30; - public readonly Bindable Score = new Bindable(); + public readonly Bindable Score = new Bindable(); protected override bool StartHidden => true; [Resolved] - private BeatmapManager beatmapManager { get; set; } + private BeatmapManager beatmapManager { get; set; } = null!; private readonly Container content; private readonly LoadingSpinner spinner; private bool wasOpened; - private Sample popInSample; - private Sample popOutSample; + private Sample? popInSample; + private Sample? popOutSample; + private CancellationTokenSource? loadCancellation; public StatisticsPanel() { @@ -71,9 +70,7 @@ namespace osu.Game.Screens.Ranking.Statistics popOutSample = audio.Samples.Get(@"Results/statistics-panel-pop-out"); } - private CancellationTokenSource loadCancellation; - - private void populateStatistics(ValueChangedEvent score) + private void populateStatistics(ValueChangedEvent score) { loadCancellation?.Cancel(); loadCancellation = null; @@ -187,7 +184,7 @@ namespace osu.Game.Screens.Ranking.Statistics LoadComponentAsync(container, d => { - if (!Score.Value.Equals(newScore)) + if (Score.Value?.Equals(newScore) != true) return; spinner.Hide(); From 77a7f475ee25314cc7d68e612f8bfbf2beae2ace Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 14 May 2024 13:03:46 +0200 Subject: [PATCH 3905/4092] Apply NRT to `ScorePanel` --- osu.Game/Screens/Ranking/ScorePanel.cs | 34 ++++++++++++-------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 1f7ba3692a..e283749e32 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -1,10 +1,7 @@ // 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 JetBrains.Annotations; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -83,8 +80,7 @@ namespace osu.Game.Screens.Ranking private static readonly Color4 contracted_top_layer_colour = Color4Extensions.FromHex("#353535"); private static readonly Color4 contracted_middle_layer_colour = Color4Extensions.FromHex("#353535"); - [CanBeNull] - public event Action StateChanged; + public event Action? StateChanged; /// /// The position of the score in the rankings. @@ -94,28 +90,30 @@ namespace osu.Game.Screens.Ranking /// /// An action to be invoked if this is clicked while in an expanded state. /// - public Action PostExpandAction; + public Action? PostExpandAction; public readonly ScoreInfo Score; [Resolved] - private OsuGameBase game { get; set; } + private OsuGameBase game { get; set; } = null!; - private AudioContainer audioContent; + private AudioContainer audioContent = null!; private bool displayWithFlair; - private Container topLayerContainer; - private Drawable topLayerBackground; - private Container topLayerContentContainer; - private Drawable topLayerContent; + private Container topLayerContainer = null!; + private Drawable topLayerBackground = null!; + private Container topLayerContentContainer = null!; + private Drawable? topLayerContent; - private Container middleLayerContainer; - private Drawable middleLayerBackground; - private Container middleLayerContentContainer; - private Drawable middleLayerContent; + private Container middleLayerContainer = null!; + private Drawable middleLayerBackground = null!; + private Container middleLayerContentContainer = null!; + private Drawable? middleLayerContent; - private DrawableSample samplePanelFocus; + private ScorePanelTrackingContainer? trackingContainer; + + private DrawableSample? samplePanelFocus; public ScorePanel(ScoreInfo score, bool isNewLocalScore = false) { @@ -334,8 +332,6 @@ namespace osu.Game.Screens.Ranking || topLayerContainer.ReceivePositionalInputAt(screenSpacePos) || middleLayerContainer.ReceivePositionalInputAt(screenSpacePos); - private ScorePanelTrackingContainer trackingContainer; - /// /// Creates a which this can reside inside. /// The will track the size of this . From 2f2257f6cec8254ca48432d6811a0cb823f0364c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 14 May 2024 13:07:49 +0200 Subject: [PATCH 3906/4092] Apply NRT to `PerformanceBreakdownChart` --- .../Statistics/PerformanceBreakdownChart.cs | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs b/osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs index 8b13f0951c..b5eed2d12a 100644 --- a/osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs +++ b/osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs @@ -1,13 +1,10 @@ // 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 System.Threading; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; @@ -31,16 +28,16 @@ namespace osu.Game.Screens.Ranking.Statistics private readonly ScoreInfo score; private readonly IBeatmap playableBeatmap; - private Drawable spinner; - private Drawable content; - private GridContainer chart; - private OsuSpriteText achievedPerformance; - private OsuSpriteText maximumPerformance; + private Drawable spinner = null!; + private Drawable content = null!; + private GridContainer chart = null!; + private OsuSpriteText achievedPerformance = null!; + private OsuSpriteText maximumPerformance = null!; private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); [Resolved] - private BeatmapDifficultyCache difficultyCache { get; set; } + private BeatmapDifficultyCache difficultyCache { get; set; } = null!; public PerformanceBreakdownChart(ScoreInfo score, IBeatmap playableBeatmap) { @@ -147,7 +144,7 @@ namespace osu.Game.Screens.Ranking.Statistics new PerformanceBreakdownCalculator(playableBeatmap, difficultyCache) .CalculateAsync(score, cancellationTokenSource.Token) - .ContinueWith(t => Schedule(() => setPerformanceValue(t.GetResultSafely()))); + .ContinueWith(t => Schedule(() => setPerformanceValue(t.GetResultSafely()!))); } private void setPerformanceValue(PerformanceBreakdown breakdown) @@ -189,8 +186,7 @@ namespace osu.Game.Screens.Ranking.Statistics maximumPerformance.Text = Math.Round(perfectAttribute.Value, MidpointRounding.AwayFromZero).ToLocalisableString(); } - [CanBeNull] - private Drawable[] createAttributeRow(PerformanceDisplayAttribute attribute, PerformanceDisplayAttribute perfectAttribute) + private Drawable[]? createAttributeRow(PerformanceDisplayAttribute attribute, PerformanceDisplayAttribute perfectAttribute) { // Don't display the attribute if its maximum is 0 // For example, flashlight bonus would be zero if flashlight mod isn't on @@ -239,7 +235,7 @@ namespace osu.Game.Screens.Ranking.Statistics protected override void Dispose(bool isDisposing) { - cancellationTokenSource?.Cancel(); + cancellationTokenSource.Cancel(); base.Dispose(isDisposing); } } From 237ae8b46a2c66af53a350e6972696d2cda79686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 14 May 2024 13:09:57 +0200 Subject: [PATCH 3907/4092] Apply NRT to `SimpleStatisticsItem` --- osu.Game/Screens/Ranking/Statistics/SimpleStatisticItem.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/SimpleStatisticItem.cs b/osu.Game/Screens/Ranking/Statistics/SimpleStatisticItem.cs index 23ccc3d0b7..d8de1b07b5 100644 --- a/osu.Game/Screens/Ranking/Statistics/SimpleStatisticItem.cs +++ b/osu.Game/Screens/Ranking/Statistics/SimpleStatisticItem.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.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; @@ -61,7 +59,7 @@ namespace osu.Game.Screens.Ranking.Statistics /// public partial class SimpleStatisticItem : SimpleStatisticItem { - private TValue value; + private TValue value = default!; /// /// The statistic's value to be displayed. @@ -80,7 +78,7 @@ namespace osu.Game.Screens.Ranking.Statistics /// Used to convert to a text representation. /// Defaults to using . /// - protected virtual string DisplayValue(TValue value) => value.ToString(); + protected virtual string DisplayValue(TValue value) => value!.ToString() ?? string.Empty; public SimpleStatisticItem(string name) : base(name) From 8e16b57d09663ecfd310487bb58db523b533e4b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 14 May 2024 13:10:36 +0200 Subject: [PATCH 3908/4092] Apply NRT to `SimpleStatisticTable` --- .../Screens/Ranking/Statistics/SimpleStatisticTable.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/SimpleStatisticTable.cs b/osu.Game/Screens/Ranking/Statistics/SimpleStatisticTable.cs index 4abf0007a7..da79fdb12b 100644 --- a/osu.Game/Screens/Ranking/Statistics/SimpleStatisticTable.cs +++ b/osu.Game/Screens/Ranking/Statistics/SimpleStatisticTable.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 osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -24,14 +21,14 @@ namespace osu.Game.Screens.Ranking.Statistics private readonly SimpleStatisticItem[] items; private readonly int columnCount; - private FillFlowContainer[] columns; + private FillFlowContainer[] columns = null!; /// /// Creates a statistic row for the supplied s. /// /// The number of columns to layout the into. /// The s to display in this row. - public SimpleStatisticTable(int columnCount, [ItemNotNull] IEnumerable items) + public SimpleStatisticTable(int columnCount, IEnumerable items) { ArgumentOutOfRangeException.ThrowIfNegativeOrZero(columnCount); From e7721b073cbd09651b48b9253e2d7d963339f5e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 14 May 2024 13:11:21 +0200 Subject: [PATCH 3909/4092] Apply NRT to `ContractedPanelTopContent` --- .../Screens/Ranking/Contracted/ContractedPanelTopContent.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Screens/Ranking/Contracted/ContractedPanelTopContent.cs b/osu.Game/Screens/Ranking/Contracted/ContractedPanelTopContent.cs index 93bc7c41e1..06d127b972 100644 --- a/osu.Game/Screens/Ranking/Contracted/ContractedPanelTopContent.cs +++ b/osu.Game/Screens/Ranking/Contracted/ContractedPanelTopContent.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.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -16,7 +14,7 @@ namespace osu.Game.Screens.Ranking.Contracted { public readonly Bindable ScorePosition = new Bindable(); - private OsuSpriteText text; + private OsuSpriteText text = null!; public ContractedPanelTopContent() { From ced1c79490f61b6d203d3777c740139e1cd7ff51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 14 May 2024 13:14:52 +0200 Subject: [PATCH 3910/4092] Apply NRT to `AccuracyCircle` --- .../Expanded/Accuracy/AccuracyCircle.cs | 42 +++++++++---------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index f04e4a6444..cebc54f490 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -93,17 +91,17 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy private readonly ScoreInfo score; - private CircularProgress accuracyCircle; - private GradedCircles gradedCircles; - private Container badges; - private RankText rankText; + private CircularProgress accuracyCircle = null!; + private GradedCircles gradedCircles = null!; + private Container badges = null!; + private RankText rankText = null!; - private PoolableSkinnableSample scoreTickSound; - private PoolableSkinnableSample badgeTickSound; - private PoolableSkinnableSample badgeMaxSound; - private PoolableSkinnableSample swooshUpSound; - private PoolableSkinnableSample rankImpactSound; - private PoolableSkinnableSample rankApplauseSound; + private PoolableSkinnableSample? scoreTickSound; + private PoolableSkinnableSample? badgeTickSound; + private PoolableSkinnableSample? badgeMaxSound; + private PoolableSkinnableSample? swooshUpSound; + private PoolableSkinnableSample? rankImpactSound; + private PoolableSkinnableSample? rankApplauseSound; private readonly Bindable tickPlaybackRate = new Bindable(); @@ -119,7 +117,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy private readonly bool withFlair; private readonly bool isFailedSDueToMisses; - private RankText failedSRankText; + private RankText failedSRankText = null!; public AccuracyCircle(ScoreInfo score, bool withFlair = false) { @@ -229,8 +227,8 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy this.Delay(swoosh_pre_delay).Schedule(() => { - swooshUpSound.VolumeTo(swoosh_volume); - swooshUpSound.Play(); + swooshUpSound!.VolumeTo(swoosh_volume); + swooshUpSound!.Play(); }); } @@ -287,8 +285,8 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy this.TransformBindableTo(tickPlaybackRate, score_tick_debounce_rate_start); this.TransformBindableTo(tickPlaybackRate, score_tick_debounce_rate_end, ACCURACY_TRANSFORM_DURATION, Easing.OutSine); - scoreTickSound.FrequencyTo(1 + targetAccuracy, ACCURACY_TRANSFORM_DURATION, Easing.OutSine); - scoreTickSound.VolumeTo(score_tick_volume_start).Then().VolumeTo(score_tick_volume_end, ACCURACY_TRANSFORM_DURATION, Easing.OutSine); + scoreTickSound!.FrequencyTo(1 + targetAccuracy, ACCURACY_TRANSFORM_DURATION, Easing.OutSine); + scoreTickSound!.VolumeTo(score_tick_volume_start).Then().VolumeTo(score_tick_volume_end, ACCURACY_TRANSFORM_DURATION, Easing.OutSine); isTicking = true; }); @@ -314,8 +312,8 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy { var dink = badgeNum < badges.Count - 1 ? badgeTickSound : badgeMaxSound; - dink.FrequencyTo(1 + badgeNum++ * 0.05); - dink.Play(); + dink!.FrequencyTo(1 + badgeNum++ * 0.05); + dink!.Play(); }); } } @@ -331,7 +329,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy Schedule(() => { isTicking = false; - rankImpactSound.Play(); + rankImpactSound!.Play(); }); const double applause_pre_delay = 545f; @@ -341,8 +339,8 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy { Schedule(() => { - rankApplauseSound.VolumeTo(applause_volume); - rankApplauseSound.Play(); + rankApplauseSound!.VolumeTo(applause_volume); + rankApplauseSound!.Play(); }); } } From b937b94bd2feb31995cbb2b0e0c0cc8126195b77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 14 May 2024 13:15:21 +0200 Subject: [PATCH 3911/4092] Apply NRT to `RankBadge` --- osu.Game/Screens/Ranking/Expanded/Accuracy/RankBadge.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/RankBadge.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/RankBadge.cs index 8aea6045eb..0e798c7d6e 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/RankBadge.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/RankBadge.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -34,8 +32,8 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy public readonly ScoreRank Rank; - private Drawable rankContainer; - private Drawable overlay; + private Drawable rankContainer = null!; + private Drawable overlay = null!; /// /// Creates a new . From 414f023817027d0433d99b921177e5f98b0ff2e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 14 May 2024 13:15:51 +0200 Subject: [PATCH 3912/4092] Apply NRT to `RankText` --- osu.Game/Screens/Ranking/Expanded/Accuracy/RankText.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/RankText.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/RankText.cs index b7adcb032f..76e59b32b8 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/RankText.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/RankText.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.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -23,9 +21,9 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy { private readonly ScoreRank rank; - private BufferedContainer flash; - private BufferedContainer superFlash; - private GlowingSpriteText rankText; + private BufferedContainer flash = null!; + private BufferedContainer superFlash = null!; + private GlowingSpriteText rankText = null!; public RankText(ScoreRank rank) { From ee9144c3bddd0b16b7bd18dd51222bfdadec5f18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 14 May 2024 13:17:31 +0200 Subject: [PATCH 3913/4092] Apply NRT to results statistics displays --- .../Ranking/Expanded/Statistics/AccuracyStatistic.cs | 4 +--- .../Screens/Ranking/Expanded/Statistics/ComboStatistic.cs | 4 +--- .../Screens/Ranking/Expanded/Statistics/CounterStatistic.cs | 4 +--- .../Ranking/Expanded/Statistics/PerformanceStatistic.cs | 6 ++---- .../Screens/Ranking/Expanded/Statistics/StatisticDisplay.cs | 6 ++---- 5 files changed, 7 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/AccuracyStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/AccuracyStatistic.cs index f1f2c47e20..a4672a475c 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/AccuracyStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/AccuracyStatistic.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.Framework.Graphics; using osu.Framework.Localisation; using osu.Game.Graphics; @@ -22,7 +20,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics { private readonly double accuracy; - private RollingCounter counter; + private RollingCounter counter = null!; /// /// Creates a new . diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/ComboStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/ComboStatistic.cs index 6290cee6da..7c91a37b77 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/ComboStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/ComboStatistic.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.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -22,7 +20,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics { private readonly bool isPerfect; - private Drawable perfectText; + private Drawable perfectText = null!; /// /// Creates a new . diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs index 8528dac83b..4042724c75 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.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.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; @@ -21,7 +19,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics private readonly int count; private readonly int? maxCount; - private RollingCounter counter; + private RollingCounter counter = null!; /// /// Creates a new . diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs index 8366f8d7ef..7ea3cbe917 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -32,7 +30,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - private RollingCounter counter; + private RollingCounter counter = null!; public PerformanceStatistic(ScoreInfo score) : base(BeatmapsetsStrings.ShowScoreboardHeaderspp) @@ -107,7 +105,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics protected override void Dispose(bool isDisposing) { - cancellationTokenSource?.Cancel(); + cancellationTokenSource.Cancel(); base.Dispose(isDisposing); } diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/StatisticDisplay.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/StatisticDisplay.cs index 686b6c7d47..9de60f013d 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/StatisticDisplay.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/StatisticDisplay.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.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.LocalisationExtensions; @@ -21,10 +19,10 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics /// public abstract partial class StatisticDisplay : CompositeDrawable { - protected SpriteText HeaderText { get; private set; } + protected SpriteText HeaderText { get; private set; } = null!; private readonly LocalisableString header; - private Drawable content; + private Drawable content = null!; /// /// Creates a new . From 12e98fe55df37690f24de1450c1f59a9e75dd274 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 May 2024 15:24:32 +0800 Subject: [PATCH 3914/4092] Move out of playfield so touch overlay is not affected by playfield position --- .../UI/DrawableManiaRuleset.cs | 10 ++++++- osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs | 30 ++----------------- .../UI/ManiaTouchInputOverlay.cs | 5 ++-- 3 files changed, 15 insertions(+), 30 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 275b1311de..a948117748 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -31,6 +31,7 @@ using osu.Game.Skinning; namespace osu.Game.Rulesets.Mania.UI { + [Cached] public partial class DrawableManiaRuleset : DrawableScrollingRuleset { /// @@ -43,7 +44,7 @@ namespace osu.Game.Rulesets.Mania.UI /// public const double MAX_TIME_RANGE = 11485; - protected new ManiaPlayfield Playfield => (ManiaPlayfield)base.Playfield; + public new ManiaPlayfield Playfield => (ManiaPlayfield)base.Playfield; public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap; @@ -103,6 +104,11 @@ namespace osu.Game.Rulesets.Mania.UI configScrollSpeed.BindValueChanged(speed => this.TransformTo(nameof(smoothTimeRange), ComputeScrollTime(speed.NewValue), 200, Easing.OutQuint)); TimeRange.Value = smoothTimeRange = ComputeScrollTime(configScrollSpeed.Value); + + KeyBindingInputManager.Add(touchOverlay = new ManiaTouchInputOverlay + { + RelativeSizeAxes = Axes.Both + }); } protected override void AdjustScrollSpeed(int amount) => configScrollSpeed.Value += amount; @@ -116,6 +122,8 @@ namespace osu.Game.Rulesets.Mania.UI private ScheduledDelegate? pendingSkinChange; private float hitPosition; + private ManiaTouchInputOverlay touchOverlay = null!; + private void onSkinChange() { // schedule required to avoid calls after disposed. diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 385a47f8b8..b3420c49f3 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -7,12 +7,10 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using System; using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics.Primitives; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; @@ -27,8 +25,6 @@ namespace osu.Game.Rulesets.Mania.UI private readonly List stages = new List(); - private readonly ManiaTouchInputOverlay touchOverlay; - public override Quad SkinnableComponentScreenSpaceDrawQuad { get @@ -64,23 +60,10 @@ namespace osu.Game.Rulesets.Mania.UI throw new ArgumentException("Can't have zero or fewer stages."); GridContainer playfieldGrid; - - RelativeSizeAxes = Axes.Y; - AutoSizeAxes = Axes.X; - - AddRangeInternal(new Drawable[] + AddInternal(playfieldGrid = new GridContainer { - playfieldGrid = new GridContainer - { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Content = new[] { new Drawable[stageDefinitions.Count] }, - ColumnDimensions = Enumerable.Range(0, stageDefinitions.Count).Select(_ => new Dimension(GridSizeMode.AutoSize)).ToArray() - }, - touchOverlay = new ManiaTouchInputOverlay - { - RelativeSizeAxes = Axes.Both - } + RelativeSizeAxes = Axes.Both, + Content = new[] { new Drawable[stageDefinitions.Count] } }); var normalColumnAction = ManiaAction.Key1; @@ -100,13 +83,6 @@ namespace osu.Game.Rulesets.Mania.UI } } - protected override void LoadComplete() - { - base.LoadComplete(); - - touchOverlay.Alpha = Mods?.Any(m => m is ModTouchDevice) == true ? 1 : 0; - } - public override void Add(HitObject hitObject) => getStageByColumn(((ManiaHitObject)hitObject).Column).Add(hitObject); public override bool Remove(HitObject hitObject) => getStageByColumn(((ManiaHitObject)hitObject).Column).Remove(hitObject); diff --git a/osu.Game.Rulesets.Mania/UI/ManiaTouchInputOverlay.cs b/osu.Game.Rulesets.Mania/UI/ManiaTouchInputOverlay.cs index ddff064133..a51a3a605b 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaTouchInputOverlay.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaTouchInputOverlay.cs @@ -33,12 +33,13 @@ namespace osu.Game.Rulesets.Mania.UI }; [Resolved] - private ManiaPlayfield playfield { get; set; } = null!; + private DrawableManiaRuleset drawableRuleset { get; set; } = null!; public ManiaTouchInputOverlay() { Anchor = Anchor.BottomCentre; Origin = Anchor.BottomCentre; + RelativeSizeAxes = Axes.Both; Height = 0.5f; } @@ -51,7 +52,7 @@ namespace osu.Game.Rulesets.Mania.UI bool first = true; - foreach (var stage in playfield.Stages) + foreach (var stage in drawableRuleset.Playfield.Stages) { foreach (var column in stage.Columns) { From 390557634a76d457d3412d207efe68417bcd7773 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 May 2024 19:16:07 +0800 Subject: [PATCH 3915/4092] Rename touch area class to match existing usage (see taiko) --- osu.Game.Rulesets.Mania.Tests/Mods/TestSceneModTouchDevice.cs | 2 +- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 4 ++-- .../UI/{ManiaTouchInputOverlay.cs => ManiaTouchInputArea.cs} | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) rename osu.Game.Rulesets.Mania/UI/{ManiaTouchInputOverlay.cs => ManiaTouchInputArea.cs} (97%) diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneModTouchDevice.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneModTouchDevice.cs index 4c5e4933ef..829cd0b62e 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneModTouchDevice.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneModTouchDevice.cs @@ -59,6 +59,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods private Drawable? getSkinnableOverlay() => this.ChildrenOfType() .SingleOrDefault(d => d.Lookup.Equals(new ManiaSkinComponentLookup(ManiaSkinComponents.TouchOverlay))); - private ManiaTouchInputOverlay.InputReceptor getReceptor(int index) => this.ChildrenOfType().ElementAt(index); + private ManiaTouchInputArea.InputReceptor getReceptor(int index) => this.ChildrenOfType().ElementAt(index); } } diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index a948117748..5974b76d65 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Mania.UI TimeRange.Value = smoothTimeRange = ComputeScrollTime(configScrollSpeed.Value); - KeyBindingInputManager.Add(touchOverlay = new ManiaTouchInputOverlay + KeyBindingInputManager.Add(touchArea = new ManiaTouchInputArea { RelativeSizeAxes = Axes.Both }); @@ -122,7 +122,7 @@ namespace osu.Game.Rulesets.Mania.UI private ScheduledDelegate? pendingSkinChange; private float hitPosition; - private ManiaTouchInputOverlay touchOverlay = null!; + private ManiaTouchInputArea touchArea = null!; private void onSkinChange() { diff --git a/osu.Game.Rulesets.Mania/UI/ManiaTouchInputOverlay.cs b/osu.Game.Rulesets.Mania/UI/ManiaTouchInputArea.cs similarity index 97% rename from osu.Game.Rulesets.Mania/UI/ManiaTouchInputOverlay.cs rename to osu.Game.Rulesets.Mania/UI/ManiaTouchInputArea.cs index a51a3a605b..0cb12128e8 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaTouchInputOverlay.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaTouchInputArea.cs @@ -14,7 +14,7 @@ using osuTK; namespace osu.Game.Rulesets.Mania.UI { - public partial class ManiaTouchInputOverlay : CompositeDrawable, ISerialisableDrawable + public partial class ManiaTouchInputArea : CompositeDrawable, ISerialisableDrawable { [SettingSource("Spacing", "The spacing between receptors.")] public BindableFloat Spacing { get; } = new BindableFloat(10) @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Mania.UI [Resolved] private DrawableManiaRuleset drawableRuleset { get; set; } = null!; - public ManiaTouchInputOverlay() + public ManiaTouchInputArea() { Anchor = Anchor.BottomCentre; Origin = Anchor.BottomCentre; From 5c9a90cb40320cf2f22e4b6ba9f94a7ab0d4e632 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 May 2024 19:28:14 +0800 Subject: [PATCH 3916/4092] Tidy class and change to be a `VisibilityContainer` similar to taiko implementation --- .../Mods/TestSceneModTouchDevice.cs | 11 ++-- .../UI/DrawableManiaRuleset.cs | 7 +-- .../UI/ManiaTouchInputArea.cs | 54 +++++++++++++++---- 3 files changed, 50 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneModTouchDevice.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneModTouchDevice.cs index 829cd0b62e..451cb617ee 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneModTouchDevice.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneModTouchDevice.cs @@ -3,12 +3,10 @@ using System.Linq; using NUnit.Framework; -using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Testing; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; -using osu.Game.Skinning; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests.Mods @@ -22,14 +20,14 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods { Mod = new ModTouchDevice(), Autoplay = false, - PassCondition = () => getSkinnableOverlay()?.IsPresent == true + PassCondition = () => getTouchOverlay()?.IsPresent == true }); [Test] public void TestOverlayNotVisibleWithoutMod() => CreateModTest(new ModTestData { Autoplay = false, - PassCondition = () => getSkinnableOverlay()?.IsPresent == false + PassCondition = () => getTouchOverlay()?.IsPresent == false }); [Test] @@ -56,9 +54,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods } } - private Drawable? getSkinnableOverlay() => this.ChildrenOfType() - .SingleOrDefault(d => d.Lookup.Equals(new ManiaSkinComponentLookup(ManiaSkinComponents.TouchOverlay))); + private ManiaTouchInputArea? getTouchOverlay() => this.ChildrenOfType().SingleOrDefault(); - private ManiaTouchInputArea.InputReceptor getReceptor(int index) => this.ChildrenOfType().ElementAt(index); + private ManiaTouchInputArea.ColumnInputReceptor getReceptor(int index) => this.ChildrenOfType().ElementAt(index); } } diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 5974b76d65..ce53862c76 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -105,10 +105,7 @@ namespace osu.Game.Rulesets.Mania.UI TimeRange.Value = smoothTimeRange = ComputeScrollTime(configScrollSpeed.Value); - KeyBindingInputManager.Add(touchArea = new ManiaTouchInputArea - { - RelativeSizeAxes = Axes.Both - }); + KeyBindingInputManager.Add(new ManiaTouchInputArea()); } protected override void AdjustScrollSpeed(int amount) => configScrollSpeed.Value += amount; @@ -122,8 +119,6 @@ namespace osu.Game.Rulesets.Mania.UI private ScheduledDelegate? pendingSkinChange; private float hitPosition; - private ManiaTouchInputArea touchArea = null!; - private void onSkinChange() { // schedule required to avoid calls after disposed. diff --git a/osu.Game.Rulesets.Mania/UI/ManiaTouchInputArea.cs b/osu.Game.Rulesets.Mania/UI/ManiaTouchInputArea.cs index 0cb12128e8..32e4616a25 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaTouchInputArea.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaTouchInputArea.cs @@ -9,13 +9,19 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Configuration; -using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Mania.UI { - public partial class ManiaTouchInputArea : CompositeDrawable, ISerialisableDrawable + /// + /// An overlay that captures and displays osu!mania mouse and touch input. + /// + public partial class ManiaTouchInputArea : VisibilityContainer { + // visibility state affects our child. we always want to handle input. + public override bool PropagatePositionalInputSubTree => true; + public override bool PropagateNonPositionalInputSubTree => true; + [SettingSource("Spacing", "The spacing between receptors.")] public BindableFloat Spacing { get; } = new BindableFloat(10) { @@ -35,6 +41,8 @@ namespace osu.Game.Rulesets.Mania.UI [Resolved] private DrawableManiaRuleset drawableRuleset { get; set; } = null!; + private GridContainer gridContainer = null!; + public ManiaTouchInputArea() { Anchor = Anchor.BottomCentre; @@ -62,16 +70,17 @@ namespace osu.Game.Rulesets.Mania.UI receptorGridDimensions.Add(new Dimension(GridSizeMode.AutoSize)); } - receptorGridContent.Add(new InputReceptor { Action = { BindTarget = column.Action } }); + receptorGridContent.Add(new ColumnInputReceptor { Action = { BindTarget = column.Action } }); receptorGridDimensions.Add(new Dimension()); first = false; } } - InternalChild = new GridContainer + InternalChild = gridContainer = new GridContainer { RelativeSizeAxes = Axes.Both, + AlwaysPresent = true, Content = new[] { receptorGridContent.ToArray() }, ColumnDimensions = receptorGridDimensions.ToArray() }; @@ -83,9 +92,36 @@ namespace osu.Game.Rulesets.Mania.UI Opacity.BindValueChanged(o => Alpha = o.NewValue, true); } - public bool UsesFixedAnchor { get; set; } + protected override bool OnKeyDown(KeyDownEvent e) + { + // Hide whenever the keyboard is used. + Hide(); + return false; + } - public partial class InputReceptor : CompositeDrawable + protected override bool OnMouseDown(MouseDownEvent e) + { + Show(); + return true; + } + + protected override bool OnTouchDown(TouchDownEvent e) + { + Show(); + return true; + } + + protected override void PopIn() + { + gridContainer.FadeIn(500, Easing.OutQuint); + } + + protected override void PopOut() + { + gridContainer.FadeOut(300); + } + + public partial class ColumnInputReceptor : CompositeDrawable { public readonly IBindable Action = new Bindable(); @@ -96,7 +132,7 @@ namespace osu.Game.Rulesets.Mania.UI private bool isPressed; - public InputReceptor() + public ColumnInputReceptor() { RelativeSizeAxes = Axes.Both; @@ -128,7 +164,7 @@ namespace osu.Game.Rulesets.Mania.UI protected override bool OnTouchDown(TouchDownEvent e) { updateButton(true); - return true; + return false; // handled by parent container to show overlay. } protected override void OnTouchUp(TouchUpEvent e) @@ -139,7 +175,7 @@ namespace osu.Game.Rulesets.Mania.UI protected override bool OnMouseDown(MouseDownEvent e) { updateButton(true); - return true; + return false; // handled by parent container to show overlay. } protected override void OnMouseUp(MouseUpEvent e) From 636e2004711fe91474574a0acdc1ebd3519e8cc4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 May 2024 21:56:00 +0800 Subject: [PATCH 3917/4092] Update tests in line with new structure --- ...ice.cs => TestSceneManiaTouchInputArea.cs} | 30 ++++++------------- 1 file changed, 9 insertions(+), 21 deletions(-) rename osu.Game.Rulesets.Mania.Tests/{Mods/TestSceneModTouchDevice.cs => TestSceneManiaTouchInputArea.cs} (64%) diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneModTouchDevice.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaTouchInputArea.cs similarity index 64% rename from osu.Game.Rulesets.Mania.Tests/Mods/TestSceneModTouchDevice.cs rename to osu.Game.Rulesets.Mania.Tests/TestSceneManiaTouchInputArea.cs index 451cb617ee..30c0113bff 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneModTouchDevice.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaTouchInputArea.cs @@ -3,42 +3,28 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Testing; using osu.Game.Rulesets.Mania.UI; -using osu.Game.Rulesets.Mods; using osu.Game.Tests.Visual; -namespace osu.Game.Rulesets.Mania.Tests.Mods +namespace osu.Game.Rulesets.Mania.Tests { - public partial class TestSceneModTouchDevice : ModTestScene + public partial class TestSceneManiaTouchInputArea : PlayerTestScene { protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); [Test] - public void TestOverlayVisibleWithMod() => CreateModTest(new ModTestData + public void TestTouchAreaNotInitiallyVisible() { - Mod = new ModTouchDevice(), - Autoplay = false, - PassCondition = () => getTouchOverlay()?.IsPresent == true - }); - - [Test] - public void TestOverlayNotVisibleWithoutMod() => CreateModTest(new ModTestData - { - Autoplay = false, - PassCondition = () => getTouchOverlay()?.IsPresent == false - }); + AddAssert("touch area not visible", () => getTouchOverlay()?.State.Value == Visibility.Hidden); + } [Test] public void TestPressReceptors() { - CreateModTest(new ModTestData - { - Mod = new ModTouchDevice(), - Autoplay = false, - PassCondition = () => true - }); + AddAssert("touch area not visible", () => getTouchOverlay()?.State.Value == Visibility.Hidden); for (int i = 0; i < 4; i++) { @@ -51,6 +37,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods () => Does.Contain(getReceptor(index).Action.Value)); AddStep($"release receptor {index}", () => InputManager.EndTouch(new Touch(TouchSource.Touch1, getReceptor(index).ScreenSpaceDrawQuad.Centre))); + + AddAssert("touch area visible", () => getTouchOverlay()?.State.Value == Visibility.Visible); } } From f781dc3300267bf99d57b5331aa4c3ed4a48d7a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 May 2024 22:38:31 +0800 Subject: [PATCH 3918/4092] Remove touch mod addition to mania Feels a bit pointless? I dunno. --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 23004e36a0..b5614e2b56 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -163,9 +163,6 @@ namespace osu.Game.Rulesets.Mania if (mods.HasFlagFast(LegacyMods.ScoreV2)) yield return new ModScoreV2(); - - if (mods.HasFlagFast(LegacyMods.TouchDevice)) - yield return new ModTouchDevice(); } public override LegacyMods ConvertToLegacyMods(Mod[] mods) @@ -228,10 +225,6 @@ namespace osu.Game.Rulesets.Mania case ManiaModRandom: value |= LegacyMods.Random; break; - - case ModTouchDevice: - value |= LegacyMods.TouchDevice; - break; } } @@ -303,7 +296,6 @@ namespace osu.Game.Rulesets.Mania case ModType.System: return new Mod[] { - new ModTouchDevice(), new ModScoreV2(), }; From cff865b556399e1b15206d4d080147d473277f84 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 May 2024 23:54:07 +0800 Subject: [PATCH 3919/4092] Continue loading even when osu! logo is being dragged at loading screen Closes https://github.com/ppy/osu/issues/28130. --- 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 4f7e21dddf..51a0c94ff0 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -113,7 +113,7 @@ namespace osu.Game.Screens.Play // not ready if the user is hovering one of the panes (logo is excluded), unless they are idle. (IsHovered || osuLogo?.IsHovered == true || idleTracker.IsIdle.Value) // not ready if the user is dragging a slider or otherwise. - && inputManager.DraggedDrawable == null + && (inputManager.DraggedDrawable == null || inputManager.DraggedDrawable is OsuLogo) // not ready if a focused overlay is visible, like settings. && inputManager.FocusedDrawable == null; From 3d190f7e88ff4beb32addcef83e20f2087de0061 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 14 May 2024 18:41:15 +0200 Subject: [PATCH 3920/4092] Remove redundant cast --- osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 967cdb0e54..c229039dc3 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Edit { } - public new ManiaPlayfield Playfield => ((ManiaPlayfield)drawableRuleset.Playfield); + public new ManiaPlayfield Playfield => drawableRuleset.Playfield; public IScrollingInfo ScrollingInfo => drawableRuleset.ScrollingInfo; From a3960bf7155f6019dd552783b2266f7896df2d34 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 May 2024 14:17:28 +0800 Subject: [PATCH 3921/4092] Add inline comment explaining `LifetimeEnd` set for future visitors --- .../Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index e70e181a50..7841e65935 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -240,6 +240,13 @@ namespace osu.Game.Rulesets.UI.Scrolling // always load the hitobject before its first judgement offset entry.LifetimeStart = Math.Min(entry.HitObject.StartTime - entry.HitObject.MaximumJudgementOffset, computedStartTime); + + // This is likely not entirely correct, but sets a sane expectation of the ending lifetime. + // A more correct lifetime will be overwritten after a DrawableHitObject is assigned via DrawableHitObject.updateState. + // + // It is required that we set a lifetime end here to ensure that in scenarios like loading a Player instance to a seeked + // location in a beatmap doesn't churn every hit object into a DrawableHitObject. Even in a pooled scenario, the overhead + // of this can be quite crippling. entry.LifetimeEnd = entry.HitObject.GetEndTime() + timeRange.Value; } From c4ac6d20a09b5704dd484b633142f517b527e2c2 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 15 May 2024 23:40:51 +0200 Subject: [PATCH 3922/4092] fix code quality --- .../Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs index a1f6a1732a..c188d23a58 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs @@ -12,9 +12,6 @@ namespace osu.Game.Screens.Edit.Compose.Components { public partial class SelectionBoxScaleHandle : SelectionBoxDragHandle { - [Resolved] - private SelectionBox selectionBox { get; set; } = null!; - [Resolved] private SelectionScaleHandler? scaleHandler { get; set; } From 21f5d891bb28a2edd835b4d4a2e69895b5ecf5dd Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 16 May 2024 04:36:14 +0300 Subject: [PATCH 3923/4092] Rename and move footer classes to appropriate places --- .../TestSceneScreenFooter.cs} | 25 ++++++++++--------- ....cs => TestSceneScreenFooterButtonMods.cs} | 16 ++++++------ .../FooterV2.cs => Footer/ScreenFooter.cs} | 12 ++++----- .../ScreenFooterButton.cs} | 6 ++--- .../Footer}/BeatmapOptionsPopover.cs | 7 +++--- .../Footer/ScreenFooterButtonMods.cs} | 5 ++-- .../Footer/ScreenFooterButtonOptions.cs} | 5 ++-- .../Footer/ScreenFooterButtonRandom.cs} | 5 ++-- 8 files changed, 43 insertions(+), 38 deletions(-) rename osu.Game.Tests/Visual/{SongSelect/TestSceneSongSelectFooterV2.cs => UserInterface/TestSceneScreenFooter.cs} (89%) rename osu.Game.Tests/Visual/UserInterface/{TestSceneFooterButtonModsV2.cs => TestSceneScreenFooterButtonMods.cs} (90%) rename osu.Game/Screens/{Select/FooterV2/FooterV2.cs => Footer/ScreenFooter.cs} (86%) rename osu.Game/Screens/{Select/FooterV2/FooterButtonV2.cs => Footer/ScreenFooterButton.cs} (97%) rename osu.Game/Screens/{Select/FooterV2 => SelectV2/Footer}/BeatmapOptionsPopover.cs (96%) rename osu.Game/Screens/{Select/FooterV2/FooterButtonModsV2.cs => SelectV2/Footer/ScreenFooterButtonMods.cs} (98%) rename osu.Game/Screens/{Select/FooterV2/FooterButtonOptionsV2.cs => SelectV2/Footer/ScreenFooterButtonOptions.cs} (83%) rename osu.Game/Screens/{Select/FooterV2/FooterButtonRandomV2.cs => SelectV2/Footer/ScreenFooterButtonRandom.cs} (97%) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectFooterV2.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs similarity index 89% rename from osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectFooterV2.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs index 93402e42ce..162609df70 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectFooterV2.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs @@ -15,15 +15,16 @@ using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Screens.Select.FooterV2; +using osu.Game.Screens.Footer; +using osu.Game.Screens.SelectV2.Footer; using osuTK.Input; -namespace osu.Game.Tests.Visual.SongSelect +namespace osu.Game.Tests.Visual.UserInterface { - public partial class TestSceneSongSelectFooterV2 : OsuManualInputManagerTestScene + public partial class TestSceneScreenFooter : OsuManualInputManagerTestScene { - private FooterButtonRandomV2 randomButton = null!; - private FooterButtonModsV2 modsButton = null!; + private ScreenFooterButtonRandom randomButton = null!; + private ScreenFooterButtonMods modsButton = null!; private bool nextRandomCalled; private bool previousRandomCalled; @@ -39,25 +40,25 @@ namespace osu.Game.Tests.Visual.SongSelect nextRandomCalled = false; previousRandomCalled = false; - FooterV2 footer; + ScreenFooter footer; Children = new Drawable[] { new PopoverContainer { RelativeSizeAxes = Axes.Both, - Child = footer = new FooterV2(), + Child = footer = new ScreenFooter(), }, overlay = new DummyOverlay() }; - footer.AddButton(modsButton = new FooterButtonModsV2 { Current = SelectedMods }, overlay); - footer.AddButton(randomButton = new FooterButtonRandomV2 + footer.AddButton(modsButton = new ScreenFooterButtonMods { Current = SelectedMods }, overlay); + footer.AddButton(randomButton = new ScreenFooterButtonRandom { NextRandom = () => nextRandomCalled = true, PreviousRandom = () => previousRandomCalled = true }); - footer.AddButton(new FooterButtonOptionsV2()); + footer.AddButton(new ScreenFooterButtonOptions()); overlay.Hide(); }); @@ -98,7 +99,7 @@ namespace osu.Game.Tests.Visual.SongSelect { AddStep("enable options", () => { - var optionsButton = this.ChildrenOfType().Last(); + var optionsButton = this.ChildrenOfType().Last(); optionsButton.Enabled.Value = true; optionsButton.TriggerClick(); @@ -108,7 +109,7 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestState() { - AddToggleStep("set options enabled state", state => this.ChildrenOfType().Last().Enabled.Value = state); + AddToggleStep("set options enabled state", state => this.ChildrenOfType().Last().Enabled.Value = state); } [Test] diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonModsV2.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooterButtonMods.cs similarity index 90% rename from osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonModsV2.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooterButtonMods.cs index 4aca9dde3d..df2109ace8 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonModsV2.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooterButtonMods.cs @@ -12,21 +12,21 @@ using osu.Game.Graphics.Sprites; using osu.Game.Overlays; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Screens.Select.FooterV2; +using osu.Game.Screens.SelectV2.Footer; using osu.Game.Utils; namespace osu.Game.Tests.Visual.UserInterface { - public partial class TestSceneFooterButtonModsV2 : OsuTestScene + public partial class TestSceneScreenFooterButtonMods : OsuTestScene { - private readonly TestFooterButtonModsV2 footerButtonMods; + private readonly TestScreenFooterButtonMods footerButtonMods; [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); - public TestSceneFooterButtonModsV2() + public TestSceneScreenFooterButtonMods() { - Add(footerButtonMods = new TestFooterButtonModsV2 + Add(footerButtonMods = new TestScreenFooterButtonMods { Anchor = Anchor.Centre, Origin = Anchor.CentreLeft, @@ -97,9 +97,9 @@ namespace osu.Game.Tests.Visual.UserInterface public void TestUnrankedBadge() { AddStep(@"Add unranked mod", () => changeMods(new[] { new OsuModDeflate() })); - AddUntilStep("Unranked badge shown", () => footerButtonMods.ChildrenOfType().Single().Alpha == 1); + AddUntilStep("Unranked badge shown", () => footerButtonMods.ChildrenOfType().Single().Alpha == 1); AddStep(@"Clear selected mod", () => changeMods(Array.Empty())); - AddUntilStep("Unranked badge not shown", () => footerButtonMods.ChildrenOfType().Single().Alpha == 0); + AddUntilStep("Unranked badge not shown", () => footerButtonMods.ChildrenOfType().Single().Alpha == 0); } private void changeMods(IReadOnlyList mods) => footerButtonMods.Current.Value = mods; @@ -112,7 +112,7 @@ namespace osu.Game.Tests.Visual.UserInterface return expectedValue == footerButtonMods.MultiplierText.Current.Value; } - private partial class TestFooterButtonModsV2 : FooterButtonModsV2 + private partial class TestScreenFooterButtonMods : ScreenFooterButtonMods { public new OsuSpriteText MultiplierText => base.MultiplierText; } diff --git a/osu.Game/Screens/Select/FooterV2/FooterV2.cs b/osu.Game/Screens/Footer/ScreenFooter.cs similarity index 86% rename from osu.Game/Screens/Select/FooterV2/FooterV2.cs rename to osu.Game/Screens/Footer/ScreenFooter.cs index 370c28e2a5..01013bb466 100644 --- a/osu.Game/Screens/Select/FooterV2/FooterV2.cs +++ b/osu.Game/Screens/Footer/ScreenFooter.cs @@ -11,9 +11,9 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osuTK; -namespace osu.Game.Screens.Select.FooterV2 +namespace osu.Game.Screens.Footer { - public partial class FooterV2 : InputBlockingContainer + public partial class ScreenFooter : InputBlockingContainer { //Should be 60, setting to 50 for now for the sake of matching the current BackButton height. private const int height = 50; @@ -23,7 +23,7 @@ namespace osu.Game.Screens.Select.FooterV2 /// The button to be added. /// The to be toggled by this button. - public void AddButton(FooterButtonV2 button, OverlayContainer? overlay = null) + public void AddButton(ScreenFooterButton button, OverlayContainer? overlay = null) { if (overlay != null) { @@ -46,9 +46,9 @@ namespace osu.Game.Screens.Select.FooterV2 } } - private FillFlowContainer buttons = null!; + private FillFlowContainer buttons = null!; - public FooterV2() + public ScreenFooter() { RelativeSizeAxes = Axes.X; Height = height; @@ -66,7 +66,7 @@ namespace osu.Game.Screens.Select.FooterV2 RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background5 }, - buttons = new FillFlowContainer + buttons = new FillFlowContainer { Position = new Vector2(TwoLayerButton.SIZE_EXTENDED.X + padding, 10), Anchor = Anchor.BottomLeft, diff --git a/osu.Game/Screens/Select/FooterV2/FooterButtonV2.cs b/osu.Game/Screens/Footer/ScreenFooterButton.cs similarity index 97% rename from osu.Game/Screens/Select/FooterV2/FooterButtonV2.cs rename to osu.Game/Screens/Footer/ScreenFooterButton.cs index 80103242f8..b3b3c9a8ec 100644 --- a/osu.Game/Screens/Select/FooterV2/FooterButtonV2.cs +++ b/osu.Game/Screens/Footer/ScreenFooterButton.cs @@ -21,9 +21,9 @@ using osu.Game.Overlays; using osuTK; using osuTK.Graphics; -namespace osu.Game.Screens.Select.FooterV2 +namespace osu.Game.Screens.Footer { - public partial class FooterButtonV2 : OsuClickableContainer, IKeyBindingHandler + public partial class ScreenFooterButton : OsuClickableContainer, IKeyBindingHandler { // This should be 12 by design, but an extra allowance is added due to the corner radius specification. private const float shear_width = 13.5f; @@ -70,7 +70,7 @@ namespace osu.Game.Screens.Select.FooterV2 private readonly Box glowBox; private readonly Box flashLayer; - public FooterButtonV2() + public ScreenFooterButton() { Size = new Vector2(BUTTON_WIDTH, BUTTON_HEIGHT); diff --git a/osu.Game/Screens/Select/FooterV2/BeatmapOptionsPopover.cs b/osu.Game/Screens/SelectV2/Footer/BeatmapOptionsPopover.cs similarity index 96% rename from osu.Game/Screens/Select/FooterV2/BeatmapOptionsPopover.cs rename to osu.Game/Screens/SelectV2/Footer/BeatmapOptionsPopover.cs index d98164c306..f73be15a36 100644 --- a/osu.Game/Screens/Select/FooterV2/BeatmapOptionsPopover.cs +++ b/osu.Game/Screens/SelectV2/Footer/BeatmapOptionsPopover.cs @@ -20,24 +20,25 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Localisation; using osu.Game.Overlays; +using osu.Game.Screens.Select; using osuTK; using osuTK.Graphics; using osuTK.Input; using WebCommonStrings = osu.Game.Resources.Localisation.Web.CommonStrings; -namespace osu.Game.Screens.Select.FooterV2 +namespace osu.Game.Screens.SelectV2.Footer { public partial class BeatmapOptionsPopover : OsuPopover { private FillFlowContainer buttonFlow = null!; - private readonly FooterButtonOptionsV2 footerButton; + private readonly ScreenFooterButtonOptions footerButton; private WorkingBeatmap beatmapWhenOpening = null!; [Resolved] private IBindable beatmap { get; set; } = null!; - public BeatmapOptionsPopover(FooterButtonOptionsV2 footerButton) + public BeatmapOptionsPopover(ScreenFooterButtonOptions footerButton) { this.footerButton = footerButton; } diff --git a/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs b/osu.Game/Screens/SelectV2/Footer/ScreenFooterButtonMods.cs similarity index 98% rename from osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs rename to osu.Game/Screens/SelectV2/Footer/ScreenFooterButtonMods.cs index 44db49b927..49961c60f8 100644 --- a/osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs +++ b/osu.Game/Screens/SelectV2/Footer/ScreenFooterButtonMods.cs @@ -21,14 +21,15 @@ using osu.Game.Graphics.Sprites; using osu.Game.Localisation; using osu.Game.Overlays; using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Footer; using osu.Game.Screens.Play.HUD; using osu.Game.Utils; using osuTK; using osuTK.Graphics; -namespace osu.Game.Screens.Select.FooterV2 +namespace osu.Game.Screens.SelectV2.Footer { - public partial class FooterButtonModsV2 : FooterButtonV2, IHasCurrentValue> + public partial class ScreenFooterButtonMods : ScreenFooterButton, IHasCurrentValue> { // todo: see https://github.com/ppy/osu-framework/issues/3271 private const float torus_scale_factor = 1.2f; diff --git a/osu.Game/Screens/Select/FooterV2/FooterButtonOptionsV2.cs b/osu.Game/Screens/SelectV2/Footer/ScreenFooterButtonOptions.cs similarity index 83% rename from osu.Game/Screens/Select/FooterV2/FooterButtonOptionsV2.cs rename to osu.Game/Screens/SelectV2/Footer/ScreenFooterButtonOptions.cs index 555215056a..74fe3e3d11 100644 --- a/osu.Game/Screens/Select/FooterV2/FooterButtonOptionsV2.cs +++ b/osu.Game/Screens/SelectV2/Footer/ScreenFooterButtonOptions.cs @@ -8,10 +8,11 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osu.Game.Input.Bindings; +using osu.Game.Screens.Footer; -namespace osu.Game.Screens.Select.FooterV2 +namespace osu.Game.Screens.SelectV2.Footer { - public partial class FooterButtonOptionsV2 : FooterButtonV2, IHasPopover + public partial class ScreenFooterButtonOptions : ScreenFooterButton, IHasPopover { [BackgroundDependencyLoader] private void load(OsuColour colour) diff --git a/osu.Game/Screens/Select/FooterV2/FooterButtonRandomV2.cs b/osu.Game/Screens/SelectV2/Footer/ScreenFooterButtonRandom.cs similarity index 97% rename from osu.Game/Screens/Select/FooterV2/FooterButtonRandomV2.cs rename to osu.Game/Screens/SelectV2/Footer/ScreenFooterButtonRandom.cs index 70d1c0c19e..e8e850a9ce 100644 --- a/osu.Game/Screens/Select/FooterV2/FooterButtonRandomV2.cs +++ b/osu.Game/Screens/SelectV2/Footer/ScreenFooterButtonRandom.cs @@ -10,12 +10,13 @@ using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Input.Bindings; +using osu.Game.Screens.Footer; using osuTK; using osuTK.Input; -namespace osu.Game.Screens.Select.FooterV2 +namespace osu.Game.Screens.SelectV2.Footer { - public partial class FooterButtonRandomV2 : FooterButtonV2 + public partial class ScreenFooterButtonRandom : ScreenFooterButton { public Action? NextRandom { get; set; } public Action? PreviousRandom { get; set; } From e3afd89dc879d6b21c41abc2df749729d314fedc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 16 May 2024 04:49:33 +0300 Subject: [PATCH 3924/4092] Allow specifying height of `ShearedButton`s Also includes a test case in `TestSceneShearedButton`s to ensure the buttons' shear factors don't change on different heights (I've encountered issues with that previously). --- .../UserInterface/TestSceneShearedButtons.cs | 51 +++++++++++++++++-- .../Graphics/UserInterface/ShearedButton.cs | 9 ++-- .../Mods/ModFooterInformationDisplay.cs | 2 +- 3 files changed, 53 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneShearedButtons.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneShearedButtons.cs index 118d32ee70..8db22f2d65 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneShearedButtons.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneShearedButtons.cs @@ -7,11 +7,13 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; +using osuTK; using osuTK.Input; namespace osu.Game.Tests.Visual.UserInterface @@ -35,7 +37,7 @@ namespace osu.Game.Tests.Visual.UserInterface if (bigButton) { - Child = button = new ShearedButton(400) + Child = button = new ShearedButton(400, 80) { LighterColour = Colour4.FromHex("#FFFFFF"), DarkerColour = Colour4.FromHex("#FFCC22"), @@ -44,13 +46,12 @@ namespace osu.Game.Tests.Visual.UserInterface Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = "Let's GO!", - Height = 80, Action = () => actionFired = true, }; } else { - Child = button = new ShearedButton(200) + Child = button = new ShearedButton(200, 80) { LighterColour = Colour4.FromHex("#FF86DD"), DarkerColour = Colour4.FromHex("#DE31AE"), @@ -58,7 +59,6 @@ namespace osu.Game.Tests.Visual.UserInterface Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = "Press me", - Height = 80, Action = () => actionFired = true, }; } @@ -171,5 +171,48 @@ namespace osu.Game.Tests.Visual.UserInterface void setToggleDisabledState(bool disabled) => AddStep($"{(disabled ? "disable" : "enable")} toggle", () => button.Active.Disabled = disabled); } + + [Test] + public void TestButtons() + { + AddStep("create buttons", () => Children = new[] + { + new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Scale = new Vector2(2.5f), + Children = new Drawable[] + { + new ShearedButton(120) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Text = "Test", + Action = () => { }, + Padding = new MarginPadding(), + }, + new ShearedButton(120, 40) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Text = "Test", + Action = () => { }, + Padding = new MarginPadding { Left = -1f }, + }, + new ShearedButton(120, 70) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Text = "Test", + Action = () => { }, + Padding = new MarginPadding { Left = 3f }, + }, + } + } + }); + } } } diff --git a/osu.Game/Graphics/UserInterface/ShearedButton.cs b/osu.Game/Graphics/UserInterface/ShearedButton.cs index b1e7066a01..caf1f76d88 100644 --- a/osu.Game/Graphics/UserInterface/ShearedButton.cs +++ b/osu.Game/Graphics/UserInterface/ShearedButton.cs @@ -17,7 +17,7 @@ namespace osu.Game.Graphics.UserInterface { public partial class ShearedButton : OsuClickableContainer { - public const float HEIGHT = 50; + public const float DEFAULT_HEIGHT = 50; public const float CORNER_RADIUS = 7; public const float BORDER_THICKNESS = 2; @@ -85,10 +85,11 @@ namespace osu.Game.Graphics.UserInterface /// If a value is provided (or the argument is omitted entirely), the button will autosize in width to fit the text. /// /// - public ShearedButton(float? width = null) + /// The height of the button. + public ShearedButton(float? width = null, float height = DEFAULT_HEIGHT) { - Height = HEIGHT; - Padding = new MarginPadding { Horizontal = shear * 50 }; + Height = height; + Padding = new MarginPadding { Horizontal = shear * height }; Content.CornerRadius = CORNER_RADIUS; Content.Shear = new Vector2(shear, 0); diff --git a/osu.Game/Overlays/Mods/ModFooterInformationDisplay.cs b/osu.Game/Overlays/Mods/ModFooterInformationDisplay.cs index 7fccf0cc13..8668879850 100644 --- a/osu.Game/Overlays/Mods/ModFooterInformationDisplay.cs +++ b/osu.Game/Overlays/Mods/ModFooterInformationDisplay.cs @@ -36,7 +36,7 @@ namespace osu.Game.Overlays.Mods Origin = Anchor.BottomRight, Anchor = Anchor.BottomRight, AutoSizeAxes = Axes.X, - Height = ShearedButton.HEIGHT, + Height = ShearedButton.DEFAULT_HEIGHT, Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0), CornerRadius = ShearedButton.CORNER_RADIUS, BorderThickness = ShearedButton.BORDER_THICKNESS, From 266f0803624415acbf6bde6eb3a9b63f7ea9b83c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 16 May 2024 05:01:49 +0300 Subject: [PATCH 3925/4092] Allow customising content of `ShearedButton`s --- .../Graphics/UserInterface/ShearedButton.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ShearedButton.cs b/osu.Game/Graphics/UserInterface/ShearedButton.cs index caf1f76d88..0fd21502a1 100644 --- a/osu.Game/Graphics/UserInterface/ShearedButton.cs +++ b/osu.Game/Graphics/UserInterface/ShearedButton.cs @@ -75,6 +75,8 @@ namespace osu.Game.Graphics.UserInterface private readonly Container backgroundLayer; private readonly Box flashLayer; + protected readonly Container ButtonContent; + /// /// Creates a new /// @@ -110,12 +112,16 @@ namespace osu.Game.Graphics.UserInterface { RelativeSizeAxes = Axes.Both }, - text = new OsuSpriteText + ButtonContent = new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = OsuFont.TorusAlternate.With(size: 17), - Shear = new Vector2(-shear, 0) + AutoSizeAxes = Axes.Both, + Shear = new Vector2(-shear, 0), + Child = text = new OsuSpriteText + { + Font = OsuFont.TorusAlternate.With(size: 17), + } }, } }, @@ -189,7 +195,7 @@ namespace osu.Game.Graphics.UserInterface { var colourDark = darkerColour ?? ColourProvider.Background3; var colourLight = lighterColour ?? ColourProvider.Background1; - var colourText = textColour ?? ColourProvider.Content1; + var colourContent = textColour ?? ColourProvider.Content1; if (!Enabled.Value) { @@ -206,9 +212,9 @@ namespace osu.Game.Graphics.UserInterface backgroundLayer.TransformTo(nameof(BorderColour), ColourInfo.GradientVertical(colourDark, colourLight), 150, Easing.OutQuint); if (!Enabled.Value) - colourText = colourText.Opacity(0.6f); + colourContent = colourContent.Opacity(0.6f); - text.FadeColour(colourText, 150, Easing.OutQuint); + ButtonContent.FadeColour(colourContent, 150, Easing.OutQuint); } } } From 7e8d5a5b0a1d0c58aab8de8afd6dc56094fd4f00 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 16 May 2024 04:50:51 +0300 Subject: [PATCH 3926/4092] Add new footer back button --- osu.Game/Screens/Footer/ScreenBackButton.cs | 64 +++++++++++++++++++++ osu.Game/Screens/Footer/ScreenFooter.cs | 17 ++++-- 2 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 osu.Game/Screens/Footer/ScreenBackButton.cs diff --git a/osu.Game/Screens/Footer/ScreenBackButton.cs b/osu.Game/Screens/Footer/ScreenBackButton.cs new file mode 100644 index 0000000000..c5e613ea51 --- /dev/null +++ b/osu.Game/Screens/Footer/ScreenBackButton.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 osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Localisation; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Footer +{ + public partial class ScreenBackButton : ShearedButton + { + // todo: see https://github.com/ppy/osu-framework/issues/3271 + private const float torus_scale_factor = 1.2f; + + public const float BUTTON_WIDTH = 240; + + public ScreenBackButton() + : base(BUTTON_WIDTH, 70) + { + } + + [BackgroundDependencyLoader] + private void load() + { + ButtonContent.Child = new FillFlowContainer + { + X = -10f, + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(20f, 0f), + Children = new Drawable[] + { + new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(20f), + Icon = FontAwesome.Solid.ChevronLeft, + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.TorusAlternate.With(size: 20 * torus_scale_factor), + Text = CommonStrings.Back, + UseFullGlyphHeight = false, + } + } + }; + + DarkerColour = Color4Extensions.FromHex("#DE31AE"); + LighterColour = Color4Extensions.FromHex("#FF86DD"); + TextColour = Color4.White; + } + } +} diff --git a/osu.Game/Screens/Footer/ScreenFooter.cs b/osu.Game/Screens/Footer/ScreenFooter.cs index 01013bb466..4a10a4cdab 100644 --- a/osu.Game/Screens/Footer/ScreenFooter.cs +++ b/osu.Game/Screens/Footer/ScreenFooter.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osuTK; @@ -15,9 +14,8 @@ namespace osu.Game.Screens.Footer { public partial class ScreenFooter : InputBlockingContainer { - //Should be 60, setting to 50 for now for the sake of matching the current BackButton height. - private const int height = 50; - private const int padding = 80; + private const int height = 60; + private const int padding = 60; private readonly List overlays = new List(); @@ -68,13 +66,20 @@ namespace osu.Game.Screens.Footer }, buttons = new FillFlowContainer { - Position = new Vector2(TwoLayerButton.SIZE_EXTENDED.X + padding, 10), + Position = new Vector2(ScreenBackButton.BUTTON_WIDTH + padding, 10), Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, Direction = FillDirection.Horizontal, Spacing = new Vector2(7, 0), AutoSizeAxes = Axes.Both - } + }, + new ScreenBackButton + { + Margin = new MarginPadding { Bottom = 10f, Left = 12f }, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Action = () => { }, + }, }; } } From 9446f45acf7da4bea4f042dc68a36e2c7d42db53 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 16 May 2024 05:03:34 +0300 Subject: [PATCH 3927/4092] Fix shear factor of `ScreenFooterButton`s not matching anything else When shear factors differ between components that are close to each other (`BackButtonV2` and `ScreenFooterButton` in this case), they look completely ugly. --- osu.Game/Screens/Footer/ScreenFooterButton.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Footer/ScreenFooterButton.cs b/osu.Game/Screens/Footer/ScreenFooterButton.cs index b3b3c9a8ec..3a23249ae0 100644 --- a/osu.Game/Screens/Footer/ScreenFooterButton.cs +++ b/osu.Game/Screens/Footer/ScreenFooterButton.cs @@ -25,8 +25,9 @@ namespace osu.Game.Screens.Footer { public partial class ScreenFooterButton : OsuClickableContainer, IKeyBindingHandler { - // This should be 12 by design, but an extra allowance is added due to the corner radius specification. - private const float shear_width = 13.5f; + // if we go by design, both this and ShearedButton should have shear factor as 1/7, + // but ShearedButton uses 1/5 so we must follow suit for the design to stay consistent. + private const float shear = 1f / 5f; protected const int CORNER_RADIUS = 10; protected const int BUTTON_HEIGHT = 90; @@ -34,7 +35,7 @@ namespace osu.Game.Screens.Footer public Bindable OverlayState = new Bindable(); - protected static readonly Vector2 BUTTON_SHEAR = new Vector2(shear_width / BUTTON_HEIGHT, 0); + protected static readonly Vector2 BUTTON_SHEAR = new Vector2(shear, 0); [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; From 7fce4cc4948803047de66133b07060a9991ff426 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 16 May 2024 05:30:19 +0300 Subject: [PATCH 3928/4092] Make `ScreenFooterButtonMods` share shear factor with base class --- .../SelectV2/Footer/ScreenFooterButtonMods.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/SelectV2/Footer/ScreenFooterButtonMods.cs b/osu.Game/Screens/SelectV2/Footer/ScreenFooterButtonMods.cs index 49961c60f8..f590a19164 100644 --- a/osu.Game/Screens/SelectV2/Footer/ScreenFooterButtonMods.cs +++ b/osu.Game/Screens/SelectV2/Footer/ScreenFooterButtonMods.cs @@ -33,12 +33,9 @@ namespace osu.Game.Screens.SelectV2.Footer { // todo: see https://github.com/ppy/osu-framework/issues/3271 private const float torus_scale_factor = 1.2f; - private const float bar_shear_width = 7f; private const float bar_height = 37f; private const float mod_display_portion = 0.65f; - private static readonly Vector2 bar_shear = new Vector2(bar_shear_width / bar_height, 0); - private readonly BindableWithCurrent> current = new BindableWithCurrent>(Array.Empty()); public Bindable> Current @@ -77,7 +74,7 @@ namespace osu.Game.Screens.SelectV2.Footer Y = -5f, Depth = float.MaxValue, Origin = Anchor.BottomLeft, - Shear = bar_shear, + Shear = BUTTON_SHEAR, CornerRadius = CORNER_RADIUS, Size = new Vector2(BUTTON_WIDTH, bar_height), Masking = true, @@ -107,7 +104,7 @@ namespace osu.Game.Screens.SelectV2.Footer { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Shear = -bar_shear, + Shear = -BUTTON_SHEAR, UseFullGlyphHeight = false, Font = OsuFont.Torus.With(size: 14 * torus_scale_factor, weight: FontWeight.Bold) } @@ -129,7 +126,7 @@ namespace osu.Game.Screens.SelectV2.Footer { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Shear = -bar_shear, + Shear = -BUTTON_SHEAR, Scale = new Vector2(0.6f), Current = { BindTarget = Current }, ExpansionMode = ExpansionMode.AlwaysContracted, @@ -138,7 +135,7 @@ namespace osu.Game.Screens.SelectV2.Footer { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Shear = -bar_shear, + Shear = -BUTTON_SHEAR, Font = OsuFont.Torus.With(size: 14 * torus_scale_factor, weight: FontWeight.Bold), Mods = { BindTarget = Current }, } @@ -304,7 +301,7 @@ namespace osu.Game.Screens.SelectV2.Footer Y = -5f; Depth = float.MaxValue; Origin = Anchor.BottomLeft; - Shear = bar_shear; + Shear = BUTTON_SHEAR; CornerRadius = CORNER_RADIUS; AutoSizeAxes = Axes.X; Height = bar_height; @@ -328,7 +325,7 @@ namespace osu.Game.Screens.SelectV2.Footer { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Shear = -bar_shear, + Shear = -BUTTON_SHEAR, Text = ModSelectOverlayStrings.Unranked.ToUpper(), Margin = new MarginPadding { Horizontal = 15 }, UseFullGlyphHeight = false, From 9265290acfea0c3d746ede43426ae479708e340b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 16 May 2024 05:30:35 +0300 Subject: [PATCH 3929/4092] Change shear factor everywhere to 0.15x --- osu.Game/Graphics/UserInterface/ShearedButton.cs | 3 ++- osu.Game/Overlays/Mods/ShearedOverlayContainer.cs | 3 ++- osu.Game/Screens/Footer/ScreenFooterButton.cs | 5 ++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ShearedButton.cs b/osu.Game/Graphics/UserInterface/ShearedButton.cs index 0fd21502a1..d546c18cb8 100644 --- a/osu.Game/Graphics/UserInterface/ShearedButton.cs +++ b/osu.Game/Graphics/UserInterface/ShearedButton.cs @@ -11,6 +11,7 @@ using osu.Framework.Localisation; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; +using osu.Game.Overlays.Mods; using osuTK; namespace osu.Game.Graphics.UserInterface @@ -66,7 +67,7 @@ namespace osu.Game.Graphics.UserInterface private readonly Box background; private readonly OsuSpriteText text; - private const float shear = 0.2f; + private const float shear = ShearedOverlayContainer.SHEAR; private Colour4? darkerColour; private Colour4? lighterColour; diff --git a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs index a372ec70db..893ac89aa4 100644 --- a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs +++ b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs @@ -22,7 +22,8 @@ namespace osu.Game.Overlays.Mods { protected const float PADDING = 14; - public const float SHEAR = 0.2f; + // todo: maybe move this to a higher place since it's used for screen footer buttons etc. + public const float SHEAR = 0.15f; [Cached] protected readonly OverlayColourProvider ColourProvider; diff --git a/osu.Game/Screens/Footer/ScreenFooterButton.cs b/osu.Game/Screens/Footer/ScreenFooterButton.cs index 3a23249ae0..e0b019bfa8 100644 --- a/osu.Game/Screens/Footer/ScreenFooterButton.cs +++ b/osu.Game/Screens/Footer/ScreenFooterButton.cs @@ -18,6 +18,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Input.Bindings; using osu.Game.Overlays; +using osu.Game.Overlays.Mods; using osuTK; using osuTK.Graphics; @@ -25,9 +26,7 @@ namespace osu.Game.Screens.Footer { public partial class ScreenFooterButton : OsuClickableContainer, IKeyBindingHandler { - // if we go by design, both this and ShearedButton should have shear factor as 1/7, - // but ShearedButton uses 1/5 so we must follow suit for the design to stay consistent. - private const float shear = 1f / 5f; + private const float shear = ShearedOverlayContainer.SHEAR; protected const int CORNER_RADIUS = 10; protected const int BUTTON_HEIGHT = 90; From 310b4d90cc49fc8f26982bc0656285458d713d6c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 16 May 2024 07:27:54 +0300 Subject: [PATCH 3930/4092] Move `SHEAR` constant to `OsuGame` and revert back to 0.2x (i.e. master) Discussed in [discord](https://discord.com/channels/188630481301012481/188630652340404224/1240490608934653984). --- osu.Game/Graphics/UserInterface/ShearedButton.cs | 2 +- osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs | 4 ++-- osu.Game/OsuGame.cs | 5 +++++ osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs | 2 +- osu.Game/Overlays/Mods/ModColumn.cs | 2 +- osu.Game/Overlays/Mods/ModFooterInformationDisplay.cs | 2 +- osu.Game/Overlays/Mods/ModPanel.cs | 2 +- osu.Game/Overlays/Mods/ModSelectColumn.cs | 6 +++--- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 4 ++-- osu.Game/Overlays/Mods/ModSelectPanel.cs | 8 ++++---- osu.Game/Overlays/Mods/RankingInformationDisplay.cs | 6 +++--- osu.Game/Overlays/Mods/ShearedOverlayContainer.cs | 3 --- osu.Game/Screens/Footer/ScreenFooterButton.cs | 2 +- 13 files changed, 25 insertions(+), 23 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ShearedButton.cs b/osu.Game/Graphics/UserInterface/ShearedButton.cs index d546c18cb8..7eed388707 100644 --- a/osu.Game/Graphics/UserInterface/ShearedButton.cs +++ b/osu.Game/Graphics/UserInterface/ShearedButton.cs @@ -67,7 +67,7 @@ namespace osu.Game.Graphics.UserInterface private readonly Box background; private readonly OsuSpriteText text; - private const float shear = ShearedOverlayContainer.SHEAR; + private const float shear = OsuGame.SHEAR; private Colour4? darkerColour; private Colour4? lighterColour; diff --git a/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs b/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs index c3a9f8a586..5f3716f36c 100644 --- a/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs +++ b/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs @@ -53,7 +53,7 @@ namespace osu.Game.Graphics.UserInterface public ShearedSearchTextBox() { Height = 42; - Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0); + Shear = new Vector2(OsuGame.SHEAR, 0); Masking = true; CornerRadius = corner_radius; @@ -116,7 +116,7 @@ namespace osu.Game.Graphics.UserInterface PlaceholderText = CommonStrings.InputSearch; CornerRadius = corner_radius; - TextContainer.Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0); + TextContainer.Shear = new Vector2(-OsuGame.SHEAR, 0); } protected override SpriteText CreatePlaceholder() => new SearchPlaceholder(); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 7c89314014..af01a1b1ac 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -91,6 +91,11 @@ namespace osu.Game /// protected const float SIDE_OVERLAY_OFFSET_RATIO = 0.05f; + /// + /// A common shear factor applied to most components of the game. + /// + public const float SHEAR = 0.2f; + public Toolbar Toolbar { get; private set; } private ChatOverlay chatOverlay; diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index c58cf710bd..5b10a2844e 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -66,7 +66,7 @@ namespace osu.Game.Overlays.Mods [BackgroundDependencyLoader] private void load() { - const float shear = ShearedOverlayContainer.SHEAR; + const float shear = OsuGame.SHEAR; LeftContent.AddRange(new Drawable[] { diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index e9f21338bd..326394a207 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -106,7 +106,7 @@ namespace osu.Game.Overlays.Mods Origin = Anchor.CentreLeft, Scale = new Vector2(0.8f), RelativeSizeAxes = Axes.X, - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0) + Shear = new Vector2(-OsuGame.SHEAR, 0) }); ItemsFlow.Padding = new MarginPadding { diff --git a/osu.Game/Overlays/Mods/ModFooterInformationDisplay.cs b/osu.Game/Overlays/Mods/ModFooterInformationDisplay.cs index 8668879850..6665a3b8dc 100644 --- a/osu.Game/Overlays/Mods/ModFooterInformationDisplay.cs +++ b/osu.Game/Overlays/Mods/ModFooterInformationDisplay.cs @@ -37,7 +37,7 @@ namespace osu.Game.Overlays.Mods Anchor = Anchor.BottomRight, AutoSizeAxes = Axes.X, Height = ShearedButton.DEFAULT_HEIGHT, - Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0), + Shear = new Vector2(OsuGame.SHEAR, 0), CornerRadius = ShearedButton.CORNER_RADIUS, BorderThickness = ShearedButton.BORDER_THICKNESS, Masking = true, diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs index cf173b0d6a..9f87a704c0 100644 --- a/osu.Game/Overlays/Mods/ModPanel.cs +++ b/osu.Game/Overlays/Mods/ModPanel.cs @@ -36,7 +36,7 @@ namespace osu.Game.Overlays.Mods Anchor = Anchor.Centre, Origin = Anchor.Centre, Active = { BindTarget = Active }, - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), + Shear = new Vector2(-OsuGame.SHEAR, 0), Scale = new Vector2(HEIGHT / ModSwitchSmall.DEFAULT_SIZE) }; } diff --git a/osu.Game/Overlays/Mods/ModSelectColumn.cs b/osu.Game/Overlays/Mods/ModSelectColumn.cs index 61b29ef65b..5ffed24e7a 100644 --- a/osu.Game/Overlays/Mods/ModSelectColumn.cs +++ b/osu.Game/Overlays/Mods/ModSelectColumn.cs @@ -70,7 +70,7 @@ namespace osu.Game.Overlays.Mods { Width = WIDTH; RelativeSizeAxes = Axes.Y; - Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0); + Shear = new Vector2(OsuGame.SHEAR, 0); InternalChildren = new Drawable[] { @@ -96,7 +96,7 @@ namespace osu.Game.Overlays.Mods { RelativeSizeAxes = Axes.X, Height = header_height, - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), + Shear = new Vector2(-OsuGame.SHEAR, 0), Velocity = 0.7f, ClampAxes = Axes.Y }, @@ -111,7 +111,7 @@ namespace osu.Game.Overlays.Mods AutoSizeAxes = Axes.Y, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), + Shear = new Vector2(-OsuGame.SHEAR, 0), Padding = new MarginPadding { Horizontal = 17, diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 25293e8e20..f521b2e38c 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -227,7 +227,7 @@ namespace osu.Game.Overlays.Mods Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, Direction = FillDirection.Horizontal, - Shear = new Vector2(SHEAR, 0), + Shear = new Vector2(OsuGame.SHEAR, 0), RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Margin = new MarginPadding { Horizontal = 70 }, @@ -847,7 +847,7 @@ namespace osu.Game.Overlays.Mods // DrawWidth/DrawPosition do not include shear effects, and we want to know the full extents of the columns post-shear, // so we have to manually compensate. var topLeft = column.ToSpaceOfOtherDrawable(Vector2.Zero, ScrollContent); - var bottomRight = column.ToSpaceOfOtherDrawable(new Vector2(column.DrawWidth - column.DrawHeight * SHEAR, 0), ScrollContent); + var bottomRight = column.ToSpaceOfOtherDrawable(new Vector2(column.DrawWidth - column.DrawHeight * OsuGame.SHEAR, 0), ScrollContent); bool isCurrentlyVisible = Precision.AlmostBigger(topLeft.X, leftVisibleBound) && Precision.DefinitelyBigger(rightVisibleBound, bottomRight.X); diff --git a/osu.Game/Overlays/Mods/ModSelectPanel.cs b/osu.Game/Overlays/Mods/ModSelectPanel.cs index 29f4c93e88..284356f37e 100644 --- a/osu.Game/Overlays/Mods/ModSelectPanel.cs +++ b/osu.Game/Overlays/Mods/ModSelectPanel.cs @@ -87,7 +87,7 @@ namespace osu.Game.Overlays.Mods Content.CornerRadius = CORNER_RADIUS; Content.BorderThickness = 2; - Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0); + Shear = new Vector2(OsuGame.SHEAR, 0); Children = new Drawable[] { @@ -128,10 +128,10 @@ namespace osu.Game.Overlays.Mods { Font = OsuFont.TorusAlternate.With(size: 18, weight: FontWeight.SemiBold), RelativeSizeAxes = Axes.X, - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), + Shear = new Vector2(-OsuGame.SHEAR, 0), Margin = new MarginPadding { - Left = -18 * ShearedOverlayContainer.SHEAR + Left = -18 * OsuGame.SHEAR }, ShowTooltip = false, // Tooltip is handled by `IncompatibilityDisplayingModPanel`. }, @@ -139,7 +139,7 @@ namespace osu.Game.Overlays.Mods { Font = OsuFont.Default.With(size: 12), RelativeSizeAxes = Axes.X, - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), + Shear = new Vector2(-OsuGame.SHEAR, 0), ShowTooltip = false, // Tooltip is handled by `IncompatibilityDisplayingModPanel`. } } diff --git a/osu.Game/Overlays/Mods/RankingInformationDisplay.cs b/osu.Game/Overlays/Mods/RankingInformationDisplay.cs index 494f8a377f..75a8f289d8 100644 --- a/osu.Game/Overlays/Mods/RankingInformationDisplay.cs +++ b/osu.Game/Overlays/Mods/RankingInformationDisplay.cs @@ -52,7 +52,7 @@ namespace osu.Game.Overlays.Mods Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, RelativeSizeAxes = Axes.Both, - Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0), + Shear = new Vector2(OsuGame.SHEAR, 0), CornerRadius = ShearedButton.CORNER_RADIUS, Masking = true, Children = new Drawable[] @@ -79,7 +79,7 @@ namespace osu.Game.Overlays.Mods { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), + Shear = new Vector2(-OsuGame.SHEAR, 0), Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold) } } @@ -94,7 +94,7 @@ namespace osu.Game.Overlays.Mods Origin = Anchor.Centre, Child = counter = new EffectCounter { - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), + Shear = new Vector2(-OsuGame.SHEAR, 0), Anchor = Anchor.Centre, Origin = Anchor.Centre, Current = { BindTarget = ModMultiplier } diff --git a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs index 893ac89aa4..acdd1db728 100644 --- a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs +++ b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs @@ -22,9 +22,6 @@ namespace osu.Game.Overlays.Mods { protected const float PADDING = 14; - // todo: maybe move this to a higher place since it's used for screen footer buttons etc. - public const float SHEAR = 0.15f; - [Cached] protected readonly OverlayColourProvider ColourProvider; diff --git a/osu.Game/Screens/Footer/ScreenFooterButton.cs b/osu.Game/Screens/Footer/ScreenFooterButton.cs index e0b019bfa8..40e79ed474 100644 --- a/osu.Game/Screens/Footer/ScreenFooterButton.cs +++ b/osu.Game/Screens/Footer/ScreenFooterButton.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.Footer { public partial class ScreenFooterButton : OsuClickableContainer, IKeyBindingHandler { - private const float shear = ShearedOverlayContainer.SHEAR; + private const float shear = OsuGame.SHEAR; protected const int CORNER_RADIUS = 10; protected const int BUTTON_HEIGHT = 90; From 820cfbcb005f759d1e0f1858781d762028ff08e2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 16 May 2024 07:47:30 +0300 Subject: [PATCH 3931/4092] Remove unused using directives --- osu.Game/Graphics/UserInterface/ShearedButton.cs | 1 - osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs | 1 - osu.Game/Screens/Footer/ScreenFooterButton.cs | 1 - 3 files changed, 3 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ShearedButton.cs b/osu.Game/Graphics/UserInterface/ShearedButton.cs index 7eed388707..87d269ccd4 100644 --- a/osu.Game/Graphics/UserInterface/ShearedButton.cs +++ b/osu.Game/Graphics/UserInterface/ShearedButton.cs @@ -11,7 +11,6 @@ using osu.Framework.Localisation; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; -using osu.Game.Overlays.Mods; using osuTK; namespace osu.Game.Graphics.UserInterface diff --git a/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs b/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs index 5f3716f36c..c6565726b5 100644 --- a/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs +++ b/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; -using osu.Game.Overlays.Mods; using osu.Game.Resources.Localisation.Web; using osuTK; diff --git a/osu.Game/Screens/Footer/ScreenFooterButton.cs b/osu.Game/Screens/Footer/ScreenFooterButton.cs index 40e79ed474..8fbd9f6cba 100644 --- a/osu.Game/Screens/Footer/ScreenFooterButton.cs +++ b/osu.Game/Screens/Footer/ScreenFooterButton.cs @@ -18,7 +18,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Input.Bindings; using osu.Game.Overlays; -using osu.Game.Overlays.Mods; using osuTK; using osuTK.Graphics; From 97de73b99c47b2c0787ece0efa8431272df49c8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 16 May 2024 08:21:52 +0200 Subject: [PATCH 3932/4092] Do not change mania column width on mobile platforms - Closes https://github.com/ppy/osu/issues/25852 - Reverts https://github.com/ppy/osu/pull/25336 / https://github.com/ppy/osu/pull/25777 With the columns not being directly touchable anymore after https://github.com/ppy/osu/pull/28173 I see very little point to this continuing to exist. --- osu.Game.Rulesets.Mania/UI/ColumnFlow.cs | 36 ------------------------ 1 file changed, 36 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs b/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs index 1593e8e76f..f444448797 100644 --- a/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs +++ b/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs @@ -3,15 +3,12 @@ #nullable disable -using System; -using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Skinning; using osu.Game.Skinning; -using osuTK; namespace osu.Game.Rulesets.Mania.UI { @@ -62,12 +59,6 @@ namespace osu.Game.Rulesets.Mania.UI onSkinChanged(); } - protected override void LoadComplete() - { - base.LoadComplete(); - updateMobileSizing(); - } - private void onSkinChanged() { for (int i = 0; i < stageDefinition.Columns; i++) @@ -92,8 +83,6 @@ namespace osu.Game.Rulesets.Mania.UI columns[i].Width = width.Value; } - - updateMobileSizing(); } /// @@ -106,31 +95,6 @@ namespace osu.Game.Rulesets.Mania.UI Content[column] = columns[column].Child = content; } - private void updateMobileSizing() - { - if (!IsLoaded || !RuntimeInfo.IsMobile) - return; - - // GridContainer+CellContainer containing this stage (gets split up for dual stages). - Vector2? containingCell = this.FindClosestParent()?.Parent?.DrawSize; - - // Will be null in tests. - if (containingCell == null) - return; - - float aspectRatio = containingCell.Value.X / containingCell.Value.Y; - - // 2.83 is a mostly arbitrary scale-up (170 / 60, based on original implementation for argon) - float mobileAdjust = 2.83f * Math.Min(1, 7f / stageDefinition.Columns); - // 1.92 is a "reference" mobile screen aspect ratio for phones. - // We should scale it back for cases like tablets which aren't so extreme. - mobileAdjust *= aspectRatio / 1.92f; - - // Best effort until we have better mobile support. - for (int i = 0; i < stageDefinition.Columns; i++) - columns[i].Width *= mobileAdjust; - } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From 5dd64a7c86bae98bc991386ba92faff97d6a205d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 16 May 2024 14:49:56 +0200 Subject: [PATCH 3933/4092] Fix duplicated localisation key in `DeleteConfiormationContentStrings` Noticed via `osu-resources` build warnings. There are also a few other warnings about https://github.com/ppy/osu/pull/27472. Seems something in crowdin innards may still be exporting those strings even though they have been fixed already. Not sure how to address that. Probably need these to be detected via static analysis at this point since it's happened again. Might look into the feasibility of making that happen. --- osu.Game/Localisation/DeleteConfirmationContentStrings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Localisation/DeleteConfirmationContentStrings.cs b/osu.Game/Localisation/DeleteConfirmationContentStrings.cs index 563fbf5654..d781fadbce 100644 --- a/osu.Game/Localisation/DeleteConfirmationContentStrings.cs +++ b/osu.Game/Localisation/DeleteConfirmationContentStrings.cs @@ -32,7 +32,7 @@ namespace osu.Game.Localisation /// /// "Are you sure you want to delete all scores? This cannot be undone!" /// - public static LocalisableString Scores => new TranslatableString(getKey(@"collections"), @"Are you sure you want to delete all scores? This cannot be undone!"); + public static LocalisableString Scores => new TranslatableString(getKey(@"scores"), @"Are you sure you want to delete all scores? This cannot be undone!"); /// /// "Are you sure you want to delete all mod presets?" From f2f03f08cb0dd79de4587a2ec63da97f3cb375da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 16 May 2024 17:36:19 +0200 Subject: [PATCH 3934/4092] Fix xmldoc mismatches in localisation files This is enforced by the localisation analyser after https://github.com/ppy/osu-localisation-analyser/pull/62, but it appears the analyser was never actually bumped game-side after that change and I'm not super sure why, as there does not appear to be a reason to _not_ do that. So this commit does it. --- .../FirstRunOverlayImportFromStableScreenStrings.cs | 2 +- osu.Game/Localisation/NotificationsStrings.cs | 2 +- osu.Game/osu.Game.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs b/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs index 04fecab3df..521a77fe20 100644 --- a/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs +++ b/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs @@ -47,7 +47,7 @@ namespace osu.Game.Localisation public static LocalisableString Calculating => new TranslatableString(getKey(@"calculating"), @"calculating..."); /// - /// "{0} items" + /// "{0} item(s)" /// public static LocalisableString Items(int arg0) => new TranslatableString(getKey(@"items"), @"{0} item(s)", arg0); diff --git a/osu.Game/Localisation/NotificationsStrings.cs b/osu.Game/Localisation/NotificationsStrings.cs index 3188ca5533..698fe230b2 100644 --- a/osu.Game/Localisation/NotificationsStrings.cs +++ b/osu.Game/Localisation/NotificationsStrings.cs @@ -114,7 +114,7 @@ Please try changing your audio device to a working setting."); public static LocalisableString MismatchingBeatmapForReplay => new TranslatableString(getKey(@"mismatching_beatmap_for_replay"), @"Your local copy of the beatmap for this replay appears to be different than expected. You may need to update or re-download it."); /// - /// "You are now running osu! {version}. + /// "You are now running osu! {0}. /// Click to see what's new!" /// public static LocalisableString GameVersionAfterUpdate(string version) => new TranslatableString(getKey(@"game_version_after_update"), @"You are now running osu! {0}. diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 5452a648d1..7588b2b3c8 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -30,7 +30,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From a3dfd99f7d5895fb0bf5f0c8398245a42b6d2848 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 16 May 2024 18:23:19 +0200 Subject: [PATCH 3935/4092] Fix discord arbitrarily refusing to work on "too short" strings Closes https://github.com/ppy/osu/issues/28192. --- osu.Desktop/DiscordRichPresence.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 74ebd38f2c..3e0a9099cb 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -164,8 +164,8 @@ namespace osu.Desktop // user activity if (activity.Value != null) { - presence.State = truncate(activity.Value.GetStatus(hideIdentifiableInformation)); - presence.Details = truncate(activity.Value.GetDetails(hideIdentifiableInformation) ?? string.Empty); + presence.State = clampLength(activity.Value.GetStatus(hideIdentifiableInformation)); + presence.Details = clampLength(activity.Value.GetDetails(hideIdentifiableInformation) ?? string.Empty); if (getBeatmapID(activity.Value) is int beatmapId && beatmapId > 0) { @@ -271,8 +271,15 @@ namespace osu.Desktop private static readonly int ellipsis_length = Encoding.UTF8.GetByteCount(new[] { '…' }); - private static string truncate(string str) + private static string clampLength(string str) { + // For whatever reason, discord decides that strings shorter than 2 characters cannot possibly be valid input, because... reasons? + // And yes, that is two *characters*, or *codepoints*, not *bytes* as further down below (as determined by empirical testing). + // That seems very questionable, and isn't even documented anywhere. So to *make it* accept such valid input, + // just tack on enough of U+200B ZERO WIDTH SPACEs at the end. + if (str.Length < 2) + return str.PadRight(2, '\u200B'); + if (Encoding.UTF8.GetByteCount(str) <= 128) return str; From 2f9d74286dd5401450f071659323415c3329d3b7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 17 May 2024 11:15:17 +0900 Subject: [PATCH 3936/4092] Bump once more --- .config/dotnet-tools.json | 2 +- osu.Game/osu.Game.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 99906f0895..ace7db82f8 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -21,7 +21,7 @@ ] }, "ppy.localisationanalyser.tools": { - "version": "2023.1117.0", + "version": "2024.517.0", "commands": [ "localisation" ] diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 7588b2b3c8..f91995feff 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -30,7 +30,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 61a415fed2ae74c50223015d957bcc55b8a11c5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 17 May 2024 10:32:39 +0200 Subject: [PATCH 3937/4092] Add client/server models & operations for "daily challenge" feature --- osu.Game/Online/Metadata/DailyChallengeInfo.cs | 16 ++++++++++++++++ osu.Game/Online/Metadata/IMetadataClient.cs | 6 ++++++ osu.Game/Online/Metadata/IMetadataServer.cs | 7 ++++++- osu.Game/Online/Metadata/MetadataClient.cs | 9 +++++++++ osu.Game/Online/Metadata/OnlineMetadataClient.cs | 11 +++++++++++ osu.Game/Online/Rooms/RoomCategory.cs | 3 +++ .../Tests/Visual/Metadata/TestMetadataClient.cs | 9 +++++++++ 7 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Online/Metadata/DailyChallengeInfo.cs diff --git a/osu.Game/Online/Metadata/DailyChallengeInfo.cs b/osu.Game/Online/Metadata/DailyChallengeInfo.cs new file mode 100644 index 0000000000..7c49556653 --- /dev/null +++ b/osu.Game/Online/Metadata/DailyChallengeInfo.cs @@ -0,0 +1,16 @@ +// 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 MessagePack; + +namespace osu.Game.Online.Metadata +{ + [MessagePackObject] + [Serializable] + public struct DailyChallengeInfo + { + [Key(0)] + public long RoomID { get; set; } + } +} diff --git a/osu.Game/Online/Metadata/IMetadataClient.cs b/osu.Game/Online/Metadata/IMetadataClient.cs index 7102554ae9..ee7a726bfc 100644 --- a/osu.Game/Online/Metadata/IMetadataClient.cs +++ b/osu.Game/Online/Metadata/IMetadataClient.cs @@ -20,5 +20,11 @@ namespace osu.Game.Online.Metadata /// Delivers an update of the of the user with the supplied . /// Task UserPresenceUpdated(int userId, UserPresence? status); + + /// + /// Delivers an update of the current "daily challenge" status. + /// Null value means there is no "daily challenge" currently active. + /// + Task DailyChallengeUpdated(DailyChallengeInfo? info); } } diff --git a/osu.Game/Online/Metadata/IMetadataServer.cs b/osu.Game/Online/Metadata/IMetadataServer.cs index 9780045333..8bf3f8f56b 100644 --- a/osu.Game/Online/Metadata/IMetadataServer.cs +++ b/osu.Game/Online/Metadata/IMetadataServer.cs @@ -7,7 +7,12 @@ using osu.Game.Users; namespace osu.Game.Online.Metadata { /// - /// Metadata server is responsible for keeping the osu! client up-to-date with any changes. + /// Metadata server is responsible for keeping the osu! client up-to-date with various real-time happenings, such as: + /// + /// beatmap updates via BSS, + /// online user activity/status updates, + /// other real-time happenings, such as current "daily challenge" status. + /// /// public interface IMetadataServer { diff --git a/osu.Game/Online/Metadata/MetadataClient.cs b/osu.Game/Online/Metadata/MetadataClient.cs index 8e99a9b2cb..b619970494 100644 --- a/osu.Game/Online/Metadata/MetadataClient.cs +++ b/osu.Game/Online/Metadata/MetadataClient.cs @@ -59,6 +59,15 @@ namespace osu.Game.Online.Metadata #endregion + #region Daily Challenge + + public abstract IBindable DailyChallengeInfo { get; } + + /// + public abstract Task DailyChallengeUpdated(DailyChallengeInfo? info); + + #endregion + #region Disconnection handling public event Action? Disconnecting; diff --git a/osu.Game/Online/Metadata/OnlineMetadataClient.cs b/osu.Game/Online/Metadata/OnlineMetadataClient.cs index 3805d12688..b94f26a71d 100644 --- a/osu.Game/Online/Metadata/OnlineMetadataClient.cs +++ b/osu.Game/Online/Metadata/OnlineMetadataClient.cs @@ -26,6 +26,9 @@ namespace osu.Game.Online.Metadata public override IBindableDictionary UserStates => userStates; private readonly BindableDictionary userStates = new BindableDictionary(); + public override IBindable DailyChallengeInfo => dailyChallengeInfo; + private readonly Bindable dailyChallengeInfo = new Bindable(); + private readonly string endpoint; private IHubClientConnector? connector; @@ -58,6 +61,7 @@ namespace osu.Game.Online.Metadata // https://github.com/dotnet/aspnetcore/issues/15198 connection.On(nameof(IMetadataClient.BeatmapSetsUpdated), ((IMetadataClient)this).BeatmapSetsUpdated); connection.On(nameof(IMetadataClient.UserPresenceUpdated), ((IMetadataClient)this).UserPresenceUpdated); + connection.On(nameof(IMetadataClient.DailyChallengeUpdated), ((IMetadataClient)this).DailyChallengeUpdated); connection.On(nameof(IStatefulUserHubClient.DisconnectRequested), ((IMetadataClient)this).DisconnectRequested); }; @@ -101,6 +105,7 @@ namespace osu.Game.Online.Metadata { isWatchingUserPresence.Value = false; userStates.Clear(); + dailyChallengeInfo.Value = null; }); return; } @@ -229,6 +234,12 @@ namespace osu.Game.Online.Metadata } } + public override Task DailyChallengeUpdated(DailyChallengeInfo? info) + { + Schedule(() => dailyChallengeInfo.Value = info); + return Task.CompletedTask; + } + public override async Task DisconnectRequested() { await base.DisconnectRequested().ConfigureAwait(false); diff --git a/osu.Game/Online/Rooms/RoomCategory.cs b/osu.Game/Online/Rooms/RoomCategory.cs index 17afb0dc7f..4534e7de59 100644 --- a/osu.Game/Online/Rooms/RoomCategory.cs +++ b/osu.Game/Online/Rooms/RoomCategory.cs @@ -13,5 +13,8 @@ namespace osu.Game.Online.Rooms [Description("Featured Artist")] FeaturedArtist, + + [Description("Daily Challenge")] + DailyChallenge, } } diff --git a/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs b/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs index 16cbf879df..b589e66d8b 100644 --- a/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs +++ b/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs @@ -21,6 +21,9 @@ namespace osu.Game.Tests.Visual.Metadata public override IBindableDictionary UserStates => userStates; private readonly BindableDictionary userStates = new BindableDictionary(); + public override IBindable DailyChallengeInfo => dailyChallengeInfo; + private readonly Bindable dailyChallengeInfo = new Bindable(); + [Resolved] private IAPIProvider api { get; set; } = null!; @@ -77,5 +80,11 @@ namespace osu.Game.Tests.Visual.Metadata => Task.FromResult(new BeatmapUpdates(Array.Empty(), queueId)); public override Task BeatmapSetsUpdated(BeatmapUpdates updates) => Task.CompletedTask; + + public override Task DailyChallengeUpdated(DailyChallengeInfo? info) + { + dailyChallengeInfo.Value = info; + return Task.CompletedTask; + } } } From a4f8ed2a0ef48d8acf77877606b9d733e4170e58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 17 May 2024 10:39:24 +0200 Subject: [PATCH 3938/4092] Add button to access daily challenge playlist from main menu --- .../UserInterface/TestSceneMainMenuButton.cs | 83 +++++++ .../UpdateableOnlineBeatmapSetCover.cs | 6 +- osu.Game/Graphics/OsuIcon.cs | 4 + osu.Game/Localisation/ButtonSystemStrings.cs | 7 +- osu.Game/Screens/Menu/ButtonSystem.cs | 57 +++-- osu.Game/Screens/Menu/DailyChallengeButton.cs | 209 ++++++++++++++++++ osu.Game/Screens/Menu/MainMenu.cs | 6 + osu.Game/Screens/Menu/MainMenuButton.cs | 183 +++++++++------ .../Screens/OnlinePlay/Playlists/Playlists.cs | 3 + 9 files changed, 476 insertions(+), 82 deletions(-) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs create mode 100644 osu.Game/Screens/Menu/DailyChallengeButton.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs new file mode 100644 index 0000000000..921e28d607 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs @@ -0,0 +1,83 @@ +// 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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Localisation; +using osu.Game.Online.API; +using osu.Game.Online.Metadata; +using osu.Game.Online.Rooms; +using osu.Game.Screens.Menu; +using osuTK.Input; +using Color4 = osuTK.Graphics.Color4; + +namespace osu.Game.Tests.Visual.UserInterface +{ + [TestFixture] + public partial class TestSceneMainMenuButton : OsuTestScene + { + [Resolved] + private MetadataClient metadataClient { get; set; } = null!; + + private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; + + [Test] + public void TestStandardButton() + { + AddStep("add button", () => Child = new MainMenuButton( + ButtonSystemStrings.Solo, @"button-default-select", OsuIcon.Player, new Color4(102, 68, 204, 255), _ => { }, 0, Key.P) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + ButtonSystemState = ButtonSystemState.TopLevel, + }); + } + + [Test] + public void TestDailyChallengeButton() + { + AddStep("add button", () => Child = new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), _ => { }, 0, Key.D) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + ButtonSystemState = ButtonSystemState.TopLevel, + }); + + AddStep("set up API", () => dummyAPI.HandleRequest = req => + { + switch (req) + { + case GetRoomRequest getRoomRequest: + if (getRoomRequest.RoomId != 1234) + return false; + + var beatmap = CreateAPIBeatmap(); + beatmap.OnlineID = 1001; + getRoomRequest.TriggerSuccess(new Room + { + RoomID = { Value = 1234 }, + Playlist = + { + new PlaylistItem(beatmap) + }, + EndDate = { Value = DateTimeOffset.Now.AddSeconds(30) } + }); + return true; + + default: + return false; + } + }); + + AddStep("beatmap of the day active", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo + { + RoomID = 1234, + })); + + AddStep("beatmap of the day not active", () => metadataClient.DailyChallengeUpdated(null)); + } + } +} diff --git a/osu.Game/Beatmaps/Drawables/UpdateableOnlineBeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/UpdateableOnlineBeatmapSetCover.cs index 93b0dd5c15..2a6b6f90e3 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableOnlineBeatmapSetCover.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableOnlineBeatmapSetCover.cs @@ -43,7 +43,11 @@ namespace osu.Game.Beatmaps.Drawables protected override double TransformDuration => 400; protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Func createContentFunc, double timeBeforeLoad) - => new DelayedLoadUnloadWrapper(createContentFunc, timeBeforeLoad); + => new DelayedLoadUnloadWrapper(createContentFunc, timeBeforeLoad) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; protected override Drawable CreateDrawable(IBeatmapSetOnlineInfo model) { diff --git a/osu.Game/Graphics/OsuIcon.cs b/osu.Game/Graphics/OsuIcon.cs index 32e780f11c..9879ef5d14 100644 --- a/osu.Game/Graphics/OsuIcon.cs +++ b/osu.Game/Graphics/OsuIcon.cs @@ -120,6 +120,7 @@ namespace osu.Game.Graphics public static IconUsage Cross => get(OsuIconMapping.Cross); public static IconUsage CrossCircle => get(OsuIconMapping.CrossCircle); public static IconUsage Crown => get(OsuIconMapping.Crown); + public static IconUsage DailyChallenge => get(OsuIconMapping.DailyChallenge); public static IconUsage Debug => get(OsuIconMapping.Debug); public static IconUsage Delete => get(OsuIconMapping.Delete); public static IconUsage Details => get(OsuIconMapping.Details); @@ -218,6 +219,9 @@ namespace osu.Game.Graphics [Description(@"crown")] Crown, + [Description(@"daily-challenge")] + DailyChallenge, + [Description(@"debug")] Debug, diff --git a/osu.Game/Localisation/ButtonSystemStrings.cs b/osu.Game/Localisation/ButtonSystemStrings.cs index ba4abf63a6..b0a205eebe 100644 --- a/osu.Game/Localisation/ButtonSystemStrings.cs +++ b/osu.Game/Localisation/ButtonSystemStrings.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// 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.Localisation; @@ -54,6 +54,11 @@ namespace osu.Game.Localisation /// public static LocalisableString Exit => new TranslatableString(getKey(@"exit"), @"exit"); + /// + /// "daily challenge" + /// + public static LocalisableString DailyChallenge => new TranslatableString(getKey(@"daily_challenge"), @"daily challenge"); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 15a2740160..33d2a8d348 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -24,6 +24,7 @@ using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.Localisation; using osu.Game.Online.API; +using osu.Game.Online.Rooms; using osu.Game.Overlays; using osuTK; using osuTK.Graphics; @@ -46,6 +47,7 @@ namespace osu.Game.Screens.Menu public Action? OnSettings; public Action? OnMultiplayer; public Action? OnPlaylists; + public Action? OnDailyChallenge; private readonly IBindable isIdle = new BindableBool(); @@ -102,10 +104,13 @@ namespace osu.Game.Screens.Menu buttonArea.AddRange(new Drawable[] { - new MainMenuButton(ButtonSystemStrings.Settings, string.Empty, OsuIcon.Settings, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O, Key.S), - backButton = new MainMenuButton(ButtonSystemStrings.Back, @"back-to-top", OsuIcon.PrevCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, - -WEDGE_WIDTH) + new MainMenuButton(ButtonSystemStrings.Settings, string.Empty, OsuIcon.Settings, new Color4(85, 85, 85, 255), _ => OnSettings?.Invoke(), Key.O, Key.S) { + Padding = new MarginPadding { Right = WEDGE_WIDTH }, + }, + backButton = new MainMenuButton(ButtonSystemStrings.Back, @"back-to-top", OsuIcon.PrevCircle, new Color4(51, 58, 94, 255), _ => State = ButtonSystemState.TopLevel) + { + Padding = new MarginPadding { Right = WEDGE_WIDTH }, VisibleStateMin = ButtonSystemState.Play, VisibleStateMax = ButtonSystemState.Edit, }, @@ -127,21 +132,31 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader] private void load(AudioManager audio, IdleTracker? idleTracker, GameHost host) { - buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Solo, @"button-default-select", OsuIcon.Player, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P)); - buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Multi, @"button-default-select", OsuIcon.Online, new Color4(94, 63, 186, 255), onMultiplayer, 0, Key.M)); - buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Playlists, @"button-default-select", OsuIcon.Tournament, new Color4(94, 63, 186, 255), onPlaylists, 0, Key.L)); + buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Solo, @"button-default-select", OsuIcon.Player, new Color4(102, 68, 204, 255), _ => OnSolo?.Invoke(), Key.P) + { + Padding = new MarginPadding { Left = WEDGE_WIDTH }, + }); + buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Multi, @"button-default-select", OsuIcon.Online, new Color4(94, 63, 186, 255), onMultiplayer, Key.M)); + buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Playlists, @"button-default-select", OsuIcon.Tournament, new Color4(94, 63, 186, 255), onPlaylists, Key.L)); + buttonsPlay.Add(new DailyChallengeButton(@"button-default-select", new Color4(94, 63, 186, 255), onDailyChallenge, Key.D)); buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play); - buttonsEdit.Add(new MainMenuButton(EditorStrings.BeatmapEditor.ToLower(), @"button-default-select", OsuIcon.Beatmap, new Color4(238, 170, 0, 255), () => OnEditBeatmap?.Invoke(), WEDGE_WIDTH, Key.B, Key.E)); - buttonsEdit.Add(new MainMenuButton(SkinEditorStrings.SkinEditor.ToLower(), @"button-default-select", OsuIcon.SkinB, new Color4(220, 160, 0, 255), () => OnEditSkin?.Invoke(), 0, Key.S)); + buttonsEdit.Add(new MainMenuButton(EditorStrings.BeatmapEditor.ToLower(), @"button-default-select", OsuIcon.Beatmap, new Color4(238, 170, 0, 255), _ => OnEditBeatmap?.Invoke(), Key.B, Key.E) + { + Padding = new MarginPadding { Left = WEDGE_WIDTH}, + }); + buttonsEdit.Add(new MainMenuButton(SkinEditorStrings.SkinEditor.ToLower(), @"button-default-select", OsuIcon.SkinB, new Color4(220, 160, 0, 255), _ => OnEditSkin?.Invoke(), Key.S)); buttonsEdit.ForEach(b => b.VisibleState = ButtonSystemState.Edit); - buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Play, @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P, Key.M, Key.L)); - buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Edit, @"button-play-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => State = ButtonSystemState.Edit, 0, Key.E)); - buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Browse, @"button-default-select", OsuIcon.Beatmap, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.B, Key.D)); + buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Play, @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), _ => State = ButtonSystemState.Play, Key.P, Key.M, Key.L) + { + Padding = new MarginPadding { Left = WEDGE_WIDTH }, + }); + buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Edit, @"button-play-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), _ => State = ButtonSystemState.Edit, Key.E)); + buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Browse, @"button-default-select", OsuIcon.Beatmap, new Color4(165, 204, 0, 255), _ => OnBeatmapListing?.Invoke(), Key.B, Key.D)); if (host.CanExit) - buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Exit, string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), () => OnExit?.Invoke(), 0, Key.Q)); + buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Exit, string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), _ => OnExit?.Invoke(), Key.Q)); buttonArea.AddRange(buttonsPlay); buttonArea.AddRange(buttonsEdit); @@ -164,7 +179,7 @@ namespace osu.Game.Screens.Menu sampleLogoSwoosh = audio.Samples.Get(@"Menu/osu-logo-swoosh"); } - private void onMultiplayer() + private void onMultiplayer(MainMenuButton _) { if (api.State.Value != APIState.Online) { @@ -175,7 +190,7 @@ namespace osu.Game.Screens.Menu OnMultiplayer?.Invoke(); } - private void onPlaylists() + private void onPlaylists(MainMenuButton _) { if (api.State.Value != APIState.Online) { @@ -186,6 +201,20 @@ namespace osu.Game.Screens.Menu OnPlaylists?.Invoke(); } + private void onDailyChallenge(MainMenuButton button) + { + if (api.State.Value != APIState.Online) + { + loginOverlay?.Show(); + return; + } + + var dailyChallengeButton = (DailyChallengeButton)button; + + if (dailyChallengeButton.Room != null) + OnDailyChallenge?.Invoke(dailyChallengeButton.Room); + } + private void updateIdleState(bool isIdle) { if (!ReturnToTopOnIdle) diff --git a/osu.Game/Screens/Menu/DailyChallengeButton.cs b/osu.Game/Screens/Menu/DailyChallengeButton.cs new file mode 100644 index 0000000000..907fd04148 --- /dev/null +++ b/osu.Game/Screens/Menu/DailyChallengeButton.cs @@ -0,0 +1,209 @@ +// 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.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Threading; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Beatmaps.Drawables.Cards; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Localisation; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Metadata; +using osu.Game.Online.Rooms; +using osu.Game.Overlays; +using osuTK; +using osuTK.Graphics; +using osuTK.Input; + +namespace osu.Game.Screens.Menu +{ + public partial class DailyChallengeButton : MainMenuButton, IHasCustomTooltip + { + public Room? Room { get; private set; } + + private readonly OsuSpriteText countdown; + private ScheduledDelegate? scheduledCountdownUpdate; + + private UpdateableOnlineBeatmapSetCover cover = null!; + private IBindable info = null!; + private BufferedContainer background = null!; + + [Resolved] + private IAPIProvider api { get; set; } = null!; + + public DailyChallengeButton(string sampleName, Color4 colour, Action? clickAction = null, params Key[] triggerKeys) + : base(ButtonSystemStrings.DailyChallenge, sampleName, OsuIcon.DailyChallenge, colour, clickAction, triggerKeys) + { + BaseSize = new Vector2(ButtonSystem.BUTTON_WIDTH * 1.3f, ButtonArea.BUTTON_AREA_HEIGHT); + + Content.Add(countdown = new OsuSpriteText + { + Shadow = true, + AllowMultiline = false, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Margin = new MarginPadding + { + Left = -3, + Bottom = 22, + }, + Font = OsuFont.Default.With(size: 12), + Alpha = 0, + }); + } + + protected override Drawable CreateBackground(Colour4 accentColour) => background = new BufferedContainer + { + Children = new Drawable[] + { + cover = new UpdateableOnlineBeatmapSetCover + { + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.X, + X = -0.5f, + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(accentColour.Opacity(0), accentColour), + Blending = BlendingParameters.Additive, + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = accentColour.Opacity(0.7f) + }, + }, + }; + + [BackgroundDependencyLoader] + private void load(MetadataClient metadataClient) + { + info = metadataClient.DailyChallengeInfo.GetBoundCopy(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + info.BindValueChanged(updateDisplay, true); + FinishTransforms(true); + + cover.MoveToX(-0.5f, 10000, Easing.InOutSine) + .Then().MoveToX(0.5f, 10000, Easing.InOutSine) + .Loop(); + } + + protected override void Update() + { + base.Update(); + + cover.Width = 2 * background.DrawWidth; + } + + private void updateDisplay(ValueChangedEvent info) + { + UpdateState(); + + scheduledCountdownUpdate?.Cancel(); + scheduledCountdownUpdate = null; + + if (info.NewValue == null) + { + Room = null; + cover.OnlineInfo = TooltipContent = null; + } + else + { + var roomRequest = new GetRoomRequest(info.NewValue.Value.RoomID); + + roomRequest.Success += room => + { + Room = room; + cover.OnlineInfo = TooltipContent = room.Playlist.FirstOrDefault()?.Beatmap.BeatmapSet as APIBeatmapSet; + + updateCountdown(); + Scheduler.AddDelayed(updateCountdown, 1000, true); + }; + api.Queue(roomRequest); + } + } + + private void updateCountdown() + { + if (Room == null) + return; + + var remaining = (Room.EndDate.Value - DateTimeOffset.Now) ?? TimeSpan.Zero; + + if (remaining <= TimeSpan.Zero) + { + countdown.FadeOut(250, Easing.OutQuint); + } + else + { + if (countdown.Alpha == 0) + countdown.FadeIn(250, Easing.OutQuint); + + countdown.Text = remaining.ToString(@"hh\:mm\:ss"); + } + } + + protected override void UpdateState() + { + if (info.IsNotNull() && info.Value == null) + { + ContractStyle = 0; + State = ButtonState.Contracted; + return; + } + + base.UpdateState(); + } + + public ITooltip GetCustomTooltip() => new DailyChallengeTooltip(); + + public APIBeatmapSet? TooltipContent { get; private set; } + + internal partial class DailyChallengeTooltip : CompositeDrawable, ITooltip + { + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); + + private APIBeatmapSet? lastContent; + + [BackgroundDependencyLoader] + private void load() + { + AutoSizeAxes = Axes.Both; + } + + public void Move(Vector2 pos) => Position = pos; + + public void SetContent(APIBeatmapSet? content) + { + if (content == lastContent) + return; + + lastContent = content; + + ClearInternal(); + if (content != null) + AddInternal(new BeatmapCardNano(content)); + } + } + } +} diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 235c5d5c56..722e776e3d 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -147,6 +147,12 @@ namespace osu.Game.Screens.Menu OnSolo = loadSoloSongSelect, OnMultiplayer = () => this.Push(new Multiplayer()), OnPlaylists = () => this.Push(new Playlists()), + OnDailyChallenge = room => + { + Playlists playlistsScreen; + this.Push(playlistsScreen = new Playlists()); + playlistsScreen.Join(room); + }, OnExit = () => { exitConfirmedViaHoldOrClick = true; diff --git a/osu.Game/Screens/Menu/MainMenuButton.cs b/osu.Game/Screens/Menu/MainMenuButton.cs index 1dc79e9b1a..fe8fb91766 100644 --- a/osu.Game/Screens/Menu/MainMenuButton.cs +++ b/osu.Game/Screens/Menu/MainMenuButton.cs @@ -38,11 +38,8 @@ namespace osu.Game.Screens.Menu public readonly Key[] TriggerKeys; - private readonly Container iconText; - private readonly Container box; - private readonly Box boxHoverLayer; - private readonly SpriteIcon icon; - private readonly string sampleName; + protected override Container Content => content; + private readonly Container content; /// /// The menu state for which we are visible for (assuming only one). @@ -59,7 +56,24 @@ namespace osu.Game.Screens.Menu public ButtonSystemState VisibleStateMin = ButtonSystemState.TopLevel; public ButtonSystemState VisibleStateMax = ButtonSystemState.TopLevel; - private readonly Action? clickAction; + public new MarginPadding Padding + { + get => Content.Padding; + set => Content.Padding = value; + } + + protected Vector2 BaseSize { get; init; } = new Vector2(ButtonSystem.BUTTON_WIDTH, ButtonArea.BUTTON_AREA_HEIGHT); + + private readonly Action? clickAction; + + private readonly Container background; + private readonly Drawable backgroundContent; + private readonly Box boxHoverLayer; + private readonly SpriteIcon icon; + + private Vector2 initialSize => BaseSize + Padding.Total; + + private readonly string sampleName; private Sample? sampleClick; private Sample? sampleHover; private SampleChannel? sampleChannel; @@ -68,9 +82,9 @@ namespace osu.Game.Screens.Menu // Allow keyboard interaction based on state rather than waiting for delayed animations. || state == ButtonState.Expanded; - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => background.ReceivePositionalInputAt(screenSpacePos); - public MainMenuButton(LocalisableString text, string sampleName, IconUsage symbol, Color4 colour, Action? clickAction = null, float extraWidth = 0, params Key[] triggerKeys) + public MainMenuButton(LocalisableString text, string sampleName, IconUsage symbol, Color4 colour, Action? clickAction = null, params Key[] triggerKeys) { this.sampleName = sampleName; this.clickAction = clickAction; @@ -79,11 +93,9 @@ namespace osu.Game.Screens.Menu AutoSizeAxes = Axes.Both; Alpha = 0; - Vector2 boxSize = new Vector2(ButtonSystem.BUTTON_WIDTH + Math.Abs(extraWidth), ButtonArea.BUTTON_AREA_HEIGHT); - - Children = new Drawable[] + AddRangeInternal(new Drawable[] { - box = new Container + background = new Container { // box needs to be always present to ensure the button is always sized correctly for flow AlwaysPresent = true, @@ -98,35 +110,46 @@ namespace osu.Game.Screens.Menu }, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Scale = new Vector2(0, 1), - Size = boxSize, - Shear = new Vector2(ButtonSystem.WEDGE_WIDTH / boxSize.Y, 0), Children = new[] { - new Box + backgroundContent = CreateBackground(colour).With(bg => { - EdgeSmoothness = new Vector2(1.5f, 0), - RelativeSizeAxes = Axes.Both, - Colour = colour, - }, + bg.RelativeSizeAxes = Axes.Y; + bg.X = -ButtonSystem.WEDGE_WIDTH; + bg.Anchor = Anchor.Centre; + bg.Origin = Anchor.Centre; + }), boxHoverLayer = new Box { EdgeSmoothness = new Vector2(1.5f, 0), RelativeSizeAxes = Axes.Both, Blending = BlendingParameters.Additive, Colour = Color4.White, + Depth = float.MinValue, Alpha = 0, }, } }, - iconText = new Container + content = new Container { - AutoSizeAxes = Axes.Both, - Position = new Vector2(extraWidth / 2, 0), + RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, Children = new Drawable[] { + new OsuSpriteText + { + Shadow = true, + AllowMultiline = false, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Margin = new MarginPadding + { + Left = -3, + Bottom = 7, + }, + Text = text + }, icon = new SpriteIcon { Shadow = true, @@ -136,20 +159,36 @@ namespace osu.Game.Screens.Menu Position = new Vector2(0, 0), Margin = new MarginPadding { Top = -4 }, Icon = symbol - }, - new OsuSpriteText - { - Shadow = true, - AllowMultiline = false, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Position = new Vector2(0, 35), - Margin = new MarginPadding { Left = -3 }, - Text = text } } } - }; + }); + } + + protected virtual Drawable CreateBackground(Colour4 accentColour) => new Container + { + Child = new Box + { + EdgeSmoothness = new Vector2(1.5f, 0), + RelativeSizeAxes = Axes.Both, + Colour = accentColour, + } + }; + + protected override void LoadComplete() + { + base.LoadComplete(); + + background.Size = initialSize; + background.Shear = new Vector2(ButtonSystem.WEDGE_WIDTH / initialSize.Y, 0); + + // for whatever reason, attempting to size the background "just in time" to cover the visible width + // results in gaps when the width changes are quick (only visible when testing menu at 100% speed, not visible slowed down). + // to ensure there's no missing backdrop, just use a ballpark that should be enough to always cover the width and then some. + // note that while on a code inspections it would seem that `1.5 * initialSize.X` would be enough, elastic usings are used in this button + // (which can exceed the [0;1] range during interpolation). + backgroundContent.Width = 2 * initialSize.X; + backgroundContent.Shear = -background.Shear; } private bool rightward; @@ -179,15 +218,15 @@ namespace osu.Game.Screens.Menu { if (State != ButtonState.Expanded) return true; - sampleHover?.Play(); - - box.ScaleTo(new Vector2(1.5f, 1), 500, Easing.OutElastic); - double duration = TimeUntilNextBeat; icon.ClearTransforms(); icon.RotateTo(rightward ? -BOUNCE_ROTATION : BOUNCE_ROTATION, duration, Easing.InOutSine); icon.ScaleTo(new Vector2(HOVER_SCALE, HOVER_SCALE * BOUNCE_COMPRESSION), duration, Easing.Out); + + sampleHover?.Play(); + background.ResizeTo(Vector2.Multiply(initialSize, new Vector2(1.5f, 1)), 500, Easing.OutElastic); + return true; } @@ -199,7 +238,7 @@ namespace osu.Game.Screens.Menu icon.ScaleTo(Vector2.One, 200, Easing.Out); if (State == ButtonState.Expanded) - box.ScaleTo(new Vector2(1, 1), 500, Easing.OutElastic); + background.ResizeTo(initialSize, 500, Easing.OutElastic); } [BackgroundDependencyLoader] @@ -246,7 +285,7 @@ namespace osu.Game.Screens.Menu sampleChannel = sampleClick?.GetChannel(); sampleChannel?.Play(); - clickAction?.Invoke(); + clickAction?.Invoke(this); boxHoverLayer.ClearTransforms(); boxHoverLayer.Alpha = 0.9f; @@ -254,13 +293,13 @@ namespace osu.Game.Screens.Menu } public override bool HandleNonPositionalInput => state == ButtonState.Expanded; - public override bool HandlePositionalInput => state != ButtonState.Exploded && box.Scale.X >= 0.8f; + public override bool HandlePositionalInput => state != ButtonState.Exploded && background.Width / initialSize.X >= 0.8f; public void StopSamplePlayback() => sampleChannel?.Stop(); protected override void Update() { - iconText.Alpha = Math.Clamp((box.Scale.X - 0.5f) / 0.3f, 0, 1); + content.Alpha = Math.Clamp((background.Width / initialSize.X - 0.5f) / 0.3f, 0, 1); base.Update(); } @@ -285,12 +324,12 @@ namespace osu.Game.Screens.Menu switch (ContractStyle) { default: - box.ScaleTo(new Vector2(0, 1), 500, Easing.OutExpo); + background.ResizeTo(Vector2.Multiply(initialSize, new Vector2(0, 1)), 500, Easing.OutExpo); this.FadeOut(500); break; case 1: - box.ScaleTo(new Vector2(0, 1), 400, Easing.InSine); + background.ResizeTo(Vector2.Multiply(initialSize, new Vector2(0, 1)), 400, Easing.InSine); this.FadeOut(800); break; } @@ -299,13 +338,13 @@ namespace osu.Game.Screens.Menu case ButtonState.Expanded: const int expand_duration = 500; - box.ScaleTo(new Vector2(1, 1), expand_duration, Easing.OutExpo); + background.ResizeTo(initialSize, expand_duration, Easing.OutExpo); this.FadeIn(expand_duration / 6f); break; case ButtonState.Exploded: const int explode_duration = 200; - box.ScaleTo(new Vector2(2, 1), explode_duration, Easing.OutExpo); + background.ResizeTo(Vector2.Multiply(initialSize, new Vector2(2, 1)), explode_duration, Easing.OutExpo); this.FadeOut(explode_duration / 4f * 3); break; } @@ -314,32 +353,44 @@ namespace osu.Game.Screens.Menu } } + private ButtonSystemState buttonSystemState; + public ButtonSystemState ButtonSystemState { + get => buttonSystemState; set { - ContractStyle = 0; + if (buttonSystemState == value) + return; - switch (value) - { - case ButtonSystemState.Initial: + buttonSystemState = value; + UpdateState(); + } + } + + protected virtual void UpdateState() + { + ContractStyle = 0; + + switch (ButtonSystemState) + { + case ButtonSystemState.Initial: + State = ButtonState.Contracted; + break; + + case ButtonSystemState.EnteringMode: + ContractStyle = 1; + State = ButtonState.Contracted; + break; + + default: + if (ButtonSystemState <= VisibleStateMax && ButtonSystemState >= VisibleStateMin) + State = ButtonState.Expanded; + else if (ButtonSystemState < VisibleStateMin) State = ButtonState.Contracted; - break; - - case ButtonSystemState.EnteringMode: - ContractStyle = 1; - State = ButtonState.Contracted; - break; - - default: - if (value <= VisibleStateMax && value >= VisibleStateMin) - State = ButtonState.Expanded; - else if (value < VisibleStateMin) - State = ButtonState.Contracted; - else - State = ButtonState.Exploded; - break; - } + else + State = ButtonState.Exploded; + break; } } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/Playlists.cs b/osu.Game/Screens/OnlinePlay/Playlists/Playlists.cs index f1d2384c2f..9e615ffa98 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/Playlists.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/Playlists.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.Online.Rooms; using osu.Game.Screens.OnlinePlay.Lounge; namespace osu.Game.Screens.OnlinePlay.Playlists @@ -10,5 +11,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists protected override string ScreenTitle => "Playlists"; protected override LoungeSubScreen CreateLounge() => new PlaylistsLoungeSubScreen(); + + public void Join(Room room) => Schedule(() => Lounge.Join(room, string.Empty)); } } From c9b7aaf442c89fcf4f27cfea3171ed4b4a90d8e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 17 May 2024 11:50:23 +0200 Subject: [PATCH 3939/4092] Fix formatting --- osu.Game/Screens/Menu/ButtonSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 33d2a8d348..e9fff9bb07 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -143,7 +143,7 @@ namespace osu.Game.Screens.Menu buttonsEdit.Add(new MainMenuButton(EditorStrings.BeatmapEditor.ToLower(), @"button-default-select", OsuIcon.Beatmap, new Color4(238, 170, 0, 255), _ => OnEditBeatmap?.Invoke(), Key.B, Key.E) { - Padding = new MarginPadding { Left = WEDGE_WIDTH}, + Padding = new MarginPadding { Left = WEDGE_WIDTH }, }); buttonsEdit.Add(new MainMenuButton(SkinEditorStrings.SkinEditor.ToLower(), @"button-default-select", OsuIcon.SkinB, new Color4(220, 160, 0, 255), _ => OnEditSkin?.Invoke(), Key.S)); buttonsEdit.ForEach(b => b.VisibleState = ButtonSystemState.Edit); From a4142937e75ec240b91f930614354dae5c63d9fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 17 May 2024 12:53:25 +0200 Subject: [PATCH 3940/4092] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index f91995feff..821a7f1fab 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + From 2027d481eecdd433f91f23a498ca7c7ee81dfa6c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 18 May 2024 06:38:25 +0300 Subject: [PATCH 3941/4092] Remove `TreatWarningsAsErrors` flags from local builds for developer convenience --- Directory.Build.props | 1 - 1 file changed, 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 2d289d0f22..5ba12b845b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,7 +2,6 @@ 12.0 - true enable From 0af32c5d4b0092f737bb91c3cac3498aebd40df8 Mon Sep 17 00:00:00 2001 From: Aurelian Date: Sat, 18 May 2024 07:45:01 +0200 Subject: [PATCH 3942/4092] Added a minimum value for the scale calculated by the CS value. --- osu.Game/Rulesets/Objects/Legacy/LegacyRulesetExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/LegacyRulesetExtensions.cs b/osu.Game/Rulesets/Objects/Legacy/LegacyRulesetExtensions.cs index 2a5a11161b..1d3416f494 100644 --- a/osu.Game/Rulesets/Objects/Legacy/LegacyRulesetExtensions.cs +++ b/osu.Game/Rulesets/Objects/Legacy/LegacyRulesetExtensions.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Objects.Legacy // It works out to under 1 game pixel and is generally not meaningful to gameplay, but is to replay playback accuracy. const float broken_gamefield_rounding_allowance = 1.00041f; - return (float)(1.0f - 0.7f * IBeatmapDifficultyInfo.DifficultyRange(circleSize)) / 2 * (applyFudge ? broken_gamefield_rounding_allowance : 1); + return (float)Math.Max(0.02, (1.0f - 0.7f * IBeatmapDifficultyInfo.DifficultyRange(circleSize)) / 2 * (applyFudge ? broken_gamefield_rounding_allowance : 1)); } public static int CalculateDifficultyPeppyStars(BeatmapDifficulty difficulty, int objectCount, int drainLength) From a912e56ca995db8560785761209f68dcc93a0e43 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 18 May 2024 11:04:40 +0300 Subject: [PATCH 3943/4092] Fix checkboxes applying extra padding --- osu.Game/Graphics/UserInterface/OsuCheckbox.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs index b7b405a7e8..caab3d97f8 100644 --- a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs @@ -52,8 +52,6 @@ namespace osu.Game.Graphics.UserInterface AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; - const float nub_padding = 5; - Children = new Drawable[] { LabelTextFlowContainer = new OsuTextFlowContainer(ApplyLabelParameters) @@ -69,15 +67,13 @@ namespace osu.Game.Graphics.UserInterface { Nub.Anchor = Anchor.CentreRight; Nub.Origin = Anchor.CentreRight; - Nub.Margin = new MarginPadding { Right = nub_padding }; - LabelTextFlowContainer.Padding = new MarginPadding { Right = Nub.DEFAULT_EXPANDED_SIZE + nub_padding * 2 }; + LabelTextFlowContainer.Padding = new MarginPadding { Right = Nub.DEFAULT_EXPANDED_SIZE + 10f }; } else { Nub.Anchor = Anchor.CentreLeft; Nub.Origin = Anchor.CentreLeft; - Nub.Margin = new MarginPadding { Left = nub_padding }; - LabelTextFlowContainer.Padding = new MarginPadding { Left = Nub.DEFAULT_EXPANDED_SIZE + nub_padding * 2 }; + LabelTextFlowContainer.Padding = new MarginPadding { Left = Nub.DEFAULT_EXPANDED_SIZE + 10f }; } Nub.Current.BindTo(Current); From a12a20e8b503bdb2a5647bebefe05de33b0553be Mon Sep 17 00:00:00 2001 From: Fabian van Oeffelt Date: Sat, 18 May 2024 18:37:44 +0200 Subject: [PATCH 3944/4092] Change Inputkeys to Ctrl+Up/Ctrl+Down --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 5dacb6db4d..b0a1684512 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -182,8 +182,8 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Shift, InputKey.F2 }, GlobalAction.SelectPreviousRandom), new KeyBinding(InputKey.F3, GlobalAction.ToggleBeatmapOptions), new KeyBinding(InputKey.BackSpace, GlobalAction.DeselectAllMods), - new KeyBinding(InputKey.PageUp, GlobalAction.IncreaseSpeed), - new KeyBinding(InputKey.PageDown, GlobalAction.DecreaseSpeed), + new KeyBinding(new[] { InputKey.Control, InputKey.Up }, GlobalAction.IncreaseSpeed), + new KeyBinding(new[] { InputKey.Control, InputKey.Down }, GlobalAction.DecreaseSpeed), }; private static IEnumerable audioControlKeyBindings => new[] From 80064c4b98d955431ac423578b3a9c55b3f4ae7e Mon Sep 17 00:00:00 2001 From: Fabian van Oeffelt Date: Sat, 18 May 2024 18:38:23 +0200 Subject: [PATCH 3945/4092] Speedchange now also works in Modselect --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 25293e8e20..572379ea2c 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -27,6 +27,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Localisation; using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Select; using osu.Game.Utils; using osuTK; using osuTK.Input; @@ -63,6 +64,9 @@ namespace osu.Game.Overlays.Mods private Func isValidMod = _ => true; + [Resolved] + private SongSelect? songSelect { get; set; } + /// /// A function determining whether each mod in the column should be displayed. /// A return value of means that the mod is not filtered and therefore its corresponding panel should be displayed. @@ -752,6 +756,14 @@ namespace osu.Game.Overlays.Mods return true; } + + case GlobalAction.IncreaseSpeed: + songSelect!.ChangeSpeed(0.05); + return true; + + case GlobalAction.DecreaseSpeed: + songSelect!.ChangeSpeed(-0.05); + return true; } return base.OnPressed(e); From 99f30d92c84789670b40ae3989cd52b2dc3a3dc2 Mon Sep 17 00:00:00 2001 From: Fabian van Oeffelt Date: Sat, 18 May 2024 18:38:31 +0200 Subject: [PATCH 3946/4092] Add Unit Tests --- .../SongSelect/TestScenePlaySongSelect.cs | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index e03ffd48f1..938b858110 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -87,6 +87,84 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("delete all beatmaps", () => manager.Delete()); } + [Test] + public void TestSpeedChange() + { + createSongSelect(); + changeMods(); + + AddStep("decreasing speed without mods", () => songSelect?.ChangeSpeed(-0.05)); + AddAssert("halftime at 0.95", () => songSelect!.Mods.Value.Single() is ModHalfTime mod && mod.SpeedChange.Value == 0.95); + + AddStep("decreasing speed with halftime", () => songSelect?.ChangeSpeed(-0.05)); + AddAssert("halftime at 0.9", () => songSelect!.Mods.Value.Single() is ModHalfTime mod && mod.SpeedChange.Value == 0.9); + + AddStep("increasing speed with halftime", () => songSelect?.ChangeSpeed(+0.05)); + AddAssert("halftime at 0.95", () => songSelect!.Mods.Value.Single() is ModHalfTime mod && mod.SpeedChange.Value == 0.95); + + AddStep("increasing speed with halftime to nomod", () => songSelect?.ChangeSpeed(+0.05)); + AddAssert("no mods selected", () => songSelect!.Mods.Value.Count == 0); + + AddStep("increasing speed without mods", () => songSelect?.ChangeSpeed(+0.05)); + AddAssert("doubletime at 1.05", () => songSelect!.Mods.Value.Single() is ModDoubleTime mod && mod.SpeedChange.Value == 1.05); + + AddStep("increasing speed with doubletime", () => songSelect?.ChangeSpeed(+0.05)); + AddAssert("doubletime at 1.1", () => songSelect!.Mods.Value.Single() is ModDoubleTime mod && mod.SpeedChange.Value == 1.1); + + AddStep("decreasing speed with doubletime", () => songSelect?.ChangeSpeed(-0.05)); + AddAssert("doubletime at 1.05", () => songSelect!.Mods.Value.Single() is ModDoubleTime mod && mod.SpeedChange.Value == 1.05); + + OsuModNightcore nc = new OsuModNightcore + { + SpeedChange = { Value = 1.05 } + }; + changeMods(nc); + AddStep("increasing speed with nightcore", () => songSelect?.ChangeSpeed(+0.05)); + AddAssert("nightcore at 1.1", () => songSelect!.Mods.Value.Single() is ModNightcore mod && mod.SpeedChange.Value == 1.1); + + AddStep("decreasing speed with nightcore", () => songSelect?.ChangeSpeed(-0.05)); + AddAssert("doubletime at 1.05", () => songSelect!.Mods.Value.Single() is ModNightcore mod && mod.SpeedChange.Value == 1.05); + + AddStep("decreasing speed with nightcore to nomod", () => songSelect?.ChangeSpeed(-0.05)); + AddAssert("no mods selected", () => songSelect!.Mods.Value.Count == 0); + + AddStep("decreasing speed nomod, nightcore was selected", () => songSelect?.ChangeSpeed(-0.05)); + AddAssert("daycore at 0.95", () => songSelect!.Mods.Value.Single() is ModDaycore mod && mod.SpeedChange.Value == 0.95); + + AddStep("decreasing speed with daycore", () => songSelect?.ChangeSpeed(-0.05)); + AddAssert("daycore at 0.9", () => songSelect!.Mods.Value.Single() is ModDaycore mod && mod.SpeedChange.Value == 0.9); + + AddStep("increasing speed with daycore", () => songSelect?.ChangeSpeed(0.05)); + AddAssert("daycore at 0.95", () => songSelect!.Mods.Value.Single() is ModDaycore mod && mod.SpeedChange.Value == 0.95); + + OsuModDoubleTime dt = new OsuModDoubleTime + { + SpeedChange = { Value = 1.02 }, + AdjustPitch = { Value = true }, + }; + changeMods(dt); + AddStep("decreasing speed from doubletime 1.02 with adjustpitch enabled", () => songSelect?.ChangeSpeed(-0.05)); + AddAssert("halftime at 0.97 with adjustpitch enabled", () => songSelect!.Mods.Value.Single() is ModHalfTime mod && mod.SpeedChange.Value == 0.97 && mod.AdjustPitch.Value); + + OsuModHalfTime ht = new OsuModHalfTime + { + SpeedChange = { Value = 0.97 }, + AdjustPitch = { Value = true }, + }; + Mod[] modlist = { ht, new OsuModHardRock(), new OsuModHidden() }; + changeMods(modlist); + AddStep("decreasing speed from halftime 0.97 with adjustpitch enabled, HDHR enabled", () => songSelect?.ChangeSpeed(0.05)); + AddAssert("doubletime at 1.02 with adjustpitch enabled, HDHR still enabled", () => songSelect!.Mods.Value.Count(mod => (mod is ModDoubleTime modDt && modDt.AdjustPitch.Value && modDt.SpeedChange.Value == 1.02) || mod is ModHardRock || mod is ModHidden) == 3); + + changeMods(new ModWindUp()); + AddStep("windup active, trying to change speed", () => songSelect?.ChangeSpeed(0.05)); + AddAssert("windup still active", () => songSelect!.Mods.Value.First() is ModWindUp); + + changeMods(new ModAdaptiveSpeed()); + AddStep("adaptivespeed active, trying to change speed", () => songSelect?.ChangeSpeed(0.05)); + AddAssert("adaptivespeed still active", () => songSelect!.Mods.Value.First() is ModAdaptiveSpeed); + } + [Test] public void TestPlaceholderBeatmapPresence() { From 3fdbd735ce063653bb92b7570df716e700a9b529 Mon Sep 17 00:00:00 2001 From: Fabian van Oeffelt Date: Sat, 18 May 2024 18:40:51 +0200 Subject: [PATCH 3947/4092] change to single Function, Nightcore now switches into Daycore, keep Adjustpitch after change --- osu.Game/Screens/Select/SongSelect.cs | 182 +++++++++++++++----------- 1 file changed, 109 insertions(+), 73 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index de0f24aa90..e1447b284a 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -98,6 +98,9 @@ namespace osu.Game.Screens.Select new OsuMenuItem(@"Select", MenuItemType.Highlighted, () => FinaliseSelection(getBeatmap())) }; + [Resolved] + private OsuGameBase? game { get; set; } + [Resolved] private Bindable> selectedMods { get; set; } = null!; @@ -144,6 +147,10 @@ namespace osu.Game.Screens.Select private Bindable configBackgroundBlur = null!; + private bool lastPitchState; + + private bool usedPitchMods; + [BackgroundDependencyLoader(true)] private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog? manageCollectionsDialog, DifficultyRecommender? recommender, OsuConfigManager config) { @@ -809,94 +816,123 @@ namespace osu.Game.Screens.Select return false; } - private void increaseSpeed() + public void ChangeSpeed(double delta) { - var rateAdjustStates = ModSelect.AllAvailableMods.Where(pair => pair.Mod is ModRateAdjust); - var stateDoubleTime = ModSelect.AllAvailableMods.First(pair => pair.Mod is ModDoubleTime); - bool rateModActive = ModSelect.AllAvailableMods.Count(pair => pair.Mod is ModRateAdjust && pair.Active.Value) > 0; - const double stepsize = 0.05d; - double newRate = 1d + stepsize; + // Mod Change from 0.95 DC to 1.0 none to 1.05 DT/NC ? - // If no mod rateAdjust mod is currently active activate DoubleTime with speed newRate - if (!rateModActive) - { - stateDoubleTime.Active.Value = true; - ((ModDoubleTime)stateDoubleTime.Mod).SpeedChange.Value = newRate; + if (game == null) return; + + ModNightcore modNc = (ModNightcore)((MultiMod)game.AvailableMods.Value[ModType.DifficultyIncrease].First(mod => mod is MultiMod multiMod && multiMod.Mods.Count(modType => modType is ModNightcore) > 0)).Mods.First(mod => mod is ModNightcore); + ModDoubleTime modDt = (ModDoubleTime)((MultiMod)game.AvailableMods.Value[ModType.DifficultyIncrease].First(mod => mod is MultiMod multiMod && multiMod.Mods.Count(modType => modType is ModDoubleTime) > 0)).Mods.First(mod => mod is ModDoubleTime); + ModDaycore modDc = (ModDaycore)((MultiMod)game.AvailableMods.Value[ModType.DifficultyReduction].First(mod => mod is MultiMod multiMod && multiMod.Mods.Count(modType => modType is ModDaycore) > 0)).Mods.First(mod => mod is ModDaycore); + ModHalfTime modHt = (ModHalfTime)((MultiMod)game.AvailableMods.Value[ModType.DifficultyReduction].First(mod => mod is MultiMod multiMod && multiMod.Mods.Count(modType => modType is ModHalfTime) > 0)).Mods.First(mod => mod is ModHalfTime); + bool rateModActive = selectedMods.Value.Count(mod => mod is ModRateAdjust) > 0; + bool incompatiableModActive = selectedMods.Value.Count(mod => modDt.IncompatibleMods.Count(incompatibleMod => (mod.GetType().IsSubclassOf(incompatibleMod) || mod.GetType() == incompatibleMod) && incompatibleMod != typeof(ModRateAdjust)) > 0) > 0; + double newRate = 1d + delta; + bool isPositive = delta > 0; + + if (incompatiableModActive) return; - } - // Find current active rateAdjust mod and modify speed, enable DoubleTime if necessary - foreach (var state in rateAdjustStates) + if (rateModActive) { - ModRateAdjust mod = (ModRateAdjust)state.Mod; + ModRateAdjust mod = (ModRateAdjust)selectedMods.Value.First(mod => mod is ModRateAdjust); - if (!state.Active.Value) continue; + // Find current active rateAdjust mod and modify speed, enable HalfTime if necessary + newRate = mod.SpeedChange.Value + delta; - newRate = mod.SpeedChange.Value + stepsize; - - if (mod.Acronym == "DT" || mod.Acronym == "NC") - mod.SpeedChange.Value = newRate; - else + if (newRate == 1.0) { - if (newRate == 1.0d) - state.Active.Value = false; + lastPitchState = false; + usedPitchMods = false; - if (newRate > 1d) - { - state.Active.Value = false; - stateDoubleTime.Active.Value = true; - ((ModDoubleTime)stateDoubleTime.Mod).SpeedChange.Value = newRate; - break; - } + if (mod is ModDoubleTime dtmod && dtmod.AdjustPitch.Value) lastPitchState = true; - if (newRate < 1d) - mod.SpeedChange.Value = newRate; + if (mod is ModHalfTime htmod && htmod.AdjustPitch.Value) lastPitchState = true; + + if (mod is ModNightcore || mod is ModDaycore) usedPitchMods = true; + + //Disable RateAdjustMods + selectedMods.Value = selectedMods.Value.Where(search => search is not ModRateAdjust).ToList(); + return; } - } - } - private void decreaseSpeed() - { - var rateAdjustStates = ModSelect.AllAvailableMods.Where(pair => pair.Mod is ModRateAdjust); - var stateHalfTime = ModSelect.AllAvailableMods.First(pair => pair.Mod is ModHalfTime); - bool rateModActive = ModSelect.AllAvailableMods.Count(pair => pair.Mod is ModRateAdjust && pair.Active.Value) > 0; - const double stepsize = 0.05d; - double newRate = 1d - stepsize; - - // If no mod rateAdjust mod is currently active activate HalfTime with speed newRate - if (!rateModActive) - { - stateHalfTime.Active.Value = true; - ((ModHalfTime)stateHalfTime.Mod).SpeedChange.Value = newRate; - return; - } - - // Find current active rateAdjust mod and modify speed, enable HalfTime if necessary - foreach (var state in rateAdjustStates) - { - ModRateAdjust mod = (ModRateAdjust)state.Mod; - - if (!state.Active.Value) continue; - - newRate = mod.SpeedChange.Value - stepsize; - - if (mod.Acronym == "HT" || mod.Acronym == "DC") - mod.SpeedChange.Value = newRate; - else + if (((mod is ModDoubleTime || mod is ModNightcore) && newRate < mod.SpeedChange.MinValue) + || ((mod is ModHalfTime || mod is ModDaycore) && newRate > mod.SpeedChange.MaxValue)) { - if (newRate == 1.0d) - state.Active.Value = false; + bool adjustPitch = (mod is ModDoubleTime dtmod && dtmod.AdjustPitch.Value) || (mod is ModHalfTime htmod && htmod.AdjustPitch.Value); - if (newRate < 1d) + //Disable RateAdjustMods + selectedMods.Value = selectedMods.Value.Where(search => search is not ModRateAdjust).ToList(); + + ModRateAdjust? oppositeMod = null; + + switch (mod) { - state.Active.Value = false; - stateHalfTime.Active.Value = true; - ((ModHalfTime)stateHalfTime.Mod).SpeedChange.Value = newRate; - break; + case ModDoubleTime: + modHt.AdjustPitch.Value = adjustPitch; + oppositeMod = modHt; + break; + + case ModHalfTime: + modDt.AdjustPitch.Value = adjustPitch; + oppositeMod = modDt; + break; + + case ModNightcore: + oppositeMod = modDc; + break; + + case ModDaycore: + oppositeMod = modNc; + break; } - if (newRate > 1d) - mod.SpeedChange.Value = newRate; + if (oppositeMod == null) return; + + oppositeMod.SpeedChange.Value = newRate; + selectedMods.Value = selectedMods.Value.Append(oppositeMod).ToList(); + return; + } + + if (newRate > mod.SpeedChange.MaxValue && (mod is ModDoubleTime || mod is ModNightcore)) + newRate = mod.SpeedChange.MaxValue; + + if (newRate < mod.SpeedChange.MinValue && (mod is ModHalfTime || mod is ModDaycore)) + newRate = mod.SpeedChange.MinValue; + + mod.SpeedChange.Value = newRate; + } + else + { + // If no ModRateAdjust is actived activate one + if (isPositive) + { + if (!usedPitchMods) + { + modDt.SpeedChange.Value = newRate; + modDt.AdjustPitch.Value = lastPitchState; + selectedMods.Value = selectedMods.Value.Append(modDt).ToList(); + } + else + { + modNc.SpeedChange.Value = newRate; + selectedMods.Value = selectedMods.Value.Append(modNc).ToList(); + } + } + else + { + if (!usedPitchMods) + { + modHt.SpeedChange.Value = newRate; + modHt.AdjustPitch.Value = lastPitchState; + selectedMods.Value = selectedMods.Value.Append(modHt).ToList(); + } + else + { + modDc.SpeedChange.Value = newRate; + selectedMods.Value = selectedMods.Value.Append(modDc).ToList(); + } } } } @@ -1111,11 +1147,11 @@ namespace osu.Game.Screens.Select return true; case GlobalAction.IncreaseSpeed: - increaseSpeed(); + ChangeSpeed(0.05); return true; case GlobalAction.DecreaseSpeed: - decreaseSpeed(); + ChangeSpeed(-0.05); return true; } From 614cbdf0a404487ebbeea3d98766781800a9ad0f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 18 May 2024 22:51:58 +0300 Subject: [PATCH 3948/4092] Reduce container nesting in PathControlPointPiece --- .../Components/PathControlPointPiece.cs | 50 +++++++------------ 1 file changed, 17 insertions(+), 33 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index c6e05d3ca3..9d819f6cc0 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -8,9 +8,9 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Graphics; @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public readonly PathControlPoint ControlPoint; private readonly T hitObject; - private readonly Container marker; + private readonly Circle circle; private readonly Drawable markerRing; [Resolved] @@ -60,38 +60,22 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components Origin = Anchor.Centre; AutoSizeAxes = Axes.Both; - InternalChildren = new Drawable[] + InternalChildren = new[] { - marker = new Container + circle = new Circle { Anchor = Anchor.Centre, Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Children = new[] - { - new Circle - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(20), - }, - markerRing = new CircularContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(28), - Masking = true, - BorderThickness = 2, - BorderColour = Color4.White, - Alpha = 0, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true - } - } - } + Size = new Vector2(20), + }, + markerRing = new CircularProgress + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(28), + Alpha = 0, + InnerRadius = 0.1f, + Progress = 1 } }; } @@ -115,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components } // The connecting path is excluded from positional input - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => marker.ReceivePositionalInputAt(screenSpacePos); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => circle.ReceivePositionalInputAt(screenSpacePos); protected override bool OnHover(HoverEvent e) { @@ -209,8 +193,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components if (IsHovered || IsSelected.Value) colour = colour.Lighten(1); - marker.Colour = colour; - marker.Scale = new Vector2(hitObject.Scale); + Colour = colour; + Scale = new Vector2(hitObject.Scale); } private Color4 getColourFromNodeType() From be642c8c428966665fff99b0383a9d5404da801f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 19 May 2024 09:41:08 +0200 Subject: [PATCH 3949/4092] Fix total score without mods migration failing on custom ruleset scores when custom ruleset cannot be loaded Closes https://github.com/ppy/osu/issues/28209. Yes this means that such scores will have a zero total score without mods in DB and thus might up getting their total recalculated to zero when we try a mod multiplier rebalance (unless we skip scores with zero completely I suppose). I also don't really care about that right now. --- osu.Game/Database/RealmAccess.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 63f61228f3..1ece81be50 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -1134,7 +1134,17 @@ namespace osu.Game.Database case 41: foreach (var score in migration.NewRealm.All()) - LegacyScoreDecoder.PopulateTotalScoreWithoutMods(score); + { + try + { + // this can fail e.g. if a user has a score set on a ruleset that can no longer be loaded. + LegacyScoreDecoder.PopulateTotalScoreWithoutMods(score); + } + catch (Exception ex) + { + Logger.Log($@"Failed to populate total score without mods for score {score.ID}: {ex}", LoggingTarget.Database); + } + } break; } From e4858a975dda5a07d7b39b3b0b875167ff4cf5d1 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Sun, 19 May 2024 14:07:40 +0200 Subject: [PATCH 3950/4092] Show mouse and joystick settings on mobile --- osu.Game/OsuGameBase.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 5533ee8337..5e4ec5a61d 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -578,17 +578,17 @@ namespace osu.Game { case ITabletHandler th: return new TabletSettings(th); - - case MouseHandler mh: - return new MouseSettings(mh); - - case JoystickHandler jh: - return new JoystickSettings(jh); } } switch (handler) { + case MouseHandler mh: + return new MouseSettings(mh); + + case JoystickHandler jh: + return new JoystickSettings(jh); + case TouchHandler th: return new TouchSettings(th); From 04acc58b7405836a205d05c4d0e1840884385988 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Sun, 19 May 2024 14:12:21 +0200 Subject: [PATCH 3951/4092] Don't show warning on android Unsure about iOS. --- .../Settings/Sections/Input/MouseSettings.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index 7805ed5834..6eb512fa35 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -105,12 +105,17 @@ namespace osu.Game.Overlays.Settings.Sections.Input highPrecisionMouse.Current.BindValueChanged(highPrecision => { - if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows) + switch (RuntimeInfo.OS) { - if (highPrecision.NewValue) - highPrecisionMouse.SetNoticeText(MouseSettingsStrings.HighPrecisionPlatformWarning, true); - else - highPrecisionMouse.ClearNoticeText(); + case RuntimeInfo.Platform.Linux: + case RuntimeInfo.Platform.macOS: + case RuntimeInfo.Platform.iOS: + if (highPrecision.NewValue) + highPrecisionMouse.SetNoticeText(MouseSettingsStrings.HighPrecisionPlatformWarning, true); + else + highPrecisionMouse.ClearNoticeText(); + + break; } }, true); } From 609268786f540e42996b711eab525f1e531044c4 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Sun, 19 May 2024 13:28:46 +0100 Subject: [PATCH 3952/4092] remove unneeded extra `Previous` calls from `RhythmEvaluator` --- .../Difficulty/Evaluators/RhythmEvaluator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs index 05939bb3ab..f23e8329fa 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs @@ -66,10 +66,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators } else { - if (current.Previous(i - 1).BaseObject is Slider) // bpm change is into slider, this is easy acc window + if (currObj.BaseObject is Slider) // bpm change is into slider, this is easy acc window effectiveRatio *= 0.125; - if (current.Previous(i).BaseObject is Slider) // bpm change was from a slider, this is easier typically than circle -> circle + if (prevObj.BaseObject is Slider) // bpm change was from a slider, this is easier typically than circle -> circle effectiveRatio *= 0.25; if (previousIslandSize == islandSize) // repeated island size (ex: triplet -> triplet) From f31928875bc8510f6bfe38b95902dbb12f5e415d Mon Sep 17 00:00:00 2001 From: James Wilson Date: Sun, 19 May 2024 16:26:51 +0100 Subject: [PATCH 3953/4092] Reduce `Previous` calls in `RhythmEvaluator` by optimising loop logic --- .../Difficulty/Evaluators/RhythmEvaluator.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs index 05939bb3ab..a121b1de0b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs @@ -36,11 +36,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators while (rhythmStart < historicalNoteCount - 2 && current.StartTime - current.Previous(rhythmStart).StartTime < history_time_max) rhythmStart++; + OsuDifficultyHitObject prevObj = (OsuDifficultyHitObject)current.Previous(rhythmStart); + OsuDifficultyHitObject lastObj = (OsuDifficultyHitObject)current.Previous(rhythmStart + 1); + for (int i = rhythmStart; i > 0; i--) { OsuDifficultyHitObject currObj = (OsuDifficultyHitObject)current.Previous(i - 1); - OsuDifficultyHitObject prevObj = (OsuDifficultyHitObject)current.Previous(i); - OsuDifficultyHitObject lastObj = (OsuDifficultyHitObject)current.Previous(i + 1); double currHistoricalDecay = (history_time_max - (current.StartTime - currObj.StartTime)) / history_time_max; // scales note 0 to 1 from history to now @@ -100,6 +101,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators startRatio = effectiveRatio; islandSize = 1; } + + lastObj = prevObj; + prevObj = currObj; } return Math.Sqrt(4 + rhythmComplexitySum * rhythm_multiplier) / 2; //produces multiplier that can be applied to strain. range [1, infinity) (not really though) From c03f68413a11770c6d59f3d363dc7d16f21ebdfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 May 2024 09:43:25 +0200 Subject: [PATCH 3954/4092] Fix skin editor closest origin selection spazzing out on scaled sprites Closes https://github.com/ppy/osu/issues/28215. `drawable.Position` is a location in the parent's coordinate space, and `drawable.OriginPosition` is in the drawable's local space and additionally does not take scale into account. --- osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index 75bb77fa73..28b2435346 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -425,9 +425,9 @@ namespace osu.Game.Overlays.SkinEditor { if (origin == drawable.Origin) return; - var previousOrigin = drawable.OriginPosition; + var previousOrigin = drawable.ToParentSpace(drawable.OriginPosition); drawable.Origin = origin; - drawable.Position += drawable.OriginPosition - previousOrigin; + drawable.Position += drawable.ToParentSpace(drawable.OriginPosition) - previousOrigin; } private static void adjustScaleFromAnchor(ref Vector2 scale, Anchor reference) From 3da3b91be51526c1d40e016ddf9002134f9d788c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 May 2024 10:14:08 +0200 Subject: [PATCH 3955/4092] Improve closest origin selection to include effects of rotation/flip Closes https://github.com/ppy/osu/issues/28237. Solution as proposed here: https://github.com/ppy/osu/pull/28089#issuecomment-2095372157 For flips and rotations by 90 degrees this does what you would expect it to. For arbitrary rotations it *sort of kind of* attempts to do this but the results are a bit wonky - probably still better than what was there before, though? --- .../SkinEditor/SkinSelectionHandler.cs | 36 +++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index 28b2435346..21909cdc10 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -421,12 +422,41 @@ namespace osu.Game.Overlays.SkinEditor drawable.Position -= drawable.AnchorPosition - previousAnchor; } - private static void applyOrigin(Drawable drawable, Anchor origin) + private static void applyOrigin(Drawable drawable, Anchor screenSpaceOrigin) { - if (origin == drawable.Origin) return; + var boundingBox = drawable.ScreenSpaceDrawQuad.AABBFloat; + + var targetScreenSpacePosition = screenSpaceOrigin.PositionOnQuad(boundingBox); + + Anchor localOrigin = Anchor.TopLeft; + float smallestDistanceFromTargetPosition = float.PositiveInfinity; + + void checkOrigin(Anchor originToTest) + { + Vector2 positionToTest = drawable.ToScreenSpace(originToTest.PositionOnQuad(drawable.DrawRectangle)); + float testedDistance = Vector2.Distance(targetScreenSpacePosition, positionToTest); + + if (testedDistance < smallestDistanceFromTargetPosition) + { + localOrigin = originToTest; + smallestDistanceFromTargetPosition = testedDistance; + } + } + + checkOrigin(Anchor.TopLeft); + checkOrigin(Anchor.TopCentre); + checkOrigin(Anchor.TopRight); + + checkOrigin(Anchor.CentreLeft); + checkOrigin(Anchor.Centre); + checkOrigin(Anchor.CentreRight); + + checkOrigin(Anchor.BottomLeft); + checkOrigin(Anchor.BottomCentre); + checkOrigin(Anchor.BottomRight); var previousOrigin = drawable.ToParentSpace(drawable.OriginPosition); - drawable.Origin = origin; + drawable.Origin = localOrigin; drawable.Position += drawable.ToParentSpace(drawable.OriginPosition) - previousOrigin; } From fe0af7e720cf17fdbf49842c94ba2d05f6055daf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 May 2024 11:36:39 +0200 Subject: [PATCH 3956/4092] Fix unnecessary padding of empty strings for discord RPC purposes Closes https://github.com/ppy/osu/issues/28248. Destroy all software. --- osu.Desktop/DiscordRichPresence.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 3e0a9099cb..780d367900 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -273,7 +273,11 @@ namespace osu.Desktop private static string clampLength(string str) { - // For whatever reason, discord decides that strings shorter than 2 characters cannot possibly be valid input, because... reasons? + // Empty strings are fine to discord even though single-character strings are not. Make it make sense. + if (string.IsNullOrEmpty(str)) + return str; + + // As above, discord decides that *non-empty* strings shorter than 2 characters cannot possibly be valid input, because... reasons? // And yes, that is two *characters*, or *codepoints*, not *bytes* as further down below (as determined by empirical testing). // That seems very questionable, and isn't even documented anywhere. So to *make it* accept such valid input, // just tack on enough of U+200B ZERO WIDTH SPACEs at the end. From 85f85dee9ef28b4b65678d58f6e3f54abf0fe2f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 May 2024 14:46:28 +0200 Subject: [PATCH 3957/4092] Enable NRT in `TestScenePresentScore` --- .../Visual/Navigation/TestScenePresentScore.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs index 212783d047..2d4302c0df 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using NUnit.Framework; @@ -26,7 +24,7 @@ namespace osu.Game.Tests.Visual.Navigation { public partial class TestScenePresentScore : OsuGameTestScene { - private BeatmapSetInfo beatmap; + private BeatmapSetInfo beatmap = null!; [SetUpSteps] public new void SetUpSteps() @@ -64,7 +62,7 @@ namespace osu.Game.Tests.Visual.Navigation Ruleset = new OsuRuleset().RulesetInfo }, } - })?.Value; + })!.Value; }); } @@ -171,9 +169,9 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("wait for menu", () => Game.ScreenStack.CurrentScreen is MainMenu); } - private Func importScore(int i, RulesetInfo ruleset = null) + private Func importScore(int i, RulesetInfo? ruleset = null) { - ScoreInfo imported = null; + ScoreInfo? imported = null; AddStep($"import score {i}", () => { imported = Game.ScoreManager.Import(new ScoreInfo @@ -188,14 +186,14 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert($"import {i} succeeded", () => imported != null); - return () => imported; + return () => imported!; } /// /// Some tests test waiting for a particular screen twice in a row, but expect a new instance each time. /// There's a case where they may succeed incorrectly if we don't compare against the previous instance. /// - private IScreen lastWaitedScreen; + private IScreen lastWaitedScreen = null!; private void presentAndConfirm(Func getImport, ScorePresentType type) { From 3b15c223be4a38c624f68abd30561638d46ea14d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 May 2024 14:48:02 +0200 Subject: [PATCH 3958/4092] Add failing test case --- .../Navigation/TestScenePresentScore.cs | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs index 2d4302c0df..2c2335de13 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs @@ -156,6 +156,27 @@ namespace osu.Game.Tests.Visual.Navigation presentAndConfirm(secondImport, type); } + [Test] + public void TestScoreRefetchIgnoresEmptyHash() + { + AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo?.Invoke()); + AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded); + + importScore(-1, hash: string.Empty); + importScore(3, hash: @"deadbeef"); + + // oftentimes a `PresentScore()` call will be given a `ScoreInfo` which is converted from an online score, + // in which cases the hash will generally not be available. + AddStep("present score", () => Game.PresentScore(new ScoreInfo { OnlineID = 3, Hash = string.Empty })); + + AddUntilStep("wait for results", () => lastWaitedScreen != Game.ScreenStack.CurrentScreen && Game.ScreenStack.CurrentScreen is ResultsScreen); + AddUntilStep("correct score displayed", () => + { + var score = ((ResultsScreen)Game.ScreenStack.CurrentScreen).Score!; + return score.OnlineID == 3 && score.Hash == "deadbeef"; + }); + } + private void returnToMenu() { // if we don't pause, there's a chance the track may change at the main menu out of our control (due to reaching the end of the track). @@ -169,14 +190,14 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("wait for menu", () => Game.ScreenStack.CurrentScreen is MainMenu); } - private Func importScore(int i, RulesetInfo? ruleset = null) + private Func importScore(int i, RulesetInfo? ruleset = null, string? hash = null) { ScoreInfo? imported = null; AddStep($"import score {i}", () => { imported = Game.ScoreManager.Import(new ScoreInfo { - Hash = Guid.NewGuid().ToString(), + Hash = hash ?? Guid.NewGuid().ToString(), OnlineID = i, BeatmapInfo = beatmap.Beatmaps.First(), Ruleset = ruleset ?? new OsuRuleset().RulesetInfo, From ed498f6eed66a4c801604484dbcbea883b229cbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 May 2024 14:48:36 +0200 Subject: [PATCH 3959/4092] Do not attempt to match score by equality of hash if it's empty Closes https://github.com/ppy/osu/issues/28216. The affected user's database contained six sentakki scores with an empty hash. When an online score is being imported, an online model (which does not have a hash) will be transmogrified into a `ScoreInfo` with an empty hash, which would end up accidentally matching those scores and basically breaking everything at that point. To fix, avoid attempting to match anything on empty hash. This does not break online score matching because for those cases the actual online ID of the score will be used. --- osu.Game/Scoring/ScoreManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 0c707ffa19..df4735b5e6 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -88,7 +88,7 @@ namespace osu.Game.Scoring { ScoreInfo? databasedScoreInfo = null; - if (originalScoreInfo is ScoreInfo scoreInfo) + if (originalScoreInfo is ScoreInfo scoreInfo && !string.IsNullOrEmpty(scoreInfo.Hash)) databasedScoreInfo = Query(s => s.Hash == scoreInfo.Hash); if (originalScoreInfo.OnlineID > 0) From db8b72eb37464d50ec5092f7180082507e7fc2b0 Mon Sep 17 00:00:00 2001 From: Aurelian Date: Mon, 20 May 2024 16:23:16 +0200 Subject: [PATCH 3960/4092] Clamped Difficulty Ranges to [0,10] --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 6fa78fa8e6..059451e228 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -383,21 +383,24 @@ namespace osu.Game.Beatmaps.Formats switch (pair.Key) { case @"HPDrainRate": - difficulty.DrainRate = Parsing.ParseFloat(pair.Value); + difficulty.DrainRate = Math.Clamp(Parsing.ParseFloat(pair.Value), 0, 10); break; case @"CircleSize": difficulty.CircleSize = Parsing.ParseFloat(pair.Value); + //If the mode is not Mania, clamp circle size to [0,10] + if (!beatmap.BeatmapInfo.Ruleset.OnlineID.Equals(3)) + difficulty.CircleSize = Math.Clamp(difficulty.CircleSize, 0, 10); break; case @"OverallDifficulty": - difficulty.OverallDifficulty = Parsing.ParseFloat(pair.Value); + difficulty.OverallDifficulty = Math.Clamp(Parsing.ParseFloat(pair.Value), 0, 10); if (!hasApproachRate) difficulty.ApproachRate = difficulty.OverallDifficulty; break; case @"ApproachRate": - difficulty.ApproachRate = Parsing.ParseFloat(pair.Value); + difficulty.ApproachRate = Math.Clamp(Parsing.ParseFloat(pair.Value), 0, 10); hasApproachRate = true; break; From e740b8bcc358653d12ff528fb33e6c42cf505559 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 May 2024 14:36:11 +0800 Subject: [PATCH 3961/4092] Fix single frame glitching in skin editor https://github.com/ppy/osu/pull/28257#discussion_r1606999574 --- osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index 21909cdc10..8fd9c1b559 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -455,9 +455,10 @@ namespace osu.Game.Overlays.SkinEditor checkOrigin(Anchor.BottomCentre); checkOrigin(Anchor.BottomRight); - var previousOrigin = drawable.ToParentSpace(drawable.OriginPosition); + Vector2 offset = drawable.ToParentSpace(localOrigin.PositionOnQuad(drawable.DrawRectangle)) - drawable.ToParentSpace(drawable.Origin.PositionOnQuad(drawable.DrawRectangle)); + drawable.Origin = localOrigin; - drawable.Position += drawable.ToParentSpace(drawable.OriginPosition) - previousOrigin; + drawable.Position += offset; } private static void adjustScaleFromAnchor(ref Vector2 scale, Anchor reference) From d7d569cf4e68acdbcc9cec844337f93bdb207a54 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 May 2024 14:34:53 +0800 Subject: [PATCH 3962/4092] Temporary rollback of framework / SDL3 --- osu.Android.props | 2 +- osu.Android/AndroidJoystickSettings.cs | 76 +++++++++++++++ osu.Android/AndroidMouseSettings.cs | 97 +++++++++++++++++++ osu.Android/OsuGameAndroid.cs | 22 +++++ osu.Desktop/OsuGameDesktop.cs | 11 +-- osu.Desktop/Program.cs | 33 +++---- .../Components/PathControlPointVisualiser.cs | 2 +- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 3 +- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 3 +- .../Screens/Ladder/LadderDragContainer.cs | 2 +- osu.Game/Database/EmptyRealmSet.cs | 5 - .../UserInterface/ExpandableSlider.cs | 8 +- .../Graphics/UserInterface/OsuSliderBar.cs | 9 +- .../Graphics/UserInterface/OsuTabControl.cs | 24 ++--- .../Graphics/UserInterface/PageTabControl.cs | 14 +-- .../UserInterface/RoundedSliderBar.cs | 5 +- .../UserInterface/ShearedSliderBar.cs | 5 +- .../UserInterfaceV2/LabelledSliderBar.cs | 4 +- .../UserInterfaceV2/SliderWithTextBoxInput.cs | 8 +- osu.Game/OsuGameBase.cs | 12 +-- .../BeatmapListingCardSizeTabControl.cs | 12 +-- ...BeatmapSearchMultipleSelectionFilterRow.cs | 4 - .../Overlays/BeatmapListing/FilterTabItem.cs | 12 +-- .../BeatmapSet/BeatmapRulesetSelector.cs | 2 +- .../OverlayPanelDisplayStyleControl.cs | 14 +-- osu.Game/Overlays/OverlayRulesetTabItem.cs | 14 +-- osu.Game/Overlays/OverlayStreamItem.cs | 12 +-- osu.Game/Overlays/OverlayTabControl.cs | 14 +-- .../Overlays/Settings/Sections/SizeSlider.cs | 3 +- .../Settings/SettingsPercentageSlider.cs | 4 +- osu.Game/Overlays/Settings/SettingsSlider.cs | 6 +- .../Toolbar/ToolbarRulesetSelector.cs | 16 ++- .../Toolbar/ToolbarRulesetTabButton.cs | 12 --- osu.Game/Rulesets/Mods/DifficultyBindable.cs | 2 +- .../Objects/Drawables/DrawableHitObject.cs | 3 +- .../Scoring/LegacyDrainingHealthProcessor.cs | 7 -- .../Rulesets/UI/FrameStabilityContainer.cs | 2 +- .../Timeline/TimelineTickDisplay.cs | 3 +- osu.Game/Screens/Edit/Editor.cs | 14 ++- .../IndeterminateSliderWithTextBoxInput.cs | 8 +- .../Match/Components/MatchTypePicker.cs | 11 +-- .../Play/PlayerSettings/PlayerSliderBar.cs | 4 +- osu.Game/osu.Game.csproj | 4 +- osu.iOS.props | 2 +- 44 files changed, 304 insertions(+), 226 deletions(-) create mode 100644 osu.Android/AndroidJoystickSettings.cs create mode 100644 osu.Android/AndroidMouseSettings.cs diff --git a/osu.Android.props b/osu.Android.props index e20ac2e0b7..2d7a9d2652 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 103ef50e0c..b2e3fc0779 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From 1127a69359f6eb9305d74a85dff8579278135997 Mon Sep 17 00:00:00 2001 From: Aurelian Date: Tue, 21 May 2024 10:15:53 +0200 Subject: [PATCH 3963/4092] Moved DIfficulty Clamping to occur after the file has been parsed This is to handle potential issues with the ruleset being parsed after circle size has been parsed. --- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 059451e228..2acabe2518 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -85,6 +85,8 @@ namespace osu.Game.Beatmaps.Formats base.ParseStreamInto(stream, beatmap); + applyDifficultyRestrictions(beatmap.Difficulty); + flushPendingPoints(); // Objects may be out of order *only* if a user has manually edited an .osu file. @@ -102,6 +104,26 @@ namespace osu.Game.Beatmaps.Formats } } + /// + /// Clamp Difficulty settings to be within the normal range. + /// + private void applyDifficultyRestrictions(BeatmapDifficulty difficulty) + { + difficulty.DrainRate = Math.Clamp(difficulty.DrainRate, 0, 10); + //If the mode is not Mania, clamp circle size to [0,10] + if (!beatmap.BeatmapInfo.Ruleset.OnlineID.Equals(3)) + difficulty.CircleSize = Math.Clamp(difficulty.CircleSize, 0, 10); + //If it is Mania, it must be within [1,20] - dual stages with 10 keys each. + //The lower bound should be 4, but there are ranked maps that are lower than this. + else + difficulty.CircleSize = Math.Clamp(difficulty.CircleSize, 1, 20); + difficulty.OverallDifficulty = Math.Clamp(difficulty.OverallDifficulty, 0, 10); + difficulty.ApproachRate = Math.Clamp(difficulty.ApproachRate, 0, 10); + + difficulty.SliderMultiplier = Math.Clamp(difficulty.SliderMultiplier, 0.4, 3.6); + difficulty.SliderTickRate = Math.Clamp(difficulty.SliderTickRate, 0.5, 8); + } + /// /// Processes the beatmap such that a new combo is started the first hitobject following each break. /// @@ -383,33 +405,30 @@ namespace osu.Game.Beatmaps.Formats switch (pair.Key) { case @"HPDrainRate": - difficulty.DrainRate = Math.Clamp(Parsing.ParseFloat(pair.Value), 0, 10); + difficulty.DrainRate = Parsing.ParseFloat(pair.Value); break; case @"CircleSize": difficulty.CircleSize = Parsing.ParseFloat(pair.Value); - //If the mode is not Mania, clamp circle size to [0,10] - if (!beatmap.BeatmapInfo.Ruleset.OnlineID.Equals(3)) - difficulty.CircleSize = Math.Clamp(difficulty.CircleSize, 0, 10); break; case @"OverallDifficulty": - difficulty.OverallDifficulty = Math.Clamp(Parsing.ParseFloat(pair.Value), 0, 10); + difficulty.OverallDifficulty = Parsing.ParseFloat(pair.Value); if (!hasApproachRate) difficulty.ApproachRate = difficulty.OverallDifficulty; break; case @"ApproachRate": - difficulty.ApproachRate = Math.Clamp(Parsing.ParseFloat(pair.Value), 0, 10); + difficulty.ApproachRate = Parsing.ParseFloat(pair.Value); hasApproachRate = true; break; case @"SliderMultiplier": - difficulty.SliderMultiplier = Math.Clamp(Parsing.ParseDouble(pair.Value), 0.4, 3.6); + difficulty.SliderMultiplier = Parsing.ParseDouble(pair.Value); break; case @"SliderTickRate": - difficulty.SliderTickRate = Math.Clamp(Parsing.ParseDouble(pair.Value), 0.5, 8); + difficulty.SliderTickRate = Parsing.ParseDouble(pair.Value); break; } } From 45fcbea182d1076ae9239984f76ed9b36b458c5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 17 May 2024 14:43:06 +0200 Subject: [PATCH 3964/4092] Compute total score without mods during standardised score conversion This is going to be used by server-side flows. Note that the server-side overload of `UpdateFromLegacy()` was not calling `LegacyScoreDecoder.PopulateTotalScoreWithoutMods()`. Computing the score without mods inline reduces reflection overheads from constructing mod instances, which feels pretty important for server-side flows. There is one weird kink in the treatment of stable scores with score V2 active - they get the *legacy* multipliers unapplied for them because that made the most sense. For all intents and purposes this matters mostly for client-side replays with score V2. I'm not sure whether scores with SV2 ever make it to submission in stable. There may be minute differences in converted score due to rounding shenanigans but I don't think it's worth doing a reverify for this. --- .../StandardisedScoreMigrationTools.cs | 67 ++++++++++--------- 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index 7d09ebdb40..db44731bed 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -16,7 +16,6 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring.Legacy; using osu.Game.Scoring; -using osu.Game.Scoring.Legacy; namespace osu.Game.Database { @@ -248,8 +247,7 @@ namespace osu.Game.Database // warning: ordering is important here - both total score and ranks are dependent on accuracy! score.Accuracy = computeAccuracy(score, scoreProcessor); score.Rank = computeRank(score, scoreProcessor); - score.TotalScore = convertFromLegacyTotalScore(score, ruleset, beatmap); - LegacyScoreDecoder.PopulateTotalScoreWithoutMods(score); + (score.TotalScoreWithoutMods, score.TotalScore) = convertFromLegacyTotalScore(score, ruleset, beatmap); } /// @@ -273,7 +271,7 @@ namespace osu.Game.Database // warning: ordering is important here - both total score and ranks are dependent on accuracy! score.Accuracy = computeAccuracy(score, scoreProcessor); score.Rank = computeRank(score, scoreProcessor); - score.TotalScore = convertFromLegacyTotalScore(score, ruleset, difficulty, attributes); + (score.TotalScoreWithoutMods, score.TotalScore) = convertFromLegacyTotalScore(score, ruleset, difficulty, attributes); } /// @@ -283,17 +281,13 @@ namespace osu.Game.Database /// The in which the score was set. /// The applicable for this score. /// The standardised total score. - private static long convertFromLegacyTotalScore(ScoreInfo score, Ruleset ruleset, WorkingBeatmap beatmap) + private static (long withoutMods, long withMods) convertFromLegacyTotalScore(ScoreInfo score, Ruleset ruleset, WorkingBeatmap beatmap) { if (!score.IsLegacyScore) - return score.TotalScore; + return (score.TotalScoreWithoutMods, score.TotalScore); if (ruleset is not ILegacyRuleset legacyRuleset) - return score.TotalScore; - - var mods = score.Mods; - if (mods.Any(mod => mod is ModScoreV2)) - return score.TotalScore; + return (score.TotalScoreWithoutMods, score.TotalScore); var playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, score.Mods); @@ -302,8 +296,13 @@ namespace osu.Game.Database ILegacyScoreSimulator sv1Simulator = legacyRuleset.CreateLegacyScoreSimulator(); LegacyScoreAttributes attributes = sv1Simulator.Simulate(beatmap, playableBeatmap); + var legacyBeatmapConversionDifficultyInfo = LegacyBeatmapConversionDifficultyInfo.FromBeatmap(beatmap.Beatmap); - return convertFromLegacyTotalScore(score, ruleset, LegacyBeatmapConversionDifficultyInfo.FromBeatmap(beatmap.Beatmap), attributes); + var mods = score.Mods; + if (mods.Any(mod => mod is ModScoreV2)) + return ((long)Math.Round(score.TotalScore / sv1Simulator.GetLegacyScoreMultiplier(mods, legacyBeatmapConversionDifficultyInfo)), score.TotalScore); + + return convertFromLegacyTotalScore(score, ruleset, legacyBeatmapConversionDifficultyInfo, attributes); } /// @@ -314,15 +313,15 @@ namespace osu.Game.Database /// The beatmap difficulty. /// The legacy scoring attributes for the beatmap which the score was set on. /// The standardised total score. - private static long convertFromLegacyTotalScore(ScoreInfo score, Ruleset ruleset, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes) + private static (long withoutMods, long withMods) convertFromLegacyTotalScore(ScoreInfo score, Ruleset ruleset, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes) { if (!score.IsLegacyScore) - return score.TotalScore; + return (score.TotalScoreWithoutMods, score.TotalScore); Debug.Assert(score.LegacyTotalScore != null); if (ruleset is not ILegacyRuleset legacyRuleset) - return score.TotalScore; + return (score.TotalScoreWithoutMods, score.TotalScore); double legacyModMultiplier = legacyRuleset.CreateLegacyScoreSimulator().GetLegacyScoreMultiplier(score.Mods, difficulty); int maximumLegacyAccuracyScore = attributes.AccuracyScore; @@ -354,17 +353,18 @@ namespace osu.Game.Database double modMultiplier = score.Mods.Select(m => m.ScoreMultiplier).Aggregate(1.0, (c, n) => c * n); - long convertedTotalScore; + long convertedTotalScoreWithoutMods; switch (score.Ruleset.OnlineID) { case 0: if (score.MaxCombo == 0 || score.Accuracy == 0) { - return (long)Math.Round(( + convertedTotalScoreWithoutMods = (long)Math.Round( 0 + 500000 * Math.Pow(score.Accuracy, 5) - + bonusProportion) * modMultiplier); + + bonusProportion); + break; } // see similar check above. @@ -372,10 +372,11 @@ namespace osu.Game.Database // are either pointless or wildly wrong. if (maximumLegacyComboScore + maximumLegacyBonusScore == 0) { - return (long)Math.Round(( + convertedTotalScoreWithoutMods = (long)Math.Round( 500000 * comboProportion // as above, zero if mods result in zero multiplier, one otherwise + 500000 * Math.Pow(score.Accuracy, 5) - + bonusProportion) * modMultiplier); + + bonusProportion); + break; } // Assumptions: @@ -472,17 +473,17 @@ namespace osu.Game.Database double newComboScoreProportion = estimatedComboPortionInStandardisedScore / maximumAchievableComboPortionInStandardisedScore; - convertedTotalScore = (long)Math.Round(( + convertedTotalScoreWithoutMods = (long)Math.Round( 500000 * newComboScoreProportion * score.Accuracy + 500000 * Math.Pow(score.Accuracy, 5) - + bonusProportion) * modMultiplier); + + bonusProportion); break; case 1: - convertedTotalScore = (long)Math.Round(( + convertedTotalScoreWithoutMods = (long)Math.Round( 250000 * comboProportion + 750000 * Math.Pow(score.Accuracy, 3.6) - + bonusProportion) * modMultiplier); + + bonusProportion); break; case 2: @@ -507,28 +508,28 @@ namespace osu.Game.Database ? 0 : (double)score.Statistics.GetValueOrDefault(HitResult.SmallTickHit) / score.MaximumStatistics.GetValueOrDefault(HitResult.SmallTickHit); - convertedTotalScore = (long)Math.Round(( + convertedTotalScoreWithoutMods = (long)Math.Round( comboPortion * estimateComboProportionForCatch(attributes.MaxCombo, score.MaxCombo, score.Statistics.GetValueOrDefault(HitResult.Miss)) + dropletsPortion * dropletsHit - + bonusProportion) * modMultiplier); + + bonusProportion); break; case 3: - convertedTotalScore = (long)Math.Round(( + convertedTotalScoreWithoutMods = (long)Math.Round( 850000 * comboProportion + 150000 * Math.Pow(score.Accuracy, 2 + 2 * score.Accuracy) - + bonusProportion) * modMultiplier); + + bonusProportion); break; default: - convertedTotalScore = score.TotalScore; - break; + return (score.TotalScoreWithoutMods, score.TotalScore); } - if (convertedTotalScore < 0) - throw new InvalidOperationException($"Total score conversion operation returned invalid total of {convertedTotalScore}"); + if (convertedTotalScoreWithoutMods < 0) + throw new InvalidOperationException($"Total score conversion operation returned invalid total of {convertedTotalScoreWithoutMods}"); - return convertedTotalScore; + long convertedTotalScore = (long)Math.Round(convertedTotalScoreWithoutMods * modMultiplier); + return (convertedTotalScoreWithoutMods, convertedTotalScore); } /// From 148afd120127c58655ed35190fb7456ce1f0e973 Mon Sep 17 00:00:00 2001 From: Fabian van Oeffelt Date: Tue, 21 May 2024 14:47:34 +0200 Subject: [PATCH 3965/4092] Change Speedchange behaviour to keep changing while holding key, Add Toast to nofity user what just happend --- osu.Game/Localisation/ToastStrings.cs | 5 +++++ osu.Game/Overlays/OSD/SpeedChangeToast.cs | 17 +++++++++++++++ osu.Game/Screens/Select/SongSelect.cs | 26 ++++++++++++++++------- 3 files changed, 40 insertions(+), 8 deletions(-) create mode 100644 osu.Game/Overlays/OSD/SpeedChangeToast.cs diff --git a/osu.Game/Localisation/ToastStrings.cs b/osu.Game/Localisation/ToastStrings.cs index da798a3937..33027966dd 100644 --- a/osu.Game/Localisation/ToastStrings.cs +++ b/osu.Game/Localisation/ToastStrings.cs @@ -49,6 +49,11 @@ namespace osu.Game.Localisation /// public static LocalisableString UrlCopied => new TranslatableString(getKey(@"url_copied"), @"URL copied"); + /// + /// "Speed Changed" + /// + public static LocalisableString SpeedChanged => new TranslatableString(getKey(@"speed_changed"), @"Speed Changed"); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Overlays/OSD/SpeedChangeToast.cs b/osu.Game/Overlays/OSD/SpeedChangeToast.cs new file mode 100644 index 0000000000..73ba23622b --- /dev/null +++ b/osu.Game/Overlays/OSD/SpeedChangeToast.cs @@ -0,0 +1,17 @@ +// 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.Configuration; +using osu.Game.Input.Bindings; +using osu.Game.Localisation; + +namespace osu.Game.Overlays.OSD +{ + public partial class SpeedChangeToast : Toast + { + public SpeedChangeToast(OsuConfigManager config, double delta) + : base(CommonStrings.Beatmaps, ToastStrings.SpeedChanged, config.LookupKeyBindings(GlobalAction.IncreaseSpeed) + " / " + config.LookupKeyBindings(GlobalAction.DecreaseSpeed)) + { + } + } +} diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index e1447b284a..7eb2be9100 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -30,6 +30,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Overlays; using osu.Game.Overlays.Mods; +using osu.Game.Overlays.OSD; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Backgrounds; @@ -151,6 +152,12 @@ namespace osu.Game.Screens.Select private bool usedPitchMods; + [Resolved] + private OnScreenDisplay? onScreenDisplay { get; set; } + + [Resolved] + private OsuConfigManager? config { get; set; } + [BackgroundDependencyLoader(true)] private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog? manageCollectionsDialog, DifficultyRecommender? recommender, OsuConfigManager config) { @@ -819,7 +826,7 @@ namespace osu.Game.Screens.Select public void ChangeSpeed(double delta) { // Mod Change from 0.95 DC to 1.0 none to 1.05 DT/NC ? - + onScreenDisplay?.Display(new SpeedChangeToast(config!, delta)); if (game == null) return; ModNightcore modNc = (ModNightcore)((MultiMod)game.AvailableMods.Value[ModType.DifficultyIncrease].First(mod => mod is MultiMod multiMod && multiMod.Mods.Count(modType => modType is ModNightcore) > 0)).Mods.First(mod => mod is ModNightcore); @@ -1135,17 +1142,10 @@ namespace osu.Game.Screens.Select public virtual bool OnPressed(KeyBindingPressEvent e) { - if (e.Repeat) - return false; - if (!this.IsCurrentScreen()) return false; switch (e.Action) { - case GlobalAction.Select: - FinaliseSelection(); - return true; - case GlobalAction.IncreaseSpeed: ChangeSpeed(0.05); return true; @@ -1155,6 +1155,16 @@ namespace osu.Game.Screens.Select return true; } + if (e.Repeat) + return false; + + switch (e.Action) + { + case GlobalAction.Select: + FinaliseSelection(); + return true; + } + return false; } From 3403789c6fef04827679b27715b653778b7d0aed Mon Sep 17 00:00:00 2001 From: Fabian van Oeffelt Date: Tue, 21 May 2024 16:11:20 +0200 Subject: [PATCH 3966/4092] Toast now only shows when speed is actually changed --- osu.Game/Screens/Select/SongSelect.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 7eb2be9100..b78134392b 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -825,8 +825,6 @@ namespace osu.Game.Screens.Select public void ChangeSpeed(double delta) { - // Mod Change from 0.95 DC to 1.0 none to 1.05 DT/NC ? - onScreenDisplay?.Display(new SpeedChangeToast(config!, delta)); if (game == null) return; ModNightcore modNc = (ModNightcore)((MultiMod)game.AvailableMods.Value[ModType.DifficultyIncrease].First(mod => mod is MultiMod multiMod && multiMod.Mods.Count(modType => modType is ModNightcore) > 0)).Mods.First(mod => mod is ModNightcore); @@ -841,6 +839,8 @@ namespace osu.Game.Screens.Select if (incompatiableModActive) return; + onScreenDisplay?.Display(new SpeedChangeToast(config!, delta)); + if (rateModActive) { ModRateAdjust mod = (ModRateAdjust)selectedMods.Value.First(mod => mod is ModRateAdjust); From 99d99cede03993796aa4f8fe5f1d344a9cfe9472 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 May 2024 11:59:34 +0800 Subject: [PATCH 3967/4092] Basic cleanup Before I gave up on attempting to fix the method. --- osu.Game/Screens/Select/SongSelect.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index b78134392b..18d5799bae 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -100,7 +100,7 @@ namespace osu.Game.Screens.Select }; [Resolved] - private OsuGameBase? game { get; set; } + private OsuGameBase game { get; set; } = null!; [Resolved] private Bindable> selectedMods { get; set; } = null!; @@ -156,7 +156,7 @@ namespace osu.Game.Screens.Select private OnScreenDisplay? onScreenDisplay { get; set; } [Resolved] - private OsuConfigManager? config { get; set; } + private OsuConfigManager config { get; set; } = null!; [BackgroundDependencyLoader(true)] private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog? manageCollectionsDialog, DifficultyRecommender? recommender, OsuConfigManager config) @@ -825,21 +825,19 @@ namespace osu.Game.Screens.Select public void ChangeSpeed(double delta) { - if (game == null) return; - ModNightcore modNc = (ModNightcore)((MultiMod)game.AvailableMods.Value[ModType.DifficultyIncrease].First(mod => mod is MultiMod multiMod && multiMod.Mods.Count(modType => modType is ModNightcore) > 0)).Mods.First(mod => mod is ModNightcore); ModDoubleTime modDt = (ModDoubleTime)((MultiMod)game.AvailableMods.Value[ModType.DifficultyIncrease].First(mod => mod is MultiMod multiMod && multiMod.Mods.Count(modType => modType is ModDoubleTime) > 0)).Mods.First(mod => mod is ModDoubleTime); ModDaycore modDc = (ModDaycore)((MultiMod)game.AvailableMods.Value[ModType.DifficultyReduction].First(mod => mod is MultiMod multiMod && multiMod.Mods.Count(modType => modType is ModDaycore) > 0)).Mods.First(mod => mod is ModDaycore); ModHalfTime modHt = (ModHalfTime)((MultiMod)game.AvailableMods.Value[ModType.DifficultyReduction].First(mod => mod is MultiMod multiMod && multiMod.Mods.Count(modType => modType is ModHalfTime) > 0)).Mods.First(mod => mod is ModHalfTime); bool rateModActive = selectedMods.Value.Count(mod => mod is ModRateAdjust) > 0; - bool incompatiableModActive = selectedMods.Value.Count(mod => modDt.IncompatibleMods.Count(incompatibleMod => (mod.GetType().IsSubclassOf(incompatibleMod) || mod.GetType() == incompatibleMod) && incompatibleMod != typeof(ModRateAdjust)) > 0) > 0; + bool incompatibleModActive = selectedMods.Value.Count(mod => modDt.IncompatibleMods.Count(incompatibleMod => (mod.GetType().IsSubclassOf(incompatibleMod) || mod.GetType() == incompatibleMod) && incompatibleMod != typeof(ModRateAdjust)) > 0) > 0; double newRate = 1d + delta; bool isPositive = delta > 0; - if (incompatiableModActive) + if (incompatibleModActive) return; - onScreenDisplay?.Display(new SpeedChangeToast(config!, delta)); + onScreenDisplay?.Display(new SpeedChangeToast(config, delta)); if (rateModActive) { @@ -912,7 +910,7 @@ namespace osu.Game.Screens.Select } else { - // If no ModRateAdjust is actived activate one + // If no ModRateAdjust is active, activate one if (isPositive) { if (!usedPitchMods) From 02a388cba6493207a170728abd607d80bfdecb3a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 May 2024 12:03:48 +0800 Subject: [PATCH 3968/4092] Fix enum not being at end (and adjust naming) --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 16 ++++++++-------- .../GlobalActionKeyBindingStrings.cs | 8 ++++---- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 4 ++-- osu.Game/Overlays/OSD/SpeedChangeToast.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 4 ++-- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index b0a1684512..09db7461d6 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -182,8 +182,8 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Shift, InputKey.F2 }, GlobalAction.SelectPreviousRandom), new KeyBinding(InputKey.F3, GlobalAction.ToggleBeatmapOptions), new KeyBinding(InputKey.BackSpace, GlobalAction.DeselectAllMods), - new KeyBinding(new[] { InputKey.Control, InputKey.Up }, GlobalAction.IncreaseSpeed), - new KeyBinding(new[] { InputKey.Control, InputKey.Down }, GlobalAction.DecreaseSpeed), + new KeyBinding(new[] { InputKey.Control, InputKey.Up }, GlobalAction.IncreaseModSpeed), + new KeyBinding(new[] { InputKey.Control, InputKey.Down }, GlobalAction.DecreaseModSpeed), }; private static IEnumerable audioControlKeyBindings => new[] @@ -411,12 +411,6 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorToggleRotateControl))] EditorToggleRotateControl, - [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.IncreaseSpeed))] - IncreaseSpeed, - - [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.DecreaseSpeed))] - DecreaseSpeed, - [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.IncreaseOffset))] IncreaseOffset, @@ -428,6 +422,12 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.StepReplayBackward))] StepReplayBackward, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.IncreaseModSpeed))] + IncreaseModSpeed, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.DecreaseModSpeed))] + DecreaseModSpeed, } public enum GlobalActionCategory diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index d0cbf52f07..18a1d3e4fe 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -370,14 +370,14 @@ namespace osu.Game.Localisation public static LocalisableString EditorToggleRotateControl => new TranslatableString(getKey(@"editor_toggle_rotate_control"), @"Toggle rotate control"); /// - /// "Increase Speed" + /// "Increase mod speed" /// - public static LocalisableString IncreaseSpeed => new TranslatableString(getKey(@"increase_speed"), @"Increase Speed"); + public static LocalisableString IncreaseModSpeed => new TranslatableString(getKey(@"increase_mod_speed"), @"Increase mod speed"); /// - /// "Decrease Speed" + /// "Decrease mod speed" /// - public static LocalisableString DecreaseSpeed => new TranslatableString(getKey(@"decrease_speed"), @"Decrease Speed"); + public static LocalisableString DecreaseModSpeed => new TranslatableString(getKey(@"decrease_mod_speed"), @"Decrease mod speed"); private static string getKey(string key) => $@"{prefix}:{key}"; } diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 572379ea2c..3b8090a4b2 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -757,11 +757,11 @@ namespace osu.Game.Overlays.Mods return true; } - case GlobalAction.IncreaseSpeed: + case GlobalAction.IncreaseModSpeed: songSelect!.ChangeSpeed(0.05); return true; - case GlobalAction.DecreaseSpeed: + case GlobalAction.DecreaseModSpeed: songSelect!.ChangeSpeed(-0.05); return true; } diff --git a/osu.Game/Overlays/OSD/SpeedChangeToast.cs b/osu.Game/Overlays/OSD/SpeedChangeToast.cs index 73ba23622b..231ef86526 100644 --- a/osu.Game/Overlays/OSD/SpeedChangeToast.cs +++ b/osu.Game/Overlays/OSD/SpeedChangeToast.cs @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.OSD public partial class SpeedChangeToast : Toast { public SpeedChangeToast(OsuConfigManager config, double delta) - : base(CommonStrings.Beatmaps, ToastStrings.SpeedChanged, config.LookupKeyBindings(GlobalAction.IncreaseSpeed) + " / " + config.LookupKeyBindings(GlobalAction.DecreaseSpeed)) + : base(CommonStrings.Beatmaps, ToastStrings.SpeedChanged, config.LookupKeyBindings(GlobalAction.IncreaseModSpeed) + " / " + config.LookupKeyBindings(GlobalAction.DecreaseModSpeed)) { } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 18d5799bae..257f6583a4 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -1144,11 +1144,11 @@ namespace osu.Game.Screens.Select switch (e.Action) { - case GlobalAction.IncreaseSpeed: + case GlobalAction.IncreaseModSpeed: ChangeSpeed(0.05); return true; - case GlobalAction.DecreaseSpeed: + case GlobalAction.DecreaseModSpeed: ChangeSpeed(-0.05); return true; } From f979200712aa0336de04e30fb2b3bebd714b5920 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 May 2024 12:06:51 +0800 Subject: [PATCH 3969/4092] Use null conditional rather than implicit not-null --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 3b8090a4b2..ad589e8fa9 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -758,11 +758,11 @@ namespace osu.Game.Overlays.Mods } case GlobalAction.IncreaseModSpeed: - songSelect!.ChangeSpeed(0.05); + songSelect?.ChangeSpeed(0.05); return true; case GlobalAction.DecreaseModSpeed: - songSelect!.ChangeSpeed(-0.05); + songSelect?.ChangeSpeed(-0.05); return true; } From d0b1ebff5a616ca89391a87699df320edbb695a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 May 2024 16:29:39 +0800 Subject: [PATCH 3970/4092] Revert "Temporary rollback of framework / SDL3" This reverts commit d7d569cf4e68acdbcc9cec844337f93bdb207a54. --- osu.Android.props | 2 +- osu.Android/AndroidJoystickSettings.cs | 76 --------------- osu.Android/AndroidMouseSettings.cs | 97 ------------------- osu.Android/OsuGameAndroid.cs | 22 ----- osu.Desktop/OsuGameDesktop.cs | 11 ++- osu.Desktop/Program.cs | 33 ++++--- .../Components/PathControlPointVisualiser.cs | 2 +- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 3 +- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 3 +- .../Screens/Ladder/LadderDragContainer.cs | 2 +- osu.Game/Database/EmptyRealmSet.cs | 5 + .../UserInterface/ExpandableSlider.cs | 8 +- .../Graphics/UserInterface/OsuSliderBar.cs | 9 +- .../Graphics/UserInterface/OsuTabControl.cs | 24 +++-- .../Graphics/UserInterface/PageTabControl.cs | 14 ++- .../UserInterface/RoundedSliderBar.cs | 5 +- .../UserInterface/ShearedSliderBar.cs | 5 +- .../UserInterfaceV2/LabelledSliderBar.cs | 4 +- .../UserInterfaceV2/SliderWithTextBoxInput.cs | 8 +- osu.Game/OsuGameBase.cs | 12 ++- .../BeatmapListingCardSizeTabControl.cs | 12 ++- ...BeatmapSearchMultipleSelectionFilterRow.cs | 4 + .../Overlays/BeatmapListing/FilterTabItem.cs | 12 ++- .../BeatmapSet/BeatmapRulesetSelector.cs | 2 +- .../OverlayPanelDisplayStyleControl.cs | 14 ++- osu.Game/Overlays/OverlayRulesetTabItem.cs | 14 ++- osu.Game/Overlays/OverlayStreamItem.cs | 12 ++- osu.Game/Overlays/OverlayTabControl.cs | 14 ++- .../Overlays/Settings/Sections/SizeSlider.cs | 3 +- .../Settings/SettingsPercentageSlider.cs | 4 +- osu.Game/Overlays/Settings/SettingsSlider.cs | 6 +- .../Toolbar/ToolbarRulesetSelector.cs | 16 +-- .../Toolbar/ToolbarRulesetTabButton.cs | 12 +++ osu.Game/Rulesets/Mods/DifficultyBindable.cs | 2 +- .../Objects/Drawables/DrawableHitObject.cs | 3 +- .../Scoring/LegacyDrainingHealthProcessor.cs | 7 ++ .../Rulesets/UI/FrameStabilityContainer.cs | 2 +- .../Timeline/TimelineTickDisplay.cs | 3 +- osu.Game/Screens/Edit/Editor.cs | 14 +-- .../IndeterminateSliderWithTextBoxInput.cs | 8 +- .../Match/Components/MatchTypePicker.cs | 11 ++- .../Play/PlayerSettings/PlayerSliderBar.cs | 4 +- osu.Game/osu.Game.csproj | 4 +- osu.iOS.props | 2 +- 44 files changed, 226 insertions(+), 304 deletions(-) delete mode 100644 osu.Android/AndroidJoystickSettings.cs delete mode 100644 osu.Android/AndroidMouseSettings.cs diff --git a/osu.Android.props b/osu.Android.props index 2d7a9d2652..e20ac2e0b7 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 b2e3fc0779..103ef50e0c 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From b25987ffe726cd5815aa8f84919180db47a88ddc Mon Sep 17 00:00:00 2001 From: Aurelian Date: Wed, 22 May 2024 11:37:55 +0200 Subject: [PATCH 3971/4092] Changed allowed mania keys, and reverted 0af32c5 --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 6 +++--- osu.Game/Rulesets/Objects/Legacy/LegacyRulesetExtensions.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 2acabe2518..e5567b2215 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -111,12 +111,12 @@ namespace osu.Game.Beatmaps.Formats { difficulty.DrainRate = Math.Clamp(difficulty.DrainRate, 0, 10); //If the mode is not Mania, clamp circle size to [0,10] - if (!beatmap.BeatmapInfo.Ruleset.OnlineID.Equals(3)) + if (beatmap.BeatmapInfo.Ruleset.OnlineID != 3) difficulty.CircleSize = Math.Clamp(difficulty.CircleSize, 0, 10); - //If it is Mania, it must be within [1,20] - dual stages with 10 keys each. + //If it is Mania, it must be within [1,18] - copying what stable does https://github.com/ppy/osu/pull/28200#discussion_r1609522988 //The lower bound should be 4, but there are ranked maps that are lower than this. else - difficulty.CircleSize = Math.Clamp(difficulty.CircleSize, 1, 20); + difficulty.CircleSize = Math.Clamp(difficulty.CircleSize, 1, 18); difficulty.OverallDifficulty = Math.Clamp(difficulty.OverallDifficulty, 0, 10); difficulty.ApproachRate = Math.Clamp(difficulty.ApproachRate, 0, 10); diff --git a/osu.Game/Rulesets/Objects/Legacy/LegacyRulesetExtensions.cs b/osu.Game/Rulesets/Objects/Legacy/LegacyRulesetExtensions.cs index 1d3416f494..2a5a11161b 100644 --- a/osu.Game/Rulesets/Objects/Legacy/LegacyRulesetExtensions.cs +++ b/osu.Game/Rulesets/Objects/Legacy/LegacyRulesetExtensions.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Objects.Legacy // It works out to under 1 game pixel and is generally not meaningful to gameplay, but is to replay playback accuracy. const float broken_gamefield_rounding_allowance = 1.00041f; - return (float)Math.Max(0.02, (1.0f - 0.7f * IBeatmapDifficultyInfo.DifficultyRange(circleSize)) / 2 * (applyFudge ? broken_gamefield_rounding_allowance : 1)); + return (float)(1.0f - 0.7f * IBeatmapDifficultyInfo.DifficultyRange(circleSize)) / 2 * (applyFudge ? broken_gamefield_rounding_allowance : 1); } public static int CalculateDifficultyPeppyStars(BeatmapDifficulty difficulty, int objectCount, int drainLength) From 97fe59cb24cc2f26ff2d099ef6da09c95d9fd6ba Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Wed, 22 May 2024 10:38:47 +0100 Subject: [PATCH 3972/4092] set `Ranked` to `true` for `OsuModTraceable` --- osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index 9671f53bea..75ad00e169 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -19,6 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Fun; public override LocalisableString Description => "Put your faith in the approach circles..."; public override double ScoreMultiplier => 1; + public override bool Ranked => true; public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles), typeof(OsuModDepth) }; From f3cae73e2ed892469e1879f834f4c8472e06cf13 Mon Sep 17 00:00:00 2001 From: Aurelian Date: Wed, 22 May 2024 13:26:00 +0200 Subject: [PATCH 3973/4092] Added tests for difficulty clamping --- .../Formats/LegacyBeatmapDecoderTest.cs | 30 +++++++++++++++++++ .../out-of-range-difficulties-mania.osu | 5 ++++ .../Resources/out-of-range-difficulties.osu | 10 +++++++ 3 files changed, 45 insertions(+) create mode 100644 osu.Game.Tests/Resources/out-of-range-difficulties-mania.osu create mode 100644 osu.Game.Tests/Resources/out-of-range-difficulties.osu diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 02432a1935..e6daba2016 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -1188,5 +1188,35 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(beatmap.HitObjects[0].GetEndTime(), Is.EqualTo(3153)); } } + + [Test] + public void TestBeatmapDifficultyIsClamped() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + + using (var resStream = TestResources.OpenResource("out-of-range-difficulties.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + var decoded = decoder.Decode(stream).Difficulty; + Assert.That(decoded.DrainRate, Is.EqualTo(10)); + Assert.That(decoded.CircleSize, Is.EqualTo(10)); + Assert.That(decoded.OverallDifficulty, Is.EqualTo(10)); + Assert.That(decoded.ApproachRate, Is.EqualTo(10)); + Assert.That(decoded.SliderMultiplier, Is.EqualTo(3.6)); + Assert.That(decoded.SliderTickRate, Is.EqualTo(8)); + } + } + [Test] + public void TestManiaBeatmapDifficultyCircleSizeClamp() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + + using (var resStream = TestResources.OpenResource("out-of-range-difficulties-mania.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + var decoded = decoder.Decode(stream).Difficulty; + Assert.That(decoded.CircleSize, Is.EqualTo(14)); + } + } } } diff --git a/osu.Game.Tests/Resources/out-of-range-difficulties-mania.osu b/osu.Game.Tests/Resources/out-of-range-difficulties-mania.osu new file mode 100644 index 0000000000..7dc2e51ad9 --- /dev/null +++ b/osu.Game.Tests/Resources/out-of-range-difficulties-mania.osu @@ -0,0 +1,5 @@ +[General] +Mode: 3 + +[Difficulty] +CircleSize:14 \ No newline at end of file diff --git a/osu.Game.Tests/Resources/out-of-range-difficulties.osu b/osu.Game.Tests/Resources/out-of-range-difficulties.osu new file mode 100644 index 0000000000..5029395614 --- /dev/null +++ b/osu.Game.Tests/Resources/out-of-range-difficulties.osu @@ -0,0 +1,10 @@ +[General] +Mode: 0 + +[Difficulty] +HPDrainRate:25 +CircleSize:25 +OverallDifficulty:25 +ApproachRate:30 +SliderMultiplier:30 +SliderTickRate:30 \ No newline at end of file From 57da4229ff621a12a43ae704bbd21884a9039d74 Mon Sep 17 00:00:00 2001 From: Fabian van Oeffelt Date: Wed, 22 May 2024 13:58:59 +0200 Subject: [PATCH 3974/4092] Add speed value to Toast --- osu.Game/Localisation/ToastStrings.cs | 4 ++-- osu.Game/Overlays/OSD/SpeedChangeToast.cs | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Localisation/ToastStrings.cs b/osu.Game/Localisation/ToastStrings.cs index 33027966dd..25899153f8 100644 --- a/osu.Game/Localisation/ToastStrings.cs +++ b/osu.Game/Localisation/ToastStrings.cs @@ -50,9 +50,9 @@ namespace osu.Game.Localisation public static LocalisableString UrlCopied => new TranslatableString(getKey(@"url_copied"), @"URL copied"); /// - /// "Speed Changed" + /// "Speed changed to" /// - public static LocalisableString SpeedChanged => new TranslatableString(getKey(@"speed_changed"), @"Speed Changed"); + public static LocalisableString SpeedChangedTo => new TranslatableString(getKey(@"speed_changed"), @"Speed changed to"); private static string getKey(string key) => $@"{prefix}:{key}"; } diff --git a/osu.Game/Overlays/OSD/SpeedChangeToast.cs b/osu.Game/Overlays/OSD/SpeedChangeToast.cs index 231ef86526..df4f825541 100644 --- a/osu.Game/Overlays/OSD/SpeedChangeToast.cs +++ b/osu.Game/Overlays/OSD/SpeedChangeToast.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 System.Threading; using osu.Game.Configuration; using osu.Game.Input.Bindings; using osu.Game.Localisation; @@ -9,8 +10,8 @@ namespace osu.Game.Overlays.OSD { public partial class SpeedChangeToast : Toast { - public SpeedChangeToast(OsuConfigManager config, double delta) - : base(CommonStrings.Beatmaps, ToastStrings.SpeedChanged, config.LookupKeyBindings(GlobalAction.IncreaseModSpeed) + " / " + config.LookupKeyBindings(GlobalAction.DecreaseModSpeed)) + public SpeedChangeToast(OsuConfigManager config, double newSpeed) + : base(CommonStrings.Beatmaps, ToastStrings.SpeedChangedTo + " " + newSpeed.ToString(Thread.CurrentThread.CurrentCulture), config.LookupKeyBindings(GlobalAction.IncreaseModSpeed) + " / " + config.LookupKeyBindings(GlobalAction.DecreaseModSpeed)) { } } From abc67ebbaccfdc3d36ef1853f766829685e1308e Mon Sep 17 00:00:00 2001 From: Fabian van Oeffelt Date: Wed, 22 May 2024 13:59:26 +0200 Subject: [PATCH 3975/4092] Fix test not running due to floating point number inaccuacy --- .../SongSelect/TestScenePlaySongSelect.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 938b858110..af8b2a7760 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -94,25 +94,25 @@ namespace osu.Game.Tests.Visual.SongSelect changeMods(); AddStep("decreasing speed without mods", () => songSelect?.ChangeSpeed(-0.05)); - AddAssert("halftime at 0.95", () => songSelect!.Mods.Value.Single() is ModHalfTime mod && mod.SpeedChange.Value == 0.95); + AddAssert("halftime at 0.95", () => songSelect!.Mods.Value.Single() is ModHalfTime mod && Math.Round(mod.SpeedChange.Value, 2) == 0.95); AddStep("decreasing speed with halftime", () => songSelect?.ChangeSpeed(-0.05)); - AddAssert("halftime at 0.9", () => songSelect!.Mods.Value.Single() is ModHalfTime mod && mod.SpeedChange.Value == 0.9); + AddAssert("halftime at 0.9", () => songSelect!.Mods.Value.Single() is ModHalfTime mod && Math.Round(mod.SpeedChange.Value, 2) == 0.9); AddStep("increasing speed with halftime", () => songSelect?.ChangeSpeed(+0.05)); - AddAssert("halftime at 0.95", () => songSelect!.Mods.Value.Single() is ModHalfTime mod && mod.SpeedChange.Value == 0.95); + AddAssert("halftime at 0.95", () => songSelect!.Mods.Value.Single() is ModHalfTime mod && Math.Round(mod.SpeedChange.Value, 2) == 0.95); AddStep("increasing speed with halftime to nomod", () => songSelect?.ChangeSpeed(+0.05)); AddAssert("no mods selected", () => songSelect!.Mods.Value.Count == 0); AddStep("increasing speed without mods", () => songSelect?.ChangeSpeed(+0.05)); - AddAssert("doubletime at 1.05", () => songSelect!.Mods.Value.Single() is ModDoubleTime mod && mod.SpeedChange.Value == 1.05); + AddAssert("doubletime at 1.05", () => songSelect!.Mods.Value.Single() is ModDoubleTime mod && Math.Round(mod.SpeedChange.Value, 2) == 1.05); AddStep("increasing speed with doubletime", () => songSelect?.ChangeSpeed(+0.05)); - AddAssert("doubletime at 1.1", () => songSelect!.Mods.Value.Single() is ModDoubleTime mod && mod.SpeedChange.Value == 1.1); + AddAssert("doubletime at 1.1", () => songSelect!.Mods.Value.Single() is ModDoubleTime mod && Math.Round(mod.SpeedChange.Value, 2) == 1.1); AddStep("decreasing speed with doubletime", () => songSelect?.ChangeSpeed(-0.05)); - AddAssert("doubletime at 1.05", () => songSelect!.Mods.Value.Single() is ModDoubleTime mod && mod.SpeedChange.Value == 1.05); + AddAssert("doubletime at 1.05", () => songSelect!.Mods.Value.Single() is ModDoubleTime mod && Math.Round(mod.SpeedChange.Value, 2) == 1.05); OsuModNightcore nc = new OsuModNightcore { @@ -120,22 +120,22 @@ namespace osu.Game.Tests.Visual.SongSelect }; changeMods(nc); AddStep("increasing speed with nightcore", () => songSelect?.ChangeSpeed(+0.05)); - AddAssert("nightcore at 1.1", () => songSelect!.Mods.Value.Single() is ModNightcore mod && mod.SpeedChange.Value == 1.1); + AddAssert("nightcore at 1.1", () => songSelect!.Mods.Value.Single() is ModNightcore mod && Math.Round(mod.SpeedChange.Value, 2) == 1.1); AddStep("decreasing speed with nightcore", () => songSelect?.ChangeSpeed(-0.05)); - AddAssert("doubletime at 1.05", () => songSelect!.Mods.Value.Single() is ModNightcore mod && mod.SpeedChange.Value == 1.05); + AddAssert("doubletime at 1.05", () => songSelect!.Mods.Value.Single() is ModNightcore mod && Math.Round(mod.SpeedChange.Value, 2) == 1.05); AddStep("decreasing speed with nightcore to nomod", () => songSelect?.ChangeSpeed(-0.05)); AddAssert("no mods selected", () => songSelect!.Mods.Value.Count == 0); AddStep("decreasing speed nomod, nightcore was selected", () => songSelect?.ChangeSpeed(-0.05)); - AddAssert("daycore at 0.95", () => songSelect!.Mods.Value.Single() is ModDaycore mod && mod.SpeedChange.Value == 0.95); + AddAssert("daycore at 0.95", () => songSelect!.Mods.Value.Single() is ModDaycore mod && Math.Round(mod.SpeedChange.Value, 2) == 0.95); AddStep("decreasing speed with daycore", () => songSelect?.ChangeSpeed(-0.05)); - AddAssert("daycore at 0.9", () => songSelect!.Mods.Value.Single() is ModDaycore mod && mod.SpeedChange.Value == 0.9); + AddAssert("daycore at 0.9", () => songSelect!.Mods.Value.Single() is ModDaycore mod && Math.Round(mod.SpeedChange.Value, 2) == 0.9); AddStep("increasing speed with daycore", () => songSelect?.ChangeSpeed(0.05)); - AddAssert("daycore at 0.95", () => songSelect!.Mods.Value.Single() is ModDaycore mod && mod.SpeedChange.Value == 0.95); + AddAssert("daycore at 0.95", () => songSelect!.Mods.Value.Single() is ModDaycore mod && Math.Round(mod.SpeedChange.Value, 2) == 0.95); OsuModDoubleTime dt = new OsuModDoubleTime { @@ -144,7 +144,7 @@ namespace osu.Game.Tests.Visual.SongSelect }; changeMods(dt); AddStep("decreasing speed from doubletime 1.02 with adjustpitch enabled", () => songSelect?.ChangeSpeed(-0.05)); - AddAssert("halftime at 0.97 with adjustpitch enabled", () => songSelect!.Mods.Value.Single() is ModHalfTime mod && mod.SpeedChange.Value == 0.97 && mod.AdjustPitch.Value); + AddAssert("halftime at 0.97 with adjustpitch enabled", () => songSelect!.Mods.Value.Single() is ModHalfTime mod && Math.Round(mod.SpeedChange.Value, 2) == 0.97 && mod.AdjustPitch.Value); OsuModHalfTime ht = new OsuModHalfTime { @@ -154,7 +154,7 @@ namespace osu.Game.Tests.Visual.SongSelect Mod[] modlist = { ht, new OsuModHardRock(), new OsuModHidden() }; changeMods(modlist); AddStep("decreasing speed from halftime 0.97 with adjustpitch enabled, HDHR enabled", () => songSelect?.ChangeSpeed(0.05)); - AddAssert("doubletime at 1.02 with adjustpitch enabled, HDHR still enabled", () => songSelect!.Mods.Value.Count(mod => (mod is ModDoubleTime modDt && modDt.AdjustPitch.Value && modDt.SpeedChange.Value == 1.02) || mod is ModHardRock || mod is ModHidden) == 3); + AddAssert("doubletime at 1.02 with adjustpitch enabled, HDHR still enabled", () => songSelect!.Mods.Value.Count(mod => (mod is ModDoubleTime modDt && modDt.AdjustPitch.Value && Math.Round(modDt.SpeedChange.Value, 2) == 1.02) || mod is ModHardRock || mod is ModHidden) == 3); changeMods(new ModWindUp()); AddStep("windup active, trying to change speed", () => songSelect?.ChangeSpeed(0.05)); From 0df634574fc5b680e168d59966a2d537a2baa160 Mon Sep 17 00:00:00 2001 From: Fabian van Oeffelt Date: Wed, 22 May 2024 13:59:33 +0200 Subject: [PATCH 3976/4092] Improve readability --- osu.Game/Screens/Select/SongSelect.cs | 223 ++++++++++++++------------ 1 file changed, 119 insertions(+), 104 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 257f6583a4..b3823d7a0f 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -823,123 +823,138 @@ namespace osu.Game.Screens.Select return false; } + private Mod getRateMod(ModType modType, Type type) + { + var modList = game.AvailableMods.Value[modType]; + var multiMod = (MultiMod)modList.First(mod => mod is MultiMod multiMod && multiMod.Mods.Count(mod2 => mod2.GetType().IsSubclassOf(type)) > 0); + var mod = multiMod.Mods.First(mod => mod.GetType().IsSubclassOf(type)); + return mod; + } + public void ChangeSpeed(double delta) { - ModNightcore modNc = (ModNightcore)((MultiMod)game.AvailableMods.Value[ModType.DifficultyIncrease].First(mod => mod is MultiMod multiMod && multiMod.Mods.Count(modType => modType is ModNightcore) > 0)).Mods.First(mod => mod is ModNightcore); - ModDoubleTime modDt = (ModDoubleTime)((MultiMod)game.AvailableMods.Value[ModType.DifficultyIncrease].First(mod => mod is MultiMod multiMod && multiMod.Mods.Count(modType => modType is ModDoubleTime) > 0)).Mods.First(mod => mod is ModDoubleTime); - ModDaycore modDc = (ModDaycore)((MultiMod)game.AvailableMods.Value[ModType.DifficultyReduction].First(mod => mod is MultiMod multiMod && multiMod.Mods.Count(modType => modType is ModDaycore) > 0)).Mods.First(mod => mod is ModDaycore); - ModHalfTime modHt = (ModHalfTime)((MultiMod)game.AvailableMods.Value[ModType.DifficultyReduction].First(mod => mod is MultiMod multiMod && multiMod.Mods.Count(modType => modType is ModHalfTime) > 0)).Mods.First(mod => mod is ModHalfTime); + ModNightcore modNc = (ModNightcore)getRateMod(ModType.DifficultyIncrease, typeof(ModNightcore)); + ModDoubleTime modDt = (ModDoubleTime)getRateMod(ModType.DifficultyIncrease, typeof(ModDoubleTime)); + ModDaycore modDc = (ModDaycore)getRateMod(ModType.DifficultyReduction, typeof(ModDaycore)); + ModHalfTime modHt = (ModHalfTime)getRateMod(ModType.DifficultyReduction, typeof(ModHalfTime)); bool rateModActive = selectedMods.Value.Count(mod => mod is ModRateAdjust) > 0; bool incompatibleModActive = selectedMods.Value.Count(mod => modDt.IncompatibleMods.Count(incompatibleMod => (mod.GetType().IsSubclassOf(incompatibleMod) || mod.GetType() == incompatibleMod) && incompatibleMod != typeof(ModRateAdjust)) > 0) > 0; - double newRate = 1d + delta; + double newRate = Math.Round(1d + delta, 2); bool isPositive = delta > 0; if (incompatibleModActive) return; - onScreenDisplay?.Display(new SpeedChangeToast(config, delta)); - - if (rateModActive) + if (!rateModActive) { - ModRateAdjust mod = (ModRateAdjust)selectedMods.Value.First(mod => mod is ModRateAdjust); + onScreenDisplay?.Display(new SpeedChangeToast(config, newRate)); - // Find current active rateAdjust mod and modify speed, enable HalfTime if necessary - newRate = mod.SpeedChange.Value + delta; - - if (newRate == 1.0) - { - lastPitchState = false; - usedPitchMods = false; - - if (mod is ModDoubleTime dtmod && dtmod.AdjustPitch.Value) lastPitchState = true; - - if (mod is ModHalfTime htmod && htmod.AdjustPitch.Value) lastPitchState = true; - - if (mod is ModNightcore || mod is ModDaycore) usedPitchMods = true; - - //Disable RateAdjustMods - selectedMods.Value = selectedMods.Value.Where(search => search is not ModRateAdjust).ToList(); - return; - } - - if (((mod is ModDoubleTime || mod is ModNightcore) && newRate < mod.SpeedChange.MinValue) - || ((mod is ModHalfTime || mod is ModDaycore) && newRate > mod.SpeedChange.MaxValue)) - { - bool adjustPitch = (mod is ModDoubleTime dtmod && dtmod.AdjustPitch.Value) || (mod is ModHalfTime htmod && htmod.AdjustPitch.Value); - - //Disable RateAdjustMods - selectedMods.Value = selectedMods.Value.Where(search => search is not ModRateAdjust).ToList(); - - ModRateAdjust? oppositeMod = null; - - switch (mod) - { - case ModDoubleTime: - modHt.AdjustPitch.Value = adjustPitch; - oppositeMod = modHt; - break; - - case ModHalfTime: - modDt.AdjustPitch.Value = adjustPitch; - oppositeMod = modDt; - break; - - case ModNightcore: - oppositeMod = modDc; - break; - - case ModDaycore: - oppositeMod = modNc; - break; - } - - if (oppositeMod == null) return; - - oppositeMod.SpeedChange.Value = newRate; - selectedMods.Value = selectedMods.Value.Append(oppositeMod).ToList(); - return; - } - - if (newRate > mod.SpeedChange.MaxValue && (mod is ModDoubleTime || mod is ModNightcore)) - newRate = mod.SpeedChange.MaxValue; - - if (newRate < mod.SpeedChange.MinValue && (mod is ModHalfTime || mod is ModDaycore)) - newRate = mod.SpeedChange.MinValue; - - mod.SpeedChange.Value = newRate; - } - else - { // If no ModRateAdjust is active, activate one - if (isPositive) - { - if (!usedPitchMods) - { - modDt.SpeedChange.Value = newRate; - modDt.AdjustPitch.Value = lastPitchState; - selectedMods.Value = selectedMods.Value.Append(modDt).ToList(); - } - else - { - modNc.SpeedChange.Value = newRate; - selectedMods.Value = selectedMods.Value.Append(modNc).ToList(); - } - } - else - { - if (!usedPitchMods) - { - modHt.SpeedChange.Value = newRate; - modHt.AdjustPitch.Value = lastPitchState; - selectedMods.Value = selectedMods.Value.Append(modHt).ToList(); - } - else - { - modDc.SpeedChange.Value = newRate; - selectedMods.Value = selectedMods.Value.Append(modDc).ToList(); - } - } + ModRateAdjust? newMod = null; + + if (isPositive && !usedPitchMods) + newMod = modDt; + + if (isPositive && usedPitchMods) + newMod = modNc; + + if (!isPositive && !usedPitchMods) + newMod = modHt; + + if (!isPositive && usedPitchMods) + newMod = modDc; + + if (!usedPitchMods && newMod is ModDoubleTime newModDt) + newModDt.AdjustPitch.Value = lastPitchState; + + if (!usedPitchMods && newMod is ModHalfTime newModHt) + newModHt.AdjustPitch.Value = lastPitchState; + + newMod!.SpeedChange.Value = newRate; + selectedMods.Value = selectedMods.Value.Append(newMod).ToList(); + return; } + + ModRateAdjust mod = (ModRateAdjust)selectedMods.Value.First(mod => mod is ModRateAdjust); + newRate = Math.Round(mod.SpeedChange.Value + delta, 2); + + // Disable RateAdjustMods if newRate is 1 + if (newRate == 1.0) + { + lastPitchState = false; + usedPitchMods = false; + + if (mod is ModDoubleTime dtmod && dtmod.AdjustPitch.Value) + lastPitchState = true; + + if (mod is ModHalfTime htmod && htmod.AdjustPitch.Value) + lastPitchState = true; + + if (mod is ModNightcore || mod is ModDaycore) + usedPitchMods = true; + + //Disable RateAdjustMods + selectedMods.Value = selectedMods.Value.Where(search => search is not ModRateAdjust).ToList(); + + onScreenDisplay?.Display(new SpeedChangeToast(config, newRate)); + + return; + } + + bool overMaxRateLimit = (mod is ModHalfTime || mod is ModDaycore) && newRate > mod.SpeedChange.MaxValue; + bool underMinRateLimit = (mod is ModDoubleTime || mod is ModNightcore) && newRate < mod.SpeedChange.MinValue; + + // Swap mod to opposite mod if newRate exceeds max/min speed values + if (overMaxRateLimit || underMinRateLimit) + { + bool adjustPitch = (mod is ModDoubleTime dtmod && dtmod.AdjustPitch.Value) || (mod is ModHalfTime htmod && htmod.AdjustPitch.Value); + + //Disable RateAdjustMods + selectedMods.Value = selectedMods.Value.Where(search => search is not ModRateAdjust).ToList(); + + ModRateAdjust? oppositeMod = null; + + switch (mod) + { + case ModDoubleTime: + modHt.AdjustPitch.Value = adjustPitch; + oppositeMod = modHt; + break; + + case ModHalfTime: + modDt.AdjustPitch.Value = adjustPitch; + oppositeMod = modDt; + break; + + case ModNightcore: + oppositeMod = modDc; + break; + + case ModDaycore: + oppositeMod = modNc; + break; + } + + if (oppositeMod == null) return; + + oppositeMod.SpeedChange.Value = newRate; + selectedMods.Value = selectedMods.Value.Append(oppositeMod).ToList(); + + onScreenDisplay?.Display(new SpeedChangeToast(config, newRate)); + + return; + } + + // Cap newRate to max/min values and change rate of current active mod + if (newRate > mod.SpeedChange.MaxValue && (mod is ModDoubleTime || mod is ModNightcore)) + newRate = mod.SpeedChange.MaxValue; + + if (newRate < mod.SpeedChange.MinValue && (mod is ModHalfTime || mod is ModDaycore)) + newRate = mod.SpeedChange.MinValue; + + mod.SpeedChange.Value = newRate; + + onScreenDisplay?.Display(new SpeedChangeToast(config, newRate)); } protected override void Dispose(bool isDisposing) From 8d02ac5e219ad064e553c560d076224683ad2651 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 May 2024 21:20:34 +0800 Subject: [PATCH 3977/4092] 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 e20ac2e0b7..8fefce3a60 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 103ef50e0c..29a0350fde 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From 66ceda1d674ce57395419b9157daec91128d403f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 May 2024 21:27:53 +0800 Subject: [PATCH 3978/4092] Update focus specifications in line with framework changes --- .../Screens/Ladder/Components/LadderEditorSettings.cs | 2 +- osu.Game/Collections/ManageCollectionsDialog.cs | 2 +- osu.Game/Graphics/UserInterface/FocusedTextBox.cs | 2 +- osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs | 2 +- osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs | 2 +- osu.Game/Overlays/AccountCreation/ScreenEntry.cs | 2 +- osu.Game/Overlays/Comments/ReplyCommentEditor.cs | 2 +- osu.Game/Overlays/Login/LoginForm.cs | 2 +- osu.Game/Overlays/Login/LoginPanel.cs | 4 ++-- osu.Game/Overlays/Login/SecondFactorAuthForm.cs | 2 +- osu.Game/Overlays/LoginOverlay.cs | 2 +- osu.Game/Overlays/Mods/AddPresetPopover.cs | 2 +- osu.Game/Overlays/Mods/EditPresetPopover.cs | 2 +- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 +- osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs | 2 +- .../Settings/Sections/Input/KeyBindingsSubsection.cs | 2 +- osu.Game/Overlays/SettingsPanel.cs | 2 +- .../Screens/Edit/Compose/Components/BeatDivisorControl.cs | 2 +- .../Compose/Components/Timeline/DifficultyPointPiece.cs | 2 +- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 2 +- osu.Game/Screens/Edit/Setup/LabelledTextBoxWithPopover.cs | 2 +- osu.Game/Screens/Edit/Setup/MetadataSection.cs | 2 +- .../Edit/Timing/IndeterminateSliderWithTextBoxInput.cs | 2 +- osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs | 6 +++--- osu.Game/Screens/Select/FilterControl.cs | 2 +- osu.Game/Screens/SelectV2/Footer/BeatmapOptionsPopover.cs | 2 +- 26 files changed, 29 insertions(+), 29 deletions(-) diff --git a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs index 9f0fa19915..08ed815253 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components editorInfo.Selected.ValueChanged += selection => { // ensure any ongoing edits are committed out to the *current* selection before changing to a new one. - GetContainingInputManager().TriggerFocusContention(null); + GetContainingFocusManager().TriggerFocusContention(null); // Required to avoid cyclic failure in BindableWithCurrent (TriggerChange called during the Current_Set process). // Arguable a framework issue but since we haven't hit it anywhere else a local workaround seems best. diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs index 16645d6796..ea663f45fe 100644 --- a/osu.Game/Collections/ManageCollectionsDialog.cs +++ b/osu.Game/Collections/ManageCollectionsDialog.cs @@ -137,7 +137,7 @@ namespace osu.Game.Collections this.ScaleTo(0.9f, exit_duration); // Ensure that textboxes commit - GetContainingInputManager()?.TriggerFocusContention(this); + GetContainingFocusManager()?.TriggerFocusContention(this); } } } diff --git a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs index 338f32f321..4ec93995a4 100644 --- a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs +++ b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs @@ -31,7 +31,7 @@ namespace osu.Game.Graphics.UserInterface if (!allowImmediateFocus) return; - Scheduler.Add(() => GetContainingInputManager().ChangeFocus(this)); + Scheduler.Add(() => GetContainingFocusManager().ChangeFocus(this)); } public new void KillFocus() => base.KillFocus(); diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs index 8b9d35e343..863ad5a173 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs @@ -57,7 +57,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 protected override void OnFocus(FocusEvent e) { base.OnFocus(e); - GetContainingInputManager().ChangeFocus(Component); + GetContainingFocusManager().ChangeFocus(Component); } protected override OsuTextBox CreateComponent() => CreateTextBox().With(t => diff --git a/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs b/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs index abd828e98f..4c16cb4951 100644 --- a/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs +++ b/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs @@ -85,7 +85,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 Current.BindValueChanged(updateTextBoxFromSlider, true); } - public bool TakeFocus() => GetContainingInputManager().ChangeFocus(textBox); + public bool TakeFocus() => GetContainingFocusManager().ChangeFocus(textBox); private bool updatingFromTextBox; diff --git a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs index f57c7d22a2..53e51e0611 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs @@ -243,7 +243,7 @@ namespace osu.Game.Overlays.AccountCreation if (nextTextBox != null) { - Schedule(() => GetContainingInputManager().ChangeFocus(nextTextBox)); + Schedule(() => GetContainingFocusManager().ChangeFocus(nextTextBox)); return true; } diff --git a/osu.Game/Overlays/Comments/ReplyCommentEditor.cs b/osu.Game/Overlays/Comments/ReplyCommentEditor.cs index 8e9e82507d..caf19829ee 100644 --- a/osu.Game/Overlays/Comments/ReplyCommentEditor.cs +++ b/osu.Game/Overlays/Comments/ReplyCommentEditor.cs @@ -39,7 +39,7 @@ namespace osu.Game.Overlays.Comments base.LoadComplete(); if (!TextBox.ReadOnly) - GetContainingInputManager().ChangeFocus(TextBox); + GetContainingFocusManager().ChangeFocus(TextBox); } protected override void OnCommit(string text) diff --git a/osu.Game/Overlays/Login/LoginForm.cs b/osu.Game/Overlays/Login/LoginForm.cs index 80dfca93d2..418721f371 100644 --- a/osu.Game/Overlays/Login/LoginForm.cs +++ b/osu.Game/Overlays/Login/LoginForm.cs @@ -150,7 +150,7 @@ namespace osu.Game.Overlays.Login protected override void OnFocus(FocusEvent e) { - Schedule(() => { GetContainingInputManager().ChangeFocus(string.IsNullOrEmpty(username.Text) ? username : password); }); + Schedule(() => { GetContainingFocusManager().ChangeFocus(string.IsNullOrEmpty(username.Text) ? username : password); }); } } } diff --git a/osu.Game/Overlays/Login/LoginPanel.cs b/osu.Game/Overlays/Login/LoginPanel.cs index a8adf4ce8c..845d20ccaf 100644 --- a/osu.Game/Overlays/Login/LoginPanel.cs +++ b/osu.Game/Overlays/Login/LoginPanel.cs @@ -186,7 +186,7 @@ namespace osu.Game.Overlays.Login } if (form != null) - ScheduleAfterChildren(() => GetContainingInputManager()?.ChangeFocus(form)); + ScheduleAfterChildren(() => GetContainingFocusManager()?.ChangeFocus(form)); }); private void updateDropdownCurrent(UserStatus? status) @@ -216,7 +216,7 @@ namespace osu.Game.Overlays.Login protected override void OnFocus(FocusEvent e) { - if (form != null) GetContainingInputManager().ChangeFocus(form); + if (form != null) GetContainingFocusManager().ChangeFocus(form); base.OnFocus(e); } } diff --git a/osu.Game/Overlays/Login/SecondFactorAuthForm.cs b/osu.Game/Overlays/Login/SecondFactorAuthForm.cs index dcd3119f33..82e328c036 100644 --- a/osu.Game/Overlays/Login/SecondFactorAuthForm.cs +++ b/osu.Game/Overlays/Login/SecondFactorAuthForm.cs @@ -141,7 +141,7 @@ namespace osu.Game.Overlays.Login protected override void OnFocus(FocusEvent e) { - Schedule(() => { GetContainingInputManager().ChangeFocus(codeTextBox); }); + Schedule(() => { GetContainingFocusManager().ChangeFocus(codeTextBox); }); } } } diff --git a/osu.Game/Overlays/LoginOverlay.cs b/osu.Game/Overlays/LoginOverlay.cs index c0aff6aae9..8dc454c0a0 100644 --- a/osu.Game/Overlays/LoginOverlay.cs +++ b/osu.Game/Overlays/LoginOverlay.cs @@ -78,7 +78,7 @@ namespace osu.Game.Overlays this.FadeIn(transition_time, Easing.OutQuint); FadeEdgeEffectTo(WaveContainer.SHADOW_OPACITY, WaveContainer.APPEAR_DURATION, Easing.Out); - ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(panel)); + ScheduleAfterChildren(() => GetContainingFocusManager().ChangeFocus(panel)); } protected override void PopOut() diff --git a/osu.Game/Overlays/Mods/AddPresetPopover.cs b/osu.Game/Overlays/Mods/AddPresetPopover.cs index b782b5d6ba..50aa5a2eb4 100644 --- a/osu.Game/Overlays/Mods/AddPresetPopover.cs +++ b/osu.Game/Overlays/Mods/AddPresetPopover.cs @@ -89,7 +89,7 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); - ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(nameTextBox)); + ScheduleAfterChildren(() => GetContainingFocusManager().ChangeFocus(nameTextBox)); nameTextBox.Current.BindValueChanged(s => { diff --git a/osu.Game/Overlays/Mods/EditPresetPopover.cs b/osu.Game/Overlays/Mods/EditPresetPopover.cs index 9554ba8ce2..8fa6b35162 100644 --- a/osu.Game/Overlays/Mods/EditPresetPopover.cs +++ b/osu.Game/Overlays/Mods/EditPresetPopover.cs @@ -136,7 +136,7 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); - ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(nameTextBox)); + ScheduleAfterChildren(() => GetContainingFocusManager().ChangeFocus(nameTextBox)); } public override bool OnPressed(KeyBindingPressEvent e) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 25293e8e20..54124e10c7 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -949,7 +949,7 @@ namespace osu.Game.Overlays.Mods RequestScroll?.Invoke(this); // Killing focus is done here because it's the only feasible place on ModSelectOverlay you can click on without triggering any action. - Scheduler.Add(() => GetContainingInputManager().ChangeFocus(null)); + Scheduler.Add(() => GetContainingFocusManager().ChangeFocus(null)); return true; } diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs index e82cebe9f4..3f6eeca10e 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs @@ -465,7 +465,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input } if (HasFocus) - GetContainingInputManager().ChangeFocus(null); + GetContainingFocusManager().ChangeFocus(null); cancelAndClearButtons.FadeOut(300, Easing.OutQuint); cancelAndClearButtons.BypassAutoSizeAxes |= Axes.Y; diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs index dd0a88bfb1..db3b56b9f0 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs @@ -106,7 +106,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input { var next = Children.SkipWhile(c => c != sender).Skip(1).FirstOrDefault(); if (next != null) - GetContainingInputManager().ChangeFocus(next); + GetContainingFocusManager().ChangeFocus(next); } } } diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index 748673035b..d5c642d24f 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -201,7 +201,7 @@ namespace osu.Game.Overlays searchTextBox.HoldFocus = false; if (searchTextBox.HasFocus) - GetContainingInputManager().ChangeFocus(null); + GetContainingFocusManager().ChangeFocus(null); } public override bool AcceptsFocus => true; diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 40b97d2137..005b96bfef 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -580,7 +580,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { base.LoadComplete(); - GetContainingInputManager().ChangeFocus(this); + GetContainingFocusManager().ChangeFocus(this); SelectAll(); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs index fc240c570b..d9084a7477 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs @@ -138,7 +138,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override void LoadComplete() { base.LoadComplete(); - ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(sliderVelocitySlider)); + ScheduleAfterChildren(() => GetContainingFocusManager().ChangeFocus(sliderVelocitySlider)); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 28841fc9e5..5c4a9faaca 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -142,7 +142,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override void LoadComplete() { base.LoadComplete(); - ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(volume)); + ScheduleAfterChildren(() => GetContainingFocusManager().ChangeFocus(volume)); } private static string? getCommonBank(IList[] relevantSamples) => relevantSamples.Select(GetBankValue).Distinct().Count() == 1 ? GetBankValue(relevantSamples.First()) : null; diff --git a/osu.Game/Screens/Edit/Setup/LabelledTextBoxWithPopover.cs b/osu.Game/Screens/Edit/Setup/LabelledTextBoxWithPopover.cs index 79288e2977..5abf40dda7 100644 --- a/osu.Game/Screens/Edit/Setup/LabelledTextBoxWithPopover.cs +++ b/osu.Game/Screens/Edit/Setup/LabelledTextBoxWithPopover.cs @@ -45,7 +45,7 @@ namespace osu.Game.Screens.Edit.Setup OnFocused?.Invoke(); base.OnFocus(e); - GetContainingInputManager().TriggerFocusContention(this); + GetContainingFocusManager().TriggerFocusContention(this); } } } diff --git a/osu.Game/Screens/Edit/Setup/MetadataSection.cs b/osu.Game/Screens/Edit/Setup/MetadataSection.cs index 752f590308..660c470204 100644 --- a/osu.Game/Screens/Edit/Setup/MetadataSection.cs +++ b/osu.Game/Screens/Edit/Setup/MetadataSection.cs @@ -73,7 +73,7 @@ namespace osu.Game.Screens.Edit.Setup base.LoadComplete(); if (string.IsNullOrEmpty(ArtistTextBox.Current.Value)) - ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(ArtistTextBox)); + ScheduleAfterChildren(() => GetContainingFocusManager().ChangeFocus(ArtistTextBox)); ArtistTextBox.Current.BindValueChanged(artist => transferIfRomanised(artist.NewValue, RomanisedArtistTextBox)); TitleTextBox.Current.BindValueChanged(title => transferIfRomanised(title.NewValue, RomanisedTitleTextBox)); diff --git a/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs index 26f374ba85..4f7a1bf589 100644 --- a/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs +++ b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs @@ -126,7 +126,7 @@ namespace osu.Game.Screens.Edit.Timing protected override void OnFocus(FocusEvent e) { base.OnFocus(e); - GetContainingInputManager().ChangeFocus(textBox); + GetContainingFocusManager().ChangeFocus(textBox); } private void updateState() diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 66bbf92e58..2f6a220c82 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -248,21 +248,21 @@ namespace osu.Game.Screens.OnlinePlay.Lounge { base.LoadComplete(); - ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(passwordTextBox)); + ScheduleAfterChildren(() => GetContainingFocusManager().ChangeFocus(passwordTextBox)); passwordTextBox.OnCommit += (_, _) => performJoin(); } private void performJoin() { lounge?.Join(room, passwordTextBox.Text, null, joinFailed); - GetContainingInputManager().TriggerFocusContention(passwordTextBox); + GetContainingFocusManager().TriggerFocusContention(passwordTextBox); } private void joinFailed(string error) => Schedule(() => { passwordTextBox.Text = string.Empty; - GetContainingInputManager().ChangeFocus(passwordTextBox); + GetContainingFocusManager().ChangeFocus(passwordTextBox); errorText.Text = error; errorText diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 73c122dda6..30eb4a8491 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -245,7 +245,7 @@ namespace osu.Game.Screens.Select searchTextBox.ReadOnly = true; searchTextBox.HoldFocus = false; if (searchTextBox.HasFocus) - GetContainingInputManager().ChangeFocus(searchTextBox); + GetContainingFocusManager().ChangeFocus(searchTextBox); } public void Activate() diff --git a/osu.Game/Screens/SelectV2/Footer/BeatmapOptionsPopover.cs b/osu.Game/Screens/SelectV2/Footer/BeatmapOptionsPopover.cs index f73be15a36..2827a9cb50 100644 --- a/osu.Game/Screens/SelectV2/Footer/BeatmapOptionsPopover.cs +++ b/osu.Game/Screens/SelectV2/Footer/BeatmapOptionsPopover.cs @@ -78,7 +78,7 @@ namespace osu.Game.Screens.SelectV2.Footer { base.LoadComplete(); - ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(this)); + ScheduleAfterChildren(() => GetContainingFocusManager().ChangeFocus(this)); beatmap.BindValueChanged(_ => Hide()); } From f7ca18b52ec4e8dbc704d58a7cfef0c301201524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 22 May 2024 15:52:57 +0200 Subject: [PATCH 3979/4092] Menial cleanups --- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index e5567b2215..8ea1d55a0d 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -105,18 +105,18 @@ namespace osu.Game.Beatmaps.Formats } /// - /// Clamp Difficulty settings to be within the normal range. + /// Ensures that all settings are within the allowed ranges. + /// See also: https://github.com/peppy/osu-stable-reference/blob/0e425c0d525ef21353c8293c235cc0621d28338b/osu!/GameplayElements/Beatmaps/Beatmap.cs#L567-L614 /// private void applyDifficultyRestrictions(BeatmapDifficulty difficulty) { difficulty.DrainRate = Math.Clamp(difficulty.DrainRate, 0, 10); - //If the mode is not Mania, clamp circle size to [0,10] - if (beatmap.BeatmapInfo.Ruleset.OnlineID != 3) - difficulty.CircleSize = Math.Clamp(difficulty.CircleSize, 0, 10); - //If it is Mania, it must be within [1,18] - copying what stable does https://github.com/ppy/osu/pull/28200#discussion_r1609522988 - //The lower bound should be 4, but there are ranked maps that are lower than this. - else - difficulty.CircleSize = Math.Clamp(difficulty.CircleSize, 1, 18); + + // mania uses "circle size" for key count, thus different allowable range + difficulty.CircleSize = beatmap.BeatmapInfo.Ruleset.OnlineID != 3 + ? Math.Clamp(difficulty.CircleSize, 0, 10) + : Math.Clamp(difficulty.CircleSize, 1, 18); + difficulty.OverallDifficulty = Math.Clamp(difficulty.OverallDifficulty, 0, 10); difficulty.ApproachRate = Math.Clamp(difficulty.ApproachRate, 0, 10); From 093be3d723ef18bcb3e7eaac1642c0006b62f922 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 May 2024 21:55:53 +0800 Subject: [PATCH 3980/4092] Cast remaining test usages to `IFocusManager` to remove obsolete notice --- .../TestSceneHitObjectSampleAdjustments.cs | 3 ++- .../Editing/TestSceneLabelledTimeSignature.cs | 9 +++++---- .../UserInterface/TestSceneModSelectOverlay.cs | 3 ++- .../TestSceneSliderWithTextBoxInput.cs | 17 +++++++++-------- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs index 1415ff4b0f..0e12ed68e4 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Collections.Generic; using Humanizer; using NUnit.Framework; +using osu.Framework.Input; using osu.Framework.Testing; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -396,7 +397,7 @@ namespace osu.Game.Tests.Visual.Editing textBox.Current.Value = bank; // force a commit via keyboard. // this is needed when testing attempting to set empty bank - which should revert to the previous value, but only on commit. - InputManager.ChangeFocus(textBox); + ((IFocusManager)InputManager).ChangeFocus(textBox); InputManager.Key(Key.Enter); }); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs b/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs index e91596b872..3d7d0797d4 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs @@ -6,6 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.Input; using osu.Framework.Testing; using osu.Game.Beatmaps.Timing; using osu.Game.Graphics.UserInterface; @@ -62,12 +63,12 @@ namespace osu.Game.Tests.Visual.Editing createLabelledTimeSignature(TimeSignature.SimpleQuadruple); AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple)); - AddStep("focus text box", () => InputManager.ChangeFocus(numeratorTextBox)); + AddStep("focus text box", () => ((IFocusManager)InputManager).ChangeFocus(numeratorTextBox)); AddStep("set numerator to 7", () => numeratorTextBox.Current.Value = "7"); AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple)); - AddStep("drop focus", () => InputManager.ChangeFocus(null)); + AddStep("drop focus", () => ((IFocusManager)InputManager).ChangeFocus(null)); AddAssert("current is 7/4", () => timeSignature.Current.Value.Equals(new TimeSignature(7))); } @@ -77,12 +78,12 @@ namespace osu.Game.Tests.Visual.Editing createLabelledTimeSignature(TimeSignature.SimpleQuadruple); AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple)); - AddStep("focus text box", () => InputManager.ChangeFocus(numeratorTextBox)); + AddStep("focus text box", () => ((IFocusManager)InputManager).ChangeFocus(numeratorTextBox)); AddStep("set numerator to 0", () => numeratorTextBox.Current.Value = "0"); AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple)); - AddStep("drop focus", () => InputManager.ChangeFocus(null)); + AddStep("drop focus", () => ((IFocusManager)InputManager).ChangeFocus(null)); AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple)); AddAssert("numerator is 4", () => numeratorTextBox.Current.Value == "4"); } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 8ddbd84890..a1452ddb31 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -10,6 +10,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input; using osu.Framework.Localisation; using osu.Framework.Testing; using osu.Framework.Utils; @@ -623,7 +624,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("press tab", () => InputManager.Key(Key.Tab)); AddAssert("search text box focused", () => modSelectOverlay.SearchTextBox.HasFocus); - AddStep("unfocus search text box externally", () => InputManager.ChangeFocus(null)); + AddStep("unfocus search text box externally", () => ((IFocusManager)InputManager).ChangeFocus(null)); AddStep("press tab", () => InputManager.Key(Key.Tab)); AddAssert("search text box focused", () => modSelectOverlay.SearchTextBox.HasFocus); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSliderWithTextBoxInput.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSliderWithTextBoxInput.cs index d23fcebae3..06b9623508 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneSliderWithTextBoxInput.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSliderWithTextBoxInput.cs @@ -5,6 +5,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Input; using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; @@ -42,7 +43,7 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep("set instantaneous to false", () => sliderWithTextBoxInput.Instantaneous = false); - AddStep("focus textbox", () => InputManager.ChangeFocus(textBox)); + AddStep("focus textbox", () => ((IFocusManager)InputManager).ChangeFocus(textBox)); AddStep("change text", () => textBox.Text = "3"); AddAssert("slider not moved", () => slider.Current.Value, () => Is.Zero); AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.Zero); @@ -61,7 +62,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("textbox changed", () => textBox.Current.Value, () => Is.EqualTo("-5")); AddAssert("current changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5)); - AddStep("focus textbox", () => InputManager.ChangeFocus(textBox)); + AddStep("focus textbox", () => ((IFocusManager)InputManager).ChangeFocus(textBox)); AddStep("set text to invalid", () => textBox.Text = "garbage"); AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5)); AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5)); @@ -71,12 +72,12 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5)); AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5)); - AddStep("focus textbox", () => InputManager.ChangeFocus(textBox)); + AddStep("focus textbox", () => ((IFocusManager)InputManager).ChangeFocus(textBox)); AddStep("set text to invalid", () => textBox.Text = "garbage"); AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5)); AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5)); - AddStep("lose focus", () => InputManager.ChangeFocus(null)); + AddStep("lose focus", () => ((IFocusManager)InputManager).ChangeFocus(null)); AddAssert("text restored", () => textBox.Text, () => Is.EqualTo("-5")); AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5)); AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5)); @@ -87,7 +88,7 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep("set instantaneous to true", () => sliderWithTextBoxInput.Instantaneous = true); - AddStep("focus textbox", () => InputManager.ChangeFocus(textBox)); + AddStep("focus textbox", () => ((IFocusManager)InputManager).ChangeFocus(textBox)); AddStep("change text", () => textBox.Text = "3"); AddAssert("slider moved", () => slider.Current.Value, () => Is.EqualTo(3)); AddAssert("current changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(3)); @@ -106,7 +107,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("textbox not changed", () => textBox.Current.Value, () => Is.EqualTo("-5")); AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5)); - AddStep("focus textbox", () => InputManager.ChangeFocus(textBox)); + AddStep("focus textbox", () => ((IFocusManager)InputManager).ChangeFocus(textBox)); AddStep("set text to invalid", () => textBox.Text = "garbage"); AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5)); AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5)); @@ -116,12 +117,12 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5)); AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5)); - AddStep("focus textbox", () => InputManager.ChangeFocus(textBox)); + AddStep("focus textbox", () => ((IFocusManager)InputManager).ChangeFocus(textBox)); AddStep("set text to invalid", () => textBox.Text = "garbage"); AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5)); AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5)); - AddStep("lose focus", () => InputManager.ChangeFocus(null)); + AddStep("lose focus", () => ((IFocusManager)InputManager).ChangeFocus(null)); AddAssert("text restored", () => textBox.Text, () => Is.EqualTo("-5")); AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5)); AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5)); From 0d13848421de5198fc438fd7f4a2420c265dc31b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 23 May 2024 00:21:19 +0900 Subject: [PATCH 3981/4092] Add whitespace to appease R# --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index e6daba2016..a4cd888823 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -1206,6 +1206,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(decoded.SliderTickRate, Is.EqualTo(8)); } } + [Test] public void TestManiaBeatmapDifficultyCircleSizeClamp() { From 73cb363eba003fd329477ab778f28610d0b9e596 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 May 2024 23:25:59 +0800 Subject: [PATCH 3982/4092] Make some more methods static --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 8ea1d55a0d..c2f4097889 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -85,7 +85,7 @@ namespace osu.Game.Beatmaps.Formats base.ParseStreamInto(stream, beatmap); - applyDifficultyRestrictions(beatmap.Difficulty); + applyDifficultyRestrictions(beatmap.Difficulty, beatmap); flushPendingPoints(); @@ -108,7 +108,7 @@ namespace osu.Game.Beatmaps.Formats /// Ensures that all settings are within the allowed ranges. /// See also: https://github.com/peppy/osu-stable-reference/blob/0e425c0d525ef21353c8293c235cc0621d28338b/osu!/GameplayElements/Beatmaps/Beatmap.cs#L567-L614 /// - private void applyDifficultyRestrictions(BeatmapDifficulty difficulty) + private static void applyDifficultyRestrictions(BeatmapDifficulty difficulty, Beatmap beatmap) { difficulty.DrainRate = Math.Clamp(difficulty.DrainRate, 0, 10); @@ -127,7 +127,7 @@ namespace osu.Game.Beatmaps.Formats /// /// Processes the beatmap such that a new combo is started the first hitobject following each break. /// - private void postProcessBreaks(Beatmap beatmap) + private static void postProcessBreaks(Beatmap beatmap) { int currentBreak = 0; bool forceNewCombo = false; @@ -183,7 +183,7 @@ namespace osu.Game.Beatmaps.Formats /// This method's intention is to restore those legacy defaults. /// See also: https://osu.ppy.sh/wiki/en/Client/File_formats/Osu_%28file_format%29 /// - private void applyLegacyDefaults(BeatmapInfo beatmapInfo) + private static void applyLegacyDefaults(BeatmapInfo beatmapInfo) { beatmapInfo.WidescreenStoryboard = false; beatmapInfo.SamplesMatchPlaybackRate = false; From c3a2a1361d045dbfd7c409fcd003ddc33dd54164 Mon Sep 17 00:00:00 2001 From: Aurelian Date: Wed, 22 May 2024 10:39:42 +0200 Subject: [PATCH 3983/4092] SliderBody's Size getter updates size to the body/path's Size --- .../Sliders/Components/SliderBodyPiece.cs | 12 +++++++++++- .../Skinning/Default/ManualSliderBody.cs | 13 ++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs index 075e9e6aa1..14d72a2d36 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs @@ -61,10 +61,20 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components body.SetVertices(vertices); } - Size = body.Size; OriginPosition = body.PathOffset; } + public override Vector2 Size + { + get + { + if (base.Size != body.Size) + Size = body.Size; + return base.Size; + } + set => base.Size = value; + } + public void RecyclePath() => body.RecyclePath(); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => body.ReceivePositionalInputAt(screenSpacePos); diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/ManualSliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Default/ManualSliderBody.cs index d171f56f40..99d954059c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/ManualSliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/ManualSliderBody.cs @@ -11,10 +11,17 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default /// public partial class ManualSliderBody : SliderBody { - public new void SetVertices(IReadOnlyList vertices) + public new void SetVertices(IReadOnlyList vertices) => base.SetVertices(vertices); + + public override Vector2 Size { - base.SetVertices(vertices); - Size = Path.Size; + get + { + if (base.Size != Path.Size) + Size = Path.Size; + return base.Size; + } + set => base.Size = value; } } } From fd9f8bd3e098ed85b84c562afd884e1191018d25 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 May 2024 01:20:58 +0800 Subject: [PATCH 3984/4092] Update framework --- osu.Android.props | 2 +- osu.Game/Collections/CollectionDropdown.cs | 7 ++++++- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 8fefce3a60..1f241c6db5 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 29a0350fde..eba9abd3b8 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From f85a1339d9e8b0a185aa48d38912ba971556bd73 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 May 2024 14:12:27 +0800 Subject: [PATCH 3985/4092] Unload daily challenge background less aggressively --- .../UpdateableOnlineBeatmapSetCover.cs | 17 +++++++++++------ osu.Game/Screens/Menu/DailyChallengeButton.cs | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/UpdateableOnlineBeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/UpdateableOnlineBeatmapSetCover.cs index 2a6b6f90e3..5bce472613 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableOnlineBeatmapSetCover.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableOnlineBeatmapSetCover.cs @@ -27,8 +27,17 @@ namespace osu.Game.Beatmaps.Drawables set => base.Masking = value; } - public UpdateableOnlineBeatmapSetCover(BeatmapSetCoverType coverType = BeatmapSetCoverType.Cover) + protected override double LoadDelay { get; } + + private readonly double timeBeforeUnload; + + protected override double TransformDuration => 400; + + public UpdateableOnlineBeatmapSetCover(BeatmapSetCoverType coverType = BeatmapSetCoverType.Cover, double timeBeforeLoad = 500, double timeBeforeUnload = 1000) { + LoadDelay = timeBeforeLoad; + this.timeBeforeUnload = timeBeforeUnload; + this.coverType = coverType; InternalChild = new Box @@ -38,12 +47,8 @@ namespace osu.Game.Beatmaps.Drawables }; } - protected override double LoadDelay => 500; - - protected override double TransformDuration => 400; - protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Func createContentFunc, double timeBeforeLoad) - => new DelayedLoadUnloadWrapper(createContentFunc, timeBeforeLoad) + => new DelayedLoadUnloadWrapper(createContentFunc, timeBeforeLoad, timeBeforeUnload) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/Menu/DailyChallengeButton.cs b/osu.Game/Screens/Menu/DailyChallengeButton.cs index 907fd04148..28b3747fbf 100644 --- a/osu.Game/Screens/Menu/DailyChallengeButton.cs +++ b/osu.Game/Screens/Menu/DailyChallengeButton.cs @@ -67,7 +67,7 @@ namespace osu.Game.Screens.Menu { Children = new Drawable[] { - cover = new UpdateableOnlineBeatmapSetCover + cover = new UpdateableOnlineBeatmapSetCover(timeBeforeLoad: 0, timeBeforeUnload: 600_000) { RelativeSizeAxes = Axes.Y, Anchor = Anchor.Centre, From 84fe3699f641b3489eda5730a2f94ea2317e53ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 May 2024 14:31:20 +0800 Subject: [PATCH 3986/4092] Reorder test steps to work better on multiple runs --- .../UserInterface/TestSceneMainMenuButton.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs index 921e28d607..5914898cb1 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs @@ -39,12 +39,7 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestDailyChallengeButton() { - AddStep("add button", () => Child = new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), _ => { }, 0, Key.D) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - ButtonSystemState = ButtonSystemState.TopLevel, - }); + AddStep("beatmap of the day not active", () => metadataClient.DailyChallengeUpdated(null)); AddStep("set up API", () => dummyAPI.HandleRequest = req => { @@ -72,12 +67,17 @@ namespace osu.Game.Tests.Visual.UserInterface } }); + AddStep("add button", () => Child = new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), _ => { }, 0, Key.D) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + ButtonSystemState = ButtonSystemState.TopLevel, + }); + AddStep("beatmap of the day active", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo { RoomID = 1234, })); - - AddStep("beatmap of the day not active", () => metadataClient.DailyChallengeUpdated(null)); } } } From 88a2f74326183605a7130c3a74cdd09ebb6a9b36 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 May 2024 16:00:23 +0800 Subject: [PATCH 3987/4092] Adjust animation --- osu.Game/Screens/Menu/DailyChallengeButton.cs | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Menu/DailyChallengeButton.cs b/osu.Game/Screens/Menu/DailyChallengeButton.cs index 28b3747fbf..3e514d0c1f 100644 --- a/osu.Game/Screens/Menu/DailyChallengeButton.cs +++ b/osu.Game/Screens/Menu/DailyChallengeButton.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Threading; +using osu.Framework.Utils; using osu.Game.Beatmaps.Drawables; using osu.Game.Beatmaps.Drawables.Cards; using osu.Game.Graphics; @@ -72,8 +73,7 @@ namespace osu.Game.Screens.Menu RelativeSizeAxes = Axes.Y, Anchor = Anchor.Centre, Origin = Anchor.Centre, - RelativePositionAxes = Axes.X, - X = -0.5f, + RelativePositionAxes = Axes.Both, }, new Box { @@ -100,18 +100,25 @@ namespace osu.Game.Screens.Menu base.LoadComplete(); info.BindValueChanged(updateDisplay, true); - FinishTransforms(true); - - cover.MoveToX(-0.5f, 10000, Easing.InOutSine) - .Then().MoveToX(0.5f, 10000, Easing.InOutSine) - .Loop(); } protected override void Update() { base.Update(); - cover.Width = 2 * background.DrawWidth; + if (cover.LatestTransformEndTime == Time.Current) + { + const double duration = 3000; + + float scale = 1 + RNG.NextSingle(); + + cover.ScaleTo(scale, duration, Easing.InOutSine) + .RotateTo(RNG.NextSingle(-4, 4) * (scale - 1), duration, Easing.InOutSine) + .MoveTo(new Vector2( + RNG.NextSingle(-0.5f, 0.5f) * (scale - 1), + RNG.NextSingle(-0.5f, 0.5f) * (scale - 1) + ), duration, Easing.InOutSine); + } } private void updateDisplay(ValueChangedEvent info) From a3639e0ce3c8cec679432624f571c71443c84978 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 May 2024 17:19:11 +0800 Subject: [PATCH 3988/4092] Remove unused field --- osu.Game/Screens/Menu/DailyChallengeButton.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/DailyChallengeButton.cs b/osu.Game/Screens/Menu/DailyChallengeButton.cs index 3e514d0c1f..7dbd90eeba 100644 --- a/osu.Game/Screens/Menu/DailyChallengeButton.cs +++ b/osu.Game/Screens/Menu/DailyChallengeButton.cs @@ -38,7 +38,6 @@ namespace osu.Game.Screens.Menu private UpdateableOnlineBeatmapSetCover cover = null!; private IBindable info = null!; - private BufferedContainer background = null!; [Resolved] private IAPIProvider api { get; set; } = null!; @@ -64,7 +63,7 @@ namespace osu.Game.Screens.Menu }); } - protected override Drawable CreateBackground(Colour4 accentColour) => background = new BufferedContainer + protected override Drawable CreateBackground(Colour4 accentColour) => new BufferedContainer { Children = new Drawable[] { From 357e55ae1f9d4d19fc7e28f508a73d2ca05e8751 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 May 2024 17:39:59 +0800 Subject: [PATCH 3989/4092] Make gradient layer a bit more dynamic --- osu.Game/Screens/Menu/DailyChallengeButton.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/DailyChallengeButton.cs b/osu.Game/Screens/Menu/DailyChallengeButton.cs index 7dbd90eeba..c365994736 100644 --- a/osu.Game/Screens/Menu/DailyChallengeButton.cs +++ b/osu.Game/Screens/Menu/DailyChallengeButton.cs @@ -39,6 +39,8 @@ namespace osu.Game.Screens.Menu private UpdateableOnlineBeatmapSetCover cover = null!; private IBindable info = null!; + private Box gradientLayer = null!; + [Resolved] private IAPIProvider api { get; set; } = null!; @@ -74,10 +76,10 @@ namespace osu.Game.Screens.Menu Origin = Anchor.Centre, RelativePositionAxes = Axes.Both, }, - new Box + gradientLayer = new Box { RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(accentColour.Opacity(0), accentColour), + Colour = ColourInfo.GradientVertical(accentColour.Opacity(0.2f), accentColour), Blending = BlendingParameters.Additive, }, new Box @@ -117,6 +119,10 @@ namespace osu.Game.Screens.Menu RNG.NextSingle(-0.5f, 0.5f) * (scale - 1), RNG.NextSingle(-0.5f, 0.5f) * (scale - 1) ), duration, Easing.InOutSine); + + gradientLayer.FadeIn(duration / 2) + .Then() + .FadeOut(duration / 2); } } From bfa23ec7a47ed41be7ffc40e46b28fd5fff2b648 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 23 May 2024 11:46:48 +0200 Subject: [PATCH 3990/4092] Fix main menu button animation not playing on initial show --- osu.Game/Screens/Menu/MainMenuButton.cs | 69 ++++++++++++++----------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenuButton.cs b/osu.Game/Screens/Menu/MainMenuButton.cs index fe8fb91766..29a661066c 100644 --- a/osu.Game/Screens/Menu/MainMenuButton.cs +++ b/osu.Game/Screens/Menu/MainMenuButton.cs @@ -179,7 +179,6 @@ namespace osu.Game.Screens.Menu { base.LoadComplete(); - background.Size = initialSize; background.Shear = new Vector2(ButtonSystem.WEDGE_WIDTH / initialSize.Y, 0); // for whatever reason, attempting to size the background "just in time" to cover the visible width @@ -189,6 +188,9 @@ namespace osu.Game.Screens.Menu // (which can exceed the [0;1] range during interpolation). backgroundContent.Width = 2 * initialSize.X; backgroundContent.Shear = -background.Shear; + + animateState(); + FinishTransforms(true); } private bool rightward; @@ -318,41 +320,46 @@ namespace osu.Game.Screens.Menu state = value; - switch (state) - { - case ButtonState.Contracted: - switch (ContractStyle) - { - default: - background.ResizeTo(Vector2.Multiply(initialSize, new Vector2(0, 1)), 500, Easing.OutExpo); - this.FadeOut(500); - break; - - case 1: - background.ResizeTo(Vector2.Multiply(initialSize, new Vector2(0, 1)), 400, Easing.InSine); - this.FadeOut(800); - break; - } - - break; - - case ButtonState.Expanded: - const int expand_duration = 500; - background.ResizeTo(initialSize, expand_duration, Easing.OutExpo); - this.FadeIn(expand_duration / 6f); - break; - - case ButtonState.Exploded: - const int explode_duration = 200; - background.ResizeTo(Vector2.Multiply(initialSize, new Vector2(2, 1)), explode_duration, Easing.OutExpo); - this.FadeOut(explode_duration / 4f * 3); - break; - } + animateState(); StateChanged?.Invoke(State); } } + private void animateState() + { + switch (state) + { + case ButtonState.Contracted: + switch (ContractStyle) + { + default: + background.ResizeTo(Vector2.Multiply(initialSize, new Vector2(0, 1)), 500, Easing.OutExpo); + this.FadeOut(500); + break; + + case 1: + background.ResizeTo(Vector2.Multiply(initialSize, new Vector2(0, 1)), 400, Easing.InSine); + this.FadeOut(800); + break; + } + + break; + + case ButtonState.Expanded: + const int expand_duration = 500; + background.ResizeTo(initialSize, expand_duration, Easing.OutExpo); + this.FadeIn(expand_duration / 6f); + break; + + case ButtonState.Exploded: + const int explode_duration = 200; + background.ResizeTo(Vector2.Multiply(initialSize, new Vector2(2, 1)), explode_duration, Easing.OutExpo); + this.FadeOut(explode_duration / 4f * 3); + break; + } + } + private ButtonSystemState buttonSystemState; public ButtonSystemState ButtonSystemState From 3411ebc4af5711c275e21b843b264bc7d96f864b Mon Sep 17 00:00:00 2001 From: Susko3 Date: Thu, 23 May 2024 12:50:06 +0200 Subject: [PATCH 3991/4092] Move `SDL3BatteryInfo` to separate file --- osu.Desktop/OsuGameDesktop.cs | 20 -------------------- osu.Desktop/SDL3BatteryInfo.cs | 27 +++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 20 deletions(-) create mode 100644 osu.Desktop/SDL3BatteryInfo.cs diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index e8783c997a..b1e1a8f118 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -22,7 +22,6 @@ using osu.Game.IPC; using osu.Game.Online.Multiplayer; using osu.Game.Performance; using osu.Game.Utils; -using SDL; namespace osu.Desktop { @@ -169,24 +168,5 @@ namespace osu.Desktop osuSchemeLinkIPCChannel?.Dispose(); archiveImportIPCChannel?.Dispose(); } - - private unsafe class SDL3BatteryInfo : BatteryInfo - { - public override double? ChargeLevel - { - get - { - int percentage; - SDL3.SDL_GetPowerInfo(null, &percentage); - - if (percentage == -1) - return null; - - return percentage / 100.0; - } - } - - public override bool OnBattery => SDL3.SDL_GetPowerInfo(null, null) == SDL_PowerState.SDL_POWERSTATE_ON_BATTERY; - } } } diff --git a/osu.Desktop/SDL3BatteryInfo.cs b/osu.Desktop/SDL3BatteryInfo.cs new file mode 100644 index 0000000000..89084b5a15 --- /dev/null +++ b/osu.Desktop/SDL3BatteryInfo.cs @@ -0,0 +1,27 @@ +// 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.Utils; +using SDL; + +namespace osu.Desktop +{ + internal unsafe class SDL3BatteryInfo : BatteryInfo + { + public override double? ChargeLevel + { + get + { + int percentage; + SDL3.SDL_GetPowerInfo(null, &percentage); + + if (percentage == -1) + return null; + + return percentage / 100.0; + } + } + + public override bool OnBattery => SDL3.SDL_GetPowerInfo(null, null) == SDL_PowerState.SDL_POWERSTATE_ON_BATTERY; + } +} From 45ed86f46cdf413d1acaa189f0faea7a10b0ad44 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Thu, 23 May 2024 12:53:33 +0200 Subject: [PATCH 3992/4092] Add back `SDL2BatteryInfo` --- osu.Desktop/OsuGameDesktop.cs | 2 +- osu.Desktop/SDL2BatteryInfo.cs | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 osu.Desktop/SDL2BatteryInfo.cs diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index b1e1a8f118..3e06dad4c5 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -160,7 +160,7 @@ namespace osu.Desktop host.Window.Title = Name; } - protected override BatteryInfo CreateBatteryInfo() => new SDL3BatteryInfo(); + protected override BatteryInfo CreateBatteryInfo() => FrameworkEnvironment.UseSDL3 ? new SDL3BatteryInfo() : new SDL2BatteryInfo(); protected override void Dispose(bool isDisposing) { diff --git a/osu.Desktop/SDL2BatteryInfo.cs b/osu.Desktop/SDL2BatteryInfo.cs new file mode 100644 index 0000000000..9ca2dc3a5c --- /dev/null +++ b/osu.Desktop/SDL2BatteryInfo.cs @@ -0,0 +1,25 @@ +// 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.Utils; + +namespace osu.Desktop +{ + internal class SDL2BatteryInfo : BatteryInfo + { + public override double? ChargeLevel + { + get + { + SDL2.SDL.SDL_GetPowerInfo(out _, out int percentage); + + if (percentage == -1) + return null; + + return percentage / 100.0; + } + } + + public override bool OnBattery => SDL2.SDL.SDL_GetPowerInfo(out _, out _) == SDL2.SDL.SDL_PowerState.SDL_POWERSTATE_ON_BATTERY; + } +} From ccf8473aae70b4898c7289c2601a07e418e23257 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Thu, 23 May 2024 13:00:18 +0200 Subject: [PATCH 3993/4092] Use appropriate `SDL_ShowSimpleMessageBox` --- osu.Desktop/Program.cs | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 23e56cdce9..0d8de8dce7 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -28,6 +28,14 @@ namespace osu.Desktop private static LegacyTcpIpcProvider? legacyIpc; + private static unsafe void showMessageBox(string title, string message) + { + if (FrameworkEnvironment.UseSDL3) + SDL3.SDL_ShowSimpleMessageBox(SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR, title, message, null); + else + SDL2.SDL.SDL_ShowSimpleMessageBox(SDL2.SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR, title, message, IntPtr.Zero); + } + [STAThread] public static void Main(string[] args) { @@ -52,19 +60,15 @@ namespace osu.Desktop // See https://www.mongodb.com/docs/realm/sdk/dotnet/compatibility/ if (windowsVersion.Major < 6 || (windowsVersion.Major == 6 && windowsVersion.Minor <= 2)) { - unsafe - { - // If users running in compatibility mode becomes more of a common thing, we may want to provide better guidance or even consider - // disabling it ourselves. - // We could also better detect compatibility mode if required: - // https://stackoverflow.com/questions/10744651/how-i-can-detect-if-my-application-is-running-under-compatibility-mode#comment58183249_10744730 - SDL3.SDL_ShowSimpleMessageBox(SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR, - "Your operating system is too old to run osu!"u8, - "This version of osu! requires at least Windows 8.1 to run.\n"u8 - + "Please upgrade your operating system or consider using an older version of osu!.\n\n"u8 - + "If you are running a newer version of windows, please check you don't have \"Compatibility mode\" turned on for osu!"u8, null); - return; - } + // If users running in compatibility mode becomes more of a common thing, we may want to provide better guidance or even consider + // disabling it ourselves. + // We could also better detect compatibility mode if required: + // https://stackoverflow.com/questions/10744651/how-i-can-detect-if-my-application-is-running-under-compatibility-mode#comment58183249_10744730 + showMessageBox("Your operating system is too old to run osu!", + "This version of osu! requires at least Windows 8.1 to run.\n" + + "Please upgrade your operating system or consider using an older version of osu!.\n\n" + + "If you are running a newer version of windows, please check you don't have \"Compatibility mode\" turned on for osu!"); + return; } setupSquirrel(); From 070668c96f4494958b0e1b4464dd4059e9ba0ec5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 23 May 2024 13:55:11 +0200 Subject: [PATCH 3994/4092] Use `ShiftPressed` instead of explicitly checking both physical keys Not only is this simpler, but it also is more correct (for explanation why, try holding both shift keys while dragging, and just releasing one of them - the previous code would briefly turn aspect ratio off). --- .../Edit/Compose/Components/SelectionBoxScaleHandle.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs index c188d23a58..12787a1c55 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs @@ -51,9 +51,9 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override bool OnKeyDown(KeyDownEvent e) { - if (IsDragged && (e.Key == Key.ShiftLeft || e.Key == Key.ShiftRight)) + if (IsDragged) { - applyScale(shouldLockAspectRatio: true); + applyScale(shouldLockAspectRatio: e.ShiftPressed); return true; } @@ -64,8 +64,8 @@ namespace osu.Game.Screens.Edit.Compose.Components { base.OnKeyUp(e); - if (IsDragged && (e.Key == Key.ShiftLeft || e.Key == Key.ShiftRight)) - applyScale(shouldLockAspectRatio: false); + if (IsDragged) + applyScale(shouldLockAspectRatio: e.ShiftPressed); } protected override void OnDragEnd(DragEndEvent e) From 9e86a08405db8a88fec2066975843b13ae831eed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 23 May 2024 14:07:43 +0200 Subject: [PATCH 3995/4092] Simplify scale origin computation --- .../Components/SelectionBoxScaleHandle.cs | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs index 12787a1c55..352a4985d6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Input.Events; using osu.Framework.Utils; @@ -102,21 +103,8 @@ namespace osu.Game.Screens.Edit.Compose.Components ? new Vector2((rawScale.X + rawScale.Y) * 0.5f) : rawScale; - scaleHandler!.Update(newScale, getOriginPosition(), getAdjustAxis()); - } - - private Vector2 getOriginPosition() - { - var quad = scaleHandler!.OriginalSurroundingQuad!.Value; - Vector2 origin = quad.TopLeft; - - if ((originalAnchor & Anchor.x0) > 0) - origin.X += quad.Width; - - if ((originalAnchor & Anchor.y0) > 0) - origin.Y += quad.Height; - - return origin; + var scaleOrigin = originalAnchor.Opposite().PositionOnQuad(scaleHandler!.OriginalSurroundingQuad!.Value); + scaleHandler!.Update(newScale, scaleOrigin, getAdjustAxis()); } private Axes getAdjustAxis() From abca62d5f0e545cd167305d292f09944a6397cd1 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Thu, 23 May 2024 14:24:42 +0200 Subject: [PATCH 3996/4092] Revert "Use appropriate `SDL_ShowSimpleMessageBox`" This reverts commit ccf8473aae70b4898c7289c2601a07e418e23257. --- osu.Desktop/Program.cs | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 0d8de8dce7..23e56cdce9 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -28,14 +28,6 @@ namespace osu.Desktop private static LegacyTcpIpcProvider? legacyIpc; - private static unsafe void showMessageBox(string title, string message) - { - if (FrameworkEnvironment.UseSDL3) - SDL3.SDL_ShowSimpleMessageBox(SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR, title, message, null); - else - SDL2.SDL.SDL_ShowSimpleMessageBox(SDL2.SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR, title, message, IntPtr.Zero); - } - [STAThread] public static void Main(string[] args) { @@ -60,15 +52,19 @@ namespace osu.Desktop // See https://www.mongodb.com/docs/realm/sdk/dotnet/compatibility/ if (windowsVersion.Major < 6 || (windowsVersion.Major == 6 && windowsVersion.Minor <= 2)) { - // If users running in compatibility mode becomes more of a common thing, we may want to provide better guidance or even consider - // disabling it ourselves. - // We could also better detect compatibility mode if required: - // https://stackoverflow.com/questions/10744651/how-i-can-detect-if-my-application-is-running-under-compatibility-mode#comment58183249_10744730 - showMessageBox("Your operating system is too old to run osu!", - "This version of osu! requires at least Windows 8.1 to run.\n" - + "Please upgrade your operating system or consider using an older version of osu!.\n\n" - + "If you are running a newer version of windows, please check you don't have \"Compatibility mode\" turned on for osu!"); - return; + unsafe + { + // If users running in compatibility mode becomes more of a common thing, we may want to provide better guidance or even consider + // disabling it ourselves. + // We could also better detect compatibility mode if required: + // https://stackoverflow.com/questions/10744651/how-i-can-detect-if-my-application-is-running-under-compatibility-mode#comment58183249_10744730 + SDL3.SDL_ShowSimpleMessageBox(SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR, + "Your operating system is too old to run osu!"u8, + "This version of osu! requires at least Windows 8.1 to run.\n"u8 + + "Please upgrade your operating system or consider using an older version of osu!.\n\n"u8 + + "If you are running a newer version of windows, please check you don't have \"Compatibility mode\" turned on for osu!"u8, null); + return; + } } setupSquirrel(); From f17f70dca7eeb60690d83435d0bdba399fdd38bd Mon Sep 17 00:00:00 2001 From: Aurelian Date: Thu, 23 May 2024 14:36:49 +0200 Subject: [PATCH 3997/4092] Changed Size to be handled by AutoSizeAxes --- .../Sliders/Components/SliderBodyPiece.cs | 13 ++----------- .../Skinning/Default/ManualSliderBody.cs | 13 +++++-------- 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs index 14d72a2d36..44c754d8f5 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Skinning.Default; @@ -41,6 +42,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private void load(OsuColour colours) { body.BorderColour = colours.Yellow; + AutoSizeAxes = Axes.Both; } private int? lastVersion; @@ -64,17 +66,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components OriginPosition = body.PathOffset; } - public override Vector2 Size - { - get - { - if (base.Size != body.Size) - Size = body.Size; - return base.Size; - } - set => base.Size = value; - } - public void RecyclePath() => body.RecyclePath(); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => body.ReceivePositionalInputAt(screenSpacePos); diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/ManualSliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Default/ManualSliderBody.cs index 99d954059c..2fc18da254 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/ManualSliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/ManualSliderBody.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics; using osuTK; namespace osu.Game.Rulesets.Osu.Skinning.Default @@ -13,15 +15,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { public new void SetVertices(IReadOnlyList vertices) => base.SetVertices(vertices); - public override Vector2 Size + [BackgroundDependencyLoader] + private void load() { - get - { - if (base.Size != Path.Size) - Size = Path.Size; - return base.Size; - } - set => base.Size = value; + AutoSizeAxes = Axes.Both; } } } From ac5c031a3a077cc5072b7f6b331623aa91681d58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 23 May 2024 14:18:29 +0200 Subject: [PATCH 3998/4092] Simplify original state management in skin selection scale handler --- .../SkinEditor/SkinSelectionScaleHandler.cs | 47 ++++++++++--------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs index 0c2ee6aae3..08df8df7e2 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs @@ -53,13 +53,8 @@ namespace osu.Game.Overlays.SkinEditor private bool allSelectedSupportManualSizing(Axes axis) => selectedItems.All(b => (b as CompositeDrawable)?.AutoSizeAxes.HasFlagFast(axis) == false); - private Drawable[]? objectsInScale; - + private Dictionary? objectsInScale; private Vector2? defaultOrigin; - private Dictionary? originalWidths; - private Dictionary? originalHeights; - private Dictionary? originalScales; - private Dictionary? originalPositions; private bool isFlippedX; private bool isFlippedY; @@ -71,12 +66,8 @@ namespace osu.Game.Overlays.SkinEditor changeHandler?.BeginChange(); - objectsInScale = selectedItems.Cast().ToArray(); - originalWidths = objectsInScale.ToDictionary(d => d, d => d.Width); - originalHeights = objectsInScale.ToDictionary(d => d, d => d.Height); - originalScales = objectsInScale.ToDictionary(d => d, d => d.Scale); - originalPositions = objectsInScale.ToDictionary(d => d, d => d.ToScreenSpace(d.OriginPosition)); - OriginalSurroundingQuad = ToLocalSpace(GeometryUtils.GetSurroundingQuad(objectsInScale.SelectMany(d => d.ScreenSpaceDrawQuad.GetVertices().ToArray()))); + objectsInScale = selectedItems.Cast().ToDictionary(d => d, d => new OriginalDrawableState(d)); + OriginalSurroundingQuad = ToLocalSpace(GeometryUtils.GetSurroundingQuad(objectsInScale.SelectMany(d => d.Key.ScreenSpaceDrawQuad.GetVertices().ToArray()))); defaultOrigin = OriginalSurroundingQuad.Value.Centre; isFlippedX = false; @@ -88,7 +79,7 @@ namespace osu.Game.Overlays.SkinEditor if (objectsInScale == null) throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!"); - Debug.Assert(originalWidths != null && originalHeights != null && originalScales != null && originalPositions != null && defaultOrigin != null && OriginalSurroundingQuad != null); + Debug.Assert(defaultOrigin != null && OriginalSurroundingQuad != null); var actualOrigin = ToScreenSpace(origin ?? defaultOrigin.Value); @@ -132,9 +123,9 @@ namespace osu.Game.Overlays.SkinEditor return; } - foreach (var b in objectsInScale) + foreach (var (b, originalState) in objectsInScale) { - UpdatePosition(b, GeometryUtils.GetScaledPosition(scale, actualOrigin, originalPositions[b])); + UpdatePosition(b, GeometryUtils.GetScaledPosition(scale, actualOrigin, originalState.ScreenSpaceOriginPosition)); var currentScale = scale; if (Precision.AlmostEquals(MathF.Abs(b.Rotation) % 180, 90)) @@ -143,15 +134,15 @@ namespace osu.Game.Overlays.SkinEditor switch (adjustAxis) { case Axes.X: - b.Width = MathF.Abs(originalWidths[b] * currentScale.X); + b.Width = MathF.Abs(originalState.Width * currentScale.X); break; case Axes.Y: - b.Height = MathF.Abs(originalHeights[b] * currentScale.Y); + b.Height = MathF.Abs(originalState.Height * currentScale.Y); break; case Axes.Both: - b.Scale = originalScales[b] * currentScale; + b.Scale = originalState.Scale * currentScale; break; } } @@ -165,11 +156,23 @@ namespace osu.Game.Overlays.SkinEditor changeHandler?.EndChange(); objectsInScale = null; - originalPositions = null; - originalWidths = null; - originalHeights = null; - originalScales = null; defaultOrigin = null; } + + private struct OriginalDrawableState + { + public float Width { get; } + public float Height { get; } + public Vector2 Scale { get; } + public Vector2 ScreenSpaceOriginPosition { get; } + + public OriginalDrawableState(Drawable drawable) + { + Width = drawable.Width; + Height = drawable.Height; + Scale = drawable.Scale; + ScreenSpaceOriginPosition = drawable.ToScreenSpace(drawable.OriginPosition); + } + } } } From f7bcccacb03358667fa40d0603bb01d3233bf591 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 23 May 2024 14:41:59 +0200 Subject: [PATCH 3999/4092] Simplify original state management in osu! scale handler --- .../Edit/OsuSelectionScaleHandler.cs | 54 ++++++++++--------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 7d5240fb69..b0299c5668 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -55,12 +55,8 @@ namespace osu.Game.Rulesets.Osu.Edit CanScaleDiagonally.Value = CanScaleX.Value && CanScaleY.Value; } - private OsuHitObject[]? objectsInScale; - + private Dictionary? objectsInScale; private Vector2? defaultOrigin; - private Dictionary? originalPositions; - private Dictionary? originalPathControlPointPositions; - private Dictionary? originalPathControlPointTypes; public override void Begin() { @@ -69,18 +65,11 @@ namespace osu.Game.Rulesets.Osu.Edit changeHandler?.BeginChange(); - objectsInScale = selectedMovableObjects.ToArray(); - OriginalSurroundingQuad = objectsInScale.Length == 1 && objectsInScale.First() is Slider slider + objectsInScale = selectedMovableObjects.ToDictionary(ho => ho, ho => new OriginalHitObjectState(ho)); + OriginalSurroundingQuad = objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider ? GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position)) - : GeometryUtils.GetSurroundingQuad(objectsInScale); + : GeometryUtils.GetSurroundingQuad(objectsInScale.Keys); defaultOrigin = OriginalSurroundingQuad.Value.Centre; - originalPositions = objectsInScale.ToDictionary(obj => obj, obj => obj.Position); - originalPathControlPointPositions = objectsInScale.OfType().ToDictionary( - obj => obj, - obj => obj.Path.ControlPoints.Select(point => point.Position).ToArray()); - originalPathControlPointTypes = objectsInScale.OfType().ToDictionary( - obj => obj, - obj => obj.Path.ControlPoints.Select(p => p.Type).ToArray()); } public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both) @@ -88,22 +77,26 @@ namespace osu.Game.Rulesets.Osu.Edit if (objectsInScale == null) throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!"); - Debug.Assert(originalPositions != null && originalPathControlPointPositions != null && defaultOrigin != null && originalPathControlPointTypes != null && OriginalSurroundingQuad != null); + Debug.Assert(defaultOrigin != null && OriginalSurroundingQuad != null); Vector2 actualOrigin = origin ?? defaultOrigin.Value; // for the time being, allow resizing of slider paths only if the slider is // the only hit object selected. with a group selection, it's likely the user // is not looking to change the duration of the slider but expand the whole pattern. - if (objectsInScale.Length == 1 && objectsInScale.First() is Slider slider) - scaleSlider(slider, scale, originalPathControlPointPositions[slider], originalPathControlPointTypes[slider]); + if (objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider) + { + var originalInfo = objectsInScale[slider]; + Debug.Assert(originalInfo.PathControlPointPositions != null && originalInfo.PathControlPointTypes != null); + scaleSlider(slider, scale, originalInfo.PathControlPointPositions, originalInfo.PathControlPointTypes); + } else { scale = getClampedScale(OriginalSurroundingQuad.Value, actualOrigin, scale); - foreach (var ho in objectsInScale) + foreach (var (ho, originalState) in objectsInScale) { - ho.Position = GeometryUtils.GetScaledPosition(scale, actualOrigin, originalPositions[ho]); + ho.Position = GeometryUtils.GetScaledPosition(scale, actualOrigin, originalState.Position); } } @@ -119,9 +112,6 @@ namespace osu.Game.Rulesets.Osu.Edit objectsInScale = null; OriginalSurroundingQuad = null; - originalPositions = null; - originalPathControlPointPositions = null; - originalPathControlPointTypes = null; defaultOrigin = null; } @@ -193,7 +183,7 @@ namespace osu.Game.Rulesets.Osu.Edit private void moveSelectionInBounds() { - Quad quad = GeometryUtils.GetSurroundingQuad(objectsInScale!); + Quad quad = GeometryUtils.GetSurroundingQuad(objectsInScale!.Keys); Vector2 delta = Vector2.Zero; @@ -207,8 +197,22 @@ namespace osu.Game.Rulesets.Osu.Edit if (quad.BottomRight.Y > OsuPlayfield.BASE_SIZE.Y) delta.Y -= quad.BottomRight.Y - OsuPlayfield.BASE_SIZE.Y; - foreach (var h in objectsInScale!) + foreach (var (h, _) in objectsInScale!) h.Position += delta; } + + private struct OriginalHitObjectState + { + public Vector2 Position { get; } + public Vector2[]? PathControlPointPositions { get; } + public PathType?[]? PathControlPointTypes { get; } + + public OriginalHitObjectState(OsuHitObject hitObject) + { + Position = hitObject.Position; + PathControlPointPositions = (hitObject as IHasPath)?.Path.ControlPoints.Select(p => p.Position).ToArray(); + PathControlPointTypes = (hitObject as IHasPath)?.Path.ControlPoints.Select(p => p.Type).ToArray(); + } + } } } From 3e34b2d37ed895f9eb707be9921964795c2e750e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 23 May 2024 14:56:08 +0200 Subject: [PATCH 4000/4092] Bring back clamping in osu! scale handler Being able to flip doesn't really feel all that good and `master` was already clamping, so let's just bring that back for now. Flipping can be reconsidered in a follow-up if it actually can be made to behave well. --- osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index b0299c5668..75b404684f 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; +using osu.Framework.Logging; using osu.Framework.Utils; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; @@ -120,6 +121,8 @@ namespace osu.Game.Rulesets.Osu.Edit private void scaleSlider(Slider slider, Vector2 scale, Vector2[] originalPathPositions, PathType?[] originalPathTypes) { + scale = Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON)); + // Maintain the path types in case they were defaulted to bezier at some point during scaling for (int i = 0; i < slider.Path.ControlPoints.Count; i++) { @@ -178,7 +181,8 @@ namespace osu.Game.Rulesets.Osu.Edit if (!Precision.AlmostEquals(selectionQuad.BottomRight.Y - origin.Y, 0)) scale.Y = selectionQuad.BottomRight.Y - origin.Y < 0 ? MathHelper.Clamp(scale.Y, br2.Y, br1.Y) : MathHelper.Clamp(scale.Y, br1.Y, br2.Y); - return scale; + Logger.Log($"scale = {scale}"); + return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON)); } private void moveSelectionInBounds() From 128029e2af5bd0d23db7935761807487ded1eb15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 23 May 2024 15:08:43 +0200 Subject: [PATCH 4001/4092] Fix aspect ratio lock applying when shift pressed on a non-corner anchor It doesn't make sense and it wasn't doing the right thing. --- .../Edit/Compose/Components/SelectionBoxScaleHandle.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs index 352a4985d6..eca0c08ba1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Input.Events; using osu.Framework.Utils; @@ -47,14 +48,14 @@ namespace osu.Game.Screens.Edit.Compose.Components rawScale = convertDragEventToScaleMultiplier(e); - applyScale(shouldLockAspectRatio: e.ShiftPressed); + applyScale(shouldLockAspectRatio: isCornerAnchor(originalAnchor) && e.ShiftPressed); } protected override bool OnKeyDown(KeyDownEvent e) { if (IsDragged) { - applyScale(shouldLockAspectRatio: e.ShiftPressed); + applyScale(shouldLockAspectRatio: isCornerAnchor(originalAnchor) && e.ShiftPressed); return true; } @@ -66,7 +67,7 @@ namespace osu.Game.Screens.Edit.Compose.Components base.OnKeyUp(e); if (IsDragged) - applyScale(shouldLockAspectRatio: e.ShiftPressed); + applyScale(shouldLockAspectRatio: isCornerAnchor(originalAnchor) && e.ShiftPressed); } protected override void OnDragEnd(DragEndEvent e) @@ -123,5 +124,7 @@ namespace osu.Game.Screens.Edit.Compose.Components return Axes.Both; } } + + private bool isCornerAnchor(Anchor anchor) => !anchor.HasFlagFast(Anchor.x1) && !anchor.HasFlagFast(Anchor.y1); } } From d8ba95f87712a7e407a8e28c9c41cc8035b9826a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 23 May 2024 15:13:42 +0200 Subject: [PATCH 4002/4092] Remove leftover log whooops. --- osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 75b404684f..af03c4d925 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -9,7 +9,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; -using osu.Framework.Logging; using osu.Framework.Utils; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; @@ -181,7 +180,6 @@ namespace osu.Game.Rulesets.Osu.Edit if (!Precision.AlmostEquals(selectionQuad.BottomRight.Y - origin.Y, 0)) scale.Y = selectionQuad.BottomRight.Y - origin.Y < 0 ? MathHelper.Clamp(scale.Y, br2.Y, br1.Y) : MathHelper.Clamp(scale.Y, br1.Y, br2.Y); - Logger.Log($"scale = {scale}"); return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON)); } From b1c7afd75b273c1f7f12738e1a48db7c2c956002 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 23 May 2024 23:45:04 +0900 Subject: [PATCH 4003/4092] Move to ctor --- .../Blueprints/Sliders/Components/SliderBodyPiece.cs | 11 ++++++----- .../Skinning/Default/ManualSliderBody.cs | 8 +++----- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs index 44c754d8f5..12626a77ed 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs @@ -28,21 +28,22 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public SliderBodyPiece() { - InternalChild = body = new ManualSliderBody - { - AccentColour = Color4.Transparent - }; + AutoSizeAxes = Axes.Both; // SliderSelectionBlueprint relies on calling ReceivePositionalInputAt on this drawable to determine whether selection should occur. // Without AlwaysPresent, a movement in a parent container (ie. the editor composer area resizing) could cause incorrect input handling. AlwaysPresent = true; + + InternalChild = body = new ManualSliderBody + { + AccentColour = Color4.Transparent + }; } [BackgroundDependencyLoader] private void load(OsuColour colours) { body.BorderColour = colours.Yellow; - AutoSizeAxes = Axes.Both; } private int? lastVersion; diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/ManualSliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Default/ManualSliderBody.cs index 2fc18da254..127d13730a 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/ManualSliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/ManualSliderBody.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osuTK; @@ -13,12 +12,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default /// public partial class ManualSliderBody : SliderBody { - public new void SetVertices(IReadOnlyList vertices) => base.SetVertices(vertices); - - [BackgroundDependencyLoader] - private void load() + public ManualSliderBody() { AutoSizeAxes = Axes.Both; } + + public new void SetVertices(IReadOnlyList vertices) => base.SetVertices(vertices); } } From d47c4cb47946877f7ba00bfe0d497137638f8230 Mon Sep 17 00:00:00 2001 From: Aurelian Date: Fri, 24 May 2024 06:28:19 +0200 Subject: [PATCH 4004/4092] Test for scaling slider flat --- .../Editor/TestSliderScaling.cs | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs index 021fdba225..157a08df46 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs @@ -68,6 +68,119 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("slider length shrunk", () => slider.Path.Distance < distanceBefore); } + [Test] + [Timeout(4000)] //Catches crashes in other threads, but not ideal. Hopefully there is a improvement to this. + public void TestScalingSliderFlat( + [Values(0, 1, 2, 3)] int type_int + ) + { + Slider slider = null; + + switch (type_int) + { + case 0: + AddStep("Add linear slider", () => + { + slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) }; + + PathControlPoint[] points = + { + new PathControlPoint(new Vector2(0), PathType.LINEAR), + new PathControlPoint(new Vector2(50, 100)), + }; + + slider.Path = new SliderPath(points); + EditorBeatmap.Add(slider); + }); + break; + case 1: + AddStep("Add perfect curve slider", () => + { + slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) }; + + PathControlPoint[] points = + { + new PathControlPoint(new Vector2(0), PathType.PERFECT_CURVE), + new PathControlPoint(new Vector2(50, 25)), + new PathControlPoint(new Vector2(25, 100)), + }; + + slider.Path = new SliderPath(points); + EditorBeatmap.Add(slider); + }); + break; + case 2: + AddStep("Add bezier slider", () => + { + slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) }; + + PathControlPoint[] points = + { + new PathControlPoint(new Vector2(0), PathType.BEZIER), + new PathControlPoint(new Vector2(50, 25)), + new PathControlPoint(new Vector2(25, 80)), + new PathControlPoint(new Vector2(40, 100)), + }; + + slider.Path = new SliderPath(points); + EditorBeatmap.Add(slider); + }); + break; + AddStep("Add perfect curve slider", () => + { + slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) }; + + PathControlPoint[] points = + { + new PathControlPoint(new Vector2(0), PathType.PERFECT_CURVE), + new PathControlPoint(new Vector2(50, 25)), + new PathControlPoint(new Vector2(25, 100)), + }; + + slider.Path = new SliderPath(points); + EditorBeatmap.Add(slider); + }); + break; + case 3: + AddStep("Add catmull slider", () => + { + slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) }; + + PathControlPoint[] points = + { + new PathControlPoint(new Vector2(0), PathType.CATMULL), + new PathControlPoint(new Vector2(50, 25)), + new PathControlPoint(new Vector2(25, 80)), + new PathControlPoint(new Vector2(40, 100)), + }; + + slider.Path = new SliderPath(points); + EditorBeatmap.Add(slider); + }); + break; + } + + AddAssert("ensure object placed", () => EditorBeatmap.HitObjects.Count == 1); + + moveMouse(new Vector2(300)); + AddStep("select slider", () => InputManager.Click(MouseButton.Left)); + AddStep("slider is valid", () => slider.Path.GetSegmentEnds()); //To run ensureValid(); + + SelectionBoxDragHandle dragHandle = null!; + AddStep("store drag handle", () => dragHandle = Editor.ChildrenOfType().Skip(1).First()); + AddAssert("is dragHandle not null", () => dragHandle != null); + + AddStep("move mouse to handle", () => InputManager.MoveMouseTo(dragHandle)); + AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left)); + moveMouse(new Vector2(0, 300)); + AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left)); + + AddStep("move mouse to handle", () => InputManager.MoveMouseTo(dragHandle)); + AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left)); + moveMouse(new Vector2(0, 300)); //Should crash here if broken, although doesn't count as failed... + AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left)); + } + private void moveMouse(Vector2 pos) => AddStep($"move mouse to {pos}", () => InputManager.MoveMouseTo(playfield.ToScreenSpace(pos))); } From d948e0fc5c3af124d56b6d8bb80d2d904f73a704 Mon Sep 17 00:00:00 2001 From: Aurelian Date: Fri, 24 May 2024 08:26:17 +0200 Subject: [PATCH 4005/4092] Nearly straight sliders are treated as linear --- osu.Game/Rulesets/Objects/SliderPath.cs | 35 +++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index e8e769e3fa..b0a5d02e71 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -269,6 +269,37 @@ namespace osu.Game.Rulesets.Objects pathCache.Validate(); } + /// + /// Checks if the array of vectors is almost straight. + /// + /// + /// The angle is first obtained based on the farthest vector from the first, + /// then we find the angle of each vector from the first, + /// and calculate the distance between the two angle vectors. + /// We than scale this distance to the distance from the first vector + /// (or by 10 if the distance is smaller), + /// and if it is greater than acceptableDifference, we return false. + /// + private static bool isAlmostStraight(Vector2[] vectors, float acceptableDifference = 0.1f) + { + if (vectors.Length <= 2 || vectors.All(x => x == vectors.First())) return true; + + Vector2 first = vectors.First(); + Vector2 farthest = vectors.MaxBy(x => Vector2.Distance(first, x)); + + Vector2 angle = Vector2.Normalize(farthest - first); + foreach (Vector2 vector in vectors) + { + if (vector == first) + continue; + + if (Math.Max(10.0f, Vector2.Distance(vector, first)) * Vector2.Distance(Vector2.Normalize(vector - first), angle) > acceptableDifference) + return false; + } + + return true; + } + private void calculatePath() { calculatedPath.Clear(); @@ -293,6 +324,10 @@ namespace osu.Game.Rulesets.Objects var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1); var segmentType = ControlPoints[start].Type ?? PathType.LINEAR; + //If a segment is almost straight, treat it as linear. + if (segmentType != PathType.LINEAR && isAlmostStraight(segmentVertices.ToArray())) + segmentType = PathType.LINEAR; + // No need to calculate path when there is only 1 vertex if (segmentVertices.Length == 1) calculatedPath.Add(segmentVertices[0]); From 481883801fbdb8df3303d2cbec05fbe52cfdbdc9 Mon Sep 17 00:00:00 2001 From: Aurelian Date: Fri, 24 May 2024 08:55:10 +0200 Subject: [PATCH 4006/4092] Removed duplicate unreachable code --- .../Editor/TestSliderScaling.cs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs index 157a08df46..44f85837dc 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs @@ -126,21 +126,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor EditorBeatmap.Add(slider); }); break; - AddStep("Add perfect curve slider", () => - { - slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) }; - - PathControlPoint[] points = - { - new PathControlPoint(new Vector2(0), PathType.PERFECT_CURVE), - new PathControlPoint(new Vector2(50, 25)), - new PathControlPoint(new Vector2(25, 100)), - }; - - slider.Path = new SliderPath(points); - EditorBeatmap.Add(slider); - }); - break; case 3: AddStep("Add catmull slider", () => { From fff52be59a9bc076aa30d7c9045dcf3acd1aff58 Mon Sep 17 00:00:00 2001 From: Aurelian Date: Fri, 24 May 2024 09:30:24 +0200 Subject: [PATCH 4007/4092] Addressed code quality issues --- .../Editor/TestSliderScaling.cs | 21 +++++++++++-------- osu.Game/Rulesets/Objects/SliderPath.cs | 5 +++-- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs index 44f85837dc..99694f82d1 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs @@ -71,12 +71,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor [Test] [Timeout(4000)] //Catches crashes in other threads, but not ideal. Hopefully there is a improvement to this. public void TestScalingSliderFlat( - [Values(0, 1, 2, 3)] int type_int + [Values(0, 1, 2, 3)] int typeInt ) { - Slider slider = null; + Slider slider = null!; - switch (type_int) + switch (typeInt) { case 0: AddStep("Add linear slider", () => @@ -93,6 +93,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor EditorBeatmap.Add(slider); }); break; + case 1: AddStep("Add perfect curve slider", () => { @@ -109,14 +110,15 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor EditorBeatmap.Add(slider); }); break; - case 2: - AddStep("Add bezier slider", () => + + case 3: + AddStep("Add catmull slider", () => { slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) }; PathControlPoint[] points = { - new PathControlPoint(new Vector2(0), PathType.BEZIER), + new PathControlPoint(new Vector2(0), PathType.CATMULL), new PathControlPoint(new Vector2(50, 25)), new PathControlPoint(new Vector2(25, 80)), new PathControlPoint(new Vector2(40, 100)), @@ -126,14 +128,15 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor EditorBeatmap.Add(slider); }); break; - case 3: - AddStep("Add catmull slider", () => + + default: + AddStep("Add bezier slider", () => { slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) }; PathControlPoint[] points = { - new PathControlPoint(new Vector2(0), PathType.CATMULL), + new PathControlPoint(new Vector2(0), PathType.BEZIER), new PathControlPoint(new Vector2(50, 25)), new PathControlPoint(new Vector2(25, 80)), new PathControlPoint(new Vector2(40, 100)), diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index b0a5d02e71..ddea23034c 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -274,9 +274,9 @@ namespace osu.Game.Rulesets.Objects /// /// /// The angle is first obtained based on the farthest vector from the first, - /// then we find the angle of each vector from the first, + /// then we find the angle of each vector from the first, /// and calculate the distance between the two angle vectors. - /// We than scale this distance to the distance from the first vector + /// We than scale this distance to the distance from the first vector /// (or by 10 if the distance is smaller), /// and if it is greater than acceptableDifference, we return false. /// @@ -288,6 +288,7 @@ namespace osu.Game.Rulesets.Objects Vector2 farthest = vectors.MaxBy(x => Vector2.Distance(first, x)); Vector2 angle = Vector2.Normalize(farthest - first); + foreach (Vector2 vector in vectors) { if (vector == first) From a80dbba9d0d4fc836b899b1825692170c1c843d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 24 May 2024 10:21:40 +0200 Subject: [PATCH 4008/4092] Update to not use obsoleted method --- osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs | 2 +- osu.Game/Screens/Edit/Editor.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs b/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs index 5930c077a4..8b6f31d599 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs @@ -65,7 +65,7 @@ namespace osu.Game.Tests.Visual.Editing // It's important values are committed immediately on focus loss so the editor exit sequence detects them. AddAssert("value immediately changed on focus loss", () => { - InputManager.TriggerFocusContention(metadataSection); + ((IFocusManager)InputManager).TriggerFocusContention(metadataSection); return editorBeatmap.Metadata.Artist; }, () => Is.EqualTo("Example ArtistExample Artist")); } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 47b2a53607..55ab03a590 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -723,7 +723,7 @@ namespace osu.Game.Screens.Edit // // This is important to ensure that if the user is still editing a textbox, it will commit // (and potentially block the exit procedure for save). - GetContainingInputManager().TriggerFocusContention(this); + GetContainingFocusManager().TriggerFocusContention(this); if (!ExitConfirmed) { From 807d982a721ec043990d1871f41316763d3b5b15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 24 May 2024 10:24:50 +0200 Subject: [PATCH 4009/4092] Move workaround to subscreen --- osu.Game/Screens/Edit/Editor.cs | 6 +----- osu.Game/Screens/Edit/EditorScreen.cs | 5 +++++ osu.Game/Screens/Edit/Setup/SetupScreen.cs | 12 ++++++++++++ 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 55ab03a590..07c32983f5 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -719,11 +719,7 @@ namespace osu.Game.Screens.Edit public override bool OnExiting(ScreenExitEvent e) { - // Before exiting, trigger a focus loss. - // - // This is important to ensure that if the user is still editing a textbox, it will commit - // (and potentially block the exit procedure for save). - GetContainingFocusManager().TriggerFocusContention(this); + currentScreen?.OnExiting(e); if (!ExitConfirmed) { diff --git a/osu.Game/Screens/Edit/EditorScreen.cs b/osu.Game/Screens/Edit/EditorScreen.cs index 3bc870b898..a795b310a2 100644 --- a/osu.Game/Screens/Edit/EditorScreen.cs +++ b/osu.Game/Screens/Edit/EditorScreen.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Screens; namespace osu.Game.Screens.Edit { @@ -37,6 +38,10 @@ namespace osu.Game.Screens.Edit protected override void PopOut() => this.FadeOut(); + public virtual void OnExiting(ScreenExitEvent e) + { + } + #region Clipboard operations public BindableBool CanCut { get; } = new BindableBool(); diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 266ea1f929..5345db0a4f 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Screens; using osu.Game.Graphics.Containers; using osu.Game.Overlays; @@ -55,6 +56,17 @@ namespace osu.Game.Screens.Edit.Setup })); } + public override void OnExiting(ScreenExitEvent e) + { + base.OnExiting(e); + + // Before exiting, trigger a focus loss. + // + // This is important to ensure that if the user is still editing a textbox, it will commit + // (and potentially block the exit procedure for save). + GetContainingFocusManager().TriggerFocusContention(this); + } + private partial class SetupScreenSectionsContainer : SectionsContainer { protected override UserTrackingScrollContainer CreateScrollContainer() From 7255cc3344e75ec0e0eb1fd99eab94da1f50f784 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 24 May 2024 11:25:29 +0200 Subject: [PATCH 4010/4092] Fix tests dying on a nullref --- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 5345db0a4f..7a7907d08a 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -64,7 +64,7 @@ namespace osu.Game.Screens.Edit.Setup // // This is important to ensure that if the user is still editing a textbox, it will commit // (and potentially block the exit procedure for save). - GetContainingFocusManager().TriggerFocusContention(this); + GetContainingFocusManager()?.TriggerFocusContention(this); } private partial class SetupScreenSectionsContainer : SectionsContainer From 9045ec24abc5e844da7cc646fcc7fcbb620e75bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 24 May 2024 12:04:09 +0200 Subject: [PATCH 4011/4092] Rewrite test --- .../SongSelect/TestScenePlaySongSelect.cs | 64 ++++++++++--------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index af8b2a7760..7f0c209215 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -93,49 +93,49 @@ namespace osu.Game.Tests.Visual.SongSelect createSongSelect(); changeMods(); - AddStep("decreasing speed without mods", () => songSelect?.ChangeSpeed(-0.05)); - AddAssert("halftime at 0.95", () => songSelect!.Mods.Value.Single() is ModHalfTime mod && Math.Round(mod.SpeedChange.Value, 2) == 0.95); + AddStep("decrease speed", () => songSelect?.ChangeSpeed(-0.05)); + AddAssert("half time activated at 0.95x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005)); - AddStep("decreasing speed with halftime", () => songSelect?.ChangeSpeed(-0.05)); - AddAssert("halftime at 0.9", () => songSelect!.Mods.Value.Single() is ModHalfTime mod && Math.Round(mod.SpeedChange.Value, 2) == 0.9); + AddStep("decrease speed", () => songSelect?.ChangeSpeed(-0.05)); + AddAssert("half time speed changed to 0.9x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.9).Within(0.005)); - AddStep("increasing speed with halftime", () => songSelect?.ChangeSpeed(+0.05)); - AddAssert("halftime at 0.95", () => songSelect!.Mods.Value.Single() is ModHalfTime mod && Math.Round(mod.SpeedChange.Value, 2) == 0.95); + AddStep("increase speed", () => songSelect?.ChangeSpeed(0.05)); + AddAssert("half time speed changed to 0.95x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005)); - AddStep("increasing speed with halftime to nomod", () => songSelect?.ChangeSpeed(+0.05)); + AddStep("increase speed", () => songSelect?.ChangeSpeed(0.05)); AddAssert("no mods selected", () => songSelect!.Mods.Value.Count == 0); - AddStep("increasing speed without mods", () => songSelect?.ChangeSpeed(+0.05)); - AddAssert("doubletime at 1.05", () => songSelect!.Mods.Value.Single() is ModDoubleTime mod && Math.Round(mod.SpeedChange.Value, 2) == 1.05); + AddStep("increase speed", () => songSelect?.ChangeSpeed(0.05)); + AddAssert("double time activated at 1.05x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.05).Within(0.005)); - AddStep("increasing speed with doubletime", () => songSelect?.ChangeSpeed(+0.05)); - AddAssert("doubletime at 1.1", () => songSelect!.Mods.Value.Single() is ModDoubleTime mod && Math.Round(mod.SpeedChange.Value, 2) == 1.1); + AddStep("increase speed", () => songSelect?.ChangeSpeed(0.05)); + AddAssert("double time speed changed to 1.1x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.1).Within(0.005)); - AddStep("decreasing speed with doubletime", () => songSelect?.ChangeSpeed(-0.05)); - AddAssert("doubletime at 1.05", () => songSelect!.Mods.Value.Single() is ModDoubleTime mod && Math.Round(mod.SpeedChange.Value, 2) == 1.05); + AddStep("decrease speed", () => songSelect?.ChangeSpeed(-0.05)); + AddAssert("double time speed changed to 1.05x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.05).Within(0.005)); OsuModNightcore nc = new OsuModNightcore { SpeedChange = { Value = 1.05 } }; changeMods(nc); - AddStep("increasing speed with nightcore", () => songSelect?.ChangeSpeed(+0.05)); - AddAssert("nightcore at 1.1", () => songSelect!.Mods.Value.Single() is ModNightcore mod && Math.Round(mod.SpeedChange.Value, 2) == 1.1); + AddStep("increase speed", () => songSelect?.ChangeSpeed(0.05)); + AddAssert("nightcore speed changed to 1.1x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.1).Within(0.005)); - AddStep("decreasing speed with nightcore", () => songSelect?.ChangeSpeed(-0.05)); - AddAssert("doubletime at 1.05", () => songSelect!.Mods.Value.Single() is ModNightcore mod && Math.Round(mod.SpeedChange.Value, 2) == 1.05); + AddStep("decrease speed", () => songSelect?.ChangeSpeed(-0.05)); + AddAssert("nightcore speed changed to 1.05x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.05).Within(0.005)); - AddStep("decreasing speed with nightcore to nomod", () => songSelect?.ChangeSpeed(-0.05)); + AddStep("decrease speed", () => songSelect?.ChangeSpeed(-0.05)); AddAssert("no mods selected", () => songSelect!.Mods.Value.Count == 0); - AddStep("decreasing speed nomod, nightcore was selected", () => songSelect?.ChangeSpeed(-0.05)); - AddAssert("daycore at 0.95", () => songSelect!.Mods.Value.Single() is ModDaycore mod && Math.Round(mod.SpeedChange.Value, 2) == 0.95); + AddStep("decrease speed", () => songSelect?.ChangeSpeed(-0.05)); + AddAssert("daycore activated at 0.95x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005)); - AddStep("decreasing speed with daycore", () => songSelect?.ChangeSpeed(-0.05)); - AddAssert("daycore at 0.9", () => songSelect!.Mods.Value.Single() is ModDaycore mod && Math.Round(mod.SpeedChange.Value, 2) == 0.9); + AddStep("decrease speed", () => songSelect?.ChangeSpeed(-0.05)); + AddAssert("daycore activated at 0.95x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.9).Within(0.005)); - AddStep("increasing speed with daycore", () => songSelect?.ChangeSpeed(0.05)); - AddAssert("daycore at 0.95", () => songSelect!.Mods.Value.Single() is ModDaycore mod && Math.Round(mod.SpeedChange.Value, 2) == 0.95); + AddStep("increase speed", () => songSelect?.ChangeSpeed(0.05)); + AddAssert("daycore activated at 0.95x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005)); OsuModDoubleTime dt = new OsuModDoubleTime { @@ -143,8 +143,9 @@ namespace osu.Game.Tests.Visual.SongSelect AdjustPitch = { Value = true }, }; changeMods(dt); - AddStep("decreasing speed from doubletime 1.02 with adjustpitch enabled", () => songSelect?.ChangeSpeed(-0.05)); - AddAssert("halftime at 0.97 with adjustpitch enabled", () => songSelect!.Mods.Value.Single() is ModHalfTime mod && Math.Round(mod.SpeedChange.Value, 2) == 0.97 && mod.AdjustPitch.Value); + AddStep("decrease speed", () => songSelect?.ChangeSpeed(-0.05)); + AddAssert("half time activated at 0.97x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.97).Within(0.005)); + AddAssert("adjust pitch preserved", () => songSelect!.Mods.Value.OfType().Single().AdjustPitch.Value, () => Is.True); OsuModHalfTime ht = new OsuModHalfTime { @@ -153,16 +154,19 @@ namespace osu.Game.Tests.Visual.SongSelect }; Mod[] modlist = { ht, new OsuModHardRock(), new OsuModHidden() }; changeMods(modlist); - AddStep("decreasing speed from halftime 0.97 with adjustpitch enabled, HDHR enabled", () => songSelect?.ChangeSpeed(0.05)); - AddAssert("doubletime at 1.02 with adjustpitch enabled, HDHR still enabled", () => songSelect!.Mods.Value.Count(mod => (mod is ModDoubleTime modDt && modDt.AdjustPitch.Value && Math.Round(modDt.SpeedChange.Value, 2) == 1.02) || mod is ModHardRock || mod is ModHidden) == 3); + AddStep("decrease speed", () => songSelect?.ChangeSpeed(0.05)); + AddAssert("double time activated at 1.02x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.02).Within(0.005)); + AddAssert("double time activated at 1.02x", () => songSelect!.Mods.Value.OfType().Single().AdjustPitch.Value, () => Is.True); + AddAssert("HD still enabled", () => songSelect!.Mods.Value.OfType().SingleOrDefault(), () => Is.Not.Null); + AddAssert("HR still enabled", () => songSelect!.Mods.Value.OfType().SingleOrDefault(), () => Is.Not.Null); changeMods(new ModWindUp()); AddStep("windup active, trying to change speed", () => songSelect?.ChangeSpeed(0.05)); AddAssert("windup still active", () => songSelect!.Mods.Value.First() is ModWindUp); changeMods(new ModAdaptiveSpeed()); - AddStep("adaptivespeed active, trying to change speed", () => songSelect?.ChangeSpeed(0.05)); - AddAssert("adaptivespeed still active", () => songSelect!.Mods.Value.First() is ModAdaptiveSpeed); + AddStep("adaptive speed active, trying to change speed", () => songSelect?.ChangeSpeed(0.05)); + AddAssert("adaptive speed still active", () => songSelect!.Mods.Value.First() is ModAdaptiveSpeed); } [Test] From 63406b6feb2215111626903bceef923ffb5ca46d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 24 May 2024 12:59:24 +0200 Subject: [PATCH 4012/4092] Rewrite implementation --- .../SongSelect/TestScenePlaySongSelect.cs | 51 ++++-- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 11 +- .../Screens/Select/ModSpeedHotkeyHandler.cs | 105 ++++++++++++ osu.Game/Screens/Select/SongSelect.cs | 152 +----------------- 4 files changed, 149 insertions(+), 170 deletions(-) create mode 100644 osu.Game/Screens/Select/ModSpeedHotkeyHandler.cs diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 7f0c209215..6581ce0323 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -93,25 +93,25 @@ namespace osu.Game.Tests.Visual.SongSelect createSongSelect(); changeMods(); - AddStep("decrease speed", () => songSelect?.ChangeSpeed(-0.05)); + decreaseModSpeed(); AddAssert("half time activated at 0.95x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005)); - AddStep("decrease speed", () => songSelect?.ChangeSpeed(-0.05)); + decreaseModSpeed(); AddAssert("half time speed changed to 0.9x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.9).Within(0.005)); - AddStep("increase speed", () => songSelect?.ChangeSpeed(0.05)); + increaseModSpeed(); AddAssert("half time speed changed to 0.95x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005)); - AddStep("increase speed", () => songSelect?.ChangeSpeed(0.05)); + increaseModSpeed(); AddAssert("no mods selected", () => songSelect!.Mods.Value.Count == 0); - AddStep("increase speed", () => songSelect?.ChangeSpeed(0.05)); + increaseModSpeed(); AddAssert("double time activated at 1.05x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.05).Within(0.005)); - AddStep("increase speed", () => songSelect?.ChangeSpeed(0.05)); + increaseModSpeed(); AddAssert("double time speed changed to 1.1x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.1).Within(0.005)); - AddStep("decrease speed", () => songSelect?.ChangeSpeed(-0.05)); + decreaseModSpeed(); AddAssert("double time speed changed to 1.05x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.05).Within(0.005)); OsuModNightcore nc = new OsuModNightcore @@ -119,22 +119,23 @@ namespace osu.Game.Tests.Visual.SongSelect SpeedChange = { Value = 1.05 } }; changeMods(nc); - AddStep("increase speed", () => songSelect?.ChangeSpeed(0.05)); + + increaseModSpeed(); AddAssert("nightcore speed changed to 1.1x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.1).Within(0.005)); - AddStep("decrease speed", () => songSelect?.ChangeSpeed(-0.05)); + decreaseModSpeed(); AddAssert("nightcore speed changed to 1.05x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.05).Within(0.005)); - AddStep("decrease speed", () => songSelect?.ChangeSpeed(-0.05)); + decreaseModSpeed(); AddAssert("no mods selected", () => songSelect!.Mods.Value.Count == 0); - AddStep("decrease speed", () => songSelect?.ChangeSpeed(-0.05)); + decreaseModSpeed(); AddAssert("daycore activated at 0.95x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005)); - AddStep("decrease speed", () => songSelect?.ChangeSpeed(-0.05)); + decreaseModSpeed(); AddAssert("daycore activated at 0.95x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.9).Within(0.005)); - AddStep("increase speed", () => songSelect?.ChangeSpeed(0.05)); + increaseModSpeed(); AddAssert("daycore activated at 0.95x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005)); OsuModDoubleTime dt = new OsuModDoubleTime @@ -143,7 +144,8 @@ namespace osu.Game.Tests.Visual.SongSelect AdjustPitch = { Value = true }, }; changeMods(dt); - AddStep("decrease speed", () => songSelect?.ChangeSpeed(-0.05)); + + decreaseModSpeed(); AddAssert("half time activated at 0.97x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.97).Within(0.005)); AddAssert("adjust pitch preserved", () => songSelect!.Mods.Value.OfType().Single().AdjustPitch.Value, () => Is.True); @@ -154,19 +156,34 @@ namespace osu.Game.Tests.Visual.SongSelect }; Mod[] modlist = { ht, new OsuModHardRock(), new OsuModHidden() }; changeMods(modlist); - AddStep("decrease speed", () => songSelect?.ChangeSpeed(0.05)); + + increaseModSpeed(); AddAssert("double time activated at 1.02x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.02).Within(0.005)); AddAssert("double time activated at 1.02x", () => songSelect!.Mods.Value.OfType().Single().AdjustPitch.Value, () => Is.True); AddAssert("HD still enabled", () => songSelect!.Mods.Value.OfType().SingleOrDefault(), () => Is.Not.Null); AddAssert("HR still enabled", () => songSelect!.Mods.Value.OfType().SingleOrDefault(), () => Is.Not.Null); changeMods(new ModWindUp()); - AddStep("windup active, trying to change speed", () => songSelect?.ChangeSpeed(0.05)); + increaseModSpeed(); AddAssert("windup still active", () => songSelect!.Mods.Value.First() is ModWindUp); changeMods(new ModAdaptiveSpeed()); - AddStep("adaptive speed active, trying to change speed", () => songSelect?.ChangeSpeed(0.05)); + increaseModSpeed(); AddAssert("adaptive speed still active", () => songSelect!.Mods.Value.First() is ModAdaptiveSpeed); + + void increaseModSpeed() => AddStep("increase mod speed", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.Up); + InputManager.ReleaseKey(Key.ControlLeft); + }); + + void decreaseModSpeed() => AddStep("decrease mod speed", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.Down); + InputManager.ReleaseKey(Key.ControlLeft); + }); } [Test] diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index ad589e8fa9..f8c67f4a10 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -64,9 +64,6 @@ namespace osu.Game.Overlays.Mods private Func isValidMod = _ => true; - [Resolved] - private SongSelect? songSelect { get; set; } - /// /// A function determining whether each mod in the column should be displayed. /// A return value of means that the mod is not filtered and therefore its corresponding panel should be displayed. @@ -138,6 +135,7 @@ namespace osu.Game.Overlays.Mods private FillFlowContainer footerButtonFlow = null!; private FillFlowContainer footerContentFlow = null!; private DeselectAllModsButton deselectAllModsButton = null!; + private ModSpeedHotkeyHandler modSpeedHotkeyHandler = null!; private Container aboveColumnsContent = null!; private RankingInformationDisplay? rankingInformationDisplay; @@ -190,7 +188,8 @@ namespace osu.Game.Overlays.Mods Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, Height = 0 - } + }, + modSpeedHotkeyHandler = new ModSpeedHotkeyHandler(), }); MainAreaContent.AddRange(new Drawable[] @@ -758,11 +757,11 @@ namespace osu.Game.Overlays.Mods } case GlobalAction.IncreaseModSpeed: - songSelect?.ChangeSpeed(0.05); + modSpeedHotkeyHandler.ChangeSpeed(0.05, AllAvailableMods.Where(state => state.ValidForSelection.Value).Select(state => state.Mod)); return true; case GlobalAction.DecreaseModSpeed: - songSelect?.ChangeSpeed(-0.05); + modSpeedHotkeyHandler.ChangeSpeed(-0.05, AllAvailableMods.Where(state => state.ValidForSelection.Value).Select(state => state.Mod)); return true; } diff --git a/osu.Game/Screens/Select/ModSpeedHotkeyHandler.cs b/osu.Game/Screens/Select/ModSpeedHotkeyHandler.cs new file mode 100644 index 0000000000..af64002bcf --- /dev/null +++ b/osu.Game/Screens/Select/ModSpeedHotkeyHandler.cs @@ -0,0 +1,105 @@ +// 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 osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Utils; +using osu.Game.Configuration; +using osu.Game.Overlays; +using osu.Game.Overlays.OSD; +using osu.Game.Rulesets.Mods; +using osu.Game.Utils; + +namespace osu.Game.Screens.Select +{ + public partial class ModSpeedHotkeyHandler : Component + { + [Resolved] + private Bindable> selectedMods { get; set; } = null!; + + [Resolved] + private OsuConfigManager config { get; set; } = null!; + + [Resolved] + private OnScreenDisplay? onScreenDisplay { get; set; } + + private ModRateAdjust? lastActiveRateAdjustMod; + + protected override void LoadComplete() + { + base.LoadComplete(); + + selectedMods.BindValueChanged(val => + { + lastActiveRateAdjustMod = val.NewValue.OfType().SingleOrDefault() ?? lastActiveRateAdjustMod; + }, true); + } + + public bool ChangeSpeed(double delta, IEnumerable availableMods) + { + double targetSpeed = (selectedMods.Value.OfType().SingleOrDefault()?.SpeedChange.Value ?? 1) + delta; + + if (Precision.AlmostEquals(targetSpeed, 1, 0.005)) + { + selectedMods.Value = selectedMods.Value.Where(m => m is not ModRateAdjust).ToList(); + onScreenDisplay?.Display(new SpeedChangeToast(config, targetSpeed)); + return true; + } + + ModRateAdjust? targetMod; + + if (lastActiveRateAdjustMod is ModDaycore || lastActiveRateAdjustMod is ModNightcore) + { + targetMod = targetSpeed < 1 + ? availableMods.OfType().SingleOrDefault() + : availableMods.OfType().SingleOrDefault(); + } + else + { + targetMod = targetSpeed < 1 + ? availableMods.OfType().SingleOrDefault() + : availableMods.OfType().SingleOrDefault(); + } + + if (targetMod == null) + return false; + + // preserve other settings from latest rate adjust mod instance seen + if (lastActiveRateAdjustMod != null) + { + foreach (var (_, sourceProperty) in lastActiveRateAdjustMod.GetSettingsSourceProperties()) + { + if (sourceProperty.Name == nameof(ModRateAdjust.SpeedChange)) + continue; + + var targetProperty = targetMod.GetType().GetProperty(sourceProperty.Name); + + if (targetProperty == null) + continue; + + var targetBindable = (IBindable)targetProperty.GetValue(targetMod)!; + var sourceBindable = (IBindable)sourceProperty.GetValue(lastActiveRateAdjustMod)!; + + if (targetBindable.GetType() != sourceBindable.GetType()) + continue; + + lastActiveRateAdjustMod.CopyAdjustedSetting(targetBindable, sourceBindable); + } + } + + targetMod.SpeedChange.Value = targetSpeed; + + var intendedMods = selectedMods.Value.Where(m => m is not ModRateAdjust).Append(targetMod).ToList(); + + if (!ModUtils.CheckCompatibleSet(intendedMods)) + return false; + + selectedMods.Value = intendedMods; + onScreenDisplay?.Display(new SpeedChangeToast(config, targetMod.SpeedChange.Value)); + return true; + } + } +} diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index b3823d7a0f..14e3931fce 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -30,7 +30,6 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Overlays; using osu.Game.Overlays.Mods; -using osu.Game.Overlays.OSD; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Backgrounds; @@ -40,6 +39,7 @@ using osu.Game.Screens.Play; using osu.Game.Screens.Select.Details; using osu.Game.Screens.Select.Options; using osu.Game.Skinning; +using osu.Game.Utils; using osuTK; using osuTK.Graphics; using osuTK.Input; @@ -137,6 +137,7 @@ namespace osu.Game.Screens.Select private double audioFeedbackLastPlaybackTime; private IDisposable? modSelectOverlayRegistration; + private ModSpeedHotkeyHandler modSpeedHotkeyHandler = null!; private AdvancedStats advancedStats = null!; @@ -148,16 +149,6 @@ namespace osu.Game.Screens.Select private Bindable configBackgroundBlur = null!; - private bool lastPitchState; - - private bool usedPitchMods; - - [Resolved] - private OnScreenDisplay? onScreenDisplay { get; set; } - - [Resolved] - private OsuConfigManager config { get; set; } = null!; - [BackgroundDependencyLoader(true)] private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog? manageCollectionsDialog, DifficultyRecommender? recommender, OsuConfigManager config) { @@ -333,6 +324,7 @@ namespace osu.Game.Screens.Select { RelativeSizeAxes = Axes.Both, }, + modSpeedHotkeyHandler = new ModSpeedHotkeyHandler(), }); if (ShowFooter) @@ -823,140 +815,6 @@ namespace osu.Game.Screens.Select return false; } - private Mod getRateMod(ModType modType, Type type) - { - var modList = game.AvailableMods.Value[modType]; - var multiMod = (MultiMod)modList.First(mod => mod is MultiMod multiMod && multiMod.Mods.Count(mod2 => mod2.GetType().IsSubclassOf(type)) > 0); - var mod = multiMod.Mods.First(mod => mod.GetType().IsSubclassOf(type)); - return mod; - } - - public void ChangeSpeed(double delta) - { - ModNightcore modNc = (ModNightcore)getRateMod(ModType.DifficultyIncrease, typeof(ModNightcore)); - ModDoubleTime modDt = (ModDoubleTime)getRateMod(ModType.DifficultyIncrease, typeof(ModDoubleTime)); - ModDaycore modDc = (ModDaycore)getRateMod(ModType.DifficultyReduction, typeof(ModDaycore)); - ModHalfTime modHt = (ModHalfTime)getRateMod(ModType.DifficultyReduction, typeof(ModHalfTime)); - bool rateModActive = selectedMods.Value.Count(mod => mod is ModRateAdjust) > 0; - bool incompatibleModActive = selectedMods.Value.Count(mod => modDt.IncompatibleMods.Count(incompatibleMod => (mod.GetType().IsSubclassOf(incompatibleMod) || mod.GetType() == incompatibleMod) && incompatibleMod != typeof(ModRateAdjust)) > 0) > 0; - double newRate = Math.Round(1d + delta, 2); - bool isPositive = delta > 0; - - if (incompatibleModActive) - return; - - if (!rateModActive) - { - onScreenDisplay?.Display(new SpeedChangeToast(config, newRate)); - - // If no ModRateAdjust is active, activate one - ModRateAdjust? newMod = null; - - if (isPositive && !usedPitchMods) - newMod = modDt; - - if (isPositive && usedPitchMods) - newMod = modNc; - - if (!isPositive && !usedPitchMods) - newMod = modHt; - - if (!isPositive && usedPitchMods) - newMod = modDc; - - if (!usedPitchMods && newMod is ModDoubleTime newModDt) - newModDt.AdjustPitch.Value = lastPitchState; - - if (!usedPitchMods && newMod is ModHalfTime newModHt) - newModHt.AdjustPitch.Value = lastPitchState; - - newMod!.SpeedChange.Value = newRate; - selectedMods.Value = selectedMods.Value.Append(newMod).ToList(); - return; - } - - ModRateAdjust mod = (ModRateAdjust)selectedMods.Value.First(mod => mod is ModRateAdjust); - newRate = Math.Round(mod.SpeedChange.Value + delta, 2); - - // Disable RateAdjustMods if newRate is 1 - if (newRate == 1.0) - { - lastPitchState = false; - usedPitchMods = false; - - if (mod is ModDoubleTime dtmod && dtmod.AdjustPitch.Value) - lastPitchState = true; - - if (mod is ModHalfTime htmod && htmod.AdjustPitch.Value) - lastPitchState = true; - - if (mod is ModNightcore || mod is ModDaycore) - usedPitchMods = true; - - //Disable RateAdjustMods - selectedMods.Value = selectedMods.Value.Where(search => search is not ModRateAdjust).ToList(); - - onScreenDisplay?.Display(new SpeedChangeToast(config, newRate)); - - return; - } - - bool overMaxRateLimit = (mod is ModHalfTime || mod is ModDaycore) && newRate > mod.SpeedChange.MaxValue; - bool underMinRateLimit = (mod is ModDoubleTime || mod is ModNightcore) && newRate < mod.SpeedChange.MinValue; - - // Swap mod to opposite mod if newRate exceeds max/min speed values - if (overMaxRateLimit || underMinRateLimit) - { - bool adjustPitch = (mod is ModDoubleTime dtmod && dtmod.AdjustPitch.Value) || (mod is ModHalfTime htmod && htmod.AdjustPitch.Value); - - //Disable RateAdjustMods - selectedMods.Value = selectedMods.Value.Where(search => search is not ModRateAdjust).ToList(); - - ModRateAdjust? oppositeMod = null; - - switch (mod) - { - case ModDoubleTime: - modHt.AdjustPitch.Value = adjustPitch; - oppositeMod = modHt; - break; - - case ModHalfTime: - modDt.AdjustPitch.Value = adjustPitch; - oppositeMod = modDt; - break; - - case ModNightcore: - oppositeMod = modDc; - break; - - case ModDaycore: - oppositeMod = modNc; - break; - } - - if (oppositeMod == null) return; - - oppositeMod.SpeedChange.Value = newRate; - selectedMods.Value = selectedMods.Value.Append(oppositeMod).ToList(); - - onScreenDisplay?.Display(new SpeedChangeToast(config, newRate)); - - return; - } - - // Cap newRate to max/min values and change rate of current active mod - if (newRate > mod.SpeedChange.MaxValue && (mod is ModDoubleTime || mod is ModNightcore)) - newRate = mod.SpeedChange.MaxValue; - - if (newRate < mod.SpeedChange.MinValue && (mod is ModHalfTime || mod is ModDaycore)) - newRate = mod.SpeedChange.MinValue; - - mod.SpeedChange.Value = newRate; - - onScreenDisplay?.Display(new SpeedChangeToast(config, newRate)); - } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -1160,11 +1018,11 @@ namespace osu.Game.Screens.Select switch (e.Action) { case GlobalAction.IncreaseModSpeed: - ChangeSpeed(0.05); + modSpeedHotkeyHandler.ChangeSpeed(0.05, ModUtils.FlattenMods(game.AvailableMods.Value.SelectMany(kv => kv.Value))); return true; case GlobalAction.DecreaseModSpeed: - ChangeSpeed(-0.05); + modSpeedHotkeyHandler.ChangeSpeed(-0.05, ModUtils.FlattenMods(game.AvailableMods.Value.SelectMany(kv => kv.Value))); return true; } From 345fb60679c3017f75b1b2d9dd54c210fde50a25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 24 May 2024 13:08:17 +0200 Subject: [PATCH 4013/4092] Fix toast strings --- osu.Game/Localisation/ToastStrings.cs | 4 ++-- osu.Game/Overlays/OSD/SpeedChangeToast.cs | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Localisation/ToastStrings.cs b/osu.Game/Localisation/ToastStrings.cs index 25899153f8..942540cfc5 100644 --- a/osu.Game/Localisation/ToastStrings.cs +++ b/osu.Game/Localisation/ToastStrings.cs @@ -50,9 +50,9 @@ namespace osu.Game.Localisation public static LocalisableString UrlCopied => new TranslatableString(getKey(@"url_copied"), @"URL copied"); /// - /// "Speed changed to" + /// "Speed changed to {0:N2}x" /// - public static LocalisableString SpeedChangedTo => new TranslatableString(getKey(@"speed_changed"), @"Speed changed to"); + public static LocalisableString SpeedChangedTo(double speed) => new TranslatableString(getKey(@"speed_changed"), @"Speed changed to {0:N2}x", speed); private static string getKey(string key) => $@"{prefix}:{key}"; } diff --git a/osu.Game/Overlays/OSD/SpeedChangeToast.cs b/osu.Game/Overlays/OSD/SpeedChangeToast.cs index df4f825541..49d3985b04 100644 --- a/osu.Game/Overlays/OSD/SpeedChangeToast.cs +++ b/osu.Game/Overlays/OSD/SpeedChangeToast.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.Threading; using osu.Game.Configuration; using osu.Game.Input.Bindings; using osu.Game.Localisation; @@ -11,7 +10,7 @@ namespace osu.Game.Overlays.OSD public partial class SpeedChangeToast : Toast { public SpeedChangeToast(OsuConfigManager config, double newSpeed) - : base(CommonStrings.Beatmaps, ToastStrings.SpeedChangedTo + " " + newSpeed.ToString(Thread.CurrentThread.CurrentCulture), config.LookupKeyBindings(GlobalAction.IncreaseModSpeed) + " / " + config.LookupKeyBindings(GlobalAction.DecreaseModSpeed)) + : base(ModSelectOverlayStrings.ModCustomisation, ToastStrings.SpeedChangedTo(newSpeed), config.LookupKeyBindings(GlobalAction.IncreaseModSpeed) + " / " + config.LookupKeyBindings(GlobalAction.DecreaseModSpeed)) { } } From 8cac87e4960253eae950f68c88923ad6dcf622dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 24 May 2024 13:09:07 +0200 Subject: [PATCH 4014/4092] Fix speed controls in mod select overlay not handling repeat --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index f8c67f4a10..bc87bb4e3d 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -704,6 +704,17 @@ namespace osu.Game.Overlays.Mods public override bool OnPressed(KeyBindingPressEvent e) { + switch (e.Action) + { + case GlobalAction.IncreaseModSpeed: + modSpeedHotkeyHandler.ChangeSpeed(0.05, AllAvailableMods.Where(state => state.ValidForSelection.Value).Select(state => state.Mod)); + return true; + + case GlobalAction.DecreaseModSpeed: + modSpeedHotkeyHandler.ChangeSpeed(-0.05, AllAvailableMods.Where(state => state.ValidForSelection.Value).Select(state => state.Mod)); + return true; + } + if (e.Repeat) return false; @@ -755,14 +766,6 @@ namespace osu.Game.Overlays.Mods return true; } - - case GlobalAction.IncreaseModSpeed: - modSpeedHotkeyHandler.ChangeSpeed(0.05, AllAvailableMods.Where(state => state.ValidForSelection.Value).Select(state => state.Mod)); - return true; - - case GlobalAction.DecreaseModSpeed: - modSpeedHotkeyHandler.ChangeSpeed(-0.05, AllAvailableMods.Where(state => state.ValidForSelection.Value).Select(state => state.Mod)); - return true; } return base.OnPressed(e); From b1b207960a46337b0a64036d575a23b846f638c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 24 May 2024 13:09:44 +0200 Subject: [PATCH 4015/4092] Actually use return value --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 6 ++---- osu.Game/Screens/Select/SongSelect.cs | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index bc87bb4e3d..8489b06f47 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -707,12 +707,10 @@ namespace osu.Game.Overlays.Mods switch (e.Action) { case GlobalAction.IncreaseModSpeed: - modSpeedHotkeyHandler.ChangeSpeed(0.05, AllAvailableMods.Where(state => state.ValidForSelection.Value).Select(state => state.Mod)); - return true; + return modSpeedHotkeyHandler.ChangeSpeed(0.05, AllAvailableMods.Where(state => state.ValidForSelection.Value).Select(state => state.Mod)); case GlobalAction.DecreaseModSpeed: - modSpeedHotkeyHandler.ChangeSpeed(-0.05, AllAvailableMods.Where(state => state.ValidForSelection.Value).Select(state => state.Mod)); - return true; + return modSpeedHotkeyHandler.ChangeSpeed(-0.05, AllAvailableMods.Where(state => state.ValidForSelection.Value).Select(state => state.Mod)); } if (e.Repeat) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 14e3931fce..269ca37ff5 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -1018,12 +1018,10 @@ namespace osu.Game.Screens.Select switch (e.Action) { case GlobalAction.IncreaseModSpeed: - modSpeedHotkeyHandler.ChangeSpeed(0.05, ModUtils.FlattenMods(game.AvailableMods.Value.SelectMany(kv => kv.Value))); - return true; + return modSpeedHotkeyHandler.ChangeSpeed(0.05, ModUtils.FlattenMods(game.AvailableMods.Value.SelectMany(kv => kv.Value))); case GlobalAction.DecreaseModSpeed: - modSpeedHotkeyHandler.ChangeSpeed(-0.05, ModUtils.FlattenMods(game.AvailableMods.Value.SelectMany(kv => kv.Value))); - return true; + return modSpeedHotkeyHandler.ChangeSpeed(-0.05, ModUtils.FlattenMods(game.AvailableMods.Value.SelectMany(kv => kv.Value))); } if (e.Repeat) From cab8cf741073959dc314516bac3e3fb63dcc262b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 24 May 2024 13:14:06 +0200 Subject: [PATCH 4016/4092] Move mod speed hotkey handler to user mod select overlay The very base class is the wrong place for it because `FreeModSelectOverlay` inherits from it, and that one has different assumptions and rules than "user" selection. In particular, in non-user selection, more than one rate adjust mod may be active, which breaks the mod speed hotkey's basic assumptions. --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 12 --------- .../Overlays/Mods/UserModSelectOverlay.cs | 26 +++++++++++++++++++ 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 8489b06f47..d2d7ace936 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -27,7 +27,6 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Localisation; using osu.Game.Rulesets.Mods; -using osu.Game.Screens.Select; using osu.Game.Utils; using osuTK; using osuTK.Input; @@ -135,7 +134,6 @@ namespace osu.Game.Overlays.Mods private FillFlowContainer footerButtonFlow = null!; private FillFlowContainer footerContentFlow = null!; private DeselectAllModsButton deselectAllModsButton = null!; - private ModSpeedHotkeyHandler modSpeedHotkeyHandler = null!; private Container aboveColumnsContent = null!; private RankingInformationDisplay? rankingInformationDisplay; @@ -189,7 +187,6 @@ namespace osu.Game.Overlays.Mods Origin = Anchor.BottomCentre, Height = 0 }, - modSpeedHotkeyHandler = new ModSpeedHotkeyHandler(), }); MainAreaContent.AddRange(new Drawable[] @@ -704,15 +701,6 @@ namespace osu.Game.Overlays.Mods public override bool OnPressed(KeyBindingPressEvent e) { - switch (e.Action) - { - case GlobalAction.IncreaseModSpeed: - return modSpeedHotkeyHandler.ChangeSpeed(0.05, AllAvailableMods.Where(state => state.ValidForSelection.Value).Select(state => state.Mod)); - - case GlobalAction.DecreaseModSpeed: - return modSpeedHotkeyHandler.ChangeSpeed(-0.05, AllAvailableMods.Where(state => state.ValidForSelection.Value).Select(state => state.Mod)); - } - if (e.Repeat) return false; diff --git a/osu.Game/Overlays/Mods/UserModSelectOverlay.cs b/osu.Game/Overlays/Mods/UserModSelectOverlay.cs index 49469b99f3..16d71e557b 100644 --- a/osu.Game/Overlays/Mods/UserModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/UserModSelectOverlay.cs @@ -3,18 +3,30 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Input.Events; +using osu.Game.Input.Bindings; using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Select; using osu.Game.Utils; namespace osu.Game.Overlays.Mods { public partial class UserModSelectOverlay : ModSelectOverlay { + private ModSpeedHotkeyHandler modSpeedHotkeyHandler = null!; + public UserModSelectOverlay(OverlayColourScheme colourScheme = OverlayColourScheme.Green) : base(colourScheme) { } + [BackgroundDependencyLoader] + private void load() + { + Add(modSpeedHotkeyHandler = new ModSpeedHotkeyHandler()); + } + protected override ModColumn CreateModColumn(ModType modType) => new UserModColumn(modType, false); protected override IReadOnlyList ComputeNewModsFromSelection(IReadOnlyList oldSelection, IReadOnlyList newSelection) @@ -38,6 +50,20 @@ namespace osu.Game.Overlays.Mods return modsAfterRemoval.ToList(); } + public override bool OnPressed(KeyBindingPressEvent e) + { + switch (e.Action) + { + case GlobalAction.IncreaseModSpeed: + return modSpeedHotkeyHandler.ChangeSpeed(0.05, AllAvailableMods.Where(state => state.ValidForSelection.Value).Select(state => state.Mod)); + + case GlobalAction.DecreaseModSpeed: + return modSpeedHotkeyHandler.ChangeSpeed(-0.05, AllAvailableMods.Where(state => state.ValidForSelection.Value).Select(state => state.Mod)); + } + + return base.OnPressed(e); + } + private partial class UserModColumn : ModColumn { public UserModColumn(ModType modType, bool allowIncompatibleSelection) From b2c4e0e951bb9fc5e1ba50ffe4429109f11b34cc Mon Sep 17 00:00:00 2001 From: Aurelian Date: Fri, 24 May 2024 14:05:56 +0200 Subject: [PATCH 4017/4092] Reworked linear line check, and optimized scaled flat slider test --- .../Editor/TestSliderScaling.cs | 147 ++++++------------ osu.Game/Rulesets/Objects/SliderPath.cs | 43 +---- 2 files changed, 53 insertions(+), 137 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs index 99694f82d1..ef3824b5b0 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs @@ -6,6 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -68,108 +69,52 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("slider length shrunk", () => slider.Path.Distance < distanceBefore); } - [Test] - [Timeout(4000)] //Catches crashes in other threads, but not ideal. Hopefully there is a improvement to this. - public void TestScalingSliderFlat( - [Values(0, 1, 2, 3)] int typeInt - ) - { - Slider slider = null!; - - switch (typeInt) - { - case 0: - AddStep("Add linear slider", () => - { - slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) }; - - PathControlPoint[] points = - { - new PathControlPoint(new Vector2(0), PathType.LINEAR), - new PathControlPoint(new Vector2(50, 100)), - }; - - slider.Path = new SliderPath(points); - EditorBeatmap.Add(slider); - }); - break; - - case 1: - AddStep("Add perfect curve slider", () => - { - slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) }; - - PathControlPoint[] points = - { - new PathControlPoint(new Vector2(0), PathType.PERFECT_CURVE), - new PathControlPoint(new Vector2(50, 25)), - new PathControlPoint(new Vector2(25, 100)), - }; - - slider.Path = new SliderPath(points); - EditorBeatmap.Add(slider); - }); - break; - - case 3: - AddStep("Add catmull slider", () => - { - slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) }; - - PathControlPoint[] points = - { - new PathControlPoint(new Vector2(0), PathType.CATMULL), - new PathControlPoint(new Vector2(50, 25)), - new PathControlPoint(new Vector2(25, 80)), - new PathControlPoint(new Vector2(40, 100)), - }; - - slider.Path = new SliderPath(points); - EditorBeatmap.Add(slider); - }); - break; - - default: - AddStep("Add bezier slider", () => - { - slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) }; - - PathControlPoint[] points = - { - new PathControlPoint(new Vector2(0), PathType.BEZIER), - new PathControlPoint(new Vector2(50, 25)), - new PathControlPoint(new Vector2(25, 80)), - new PathControlPoint(new Vector2(40, 100)), - }; - - slider.Path = new SliderPath(points); - EditorBeatmap.Add(slider); - }); - break; - } - - AddAssert("ensure object placed", () => EditorBeatmap.HitObjects.Count == 1); - - moveMouse(new Vector2(300)); - AddStep("select slider", () => InputManager.Click(MouseButton.Left)); - AddStep("slider is valid", () => slider.Path.GetSegmentEnds()); //To run ensureValid(); - - SelectionBoxDragHandle dragHandle = null!; - AddStep("store drag handle", () => dragHandle = Editor.ChildrenOfType().Skip(1).First()); - AddAssert("is dragHandle not null", () => dragHandle != null); - - AddStep("move mouse to handle", () => InputManager.MoveMouseTo(dragHandle)); - AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left)); - moveMouse(new Vector2(0, 300)); - AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left)); - - AddStep("move mouse to handle", () => InputManager.MoveMouseTo(dragHandle)); - AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left)); - moveMouse(new Vector2(0, 300)); //Should crash here if broken, although doesn't count as failed... - AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left)); - } - private void moveMouse(Vector2 pos) => AddStep($"move mouse to {pos}", () => InputManager.MoveMouseTo(playfield.ToScreenSpace(pos))); } + [TestFixture] + public class TestSliderNearLinearScaling + { + [Test] + public void TestScalingSliderFlat() + { + Slider sliderPerfect = new Slider + { + Position = new Vector2(300), + Path = new SliderPath( + [ + new PathControlPoint(new Vector2(0), PathType.PERFECT_CURVE), + new PathControlPoint(new Vector2(50, 25)), + new PathControlPoint(new Vector2(25, 100)), + ]) + }; + + Slider sliderBezier = new Slider + { + Position = new Vector2(300), + Path = new SliderPath( + [ + new PathControlPoint(new Vector2(0), PathType.BEZIER), + new PathControlPoint(new Vector2(50, 25)), + new PathControlPoint(new Vector2(25, 100)), + ]) + }; + + scaleSlider(sliderPerfect, new Vector2(0.000001f, 1)); + scaleSlider(sliderBezier, new Vector2(0.000001f, 1)); + + for (int i = 0; i < 100; i++) + { + Assert.True(Precision.AlmostEquals(sliderPerfect.Path.PositionAt(i / 100.0f), sliderBezier.Path.PositionAt(i / 100.0f))); + } + } + + private void scaleSlider(Slider slider, Vector2 scale) + { + for (int i = 0; i < slider.Path.ControlPoints.Count; i++) + { + slider.Path.ControlPoints[i].Position *= scale; + } + } + } } diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index ddea23034c..eca14269fe 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -269,38 +269,6 @@ namespace osu.Game.Rulesets.Objects pathCache.Validate(); } - /// - /// Checks if the array of vectors is almost straight. - /// - /// - /// The angle is first obtained based on the farthest vector from the first, - /// then we find the angle of each vector from the first, - /// and calculate the distance between the two angle vectors. - /// We than scale this distance to the distance from the first vector - /// (or by 10 if the distance is smaller), - /// and if it is greater than acceptableDifference, we return false. - /// - private static bool isAlmostStraight(Vector2[] vectors, float acceptableDifference = 0.1f) - { - if (vectors.Length <= 2 || vectors.All(x => x == vectors.First())) return true; - - Vector2 first = vectors.First(); - Vector2 farthest = vectors.MaxBy(x => Vector2.Distance(first, x)); - - Vector2 angle = Vector2.Normalize(farthest - first); - - foreach (Vector2 vector in vectors) - { - if (vector == first) - continue; - - if (Math.Max(10.0f, Vector2.Distance(vector, first)) * Vector2.Distance(Vector2.Normalize(vector - first), angle) > acceptableDifference) - return false; - } - - return true; - } - private void calculatePath() { calculatedPath.Clear(); @@ -325,10 +293,6 @@ namespace osu.Game.Rulesets.Objects var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1); var segmentType = ControlPoints[start].Type ?? PathType.LINEAR; - //If a segment is almost straight, treat it as linear. - if (segmentType != PathType.LINEAR && isAlmostStraight(segmentVertices.ToArray())) - segmentType = PathType.LINEAR; - // No need to calculate path when there is only 1 vertex if (segmentVertices.Length == 1) calculatedPath.Add(segmentVertices[0]); @@ -366,6 +330,13 @@ namespace osu.Game.Rulesets.Objects if (subControlPoints.Length != 3) break; + //If a curve's theta range almost equals zero, the radius needed to have more than a + //floating point error difference is very large and results in a nearly straight path. + //Calculate it via a bezier aproximation instead. + //0.0005 corresponds with a radius of 8000 to have a more than 0.001 shift in the X value + if (Math.Abs(new CircularArcProperties(subControlPoints).ThetaRange) <= 0.0005d) + break; + List subPath = PathApproximator.CircularArcToPiecewiseLinear(subControlPoints); // If for some reason a circular arc could not be fit to the 3 given points, fall back to a numerically stable bezier approximation. From 497701950d9582c9792f0da3ec883d69079adeb6 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 24 May 2024 18:11:28 +0200 Subject: [PATCH 4018/4092] fix nitpick --- .../Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs index 8a7f6b5344..79b4fa2841 100644 --- a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs @@ -28,7 +28,8 @@ namespace osu.Game.Screens.Edit.Compose.Components while (true) { - Vector2 currentPosition = StartPosition.Value + index++ * step; + Vector2 currentPosition = StartPosition.Value + index * step; + index++; if (!lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize, out var p1, out var p2)) { From 1e90e5e38e22e43c7bbcbb6ee5d11d82aac77216 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 25 May 2024 13:24:25 +0300 Subject: [PATCH 4019/4092] Use sb element path as a name --- osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs | 1 + osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index fae9ec7f2e..f66f84af7a 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -83,6 +83,7 @@ namespace osu.Game.Storyboards.Drawables Origin = animation.Origin; Position = animation.InitialPosition; Loop = animation.LoopType == AnimationLoopType.LoopForever; + Name = animation.Path; LifetimeStart = animation.StartTime; LifetimeEnd = animation.EndTimeForDisplay; diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index ec875219b6..c5d70ddecc 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -85,6 +85,7 @@ namespace osu.Game.Storyboards.Drawables Sprite = sprite; Origin = sprite.Origin; Position = sprite.InitialPosition; + Name = sprite.Path; LifetimeStart = sprite.StartTime; LifetimeEnd = sprite.EndTimeForDisplay; From 6aa92bcc4559ec52a85e8e026e579c03f1d7aca0 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 25 May 2024 18:31:19 +0200 Subject: [PATCH 4020/4092] Add simple scale tool --- .../Edit/OsuHitObjectComposer.cs | 6 +- .../Edit/PreciseScalePopover.cs | 121 ++++++++++++++++++ .../Edit/TransformToolboxGroup.cs | 33 ++++- .../Input/Bindings/GlobalActionContainer.cs | 4 + .../GlobalActionKeyBindingStrings.cs | 5 + 5 files changed, 167 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 3ead61f64a..6f3ed9730e 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -101,7 +101,11 @@ namespace osu.Game.Rulesets.Osu.Edit RightToolbox.AddRange(new EditorToolboxGroup[] { - new TransformToolboxGroup { RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, }, + new TransformToolboxGroup + { + RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, + ScaleHandler = BlueprintContainer.SelectionHandler.ScaleHandler, + }, FreehandlSliderToolboxGroup } ); diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs new file mode 100644 index 0000000000..62408e223d --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -0,0 +1,121 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Screens.Edit.Components.RadioButtons; +using osu.Game.Screens.Edit.Compose.Components; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public partial class PreciseScalePopover : OsuPopover + { + private readonly SelectionScaleHandler scaleHandler; + + private readonly Bindable scaleInfo = new Bindable(new PreciseScaleInfo(1, ScaleOrigin.PlayfieldCentre, true, true)); + + private SliderWithTextBoxInput scaleInput = null!; + private EditorRadioButtonCollection scaleOrigin = null!; + + private RadioButton selectionCentreButton = null!; + + public PreciseScalePopover(SelectionScaleHandler scaleHandler) + { + this.scaleHandler = scaleHandler; + + AllowableAnchors = new[] { Anchor.CentreLeft, Anchor.CentreRight }; + } + + [BackgroundDependencyLoader] + private void load() + { + Child = new FillFlowContainer + { + Width = 220, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(20), + Children = new Drawable[] + { + scaleInput = new SliderWithTextBoxInput("Scale:") + { + Current = new BindableNumber + { + MinValue = 0.5f, + MaxValue = 2, + Precision = 0.001f, + Value = 1, + Default = 1, + }, + Instantaneous = true + }, + scaleOrigin = new EditorRadioButtonCollection + { + RelativeSizeAxes = Axes.X, + Items = new[] + { + new RadioButton("Playfield centre", + () => scaleInfo.Value = scaleInfo.Value with { Origin = ScaleOrigin.PlayfieldCentre }, + () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), + selectionCentreButton = new RadioButton("Selection centre", + () => scaleInfo.Value = scaleInfo.Value with { Origin = ScaleOrigin.SelectionCentre }, + () => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare }) + } + } + } + }; + selectionCentreButton.Selected.DisabledChanged += isDisabled => + { + selectionCentreButton.TooltipText = isDisabled ? "Select more than one object to perform selection-based scaling." : string.Empty; + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ScheduleAfterChildren(() => scaleInput.TakeFocus()); + scaleInput.Current.BindValueChanged(scale => scaleInfo.Value = scaleInfo.Value with { Scale = scale.NewValue }); + scaleOrigin.Items.First().Select(); + + scaleHandler.CanScaleX.BindValueChanged(e => + { + selectionCentreButton.Selected.Disabled = !e.NewValue; + }, true); + + scaleInfo.BindValueChanged(scale => + { + var newScale = new Vector2(scale.NewValue.XAxis ? scale.NewValue.Scale : 1, scale.NewValue.YAxis ? scale.NewValue.Scale : 1); + scaleHandler.Update(newScale, scale.NewValue.Origin == ScaleOrigin.PlayfieldCentre ? OsuPlayfield.BASE_SIZE / 2 : null); + }); + } + + protected override void PopIn() + { + base.PopIn(); + scaleHandler.Begin(); + } + + protected override void PopOut() + { + base.PopOut(); + + if (IsLoaded) + scaleHandler.Commit(); + } + } + + public enum ScaleOrigin + { + PlayfieldCentre, + SelectionCentre + } + + public record PreciseScaleInfo(float Scale, ScaleOrigin Origin, bool XAxis, bool YAxis); +} diff --git a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs index 9499bacade..146d771e19 100644 --- a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs @@ -19,13 +19,20 @@ namespace osu.Game.Rulesets.Osu.Edit public partial class TransformToolboxGroup : EditorToolboxGroup, IKeyBindingHandler { private readonly Bindable canRotate = new BindableBool(); + private readonly Bindable canScale = new BindableBool(); private EditorToolButton rotateButton = null!; + private EditorToolButton scaleButton = null!; private Bindable canRotatePlayfieldOrigin = null!; private Bindable canRotateSelectionOrigin = null!; + private Bindable canScaleX = null!; + private Bindable canScaleY = null!; + private Bindable canScaleDiagonally = null!; + public SelectionRotationHandler RotationHandler { get; init; } = null!; + public SelectionScaleHandler ScaleHandler { get; init; } = null!; public TransformToolboxGroup() : base("transform") @@ -45,7 +52,9 @@ namespace osu.Game.Rulesets.Osu.Edit rotateButton = new EditorToolButton("Rotate", () => new SpriteIcon { Icon = FontAwesome.Solid.Undo }, () => new PreciseRotationPopover(RotationHandler)), - // TODO: scale + scaleButton = new EditorToolButton("Scale", + () => new SpriteIcon { Icon = FontAwesome.Solid.ArrowsAlt }, + () => new PreciseScalePopover(ScaleHandler)) } }; } @@ -66,9 +75,25 @@ namespace osu.Game.Rulesets.Osu.Edit canRotate.Value = RotationHandler.CanRotatePlayfieldOrigin.Value || RotationHandler.CanRotateSelectionOrigin.Value; } + // aggregate three values into canScale + canScaleX = ScaleHandler.CanScaleX.GetBoundCopy(); + canScaleX.BindValueChanged(_ => updateCanScaleAggregate()); + + canScaleY = ScaleHandler.CanScaleY.GetBoundCopy(); + canScaleY.BindValueChanged(_ => updateCanScaleAggregate()); + + canScaleDiagonally = ScaleHandler.CanScaleDiagonally.GetBoundCopy(); + canScaleDiagonally.BindValueChanged(_ => updateCanScaleAggregate()); + + void updateCanScaleAggregate() + { + canScale.Value = ScaleHandler.CanScaleX.Value || ScaleHandler.CanScaleY.Value || ScaleHandler.CanScaleDiagonally.Value; + } + // bindings to `Enabled` on the buttons are decoupled on purpose // due to the weird `OsuButton` behaviour of resetting `Enabled` to `false` when `Action` is set. canRotate.BindValueChanged(_ => rotateButton.Enabled.Value = canRotate.Value, true); + canScale.BindValueChanged(_ => scaleButton.Enabled.Value = canScale.Value, true); } public bool OnPressed(KeyBindingPressEvent e) @@ -82,6 +107,12 @@ namespace osu.Game.Rulesets.Osu.Edit rotateButton.TriggerClick(); return true; } + + case GlobalAction.EditorToggleScaleControl: + { + scaleButton.TriggerClick(); + return true; + } } return false; diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 09db7461d6..394cb98089 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -142,6 +142,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.MouseWheelRight }, GlobalAction.EditorCyclePreviousBeatSnapDivisor), new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.MouseWheelLeft }, GlobalAction.EditorCycleNextBeatSnapDivisor), new KeyBinding(new[] { InputKey.Control, InputKey.R }, GlobalAction.EditorToggleRotateControl), + new KeyBinding(new[] { InputKey.S }, GlobalAction.EditorToggleScaleControl), }; private static IEnumerable inGameKeyBindings => new[] @@ -411,6 +412,9 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorToggleRotateControl))] EditorToggleRotateControl, + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorToggleScaleControl))] + EditorToggleScaleControl, + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.IncreaseOffset))] IncreaseOffset, diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index 18a1d3e4fe..2e44b96625 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -369,6 +369,11 @@ namespace osu.Game.Localisation /// public static LocalisableString EditorToggleRotateControl => new TranslatableString(getKey(@"editor_toggle_rotate_control"), @"Toggle rotate control"); + /// + /// "Toggle scale control" + /// + public static LocalisableString EditorToggleScaleControl => new TranslatableString(getKey(@"editor_toggle_scale_control"), @"Toggle scale control"); + /// /// "Increase mod speed" /// From 88314dc584f186cf8a99c631d16047f321135292 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 25 May 2024 18:41:31 +0200 Subject: [PATCH 4021/4092] select all input text on popup for an easy typing experience --- osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs | 6 +++++- osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 6 +++++- osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs | 2 ++ osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs | 2 ++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs index 88c3d7414b..812d622ae5 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs @@ -78,7 +78,11 @@ namespace osu.Game.Rulesets.Osu.Edit { base.LoadComplete(); - ScheduleAfterChildren(() => angleInput.TakeFocus()); + ScheduleAfterChildren(() => + { + angleInput.TakeFocus(); + angleInput.SelectAll(); + }); angleInput.Current.BindValueChanged(angle => rotationInfo.Value = rotationInfo.Value with { Degrees = angle.NewValue }); rotationOrigin.Items.First().Select(); diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index 62408e223d..ed52da56d3 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -80,7 +80,11 @@ namespace osu.Game.Rulesets.Osu.Edit { base.LoadComplete(); - ScheduleAfterChildren(() => scaleInput.TakeFocus()); + ScheduleAfterChildren(() => + { + scaleInput.TakeFocus(); + scaleInput.SelectAll(); + }); scaleInput.Current.BindValueChanged(scale => scaleInfo.Value = scaleInfo.Value with { Scale = scale.NewValue }); scaleOrigin.Items.First().Select(); diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs index 863ad5a173..8dfe729ce7 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs @@ -50,6 +50,8 @@ namespace osu.Game.Graphics.UserInterfaceV2 Component.BorderColour = colours.Blue; } + public bool SelectAll() => Component.SelectAll(); + protected virtual OsuTextBox CreateTextBox() => new OsuTextBox(); public override bool AcceptsFocus => true; diff --git a/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs b/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs index 4c16cb4951..f1f4fe3b46 100644 --- a/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs +++ b/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs @@ -87,6 +87,8 @@ namespace osu.Game.Graphics.UserInterfaceV2 public bool TakeFocus() => GetContainingFocusManager().ChangeFocus(textBox); + public bool SelectAll() => textBox.SelectAll(); + private bool updatingFromTextBox; private void textChanged(ValueChangedEvent change) From 4eeebdf60cd70eedcf6692cbad36cc8df6abc8de Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 25 May 2024 20:17:27 +0200 Subject: [PATCH 4022/4092] calculate max scale bounds for scale slider --- .../Edit/OsuSelectionScaleHandler.cs | 47 ++++++++++--------- .../Edit/PreciseScalePopover.cs | 35 ++++++++++++-- .../Components/SelectionScaleHandler.cs | 8 ++++ 3 files changed, 64 insertions(+), 26 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index af03c4d925..331e8de3f1 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Edit objectsInScale = selectedMovableObjects.ToDictionary(ho => ho, ho => new OriginalHitObjectState(ho)); OriginalSurroundingQuad = objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider - ? GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position)) + ? GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => slider.Position + p.Position)) : GeometryUtils.GetSurroundingQuad(objectsInScale.Keys); defaultOrigin = OriginalSurroundingQuad.Value.Centre; } @@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Osu.Edit } else { - scale = getClampedScale(OriginalSurroundingQuad.Value, actualOrigin, scale); + scale = GetClampedScale(scale, actualOrigin); foreach (var (ho, originalState) in objectsInScale) { @@ -155,30 +155,33 @@ namespace osu.Game.Rulesets.Osu.Edit return (xInBounds, yInBounds); } - /// - /// Clamp scale for multi-object-scaling where selection does not exceed playfield bounds or flip. - /// - /// The quad surrounding the hitobjects - /// The origin from which the scale operation is performed - /// The scale to be clamped - /// The clamped scale vector - private Vector2 getClampedScale(Quad selectionQuad, Vector2 origin, Vector2 scale) + public override Vector2 GetClampedScale(Vector2 scale, Vector2? origin = null) { //todo: this is not always correct for selections involving sliders. This approximation assumes each point is scaled independently, but sliderends move with the sliderhead. + if (objectsInScale == null) + return scale; - var tl1 = Vector2.Divide(-origin, selectionQuad.TopLeft - origin); - var tl2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - origin, selectionQuad.TopLeft - origin); - var br1 = Vector2.Divide(-origin, selectionQuad.BottomRight - origin); - var br2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - origin, selectionQuad.BottomRight - origin); + Debug.Assert(defaultOrigin != null && OriginalSurroundingQuad != null); - if (!Precision.AlmostEquals(selectionQuad.TopLeft.X - origin.X, 0)) - scale.X = selectionQuad.TopLeft.X - origin.X < 0 ? MathHelper.Clamp(scale.X, tl2.X, tl1.X) : MathHelper.Clamp(scale.X, tl1.X, tl2.X); - if (!Precision.AlmostEquals(selectionQuad.TopLeft.Y - origin.Y, 0)) - scale.Y = selectionQuad.TopLeft.Y - origin.Y < 0 ? MathHelper.Clamp(scale.Y, tl2.Y, tl1.Y) : MathHelper.Clamp(scale.Y, tl1.Y, tl2.Y); - if (!Precision.AlmostEquals(selectionQuad.BottomRight.X - origin.X, 0)) - scale.X = selectionQuad.BottomRight.X - origin.X < 0 ? MathHelper.Clamp(scale.X, br2.X, br1.X) : MathHelper.Clamp(scale.X, br1.X, br2.X); - if (!Precision.AlmostEquals(selectionQuad.BottomRight.Y - origin.Y, 0)) - scale.Y = selectionQuad.BottomRight.Y - origin.Y < 0 ? MathHelper.Clamp(scale.Y, br2.Y, br1.Y) : MathHelper.Clamp(scale.Y, br1.Y, br2.Y); + if (objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider) + origin = slider.Position; + + Vector2 actualOrigin = origin ?? defaultOrigin.Value; + var selectionQuad = OriginalSurroundingQuad.Value; + + var tl1 = Vector2.Divide(-actualOrigin, selectionQuad.TopLeft - actualOrigin); + var tl2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - actualOrigin, selectionQuad.TopLeft - actualOrigin); + var br1 = Vector2.Divide(-actualOrigin, selectionQuad.BottomRight - actualOrigin); + var br2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - actualOrigin, selectionQuad.BottomRight - actualOrigin); + + if (!Precision.AlmostEquals(selectionQuad.TopLeft.X - actualOrigin.X, 0)) + scale.X = selectionQuad.TopLeft.X - actualOrigin.X < 0 ? MathHelper.Clamp(scale.X, tl2.X, tl1.X) : MathHelper.Clamp(scale.X, tl1.X, tl2.X); + if (!Precision.AlmostEquals(selectionQuad.TopLeft.Y - actualOrigin.Y, 0)) + scale.Y = selectionQuad.TopLeft.Y - actualOrigin.Y < 0 ? MathHelper.Clamp(scale.Y, tl2.Y, tl1.Y) : MathHelper.Clamp(scale.Y, tl1.Y, tl2.Y); + if (!Precision.AlmostEquals(selectionQuad.BottomRight.X - actualOrigin.X, 0)) + scale.X = selectionQuad.BottomRight.X - actualOrigin.X < 0 ? MathHelper.Clamp(scale.X, br2.X, br1.X) : MathHelper.Clamp(scale.X, br1.X, br2.X); + if (!Precision.AlmostEquals(selectionQuad.BottomRight.Y - actualOrigin.Y, 0)) + scale.Y = selectionQuad.BottomRight.Y - actualOrigin.Y < 0 ? MathHelper.Clamp(scale.Y, br2.Y, br1.Y) : MathHelper.Clamp(scale.Y, br1.Y, br2.Y); return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON)); } diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index ed52da56d3..50195ebd1e 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.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 System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -22,6 +23,7 @@ namespace osu.Game.Rulesets.Osu.Edit private readonly Bindable scaleInfo = new Bindable(new PreciseScaleInfo(1, ScaleOrigin.PlayfieldCentre, true, true)); private SliderWithTextBoxInput scaleInput = null!; + private BindableNumber scaleInputBindable = null!; private EditorRadioButtonCollection scaleOrigin = null!; private RadioButton selectionCentreButton = null!; @@ -45,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Edit { scaleInput = new SliderWithTextBoxInput("Scale:") { - Current = new BindableNumber + Current = scaleInputBindable = new BindableNumber { MinValue = 0.5f, MaxValue = 2, @@ -61,10 +63,10 @@ namespace osu.Game.Rulesets.Osu.Edit Items = new[] { new RadioButton("Playfield centre", - () => scaleInfo.Value = scaleInfo.Value with { Origin = ScaleOrigin.PlayfieldCentre }, + () => setOrigin(ScaleOrigin.PlayfieldCentre), () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), selectionCentreButton = new RadioButton("Selection centre", - () => scaleInfo.Value = scaleInfo.Value with { Origin = ScaleOrigin.SelectionCentre }, + () => setOrigin(ScaleOrigin.SelectionCentre), () => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare }) } } @@ -96,14 +98,39 @@ namespace osu.Game.Rulesets.Osu.Edit scaleInfo.BindValueChanged(scale => { var newScale = new Vector2(scale.NewValue.XAxis ? scale.NewValue.Scale : 1, scale.NewValue.YAxis ? scale.NewValue.Scale : 1); - scaleHandler.Update(newScale, scale.NewValue.Origin == ScaleOrigin.PlayfieldCentre ? OsuPlayfield.BASE_SIZE / 2 : null); + scaleHandler.Update(newScale, getOriginPosition(scale.NewValue)); }); } + private void updateMaxScale() + { + if (!scaleHandler.OriginalSurroundingQuad.HasValue) + return; + + const float max_scale = 10; + var scale = scaleHandler.GetClampedScale(new Vector2(max_scale), getOriginPosition(scaleInfo.Value)); + + if (!scaleInfo.Value.XAxis) + scale.X = max_scale; + if (!scaleInfo.Value.YAxis) + scale.Y = max_scale; + + scaleInputBindable.MaxValue = MathF.Max(1, MathF.Min(scale.X, scale.Y)); + } + + private void setOrigin(ScaleOrigin origin) + { + scaleInfo.Value = scaleInfo.Value with { Origin = origin }; + updateMaxScale(); + } + + private Vector2? getOriginPosition(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.PlayfieldCentre ? OsuPlayfield.BASE_SIZE / 2 : null; + protected override void PopIn() { base.PopIn(); scaleHandler.Begin(); + updateMaxScale(); } protected override void PopOut() diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs index a96f627e56..fb421c2329 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs @@ -34,6 +34,14 @@ namespace osu.Game.Screens.Edit.Compose.Components public Quad? OriginalSurroundingQuad { get; protected set; } + /// + /// Clamp scale where selection does not exceed playfield bounds or flip. + /// + /// The origin from which the scale operation is performed + /// The scale to be clamped + /// The clamped scale vector + public virtual Vector2 GetClampedScale(Vector2 scale, Vector2? origin = null) => scale; + /// /// Performs a single, instant, atomic scale operation. /// From 37530eebccdc6b7bacee3742946830ac61e2e815 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 25 May 2024 20:35:06 +0200 Subject: [PATCH 4023/4092] Enable scale buttons at the correct times --- osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs | 2 ++ osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 2 +- osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs | 8 ++++---- .../Edit/Compose/Components/SelectionScaleHandler.cs | 10 ++++++++++ 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 331e8de3f1..e45494977f 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -53,6 +53,8 @@ namespace osu.Game.Rulesets.Osu.Edit CanScaleX.Value = quad.Width > 0; CanScaleY.Value = quad.Height > 0; CanScaleDiagonally.Value = CanScaleX.Value && CanScaleY.Value; + CanScaleSelectionOrigin.Value = CanScaleX.Value || CanScaleY.Value; + CanScalePlayfieldOrigin.Value = selectedMovableObjects.Any(); } private Dictionary? objectsInScale; diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index 50195ebd1e..355064f000 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Edit scaleInput.Current.BindValueChanged(scale => scaleInfo.Value = scaleInfo.Value with { Scale = scale.NewValue }); scaleOrigin.Items.First().Select(); - scaleHandler.CanScaleX.BindValueChanged(e => + scaleHandler.CanScaleSelectionOrigin.BindValueChanged(e => { selectionCentreButton.Selected.Disabled = !e.NewValue; }, true); diff --git a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs index 146d771e19..e1f53846dc 100644 --- a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Edit private Bindable canScaleX = null!; private Bindable canScaleY = null!; - private Bindable canScaleDiagonally = null!; + private Bindable canScalePlayfieldOrigin = null!; public SelectionRotationHandler RotationHandler { get; init; } = null!; public SelectionScaleHandler ScaleHandler { get; init; } = null!; @@ -82,12 +82,12 @@ namespace osu.Game.Rulesets.Osu.Edit canScaleY = ScaleHandler.CanScaleY.GetBoundCopy(); canScaleY.BindValueChanged(_ => updateCanScaleAggregate()); - canScaleDiagonally = ScaleHandler.CanScaleDiagonally.GetBoundCopy(); - canScaleDiagonally.BindValueChanged(_ => updateCanScaleAggregate()); + canScalePlayfieldOrigin = ScaleHandler.CanScalePlayfieldOrigin.GetBoundCopy(); + canScalePlayfieldOrigin.BindValueChanged(_ => updateCanScaleAggregate()); void updateCanScaleAggregate() { - canScale.Value = ScaleHandler.CanScaleX.Value || ScaleHandler.CanScaleY.Value || ScaleHandler.CanScaleDiagonally.Value; + canScale.Value = ScaleHandler.CanScaleX.Value || ScaleHandler.CanScaleY.Value || ScaleHandler.CanScalePlayfieldOrigin.Value; } // bindings to `Enabled` on the buttons are decoupled on purpose diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs index fb421c2329..495cce7ad6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs @@ -32,6 +32,16 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public Bindable CanScaleDiagonally { get; private set; } = new BindableBool(); + /// + /// Whether scaling anchored by the selection origin can currently be performed. + /// + public Bindable CanScaleSelectionOrigin { get; private set; } = new BindableBool(); + + /// + /// Whether scaling anchored by the center of the playfield can currently be performed. + /// + public Bindable CanScalePlayfieldOrigin { get; private set; } = new BindableBool(); + public Quad? OriginalSurroundingQuad { get; protected set; } /// From d4489545f275fb29eae60ea96fbe4c84d8f82cf2 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 25 May 2024 21:44:08 +0200 Subject: [PATCH 4024/4092] add axis toggles --- .../Edit/PreciseScalePopover.cs | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index 355064f000..b76d778b3d 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -8,6 +8,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit.Components.RadioButtons; @@ -28,6 +29,9 @@ namespace osu.Game.Rulesets.Osu.Edit private RadioButton selectionCentreButton = null!; + private OsuCheckbox xCheckBox = null!; + private OsuCheckbox yCheckBox = null!; + public PreciseScalePopover(SelectionScaleHandler scaleHandler) { this.scaleHandler = scaleHandler; @@ -69,7 +73,28 @@ namespace osu.Game.Rulesets.Osu.Edit () => setOrigin(ScaleOrigin.SelectionCentre), () => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare }) } - } + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(4), + Children = new Drawable[] + { + xCheckBox = new OsuCheckbox(false) + { + RelativeSizeAxes = Axes.X, + LabelText = "X-axis", + Current = { Value = true }, + }, + yCheckBox = new OsuCheckbox(false) + { + RelativeSizeAxes = Axes.X, + LabelText = "Y-axis", + Current = { Value = true }, + }, + } + }, } }; selectionCentreButton.Selected.DisabledChanged += isDisabled => @@ -90,6 +115,9 @@ namespace osu.Game.Rulesets.Osu.Edit scaleInput.Current.BindValueChanged(scale => scaleInfo.Value = scaleInfo.Value with { Scale = scale.NewValue }); scaleOrigin.Items.First().Select(); + xCheckBox.Current.BindValueChanged(x => setAxis(x.NewValue, yCheckBox.Current.Value)); + yCheckBox.Current.BindValueChanged(y => setAxis(xCheckBox.Current.Value, y.NewValue)); + scaleHandler.CanScaleSelectionOrigin.BindValueChanged(e => { selectionCentreButton.Selected.Disabled = !e.NewValue; @@ -126,6 +154,12 @@ namespace osu.Game.Rulesets.Osu.Edit private Vector2? getOriginPosition(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.PlayfieldCentre ? OsuPlayfield.BASE_SIZE / 2 : null; + private void setAxis(bool x, bool y) + { + scaleInfo.Value = scaleInfo.Value with { XAxis = x, YAxis = y }; + updateMaxScale(); + } + protected override void PopIn() { base.PopIn(); From 76f13b21da3ed79e9daf3d7421342bdb29762dae Mon Sep 17 00:00:00 2001 From: Joppe27 Date: Sat, 25 May 2024 23:28:51 +0200 Subject: [PATCH 4025/4092] Correct scale of taiko-glow element --- osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs index 623243e9e1..487106d879 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs @@ -21,6 +21,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy private Sprite sprite = null!; + private const float base_scale = 0.8f; + [BackgroundDependencyLoader(true)] private void load(ISkinSource skin, HealthProcessor? healthProcessor) { @@ -30,7 +32,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy Origin = Anchor.Centre, Anchor = Anchor.Centre, Alpha = 0, - Scale = new Vector2(0.7f), + Scale = new Vector2(base_scale), Colour = new Colour4(255, 228, 0, 255), }; @@ -58,8 +60,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy if (!result.IsHit || !isKiaiActive) return; - sprite.ScaleTo(0.85f).Then() - .ScaleTo(0.7f, 80, Easing.OutQuad); + sprite.ScaleTo(base_scale + 0.15f).Then() + .ScaleTo(base_scale, 80, Easing.OutQuad); } } } From a62b9fa633437bd31dd375bf6c9b12a8591ff225 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 May 2024 11:42:36 +0900 Subject: [PATCH 4026/4092] Revert windows 16px icon to original version This also fixes the 48px version looking uncanny due to smaller paddings. --- osu.Desktop/lazer.ico | Bin 76679 -> 76679 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/osu.Desktop/lazer.ico b/osu.Desktop/lazer.ico index 24c1c29ba269aaeb38a9b9c604a1c364c986a753..d5dbf933c1c6795b54b485b349aa76f914707996 100644 GIT binary patch delta 11340 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 b&=ZDEW}Izh;E>_$Qd7_DezO;xnO^@5U$|tB delta 11404 zcmZu%d2E%}m5-|?N+z8V9i>VlWi&*SQKwb^V5Dd?f3zwi&1fN3D zNr>8yCQvZ8v5jq>7x1$IW45pbLmC2(X%fH~jCbRO=Y7Y!!9dzGzu&pv{ob?FPde{z z=bm%!x$8OSZL9m?wz?mld~*015t#{?9Rjmhmv(9>eFidwa6lfQJS}zPAJXzh9{byJ zlv3Q589@EuaTG`+&qet=pb;K>bO^O4e82v2*!urQj=YKiQ@-BYcctu1ubkN4EQP;o zkgRnzlDW28A!}W=*4NMqW^@y>W;>X-J_(61cSQ-%OQKlVMD*eF})T$L?zNq zXIiT1)|Xjt>R2Can~L|z|6YaMH|1P<>vVYnU^L4WCGGQ}U-=TA4EvEo?i7C8tJfty z{JJD9r~p(-BGOPuTKN6C&~}^OI_H|#FD)5vYiL z?=`3lI~P{TA6|h>NRt*2Dov_oH?MWfR|@eJ*KN1`6X%;TX}gPm*lCuLUY)QfEbln1 z$`_4VC`Qg3^-6N*!fK?|k}wbJV(v}NlT;Ac&a%M^)?1uvNt|B^^|EiUj``$8rth9N zZiUX*qZBC`;{3uvW)^^oQ;glQRTBSdxq;V^5>Vdu$_?52a+ykzyr>40U07IS>X3%k zC9@1*f6ZI`sp+%YY44j2VE%wI*pIvY1qY=WfB-G`NhU0m<=Fc_6WY*NrR=3R=^WYT;>wU_3<(`cD9^+TlRevQm* zmiS#h(x(Jd7u7345;b5L>~smTF8Y9)-w+B#Vm8eGgmdrE)elxNrTC|Y0}&plv&pRMzz=A>b9Z&XWF>!<#Mls?VK>TN`}pzrF_MZ_9+J$Pm$+$ zcPsZaN(V?=0_g!vS(iNDsLYm`QgxLfb5kNzn)W*xxd+xTf3?s&;?q0K_o|fF4g*jt-~*DjB3pTiU1x zo?fK5G`HiZ*|`7*pVE~+0+hd*?!)wjEZnunH|$C8B1>~@L<^z z{*B~*P$orLb%0vQe($Pmez_PoL4^(=ZE=$W=Gc|EIXBO_D4FkEmeal(g`j)~p!k-z zDikFK-(nmAXw9@#4}Z`hnZS#G?S|YNf2h^hi?E*|xNlh-+S&na!XwsxR6m7qvRB^L ztA>xnZF8?k!iq~+C7l*ePA9a<$@oA>8N?zCQ3_Y=4<3cG>ergO>SodPh4(5Rk0p9g-DcfLFNvK2OJ-<8(>J(=BcF zHMw!V>wAlD1XGh9Q7=WQEw-ENS=XR+GnTaCh;;&Tc2}F%n6;)BM^>|(&uP`2aL(5x zDGM4TV{xna*9D27!)zC{@BWQOK>r3^)mm5ZQN0c-JEBu8@TqIRqSRK|^%$nTE02S1)QY*Q^`z#xT=3gkiy%jSi}x39tKnXA9&oEt=u=yIB`1dS4(*}O?xihK zw5!$Y=U?9}IV-vV-8zuag_!#$&yL&DUhUWsOz0xoy|_)a;Zd13f_)p>B?lwOUePJF zB_oQ))xYl}JlE%!DR5nIXYu1!^}pVUIlpbK2PwgT6Java0?KgKlw<$5OHmqj1orYD zL)4u6Mw2^&9x&{hvXwXmJY?E&c=2PJu_PeACGFD(z!g7Jk??bTGlY-mmOU$ar68_L zoY14PyLnUsm3J(zreEg|O6JlI@hxqa#w)=Qe{-TmYQOEBviiA*&+Q5xW_jxalD)iJ z_C{i=Fb!Y&dOh0tZ?)i@Xnkqg2wJf9I_1#&!NrCiGu^{SJD*MJm8@l*N;EgBUyg0+ zrJA{zpT2rzNcKebpsi2gz*}8V`vYC*WyeP_@-8`>7!<<)I@SlYVeot9Y(lW@U|ctj zfObzlUKKtZY8=P~U%xjH%Ek{BdIVRT1E1wxvUf$F049jO*26P(9 zC}(*OXqiEuPZ>e`knD}>(^HuDo1S^h@J!$cD$bgzq|`=!9f2QZ>2JBOcqFI-McolU zAUASAM@2#X(F0)Gi+YR@FeN3P4#ur#*;#?nag<1jOoD_xkX}#B_Dm-g8u{&YF8rKjw0EaF?LLeVgeN%uq!9T*%~_zG&fDf#-Yv@vx^Bhw5TbR}AlE_d8)%j7?F`n@YcnnOxHS$|(;V_N^QN z)e$49@8;uSKYdm;2!?D5!l>l?zDDzN{z%i5A3dUL{$ghEOr=XPXfjhpT+v4LB|SyaWL9at z3#9ZIP8RHAYGnUPaE~^WnI{KR9@jj}`&W(20f5ctN1>lBvya(LCJ4xj&AD4Zoi`r6@6DkT*%g)r--n5VQKa|3_9^Hlql$(+AV9Y&8 zcMqKIYI-mhc`X~R+UDYTxV8`RLfULzrHq+)R(C}KR1>lwIp4D&o%%VWP|%Ot=pdxy zNyFX;VO5Nrsr7(6&Kx|QHUr=_2SQw*{CAn zhXx~Gsl4{M-U^|c!r1!;VtlXsQ`pa6Jmi)S#Xj)b$dRjjFAkxjAL0cQ+>azpuk$Y* zWTF7x`Y(mumcIrQgcFO?ZiYc-B>X_g;Lz%aQ&ZF();iR$Htj}0s0lQ5cUo}c!G zak&5Kpq9_e06@Qy{s*|X@HD^^gOkBwbMcX?a)^DX9Le?Y>*Q)Vo8jA`vu{o$9$KI^ zZTJB&n(3a~2uu8zgEH^TWPtZk^*kJMV>|SX#0#F9(QWS?1$R+4CllMaTXXPuFf!*1McjS+KE77i!_#;Ddd~<#9HtM{(ee zVbtl-Vo;!Cb?uLkA=MLrAv7^8=Ec0i{5nrQx?sgxq#T!Yc;ILKu!=IkvS`k6CoCIg%%_7v~@|8xR0U<%I$9t*(TBunaD{ zGT9Dq8eyAFwH{@-^H^7~sa~p!y*oHS&COZ@LL`rQzAudK>m!T|frk6VLEIOjQJ}u04`XJm#>RUR=F&|WjOj4^WZQyy#xRUK)3#+q z)qs{UDnWKLm^>GY40WOnsmrUq&<;a1AHRga9*Ed@ho)`_$L53Whz79D4dv{H3(hk0 z-YYY}D8PN?TX{>JEjH{z#|u5}bfD5#5?XKa2atmT~em54Yut-s5xcT70LVXrew$6kR9;cQy2{it-2UwYMQdiRy zydA1vE*~C%&!a`sBCDkC@{~Z7lz-WUjan%_c$1kA{f6K>&4}ezs8wG}Xxr^dTr_Xg zsT0?_F?}l=6i}k#K+zsW^?}I*prSr zU$k*oHkFRRW!0)a3TlPhhbJuK8C>ZM59CA5H(bB!#oR9aa|7wn<;lK`0&&zWRU;a5 zU<8| zmjepi0W(R7Bd2GD?jU+-Xbl#|(N_Tvj(T%UJ=^J*Qh{7gjOP@Kg6|7;>d$gLLZn53 zC;;zMzC2wmJLIcA2BhWvt@l-R#+76|HheHzR}UY1EHkAL))Arts}IC@Z%lvYt=9&A z3J5{}*Fft7Omi1pPn=dq0_Gr84~;HF+9f2YMl*a9hKTiFAa(s0)>T0NgzPDa(to{%IbQlU_?vB}l?g&9N3_^;dY)+Z2br&UeC_O0~{fPlyk^bz`$ z_u%5{IIwO&sTzCT*oJEPTKip7*E;s^tyHyKY4Xv6B_2}1ftYbQ3>}5IO!bk<7w|?h;ni_^3;eLy zG!)AkqsBV3Hlu4t!eX=bBu!#27p{U3E6u^<^cPbe?6oDMa%QX9l^m~{gVrcTs?tJg zYjhUA-HWw1O4<17gZvm^J@Wj8Buuf#R0hO3Czz1sLeDh%=eE!4g2MDC=)LQ zQ0#&qDeGvzJohiiL=SaM1}^#$BYU@AUr@X7M?mKJf4Y0)-v!tK$WJ-QY;L@YIKtXG zylO)J$fkcrGk;o3hehCCCR26?iPU^Gtgjuuc9U-)7Jst90Qe@{6;UZAe!U^7uDnAZI2}@Yx?66SPDmF*QU~7;=nJLp zt1&743}GZZE&^qD@!G-v5)kDZzqhaO_=k z-s=&IOt4A*DaV+z~nH>Lf0L*#Is<{>2a(xn=(1;VkI2(;aQV%Zdd~<-& zK{<+;mcD+JIoAO{5OY|6_{}lz=rZ0Y_=4O`fBZM&(o!}FRB=o%{-&KD`WtSTaLRP^ s#9jI9cVi%f_Y1BX=D$5KEF(R-VWPRaxcz3Z1Hca`b|D!lchrLa2RxK$m;e9( From 0d6adf160bcff5da41e5e2262b7e223163670179 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 May 2024 14:20:28 +0900 Subject: [PATCH 4027/4092] Share scale factor with hit target --- .../Skinning/Legacy/LegacyKiaiGlow.cs | 8 +++----- .../Skinning/Legacy/TaikoLegacyHitTarget.cs | 10 ++++++++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs index 487106d879..9877efa127 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs @@ -21,8 +21,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy private Sprite sprite = null!; - private const float base_scale = 0.8f; - [BackgroundDependencyLoader(true)] private void load(ISkinSource skin, HealthProcessor? healthProcessor) { @@ -32,7 +30,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy Origin = Anchor.Centre, Anchor = Anchor.Centre, Alpha = 0, - Scale = new Vector2(base_scale), + Scale = new Vector2(TaikoLegacyHitTarget.SCALE), Colour = new Colour4(255, 228, 0, 255), }; @@ -60,8 +58,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy if (!result.IsHit || !isKiaiActive) return; - sprite.ScaleTo(base_scale + 0.15f).Then() - .ScaleTo(base_scale, 80, Easing.OutQuad); + sprite.ScaleTo(TaikoLegacyHitTarget.SCALE + 0.15f).Then() + .ScaleTo(TaikoLegacyHitTarget.SCALE, 80, Easing.OutQuad); } } } diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyHitTarget.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyHitTarget.cs index 0b43f1c845..2a008d81d9 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyHitTarget.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyHitTarget.cs @@ -12,6 +12,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { public partial class TaikoLegacyHitTarget : CompositeDrawable { + /// + /// In stable this is 0.7f (see https://github.com/peppy/osu-stable-reference/blob/7519cafd1823f1879c0d9c991ba0e5c7fd3bfa02/osu!/GameModes/Play/Rulesets/Taiko/RulesetTaiko.cs#L592) + /// but for whatever reason this doesn't match visually. + /// + public const float SCALE = 0.8f; + [BackgroundDependencyLoader] private void load(ISkinSource skin) { @@ -22,7 +28,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy new Sprite { Texture = skin.GetTexture("approachcircle"), - Scale = new Vector2(0.83f), + Scale = new Vector2(SCALE + 0.03f), Alpha = 0.47f, // eyeballed to match stable Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -30,7 +36,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy new Sprite { Texture = skin.GetTexture("taikobigcircle"), - Scale = new Vector2(0.8f), + Scale = new Vector2(SCALE), Alpha = 0.22f, // eyeballed to match stable Anchor = Anchor.Centre, Origin = Anchor.Centre, From 11c3d11db9b66097d61f877cb2e9b602bf90b5e5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 May 2024 14:38:43 +0900 Subject: [PATCH 4028/4092] Fix cinema mod not hiding playfield skin layer --- osu.Game/Rulesets/Mods/ModCinema.cs | 2 ++ osu.Game/Screens/Play/HUDOverlay.cs | 15 +++++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index 7c88a8a588..0c00eb6ae0 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -36,6 +36,8 @@ namespace osu.Game.Rulesets.Mods { overlay.ShowHud.Value = false; overlay.ShowHud.Disabled = true; + + overlay.PlayfieldSkinLayer.Hide(); } public void ApplyToPlayer(Player player) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 9d7a05bc90..16dfff8c19 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -109,7 +109,10 @@ namespace osu.Game.Screens.Play private readonly List hideTargets; - private readonly Drawable playfieldComponents; + /// + /// The container for skin components attached to + /// + internal readonly Drawable PlayfieldSkinLayer; public HUDOverlay([CanBeNull] DrawableRuleset drawableRuleset, IReadOnlyList mods, bool alwaysShowLeaderboard = true) { @@ -129,7 +132,7 @@ namespace osu.Game.Screens.Play drawableRuleset != null ? (rulesetComponents = new HUDComponentsContainer(drawableRuleset.Ruleset.RulesetInfo) { AlwaysPresent = true, }) : Empty(), - playfieldComponents = drawableRuleset != null + PlayfieldSkinLayer = drawableRuleset != null ? new SkinComponentsContainer(new SkinComponentsContainerLookup(SkinComponentsContainerLookup.TargetArea.Playfield, drawableRuleset.Ruleset.RulesetInfo)) { AlwaysPresent = true, } : Empty(), topRightElements = new FillFlowContainer @@ -247,10 +250,10 @@ namespace osu.Game.Screens.Play { Quad playfieldScreenSpaceDrawQuad = drawableRuleset.Playfield.SkinnableComponentScreenSpaceDrawQuad; - playfieldComponents.Position = ToLocalSpace(playfieldScreenSpaceDrawQuad.TopLeft); - playfieldComponents.Width = (ToLocalSpace(playfieldScreenSpaceDrawQuad.TopRight) - ToLocalSpace(playfieldScreenSpaceDrawQuad.TopLeft)).Length; - playfieldComponents.Height = (ToLocalSpace(playfieldScreenSpaceDrawQuad.BottomLeft) - ToLocalSpace(playfieldScreenSpaceDrawQuad.TopLeft)).Length; - playfieldComponents.Rotation = drawableRuleset.Playfield.Rotation; + PlayfieldSkinLayer.Position = ToLocalSpace(playfieldScreenSpaceDrawQuad.TopLeft); + PlayfieldSkinLayer.Width = (ToLocalSpace(playfieldScreenSpaceDrawQuad.TopRight) - ToLocalSpace(playfieldScreenSpaceDrawQuad.TopLeft)).Length; + PlayfieldSkinLayer.Height = (ToLocalSpace(playfieldScreenSpaceDrawQuad.BottomLeft) - ToLocalSpace(playfieldScreenSpaceDrawQuad.TopLeft)).Length; + PlayfieldSkinLayer.Rotation = drawableRuleset.Playfield.Rotation; } float? lowestTopScreenSpaceLeft = null; From bdfce4b9dafc90314e6b55f448e899d164ca1d97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 27 May 2024 08:26:00 +0200 Subject: [PATCH 4029/4092] Fix xmldoc reference --- osu.Game/Screens/Play/HUDOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 16dfff8c19..0c0941573c 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -110,7 +110,7 @@ namespace osu.Game.Screens.Play private readonly List hideTargets; /// - /// The container for skin components attached to + /// The container for skin components attached to /// internal readonly Drawable PlayfieldSkinLayer; From b6471f0b9cd4ebb3dee155990e89b44e1e2a14f8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 May 2024 17:09:35 +0900 Subject: [PATCH 4030/4092] Allow previewing audio of playlist items --- .../Drawables/Cards/BeatmapCardThumbnail.cs | 5 +- osu.Game/Overlays/BeatmapSet/BasicStats.cs | 5 +- .../Overlays/BeatmapSet/Buttons/PlayButton.cs | 5 +- .../BeatmapSet/Buttons/PreviewButton.cs | 6 +-- osu.Game/Overlays/BeatmapSet/Details.cs | 1 + .../OnlinePlay/DrawableRoomPlaylistItem.cs | 49 ++++++++++++++++--- 6 files changed, 51 insertions(+), 20 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs index cd498c474a..e70d115715 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps.Drawables.Cards.Buttons; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Framework.Graphics.UserInterface; using osuTK; @@ -36,14 +35,14 @@ namespace osu.Game.Beatmaps.Drawables.Cards [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; - public BeatmapCardThumbnail(APIBeatmapSet beatmapSetInfo) + public BeatmapCardThumbnail(IBeatmapSetInfo beatmapSetInfo) { InternalChildren = new Drawable[] { new UpdateableOnlineBeatmapSetCover(BeatmapSetCoverType.List) { RelativeSizeAxes = Axes.Both, - OnlineInfo = beatmapSetInfo + OnlineInfo = beatmapSetInfo as IBeatmapSetOnlineInfo }, background = new Box { diff --git a/osu.Game/Overlays/BeatmapSet/BasicStats.cs b/osu.Game/Overlays/BeatmapSet/BasicStats.cs index 0b1befe7b9..364874cdf7 100644 --- a/osu.Game/Overlays/BeatmapSet/BasicStats.cs +++ b/osu.Game/Overlays/BeatmapSet/BasicStats.cs @@ -16,7 +16,6 @@ using osu.Game.Beatmaps; using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Resources.Localisation.Web; using osuTK; @@ -26,9 +25,9 @@ namespace osu.Game.Overlays.BeatmapSet { private readonly Statistic length, bpm, circleCount, sliderCount; - private APIBeatmapSet beatmapSet; + private IBeatmapSetInfo beatmapSet; - public APIBeatmapSet BeatmapSet + public IBeatmapSetInfo BeatmapSet { get => beatmapSet; set diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/PlayButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/PlayButton.cs index 5f9cdf5065..921f136de9 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/PlayButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/PlayButton.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Audio; +using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests.Responses; @@ -28,9 +29,9 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons [CanBeNull] public PreviewTrack Preview { get; private set; } - private APIBeatmapSet beatmapSet; + private IBeatmapSetInfo beatmapSet; - public APIBeatmapSet BeatmapSet + public IBeatmapSetInfo BeatmapSet { get => beatmapSet; set diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs index 2254514a44..1eff4a7c11 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs @@ -8,9 +8,9 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Audio; +using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Online.API.Requests.Responses; using osuTK; namespace osu.Game.Overlays.BeatmapSet.Buttons @@ -24,7 +24,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons public IBindable Playing => playButton.Playing; - public APIBeatmapSet BeatmapSet + public IBeatmapSetInfo BeatmapSet { get => playButton.BeatmapSet; set => playButton.BeatmapSet = value; @@ -32,8 +32,6 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons public PreviewButton() { - Height = 42; - Children = new Drawable[] { background = new Box diff --git a/osu.Game/Overlays/BeatmapSet/Details.cs b/osu.Game/Overlays/BeatmapSet/Details.cs index d656a6b14b..7d69cb7329 100644 --- a/osu.Game/Overlays/BeatmapSet/Details.cs +++ b/osu.Game/Overlays/BeatmapSet/Details.cs @@ -68,6 +68,7 @@ namespace osu.Game.Overlays.BeatmapSet preview = new PreviewButton { RelativeSizeAxes = Axes.X, + Height = 42, }, new DetailBox { diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 1b8e2d8be6..b28269c6e6 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -22,6 +22,7 @@ using osu.Framework.Localisation; using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; +using osu.Game.Beatmaps.Drawables.Cards; using osu.Game.Collections; using osu.Game.Database; using osu.Game.Graphics; @@ -32,6 +33,7 @@ using osu.Game.Online.Chat; using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapSet; +using osu.Game.Overlays.BeatmapSet.Buttons; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -81,7 +83,7 @@ namespace osu.Game.Screens.OnlinePlay private Mod[] requiredMods = Array.Empty(); private Container maskingContainer; - private Container difficultyIconContainer; + private FillFlowContainer difficultyIconContainer; private LinkFlowContainer beatmapText; private LinkFlowContainer authorText; private ExplicitContentBeatmapBadge explicitContent; @@ -93,6 +95,7 @@ namespace osu.Game.Screens.OnlinePlay private Drawable removeButton; private PanelBackground panelBackground; private FillFlowContainer mainFillFlow; + private BeatmapCardThumbnail thumbnail; [Resolved] private RealmAccess realm { get; set; } @@ -282,10 +285,23 @@ namespace osu.Game.Screens.OnlinePlay if (beatmap != null) { - difficultyIconContainer.Child = new DifficultyIcon(beatmap, ruleset, requiredMods) + difficultyIconContainer.Children = new Drawable[] { - Size = new Vector2(icon_height), - TooltipType = DifficultyIconTooltipType.Extended, + thumbnail = new BeatmapCardThumbnail(beatmap.BeatmapSet!) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Width = 60, + RelativeSizeAxes = Axes.Y, + Dimmed = { Value = IsHovered } + }, + new DifficultyIcon(beatmap, ruleset, requiredMods) + { + Size = new Vector2(icon_height), + TooltipType = DifficultyIconTooltipType.Extended, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, }; } else @@ -329,7 +345,7 @@ namespace osu.Game.Screens.OnlinePlay protected override Drawable CreateContent() { - Action fontParameters = s => s.Font = OsuFont.Default.With(weight: FontWeight.SemiBold); + Action fontParameters = s => s.Font = OsuFont.Default.With(size: 14, weight: FontWeight.SemiBold); return maskingContainer = new Container { @@ -364,12 +380,15 @@ namespace osu.Game.Screens.OnlinePlay { new Drawable[] { - difficultyIconContainer = new Container + difficultyIconContainer = new FillFlowContainer { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Left = 8, Right = 8 }, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(4), + Margin = new MarginPadding { Right = 8 }, }, mainFillFlow = new MainFlow(() => SelectedItem.Value == Model || !AllowSelection) { @@ -484,6 +503,20 @@ namespace osu.Game.Screens.OnlinePlay }, }; + protected override bool OnHover(HoverEvent e) + { + if (thumbnail != null) + thumbnail.Dimmed.Value = true; + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + if (thumbnail != null) + thumbnail.Dimmed.Value = false; + base.OnHoverLost(e); + } + protected override bool OnClick(ClickEvent e) { if (AllowSelection && valid.Value) From 1e2cac3e92f3cec02d28639a1314db80ae0bf022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 27 May 2024 11:44:55 +0200 Subject: [PATCH 4031/4092] Remove unused using directive --- osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index b28269c6e6..090236d6e2 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -33,7 +33,6 @@ using osu.Game.Online.Chat; using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapSet; -using osu.Game.Overlays.BeatmapSet.Buttons; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; From 1f41261fc7d5e0c0f5a77cc064a1ba3c1d525ff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 27 May 2024 12:01:02 +0200 Subject: [PATCH 4032/4092] Fix test failure --- .../Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 6446ebd35f..bd62a8b131 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -16,6 +16,7 @@ using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; +using osu.Game.Beatmaps.Drawables.Cards; using osu.Game.Database; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; @@ -317,13 +318,13 @@ namespace osu.Game.Tests.Visual.Multiplayer p.RequestResults = _ => resultsRequested = true; }); + AddUntilStep("wait for load", () => playlist.ChildrenOfType().Any() && playlist.ChildrenOfType().First().DrawWidth > 0); AddStep("move mouse to first item title", () => { var drawQuad = playlist.ChildrenOfType().First().ScreenSpaceDrawQuad; var location = (drawQuad.TopLeft + drawQuad.BottomLeft) / 2 + new Vector2(drawQuad.Width * 0.2f, 0); InputManager.MoveMouseTo(location); }); - AddUntilStep("wait for text load", () => playlist.ChildrenOfType().Any()); AddAssert("first item title not hovered", () => playlist.ChildrenOfType().First().IsHovered, () => Is.False); AddStep("click left mouse", () => InputManager.Click(MouseButton.Left)); AddUntilStep("first item selected", () => playlist.ChildrenOfType().First().IsSelectedItem, () => Is.True); From d97622491297efa4ee4699b06748588b2ea84a07 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 May 2024 19:59:25 +0900 Subject: [PATCH 4033/4092] Standardise padding on both sides of difficulty icon --- osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 090236d6e2..72866d1694 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -387,7 +387,7 @@ namespace osu.Game.Screens.OnlinePlay RelativeSizeAxes = Axes.Y, Direction = FillDirection.Horizontal, Spacing = new Vector2(4), - Margin = new MarginPadding { Right = 8 }, + Margin = new MarginPadding { Right = 4 }, }, mainFillFlow = new MainFlow(() => SelectedItem.Value == Model || !AllowSelection) { From 75d961e6f2cc74b631a1980be91f3525e4551b80 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 May 2024 20:03:46 +0900 Subject: [PATCH 4034/4092] Pass the same thing in twice for better maybe --- .../Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs | 2 +- osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs | 2 +- osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs | 2 +- osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs | 4 ++-- osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs index f44fe2b90c..f5f9d121cc 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.Beatmaps var beatmapSet = CreateAPIBeatmapSet(Ruleset.Value); beatmapSet.OnlineID = 241526; // ID hardcoded to ensure that the preview track exists online. - Child = thumbnail = new BeatmapCardThumbnail(beatmapSet) + Child = thumbnail = new BeatmapCardThumbnail(beatmapSet, beatmapSet) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs index 175c15ea7b..2c2761ff0c 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs @@ -61,7 +61,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - thumbnail = new BeatmapCardThumbnail(BeatmapSet) + thumbnail = new BeatmapCardThumbnail(BeatmapSet, BeatmapSet) { Name = @"Left (icon) area", Size = new Vector2(height), diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs index 18e1584a98..c6ba4f234a 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs @@ -62,7 +62,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - thumbnail = new BeatmapCardThumbnail(BeatmapSet) + thumbnail = new BeatmapCardThumbnail(BeatmapSet, BeatmapSet) { Name = @"Left (icon) area", Size = new Vector2(height), diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs index e70d115715..5d2717a787 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs @@ -35,14 +35,14 @@ namespace osu.Game.Beatmaps.Drawables.Cards [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; - public BeatmapCardThumbnail(IBeatmapSetInfo beatmapSetInfo) + public BeatmapCardThumbnail(IBeatmapSetInfo beatmapSetInfo, IBeatmapSetOnlineInfo onlineInfo) { InternalChildren = new Drawable[] { new UpdateableOnlineBeatmapSetCover(BeatmapSetCoverType.List) { RelativeSizeAxes = Axes.Both, - OnlineInfo = beatmapSetInfo as IBeatmapSetOnlineInfo + OnlineInfo = onlineInfo }, background = new Box { diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 72866d1694..e9126a1404 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -286,7 +286,7 @@ namespace osu.Game.Screens.OnlinePlay { difficultyIconContainer.Children = new Drawable[] { - thumbnail = new BeatmapCardThumbnail(beatmap.BeatmapSet!) + thumbnail = new BeatmapCardThumbnail(beatmap.BeatmapSet!, (IBeatmapSetOnlineInfo)beatmap.BeatmapSet!) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, From aad0982e26ba3fe57caf1df7999d7d582c413097 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 May 2024 20:33:24 +0900 Subject: [PATCH 4035/4092] Adjust size of play button / progress to match `BeatmapCardThumbnail` usage --- osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs index 5d2717a787..7b668d7dc4 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs @@ -61,7 +61,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(50), InnerRadius = 0.2f }, content = new Container @@ -92,6 +91,9 @@ namespace osu.Game.Beatmaps.Drawables.Cards { base.Update(); progress.Progress = playButton.Progress.Value; + + playButton.Scale = new Vector2(DrawWidth / 100); + progress.Size = new Vector2(50 * DrawWidth / 100); } private void updateState() From 405c72c0d66ef895cbb5d34aa6c32e5d81dd5d2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 27 May 2024 13:36:44 +0200 Subject: [PATCH 4036/4092] Fix mod display not being aligned with mapper text --- osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index e9126a1404..ab32ca2558 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -416,6 +416,8 @@ namespace osu.Game.Screens.OnlinePlay new FillFlowContainer { AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, Direction = FillDirection.Horizontal, Spacing = new Vector2(10f, 0), Children = new Drawable[] @@ -438,7 +440,8 @@ namespace osu.Game.Screens.OnlinePlay Child = modDisplay = new ModDisplay { Scale = new Vector2(0.4f), - ExpansionMode = ExpansionMode.AlwaysExpanded + ExpansionMode = ExpansionMode.AlwaysExpanded, + Margin = new MarginPadding { Vertical = -6 }, } } } From d81be56adf67a46c0302695b273ac7c0f6c8e212 Mon Sep 17 00:00:00 2001 From: Aurelian Date: Mon, 27 May 2024 19:35:33 +0200 Subject: [PATCH 4037/4092] Test for accuracy of perfect curves --- .../Editor/TestSliderScaling.cs | 125 ++++++++++++++---- 1 file changed, 99 insertions(+), 26 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs index ef3824b5b0..2ffde0d3a3 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs @@ -3,8 +3,10 @@ #nullable disable +using System; using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics.Primitives; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; @@ -72,48 +74,119 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private void moveMouse(Vector2 pos) => AddStep($"move mouse to {pos}", () => InputManager.MoveMouseTo(playfield.ToScreenSpace(pos))); } + [TestFixture] public class TestSliderNearLinearScaling { + private readonly Random rng = new Random(1337); + [Test] public void TestScalingSliderFlat() { - Slider sliderPerfect = new Slider - { - Position = new Vector2(300), - Path = new SliderPath( - [ - new PathControlPoint(new Vector2(0), PathType.PERFECT_CURVE), - new PathControlPoint(new Vector2(50, 25)), - new PathControlPoint(new Vector2(25, 100)), - ]) - }; + SliderPath sliderPathPerfect = new SliderPath( + [ + new PathControlPoint(new Vector2(0), PathType.PERFECT_CURVE), + new PathControlPoint(new Vector2(50, 25)), + new PathControlPoint(new Vector2(25, 100)), + ]); - Slider sliderBezier = new Slider - { - Position = new Vector2(300), - Path = new SliderPath( - [ - new PathControlPoint(new Vector2(0), PathType.BEZIER), - new PathControlPoint(new Vector2(50, 25)), - new PathControlPoint(new Vector2(25, 100)), - ]) - }; + SliderPath sliderPathBezier = new SliderPath( + [ + new PathControlPoint(new Vector2(0), PathType.BEZIER), + new PathControlPoint(new Vector2(50, 25)), + new PathControlPoint(new Vector2(25, 100)), + ]); - scaleSlider(sliderPerfect, new Vector2(0.000001f, 1)); - scaleSlider(sliderBezier, new Vector2(0.000001f, 1)); + scaleSlider(sliderPathPerfect, new Vector2(0.000001f, 1)); + scaleSlider(sliderPathBezier, new Vector2(0.000001f, 1)); for (int i = 0; i < 100; i++) { - Assert.True(Precision.AlmostEquals(sliderPerfect.Path.PositionAt(i / 100.0f), sliderBezier.Path.PositionAt(i / 100.0f))); + Assert.True(Precision.AlmostEquals(sliderPathPerfect.PositionAt(i / 100.0f), sliderPathBezier.PositionAt(i / 100.0f))); } } - private void scaleSlider(Slider slider, Vector2 scale) + [Test] + public void TestPerfectCurveMatchesTheoretical() { - for (int i = 0; i < slider.Path.ControlPoints.Count; i++) + for (int i = 0; i < 20000; i++) { - slider.Path.ControlPoints[i].Position *= scale; + //Only test points that are in the screen's bounds + float p1X = 640.0f * (float)rng.NextDouble(); + float p2X = 640.0f * (float)rng.NextDouble(); + + float p1Y = 480.0f * (float)rng.NextDouble(); + float p2Y = 480.0f * (float)rng.NextDouble(); + SliderPath sliderPathPerfect = new SliderPath( + [ + new PathControlPoint(new Vector2(0, 0), PathType.PERFECT_CURVE), + new PathControlPoint(new Vector2(p1X, p1Y)), + new PathControlPoint(new Vector2(p2X, p2Y)), + ]); + + assertMatchesPerfectCircle(sliderPathPerfect); + + scaleSlider(sliderPathPerfect, new Vector2(0.00001f, 1)); + + assertMatchesPerfectCircle(sliderPathPerfect); + } + } + + private void assertMatchesPerfectCircle(SliderPath path) + { + if (path.ControlPoints.Count != 3) + return; + + //Replication of PathApproximator.CircularArcToPiecewiseLinear + CircularArcProperties circularArcProperties = new CircularArcProperties(path.ControlPoints.Select(x => x.Position).ToArray()); + + if (!circularArcProperties.IsValid) + return; + + //Addresses cases where circularArcProperties.ThetaRange>0.5 + //Occurs in code in PathControlPointVisualiser.ensureValidPathType + RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(path.ControlPoints.Select(x => x.Position).ToArray()); + if (boundingBox.Width >= 640 || boundingBox.Height >= 480) + return; + + int subpoints = (2f * circularArcProperties.Radius <= 0.1f) ? 2 : Math.Max(2, (int)Math.Ceiling(circularArcProperties.ThetaRange / (2.0 * Math.Acos(1f - 0.1f / circularArcProperties.Radius)))); + + //ignore cases where subpoints is int.MaxValue, result will be garbage + //as well, having this many subpoints will cause an out of memory error, so can't happen during normal useage + if (subpoints == int.MaxValue) + return; + + for (int i = 0; i < Math.Min(subpoints, 100); i++) + { + float progress = (float)rng.NextDouble(); + + //To avoid errors from interpolating points, ensure we check only positions that would be subpoints. + progress = (float)Math.Ceiling(progress * (subpoints - 1)) / (subpoints - 1); + + //Special case - if few subpoints, ensure checking every single one rather than randomly + if (subpoints < 100) + progress = i / (float)(subpoints - 1); + + //edge points cause issue with interpolation, so ignore the last two points and first + if (progress == 0.0f || progress >= (subpoints - 2) / (float)(subpoints - 1)) + continue; + + double theta = circularArcProperties.ThetaStart + circularArcProperties.Direction * progress * circularArcProperties.ThetaRange; + Vector2 vector = new Vector2((float)Math.Cos(theta), (float)Math.Sin(theta)) * circularArcProperties.Radius; + + Assert.True(Precision.AlmostEquals(circularArcProperties.Centre + vector, path.PositionAt(progress), 0.01f), + "A perfect circle with points " + string.Join(", ", path.ControlPoints.Select(x => x.Position)) + " and radius" + circularArcProperties.Radius + "from SliderPath does not almost equal a theoretical perfect circle with " + subpoints + " subpoints" + + ": " + (circularArcProperties.Centre + vector) + " - " + path.PositionAt(progress) + + " = " + (circularArcProperties.Centre + vector - path.PositionAt(progress)) + ); + } + } + + private void scaleSlider(SliderPath path, Vector2 scale) + { + for (int i = 0; i < path.ControlPoints.Count; i++) + { + path.ControlPoints[i].Position *= scale; } } } From 172cfdf88d4fb6ffb1b4dd35c5183b272407629e Mon Sep 17 00:00:00 2001 From: Aurelian Date: Mon, 27 May 2024 19:41:38 +0200 Subject: [PATCH 4038/4092] Added missing brackets for formulas --- osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs index 2ffde0d3a3..52a170b84e 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs @@ -149,7 +149,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor if (boundingBox.Width >= 640 || boundingBox.Height >= 480) return; - int subpoints = (2f * circularArcProperties.Radius <= 0.1f) ? 2 : Math.Max(2, (int)Math.Ceiling(circularArcProperties.ThetaRange / (2.0 * Math.Acos(1f - 0.1f / circularArcProperties.Radius)))); + int subpoints = (2f * circularArcProperties.Radius <= 0.1f) ? 2 : Math.Max(2, (int)Math.Ceiling(circularArcProperties.ThetaRange / (2.0 * Math.Acos(1f - (0.1f / circularArcProperties.Radius))))); //ignore cases where subpoints is int.MaxValue, result will be garbage //as well, having this many subpoints will cause an out of memory error, so can't happen during normal useage @@ -171,7 +171,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor if (progress == 0.0f || progress >= (subpoints - 2) / (float)(subpoints - 1)) continue; - double theta = circularArcProperties.ThetaStart + circularArcProperties.Direction * progress * circularArcProperties.ThetaRange; + double theta = circularArcProperties.ThetaStart + (circularArcProperties.Direction * progress * circularArcProperties.ThetaRange); Vector2 vector = new Vector2((float)Math.Cos(theta), (float)Math.Sin(theta)) * circularArcProperties.Radius; Assert.True(Precision.AlmostEquals(circularArcProperties.Centre + vector, path.PositionAt(progress), 0.01f), From 6c4def1c097298abbda31be5338ba791cd12dc1d Mon Sep 17 00:00:00 2001 From: Aurelian Date: Mon, 27 May 2024 20:32:18 +0200 Subject: [PATCH 4039/4092] Added check for infinite subpoints for PerfectCurve --- osu.Game/Rulesets/Objects/SliderPath.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index eca14269fe..aa2570c336 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -330,11 +330,18 @@ namespace osu.Game.Rulesets.Objects if (subControlPoints.Length != 3) break; - //If a curve's theta range almost equals zero, the radius needed to have more than a - //floating point error difference is very large and results in a nearly straight path. - //Calculate it via a bezier aproximation instead. - //0.0005 corresponds with a radius of 8000 to have a more than 0.001 shift in the X value - if (Math.Abs(new CircularArcProperties(subControlPoints).ThetaRange) <= 0.0005d) + CircularArcProperties circularArcProperties = new CircularArcProperties(subControlPoints); + + //if false, we'll end up breaking anyways when calculating subPath + if (!circularArcProperties.IsValid) + break; + + //Coppied from PathApproximator.CircularArcToPiecewiseLinear + int subPoints = (2f * circularArcProperties.Radius <= 0.1f) ? 2 : Math.Max(2, (int)Math.Ceiling(circularArcProperties.ThetaRange / (2.0 * Math.Acos(1f - (0.1f / circularArcProperties.Radius))))); + + //if the amount of subpoints is int.MaxValue, causes an out of memory issue, so we default to bezier + //this only occurs for very large radii, so the result should be essentially a straight line anyways + if (subPoints == int.MaxValue) break; List subPath = PathApproximator.CircularArcToPiecewiseLinear(subControlPoints); From 96af0e1ec3c6c84ecd28178b5ae1b8ff6f4d3b43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 28 May 2024 13:07:11 +0200 Subject: [PATCH 4040/4092] Add failing test case for catch conversion Test is an abridged / cropped version of https://osu.ppy.sh/beatmapsets/971028#fruits/2062131 to demonstrate the specific failure case (unfortunately can't use the whole beatmap due to other conversion failures). --- .../CatchBeatmapConversionTest.cs | 1 + ...tiplier-precision-expected-conversion.json | 1 + .../high-speed-multiplier-precision.osu | 238 ++++++++++++++++++ 3 files changed, 240 insertions(+) create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/high-speed-multiplier-precision-expected-conversion.json create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/high-speed-multiplier-precision.osu diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs index 81e0675aaa..f4c36d5188 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs @@ -54,6 +54,7 @@ namespace osu.Game.Rulesets.Catch.Tests [TestCase("3949367", new[] { typeof(CatchModDoubleTime), typeof(CatchModEasy) })] [TestCase("112643")] [TestCase("1041052", new[] { typeof(CatchModHardRock) })] + [TestCase("high-speed-multiplier-precision")] public new void Test(string name, params Type[] mods) => base.Test(name, mods); protected override IEnumerable CreateConvertValue(HitObject hitObject) diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/high-speed-multiplier-precision-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/high-speed-multiplier-precision-expected-conversion.json new file mode 100644 index 0000000000..a562074fe9 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/high-speed-multiplier-precision-expected-conversion.json @@ -0,0 +1 @@ +{"Mappings":[{"StartTime":265568.0,"Objects":[{"StartTime":265568.0,"Position":486.0,"HyperDash":false},{"StartTime":265658.0,"Position":465.1873,"HyperDash":false},{"StartTime":265749.0,"Position":463.208435,"HyperDash":false},{"StartTime":265840.0,"Position":465.146484,"HyperDash":false},{"StartTime":265967.0,"Position":459.5862,"HyperDash":false}]}]} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/high-speed-multiplier-precision.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/high-speed-multiplier-precision.osu new file mode 100644 index 0000000000..ff641d9b0a --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/high-speed-multiplier-precision.osu @@ -0,0 +1,238 @@ +osu file format v14 + +[General] +AudioFilename: audio.mp3 +AudioLeadIn: 0 +PreviewTime: 226943 +Countdown: 0 +SampleSet: Soft +StackLeniency: 0.7 +Mode: 2 +LetterboxInBreaks: 0 +WidescreenStoryboard: 1 + +[Editor] +Bookmarks: 85568,86768,90968,265568 +DistanceSpacing: 0.9 +BeatDivisor: 12 +GridSize: 16 +TimelineZoom: 1 + +[Metadata] +Title:Snow +TitleUnicode:Snow +Artist:Ricky Montgomery +ArtistUnicode:Ricky Montgomery +Creator:Crowley +Version:Bury Me Six Feet in Snow +Source: +Tags:indie the honeysticks alternative english +BeatmapID:2062131 +BeatmapSetID:971028 + +[Difficulty] +HPDrainRate:6 +CircleSize:4.2 +OverallDifficulty:8.3 +ApproachRate:8.3 +SliderMultiplier:3.59999990463257 +SliderTickRate:1 + +[Events] +//Background and Video events +0,0,"me.jpg",0,0 +//Break Periods +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Layer 4 (Overlay) +//Storyboard Sound Samples + +[TimingPoints] +368,1200,2,2,1,30,1,0 +368,-66.6666666666667,2,2,1,30,0,0 +29168,-58.8235294117647,2,2,1,40,0,0 +30368,-58.8235294117647,2,2,2,40,0,0 +30568,-58.8235294117647,2,2,1,40,0,0 +31368,-58.8235294117647,2,2,2,40,0,0 +31568,-58.8235294117647,2,2,1,40,0,0 +32768,-58.8235294117647,2,2,2,40,0,0 +33568,-58.8235294117647,2,2,2,40,0,0 +33968,-58.8235294117647,2,2,1,40,0,0 +35168,-58.8235294117647,2,2,2,40,0,0 +35968,-58.8235294117647,2,2,1,40,0,0 +36168,-58.8235294117647,2,2,2,40,0,0 +36368,-58.8235294117647,2,2,1,40,0,0 +37568,-58.8235294117647,2,2,2,40,0,0 +37968,-58.8235294117647,2,2,1,40,0,0 +38368,-58.8235294117647,2,2,2,40,0,0 +38768,-58.8235294117647,2,2,1,40,0,0 +39968,-58.8235294117647,2,2,2,40,0,0 +40168,-58.8235294117647,2,2,1,40,0,0 +40968,-58.8235294117647,2,2,2,40,0,0 +41168,-58.8235294117647,2,2,1,40,0,0 +42368,-58.8235294117647,2,2,2,40,0,0 +43168,-58.8235294117647,2,2,2,40,0,0 +43568,-58.8235294117647,2,2,1,40,0,0 +44768,-58.8235294117647,2,2,2,40,0,0 +45768,-58.8235294117647,2,2,2,40,0,0 +45968,-58.8235294117647,2,2,1,50,0,0 +47168,-58.8235294117647,2,2,2,50,0,0 +48368,-62.5,2,2,1,50,0,0 +67568,-58.8235294117647,2,2,1,70,0,1 +84668,-58.8235294117647,2,2,1,5,0,1 +84768,-58.8235294117647,2,2,1,70,0,1 +85068,-58.8235294117647,2,2,1,5,0,1 +85168,-58.8235294117647,2,2,1,70,0,1 +85468,-58.8235294117647,2,2,1,5,0,1 +85568,-58.8235294117647,2,2,1,70,0,1 +86768,-58.8235294117647,2,2,1,30,0,0 +91168,-58.8235294117647,2,2,1,50,0,0 +91568,1200,2,2,1,50,1,0 +91568,-58.8235294117647,2,2,1,50,0,1 +91643,-58.8235294117647,2,2,1,50,0,0 +92768,-58.8235294117647,2,2,2,50,0,0 +92968,-58.8235294117647,2,2,1,50,0,0 +95168,-58.8235294117647,2,2,2,50,0,0 +95368,-58.8235294117647,2,2,1,50,0,0 +97568,-58.8235294117647,2,2,2,50,0,0 +97768,-58.8235294117647,2,2,1,50,0,0 +99968,-58.8235294117647,2,2,2,50,0,0 +100168,-58.8235294117647,2,2,1,50,0,0 +100768,-58.8235294117647,2,2,2,50,0,0 +101168,-58.8235294117647,2,2,1,50,0,0 +102368,-58.8235294117647,2,2,2,50,0,0 +102568,-58.8235294117647,2,2,1,50,0,0 +104768,-58.8235294117647,2,2,2,50,0,0 +104968,-58.8235294117647,2,2,1,50,0,0 +107168,-58.8235294117647,2,2,2,50,0,0 +107368,-58.8235294117647,2,2,1,50,0,0 +108968,-58.8235294117647,2,2,2,50,0,0 +109168,-58.8235294117647,2,2,1,50,0,0 +109568,-58.8235294117647,2,2,2,50,0,0 +109968,-58.8235294117647,2,2,1,50,0,0 +110368,-58.8235294117647,2,2,2,50,0,0 +110768,-100,2,2,1,40,0,0 +127568,-62.5,2,2,2,50,0,0 +127968,-62.5,2,2,1,50,0,0 +128168,-62.5,2,2,2,50,0,0 +129968,-58.8235294117647,2,2,1,50,0,0 +131168,-58.8235294117647,2,2,2,50,0,0 +131368,-58.8235294117647,2,2,1,50,0,0 +133568,-58.8235294117647,2,2,2,50,0,0 +133768,-58.8235294117647,2,2,1,50,0,0 +135968,-58.8235294117647,2,2,2,50,0,0 +136168,-58.8235294117647,2,2,1,50,0,0 +138368,-58.8235294117647,2,2,2,50,0,0 +138568,-58.8235294117647,2,2,1,50,0,0 +139168,-58.8235294117647,2,2,2,50,0,0 +139368,-58.8235294117647,2,2,1,50,0,0 +139568,-58.8235294117647,2,2,1,50,0,0 +140768,-58.8235294117647,2,2,2,50,0,0 +140968,-58.8235294117647,2,2,1,50,0,0 +143168,-58.8235294117647,2,2,2,50,0,0 +143368,-58.8235294117647,2,2,1,50,0,0 +145568,-58.8235294117647,2,2,2,50,0,0 +145768,-58.8235294117647,2,2,1,50,0,0 +147368,-58.8235294117647,2,2,2,50,0,0 +147768,-58.8235294117647,2,2,1,50,0,0 +147968,-58.8235294117647,2,2,1,60,0,0 +148768,-58.8235294117647,2,2,2,60,0,0 +149168,-58.8235294117647,2,2,1,70,0,1 +158268,-58.8235294117647,2,2,2,70,0,1 +158568,-58.8235294117647,2,2,1,70,0,1 +166268,-58.8235294117647,2,2,1,5,0,1 +166368,-58.8235294117647,2,2,1,70,0,1 +166668,-58.8235294117647,2,2,1,5,0,1 +166768,-58.8235294117647,2,2,1,70,0,1 +167068,-58.8235294117647,2,2,1,5,0,1 +167168,-58.8235294117647,2,2,1,70,0,1 +168368,-62.5,2,2,1,50,0,0 +172368,-62.5,2,2,1,50,0,1 +173168,-62.5,2,2,1,50,0,0 +185168,-62.5,2,2,1,60,0,0 +185468,-62.5,2,2,1,5,0,0 +185568,-62.5,2,2,1,60,0,0 +185868,-62.5,2,2,1,5,0,0 +185968,-62.5,2,2,1,60,0,0 +186268,-62.5,2,2,1,5,0,0 +186368,-62.5,2,2,1,60,0,0 +186668,-62.5,2,2,1,5,0,0 +186768,-52.6315789473684,2,2,1,60,0,0 +187068,-62.5,2,2,1,5,0,0 +187168,-62.5,2,2,1,60,0,0 +187468,-62.5,2,2,1,5,0,0 +187568,-62.5,2,2,1,20,0,0 +187768,-62.5,2,2,1,24,0,0 +187968,-62.5,2,2,1,28,0,0 +188168,-62.5,2,2,1,32,0,0 +188368,-62.5,2,2,1,36,0,0 +188568,-62.5,2,2,1,40,0,0 +188768,1200,2,2,1,50,1,1 +188768,-58.8235294117647,2,2,1,50,0,1 +188843,-58.8235294117647,2,2,1,50,0,0 +189968,-58.8235294117647,2,2,2,50,0,0 +190168,-58.8235294117647,2,2,1,50,0,0 +192368,-58.8235294117647,2,2,2,50,0,0 +192568,-58.8235294117647,2,2,1,50,0,0 +194768,-58.8235294117647,2,2,2,50,0,0 +194968,-58.8235294117647,2,2,1,50,0,0 +196568,-58.8235294117647,2,2,2,50,0,0 +196768,-58.8235294117647,2,2,1,50,0,0 +197168,-58.8235294117647,2,2,2,50,0,0 +197368,-58.8235294117647,2,2,1,50,0,0 +197568,-58.8235294117647,2,2,2,50,0,0 +197968,-58.8235294117647,2,2,1,50,0,0 +198368,-58.8235294117647,2,2,1,50,0,0 +199568,-58.8235294117647,2,2,2,50,0,0 +199768,-58.8235294117647,2,2,1,50,0,0 +201968,-58.8235294117647,2,2,2,50,0,0 +202168,-58.8235294117647,2,2,1,50,0,0 +204368,-58.8235294117647,2,2,2,50,0,0 +204568,-58.8235294117647,2,2,1,50,0,0 +206768,-58.8235294117647,2,2,1,60,0,0 +207168,-58.8235294117647,2,2,2,60,0,0 +207968,-58.8235294117647,2,2,1,70,0,1 +216968,-58.8235294117647,2,2,2,70,0,1 +217168,-58.8235294117647,2,2,1,70,0,1 +217368,-58.8235294117647,2,2,2,70,0,1 +217568,-58.8235294117647,2,2,1,70,0,1 +225068,-58.8235294117647,2,2,1,5,0,1 +225168,-58.8235294117647,2,2,1,70,0,1 +225468,-58.8235294117647,2,2,1,5,0,1 +225568,-58.8235294117647,2,2,1,70,0,1 +225868,-58.8235294117647,2,2,1,5,0,1 +225968,-58.8235294117647,2,2,1,70,0,1 +227168,-58.8235294117647,2,2,1,30,0,0 +234368,-58.8235294117647,2,2,1,40,0,0 +236768,-58.8235294117647,2,2,1,70,0,1 +255968,-58.8235294117647,2,2,1,70,0,1 +261168,-58.8235294117647,2,2,1,70,0,1 +263068,-58.8235294117647,2,2,1,70,0,0 +263168,-58.8235294117647,2,2,1,60,0,1 +263243,-58.8235294117647,2,2,1,60,0,0 +264368,-58.8235294117647,2,2,1,60,0,1 +264443,-58.8235294117647,2,2,1,60,0,0 +265568,-444.444444444444,2,2,1,50,0,1 +265643,-444.444444444444,2,2,1,50,0,0 +266768,-444.444444444444,2,2,1,40,0,0 +267968,-444.444444444444,2,2,1,30,0,0 +269168,-444.444444444444,2,2,1,20,0,0 +270368,-444.444444444444,2,2,1,10,0,0 +271168,-444.444444444444,2,2,1,9,0,0 +271568,-444.444444444444,2,2,1,8,0,0 +271968,-444.444444444444,2,2,1,7,0,0 +272368,-444.444444444444,2,2,1,6,0,0 +272768,-444.444444444444,2,2,1,5,0,0 +275168,-444.444444444444,2,2,1,5,0,0 + + +[Colours] +Combo1 : 255,128,128 +Combo2 : 72,72,255 +Combo3 : 192,192,192 +Combo4 : 255,136,79 + +[HitObjects] +486,179,265568,6,0,P|461:174|454:174,1,26.999997997284,6|0,1:2|0:0,0:0:0:0: From a3b849375101c5ff4461fb78b92adb9e290af03a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 28 May 2024 13:07:13 +0200 Subject: [PATCH 4041/4092] Remove rounding of slider velocity multiplier on juice streams Compare: https://github.com/ppy/osu/pull/26616 This came up elsewhere, namely in https://github.com/ppy/osu/pull/28277#issuecomment-2133505958. As it turns out, at least one beatmap among those whose scores had unexpected changes in total score, namely https://osu.ppy.sh/beatmapsets/971028#fruits/2062131, was using slider velocity multipliers that were not a multiple of 0.01 (the specific value used was 0.225x). This meant that due to the rounding applied to `SliderVelocityMultiplierBindable` via `Precision`, the raw value was being incorrectly rounded, resulting in incorrect conversion. The "direct" change that revealed this is most likely https://github.com/ppy/osu-framework/pull/6249, by the virtue of shuffling the `BindableNumber` rounding code around and accidentally changing midpoint rounding semantics in the process. But it was not at fault here, as rounding was just as wrong before that change as after in this specific context. --- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 671291ef0e..dade129038 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -29,7 +29,6 @@ namespace osu.Game.Rulesets.Catch.Objects public BindableNumber SliderVelocityMultiplierBindable { get; } = new BindableDouble(1) { - Precision = 0.01, MinValue = 0.1, MaxValue = 10 }; From c2e7cdfdccf1aaa83c5e9ab78cdf778547580951 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 May 2024 21:29:29 +0900 Subject: [PATCH 4042/4092] 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 1f241c6db5..3a20dd2fdb 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 eba9abd3b8..2f64fcefa5 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From bf0040447cfaefb6f5a56a399391ba18242f2549 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 28 May 2024 15:24:37 +0200 Subject: [PATCH 4043/4092] Fix legacy mania note body animation not resetting sometimes Hopefully closes https://github.com/ppy/osu/issues/28284. As far as I can tell this is a somewhat difficult one to reproduce because it relies on a specific set of circumstances (at least the reproduction case that I found does). The reset to frame 0 would previously be called explicitly when `isHitting` changed: https://github.com/ppy/osu/blob/182ca145c78432f4b832c8ea407e107dfeaaa8ad/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs#L144 However, it can be the case that `bodyAnimation` is not loaded at the point of this call. This is significant because `SkinnableTextureAnimation` contains this logic: https://github.com/ppy/osu/blob/182ca145c78432f4b832c8ea407e107dfeaaa8ad/osu.Game/Skinning/LegacySkinExtensions.cs#L192-L211 which cannot be moved any earlier (because any earlier the `Clock` may no longer be correct), and also causes the animation to be seeked forward while it is stopped. I can't figure out a decent way to layer this otherwise (by scheduling or whatever), so this commit is just applying the nuclear option of just seeking back to frame 0 on every update frame in which the body piece is not being hit. --- osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs index a8200e0144..00054f6be2 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs @@ -140,10 +140,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy private void onIsHittingChanged(ValueChangedEvent isHitting) { if (bodySprite is TextureAnimation bodyAnimation) - { - bodyAnimation.GotoFrame(0); bodyAnimation.IsPlaying = isHitting.NewValue; - } if (lightContainer == null) return; @@ -219,6 +216,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy { base.Update(); + if (!isHitting.Value) + (bodySprite as TextureAnimation)?.GotoFrame(0); + if (holdNote.Body.HasHoldBreak) missFadeTime.Value = holdNote.Body.Result.TimeAbsolute; From 36453f621513ee8e7ad5971f3bc7c1a39404d700 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 28 May 2024 15:56:59 +0200 Subject: [PATCH 4044/4092] Change scale hotkey to Ctrl+T --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 394cb98089..fd8e6fd6d0 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -142,7 +142,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.MouseWheelRight }, GlobalAction.EditorCyclePreviousBeatSnapDivisor), new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.MouseWheelLeft }, GlobalAction.EditorCycleNextBeatSnapDivisor), new KeyBinding(new[] { InputKey.Control, InputKey.R }, GlobalAction.EditorToggleRotateControl), - new KeyBinding(new[] { InputKey.S }, GlobalAction.EditorToggleScaleControl), + new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.EditorToggleScaleControl), }; private static IEnumerable inGameKeyBindings => new[] From a89ba33b475a4f73167f0c81e53eec8f082c16ec Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 28 May 2024 16:14:16 +0200 Subject: [PATCH 4045/4092] rename CanScaleSelectionOrigin/PlayfieldOrigin to make clear its not the origin being scaled --- .../Edit/OsuSelectionRotationHandler.cs | 4 ++-- osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs | 4 ++-- osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs | 2 +- osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 2 +- osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs | 10 +++++----- .../Visual/Editing/TestSceneComposeSelectBox.cs | 2 +- .../SkinEditor/SkinSelectionRotationHandler.cs | 2 +- .../Screens/Edit/Compose/Components/SelectionBox.cs | 2 +- .../Compose/Components/SelectionRotationHandler.cs | 4 ++-- .../Edit/Compose/Components/SelectionScaleHandler.cs | 4 ++-- 10 files changed, 18 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs index d48bc6a90b..b581e3fdea 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs @@ -41,8 +41,8 @@ namespace osu.Game.Rulesets.Osu.Edit private void updateState() { var quad = GeometryUtils.GetSurroundingQuad(selectedMovableObjects); - CanRotateSelectionOrigin.Value = quad.Width > 0 || quad.Height > 0; - CanRotatePlayfieldOrigin.Value = selectedMovableObjects.Any(); + CanRotateFromSelectionOrigin.Value = quad.Width > 0 || quad.Height > 0; + CanRotateFromPlayfieldOrigin.Value = selectedMovableObjects.Any(); } private OsuHitObject[]? objectsInRotation; diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index e45494977f..a9cbc1b8f1 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -53,8 +53,8 @@ namespace osu.Game.Rulesets.Osu.Edit CanScaleX.Value = quad.Width > 0; CanScaleY.Value = quad.Height > 0; CanScaleDiagonally.Value = CanScaleX.Value && CanScaleY.Value; - CanScaleSelectionOrigin.Value = CanScaleX.Value || CanScaleY.Value; - CanScalePlayfieldOrigin.Value = selectedMovableObjects.Any(); + CanScaleFromSelectionOrigin.Value = CanScaleX.Value || CanScaleY.Value; + CanScaleFromPlayfieldOrigin.Value = selectedMovableObjects.Any(); } private Dictionary? objectsInScale; diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs index 812d622ae5..ea6b28b215 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.Edit angleInput.Current.BindValueChanged(angle => rotationInfo.Value = rotationInfo.Value with { Degrees = angle.NewValue }); rotationOrigin.Items.First().Select(); - rotationHandler.CanRotateSelectionOrigin.BindValueChanged(e => + rotationHandler.CanRotateFromSelectionOrigin.BindValueChanged(e => { selectionCentreButton.Selected.Disabled = !e.NewValue; }, true); diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index b76d778b3d..124a79390a 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Osu.Edit xCheckBox.Current.BindValueChanged(x => setAxis(x.NewValue, yCheckBox.Current.Value)); yCheckBox.Current.BindValueChanged(y => setAxis(xCheckBox.Current.Value, y.NewValue)); - scaleHandler.CanScaleSelectionOrigin.BindValueChanged(e => + scaleHandler.CanScaleFromSelectionOrigin.BindValueChanged(e => { selectionCentreButton.Selected.Disabled = !e.NewValue; }, true); diff --git a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs index e1f53846dc..67baf7d165 100644 --- a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs @@ -64,15 +64,15 @@ namespace osu.Game.Rulesets.Osu.Edit base.LoadComplete(); // aggregate two values into canRotate - canRotatePlayfieldOrigin = RotationHandler.CanRotatePlayfieldOrigin.GetBoundCopy(); + canRotatePlayfieldOrigin = RotationHandler.CanRotateFromPlayfieldOrigin.GetBoundCopy(); canRotatePlayfieldOrigin.BindValueChanged(_ => updateCanRotateAggregate()); - canRotateSelectionOrigin = RotationHandler.CanRotateSelectionOrigin.GetBoundCopy(); + canRotateSelectionOrigin = RotationHandler.CanRotateFromSelectionOrigin.GetBoundCopy(); canRotateSelectionOrigin.BindValueChanged(_ => updateCanRotateAggregate()); void updateCanRotateAggregate() { - canRotate.Value = RotationHandler.CanRotatePlayfieldOrigin.Value || RotationHandler.CanRotateSelectionOrigin.Value; + canRotate.Value = RotationHandler.CanRotateFromPlayfieldOrigin.Value || RotationHandler.CanRotateFromSelectionOrigin.Value; } // aggregate three values into canScale @@ -82,12 +82,12 @@ namespace osu.Game.Rulesets.Osu.Edit canScaleY = ScaleHandler.CanScaleY.GetBoundCopy(); canScaleY.BindValueChanged(_ => updateCanScaleAggregate()); - canScalePlayfieldOrigin = ScaleHandler.CanScalePlayfieldOrigin.GetBoundCopy(); + canScalePlayfieldOrigin = ScaleHandler.CanScaleFromPlayfieldOrigin.GetBoundCopy(); canScalePlayfieldOrigin.BindValueChanged(_ => updateCanScaleAggregate()); void updateCanScaleAggregate() { - canScale.Value = ScaleHandler.CanScaleX.Value || ScaleHandler.CanScaleY.Value || ScaleHandler.CanScalePlayfieldOrigin.Value; + canScale.Value = ScaleHandler.CanScaleX.Value || ScaleHandler.CanScaleY.Value || ScaleHandler.CanScaleFromPlayfieldOrigin.Value; } // bindings to `Enabled` on the buttons are decoupled on purpose diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs index 6bd6c4a8c4..d12f7ebde9 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.Editing { this.getTargetContainer = getTargetContainer; - CanRotateSelectionOrigin.Value = true; + CanRotateFromSelectionOrigin.Value = true; } [CanBeNull] diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs index 7ecf116b68..3a3eb9457b 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs @@ -41,7 +41,7 @@ namespace osu.Game.Overlays.SkinEditor private void updateState() { - CanRotateSelectionOrigin.Value = selectedItems.Count > 0; + CanRotateFromSelectionOrigin.Value = selectedItems.Count > 0; } private Drawable[]? objectsInRotation; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index 31a5c30fff..9f709f8c64 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -127,7 +127,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void load() { if (rotationHandler != null) - canRotate.BindTo(rotationHandler.CanRotateSelectionOrigin); + canRotate.BindTo(rotationHandler.CanRotateFromSelectionOrigin); if (scaleHandler != null) { diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs index 459e4b0c41..8c35dc07b7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs @@ -15,12 +15,12 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Whether rotation anchored by the selection origin can currently be performed. /// - public Bindable CanRotateSelectionOrigin { get; private set; } = new BindableBool(); + public Bindable CanRotateFromSelectionOrigin { get; private set; } = new BindableBool(); /// /// Whether rotation anchored by the center of the playfield can currently be performed. /// - public Bindable CanRotatePlayfieldOrigin { get; private set; } = new BindableBool(); + public Bindable CanRotateFromPlayfieldOrigin { get; private set; } = new BindableBool(); /// /// Performs a single, instant, atomic rotation operation. diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs index 495cce7ad6..b72c3406f1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs @@ -35,12 +35,12 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Whether scaling anchored by the selection origin can currently be performed. /// - public Bindable CanScaleSelectionOrigin { get; private set; } = new BindableBool(); + public Bindable CanScaleFromSelectionOrigin { get; private set; } = new BindableBool(); /// /// Whether scaling anchored by the center of the playfield can currently be performed. /// - public Bindable CanScalePlayfieldOrigin { get; private set; } = new BindableBool(); + public Bindable CanScaleFromPlayfieldOrigin { get; private set; } = new BindableBool(); public Quad? OriginalSurroundingQuad { get; protected set; } From 8eb23f8a604818b6ce94dc0810a551c2a5455a4d Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 28 May 2024 16:19:57 +0200 Subject: [PATCH 4046/4092] remove redundant CanScaleFromSelectionOrigin --- .../Edit/OsuSelectionScaleHandler.cs | 1 - .../Edit/PreciseScalePopover.cs | 16 ++++++++++++---- .../Compose/Components/SelectionScaleHandler.cs | 5 ----- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index a9cbc1b8f1..f120c8bd75 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -53,7 +53,6 @@ namespace osu.Game.Rulesets.Osu.Edit CanScaleX.Value = quad.Width > 0; CanScaleY.Value = quad.Height > 0; CanScaleDiagonally.Value = CanScaleX.Value && CanScaleY.Value; - CanScaleFromSelectionOrigin.Value = CanScaleX.Value || CanScaleY.Value; CanScaleFromPlayfieldOrigin.Value = selectedMovableObjects.Any(); } diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index 124a79390a..dca262cf5a 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -32,6 +32,9 @@ namespace osu.Game.Rulesets.Osu.Edit private OsuCheckbox xCheckBox = null!; private OsuCheckbox yCheckBox = null!; + private Bindable canScaleX = null!; + private Bindable canScaleY = null!; + public PreciseScalePopover(SelectionScaleHandler scaleHandler) { this.scaleHandler = scaleHandler; @@ -118,10 +121,15 @@ namespace osu.Game.Rulesets.Osu.Edit xCheckBox.Current.BindValueChanged(x => setAxis(x.NewValue, yCheckBox.Current.Value)); yCheckBox.Current.BindValueChanged(y => setAxis(xCheckBox.Current.Value, y.NewValue)); - scaleHandler.CanScaleFromSelectionOrigin.BindValueChanged(e => - { - selectionCentreButton.Selected.Disabled = !e.NewValue; - }, true); + // aggregate two values into canScaleFromSelectionCentre + canScaleX = scaleHandler.CanScaleX.GetBoundCopy(); + canScaleX.BindValueChanged(_ => updateCanScaleFromSelectionCentre()); + + canScaleY = scaleHandler.CanScaleY.GetBoundCopy(); + canScaleY.BindValueChanged(_ => updateCanScaleFromSelectionCentre(), true); + + void updateCanScaleFromSelectionCentre() => + selectionCentreButton.Selected.Disabled = !(scaleHandler.CanScaleY.Value || scaleHandler.CanScaleFromPlayfieldOrigin.Value); scaleInfo.BindValueChanged(scale => { diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs index b72c3406f1..2c8b413560 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs @@ -32,11 +32,6 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public Bindable CanScaleDiagonally { get; private set; } = new BindableBool(); - /// - /// Whether scaling anchored by the selection origin can currently be performed. - /// - public Bindable CanScaleFromSelectionOrigin { get; private set; } = new BindableBool(); - /// /// Whether scaling anchored by the center of the playfield can currently be performed. /// From 7cdc755c1614ba666d63d3eba5d8062a9313e1a4 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 28 May 2024 16:57:24 +0200 Subject: [PATCH 4047/4092] Bind axis checkbox disabled state to CanScaleX/Y --- .../Edit/PreciseScalePopover.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index dca262cf5a..ac6e2849c9 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -35,6 +35,8 @@ namespace osu.Game.Rulesets.Osu.Edit private Bindable canScaleX = null!; private Bindable canScaleY = null!; + private bool scaleInProgress; + public PreciseScalePopover(SelectionScaleHandler scaleHandler) { this.scaleHandler = scaleHandler; @@ -124,15 +126,26 @@ namespace osu.Game.Rulesets.Osu.Edit // aggregate two values into canScaleFromSelectionCentre canScaleX = scaleHandler.CanScaleX.GetBoundCopy(); canScaleX.BindValueChanged(_ => updateCanScaleFromSelectionCentre()); + canScaleX.BindValueChanged(e => updateAxisCheckBoxEnabled(e.NewValue, xCheckBox.Current), true); canScaleY = scaleHandler.CanScaleY.GetBoundCopy(); canScaleY.BindValueChanged(_ => updateCanScaleFromSelectionCentre(), true); + canScaleY.BindValueChanged(e => updateAxisCheckBoxEnabled(e.NewValue, yCheckBox.Current), true); void updateCanScaleFromSelectionCentre() => selectionCentreButton.Selected.Disabled = !(scaleHandler.CanScaleY.Value || scaleHandler.CanScaleFromPlayfieldOrigin.Value); + void updateAxisCheckBoxEnabled(bool enabled, Bindable current) + { + current.Disabled = false; // enable the bindable to allow setting the value + current.Value = enabled; + current.Disabled = !enabled; + } + scaleInfo.BindValueChanged(scale => { + if (!scaleInProgress) return; + var newScale = new Vector2(scale.NewValue.XAxis ? scale.NewValue.Scale : 1, scale.NewValue.YAxis ? scale.NewValue.Scale : 1); scaleHandler.Update(newScale, getOriginPosition(scale.NewValue)); }); @@ -172,6 +185,7 @@ namespace osu.Game.Rulesets.Osu.Edit { base.PopIn(); scaleHandler.Begin(); + scaleInProgress = true; updateMaxScale(); } @@ -180,7 +194,10 @@ namespace osu.Game.Rulesets.Osu.Edit base.PopOut(); if (IsLoaded) + { scaleHandler.Commit(); + scaleInProgress = false; + } } } From d143a697d2efbd7d57fc7d7ff7d3d4e3cfc13a7d Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 28 May 2024 17:12:16 +0200 Subject: [PATCH 4048/4092] refactor CanScaleFromPlayfieldOrigin and GetClampedScale to derived class --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 2 +- .../Edit/OsuSelectionScaleHandler.cs | 13 ++++++++++++- osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 5 ++--- osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs | 2 +- .../Compose/Components/SelectionScaleHandler.cs | 13 ------------- 5 files changed, 16 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 6f3ed9730e..cc1d1fe89f 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.Edit new TransformToolboxGroup { RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, - ScaleHandler = BlueprintContainer.SelectionHandler.ScaleHandler, + ScaleHandler = (OsuSelectionScaleHandler)BlueprintContainer.SelectionHandler.ScaleHandler, }, FreehandlSliderToolboxGroup } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index f120c8bd75..32057bd7fe 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -24,6 +24,11 @@ namespace osu.Game.Rulesets.Osu.Edit { public partial class OsuSelectionScaleHandler : SelectionScaleHandler { + /// + /// Whether scaling anchored by the center of the playfield can currently be performed. + /// + public Bindable CanScaleFromPlayfieldOrigin { get; private set; } = new BindableBool(); + [Resolved] private IEditorChangeHandler? changeHandler { get; set; } @@ -156,7 +161,13 @@ namespace osu.Game.Rulesets.Osu.Edit return (xInBounds, yInBounds); } - public override Vector2 GetClampedScale(Vector2 scale, Vector2? origin = null) + /// + /// Clamp scale for multi-object-scaling where selection does not exceed playfield bounds or flip. + /// + /// The origin from which the scale operation is performed + /// The scale to be clamped + /// The clamped scale vector + public Vector2 GetClampedScale(Vector2 scale, Vector2? origin = null) { //todo: this is not always correct for selections involving sliders. This approximation assumes each point is scaled independently, but sliderends move with the sliderhead. if (objectsInScale == null) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index ac6e2849c9..f14e166d3b 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -12,14 +12,13 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit.Components.RadioButtons; -using osu.Game.Screens.Edit.Compose.Components; using osuTK; namespace osu.Game.Rulesets.Osu.Edit { public partial class PreciseScalePopover : OsuPopover { - private readonly SelectionScaleHandler scaleHandler; + private readonly OsuSelectionScaleHandler scaleHandler; private readonly Bindable scaleInfo = new Bindable(new PreciseScaleInfo(1, ScaleOrigin.PlayfieldCentre, true, true)); @@ -37,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Edit private bool scaleInProgress; - public PreciseScalePopover(SelectionScaleHandler scaleHandler) + public PreciseScalePopover(OsuSelectionScaleHandler scaleHandler) { this.scaleHandler = scaleHandler; diff --git a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs index 67baf7d165..e70be8d93c 100644 --- a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Edit private Bindable canScalePlayfieldOrigin = null!; public SelectionRotationHandler RotationHandler { get; init; } = null!; - public SelectionScaleHandler ScaleHandler { get; init; } = null!; + public OsuSelectionScaleHandler ScaleHandler { get; init; } = null!; public TransformToolboxGroup() : base("transform") diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs index 2c8b413560..a96f627e56 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs @@ -32,21 +32,8 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public Bindable CanScaleDiagonally { get; private set; } = new BindableBool(); - /// - /// Whether scaling anchored by the center of the playfield can currently be performed. - /// - public Bindable CanScaleFromPlayfieldOrigin { get; private set; } = new BindableBool(); - public Quad? OriginalSurroundingQuad { get; protected set; } - /// - /// Clamp scale where selection does not exceed playfield bounds or flip. - /// - /// The origin from which the scale operation is performed - /// The scale to be clamped - /// The clamped scale vector - public virtual Vector2 GetClampedScale(Vector2 scale, Vector2? origin = null) => scale; - /// /// Performs a single, instant, atomic scale operation. /// From 9548585b15fe4154e7e7da80d21da9779486e898 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 28 May 2024 17:24:31 +0200 Subject: [PATCH 4049/4092] fix axis checkboxes being disabled in playfield origin scale --- .../Edit/PreciseScalePopover.cs | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index f14e166d3b..d45c4020b9 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -125,21 +125,14 @@ namespace osu.Game.Rulesets.Osu.Edit // aggregate two values into canScaleFromSelectionCentre canScaleX = scaleHandler.CanScaleX.GetBoundCopy(); canScaleX.BindValueChanged(_ => updateCanScaleFromSelectionCentre()); - canScaleX.BindValueChanged(e => updateAxisCheckBoxEnabled(e.NewValue, xCheckBox.Current), true); + canScaleX.BindValueChanged(e => updateAxisCheckBoxesEnabled()); canScaleY = scaleHandler.CanScaleY.GetBoundCopy(); canScaleY.BindValueChanged(_ => updateCanScaleFromSelectionCentre(), true); - canScaleY.BindValueChanged(e => updateAxisCheckBoxEnabled(e.NewValue, yCheckBox.Current), true); + canScaleY.BindValueChanged(e => updateAxisCheckBoxesEnabled(), true); void updateCanScaleFromSelectionCentre() => - selectionCentreButton.Selected.Disabled = !(scaleHandler.CanScaleY.Value || scaleHandler.CanScaleFromPlayfieldOrigin.Value); - - void updateAxisCheckBoxEnabled(bool enabled, Bindable current) - { - current.Disabled = false; // enable the bindable to allow setting the value - current.Value = enabled; - current.Disabled = !enabled; - } + selectionCentreButton.Selected.Disabled = !(scaleHandler.CanScaleX.Value || scaleHandler.CanScaleY.Value); scaleInfo.BindValueChanged(scale => { @@ -150,6 +143,27 @@ namespace osu.Game.Rulesets.Osu.Edit }); } + private void updateAxisCheckBoxesEnabled() + { + if (scaleInfo.Value.Origin == ScaleOrigin.PlayfieldCentre) + { + setBindableEnabled(true, xCheckBox.Current); + setBindableEnabled(true, yCheckBox.Current); + } + else + { + setBindableEnabled(canScaleX.Value, xCheckBox.Current); + setBindableEnabled(canScaleY.Value, yCheckBox.Current); + } + } + + private void setBindableEnabled(bool enabled, Bindable current) + { + current.Disabled = false; // enable the bindable to allow setting the value + current.Value = enabled; + current.Disabled = !enabled; + } + private void updateMaxScale() { if (!scaleHandler.OriginalSurroundingQuad.HasValue) @@ -170,6 +184,7 @@ namespace osu.Game.Rulesets.Osu.Edit { scaleInfo.Value = scaleInfo.Value with { Origin = origin }; updateMaxScale(); + updateAxisCheckBoxesEnabled(); } private Vector2? getOriginPosition(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.PlayfieldCentre ? OsuPlayfield.BASE_SIZE / 2 : null; From 9a18ba2699883a0278adba889b2141b3e21f044b Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 28 May 2024 18:27:01 +0200 Subject: [PATCH 4050/4092] disable playfield centre origin when scaling slider and simplify logic --- .../Edit/OsuSelectionScaleHandler.cs | 6 +++ .../Edit/PreciseScalePopover.cs | 42 +++++++------------ 2 files changed, 21 insertions(+), 27 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 32057bd7fe..9d1c3bc78f 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -29,6 +29,11 @@ namespace osu.Game.Rulesets.Osu.Edit /// public Bindable CanScaleFromPlayfieldOrigin { get; private set; } = new BindableBool(); + /// + /// Whether a single slider is currently selected, which results in a different scaling behaviour. + /// + public Bindable IsScalingSlider { get; private set; } = new BindableBool(); + [Resolved] private IEditorChangeHandler? changeHandler { get; set; } @@ -59,6 +64,7 @@ namespace osu.Game.Rulesets.Osu.Edit CanScaleY.Value = quad.Height > 0; CanScaleDiagonally.Value = CanScaleX.Value && CanScaleY.Value; CanScaleFromPlayfieldOrigin.Value = selectedMovableObjects.Any(); + IsScalingSlider.Value = selectedMovableObjects.Count() == 1 && selectedMovableObjects.First() is Slider; } private Dictionary? objectsInScale; diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index d45c4020b9..b792baf428 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -26,16 +26,12 @@ namespace osu.Game.Rulesets.Osu.Edit private BindableNumber scaleInputBindable = null!; private EditorRadioButtonCollection scaleOrigin = null!; + private RadioButton playfieldCentreButton = null!; private RadioButton selectionCentreButton = null!; private OsuCheckbox xCheckBox = null!; private OsuCheckbox yCheckBox = null!; - private Bindable canScaleX = null!; - private Bindable canScaleY = null!; - - private bool scaleInProgress; - public PreciseScalePopover(OsuSelectionScaleHandler scaleHandler) { this.scaleHandler = scaleHandler; @@ -70,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Edit RelativeSizeAxes = Axes.X, Items = new[] { - new RadioButton("Playfield centre", + playfieldCentreButton = new RadioButton("Playfield centre", () => setOrigin(ScaleOrigin.PlayfieldCentre), () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), selectionCentreButton = new RadioButton("Selection centre", @@ -101,6 +97,10 @@ namespace osu.Game.Rulesets.Osu.Edit }, } }; + playfieldCentreButton.Selected.DisabledChanged += isDisabled => + { + playfieldCentreButton.TooltipText = isDisabled ? "Select something other than a single slider to perform playfield-based scaling." : string.Empty; + }; selectionCentreButton.Selected.DisabledChanged += isDisabled => { selectionCentreButton.TooltipText = isDisabled ? "Select more than one object to perform selection-based scaling." : string.Empty; @@ -117,27 +117,20 @@ namespace osu.Game.Rulesets.Osu.Edit scaleInput.SelectAll(); }); scaleInput.Current.BindValueChanged(scale => scaleInfo.Value = scaleInfo.Value with { Scale = scale.NewValue }); - scaleOrigin.Items.First().Select(); xCheckBox.Current.BindValueChanged(x => setAxis(x.NewValue, yCheckBox.Current.Value)); yCheckBox.Current.BindValueChanged(y => setAxis(xCheckBox.Current.Value, y.NewValue)); - // aggregate two values into canScaleFromSelectionCentre - canScaleX = scaleHandler.CanScaleX.GetBoundCopy(); - canScaleX.BindValueChanged(_ => updateCanScaleFromSelectionCentre()); - canScaleX.BindValueChanged(e => updateAxisCheckBoxesEnabled()); + selectionCentreButton.Selected.Disabled = !(scaleHandler.CanScaleX.Value || scaleHandler.CanScaleY.Value); + playfieldCentreButton.Selected.Disabled = scaleHandler.IsScalingSlider.Value && !selectionCentreButton.Selected.Disabled; - canScaleY = scaleHandler.CanScaleY.GetBoundCopy(); - canScaleY.BindValueChanged(_ => updateCanScaleFromSelectionCentre(), true); - canScaleY.BindValueChanged(e => updateAxisCheckBoxesEnabled(), true); - - void updateCanScaleFromSelectionCentre() => - selectionCentreButton.Selected.Disabled = !(scaleHandler.CanScaleX.Value || scaleHandler.CanScaleY.Value); + if (playfieldCentreButton.Selected.Disabled) + scaleOrigin.Items.Last().Select(); + else + scaleOrigin.Items.First().Select(); scaleInfo.BindValueChanged(scale => { - if (!scaleInProgress) return; - var newScale = new Vector2(scale.NewValue.XAxis ? scale.NewValue.Scale : 1, scale.NewValue.YAxis ? scale.NewValue.Scale : 1); scaleHandler.Update(newScale, getOriginPosition(scale.NewValue)); }); @@ -152,8 +145,8 @@ namespace osu.Game.Rulesets.Osu.Edit } else { - setBindableEnabled(canScaleX.Value, xCheckBox.Current); - setBindableEnabled(canScaleY.Value, yCheckBox.Current); + setBindableEnabled(scaleHandler.CanScaleX.Value, xCheckBox.Current); + setBindableEnabled(scaleHandler.CanScaleY.Value, yCheckBox.Current); } } @@ -199,7 +192,6 @@ namespace osu.Game.Rulesets.Osu.Edit { base.PopIn(); scaleHandler.Begin(); - scaleInProgress = true; updateMaxScale(); } @@ -207,11 +199,7 @@ namespace osu.Game.Rulesets.Osu.Edit { base.PopOut(); - if (IsLoaded) - { - scaleHandler.Commit(); - scaleInProgress = false; - } + if (IsLoaded) scaleHandler.Commit(); } } From b74f66e3351457ef7233c277c88b18db7e174f2a Mon Sep 17 00:00:00 2001 From: Aurelian Date: Tue, 28 May 2024 19:38:33 +0200 Subject: [PATCH 4051/4092] SliderBall's rotation updates based on CurvePositionAt --- .../Objects/Drawables/DrawableSliderBall.cs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs index 46f0231981..24c0d0fcf0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs @@ -10,9 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Skinning.Default; -using osu.Game.Screens.Play; using osu.Game.Skinning; -using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -63,22 +61,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.ApplyTransformsAt(time, false); } - private Vector2? lastPosition; - public void UpdateProgress(double completionProgress) { - Position = drawableSlider.HitObject.CurvePositionAt(completionProgress); + Slider slider = drawableSlider.HitObject; + Position = slider.CurvePositionAt(completionProgress); - var diff = lastPosition.HasValue ? lastPosition.Value - Position : Position - drawableSlider.HitObject.CurvePositionAt(completionProgress + 0.01f); - - bool rewinding = (Clock as IGameplayClock)?.IsRewinding == true; + //0.1 / slider.Path.Distance is the additional progress needed to ensure the diff length is 0.1 + var diff = slider.CurvePositionAt(completionProgress) - slider.CurvePositionAt(Math.Min(1, completionProgress + 0.1 / slider.Path.Distance)); // Ensure the value is substantially high enough to allow for Atan2 to get a valid angle. + // Needed for when near completion, or in case of a very short slider. if (diff.LengthFast < 0.01f) return; - ball.Rotation = -90 + (float)(-Math.Atan2(diff.X, diff.Y) * 180 / Math.PI) + (rewinding ? 180 : 0); - lastPosition = Position; + ball.Rotation = -90 + (float)(-Math.Atan2(diff.X, diff.Y) * 180 / Math.PI); } } } From 542809a748072f8438b9289027bafd018a33c5f3 Mon Sep 17 00:00:00 2001 From: Aurelian Date: Wed, 29 May 2024 09:39:46 +0200 Subject: [PATCH 4052/4092] Reduced subpoints limit to be a more practical value --- osu.Game/Rulesets/Objects/SliderPath.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index aa2570c336..730a2013b0 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -339,9 +339,10 @@ namespace osu.Game.Rulesets.Objects //Coppied from PathApproximator.CircularArcToPiecewiseLinear int subPoints = (2f * circularArcProperties.Radius <= 0.1f) ? 2 : Math.Max(2, (int)Math.Ceiling(circularArcProperties.ThetaRange / (2.0 * Math.Acos(1f - (0.1f / circularArcProperties.Radius))))); - //if the amount of subpoints is int.MaxValue, causes an out of memory issue, so we default to bezier - //this only occurs for very large radii, so the result should be essentially a straight line anyways - if (subPoints == int.MaxValue) + //theoretically can be int.MaxValue, but lets set this to a lower value anyways + //1000 requires an arc length of over 20 thousand to surpass this limit, which should be safe. + //See here for calculations https://www.desmos.com/calculator/210bwswkbb + if (subPoints >= 1000) break; List subPath = PathApproximator.CircularArcToPiecewiseLinear(subControlPoints); From 4c881b56331626feb5272e0b33442ec388765acf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 29 May 2024 09:40:29 +0200 Subject: [PATCH 4053/4092] Use better name if we're renaming --- osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs | 4 ++-- osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs | 2 +- osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs | 6 +++--- osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs | 2 +- .../Overlays/SkinEditor/SkinSelectionRotationHandler.cs | 2 +- osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs | 2 +- .../Edit/Compose/Components/SelectionRotationHandler.cs | 4 ++-- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs index b581e3fdea..7624b2f27e 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs @@ -41,8 +41,8 @@ namespace osu.Game.Rulesets.Osu.Edit private void updateState() { var quad = GeometryUtils.GetSurroundingQuad(selectedMovableObjects); - CanRotateFromSelectionOrigin.Value = quad.Width > 0 || quad.Height > 0; - CanRotateFromPlayfieldOrigin.Value = selectedMovableObjects.Any(); + CanRotateAroundSelectionOrigin.Value = quad.Width > 0 || quad.Height > 0; + CanRotateAroundPlayfieldOrigin.Value = selectedMovableObjects.Any(); } private OsuHitObject[]? objectsInRotation; diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs index ea6b28b215..3a0d3c4763 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.Edit angleInput.Current.BindValueChanged(angle => rotationInfo.Value = rotationInfo.Value with { Degrees = angle.NewValue }); rotationOrigin.Items.First().Select(); - rotationHandler.CanRotateFromSelectionOrigin.BindValueChanged(e => + rotationHandler.CanRotateAroundSelectionOrigin.BindValueChanged(e => { selectionCentreButton.Selected.Disabled = !e.NewValue; }, true); diff --git a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs index e70be8d93c..4da1593fb7 100644 --- a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs @@ -64,15 +64,15 @@ namespace osu.Game.Rulesets.Osu.Edit base.LoadComplete(); // aggregate two values into canRotate - canRotatePlayfieldOrigin = RotationHandler.CanRotateFromPlayfieldOrigin.GetBoundCopy(); + canRotatePlayfieldOrigin = RotationHandler.CanRotateAroundPlayfieldOrigin.GetBoundCopy(); canRotatePlayfieldOrigin.BindValueChanged(_ => updateCanRotateAggregate()); - canRotateSelectionOrigin = RotationHandler.CanRotateFromSelectionOrigin.GetBoundCopy(); + canRotateSelectionOrigin = RotationHandler.CanRotateAroundSelectionOrigin.GetBoundCopy(); canRotateSelectionOrigin.BindValueChanged(_ => updateCanRotateAggregate()); void updateCanRotateAggregate() { - canRotate.Value = RotationHandler.CanRotateFromPlayfieldOrigin.Value || RotationHandler.CanRotateFromSelectionOrigin.Value; + canRotate.Value = RotationHandler.CanRotateAroundPlayfieldOrigin.Value || RotationHandler.CanRotateAroundSelectionOrigin.Value; } // aggregate three values into canScale diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs index d12f7ebde9..79a808bbd2 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.Editing { this.getTargetContainer = getTargetContainer; - CanRotateFromSelectionOrigin.Value = true; + CanRotateAroundSelectionOrigin.Value = true; } [CanBeNull] diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs index 3a3eb9457b..6a118a73a8 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs @@ -41,7 +41,7 @@ namespace osu.Game.Overlays.SkinEditor private void updateState() { - CanRotateFromSelectionOrigin.Value = selectedItems.Count > 0; + CanRotateAroundSelectionOrigin.Value = selectedItems.Count > 0; } private Drawable[]? objectsInRotation; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index 9f709f8c64..fec3224fad 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -127,7 +127,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void load() { if (rotationHandler != null) - canRotate.BindTo(rotationHandler.CanRotateFromSelectionOrigin); + canRotate.BindTo(rotationHandler.CanRotateAroundSelectionOrigin); if (scaleHandler != null) { diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs index 8c35dc07b7..787716a38c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs @@ -15,12 +15,12 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Whether rotation anchored by the selection origin can currently be performed. /// - public Bindable CanRotateFromSelectionOrigin { get; private set; } = new BindableBool(); + public Bindable CanRotateAroundSelectionOrigin { get; private set; } = new BindableBool(); /// /// Whether rotation anchored by the center of the playfield can currently be performed. /// - public Bindable CanRotateFromPlayfieldOrigin { get; private set; } = new BindableBool(); + public Bindable CanRotateAroundPlayfieldOrigin { get; private set; } = new BindableBool(); /// /// Performs a single, instant, atomic rotation operation. From 4a8273b6ed8defecef61bcf6e77a937372a1de4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 29 May 2024 09:43:09 +0200 Subject: [PATCH 4054/4092] Rename another method --- osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs | 4 ++-- osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 9d1c3bc78f..de00aa6ad3 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.Edit } else { - scale = GetClampedScale(scale, actualOrigin); + scale = ClampScaleToPlayfieldBounds(scale, actualOrigin); foreach (var (ho, originalState) in objectsInScale) { @@ -173,7 +173,7 @@ namespace osu.Game.Rulesets.Osu.Edit /// The origin from which the scale operation is performed /// The scale to be clamped /// The clamped scale vector - public Vector2 GetClampedScale(Vector2 scale, Vector2? origin = null) + public Vector2 ClampScaleToPlayfieldBounds(Vector2 scale, Vector2? origin = null) { //todo: this is not always correct for selections involving sliders. This approximation assumes each point is scaled independently, but sliderends move with the sliderhead. if (objectsInScale == null) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index b792baf428..b7202b9310 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -163,7 +163,7 @@ namespace osu.Game.Rulesets.Osu.Edit return; const float max_scale = 10; - var scale = scaleHandler.GetClampedScale(new Vector2(max_scale), getOriginPosition(scaleInfo.Value)); + var scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(max_scale), getOriginPosition(scaleInfo.Value)); if (!scaleInfo.Value.XAxis) scale.X = max_scale; From bd5060965f4a9e0fcbed9fc6044126e66e5d36d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 29 May 2024 09:49:16 +0200 Subject: [PATCH 4055/4092] Simplify toolbox button enable logic --- .../Edit/TransformToolboxGroup.cs | 38 +++++-------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs index 4da1593fb7..278d38b2d9 100644 --- a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs @@ -18,8 +18,8 @@ namespace osu.Game.Rulesets.Osu.Edit { public partial class TransformToolboxGroup : EditorToolboxGroup, IKeyBindingHandler { - private readonly Bindable canRotate = new BindableBool(); - private readonly Bindable canScale = new BindableBool(); + private readonly AggregateBindable canRotate = new AggregateBindable((x, y) => x || y); + private readonly AggregateBindable canScale = new AggregateBindable((x, y) => x || y); private EditorToolButton rotateButton = null!; private EditorToolButton scaleButton = null!; @@ -63,37 +63,17 @@ namespace osu.Game.Rulesets.Osu.Edit { base.LoadComplete(); - // aggregate two values into canRotate - canRotatePlayfieldOrigin = RotationHandler.CanRotateAroundPlayfieldOrigin.GetBoundCopy(); - canRotatePlayfieldOrigin.BindValueChanged(_ => updateCanRotateAggregate()); + canRotate.AddSource(RotationHandler.CanRotateAroundPlayfieldOrigin); + canRotate.AddSource(RotationHandler.CanRotateAroundSelectionOrigin); - canRotateSelectionOrigin = RotationHandler.CanRotateAroundSelectionOrigin.GetBoundCopy(); - canRotateSelectionOrigin.BindValueChanged(_ => updateCanRotateAggregate()); - - void updateCanRotateAggregate() - { - canRotate.Value = RotationHandler.CanRotateAroundPlayfieldOrigin.Value || RotationHandler.CanRotateAroundSelectionOrigin.Value; - } - - // aggregate three values into canScale - canScaleX = ScaleHandler.CanScaleX.GetBoundCopy(); - canScaleX.BindValueChanged(_ => updateCanScaleAggregate()); - - canScaleY = ScaleHandler.CanScaleY.GetBoundCopy(); - canScaleY.BindValueChanged(_ => updateCanScaleAggregate()); - - canScalePlayfieldOrigin = ScaleHandler.CanScaleFromPlayfieldOrigin.GetBoundCopy(); - canScalePlayfieldOrigin.BindValueChanged(_ => updateCanScaleAggregate()); - - void updateCanScaleAggregate() - { - canScale.Value = ScaleHandler.CanScaleX.Value || ScaleHandler.CanScaleY.Value || ScaleHandler.CanScaleFromPlayfieldOrigin.Value; - } + canScale.AddSource(ScaleHandler.CanScaleX); + canScale.AddSource(ScaleHandler.CanScaleY); + canScale.AddSource(ScaleHandler.CanScaleFromPlayfieldOrigin); // bindings to `Enabled` on the buttons are decoupled on purpose // due to the weird `OsuButton` behaviour of resetting `Enabled` to `false` when `Action` is set. - canRotate.BindValueChanged(_ => rotateButton.Enabled.Value = canRotate.Value, true); - canScale.BindValueChanged(_ => scaleButton.Enabled.Value = canScale.Value, true); + canRotate.Result.BindValueChanged(rotate => rotateButton.Enabled.Value = rotate.NewValue, true); + canScale.Result.BindValueChanged(scale => scaleButton.Enabled.Value = scale.NewValue, true); } public bool OnPressed(KeyBindingPressEvent e) From 96a8bdf92064f04c00120a3274b451cf9ebd1ea9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 29 May 2024 09:59:19 +0200 Subject: [PATCH 4056/4092] Use more generic tooltip copy --- osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index b7202b9310..ac1b40e235 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -99,11 +99,11 @@ namespace osu.Game.Rulesets.Osu.Edit }; playfieldCentreButton.Selected.DisabledChanged += isDisabled => { - playfieldCentreButton.TooltipText = isDisabled ? "Select something other than a single slider to perform playfield-based scaling." : string.Empty; + playfieldCentreButton.TooltipText = isDisabled ? "The current selection cannot be scaled relative to playfield centre." : string.Empty; }; selectionCentreButton.Selected.DisabledChanged += isDisabled => { - selectionCentreButton.TooltipText = isDisabled ? "Select more than one object to perform selection-based scaling." : string.Empty; + selectionCentreButton.TooltipText = isDisabled ? "The current selection cannot be scaled relative to its centre." : string.Empty; }; } From ba4073735649eaf4a581ce3f0ae1f566fab38ddc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 29 May 2024 10:01:04 +0200 Subject: [PATCH 4057/4092] Simplify logic --- osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index ac1b40e235..d3e1b491b0 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -124,10 +124,7 @@ namespace osu.Game.Rulesets.Osu.Edit selectionCentreButton.Selected.Disabled = !(scaleHandler.CanScaleX.Value || scaleHandler.CanScaleY.Value); playfieldCentreButton.Selected.Disabled = scaleHandler.IsScalingSlider.Value && !selectionCentreButton.Selected.Disabled; - if (playfieldCentreButton.Selected.Disabled) - scaleOrigin.Items.Last().Select(); - else - scaleOrigin.Items.First().Select(); + scaleOrigin.Items.First(b => !b.Selected.Disabled).Select(); scaleInfo.BindValueChanged(scale => { From 9bd4b0d61303fb0dde3892d51b595174a61a2b4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 29 May 2024 10:04:49 +0200 Subject: [PATCH 4058/4092] Rename method --- .../Edit/PreciseScalePopover.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index d3e1b491b0..a299eebbce 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -137,21 +137,23 @@ namespace osu.Game.Rulesets.Osu.Edit { if (scaleInfo.Value.Origin == ScaleOrigin.PlayfieldCentre) { - setBindableEnabled(true, xCheckBox.Current); - setBindableEnabled(true, yCheckBox.Current); + toggleAxisAvailable(xCheckBox.Current, true); + toggleAxisAvailable(yCheckBox.Current, true); } else { - setBindableEnabled(scaleHandler.CanScaleX.Value, xCheckBox.Current); - setBindableEnabled(scaleHandler.CanScaleY.Value, yCheckBox.Current); + toggleAxisAvailable(xCheckBox.Current, scaleHandler.CanScaleX.Value); + toggleAxisAvailable(yCheckBox.Current, scaleHandler.CanScaleY.Value); } } - private void setBindableEnabled(bool enabled, Bindable current) + private void toggleAxisAvailable(Bindable axisBindable, bool available) { - current.Disabled = false; // enable the bindable to allow setting the value - current.Value = enabled; - current.Disabled = !enabled; + // enable the bindable to allow setting the value + axisBindable.Disabled = false; + // restore the presumed default value given the axis's new availability state + axisBindable.Value = available; + axisBindable.Disabled = !available; } private void updateMaxScale() From 9477e3b67de6f33a6866d9761b14a2cda20785b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 29 May 2024 10:14:47 +0200 Subject: [PATCH 4059/4092] Change editor scale hotkey to Ctrl-E Forgot that Ctrl-T was taken by the game-global toolbar already, so it wasn't working. --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index fd8e6fd6d0..2af564d8ba 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -142,7 +142,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.MouseWheelRight }, GlobalAction.EditorCyclePreviousBeatSnapDivisor), new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.MouseWheelLeft }, GlobalAction.EditorCycleNextBeatSnapDivisor), new KeyBinding(new[] { InputKey.Control, InputKey.R }, GlobalAction.EditorToggleRotateControl), - new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.EditorToggleScaleControl), + new KeyBinding(new[] { InputKey.Control, InputKey.E }, GlobalAction.EditorToggleScaleControl), }; private static IEnumerable inGameKeyBindings => new[] From 84513343d6789cf38bd2d8e355a233e277382f19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 29 May 2024 10:18:22 +0200 Subject: [PATCH 4060/4092] Remove unused fields --- osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs index 278d38b2d9..28d0f8320f 100644 --- a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs @@ -24,13 +24,6 @@ namespace osu.Game.Rulesets.Osu.Edit private EditorToolButton rotateButton = null!; private EditorToolButton scaleButton = null!; - private Bindable canRotatePlayfieldOrigin = null!; - private Bindable canRotateSelectionOrigin = null!; - - private Bindable canScaleX = null!; - private Bindable canScaleY = null!; - private Bindable canScalePlayfieldOrigin = null!; - public SelectionRotationHandler RotationHandler { get; init; } = null!; public OsuSelectionScaleHandler ScaleHandler { get; init; } = null!; From 22a2adb5e6ca899487d0a09f3edbaade37c6338b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 29 May 2024 10:57:30 +0200 Subject: [PATCH 4061/4092] Revert unrelated changes --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index 0c7ba180f2..73c061afbd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public const float DEFAULT_TICK_SIZE = 16; - public DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject; + protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject; private SkinnableDrawable scaleContainer; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 248f40208a..cc3ffd376e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -248,8 +248,6 @@ namespace osu.Game.Rulesets.Osu.Objects if (TailCircle != null) TailCircle.Position = EndPosition; - - // Positions of other nested hitobjects are not updated } protected void UpdateNestedSamples() From a6c776dac891203fa591426dc4c086eefcdf499c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 29 May 2024 11:11:43 +0200 Subject: [PATCH 4062/4092] Use hopefully safer implementation of anchoring judgements to objects --- .../TestSceneDrawableJudgement.cs | 2 +- .../Objects/Drawables/DrawableOsuJudgement.cs | 14 +++---------- .../Rulesets/Judgements/DrawableJudgement.cs | 20 ++++++++++++------- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs index 5f5596cbb3..a239f671af 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs @@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Osu.Tests private partial class TestDrawableOsuJudgement : DrawableOsuJudgement { public new SkinnableSprite Lighting => base.Lighting; - public new SkinnableDrawable JudgementBody => base.JudgementBody; + public new SkinnableDrawable? JudgementBody => base.JudgementBody; } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index ffbf45291f..98fb99609a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.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.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Configuration; @@ -14,10 +12,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public partial class DrawableOsuJudgement : DrawableJudgement { - internal SkinnableLighting Lighting { get; private set; } + internal SkinnableLighting Lighting { get; private set; } = null!; [Resolved] - private OsuConfigManager config { get; set; } + private OsuConfigManager config { get; set; } = null!; [BackgroundDependencyLoader] private void load() @@ -38,19 +36,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Lighting.ResetAnimation(); Lighting.SetColourFrom(JudgedObject, Result); - - if (JudgedObject is DrawableOsuHitObject osuObject) - { - Position = osuObject.ToSpaceOfOtherDrawable(osuObject.OriginPosition, Parent!); - Scale = new Vector2(osuObject.HitObject.Scale); - } } protected override void Update() { base.Update(); - if (JudgedObject is DrawableOsuHitObject osuObject && Parent != null && osuObject.HitObject != null) + if (JudgedObject is DrawableOsuHitObject osuObject && JudgedObject.IsInUse) { Position = osuObject.ToSpaceOfOtherDrawable(osuObject.OriginPosition, Parent!); Scale = new Vector2(osuObject.HitObject.Scale); diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index b4686c52f3..37a9766b71 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -1,11 +1,8 @@ // 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.Diagnostics; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -24,13 +21,13 @@ namespace osu.Game.Rulesets.Judgements { private const float judgement_size = 128; - public JudgementResult Result { get; private set; } + public JudgementResult? Result { get; private set; } - public DrawableHitObject JudgedObject { get; private set; } + public DrawableHitObject? JudgedObject { get; private set; } public override bool RemoveCompletedTransforms => false; - protected SkinnableDrawable JudgementBody { get; private set; } + protected SkinnableDrawable? JudgementBody { get; private set; } private readonly Container aboveHitObjectsContent; @@ -97,12 +94,19 @@ namespace osu.Game.Rulesets.Judgements /// /// The applicable judgement. /// The drawable object. - public void Apply([NotNull] JudgementResult result, [CanBeNull] DrawableHitObject judgedObject) + public void Apply(JudgementResult result, DrawableHitObject? judgedObject) { Result = result; JudgedObject = judgedObject; } + protected override void FreeAfterUse() + { + base.FreeAfterUse(); + + JudgedObject = null; + } + protected override void PrepareForUse() { base.PrepareForUse(); @@ -121,6 +125,8 @@ namespace osu.Game.Rulesets.Judgements ApplyTransformsAt(double.MinValue, true); ClearTransforms(true); + Debug.Assert(Result != null && JudgementBody != null); + LifetimeStart = Result.TimeAbsolute; using (BeginAbsoluteSequence(Result.TimeAbsolute)) From cc136556175b4c2e854aca51d3aebe35f0e5069f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 29 May 2024 13:31:15 +0200 Subject: [PATCH 4063/4092] Derive API response version from game version (Or local date, in the case of non-deployed builds). Came up when I was looking at https://github.com/ppy/osu-web/pull/11240 and found that we were still hardcoding this. Thankfully, this *should not* cause issues, since there don't seem to be any (documented or undocumented) API response version checks for versions newer than 20220705 in osu-web master. For clarity and possible debugging needs, the API response version is also logged. --- osu.Game/Online/API/APIAccess.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index d3707fe74d..6f84e98d2c 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -42,7 +42,7 @@ namespace osu.Game.Online.API public string WebsiteRootUrl { get; } - public int APIVersion => 20220705; // We may want to pull this from the game version eventually. + public int APIVersion { get; } public Exception LastLoginError { get; private set; } @@ -84,12 +84,23 @@ namespace osu.Game.Online.API this.config = config; this.versionHash = versionHash; + if (game.IsDeployedBuild) + APIVersion = game.AssemblyVersion.Major * 10000 + game.AssemblyVersion.Minor; + else + { + var now = DateTimeOffset.Now; + APIVersion = now.Year * 10000 + now.Month * 100 + now.Day; + } + APIEndpointUrl = endpointConfiguration.APIEndpointUrl; WebsiteRootUrl = endpointConfiguration.WebsiteRootUrl; NotificationsClient = setUpNotificationsClient(); authentication = new OAuth(endpointConfiguration.APIClientID, endpointConfiguration.APIClientSecret, APIEndpointUrl); + log = Logger.GetLogger(LoggingTarget.Network); + log.Add($@"API endpoint root: {APIEndpointUrl}"); + log.Add($@"API request version: {APIVersion}"); ProvidedUsername = config.Get(OsuSetting.Username); From ab01fa6d4572af4239527c0090ae438f549f55c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 29 May 2024 13:34:12 +0200 Subject: [PATCH 4064/4092] Add xmldoc to `APIAccess.APIVersion` --- osu.Game/Online/API/APIAccess.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 6f84e98d2c..923f841bd8 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -42,6 +42,10 @@ namespace osu.Game.Online.API public string WebsiteRootUrl { get; } + /// + /// The API response version. + /// See: https://osu.ppy.sh/docs/index.html#api-versions + /// public int APIVersion { get; } public Exception LastLoginError { get; private set; } From 8916f08f8620d38b308f211006e6c75535138342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 30 May 2024 09:03:02 +0200 Subject: [PATCH 4065/4092] Only take initial judgement position from object instead of following Looks less bad with mods like depth active. Co-authored-by: Dean Herbert --- .../Objects/Drawables/DrawableOsuJudgement.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 98fb99609a..6e252a53e2 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -17,6 +17,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables [Resolved] private OsuConfigManager config { get; set; } = null!; + private bool positionTransferred; + [BackgroundDependencyLoader] private void load() { @@ -36,16 +38,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Lighting.ResetAnimation(); Lighting.SetColourFrom(JudgedObject, Result); + + positionTransferred = false; } protected override void Update() { base.Update(); - if (JudgedObject is DrawableOsuHitObject osuObject && JudgedObject.IsInUse) + if (!positionTransferred && JudgedObject is DrawableOsuHitObject osuObject && JudgedObject.IsInUse) { Position = osuObject.ToSpaceOfOtherDrawable(osuObject.OriginPosition, Parent!); Scale = new Vector2(osuObject.HitObject.Scale); + + positionTransferred = true; } } From 2f2bc8e52eaa6bf2213357f8ca624e902cfcf2f4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 May 2024 17:37:55 +0900 Subject: [PATCH 4066/4092] Avoid `ChatAckRequest` failures flooding console in `OsuGameTestScene`s --- osu.Game.Tests/Chat/TestSceneChannelManager.cs | 2 +- osu.Game/Online/API/DummyAPIAccess.cs | 8 ++++++++ osu.Game/Online/API/Requests/Responses/ChatAckResponse.cs | 4 ++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Chat/TestSceneChannelManager.cs b/osu.Game.Tests/Chat/TestSceneChannelManager.cs index 95fd2669e5..ef4d4f683a 100644 --- a/osu.Game.Tests/Chat/TestSceneChannelManager.cs +++ b/osu.Game.Tests/Chat/TestSceneChannelManager.cs @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Chat return true; case ChatAckRequest ack: - ack.TriggerSuccess(new ChatAckResponse { Silences = silencedUserIds.Select(u => new ChatSilence { UserId = u }).ToList() }); + ack.TriggerSuccess(new ChatAckResponse { Silences = silencedUserIds.Select(u => new ChatSilence { UserId = u }).ToArray() }); silencedUserIds.Clear(); return true; diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 4962838bd9..8d1e986577 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.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.Threading; using System.Threading.Tasks; using osu.Framework.Bindables; @@ -84,6 +85,13 @@ namespace osu.Game.Online.API { if (HandleRequest?.Invoke(request) != true) { + // Noisy so let's silently allow these to succeed. + if (request is ChatAckRequest ack) + { + ack.TriggerSuccess(new ChatAckResponse()); + return; + } + request.Fail(new InvalidOperationException($@"{nameof(DummyAPIAccess)} cannot process this request.")); } }); diff --git a/osu.Game/Online/API/Requests/Responses/ChatAckResponse.cs b/osu.Game/Online/API/Requests/Responses/ChatAckResponse.cs index 6ed22a19b2..f68735d390 100644 --- a/osu.Game/Online/API/Requests/Responses/ChatAckResponse.cs +++ b/osu.Game/Online/API/Requests/Responses/ChatAckResponse.cs @@ -1,7 +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 System.Collections.Generic; +using System; using Newtonsoft.Json; namespace osu.Game.Online.API.Requests.Responses @@ -10,6 +10,6 @@ namespace osu.Game.Online.API.Requests.Responses public class ChatAckResponse { [JsonProperty("silences")] - public List Silences { get; set; } = null!; + public ChatSilence[] Silences { get; set; } = Array.Empty(); } } From 36d7775032c08f0983ebd2ea28960bcfb0db4968 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 May 2024 17:38:05 +0900 Subject: [PATCH 4067/4092] Fix typo in `IAPIProvider` xmldoc --- osu.Game/Online/API/IAPIProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index 66f124f7c3..7b95b68ec3 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -61,7 +61,7 @@ namespace osu.Game.Online.API string APIEndpointUrl { get; } /// - /// The root URL of of the website, excluding the trailing slash. + /// The root URL of the website, excluding the trailing slash. /// string WebsiteRootUrl { get; } From 50bd0897f653f64fa394abc490b691ac84170547 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 30 May 2024 10:38:22 +0200 Subject: [PATCH 4068/4092] Fix main menu button backgrounds not covering their entire width sometimes I thought I had fixed this already once but it still looks broken. Basically when hovering over main menu buttons every now and then it will look like their backgrounds are not covering their entire width when they expand. The removed X position set looks wrong to me when inspecting the draw visualiser with the element because the element looks to be off centre horizontally, and removing it fixes that. --- osu.Game/Screens/Menu/MainMenuButton.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Menu/MainMenuButton.cs b/osu.Game/Screens/Menu/MainMenuButton.cs index 29a661066c..4df5e6d309 100644 --- a/osu.Game/Screens/Menu/MainMenuButton.cs +++ b/osu.Game/Screens/Menu/MainMenuButton.cs @@ -115,7 +115,6 @@ namespace osu.Game.Screens.Menu backgroundContent = CreateBackground(colour).With(bg => { bg.RelativeSizeAxes = Axes.Y; - bg.X = -ButtonSystem.WEDGE_WIDTH; bg.Anchor = Anchor.Centre; bg.Origin = Anchor.Centre; }), From f3bc944ac86c7f5bd37c04cc35c0c2fb71cebfba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 May 2024 17:45:32 +0900 Subject: [PATCH 4069/4092] Remove using statement --- osu.Game/Online/API/DummyAPIAccess.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 8d1e986577..960941fc05 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using osu.Framework.Bindables; From f6a59bee9addebb75dc34b4d1253da84967b35bb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 30 May 2024 19:17:18 +0900 Subject: [PATCH 4070/4092] Use delayed resume overlay for autopilot --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 2 -- osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs | 9 ++++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index efcc728d55..b45b4fea13 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -67,8 +67,6 @@ namespace osu.Game.Rulesets.Osu.Mods // Generate the replay frames the cursor should follow replayFrames = new OsuAutoGenerator(drawableRuleset.Beatmap, drawableRuleset.Mods).Generate().Frames.Cast().ToList(); - - drawableRuleset.UseResumeOverlay = false; } } } diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index c3efd48053..f0390ad716 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -13,6 +13,7 @@ using osu.Game.Replays; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Configuration; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.UI; @@ -45,7 +46,13 @@ namespace osu.Game.Rulesets.Osu.UI public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new OsuPlayfieldAdjustmentContainer { AlignWithStoryboard = true }; - protected override ResumeOverlay CreateResumeOverlay() => new OsuResumeOverlay(); + protected override ResumeOverlay CreateResumeOverlay() + { + if (Mods.Any(m => m is OsuModAutopilot)) + return new DelayedResumeOverlay { Scale = new Vector2(0.65f) }; + + return new OsuResumeOverlay(); + } protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuFramedReplayInputHandler(replay); From 87a331fdde783dbfc3b5f395339d2d09cba1105e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 May 2024 16:34:52 +0900 Subject: [PATCH 4071/4092] Improve text on external link warning dialog --- osu.Game/Online/Chat/ExternalLinkOpener.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Chat/ExternalLinkOpener.cs b/osu.Game/Online/Chat/ExternalLinkOpener.cs index 56d24e35bb..08adf965da 100644 --- a/osu.Game/Online/Chat/ExternalLinkOpener.cs +++ b/osu.Game/Online/Chat/ExternalLinkOpener.cs @@ -10,6 +10,7 @@ using osu.Framework.Platform; using osu.Game.Configuration; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Online.Chat { @@ -44,8 +45,8 @@ namespace osu.Game.Online.Chat { public ExternalLinkDialog(string url, Action openExternalLinkAction, Action copyExternalLinkAction) { - HeaderText = "Just checking..."; - BodyText = $"You are about to leave osu! and open the following link in a web browser:\n\n{url}"; + HeaderText = "You are leaving osu!"; + BodyText = $"Are you sure you want to open the following link in a web browser:\n\n{url}"; Icon = FontAwesome.Solid.ExclamationTriangle; @@ -53,17 +54,17 @@ namespace osu.Game.Online.Chat { new PopupDialogOkButton { - Text = @"Yes. Go for it.", + Text = @"Open in browser", Action = openExternalLinkAction }, new PopupDialogCancelButton { - Text = @"Copy URL to the clipboard instead.", + Text = @"Copy URL to the clipboard", Action = copyExternalLinkAction }, new PopupDialogCancelButton { - Text = @"No! Abort mission!" + Text = CommonStrings.ButtonsCancel, }, }; } From ed64bfff8d9c13c54b27b955a706e28d3cefb134 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 May 2024 16:39:53 +0900 Subject: [PATCH 4072/4092] Bypass external link dialog for links on the trusted osu! domain --- osu.Game/OsuGame.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index af01a1b1ac..29c040c597 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -485,10 +485,19 @@ namespace osu.Game } }); - public void OpenUrlExternally(string url, bool bypassExternalUrlWarning = false) => waitForReady(() => externalLinkOpener, _ => + public void OpenUrlExternally(string url, bool forceBypassExternalUrlWarning = false) => waitForReady(() => externalLinkOpener, _ => { + bool isTrustedDomain; + if (url.StartsWith('/')) - url = $"{API.APIEndpointUrl}{url}"; + { + url = $"{API.WebsiteRootUrl}{url}"; + isTrustedDomain = true; + } + else + { + isTrustedDomain = url.StartsWith(API.APIEndpointUrl, StringComparison.Ordinal); + } if (!url.CheckIsValidUrl()) { @@ -500,7 +509,7 @@ namespace osu.Game return; } - externalLinkOpener.OpenUrlExternally(url, bypassExternalUrlWarning); + externalLinkOpener.OpenUrlExternally(url, forceBypassExternalUrlWarning || isTrustedDomain); }); /// From 53b7c29488f6dd28ae30877e0df148d4c6be6d33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 May 2024 17:35:42 +0900 Subject: [PATCH 4073/4092] Add test coverage of menu banner link opening --- .../Visual/Menus/TestSceneMainMenu.cs | 49 ++++++++++++++++++- osu.Game/Screens/Menu/OnlineMenuBanner.cs | 5 ++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs index e2a841d79a..240421b360 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; using osu.Game.Screens.Menu; using osuTK.Input; @@ -15,8 +16,14 @@ namespace osu.Game.Tests.Visual.Menus { private OnlineMenuBanner onlineMenuBanner => Game.ChildrenOfType().Single(); + public override void SetUpSteps() + { + base.SetUpSteps(); + AddStep("don't fetch online content", () => onlineMenuBanner.FetchOnlineContent = false); + } + [Test] - public void TestOnlineMenuBanner() + public void TestOnlineMenuBannerTrusted() { AddStep("set online content", () => onlineMenuBanner.Current.Value = new APIMenuContent { @@ -25,13 +32,51 @@ namespace osu.Game.Tests.Visual.Menus new APIMenuImage { Image = @"https://assets.ppy.sh/main-menu/project-loved-2@2x.png", - Url = @"https://osu.ppy.sh/home/news/2023-12-21-project-loved-december-2023", + Url = $@"{API.WebsiteRootUrl}/home/news/2023-12-21-project-loved-december-2023", } } }); AddAssert("system title not visible", () => onlineMenuBanner.State.Value, () => Is.EqualTo(Visibility.Hidden)); AddStep("enter menu", () => InputManager.Key(Key.Enter)); AddUntilStep("system title visible", () => onlineMenuBanner.State.Value, () => Is.EqualTo(Visibility.Visible)); + AddUntilStep("image loaded", () => onlineMenuBanner.ChildrenOfType().FirstOrDefault()?.IsLoaded, () => Is.True); + + AddStep("click banner", () => + { + InputManager.MoveMouseTo(onlineMenuBanner); + InputManager.Click(MouseButton.Left); + }); + + // Might not catch every occurrence due to async nature, but works in manual testing and saves annoying test setup. + AddAssert("no dialog", () => Game.ChildrenOfType().SingleOrDefault()?.CurrentDialog == null); + } + + [Test] + public void TestOnlineMenuBannerUntrustedDomain() + { + AddStep("set online content", () => onlineMenuBanner.Current.Value = new APIMenuContent + { + Images = new[] + { + new APIMenuImage + { + Image = @"https://assets.ppy.sh/main-menu/project-loved-2@2x.png", + Url = @"https://google.com", + } + } + }); + AddAssert("system title not visible", () => onlineMenuBanner.State.Value, () => Is.EqualTo(Visibility.Hidden)); + AddStep("enter menu", () => InputManager.Key(Key.Enter)); + AddUntilStep("system title visible", () => onlineMenuBanner.State.Value, () => Is.EqualTo(Visibility.Visible)); + AddUntilStep("image loaded", () => onlineMenuBanner.ChildrenOfType().FirstOrDefault()?.IsLoaded, () => Is.True); + + AddStep("click banner", () => + { + InputManager.MoveMouseTo(onlineMenuBanner); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("wait for dialog", () => Game.ChildrenOfType().SingleOrDefault()?.CurrentDialog != null); } } } diff --git a/osu.Game/Screens/Menu/OnlineMenuBanner.cs b/osu.Game/Screens/Menu/OnlineMenuBanner.cs index b9d269c82a..edd34d0bfb 100644 --- a/osu.Game/Screens/Menu/OnlineMenuBanner.cs +++ b/osu.Game/Screens/Menu/OnlineMenuBanner.cs @@ -73,6 +73,9 @@ namespace osu.Game.Screens.Menu Task.Run(() => request.Perform()) .ContinueWith(r => { + if (!FetchOnlineContent) + return; + if (r.IsCompletedSuccessfully) Schedule(() => Current.Value = request.ResponseObject); @@ -170,6 +173,8 @@ namespace osu.Game.Screens.Menu private Sprite flash = null!; + public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; + private ScheduledDelegate? openUrlAction; public MenuImage(APIMenuImage image) From 474ff5b99d089cd5db38d26541598735cb65b9cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 31 May 2024 10:46:30 +0900 Subject: [PATCH 4074/4092] Use question mark for more grammatical correctness Co-authored-by: Joseph Madamba --- osu.Game/Online/Chat/ExternalLinkOpener.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/ExternalLinkOpener.cs b/osu.Game/Online/Chat/ExternalLinkOpener.cs index 08adf965da..513ccd43af 100644 --- a/osu.Game/Online/Chat/ExternalLinkOpener.cs +++ b/osu.Game/Online/Chat/ExternalLinkOpener.cs @@ -46,7 +46,7 @@ namespace osu.Game.Online.Chat public ExternalLinkDialog(string url, Action openExternalLinkAction, Action copyExternalLinkAction) { HeaderText = "You are leaving osu!"; - BodyText = $"Are you sure you want to open the following link in a web browser:\n\n{url}"; + BodyText = $"Are you sure you want to open the following link in a web browser?\n\n{url}"; Icon = FontAwesome.Solid.ExclamationTriangle; From e52f524ea2ce15b6ef891900a3c7cee236f6d7cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 31 May 2024 11:44:53 +0900 Subject: [PATCH 4075/4092] Use common header text --- osu.Game/Online/Chat/ExternalLinkOpener.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Chat/ExternalLinkOpener.cs b/osu.Game/Online/Chat/ExternalLinkOpener.cs index 513ccd43af..2f046d8fd4 100644 --- a/osu.Game/Online/Chat/ExternalLinkOpener.cs +++ b/osu.Game/Online/Chat/ExternalLinkOpener.cs @@ -8,9 +8,10 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Platform; using osu.Game.Configuration; +using osu.Game.Localisation; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; -using osu.Game.Resources.Localisation.Web; +using CommonStrings = osu.Game.Resources.Localisation.Web.CommonStrings; namespace osu.Game.Online.Chat { @@ -45,7 +46,7 @@ namespace osu.Game.Online.Chat { public ExternalLinkDialog(string url, Action openExternalLinkAction, Action copyExternalLinkAction) { - HeaderText = "You are leaving osu!"; + HeaderText = DeleteConfirmationDialogStrings.HeaderText; BodyText = $"Are you sure you want to open the following link in a web browser?\n\n{url}"; Icon = FontAwesome.Solid.ExclamationTriangle; From 5dfeaa3c4afa75dbfebffbd1c7a61af0dd93b6d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 31 May 2024 11:46:32 +0900 Subject: [PATCH 4076/4092] Move dialog strings to more common class name --- ...{DeleteConfirmationDialogStrings.cs => DialogStrings.cs} | 6 +++--- osu.Game/Online/Chat/ExternalLinkOpener.cs | 2 +- osu.Game/Overlays/Dialog/DangerousActionDialog.cs | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) rename osu.Game/Localisation/{DeleteConfirmationDialogStrings.cs => DialogStrings.cs} (80%) diff --git a/osu.Game/Localisation/DeleteConfirmationDialogStrings.cs b/osu.Game/Localisation/DialogStrings.cs similarity index 80% rename from osu.Game/Localisation/DeleteConfirmationDialogStrings.cs rename to osu.Game/Localisation/DialogStrings.cs index 25997eadd3..cea543eb9f 100644 --- a/osu.Game/Localisation/DeleteConfirmationDialogStrings.cs +++ b/osu.Game/Localisation/DialogStrings.cs @@ -5,14 +5,14 @@ using osu.Framework.Localisation; namespace osu.Game.Localisation { - public static class DeleteConfirmationDialogStrings + public static class DialogStrings { - private const string prefix = @"osu.Game.Resources.Localisation.DeleteConfirmationDialog"; + private const string prefix = @"osu.Game.Resources.Localisation.DialogStrings"; /// /// "Caution" /// - public static LocalisableString HeaderText => new TranslatableString(getKey(@"header_text"), @"Caution"); + public static LocalisableString Caution => new TranslatableString(getKey(@"header_text"), @"Caution"); /// /// "Yes. Go for it." diff --git a/osu.Game/Online/Chat/ExternalLinkOpener.cs b/osu.Game/Online/Chat/ExternalLinkOpener.cs index 2f046d8fd4..82ad4215c2 100644 --- a/osu.Game/Online/Chat/ExternalLinkOpener.cs +++ b/osu.Game/Online/Chat/ExternalLinkOpener.cs @@ -46,7 +46,7 @@ namespace osu.Game.Online.Chat { public ExternalLinkDialog(string url, Action openExternalLinkAction, Action copyExternalLinkAction) { - HeaderText = DeleteConfirmationDialogStrings.HeaderText; + HeaderText = DialogStrings.Caution; BodyText = $"Are you sure you want to open the following link in a web browser?\n\n{url}"; Icon = FontAwesome.Solid.ExclamationTriangle; diff --git a/osu.Game/Overlays/Dialog/DangerousActionDialog.cs b/osu.Game/Overlays/Dialog/DangerousActionDialog.cs index 42a3ff827c..60f51e7e06 100644 --- a/osu.Game/Overlays/Dialog/DangerousActionDialog.cs +++ b/osu.Game/Overlays/Dialog/DangerousActionDialog.cs @@ -30,7 +30,7 @@ namespace osu.Game.Overlays.Dialog protected DangerousActionDialog() { - HeaderText = DeleteConfirmationDialogStrings.HeaderText; + HeaderText = DialogStrings.DialogCautionHeader; Icon = FontAwesome.Regular.TrashAlt; @@ -38,12 +38,12 @@ namespace osu.Game.Overlays.Dialog { new PopupDialogDangerousButton { - Text = DeleteConfirmationDialogStrings.Confirm, + Text = DialogStrings.DialogConfirm, Action = () => DangerousAction?.Invoke() }, new PopupDialogCancelButton { - Text = DeleteConfirmationDialogStrings.Cancel, + Text = DialogStrings.Cancel, Action = () => CancelAction?.Invoke() } }; From cb72630ce1894a3ed73f607112095c4845413349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 31 May 2024 08:09:06 +0200 Subject: [PATCH 4077/4092] Fix compile failures --- osu.Game/Overlays/Dialog/DangerousActionDialog.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Dialog/DangerousActionDialog.cs b/osu.Game/Overlays/Dialog/DangerousActionDialog.cs index 60f51e7e06..31160d1832 100644 --- a/osu.Game/Overlays/Dialog/DangerousActionDialog.cs +++ b/osu.Game/Overlays/Dialog/DangerousActionDialog.cs @@ -30,7 +30,7 @@ namespace osu.Game.Overlays.Dialog protected DangerousActionDialog() { - HeaderText = DialogStrings.DialogCautionHeader; + HeaderText = DialogStrings.Caution; Icon = FontAwesome.Regular.TrashAlt; @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.Dialog { new PopupDialogDangerousButton { - Text = DialogStrings.DialogConfirm, + Text = DialogStrings.Confirm, Action = () => DangerousAction?.Invoke() }, new PopupDialogCancelButton From 1a26a5dbdaf77256564930e69e5f20d3e9a2cd17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 31 May 2024 08:09:25 +0200 Subject: [PATCH 4078/4092] Fix mismatching localisation key prefix The `Strings` suffix is not supposed to be in here, judging by other localisation classes. --- osu.Game/Localisation/DialogStrings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Localisation/DialogStrings.cs b/osu.Game/Localisation/DialogStrings.cs index cea543eb9f..043a3f5b4c 100644 --- a/osu.Game/Localisation/DialogStrings.cs +++ b/osu.Game/Localisation/DialogStrings.cs @@ -7,7 +7,7 @@ namespace osu.Game.Localisation { public static class DialogStrings { - private const string prefix = @"osu.Game.Resources.Localisation.DialogStrings"; + private const string prefix = @"osu.Game.Resources.Localisation.Dialog"; /// /// "Caution" From 9111da81d22c2a9e35533df964cb9e5c0e691807 Mon Sep 17 00:00:00 2001 From: Aurelian Date: Fri, 31 May 2024 01:30:26 +0200 Subject: [PATCH 4079/4092] Updated comments --- osu.Game/Rulesets/Objects/SliderPath.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 730a2013b0..5550815370 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -332,16 +332,15 @@ namespace osu.Game.Rulesets.Objects CircularArcProperties circularArcProperties = new CircularArcProperties(subControlPoints); - //if false, we'll end up breaking anyways when calculating subPath + // `PathApproximator` will already internally revert to B-spline if the arc isn't valid. if (!circularArcProperties.IsValid) break; - //Coppied from PathApproximator.CircularArcToPiecewiseLinear + // taken from https://github.com/ppy/osu-framework/blob/1201e641699a1d50d2f6f9295192dad6263d5820/osu.Framework/Utils/PathApproximator.cs#L181-L186 int subPoints = (2f * circularArcProperties.Radius <= 0.1f) ? 2 : Math.Max(2, (int)Math.Ceiling(circularArcProperties.ThetaRange / (2.0 * Math.Acos(1f - (0.1f / circularArcProperties.Radius))))); - //theoretically can be int.MaxValue, but lets set this to a lower value anyways - //1000 requires an arc length of over 20 thousand to surpass this limit, which should be safe. - //See here for calculations https://www.desmos.com/calculator/210bwswkbb + // 1000 subpoints requires an arc length of at least ~120 thousand to occur + // See here for calculations https://www.desmos.com/calculator/umj6jvmcz7 if (subPoints >= 1000) break; From c6a7082034da26c6399633ad18665a85ccc58f30 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 31 May 2024 15:38:26 +0900 Subject: [PATCH 4080/4092] Fix incorrect prefix check --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 29c040c597..0833f52b1e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -496,7 +496,7 @@ namespace osu.Game } else { - isTrustedDomain = url.StartsWith(API.APIEndpointUrl, StringComparison.Ordinal); + isTrustedDomain = url.StartsWith(API.WebsiteRootUrl, StringComparison.Ordinal); } if (!url.CheckIsValidUrl()) From 69990c35cb638d57e735cedd6785fe2a06bd4341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 31 May 2024 08:47:19 +0200 Subject: [PATCH 4081/4092] Add commentary on presence of `IsPresent` override --- osu.Game/Screens/Menu/OnlineMenuBanner.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Menu/OnlineMenuBanner.cs b/osu.Game/Screens/Menu/OnlineMenuBanner.cs index edd34d0bfb..49fc89c171 100644 --- a/osu.Game/Screens/Menu/OnlineMenuBanner.cs +++ b/osu.Game/Screens/Menu/OnlineMenuBanner.cs @@ -173,6 +173,9 @@ namespace osu.Game.Screens.Menu private Sprite flash = null!; + /// + /// Overridden as a safety for functioning correctly. + /// public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; private ScheduledDelegate? openUrlAction; From e3205fce4769afd2ac3cce60f6e1321f530ec029 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 1 Jun 2024 14:29:23 +0900 Subject: [PATCH 4082/4092] Fix unable to drag-scroll on collections right-click menu --- osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs b/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs index 686c490930..b9e81e1bf2 100644 --- a/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs @@ -26,9 +26,12 @@ namespace osu.Game.Graphics.UserInterface // Right mouse button is a special case where we allow actioning without dismissing the menu. // This is achieved by not calling `Clicked` (as done by the base implementation in OnClick). if (IsActionable && e.Button == MouseButton.Right) + { Item.Action.Value?.Invoke(); + return true; + } - return true; + return false; } private partial class ToggleTextContainer : TextContainer From 091104764e677d4efecbe8958ca71b7f9cac03b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 2 Jun 2024 17:33:06 +0900 Subject: [PATCH 4083/4092] Fix `AssemblyRulesetStore` not marking rulesets as available --- osu.Game/Rulesets/AssemblyRulesetStore.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/AssemblyRulesetStore.cs b/osu.Game/Rulesets/AssemblyRulesetStore.cs index 03554ef2db..935ef241dc 100644 --- a/osu.Game/Rulesets/AssemblyRulesetStore.cs +++ b/osu.Game/Rulesets/AssemblyRulesetStore.cs @@ -43,7 +43,12 @@ namespace osu.Game.Rulesets // add all legacy rulesets first to ensure they have exclusive choice of primary key. foreach (var r in instances.Where(r => r is ILegacyRuleset)) - availableRulesets.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID)); + { + availableRulesets.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID) + { + Available = true + }); + } } } } From 96514132c1ecc5e3efabf9427741148b7e283fd1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 Jun 2024 12:24:50 +0900 Subject: [PATCH 4084/4092] Fix occasional test failures on new menu content tests Scheduled data transfer could still overwrite test data. --- osu.Game/Screens/Menu/OnlineMenuBanner.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/OnlineMenuBanner.cs b/osu.Game/Screens/Menu/OnlineMenuBanner.cs index 49fc89c171..aa73ce2136 100644 --- a/osu.Game/Screens/Menu/OnlineMenuBanner.cs +++ b/osu.Game/Screens/Menu/OnlineMenuBanner.cs @@ -77,7 +77,15 @@ namespace osu.Game.Screens.Menu return; if (r.IsCompletedSuccessfully) - Schedule(() => Current.Value = request.ResponseObject); + { + Schedule(() => + { + if (!FetchOnlineContent) + return; + + Current.Value = request.ResponseObject; + }); + } // if the request failed, "observe" the exception. // it isn't very important why this failed, as it's only for display. From f13ca28d5eae95792bb6cdac1e75f05f56d26c2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 4 Jun 2024 10:25:08 +0200 Subject: [PATCH 4085/4092] Fix performance overhead from ternary state bindable callbacks when selection is changing Closes https://github.com/ppy/osu/issues/28369. The reporter of the issue was incorrect; it's not the beat snap grid that is causing the problem, it's something far stupider than that. When the current selection changes, `EditorSelectionHandler.UpdateTernaryStates()` is supposed to update the state of ternary bindables to reflect the reality of the current selection. This in turn will fire bindable change callbacks for said ternary toggles, which heavily use `EditorBeatmap.PerformOnSelection()`. The thing about that method is that it will attempt to check whether any changes were actually made to avoid producing empty undo states, *but* to do this, it must *serialise out the entire beatmap to a stream* and then *binary equality check that* to determine whether any changes were actually made: https://github.com/ppy/osu/blob/7b14c77e43e4ee96775a9fcb6843324170fa70bb/osu.Game/Screens/Edit/EditorChangeHandler.cs#L65-L69 As goes without saying, this is very expensive and unnecessary, which leads to stuff like keeping a selection box active while a taiko beatmap is playing under it dog slow. So to attempt to mitigate that, add precondition checks to every single ternary callback of this sort to avoid this serialisation overhead. And yes, those precondition checks use linq, and that is *still* faster than not having them. --- .../Edit/TaikoSelectionHandler.cs | 6 ++++++ .../Compose/Components/EditorSelectionHandler.cs | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs index 7ab8a54b02..ae6dced9aa 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs @@ -53,6 +53,9 @@ namespace osu.Game.Rulesets.Taiko.Edit public void SetStrongState(bool state) { + if (SelectedItems.OfType().All(h => h.IsStrong == state)) + return; + EditorBeatmap.PerformOnSelection(h => { if (!(h is Hit taikoHit)) return; @@ -67,6 +70,9 @@ namespace osu.Game.Rulesets.Taiko.Edit public void SetRimState(bool state) { + if (SelectedItems.OfType().All(h => h.Type == (state ? HitType.Rim : HitType.Centre))) + return; + EditorBeatmap.PerformOnSelection(h => { if (h is Hit taikoHit) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index a73278a61e..7420362e19 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -198,6 +198,9 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The name of the sample bank. public void AddSampleBank(string bankName) { + if (SelectedItems.All(h => h.Samples.All(s => s.Bank == bankName))) + return; + EditorBeatmap.PerformOnSelection(h => { if (h.Samples.All(s => s.Bank == bankName)) @@ -214,6 +217,9 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The name of the hit sample. public void AddHitSample(string sampleName) { + if (SelectedItems.All(h => h.Samples.Any(s => s.Name == sampleName))) + return; + EditorBeatmap.PerformOnSelection(h => { // Make sure there isn't already an existing sample @@ -231,6 +237,9 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The name of the hit sample. public void RemoveHitSample(string sampleName) { + if (SelectedItems.All(h => h.Samples.All(s => s.Name != sampleName))) + return; + EditorBeatmap.PerformOnSelection(h => { h.SamplesBindable.RemoveAll(s => s.Name == sampleName); @@ -245,6 +254,9 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Throws if any selected object doesn't implement public void SetNewCombo(bool state) { + if (SelectedItems.OfType().All(h => h.NewCombo == state)) + return; + EditorBeatmap.PerformOnSelection(h => { var comboInfo = h as IHasComboInformation; From ecfcf7a2c047a589858a532e0c764b98d9fd3550 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 4 Jun 2024 10:36:30 +0200 Subject: [PATCH 4086/4092] Add xmldoc mention about performance overhead of `PerformOnSelection()` --- osu.Game/Screens/Edit/EditorBeatmap.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 7a3ea474fb..6363ed2854 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -198,6 +198,11 @@ namespace osu.Game.Screens.Edit /// Perform the provided action on every selected hitobject. /// Changes will be grouped as one history action. /// + /// + /// Note that this incurs a full state save, and as such requires the entire beatmap to be encoded, etc. + /// Very frequent use of this method (e.g. once a frame) is most discouraged. + /// If there is need to do so, use local precondition checks to eliminate changes that are known to be no-ops. + /// /// The action to perform. public void PerformOnSelection(Action action) { From 442b61da849dbca8983475803c3d4d5d1f9702a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 4 Jun 2024 15:13:50 +0200 Subject: [PATCH 4087/4092] Disable primary constructor related inspections I'm not actually sure whether the editorconfig incantation does anything but it's the one official sources give: https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0290 --- .editorconfig | 3 +++ osu.sln.DotSettings | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index c249e5e9b3..7aecde95ee 100644 --- a/.editorconfig +++ b/.editorconfig @@ -196,6 +196,9 @@ csharp_style_prefer_switch_expression = false:none csharp_style_namespace_declarations = block_scoped:warning +#Style - C# 12 features +csharp_style_prefer_primary_constructors = false + [*.{yaml,yml}] insert_final_newline = true indent_style = space diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 08eb264aab..04633a9348 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -82,7 +82,7 @@ WARNING WARNING HINT - HINT + DO_NOT_SHOW WARNING HINT DO_NOT_SHOW From 7dd18a84f6f6a29df941c5aa2e2d7da6d7a29020 Mon Sep 17 00:00:00 2001 From: Xesquim Date: Wed, 5 Jun 2024 12:25:33 -0300 Subject: [PATCH 4088/4092] Fixing the GetTotalDuration in PlaylistExtesions --- osu.Game/Online/Rooms/PlaylistExtensions.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Rooms/PlaylistExtensions.cs b/osu.Game/Online/Rooms/PlaylistExtensions.cs index cd52a3c6e6..3171716992 100644 --- a/osu.Game/Online/Rooms/PlaylistExtensions.cs +++ b/osu.Game/Online/Rooms/PlaylistExtensions.cs @@ -1,11 +1,13 @@ // 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.Collections.Generic; using System.Linq; using Humanizer; using Humanizer.Localisation; using osu.Framework.Bindables; +using osu.Game.Rulesets.Mods; namespace osu.Game.Online.Rooms { @@ -39,6 +41,17 @@ namespace osu.Game.Online.Rooms } public static string GetTotalDuration(this BindableList playlist) => - playlist.Select(p => p.Beatmap.Length).Sum().Milliseconds().Humanize(minUnit: TimeUnit.Second, maxUnit: TimeUnit.Hour, precision: 2); + playlist.Select(p => + { + var ruleset = p.Beatmap.Ruleset.CreateInstance(); + double rate = 1; + if (p.RequiredMods.Count() > 0) + { + List mods = p.RequiredMods.Select(mod => mod.ToMod(ruleset)).ToList(); + foreach (var mod in mods.OfType()) + rate = mod.ApplyToRate(0, rate); + } + return p.Beatmap.Length / rate; + }).Sum().Milliseconds().Humanize(minUnit: TimeUnit.Second, maxUnit: TimeUnit.Hour, precision: 2); } } From 860afb812367c21a88f667ad1d9431f75dab26ab Mon Sep 17 00:00:00 2001 From: Xesquim Date: Thu, 6 Jun 2024 10:06:07 -0300 Subject: [PATCH 4089/4092] Creating method in ModUtils to calculate the rate for the song --- .../Beatmaps/Drawables/DifficultyIconTooltip.cs | 9 ++------- osu.Game/Online/Rooms/PlaylistExtensions.cs | 12 +++++------- .../Overlays/Mods/BeatmapAttributesDisplay.cs | 4 +--- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 4 +--- osu.Game/Screens/Select/Details/AdvancedStats.cs | 5 ++--- osu.Game/Utils/ModUtils.cs | 16 ++++++++++++++++ 6 files changed, 27 insertions(+), 23 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index 1f3dcfee8c..a5cac69afd 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Utils; using osuTK; namespace osu.Game.Beatmaps.Drawables @@ -123,13 +124,7 @@ namespace osu.Game.Beatmaps.Drawables difficultyFillFlowContainer.Show(); miscFillFlowContainer.Show(); - double rate = 1; - - if (displayedContent.Mods != null) - { - foreach (var mod in displayedContent.Mods.OfType()) - rate = mod.ApplyToRate(0, rate); - } + double rate = ModUtils.CalculateRateWithMods(displayedContent.Mods); double bpmAdjusted = displayedContent.BeatmapInfo.BPM * rate; diff --git a/osu.Game/Online/Rooms/PlaylistExtensions.cs b/osu.Game/Online/Rooms/PlaylistExtensions.cs index 3171716992..4bcaaa8131 100644 --- a/osu.Game/Online/Rooms/PlaylistExtensions.cs +++ b/osu.Game/Online/Rooms/PlaylistExtensions.cs @@ -8,6 +8,7 @@ using Humanizer; using Humanizer.Localisation; using osu.Framework.Bindables; using osu.Game.Rulesets.Mods; +using osu.Game.Utils; namespace osu.Game.Online.Rooms { @@ -40,17 +41,14 @@ namespace osu.Game.Online.Rooms : GetUpcomingItems(playlist).First(); } + /// + /// Returns the total duration from the in playlist order from the supplied , + /// public static string GetTotalDuration(this BindableList playlist) => playlist.Select(p => { var ruleset = p.Beatmap.Ruleset.CreateInstance(); - double rate = 1; - if (p.RequiredMods.Count() > 0) - { - List mods = p.RequiredMods.Select(mod => mod.ToMod(ruleset)).ToList(); - foreach (var mod in mods.OfType()) - rate = mod.ApplyToRate(0, rate); - } + double rate = ModUtils.CalculateRateWithMods(p.RequiredMods.Select(mod => mod.ToMod(ruleset)).ToList()); return p.Beatmap.Length / rate; }).Sum().Milliseconds().Humanize(minUnit: TimeUnit.Second, maxUnit: TimeUnit.Hour, precision: 2); } diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index 5b10a2844e..b625de27f8 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -165,9 +165,7 @@ namespace osu.Game.Overlays.Mods starRatingDisplay.FinishTransforms(true); }); - double rate = 1; - foreach (var mod in Mods.Value.OfType()) - rate = mod.ApplyToRate(0, rate); + double rate = ModUtils.CalculateRateWithMods(Mods.Value.ToList()); bpmDisplay.Current.Value = FormatUtils.RoundBPM(BeatmapInfo.Value.BPM, rate); diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 3cab4b67b6..0e2d1db6b7 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -402,9 +402,7 @@ namespace osu.Game.Screens.Select return; // this doesn't consider mods which apply variable rates, yet. - double rate = 1; - foreach (var mod in mods.Value.OfType()) - rate = mod.ApplyToRate(0, rate); + double rate = ModUtils.CalculateRateWithMods(mods.Value.ToList()); int bpmMax = FormatUtils.RoundBPM(beatmap.ControlPointInfo.BPMMaximum, rate); int bpmMin = FormatUtils.RoundBPM(beatmap.ControlPointInfo.BPMMinimum, rate); diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index cb820f4da9..1da890100e 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -27,6 +27,7 @@ using osu.Game.Configuration; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; using osu.Game.Overlays.Mods; +using osu.Game.Utils; namespace osu.Game.Screens.Select.Details { @@ -179,9 +180,7 @@ namespace osu.Game.Screens.Select.Details if (Ruleset.Value != null) { - double rate = 1; - foreach (var mod in mods.Value.OfType()) - rate = mod.ApplyToRate(0, rate); + double rate = ModUtils.CalculateRateWithMods(mods.Value); adjustedDifficulty = Ruleset.Value.CreateInstance().GetRateAdjustedDisplayDifficulty(originalDifficulty, rate); diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index 2c9eef41e3..3378e94ec0 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -276,5 +276,21 @@ namespace osu.Game.Utils return scoreMultiplier.ToLocalisableString("0.00x"); } + + /// + /// Calculate the rate for the song with the selected mods. + /// + /// The list of selected mods. + /// The rate with mods. + public static double CalculateRateWithMods(IEnumerable mods) + { + double rate = 1; + if (mods != null) + { + foreach (var mod in mods.OfType()) + rate = mod.ApplyToRate(0, rate); + } + return rate; + } } } From dd3f4bcdab623c3c7563d7cc191dc060cc518d37 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Jun 2024 23:59:15 +0800 Subject: [PATCH 4090/4092] Fix code quality and null handling --- osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs | 4 +++- osu.Game/Online/Rooms/PlaylistExtensions.cs | 4 +--- osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs | 2 +- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 2 +- osu.Game/Utils/ModUtils.cs | 9 ++++----- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index a5cac69afd..36ddb6030e 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -124,7 +124,9 @@ namespace osu.Game.Beatmaps.Drawables difficultyFillFlowContainer.Show(); miscFillFlowContainer.Show(); - double rate = ModUtils.CalculateRateWithMods(displayedContent.Mods); + double rate = 1; + if (displayedContent.Mods != null) + rate = ModUtils.CalculateRateWithMods(displayedContent.Mods); double bpmAdjusted = displayedContent.BeatmapInfo.BPM * rate; diff --git a/osu.Game/Online/Rooms/PlaylistExtensions.cs b/osu.Game/Online/Rooms/PlaylistExtensions.cs index 4bcaaa8131..003fd23d40 100644 --- a/osu.Game/Online/Rooms/PlaylistExtensions.cs +++ b/osu.Game/Online/Rooms/PlaylistExtensions.cs @@ -1,13 +1,11 @@ // 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.Collections.Generic; using System.Linq; using Humanizer; using Humanizer.Localisation; using osu.Framework.Bindables; -using osu.Game.Rulesets.Mods; using osu.Game.Utils; namespace osu.Game.Online.Rooms @@ -48,7 +46,7 @@ namespace osu.Game.Online.Rooms playlist.Select(p => { var ruleset = p.Beatmap.Ruleset.CreateInstance(); - double rate = ModUtils.CalculateRateWithMods(p.RequiredMods.Select(mod => mod.ToMod(ruleset)).ToList()); + double rate = ModUtils.CalculateRateWithMods(p.RequiredMods.Select(mod => mod.ToMod(ruleset))); return p.Beatmap.Length / rate; }).Sum().Milliseconds().Humanize(minUnit: TimeUnit.Second, maxUnit: TimeUnit.Hour, precision: 2); } diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index b625de27f8..1f4e007f47 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -165,7 +165,7 @@ namespace osu.Game.Overlays.Mods starRatingDisplay.FinishTransforms(true); }); - double rate = ModUtils.CalculateRateWithMods(Mods.Value.ToList()); + double rate = ModUtils.CalculateRateWithMods(Mods.Value); bpmDisplay.Current.Value = FormatUtils.RoundBPM(BeatmapInfo.Value.BPM, rate); diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 0e2d1db6b7..02682c1851 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -402,7 +402,7 @@ namespace osu.Game.Screens.Select return; // this doesn't consider mods which apply variable rates, yet. - double rate = ModUtils.CalculateRateWithMods(mods.Value.ToList()); + double rate = ModUtils.CalculateRateWithMods(mods.Value); int bpmMax = FormatUtils.RoundBPM(beatmap.ControlPointInfo.BPMMaximum, rate); int bpmMin = FormatUtils.RoundBPM(beatmap.ControlPointInfo.BPMMinimum, rate); diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index 3378e94ec0..f901f15388 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -285,11 +285,10 @@ namespace osu.Game.Utils public static double CalculateRateWithMods(IEnumerable mods) { double rate = 1; - if (mods != null) - { - foreach (var mod in mods.OfType()) - rate = mod.ApplyToRate(0, rate); - } + + foreach (var mod in mods.OfType()) + rate = mod.ApplyToRate(0, rate); + return rate; } } From 6e3bea938e1ecfe255bc4dcfe558870007e8cb6e Mon Sep 17 00:00:00 2001 From: Xesquim Date: Thu, 6 Jun 2024 13:26:52 -0300 Subject: [PATCH 4091/4092] Instancing a Ruleset only when it's necessary to --- osu.Game/Online/Rooms/PlaylistExtensions.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Rooms/PlaylistExtensions.cs b/osu.Game/Online/Rooms/PlaylistExtensions.cs index 003fd23d40..5a5950333b 100644 --- a/osu.Game/Online/Rooms/PlaylistExtensions.cs +++ b/osu.Game/Online/Rooms/PlaylistExtensions.cs @@ -6,6 +6,7 @@ using System.Linq; using Humanizer; using Humanizer.Localisation; using osu.Framework.Bindables; +using osu.Game.Rulesets.Mods; using osu.Game.Utils; namespace osu.Game.Online.Rooms @@ -45,8 +46,13 @@ namespace osu.Game.Online.Rooms public static string GetTotalDuration(this BindableList playlist) => playlist.Select(p => { - var ruleset = p.Beatmap.Ruleset.CreateInstance(); - double rate = ModUtils.CalculateRateWithMods(p.RequiredMods.Select(mod => mod.ToMod(ruleset))); + IEnumerable modList = []; + if (p.RequiredMods.Length > 0) + { + var ruleset = p.Beatmap.Ruleset.CreateInstance(); + modList = p.RequiredMods.Select(mod => mod.ToMod(ruleset)); + } + double rate = ModUtils.CalculateRateWithMods(modList); return p.Beatmap.Length / rate; }).Sum().Milliseconds().Humanize(minUnit: TimeUnit.Second, maxUnit: TimeUnit.Hour, precision: 2); } From 7cbe93efc3dcef9389922f034b12a92c6777aca7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Jun 2024 10:37:27 +0800 Subject: [PATCH 4092/4092] Refactor latest changes to avoid unnecessary call when mods not present --- osu.Game/Online/Rooms/PlaylistExtensions.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Rooms/PlaylistExtensions.cs b/osu.Game/Online/Rooms/PlaylistExtensions.cs index 5a5950333b..e9a0519f3d 100644 --- a/osu.Game/Online/Rooms/PlaylistExtensions.cs +++ b/osu.Game/Online/Rooms/PlaylistExtensions.cs @@ -6,7 +6,6 @@ using System.Linq; using Humanizer; using Humanizer.Localisation; using osu.Framework.Bindables; -using osu.Game.Rulesets.Mods; using osu.Game.Utils; namespace osu.Game.Online.Rooms @@ -46,13 +45,14 @@ namespace osu.Game.Online.Rooms public static string GetTotalDuration(this BindableList playlist) => playlist.Select(p => { - IEnumerable modList = []; + double rate = 1; + if (p.RequiredMods.Length > 0) { var ruleset = p.Beatmap.Ruleset.CreateInstance(); - modList = p.RequiredMods.Select(mod => mod.ToMod(ruleset)); + rate = ModUtils.CalculateRateWithMods(p.RequiredMods.Select(mod => mod.ToMod(ruleset))); } - double rate = ModUtils.CalculateRateWithMods(modList); + return p.Beatmap.Length / rate; }).Sum().Milliseconds().Humanize(minUnit: TimeUnit.Second, maxUnit: TimeUnit.Hour, precision: 2); }

EM!on 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